feat(editor): save leptos island and page aggregate alignment progress
- switch main document flow toward leptos tiptap island host and generated runtime assets - align page aggregate loading, page head single-source updates, and AI tool result recovery - add tests and smoke scripts for title sync, AI route recovery, and editor host cutover
This commit is contained in:
@@ -13,6 +13,11 @@ import {
|
||||
startCodexJsonRun,
|
||||
} from "@/lib/ai/codex/codexExec";
|
||||
import { startHermesRun, streamHermesRunEvents, type HermesRunEvent } from "@/lib/ai-agent/hermes/bridge";
|
||||
import {
|
||||
readHermesToolArgsFromEvent,
|
||||
readHermesToolResultFromEvent,
|
||||
} from "@/lib/ai-agent/hermes/tool-result-recovery";
|
||||
import { fetchHermesStructuredToolResultFromMnoteWeb } from "@/lib/server/mnote-web-hermes";
|
||||
|
||||
export const dynamic = "force-dynamic";
|
||||
|
||||
@@ -223,15 +228,76 @@ const buildHermesInput = (messages: AgentMessage[]) => {
|
||||
.map((item) => ({ role: item.role, content: String(item.content ?? "") }));
|
||||
};
|
||||
|
||||
type PendingHermesToolCall = {
|
||||
preview: string;
|
||||
argsJson: Record<string, unknown> | null;
|
||||
};
|
||||
|
||||
const recoverStructuredHermesToolResult = async (input: {
|
||||
request: Request;
|
||||
payload: RequestPayload;
|
||||
userId: string;
|
||||
tool: string;
|
||||
argsJson: Record<string, unknown> | null;
|
||||
fallbackRequestId: string;
|
||||
fallbackTraceId: string;
|
||||
}): Promise<unknown | null> => {
|
||||
if (!input.argsJson) {
|
||||
return null;
|
||||
}
|
||||
if (
|
||||
input.tool !== "slash_run" &&
|
||||
input.tool !== "doc_insert_blocks" &&
|
||||
input.tool !== "doc_replace_range"
|
||||
) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const documentId = String(input.payload.context?.documentId ?? "").trim() || null;
|
||||
const data =
|
||||
input.tool === "slash_run"
|
||||
? { source: "ai-agent-route" }
|
||||
: input.payload.context?.documentBlocks ?? null;
|
||||
|
||||
if ((input.tool === "doc_insert_blocks" || input.tool === "doc_replace_range") && data == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return fetchHermesStructuredToolResultFromMnoteWeb({
|
||||
request: input.request,
|
||||
userId: input.userId,
|
||||
tool: input.tool,
|
||||
argsJson: input.argsJson,
|
||||
data,
|
||||
requestId: input.fallbackRequestId,
|
||||
traceId: input.fallbackTraceId,
|
||||
target: documentId
|
||||
? {
|
||||
pageId: documentId,
|
||||
blockId:
|
||||
input.tool === "doc_replace_range"
|
||||
? String(input.argsJson.blockId ?? "").trim() || null
|
||||
: null,
|
||||
}
|
||||
: null,
|
||||
}).catch(() => null);
|
||||
};
|
||||
|
||||
const streamHermesLegacyEvents = async ({
|
||||
messages,
|
||||
instructions,
|
||||
sessionId,
|
||||
request,
|
||||
payload,
|
||||
userId,
|
||||
onEvent,
|
||||
}: {
|
||||
messages: AgentMessage[];
|
||||
instructions: string;
|
||||
sessionId: string | null;
|
||||
request: Request;
|
||||
payload: RequestPayload;
|
||||
userId: string;
|
||||
onEvent: (event: LegacyStreamEvent) => Promise<void> | void;
|
||||
}) => {
|
||||
const input = buildHermesInput(messages);
|
||||
@@ -242,7 +308,7 @@ const streamHermesLegacyEvents = async ({
|
||||
});
|
||||
|
||||
const pendingToolIds = new Map<string, string[]>();
|
||||
const previewById = new Map<string, string>();
|
||||
const pendingToolCalls = new Map<string, PendingHermesToolCall>();
|
||||
let toolCount = 0;
|
||||
let assistantBuffer = "";
|
||||
let failureMessage = "";
|
||||
@@ -256,7 +322,10 @@ const streamHermesLegacyEvents = async ({
|
||||
queue.push(id);
|
||||
pendingToolIds.set(tool, queue);
|
||||
const preview = typeof event.preview === "string" ? event.preview : "";
|
||||
previewById.set(id, preview);
|
||||
pendingToolCalls.set(id, {
|
||||
preview,
|
||||
argsJson: readHermesToolArgsFromEvent(event, tool),
|
||||
});
|
||||
await onEvent({
|
||||
type: "tool_call",
|
||||
data: {
|
||||
@@ -273,8 +342,22 @@ const streamHermesLegacyEvents = async ({
|
||||
const queue = pendingToolIds.get(tool) ?? [];
|
||||
const id = queue.shift() ?? `hermes_${runId}_${toolCount}`;
|
||||
pendingToolIds.set(tool, queue);
|
||||
const preview = previewById.get(id) ?? "";
|
||||
const pendingToolCall = pendingToolCalls.get(id) ?? null;
|
||||
pendingToolCalls.delete(id);
|
||||
const preview = pendingToolCall?.preview ?? "";
|
||||
const duration = Number(event.duration ?? 0);
|
||||
const structuredResultFromEvent = !Boolean(event.error) ? readHermesToolResultFromEvent(event) : null;
|
||||
const recoveredResult =
|
||||
structuredResultFromEvent ??
|
||||
(await recoverStructuredHermesToolResult({
|
||||
request,
|
||||
payload,
|
||||
userId,
|
||||
tool,
|
||||
argsJson: pendingToolCall?.argsJson ?? null,
|
||||
fallbackRequestId: makeRunId(),
|
||||
fallbackTraceId: makeRunId(),
|
||||
}));
|
||||
await onEvent({
|
||||
type: "tool_result",
|
||||
data: {
|
||||
@@ -282,7 +365,9 @@ const streamHermesLegacyEvents = async ({
|
||||
tool,
|
||||
ok: !Boolean(event.error),
|
||||
ms: Number.isFinite(duration) ? Math.max(0, Math.round(duration * 1000)) : 0,
|
||||
result: preview ? { preview, error: Boolean(event.error) } : { error: Boolean(event.error) },
|
||||
result:
|
||||
recoveredResult ??
|
||||
(preview ? { preview, error: Boolean(event.error) } : { error: Boolean(event.error) }),
|
||||
},
|
||||
});
|
||||
return;
|
||||
@@ -551,6 +636,9 @@ export async function POST(request: Request) {
|
||||
messages: payload.messages,
|
||||
instructions,
|
||||
sessionId,
|
||||
request,
|
||||
payload,
|
||||
userId,
|
||||
onEvent: (event) => {
|
||||
events.push(event);
|
||||
},
|
||||
@@ -589,6 +677,9 @@ export async function POST(request: Request) {
|
||||
messages: payload.messages,
|
||||
instructions,
|
||||
sessionId,
|
||||
request,
|
||||
payload,
|
||||
userId,
|
||||
onEvent: (event) => {
|
||||
send(event.type, event.data ?? null);
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user