Files
dsh_shenxian/dsh-server-docs/04-调整方案/56-实例助手与文件下载端点.md
T
admin 5ad755116e chore(docs): 文档库并入代码仓(R4 选 a)+ 索引/台账跟进
1) dsh-server-docs/ 从工作区(原 E:\...\aliyun-dsh-server\dsh-server-docs)**整体并入本仓**,
   保留目录名 ⇒ 仓库内 dsh-server-docs/... 的相对引用天然继续有效;旧目录(含其 .git)已归档到
   工作区 _中间产物_待清理/,未随本提交带入。
2) .gitattributes:新增 `dsh-server-docs/** -text` —— 原文档库是 `* -text` + autocrlf=false,
   必须保持纯 LF,否则会被本仓的 CRLF 规则翻掉。
3) 活引用里的绝对路径已全部改到新位置(docs 的 INDEX / README / scripts / skills + 用户级 skills
   + ~/.workbuddy/settings.json 的 hooks);历史档案(04-调整方案/、archive/)按「只增不改」未动。
   ⚠️ hooks 路径改动需「完全重启会话」才生效(配置是会话启动快照)。
4) 交接单/T08:新增 §16「生产整体切换执行记录」(形态 / 落地动作 / **4 个只有真上线才暴露的真 bug** /
   验收证据 / 回滚命令 / 残留项);台账 T08 行 → 已完成并归档;03-路线图 §二 登记 T08 收尾项。
5) 统一称谓:**「本机」只指跑 WorkBuddy 的开发机**,47 / 106 一律写「远程服务器」。
2026-09-15 18:47:13 +08:00

8.4 KiB
Raw Blame History

56 · 实例助手:我的文件 / 能力清单 / 档位提示(+ 文件下载端点)

  • 日期:2026-09-11
  • 状态:✅ 已实施并端到端验证(commit ebe8075;双端文档 99/99)
  • 触发:① 用户要求「按照建议处理」= 落地档案 55 §四.1(老会话档位提示)与 §四.2(实例能力清单);② 用户追加问题「AI 生成的文件看不到、下不了,本机地址浏览器打不开」
  • 关联:档案 55(取证与结论)、50(实例页注入先例)、39(loopback 封锁)、33(权限档位默认值)、19 §C8(k8s 未验证路径)

一、三个问题与根因(都已解决)

# 用户可感知的问题 根因 本次处置
1 老会话里 AI「bash 不可用」,但没人告诉他为什么、怎么修 权限档位会话级播种:平台默认已改(档案 33),既有会话仍 workspace-write;本机沙箱后端不可用 → dsh fail-closed 拒绝任何 shell 实例页顶部提示条:说明原因 + 给出"切档位 / 新建会话"两条路
2 用户反复问「能力有变化吗」;AI 每会话都要现场试一遍(实测同批探测重复 3 轮) 平台没有权威能力说明,只有"试出来的"结论 能力清单(人看的实例页面板 + agent 看的共享技能,同源生成)
3 AI 产出的文件看不到、下不了;AI 给的是 /var/lib/.../ws/... 或 127.0.0.1:<port> 平台根本没有读取/下载端点(/api/desktop/tree 只列目录);且实例内不可访问宿主 loopback(档案 39)→ 127.0.0.1 链接必失效 新增 GET /api/fs/download + 实例页右下「我的文件」面板(浏览 + 下载)

二、实现

① 档位提示

改动 说明
新增 src/supervisor/session-preset.ts 解析会话事件流(多帧 zstd,按 28 b5 2f fd 切分逐帧解压)取最后一条 permission/preset;跨工作区取最近修改的会话
新增 GET /api/dsh/session-permission 返回 { expected, session, stale };stale 仅在「会话档位 = workspace-write 且 ≠ 平台默认」时为真(这是唯一真正挡住用户的组合)
实例页注入脚本 stale 时顶部横幅:说明「沙箱不可用 → AI 无法执行命令」+ 两条修法;可关闭(按 sessionId 记 sessionStorage)

② 能力清单(人机同源)

改动 说明
新增 scripts/gen-capabilities.cjs 探测工具链版本 + 技能清单,写出 ① /opt/dsh/state/capabilities.json(机读) ② bundled-skills/platform-capabilities/SKILL.md(平台首个共享技能,agent 可直接读)
内容 权限档位含义、可读/可写范围、网络边界(明确 127.0.0.1 不可达)、工具版本表、已注册技能清单、"把文件交给用户"的正确方式
新增 GET /api/capabilities 给实例页「能力」面板读同一份 JSON
cron 每日 05:05 重新生成(平台变化后清单自动跟上)

③ 文件查看 / 下载

