19 个真问题,答案都能在代码里核对。文中路径相对仓库根;$DSH_HOME 默认是 ~/.dsh。
这个插件的中心规则:完成要人点头。 Agent 在工具里报完成,和交给的会话干净收尾后宿主自动 收尾,都只把记录送进「待确认」;你点「确认完成」才算完成,点「退回清单」就回到清单里。为什么 两条路都要人回答,见第 3 问。
默认 $DSH_HOME/global-todo/todos.json,可以用插件配置的 storePath 指到任意绝对路径
(lib/store.js 的 resolveStorePath)。同一个目录里还有 settings.json(宿主侧开关)和
client-errors.log(面板错误日志,有上限)。
安装和卸载都不碰这些文件。卸载后手动 rm -r 那个目录才会彻底清掉。
0.21.0 起会了,但要你自己指定一个碰头目录(插件仍然没有账号、没有服务器):
在每台机器的面板里打开 设置 → 多端同步,把「共享目录」填成同一个绝对路径(Windows 用盘符或 UNC,
例如 Z:\Agent产出\global-todo;「自动同步间隔」填 0 就只在手动同步)。无面板的 headless 部署可以在
cordis.patch.yml 的 config.sync.dir 里给默认值。
每台机器各存一份本地文件,在那个目录里交换一份 todos.hub.json。同步 = 两边都折一次:
按记录合并(同一条看 updatedAt,删除靠墓碑传播,seq 重号会按创建顺序重排),所以顺序、重试、
两台同时同步都不会丢记录。规则与限制见 README 的「两台机器,一份清单」。
- 读列表永远读本地文件:共享盘挂了、NAS 掉线,清单照常能用,只是
/health.sync报失败原因。 - 手动同步:面板/脚本
POST /api/global-todo/sync,或让 Agent 调global_todo_sync。 - 不要再把
storePath指到共享盘上:那是另一种做法(共享同一个文件),每次读都走网络、 断网就打不开,而且它和sync混用会互相覆盖。
老办法仍然可用(把 storePath 放在 Syncthing/Dropbox 里),但它没有合并逻辑:两边同时改,
最后落盘的那份赢,另一边丢失;删除也会在对方的下一次写里复活。真正跨机请用 sync。
按顺序查(GET /api/global-todo/sync 或 /health.sync 会给答案):
现象(last.error / 状态) |
原因 |
|---|---|
sync-not-configured |
那台机器的面板里没填共享目录,或 headless 部署的 config.sync.dir 没写 |
sync-unavailable |
上层目录不存在(共享盘没挂上)。插件故意不替你建这个目录,否则会安静地写进本地空目录 |
sync-busy |
上一次还没跑完(网络盘卡住时常见);等下一轮 |
sync-timeout |
单次文件操作超过 20 秒:挂载点僵死,检查 mount / 重挂 |
sync-corrupt |
todos.hub.json 不是合法 JSON,或被别的程序改坏。它不会被覆盖:手工检查/移走后下一轮重建 |
| 一切正常但另一台没变 | 两台看的是不是同一个目录(Windows 盘符映射 vs Linux 挂载点)?那台机器的插件是不是 ≥ 0.21.0? |
记录变成 done,同时带上 review: { pending: true }(lib/model.js 的 completeTodo)。
清单里两处可见:行上的「待确认」徽标,顶部的「待确认」筛选与计数。
| 你的回答 | 结果 |
|---|---|
| 确认完成 | 保持已完成,标记清除,动态里记下是谁确认的 |
| 退回清单 | 回到 open;doneAt 和完成记录清空;动态里加一条 reopened |
派发出去的会话干净结束时,自动收尾也走同一条规则——自动完成同样是「agent 的声明」,一样要人回答。
因为子任务就是另一条待办:要有自己的 id、状态、加入时间、附件、派发目标和完成记录。真做出来, 「一份清单」就变成了「一堆互相嵌套的清单」。
steps 明确是一条工作里的计划:扁平、最多 20 步、每步只有 text 和 done,派发时作为编号
计划原样交出去。需要各自派发的,就拆成多条待办——那才是清单该有的粒度。
抓取时逐字引用的那一句用户原话,最多 300 字(lib/model.js 的 LIMITS.evidence)。
global_todo_capture 会要求模型带上,global_todo_add 也收。
面板详情里显示在「原话」下;Markdown 导出里是 原话: 一行。你在面板里手敲的条目没有这一项——
它只服务于「抓来的东西有没有听错」这件事。
三个门,按需要选:
| 入口 | 形状 | 行为 |
|---|---|---|
| JSON 文档 | { "version": 2, "todos": [...], "trash": [...] } |
面板「更多操作 → 导入」,或 POST /api/global-todo/import;面板用 merge(按记录合并:updatedAt 晚的赢,删除随 trash 传播,旧副本里的已删记录不会复活),dryRun: true 只返回 wouldCreate / wouldUpdate |
| Markdown | - [ ] 标题 任务列表,缩进标注 优先级 / 截止 / 标签 / 原话 / 描述 / 附件 / # |
同上,选 .md 文件;按 create 新建,没有 id 所以不会覆盖任何记录 |
| 外部写入 | POST /api/global-todo/ingest,带 Authorization: Bearer <settings.ingestToken> |
来源只接受 mail / notification / announcement;一次最多 100 条;置信度低于 ingestConfirmBelow(默认 0.8)的进收件箱 |
CSV 没有直接入口:先转成 JSON 或 Markdown。Markdown 解析是容错的——不认识的行忽略,日期解析失败 就丢掉那一项,整份文件里一行任务都没有才会报错。
能。面板只依赖宿主的 web server 和那个 JSON 文件:增删改查、标签、优先级、截止、步骤、附件、
筛选、搜索、导出导入、提醒都不需要 Agent。global_todo_* 工具只是可选挂载面。
没有 Agent 参与时用不上的只有四件事:抓取、派发、Agent 的完成声明、会话回读。
| 目前没有 | 现状 |
|---|---|
| 看板 / 日历视图 | 现在只有列表,加三种分组(对话 / 时间 / 级别);没有拖拽看板,没有日历 |
| 重复任务 | dueAt 是单个时间点,还没有 recurrence 字段;同一截止只提醒一次,改了时间才会重新提醒 |
| 子任务树 | 现在只有扁平的 steps,最多 20 条 |
| 上传附件 | 现在只存引用(绝对路径或 URL),不复制字节 |
| 多清单 / 项目 | 现在只有一份全局清单 |
| 自动开工 | 现在没有;它是一份个人清单,不是任务队列 |
| 桌面端面板 | 现在只有 Web 客户端 |
| 账号 / 多用户 | 现在没有;只有一个本机(或受信网络)可访问的 HTTP 面 |
有锁,会排队。 每次写之前,先在文件旁边独占建出 todos.json.lock(open(…, 'wx')),写完删掉:
- 同一个进程里,人和 Agent 的写本来就走同一条串行链。
- 另一个宿主进程(Web 面板 + 桌面端 / 命令行会话,或两台机器共用一份同步盘)只能排队。轮到了就继续;
等不到 5 秒就报
store-busy(HTTP 503,工具返回{ ok: false, reason: "store-busy" }), 不会默默把你的改动覆盖掉。 - 持锁进程崩了也不会把清单锁死:锁文件里记着
pid、host和一个随机 token。同一台机器上, 只有持有者进程确实不在了才算过期;跨机器没有 pid 可问,按 30 秒超龄接管。接管会写一条 warn 日志; 被接管过的锁,前一个持有者回来也不会误删新的那把。 - 读者永远不会看到半截文件(rename 是原子的)。清单在别处变过,面板仍然会提示。
有三件事它管不到,说清楚:旧版本插件(不拿锁)、你手改文件、同步盘客户端在任意时刻写入—— 这几种仍然是「后写覆盖先写」。所以把清单放进同步盘时,别让多个客户端同时同步它。
要并行又不想排队?那就各自一份文件,再用导出 / 导入合并。
宿主每 30 秒扫一次(lib/index.js 的 reminder tick):状态是 open / doing、dueAt 已过、
且这个 id → dueAt 还没报过的条目,进 /health 的 reminders.due,面板顶部显示成一条提醒条。
| 设置 | 默认 | 作用 |
|---|---|---|
remindersEnabled |
开 | 关掉就不再扫、不再显示 |
reminderLeadMinutes |
0 | 提前几分钟报;0 = 到点才报 |
点「知道了」调 POST /api/global-todo/reminders/ack,只从当前进程的提醒列表里移除。
「报过了」的记忆也在内存里,所以重启宿主后,仍然到点且未完成的条目会再报一次。
浏览器通知是可选的:面板里点「开启系统通知」授权后,到点的条目会发一条系统通知。
先分清三件不同的事:
| 开关 | 默认 | 管什么 | 怎么关 |
|---|---|---|---|
autoCollect |
关 | 插件自动去问一个安静了 30 分钟的对话「有没有要跟进的事」 | 面板设置里的「自动获取」开关,或 POST /api/global-todo/settings { autoCollect: false } |
guidance |
开 | 往 system prompt 里注入「主动记」的引导 | 插件配置 guidance: false |
| 对话分组上的「豁免」 | 无 | 把某个工作目录整个排除在自动获取之外 | 再点一次取消 |
「抓一下待办」/「从本对话抓取」不在此列:那是你手动触发的,永远听你的。
dsh plugin --profile <profile> remove dsh-global-todo然后重启宿主、刷新页面,侧栏入口消失。清单和设置都还在原处——这是刻意的。
要彻底清掉(先确认里面的 todos.json 你不要了):
rm -r "$DSH_HOME/global-todo"更稳的顺序:先导出 JSON 或 Markdown,再删目录。重新装回来后,把导出的 JSON 用「更多操作 → 导入」 就能接着用。
| 收件箱(Inbox) | 待确认(Review) | |
|---|---|---|
| 谁产生的 | 外部写入(/ingest),置信度低于阈值 |
Agent 的完成声明(工具或自动收尾) |
| 问题是什么 | 「这条要不要收下」 | 「这活是不是真做完了」 |
| 两个动作 | 确认(去掉 inbox 标签进清单)/ 丢弃(归档并去掉标签) |
确认完成 / 退回清单 |
| 不处理会怎样 | 超过 7 天自动归档(宿主每 6 小时扫一次) | 一直挂着,行上一直有「待确认」 |
| 原因 | 表现与处理 |
|---|---|
| 没有工作目录 | 明确报错,因为没有目录的会话里 agent 做不了事;改用「开新对话」,或先选目录 |
| 目标对话已归档 | 选择器里标「(已归档)」;换一个,或新开 |
| 宿主没有 agents 服务 | agents-unavailable:精简组合里没有能接活的 agent |
bound 模式但没绑定 |
直接告诉你没有绑定会话,并建议改用 auto |
失败一定带原因,也记在详情的「最近派发」里;不会静默什么都不做。
插件本身已经加载了,这是「显示文字」取不到,不影响面板和工具。宿主读插件页标题/描述的顺序是:
先解析 <包>/locale/en.json(走 exports 里的 ./locale/*.json),解析成功后扫这个目录,
把 zh.json/en.json 逐个读进来。目录不存在,扫描就抛 ENOENT,于是元信息报错。
也就是说:你那份本地插件目录缺 locale/ 文件夹。常见于把仓库文件夹手动复制到
%USERPROFILE%\.dsh\plugins-local\ 时只拷了 lib、package.json、cordis.patch.yml。
先确认到底缺什么,别靠猜:
npx dsh-global-todo-doctor "$HOME/.dsh/plugins-local/全局待办插件"
# Windows: npx dsh-global-todo-doctor "$env:USERPROFILE\.dsh\plugins-local\全局待办插件"它逐项检查 package.json、patch 文件、两个入口、locale/、图标与 lib/,缺哪样直接点出来,
缺必需项时退出码为 1(可以放进安装脚本里当门禁)。
修法二选一:
-
推荐:别用本地目录,直接装 npm 包(包里有
locale/en.json、locale/zh.json):dsh plugin add dsh-global-todo
-
保留本地目录,把缺的补齐(
$src换成仓库或 npm 包里那份):$dst = "$env:USERPROFILE\.dsh\plugins-local\全局待办插件" $src = "C:\path\to\dsh-global-todo" New-Item -ItemType Directory -Force "$dst\locale" | Out-Null Copy-Item "$src\locale\*.json" "$dst\locale\" -Force Copy-Item "$src\docs\icon.svg" "$dst\docs\" -Force # 图标同理会报错
然后重启宿主(插件元信息在启动时读一次)。
为什么会这么脆:exports 的 ./locale/*.json 是通配映射,Node 解析它时不检查文件是否存在,
所以宿主以为文件在、去扫目录,才发现没有。完整安装不会触发;只有手工拷半份才会。
说明有进程正持有 <store>.lock。锁文件里记着 pid、host、at,按顺序看:
host是本机、pid还活着:真的在写,正常几毫秒就过去。一直卡住说明那个进程卡死了—— 重启它(或等它退出),下一次写入会按 pid 判死、立刻接管。host是本机、pid已经不在了:下一次写入立刻接管并写一条 warn 日志,不用手动删。host是另一台机器:没有 pid 可问,按 30 秒超龄接管,最多等半分钟。两台机器时钟差得多时 先校准时钟,否则判断会偏。- 确认没有第二个宿主在用同一个
storePath(Web 面板 + 桌面端 / 命令行会话,或同步盘两端)—— 这是唯一会把「排队」变成「一直排队」的情况。 - 兜底:确认没有任何进程在写之后,直接删掉那个
.lock文件。它是缓存,不是数据,删了不会丢条目。
会,而且这是最常见的浪费。插件在检测到 mnemonMemory 服务时(也就是记忆插件真的装着)会多注入一段分工:
- 要跟进的动作用待办(
global_todo_*);已经知道的事实用记忆(mnemon_remember)。同一件事只写一边。 - 待办标完成时,工具结果里会多一句「结论值得以后想起来就
mnemon_remember一条,正文带上待办 id」, 所以「完成了」和「记住了」不是两套记录,而是一条线索。 - 从记忆里看到「还有件事要办」→ 用
global_todo_add记成待办;记忆里的条目不会被画掉。
不装记忆插件时这段和那句都不出现:模型不会被引导去调一个不存在的工具。
想更深一层(让记忆检索直接召回待办)需要另写一个 dsh-mnemon-source-global-todo(用它的 Source SDK),
那是第二个包、带 peer 依赖,本仓库故意不做——这个插件必须能单独工作。
0.22.0 起,Agent 写进清单要过一道门(阈值由数据定,见 README 的「自动记录的门槛」):
| 情形 | 结果 |
|---|---|
没有截止时间(dueAt 为空) |
进「收件箱」,等你点「收下」或「丢弃」 |
| 有截止时间,但今天已经进了 8 条 Agent 记录 | 进收件箱,原因写 quota(不丢,只是不在清单里) |
| 有截止时间、额度还有 | 直接进清单 |
| 面板里你自己敲的 | 永远直接进清单 |
| 升级到 0.22 那一刻 | 做一次清理:没有截止时间、且你从没动过的 Agent 记录移进收件箱(本机回放:68 条),之后再按新规则走 |
顺带两件事也变了:
- 级别按截止时间算:没有日期 = P3、7 天内 = P2、48 小时内 = P1、已逾期 = P0。你在面板上改过一次级别,这条就锁成你的(
prioritySource: human),规则不再覆盖;Agent 当时想给的级别留在suggestedPriority里。 - 同一轮里 Agent 已经自己记过,就不再触发自动获取的提问——它已经把要说的写下来了,再问一次只会多出没人要的条目。
收件箱里的记录不算进「全部 / 未完成」的计数,所以那两个数字现在就是清单本身。想把它放回清单,就「收下」;想彻底不要,就「丢弃」(归档,不删)。