dsh-journal-calendar
A daily journal the agent keeps: the journal_record tool appends dated todos and memories, with optional detail and tags, to one JSON file per day under the DSH home; the right sidebar draws those files as a month calendar marked per day by open todos, finished todos or memories, with checkable todo
Install
dsh plugin --profile web add dsh-journal-calendar
Sandbox test results
| Stage | Result |
|---|---|
| 1 install | ✓ |
| 2 list | ✓ |
| 3 mount | ✓ |
| 4 boot | — |
Four stages run in an isolated sandbox: install (network on) → list → mount → boot (last three with no network).
README
dsh-journal-calendar
[English](README.md) | 中文
给 DeepSeek Harness(DSH)用的每日日志与日历。Agent 把"你做了什么、你打算做什么"记成一天一个 JSON 文件,Web UI 把这些文件画成右侧栏里的日历。
它贡献什么
一行组合包同时挂载三个面:
面 · 是什么
Host 服务 · journal,覆盖 $DSH_HOME/journal 的 Remote 命名空间
工具 · journal_record,以及一条常驻职责提示词段
浏览器半边 · 日历卡片,一个右侧栏标签页
卡片是月历网格 + 每天的标记,下面是所选日期的 TO DO 与 MEMORY 两段。待办带勾选框,勾选直接写回当天文件。
安装
dsh plugin --profile web add dsh-journal-calendar
刷新页面,右侧栏出现 日历 标签页。
它从你配置的 npm 源取已发布的包,里面就是预构建产物,不在你机器上编译,也不需要任何构建授权。dsh plugin 会转发给 pnpm,所以机器上需要 pnpm;要锁版本就写 dsh-journal-calendar@0.3.0。卸载:
dsh plugin --profile web remove dsh-journal-calendar
改为装 Release 附件
每次发版都会把同一份预构建产物挂到 GitHub Release 上,适合你所用的源还没同步该版本的场合:
dsh plugin --profile web add https://github.com/crease123/dsh-journal-calendar/releases/download/v0.3.0/dsh-journal-calendar-0.3.0.tgz
改为从仓库安装
从源码构建同样可行,也是还没有 Release 时的唯一方式:
dsh plugin --profile web add git+https://github.com/crease123/dsh-journal-calendar
这条拉的是源码,会在你机器上现场构建。第一次会停下:pnpm 拒绝执行 git 托管包的构建脚本,除非你显式放行那个确切的包。dsh 会把该填进 profile 的 pnpm-workspace.yaml 里 allowBuilds 的键打印出来,填好再重跑即可。请把这项授权理解为「允许这个包的代码在安装时于你的机器上执行」;若想让装上的代码固定不变,就钉住 commit(…#<sha>)。
三条路径最终拿到的是同一份预构建产物:Release 附件与 npm 源上那个包逐字节相同(同一个 sha1 可证),两者全程不需要任何授权步骤;git+https:// 那条在你机器上编译,走一次构建授权。各自启动无警告,并真实读写日期文件。
记录存在哪
一天一个 JSON 文件,位于 $DSH_HOME/journal/<YYYY-MM-DD>.json(默认 ~/.dsh/journal)。每个文件就是那一天的完整记录:
{
"version": 1,
"date": "2026-09-30",
"entries": [
{ "id": "…", "kind": "todo", "time": "20:00", "title": "跑 2 公里", "done": false }
]
}
想换个位置就给那一行加 dir:
- id: journal
name: dsh-journal-calendar
config:
dir: /absolute/path/to/journal
这些文件是你自己的。 任何能读 JSON 的东西都能读,手改也支持:保留每个条目的 id 和顶层的 "version": 1 即可。
服务把读不懂的文件当成硬错误,而不是重建它——静默重写一天,等于毁掉那天留下的记录。
Agent 拿它做什么
插件注册 journal_record 工具,外加一句提示词,告诉 agent 记录是它的职责之一。工具参数:kind(todo = 打算做的事,可勾选;memory = 已经发生的事)、单行 title,可选 detail、tags、date、time。
同一个 id 记两次返回已存在的那条,而不会追加第二条,所以工具调用重试是安全的。
跳回对话
在对话里记录的条目会同时记下所属 Session 和轮次,因此卡片里的每条 memory 都是一个链接:点一下会切到那次对话,滚到记录这条时所在的那一轮,并短暂高亮。手改 JSON 写进去的、以及这个功能上线前的老条目没有来源,保持纯文本。滚动靠 Web 客户端的 data-chat-turn 属性定位;将来客户端不再输出这个属性时,点击仍然会打开那次对话,只是不再滚动。
改写那句职责提示词
那句话归你写。日历卡片月份标题旁有一个 ✎ 按钮:点开是内联编辑器,预填当前生效的文本,带保存、恢复默认、取消三个操作。文本落在 $DSH_HOME/journal/duty.md(与每日记录同目录,跟随 dir),下一条消息就生效——提示词组装时每次重读这个文件,无需重载。清空后保存即删除文件、恢复内置句子。上限 2000 字符;文本原样插入,其中的 {{ 是你自己的文字,不会被当成变量引用。
开发
pnpm install pnpm run build # tsc(两个面)再 tsdown(两个 bundle) pnpm run test pnpm run typecheck pnpm pack # 先构建,再打出可分发的 tarball
本仓库自带工具根——pnpm-workspace.yaml 与 vitest.config.ts 的存在,是为了在这个包被放进 DSH checkout 内开发时,pnpm 和 Vitest 不会向上走进上一层仓库。
生成出来的 Remote 产物
generated/ 存放 Typert 产物(typert.host.、typert.remote-client.),Host 半边导出它们,浏览器 bundle 内联它们。它们是生成物,不是在这里构建的:
@deepseek-ai/dsh-typert-generator 只在 <root>/packages 下发现贡献包,并且通过"该符号必须声明在一个已注册包里"来识别 TypertRemoteService。因此,从 node_modules 取 @deepseek-ai/dsh-typert-protocol 的包,没法就地分析。
pnpm run regen-typert 自己补上这道缝:它把本仓库的 src/ 按上游实验包名复制进 checkout,在 Host 聚合里注册这个脚手架跑一次生成器,结束后把 checkout 逐字节还原。checkout 里只有 packages/typert/generator 需要先构建(pnpm run build);脚本会拒绝导出缺了任一 Remote 方法名的产物,所以过期副本不会静默通过:
DSH_CHECKOUT=/path/to/deepseek-harness pnpm run regen-typert
checkout 里的 packages/typert/generator 必须先构建过。凡是 @Remote 方法的名称、签名、返回类型变了,或者 Remote 错误码表变了,就重跑一次。scripts/typert-identity.mjs 写明产物需要的身份重写,而 tests/build-artifacts.spec.ts 会在重新生成后仍带着上游包名时让测试套件失败——Typert loader 会在启动时拒绝这种贡献。
重新生成之后要重启 DSH 宿主。 Typert loader 从包的 ./typert 导出登记 Host 半边,并把 import 到的 manifest 按包名缓存在整个进程生命周期内;而热重载这个包只会撤下它的严格定义、不会补登记。结果是磁盘上的产物明明是对的,运行中的卡片却每次调用都报 its strict definition was withdrawn and SRC fallback is forbidden。重启一次,新文件就会被登记。
publint 关于 ./client 的那条警告
npx publint 会报一条警告:./lib/client.js 是 CommonJS,却位于 "type": "module" 的包里,Node 会按 ESM 解读它。
不要改。 浏览器模块表取的是这个文件的字节,再经 window.__ModuleLoader__.load({ id, factory }) 物化;Node 从不解析或导入它,所以「按什么扩展名判定」这件事根本不适用。为了消掉这条警告而改名成 .cjs,会偏离所有 DSH 客户端插件的做法——它们全都是 "type": "module" 配 lib/client.js。
兼容性
通过 peerDependencies 与 engines.dsh 声明。已对 DSH 0.1.7-rc.1 与 0.2.0-rc.2 做过端到端验证:安装、启动、bundle 投递,以及 month / day / setDone 三个端点读写真实日期文件。
在 Node 22.19.0 与 26.4.0 上,同一套验收同样通过,宿主产物也能正常加载。Node 24、25 位于两个已验证版本之间;engines.node 写的是 DSH 自身支持的两条发布线。
每一条 DSH 范围都显式写出各条受支持的发布线,例如 >=0.1.7-rc.1 <0.2.0 || >=0.2.0-rc.1 <0.3.0-0。这不是装饰:node-semver 只有当范围里某个比较符与该版本的 major.minor.patch 元组完全一致、且自身也带预发布标签时,才放行预发布版本。看起来很宽的 >=0.1.7-rc.1 <0.3.0 会静默排除 0.2.0-rc.2——pnpm 只会警告,安装结果两边都不符合预期。tests/manifest.spec.ts 把范围钉在验收实际跑过的版本上。
DSH 会在安装时读这些范围并拒绝不兼容的插件,同时回滚 profile 清单——这一条用一个故意写成不兼容的副本实测过,确实被拒。但 DSH 是以 includePrerelease 求值的,所以一个对你的包管理器来说过宽的范围,在那里照样能过。正是这个不对称,使得范围要靠测试钉死,而不是靠肉眼看。
发布
本仓库同时是 npm 包与社区列表的事实来源。
npm login --registry https://registry.npmjs.org npm publish
prepack 会在打包前构建,所以 npm publish 发的是编译产物,安装方不需要任何构建步骤。publishConfig 同时钉住了 public 访问级别与官方源——机器上若配置了镜像,否则会发到一个别人装不到的地方。用 npm publish --dry-run 可以核对究竟会发出什么。
npm 现在要求发布时具备双因素认证。账号没开 2FA、或 token 没勾 Bypass 2FA,就发不了直发:上传只会落到暂存区(npm stage publish),要靠 npm stage approve <stage-id> 在浏览器里证明本人在场,版本才真正上线。账号开启 2FA 才能让这步批准走通;没开的话它证明不了任何东西,注册表一律拒绝。
要进入 dsh-market 插件市场,向 awesome-dsh-plugin 注册表提一个只加一个文件的 PR——data/plugins/crease123__dsh-journal-calendar.yml:
url: https://github.com/crease123/dsh-journal-calendar name: crease123/dsh-journal-calendar category: memory tarball: https://github.com/crease123/dsh-journal-calendar/releases/download/v0.3.0/dsh-journal-calendar-0.3.0.tgz description: en: 'A daily journal the agent keeps for you. The journal_record tool appends entries to one JSON file per day under the DSH home, carrying the age …(truncated)
Rendered from the README published on npm. Formatted only, not edited.
Versions
Published on npm::3,最后更新 2026-11-02