feat: daily sync (#38593)

Co-authored-by: 盐粒 Yanli <mail@yanli.one>
Co-authored-by: yyh <yuanyouhuilyz@gmail.com>
Co-authored-by: Joel <iamjoel007@gmail.com>
Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
Co-authored-by: QuantumGhost <obelisk.reg+git@gmail.com>
Co-authored-by: yyh <92089059+lyzno1@users.noreply.github.com>
Co-authored-by: 盐粒 Yanli <yanli@dify.ai>
This commit is contained in:
zyssyz123
2026-07-09 10:35:15 +00:00
committed by GitHub
co-authored by 盐粒 Yanli yyh Joel autofix-ci[bot] QuantumGhost yyh 盐粒 Yanli
parent 9252d81826
commit 925f97be20
190 changed files with 9739 additions and 345 deletions
@@ -76,7 +76,7 @@ describe('AgentDetailLayout', () => {
)
await waitFor(() => {
expect(mockReplace).toHaveBeenCalledWith('/roster')
expect(mockReplace).toHaveBeenCalledWith('/agents')
})
expect(screen.queryByText('Agent detail content')).not.toBeInTheDocument()
})
@@ -5,7 +5,7 @@ import userEvent from '@testing-library/user-event'
import { AgentDetailSection, AgentDetailTop } from '../navigation'
const mocks = vi.hoisted(() => ({
pathname: '/roster/agent/agent-1/configure',
pathname: '/agents/agent-1/configure',
queryData: undefined as AgentAppDetailWithSite | undefined,
}))
@@ -93,7 +93,7 @@ function renderAgentDetailSection(expand = true) {
describe('AgentDetailSection', () => {
beforeEach(() => {
mocks.pathname = '/roster/agent/agent-1/configure'
mocks.pathname = '/agents/agent-1/configure'
mocks.queryData = createAgent()
})
@@ -144,7 +144,7 @@ describe('AgentDetailTop', () => {
render(<AgentDetailTop />)
expect(screen.getByRole('link', { name: 'common.mainNav.home' })).toHaveAttribute('href', '/')
expect(screen.getByRole('link', { name: 'common.menus.roster' })).toHaveAttribute('href', '/roster')
expect(screen.getByRole('link', { name: 'Agents' })).toHaveAttribute('href', '/agents')
expect(screen.queryByRole('button', { name: 'common.operation.back' })).not.toBeInTheDocument()
})
})
@@ -603,7 +603,16 @@ describe('AgentPreviewChat', () => {
inputs: {},
message: [],
message_files: [],
agent_thoughts: [],
agent_thoughts: [{
id: 'thought-with-answer',
message_id: 'message-after-send',
thought: '',
answer: 'history thought answer',
tool: '',
tool_input: '',
observation: '',
position: 1,
}],
feedbacks: [],
answer_tokens: 1,
message_tokens: 1,
@@ -635,6 +644,51 @@ describe('AgentPreviewChat', () => {
}))).toBe(conversationMessagesResponse)
})
it('should preserve historical agent thought answer when formatting chat history', async () => {
chatMessagesGetMock.mockResolvedValue({
data: [
{
id: 'message-with-thought-answer',
conversation_id: 'conversation-1',
query: 'hello',
answer: '',
inputs: {},
message: [],
message_files: [],
agent_thoughts: [{
id: 'thought-with-answer',
message_id: 'message-with-thought-answer',
thought: '',
answer: 'history thought answer',
tool: '',
tool_input: '',
observation: '',
position: 1,
}],
feedbacks: [],
status: 'success',
from_source: 'console',
},
],
})
renderPreviewChat({
conversationId: 'conversation-1',
})
await waitFor(() => {
const formattedTree = useChatMock.mock.calls.find((call) => {
const chatTree = call[2]
return JSON.stringify(chatTree).includes('history thought answer')
})?.[2]
expect(formattedTree?.[0]?.children?.[0]?.agent_thoughts?.[0]).toEqual(expect.objectContaining({
id: 'thought-with-answer',
answer: 'history thought answer',
}))
})
})
it('should notify the owner when a send settles with an error', async () => {
const onSendInterrupted = vi.fn()
renderPreviewChat({
@@ -261,6 +261,7 @@ const toAgentThoughtItem = (thought: AgentThought, conversationId: string): Thou
id: thought.id,
tool: thought.tool ?? '',
thought: thought.thought ?? '',
answer: thought.answer ?? '',
tool_input: thought.tool_input ?? '',
message_id: thought.message_id,
conversation_id: conversationId,
@@ -34,7 +34,7 @@ export function AgentDetailLayout({
useEffect(() => {
if (shouldRedirectToRoster)
router.replace('/roster')
router.replace('/agents')
}, [router, shouldRedirectToRoster])
if (shouldRedirectToRoster)
@@ -120,8 +120,8 @@ export function AgentDetailTop({
<span className="shrink-0 system-md-regular text-text-quaternary">
/
</span>
<Link href="/roster" className="shrink-0 truncate rounded-lg px-1.5 py-2 system-sm-semibold-uppercase text-text-secondary transition-colors hover:bg-background-default-hover hover:text-text-primary focus-visible:ring-2 focus-visible:ring-state-accent-solid focus-visible:outline-hidden">
{tCommon('menus.roster')}
<Link href="/agents" className="shrink-0 truncate rounded-lg px-1.5 py-2 system-sm-semibold-uppercase text-text-secondary transition-colors hover:bg-background-default-hover hover:text-text-primary focus-visible:ring-2 focus-visible:ring-state-accent-solid focus-visible:outline-hidden">
Agents
</Link>
</div>
<Tooltip>
+3 -3
View File
@@ -3,12 +3,12 @@ import type { AgentDetailSectionKey } from './section'
export const getAgentDetailPath = (
agentId: string,
section: AgentDetailSectionKey,
) => `/roster/agent/${agentId}/${section}`
) => `/agents/${agentId}/${section}`
export const getAgentIdFromPathname = (pathname: string) => {
const [section, type, agentId] = pathname.split('/').filter(Boolean)
const [section, agentId] = pathname.split('/').filter(Boolean)
if (section !== 'roster' || type !== 'agent')
if (section !== 'agents')
return undefined
return agentId
@@ -91,7 +91,7 @@ describe('CreateAgentDialog', () => {
})
expect(toastMock.success).toHaveBeenCalledWith('agentV2.roster.createSuccess')
expect(routerPushMock).toHaveBeenCalledWith('/roster/agent/agent-1/configure')
expect(routerPushMock).toHaveBeenCalledWith('/agents/agent-1/configure')
})
it('shows a field error when creating with an empty name', async () => {
@@ -130,7 +130,7 @@ function AgentRosterItem({
<article className="group relative col-span-1 h-36.5 min-w-0 overflow-hidden rounded-xl border-[0.5px] border-solid border-components-card-border bg-components-card-bg shadow-xs shadow-shadow-shadow-3 transition-shadow duration-200 ease-in-out after:pointer-events-none after:absolute after:inset-0 after:rounded-xl after:content-[''] hover:shadow-lg has-[>div>a:focus-visible]:after:inset-ring-2 has-[>div>a:focus-visible]:after:inset-ring-state-accent-solid">
<div className="flex h-full min-w-0 flex-col">
<Link
href={`/roster/agent/${agent.id}/configure`}
href={`/agents/${agent.id}/configure`}
aria-labelledby={nameId}
aria-describedby={agent.description ? descriptionId : undefined}
className="block shrink-0 cursor-pointer touch-manipulation outline-hidden"
+2 -3
View File
@@ -43,7 +43,6 @@ const getFilteredRosterItems = (
export default function RosterPage() {
const { t } = useTranslation('agentV2')
const { t: tCommon } = useTranslation('common')
const [keyword] = useQueryState(rosterQueryParamNames.keyword, rosterKeywordQueryParser)
const [rosterFilter] = useQueryState(rosterQueryParamNames.filter, rosterFilterQueryParser)
const [createdByMe] = useQueryState(rosterQueryParamNames.createdByMe, rosterCreatedByMeQueryParser)
@@ -84,14 +83,14 @@ export default function RosterPage() {
const draftAgents = Math.max(rosterItems.length - publishedAgents, 0)
const filteredRosterItems = getFilteredRosterItems(rosterItems, rosterFilter)
useDocumentTitle(tCommon('menus.roster'))
useDocumentTitle('Agents')
return (
<div className="flex h-0 min-w-0 grow flex-col overflow-hidden bg-background-body">
<div className="shrink-0 bg-background-body px-8 pt-4 pb-2">
<div className="flex h-6 min-w-0 items-center justify-between gap-4">
<h1 className="min-w-0 flex-1 truncate text-[18px]/[21.6px] font-semibold text-text-primary">
{tCommon('menus.roster')}
Agents
</h1>
<a
href="https://docs.dify.ai/"