ALittleLov/dsh-clock-tools

dsh-clock-tools

Host-measured task timing for DSH agents: a task_clock tool that does not touch presets

未分類dshtask-timing

DSH Clock Tools

A task-timing plugin for DSH local AI agents.

为 DSH 本地 AI Agent 提供可核实的任务计时能力,把预计耗时、实际耗时和任务状态分开管理。

项目状态:0.1.8 已发布到 GitHub(仓库 · Releases)。
已验证环境:DSH 0.2.1-rc.2(仅此版本实测;其他版本未验证)。

为什么需要这个项目?

AI Agent 能理解“5 分钟”“20 分钟”“超时”这些词,但这不代表它知道现实中已经过去了多久。

典型问题:Agent 预计任务要 20 分钟,实际不到 2 分钟就做完,之后却把“预计 20 分钟”说成“已经过了 20 分钟”。

这暴露的是系统设计问题,不是模型态度问题:

  • 预计耗时是预测,不是测量。
  • 距上一条消息的间隔,不等于任务执行时间。
  • 当前日期时间,不等于持续运行期间的经过时间。
  • 任务跨 turn 后,需要能保留和查询之前的计时记录。

DSH 自带的时间上下文能提供当前时间,但任务级计时需要另一套机制。这个插件的做法是:宿主程序负责计时,Agent 负责在合适的时候调用工具并解释结果。

功能

1. 任务计时

task_clock 工具提供五个动作:

操作 · 用途 start · 创建任务计时,记录预计耗时与单调时钟起点 check · 查询当前实际耗时、预计耗时与剩余时间 finish · 结束计时,记录实际耗时、偏差与状态 list · 查询已保存的任务记录 now · 查询当前本地时间与时区(不创建任务;需要报时刻时用它,不要推算)

参数(完整定义以包内 index.js 为准):

参数 · 必填 · 说明 action · 是 · start / check / finish / list / now taskId · 否 · 指定任务;check / finish 默认当前 session 的 running 任务 plannedSeconds · 否 · 预计耗时,单位秒(start) label · 否 · 任务标签(start) outcome · 否 · completed / aborted,默认 completed(finish) limit · 否 · list 条数,默认 10,最大 50 allSessions · 否 · list 是否包含其他 session 的记录

返回值为一段人类可读文本(text),不是结构化 JSON 字段。

2. 使用单调时钟测量经过时间

同一进程内,实际耗时用 performance.now() 的差值计算。墙钟(Date.now())只用于显示开始/结束时间,以及跨进程续算时的空档计算。

这样,普通的系统日期时间调整不会直接改变进程内连续运行的耗时结果。

3. 区分预计与实际

任务记录分开保存:

  • plannedMs:预计耗时
  • actualMs:实际耗时
  • status:running / completed / aborted / uncertain

偏差:

Variance = actualMs - plannedMs

负数表示比预计早完成,正数表示超时。

显示时长按整秒向下取整;内部仍保留毫秒。精确核对请看 tasks.json 的原始值,不要拿格式化后的文本反推。

4. 任务记录持久化

记录写入 ~/.dsh/clock-context/tasks.json(JSON 数组,原子替换)。

  • 跨 turn:普通新消息不会清空记录,后续 list 能查到。
  • 跨 session:默认 list 只看当前 session;allSessions: true 可看同一文件里的全部记录。
  • 跨进程重启:单调时钟起点不能延续,见下一节。

5. 多种 preset 支持

验证口径(DSH 0.2.1-rc.2):

  • 0.1.0 的四个动作(start / check / finish / list):Standard / Cordis / Minimal / PTC 四种 preset 均完成真实会话验证。
  • 0.1.1 新增的 `now`:目前只在 Standard 会话实测通过;Cordis / Minimal / PTC 未单独验证 now 动作。

preset · 四动作(0.1.0) · now(0.1.1) Standard · ✅ · ✅ Cordis · ✅ · 未单独验证 Minimal · ✅ · 未单独验证 PTC · ✅(run_code / ptc-dispatch) · 未单独验证

插件在 Profile 层注册,不依赖 preset。但这不代表所有 DSH 版本、所有 Profile 或所有平台都已验证。

6. 时间规则注入(0.1.8 起,默认开启)

插件默认通过 ctx.systemPrompt.context() 把三条固定规则放进每次模型请求的运行时上下文,不产生 user 消息,不作为对话消息出现在历史里:

时间规则(dsh-clock-tools):
- 报时间、写时间戳前先查 task_clock,禁止推算;查不到的不写。
- 查到原样用:HH:MM 截断,不四舍五入,问秒才给秒。
- 跨度不等于工时,有断档分段说。

文本是包内常量,只读本地配置决定开/关,不发网络请求。

关闭方式按优先级从高到低:

  1. Profile patch 的 config(最高优先):
- id: clock-tools
     name: 'dsh-clock-tools'
     config:
       injectTimeRules: false
  1. 配置文件 ~/.dsh/clock-context/config.json:
{ "injectTimeRules": false }

