DeepSeek Harness Plugin Hub

发布与管理完整 Harness Profiles,发现适合你的插件。

探索

插件目录环境预设文档中心动态

社区

发布插件联系我们报告问题

相关链接

Plugin Hub GitHubDeepSeek Harness 官方项目系统状态隐私说明
© 2026 DeepSeek Harness Plugin HubPowered byPaxTech

独立、非官方社区项目,与 DeepSeek 官方无隶属、授权或背书关系。

DeepSeek Harness Plugin Hub
ProfilesPlugins分类动态文档登录管理 Profiles
ProfilesPlugins分类动态文档登录
← Plugins

dsh-mesh-waker

Mesh Waker

dsh-mesh 的 DSH 客户端:通过认领机制拉取 hub 收件箱(无需入站端口),将邮件转换为真正的会话轮次,从 hubUrl + joinCode 自动完成自举,并可独立运行以协调本地多会话。

插件会安装到这里;不确定时保持 web。

npx -y @deepseek-ai/dsh plugin --profile web add dsh-mesh-waker@0.8.0
README兼容性版本

兼容性与来源证明

Mesh Waker 以 dsh-mesh-waker 发布,当前版本为 0.8.0。Plugin Hub 会校验它的 manifest,并保存精确安装来源,便于复现安装结果。

DSH 兼容范围
*
运行环境
web
发布来源
npm
Registry 更新时间
2026/11/4

版本

0.8.0stable
2026/11/2
0.7.5stable
2026/10/31
0.5.6stable
2026/10/29
查看其余 22 个版本收起版本
0.5.4stable
2026/10/29
0.4.60stable
2026/10/29
0.4.59stable
2026/10/28
0.4.55stable
2026/10/27
0.4.54stable
2026/10/27
0.4.53stable
2026/10/27
0.4.52stable
2026/10/27
0.4.51stable
2026/10/27
0.4.50stable
2026/10/27
0.4.49stable
2026/10/26
0.4.48stable
2026/10/26
0.4.47stable
2026/10/26
0.4.46stable
2026/10/25
0.4.45stable
2026/10/25
0.4.44stable
2026/10/25
0.4.43stable
2026/10/25
0.4.41stable
2026/10/25
0.4.40stable
2026/10/25
0.4.39stable
2026/10/25

相关插件

正在加载相关插件…

最新版
0.8.0
DSH
*
HMR
重启进程
Tree shaking
未声明可安全裁剪
解包体积
535.9 kB
文件数
17
Surface
web
许可证
MIT
发布源
npm
周下载
2,352
安全扫描
✓ v0.8.0 扫描通过
查看源码 ↗项目主页 ↗
README Badge

点击下方 Badge 复制 Markdown,粘贴到 README 即可。

这是你的 Plugin?认领权益 · 优先安全扫描

验证 package.json 声明的 GitHub 仓库,即可管理这个公开页面。认领后,Hub 会优先安排当前版本的安全扫描,并在通过后公开展示结果。

认领这个 Plugin →
报告问题
0.4.38stable
2026/10/25
0.4.37stable
2026/10/25
0.4.36stable
2026/10/25

相关插件

继续浏览 agents-orchestration 分类下经过校验的插件。

Hindsight Coding Agents@vectorize-io/hindsight-coding-agents面向编程代理的仅反思式 Hindsight 长期记忆(可插拔支持多种运行框架:opencode、……),自动在后台导入(无需设置 CLI)。Liangshen@linxin666/dsh-liangshen适用于 dsh Web GUI 的 LiangShen (梁神) agent preset 及其 composer lever。主机部分在激活时将内置 preset 声明到 harness 的 agent-preset 注册表中(declare = enabled),因此无需写入任何内容,preset 即可供新会话选择Remote Bridge@linxin666/dsh-remote-bridgeDSH 远程桥接:主机端通过唯一且精确的 /ios/v1 WebSocket 路由提供协议 v1——会话列表、历史记录基线、提示、包含工具活动的实时回合增量、中断以及进程内代理审批——连接到通过一次性配对的设备Ds Botds-botDS Bot:DeepSeek Harness 的一组长期运行的 Bot,支持主 Bot、Bot 间消息和群聊

README

dsh-mesh-waker

DSH 客户端插件:dsh-mesh 局域网 agent 中枢的接入端。

默认是 pull 模式:本机按间隔向中枢 GET /api/inbox?claim=1 拉信,所以不需要公网 IP、不需要入站端口、不需要 NODE_EXTRA_CA_CERTS。有入站能力时再叠加 push(中枢发 HMAC 签名的 /mesh/wake ping),把延迟从「一个轮询间隔」压到「一次 HTTP」;两条路径共用同一套 conversation 冷却与去重,不会重复开 turn。

安装

dsh plugin add dsh-mesh-waker
dsh plugin --profile web add dsh-mesh-waker

装完打开 设置 → Mesh,填两样就能接入:

  1. hubUrl,例如 http://<HUB-HOST>:8790 或 https://mesh.example.com;
  2. joinCode(中枢控制台/管理员发的 8 位一次性注册码)。

启动时插件 GET /api/bootstrap 取服务信息(以及可选 CA),再 POST /api/enroll 换回自己的 agent token,按 0600 写进 tokenFile;日志里只出现 token fp=<前6位>… len=<n>,重启后不再需要 joinCode。

不用中枢也能跑:不填 hubUrl 就是 standalone,只起本机 127.0.0.1:8795 的五个本地端点,供同一 DSH 里的会话互相 inject。

开发模式:本机 link(不是 npm 安装)

本仓库维护者跑的是开发态:DSH profile 的 node_modules/@local/dsh-mesh-waker 是指向本机 checkout 的 link,而不是 registry 上的 npm 包。这与发布态是两件事,混在一起就会出现「改了仓库为什么没生效」。

形态谁用安装方式改动如何生效
发布态外部用户dsh plugin add dsh-mesh-waker重新 npm install / 升级版本
开发态维护本仓库的人DSH profile 的 node_modules/@local/dsh-mesh-waker link 到本机 checkout同步文件 + 重启 DSH

为什么开发态要 link:npm 安装的是 registry 上的版本,改本地 checkout 不会影响已安装副本;link 让 profile 直接加载 checkout 里的文件,才能边改边验。

