diff --git a/frontend/src/i18n/locales/en.json b/frontend/src/i18n/locales/en.json index 93cc27a60..895e378fc 100644 --- a/frontend/src/i18n/locales/en.json +++ b/frontend/src/i18n/locales/en.json @@ -2730,6 +2730,20 @@ "reasonUpstreamToolError": "the provider tool returned an error", "reasonBrokerUnavailable": "the broker was unavailable", "reasonBrokerError": "the broker failed", + "reasonToolDrift": "the provider changed this tool since Commonly checked it", + "reasonToolUnavailable": "this connection's tool list does not include this tool", + "reasonToolNotFound": "this tool is not registered with Commonly", + "reasonToolChangedSinceApproval": "the tool changed after approval was asked for, so it needs asking again", + "reasonArgsChangedSinceApproval": "the arguments no longer match the ones sent for approval", + "reasonRepoChangedSinceApproval": "the connected repository changed after approval was asked for", + "reasonPrChangedSinceApproval": "the pull request changed after approval was asked for", + "reasonPrHeadUnavailable": "the pull request's latest commit could not be read", + "reasonTaskPodMismatch": "this grant works only in its task's pod", + "reasonTaskClaimRequired": "the agent has to claim the bound task first", + "reasonTaskClosed": "the bound task closed after this grant was issued", + "reasonTaskNotFound": "the bound task no longer exists", + "reasonAgentIdentityRequired": "the call did not say which agent made it", + "reasonGrantNotFound": "the grant no longer exists", "owner": "owner", "readInTesting": "read in testing", "readOrWrite": "read, or read and write", diff --git a/frontend/src/i18n/locales/zh-CN.json b/frontend/src/i18n/locales/zh-CN.json index 595f37c7e..cf87a12ec 100644 --- a/frontend/src/i18n/locales/zh-CN.json +++ b/frontend/src/i18n/locales/zh-CN.json @@ -2733,6 +2733,20 @@ "reasonUpstreamToolError": "服务商工具返回了错误", "reasonBrokerUnavailable": "代理服务不可用", "reasonBrokerError": "代理服务出错", + "reasonToolDrift": "服务商在 Commonly 核对后改动了此工具", + "reasonToolUnavailable": "此连接的工具列表中没有这个工具", + "reasonToolNotFound": "此工具未在 Commonly 中注册", + "reasonToolChangedSinceApproval": "工具在申请审批后有变动,需要重新审批", + "reasonArgsChangedSinceApproval": "参数与提交审批时的不一致", + "reasonRepoChangedSinceApproval": "连接的仓库在申请审批后有变动", + "reasonPrChangedSinceApproval": "PR 在申请审批后有变动", + "reasonPrHeadUnavailable": "无法读取 PR 的最新提交", + "reasonTaskPodMismatch": "此授权只能在其任务所在的 Pod 中使用", + "reasonTaskClaimRequired": "智能体需要先认领绑定的任务", + "reasonTaskClosed": "绑定的任务在授权签发后关闭过", + "reasonTaskNotFound": "绑定的任务已不存在", + "reasonAgentIdentityRequired": "调用没有说明是哪个智能体发起的", + "reasonGrantNotFound": "授权已不存在", "owner": "所有者", "readInTesting": "只读功能测试中", "readOrWrite": "读取,或读取和写入", diff --git a/frontend/src/v2/__tests__/ConnectorsPage.tools.test.tsx b/frontend/src/v2/__tests__/ConnectorsPage.tools.test.tsx index 068195762..7ab653dd2 100644 --- a/frontend/src/v2/__tests__/ConnectorsPage.tools.test.tsx +++ b/frontend/src/v2/__tests__/ConnectorsPage.tools.test.tsx @@ -428,6 +428,49 @@ describe('Connectors page: tools', () => { ]); }); + // The broker records any refusal it throws as the line's reason, and these were + // shown as raw codes: a hosted tool that drifted, an approved call whose PR or + // arguments moved, a task-bound grant used off its task (F18, 11-02 prod walk). + // Every code is listed, so dropping any one label turns this red. + const REFUSAL_LABELS = [ + ['tool_drift', 'the provider changed this tool since Commonly checked it', '服务商在 Commonly 核对后改动了此工具'], + ['tool_unavailable', 'this connection\'s tool list does not include this tool', '此连接的工具列表中没有这个工具'], + ['tool_not_found', 'this tool is not registered with Commonly', '此工具未在 Commonly 中注册'], + ['definition_digest_mismatch', 'the tool changed after approval was asked for, so it needs asking again', '工具在申请审批后有变动,需要重新审批'], + ['args_digest_mismatch', 'the arguments no longer match the ones sent for approval', '参数与提交审批时的不一致'], + ['repo_mismatch', 'the connected repository changed after approval was asked for', '连接的仓库在申请审批后有变动'], + ['merge_head_mismatch', 'the pull request changed after approval was asked for', 'PR 在申请审批后有变动'], + ['merge_head_unavailable', 'the pull request\'s latest commit could not be read', '无法读取 PR 的最新提交'], + ['task_pod_mismatch', 'this grant works only in its task\'s pod', '此授权只能在其任务所在的 Pod 中使用'], + ['task_claim_required', 'the agent has to claim the bound task first', '智能体需要先认领绑定的任务'], + ['task_closed', 'the bound task closed after this grant was issued', '绑定的任务在授权签发后关闭过'], + ['task_not_found', 'the bound task no longer exists', '绑定的任务已不存在'], + ['agent_identity_required', 'the call did not say which agent made it', '调用没有说明是哪个智能体发起的'], + ['grant_not_found', 'the grant no longer exists', '授权已不存在'], + ]; + it.each([['en', 1], ['zh-CN', 2]])('F18: every refusal the broker records reads as words, in %s', async (language, column) => { + await i18nReady; + await act(async () => { await i18n.changeLanguage(language); }); + try { + const refused = { + ...trail, + calls: REFUSAL_LABELS.map(([reason], i) => ( + { ...trail.calls[0], callId: `r${i}`, outcome: 'refused', reason, at: iso(-60000 * (i + 1)) } + )), + }; + mockTools({ trails: { grant_live: refused } }); + renderPage(); + const drawer = await openTool('GitHub'); + fireEvent.click(drawer.querySelector('.v2-connector-row__selection')); + const aside = await screen.findByRole('complementary'); + await waitFor(() => expect(within(aside).getAllByRole('listitem')).toHaveLength(REFUSAL_LABELS.length)); + expect(Array.from(aside.querySelectorAll('.v2-tools__trail-reason')).map((node) => node.textContent)) + .toEqual(REFUSAL_LABELS.map((row) => ` · ${row[column]}`)); + } finally { + await act(async () => { await i18n.changeLanguage('en'); }); + } + }); + it('TASK-050: a human member in the audience is named, not called "an agent"', async () => { mockTools({ grants: [{ ...grantLive, effectiveAudience: ['u1', 'a1'] }], seats: { p1: [scout] } }); renderPage(); diff --git a/frontend/src/v2/connectors/useToolConnectors.ts b/frontend/src/v2/connectors/useToolConnectors.ts index 1d0bee736..50a957f9b 100644 --- a/frontend/src/v2/connectors/useToolConnectors.ts +++ b/frontend/src/v2/connectors/useToolConnectors.ts @@ -251,6 +251,20 @@ export const useToolConnectors = ( upstream_tool_error: t('tools.reasonUpstreamToolError', { defaultValue: 'the provider tool returned an error' }), broker_unavailable: t('tools.reasonBrokerUnavailable', { defaultValue: 'the broker was unavailable' }), broker_error: t('tools.reasonBrokerError', { defaultValue: 'the broker failed' }), + tool_drift: t('tools.reasonToolDrift', { defaultValue: 'the provider changed this tool since Commonly checked it' }), + tool_unavailable: t('tools.reasonToolUnavailable', { defaultValue: "this connection's tool list does not include this tool" }), + tool_not_found: t('tools.reasonToolNotFound', { defaultValue: 'this tool is not registered with Commonly' }), + definition_digest_mismatch: t('tools.reasonToolChangedSinceApproval', { defaultValue: 'the tool changed after approval was asked for, so it needs asking again' }), + args_digest_mismatch: t('tools.reasonArgsChangedSinceApproval', { defaultValue: 'the arguments no longer match the ones sent for approval' }), + repo_mismatch: t('tools.reasonRepoChangedSinceApproval', { defaultValue: 'the connected repository changed after approval was asked for' }), + merge_head_mismatch: t('tools.reasonPrChangedSinceApproval', { defaultValue: 'the pull request changed after approval was asked for' }), + merge_head_unavailable: t('tools.reasonPrHeadUnavailable', { defaultValue: 'the pull request\'s latest commit could not be read' }), + task_pod_mismatch: t('tools.reasonTaskPodMismatch', { defaultValue: 'this grant works only in its task\'s pod' }), + task_claim_required: t('tools.reasonTaskClaimRequired', { defaultValue: 'the agent has to claim the bound task first' }), + task_closed: t('tools.reasonTaskClosed', { defaultValue: 'the bound task closed after this grant was issued' }), + task_not_found: t('tools.reasonTaskNotFound', { defaultValue: 'the bound task no longer exists' }), + agent_identity_required: t('tools.reasonAgentIdentityRequired', { defaultValue: 'the call did not say which agent made it' }), + grant_not_found: t('tools.reasonGrantNotFound', { defaultValue: 'the grant no longer exists' }), }; // Own keys only: a code such as `constructor` or `__proto__` must show as itself, not as an inherited member. return Object.prototype.hasOwnProperty.call(labels, reason) ? labels[reason] : reason;