改动 说明
UserFs 接口 + LocalUserFs.readFile 目录 → not_a_file;>32MB → too_large;路径逃逸 → bad_path;符号链接逃逸同样拒绝(复用既有 assertNoLinks)
K8sUserFs.readFile 显式抛 unsupported(sidecar 尚无 read 端点)—— 属档案 19 §C8 的未验证路径,不假装支持
新增 GET /api/fs/download requireAuth;限定在调用者自己的根内;带 content-type / content-disposition / no-store
实例页「我的文件」面板 右下角 📁 我的文件:目录可下钻、文件可一键下载、显示大小;走平台代理,不暴露宿主路径

注入方式:复用档案 50 的 HTML 注入点(proxy.ts 的 injectRecovery),新脚本与「401 自愈」并存,不改官方包、不落盘(R2)。


三、验证(实测,非推断)

CI:typecheck + build 通过;单测 42 通过 / 0 失败(新增 4 项 readFile 安全测试:目录/缺失/超限/路径逃逸/符号链接)。

端到端(铸造临时 session → 实测后即删):

检查 结果
GET /api/dsh/session-permission 200,stale=true(admin 最近会话仍是 workspace-write)✓
GET /api/capabilities 200,工具 10 项、含 python3=Python 3.12.14、网络边界写明 127.0.0.1 不可用 ✓
GET /api/fs/download?path=<文件> 200,content-type: text/markdown; charset=utf-8,内容正确 ✓
越界 ../../etc/passwd 400 bad_path(未读到)✓
目录(空路径) 400 not_a_file ✓
实例首页 HTML(35KB) 含 __dshAssist(新助手)且含 __dshRecover(原 401 自愈未破坏)✓
三个新端点未登录 均 401(已注册且受 requireAuth 保护)✓

产物:/opt/dsh/state/capabilities.json(2.5KB)、bundled-skills/platform-capabilities/SKILL.md(2.9KB)。


四、怎么用

用户(在会话页右下角)

  • 📁 我的文件 → 浏览自己的工作区、下载任意文件(AI 产出的报告/表格/脚本都在这里,不再需要服务器路径)。
  • 🧭 能力 → 看到权威的"能做什么/不能做什么"(含工具版本与技能清单)。
  • 若顶部出现黄色提示条 → 说明当前会话档位过时,按提示切换档位或新建会话即可让 AI 能执行命令。

AI(实例内)

  • 新会话会看到共享技能 platform-capabilities → 开工前读它即可,不必再现场试探;
  • 交付文件时按其中《把文件交给用户》一节:用工作区相对路径,并提示用户点右下角「我的文件」下载。

五、边界与已知限制

  1. 档位切换仍需用户在 dsh 界面操作:平台不会自动改既有会话的档位(那等于把受限会话静默提升为完全权限,属安全语义变更,必须用户知情)→ 提示条 + 新建会话是最稳的路径。
  2. /api/fs/download 仅 local 部署可用:k8s 的 sidecar 尚无 read 端点(抛 unsupported),已在接口注释与档案 19 §C8 标注。
  3. 单文件 32MB 上限;只允许访问自己的工作区(根外一律 bad_path)。
  4. 能力清单里的技能列表当前为空(平台尚未投放业务技能)——这正是档案 55 §四.3 待你裁定的事项;清单已把"空"如实写出来。

六、回滚

对象 回滚方式
代码 git revert ebe8075 + npm run build + 重启(备份:/opt/dsh/backups/p56-20260911-2251/)
注入脚本 摘掉 proxy.ts 里 SESSION_ASSIST_JS 的拼接即可(自愈脚本独立)
能力清单 删除 /opt/dsh/state/capabilities.json 与 bundled-skills/platform-capabilities/;cron 行删除
下载端点 删除 desktop.ts 的 /api/fs/download 路由(readFile 保留无副作用)

七、后续(可选,待裁定)

  1. 门户侧下载入口:门户「服务管理 → 浏览文件」目前只能列表/上传(portal.html),可补"下载"按钮复用同一端点。
  2. 技能投放:是否投放业务技能(决定能力清单里"技能"一节是否长期为空)。
  3. 档位一键切换:若你希望连"点一下切换"也由平台代劳,需要进一步评估(改会话状态有风险,建议保持现状)。

修正(2026-09-13 08:3x):注入脚本一直是坏的,今天才真正跑起来

2026-09-13 排查另一处注入脚本事故时发现:本档的 SESSION_ASSIST_JS(src/supervisor/proxy.ts) 在浏览器里从来没有执行过 —— 脚本内 8 处 '\n' 写成单反斜杠,而它位于 TS 模板字面量内, \n 在模板求值时就变成裸换行,注入后直接 SyntaxError,整段脚本作废。

⇒ 本档的「我的文件 / 能力清单」面板在前端实际未生效;当时的验收只 grep 了 页面 HTML 里是否出现 __dshAssist 字样(验的是"文本在不在",不是"脚本跑不跑"),所以是假绿。 2026-09-13 08:3x 已修(按源文件补成 \\n),并新增常设校验 scripts/verify-inject.cjs (先模板求值再 node --check,已接入 npm test)防复发。 后续:需要在真实浏览器里重跑一遍本档的面板验收(原本验收结论需重新判定)。