开发态的同步三步(<checkout> 是本机 clone 路径,<link-target> 是 profile 里 @local/dsh-mesh-waker 指向的目录):

  1. 同步文件:把 plugins/mesh-waker/ 下的 index.js、client.js、locale/*、icon.svg、README/CHANGELOG 复制到 <link-target>(Windows 示例:Copy-Item "<checkout>\plugins\mesh-waker\index.js" "<link-target>\index.js" -Force);
  2. 保护 link 目标的 package.json:name 必须是 @local/dsh-mesh-waker,dsh.bundle.patch 必须是 ./cordis.patch.yml。不要用仓库的 package.json 覆盖它——覆盖后宿主会按 npm 形态解析,插件加载失败、客户端也可能崩;
  3. 重启 DSH 并确认:curl http://127.0.0.1:8795/mesh/status,pluginVersions.process 应等于新版本;若 stale=true,说明文件已换但进程还跑旧代码。

常见故障:

  • process 版本没变 → 没重启 DSH,或状态里 stale=true 说明进程仍是旧的;
  • 插件完全不加载、8795 无响应 → 先查 profile 的 cordis patch 里是否有 disabled: true 的 waker 条目,再查 <link-target>/package.json 的 exports 是否包含 ./package.json 与 ./cordis.patch.yml(缺了会 ERR_PACKAGE_PATH_NOT_EXPORTED);
  • 开发态与发布态混淆 → 对照上表:@local/dsh-mesh-waker 是 link 的开发态,dsh-mesh-waker 是 npm 发布态,两者可共存但加载的是不同副本。

本仓库所有验证都在开发态(link)下完成;发布态用户按上面「安装」一节即可,不需要做这些步骤。

它提供什么

  • 唤醒端点:/mesh/wake,校验 HMAC 与时间窗;错误签名直接拒绝,不消耗会话;
  • 设置页:中枢地址、信任级别(strict / normal / full)、按设备信任、收件箱目录、投递方式、启动补收;
  • 收件箱:右栏「开始」里的「Mesh 信箱」卡片 + 左侧侧边栏整页视图,按会话聚合、悬停预览、点击跳转、按收发着色;
  • 本地历史缓存:中枢只保留 7 天消息,插件把见过的消息镜像到本地 history.json,不过期;
  • 会话绑定:会话 ↔ mesh conversation 的映射分「唤醒路由」与「面板显示」两个字段,互不干扰;
  • pull 循环(默认):claim=1 → 按 conversation 分组 → 走现有唤醒路径 → mark_read;间隔自适应(有信 5s / 常态 30s / 连续 3 次空 60s),失败按 5s × 2^n 退避(上限 5 分钟);
  • 自举接入:hubUrl + joinCode → 取 CA → 换 token → 写 tokenFile(0600),全程不打印密钥;
  • standalone:没有 hubUrl 时只起本机监听,/mesh/status.mode="standalone",不发任何网络请求。

配置

全部可选;写在 profile 的 cordis patch 里(或 ~/.dsh/mesh-waker/config.json)。按用途分组,每组一张表。

中枢连接

键默认说明
hubUrl—中枢地址;留空 = standalone
token—agent token 明文;优先级低于 tokenFile
tokenFile—token 文件路径(自举会把换到的 token 写进去,0600)
joinCode—一次性注册码;有它且没有 token 时自动换 token
agentName—自举/注册时用的节点名([A-Za-z0-9_-]{1,32});不填由中枢分配
agentPreset—该节点的权限预设名(交给宿主解释)
modeautoauto(pull 常开,push 叠加)/ pull / push
caFile—额外 CA PEM,用于出站 TLS(替代 NODE_EXTRA_CA_CERTS)
caSha256—可选指纹钉扎;与 caFile 或 bootstrap 的 tls.ca_sha256 不符即拒绝连接

唤醒与轮询

键默认说明
wakeSecret—只有 push 需要的 HMAC 密钥明文
wakeSecretFile—密钥文件路径;与 wakeSecret 二选一
listenHost127.0.0.1只有 push 才需要改 0.0.0.0 或 LAN 地址
listenPort8795本地面板与会话端口
advertiseHost—push 时告诉中枢的可达地址(决定 wakeUrl)
wakePath/mesh/wake唤醒端点路径
pollIntervalMs30000常态轮询间隔(下限 5000,上限 600000)
pollBusyIntervalMs5000拉到消息后的快档间隔
pollIdleIntervalMs60000连续 3 次空轮询后的慢档间隔
claimTtlMs300000claim 租约;puller 崩溃后由中枢自动回收
registerIntervalMs300000向中枢重报 wakeUrl 的间隔
historySyncIntervalMs600000面板历史同步间隔(下限 60000)

投递与会话

键默认说明
boundDeliveryModequeue绑定会话的默认投递:queue / steer / branch
allowSenderDeliveryModetrue是否允许发送方按消息指定投递模式
delivery—单条消息的投递覆盖(queue/steer/branch),非插件级配置
sessionTitlemesh 收件箱收件箱会话标题
sessionCwd—收件箱会话的工作目录
workspacePath—收件箱会话绑定到的工作区路径
workspaceTitleMesh 收件箱侧栏里该工作区的显示名
branchReuseHours12branch 侧会话复用窗口(小时)
branchTitlePrefixmesh · branch 侧会话标题前缀
resetAfterHours12收件箱会话闲置多久后重置
maxWakes50单个会话累计唤醒次数上限
maxSteerPerWindow1每窗口允许的 steer 次数
steerWindowMs60000steer 计数窗口
maxInlineBytes4096单条正文内联进唤醒回合的 UTF-8 字节上限;超出时按字符边界截断并在正文末尾标注「正文已截断:原文 N 字节…完整内容请用 mesh_inbox 读取」(收件箱原文不变)

补收与冷却

键默认说明
catchUpOnStarttrue启动时补收离线期间的消息
catchUpDelayMs12000启动后延迟多久开始补收
catchUpMaxConversations3一次补收最多覆盖几个会话
cooldownMs1500同一会话两轮之间的最小间隔
conversationCooldownMs60000同一 conversation 的冷却窗口

限速与节流

键默认说明
throttleMaxWakesPerWindow30每窗口最多唤醒次数
throttleWindowMs3600000节流窗口(1 小时)
minMessageChars24短于该长度的消息不单独起一轮(合并处理)

主题路由

键默认说明
topics[]主题路由表:每项 {label, title, allowFrom},把远端标签解析到本地会话
allowFrom[]允许向该主题/桥发消息的 peer 白名单(空 = 不限制)

审批桥(approvalBridge)

键默认说明
approvalBridge关远程审批桥配置对象(下同)
enabledfalse是否启用
to—审批消息发给哪个 peer
allowFrom[]只接受这些 peer 的回复(空 = 任何 peer,不推荐)
timeoutSeconds120等待决定的秒数(1–3600),超时按拒绝
conversationPrefixapproval/审批对话的 conversation 前缀
pollMs2000轮询回复间隔(250–60000)
diagfalse打印诊断日志(含消息形状,不含正文)

提问桥(questionBridge)

键默认说明
questionBridge关向人提问的桥配置对象(下同)
enabledfalse是否启用
to—提问消息发给哪个 peer
allowFrom[]只接受这些 peer 的回复
timeoutSeconds300等待回答的秒数(1–3600)
conversationPrefixquestion/提问对话的 conversation 前缀
pollMs2000轮询回复间隔(250–60000)
diagfalse诊断日志

面板与日志

键默认说明
logFile<tmpdir>/mesh-waker.log日志文件;推荐 ~/.dsh/mesh-waker/waker.log(重启不丢)
logMaxBytes2097152(2 MiB)超过即滚动为 <logFile>.1(只留 1 份历史),下限 1 KiB
injectHeadertrue注入正文前加一行 [TASK …] 表头;设 false 退回逐字投递
tasksRetentionDays30任务库保留天数(启动时裁掉更旧的记录)
tasksMaxRows5000任务库最多保留条数(超量裁最旧)
compactTaskTexttrue启动时把仍内嵌正文的任务行迁移为 textRef(内容寻址存 texts/<sha256>.txt);迁移失败不改写任务库
storageTierstrue任务/回执按工作区分层存放(区内 ws/<slug>/、跨区 local/);首次启动把旧单文件拆分并保留 *.legacy.jsonl
requireNamespacedIdsfalse跨区任务(落 local/)的单号建议带命名空间(如 dsh-mesh/T-123);默认只 WARN,设 true 时拒绝裸 T-… 单号
logsRetentionDays30日志保留天数:启动时删除 local/logs/ 与 ws/*/logs/ 下超期的按天文件(与 tasksRetentionDays 对齐)
entryLabelMesh 信箱信箱入口显示名(侧栏 tooltip、页面标题、设置页跳转文案共用;≤40 字)

