fix: keep chat replies out of results

Completed local and channel replies were duplicated into Results as Markdown artifacts, making the panel a second conversation history. Replies now stay in their conversations, legacy duplicates are filtered without deleting data, and standalone images, scheduled jobs, headless delegations, heartbeats, and imports remain visible.

Release note: 普通本地与消息通道回复不再重复出现在成果栏;已有重复内容仅从列表隐藏,不会删除历史数据。
This commit is contained in:
mesalogo
2026-08-18 13:14:13 +08:00
parent e387c09f52
commit 0413807b8f
10 changed files with 173 additions and 84 deletions
+93 -13
View File
@@ -974,19 +974,6 @@ describe('AssistantDatabase', () => {
status: 'completed',
completedAt: expect.any(String)
})
const artifact = database.createTextArtifact({
projectId: project.id,
taskId,
title: '发布说明',
content: '# 发布说明\n\n内容'
})
expect(database.listArtifacts(project.id)).toEqual([
expect.objectContaining({
id: artifact.id,
kind: 'markdown',
content: '# 发布说明\n\n内容'
})
])
const memory = database.createMemory({
scope: 'project',
scopeId: project.id,
@@ -2205,6 +2192,99 @@ describe('AssistantDatabase', () => {
durable.close()
})
it('keeps duplicate chat replies out of artifact listings', async () => {
const database = await createDatabase()
const project = database.listProjects()[0]!
const chatTaskId = '00000000-0000-4000-8000-000000000231'
const channelTaskId = '00000000-0000-4000-8000-000000000232'
const scheduleTaskId = '00000000-0000-4000-8000-000000000233'
const delegationTaskId = '00000000-0000-4000-8000-000000000234'
database.createTask({
id: chatTaskId,
projectId: project.id,
conversationId: 'conversation-chat',
title: '普通对话',
instructions: '回答问题',
workMode: 'ask'
})
database.createTask({
id: channelTaskId,
projectId: project.id,
conversationId: 'conversation-channel',
title: '远程对话',
instructions: '回答远程消息',
workMode: 'ask',
origin: 'delegation'
})
database.createTask({
id: scheduleTaskId,
projectId: project.id,
conversationId: 'schedule:daily',
title: '每日报告',
instructions: '生成报告',
workMode: 'ask',
origin: 'schedule'
})
database.createTask({
id: delegationTaskId,
projectId: project.id,
conversationId: 'delegation:weekly-report',
title: '委派报告',
instructions: '生成远程委派报告',
workMode: 'ask',
origin: 'delegation'
})
const chatReply = database.createTextArtifact({
projectId: project.id,
taskId: chatTaskId,
title: '普通对话',
content: '普通回复'
})
const channelReply = database.createTextArtifact({
projectId: project.id,
taskId: channelTaskId,
title: '远程对话',
content: '远程回复'
})
const scheduledReport = database.createTextArtifact({
projectId: project.id,
taskId: scheduleTaskId,
title: '每日报告',
content: '# 每日报告'
})
const delegatedReport = database.createTextArtifact({
projectId: project.id,
taskId: delegationTaskId,
title: '委派报告',
content: '# 委派报告'
})
const generatedImage = database.createImageArtifact({
projectId: project.id,
taskId: chatTaskId,
title: '生成图片',
mimeType: 'image/png',
base64: 'iVBORw0KGgo='
})
const visibleArtifactIds = database
.listArtifacts(project.id)
.map((artifact) => artifact.id)
expect(visibleArtifactIds).toEqual(
expect.arrayContaining([
scheduledReport.id,
delegatedReport.id,
generatedImage.id
])
)
expect(visibleArtifactIds).not.toContain(chatReply.id)
expect(visibleArtifactIds).not.toContain(channelReply.id)
expect(database.getArtifact(chatReply.id)).toMatchObject({
id: chatReply.id,
content: '普通回复'
})
database.close()
})
it('loads image artifact content only when requested by id', async () => {
const database = await createDatabase()
const artifact = database.createInlineArtifact({
+16 -1
View File
@@ -3166,11 +3166,25 @@ export class AssistantDatabase {
CASE WHEN kind = 'image' THEN NULL
ELSE inline_content END AS inline_content,
byte_size, created_at, updated_at`
const visibleArtifact = `NOT (
kind = 'markdown' AND EXISTS (
SELECT 1
FROM tasks
WHERE tasks.id = artifacts.task_id
AND tasks.conversation_id IS NOT NULL
AND (
tasks.origin = 'user' OR (
tasks.origin = 'delegation'
AND tasks.conversation_id NOT LIKE 'delegation:%'
)
)
)
)`
const rows = projectId
? this.requireDatabase()
.prepare(
`SELECT ${columns} FROM artifacts
WHERE project_id = ?
WHERE project_id = ? AND ${visibleArtifact}
ORDER BY created_at DESC
LIMIT ?`
)
@@ -3178,6 +3192,7 @@ export class AssistantDatabase {
: this.requireDatabase()
.prepare(
`SELECT ${columns} FROM artifacts
WHERE ${visibleArtifact}
ORDER BY created_at DESC
LIMIT ?`
)
+4 -6
View File
@@ -3213,12 +3213,7 @@ describe('registerIpcHandlers agent terminal state', () => {
).toBe(expectedOutput)
expect(
harness.assistantDatabase.createTextArtifact
).toHaveBeenCalledWith(
expect.objectContaining({
taskId: requestId,
content: expectedOutput
})
)
).not.toHaveBeenCalled()
await harness.dispose()
})
@@ -4663,6 +4658,9 @@ describe('registerIpcHandlers agent terminal state', () => {
'base64'
).toString('utf8')
).toBe('# 本周报告\n\n已完成。')
expect(
harness.assistantDatabase.createTextArtifact
).not.toHaveBeenCalled()
await harness.dispose()
})
+5 -23
View File
@@ -1551,7 +1551,7 @@ export function registerIpcHandlers(
})
}
}
if (output.trim()) {
if (origin !== 'channel' && output.trim()) {
assistantDatabase.createTextArtifact({
projectId: schedule.projectId,
taskId: requestId,
@@ -1568,7 +1568,9 @@ export function registerIpcHandlers(
body:
origin === 'channel'
? '结果已回复,并保存到远程通道会话。'
: '结果已保存到 GoodBuddy 成果工作栏。'
: origin === 'schedule'
? '结果已保存到 GoodBuddy 成果工作栏。'
: '结果已保存到成果工作栏和委派记录。'
})
return {
status: 'completed',
@@ -2413,7 +2415,6 @@ export function registerIpcHandlers(
activeRequests.set(request.requestId, controller)
const execution = (async () => {
let outputText = ''
let completed = false
let runtimeErrorEvent:
| Extract<AgentEvent, { type: 'error' }>
@@ -2784,15 +2785,6 @@ export function registerIpcHandlers(
.slice(0, 120)
})
: agentEvent
if (
publicEvent.type === 'text' &&
outputText.length < 1_000_000
) {
outputText += publicEvent.delta.slice(
0,
1_000_000 - outputText.length
)
}
if (publicEvent.type === 'tool') {
toolStates.set(publicEvent.callId, publicEvent)
}
@@ -2831,23 +2823,13 @@ export function registerIpcHandlers(
}
if (publicEvent.type === 'done') {
completed = true
if (outputText.trim()) {
assistantDatabase.createTextArtifact({
projectId: request.projectId,
taskId: request.requestId,
title: parsedRequest.prompt
.split(/\r?\n/, 1)[0]!
.slice(0, 120),
content: outputText
})
}
assistantDatabase.updateTaskStatus(
request.requestId,
'completed'
)
showDesktopNotificationWhenUnfocused(window, {
title: 'GoodBuddy 任务已完成',
body: '任务结果已保存到成果工作栏。'
body: '回复已生成,可返回会话查看。'
})
if (!window.isDestroyed()) {
window.webContents.send(