← Back to home@Som2233

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
GitHub repo

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。

  1. 把一条 30 天前到期的短期记录写进状态文件 → 重新挂载插件(apply 重跑、load() 重读); 5 秒后的首轮扫描立刻把它处理掉:archivedAt 被写入、autoArchived: true、archiveAt: null。
  2. 再对一个真实的空白会话做完整链路测试(提前 10 天到期): 插件记录被标记归档,同时 $DSH_HOME/storages/workspace.json 的 archivedSessionIds 从 19 条变成 20 条 —— 即会话真的进入了 DSH 的归档集合、会从侧栏活动列表消失。
  3. 测试后通过 ctx.uiWorkspace.unarchiveSession() 还原,集合回到 19 条,用户数据无残留。

顺带发现并修掉一个真实缺陷:load() 原先直接 JSON.parse 读状态文件,遇到带 UTF-8 BOM 的文件会抛错并静默把所有标签清空;现在先剥掉 BOM 再解析。

⚠️ 注意:Host 半的 JS 改动需要重启 DSH 才生效。set_bundle 的禁用/启用会让 apply 重跑(所以状态会被重新读取),但模块本身是按条目缓存的,新代码不会加载 —— 连改掉入口文件名(换一个模块 URL)都试过,无效,所以确实只能重启。 Client 半没有这个限制,文件一改就通过 client HMR 热换。