两者都没有设置时默认注入。文件不存在、不是合法 JSON、字段不是布尔值,一律当作“未配置”,仍然默认开启。配置文件与任务记录放在同一目录(如果改过 storeDir,config.json 也跟着走)。

开关在 DSH 启动、插件加载时读取;改完需要完全退出并重启 DSH 才生效。

工作原理

  1. Agent 判断任务是否需要计时。
  2. 调用 task_clock.start,记录任务标识、预计耗时、墙钟与单调时钟起点。
  3. 执行过程中可调用 task_clock.check 查询实际经过时间。
  4. 任务完成后调用 task_clock.finish。
  5. 记录写入本地文件,后续可用 task_clock.list 查询。

插件不会因为存在计时能力就自动给所有任务计时;是否在合适的时机调用,是 Agent 的行为问题,不是插件的保证。

时钟连续性与重启

情况 · 行为 连续运行(同一进程) · 单调时钟差值,continuity: continuous 墙钟发生跳变 · continuity: clock-changed,仍用单调时钟,标注墙钟变化 仅重启 DSH(系统未重启) · continuity: resumed:前后两段用单调时钟,空档用系统运行时间桥接,标注 (resumed, uptime-bridged) 系统也重启 · continuity: resumed-gap:重启前、重启后两段精确,关机空档用墙钟估算,标注 (resumed, gap estimated) 墙钟回退、无法估算 · actualMs: null,状态 uncertain,不编造耗时

跨重启的空档只能是估算,不能等同于连续运行时的单调时钟结果。插件也不声称能抵御所有系统级计时篡改。

跨重启续计时(0.1.4 起,0.1.5 完善)

DSH 重启会结束进程,单调时钟起点随之丢失。0.1.4 起支持续算,0.1.5 修正了分段公式并加入累计量:

  • 记录里保存 accumulatedMs(截至检查点已确认的累计耗时)和产生它的计时基准(baseBootId / baseMono / baseUptime / baseWall)。
  • 有 running 任务时,每 10 秒做一次本地检查点:把已确认耗时写进 accumulatedMs 并更新基准。纯本地文件读写,不调模型。
  • 计算总耗时:

情况 · 公式 同一进程 · accumulatedMs + (nowMono - baseMono) 跨 DSH 重启、系统未重启 · accumulatedMs + (processStartUptime - baseUptime)×1000 + (nowMono - processStartMono) 跨系统重启 · accumulatedMs + (processStartWall - baseWall) + (nowMono - processStartMono),标 resumed-gap 无法桥接 · 退回整段墙钟估算(restarted),或 unknown

  • 中间段只算到新进程启动那一刻,不把重启后的时间重复计入。
  • 系统启动周期怎么判断见下方「平台适配」。
  • 强杀、崩溃时最多丢一个检查点(约 10 秒)。

Actual 的含义始终是 `start` 到 `finish` 的总经过时间,包含 DSH 关闭期间和系统睡眠时间;它不是 Agent 实际执行工作的时间。

平台适配

平台 · 启动周期识别 · 验证状态 Windows · 墙钟 − os.uptime() 推断(差 ≤ 2 秒视为同一次启动) · 实测 Linux · 读 /proc/sys/kernel/random/boot_id;读不到回退墙钟推断 · 未测(代码已支持) macOS · 回退墙钟推断 · 未测

读取失败(文件不存在、权限不足)不会报错,自动回退墙钟推断。未验证平台上如果识别错误,结果会降级为 restarted / unknown,不会伪造精确值。

需要自定义时,编辑 index.js 顶部的 readPlatformBootId(),或在插件配置里设置 platformBootToken(字符串)直接覆盖。

安装

  • 支持版本:DSH 0.2.1-rc.2(仅此版本实测)
  • 安装范围:Profile 级(同一 Profile 下的所有 preset 共享;其他 Profile 需分别安装)
  • 安装后重启:需要,完全退出 DSH 再启动

GUI(已验证)

  1. 侧边栏打开 Plugins(插件)
  2. 点 Add plugin(添加插件)
  3. 解压发行 ZIP,输入解压出来的 dsh-clock-tools 目录的绝对路径(选择目录,不是 ZIP 文件本身)
  4. 安装 → Enable now(立即启用)
  5. 完全退出 DSH,再启动

装完后插件页面会显示标题与描述,新会话工具列表出现 task_clock。

装好只是第一步,还得把规则写进 AGENTS.md,见下方「让 Agent 真的用起来」。

CLI(命令来自 DSH 支持的方式,本包未实测)

dsh plugin --profile <你的 Profile> add "<本地目录绝对路径>"

升级

当前发行包未提供升级命令;升级时建议卸载后重新安装(未实测)。

卸载

GUI:插件页面 → 对应插件 → Remove(入口存在,卸载流程本身未实测)。

CLI(未实测):

dsh plugin --profile <你的 Profile> remove dsh-clock-tools

任务记录不会被删除;需要清理时手动删除 ~/.dsh/clock-context/。

使用

task_clock start   plannedSeconds=1200  label="写报告"
task_clock check
task_clock finish  outcome=completed
task_clock list
task_clock now