Просмотр исходного кода

feat(desktop-host): 电脑面板增加文件与命令授权入口与状态展示

Samien 1 месяц назад
Родитель
Сommit
5b37233c81
2 измененных файлов с 13 добавлено и 2 удалено
  1. 10 1
      docs/desktop-hosts.md
  2. 3 1
      src/client/components/workspace/LocalVmChatPanel.vue

+ 10 - 1
docs/desktop-hosts.md

@@ -13,6 +13,15 @@
 4. 管理员在机器人聊天右侧电脑面板选择「本机」或「仅浏览器」后,用「操作的电脑」选择器把机器人绑定到某台主机(`agent.desktopHost`);未绑定时自动解析:优先本机回环,其次唯一在线的远程主机,多台在线必须显式选择。
 5. 首次授权:管理员点「在桌面端授权」,那台 Mac 弹出原生授权对话框;确认后引导开启屏幕录制与辅助功能。授权按账号哈希(ownerKey)记录在各 Mac 的 `computer-permissions.json`,各机独立;菜单「撤销本机控制授权」同时作用于本机与远程通道。
 
+## 文件与命令(全面操作)
+
+屏幕授权之外是独立的第二档授权「文件与命令」:电脑面板完成屏幕授权后出现「授权文件与命令」按钮,Mac 端弹窗单独确认。授权后该账号绑定这台机器的机器人获得四个工具:
+
+- `desktop_file_list` / `desktop_file_read` / `desktop_file_write`:在这台 Mac 的**用户主目录**范围内列目录、读文件(≤12MB)和写文件(≤10MB);`..`、绝对路径越界和符号链接逃逸都会被拒绝,TCC 保护目录仍按系统权限处理。
+- `desktop_shell`:在主目录执行一条 zsh 命令(最长 120 秒,stdout/stderr 各截断 512KB,超时终止并如实报告)。
+
+两档授权分别记录(`approved` 与 `approvedFull`),「撤销本机控制授权」同时清除;工具只注入给已获授权的机器人,且每次调用在服务端二次校验。
+
 ## 传输与边界
 
 - 机器端点 `POST /api/desktop-host/v1/<hostId>/exchange`:Bearer 令牌(sha256 后 timingSafeEqual)+ `x-desktop-host-protocol: 1`;拒绝任何携带 Origin 的请求,不进入用户会话、CSRF 或 CORS 体系;Host 白名单照常生效。
@@ -30,5 +39,5 @@
 ## 验证
 
 - 服务端:`tests/server/desktopHosts.test.ts`(注册令牌一次性、准入与协议头、撤销围栏)、`tests/server/desktopEnvironments.test.ts`(多主机隔离、绑定解析、按机围栏、接管按机独立)。
-- App:`desktop/remote-login.test.mjs`(地址归一化、账号密码登录的 CSRF/Origin 往返、管理员注册桌面主机、错误路径)、`desktop/host-manager.test.mjs`(配置校验、加密导入、交换往返、403 停止、退避重试)、`desktop/preferences.test.mjs`(启动选择与远程地址偏好持久化)。启动选择/登录窗口需按上述流程手工验收(自动化桌面测试通过 fixture 环境固定走本地路径)。
+- App:`desktop/remote-login.test.mjs`(地址归一化、账号密码登录的 CSRF/Origin 往返、管理员注册桌面主机、错误路径)、`desktop/host-files.test.mjs`(目录内列/读/写、路径与符号链接逃逸拒绝、命令退出码/超时/工作目录)、`desktop/host-manager.test.mjs`(配置校验、加密导入、交换往返、403 停止、退避重试)、`desktop/preferences.test.mjs`(启动选择与远程地址偏好持久化)。启动选择/登录窗口需按上述流程手工验收(自动化桌面测试通过 fixture 环境固定走本地路径)。
 - 浏览器:`tests/e2e/desktop-hosts.spec.ts`(注册/下载/停用;模拟远程主机连接后从电脑面板绑定机器人)。真实多机链路需按上述流程手工验收。

+ 3 - 1
src/client/components/workspace/LocalVmChatPanel.vue

@@ -145,9 +145,11 @@ onBeforeUnmount(()=>{closed=true;revision++;clearTimeout(timer)})
       <span class="hint">选择机器人在哪台电脑上查看和操作;每台电脑需单独授权。</span>
      </label>
      <template v-if="nativeSelected==='local'">
-      <div class="permission-list"><span>{{native?.local.authorized?'✓':'○'}} 本机控制授权</span><span>{{native?.local.screen?'✓':'○'}} 屏幕录制</span><span>{{native?.local.accessibility?'✓':'○'}} 辅助功能</span></div>
+      <div class="permission-list"><span>{{native?.local.authorized?'✓':'○'}} 本机控制授权</span><span>{{native?.local.screen?'✓':'○'}} 屏幕录制</span><span>{{native?.local.accessibility?'✓':'○'}} 辅助功能</span><span>{{native?.local.fullAuthorized?'✓':'○'}} 文件与命令</span></div>
       <button v-if="!native?.local.ready" class="primary" :disabled="busy||!native?.online||!isAdmin" @click="run(()=>apiRequest(`/api/app/agents/${selected}/desktop-environment/authorize`,{method:'POST',body:authorizeBody(),timeoutMs:125000}))">在桌面端授权</button>
       <p v-if="!native?.local.ready" class="hint">在连接的 Mac 上确认授权,并按系统提示开启权限;系统可能要求重新打开 App。</p>
+      <button v-else-if="!native?.local.fullAuthorized" :disabled="busy||!isAdmin" @click="run(()=>apiRequest(`/api/app/agents/${selected}/desktop-environment/authorize`,{method:'POST',body:{...authorizeBody(),scope:'full'},timeoutMs:125000}))">授权文件与命令</button>
+      <p v-if="native?.local.ready&&!native?.local.fullAuthorized" class="hint">授权后,机器人可以在这台 Mac 的用户主目录内读写文件并执行命令;每台电脑单独授权,可随时撤销。</p>
      </template>
      <label v-else>浏览器资料<select :value="native?.browser.profile" :disabled="busy" @change="run(()=>apiRequest(`/api/app/agents/${selected}/browser-profile`,{method:'PUT',body:{profile:($event.target as HTMLSelectElement).value}}))"><option value="persistent">此机器人的浏览器 · 保存登录</option><option value="temporary">临时浏览器 · 退出桌面端后清除</option></select></label>
      <button class="primary" :disabled="busy||!native?.online||(nativeSelected==='local'&&!native?.local.ready)" @click="openDesktop">{{nativeSelected==='browser'?'打开浏览器':'接管本机'}}</button>