mirror of
https://github.com/HKUDS/nanobot.git
synced 2026-09-04 10:11:46 +03:00
refactor: simplify cross-session messaging
This commit is contained in:
@@ -33,7 +33,6 @@ import type {
|
||||
OutboundCliAppMention,
|
||||
OutboundMcpPresetMention,
|
||||
OutboundMedia,
|
||||
SessionHandle,
|
||||
SessionMention,
|
||||
GoalStateWsPayload,
|
||||
MessageDeliveryStatus,
|
||||
@@ -170,7 +169,6 @@ export interface SendOptions {
|
||||
cliApps?: OutboundCliAppMention[];
|
||||
mcpPresets?: OutboundMcpPresetMention[];
|
||||
sessionMentions?: SessionMention[];
|
||||
sessionHandles?: SessionHandle[];
|
||||
quotedContext?: string;
|
||||
workspaceScope?: WorkspaceScopePayload | null;
|
||||
sideChannel?: boolean;
|
||||
@@ -190,7 +188,6 @@ function eventExtendsModelActivity(ev: InboundEvent): boolean {
|
||||
ev.event === "delta"
|
||||
|| ev.event === "reasoning_delta"
|
||||
|| ev.event === "file_edit"
|
||||
|| ev.event === "session_message"
|
||||
) return true;
|
||||
return ev.event === "message"
|
||||
&& (ev.kind === "tool_hint" || ev.kind === "progress" || ev.kind === "reasoning");
|
||||
@@ -221,31 +218,27 @@ function transitionTurnDelivery(
|
||||
return changed ? next : messages;
|
||||
}
|
||||
|
||||
function appendLiveSessionMessage(
|
||||
function appendProjectedSessionInput(
|
||||
messages: UIMessage[],
|
||||
event: Extract<InboundEvent, { event: "session_message" }>,
|
||||
event: Extract<InboundEvent, { event: "user_message" }>,
|
||||
): UIMessage[] {
|
||||
const messageId = event.session_message?.message_id?.trim();
|
||||
if (!messageId || event.session_message.direction !== "incoming") return messages;
|
||||
const sessionMessage = event.provenance?.session_message;
|
||||
const messageId = sessionMessage?.message_id?.trim();
|
||||
if (!sessionMessage || !messageId) return messages;
|
||||
if (messages.some((message) => message.sessionMessage?.message_id === messageId)) return messages;
|
||||
|
||||
const row: UIMessage = {
|
||||
id: `session-message:${messageId}`,
|
||||
role: "user",
|
||||
content: event.text,
|
||||
createdAt: Number.isFinite(event.created_at_ms) ? event.created_at_ms : Date.now(),
|
||||
sessionMessage: event.session_message,
|
||||
createdAt: typeof event.created_at_ms === "number"
|
||||
&& Number.isFinite(event.created_at_ms)
|
||||
? event.created_at_ms
|
||||
: Date.now(),
|
||||
sessionMessage,
|
||||
...turnFieldsFromEvent(event, "user"),
|
||||
};
|
||||
const sameTurnIndex = event.turn_id
|
||||
? messages.findIndex((message) => message.turnId === event.turn_id)
|
||||
: -1;
|
||||
if (sameTurnIndex < 0) return [...messages, row];
|
||||
return [
|
||||
...messages.slice(0, sameTurnIndex),
|
||||
row,
|
||||
...messages.slice(sameTurnIndex),
|
||||
];
|
||||
return [...messages, row];
|
||||
}
|
||||
|
||||
export function useNanobotStream(
|
||||
@@ -675,18 +668,6 @@ export function useNanobotStream(
|
||||
});
|
||||
}, [cancelStreamEndTimer, client]);
|
||||
|
||||
useEffect(() => {
|
||||
return client.onRunStatus((updatedChatId, startedAt) => {
|
||||
if (updatedChatId !== chatId) return;
|
||||
// Canonical HTTP reconciliation can settle a turn before its delayed
|
||||
// WebSocket completion frame reaches this mounted thread. The client
|
||||
// then fences that duplicate frame, so keep the pane-local timer in
|
||||
// sync with the client's authoritative per-chat run projection.
|
||||
setRunStartedAt(startedAt);
|
||||
if (startedAt !== null) setIsStreaming(true);
|
||||
});
|
||||
}, [chatId, client]);
|
||||
|
||||
// Reset local state when switching chats. Do not reset on every
|
||||
// ``initialMessages`` update: a brand-new chat can receive an empty/404
|
||||
// history response after the optimistic first message has already rendered.
|
||||
@@ -752,6 +733,13 @@ export function useNanobotStream(
|
||||
}
|
||||
if (ev.event === "message_accepted") return;
|
||||
if (ev.event === "user_message") {
|
||||
if (ev.provenance?.session_message) {
|
||||
flushPendingStreamEvents({ closeAnswerSegment: true });
|
||||
clearActivitySegment();
|
||||
setIsStreaming(true);
|
||||
setMessages((prev) => appendProjectedSessionInput(prev, ev));
|
||||
return;
|
||||
}
|
||||
setMessages((prev) => {
|
||||
if (ev.turn_id && prev.some((message) => (
|
||||
message.role === "user" && message.turnId === ev.turn_id
|
||||
@@ -766,7 +754,10 @@ export function useNanobotStream(
|
||||
turnPhase: "user",
|
||||
turnSeq: 0,
|
||||
deliveryStatus: "accepted",
|
||||
createdAt: Date.now(),
|
||||
createdAt: typeof ev.created_at_ms === "number"
|
||||
&& Number.isFinite(ev.created_at_ms)
|
||||
? ev.created_at_ms
|
||||
: Date.now(),
|
||||
...(ev.media_urls?.length ? { media: ev.media_urls } : {}),
|
||||
...(ev.cli_apps?.length ? { cliApps: ev.cli_apps } : {}),
|
||||
...(ev.mcp_presets?.length ? { mcpPresets: ev.mcp_presets } : {}),
|
||||
@@ -844,20 +835,12 @@ export function useNanobotStream(
|
||||
|
||||
const shouldCloseAnswerBeforeEvent =
|
||||
ev.event === "file_edit"
|
||||
|| ev.event === "session_message"
|
||||
|| (
|
||||
ev.event === "message"
|
||||
&& (ev.kind === "tool_hint" || ev.kind === "progress")
|
||||
);
|
||||
flushPendingStreamEvents({ closeAnswerSegment: shouldCloseAnswerBeforeEvent });
|
||||
|
||||
if (ev.event === "session_message") {
|
||||
clearActivitySegment();
|
||||
setIsStreaming(true);
|
||||
setMessages((prev) => appendLiveSessionMessage(prev, ev));
|
||||
return;
|
||||
}
|
||||
|
||||
if (ev.event === "reasoning_end") {
|
||||
if (suppressStreamUntilTurnEndRef.current) return;
|
||||
setMessages((prev) => closeReasoningStream(prev, Date.now()));
|
||||
@@ -1188,9 +1171,6 @@ export function useNanobotStream(
|
||||
...(options?.sessionMentions?.length
|
||||
? { sessionMentions: options.sessionMentions }
|
||||
: {}),
|
||||
...(options?.sessionHandles?.length
|
||||
? { sessionHandles: options.sessionHandles }
|
||||
: {}),
|
||||
},
|
||||
];
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user