mirror of
https://github.com/NanmiCoder/cc-haha
synced 2026-07-16 13:03:31 +08:00
DingTalk AI cards can receive all intermediate text before the final message_complete event. In that case the message buffer is empty, but the platform card still needs an explicit terminal update to leave the inputing state. Constraint: DingTalk AI cards expose completion separately from buffered text delivery Rejected: Rely on MessageBuffer.complete only | an empty buffer cannot finalize an active card Confidence: high Scope-risk: narrow Directive: Keep IM terminal-state updates independent from whether a text buffer has pending content Tested: bun test adapters/common/__tests__/message-buffer.test.ts adapters/dingtalk/__tests__/stream-state.test.ts adapters/dingtalk/__tests__/ai-card.test.ts Tested: bunx tsc -p adapters/tsconfig.json --noEmit Tested: bun run check:adapters Tested: cd desktop && bun run build Tested: bun run check:native Not-tested: bun run check:coverage is blocked by existing desktop pages.test.tsx sidebar expectation unrelated to IM
79 lines
2.8 KiB
TypeScript
79 lines
2.8 KiB
TypeScript
import { describe, expect, it } from 'bun:test'
|
|
import { MessageBuffer } from '../../common/message-buffer.js'
|
|
import { finishAndResetDingTalkStreamingState, resetDingTalkStreamingState } from '../stream-state.js'
|
|
|
|
describe('DingTalk streaming state', () => {
|
|
it('drops the active AI card stream when permission interrupts output ordering', async () => {
|
|
let resetCalled = false
|
|
const buffer = new MessageBuffer(async () => {}, 100, 1000)
|
|
const originalReset = buffer.reset.bind(buffer)
|
|
buffer.reset = () => {
|
|
resetCalled = true
|
|
originalReset()
|
|
}
|
|
|
|
const state = {
|
|
aiCardBuffers: new Map([['chat-1', buffer]]),
|
|
streamingCards: new Map([['chat-1', Promise.resolve(null)]]),
|
|
streamingCardText: new Map([['chat-1', 'pre-permission text']]),
|
|
}
|
|
|
|
resetDingTalkStreamingState(state, 'chat-1')
|
|
|
|
expect(resetCalled).toBe(true)
|
|
expect(state.aiCardBuffers.has('chat-1')).toBe(false)
|
|
expect(state.streamingCards.has('chat-1')).toBe(false)
|
|
expect(state.streamingCardText.has('chat-1')).toBe(false)
|
|
})
|
|
|
|
it('completes the existing stream before dropping it for a permission request', async () => {
|
|
const flushed: Array<{ text: string; complete: boolean }> = []
|
|
let finalized = false
|
|
const buffer = new MessageBuffer(
|
|
async (text, complete) => {
|
|
flushed.push({ text, complete })
|
|
},
|
|
100,
|
|
1000,
|
|
)
|
|
buffer.append('pre-permission text')
|
|
|
|
const state = {
|
|
aiCardBuffers: new Map([['chat-1', buffer]]),
|
|
streamingCards: new Map([['chat-1', Promise.resolve(null)]]),
|
|
streamingCardText: new Map([['chat-1', 'already streamed']]),
|
|
finalize: async () => {
|
|
finalized = true
|
|
},
|
|
}
|
|
|
|
await finishAndResetDingTalkStreamingState(state, 'chat-1')
|
|
|
|
expect(flushed).toEqual([{ text: 'pre-permission text', complete: true }])
|
|
expect(finalized).toBe(true)
|
|
expect(state.aiCardBuffers.has('chat-1')).toBe(false)
|
|
expect(state.streamingCards.has('chat-1')).toBe(false)
|
|
expect(state.streamingCardText.has('chat-1')).toBe(false)
|
|
})
|
|
|
|
it('finalizes an already-flushed card even when the message buffer is empty', async () => {
|
|
let finalized = false
|
|
const buffer = new MessageBuffer(async () => {}, 100, 1000)
|
|
const state = {
|
|
aiCardBuffers: new Map([['chat-1', buffer]]),
|
|
streamingCards: new Map([['chat-1', Promise.resolve(null)]]),
|
|
streamingCardText: new Map([['chat-1', 'already streamed']]),
|
|
finalize: async () => {
|
|
finalized = true
|
|
},
|
|
}
|
|
|
|
await finishAndResetDingTalkStreamingState(state, 'chat-1')
|
|
|
|
expect(finalized).toBe(true)
|
|
expect(state.aiCardBuffers.has('chat-1')).toBe(false)
|
|
expect(state.streamingCards.has('chat-1')).toBe(false)
|
|
expect(state.streamingCardText.has('chat-1')).toBe(false)
|
|
})
|
|
})
|