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 一律写「远程服务器」。
8.4 KiB
8.4 KiB
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→ 开工前读它即可,不必再现场试探; - 交付文件时按其中《把文件交给用户》一节:用工作区相对路径,并提示用户点右下角「我的文件」下载。
五、边界与已知限制
- 档位切换仍需用户在 dsh 界面操作:平台不会自动改既有会话的档位(那等于把受限会话静默提升为完全权限,属安全语义变更,必须用户知情)→ 提示条 + 新建会话是最稳的路径。
/api/fs/download仅 local 部署可用:k8s 的 sidecar 尚无 read 端点(抛unsupported),已在接口注释与档案 19 §C8 标注。- 单文件 32MB 上限;只允许访问自己的工作区(根外一律
bad_path)。 - 能力清单里的技能列表当前为空(平台尚未投放业务技能)——这正是档案 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 保留无副作用) |
七、后续(可选,待裁定)
- 门户侧下载入口:门户「服务管理 → 浏览文件」目前只能列表/上传(
portal.html),可补"下载"按钮复用同一端点。 - 技能投放:是否投放业务技能(决定能力清单里"技能"一节是否长期为空)。
- 档位一键切换:若你希望连"点一下切换"也由平台代劳,需要进一步评估(改会话状态有风险,建议保持现状)。
修正(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)防复发。
后续:需要在真实浏览器里重跑一遍本档的面板验收(原本验收结论需重新判定)。