Skip to main content
5

dsh-work-assistant

544244857/dsh-work-assistant

A work assistant for WeChat: it records what you tell it into a SQLite task ledger, ranks items by urgency, reminds you on schedule, extracts people and their relations from your notes, and hands tasks to separate assistant sessions. Ships its own WeChat bot channel (scan to log in; private chat only) and a full-page dashboard.

Install

dsh plugin --profile web add github:544244857/dsh-work-assistant

README

dsh-work-assistant

dsh-work-assistant

微信里的工作助理。你把事说给它,它记成账本、排好先后、到点提醒你;
干不完的活儿派给独立副手,回头还从笔记里认出人和关系。

npm license node DSH plugin tests


一句话:微信官方机器人只能私聊,你在群里被 @ 了就转发给它,剩下的记账、排序、提醒、派活、认人交给它。

不依赖任何第三方 IM 插件——微信通道、账本、知识库、人物图谱、定时提醒,全在这一个包里。

你会说的话它干的事
周五前把 Q3 报销整理好发我记成工作项,自动判优先级,逾期会一直顶在最前
现在有哪些活按紧急度念一遍,不是只报个数
这个你派个人去做开一个独立会话去干,你继续跟助理说话,干完回来汇报
值班表按 5 个人轮班排到年底排完直接导出一份 xlsx 发到微信
你认识哪些人 / 老周是谁从聊天和笔记里认出的人,带关系判断和把握度

长什么样

在微信里说一句话,它记账、排期、出文件;手上一堆活儿的时候,回头在仪表盘上看全局。

一句"值班表先给我出一份",它把人、规则记下来,排到 12-31,顺手导出一份 xlsx 发回微信。

在微信里排值班表并导出 Excel

PNG / XLSX / DOCX / MD 四种文件都能发到手机上,发完还能读回来核对内容。

在微信里收发各种文件

想看这个插件到底解决什么问题、内部数据怎么组织,读 INTRO.md。这份 README 讲怎么装、怎么配、接口长什么样。

目录 先说边界 · 装 · 登录和设置 · 它能做什么 · 工具 · 配置 · 数据 · 接口 · 取舍 · 限制 · 许可

先讲清楚一条边界

微信官方机器人只能私聊,读不到群聊,也没有"@我"这种事件。

所以这个插件的做法是:你在群里被 @ 了,自己把那条消息转发给机器人。它记账、分析、提醒。

这不是偷懒。个人微信没有对外开放的群消息接口,逆向客户端违反腾讯服务条款、有封号风险,所以这里不做。企业微信的智能机器人可以收群 @ 消息,那是另一条路,需要的话可以另做渠道。

它是怎么跑的

微信(你的手机)                        DSH 宿主进程
  │                                        │
  │  你转发一句话 / 发个文件               │
  ├───────────────►  iLink Bot 长轮询 ────►│ 通道
  │                                        │   ↓
  │                                        │ 助手会话(挂你选的预设与模型)
  │                                        │   ↓ 调 work_* / kb_* / assign_* 工具
  │                                        │ SQLite 单文件账本(work-assistant.db)
  │  收到回执、提醒、文件                   │   ↓
  │◄────────────── 发消息 / 发文件 ◄───────│ 60 秒 ticker + 每日摘要 + 周复盘

三个关键点:

  • 不需要公网地址。走的是长轮询,不是 webhook,本机开着就能收。
  • 提醒是模板直发,不经过模型,不烧 token,断网重连也不会重复轰炸。
  • 副手是独立会话,跟你在 GUI 里开一个会话没区别,能看过程、能接着聊。

装

已经发到 npm,从网上装(推荐,装的是预构建产物,不需要本地编译):

dsh plugin --profile <你的 profile> add dsh-work-assistant

从源码改着玩就装本地目录:

dsh plugin --profile <你的 profile> add -w link:<这个目录的绝对路径>

也可以直接从 GitHub Release 的 tarball 装:

dsh plugin --profile <你的 profile> add https://github.com/544244857/dsh-work-assistant/releases/download/v0.5.6/dsh-work-assistant-0.5.6.tgz

装完重启宿主。插件会自己创建数据目录,默认在 ~/.dsh/dsh-work-assistant/。

