Merge pull request #5915 from deepseek-harness/turtle/exp-badge-align-42f8ebeac170

fix(web): align and localize Auto review permission option
This commit is contained in:
Turtle authored and GitHub committed 2026-10-09 16:50:08 +08:00
commit 5876567dfc
28 files changed
+117 -91

No files matched your search

@@ -21,8 +21,8 @@
en: 237018634b5f9dcf
zh: cbc49d9c1b794065
/agent-note-experimental-auto-review-before-each-tool-call/decision/process-catalog-and-children:
en: 6ebe6f90241fd322
zh: eafdebc5277e83a1
en: 0042c645360c8361
zh: 5dba39466d376559
/agent-note-experimental-auto-review-before-each-tool-call/alternatives-considered:
en: fe97128ed05b9e43
zh: 4aaa344933fa89b4
@@ -58,7 +58,7 @@ A persisted Auto Session cannot publish when the complete integration is absent
The permission owner publishes one complete process catalog through generated `permissionPresets` Remote methods; the BFF explicitly mounts it and forwards a payload-free invalidation event. One browser directory subscribes before reading and serves both pickers. Epoch and connection-generation checks publish only the winning complete result. A winning failure or connection reset clears the previous snapshot; only a later existing read, notification, or reset retries. Disposed or stale settlements cannot publish. Each catalog invalidation dismisses the slash picker and its pending confirmation through the command owner while preserving the draft, so a picker waiting on its own read keeps its failure and retry state; reopening loads the current catalog. Session projection carries current selection only, so catalog installation or removal writes no Session event or sequence.
Auto appears with a superscript `EXP` badge. Both visible current-session pickers require the experimental confirmation; explicit `/permission auto` is already consent. The composer uses the generic Menu's existing portal placement to stay within the viewport while keeping its 218–360px bounds. The slash popup keeps `min(220px, 100%)` with `max-width: 100%`, including when a narrow composer collapses its trigger.
Auto appears with an `EXP` badge in picker options; the selected composer control omits it. Both visible current-session pickers require the experimental confirmation; explicit `/permission auto` is already consent. The composer uses the generic Menu's existing portal placement to stay within the viewport while keeping its 218–360px bounds. The slash popup keeps `min(220px, 100%)` with `max-width: 100%`, including when a narrow composer collapses its trigger.
The [delegation-time policy capture](../../../../packages/subagent/subagent/README.md) records Auto or Full access before the first await and appends that existing preset event after fork seeding and sandbox/approval overrides. One-shot and continuable creation share the rule; cold resume reads only the child log. Later parent switches do not alter that child, while a later child switch can win. Read Only and Workspace Write retain sandbox inheritance plus `approval: never` and may therefore remain `custom`. Auto children classify each call independently using the existing lineage and messages, without parent call metadata, delegation records, receipts, Header/descriptor additions, or a Session-format change. Out-of-process children retain their own permission systems after the parent delegation is allowed.
@@ -58,7 +58,7 @@ Reviewer 可以输出 reasoning blocks,随后恰好一个 JSON text block 和
Permission owner 通过生成的 `permissionPresets` Remote 方法发布一份完整进程目录;BFF 显式挂载它,并转发无 payload 的失效事件。一个浏览器目录在读取前订阅,为两个选择器提供数据。Epoch 与 connection-generation 检查只发布胜出的完整结果。胜出读取失败或 connection reset 会清空旧快照;只有后续既有读取、通知或 reset 才重试。已 dispose 或陈旧的结算不能发布。每次目录失效都通过命令 owner 关闭 slash 选择器与待确认对话框,同时保留草稿,因此等待自身读取的选择器仍保留失败与重试状态;重新打开时加载当前目录。Session 投影只携带当前选择,因此目录安装或移除不写 Session 事件或序号。
Auto 带右上标 `EXP`。两个可见当前会话选择器都要求实验确认;显式 `/permission auto` 已构成同意。Composer 使用通用 Menu 既有 portal 定位保持在视口内,同时保留 218–360px 边界。Slash popup 保留 `min(220px, 100%)` 与 `max-width: 100%`,窄 composer 将 trigger 折叠时也一样。
Auto 在选择器选项中带 `EXP` 标记;选中后的 composer 控件不显示该标记。两个可见当前会话选择器都要求实验确认;显式 `/permission auto` 已构成同意。Composer 使用通用 Menu 既有 portal 定位保持在视口内,同时保留 218–360px 边界。Slash popup 保留 `min(220px, 100%)` 与 `max-width: 100%`,窄 composer 将 trigger 折叠时也一样。
[委派时权限捕获](../../../../packages/subagent/subagent/README.zh.md)在首次 await 前记录 Auto 或 Full access,并在 fork seed 与 sandbox/approval override 之后追加该既有 preset event。单次与可继续创建共用此规则;cold resume 只读取 child 日志。后续 parent 切换不改变该 child,后续 child 自己的切换仍可胜出。Read Only 与 Workspace Write 保留 sandbox 继承加 `approval: never`,因此可能保持 `custom`。Auto child 使用既有 lineage 与消息独立分类每次调用,不增加父 call metadata、委派记录、receipt、Header/descriptor 字段或 Session format。进程外 child 在父委派获准后保留自己的权限系统。
+24 -15
View File
@@ -64,7 +64,7 @@ describe('web e2e: experimental Auto and Full access confirmation', () => {
const currentMenu = page.getByRole('menu')
await currentMenu.waitFor({ timeout: 10_000 })
expect(await currentMenu.getByRole('menuitem').allTextContents())
.toEqual(['仅可查看', '工作区内修改', '完全权限', 'Auto reviewEXP'])
.toEqual(['仅可查看', '工作区内修改', '完全权限', '自动审查EXP'])
await captureAutoReviewState(page, 'experimental-current-session-picker')
const currentSnapshot = await captureStableAria(page, '[role="menu"]', scaffold.workspaceCwd)
await compareOrRefreshGolden(CURRENT_SESSION_PICKER_EXPECTED, currentSnapshot, MODE)
@@ -77,10 +77,10 @@ describe('web e2e: experimental Auto and Full access confirmation', () => {
expect(currentMenuBox!.width).toBeGreaterThan(currentTriggerBox!.width)
expect(Math.abs(currentTriggerBox!.y - currentMenuBox!.y - currentMenuBox!.height - 4)).toBeLessThan(1)
await currentMenu.getByRole('menuitem', { name: 'Auto review EXP' }).click()
const autoDialog = page.getByRole('dialog', { name: '确认启用 Auto review(实验)?' })
await currentMenu.getByRole('menuitem', { name: '自动审查 EXP' }).click()
const autoDialog = page.getByRole('dialog', { name: '确认启用自动审查(实验)?' })
await autoDialog.waitFor({ timeout: 10_000 })
const autoEnable = autoDialog.getByRole('button', { name: '启用 Auto review' })
const autoEnable = autoDialog.getByRole('button', { name: '启用自动审查' })
expect(await autoEnable.isDisabled()).toBe(true)
expect(await autoDialog.getByText(/不使用沙箱/).count()).toBe(1)
expect(await autoDialog.getByText(/误放行或误拒绝/).count()).toBe(1)
@@ -105,7 +105,7 @@ describe('web e2e: experimental Auto and Full access confirmation', () => {
await slashPicker.waitFor({ timeout: 10_000 })
const slashRows = slashPicker.getByRole('option')
expect(await slashRows.count()).toBe(4)
expect(await slashPicker.getByRole('option', { name: 'Auto review EXP' }).count()).toBe(1)
expect(await slashPicker.getByRole('option', { name: '自动审查 EXP' }).count()).toBe(1)
await captureAutoReviewState(page, 'experimental-slash-picker')
const slashSnapshot = await captureStableAria(page, '[aria-label="/permission 选项"]', scaffold.workspaceCwd)
await compareOrRefreshGolden(SLASH_PICKER_EXPECTED, slashSnapshot, MODE)
@@ -119,15 +119,15 @@ describe('web e2e: experimental Auto and Full access confirmation', () => {
expect(slashPickerBox!.x).toBeCloseTo(composerBox!.x)
expect(Math.abs(composerBox!.y - slashPickerBox!.y - slashPickerBox!.height - 4)).toBeLessThan(1)
await slashPicker.getByRole('option', { name: 'Auto review EXP' }).click()
const slashDialog = page.getByRole('dialog', { name: '确认启用 Auto review(实验)?' })
await slashPicker.getByRole('option', { name: '自动审查 EXP' }).click()
const slashDialog = page.getByRole('dialog', { name: '确认启用自动审查(实验)?' })
await slashDialog.waitFor({ timeout: 10_000 })
expect(await slashDialog.count()).toBe(1)
expect(await slashPicker.count()).toBe(0)
await slashDialog.getByRole('checkbox', { name: '我已了解这些风险,并愿意继续' }).check()
await slashDialog.getByRole('button', { name: '启用 Auto review' }).click()
await slashDialog.getByRole('button', { name: '启用自动审查' }).click()
await expect.poll(() => access.getAttribute('aria-label'), { timeout: 10_000 })
.toBe('访问模式,当前:Auto review EXP')
.toBe('访问模式,当前:自动审查')
expect(await slashDialog.count()).toBe(0)
expect(await input.textContent()).toBe('')
await captureAutoReviewState(page, 'experimental-current-session')
@@ -188,12 +188,18 @@ describe('web e2e: experimental Auto and Full access confirmation', () => {
expect(menuBox.width).toBeLessThanOrEqual(360)
expect(menuBox.x).toBeGreaterThanOrEqual(0)
expect(menuBox.x + menuBox.width).toBeLessThanOrEqual(420)
for (const name of ['仅可查看', '工作区内修改', '完全权限', 'Auto review']) {
for (const name of ['仅可查看', '工作区内修改', '完全权限', '自动审查']) {
expect(await menu.getByText(name, { exact: true }).evaluate(node => node.scrollWidth <= node.clientWidth)).toBe(true)
}
await page.keyboard.press('Escape')
const input = page.locator('[data-composer-input]').first()
await writeComposerDraft(page, input, '/permission auto')
await input.press('Enter')
await expect.poll(() => access.getAttribute('aria-label')).toBe('访问模式,当前:自动审查')
expect(await access.getByText('自动审查', { exact: true }).isVisible()).toBe(false)
expect(await access.getByText('EXP', { exact: true }).count()).toBe(0)
expect(await access.getAttribute('title')).toBe('无沙箱运行;每次原生工具调用和 PTC 内层调用前由同一模型进行实验性审查。')
await writeComposerDraft(page, input, '/permission')
await input.press('Escape')
await input.press('Enter')
@@ -206,10 +212,13 @@ describe('web e2e: experimental Auto and Full access confirmation', () => {
expect(slashBox.x + slashBox.width).toBeLessThanOrEqual(420)
expect(await slash.evaluate(node => getComputedStyle(node).minWidth)).toBe('min(220px, 100%)')
expect(await slash.evaluate(node => getComputedStyle(node).maxWidth)).toBe('100%')
for (const name of ['仅可查看', '工作区内修改', '完全权限', 'Auto review']) {
for (const name of ['仅可查看', '工作区内修改', '完全权限', '自动审查']) {
expect(await slash.getByText(name, { exact: true }).evaluate(node => node.scrollWidth <= node.clientWidth)).toBe(true)
}
await page.keyboard.press('Escape')
await writeComposerDraft(page, input, '/permission danger-full-access')
await input.press('Enter')
await expect.poll(() => access.getAttribute('aria-label')).toBe('访问模式,当前:完全权限')
expect(tripwire.pageErrors).toEqual([])
})
@@ -219,7 +228,7 @@ describe('web e2e: experimental Auto and Full access confirmation', () => {
const access = page.locator('button[aria-label^="访问模式"]').first()
await writeComposerDraft(page, input, '/permission auto')
await input.press('Enter')
await expect.poll(() => access.getAttribute('aria-label')).toBe('访问模式,当前:Auto review EXP')
await expect.poll(() => access.getAttribute('aria-label')).toBe('访问模式,当前:自动审查')
expect(await page.getByRole('dialog').count()).toBe(0)
await writeComposerDraft(page, input, '/permission')
await input.press('Escape')
@@ -237,7 +246,7 @@ describe('web e2e: experimental Auto and Full access confirmation', () => {
await input.press('Enter')
await slash.waitFor()
expect(await slash.getByRole('option').count()).toBe(3)
expect(await slash.getByText('Auto review', { exact: true }).count()).toBe(0)
expect(await slash.getByText('自动审查', { exact: true }).count()).toBe(0)
await page.keyboard.press('Escape')
await access.click()
await expect.poll(() => page.getByRole('menuitem').allTextContents())
@@ -248,7 +257,7 @@ describe('web e2e: experimental Auto and Full access confirmation', () => {
await scaffold.ctx.loader.await()
await access.click()
await expect.poll(() => page.getByRole('menuitem').allTextContents())
.toEqual(['仅可查看', '工作区内修改', '完全权限', 'Auto reviewEXP'])
.toEqual(['仅可查看', '工作区内修改', '完全权限', '自动审查EXP'])
expect(await access.getAttribute('aria-label')).toBe('访问模式,当前:完全权限')
await captureAutoReviewState(page, 'reinstalled-current-session-picker')
await page.keyboard.press('Escape')
@@ -302,7 +311,7 @@ describe('web e2e: default permission choices', () => {
const slash = page.locator('[aria-label="/permission 选项"]')
await slash.waitFor()
expect(await slash.getByRole('option').count()).toBe(3)
expect(await slash.getByText('Auto review', { exact: true }).count()).toBe(0)
expect(await slash.getByText('自动审查', { exact: true }).count()).toBe(0)
await captureAutoReviewState(page, 'default-slash-picker')
})
})
+1 -1
View File
@@ -63,7 +63,7 @@ describe.skipIf(MODE === 'record')('web e2e: cold Auto-review user rejection and
const access = page.locator('button[aria-label^="Access mode"]').first()
await expect.poll(() => access.getAttribute('aria-label'), { timeout: 10_000 })
.toBe('Access mode, current: Auto review EXP')
.toBe('Access mode, current: Auto review')
const collapsed = (await captureStableAria(page, '[class*="centerCol"]', scaffold.workspaceCwd))
.split(SEED_ID).join('{{seededId}}')
+1 -1
View File
@@ -92,7 +92,7 @@ describe.skipIf(MODE === 'record')('web e2e: cold Auto-review denial', () => {
const access = page.locator('button[aria-label^="Access mode"]').first()
await expect.poll(() => access.getAttribute('aria-label'), { timeout: 10_000 })
.toBe('Access mode, current: Auto review EXP')
.toBe('Access mode, current: Auto review')
await captureAutoReviewState(page, 'deny-collapsed')
const collapsed = (await captureStableAria(page, '[class*="centerCol"]', scaffold.workspaceCwd))
@@ -1,8 +1,8 @@
- dialog "确认启用 Auto review(实验)?":
- heading "确认启用 Auto review(实验)?" [level=2]
- dialog "确认启用自动审查(实验)?":
- heading "确认启用自动审查(实验)?" [level=2]
- button "关闭"
- paragraph: Auto review 不使用沙箱。每次原生工具调用和 PTC 内层调用前,都会由与当前 agent 相同的模型进行审查;审查拒绝的调用由你批准或拒绝。此功能仍属实验性,可能误放行或误拒绝,并会消耗额外 token。
- paragraph: 自动审查不使用沙箱。每次原生工具调用和 PTC 内层调用前,都会由与当前 agent 相同的模型进行审查;审查拒绝的调用由你批准或拒绝。此功能仍属实验性,可能误放行或误拒绝,并会消耗额外 token。
- checkbox "我已了解这些风险,并愿意继续"
- text: 我已了解这些风险,并愿意继续
- button "取消"
- button "启用 Auto review" [disabled]
- button "启用自动审查" [disabled]
@@ -2,6 +2,4 @@
- menuitem "仅可查看"
- menuitem "工作区内修改"
- menuitem "完全权限"
- menuitem "Auto review EXP":
- text: Auto review
- superscript: EXP
- menuitem "自动审查 EXP"
@@ -4,7 +4,7 @@
- option "仅可查看"
- option "工作区内修改" [selected]
- option "完全权限"
- option "Auto review EXP":
- text: Auto review
- option "自动审查 EXP":
- text: 自动审查
- superscript: EXP
- text: 无沙箱运行;每次原生工具调用和 PTC 内层调用前由同一模型进行实验性审查。
@@ -10,7 +10,7 @@
zh: 74b8ded4da556e81
/deepseek-ai-dsh-client-ui-permission-presets/summary:
en: 2591adc4721c0564
zh: 311292f58621adb6
zh: 558413d3f819d3af
/deepseek-ai-dsh-client-ui-permission-presets/table-of-contents:
en: d152484eb41ac6b4
zh: 09388d293f9be9cb
@@ -18,14 +18,14 @@
en: 91606bff7da02253
zh: ef68e813d64c89be
/deepseek-ai-dsh-client-ui-permission-presets/use-this-package/the-picker:
en: d851eab85fe002bd
zh: 0fdf6eeb1cbd609b
en: 4e5a070ad4321548
zh: 010063336bd2b8df
/deepseek-ai-dsh-client-ui-permission-presets/use-this-package/the-settings-row:
en: 5067f6aa12496225
zh: cfa6796684f97535
/deepseek-ai-dsh-client-ui-permission-presets/understand-the-implementation:
en: 4089d5bebf3e8bf0
zh: bc5c3652b0612784
zh: 22071e6cd6de0ffc
/deepseek-ai-dsh-client-ui-permission-presets/further-exploration:
en: 9bdbdea03d0e2a96
zh: cc3bf480a0706ef2
@@ -37,7 +37,7 @@
zh: 4fcbc876952d240d
/deepseek-ai-dsh-client-ui-permission-presets/known-limitations-and-deferred-work:
en: 08cdc0ad196d12c1
zh: 1b65f5d30e219197
zh: 157bee9d5b29f470
/deepseek-ai-dsh-client-ui-permission-presets/known-limitations-and-deferred-work/dev-note:
en: e67f633dc6fe7773
zh: 3bb0c20c93f16f98
@@ -29,7 +29,7 @@ Mount this plugin alongside the settings, commands, and conversation packages. G
### The picker
A pick submits the `/permission <preset>` command line. The argument-bearing form (`/permission <preset>` typed directly) still switches directly; the decoration replaces only the bare invocation. The built-in labels are `Read Only`, `Workspace Write`, `Full access`, and `Auto review` in English and `仅可查看`, `工作区内修改`, `完全权限`, and `Auto review` in Chinese. Explicit host labels remain unchanged, unknown kebab-case names render in title case, and `auto` carries an `EXP` badge plus an experimental-risk confirmation. `custom` is display state, never a target.
A pick submits the `/permission <preset>` command line. The argument-bearing form (`/permission <preset>` typed directly) still switches directly; the decoration replaces only the bare invocation. The built-in labels are `Read Only`, `Workspace Write`, `Full access`, and `Auto review` in English and `仅可查看`, `工作区内修改`, `完全权限`, and `自动审查` in Chinese. Explicit host labels remain unchanged, unknown kebab-case names render in title case, and `auto` carries an `EXP` badge in menu options plus an experimental-risk confirmation. The selected composer control shows the mode without the badge. `custom` is display state, never a target.
When the live catalog withdraws a preset, the composer closes its pending confirmation and shows the Session's current value instead of an unavailable optimistic pick. Every catalog invalidation — the payload-free notification or a connection-generation change — closes an open slash picker or its confirmation without consuming the draft, while publishing the result of a read the picker waits for leaves it open with its failure and retry state; reopening reads the current catalog. A submitted command remains busy until its response settles.
@@ -9,7 +9,7 @@ kind: "package-reference"
## 概述
为当前 Web 会话或未来会话选择权限预设。通用设置行只更改之后创建会话所用的默认值;composer 与 `/permission` 选择器切换当前会话。默认 Web 提供仅可查看、工作区内修改与完全权限。显式加载实验 Auto integration 后,当前会话选择器会增加带 `EXP` 标记的 Auto review。通过可见选项选择完全权限或 Auto 时,需要分别确认对应风险;完整的 `/permission <preset>` 命令直接执行。宿主通过 Session 投影确认每次变更。
为当前 Web 会话或未来会话选择权限预设。通用设置行只更改之后创建会话所用的默认值;composer 与 `/permission` 选择器切换当前会话。默认 Web 提供仅可查看、工作区内修改与完全权限。显式加载实验 Auto integration 后,当前会话选择器会增加带 `EXP` 标记的自动审查。通过可见选项选择完全权限或 Auto 时,需要分别确认对应风险;完整的 `/permission <preset>` 命令直接执行。宿主通过 Session 投影确认每次变更。
## 目录
@@ -29,7 +29,7 @@ kind: "package-reference"
### 选择器
选中即提交 `/permission <preset>` 命令行。带参形式(直接键入 `/permission <preset>`)仍直接切换;装饰只替换裸调用。内置标签在英文界面中是 `Read Only`、`Workspace Write`、`Full access` 和 `Auto review`,在中文界面中是「仅可查看」「工作区内修改」「完全权限」和 `Auto review`。显式 host 标签保持原样,未知 kebab-case 名称渲染为 Title Case;`auto` 带有 `EXP` badge,并在可见选择时要求实验风险确认。`custom` 只是显示状态,绝非目标。
选中即提交 `/permission <preset>` 命令行。带参形式(直接键入 `/permission <preset>`)仍直接切换;装饰只替换裸调用。内置标签在英文界面中是 `Read Only`、`Workspace Write`、`Full access` 和 `Auto review`,在中文界面中是「仅可查看」「工作区内修改」「完全权限」和「自动审查」。显式 host 标签保持原样,未知 kebab-case 名称渲染为 Title Case;`auto` 在菜单选项中带有 `EXP` 标记,并在可见选择时要求实验风险确认。选中后的 composer 控件显示模式,不展示该标记。`custom` 只是显示状态,绝非目标。
实时目录撤销某个预设时,composer 关闭对应的待确认对话框,并用 Session 的当前值替代已不可用的乐观选择。每次目录失效(无 payload 的通知或连接代际变更)会关闭已打开的 slash 选择器或其确认对话框,不消费草稿;而发布某个 picker 正在等待的读取结果时,该 picker 保持打开并保留失败与重试状态;重新打开时读取当前目录。已经提交的命令在响应结束前继续保持忙碌状态。
@@ -45,7 +45,7 @@ kind: "package-reference"
<details>
<summary>实现细节——点击展开</summary>
General Settings 行经 `ctx.configForms` 读取显式暴露的 `permission` Settings 描述符,并携带描述符 revision 写入一条 `settings.mutate` 路径操作;其 observable 经 slot 系统的 `hooks` compartment 传递,因此 React 钩子绑定归渲染器,推送失效通知会重新获取描述符。该值只在之后创建会话时读取。当前会话界面是挂在宿主 `/permission` 命令上的 popupSelect 装饰(`ctx.commandUi.decorate`):宿主命令保留斜杠菜单行、带参形式与持久生命周期记账,装饰只把裸调用替换为选择器。一个进程级目录会在首次 Remote 读取前订阅无 payload 的目录通知,并且只发布当前连接代际中最新的完整成功结果。胜出的读取失败或连接 reset 会清空旧快照:composer seat 因此隐藏,直到后续既有触发重试,而 slash 选择器保持可用并在弹窗内显示失败与重试;旧连接代际与 dispose 后才返回的结果会被忽略。它的公共 observable 是 `{ value }` 与 `invalidations`(每次目录通知或连接代际变更打一个点);失败仅供命令式加载内部使用。slash popup 与 composer seat 共用这份目录,而 Session `permissions` 投影只提供 `currentValue`。Full access 与 Auto review 各自携带本地化确认文案;Auto 还携带由共享 popup 外壳渲染的 badge。
General Settings 行经 `ctx.configForms` 读取显式暴露的 `permission` Settings 描述符,并携带描述符 revision 写入一条 `settings.mutate` 路径操作;其 observable 经 slot 系统的 `hooks` compartment 传递,因此 React 钩子绑定归渲染器,推送失效通知会重新获取描述符。该值只在之后创建会话时读取。当前会话界面是挂在宿主 `/permission` 命令上的 popupSelect 装饰(`ctx.commandUi.decorate`):宿主命令保留斜杠菜单行、带参形式与持久生命周期记账,装饰只把裸调用替换为选择器。一个进程级目录会在首次 Remote 读取前订阅无 payload 的目录通知,并且只发布当前连接代际中最新的完整成功结果。胜出的读取失败或连接 reset 会清空旧快照:composer seat 因此隐藏,直到后续既有触发重试,而 slash 选择器保持可用并在弹窗内显示失败与重试;旧连接代际与 dispose 后才返回的结果会被忽略。它的公共 observable 是 `{ value }` 与 `invalidations`(每次目录通知或连接代际变更打一个点);失败仅供命令式加载内部使用。slash popup 与 composer seat 共用这份目录,而 Session `permissions` 投影只提供 `currentValue`。完全权限与自动审查各自携带本地化确认文案;Auto 还携带由共享 popup 外壳渲染的 badge。
</details>
@@ -80,7 +80,7 @@ General Settings 行经 `ctx.configForms` 读取显式暴露的 `permission` Set
这些限制界定了当前权限界面。它们是当前包约束,不是通用策略对比或任务积压。
- **设置行仅限 Web**——非 Web 客户端仍可经 `/permission` 切换当前会话,但不会获得这项浏览器贡献。
- **Auto review 仅限当前会话**——General Settings 行有意省略它,且只有通过可见选择器选择时才显示实验确认;显式键入 `/permission auto` 已经构成明确同意。
- **自动审查仅限当前会话**——General Settings 行有意省略它,且只有通过可见选择器选择时才显示实验确认;显式键入 `/permission auto` 已经构成明确同意。
- **预设描述来自宿主**——本地化的内置标签旁边可能显示另一种语言编写的描述。
<a id="dev-note"></a>
@@ -52,7 +52,7 @@
.optionLabel {
display: inline-flex;
align-items: baseline;
align-items: center;
gap: 4px;
min-width: 0;
max-width: 100%;
@@ -67,12 +67,10 @@
.badge {
flex: none;
align-self: flex-start;
margin-top: -1px;
color: var(--dsw-alias-label-tertiary);
font-size: 8px;
line-height: 10px;
font-weight: 600;
font-weight: 500;
letter-spacing: 0.2px;
}
@@ -85,8 +83,8 @@
transition: transform 120ms ease;
}
/* Narrow composer: the trigger collapses to icon + chevron so the row keeps
fitting. Only triggers that actually carry a mode glyph drop their label —
/* Narrow composer: mode text collapses while icons stay visible.
Only triggers that actually carry a mode glyph drop their label —
a host-configured mode without one keeps its text as the sole identifier.
The 460px cut is the point where the row (attach + modes + model + send)
starts squeezing labels; the container is the composer row (InputBar .row —
@@ -2,7 +2,7 @@ import { useEffect, useState } from 'react'
import type { ReactNode } from 'react'
import clsx from 'clsx'
import {
IconChevronDownOutlineRegular, Menu, PermissionIconFullAccessRegular,
IconChevronDownOutlineRegular, Menu, PermissionIconAutoReviewRegular, PermissionIconFullAccessRegular,
PermissionIconReadOnlyRegular, PermissionIconWorkspaceWriteRegular, RiskConfirmation,
} from '@deepseek-ai/dsh-client-ui-primitives'
import type { MenuEntry } from '@deepseek-ai/dsh-client-ui-primitives'
@@ -29,6 +29,7 @@ const permissionGlyphs = new Map<string, ReactNode>([
['read-only', <PermissionIconReadOnlyRegular />],
['workspace-write', <PermissionIconWorkspaceWriteRegular />],
[FULL_ACCESS, <PermissionIconFullAccessRegular />],
[AUTO_REVIEW, <PermissionIconAutoReviewRegular />],
])
/** Glyph for a permission option value; host-configured names outside the design set get none. */
@@ -112,7 +113,7 @@ export function PermissionSelect({
: (
<span className={css.optionLabel} aria-label={`${label} ${badge}`}>
<span className={css.optionLabelText}>{label}</span>
<sup className={css.badge}>{badge}</sup>
<span className={css.badge}>{badge}</span>
</span>
),
...icon === undefined ? {} : { icon },
@@ -159,8 +160,6 @@ export function PermissionSelect({
const confirmationEnable = confirmation === AUTO_REVIEW
? t('auto.confirm.enable')
: t('confirm.enable')
const currentBadge = optionBadge(currentValue, t)
const currentAccessibleLabel = currentBadge === undefined ? currentLabel : `${currentLabel} ${currentBadge}`
return (
<>
@@ -176,7 +175,7 @@ export function PermissionSelect({
<button
type="button"
className={css.trigger}
aria-label={t('mode', { name: currentAccessibleLabel })}
aria-label={t('mode', { name: currentLabel })}
title={current === undefined ? undefined : optionDescription(current, t)}
disabled={locked || busy}
onClick={() => { setOpen(!open) }}
@@ -185,9 +184,6 @@ export function PermissionSelect({
<span className={css.triggerIcon} aria-hidden>{permissionGlyph(currentValue)}</span>
)}
<span className={css.triggerLabel}>{currentLabel}</span>
{currentBadge !== undefined && (
<sup className={css.badge}>{currentBadge}</sup>
)}
<span className={clsx(css.chevron, open && css.chevronOpen)} aria-hidden>
<IconChevronDownOutlineRegular />
</span>
@@ -50,13 +50,13 @@ export const accessZh = {
'confirm.acknowledge': '我已了解风险,并愿意继续',
'confirm.cancel': '取消',
'confirm.enable': '启用完全权限',
'auto.label': 'Auto review',
'auto.label': '自动审查',
'auto.badge': 'EXP',
'auto.description': '无沙箱运行;每次原生工具调用和 PTC 内层调用前由同一模型进行实验性审查。',
'auto.confirm.title': '确认启用 Auto review(实验)?',
'auto.confirm.description': 'Auto review 不使用沙箱。每次原生工具调用和 PTC 内层调用前,都会由与当前 agent 相同的模型进行审查;审查拒绝的调用由你批准或拒绝。此功能仍属实验性,可能误放行或误拒绝,并会消耗额外 token。',
'auto.confirm.title': '确认启用自动审查(实验)?',
'auto.confirm.description': '自动审查不使用沙箱。每次原生工具调用和 PTC 内层调用前,都会由与当前 agent 相同的模型进行审查;审查拒绝的调用由你批准或拒绝。此功能仍属实验性,可能误放行或误拒绝,并会消耗额外 token。',
'auto.confirm.acknowledge': '我已了解这些风险,并愿意继续',
'auto.confirm.enable': '启用 Auto review',
'auto.confirm.enable': '启用自动审查',
} satisfies Record<string, string>
/** Current-session popup-gate key union. */
@@ -225,7 +225,7 @@ describe('ui-permission browser plugin', () => {
})
b.locale.setLocale('zh')
const localized = await b.popup().options(proj, new AbortController().signal)
expect(localized.map(option => option.label)).toEqual(['仅可查看', '工作区内修改', '完全权限', 'Auto review'])
expect(localized.map(option => option.label)).toEqual(['仅可查看', '工作区内修改', '完全权限', '自动审查'])
expect(localized.find(option => option.id === 'danger-full-access')?.confirmation).toEqual({
title: '确认启用完全权限?',
description: accessZh['confirm.description'],
@@ -81,7 +81,7 @@ describe('PermissionSelect', () => {
fireEvent.click(trigger())
expect(screen.getAllByRole('menuitem').map(item => item.textContent))
.toEqual(['仅可查看', '工作区内修改', '完全权限', 'Auto reviewEXP'])
.toEqual(['仅可查看', '工作区内修改', '完全权限', '自动审查EXP'])
fireEvent.click(screen.getByRole('menuitem', { name: '工作区内修改' }))
expect(select).toHaveBeenCalledExactlyOnceWith('workspace-write')
@@ -143,19 +143,25 @@ describe('PermissionSelect', () => {
it('marks Auto experimental and uses the current-session risk copy', async () => {
const { select, selection } = setup()
fireEvent.click(trigger())
fireEvent.click(screen.getByRole('menuitem', { name: 'Auto review EXP' }))
const autoOption = screen.getByRole('menuitem', { name: '自动审查 EXP' })
const autoGlyph = autoOption.querySelector('svg[aria-hidden="true"]')
expect(autoGlyph).not.toBeNull()
expect(autoGlyph!.innerHTML)
.not.toBe(screen.getByRole('menuitem', { name: '完全权限' }).querySelector('svg')!.innerHTML)
fireEvent.click(autoOption)
const dialog = screen.getByRole('dialog', { name: '确认启用 Auto review(实验)?' })
const dialog = screen.getByRole('dialog', { name: '确认启用自动审查(实验)?' })
expect(dialog.textContent).toContain('不使用沙箱')
expect(dialog.textContent).toContain('误放行或误拒绝')
fireEvent.click(screen.getByRole('checkbox', { name: '我已了解这些风险,并愿意继续' }))
fireEvent.click(screen.getByRole('button', { name: '启用 Auto review' }))
fireEvent.click(screen.getByRole('button', { name: '启用自动审查' }))
expect(select).toHaveBeenCalledExactlyOnceWith('auto')
act(() => { selection.set({ value: { currentValue: 'auto' } }) })
await act(async () => {})
expect(trigger().getAttribute('aria-label')).toBe('访问模式,当前:Auto review EXP')
expect(trigger().querySelector('sup')?.textContent).toBe('EXP')
expect(trigger().getAttribute('aria-label')).toBe('访问模式,当前:自动审查')
expect(trigger().textContent).toBe('自动审查')
expect(trigger().querySelectorAll('svg[aria-hidden="true"]')).toHaveLength(2)
expect(trigger().getAttribute('title')).toBe('无沙箱运行;每次原生工具调用和 PTC 内层调用前由同一模型进行实验性审查。')
})
@@ -186,7 +192,7 @@ describe('PermissionSelect', () => {
const withoutAuto = { ...CATALOG, options: CATALOG.options.filter(option => option.value !== 'auto') }
const chooseAuto = () => {
fireEvent.click(trigger())
fireEvent.click(screen.getByRole('menuitem', { name: 'Auto review EXP' }))
fireEvent.click(screen.getByRole('menuitem', { name: '自动审查 EXP' }))
}
try {
chooseAuto()
@@ -201,9 +207,9 @@ describe('PermissionSelect', () => {
chooseAuto()
expect(screen.getByRole<HTMLInputElement>('checkbox').checked).toBe(false)
fireEvent.click(screen.getByRole('checkbox'))
fireEvent.click(screen.getByRole('button', { name: '启用 Auto review' }))
fireEvent.click(screen.getByRole('button', { name: '启用自动审查' }))
expect(select).toHaveBeenCalledExactlyOnceWith('auto')
expect(trigger().textContent).toBe('Auto reviewEXP')
expect(trigger().textContent).toBe('自动审查')
act(() => {
selection.set({ value: { currentValue: 'danger-full-access' } })
@@ -18,8 +18,8 @@
en: 7bae72c97d0449f5
zh: 4589ecc2b216edc5
/deepseek-ai-dsh-client-ui-primitives/use-this-package/component-catalog:
en: b9ef1bec616d1038
zh: 51db8274f3a432fc
en: eb1d57b0d5851435
zh: 2789bf9b1e478905
/deepseek-ai-dsh-client-ui-primitives/use-this-package/controls-and-icons:
en: f0e54915b896af5a
zh: 2047520fd321be90
+1 -1
View File
@@ -69,7 +69,7 @@ Check this table before writing a control in a feature package. A plugin cannot
| `CommandText` | Selectable command or argument text; only horizontal overflow adds a named keyboard focus target. |
| `TerminalBlock`, `ReadBlock`, `DiffBlock`, `SearchBlock`, `WebBlock` | The agent-output card matching each tool-result intent. |
| `icons/*`, `FishLogo`, `BrandWordmark`, `ReferenceIconRegular`/`ReferenceIconMedium`, `LinkIconRegular`/`LinkIconMedium` | Glyphs and brand marks. The thinking glyph keeps an inset orbit within its 16px view box. Use `LinkIconMedium` for 14px clickable-link categories and known-site marks. |
| `PermissionIconReadOnlyRegular`/`Medium`, `PermissionIconWorkspaceWriteRegular`/`Medium`, `PermissionIconFullAccessRegular`/`Medium` | Permission-mode glyphs for read-only, workspace-write, and full-access choices. |
| `PermissionIconReadOnlyRegular`/`Medium`, `PermissionIconWorkspaceWriteRegular`/`Medium`, `PermissionIconFullAccessRegular`/`Medium`, `PermissionIconAutoReviewRegular`/`Medium` | Permission-mode glyphs for read-only, workspace-write, full-access, and auto-review choices. |
| `PluginArtworkTerminal`/`Loop`/`Subagent`/`Search`/`Default` | Fixed-palette 36×36 plugin artwork; `Terminal` supplies the light-blue prompt in plugin cards and sidebar guide entries. `Default` marks plugins without artwork of their own. Def ids are per-instance, so the same artwork repeats safely on one page. |
| `GuideArtworkBrowser`/`Files` | Fixed-palette 36×36 browser and folder artwork for sidebar guide entries. |
| `FileTypeIcon`, `classifyFileType`, `fileExtension` | A category-colored 28px file or folder glyph and the shared case-insensitive filename mapping behind it. Code and configuration files use detailed full-color technology glyphs; use `LinkIconMedium` for link-leading glyphs and image previews for image content. |
+1 -1
View File
@@ -69,7 +69,7 @@ kind: "package-library"
| `CommandText` | 可选中的命令或参数文本;仅横向溢出时添加具名的键盘聚焦目标。 |
| `TerminalBlock`、`ReadBlock`、`DiffBlock`、`SearchBlock`、`WebBlock` | 与各类工具结果意图对应的 agent 输出卡片。 |
| `icons/*`、`FishLogo`、`BrandWordmark`、`ReferenceIconRegular`/`ReferenceIconMedium`、`LinkIconRegular`/`LinkIconMedium` | 字形与品牌标识。思考图标的轨道在 16px 视口内保留内边距。`LinkIconMedium` 用于 14px 的可点击链接分类及已知站点标记。 |
| `PermissionIconReadOnlyRegular`/`Medium`、`PermissionIconWorkspaceWriteRegular`/`Medium`、`PermissionIconFullAccessRegular`/`Medium` | 只读、工作区写入与完全访问选项使用的权限模式图形。 |
| `PermissionIconReadOnlyRegular`/`Medium`、`PermissionIconWorkspaceWriteRegular`/`Medium`、`PermissionIconFullAccessRegular`/`Medium`、`PermissionIconAutoReviewRegular`/`Medium` | 只读、工作区写入、完全访问与自动审查选项使用的权限模式图形。 |
| `PluginArtworkTerminal`/`Loop`/`Subagent`/`Search`/`Default` | 固定配色的 36×36 插件插画;`Terminal` 为插件卡片和侧边栏开始页入口提供浅蓝色提示符。`Default` 用于没有自有插画的插件。def id 按实例生成,同一插画可在一页中安全重复。 |
| `GuideArtworkBrowser`/`Files` | 固定配色的 36×36 浏览器与文件夹插画,用于侧栏引导入口。 |
| `FileTypeIcon`、`classifyFileType`、`fileExtension` | 按类别着色的 28px 文件或文件夹图形,以及它背后共享的不区分大小写文件名映射。代码与配置文件使用细分的全彩技术图形;链接前置图形使用 `LinkIconMedium`,图片内容使用图片预览。 |
@@ -37,6 +37,15 @@ function FullAccessArtwork({ size = 16, className, strokeWidth }: PermissionIcon
)
}
function AutoReviewArtwork({ size = 16, className, strokeWidth }: PermissionIconArtworkProps): ReactNode {
return (
<svg width={size} height={size} className={className} viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg" aria-hidden="true" strokeWidth={strokeWidth}>
<path d="M6.59624 2.14853C7.50155 1.80917 8.49914 1.80919 9.40444 2.14859L13.9245 3.84317V7.11961C13.9245 11.6089 10.5565 13.5975 8.00035 14.5779C5.44423 13.5975 2.07544 11.6089 2.07544 7.11961V3.84317L6.59624 2.14853Z" stroke="currentColor" strokeLinejoin="round" />
<path d="M5.75 10.15L8 4.95L10.25 10.15M6.43 8.59H9.57" stroke="currentColor" strokeLinecap="round" strokeLinejoin="round" />
</svg>
)
}
/**
* Render the read-only permission icon with a one-pixel stroke.
* @param props - Size and optional class.
@@ -90,3 +99,21 @@ export function PermissionIconFullAccessRegular(props: IconProps): ReactNode {
export function PermissionIconFullAccessMedium(props: IconProps): ReactNode {
return <FullAccessArtwork {...props} strokeWidth={ICON_MEDIUM_STROKE} />
}
/**
* Render the auto-review permission icon with a one-pixel stroke.
* @param props - Size and optional class.
* @returns The regular decorative permission glyph.
*/
export function PermissionIconAutoReviewRegular(props: IconProps): ReactNode {
return <AutoReviewArtwork {...props} strokeWidth={ICON_REGULAR_STROKE} />
}
/**
* Render the auto-review permission icon with a 1.3px stroke.
* @param props - Size and optional class.
* @returns The medium decorative permission glyph.
*/
export function PermissionIconAutoReviewMedium(props: IconProps): ReactNode {
return <AutoReviewArtwork {...props} strokeWidth={ICON_MEDIUM_STROKE} />
}
@@ -46,6 +46,7 @@ export { FishLogo, FISH_LOGO_PATH, FISH_LOGO_VIEWBOX } from './FishLogo.tsx'
export { BrandWordmark } from './BrandWordmark.tsx'
export type { BrandWordmarkProps } from './BrandWordmark.tsx'
export {
PermissionIconAutoReviewMedium, PermissionIconAutoReviewRegular,
PermissionIconFullAccessMedium, PermissionIconFullAccessRegular,
PermissionIconReadOnlyMedium, PermissionIconReadOnlyRegular,
PermissionIconWorkspaceWriteMedium, PermissionIconWorkspaceWriteRegular,
@@ -82,6 +82,7 @@ describe('product icon set', () => {
[primitives.PermissionIconReadOnlyRegular, primitives.PermissionIconReadOnlyMedium],
[primitives.PermissionIconWorkspaceWriteRegular, primitives.PermissionIconWorkspaceWriteMedium],
[primitives.PermissionIconFullAccessRegular, primitives.PermissionIconFullAccessMedium],
[primitives.PermissionIconAutoReviewRegular, primitives.PermissionIconAutoReviewMedium],
] as const
for (const [Regular, Medium] of pairs) {
const regular = render(<Regular />)
@@ -18,8 +18,8 @@
en: 8f9e85817f05aa4d
zh: f60136fb39abcf29
/deepseek-ai-dsh-experimental-auto-review/use-this-package/install-into-a-profile:
en: 63f8f1baa0fb2655
zh: 6bc64160f6d02114
en: cf146a773e8c7010
zh: 57f7e9be7b257fcc
/deepseek-ai-dsh-experimental-auto-review/use-this-package/what-you-get:
en: 556e89c0055ae9cc
zh: 75afbcb9a2af6467
+1 -1
View File
@@ -35,7 +35,7 @@ From this source checkout, install the package into the Web profile through the
pnpm dsh plugin --profile web add ./packages/experimental/auto-review
```
The CLI initializes the profile when needed and appends this package's declared patch after the base and Web layers. Reconciliation activates the patch as a profile layer; a package without `dsh.bundle.patch` is only an installed dependency. Select `Auto review` with its superscript `EXP` badge in the composer or `/permission` picker and confirm the current-session risk dialog. An explicit `/permission auto` command switches directly. General settings and future-session defaults do not offer Auto.
The CLI initializes the profile when needed and appends this package's declared patch after the base and Web layers. Reconciliation activates the patch as a profile layer; a package without `dsh.bundle.patch` is only an installed dependency. Select `Auto review` with its `EXP` badge in the composer menu or `/permission` picker and confirm the current-session risk dialog. An explicit `/permission auto` command switches directly. General settings and future-session defaults do not offer Auto.
Remove the layer through the same CLI:
@@ -35,7 +35,7 @@ kind: "package-bundle"
pnpm dsh plugin --profile web add ./packages/experimental/auto-review
```
CLI 会在需要时初始化 profile,并将本包声明的 patch 追加到 base 与 Web 层之后。Reconciliation 将 patch 激活为 profile 层;没有 `dsh.bundle.patch` 的包只是已安装依赖。在 composer 或 `/permission` 选择器中选择带右上标 `EXP` 的 `Auto review`,并确认当前会话风险对话框。显式 `/permission auto` 命令直接切换。通用设置与未来会话默认值不提供 Auto。
CLI 会在需要时初始化 profile,并将本包声明的 patch 追加到 base 与 Web 层之后。Reconciliation 将 patch 激活为 profile 层;没有 `dsh.bundle.patch` 的包只是已安装依赖。在 composer 菜单或 `/permission` 选择器中选择带 `EXP` 标记的「自动审查」,并确认当前会话风险对话框。显式 `/permission auto` 命令直接切换。通用设置与未来会话默认值不提供 Auto。
通过同一 CLI 移除此层:
@@ -18,9 +18,7 @@
- 'button "Tool call Error: Auto review of tool \"mystery\" failed; its body was not executed: auto-review: reviewer ended with error UNKNOWN: provider unavailable"'
- textbox "Message or run a task, / commands, @ files or sessions"
- button "Add files or run commands"
- 'button "Access mode, current: Auto review EXP"':
- text: Auto review
- superscript: EXP
- 'button "Access mode, current: Auto review"': Auto review
- button "Select model, current DeepSeek-V4-Flash": DeepSeek-V4-Flash
- button "Send message" [disabled]
- button "1 turns 2 steps"
@@ -49,9 +47,7 @@
- button "Inspect"
- textbox "Message or run a task, / commands, @ files or sessions"
- button "Add files or run commands"
- 'button "Access mode, current: Auto review EXP"':
- text: Auto review
- superscript: EXP
- 'button "Access mode, current: Auto review"': Auto review
- button "Select model, current DeepSeek-V4-Flash": DeepSeek-V4-Flash
- button "Send message" [disabled]
- button "1 turns 2 steps"
@@ -19,9 +19,7 @@
- button "Bash Rejected by Auto review"
- textbox "Message or run a task, / commands, @ files or sessions"
- button "Add files or run commands"
- 'button "Access mode, current: Auto review EXP"':
- text: Auto review
- superscript: EXP
- 'button "Access mode, current: Auto review"': Auto review
- button "Select model, current DeepSeek-V4-Flash": DeepSeek-V4-Flash
- button "Send message" [disabled]
- button "1 turns 2 steps"
@@ -51,9 +49,7 @@
- button "Inspect"
- textbox "Message or run a task, / commands, @ files or sessions"
- button "Add files or run commands"
- 'button "Access mode, current: Auto review EXP"':
- text: Auto review
- superscript: EXP
- 'button "Access mode, current: Auto review"': Auto review
- button "Select model, current DeepSeek-V4-Flash": DeepSeek-V4-Flash
- button "Send message" [disabled]
- button "1 turns 2 steps"
@@ -110,9 +106,7 @@
- tabpanel "Result": "AutoReviewDeniedError: AUTO_REVIEW_DENIED Error: Auto review rejected tool \"bash\"; its body was not executed"
- textbox "Message or run a task, / commands, @ files or sessions"
- button "Add files or run commands"
- 'button "Access mode, current: Auto review EXP"':
- text: Auto review
- superscript: EXP
- 'button "Access mode, current: Auto review"': Auto review
- button "Select model, current DeepSeek-V4-Flash": DeepSeek-V4-Flash
- button "Send message" [disabled]
- button "1 turns 2 steps"