381 lines
34 KiB
Markdown
381 lines
34 KiB
Markdown
# 工作日志 · 2026-09(第 25 片)
|
||||
|
|
|
|||
|
|
> ⚠️ **本目录日志已按【月】分片**(2026-09-15 用户定,单文件 ≤50 KB):`2026-09.md` / `2026-09-下.md` / … / `2026-09-下24.md`
|
|||
|
|
> **写入约定**:一律 append 到**当月最后一片**;该片超 50 KB ⇒ 新建 `2026-09-下N.md`;⛔ **不要再按日新建 `2026-09-DD.md`**。
|
|||
|
|
> **覆盖来源**:2026-09-15.md(续)
|
|||
|
|
> **上一片**:`2026-09-下23.md` **下一片**:(无,本片为末片)
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
## 17:00–17:20 日志改「按月分片」+ 钩子「从未生效」真因闭环
|
|||
|
|
|
|||
|
|
### 用户两条指令
|
|||
|
|
1. **日志改按月分片**(在我给的 A 维持 / B 按 50 KB 切 / C 按月分片 里选了 **C**)
|
|||
|
|
2. **已重启** ⇒ 做 `UserPromptSubmit` 探针定论
|
|||
|
|
|
|||
|
|
### ① 日志按月分片(8 个日文件 → 24 片,逐字节无损)
|
|||
|
|
|
|||
|
|
- 迁移脚本:`.workbuddy/tmp/shard-memory-by-month.py`(幂等;**先备份 + 校验** ⇒ 只在 `## ` 章节边界切 ⇒ **回读逐字节比对**)
|
|||
|
|
- 结果:`2026-09-08..15.md`(1,101,184 B)→ `2026-09.md` + `2026-09-下.md` … `-下23.md`,**24 片**,各片 ≤49.9 KB
|
|||
|
|
- **独立复核**:各片去头后按真实片序拼接,`sha256 = d162d194…` **与原件拼接完全一致**(1,101,184 B,零丢失)
|
|||
|
|
- 每片头部带:**系列指针(上一片 / 下一片)** + **覆盖来源**(原日文件)⇒ 旧引用 `2026-09-12.md:1110` 这类可按"覆盖来源"行定位
|
|||
|
|
- 备份:`.workbuddy/tmp/mem-bak-20260915_171005/`(8 个原日文件)
|
|||
|
|
- ⚠️ 踩到两个**自己的校验 bug**(都是排序):① `sorted(glob)` 是字典序 ⇒ `-下10` 排在 `-下2` 前;② 我的 `key()` 把 `-下.md` 排到了首片前。**同一晚两次**栽在"按名字排序"上 —— 片序必须显式定义(首片=0 / `-下`=1 / `-下N`=N)
|
|||
|
|
- ⚠️ 首版迁移脚本有个**真会丢字节**的 bug:各片 `"\n".join()` 会丢掉**片与片之间那个换行** ⇒ 已补(末片外每片补一个 `\n`)
|
|||
|
|
|
|||
|
|
### ② 钩子 `UserPromptSubmit`「从未被调用」—— 真因是 `-E`,不是宿主不支持
|
|||
|
|
|
|||
|
|
**排查链路(有两次误判,都靠更硬的判据纠正)**
|
|||
|
|
|
|||
|
|
| 步 | 观察 | 判定 |
|
|||
|
|
|---|---|---|
|
|||
|
|
| 1 | `.workbuddy/stop-dialog-guard.log` **不存在**,全盘也搜不到 | 疑似"宿主不调用该事件" |
|
|||
|
|
| 2 | 但 `lock-hook.log` 正常 ⇒ 钩子机制本身能落盘 | 推翻上一步的笼统结论 |
|
|||
|
|
| 3 | 对照两者安装形态:`lock-guard-hook.py` **无 flag**;`stop-dialog-guard.py` 是 **`-S -E`** | 差异点锁定 |
|
|||
|
|
| 4 | 实测 `-S -E` ⇒ `stdin.encoding = **gbk**`、`utf8_mode=0`;`-S`(无 `-E`)⇒ `utf-8`、`utf8_mode=1` | **`-E` 屏蔽了 `PYTHONUTF8=1` / `PYTHONIOENCODING=utf-8`** |
|
|||
|
|
| 5 | 中文 payload 在 `-E` 下:`json.loads` 抛 ValueError ⇒ 原代码 `except ValueError: return` | **静默空转** —— 钩子一直在被调用,只是每次都悄悄退出 |
|
|||
|
|
|
|||
|
|
**修法(`3e170f9`)**:脚本加固到**与 flag / locale 解耦**
|
|||
|
|
- `_read_stdin_text()`:`sys.stdin.buffer.read().decode('utf-8','replace')`
|
|||
|
|
- `_emit()`:`sys.stdout.buffer.write(json.dumps(…, ensure_ascii=False).encode('utf-8'))`(原 `sys.stdout.write` 在 cp936 下遇中文/`⛔` 会 UnicodeEncodeError)
|
|||
|
|
- `_entry_log()`:**入口即留痕**(parse-fail / 未进作用域 / 空 stdin 三种静默情形都记)—— 根治"日志缺失时无法区分『没被调用』与『被静默 return』"
|
|||
|
|
- `WS_FALLBACK`:host 未给 cwd 时按「脚本位置上溯三级」兜底
|
|||
|
|
- docstring 安装处加 ⛔ **不要给本脚本加 `-E`**
|
|||
|
|
|
|||
|
|
**实证**:修复前 `entry|event=(parse-fail)` → 修复后 `entry|event=UserPromptSubmit|cwd=E:\ProgramData\AI技能\aliyun-dsh-server|in_scope=True` + `invoked(user-prompt)|mode=probe`;4 个用例均能区分并留痕
|
|||
|
|
|
|||
|
|
**同款风险(未改,只报告)**:`lock-guard-hook.py` 当前可用(无 `-E`),但同样依赖环境里的 `PYTHONUTF8=1` ⇒ 该变量一旦消失会**静默 fail-open**(守卫失效)
|
|||
|
|
|
|||
|
|
### ③ 记忆整编(含一次"整编差点丢内容"的教训)
|
|||
|
|
|
|||
|
|
- `MEMORY.md` 已涨到 **13,170 B**(另发现有**别的写入者**加过一节),超 12 KB 额度
|
|||
|
|
- 改法:**先抽 142 个"事实 token"作回扫基线** ⇒ 整编 ⇒ **回扫**(缺 1 个,且只是排版片段)
|
|||
|
|
- 结构性去重:把它与根 `CODEBUDDY.md §2` **重复**的「去查表」迁到 `PLAYBOOK §11`,MEMORY 只留指针
|
|||
|
|
- 新增两条铁律:**日志按月分片约定** + **Python 编码铁律**(详版 PLAYBOOK §10)
|
|||
|
|
- 终值 **12,255 B ≤ 12 KB** ✓(备份:`.workbuddy/tmp/MEMORY.md.bak-pre-shard-20260915`)
|
|||
|
|
- 💡 **本晚最有价值的元教训**:**整编记忆必须"先抽 token 再改写,改完回扫"** —— 纯靠记忆判断"我没删东西"是不可信的(本晚 `pkill` 例证就被我压掉过,是回扫抓出来的)
|
|||
|
|
|
|||
|
|
### 提交 / 锁
|
|||
|
|
- 提交 `3e170f9`(脚本修复,1 文件)|镜像已推(md5 双端一致,600 root:root,备份 TS `20260915_170920`)
|
|||
|
|
- 锁:`--claim-exec "钩子留痕-1703"` → 全程 → `--release-exec`(均按硬判据校验:不接管道查退出码 + 复读 OWNER 断言)
|
|||
|
|
|
|||
|
|
## 17:17–17:25 上抛「取舍筛」(用户明令)+ 钩子定论切 inject + 锁守卫加固
|
|||
|
|
|
|||
|
|
### 用户原话
|
|||
|
|
「**需要我确认的方案需要说明优点和缺点,现在没法判断,假如只有优点或只有缺点那不需要我判断**」
|
|||
|
|
|
|||
|
|
⇒ 给的是一个**上抛筛选器 + 呈现要求**(此前只写"可感知差别",用户据此**没法判断**):
|
|||
|
|
- **筛选器**:候选各写「优点 / 缺点」—— 某个**只有优点**(明显更优)或**只有缺点** ⇒ **不需要用户判断**,自己拍掉再陈述;
|
|||
|
|
只有**各有优有劣、客观标准分不出高下**(真取舍)才上抛
|
|||
|
|
- **呈现**:上抛时**逐项列出优点与缺点**(只写"差别在哪"不算)
|
|||
|
|
|
|||
|
|
### 全载体同步(严格按 `dsh-change-workflow §5` 的「跨载体规则必做全载体扫描」清单执行)
|
|||
|
|
`grep` 命中 **8 处**,全部改齐(这是该 SOP 立起来的当天第一次真用,命中即全改,没漏):
|
|||
|
|
|
|||
|
|
| 载体 | 改动 |
|
|||
|
|
|---|---|
|
|||
|
|
| `dsh-feature-first` **1.6.0** | §5.1 骨架改为「每个候选写 优点|缺点 + 我的倾向」;§5.3 四条→**五条铁律**(新增铁律 5 = 取舍筛);§5.4 硬约束 七→**八条**、提问骨架加"必须写优缺点"、反模式 十一→**十二条**;§6 自检加第 13 条 |
|
|||
|
|
| 项目根 `CODEBUDDY.md §1` | **判据行**加「上抛门槛 = 真取舍」;回话前自检第 ③ 问改为「候选之间是**真取舍**吗」;📌 口径行;排版指针行(八条 / 十二条) |
|
|||
|
|
| 用户级 `~/.workbuddy/MEMORY.md` | 跨项目指针同步 |
|
|||
|
|
| `scripts/stop-dialog-guard.py` | `REASON` / `CONTEXT` 加「候选只有优点/只有缺点 ⇒ 自己拍掉、不要问」 |
|
|||
|
|
| `dsh-decision-method` **2.7.3** | `references/素材库-U` 追加「**修正 2**」,写清筛选器 + 呈现两条 |
|
|||
|
|
| `dsh-env-bootstrap` **1.0.3** | `references/常驻规则-快照` 由 `CODEBUDDY.md` **重生成**(`--check` ✅ 关键规则齐备) |
|
|||
|
|
| `README.md` | 模块表 3 行版本号 |
|
|||
|
|
|
|||
|
|
### 钩子「探针定论」—— 结论:**机制是通的,之前是我的编码 bug**
|
|||
|
|
|
|||
|
|
用户这条消息触发了钩子,日志(`.workbuddy/stop-dialog-guard.log`)首次出现:
|
|||
|
|
```
|
|||
|
|
17:17:00 entry|event=UserPromptSubmit|cwd=e:\ProgramData\AI技能liyun-dsh-server|in_scope=True|stdin_len=501
|
|||
|
|
17:17:00 invoked(user-prompt)|mode=probe|上轮收尾=征询句:False|…
|
|||
|
|
```
|
|||
|
|
- **宿主确实在调用 `UserPromptSubmit`** ⇒ 之前"全盘无日志"纯粹是 `-E` → cp936 → `json.loads` 失败 → **静默 return**
|
|||
|
|
- `in_scope=True`(transcript 路径形如 `\e-ProgramData-AI技能-aliyun-dsh-server\<uuid>.jsonl`,含 `aliyun-dsh-server`)⇒ 作用域判据有效
|
|||
|
|
- 上轮末行是陈述句 ⇒ 正确判为"非征询"
|
|||
|
|
⇒ **已按计划切 `inject`**(`echo inject > .workbuddy/stop-guard-mode`),此后命中时会真注入自检上下文
|
|||
|
|
|
|||
|
|
### 锁守卫加固(按新规则自检 = **只有优点** ⇒ 自决,未上抛)
|
|||
|
|
`lock-guard-hook.py`:走 `sys.stdin.buffer` 显式 UTF-8 + **解析失败留痕**。
|
|||
|
|
原来 `except: return` 是**静默 fail-open** —— 锁守卫会不再拦人却没有任何痕迹(比"拦错"更危险)。
|
|||
|
|
**实测**:垃圾 payload ⇒ 退出码 0(仍放行,方向正确)**且留下** `payload-unparsable JSONDecodeError: Expecting value…`;加 `-S -E` 也能解析(隐患消除)。
|
|||
|
|
|
|||
|
|
### 验收 / 提交 / 锁
|
|||
|
|
- 4 个技能 9 个文件两副本 md5 全一致(CR=0);镜像 **8 个文件**就位后 md5 双端一致(600 root:root,备份 TS `20260915_172214`)
|
|||
|
|
- 七件套全绿;`grep 优点/缺点` 回扫只命中已改处;`resident-rules.py --check` ✅
|
|||
|
|
- ⚠️ `shrink-guard` 曾报 `MEMORY.md 90 → 59 行(-34%)` ⇒ **是我今晚的整编**(系统要求 + 收编方向)。
|
|||
|
|
已用**备份 + 142-token 回扫**确认无损;按脚本提示**刷新基线**,但**刻意不加 `--allow-shrink` 白名单**
|
|||
|
|
—— 这文件正是当初被并行会话抹行才催生这条守卫,不能给它免检
|
|||
|
|
- 提交 `0c88b4b`(8 文件)|**锁已释放**(`--release-exec` + 目录断言)
|
|||
|
|
|
|||
|
|
### 本晚第三次踩同一个坑(必须记)
|
|||
|
|
**bash heredoc / `-c` 内联里的 `\\` 会被 MSYS 改成 `/` 或吞掉** ⇒ 本轮版本 bump 脚本因此在 print 处崩掉、
|
|||
|
|
**只跑完第 1 个文件**(`env-bootstrap` 漏 bump,靠核对才发现)。
|
|||
|
|
⇒ **凡含 Windows 路径 / 反斜杠的 Python 一律用 Write 落成 .py 再跑**,别走 heredoc。
|
|||
|
|
|
|||
|
|
## 17:25–17:32 待确认项的候选必须「竖排成段」
|
|||
|
|
|
|||
|
|
### 用户原话
|
|||
|
|
「**每个需要我决策的问题的潜在解决方案 A B C 也按照段落式排版,别横着排列**」
|
|||
|
|
|
|||
|
|
**触发它的正是我自己的输出** —— 前两轮我把三个候选写成一行并列:
|
|||
|
|
`选项:A 维持现状 · B 按 50 KB 切分 · C 改成按月分片` ⇒ 这就是"横着排列"。
|
|||
|
|
|
|||
|
|
### 规则(补进 §5.3 铁律 5 的 ④)
|
|||
|
|
- 候选 A / B / C **各占一行(各自成段)**
|
|||
|
|
- ⛔ 不许 `A:… · B:…` 一行横排
|
|||
|
|
- ⛔ 不许把候选做成**表格的列**(表格是横向对比,与"段落式"正相反)
|
|||
|
|
- 段内「优点…;缺点…」连写即可,**不必每个字段再拆行** —— 否则 3 候选 × 3 行 = 9 行,会撞 §5.4 硬约束 3「每节 ≤7 行」
|
|||
|
|
|
|||
|
|
### 全载体同步(grep 命中 9 行 / 4 文件,全改齐)
|
|||
|
|
| 载体 | 改动 |
|
|||
|
|
|---|---|
|
|||
|
|
| `dsh-feature-first` **1.7.0** | §5.1 骨架改**竖排段落式**(`**A** —— 优点:…;缺点:…` 各占一行)+ 加 📐 段;§5.3 铁律 5 加 ④;§5.4 硬约束 八→**九条**;提问骨架注明"候选各占一段";反模式 十二→**十三条**;§6 自检加第 14 条 |
|
|||
|
|
| 项目根 `CODEBUDDY.md §1` | 📌 行 + 排版指针行(九条 / 十三条) |
|
|||
|
|
| 用户级 `~/.workbuddy/MEMORY.md` | 跨项目指针 |
|
|||
|
|
| `scripts/stop-dialog-guard.py` | `REASON` / `CONTEXT` 加"竖排成段"判据 |
|
|||
|
|
| `dsh-decision-method` **2.7.4** | 素材库-U 追加「修正 3」 |
|
|||
|
|
| `dsh-env-bootstrap` **1.0.4** | 常驻规则快照重生成(`--check` ✅) |
|
|||
|
|
| `README.md` | 模块表三行版本号 |
|
|||
|
|
|
|||
|
|
### ⚠️ 我犯并自查出的一处内容损失(值得单记)
|
|||
|
|
编辑 `素材库-U` 时,我把「**修正 2**」整段当作 `old_string`,而 `new_string` **只写了「修正 3」**
|
|||
|
|
⇒ **修正 2 被整段替换掉**(差一点就静默丢失一条用户明令)。
|
|||
|
|
发现靠的是**收尾核对**(`grep -c "^- \*\*修正 2"` 只有 1 条、且与 git HEAD 逐行比对)。
|
|||
|
|
已从 `git show HEAD:…` 恢复,并**逐字节 diff 确认与已提交版一致**。
|
|||
|
|
|
|||
|
|
**教训**:**`Edit` 是"替换"不是"追加"** —— 要在既有条目**之后追一条新条目**时,
|
|||
|
|
`old_string` 必须**包含旧条目**、`new_string` 必须**同时含新旧两条**。
|
|||
|
|
(同族:本晚已踩 3 次的「bash heredoc 吞反斜杠」。两者的共同点 = **工具语义与直觉不符时,
|
|||
|
|
必须用"改完取证"兜住**,而不是靠记住。)
|
|||
|
|
|
|||
|
|
### 验收 / 提交 / 锁
|
|||
|
|
- 4 技能 9 文件两副本 md5 全一致(CR=0);镜像 7 文件 md5 双端一致(600 root:root,备份 TS `20260915_172924`)
|
|||
|
|
- 七件套全绿;`grep 竖排成段` 只命中已改的 4 个文件;`resident-rules.py --check` ✅
|
|||
|
|
- 提交 `bb9ebe9`(7 文件)|**锁已释放**(`--release-exec` + 目录断言)
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 调研:dsh 单机运行的官方开源构成(23:4x · 只读,未动任何文件)
|
|||
|
|
|
|||
|
|
**触发**:用户问「dsh 单机运行有哪些开源项目」+「有没有官方的开源项目」。
|
|||
|
|
|
|||
|
|
**官方(`deepseek-ai` 组织)** —— 结论:**官方就是主力**。
|
|||
|
|
- 主仓 `deepseek-ai/deepseek-harness`(TypeScript monorepo,**MIT**,"Everything is a Plugin");
|
|||
|
|
顶层 = `apps/ packages/ native/ python/(Python SDK) docs/ benchmarks/ scripts/ patches/ snapshots/`;版本已到 `0.1.6-alpha.1`(线上我们跑 `0.1.5-rc.1`)。
|
|||
|
|
- **npm 上 `@deepseek-ai/*` 共 294 个包**(分页枚举到 `from=500` 归零 ⇒ 该数完整)。
|
|||
|
|
分布:`client-ui-* 45 | session-* 25 | tool-* 24 | 沙箱/执行器 12 | client-* 12 | MCP/ACP/SDK 11 | web* 9 | 存储·设置 9 | host-* 9 | node-addon-* 8 | subagent* 8 | cordis+schemastery 8 | llm* 6 | api-* 6 | command* 4 | util-* 4 | skill* 3 | 其他 91`
|
|||
|
|
`@deepseek-ai/dsh` 自身直接依赖里就有 **69 个官方包**。
|
|||
|
|
- 官方维护的**指南**仓(非工具本体):`deepseek-ai/awesome-deepseek-agent`(22 篇接入指南)。
|
|||
|
|
- ⚠️ **许可证不一致**:`@deepseek-ai/dsh` = MIT,但 **`@deepseek-ai/dsh-base` = BSD-3-Clause** ⇒ 开源导出对账时⛔ 别一律当 MIT。
|
|||
|
|
|
|||
|
|
**官方七种单机形态(profile bundle)**:`dsh`(CLI)|`dsh-web-app`(浏览器界面)|`dsh-headless`(无宿主一次性)|`dsh-sdk-minimal`(最小独立 SDK)|`dsh-sdk-app`(stdio JSON-RPC)|`dsh-acp-app`(ACP 自动化)|`dsh-base`(所有 profile 的第一 patch 层)。
|
|||
|
|
|
|||
|
|
**官方单机跑法**:`git clone …/deepseek-harness.git` → `pnpm install` → `pnpm run build` → `pnpm dsh web` → `127.0.0.1:3080`。
|
|||
|
|
|
|||
|
|
**第三方生态(非官方,GitHub topic `dsh-plugin` 头部)**:
|
|||
|
|
`nexu-io/open-design` 96.3k | `ruvnet/ruflo` 72.5k | `volcengine/OpenViking` 37.4k | `esengine/DeepSeek-Reasonix` 35.6k | `anywhere-labs/dsh-desktop` 26.8k | `awesome-dsh-plugin/awesome-dsh-plugin` **15.8k(收录 3,627 条插件)** | `walkinglabs/learn-harness-engineering` 15.2k | `EverMind-AI/EverOS` 13k | `MemTensor/MemOS` 11.3k | `YaoApp/yao` 8k | `zhu1090093659/dsh-web` 7.6k。
|
|||
|
|
|
|||
|
|
**判定**:官方**只出单机形态**;多租户托管(门户 + bwrap + 独立 uid + scope + Manager/Worker)**没有官方开源对应物** ⇒ 那部分是我们自造的增量。
|
|||
|
|
**可复用取证命令**:`registry.npmjs.org/-/v1/search?text=@deepseek-ai/&size=250&from=N` 分页 + 前缀过滤(⚠️ npm 的 `scope:` 语法不被支持,会退化成全文搜 ⇒ 必须自己按前缀过滤);`registry.npmjs.org/@deepseek-ai%2Fdsh/latest` 取官方依赖全集。
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 评估:能不能「部署在客户端 / 局域网多人访问」(00:0x · 只读代码,未改动)
|
|||
|
|
|
|||
|
|
**结论:⚠️ 取决于客户机是不是 Linux。** Linux ⇒ 可行(隔离层原样复用);Windows ⇒ 平台能起但**租户隔离整套失效**,多人访问不可接受。
|
|||
|
|
> 🔴 **以上判定有误,已被用户当场纠正两次,结论以本节末尾「§ 更正」为准**(错误原因:只看了我们平台自己的代码,没查 dsh 本体的 Windows 支持)。
|
|||
|
|
|
|||
|
|
**代码级事实(本轮实测,后续复用)**:
|
|||
|
|
- `DSHS_ISOLATION_MODE` **只有 `soft` / `account` 两档**,**默认 `soft`**(`src/config.ts:180`);`cli.ts:39` 自己写着 `"account" … (Linux, needs root)`。
|
|||
|
|
- `soft` 分支 = 裸 `spawn(command, args)`(`orchestrator.ts:663`)⇒ **零 Linux 依赖、任何系统能跑,但零隔离**。
|
|||
|
|
- `account` 分支 = `systemd-run --scope -p Memory… -- bwrap … -- setpriv --reuid …`(`orchestrator.ts:853-861`)⇒ **三件套全是 Linux 专属**。
|
|||
|
|
- `portGuard`(nft 防火墙)**默认 false**(`DSHS_PORT_GUARD`,`config.ts:323`);非 Linux 上启用会 fail loud(`firewall.ts:42`)⇒ 不启用即可跨平台。
|
|||
|
|
- `dataRoot` 默认 `~/.dshs`(**不是**硬编码 `/var/lib`);`dbPath` = SQLite,`dbUrl` = PG 可选 ⇒ **存储层跨平台**。
|
|||
|
|
- 子域路由 = `DSHS_BASE_DOMAIN` + Host 头(`subdomainForUser`,`routes/dsh.ts:59`)⇒ **域名不硬编码,可换内网域名**。
|
|||
|
|
- `--secure-cookies` 是**可选开关**(默认关)⇒ 局域网 http 也能登录。
|
|||
|
|
- ⚠️ **平台侧 Windows 隔离代码 = 0 行**(全仓 grep `win32|windows` 只命中 `fs/workspace.ts` 一句注释)。
|
|||
|
|
- ✅ **上游 dsh 本体本身支持 Windows**:`dsh-pwsh-local` / `dsh-pwsh-sandbox` / `dsh-tool-pwsh` / `dsh-sandbox-windows-acl`(restricted-token 写限制)/ `dsh-win32-process`(Job Object 资源控制)⇒ "Windows 上做隔离"有原料,但要**新写后端**(独立账户 + 文件 ACL + Job Object),替代 bwrap/uid/cgroup。
|
|||
|
|
|
|||
|
|
**网络拓扑(好消息)**:实例**只监听 `127.0.0.1`**,局域网只需暴露**平台一个端口**,由平台按 Host 头分流 ⇒ 不需要给每个实例开端口。
|
|||
|
|
|
|||
|
|
**要落地的三块**:① 入口(内网 DNS 泛解析 + 泛域名证书,替代 CF/LE)② 安装(现在是一堆 `.sh` + 宝塔 + 手工 provisioning ⇒ 要收敛成一键包)③ 隔离(Linux 复用;Windows 需新写后端)。
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
### § 更正:**Windows 上 dsh 原生可用,连 Linux 都不用套**(2026-09-16 00:4x)
|
|||
|
|
|
|||
|
|
**触发**:用户连纠两次 —— ①「软件里面套个 linux 环境不就行了,官方原生的 dsh 也能在客户端部署运行」②「**linux 环境都不用套,根本不需要**」。**用户是对的,上面那份判定作废。**
|
|||
|
|
|
|||
|
|
**官方证据(全部 `Status: implemented`,取自 `deepseek-ai/deepseek-harness` 的 **master** 分支 — 注意该仓默认分支是 master 不是 main)**:
|
|||
|
|
|
|||
|
|
1. **Windows 默认走 PowerShell 栈**(`.agents/notes/archived/feature/2026-08-01-windows-pwsh-default.zh.md`)原文:
|
|||
|
|
> 启动交付 profile(`dsh web`、`dsh --profile headless`、一次性任务)的 **Windows 主机默认获得 PowerShell 栈**;POSIX 主机不变。
|
|||
|
|
> 受限 pwsh 栈运行在 **ACL 受限令牌 runner** 之上,**权限面与 POSIX 完全一致**。
|
|||
|
|
机制:同一份 base patch 按 `process.platform === 'win32'` 门控两套 shell 栈(`bash-sandbox`/`tool-bash` 在 win32 `disabled`,孪生行 `pwsh-sandbox`/`tool-pwsh` 仅在 win32 挂载)⇒ 每个宿主恰好挂一套。
|
|||
|
|
|
|||
|
|
2. **TUI 三平台对等支持,且官方明确否决 WSL/Cygwin**(`2026-07-20-windows-tui-support.zh.md`)原文:
|
|||
|
|
> **DeepSeek Harness 不增加平台拒绝逻辑,也不采用功能受限的 Windows 模式。**
|
|||
|
|
其「曾考虑的替代方案」里写死:**「通过 MSYS、Cygwin 或 WSL 运行 POSIX 驱动:不予采纳,因为这会测试兼容环境,而不是用户实际运行的原生 Windows 控制台路径。」**
|
|||
|
|
|
|||
|
|
3. **原生 Windows CI 是阻断性门禁**(`2026-08-08-native-windows-pull-request-ci.zh.md`):每个 PR 在组织自有 `dsh-windows-2025-16core` 运行器跑 4 个原生作业,其中 `windows-build` 与 `windows-native-tests` **是 `all checks passed` 的依赖项**。
|
|||
|
|
|
|||
|
|
**Windows 原生能力面(与 POSIX 对等)**:
|
|||
|
|
|
|||
|
|
| 面 | Windows 实现 |
|
|||
|
|
|---|---|
|
|||
|
|
| shell 执行器 | `dsh-pwsh-local` + `dsh-pwsh-sandbox`;工具 `dsh-tool-pwsh` / `-persistent` |
|
|||
|
|
| 沙箱 | `dsh-sandbox-windows-acl` —— 受限令牌 + 写限制 SID,**任何 Win32 失败都阻止子进程在不受限下启动**(fail closed) |
|
|||
|
|
| 进程/资源 | `dsh-win32-process` —— Win32 进程、stdio、**Job Object** 原语 |
|
|||
|
|
| 终端 / 文件 | ConPTY(测试用 `node-pty`);`dsh-fs-local/src/win32.ts` |
|
|||
|
|
| 其他 | Python SDK 有 **Windows x64 运行时**;桌面端有 Windows 签名 + 冒烟脚本 |
|
|||
|
|
|
|||
|
|
**⚠️ 官方自认的限制(要记住)**:`sandbox-windows-acl` 的 README 原文说保证是**「部分强制(partial)」**——
|
|||
|
|
> 进程启动会保留 Everyone 访问权限,NTFS 硬链接也可以通过其他路径暴露同一文件
|
|||
|
|
> `WRITE_RESTRICTED` 只交叉检查**写**访问,需要**读侧隔离或网络限制**时,请把此后端与读侧策略**或 AppContainer 能力令牌**配对。
|
|||
|
|
|
|||
|
|
⇒ Windows 沙箱是**写限制**;**跨租户读隔离**要另配(AppContainer / 独立账户 + NTFS ACL)。**但这与 Linux 无关** —— Windows 自带对应机制,不需要 WSL/容器。
|
|||
|
|
|
|||
|
|
**⚠️ 本机安全策略(未来复用别踩)**:`wsl.exe` 在 **Program Blacklist** 里 —— bash 调用直接 `Permission denied` + 「PROGRAM BLOCKED BY SECURITY POLICY」,且明确**禁止换 shell/脚本绕行**。⇒ 验证 Windows 原生能力**不要走 wsl.exe**。
|
|||
|
|
|
|||
|
|
**修正后的判定**:**dsh 本体在 Windows 原生可用、与 Linux 对等(官方原话"权限面与 POSIX 完全一致"),不需要套 Linux。** 我们平台 `soft` 模式纯 Node 也能起;多租户隔离在 Windows 上走 Windows 自己的机制,平台侧这块目前 0 行 —— 是「要做」,不是「做不到」。
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
### 📄 产出:`dsh客户端化部署方案_20260916.md`(00:5x · 规划稿 · 工作区根)
|
|||
|
|
|
|||
|
|
**路径**:`E:\ProgramData\AI技能\aliyun-dsh-server\dsh客户端化部署方案_20260916.md`(与 `集群化改造方案_Manager-Worker_20260914.md` 同级)
|
|||
|
|
**性质**:**只做规划,未动任何代码 / 未改任何服务器**(方案与执行分离)。⛔ **未写进文档库** —— 规划会话对 `04-调整方案/` 只读,且这样可避开抢全局执行锁。
|
|||
|
|
|
|||
|
|
**本轮新增的关键事实(前两轮没有的)**:
|
|||
|
|
- ✅ **平台入口支持"子路径"分流**:`src/supervisor/proxy.ts:546` 有 legacy 代理 **`/u/:slug/dsh/*`**(已认证);`DEFAULT_BASE_DOMAIN = ''` 时 `parseSubdomain`/`subdomainForUser` 返回 null ⇒ **纯 IP 访问形态不需要改任何路由代码**(✅ 修正了上一轮"换 IP 要动核心路由"的判断)。
|
|||
|
|
- ✅ **平台控制面天然跨平台**:`dataRoot` 默认 `~/.dshs`(非硬编码)、SQLite 默认、`--secure-cookies` 可选、`portGuard` 默认 false ⇒ 客户端化的**代码改动量 ≈ 0**,工作量全在「入口配置 + 安装打包 + (可选)Windows 隔离开发」。
|
|||
|
|
- ✅ 已有容器资产:`Dockerfile`(控制面 **非 root uid 65532 + drop ALL + restricted PSA 友好** ⇒ Docker Desktop 能跑)、`Dockerfile.dsh`(每用户镜像)、`deploy/*.yaml` ×11。⚠️ **K8s 后端代码 2026-09-15 已从仓库下线**(备份 `D:\github\_dsh_shenxian_K8s后端备份_20260915\`)。
|
|||
|
|
|
|||
|
|
**方案空间(4 案 + 对比表)**:A Linux 宿主(零改动)|B1 Windows+WSL2(隔离零改动)|B2 Windows 原生(要开发隔离后端,交付体感最好)|C 容器(要恢复 K8s 后端)。
|
|||
|
|
**待决三项(缺客户环境信息,非技术优劣)**:D1 客户机器系统与配置 | D2 隔离要"互不可见"还是"各有工作区" | D3 入口用域名还是纯 IP。
|
|||
|
|
|
|||
|
|
**⚠️ 一条必须说清的区分(易被误读)**:官方否决 WSL,指的是"不让 **dsh 的 TUI** 走 WSL 的 POSIX 路径";**不等于禁止把整个平台部署在 WSL 里** —— 在 WSL 里平台看到的就是一台正常 Linux 机器。
|
|||
|
|
|
|||
|
|
**⚠️ 写作坑(本轮踩,与上一轮同族)**:`Edit` 的 `old_string` = **旧内容**、`new_string` = **旧+新**(追加时)。本轮把两者写反 ⇒ 报 `String to replace not found`。**追加前一律先 Read 末尾取准锚点。**
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
### ✅ 客户端化选型定稿 + B2 路实测(01:0x)
|
|||
|
|
|
|||
|
|
**用户拍板**:`1、B2 2、乙 3、乙` ⇒ 宿主 = **Windows 原生** | 隔离 = **不启用**(各有工作区,小团队互信)| 入口 = **纯内网 IP + 子路径**。
|
|||
|
|
**文档已从 v1 规划稿改写为 `v2 定稿`**(同一路径),核心从"4 案对比"收敛为"B2 单一路径 + 落地清单"。
|
|||
|
|
|
|||
|
|
**🔴 本机实测到的唯一硬阻塞(可复现,Windows + Node 22.22.2)**:
|
|||
|
|
|
|||
|
|
| 尝试 | 结果 |
|
|||
|
|
|---|---|
|
|||
|
|
| `spawn('npm', ['--version'])` ← **平台当前就是这么做**(`orchestrator.ts:663-665`) | ❌ `ENOENT` |
|
|||
|
|
| `spawn('…\\npm.cmd', […])` 显式 .cmd | ❌ `THROW EINVAL`(Node 安全限制:`.cmd` 必须带 shell) |
|
|||
|
|
| `spawn('cmd.exe', ['/c','npm','--version'])` | ✅ `exit=0` |
|
|||
|
|
| `spawn(p, { shell: true })` | ✅ `exit=0` |
|
|||
|
|
|
|||
|
|
⇒ **B2 路唯一必须改代码的地方**:Windows 上实例启动要走 `cmd.exe /c` 或 `shell: true`。
|
|||
|
|
|
|||
|
|
**✅ 实测确认"不用改"的部分**:
|
|||
|
|
- `typeof process.getuid === 'undefined'`(Windows)⇒ `local-user-fs.ts:40` 的 chown/chmod 整块**自动跳过**。
|
|||
|
|
- `--host` **只从命令行读、无 env**(`config.ts:269`)⇒ 服务化时把 `--host 0.0.0.0` 写进启动参数即可。
|
|||
|
|
- `orchestrator.ts:503` 的 `chownSync` 是**无条件调用**,但在 `try/catch` 内 ⇒ 只刷日志、不阻塞(建议顺手加平台判断)。
|
|||
|
|
|
|||
|
|
**交付口径**:B2 路 ≈ **1 处小改(几行) + 配置 + 打包**;最大风险是 **R1「用户之间可互读文件」**(选"乙"的必然代价,须书面告知客户,仅适合互信小团队)。
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
### 🔄 范围再次收窄 → `v3:单机自用`(07:0x)
|
|||
|
|
|
|||
|
|
**用户两条指令(连续)**:
|
|||
|
|
1. 「客户端部署考虑**用户自己使用**就可以了,不用考虑当作服务器、其他人访问什么的」
|
|||
|
|
2. 「**机制都保留,只是不使用**,因为多租户没有域名跑不起来」
|
|||
|
|
|
|||
|
|
⇒ **v2 的"内网多用户"整块作废**;文档已改写为 `v3 单机自用`(同一路径,v1/v2 要点存进附录)。
|
|||
|
|
|
|||
|
|
**v3 的核心(两条原则)**:
|
|||
|
|
- **① 使用者只有他自己** ⇒ 监听回环(**默认就是 `127.0.0.1`,零配置**)、单账号、不做分流、不做隔离、不做服务化。
|
|||
|
|
- **② 平台机制全部保留、只是不启用** —— ⛔ **不为客户端化删改多租户相关代码**(多租户/门户/账号/隔离档位/配额/集群 **一行不动**)。用户的依据 =「多租户需要域名才能跑起来,客户端没有域名」。
|
|||
|
|
|
|||
|
|
**⚠️ 一条应记下的事实(与用户判断有出入,但**不影响结论**)**:平台**内置了无域名降级路径** ——
|
|||
|
|
`src/web/routes/dsh.ts:58-62` `dshUrl()`:`baseDomain` 为空时 `subdomainForUser` 返回 null ⇒ 入口自动回落 **`/u/<userId>/dsh/`** 子路径。
|
|||
|
|
即"没域名跑不起来"在**代码层面有兜底**;但客户端单人场景**不需要依赖它**,故不在方案里改变结论,只作为事实记入文档附录 B。
|
|||
|
|
|
|||
|
|
**v3 的改造清单(只剩两条必做)**:
|
|||
|
|
- **C1**:Windows 子进程启动适配(**唯一必须改代码处**,实测见上一节)
|
|||
|
|
- **C2**:安装包 / 一键安装脚本
|
|||
|
|
(该做:启动器 / 容量参数重算 / 备份清理 Windows 实现;可选:开机自启 / 免登录 / 修两处小点)
|
|||
|
|
|
|||
|
|
**v3 消失的风险**:用户互读文件、无 HTTPS、局域网暴露。**新增须知悉**:Windows 上实例沙箱强度低于 Linux(官方标注 partial)。
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
### 🔍 调研:能否沿用官方 Electron 桌面版「把项目装进去」(07:1x)→ 结论写进文档 §13 附录 D
|
|||
|
|
|
|||
|
|
**用户问**:「看能否沿用官方桌面客户端方案,把这个项目装进去」。
|
|||
|
|
|
|||
|
|
**官方桌面版事实**(`apps/desktop` + `apps/desktop-host`,均 `private: true`,版本 `0.1.6-alpha.1`):
|
|||
|
|
- `@deepseek-ai/dsh-desktop` = Electron 壳;`@deepseek-ai/dsh-desktop-host` = **"Private Node-mode host process"**(依赖里含 `dsh-host-webserver`)。
|
|||
|
|
- **不开监听端口**,用"分帧字节管道"承载 Fetch 与流式响应;`dsh-app://` 服务客户端资源。
|
|||
|
|
- Electron **独占** `$DSH_HOME/profiles/desktop`;CLI 无法启动或修改该 profile。
|
|||
|
|
- profile 的 `dependencies` **只放外部插件**;有独立插件管理窗口(增删改查);用自带 pnpm。
|
|||
|
|
- 与 CLI **共享** `$DSH_HOME` 的 会话/设置/凭据/工作区/存储。
|
|||
|
|
- ⚠️ **GitHub Releases 有 5 个 tag,但 `assets` 全为 0 ⇒ 官方未发布可下载安装包**(要自己用 `package:win:x64` 打)。
|
|||
|
|
|
|||
|
|
**❌ 装不进去的三条硬理由**:
|
|||
|
|
1. **载体只接受 dsh 插件** —— 我们平台是独立 HTTP 服务(路由 + DB + 子进程编排),无"作为 profile 插件加载"的形态。
|
|||
|
|
2. **桌面版无 web server** —— 官方 `Known limitations` 原文:「The Web "Open In..." action is disabled in Desktop because its host plugin requires HTTP routes; **Desktop does not provide a `webServer`**」。而我们的 `business-plugins` **整个是平台的前端**:数据全来自 `portalHost() + /api/...`(`/api/plugins/mine`、`/api/dsh/status`、`/api/skills/mine`、`/api/me/keys`,见 `poc/business-plugins/lib/client.js`)⇒ 装进去就是空壳。
|
|||
|
|
3. **两套编排者争同一份 `$DSH_HOME`** —— 桌面版自己在跑 dsh,平台还要每用户再起实例。
|
|||
|
|
(另注:`business-plugins` 的 host 侧 `apply(_ctx)` 是**空实现**,能力全在 client 侧 ⇒ 进一步坐实"它是平台前端"。)
|
|||
|
|
|
|||
|
|
**✅ 可以沿用的**:**形态**(Electron 桌面应用)+ **打包链**(官方 `apps/desktop/scripts/` 的 electron-builder / NSIS / Windows 签名 / 冒烟脚本)+ **数据互通**(指向同一 `$DSH_HOME`)。
|
|||
|
|
**可行做法** = 自建**薄 Electron 壳**(不复用官方壳代码):启动平台 → 等 `127.0.0.1:3080` 就绪 → 窗口加载 → 退出收子进程。
|
|||
|
|
**三档形态**:① 快捷方式 + 浏览器(最小)② 薄 Electron 壳(中)③ Fork 官方桌面版(大,⛔ 不建议 —— 官方设计方向与"承载平台服务"相反)。
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
### 📄 产出:`dsh桌面客户端_开发方案_20260916.md`(08:0x · 规划稿 · 工作区根)
|
|||
|
|
|
|||
|
|
**用户拍板**:「肯定是要**基于官方的壳进行迭代**,规划一套方案进行开发,看是否需要单独一个代码仓库」⇒ 选了**第三档(fork 官方壳)**,与上一轮我的建议相反 —— 按用户决定执行规划,不再争辩,但把代价写清。
|
|||
|
|
|
|||
|
|
**用户问的"是否要独立仓库" → 判定:✅ 需要**(命名建议 `dsh-desktop`)。五条理由:发布物与节奏不同 | 上游要持续同步 | **R2 边界**(桌面壳是官方源码衍生品,混进平台仓库会让归属与合规边界模糊)| 依赖形态差异大(拖慢平台 CI)| 安全信任模型不同(用户机 vs 服务器)。
|
|||
|
|
|
|||
|
|
**🔑 可行性硬证据(决定"能不能独立")**:
|
|||
|
|
- `apps/desktop` 的 `dependencies` **只有 2 个且全是公开包**(`electron-updater`、`semver`)。
|
|||
|
|
- `devDependencies` 16 个中**只有 1 个** monorepo 内部引用:`@deepseek-ai/dsh-home-paths: workspace:^`,而它**已在 npm 发布**(`0.0.1-rc.3`)。
|
|||
|
|
- ⇒ **独立仓库只需把那 1 处 `workspace:^` 换成 npm 版本**,其余全公开包。
|
|||
|
|
- ⚠️ 但 `@deepseek-ai/dsh-desktop` / `dsh-desktop-host` 是 **`private: true`、未发 npm** ⇒ **必须 fork 源码**,不能直接依赖。
|
|||
|
|
|
|||
|
|
**官方壳结构(已摸清)**:`apps/desktop` = **100 文件**(`src/` 主进程 + `renderer/` 启动页与插件管理 + `scripts/` 打包链 + 大量 tests);`apps/desktop-host` = **仅 6 文件**。
|
|||
|
|
**改造策略 = 取骨架与打包链,换内核**:
|
|||
|
|
- ✅ 保留:`single-instance` / `locale` / `ipc` / `paths` / `preload*` / `update-coordinator` / `startup-document` / `startup-error` / `renderer/startup.*` / **全套 Windows 打包链**(`electron-builder.config.mjs` + `package-target.ts` + `windows-sign.mjs` + `installer.nsh` + `smoke-windows.ps1` 等)
|
|||
|
|
- 🔧 改:`main.ts`(改加载目标与启动对象)、`backend-controller.ts`(**换内核** → 管理平台进程)
|
|||
|
|
- ❌ 删:`host-process` / `host-protocol`(走 HTTP 不用管道)、`project-manager` / `profile-packages` / `runtime-tree` / `core-package-set` / `owned-directory` / `release`、`renderer/plugin-manager.*`、`scripts/prepare-*.ts`、整个 `apps/desktop-host/**`
|
|||
|
|
- 🟡 重写:针对内置运行时的 tests
|
|||
|
|
|
|||
|
|
**其他关键事实**:
|
|||
|
|
- **Windows 签名是外部依赖**,从环境变量读(`DSH_DESKTOP_WINDOWS_CER_FILE` / `SIGNTOOL` / `TOKEN_PIN` / `KEY_CONTAINER`);✅ **官方支持 `package:win:x64:unsigned`** ⇒ 开发内测**不需要证书**。
|
|||
|
|
- `appId` / `productName` 由 `resolveDesktopAppId(env)` 解析 ⇒ **配置项,不用改代码**。
|
|||
|
|
- 同步机制建议 = **`upstream-baseline/` 快照 + `patches/` 补丁清单**(改动尽量放新增文件,减少与官方的冲突面);官方自称 developer preview、明写会有破坏性变更。
|
|||
|
|
- 交付含 **平台侧配套 3 项**(P1 Windows 子进程启动适配 ← 必做;P2 打包形态;P3 端口可配置)。
|
|||
|
|
- 里程碑 **M0→M4**,M0(最薄链路:壳 + spawn 平台 + 窗口加载)同时验证桌面改造与 P1。
|
|||
|
|
- 未决 3 项:**D1 关闭窗口行为**(退出 vs 托盘)|**D2 与官方桌面版关系**(独立目录 vs 共享)|**D3 是否保留插件管理界面**(删 vs 留)。
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
### ✅ 确认:多人形态**保留**,靠"配域名"启用,**零改动**(08:0x)
|
|||
|
|
|
|||
|
|
**用户指令**:「保留客户端部署 可以当作服务器 多人访问的机制,**只要他配置域名就行**,这个没问题把(对当前项目**最小改动 能不动的就不动**)」。
|
|||
|
|
|
|||
|
|
**判定:没问题,而且一行代码都不用改** —— 平台现有配置项本身就是这个开关:
|
|||
|
|
|
|||
|
|
| 配置 | 形态 | 地址 |
|
|||
|
|
|---|---|---|
|
|||
|
|
| 域名**留空**(默认) | 单机自用 | `http://127.0.0.1:3080` |
|
|||
|
|
| 配了**域名** | 多人访问 | `http://<用户名>.<域名>` |
|
|||
|
|
|
|||
|
|
- 开关 = **`DSHS_BASE_DOMAIN`** + `DSHS_COOKIE_DOMAIN`(有 HTTPS 再加 `DSHS_SECURE_COOKIES`)—— **全是环境变量**,`src/config.ts:320-321` ✓
|
|||
|
|
- 配套(用户侧)= 内网 DNS 泛解析 `*.<域名>` → 本机
|
|||
|
|
- ✅ **不需要 HTTPS 也能跑**:门户域与用户子域属于**同一 site**,Cookie 的 `SameSite=Lax` 足以支撑跨子域跳转(`src/web/auth.ts:55-57` 正是按"有没有 HTTPS"分这两档)
|
|||
|
|
- 隔离档位仍默认 `soft` ⇒ 多人时**用户之间无隔离**(与既定取舍一致;且 `account` 在 Windows 上本来不可用)
|
|||
|
|
|
|||
|
|
**已写进两份文档**:部署方案 **§1.3** | 开发方案 **§4.7**(含一条防自堵的硬要求:⛔ **客户端 spawn 平台时不要清理或白名单化环境变量**,否则"零改动切多人"会被自己堵死)。
|