dsh-session-task-tags
Tag every DeepSeek Harness session as a long-term or short-term task plus its agent mode, and auto-archive it when its window elapses.
- Stars
- 0
- Language
- JavaScript
- Created
- Oct 7, 2026
- Updated
- Oct 7, 2026
Introduction
dsh-session-task-tags
English · 中文
Tag every session in DeepSeek Harness as a long-term or short-term task, record the agent mode it was created with, and auto-archive it when its window elapses.
- Short-term tasks auto-archive after 1 / 5 / 7 / 15 / 30 days (default 7).
- Long-term tasks are manual by default; they can optionally auto-archive after 15–85 days. Manual records are never touched by the sweep.
- The selector sits after the agent-preset chip: in the session header once a conversation exists, and in the Hero chip row on a blank session.
- Every sidebar row carries a 50 %-translucent long/short mark, and the chip itself shows the
set window and the live countdown (
短期任务 7天 · 剩6天).
dsh plugin --profile web add dsh-session-task-tags
DSH 会话任务标签插件。给每个会话加上 长期任务 / 短期任务 标签与 Agent 模式 记录, 并可按窗口自动归档:短期任务默认 7 天(可选 1 / 5 / 7 / 15 / 30 天), 长期任务默认手动归档(可选 15 / 25 / 35 / 45 / 55 / 65 / 75 / 85 天)。
能力
| 需求 | 实现位置 |
|---|---|
| 每个会话带「长期任务 / 短期任务」标签 | Host 记录表 + sidebar.session.row.leading 席位 |
| 每个会话带「Agent 模式」(按创建会话时的选择出现) | 监听 agent-preset/selected 与 agent/created,记录 preset id/名称;宿主自身的 preset chip 已经在界面显示它,插件下拉里再复述一次 |
| 短期任务到期自动归档,可选 1 / 5 / 7 / 15 / 30 天 | Host 定时扫描 + workspaceRegistry.archiveSession(id, { stopActivity: true }) |
| 长期任务可手动或按 15–85 天(每 10 天一档)归档,默认手动 | 同一个扫描:archiveAfterDays === null 即手动,永远不碰;有值则与短期走完全相同的到期判定 |
| 侧边栏每个会话显示标签,字体 50% 半透 | sidebar.session.row.leading,9px、opacity:.5 |
| 对话阶段:选择器放在会话头部动作行(创造模式之后) | conversation.session.header.actions,order: 0(宿主占用 -30/-20/-10/20),行内 8px 间距由宿主 flex 提供 |
| 新建阶段:选择器与工作区/preset chip 并排 | conversation.input.dock 席位 + useHeroRowRide 把 chip 抬进行上方那一行,间距按测得的行间距与行内 2px gap 对齐 |
| chip 与所在行的邻居 chip 完全同款 | 两个变体类:.stt-chip--header(对齐 AgentPresetLabel:12px / 22px 高 / label-tertiary / fill-tsp-secondary / radius-xs / 图标 opacity:.7),.stt-chip--hero(对齐 AgentPresetSeat:13px / 28px 高 / label-primary / radius-sm / padding:0 8px) |
| 设定时间与归档倒计时 | chip 自身文本里:短期任务 7天 · 剩6天 / 长期任务 35天 · 剩20天(与主标题同色同字号,不另起一档样式);归档后显示 已归档;手动长期任务只显示 长期任务,下拉底部提示「手动归档:不会自动归档」 |
结构
index.js— Host 半:状态文件、事件监听、HTTP 路由、自动归档扫描。零外部 import。client.js— Client 半:手写的 lazy-CJS bundle,只require('react')/require('react-dom')。cordis.patch.yml— 插入一行 Host 插件条目(也是浏览器半被/plugins服务的依据)。
关于「并排」与「不重复」的实现说明
- 对话阶段是纯槽位方案:
conversation.session.header.actions是宿主headerActions(display:flex; gap:8px)的直接子项,天然并排、间距天然一致。 - 新建(Hero)阶段没有第三方席位:
heroWorkspaceRow的三个孩子分别是硬编码的 WorkspaceChip 与两个single槽(conversation.hero.workspace、conversation.hero.agentPreset), 都已被占用,抢占会顶掉工作区选择器或 preset chip。所以插件用useHeroRowRide:在自己的 dock 席位上量一次上方那一行的几何,把 chip 用position:absolute钉在该行最后一个 chip 之后(水平 +2px 对应该行gap:2px, 垂直用测得的行间距)。 - 量的是「最右可见后代」,不是
lastElementChild:宿主每个槽位出口都是<div data-slot="…" style="display:contents">锚点(SlotOutlet),而display:contents元素自身的getBoundingClientRect()恒为 0×0。Hero 行的最后一个子节点 正是data-slot="conversation.hero.agentPreset"这个锚点 —— 取lastElementChild会得到 0 宽度而被判废。现在改为遍历该行所有后代、取可见元素的最大right边,与包装层级无关。 - 偏移量必须是真实
DOMRect:定位用的dockRect只能是dock.getBoundingClientRect(); 插件内的诊断辅助函数返回的是{x,y,w,h},一旦误用会让left/lift变成NaN, 内联样式left: NaNpx失效、chip 退回静态位置。现在另有Number.isFinite守卫: 偏移非有限值时直接走 in-flow 兜底,不会产生非法定位。 - 只显示一次:dock 席位常驻一个锚点(
stt-dock--lifted,负 margin 抵消 flex gap); 量到 Hero 行就把 chip 抬进去;量不到时,若会话头部 chrome 存在(说明头部席位正在画同一个 chip)就不渲染,否则退回普通流的一行(stt-dock--inline)。既不会出现两个,也不会消失。
配置
cordis.patch.yml 中 session-task-tags 行的 config:
shortArchiveOptions: [1, 5, 7, 15, 30] # 短期任务可选窗口(天)
defaultShortArchiveAfterDays: 7 # 短期任务默认窗口
longArchiveOptions: [15, 25, 35, 45, 55, 65, 75, 85] # 长期任务可选窗口(15 起每 10 天,上限 90)
sweepIntervalMinutes: 5 # 自动归档扫描间隔
autoArchiveEnabled: true # 关闭后只记录、不归档
长期任务默认手动归档(记录里 archiveAfterDays: null,扫描永不触碰);选中某个窗口后
才进入自动归档。Client 半按 Host 上报的这两个集合渲染下拉:Host 支持什么就提供什么
(对早于本改动的 Host,长期只有「手动归档」一项)。
数据
$DSH_HOME/plugin-data/dsh-session-task-tags/state.json,按 sessionId 保存
kind / archiveAfterDays / agentPresetId / agentPresetName / taggedAt /
lastActivityAt / archivedAt。归档窗口从「打标签时刻」与「最近活动时刻」中较晚者起算;
archiveAfterDays: null 表示手动归档(长期任务的默认值)。
浏览器 API
POST /session-task-tags/api/list— 读取全表 + 配置(含两个窗口集合)。POST /session-task-tags/api/set—{ sessionId, kind, archiveAfterDays }写入一条;archiveAfterDays省略或不在该 kind 的窗口集合里时:短期回落到默认窗口,长期回落到手动。
两条路由都走与 /api 网关相同的 Host 头信任围栏(loopback + 同源 Origin)。
自动归档的验证记录(2026-10-05)
在运行中的 Host 上做过一次可逆的实测,结论:到期会自动归档,走的是真实的归档 API。
- 把一条 30 天前到期的短期记录写进状态文件 → 重新挂载插件(
apply重跑、load()重读); 5 秒后的首轮扫描立刻把它处理掉:archivedAt被写入、autoArchived: true、archiveAt: null。 - 再对一个真实的空白会话做完整链路测试(提前 10 天到期):
插件记录被标记归档,同时
$DSH_HOME/storages/workspace.json的archivedSessionIds从 19 条变成 20 条 —— 即会话真的进入了 DSH 的归档集合、会从侧栏活动列表消失。 - 测试后通过
ctx.uiWorkspace.unarchiveSession()还原,集合回到 19 条,用户数据无残留。
顺带发现并修掉一个真实缺陷:load() 原先直接 JSON.parse 读状态文件,遇到带
UTF-8 BOM 的文件会抛错并静默把所有标签清空;现在先剥掉 BOM 再解析。
⚠️ 注意:Host 半的 JS 改动需要重启 DSH 才生效。
set_bundle的禁用/启用会让apply重跑(所以状态会被重新读取),但模块本身是按条目缓存的,新代码不会加载 —— 连改掉入口文件名(换一个模块 URL)都试过,无效,所以确实只能重启。 Client 半没有这个限制,文件一改就通过 client HMR 热换。