feat: expand multi-runtime workflows for 0.10.0
GoodBuddy previously exposed Runtime capabilities, MCP assignments, plugin controls, context compaction, usage reporting, and task-completion behavior through incomplete or inconsistent paths. This release unifies managed OpenCode, Continue, and DeepSeek Harness controls; adds DSH plugin and image workflows; strengthens MCP and Runtime lifecycle bounds; fixes Windows notification activation; validates cross-architecture packages; and presents the approved bilingual four-section release notes. The DSH plugin marketplace remains a default-off preview whose trusted third-party code runs with current-user permissions. Ask remains read-only, Execute keeps approval controls, and context compaction may use the selected model without deleting GoodBuddy chat history. Release note: GoodBuddy 0.10.0 重点完善多 Runtime 工作流,统一 OpenCode、Continue 与 DeepSeek Harness 的能力、MCP、插件和上下文管理,并提升长对话、多会话与任务通知的连贯性。
This commit is contained in:
@@ -27,7 +27,10 @@ vi.mock('./runtime-discovery', () => ({
|
||||
detectRuntimeBinary: mocks.detectRuntimeBinary
|
||||
}))
|
||||
|
||||
import { ContinueAgentRuntime } from './continue-runtime'
|
||||
import {
|
||||
buildContinuePrompt,
|
||||
ContinueAgentRuntime
|
||||
} from './continue-runtime'
|
||||
|
||||
function createRuntime(): ContinueAgentRuntime {
|
||||
return new ContinueAgentRuntime({
|
||||
@@ -618,6 +621,151 @@ describe('ContinueAgentRuntime', () => {
|
||||
expect(prompt).not.toContain('old secret turn')
|
||||
})
|
||||
|
||||
it('keeps a persisted summary when its covered prefix rolls out of the bounded history window', () => {
|
||||
const history = Array.from({ length: 500 }, (_, index) => ({
|
||||
role:
|
||||
index % 2 === 0
|
||||
? ('user' as const)
|
||||
: ('assistant' as const),
|
||||
content: `recent message ${index}`
|
||||
}))
|
||||
const prompt = buildContinuePrompt({
|
||||
requestId: randomUUID(),
|
||||
conversationId: 'evicted-summary-conversation',
|
||||
prompt: 'continue',
|
||||
history,
|
||||
historyMessageIds: history.map(() => randomUUID()),
|
||||
contextCompressionState: {
|
||||
coveredHistoryDigest: createHash('sha256')
|
||||
.update(
|
||||
JSON.stringify([
|
||||
{ role: 'user', content: 'evicted question' },
|
||||
{ role: 'assistant', content: 'evicted answer' }
|
||||
])
|
||||
)
|
||||
.digest('hex'),
|
||||
coveredMessageCount: 2,
|
||||
coveredFromMessageId: randomUUID(),
|
||||
coveredThroughMessageId: randomUUID(),
|
||||
summary: 'persisted evicted facts'
|
||||
}
|
||||
})
|
||||
|
||||
expect(prompt).toContain('persisted evicted facts')
|
||||
expect(prompt).toContain('recent message 499')
|
||||
expect(prompt).not.toContain('evicted question')
|
||||
})
|
||||
|
||||
it('keeps a persisted summary when filtered messages shorten the bounded history window', () => {
|
||||
const history = Array.from({ length: 499 }, (_, index) => ({
|
||||
role:
|
||||
index % 2 === 0
|
||||
? ('user' as const)
|
||||
: ('assistant' as const),
|
||||
content: `filtered recent message ${index}`
|
||||
}))
|
||||
const prompt = buildContinuePrompt({
|
||||
requestId: randomUUID(),
|
||||
conversationId: 'filtered-evicted-summary-conversation',
|
||||
prompt: 'continue',
|
||||
history,
|
||||
historyMessageIds: history.map(() => randomUUID()),
|
||||
contextCompressionState: {
|
||||
coveredHistoryDigest: createHash('sha256')
|
||||
.update(
|
||||
JSON.stringify([
|
||||
{ role: 'user', content: 'evicted question' },
|
||||
{ role: 'assistant', content: 'evicted answer' }
|
||||
])
|
||||
)
|
||||
.digest('hex'),
|
||||
coveredMessageCount: 2,
|
||||
coveredFromMessageId: randomUUID(),
|
||||
coveredThroughMessageId: randomUUID(),
|
||||
summary: 'persisted facts after filtering'
|
||||
}
|
||||
})
|
||||
|
||||
expect(prompt).toContain('persisted facts after filtering')
|
||||
expect(prompt).toContain('filtered recent message 498')
|
||||
expect(prompt).not.toContain('evicted question')
|
||||
})
|
||||
|
||||
it('keeps a persisted summary when only its covered start rolls out of the history window', () => {
|
||||
const coveredThroughMessageId = randomUUID()
|
||||
const history = [
|
||||
{
|
||||
role: 'assistant' as const,
|
||||
content: 'covered answer still at window start'
|
||||
},
|
||||
...Array.from({ length: 499 }, (_, index) => ({
|
||||
role:
|
||||
index % 2 === 0
|
||||
? ('user' as const)
|
||||
: ('assistant' as const),
|
||||
content: `later message ${index}`
|
||||
}))
|
||||
]
|
||||
const prompt = buildContinuePrompt({
|
||||
requestId: randomUUID(),
|
||||
conversationId: 'partially-evicted-summary-conversation',
|
||||
prompt: 'continue',
|
||||
history,
|
||||
historyMessageIds: [
|
||||
coveredThroughMessageId,
|
||||
...history.slice(1).map(() => randomUUID())
|
||||
],
|
||||
contextCompressionState: {
|
||||
coveredHistoryDigest: createHash('sha256')
|
||||
.update(
|
||||
JSON.stringify([
|
||||
{ role: 'user', content: 'evicted covered question' },
|
||||
history[0]
|
||||
])
|
||||
)
|
||||
.digest('hex'),
|
||||
coveredMessageCount: 2,
|
||||
coveredFromMessageId: randomUUID(),
|
||||
coveredThroughMessageId,
|
||||
summary: 'persisted partially evicted facts'
|
||||
}
|
||||
})
|
||||
|
||||
expect(prompt).toContain('persisted partially evicted facts')
|
||||
expect(prompt).toContain('later message 498')
|
||||
expect(prompt).not.toContain('covered answer still at window start')
|
||||
})
|
||||
|
||||
it('rejects a persisted summary that contradicts the current bounded history window', () => {
|
||||
const coveredThroughMessageId = randomUUID()
|
||||
const history = Array.from({ length: 500 }, (_, index) => ({
|
||||
role:
|
||||
index % 2 === 0
|
||||
? ('user' as const)
|
||||
: ('assistant' as const),
|
||||
content: `conflicting message ${index}`
|
||||
}))
|
||||
const historyMessageIds = history.map(() => randomUUID())
|
||||
historyMessageIds[10] = coveredThroughMessageId
|
||||
const prompt = buildContinuePrompt({
|
||||
requestId: randomUUID(),
|
||||
conversationId: 'conflicting-summary-conversation',
|
||||
prompt: 'continue',
|
||||
history,
|
||||
historyMessageIds,
|
||||
contextCompressionState: {
|
||||
coveredHistoryDigest: '0'.repeat(64),
|
||||
coveredMessageCount: 2,
|
||||
coveredFromMessageId: randomUUID(),
|
||||
coveredThroughMessageId,
|
||||
summary: 'contradictory summary must not appear'
|
||||
}
|
||||
})
|
||||
|
||||
expect(prompt).toContain('conflicting message 499')
|
||||
expect(prompt).not.toContain('contradictory summary must not appear')
|
||||
})
|
||||
|
||||
it('falls back to bounded raw history when a persisted summary is stale', async () => {
|
||||
const runtime = createRuntime()
|
||||
for await (const _event of runtime.run(
|
||||
|
||||
Reference in New Issue
Block a user