仓库在 544244857/dsh-work-assistant,npm 包名 dsh-work-assistant。

登录和设置

重启宿主后,打开 DSH 的设置,左边会多出一项「工作助理」。这个页面里什么都有:

  • 登录状态、扫码二维码(没登录时才显示)
  • 账本概览:未完成几件、逾期几件、进行中几件、能代做几件,还有前 10 条工作项
  • 每日摘要时间和提醒检查间隔,改完点保存,立刻生效
  • 「立刻发一条摘要」按钮,用来验证通道通不通
  • 对话记录:跟你聊过的每一天都留在这儿,点一天看当天的往来
  • 会话设置:助手用哪个预设、哪个模型、推理强度多高、什么权限档位,四组下拉直接选,保存后下一条消息就按新的来

二维码本身是 qrcode 包在宿主进程里渲染成 dataURL 的,不联网,不上传。

如果设置页因为某种原因没出来(比如宿主版本差异),也可以直接开这个地址:

http://127.0.0.1:<你的 DSH 端口>/api/dsh-workassistant/login

这是同一个功能的纯网页版,扫码、看状态、换码都在里面。

用手机微信扫码,把机器人加成好友,就好了。凭据只存在本机,不上传。

登录之后,你给这个机器人发消息就等于跟工作助理说话;它的回复会直接推回微信。

它能做什么

记账

收到消息先判断里面有没有"需要被完成的事"。有就记下来,没有就不记。一件事记不住就拆成几条。

记的时候会顺手判断:期限是什么时候、优先级多高、打什么标签、这件事是不是它能直接代做。

记完一定会给回执,形如:

记下了 w-20261020-006:催老周补发票
期限 10/24 18:00,优先级 高
按紧急度排第 1 位(越靠前越该先做)
现在手上共 6 件未完成

看到这段就说明真的写进账本了。同一件事被反复说(你重发、它重试)不会记成多条——标题或内容重合会认出来,只把新信息补进去,回执改说"已经记过了"。

自动排优先级

不用你操心优先级。每条工作项都会算一个紧急度分数,影响排序和摘要顺序:

因素影响
已逾期分数最高,一直顶在最前
期限越近24 小时内最高,一周内次之,之后递减
高优先级措辞标题或正文出现"催""尽快""马上""今天""急"等,算高
正在做比同等条件下还没动的稍微靠前

分数只在排序时用,不会覆盖你手动设的优先级。如果你想强按某件的顺序,说一句"这件最高优先级"它就会显式标成 high。

主动请缨

遇到查资料、改表格、整理文件、写文档、核对数据这类它手上能干的活,它会主动问一句"这个我可以直接做,要我做吗"。你说"做吧"或者"做第 3 件",它就去干。

干完会汇报结果,并把过程要点存进知识库。

提醒

  • 单条到期提醒:给某件事设了提醒时间,到点推一条。创建时如果只填了期限没填提醒时间,默认提前一小时。
  • 每日摘要:默认 09:00 和 18:30 各推一次,列出逾期、今天到期、本周内要交的。手上没活就不推。
  • 安静时段:默认 22:00 到 08:00 一条都不推。落在这段时间里的提醒不会丢,也不会被标成已发,等时段结束一起补给你。
  • 每周复盘:默认周五 18:00 推一次,说这一周做完了哪些、还欠着哪些。设成"关掉"就不推。

提醒是模板直发,不烧 token。所有"今天是否已经推过"的状态都落盘,重启不会重复轰炸。

重复的事

「每月五号交报表」这种可以直接记成重复任务。做完一件会自动滚出下一件,不用每个月重新说一遍。

让人去办

「等老周补发票」这类不是自己动手的事,可以记成「等某人」并约好什么时候去问一句。到点提醒你"该问问老周了",而不是提醒你去做那件你本来也做不了的事。

具体行为:

  • 说了「下周问他」「周五再催」→ 按那个时间提醒。
  • 没说 → 默认记下来那天往后 3 天。回执里会给确切时刻,不用猜。
  • 到点推一条「该去问一句了:等老周补发票,等 老周」。催过就把下一次往后推 3 天,不会反复轰炸。
  • 第二次起措辞变成「还没回音:…」,提醒你这事还悬着。
  • 标成已完成或放弃后就不再催。
  • 推送失败不动时间,下一轮接着试,不会静默漏掉。