信任与权限

键默认说明
trustnormal本机默认信任档:strict / normal / full(见下节)
trustByPeer{}按发送方覆盖信任档,如 {"<peer>":"strict"}
permissionPresetByTrust见下节信任档 → DSH 权限预设的映射

激活矩阵:

hubUrltokenjoinCode行为
无任意任意standalone:只绑 listenHost:listenPort,不发任何网络请求
有有任意正常启动,mode 决定 pull / push / auto
有无有自举:/api/bootstrap → /api/enroll → token 写 tokenFile(0600) → 继续启动
有无无standalone,且 lastError="missing token (provide joinCode or tokenFile)"(不静默退出)

CA 只记 sha256 前 16 位,token 只记 fp=<前6位>… len=<n>;任何日志都不打印密钥。

信任与权限

信任档只在本机设置,中枢与对端都不能提升它(代码注释原话:a remote message can never turn this machine into an unapproved execution surface)。远端能做的只有:在本机允许的档位内发消息。

档位被远程唤醒的这一轮允许做什么
strict只读:只读取、只回复;不写文件、不执行命令、不装东西。要动手的事写进回信请人批准
normal(默认)能直接办的就办;写文件/装插件/改系统/提权这类先申请批准
full按完全信任执行,不停下来征求批准;只有不可逆操作才先确认
  • trustByPeer 按发送方覆盖默认档(存 <tokenFile 目录>/trust.json,或用 POST /mesh/trust 修改,仅 loopback);
  • permissionPresetByTrust 把信任档映射到 DSH 权限预设,默认 strict → read-only、normal → workspace-write、full → danger-full-access;只有宿主的预设名不同或你想让某档更保守时才需要改;
  • normal/strict 下的「申请批准」走审批桥(approvalBridge,fail-closed、超时按拒绝),不是聊天问答;审批桥没启用时,危险操作会停在本机提示上等人处理。

任务回执(0.5.1)

/mesh/inject 可以带 meta.taskId(不污染正文):插件在该会话的回合结束时写恰好一条回执,一个 taskId 只结算一次;配合 dsh-mesh-dispatch 使用:

npx dsh-mesh-dispatch --title "<session-title>" --file task.txt --task T-20261029-hub
npx dsh-mesh-dispatch --title "<session-title>" --file task.txt --task --receipt queue

--task 不带值时自动生成 T-<base36 时间戳>;--receipt 可覆盖全局默认(并隐含开启 task)。

键默认说明
receiptModefilefile 只写 JSONL;queue 再把 [RECEIPT] 注入 receiptTo(mode=queue);wake 额外唤醒(需 wakeSecret,否则降级 queue 并记 WARN)
receiptFile~/.dsh/mesh-waker/receipts.jsonl追加写,一行一条 JSON(0600)
receiptTo空接收回执的会话:标题(如 <session-title>)或 session-…;为空则只写文件
receiptSummaryChars300摘要按码点截断,不会切出半个字符
receiptStallMs600000注入后这么久仍无 turn/start,判 not-started
startedClaimWindowMs30000baselineTurn=null(注入时还没有回合信息)时,turn/start 距注入不超过此窗口才认领 started;超窗保持 pending(不造进度)
receiptNoticeSummaryfalse注入的回执通知是否附带摘要正文。默认只发状态行(回执是完成信号,不是信件);error/lost 始终附一行 reasonDetail(≤120 字)供处置;摘要仍写入 receipts.jsonl、面板与任务 API 可查

回执字段:taskId / sessionId / turn / status / reasonKind / reasonDetail / injectedAt / endedAt / durationMs / summary / receiptMode / baselineTurn / settledTurn / injectedDuringTurn。status ∈ completed | aborted | blocked | error | max-tokens | interrupted | forked | not-started;reasonDetail 放 error.code 或 abort 原因。baselineTurn/settledTurn 是结算诊断:queue 注入发生在回合进行中时 baselineTurn 等于该回合,只有更晚回合的 turn/end 才会结算(settledTurn > baselineTurn),steer 则同一回合即可。注入给接收方的通知首行是 [RECEIPT] <taskId> <status> <durationMs>ms;0.7.28 起默认只有这一行(摘要留在文件/面板/API,避免回执被当成回信对刷)。status ∈ {error, lost} 时附一行 reasonDetail(≤120 字);需要旧行为(状态行 + 摘要)时设 receiptNoticeSummary: true。

回执投递给谁(0.7.7 / 0.7.24 收紧):优先投给派活方(requester),否则 receiptTo,都没有才只写文件。0.7.24 起 file 模式也会在 waker 日志留一行 receipt <id> <status> → file only (requester=有/无, receiptTo=有/无);两者都缺时额外写 WARN … nobody was notified。requester 缺失时按目标会话的 cwd 推断同工作区唯一的协调会话(标题以 · 协调/· coordinator 结尾):恰好 1 个才用,0 个或多个不猜(日志写 requester inference ambiguous);不会拿全局 receiptTo 跨工作区兜底。

  1. requester(/mesh/inject 的 body 字段或 mesh_dispatch 的工具参数,CLI --requester 会带)解析成功 → 只投给该会话;mesh_dispatch 被模型调用时若宿主把调用方 session 传给 handler(_extra/第二参数),自动用它;
  2. requester 解析失败 → 记录 lastError(requester not resolved … falling back to receiptTo)后回退 receiptTo;
  3. 都没有 → 只写文件,lastError 说明原因。

batch 聚合通知(以及 batch alert)投给该 batch 的第一个 requester;batch 内混有多个 requester 时,其余记日志与回执行(batch … mixed requesters)。/mesh/status 的 pendingTaskList 每行带 requester/requesterSessionId、injectedAtTurn/sameTurnInjected/baselineTurn,lastReceipt.deliveredTo 是实际投到的会话 id。

