diff --git a/docs/reference/dsh-compatibility.md b/docs/reference/dsh-compatibility.md index b6fab8a..4d93d45 100644 --- a/docs/reference/dsh-compatibility.md +++ b/docs/reference/dsh-compatibility.md @@ -168,6 +168,10 @@ Git Client 从 primitives 的公开入口复用 `DiffBlock`,不导入官方 `u 候选验证于 2026-10-17 在开发机完成。`DSH_COMPAT_MODE=warn` 下分别以 `0.2.1-alpha.5`、`0.2.1-rc.1`、`0.2.1-rc.2` 三条车道安装,`pnpm compat:check`、`pnpm test`(299)、`pnpm docs:check`、`pnpm build` 与 `pnpm test:artifact` 全部通过;晋级清单写入后在 `0.2.1-rc.2` 安装树上以正式门禁重跑 `pnpm compat:check` 通过,开发 pin 与锁文件恢复 `0.2.0-rc.2`。 +## Session 日志的回读边界(降级不可读) + +宿主持久化读路径拒绝解释包含未知必需事件的日志:`dsh-session-persistence` 对不在 `KNOWN_SESSION_EVENT_TYPES` 且未标 `ignorable` 的事件直接拒绝。`0.2.1-alpha.3` 起写入方新增 `job/registered` / `job/settled` 两个必需事件(后台作业登记,agent 的 bash 调用即产生);因此凡在 ≥ `0.2.1-alpha.3` 宿主上运行过这类作业的会话,回到 `0.2.1-alpha.2` 及更早版本(含 `0.2.0-rc.2`)打开时,历史加载以 `SESSION_QUERY_PERSISTENCE_FAILED` 失败。这是上游刻意的前向兼容防护,与本插件的支持声明无关——插件不读 Session 日志,宿主该行为不受插件影响。症状与解法见 [troubleshooting 条目](../troubleshooting/session-log-newer-harness.md)。 + ## 静态一致性门禁 上游 DSH 包把同级依赖写成 caret,例如 `^0.1.5-rc.2`。这个范围接受同一 core 上更后的 prerelease,pnpm 在 `autoInstallPeers` 下会装成 registry 里当前最高的匹配版本。只钉住根清单时,锁文件仍会混入 `0.1.5-rc.3`、`0.1.7-alpha.1` 或更旧的 `0.1.1-rc.2`。`.pnpmfile.cjs` 在每次安装时把传递依赖、可选依赖和 peer 里的全部 `@deepseek-ai/dsh-*` 改成当前精确版本。目标版本优先取 `DSH_COMPAT_VERSION`,否则取根 devDependency 的唯一 pin。根清单自己的 devDependency pin 和多版本 peer OR 范围保持不变。 diff --git a/docs/troubleshooting/README.en.md b/docs/troubleshooting/README.en.md index d66b1df..5172026 100644 --- a/docs/troubleshooting/README.en.md +++ b/docs/troubleshooting/README.en.md @@ -15,3 +15,4 @@ Holds **recurring problems, or problems whose diagnosis was expensive**: symptom - [`minimum-release-age-dev-pin.en.md`](minimum-release-age-dev-pin.en.md) — Main CI frozen install rejects a development pin younger than pnpm's 24-hour release-age window; replace `minimumReleaseAgeExclude` with that pin only (pnpm honors the first version per package name). - [`windows-desktop-console-flood.md`](windows-desktop-console-flood.md) — Windows desktop terminal-window flood with a blank Git UI: the host's `CreateProcessW` omits `CREATE_NO_WINDOW`, which multiplies focus events, while the client refresh neither coalesces nor lets overlapping rounds land. (Chinese canonical.) - [`worktree-endpoints-linux-ci-timeout.md`](worktree-endpoints-linux-ci-timeout.md) — Intermittent `worktree-endpoints` timeouts on the compatibility matrix's Linux lanes: `dsh-subprocess-local` blocks on a user-systemd probe per spawn with a success-only cache, giving an unbounded tail; the spec now injects a direct `child_process` fake subprocess service. (Chinese canonical.) +- [`session-log-newer-harness.en.md`](session-log-newer-harness.en.md) — Older hosts fail to open sessions written by newer harnesses with "history load failed": `0.2.1-alpha.3` began writing required `job/registered` / `job/settled` events, and earlier persistence readers refuse the whole log on an unknown non-ignorable event — an upstream forward-compat guard, not a plugin defect. diff --git a/docs/troubleshooting/README.md b/docs/troubleshooting/README.md index 0bb4f14..c57c739 100644 --- a/docs/troubleshooting/README.md +++ b/docs/troubleshooting/README.md @@ -15,3 +15,4 @@ - [`minimum-release-age-dev-pin.md`](minimum-release-age-dev-pin.md) —— 主 CI 冻结安装因 pnpm 24 小时发布冷却拒绝未满一天的开发 pin;用当前 pin 整表替换 `minimumReleaseAgeExclude`(同名包只认第一条版本)。 - [`windows-desktop-console-flood.md`](windows-desktop-console-flood.md) —— Windows 桌面端终端窗口风暴与 Git 界面空白:宿主 `CreateProcessW` 缺 `CREATE_NO_WINDOW` 放大焦点事件,客户端刷新无合并且轮次互相判旧。 - [`worktree-endpoints-linux-ci-timeout.md`](worktree-endpoints-linux-ci-timeout.md) —— 兼容矩阵 Linux 车道 `worktree-endpoints` 用例间歇性超时:`dsh-subprocess-local` 每次 spawn 阻塞探测用户 systemd 且只锁正向缓存,尾部无界;该 spec 改为注入直起 `child_process` 的 fake subprocess 服务。 +- [`session-log-newer-harness.md`](session-log-newer-harness.md) —— 旧宿主打开新版写入的会话报「历史加载失败」:`0.2.1-alpha.3` 起写入 `job/registered` / `job/settled` 必需事件,更早 harness 的持久化读路径按未知非 ignorable 事件整段拒读,属上游前向兼容防护而非插件缺陷。 diff --git a/docs/troubleshooting/session-log-newer-harness.en.md b/docs/troubleshooting/session-log-newer-harness.en.md new file mode 100644 index 0000000..e38bfe3 --- /dev/null +++ b/docs/troubleshooting/session-log-newer-harness.en.md @@ -0,0 +1,27 @@ +# Older hosts refuse session logs written by newer harnesses (`job/registered`) + +## Symptom + +- On DSH `0.2.0-rc.2` (or any host older than `0.2.1-alpha.3`), opening an existing session shows "history load failed" with `failed to observe session "...": session "..." contains event type "job/registered" (seq N) unknown to this harness and not marked ignorable; refusing to interpret the log — it was likely written by a newer harness`, tagged `(gateway/internal)`. +- Sessions created on the same host, and older logs without `job/*` events, are unaffected; plugin features (Changes / Graph / Worktrees / commit message generation) are unaffected. + +## Root cause + +Starting with `0.2.1-alpha.3`, the upstream session event vocabulary gained `job/registered` and `job/settled` (written by `dsh-commands`, `dsh-subagent`, `dsh-client-file-upload`, etc. when registering background jobs; an agent bash tool call produces a `kind:"bash"` job event). They are written as **required** events: the envelope carries no `ignorable: true`, because job state participates in session reconstruction and silently dropping it would rebuild a wrong session. + +On the read path, `dsh-session-persistence`'s `validateStoredEvents` checks every event against `KNOWN_SESSION_EVENT_TYPES`; an unknown type without `ignorable` throws `SessionFormatUnsupportedError`. `dsh-session-query.observeSession` wraps it as `SESSION_QUERY_PERSISTENCE_FAILED` and returns it to the client history stream through the gateway. Neither `0.2.0-rc.2` nor `0.2.1-alpha.1` / `alpha.2` knows `job/*`, so the whole log is refused. + +The log format version is not the blocker: both sides use `SESSION_FORMAT_VERSION` 4; the version check passes and the failure happens during event validation. + +Unrelated to this plugin: the plugin never calls `observeSession` / `sessionQuery` and never parses session logs; its `ctx.subprocess` git calls emit no `job/*` events; the `working-directory/change` event it can trigger is a known type on both version lines. The error fires inside the client's history stream when a session is opened — host behavior independent of whether this plugin is installed. The plugin's declared `0.2.0-rc.2` support remains valid. + +## Fix (verified) + +- Open those sessions on a host ≥ `0.2.1-alpha.3` (the version that wrote them); staying on `0.2.0-rc.2` leaves logs containing `job/*` events permanently unreadable — an intentional upstream forward-compatibility guard, not a plugin defect. +- Do not patch logs or intercept this error in the plugin: the read path lives in the host persistence layer, unreachable from the plugin, and treating a required event as `ignorable` would corrupt job-state reconstruction. +- If an upstream fix is wanted, the options are backporting the `job/*` event types onto the `0.2.0` line or having the writer mark them `ignorable` (a semantic-safety call for upstream); both are out of this repository's scope. + +## Related + +- Evidence: `validateStoredEvents` in `dsh-session-persistence@0.2.0-rc.2`; `KNOWN_SESSION_EVENT_TYPES` diffs across `dsh-session` versions showing `job/*` entering at `0.2.1-alpha.3`; a real `session.v4.jsonl.zstd` whose seq 24 is a `job/registered` (`kind:"bash"`) without `ignorable`. +- Compatibility contract: [`../reference/dsh-compatibility.md`](../reference/dsh-compatibility.md). diff --git a/docs/troubleshooting/session-log-newer-harness.md b/docs/troubleshooting/session-log-newer-harness.md new file mode 100644 index 0000000..b3c3b5f --- /dev/null +++ b/docs/troubleshooting/session-log-newer-harness.md @@ -0,0 +1,27 @@ +# 旧宿主拒绝读取新版写入的 Session 日志(`job/registered`) + +## 症状 + +- 在 DSH `0.2.0-rc.2`(或任何早于 `0.2.1-alpha.3` 的宿主)上打开既有会话,历史区域报「历史加载失败」,详情为 `failed to observe session "...": session "..." contains event type "job/registered" (seq N) unknown to this harness and not marked ignorable; refusing to interpret the log — it was likely written by a newer harness`,错误标记 `(gateway/internal)`。 +- 同一宿主上新建的会话、以及不含 `job/*` 事件的旧会话不受影响;插件自身功能(Changes / Graph / Worktrees / 提交信息生成)不受影响。 + +## 根因 + +`0.2.1-alpha.3` 起,上游 Session 事件词表新增 `job/registered` 与 `job/settled`(`dsh-commands`、`dsh-subagent`、`dsh-client-file-upload` 等在登记后台作业时写入;agent 的 bash 工具调用即产生 `kind:"bash"` 的 job 事件)。这些事件按**必需事件**写入:envelope 上没有 `ignorable: true`——作业状态参与会话重建,静默丢弃会重建出错误的会话。 + +读路径上,`dsh-session-persistence` 的 `validateStoredEvents` 逐条检查 `KNOWN_SESSION_EVENT_TYPES`:遇到未知类型且未标 `ignorable` 的事件即抛 `SessionFormatUnsupportedError`;`dsh-session-query.observeSession` 把它包装成 `SESSION_QUERY_PERSISTENCE_FAILED`,经 gateway 返回客户端历史流。`0.2.0-rc.2` 与 `0.2.1-alpha.1` / `alpha.2` 的词表都不含 `job/*`,因此整段日志被拒绝解释。 + +日志格式版本不是原因:两侧 `SESSION_FORMAT_VERSION` 均为 4,版本检查通过后才在事件校验处失败。 + +与本插件无关:插件不调用 `observeSession` / `sessionQuery`,不解析 Session 日志;`ctx.subprocess` 的 git 调用不产生 `job/*` 事件;插件触发的 `working-directory/change` 是两个版本线共有的已知类型。报错发生在客户端打开会话时的历史流加载阶段,属于宿主行为,与是否安装本插件无关;插件对 `0.2.0-rc.2` 的支持声明保持有效。 + +## 解法(已验证) + +- 用写入这些日志的宿主版本(≥ `0.2.1-alpha.3`)打开对应会话;若留在 `0.2.0-rc.2`,含 `job/*` 事件的会话将持续不可读——这是上游刻意的前向兼容防护,不是本插件缺陷。 +- 不要在插件侧修补日志或拦截该错误:读路径在宿主持久化层,插件不可达;把必需事件当作 `ignorable` 跳过会破坏作业状态重建。 +- 若需要上游修复,方向是向 `0.2.0` 线回补 `job/*` 事件类型,或由上游裁决是否将这类事件标记为 `ignorable`;两者都超出本仓库范围。 + +## 关联 + +- 验证依据:`dsh-session-persistence@0.2.0-rc.2` 的 `validateStoredEvents`;`dsh-session` 各版本 `KNOWN_SESSION_EVENT_TYPES` 差分显示 `job/*` 自 `0.2.1-alpha.3` 进入词表;实机日志 `session.v4.jsonl.zstd` 中 seq 24 为未标 `ignorable` 的 `job/registered`(`kind:"bash"`)。 +- 兼容契约:[`../reference/dsh-compatibility.md`](../reference/dsh-compatibility.md)。