|
@@ -1313,6 +1313,61 @@ it('applies independent Bot approval choices on the same Profile and does not re
|
|
|
await finished(runs[0]!.id)
|
|
await finished(runs[0]!.id)
|
|
|
})
|
|
})
|
|
|
|
|
|
|
|
|
|
+it('automatically allows this request and the next one while persisting only the owning Bot preference', async () => {
|
|
|
|
|
+ const bot = agent('选择自动允许'), peer = agent('同一基础机器人')
|
|
|
|
|
+ reply = (socket, params) => socket.send(JSON.stringify({ method: 'event', params: { type: 'approval.request', session_id: params.session_id, payload: { request_id: 'first-approval', message: '生成图片' } } }))
|
|
|
|
|
+ server.on('connection', socket => socket.on('message', raw => {
|
|
|
|
|
+ const frame = JSON.parse(String(raw))
|
|
|
|
|
+ if (frame.method !== 'approval.respond') return
|
|
|
|
|
+ if (frame.params.request_id === 'first-approval') socket.send(JSON.stringify({ method: 'event', params: { type: 'approval.request', session_id: frame.params.session_id, payload: { request_id: 'next-approval', message: '保存图片' } } }))
|
|
|
|
|
+ else completeReply(socket, frame.params, '自动完成')
|
|
|
|
|
+ }))
|
|
|
|
|
+ const run = runtime.send(owner, direct(bot.id).id, { requestId: randomUUID(), content: '生成图片' })
|
|
|
|
|
+ await vi.waitFor(() => expect(store.list<WorkspaceInteraction>(owner, 'interaction')).toHaveLength(1))
|
|
|
|
|
+ const request = store.list<WorkspaceInteraction>(owner, 'interaction')[0]!
|
|
|
|
|
+ await expect(runtime.respond(other, request.id, 'auto')).rejects.toThrow('记录不存在')
|
|
|
|
|
+ expect(store.require<any>(owner, 'agent', bot.id).approvalPolicy).toBe('ask')
|
|
|
|
|
+ await runtime.respond(owner, request.id, 'auto')
|
|
|
|
|
+ await finished(run.id)
|
|
|
|
|
+ expect(requests.filter(frame => frame.method === 'approval.respond').map(frame => frame.params.choice)).toEqual(['once', 'once'])
|
|
|
|
|
+ expect(store.require<any>(owner, 'agent', bot.id).approvalPolicy).toBe('allow')
|
|
|
|
|
+ expect(store.require<any>(owner, 'agent', peer.id).approvalPolicy).toBe('ask')
|
|
|
|
|
+ store.updateAgent(owner, bot.id, { approvalPolicy: 'ask' })
|
|
|
|
|
+ await runtime.respond(owner, request.id, 'auto')
|
|
|
|
|
+ expect(store.require<any>(owner, 'agent', bot.id).approvalPolicy).toBe('ask')
|
|
|
|
|
+ expect(requests.filter(frame => frame.method === 'approval.respond')).toHaveLength(2)
|
|
|
|
|
+})
|
|
|
|
|
+
|
|
|
|
|
+it('preserves current team grants for approval-only edits without reviving revoked grants', () => {
|
|
|
|
|
+ const bot = agent('有效工具授权')
|
|
|
|
|
+ store.put(owner, 'turn', 'valid-grant', { id: 'valid-grant', agentId: bot.id, status: 'waiting', teamManagementRevision: bot.revision })
|
|
|
|
|
+ store.put(owner, 'turn', 'revoked-grant', { id: 'revoked-grant', agentId: bot.id, status: 'running', teamManagementRevision: bot.revision - 1 })
|
|
|
|
|
+ const updated = store.updateAgent(owner, bot.id, { approvalPolicy: 'allow' })
|
|
|
|
|
+ expect(store.require<any>(owner, 'turn', 'valid-grant').teamManagementRevision).toBe(updated.revision)
|
|
|
|
|
+ expect(store.require<any>(owner, 'turn', 'revoked-grant').teamManagementRevision).toBe(bot.revision - 1)
|
|
|
|
|
+ store.updateAgent(owner, bot.id, { instructions: '新规则' })
|
|
|
|
|
+ expect(store.require<any>(owner, 'turn', 'valid-grant').teamManagementRevision).toBe(updated.revision)
|
|
|
|
|
+})
|
|
|
|
|
+
|
|
|
|
|
+it('keeps an explicit automatic preference but reports an unconfirmed reply without retrying it', async () => {
|
|
|
|
|
+ const bot = agent('自动设置与答复状态')
|
|
|
|
|
+ reply = (socket, params) => socket.send(JSON.stringify({ method: 'event', params: { type: 'approval.request', session_id: params.session_id, payload: { request_id: 'uncertain-auto', message: '需要确认' } } }))
|
|
|
|
|
+ runtime.send(owner, direct(bot.id).id, { requestId: randomUUID(), content: '继续' })
|
|
|
|
|
+ await vi.waitFor(() => expect(store.list<WorkspaceInteraction>(owner, 'interaction')).toHaveLength(1))
|
|
|
|
|
+ const interaction = store.list<WorkspaceInteraction>(owner, 'interaction')[0]!
|
|
|
|
|
+ const rpc = WorkspaceGateway.prototype.rpc
|
|
|
|
|
+ const spy = vi.spyOn(WorkspaceGateway.prototype, 'rpc').mockImplementation(function (this: WorkspaceGateway, method, params) {
|
|
|
|
|
+ return method === 'approval.respond' ? Promise.reject(new Error('connection lost')) : rpc.call(this, method, params)
|
|
|
|
|
+ })
|
|
|
|
|
+ try {
|
|
|
|
|
+ await expect(runtime.respond(owner, interaction.id, 'auto')).rejects.toMatchObject({ code: 'approval_auto_response_failed' })
|
|
|
|
|
+ expect(store.require<any>(owner, 'agent', bot.id).approvalPolicy).toBe('allow')
|
|
|
|
|
+ expect(store.require<any>(owner, 'interaction', interaction.id)).toMatchObject({ resolved: false, responseState: 'uncertain', answer: 'auto' })
|
|
|
|
|
+ await expect(runtime.respond(owner, interaction.id, 'auto')).rejects.toMatchObject({ code: 'interaction_uncertain' })
|
|
|
|
|
+ expect(spy.mock.calls.filter(call => call[0] === 'approval.respond')).toHaveLength(1)
|
|
|
|
|
+ } finally { spy.mockRestore() }
|
|
|
|
|
+})
|
|
|
|
|
+
|
|
|
it('leaves clarifications and already pending approvals for the user after a policy change', async () => {
|
|
it('leaves clarifications and already pending approvals for the user after a policy change', async () => {
|
|
|
const bot = agent('更改审批')
|
|
const bot = agent('更改审批')
|
|
|
let kind = 'approval'
|
|
let kind = 'approval'
|