dsh-cyber-im-avatar

identitytested: passnpmMIT

IM stand-in seat for DeepSeek Harness: a sidebar panel polls Feishu/Lark, WeCom and DingTalk (switched on from the panel) through the platform CLI you are already logged in to, and wakes the DSH session you bound when a new message arrives, so that session reads it and replies as you — drafts are sh

下载量 / Downloads
1,251
依赖体积 / node_modules
0.75 MB
测试时间 / Tested
2026-11-06
DSH 版本 / Version
0.2.2-rc.1

Install

dsh plugin --profile web add dsh-cyber-im-avatar

Sandbox test results

StageResult
1 install✓
2 list✓
3 mount✓
4 boot—

Four stages run in an isolated sandbox: install (network on) → list → mount → boot (last three with no network).

README

dsh-cyber-im-avatar · IM 分身壳

把仓库根目录那套 skill(SKILL.md + scripts/)做成 DeepSeek Harness 里的常驻壳:

侧边栏一个席位,点开是每日关注、值守实例、服务对象(群工作本 / 人物档案)与手动发送。

分工(spec 0004):插件只当壳,skill 是唯一的脑子。

层 · 干什么

壳(本插件) · 按节奏拉取(耳朵)、命中唤醒绑定 DSH 会话(嘴)、消息展示与手动发送(脸)

skill · 闭环协议、平台差异、铁律、故障自愈

agent 会话 · 被唤醒后全权处理:自己调 IM CLI 拉上下文、调研、起草、发送

cyber-im-avatar/dsh-plugin/
├─ package.json            # dsh.bundle(patch) + dsh.client(platform/inject)
├─ cordis.patch.yml        # 把 bundle 插进 web 组合
├─ lib/index.js            # 宿主半:同源路由 + token + 装 skill + 注入 DSH 服务
├─ lib/app.js              # /api/* 路由表与参数校验
├─ lib/state.js            # state.json:配置 / 值守名单 / 最近 10 条指针
├─ lib/poller.js           # 轮询、分类、命中后唤醒(待回派生供计数与详情)
├─ lib/turns.js            # live turn:resolveAgent / followup / 等 turn/end / 取消
├─ lib/im.js               # 平台适配:lark-cli / wecom-cli 封装 + @我 判定
├─ lib/profiles.js         # 画像:人物档案(单聊一份)与群工作本;旧私聊工作本整文件搬迁
├─ lib/workspaces.js       # 工作区列表(读 ~/.dsh/storages/workspace.json)
├─ lib/sessions.js         # DSH 会话目录路径换算(只读)
├─ lib/client.js           # 构建产物(内联了 web/panel.js)
└─ web/panel.js            # 面板 UI 的唯一真源(原生 DOM,无框架)

这个插件不在本机开任何端口。 面板数据走 DSH 自己的 Web 服务(同源),

后端就是宿主进程里的这些 ESM 模块;整台机器上唯一的监听 socket 是 DSH 那个。

没有 Python 服务、没有 stdio 管道、没有 SQLite、没有 dsh headless 子进程。

装

# 需要一个 DSH(web profile)。已发到 npm,直接装包名:
dsh plugin --profile web add dsh-cyber-im-avatar
dsh web        # 重启后侧边栏底部出现「IM 值守」

# 或者装本地目录(改代码时用这个)
dsh plugin --profile web add ~/cyber-im-avatar/dsh-plugin

想让它出现在 DSH 的「插件 → 发现」里可搜可点,把市场索引加进去:

https://raw.githubusercontent.com/buger-gif/cyber-im-avatar/main/dsh-plugin/market/index.json

前置:lark-cli / wecom-cli 已安装并登录;要用钉钉再装 dws(npm i -g dingtalk-workspace-cli)。

钉钉默认不启用:在面板「体检」页点钉钉卡片的「启用」,或加入一个钉钉会话值守,才会开始轮询。

skill 不用单独装:插件首次启动会把

cyber-im-avatar 装到 ~/.agents/skills/cyber-im-avatar/(npm 包自带副本;本地

link 安装读仓库根目录)。已经手装过同一份的不会被覆盖,只打一行提示。

起 DSH 建议用仓库里的脚本,别直接 node bin.js web &:

./scripts/start-dsh.sh            # 已在跑就只打印地址(幂等)
./scripts/start-dsh.sh --restart  # 先停再起

它做两件事:让 DSH 进独立会话(关掉终端也不会被连带关闭),并清掉代理变量与

NODE_OPTIONS 注入项。token 落到 ~/.dsh/web-token(600)。

命中 → 唤醒 → 同会话续跑

值守会话来了新消息(私聊消息 / 群 @我;私聊可切「只盯 @我」,见下),插件在宿主里拿绑定的 DSH 会话跑一轮

live turn。每次唤醒的载荷都只追加本次新消息:

1. 新会话首轮首行 /cyber-im-avatar —— DSH 的 user invocation,直接把 skill 正文注入

这一步(闭环、平台差异、铁律、故障自愈都在 skill 里,prompt 不再抄);同一轮另带

平台/对象、chat_id、运行形态与记忆文件路径(单聊:人物档案一份;群:会话工作本

与本批发言人的人物档案路径,≤5、去重、@我优先)与本次新消息;

2. 已有会话的后续唤醒只发本次新消息行(外加本会话还没给过的人的档案行;

同一人在同一条 DSH 会话里只给一次路径),不再重复平台、

chat_id、运行形态、工作本路径或媒体说明——这些已经在同一 session 的历史里。

插件不注入画像正文、不解析 agent 回复、不代写画像;agent 按首轮载荷里的路径自己读、

自己改:单聊只有对方的人物档案一份;群才是「工作本写本群规矩 + 人物档案写人的评价」。

换会话或重置会话会重新带 /cyber-im-avatar 入口。进程被杀后 state.json 的

prompt_session 与 recent 还在,下一轮继续追加 pending/新消息,不重建完整载荷。

富媒体在唤醒时取回:只对本批待回消息下载,不动历史。图片用 DSH

attachments.admitPromptContent() 变成 image block,模型直接可见;文件/语音/视频

落 ~/.im-standin/media/ 并在 prompt 给本地路径;sticker 只标 [表情]。卡片、合并

转发、位置、名片、系统消息等未适配类型,以及下载失败的消息,才在 prompt 标“可用 CLI 取”。

单批最多 4 张图 / 合计 10MB、单条 20 秒;模型不支持图片或附件服务缺失时降级为

文本 + 本地路径,不阻断 turn。

命中回执:followup 被接受后,插件用 lark-cli im reactions create 给本批 @ 你的消息点一个 Get 表情(不是新消息);只认群 @ 与私聊「只认 @」模式的 dm,仅飞书,失败不影响 turn,设置页可关(dsh.ack_reaction)。

agent 在这一轮里自己用 lark-cli 拉上下文、去绑定工作区核实事实(只读)、

起草并发送或等你在会话里确认——它有多少工具就用多少,插件不封顶。

壳内不挂监听:轮询、水位、唤醒都是插件的事。agent 不运行
listen_cli / bootstrap.py,也不重挂监听——壳内沙箱是 workspace-write,
~/.im-standin 在工作区外,写不了;也不需要写。
  • 同一通 IM 会话 = 一个长期复用的 DSH 会话(chats.dsh_session),跨命中、

跨重启累积历史;在 DSH 侧边栏能看到、能点开接着聊。

  • 在飞 turn 可打断:同一会话又来了新消息 → agent.cancel 旧 turn →

同 session 重起,任务里注明「上一轮被新消息打断,以最新上下文为准」。

  • 批次合并:同一轮 poll 拉到多条新消息只起一个 turn,不让 agent 被连发消息叫醒 N 次。
  • 并发安全:就是宿主里的同一个 agent,DSH UI 打开它不再抢 session.lock;

另一个 DSH 宿主持有写锁时才报 session_locked_other_host。

  • 失败重喊:turn 失败/超时后,积压还在就下一轮再喊;不自动连环重试。
  • 重开会话:实例卡片或会话详情点「重开会话」→ 打断在飞 turn、解绑当前会话,

值守中(未屏蔽)立刻在新会话里跑一轮开场——不用等下一轮命中,@-only 私聊也不会看起来

像没建会话。旧会话保留在侧边栏,只是不再续聊。闲置超过阈值也会自动换,见下。

