Перейти к основному содержимому
I

dsh-gui-last-session

itchenshi/dsh-gui-last-session

Reopens the conversation you were last in after restarting DeepSeek Harness, by keeping a pointer file under the DSH home directory instead of patching engine files.

Установка

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

README

dsh-gui-last-session

English 中文

重启 DeepSeek Harness(DSH)之后,自动回到你 上一次待着的那个会话 —— 不用再一层层翻历史。

装上就能用

  • 用 DSH Ready GUI(推荐):插件随 GUI 内置,而且是默认开启的 —— 装好 GUI 就已经在用了。 要关掉就在设置窗口 → 第三方插件里取消勾选。

  • 其它 DSH 宿主dsh web / CLI):

    git clone https://github.com/itchenshi/dsh-gui-last-session.git
    dsh plugin --profile web add file:<clone 出来的绝对路径>
    

    装到的是磁盘上的真实目录,所以以后 git pull 更新的就是同一份代码;反过来,目录被移动或删掉 会让这条依赖失效(重新 add 一次即可)。装完重启引擎(插件是按目录装的,引擎在启动时组装)。

npm 上暂时没有这个包:注册账号那一环走不通(www.npmjs.com 返回 Cloudflare 托管挑战),包发不 出去。所以现在只能按上面两种方式装。等注册通了会照常发布。

它替你解决什么

原来的实现直接改引擎文件:重写 node_modules/@deepseek-ai/dsh-client-ui-conversation/lib/client.js, 靠匹配引擎源码文本找插入点,并用一个精确的引擎版本号做门禁。于是:

失效方式原因
版本门禁引擎版本不是它编写时针对的那一个,补丁就拒绝应用
锚点漂移插入点靠匹配字面源码行找到,重新构建 / 改名 / 重新格式化都会让它失效
静默降级失败时只记一行日志,功能就这么没了,界面上没有任何错误
重装即丢失重装引擎会把补丁整个抹掉

现在它是一个插件:住在自己的包里,通过引擎公开的服务契约(ctx.sessions)对话,所以引擎更新 不再会删掉这个功能;契约真的变了,引擎会大声报错,而不是悄悄关掉它。

行为细节

有两处很关键,都有测试覆盖:

  1. 引擎启动时的空白会话永远不会被记录。 页面加载时引擎自己会导航到一个工作区,并可能创建/选中 一个会话;要是记下它,下次启动就会去续接一个空对话。两道防线:

    • 记录在「续接尝试落定(或 4 秒兜底定时器触发)」之前一直未启用,所以引擎启动导航发生时 插件还没开始监听;
    • 即便已启用,标记 blank: true 的行也永远不记。
  2. 续接会等目标变成可寻址的。 会话列表是页面挂载之后才从网络到的;对未知 id 调 open() 会失败, 所以插件每 150ms 轮询一次列表快照(上限约 30 秒),等那一行出现再选中它。

尽力而为:如果记住的会话已被删除,插件安静放弃,把引擎自己的启动行为留在原地。

配置(cordis.patch.yml 的行 config,全部可选)

- insert:
    - id: gui-last-session
      name: dsh-gui-last-session
      config:
        enabled: true   # 总开关
        quiet: false    # true = 激活时不记任何日志

想在不改包的前提下覆盖某个 profile 的配置,就在该 profile 自己的 cordis.patch.yml 里加一条 id 相同的行(它会整体替换 config,所以每个键都要写全)。

权限与边界(给会静态扫描的商城看的)

  • 运行依赖:无。 只用 Node 内建模块(node:fs/promisesnode:cryptonode:pathnode:os)。
  • 文件:恰好一个。 宿主半边在 <DSH_HOME>/last-session.json 维护一份很小的指针文档,原子写入 (唯一临时名 + rename),所以崩溃不会留下半个文件。它只记一个 session id,别的什么都不记。
  • 本机路由:一条。 宿主注册唯一一条面向页面的路由,页面半边用它读写指针;它走引擎的信任围栏 (Host 白名单 + 浏览器会话 cookie),围栏拿不到时失败即关闭
  • 对外网络:没有。 唯一的 fetch 是发往上面那条本地路由的同源请求。
  • 凭据 / 命令 / 原生制品 / 生命周期脚本:无。
  • 失败边界: 指针缺失、读不出来或损坏,都按「没有可续接的东西」处理 —— 记一条日志,启动照常落在 普通界面上。绝不阻断引擎启动;卸载即恢复原生行为,除了那一个文件之外不留残留状态。

HTTP 接口

GET  /gui-last-session   -> { sessionId: string | null, updatedAt?: number }
POST /gui-last-session   -> { ok: true, sessionId, updatedAt }
     body: { "sessionId": "session-..." }

两个方向都用 /^session-[A-Za-z0-9_-]{4,200}$/ 校验 id;POST 带了别的内容会被 400 拒绝,文件保持 原样。这条路由受信任围栏保护,所以裸 curl 会得到 401 —— 要手工调用,先用 dsh web 打印的 URL 打开一次拿到会话 cookie,再带上:

curl -X POST http://127.0.0.1:<port>/gui-last-session \
     -H 'content-type: application/json' \
     -H 'cookie: <引擎会话 cookie>' \
     -d '{"sessionId":"session-..."}'

两部分构成

部分文件运行在职责
宿主lib/index.jsNode把指针原子写进 $DSH_HOME/last-session.json,并提供一条很小的 JSON 路由
页面client/client.js浏览器记录当前会话;加载时重新打开已存下的那一个

页面半边是手写的、无依赖的 bundle,没有构建步骤也没有打包器。引擎的客户端模块系统不接受普通 ESM:bundle 必须注册一个惰性的 CJS 工厂(window.__ModuleLoader__.load({ id, factory })id 必须 等于包名),所有副作用都放在工厂闭包里、物化时才运行。两套 inject 别搞混package.jsondsh.client.inject 写的是包名(决定浏览器模块图的加载顺序),bundle 工厂导出的 inject 写的是 服务名(如 ['sessions'])。两者都必需 —— 漏掉导出的 inject,页面一加载 apply() 里取 ctx.sessions 就会抛 cannot get property "sessions" without inject

tests/test.mjs 正是按这个契约加载 bundle 的,所以「退回普通 ESM」或「漏了导出的 inject」都会让测试 失败,而不是留到浏览器里才炸。

开发

node --check lib/index.js
npm test        # 本地行为测试(不联网、不需要引擎)

兼容性:插件依赖的是公开服务契约,不是引擎版本号,所以有意不做版本门禁(版本区间在这里 也不合适:npm semver 会把预发布版排除在 >=0.1.2-0 <0.2.0 这类区间之外,反而误杀 0.1.5-rc.1)。 已在 dsh 0.1.5-rc.1 上端到端验证:装上后引擎能启动、GET /gui-last-session 返回已存下的指针、 dsh-gui-last-session/client.js 随启动载荷下发(HTTP 200)。

本包是零依赖的普通 JavaScript。

许可

MIT

Похожие плагины