From 44fc4192e080af10aee36433e30d0e474786f385 Mon Sep 17 00:00:00 2001 From: creatixchu Date: Fri, 30 Oct 2026 14:11:06 +0800 Subject: [PATCH] refactor(cua): keep native permission guidance in macOS provider --- docs/subsystems/computer-use.i18n.yaml | 4 +- docs/subsystems/computer-use.md | 14 +-- docs/subsystems/computer-use.zh.md | 14 +-- .../experimental/cua-macos/README.i18n.yaml | 4 +- packages/experimental/cua-macos/README.md | 2 + packages/experimental/cua-macos/README.zh.md | 2 + .../CuaNative/NativeProtocol.generated.swift | 2 +- .../Sources/CuaNative/Permissions.swift | 13 ++- .../CuaNative/RequestCoordinator.swift | 2 +- .../cua-macos/native/generate-protocol.ts | 5 +- .../cua-macos/native/protocol.schema.json | 17 ++- .../tests/swift/SystemPermissionsTests.swift | 22 +++- packages/experimental/cua-macos/package.json | 3 +- packages/experimental/cua-macos/src/index.ts | 19 ++- .../cua-macos/src/permission-guide.ts | 110 ++++++++++++++++++ .../cua-macos/tests/permission-guide.spec.ts | 38 ++++++ .../tests/provider-lifecycle.spec.ts | 17 +++ packages/experimental/cua-macos/tsconfig.json | 3 + packages/experimental/cua/README.i18n.yaml | 4 +- packages/experimental/cua/README.md | 4 +- packages/experimental/cua/README.zh.md | 4 +- packages/experimental/cua/src/access-types.ts | 10 +- packages/experimental/cua/src/access.ts | 39 +++---- .../experimental/cua/src/client/controller.ts | 10 +- .../experimental/cua/src/client/locales.ts | 40 ------- packages/experimental/cua/src/client/mount.ts | 8 +- packages/experimental/cua/src/index.ts | 15 ++- packages/experimental/cua/src/protocol.ts | 5 +- packages/experimental/cua/src/remote-types.ts | 2 +- .../experimental/cua/tests/access.spec.ts | 35 ++---- .../cua/tests/agent-scopes.spec.ts | 1 + .../cua/tests/controller.client.spec.ts | 28 +++-- .../cua/tests/mount.client.spec.ts | 4 +- .../extensions/tool-cordis/src/api-catalog.ts | 18 ++- pnpm-lock.yaml | 3 + scripts/gen-cordis-catalog.ts | 1 - 36 files changed, 330 insertions(+), 192 deletions(-) create mode 100644 packages/experimental/cua-macos/src/permission-guide.ts create mode 100644 packages/experimental/cua-macos/tests/permission-guide.spec.ts diff --git a/docs/subsystems/computer-use.i18n.yaml b/docs/subsystems/computer-use.i18n.yaml index 18283728dd..dbf1b7ddc3 100644 --- a/docs/subsystems/computer-use.i18n.yaml +++ b/docs/subsystems/computer-use.i18n.yaml @@ -3,8 +3,8 @@ # After editing either side, bring the other along and re-record with: # pnpm run verify-translation-pairing --write docs/subsystems/computer-use.md /computer-use: - en: f0a1e4ef2656deb9 - zh: b00aad01231cf8ad + en: 38cfa2a698be898f + zh: 60961256fee5ef21 /computer-use/choose-a-provider: en: fa2e0d7b41c0450b zh: af9a799711f901e6 diff --git a/docs/subsystems/computer-use.md b/docs/subsystems/computer-use.md index 46a6b978af..08888a044c 100644 --- a/docs/subsystems/computer-use.md +++ b/docs/subsystems/computer-use.md @@ -14,7 +14,6 @@ The CUA provider owns `ctx.cuaAccess` for system-permission status, explicit sys |---|---| | `CuaApplicationId`, `CuaApplicationGrant` | A platform-provided application identity and its displayed saved decision. | | `CuaSystemPermission` | Accessibility or Screen Recording, selected by the human. | -| `CuaPermissionGuideText` | Client-localized native guide instructions and recovery messages; the helper supplies the application name. | | `CuaPermissionProcess`, `CuaSystemPermissionState` | Native privilege checks, observed helper/parent processes, and the application identified by macOS process responsibility at helper startup. The independent helper uses its own bundle when system attribution is unavailable; application metadata is absent if neither bundle can be identified. | | `CuaApplicationState` | Saved grants, refused applications, and whether persistence is available. Session-only grants remain internal to the provider. System observations and application icons are queried separately so the page can display each result as it arrives. | @@ -104,21 +103,20 @@ Browser controls for provider-owned native permission and application decisions. @Remote async applicationIcons(): Promise> /** - * Open the selected System Settings pane; show the native drag guide only for a missing privilege. - * Guide preparation failures still attempt settings navigation before reporting the failure. + * Request the selected system privilege through the native provider. * @param permission macOS privilege to request. - * @param text Localized guide copy; application identity comes from the native helper. - * @returns Current permission observations; opening the guide does not grant permission. + * @param locale Current Client language for provider-owned permission UI. + * @returns Current permission observations; requesting does not establish consent. */ -@Remote async requestPermission(permission: CuaSystemPermission, text: CuaPermissionGuideText): Promise +@Remote async requestPermission(permission: CuaSystemPermission, locale: string): Promise /** * Open the macOS privacy pane selected by the user. * @param permission Corresponding system privilege. - * @param failureMessage Localized manual navigation instructions if macOS rejects the open request. + * @param locale Current Client language for provider-owned recovery instructions. * @returns Completion after macOS accepted the open request. */ -@Remote async openSettings(permission: CuaSystemPermission, failureMessage: string): Promise +@Remote async openSettings(permission: CuaSystemPermission, locale: string): Promise /** * Remove saved and session access and join affected operations. diff --git a/docs/subsystems/computer-use.zh.md b/docs/subsystems/computer-use.zh.md index 9ea73b79dd..28fdc9b794 100644 --- a/docs/subsystems/computer-use.zh.md +++ b/docs/subsystems/computer-use.zh.md @@ -14,7 +14,6 @@ CUA provider 通过 `ctx.cuaAccess` 提供系统权限状态、用户主动申 |---|---| | `CuaApplicationId`、`CuaApplicationGrant` | 平台提供的应用身份,以及显示给用户的已保存决定。 | | `CuaSystemPermission` | 用户选择申请的辅助功能或屏幕录制权限。 | -| `CuaPermissionGuideText` | Client 提供的本地化原生引导说明和恢复提示;应用名由 helper 提供。 | | `CuaPermissionProcess`、`CuaSystemPermissionState` | 观测到的 helper、父进程和原生权限状态,以及 helper 启动时由 macOS 责任进程记录识别出的应用。系统归属信息不可用时,独立 helper 使用自身 bundle;两者都无法识别时才省略应用信息。 | | `CuaApplicationState` | 持久授权、拒绝记录,以及能否持久保存。会话授权只保留在 provider 内部。系统权限和应用图标分别查询,页面在各项结果返回后分别显示。 | @@ -104,21 +103,20 @@ Browser controls for provider-owned native permission and application decisions. @Remote async applicationIcons(): Promise> /** - * Open the selected System Settings pane; show the native drag guide only for a missing privilege. - * Guide preparation failures still attempt settings navigation before reporting the failure. + * Request the selected system privilege through the native provider. * @param permission macOS privilege to request. - * @param text Localized guide copy; application identity comes from the native helper. - * @returns Current permission observations; opening the guide does not grant permission. + * @param locale Current Client language for provider-owned permission UI. + * @returns Current permission observations; requesting does not establish consent. */ -@Remote async requestPermission(permission: CuaSystemPermission, text: CuaPermissionGuideText): Promise +@Remote async requestPermission(permission: CuaSystemPermission, locale: string): Promise /** * Open the macOS privacy pane selected by the user. * @param permission Corresponding system privilege. - * @param failureMessage Localized manual navigation instructions if macOS rejects the open request. + * @param locale Current Client language for provider-owned recovery instructions. * @returns Completion after macOS accepted the open request. */ -@Remote async openSettings(permission: CuaSystemPermission, failureMessage: string): Promise +@Remote async openSettings(permission: CuaSystemPermission, locale: string): Promise /** * Remove saved and session access and join affected operations. diff --git a/packages/experimental/cua-macos/README.i18n.yaml b/packages/experimental/cua-macos/README.i18n.yaml index 7cc3efe49b..270aaa49bf 100644 --- a/packages/experimental/cua-macos/README.i18n.yaml +++ b/packages/experimental/cua-macos/README.i18n.yaml @@ -12,8 +12,8 @@ en: 4549f02fc53854a5 zh: 6f74f1130b6623a8 /deepseek-ai-dsh-experimental-cua-macos/table-of-contents: - en: ffab8081f3341b92 - zh: 29867b195b3c7e51 + en: 1c91307fdaebba7a + zh: 910fba0aa793f47f /deepseek-ai-dsh-experimental-cua-macos/use-this-package: en: ead96859cdf97de5 zh: acaf62c479d5d6e8 diff --git a/packages/experimental/cua-macos/README.md b/packages/experimental/cua-macos/README.md index a886c8e3f9..bce90bd7bf 100644 --- a/packages/experimental/cua-macos/README.md +++ b/packages/experimental/cua-macos/README.md @@ -26,6 +26,8 @@ The helper runs through LaunchServices as the independent application `DSH Compu Permission requests open the selected privacy pane and a native floating guide. The guide selects the largest settings window once and follows that window by ID. It uses the normal window level, ordered immediately above the selected window so authentication dialogs remain above it. Placement reads window bounds without requesting Accessibility access. The guide stays hidden until the settings window is identified; a failed settings-open request displays manual navigation instructions independently. A material alpha mask gives the panel and its shadow rounded corners. The latest request replaces the shared guide, and only its owning connection can close it during cleanup. Navigation is attempted even when the application is unavailable, and the page reports that failure. Already-granted permissions open settings without a guide; explicit settings navigation dismisses previous guidance. A rejected settings-open request reports `SETTINGS_UNAVAILABLE`; missing or replaced application targets report `APPLICATION_UNAVAILABLE`. System Settings owns drag acceptance and any relaunch requirement. +The provider resolves guide and recovery copy from the current Client language, with English for unsupported languages. Drag the guide’s application row into the permission list and enable its switch; enable the existing entry when already listed. The guide hides during dragging, closes when a drop is accepted, and returns after a canceled or rejected drag. The back button closes it and returns to the previous application. Right-click the row, or click it for keyboard focus and press Return or Space, to reveal the bundle in Finder. While open, the guide checks permission and window position every `permissionPollIntervalMs` (default 1000 ms), and closes after a confirmed grant, dismissal, disappearance of the settings window, or its owning connection’s cleanup. Dragging does not establish consent. Follow any macOS quit-and-reopen prompt; the shared helper is not automatically terminated. + ----- diff --git a/packages/experimental/cua-macos/README.zh.md b/packages/experimental/cua-macos/README.zh.md index 2e4cf06d23..494e3d94d1 100644 --- a/packages/experimental/cua-macos/README.zh.md +++ b/packages/experimental/cua-macos/README.zh.md @@ -26,6 +26,8 @@ helper 通过 LaunchServices 作为独立应用 `DSH Computer Use.app` 运行。 权限请求打开对应隐私设置页和原生悬浮引导框。引导框首次选择面积最大的设置窗口,随后按窗口 ID 跟随。它使用普通窗口层级,紧邻所选窗口上方排列,使认证弹窗保持在其上方。定位读取窗口边界,不申请辅助功能权限。引导框等到识别出设置窗口后才显示;打开设置失败时独立显示手动导航说明。材质的透明度遮罩为面板和阴影提供圆角。最新请求替换共享引导,连接清理只关闭自身创建的引导。应用不可用时仍尝试打开设置,并在页面报告失败原因。权限已授予时只打开设置;显式进入设置会关闭原有引导。系统拒绝打开设置时报告 `SETTINGS_UNAVAILABLE`;应用目标缺失或被替换时报告 `APPLICATION_UNAVAILABLE`。系统设置决定是否接收拖入的应用,以及是否要求重新启动。 +provider 按当前界面语言生成引导和恢复文案,不支持的语言使用英语。将引导框内的应用拖入权限列表并开启开关;已有此应用时直接开启现有条目。拖动时隐藏引导框,放置被接收后关闭,拖动取消或被拒绝后恢复。返回按钮关闭引导框并返回之前的应用。右键点击应用行,或点击它使引导框获得键盘焦点后按 Return、空格,可在 Finder 中选中同一应用。原生引导打开期间,每隔 `permissionPollIntervalMs` 检查权限和窗口位置,默认 1000 毫秒;在确认授权、用户关闭、系统设置窗口消失或所属连接清理时关闭。拖动应用不代表授权完成。若 macOS 提示退出并重新打开应用,请按提示操作;系统不会自动终止共享 helper。 + ----- diff --git a/packages/experimental/cua-macos/native/Sources/CuaNative/NativeProtocol.generated.swift b/packages/experimental/cua-macos/native/Sources/CuaNative/NativeProtocol.generated.swift index 295cc9b53c..940fed6c19 100644 --- a/packages/experimental/cua-macos/native/Sources/CuaNative/NativeProtocol.generated.swift +++ b/packages/experimental/cua-macos/native/Sources/CuaNative/NativeProtocol.generated.swift @@ -14,7 +14,7 @@ enum NativeProtocol { "launchApp": ["appHandle"], "listApps": [], "listWindows": ["appHandle"], - "openPermissionSettings": ["permission"], + "openPermissionSettings": ["permission", "settingsMessage"], "paste": ["appHandle", "format", "maxClipboardBytes", "maxDepth", "maxElements", "pasteReadTimeoutMs", "text"], "performSecondaryAction": ["action", "appHandle", "elementIndex", "snapshotId"], "permissionStatus": [], diff --git a/packages/experimental/cua-macos/native/Sources/CuaNative/Permissions.swift b/packages/experimental/cua-macos/native/Sources/CuaNative/Permissions.swift index 3f2516c1e7..53e0259904 100644 --- a/packages/experimental/cua-macos/native/Sources/CuaNative/Permissions.swift +++ b/packages/experimental/cua-macos/native/Sources/CuaNative/Permissions.swift @@ -41,7 +41,10 @@ import Foundation "helper": Self.process(getpid()), "parent": Self.process(getppid()), ] - if let launchApplication { result["launchApplication"] = launchApplication } + if let launchApplication, case .object(var application) = launchApplication { + application.removeValue(forKey: "bundleId") + result["launchApplication"] = .object(application) + } return .object(result) } @@ -78,9 +81,13 @@ import Foundation func closeGuide(owner: UUID) { guide.close(owner: owner) } - func openSettings(_ permission: String) throws(NativeFailure) -> JSONValue { + func openSettings(_ permission: String, failureMessage: String) throws(NativeFailure) -> JSONValue { guide.dismiss() - return try openPane(permission) + do { return try openPane(permission) } + catch { + guard error.code == "SETTINGS_UNAVAILABLE" else { throw error } + throw NativeFailure(code: error.code, message: failureMessage) + } } private func openPane(_ permission: String) throws(NativeFailure) -> JSONValue { diff --git a/packages/experimental/cua-macos/native/Sources/CuaNative/RequestCoordinator.swift b/packages/experimental/cua-macos/native/Sources/CuaNative/RequestCoordinator.swift index e6ba227e4c..67a1ce6fa1 100644 --- a/packages/experimental/cua-macos/native/Sources/CuaNative/RequestCoordinator.swift +++ b/packages/experimental/cua-macos/native/Sources/CuaNative/RequestCoordinator.swift @@ -184,7 +184,7 @@ import Darwin result = try permissions.request(params.string("permission"), owner: permissionOwner, text: text, interval: Double(params.integer("pollIntervalMs", in: 100...Int(Int32.max))) / 1000) } else if request.method == "openPermissionSettings" { - result = try permissions.openSettings(params.string("permission")) + result = try permissions.openSettings(params.string("permission"), failureMessage: params.string("settingsMessage")) } else if request.method == "findApps" { result = .array(try ApplicationDiscovery.applications(matching: params.string("query"), exact: false, limit: params.integer("maxAppMatches", in: 1...1000), cancellation: cancellation).map { $0.json(catalog: catalog) }) diff --git a/packages/experimental/cua-macos/native/generate-protocol.ts b/packages/experimental/cua-macos/native/generate-protocol.ts index 0dc674d4c3..1374c6fdfa 100644 --- a/packages/experimental/cua-macos/native/generate-protocol.ts +++ b/packages/experimental/cua-macos/native/generate-protocol.ts @@ -4,6 +4,8 @@ import { z } from 'zod' import type { JSONSchema } from 'zod/v4/core' import { NativeOnlyOperations, NativeResourceParameters, Request } from '@deepseek-ai/dsh-cua/protocol' +import { PermissionGuideParameters, PermissionSettingsParameters } from '../src/permission-guide.ts' + type Schema = JSONSchema.JSONSchema function object(schema: Schema | boolean | undefined): Schema { @@ -17,7 +19,8 @@ function variants(schema: Schema): Schema[] { return schema.anyOf ?? schema.oneO * @returns Native JSON Schema and Swift field/enum tables, including private continuations. */ export function renderNativeProtocol(): { json: string; swift: string } { - const resources = new Map(Object.entries(NativeResourceParameters)) + const resources = new Map([...Object.entries(NativeResourceParameters), + ['requestPermission', PermissionGuideParameters], ['openPermissionSettings', PermissionSettingsParameters]]) const modelMethods = Request.options.map(schema => schema.shape.method.value).sort() const schemas = [...Request.options, ...NativeOnlyOperations].map(schema => z.toJSONSchema(schema)) const fields = new Map>() diff --git a/packages/experimental/cua-macos/native/protocol.schema.json b/packages/experimental/cua-macos/native/protocol.schema.json index 8199f1978e..6053f13e40 100644 --- a/packages/experimental/cua-macos/native/protocol.schema.json +++ b/packages/experimental/cua-macos/native/protocol.schema.json @@ -1115,6 +1115,10 @@ "screenRecording" ] }, + "settingsMessage": { + "type": "string", + "minLength": 1 + }, "instruction": { "type": "string", "minLength": 1 @@ -1131,10 +1135,6 @@ "type": "string", "minLength": 1 }, - "settingsMessage": { - "type": "string", - "minLength": 1 - }, "pollIntervalMs": { "type": "integer", "minimum": 100, @@ -1143,11 +1143,11 @@ }, "required": [ "permission", + "settingsMessage", "instruction", "backLabel", "finderLabel", "unavailableMessage", - "settingsMessage", "pollIntervalMs" ], "additionalProperties": false @@ -1179,10 +1179,15 @@ "accessibility", "screenRecording" ] + }, + "settingsMessage": { + "type": "string", + "minLength": 1 } }, "required": [ - "permission" + "permission", + "settingsMessage" ], "additionalProperties": false } diff --git a/packages/experimental/cua-macos/native/tests/swift/SystemPermissionsTests.swift b/packages/experimental/cua-macos/native/tests/swift/SystemPermissionsTests.swift index c5d0b5efc9..fc35a354c0 100644 --- a/packages/experimental/cua-macos/native/tests/swift/SystemPermissionsTests.swift +++ b/packages/experimental/cua-macos/native/tests/swift/SystemPermissionsTests.swift @@ -43,6 +43,8 @@ func permissionRequestOpensTheNativeGuideAndMatchingSettings(permission: String) #expect(!guide.isOpen) let state = try permissions.request(permission, owner: owner, text: copy, interval: 1) #expect(state[permission] == .bool(false)) + #expect(state["launchApplication"]["name"] == .string("Helper.app")) + #expect(state["launchApplication"]["bundleId"] == .null) #expect(guide.isOpen) #expect(guide.application?.url.path == "/generation/Helper.app") #expect(guide.text?.instruction == copy.instruction) @@ -123,7 +125,7 @@ func unavailablePermissionApplicationsStillOpenSettings(identified: Bool) throws guide.isOpen = true let permissions = SystemPermissions(application: application, actions: SystemPermissionActions(accessibility: { false }, screenRecording: { true }, open: { _ in true }), guide: guide) - #expect(try permissions.openSettings("screenRecording")["opened"] == .bool(true)) + #expect(try permissions.openSettings("screenRecording", failureMessage: copy.settingsMessage)["opened"] == .bool(true)) #expect(!guide.isOpen) } @@ -223,3 +225,21 @@ func permissionGuideMaterialKeepsTransparentCornersWhenResized(size: NSSize) thr #expect(PermissionGuidePlacement.needsOrdering(guide: 20, above: 10, frontToBack: [30, 10])) #expect(!PermissionGuidePlacement.needsOrdering(guide: 20, above: 10, frontToBack: [30, 20, 10])) } + +@MainActor @Test(arguments: ["accessibility", "invalid"]) +func explicitSettingsFailuresPreserveValidationAndLocalizeNavigation(permission: String) throws { + let permissions = SystemPermissions(application: application, + actions: SystemPermissionActions(accessibility: { false }, screenRecording: { false }, open: { _ in false }), + guide: GuideFixture()) + do { + _ = try permissions.openSettings(permission, failureMessage: "请手动打开系统设置中的辅助功能") + Issue.record("Failed settings navigation was accepted") + } catch let failure as NativeFailure { + if permission == "accessibility" { + #expect(failure.code == "SETTINGS_UNAVAILABLE") + #expect(failure.message == "请手动打开系统设置中的辅助功能") + } else { + #expect(failure.code == "INVALID_ARGUMENT") + } + } +} diff --git a/packages/experimental/cua-macos/package.json b/packages/experimental/cua-macos/package.json index 13a7c2fd2b..92e43139fb 100644 --- a/packages/experimental/cua-macos/package.json +++ b/packages/experimental/cua-macos/package.json @@ -55,7 +55,8 @@ "dependencies": { "zod": "^4.4.3", "@deepseek-ai/dsh-atomic-write": "workspace:*", - "@deepseek-ai/dsh-cua": "workspace:*" + "@deepseek-ai/dsh-cua": "workspace:*", + "@deepseek-ai/schemastery": "workspace:~" }, "devDependencies": { "@deepseek-ai/dsh-working-directory": "workspace:*", diff --git a/packages/experimental/cua-macos/src/index.ts b/packages/experimental/cua-macos/src/index.ts index b351d8be8f..a68301c777 100644 --- a/packages/experimental/cua-macos/src/index.ts +++ b/packages/experimental/cua-macos/src/index.ts @@ -1,6 +1,8 @@ /** macOS native CUA provider using the shared application and window API. */ import { isAbsolute, resolve } from 'node:path' import { fileURLToPath } from 'node:url' +import Schema from '@deepseek-ai/schemastery' +import { createPermissionParameters } from './permission-guide.ts' import type { Context } from '@deepseek-ai/cordis' import { CuaConfig, installCua } from '@deepseek-ai/dsh-cua' import { nativeHostArchitecture, prepareNativeExecutable } from './native-build.ts' @@ -13,9 +15,14 @@ export const name = 'experimental-cua-macos' /** Services required by the shared CUA executor and the native launcher. */ export const inject = ['computerUse', 'tools', 'systemPrompt', 'subprocess', 'agents'] /** Native preparation, operation bounds, and profile-local application grants. */ -export type Config = CuaConfig -/** Validate the shared configuration before preparing the macOS helper. */ -export const Config: typeof CuaConfig = CuaConfig +export interface Config extends CuaConfig { + /** Interval in milliseconds between permission and position checks while the native guide is open. */ + permissionPollIntervalMs: number +} +/** Validate shared resource bounds and macOS guide configuration before preparing the helper. */ +export const Config = Schema.intersect([CuaConfig, Schema.object({ + permissionPollIntervalMs: Schema.number().step(1).min(100).max(2 ** 31 - 1).default(1000), +})]) satisfies Schema, Config> const description = 'Inspect macOS permissions, select an application and window, read AX state, capture screenshots or dispatch input. Use appHandle from getApp; verify actions by reading fresh state.' @@ -26,9 +33,11 @@ const description = 'Inspect macOS permissions, select an application and window * @returns Completion after native preparation and tool registration. */ export async function apply(ctx: Context, config: Config): Promise { + const backend = { createChannel: createSharedNativeChannel, guidance: GUIDANCE, description, + permissionParameters: createPermissionParameters(config.permissionPollIntervalMs) } if (config.helperPath !== undefined) { if (!isAbsolute(config.helperPath)) throw new Error('dsh-cua helperPath must be absolute') - installCua(ctx, config, { helperPath: config.helperPath, createChannel: createSharedNativeChannel, guidance: GUIDANCE, description }) + installCua(ctx, config, { helperPath: config.helperPath, ...backend }) return } const architecture = nativeHostArchitecture(process.platform, process.arch) @@ -45,5 +54,5 @@ export async function apply(ctx: Context, config: Config): Promise { }, 'dsh-cua.native-preparation') const helperPath = await prepared controller.signal.throwIfAborted() - installCua(ctx, config, { helperPath, createChannel: createSharedNativeChannel, guidance: GUIDANCE, description }) + installCua(ctx, config, { helperPath, ...backend }) } diff --git a/packages/experimental/cua-macos/src/permission-guide.ts b/packages/experimental/cua-macos/src/permission-guide.ts new file mode 100644 index 0000000000..1074104351 --- /dev/null +++ b/packages/experimental/cua-macos/src/permission-guide.ts @@ -0,0 +1,110 @@ +/** macOS permission guide copy and private native request parameters. */ +import { z } from 'zod' +import type { CuaBackend } from '@deepseek-ai/dsh-cua' + +/** Native-only fields for opening the selected privacy pane. */ +export const PermissionSettingsParameters = z.object({ settingsMessage: z.string().min(1) }) +/** Native-only guide fields, validated again by the Swift request parser. */ +export const PermissionGuideParameters = PermissionSettingsParameters.extend({ + instruction: z.string().min(1), backLabel: z.string().min(1), finderLabel: z.string().min(1), + unavailableMessage: z.string().min(1), pollIntervalMs: z.number().int().min(100).max(2 ** 31 - 1), +}) + +const en = { + instruction: 'Drag {application} to the list above to allow {permission}', + backLabel: 'Back', + finderLabel: 'Show in Finder', + unavailableMessage: 'Application unavailable. Enable Computer Use again and retry.', + settingsMessage: 'Open System Settings > Privacy & Security > {permission} manually.', + accessibility: 'Accessibility', + screenRecording: 'Screen Recording', +} + +const zh = { + instruction: '将 {application} 拖入上方列表以允许{permission}', + backLabel: '返回', + finderLabel: '在 Finder 中显示', + unavailableMessage: '找不到此应用。请重新启用 Computer Use 后重试', + settingsMessage: '请手动打开系统设置 > 隐私与安全性 > {permission}', + accessibility: '辅助功能', + screenRecording: '屏幕录制', +} satisfies typeof en + +const ja = { + instruction: '{permission}を許可するには、{application}を上の一覧にドラッグしてください', + backLabel: '戻る', + finderLabel: 'Finder で表示', + unavailableMessage: 'アプリが見つかりません。Computer Use を再度有効にしてください。', + settingsMessage: 'システム設定 > プライバシーとセキュリティ > {permission} を開いてください。', + accessibility: 'アクセシビリティ', + screenRecording: '画面収録', +} satisfies typeof en + +const es = { + instruction: 'Arrastra {application} a la lista de arriba para permitir {permission}', + backLabel: 'Volver', + finderLabel: 'Mostrar en Finder', + unavailableMessage: 'Aplicación no disponible. Vuelve a activar Computer Use e inténtalo de nuevo.', + settingsMessage: 'Abre Configuración del sistema > Privacidad y seguridad > {permission}.', + accessibility: 'Accesibilidad', + screenRecording: 'Grabación de pantalla', +} satisfies typeof en + +const fr = { + instruction: 'Glissez {application} dans la liste ci-dessus pour autoriser {permission}', + backLabel: 'Retour', + finderLabel: 'Afficher dans le Finder', + unavailableMessage: 'Application introuvable. Réactivez Computer Use et réessayez.', + settingsMessage: 'Ouvrez Réglages Système > Confidentialité et sécurité > {permission}.', + accessibility: 'Accessibilité', + screenRecording: "Enregistrement d'écran", +} satisfies typeof en + +const de = { + instruction: 'Ziehe {application} in die Liste oben, um {permission} zu erlauben', + backLabel: 'Zurück', + finderLabel: 'Im Finder anzeigen', + unavailableMessage: 'Anwendung nicht verfügbar. Aktiviere Computer Use erneut.', + settingsMessage: 'Öffne Systemeinstellungen > Datenschutz & Sicherheit > {permission}.', + accessibility: 'Barrierefreiheit', + screenRecording: 'Bildschirmaufzeichnung', +} satisfies typeof en + +const ru = { + instruction: 'Перетащите {application} в список выше, чтобы разрешить {permission}', + backLabel: 'Назад', + finderLabel: 'Показать в Finder', + unavailableMessage: 'Приложение недоступно. Снова включите Computer Use и повторите попытку.', + settingsMessage: 'Откройте Системные настройки > Конфиденциальность и безопасность > {permission}.', + accessibility: 'Доступность', + screenRecording: 'Запись экрана', +} satisfies typeof en + +const pt = { + instruction: 'Arraste {application} para a lista acima para permitir {permission}', + backLabel: 'Voltar', + finderLabel: 'Mostrar no Finder', + unavailableMessage: 'Aplicativo indisponível. Ative Computer Use novamente e tente de novo.', + settingsMessage: 'Abra Ajustes do Sistema > Privacidade e Segurança > {permission}.', + accessibility: 'Acessibilidade', + screenRecording: 'Gravação de tela', +} satisfies typeof en + +const dictionaries = new Map(Object.entries({ en, zh, ja, es, fr, de, ru, pt })) + +/** + * Resolve native permission copy from the Client language; unsupported languages use English. + * @param pollIntervalMs Interval between native guide permission and position checks. + * @returns Provider-owned parameters for the selected permission action. + */ +export function createPermissionParameters(pollIntervalMs: number): CuaBackend['permissionParameters'] { + return (method, permission, locale) => { + const language = locale.toLowerCase().replace(/-.*/, '') + const text = dictionaries.get(language) ?? en + const settingsMessage = text.settingsMessage.replace('{permission}', text[permission]) + if (method === 'openPermissionSettings') return { permission, settingsMessage } + return { permission, settingsMessage, pollIntervalMs, + instruction: text.instruction.replace('{permission}', text[permission]), + backLabel: text.backLabel, finderLabel: text.finderLabel, unavailableMessage: text.unavailableMessage } + } +} diff --git a/packages/experimental/cua-macos/tests/permission-guide.spec.ts b/packages/experimental/cua-macos/tests/permission-guide.spec.ts new file mode 100644 index 0000000000..47a4c8a6f1 --- /dev/null +++ b/packages/experimental/cua-macos/tests/permission-guide.spec.ts @@ -0,0 +1,38 @@ +/** Native permission preparation belongs to the macOS provider. */ +import { expect, it } from 'vitest' +import { CuaConfig } from '@deepseek-ai/dsh-cua' +import { Config } from '../src/index.ts' +import { createPermissionParameters, PermissionGuideParameters, PermissionSettingsParameters } from '../src/permission-guide.ts' + +it('keeps the guide interval in macOS configuration and rejects invalid bounds', () => { + expect(CuaConfig({})).not.toHaveProperty('permissionPollIntervalMs') + expect(Config({}).permissionPollIntervalMs).toBe(1000) + expect(Config({ permissionPollIntervalMs: 250 }).permissionPollIntervalMs).toBe(250) + for (const permissionPollIntervalMs of [99, 100.5, 2 ** 31]) expect(() => Config({ permissionPollIntervalMs })).toThrow() +}) + +it.each(['en', 'zh', 'ja', 'es', 'fr', 'de', 'ru', 'pt'])('prepares both permissions in %s without sending settings-only callers guide fields', (locale) => { + const parameters = createPermissionParameters(250) + for (const permission of ['accessibility', 'screenRecording'] as const) { + const guide = parameters('requestPermission', permission, locale) + expect(PermissionGuideParameters.parse(guide)).toMatchObject({ pollIntervalMs: 250 }) + expect(guide.instruction).toContain('{application}') + expect(guide.instruction).not.toContain('{permission}') + const settings = parameters('openPermissionSettings', permission, locale) + expect(settings).toEqual({ permission, settingsMessage: guide.settingsMessage }) + expect(PermissionSettingsParameters.parse(settings)).toEqual({ settingsMessage: guide.settingsMessage }) + } +}) + +it('matches language variants and falls back to English for unsupported languages', () => { + const parameters = createPermissionParameters(2000) + expect(parameters('requestPermission', 'accessibility', 'ZH-CN')).toEqual({ + permission: 'accessibility', pollIntervalMs: 2000, + instruction: '将 {application} 拖入上方列表以允许辅助功能', backLabel: '返回', finderLabel: '在 Finder 中显示', + unavailableMessage: '找不到此应用。请重新启用 Computer Use 后重试', + settingsMessage: '请手动打开系统设置 > 隐私与安全性 > 辅助功能', + }) + expect(parameters('requestPermission', 'screenRecording', 'it')).toEqual(parameters('requestPermission', 'screenRecording', 'en')) + expect(() => PermissionGuideParameters.parse({ ...parameters('requestPermission', 'accessibility', 'en'), pollIntervalMs: 0 })).toThrow() + expect(() => PermissionSettingsParameters.parse({ settingsMessage: '' })).toThrow() +}) diff --git a/packages/experimental/cua-macos/tests/provider-lifecycle.spec.ts b/packages/experimental/cua-macos/tests/provider-lifecycle.spec.ts index 183f8b5a30..d0d5856c39 100644 --- a/packages/experimental/cua-macos/tests/provider-lifecycle.spec.ts +++ b/packages/experimental/cua-macos/tests/provider-lifecycle.spec.ts @@ -754,3 +754,20 @@ it.each([true, false])('uses configured grants without requesting a human answer expect(native.instances).toHaveLength(1) expect(native.request).toHaveBeenCalledTimes(2) }) + +it('routes permission actions through the macOS provider with the Client language and configured interval', async () => { + const f = await setup() + native.request.mockResolvedValueOnce({ accessibility: false, screenRecording: false, helper: { pid: 9 }, parent: { pid: 8 } }) + await f.ctx.cuaAccess.requestPermission('accessibility', 'zh') + expect(native.request).toHaveBeenLastCalledWith('requestPermission', { + permission: 'accessibility', pollIntervalMs: 1000, + instruction: '将 {application} 拖入上方列表以允许辅助功能', backLabel: '返回', finderLabel: '在 Finder 中显示', + unavailableMessage: '找不到此应用。请重新启用 Computer Use 后重试', + settingsMessage: '请手动打开系统设置 > 隐私与安全性 > 辅助功能', + }, expect.any(AbortSignal)) + native.request.mockResolvedValueOnce({ opened: true }) + await f.ctx.cuaAccess.openSettings('screenRecording', 'en') + expect(native.request).toHaveBeenLastCalledWith('openPermissionSettings', { + permission: 'screenRecording', settingsMessage: 'Open System Settings > Privacy & Security > Screen Recording manually.', + }, expect.any(AbortSignal)) +}) diff --git a/packages/experimental/cua-macos/tsconfig.json b/packages/experimental/cua-macos/tsconfig.json index 986873b01b..9e61f86dc3 100644 --- a/packages/experimental/cua-macos/tsconfig.json +++ b/packages/experimental/cua-macos/tsconfig.json @@ -19,6 +19,9 @@ }, { "path": "../../util/atomic-write" + }, + { + "path": "../../../vendor/schemastery" } ] } diff --git a/packages/experimental/cua/README.i18n.yaml b/packages/experimental/cua/README.i18n.yaml index d43520ca62..eac008c304 100644 --- a/packages/experimental/cua/README.i18n.yaml +++ b/packages/experimental/cua/README.i18n.yaml @@ -18,8 +18,8 @@ en: ed736a45140a6d56 zh: 8dc89373a0a88fcc /deepseek-ai-dsh-cua/use-this-package/permission-controls: - en: 7c64adec7de815cf - zh: f9b0c7fbeb5eb7c3 + en: 2da530aaad735728 + zh: 9fe12c2febbfcb58 /deepseek-ai-dsh-cua/understand-the-implementation: en: 90e8fcb6ebe271d1 zh: f0c90834bec8c73a diff --git a/packages/experimental/cua/README.md b/packages/experimental/cua/README.md index 87f66f3416..5a4b9567ec 100644 --- a/packages/experimental/cua/README.md +++ b/packages/experimental/cua/README.md @@ -45,9 +45,9 @@ Actions return `dispatched:true, verified:false`; read fresh state to verify the On the Computer Use detail page, permission cards, component rows, and source facts use l3 borders, with 32px gaps between sections. Application badges retain their l1 borders. -The [Computer Use bundle](../cua-bundle/README.md) mounts this page with its native provider. **Authorize computer use** groups the new-session default, PTC execution mode, Accessibility, and Screen Recording inside one border. Each permission row shows the responsible application’s name and icon. **Request permission** opens the selected System Settings pane; the native guide appears over its lower margin once the window is identified. Drag the application row from this guide into the permission list, then enable its switch; enable the existing entry when already listed. The guide hides during dragging, closes when a drop is accepted, and returns after a canceled or rejected drag. The back button closes the guide and returns to the previous application. Right-click the application row, or click it to give the guide keyboard focus and press Return or Space, to reveal the same bundle in Finder. An outlined **Allowed** button opens settings for revocation; unknown status uses **Unknown**. Application grants remain in a separate group. +The [Computer Use bundle](../cua-bundle/README.md) mounts this page with its native provider. **Authorize computer use** groups the new-session default, PTC execution mode, Accessibility, and Screen Recording inside one border. Each permission row shows the responsible application’s name and icon. **Request permission** delegates to the provider with the selected permission and current Client language; the [macOS provider](../cua-macos/README.md) owns the native drag guide. An outlined **Allowed** button opens settings for revocation; unknown status uses **Unknown**. Application grants remain in a separate group. -The page refreshes on entry, window focus, and completed actions. Query failures retain the last observation as unknown and offer **Retry**. While open, the native guide checks permission and window position every `permissionPollIntervalMs` (default 1000 ms), and closes after a confirmed grant, dismissal, disappearance of the settings window, or its owning connection’s cleanup. Opening settings or dragging the application does not establish consent. Settings-open failures retain manual navigation instructions. Follow any macOS quit-and-reopen prompt; the shared helper is not automatically terminated. +The page refreshes on entry, window focus, and completed actions. Query failures retain the last observation as unknown and offer **Retry**. Opening settings or requesting permission does not establish consent. The provider owns native guidance, its configuration, and localized recovery instructions. System-permission operations affect the computer running DSH. When accessing DSH remotely, complete authorization on that computer. diff --git a/packages/experimental/cua/README.zh.md b/packages/experimental/cua/README.zh.md index 23d8e0c766..b937627025 100644 --- a/packages/experimental/cua/README.zh.md +++ b/packages/experimental/cua/README.zh.md @@ -45,9 +45,9 @@ kind: "package-reference" Computer Use 详情页的权限卡片、组件列表和来源信息使用 l3 边框,区块间距为 32px。应用名称徽标保留 l1 边框。 -[Computer Use 组合](../cua-bundle/README.zh.md)将本页与原生 provider 一起挂载。“给 Computer Use 授权”把新会话默认模式、PTC 执行方式、辅助功能和屏幕录制放在同一个边框内。每条权限要求显示责任应用的名称和图标。“申请权限”打开对应系统设置页;识别到设置窗口后,原生引导框才在其底部显示。将引导框内的应用拖入权限列表,再开启开关;已有此应用时直接开启现有条目。拖动时隐藏引导框,放置被接收后关闭,拖动取消或被拒绝后恢复。返回按钮关闭引导框并返回之前的应用。右键点击应用行,或点击它使引导框获得键盘焦点后按 Return、空格,可在 Finder 中选中同一应用。“已允许”描边按钮打开设置以撤销权限;未知状态使用“未确认”。应用访问授权保持独立分组。 +[Computer Use 组合](../cua-bundle/README.zh.md)将本页与原生 provider 一起挂载。“给 Computer Use 授权”把新会话默认模式、PTC 执行方式、辅助功能和屏幕录制放在同一个边框内。每条权限要求显示责任应用的名称和图标。“申请权限”将所选权限和当前界面语言交给 provider;原生拖动引导由 [macOS provider](../cua-macos/README.zh.md)负责。“已允许”描边按钮打开设置以撤销权限;未知状态使用“未确认”。应用访问授权保持独立分组。 -进入页面、返回窗口和操作结束后会刷新状态。查询失败保留上次观测但标为未确认,并提供“重试”。原生引导打开期间,每隔 `permissionPollIntervalMs` 检查权限和窗口位置,默认 1000 毫秒;在确认授权、用户关闭、系统设置窗口消失或所属连接清理时关闭。打开设置或拖动应用不代表授权完成。设置打开失败时保留手动导航说明。若 macOS 提示退出并重新打开应用,请按提示操作;系统不会自动终止共享 helper。 +进入页面、返回窗口和操作结束后会刷新状态。查询失败保留上次观测但标为未确认,并提供“重试”。打开设置或申请权限不代表授权完成。原生引导、配置和本地化恢复提示由 provider 负责。 系统权限操作作用于运行 DSH 的电脑。远程访问时,请在该电脑上完成授权。 diff --git a/packages/experimental/cua/src/access-types.ts b/packages/experimental/cua/src/access-types.ts index 626b0b20dc..eabda165aa 100644 --- a/packages/experimental/cua/src/access-types.ts +++ b/packages/experimental/cua/src/access-types.ts @@ -4,14 +4,6 @@ export type { CuaApplicationGrant, CuaApplicationId } from './application-types. /** macOS privileges used by the native provider. */ export type CuaSystemPermission = 'accessibility' | 'screenRecording' -/** Localized native guide copy; the helper replaces the literal {application} with its own bundle name. */ -export interface CuaPermissionGuideText { - instruction: string - backLabel: string - finderLabel: string - unavailableMessage: string - settingsMessage: string -} /** Observed process; neither the helper nor its parent is assumed to be the TCC owner. */ export interface CuaPermissionProcess { pid: number @@ -25,7 +17,7 @@ export interface CuaSystemPermissionState { helper: CuaPermissionProcess parent: CuaPermissionProcess /** Application macOS reports as responsible for the helper, captured at startup; absent when it cannot be identified. */ - launchApplication?: { name: string; path: string; icon?: string | undefined; bundleId?: string | undefined } | undefined + launchApplication?: { name: string; path: string; icon?: string | undefined } | undefined } /** Saved grants and refused applications, independent of native permission checks and icons. */ export interface CuaApplicationState { diff --git a/packages/experimental/cua/src/access.ts b/packages/experimental/cua/src/access.ts index 1259ea7904..8c092eab64 100644 --- a/packages/experimental/cua/src/access.ts +++ b/packages/experimental/cua/src/access.ts @@ -8,22 +8,21 @@ import { ApplicationAccess, type AccessOptions } from './application-access.ts' import type { JsonValue } from '@deepseek-ai/dsh-util-values' import { z } from 'zod' import { ApplicationIcon } from './protocol.ts' -import { NativeClient, NativeError, type NativeClientConfig, type NativeChannelFactory } from './native-client.ts' -import type { CuaApplicationId, CuaApplicationState, CuaPermissionGuideText, CuaSystemPermission, CuaSystemPermissionState } from './access-types.ts' +import { NativeClient, type NativeClientConfig, type NativeChannelFactory } from './native-client.ts' +import type { CuaApplicationId, CuaApplicationState, CuaSystemPermission, CuaSystemPermissionState } from './access-types.ts' export { ApplicationAuthorizationResult } from './application-access.ts' export type * from './access-types.ts' interface ControlOptions extends AccessOptions { native: NativeClientConfig createChannel: NativeChannelFactory - permissionPollIntervalMs: number + permissionParameters: (method: 'requestPermission' | 'openPermissionSettings', permission: CuaSystemPermission, locale: string) => Record } type Control = (method: string, params: Record) => Promise const permissionProcess = z.object({ pid: z.number().int().positive(), path: z.string().optional(), name: z.string().optional() }) const systemPermissions = z.object({ accessibility: z.boolean(), screenRecording: z.boolean(), helper: permissionProcess, parent: permissionProcess, - launchApplication: z.object({ name: z.string().min(1), path: z.string().min(1), - icon: ApplicationIcon.optional(), bundleId: z.string().optional() }).optional(), + launchApplication: z.object({ name: z.string().min(1), path: z.string().min(1), icon: ApplicationIcon.optional() }).optional(), }) declare module '@deepseek-ai/cordis' { @@ -39,10 +38,10 @@ export class CuaAccess extends TypertRemoteService { * @param ctx Provider context. * @param policy Application decision owner. * @param control Serialized native permission dispatch. - * @param permissionPollIntervalMs Interval between native guide permission and position checks. + * @param permissionParameters Provider-owned native parameters for the selected action and Client language. */ constructor(ctx: Context, private readonly policy: ApplicationAccess, - private readonly control: Control, private readonly permissionPollIntervalMs: number) { + private readonly control: Control, private readonly permissionParameters: ControlOptions['permissionParameters']) { super(ctx, 'cuaAccess') } @@ -99,33 +98,27 @@ export class CuaAccess extends TypertRemoteService { } /** - * Open the selected System Settings pane; show the native drag guide only for a missing privilege. - * Guide preparation failures still attempt settings navigation before reporting the failure. + * Request the selected system privilege through the native provider. * @param permission macOS privilege to request. - * @param text Localized guide copy; application identity comes from the native helper. - * @returns Current permission observations; opening the guide does not grant permission. + * @param locale Current Client language for provider-owned permission UI. + * @returns Current permission observations; requesting does not establish consent. */ @Remote - async requestPermission(permission: CuaSystemPermission, text: CuaPermissionGuideText): Promise { + async requestPermission(permission: CuaSystemPermission, locale: string): Promise { return systemPermissions.parse(await this.control('requestPermission', - { permission, ...text, pollIntervalMs: this.permissionPollIntervalMs })) + this.permissionParameters('requestPermission', permission, locale))) } /** * Open the macOS privacy pane selected by the user. * @param permission Corresponding system privilege. - * @param failureMessage Localized manual navigation instructions if macOS rejects the open request. + * @param locale Current Client language for provider-owned recovery instructions. * @returns Completion after macOS accepted the open request. */ @Remote - async openSettings(permission: CuaSystemPermission, failureMessage: string): Promise { - try { z.object({ opened: z.literal(true) }).parse(await this.control('openPermissionSettings', { permission })) } - catch (error) { - if (error instanceof NativeError && error.code === 'SETTINGS_UNAVAILABLE') { - throw new NativeError(error.code, failureMessage, { cause: error }) - } - throw error - } + async openSettings(permission: CuaSystemPermission, locale: string): Promise { + z.object({ opened: z.literal(true) }).parse(await this.control('openPermissionSettings', + this.permissionParameters('openPermissionSettings', permission, locale))) } /** @@ -174,6 +167,6 @@ export function installCuaAccess(ctx: Context, options: ControlOptions): Pick CuaPermissionGuideText) {} + private readonly locale: () => string) {} /** * Bind the permission projection to its user actions. * @returns Reactive state and controls shared by the page, prompt, and toast host. @@ -65,10 +65,10 @@ export class PermissionController { hooks: { cuaPermissions: this.state }, refresh: () => { this.enqueue() }, request: (permission) => { - this.enqueue(() => this.remote.requestPermission(permission, this.guideText(permission)), 'requested') + this.enqueue(() => this.remote.requestPermission(permission, this.locale()), 'requested') }, openSettings: (permission) => { - this.enqueue(() => this.remote.openSettings(permission, this.guideText(permission).settingsMessage), 'opened') + this.enqueue(() => this.remote.openSettings(permission, this.locale()), 'opened') }, revoke: (id) => { this.enqueue(() => this.remote.revoke(id), 'revoked') }, retryApproval: (id) => { this.enqueue(() => this.remote.retryApproval(id), 'retryAllowed') }, diff --git a/packages/experimental/cua/src/client/locales.ts b/packages/experimental/cua/src/client/locales.ts index 2c74ea71f3..0d9082bee1 100644 --- a/packages/experimental/cua/src/client/locales.ts +++ b/packages/experimental/cua/src/client/locales.ts @@ -2,11 +2,6 @@ export const NS = 'cuaPermissions' /** English permission-page dictionary. */ export const en = { - nativeInstruction: 'Drag {application} to the list above to allow {permission}', - nativeBack: 'Back', - nativeFinder: 'Show in Finder', - nativeUnavailable: 'Application unavailable. Enable Computer Use again and retry.', - nativeSettingsUnavailable: 'Open System Settings > Privacy & Security > {permission} manually.', title: 'Authorize computer use', applicationsTitle: 'Manage applications computer use can access', setupTitle: 'Set up computer use before use', @@ -45,11 +40,6 @@ export const en = { export type CuaPermissionLocaleKey = keyof typeof en /** Chinese permission-page dictionary. */ export const zh: Record = { - nativeInstruction: '将 {application} 拖入上方列表以允许{permission}', - nativeBack: '返回', - nativeFinder: '在 Finder 中显示', - nativeUnavailable: '找不到此应用。请重新启用 Computer Use 后重试', - nativeSettingsUnavailable: '请手动打开系统设置 > 隐私与安全性 > {permission}', title: '给 Computer Use 授权', applicationsTitle: '管理 Computer Use 可访问的应用', setupTitle: '使用 Computer Use 前需要设置', setupBody: 'Computer Use 插件已开启。使用前请切换到 PTC 模式,并授予辅助功能和屏幕录制权限。请前往插件详情页完成设置。', setupQueryFailed: 'Computer Use 插件已开启,但暂时无法检测系统权限。请前往插件详情页查看原因并重试。', @@ -85,11 +75,6 @@ export const zh: Record = { /** Japanese copy, checked against the English keys. */ export const ja = { - nativeInstruction: '{permission}を許可するには、{application}を上の一覧にドラッグしてください', - nativeBack: '戻る', - nativeFinder: 'Finder で表示', - nativeUnavailable: 'アプリが見つかりません。Computer Use を再度有効にしてください。', - nativeSettingsUnavailable: 'システム設定 > プライバシーとセキュリティ > {permission} を開いてください。', 'title': 'コンピュータ操作を承認', 'applicationsTitle': 'Computer Useがアクセスできるアプリを管理', 'setupTitle': '使用前にComputer Useを設定', @@ -143,11 +128,6 @@ export const ja = { /** Spanish dictionary, checked against the English keys. */ export const es = { - nativeInstruction: 'Arrastra {application} a la lista de arriba para permitir {permission}', - nativeBack: 'Volver', - nativeFinder: 'Mostrar en Finder', - nativeUnavailable: 'Aplicación no disponible. Vuelve a activar Computer Use e inténtalo de nuevo.', - nativeSettingsUnavailable: 'Abre Configuración del sistema > Privacidad y seguridad > {permission}.', title: 'Autorizar el uso de la computadora', applicationsTitle: 'Administrar aplicaciones a las que puede acceder el uso de la computadora', setupTitle: 'Configurar el uso de la computadora antes de usarla', @@ -185,11 +165,6 @@ export const es = { /** French dictionary, checked against the English keys. */ export const fr = { - nativeInstruction: 'Glissez {application} dans la liste ci-dessus pour autoriser {permission}', - nativeBack: 'Retour', - nativeFinder: 'Afficher dans le Finder', - nativeUnavailable: 'Application introuvable. Réactivez Computer Use et réessayez.', - nativeSettingsUnavailable: 'Ouvrez Réglages Système > Confidentialité et sécurité > {permission}.', title: "Autoriser l'utilisation de l'ordinateur", applicationsTitle: "Gérer les applications auxquelles l'ordinateur peut accéder", setupTitle: "Configurer l'utilisation de l'ordinateur avant utilisation", @@ -227,11 +202,6 @@ export const fr = { /** German dictionary, checked against the English keys. */ export const de = { - nativeInstruction: 'Ziehe {application} in die Liste oben, um {permission} zu erlauben', - nativeBack: 'Zurück', - nativeFinder: 'Im Finder anzeigen', - nativeUnavailable: 'Anwendung nicht verfügbar. Aktiviere Computer Use erneut.', - nativeSettingsUnavailable: 'Öffne Systemeinstellungen > Datenschutz & Sicherheit > {permission}.', title: 'Autorisieren Sie die Computernutzung', applicationsTitle: 'Verwalten Sie Anwendungen, auf die der Computer zugreifen kann', setupTitle: 'Richten Sie die Computernutzung vor der Nutzung ein', @@ -269,11 +239,6 @@ export const de = { /** Russian dictionary, checked against the English keys. */ export const ru = { - nativeInstruction: 'Перетащите {application} в список выше, чтобы разрешить {permission}', - nativeBack: 'Назад', - nativeFinder: 'Показать в Finder', - nativeUnavailable: 'Приложение недоступно. Снова включите Computer Use и повторите попытку.', - nativeSettingsUnavailable: 'Откройте Системные настройки > Конфиденциальность и безопасность > {permission}.', title: 'Разрешить использование компьютера', applicationsTitle: 'Управление приложениями, к которым может иметь доступ компьютер', setupTitle: 'Настройте использование компьютера перед использованием', @@ -311,11 +276,6 @@ export const ru = { /** Portuguese dictionary, checked against the English keys. */ export const pt = { - nativeInstruction: 'Arraste {application} para a lista acima para permitir {permission}', - nativeBack: 'Voltar', - nativeFinder: 'Mostrar no Finder', - nativeUnavailable: 'Aplicativo indisponível. Ative Computer Use novamente e tente de novo.', - nativeSettingsUnavailable: 'Abra Ajustes do Sistema > Privacidade e Segurança > {permission}.', title: 'Autorizar o uso do computador', applicationsTitle: 'Gerenciar aplicativos que o computador pode acessar', setupTitle: 'Configure o uso do computador antes de usar', diff --git a/packages/experimental/cua/src/client/mount.ts b/packages/experimental/cua/src/client/mount.ts index 64124605b6..1f51360843 100644 --- a/packages/experimental/cua/src/client/mount.ts +++ b/packages/experimental/cua/src/client/mount.ts @@ -34,13 +34,7 @@ export async function mountCuaPermissions(ctx: Context, contribution: TypertRemo const transport = ctx.plugin(async (owner) => { await owner.remote.$mount(contribution) }) const ui = ctx.inject(['remote.cuaAccess', 'slots', 'locale'], (ctx) => { ctx.effect(() => ctx.locale.register(NS, { en, zh, ja, es, fr, de, ru, pt })) - const t = ctx.locale.bind(NS) - const controller = new PermissionController(ctx.remote.cuaAccess, permission => ({ - instruction: t('nativeInstruction', { application: '{application}', permission: t(permission) }), - backLabel: t('nativeBack'), finderLabel: t('nativeFinder'), - unavailableMessage: t('nativeUnavailable'), - settingsMessage: t('nativeSettingsUnavailable', { permission: t(permission) }), - })) + const controller = new PermissionController(ctx.remote.cuaAccess, () => ctx.locale.getSnapshot().active) const face = controller.face() ctx.slots.inject('conversation.composer', () => ctx.slots.register({ name: 'conversation.composer', priority: -1, locale: NS, diff --git a/packages/experimental/cua/src/index.ts b/packages/experimental/cua/src/index.ts index 3bd2f45d90..c776941a7b 100644 --- a/packages/experimental/cua/src/index.ts +++ b/packages/experimental/cua/src/index.ts @@ -15,7 +15,7 @@ import { z } from 'zod' import { NativeClient, NativeError, type NativeClientConfig, type NativeChannelFactory } from './native-client.ts' import { CuaPermissionStore } from './permission-store.ts' import { ApplicationAuthorizationResult, installCuaAccess } from './access.ts' -import type { CuaApplicationGrant, CuaApplicationId } from './access-types.ts' +import type { CuaApplicationGrant, CuaApplicationId, CuaSystemPermission } from './access-types.ts' import type { CuaAppHandle, CuaOperation } from './types.ts' import { ApplicationIcon, AppResult, NativeScreenshotResult, Request, ScreenshotResult, StateResult, WindowsResult } from './protocol.ts' import { CuaCoordinates } from './coordinates.ts' @@ -71,8 +71,6 @@ export interface CuaConfig { textEventIntervalMs: number /** Native connection cleanup, socket-write and launcher termination budget in milliseconds. */ graceMs: number - /** Interval in milliseconds between permission and position checks while the native guide is open. */ - permissionPollIntervalMs: number /** Maximum nodes admitted by a native accessibility snapshot. */ maxElements: number /** Maximum depth traversed by a native accessibility snapshot. */ @@ -102,7 +100,6 @@ export const CuaConfig = Schema.object({ maxResponseBytes: Schema.number().step(1).min(1024).default(32 * 1024 * 1024), textEventIntervalMs: Schema.number().min(0).max(1000).default(0.25), graceMs: Schema.number().step(1).min(1).max(2 ** 31 - 1).default(3000), - permissionPollIntervalMs: Schema.number().step(1).min(100).max(2 ** 31 - 1).default(1000), maxElements: Schema.number().step(1).min(1).max(10000).default(2000), maxDepth: Schema.number().step(1).min(1).max(100).default(25), }) satisfies Schema, CuaConfig> @@ -151,6 +148,14 @@ export interface CuaBackend { createChannel: NativeChannelFactory guidance: string description: string + /** + * Build native permission parameters for the selected action and Client language. + * @param method Native permission action. + * @param permission System privilege selected by the user. + * @param locale Current Client language. + * @returns Parameters accepted by the provider's native implementation. + */ + permissionParameters: (method: 'requestPermission' | 'openPermissionSettings', permission: CuaSystemPermission, locale: string) => Record } /** @@ -170,7 +175,7 @@ export function installCua(ctx: Context, config: CuaConfig, backend: CuaBackend) } const access = installCuaAccess(ctx, { native, createChannel: backend.createChannel, - permissionPollIntervalMs: config.permissionPollIntervalMs, + permissionParameters: backend.permissionParameters, store: new CuaPermissionStore(ctx, config.allowedApps.map(grant => ({ ...grant, id: grant.id as CuaApplicationId })), config.timeoutMs), }) const clients = new Map() diff --git a/packages/experimental/cua/src/protocol.ts b/packages/experimental/cua/src/protocol.ts index 7a53b1c81b..2918014fac 100644 --- a/packages/experimental/cua/src/protocol.ts +++ b/packages/experimental/cua/src/protocol.ts @@ -85,10 +85,7 @@ export const NativeResourceParameters = { export const NativeOnlyOperations = [ operation('launchApp', app), operation('permissionStatus', {}), operation('applicationIcons', { paths: z.array(z.string().min(1)) }), - operation('requestPermission', { permission: z.enum(['accessibility', 'screenRecording']), - instruction: z.string().min(1), backLabel: z.string().min(1), finderLabel: z.string().min(1), - unavailableMessage: z.string().min(1), settingsMessage: z.string().min(1), - pollIntervalMs: z.number().int().min(100).max(2 ** 31 - 1) }), + operation('requestPermission', { permission: z.enum(['accessibility', 'screenRecording']) }), operation('openPermissionSettings', { permission: z.enum(['accessibility', 'screenRecording']) }), ] as const diff --git a/packages/experimental/cua/src/remote-types.ts b/packages/experimental/cua/src/remote-types.ts index 417fdbfa27..6c16dde03d 100644 --- a/packages/experimental/cua/src/remote-types.ts +++ b/packages/experimental/cua/src/remote-types.ts @@ -1,3 +1,3 @@ /** Named Remote boundary types required by Typert's generated Client declarations. */ export type { CuaApplicationId } from './application-types.ts' -export type { CuaApplicationState, CuaSystemPermission, CuaSystemPermissionState, CuaPermissionGuideText } from './access-types.ts' +export type { CuaApplicationState, CuaSystemPermission, CuaSystemPermissionState } from './access-types.ts' diff --git a/packages/experimental/cua/tests/access.spec.ts b/packages/experimental/cua/tests/access.spec.ts index 1cfc7910b0..c72387d69e 100644 --- a/packages/experimental/cua/tests/access.spec.ts +++ b/packages/experimental/cua/tests/access.spec.ts @@ -11,14 +11,6 @@ import type { CuaAppHandle } from '../src/types.ts' import { NativeClient, NativeError } from '../src/native-client.ts' import { permissionFixture } from './permission-fixture.ts' -const guideText = { - instruction: 'Drag {application} to the list above to allow Accessibility', - backLabel: 'Back', - finderLabel: 'Show in Finder', - unavailableMessage: 'Application unavailable', - settingsMessage: 'Open System Settings manually', -} - const native = vi.hoisted(() => ({ instances: [] as Array<{ disposed: boolean request: ReturnType @@ -58,7 +50,7 @@ async function setup(choice = 'Allow for this session', options: { writable?: bo ctx.on('user-questions/request', answer) const permissions = permissionFixture(ctx, options.initial, options.writable) const access = installCuaAccess(ctx, { native: nativeConfig, createChannel: unusedConnector, store: permissions.store, - permissionPollIntervalMs: 1000 }) + permissionParameters: (method, permission, locale) => ({ method, permission, locale }) }) const client = new NativeClient(ctx, nativeConfig, unusedConnector) return { ctx, owner, agent: owner.agent, answer, access, client, ...permissions } } @@ -169,12 +161,12 @@ it('checks native permissions without converting an OS request into a grant', as const before = (await f.ctx.cuaAccess.applications()) expect(before).toEqual({ allowedApps: [], blockedApps: [], persistentAvailable: true }) expect(await f.ctx.cuaAccess.systemStatus()).toMatchObject({ accessibility: false, screenRecording: false }) - expect(await f.ctx.cuaAccess.requestPermission('accessibility', guideText)).toMatchObject({ accessibility: false }) - await f.ctx.cuaAccess.openSettings('screenRecording', guideText.settingsMessage) + expect(await f.ctx.cuaAccess.requestPermission('accessibility', 'en')).toMatchObject({ accessibility: false }) + await f.ctx.cuaAccess.openSettings('screenRecording', 'en') const transport = native.instances[1]! expect(transport.request).toHaveBeenCalledWith('requestPermission', - { permission: 'accessibility', ...guideText, pollIntervalMs: 1000 }, expect.any(AbortSignal)) - expect(transport.request).toHaveBeenCalledWith('openPermissionSettings', { permission: 'screenRecording' }, expect.any(AbortSignal)) + { method: 'requestPermission', permission: 'accessibility', locale: 'en' }, expect.any(AbortSignal)) + expect(transport.request).toHaveBeenCalledWith('openPermissionSettings', { method: 'openPermissionSettings', permission: 'screenRecording', locale: 'en' }, expect.any(AbortSignal)) transport.request.mockRejectedValueOnce(new Error('transport ended')) await expect(f.ctx.cuaAccess.systemStatus()).rejects.toThrow('transport ended') transport.disposed = true @@ -472,22 +464,11 @@ it('does not reactivate old handles when stored permission was revoked and later expect(f.answer).toHaveBeenCalledOnce() }) -it('localizes settings-open refusal and retains the native cause', async () => { - const f = await setup() - await f.ctx.cuaAccess.systemStatus() - const failure = new NativeError('SETTINGS_UNAVAILABLE', 'Native settings-open failure') - native.instances.at(-1)!.request.mockRejectedValueOnce(failure) - const promise = f.ctx.cuaAccess.openSettings('accessibility', '请手动打开系统设置中的辅助功能') - await expect(promise).rejects.toMatchObject({ - code: 'SETTINGS_UNAVAILABLE', message: 'SETTINGS_UNAVAILABLE: 请手动打开系统设置中的辅助功能', cause: failure, - }) -}) - -it.each([new Error('transport ended'), new NativeError('NATIVE_EXIT', 'helper exited')])( - 'keeps unrelated settings failures intact: %s', async (failure) => { +it.each([new NativeError('SETTINGS_UNAVAILABLE', '请手动打开系统设置'), new Error('transport ended'), new NativeError('NATIVE_EXIT', 'helper exited')])( + 'preserves provider and transport settings failures: %s', async (failure) => { const f = await setup() await f.ctx.cuaAccess.systemStatus() native.instances.at(-1)!.request.mockRejectedValueOnce(failure) - await expect(f.ctx.cuaAccess.openSettings('accessibility', guideText.settingsMessage)).rejects.toBe(failure) + await expect(f.ctx.cuaAccess.openSettings('accessibility', 'en')).rejects.toBe(failure) }, ) diff --git a/packages/experimental/cua/tests/agent-scopes.spec.ts b/packages/experimental/cua/tests/agent-scopes.spec.ts index a4ba6a4af3..b19ed845aa 100644 --- a/packages/experimental/cua/tests/agent-scopes.spec.ts +++ b/packages/experimental/cua/tests/agent-scopes.spec.ts @@ -18,6 +18,7 @@ const provider = { installCua(ctx, config, { helperPath: resolve('unused-cua-helper'), createChannel() { throw new Error('Scope tests must not start a native helper') }, + permissionParameters: (_method, permission, locale) => ({ permission, locale }), description: 'Fixture computer interaction', guidance: 'Fixture CUA guidance', }) }, diff --git a/packages/experimental/cua/tests/controller.client.spec.ts b/packages/experimental/cua/tests/controller.client.spec.ts index c1c9a8ca97..06cdeb698d 100644 --- a/packages/experimental/cua/tests/controller.client.spec.ts +++ b/packages/experimental/cua/tests/controller.client.spec.ts @@ -7,14 +7,6 @@ import { expect, it, onTestFinished, vi } from 'vitest' import type { CuaApplicationId, CuaApplicationState, CuaSystemPermissionState } from '../src/access-types.ts' import { PermissionController } from '../src/client/controller.ts' -const guideText = { - instruction: '将 {application} 拖入上方列表以允许辅助功能', - backLabel: '返回', - finderLabel: '在 Finder 中显示', - unavailableMessage: '找不到此应用', - settingsMessage: '请手动打开系统设置', -} - const value: CuaSystemPermissionState = { accessibility: true, screenRecording: false, helper: { pid: 9 }, parent: { pid: 8 } } const applications: CuaApplicationState = { @@ -32,9 +24,10 @@ function fixture() { retryApproval: vi.fn(async () => ({ ok: true, value: undefined })), enablePersistentPtc: vi.fn(async () => ({ ok: true, value: undefined })), } - const controller = new PermissionController(actions, () => guideText) + let locale = 'zh' + const controller = new PermissionController(actions, () => locale) onTestFinished(() => controller.dispose()) - return { actions, controller, face: controller.face() } + return { actions, controller, face: controller.face(), setLocale: (value: string) => { locale = value } } } it('refreshes after a request and then waits for an explicit refresh', async () => { @@ -59,7 +52,7 @@ it('refreshes after a user request without treating the request itself as a gran const f = fixture() f.face.request('screenRecording') await vi.waitFor(() => { expect(f.controller.state.getSnapshot().busy).toBe(false) }) - expect(f.actions.requestPermission).toHaveBeenCalledWith('screenRecording', guideText) + expect(f.actions.requestPermission).toHaveBeenCalledWith('screenRecording', 'zh') expect(f.controller.state.getSnapshot().system.value?.screenRecording).toBe(false) expect(f.controller.state.getSnapshot().toast?.kind).toBe('requested') f.face.dismissToast() @@ -82,7 +75,7 @@ it.each([new Error('write refused'), 'write refused'])('reports settings and rev const f = fixture() f.face.openSettings('accessibility') await vi.waitFor(() => { expect(f.controller.state.getSnapshot().toast?.kind).toBe('opened') }) - expect(f.actions.openSettings).toHaveBeenCalledWith('accessibility', guideText.settingsMessage) + expect(f.actions.openSettings).toHaveBeenCalledWith('accessibility', 'zh') f.face.revoke('a'.repeat(64) as CuaApplicationId) await vi.waitFor(() => { expect(f.controller.state.getSnapshot().toast?.kind).toBe('revoked') }) f.face.retryApproval('a'.repeat(64) as CuaApplicationId) @@ -393,3 +386,14 @@ it('preserves settled failures and values while all four observations are paused } await f.controller.dispose() }) + +it('reads the current Client language for each permission action', async () => { + const f = fixture() + f.face.request('accessibility') + await vi.waitFor(() => { expect(f.controller.state.getSnapshot().busy).toBe(false) }) + expect(f.actions.requestPermission).toHaveBeenLastCalledWith('accessibility', 'zh') + f.setLocale('en') + f.face.openSettings('screenRecording') + await vi.waitFor(() => { expect(f.controller.state.getSnapshot().busy).toBe(false) }) + expect(f.actions.openSettings).toHaveBeenLastCalledWith('screenRecording', 'en') +}) diff --git a/packages/experimental/cua/tests/mount.client.spec.ts b/packages/experimental/cua/tests/mount.client.spec.ts index d41924ea8b..79668bf90e 100644 --- a/packages/experimental/cua/tests/mount.client.spec.ts +++ b/packages/experimental/cua/tests/mount.client.spec.ts @@ -94,8 +94,8 @@ it('withdraws the permission Remote and all controls with the optional plugin', await vi.waitFor(() => { expect(actions.hooks.cuaPermissions.getSnapshot().toast?.kind).toBe('requested') }) actions.openSettings('screenRecording') await vi.waitFor(() => { expect(actions.hooks.cuaPermissions.getSnapshot().toast?.kind).toBe('opened') }) - expect(f.openSettings).toHaveBeenCalledWith('screenRecording', - 'Open System Settings > Privacy & Security > Screen Recording manually.') + expect(f.requestPermission).toHaveBeenCalledWith('accessibility', 'en') + expect(f.openSettings).toHaveBeenCalledWith('screenRecording', 'en') actions.revoke('a'.repeat(64) as import('../src/access-types.ts').CuaApplicationId) await vi.waitFor(() => { expect(actions.hooks.cuaPermissions.getSnapshot().toast?.kind).toBe('revoked') }) actions.retryApproval('a'.repeat(64) as import('../src/access-types.ts').CuaApplicationId) diff --git a/packages/extensions/tool-cordis/src/api-catalog.ts b/packages/extensions/tool-cordis/src/api-catalog.ts index e15422dc9d..af5303753d 100644 --- a/packages/extensions/tool-cordis/src/api-catalog.ts +++ b/packages/extensions/tool-cordis/src/api-catalog.ts @@ -990,15 +990,15 @@ export const SERVICE_API: readonly ServiceApiEntry[] = [ returns: 'Optional native PNG icons keyed by executable path; applications without paths are skipped.', }, { - signature: '@Remote async requestPermission(permission: CuaSystemPermission, text: CuaPermissionGuideText): Promise', - description: 'Open the selected System Settings pane; show the native drag guide only for a missing privilege. Guide preparation failures still attempt settings navigation before reporting the failure.', - parameters: [{ name: 'permission', description: 'macOS privilege to request.' }, { name: 'text', description: 'Localized guide copy; application identity comes from the native helper.' }], - returns: 'Current permission observations; opening the guide does not grant permission.', + signature: '@Remote async requestPermission(permission: CuaSystemPermission, locale: string): Promise', + description: 'Request the selected system privilege through the native provider.', + parameters: [{ name: 'permission', description: 'macOS privilege to request.' }, { name: 'locale', description: 'Current Client language for provider-owned permission UI.' }], + returns: 'Current permission observations; requesting does not establish consent.', }, { - signature: '@Remote async openSettings(permission: CuaSystemPermission, failureMessage: string): Promise', + signature: '@Remote async openSettings(permission: CuaSystemPermission, locale: string): Promise', description: 'Open the macOS privacy pane selected by the user.', - parameters: [{ name: 'permission', description: 'Corresponding system privilege.' }, { name: 'failureMessage', description: 'Localized manual navigation instructions if macOS rejects the open request.' }], + parameters: [{ name: 'permission', description: 'Corresponding system privilege.' }, { name: 'locale', description: 'Current Client language for provider-owned recovery instructions.' }], returns: 'Completion after macOS accepted the open request.', }, { @@ -6098,10 +6098,6 @@ export const TYPE_API: readonly TypeApiEntry[] = [ name: 'CuaApplicationState', declaration: 'export interface CuaApplicationState {\n allowedApps: readonly CuaApplicationGrant[];\n blockedApps: readonly CuaApplicationGrant[];\n persistentAvailable: boolean;\n}', }, - { - name: 'CuaPermissionGuideText', - declaration: 'export interface CuaPermissionGuideText {\n instruction: string;\n backLabel: string;\n finderLabel: string;\n unavailableMessage: string;\n settingsMessage: string;\n}', - }, { name: 'CuaPermissionProcess', declaration: 'export interface CuaPermissionProcess {\n pid: number;\n path?: string | undefined;\n name?: string | undefined;\n}', @@ -6112,7 +6108,7 @@ export const TYPE_API: readonly TypeApiEntry[] = [ }, { name: 'CuaSystemPermissionState', - declaration: 'export interface CuaSystemPermissionState {\n accessibility: boolean;\n screenRecording: boolean;\n helper: CuaPermissionProcess;\n parent: CuaPermissionProcess;\n launchApplication?: {\n name: string;\n path: string;\n icon?: string | undefined;\n bundleId?: string | undefined;\n } | undefined;\n}', + declaration: 'export interface CuaSystemPermissionState {\n accessibility: boolean;\n screenRecording: boolean;\n helper: CuaPermissionProcess;\n parent: CuaPermissionProcess;\n launchApplication?: {\n name: string;\n path: string;\n icon?: string | undefined;\n } | undefined;\n}', }, { name: 'DailyInput', diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 443827158f..c0e7a914e3 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -9864,6 +9864,9 @@ importers: '@deepseek-ai/dsh-cua': specifier: workspace:* version: link:../cua + '@deepseek-ai/schemastery': + specifier: link:../../../vendor/schemastery + version: link:../../../vendor/schemastery zod: specifier: ^4.4.3 version: 4.4.3 diff --git a/scripts/gen-cordis-catalog.ts b/scripts/gen-cordis-catalog.ts index f0e707b961..866a6ba263 100644 --- a/scripts/gen-cordis-catalog.ts +++ b/scripts/gen-cordis-catalog.ts @@ -352,7 +352,6 @@ export const LINK_MAP: Readonly> = { CuaPermissionProcess: 'computer-use.md', CuaApplicationState: 'computer-use.md', CuaSystemPermission: 'computer-use.md', - CuaPermissionGuideText: 'computer-use.md', CuaSystemPermissionState: 'computer-use.md', RenderedDocumentBytes: 'office-to-pdf.md', OfficeToPdfRequest: 'office-to-pdf.md',