Files
dsh_ai1net_server/.workbuddy/memory/2026-09-下.md
T
admin ce8e6ceed9 chore(工作区): 纳入版本控制基线(回收 411 MB 过程产物)
回收 411 MB(470 M → 58.8 M),全部经回收站,可恢复:
- 待清理/(146.2 M,含 relay 分片 128 M 与 42 项过程目录)
- tmp/(32.4 M,按接续棒命名的过程临时区)
- .workbuddy/tmp/(39.5 M)
- 4 份 workbuddy.db 冗余副本(101 M,09-23 事故的坏副本 / 抢救产物)
- tmp/im16/gw/centrifugo 二进制(63.9 M,可重下)+ 缓存残留

入库范围:常驻规则(CODEBUDDY.md / README.md / state.py)、在途接续入口与
接续包、docs/、交付物/、交接单/、归档/、scripts/、.codebuddy/、
.workbuddy/memory/;共 398 件,其中 >60 KB 的 26 件全为文档。

排除(.gitignore):tmp/、待清理/、运行态日志与缓存、*.db 与 DB 备份整目录、
打包二进制(*.tar.gz / *.tgz)、记忆修复前备份。
2026-09-24 07:51:03 +08:00

49 KiB
Raw Blame History

工作日志 · 2026-09(第 2 片)

⚠️ 本目录日志已按【月】分片(2026-09-15 用户定,单文件 ≤50 KB):同月续片 2026-09.md / 2026-09-下.md / 2026-09-下2.md / 2026-09-下3.md / 2026-09-下4.md / 2026-09-下5.md / 2026-09-下6.md / 2026-09-下7.md / 2026-09-下8.md / 2026-09-下9.md / 2026-09-下10.md / 2026-09-下11.md / 2026-09-下12.md / 2026-09-下13.md / 2026-09-下14.md / 2026-09-下15.md / 2026-09-下16.md / 2026-09-下17.md / 2026-09-下18.md / 2026-09-下19.md / 2026-09-下20.md / 2026-09-下21.md / 2026-09-下22.md / 2026-09-下23.md 写入约定:一律 append 到当月最后一片;该片超 50 KB ⇒ 新建 2026-09-下N.md;⛔ 不要再按日新建 2026-09-DD.md。 覆盖来源:2026-09-10.md、2026-09-11.md 上一片:2026-09.md 下一片:2026-09-下2.md


档案 21 续二:刷新载入慢 = 930KB bundle 未压缩(23:12-23:20)

新症状:刷新后载入历史会话要几分钟,体感网络卡。

取证(nginx 日志 4000 行窗口内 guest 子域 823 请求):

  • 单次刷新要下载 GET /plugins/??<45 个 client.js 拼接>&rev=<hash> = 实发 980 KB / 930 KB(含 dsh-client-hmr、typert-registry、api-gateway、~30 个 dsh-client-ui-*),另有 /assets/vendor-*.js 209 KB
  • 小 API 轮询密集:agentPresets/list 131、commands/list 105、modelCatalog 78、syncInspectManifest 76、subagents/list 73、session/list 68…

