41 KiB
2026-10-06 工作日志(本工作区 ai1net-dsh-server)
承接
2026-10-05.md。本日主线:看板改名 + 队列按目标过滤(用户逐字驱动)+ 撤销上轮对agent-product的误建。
三十四、看板改名「检查程序」+ 队列按目标过滤(不再历史累积)
用户逐字(两条)
看板中 常驻程序 改为 目标检查,里面的队列 应该是随着目标的不是目标累积的- (答我反问,纠正我复述的"目标检查")
没有常驻程序的说法 ,改为 检查程序
⇒ ⚠️ 正名是 「检查程序」(⛔ 不是"目标检查")。我复述错了一次,被当场纠正。
一、改名:根因是「角色名 ↔ 运行形态」混成一个词(同族第三次)
- 病根:「常驻」是运行形态(
--supervise),不是角色名。把形态当角色 ⇒ 名字里没有"它是干什么的"。 - 同族前科(同一类错,别只记这一条):
- 2026-10-03:我把「上报」改成「常驻」,用户驳回逐字 「怎么又把上报 改成常驻了 常驻什么,不是 协作程序常驻吗」。
- 本轮:用户又要求把「常驻程序」改掉 ⇒ 同一个词第三次被嫌。
- 判据(以后选名先过这一条):名字必须答"这个位子负责什么",⛔ 不答"它怎么跑的"。
- 改的范围:只改用户可见文案(标题/tooltip/chip/说明区/
aria-label/peer 说明), ⛔ 代码标识符一律不动(prog/rt.prog/文件名)—— 改了会牵动判据与存储。
二、队列按目标过滤(这是真缺陷,非文案)
- 病根:
tasks.json只增不减 ⇒ 看板那格报的是历史累计,读者看不出「当前目标在跑多少」。 - 🔴 机制自己早就知道:
goalctl.py:577的「静默陷阱」注释逐字「判据不绑目标」—— 那次只警告没修(怕动存量数据)。⇒ 本条是同一病根的正面修法,⛔ 不是新发现。 - 修法(四处,缺一不可):
collabd.goal_fp()—— 目标指纹 =sha1(title)[:6](与goal_dir_name()同源 ⇒ 目标目录名尾部的-xxxxxx与指纹逐字相同,三处可对着读)。task_report()给新条目setdefault("goal_fp", goal_fp())—— ⛔ 只打新条目;存量不补(补 = 把历史条目标成"当前目标" = 伪造归属)。 ⚠️setdefault只写一次 ⇒ 同一条目后续上报不改归属(防"跑到一半换目标 ⇒ 整条倒戈")。board._queue(tasks, st, cur_fp)—— 只把goal_fp == cur_fp的算进total/by/open。- 无归属存量单列
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✅ 全绿;--manifest56 份/语法失败 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(都是我自己引入的)
- 技能里留了项目串:
goal_fp()docstring 写了工作区名/历史线名/goal.json的 id 值 ⇒ 判据「技能就是技能,谁用产生的文件放在他自己那里」报红 ⇒ 已泛化(留事实、去专名)。 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),而 repoHEAD版是 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 删,非本轮产物,疑似别的会话或早前轮次在途) ⇒ ⛔ 不代它提交(无边界扫进来=伪造别人的工作)。
- ⚠️ 推送用
[email protected]: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⇒ 五条同时红 ✅|还原 ⇒ 绿 ✅。
验收
selftestPASS 97 / FAIL 0|install.py --verify✅ 全绿|--manifest56 份/语法失败 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-boardv0.4.4 —— DSH(DeepSeek Harness)的 Web GUI 插件,src/下 80 个 TS/TSX 文件、20,825 行。- 挂载方式:
cordis.patch.yml+dsh.bundle.patchprofile 层 ⇒ 只在 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/(对照实验副本)已删;计划任务已重指回正式交付目录。
40、goalctl 两处口径分叉修复(用户 ⑤-2「vibe-product 最新会话反应新问题 看看如何解决」)
40.1 是什么问题
vibe-product 上一条会话(10-06 08:15~08:20)已诊断出两条缺陷并修了数据侧(换任务图、存值改 pass),
但它自己在记忆里登记了「同族缺陷未修 / 技能侧缺陷未修」⇒ 我这一轮把技能侧修了。
用户在 ⑤-2 里说的「新问题」,逐字对应那份 目标执行状态.md 的两条阻断:
| 阻断 | 内容 | 归属 |
|---|---|---|
| ① | goal.json.execution_doc 指向前身目标的目录(换目标漏改) |
工作区数据(前一会话已修) |
| ② | 本目标从未建立过任务会话(零排期)+ NEXT.md 是过期投影 |
工作区数据(已随目标完成自然消解) |
⇒ 但真正跨工作区、属技能侧的根因有两处,前一会话只绕开了、没修:
40.2 🔴 根因一:goalctl.py:262 判定层死板比字面值
- 原文:
if str(acc[k]) != "pass":—— 只认英文pass。 - 而存值真源就是中文(
过|pid 8024 活…/已过|…/达(1440 与 390 两视口都有))。 - ⇒ 已通过的验收全部被计入 why ⇒ 恒判「未完成」⇒ 控制台打出 「未完成(验收 V1=过;V2=过;…)」这种字面自相矛盾的读数。
- 🔴 病根不只是这一行:
board.py::acc_is_pass()的 docstring 明写 「三处判据(board.py/collabd.py::_acc_is_pass/board.html::accIsPass)必须同款」, 而board.py:1618与collabd.py:3022都做了中文归一化(ACC_PASS_WORDS+ 剥完成副词) —— 实际是四处,goalctl被漏掉了 ⇒ 同一字段三套读法。
40.3 🔴 根因二:goalctl.py:136 配置落点写死技能目录
- 原文:
CFG = HERE / "collabd.config.json",HERE= 技能包目录。 - 按定则「技能就是技能、程序就是程序,谁用产生的文件放在他自己那里」⇒
技能目录里⛔ 不放生产配置 ⇒ 该文件恒不存在 ⇒
_load(CFG,{})恒{}。 - ⇒
taskgraph取默认INBOX/taskgraph.json⇒ 配置改了不生效 (实测:vibe-product 配置里已改指 proto-board,goals_open()仍报「任务图读不到」)。 collabd.py早有_cfg_candidates()三级解析(env → 工作区标准落点)⇒ 又是"一个技能两套读法"。
40.4 处置
goalctl新增_acc_is_pass():复用collabd._acc_is_pass()(它自己再复用board真身); 拿不到 ⇒ fail-closed 声明式兜底+stderr 留痕,⛔ 绝不静默返回。CFG改为与collabd._cfg_candidates()同款三级(env → 工作区标准落点 → 旧路径+告警)。- 修正
:605误导文案(原「要名字=pass或名字=未过」——未过与任意中文值旧判定层同样恒判未过)。 - 新增 2 条自检用例+
pitfalls.mdP0-87。
40.5 实测读数
| 项 | 修前 | 修后 |
|---|---|---|
vibe-product goalctl.goals_open() |
{'open': True, 'why': [], 'bad': ['任务图读不到(taskgraph.json)']} |
{'open': False, 'why': [], 'bad': []} |
vibe-product goalctl 状态台 |
(目标判不出来) | 「目标状态 = 三路全过」 |
技能 selftest |
PASS 97 / FAIL 0 | PASS 99 / FAIL 0 |
--verify |
— | rc=0 ✅ |
变异对照:改回 != "pass" ⇒ PASS 97 / FAIL 1(命中本用例);还原 ⇒ 99/0 ✅
40.6 🔴 两处踩坑(都是"我喂错了,不是代码错")
selftest的imp()加载的是collabd,不是goalctl—— 两者goals_open()同名但返回类型不同 (collabd 返bool、goalctl 返dict)⇒ 直接用 ⇒TypeError: 'bool' object is not subscriptable。- 两个模块的
INBOX不是同一个目录(collabd来自配置、goalctl硬编码WS/tmp/supervise-inbox) ⇒ 夹具写错落点 ⇒bad=['任务图读不到']⇒ 用例恒红。 ⭐ 教训:用例红了先分"被测代码错"还是"我喂错了" —— 三次都是后者, 若直接去"修代码",会把好的代码改坏。
40.7 🔴 又撞一次 CRLF(本轮第二次)
goalctl.py 工作副本被改成 CRLF(877 个 CR),HEAD 是 LF ⇒ git diff 整文件重写
(877 增 / 672 删)⇒ 按字节归一化回 LF ⇒ 降到 228 增 / 23 删真内容。
铁律:看到"整文件重写"先查行尾(b.count(b'\r'))。前一次是 board.html。
40.8 挂账(未做,需用户拍板)
🔴 vibe-product 的收口动作未做:它的 lifecycle 仍是 进行中(前一会话只改了 acceptance_state,
没走收口流程)⇒ 常驻程序按设计继续跑(收工只在 --set-life 已完成 时触发)。
要收工=改 lifecycle=已完成 ⇒ 会 supervise_stop 停掉该工作区的常驻进程。
⚠️ 这属「影响面超出本平台/跨工作区状态变更」⇒ 先报用户,⛔ 不擅自动手。
⚠️ 另:vibe-product 常驻主循环跑的是旧代码(09-29 启动,配置改动需重启才生效)——
本次修的是技能源,其工作区副本要同步+重启才生效(这一步同样未做)。
41、项目管理界面改「跨工作区」+ 增删落地(用户:「项目管理管的是本机所有工作区的项目 不是单个工作区的」)
41.1 用户口径(逐字)
项目管理管的是本机所有工作区的项目 不是单个工作区的,可以通过项目管理工具 创建新目标(新工作区),管理目标
外加三条 AskUserQuestion 选定:① 项目范围=只列「装了机制」的工作区;② 建新目标=填表建目录+登记目标;
③ 写权限=允许增删,改状态交给机制(承接上一轮 增删查(状态不需要改 让会话机制自己处理))。
41.2 数据面(🔴 关键设计:直读各区文件,⛔ 不拉各区看板)
真因:并非每个区都起着 board.py(本机 3 个装机制的区里只有 1 个在看板跑)⇒ 拉看板会静默漏掉没起看板的区。
✅ 改直读:<区>/tmp/supervise-inbox/goal.json(项目)+ tasks.json(卡片)+ collabd-state.json(队列)
+ <区>/.workbuddy/collab/collabd.config.json(判「装没装机制」)。
「装了机制」判据:① 有 collabd.config.json 或 ② 有 goal.json(两条任一)。
🔴 跳过的也要带出来(skipped 列表)—— 同族 P0「作用域不许静默排除」,⛔ 不静默丢。
41.3 新增能力
| 能力 | 实现 | 实测 |
|---|---|---|
| 查 | GET /api/projects |
3 个项目(本机协作 / vibe-product / agent-product 空壳)+ 12 个未装机制目录 |
| 增 | POST /api/create → init_workspace.py |
ok=True rc=0,骨架+目标登记齐全 |
| 删(轻) | POST /api/delete {mode:"goal"} |
goal.json → goal.json.archived-<时间戳>(可逆) |
| 删(重) | POST /api/delete {mode:"ws"} |
送系统回收站(可逆) |
41.4 🔴🔴 本机回收站:gio 不存在,改走 SHFileOperationW
症状:mode="ws" 原写 gio trash ⇒ gio: command not found(PortableGit 里 gio/trash-put/trash 一个都没有);
改用 PowerShell [Microsoft.VisualBasic.FileIO.FileSystem] ⇒ 被安全策略拦("Add-Type compiles and loads .NET code at runtime")。
✅ 正解:纯 ctypes 调 Windows Shell API SHFileOperationW(不编译代码、不碰 .NET、不弹 UAC)。
三个坑(都踩过):
- 🔴
pFrom必须手搓双\0结尾缓冲 ——LPCWSTR会在第一个\0截断 ⇒ 字段类型必须写c_void_p, 用ctypes.create_unicode_buffer(abspath, len(abspath)+2)再cast塞进去。⛔ 不能直接赋str。 - 🔴🔴 判据不是
rc == 0:送回收站成功(目录消失、$I元数据可解出原路径),返回值却恒为rc=2(ERROR_FILE_NOT_FOUND,Shell 内部探测残留的GetLastError)⇒ 拿rc当判据会假报错 (用户看到"失败"但东西已进回收站)。✅ 真判据只有两条:① 原路径不存在了 ②fAnyOperationsAborted为假。 - ⚠️ 回收站
$I元数据:v2格式、路径是 UTF-16LE、起点 offset 24(⛔ 不是 26;开头 4 字节是长度前缀)。 验证可恢复性时按此解析。
实测取证:_pmtest-{verify,trash,wsdel,wsdel2,http} 全部送进 E:/$RECYCLE.BIN/S-1-5-21-…-500/,
共 29+ 条 $I 元数据,逐条解出的原路径与送入路径一致 ⇒ 可恢复。
41.5 另一个已修缺陷:子进程中文乱码
init_workspace.py 输出 UTF-8(开头 reconfigure("utf-8")),而 subprocess.run 不给 encoding
时按**本机默认编码(中文 Windows=GBK)**解码 ⇒ 中文全变乱码。✅ 修:encoding="utf-8" + PYTHONIOENCODING=utf-8。
41.6 起服务:只许触发计划任务
(python project-board.py --serve 20100 &) 活不过工具调用边界(被 SIGTERM)。
✅ Start-ScheduledTask -TaskName 'dsh-project-board-keepalive'(动作走 pythonw.exe + project-board-launch.py,
⛔ 不直起业务脚本 —— 同族 P0-73/74)。重启后 PID 64312 → 10408,/healthz HTTP 200。
41.7 挂账
- 🔴 vibe-product 是否收工 —— 仍待用户拍板(见 §40.8,未动)。
- 🔴 技能仓历史分叉未决(§36,仍待拍板)。
42、项目管理界面:统一视图 + 提示收进「!」 + 数据时效(用户逐字驱动)
42.1 用户口径(三条逐字)
没安装机制的工作区不显示 黄字描述都放到 !号的图标中,不显示在工作台这里分类不要按照工作区分了,记住 项目管理是统一管理- (我反问刷新机制后)
页面应该有自己的刷新机制,保证数据的有效性,不然看着刷新了实际没有刷新
42.2 落点
| 项 | 做法 |
|---|---|
| 统一视图 | ⛔ 删掉「按工作区分块」⇒ 一张四列看板汇总本机所有目标的卡片;归属改由卡片上的「目标」标签体现(.badge.goal) |
| 提示收进「!」 | 工作台上去掉所有黄字横幅(原 notice 容器整体删除);无内容时图标淡显不可点,有内容点开弹层、点外/Esc 关 |
| 未装机制不显示 | UI 彻底不列(含原「扫到但没装机制 N 个目录」那行)⚠️ 后端仍留 skipped(只给 CLI/取证) |
| 数据时效 | 服务快照带 epoch;读数「数据 N 秒前」只用服务 epoch 算;>15 s 转黄(.is-stale);每秒重画(⛔ 不重取数) |
| 轮询自适应 | 有变化 ⇒ 3 s;连续 3 次没变 ⇒ 15 s;一变立刻回收。⛔ 不做"页面醒了才刷"这类花活 |
| 操作反馈 | 一次性 toast(新建/删除/失败),⛔ 不常驻 |
42.3 🔴 「看着刷新了实际没刷新」的根因与修法(用户第 3 条的直接答复)
- 病根:原来
stamp显示的是服务返回的ts字符串,而页面每 5 秒无条件setInterval(load)⇒ 每次拉取都会把它改成"刚刚" ⇒ 数据其实没变,但读数一直在跳 ⇒ 用户看到的"新"只是页面在轮询,⛔ 证明不了数据是新的。 - ✅ 正解(照现有看板
board.html::paintAge的口径):① 读数只用服务给的epoch算 (⛔ 不用本地Date.now()顶上);② 超阈值转警示色;③ 读数每秒重画但不重取数 ⇒ 数据没动时读数会如实往上爬,一眼分清"页面在转"与"数据在动"。 - ⚠️ 浏览器侧验收踩了缓存坑:第一次探针读出
groups=3 / cols=8 / goal_badges=[](=旧版), 差点判成"改没生效"⇒ 必须Network.setCacheDisabled+Page.reload(ignoreCache=True)才读到真页面 (同族 P0:「浏览器侧验收假阴性」)。
42.4 实测(浏览器侧·强刷新后)
- 4 个列标题(⛔ 不再是 8 个);12 张卡片全在一张板上;标签
本机协作 ×1 / vibe-product ×11 - 「!」1 条内容、默认收起、点击展开、再点关闭;时效读数正常跳动
- 死代码清理:随分区一起废掉的
projgroup/pghead/pgname/pglink/pgmeta/pchip/lane*共 15 行,已全数删除(残留检查全 0) - 提交
d97fb0f(工作区)
43、vibe-product「最新会话反应的问题」核查(用户第 2 问)
43.1 问题出处
正式载体=执行会话/目标-vibe-product-5d27fe/目标执行状态.md(第 1 棒检查会话,2026-10-05 18:0x 写)。
它报两条阻断 ⇒ 判不出来:① execution_doc 指向前身目标目录;② 本目标从未建立过任务会话(零排期)。
43.2 核查结论:两条都已被后续动作消解,但留下了三条新问题
| 原阻断 | 现状 | 判 |
|---|---|---|
① execution_doc 指旧目标 |
已改指 执行会话/目标-vibe-product-5d27fe/(该目录现存在) |
✅ 已解 |
| ② 零排期 | 台账 11 件全 done、任务图 N1 |
✅ 已解 |
| ③ 判据与存值口径 | goalctl.py 已同步(与技能源 md5 相同 c4eb6c02…) |
✅ 已解(本轮所修) |
🔴 但留下三条(见 §43.3)。
43.3 🔴 三条新问题(都没人报,是本轮核查挖出来的)
NEXT.md是过期投影,还在喊「V1 pending」tmp/supervise-inbox/NEXT.md(10-05 18:06)内容是本目标四条 V1~V4、 而goal.json现已是另一份目标(「规划产品原型项目管理工具」),且它自己的那条 V1 早过。 ⇒ 文件在,读数全错。⚠️ 真因:清NEXT.md的paused_round()只在--once分支跑, 常驻主循环走另一条路 ⇒ 常驻模式下这份投影永不被清。- 常驻在空转,且规模已可观
pid 432起于08:50:47,round累计至 54;日志 10,102 行 / 1,412,248 字节。 去重后只有三类内容,全是"没事发生":闸④:跳过不会再触发的一次性排期(⛔ 否则它会永远卡住闸)|<12 条旧排期名>目标检查:同名排期已在册 ⇒ 跳过([检查]-[目标检查]-vibe-product-第2棒)跨区自愈:ai1net-dsh-server=目标已完成 且队列无未完成件 ⇒ 不拉⇒ 每分钟固定刷约 10 行,纯开销、零产出。
- 工作区副本跑的是 09-29 的旧代码
.workbuddy/collab/collabd.py未同步技能源(goalctl.py已同步,但collabd.py未核)。
43.4 与用户第 1 问的交叉
🔴 第 2 问的根因正好被第 1 问的界面暴露出来:统一视图把 12 张卡片摊在一张板上,
一眼能看出 11 张是 vibe-product 的、且全 done —— 即"这个目标已干完、常驻还在空转"。
⇒ 我的处置建议:收工该目标(lifecycle=已完成 ⇒ 会 supervise_stop 停掉该区常驻)。
⚠️ 但这属跨工作区状态变更 ⇒ 仍按 §40.8「先报用户、⛔ 不擅自动手」。
44、A 方案执行:收工 vibe-product(用户拍板)+ 界面三处返修
44.1 A 方案 —— 收工 vibe-product(已执行并取证)
用户拍板「A. 只收工 vibe-product」。正解入口=collabd.py --set-life 已完成
(⛔ 不是手改 goal.json:goal_life_set() 在写盘后会自动调 supervise_stop(),
手改只写字段、常驻照跑)。
cd E:/ProgramData/AIProject/vibe-product
python .workbuddy/collab/collabd.py --set-life 已完成 \
--by "pm-board-main-20261006" --check-why "三路全过、队列全 done ⇒ 用户拍板 A 方案:收工"
回显:✅ 目标状态:进行中 → 已完成 |常驻程序:已优雅退出(pid 432,因:目标已完成…)
🔴 三条取证(⛔ 不看回显就报成功):
| 观测量 | 收工前 | 收工后 |
|---|---|---|
tasklist /FI "PID eq 432" |
进程在 | 没有运行的任务匹配指定标准 ⇒ 真没了 |
_collabd.log 35 s 增量 |
32 s 涨 1357 B(每分钟约 2.5 KB 空转) | 0 字节 |
goal.json.lifecycle |
进行中 | 已完成(lifecycle_at/by/why 留痕齐) |
⚠️ 备份在 tmp/supervise-inbox/goal.json.bak-setlife-20261006-0926(11160 B)⇒ 可回滚。
🔴 连带结论($43.3 三条新问题):① 已消解(收工即停写投影);③ 不成立(collabd.py
副本与技能源 md5 都是 955bd92009d7cd0cccc53d867f591b49 ⇒ 已同步,上轮记的"未核"要划掉)。
②「NEXT.md 过期投影」的共性缺陷仍在(paused_round() 只在 --once 分支跑)
—— 停了这个区只是治标,⛔ 未修机制。
44.2 界面返修三处(用户当轮四条反馈)
① 「卡片挤到一起去了」—— 真因=嵌套 grid(P0 级,我自己上一轮引入的)
renderAll() 写 cols.innerHTML = '<div class="cols">' + …,而容器 #cols 本身就带
class="cols" ⇒ 内层 div 成了外层 grid 的一个 grid item,先被压到 min-width:auto
(=内容宽 399 px),再在 399 px 里自己切 4 列 ⇒ 每列 89 px、卡片 67 px。
实测:col 89px / card 67px / 页高 3192px
修后:col ~400px / card 381px / 页高 1415px
✅ 判据:#cols 是容器时,内层⛔ 不能再套 .cols。同族坑:光看 CSS 全对
(.cols{grid-template-columns:repeat(4,minmax(0,1fr))} 逐字正确),读样式表查不出错
⇒ 必须量 getBoundingClientRect()。⚠️ 我上一轮"统一视图"改造时引入,用户肉眼先发现的。
② 「卡片上下之间的距离太远」 grid 默认 align-items:stretch ⇒ 12 张卡全在「已完成」、
另三列空着,四列被拉成等高 ⇒ 整页虚胖。改 align-items:start。
连带:卡片 gap 9→6、.cards padding 10→8、.col min-height:160px→0、main 下 padding 60→40。
③ 工作区 / 目标 两个维度串了(用户逐字纠正:「下拉框是选工作区的 跟目标没关系」)
我上一版写「全部(N 个目标)」,而 N 取的是 projects.length = 工作区条数
⇒ 把两个维度混成一个数(用户看到"3 个目标"才追问"本机不是只有一个目标吗")。
事实(/api/projects 实测):
| # | dir | name(改前) | has_goal | 卡片 |
|---|---|---|---|---|
| 0 | ai1net-dsh-server |
本机协作 | true | 1 |
| 1 | vibe-product |
vibe-product | true | 11 |
| 2 | agent-product |
(未命名 · agent-product) | false | 0 |
⚠️ 我一度把 #2 判成"空壳"、建议藏起来 —— 用户否了:「加载机制就可以显示」
⇒ 它装了机制只是没登记目标(目录里没有 goal.json,但有 collabd-state.json
+ _ensure.stamp)。用户说它的目标是「加载机制」。
⇒ 口径:装了机制的工作区一律列出,⛔ 不许因"没目标"就藏(同族「作用域不许静默排除」)。
改后:① 标签「项目」→「工作区」;② 选项文字=目录名(+目标名);
③ 「全部(3 个工作区)」;④ 概要条分开报「工作区 3 个 / 其中已登记目标 2 个」;
⑤ ! 弹层措辞改「装了机制但还没登记目标」。
④ 「显示的是目录名,应该是真正名称」 project-board.py:209 原 name = short or title
⇒ 登记过 short 的区一律显示短名。改 title or short(都没有 ⇒ 「未登记目标 ·
🔴 我怀疑过是收工操作的副作用,查完是虚惊:goal.json 的 title/short
收工前后逐字一致(与备份 diff)⇒ 不是新问题,是老口径。
⇒ 教训:报"疑似副作用"前先 diff 备份,⛔ 别让怀疑变成向用户上报的事实。
44.3 卡片信息重排 + 本工作区高亮
- 工作区名移到卡片末行(用户:「工作区名称要不要 放到卡片下面」)⇒ 采纳。
.wsrow(上边框分隔)+.wsname(等宽字体)+.wsself角标「本工作区」。 ⛔ 不再混在 badge 堆里(那样会被忽略)。 - 本工作区卡片换色(用户:「本工作区的卡片是不得 换个颜色」)⇒
.card.is-self蓝底 + 蓝框 + 标题提亮。🔴 判定来自服务端self字段,⛔ 前端不硬编码目录名: 启动器注入DSH_PM_BOARD_SELF_WS=启动器文件位置往上两级。 ⚠️ ⛔ 不能用Path.cwd()—— 实测计划任务的工作目录是交付物/项目管理界面, 不是工作区根,那样算出来的"本工作区"是错的。 - 修时间戳印成
1791957640.7257342:台账t_start/t_end有两种形态 (ISO 串 / 纯数字 Unix 秒),原fmtWhen只认前者、不匹配就原样返回。 补:1e9 < n < 4e9⇒ 走new Date(n*1000)转本地时间。
44.4 浏览器侧验收(沿用的两条硬规矩)
- 必须强刷新:
new_tab后直接读 DOM 会读到旧缓存(本轮又差点误判)。 ✅cdp("Network.setCacheDisabled", cacheDisabled=True)+cdp("Page.navigate", …)+wait_for_load()+sleep(3)。 - 量尺寸⛔ 不靠读 CSS:本轮"CSS 全对但渲染全错"就是靠
getBoundingClientRect()抓到的。 - ⚠️ harness 预导入里没有
sleep⇒ 脚本头部自己import time; def sleep(s): time.sleep(s)。 - ⚠️ 探针选择器要跟着结构改:去掉内层
.cols后#cols > .cols > .col恒为 0 条 (不是布局坏了)⇒ 改#cols > .col。