单轮守时:dsh.timeout(默认 600 秒)是停滞阈值——本轮有工具/模型活动就重置计时,真卡死才取消;dsh.turn_cap_sec(默认 3600 秒,0 = 不限)从轮次开始兜总时长。长任务不会被「单轮只能跑 10 分钟」的墙钟切掉。

模型与会话都走 DSH

不需要在这儿再配一遍模型。插件在宿主进程内拿 DSH 的 sessionController /

sessions,模型、网关、凭据全部由 DSH 自己解析:

要什么 · 从哪读

会话 / agent · ctx.sessionController.resolveAgent() 或 create()

模型 / 网关 / 凭据 · DSH 自己的 profile 配置与凭据库,插件不读、不存、不回传

工作区 · ctx.workspaceRegistry + ~/.dsh/storages/workspace.json(只读展示)

所以「DSH 里能正常对话」=「值守能干活」。插件没有第二份 key、没有 headless profile、

没有起草模型设置。

实例的生命周期

一通值守会话 = 一个分身实例 = 一个 DSH 会话。插件管四件事:

事 · 行为

建 · 首个命中时 create({workspaceId · cwd}),DSH 自动把它归属到工作区

续 · 复用;历史与工具调用都落在同一个 session-… 里;一段连续闲置最多跑一轮值守心跳(dsh.warm_min)

断 · 新消息到达时 agent.cancel 在飞 turn,同 session 重起

换 · 手动「重开会话」(解绑后立刻起开场轮)、工作区漂移、会话丢失,或闲置超过第二个保温周期(约 2 × dsh.warm_min)后的下一条消息;旧会话留在侧边栏

实例卡片显示绑定会话、累计轮数(保温轮不计)、最近一次 turn 是否成功与上次保温结果。

保温与会话到期

  • 值守心跳(dsh.warm_min,默认 55 分钟):一段连续闲置达到这个间隔,轮询器在已绑定

会话里起一轮很轻的维护——续上下文缓存,并按 skill 规则整理载荷列出的文件(单聊一份

人物档案;群工作本 + 最多 3 份档案),不扫目录。它不计入「N 轮」、不推进闲置时钟;真实

消息到达会立刻打断它,详情页显示上次保温结果。

  • 只保温一次:再过一个同样长的周期(默认约 110 分钟)仍没有真实消息,就停止保温;此后

的第一条消息到来时先解绑旧会话,在新会话里带完整首轮载荷处理。旧会话不删。手动重开、

换工作区、会话丢失也会在详情里写明换会话的原因。

  • dsh.warm_min=0 同时关闭保温与自动换会话;想让它活得更久就调大间隔。面板「设置 →

会话生命周期」可改。

会话所在 cwd 与绑定的工作区不一致时(换了工作区),DSH 会拒用旧会话,

插件会丢掉旧 id 在正确工作区重开,并在实例卡上提示「已自动新建」。

显式工作区目录存在但未登记进 DSH 时,仍先恢复旧会话,不会每轮白开新的。

拉取节奏:定时 / 按需 / 手动

路径 · 什么时候发生 · 拉什么

值守(快车道) · 默认每 60 秒(poll.fast_sec,0 = 关) · 值守中的私聊,以及关注程度不是「不盯」的群

发现(慢车道) · 默认每 10 分钟(可调,下限 10 分钟,上限 240) · whoami + 刷会话列表(发现新会话),再补拉一轮候选

按需 · 点开某个会话时 · 在线拉 30 条;群聊平时完全不进快车道,只有勾了才进

手动 · 顶部「同步」、会话里的「拉一次」、「设置 → 立即拉一轮」 · 不受间隔约束;同一会话 20 秒冷却,连点不会重复打接口

面板每 60 秒刷新一次状态(值守状态点 / 处理中),和拉取节奏是两件事。

刷新不会吞掉正在输入的内容:搜索、企微手动加入、详情发送框与设置页数字框都有内存草稿;

接口失败时保留上一份数据并给「重试」,屏蔽 / 清理待回 / 运行中移出会先确认。

为什么要给群单独一套规则:群消息量远大于私聊,绝大

…(truncated)

Rendered from the README published on npm. Formatted only, not edited.

Versions

Published on npm::14,最后更新 2026-11-06

Links

GitHub · Homepage · npm

Same category