副手:把活儿派出去

有些活儿不是一两句话能办完的——要把三个月的报销单理成一张表、要把一份合同逐条比对模板、要翻一堆资料查清楚某件事。这种活儿交给微信那头等着不合适,你在那儿干等,它也不一定一次干得完。

现在可以派出去。派出去之后它在一个独立的会话里干,你继续在微信里跟助理说话,两边不耽误。干完它会回来汇报,报告直接推到微信。

用法就是在微信里说一句"这个你派个人去做",助理会调 assign_create 开一个副手。也可以自己在整页视图的「副手」卡片里点「派一个」,写清楚要什么结果。

几个细节:

  • 副手是独立的会话,会出现在 GUI 的会话列表里。这是故意的,不是 bug——你想看它到底在干什么,可以直接点进去看它的完整过程,或者点卡片上的「去看」跳过去。
  • 默认最多同时开 3 个。开满了再派会直接失败并告诉你哪几个还占着,不会排队、不会假装派出去了。要改上限改 maxAssignments(1 到 32)。
  • 助手随时能跟它说话。派完想起来还有要求,用 assign_send 补一句;默认算"新的一轮",它会接着干。也能用 assign_inspect 看它最近几步干了什么,assign_stop 停掉。
  • 它交的报告可能是在问你。报告可以标"等你回话",这时候卡片上会亮黄,微信那头也会提示。名额开满时它还是会交报告,只是状态不会切到"等回话"——话照样送到你手上,回执里会说清这一点。
  • 宿主重启后副手会标成"掉线"。下次给它发消息时会自动把会话恢复起来;真找不回来了(比如会话被删了)就如实报错,不会假装还活着。
  • 副手可以绑一个工作区(assignmentCwd)、用别的模型(assignmentModel)、换预设(assignmentPreset)。默认跟助手一样。

一句话总结:助理是那个记事和派活的,副手是那个埋头干活的。

附件

微信里发来的图片、文件、语音会自动收进账本,挂在对应的工作项上。设置页能看到挂了哪些附件,助手也能读图说话。

知识库

客户是谁、同事负责什么、某件事的来龙去脉,都可以存进知识库,以后按关键词搜。

认识的人(知识图谱)

聊天、工作项、知识库里出现过的人名,会被定期整理成一张关系表:谁是你的同事、谁是下属、谁是客户、谁在替谁办事。

举个例子:知识库里那篇"值班表排法"写着"值班共 5 人轮转——老周、小李、大刘、陈工、孙姐",跑一次分析之后,这五个人就进了名单,并带上"同事"或"下属"这种关系判断,而不是当成五个陌生字符串。

整理是模型做的,节奏有两条:

  1. 每天一次,默认凌晨 3 点(analysisDailyTime)。
  2. 闲时一次:只要新消息攒着、而且最后一条已经过了 analysisIdleMinutes 分钟没人说话,就顺手跑一遍。

整页视图里有"认识的人"卡片,点名字能看这个人的关系卡;微信里也可以直接问"你认识哪些人""老周是谁"。

关系是模型根据上下文猜的,不确定的会标低把握度——它认错的时候你直接说"老周不是我下属,是同事",它会改。

看现有任务

五个地方都能看,看到的是同一份账本、同一个紧急度顺序:

  1. 侧边栏的整页面板(左边一排图标里那个勾选框)。这是主入口:六个数字、四张图、能直接改的任务清单、知识库全在里面。往下看"整页视图"。
  2. 微信里问一句"现在有哪些活""哪几件最急"。它会调 work_list 把条目念出来,不是只说个数。
  3. 设置页的任务清单卡片(DSH 设置 → 工作助理)。能切"未完成 / 已完成 / 全部",能按关键词和标签搜,每条右边有"标完成"按钮。
  4. 设置页的知识库卡片,能搜、能翻全文。
  5. 每日摘要,按逾期、今天到期、本周内分节列。

整页视图

侧边栏点进去是一整页,不用挤在设置面板那一小块里。里面分四段:

上面六个数字:未完成、已逾期、进行中、本周做完、本周新记、压了一周还没动。逾期那个只要不是 0 就变红。

