Bundle
dsh-wait-minute
DSH 延迟发送消息:设置小时和分钟,持久化定时投递到当前会话
- Source
- TZDXF
- License
- MIT
- Updated
- Updated 7 days ago
Readme
# Wait Minute — 会话延迟消息队列(1.0.2)
这是会话内的延迟发送功能,不是单次自动化任务。消息保存到插件自己的持久化队列,到期后通过 DSH 普通会话发送接口提交原始文本。
**不调用 Schedule,不创建自动化任务,不发送 `[SCHEDULE REMINDER]` 包装。** 开发阶段不读取、不迁移、不清理旧版 Schedule 数据。
## 发送与队列操作
1. 在原聊天输入框输入消息。
2. 点击发送按钮旁的时钟图标,分别设置小时和分钟,例如 `00:05`、`01:30`。
3. 点击原发送按钮或在消息编辑器按 Enter。保存成功后清空草稿,消息进入当前会话的延迟队列并显示倒计时。
4. 队列中每条消息提供:
- **编辑**:修改消息文本,默认保留原发送时间;勾选“重新设置延迟”后,可修改小时/分钟,延迟从本次保存时刻重新计算。保存失败保留编辑草稿。
- **立即发送**:不等待倒计时,立即把原文提交到当前会话的普通消息发送流程。成功确认接收后移出延迟队列。若 AI 正忙,遵循 DSH 普通排队发送,不强行停止或插话。
- **取消发送**:删除尚未发送的队列消息,不撤回已经提交到会话的消息。
消息到期、编辑、取消和立即发送共享串行操作队列,避免相互竞争产生重复提交。编辑期间若消息已经到期提交,界面保留编辑草稿,但不能把已提交消息改回未发送状态。
## 队列外观与侧栏状态
- 外观对齐原生对话等待队列:贴合输入框的菜单条、紧凑消息预览与图标操作;单条直接展示,多条默认折叠为数量标题,编辑时展开。
- 空闲会话行显示时钟及延迟消息数量;悬浮详情显示最近计划时间、正在提交或结果待确认的数量。不把会话伪装成 AI 正在执行。
- 原生运行中、待审批及未读完成标识优先保留;这些行的延迟信息仍在悬浮详情中,不覆盖原生状态或 Stop 行为。
- 修改队列后立即读取新状态,其余会话每 5 秒刷新;最后一条消息发送或取消后清除延迟状态。刷新失败保留上次状态。
## 时间输入
- 一个视觉外框,内部为独立小时/分钟数字段,交互参考 Reka UI,不引入 Vue/Reka 依赖。
- 小时 `0–8760`,分钟 `0–59`,只接受数字;无效或越界输入不会写入。
- 离开字段后自动补零:`1:5` → `01:05`。
- 鼠标滚轮、`↑/↓` 调整当前段,到边界停止;左右方向键切换段。
- 支持粘贴短格式时间及全角冒号并自动规范化。
- 时长最短为 1 分钟,`00:00` 不可发送,不会退化成立即发送。
## 新会话首条消息
先按 DSH 原流程选择工作区,使新会话输入框可编辑。第一条消息即可直接延迟发送,无需先发送即时消息。
入队后立即发布摘要标题(`延迟 · 消息摘要`),保存会话创建记录并同步原生 engagement 状态;同时后台发起一次专用的 `session-title` 模型请求,生成正式标题。该辅助请求只生成标题,不提交普通聊天消息、不执行问题、不伪造轮次,也不阻塞消息入队。优先沿用会话已有请求的模型配置,否则使用当前默认模型;失败或超过 6 秒保留摘要标题。
已有标题不覆盖;生成期间手动改名会优先保留用户标题。该会话不会继续作为“新会话”占位被复用;取消消息、刷新或重连后仍保持已创建状态。
## 安装
适用于已检查的 **DSH 0.2.0-rc.2**,**不再依赖 Schedule bundle**。同时支持两种运行形态,二者插件完全一致,只是配置互相独立:
- **桌面应用**:配置名 `desktop`,目录 `%USERPROFILE%\.dsh\profiles\desktop`。
- **浏览器版**(`dsh web`):配置名 `web`,目录 `%USERPROFILE%\.dsh\profiles\web`。
插件服务端只用 `webServer` 与 `connection`,没有桌面专属依赖;客户端依赖 `dsh-client-ui-conversation`、`dsh-client-ui-renderer`、`dsh-client-ui-session`,三者都由 `@deepseek-ai/dsh-web-app` 提供。因此**装到哪个配置就在哪个形态生效**:只装 `desktop` 时 `dsh web` 里不会出现延迟功能,反之亦然。
### 1.0.0:实际运行实现校验
开发目录中的 Host ESM 模块可能被运行时缓存,停用/启用并不保证重新导入修改后的源码,插件页显示的版本号也不代表正在执行的代码。更新请直接重新安装本次 tgz,而不是仅切换开关。
独立队列改用 `/api/wait-minute/outbox-v1`,不回退到旧接口。响应包含 `X-Wait-Minute-Engine: standalone-outbox-v1` 和运行版本标识。客户端在任何修改前先做只读校验;旧实现、缺失标识或错误路径将被阻止,不继续创建消息或自动化任务。实际验证必须检查运行接口标识,不能仅查看源码或 bundle 版本。
在 DSH 插件安装入口选择最新 Release 的附件 `dsh-wait-minute-<版本>.tgz`(当前为 `dsh-wait-minute-1.0.2.tgz`),或输入项目绝对目录(例如 `C:\code\wait-minute`)。如果安装工具返回 `restart-required`,必须完全退出并重新打开 DSH,不能只刷新网页。安装包包含客户端构建产物,无需第三方安装脚本。
启动成功后,`GET /.well-known/wait-minute` 返回只读的引擎、运行版本和 `ready: true`,不包含消息、会话、令牌或其他私有信息。该端点在两个存储单元成功打开后才注册,可用于真实启动核验。私有 `/api` 请求会先被统一身份认证拦截,未经认证的 401 不能证明某条路由存在或后端版本已更新。
### 通过 dsh 命令安装
`dsh plugin --profile <配置名> <参数...>` 会把参数原样转发给该配置目录里的 pnpm,因此 `add`、`remove`、`install`、`why` 都可用。把下面命令里的 `desktop` 换成 `web`,就是给浏览器版安装。
```powershell
# 首次安装或升级到新版本:按标签定位附件
dsh plugin --profile desktop add https://github.com/TZDXF/dsh-wait-minute/releases/download/v1.0.2/dsh-wait-minute-1.0.2.tgz
dsh plugin --profile web add https://github.com/TZDXF/dsh-wait-minute/releases/download/v1.0.2/dsh-wait-minute-1.0.2.tgz
# 重复安装或回退同一版本:先下载附件,再按本地文件安装(可反复执行)
Invoke-WebRequest -Uri https://github.com/TZDXF/dsh-wait-minute/releases/download/v1.0.2/dsh-wait-minute-1.0.2.tgz -OutFile "$env:LOCALAPPDATA\dsh-wait-minute\dsh-wait-minute-1.0.2.tgz"
dsh plugin --profile desktop add "file:$env:LOCALAPPDATA\dsh-wait-minute\dsh-wait-minute-1.0.2.tgz"
# 开发调试:直接从项目目录安装(link)
dsh plugin --profile desktop add C:\code\wait-minute
# 查看当前安装来源与版本,以及卸载
dsh plugin --profile desktop why dsh-wait-minute
dsh plugin --profile desktop remove dsh-wait-minute
```
安装或更新后必须完全退出并重新打开:桌面应用要完全退出后重开,`dsh web` 要停掉原进程重新启动。插件页显示的版本号不代表运行时已重新导入新代码。已经装好的配置只需 `dsh plugin --profile <配置名> install` 核对,正常输出 `Already up to date`。
**地址必须带版本标签,不要写成 `releases/latest/download/...`。** 附件文件名含版本号,`latest` 只在「文件名里的版本恰好就是当前最新版」时可用:本仓库现在 `latest/download/dsh-wait-minute-1.0.2.tgz` 返回 200,而 `latest/download/dsh-wait-minute-1.0.1.tgz` 直接 `ERR_PNPM_FETCH_404`。一旦发布 v1.0.3,任何写死 `latest/...1.0.2.tgz` 的命令都会失效;而没有版本号的固定地址又永远命中同一 specifier,`add`/`update` 都只回 `Already up to date`,无法升级。按标签取地址则两者都成立。
> **已知问题:缓存的附件地址无法被重新解析。** pnpm 11.7.0 在该地址已进入本地 store 缓存时(输出里的 `reused 1, downloaded 0`)会产出不带 `integrity` 的解析结果,随后报 `ERR_PNPM_MISSING_TARBALL_INTEGRITY`;`--force`、`store prune` 都无效(`pnpm store prune` 保留仍被配置引用的条目)。触发条件是**当前配置需要全新解析一个已缓存的地址**:全新配置装同一版本、或 `remove` 后再 `add` 都会失败,而已装好的配置重复执行 `add`/`install` 会先命中 `Already up to date`、不受影响。升级到新版本是全新地址,必然真正下载,因此始终正常。受影响的场景请用上面的本地附件方式。
## 持久化及失败行为
- 消息位于 Storage kv 独立单元 `wait_minute_outbox`,会话创建记录位于 `wait_minute_created_sessions`,不出现在自动化任务管理中。
- DSH 的单元/表名称必须匹配 `^[a-z][a-z0-9_]*$`。插件采用合法的单元名称,并显式等待 `storage.backend.json` 注册就绪。
- 关闭浏览器页面不影响已保存消息。DSH 主程序须运行;退出、关机或休眠期间不能准时发送,恢复后处理到期消息。
- 只支持普通文本,不支持附件或斜杠命令。延迟模式下会拦截并提示,而不是悄悄即时发送。
- 持久化成功后才显示创建成功;失败保留原草稿,请先刷新确认再重试。
- “立即发送成功”表示会话确认接收,不表示 AI 已完成执行。
- 发送前先保存发送意图,发送成功保留已发送标记,重复的立即发送请求不会再次提交。
- 崩溃或发送失败导致接收结果不明确时,不自动重发,队列显示错误。请先检查会话记录;手动立即重试会弹出可能重复发送的确认。
- 发送前存储故障时不发送,不做零延迟自动重试,提示修复后手动重试。
- 消息提交与已发送状态写入不是同一个原子事务,极端崩溃窗口不保证 exactly-once;已有不明确结果的消息不会自动重发。
- 禁用本插件会停止独立队列的计时发送;重新启用后恢复。卸载前建议取消未发送消息。
## 实现与验证
- `src/outbox.js`:独立持久化消息队列、到期计时、编辑/取消/立即发送及失败恢复。
- `src/index.js`:通过 DSH Connection 鉴权保护的 JSON API。
- [created-sessions.js](src/created-sessions.js)、[client-sessions.js](src/client-sessions.js):新会话创建状态、即时标题和异步改名保护。
- [title-generator.js](src/title-generator.js):专用辅助模型标题请求,不执行聊天问题。
- [client-sidebar.js](src/client-sidebar.js):会话延迟状态、数量及悬浮详情。
- [client-core.js](src/client-core.js)、[client.js](src/client.js):输入控件、原生等待队列式展示和内联编辑。
- `scripts/build.mjs`:无第三方构建依赖的客户端打包器。
使用 DSH 普通 `sessionController.prompt`,以 `mode: queue` 发送 `{type: text, text: 原文}`。当前运行时缺少公开的 pre-submit/delayed-engagement 接口,因此 composer 拦截和客户端 engagement 桥接按已检查版本固定;不修改 DSH 安装文件,其他版本需重新验证。
```powershell
node scripts/build.mjs
node --test test/*.test.mjs
```
126 项自动测试覆盖独立队列不访问 Schedule、原文发送、到期/立即发送竞争去重、编辑时刻、取消、持久化故障、重启恢复、会话隔离、内联编辑、分段时间、原生队列式折叠与行操作、侧栏状态汇总及过期响应保护、即时标题与后台生成/超时/改名保护。测试使用模拟存储、时钟、消息接收接口和 UI 事件,不等同于真实浏览器点击与实际模型执行的完整端到端测试。
## 发布与自动化
- [ci.yml](.github/workflows/ci.yml):分支推送与 PR 上构建客户端 bundle 并运行测试(Node 22 与 24)。
- [release.yml](.github/workflows/release.yml):推送 `v*` 版本标签时校验标签与 `package.json` 版本一致,再构建、测试、打包 `dsh-wait-minute-<版本>.tgz`,上传构建产物并创建 GitHub Release。
- [package.yml](.github/workflows/package.yml):手动触发,只打包并上传 tgz,用于打标签前验证安装包。
- Release 附件即安装包;发布说明取自标签对应提交的 [RELEASENOTES.md](RELEASENOTES.md),自动生成的提交列表追加在其后。
标签与 `package.json` 的 `version` 必须一致(`v1.0.2` ↔ `1.0.2`),标签提交还必须位于 `master` 历史中;否则发布在构建前失败,且不产生 Release。接口回显的运行版本号直接读取 `package.json`,因此发布时只需修改包版本。完整步骤、版本号规则与失败处理见 [.github/RELEASE.md](.github/RELEASE.md)。
## 许可证
本项目使用 MIT 许可证,完整条款见 [LICENSE](LICENSE)。
Install
dsh plugin --profile web add github:TZDXF/dsh-wait-minute
Profile: web
With the hub plugin installed, ask your agent to install it by name — it resolves the same plan shown here.
dsh plugin --profile web add github:stvlynn/dsh.fish#path:packages/dsh-plugin-hub
install dsh-wait-minute from the hub
- This package builds from source on install. pnpm will ask you to allow its build script — that is permission to run the package’s code on your machine, outside the agent sandbox. Only allow sources you trust.
- This source has no pinned commit, so a later push upstream changes what installs. Prefer pinning a commit.