Releases: liancha22/dsh-puzzle-mode
Release list
v0.27.0 · 熔断之后继续推:报过不再闭嘴,第二次起撤掉「转人工」
v0.27.0 · 熔断之后继续推:报过不再闭嘴,第二次起撤掉「转人工」
一句话
重复思考熔断以前只报一次,报完就永久沉默——模型被点名后要是没换动作,
后面每一次重复都被静默放行,最后还得人工推一把。现在它会按间隔反复推,
而且第二次起不再允许「把问题抛回用户」。
症状(用户原话)
模型复读了之后它会熔断,然后熔断然后主动去揭发它,就让它继续……熔断了之后
他就没有继续……还是得人工手动。
根因:不是检测问题,是熔断之后的处置问题
检测一直是对的——noteAction 能准确认出「同名工具 + 同参数」连续撞了 3 次。
问题在报完之后:
const alreadyFired = previous?.signature === signature && previous.fired === true
const shouldFire = count >= limit && !alreadyFiredfired 一置位,签名不变就永远不再报。当时的理由是「第 4、5、6 次各报一条
比不报更烦」——防噪音防过了头:一次提醒不是熔断,持续推动才是。
于是模型有两条出路,两条都不需要它真的推进:
| 出路 | 后果 |
|---|---|
| 继续原样重复 | 无人提醒,静默空转,每轮都是实打实的成本 |
| 挑「停下来说清」 | 把问题原样抛回用户,熔断变成转人工 |
修法
① 报过一次不等于闭嘴:升级重报
新增 LOOP_ESCALATE_EVERY = 3:到阈值报第 1 次,之后每 3 次再报一次。
noteAction 多返回一个 level,记录这是这一段连击的第几次提醒。
噪音由间隔控制,不再靠永久沉默。
② 第二次起撤掉「转人工」这个逃逸口
loopBreakText 按 level 分档:
- 第 1 档(刚撞阈值):维持原样,三选一——换输入 / 换动作 / 停下来说清。
留着第三条是合理的,确实可能真卡住。 - 第 2 档起:撤掉「停下来说清」,改成硬指令,按顺序不许跳步:
- 先捋事实——把上下文里已经拿到的信息列成几条结论;
- 再基于事实下结论 / 动手,不要再调一次工具去确认;
- 只有连「基于已有信息的最小下一步」都做不了时,才允许说卡住,
且必须说清卡在哪一步、缺哪一条具体信息,不许笼统地说「需要你确认」。
为什么必须撤:那条出路原本是给「真缺权限 / 工具坏了」准备的,但实测里它成了
最省力的逃逸口——一被点名就挑它。第一次提醒留着它没问题;第二次还在原地重复,
就说明它不是真卡住,而是没在推进。
③ 文案封顶,但推动不封顶
LOOP_MAX_LEVEL = 3:到顶后不再加码,但仍然按间隔重报。
理由写在常量注释里:沉默比重复更贵——沉默等于放任空转。
④ 提示段同步改口
提示段原先只说「会注入一条提示」,与新的「会反复注入、第二次变硬」两说并存。
补上一句说明它不是只报一次,以及第二次起不要再试图问用户——那正是它要拦的行为。
验收判据
| 验证项 | 判据 |
|---|---|
| 检测未回归 | 连续 3 次同签名仍触发第 1 次熔断(shouldFire === true) |
| 间隔内不刷屏 | 报过之后紧邻的 1–2 次不重复注入(LOOP_ESCALATE_EVERY = 3) |
| 必须再报 | 没换动作时,每 3 次必须再报一次——这是「熔断后能继续推进」的核心判据 |
| 档位升级 | 第二次提醒 level === 2,文案含「先捋事实」且不再含「三选一」 |
| 逃逸口已撤 | 第 2 档文案含「现在不适用」,且要求「卡在哪一步」 |
| 封顶仍推 | level 到 LOOP_MAX_LEVEL 后不再加码,但仍持续重报 |
| 换动作复位 | 签名一变,连击与 level 双双归零,重新给温和的第一档 |
| 接线未断 | 钩子层:不改动作时真的重复注入,且第二次是硬指令(只验纯函数会漏掉「没把 level 传下去」) |
| 误报防线未松 | 参数每次不同 / 无参轮询(job_list)/ 工具被 block,一律不注入 |
| 全量 | npm test 全绿:60 + 47 + 7 + 51 + 17 + 36 + 12 + 5;跨插件握手 18 / 0 |
新增测试
test/60-loopguard.test.mjs 从 13 项增到 17 项,新增的 4 项正对着这次的回归:
间隔内不刷屏 / 没换动作必须再报 / 封顶后仍重报 / 接线层重复注入且第二次是硬指令。
变异验证(本仓约定:守卫必须能抓到漂移,否则形同虚设):
把 shouldFire 退回 firstTime(即旧的「只报一次」),测试在
「没换动作时每 3 次必须再报一次」那条当场变红;还原后复绿。
删掉这条断言,功能会静默退化回「只报一次」而没人发现——这正是它存在的理由。
装
python3 "$DSH_HOME/plugin-manager.py" github liancha22 dsh-puzzle-mode v0.27.0或在插件市场点更新。装完重启该 profile,再刷新浏览器页面。
v0.26.1 · 修 op:audit 带源码体检必失败
v0.26.1 · 修 op:audit 带源码体检必失败
一句话
puzzle_mode{op:'audit'} 只要带源码体检,之前一律失败;现在正常了。
这是 v0.26.0 里就存在的一个返回体形状问题,与文档、与你的代码都无关。
症状
Error: tool "puzzle_mode" returned invalid output: value is not lossless JSON
出现条件:带源码体检(source 默认就是 true,也就是它的主用法)。
绕过方式是显式传 source:false —— 但那等于放弃了源码体检,
而体检正是 op:audit 用来发现「巨函数 / 没分层 / 死导出」的唯一途径。
根因
宿主对工具返回做「无损 JSON」校验:返回值必须能
JSON.parse(JSON.stringify(x)) 原样往返。显式 undefined 过不了这一关——
{ file: undefined } → JSON.stringify → "{}" → JSON.parse → {} // 丢失了 filelib/index.js 把源码体检的发现并进 findings 时,无条件写了 file: item.file。
而其中两条是项目级发现,本来就没有单个文件:
| 发现 | 事实 |
|---|---|
source_flat |
「38 个源码文件全在同一层目录(.)」 |
source_long_function(截断提示) |
「另有 N 个函数也超过阈值,未逐条列出」 |
它们没有 file,于是 file: item.file 写出了一个显式的 undefined 属性。
修法
字段要么是字符串、要么根本不存在:
...(typeof item.file === 'string' && item.file !== '' ? { file: item.file } : {}),带 file 的发现照旧带 file(没修成一律不带);项目级的那两条不再带 file 键。
验收判据
| 验证项 | 判据 |
|---|---|
| 主用法可用 | op:audit(带源码体检)成功返回,不再报 not lossless JSON |
| 返回体无损 | 整个返回能 JSON.parse(JSON.stringify(x)) 深比较相等 |
| 项目级发现不带 file | 造出「7 个同层文件」时 source:source_flat 命中,且没有 file 键 |
| 带 file 的照旧带 | 所有 source:* 里存在的 file 都必须是非空字符串 |
| 没修过头 | source:false 仍可用,且不含任何源码发现 |
| 全量 | npm test 全绿:60 + 47 + 7 + 51 + 13 + 36 + 12 + 5;跨插件握手 18 / 0 |
新增测试
test/90-audit-tool.test.mjs(5 项)。它不钉某个具体字段,而是断言
整个返回能无损往返 —— 这条判据不管以后哪个字段被写成 undefined 都会红。
为了让 source_flat 真的出现,测试会造出「7 个源码文件、只有一层目录」的形状
(只造 3 个文件不会命中,那样测试就是假的)。
诊断过程(为什么值得单独记)
inspectSource 与 auditOf 单独跑都是可序列化的 ——
所以问题只可能在「组装工具返回」那一段,而那一段只有真调一次工具才覆盖得到。
这就是为什么加的是工具级测试,而不是给 lib/source.js 补单测:
单测会全绿,而真机照旧报错。
装
python3 "$DSH_HOME/plugin-manager.py" github liancha22 dsh-puzzle-mode v0.26.1或在插件市场点更新。装完重启该 profile,再刷新浏览器页面。
v0.26.0 · 固定收尾问可全局关闭
v0.26.0 · 固定收尾问可全局关闭
一句话
面板左栏多了一颗 固定收尾问:开 · 点此关闭。关掉之后,所有会话的提问都不再带
「要不要先停下?」——包括提示段、工具返回、拒绝理由与面板提示语四处。
用户原话
提问到最后还要选继续还是停下?的功能加一个全局关闭功能。
关掉之后到底变了什么
| 位置 | 关掉前 | 关掉后 |
|---|---|---|
| 提示段 | 「提问的最后一项固定问『要不要先停下?』…」 | 「固定收尾问已全局关闭(用户设置):不要再问…」「问到实质问题问完就结束」 |
| 工具返回 | askPause:true + pauseQuestion + pauseOptions |
askPause:false,不带后两个字段 |
| 只拼不写的拒绝理由 | 要求「最后问一次『要不要先停下?』」 | 「固定收尾问已全局关闭,不要再问」 |
| 面板右栏 | 「每次提问的最后都会问:要不要先停下?」 | 「固定收尾问已全局关闭:提问不再带…」 |
| 面板「提问」模板 | 模板里塞一段收尾问 | 不再塞(否则等于用一个按钮把设置又打开一遍) |
为什么关掉后要「不带字段」而不是「带 false」:留着 pauseQuestion 比不带更糟——
模型看到收尾问文案就会继续问,「关掉了」于是变成一句空话。字段在不在,本身就是给模型的信号。
默认值与作用范围
- 默认是开,而且设置文件里缺这个字段也算开。
这个开关的语义是「默认行为」而不是「新特性」——反过来(缺 = 关)会让所有老用户的
提问静默变了行为,比功能本身严重得多。 - 全局:写进
$DSH_HOME/.dsh-puzzle-mode.json,跨会话、跨项目、跨 profile 一致。 - 它与左栏那颗「关掉本会话的拼图模式」是两个开关,共用同一个文件但互不覆盖
(任一侧写入都带上另一侧)。这正是本仓记过的「同名不同形的字段会互相盖掉」,所以有断言钉着。
顺手修掉的一处测试脆弱性
10-puzzle / 30-rpc 里的断言会去读运行者真实的 DSH_HOME。用户一旦在面板上关掉
收尾问,npm test 立刻变红——而代码一行没错。测试不该依赖运行者的个人设置。
现在统一走 test/helpers/isolate-home.mjs,并且它必须在其它 import 之前:
ESM 的 import 会被提升,写在文件中间赋值环境变量太晚了(模块体在依赖求值之后才跑)。
实测:把真实设置改成 askPause:false 再跑全套测试,仍然全绿。
验收判据
| 验证项 | 判据 |
|---|---|
| 默认开 | 删掉设置文件后 pauseFields() 回 {askPause:true, pauseQuestion, pauseOptions} |
| 缺字段算开 | 设置文件写 {"disabledSessions":[]} 时 isAskPauseEnabled() 为 true |
| 坏值算开 | "false" / 0 / null / [] 一律当开(只有显式布尔 false 才关) |
| 关掉生效 | 走真实 RPC:method:settings 带 askPause:false 后,state 返回 askPause:false 且无 pauseQuestion |
| 提示段跟着换 | 关掉后提示段含「固定收尾问已全局关闭」,且不再含「提问的最后一项固定问」 |
| 能再打开 | askPause:true 后返回与提示段都恢复 |
| 互不覆盖 | 关收尾问后禁用名单仍在;禁用/恢复会话后 askPause 不变 |
| 不误伤会话 | 关收尾问不该让提示段整段消失(那是「禁用会话」的效果) |
| 测试不受影响 | 把真实 DSH_HOME 设置改成关闭,npm test 仍全绿 |
| 全套测试 | npm test 全绿:60 + 47 + 7 + 51 + 13 + 36 + 12;跨插件握手 18 / 0 |
测试
test/80-ask-pause.test.mjs(12 项):默认值语义、坏值、幂等、两个开关互不覆盖。test/50-contract.test.mjs+2 条契约锚:默认开 + 关掉后不下发文案 + 互不覆盖。tools/verify-ask-pause.mjs(7 项):走真实 RPC 验「设置 → 工具返回 → 提示段」三处接线。
纯函数全绿而接线断了,正是本仓反复记过的假绿。test/helpers/isolate-home.mjs:把DSH_HOME隔离成 import 顺序问题,不靠注释提醒。
装
python3 "$DSH_HOME/plugin-manager.py" github liancha22 dsh-puzzle-mode v0.26.0装完重启该 profile,再刷新浏览器页面。
v0.24.2
v0.24.2 · 审查器没跟上「项目规模」——大档项目被全线误报
用户原话:
审查器没有同步规模改动
看看提示词有没有旧的无规模的限制提示词
两处都成立,而且第一处是真 bug:大档项目的审查结果全是假发现。
① 审查器三处写死了中档上限(真 bug)
「项目规模(小 / 中 / 大)」是 v0.23.0 加的:同一个项目写多细由档位决定。
写入侧改对了——一直走 capsOfSize(档位) / limitsOfSize(档位);
审查侧漏了——三处仍拿中档的尺子去量:
| 位置 | 原先(写死中档) | 改成 |
|---|---|---|
| 条数上限 | ENTRY_CAPS = 悬而未决 4 / 已定 10 |
capsOfSize(state.size) |
| 模块条目字数 | 中档 20 字 | 按档位(大档 40 字) |
| 主文档条目字数 | 中档 50 字 | 按档位(大档 80 字) |
后果:大档项目(要点 40 字、已定 30 条)每一条合法内容都被报成违规。
实测对照(大档项目写 31 字要点 + 12 条已定):
修前:entryIssues limit=20 报「要点超长」
over_cap 报「已定 12 条,超过上限 10 条」
修后:两条都归零
这正是本仓记过的「两把尺子」:同一份规格,写入侧与审查侧必须同源。
audit.js 里那句注释「上限是硬规则(悬而未决 ≤4 / 已定 ≤10)」也停在规模改动之前,一并改正。
修法是新增 mainEntrySpecOf(size) / moduleEntrySpecOf(size)(entries.js),
moduleEntries 加 size 参数(默认中档,所以旧调用点行为不变)。
② 提示词四处旧上限
| 位置 | 问题 |
|---|---|
| 文档锁理由 | 每次拦下 write / edit 都回给模型,最容易被读到——却写死中档值 |
| 提示段「文档分工」 | 写死「一句话 ≤50 字;坑 ≤20 字」 |
| 「条目级发现优先」 | 写死四个中档字数 |
工具 schema 的 content 说明 |
写死中档字数 |
而提示段自己两说并存:一行说死数字,下一行说「别信死数字,看返回里的 limits」。
现在统一改成「随项目规模变,以 op:read / op:size 返回的 limits 为准」。
两处刻意保持静态:
- 工具 schema——它是提示缓存的一部分,不该随会话变;
- 工作流的名字 / 步数上限(名字 ≤20 字 / 每步 ≤80 字 / 每块 ≤12 步)——这三个数与规模无关。
验证
新增 1 条断言,双向钉住:
| 方向 | 钉住什么 |
|---|---|
| 正向 | 大档的合法要点(中档超长、大档合法)不许被报超长 |
| 正向 | 大档的合法条数(>中档 10、≤大档 30)不许被报超上限 |
| 反向 | 中档项目里已有的超长条目仍须被报出来 |
反向那条不是凑数:只测正向的话,把上限全改成 Infinity 也能绿。
写这条断言时踩到一个真教训:我最初用
updateModuleSection造「中档超长条目」,
测试当场报红——因为写入侧会直接拒绝(条目 25 字,超过上限 20 字),数据根本没落盘。
超长条目只可能来自旧文档(迁移不追溯),所以反向构造改成直接写文件——
那正是审查存在的理由。
两处变异验证都真的红了,且报出对应事实:
| 变异 | 结果 |
|---|---|
| 条数上限改回写死中档 | ❌ 大档的 12 条已定不该被报超上限 |
| 条目字数改回固定中档 | ❌ 大档的 25 字要点不该被报超长 |
npm test 全绿:60 + 41 + 7 + 49 + 13;跨插件握手 18 / 0。
跨插件契约本次未动(只改上限取值与提示文案),已复核三处段序一致(10120)、
六条分工条款全在、默认段序不撞 DSH 内置表。
验收判据
patchReload: startup)——本次改的是宿主半,不重启不生效。
重启后:
- 大档不再误报(最重要):把一个项目设成大档,写几条大档才合法的要点
(21–40 字)和超过 10 条的已定,然后跑op:audit——
不应出现entry_issue(超长)或over_cap(超上限)这两类发现。
若仍报「超过上限 10 条」或「超 20 字」,就是没修好。 - 中档仍会报(防「把上限放开成不限」):在中档项目里,用
read/write工具
手工改文件塞一条 25 字以上的要点(写入工具会拒绝,必须直接改文件),
再跑op:audit——应该报出entry_issue。这条不响就说明审查根本没在量。 - 提示词不再自相矛盾:让模型读提示段,看「文档分工」一节——
不应再出现「≤50 字 / ≤20 字」这类死数字,应是「随项目规模变,以limits为准」。 - 文档锁的理由也换了:在「只拼不写」会话里让模型
write拼图文档,
被拦时回的那句理由不应再列具体数字,而应指向limits。 - 回归:
op:size三档切换仍正常;工作流的「名字 ≤20 字 / 每步 ≤80 字」照旧生效。
第 1、2 条都在真机上看;自动化断言只能证明「审查读的是档位上限、且校验真的在跑」,
不能替代真机。
v0.24.1
v0.24.1 · 两处「看着烦」的收拾:接续不翻代码 + 空态删占位
两处都是用户直接点名的观感 / 轮数问题。没有新能力,只有两件该早就做的事。
① 接续会话不再自己翻代码推进度
用户原话:
把接续会话里的分析代码删了吧,这种事交给专门的审查就行了,不然拆来拆去看得我烦。
「接续会话」按钮填进输入框的提示词,第 5 步原先是:
5. 结合代码与文档的**当前状态**判断进度(哪些写完了、哪些还是空壳 / TODO),从断掉的地方接着做
删掉,换成:
5. 从文档里读到的断点直接接着做——**不要从头重做已经做完的部分**。
理由与 v0.21.0 删「报五维最弱项」完全一致:接续会话的读者是干活的人,
不是评审。让它先报分数、或者自己翻代码推进度,都是把「继续做」变成「先做一轮评估」——
而后者更贵,因为它要真的把源码读进来。进度该由 op:audit 的客观发现给(那是专门的审查)。
刻意保留第 4 步「按源码索引跳、不要全仓搜」:那条不是分析代码,是防乱翻的护栏,
删了反而更容易满仓找。断言里专门有一条反向保护钉着它。
多绑定警告里同族的「逐个判断进度」也一并改成「逐个接上」。
② 空态中栏删掉虚线占位卡
用户原话:
把未绑定态面版中间的白色大块占位删了,没用。
删掉空态(未绑定态)中栏那块虚线卡:
删掉前 删掉后
┌ ─ ─ ─ ─ ─ ─ ─ ─ ─ ┐
│ [收件箱图标] │
│ 本会话还没绑定拼图项目 │ ← 与顶部副标题重复 ──── 全删
│ 新会话默认是空的,不会… │ ← 但这句是有效信息 ──── 降级成一行提示
└ ─ ─ ─ ─ ─ ─ ─ ─ ─ ┘
↓
「或交给 AI」三个按钮 ← 位置前移
删它的两条理由:
- 标题与面板顶部副标题「本会话未绑定项目」重复——面板出现在空态本身就说明了这件事;
- 它占着中栏最值钱的位置——下面紧跟着才是「或交给 AI」的三个真按钮,那块卡把主操作往下推。
但那行灰字不能一起删(这条是测试拦下来的)
动手删整块时 npm test 当场报红:
AssertionError: 空态要说清不会自动占用别人的项目
卡里那行灰字是回答**「我的项目怎么不见了」**的信息(新会话不会自动占用上一个会话的项目),
来自 v0.14.0「一个会话只绑一个项目」那次大改,有一条断言专门钉着它。
所以最终处理成删占位、留信息:去掉虚线框、图标、重复标题,那行字降级成一行普通提示
(不占额外高度、不带框与图标)。信息不丢,占位没了。
顺带清理
- 随之成为孤儿的
inbox图标(16px 圆里一道横)已删——它只被这块卡用; dshpz-empty/-emptyicon/-emptytitle三个 CSS 类保留:「还没有模块」那块还在用它们,
删了就破相(删之前查过引用)。
验证
新增 8 条断言,逐条做过变异验证(去掉修复即红):
| 断言 | 钉住什么 | 变异结果 |
|---|---|---|
| 接续模板不含「结合代码与文档」 | ① | 放回原句 → ❌ 红 |
| 接续模板不含「空壳 / TODO」 | ① | — |
| 接续模板仍含「不要全仓搜」 | ① 护栏 | 拆掉护栏 → ❌ 红 |
| 接续模板仍含「不要从头重做」 | ① | — |
| 多绑定警告不含「逐个判断进度」 | ① | — |
空态无 dshpz-empty 卡 |
② | 把卡放回去 → ❌ 红 |
空态无 dshpz-emptyicon |
② | — |
空态无 dshpz-emptytitle |
② | — |
| (原有)空态仍含「不会自动占用」 | ② 信息不许丢 | — |
npm test 全绿:59 + 41 + 7 + 49 + 13;跨插件握手 18 / 0。
UI.md 改过 → 已重渲染教程图(只有 04-已绑定态 与长图变化,与正文一致)。
验收判据
patchReload: startup)——本次改的都是宿主半 / 浏览器半,
不重启不生效。
重启后:
- 空态不再有大块占位(最直观):新开一个没绑项目的会话,打开面板——
中栏第一眼就是「或交给 AI」那三个按钮,上方不应再有虚线框、图标或
「本会话还没绑定拼图项目」这行标题。 - 但那句说明还在:中栏应仍能看到一行「新会话默认是空的,不会自动占用上一个会话的项目。」
——它没丢。若这行也没了,就是删过头了。 - 接续不再翻代码:点「接续会话」按钮,看填进输入框的提示词——
不应再出现「结合代码与文档的当前状态判断进度」「空壳 / TODO」这类要求;
应仍保留「按主文档的源码索引直接跳到源码,不要全仓搜」。 - 回归:三个按钮(照现有项目搭文档 / 快速建空壳 / 采访后再建)都还在,点了仍只填提示词、不自动发送。
第 1、3 条在真机上看;自动化断言只能证明「渲染树里没有那个类名」「提示词里没有那句话」,
不能替代真机——它证明不了版面看起来舒服。
v0.24.0
v0.24.0 · 重复思考熔断:模型绕圈时把它推出去
长任务里模型会陷进「同一步反复做同一件事」——反复读同一个文件、反复跑同一条命令、
反复用同样的参数调同一个工具。每一轮都要把整个上下文重发一遍,所以空转的每一轮
都是实打实的成本;而模型自己往往出不来,因为它看不到「我刚做过」(历史每轮重发,
重复的动作混在长上下文里不显眼)。
这一版给宿主加了一道熔断:同一个工具连续 3 次用同样的参数调用,就注入一条提示,
把模型推出去。
判据:工具调用签名,不是推理文本
「重复思考」在动作层有确定性指纹:工具名 + 参数。抓思维链要读模型输出的
reasoning,形状随模型/版本变、拿不稳;动作层的指纹稳定,而且可以纯函数测试。
签名做了两件必要的归一化:
- 键排序:模型每次生成参数时键序可能不同(
{a,b}与{b,a})。不排序的话
同一件事会算出两个签名,连击永远数不到 3——熔断就成了永不触发的死代码。
这是有断言钉住的(变异验证:去掉.sort()当场红)。 - 长文本截断:参数里塞了整个文件内容时,签名只留前缀,别把内存撑爆。
为什么挂在 tools/post-execute
不是 agent/pre-step——这是本仓已经用假绿换过的教训:
agent/pre-step的decision.messages契约是UserMessage[],
里面根本没有 tool-call。旧测试伪造了一条带 tool-call 的 assistant 消息,
于是「拦截生效」是假绿——真实运行时永远走不到那个分支。
真实的工具调用只在 ToolExecution 上(exec.name / exec.arguments)。
而 post-execute 的 additionalContexts 是唯一能「不拦、不打断、只提醒一句」的
通道(pre-execute 的 allow 分支会忽略 reason 字段)。这条与「工作流触发」同源。
三条误报防线(宁可漏报,不可误伤)
熔断最怕的不是漏报,是误伤正经工作——一个总在乱响的提醒等于没有。
| 防线 | 挡住什么 |
|---|---|
| 无参调用一律不计 | job_list / 截图 / 读状态这类轮询工具,反复调是正常行为 |
| 阈值 3 | 连续 2 次相同调用极常见(改完再跑一次测试),留一步容错 |
| 同一段连击只报一次 | 报过之后要等签名变了才重新武装,否则第 4、5、6 次各报一条,比不报更烦 |
提示长什么样
触发时注入一条以 【拼图模式 · 重复思考熔断】 开头的消息:先把事实摆出来
(「你已经连续 3 次用同样的参数调用 X——这一步没有产生新信息」),再给三条具体出路:
- 换输入——参数里有什么不同,让这次调用真的带回新东西;
- 换动作——要的信息已经拿到了,直接下结论 / 动手改,不要再确认一遍;
- 停下来说清——确实卡住了(缺权限 / 缺信息 / 工具坏了),把卡点讲给用户。
语气刻意是陈述事实 + 给出口,不是训斥:模型不是「不听话」,是它看不到自己刚做过。
只说「别重复」等于没给信息,所以三条出路缺一不可(有断言钉住)。
提示段同时补了第二道(hook 判定 + 提示段兜底),与「首轮自动判定」同一条设计。
验证
新增 13 条断言(test/60-loopguard.test.mjs),按本仓约定断言引常量
(阈值取 LOOP_REPEAT_THRESHOLD,不写死 3),并且每条都覆盖接线与误报两侧:
| 组 | 钉住什么 |
|---|---|
| 纯函数 | 键序不敏感 / 工具名与参数敏感 / 无参不计 / 连击计数 / 只报一次 / 换签名重新武装 / 按会话隔离 / 空 sessionId 安全降级 |
| 提示文本 | 点名工具与次数 / 带标记 / 三条出路齐全 |
| 接线 | 真驱动 tools/post-execute:连续重复后真的注入了提示,且内容正确 |
| 误报自证 | 参数每次不同 → 一条都不注入;无参轮询 → 不注入;工具失败(block)→ 不插话 |
两处变异验证都真的红了(本仓规矩「守卫须能抓到漂移否则形同虚设」):
| 变异 | 结果 |
|---|---|
| 拆掉熔断钩子(接线断开) | ❌ 必须有一个钩子...注入熔断提示 |
| 去掉签名的键排序 | ❌ 键序不同 → 同一个签名 |
还原后复跑全绿,无残留。
npm test 全绿:59 + 41 + 7 + 49 + 13;跨插件握手 18 / 0。
验收判据
patchReload: startup)——本次改的是宿主半,
不重启的话钩子不会加载,熔断完全不生效(不会有任何报错,只是安静地不工作)。
重启后:
- 熔断会响:让模型连续三次用同样参数调同一个工具
(最省事的造法:让它反复读同一个文件,或反复跑同一条命令)。
第 3 次之后,下一轮应看到一条以【拼图模式 · 重复思考熔断】开头的提示,
里面点名了工具名与次数(「连续 3 次」)。 - 不误伤:让模型连续调同一个工具但每次参数都不同(读不同文件)——
不应出现那条提示。 - 轮询不算绕圈:反复调
job_list/ 截屏这类无参工具——不应出现提示。 - 不打断:熔断不拦工具、不弹审批框。第 3 次调用本身应照常成功,
提示只是附加在结果后面的上下文。若某次调用被拒或被要求审批,那就是 bug。 - 同段只报一次:继续原样再调第 4、5 次,不应每轮都报一条。
第 1、2 条都在真机上看;自动化断言只能证明「接线没断、签名算得对」,
不能替代真机——它证明不了模型真的会因此改道。
v0.23.3
v0.23.3 · 修「项目规模」这条线上的三个 bug(其中一个是静默删数据)
三个 bug 都长在「项目规模」这条线上。第一个会静默删掉你的文档条目,
而且是本插件在自己的文档上真的发生过。
① 重写 front-matter 漏透传 规模: → 静默删除条目(严重)
现象:往主文档追加一条「坑」(或写一次工作流、改一次会话绑定)之后,
规模: 这一行从 front-matter 里消失了。
后果不是「显示回中档」这么轻:
规模: 大 →(写一条坑,重写 front-matter 时漏透传)→ 字段被抹掉
→ 下次读按「中档」算 → 中档的 pit 上限更严 → 下一次写入静默删掉超出的条目
实测代价(本插件自己的文档):主文档「坑」从 149 条被砍到 60 条,丢了 93 条。
被删条目只出现在工具返回值 droppedEntries 里——界面上没有任何提示,你不看返回值就不知道。
范围:三条路径都漏了,不只一条:
| 路径 | 触发动作 |
|---|---|
updateMainSection |
写坑 / 写工作流 / 写主文档任何小节 |
writeWorkflowDoc |
写工作流归档 |
writeSessionList |
改会话绑定 |
setMainFields(改模式 / 改源码根那条路)本来就有这一行,注释里也写明了这个后果——
另外三条路当初漏了。三处已补齐。
这条也解释了为什么「改个模式就掉档」这类报告难复现:丢数据的动作与设置档位的动作不是同一个,
中间隔着一次无关的写入。
② 大档的「坑」比中档更严——切档反而收紧
SIZE_CAPS.中.pit = null(不限),而 大.pit = 60。实测(真驱动写入函数):
| 档位 | 写 70 条坑 | 结果 |
|---|---|---|
| 中 | 落盘 71 条 | 全留 |
| 大 | 落盘 60 条 | 被砍掉 10 条 |
这恰好违反分档的初衷(大项目更不该丢内容),而「坑」是只增不减的踩坑记录——
决策类被取代还有理由删,坑被删就是纯粹的信息损失。
改成 大.pit = null,恢复单调性:小 10 ≤ 中不限 = 大不限。
客户端 FALLBACK_LIMITS 同步——契约测试「与宿主逐字段相等」当场抓到这一处,正是它该干的活。
③ 切项目时面板沿用上一个项目的模式
用户报「执行模式怎么继承到下一个打开的面板了」。
不是数据被写串了(宿主侧两个项目的 模式: 各写各的,单独验过),
而是切项目时只改了「当前是哪个」的标记、没换 mode,而重读是异步的:
① 当前 A(只拼不写) 高亮 = 只拼不写
② 在 A 点「边拼边写」 高亮 = 边拼边写
③ 切到 B(回包未到) 高亮 = 边拼边写 ← 这是 A 的模式,就是「继承」
④ 回包到达 高亮 = 写后再拼 ← 最终才对
改成从胶囊就地取目标项目的 mode / health——胶囊本来就带这些字段,
所以这一步不需要任何额外请求。权威值仍以随后的重读为准,这里只是把窗口期填对。
modules刻意没动:胶囊里只有模块个数、没有列表,填不对;清空又会多一次
「模块区闪一下空」的抖动。所以窗口期内模块区可能还是上一个项目的——这是已知的、
刻意留下的同族残留,注释里写明了,不要以为它也被修了。
验证
新增 3 条断言,逐条做过变异验证(去掉修复即红):
| 断言 | 钉住什么 |
|---|---|
| 规模档位在所有重写 front-matter 的路径上都被透传 | ①(7 条路径逐个过,不只测踩到的那条) |
| 切项目:面板立刻换成目标项目的模式 | ③ |
| (20-client 已有的规模 / 模式两条延续) | ② |
第一条刻意写成「7 条路径逐个过一遍」(写坑 / 写工作流 / 写模块 / 改模式 / 改源码根 /
写会话 / 写归档),因为同一形状的坑在本仓已经出现过多次——只测踩到的那条,
下次换个入口又中。
又一次假绿(本轮第三次,成因各不相同)
切项目那条断言最初写成 onClick() 之后 await setTimeout(0) 再读渲染树。
但本地更新是在 current 请求的 .then 回调(微任务)里跑的,而 setTimeout 是宏任务:
回调还没跑就读树,看到的是「点了没反应」那一态——断言与修复无关,删掉修复照样绿。
改成排空微任务(await Promise.resolve())才咬住。三种假绿成因已全部写进注释:
- 宏任务越过回包(
setTimeout排在微任务链之后) - 假 React 的
useEffect每次渲染都跑,面板 effect 里的load()把乐观更新冲掉 - 微任务里的本地更新没排空
npm test 全绿:59 + 41 + 7 + 49;跨插件握手 18 / 0。
验收判据
patchReload: startup)——本次改的是宿主半,
不重启的话修复不生效,旧代码仍会砍你的文档。
重启后:
- 档位不再丢(最重要):把某个项目设为「大」,然后写一条「坑」或写一次工作流,
再op:read看size—— 应仍是「大」,front-matter 里也应仍有规模: 大那一行。
若它变回「中」,就是本 bug 没修好(并且此时再写大段内容会开始删条目)。 - 大档不再比中档严:
op:read看limits.entryCaps.pit—— 大档应是null(不限)。
实测口径:写 70 条坑,一条都不该被删(dropped为空)。 - 切项目不残留:绑两个项目、模式各不相同。在 A 上点一个模式,再点 B 的胶囊——
高亮立刻应是 B 自己的模式,不是 A 的。 - 回归:切换条一直在;规模三档的「当前上限」仍是三个不同的数字。
第 1、2 条都在真机上看;自动化断言只能证明「接线没断、字段没丢」,不能替代真机。
如果你已经在旧版本上丢过条目:被删的条目名与原文只出现在那次工具调用的返回值
droppedEntries里,界面上没有痕迹。建议对重要项目先做一次外部备份(本插件
不做自动备份)。
v0.23.2
v0.23.2 · 修两个面板显示 bug:胶囊模式「弹回去」+ 规模切换「假态 / 延时」
两个都是真机报出来的,而且都不是「界面崩了」——是显示的值不对。
所以这版新增的断言一律写成「断言渲染出来的文本 / 高亮」,而不是断言内部字段:
读错字段时界面照常渲染,不报错、不空白,只有渲出来的字能抓住它。
① 写操作返回缺绑定组 → 胶囊上的模式「点了又自己弹回去」
现象:点「边拼边写」,按钮亮了,但切换条上那个项目的胶囊仍显示旧模式,
看着就像「点了又自己弹回去」。
根因:mode / size / current / workflow 四个写操作的返回原先只有
summarize(readState(...)),里面没有 bindings;而客户端 mergePanelData 的规则是
「新值没给这个键就沿用旧的」。
这条规则本身是对的(它防的是「切着切着胶囊没了」),但只防得住「值没了」,防不住「值过期」:
胶囊的悬停提示直接渲染 item.mode(健康性同理由 % 渲染),旧数组原样留着,
于是那个字段停在改动前。
修法:抽 bindingPanel(...) 一处算绑定组,state 与四个写操作共用同一份——
而不是让每个写操作各自拼一遍(那正是「两把尺子」类漂移的温床)。
客户端那层合并保留,降级场景(老版本宿主、返回被截断)仍然需要它。
② 项目规模切换「假态」+「延时切换」
假态:那行「当前上限:悬而未决 ≤x · 已定 ≤y · 工作流 ≤z · 坑 …」原先读
limits.sizeCaps——那是「小 / 中 / 大三档的整张表」,不是当前这一档。
于是 caps.pending 恒为 undefined,靠 undefined === undefined ? 4 : … 的兜底
永远显示中档数字:
| 你点了 | 那行显示 | 应该显示 |
|---|---|---|
| 小 | 已定 ≤10 | 已定 ≤6 |
| 中 | 已定 ≤10 | 已定 ≤10 |
| 大 | 已定 ≤10 | 已定 ≤30 |
真值在 limits.entryCaps(= capsOfSize(当前档))。改读它,并留两级回退。
延时:按钮只在回包后才变色(写盘 + 重读 + 回包)。
这三颗是「我点了一个开关」的直接反馈,必须按下即亮——改成乐观更新。
同一处纪律也补给了执行模式按钮(它原先同样要等回包)。
两处的失败分支都改成「回宿主重读」:乐观更新已经把界面翻过去了,
不拉回来面板就会停在一个没写进文档的假态上——正是这个 bug 的形状。
验证
新增 4 条断言,逐条做过变异验证(把对应修复去掉,断言即红):
| 断言 | 钉住什么 |
|---|---|
RPC:mode / size / current 都回完整绑定组且值是新的 |
① 的根因(接线断了) |
| 规模:三档渲染出三行不同的「当前上限」 | ② 的假态 |
| 规模:扣住回包时点「大」也立刻翻档 | ② 的延时 |
| 模式:扣住回包时点一下也立刻高亮 | 延时的同一处纪律 |
第 1 条刻意驱动真实 RPC 路由而不是只测 bindingPanel 纯函数——
这个 bug 的本质是接线断了(bindingPanel 自己怎么写都对,错在分支根本没调它),
纯函数断言全绿也照漏。
一个写进注释的坑:第一版断言是假绿的
「按下即亮」的断言,我第一版是 onClick() 之后 await 一个宏任务再读渲染树。
它把乐观更新整个删掉照样绿。 两个原因叠在一起:
setTimeout(0)是宏任务,排在微任务链之后——fetch → json → then早已跑完,
读到的其实是回包之后的状态;- 测试用的假 React 的
useEffect是每次渲染都跑的(真实 React 只在挂载 / 依赖变化时跑),
而面板 effect 里有load(sessionId)——那次state拉取会把乐观更新冲掉。
修法是「扣住回包 + 同步读树」:给写操作加一道闸门扣住响应,onClick() 之后
同步读树,此刻 store 里只可能是乐观更新写进去的那份。
这条值得单独说:断言绿 ≠ 断言有效。变异验证(去掉修复看它红不红)才是那把尺子。
npm test 全绿:58 + 41 + 7 + 49;跨插件握手 18 / 0(无限五代不在场,互校跳过)。
验收判据
刷新页面后(宿主半改动需重启 profile,纯客户端改动刷新即可):
- 规模:点「规模」区的小 / 中 / 大 ——
- 按钮按下立刻亮(不等回包);
- 下面「当前上限」那行三档显示三个不同的数字:小 =「已定 ≤6」、
中 =「已定 ≤10」、大 =「已定 ≤30」。若三档都显示「已定 ≤10」,就是本 bug 没修好。
- 执行模式:点「写后再拼」/「边拼边写」—— 高亮立刻切过去。
- 胶囊:绑两个项目后点模式按钮,把鼠标悬停在那个项目的胶囊上,
提示里应是新模式(形如「当前项目 · aaa(边拼边写 · 健康性 62% · 3 个模块)」),
而不是改动前那个。 - 回归:切换条仍然一直在(不会切几次就只剩一个胶囊)。
以上 4 条都在真机上看;自动化断言只能证明「接线没断、渲染出的字跟着档位走」,
不能替代真机。
v0.23.0
v0.23.0 · 项目规模(小 / 中 / 大)+ 删掉面板那颗审查按钮
三件事:① 项目分档位、条目上限随档放宽或收紧,并让「照现有项目搭文档」自动判档;
② 删掉面板「看项目」区那颗「审查 / 真实值」按钮与它配套的显示块;
③ 查一遍缓存与提示段稳定性,修掉查出来的真问题。
① 项目规模:小 / 中 / 大
为什么要它:固定上限对两头都不合适——小项目(几百行脚本)的「已定 10 条」根本写不满;
大项目(几万行、十几个模块)四条「悬而未决」一上午就顶满,于是最旧的决策被静默挤掉,
而那正是文档存在的意义。
| 档位 | 悬而未决 | 已定 | 工作流 | 坑(主文档) | 字数上限 |
|---|---|---|---|---|---|
| 小 | 4 | 6 | 3 | 10 | 与中档相同 |
| 中(默认) | 4 | 10 | 5 | 不限 | 与升级前相同 |
| 大 | 12 | 30 | 12 | 60 | 要点/已定/悬而未决/可复用 40 字、详细记录 80 字、主文档条目 80 字 |
三个设计取舍:
| 取舍 | 为什么 |
|---|---|
存主文档 front-matter 的 规模: |
跟着项目走——换会话、换机器都还在;且一个项目里的模块不该各写各的 |
| 中档 = 升级前的全部值 | 没写过 规模: 的存量文档一个都不用改,行为与升级前逐字节一致 |
| 写「中」时删掉那一行 | 默认值写出来是噪音;读侧认不出就当「中」 |
改档位:面板「规模」三颗按钮,或 puzzle_mode{op:'size', size:'大'}。
顺带修掉一个真问题(缓存检查查出来的)
提示段 POLICY_BODY 是模块级常量(为了缓存稳定——每轮注入的字符串必须逐字节相同,
否则提示缓存每步都 miss)。但它原先用 ENTRY_CAPS.pending(中档固定值)告诉模型
「悬而未决 ≤4 条」——而大档实际能写 12 条。
后果:模型照提示段写就少写;照写入侧写又「违反」提示段。两边数字不一致。
修法(两半,缺一不可):
- 提示段不再写死数字,只说「各节上限随项目规模变化,动手前先
op:'read'看返回里的limits」; receipt下发的limits改成按当前档算(limitsFor(state.size)),与写入侧同一个函数。
这样提示段保持常量(缓存不破),真实上限由每轮返回携带。
「照现有项目搭文档」自动判档
adoptTemplate 加了一步:让模型按第 1 步真读到的数字自判档位并 op:size 写下来——
小(≲2k 行、≲3 模块)/ 中(≲20k 行、≲8 模块)/ 大(更多)。
说不准就写「中」(默认档,写错无额外后果)。
② 删掉面板的「审查 / 真实值」按钮
删了两样(用户裁定「看项目的审查没什么用」):
- 「看项目」区的 「审查 / 真实值」按钮(它拉
op:audit的 RPC); - 配套的 「真实值(声明 → 实测)」显示区——没按钮拉数据,它就永远是空的。
审查能力本身没动:op:audit 还在,「让 AI 动手」区的「审查(交给 AI)」仍可用;
state.findings 那块「审查 · 客观发现」也照旧(它随 state 下发,与那颗按钮无关)。
③ 缓存检查:查了什么、结论如何
| 检查项 | 结论 |
|---|---|
| 提示段是否随会话/时间变化 | ✅ 稳定:跨会话、20 次调用逐字节相同(6684 字符),无时间戳 / 随机 / 会话 ID |
| 提示段是否含项目名或绝对路径 | ✅ 不含(用 <工作区> 占位),跨项目可复用同一份缓存 |
| 提示段是否写死条目上限 | ❌ 是(见 ① 的修复) |
| 写后立即读(同进程) | ✅ 正确:setMode / updateMainSection 写后立刻读到新值 |
| 同毫秒连续追加 | ✅ 正确:连续 5 次追加,条数 2 → 7 累积无丢失 |
外部直接改文件(不经 atomicWrite) |
✅ 正确:mtime+size 指纹发现变化并重读 |
| 工具描述是否随会话变化 | ✅ 常量(模块级),其中的 ${ORDER} 是进程内固定值(环境变量不会中途变) |
验证
| 项 | 结果 |
|---|---|
npm test(10/20/30/40/50 + 握手) |
58 + 40 + 7 + 49 全绿,握手 18 通过 / 0 失败 |
| 新增断言 | 4 条(规模中档=旧值 / 大档放宽小档收紧 / 提示段不写死上限 / receipt 是当前档真值) |
| 逐条验红 | 改回去就红(实测:中档改 5 → 红;提示段写死 ENTRY_CAPS.pending → 红) |
验收判据
npm test→ 退出码 0,末尾跨插件握手校验: 18 通过 / 0 失败。- 面板打开一个已绑定项目 → 五维下面出现**「规模」三颗按钮**(小 / 中 / 大),
当前档高亮;下方一行写着当前各节上限(坑在「中」档显示「不限」)。 - 点「大」→ 按钮切换;再点「仅迁移格式」不相关;
op:read返回的limits.entryCaps
变成{pending:12, decided:30, workflow:12, pit:60}。 - 切「中」→ 主文档 front-matter 里没有
规模:那一行(默认值不留噪音)。 - 大档下写第 11 条「已定」→ 成功(中档会被删到 10 条);切回中档再写 → 只留 10 条。
- 小档下写 15 条「坑」→ 只剩 10 条(最旧的 5 条被删)。
- 面板「看项目」区不再有「审查 / 真实值」按钮,也不再有「真实值(声明 → 实测)」区块;
「让 AI 动手」区的「审查(交给 AI)」仍在。 - 老项目(没写过
规模:)打开面板 → 显示「中」,条目行为与 v0.22.0 完全一致。
装法
见 README 的「下载与安装」。
附件 dsh-puzzle-mode-0.23.0.tgz 可直接用插件管理器的「本地压缩包」入口装。
v0.22.0
v0.22.0 · 与「无限五代」同装时按契约共存
本版把跨插件共存从复刻仓并回主线:与 dsh-infinite-gen-5(无限五代)同装时,
两边不再各说一套,而是段序可协商 + 六条分工条款 + 双向核对。
问题:两个插件都自认「最后一段」
两个插件都会往系统提示里插一段常驻规则,而它们对同一批动作给过相反的要求:
| 冲突点 | 本插件原先说 | 无限五代说 |
|---|---|---|
| 提问额度 | 一轮最多 10 问、每题 3–6 个岔路 | 同一轮最多一问、2–5 个互斥选项 |
| 整批题在场 | 照常采访、提问 | 批量合同优先:不采访、不回问、不中停 |
| 段序 | 写死 order = 10500(自认最后一段) |
真末位锚点 order = 10150(自认最后一段) |
两边都「自认最后一段」——同装时那句话必然有一边是假的。规则打架时模型只能猜。
改法一:段序从写死改为可协商
const DEFAULT_SECTION_ORDER = 10120
const ORDER = Number.parseInt(process.env.PUZZLE_SECTION_ORDER ?? '', 10) || DEFAULT_SECTION_ORDER
export const SECTION_ORDER_VALUE = ORDER- 默认 10120:小于无限五代末位锚点
10150,于是它的「本载荷末位」成立; - 刻意避开 DSH 内置段序表的全部取值——内置表里有
WEB_SURFACE: 10100,
同号时段序相同时按段名比较,位置就不再由协商决定; - 需要恢复旧行为:
PUZZLE_SECTION_ORDER=10500 dsh ...。
复刻仓原先默认
10100,正好撞上内置的WEB_SURFACE: 10100。并入时改成10120。
改法二:六条分工条款写进提示段
两份载荷的规则落在同一份提示里,不靠外部约定:
| 规则 | 谁说了算 |
|---|---|
| 域划分 | 交付物内容与形态 → 无限五代;拼图文档与采访节奏 → 本插件(不重叠,第一顺位) |
| 提问额度 | 采访轮 / 审查确认轮 → 本插件额度;其余场景 → 无限五代口径 |
| 批量优先 | 整批题在场 → 无限五代批量合同优先;拼图文档只在该轮结束后幂等回写 |
| 工具形态 | 拼图文档只走 puzzle_mode;其它工具的 schema 不受「参数扁平」约束 |
| 停下语义 | 只停动作:本轮不写,但已交付的产物 / 结论不回退 |
| 末位让位 | 无限五代的末位锚点只声明「本载荷的末位」,不声明整份提示的最后一段 |
改法三:把「会不会漂」变成能自动发现
compat.json:双方同一组数字的契约(ig5-puzzle-coexist/1);tools/verify-cross-plugin.mjs(npm run verify:cross,已挂在npm test链尾):
自证 + 装了对方时双向互校——对方记的拼图段序 == 本仓默认、对方记的末位锚点 == 本仓契约、关系成立;.github/workflows/verify.yml:CI 跑全链 + 契约可解析 + 包内容门禁;test/50-contract.test.mjs新增 2 条钉字面量断言(共存数字改了必须红)。
对方不在场时走「互校跳过」分支并标注,不判失败——CI 上没有那个仓库,
这一分支必须能跑通,否则 CI 会红在本机永远走不到的那条路上。
验证
| 项 | 结果 |
|---|---|
npm test(10/20/30/40/50 + 握手) |
58 + 37 + 7 + 45 全绿,握手 18 通过 / 0 失败 |
| 新增契约断言 | 2 条,逐条验过「改回去就红」 |
| 段序环境覆盖 | 实测 10120(默认)/ 10500 / 10000 / 非法值回退默认 |
| 包内容 | npm pack --dry-run 含 compat.json、COMPAT.md、tools/verify-cross-plugin.mjs |
两条断言的验红方式(都是真删真改,不是看着对):
- 把
DEFAULT_SECTION_ORDER改回10100→ 红(同时撞内置段序、与 compat.json 不符); - 真删「批量优先」那条整行 → 红(
政策文本缺条款:batch-first)。
验收判据
npm test→ 退出码 0,末尾出现
跨插件握手校验: 18 通过 / 0 失败;无限五代不在场时带「互校跳过」标注。PUZZLE_SECTION_ORDER=10500 node -e "import('./lib/index.js').then(m=>console.log(m.SECTION_ORDER_VALUE))"
→ 打印10500;不带环境变量 →10120。- 装了两边插件时
npm run verify:cross→ 六条文本条款与两个段序数字双向核对通过;
任一侧改数字 → 两边都红并指出改哪一处。 npm pack --dry-run→ 输出里有compat.json、COMPAT.md、tools/verify-cross-plugin.mjs。- 真机:同装无限五代时,拼图提示段排在它的末位锚点之前(
10120 < 10150),
两边不再抢「最后一段」。
来源
这套兼容层最初由 @SunsetRNE 在复刻仓
SunsetRNE/dsh-puzzle-mode-2 做出来(「上游管功能,本仓管兼容」),
与无限五代 SunsetRNE/dsh-infinite-gen-5 的 data/arbitration.mjs 逐次互校。
本版并入主线,此后上游每次发版自带这层,不再依赖复刻仓逐版追赶。
装法
见 README 的「下载与安装」。
附件 dsh-puzzle-mode-0.22.0.tgz 可直接用插件管理器的「本地压缩包」入口装。