同一轮内注入多个任务(0.7.9 收紧):queue 任务的 prompt 各占一个后续回合,所以同一开放轮内先后注入的第二个任务会等到更晚的一轮(baselineTurn 按同轮队列位次+1),不会被第一个任务的 turn/end 带走;位次判断不确定时宁可保持 pending(等 receiptStallMs 报 not-started),也不发假回执。steer 仍按插入当前轮处理。

回执只走本机 inject,绝不推到 hub;receiptTo 解析不到时只写文件并置 lastError,不会去打扰别的会话。日志与回执里没有 token、密钥。

任务库(0.7.11)

派活的任务正文与生命周期会落盘,事后可查「这单到底要求了什么」,也能把 task → 回执 → 目标会话串起来。

存储(~/.dsh/mesh-waker/,0600)——任务与回执按「派活两端的最低公共祖先」分层(详见仓库 .worklog/docs-internal/storage-tiers.md):

  • workspaces.json:本机工作区注册表 {machine, workspaces: {slug: {cwd, coordinator, coordinatorStale?, roles, slug?}}};首次启动按会话索引的 cwd 自动生成,已存在时只增补新 cwd,不覆盖人工修改(GET /mesh/workspaces 只读查看)。slug 取 cwd 目录名,public / src / dist / app / main / build 这类泛化名改用父目录(…\dsh-mesh\public → dsh-mesh),人工写入 "slug": "xxx" 优先且重启不改;coordinator 取该 cwd 下「· 协调 / · coordinator」会话(允许编号,如「协调 2」「coordinator-3」)中最近活跃的一个,超过 7 天标 coordinatorStale: true 并 WARN;只含 *-selftest-* / test-* / *-test 会话且从无任务记录的临时工作区不进注册表(已有条目与空 ws/ 目录会被收敛清理)。启动会做一次幂等收敛:同一 realpath 去重、同 slug 合并、旧 slug 的 ws/ 目录迁移,日志 workspaces: converged merged=<n> ephemeral=<n> reslugged=<n>;无变化时标 (no changes);
  • 区内任务/回执写 ws/<slug>/tasks.jsonl 与 ws/<slug>/receipts.jsonl;同机跨区写 local/tasks.jsonl 与 local/receipts.jsonl;记录带 scope(workspace/machine)、origin、跨区时的 peers,无法判定归属时进 local/ 并标 scopeUncertain: true;
  • 旧版单文件 tasks.jsonl / receipts.jsonl 在 storageTiers 为 true 时首次启动自动拆分(先写临时文件再原子替换),完成后改名为 *.legacy.jsonl 保留;中途失败不改动旧文件。storageTiers: false 可关闭分层,compactTaskText: false 可关闭正文迁移;
  • tasks.jsonl 字段:taskId / batchId / thread / requester / targetSessionId / targetTitle / subject(正文首行 ≤120 字)/ textBytes / textRef / createdAt / deadlineMs / status / startedAt / settledAt / durationMs / receiptMode / receiptRef / receiptSummary / refs[] / reusedId? / scope / origin / peers? / workspace?(reusedId: true 表示同一 taskId 在 24 小时内重复注册;receiptMode 是投递时实际生效的回执模式,重启补发 lost 时据此通知等待方);
  • 正文按内容寻址存 texts/<sha256>.txt(0600,相同正文只写一次,不分层),jsonl 只记 textRef: "sha256:…";旧行若仍内嵌 text(或旧版 tasks/<taskId>.md),启动时会一次性迁移(迁移失败不修改 jsonl);
  • 保留策略:tasksRetentionDays(默认 30 天)与 tasksMaxRows(默认 5000 条),启动时对所有层合并后按「超龄或超量」裁最旧的,并压实各层文件(被裁任务的正文文件一并清理);
  • 读取聚合:GET /mesh/tasks、GET /mesh/batches、/mesh/status.pendingTaskList 与面板都读取「local/ + 全部 ws/*/」的聚合结果,对外行为不变;按 id 查找时先本工作区、再 ,都没有就明确说不存在;

API(仅本机 loopback):

端点用途
GET /mesh/tasks?status=&session=&batch=&limit=&offset=&include=text列表(跨层聚合,默认不含正文;include=text 时每条附 text(经 textRef/旧 text 解析);默认 limit=50、createdAt 倒序、返回 total;session 同时匹配目标会话与 requester)
GET /mesh/tasks/:id详情:全文 + 回执摘要(receiptSummary/receiptRef)+ 目标会话 {sessionId, title, live}
DELETE /mesh/tasks/:id仅 pending 可取消(→ cancelled);已 settled/stalled 返回 409,历史不改写
GET /mesh/workspaces本机工作区注册表(只读):{machine, count, workspaces}
GET /mesh/threads线程列表:{ thread, targetSessionId, targetTitle, count, lastTaskId, updatedAt, taskIds[] }(taskIds 从任务库派生,按 createdAt 升序)

线程(0.7.21):派活可带 thread(≤128 字节)把后续补丁归到同一线索。绑定写在 ~/.dsh/mesh-waker/threads.json(0600,只在成功投递后更新):{ "TH-1": { targetSessionId, targetTitle, lastTaskId, count, updatedAt } }。之后 mesh_dispatch({ thread, text }) 可省略 target,按绑定投递;未知 thread 直接报错并列出已知项(不猜、不回落到默认会话);显式传 target 时以 target 为准并改绑该线程(日志 thread TH-x rebound: old -> new)。回执仍按 requester 返回派活方。

状态流转(0.7.14 起含 started,0.7.26 起含 lost):派活写 pending → 目标会话的 turn/start 且该 turn 归属此任务时写 started(含 startedAt)→ 结算写 settled(补 settledAt/durationMs/receiptRef/receiptSummary);超时未动写 stalled(即回执的 not-started);DELETE 或批次取消写 cancelled。宿主重启后启动对账会把上一进程遗留的 pending/started(settledAt=null)标成 lost 并补发一条 lost 回执(摘要 session restarted before this task finished; resend if still needed),已结束的行不动。取消/停滞可以发生在 pending 或 started。durationMs 口径:任务库的 durationMs 从 startedAt 算起(未被观测到开始的从 createdAt 算),回执里的 durationMs 仍从投递时算。refs[] 由派活方经 meta.refs 传入(只存字符串,waker 不解析 .worklog)。

面板:任务面板在「Mesh 信箱」页面顶部标签「派活任务」里(与「信箱」并列);设置页只保留一行跳转说明。左右分栏:左栏按批次聚合(组头 B-xxx · done/total · 总耗时,可折叠;无 batchId 归「未分组」置底),行内 ID / 状态 / 耗时(人读) / 目标会话;右栏为选中任务详情(创建→开始→结束时间线、正文全文、回执摘要、refs、打开会话/取消任务),空态「选择左侧任务查看详情」;窄屏(<900px)退化为上下布局。顶部统计 pending/进行中/今日完成/停滞,支持状态与目标会话过滤;右栏 tab 顶部还有一条「派活任务:N 条待办/进行中」入口摘要。入口名可用 entryLabel 配置(默认 Mesh 信箱)。

「打开会话」依赖宿主 uiWorkspace:package.json 的 dsh.client.inject 必须包含 @deepseek-ai/dsh-client-ui-workspace(它提供 uiWorkspace 服务),client 运行时 inject 同时声明 uiWorkspace——两者缺一,ctx.uiWorkspace 都是 undefined。改完包声明后需要重启宿主才生效。target 直接传裸 session id(宿主 replaceMain(summary.id, …) 同样如此);成功会切到该会话并提示;会话已归档/跨工作区或宿主无导航时显示明确提示,并提供「复制会话 id」「复制任务标记 [TASK T-xxx]」两个退路。控件挂载/点击时会在控制台打印一次 [mesh-waker] uiWorkspace … 与 openSession target 便于排查。

两个「打开任务面板」入口(右栏 tab 的服务行、设置页「任务与回执」)走宿主 ctx.layout.selectPanel('mesh-inbox') 切主视图,并用共享 tab store 让页面直接落在「派活任务」标签;dsh.client.inject 因此还要包含 @deepseek-ai/dsh-client-ui-layout、运行时 inject 声明 layout(同样需重启生效)。导航不可用/切换失败时,按钮旁边会显示明确提示(右栏还带「复制入口」)。

设置页(0.7.13)

设置页按功能分五章、每章可折叠:接入(中枢登记/控制台/唤醒端口/接入向导占位)、唤醒行为(信任级别、投递方式、maxWakes、resetAfterHours、启动补收、投递模式、发送方指定、按设备信任)、界面(entryLabel、收件箱目录、本机会话归档)、任务与回执(回执状态、任务库保留策略、去任务面板)、维护(本地缓存、清缓存、轮换令牌、右栏状态)。

  • 可编辑:saveConfig 白名单内的字段(trust / delivery / maxWakes / resetAfterHours / catchUpOnStart / boundDeliveryMode / allowSenderDeliveryMode / workspacePath 等)在 UI 里直接改,保存走 POST /mesh/config,成功提示「已保存」、失败显示服务端原文;保存后重新拉 /mesh/status,控件值以服务端为准。
  • 只读:不在白名单里的项(如 entryLabel、tasksRetentionDays、tasksMaxRows)灰显并在 tooltip 标注「需改配置文件并重启」,不做成假可编辑。
  • 布局:每行 flex(min-height:32px、gap:16px),标题/说明 flex:1; min-width:0; overflow:hidden; text-overflow:ellipsis; white-space:nowrap,控件 flex-shrink:0,完整文本放行级 title。

批次与进度判定(0.5.2)

batch:一次派 N 单,全部结束才通知一次。dsh-mesh-dispatch 加 --batch [id](同 id 的多次调用归同一批,不带值自动生成 B-<base36>)与 --deadline 2h|90m|600s:

npx dsh-mesh-dispatch --title "<session-title>"  --file t1.txt --batch B-20261029 --deadline 90m
npx dsh-mesh-dispatch --title "<session-title>" --file t2.txt --batch B-20261029

批次内单任务不单独打扰(回执按 file 处理),只有聚合回执走 barrierMode:

[BATCH] B-20261029 3/3 done in 42m
✅ T-114 hub bootstrap        8m  completed
❌ T-115 waker pull          12m  error(provider_error)
⏳ T-116 docs onboarding         not-started

JSONL:{kind:"batch", batchId, tasks:[{taskId,status,durationMs,summary}], startedAt, endedAt, done, total}。deadline 到点仍有未结 → 发一次「部分完成」聚合,未结项带当前 liveness;随后清批次。

liveness:每个会话从事件流派生状态(session/event),判定顺序固定:

state判据detail 示例
not-started有 pending 任务但从未收到 turn/startno turn/start since injection
lost插件重启时 pending 仍开着(多半打断了回合)plugin restarted while the turn was open
waiting-approval有未决 approval/askedbash 2m
waiting-job有未结算 job/registeredjob-7 1m
waiting-tool有未返回 tool/callread_file 3m
stalledturn 开着、无 job/approval/tool 且静默超过 stallQuietMsquiet 6m (turn 4)
idleopenTurn === null—
working其余(turn 开着且有近期事件)turn 4

批次进行中,某任务处于 stalled/waiting-approval/waiting-job 且已挂起超过 waitLongMs → 发一次 [BATCH-ALERT](同批次同任务同状态只发一次),不结算该任务,批次继续等 turn/end 或 deadline。

键默认说明
stallQuietMs300000静默多久判 stalled
waitLongMs1800000挂起多久发「挂了很久」提醒
batchDeadlineMs7200000批次兜底 deadline
barrierModewake聚合回执投递方式 `file
alertModequeue中途告警方式
batchFile~/.dsh/mesh-waker/batches.json批次持久化(0600,启动恢复,>24h 清理)

HTTP(loopback):GET /mesh/batches、GET /mesh/batches/<id>(含每个任务的 liveness)、DELETE /mesh/batches/<id>(取消,不再回执)。

本机会话协作(不经过中枢)

同机、同一个 DSH 里的多个会话可以直接互相派活:waker 在 127.0.0.1:8795 暴露会话发现与注入接口,全程本地、毫秒级、离线可用。所有端点只接受 loopback 请求。

端点作用
GET /mesh/sessions列出本机会话(按最后活动排序):id / title / cwd / workspace / last_active / live / archived。分页参数 limit(默认 80,夹取到 1–500)、offset(默认 0)、archived(1/0/省略)、cwd(精确匹配)、q(标题子串,大小写不敏感);响应含 { ok, total, limit, offset, sessions },total 是过滤后总数。?live=1 仍只返回未归档会话
POST /mesh/inject向已有会话投一轮任务:按 session(完整 id)→ 精确标题 → 标题唯一子串寻址;歧义或找不到返回 400。mode 为 queue(默认,等当前回合)或 steer(打断当前回合)
POST /mesh/sessions/create新建会话并绑定工作区,可选首轮 prompt;返回 sessionId / workspaceId / workspacePath / prompted
POST /mesh/sessions/archive归档会话:日志保留、侧栏隐藏;存活的会话会被宿主拒绝(workspace/session-active)
POST /mesh/sessions/delete删除会话:支持 session(id,可省略 session- 前缀)或 title(精确/唯一子串)寻址,标题歧义返回 400 并列出候选;先停掉在跑的 agent(sessionController.cancel + 宿主 dispose 钩子),摘掉 conversations.json 里指向它的绑定,再删会话目录;停不下来的返回 409,不会假报成功

可直接复制:

# 1. 看看有哪些会话
curl.exe -s http://127.0.0.1:8795/mesh/sessions

# 2. 给某个会话派活(按标题寻址;同名会 400 而不是随便挑一个)
curl.exe -s -X POST http://127.0.0.1:8795/mesh/inject -H 'content-type: application/json' -d '{"title":"hub 文档维护","text":"列出 README 与实现不一致处,只列不改"}'

# 3. 新建执行会话并立刻投首轮任务(cwd 指向具体仓库根)
curl.exe -s -X POST http://127.0.0.1:8795/mesh/sessions/create -H 'content-type: application/json' -d '{"title":"<session-title>","cwd":"<workspace>","prompt":"先读 hub/README.md,列出与 docs 不一致处"}'

# 4. 归档 / 删除(都按完整 id)
curl.exe -s -X POST http://127.0.0.1:8795/mesh/sessions/archive -H 'content-type: application/json' -d '{"session":"session-…"}'
curl.exe -s -X POST http://127.0.0.1:8795/mesh/sessions/delete -H 'content-type: application/json' -d '{"session":"session-…"}'

delete 只删 waker 能发现的会话(目标必须出现在 GET /mesh/sessions 里),不接受路径;inject、archive、delete 都支持 session(id)或 title(精确 / 唯一子串)寻址,歧义时返回 400 并列出候选。

细节与约定见 docs/local-sessions.md;端点验收:node test/waker-endpoints.test.js。

多会话分工(任何项目可用)

派活能力长在插件里:装了 waker 的项目,模型可以直接调 mesh_dispatch 工具,CLI 也行——不需要本仓的 dsh-mesh-dispatch。

3 步示例(任何项目根目录):

# 1. 看本机有哪些会话(?live=1 只看未归档;id/title/archived 都在返回里)
curl.exe -s "http://127.0.0.1:8795/mesh/sessions?live=1"

# 2. 派活(带 taskId 才会写回执;同一批用同一个 batchId,全部结束只叫一次)
dsh-mesh-dispatch --target "<session-title>" --file task.txt --task --batch B-20261030 --deadline 90m
# → sent session=session-… task=T-mf3k… batch=B-20261030

# 3. 读回执(默认 ~/.dsh/mesh-waker/receipts.jsonl;批次的聚合行 kind="batch")
tail -n 2 ~/.dsh/mesh-waker/receipts.jsonl

模型入口(工具)参数与 CLI 一一对应:

工具参数CLI说明
target(必填)--target会话 id、完整标题或唯一标题子串
text(必填)--text / --file原文投递,≤64 KiB;注入时正文前会加一行 `[TASK · THREAD <id
createIfMissing--create解析不到时按 target 当标题新建会话再投递(新会话用 sessionCwd/默认工作区)
mode--modequeue(默认,等当前回合)或 steer(打断)
taskId--task [id]不传自动 T-<base36>;不带值也自动生成
batchId--batch [id]同批次 barrier:全部结束只回执一次
receipt--receiptfile/queue/wake,覆盖插件默认
deadline--deadline30m/2h/600s,默认 batchDeadlineMs
subject(CLI 暂缺)任务库/面板显示的可读标题(≤120 字);省略时由正文首行清洗(优先加粗片段、去掉 任务(…):/【…】 前缀与 **)。目前只有工具参数支持
thread--thread线索 id(≤128 字节,如 TH-1):已知 thread 可省略 target,后续补丁续派到同一会话;显式 target 会改绑
requester--requester回执收件人:会话 id 或标题,服务端解析(CLI 原样透传,不自己解析)。不传时 waker 先按目标会话的 cwd 推断同工作区协调会话(唯一或明显最新者),推断不出才回退 receiptTo;receipt 为 file(默认)时只写 receipts.jsonl,不注入任何会话

工具描述里明确写了:投递后不要轮询等待,插件会在回合结束时写回执;要「全部完成再叫我」就用同一个 batchId。

任务 ID 会随正文交给会话:注入到目标会话的正文前有一行 [TASK <id> · THREAD <id|-> · BATCH <id|-> · FROM <requester>] <本地时间> 表头(默认开启,injectHeader: false 关闭),正文本身逐字不变;会话可以直接在自己的账本/WORKLOG 里引用该 taskId/thread,不用再从派活正文里猜。

worker 侧线程工具(0.7.23,mesh_thread):action=list(scope=mine|all)看绑定;action=subscribe, thread=TH-1 把调用方会话绑到该线程(已被别的会话占用 → 拒绝并报占用者,不抢);action=release, thread=TH-1 只有当前绑定者能解绑,解绑后该线程从 threads.json 移除——未来不带 target 的派活会报 unknown thread,即「交回协调」语义。调用方会话来自宿主传入的 caller(extra.agent.id/sessionId),拿不到就明确报错,不猜默认会话。同一逻辑也暴露为 loopback HTTP:POST /mesh/threads/subscribe、POST /mesh/threads/release(body {thread, sessionId});GET /mesh/threads 每条新增 subscribedBy(dispatch/self)与 taskIds[]。

worker 侧完成声明(0.7.31,mesh_complete):被派活的会话可主动声明结果——mesh_complete({taskId, status: 'done'|'partial'|'blocked', summary?, detail?})。命中属于调用方会话的在飞任务才结算:done→completed、partial→partial、blocked→failed(detail 成为回执 reasonDetail),summary 用会话自己写的(≤300 字,只落 receipts.jsonl),回执 source=declared;未知 taskId、别人的任务、已结算任务都明确报错(已结算返回 alreadySettled:true,不重复投递)。未声明的任务仍按 turn/end 自动结算,回执 source=observed。HTTP 侧:POST /mesh/tasks/complete(body {taskId, status, summary?, detail?, sessionId})。

自查工具是否注册成功:GET /mesh/status 的 tools 字段给出 {dispatch, ask, thread, complete} 四个布尔值,失败原因在 toolsError(例如 host exposes no tools service)。工具注册会等待宿主 tools 服务就绪(最多重试 5 次、每次 500ms),每次成功/失败都写一行到 waker 日志;模型说「工具未注册」时先看这些字段,不要猜。

# 新建一个执行会话并投第一单(--create 的典型用法)
dsh-mesh-dispatch --target "worker-a" --create --text "先读 README,列出与实现不一致处" --task --batch B-a

CLI 只依赖 Node 内置 fetch,通过 --waker 指向本机 waker(默认 http://127.0.0.1:8795);连不上时退出码 2。

主题路由(topics,0.5.7)

远程不掌握本机的会话 id,也不该掌握:用稳定标签投递,由 waker 在本机解析成当前匹配的会话。

# cordis.patch.yml / 插件配置
topics:
  - label: "ops/frontend"       # 对外的稳定标签,也用作 conversation
    titlePattern: "web 运维"     # 本地解析:标题唯一子串(也接受精确标题)
    allowFrom: ["dsh-kaniol-pc"] # 可选:只允许这些 peer 投递;空/缺省=不限制
    mode: "queue"                # 可选,默认 queue;steer 插当前轮

远端(跨机)发消息时把 conversation 写成标签即可:

POST /api/send { "to": "<本机 agent 名>", "conversation": "ops/frontend", "body": "…" }

行为与边界:

  • 自动绑定/重绑:启动时和每次投递前按 titlePattern 找未归档会话(精确或唯一子串);绑定的会话被归档/删除后,下次投递自动改绑到新会话。解析不到只记 topic unresolved label=… pattern=…,不报错,落回原有「收件箱 / 按 conversation 新建」兜底。
  • 不覆盖手工绑定:/mesh/bind 绑到仍存活会话时,topic 不会改动它。
  • allowFrom:非空时只允许名单里的 sender;其他人投递不注入、消息留在收件箱未读,本地日志记 topic denied label=… peer=…。这是投递闸门,不是收件箱规则——agent 仍能在收件箱看到它。
  • 标签校验:^[a-z0-9][a-z0-9/_-]{1,40}$;非法或重复的 label 启动时 WARN 并忽略该条。
  • 对外最少暴露:GET /mesh/status 只给 topics: [{ label, bound, lastDeliveredAt }],不含会话 id/标题(本地细节看 /mesh/admin/bindings)。会话 id 是内部句柄且易失,运维会话能执行 shell,暴露 id 等于开放提权面。
  • 零配置兼容:不配 topics 时行为与 0.5.6 完全一致。

远程审批(approvalBridge,0.6.0)

人不在电脑前时,让 DSH 的审批请求走 mesh:危险命令触发 approval/request → waker 给 owner 的 peer 发一条静默消息 → owner 回 approve / deny → 命令放行或被拒。

approvalBridge:
  enabled: true                 # 默认 false:不配就完全不接管(零影响)
  to: 'dsh-kaniol-pc'           # 审批请求发给谁
  allowFrom: ['dsh-kaniol-pc']  # 只接受这些 sender 的回复;空=只通知、不接受回复
  timeoutSeconds: 120           # 超时后 next() 交回本地提示框,绝不自动放行
  conversationPrefix: 'approval/'
  pollMs: 2000
  diag: false                   # true 时打印请求字段名/类型(不含命令全文),用于核对内核形状

消息长这样(conversation: approval/<callId 前 8 位>,silent: true):

【审批请求】bash · 会话「mesh · hub」
摘要:run a dangerous command
request_id:call-12345678
回复方式:approve 放行 / deny 拒绝(只回一个词即可)。
120s 内未回复则转本地提示框;超时不会自动放行。
  • 判定:approve/allow/同意/批准/放行 → allowed-once;deny/reject/拒绝/不同意/驳回 → rejected;同时出现两边词或其它内容 → rejected(fail-closed)。
  • 超时:一律 next() 透传本地提示框——不自动放行,也不静默拒绝。无人值守时误杀一个长任务比多问一次更糟。
  • 为什么 silent:唤醒 agent 等于把「批准权」交给可能被注入的 agent;silent 只落收件箱,allowFrom 之外的人回复会被忽略并保持未读。
  • 防自批:审批会话(approval/ 前缀)由 bridge 独占消费,pull 循环不会把它注入任何会话;bridge 正在等待的会话会跳过消费,避免抢答。
  • 审计:每次决定写一行 approval decision call=… by=… outcome=… ms=…,不含命令全文。

向人提问(mesh_ask / questionBridge,0.7.0)

提问是商量,不是安全闸门:危险命令放行仍走 approval(fail-closed);mesh_ask 只承载「发不发版 / 选哪个方案 / 继续还是停」这类协调决策,超时返回未回答,绝不替调用者挑默认值。

questionBridge:
  enabled: true                # 默认 false;控制 user-questions 接缝与默认收件人
  to: 'dsh-kaniol-pc'
  allowFrom: ['dsh-kaniol-pc'] # 只接受这些 peer 的回复;空=不等待,直接 unanswered
  timeoutSeconds: 300          # 1–3600(建议 ≥30)
  pollMs: 2000
  conversationPrefix: 'question/'
  diag: false

模型侧直接调工具(也可只传 to,不依赖 enabled):

mesh_ask({
  question: '这版要不要现在发到 npm?',
  options: ['现在发', '等 CI 绿了再发', '先不发'],
  timeoutSeconds: 300,
})
// → { status: 'answered', answer: '等 CI 绿了再发', by: 'dsh-kaniol-pc', ms: 41230, questionId: 'q-…' }
// 超时 → { status: 'unanswered', timeoutSeconds: 300, questionId: 'q-…' }
  • 消息:silent: true(不唤醒任何 agent)、conversation = question/<id 前 10 位>,正文是问题 + 编号选项 + 「回 1/2/3 或直接回复文字」+ 超时说明。
  • 回答:纯数字命中选项 → 返回该选项文本;否则原样返回文字(去首尾空白、截 2000)。
  • 超时:unanswered,不带任何选项;调用方自己决定怎么继续。
  • 隔离:question/ 会话由提问工具独占消费(claim + read),pull/push/catch-up 都不注入、不建会话;桥在发送之前就登记该会话,避免消息落库瞬间被常规 wake 抢走。allowFrom 外的回复忽略并保持未读(日志 question ignored,按消息去重)。
  • 遗留清理:历史上被误建的 mesh · question/… 会话(0.7.3 之前)请运营者手工归档;插件不会自动删除,以免误删真实会话。
  • 可选接缝:运行时若存在 ctx.userQuestions 服务才挂 user-questions/request(形状不认识或超时一律 next() 交回本地 UI);没有接缝只记一行 user-questions seam absent; mesh_ask only,工具照常可用。插件不 import @deepseek-ai/dsh-user-questions 或 dsh-user-approval,缺包不影响加载。

/mesh/status 的待办字段(0.7.2)

运营者不用翻日志就能发现「现在有东西在等我」,GET /mesh/status(仅本机)新增:

字段形如说明
pendingQuestions[{conversation, askedAt, expiresAt, preview, options}]正在等待回答的 mesh_ask 问题;preview 单行 ≤80 字符;回答或超时后自动消失;无则为 []
pendingApprovals[{conversation, askedAt, expiresAt, preview, toolName}]正在等待决定的审批请求;preview 形如 bash · <摘要>;无则为 []

两个字段都只在本机 status 输出里;conversation 可以直接用来在收件箱里定位原消息(问题/审批消息本身仍按各自桥的隔离规则处理)。

日志与取证(0.7.5)

  • 路径:默认 <tmpdir>/mesh-waker.log(重启/清临时目录会丢);生产建议 logFile: ~/.dsh/mesh-waker/waker.log。
  • 滚动:单文件超过 logMaxBytes(默认 2 MiB)时改名为 <logFile>.1,新文件从空开始;只保留 1 份历史,不引第三方库。
  • 状态:/mesh/status 的 logFile / logBytes 可直接确认「插件在写、写在哪、多大」。
  • 定位重复报错:
grep -c "pull failed" ~/.dsh/mesh-waker/waker.log     # 同类错误出现次数
tail -n 50 ~/.dsh/mesh-waker/waker.log                # 最近上下文

面板操作(0.7.6)

设置页「本机会话」列表(数据来自 GET /mesh/sessions):

  • 归档可见:archived: true 的行置灰(opacity 0.55)并显示「已归档」标签,不给归档按钮;
  • 归档操作:活跃会话行有「归档」按钮,调 POST /mesh/sessions/archive,成功后刷新列表;
  • 删除只走 CLI:面板不提供删除,避免误删会话目录。需要删除时用 curl.exe -s -X POST http://127.0.0.1:8795/mesh/sessions/delete -H 'content-type: application/json' -d '{"session":"session-…"}' (标题寻址也支持,歧义会 400 并列候选)。

已知限制(宿主无归档移除 API):插件可见的 ctx.workspaceRegistry 只有 archiveSession / deleteKnown / archivedSessionIds,没有 unarchive 或从归档集合删除 id 的接口(unarchiveSession 只在 workspace-controller 的命令/远程面,插件拿不到)。因此删除一个已归档会话后,宿主归档集合里会永久留着那个 id;waker 用本地 tombstone(~/.dsh/mesh-waker/archived-tombstones.json,0600)在 /mesh/sessions 输出里把它减掉——删除后归档视图不再显示该 id;若同一 id 被重新归档,tombstone 自动清除。

依赖

中枢本体见 hub/,Docker 一条命令即可起:

cd hub && cp .env.example .env && docker compose up -d --build

许可

MIT

跨机消息绑定(/mesh/bind)

这条路径只服务跨机来件:hub 把消息 ping 给 waker,若该 conversation 有 deliverTo,waker 直接把 wake prompt 投进那个会话(默认 mode: 'queue',像用户打字一样排队),而不是另起/复用「Mesh 收件箱」会话。

本机会话之间不要用这套。 同机派活走 /mesh/inject(命令行:npx dsh-mesh-dispatch --title "<session-title>" --file task.txt,需要唤醒时加 --wake)——毫秒级、离线可用,不必让消息绕中枢一圈。

POST /mesh/bind

{ "conversation": "conv/wake-verify", "sessionId": "session-…", "deliver": true, "backfill": 12 }
字段说明
conversation必填,≤120 字节,与对方 mesh_send 用的 conversation 一致
sessionId必填,本机会话 id(GET /mesh/sessions)
deliver默认 true;false 只做面板展示绑定(写 panelSessionId),不投递
backfill可选,绑定时把该 conversation 最近 N 条补投进会话(默认 12,仅 deliver:true 时生效)

响应:{ "ok": true, "conversation": "conv/wake-verify", "sessionId": "session-…", "deliverTo": "session-…", "backfilled": 0 }

投递失败(会话已删除/归档、prompt 被拒)会自动清掉 deliverTo 并回退到「Mesh 收件箱」会话,邮件不会丢;此时 POST /mesh/admin/bindings 里该条的 deliverTo 会变成 null,这是排查的第一现场。

GET /mesh/admin/bindings

{
  "ok": true,
  "self": "dsh-kaniol-pc",
  "bindings": [
    { "conversation": "conv/wake-verify", "panelSessionId": "session-…", "deliverTo": "session-…", "wakes": 1, "title": "conv/wake-verify" }
  ]
}

只列 panelSessionId 或 deliverTo 非空的 conversation;wakes 是该 conversation 的累计投递次数(每次成功投进绑定会话 +1)。

POST /mesh/admin/unbind

{ "conversation": "conv/wake-verify", "keepDisplaying": false }

清掉绑定;keepDisplaying: true 只清 deliverTo、保留面板展示绑定,默认两个都清。响应 { "ok": true, "conversation": "conv/wake-verify", "entry": { … } }。

以上管理端点都只接受 loopback;绑定的读写落在 token 同目录的 conversations.json,重启后保留。

端到端自检

  1. POST /mesh/sessions/create 建一个临时会话(cwd 指向具体仓库根);
  2. POST /mesh/bind 把 conv/x 绑到它;
  3. 从另一台机器或另一身份发:mesh_send({ to: "<本机>", conversation: "conv/x", body: "…" });
  4. 判据:GET /mesh/admin/bindings 中该条 wakes +1,临时会话 last_active 前进,且会话日志里出现带消息正文的 wake prompt;
  5. 清理:POST /mesh/admin/unbind → 归档/删除临时会话 → /mesh/admin/bindings 无残留。

常见「投了但没落到绑定会话」:正文 <24 字且是 ack 词(收到/ok/ping…)被 trivial 拦;同一 conversation 60 秒内的重复 ping 被 cooldown 吃掉;waker 正忙(busy)或该 conversation 触顶(默认 30 次/小时)。命中时会写进 waker 日志与 /mesh/status 的 throttled。

与中枢控制台的分工

设置 → Mesh 只管理本机:唤醒(trust / 投递方式 / 启动补收)、收件箱与历史缓存、会话绑定、本机会话(/mesh/sessions 系列)。它不代理任何中枢管理请求。

管理动作留在中枢自带控制台 hubUrl/console:发注册码、批准新节点、轮换/吊销令牌、改 scopes、看审计。面板里只有一行只读的「中枢控制台 → 打开」:/mesh/status 的 hubConsoleUrl 由 hubUrl 去尾斜杠后拼 /console 得到,按钮就是 window.open(hubConsoleUrl, '_blank', 'noopener,noreferrer');没有 hubUrl 时该字段为 null,整行不渲染。

为什么不把 admin token 放进插件:

  1. admin token 是全权——能轮换/吊销任何 agent、改 scopes、发注册码,泄漏一次等于中枢失守;
  2. 面板走 loopback 且无鉴权,同机任何进程都能读它的状态接口,把 admin token 塞进来等于把全权面交给本机所有程序;
  3. 插件的 cordis.patch.yml 是明文落盘(还有各种 .bak),写进去就长期存在,也容易随备份、截图或排障日志外流。

管理动作一律在控制台完成,它自己有登录会话与 CSRF 校验;插件这边只保留只读跳转。

local/
  • 正文隐私:日志只打 textBytes 与 subject,从不打印正文;列表 API 默认不含正文,只有详情 API(或 ?include=text)经解析器返回全文。
  • 日志分层(0.7.31,与任务同构):全局事件(启动、注册、同步、无归属错误)进 local/logs/YYYY-MM-DD.log;某工作区会话/任务的事件进 ws/<slug>/logs/YYYY-MM-DD.log。文件按本地日期每天一个(跨零点自动换文件),单文件超 logMaxBytes 时追加序号 YYYY-MM-DD-2.log;启动时按 logsRetentionDays(默认 30)清理过期按天文件;旧 waker.log 不逐行拆分,直接保留为 local/logs/legacy-waker.log。registered … / history sync … 两类固定心跳只在内容变化时立即记,未变化时每小时记一次 (unchanged, <n> repeats);其它事件照常逐条记录。排查用只读 GET /mesh/logs?day=&scope=all|local|<slug>&tail=N 拿跨层合并视图(每条带 file/scope)。