Skip to content

Releases

dsh-gui-last-session v0.1.9 — 文档重构

Choose a tag to compare

@itchenshi itchenshi released this 05 Oct 10:14

文档重构版:README 重写为「能做什么 / 安装 / 使用或首次启动 / 配置 / 兼容性 / FAQ / 开发 / 许可」的统一结构,
中英各一份,所有声明都对着代码核过(核不到的旧说法已删除)。功能代码未改动。

同批:CI 在没有 NPM_TOKEN 时跳过发布步骤(此前每次 tag 都白报一次 ENEEDAUTH 失败)。

dsh-gui-last-session v0.1.8 — 审计修复

Choose a tag to compare

@itchenshi itchenshi released this 05 Oct 06:24

审计修复版。详见 GUI 仓库同批次提交(含逐条证据)。

要点:Command Code 半边此前因未定义标识符整条死掉;V4.1 守卫读错层,会给从没配置过该路由的
用户写入 30 条模型列表;会话头补丁在认不出目标主机时失败开放;未设 \ 时把缓存写进
当前工作目录。

dsh-gui-last-session v0.1.7 — 恢复失败会说话;惰性解析 opener;目标迟到会重试

Choose a tag to compare

@itchenshi itchenshi released this 05 Oct 05:05

fix: 恢复会话的失败现在会说话(并把「目标还没进列表」改成会重试)0.1.7

现状:记录侧已经确认工作(指针跟着当前会话走),但启动后仍然落在新会话上。而恢复那条路
一行日志都没有 —— 「没指针」「目标一直没进列表」「这台引擎没有打开会话的 API」「打开后又被
引擎自己导航走了」这几种情况在日志里长得一模一样,所以只能靠猜。这一版先把这件事变成可诊断的,
同时修掉最可能的两个原因。

1. 诊断:宿主侧新增 /gui-last-session/report

浏览器半边每次尝试恢复都会发一行结论给宿主半边,宿主记进应用日志(引擎 stdout 会被外壳合并):

  • reopen: no usable pointer
  • reopen: target <session-id>
  • reopen: open() called for <session-id>
  • reopen: FAILED — the session never appeared in the list (N attempts)
  • reopen: retried successfully for <session-id>
  • reopen: no way to open a session on this engine
  • reopen: giving up (the target never became listable)

为什么不直接打浏览器日志:页面 console 只有在外壳开了 page-debug 标志时才会进应用日志 ——
靠它等于什么都没有。这条路由和已经工作的指针写入走同一套信任围栏。

2. 修:uiWorkspace 改成惰性解析

原来在 apply() 里查一次 ctx.get('uiWorkspace')。但 uiWorkspace 由另一个客户端插件提供,
可能在本插件 fiber 激活时还没挂上 —— 那样 opener 就是 null,插件会直接把自己关掉,而且一关
就是一整个会话。现在改成真正要打开时才解析(并额外兜底 ctx.uiWorkspace)。

3. 修:目标还没进列表时会重试

会话列表是走网络拿的、而且按工作区划分,所以第一次尝试时目标 id 常常根本不在列表里;引擎自己的
启动导航也可能发生在我们之后。现在失败的恢复会在兜底复读里重试(有 5 分钟上限,避免很久以后
把用户硬拽回去),一旦目标出现就打开并记一行 retried successfully。

测试

新增用例:目标不在列表里时不打开、且发出报告;目标随后出现时重试成功、并再次报告。
连同既有用例共 22 项通过。

怎么用它

重启 GUI(会装上 0.1.7)之后,应用日志里会出现 [gui-last-session] reopen: … 这一行 ——
它直接说明恢复卡在哪一步。若显示 open() called for <id> 却仍然是新会话,那就是打开之后被引擎
自己的启动导航盖掉
了,下一步就从那里入手。

dsh-gui-last-session v0.1.6 — 用 uiWorkspace.openSession 打开会话(引擎已移除 sessions.open)

Choose a tag to compare

@itchenshi itchenshi released this 05 Oct 04:01

fix: 找回「打开上次会话」的现代 API —— 引擎 0.2.0 已移除 sessions.open()(0.1.6)

现象:装上插件、记录也正常了(指针已经跟着当前会话走),但启动 DSH 后还是打开一个新会话。

根因(在引擎源码里确认):引擎 0.2.0 把客户端会话服务上的 open(id) 删掉了 ——
该服务的公开方法只有 retain / using / create / fork / scope / refresh / …,全引擎搜索没有任何
地方定义 sessions.open
。而「打开某个会话」在 0.2.0 里的入口是:

