dsh-court · 三省六部 · 朝堂
DSH 插件:把一件大任务当作圣旨交下来,朝堂自动走完 太子分拣 → 中书拟敕 → 门下审覆(可封驳退回重拟)→ 尚书派工 → 六部并行办案(六部尚书把本部敕令下发给本部四司·二十四官吏)→ 御史台按劾 ∥ 都省覆核 → 奏疏待御批。
多智能体并行由 DSH 原生 ctx.subagents 驱动,零外部依赖(不 import 任何 npm 包)。v0.8.0 起默认走长线子智能体口径:每个节点(太子/中书/门下/尚书/六部/二十四司/御史台)各养一位常任 agent(startContinuable + sendMessage,阶段之间不 dispose),面板点节点弹出的「问对」直连当事人本人,退朝即散场时统一退班(drainContinuableChildren)。宿主缺这些原语就自动退回「每阶段一个一次性 agent」的老口径并留痕。
制度与工程口径参照 cft0808/edict(三省六部制 · OpenClaw 多 Agent 编排):太子分拣、九态状态机、停滞「重试→升级→回滚」、奏折归档、官员总览与旨库。差别也写清楚:edict 是常驻多 Agent 服务(看板派发 + Redis EventBus),本插件跑在 DSH 的 subagents 原语上,一场朝议即一次函数调用链,故状态机用于校验 + 观测 + 停滞处理,不做跨进程派发。
安装
本插件是 host-plane 编排器(注册 court_* 六个工具 + /court/api 面板路由),必须落在 profile 的 host 组合里
(cordis.patch.yml 的 insert 块),不要塞进 agent preset。
从 GitHub 取源码:
git clone https://github.com/dashi666666-zxp/dsh-court.git
桌面 profile(本机 pnpm 不可用、dsh plugin add 走不通)装法是手工 link 三件事,与 dsh-arch 同机制:
# 需在 danger-full-access 下执行(要写会话工作区之外的 profile)
# 1) profile package.json 的 dependencies 加: "dsh-court": "link:<本仓路径>"
# 2) 同文件 dsh.profile.bundles 追加 "dsh-court"
# 3) profiles\desktop\node_modules\dsh-court 建 junction 指向本仓路径
改完由 DSH 配置热加载自动建 loader entry,无需重启。v0.11.0 起朝堂默认关闭,装好后要先去面板「朝堂」页拨开总开关
(或 court_control { action: "power", on: true })才能 court_hold 下旨。
复跑测试:
node tests/host-smoke.mjs # 引擎/工具/协议
node tests/client-smoke.mjs --offline # 面板静态断言(不打真 HTTP)
node tests/preview.mjs # 面板离线预览
一、工具
| 工具 | 作用 |
|---|---|
court_hold |
下旨开朝。前置:总开关要开着(v0.11.0 起朝堂默认关闭——关着时本工具直接回绝并告知怎么开)。参数:edict(必填)、context、workspace、ministries(限定若干部)、draftCount(廷议 1-3 稿并行)、reviewRounds(门下最多审几轮)、rescript(auto 回执 / human 弹真人朱批)、background、si(是否令六部下发本部四司,默认 true)、taizi(是否先由太子分拣,默认 true)、tokenBudget(本场 token 限额,v0.6.0:0/不传=只计度不禁支;v0.9.0 起运行中可调)、longLine(v0.8.0:是否走长线子智能体口径,默认 true)、quotas(v0.9.0:各部 token 分额,如 {"兵部":400000};分额用尽则该部不再派新差事)、redlines(v0.9.0:各部/御前 token 红线,如 {"兵部":120000,"_run":50000};可用额度跌破即上奏) |
court_status |
查朝堂。参数:id(留空=列近期朝议,含奏折阁存档)、detail(brief=摘要,奏疏截前 1200 字;full 或省略=完整奏疏,含敕令/六部回报/二十四司承差/御史台按劾/都省覆核/内侍监结账/待御批) |
court_ministries |
六部职权名录 + 二十四司名册 + 三省与御史台与御前内侍监的权力线 + 朝议状态机 + 旨库(无参数;写圣旨前先看) |
court_control |
朝议控制:power 总开关(v0.11.0:on:true 开启 / on:false 关闭;朝堂默认关闭,这是关闭态唯一还能用的动作,否则关了就没路再打开;关闭当场收场——在办的叫停、在朝 agent 退班) / stop 叫停(中止当前阶段,已产出的部分保留)/ resume 续办(已有敕令不重拟,只补没办完的部;若是「内侍监告匮而止」的场次,须同传 tokenBudget 加限,否则一续就再停)/ reopen 重开(v0.6.0:散场/退朝过的场次照原旨另起一场,原场存档不动,内侍监并账) / budget 内侍监调额(v0.9.0:限额 / 分部额度 quotas / 各部红线 redlines 运行中皆可加可减,传 0 即撤掉那一项) / dismiss 散朝。参数:id(power 不需要)、action、on(power 必填)、tokenBudget(resume 加限 / reopen 新场限额 / budget 新限额)、quotas、redlines |
court_dismiss |
退朝(散朝):中止正在进行的朝议并当场散场——长线子 agent(本场各衙的长驻 agent)一律退班释放,面板上官员退班、连线转灰、树收成一栏散场条(v0.8.0)。叫停过(stopped,可续办)的场次也点得动:只收场散班,状态仍留「可续办」,续办会重新点卯。参数:id,或显式 all:true;两者都不给则驳回(防误散全朝)。已退朝/已散场的场次不再改写状态,也不绕过状态机 |
court_templates |
旨库:九条圣旨模板(全面体检/代码审查/功能落地/疑难排障/结构改造/性能优化/文档补齐/并行调研/发布准备),填好占位即可下旨 |
court_hold 默认最多等 waitMs(120s),超时返回朝议 id,之后用 court_status 续查。
总开关(v0.11.0):朝堂默认关闭——装好之后不主动占宿主的子 agent 名额。要在会话里下旨,先去面板「朝堂」页把总开关拨开(或
court_control { action: "power", on: true });关闭期间面板照旧只读(历史案卷、树、文书都翻得到),只是court_hold/ 续办 / 重开 / 问对会被回绝。详见第二十六节。
什么时候该用:一件任务需要多角度专业分工、且各分片互相独立可并行(全面体检、多方向并行调研、大规模改造+验证)。 什么时候别用:单文件小改、一问一答、强串行依赖的细碎任务——那些直接自己做更快。
闲谈/寒暄会被太子挡在门外(
declined,一场朝议都不开);若你确实要开朝而太子判成闲谈,传taizi:false跳过它。
二、权力线(制度即约束)
| 角色 | 权力 | 边界(三重落地:PERSONA 提示词 + 运行期护栏 + 工具白名单) |
|---|---|---|
| 皇帝(调用方 / 真人) | 最终裁决、御批 | 不经中书、门下,不成其为敕 |
| 内侍监(御前计度线) | 传宣、计度与限额(token) | 立于皇帝与太子之间,直接对接御前;不派 agent、不办案、不估算——数字只取 DSH tokenUsage 会话投影的实测值,取不到就如实记「未报」;到限额停派新差事,判词随奏疏并呈。v0.9.0:额度运行中可调(限额/分部额度/各部红线),可用额度跌破红线即上奏皇帝与内侍监(只奏不停;某部分额用尽才不再给它派新差事) |
| 中书省(中书舍人) | 出令:取旨拟敕 | 只拟不办;敕草未经门下审覆不得下行 |
| 门下省(给事中) | 审覆、封驳(封还/涂归) | 只审不办;不得代拟,只能驳与议 |
| 尚书省(左仆射 / 都省) | 奉行、派工、勾检稽核 | 不得改敕;发现不可行须覆奏。覆核工具白名单为只读(CENTRAL_TOOLS) |
| 六部(尚书) | 在本部职权内办案 | 越部须覆奏;不得自开朝议——由 court_hold 的 sessionId 护栏运行期驳回 |
| 御史台(侍御史) | 独立监察、按劾 | 只察不办;不属六部 |
落地方式说明(勿以为「写进提示词」就完事):
PERSONA(lib/protocol.js)负责让模型知道边界;越权的兜底另有两条——① 尚书省/中书的工具白名单只有只读工具(CENTRAL_TOOLS);② 朝议子会话调用court_hold直接被 sessionId 护栏驳回(lib/engine.jsisCourtChild+lib/index.js前置检查)。POWER_LINES只用于面板与court_ministries展示,它本身不是护栏。
六部现代职能映射(史实职掌见 docs/history-sansheng-liubu.md):
- 吏部 人事与编排(铨选·考课)——分工、职责边界、验收标准
- 户部 资源与成本(度支·仓部)——预算、耗时、依赖、可持续性
- 礼部 对外与文档(贡举·主客)——文档、接口契约、体例、兼容
- 兵部 攻坚与执行(军籍·驿传)——构建、运行、测试、修复
- 刑部 审查与校验(刑法·勾覆)——安全、边界、反例、证据链
- 工部 工程与实现(百工·水部)——代码实现、结构改造、批处理
三、界面
v0.4.0 起面板是一张真正的「自上而下的树状图」(组织架构式)——不再是「一路缩进的卡片列表」: 干线一列居中(皇帝 → 太子 → 中书省 → 门下省 → 尚书省,牌与牌之间是金箭线)→ 尚书省之下一条横总线分叉出六部(六个并列的列,从总线上垂下来)→ 每部之下用 ├─/└─ 肘线挂四司(二十四司一眼看全)→ 御史台走虚线旁支(不属六部,独立监察线)。 连线全部由 CSS 伪元素的 border 画出(无图库、无 SVG、缩放不糊)。每枚方牌是「官称·品级 / 名 / 职权 / 状态药丸 / 用时」, 右上角两枚极小控件
文(看该节点文书)、问(在该节点朝堂问对):点任一个 → 该节点的文书与对话在树下方那一格里就地展开(不跳转、不新开页;收起即无)。 v0.1.5 起底色与 DSH 对话面板同源(.ct-root自身background:transparent,颜色走--dsw-alias-*主题令牌,dark 下 =--dsw-alias-bg-base=#151517)——不再自带一块#0d1117;骨架仍沿用 GitHub Primer(Dark) 的尺寸层级(1px 描边、6px 圆角、药丸标签、Counter、BlankSlate、btn/btn-sm、表格),调色板是项目自己的四色—— 主色 = 黑(跟随 DSH 主题底 +--dsw-alias-bg-layer-1/2的卡面)+ 金(#e3b341:在朝、准奏、主按钮、干线箭线); 副色 = 灰(取自--dsw-alias-label-secondary,dark 下#a2a4a6:次要文字、中性状态、描边)+ 红(#f85149:封驳、失手、危险操作)。 状态口径三色闭环:金 = 在进行 | 灰白 = 已办完 | 红 = 出问题。底色跟随所在面板(深色对话面板 → 同色;设置弹窗那种浅底 → 也跟着浅),金/红 accent 不变。 样式全部 scope 在.ct-root之下(选择器一律以.ct-root开头,连状态类也是)——不会碰到别的插件、也不会碰 DSH 自身的 UI;有断言逐条扫描样式表把关(client-smoke「样式零外泄」)。
- 树状图(v0.4.0):一场朝议 = 一棵自上而下的树
(上图是结构示意:真实面板里干线是「一列居中、牌间竖箭线」,六部是并列的列从横总线上垂下来,四司用 ├─/└─ 肘线挂在部下面,御史台在最右侧走虚线旁支。皇帝牌上是圣旨原文,点┌──────────────────────────┐ │ 皇帝 · 圣旨(敕) │ ← 根 └────────────┬─────────────┘ │ 〇 拣 ┌─────▼─────┐ │ 太子 │ 分拣:闲谈直接回 / 旨意才开朝 └─────┬─────┘ │ 一 拟 ┌──────────┐ ┌─────▼─────┐ ┌──────────┐ │ 御史台 │ ┊ ┊ │ 中书省 │ │ 门下省 │ 二 审 → 三 行 └──────────┘ └─────┬─────┘ └──────────┘ │ 三 行 ┌─────▼─────┐ │ 尚书省 │ └─────┬─────┘ ┌───────┬───────┬─────┴─────┬───────┬───────┐ ┌─▼─┐ ┌─▼─┐ ┌─▼─┐ ┌─▼─┐ ┌─▼─┐ ┌─▼─┐ │吏部│ │户部│ │礼部│ ... │兵部│ │刑部│ │工部│ └─┬─┘ └─┬─┘ └─┬─┘ └─┬─┘ └─┬─┘ └─┬─┘ ├─吏部司 ├─户部司 ├─礼部司 ├─兵部司 ├─刑部司 ├─工部司 ├─司封 ├─度支 ├─祠部 ├─职方 ├─都官 ├─屯田 ├─司勋 ├─金部 ├─膳部 ├─驾部 ├─比部 ├─虞部 └─考功 └─仓部 └─主客 └─库部 └─司门 └─水部文看全文。)- 官称与品级依《唐六典》卷八·卷九与《新唐书·百官志》:中书令/侍中正三品、尚书令正二品、六部尚书并正三品;二十四司的长官是郎中(从五品上),叶子牌上标「水部郎中 · 从五品上」(依据
docs/tang-liubu-ershisi-si-kaoju.md)。 - 尊卑的分量差:干线牌最重(衬线大字、居中),部牌次之,司叶子最小最沉(inset 底色)——层级不只靠位置,也靠分量。
- 每个节点右上的
文/问:文展该节点文书(中书敕草全文 / 门下逐轮涂归 / 尚书敕令分派+奏疏 / 部承办敕令+回报+「下发二十四司:N 司领差」/ 司的差事+本司回报 / 御史台按劾),问在该节点就地开朝堂对话。两者都渲染在树下方那一格里,收起即无(不占版面)。 - 二十四司始终可见:本轮没派工的司也以叶子列出并标「未派差事」;没开
si时四司仍列出(书志层级),只是没有文书可问。 - 面板底部三折:敕令清单 / 都省注记 / 奏折活动流;再往下是全页共用的 官员总览 与 旨库。
- 二十四司(v0.2.0 起是活的承办层级):六部尚书把本部敕令下发给本部四司(二十四官吏),四司并行承差,尚书覆核四司回报后才成本部回报。面板上「二十四司」不再是静态书志折叠,而是挂在部之下的四个司节点(d3):司名(水部)+ 长官官称与品级(水部郎中·从五品上)+ 本司史实职掌 + 下发到本司的差事 + 本司回报原文 + 本司自己那一格「朝堂对话」(scope =
si:工部:水部)。尚书没派到的那几司照样列出(「未派差事」)——二十四司看得见全貌;关掉si就退回 v0.1.x 的静态书志折叠。依据docs/tang-liubu-ershisi-si-kaoju.md(《唐六典》卷二—卷七、《旧唐书》卷四三「尚書省領二十四司。六尚書,各分領四司」)。 - 太子(v0.3.0):树的最上一层多一格「〇 拣 太子 · 分拣权」,显示分拣判定(旨意/闲谈)、提炼的标题、紧急度与验收口径;闲谈则整棵树只到太子为止(面板给出「未开朝」与太子该回的话)。
- 自上而下(v0.1.4):树容器是纵向列(
flex-direction:column),节点自御座往下依次排开(d0 → d1×3 → d2×6 → d1);每块折叠区都在它自己的节点内往下长,不横着铺、不弹到别处。 - 古代朝堂质感:匾额式页头(金线 + 回纹牙子)、朱印(敕 / 准 / 驳 / 问)、官名用衬线字(宋体一系)、事实栏做成简牍式方格(用时 / 敕令 / 回报的部 / 门下审覆 / 御史台)。
- 官称与品级依《唐六典》卷八·卷九与《新唐书·百官志》:中书令/侍中正三品、尚书令正二品、六部尚书并正三品;二十四司的长官是郎中(从五品上),叶子牌上标「水部郎中 · 从五品上」(依据
- 朝堂对话(v0.1.4:就在各节点里折叠展开,且不跳转):每个节点自带一块「朝堂对话」——默认收起只占一行(御座那格 = 整场,三省 / 六部 / 御史台各是自己那块);点节点页脚的
问对(展开后变收起)或那一行标题,就在该节点内往下展开:分支回话、输入框、止全在那一格里。卡头的朝堂问对是御座那格的快捷入口。链路:GET /court/api/branch?id=<朝议 id>&scope=<范围>→ host 生成一份自洽的问对文书(抬头写明「这是从朝堂另起的分支、先读后等令」+ 圣旨 / 敕草 / 涂归 / 敕令 / 六部回报 / 按劾 / 奏疏 / 注记);ctx.sessions.fork({ sessionId, increaseTitle: true })从当前对话另起分支(与 DSH 自带分支同一原语);retain(child)→session.prompt([{type:'text', text: 文书 + 【朕的垂询】+ 你的问题}], 'queue'):分支里的 agent 拿到全部朝堂文书后真的开始办事(它是一整个会话,能读文件、跑命令、返工);- 就地回话:面板订阅该分支的
eventSource,只取 fork 之后的新事件(seq > 基线,不把父会话旧history倒进来),把朕 / 对 / 办三种行实时渲染在树下方那一格里(每 scope 一格,点哪个节点就换哪一格,互不串台)——全程不调用uiWorkspace.openSession,不跳转,视线不离开面板。 - 后续追问只发问题本身(文书已在分支上下文里);
止可停掉当前这一轮;分支会话同时留在左侧会话列表(标题「朝堂分支 …」),想跟到底也可以自己进去看。
scope∈all/taizi/zhongshu/menxia/shangshu/censor/吏部…工部,以及每司的动态 scopesi:<部>:<司>(如si:工部:水部)与整部四司的si:<部>;单省单部只带该块(不夹带无关文书),shangshu会连六部回报与二十四司摘要一起带(都省覆核的对象就是它们),单部的分支会把该部四司的派工与回报原文一并带上。- 文书由 host 统一生成(体例一致、截断口径唯一、纯函数可单测):单段上限 6000 字、总上限 40000 字,超出留「已截断」字样。
- 前置条件:当前对话至少有一轮已完成——这是 DSH 原生 fork 的限制(空对话会返回
session/fork-unavailable),面板会把原因显示成可读文案而不是静默失败;sessions服务缺失时按钮直接禁用并说明缺什么。
- 设置页「三省六部」(
settings.section插槽,order 60):职官与权力线表 + 六部史实职掌,六部同样各自一张边框卡。 - 面板取数(客户端实际调用):
GET /court/api/state(轻量列表 + 官员总览 stats + 旨库 templates + 状态机 states + roster(含二十四司差遣))→ 展开最新一场时再取GET /court/api/run?id=(全文:敕草 / 涂归 / 各部回报 / 二十四司回报 / 按劾 / 奏疏 / 内侍监结账);分支走GET /court/api/branch?id=&scope=;散朝走POST /court/api/dismiss?id=;叫停/续办/重开走POST /court/api/control?id=&action=stop|resume|reopen;奏折阁浏览走GET /court/api/memorials。 - 奏折时间线 + 活动流(v0.3.0):每场朝议在事实栏下有一条时间线(状态机每一跳 + 该段耗时),底部折叠「奏折活动流」(流转 / 领差 / 回报 / 停滞,逐条带时刻)。停滞时卡头出现红色/金色徽记「停滞·升级N级」,鼠标悬停给出重试次数与是否已回滚。
- 叫停 / 续办(v0.3.0)/ 重开(v0.6.0):在办的朝议右上多一个
叫停(中止当前阶段、保留已产出),被叫停的显示续办(已有准敕令不重走太子/中书/门下,只补没办完的部;若上一场是内侍监告匮而止,续办会自动加限)。散朝不可续,但已散场的场次给重开(照原旨另起一场、原场存档不动、内侍监并账);叫停的场次不给重开——那一路的正解是「续办」。终态后这一场收成一栏散场条(朱印「散」+ 因何收场 + 本场用度 + 并账累计 + 「展开朝堂图」),朝堂图默认收起,展开即回到原样。 - 官员总览 / 旨库(v0.3.0):页面底部两块折叠。官员总览按角色统计出场次数、平均用时、已成/带疑/失手;旨库列九条圣旨模板并可一键复制(下旨仍由用户在对话里发起——面板不假装能替用户下旨)。
POST /court/api/dismiss与工具同一授权口径:不带id必须显式all=1,否则 400 拒绝(该路由无鉴权,故自守)。- 面板路由可自愈:apply 期
webServer未就绪(启动加载顺序)或热重载交叠导致路由丢失时,自动重试补挂(panelRetryMs,见第九节 #8);异常路径落盘~/.dsh/super-injector/dsh-court-apply.log与插件包目录(#9)。若面板显示「/court/api不可达」,先看这两个日志文件。
四、配置(插件 config)
写在 profile 的 patch 条目上(cordis.patch.yml 的 config:);下表 29 行 = 29 键(引擎 26 键 + 入口级 3 键 waitMs/promptSection/panelRetryMs),一项一行(有断言核对「引擎认的每个键,入口都得真的转发」):
| 键 | 默认 | 说明 |
|---|---|---|
enabled |
false | 总开关(v0.11.0):朝堂默认关闭——court_hold / 续办 / 重开 / 问对一律拒绝(查阅不受影响),直到在面板「朝堂」页拨开,或 court_control { action: "power", on: true }。写 true 即「装好就是开的」。见第二十六节 |
settingsPath |
~/.dsh/court/settings.json(DSH_COURT_SETTINGS 可改) |
总开关落盘位置:面板拨过就活过重启;创建引擎时定死。文件被写坏/读不动时退回「关闭」(安全侧) |
settings |
true | false = 总开关只在内存里(测试口径:别去读写用户家目录) |
waitMs |
120000(毫秒,即 120s) | court_hold 同步等待上限;超时返回 id |
reviewRounds |
2 | 门下省最多审几轮,每轮封驳退回中书重拟 |
draftCount |
1 | 中书拟敕稿数(>1 为廷议并行多稿) |
censor |
true | 是否设御史台(台省并立,与都省覆核并行) |
maxParallel |
6 | 六部并行上限 |
si |
true | 六部尚书是否把本部敕令下发给本部四司(二十四官吏)并行承差;false=退回「一部一个 agent」的省算口径 |
siMaxPerDept |
4 | 每部最多下发几个司(二十四司之四) |
siMaxParallel |
2 | 二十四司承差的全局并发上限(6 部 × 4 司 = 24,不限流会一口气全拉起;v0.10.0 由 8 降到 2——原值与宿主的子 agent 名额上限撞在一起) |
siTimeoutMs |
600000 | 单司承差的阶段超时 |
crewLimit |
8 | 在朝常任子 agent 名额上限(v0.10.0,=宿主实测上限);0=不设名额 |
crewReserve |
2 | 名额里留给场外的余量(面板问对 / 别的插件 / Agent Teams 共用同一个池子) |
crewWaitMs |
20000 | 领不到名额时最多等多久;到点仍没有 ⇒ 该阶段降级为一次性 agent(照办,不判失败) |
crewTickMs |
250 | 等名额的轮询周期 |
crewDrainIdle |
true | 用毕即退班:太子分拣毕 / 司承差毕 / 部覆核毕当场退班还名额(名额只进不出的话,二十四司必然撞上限) |
taizi |
true | 是否先由太子分拣(闲谈直接回、不开朝;旨意提炼标题与验收口径) |
stallThresholdSec |
180 | 单阶段多久没动静算「停滞」(秒;可为小数,测试用小值演练) |
scanIntervalMs |
20000 | 停滞扫描周期(毫秒);0=关掉扫描 |
maxStallRetry |
1 | 每个阶段停滞时先自动重开该阶段几次(按每一次尝试定位,不误伤并行的其它阶段) |
escalationMax |
2 | 重试无效后升级几级(1=门下省协调,2=尚书都省协调),再无效则中止该阶段并回滚 |
archive |
true | 奏折阁:朝议落盘存档(重启/热重载后仍能翻到) |
archiveLimit |
50 | 奏折阁保留份数(超出删最早的) |
archiveDir |
~/.dsh/court/memorials(DSH_COURT_ARCHIVE_DIR 可改) |
存档目录;创建引擎时就定死,不在写入时读 env |
stageTimeoutMs |
600000 | 单阶段(每个子 agent)超时;超时该阶段记 warn 并保留已产出部分 |
reconcileTimeoutMs |
300000 | 都省收官阶段超时(据御史台按劾重出终稿),短于普通阶段以免拖着不退朝 |
totalTimeoutMs |
1800000 | 整场朝议超时 |
model |
空 | 子 agent 模型覆盖;空=继承调用方 |
provider |
spawn |
子 agent provider;spawn 时不覆盖 agentOptions.provider(即默认继承) |
historyLimit |
30 | 内存中保留的朝议记录数(奏折阁的存档另算) |
ministerMaxDepth |
1 | 子 agent 深度上限:1=可办案但不得再开府(防递归);不设默认值时由引擎补 1 |
tokenBudget |
0 | 本场累计 token 限额(0=只计度不禁支)。court_hold 的 tokenBudget 参数按场覆盖它;到限额即停派新差事并在跑的差事一并叫停(v0.7.0),按「可续办的叫停」收场 |
tokenWarnRatio |
0.8 | 用度到限额的这个比例时内侍监先呈报一次(余量不足) |
budgetTickMs |
5000 | 内侍监预算巡检周期(毫秒,v0.7.0):勘合只在派新差事时做,在跑的差事靠这个周期巡检才掐得住;调小=更贴限额,调大=超支幅度更大 |
longLine |
true | 长线子智能体(v0.8.0):一节点一常任 agent(startContinuable),阶段之间复用同一个人、退朝时统一退班;false=每阶段一个一次性 agent(省内存,但节点之间互不相识)。宿主缺 startContinuable/sendMessage/sessions 时自动退回一次性口径并留痕,不判失败 |
talkTimeoutMs |
300000 | 长线 agent 一轮的等待上限(毫秒,含面板弹窗的「问对」):到点 interrupt 这一轮(人不散),按停滞重开处理 |
schemaRepair |
true | 回报契约补问(v0.12.0):每阶段 JSON 回报按 RESULT_CONTRACT 校验,缺关键字段时补问一次格式(只补字段、不改结论);false=只记账不补问 |
schemaRepairTimeoutMs |
120000 | 格式补问的单阶段超时(只补 JSON,不该重新办案,故短于普通阶段) |
unsandboxedFallback |
false | 无过滤降级开关(v0.12.0):默认禁止第三档 spawn(不给 toolFilter)——宿主两次都不接受白名单时该阶段判失败交人裁决,而不是放一个可改文件/可调 court_* 的 agent 出去;仅在对 provider 兼容性有把握时设 true |
runRedline |
0 | 御前余量红线(v0.9.0):本场可用额度(限额 − 全场已用)跌破它即上奏;0=不设。下旨时 redlines._run 可按场覆盖 |
deptRedline |
0 | 各部默认红线(v0.9.0):下旨未逐一给 redlines 时,六部各按此值立一条;0=不设(想只给某几部立线,就在 redlines 里逐部写) |
promptSection |
false | 是否向系统提示注入一行朝堂说明 |
panelRetryMs |
500 | 面板路由自愈重试间隔(apply 期拿不到 webServer 时启用,最多重试 120 次;测试会调小它) |
DSH_COURT_DOCTRINE(环境变量,不是 config 键) |
1(开) |
作业条规开关:=0 时三省、六部与二十四司的承办提示词里不再出现条规(对比实验 / 出问题时的一键卸力)。见第十六节 |
DSH_COURT_SKILLS_DIR(环境变量,不是 config 键) |
~/.agents/skills(回落 ~/.dsh/skills) |
条规里「技能全文在哪」的根目录;多个用 ; 分隔 |
五、文件结构
dsh-court/
├── package.json # main=lib/index.js;dsh.client.platform=web
├── cordis.patch.yml # bundle 装配:insert id/name = dsh-court
├── lib/index.js # 插件入口:6 个工具 + 服务 ctx.court(ctx.provide)+ /court/api
├── lib/engine.js # 朝议引擎:阶段编排、并行扇出、都省收官、超时/中止、快照(轻量/全文两档)
├── lib/protocol.js # 职官/权力线/六部定义、persona、提示词、JSON 容错解析、分支文书 branchSeed
├── lib/client.js # 朝堂面板(__ModuleLoader__ CJS 包装,React;黑金灰红 Primer 风格 + 另起分支对话)
├── tests/host-smoke.mjs # host 半边冒烟(22 项,零依赖,不拉 LLM;含分支种子、面板路由与热重载交叠回归)
├── tests/client-smoke.mjs# client 半边冒烟(20 项带真实 HTTP 探针;14 项 --offline:UI 骨架 + 分支调用序列)
├── tests/preview.mjs # 面板静态预览生成器(`npm run preview`):无浏览器也能看面板长相
├── evidence/ # 端到端实跑的原始快照与奏疏(e2e-court-run.json);panel-preview.html 是设计预览
└── docs/history-sansheng-liubu.md # 史料考:制度与权力线(唯一依据)
六、已知限制
- provider 的 maxDepth 限制 / 降级阶梯:spawn provider 把「子本身」算作 depth 1,传
maxDepth: 0会被拒(「subagent depth 1 exceeds maxDepth 0」)。引擎默认发maxDepth: 1(= 六部可办案、不得再开府),并带降级重试:完整(白名单+深度)→ 去深度(保留工具白名单)→ 默认止步。第三档「无过滤」自 v0.12.0 起默认禁止(unsandboxedFallback:false):toolFilter 是工具面唯一护栏,宿主两次都不接受时宁可让该阶段失败交人裁决;只有显式设 true 才会走无过滤 spawn。另:court_*六个工具对朝议子会话统一拦(sessionId 护栏,不只court_hold)——即便走了无过滤降级,子会话也调不动查看/散朝/调额。 - 改源码会热重载插件、清空朝议:DSH 会监视插件文件,编辑
lib/*.js会重建 fiber,在跑的朝议随之丢失(实测踩到)。要改代码请等退朝,或接受朝议中断。 - 朝议不跟随工具调用取消:
court_hold刻意不接exec.signal——否则工具一到waitMs返回就会把仍在办案的六部全部 abort(实测:六部齐刷刷stopReason=aborted)。朝议只由court_dismiss/ 阶段超时 / 总超时结束;所以background:true与超时续查是可靠的。 court_dismiss与面板散朝路由都需显式授权:工具必须给id或显式all:true;POST /court/api/dismiss必须给id或all=1,否则 400。本工具全局可见(任何会话的 agent 都能调),不带参数即散掉全部朝议的设计太危险,故驳回。- 朝议记录在内存:进程重启后
court_status的历史清空(正在跑的朝议会随进程结束而中止,面板也随之清空)。 - 封驳不过则止:门下省在
reviewRounds内未准奏,该场朝议以rejected结束、不施行——按「封驳不过则止」处理,不自动放行。 - 六部默认可写可执行:各部工具白名单含写入与命令执行(工部/兵部/户部/刑部可
pwsh)。这是设计意图(真办案),在生产仓库上使用前请确认圣旨范围。 - 面板 API 无鉴权:
/court/api走本机 webserver 前缀路由,与 DSH 其它本地面板同样不做额外鉴权(故散朝路由自守,见第 4 条)。 - 单阶段超时会截断重活:
stageTimeoutMs默认 600s 是每个子 agent 的上限。实测一场重审计里刑部/礼部双双击穿 600s,状态记warn,已写出的产物保留、报告可能不完整。重活请调大stageTimeoutMs,或把圣旨范围切小。 - 都省覆核分两阶段:御史台与都省初稿并立同办,按劾出来后再由都省收官重出终稿(
reconcileTimeoutMs)。收官失败时奏疏退回初稿,御史台按劾原文仍并呈——此时奏疏可能残留「请补呈按劾」一类措辞,看的时候留意都省收官 未成标记。 - 首次注入的历史包袱:若插件曾被注入到一个非运行 profile(如
profiles/web),loader 会留下一个[no-fiber]幽灵 entry(实测profiles/web/node_modules/dsh-court有 junction 残留、其配置无对应条目);它不影响运行。正确装法是dev_install_package { profile: 'desktop' }(或dsh plugin add)。 - 面板路由的残余风险:自愈只在本 fiber 活着时生效(定时重试 + 只摘自己那条)。若 webserver 服务整个被卸载重装(
self-heal.log里那种路由表清洗),重试窗口(120×panelRetryMs≈60s)之后不会再补;此时重载插件即可(dev_reload_package dsh-court),或直接看 #9 的落盘日志确认。 - log() 是空操作:本机 profile 没有
logger服务,ctx.get('logger')为 undefined,常规日志不落任何地方;只有异常路径走note()落盘(见 #9)。排查问题请看那两个文件,不要指望 DSH 主日志里有[dsh-court]。 - 另起分支对话的三个前提:① 当前对话至少有一轮已完成——DSH 原生
sessions.fork对空对话返回session/fork-unavailable(面板把它转成可读文案,不是静默失败);② 页面提供sessions与uiWorkspace服务(缺失时按钮禁用并写明缺什么,面板其余功能不受影响);③ 投喂是把文书作为分支的第一条消息发出去,所以新分支会自动跑一轮回复(文书抬头已写明「先读完、等我指令再动手」,避免它凭材料自行开工)。文书默认单段 6000 字、总 40000 字上限,超长截断并留「已截断」字样。 - 面板强制深色:黑金灰红是产品要求的配色,面板不随 DSH 浅色主题切换(不用
--dsw-*令牌,颜色写死在自己的.ct-root变量里)。 - 分支文书是快照:投喂进分支的是点击那一刻的朝议状态。朝议之后又走了新阶段(比如都省收官出终稿),分支里拿到的还是旧的——要最新文书就再点一次分支,或回原会话看面板。
七、史料依据
制度不是装饰:每个角色的权力与边界都取自原始文献(《唐六典》卷八/卷九/卷一/卷二、《资治通鉴》卷一九三与卷二〇四、顾炎武《日知录》卷九、《新唐书·百官志》、司马光《传家集》等),逐条出处见 docs/history-sansheng-liubu.md。
其中两处常被误引者已显式辨证:《唐六典》给事中条并无「封还」「涂归」二词(「涂归」成词首见《新唐书·百官志》);「三省六部一台」是现代概括语,非唐人称谓。
八、交付与实测记录(2026-10-05)
装载:dev_install_package { profile: 'desktop' } → 桌面 profile 的 dependencies 加 link、bundles 加 dsh-court、node_modules junction + loader entry。回执:host ✓ / client ✓。重启后由 bundles 列表正常装配(双路径一致)。
第 1 场实跑(court-muvbnq4g-1,约 11 分钟,后台模式):
| 阶段 | 实测 |
|---|---|
| 中书拟敕 | 4 稿(12.3s / 37.7s / 82.8s 重拟 + 并稿) |
| 门下审覆 | 3 审:第 1、2 稿封驳(附逐条涂归意见),第 3 稿准奏(7 条敕令)——46.1s / 43.8s / 32.8s |
| 尚书派工 | 7 条敕令分派 2 部 |
| 六部并行 | 刑部 192.8s ∥ 工部 56.4s,2/2 有回报 |
| 御史台 ∥ 都省覆核 | 御史台 220.9s 出 6 条按劾 + 无异议清单;都省 49.4s 出奏疏 |
| 奏疏 | 53,348 字,原始快照存 evidence/e2e-court-run.json |
第 2 场实跑 · 插件自审(court-muvc83gi-1,1000s,六部派 3 部:工部/刑部/礼部;圣旨=「对 dsh-court 做宣称 vs 实现独立审计」):
| 阶段 | 实测 |
|---|---|
| 中书拟敕 | 3 稿(43.1s / 26.7s 重拟) |
| 门下审覆 | 2 审:第 1 稿封驳 → 第 2 稿准奏(5 条敕令) |
| 六部并行 | 工部 done 195s;刑部/礼部 击穿 600s 单阶段超时记 warn(产物已在盘上:court-audit/dept-*.md) |
| 御史台 | 252s,产出按劾(其中「工部无异议、刑部 6 条虚报/无据/擅改、礼部多处不一致」) |
| 都省覆核 | 202s 出奏疏(29,282 字),原始快照存 court-audit/run-muvc83gi-1.json |
这一场真抓出了本插件的毛病(审计报告在 court-audit/,逐条带文件:行号)——已据此修完(见第九节),未修的照实留在限制里。
面板 API:GET /court/api/state → 200 ok=true version=0.1.0 六部 6 条,live/runs 随朝议实时变化;GET /court/api/run?id= → 全文;GET /court/api/nope → 404;POST /court/api/dismiss 无 id 无 all → 400。
UI 注册(浏览器侧实测,非「应该能行」):client Inspect 的 Slots 实时注册表里,conversation.view 有 {registrant: dsh-court, id: dsh-court, order: 40, active: true},settings.section 有 {registrant: dsh-court, id: dsh-court-roster, order: 60, active: true}——这两行是页面里跑出来的,说明 client bundle 已被加载执行;若面板上看不到「朝堂」,刷新一次页面即可。
第 3 场实跑 · 复核修复(court-muvd1dkk-1,1079s,派 2 部):工部 435s ∥ 刑部 379s(未再击穿超时);御史台 171.9s;都省收官真实跑起来(reconcile=done/258.7s,面板 phase 出现 都省收官:并呈御史按劾),终稿 9,528 字含「按劾处置」与采信/部分采信/不采信,无「请补呈御史台按劾」残留。复核报告 court-audit/verify-fixes.md:5 项修复 4 项判「一致」、第 4 项标「本场采样时未走到,活证据未取得」(该缺口由本场收官阶段的完成态补齐),并抓出下面第 6、7 条新问题。
第 4 场实跑 · 复核补修(court-muvds3rh-1,派 1 部,收官 reconcile=done/250.5s):报告 court-audit/verify-fixes2.md——config 透传、detail:'brief'、测试套件三项判「一致」;第 4 项 HTTP 实测判「不一致」,原因是复核方按敕令去取 court-muvd1dkk-1 的 /run,而该场记录已被修复时的热重载清空(内存历史,见第六节第 2、5 条)→ 得 404。这是已知行为,不是接口缺陷;同一时刻当前场 court-muvds3rh-1 的 /run 实测 200,全文齐备(各部 5,609 / 按劾 4,637 / 都省终稿 11,206 字,奏疏 25,489 字)。
自动化测试:node tests/host-smoke.mjs 13 项 + node tests/client-smoke.mjs 10 项,退出码均 0(client 那套含真实 HTTP 探针,会打本机 /court/api;host 那套含「引擎每个 config 键都必须被入口转发」的结构性断言)。
实测行为特征:门下省显著偏严——三场测试的第 1 稿全部被封驳,末轮才收敛;reviewRounds 建议 ≥3。中书也倾向过度拆分(小任务出 7 条敕令),正由门下以「无法验收」驳回纠偏,这一对冲正是制度设计意图。
九、自审发现并已修的问题(2026-10-05)
第 2 场朝议就是拿本插件开刀,工部/刑部/礼部按 README 逐条核对实现,发现并已修:
| # | 问题(谁发现) | 修法 |
|---|---|---|
| 1 | 面板「逐部展开回报原文」是空头支票:/state 快照只有 brief,客户端却读 text,成功阶段一律显示「(无回报)/(无)」(礼部) |
快照分两档:snapshot() 轻量、snapshot(run,{full:true}) 带全文;新增客户端 getRun() 走 /court/api/run?id= 取最新一场全文,取不到退 brief。测试:host 侧断言两档差异,client 侧断言 /state 无 text 而 /run 有 |
| 2 | court_status 的 detail 是空转参数:brief/full/省略三种给同一串(工部) |
detail:"brief" 真给摘要(截 1200 字 + 提示),其余给完整奏疏;参数写进 README |
| 3 | POST /court/api/dismiss 无授权护栏:不带 id 会把全部在跑朝议散掉(礼部) |
与工具同口径:无 id 必须显式 all=1,否则 400;测试里加了这条探针 |
| 4 | 都省覆核看不到御史台按劾原文(引擎只传占位说明),导致奏疏里出现「请补呈御史台按劾」这种荒唐请求(都省自陈 + 第 2 场实测) | 新增都省收官二阶段:台省并立跑完后,把按劾原文交回都省重出终稿(reconcileTimeoutMs,失败则退回初稿并留标记) |
| 5 | ctx.set('court', api) 缺 provide,每次装配抛 cannot set property "court" without provide(日志实测) |
改 ctx.provide('court', api) 并写明缘由 |
| 6 | README 承诺 reconcileTimeoutMs 可配置,但入口 config 透传块漏了这一键(第 3 场复核抓到) |
补上透传;并加结构性测试:从 engine.js 的 cfg 默认块抓键,断言 index.js 逐个转发——以后再漏键测试直接红 |
| 7 | detail:"brief" 两处边角:奏疏短于 1200 字仍加「已截断」提示;非 done 状态忽略 brief(第 3 场复核抓到) |
只在真的截断时加提示;brief 判断提到状态判断之前,任何状态都生效 |
| 8 | 面板路由会静默消失(交付后实测:/court/api/state 404,而四个工具、ctx.court 服务全好,插件在 loader 里也显示 active)——两个窗口:① apply 期 webServer 还没起来,那次 register 没人接;② 热重载交叠,新 fiber 先挂上、旧 fiber 的 dispose 随后按同路径摘掉 |
面板挂载改为可自愈:拿不到服务就定时重试(panelRetryMs);挂上后记住「路由表里那条是不是我挂的」,卸载时只摘自己那条,不是自己的连 dispose 都不调(它按路径无条件删)。新增 3 项确定性回归测试(就绪即挂 / 晚到重试 / 旧 fiber 不得摘新的) |
| 9 | 上述失效之所以查了半天,是因为日志根本没有出口:本机 profile 里 ctx.get('logger') 是 undefined,log() 全是空操作,异常路径也没落盘 |
新增 note()/writeDiag():异常路径(晚到挂载、挂载失败)一律落盘 ~/.dsh/super-injector/dsh-court-apply.log 与插件包目录,正常流水不写;DSH_COURT_DIAG_DIR 可改道(测试用,避免污染包目录)。测试断言「留痕」这件事本身 |
同轮更正的口径:README 原写「边界写进 persona」,实为三重落地(persona + 只读白名单 + sessionId 护栏),POWER_LINES 只供展示;「拦 court_*」收窄为「拦 court_hold」;配置表由「10 行/12 键」改为 13 行 13 键(一项一行)。
十、v0.1.2:面板按 GitHub 设计重做 + 朝堂信息另起分支对话(2026-10-06)
需求四条:① 参照 GitHub 的项目设计 UI;② 主色黑金、副色灰红;③ 新增「朝堂信息另起分支对话」;④ 三省六部各有边框。
| 需求 | 落地 | 机器判据(哪条测试在钉它) |
|---|---|---|
| 参照 GitHub 设计 | 照 Primer(Dark) 的骨架与尺寸层级:1px 描边 + 6px 圆角卡片、药丸 Label、Counter、BlankSlate 空态、btn/btn-sm、Primer 表格;页头底部描边 | client-smoke「配色与骨架」断言 .ct-card{border:1px solid …;border-radius:6px}、.ct-counter、.ct-blankslate、.ct-btn、border-radius:2em 等规则存在 |
| 主色黑金 | 黑=画布 #0d1117 / 面板 #161b22;金=#e3b341(在朝、准奏、主按钮、边框 hover 转金、官署名) |
同上,逐色断言 |
| 副色灰红 | 灰=#8b949e(次要文字、中性状态、描边);红=#f85149(封驳、失手、危险操作按钮) |
同上,逐色断言 |
| 三省六部各有边框 | 中书省/门下省/尚书省 3 张 + 吏户礼兵刑工 6 张 + 御史台 1 张 = 10 张独立边框卡(.ct-office,整圈 1px + 左侧 3px 状态色条),每张带状态药丸、职权行、摘要、展开原文、⎇ 分支 |
client-smoke 渲染 RunCard 后断言卡数=10、官署名与顺序、每卡恰好 1 枚状态药丸、点击回调的 scope 序列 |
| 三省卡有内容可看 | /run 全文快照补 drafts[].text 与 reviews[].opinions/concur——原先只有 brief,中书/门下两张卡必然空 |
host-smoke 断言轻量档无 text/opinions、全文档有;client-smoke 真实 HTTP 探针断言 /run 每稿带 text |
| 朝堂信息另起分支对话 | GET /court/api/branch?id=&scope=(host 出文书)→ sessions.fork({sessionId, increaseTitle:true}) → retain + session.prompt(文书,'queue') → uiWorkspace.openSession(child);scope ∈ 整场/三省/御史台/六部名 |
client-smoke 断言调用序列 fork→retain→prompt→release→openSession、fork 参数、投喂 mode=queue、scope 取数 URL、四条失败路径文案;host-smoke 断言种子内容与裁剪口径 |
为什么分支文书由 host 出、而不是面板拼:① 体例只有一处,分支里的 agent 读到的格式稳定;② 截断口径唯一(单段 6000 / 总 40000);③ 纯函数可直接单测——面板拼装的话这段逻辑在 host 侧无法验证。
真实数据抓出的一个缺陷(已修):/branch?scope=shangshu 的奏疏段原先取 run.memorial.text——那是都省那一次输出的原始 JSON(实测 5,653 字),而「呈御前的完整奏疏」是 engine 的 memorialText(实测 17,766 字,含圣旨/敕令/六部回报/按劾/终稿,也正是 court_status 给用户看的那一份)。两者在真实朝议里差 3 倍,等于把「都省分支」喂成残卷。现改为优先 memorialText,并加断言钉死(shangshu 必须含「奏疏(呈御前)」、且不得出现都省原始 JSON)。
发现方式:tests/capture-branch-evidence.mjs 拿真实朝议逐个 scope 核对取舍——构造数据的单测不会暴露这个问题,因为构造数据里这两份文本本来就没差别。同轮还修掉两处只有渲染出来才看得见的瑕疵:官署卡左侧状态色条恒灰(--oc 没在任何状态类里赋值,等于没生效)、阶段文案与状态药丸重复显示(phase='退朝' + status=done 各显示一遍「退朝」)。
实测(2026-10-06,本机):host-smoke 22 项 + client-smoke 20 项(含真实 HTTP 探针)退出码均 0;热重载后线上 GET /court/api/state → version=0.1.2;client Inspect 实时注册表里 conversation.view 有 {registrant: dsh-court, id: dsh-court, order: 40, active: true}、settings.section 有 {registrant: dsh-court, id: dsh-court-roster, order: 60, active: true}。
两场真实朝议端到端(node tests/capture-branch-evidence.mjs 逐个 scope 核对取舍,原始数据存 evidence/branch-source-*.json、branch-seeds-*.json):
| 场次 | 结果 |
|---|---|
court-muvg8jeu-1(修复前) |
11 个 scope 里 shangshu 核对失败(奏疏段给的是都省原始 JSON)→ 据此修掉该缺陷 |
court-muvgg1a4-1(修复后) |
11 个 scope 全部通过:整场 25,583 字 | 中书 9,631 | 门下 4,251 | 尚书 9,929(含呈御前奏疏)| 御史 2,755 | 单部 343–454 字——「点哪块带哪块」在真实数据上成立 |
静态设计预览:npm run preview 生成 evidence/panel-preview.html(无浏览器也能看面板长相;数据是样例,不含交互)。
十一、v0.1.3:树状朝堂 + 朝堂问对(有实际作用、不跳转)(2026-10-06)
需求四条:① UI 呈树状图,有尊卑井井有条的感觉;② 参照 GitHub 的项目设计 UI,主色黑金、副色灰红(沿用);③ 朝堂对话有实际作用且别跳转;④ 别动别的插件的 UI。
| 需求 | 落地 | 机器判据(哪条测试在钉它) |
|---|---|---|
| 树状图 | 一场朝议渲染成一棵树:皇帝(圣旨)d0 → 三省 d1 → 六部 d2(挂在尚书省之下,退让一格) → 御史台 d1 走虚线独立轨道;层级靠 .ct-rail 轨道 + 折角横线表达,末枝在折角处收住 |
client-smoke「树状图:皇帝为根…」断言深度集合 = {0,1,2}、根唯一、三省/御史台 = d1、六部 = d2、官署名与顺序;「层级靠连线表达」断言每个 d>0 节点恰有 1 条轨道、末枝有 is-last、虚线轨道唯一且属于御史台,并断言对应的 CSS 规则存在(d1 缩进 14 / d2 缩进 34 / 折角 14px / is-dashed 虚线) |
| 尊卑井井有条 | 官称 + 品级徽(中书令·侍中正三品、尚书令正二品、六部尚书正三品,依《唐六典》卷八·卷九与《新唐书·百官志》);省一级字号/底色坐实,部一级退半格;主脉金线、支脉灰线 | client-smoke「尊卑有据」逐条断言 10 个徽的文案与顺序(御史台不著品级,故只写官署名);CSS 断言 .ct-node.d2 .ct-office 有独立的字号/底色规则 |
| 参照 GitHub + 黑金灰红 | 骨架仍照 Primer(Dark);主色黑 #0d1117/#161b22 + 金 #e3b341;副色灰 #8b949e + 红 #f85149;另加古代质感:匾额页头(金线 + 回纹牙子)、朱印、衬线官名、简牍式事实栏 |
client-smoke「配色与骨架」逐色 + Primer 骨架断言;--c-serif 与 .ct-seal 存在性断言 |
| 朝堂对话有实际作用 | 问对 = 真 fork 一个会话,把该 scope 的朝堂文书 + 垂询一起投喂(queue),分支里的 agent 拿到全部文书后真办事(能读文件、跑命令、返工);后续追问只发问题本身;止 停当前一轮 |
client-smoke「问对链路」断言 fork→retain→prompt 序列、increaseTitle、retain 白名单 source、queue、prompt 内含文书抬头 + 【朕的垂询】 + 问题、scope 取数 URL;「后续垂询」断言只发问题且 asked 递增、cancel 可调 |
| 别跳转 | 面板订阅分支的 eventSource,只取 fork 之后的新事件(seq > 基线)就地渲染成 朕 / 对 / 办 三种行;可执行代码里没有 openSession / uiWorkspace |
client-smoke「不跳转」——去掉注释后扫描源码不得出现 openSession / uiWorkspace;「问对链路」再断言调用序列里没有 openSession;「就地回话」用一段含父会话历史的假事件窗口,断言旧history被基线过滤、四类事件分类正确、种子文书显示时被折叠成垂询 |
| 别动别的插件的 UI | 样式表里每一条选择器都以 .ct-root 开头(含状态类 .st-* 与 @keyframes ct-pulse),零全局选择器、零全局状态类 |
client-smoke「样式零外泄」逐条解析 STYLE 的选择器并断言全部以 .ct-root 开头(@ 规则与 keyframe 步进除外),并断言动画名带 ct- 前缀 |
实测(2026-10-06,本机):node --check lib/client.js 0;host-smoke 22 项 + client-smoke 25 项(含真实 HTTP 探针)退出码均 0;热重载后线上 GET /court/api/state → version=0.1.3;client Inspect 实时注册表里 conversation.view 仍是 {registrant: dsh-court, id: dsh-court, order: 40, active: true}。
肉眼复核(这轮特意做了):npm run preview → evidence/panel-preview.html → 无头 Edge 截图 evidence/panel-preview.png。截图当场抓出一个真问题:预览页当初没把内容包进 .ct-root,于是整页"只有按钮有样式、其余全黑"——一眼看像面板坏了。这正是「样式全部 scope 在 .ct-root 之下」的副作用,预览壳必须跟着改(已修)。这类问题单测永远抓不到:preview.mjs 的存在价值就在这里。
本轮另装了三个 GitHub 上的 Agent Skills(用 skill 工具加载,装到 ~/.agents/skills/):
frontend-design(anthropics/skills)——按它的判据自检并据此改了几处:去掉「A · B · C」式的 meta 串烧(改成简牍式事实栏)、不再给每块内容套同一套圆角卡片(改为层级树)、把"重音"只花在一处(朱印 + 御座)、三省才编号(因为那确实是先后次序);webapp-testing(anthropics/skills)——本地 web 应用的 Playwright 验证套路;本轮实际用的是它同一个思路的手工版(无头 Edge 截图);theme-factory(anthropics/skills)——主题 token 参考。
同轮修掉的渲染/工程瑕疵:TreeNode 的 <span class="ct-node-tip"> 飘在节点右上角离官名太远(改成节点行内的「一 拟 / 二 审 / 三 行」);写这段嵌套结构时括号对不上(node --check 报 Unexpected token ')'),改用「按行号整块替换」的确定性补丁器定位并修好(该补丁器的教训:手写深层嵌套 JSX 时不要用肉眼数括号,用 node --check 当门禁)。
十二、v0.1.4:自上而下 + 各部对话折叠 + 二十四司(2026-10-06)
改的是同一棵树,不是重画一棵。
| 要求 | 实现 | 断言 |
|---|---|---|
| 朝堂对话直接显示在朝堂面板里 | 撤回「面板底部一块共享对话区」的做法:每个节点自带一格「朝堂对话」,分支回话 / 输入框 / 止 全在该节点内 |
client-smoke「对话就地」:11 块(御座 + 三省 + 六部 + 御史台),每块 data-scope 对应一个 scope,默认全收起;「不跳转」照旧扫描 openSession / uiWorkspace |
| 树状图 | 沿用 v0.1.3 的轨道 + 折角连线(三省金线主脉、六部灰线支脉、御史台虚线) | 「树状图」「层级靠连线表达」断言不变 |
| 各部对话折叠可展开 | 节点页脚一枚 问对(展开后变 收起),折叠时只占一行;那一行标题也可点开;展开态在该节点内往下长 |
「对话入口」断言 10 枚 问对 + 卡头 朝堂问对;「对话就地」断言默认无 is-open |
| 自上而下 | 树容器 flex-direction:column;御座 d0 → 三省 d1 → 六部 d2 → 御史台 d1 依次向下;每块折叠区都往下长 |
新断言「自上而下」:主节点深度序列 [0,1,1,1,2,2,2,2,2,2,1] + 三条 CSS 规则存在性 |
| 三省六部二十四司 | 六部各带一行折叠「二十四司 · 本部下四司」(吏部司/司封/司勋/考功 … 工部司/屯田/虞部/水部),展开列出四司与职掌 | 新断言「三省六部二十四司」:6 部 × 4 司 = 24、每部司名逐个比对、每司有职掌说明、树上恰 6 个折叠开关、默认收起 |
口径:二十四司是书志层级(依据 docs/tang-liubu-ershisi-si-kaoju.md:《唐六典》卷二—卷七 + 《旧唐书》卷四三)——朝议仍只派工到部、不派到司;面板只是把「部下有哪四司」摆出来,不假造一层执行单位。
实测(2026-10-06,本机):node --check 四个 lib 文件全 0;host-smoke 22 项 + client-smoke 28 项(含真实 HTTP 探针)退出码均 0;热重载后线上 GET /court/api/state → version=0.1.4;tests/preview.mjs 重新生成 evidence/panel-preview.html(13 个树节点 / 轨道 12 条 / 10 张官署 / 11 枚状态药丸),无头 Edge 截图 evidence/panel-preview.png 肉眼复核:御座在最上、六部逐级退让、每节点带「二十四司」与「朝堂对话」折叠行、展开态两格对话(整场 / 工部)显示 朕/对/办 与「递牌子」。
一处顺带修的体验:折叠态那一行标题原本不可点(只有页脚按钮能开),现在整行可点(.ct-talk-hd.is-clickable,hover 微微提亮)——「折叠可展开」的东西,用户会本能地点那一行。
踩记:这轮改 lib/client.js 又触发了热重载,把当时正在跑的那场朝议(court-muvhe50g-1,用来复核「别的插件 UI 消失」)清空了——这是本项目第 3 次踩同一个坑:改 client 前先确认没有在跑的朝议,或先 court_status 收下结论。
十三、v0.1.5:底色与对话面板同源 + 收起态压成一行 + 一次「复现不出来」的排查(2026-10-06)
1. 底色不再自带「另一块黑」
原来 .ct-root 自己铺 #0d1117(Primer 画布色),而 DSH 深色对话面板的底是 #151517 —— 两块黑差一档,面板像贴在对话区上的一张纸。v0.1.5 起:
.ct-root不再自己画底(background:transparent),直接透出所在面板的底色;- 颜色一律取自 DSH 自己的主题令牌(由
cordis_inspect_query的 client/ThemelistTokens给清单,值从dsh-client-ui-theme的 dark 块取):--dsw-alias-bg-base(dark =#151517)、--dsw-alias-bg-layer-1/2(#232324/#2c2c2e)、--dsw-alias-border-l1/l2(#ffffff0f/#ffffff1f)、--dsw-alias-label-primary/secondary、--dsw-alias-state-error-primary;取不到才退回内置深色值(每个都写成「先常量、再color-mix()覆盖」两行,老浏览器也吃得住); - 金
#e3b341/ 红#f85149/ 灰(label-secondary)仍作 accent —— 项目口径「黑金主色、灰红副色」不变,只是「黑」跟着 DSH 走;顺带把散在样式里的 7 处硬编码色(#c9d1d9/#eef3f8/#dbe3ec/#d7dee6/#171c24等)一并收进令牌。
实测(真浏览器):无头 Edge + CDP 打开会话视图 → 朝堂,getComputedStyle 得 .ct-root = rgba(0,0,0,0)、所在容器 Dc7zOa_root = rgb(21,21,23),中间整条祖先链全透明 —— 面板与对话面板同一个底色。设置页那一格同理(设置 → 三省六部 首次实测:6 节点 6 轨道;设置弹窗是浅底,面板也跟着浅,不再是一块突兀的黑)。
2. 收起态压成一行
.ct-talk:not(.is-open) .ct-talk-hd{flex-wrap:nowrap;overflow:hidden} + 标题/说明 nowrap + text-overflow:ellipsis(说明 flex:1 1 auto;min-width:0):窄了也只占一行,长句变省略号;展开态保持 wrap(那一行还要放「已递 N 次 / 分支 id / 收起」)。
实测:11 个折叠行高度 36,31,31,31,32,31,31,31,31,31,31(真折两行会到 ~50px),其中唯一 flex-wrap:wrap 的那个正是被点开的那格。
3. 「不能正常运行」的排查(结论:本机复现不出来,附证据)
四条线逐条验,全部通过:
| 线 | 查法 | 结果 |
|---|---|---|
| host 路由 | GET /court/api/state |
200,version=0.1.5 |
| 工具 | cordis_inspect_query host/Tool |
court_hold / court_status / court_ministries / court_dismiss 四件在册 |
| 端到端 | court_hold {background:true} 真开一场(court-muvkisqs-1)→ 面板实时画到「六部并行(5 部)」→ court_dismiss |
通过(跑完即散朝) |
| 真浏览器 | CDP:朝堂 = 12 轨道 / 13 节点 / 11 折叠行;点 刷新、点折叠行、点 朝堂问对 均无 JS 异常(页面只有 memory-evolve 那 7 条既有 404,与本插件无关) |
通过 |
| 数据面 | 把真朝议快照做成夹具(court-audit/build-fixture.mjs → fixture/)喂进面板 |
树/奏疏/按劾/二十四司全画得出 |
最像「不能运行」的两种正常情形:① 面板是会话视图里的标签页(与 对话/轨迹/记忆… 并列),不在对话流里,得点 朝堂 才看到;② 朝议历史只在内存,热重载/重启后归零 —— 看到「尚无朝议」不是坏了,是还没有朝议(court_hold 下旨即可)。另:面板头部现在会显示版本号,看到 v0.1.5 才说明浏览器吃到了新包,否则按一次 Ctrl+R。
排查工具(可复用,落在工作区 court-audit/):live-ui-probe.mjs(无头 Edge + CDP:抓 console/异常、量 DOM、截图;支持 FIXTURE_DIR= 注入夹具、CLICKS=/CLICK_SELECTORS= 点进去)、build-fixture.mjs(真朝议快照 → 面板夹具)、ui-incident/tools/dsh-fetch.mjs(铸 DSH 会话 cookie,带鉴权取 shell HTML / bundle)。坑:本机受限沙箱里无头 Edge 起不来(exit 13),需要一次性 danger-full-access 才出得来图。
十三、v0.1.5:底色与对话面板同源 + 收起态压成一行 + 一次「查不出 bug」的排查(2026-10-06)
1. 底色不再自带「另一块黑」
原来 .ct-root 自己铺 #0d1117(GitHub Primer 的画布色),而 DSH 深色对话面板的底是 #151517 —— 两块黑差一档,面板像贴在对话区上的一张纸。v0.1.5 起:
.ct-root不再自己画底(background:transparent),直接透出所在面板的底色;- 所有颜色取自 DSH 自己的主题令牌(
cordis_inspect_query的 client/ThemelistTokens给出清单):--dsw-alias-bg-base(dark =#151517)、--dsw-alias-bg-layer-1/2、--dsw-alias-border-l1/l2、--dsw-alias-label-primary/secondary、--dsw-alias-state-error-primary,取不到才退回内置深色值(每个都写成「先给 fallback 常量、再用color-mix()覆盖」两行,老浏览器也吃得住); - 金
#e3b341/ 红#f85149/ 灰(label-secondary)仍作 accent —— 项目口径的「黑金主色、灰红副色」不变,只是「黑」跟着 DSH 走。
实测(真浏览器):无头 Edge + CDP 打开会话视图 → 朝堂标签页,getComputedStyle:.ct-root = rgba(0,0,0,0),其所在容器 Dc7zOa_root = rgb(21,21,23);整条祖先链(viewArea / scrollBody / body)全透明 —— 也就是面板与对话面板同一个底色。设置页那一格同理(设置弹窗是浅底,面板也跟着浅,不再出现一块突兀的黑)。
2. 收起态压成一行
.ct-talk:not(.is-open) .ct-talk-hd{flex-wrap:nowrap;overflow:hidden} + 标题/说明 nowrap + text-overflow:ellipsis(说明 flex:1 1 auto;min-width:0)——窄了也只占一行,长句变省略号;展开态保持 wrap(那一行要放「已递 N 次/分支 id/收起」)。
实测:11 个折叠行的实测高度 36,31,31,31,32,31,31,31,31,31,31(两行会到 ~50),其中唯一 flex-wrap:wrap 的那个正是被点开的那格。
3. 「不能正常运行」的排查(结论:本机复现不出来,附证据)
按「面板/工具/路由/交互」四条线逐条验,全部通过:
| 线 | 查法 | 结果 |
|---|---|---|
| host 路由 | GET /court/api/state |
200,version=0.1.5 |
| 工具 | cordis_inspect_query host/Tool |
court_hold / court_status / court_ministries / court_dismiss 四件在册 |
| 端到端 | court_hold {background:true} 真开一场(court-muvkisqs-1)→ 面板实时画到「六部并行(5 部)」→ court_dismiss |
通过(跑完即散朝) |
| 真浏览器 | CDP:朝堂标签页 = 12 轨道 / 13 节点 / 11 折叠行;点 刷新、点折叠行、点 朝堂问对 均无 JS 异常(页面上只有 memory-evolve 那 7 条既有 404,与本插件无关);设置 → 三省六部 那一格首次验证:6 节点 6 轨道正常 |
通过 |
| 数据面 | 用真朝议快照当夹具(court-audit/fixture/,由 build-fixture.mjs 生成)喂进面板 |
树/奏疏/按劾/二十四司全画得出 |
最可能的一种「看起来不能运行」:面板是会话视图里的标签页(与 对话/轨迹/记忆 … 并列),不在对话流里;且历史朝议只在内存 —— 热重载/重启后 court_status 与面板都归零,只剩「尚无朝议」空态。若看到的是空态,那不是坏了,是还没有朝议:court_hold 下旨即可,或点 刷新。
排查工具(可复用,落在 court-audit/):live-ui-probe.mjs(无头 Edge + CDP:抓 console/异常、量 DOM、截图;支持 FIXTURE_DIR 注入夹具、CLICKS/CLICK_SELECTORS 点进去)、build-fixture.mjs(真朝议快照 → 面板夹具)、ui-incident/tools/dsh-fetch.mjs(铸 DSH 会话 cookie 以便带鉴权取 shell HTML/bundle)。注意:本机受限沙箱里无头 Edge 起不来(exit 13),要一次性 danger-full-access。
第二十七节 · v0.12.0:回报契约 · 权限降级收口 · 阶段分级(2026-11-09)
三处低风险结构改进,均不改变任何控制语义,只在「回报是否可信」「降级后还剩多少护栏」「失败算哪一类」三处收紧。
1. 回报契约(lib/protocol.js 的 RESULT_CONTRACT)
parseJson 成功 ≠ 契约成立:模型可能给出合法 JSON 却缺关键字段(典型:门下省只回 {"理由":"可行"},缺 准奏——下游会把「缺字段」读成「封驳」)。现在每阶段有最小字段契约:
| 契约 | 阶段 | 必需字段 |
|---|---|---|
taizi |
太子分拣 | 判 / kind / verdict 任一 |
edict |
中书拟敕 | 敕令 / orders 非空数组 |
review |
门下审覆 | 准奏 / accept(false 也算在场) |
memorial |
都省覆核 | 奏疏 / memorial / 总述 |
censor |
御史台 | 弹劾 / 按劾 / 无异议的部 / 总评 |
minister |
六部办案 | 办案 / 本部小结 / 部 |
si |
二十四司承差 | 承差 / 本司小结 / 司 |
不合格时补问一次格式(repairTask:只补字段、结论不得改;原文截 4000 字),仍不合格则保留原回报、记账,交阶段分级裁量——不把「写漏字段」升级成整场失败。统计进 run.schema(checked/failed/repaired/repairedFailed),随快照与奏疏外露。尚书「下发二十四司」不设契约(既有「派工不可用 ⇒ 本部自理」降级路径优先)。
2. 权限降级收口(cfg.unsandboxedFallback + 统一子会话护栏)
- spawn 降级阶梯的第三档(不给
toolFilter)默认禁止:宿主两次都不接受白名单时该阶段判失败交人裁决。只有显式unsandboxedFallback:true才保留旧行为。 court_*六个工具对朝议子会话统一拦(此前只有court_hold挂护栏)——子 agent 调court_status/court_ministries/court_dismiss/court_control/court_templates一律驳回。
3. 阶段分级 + 机读完整性(integrityOf)
- critical:太子分拣失手(无 JSON 且阶段 error)⇒ 直接判
failed;门下审覆全无裁定 ⇒failed(与制度性的「封驳不过则止」rejected区分开,reviews[].jsonOk记录每一轮到底有没有裁定)。 - best-effort:六部办案、御史台、都省收官仍是
warn/失败可入奏,不阻断整场。 - 快照新增
integrity = { decree, ministers:{reported,failed,total}, censor, reconcile, schema, partial };奏疏加一行人读「完整性:…」与文末机读行INTEGRITY {json}——面板、下游 agent、离线审计都能直接读缺件,不再靠人翻注记。
复跑
node tests/host-smoke.mjs # 引擎/工具/协议(本版新增契约、补问、降级、护栏、完整性断言)
node tests/client-smoke.mjs --offline # 面板静态断言(--offline 不打真 HTTP,避免污染在办朝议)
node --check lib/engine.js && node --check lib/protocol.js && node --check lib/index.js
No comments yet. Be the first to write one.