mirror of
https://github.com/bytedance/deer-flow.git
synced 2026-09-19 11:06:18 +00:00
* feat(frontend): reference conversations from the composer
Adds a "Reference a conversation" button next to the attachment button,
shown only while GET /api/features reports read_conversation enabled. It
opens a picker over the recent-conversation list (current thread excluded,
capped at max_references) and shows removable chips in the composer.
On send the thread IDs ride SendMessageOptions.conversationReferences into
run context.conversation_references, which the Gateway consumes at
admission; the LangGraph SDK drops unknown top-level body fields. A
display-only copy ({thread_id, title}) on the visible human message lets
the transcript render read-only chips linking to the source.
References are per message: not persisted with the draft and cleared on
send or thread switch; regenerating or editing a turn runs without them
unless they are attached again.
Related to #5398. Depends on #5463.
* fix(frontend): pin the run-context contract and finish the picker states
Both thread.submit paths now build their run context through one exported
buildRunContext helper, tested directly: attached references travel as a
plain string[] under context.conversation_references only when the caller
passed them, a stray key in local settings is dropped instead of forwarded,
and the regenerate/edit replay path never carries references.
The picker shows a loading row while the conversation list is still in
flight instead of claiming there are no conversations, and the transcript
chip group is labelled with the previously unused referencedConversations
translation.
* fix(frontend): route conversation-reference chips to custom-agent sources
The picker offered custom-agent conversations but kept only the thread ID
and title, so transcript chips always linked to /workspace/chats/{id} and
dropped the source's custom-agent context on navigation.
Preserve the agent identity end to end: the picker now attaches
agentNameOfThread() (context first, then metadata.agent_name, mirroring
pathOfThread) to the selection, the display-only additional_kwargs metadata
round-trips it as agent_name, and the transcript chip passes it to
pathOfThread so custom-agent sources resolve to
/workspace/agents/{agent}/chats/{id}.
Tests: agent_name metadata round-trip and malformed-entry tolerance, picker
toggle carrying the metadata agent with run context winning, and a
picker-to-transcript regression pinning the /workspace/agents/writer/chats/
source-1 href.
---------
Co-authored-by: Totoro-qaq <279883115+Totoro-qaq@users.noreply.github.com>
102 lines
2.7 KiB
TypeScript
102 lines
2.7 KiB
TypeScript
import { beforeEach, describe, expect, it, rs } from "@rstest/core";
|
|
|
|
rs.mock("@/core/api/fetcher", () => ({ fetch: rs.fn() }));
|
|
rs.mock("@/core/config", () => ({ getBackendBaseURL: () => "" }));
|
|
|
|
import { fetch } from "@/core/api/fetcher";
|
|
import {
|
|
fetchConversationReferencesCapability,
|
|
fetchSubagentBatchesCapability,
|
|
} from "@/core/features/api";
|
|
|
|
const mockedFetch = rs.mocked(fetch);
|
|
|
|
function jsonResponse(body: unknown): Response {
|
|
return new Response(JSON.stringify(body), {
|
|
status: 200,
|
|
headers: { "Content-Type": "application/json" },
|
|
});
|
|
}
|
|
|
|
beforeEach(() => {
|
|
mockedFetch.mockReset();
|
|
});
|
|
|
|
describe("subagent batch feature capability", () => {
|
|
it("keeps repository and worker availability independent", async () => {
|
|
mockedFetch.mockResolvedValueOnce(
|
|
jsonResponse({
|
|
agents_api: { enabled: true },
|
|
subagent_batches: {
|
|
enabled: false,
|
|
repository_available: true,
|
|
worker_running: false,
|
|
max_running: 3,
|
|
},
|
|
}),
|
|
);
|
|
|
|
await expect(fetchSubagentBatchesCapability()).resolves.toEqual({
|
|
repositoryAvailable: true,
|
|
workerRunning: false,
|
|
maxRunning: 3,
|
|
});
|
|
});
|
|
|
|
it("falls back to the legacy enabled flag during rolling upgrades", async () => {
|
|
mockedFetch.mockResolvedValueOnce(
|
|
jsonResponse({
|
|
agents_api: { enabled: true },
|
|
subagent_batches: { enabled: true, max_running: 4 },
|
|
}),
|
|
);
|
|
|
|
await expect(fetchSubagentBatchesCapability()).resolves.toEqual({
|
|
repositoryAvailable: true,
|
|
workerRunning: true,
|
|
maxRunning: 4,
|
|
});
|
|
});
|
|
});
|
|
|
|
describe("conversation references feature capability", () => {
|
|
it("reports the flag and the per-run cap", async () => {
|
|
mockedFetch.mockResolvedValueOnce(
|
|
jsonResponse({
|
|
agents_api: { enabled: true },
|
|
conversation_references: { enabled: true, max_references: 3 },
|
|
}),
|
|
);
|
|
|
|
await expect(fetchConversationReferencesCapability()).resolves.toEqual({
|
|
enabled: true,
|
|
maxReferences: 3,
|
|
});
|
|
});
|
|
|
|
it("treats a backend without the field as disabled", async () => {
|
|
mockedFetch.mockResolvedValueOnce(
|
|
jsonResponse({ agents_api: { enabled: true } }),
|
|
);
|
|
|
|
await expect(fetchConversationReferencesCapability()).resolves.toEqual({
|
|
enabled: false,
|
|
maxReferences: 0,
|
|
});
|
|
});
|
|
|
|
it("never reports a cap below zero or a non-numeric one", async () => {
|
|
mockedFetch.mockResolvedValueOnce(
|
|
jsonResponse({
|
|
agents_api: { enabled: true },
|
|
conversation_references: { enabled: true, max_references: "3" },
|
|
}),
|
|
);
|
|
|
|
await expect(fetchConversationReferencesCapability()).resolves.toEqual({
|
|
enabled: true,
|
|
maxReferences: 0,
|
|
});
|
|
});
|
|
});
|