diff --git a/frontend/src/i18n/locales/en.json b/frontend/src/i18n/locales/en.json index b825c537c..2fc82986e 100644 --- a/frontend/src/i18n/locales/en.json +++ b/frontend/src/i18n/locales/en.json @@ -2640,6 +2640,26 @@ "outcomeOk": "ok", "outcomeRefused": "refused", "outcomeSuperseded": "answered", + "reasonNotInAudience": "this seat is outside the grant audience", + "reasonToolNotAllowed": "this tool is not on the allow-list", + "reasonWriteNotAllowed": "writes are not granted", + "reasonGrantRevoked": "the grant was revoked", + "reasonGrantExpired": "the grant expired", + "reasonBudgetExhausted": "the call budget is spent", + "reasonApprovalUnavailable": "no one could be asked to approve it", + "reasonApprovalDeclined": "approval was declined", + "reasonApprovalExpired": "approval expired before the call ran", + "reasonInvalidArgs": "the arguments did not validate", + "reasonConnectionMismatch": "the connection no longer matches the grant", + "reasonReconnect": "the connection needs reconnecting", + "reasonCredentialMissing": "no credential is stored", + "reasonCredentialRejected": "the provider rejected the credential", + "reasonRefreshUnreachable": "the credential could not be refreshed", + "reasonProviderError": "the provider returned an error", + "reasonProviderUnreachable": "the provider could not be reached", + "reasonUpstreamToolError": "the provider tool returned an error", + "reasonBrokerUnavailable": "the broker was unavailable", + "reasonBrokerError": "the broker failed", "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 047e6c131..cba6caeaa 100644 --- a/frontend/src/i18n/locales/zh-CN.json +++ b/frontend/src/i18n/locales/zh-CN.json @@ -2643,6 +2643,26 @@ "outcomeOk": "成功", "outcomeRefused": "已拒绝", "outcomeSuperseded": "已回答", + "reasonNotInAudience": "此席位不在授权范围内", + "reasonToolNotAllowed": "此工具不在允许列表中", + "reasonWriteNotAllowed": "未授予写入权限", + "reasonGrantRevoked": "授权已撤销", + "reasonGrantExpired": "授权已过期", + "reasonBudgetExhausted": "调用次数已用完", + "reasonApprovalUnavailable": "无法请求人工批准", + "reasonApprovalDeclined": "审批已被拒绝", + "reasonApprovalExpired": "审批在调用运行前已过期", + "reasonInvalidArgs": "参数校验未通过", + "reasonConnectionMismatch": "连接与授权已不再匹配", + "reasonReconnect": "需要重新连接", + "reasonCredentialMissing": "未保存凭据", + "reasonCredentialRejected": "服务商拒绝了该凭据", + "reasonRefreshUnreachable": "无法刷新凭据", + "reasonProviderError": "服务商返回了错误", + "reasonProviderUnreachable": "无法连接服务商", + "reasonUpstreamToolError": "服务商工具返回了错误", + "reasonBrokerUnavailable": "代理服务不可用", + "reasonBrokerError": "代理服务出错", "owner": "所有者", "readInTesting": "只读功能测试中", "readOrWrite": "读取,或读取和写入", diff --git a/frontend/src/v2/__tests__/ConnectorsPage.tools.test.tsx b/frontend/src/v2/__tests__/ConnectorsPage.tools.test.tsx index 2bddc2c0b..ee23c8a83 100644 --- a/frontend/src/v2/__tests__/ConnectorsPage.tools.test.tsx +++ b/frontend/src/v2/__tests__/ConnectorsPage.tools.test.tsx @@ -315,7 +315,7 @@ describe('Connectors page: tools', () => { const lines = within(aside).getAllByRole('listitem'); expect(lines.map((line) => line.textContent)).toEqual([ 'Scout · github.list_issues · 成功2分钟前', - 'Scout · github.comment_on_issue · 已拒绝5分钟前', + 'Scout · github.comment_on_issue · 已拒绝 · 此席位不在授权范围内5分钟前', 'Scout · github.comment_on_issue · 等待人工确认7分钟前', 'Scout · github.close_issue · 已回答8分钟前', ]); @@ -386,7 +386,7 @@ describe('Connectors page: tools', () => { const lines = within(aside).getAllByRole('listitem'); expect(lines.map((line) => line.textContent)).toEqual([ 'Scout · github.list_issues · ok2m ago', - 'Scout · github.comment_on_issue · refused5m ago', + 'Scout · github.comment_on_issue · refused · this seat is outside the grant audience5m ago', 'Scout · github.comment_on_issue · awaiting a person7m ago', 'Scout · github.close_issue · answered8m ago', ]); @@ -406,6 +406,28 @@ describe('Connectors page: tools', () => { expect(axios.get).toHaveBeenCalledWith('/api/grants/grant_live/calls', expect.objectContaining({ headers: expect.any(Object) })); }); + it('TASK-188: a failed call says why it failed, and a code with no label shows as-is', async () => { + const failed = { + ...trail, + calls: [ + { ...trail.calls[0], callId: 'f1', outcome: 'failed', reason: 'upstream_tool_error', at: iso(-120000) }, + { ...trail.calls[0], callId: 'f2', outcome: 'failed', reason: 'some_new_code', at: iso(-300000) }, + { ...trail.calls[0], callId: 'f3', outcome: 'ok', reason: 'provider_unreachable', at: iso(-420000) }, + { ...trail.calls[0], callId: 'f4', outcome: 'failed', reason: 'constructor', at: iso(-600000) }, + ], + }; + mockTools({ trails: { grant_live: failed } }); + renderPage(); + const { aside } = await openRow('GitHub', 'View GitHub in Launch pod'); + await waitFor(() => expect(within(aside).getAllByRole('listitem')).toHaveLength(4)); + expect(within(aside).getAllByRole('listitem').map((line) => line.textContent)).toEqual([ + 'Scout · github.list_issues · failed · the provider tool returned an error2m ago', + 'Scout · github.list_issues · failed · some_new_code5m ago', + 'Scout · github.list_issues · ok7m ago', + 'Scout · github.list_issues · failed · constructor10m ago', + ]); + }); + 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/ToolPanels.tsx b/frontend/src/v2/connectors/ToolPanels.tsx index 4c0050ce9..10b22e02a 100644 --- a/frontend/src/v2/connectors/ToolPanels.tsx +++ b/frontend/src/v2/connectors/ToolPanels.tsx @@ -88,6 +88,7 @@ export const useToolPanels = (m: ToolConnectorsModel) => { irreversibleTools, asksFirst, outcomeLabel, + reasonLabel, modeLabel, entryGrantModes, entryModeCopy, @@ -598,7 +599,7 @@ export const useToolPanels = (m: ToolConnectorsModel) => {