ctx.uiWorkspace.openSession(id)        // 侧边栏/日程等 UI 都是这么切会话的

本插件的 record 修好之后,卡住的是 reopen 那一半:它调用的 sessions.open(id) 每次都抛
TypeError,被循环里的 try/catch 吞掉、傻等 30 秒(200 次 × 150ms)后放弃 —— 于是页面留在引
擎自己新建的会话上,表现就是「启动后还是新会话」。

顺带一个平台层发现:引擎自己 dsh-client-ui-conversation 里那段「记住上次会话并恢复」的
代码,读的是 sessions.list.getSnapshot().current(0.2.0 的列表状态里没有这个字段),
调用的也是 sessions.open(...)(已不存在)—— 所以引擎自带的这条恢复路径在 0.2.0 上同样
失效。这一点我改不了,只能修我们的插件,但值得反馈给引擎。

修法:

  • 新增 resolveSessionOpener({ uiWorkspace, sessions }):优先用 uiWorkspace.openSession(id)
    (0.2.0 及以后),退回到旧的 sessions.open(id)(老引擎 / 仍暴露它的宿主);两者都没有时返回
    null;
  • openWhenReady() 改为接受注入的 opener;
  • 没有 opener 时立刻放弃并记一条明确的警告(不再空转 30 秒);
  • 旧文档里「未知会话按契约会大声失败」的重试语义保持不变。

测试:新增 2 条用例 —— ①opener 的选择顺序(workspace API 优先、旧 API 兜底、都没有则为
null);②reopen 使用注入的 opener,且在没有 opener 时不做无意义轮询。共 21 项通过。

怎么确认生效:下次启动 GUI 后应当直接落在最近那次对话上;引擎日志里会先出现
[gui-last-session] pointer -> <id>(记录),页面则会用它打开该会话。

dsh-gui-last-session v0.1.5 — 找回真正的根因:引擎不再提供 current

Choose a tag to compare

@itchenshi itchenshi released this 05 Oct 03:25

fix: 会话指针真正的原因 —— 引擎 0.2.0 的列表状态里根本没有 current(0.1.5)

这次找到了确凿的根因,不再靠推断。

引擎 0.2.0 的客户端会话列表状态是(dsh-api-session-controller/lib/client.js 的
projectList() 最后一行):

this.list.set({ ids, byId, phase, projectionsBySession })

没有 current,而且全文件里 current: 从来没有任何一处写入。

而本插件的记录路径第一行就是:

const current = state?.current
if (!isSessionId(current)) return      // ← current 恒为 undefined,这里永远 return

所以:插件加载正常、路由正常、记录永远不发生,而且一行日志都不打 —— 指针从引擎升到
0.2.0 那天起就没有再动过。上一版(0.1.4)我改的是「行是否被用过」的判断,但代码根本走不到
那里,所以没解决问题。

(顺带:引擎自己 dsh-client-ui-conversation 里有一段几乎一样的「记住上次会话」逻辑,
读的也是 sessions.list.getSnapshot().current,所以这条也是平台层的回归;我只能修我们的插件。)

修法:current 存在时仍优先用它;不存在时,从列表里推导「上次所在的对话」——
取 lastPromptAt(用户最后一次提问)最新的那一个,其次按 updatedAt,并跳过未使用
(blank)的引导会话与 subagent 行。投影元数据从两处读(row.projectionValues 与
projectionsBySession[id].values.sessionListMetadata),因为 projectList() 只在 manager
已附带时才把它复制到行上。

回归测试:新增 3 条用例,其中一条用的就是上面那个真实的 0.2.0 状态形状(没有
current)
—— 这条如果早写,就不会有这次返工。另两条覆盖「元数据只在 state 级投影里」
与「跳过 subagent / 有 current 时优先用它」。共 19 项通过。

可诊断性:宿主半边现在每次指针写入都记一行
[gui-last-session] pointer -> <session-id> —— 这行会进 GUI 自己的日志(外壳会把引擎 stdout
合并进去)。此前整条记录路径完全静默,「没记录」和「记录了没生效」无法区分,这正是它能悄悄坏掉
一周的原因。

dsh-gui-last-session v0.1.4 — 记录路径不再静默卡死

Choose a tag to compare

@itchenshi itchenshi released this 05 Oct 01:45

修:记录路径静默卡死

现象:重启 GUI 后回到的不是最近那次对话。指针文件 <DSH_HOME>/last-session.json
停在 2026-09-29,而这期间用户一直在用多个会话。

