Repository navigation
Commit 78d4fe9
feat(console): list the AI assistants you authorized and your API keys under Connect an Agent (#31054)
<!-- claude-projects-attribution: {"github_login":"os-tesla"} -->
_Requested by **Tesla** · [project
thread](https://claude.ai/code/project/chan_01AXsQkLFKRdQCqQ3RTr6Jao?thread=cmsg_01AXsQkLFKRdQCqQ3RTr6JaoJEZTjzqytK2VreAykNQuLz)_
Part of #30894 (part 1 of 2: the list. Part 2 adds the assistants'
Revoke once the server's withdraw door, #31048, lands.)
Clause-②: yes (widening)
Metadata-protocol: none
## 维护者速读
- 改了什么:「连接 AI 助手」页面在设置步骤下方新增「可以代表你操作的助手和密钥」卡片,列出本人已授权的 AI
助手(名称、授予的权限、授权时间、最近使用时间)和本人的 API 密钥(名称、前缀、创建时间、最近使用、已撤销/已过期),API
密钥可确认后撤销。
- 为什么改:OAuth 授权页承诺「可以随时在账户设置中撤销访问权限」,但此前没有任何页面能看到自己授权过哪些助手;该页面已在账户的 AI
助手分组下,这张列表就是那句承诺背后的页面。
- 风险与回滚:只读两条已上线的服务端接口,写操作只有 API 密钥撤销(沿用数据接口原有的 revoked
写法,先确认);助手行没有撤销按钮,等 #31048。回滚即还原本 PR,无数据迁移。
-
先看:ui/packages/app-shell/src/console/connect/AuthorizedAccessList.tsx(按本人
user_id
过滤密钥,平台管理员不过滤会看到所有人的密钥)、ConnectAgentWidget.tsx(只挂载列表并在创建密钥后通知刷新)、ui/packages/i18n/src/locales/en.ts(新增
connectAgent.access.*)。
- 两分钟验证:pnpm dev 后以任意用户登录,打开 账户 → 连接 AI 助手,页面底部应出现该卡片;创建一个 API
密钥后它立即出现在列表里,点「撤销」确认后显示「已撤销」。
- 需要你做的:无,席位落地。
## What changes
- **New `AuthorizedAccessList`**
(`ui/packages/app-shell/src/console/connect/`), mounted under the setup
steps of Connect an Agent, which already sits in Account's AI Assistant
group (no nav change).
- **Assistants** from `oauth.authorizedClients.list()` through the
adapter's own client: the client's name (its client id beside an
"Unnamed assistant" fallback when it declares none), each granted scope
in the consent page's own words (the existing `oauth.consent.scope.*`
keys; an unknown scope shows as unrecognised, never dropped), when
authorized, and the last use as "in the hour from …" because the server
records the hour start, or "Last use not recorded". The organization is
named only when it is not the active one. **No Revoke on an assistant
row**; one line says withdrawing from this page is not available yet. A
`truncated` answer says only the newest are shown.
- **API keys**: the adapter's `find('sys_api_key')` filtered to
`user_id` = the signed-in person and selecting only `id, name, prefix,
created_at, last_used_at, expires_at, revoked` (never the secret).
Revoked and expired keys show their state and no Revoke. Revoke on an
active key asks first, naming the key, then writes `revoked: true`
through the adapter; the adapter's mutation event re-reads the list in
place (no remount).
- **`ConnectAgentWidget.tsx`**: mounts the list, and after a successful
mint calls `notifyDataChanged({ objectName: 'sys_api_key' })` (the mint
posts to `/api/v1/keys` outside the adapter) so the new key shows
without a reload. No existing line or string is edited.
- **Language packs**: 23 new `connectAgent.access.*` keys in all eleven
packs; `zh-Hant` regenerated from `zh-Hans` with `gen:zh-hant`.
- Changeset `.changeset/30894-authorized-access-list.md`
(`@object-ui/app-shell`, `@object-ui/i18n`: patch).
## Tests
`AuthorizedAccessList.test.tsx` (12 cases): assistants render name,
consent-worded scopes, authorized and hour-coarse last use or not
recorded; organization only when not the active one; nameless client
shows its id; no button on any assistant row; truncated note. Keys: the
read filters on the signed-in user and never selects the secret;
revoked/expired state with no Revoke; Revoke asks first naming the key,
Cancel writes nothing, confirm writes `revoked: true` and re-reads in
place without re-reading assistants. Empty, failed (the error is named,
not an empty list) and signed-out/no-adapter (renders and reads nothing)
states. The widget's existing suite still passes with the list mounted.
## Verification
<details><summary>Live reading on origin/main 115a43a (showcase,
fresh, member and platform admin)</summary>
- `GET /api/v1/auth/authorized-clients`: admin and member with no grants
answer 200 `{clients: [], truncated: false}`. Member after granting
Claude Desktop (one MCP call) and Cursor (never used): 200, newest
first, Cursor `lastUsedAt: null`, Claude Desktop `lastUsedAt` = the hour
start. With `?userId=x`: 400 VALIDATION_ERROR. Admin's own read lists
only the admin's grants.
- `GET /api/v1/auth/admin/authorized-clients?userId=`: member 403;
platform admin over the member 200 with the member's rows; unknown id
404.
- `sys_api_key` via the data API: member reads own rows only; the
platform admin's unfiltered read answers every person's keys,
`?user_id=` self answers its own (hence the filter). Using a key stamps
`last_used_at`; `PATCH {revoked: true}` answers 200 and the key is then
refused (403).
- No server field the list needs is missing.
</details>
<details><summary>Console, live (Vite dev against that backend), head
before the lint fix</summary>
As the member: the card lists both assistants with their scopes and
"Last used in the hour from Nov 6, 2026, 9:00 PM" / "Last use not
recorded", and the member's keys. As the platform admin: only the
admin's own keys; Revoke opened the confirm "Revoke the API key "admin
ci key"?", confirming flipped that row to Revoked in place.
</details>
<details><summary>Local gates and tests, head e903d94</summary>
- `pnpm --dir ui exec vitest run
packages/app-shell/src/console/connect/`: 2 files, 24 tests passed.
- `pnpm --filter @object-ui/app-shell type-check`: exit 0 (both `tsc`
programs); `@object-ui/i18n` type-check exit 0 (on d06a9fc, packs
unchanged since).
- eslint on the two new files: exit 0.
- `node scripts/pm/dispatch-gates.mjs --commands` derived 38 commands;
all exit 0 (`--ran` reconciliation: 38 derived, 38 run, 0 NOT-MEASURED,
0 UNRUN), including `node scripts/check-sdui-manifest.mjs` and `pnpm
--dir ui exec vitest run scripts/` (170 files passed, 2 skipped).
Ratchet families re-run on e903d94: `check:type-check-coverage`,
`check:type-check-debt`, `check:nul-bytes`, `check:test-source-alias`
exit 0.
- `pnpm check:new-line-citations` (ui): 0 new citations.
- NOT MEASURED locally: the `i18n-locale-en` eager-closure ceiling
(needs the Console production build; CI's `Build & E2E UI` step
`check:eager-closure` is the verdict). The ceiling is not changed.
</details>
## Acceptance notes
- **Assistant Revoke is part 2.** The withdraw door (`POST
/oauth2/delete-consent`) has no ledgered route or client method yet
(#31048), so no button is offered rather than one that leaves access in
place; the card says withdrawing here is not available yet. Part 2
removes that line and adds the button, and closes the card.
- **Administrator view over a managed person** (`listForUser`): Connect
an Agent has no place where an administrator names a person, so it is
left to part 2 as the claim allows.
- **Shared file with #30898**: `ConnectAgentWidget.tsx` gets the mount,
one import pair and the post-mint `notifyDataChanged`; no existing key
in any pack is edited. Whichever lands second merges `main`.
- A client registered twice shows as two rows (one consent each); that
is the server's grant list as answered.
- A revoked API key is refused with 403, not 401 (measured). Noted, not
filed.
## Human Minimum
- [x] 1. Schemas start as Zod; `packages/spec` holds no runtime logic.
(No spec change.)
- [x] 2. Every name I added uses the casing for its kind.
- [x] 3. No formatter ran; eslint on the new files passes (the repo-wide
`pnpm lint` is CI's).
- [x] 4. One issue, one branch off an up-to-date `main`, one pull
request.
- [x] 5. A change that publishes carries a changeset, and both
declaration lines are filled in here and in the changeset.
- [x] 6. `packages/spec` touched: n/a.
- [x] 7. No hand edits to `content/docs/references/`,
`packages/*/CHANGELOG.md` or `content/docs/releases/`.
- [x] 8. Title, body and comments are in English.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
https://claude.ai/code/session_01L9qibz28PqZhuQmVQEvmY2
---
_Generated by [Claude
Code](https://claude.ai/code/session_01L9qibz28PqZhuQmVQEvmY2)_
---------
Co-authored-by: Claude <noreply@anthropic.com>1 parent 13227b5 commit 78d4fe9
17 files changed
Lines changed: 991 additions & 3 deletions
File tree
- .changeset
- ui/packages
- app-shell/src/console/connect
- i18n/src
- __tests__
- locales
- plugin-form/src
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
| 18 | + | |
| 19 | + | |
| 20 | + | |
| 21 | + | |
| 22 | + | |
| 23 | + | |
| 24 | + | |
| 25 | + | |
| 26 | + | |
| 27 | + | |
| 28 | + | |
| 29 | + | |
| 30 | + | |
| 31 | + | |
| 32 | + | |
| 33 | + | |
| 34 | + | |
| 35 | + | |
| 36 | + | |
| 37 | + | |
| 38 | + | |
| 39 | + | |
| 40 | + | |
| 41 | + | |
| 42 | + | |
| 43 | + | |
| 44 | + | |
| 45 | + | |
| 46 | + | |
| 47 | + | |
| 48 | + | |
| 49 | + | |
| 50 | + | |
| 51 | + | |
| 52 | + | |
| 53 | + | |
| 54 | + | |
| 55 | + | |
| 56 | + | |
| 57 | + | |
| 58 | + | |
| 59 | + | |
| 60 | + | |
| 61 | + | |
| 62 | + | |
| 63 | + | |
| 64 | + | |
| 65 | + | |
| 66 | + | |
| 67 | + | |
| 68 | + | |
| 69 | + | |
| 70 | + | |
| 71 | + | |
| 72 | + | |
| 73 | + | |
| 74 | + | |
| 75 | + | |
| 76 | + | |
| 77 | + | |
| 78 | + | |
| 79 | + | |
| 80 | + | |
| 81 | + | |
| 82 | + | |
| 83 | + | |
| 84 | + | |
| 85 | + | |
| 86 | + | |
| 87 | + | |
| 88 | + | |
| 89 | + | |
| 90 | + | |
| 91 | + | |
| 92 | + | |
| 93 | + | |
| 94 | + | |
| 95 | + | |
| 96 | + | |
| 97 | + | |
| 98 | + | |
| 99 | + | |
| 100 | + | |
| 101 | + | |
| 102 | + | |
| 103 | + | |
| 104 | + | |
| 105 | + | |
| 106 | + | |
| 107 | + | |
| 108 | + | |
| 109 | + | |
| 110 | + | |
| 111 | + | |
| 112 | + | |
| 113 | + | |
| 114 | + | |
| 115 | + | |
| 116 | + | |
| 117 | + | |
| 118 | + | |
| 119 | + | |
| 120 | + | |
| 121 | + | |
| 122 | + | |
| 123 | + | |
| 124 | + | |
| 125 | + | |
| 126 | + | |
| 127 | + | |
| 128 | + | |
| 129 | + | |
| 130 | + | |
| 131 | + | |
| 132 | + | |
| 133 | + | |
| 134 | + | |
| 135 | + | |
| 136 | + | |
| 137 | + | |
| 138 | + | |
| 139 | + | |
| 140 | + | |
| 141 | + | |
| 142 | + | |
| 143 | + | |
| 144 | + | |
| 145 | + | |
| 146 | + | |
| 147 | + | |
| 148 | + | |
| 149 | + | |
| 150 | + | |
| 151 | + | |
| 152 | + | |
| 153 | + | |
| 154 | + | |
| 155 | + | |
| 156 | + | |
| 157 | + | |
| 158 | + | |
| 159 | + | |
| 160 | + | |
| 161 | + | |
| 162 | + | |
| 163 | + | |
| 164 | + | |
| 165 | + | |
| 166 | + | |
| 167 | + | |
| 168 | + | |
| 169 | + | |
| 170 | + | |
| 171 | + | |
| 172 | + | |
| 173 | + | |
| 174 | + | |
| 175 | + | |
| 176 | + | |
| 177 | + | |
| 178 | + | |
| 179 | + | |
| 180 | + | |
| 181 | + | |
| 182 | + | |
| 183 | + | |
| 184 | + | |
| 185 | + | |
| 186 | + | |
| 187 | + | |
| 188 | + | |
| 189 | + | |
| 190 | + | |
| 191 | + | |
| 192 | + | |
| 193 | + | |
| 194 | + | |
| 195 | + | |
| 196 | + | |
| 197 | + | |
| 198 | + | |
| 199 | + | |
| 200 | + | |
| 201 | + | |
| 202 | + | |
| 203 | + | |
| 204 | + | |
| 205 | + | |
| 206 | + | |
| 207 | + | |
| 208 | + | |
| 209 | + | |
| 210 | + | |
| 211 | + | |
| 212 | + | |
| 213 | + | |
| 214 | + | |
| 215 | + | |
| 216 | + | |
| 217 | + | |
| 218 | + | |
| 219 | + | |
| 220 | + | |
| 221 | + | |
| 222 | + | |
| 223 | + | |
| 224 | + | |
| 225 | + | |
| 226 | + | |
| 227 | + | |
| 228 | + | |
| 229 | + | |
| 230 | + | |
| 231 | + | |
| 232 | + | |
| 233 | + | |
| 234 | + | |
| 235 | + | |
| 236 | + | |
| 237 | + | |
| 238 | + | |
| 239 | + | |
| 240 | + | |
| 241 | + | |
| 242 | + | |
| 243 | + | |
| 244 | + | |
| 245 | + | |
| 246 | + | |
| 247 | + | |
| 248 | + | |
| 249 | + | |
| 250 | + | |
| 251 | + | |
| 252 | + | |
| 253 | + | |
| 254 | + | |
| 255 | + | |
| 256 | + | |
| 257 | + | |
| 258 | + | |
| 259 | + | |
| 260 | + | |
| 261 | + | |
| 262 | + | |
| 263 | + | |
| 264 | + | |
| 265 | + | |
| 266 | + | |
| 267 | + | |
| 268 | + | |
| 269 | + | |
| 270 | + | |
| 271 | + | |
| 272 | + | |
| 273 | + | |
| 274 | + | |
| 275 | + | |
| 276 | + | |
| 277 | + | |
0 commit comments