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.
74 lines
2.4 KiB
TypeScript
74 lines
2.4 KiB
TypeScript
import type { ThreadSnapshot } from "@rakazo/contracts";
|
|
import type { BotPersonaProfile } from "./bot-persona";
|
|
|
|
/** UX/UI/product-design specialists — client workshop Voice flow, not executive handoff. */
|
|
export function isClientWorkshopSpecialty(input: {
|
|
name: string;
|
|
title: string;
|
|
description: string;
|
|
}): boolean {
|
|
const haystack = `${input.name} ${input.title} ${input.description}`.toLowerCase();
|
|
return (
|
|
haystack.includes("ux designer") ||
|
|
haystack.includes("ux/ui") ||
|
|
haystack.includes("ui designer") ||
|
|
haystack.includes("product design") ||
|
|
haystack.includes("voice first") ||
|
|
(haystack.includes("design") &&
|
|
(haystack.includes("lead") || haystack.includes("product") || haystack.includes("ux")))
|
|
);
|
|
}
|
|
|
|
export interface OutgoingHandoff {
|
|
messageId: string;
|
|
toBotId: string;
|
|
toBotName?: string;
|
|
text: string;
|
|
}
|
|
|
|
/** Most recent handoff this bot initiated (fromBotId === botId). */
|
|
export function extractOutgoingHandoff(
|
|
snapshot: ThreadSnapshot | null,
|
|
botId: string,
|
|
): OutgoingHandoff | null {
|
|
if (!snapshot) return null;
|
|
const members = snapshot.members ?? [];
|
|
for (const message of [...snapshot.messages].reverse()) {
|
|
for (let index = message.blocks.length - 1; index >= 0; index -= 1) {
|
|
const block = message.blocks[index];
|
|
if (block?.kind !== "handoff" || block.fromBotId !== botId) continue;
|
|
const member = members.find((entry) => entry.botId === block.toBotId);
|
|
return {
|
|
messageId: message.id,
|
|
toBotId: block.toBotId,
|
|
toBotName: member?.name,
|
|
text: block.text.trim(),
|
|
};
|
|
}
|
|
}
|
|
return null;
|
|
}
|
|
|
|
/** Short spoken line when Voice opens on a bot that just handed work off — no task brief. */
|
|
export function voiceHandoffTransferLine(
|
|
profile: BotPersonaProfile,
|
|
targetName: string | undefined,
|
|
): string {
|
|
const target = targetName?.trim() || "a specialist";
|
|
switch (profile.archetype) {
|
|
case "cto-executive":
|
|
return `This goes to ${target} now. Hang up to continue in text.`;
|
|
case "chief-operator":
|
|
return `Transferred to ${target}. Hang up to continue in text.`;
|
|
default:
|
|
return `Handed to ${target}. Hang up to continue in text.`;
|
|
}
|
|
}
|
|
|
|
export function voiceHandoffIdleCaption(targetName: string | undefined): string {
|
|
const target = targetName?.trim();
|
|
return target
|
|
? `Handed to ${target} — hang up to continue in text.`
|
|
: `Handed to a specialist — hang up to continue in text.`;
|
|
}
|