排查(有证据):插件装着、启用着;宿主半边在跑(路由注册成功);客户端半边也在跑
(把页面在 Electron 里真打开,能看到它请求 /gui-last-session 并得到 200,启动清单里的条目
与注入声明都正常、模块图已就绪)。也就是说读指针正常、写指针停了。

两处改动:

  1. 记录闸门不再依赖那个「保守为真」的展示位。 引擎 0.2.0 的行字段 blank 是展示语义
    (可复用的空白 New-Session 槽位),客户端在 host 的 sessionListMetadata.blank === false
    到达之前保守保持 true,而该投影只在第一次 turn/start 时翻转 —— 于是「用户明明在用的
    会话」也可能被判成空白、永远不记录。现在改用同一份投影里的 lastPromptAt(第一次提交
    user/message 时写入,不依赖那个位是否对齐):判据是「host 说非空白 或 该会话已有关联的
    用户提问」。真正的空白引导会话两个条件都不满足,依旧跳过(有回归用例守着)。
  2. 加了 15s 兜底复读。 记录此前只挂在会话列表订阅上:通知一旦没送到,指针就永远冻着,而且
    不报任何错。复读在快照未变时立即返回,成本只有一次快照读取。

顺带:指针真的移动时现在会记一行日志([gui-last-session] remembered <id>)—— 之前这条路径
完全静默,「没记录」和「记录了但没生效」在支持场景里无法区分。

测试:新增 2 条用例(① host 说已提问时即使展示位仍空白也要记录;② 订阅完全没通知时复读也能
记录),连同既有用例 16 项通过。

环境要求

DSH >= 0.2.0-rc.2、Node >= 20、零运行时依赖。

升级

dsh plugin --profile web add github:itchenshi/dsh-gui-last-session

或本 Release 的 tarball(dsh-gui-last-session-0.1.4.tgz)。用 DSH Ready GUI 的话,随包版本已抬高
到 0.1.4,下次启动 GUI 会自动换过来,之后指针就会跟着你当前所在的会话走。

dsh-gui-last-session v0.1.3 — DSH 兼容性声明(范围 + 在跑的引擎版本)

Choose a tag to compare

@itchenshi itchenshi released this 04 Oct 11:13

只改清单元数据:把 DSH 兼容性声明补成「范围 + 在跑的引擎版本」

DSH STORE 的固定 commit 自动检查对同类插件报过一条
「DSH compatibility is not explicitly declared」。查下来原因是清单里只写了:

"dshReleases": { "0.1.5-rc.2": "compatible" }

而本插件实际跑的引擎是 0.2.0-rc.2 —— 也就是说,清单里没有任何一条声明对应
「正在被审查的那个引擎版本」。

现在改为:

"compatibility": {
  "node": ">=20",
  "dsh": ">=0.1.5-rc.2",
  "dshReleases": { "0.1.5-rc.2": "compatible", "0.2.0-rc.2": "compatible" }
}
  • dsh 是范围(商城建议的写法):>=0.1.5-rc.2 语义上覆盖 0.2.0-rc.2 及之后;
  • dshReleases 是逐版本证据,两端都有实测:0.1.5-rc.2 是此前长期在跑的引擎,
    0.2.0-rc.2 是现在在跑的(verify:builtin 第 4 幕就是在它上面启动引擎、四个插件
    都在 bundle 列表里、ALL CHECKS PASSED)。不是猜的。

运行时代码一行没动,行为与前一版完全一致。版本号照常抬高,是为了让「声明的兼容范围」
能对应到一个确定的版本 —— 同版本改内容会让这个声明无从归属。

本插件的能力边界(便于静态审查)

  • 运行时依赖:无(dependencies 为空)。
  • 凭据 / 命令 / 原生制品 / 生命周期脚本:无。
  • 详见 README 的「权限与边界」一节。

安装

dsh plugin --profile web add github:itchenshi/dsh-gui-last-session

或本 Release 的 tarball(dsh-gui-last-session-0.1.3.tgz)。

dsh-gui-last-session v0.1.2

Choose a tag to compare

@itchenshi itchenshi released this 23 Sep 02:33

Reopens the conversation you were last in after restarting DeepSeek Harness.

  • Keeps a pointer file under the DSH home directory instead of patching engine files
  • Guards against the engine's own startup navigation creating an empty session, which would otherwise overwrite the pointer
  • Enabled by default

Install: dsh plugin --profile web add github:itchenshi/dsh-gui-last-session