四张图,都是拿 SVG 和 div 现画的(宿主没给插件图表库,也不该为这个塞一个进去):

  • 优先级分布:一圈环形图,按高 / 普通 / 低切片。
  • 近 14 天趋势:两条折线,一条是新建、一条是做完。能看出这周是在往里堆还是在往外清。
  • 标签排行:横条,哪个标签的事最多。
  • 等谁排行:横条,谁欠你的事最多。
  • 最老的几件:横条长度是压了多少天,颜色是优先级。压过 30 天就顶格。

任务清单:四个筛选页签(未完成 / 进行中 / 已完成 / 全部)+ 关键词搜索。点任意一条展开,能就地改标题、优先级、期限、标签,改完点保存。也能一键标完成 / 重新打开。不用回微信说,也不用跳去设置页。

知识库和运行参数:左边能搜能翻全文,右边只读显示数据目录、预设、权限档位——这些要改还是去设置页或者 cordis.patch.yml。

右上角有"刷新"和"回到对话"两个按钮。页面每 15 秒自己拉一次数据,切页签、改完东西也会立刻刷。

回看以前的对话

每一条进来的微信、每一条回出去的答复,都会按「谁 + 哪一天」存一份。三处能看:

  1. 侧边栏的「对话」页签(和「任务」「频道」并排)。这一页只做一件事:列会话。一行一个——左边小圆点是在不在跑(蓝的在干活、黄的在等你回话、灰的歇着),中间是会话名,右边是「多久以前」。点一行不是在这里展开,而是把那个会话直接开到右边主区去:思考过程、工具调用、接着打字,全跟平时用会话一样。做法和「频道」页签一致——侧栏负责挑,主区负责聊,同一条会话只有一个界面,不会两边不同步。
  2. 整页面板上的「对话记录」卡片:一行是一天(日期、当天几条、最后一句的开头几个字),点开看全文,左上角「返回」回到列表。想按天翻旧账就来这里。
  3. 设置页里的同一张卡片,列表短一些,适合顺手翻一翻。

关于这个侧栏页签:宿主本身只声明了 sidebar.panellist / sidebar.workspaces / sidebar.settings / sidebar.footer.action 这四个侧栏座位,没有 sidebar.channels。你截图里那个「频道」页签是 im 助理插件自己包了 sidebar.workspaces、再往它私有的 __dshNativeTabs 页签表里插了一条造出来的。我们没有第二套侧栏可用,所以这里的做法是:等 im 助理把页签表挂好,再往同一张表里插一条自己的(id 是 work-assistant-chats,order 45)。这意味着两件事:没装 im 助理插件时这个页签不会出现(面板里那张卡片照常可用);im 助理哪天改了这套私有机制,页签可能会掉,但页签组件外面包了错误边界,掉了也不会把整个侧栏带崩。

老对话怎么补进来的:这份记录是从这个版本开始写的,之前版本只把消息喂给模型、没有落库。所以升级后第一次启动会去读一遍助手会话自己的事件日志(wa:main),把里面真正的用户发言和助手正文抽出来补进库,只补一次(meta 表里记 messages_seed)。读完会跳过工具调用、思考块、以及运行时注入的上下文(时间、技能目录之类),只留人话。会话日志读不到就跳过、不写标记,下次启动再试,所以不会把"没读到"当成"补过了"。

存的是文本,不是聊天记录的备份:图片、文件只记一句说明,原文在微信里。默认全留在本地 SQLite 里,不会上传。

会话设置(预设 / 模型 / 强度 / 权限)

设置页里「助手会话」卡片有四组下拉,和 im 助理那边一个套路:

  • Agent 预设:助手会话挂哪套人格和工具(列的是宿主里能用的预设)。第一项是「跟随宿主默认」:选它就挂宿主自己的默认预设,插件不替你挑。别人装这个插件时,他那边配的默认预设(或者他自己建的人格预设)才是对的,所以出厂值也是空。
  • 模型:怎么选模型。第一项是「跟随宿主默认」,选了就用宿主当前的默认模型;要指定就从列表里挑一个,提供方和模型名是一起选的,只能选一个会报错。模型列表和每个模型的推理强度都由宿主的 llm 服务现读,不写死在插件里。
  • 推理强度:只有当前模型真的声明了强度档位才出现这一栏,否则藏起来(不同模型支持的范围不一样,硬塞一个会失败)。
  • 权限:助手会话用哪一档权限,档位描述也是宿主给的。

