From 3476338e1262e021c354c718b91bbd7c56039989 Mon Sep 17 00:00:00 2001 From: QAyong Date: Mon, 28 Sep 2026 16:13:13 +0800 Subject: [PATCH 1/3] feat(buddy): add default permission mode for new tasks --- .../electron/main/config/LexoraConfigStore.ts | 8 + .../__tests__/LexoraConfigStore.spec.ts | 28 ++- .../shared/__tests__/desktopApi.spec.ts | 5 +- apps/buddy/electron/shared/desktopApi.ts | 3 + .../electron/shared/desktopApiSchemas.ts | 2 + .../shared/permissions/permissionMode.ts | 2 + apps/buddy/src/i18n/locales/en-US/settings.ts | 2 + apps/buddy/src/i18n/locales/zh-CN/settings.ts | 2 + apps/buddy/src/modules/prompt-input/index.ts | 1 + .../drafts/__tests__/useChatDrafts.spec.ts | 18 ++ .../tasks/state/drafts/useChatDrafts.ts | 19 +- .../modules/tasks/state/useTaskCapability.ts | 5 + .../__tests__/BuddyChatActivities.spec.ts | 2 +- .../__tests__/BuddyChatMessageContent.spec.ts | 4 +- .../__tests__/DesktopChatTranscript.spec.ts | 2 +- .../feature-005-default-permission-mode.md | 192 ++++++++++++++++++ 16 files changed, 275 insertions(+), 20 deletions(-) create mode 100644 docs/specs/feature-005-default-permission-mode.md diff --git a/apps/buddy/electron/main/config/LexoraConfigStore.ts b/apps/buddy/electron/main/config/LexoraConfigStore.ts index 0babe06d..4edddcaf 100644 --- a/apps/buddy/electron/main/config/LexoraConfigStore.ts +++ b/apps/buddy/electron/main/config/LexoraConfigStore.ts @@ -12,6 +12,7 @@ import { Emitter } from '../../../shared/events/Emitter' import { copyEventSnapshot } from '../../../shared/events/eventSnapshot' import { extensionAuthorSchema } from '../../../shared/extensions/extensionIdentity' import { DEFAULT_PROXY_SETTINGS, proxySettingsSchema } from '../../../shared/network/proxySettings' +import { BUDDY_PERMISSION_MODES } from '../../../shared/permissions/permissionMode' import { DEFAULT_RUNTIME_PREFERENCES, runtimePreferencesSchema } from '../../../shared/runtime/runtimePreferences' import { keybindingsSchema } from '../../../shared/shortcuts/keybindingSchema' import { DEFAULT_DESKTOP_CHAT_PREFERENCES, DESKTOP_CHAT_OUTLINE_POSITIONS, DESKTOP_CHAT_WELCOME_VARIANT_IDS, DESKTOP_PROFILE_AVATAR_MAX_DATA_URL_LENGTH, DESKTOP_TASK_SIDEBAR_SECTIONS } from '../../shared/desktopApi' @@ -43,6 +44,7 @@ const desktopConfigSchema = z.object({ background_close_notice_shown: z.boolean().default(false), chat: z.object({ outline_position: z.enum(DESKTOP_CHAT_OUTLINE_POSITIONS).default(DEFAULT_DESKTOP_CHAT_PREFERENCES.outlinePosition), + permission_mode: z.enum(BUDDY_PERMISSION_MODES).default(DEFAULT_DESKTOP_CHAT_PREFERENCES.permissionMode), welcome: z.enum(['none', 'random', ...DESKTOP_CHAT_WELCOME_VARIANT_IDS]).default(DEFAULT_DESKTOP_CHAT_PREFERENCES.welcome), }).passthrough().prefault({}), context_panel_mode: z.enum(['task', 'independent']).default('task'), @@ -74,6 +76,7 @@ const desktopConfigSchema = z.object({ plugin_author: '', chat: { outline_position: DEFAULT_DESKTOP_CHAT_PREFERENCES.outlinePosition, + permission_mode: DEFAULT_DESKTOP_CHAT_PREFERENCES.permissionMode, welcome: DEFAULT_DESKTOP_CHAT_PREFERENCES.welcome, }, context_panel_mode: 'task', @@ -288,6 +291,7 @@ function decodeConfig(value: unknown): LexoraConfig { pluginAuthor: config.desktop.plugin_author, chat: { outlinePosition: config.desktop.chat.outline_position, + permissionMode: config.desktop.chat.permission_mode, welcome: config.desktop.chat.welcome, }, contextPanelMode: config.desktop.context_panel_mode, @@ -337,6 +341,7 @@ function encodeConfig(config: LexoraConfig) { plugin_author: config.desktop.pluginAuthor, chat: { outline_position: config.desktop.chat.outlinePosition, + permission_mode: config.desktop.chat.permissionMode, welcome: config.desktop.chat.welcome, }, context_panel_mode: config.desktop.contextPanelMode, @@ -382,6 +387,9 @@ function mergeConfig(current: LexoraConfig, patch: LexoraConfigPatch): LexoraCon chat: { ...current.desktop.chat, ...patch.desktop?.chat, + permissionMode: z.enum(BUDDY_PERMISSION_MODES).parse( + patch.desktop?.chat?.permissionMode ?? current.desktop.chat.permissionMode, + ), }, keybindings: keybindingsSchema.parse(patch.desktop?.keybindings ?? current.desktop.keybindings), taskSidebar: { diff --git a/apps/buddy/electron/main/config/__tests__/LexoraConfigStore.spec.ts b/apps/buddy/electron/main/config/__tests__/LexoraConfigStore.spec.ts index a5ee839b..558af4b4 100644 --- a/apps/buddy/electron/main/config/__tests__/LexoraConfigStore.spec.ts +++ b/apps/buddy/electron/main/config/__tests__/LexoraConfigStore.spec.ts @@ -39,19 +39,37 @@ describe('lexoraConfigStore', () => { await mkdir(dirname(configPath), { recursive: true }) await writeFile(configPath, '[desktop]\ntheme = "dark"\nwelcome_variant = "writing"\n[custom]\nkeep = true\n') const original = await store.read() - expect(original.desktop.chat).toEqual({ outlinePosition: 'top-right', welcome: 'random' }) + expect(original.desktop.chat).toEqual({ outlinePosition: 'top-right', permissionMode: 'policy_approval', welcome: 'random' }) await store.update({ desktop: { chat: { welcome: 'none' } } }) for (const outlinePosition of DESKTOP_CHAT_OUTLINE_POSITIONS) { await store.update({ desktop: { chat: { outlinePosition } } }) const restored = await new LexoraConfigStore({ configPath }).read() expect(restored).toEqual({ ...original, - desktop: { ...original.desktop, chat: { outlinePosition, welcome: 'none' } }, + desktop: { ...original.desktop, chat: { outlinePosition, permissionMode: 'policy_approval', welcome: 'none' } }, }) expect(await readFile(configPath, 'utf8')).toContain('keep = true') } }) + it('persists permission mode, defaults legacy profiles, and rejects invalid values without a partial write', async () => { + const { configPath, store } = await createConfigStore() + await mkdir(dirname(configPath), { recursive: true }) + await writeFile(configPath, '[desktop.chat]\nwelcome = "random"\n') + expect((await store.read()).desktop.chat.permissionMode).toBe('policy_approval') + + await store.update({ desktop: { chat: { permissionMode: 'full_access' } } }) + expect(await readFile(configPath, 'utf8')).toContain('permission_mode = "full_access"') + expect((await new LexoraConfigStore({ configPath }).read()).desktop.chat.permissionMode).toBe('full_access') + + const saved = await readFile(configPath, 'utf8') + await expect(store.update({ desktop: { chat: { permissionMode: 'invalid' as never } } })).rejects.toThrow() + expect(await readFile(configPath, 'utf8')).toBe(saved) + + await writeFile(configPath, '[desktop.chat]\npermission_mode = "invalid"\n') + await expect(store.read()).rejects.toThrow() + }) + it('migrates browser preferences with defaults and preserves unrelated settings across updates', async () => { const { configPath, store } = await createConfigStore() await mkdir(dirname(configPath), { recursive: true }) @@ -139,7 +157,7 @@ describe('lexoraConfigStore', () => { it('updates only requested settings and writes a private TOML file atomically', async () => { const { configPath, store } = await createConfigStore() await expect(store.read()).resolves.toMatchObject({ - desktop: { theme: 'system', chat: { outlinePosition: 'top-right', welcome: 'random' } }, + desktop: { theme: 'system', chat: { outlinePosition: 'top-right', permissionMode: 'policy_approval', welcome: 'random' } }, pet: { alwaysOnTop: true, enabled: true, rememberPosition: true }, }) @@ -157,7 +175,7 @@ describe('lexoraConfigStore', () => { keybindings: {}, backgroundCloseNoticeShown: false, pluginAuthor: '', - chat: { outlinePosition: 'top-right', welcome: 'writing' }, + chat: { outlinePosition: 'top-right', permissionMode: 'policy_approval', welcome: 'writing' }, taskSidebarPinnedItems: [], taskSidebar: { collapsed: false, @@ -300,7 +318,7 @@ describe('lexoraConfigStore', () => { await writeFile(configPath, '[desktop.chat]\nwelcome = "none"\nfuture = true\n') await expect(store.read()).resolves.toMatchObject({ - desktop: { chat: { outlinePosition: 'top-right', welcome: 'none' } }, + desktop: { chat: { outlinePosition: 'top-right', permissionMode: 'policy_approval', welcome: 'none' } }, }) await store.update({ desktop: { theme: 'dark' } }) diff --git a/apps/buddy/electron/shared/__tests__/desktopApi.spec.ts b/apps/buddy/electron/shared/__tests__/desktopApi.spec.ts index 48ac7c7e..860f1d04 100644 --- a/apps/buddy/electron/shared/__tests__/desktopApi.spec.ts +++ b/apps/buddy/electron/shared/__tests__/desktopApi.spec.ts @@ -5,8 +5,9 @@ import { feedbackIssueInputSchema, lexoraConfigPatchSchema, releasePageInputSche describe('desktop Preload API contract', () => { it('rejects unknown outline positions before updating the application configuration', () => { expect(() => lexoraConfigPatchSchema.parse({ desktop: { chat: { outlinePosition: 'center' } } })).toThrow() - expect(lexoraConfigPatchSchema.parse({ desktop: { chat: { outlinePosition: 'bottom-left', welcome: 'none' } } })) - .toEqual({ desktop: { chat: { outlinePosition: 'bottom-left', welcome: 'none' } } }) + expect(lexoraConfigPatchSchema.parse({ desktop: { chat: { outlinePosition: 'bottom-left', permissionMode: 'full_access', welcome: 'none' } } })) + .toEqual({ desktop: { chat: { outlinePosition: 'bottom-left', permissionMode: 'full_access', welcome: 'none' } } }) + expect(() => lexoraConfigPatchSchema.parse({ desktop: { chat: { permissionMode: 'invalid' } } })).toThrow() }) it('accepts release pages from the canonical repository', () => { diff --git a/apps/buddy/electron/shared/desktopApi.ts b/apps/buddy/electron/shared/desktopApi.ts index 84cc6d27..fc5153e4 100644 --- a/apps/buddy/electron/shared/desktopApi.ts +++ b/apps/buddy/electron/shared/desktopApi.ts @@ -8,6 +8,7 @@ import type { BuddyCapabilities } from '../../shared/platform' import type { BuddyRuntimeProfile } from '../../shared/runtime/profile' import type { DesktopCommandId, DesktopPlatform } from './desktopCommands' import type { LocalChatApi } from './localChatApi' +import { BUDDY_DEFAULT_PERMISSION_MODE } from '../../shared/permissions/permissionMode' export const DESKTOP_IPC_CHANNELS = { workbenchRead: 'lexora:workbench:read', @@ -148,11 +149,13 @@ export type DesktopChatOutlinePosition = typeof DESKTOP_CHAT_OUTLINE_POSITIONS[n export interface DesktopChatPreferences { outlinePosition: DesktopChatOutlinePosition + permissionMode: import('../../shared/permissions/permissionMode').BuddyPermissionMode welcome: DesktopChatWelcomePreference } export const DEFAULT_DESKTOP_CHAT_PREFERENCES: Readonly = { outlinePosition: 'top-right', + permissionMode: BUDDY_DEFAULT_PERMISSION_MODE, welcome: 'random', } diff --git a/apps/buddy/electron/shared/desktopApiSchemas.ts b/apps/buddy/electron/shared/desktopApiSchemas.ts index 14bb8be9..25556ca8 100644 --- a/apps/buddy/electron/shared/desktopApiSchemas.ts +++ b/apps/buddy/electron/shared/desktopApiSchemas.ts @@ -3,6 +3,7 @@ import { z } from 'zod' import { browserPreferencesSchema } from '../../shared/browser/browserPreferences' import { extensionAuthorSchema } from '../../shared/extensions/extensionIdentity' import { proxySettingsSchema } from '../../shared/network/proxySettings' +import { BUDDY_PERMISSION_MODES } from '../../shared/permissions/permissionMode' import { runtimePreferencesSchema } from '../../shared/runtime/runtimePreferences' import { keybindingsSchema } from '../../shared/shortcuts/keybindingSchema' import { @@ -65,6 +66,7 @@ export const lexoraConfigPatchSchema: z.ZodType = z.object({ backgroundCloseNoticeShown: z.boolean().optional(), chat: z.object({ outlinePosition: z.enum(DESKTOP_CHAT_OUTLINE_POSITIONS).optional(), + permissionMode: z.enum(BUDDY_PERMISSION_MODES).optional(), welcome: z.enum(['none', 'random', ...DESKTOP_CHAT_WELCOME_VARIANT_IDS]).optional(), }).strict().optional(), contextPanelMode: z.enum(['task', 'independent']).optional(), diff --git a/apps/buddy/shared/permissions/permissionMode.ts b/apps/buddy/shared/permissions/permissionMode.ts index e5e958dc..732afeb4 100644 --- a/apps/buddy/shared/permissions/permissionMode.ts +++ b/apps/buddy/shared/permissions/permissionMode.ts @@ -10,6 +10,8 @@ export const BUDDY_PERMISSION_MODES = [ export type BuddyPermissionMode = typeof BUDDY_PERMISSION_MODES[number] +export const BUDDY_DEFAULT_PERMISSION_MODE: BuddyPermissionMode = 'policy_approval' + export interface BuddyPermissionSettings { approvalPolicy: BuddyApprovalPolicy executionProfile: BuddyExecutionProfile diff --git a/apps/buddy/src/i18n/locales/en-US/settings.ts b/apps/buddy/src/i18n/locales/en-US/settings.ts index 2cdbcff8..4f53d464 100644 --- a/apps/buddy/src/i18n/locales/en-US/settings.ts +++ b/apps/buddy/src/i18n/locales/en-US/settings.ts @@ -87,6 +87,8 @@ export default { 'desktop.browser.screenshotSaved': 'Screenshot saved', 'desktop.browser.screenshotCopied': 'Screenshot copied to clipboard', 'desktop.browser.screenshotFailed': 'Could not capture the screenshot. Retry or change the destination in browser settings.', + 'desktop.settings.defaultPermissionMode': 'Default permission for new tasks', + 'desktop.settings.defaultPermissionModeDescription': 'Applies only to new tasks. Existing conversations keep their current mode.', 'desktop.settings.contextPanel': 'Resource panel', 'desktop.settings.contextPanelMode': 'Browsing mode', 'desktop.settings.contextPanelModeDescription': 'Task-linked selects resources when focusing a task. Independent keeps the current selection.', diff --git a/apps/buddy/src/i18n/locales/zh-CN/settings.ts b/apps/buddy/src/i18n/locales/zh-CN/settings.ts index d2f3ac4b..254c4074 100644 --- a/apps/buddy/src/i18n/locales/zh-CN/settings.ts +++ b/apps/buddy/src/i18n/locales/zh-CN/settings.ts @@ -85,6 +85,8 @@ export default { 'desktop.browser.screenshotSaved': '截图已保存', 'desktop.browser.screenshotCopied': '截图已复制到剪贴板', 'desktop.browser.screenshotFailed': '截图失败,请重试或在浏览器设置中切换保存方式。', + 'desktop.settings.defaultPermissionMode': '新任务默认权限', + 'desktop.settings.defaultPermissionModeDescription': '只影响新建任务,已有会话保持原模式。', 'desktop.settings.contextPanel': '资源面板', 'desktop.settings.contextPanelMode': '浏览模式', 'desktop.settings.contextPanelModeDescription': '任务联动在聚焦任务时选择其资源,独立浏览保留当前选择。', diff --git a/apps/buddy/src/modules/prompt-input/index.ts b/apps/buddy/src/modules/prompt-input/index.ts index 9606ef90..d37b5bfa 100644 --- a/apps/buddy/src/modules/prompt-input/index.ts +++ b/apps/buddy/src/modules/prompt-input/index.ts @@ -1,3 +1,4 @@ +export { default as DesktopFullAccessConfirmationDialog } from './components/DesktopFullAccessConfirmationDialog.vue' export { CHAT_PROMPT_DIRECTIVE_NODE_NAME, chatComposerDocumentToUserContent, userContentToChatComposerDocument } from './model/chatComposerDocument' export type { ChatComposerContextOptions, ChatComposerSubmitPayload, ChatComposerTrigger, ChatPromptContextOption } from './model/chatComposerInput' export { createChatComposerContentFromText, createChatComposerSourceOptions, createChatComposerSuggestions, findChatComposerTrigger, serializeChatComposerContent, shouldSubmitChatComposerKey } from './model/chatComposerInput' diff --git a/apps/buddy/src/modules/tasks/state/drafts/__tests__/useChatDrafts.spec.ts b/apps/buddy/src/modules/tasks/state/drafts/__tests__/useChatDrafts.spec.ts index 1a196492..70a0299e 100644 --- a/apps/buddy/src/modules/tasks/state/drafts/__tests__/useChatDrafts.spec.ts +++ b/apps/buddy/src/modules/tasks/state/drafts/__tests__/useChatDrafts.spec.ts @@ -1,4 +1,5 @@ import type { LocalComposerDraft } from '@buddy-shared/conversation/composerApi' +import type { BuddyPermissionMode } from '@buddy-shared/permissions/permissionMode' import { describe, expect, it, vi } from 'vitest' import { computed, ref } from 'vue' @@ -7,6 +8,23 @@ import { createBuddyUserContent } from '../../../../../../shared/conversation/bu import { useChatDrafts } from '../useChatDrafts' describe('useChatDrafts', () => { + it('uses the configured permission mode for newly created scopes but leaves existing drafts unchanged', () => { + const targetKey = ref('global') + const defaultPermissionMode = ref('policy_approval') + const drafts = useChatDrafts({ + defaultPermissionMode: computed(() => defaultPermissionMode.value), + onChange: vi.fn(), + targetKey: computed(() => targetKey.value), + }) + expect(drafts.executionProfile.value).toBe('workspace_write') + + defaultPermissionMode.value = 'full_access' + expect(drafts.executionProfile.value).toBe('workspace_write') + targetKey.value = 'space:space-1' + expect(drafts.executionProfile.value).toBe('full_access') + expect(drafts.approvalPolicy.value).toBe('policy') + }) + it('owns a complete editor value per strict Draft scope', () => { const { drafts, targetKey } = createFixture() const globalId = drafts.draftId.value diff --git a/apps/buddy/src/modules/tasks/state/drafts/useChatDrafts.ts b/apps/buddy/src/modules/tasks/state/drafts/useChatDrafts.ts index 3274cf10..1ee9bb10 100644 --- a/apps/buddy/src/modules/tasks/state/drafts/useChatDrafts.ts +++ b/apps/buddy/src/modules/tasks/state/drafts/useChatDrafts.ts @@ -1,13 +1,12 @@ import type { BuddyUserContentV1 } from '@buddy-shared/conversation/buddyUserContent' import type { LocalComposerDraft } from '@buddy-shared/conversation/composerApi' -import type { BuddyPermissionSettings } from '@buddy-shared/permissions/permissionMode' +import type { BuddyPermissionMode, BuddyPermissionSettings } from '@buddy-shared/permissions/permissionMode' import type { JSONContent } from '@tiptap/core' import type { ComputedRef } from 'vue' import type { ChatDrafts, ChatDraftSnapshot, ChatDraftState, ComposerDraftReceipt } from './typing' import { buddyUserContentToText, createBuddyUserContent } from '@buddy-shared/conversation/buddyUserContent' -import { BUDDY_DEFAULT_APPROVAL_POLICY } from '@buddy-shared/permissions/approvalPolicy' -import { BUDDY_DEFAULT_EXECUTION_PROFILE } from '@buddy-shared/permissions/executionProfile' +import { BUDDY_DEFAULT_PERMISSION_MODE, resolveBuddyPermissionSettings } from '@buddy-shared/permissions/permissionMode' import { computed, shallowReactive, shallowRef, watch } from 'vue' import { chatComposerDocumentToUserContent, @@ -19,6 +18,7 @@ import { import { createDraftValueFingerprint } from '../../model/drafts/draftValueFingerprint' interface UseChatDraftsOptions { + defaultPermissionMode?: ComputedRef onChange: () => void targetKey: ComputedRef } @@ -36,7 +36,7 @@ export function useChatDrafts(options: UseChatDraftsOptions): ChatDrafts { () => [activeTargetKey.value, draftsByScope.has(activeTargetKey.value)] as const, ([key, exists]) => { if (!exists) - draftsByScope.set(key, emptyDraft(key)) + draftsByScope.set(key, emptyDraft(key, options.defaultPermissionMode?.value)) }, { flush: 'sync', immediate: true }, ) @@ -71,7 +71,7 @@ export function useChatDrafts(options: UseChatDraftsOptions): ChatDrafts { const existing = draftsByScope.get(key) if (existing) return existing - const value = emptyDraft(key) + const value = emptyDraft(key, options.defaultPermissionMode?.value) draftsByScope.set(key, value) return value } @@ -180,7 +180,7 @@ export function useChatDrafts(options: UseChatDraftsOptions): ChatDrafts { resumeIsolated(targetKey: string) { if (!draftsByScope.has(targetKey)) { const current = currentDraft.value - draftsByScope.set(targetKey, { ...emptyDraft(), approvalPolicy: current.approvalPolicy, executionProfile: current.executionProfile, modelSelection: current.modelSelection }) + draftsByScope.set(targetKey, { ...emptyDraft('', options.defaultPermissionMode?.value), approvalPolicy: current.approvalPolicy, executionProfile: current.executionProfile, modelSelection: current.modelSelection }) } isolatedDraft.value = { sourceKey: options.targetKey.value, targetKey } options.onChange() @@ -322,15 +322,16 @@ export function useChatDrafts(options: UseChatDraftsOptions): ChatDrafts { } } -function emptyDraft(targetKey = ''): ChatDraftState { +function emptyDraft(targetKey = '', mode: BuddyPermissionMode = BUDDY_DEFAULT_PERMISSION_MODE): ChatDraftState { + const defaultSettings = resolveBuddyPermissionSettings(mode) return { - approvalPolicy: BUDDY_DEFAULT_APPROVAL_POLICY, + approvalPolicy: defaultSettings.approvalPolicy, confirmedSnapshot: null, content: createBuddyUserContent(), draftId: targetKey.startsWith('draft:') ? targetKey.slice(6) : crypto.randomUUID(), editorSessionId: crypto.randomUUID(), editVersion: 0, - executionProfile: BUDDY_DEFAULT_EXECUTION_PROFILE, + executionProfile: defaultSettings.executionProfile, modelSelection: null, revision: null, } diff --git a/apps/buddy/src/modules/tasks/state/useTaskCapability.ts b/apps/buddy/src/modules/tasks/state/useTaskCapability.ts index 8b32b1e0..efe52131 100644 --- a/apps/buddy/src/modules/tasks/state/useTaskCapability.ts +++ b/apps/buddy/src/modules/tasks/state/useTaskCapability.ts @@ -7,6 +7,7 @@ import type { ModelProvidersStore } from '@/modules/models' import type { ApplicationSettings } from '@/modules/settings' import type { ChatBlockerKind } from '@/modules/tasks/model/status/typing' import type { RuntimeSupervisorStore } from '@/platform/runtime/useRuntimeSupervisorStore' +import { BUDDY_DEFAULT_PERMISSION_MODE } from '@buddy-shared/permissions/permissionMode' import { until } from '@vueuse/core' import { computed, readonly, shallowRef, watch } from 'vue' import { useBuddyI18n } from '@/i18n/buddyI18n' @@ -127,7 +128,11 @@ export function useTaskCapability(options: UseTaskCapabilityOptions): TaskCapabi spaceId: spaceId.value, })) let persistDraftChanges = () => {} + const defaultPermissionMode = computed(() => ( + applicationSettings.config.value?.desktop.chat.permissionMode ?? BUDDY_DEFAULT_PERMISSION_MODE + )) const drafts = useChatDrafts({ + defaultPermissionMode, onChange: () => persistDraftChanges(), targetKey: draftScopeKey, }) diff --git a/apps/buddy/src/modules/tasks/widgets/transcript/__tests__/BuddyChatActivities.spec.ts b/apps/buddy/src/modules/tasks/widgets/transcript/__tests__/BuddyChatActivities.spec.ts index be3e96f0..e02461b1 100644 --- a/apps/buddy/src/modules/tasks/widgets/transcript/__tests__/BuddyChatActivities.spec.ts +++ b/apps/buddy/src/modules/tasks/widgets/transcript/__tests__/BuddyChatActivities.spec.ts @@ -412,7 +412,7 @@ function mountTurn(nodes: ChatAgentTurnNode[], status: ChatAgentTurn['status'] = language: shallowRef('zh-CN'), isDark: shallowRef(false), appSidebarCollapsed: shallowRef(false), - chat: shallowRef({ outlinePosition: 'top-right', welcome: 'random' }), + chat: shallowRef({ outlinePosition: 'top-right', permissionMode: 'policy_approval', welcome: 'random' }), }) useProvideChatContent({ canPreviewFile: () => false, previewFile: () => {}, writeClipboardText: async () => {} }) const navigation = useChatActivityNavigation() diff --git a/apps/buddy/src/modules/tasks/widgets/transcript/__tests__/BuddyChatMessageContent.spec.ts b/apps/buddy/src/modules/tasks/widgets/transcript/__tests__/BuddyChatMessageContent.spec.ts index 84ed6a91..21b56063 100644 --- a/apps/buddy/src/modules/tasks/widgets/transcript/__tests__/BuddyChatMessageContent.spec.ts +++ b/apps/buddy/src/modules/tasks/widgets/transcript/__tests__/BuddyChatMessageContent.spec.ts @@ -85,7 +85,7 @@ describe('buddyChatMessageContent', () => { language: shallowRef('zh-CN'), isDark: shallowRef(false), appSidebarCollapsed: shallowRef(false), - chat: shallowRef({ outlinePosition: 'top-right', welcome: 'random' }), + chat: shallowRef({ outlinePosition: 'top-right', permissionMode: 'policy_approval', welcome: 'random' }), }) useProvideChatContent({ canPreviewFile, @@ -224,7 +224,7 @@ function renderContent(message: LocalMessage, isDark = false) { language: shallowRef('zh-CN'), isDark: shallowRef(isDark), appSidebarCollapsed: shallowRef(false), - chat: shallowRef({ outlinePosition: 'top-right', welcome: 'random' }), + chat: shallowRef({ outlinePosition: 'top-right', permissionMode: 'policy_approval', welcome: 'random' }), }) return () => h(BuddyChatMessageContent, { language: 'zh-CN', diff --git a/apps/buddy/src/modules/tasks/widgets/transcript/__tests__/DesktopChatTranscript.spec.ts b/apps/buddy/src/modules/tasks/widgets/transcript/__tests__/DesktopChatTranscript.spec.ts index 45790258..53a9a36f 100644 --- a/apps/buddy/src/modules/tasks/widgets/transcript/__tests__/DesktopChatTranscript.spec.ts +++ b/apps/buddy/src/modules/tasks/widgets/transcript/__tests__/DesktopChatTranscript.spec.ts @@ -96,7 +96,7 @@ function mountTranscript(loadOutlineMessages: () => Promise h(DesktopChatTranscript, props.value) }, diff --git a/docs/specs/feature-005-default-permission-mode.md b/docs/specs/feature-005-default-permission-mode.md new file mode 100644 index 00000000..40ece7b1 --- /dev/null +++ b/docs/specs/feature-005-default-permission-mode.md @@ -0,0 +1,192 @@ +# Spec-005:新增"新任务默认权限"设置 + +**日期:** 2026-09-28 + +**状态:** 已实现;自动化测试覆盖核心配置与草稿逻辑,待手动验收 + +**目标:** 在设置页新增一项"新任务默认权限",让用户把新任务的默认审批模式从"智能审批"改成自己偏好的模式(例如"完全访问")。该设置只影响此后新建的任务,不追溯已有会话。 + +--- + +## 1. 背景与问题定位 + +### 1.1 现状 + +每个任务的权限模式由"草稿"(composer draft)携带,底层字段是两个: + +- `approvalPolicy`(审批策略):`manual` / `policy` +- `executionProfile`(执行档位):`read_only` / `workspace_write` / `full_access` + +界面上看到的 4 个模式,是由这两个字段反推出来的(`resolveBuddyPermissionMode()`): + +| 界面名称 | `approvalPolicy` | `executionProfile` | +|---|---|---| +| 只读 | `policy` | `read_only` | +| 人工审批 | `manual` | `workspace_write` | +| 智能审批 | `policy` | `workspace_write` | +| 完全访问 | `policy` | `full_access` | + +相关代码: + +- `apps/buddy/shared/permissions/permissionMode.ts`:模式定义与双向转换。 +- `apps/buddy/src/modules/tasks/state/drafts/useChatDrafts.ts`:`emptyDraft()` 决定新草稿的初值。 +- `apps/buddy/src/modules/prompt-input/components/DesktopPermissionModeSelector.vue`:输入框里的权限弹窗,当前唯一的修改入口。 + +### 1.2 问题 + +新草稿的初值是**硬编码**的: + +- `BUDDY_DEFAULT_APPROVAL_POLICY = 'policy'` +- `BUDDY_DEFAULT_EXECUTION_PROFILE = 'workspace_write'` + +两者组合即"智能审批"。用户可以在输入框的权限弹窗里逐个任务改成"完全访问",但**没有任何全局默认值**。不认同"智能审批"的用户,每新建一个任务都要手动改一次。 + +### 1.3 本功能要解决的事 + +提供一个全局默认值,让用户在设置页改一次,之后新建的任务自动使用该模式。 + +## 2. 已确认的决定 + +### 决定 1:出厂默认保持"智能审批" + +`config.toml` 中该设置的默认值定为 `policy_approval`,不是 `full_access`。 + +理由:如果出厂默认改成"完全访问",所有**已经安装** Lexora 的用户在升级后会静默变成完全访问 —— 用户没有点过任何按钮,权限就被放开了。需要完全访问的用户自己在设置里改一次即可。 + +### 决定 2:设置变化不追溯已有任务 + +修改设置后,**只有此后新建的任务**使用新默认值;已经打开或已经存在的任务保持原模式不变。 + +原因见 4.2 节(草稿在打开时就已经落库)。当前那个"还没开始用的空任务"不会跟着变,设置行下方需要一句说明文案。 + +## 3. 需求边界 + +**包含:** + +- 设置页新增一项"新任务默认权限",可选 4 个模式(只读 / 人工审批 / 智能审批 / 完全访问)。 +- 该值持久化到 `~/.lexora/config.toml`。 +- 新建任务的草稿初值使用该设置。 +- 选择"完全访问"作为默认值时,弹出确认对话框(复用现有组件)。 + +**不包含:** + +- 不改变已有会话、已有草稿、正在运行任务的权限。 +- 不改变输入框权限弹窗的逐个任务覆盖行为(它仍然是每个任务的临时开关)。 +- **不作用到后台定时任务。** 定时任务有自己的 `executionProfile`(`service/src/automations/createAutomationTool.ts`,默认 `workspace_write`)。"无人值守 + 完全访问"风险更高,应由独立开关控制,不跟随本设置。 +- 不新增权限模式,不修改权限判定与越级限制逻辑。 + +## 4. 设计方案 + +### 4.1 数据模型:存"模式",不存两个底层字段 + +配置项存 `BuddyPermissionMode`(即 4 个模式之一),而不是分别存 `approvalPolicy` 和 `executionProfile`。 + +理由: + +- "模式"才是用户看得见的概念; +- 分别存两个字段会产生 UI 里根本不存在的组合(例如 `manual` + `read_only`); +- 已有 `resolveBuddyPermissionSettings(mode)` 可以直接把模式展开成两个底层字段,不需要新增转换逻辑。 + +配置落点:`desktop.chat.permissionMode`,与 `desktop.chat.welcome`、`desktop.chat.outlinePosition` 同级。在 TOML 文件里写作 `permission_mode`。 + +同时在 `apps/buddy/shared/permissions/permissionMode.ts` 新增常量: + +```ts +export const BUDDY_DEFAULT_PERMISSION_MODE: BuddyPermissionMode = 'policy_approval' +``` + +由它作为出厂默认的唯一定义处,现有两个 `BUDDY_DEFAULT_*` 常量继续作为配置加载完成前的兜底值。 + +### 4.2 生效路径 + +新草稿的初值只在一个地方产生:`useChatDrafts()` 里的 `emptyDraft()`。方案是在这里注入配置值: + +1. `useChatDrafts()` 增加入参 `defaultPermissionSettings`,`emptyDraft()` 用它填初值。 +2. `apps/buddy/src/modules/tasks/state/useTaskCapability.ts` 从 `applicationSettings.config` 计算该值: + `resolveBuddyPermissionSettings(config?.desktop.chat.permissionMode ?? BUDDY_DEFAULT_PERMISSION_MODE)` +3. 往下全部复用现有链路:草稿落库时写入 `initialExecutionConfig`,发送消息时会话继承草稿的权限设置。 +4. 本地服务侧不需要改动。`isExecutionProfileWithin()` 的越级限制照旧生效。 + +#### 为什么"不追溯已有任务"是自然结果 + +按现在的实现,草稿在任务工作区挂载时就会被写入数据库(`useTaskWorkspacePersistence.restoreWorkspace()` → `ensureDraft()` → `composerDrafts.open()`)。 + +因此会出现这个现象: + +1. 用户打开软件,进入任务页 → 当前空任务的权限被存成"智能审批"; +2. 用户去设置页改成"完全访问"; +3. 用户回到刚才那个任务 → 仍然是"智能审批"(它的记录已经存在); +4. 用户新建任务 → 才是"完全访问"。 + +配置尚未加载时创建的草稿也按此规则处理:先用 `BUDDY_DEFAULT_PERMISSION_MODE`("智能审批")创建;如果该草稿随后已落库,配置加载完成或用户修改设置后都不追溯刷新它。这里的"新建任务使用新默认值"指创建草稿时默认配置已经可用的任务;配置加载前创建的草稿属于已存在草稿。 + +这不是缺陷,而是"已落库的记录不被追溯修改"。本方案接受该行为(决定 2),但必须在设置页写清说明文案。 + +如果将来要改成"立刻跟随",需要额外引入"该草稿是否被用户手动改过权限"的标记,只在"内容为空 + 未手动改过"时刷新。这属于后续可选方向,不在本次范围。 + +### 4.3 界面 + +最小方案:在"常规"设置页新增一行。 + +- 位置:`apps/buddy/src/modules/settings/widgets/app/DesktopGeneralSettings.vue`,复用现有的 `desktop-settings-row` 结构与样式。 +- 控件:`NSelect`,4 个选项。 +- 文案:**复用**输入框权限弹窗已有的标签和描述文案键,不新写一套,避免以后改文案要改两处。权限弹窗会根据沙盒状态切换 sandbox 描述;设置页不展示沙盒状态专属描述,只展示下列通用描述,因为此设置表达的是默认权限模式,而非当前任务的实际沙盒边界: + - 标签:`desktop.chat.executionProfileReadOnly`、`desktop.chat.permissionModeManual`、`desktop.chat.permissionModePolicy`、`desktop.chat.executionProfileFull` + - 描述:`desktop.chat.permissionModeReadOnlyDescription`、`desktop.chat.permissionModeManualDescription`、`desktop.chat.permissionModePolicyDescription`、`desktop.chat.permissionModeFullDescription` +- 行下方常驻说明文案(新增键):"只影响新建任务,已有会话保持原模式。" +- 选择"完全访问"时,先弹出 `apps/buddy/src/modules/prompt-input/components/DesktopFullAccessConfirmationDialog.vue`(现有组件,带勾选确认),确认后才写入设置。 + +若后续还要加入沙盒状态、审批规则、工具白名单等,再考虑独立开一个"权限"设置分类(新增路由 + i18n + 侧边栏项)。只加这一个开关时,常规页改动面更小。 + +### 4.4 预期改动范围 + +| 文件 | 计划调整 | +|---|---| +| `apps/buddy/shared/permissions/permissionMode.ts` | 新增 `BUDDY_DEFAULT_PERMISSION_MODE` 常量 | +| `apps/buddy/electron/shared/desktopApi.ts` | `DesktopChatPreferences` 与 `DEFAULT_DESKTOP_CHAT_PREFERENCES` 增加 `permissionMode` | +| `apps/buddy/electron/shared/desktopApiSchemas.ts` | `lexoraConfigPatchSchema.desktop.chat` 增加 `permissionMode`(该对象是 `.strict()`,不加会直接报错) | +| `apps/buddy/electron/main/config/LexoraConfigStore.ts` | `desktopConfigSchema.chat` 增加 `permission_mode`;`decodeConfig`、`encodeConfig` 的 chat 映射各加一行;schema 默认值块同步 | +| `apps/buddy/src/modules/tasks/state/drafts/useChatDrafts.ts` | `emptyDraft()` 接收注入的默认权限设置 | +| `apps/buddy/src/modules/tasks/state/useTaskCapability.ts` | 从配置计算默认权限设置并传入 | +| `apps/buddy/src/modules/settings/widgets/app/DesktopGeneralSettings.vue` | 新增设置行与完全访问确认 | +| `apps/buddy/src/i18n/locales/zh-CN/settings.ts`、`en-US/settings.ts` | 新增行标题与说明文案 | +| 相关类型与 schema 测试 | 验证配置类型、默认值、合法/非法值校验及读写映射 | +| `useChatDrafts`、设置组件及配置存储相关测试 | 覆盖默认注入、配置加载前回退、既有草稿不追溯、确认取消与保存失败等边界 | + +**最容易漏的一处:** `desktopConfigSchema.chat` 使用了 `.passthrough()`。只往 TOML 里写 `permission_mode` 而不同步 `decodeConfig` / `encodeConfig` 的映射,值会被静默保留但读不出来 —— 表现为"设置改了但完全没生效,而且不报错"。 + +## 5. 验收标准 + +- [ ] 设置页出现"新任务默认权限",可选 4 个模式,默认显示"智能审批"。 +- [ ] 选择"完全访问"时先弹出确认对话框,取消则不写入。 +- [ ] 设置值写入 `~/.lexora/config.toml` 的 `permission_mode`,重启应用后仍生效。 +- [ ] 修改设置后,新建任务的权限弹窗显示新模式。 +- [ ] 修改设置后,已有会话与已有草稿的权限模式保持不变。 +- [ ] 设置页的说明文案明确写出"只影响新建任务"。 +- [ ] 后台定时任务的 `executionProfile` 不受该设置影响。 +- [ ] 配置更新入口拒绝非法 `permissionMode`,不产生半写入状态;配置文件加载时遇到非法 `permission_mode`,按配置 store 的既有 schema 校验策略处理,不得静默接受非法值,并应覆盖相应行为的测试。 +- [ ] 配置加载完成前(`config` 为 `null`)新建的草稿回退到"智能审批",不报错;该草稿之后不因配置加载完成而自动刷新权限。 +- [ ] 完全访问确认对话框取消时不写入设置;确认后才提交更新。 +- [ ] 设置保存失败时界面显示失败反馈,选择器恢复为配置中的实际值;保存进行中禁用重复提交。 + +## 6. 风险和取舍 + +- **完全访问的暴露面变大。** 之前它是"单个任务的临时选择",现在会成为"每个新任务的起点"。因此保留确认对话框是必要的,且输入框的红色危险样式继续保留,让用户在每个新任务上都能看到当前是完全访问。 +- **设置与当前任务不一致的观感。** 用户改完设置回到刚才的任务,模式没变(见 4.2)。这是决定 2 的已知代价,用说明文案缓解。 +- **出厂默认不能跟着改。** 决定 1 会让"想要完全访问"的用户仍需手动改一次。这是为了保护已有用户不被静默放开权限,属于有意取舍。 +- **本功能不提升权限上限。** 完全访问仍然受操作系统权限约束,不会获得管理员权限,部分敏感操作仍需确认。 + +## 7. 未决事项 + +- 是否在输入框权限弹窗底部增加"设为默认"入口(让用户不必跳到设置页)。当前未纳入本次范围。 +- 是否需要在将来让"还没开始用的空任务"立刻跟随新默认值(4.2 末尾描述的可选方向)。 + +## 8. 实现记录 + +已实现本方案的配置持久化、设置页入口和新草稿默认值注入。配置仍以 `desktop.chat.permissionMode` 表示,并通过既有双向转换得到底层审批策略与执行档位;后台自动化不读取该设置。选择"完全访问"时复用现有确认对话框,保存失败时复用设置页错误提示,选择器由配置值控制。 + +自动化测试覆盖配置默认值、TOML 读写、非法值拒绝及新建草稿使用配置模式/既有草稿不追溯。验收清单暂不勾选:设置页完整交互、应用重启后生效及相关边界仍需手动验收;规范中未列出的组件级 UI 自动化测试也未补充。 + +--- + +**状态说明:** 实现已随本 PR 提交;验收清单保留为待验证项,合并前后可继续补充手动验收结果。 From f6f0db0ea1b9f59520252ef847ff4f60fe2b2f16 Mon Sep 17 00:00:00 2001 From: QAyong Date: Mon, 28 Sep 2026 16:19:52 +0800 Subject: [PATCH 2/3] docs: add default permission settings screenshot --- .../feature-005-default-permission-mode.png | Bin 0 -> 16650 bytes .../feature-005-default-permission-mode.md | 4 ++++ 2 files changed, 4 insertions(+) create mode 100644 docs/specs/assets/feature-005-default-permission-mode.png diff --git a/docs/specs/assets/feature-005-default-permission-mode.png b/docs/specs/assets/feature-005-default-permission-mode.png new file mode 100644 index 0000000000000000000000000000000000000000..5566b31f035370cfb0699fddec72e92b2ae5c98e GIT binary patch literal 16650 zcmd732T)U8_dXg$L;>NoAkr-;2vVeobS!k~1f+%{HG~#=7eroB1VMW59YhES0Rk$Z z^cs2)DWM015J(98PvUpK-`u(LpF4Nv-kF<$$;rviK6|gd_A1Y__8VPo)eH3O^dJ!E zg1XvMeGrId83a1Tc1-B zsA7G2`LRKaa+N&hDu?=w0{0k1F<#@5qx8kQR`-Hn)rvNcF3DR}WeB?khSA)NiqpIf zK{e=;t8kY{_>w4feU7?gM*bJ4cgeOm`C5X*e%7ILXEb2}&HDoo;L3Jv8VPUQ?R4d6BWuN4#JU5Wi-?x3*ie`f%TeT}6 zvO{oAyly_`R>1nt$@qfbbe3o{x8{;~uOrWy8O=G!#zRZY778 zx5m3Ypo%{30UJy2E^BK^1Lk4g1xQ1EWp{(g-B6RBDg?3G^dJw+7CLUZxJf5_TV3@* z&^ObWzQmci!v>JEwJ27RSu=IKfhRQnb*z)n>Ej_ zP47f}Z=I0r8FPwqQx83g+s~Lu#nv4P?q4CeX{egts)Ua;8nRO)bDbN^3fv|D) z-U1p1g_yUrIDAY`$j_MJTzY}!mzAC7q!>t$k|}`zN}E0Wn|_5e^xnA7 z#@35E_|1@ca0Ivbm|Q$FV_wTaX&rVkx42j}+uIkq5y^V?_3k^fBcV%gN=SYU!mTSe zA5%?{)zhB7zOfEgi-N6%+32S??HfM6X+Qdo?ecCvWZjddhAY zq}MU6Ek%fvi6-rGm)`KpzSYd7YJp1jMup4VZm+mNO146y>GbH2}`<~Bx4vn=7)K#b}48z<8|En1g*({8Fa{r=p1 zqcnrGgc-Wro7v`EU;Z={^*CUHOUa>PFbhdPngz*utR96PK!2kvNEI|vgN{3+0)3NH ztKv3Z{drN*CQKRjrY~-EFQOLj{zX5XTSRvxLrB1jZkO%$@XMsaSSnUWR}q%JFTM?5_*+Xe@Q*BCst4#i5@D7bzniDlwFAFkBGn7mqsC z_UUkY#1)1?SLmvxi9@6Nc@pwPEIi&wZ&VxtBNtDU53MpXx-yYCn>n>>SV#}fIn?R+ zw%E%ei*O)m3)W5LnrBY$ZJbgU7}`rOT5I3jPhq(o&lOFWfNAq9)`j@UePjhYC)5t? zXA((l`)^C1GMKY$lcCT(m*+mTvwTO>I8=*MI+MCUb2;XiLqh_dCOy$5xbEGX9QFh9y_!NsptZnCnX{xTZoy{lU}hd@M%2TBYn8 zRaFBY3%0KcrHPw0Of*C>Pq6ssH*xZuyr)k#iJgX&3<>!pqFR0ypUze^POlfyQ*IS} z>g#jROCRSoa!K|F(ozwn6qz-U^MsDsTdhe$`LHUOx!wuISJALyz3T?VHP@c@f9x}m zZ+YbjVzWK!@UIzhehqg%Tsgm7T|v0exTz%>u$UwH;$+HiqE6r^%vtQ4JCCd7Z^MCN z=YR^CElIfPZ;3x-Xy$H z>}>yGG^C`@8Rs%`{HcHq179NWxMw?59aKcTGNEq&u=WQ5;fGq98`n*$50x=@mzk#w z+8wGUO0`&MYa+*^Ew@wkvkcR@)k}E0o>bsxI+;Q1vW7cf1b=m6j9Uc$SXj%<wbD|S>YQfc@}k`UhA`fWd`j=8HzGKk%UZ!B+{?pM;T#f`RX$x5hWJf= z{*xDbXLkEx`yV}#VqEDqmia~b%T}RLXyj=`%X9NU1Fh!GD6X_a!=nti;5$@)6t)+A zt9pXP-FPl*6Hvc5HrB`UG?-FcY_kpBX5ML*HLgetY&fCs8jq|oCawFJAWkoFQdK&v zWgt0nT=Q$52F*;vvIWN6R`K&aw>2lD!NyhS&iK&e4VFYld%dWs zi10(fb;L^K`vd9U%FB%lJMbkgDUlk%KWSxjn<(XYdkhS6vtKt3a($Jn<#JL>deirYV28|jY^)LIa z{VTKPYe}A&!4$(TgL&f7_vphs*7{O>m;`*vTnpK#L}Dgp=x}55l%Zx`V+E&I6JY_u z_xhTVmJIg%_2I~jNSyu0gJ+>Q-I3j$yIVL;Nxn8SMS7V0Mxny%hY7u0>>)M!AC zl6W%M+5XFuZjr&`{?kJIQ+ZM8GQX3Zda}z>mC>hNvc}MWl`cZQfRLJ%c9SO!hHS^> zeZ%(w$0F@JllEwP`&6LJaeQN$-%Q#vDzwr&Bv6#Zxr-P+iarg!?O(P;5tJyeik4_Y zQObDvwtx9!7jS>3(@d}-0=6mg5x|7UVv(vVcu67*l^vY(h_|^TkzUx@V#o3Z5vYBK7gwsz$1nQH~K;-4n zGyKivmgxfYb)hh=$AzlB_@WW&; zxNtj=K*l-Aq#1%OD=XhPY~XV(%%I(drmfnQ_R0DA>R?H&W8Z5#7TkVrXN^raTcwo0e6p%b!}akg!Y*V?p;h1(F@z5n}iW zp2ahXbSh3^t_f!xu!x$yt4`EtU$Q5d?Dn)!gIGr7%d@-g28gnHKkcLSC%IQ$XDa-w zYPmA8DG1N%khN!JE@s8UV||$V6dzcn$?eLLM6^+PZnfr4(r8&@o;?1OmMEPVeftdd z0$`!7_N2?LFnUXn7HxN3Xg3FV@eEux2&$jR9{j`|S&ud>pLcZnL*HPb?8ZM)(MgYD z7-F|Z0y28bak;om78hZ~#5f^{Izi!i+zz0Tk8jj!#$Sn1!K0W@EKhb`qaGkn7v4&V zO@mX_HFW=Q)-1a2)6?N06>uunFFCoiZl>%tEBNZFl7FhkdI(lVMDC~m%H6wTWwU`} z8#6{pX*3|lHrbQ?sx0VKo})w!h7;(0WA`M-_juEyR&YW0K9I?@ed2KJnE7p3-{>-%m zS>x%On`W@Bk?}gbPh_lyvChqZX;RzHV_9Q*49Mar^kt6h;X+o3n9buxYDRxeJz8J} zyw+#>P+fpL*)!F!ZnsIopJvfA&3SFHUmt9?lbsi>k{`VsDwT`NMR{!UAmvM(^a6QE zca<ULy+aE18~tlL@HPjXcEMpWpYnYt4i8sCAy55B07c!UYSv&V0e+?x?fO9aae= z_rxrut@F0B%wvsP*;T}e5%z3#SPA7G&vWeH^)C%Zc+aU=}_9sGouEK=%-5qDPm^7`OW zW#gu`WZ)D%7{BfyQEi)PPYyyA7mE}s22MV&Bo`GPP)^9NlbAWpMnBT-bD`}S@rWQ)tL{pizLvdb8U zJ^WeC)e1-fhu1UNarwMt1&OAvkJ-&O55ZoZrPuzbUXW}TlTUt|}mUY#KCh6?*hZrE4- zjIGmZm0iIdN4T1!)TB}@PBr;dqX!6Dbtio@@0}dJSoKOV8T*6xq36B53PiGvB`m|5K&(`o><$dMpxt=Vc+n^-HG|B zaV8I=ZI8%<9XsM=W$5q#IlPqXG&y`~5s6hFkX5|)P?723B^qdjO_iTJ{P`)@8hT65 zlTTI(#au)*Pg19*lu=_j4m~&GQ4aA_2;2&DqFw*~PevP7HN0xJymH&0%;Pnu3DI7O zjWT6Z>@+VjOXlr=;2y$%Xk=^ydho;}NcI;U=QtMD?<$(z({t$N5^aeOwxn$h{YhF- z`ZInCbX~l0V1YM<=7(WOyLrU5ezGS{nzJ`JeCWVN3w^QWe!E4X&pseJ)K*HX_j=PQN`qF^!DX@+ry4nIpD}81w5Gz40+Nzg*U@peW{7wpMk$N~;NXSoP~y za{P;I4_#4w;9R>+?u^Ce{&}oD|33L1??7PE0EvO|w@Sw>%Eq-Nio=qDc=0R!&S7Wl zl>}VKGf88jaj(Jgqg(DifPqi#aI-GVJ+~ZEP4jE!{_}Qe{uJ5%eeWaci2FbeTkEfW zO0Q|0TZX{3WKqu=F!IW%(7x&bsGu-<(H5fZ2iXEW2th~G46+*D$4#}EDuxC}U(_WZ zUrnyED6>Er@626~H+*4W6QXSy(k^E*=yfvNFR>@AlfEf=!ozZ8&;LcMb-FBGHaq<^ z=Ixx}zMJ=9Y~qYK@9*}RBjn{{*o(iu2mE?HD-#)5@gO^tA9#+W7G zs+8kHv;jK?u90IjuWH69)UtV{x*|v+iaP?q2c5E~Lemd#by$y8PZtW4;BSq4Bg$9$ zna{tuA-!{GP?=&jg0XYihhft>LMmLK8;9{WmcN8-|tSv zTTT~d8f)vSF5C|;*C-)sQ2ce2J8c4SZZl#W9XtJ{}S^aWm>g7|oNur!g@QJL?iSHs| z5)l7+{R_j7+U3T`r>sO9zj=p7G!oe;TE4=teBQcezS4)NUH!mFsUdtfLcs4X-fGK| zOlkMj3_$ah{4{vEPa<6^|3XhB&Ge3843-JpM}XB-H+2kE zS!TW<7Ah**k7HUq+WvK6d6}8{y!cf#*GLRRzQQ3P=uSU2V>@5#-JwZWLm&Ld!Uj*o zjABKK;@LAHS?$%>z$rk(ozU63u(P|z8yqi0vuN6WjAgrX@?N38z@JjiQ;kaw9N2Y< z@!MJLCP;GDPcxN={B+*jNXwb5QWD=G-erf#>FCT@RP5AtOH7_Cvp^a{yAqkF<~RP$ z(kijy9;(&sT|2nH8KiFdOKt%F)@uP;j(`(*_Bvmkf78BBly1UL7|EAcufASqU44U( zrTjZR+Occ$euF=FgQqc@f!Z8u1fI}&ulwP4+_W$ z49{vIp0M6}yR&ci$@epTmfWGS4AGT2E-fcELhcw+Kz3Mm-qD1Em`=jmx03!xcIZ$V zbpFqQLPn0>X!jW@xlZ*LL{U+xJ_rb;~N?=1naRbg{aDag>NlHf0san zkB`6Qce`-c1LtI)30yIh;5>%1e-BtRc_K&jlYNCo=}BS_Uv}#3%e*1SUl-aM`{eF0 zE*@+r>nIqwY6B2iiT>M353)bk2tUKJ(XY~R8{;2Yd1?5!g`Fhtxh9LXc&^7Bh3tfs z^^<=XimD$xAGlcEb`{!A^iOkfQ$MWwr%}JQ+H0z19un{R{LAxz7R?4sQh`kH3uyed z?*q!}+*5qdO~woN)XG6&YDEJMN?CgghyUrK-12*FgOx9 zA$g}i@WR3QGz+}l<~BoxPflg17SVm~R@NX6(o%o2nSAs}O8Ch1Xm0ip_qFT$pTx2u z>lSApwY8}hO$$h-O?41TlU8%)WZ%HB_83Ye$7?B&wDq($R4+UIsSwl3!dta5GozW6 z2tthzK!woX*NfFi+U z-7(zeN?t|ZZYZ@S8@0?=O#~P@=*Th85}Nlx!P*H@RpqV<4J5fB+*0dlg{U1n^lj`+ zviVz~s}2@P!il9Dw3qkSZl`7!wtUKRn3vw&^=J_p4~L@-$0T0!QgY$j3x!*RYK9ecI5fl$*t(2 zpUr*pxL8}cc`lK}*4Y=9xp@23yPNj=T0(=QsJ*y?`?j85J6{-V5<^_67FjLGF0Z&+x>=L>3(^DDJ9WCV**w zz+}A&bA1g})kXf4?-wDw|J+juNaXtLrLPS_x$?Yr4p@Fc!E{}w>ol(bKB01BBjeQ* z{jWPcS4S}rY9?90n<$d=$X0Ii#`@J5mBdUtivdoMn6gh&Fxivzd`WHf;A&3weQMLa z(rS@J4Y5wfr6I@BF_l72rJ|YvW#9MUZ~ye4r!bCc0+Y$=s)N^1x=f&3Kiyx!slz`_ z8~o;(@@HLceST|?+n^uU^hghvT#Bys8_YL|Pzg$h4xFYvBzMM*2lRt?$gys2ex_Q8 zqz)-kk=j4#mT4`GBWzMop!1+G?AeNo@4wc@M39MS(A%l2rW@sFU0 ziU;*;(|$j&es*NSqE9=W6f*PW?Qa^;^(UN)$0l+T90c_1U@ZM|4U83Trn2QtOzozy zzRcoY1Wlf?KeYj_AT=7)LNYw)t;)cb`R>F{JT*g|A<_@nheF0YeNl(BzMlk^=Zl=> z3rr=AuB2ITFMmNdiH+6!b@iGy8Y^b5xk^(3iGkTG*IV6yJYTLlkGPcP%A`-p`323L zTA9M0c^n-$=qn-Q_Wi=ZaB;%x$2SBhOMQ*^)R<=!Teo;eMPlGs^K?_C!2Lk3NYgL( z6@+){O&Iw6y9UZXI`@p1jvb%Mshep8m(Q2fxgi>0(5S#rQ0nzb59p_|o*OAdIfc(IJYO z^^`fJpq4L@vwbLfSEH4L7}_}LCZwWPNm4phfMsdvES~V`-j-b5zqGlCxL^7VIjcsY zeMaOR5=Y|b4MtQNtghMr^kr{KXZe?)HMf!4t?;qkJbrYjpsXW?o};fQuls;>I?i%- zHd{l%dZrnOG_#JcL`WM<0$VNabK4-MOJe`V2+&+e*%WM2lGkm@0;?%{Pg;9e60F+S znLAijDY<_IpLQpbL16StU^TlS!|g<8@K*^xC_Q;B;oMT2T0*zie%nG319d;#FAaLR z@z#We*;$H2s-)m7-%OuFZU_!vb;d;Bl$^kNr+=*^T}irv(XSkCim)eK7Z`0Owm4&+ z7v406>UG#l;U-syhv!q2ZKb2s=P0@2JN7@jN3njvZ1pxBI ziW{kj=`6zn3H8kLg4eGG19i{uyiu5m;+SipM;H`{7Kg72mK=pXk_l}lZW2XdBjjS)lS%cyA zWY53W?7sbhk2vWl+`p{6hFKzoZU=SG)43JSxw#=<0>guq?|szN*L##(BvN}dATSWoLLXQ$K8!Ks zO4oOjPX>3mRWX*AHTNnIIF;_OS1qtmweH*3hOb$7Dz#$t?OyVTAqan$VblP(x9>6b=IguNDh8aJ${p zf8u`>=C3( z0QDe2x^u?o7FD@*ZJ_PQ3xJx_TwW*u`I6bw8()YRoo<&d4gyqM^TUeBNY^H3WEPKoyh59bp&1CbSRK#V_i#Qzx zvReEV`bglfpk%!UIUC|4{|UFTlciwBf8|eKOFn-}!|DfVC<21fh0%IB9UI`weh{|i zUu}P+edP~4V5p68&efi$owLnRMU=-~G?}ZvK~2zTrTyrEP?PW5slFTW1*hk70K@Dq zEdH$_eNt7FPBiU;uPBclHbrMFVtO6_ON&MvSU3$*!in{?B8KhkzxlT*5pFOh;9jgU&sJSLjA%ytTF0?S?ZKa;uGODB}lW$Z+F zG6>KB+hYHxSxRD40=-LGMnYCh4`9H=rfT!Pi^o&3>$BNxi>IvW*m@UcRv4F6r$@a$ zg+2;crZ@KUoy$m^1>6DP8BgbnrJzPka_Ak>h%XVCHNw}>g< zZ{srp3e`fezfapdbq8Ep)B0H19?83!gZ?O0=D1+rr%A+r0;milb-@Y`0PmI&4XzH6 z0h9fhK&x@x(#!p1s|;I(vhYKthwXCiw+a0)Q!m`rH}QP&$wmZh56i4{6`8ijN|7?ddkP#>ntw3?J+d<9SkyxrP$@P*DGY2AAVHt^3H{8Gr%m~)O5^w zKeMv5XKX{j4tIUlulwP7?!r8{z|H4u6(_xNYmN)+i&Mc z!G(6NwnnO?!Y*#)Fx6dgw(gM7sxBFxHcPwmAc`?sztU67%&p>{q27}HxWv9`pO~*6 z^6S3g{`a*eUXw8|=>3NvtQa*YR;fTYfv1f1?J8X?b#o&V*zs|0=M>EP%Ct=HLkfed zhS1W><#hv0bI%Ou;uxv+`If=7%vk+L=R_#`!JZwWDWIoJq4*;Kaw4$21Mjtn`ROH5 zT2}Pah1UK3b*g<^bfR)VX$a58qClCV)ndVFqi?@k2MAl)$Kc+CHGtc4*^w$1l6z{f zed^trzoO*_0>;Fu5ZQyH4M4EXf7W=hKFm*nXFS@k6gF5jUCp(_WMBTOI+t|k{w&u+ zf_c&=HP+;`2F;CbY@gRYjg+9_oN|`0}-g*rj7s3gacz}`bzepb$1pn)k<8>%L zd6=d5l+{BzOY?x_SMCM4KW%8qyv0$YKCt3rS&O8Gx( zogDfzfLfJei93X#TEW_Ie^=M{o}Qn*j=p=;GEjOByI2mCSpl~`rl-%dz}49U=O1Ru z`RmKe%iFV38h~zT>*&O4XUY`Tu)Y^Z7Cd8ImX?<8=149cXhO2Q`ScHU99D}!NfzL8 z2R=#(4-k$#?QBGo3mAmIDA|fM>+*#DscAwYSz=WBZcw{?QQ;_2wzcINeDt%6UCO6I z_{*=;aJ6TElY7xwgaIhC%h^1}Iy%+C$Nuj<;hr?1cEHct+HMQJP6@8rjKe=H(5(<+ zj|t!ooZ8sw-yGKL?8?3Z68qkj9aN&re+7BB?7-F}HZzd0kfj!Tv*jW_Bbxux8%LmI zDyGHV{N6XB#W(X*?p`i=`HPur(xt!yM~Zd$juZjI4a#VFF;~;8fU?@!(v(br!10J0 zGU&QNGk%|k#MN7Q_O7g~ne?PIJ0R=McRynYF?-aVpX>12O{#W4B!%zQ_LXpHpcpIF zF^6ThB(G*?%Yz9%{owMS4iXp_0#^{ZPzHfP1x1&`KI&pjmvF(;8G8Q zr6dlphqH(@yRB&%i#}vBGypKBXf7|jsp&evP-9B+V$vn6+cvzPjWlbNTEh~)0;|sm zO4bC%9JUBomyoIzxh$M~)wRqBteTjXtX0cCg)a5d{J_30XvjMWUN!2QXfZDnG=RPJ z4gJaV2Ol|Q+Nwd~?faV*25hZX3Ruj#Y$bl&EQ(59!%Pv?@DLLl+aBb`E=vUscjnQx~n-00FXiW{t z@+BAxtb)RSTkFG`|1e+Qq(*9L3{-FDbYaFz6lgueUpnJTDLjv8jV(t&1Pz}9-4!G zDN_Kfwtp8dbawXn-o}4-hwW2Re%yY!4c}|x3l{h~-7A1I{yCQNOf{j*Ijz?Sggy7y z3l+2jtVM85x6fDa{>x8-KS%rPb7_^8z17KRnEGmdnd@N`m7Nv6iAULFHEjYyf-6SP90MeNxf018T6^Qi`Z&tV38l%CKocQ`-Me~;ZexWyb-_Xd=4e&zf)hf-n z4r4nJFAIiQ(~GlB#|JrPqCd8WHppl?<3v%0b#1UAEw zd+|Fve27dLZ;fJ|Y;8X+(6wg(m2F~T!Y*$AZn7{|@lZEcm*2-=l${4~MkE=+R+pZ# z?ZWH==W=}n6`K+3EHx+|ou1a@rxRTSKy)B8^IjXhTMM1|4+TywNj=pT*ZpdNiR$v% zA~dAK=9A8MaZ$wqM354v!w*l%i#9$VebGO3 zaO*M_g}!5lTj~3|=EdcGHPjWpCMK5BRaBjP@>sRb;BDsJMa7A`9rel1QH|3|by8~z z#TtrOr%M`d8w3`{ohDsRC@B4#)4jCY$xT_lJxMo$^y`r`5WR3NAcei!=>(4?+mnBgiPu*H}^2!+c z9M(p&LhH+L9ie!Z+RKy)gq>l#(z^>L=jn0ze*J_rbv?60IirBPS|oEQ_q@lGr23Mw zXfK7Q(5=R@89Y~y4^`2z=X#j^y4<&bij}tOvU$Zx^7+My3n}K{_cL0CPc>G=Xujq-6(zr4W*q8x1?!nydDwWm~O%4+duAO+_UMlm_8@z z!}duV);dz!ot>FPJUH#C7~)co_%X8|2w+UW=7EPBadWeGG}+o`OH#{aXXk#RHUrFQ+6+b%BEALwgx!hHAxeNmXV^0)Q@;zAJ-;ZKUtCePY z4I~UqLAV=(pPNImfn8TaDF$q7Y|6l$l%V=M*%WHxoH-J*sWZB9xk{E}%W(CzRHYN% zrvpQ>+`PNKm1&+)lQj-N@IS8qH^=al_u1Ombd*RtnBd0MK|BFoOS}FupSE9pL^Cmi zD=&cxZpg}3SsCye}f%ESSG{;2R970 z0Ge-4LM&jW#0fs4cEubUgVm=a=t0U_mcP33yMdADj=5*--CY!`cf2?f%S)2`qsz~$ zY!N%Vxdrb7kelY>0ee?ju{xWrT6(1a+sX;ow91&}=L4HgP`&yqUJqv2%-pNIzNMq# z$fBX-irQ+ktWWVsL{0{&3H!FeO!4MF-GD3Yx7>d0yfys~odsNgZgJ_K|d7s^`|o7J0ak8w5r9=lR%2Xhvu zmX~2E1lzJdmJq5Ove1aK~CX;Y<^OnJTl@h`0W<|FvhWKB=$Wo3AzV4xz1(Wo>*~Rg5p}3TR(=algsE1h9Bznve5cnjW zy&m##1IoejlF%fSWgYWkTntBis@nGebeDt)%_>7R?#!*nG|Y|6%^cvs;tfwturU=S zJ=DlWO<`ngwzt)=__Hjbrt32|vzI`ii{#$K8rHRIPbYS3&`n{lUYoo{@Wy?AAQZgu zFA?q&a5Ur9b)P(vFEmWZh=a9e1+pBp78P@hOa}v1Xyl#hc>>;8nSwE{LInbUWEF2o zMFxu&eZEX7IlO-O3W%F=E)k6*m^mQzdD1SDf{Y;O((Qf#&rw*cvAF-u`yS=Q&YmJ0 zfI@yB87rTJKluGt7?DK-68lk{9bBQyUl0etR6sO$V-|;jzxP*4oSpH>r%+a5wU;xN zE`M)LnHrhw`@{@z%rPnnXEzGY`l(SN=41iEONHgt1>Vm<$ZKDKiGw|Dg612r8G8!9dS9hQ|r_Y84%2ki`QTom;KU>L;#8 zOWhp4ANplu(=LYXNyC@lHN}P&p@I%xI(*O|v_KAg=yu~C1CWEht7}!~VpjFc4Clba z_5YjmVquSLs96<%3hA{wDv)FjuN}Zi_mXFN33iB7A&|;tR0Z^`6+!xU%>(1X_uX0b zu4mnCQ7;HvRv3;|aDh}FMy)#F=0!V)>|y|JFaVw%{_1gV?}cSZ&`xAitnd2KeXO8} zh{!w*kgK;1ZZkBxhCDKf=gW|7oMaqtB~+1#m;2?b9rkHg@;Q`VuL&}x`QN|tM^SZ# zaWv$SY>DX>a6r&^U|o6|s5zaTodqPN;lJ?5tDTnhJKdL8Vv;GLI~}YtJBQdNei4yX z5ei0fEhso@I!tuqa_=npC1L;9E1z2#NRnJ^@ad*oC0uPk?9Y0rLSD}Jb6)FCd~V0w z)IirY9b{>||7;sTNF?{aG6a7EP@eAQjyd2=9%T-IM%<(To9}d&Sq&7IlYt%=dTNma z{u@DT^>?xY22Q+U)qQB?gG)^2ZuO?v2dsj-zm=)sn z0q9Wh(wmgoc(HBHT>!{`Zi2M~Bs8!AR4}!Pu9SDX0E}>O*>VWjbuKXf58z2!?~(9y zFikW`PnRd`BHz{5!N+{Cvv%TfnrKEYlrFbt$f7v0H*mlf+mDgTotxO&+Cs02w=^yO z*1Svv4!!{=>PBa0D|>{I67#WuMQGbryZt@Q)@HiOebe{-7s#jm8NTz_&)Fj_u=nD$ zh~jtE1#N>5r%#;DE-Kf~a{ZW{KL1Eoou2?@V0+t2t7Yr!rkbx8yf@U<-P7~7620~A zXNeVta;PxVH>=a~A^Pu3)&vLB$XT%Lvc{mtK2{*+bJ(q}N2laVF0`%Ky|(sEXZsP% zLwe;&u-(N>xyN|$9LEFalz@dipD7Jj@n5K5`>h?Sl4;BR=IO`Gj>17;T~*k3dy2nR z&*C(ziu)d_j^aEkPu)=1*SoYk4}xxkK>$V-@3cH@-eKu&)I)*_A_%>F6$NNg!5B;iAq}- zCeF+MblXm!MLnSAj)OdKw@UP6O#U&Y%k5# zzxsaeD%G}Tc832Y(nNx3r*e*;BlRSC)E*M1-y>*)2zU-=tG)A#Z3@=r3JZPPTze1a zIeoDIV0YOyhJ|Gb+FxwKi({xZTB-DmIqH5^{i)Y*)Mm~DHUN@)7{z#JAulU-&q2x%_w zu7nB&0%vn-@!O}e2Xf%)S7(vRo4`5AJPlYtP!;_WJ8Pf?37!aj_HH-T%2bXaE2J literal 0 HcmV?d00001 diff --git a/docs/specs/feature-005-default-permission-mode.md b/docs/specs/feature-005-default-permission-mode.md index 40ece7b1..5d53514b 100644 --- a/docs/specs/feature-005-default-permission-mode.md +++ b/docs/specs/feature-005-default-permission-mode.md @@ -185,6 +185,10 @@ export const BUDDY_DEFAULT_PERMISSION_MODE: BuddyPermissionMode = 'policy_approv 已实现本方案的配置持久化、设置页入口和新草稿默认值注入。配置仍以 `desktop.chat.permissionMode` 表示,并通过既有双向转换得到底层审批策略与执行档位;后台自动化不读取该设置。选择"完全访问"时复用现有确认对话框,保存失败时复用设置页错误提示,选择器由配置值控制。 +设置页效果截图: + +![新任务默认权限设置截图](assets/feature-005-default-permission-mode.png) + 自动化测试覆盖配置默认值、TOML 读写、非法值拒绝及新建草稿使用配置模式/既有草稿不追溯。验收清单暂不勾选:设置页完整交互、应用重启后生效及相关边界仍需手动验收;规范中未列出的组件级 UI 自动化测试也未补充。 --- From 8da9d31b0a7b397f46852dc87b8ce79299e08661 Mon Sep 17 00:00:00 2001 From: shanyuhai123 <864299347@qq.com> Date: Mon, 28 Sep 2026 22:02:06 +0800 Subject: [PATCH 3/3] =?UTF-8?q?fix(buddy):=20=E9=80=82=E9=85=8D=E6=96=B0?= =?UTF-8?q?=E4=BB=BB=E5=8A=A1=E6=9D=83=E9=99=90=E8=AE=BE=E7=BD=AE=E7=BB=93?= =?UTF-8?q?=E6=9E=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/buddy/src/i18n/locales/en-US/settings.ts | 11 +- apps/buddy/src/i18n/locales/zh-CN/settings.ts | 11 +- .../DesktopFullAccessConfirmationDialog.vue | 45 ++-- .../DesktopPermissionModeSelector.vue | 13 +- apps/buddy/src/modules/prompt-input/index.ts | 1 - apps/buddy/src/modules/prompt-input/ui.ts | 1 + .../runtime/DesktopRuntimeSettings.vue | 128 +++++++++--- .../feature-005-default-permission-mode.png | Bin 16650 -> 0 bytes .../feature-005-default-permission-mode.md | 196 ------------------ 9 files changed, 152 insertions(+), 254 deletions(-) delete mode 100644 docs/specs/assets/feature-005-default-permission-mode.png delete mode 100644 docs/specs/feature-005-default-permission-mode.md diff --git a/apps/buddy/src/i18n/locales/en-US/settings.ts b/apps/buddy/src/i18n/locales/en-US/settings.ts index 4f53d464..16d547c0 100644 --- a/apps/buddy/src/i18n/locales/en-US/settings.ts +++ b/apps/buddy/src/i18n/locales/en-US/settings.ts @@ -2,7 +2,14 @@ import type zhCN from '../zh-CN/settings' export default { 'desktop.settings.category.runtime': 'Runtime', - 'desktop.settings.categoryDescription.runtime': 'Manage context and model request behavior.', + 'desktop.settings.categoryDescription.runtime': 'Manage permissions for new tasks, context, and model request behavior.', + 'desktop.settings.runtime.taskPermissions': 'Task permissions', + 'desktop.settings.runtime.defaultPermissionMode': 'New task permissions', + 'desktop.settings.runtime.defaultPermissionModeDescription': 'New tasks start with the selected permissions. Existing tasks, including unsent drafts, keep their current settings.', + 'desktop.settings.runtime.fullAccessDefaultTitle': 'Use full access by default for new tasks?', + 'desktop.settings.runtime.fullAccessDefaultDescription': 'New tasks will start with local access to your files, credentials, and network. The sandbox cannot restrict what arbitrary commands do; some sensitive operations will still ask for confirmation.', + 'desktop.settings.runtime.fullAccessDefaultAcknowledgement': 'I understand that full access will apply to every new task by default', + 'desktop.settings.runtime.fullAccessDefaultConfirm': 'Use as default', 'desktop.settings.runtime.context': 'Context', 'desktop.settings.runtime.cacheWarming': 'Cache warming', 'desktop.settings.runtime.cacheWarmingDescription': 'Maintain prompt caches during active tasks when the model supports it and the expected benefit covers the cost. Additional usage is included in usage reports. Stops when the task ends.', @@ -87,8 +94,6 @@ export default { 'desktop.browser.screenshotSaved': 'Screenshot saved', 'desktop.browser.screenshotCopied': 'Screenshot copied to clipboard', 'desktop.browser.screenshotFailed': 'Could not capture the screenshot. Retry or change the destination in browser settings.', - 'desktop.settings.defaultPermissionMode': 'Default permission for new tasks', - 'desktop.settings.defaultPermissionModeDescription': 'Applies only to new tasks. Existing conversations keep their current mode.', 'desktop.settings.contextPanel': 'Resource panel', 'desktop.settings.contextPanelMode': 'Browsing mode', 'desktop.settings.contextPanelModeDescription': 'Task-linked selects resources when focusing a task. Independent keeps the current selection.', diff --git a/apps/buddy/src/i18n/locales/zh-CN/settings.ts b/apps/buddy/src/i18n/locales/zh-CN/settings.ts index 254c4074..3baeea11 100644 --- a/apps/buddy/src/i18n/locales/zh-CN/settings.ts +++ b/apps/buddy/src/i18n/locales/zh-CN/settings.ts @@ -1,6 +1,13 @@ export default { 'desktop.settings.category.runtime': '运行时', - 'desktop.settings.categoryDescription.runtime': '管理上下文与模型调用策略。', + 'desktop.settings.categoryDescription.runtime': '管理新任务权限、上下文与模型调用策略。', + 'desktop.settings.runtime.taskPermissions': '任务权限', + 'desktop.settings.runtime.defaultPermissionMode': '新任务权限', + 'desktop.settings.runtime.defaultPermissionModeDescription': '新建任务时使用所选权限;已有任务(包括未发送的草稿)保持当前设置。', + 'desktop.settings.runtime.fullAccessDefaultTitle': '将完全访问设为新任务默认权限?', + 'desktop.settings.runtime.fullAccessDefaultDescription': '之后新建的任务将默认以你的本机权限访问文件、凭据和网络。任意命令的实际行为无法由沙盒限制;部分敏感操作仍会请求确认。', + 'desktop.settings.runtime.fullAccessDefaultAcknowledgement': '我已了解完全访问会默认应用于每个新任务', + 'desktop.settings.runtime.fullAccessDefaultConfirm': '设为默认权限', 'desktop.settings.runtime.context': '上下文', 'desktop.settings.runtime.cacheWarming': '缓存预热', 'desktop.settings.runtime.cacheWarmingDescription': '任务执行期间按需维护模型的提示词缓存,仅在支持的模型上且预计有收益时执行。会产生额外用量,计入使用情况;任务结束后停止。', @@ -85,8 +92,6 @@ export default { 'desktop.browser.screenshotSaved': '截图已保存', 'desktop.browser.screenshotCopied': '截图已复制到剪贴板', 'desktop.browser.screenshotFailed': '截图失败,请重试或在浏览器设置中切换保存方式。', - 'desktop.settings.defaultPermissionMode': '新任务默认权限', - 'desktop.settings.defaultPermissionModeDescription': '只影响新建任务,已有会话保持原模式。', 'desktop.settings.contextPanel': '资源面板', 'desktop.settings.contextPanelMode': '浏览模式', 'desktop.settings.contextPanelModeDescription': '任务联动在聚焦任务时选择其资源,独立浏览保留当前选择。', diff --git a/apps/buddy/src/modules/prompt-input/components/DesktopFullAccessConfirmationDialog.vue b/apps/buddy/src/modules/prompt-input/components/DesktopFullAccessConfirmationDialog.vue index a2a78553..2601fe78 100644 --- a/apps/buddy/src/modules/prompt-input/components/DesktopFullAccessConfirmationDialog.vue +++ b/apps/buddy/src/modules/prompt-input/components/DesktopFullAccessConfirmationDialog.vue @@ -1,63 +1,66 @@