Replies: 14 comments
|
你的三道闸门我逐条对着源码核实了,全部属实 —— 包括“迁移比消费者严格”那一点:
我把这个诊断做成了一个只读扫描器,让踩坑的人先能定位、再决定要不要动手: npx dsh-session-check scan ~/.dsh/sessions它不写任何文件 —— 在“该不该动手改历史”这个问题上,它只负责告诉你是谁、为什么。 零误报是拿官方校验器反证出来的:被拦的会话会在扫描器指名的那个事件上,被 过程中修掉两个误报,否则工具会冤枉好人:
在我本机 32 份真实存档上,这两处任一写错,就会把 20 个真正被拦的报成 30 个。 一个可能有用的实测对照已有的 dsh-session-doctor 在同一个语料上跑 关于你那份配方你本机 123 份、 要不要我把它一起打包进这个工具? 帧级手术那块我照你的配方实现, 你这条帖子 0 回复,但你这个诊断值得被更多人看到。 |
|
独立复现:同一组闸门,另一台机器 / 另一份语料,补一条你 ① 类里没有的形状。
修前的分布(用官方
|
| 类 | 会话数 |
|---|---|
source.kind 未注册(退役或插件自造) |
22 |
已知 kind 的 source 带白名单外字段 |
20 |
subagent/descriptor 的 version: 2 |
231 |
未知事件类型、但 ignorable: true |
17 |
(同一会话可能同时命中多类,所以不要直接相加。)修完前三类后,官方口径下 干净 355 / 372,剩下的 17 个全部是 message-edit/version,正好落在你 ③ 之外的那类上。
增量:① 类里有一半是「第三方插件新造 kind」,不是退役的核心 kind
我这 22 个的构成是:automation 16 + at-file-mention 4 + instruction-hint 2。
后一个与你的 ① 同形(核心曾自己写过,归一化映射加在核心侧就行);前两个核心从来没写过这个 kind,所以核心侧没有"改名映射"可加 —— 只能改插件的写入形态。带行号:
@dsh-external/dsh-automation写侧lib/index.js:21906:{ kind: "automation", automationId, runId, scheduledFor }。改的时候必须同时改回读侧lib/index.js:22084——ownsSession()正是按source.kind === "automation"判定"这条会话属于哪次自动化",只改写入端会让已经修好的历史会话不再被识别。dsh-at-file写侧lib/index.js:15965:{ kind: "at-file-mention", relative }。这个 kind 在插件内没有被读回,是纯写入点。
两个都已报到插件仓库(原帖就是同一个现象):
- BUG:[dsh-at-file] 写入未注册的 source.kind="at-file-mention",导致旧格式会话迁移失败(历史加载失败) FSMargoo/dsh-at-file#39 (追加评论)
- 写入未注册的
source.kind="automation",导致旧格式会话迁移失败(会话打不开、内容搜索整体不可用) titanwings/dsh-automation#26
我自己是按 {kind:"plugin", plugin:"<包名>"} 归一化修的(逐帧改写、只重压变化帧、逐文件备份),修完这 22 个会话能正常迁移、也能进内容索引 —— 与你的 ① 结论一致:这里需要的只是键集,而 plugin 源的键集本来就允许附加字段。
一条独立数据点
descriptor.version !== 3 这一类在我这台机器占 231/372(62%),是四类里最大的一类,和 #6545 里 34/323 的观察方向一致:这一类不是边角案例,值得优先做宽松化。
关于"为什么所有报这条的人都得先做一遍考古",我在 #6545 下补了一条具体位置(Web 侧把错误对象整段丢了),与你这篇的期望 2 相关。
|
两条都读了,先答 @Robin1987China 那个问题:配方拿去打包,署名走 README 致谢 + 链回这条讨论就行,共同作者不必 —— 这包我不维护,挂了名却管不了行为。判据要改我会在这帖追一条,不悄悄编辑。 然后一份双向对照,把
但"拿官方校验器反证零误报"这条,我这台给出了反例,而且两个方向都有。 真迁移链是
给 @483218131 补一条键尺寸的数据:我那 81 行 两处环境提醒: 上游到今天零回应,三道闸在发布线上原样在(这台 0.1.5-rc.1、组件 rc.2)。你们两份独立语料 + 我这台 = 三台机器四种形状,这比任何一条单独的报告都有用。 |
|
上一条里有一处我只给了推断,现在有实测,补在这里 —— 都是公开仓库 HEAD,跟本机装没装这两个插件无关。 @483218131 你引的三个行号逐个对上: @Robin1987China 给你的 对你那张分布表也剩一条:22 个「未登记 kind」不可能出自 |
|
给你补一份 master(c291e7961)TS 源码侧的三道闸门精确定位(原帖引用的是 npm 编译产物行号,以下对应到源码),并确认:三道闸门在 master 上原样存在、仍未修复。
结论与你一致:拒载粒度是整条会话、无行级降级;"行级降级 + warning"属于官方决策。对踩坑用户的建议维持不变:先用只读检查工具定位具体闸门,你的修复配方已双向验证过。我未在本机重跑迁移(未验证),行号以 master 为准。 |
|
@PerryLink 六处行号逐个核了,全中(ref 取 更要紧的是它不只在 c291 上成立:今天 master 和 你说未在本机重跑的那半,我这边跑过:这台机器发帖时 123 份 v0 存档、49 份拒载,三处归一之后今天全库 128 份官方 三处你没点、但写修复时用得上:
产物行号 ↔ TS 源码行号 ↔ 最新 tag 状态,这三样现在齐了,踩坑的人不必再做一遍考古;缺的只剩「整条会话拒载改成单行降级加 warning」这一个决定。 |
|
@mengge237 两点都收到。第 1 点是我的问题,第 2 点给你实测答案。 1. 分布表「两把尺子」—— 你说得对我那两列确实不是同一把尺子量出来的:
两者判据不同、拒载点不同,并排放一张表确实会让人误以为"同一道闸的两个分支"。 2. automation / at-file-mention 归一化时是否撞
|
|
更正我上一条里的一个说法 —— 「 我错在哪我上一条写的是:
这句话对上游成立,但对我们这台机器不成立 —— 因为我们本地对 实测证据:本地 vs 上游,只差 2 处,而且成对拿我们 pin 的 commit( @@ -21903,7 +21903,8 @@ ← 写侧
handle.agent.followup(createUserMessage({
content: [{ type: "text", text: run.promptSnapshot }],
source: {
- kind: "automation",
+ kind: "plugin",
+ plugin: "dsh-automation",
automationId: definition.id,
runId: run.id,
scheduledFor: run.scheduledFor@@ -22081,7 +22082,7 @@ ← 读侧
return events.some((event) => {
if (event.type !== "user/message" || typeof event.data !== "object" || event.data === null) return false;
const source = event.data.source;
- return typeof source === "object" && source !== null && source.kind === "automation";
+ return typeof source === "object" && source !== null && source.kind === "plugin" && source.plugin === "dsh-automation";
});
}写侧改成 所以结论要修正
你(和 顺带一个真实风险(比"归属断了"更值得说)这个本地 hotfix 没有持久化:
→ 下次 所以对我们来说,该补的不是"归属处理",而是把这个 hotfix 固化成 pnpm patch。 |
|
@483218131 那段
|
|
@mengge237 三条建议都收到了,逐条回一下处置情况。 1. 读侧「两种都认」—— 已采纳
return typeof source === "object" && source !== null
&& (source.kind === "automation"
|| (source.kind === "plugin" && source.plugin === "dsh-automation"));历史与未来两种形态都能识别,这条没有任何代价,照做。 2. 写侧 —— 我选择暂留,但理由和你的前提有一处不同你说「写侧改成 那么写侧留不留,就变成一个纯粹的取舍:
本机是每周一次的自动化任务,回退的代价是每周稳定产生一个打不开、且不进内容索引的会话, 3. 你的第 2 条提醒促成了一个更好的收尾方案「归一化之前那批 v0 原件别删 ——
也就是说「单向门」确实存在,但我们能掉头走回来 —— 前提是那批 v0 原件在。 4. 第 3 条(上游最小口子)同意
|
第 2 点你说得对,我方认「单向门」是我方把两件事压成一件说了。写侧只影响未来会话、随时能单独回退;真正不可逆的是已经重写过的历史,而历史那半恰好被我方第 1 条(读侧两种都认)拆掉了。上一条那句措辞不准。 你那张表要补一格,补完结论不变但前提变了保留写侧的这段时间里涨的是新形态那批历史:每周一份 这归结成一个只有你能答的问题:你那版写侧补丁的输出里, 上游侧今天复算了一遍(盯代码不等回音)master tip 是 |
|
补一处一个字符串级别的落点,另外两类形状另说,不塞进这条。
最小落地: 判据(盯代码就能量): 等不起的那批人现在能自己修:逐帧解 zstd、只改 kind、只重压命中的那几帧(其余帧字节不动),断言帧数/每帧行数/seq 序列/未改帧字节/头帧五项,任一不过自动用备份盖回;修完官方迁移自己把 v0 推到 v3。本机读数:修前 74/123 可读,修后 123/123。 |
|
感谢这份诊断——三台机器独立复现的结论我这里可以再加一台,并补一个我觉得对定级有用的对照。
1)我这里的分布: 用内核
113/115 = 98% 落在你 ② 那一类。 结合 @483218131 的 231/372(62%)、#6545 的 34/323,四台机器的方向完全一致:这一条不是边角,是主因。你主帖里"迁移比消费者严格"那处对照( 2)一个可能对"该不该放宽"有用的旁证 我这里那 113 条里, 3)一个不属于迁移闸门、但同样让会话点不开的类——建议在工具里分开标 本机有 1 条( 我按
→ 建议 4)对你"期望 3"的一点附议 你说口径应该是"把拒载粒度降到行级 + 记 warning,别一形状一白名单"——本机这 176 份 v0 存档横跨 2026-08-24 ~ 09-10,四台机器上已出现 6 种形状,我也认为按形状补白名单是补不完的。 |
|
这三道闸里,② 和 ③ 加上 ①,dsh-session-surgeon 现在都会在写盘前归一(main
三条都用官方 0.1.7-rc.1 的真转换器对照过:修前 边界也说清楚(你那台剩的 1 条): 还没装: |
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
现象
侧栏列得出会话,点开就失败,文案只有一句:
包装点:
dsh-session-persistence-jsonl/lib/index.js:2622。同一个requireStoredLog()里,v2→v3 的
assertSource()(dsh-session-format-v2-to-v3/lib/index.js:125)和 v0→v1 的一串校验(
dsh-session-format-v0-to-v1/lib/index.js:1586/:283)任何一条命中,整条会话就判死。一台真实机器上的分布:49 份读不出,74 份正常迁移。
三类拦路形状(都带行号,逐条可复算)
① 退役的 message source kind(32 份 / 81 行)
SOURCE_KINDS(dsh-session-format-v2-to-v3/lib/index.js:14)里没有instruction-hint,于是
assertSource()在:125抛错。旧日志里的形状是:写侧现在用的是
{ kind: "plugin", plugin: name }(dsh-agent-instructions/lib/index.js:791),键集完全相同,只是 kind 改了名。两条实测:整个 0.1.5-rc.1 安装树里
instruction-hint字面量 0 个文件命中;这台机器上 0.1.5 新写的 v3 存档里有 27 行
{"kind":"plugin","plugin":X}、0 行instruction-hint。也就是说产生这些行的代码已经退役,但迁移没留下对应的归一化映射。
②
subagent/descriptor的版本号(14 份)dsh-session-format-v0-to-v1/lib/index.js:1586:版本不是 3 且在 v0 阶段就抛。键集与 v3 完全一致(只有
mode/provider/version/label/agentProvider/agentModel,都在 released 清单里),拦路的只有那个数字。而运行时自己是这样处理的:
迁移比消费者严格:一个
parseSubagentDescriptor()愿意忽略的字段,迁移却把整条历史判死。:1586上一行本来就有return分支(v1/v2 阶段走它),v0 阶段独独改成 throw。③
agent/inbox/spliced的插入消息缺id/role(3 份)——这一类我没找到别的线程报过dsh-session-format-v0-to-v1/lib/index.js:283:messageValue(:715)要求id,我补上id之后同一个调用又要求role。调用点第 4 个实参已经把正确答案写死了 ——
"user"。校验器知道该填什么,却选择拒绝整条会话。这 3 行的其余形状(
content/source)都在,只是当年写侧没落这两个字段。期望
source.kind改名、描述符版本落后、插入消息缺id/role,这三件事都只影响这一行的呈现或续跑语义,按上面的映射归一即可(每条都只有一个正确答案)。迁移应当降级保留 + 记一条 warning,
而不是让
requireStoredLog()抛错把整条会话变成砖。dsh session migrate --force;以及能在 UI 里直接看懂的文案(现在只有 "source v0 artifact remains unchanged" + 一个路径,
用户读到的是"我升级之后历史没了")。
现在覆盖的都是"官方写侧当年会写出的形状",而本机 49 份里 100% 落在上面三条。
我本机验证过的绕法(先副本,后正式,逐文件备份,可回滚)
逐帧解 zstd(多帧追加写),只改这三处值,帧数/每帧行数/seq 序列/header 帧字节全不变,
没命中的帧连压缩字节都不动;改完用官方
JsonlSessionPersistence直接回读校验(
open(id,'read')走prepareStoredMigration,open(id,'write')才publishStoredMigration):source.kind: "instruction-hint""plugin"(键集已相同)subagent/descriptor的data.version: 23id/role"user"结果:本机 123 份全库判级,修前 74/123 可读,修后 123/123 可读,读写两条路都过(含迁移发布与
verifyCurrentGeneration)。这个绕法不需要动任何编译产物,重启之后官方迁移自己把 v0 变成 v3。
配方本身单独复跑过一次(怕全库那轮有别的因素混进来):把 49 份未改动的原件从备份还原成一个隔离库,
按上面同一条命令跑:修前
total=49 ok=0 fail=49,修后total=49 ok=49 fail=0,改动量统计回到同一组数(R1 81 行 / R2 14 行 / R3 3 行、0 error、0 未处理形状);
再挑两份走
open(id,'write')触发真实迁移发布,回读事件数 113/113 与 728/728 一致。这台机器上 49 份共 104MB,分 6 片跑完约 3.5 分钟(串行会到 8 分钟以上,瓶颈是逐帧解压不是改值)。
已知同源线程(第 ③ 类之外都有人先报了)
source kind:#6101、#6311、#6355、#6151 · descriptor 版本:#5753、#6045、#6545 · 迁移非原子:#6493。
这篇的增量只有两件:第 ③ 类,和"三类一次修完、123/123 回读通过"的完整配方与判据。
需要我把配方拆成评论贴到对应线程下也可以,说一声就行。
09-15 更正(作者后补,两处)
发完这条之后核了两份社区语料,我主帖有两处说得不全:
而 v0→v1 的清单只认 ["preset"] —— 同属"官方自己写过、迁移不认"的形状,比我这条早两天。
descriptor 版本号、插入消息缺 id/role、data 多 origin、未知事件类型带 ignorable、message-edit/version
(本机 49 份拒载 / Session search fails entirely when any session contains a released subagent/descriptor v2 event (two v0→v1 migration gates + no per-session tolerance) #6545 的 34/323 / 另一台的 372 份 v0 工件)。一种形状加一条白名单是加不完的,
所以诉求收敛成一句:把拒载粒度从整条会话降到单行,跳过的行记 warning。这 6 种都能直接进 fixture 当回归用例。
三道闸的位置与行号没变,修复配方没变(instruction-hint 81 行 / descriptor 14 行 / 插入消息 3 行)。
09-15 追加:三道闸的 TS 源码定位,以及最新 tag 的状态
上面三条闸引的是 npm 编译产物行号。@PerryLink 在 #discussioncomment-18443937 把它们映射到 master 的 TS 源码(ref c291e79,仓库默认分支是 master),我逐条取了行,六处全部命中:
packages/session/session-format-v2-to-v3/src/payload.ts:10的SOURCE_KINDS共 15 项,不含instruction-hint;抛点在:110-114,文案就是cannot safely transform unclassified message source。packages/session/session-format-v0-to-v1/src/validation.ts:198-206:data['version'] !== 3时只有version === 0才抛,v1/v2 直接return;而运行时消费者packages/subagent/subagent/src/descriptor.ts:210是if (version !== SUBAGENT_DESCRIPTOR_VERSION) return undefined(常量 =3 在:48)。「迁移比消费者严格」在源码层原样成立。packages/session/session-format-v0-to-v1/src/payload-validation.ts:23-31把inserted交给messageValue(value, label, version, 'user');messageValue在:487要exactRecord(['id','role','content','source'])、:488要 id 非空,而:489-491的role合法值正是从那个实参推出来的 —— 校验器手里就拿着正确答案,却拒整条会话。(raw log: <path>)的包装点:packages/session/session-persistence-jsonl/src/index.ts:748-749。只按一个 commit 查容易把现状看旧,所以又按 blob 哈希比了最新 tag
dsh-v0.1.6-alpha.1(提交 0a15e36,09-15):payload.ts、payload-validation.ts、descriptor.ts、session-persistence-jsonl/src/index.ts四个文件与 c291 逐字节相同;validation.ts两版都 217 行、只差 2 行措辞(把一处 provenance 改成 references),三处判定点仍在:120/:194/:198-202。结论:三道闸在 0.1.6-alpha.1 上原样存在,升到最新 tag 不解决历史会话打不开。另外两处源码事实对做修复直接相关:
validation.ts:194的错误文案自己写着migration refuses unknown historical events even when ignorable,说明「即使 ignorable 也不放过」是明写下来的决定,不是漏检;index.ts:748-751把格式拒载归SessionFormatUnsupportedError、其它解析失败才归SessionPersistenceCorruptionError,这就是两类只读检查工具在同一份语料上结果互不重叠的原因。期望 3(行级降级加 warning)不改一字。现在产物行号、TS 源码行号、最新 tag 状态三样齐了,踩坑的人不必再做一遍考古。
环境行更正(09-19 追加)
自纠一句:本帖开头环境行写的「发帖时 npm
latest就是它(0.1.5-rc.1)」不成立。npm 时间戳:0.1.5-rc.1 发布 2026-09-10T03:12:53Z,0.1.5-rc.2 发布 2026-09-10T14:57:10Z —— 我 09-14 发帖时 latest 已经是 rc.2。
这台机器的内嵌运行时当时也已是 rc.2(239 个包逐个读
package.json),只有 CLI 壳停在 rc.1,而壳升不升行为零变化。三道闸与行号请一律按 0.1.5-rc.2 产物看(
dsh-session-format-v2-to-v3/lib/index.js:14的 15 项SOURCE_KINDS、:125整条记录抛错,都在 rc.2 上复算过,09-18 又对了一遍 master tip 未变)。那句是我没查 npm time 就写下的判断,撤回。
版本口径单独钉一下(免得照 0.1.5-rc.1 去找行号)。
@deepseek-ai/dsh那层只是 CLI 壳,本机装的是 rc.1;跑起来的运行时全是 0.1.5-rc.2 ——本机安装树里 239 个内嵌
@deepseek-ai/*包逐个读package.json实测,dsh-agent-loop、dsh-llm-retry、dsh-llm-pi-ai、dsh-llm-deepseek、dsh-client-ui-chat、dsh-session-format-v2-to-v3、dsh-session-stats、dsh-tool-skill全是 rc.2。壳的 0.1.5-rc.2 在 npm 上 tarball 16,630 字节、解包 48,910 字节(72 个依赖声明),把壳升到 rc.2 我这台行为零变化 ——
所以直接
npm i @deepseek-ai/dsh@latest拿到的运行时就跟这台一致,我前面引的行号也全按 rc.2 产物读。All reactions