保存之后不会立刻把会话拆掉——万一这时候正有一轮在回,拆了就丢回复。做法是打一个"过期"标记,等你下一条微信消息进来,才按新配置重开一个助手会话;旧会话留在宿主的会话列表里,不会丢。

有一件事容易踩:如果你在 Web 界面里点开过助手会话直接聊天,那个会话是宿主自己建的,只挂了界面那一侧的预设——台账工具一个都没有,人格也不是这里配的那个,模型会说「工具不存在」。现在复用这种已经开着的会话时,会先就地补挂本插件的工具、人格、权限和提示词,再交回去用;GUI 里点开会话刚开始跑的那一刻也会补挂一次。会话正被宿主写的时候(报 already owned by an active write handle),会先退让等一会儿再把活着的会话接管下来,不再把消息直接丢掉。

这几项写进 state 表的 settingsOverrides,重启也在。也可以直接改 cordis.patch.yml 的 agentPreset / permissionPreset / agentProvider / agentModel / agentReasoningEffort。

预设和插件提示词是什么关系:人格、自称、称呼、句子长短,全部由预设决定,插件不掺和。插件只往会话里加一段「台账工具怎么用」的约定(什么时候记、怎么派副手、文件怎么发),开头就写明「以这个会话挂的人格设定为准」。老版本这里是写死的「你是工作助理、对方叫老板、短句分点」,结果和人格预设互相打架——用户配的狼娘人格被压住,自称、渠道全说错。现在换成中立写法,挂谁就是谁,工具照常可用。

同理,预设名和模型名都从宿主现读,没有写死任何一个。agentPreset 留空就是跟宿主默认;万一填的名字在别人机器上不存在,会自动退回默认,不让会话起不来(副手的 assignmentPreset 也一样)。

微信里的"正在输入"

你发完消息,微信顶部就会显示"对方正在输入…",模型想事情、调工具的十几秒里一直亮着,回复发出来才消失。

这件事没有想象中直白:微信的输入状态是一次性的,发一次几秒后就自己没了。所以插件内部每 5 秒补发一次,一轮结束时立刻撤掉。撤不干净的话你会看到"回复都已经到了,顶上还写着对方正在输入",那是骗人的。

它只是锦上添花——取不到票据、网络抖动都只是悄悄跳过,绝不会影响正经回复。

微信里发不出文件?现在堵住了

微信会话只认一件事:文件必须走 work_send_file 才可能到手机上。

之前踩过的坑:模型生成完文件,调了 present(那是 Web 界面里的"交付物卡片"工具),返回写着"Presented C:...\测试图片.png",模型就以为发过去了,回一句"已发送,请查收"——微信那边一个字节都没到。visualize、ask_user_question 同理,都是只在电脑界面上才有意义的工具。

现在助手会话里这三个工具被摘掉了(lib/assistant.js 的 WECHAT_HIDDEN_TOOLS),而且是两道锁:

  1. 名单屏蔽:从可见工具列表里拿掉,模型根本看不见,就不会去调。
  2. 执行期闸门:万一名单漏了(预设被换掉、工具被别的插件重新注册),真调下去那一刻也会被拒,返回"present 在微信里发不出任何东西,要给他看文件请用 work_send_file"。

这里有个时序坑值得记一笔:present、visualize 是 agent 预设带进来的工具,预设挂载完成之前它们不在可见名单里。这时候去屏蔽会报 unknown global tool,被 catch 吞掉,"屏蔽"就成了一句空话——第一版就是这么漏的。所以 setupScope 现在先 await presets.mount(...) 等预设真的挂完,再动屏蔽;这个方法也因此改成了 async(宿主会 await setup 回调,所以安全)。

系统提示词里也写死了两条:发文件只有 work_send_file 一条路;文件发成功之前不许说"已发送"。

给模型用的工具

23 个工具,只注册在助手会话和副手会话里,不污染你的其他对话。你不用记它们——直接用平常话说就行,它们只是模型伸手够得着的那几样东西。

账本(记、查、改、提醒)