根因:① 全局 gzip_proxied 只列了 expired no-cache no-store private auth,不含 any → 反代响应多数不压缩(930 KB 原样下发;若压缩应是 ~250 KB 量级)② 哈希资源(/assets/*)无长缓存 → 每次刷新重下 ~1.2 MB。叠加 200+ 次小请求 RTT,在差链路上就是"几分钟"。

优化(已执行):两个 443 站点块的 location / 加 gzip_proxied any;;新增 location ~* ^/assets/ → 同代理参数 + gzip_proxied any + expires 30d。备份 *.bak-YYYYMMDD-HHMM-pre-gzip;nginx -t 通过 → reload → 生效(gzip_proxied any ×4、assets 块 ×2)→ 主域 200。 预期:930 KB → ~250 KB;重复刷新走缓存。验证:下次该请求的 $body_bytes_sent。

踩坑(工具):nginx 日志用 awk 按空格切字段会被 UA 里的空格/数字带偏(得出"耗时=0"的假结论);改用 python 正则解析器 _中间产物_待清理/nginx-slow.py(按 "请求行" 状态 字节 抓)。

待:用户硬刷新(Cmd/Ctrl+Shift+R)后复验体感;若仍慢 → 查该机链路(测速/VPN)与客户端轮询放大效应。文档双端 47/47 一致 → 提交 ede8156 推送。

域名切换请求侦察:evertwins.com(23:04-23:12,未改动任何配置)

用户请求:把访问域名改为 evertwins.com。侦察结论:3 个前置条件在服务器之外,暂不能执行。

硬事实(实测):

  1. evertwins.com 现解析到 AWS(76.223.54.146 / 13.248.169.48,AWS Global Accelerator 段),不指向本机 47.77.182.89 → 之前的 "Host: evertwins.com 880 次" 是扫描器伪造 Host 打到本机,不是该域名真指向我们
  2. CF token 仅覆盖 alotbuy.com 单 zone(token 有效,zones 列表只有 alotbuy.com;查 ?name=evertwins.com 命中 0)→ 无法用现有凭据签发 *.evertwins.com 通配证书(通配必须 DNS-01)
  3. 现证书仅 *.dsh.alotbuy.com + apex(Let's Encrypt,有效至 2026-12-07);certbot 走 authenticator = dns-cloudflare,凭据 /etc/cloudflare.ini(600)
  4. 编排器 DSHS_BASE_DOMAIN / COOKIE_DOMAIN 是单值;用户子域 = <username>.<baseDomain>(subdomainForUser),CORS 白名单亦基于它(server.ts:47 isAllowedOrigin)→ 不支持双域名并存,并存需改代码
  5. ⚠️ 中国大陆 Aliyun ECS 关键约束:域名解析到国内机器并走 80/443 需 ICP 备案(alotbuy.com 显然已备),evertwins.com 未确认备案 → 解析过来也可能被拦

需用户先办:① 确认拥有/可改 evertwins.com DNS;② 确认备案(或接入阿里云);③ 证书路径三选一(加入同一 CF 账号 / 提供当前 DNS 商 API 凭据 / 非通配 HTTP-01(不推荐))

待用户选:Q1 切换方式(完全替换 / 双域名并存(需改码) / 仅门户试水);Q2 子域形态(<user>.evertwins.com 推荐 / 路径式(需改码+nginx)) 可立即准备:新 vhost 模板(含本次 proxy_buffering off 修复项)、certbot 命令、切换与回滚脚本、切换前检查清单

用户 23:09 表示:还没准备好 → 域名切换暂缓(parked),不再推进,等用户后续主动提起。 侦察结论保留在本文件,可直接复用。

档案 22:服务域名迁移 → alotbuy.com(23:25-23:45,已完成上线)

用户裁定:路径 ②走 Cloudflare 代理(橙云,零 DNS 改动)。

前置实测:alotbuy.com A 已指向本机(经 CF);根域当时 nginx 404 空站 → 接管无冲突;*.alotbuy.com 通配已存在;ss 实测 CF 回源全走 443(非 Flexible,无明文风险);CF token 仅 DNS 权限(改不了 zone 设置)。

执行:

  1. certbot certonly --dns-cloudflare --dns-cloudflare-propagation-seconds 60 -d alotbuy.com -d '*.alotbuy.com' → 成功(至 2026-12-09)
  2. 新增 vhost alotbuy.com.conf:server_name alotbuy.com www.alotbuy.com *.alotbuy.com;80 段代理(兼容任一 CF 模式);继承 proxy_buffering off/gzip_proxied any/assets 缓存;CF 回源段 set_real_ip_from + real_ip_header CF-Connecting-IP(放在 server 块内)
  3. /opt/dsh/switch-domain-alotbuy.sh:改 env(BASE_DOMAIN=alotbuy.com、COOKIE_DOMAIN=.alotbuy.com,备份 env.bak-20260910-2332)→ drain(按 pid 结束,不用 pkill -f 自匹配)→ 重启 → 残留 0、服务 active
  4. 旧域改纯 301:map $host $alotbuy_new_host + 通配 server_name → 用户名映射正确

踩坑 4 个(已写进档案 22):① DNS-01 默认等 10 s 不够(CF 上实测 ~10 s 才可见)→ 须 --dns-cloudflare-propagation-seconds 60;certbot 只把该参数写进新证书的 renewal conf,旧证书要手工补(已补两个)② set_real_ip_from 写 vhost 文件顶层=http 上下文全局生效 → 必须放 server 块内 ③ 正则 server_name ~^(?<sub>...)\.dsh\.alotbuy\.com$ 实测不匹配(SNI 命中默认 vhost,服务的是 alotbuy 证书)→ 改用 map + 通配 ④ inline shell 在 Windows→ssh 边界多次被引号吃掉 → 一律「本地写文件 + scp + 远端执行」。

验证:门户经 CF 200;admin.alotbuy.com → 401(未登录,证明已被识别为用户子域);旧域 301 映射正确(apex→apex,admin.dsh.*→admin.alotbuy.com);源站证书校验 0。

遗留/待办:① CF SSL 模式建议人工确认 Full (Strict) + 开 Always Use HTTPS(token 无该权限)② 全员需重新登录(Cookie 域变化)③ Cookie 域覆盖 .alotbuy.com 全域(gao/mao/work 也会收到,如需收窄要改码)④ 宝塔保存站点会重写 vhost(可能抹掉我们的自定义指令)⑤ 档案 21 的 A1 禁 HMR / A2 catch-all 仍未做。

产出:档案 22-服务域名迁移到alotbuy.com.md;INDEX 场景表新增「访问入口」行;双端 48/48 一致 → 提交 7b2d268 推送;MEMORY.md 已更新访问入口与 CF/证书事实。

2026-09-11 工作日志

按方案执行:代码入库 + CI + 文档补齐 + 归档(00:07-00:25)

用户指令:「按照方案执行」;并反馈换域名后速度明显变快;档案 21 整体暂缓(A1 禁 HMR / A2 catch-all / 流式与刷新复验均不做)。

1. 代码:提交 + 双仓库同步(完成)

  • 服务器 /opt/dshs 提交 7cba3e3(前 HEAD 8355e99),内容:
    • 档案 20:crash-policy.ts(新增)+ orchestrator.ts/spawner.ts/config.ts/routes/dsh.ts/ensure-role-profile-patch.cjs/package.json(改)
    • 档案 18:poc/workspace-scoped-picker/(插件源码 + README + 自检)
    • 档案 19 §C3:scripts/ci.sh(新增)、删 smoke-plugins.mjs(目标路由已在档案 16 阶段 0 移除)、删 smoke-watchdog.mjs(watchdog 从不启动)
  • 仓库根污染清理:10 个 bak-* 移入 /opt/dsh/backups/repo-bak-20260911-0018/(非删除)
  • 同步路径:服务器 git bundle master --not 8355e99 → scp → 本机 D:\github\dsh_shenxian fetch + merge --ff-only → push Gitea 8355e99..7cba3e3 ✅

2. 档案 20 的 live 自愈实测(顺带完成)

kill admin main 后 journald 出现:

[crash-restart] {"event":"instance-restart","userId":"cce6d1cd-…","attempt":1,"delayMs":1000,"restartsInWindow":1,"restarts":1,...}

→ 1 s 退避重启、计数与结构化日志均如设计 ✅(熔断 live 实测仍待(需连续 6 次 kill),策略已由 7 项单测覆盖)

3. 档案 18 机制修正(重要实证结论)

用户截图证实第一次覆盖未生效(admin 仍能看到整个文件系统)。取证:

  • admin profile cordis.patch.yml 的覆盖行确实存在,但 --dump-config 仍显示官方 -auto 行、本包出现 0 次;插件本身从 profile 目录可正常 import ✓
  • ⇒ 本 dsh 构建下「同 id 换 name」的覆盖写法不生效(bundle 层与 profile 层都试过)。档案 09 的先例是 client 行 + disabled: true,不是换 name。
  • 另确认:--dump-config 不反映 bundle/profile patch 层(连已生效的 portal-entry/business-plugins 行、档案 09 的禁用行都不显示)→ 不能作为验收口径。
  • 改为 v2 写法(写入 admin profile patch 并已重启实例):
    - insert:
        - id: workspace-scoped-picker
          name: "@dsh-local/workspace-scoped-picker"
    - id: directory-picker
      name: "@deepseek-ai/dsh-host-directory-picker-auto"
      disabled: true
    
  • 插件源码同步更新:cordis.patch.yml 改 insert 形态 + 新增 README.md(记录机制结论与安装要点);文档库片段同步订正。
  • 待用户浏览器验证:重载后点「添加工作区」是否只剩自有目录。

4. 文档补齐(档案 19 §D1/D2/D3)+ 运维归档

  • 01-规划与架构:追加「附一 机制索引」表(机制 → 生效位置 → 档案号,12 行)
  • 02-运维手册:追加「附录 C」——域名与证书(含 DNS-01 必须 60 s 传播)、nginx 四行关键配置及其失效症状、平台脚本族、排障速查(含 bwrap 包装进程假象提醒)
  • 03-路线图:刷新头部状态、已完成清单补 14-22(共 22 项)、待办表重写(picker 全量 / H2 / CF Strict / Cookie 域 / 熔断实测 / 档案21 暂缓)
  • 归档新增:ops/nginx/alotbuy.com.conf、ops/nginx/dsh.alotbuy.com.conf(旧域301)、ops/scripts/switch-domain-alotbuy.sh、scripts/{cf-dns.py,cf-probe.py,nginx-slow.py}
  • 文档双端 54/54 一致 → 提交 3e6cf98、b5b599c 推送

5. 本机整理

_中间产物_待清理/ 已清空删除;插件源码保留在工作区根 poc-dsh-local/workspace-scoped-picker/(唯一真源)。

待办:① admin 浏览器验证 picker(若仍不行 → 转 H3 自建 host 插件覆盖 seam 或 bwrap 层方案)② 通过后写 install-workspace-picker.cjs 全量 ③ H2 写保护 ④ CF SSL 改 Full (Strict)(人工)⑤ 档案 19 剩余批次(C5 死代码、C6 扫描规则、C8-C10、D6-D8)

档案 23:实例内 AI 能力与权限限制核查(00:25-00:45)

用户提供:guest 对话里 AI 遇到的 9 条"平台/环境限制",要求判定正常/不正常。

判定结论:

# 项 判定
1 沙箱后端缺失 → bash 工具被整体拒绝 🔴 P1 真 bug(已修,待重启生效)
2 只读根文件系统(/etc 等 4 处) ✅ 设计使然
3 无 sudo ✅ 设计使然(bwrap 默认 no_new_privs → setuid 失效;宿主 sudo 本身是 setuid root)
4 他人目录 Permission denied ✅ 隔离生效(users/ 711、用户目录 700)
5 rg / jq / ffmpeg MISSING ⚠️ 可优化(宿主装即可,/usr ro-bind 直接可见)
6 无浏览器 / 无 playwright ⚠️ 预期内但影响抖音类任务(受 512M 内存上限约束)
7 python 3.6.8 + pip 9.0.3 ⚠️ 可优化(EOL;宿主系统 python)
8 无 DeepSeek key → curl 401 ⚠️ 半正常:实例 env 确实含 DEEPSEEK_API_KEY(实测键名),401 因未带鉴权头;但统一 key 注入实例 = 租户 agent 可外泄平台 key(P1 风险)
9 api.vvhan.com 解析失败 ✅ 与平台无关(公共 DNS 亦 NXDOMAIN,vvhan.com 本身可解析)

第 1 条根因链(关键发现):dsh-sandbox-local 在 Linux 的 runner 链 = bwrap → Landlock;Landlock 需内核 ≥5.13(本机 al8 为 4.19,不可用);而 bwrap 功能探针在我们构造的合成根(只 bind /usr /lib64 /etc /dev /proc /tmp + 用户根)里 execvp 失败(缺 /bin、/sbin、/lib) → SANDBOX_UNAVAILABLE → dsh-sandbox 拒绝无沙箱运行("refusing to run the command unconfined")→ 实例内 bash 工具完全不可用。 修复:spawnAsUser bwrap 参数补 3 个符号链接(不扩大可见面):--symlink usr/bin /bin、usr/sbin /sbin、usr/lib /lib。实测嵌套 bwrap 补链后 NESTED-SHELL-OK;ci.sh 通过。 提交:服务器 32a9ad8 → bundle → 本机 merge → push Gitea 7cba3e3..32a9ad8。生效需重启 dshs(drain 实例)。

产出:档案 23-实例内AI能力与权限限制核查.md(含逐条判定、根因链、建议项成本表、红线声明);双端 55/55 一致 → 提交 023ba48 推送。 待用户:① 授权重启使沙箱修复生效 ② 是否装 ffmpeg/python3.11/rg/jq ③ 是否评估 per-user key 或内网 LLM 网关(消除统一 key 外泄面)

档案 24:实例侧 401(token 过期)自动恢复(00:32-01:00)

用户报障:刷新实例子域页面显示 dsh web authentication required; reopen the URL printed by dsh web.,不跳转也不重启。

成因:实例重启后 launch token 轮换(本次由我 00:14 为验证 picker 重启实例引起;23:32 域名迁移 drain 同理),旧标签页 URL 带旧 token → dsh 返回 401 + 该文案;而 proxy.ts 只处理了「门户侧 401」(resolveSubdomainAccess 的 unauthorized → 302 login.html),上游(实例)401 被原样透传;not_running 自动重启分支不适用(实例在跑)。

修复(src/supervisor/proxy.ts):

  • proxyHttp 增加 freshAuthUrl?: () => Promise<string|undefined> 参数
  • 上游 401 + 浏览器导航(GET + accept: text/html)→ upRes.resume() + 302 到 freshAuthUrl()(取 supervisor.status(userId).main.launchToken → https://<同Host>/?token=<新token>,拿不到则回门户)
  • SubdomainAccess 成功分支补 userId;路径式 /u/:slug/dsh/ 路由同样接入
  • 编译 + ci.sh(38 pass / 0 fail)通过

同批上线:档案 23 的沙箱修复(bwrap 补 /bin /sbin /lib 符号链接)→ 实例内 bash 工具恢复可用。 重启:drain + systemctl start → active / 残留实例 0 / 门户 200;lib/ 产物确认 symlink×3 与 freshAuthUrl×3。 提交:服务器 d7ab5b4 → bundle → 本机 merge → push Gitea 32a9ad8..d7ab5b4;文档档案 24 + INDEX/README → 双端 56/56 一致 → 推送 102ffda。 回滚:/opt/dsh/backups/proxy.ts.bak-<TS> 或 git revert d7ab5b4 + build + 重启。 待用户:重新进入会话(drain 后需重进)并实测:① 实例重启后刷新旧页应自动恢复 ② agent 的 bash 工具应可用。

档案 24 后续:预置默认工作区 + picker 友好化(00:43-00:55)

用户反馈(附截图):编辑器要求"必须选择一个工作区"才能开始会话;要求「只允许在自己的目录下创建工作区」「不要给用户看完整路径」。

关键发现:工作区列表持久化在 <实例 home>/storages/workspace.json(schema: unit{name:workspace,version:2} + global{initialized,workspaceIds[],archivedSessionIds[]} + tables.workspaces{<uuid>:{path,title,sessionIds[],createdAt,updatedAt}})。实测:admin 的列表为空(workspaceIds: [] → 所以一直被要求选工作区);guest 有(path=ws/MCN短视频创作,title 是短名 → 本来就不显示完整路径)。UI 侧 label 用法远多于 .path(108 vs 2)→ 展示层可收敛。

已实施(两项,均已上线):

  1. 预置默认工作区(编排器 seedDefaultWorkspace,spawnInstance 内 isMain 时调用):仅当 workspaceIds 为空时,把 <userRoot>/ws 以短标题 「我的工作区」 写入 workspace.json,并 chown 给该 uid;不覆盖用户已有工作区;失败只记日志不阻断启动 → 用户开箱即用,不必手动选。
  2. 插件 v0.1.1:① 面包屑根节点显示 「我的工作区」(可用 DSH_WORKSPACE_LABEL 覆盖),不再暴露 /var/lib/.../users/<uuid>/ws;② 新增加载标记 [workspace-scoped-picker] loaded root=...(启动日志可直接确认是否生效,解决"无法验证"问题)。
    • 安装:重建 tgz(package/ 前缀)→ 两个 profile pnpm add file:...tgz(guest 的 profile 是 pnpm workspace 根,需 -w,否则 ERR_PNPM_ADDING_TO_ROOT)→ 软链指向 0.1.1 ✓
    • 自检 26/26 通过;ci.sh 通过;服务已重启(active / 0 实例 / 门户 200)

待用户实测:① 进入会话后编辑器应直接可用(默认工作区已种)② 点 + 添加工作区时面包屑应显示「我的工作区」且看不到完整路径、出不去自己目录 ③ 我可在日志里查 [workspace-scoped-picker] loaded 与 [seed-workspace] 验证。

档案 25:事故 + not_running 兜底(00:49-01:05)

用户报障:刷新后显示裸 JSON {"error":"not_running"},无自动跳转。

取证(journald):dsh: plugin tree failed to load ... duplicate loader entry id: workspace-scoped-picker → 实例启动即退出 → 崩溃循环 5 次(1→2→4→8→16s)→ 熔断 crash-loop-circuit-open → 用户刷新落到 reply.code(404).send({error:'not_running'}) 显示裸 JSON。

根因(自造回归):我把插件包内 cordis.patch.yml 从"覆盖行"改成 insert: workspace-scoped-picker,而 profile 层也 insert 同一 id → 双写 → loader 视为致命错误。 正面收获:档案 20 的熔断按设计工作(拦住无限重启),并把实例标 failed 后允许重新进入。

修复(已上线,均推送):

  1. 包内 bundle patch 改 空补丁 [](单点插入只在 profile 层);插件版本 0.1.1→0.1.2
  2. proxy.ts:not_running 浏览器导航永不吐 JSON;并发进场(AlreadyRunningError)→ 等待在飞实例 token(≤20s)→ 302 带 token;仍拿不到 → 302 回门户
  3. 同批:seedDefaultWorkspace(预置工作区)+ 插件友好面包屑 + 加载标记

安装踩坑:pnpm add 需 HOME=<ws>;-w 仅在 profile 是 pnpm workspace 根时需要(guest 需要、admin 不需要,否则 --workspace-root may only be used inside a workspace);换版本号以避开 pnpm 缓存。

验证:ci.sh 38 pass;两 profile 装 0.1.2(包内 patch 确认为 []);自检 26/26;服务重启 active/0/门户 200。 提交:代码 ce1b3ac(push d7ab5b4..ce1b3ac);文档档案 25 → 双端 57/57 → push 86e662f。 待验证:用户进入会话后核对日志 [workspace-scoped-picker] loaded 与 [seed-workspace](确认插件生效 + 默认工作区已种)。

验证结果:插件生效 ✅ / seed 兜底不可靠 ⚠️(01:00)

① 插件已确认生效(首次确定性证明,靠新加的加载标记):

00:53:33 [dsh-child main] [workspace-scoped-picker] loaded root=/var/lib/.../users/cce6d1cd-.../ws (cwd)

→ disabled 官方行 + insert 自建行 的组合成功;scoped picker 生效(只看自有目录 + 友好面包屑)。用户"能自己建/选工作区且不见系统路径"的需求已满足。

② seed 默认工作区不可靠:日志显示种子写入成功(00:49:29 [seed-workspace] … → …/ws),但 admin 的 workspace.json 随后被实例自身的注册表规范化重写为空列表(mtime 00:54、workspaceIds: []、tables.workspaces: {},仅保留 archivedSessionIds)。 → 推论:dsh 启动时会加载并重写该存储,对我们的条目做了校验/清理(dsh-workspace 有 order(显示顺序)与不变量 initialized && order.size !== table.size,且会报 order unknown workspace '…')——预置条目会在启动时被丢弃,因此不能作为"开箱即用"的可靠手段。 → 结论:改为依赖用户经 + 自行创建(已可用);若要做"开箱即用",应改为实例启动后经 dsh 自身 API(workspace/create)创建(需探针确认方法名,列为后续项)。

用户顾虑已确认成立:用户可删除工作区 → 删空后回到"必须选一个工作区";但现在他们能自己建(+ → 只在自己目录内),所以不再卡死。

档案 26:dsh 升级耦合点与回归清单(01:05-01:15)

用户问:这些改动是否影响 dsh 原始包?dsh 更新是否会覆盖?

实测证据(只读):

  • 官方包目录(/usr/local/lib/node_modules/@deepseek-ai/dsh,版本 0.1.2-rc.1)内零改动:grep 我们的标识(workspace-scoped-picker / dshs / freshAuthUrl / crash-restart)无命中;包内无 2026-09-10 之后被修改的文件
  • 所有改动都在 dsh 之外四层:① 编排器 /opt/dshs(6 提交)② profile 层(dataRoot 内 cordis.patch.yml/bundles/node_modules/@dsh-local/{portal-entry,business-plugins,workspace-scoped-picker})③ 实例环境(bwrap 参数,编排器代码)④ 系统层(nginx vhost、systemd dshs/dsh-egress/dsh-provision、env、certbot)
  • 结论:升级不会"覆盖"我们的改动(无可覆盖之物);真正风险 = 官方契约变化导致失效。

六类耦合点(升级必查,已写入档案 26):

  • C1 profile patch 引用的官方 client 行 id(ui-settings-models/-plugins/-plugin-inventory/ui-cordis)→ 检查设置面板是否仍隐藏
  • C2 directory-picker 行(已回滚,现为官方默认 []);双面性教训:disable 该行会同时失去客户端对话框
  • C3 自建插件继承官方 seam 基类(dsh-host-directory-picker)→ 当前未挂载,风险 0;靠加载标记可验
  • C4 自建 client bundle 契约(portal-entry / business-plugins)
  • C5 编排器 bwrap 合成根(/bin /sbin /lib symlink ↔ dsh 沙箱后端 bwrap/Landlock)
  • C6 proxy 的 401 / not_running 假设(dsh 文案、响应码、launch token)

建议:升级前 git tag pre-dsh-upgrade-<版本> + 备份(nft/env/vhost/profile patch);升级后逐项回归;任一失败先 pin 回旧版。并给 portal-entry / business-plugins 各加一行加载标记(下次改造顺手做),把 C4 回归从"看 UI"变成"看日志"。

产出:档案 26-dsh升级耦合点与回归清单.md;双端 58/58 一致 → 提交 c443da9 推送。

档案 18 v3:官方对话框 + 受限 host(用户已验证)+ 隐藏改路径入口(01:05-01:25)

关键机制(实测):官方 directory-picker 行(@…-auto) 在启动时连带挂载客户端对话框 @deepseek-ai/dsh-client-ui-directory-picker-browse(该 client 包自带 dsh.client 元数据、且是 dsh-web-app 的依赖)。disable 那一行 → 对话框消失(点不动,客户端连 RPC 都不发)。 v3 解法(配置层,不改官方包):

- insert:
    - id: workspace-scoped-picker        # host 面:自建受限实现(根=<userRoot>/ws)
      name: "@dsh-local/workspace-scoped-picker"
    - id: ui-directory-picker-browse     # client 面:官方对话框 UI 单独插回
      name: "@deepseek-ai/dsh-client-ui-directory-picker-browse"
- id: directory-picker
  name: "@deepseek-ai/dsh-host-directory-picker-auto"
  disabled: true

→ 用户实测:对话框能开 ✓,路径框里手输 / 被拒 ✓(host 侧越界校验生效)。

v0.1.4 增量:隐藏"改路径"入口——插件新增 client 面(lib/client.js),以 window.__ModuleLoader__.load({factory}) 形态只导出 apply+inject(禁 exports.default,红线 R3),注入 CSS 隐藏官方对话框的 crumbEditZone/crumbEditGlyph(用 [class*=…] 模糊匹配抗 CSS-module 哈希),并挂 MutationObserver 兜懒挂载。

全量铺开(已有工具):

  • poc/workspace-scoped-picker/ensure-workspace-picker-patch.cjs:以 # >>> platform: workspace-scoped-picker 标记幂等追加平台段(保留角色 patch),支持 --dry-run/--restart/指定用户
  • /opt/dsh/install-wsp.sh <tgz>:按用户正确切换 pnpm add 的 -w(guest 的 profile 是 pnpm workspace 根需 -w;admin 不需要)并校验 lib/

本轮踩坑(3 个,都要记):

  1. pnpm 同版本缓存:同名同版本 tgz 即使内容变了也复用旧包 → 必须升版本号(0.1.3→0.1.4)才生效;
  2. scp 报错被我 grep 过滤 → lib/client.js 没传上服务器却发现得晚 → 传输后必须显式 ls 校验;
  3. 脚本追加平台段前未做"整文件去重" → admin 的 patch 出现两段(手写 v3 + 追加段)→ 又是 duplicate id 隐患;已重写为单段。教训:平台段必须以标记包裹,且写入前先检测整份文件是否已含这些 id。

当前状态:admin/guest 两个 profile 均已装 0.1.4(lib/{index.js,client.js})、平台段均为单份;实例已重启(kill→自愈拉起),日志 [workspace-scoped-picker] loaded root=…/ws ✓、无 duplicate 报错;ws 里历史 tgz 已清理。 待用户:硬刷新后验证「选择工作区」顶部不再出现可编辑路径框/铅笔图标。

续:紧急修复 + 全量铺开 + B1 写保护(07:44-08:02)

① 紧急修复:重复平台段(我的脚本 bug)

  • 现象:ensure-workspace-picker.cjs 初版 marker 检测写成 …picker.cjs,与历史 …picker-patch.cjs 不匹配 → 二次追加 → 两份 profile 各 2 份平台段(duplicate loader id 隐患,下次实例启动会崩)。
  • 修复:① normalize-picker-patch.py 一次性归一化(已执行,各 id 恰好 1 份;guest 角色 patch 完整保留 ✓)② 脚本改为正则宽松匹配 marker + 写入前剥离所有旧段再比对 → 天然自愈(幂等已验证:两用户均 skip)。

② 新用户自动铺开(第一层,已上线)

  • ensure-workspace-picker.cjs:全量幂等(① 写平台段 ② 逐用户 pnpm add 插件,含版本比对;--dry-run/--restart/指定用户)
  • 已接入 /usr/local/bin/provision-new-users.sh 尾部(新用户注册自动补齐);插件产物固定 /opt/dsh/artifacts/workspace-scoped-picker-0.1.4.tgz

③ B1 / H2 写保护(P1,已实现 + 双重验证 + 已激活)

  • 威胁:用户把 <userRoot>/home/profiles/web 加为工作区 → 用 bash 改写 cordis.patch.yml(去掉平台段)或 package.json(挂任意 bundle)→ 绕过平台策略
  • 实现:spawnAsUser 增 role 形参;在 --bind root root 之后追加 --ro-bind-try(cordis.patch.yml / package.json / pnpm-lock.yaml);不动 cordis.yml(dsh 启动会写它)
  • 验证:手工 bwrap 写测试(读✅/写策略文件被拒✅/cordis.yml 可写✅/ws 可写✅)+ 真实 dsh 启动(guest profile + 新参数)正常打印 launch token ✅ + ci.sh 通过
  • 已重启服务激活(active / 0 残留 / 门户 200);lib/ 含 ro-bind-try ✓

④ 其他

  • A1 已验证:CF Always Use HTTPS 生效(http→301、子域同、https 200)
  • A2 结论:Cookie 域 .alotbuy.com 不影响 gao/mao/work 功能;属隐私边界取舍;当前架构不能改 host-only;彻底隔离需专用子域(成本高)→ 记已知取舍
  • B5 前置不满足:portal-entry/business-plugins 源码不在仓库(仅 /tmp 遗留)→ 加加载标记暂缓
  • 运维要点:本地 shell PATH 曾损坏(dirname/grep/ssh 全找不到)→ 需显式 export PATH=<PortableGit>/usr/bin:/c/Windows/System32:$PATH;git fetch 引用 bundle 必须用 D:/… 路径(MSYS 的 /d/… 会失败)

提交:277d817(脚本+归一化)、34190b1(B1)→ 推送 Gitea be3d3f6..34190b1 ✓

B6 文档批次 + B4/B2/B3 实现并激活(08:02-08:30)

B6(档案 19 §C8/C9/C10 + §D6/D7/D8)全部完成(纯文档,双端 59/59)

  • C9:新增 DEPLOY-本部署.md —— 拓扑/目录布局/env 键(8 个)/依赖版本(实测 Node 22.23.2、pnpm 9.15.9、bwrap 0.4.0、nginx 1.28.3、sqlite3 3.26)/平台脚本族/构建·部署·回滚三步/默认红线 5 条
  • C10:两处 docs/ 互指(文档库 README ↔ 代码仓库 README,均标注"上游 7 篇勿混")
  • C8:02-运维手册 附录 C.5 列出本部署未使用/未验证子系统(k8s-spawner/pg/leader/reconcile)
  • D6:档案 12(VoxEMW)标注已冻结;D7:档案 18 头部加"与 17 同主线"互指(保持独立编号避免断链);D8:06-工作台UI规范 加"同步时间戳 + 回拷四步"
  • INDEX 登记 DEPLOY;03 路线图刷新

B4(档案 19 §C6 安全扫描分级,已实现+已激活+已冒烟)

  • src/web/security-scan.ts:规则拆 P0 阻断(原 7 条 + nc -e 反弹 / /dev/tcp 反弹 / base64 解码后执行 / 给系统二进制加 setuid / fork 炸弹)与 P1 告警(动态执行子进程、vm/eval/new Function、敏感 env、超长 base64、外部网络、隐藏目录/.git)
  • 覆盖面:扩展名 19→31、无扩展名按 shebang、二进制按 0x00 跳过、上限 2MB→8MB;P1 以 findings 返回并写 journald(upload-scan-warnings)
  • 冒烟实测:P1 样例(child_process + DEEPSEEK_API_KEY)→ 2 条 findings 且不阻断 ✅;P0 样例(nc -e)→ 阻断 ✅;良性样例 → 零误报 ✅

B2(档案 18 v3 第二层自愈,已实现+已激活)

  • orchestrator.ensurePickerProfile(userId):异步 fire-and-forget 调 ensure-workspace-picker.cjs --user-id <id>;调用点 = launch() 前 + main spawn 成功后 → 覆盖 profile 被清空/重建/平台段被删/插件被卸
  • 脚本新增 --user-id 过滤(实测命中 admin 并 skip;md5 双端一致)

B3(§C5)子集:删除 src/runtime.ts(全仓无引用)→ 并清掉 tsc 遗留的 lib/runtime.js 过期产物。 folder_plugins/enablePatch/renderPatch/patch? 链路跨 10 文件 → 留专项(不在本轮)。

验证与激活:ci.sh 全绿;drain + 重启后 lib/ 含 WARN_RULES/BLOCK_PATTERNS/ensurePickerProfile/ro-bind-try,lib/runtime.js 已清;服务 active / 0 残留 / 门户 200。

提交:代码 277d817、34190b1(B1)、a9c065d(B4+B2+B3)→ 推送 至 a9c065d;文档 6940c8b、859ed06 → 59/59 一致。

运维踩坑(再次出现,务必记住):

  1. 本地 shell 的 PATH 会莫名丢失(dirname/grep/cat/ls 全找不到)→ 每条命令都要显式 export PATH(PortableGit/usr/bin + System32);
  2. scp 相对路径 + 命令链中段失败会让文件没传上去(表现为服务器脚本仍是旧版)→ 一律用绝对本地路径 scp 并当场比对 md5;
  3. git fetch 引用 bundle 必须用 D:/… 路径;
  4. ssh 内联 python/node 一遇括号/引号就炸 → 一律"本地写脚本文件 + scp + 执行"

档案 27:MCN 工作台插件平台化评估 + B3/B5 收尾(08:17-08:35)

用户问:MCN 工作台插件能否作为业务插件投放到平台?多用户?数据/产物如何存?插件自带 DB 有无风险?业务插件调用 skill 应整合还是外置?

实测结论(档案 27):

  • 插件形态:dsh-plugin-mcn(569K)标准 bundle(dsh.client inject sidebar/runtime + cordis.patch.yml insert mcn-nav);host 面 lib/{index,db,data,http,mcp,imports,video-tools,workshop,config}.js
  • DB:node:sqlite(DatabaseSync),路径 homedir()/.dsh/mcn-plugin.db;10 张表按 account_id 组织、无 userId(单租户)
  • 技能调用方式:插件不执行技能 —— UI 收集参数后拼提示词要求 agent 调用具名技能(如「请调用 mcn-dou-analysis 技能(路径 ~/.dsh/skills/mcn-dou-analysis/)」);硬编码技能路径 13 处(mcn-dou-analysis×6、script-review×3、short-video-script×2、storyboard-prompt×1、mcn-data-insight×1)
  • 能力来源 = agent preset:<DSH_HOME>/.agent-presets/mcn/{preset.yml,agent.cordis.yml}(挂 persona/tool-bash/tool-fs…),插件经 ctx.get("agentPresets").resolve('mcn') + mount() 加载
  • MCP:自带 stdio 客户端 npx myai-mcp(NPX_CMD/MCP_PACKAGE/MCP_ENV);mcp.js 已优先读 DSH_HOME(正确写法,config.js 漏改)

结论:① 可作业务插件投放,但 3 处 P0 必改:homedir()/.dsh→DSH_HOME、技能路径改只报技能名、agent preset 随包投放(平台每用户独立 DSH_HOME,无人写就没 preset→退化为默认,能力缺失);② 多用户天然成立(每用户独立实例 → 每用户一份 DB,无需加 userId);③ 产物写用户工作区 ✓ 隔离;④ node:sqlite 平台 Node 22.23.2 实测可用(ExperimentalWarning);风险=实验 API 随 Node 变动(应补档案 26)、用户可损坏自己的库(建议重建兜底);⑤ 技能=外置(平台共享技能层,一份 vs 每用户一份、可独立更新、复用平台技能管理面),preset 仍需随插件投放,MCP 建议预装(避免每次 npx 拉包)

B3 便宜版完成 / 完整版关闭:src/runtime.ts 已删(上轮);本轮给 folder_plugins/workspaces 加废弃注释(schema sqlite+pg 各 2 处,共 4 处)+ 档案 16 标注"表保留、无业务调用、勿再加功能"。决策:C5 完整版关闭(跨 10 文件 + 含 k8s/PG 未验证路径,删除风险不对称)。 踩坑:注释里含反引号会截断 TS 模板字符串(schema 是模板字面量)→ 改无引号写法。 B5 降级为"顺手做"(不解决当前问题 + 源码不在仓库 + 仅提升升级排查速度)。

提交:代码 57a7d34(推送 a9c065d..57a7d34);文档 1452541(859ed06..1452541,双端 60/60)。 待办:MCN 平台化改造(3 项 P0 + 3 项 P1)待启动;其余为触发式(会话 GC、档案16 阶段3-4)。

档案 28:用户数据清理策略(工作区/会话/回收站)+ 修掉污染 ws 的平台 bug(08:32-09:00)

用户口径:AI 生成的代码/脚本按对话任务一次性产生、无复用价值 → 是定期清理的重点;会话记录达到某大小阈值后可清 90 天前的。

关键发现(平台自身 bug):安装脚本原用 HOME=<用户 ws> pnpm add … → pnpm 把 store($HOME/.local/share/pnpm/store)与 cache($HOME/.cache/pnpm)写进用户工作区,再叠加我们复制进去的 *.tgz、poc/、.poc-backup/。 实测:admin ws 18MB/2045 文件(17.6MB 全是平台产物;真内容仅 92KB)、guest 2.5MB/30 文件(2.0MB 平台产物;真内容 504KB)。→ 这才是"ws 里一堆文件"的真凶,与 AI 无关。

修复与清理(已执行 + 已实测):

  1. ensure-workspace-picker.cjs 改为 --store-dir <home>/.pnpm-store --cache-dir <home>/.pnpm-cache,并直接用 /opt/dsh/artifacts/ 的 tgz(不再复制进 ws);
  2. 存量污染用 scripts/clean-ws-pollution.cjs(白名单精确匹配)mv 到 <userRoot>/trash/2026-09-11-ws-pollution/ → admin 12KB/0 文件、guest 480KB/8 文件;
  3. 实测清理后真实 dsh 正常启动 + 插件正常加载([workspace-scoped-picker] loaded… + token)→ 证明清 store/cache 无副作用。

新增三件套(档案 28):

  • scripts/ws-cleanup.cjs:三档 —— T1 平台产物/明确垃圾(.local/.cache/poc/.poc-backup/__pycache__/node_modules/.pytest_cache + *.tgz/.tmp/.log/.bak/.part)直接清;T2 ws 顶层一次性脚本(.py/.js/.sh/.ps1/.ts/.ipynb/.sql 等)>90 天才清;T3 其余(含所有目录)永不自动删,只统计。阈值 ws ≥ 2048MB 才动手;阈值/天数可 --threshold/--days 调。
  • scripts/session-gc.cjs:会话保留期回收(sessions/<slug>/<session-id>/session.jsonl.zstd;sessions ≥ 500MB 才触发,清 >90 天;结构与"无索引文件"已实测)。
  • scripts/purge-trash.sh:回收站 超 30 天真删(唯一执行删除的组件)。
  • cron(/etc/cron.d/dsh-maintenance):每天 04:10 ws-cleanup / 04:20 session-gc / 04:40 purge-trash;日志 /var/log/dsh-{ws-cleanup,session-gc,trash-purge}.log。
  • 安全设计:所有清理一律 mv 到 <userRoot>/trash/<日期>-<任务>/(同盘秒级、可整目录移回),30 天后才真删。
  • 三脚本 dry-run 与 --apply(阈值未到时正确跳过)均实测通过。

未做/待细化:session_projcache 同步回收(KB 级暂不动);T2 保留标记(.keep/项目内 scripts/ 豁免);门户用量可见性;可选硬配额(XFS project quota)。

提交:代码 3f6481b(57a7d34..3f6481b);文档 3f40de8(1452541..3f40de8,双端 61/61)。

档案 28 续:清理策略"一次性优化到位"(08:46-09:00)

用户要求:一次性优化到位;AI 对话记录(sessions)保留时间可以长些。

本轮全部落地(5 项):

  1. 会话保留期加长:90 → 365 天;阈值 500 → 1024 MB(cron 已更新)
  2. .keep 豁免:ws/.keep 存在 → 跳过 T2(一次性脚本不清理),T1 仍清
  3. projcache 同步回收:session-gc.cjs 删会话时按 session-id 精确回收 storages/session_projcache/sessions/<id>.json
  4. 用量可见:新增 scripts/storage-report.cjs(每小时快照 /var/run/dsh-storage-report.json)+ 门户 GET /api/admin/storage(requireAdmin,只读快照不做实时 du)+ admin.html 新增「存储用量」区块(工作区/会话/回收站/可清理项数,超阈值标红)
  5. 硬配额评估结论 = 不做:根 fs 实测 ext4(未启用 prjquota)→ 强行上需重挂载/重建(停机与数据风险不对称);当前用量 24%(8.6G/40G)无压力 → 以"软化阈值 + 小时快照 + 门户可视 + 每日清理"替代

cron(/etc/cron.d/dsh-maintenance 已更新):每小时 05 分 storage-report;04:10 ws-cleanup(2048MB/90d);04:20 session-gc(1024MB/365d);04:40 purge-trash(30d)。

验证:ci.sh 通过;storage-report 首跑成功(admin ws=0 sessions=0.7M trash=17.6M;guest ws=0.5M sessions=1.1M trash=2.0M);服务重启后 /api/admin/storage 未登录返回 401(证明路由生效且受保护);admin.html 已含用量区块。

提交:代码 b048dd9(3f6481b..b048dd9);文档 fe87fc5(3f40de8..fe87fc5),双端 61/61。 踩坑:git commit -m "多行消息含 '+' 开头行" 会被 shell 拆成 pathspec → 改用 git commit -F <消息文件>(已验证有效)。

备份修复 + 全量同步确认(09:43-09:50)

核查发现(真问题):/opt/dsh/backup.sh 无执行权限(-rw-------)且未接任何调度 → 最后一次全量备份停在 2026-09-08(之后 3 天含域名迁移/插件/清理策略等大改动,全部无备份)。 修复与加固:

  • chmod +x;脚本加固为 ① sqlite3 .backup 一致性快照(避免只拷到 -wal 导致库不一致)② 归档纳入 opt/dsh/artifacts + opt/dshs/scripts + etc/cron.d/dsh-* + nftables-dsh-egress.nft + www/server/panel/vhost/nginx(可重建)
  • 调度 /etc/cron.d/dsh-backup:每周日 05:00 全量 + 05:30 清超 60 天旧备份;日志 /var/log/dsh-backup.log
  • 入库副本 scripts/backup-platform.sh
  • 实测:首跑全量 8.7MB / 6919 条目(users 6858 + DB + env + artifacts + scripts 20 + cron 2 + nginx vhost 22 + systemd 3)+ DB 快照 7.8K;恢复演练:解出 DB → select count(*) from users = 2 行 ✓
  • 文档:02-运维手册 附录 C.8 备份与恢复(含恢复命令与演练证据)+ DEPLOY 6.5 定时维护(三张 cron 表汇总)

同步状态确认(三端一致):

  • 文档库双端 61/61 一致(md5 对账);文档 git HEAD a77de58 已推送
  • 代码:服务器 709b359(工作树干净)= 本机镜像 709b359 = origin/master 709b359 ✓
  • 发现并修复:本机镜像曾出现 57 项未提交删除(src/** 45 + scripts/** 12,本地误删;服务器与远端完好)→ git restore . 恢复,现 0 项未提交
  • 路线图刷新:清理陈旧行(picker v3 / H2 写保护 / 备份核查 → 已完成);修复待办表空行断表

提交:代码 709b359;文档 a77de58。

A/B 同步(并行会话成果回收)+ C 待确认清单(21:05-21:35)

背景:21:05 拉状态发现另一位会话在过去 11 小时并行推进到档案 52(代码 3b82d31,29 个提交),文档库服务器侧 88 文件 / 本机 61;且我上午留下的两个 bug 已被对方修复。

A 完成:文档库反向拉取(服务器 → 本机 → Gitea)

  • 按内容比对(mtime 不可靠:scp 会改服务器 mtime)确定清单 → 从 /opt/dsh/docs 拉取 42 个文件(27 新增「档案 29-52」+ 15 更新)+ 2 个 .bak → 本机文件数 61 → 88
  • 抽查确认我 09:00 的内容未丢:02 附录 C.5/C.6/C.7/C.8、01 机制索引、06 同步规则、档案 18 主线说明、DEPLOY 6.5 定时维护 ✓
  • 合并 03-路线图与待办.md:对方基于较早快照重写,丢了我加的条目(14~22 已完成、C5 关闭结论、备份核查)→ 已补回 7 条并保留对方新决策(MCN 改造暂缓、白名单源码安装)
  • 期间踩坑:tar -T - 走 stdin 无效(45 字节空包);列表文件是 CRLF(python open(...,'w') 默认翻译)→ 服务器侧 tr -d "\r" 解决
  • 结果:双端 88/88 一致 ✅;提交推送 a77de58..e3380d9

B 完成:代码同步(服务器 → 本机镜像 → Gitea)

  • 服务器 git bundle master --not 709b359(110KB / 29 个提交)→ 本机镜像 merge → push
  • 结果:三端一致 3b82d31(服务器工作树 0 未提交)✅

对方(并行会话)已修复的、我引入的缺陷:

  • 档案 35:ws-cleanup.cjs T1 规则无条件删除 ws 顶层 *.tgz → 误删平台自己的 bundle 包(.keep 豁免不了 T1)→ 已修(70302e6)
  • 档案 52:新用户实例无法启动 —— 根因之一是我的 stripPlatformSegments():profile 模板是「3 行注释 + []」,剥段后 body ≠ '[]' → 判定失效 → 写出非法 YAML([] + 平台段) → 实例崩溃循环;另两处为 ro-bind 遮蔽 /usr、以及 new user 缺少插件(正是 071d678a 那个用户)
  • 另有档案 30(孤儿实例清理)、43(root 污染用户 ws 与属主自愈)、46(共享 jq/ripgrep/ffmpeg)等,覆盖了我此前列的待办

C 待用户详细确认(清单已按对方进展更新):MCN 改造(对方已暂缓)、档案 16 阶段 3/4(疑似被档案 41 覆盖)、Cookie 域、熔断实测、档案 21 后续、白名单源码安装(对方新列 P2)

协作建议:两会话并行改同一批文件已造成一次内容丢失(我已在 03-路线图 合并修复);后续应约「改前先拉取 / 分工到目录」。

P2 两项处理完毕(21:38-21:52)

P2-1 熔断 live 实测 → ✅ 完成(无需人为 kill)

  • 实证来源:真实故障用户(档案 52 的 071d678a)崩溃循环日志
  • 退避序列 attempt1→4:delayMs 1000 → 2000 → 4000 → 8000(base×2);restartsInWindow 1→4
  • 熔断:第 5 次触发 crash-loop-circuit-open(windowMs=600000、restartsInWindow=5/max=5),当日 2 次开断
  • 结论:退避 + 计数 + 开断 + 结构化日志四要素齐全 → 实测通过;归档到 档案 20 附录「熔断 live 实测记录」

P2-2 白名单源码安装支持 → ✅ 评估关闭(不做)

  • 档案 29 §六.1 原本就写明代价(以 root 跑第三方构建脚本:慢/需工具链/易失败/供应链风险)并"当前决策:不做"
  • 本轮正式定稿:不做。理由 = 与「平台不执行第三方构建脚本」红线冲突(供应链 + 可复现性 + 性能)
  • 替代路径:admin 本地构建 → 走「上传 tgz」通道(已含 P0/P1 扫描,档案 19 §C6)✓ 覆盖源码类真实需求
  • 打开条件清单(沙箱构建 / --no-... ignore-scripts / 仅 admin / 审计 / 全量扫描)已作为升级条件写入 档案 29 §七

同步与踩坑

  • 文档库:修正后 双端 88/88 一致 ✅;提交推送 e3380d9、ea431b1
  • 踩坑(Windows 非法文件名):服务器侧编辑器备份 INDEX.md.bak-20260911111003.(结尾点号)在 Windows 上无法创建 → tar 解包留下"幽灵文件",导致 git add -A 报 unable to index file → 处置:.gitignore 加 *.bak-*/*.bak(避免 -A 触碰)+ 用 \?\ 扩展路径把该文件读出(37550 B)并回传服务器恢复双端对称;删除该文件因只读属性 + 尾点号在 Windows 上无法完成(os.chmod(S_IWRITE) 也失败) → 教训:服务器侧生成备份文件时不要用结尾点号(跨 Windows 不兼容)

核查:档案 05 PoC-2 三项待办是否还有必要(21:55-22:15)

用户问「管理类插件化 / 插件↔门户鉴权令牌 / portal_ping 端到端 还有必要做吗」。只读核查(10 次探针),结论如下。

① 管理类插件化(KEY/用户管理搬进会话窗口)→ 无必要

  • 门户 portal.html 已是完整管理门户,导航含:服务管理 / 密钥管理 / 用户管理 / 技能管理 / 插件管理 / 运行环境
  • admin 在会话内已可一键直达:portal-entry v0.4.3 提供「平台管理 / 打开管理台」(整页跳转,其 client 注释写明"浏览器侧跨子域调用门户会被拦截")
  • 端点数:KEY = GET/POST /api/me/keys、/api/me/keys/:id/select、DELETE /api/me/keys/:id(requireAdmin);用户 = /api/admin/users*(ADMIN);插件投放 = /api/plugins/business*(ADMIN)
  • 反向论点:把 admin 能力调用搬进实例子域,会扩大 admin 权限执行面(该域内用户可装插件、agent 可写文件)→ 与档案 39「收窄可见面/网络面」方向相反

② 插件↔门户鉴权令牌 → 已完成(但不是用令牌实现的)→ 可关闭

  • 实际机制 = 共享会话 Cookie(Domain=.alotbuy.com,HttpOnly)+ 门户 CORS 白名单
    • src/web/server.ts 有 isAllowedOrigin(origin, baseDomain):允许 baseDomain 及其子域,回 Access-Control-Allow-Origin + Allow-Credentials: true,并处理 OPTIONS;注释原文说明就是为「功能插件启停 section 在 <user>.alotbuy.com 上调门户 API」而加
    • business-plugins v0.2.1 client 用 portalHost()(去掉实例子域 label)+ credentials:"include" 调 /api/plugins/mine,已在生产跑通(档案 36/37)
  • 结论:再做独立令牌 = 重复建设,且无额外安全收益(同一会话、同一信任边界)

③ portal_ping 端到端 → 原验收项已失效(安全收紧导致)

  • 工具仍在:portal-entry v0.4.3 host 面 defineTool({name:"portal_ping"}) + ctx.tools.register(boot 时注册)
  • 但其实现是 fetch(process.env.DSHS_URL ?? "http://127.0.0.1:3080")
  • 档案 39 已封 127.0.0.0/8(实测 loopback :3080 BLOCKED,"实例进程自身不需要任何 loopback 连接")→ 原验收动作现在注定失败
  • 如实补充:该机制(实例内 host 插件给 agent 注册工具)目前仍无生产验证——business-plugins 只注册 settings section(tools.register = 0),workspace-scoped-picker 不注册工具 → 若要保留这条验证,应换用不依赖 loopback 的探针(属重新设计,不是完成原待办)

顺带发现(待核):/api/skills/mine*(10 条中的 6 条)已是 auth 守卫(普通用户可用),但现存三个 client 插件都不含「技能」UI → 档案 16 阶段 3 可能只落地了后端;门户「技能管理」偏 admin 共享技能。→ 可作为 C2 的核对入口。

未改动任何文件(纯只读核查,等用户裁定是否在路线图关闭这三项)。