feat: add scoped context payload and rollbackable document changes

This commit is contained in:
Ruslan Bakiev
2026-02-21 16:27:09 +07:00
parent 052f37d0ec
commit fa1231df37
5 changed files with 678 additions and 13 deletions

View File

@@ -4,6 +4,7 @@ import { getAuthContext } from "../utils/auth";
import { prisma } from "../utils/prisma";
import { buildChangeSet, captureSnapshot } from "../utils/changeSet";
import { persistChatMessage, runCrmAgentFor, type AgentTraceEvent } from "../agent/crmAgent";
import type { PilotContextPayload } from "../agent/crmAgent";
import type { ChangeSet } from "../utils/changeSet";
function extractMessageText(message: any): string {
@@ -25,6 +26,69 @@ function getLastUserText(messages: any[]): string {
return "";
}
function sanitizeContextPayload(raw: unknown): PilotContextPayload | null {
if (!raw || typeof raw !== "object") return null;
const item = raw as Record<string, any>;
const scopesRaw = Array.isArray(item.scopes) ? item.scopes : [];
const scopes = scopesRaw
.map((scope) => String(scope))
.filter((scope) => scope === "summary" || scope === "deal" || scope === "message" || scope === "calendar") as PilotContextPayload["scopes"];
if (!scopes.length) return null;
const payload: PilotContextPayload = { scopes };
if (item.summary && typeof item.summary === "object") {
const contactId = String(item.summary.contactId ?? "").trim();
const name = String(item.summary.name ?? "").trim();
if (contactId && name) payload.summary = { contactId, name };
}
if (item.deal && typeof item.deal === "object") {
const dealId = String(item.deal.dealId ?? "").trim();
const title = String(item.deal.title ?? "").trim();
const contact = String(item.deal.contact ?? "").trim();
if (dealId && title && contact) payload.deal = { dealId, title, contact };
}
if (item.message && typeof item.message === "object") {
const contactId = String(item.message.contactId ?? "").trim();
const contact = String(item.message.contact ?? "").trim();
const intent = String(item.message.intent ?? "").trim();
if (intent === "add_message_or_reminder") {
payload.message = {
...(contactId ? { contactId } : {}),
...(contact ? { contact } : {}),
intent: "add_message_or_reminder",
};
}
}
if (item.calendar && typeof item.calendar === "object") {
const view = String(item.calendar.view ?? "").trim();
const period = String(item.calendar.period ?? "").trim();
const selectedDateKey = String(item.calendar.selectedDateKey ?? "").trim();
const focusedEventId = String(item.calendar.focusedEventId ?? "").trim();
const eventIds = Array.isArray(item.calendar.eventIds)
? item.calendar.eventIds.map((id: any) => String(id ?? "").trim()).filter(Boolean)
: [];
if (
(view === "day" || view === "week" || view === "month" || view === "year" || view === "agenda") &&
period &&
selectedDateKey
) {
payload.calendar = {
view,
period,
selectedDateKey,
...(focusedEventId ? { focusedEventId } : {}),
eventIds,
};
}
}
return payload;
}
function humanizeTraceText(trace: AgentTraceEvent): string {
if (trace.toolRun?.name) {
return `Использую инструмент: ${trace.toolRun.name}`;
@@ -62,9 +126,10 @@ function renderChangeSetSummary(changeSet: ChangeSet): string {
export default defineEventHandler(async (event) => {
const auth = await getAuthContext(event);
const body = await readBody<{ messages?: any[] }>(event);
const body = await readBody<{ messages?: any[]; contextPayload?: unknown }>(event);
const messages = Array.isArray(body?.messages) ? body.messages : [];
const userText = getLastUserText(messages);
const contextPayload = sanitizeContextPayload(body?.contextPayload);
if (!userText) {
throw createError({ statusCode: 400, statusMessage: "Last user message is required" });
@@ -94,6 +159,7 @@ export default defineEventHandler(async (event) => {
teamId: auth.teamId,
userId: auth.userId,
userText,
contextPayload,
requestId,
conversationId: auth.conversationId,
onTrace: async (trace: AgentTraceEvent) => {