@shgroup/dsh-serenity-hooks 1.30.9 → 1.30.11

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,50 +1,353 @@
1
- # @shgroup/dsh-serenity-hooks
1
+ # 宁静号 ACC —— 给 DeepSeek Harness 装一个「AI 工作区」
2
2
 
3
- 宁静号 ACC(Abstract Cognitive Container)harness — **DeepSeek Harness 原生 Cordis 插件**(npm 发布单元,v1.30.0)。
3
+ > **一句话**:装上这个插件,你在电脑上给 AI 划一块自己的工作区(就是一个普通目录),
4
+ > AI 在里面干活时就有了**记忆、纪律、工具和边界**——中途换模型、重启电脑、第二天再来,都能接着干。
5
+ >
6
+ > 适用 [DeepSeek Harness](https://github.com/deepseek-ai/deepseek-harness)(下称 DSH)0.1.2-rc.1 及以上。
7
+ > 想了解背后的想法(为什么叫"认知容器"),看 [docs/cognitive-container-theory.md](https://github.com/tellmewhattodo/dsh-serenity-plugin/blob/master/docs/cognitive-container-theory.md);本文只讲**能干什么、怎么用**。
4
8
 
5
- 为 DSH 会话提供认知容器基础设施:真实 DSH 工具(10 个)+ 拦截缝机械约束(safe-mode / 路径守卫 / 凭据守卫 / 输出守卫)+ 系统提示词注入(8 块,对齐 opencode-serenity-plugin)+ WebUI 状态胶囊 + 外部访问(双端口网关 / Skiff 问答 / ACP+Skiff 问答页)+ 微信桥(F4c-3 iLink 接入)+ Autopilot Trajectory(时钟驱动自主巡航)。
9
+ **几个词先说明白**(后面都用这几个词,不再解释):
6
10
 
7
- ## 安装
11
+ | 词 | 说人话 |
12
+ |---|---|
13
+ | **工作区 / CCC** | 一个目录,里面放一个 `.serenity` 空标记文件。DSH 在别的目录里跑,插件完全不插手;一旦你进到这种目录,它自动生效 |
14
+ | **插件 / ACC** | 就是这个仓库(npm 包 `@shgroup/dsh-serenity-hooks`)。它给 DSH 加工具、加约束、加记忆 |
15
+ | **MSM** | 你写在工作区里的可执行小工具(一个脚本 + 一行注册)。AI 通过 `msm("名字", ["参数"])` 调用它们 |
16
+ | **SESSION.md** | 工作区里的"工作日志"。AI 把目标、决定、进度写进去,中断后再来就从这里接着干 |
17
+
18
+ ---
19
+
20
+ ## 1. 它到底解决什么问题
21
+
22
+ 不吹概念,直接说四个每天都会遇到的麻烦:
23
+
24
+ | 麻烦 | 没有它 | 有了它 |
25
+ |---|---|---|
26
+ | **AI 干到一半忘了自己在干什么** | 上下文一满,之前的目标、决定全丢,你得重讲一遍 | 每个工作区有一本工作日志,AI 每推进一段就写进去;上下文满了就"换载体"重来,日志还在原地,接着干 |
27
+ | **AI 到处乱翻、乱改文件** | 它能读你整台机器的文件,包括密钥 | 工作区有围墙:墙内随便用,墙外一律拒绝;密钥文件连"读"都读不到 |
28
+ | **出门在外想用** | 只能坐在那台电脑前 | 自带一个带登录的网页入口(密码或手机验证码二选一),手机也能用 |
29
+ | **家人想用微信问点事** | 得教他们装软件、开电脑 | 扫一次码把微信接上,家人在微信里说话,AI 用你定义的人格回话 |
30
+
31
+ ---
32
+
33
+ ## 2. 快速开始(2 分钟)
34
+
35
+ 前置:Node ≥ 20(或 bun)、DSH 0.1.2-rc.1 及以上。
8
36
 
9
37
  ```bash
38
+ # 1. 装插件(自动加入 DSH 的 web profile)
10
39
  dsh plugin --profile web add @shgroup/dsh-serenity-hooks
40
+
41
+ # 2. 重启 dsh web(插件和网页端界面一起生效)
42
+ dsh web
43
+
44
+ # 3. 验证:进到带 .serenity 标记的目录里开一个会话
45
+ # · 会话开头会自动带上这个插件的身份说明和技能目录
46
+ # · 网页端会话标题旁出现一个状态胶囊(绿点常亮 + SAFE 滑块)
47
+ # · 输入 dashboard health,看到工作区三项检查全通过
48
+ ```
49
+
50
+ 卸载:`dsh plugin --profile web remove @shgroup/dsh-serenity-hooks`
51
+
52
+ 从源码装(自己改代码时用):
53
+
54
+ ```bash
55
+ git clone https://github.com/tellmewhattodo/dsh-serenity-plugin.git
56
+ cd dsh-serenity-plugin
57
+ dsh plugin --profile web add link:$(pwd)/hooks/dsh-serenity-hooks
58
+ ```
59
+
60
+ > ⚠️ 别用 `dsh plugin add github:...` 这种写法——那个地址指向的是仓库根目录(不是插件包本身),装上了也不会生效。用 npm 或上面的 `link:` 方式。
61
+
62
+ **安全模式**:点一下网页端胶囊里的 SAFE 滑块,`bash` 就会从 AI 的工具列表里**直接消失**(不是报错,是它根本看不到这个工具)。于是 AI 只能走你注册过、测试过的小工具通道。这个开关是给你用的——AI 看不见、也打不开。
63
+
64
+ ---
65
+
66
+ ## 3. 装完之后你多了什么
67
+
68
+ ### 3.1 十个工具
69
+
70
+ | 工具 | 干什么 | 什么时候用 |
71
+ |---|---|---|
72
+ | `container_fs` | 在工作区里管文件:列目录、找文件、复制、移动、新建、追加、在文件管理器里打开 | 需要看/整理工作区里的文件时 |
73
+ | `logbook` | 工作日志的全生命周期:新建、查看、切换、关闭、归档,还有"原地重建" | 任何多步骤的活儿,第一步就是它 |
74
+ | `dashboard` | 仪表盘:工作区健康检查(三项)、当前时间、等待 | 进工作区先自查一下;等外部服务时用 |
75
+ | `container_git` | git 操作:status / commit / push / log / pull / diff | 提交和推送代码;它绝不自动强推 |
76
+ | `msm` | 小工具执行入口:`msm("名字", ["参数"])`;名字记不全就给候选;`inspect=true` 看用法 | 调用工作区里注册的任何小工具 |
77
+ | `praxis` | 按需给 AI 注入三套"做事方法":输出自检(eap)、设计对齐(neat)、认知连续性(cce) | 要它把话说清楚 / 先对齐再动手时 |
78
+ | `handyman` | 杂工:派一个便宜模型的助手,循环干活直到完成;也可以一次派多个并行 | 大批量、重复性的活(比如扫描几十个技能) |
79
+ | `localstore` | 存密钥和配置(凭据、偏好两个命名空间) | API key、密码集中放一处,不进 git |
80
+ | `container_admin` | 机务舱:管理子角色、管理小工具注册表、查看全部配置 | 定义"子角色"、注册新小工具时 |
81
+ | `autopilot-trajectory` | 自动巡航:定时唤醒工作区、注入当前焦点,支持多个工作区各自独立 | 想让 AI 定时自己干活(见 §6.5) |
82
+
83
+ > **改过名**(旧名已彻底停用,没有兼容别名):`cc_fs` → `container_fs` · `cc_git` → `container_git` · `session`+`session_rebuild` → `logbook` · `acc_kit` → `dashboard` · `acc_msm` → `msm`(执行)+ `container_admin`(管理)· `eap`/`neat`/`cce` → `praxis` · `skiff_admin` → `container_admin role`。老会话里看到旧名,照这张表对照即可。
84
+
85
+ ### 3.2 机械约束(AI 绕不过去)
86
+
87
+ 这些不是"提示词里劝它别做",而是**机制上做不到**:
88
+
89
+ | 约束 | 你会看到的效果 | 为什么这样做 |
90
+ |---|---|---|
91
+ | **安全模式** | bash 从工具列表里消失(每一步都同步一次),就算它想调也会被兜底拦下 | 走注册过的小工具,比让 AI 自己拼 shell 命令可靠得多 |
92
+ | **工作区围墙** | 墙内什么都能干,墙外一律拒绝(连路径都解析不出去) | AI 不该碰工作区之外的东西 |
93
+ | **黑名单 / 治理文件** | 可配黑名单;`.serenity` 这类治理文件禁止 AI 写 | 防止 AI 把自己所在的"地基"改坏 |
94
+ | **密钥文件守卫** | `localstore.json` 对**所有**工具拒绝(包括 read/grep/glob) | 密钥值在结构上就出不来 |
95
+ | **对外输出守卫** | 对外面的会话(子角色 / ACP / 重建会话)如果答出敏感词,会被打回重答,并告诉它命中了哪个词、该怎么改 | 外面的人不该看到内部机制 |
96
+ | **轨迹提醒** | 做久了会提醒 AI"把进度写回工作日志",并要求它回一个确认码 | 提醒是机制,不是靠自觉 |
97
+
98
+ ### 3.3 对外的入口
99
+
100
+ | 入口 | 默认端口 | 给谁用 |
101
+ |---|---|---|
102
+ | DSH 主界面 | 3080 | 你自己在本机用(插件不碰这个端口) |
103
+ | **网页登录入口** | 3081 | 外部/手机访问完整界面:登录后反向代理到 3080,可配工作区白名单 |
104
+ | **微信主动发送入口** | 3082(只绑 127.0.0.1) | 工作区里的小工具用它主动给微信发消息(公网到不了,所以不需要密钥) |
105
+ | **子角色调试页** | 3099(只绑 127.0.0.1) | 你调试"子角色"时用,能切换工作区、看对话轨迹 |
106
+ | **ACP + 对外问答页** | 3100(只绑 127.0.0.1) | 程序化接入(JSON-RPC)+ 给别人用的问答页(key 认证,只返回答案,不返回内部轨迹) |
107
+ | **微信桥** | 无需端口(出站长轮询) | 家人在微信里直接和 AI 说话 |
108
+
109
+ > 默认只监听 127.0.0.1 的入口,要暴露到公网由你自己决定(隧道 / 反代 / 端口映射都行),插件不绑定任何特定做法。
110
+
111
+ ---
112
+
113
+ ## 4. 一个工作区长什么样
114
+
115
+ 工作区就是一个普通目录,加一个标记文件:
116
+
117
+ ```
118
+ my-workspace/ ← 工作区根目录(放一个 .serenity 就成)
119
+ ├── .serenity ← 标记:这个目录是一个工作区
120
+ ├── .opencode/
121
+ │ ├── serenity.json ← 工作区级配置:助手模型白名单 / 日志阈值 / 子角色
122
+ │ └── skills/ ← 领域技能(每个技能 = 一个领域的知识 + 可能有小工具)
123
+ │ ├── home-media/ ← 例如:媒体(找片源 / 做字幕 / 推送)
124
+ │ ├── home-wealth/ ← 例如:家庭财务
125
+ │ └── …(每个技能可以自带脚本)
126
+ ├── AGENT_SESSIONS/ ← 工作日志库:每个目录一本 SESSION.md
127
+ │ └── 2026-09-08--S142--xxx/
128
+ │ └── SESSION.md ← 目标 / 决定 / 进度(永远留在这里,不会被搬走)
129
+ └── _tmp/ ← 运行时落盘:你粘贴的图片和文件
130
+ ├── images_from_user/
131
+ └── files_from_user/
132
+ ```
133
+
134
+ ---
135
+
136
+ ## 5. 能拿它做什么(12 个真实用例)
137
+
138
+ > 下面这些都在真实部署里跑着。地址、账号、路径都做了泛化。
139
+
140
+ | # | 你想干的事 | 实际怎么走 |
141
+ |---|---|---|
142
+ | 1 | **长期项目不断线** | `logbook create` 建日志 → 每推进一段写进去 → 中断后 `logbook use` 接上 → 上下文满了 `logbook rebuild` 原地重建并自动继续 |
143
+ | 2 | **批量同步代码** | 当前仓库 `container_git commit/push`;多个子仓库一条命令全同步(自动提交 + 推送) |
144
+ | 3 | **做一集字幕** | 搜片源 → 下载 → Whisper 转写 → 翻译 → 双语 SRT → 机械质检(7 项)→ 推送订阅/邮件 |
145
+ | 4 | **服务器巡检** | 一条命令出 CPU/内存/GPU/容器/服务报告;重启容器也在同一条白名单通道里 |
146
+ | 5 | **内网服务定位** | 仓库全景(分类/技术栈/关联)+ 设备端口扫描 |
147
+ | 6 | **家庭财务** | 结构化记录资产/负债/收支/预算,随时查询汇总;房贷利率对比这类宏观跟踪 |
148
+ | 7 | **家人档案** | 成员资料统一维护,工作区是唯一真相源 |
149
+ | 8 | **想法随手记** | 想到什么就聊,AI 访谈式问清 → 结构化归档 → 定期回顾你的思考模式 |
150
+ | 9 | **手机/外出使用** | 浏览器打开 `http://内网地址:3081` → 输密码或 6 位验证码 → 直接用完整界面 |
151
+ | 10 | **粘贴资料自动处理** | 粘图片 → 自动落盘 → 视觉模型识别(快递单/截图/图表);粘 PDF/压缩包 → 自动落盘 → 提取文本/解压/读表格 |
152
+ | 11 | **微信里用 AI** | 面板扫一次码 → 家人在微信发消息(文字/语音/图片/文件)→ 路由到指定子角色 → 回复回到微信 |
153
+ | 12 | **定时自己干活** | 工作区配好巡航(间隔/目标会话/焦点/偏见脚本)→ 到点自动唤醒并注入焦点,全程在你眼前发生,可随时介入 |
154
+
155
+ **典型一天**:
156
+
157
+ ```
158
+ 早上 服务器巡检(一条命令)→ 一切正常
159
+ 上午 同步昨天的代码 → 子仓库全部推送
160
+ 午间 收到 PDF 账单 → 粘进对话 → 自动落盘 + 表格提取 → 记进财务
161
+ 下午 做一集视频字幕(转写 → 翻译 → 双语 SRT → 质检)→ 推送订阅
162
+ 晚间 手机登录 3081 处理运维(验证码验证)
163
+ 全程 每段工作都落在 SESSION.md 里 → 轨迹连续,随时换人/换模型/换机器接着干
11
164
  ```
12
165
 
13
- 安装后重启 dsh web。在带 `.serenity` 标记的 CCC 目录中的会话自动获得全部能力。
166
+ ---
14
167
 
15
- ## 工具(10,v1.30 命名重构:13 → 10 合一)
168
+ ## 6. 对外入口详解
16
169
 
17
- `container_fs` / `logbook`(含 rebuild)/ `dashboard` / `container_git` / `msm`(单入口执行+发现)/ `praxis`(eap+neat+cce 三合一)/ `handyman` / `localstore` / `container_admin`(role + msm 管理 + config 机务舱)/ `autopilot-trajectory`
170
+ ### 6.1 网页登录入口(3081)
18
171
 
19
- > **改名对照**(硬切,无别名):cc_fs → container_fs · cc_git → container_git · session+session_rebuild → logbook(rebuild 并入)· acc_kit → dashboard · acc_msm → msm(执行)+ container_admin(管理)· eap/neat/cce → praxis · skiff_admin → container_admin role。旧会话历史引用报错时按本表对照即可。
172
+ 插件自己起第二个监听器,请求流程是:
20
173
 
21
- ## 系统提示词(8 块)
174
+ ```
175
+ 外部浏览器 → http://内网IP:3081
176
+ → 没登录 → 极简登录页(用户名 + 密码,或 6 位动态验证码,二选一;手机端适配)
177
+ → 提交 → scrypt 校验 / TOTP 校验 + CSRF 校验 + 连续失败锁定(5 次 → 15 分钟指数退避)
178
+ → 通过 → 下发 HttpOnly cookie(SameSite=Strict,24 小时滑动续期)→ 302 跳转
179
+ → 已登录 → 反向代理到 127.0.0.1:3080(改写过 Host/Origin,作为信任栅栏)
180
+ → 工作区列表按白名单过滤 + 新建工作区做校验
181
+ → WebSocket 升级也转发(101 回写 + 双向错误监听,防止连接被压垮)
182
+ ```
183
+
184
+ ### 6.2 微信桥
22
185
 
23
- `systemPrompt.section`(order -50):`=== Serenity ACC ===`(身份+工具清单)/ `=== Serenity Metaphor ===`(世界模型三层隐喻)/ `=== Serenity Principles ===`(认知容器本体论 + MSM 原则)/ `=== Serenity CCE ===`(5 行为约束)/ `=== Serenity EAP ===`(E↑R↓S↑ 自检)/ 状态块(Safe Mode / Localstore)/ 顶层入口 skill 全文(按 `.serenity` 记号发现)/ `=== Serenity Session ===`(活跃会话 + Trajectory Steward 预声明)—— 平台无关文本与 [opencode-serenity-plugin](https://github.com/tellmewhattodo/opencode-serenity-plugin) 逐字节对齐;同一 CCC 可任意换用 osp / dsh 运行时。
186
+ 工作区级配置(`.opencode/serenity.json` 的 `weixin` 段),**凭据放在工作区的 `localstore.json`**(不落 git 明文)。
187
+ DSH 一个进程可以同时带多个工作区,每个工作区各自对接自己的微信。
24
188
 
25
- ## 配置
189
+ - **扫码绑定**:设置面板 → 微信桥 → 选工作区 → 扫码(手机微信确认)→ 机器人 token 自动写入凭据
190
+ - **多账号**:每个账号独立扫码、独立移除
191
+ - **能收什么**:文字、语音(微信服务端自带转写,无需额外识别)、图片、文件
192
+ (图片和文件会从微信 CDN 下载并解密,落到 `_tmp/weixin-inbound/`,再把路径告诉 AI)
193
+ - **正在输入**:处理期间微信会显示"正在输入…"
194
+ - **回复干净**:自动剥掉思考过程,微信只看到正文
195
+ - **记得住**:同一个微信号对应固定会话,重启后恢复历史,不会"失忆"
196
+ - **路由**:微信号 → 子角色(精确匹配优先,`*` 兜底)
197
+ - **主动发消息**:工作区里的小工具可以主动给指定用户发消息——
198
+ `msm("weixin-send", ["send", "--ccc", "<工作区>", "--user", "yh", "内容"])`
199
+ (`--ccc` 必填,没有"默认当前工作区"这种猜测;发出的消息会自动被记录)
200
+ - **让 AI 自己决定怎么回**(v1.30.10):配置 `"weixin": { "autoReplyWithLastMessage": false }`
201
+ 后,插件不再自动把 AI 最后那段话转给用户,而是每轮告诉它"你必须自己发",并附上一条可直接照抄的命令。
202
+ 适合需要过程汇报、想分多条发、或者该安静就安静的角色。默认 `true`(保持原行为)。
203
+ - **消息记录**:配一个 `weixin.hook` 脚本,每收/发一条消息就把事件(JSON)喂给它,存哪里由你决定。
204
+ 记录里 `source: "reply"` 表示"回复用户",`source: "proactive"` 表示"AI 主动发起"。
205
+ - **排障**:`msm("weixin-doctor", ["status"|"diag"|"verify"|"guide"])`
26
206
 
27
- `.opencode/serenity.json`(CCC 级):
207
+ ### 6.3 子角色(Skiff)
208
+
209
+ 你可以从一个"什么都能干"的助手身上,切出一个**能力受限的小角色**——不只是问答,也可以带操作能力:
28
210
 
29
211
  ```jsonc
30
212
  {
31
- "handyman": { "models": ["provider/model"], "defaultModel": "provider/model" },
32
- "sessionKeeper": { "threshold": 100 },
33
- "safeMode": { "blacklist": [".secrets/", "regex:\\.env$"] },
34
- "skiff": { "roles": { "qa": { "msms": [], "tools": [], "systemPrompt": "" } } },
35
- "autopilotTrajectory": { "enabled": true, "intervalHours": 2, "session": "S###", "biasProvider": "autopilot-bias.ts", "topPrompt": "…" },
36
- "weixin": { "enabled": true, "accounts": [{ "accountId": "wechat-1", "name": "微信1", "enabled": true }], "routes": [{ "user": "*", "role": "zhaocai" }] }
213
+ "skiff": {
214
+ "roles": {
215
+ "qa": {
216
+ "model": "provider/model", // 这个角色单独用哪个模型
217
+ "msms": ["web-search", "vlm-describe"], // 它能调哪些小工具
218
+ "tools": ["read", "grep", "glob"], // 它能用哪些平台工具
219
+ "systemPromptFile": "roles/qa.md" // 它的人格与边界(也可以直接内联)
220
+ }
221
+ }
222
+ }
37
223
  }
38
224
  ```
39
225
 
40
- plugin 级配置(账号/开关/阈值)在 DSH 设置面板 + `~/.dsh/serenity-hooks.json`(0600)。
226
+ - 两份白名单分开配(小工具 / 平台工具),没列出来的一律隐藏
227
+ - 调试页(3099)可以切工作区、看轨迹;回答用 markdown 渲染,思考过程折叠
228
+ - `container_admin role validate` 校验配置,`apply` 才真正生效
229
+
230
+ ### 6.4 对外问答页(3100)
231
+
232
+ - **key 认证**(常量时间比较 + 失败 IP 锁定 + 可轮换)+ 容器白名单(留空 = 全部开放)
233
+ - **只给答案**:响应里只有 answer / answer_html / sessionId——内部轨迹、工具结果、机制信息都不出去
234
+ - 默认只监听 127.0.0.1;要给别人用,怎么暴露(隧道/反代/端口映射)由你决定
235
+
236
+ ### 6.5 自主巡航(Autopilot Trajectory)
237
+
238
+ 让工作区**到点自己醒过来干活**,全程在你眼前发生(前台注入,随时可介入):
239
+
240
+ - **什么时候醒**:工作区开关打开 + 全局开关打开(默认关,只在你想跑的机器上开)+ 目标会话存在(目录带 `--auto` 后缀)+ 到了间隔(支持小数,最密约 36 秒)+ 不在避开的高峰窗口(默认北京时间 8~18 点)+ 偏见脚本就绪
241
+ - **醒了先看什么**:先看你自己写的"焦点"(`topPrompt`,每轮最先注入,防跑偏),再看随机生成的"偏见内容"(你写的脚本,负责探索方向)
242
+ - **多个工作区各自独立**:每个工作区自己的间隔/会话/焦点/窗口,互不干扰
243
+ - **有审计**:每次唤醒都记一笔,面板里能看到最近几次;失败会指数退避重试
244
+ - 没有"每天最多唤醒几次"的限制——频率只由间隔和窗口决定
245
+
246
+ ### 6.6 安全模型
247
+
248
+ | 层面 | 做了什么 |
249
+ |---|---|
250
+ | 登录 | scrypt 密码哈希 + 常量时间比较 + 256-bit token + 24 小时滑动有效期 + 审计日志 |
251
+ | 双因素 | TOTP(兼容 Authenticator),扫码绑定;密码和验证码二选一 |
252
+ | 防爆破 | 按账号锁定:连续失败 5 次 → 锁 15 分钟,且指数退避 |
253
+ | 防 CSRF | 登录双提交 + 配置写入校验 Origin + 服务端 token 集合(多标签页不冲突) |
254
+ | 凭据 | 集中放 `localstore.json`(默认禁止提交到 git);密钥文件对工具结构性隔离 |
255
+ | 对外输出 | 敏感词检测 → 打回重答,并告知命中词和规避方向 |
41
256
 
42
- ## 文档
257
+ ---
43
258
 
44
- - 完整文档:仓库 [README](https://github.com/tellmewhattodo/dsh-serenity-plugin)(能力为主)
45
- - 理论叙述:`docs/cognitive-container-theory.md`
46
- - 设计决策与架构:`CHANGELOG.md` + 维护 skill(`dsh-serenity-plugin-development`)
259
+ ## 7. 配置分四层
260
+
261
+ | 层 | 位置 | 放什么 |
262
+ |---|---|---|
263
+ | DSH 原生设置 | DSH 的 settings.yaml | 简单开关:网关 / 重建 / 会话命名、重建阈值、子角色开关与调试端口、ACP 与问答页开关、**巡航总开关(默认关)** |
264
+ | 插件全局文件 | `~/.dsh/serenity-hooks.json`(权限 0600) | 网关账号(scrypt + TOTP)、监听地址与端口、工作区白名单、cookie 安全开关、问答页 key |
265
+ | 工作区配置 | `.opencode/serenity.json` | 助手模型白名单、日志阈值、安全模式黑名单、子角色、**巡航(间隔/会话/焦点/窗口)**、**微信(账号/路由/开关)** |
266
+ | 工作区凭据 | `localstore.json` | 密钥与本地偏好;微信机器人 token 也在这一层 |
267
+
268
+ > 原则:**插件是全局的,工作区是具体的**——账号、开关、阈值归插件层;角色、凭据、本地偏好归工作区层。
269
+
270
+ ---
271
+
272
+ ## 8. 上下文快满了怎么办
273
+
274
+ AI 一次能"记住"的内容有上限。满了不用你手动开新会话:
275
+
276
+ | 机制 | 说人话 |
277
+ |---|---|
278
+ | **工作日志(SESSION.md)** | AI 的"笔记本",永远留在原地。目标、决定、进度都写这儿 |
279
+ | **原地重建(logbook rebuild)** | 快满时它会提示 AI 主动重建:把这一轮对话清空,但重新注入"你是谁 + 继续 S### 的工作"——**载体换了,活儿接着干**。重建后的 token 计量也正确回落 |
280
+ | **进度提醒** | 做久了会按计分提醒 AI 把进度写回日志,并要求它回确认码 |
281
+ | **沉淀纪律** | 重建前如果产生了有价值的认知,先把它写进相关技能(而不是丢掉) |
282
+
283
+ ---
284
+
285
+ ## 9. 给插件开发者
286
+
287
+ ```bash
288
+ pnpm typecheck # 类型检查(node + 浏览器端两套)
289
+ pnpm test # 全量测试(当前 70 个文件 / 993 个用例)
290
+ pnpm build # 打包(lib/index.js + client.js)
291
+ ```
292
+
293
+ - **开发用小工具**:`scripts/dsh-develop.ts`——typecheck / test / build / status / commit / push / version / bump / deploy / restart-web / publish / pack-check / github-push 一条龙。
294
+ `pack-check` 会在发布前核对打包产物是否完整(曾经踩过"发到 npm 少了文件"的坑);`scripts/dsh-crash-investigate.ts` 用来查崩溃(只读)
295
+ - **架构**:Cordis 原生插件,用 DSH 的正式接口注册工具和拦截点——**从不修改 DSH 本体**
296
+ - **代码地图**:[docs/codebase-overview-v1.22.md](https://github.com/tellmewhattodo/dsh-serenity-plugin/blob/master/docs/codebase-overview-v1.22.md)
297
+ - **设计决策**:见 [CHANGELOG.md](https://github.com/tellmewhattodo/dsh-serenity-plugin/blob/master/CHANGELOG.md) 和维护技能 `dsh-serenity-plugin-development`
298
+ - **发布**:npm `@shgroup/dsh-serenity-hooks` + GitHub 双仓库同步推送
299
+
300
+ ---
301
+
302
+ ## 10. 和 opencode 版是什么关系
303
+
304
+ | | opencode-serenity-plugin | dsh-serenity-plugin(本仓库) |
305
+ |---|---|---|
306
+ | 跑在 | OpenCode | DeepSeek Harness |
307
+ | 实现 | 独立 | **独立**(不复用源码,但遵循同一套标准) |
308
+ | 系统提示词 | `system.transform` | `systemPrompt.section`,平台无关的部分逐字对齐 |
309
+ | 工具 | msm / container_fs / logbook 等 | container_fs / logbook / dashboard / container_git / msm / praxis / handyman / localstore / container_admin / autopilot-trajectory |
310
+
311
+ **同一个工作区可以随时换运行时**:`.serenity` 标记、`.opencode/skills/`、配置、`AGENT_SESSIONS/` 的文件格式都一致;
312
+ 差别只在平台层(工具名、注入方式),换过去以后 AI 收到的约束是一样的。
313
+
314
+ ---
315
+
316
+ ## 11. 常见问题
317
+
318
+ **Q:装完没反应?**
319
+ 先确认你进的是带 `.serenity` 标记的目录。不是工作区的话,插件完全不介入。进去后输入 `dashboard health` 看三项检查。
320
+
321
+ **Q:bash 怎么不见了?**
322
+ 安全模式开着——这是设计,不是 bug。走注册过的小工具比让 AI 自己拼命令可靠。关掉胶囊里的 SAFE 滑块就回来了。
323
+
324
+ **Q:3081 登录被锁了?**
325
+ 连续失败 5 次锁 15 分钟(指数退避)。等锁过期,或检查账号的验证码绑定状态。
326
+
327
+ **Q:上下文快满了怎么办?**
328
+ 先让 AI 把进度写回 SESSION.md,然后按提示调用 `logbook rebuild`。轨迹会自动接续,不用手动开新会话。
329
+
330
+ **Q:对外问答页会返回内部信息吗?**
331
+ 不会。只返回答案本身,内部轨迹和工具结果都不出去。
332
+
333
+ **Q:微信桥里,AI 的回复是怎么发出去的?**
334
+ 默认由插件自动把它的最后一段话转发给你。如果配了 `autoReplyWithLastMessage: false`,插件就不转了,改由 AI 自己发——所以这时候它如果没发,你就收不到消息(没有兜底,这是刻意设计)。
335
+
336
+ **Q:主动发的微信消息会被记录吗?**
337
+ 会。和工作区里配的 `weixin.hook` 记录脚本走同一条路,事件里标 `source: "proactive"`;自动回复标 `source: "reply"`。
338
+
339
+ ---
340
+
341
+ ## 12. 延伸阅读
342
+
343
+ - **这套东西的想法从哪来**:[docs/cognitive-container-theory.md](https://github.com/tellmewhattodo/dsh-serenity-plugin/blob/master/docs/cognitive-container-theory.md)——认知容器是什么、认知的"发生/存储/再发生"、轨迹与载体
344
+ - **权威标准**:[serenity-acc-specs](https://github.com/tellmewhattodo/serenity-acc-specs)——理论根基、注入规范、不变量
345
+ - **改了什么**:[CHANGELOG.md](https://github.com/tellmewhattodo/dsh-serenity-plugin/blob/master/CHANGELOG.md)——每个版本做了什么、为什么这么做
346
+
347
+ ---
47
348
 
48
349
  ## 许可
49
350
 
50
- MIT
351
+ MIT(见 [LICENSE](https://github.com/tellmewhattodo/dsh-serenity-plugin/blob/master/LICENSE))
352
+
353
+ > **版本**:v1.30.11 | **前置**:DSH 0.1.2-rc.1+ / Node ≥ 20 或 bun | **测试**:70 个文件 / 993 个用例
package/dsh.plugin.json CHANGED
@@ -1,10 +1,10 @@
1
1
  {
2
2
  "id": "dsh-serenity-hooks",
3
- "version": "1.30.9",
3
+ "version": "1.30.11",
4
4
  "main": "lib/index.js",
5
- "description": "宁静号 ACC harness(Native Cordis 插件):真实 DSH 工具 container_fs/logbook/dashboard/container_git/msm/praxis/handyman/localstore/container_admin/autopilot-trajectory + 拦截缝机械约束(safe-mode/路径守卫)+ 高级设定面板(双端口网关/账号管理)+ Skiff 认知子集角色(实验性)。适配 DSH 公开版(0.1.0-rc,deepseek-ai/deepseek-harness)。",
5
+ "description": "宁静号 ACC harness(Native Cordis 插件):给 DSH 装一个「AI 工作区」——10 个工具(container_fs/logbook/dashboard/container_git/msm/praxis/handyman/localstore/container_admin/autopilot-trajectory)+ 机械约束(安全模式/工作区围墙/密钥守卫)+ 工作日志与原地重建 + 网页登录入口/微信桥/子角色/对外问答页/自主巡航。适配 DSH 0.1.2-rc.1(deepseek-ai/deepseek-harness)。",
6
6
  "engines": {
7
- "dsh": ">=0.1.0-rc.5"
7
+ "dsh": ">=0.1.2-rc.1"
8
8
  },
9
9
  "contributes": {
10
10
  "tools": [
package/lib/ccc.d.ts CHANGED
@@ -139,6 +139,19 @@ export interface WeixinSettings {
139
139
  * 执行语义:旁路 fire-and-forget + 超时 + 失败仅日志(微信桥可靠性不受 hook 影响)。
140
140
  */
141
141
  hook?: string;
142
+ /**
143
+ * 桥是否**自动回发 agent 的最终文本**(v1.30.10,用户拍板命名;缺省 true = 现状)。
144
+ *
145
+ * 关闭(false)→ 桥不再把一轮结束时 agent 的最终 assistant 文本转发给用户;
146
+ * 输出权完全交给 agent:每轮 question 前缀注入「输出纪律」块(含可直接执行的
147
+ * weixin-send 命令),由 agent 自己决定发什么/发几条(全部记 `source=proactive`)。
148
+ *
149
+ * 为什么需要(R↓):v1.30.9 起 agent 已能经 msm 主动输出(`weixin-send` → 桥 → iLink),
150
+ * 「桥替 agent 说最后一句」从机制上变成可选项——进程式汇报、多段输出、静默处理都需要它。
151
+ * 语义边界(用户拍板):只关**最终文本**;系统类消息(新对话通知 / 语音无法解析提示)保留;
152
+ * agent 一轮未输出 → 静默不兜底(否则开关语义被破坏)。
153
+ */
154
+ autoReplyWithLastMessage?: boolean;
142
155
  }
143
156
  /** 微信桥账号配置(serenity.json 内;凭据部分在 localstore) */
144
157
  export interface WeixinAccountConfig {
package/lib/index.js CHANGED
@@ -3,7 +3,7 @@ import { a as findSerenityRoot, c as matchBlacklist, d as readCccName$2, f as re
3
3
  import { a as resolveRoleSystemPrompt, i as readSkiffRoles, l as systemPromptSource, r as isSkiffSessionId, s as roleToolWhitelist } from "./skiff-role-DlrbHPLD.js";
4
4
  import { C as newStopToken, D as writeFailedStatus, E as splitModel, O as writeProgress, S as listActiveHandymen, T as requireWhitelistedModel, _ as workspaceTrajectoryLine, a as startSkiffDebugServer, b as buildRoundPrompt, c as askSkiff, d as getSkiffAgent, f as skiffMsmGate, g as unregisterSkiffSession, h as skiffTrajectoryEnabled, k as skiffRoleFor, l as createSkiffAgent, m as skiffSessionSnapshot, n as jscSafeJsonText, o as stopSkiffDebugServer, p as skiffSessionInfo, r as renderSkiffMarkdown, s as stripThink, t as discoverCccs, u as ensureSkiffSession, v as waitAgentIdle, w as readProgress, x as handymanProgressPaths, y as HANDYMAN_GUIDE } from "./skiff-debug-TONJlpgF.js";
5
5
  import { i as hostSessions, n as hostInjected, o as hostWebServer, r as hostService, t as hostAgents } from "./access-CdL6BAYj.js";
6
- import { a as readWeixinCredential, c as weixinInboundDir, d as LOCALSTORE_SCOPES, f as checkLocalstoreGitCompliance, h as runLocalStore, i as matchWeixinRoute, l as weixinSessionIdFor, m as readGitTrack, n as extractWeixinText, o as readWeixinSettings, p as localstorePath, r as hasVoiceItem, s as sanitizeFileName$1, t as extractWeixinMedia } from "./weixin-route-BJ4g4ZNq.js";
6
+ import { a as readWeixinCredential, c as weixinInboundDir, d as LOCALSTORE_SCOPES, f as checkLocalstoreGitCompliance, h as runLocalStore, i as matchWeixinRoute, l as weixinSessionIdFor, m as readGitTrack, n as extractWeixinText, o as readWeixinSettings, p as localstorePath, r as hasVoiceItem, s as sanitizeFileName$1, t as extractWeixinMedia } from "./weixin-route-BD_yYAZz.js";
7
7
  import { C as summarize, S as showSession, _ as readActiveSessionMd, a as archiveSessions, b as sessionsRoot, c as createSession, d as findSession, f as getActiveSessionInfo, g as qaCheck, h as parseSessionContextFromEvents, i as SESSION_ACTIONS, l as extractSessionMdPathFromText, m as listSessions, n as readLastBound, o as clearActiveSessionInfo, p as healthCheck, r as DEFAULT_SESSION_SCOPE, s as closeSession, t as appendBound, u as findLatestActiveSessionMd, v as resolveSessionByTitle, w as useSession, x as setActiveSessionInfo, y as sessionEvents } from "./session-bound-D2ANqVn-.js";
8
8
  import { n as registerSettingsSection, t as readSimpleSettings } from "./settings-section-BPk0e1du.js";
9
9
  import { a as markdownToPlainText, c as sniffImageExt, i as getUpdates, n as downloadMedia, o as sendTextMessage, r as getConfig, s as sendTyping, t as TypingStatus } from "./weixin-api-BMpljodH.js";
@@ -1836,10 +1836,19 @@ CCC 级微信个人号接入(iLink 协议):dsh 一进程多 CCC,每 CCC
1836
1836
  的 weixinApi.port,默认 3082;公网不可达故无密钥)→ weixin-bridge.sendProactiveText →
1837
1837
  成功后触发 outgoing hook(source="proactive")。send-file 仍由 MSM 直连 iLink 上传并自行补记。
1838
1838
 
1839
+ ▸ weixin.autoReplyWithLastMessage(v1.30.10,关闭桥的自动输出):
1840
+ 缺省 true = 现状:桥在 agent 一轮结束后把**最终 assistant 文本**自动回发用户(source="reply")。
1841
+ 显式 false = 关闭:桥**不再转发最终文本**,输出权交给 agent——每轮 question 前缀注入
1842
+ 「Reply Output (manual mode)」纪律块(含可直接照抄的 weixin-send 命令,CCC 根/账号/用户 id
1843
+ 已填好),agent 自己决定发什么、发几条(全部记 source="proactive")。
1844
+ 语义边界(用户拍板):只关最终文本;系统类消息(新对话通知 / 语音无法解析提示)保留;
1845
+ **agent 一轮未输出 → 静默不兜底**(否则开关失去意义);typing 指示与 incoming hook 不受影响。
1846
+
1839
1847
  Config:
1840
1848
  { "weixin": {
1841
1849
  "enabled": true,
1842
1850
  "hook": "scripts/weixin-message-hook.ts",
1851
+ "autoReplyWithLastMessage": true,
1843
1852
  "accounts": [{ "accountId": "wechat-1", "name": "家庭助手", "enabled": true }],
1844
1853
  "routes": [{ "user": "*", "role": "zhaocai" }]
1845
1854
  } }
@@ -6582,11 +6591,11 @@ function registerStatusApi(ctx, opts = {}) {
6582
6591
  sendJson$2(res, 400, { error: chk.error });
6583
6592
  return;
6584
6593
  }
6585
- const { readWeixinSettings } = await import("./weixin-route-BJ4g4ZNq.js").then((n) => n.u);
6594
+ const { readWeixinSettings } = await import("./weixin-route-BD_yYAZz.js").then((n) => n.u);
6586
6595
  const { weixinBridgeStatus } = await Promise.resolve().then(() => weixin_bridge_exports);
6587
6596
  const settings = readWeixinSettings(chk.root);
6588
6597
  const bridge = weixinBridgeStatus().find((b) => b.ccc === chk.root);
6589
- const { readWeixinCredential } = await import("./weixin-route-BJ4g4ZNq.js").then((n) => n.u);
6598
+ const { readWeixinCredential } = await import("./weixin-route-BD_yYAZz.js").then((n) => n.u);
6590
6599
  const accounts = (settings.accounts ?? []).map((a) => ({
6591
6600
  accountId: a.accountId,
6592
6601
  name: a.name ?? void 0,
@@ -6618,7 +6627,7 @@ function registerStatusApi(ctx, opts = {}) {
6618
6627
  const { fetchQRCode } = await import("./weixin-api-BMpljodH.js").then((n) => n.l);
6619
6628
  const { randomUUID } = await import("node:crypto");
6620
6629
  for (const [key, v] of weixinLogins) if (Date.now() - v.startedAt > WEIXIN_LOGIN_TTL_MS) weixinLogins.delete(key);
6621
- const { readWeixinSettings } = await import("./weixin-route-BJ4g4ZNq.js").then((n) => n.u);
6630
+ const { readWeixinSettings } = await import("./weixin-route-BD_yYAZz.js").then((n) => n.u);
6622
6631
  const qr = await fetchQRCode({ botType: readWeixinSettings(root).botType ?? void 0 });
6623
6632
  const loginKey = randomUUID();
6624
6633
  weixinLogins.set(loginKey, {
@@ -6639,7 +6648,7 @@ function registerStatusApi(ctx, opts = {}) {
6639
6648
  sendJson$2(res, 400, { error: "missing accountId" });
6640
6649
  return;
6641
6650
  }
6642
- const { removeWeixinAccount } = await import("./weixin-route-BJ4g4ZNq.js").then((n) => n.u);
6651
+ const { removeWeixinAccount } = await import("./weixin-route-BD_yYAZz.js").then((n) => n.u);
6643
6652
  const { syncCccBridge } = await Promise.resolve().then(() => weixin_bridge_exports);
6644
6653
  removeWeixinAccount(root, body.accountId);
6645
6654
  syncCccBridge(ctx, root);
@@ -6652,7 +6661,7 @@ function registerStatusApi(ctx, opts = {}) {
6652
6661
  sendJson$2(res, 400, { error: "invalid routes (expected [{user, role}, ...])" });
6653
6662
  return;
6654
6663
  }
6655
- const { saveWeixinRoutes } = await import("./weixin-route-BJ4g4ZNq.js").then((n) => n.u);
6664
+ const { saveWeixinRoutes } = await import("./weixin-route-BD_yYAZz.js").then((n) => n.u);
6656
6665
  const { readSkiffRoles } = await import("./skiff-role-DlrbHPLD.js").then((n) => n.c);
6657
6666
  const roles = readSkiffRoles(root);
6658
6667
  for (const r of routes) if (!roles.has(r.role)) {
@@ -6665,7 +6674,7 @@ function registerStatusApi(ctx, opts = {}) {
6665
6674
  }
6666
6675
  if (body.action === "set-enabled") {
6667
6676
  const enabled = body.enabled === true;
6668
- const { readWeixinSettings, setWeixinEnabled } = await import("./weixin-route-BJ4g4ZNq.js").then((n) => n.u);
6677
+ const { readWeixinSettings, setWeixinEnabled } = await import("./weixin-route-BD_yYAZz.js").then((n) => n.u);
6669
6678
  const { syncCccBridge } = await Promise.resolve().then(() => weixin_bridge_exports);
6670
6679
  const settings = readWeixinSettings(root);
6671
6680
  if (enabled && (settings.accounts ?? []).length === 0) {
@@ -6712,7 +6721,7 @@ function registerStatusApi(ctx, opts = {}) {
6712
6721
  return;
6713
6722
  }
6714
6723
  const { pollQRStatus } = await import("./weixin-api-BMpljodH.js").then((n) => n.l);
6715
- const { readWeixinSettings } = await import("./weixin-route-BJ4g4ZNq.js").then((n) => n.u);
6724
+ const { readWeixinSettings } = await import("./weixin-route-BD_yYAZz.js").then((n) => n.u);
6716
6725
  const settings = readWeixinSettings(login.root);
6717
6726
  const status = await pollQRStatus({
6718
6727
  baseUrl: void 0,
@@ -6727,7 +6736,7 @@ function registerStatusApi(ctx, opts = {}) {
6727
6736
  });
6728
6737
  return;
6729
6738
  }
6730
- const { upsertWeixinAccount, writeWeixinCredential, nextWeixinAccountId } = await import("./weixin-route-BJ4g4ZNq.js").then((n) => n.u);
6739
+ const { upsertWeixinAccount, writeWeixinCredential, nextWeixinAccountId } = await import("./weixin-route-BD_yYAZz.js").then((n) => n.u);
6731
6740
  const { syncCccBridge } = await Promise.resolve().then(() => weixin_bridge_exports);
6732
6741
  const accountId = nextWeixinAccountId(settings);
6733
6742
  upsertWeixinAccount(login.root, {
@@ -9309,7 +9318,8 @@ var weixin_bridge_exports = /* @__PURE__ */ __exportAll({
9309
9318
  stopAllBridges: () => stopAllBridges,
9310
9319
  stopCccBridge: () => stopCccBridge,
9311
9320
  syncCccBridge: () => syncCccBridge,
9312
- weixinBridgeStatus: () => weixinBridgeStatus
9321
+ weixinBridgeStatus: () => weixinBridgeStatus,
9322
+ weixinManualOutputLine: () => weixinManualOutputLine
9313
9323
  });
9314
9324
  const bridges = /* @__PURE__ */ new Map();
9315
9325
  /** 轮询间隔(getupdates 失败后重试延迟;成功 = 立即续轮询) */
@@ -9389,6 +9399,30 @@ async function runAccountLoop(ctx, root, accountId, cred, loop) {
9389
9399
  }
9390
9400
  }
9391
9401
  /**
9402
+ * 手动输出模式的**输出纪律块**(v1.30.10,`weixin.autoReplyWithLastMessage: false` 时注入)。
9403
+ *
9404
+ * 为什么必须注入(R↓):关掉自动回发后,agent 若不知道"桥不会替你转发",用户就收不到任何回复
9405
+ * ——机制变更必须伴随可执行的行动指引。因此这里给的是**可直接照抄的完整命令**(CCC 根 + 账号 +
9406
+ * 用户 id 全部填好),而不是抽象提示(E↑)。
9407
+ *
9408
+ * 参数全部显式:`--ccc`(必填,无隐式当前 CCC)、`--account`(沿用收到消息的账号,避免多账号下
9409
+ * 用错 bot 身份回话)、`--user`(该用户 iLink id,免去别名解析)。
9410
+ */
9411
+ function weixinManualOutputLine(root, accountId, userId) {
9412
+ return [
9413
+ "── Reply Output (manual mode) ──",
9414
+ "The bridge will NOT forward your final message to the user for this CCC.",
9415
+ "Send everything the user should see yourself, e.g.:",
9416
+ ` msm("weixin-send", ["send", "--ccc", "${root}", "--account", "${accountId}", "--user", "${userId}", "<your reply>"])`,
9417
+ "Rules:",
9418
+ " 1. Send your reply before the turn ends — if you send nothing, the user sees nothing (no fallback).",
9419
+ " 2. You may send several messages (progress / final answer); split only when it helps the reader.",
9420
+ " 3. Plain text only — WeChat renders no markdown.",
9421
+ " 4. Each send is recorded automatically (source=proactive); do not repeat the same content.",
9422
+ "── ──"
9423
+ ].join("\n");
9424
+ }
9425
+ /**
9392
9426
  * 处理单条微信消息:路由 → skiff 会话(固定 id 创建/延续)→ 提问 → 回复回写。
9393
9427
  *
9394
9428
  * 会话语义:`weixinSessionIdFor(fromUserId)` 固定可重建——同用户长期同一会话;
@@ -9502,6 +9536,8 @@ async function handleIncoming(ctx, root, accountId, cred, msg) {
9502
9536
  const parts = [];
9503
9537
  const activeWorkspace = getActiveSessionInfo(sessionId);
9504
9538
  if (activeWorkspace?.mdPath) parts.push(workspaceTrajectoryLine(activeWorkspace.mdPath));
9539
+ const manualOutput = settings.autoReplyWithLastMessage === false;
9540
+ if (manualOutput) parts.push(weixinManualOutputLine(root, accountId, fromUserId));
9505
9541
  if (text) parts.push(text);
9506
9542
  parts.push(...mediaNotes, ...degradedNotes);
9507
9543
  const question = parts.join("\n");
@@ -9518,6 +9554,7 @@ async function handleIncoming(ctx, root, accountId, cred, msg) {
9518
9554
  console.log(`[serenity-hooks] weixin hook incoming error: ${err instanceof Error ? err.message : String(err)}`);
9519
9555
  });
9520
9556
  const answer = (await askSkiff(ctx, ref.agent, question, void 0, { includeTrajectory: false })).answer ?? "";
9557
+ if (manualOutput) return;
9521
9558
  if (answer === "") return;
9522
9559
  const reply = markdownToPlainText(stripThink(answer));
9523
9560
  await sendTextMessage({
package/lib/msm-ops.d.ts CHANGED
@@ -19,7 +19,7 @@ export declare const MSM_ACTIONS: readonly MsmAction[];
19
19
  export declare const ACC_CATALOG = "\u2550\u2550\u2550 ACC Usage Catalog \u2550\u2550\u2550\nDirectory of ACC capabilities \u2014 each area lists where to go for details (guides live with their feature; this catalog only points).\n\n \u2460 \u4F1A\u8BDD\u4E0E\u8F68\u8FF9 \u2192 logbook\uFF08list/show/create/use/close/health/qa/archive/summary/hook-develop-guide + rebuild \u8D85\u9650\u91CD\u5EFA\uFF09\n session_rebuild \u5DF2\u5E76\u5165 logbook rebuild\uFF08\u8D85\u9650\u91CD\u5EFA\uFF1B\u9608\u503C K \u89C1 container_admin ccc-config / \u8BBE\u7F6E\u9762\u677F\u91CD\u5EFA\u9608\u503CK\uFF09\n \u2461 \u8BA4\u77E5\u8D28\u91CF\u6846\u67B6 \u2192 praxis\uFF08eap/neat/cce \u4E09\u5408\u4E00\u2014\u2014\u53EF\u5B9E\u8DF5\u7406\u8BBA\u6CE8\u5165\uFF0Csection \u6E10\u8FDB\u62AB\u9732\uFF0C\u65E0\u53C2\u5373\u76EE\u5F55\uFF09\n \u2462 \u5DE5\u5177\u4E0E\u6267\u884C \u2192 container_fs\uFF08\u5BB9\u5668\u6587\u4EF6\u7CFB\u7EDF 15 \u5B50\u547D\u4EE4\uFF09/ container_git\uFF08git\uFF09/ dashboard\uFF08health \u542B\u6CE8\u518C\u8868\u68C0\u67E5/time/wait\uFF09\n acc_msm \u5DF2\u5E76\u5165 msm\uFF08\u6267\u884C+\u53D1\u73B0\uFF09\u4E0E container_admin\uFF08\u7BA1\u7406\uFF09\u2014\u2014msm(\"<name>\", [\"<args>\"])\uFF1Binspect=true \u67E5\u7528\u6CD5\n handyman\uFF08\u6742\u5DE5 agent \u7F16\u6392\u2014\u2014guide \u5B50\u547D\u4EE4\uFF1Ahandyman guide\uFF09\n \u2463 \u89D2\u8272\u4E0E\u5BF9\u5916\u9762 \u2192 container_admin role\uFF08Skiff \u8BA4\u77E5\u5B50\u96C6\u89D2\u8272\uFF1Aguide/validate/apply/list\u2014\u2014skiff_admin \u5DF2\u5E76\u5165\uFF09\n ACP\uFF08\u7A0B\u5E8F\u5316 JSON-RPC 3100\uFF09/ Skiff \u95EE\u7B54\u9875\uFF08\u516C\u7F51 ask\uFF09\u2014\u2014\u9762\u677F\u300C\u5916\u90E8\u80FD\u529B\u300D\u7EC4\n \u2464 \u81EA\u4E3B\u4E0E\u63A5\u5165 \u2192 autopilot-trajectory\uFF08Autopilot \u4E00\u7AD9\u5F0F\uFF1Aall/init/random/diag/doc/check/status/guide\uFF09\n weixin \u5FAE\u4FE1\u6865\uFF08\u914D\u7F6E/\u626B\u7801/\u8DEF\u7531/\u6D88\u606F hook\u2014\u2014CCC \u4FA7 weixin-doctor MSM\uFF1Amsm(\"weixin-doctor\", [\"guide\"])\uFF09\n \u2465 CCC \u914D\u7F6E\u603B\u89C8 \u2192 container_admin msm ccc-config\uFF088 \u6BB5\uFF1Ahandyman/sessionKeeper/localstore/hooks/safeMode/skiff/autopilotTrajectory/weixin\uFF09\n \u2466 \u6CE8\u518C\u8868\u4E0E\u5B89\u5168 \u2192 mech-registry.json \u7531 container_admin msm register/deregister \u7BA1\u7406\uFF08\u5199\u4FDD\u62A4\u2014\u2014\u4E0D\u53EF\u76F4\u63A5\u7F16\u8F91\uFF09\n dashboard health \u8F93\u51FA registry \u5B8C\u6574\u6027\u68C0\u67E5\uFF1B\u635F\u574F\u6062\u590D\u6307\u5F15\u89C1 health registry.issues\n\n\u6BCF\u533A\uFF1A\u4E00\u53E5\u8BDD\u5B9A\u4F4D + \u8BE6\u7EC6\u5165\u53E3\uFF08\u5DE5\u5177\u540D / guide \u5B50\u547D\u4EE4\uFF09\u3002\u8BE6\u60C5\u6C38\u8FDC\u4EE5\u5BF9\u5E94\u5DE5\u5177\u7684 guide/ccc-config \u4E3A\u5355\u4E00\u771F\u76F8\u6E90\u3002\n";
20
20
  export declare const MSM_GUIDE = "MSM Development Manual (Mech & Semi-Mech framework)\n\n## What it is\nMSM = the executable-unit layer. Mech is pure TS with zero LLM reasoning; Semi-Mech is a TS framework + LLM decision points.\nMSM is ACC's deterministic executable-unit layer \u2014 all shell/exec operations go through MSM and cannot be bypassed.\n\n## Registering a new MSM (container_admin msm register)\n1. Write the script under <skill>/scripts/ (runnable via tsx; must include a main() CLI guard with an import.meta.url check)\n2. container_admin msm register --domain msm --action register <name> --skill <s> --path <script path relative to root> --category <mech|semi-mech> --description <desc>\n3. Auto-writes mech-registry.json (preserving the original format) + git commit (commits only the registry file)\n4. Validation: path must be inside root, script must exist, name must be globally unique\n\n## flag schema (v1)\nflags is a new-style object array for parameter validation and path-escape guarding:\n [{\"name\":\"output\",\"type\":\"string\",\"description\":\"output path\"},\n {\"name\":\"target\",\"type\":\"path\",\"description\":\"operation target (type:path enables in-root validation + symlink defense)\"},\n {\"name\":\"force\",\"type\":\"boolean\",\"description\":\"force mode\",\"default\":false}]\n- {name, type} format \u2014 new style, type:\"path\" enables the path-escape guard\n- {flag, description} format \u2014 old style, CLI flag description string\n- On registration, flags are passed via container_admin msm register --flags '<json>'\n\n## Script conventions\n- Top-of-file documentation: purpose / usage / exit codes\n- Exit codes: 0 success / 1 user / 2 system / 3 operator (per ACC protocol classification)\n- main() CLI guard (DC-M2): the script must start with\n if (import.meta.url === `file://${process.argv[1]}`) { main() }\n or an equivalent isMain / require.main === check \u2014 so vitest imports never trigger top-level code\n- A paired .test.ts or .spec.ts (DC-M1, vitest)\n- Business subprocess environment: SERENITY_ROOT / SERENITY_CCC / SERENITY_VERSION injected\n\n## Interaction & confirmation conventions (no blocking confirmation)\n- MSMs run in **user-less subprocesses** (spawn/execFile, 600s timeout) \u2014 **never** use\n readline / prompt / process.stdin etc. to block waiting for user input (it would hang until killed by the timeout)\n- When a second confirmation is needed: **directly return the confirmation request** (list the operation to perform and its impact + how to retry with the confirmation flag),\n returning a non-zero exit code (or an explicit hint)\n- After the agent confirms, **re-invoke the MSM with the confirmation flag** (e.g. --confirm / --yes / --force) to retry\n- Standard mode (two-phase):\n 1. First call: destructive/confirmation-requiring operation detected and no confirmation flag present \u2192 output the confirmation request (list operation/impact/rollback),\n exit non-zero (e.g. 1 user), **perform no changes**\n 2. After the agent evaluates, re-invoke: with the confirmation flag \u2192 execute and output the result\n- Applies to: delete/overwrite/push/batch operations and other irreversible or wide-impact operations\n- Anti-example (forbidden): readline waiting for user input, process.stdin.on('data') blocking waits\n (MSMs have no stdin interaction channel \u2014 they hang until the 600s timeout)\n\n## Quality checks (container_admin msm check, DC-M1~M4)\nDC-M1 has .test.ts/.spec.ts; DC-M2 has a main() guard (function main( / isMain / require.main === / import.meta.url);\nDC-M3 bidirectional: scripts unregistered + registry references missing scripts; DC-M4 path-type flags marked type:\"path\"\n\n## Self-description (protocol flags, first argument only)\nmsm(\"<name>\", [\"--list\"]) \u2014 list all MSMs\nmsm(\"<name>\", [\"--schema\", \"<n>\"]) \u2014 view a specific MSM's parameter schema\nmsm(\"<name>\", [\"--format=json\"]) \u2014 JSON output mode (remaining args passed through losslessly)\n";
21
21
  /** CCC 配置参考(对齐 osp ccc-config action) */
22
- export declare const CCC_CONFIG_REFERENCE = "\u2550\u2550\u2550 CCC Configuration Reference \u2550\u2550\u2550\n\nCCC-level features are configured in .opencode/serenity.json.\nBelow are all available configuration sections.\n\n\u2500\u2500 1. handyman.models \u2500\u2500\nhandyman\uFF08\u6742\u5DE5\uFF09\u5DE5\u5177\u53EF\u7528\u6A21\u578B\u767D\u540D\u5355\uFF08provider/model \u5217\u8868\uFF09\uFF1B\u672A\u914D\u7F6E\u65F6 handyman \u62A5\u9519\u8981\u6C42\u914D\u7F6E\u3002\n\u7F3A\u7701\u6A21\u578B = models[0]\uFF08\u53EF\u7528 handyman.defaultModel \u6307\u5B9A\uFF0C\u5FC5\u987B \u2208 models\uFF09\u3002\n\n Config:\n { \"handyman\": { \"models\": [\"provider/model-name\"] } }\n\n Example:\n { \"handyman\": { \"models\": [\"minimax-cn-coding-plan/MiniMax-M3\"], \"defaultModel\": \"minimax-cn-coding-plan/MiniMax-M3\", \"maxRounds\": 100, \"maxParallel\": 10 } }\n\n\u2500\u2500 2. sessionKeeper.threshold \u2500\u2500\ntrajectory-assistant\uFF08TRAJECTORY-ASSISTANT \u00B7 CHECKPOINT\uFF09\u63D0\u9192\u673A\u5236\u7684\u79EF\u5206\u9608\u503C\uFF08\u975E headless \u4E3B agent\uFF09\u3002\n\u6309\u5DE5\u5177\u8C03\u7528\u52A0\u6743 + \u8017\u65F6\u8BA1\u5206\uFF1B\u8FBE\u5230\u9608\u503C\u6CE8\u5165\u63D0\u9192\uFF0C\u8981\u6C42\u6A21\u578B\u56DE\u590D ACK \u7801\u3002\n\n Config:\n { \"sessionKeeper\": { \"threshold\": 150 } }\n\n \u8BA1\u5206\uFF1Awrite/edit = 3\uFF0Ctask = 10\uFF0Cread/grep/glob/msm \u7B49 = 1\uFF0C\u65F6\u95F4 = 1/\u5206\u949F\n \u9ED8\u8BA4\uFF1A150\n\n\u2500\u2500 3. localstore.gitTrack \u2500\u2500\nlocalstore.json \u7684 git \u7B56\u7565\uFF1Aallow \u53EF\u63D0\u4EA4 / deny \u7981\u63D0\u4EA4\uFF08\u9ED8\u8BA4 deny\uFF09\u3002\ndeny \u65F6\u5199\u5165\u81EA\u52A8\u786E\u4FDD .gitignore \u542B\u8BE5\u6587\u4EF6\uFF08\u7269\u7406\u4FDD\u8BC1\uFF09\uFF0Ccontainer_git commit \u4F1A\u68C0\u67E5\u62D2\u7EDD\u3002\n\n Config:\n { \"localstore\": { \"gitTrack\": \"allow\" } }\n\n\u2500\u2500 4. hooks.autoRestoreSession \u2500\u2500\n\u4F1A\u8BDD\u81EA\u52A8\u6062\u590D\uFF08\u9ED8\u8BA4 true\uFF1B\u53D7 events \u95E8\u63A7\u2014\u2014\u4EC5\u6839\u4F1A\u8BDD + \u5DF2\u6709\u5BF9\u8BDD\u5386\u53F2\u624D\u6062\u590D\uFF09\u3002\n\n Config:\n { \"hooks\": { \"autoRestoreSession\": false } }\n\n\u2500\u2500 5. safeMode / blacklist \u2500\u2500\nsafe-mode \u7531 WebUI \u5F00\u5173\u63A7\u5236\uFF08\u5199 .serenity-safe-on \u6807\u8BB0\uFF09\uFF1B\u9ED1\u540D\u5355\u8DEF\u5F84\u53D7 guards seam \u62E6\u622A\u3002\n\n Config:\n { \"safeMode\": { \"blacklist\": [\".secrets/\"] } }\n\n\u2500\u2500 6. skiff.roles\uFF08F4 \u8BA4\u77E5\u5B50\u96C6\u89D2\u8272\uFF09\u2500\u2500\nSkiff \u89D2\u8272 = \u5168\u77E5\u5168\u80FD trajectory \u7684\u4EFB\u610F\u5B50\u96C6\uFF08CCC \u5B9A\u4E49\uFF09\u3002\u6BCF\u89D2\u8272\u72EC\u7ACB\u6A21\u578B + \u53CC\u767D\u540D\u5355\n\uFF08MSM \u767D\u540D\u5355 msms[] \u4E0E\u975E MSM \u5DE5\u5177\u767D\u540D\u5355 tools[]\uFF0C\u767D\u540D\u5355\u5916\u5168\u9690\u85CF\uFF1Bskill \u52A0\u8F7D\u6052\u53EF\u7528\uFF09\u3002\ntrajectory \u7EAA\u5F8B\u5B50\u96C6\uFF08session/keeper/rebuild \u53C2\u4E0E\u9879\uFF09\u9ED8\u8BA4\u5168\u5173 = \u5B8C\u5168\u72EC\u7ACB\u3002\nsystemPrompt \u5185\u8054 \u6216 systemPromptFile\uFF08.md \u5F15\u7528\uFF0C\u63A8\u8350\uFF09\u2014\u2014\u89D2\u8272\u4F1A\u8BDD\u7684\u5B8C\u6574\u4EBA\u683C/\u8FB9\u754C\u63D0\u793A\u8BCD\u3002\nvalidate \u6821\u9A8C / apply \u751F\u6548 / list \u67E5\u770B\uFF1Acontainer_admin role <guide|validate|apply|list>\uFF08skiff_admin \u5DF2\u5E76\u5165\uFF09\u3002\n\n Config:\n { \"skiff\": { \"roles\": {\n \"qa\": {\n \"model\": \"provider/model\",\n \"msms\": [\"web-search\", \"vlm-describe\"],\n \"tools\": [\"read\", \"grep\", \"glob\"],\n \"systemPromptFile\": \".opencode/skiff/qa.md\"\n } } } }\n\n\u2500\u2500 7. autopilotTrajectory\uFF08\u81EA\u52A8\u5DE1\u822A\u8F68\u8FF9\uFF09\u2500\u2500\nCCC \u5B9A\u4E49\u7684\u4E00\u6761\u81EA\u4E3B trajectory\u2014\u2014\u65F6\u949F\u5230\u70B9\u81EA\u52A8\u5524\u8D77\uFF08\u524D\u53F0\u6CE8\u5165\uFF0C\u7528\u6237\u53EF\u89C1\u53EF\u4ECB\u5165\uFF09\u3002\n\u672A\u914D\u7F6E\u6216 enabled=false \u2192 \u5B8C\u5168\u4E0D\u542F\u52A8\uFF08\u96F6\u8D44\u6E90\u5360\u7528\uFF09\u3002\u591A CCC \u72EC\u7ACB\uFF1A\u6BCF CCC \u81EA\u5DF1\u7684\u914D\u7F6E\u3002\n\u5524\u8D77\u6D88\u606F\u56DB\u6BB5\u5F0F\uFF1A\u8F68\u8FF9\u7126\u70B9 topPrompt\uFF08\u6700\u5148\u6CE8\u5165\uFF0C\u7A33\u5B9A\u951A\uFF09\u2192 \u8EAB\u4EFD\u951A\u5B9A \u2192 \u5148\u9A8C\u504F\u89C1\n\uFF08CCC \u6839\u811A\u672C biasProvider \u8F93\u51FA\uFF09\u2192 \u4EFB\u52A1\u3002\u76EE\u6807\u4F1A\u8BDD session \u5FC5\u586B\uFF08\u76EE\u5F55\u987B\u5E26 --auto \u540E\u7F00\uFF09\u3002\n\u8BCA\u65AD/\u72B6\u6001/\u7ACB\u5373\u5524\u8D77\uFF1Aautopilot-trajectory <all|check|status|diag>\uFF08msm(\"autopilot-trajectory\", [\"all\"])\uFF09\u3002\n\n Config:\n { \"autopilotTrajectory\": {\n \"enabled\": true,\n \"intervalHours\": 2,\n \"session\": \"S151\",\n \"biasProvider\": \"autopilot-bias.ts\",\n \"topPrompt\": \"\u672C\u8F68\u8FF9\u6838\u5FC3\u76EE\u6807/\u7EAA\u5F8B/\u8D28\u91CF\u8981\u6C42\uFF08CCC \u81EA\u586B\uFF0C\u9632\u6F02\u79FB\uFF09\",\n \"avoidWakeHours\": { \"start\": 8, \"end\": 18 }\n } }\n\n\u2500\u2500 8. weixin\uFF08\u5FAE\u4FE1\u6865 F4c-3\uFF0C\u542B\u6D88\u606F\u8BB0\u5F55 hook\uFF09\u2500\u2500\nCCC \u7EA7\u5FAE\u4FE1\u4E2A\u4EBA\u53F7\u63A5\u5165\uFF08iLink \u534F\u8BAE\uFF09\uFF1Adsh \u4E00\u8FDB\u7A0B\u591A CCC\uFF0C\u6BCF CCC \u72EC\u7ACB\u5BF9\u63A5\u5FAE\u4FE1\u6865\u3002\n\u8D26\u53F7/\u8DEF\u7531/\u5F00\u5173\u5728\u6B64\u6587\u4EF6\uFF1B**bot_token \u51ED\u636E\u5728 CCC localstore credential scope**\n\uFF08\u626B\u7801\u7ED1\u5B9A\u540E\u81EA\u52A8\u5199\u5165\uFF0C\u6C38\u4E0D\u8FDB git \u660E\u6587\u9762\uFF09\u3002\n\u8DEF\u7531 user \u2192 role\uFF1Aexact \u4F18\u5148\uFF0C* \u901A\u914D\u515C\u5E95\uFF1Brole \u5FC5\u987B \u2208 \u8BE5 CCC skiff.roles\u3002\n\u9762\u677F\uFF08WebUI \u8BBE\u7F6E \u2192 \u5FAE\u4FE1\u6865\uFF09\u53EF\u626B\u7801\u7ED1\u5B9A/\u79FB\u9664\u8D26\u53F7/\u7F16\u8F91\u8DEF\u7531\uFF1Bmsm(\"weixin-doctor\", [\"status\"|\"diag\"|\"verify\"|\"guide\"]) \u6392\u67E5 + \u5927\u800C\u5168\u6307\u5357\u3002\u51ED\u636E\u4E3B\u52A8\u67E5\u770B\uFF1Alocalstore get WEIXIN_<ACCOUNT>_TOKEN\u3002\n \u25B8 \u4E3B\u52A8\u53D1\u9001\uFF08v1.30.9\uFF09\uFF1ACCC \u7528 msm(\"weixin-send\", [\"send\", \"--ccc\", \"<CCC>\", \"--user\", \"<\u522B\u540D|id>\", \"<\u6587\u672C>\"])\n \u7ED9\u7528\u6237\u53D1\u6D88\u606F\u2014\u2014\u7ECF dsp \u4E3B\u52A8\u53D1\u9001\u5165\u53E3\uFF08**\u53EA\u7ED1 127.0.0.1**\uFF0Cplugin \u5168\u5C40\u914D\u7F6E ~/.dsh/serenity-hooks.json\n \u7684 weixinApi.port\uFF0C\u9ED8\u8BA4 3082\uFF1B\u516C\u7F51\u4E0D\u53EF\u8FBE\u6545\u65E0\u5BC6\u94A5\uFF09\u2192 weixin-bridge.sendProactiveText \u2192\n \u6210\u529F\u540E\u89E6\u53D1 outgoing hook\uFF08source=\"proactive\"\uFF09\u3002send-file \u4ECD\u7531 MSM \u76F4\u8FDE iLink \u4E0A\u4F20\u5E76\u81EA\u884C\u8865\u8BB0\u3002\n\n Config:\n { \"weixin\": {\n \"enabled\": true,\n \"hook\": \"scripts/weixin-message-hook.ts\",\n \"accounts\": [{ \"accountId\": \"wechat-1\", \"name\": \"\u5BB6\u5EAD\u52A9\u624B\", \"enabled\": true }],\n \"routes\": [{ \"user\": \"*\", \"role\": \"zhaocai\" }]\n } }\n\n \u25B8 weixin.hook\uFF08\u6D88\u606F\u8BB0\u5F55 hook\uFF0Cv1.27.13\uFF09\uFF1A\n \u5FAE\u4FE1\u6865\u6BCF\u6536/\u53D1\u4E00\u6761\u6D88\u606F\u89E6\u53D1\u4E00\u6B21 CCC \u81EA\u5199\u811A\u672C\uFF0C\u7531 CCC \u81EA\u884C\u6301\u4E45\u5316\u4FDD\u5B58\uFF08\u5B58\u54EA/\u5B58\u6210\u4EC0\u4E48\n /\u662F\u5426\u5165\u5E93\u5168\u5F52 CCC\u2014\u2014ACC \u4E0D\u7ED1\u5B9A\u5B58\u50A8\uFF09\u3002\u672A\u914D\u7F6E hook \u2192 \u96F6\u53D8\u5316\uFF08\u4E0D\u89E6\u53D1\uFF09\u3002\n\n \u89E6\u53D1\uFF08\u53CC\u5411\uFF09\uFF1A\n incoming = \u7528\u6237 \u2192 bot\uFF1A\u8DEF\u7531\u547D\u4E2D\u540E\u3001\u5A92\u4F53\u843D\u76D8\u540E\u89E6\u53D1\uFF08\u6587\u672C\u542B\u8BED\u97F3\u8F6C\u5199\uFF1B\u5A92\u4F53\u5E26\u843D\u76D8 relPath\uFF09\n outgoing = bot \u2192 \u7528\u6237\uFF1A\u53D1\u9001\u6210\u529F\u540E\u89E6\u53D1\uFF08reply = \u7528\u6237\u5B9E\u9645\u6536\u5230\u7684\u7EAF\u6587\u672C\uFF0C\u5DF2\u5265\u79BB think\uFF09\n \u00B7 source=\"reply\"\uFF08\u7F3A\u7701\uFF09= \u56DE\u590D\u7528\u6237\u6D88\u606F\uFF1Bsource=\"proactive\"\uFF08v1.30.9\uFF09= bot \u4E3B\u52A8\u53D1\u8D77\n \uFF08CCC \u7ECF weixin-send MSM \u2192 dsp \u4E3B\u52A8\u53D1\u9001\u5165\u53E3 \u2192 sendProactiveText\uFF09\n \u00B7 file\uFF08\u4EC5 send-file \u8865\u8BB0\uFF09\uFF1A{ \"name\": \"\u62A5\u544A.pdf\", \"size\": 12345, \"caption\": \"\u53EF\u9009\" }\n\n \u811A\u672C\u7EA6\u5B9A\uFF08\u4E8B\u4EF6 JSON \u5355\u884C\u7ECF stdin \u4F20\u5165\uFF1Bbun \u4F18\u5148 node \u515C\u5E95\uFF09\uFF1A\n #!/usr/bin/env bun \u6216 node script.js \u2014\u2014 \u8BFB process.stdin \u6574\u6BB5 JSON.parse\n const ev = JSON.parse(await new Response(process.stdin).text())\n ev.event === 'incoming' | 'outgoing'\n\n \u4E8B\u4EF6 schema\uFF08**\u4E0D\u542B\u4EFB\u4F55\u4F1A\u8BDD\u51ED\u636E**\u2014\u2014context_token/bot_token/token/aes_key \u5747\u4E0D\u51FA\u73B0\uFF09\uFF1A\n { \"event\": \"incoming\", \"ts\": 1788359941488, \"cccRoot\": \"/path/ccc\",\n \"accountId\": \"wechat-1\", \"userId\": \"u1@im.wechat\",\n \"sessionId\": \"skiff-weixin-xxx\", \"role\": \"zhaocai\",\n \"message\": { \"text\": \"\u4F60\u597D\", \"media\": [{ \"kind\": \"image\", \"relPath\": \"_tmp/weixin-inbound/<hash>/img_x.jpg\" }] } }\n { \"event\": \"outgoing\", \"ts\": ..., \"cccRoot\": ..., \"accountId\": ...,\n \"userId\": ..., \"sessionId\": ..., \"role\": ...,\n \"reply\": \"\u5DF2\u8BB0\u5F55\uFF08\u7EAF\u6587\u672C\uFF09\", \"source\": \"reply\" | \"proactive\" }\n\n \u793A\u4F8B\u811A\u672C\uFF08\u8FFD\u52A0\u5230\u6309\u65E5\u6587\u4EF6\u2014\u2014\u6301\u4E45\u5316\u5F52 CCC \u81EA\u9009\uFF1A\u6587\u4EF6/DB/\u8FDC\u7AEF\u5747\u53EF\uFF09\uFF1A\n const fs = require('node:fs'); const p = '/path/ccc/AGENT_SESSIONS/_weixin-log.jsonl';\n fs.appendFileSync(p, JSON.stringify(ev) + '\\n');\n\n \u6267\u884C\u8BED\u4E49\uFF08\u65C1\u8DEF\u5BB9\u5FCD\uFF09\uFF1A\u5F02\u6B65 fire-and-forget + 15s \u8D85\u65F6 kill + \u5931\u8D25\u4EC5\u65E5\u5FD7\u2014\u2014\n \u5FAE\u4FE1\u6865\u6D88\u606F\u5904\u7406/\u56DE\u590D\u4E0D\u53D7 hook \u5F71\u54CD\uFF1B\u811A\u672C\u987B\u5728 CCC \u6839\u5185\uFF08\u8DEF\u5F84\u9003\u9038\u62D2\u7EDD\uFF09\u3002\n \u5A92\u4F53 relPath \u6307\u5411 _tmp/weixin-inbound/\uFF08\u4E34\u65F6\u76EE\u5F55\uFF09\u2014\u2014\u9700\u6301\u4E45\u4FDD\u5B58\u5A92\u4F53\u6587\u4EF6\u8BF7\u81EA\u884C copy\u3002\n";
22
+ export declare const CCC_CONFIG_REFERENCE = "\u2550\u2550\u2550 CCC Configuration Reference \u2550\u2550\u2550\n\nCCC-level features are configured in .opencode/serenity.json.\nBelow are all available configuration sections.\n\n\u2500\u2500 1. handyman.models \u2500\u2500\nhandyman\uFF08\u6742\u5DE5\uFF09\u5DE5\u5177\u53EF\u7528\u6A21\u578B\u767D\u540D\u5355\uFF08provider/model \u5217\u8868\uFF09\uFF1B\u672A\u914D\u7F6E\u65F6 handyman \u62A5\u9519\u8981\u6C42\u914D\u7F6E\u3002\n\u7F3A\u7701\u6A21\u578B = models[0]\uFF08\u53EF\u7528 handyman.defaultModel \u6307\u5B9A\uFF0C\u5FC5\u987B \u2208 models\uFF09\u3002\n\n Config:\n { \"handyman\": { \"models\": [\"provider/model-name\"] } }\n\n Example:\n { \"handyman\": { \"models\": [\"minimax-cn-coding-plan/MiniMax-M3\"], \"defaultModel\": \"minimax-cn-coding-plan/MiniMax-M3\", \"maxRounds\": 100, \"maxParallel\": 10 } }\n\n\u2500\u2500 2. sessionKeeper.threshold \u2500\u2500\ntrajectory-assistant\uFF08TRAJECTORY-ASSISTANT \u00B7 CHECKPOINT\uFF09\u63D0\u9192\u673A\u5236\u7684\u79EF\u5206\u9608\u503C\uFF08\u975E headless \u4E3B agent\uFF09\u3002\n\u6309\u5DE5\u5177\u8C03\u7528\u52A0\u6743 + \u8017\u65F6\u8BA1\u5206\uFF1B\u8FBE\u5230\u9608\u503C\u6CE8\u5165\u63D0\u9192\uFF0C\u8981\u6C42\u6A21\u578B\u56DE\u590D ACK \u7801\u3002\n\n Config:\n { \"sessionKeeper\": { \"threshold\": 150 } }\n\n \u8BA1\u5206\uFF1Awrite/edit = 3\uFF0Ctask = 10\uFF0Cread/grep/glob/msm \u7B49 = 1\uFF0C\u65F6\u95F4 = 1/\u5206\u949F\n \u9ED8\u8BA4\uFF1A150\n\n\u2500\u2500 3. localstore.gitTrack \u2500\u2500\nlocalstore.json \u7684 git \u7B56\u7565\uFF1Aallow \u53EF\u63D0\u4EA4 / deny \u7981\u63D0\u4EA4\uFF08\u9ED8\u8BA4 deny\uFF09\u3002\ndeny \u65F6\u5199\u5165\u81EA\u52A8\u786E\u4FDD .gitignore \u542B\u8BE5\u6587\u4EF6\uFF08\u7269\u7406\u4FDD\u8BC1\uFF09\uFF0Ccontainer_git commit \u4F1A\u68C0\u67E5\u62D2\u7EDD\u3002\n\n Config:\n { \"localstore\": { \"gitTrack\": \"allow\" } }\n\n\u2500\u2500 4. hooks.autoRestoreSession \u2500\u2500\n\u4F1A\u8BDD\u81EA\u52A8\u6062\u590D\uFF08\u9ED8\u8BA4 true\uFF1B\u53D7 events \u95E8\u63A7\u2014\u2014\u4EC5\u6839\u4F1A\u8BDD + \u5DF2\u6709\u5BF9\u8BDD\u5386\u53F2\u624D\u6062\u590D\uFF09\u3002\n\n Config:\n { \"hooks\": { \"autoRestoreSession\": false } }\n\n\u2500\u2500 5. safeMode / blacklist \u2500\u2500\nsafe-mode \u7531 WebUI \u5F00\u5173\u63A7\u5236\uFF08\u5199 .serenity-safe-on \u6807\u8BB0\uFF09\uFF1B\u9ED1\u540D\u5355\u8DEF\u5F84\u53D7 guards seam \u62E6\u622A\u3002\n\n Config:\n { \"safeMode\": { \"blacklist\": [\".secrets/\"] } }\n\n\u2500\u2500 6. skiff.roles\uFF08F4 \u8BA4\u77E5\u5B50\u96C6\u89D2\u8272\uFF09\u2500\u2500\nSkiff \u89D2\u8272 = \u5168\u77E5\u5168\u80FD trajectory \u7684\u4EFB\u610F\u5B50\u96C6\uFF08CCC \u5B9A\u4E49\uFF09\u3002\u6BCF\u89D2\u8272\u72EC\u7ACB\u6A21\u578B + \u53CC\u767D\u540D\u5355\n\uFF08MSM \u767D\u540D\u5355 msms[] \u4E0E\u975E MSM \u5DE5\u5177\u767D\u540D\u5355 tools[]\uFF0C\u767D\u540D\u5355\u5916\u5168\u9690\u85CF\uFF1Bskill \u52A0\u8F7D\u6052\u53EF\u7528\uFF09\u3002\ntrajectory \u7EAA\u5F8B\u5B50\u96C6\uFF08session/keeper/rebuild \u53C2\u4E0E\u9879\uFF09\u9ED8\u8BA4\u5168\u5173 = \u5B8C\u5168\u72EC\u7ACB\u3002\nsystemPrompt \u5185\u8054 \u6216 systemPromptFile\uFF08.md \u5F15\u7528\uFF0C\u63A8\u8350\uFF09\u2014\u2014\u89D2\u8272\u4F1A\u8BDD\u7684\u5B8C\u6574\u4EBA\u683C/\u8FB9\u754C\u63D0\u793A\u8BCD\u3002\nvalidate \u6821\u9A8C / apply \u751F\u6548 / list \u67E5\u770B\uFF1Acontainer_admin role <guide|validate|apply|list>\uFF08skiff_admin \u5DF2\u5E76\u5165\uFF09\u3002\n\n Config:\n { \"skiff\": { \"roles\": {\n \"qa\": {\n \"model\": \"provider/model\",\n \"msms\": [\"web-search\", \"vlm-describe\"],\n \"tools\": [\"read\", \"grep\", \"glob\"],\n \"systemPromptFile\": \".opencode/skiff/qa.md\"\n } } } }\n\n\u2500\u2500 7. autopilotTrajectory\uFF08\u81EA\u52A8\u5DE1\u822A\u8F68\u8FF9\uFF09\u2500\u2500\nCCC \u5B9A\u4E49\u7684\u4E00\u6761\u81EA\u4E3B trajectory\u2014\u2014\u65F6\u949F\u5230\u70B9\u81EA\u52A8\u5524\u8D77\uFF08\u524D\u53F0\u6CE8\u5165\uFF0C\u7528\u6237\u53EF\u89C1\u53EF\u4ECB\u5165\uFF09\u3002\n\u672A\u914D\u7F6E\u6216 enabled=false \u2192 \u5B8C\u5168\u4E0D\u542F\u52A8\uFF08\u96F6\u8D44\u6E90\u5360\u7528\uFF09\u3002\u591A CCC \u72EC\u7ACB\uFF1A\u6BCF CCC \u81EA\u5DF1\u7684\u914D\u7F6E\u3002\n\u5524\u8D77\u6D88\u606F\u56DB\u6BB5\u5F0F\uFF1A\u8F68\u8FF9\u7126\u70B9 topPrompt\uFF08\u6700\u5148\u6CE8\u5165\uFF0C\u7A33\u5B9A\u951A\uFF09\u2192 \u8EAB\u4EFD\u951A\u5B9A \u2192 \u5148\u9A8C\u504F\u89C1\n\uFF08CCC \u6839\u811A\u672C biasProvider \u8F93\u51FA\uFF09\u2192 \u4EFB\u52A1\u3002\u76EE\u6807\u4F1A\u8BDD session \u5FC5\u586B\uFF08\u76EE\u5F55\u987B\u5E26 --auto \u540E\u7F00\uFF09\u3002\n\u8BCA\u65AD/\u72B6\u6001/\u7ACB\u5373\u5524\u8D77\uFF1Aautopilot-trajectory <all|check|status|diag>\uFF08msm(\"autopilot-trajectory\", [\"all\"])\uFF09\u3002\n\n Config:\n { \"autopilotTrajectory\": {\n \"enabled\": true,\n \"intervalHours\": 2,\n \"session\": \"S151\",\n \"biasProvider\": \"autopilot-bias.ts\",\n \"topPrompt\": \"\u672C\u8F68\u8FF9\u6838\u5FC3\u76EE\u6807/\u7EAA\u5F8B/\u8D28\u91CF\u8981\u6C42\uFF08CCC \u81EA\u586B\uFF0C\u9632\u6F02\u79FB\uFF09\",\n \"avoidWakeHours\": { \"start\": 8, \"end\": 18 }\n } }\n\n\u2500\u2500 8. weixin\uFF08\u5FAE\u4FE1\u6865 F4c-3\uFF0C\u542B\u6D88\u606F\u8BB0\u5F55 hook\uFF09\u2500\u2500\nCCC \u7EA7\u5FAE\u4FE1\u4E2A\u4EBA\u53F7\u63A5\u5165\uFF08iLink \u534F\u8BAE\uFF09\uFF1Adsh \u4E00\u8FDB\u7A0B\u591A CCC\uFF0C\u6BCF CCC \u72EC\u7ACB\u5BF9\u63A5\u5FAE\u4FE1\u6865\u3002\n\u8D26\u53F7/\u8DEF\u7531/\u5F00\u5173\u5728\u6B64\u6587\u4EF6\uFF1B**bot_token \u51ED\u636E\u5728 CCC localstore credential scope**\n\uFF08\u626B\u7801\u7ED1\u5B9A\u540E\u81EA\u52A8\u5199\u5165\uFF0C\u6C38\u4E0D\u8FDB git \u660E\u6587\u9762\uFF09\u3002\n\u8DEF\u7531 user \u2192 role\uFF1Aexact \u4F18\u5148\uFF0C* \u901A\u914D\u515C\u5E95\uFF1Brole \u5FC5\u987B \u2208 \u8BE5 CCC skiff.roles\u3002\n\u9762\u677F\uFF08WebUI \u8BBE\u7F6E \u2192 \u5FAE\u4FE1\u6865\uFF09\u53EF\u626B\u7801\u7ED1\u5B9A/\u79FB\u9664\u8D26\u53F7/\u7F16\u8F91\u8DEF\u7531\uFF1Bmsm(\"weixin-doctor\", [\"status\"|\"diag\"|\"verify\"|\"guide\"]) \u6392\u67E5 + \u5927\u800C\u5168\u6307\u5357\u3002\u51ED\u636E\u4E3B\u52A8\u67E5\u770B\uFF1Alocalstore get WEIXIN_<ACCOUNT>_TOKEN\u3002\n \u25B8 \u4E3B\u52A8\u53D1\u9001\uFF08v1.30.9\uFF09\uFF1ACCC \u7528 msm(\"weixin-send\", [\"send\", \"--ccc\", \"<CCC>\", \"--user\", \"<\u522B\u540D|id>\", \"<\u6587\u672C>\"])\n \u7ED9\u7528\u6237\u53D1\u6D88\u606F\u2014\u2014\u7ECF dsp \u4E3B\u52A8\u53D1\u9001\u5165\u53E3\uFF08**\u53EA\u7ED1 127.0.0.1**\uFF0Cplugin \u5168\u5C40\u914D\u7F6E ~/.dsh/serenity-hooks.json\n \u7684 weixinApi.port\uFF0C\u9ED8\u8BA4 3082\uFF1B\u516C\u7F51\u4E0D\u53EF\u8FBE\u6545\u65E0\u5BC6\u94A5\uFF09\u2192 weixin-bridge.sendProactiveText \u2192\n \u6210\u529F\u540E\u89E6\u53D1 outgoing hook\uFF08source=\"proactive\"\uFF09\u3002send-file \u4ECD\u7531 MSM \u76F4\u8FDE iLink \u4E0A\u4F20\u5E76\u81EA\u884C\u8865\u8BB0\u3002\n\n \u25B8 weixin.autoReplyWithLastMessage\uFF08v1.30.10\uFF0C\u5173\u95ED\u6865\u7684\u81EA\u52A8\u8F93\u51FA\uFF09\uFF1A\n \u7F3A\u7701 true = \u73B0\u72B6\uFF1A\u6865\u5728 agent \u4E00\u8F6E\u7ED3\u675F\u540E\u628A**\u6700\u7EC8 assistant \u6587\u672C**\u81EA\u52A8\u56DE\u53D1\u7528\u6237\uFF08source=\"reply\"\uFF09\u3002\n \u663E\u5F0F false = \u5173\u95ED\uFF1A\u6865**\u4E0D\u518D\u8F6C\u53D1\u6700\u7EC8\u6587\u672C**\uFF0C\u8F93\u51FA\u6743\u4EA4\u7ED9 agent\u2014\u2014\u6BCF\u8F6E question \u524D\u7F00\u6CE8\u5165\n \u300CReply Output (manual mode)\u300D\u7EAA\u5F8B\u5757\uFF08\u542B\u53EF\u76F4\u63A5\u7167\u6284\u7684 weixin-send \u547D\u4EE4\uFF0CCCC \u6839/\u8D26\u53F7/\u7528\u6237 id\n \u5DF2\u586B\u597D\uFF09\uFF0Cagent \u81EA\u5DF1\u51B3\u5B9A\u53D1\u4EC0\u4E48\u3001\u53D1\u51E0\u6761\uFF08\u5168\u90E8\u8BB0 source=\"proactive\"\uFF09\u3002\n \u8BED\u4E49\u8FB9\u754C\uFF08\u7528\u6237\u62CD\u677F\uFF09\uFF1A\u53EA\u5173\u6700\u7EC8\u6587\u672C\uFF1B\u7CFB\u7EDF\u7C7B\u6D88\u606F\uFF08\u65B0\u5BF9\u8BDD\u901A\u77E5 / \u8BED\u97F3\u65E0\u6CD5\u89E3\u6790\u63D0\u793A\uFF09\u4FDD\u7559\uFF1B\n **agent \u4E00\u8F6E\u672A\u8F93\u51FA \u2192 \u9759\u9ED8\u4E0D\u515C\u5E95**\uFF08\u5426\u5219\u5F00\u5173\u5931\u53BB\u610F\u4E49\uFF09\uFF1Btyping \u6307\u793A\u4E0E incoming hook \u4E0D\u53D7\u5F71\u54CD\u3002\n\n Config:\n { \"weixin\": {\n \"enabled\": true,\n \"hook\": \"scripts/weixin-message-hook.ts\",\n \"autoReplyWithLastMessage\": true,\n \"accounts\": [{ \"accountId\": \"wechat-1\", \"name\": \"\u5BB6\u5EAD\u52A9\u624B\", \"enabled\": true }],\n \"routes\": [{ \"user\": \"*\", \"role\": \"zhaocai\" }]\n } }\n\n \u25B8 weixin.hook\uFF08\u6D88\u606F\u8BB0\u5F55 hook\uFF0Cv1.27.13\uFF09\uFF1A\n \u5FAE\u4FE1\u6865\u6BCF\u6536/\u53D1\u4E00\u6761\u6D88\u606F\u89E6\u53D1\u4E00\u6B21 CCC \u81EA\u5199\u811A\u672C\uFF0C\u7531 CCC \u81EA\u884C\u6301\u4E45\u5316\u4FDD\u5B58\uFF08\u5B58\u54EA/\u5B58\u6210\u4EC0\u4E48\n /\u662F\u5426\u5165\u5E93\u5168\u5F52 CCC\u2014\u2014ACC \u4E0D\u7ED1\u5B9A\u5B58\u50A8\uFF09\u3002\u672A\u914D\u7F6E hook \u2192 \u96F6\u53D8\u5316\uFF08\u4E0D\u89E6\u53D1\uFF09\u3002\n\n \u89E6\u53D1\uFF08\u53CC\u5411\uFF09\uFF1A\n incoming = \u7528\u6237 \u2192 bot\uFF1A\u8DEF\u7531\u547D\u4E2D\u540E\u3001\u5A92\u4F53\u843D\u76D8\u540E\u89E6\u53D1\uFF08\u6587\u672C\u542B\u8BED\u97F3\u8F6C\u5199\uFF1B\u5A92\u4F53\u5E26\u843D\u76D8 relPath\uFF09\n outgoing = bot \u2192 \u7528\u6237\uFF1A\u53D1\u9001\u6210\u529F\u540E\u89E6\u53D1\uFF08reply = \u7528\u6237\u5B9E\u9645\u6536\u5230\u7684\u7EAF\u6587\u672C\uFF0C\u5DF2\u5265\u79BB think\uFF09\n \u00B7 source=\"reply\"\uFF08\u7F3A\u7701\uFF09= \u56DE\u590D\u7528\u6237\u6D88\u606F\uFF1Bsource=\"proactive\"\uFF08v1.30.9\uFF09= bot \u4E3B\u52A8\u53D1\u8D77\n \uFF08CCC \u7ECF weixin-send MSM \u2192 dsp \u4E3B\u52A8\u53D1\u9001\u5165\u53E3 \u2192 sendProactiveText\uFF09\n \u00B7 file\uFF08\u4EC5 send-file \u8865\u8BB0\uFF09\uFF1A{ \"name\": \"\u62A5\u544A.pdf\", \"size\": 12345, \"caption\": \"\u53EF\u9009\" }\n\n \u811A\u672C\u7EA6\u5B9A\uFF08\u4E8B\u4EF6 JSON \u5355\u884C\u7ECF stdin \u4F20\u5165\uFF1Bbun \u4F18\u5148 node \u515C\u5E95\uFF09\uFF1A\n #!/usr/bin/env bun \u6216 node script.js \u2014\u2014 \u8BFB process.stdin \u6574\u6BB5 JSON.parse\n const ev = JSON.parse(await new Response(process.stdin).text())\n ev.event === 'incoming' | 'outgoing'\n\n \u4E8B\u4EF6 schema\uFF08**\u4E0D\u542B\u4EFB\u4F55\u4F1A\u8BDD\u51ED\u636E**\u2014\u2014context_token/bot_token/token/aes_key \u5747\u4E0D\u51FA\u73B0\uFF09\uFF1A\n { \"event\": \"incoming\", \"ts\": 1788359941488, \"cccRoot\": \"/path/ccc\",\n \"accountId\": \"wechat-1\", \"userId\": \"u1@im.wechat\",\n \"sessionId\": \"skiff-weixin-xxx\", \"role\": \"zhaocai\",\n \"message\": { \"text\": \"\u4F60\u597D\", \"media\": [{ \"kind\": \"image\", \"relPath\": \"_tmp/weixin-inbound/<hash>/img_x.jpg\" }] } }\n { \"event\": \"outgoing\", \"ts\": ..., \"cccRoot\": ..., \"accountId\": ...,\n \"userId\": ..., \"sessionId\": ..., \"role\": ...,\n \"reply\": \"\u5DF2\u8BB0\u5F55\uFF08\u7EAF\u6587\u672C\uFF09\", \"source\": \"reply\" | \"proactive\" }\n\n \u793A\u4F8B\u811A\u672C\uFF08\u8FFD\u52A0\u5230\u6309\u65E5\u6587\u4EF6\u2014\u2014\u6301\u4E45\u5316\u5F52 CCC \u81EA\u9009\uFF1A\u6587\u4EF6/DB/\u8FDC\u7AEF\u5747\u53EF\uFF09\uFF1A\n const fs = require('node:fs'); const p = '/path/ccc/AGENT_SESSIONS/_weixin-log.jsonl';\n fs.appendFileSync(p, JSON.stringify(ev) + '\\n');\n\n \u6267\u884C\u8BED\u4E49\uFF08\u65C1\u8DEF\u5BB9\u5FCD\uFF09\uFF1A\u5F02\u6B65 fire-and-forget + 15s \u8D85\u65F6 kill + \u5931\u8D25\u4EC5\u65E5\u5FD7\u2014\u2014\n \u5FAE\u4FE1\u6865\u6D88\u606F\u5904\u7406/\u56DE\u590D\u4E0D\u53D7 hook \u5F71\u54CD\uFF1B\u811A\u672C\u987B\u5728 CCC \u6839\u5185\uFF08\u8DEF\u5F84\u9003\u9038\u62D2\u7EDD\uFF09\u3002\n \u5A92\u4F53 relPath \u6307\u5411 _tmp/weixin-inbound/\uFF08\u4E34\u65F6\u76EE\u5F55\uFF09\u2014\u2014\u9700\u6301\u4E45\u4FDD\u5B58\u5A92\u4F53\u6587\u4EF6\u8BF7\u81EA\u884C copy\u3002\n";
23
23
  export interface MsmFlag {
24
24
  name: string;
25
25
  type?: string;
@@ -17,6 +17,17 @@ import { type WeixinAccountCredential } from './weixin-route.js';
17
17
  import { type WeixinMessage } from './weixin-api.js';
18
18
  /** 测试辅助:清空 typing_ticket 缓存(生产零调用) */
19
19
  export declare function resetWeixinTypingCache(): void;
20
+ /**
21
+ * 手动输出模式的**输出纪律块**(v1.30.10,`weixin.autoReplyWithLastMessage: false` 时注入)。
22
+ *
23
+ * 为什么必须注入(R↓):关掉自动回发后,agent 若不知道"桥不会替你转发",用户就收不到任何回复
24
+ * ——机制变更必须伴随可执行的行动指引。因此这里给的是**可直接照抄的完整命令**(CCC 根 + 账号 +
25
+ * 用户 id 全部填好),而不是抽象提示(E↑)。
26
+ *
27
+ * 参数全部显式:`--ccc`(必填,无隐式当前 CCC)、`--account`(沿用收到消息的账号,避免多账号下
28
+ * 用错 bot 身份回话)、`--user`(该用户 iLink id,免去别名解析)。
29
+ */
30
+ export declare function weixinManualOutputLine(root: string, accountId: string, userId: string): string;
20
31
  /**
21
32
  * 处理单条微信消息:路由 → skiff 会话(固定 id 创建/延续)→ 提问 → 回复回写。
22
33
  *
@@ -347,6 +347,7 @@ function readWeixinSettings(root, paths = DEFAULT_SERENITY_CONFIG_PATHS) {
347
347
  enabled: w.enabled === true,
348
348
  botType: typeof w.botType === "string" && w.botType !== "" ? w.botType : void 0,
349
349
  hook: typeof w.hook === "string" && w.hook.trim() !== "" ? w.hook.trim() : void 0,
350
+ autoReplyWithLastMessage: w.autoReplyWithLastMessage !== false,
350
351
  accounts: Array.isArray(w.accounts) ? w.accounts.filter((a) => typeof a === "object" && a !== null && typeof a.accountId === "string" && a.accountId !== "").map((a) => ({
351
352
  accountId: a.accountId,
352
353
  name: typeof a.name === "string" ? a.name : void 0,
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@shgroup/dsh-serenity-hooks",
3
- "version": "1.30.9",
4
- "description": "宁静号 ACC harness — Native Cordis 插件(DeepSeek Harness 运行时)。真实 DSH 工具注册(cc_fs/session/acc_msm 等 9 工具)+ 拦截缝机械约束(safe-mode/路径守卫/会话落盘)+ 系统提示词注入(ACC/CCE/Constraints/SKILL/Session 五块)。适配 DSH 公开版(deepseek-ai/deepseek-harness 0.1.0-rc)。",
3
+ "version": "1.30.11",
4
+ "description": "宁静号 ACC harness(Native Cordis 插件)——给 DeepSeek Harness 装一个「AI 工作区」:10 个工具(container_fs/logbook/dashboard/container_git/msm/praxis/handyman/localstore/container_admin/autopilot-trajectory)+ 机械约束(安全模式/工作区围墙/密钥守卫/对外输出守卫)+ 工作日志与原地重建 + 网页登录入口/微信桥/子角色/对外问答页/自主巡航。适配 DSH 0.1.2-rc.1。",
5
5
  "license": "MIT",
6
6
  "repository": {
7
7
  "type": "git",