工具干什么
work_capture记一条工作项,自动去重、自动判优先级,回执带紧急度排名
work_list查工作项,可按状态、期限、标签、关键词、等谁筛,默认按紧急度排
work_update改状态、改期限、加备注、标记"我接了"
work_snooze把一条的提醒往后推,比如"这事下周再说"
work_digest生成当前的工作摘要
work_review生成这一周的复盘(做完的 / 还欠着的)

知识库与人(长期记忆)

工具干什么
kb_write往知识库写一条
kb_search搜知识库
kb_read读知识库某一条
kb_list列出知识库条目
work_people看整理出来的认识的人,按提及次数排
work_person看某个人的关系卡,以及这个人在哪些事里出现过

交付与跟进(发出去、等回音)

工具干什么
work_send_file把本机文件或知识库里的内容发到微信
work_attach把图片/文件挂到某条工作项上
work_notify主动往微信推一条消息
work_assign记成"等某人办",并约好什么时候去问一句

委派(把活儿派出去)

工具干什么
assign_create派一个副手出去干一件大事
assign_list看现在有哪些副手、都在干什么
assign_inspect看某个副手最近几步的动静
assign_send给副手补一句要求(不唤醒 / 下一步 / 新的一轮三档)
assign_stop停掉一个副手
assign_wait等副手交报告,默认等 60 秒,最多 120 秒
assign_report副手用它往回交活(只注册在副手会话里)

配置

在 cordis.patch.yml 里改:

项默认值说明
stateDir空数据目录。留空则用 ~/.dsh/dsh-work-assistant/
agentPreset空助手会话用的预设。留空=用宿主自己的默认预设
permissionPresetworkspace-write助手会话的权限档位
agentProvider空助手用哪个模型提供方。留空跟随宿主默认
agentModel空助手用哪个模型。留空跟随宿主默认
agentReasoningEffort空助手的推理强度,取值随模型而定。留空用模型默认
dailyBriefTimes['09:00', '18:30']每日摘要时间,可多个
tickIntervalSeconds60定时器多久检查一次,最小 10
mergeWindowMs1500消息合并窗口。微信常把一句话拆成几条发,合并后再交给模型
maxMessageChars1200单条微信消息的字数上限,超了自动分段
timeZone空时区。留空用系统时区
quietHoursStart'22:00'安静时段开始。和结束一起留空表示关掉
quietHoursEnd'08:00'安静时段结束
weeklyReviewWeekday5每周复盘的日子,1-7 是周一到周日,0 表示关掉
weeklyReviewTime'18:00'每周复盘的时间
maxAssignments3最多同时开几个副手,1 到 32
assignmentCwd空副手在哪个目录干活。留空跟助手一样
assignmentModel空副手用哪个模型,写成 provider/model。留空跟助手一样
assignmentPreset空副手用的预设。留空=跟随宿主的默认预设
analysisEnabledtrue要不要自动整理知识库、认人
analysisDailyTime'03:00'每天几点整理一次,格式 HH:mm
analysisIdleMinutes30闲多少分钟没新消息就跑一次,0 表示关掉闲时分析
analysisProvider空分析用的模型提供方,留空跟助手一样(只能在 cordis.patch.yml 里改)
analysisModel空分析用的模型,留空跟助手一样

dailyBriefTimes、tickIntervalSeconds、安静时段、每周复盘,以及会话的预设 / 权限 / 模型 / 推理强度,这几项在设置页里改更方便,改完写进库里的 state 表(键名 settingsOverrides),重启后会被读回来盖在 cordis.patch.yml 的默认值上,所以不会丢。其余几项得改 cordis.patch.yml 再重启宿主。

会话设置保存后不重建会话:下一条微信消息进来时,会用新的预设、模型和权限档位重开一个助手会话。旧会话留在宿主的会话列表里,不会丢。

数据放在哪

全在 stateDir 下。主要是一个 SQLite 数据库文件,可以直接备份、也可以用任何 SQLite 工具打开看:

文件内容
work-assistant.dbSQLite 数据库:工作账本、副手登记簿、知识库、人物图谱、运行状态全在这里
media/微信里收下来的图片、文件、语音
gateway.log排错日志,超过 1MB 自动截断

删掉整个目录等于重置。

