feat: show runtime checklists and support remote Continue
Display native OpenCode and Continue checklists alongside scheduled tasks, preserving request ownership, explicit clears and saved progress. Bundle both remote runtimes and support managed Continue installation, model routing, cancellation and restart continuation. Validation: real local and Linux remote model checks, full Electron remote Continue flows, typecheck and lint passed. Full suite: 4707 passed, 67 skipped, one OpenCode compaction failure passed targeted rerun. Release note: Display native execution checklists above conversations and support managed remote Continue projects with saved progress and cancellation recovery.
This commit is contained in:
@@ -263,6 +263,25 @@ function claimManualScheduleQueueItem(
|
||||
}
|
||||
|
||||
describe('AssistantDatabase', () => {
|
||||
it('round trips long checklist metadata and explicit clears across reopen', async () => {
|
||||
const directory = await mkdtemp(join(tmpdir(), 'goodbuddy-checklist-'))
|
||||
temporaryDirectories.push(directory)
|
||||
const path = join(directory, 'assistant.sqlite')
|
||||
const database = new AssistantDatabase(path)
|
||||
await database.initialize(process.cwd())
|
||||
const id = randomUUID()
|
||||
const message = { id: randomUUID(), role: 'assistant' as const, state: 'complete' as const, content: '', createdAt: 1,
|
||||
runtimeChecklist: { source: 'continue' as const, items: [{ content: 'task'.repeat(1500), status: 'pending' as const }] } }
|
||||
database.replaceConversations([{ id, title: 'Checklist', updatedAt: 1, messages: [message] }])
|
||||
database.close()
|
||||
const reopened = new AssistantDatabase(path)
|
||||
await reopened.initialize(process.cwd())
|
||||
try {
|
||||
expect(reopened.getConversation(id).messages[0]?.runtimeChecklist).toEqual(message.runtimeChecklist)
|
||||
reopened.saveLocalConversations([{ header: { id, title: 'Checklist', updatedAt: 2 }, messages: [{ ...message, runtimeChecklist: { source: 'continue', items: [] } }] }])
|
||||
expect(reopened.getConversation(id).messages[0]?.runtimeChecklist).toEqual({ source: 'continue', items: [] })
|
||||
} finally { reopened.close() }
|
||||
})
|
||||
it('lists lightweight history without dropping searchable text or full process metadata', async () => {
|
||||
const database = await createDatabase()
|
||||
try {
|
||||
@@ -3828,6 +3847,13 @@ describe('AssistantDatabase', () => {
|
||||
}))
|
||||
]
|
||||
})
|
||||
const checklistEvent = { requestId: taskId, type: 'checklist' as const,
|
||||
checklist: { source: 'opencode' as const, items: [{ content: 'task', status: 'in_progress' as const }] } }
|
||||
expect(() => append('7', 1, { ...checklistEvent, checklist: { source: 'invalid' as never, items: [] } })).toThrow()
|
||||
expect(append('7', 1, checklistEvent)).toBe(true)
|
||||
expect(append('7', 1, checklistEvent)).toBe(false)
|
||||
expect(database.getConversation(conversationId).messages[1]?.runtimeChecklist).toEqual(checklistEvent.checklist)
|
||||
expect(append('7', 2, { ...checklistEvent, checklist: { source: 'opencode', items: [] } })).toBe(true)
|
||||
append('8', 0, {
|
||||
requestId: taskId,
|
||||
type: 'done'
|
||||
@@ -3845,6 +3871,7 @@ describe('AssistantDatabase', () => {
|
||||
expect(recoveredMessage).toMatchObject({
|
||||
content: recoveredText,
|
||||
reasoning: '先验证状态',
|
||||
runtimeChecklist: { source: 'opencode', items: [] },
|
||||
state: 'complete',
|
||||
artifactIds: [
|
||||
'00000000-0000-4000-8000-000000000707'
|
||||
@@ -3961,6 +3988,7 @@ describe('AssistantDatabase', () => {
|
||||
id: assistantMessageId,
|
||||
content: recoveredText,
|
||||
state: 'error',
|
||||
runtimeChecklist: { source: 'opencode', items: [] },
|
||||
status: '远端请求已不存在'
|
||||
})
|
||||
expect(reopened.listRecoverableRemoteTasks()).toEqual([])
|
||||
|
||||
@@ -275,6 +275,7 @@ type MessageMetadata = {
|
||||
queueItemId?: string
|
||||
status?: string
|
||||
reasoning?: ConversationSnapshot['messages'][number]['reasoning']
|
||||
runtimeChecklist?: ConversationMessage['runtimeChecklist']
|
||||
blocks?: ConversationSnapshot['messages'][number]['blocks']
|
||||
displayCaptureTruncated?: boolean
|
||||
contextCompression?: ConversationSnapshot['messages'][number]['contextCompression']
|
||||
@@ -1171,6 +1172,7 @@ function toConversationSnapshot(
|
||||
role: message.role,
|
||||
content: message.content,
|
||||
reasoning: metadata.reasoning,
|
||||
runtimeChecklist: metadata.runtimeChecklist,
|
||||
blocks: metadata.blocks,
|
||||
displayCaptureTruncated:
|
||||
metadata.displayCaptureTruncated,
|
||||
@@ -1205,6 +1207,7 @@ function serializeConversationMessageMetadata(
|
||||
queueItemId: message.queueItemId,
|
||||
status: message.status,
|
||||
reasoning: message.reasoning,
|
||||
runtimeChecklist: message.runtimeChecklist,
|
||||
blocks: message.blocks,
|
||||
displayCaptureTruncated: message.displayCaptureTruncated,
|
||||
contextCompression: message.contextCompression,
|
||||
@@ -1373,6 +1376,8 @@ function reduceRecoveredAgentEvent(
|
||||
blocks,
|
||||
status: undefined
|
||||
}
|
||||
} else if (event.type === 'checklist') {
|
||||
next = { ...message, runtimeChecklist: event.checklist }
|
||||
} else if (event.type === 'status') {
|
||||
next = { ...message, status: event.message }
|
||||
} else if (event.type === 'context-compression') {
|
||||
@@ -5015,6 +5020,7 @@ export class AssistantDatabase {
|
||||
createdAt: metadata.createdAt ?? Date.parse(row.created_at),
|
||||
status: metadata.status,
|
||||
reasoning: metadata.reasoning,
|
||||
runtimeChecklist: metadata.runtimeChecklist,
|
||||
blocks: metadata.blocks,
|
||||
displayCaptureTruncated:
|
||||
metadata.displayCaptureTruncated,
|
||||
@@ -5429,6 +5435,7 @@ export class AssistantDatabase {
|
||||
metadata.createdAt ?? Date.parse(row.created_at),
|
||||
status: metadata.status,
|
||||
reasoning: metadata.reasoning,
|
||||
runtimeChecklist: metadata.runtimeChecklist,
|
||||
blocks: metadata.blocks,
|
||||
displayCaptureTruncated:
|
||||
metadata.displayCaptureTruncated,
|
||||
|
||||
Reference in New Issue
Block a user