mirror of
https://github.com/bytedance/deer-flow.git
synced 2026-07-21 10:15:47 +00:00
* feat(frontend): add side conversations for quoted follow-ups * style(frontend): apply prettier formatting to sidecar-chat files * fix(frontend): surface sidecar cascade cleanup failures via console.warn Previously deleteSidecarThreadsForParent silently swallowed both lookup errors and per-thread deletion failures, so parent thread deletions could succeed while orphaning sidecar threads with no signal to the caller. Log a warning that includes the parent id and the failed thread ids/reasons so the leak is discoverable in telemetry, matching the existing console.warn/error pattern in this file. * fix(frontend): address all sidecar review feedback Resolve every reviewer comment on PR #3934: - input-box/hooks/sidecar-panel: clear quoted references only via an `onSent` callback that fires after the in-flight guard, so a dropped send no longer silently discards quotes (willem-bd #3550). - message-list: flip the selection toolbar below the selection when it would clip above the viewport (willem-bd #3551). - reference-metadata/thread/input-box: keep referenced ids, roles, and count arrays 1:1 parallel instead of deduping ids (willem-bd #3552). - message-list: widen selection containment to the shared assistant-turn container and hint when a selection crosses messages (willem-bd #3553). - sidecar/api: coalesce concurrent sidecar creates for one parent behind a single in-flight promise to prevent duplicates (willem-bd #3554). - sidecar-trigger/context: force-restore on trigger click so a sidecar deleted elsewhere self-heals instead of opening a dead thread (willem-bd #3555). - threads/hooks: surface sidecar cascade cleanup failures via console.warn for both lookup and per-thread deletes (Copilot). Add unit + e2e coverage for parallel metadata, atomic create, and trigger self-healing.
65 lines
2.1 KiB
TypeScript
65 lines
2.1 KiB
TypeScript
import type { AgentThread } from "@/core/threads";
|
|
|
|
import { normalizeSidecarContexts, type SidecarContext } from "./context";
|
|
|
|
export const SIDECAR_METADATA_KEY = "deerflow_sidecar";
|
|
|
|
export type SidecarThreadMetadata = {
|
|
[SIDECAR_METADATA_KEY]: true;
|
|
parent_thread_id: string;
|
|
sidecar_context_type: SidecarContext["type"];
|
|
sidecar_context_label: string;
|
|
sidecar_context_count: number;
|
|
referenced_message_id?: string;
|
|
referenced_message_ids: string[];
|
|
referenced_message_role: SidecarContext["role"];
|
|
referenced_message_roles: SidecarContext["role"][];
|
|
};
|
|
|
|
export function buildSidecarThreadMetadata(
|
|
parentThreadId: string,
|
|
contextOrContexts: SidecarContext | SidecarContext[],
|
|
): SidecarThreadMetadata {
|
|
const contexts = normalizeSidecarContexts(contextOrContexts);
|
|
const primaryContext = contexts[0];
|
|
if (!primaryContext) {
|
|
throw new Error("At least one sidecar context is required.");
|
|
}
|
|
|
|
// Keep `referenced_message_ids`, `referenced_message_roles`, and
|
|
// `sidecar_context_count` 1:1 parallel with `contexts` so consumers can zip
|
|
// them safely (two fragments of the same source message would otherwise make
|
|
// a deduped id array shorter than the role array).
|
|
const referencedMessageIds = contexts.map(
|
|
(context) => context.messageId ?? "",
|
|
);
|
|
|
|
return {
|
|
[SIDECAR_METADATA_KEY]: true,
|
|
parent_thread_id: parentThreadId,
|
|
sidecar_context_type: primaryContext.type,
|
|
sidecar_context_label: primaryContext.label,
|
|
sidecar_context_count: contexts.length,
|
|
referenced_message_id: primaryContext.messageId,
|
|
referenced_message_ids: referencedMessageIds,
|
|
referenced_message_role: primaryContext.role,
|
|
referenced_message_roles: contexts.map((context) => context.role),
|
|
};
|
|
}
|
|
|
|
export function isSidecarThread(
|
|
thread:
|
|
| Pick<AgentThread, "metadata">
|
|
| { metadata?: Record<string, unknown> },
|
|
) {
|
|
return thread.metadata?.[SIDECAR_METADATA_KEY] === true;
|
|
}
|
|
|
|
export function shouldShowInPrimaryThreadLists(
|
|
thread:
|
|
| Pick<AgentThread, "metadata">
|
|
| { metadata?: Record<string, unknown> },
|
|
) {
|
|
return !isSidecarThread(thread);
|
|
}
|