import type { MessageBlock } from "@rakazo/contracts"; import { type ProseToolCall, sanitizeBotReplyText } from "@rakazo/core"; import type { ExecutorDeps } from "./executor.js"; import { handoffToGroupBot } from "./group-handoff.js"; export const INBOUND_HANDOFF_WORKSHOP_FALLBACK = "Ich übernehme das direkt in meiner Spur — gleich skizziere ich erste Richtungen und stelle dir gezielte Fragen."; export const INBOUND_HANDOFF_FAILURE_FALLBACK = "Die Übergabe ist gerade fehlgeschlagen — ich bleibe dabei und mache hier mit dir weiter."; type HandoffRun = { id: string; spaceId: string; threadId: string; botId: string; userId: string; }; export function sanitizeTextBlocks(blocks: readonly MessageBlock[]): MessageBlock[] { const next: MessageBlock[] = []; for (const block of blocks) { if (block.kind !== "text") { next.push(block); continue; } const sanitized = sanitizeBotReplyText(block.text); if (sanitized.text) next.push({ kind: "text", text: sanitized.text }); } return next; } export function proseHandoffArgs(call: ProseToolCall): { bot_id?: string; confirm_name?: string; message: string; } { const params = call.parameters; return { bot_id: params.bot_id != null ? String(params.bot_id) : undefined, confirm_name: params.confirm_name != null ? String(params.confirm_name) : undefined, message: String(params.message ?? ""), }; } export async function tryRecoverProseHandoff( deps: Pick, run: HandoffRun, groupId: string | null | undefined, text: string, handedOff: boolean, ): Promise<{ handedOff: boolean; sanitizedText: string; blockedUpward: boolean; handoffFailed: boolean; proseToolCalls: ProseToolCall[]; }> { const sanitized = sanitizeBotReplyText(text); if (handedOff || !groupId || sanitized.proseToolCalls.length === 0) { return { handedOff, sanitizedText: sanitized.text, blockedUpward: false, handoffFailed: false, proseToolCalls: sanitized.proseToolCalls, }; } let nextHandedOff: boolean = handedOff; let blockedUpward = false; let handoffFailed = false; for (const call of sanitized.proseToolCalls) { if (call.name !== "handoff_to_bot") continue; const args = proseHandoffArgs(call); if (!args.message.trim()) continue; const result = await handoffToGroupBot(deps, run, groupId, args); if ("ok" in result && result.ok) { nextHandedOff = true; continue; } if ("error" in result) { if ( result.error.includes("sender") || result.error.includes("you own this handed-off stage") ) { blockedUpward = true; } else { handoffFailed = true; } } } return { handedOff: nextHandedOff, sanitizedText: sanitized.text, blockedUpward, handoffFailed, proseToolCalls: sanitized.proseToolCalls, }; } export function workshopFallbackAfterBlockedHandoff( sanitizedText: string, blockedUpward: boolean, handoffFailed: boolean, ): string | undefined { if (sanitizedText.trim()) return undefined; if (blockedUpward) return INBOUND_HANDOFF_WORKSHOP_FALLBACK; if (handoffFailed) return INBOUND_HANDOFF_FAILURE_FALLBACK; return undefined; }