Merge pull request 'Tell removed members why sending is refused' (#121) from fix/multiuser-collab-followups into main
This commit was merged in pull request #121.
This commit is contained in:
+9
-2
@@ -1145,10 +1145,12 @@ export const store = {
|
||||
set({ outbox: enqueueMessageFront(state.outbox, { id, text, attachmentIds, attachmentNames: [], ...(taskRef ? { taskRef } : {}) }) })
|
||||
return 'busy'
|
||||
}
|
||||
store.toast(`发送失败:${e}`)
|
||||
const removed = isNotProjectMember(e)
|
||||
store.toast(removed ? '无法继续发送:你已不在该项目中或项目已关闭;历史对话仍可查看' : `发送失败:${e}`)
|
||||
set({ outbox: enqueueMessageFront(state.outbox, { id, text, attachmentIds,
|
||||
attachmentNames: attachmentIds.map(attachmentId => state.artifacts.find(artifact => artifact.id === attachmentId)?.name ?? '已添加材料'),
|
||||
...(taskRef ? { taskRef } : {}), deliveryError: '发送未成功,内容已保留;可修改后重试或移除' }) })
|
||||
...(taskRef ? { taskRef } : {}),
|
||||
deliveryError: removed ? '无法发送:你已不在该项目中或项目已关闭,内容已保留' : '发送未成功,内容已保留;可修改后重试或移除' }) })
|
||||
await store.reconcile()
|
||||
return 'rejected'
|
||||
} finally {
|
||||
@@ -1720,6 +1722,11 @@ function isConversationBusy(err: unknown): boolean {
|
||||
return String(err).includes('409:')
|
||||
}
|
||||
|
||||
/** 已被移出项目或项目已关闭:后端以 403 `conversation: not-project-member` 拒绝继续发送。 */
|
||||
function isNotProjectMember(err: unknown): boolean {
|
||||
return String(err).includes('not-project-member')
|
||||
}
|
||||
|
||||
function shellPhase() {
|
||||
const liveRunId = state.live?.runId ?? null
|
||||
return deriveShellPhase({
|
||||
|
||||
@@ -0,0 +1,57 @@
|
||||
// 被移出项目/项目关闭后的发送拒绝:403 `conversation: not-project-member` 走专用文案,
|
||||
// 其他失败保持通用文案(见 src/store.ts sendMessage 的 catch 分支)。
|
||||
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
|
||||
import { api } from '../src/api'
|
||||
import { store } from '../src/store'
|
||||
import type { Conversation, Message } from '../src/contracts'
|
||||
|
||||
const REMOVED_TOAST = '无法继续发送:你已不在该项目中或项目已关闭;历史对话仍可查看'
|
||||
const REMOVED_DELIVERY_ERROR = '无法发送:你已不在该项目中或项目已关闭,内容已保留'
|
||||
const GENERIC_DELIVERY_ERROR = '发送未成功,内容已保留;可修改后重试或移除'
|
||||
|
||||
function detail(id: string) {
|
||||
return {
|
||||
conversation: { id, projectId: 'p', title: id, createdAt: '', updatedAt: '' } as Conversation,
|
||||
messages: [] as Message[], runs: [], activeHitl: [], lastSeq: 0, resumeSeq: 0,
|
||||
}
|
||||
}
|
||||
class FakeEventSource {
|
||||
onopen = null
|
||||
onerror = null
|
||||
onmessage = null
|
||||
addEventListener() {}
|
||||
close() {}
|
||||
}
|
||||
|
||||
beforeEach(async () => {
|
||||
vi.stubGlobal('EventSource', FakeEventSource)
|
||||
vi.spyOn(api, 'conversationDetail').mockImplementation(async id => detail(id))
|
||||
vi.spyOn(api, 'getPlan').mockResolvedValue(null)
|
||||
vi.spyOn(api, 'getTodos').mockResolvedValue(null)
|
||||
vi.spyOn(api, 'listCards').mockResolvedValue([])
|
||||
vi.spyOn(api, 'listTasks').mockResolvedValue([])
|
||||
vi.spyOn(api, 'listArtifacts').mockResolvedValue([])
|
||||
vi.spyOn(api, 'surfaceList').mockResolvedValue([])
|
||||
vi.spyOn(api, 'projectSurfaces').mockResolvedValue([])
|
||||
vi.spyOn(api, 'listProjectCards').mockResolvedValue([])
|
||||
await store.openProject('p')
|
||||
await store.openConversation('a')
|
||||
})
|
||||
afterEach(() => { vi.useRealTimers(); vi.restoreAllMocks(); vi.unstubAllGlobals() })
|
||||
|
||||
describe('sendMessage rejection copy', () => {
|
||||
it('keeps the message in the outbox with the removed-member copy on 403 not-project-member', async () => {
|
||||
vi.spyOn(api, 'sendMessage').mockRejectedValue(new Error('403: conversation: not-project-member'))
|
||||
expect(await store.sendMessage('这条还要继续吗', [], 'kept-id')).toBe('rejected')
|
||||
expect(store.getState().outbox[0]).toMatchObject({ id: 'kept-id', text: '这条还要继续吗', attachmentIds: [] })
|
||||
expect(store.getState().outbox[0].deliveryError).toBe(REMOVED_DELIVERY_ERROR)
|
||||
expect(store.getState().toast).toBe(REMOVED_TOAST)
|
||||
})
|
||||
|
||||
it('keeps the generic copy for any other send failure', async () => {
|
||||
vi.spyOn(api, 'sendMessage').mockRejectedValue(new Error('500: upstream down'))
|
||||
expect(await store.sendMessage('这条还要继续吗', [], 'kept-id')).toBe('rejected')
|
||||
expect(store.getState().outbox[0].deliveryError).toBe(GENERIC_DELIVERY_ERROR)
|
||||
expect(store.getState().toast).toContain('发送失败:')
|
||||
})
|
||||
})
|
||||
Reference in New Issue
Block a user