fix: deduplicate terminal notices and distinguish cancellation
Keep cancellation and failure details in message status while preserving existing replies. Render cancellation neutrally, restore terminal metadata from history, and offer Edit again only when input can be restored. Release note: Cancelled and failed requests show a single notice; cancellation no longer appears as an error, and Edit again accurately describes the recovery action.
This commit is contained in:
@@ -4428,6 +4428,7 @@ describe('AssistantDatabase', () => {
|
||||
id: assistantMessageId,
|
||||
content: recoveredText,
|
||||
state: 'error',
|
||||
terminalStatus,
|
||||
runtimeChecklist: { source: 'opencode', items: [] },
|
||||
status: '远端请求已不存在'
|
||||
})
|
||||
@@ -4553,7 +4554,7 @@ describe('AssistantDatabase', () => {
|
||||
} finally { database.close() }
|
||||
})
|
||||
|
||||
it('rolls back a recovered event when its message update fails', async () => {
|
||||
it.each(['failed', 'cancelled'] as const)('rolls back and durably recovers a %s event without creating reply text', async (terminalStatus) => {
|
||||
const directory = await mkdtemp(
|
||||
join(tmpdir(), 'goodbuddy-remote-event-atomic-')
|
||||
)
|
||||
@@ -4617,7 +4618,7 @@ describe('AssistantDatabase', () => {
|
||||
event: {
|
||||
requestId: taskId,
|
||||
type: 'error',
|
||||
status: 'failed',
|
||||
status: terminalStatus,
|
||||
message: '远程失败'
|
||||
}
|
||||
})
|
||||
@@ -4664,7 +4665,7 @@ describe('AssistantDatabase', () => {
|
||||
event: {
|
||||
requestId: taskId,
|
||||
type: 'error',
|
||||
status: 'failed',
|
||||
status: terminalStatus,
|
||||
message: '远程失败'
|
||||
}
|
||||
})
|
||||
@@ -4687,17 +4688,24 @@ describe('AssistantDatabase', () => {
|
||||
expect(
|
||||
recovered.getConversation(conversationId).messages[1]
|
||||
).toMatchObject({
|
||||
content: '远程失败',
|
||||
content: '',
|
||||
state: 'error',
|
||||
terminalStatus,
|
||||
status: '远程失败'
|
||||
})
|
||||
expect(
|
||||
recovered.listTasks().find((task) => task.id === taskId)
|
||||
).toMatchObject({
|
||||
status: 'failed',
|
||||
status: terminalStatus,
|
||||
error: '远程失败'
|
||||
})
|
||||
recovered.close()
|
||||
const reopened = new AssistantDatabase(databasePath)
|
||||
reopened.initialize('C:\\Workspace')
|
||||
const message = reopened.getConversation(conversationId).messages[1]
|
||||
expect(message).toMatchObject({ content: '', state: 'error', terminalStatus, status: '远程失败' })
|
||||
expect(message?.blocks?.some(block => block.type === 'text')).toBe(false)
|
||||
reopened.close()
|
||||
})
|
||||
|
||||
it('persists and arbitrates a FIFO conversation input queue', async () => {
|
||||
|
||||
@@ -290,6 +290,7 @@ type MessageMetadata = {
|
||||
createdAt?: number
|
||||
queueItemId?: string
|
||||
status?: string
|
||||
terminalStatus?: ConversationMessage['terminalStatus']
|
||||
reasoning?: ConversationSnapshot['messages'][number]['reasoning']
|
||||
runtimeChecklist?: ConversationMessage['runtimeChecklist']
|
||||
blocks?: ConversationSnapshot['messages'][number]['blocks']
|
||||
@@ -1213,6 +1214,7 @@ function toConversationSnapshot(
|
||||
createdAt:
|
||||
metadata.createdAt ?? Date.parse(message.created_at),
|
||||
state: message.state,
|
||||
terminalStatus: metadata.terminalStatus,
|
||||
status: metadata.status,
|
||||
contextCompression: metadata.contextCompression,
|
||||
contextCompressions: metadata.contextCompressions,
|
||||
@@ -1240,6 +1242,7 @@ function serializeConversationMessageMetadata(
|
||||
createdAt: message.createdAt,
|
||||
queueItemId: message.queueItemId,
|
||||
status: message.status,
|
||||
terminalStatus: message.terminalStatus,
|
||||
reasoning: message.reasoning,
|
||||
runtimeChecklist: message.runtimeChecklist,
|
||||
blocks: message.blocks,
|
||||
@@ -1568,15 +1571,14 @@ function reduceRecoveredAgentEvent(
|
||||
? ('cancelled' as const)
|
||||
: ('failed' as const)
|
||||
: ('interrupted' as const)
|
||||
const fallback =
|
||||
event.type === 'error' && !represented && !message.content
|
||||
? event.message
|
||||
: ''
|
||||
next = {
|
||||
...message,
|
||||
state: event.type === 'error' ? 'error' : 'complete',
|
||||
terminalStatus: event.type === 'error'
|
||||
? event.status === 'cancelled' ? 'cancelled' : 'failed'
|
||||
: undefined,
|
||||
status:
|
||||
event.type === 'error' && !represented
|
||||
event.type === 'error' && (event.status === 'cancelled' || !represented)
|
||||
? event.message
|
||||
: undefined,
|
||||
contextCompression:
|
||||
@@ -1620,10 +1622,9 @@ function reduceRecoveredAgentEvent(
|
||||
: subagent
|
||||
),
|
||||
blocks: terminalizeRecoveredToolBlocks(
|
||||
appendRecoveredMessageBlock(message.blocks, 'text', fallback),
|
||||
message.blocks,
|
||||
toolState
|
||||
),
|
||||
content: fallback || message.content
|
||||
)
|
||||
}
|
||||
}
|
||||
return conversationMessageSchema.parse(next)
|
||||
@@ -5443,6 +5444,7 @@ export class AssistantDatabase {
|
||||
state: row.state,
|
||||
createdAt: metadata.createdAt ?? Date.parse(row.created_at),
|
||||
status: metadata.status,
|
||||
terminalStatus: metadata.terminalStatus,
|
||||
reasoning: metadata.reasoning,
|
||||
runtimeChecklist: metadata.runtimeChecklist,
|
||||
blocks: metadata.blocks,
|
||||
@@ -5858,6 +5860,7 @@ export class AssistantDatabase {
|
||||
createdAt:
|
||||
metadata.createdAt ?? Date.parse(row.created_at),
|
||||
status: metadata.status,
|
||||
terminalStatus: metadata.terminalStatus,
|
||||
reasoning: metadata.reasoning,
|
||||
runtimeChecklist: metadata.runtimeChecklist,
|
||||
blocks: metadata.blocks,
|
||||
|
||||
Reference in New Issue
Block a user