数据库用的是 Node 自带的 node:sqlite(宿主是 Node 24),不需要装任何依赖,也不需要编译原生模块。表结构在 lib/db.js 里,第一次启动自动建表。

旧版本留下的 state.json / ledger.json / assignments.json / kb/index.json(以及 kb/*.md 正文)会在第一次启动时读进数据库。账本、副手登记和知识库索引搬完会改名成 *.migrated-<时间戳> 留档;state.json 只读不删。迁移是否做过记在库里的 meta 表,中途失败(比如正文文件读不到)不会写标记,下次启动会接着迁。

如果数据库文件真的坏了(SQLite 报 SQLITE_CORRUPT / SQLITE_NOTADB),插件会把主库和 -wal / -shm 一起改名成 work-assistant.db.corrupt-<时间戳> 留着,再写一行日志说明。只是被别的进程占着写锁(SQLITE_BUSY)不算坏库,会等 5 秒而不是改名。

HTTP 接口

都挂在 /api/dsh-workassistant 下,只接受本机访问:

路径方法说明
/loginGET扫码登录页(设置页不可用时的备用入口)
/login/scanGET查当前二维码和登录状态
/login/statusGET只看登录状态
/statusGET运行状态(本实例是主还是待命、还活着的宿主清单)
/reloginPOST重新登录(换一张码)
/overviewGET设置页用:登录状态 + 账本概览 + 生效中的设置
/statsGET整页视图用:优先级分布、14 天趋势、标签/等谁排行、最老的几件、六个汇总数字
/itemsGET设置页和整页视图共用:按紧急度排好的任务清单,?status=open|doing|done|dropped|all,可加 &query= 和 &tag=
/itemPOST改一条任务,body 形如 {"id":"w-20261020-001","status":"done"};除了 status 还能改 title / priority / dueAt / tags
/kbGET设置页用:知识库目录,?query= 走搜索、?limit= 限条数
/kb/docGET设置页用:读知识库某一条全文,?id=k-20261020-001
/kb/docPOST设置页用:新建知识库条目,body 形如 {"title":"报销流程","content":"先填单"}
/conversationsGET对话记录目录:按「天 + 联系人」分好组,?limit= 限条数
/conversationGET读某一天的往来,必须给 ?day=2026-10-22,可加 &chatId= 和 &limit=
/optionsGET设置页的下拉项:可用预设、各提供方的模型与推理强度、权限档位,带上当前选中值
/sessionsGET侧栏「对话」页签用:能点开的助手会话(主会话 + 每个副手),带会话 id、名字、状态、最后活动时间
/settingsPOST设置页用:改每日摘要时间、检查间隔、安静时段、每周复盘
/digestPOST立刻推一条摘要,验证通道
/assignmentsGET整页视图用:副手列表,?status=active|all
/assignmentGET整页视图用:一个副手的详情和最近几步,?id=a-20261021-001
/assignmentPOST整页视图用:{"action":"create","purpose":"..."} / {"action":"send","id":"...","message":"..."} / {"action":"stop","id":"..."}
/peopleGET整页视图用:认识的人名单,?limit= 限条数
/personGET整页视图用:一个人的关系卡,?name=老周
/analysisGET整理任务的状态:上次跑的结果、还有多少条消息没整理
/analysisPOST立刻整理一次(不等定时)

/login/scan 是只读的:页面每 2 秒问一次,它只把同一张码和状态返回去,不会换码。换码只在你点"重新获取"或者码过期时才发生。

设置页改的值写进库里的 state 表(键名 settingsOverrides),重启后会被盖回配置,不会又被 cordis.patch.yml 里的默认值冲掉。

设计上的几个取舍

为什么不用宿主的 schedule 服务? 那个服务只是往会话里注入一条消息唤醒模型,发不到微信。定时提醒要真推微信,所以这里自己起一个 60 秒的 ticker,到点用模板拼文本直接发。

为什么工具定义是手写的字面对象? 在 profile 目录下 require.resolve('@deepseek-ai/dsh-tools') 是解析不到的——核心包在宿主的 app.asar 里。宿主对工具注册的要求只是「有 name、description、parameters、output.schema、output.render、execute」,所以不需要 import 那个包。

为什么要做消息合并? 微信里人习惯一句一句发。逐条交给模型既碎又费 token。1.5 秒的窗口把连续几条拼成一段,模型看到的是完整意图。

为什么只认 owner? 机器人加上好友后,别人也可能给它发消息。不设防的话,谁都能指挥你的助理去改文件。所以只认登录的那个微信账号,陌生人一律丢。

为什么设置页是单独的 lib/client.js? DSH 的设置面板跑在浏览器里,跟宿主进程是两种环境。lib/client.js 用 window.__ModuleLoader__.load 注册一个工厂,再往 settings.section 插槽里塞一个 React 组件;React 由宿主的模块基座提供,插件不自己装。它跟 index.js 各跑一边,靠 /api/dsh-workassistant/* 这几个 HTTP 接口通信——所以就算设置页没显示出来,用浏览器直接开 /login 也一样能扫码。

为什么要有"宿主租约"? 同一个数据目录被两个宿主指着(比如桌面端再加一个后台 dsh web),就会各起一套轮询和定时器:同一条微信被两个进程抢着收,提醒也各发一遍。真出过这个事故——16:30 的摘要和 18:00 的周复盘各发了两条,内容还不一样,其中一份读的是几天前的旧快照。

现在 hosts 表里每个宿主每 30 秒写一次心跳,90 秒没更新的算死;started_at 最早的那个是主,其余待命:不轮询、不跑 ticker、不推消息,但面板照常能看能改。主进程退出后,下一次心跳(最多 30 秒)里待命的那个自动升主并接手。面板上会写明"另一个宿主正在跑",省得你以为是插件不动了。

为什么"正在输入"要自己定时补发? 微信的输入状态不是常驻开关,发一次过几秒就自动消失。想让它一直亮着只能定时补发(这里 5 秒一次),一轮结束再显式发一个"停止"。另外这个状态要先拿 typing_ticket,票有有效期,按用户缓存 5 分钟,失效了下次重新取。

已知限制

  • 只有私聊,没有群聊,没有 @ 事件。
  • 一个账号只能跑一个实例,多开会抢同一个轮询。
  • 副手是独立的顶层会话,所以关掉宿主时它们也跟着停;重启后标记"掉线",第一次发消息时才恢复。
  • 副手的报告是它自己调的 assign_report。它要是忘了调、直接结束回合,你在卡片上只会看到"空闲",得点「去看」问它。
  • 停掉一个"之前就开着、被复用上"的副手时,只能叫停它、没法把那个会话从列表里抹掉(宿主没给释放别人会话的接口)。这种情况回执里会明说,你自己把那个会话关掉即可。
  • 数据目录默认是 ~/.dsh/dsh-work-assistant/,跟 profile 无关。两个宿主用同一个数据目录时只有主的那一个干活,另一个在面板上标「待命」,主退出后 30 秒内自动接手;想让两个都干活就给其中一个改 stateDir。
  • 认人靠模型从上下文里抽,会认错、会漏。名字越常见("小王""张经理")越容易和别人的事串在一起,拿不准的时候它会标低把握度。
  • 同名的人会合并成一条档案(库里按名字做唯一键)。真遇到两个「王伟」,现在只能看到一个;等以后加了人工合并/拆分再补。
  • 分析要调模型,会花 token。analysisEnabled 关掉就完全不跑。
  • 建议用专用小号,别拿主号当机器人。
  • 提醒靠本地进程活着。宿主关了就发不出来,下次启动会补发没发过的摘要。
  • 安静时段结束后的补发是"有什么补什么",不会按原定时间重排。半夜错过的三条会在早上一起到。
  • 侧栏那个「对话」页签是借 im 助理插件的私有页签表插进去的(宿主没有 sidebar.channels 这个座位)。没装或停用 im 助理时页签不出现,整页面板里的对话记录照常可用;im 助理以后改这套私有机制,页签可能会掉,掉了不影响其它页签。
  • 对话记录存的是文字,不是聊天备份。图片、语音、文件在记录里只有一句说明;回填也只能补回会话日志里还在的部分(宿主自己清理过的日志补不回来)。

许可

MIT。协议实现参考了 @michengai/dsh-im-connect(Apache-2.0)中腾讯 iLink Bot 的调用方式。

Related plugins