# dsh-work-diary [English](README.md) | 中文 DSH Web GUI 的每日工作日记。侧边栏多一个「日记」图标,点开按天汇总当天有活动的会话,生成一份 Markdown 日记;可每天定时生成,也可随时手动生成,用来总结的模型可配置。 一天开十几个会话、跑几十个工具调用,到下班往往想不起今天到底做了什么。这个插件替你把这些会话翻一遍,攒成一篇能看的日记——不读全文,够说明「大致做了什么」就行。默认档一天一次约 8900-11600 token。 ## 做什么 - 按本地时区挑出某一天有活动的会话(默认今天,可切昨天或任意日期)。「有活动」按最近一次真人提问判定,跨天继续干的长会话不会漏。 - 每个会话只取元数据、首条用户诉求、末条助手结论,加上工具调用碰过的文件名,拼成摘要交给模型。末条助手结论答的是「最后干成了什么」,比用户诉求更直接——诉求说的是想做什么;末条只是个半句时改用上一条完整结论,并标上「(进行中)」。 - 模型按固定节输出:今日概览 / 按项目列出今天做了哪些工作;完整档多一节遗留与下一步。 - 面板下方始终列出当天会话,点卡片直接跳到对应会话。 ## 安装 从 npm 安装,或从任何收录了它的插件市场安装: ```sh dsh plugin --profile web add @smwyylc/dsh-work-diary ``` 或把源码目录作为本地 bundle 安装:`lib/index.js` 是 Host 半边,`lib/client.js` 是 Client 半边,`cordis.patch.yml` 声明要挂载的行,`icon.svg` 是侧栏图标,没有构建步骤。 1. 把本目录放到任意位置(和其他插件放在一起即可)。 2. 用 DSH 插件管理器的「安装本地 bundle」把它装入当前 profile。这一步会写好 profile 的依赖项与挂载,不用手改 profile。 3. 重启 DSH,侧边栏出现「日记」图标。 改代码后需要重启才生效:模块在进程启动时加载。 ## 用法 1. 侧边栏点「日记」图标。 2. 顶部选日期(今天 / 昨天 / 日期框),点「生成总结」,正文流式出现。 3. 要改定时或模型:点右上角「设置」,内联展开设置区,改完点「保存设置」。 已有总结的日期,按钮是「重新生成」——每点一次都是一次真实的模型调用。 模型输出撞到长度上限时,正文下方会标出来,那一次的结尾可能是半句话。 ## 预计花费 口径:**输入 = 当天所有会话的 digest + 固定模板**(模板精简档 267 字符、完整档 299 字符)。读会话、扫工具调用里的文件名都是本地磁盘 I/O,不花 token;只有生成那一次调模型。下表以 13 个会话的一天为样本,会话数翻倍时输入大致也翻倍,输出不变。 | 项 | 精简(默认) | 完整 | | --- | --- | --- | | 输入字符(digest + 固定模板) | 约 10-12k | 约 13-17k | | 输入 token(按 1.5 字符/token) | 约 6700-8000 | 约 8700-11300 | | 输出 + 思考 token | 约 2200-3600 | 约 2600-4000 | | 一天合计 token | 约 8900-11600 | 约 11300-15300 | 区间来自两件事叠加:结论额度从 500 提到 1000 字(精简档输入因此从约 4400 token 涨到约 6.7k),以及「末条太短时回填上一条完整结论」(见下节)。回填能拿回多长取决于会话本身——实测同一天里,回填可能只多拿 45 字,也可能拿回 900 字,所以上下界差得开。13 个会话里通常只有一两个触发。 输出那一半由思考主导,而思考在这个网关上关不掉(见「省 token 的做法」)。2026-10-28 实测:精简档在 6 个会话的一天是输入 2920 token + 输出 2710 token,完整档是 4185 + 3067。会话数翻倍时输入大致也翻倍,输出不变。 一天自动跑一次。关掉「每天定时生成」后固定开销为 0,只有点「生成总结」时才发生调用。 ## 省 token 的做法 默认档从取样到输出逐层压,每一层只留「答得出今天做了什么」所需的部分。 **取样**:每个会话送两行正文(首条诉求 + 末条结论),会话头里再点几个文件名。 - 先滤噪声。会话的首条和末条经常不是工作内容,而是「继续」「好的谢谢」这类客套、短追问,或者整段粘进来的代码、表格。原样送进去等于花 token 买噪声: - 整句只由客套词拼成(去掉标点后只剩「好的 / 谢谢 / 继续 / ok / thanks」这类),才算客套; - 短追问要三条同时成立才丢:疑问性开头(「所以 / 那 / 然后 / 是不是」)、疑问语气收尾、不足 20 字。避免误杀「帮我把并发降到 4?」这种真指令; - 整段代码:首词是 `const` / `function` / `select` / `import` 之类并且近处出现语句标点,或者多行带分号花括号。只看首词会把「delete the temp files」这种正常句子误判成代码; - 整段粘贴的日志或命令:长度 60 字以上而中文占比不足 25%。真诉求是中文句子,日志、报错栈、curl 命令几乎纯 ASCII,两边分得很开。这条不能省:粘贴物和前后文的重合度最低,完整档按「最不像」挑主线时第一个就会挑中它; - 标题另有一套判据,只排代码与过长粘贴,短标题(「git 版本」)照常保留——否则摘要头会全退化成项目名,分不出会话。 - 首条诉求 + 末条助手结论。首条声明要干什么;末条助手正文说的是最后做成了什么——「删干净了,远端分支已删除」「没做,只写了方案,等你拍板」「223153 已投进测试 MQ,无失败」。这是「今天做了哪些工作」最直接的答案:诉求说的是**想**做什么,结论说的是**做成了**什么。结论只收 `text` 块,模型内部的 `reasoning` 块(思考过程)不进摘要——「想过」不等于「做过」。 - 完整档另送主线一条、结尾两条。结尾说明用户最后要求了什么;主线补首尾看不到的中段,挑「和首尾最不像、同时够具体」的一条,按长度打折,短岔路赢不过稍长的新话题,话题转折处通常正是这一轮换目标的时刻。整场都在同一件事上打转时退回取最长的一条;40 条诉求以下的会话不花主线的钱。精简档这两行都不要:结尾与结论问的是同一件事、结论答得更准;主线在长会话里最容易被用户**粘回来的助手回复**占住——这类文本和短锚点最不像、又足够长,正好命中主线的判据,而精简档只有一条首条诉求做对照,被它带偏的代价比省下的那 120 字大得多。 - 文件名取最常碰的那个。工具调用里出现过的文件名按出现次数排序,会话头里列第一个(完整档 5 个),多出来的只报 `+N`。文件名是「真改了哪儿」的直接证据,一句「碰过 CertStandardName.asp」比任何一条诉求都具体,而它只占十来个字符。只留 basename,目录丢掉;必须带代码或文档后缀才算,URL 和句子里的词不算。 - 逐条截断:精简档诉求 200 字、完整档 500 字;主线 400 字(仅完整档),结论精简档 1000 字、完整档 1500 字。结论和诉求各占一份额度,几条诉求拼成一段再截会合吃一份额度。结论额度比诉求宽得多:诉求一句话就说完,而结论常常是几百上千字的完整答复,切在半句等于没答。 - 末条太短时往回回填。活跃会话的最后一条助手消息常常只是引子(「消息体只有一个 id 字段。现在看谁调用它」),拿它当「做成了什么」等于作废这条信号。所以末条不足 80 字时改用最后一条够长的结论,并在行尾标「(进行中)」,让模型知道这条是回填的、当时会话还在跑。80 这条线是量出来的:真答复也有短的,实测 97 / 248 / 278 字那三条都是完整结论,都在这条线以上,不会被换掉;短于 80 且没有更长结论可回填时仍然用末条——短也胜于无。 - 会话头压成一行:时间、诉求条数、碰过的文件;精简档不带 cwd 长路径。跨天会话两端都带日期(`10-23 17:46 - 10-26 17:35`),不会显示成看着倒挂的 `17:46-17:35`。 **输出**: - 请求带 `maxTokens` 硬顶(精简 4000 / 完整 6000)。这个额度必须**同时装下思考与正文**:2026-10-28 实测八个模型在 900 额度下全部返回空正文,因为它们的思考长度在 1500-9000 字之间波动,额度在吐出正文前就被吃光。字数约束仍然写在提示词里(精简不超 600 字、完整不超 800 字);4000 额度下实测两个模型分别写出 724 和 741 字,基本服管。 - 思考在这个网关上关不掉。2026-10-28 试过六种写法(`reasoning_effort=none/low/minimal`、`enable_thinking=false`、`thinking.type=disabled`、以及什么都不传):网关全部照收不报错,也一个都不转发,思考最短仍剩约 460 字;而 `reasoning_effort=none` 在真实 digest 上有时反而让思考涨到 6536 字。唯一还能抓的抓手是提示词,现在已明确禁止输出思考、分析与草稿过程——受控实测中这让正文从「空」变成 905 字。所以思考是输出成本里更大的一半,也是总花费降不下来的主因。 ## 配置 设置保存在 `[DSH_HOME]/work-diary/settings.json`(`DSH_HOME` 未设置时为 `~/.dsh`)。字段: | 字段 | 默认 | 说明 | | --- | --- | --- | | `enabled` | `true` | 是否每天定时自动生成 | | `time` | `18:00` | 本地时间 HH:MM,到点自动跑一次 | | `provider` | 空 | 总结模型的 provider;provider 与 model 都为空表示跟随默认模型 | | `model` | 空 | 总结模型的 model | | `language` | `zh` | 输出语言,`zh` 或 `en` | | `verbosity` | `brief` | 详细程度:`brief` 精简(省 token)/ `full` 完整 | | `maxSessions` | `40` | 单次总结最多纳入多少个会话,超出的在面板上提示数量 | 设置面板里可以改开关、时间、provider / model、语言、详细程度;`maxSessions` 需要手改 `settings.json`,改完下次启动生效。 自动执行:到点对当天跑一次(面板状态显示「自动」)。DSH 启动时若当天还没有结果、且当前时间已过设定时间,会立即补跑一次,Host 日志里能看到 `catch-up` 字样。 生成结果按日期保存在 `[DSH_HOME]/work-diary/last-runs.json`,保留最近 30 份。打开面板时该日期已有结果就直接显示,不会重复生成。 ## 数据从哪来 - 会话清单取两路并集:`sessionController.list()` 里 `updatedAt`(最近一次真人提问,取自持久化投影缓存)落在当天的会话,加上最近 7 天创建的会话。投影缓存不是全覆盖,没覆盖的会话 `updatedAt` 会退回创建时间而被漏掉,第二路就是补这个漏;最后用 `readSession` 的真实事件时间终审。第二路是纯追加,猜错只会多读几个会话,不会漏。 - 每个会话只读一次 `sessionQuery.readSession`:一次拿齐 header、标题、计数、全部真人诉求、末条助手结论和工具调用事件。listEvents / readEvent / readSession 每次都是全量加载会话日志,观测缓存只有 5 条 LRU,多读一次就是成倍等待。 - 诉求只认真人输入:`source.kind === 'user'` 的 `user/message` 才算,agent 注入的上下文不算;一条真人输入都没有的会话直接跳过。 - 结论取 `assistant/message` 的 `data.message.content`(注意不是 `data.content`,那是用户消息的路径):块数组里只收 `type === 'text'` 的块,多块用换行拼,整场取最后一条非空的——末条才是「做到哪了」,中间的助手消息是过程。空正文(只有工具调用)不覆盖上一条。同时记下最后一条 ≥ 80 字的结论(`resultFull`),末条不足 80 字时 digest 用它并在行尾加「(进行中)」,两条结论都随 collect 回给面板。 - 标题取 `session/title` 事件(last-wins,与官方 title 投影一致);标题是代码或过长粘贴时退回项目名。 - 生成走 `llm.stream` 流式接口;模型由设置决定,留空则跟随 `agentDefaultModel`。 - 同一天的并发收集会合并成一次;过去日期的结果直接缓存复用,当天的只信 5 分钟。面板先读落盘的总结,再后台收集会话清单。内存里最多保留 8 份运行记录。 - 单个会话读取失败只跳过该会话并记一行日志,不影响整次总结。