Skip to content
dsh.fish
Bundle

dsh-schedule-panel

DSH「日程」面板:左侧栏日历 + schedule 工具,共用同一份数据(宿主 Typert Remote 服务 + 客户端 ctx.remote)

Source
MARIOMLY
License
MIT
Updated
Updated 2 days ago

Readme

# dsh-schedule-panel

给 [DeepSeek Harness](https://github.com/deepseek-ai/deepseek-harness) Web 界面加一个**「日程」面板**:左侧栏日历 + 主面板月/周/概览视图,并注册一个同名的 `schedule` 工具,**面板与模型工具共用同一份数据**。

数据存在 DSH 自己的持久化存储域里(`ctx.storageDomain`,落在 `$DSH_HOME/storages/storage_panel.json`),重启后仍在,不受工具文件沙箱限制。

## 功能

| 功能 | 说明 |
|---|---|
| 月 / 周 / 概览视图 | 每天格子里直接显示日程标题(最多 4 条,超出显示"+N 项") |
| 新建 / 编辑 / 完成 / 删除 | 标题、时间(省略=全天)、分类、备注 |
| 到点提醒 | 每条日程可单独设置提前量(0/5/10/15/30/60/120/1440 分钟);到点弹系统通知,不可用时退化为面板顶部横幅 |
| 提醒总开关 | 工具栏一个手动开关,状态记在浏览器本地;关掉后**不再轮询、不再弹任何提醒** |
| 补课机制 | 窗口最小化/后台时浏览器会降频定时器;恢复后按"上次检查时刻"把关闭期间到点的提醒补发(标题会写「错过的日程提醒」) |
| 收进托盘 | 桌面应用(Chromium `--app` 窗口)里多一个按钮,请常驻托盘宿主把窗口隐藏到系统托盘 |
| `schedule` 工具 | 让我在会话里读写同一份日程:`action=list/add/update/remove/clear`,支持 `remindBefore` |

## 安装

```powershell
# 从本地目录(link 方式,改源码即时生效)
dsh plugin --profile web add D:\path\to\dsh-schedule-panel-ts

# 或从 git
dsh plugin --profile web add https://github.com/MARIOMLY/dsh-schedule-panel.git
```

`dsh plugin` 会用 pnpm 把包装进 profile,并按包内 `dsh.bundle` 声明把本包追加到 `dsh.profile.bundles`,启动时合并 `cordis.patch.yml` 里的 `insert`——所以**重启后自动加载,不需要手改 profile 文件**。

> 仓库里提交了 `lib/`(构建产物),因此从 git 安装无需本地构建。

## 结构

```
lib/index.js           宿主半:普通 ESM。注册 Cordis 服务 schedulePanel(Typert Remote)
                       与模型工具 schedule;落盘走 ctx.storageDomain
lib/client/index.js    浏览器半:ModuleLoader 工厂格式(客户端产物必须是这个格式)
src/client/            浏览器半源码(React + TSX)
src/client/contribution.ts  手写的 Remote 契约(描述符 + codec)
tools/                 构建后包装、离线校验脚本
cordis.patch.yml       本包的 bundle patch(composition insert 一行)
```

## 开发

```powershell
pnpm install
pnpm exec tsc --noEmit
pnpm run build      # tsdown(cjs) + tools/wrap-client.mjs 包成 ModuleLoader 工厂
node verify.mjs     # 离线校验:产物格式/契约/接线/提醒逻辑,失败即 exit 1
```

`verify.mjs` 覆盖的都是实际踩过的坑,例如:

- 客户端产物**必须是** `window.__ModuleLoader__.load` 工厂格式(裸 ESM 会让整批客户端模块中止)
- Remote 描述符每个 codec 必须有非空 `typeSymbol`(少了会报一句毫不相关的 `reading 'length'`)
- 端点名不能撞 Cordis `Service` 原型成员(`remove` 撞过,故改名 `drop`)
- `store` 必须是模块级/globalThis 单例;首次拉取不能依赖多个 effect 的相对顺序
- 到点提醒的时间窗口、边界、每条各自的提前量、补课语义(20 项单测)

## 实现要点

- **宿主半是纯 JS**:Node 24 不支持标准装饰器语法,且官方 codegen 依赖未发布的 `@deepseek-ai/dsh-type-meta`,所以走网关的 **SRC 回退**——它用 `Function.prototype.toString` 读方法**形参名**当 wire 字段,因此宿主形参名必须与 `src/client/contribution.ts` 里的 `wire` 逐字一致(`verify.mjs` 会断言)。
- **客户端拿到的是 `{ ok, value }` 信封**:浏览器侧 Remote 方法返回信封,而直接打宿主 HTTP RPC 返回裸对象。所有调用统一走 `callSnapshot()` 拆信封。
- **通知权限必须由用户手势触发**:所以页头放显式入口,并对 `requestPermission()` 加超时(Chromium `--app` 窗口可能既不弹询问也不返回)。
- **网页无法隐藏原生窗口**:桌面应用的"收进托盘"是客户端写指令文件 → 常驻托盘宿主轮询后执行。

## License

MIT

Install

dsh plugin --profile web add github:MARIOMLY/dsh-schedule-panel

Profile: web

  • 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.
Source