diff --git a/dsh-server-docs/02-运维手册.md b/dsh-server-docs/02-运维手册.md index bb465d3..46373fb 100644 --- a/dsh-server-docs/02-运维手册.md +++ b/dsh-server-docs/02-运维手册.md @@ -208,8 +208,10 @@ curl -s -X POST http://127.0.0.1:8900/api/llm/listProviders \ -H 'Content-Type: application/json' \ -d '{"type":"client-request","rpcId":"v1","method":"llm/listProviders","payload":{"args":{}}}' -# 测试 session 生成(门户 API 直调,10 分钟) +# 测试 session 生成(**PG 直插**,连接串取自 env / dshs.env / dshs.service.d;10 分钟;user_agent=poc-curl2) node /opt/dshs/mksess.cjs +# ❗ 勘误(2026-09-17 序⑬ Q1 实测):档案 77 与更早文档对该脚本存储机制的描述属**当时形态**(写 `/var/lib/dshs/dshs.db`); +# 集群化后权威库 = 47 的 PG13(DSHS_DB_URL)、那份文件仅回滚用 ⇒ 现实现 = **PG 直插**,旧描述已失效。 # 普通用户角色化 profile patch(隐藏「模型」设置分区,档案 09;admin 不受影响) node /opt/dshs/ensure-role-profile-patch.cjs [--restart] diff --git a/dsh-server-docs/INDEX.md b/dsh-server-docs/INDEX.md index b8a7e35..8a6b2ab 100644 --- a/dsh-server-docs/INDEX.md +++ b/dsh-server-docs/INDEX.md @@ -19,6 +19,7 @@ | **开源导出 / 发新版本** | **`skills/dsh-opensource-release/SKILL.md`**:五条硬规则 R-O1–R-O5(源仓库只读 / 探针 0 命中 / 去插件 / 不带文档与 skill / 分层授权)+ 脱敏映射表 + **迭代 SOP** + 验证六件套;产物在本机 `_开源导出_20260913/` | | **排查业务插件故障(没 UI / 装不上 / 改了没生效)** | **`skills/dsh-plugin-diagnose/SKILL.md`**:三层归属 + 三把尺子(inject 差集 / glibc 直测 / 产物插探针)| | **换电脑 / 改了工作区路径,规则会不会丢** | **`skills/dsh-env-bootstrap/SKILL.md`**:常驻规则快照 + `--check` 校验 / `--inject` 注入 / `--env-check` 环境自检(默认只报不改)| +| **跑跨会话长任务(自动接力)** | **`skills/dsh-auto-handoff-chain/SKILL.md`**:六件套 prompt 骨架 + 登记门禁 + 五条实测防护 + 复跑脚本 `scripts/chain_report.py` | | 看还有什么没做完 | `03-路线图与待办.md` §二 | **`交接单/README.md` §一**(已规划待执行) | | **改前端页面(强制基线)** | **`06-工作台UI规范.md`** | | **改实例 UI 分区(设置面板)** | **`07-实例UI分区登记表.md`**(哪个包提供 / 源码在哪 / 能不能改)+ `06-工作台UI规范.md` | @@ -165,6 +166,7 @@ | — | ✅ | `skills/dsh-opensource-release/SKILL.md`:**开源导出与版本迭代**(五条硬规则 R-O1–R-O5 · 脱敏映射表唯一口径 · 分层授权与其 MIT 法律前提 · 迭代发布 SOP · 验证六件套 · 8 条实测坑)(工作副本在本机 `.workbuddy/skills/`)| | — | ✅ | `skills/dsh-plugin-diagnose/SKILL.md`:**业务插件故障诊断**(三层归属 host / client / 网关·原生绑定 · 三把尺子 inject 差集 / glibc 直测 / 产物插探针 · 8 条实测坑)(工作副本在本机 `.workbuddy/skills/`)| | — | ✅ | `skills/dsh-env-bootstrap/SKILL.md`:**环境引导 / 迁移**(常驻规则快照 `references/常驻规则-快照.md` + `scripts/resident-rules.py` 的 `--check / --inject / --env-check / --snapshot`;**权威方向单向**:CODEBUDDY.md 为权威、快照为副本)(工作副本在本机 `.workbuddy/skills/`)| +| — | ✅ | `skills/dsh-auto-handoff-chain/SKILL.md`:**多棒自动接力编排法**(规划棒 ↔ 执行棒交替 · 一次性 automation 链条 · 六件套 prompt 骨架 · 登记门禁 · 五条实测防护 · 断链 / 双开 / once 不转完成态 等 · `scripts/chain_report.py` 复跑)(工作副本在本机 `.workbuddy/skills/`)| > **待办与优先级不在本文件维护**(单一来源):**未规划**的见 `03-路线图与待办.md` §二;**已规划待执行**的见 `交接单/README.md` §一。 diff --git a/dsh-server-docs/README.md b/dsh-server-docs/README.md index 69c49f6..8f56623 100644 --- a/dsh-server-docs/README.md +++ b/dsh-server-docs/README.md @@ -32,8 +32,9 @@ | **`06-工作台UI规范.md`** | **前端 UI 强制基线**:设计 Token(色/字/圆角/阴影/间距)、布局框架与滚动规则、页面路由、组件规范(按钮/输入/表格/Tab/卡片/徽章/弹窗/Toast/空状态/分页/评分/图标)、关键交互约定、9 条已知坑与规避 | **开发/改任何前端页面前必读并遵循**(门户页 desktop/admin/skills/plugins + 工作台页);权威源 `mcn-work-shop/docs/工作台UI规范.md` | | `skills/dsh-change-workflow/` | 平台改造工作流 skill(**v2.9.2**):需求识别→调研→规划→开发→验证→归档六阶段 + 红线机制 R1-R11 + 档案模板 + **多任务并行调度协议**(按冲突域定并行度)+ **「三把锁」落地机制**(全局执行锁 / 单级占用锁 / 服务器侧操作锁) | 执行平台功能改造时对照本流程。**同步方向:工作副本在本机 `.workbuddy/skills/`(技能必须本地加载),本目录为服务器归档副本 —— 改动后单向推进来(本机 → 此处),勿反向覆盖** | | `skills/dsh-env-bootstrap/` | 环境引导 / 迁移 skill(**v1.0.4**):把工作区 `CODEBUDDY.md` 的**关键章节**做成技能内**快照**,并给出 `--check`(校验关键规则齐备,默认只报)/ `--inject`(标记块内替换,首次需 `--init`)/ `--env-check`(路径与 hooks 命令自检)/ `--snapshot`(由权威重生成快照) | 换电脑、换工作区路径、新环境初始化时用。**设计红线:权威方向单向(CODEBUDDY.md 为权威,快照为副本),避免造第二真相源** | -| `skills/dsh-decision-method/` | **改造决策方法论** skill(**v2.7.4**):决策素材库(用户有效决策 U1-U28 / AI 有效决策 A1-A25 / 反例 X1-X14,已拆到 `references/`)+ **如何确认最优解**(判定矩阵 + 拍板前十问 + 验收口径 L1-L5)+ 决策流程十步 + 交互 UI 改造专项清单 + 决策语言对照表 | **先定案再执行**:本技能管「怎么想、怎么定」,`dsh-change-workflow` 管「怎么落地」。素材源 = 本库 68 份改造档案的真实决策痕迹。**同步方向同上(本机 → 此处,勿反向覆盖)** | +| `skills/dsh-decision-method/` | **改造决策方法论** skill(**v2.8.0**):决策素材库(用户有效决策 U1-U28 / AI 有效决策 A1-A25 / 反例 X1-X14,已拆到 `references/`)+ **如何确认最优解**(判定矩阵 + 拍板前十问 + 验收口径 L1-L5)+ **技术选型判据轴 §4.6**(⛔ 热度 ≠ 安全/性能;先立轴再排序;必查默认值 + CVE 历史)+ 决策流程十步 + 交互 UI 改造专项清单 + 决策语言对照表 | **先定案再执行**:本技能管「怎么想、怎么定」,`dsh-change-workflow` 管「怎么落地」。素材源 = 本库 68 份改造档案的真实决策痕迹。**同步方向同上(本机 → 此处,勿反向覆盖)** | | `skills/dsh-feature-first/` | **功能优先协作协议** skill(**v1.7.0**):复盘证实「技术类 40% + 报障类 24% 吃掉用户注意力」→ 用户的输入收窄为**功能卡 4 问**;AI 自主决策的 **9 类白名单(永不问)**;只上抛**功能语义分叉与红线门禁**;上抛语言转换表;报障闭环前置五条;交付回执格式(功能性语言优先 + 技术附录折叠);**§5.3 五条铁律 / §5.4 九条硬约束:待确认项 = 回复最后一节、逐条编号、候选竖排成段(不横排)且必须写「优点 / 缺点」** | **改协作方式时对照本协议**:把"事前请示"改成"**默认自主 + 事后可推翻**" | +| `skills/dsh-auto-handoff-chain/` | **多棒自动接力编排法** skill(**v1.3.2**):把跨多个上下文窗口的长任务拆成「规划棒 ↔ 执行棒」交替的**一次性自动化链条**,每棒做完自动开新会话接下一棒(零人工点击)。核心 = 六件套 prompt 骨架(状态单点 → 唯一执行依据指针 → 全局锁 → 单一动作 → 成本纪律 → 收尾四件套)+ **登记门禁**(要拍板的先等拍板再登记)+ 五条实测防护(断链 / 双开 / once 不转完成态 / 跨过拍板点 / 下一棒定太晚)+ 复跑脚本 `scripts/chain_report.py` | **跑跨会话长任务时用**:与 `dsh-change-workflow`(单次改造落地)、`dsh-decision-method`(单次取舍)分工互补;唯一执行依据 = 工作区 `接续入口_<线名>_<日期>.md`。**同步方向同上(本机 → 此处,勿反向覆盖)** | | `交接单/` | **规划会话 → 执行会话的任务单**(目标 / 只读前置 / 范围 / 决策点 / 步骤 / 验收 / 回滚 / 回报格式)+ 交接约定 | **要执行某个任务时才读**;单子完成后移入 `archive/交接单-已完成/` | | `04-调整方案/` | 每个功能改造一份档案:需求 → 改动文件 → commit → 验证结果 | **核对用**:某功能当时怎么改的 | | `archive/` | `dsh-improvement-plan-20260909-full.md`:拆分前的 19 章完整时间线归档 | 保留历史全文,防拆分遗漏 | diff --git a/dsh-server-docs/scripts/stop-dialog-guard.py b/dsh-server-docs/scripts/stop-dialog-guard.py index 9a24684..a537ddd 100644 --- a/dsh-server-docs/scripts/stop-dialog-guard.py +++ b/dsh-server-docs/scripts/stop-dialog-guard.py @@ -272,7 +272,8 @@ ALERT_LEVEL_REL = os.path.join('.workbuddy', '.budget-alert-level') # ③ 因此本级**不做"自动开"**,做**"强制收口"**:把状态固化成文档,让下一个会话能无损接上。 LV_PREFIX = { 1: '', - 2: '⚠️ 上下文已过 **20 万**,本轮收口后建议开新会话。\n', + 2: '⚠️ 上下文已过 **20 万**,本轮收口后建议开新会话。\n' + ' ⚠️ 本轮若你**登记了自动接续(automation)/ 要开新会话** ⇒ **必须在给用户的回复里用陈述句说明**(别悄悄做掉)。\n', 3: ('🔴 **已过 30 万 ⇒ 进入强制收口模式**(先落盘、再接续):\n' ' ① 把在途状态写进 `.workbuddy/memory/`(今日日志 + 必要的 MEMORY.md 条目);\n' ' ② 产出**接续包**:目标 / 已完成 / 在途 / 未完成 / 下一步 / 关键决定 / 回滚点' @@ -282,7 +283,19 @@ LV_PREFIX = { ' prompt **照 `会话接续规范 §3.2.1` 模板**:⓪ 先跑 `state.py` + 接续包路径 + 开机四步 + 工具调用上限;\n' ' ⛔ prompt 里**不许复制任务细节**("细节的唯一来源是交接单/接续包";实测:抄细节的 prompt 会挤掉"开机四步",' '那轮跑了 40 次调用 / 9.37 分);\n' - ' ④ ③ 若做不成(工具不可用等)⇒ 再明确告知用户「请开新会话,接续点在 X」,**由用户开**。\n' + ' 🔴 **prompt 必须带「接续包 md5」**(`md5sum <接续包路径>`)—— 新会话开工前会重算校验,' + '不符即停、报告「口径已更新,需重新接续」(2026-09-16 实测:登记 17:01 → 触发 17:03,' + '而原会话一路工作到 18:21、17:2x–17:29 还在改判断 ⇒ 新旧两张皮);\n' + ' 🔴 **登记后若你还要改接续包 / 改关键判断 ⇒ 回来撤销或重登记**这条 automation ——' + '⛔ 不重登记 = 下一棒按旧口径开工且无从知道;\n' + ' ④ ★**必做 · 告知用户**:在**给用户的最后一条回复里**(用陈述句,不是征询句)写明这次自动接续 —— ' + '例如「已登记自动接续:约 N 分钟后自动开新会话继续(**不需要你操作**);接续点 = `<文件>`;' + '若想自己开,口令 = ``」。\n' + ' ⛔ **这不是可选项**:新建会话 / 新建自动化是**用户可感知的状态变更**' + '(提问闸门 A 类原话:「AI 会不会悄悄改他的设置」)—— 悄悄做掉不告知,' + '用户会在会话列表里凭空看见多出一个会话而不知何来。\n' + ' 📌 2026-09-16 用户实测反馈:「**他在最后一个回复结尾没说这个事,导致我不知道**」⇒ 本条即由此而来。\n' + ' ⑤ ③ 若做不成(工具不可用等)⇒ 兜底同样要告知:「请开新会话,接续点在 X」,**由用户开**。\n' ' ⛔ 不要在本会话继续开新任务 —— 每多跑一轮,成本按当前水位线性放大。\n'), } diff --git a/dsh-server-docs/skills/dsh-auto-handoff-chain/SKILL.md b/dsh-server-docs/skills/dsh-auto-handoff-chain/SKILL.md new file mode 100644 index 0000000..d5ab964 --- /dev/null +++ b/dsh-server-docs/skills/dsh-auto-handoff-chain/SKILL.md @@ -0,0 +1,237 @@ +--- +name: dsh-auto-handoff-chain +description: 长任务「多棒自动接力」编排法 —— 把一个跨越多个上下文窗口的大任务,拆成「规划棒 ↔ 执行棒」交替的一次性自动化链条,每棒做完自动开新会话接下一棒,**全程零人工点击**。当用户说「自动新建会话接续处理」「接力跑下去」「多步骤任务自动推进」「跑完一棒自动接下一棒」「无人值守推进」,或一个任务预计要跨 ≥3 个会话 / 超过一个上下文窗口时触发。核心 = 六件套 prompt 骨架(状态单点 → 唯一执行依据指针 → 全局锁 → 单一动作 → 成本纪律 → 收尾四件套)+ **登记门禁(★要拍板的,等拍了再登记 —— 用户 2026-09-17 明令,当天已有实测事故 §3.3.1)** + 五条实测防护(断链 / 双开 / once 不转完成态 / 跨过拍板点 / **下一棒定太晚**,间隔纪律见 §3.1.1)+ 实测成本基线 + 复跑脚本 `scripts/chain_report.py`。⛔ 两条关键判据:**prompt 里绝不抄任务细节**(细节只有一个漂移源 = 入口文件的「本轮动作」块);**先判「是不是要拍板」,未命中才轮到「候选排不排得出优劣」—— 顺序颠倒就会自我扩权**。 +version: 1.3.2 +updated_at: 2026-09-17 +created_from: 覆盖网络线 19 个会话(2026-09-16 ~ 09-17)的实测复盘 —— 09-17 链条连续 8 棒零断链,规划棒成本降至旧形态的 1/8–1/12 +agent_created: true +--- + +# dsh-auto-handoff-chain — 多棒自动接力编排法 + +> **一句话**:不要用「一条 prompt 跑完整条工作线」,而是让**每一棒只做一件事**,做完**自己把下一棒排上**。 + +用户原话(2026-09-17 立项): +> 「每次执行完毕后 自动根据方法决策 自动新建会话接续处理,**在处理多步骤任务的时候非常好**」 + +--- + +## 0. 与其他技能 / 文档的分工 + +| 谁 | 管什么 | +|---|---| +| **本技能** | **怎么把长任务排成一条自动接力的链条**(编排层) | +| `dsh-change-workflow` | 单次改造怎么落地(六阶段) | +| `dsh-decision-method` | 单次取舍怎么定得对(**规划棒应加载**) | +| 工作区 `会话接续规范_20260916.md` | 上下文超限时的**接续包 v2 模板 / 开机四步 / 多线并行**(本技能是其"多棒编排"的补充,**不重复**它已写的接续包字段与成本公式) | +| 工作区 `接续入口_<线名>_<日期>.md` | **每一条工作线的唯一执行依据**(本技能的核心依赖) | + +--- + +## 1. 形态:规划棒 ↔ 执行棒 交替 + +```text +规划棒①(出交接单)→ 执行棒①(照单落地)→ 规划棒②(出下一单)→ 执行棒② → … + ↑ 每棒 = 一个全新会话 + 一条一次性 automation,做完自己登记下一棒 +``` + +**为什么拆成两种棒(不是随便切段)** + +| 棒 | 只做什么 | 成本量级(实测) | +|---|---|---| +| **规划棒** | **只出交接单**:不改服务器、不改代码、不部署 | 23–56 次调用 / 5.7–9.4 积分 / 3–10 min | +| **执行棒** | **照单落地**:按单子的 S0–Sn 逐条执行、逐条验收 | 93–215 次调用 / 23–29 积分 / 21–41 min | + +⇒ 这是「**规划与执行分离**」从纪律变成**机制**:规划棒物理上碰不到生产,执行棒物理上不必做取舍。 + +**⛔ 什么时候不要用这套** + +- 一次性小任务(直接做,别排链条) +- 步骤可并行(用并行会话 + 全局锁,不需要串成链) +- **任务形态本身就贵**(例:批量改写 N 份文档)—— 那只该**先写脚本一次跑完**;接力**救不了**贵的活,只换场地 +- 单棒做不完(会超出上下文预算)⇒ 说明**棒还要再切细**,或改用「接续包 v2 + 开机四步」 +- 🔴 **链条上存在「需要用户拍板」的决策点** ⇒ 可自决的段落照常接力,但**必须在拍板点前停下**,不许自动跨过去(**见 §3.3 登记门禁**) + +--- + +## 2. 六件套 prompt 骨架(照抄填空) + +> **位置**:每棒的 prompt 写在 `automation_update` 的 `prompt` 字段里。 +> **要点**:整段 400–1,300 字符。**越长越糟** —— 每多抄一个技术细节,就多一个漂移源。 + +```text +<线名> · <第 N 棒:规划棒 / 执行棒>(本轮只做这一件事,做完即停)。 + +第 0 步:跑 `<绝对路径>/state.py` 看状态(只读、免抢锁、1 次调用拿到锁/git/入口/收口)。 +第 1 步:抢全局执行锁 `bash "<绝对路径>/handoff-guard.sh" --claim-exec "<线名>-<第N棒>"`;**抢不到 = 有会话在跑 ⇒ 只报告并立刻停**。 +第 2 步:读**唯一执行依据** = `<入口文件绝对路径>` 的 **§2「🎯 本轮动作」块**,按它点名的那份交接单开工。 + +(规划棒专属)本轮任务:出可执行交接单,落盘 `<路径>`,按 8 段模板:目标 / 只读前置 / 范围 / 决策点 / 步骤 S0–Sn / 逐条验收判据 / 回滚 / §8 回报格式。 +(规划棒专属)⛔ 开工前先加载技能 `dsh-decision-method`(取舍判据以它为准,不凭记忆)。 + +约束:⛔ 不 commit / push;⛔ 不做 <明确点名的排除项>;⛔ 不重做 <已收官的序号>;⛔ 不扩大单子范围(单外发现的缺陷先报告、不动手)。 +成本纪律:批量活先写脚本再让脚本跑;取证最多 3 条命令;⛔ 不要 Glob/Grep 全库摸底;一轮内工具调用次数尽量压低。 + +纪律:技术实现项**自决不上抛**;只有「没有客观优劣」的取舍才列候选,且每个候选必须写「优点 / 缺点」、**候选竖排成段**(不横排);判据必须可被第三方复现。 + +收尾(缺一即算未完成):① 释放锁 `--release-exec`;② **先过登记门禁(见 §3.3)**——只有「下一棒可自决」才登记,**`scheduledAt` = 此刻 + 2~5 分钟(见 §3.1.1,⛔ 不许留长等待窗口)**;用**陈述句**告知「已登记自动接续、约 2~5 分钟后自动开新会话、接续点 = X」;③ 把入口 §2「🎯 本轮动作」推进到再下一棒;④ 写工作区日志。 +``` + +### 2.1 三条骨架为什么长这样(都有实测出处) + +| 骨架 | 治什么 | 实测依据 | +|---|---|---| +| **第 2 步 = 指向入口,不抄细节** | **细节漂移**(细节有两个来源 ⇒ 必然打架) | 09-16 那版 prompt 重述了整段技术细节 ≈1.2 KB,反而**挤掉了"开机四步"那一行** ⇒ 40 次调用 / 9.37 积分(立项口径 ≤10 次 / ≈1 分) | +| **「本轮只做这一件事,做完即停」** | 无人值守时的**自我扩权** | `b08b1c35`:用户只说「先确认待办」,第 28 次调用**已在写代码** | +| **「取证最多 3 条命令」** | 防御性过度取证 | 同一会话第 7–24 次**连续 17 次取证**,reasoning 里三连自我加码「取证非常完整了」 | +| **纪律块(自决不上下抛 + 候选写优缺点)** | **把决策方法内联**(新会话读不到旧上下文,方法论不会自己进来) | 09-17 实测:9 棒里 `Skill` 调用 **0 次** ⇒ 判据全靠这段内联文字撑住 | + +### 2.2 ★ 已知缺口:Skill 调用 = 0(本技能给出的修法) + +**实测**(09-17 全部 9 棒):`function_call.name == "Skill"` **一次都没有**。判据是 prompt 里的**内联摘要**在起作用,完整方法论从未进上下文。 + +**修法**(分棒区别对待,别一刀切): + +- **规划棒必须加**:`⛔ 开工前先加载技能 dsh-decision-method`(规划棒基数只有 23–56 次调用,+1 次可接受,且它确实要做取舍、要出单) +- **执行棒可以不加**:单子已经把判断写死了,再加载方法论是纯开销(执行棒基数已 93–215 次) + +--- + +## 3. 收尾四件套(缺一即算未完成) + +> 这四件里**第 ② 件是唯一会"断链"的地方**,也是钩子**做不到**的地方(钩子不能创建会话、不能创建自动化)。 + +| # | 动作 | 判据 | +|---|---|---| +| ① | 释放锁 `--release-exec` | 跑一次信息模式确认已释放 | +| ② | **先过 §3.3 登记门禁** → 登记下一棒的一次性 automation(**`scheduledAt` = 此刻 + 2~5 分钟**,见 §3.1.1)+ **在给用户的回复里用陈述句告知** | 门禁不过 ⇒ **不登记,改为告知"链条已暂停待拍板"**;陈述句三要素:约 2~5 分钟后自动开新会话 / **不用你操作** / 接续点 = X | +| ③ | 把入口文件 §2「🎯 本轮动作」**推进到再下一棒** | 入口 = 下一棒的**第一信息源**;不推进 ⇒ 下一棒照旧口径做,做重工 | +| ④ | 写工作区日志(当日 `memory/YYYY-MM-DD.md` 追加自己的小节) | 只追加自己的小节,⛔ 不重写别人的段落 | + +### 3.1 收尾陈述句模板(实测原文,照抄) + +```text +已登记自动接续:一次性 automation ``,约 5 分钟后(08:37)自动开新会话,不用你操作; +接续点 = 序 ④「443/TCP 兜底」的规划棒(出 `交接单_443兜底_20260917.md`,只出单、不改服务器)。 + +收口:锁 抢 ✓ → 释放 ✓(08:32)|未 commit / 未 push|入口 §0/§2 已刷到「③ 已完成 → 下一棒 ④」|产出物已交付。 +``` + +**⛔ 只登记不告知 = 缺陷**:新建会话是**用户可感知的状态变更**。实测:`408636f2` 登记了自动化却一字未提,用户两小时后自己发现才追问。 + +#### 3.1.1 🔴 间隔纪律:`scheduledAt` = 收口时刻 **+ 2~5 分钟**(2026-09-17 用户追问后定) + +- **实测反例**:序⑦ 执行棒 12:2x 收口,把下一棒定在 **12:50**(留 ~25 min)⇒ 用户 12:26 直接追问「**为什么要等20多分钟才执行接续会话**」。当时的自我理由是「给用户留一个在本会话追改的窗口」+「让旧锁自然陈旧」——**两条都站不住**:锁在收尾 ① 里**已经释放**(不存在"等锁陈旧"),而"追改窗口"等价于**主动制造 20 分钟空转**。 +- **判据**:收口那一刻,链条上**没有任何"要等的对象"** ⇒ **等待时间越短越好**(2~5 min 只用于避开调度器的最小提前量与文件落盘竞态)。 +- **唯一允许拉长的情形**:下一棒**明确要等一个外部窗口**(对方服务重启完 / 另一条棒在跑 / 用户拍板)⇒ 可以拉长,但**必须在陈述句里写明在等什么**。 +- ⛔ **不许把"给用户留追改窗口"当理由** —— 用户要的是**尽快推进**,追改可以在任何一轮直接打断。 + +#### 3.1.2 🔴 下一棒的 **automation id 只能来自工具返回值**(2026-09-17 实测踩坑) + +- **实测反例**:序⑧ 执行棒收口时,我先把「下一棒 id = `ad8d1c4f-…`」写进了自动化记忆文件,**之后**才调 `automation_update` 建单 —— 真实 id 是 **`0c3feee5-…`**(工具生成,无法预知)⇒ 记忆文件里留下一条**指向不存在的自动化**的假 id,必须回头再改一次。 +- **正确顺序**(不可颠倒):① 先 `automation_update(mode=list)` 查重 ⇒ ② `mode=create` 建单 ⇒ ③ **从返回值取真 id** ⇒ ④ 再写记忆文件 / 入口文件 / 日志。 +- ⛔ **不许"先占位再补"**:id 是**外部生成的事实**,编造 = 判据级污染(下一位读者会拿它去 `mode=view`,查不到就会误判"链条断了")。 +- ⚠️ 同理适用于:`scheduledAt` 的实际生效值(以返回的 `nextRunAt` 为准)、被工具规范化过的 name。 + +### 3.2 ★ 登记前必须先看有没有在跑的(防双开) + +实测(序③规划棒,09-17 07:56): + +> 「执行棒会话**已经起来了**(07:53 触发,现在 07:56),所以我**不再新建接续 automation** —— 链条已经接上,多建一条只会重复开工。」 + +**判据**:登记前先确认下一棒是否**已经存在**(会话列表 / `automation_runtime_state.running`)。已存在 ⇒ **不登记,只报告**。 + +### 3.3 🔴 登记门禁:要拍板的,等拍了再登记(用户 2026-09-17 明令) + +用户原话: +> 「**需要我拍板时,等拍了再新建接续会话就行**」 + +**动作只有两句**: + +- 下一棒若含**需要用户拍板**的事 ⇒ ⛔ **不登记**;本棒正常收口,停在「待你拍板」节,并告知「**链条已暂停,等你拍板**」 +- **拍板到手后** ⇒ 再由当时的会话登记下一棒 + +**什么算「需要拍板」** = 边界外六类 + 不可逆操作: +业务目标 / 优先级 · 花钱或承诺资源 · 对外承诺与合规 · 需要用户提供凭据或审批 · 无客观优劣的偏好 · 影响面超出本平台 · 不可逆操作(删数据 / 迁库 / 清目录)。 + +⚠️ **判据顺序**:先判「是不是要拍板」;**未命中**才轮到「候选排不排得出优劣」。顺序反了就会自我扩权 —— 实测事故见 §3.3.1。 + +**唯一例外**:条件式非阻塞项(不做决定也能先开工)⇒ 可以登记,但要把「遇到即停」写进下一棒 prompt。 +> 实测正例(序④规划棒):「只有 S3 实测证明『地址覆盖 + 指定 SNI』不可行时,这一条才成为唯一缺口」⇒ 不阻塞开工 ⇒ 正常登记。 + +### 3.3.1 实测事故:序⑥ → 序⑦「拍板未定,接续已跑」(2026-09-17 用户报障) + +用户原话: +> 「**最新会话遇到需要我拍板 还是自动创建接续会话的情况,导致拍板还没定,接续会话已经开始执行**」 + +**经过**:09:52 序⑥**规划棒**把「真机来源」(要用户出设备 = 边界外)自己拍了 ⇒ 登记执行棒 → 10:58 序⑦规划棒按链条自动开跑 → **11:00 用户才给拍板** → 11:09 序⑦执行棒又自动开起来 → 11:11 用户手动喊停。**多烧 ≥2 轮,且要用户自己介入收拾。** + +**三条留档(别再犯)** + +1. **边界外的事,候选再明显更优也不许自己拍** —— 这次就是「A 零成本零等待」看着更优,但选项本身越界。 +2. **拍板项一旦上抛,本棒就不许再登记下一棒** —— 机器定时与人的拍板节奏**没有同步点**,不等就必然错位。 +3. **判「某棒是否在跑」看全局锁的时间戳**,⛔ 不是 `automation.status` —— 12 条已跑完的一次性任务,`status` 全是 `ACTIVE`。 + +--- + +## 4. 五条实测防护(都是真踩过的) + +| # | 现象 | 后果 | 处置 | +|---|---|---|---| +| ① | **漏登记下一棒**(链条断在收工处) | 无人接着做,**要等用户发现** | 实测:07:2x 那棒漏登记 ⇒ 用户 07:39 主动追问「为什么还不创建新会话接续任务」。⇒ 修法 = 把「收尾四件套缺一即算未完成」**写进每棒 prompt** | +| ② | **双开 / 重复登记** | 同一份活被跑两遍,白烧一轮 | 见 §3.2(登记前先看有没有在跑的) | +| ③ | **once 型 automation 跑完不自动转完成态** | 列表里堆积"已过期但 ACTIVE"的一次性任务;调度器补跑窗口 **12 小时** ⇒ 理论上可能被扫到、**多开一个会话** | 实测 09-17:列表里躺着 4 条过期仍 ACTIVE 的 once 型任务。处置:**保持原样**(不动既有配置)或**设为暂停**;⛔ 不要用 shell 去改库 | +| ④ | **自动跨过"需要拍板"的点** | 拍板未定就跑,**要等用户自己发现**(已有实测事故 §3.3.1) | **要拍板的,等拍了再登记**(§3.3 门禁) | +| ⑤ | **下一棒 `scheduledAt` 拉太长**(实测 ~25 min) | 链条白白空转,**用户当场追问** | 实测:序⑦执行棒 12:2x 收口、下一棒排 12:50 ⇒ 用户 12:26 追问「**为什么要等20多分钟才执行接续会话**」。⇒ 修法 = §3.1.1:**= 收口时刻 + 2~5 分钟** | + +--- + +## 5. 实测成本基线(2026-09-17 链条,连续 8 棒零断链) + +| 棒 | 工具调用 | 积分 | 时长 | 用户轮数 | +|---|---|---|---|---| +| 序③ 规划棒 | 50 | 6.66 | 10 min | 1 | +| 序③ 执行棒 | 215 | 24.65 | 41 min | 4 | +| 序④ 规划棒 | 56 | 5.73 | 6 min | 1 | +| 序④ 执行棒 | 123 | 24.90 | 23 min | 3 | +| 序⑤ 规划棒 | 25 | 7.66 | 3 min | 1 | +| 序⑤ 执行棒 | 121 | 28.76 | 21 min | 1 | +| 序⑥ 规划棒 | 23 | 9.35 | 7 min | 1 | +| 序⑥ 执行棒 | 93(进行中) | 22.99 | — | 9 | + +**对比旧形态**(同一条线、09-16「一条 prompt 跑完整条线」): + +| 会话 | 工具调用 | 积分 | 时长 | +|---|---|---|---| +| 规划覆盖网络任务落地步骤 | 207 | 77.99 | 195 min | +| R3 接续(relay 常驻 + via 切换) | 641 | 72.22 | 166 min | + +⇒ **规划棒成本降到旧形态的 1/8–1/12**;且**每棒用户轮数 ≈ 1**(只有 automation prompt 本身,**零人工介入**)。 + +**取数方法**(已固化为脚本,一条命令出全套): + +```bash +"E:/ProgramData/.workbuddy/binaries/python/versions/3.13.12/python.exe" \ + "E:/ProgramData/.workbuddy/skills/dsh-auto-handoff-chain/scripts/chain_report.py" "<线名关键词>" +``` + +它一次输出四段:① 逐会话(工具调用 / 积分 / 积分每次 / **Skill 调用数** / top tools)② 合计 ③ **自动化运行链 + 断链检测**(相邻两棒间隔 > 90 min 报 ⚠️)④ **过期但仍 ACTIVE 的一次性 automation 清单**。 + +> **实测判读**:09-17 那 8 棒间隔全部 < 90 min(**零断链**);09-16 旧形态连续 6 次 > 90 min(人工介入、无接力)。 + +底层口径(要自己写脚本时看这三行): +- 会话/自动化元数据 → `<配置目录>/workbuddy.db`(`sessions` / `automations` / `automation_runs`) +- 逐次积分 → 转录 `projects/<工作区目录名>/.jsonl`,**递归**收集 `rawUsage` 节点求和(⛔ 顶层 `d.get("rawUsage")` 抓不到) +- 工具调用次数 → 同一份 jsonl 里数 `type == "function_call"` 的行;文本元素类型是 `input_text` / `output_text`(⛔ 不是 `"text"`,写错会静默抽空) + +--- + +## 6. 落地清单(新开一条接力链时) + +1. **建入口文件** `接续入口_<线名>_<日期>.md`,把状态、定序、`§2「🎯 本轮动作」块` 写进去 +2. **让 `state.py` 能读到它**(状态脚本的 `[入口]` 段只展开入口 §2 ⇒ 钉在 §2 顶部才有用) +3. **定序**:把整条线切成「规划棒①→执行棒①→规划棒②→…」,并**写进入口 §0** +4. **起第一棒**:`automation_update` 建一次性 automation(`+2~15 分钟`),prompt 照 §2 骨架填 +5. **每棒收尾走完 §3 四件套**(其中第 ② 件**前置 §3.3 登记门禁**)—— 推进 §2 + 登记下一棒 +6. **收口**:链条跑完时,最后一棒不登记下一棒,改为**明确告知用户"链条已完结"** +7. **遇到拍板点**:停在「待你拍板」节(候选竖排 + 优缺点 + 我的倾向),**并明确告知链条已暂停**;等用户拍板后再由当时会话登记下一棒 diff --git a/dsh-server-docs/skills/dsh-auto-handoff-chain/scripts/chain_report.py b/dsh-server-docs/skills/dsh-auto-handoff-chain/scripts/chain_report.py new file mode 100644 index 0000000..c513326 --- /dev/null +++ b/dsh-server-docs/skills/dsh-auto-handoff-chain/scripts/chain_report.py @@ -0,0 +1,233 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +"""接力链体检 —— 一条命令出「某条接力链跑了哪些棒 / 每棒成本 / 有没有断链」。 + +用法: + python chain_report.py # 默认看全部会话 + python chain_report.py "覆盖网络" # 只看标题含该关键词的会话 + python chain_report.py "覆盖网络" --json # 追加输出机器可读 JSON + +数据源: + /workbuddy.db → sessions / automations / automation_runs + /projects/*/.jsonl → 逐次 rawUsage(含 credit)+ function_call 计数 + +坑(踩过的,别再踩): + * rawUsage 不在顶层,必须【递归】收集;顶层 d.get("rawUsage") 抓不到。 + * 文本元素类型是 input_text / output_text,不是 "text"。 + * 工作区目录名会随工作区搬迁改变(e-... / d-...),两个目录都要找。 + * 本机没有 sqlite3 CLI ⇒ 必须用 python 的 sqlite3,以 mode=ro 只读打开。 + * 读文件必须显式 UTF-8(newline=""),否则 CP936 静默乱码。 +""" +import sys, os, io, json, glob, sqlite3, datetime, argparse + +sys.stdout.reconfigure(encoding="utf-8") + +HOME = os.environ.get("WORKBUDDY_CONFIG_DIR") or os.path.expanduser("~/.workbuddy") +DB = os.path.join(HOME, "workbuddy.db") + + +def ts(v): + if not v: + return "-" + try: + return datetime.datetime.fromtimestamp(int(v) / 1000).strftime("%m-%d %H:%M") + except Exception: + return str(v) + + +def dur(a, b): + if not a or not b: + return "-" + try: + return "%dmin" % round((int(b) - int(a)) / 60000.0) + except Exception: + return "-" + + +def walk_usage(o, hits): + """递归收集 rawUsage 节点(顶层抓不到)。""" + if isinstance(o, dict): + for k, v in o.items(): + if k == "rawUsage" and isinstance(v, dict): + hits.append(v) + else: + walk_usage(v, hits) + elif isinstance(o, list): + for x in o: + walk_usage(x, hits) + + +def get_text(d): + """文本元素类型是 input_text / output_text。""" + c = d.get("content") + if isinstance(c, str): + return c + out = [] + if isinstance(c, list): + for x in c: + if isinstance(x, dict) and x.get("type") in ("input_text", "output_text", "text"): + out.append(x.get("text", "")) + return "\n".join(out) + + +def find_jsonl(sid, projdirs): + for d in projdirs: + p = os.path.join(d, sid + ".jsonl") + if os.path.exists(p): + return p + return None + + +def scan_session(path): + ncall = nuser = nasst = 0 + credit = 0.0 + tools, skills_missing = {}, 0 + with io.open(path, "r", encoding="utf-8", errors="replace", newline="") as f: + for line in f: + line = line.strip() + if not line: + continue + try: + d = json.loads(line) + except Exception: + continue + t = d.get("type") + if t == "function_call": + ncall += 1 + nm = d.get("name") or "?" + tools[nm] = tools.get(nm, 0) + 1 + elif t == "message": + if d.get("role") == "user": + nuser += 1 + elif d.get("role") == "assistant": + nasst += 1 + hits = [] + walk_usage(d, hits) + for x in hits: + try: + credit += float(x.get("credit") or 0) + except Exception: + pass + return dict(calls=ncall, credit=credit, user=nuser, asst=nasst, + tools=tools, skill=tools.get("Skill", 0)) + + +def main(): + ap = argparse.ArgumentParser() + ap.add_argument("keyword", nargs="?", default="") + ap.add_argument("--json", action="store_true") + args = ap.parse_args() + + if not os.path.exists(DB): + print("找不到 workbuddy.db: %s" % DB) + return 1 + + con = sqlite3.connect("file:%s?mode=ro" % DB.replace("?", "%3f"), uri=True) + cur = con.cursor() + + kw = args.keyword + sql = "SELECT id,title,created_at,last_activity_at,is_background_automation FROM sessions" + sess = [] + for r in cur.execute(sql): + if kw and kw not in (r[1] or ""): + continue + sess.append(r) + sess.sort(key=lambda r: r[2] or 0) + + projdirs = glob.glob(os.path.join(HOME, "projects", "*")) + + print("=" * 96) + print("接力链体检 · 关键词 = %s · 会话 %d 个 · %s" % (kw or "(全部)", len(sess), + datetime.datetime.now().strftime("%Y-%m-%d %H:%M"))) + print("=" * 96) + print("%-10s %-12s %-7s %-5s %-8s %-8s %-7s %-6s %s" % ( + "sid", "创建", "时长", "auto", "工具调用", "积分", "积分/次", "Skill", "标题")) + + rows = [] + T = dict(calls=0, credit=0.0, user=0) + for r in sess: + sid, title = r[0], r[1] + p = find_jsonl(sid, projdirs) + if not p: + print("%-10s %-12s %-7s %-5s %-8s %-8s %-7s %-6s %s" % ( + sid[:8], ts(r[2]), dur(r[2], r[3]), r[4], "无转录", "-", "-", "-", title)) + continue + s = scan_session(p) + per = (s["credit"] / s["calls"]) if s["calls"] else 0 + T["calls"] += s["calls"] + T["credit"] += s["credit"] + T["user"] += s["user"] + print("%-10s %-12s %-7s %-5s %-8d %-8.2f %-7.3f %-6d %s" % ( + sid[:8], ts(r[2]), dur(r[2], r[3]), r[4], s["calls"], s["credit"], per, + s["skill"], title)) + top = sorted(s["tools"].items(), key=lambda kv: -kv[1])[:5] + if top: + print(" top: " + ", ".join("%s×%d" % (a, b) for a, b in top)) + rows.append(dict(sid=sid, title=title, created_at=r[2], **{ + k: s[k] for k in ("calls", "credit", "user", "asst", "skill")})) + + print("-" * 96) + print("合计: 工具调用 %d | 积分 %.2f | 用户轮 %d" % (T["calls"], T["credit"], T["user"])) + + # ---- 自动化运行链(断链检测) ---- + print() + print("=" * 96) + print("自动化运行链(每行 = 一次触发 = 一个新会话)") + print("=" * 96) + anames = {r[0]: r[1] for r in cur.execute("SELECT id,name FROM automations")} + recs = [] + for r in cur.execute("SELECT automation_id, runs_json FROM automation_runs"): + try: + runs = json.loads(r[1] or "[]") + except Exception: + runs = [] + for x in runs: + recs.append((x.get("startedAt") or 0, anames.get(r[0], r[0]), x)) + recs.sort() + prev = None + gaps = [] + for st, name, x in recs: + if kw and kw not in (name or ""): + continue + if prev: + gap = (int(st) - int(prev)) / 60000.0 + if gap > 90: + gaps.append((ts(prev), ts(st), gap)) + prev = st + print("%-19s ok=%-5s %-6s %-14s %s" % ( + ts(st), x.get("success"), + dur(st, x.get("finishedAt")), str(x.get("conversationId"))[:12], (name or "")[:40])) + if gaps: + print() + print("⚠️ 疑似断链(相邻两棒间隔 > 90 分钟):") + for a, b, g in gaps: + print(" %s → %s (间隔 %.0f 分钟)" % (a, b, g)) + + # ---- 完成态核查 ---- + print() + print("=" * 96) + print("过期但仍 ACTIVE 的一次性 automation(once 型跑完不自动转完成态 ⇒ 有补跑窗口)") + print("=" * 96) + n = 0 + for r in cur.execute("SELECT id,name,status,scheduled_at,deleted_at FROM automations ORDER BY created_at DESC"): + if r[2] != "ACTIVE" or r[4] or not r[3]: + continue + try: + when = datetime.datetime.fromisoformat(r[3]) + except Exception: + continue + if when < datetime.datetime.now(): + n += 1 + print(" %s | %-40s | 触发时刻 %s | deleted_at=%s" % (r[0][:8], (r[1] or "")[:40], r[3], r[4])) + if not n: + print(" (无)") + + if args.json: + print() + print(json.dumps(rows, ensure_ascii=False, indent=2)) + con.close() + return 0 + + +if __name__ == "__main__": + sys.exit(main()) diff --git a/dsh-server-docs/skills/dsh-change-workflow/SKILL.md b/dsh-server-docs/skills/dsh-change-workflow/SKILL.md index 5385078..578e1af 100644 --- a/dsh-server-docs/skills/dsh-change-workflow/SKILL.md +++ b/dsh-server-docs/skills/dsh-change-workflow/SKILL.md @@ -143,7 +143,7 @@ dsh 多租户平台(服务器 47.77.182.89,dshs + dsh 0.1.2-rc.1)功能改 - R1 不触发 dsh 自动获取最新版本;版本升级走独立"升级测试→评估→修复"流程(档案 07) - R2 不改官方 dsh 主程序与缓存;扩展只走 profile 层 `dsh plugin remove/add`(tgz)官方机制 - R3 client bundle **严禁 `exports.default = apply`**——loader ESM/CJS interop 会取纯函数为插件主体 → 无 inject → `ctx.` 抛 `cannot get property ... without inject`;官方 bundle 只导出 `apply`+`inject` - - **R4 禁止用真实账号(`admin` / `guest`)做 API 登录测试**——`auth.ts` 登录时 `deleteUserSessions(user.id)` 是 last-wins 单活跃会话,**会当场踢掉用户正在用的浏览器会话**(2026-09-10 实证:curl 登录 guest 把用户浏览器 guest 会话顶掉,页面刷出 `unauthorized`)。要测登录态用 `node /opt/dshs/mksess.cjs` 直插临时 session,或注册专用测试账号用完即删 + - **R4 禁止用真实账号(`admin` / `guest`)做 API 登录测试**——`auth.ts` 登录时 `deleteUserSessions(user.id)` 是 last-wins 单活跃会话,**会当场踢掉用户正在用的浏览器会话**(2026-09-10 实证:curl 登录 guest 把用户浏览器 guest 会话顶掉,页面刷出 `unauthorized`)。要测登录态用 `node /opt/dshs/mksess.cjs` (**PG 直插**)建临时 session,或注册专用测试账号用完即删 - **专用测试账号模板(2026-09-11 实证)**:`POST /api/auth/register` → DB `UPDATE users SET role='active', approved_by=<真实 admin id>`。⚠️ 两个坑:① `users` 表**没有 `status` 列**, 审批就是改 `role`,CHECK 只允许 `admin/pending/active/disabled`(写 `'user'` 会 CHECK 失败); @@ -392,7 +392,7 @@ dsh 多租户平台(服务器 47.77.182.89,dshs + dsh 0.1.2-rc.1)功能改 1. **禁止启动 dsh 时自动获取最新版本**;版本升级独立流程(档案 07;/usr/local/etc/npmrc `update-notifier=false` 已全局生效)。 2. **不改官方 dsh 主程序与缓存**(`/usr/local/lib/node_modules/@deepseek-ai/dsh` 及依赖);扩展只走 profile 层官方插件机制。 3. **client bundle 严禁 `exports.default = apply`**(或任何 default 函数导出)——详见上文 R3 解释(@dsh-local/portal-entry v0.4.0→v0.4.1 实证教训)。 -4. **禁止用真实账号(`admin`/`guest`)做 API 登录测试**:登录 last-wins 会删该账号全部旧会话,直接把用户踢下线。改用 `mksess.cjs` 直插临时 session 或专用测试账号(档案 15 实证教训)。 +4. **禁止用真实账号(`admin`/`guest`)做 API 登录测试**:登录 last-wins 会删该账号全部旧会话,直接把用户踢下线。改用 `mksess.cjs` **PG 直插**临时 session 或专用测试账号(档案 15 实证教训)。 5. **权限可见面只准收窄,扩大必须先确认**(2026-09-11 用户新增,R5)——见下节「R5 权限扩大门禁」。 6. **R7|禁止未经确认的批量 / 全仓写入**(2026-09-12 用户新增红线)—— 见下节「R7 批量写入门禁」。 7. **R8|中断在线用户的生产变更须先确认**(2026-09-12 用户新增红线)—— 见下节「R8 生产变更知会」。 @@ -492,7 +492,7 @@ dsh 多租户平台(服务器 47.77.182.89,dshs + dsh 0.1.2-rc.1)功能改 - 门户重启后实例需重新 launch:`curl -b "sid=..." -X POST http://127.0.0.1:3080/api/dsh/enter` → 返回实例 port + token url - 实例检测:`pgrep -af "^node /usr/local/bin/dsh --profile web"`;监听:`ss -tlnp | grep :` -- 测试 session 生成:`node /opt/dshs/mksess.cjs`(DB 直插,10 分钟,user_agent=poc-curl2);清理:DELETE WHERE user_agent='poc-curl2' +- 测试 session 生成:`node /opt/dshs/mksess.cjs`(**PG 直插**,连接串取自 env / `dshs.env` / `dshs.service.d`;10 分钟;`user_agent=poc-curl2`);清理:DELETE WHERE user_agent='poc-curl2' - **存量会话档位体检(档案 36 P0-1,只读,建议定期跑)**:权限档位**会话级播种**(建会话时写 `permission/preset` + `sandbox/mode` + `approval/policy`,**resume 不重播**)→ 改默认档位只惠及新会话。查全平台还有多少会话是旧档位: ```bash for f in /var/lib/dshs/users/*/home/sessions/*/*/session.jsonl.zstd; do diff --git a/dsh-server-docs/skills/dsh-decision-method/SKILL.md b/dsh-server-docs/skills/dsh-decision-method/SKILL.md index 5f8eee2..328cf2d 100644 --- a/dsh-server-docs/skills/dsh-decision-method/SKILL.md +++ b/dsh-server-docs/skills/dsh-decision-method/SKILL.md @@ -1,7 +1,7 @@ --- name: dsh-decision-method description: dsh 多租户平台(alotbuy.com)「改造 / 优化功能交互 / UI 界面」的**决策方法论**。当用户提出一个新需求、问「这是不是最优方案 / 还有没有更好的做法」、要在多个方案里选型、要判断某个决策是否该做 / 该不该扩大范围、要**自主给技术实现选取最优解**、或者要复盘「为什么这么定」时使用。⛔ **用户点名「决策方法」/「参考决策方法」/「按你的规划」/「别问我」/「自行决策」/「自主决策」⇒ 必须立即加载本技能,不得凭记忆代替**(机制层由钩子 `dsh-server-docs/scripts/skill-load-guard.py` 强制注入加载提醒;2026-09-16 实证:用户点名后 AI 全程 `Skill` 调用 **0 次**)。核心 = 决策素材库(用户有效决策 **U1-U28** / AI 有效决策 A1-A25 / 反例 X1-X14,**素材源含 180 条用户真实发言**)+ 「如何确认最优解」的十问与判定矩阵 + **技术实现的默认裁决顺序(8 条,AI 自主用、不问用户)** + 决策流程十步 + 交互 UI 改造专项清单 + 决策语言对照表 + **复跑脚本 `extract-user-voice.py`**。**v2.0.0(2026-09-14)结构变更:素材库(U/A/X)已拆到 `references/`,按需读;本文件只留判定核心 + 触发词索引**(索引绑定可识别动作)。**与 dsh-change-workflow 分工:本技能管「怎么想、怎么定」,那个管「怎么落地」。** 与 dsh-feature-first 分工:那个管「谁定什么」,本技能管「怎么定得对」。 -version: 2.7.5 +version: 2.8.0 updated_at: 2026-09-16 created_from: 本工作区 62 份改造档案 + 5 天工作日志(2026-09-08 ~ 09-12)全量提炼 agent_created: true @@ -97,6 +97,7 @@ python3 dsh-server-docs/scripts/extract-user-voice.py --needle 复用价值 | 你正在判断什么 | 去查 | |---|---| | 「我刚被纠正了,同类还有哪些坑」 | X1(批量)· X4(归因)· X9(过度上抛)· X10(答非所问)· X11(越界)· **X12**(把用户材料当权威)· **X13**(把自己的解析失败当成版本差异)· **X14**(一批改造拆成多次中断动作)| +| **「这个库 / 方案成熟、star 高,所以选它」** | **§4.6**(选型判据轴:⛔ 热度≠安全/性能;先立轴再排序;必查默认值 + CVE 历史)| | 动手前的"别踩"清单 | 读全表 13 条 | @@ -203,6 +204,30 @@ R7-边界② 的"只报告不动手"**只针对「别人的 / 归属不明」的 --- +### 4.6 技术选型:**先立判据轴,再排序**;⛔ 不许用 star / 年龄 / 生态当选型依据(**2026-09-16 加,源自覆盖网络 relay 选型**) + +**反例(我当场犯的)**:把 `frp` 排「首选」,排查依据只有 **~10.6 万 star + 约一月一版 + 生态最好** —— 那是**「省心度」轴**,不是**「安全 / 性能」轴**。用户一句「**项目时间比较久,性能和安全性还真不一定是最好**」直接击穿,复查后**撤销排序**。 +复查出的**真实安全面**:`CVE-2026-40910`(认证绕过 + 未授权 DoS,**影响 ≥0.53.0**)|dashboard **默认 `admin:admin` 且口令明文存配置**|**`proxyBindAddr` 默认跟随 `bindAddr`**(官方自己写「**大多数指南遗漏的配置**」—— 不设则代理监听器**绑到公网**)|服务端**默认不强制 TLS**|`auth.token` 是**单一静态共享令牌**、frpc 明文存、`allowPorts` 白名单**默认关**(⇒ 一台客户端失陷可申请**任意**端口)。 +⇒ **"项目久"是双刃**:稳定 + 文档好 **vs 漏洞历史长 + 默认值停在历史约定(安全靠运维纪律补)**。 + +**四条硬规矩** + +1. **⛔ 不用「star / fork / 项目年龄 / 发布频率 / 生态」做首轮排序** —— 它们度量的是**热度与省心度**,推不出"在本案里更安全 / 更快"。**只能当同分时的决胜项**,不能当**排序主依据**。 +2. **判据轴必须从"本案的真实约束"反推**,并且**每条都要能回答"这条轴上的差异会不会改变本项目的结论?"** —— 不会 ⇒ **该轴不参与选型**。 + · 本例立出六条:**① 认证模型(身份 > 每服务密钥 > 共享 token)② 默认拒绝还是默认放行 ③ 能否零新增入站 ④ 单节点失陷的爆炸半径 ⑤ 可观测 ⑥ 生态**。 + · 结果:**frp 在 ①②④ 三条里都最差一档**(共享 token / 默认放行 / 可申请任意端口),只在 ⑤⑥ 领先 ⇒ 排序翻转为 **证书身份型(OpenZiti / Nebula)> rathole(Noise_NK 双向认证 + 每服务 token 必填)> frp**。 +3. **性能轴先自证"它是不是本案瓶颈"**:本例**第一瓶颈是 presence 不是带宽**、量级是「每 worker 几十个 HTTP 会话」⇒ **吞吐 benchmark 不参与选型**(且多为厂商/二手自测)。**真要测就测链路本身**(RTT / jitter / 带宽)—— **任何 relay 的上限由链路决定,不由实现决定**。 +4. **老 / 流行项目必查两张单子**:① **默认值清单**(逐项问"**不设它会怎样**"—— 本例 5 项里任何一项漏设都会**静默**破掉既定安全目标)② **CVE / 安全公告历史**(编号 + 影响版本区间)。**"成熟" ≠ "默认安全"。** + +**配套两条(本例同时验证)** + +- **"可选性"优先于"选对"**:先把接口抽出来(`Reachability.via` + `Rendezvous` 注册表)⇒ **换实现是 env 级切换** ⇒ 选型**可以推迟**,选错也不致命。**能在不选的情况下保留选择权,就别为"一次选对"付引入成本。** +- **替换 ≠ 无条件升级**:换第三方 = 用**我们不掌控的攻击面**替换**已收窄、且有系统补丁渠道的面**(例:sshd + `restrict,port-forwarding`)。⇒ **没有明确痛点之前,"维持现状"也是合法候选**,别把"换掉旧的"默认当成正向。 + +**判定触发器**:只要出现「成熟 / 久经考验 / 用得最多 / star 高 / 大家都在用」这类**热度型论据**给排序 ⇒ **立即反问三句**:① 这条论据落在**哪个轴**上?② 这个轴是**本案的瓶颈轴**吗?③ 它的**默认值**与 **CVE 历史**查过没有? + +--- + ## 5. 决策流程十步(新需求到手照这个走) | 步 | 动作 | 产出 | diff --git a/dsh-server-docs/skills/dsh-env-bootstrap/references/常驻规则-快照.md b/dsh-server-docs/skills/dsh-env-bootstrap/references/常驻规则-快照.md index ed75cb4..b163d8e 100644 --- a/dsh-server-docs/skills/dsh-env-bootstrap/references/常驻规则-快照.md +++ b/dsh-server-docs/skills/dsh-env-bootstrap/references/常驻规则-快照.md @@ -52,7 +52,7 @@ | **R1** | 不自动升级 dsh | 升级须走独立"测试 → 评估 → 修复"流程 | | **R2** | 不改官方 dsh 主程序与缓存 | `@deepseek-ai/dsh` **零改动**;扩展只走 profile 层官方插件机制 | | **R3** | client bundle 禁 `exports.default` | 只导出 `apply` + `inject` | -| **R4** | 不用真实账号测登录 | 用临时 session(`mksess.cjs` 直插),用完即删 | +| **R4** | 不用真实账号测登录 | 用临时 session(`mksess.cjs` **PG 直插**),用完即删 | | **R5** | **权限只准收窄** | 凡**扩大**(新挂载 / 放开遮蔽 / 暴露平台目录或 env / 放宽 nft / 提档位)→ 先出「权限影响评估」并取得确认 | | **R6** | 先查已有资产再动手 | 可用技能 → 本机 / 项目已有技能与记忆 → "本机已有的能否满足" | | **R7** | **禁未经确认的批量 / 全仓写入** | **只做被明确要求的事**;额外发现的问题**先报告、后动手**;禁全库遍历改写 / 通配符重写 / 批量 `chmod`·`chown` / **批量换行符转换** / `cp -r` 整目录覆盖 / `git add -A`;**可能影响 >10 文件 → 先出清单 + 确认**;先单点验证;**本机不是沙箱**(会经 scp 传导到生产) | diff --git a/dsh-server-docs/skills/dsh-knowledge-upkeep/SKILL.md b/dsh-server-docs/skills/dsh-knowledge-upkeep/SKILL.md index 1cde4e4..9347aa5 100644 --- a/dsh-server-docs/skills/dsh-knowledge-upkeep/SKILL.md +++ b/dsh-server-docs/skills/dsh-knowledge-upkeep/SKILL.md @@ -72,6 +72,35 @@ python3 scripts/docs-consistency.py # 事实:写死取值 + 跨页取值冲 - 判据 = 「**承诺现行**的文件不许含已废止/写死/矛盾的取值」; **历史豁免**:`04-调整方案/**`、`archive/**`、`01-规划与架构`、`02-运维手册`。 +**建议补跑(不在四件套内,但会毁可读性)—— 编码合法性** +```python +for p in all_md: # 用 open(p,'rb').read().decode('utf-8') 整块判,别逐字节 + try: open(p,'rb').read().decode('utf-8') + except UnicodeDecodeError as e: print(p, e.start) +``` +🔴 **只有几个非法字节也会毁掉整份文件**:自动编码检测判"不是 UTF-8"⇒ 走偏为 GBK ⇒ **整份读出来全乱码**。 +(2026-09-16 实测:212 KB 的日志因**开头 2 字节**坏 ⇒ `Read` 全文乱码。处置见 `PLAYBOOK §10.1`。) + +--- + +### 3.1 交接单的「口径指纹」—— 改正文必连带(2026-09-16 实测) + +| 项 | 内容 | +|---|---| +| 算法 | `tail -n +4 <单子>.md \| md5sum`(**跳过前 3 行**:标题 / 空行 / 指纹行) | +| 含义 | 前 3 行**不参与** ⇒ 改指纹行本身不破指纹;改**正文任何一字** ⇒ 指纹必变 | +| 引用点 | ① 单子第 3 行 ② 接续包/入口里对它的引用 ③ 记忆(`MEMORY.md` / 日志)里记的值 | + +🔴 **改完单子正文的正确收尾顺序**:改正文 → **复算指纹** → 写回第 3 行 → 用脚本**全文 `replace`** 所有引用点(⛔ 别手改)→ 复算自证「实算值 == 三处登记值」。 +⚠️ **一次要改多处正文时,先全改完再算一次**(实测改两轮 ⇒ 指纹算三轮 ⇒ 白绕 2 轮工具调用)。 + +**⛔ 最容易漏的不是单子,而是"单子之外"的过期口径**(本轮真漏过一次): +```bash +grep -rn '<旧口径关键词>' --include='*.md' . # 例:未修 / 待专门一轮 / 已定位、未修 +``` +- **文件顶部 `>` 摘要块最容易残留** —— 下一棒第一眼读的就是它。 +- 单子的**历史章节**(如"§11.6 当时未修")**保留原文 + 加勘误段/标题后缀**,别删 —— 历史可追溯,且不会误导跳读者。 + --- ## 4. 漂移处理 SOP(发现 → 收敛,五步)