# dsh-calendar 面向 [DeepSeek Harness](https://github.com/deepseek-ai/deepseek-harness) 的会话交互日历插件:一个根作用域的日历界面,展示整个 harness 中所有 session 的交互事件。侧边栏底部入口打开全视口月视图;每个日期格带当天活跃 session 数的徽标,选中某天后按 session 列出标题、当天时长和事件数,并可展开查看事件及原始载荷。实时 append 的事件会即时流入已打开的日历。 [English](README.md) | 中文 ## 特性 - **全 harness 视图** —— 一个日历覆盖所有 session(用户消息、助手回复、工具调用、回合边界),而不仅是当前会话。 - **实时** —— host 端 SSE 通道在事件 append 的瞬间推送到已打开的日历。 - **历史回填** —— 启动时灌入已存活的 sessions,并通过 `sessionPersistence` 尽力读取持久化日志。 - **可扩展** —— 未知(插件合并的)事件类型回退到类型名;无需维护按类型的注册表。 - **内存有界** —— 滚动 90 天窗口,所有 session 合计每天上限 2000 条事件。 ## 安装 > 插件面向 DeepSeek Harness 的 **web** 界面(`dsh web`)。 用 `dsh plugin`(会转发给 profile 目录内的 pnpm)把插件装进 profile。本包声明了 `dsh.bundle`,因此 `dsh` 会自动把它追加到 profile 的 `dsh.profile.bundles` 列表: ```bash dsh plugin --profile web add @necokeine/dsh-calendar ``` 重启 `dsh web` 并刷新页面——侧边栏底部会出现**日历**入口。该 bundle 会插入一个 具有唯一 id 的 Loader 行;当前 `@deepseek-ai/dsh-web-app` 组合中没有需要禁用或替换 的内置日历行。 用下面的命令移除: ```bash dsh plugin --profile web remove @necokeine/dsh-calendar ``` 其他安装方式同样可用: - **本地源码** —— `dsh plugin --profile web add ./path/to/dsh-calendar`(或 `link:./path/to/dsh-calendar`)。 - **打包 tarball** —— 先 `pnpm pack`,再 `dsh plugin --profile web add ./necokeine-dsh-calendar-0.1.1.tgz`;已预构建 `lib/`,无需构建权限。 - **Git 安装** —— `dsh plugin --profile web add github:necokeine/dsh-calendar#`;这会拉取源码(无 `lib/`),pnpm ≥10 会阻止构建,需先在 profile 的 `pnpm-workspace.yaml` 的 `allowBuilds` 中允许该包,且源码 checkout 需自行产出 `lib/`。 ## 开发 ```bash pnpm install pnpm test # vitest:node half + browser half 数据层 + 组件测试 pnpm build # tsc -b && tsdown → lib/(node half + browser client bundle) pnpm lint # oxlint(src + tests) pnpm pack # 通过 prepack 构建并生成可由 dsh plugin 安装的 tarball ``` 独立测试为 Harness `client-store` 平台模块使用一个很小的 `createSnapshotStore` 契约 stub。测试仍会在契约形状的 context 上执行真实的日历 `apply`,覆盖 locale/slot 注册和 controller 清理;Host 聚合、路由语义、请求竞态、 SSE 折叠、组件行为以及 package manifest/patch 契约分别有独立测试。 本包是像 harness 自带的 `dsh-client-hmr` 那样的双面包插件: - **Node half**(`src/index.ts`)—— 注入 `sessions` + `webServer` 的 host 插件。订阅 `session/event` 事件流,把每个事件总结为一条简短 wire 记录 `{ sessionId, seq, time, type, summary }`(由 host 生成、中文产品文案),用 `foldSessionTitle` 从 `session/title` 事件折叠每个 session 的标题,并按 session×天聚合——每天分桶列出每个活跃 session 的 `CalendarSessionWire`(标题、当天时间跨度、事件数,以及展开视图用的原始事件列表)。注册两条路由: - `GET /calendar/events?from=YYYY-MM-DD&to=YYYY-MM-DD` —— 指定本地日期范围内按 session 分桶的 JSON。 - `GET /calendar/stream` —— SSE 通道:连接时先发 backlog 帧,之后每次实时 append 发一条 `event` 帧(浏览器把它们折叠进对应 session 行)。 - **Browser half**(`src/client/index.ts`)—— 注册 `sidebar.footer.action` 条目和 `calendar` locale 命名空间。私有的 `CalendarController` 拥有视图 store(绑定为 `useCalendar`)、按月的 `/calendar/events` 请求,以及 `/calendar/stream` EventSource 生命周期;组件只接收可观察 hook 与普通 action 回调。月视图显示每天活跃 session 数;日详情区按 session 列出(标题 + 时长 + 事件数),点击展开原始事件列表——展开状态是组件本地状态。 ### 仓库结构 ``` src/ index.ts # node half:聚合器、索引、路由、SSE events.ts # 共享 wire 协议(两个 half) invariant.ts # 包的不变量伴侣 client/ index.ts # browser half 入口(footer action 注册) calendar-controller.ts # store + wire 调用 + SSE 生命周期 CalendarAction.tsx # footer 触发器 + 月视图 + 日详情 calendar-action.module.css locales.ts # 中英文词典 tests/ # vitest 测试(node half、controller、组件) cordis.patch.yml # bundle patch:以唯一 Loader 行 id 插入本包 tsdown.config.ts # 独立构建(CSS modules 内联 + loader banner) ``` ## 模型体验 本插件不添加工具、prompt 段、schema 或模型可见消息。它只被动索引持久化 session 事件并把结果提供给 Web 日历界面,因此安装后不会改变模型输入或 KV cache 行为。 ## 许可证 MIT