docs: 交付文档、维护指引与端到端回归

补齐 README、开发计划、维护交接与 E2E 用例,执行最终验收。
This commit is contained in:
WangShen007 committed 2026-10-21 21:40:00 +08:00
1 parent 5be5ca3376
commit 308332eb94
216 files changed
+37038

No files matched your search

@@ -0,0 +1,26 @@
import { test, expect, channels, chatsWithAgent, setAlwaysClosed, setAlwaysOpen, unique, AI_REPLY } from '@e2e'
// Changes the org's business hours, so it runs in the serial `org` project and
// reopens the org afterwards (setup leaves it open 24/7).
test.describe('outside business hours', () => {
test.use({ agentSettings: { transferToHuman: true } })
test.afterEach(async ({ adminApi, org }) => { await setAlwaysOpen(adminApi, org.orgId) })
test('a customer asking for a person is told the team will follow up, and the chat waits in the team\'s queue @handover', async ({ agent, llm, adminApi, request, org, handoverTeam }) => {
await setAlwaysClosed(adminApi, org.orgId)
await llm.script(agent, (s) => s.otherwise().handOver('We are closed now; the team will get back to you.'))
const chat = (await channels.WhatsApp({ api: adminApi, request, agentId: agent.id })).chatWith(unique('Nora'))
await chat.send('I need to talk to someone')
await expect.poll(() => chat.replies(), AI_REPLY).toEqual(['We are closed now; the team will get back to you.'])
// Queued for the team, unclaimed — whoever opens up next picks it up.
const [conversation] = await chatsWithAgent(adminApi, agent.id)
expect(conversation).toMatchObject({ status: 'transferred', group_id: (await handoverTeam()).groupId, user_id: null })
// The reply was written knowing the team is closed.
const prompts = (await llm.requests(agent)).map((r) => r.lastUser).join('\n')
expect(prompts).toContain('Currently within business hours: False')
})
})
+30
View File
@@ -0,0 +1,30 @@
import { test, expect, env, unique, AI_REPLY } from '@e2e'
test.describe('attachments', () => {
test.use({ agentSettings: { transferToHuman: true, allowAttachments: true } })
test('a customer sends a file to the teammate, who can open it — and only through a signed link @handover', async ({ agent, widget, inbox, llm, request }) => {
await llm.script(agent, (s) => s.otherwise().handOver('Let me connect you with a teammate.'))
await widget.embed()
await widget.open()
await widget.send('I need a person')
await widget.shareContactDetails()
await inbox.goto()
await inbox.openConversationWith(widget.customer.name)
await inbox.takeOver()
await inbox.reply('Please send me the receipt.')
await expect(widget.lastAgentMessage()).toHaveText('Please send me the receipt.', AI_REPLY)
const content = `Receipt ${unique('R')} — total 42.00`
await widget.sendFile({ name: 'receipt.txt', mimeType: 'text/plain', content }, 'Here it is')
const link = inbox.attachment('receipt.txt')
await expect(link).toBeVisible(AI_REPLY)
const href = await link.getAttribute('href')
expect((await (await inbox.page.request.get(href!)).text())).toBe(content)
// The stored file is not reachable without the signature.
const unsigned = new URL(href!, 'http://x').pathname
expect((await request.get(`${env.backendUrl}${unsigned}`)).status()).toBe(403)
})
})
+30
View File
@@ -0,0 +1,30 @@
import { test, expect, expectUnchanged, AI_REPLY } from '@e2e'
test.describe('human handover', () => {
test.use({ agentSettings: { transferToHuman: true } })
test('a customer asks for a person, the teammate\'s reply reaches them, and the AI stays out of it @smoke @handover', async ({ agent, widget, inbox, llm }) => {
await llm.script(agent, (s) => s
.when(/human|person/i).handOver('Let me connect you with a teammate.')
.otherwise().reply('Happy to help!'))
await widget.embed()
await widget.open()
await widget.send('I want to talk to a human')
await widget.shareContactDetails()
await inbox.goto()
await inbox.openConversationWith(widget.customer.name)
await inbox.takeOver()
await inbox.reply('Hi, this is Sam. How can I help?')
await expect(widget.lastAgentMessage()).toHaveText('Hi, this is Sam. How can I help?', AI_REPLY)
const modelCalls = (await llm.requests(agent)).length
await widget.send('My parcel never arrived')
await expect(inbox.lastCustomerMessage()).toContainText('My parcel never arrived')
// A person owns the chat now: the model is not asked, and no bot answer appears.
await expectUnchanged('model calls after takeover', async () => (await llm.requests(agent)).length)
expect((await llm.requests(agent))).toHaveLength(modelCalls)
await expect(widget.lastAgentMessage()).toHaveText('Hi, this is Sam. How can I help?')
})
})
+20
View File
@@ -0,0 +1,20 @@
import { test, expect, channels, chatsWithAgent, createGroup, createUser, unique, AI_REPLY } from '@e2e'
test('with nobody from the team online, the customer is told the team will follow up, and the chat waits in its queue @handover', async ({ makeAgent, llm, adminApi, request }) => {
// A team whose only member has never signed in, so nobody is online.
const id = unique('offline')
const member = await createUser(adminApi, { name: `Offline ${id}`, email: `${id}@e2e.example.com`, password: 'E2e-Offline-Passw0rd!', role: 'Agent' })
const groupId = await createGroup(adminApi, `Team ${id}`, [member])
const agent = await makeAgent({ transferToHuman: true }, [groupId])
await llm.script(agent, (s) => s.otherwise().handOver('Our team will get back to you shortly.'))
const chat = (await channels.WhatsApp({ api: adminApi, request, agentId: agent.id })).chatWith(unique('Nora'))
await chat.send('I need to talk to someone')
await expect.poll(() => chat.replies(), AI_REPLY).toEqual(['Our team will get back to you shortly.'])
const [conversation] = await chatsWithAgent(adminApi, agent.id)
expect(conversation).toMatchObject({ status: 'transferred', group_id: groupId, user_id: null })
// The reply was written knowing nobody is online.
const prompts = (await llm.requests(agent)).map((r) => r.lastUser).join('\n')
expect(prompts).toContain('(0 online)')
})