|
@@ -8,15 +8,21 @@
|
|
|
* projection (the same host-computed select the composer chip renders); a
|
|
* projection (the same host-computed select the composer chip renders); a
|
|
|
* pick submits the `/permission <preset>` command line, so both surfaces
|
|
* pick submits the `/permission <preset>` command line, so both surfaces
|
|
|
* write through one path and the pushed projection frame is the one
|
|
* 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 { ClientContext, SessionFace } from '@deepseek-ai/dsh-client-runtime/client'
|
|
|
import type { CommandServiceContract, SelectOption } from '@deepseek-ai/dsh-client-ui-command/client'
|
|
import type { CommandServiceContract, SelectOption } from '@deepseek-ai/dsh-client-ui-command/client'
|
|
|
import type { ClientSessionContext } from '@deepseek-ai/dsh-client-ui-slash/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'
|
|
import type { PermissionSelect } from '@deepseek-ai/dsh-permission/client'
|
|
|
|
|
|
|
|
/** Required services (cordis fiber inject). */
|
|
/** 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). */
|
|
/** Read one session's current permissions projection value (undefined = capability absent). */
|
|
|
function selectOf(session: SessionFace | undefined): PermissionSelect | undefined {
|
|
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
|
|
* Display transform twin of the composer chip's (ui-conversation
|
|
|
* PermissionSelect): kebab-case machine names render as title-case labels
|
|
* 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 {
|
|
function displayName(name: string): string {
|
|
|
if (!/^[a-z0-9]+(-[a-z0-9]+)*$/.test(name)) return name
|
|
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. */
|
|
/** 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
|
|
return value.options
|
|
|
.filter(option => option.value !== 'custom')
|
|
.filter(option => option.value !== 'custom')
|
|
|
.map(option => ({
|
|
.map(option => ({
|
|
|
id: option.value,
|
|
id: option.value,
|
|
|
- label: displayName(option.name),
|
|
|
|
|
|
|
+ label: option.value === FULL_ACCESS ? 'Full access' : displayName(option.name),
|
|
|
...(option.description !== undefined ? { detail: option.description } : {}),
|
|
...(option.description !== undefined ? { detail: option.description } : {}),
|
|
|
...(option.value === value.currentValue ? { active: true } : {}),
|
|
...(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 {
|
|
export function apply(ctx: ClientContext): void {
|
|
|
const command = ctx.get('command') as CommandServiceContract
|
|
const command = ctx.get('command') as CommandServiceContract
|
|
|
const sessions = ctx.sessions
|
|
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 =>
|
|
const sessionFor = (session: ClientSessionContext): SessionFace | undefined =>
|
|
|
sessions.binding(session.sessionId)?.session
|
|
sessions.binding(session.sessionId)?.session
|
|
|
ctx.effect(() => command.decorate({
|
|
ctx.effect(() => command.decorate({
|
|
@@ -67,7 +105,7 @@ export function apply(ctx: ClientContext): void {
|
|
|
options: (session) => {
|
|
options: (session) => {
|
|
|
const value = selectOf(sessionFor(session))
|
|
const value = selectOf(sessionFor(session))
|
|
|
if (value === undefined) throw new Error('permission presets are not available on this host')
|
|
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) => {
|
|
onSelect: async (option, session) => {
|
|
|
const live = sessionFor(session)
|
|
const live = sessionFor(session)
|