Files
dsh_ai1net_server/.workbuddy/memory/2026-10-06.md
T
admin 1b68b1ac3f 项目管理界面:照 dsh-task-board 模式新建(目标=项目 · 只读复用现有看板 · 端口20100)
用户逐字:「不动现有任务会话看板 不是让你整体搬是照的他的模式做一个 项目管理的界面
          来管理 执行任务,目标等于项目」

- 新增 交付物/项目管理界面/{project-board.py,.html,-launch.py}
- 映射:目标→项目、任务类别→工作流、台账条目→卡片、会话→可跳转
- 数据源=现有看板已算好的 /board.json(⛔ 不重算、⛔ 不直读台账)
- 列=机制真实四态(待执行/执行中/有阻碍/已完成),⛔ 不硬套它那五列
- 现有看板 20099 / board.py / board.html 零改动
- 坑:pythonw.exe 下 sys.stdout is None ⇒ print/flush 崩(终端好、计划任务死)⇒ 加 _say() 兜
2026-10-06 08:23:15 +08:00

291 lines
19 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 2026-10-06 工作日志(本工作区 `ai1net-dsh-server`)
> 承接 `2026-10-05.md`。本日主线:**看板改名 + 队列按目标过滤**(用户逐字驱动)+ 撤销上轮对 `agent-product` 的误建。
---
## 三十四、看板改名「检查程序」+ 队列按目标过滤(不再历史累积)
### 用户逐字(两条)
1. `看板中 常驻程序 改为 目标检查,里面的队列 应该是随着目标的不是目标累积的`
2. (答我反问,**纠正我复述的"目标检查"**)`没有常驻程序的说法 ,改为 检查程序`
⇒ ⚠️ 正名是 **「检查程序」**(⛔ 不是"目标检查")。我复述错了一次,被当场纠正。
### 一、改名:根因是「角色名 ↔ 运行形态」混成一个词(同族第三次)
- **病根**:「常驻」是**运行形态**(`--supervise`),**不是角色名**。把形态当角色 ⇒ 名字里没有"它是干什么的"。
- **同族前科(同一类错,别只记这一条)**:
- 2026-10-03:我把「上报」改成「常驻」,用户驳回逐字
「**怎么又把上报 改成常驻了 常驻什么,不是 协作程序常驻吗**」。
- 本轮:用户又要求把「常驻程序」改掉 ⇒ **同一个词第三次被嫌**。
- **判据(以后选名先过这一条)**:名字必须答"**这个位子负责什么**",⛔ 不答"它怎么跑的"。
- **改的范围**:**只改用户可见文案**(标题/tooltip/chip/说明区/`aria-label`/peer 说明),
⛔ **代码标识符一律不动**(`prog`/`rt.prog`/文件名)—— 改了会牵动判据与存储。
### 二、队列按目标过滤(这是真缺陷,非文案)
- **病根**:`tasks.json` **只增不减** ⇒ 看板那格报的是**历史累计**,读者看不出「当前目标在跑多少」。
- 🔴 **机制自己早就知道**:`goalctl.py:577` 的「静默陷阱」注释逐字「**判据不绑目标**」——
那次**只警告没修**(怕动存量数据)。⇒ 本条是**同一病根的正面修法**,⛔ 不是新发现。
- **修法(四处,缺一不可)**:
1. `collabd.goal_fp()` —— 目标指纹 = `sha1(title)[:6]`
(与 `goal_dir_name()` **同源** ⇒ 目标目录名尾部的 `-xxxxxx` 与指纹**逐字相同**,三处可对着读)。
2. `task_report()` 给新条目 `setdefault("goal_fp", goal_fp())` ——
⛔ **只打新条目**;存量**不补**(补 = 把历史条目标成"当前目标" = **伪造归属**)。
⚠️ `setdefault` 只写一次 ⇒ 同一条目后续上报**不改归属**(防"跑到一半换目标 ⇒ 整条倒戈")。
3. `board._queue(tasks, st, cur_fp)` —— 只把 `goal_fp == cur_fp` 的算进 `total`/`by`/`open`。
4. 无归属存量**单列 `legacy`**(看板显示「另有历史 N 件,不计入」),⛔ 不混进当前目标。
- 🔴🔴 **指纹必须从形参 `g` 现算**(⛔ **不许读 `INBOX/goal.json`**):
`_goal_block()` 也渲染 **peer 格**(别的**工作区**的目标)⇒ 读本区文件 ⇒ 用**本区指纹**过滤对方台账
⇒ **对方格队列恒为 0**(跨工作区**静默**错,界面上看不出)。
✅ 实测:`vibe-product` 格 `fp=5d27fe`(**对方**指纹)—— 若串区会显示本区的 `c8154d`。
- **空指纹语义**:`cur_fp` 为空(未声明目标)⇒ **如实把全部当 `legacy`**,⛔ **不假装"队列为空"**
(那是把"没有归属信息"说成"没有活")。
- ⚠️ **`cur_fp` 的插入位置踩了两次坑**:① 插进**字典字面量**里 ⇒ `SyntaxError: ':' expected after
dictionary key`;② 插到 `build()` 的 `warn = []` 之后 ⇒ **函数归属错**(`_goal_block()` 的 `warn`
是**形参**、没有 `warn = []` 这行)⇒ 正解是 `_goal_block()` 内、`return {` **之前**、
`_lb = _labor(...)` 之后。
### 三、验收(都跑过,非声称)
- `selftest`:**PASS 97 / FAIL 0**(改前 95/2);`install.py --verify` **✅ 全绿**;`--manifest` 56 份/语法失败 0。
- `_queue` **四情形单元验证**全过:当前目标命中(total 4/legacy 3)|另一目标(total 1/legacy 6)|
空指纹(total 0/legacy 7)|无匹配(total 0/legacy 7)。
⚠️ 第一版**自己写错了期望值**(把 `open` 当成 "pending+running")⇒ 实测 `open = pending+running+blocked+other`
⇒ **是测试错,不是代码错**(差点误修代码)。
- 看板重启后 `board.json` 实测:本区 `fp=c8154d total=0 legacy=1`;`peer` 格 `fp=5d27fe`。
⛔ **`board.py` 是代码改动 ⇒ 必须重启看板**(只有 `board.html` 才是每请求实时读盘、不用重启)。
### 四、顺手修掉两处 `selftest` FAIL(都是我自己引入的)
1. **技能里留了项目串**:`goal_fp()` docstring 写了工作区名/历史线名/`goal.json` 的 id 值
⇒ 判据「技能就是技能,谁用产生的文件放在他自己那里」报红 ⇒ 已**泛化**(留事实、去专名)。
2. **`pitfalls.md` 的 P0-86 重复了两份**(3157 行与 3305 行,**逐字相同**,仅差一个尾空行)
⇒ 删后一份;并把留下的那份从 **7.3 KB/密度 2.1** 压缩到 ~5 KB(**判据要点、变异表、教训一句不丢**)。
⚠️ 判据「经验不许写成流水账」的**红线是超长 + 低证据密度**(>6 KB 且 密度<4.0),
不是"长了就删" ⇒ 压缩时**先保要点**。
### 五、另一个坑:`board.html` 行尾被改成 CRLF(差点提交 1856 行纯噪音)
- 现场:`board.html` 工作副本 **CRLF**(1856 个 CR),而 repo `HEAD` 版是 **LF**,`core.autocrlf=false`
⇒ `git diff` 整个文件重写(3544 行)。
- ✅ 处置:**按字节归一化回 LF** 再提交(`b.replace(b'\r\n', b'\n')`)⇒ diff 从 3544 行降到 386 行(真内容)。
- 🔴 **判据(沿用既有铁律)**:**行尾只信字节级**(`b.count(b'\r')`),⛔ 不看编辑器显示。
⚠️ 提交前若见"整文件重写",**先查行尾**,别当成真改动。
### 六、提交
`5dc6ced session-mechanism: 看板改名「检查程序」+ 队列按目标过滤(不再历史累积)`
(4 files:`board.html`/`board.py`/`collabd.py`/`pitfalls.md`;⛔ **未推送**)
---
## 三十五、撤销上轮对 `agent-product` 的误建(详情见 `2026-10-05.md` §33,此处只记结论)
- 用户令逐字:`删掉 让那个会话自己建立`。
- 删了 4 项 + 1 项连带:`tmp/supervise-inbox/goal.json`/`交付物/任务图-会话协作自检.json`/
`执行会话/目标-agent-product-3e3182/`/`collabd-state.json` 的 `roles` 主会话登记/
配置里的 `taskgraph` 指针(删载体要连"谁指向它"一起查,否则常驻每 10 s 刷 `Errno 2`)。
- 备份:`ai1net-dsh-server/tmp/bak/agent-product-misbuild-20261005/`。
- **交接姿态**:常驻**故意没重起**(用户令"让那个会话自己建立")⇒ **我不再插手该工作区**。
---
## 三十六、⚠️ 技能总仓「提交/推送」发现**历史分叉**(未决 · 待用户拍板)
用户 05:0x 说「提交」⇒ 核实两仓后发现**技能仓不能直接推**:
| 仓 | 本地 | 远端 | 关系 |
|---|---|---|---|
| 工作区 `ai1net-dsh-server` | `746e4f2` | 有上游 | ✅ 正常可推 |
| 技能总仓 `workbuddy_skills` | `5dc6ced`(含 5 个未推提交) | `19101ac` | 🔴 **两边无共同祖先** |
- 🔴 **远端是"重建的干净库"**:远端唯一提交 `19101ac init: workbuddy_skills 重建,仅收录 session-mechanism`
⇒ 有人**把远端历史重写过**(`git fetch` 报 `+ fe6ce9f...19101ac master (forced update)`)。
- 🔴 **本地是旧的多提交历史**:`5dc6ced`/`e4cbd2f`/`935e423`/`4adc431`/`1eec42e` + 更早。
- ⇒ `git merge-base HEAD origin/master` **为空** ⇒ **无法快进、无法普通合并**。
直接 `push` 会被拒(non-fast-forward);要上去只能 **`push --force`(覆盖远端历史)**。
- ⚠️ **这是不可逆的对外动作**(`--force` 会丢弃远端那个 init 提交)⇒ ⛔ **不擅自决定,必须用户拍板**。
- ⚠️ 另:技能仓还有 **19 个文件的未提交改动**(515 增/692 删,非本轮产物,疑似别的会话或早前轮次在途)
⇒ ⛔ **不代它提交**(无边界扫进来=伪造别人的工作)。
- ⚠️ 推送用 **`git@work.alotbuy.com:maogeigei/workbuddy_skills.git`**(work 域名,非 github)
⇒ 与 `~/.ssh/config` 里的 github 密钥路由**无关**(别去翻那套)。
---
## 三十七、看板 tab 改「按创建时间」稳定排序(用户:「tab 的排列顺序 应该按照创建时间顺序来」)
### 为什么改(活跃度排序的真实毛病)
- 旧键 `tab_hot` = 该块**最近会话活动**距今分钟数(`age_min`),10-04 定的「最新在运行的排前面」。
- 🔴 **病根**:会话活动**每次协作都会变** ⇒ tab 顺序**自己会漂**。
用户脑子里认的是「第几个 tab 是什么」⇒ 位置一变就**要重新找**,且**无从判断为什么变了**。
- ✅ 新键 `tab_created` = `goal.json` 的 **`declared_at`**(目标声明时间)—— 目标建立后**不动**
⇒ 顺序**稳定**。
- ⚠️ **为什么不用 `topics_declared_at`**:两者多数相同,但 `declared_at` 是**目标本身**的建立时间,
`topics_declared_at` 是**任务类别清单**的确认时间(可能后补/缺失)。语义要取前者。
- ⚠️ **缺 `declared_at` 的块垫底**(`"~"` 大于任何 ISO 时间串),⛔ **不许猜**一个时间塞进去。
### 🔴🔴 本轮最大的坑:`declared_at` 被块**丢掉**了(排序静默失效)
- 块里的 `goal` **子字典只留 3 键**(`title`/`short`/`acceptance`,`board.py:1795`)
⇒ `declared_at` **不在里面**。
- 我第一版从 `_b["goal"].get("declared_at")` 取 ⇒ **恒为空** ⇒ 三块 `tab_created` 全是 `'~'`
⇒ **排序形同没做**,而"升序"判据**照样绿**(因为三个都一样,升序恒真)。
- ✅ 正解:在 `_goal_block()` 里**单独带出 `goal_declared_at` 字段**(从**形参 `g`** 取)。
⚠️ 从 `g` 取而非回读 `INBOX/goal.json`:peer 块传的是**对方**的 goal ⇒ 取到对方时间,正确。
- 🔴 **同族教训(第 N 次)**:**块是"精简过的视图",不是 goal 原样**
⇒ 任何"要参与逻辑的字段"都必须**显式带出来**,⛔ 不能指望它在块里躺着。
### 判据重写(附变异对照,证有牙)
- 夹具改成**时间乱序**(t2 10-01 最老/t0 10-02/t3 10-03),且**文件出现次序就是时间倒序**
⇒ 排序一旦失效,输出顺序**立刻不同**(⛔ 三个时间同序 ⇒ 判据恒绿,同族栽过)。
- 新增**真造第四块**(**没有** `declared_at`)⇒ 必须垫底且 `tab_i` 最后一位
(⛔ 不造这块 ⇒ 判据恒真 = 假绿)。
- 新增**防回退**判据:源码里 ⛔ 不许再出现 `tab_hot` 当排序键。
- ⚠️ **我自己写判据时取错层**(`short` 在 `goal` 子字典里,⛔ 不在块顶层)⇒ 判据**恒红**,已修。
⇒ 写判据也要先确认"字段在哪一层",⛔ 别凭印象。
- **变异对照**:A 排序 `reverse=True` ⇒ 红 ✅|B 排序键换回 `tab_hot` ⇒ **五条同时红** ✅|还原 ⇒ 绿 ✅。
### 验收
- `selftest` **PASS 97 / FAIL 0**|`install.py --verify` **✅ 全绿**|`--manifest` 56 份/语法失败 0。
- 真起看板实测:`i=0 本机协作(2026-10-01T22:29)` → `i=1 vibe-product(2026-10-05T18:04)` ✅。
- ⛔ **`board.py` 代码改动 ⇒ 必须重启看板**(走 `dsh-board-keepalive` 计划任务,⛔ 不手动裸起)。
- 提交:`5e77f0e`(技能仓,未推送)。
### ⚠️ 顺带记录:本机裸 `nohup python board-launch.py &` 活不过工具调用边界
- 实测:`nohup ... &` 起看板 ⇒ 打印了「看板已起」但**进程随即消失**(工具调用结束即被收走)。
- ✅ **唯一可行起法 = 触发计划任务** `dsh-board-keepalive`(`Start-ScheduledTask`)
⇒ 与记忆里「常驻起来只有两条路:计划任务 / 会话后台任务+stdout 重定向」一致。
---
## 三十八、评估 `dsh-task-board` 能否移植进「任务会话技能」当工作台(结论:⛔ 不能移植,但可**映射概念**)
用户问:`E:\github\dsh-web\packages\dsh-task-board` 能否把这个功能移植到任务会话技能里当工作台。
### 它是什么(实读,⛔ 非推断)
- **`@linxin666/dsh-client-ui-task-board` v0.4.4** —— **DSH(DeepSeek Harness)的 Web GUI 插件**,
`src/` 下 80 个 TS/TSX 文件、**20,825 行**。
- 挂载方式:`cordis.patch.yml` + `dsh.bundle.patch` profile 层 ⇒ **只在 DSH 宿主里活**。
- 🔴 **依赖面全是 DSH 私有 SDK**:`@deepseek-ai/dsh-tools`/`dsh-llm`/`dsh-api-gateway`/
`dsh-workspace`/`dsh-host-webserver`/`dsh-client-ui-slots`/`dsh-api-session-controller`… **20+ 个包**,
另有 `cordis`(DSH 的插件内核)。
### 结论:⛔ **不能"移植"** —— 宿主不同,不是"换个壳子"的问题
| 维度 | `dsh-task-board` | 本技能(WorkBuddy 侧) |
|---|---|---|
| 宿主 | **DSH**(DeepSeek Harness) | **WorkBuddy** |
| 插件内核 | `cordis`(`dsh.bundle.patch`) | 技能包 + `.workbuddy/collab/` 脚本 |
| 界面座位 | DSH shell 的 `sidebar.panellist` / `main` 座位 | 自建 `board.py --serve`(20099)+ 手写 `board.html` |
| 数据面 | DSH Host `$DSH_HOME/task-board/ledger-v2.json` | `tmp/supervise-inbox/{goal.json,tasks.json}` |
| 执行引擎 | DSH `/goal` 目标驱动器 + dsh-llm-verifier | `collabd.py` 常驻 + 排期拉会话 |
| 语言 | TypeScript/React 插件 | Python + 单页 HTML |
⇒ **零修改侵入 DSH 源码**是它的卖点,但**反向也成立**:脱离 DSH 它**一行都跑不了**。
移植 = **重写**(20k 行 TS + React 换成 Python/HTML),⛔ 不是迁移。
### ✅ 但它有**真价值**:几处设计可直接**映射**到本技能的短板
| 它的机制 | 本技能的现状 | 可映射性 |
|---|---|---|
| **Host 权威账本**(浏览器只是异步视图,关页面不停调度) | 已有同思想(`tasks.json` 是唯一权威) | ✅ 已一致 |
| **账本原子写**(临时文件 + 原子 rename + revision) | `tasks.json` 写法定过 | ⚠️ 可对照补 |
| **任务验收门禁**(`update_goal(complete)` 被宿主拦截,不信 agent 自述) | **本技能正缺这一环**(改完靠检查会话**读**,无"拦截完成声明") | 🔴 **最值得借** |
| **有界执行历史**(每任务只留 20 条) | `tasks.json` **只增不减**(本轮刚处理"队列按目标过滤",病根同源) | 🔴 **值得借** |
| **8 个 agent 工具**(`task_board_list/create/run/schedule`…) | 机制侧靠 `collabd.py --report` 等 CLI | ⚠️ 形态不同,思路可借 |
| **cron 调度器**(5 段 + IANA 时区 + DST 语义) | 本技能有 `automation_update` 排期 | ⚠️ 已够用 |
### 我的判断(一句话)
**⛔ 别移植代码**(宿主不同、语言不同、依赖 20+ DSH 私有包);
**✅ 借两个设计**:① **验收门禁**(不信自述、宿主侧拦截完成声明)② **有界历史**(台账不许无限增长)。
若用户要的是"**在 WorkBuddy 里有个像它那样的工作台**" ⇒ 那是**把现有看板(20099)做厚**,
⛔ 不是搬这个包。
### ⚠️ 待用户确认的语义分叉(属"功能语义分叉",须提报)
用户说"移植到任务会话技能中当作工作台" —— **"工作台"指哪个?**
- **A**:WorkBuddy 里一个**看得见、能点**的面板(=把现有看板做强)
- **B**:只是**借用它的任务/验收模型**(=改机制,不加界面)
- **C**:真要在 **DSH** 里用(那与本技能无关,装进 DSH 即可)
⇒ 三选一才谈得上下一步,⛔ 别猜。
---
## 三十九、新建「项目管理界面」(照 dsh-task-board 模式 · 目标=项目)— 已交付在跑
### 用户纠正我(逐字)
> 「**不动现有任务会话看板**,**不是让你整体搬**,是照的他的模式做一个 **项目管理的界面**
> 来管理 执行任务,**目标等于项目**」
⇒ ⚠️ 我上一轮把问题问成"要不要移植/怎么移植"=**问错了方向**:用户要的是**新做一个界面**,
⛔ 不是搬迁、⛔ 不是改现有看板。
### 交付物(新建,⛔ 现有看板零改动)
📁 `交付物/项目管理界面/`(**3 个文件**)
| 文件 | 作用 |
|---|---|
| `project-board.py` | 只读服务 + 映射层(`--serve 20100`/`--once` 打摘要) |
| `project-board.html` | 界面:项目下拉 + 四列卡片 + 点开抽屉 |
| `project-board-launch.py` | **启动器**(计划任务动作指向它,⛔ 不直起业务脚本) |
- **端口 20100**(现有看板 20099 **不动**);计划任务 **`dsh-project-board-keepalive`**(5 分钟判活)。
- ✅ 实测:20100 `HTTP 200`;`/api/projects` 返回 2 个项目/12 张卡片;20099 同时 `HTTP 200`(互不影响)。
### 🔴 映射关系(唯一真源 = 现有看板的 `/board.json`,⛔ 不重算)
| 它的概念 | 本机制 | 数据来源 |
|---|---|---|
| 项目 | **目标** | `goals[]`(`goal.short`/`tab_created`) |
| 工作流分组 | **任务类别** | `labor[]` |
| 卡片 | **台账条目** | `tasks{}`(`state` → 列) |
| 点卡片 → 会话 | **会话跳转** | `sessions[].id8` |
🔴 **关键决定:⛔ 不直读台账、⛔ 不重算** —— 复用现有看板**已经算好的** `/board.json`
(重算 = 把 `board.py` 那套逻辑抄第二份 ⇒ 同族 P0「一条规则两份实现」必漂)。
### 🔴 列用**机制真实四态**,⛔ 不硬套它那五列
- 它:待规划/待办/进行中/已完成/已失败(5 列)
- 本机制台账只有 **待执行/执行中/有阻碍/已完成**(4 态)
- ⇒ 硬套 5 列会**凭空多出机制里不存在的「待规划」**(看着像有、其实永远空)⇒ ⛔ 不做。
- ⚠️ 用户问「什么工作流状态 干啥用的」⇒ 就是这一列的含义,已当面解释。
### 🔴🔴 本轮踩的真坑:`pythonw.exe` 下 `sys.stdout is None`(终端好、计划任务死)
- **症状**:计划任务 `Result=1`,**20100 起不来**;但**前台跑同一脚本一切正常**。
- **根因**:计划任务用 `pythonw.exe`(**无控制台**)⇒ **`sys.stdout is None`**
⇒ 我写的 `sys.stdout.flush()` 抛 `AttributeError: 'NoneType' object has no attribute 'flush'`
⇒ 进程**当场崩**。而 `Result=1` 只说"失败",**看不出为什么**。
- **诊断方法(✅ 可复用)**:加一个**只写异常到文件**的 wrapper(`launch-diag.py`),
指向计划任务动作 ⇒ 异常立刻现形(⛔ 别靠猜 `Result=1`)。
- **修法**:加 `_say()` —— **有 stdout 才写、没有就静默丢**;⛔ 常驻进程不许因为没地方打印就崩。
- 🔴 **同族铁律**:**`pythonw.exe` 下 `print`/`sys.stdout` 一律不可靠**
⇒ 凡"计划任务跑的脚本"⛔ 不许裸用 `print`。
### ⚠️ 另一条弯路(别重蹈)
- 我一度以为是**中文路径**(`交付物/项目管理界面`)导致计划任务失败 ⇒ 复制到 ASCII 路径 `tmp/pm-board` **对照**
⇒ **同样 `Result=1`** ⇒ 证明**与路径无关**,真因是上面那条。
- ⇒ 教训:**路径猜测**要有对照实验才下结论(我用了一轮,但及时纠正并清理了临时副本)。
### 清理
- `tmp/pm-board/`(对照实验副本)**已删**;计划任务已**重指回正式交付目录**。