fix(client): gate every full access picker
This commit is contained in:
@@ -8,15 +8,21 @@
|
||||
* projection (the same host-computed select the composer chip renders); a
|
||||
* pick submits the `/permission <preset>` command line, so both surfaces
|
||||
* write through one path and the pushed projection frame is the one
|
||||
* confirmation.
|
||||
* confirmation. The Full access row carries the same explicit risk gate as
|
||||
* the composer chip; the shared popup shell owns the modal mechanics.
|
||||
*/
|
||||
import type { ClientContext, SessionFace } from '@deepseek-ai/dsh-client-runtime/client'
|
||||
import type { CommandServiceContract, SelectOption } from '@deepseek-ai/dsh-client-ui-command/client'
|
||||
import type { ClientSessionContext } from '@deepseek-ai/dsh-client-ui-slash/client'
|
||||
// Type-only: pulls the locale plugin's Context merge (ctx.locale).
|
||||
import type {} from '@deepseek-ai/dsh-client-locale/client'
|
||||
import type { PermissionSelect } from '@deepseek-ai/dsh-permission/client'
|
||||
|
||||
/** Required services (cordis fiber inject). */
|
||||
export const inject = ['command', 'sessions']
|
||||
export const inject = ['command', 'sessions', 'locale']
|
||||
|
||||
const FULL_ACCESS = 'danger-full-access'
|
||||
const ACCESS_NS = 'permission.access'
|
||||
|
||||
/** Read one session's current permissions projection value (undefined = capability absent). */
|
||||
function selectOf(session: SessionFace | undefined): PermissionSelect | undefined {
|
||||
@@ -26,8 +32,9 @@ function selectOf(session: SessionFace | undefined): PermissionSelect | undefine
|
||||
/**
|
||||
* Display transform twin of the composer chip's (ui-conversation
|
||||
* PermissionSelect): kebab-case machine names render as title-case labels
|
||||
* (`workspace-write` → `Workspace Write`) so both permission surfaces show
|
||||
* the same text; non-kebab host-configured names pass through.
|
||||
* (`workspace-write` → `Workspace Write`); non-kebab host-configured names
|
||||
* pass through. Full access intentionally uses the product label rather than
|
||||
* a title-cased machine value; its warning body remains locale-aware.
|
||||
*/
|
||||
function displayName(name: string): string {
|
||||
if (!/^[a-z0-9]+(-[a-z0-9]+)*$/.test(name)) return name
|
||||
@@ -35,14 +42,25 @@ function displayName(name: string): string {
|
||||
}
|
||||
|
||||
/** Flatten the projection select into popup rows; `custom` is display state, never a target. */
|
||||
function optionsOf(value: PermissionSelect): SelectOption[] {
|
||||
function optionsOf(value: PermissionSelect, t: (key: string) => string): SelectOption[] {
|
||||
return value.options
|
||||
.filter(option => option.value !== 'custom')
|
||||
.map(option => ({
|
||||
id: option.value,
|
||||
label: displayName(option.name),
|
||||
label: option.value === FULL_ACCESS ? 'Full access' : displayName(option.name),
|
||||
...(option.description !== undefined ? { detail: option.description } : {}),
|
||||
...(option.value === value.currentValue ? { active: true } : {}),
|
||||
...(option.value === FULL_ACCESS
|
||||
? {
|
||||
confirmation: {
|
||||
title: t('confirm.title'),
|
||||
description: t('confirm.description'),
|
||||
acknowledgeLabel: t('confirm.acknowledge'),
|
||||
cancelLabel: t('confirm.cancel'),
|
||||
confirmLabel: t('confirm.enable'),
|
||||
},
|
||||
}
|
||||
: {}),
|
||||
}))
|
||||
}
|
||||
|
||||
@@ -54,6 +72,26 @@ function optionsOf(value: PermissionSelect): SelectOption[] {
|
||||
export function apply(ctx: ClientContext): void {
|
||||
const command = ctx.get('command') as CommandServiceContract
|
||||
const sessions = ctx.sessions
|
||||
ctx.effect(() => {
|
||||
const disposers = [
|
||||
ctx.locale.register(ACCESS_NS, 'zh', {
|
||||
'confirm.title': '确认启用 Full access?',
|
||||
'confirm.description': '启用 Full access 后,agent 将减少确认步骤,并且可以直接执行更多操作,包括敏感操作、文件修改或外部命令。仅建议在你信任当前任务时使用。',
|
||||
'confirm.acknowledge': '我已了解风险,并愿意继续',
|
||||
'confirm.cancel': '取消',
|
||||
'confirm.enable': '启用 Full access',
|
||||
}),
|
||||
ctx.locale.register(ACCESS_NS, 'en', {
|
||||
'confirm.title': 'Enable Full access?',
|
||||
'confirm.description': 'Full access reduces confirmation steps and lets the agent perform more actions directly, including sensitive operations, file changes, or external commands. Only use it when you trust the current task.',
|
||||
'confirm.acknowledge': 'I understand the risks and want to continue',
|
||||
'confirm.cancel': 'Cancel',
|
||||
'confirm.enable': 'Enable Full access',
|
||||
}),
|
||||
]
|
||||
return () => { for (const dispose of disposers) dispose() }
|
||||
}, 'ui-permission: Full access confirmation dictionaries')
|
||||
const t = ctx.locale.bind(ACCESS_NS)
|
||||
const sessionFor = (session: ClientSessionContext): SessionFace | undefined =>
|
||||
sessions.binding(session.sessionId)?.session
|
||||
ctx.effect(() => command.decorate({
|
||||
@@ -67,7 +105,7 @@ export function apply(ctx: ClientContext): void {
|
||||
options: (session) => {
|
||||
const value = selectOf(sessionFor(session))
|
||||
if (value === undefined) throw new Error('permission presets are not available on this host')
|
||||
return Promise.resolve(optionsOf(value))
|
||||
return Promise.resolve(optionsOf(value, t))
|
||||
},
|
||||
onSelect: async (option, session) => {
|
||||
const live = sessionFor(session)
|
||||
|
||||
Reference in New Issue
Block a user