113 lines
3.2 KiB
TypeScript
113 lines
3.2 KiB
TypeScript
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<ExecutorDeps, "prisma" | "events" | "jobs">,
|
|
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;
|
|
}
|