Skip to content

Commit 2ce2646

Browse files
feat(console): Connect an AI Assistant names the connected assistant and its last call; plain-words page subtitle (#31198)
<!-- claude-projects-attribution: {"github_login":"os-project-manager"} --> _Requested by **Leehom** · [project thread](https://claude.ai/code/project/chan_01TenzsRopZj4jMbvjX8uz1X?thread=cmsg_01TenzsRopZj4jMbvjX8uz1XUajPDb8jMKTWyncZpJ2wTS)_ Fixes #30898 Clause-②: yes (widening) Metadata-protocol: none ## 维护者速读 - 改了什么:「连接 AI 助手」页示例请求下方那一行,在本人已连接后改为说明连的是哪个助手(或哪把 API 密钥)、最近一次调用是什么时候;没连接时仍是「先试第一条」。页头副标题去掉 MCP、行级安全等协议词,改成大白话。 - 为什么改:卡 #30898 期望 2 与期望 3 的剩余部分:连接之后回到页面应能看出连接成功了;页头仍在说「支持 MCP 的 AI 客户端」。 - 风险与回滚:只读两份已有记录(本人已授权的助手、本人的 API 密钥),不新增服务端接口;读失败时退回「先试第一条」,绝不误报「未连接」。回滚即还原本 PR,无数据迁移。 - 先看:ui/packages/app-shell/src/console/connect/connectedAccess.ts(「已连接」的唯一判定与排序)、ConnectAgentWidget.tsx 的 ConnectedStatus、ui/packages/i18n/src/locales/en.ts(新增 6 个键)。 - 两分钟验证:pnpm dev 后以授权过助手或建过 API 密钥的用户打开 账户 → 连接 AI 助手,示例请求下方显示「已连接:Claude Desktop。最近一次调用在 … 起的那一小时内。」;撤销后该行回到「先试第一条」。 - 需要你做的:无,席位落地。 ## What changes - **The Connected line** (`ConnectAgentWidget.tsx`, `ConnectedStatus`). Under the example requests, once the signed-in person has connected, the page names what is connected and when it last made a call, the most recent call first: - an authorized assistant by its name ("Connected: Claude Desktop."), with its last call "in the hour from" the recorded hour, because the server records `lastUsedAt` as the start of an hour; - an API key by its name ("Connected with the API key "doubao key"."), with its last call as a moment (`last_used_at`); - "No call yet: ask it the first request above to check the connection." when nothing is recorded; - one more line pointing to the list at the bottom of the page when more than one thing is connected. With nothing connected the line stays the try-first hint PR #31052 shipped. While the read is out the line draws nothing, so it does not flip after a moment. - **One "has connected" read** (`connectedAccess.ts`, `useConnectedAccess`). It reads only the records the "What can act as you" list already reads: `oauth.authorizedClients.list()` and the person's own `sys_api_key` rows (filtered to `user_id`, as the list does). A revoked key or one past `expires_at` is not a connection. A failed read is never reported as "nothing connected": with the other read empty the answer is `unknown` (the page shows the try-first hint), and with the other read holding a connection that one is named. The combining rule is the exported pure function `combineConnectedReads`. #30895's Home card and checklist row are meant to read this same hook. No new server route. - **It follows the page's own Revoke.** An API-key revoke already announces `sys_api_key` on the data bus. The assistants list's withdraw now announces `sys_oauth_consent` (the record a grant is) after every withdraw, answered or refused, so the Connected line reads again. - `accessRecords.ts` (new): the `sys_api_key` columns, the `ApiKeyRow` / `AuthorizedClient` types and the expiry rule, moved out of `AuthorizedAccessList.tsx` so the list and the hook share them (the list keeps exporting components only, so it adds no react-refresh warning). - **Page subtitle** (Expected 3 leftover, wording only): `packages/mcp/src/connect-ui.ts` now reads "Work in this system from Claude, Doubao and other AI assistants. Each one acts as you: it sees and changes only what your own permissions allow." `pages.connect_agent.subtitle` follows in en, es-ES, ja-JP, zh-CN and zh-Hant, and the four translated locales' source digests are re-recorded (`collectSourceHashes(en)` gives `51c467af44024a1b`). - **Language packs**, all eleven: `connectAgent.capabilities.connected`, `connectedKey`, `lastCallHour`, `lastCallAt`, `noCallYet`, `moreConnected` added; no existing key reworded or removed. `zh-Hant` regenerated from `zh-Hans` with `gen:zh-hant`. - Changeset `.changeset/30898-connect-agent-connected-line.md` (`@object-ui/app-shell`, `@object-ui/i18n`, `@objectstack/mcp`, `@objectstack/platform-objects`: patch). ## Why The card's Expected 2: "Once the person has connected, the page says so: which assistant, and when it last made a call. With nothing connected yet, it says what to try first." It waited on #30894, which closed today with the authorized-assistants read and Revoke. Expected 3 left the page-header subtitle saying "MCP-capable AI client" and "row-level security". ## Tests - `ConnectAgentWidget.connected-30898.test.tsx`, 14 cases, rendering the whole page and driving the real controls: §1 nothing connected (no grants, only a revoked and an expired key) keeps the try-first line and reads only the person's own keys; §2 an assistant is named with its hour, a nameless one by the fallback, "no call yet" when none is recorded; §3 the most recent call wins (a key that called after every assistant is named as an API key at its moment; a revoked key that called later still is not named; more than one points to the list); §4 a failed read falls back to the try-first line, or still names what the other read found; §5 clicking Revoke and confirming, on the only assistant and on the only key, turns the line back to the try-first hint; §6 `combineConnectedReads` on its own (loading, unknown, not connected only when both answered empty, ordering). - `ConnectAgentWidget.keyName-13355.test.tsx`: the capability-key list pin gains the six keys, so every pack must carry each of them and none may be an English copy. - `de-quote-pairing-3876.test.ts`: 109 → 110 paired „…“ spans, for `connectAgent.capabilities.connectedKey` („{{name}}“), recorded in the pin's history comment as earlier additions were. - Ablations (one-off, `scripts/ablation-replace.mjs`, restored blob equal to HEAD each time): deleting the withdraw's `notifyDataChanged({ objectName: 'sys_oauth_consent', … })` turned §5's assistant case red (1 failed, 13 passed); replacing the dead-key filter with `rows.map(keyEntry)` turned six cases red (6 failed, 8 passed). ## Acceptance notes - **"Last call" exists on both records**, so nothing is invented: `lastUsedAt` on an authorized assistant (the start of the hour, which is why the line says "in the hour from") and `last_used_at` on an API key. - **An API key counts as a connection.** The card names "an authorization or an API key the person holds" for #30895's checklist row, and the order reads both records; a key is named as an API key, so a CI key reads as what it is. - **The page now reads both lists twice** (the Connected line and the list at the bottom each read them once). The list was left as PR #31084 shipped it; the cost is one more `GET /auth/authorized-clients` and one more `sys_api_key` find on this page. - `Clause-②: yes (widening)` is the seat's measured arm for new catalogue keys in the published `@object-ui/i18n`; no key is removed. ## Verification <details><summary>Live, showcase on this branch (Vite console against it), admin and member, en and zh</summary> - Administrator, en, after minting "doubao key" and calling `GET /api/v1/meta/objects` with it (200): page header "Work in this system from Claude, Doubao and other AI assistants. Each one acts as you: it sees and changes only what your own permissions allow."; Connected line "Connected with the API key "doubao key". Its last call was at Nov 7, 2026, 4:22 PM." - Administrator, zh: header 「在 Claude、豆包等 AI 助手里使用本系统。助手始终以你的身份操作:只能查看和修改你有权限的内容。」; line 「已用 API 密钥“doubao key”连接。最近一次调用在 2026年11月7日 16:22。」 - Member (created with `admin/create-user`, password changed), en, nothing connected: no Connected line; the try-first line is shown. - Member with one grant: the `sys_oauth_application` and `sys_oauth_consent` rows were written straight into the dev database (Claude Desktop, `last_used_at` 16:00), not through an OAuth flow; `GET /auth/authorized-clients` then answered that grant. zh: 「已连接:Claude Desktop。最近一次调用在 2026年11月7日 16:00 起的那一小时内。」 en, then Revoke on its row and confirm in the dialog: the Connected line left, the try-first line came back, and `GET /auth/authorized-clients` answered `[]`. </details> <details><summary>Local tests and gates</summary> - `pnpm --dir ui exec vitest run packages/app-shell/src/console/connect/ packages/i18n/ packages/app-shell/src/__tests__/product-neutral-renderer.ratchet.test.ts`: 102 files passed, 1832 tests passed, 13 skipped (head a93d7f2; later commits touch only the changeset). - `pnpm --filter @object-ui/app-shell run type-check` and `@object-ui/i18n` type-check: exit 0. eslint on `console/connect/`: 0 errors, 3 warnings (all existing, in `ConnectAgentWidget.tsx`'s untouched `any`s). ui `check:new-line-citations`: 0 new. - `pnpm --filter @objectstack/platform-objects test`: 119 files, 1656 passed. `pnpm --filter @objectstack/mcp test`: 60 files, 767 passed. - `node scripts/pm/dispatch-gates.mjs --commands` derived 66 families; all 66 exit 0; `--ran` reconciliation: 66 derived, 66 run, 0 NOT-MEASURED, 0 UNRUN. Two needed a second run: `pnpm --dir ui exec vitest run scripts/` was SIGKILLed by the box's memory limit on the first pass and then passed (170 files, 2 skipped); `pnpm check:dual-build-cjs-loads` answered exit 3 PREREQUISITE NOT MET (seven packages without dist) until they were built, then exit 0. The changeset families and `check:nul-bytes` were re-run on the final head 7005f76, all exit 0. </details> ## 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 touched 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_01FEkQ5pMhSEyeAKtt6btsBe --- _Generated by [Claude Code](https://claude.ai/code/session_01FEkQ5pMhSEyeAKtt6btsBe)_ --------- Co-authored-by: Claude <noreply@anthropic.com>
1 parent 30002df commit 2ce2646

29 files changed

Lines changed: 688 additions & 55 deletions
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
---
2+
'@object-ui/app-shell': patch
3+
'@object-ui/i18n': patch
4+
'@objectstack/mcp': patch
5+
'@objectstack/platform-objects': patch
6+
---
7+
8+
feat(console): Connect an AI Assistant names the connected assistant and its last call; plain-words page subtitle
9+
10+
Clause-②: yes (widening)
11+
Metadata-protocol: none
12+
13+
- **`@object-ui/app-shell`**: once the signed-in person has connected, the line under the example requests on Connect an AI Assistant names what is connected and when it last made a call, the most recent call first: an authorized assistant by its name, with its last call "in the hour from" the recorded hour, or an API key by its name, with its last call as a moment; "no call yet" when none is recorded, and a pointer to the list at the bottom when more than one is connected. With nothing connected (or when it cannot be read) the line stays the try-first hint. It reads the records the "What can act as you" list reads (`oauth.authorizedClients.list()` and the person's own `sys_api_key` rows; a revoked or expired key is not a connection) through one hook, `useConnectedAccess`, and follows a Revoke made on the page.
14+
- **`@object-ui/i18n`**: `connectAgent.capabilities.connected`, `connectedKey`, `lastCallHour`, `lastCallAt`, `noCallYet` and `moreConnected` added in all eleven packs.
15+
- **`@objectstack/mcp`**: the Connect an AI Assistant page header no longer speaks of MCP-capable clients or row-level security: "Work in this system from Claude, Doubao and other AI assistants. Each one acts as you: it sees and changes only what your own permissions allow."
16+
- **`@objectstack/platform-objects`**: `pages.connect_agent.subtitle` follows in en, es-ES, ja-JP, zh-CN and zh-Hant, with the source digests re-recorded.

‎packages/mcp/src/connect-ui.ts‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -74,8 +74,8 @@ export const CONNECT_AGENT_PAGE: Page = {
7474
properties: {
7575
title: 'Connect an AI Assistant',
7676
subtitle:
77-
'Give any MCP-capable AI client governed access to this system — ' +
78-
'every call runs under the caller\'s own permissions and row-level security.',
77+
'Work in this system from Claude, Doubao and other AI assistants. ' +
78+
'Each one acts as you: it sees and changes only what your own permissions allow.',
7979
// `icon` removed here (#6946): `page:header` never drew one — retired from the spec.
8080
},
8181
},

‎packages/platform-objects/src/apps/translations/en.ts‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -290,8 +290,8 @@ export const en: TranslationData = {
290290
connect_agent: {
291291
label: 'Connect an AI Assistant',
292292
subtitle:
293-
'Give any MCP-capable AI client governed access to this system — '
294-
+ "every call runs under the caller's own permissions and row-level security.",
293+
'Work in this system from Claude, Doubao and other AI assistants. '
294+
+ 'Each one acts as you: it sees and changes only what your own permissions allow.',
295295
},
296296

297297
// The platform's own record pages (`@objectstack/platform-objects/pages`,

‎packages/platform-objects/src/apps/translations/es-ES.source-hashes.ts‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -182,7 +182,7 @@ export const esESSourceHashes: Readonly<Record<string, string>> = {
182182
'pages.cloud_connection_settings.label': '3a7caad0cb98e71d',
183183
'pages.cloud_connection_settings.subtitle': 'f5cdd253e8ffef9f',
184184
'pages.connect_agent.label': 'a4542422fb5e8a68',
185-
'pages.connect_agent.subtitle': 'b0d7c6d174511bcb',
185+
'pages.connect_agent.subtitle': '51c467af44024a1b',
186186
'pages.sys_user_detail.label': '6c0d22a931e235bd',
187187
'pages.sys_organization_detail.label': '3e55836156e1c1de',
188188
};

‎packages/platform-objects/src/apps/translations/es-ES.ts‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -191,8 +191,8 @@ export const esES: TranslationData = {
191191
connect_agent: {
192192
label: 'Conectar un asistente de IA',
193193
subtitle:
194-
'Concede a cualquier cliente de IA compatible con MCP acceso controlado a este sistema: '
195-
+ 'cada llamada se ejecuta con los permisos propios de quien la realiza y con seguridad a nivel de fila.',
194+
'Trabaja en este sistema desde Claude, Doubao y otros asistentes de IA. '
195+
+ 'Cada uno actúa como tú: solo ve y cambia lo que tus propios permisos permiten.',
196196
},
197197

198198
// Platform record pages (#15743) -- see `en.ts` for the key set.

‎packages/platform-objects/src/apps/translations/ja-JP.source-hashes.ts‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -182,7 +182,7 @@ export const jaJPSourceHashes: Readonly<Record<string, string>> = {
182182
'pages.cloud_connection_settings.label': '3a7caad0cb98e71d',
183183
'pages.cloud_connection_settings.subtitle': 'f5cdd253e8ffef9f',
184184
'pages.connect_agent.label': 'a4542422fb5e8a68',
185-
'pages.connect_agent.subtitle': 'b0d7c6d174511bcb',
185+
'pages.connect_agent.subtitle': '51c467af44024a1b',
186186
'pages.sys_user_detail.label': '6c0d22a931e235bd',
187187
'pages.sys_organization_detail.label': '3e55836156e1c1de',
188188
};

‎packages/platform-objects/src/apps/translations/ja-JP.ts‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -190,7 +190,7 @@ export const jaJP: TranslationData = {
190190
connect_agent: {
191191
label: 'AI アシスタントを接続',
192192
subtitle:
193-
'MCP 対応の AI クライアントにこのシステムへの統制されたアクセスを許可します。すべての呼び出しは、呼び出し元自身の権限と行レベルセキュリティのもとで実行されます。',
193+
'Claude や Doubao などの AI アシスタントからこのシステムを使えます。アシスタントは常にあなたとして動作し、あなたの権限で許された範囲だけを参照・変更します。',
194194
},
195195

196196
// Platform record pages (#15743) -- see `en.ts` for the key set.

‎packages/platform-objects/src/apps/translations/zh-CN.source-hashes.ts‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -182,7 +182,7 @@ export const zhCNSourceHashes: Readonly<Record<string, string>> = {
182182
'pages.cloud_connection_settings.label': '3a7caad0cb98e71d',
183183
'pages.cloud_connection_settings.subtitle': 'f5cdd253e8ffef9f',
184184
'pages.connect_agent.label': 'a4542422fb5e8a68',
185-
'pages.connect_agent.subtitle': 'b0d7c6d174511bcb',
185+
'pages.connect_agent.subtitle': '51c467af44024a1b',
186186
'pages.sys_user_detail.label': '6c0d22a931e235bd',
187187
'pages.sys_organization_detail.label': '3e55836156e1c1de',
188188
};

‎packages/platform-objects/src/apps/translations/zh-CN.ts‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -195,7 +195,7 @@ export const zhCN: TranslationData = {
195195
},
196196
connect_agent: {
197197
label: '连接 AI 助手',
198-
subtitle: '让任意支持 MCP 的 AI 客户端受控访问本系统——每次调用都在调用者自身的权限与行级安全范围内执行。',
198+
subtitle: '在 Claude、豆包等 AI 助手里使用本系统。助手始终以你的身份操作:只能查看和修改你有权限的内容。',
199199
},
200200

201201
// Platform record pages (#15743) -- see `en.ts` for the key set.

‎packages/platform-objects/src/apps/translations/zh-Hant.source-hashes.ts‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -190,7 +190,7 @@ export const zhHantSourceHashes: Readonly<Record<string, string>> = {
190190
'pages.cloud_connection_settings.label': '3a7caad0cb98e71d',
191191
'pages.cloud_connection_settings.subtitle': 'f5cdd253e8ffef9f',
192192
'pages.connect_agent.label': 'a4542422fb5e8a68',
193-
'pages.connect_agent.subtitle': 'b0d7c6d174511bcb',
193+
'pages.connect_agent.subtitle': '51c467af44024a1b',
194194
'pages.sys_user_detail.label': '6c0d22a931e235bd',
195195
'pages.sys_organization_detail.label': '3e55836156e1c1de',
196196
};

0 commit comments

Comments
 (0)