@shgroup/dsh-serenity-hooks 1.27.12 → 1.27.14
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 +7 -5
- package/dsh.plugin.json +1 -1
- package/lib/ccc.d.ts +7 -0
- package/lib/client.js +4 -4
- package/lib/index.js +290 -14
- package/lib/msm-ops.d.ts +1 -1
- package/lib/{weixin-api-DAugVJ-f.js → weixin-api-OUCuw0Ws.js} +1 -1
- package/lib/weixin-hook.d.ts +103 -0
- package/lib/{weixin-route-EVu56Ytp.js → weixin-route-BUJLsCGS.js} +1 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
# @shgroup/dsh-serenity-hooks
|
|
2
2
|
|
|
3
|
-
宁静号 ACC(Abstract Cognitive Container)harness — **DeepSeek Harness 原生 Cordis 插件**(npm 发布单元,v1.
|
|
3
|
+
宁静号 ACC(Abstract Cognitive Container)harness — **DeepSeek Harness 原生 Cordis 插件**(npm 发布单元,v1.27.12)。
|
|
4
4
|
|
|
5
|
-
为 DSH 会话提供认知容器基础设施:真实 DSH 工具(
|
|
5
|
+
为 DSH 会话提供认知容器基础设施:真实 DSH 工具(13 个)+ 拦截缝机械约束(safe-mode / 路径守卫 / 凭据守卫 / 输出守卫)+ 系统提示词注入(8 块,对齐 opencode-serenity-plugin)+ WebUI 状态胶囊 + 外部访问(双端口网关 / Skiff 问答 / ACP+Skiff 问答页)+ 微信桥(F4c-3 iLink 接入)+ Autopilot Trajectory(时钟驱动自主巡航)。
|
|
6
6
|
|
|
7
7
|
## 安装
|
|
8
8
|
|
|
@@ -12,9 +12,9 @@ dsh plugin --profile web add @shgroup/dsh-serenity-hooks
|
|
|
12
12
|
|
|
13
13
|
安装后重启 dsh web。在带 `.serenity` 标记的 CCC 目录中的会话自动获得全部能力。
|
|
14
14
|
|
|
15
|
-
## 工具(
|
|
15
|
+
## 工具(13)
|
|
16
16
|
|
|
17
|
-
`cc_fs` / `session` / `acc_kit` / `cc_git` / `acc_msm` / `eap` / `neat` / `cce` / `handyman` / `session_rebuild` / `localstore` / `skiff_admin`
|
|
17
|
+
`cc_fs` / `session` / `acc_kit` / `cc_git` / `acc_msm` / `eap` / `neat` / `cce` / `handyman` / `session_rebuild` / `localstore` / `skiff_admin` / `autopilot-trajectory`
|
|
18
18
|
|
|
19
19
|
## 系统提示词(8 块)
|
|
20
20
|
|
|
@@ -29,7 +29,9 @@ dsh plugin --profile web add @shgroup/dsh-serenity-hooks
|
|
|
29
29
|
"handyman": { "models": ["provider/model"], "defaultModel": "provider/model" },
|
|
30
30
|
"sessionKeeper": { "threshold": 100 },
|
|
31
31
|
"safeMode": { "blacklist": [".secrets/", "regex:\\.env$"] },
|
|
32
|
-
"skiff": { "roles": { "qa": { "msms": [], "tools": [], "systemPrompt": "" } } }
|
|
32
|
+
"skiff": { "roles": { "qa": { "msms": [], "tools": [], "systemPrompt": "" } } },
|
|
33
|
+
"autopilotTrajectory": { "enabled": true, "intervalHours": 2, "session": "S###", "biasProvider": "autopilot-bias.ts", "topPrompt": "…" },
|
|
34
|
+
"weixin": { "enabled": true, "accounts": [{ "accountId": "wechat-1", "name": "微信1", "enabled": true }], "routes": [{ "user": "*", "role": "zhaocai" }] }
|
|
33
35
|
}
|
|
34
36
|
```
|
|
35
37
|
|
package/dsh.plugin.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"id": "dsh-serenity-hooks",
|
|
3
|
-
"version": "1.27.
|
|
3
|
+
"version": "1.27.14",
|
|
4
4
|
"main": "lib/index.js",
|
|
5
5
|
"description": "宁静号 ACC harness(Native Cordis 插件):真实 DSH 工具 cc_fs/session/acc_msm/eap/neat/cce/handyman/session_rebuild/skiff_admin + 拦截缝机械约束(safe-mode/路径守卫)+ 高级设定面板(双端口网关/账号管理)+ Skiff 认知子集角色(实验性)。适配 DSH 公开版(0.1.0-rc,deepseek-ai/deepseek-harness)。",
|
|
6
6
|
"engines": {
|
package/lib/ccc.d.ts
CHANGED
|
@@ -123,6 +123,13 @@ export interface WeixinSettings {
|
|
|
123
123
|
accounts?: WeixinAccountConfig[];
|
|
124
124
|
/** 消息路由:微信用户 → 该 CCC 的 skiff role(不绑定具体 role——用户自选) */
|
|
125
125
|
routes?: WeixinRouteConfig[];
|
|
126
|
+
/**
|
|
127
|
+
* 消息记录 hook(v1.27.13,用户"微信桥所有消息记录,支持 hook,CCC 自写持久性保存"):
|
|
128
|
+
* 相对 CCC 根的脚本路径——微信桥每收/发一条消息触发一次(事件 JSON 经 stdin 传入,
|
|
129
|
+
* 脚本自行持久化:写文件/入库/推远端全归 CCC)。未配置 → 零变化(不触发)。
|
|
130
|
+
* 执行语义:旁路 fire-and-forget + 超时 + 失败仅日志(微信桥可靠性不受 hook 影响)。
|
|
131
|
+
*/
|
|
132
|
+
hook?: string;
|
|
126
133
|
}
|
|
127
134
|
/** 微信桥账号配置(serenity.json 内;凭据部分在 localstore) */
|
|
128
135
|
export interface WeixinAccountConfig {
|
package/lib/client.js
CHANGED
|
@@ -8,7 +8,7 @@ window.__ModuleLoader__.load({
|
|
|
8
8
|
let react = require("react");
|
|
9
9
|
let react_jsx_runtime = require("react/jsx-runtime");
|
|
10
10
|
//#region \0sp-css:/home/yh/home/home-serenity/AI_LAB/dsh-serenity-plugin/hooks/dsh-serenity-hooks/src/client/SafeModePanel
|
|
11
|
-
const css$3 = "/* SafeModePanel — 会话头部 Serenity 状态胶囊(v1.24.10 胶囊改版,对齐 OcgoDockEntry pill)\n * 胶囊 999px 全圆 → 点击 → 自绘 popover(340px 右上角卡片,外点/Escape 关闭)\n * popover 内:分组(groupTitle 标题 + rowCard 行卡)——看齐官方 settings 设计语言\n * 遵循 web-styling.md:颜色只用 --dsw-alias-* 语义 token(明暗自适应)+ --sp-safe-green 统一绿家族;\n * 前缀 sp- 防冲突。 */\n\n/* ── 统一绿色变量家族(一处改全改;emerald 500~600 区间保证深色对比度) ── */\n\n.sp-root {\n --sp-safe-green: #0ba875; /* emerald-550:SAFE ON 主色 */\n --sp-safe-green-deep: #059669; /* emerald-600:渐变深端 */\n}\n\n/* ── 头部状态胶囊(可点击) ── */\n\n.sp-card {\n display: inline-flex;\n align-items: center;\n gap: 6px;\n padding: 4px 9px; /* 无固定 height:由内容撑高(胶囊) */\n border: 1px solid var(--dsw-alias-border-l1);\n border-radius: 999px;\n background: var(--dsw-alias-bg-layer-2
|
|
11
|
+
const css$3 = "/* SafeModePanel — 会话头部 Serenity 状态胶囊(v1.24.10 胶囊改版,对齐 OcgoDockEntry pill)\n * 胶囊 999px 全圆 → 点击 → 自绘 popover(340px 右上角卡片,外点/Escape 关闭)\n * popover 内:分组(groupTitle 标题 + rowCard 行卡)——看齐官方 settings 设计语言\n * 遵循 web-styling.md:颜色只用 --dsw-alias-* 语义 token(明暗自适应)+ --sp-safe-green 统一绿家族;\n * 前缀 sp- 防冲突。 */\n\n/* ── 统一绿色变量家族(一处改全改;emerald 500~600 区间保证深色对比度) ── */\n\n.sp-root {\n --sp-safe-green: #0ba875; /* emerald-550:SAFE ON 主色 */\n --sp-safe-green-deep: #059669; /* emerald-600:渐变深端 */\n}\n\n/* ── 头部状态胶囊(可点击) ── */\n\n.sp-card {\n display: inline-flex;\n align-items: center;\n gap: 6px;\n padding: 4px 9px; /* 无固定 height:由内容撑高(胶囊) */\n border: 1px solid var(--dsw-alias-border-l1);\n border-radius: 999px;\n background: var(--dsw-alias-bg-layer-2);\n color: inherit;\n font-size: 12px;\n line-height: 1;\n cursor: pointer;\n transition: background var(--ds-transition-duration-fast, 0.1s) var(--ds-ease-in-out, ease),\n border-color var(--ds-transition-duration-fast, 0.1s) var(--ds-ease-in-out, ease);\n}\n\n.sp-card:hover {\n background: var(--dsw-alias-interactive-bg-hover);\n border-color: var(--dsw-alias-border-l2);\n}\n\n.sp-card:focus-visible {\n outline: 2px solid var(--dsw-alias-button-ghost-active-border);\n outline-offset: 1px;\n}\n\n.sp-card:disabled {\n opacity: 0.6;\n cursor: default;\n}\n\n/* 绿点 = Serenity 在线(恒亮绿,不随 SAFE 变);离线/未激活 → 灰 */\n.sp-dot {\n flex: none;\n width: 7px;\n height: 7px;\n border-radius: 50%;\n background: var(--dsw-alias-state-success-primary);\n}\n\n.sp-dotOff {\n background: var(--dsw-alias-label-dimmed);\n}\n\n.sp-cardInactive {\n opacity: 0.85;\n}\n\n.sp-cardInactive .sp-brand {\n color: var(--dsw-alias-label-secondary);\n font-weight: 500;\n}\n\n.sp-brand {\n color: var(--dsw-alias-label-primary);\n font-weight: 500;\n}\n\n/* 版本号:monospace + translateY(1px) 下沉对齐(与文字基线齐平) */\n.sp-ver {\n color: var(--dsw-alias-label-secondary);\n font-size: 11px;\n font-family: var(--ds-font-family-code, monospace);\n transform: translateY(1px);\n}\n\n/* 1px 深灰分隔线(两侧紧凑) */\n.sp-sep {\n flex: none;\n width: 1px;\n height: 12px;\n margin: 0 2px;\n background: var(--dsw-alias-label-tertiary);\n}\n\n.sp-shield {\n display: inline-flex;\n width: 14px;\n height: 14px;\n flex: none;\n}\n\n.sp-shield svg {\n width: 14px;\n height: 14px;\n}\n\n.sp-statusWord {\n font-size: 11px;\n font-weight: 600;\n letter-spacing: 0;\n white-space: nowrap;\n}\n\n/* SAFE OFF(未保护):灰——低姿态提醒,非错误(用户自己的开关选择) */\n.sp-cardUnprotected .sp-shield {\n color: var(--dsw-alias-label-secondary);\n}\n\n.sp-cardUnprotected .sp-statusWord {\n color: var(--dsw-alias-label-secondary);\n}\n\n/* SAFE ON(保护中):emerald 绿——安心(盾牌实色 + SAFE 文字渐变质感) */\n.sp-cardProtected .sp-shield {\n color: var(--sp-safe-green);\n}\n\n.sp-cardProtected .sp-statusWord {\n color: var(--sp-safe-green);\n background: linear-gradient(135deg, var(--sp-safe-green) 0%, var(--sp-safe-green-deep) 100%);\n -webkit-background-clip: text;\n background-clip: text;\n -webkit-text-fill-color: transparent;\n}\n\n/* ── Mac 风格滑块(v1.24.10:轨道 24×13 / thumb 11px 灰白 #f2f4f6 无图标 / cubic-bezier 平滑) ── */\n\n.sp-switch {\n display: inline-block;\n width: 24px;\n height: 13px;\n border-radius: 999px;\n position: relative;\n flex: none;\n cursor: pointer;\n transition: background 0.25s cubic-bezier(0.4, 0, 0.2, 1);\n}\n\n.sp-switchOff {\n background: var(--dsw-alias-border-l2);\n}\n\n.sp-switchOn {\n background: var(--sp-safe-green);\n}\n\n.sp-switchThumb {\n position: absolute;\n top: 1px;\n width: 11px;\n height: 11px;\n border-radius: 50%;\n background: #f2f4f6; /* Mac 灰白 thumb(明暗通用) */\n box-shadow: 0 1px 2px rgba(0, 0, 0, 0.3);\n transition: left 0.25s cubic-bezier(0.4, 0, 0.2, 1);\n}\n\n.sp-switchOff .sp-switchThumb {\n left: 1px;\n}\n\n.sp-switchOn .sp-switchThumb {\n left: 12px; /* 24 - 11 - 1 */\n}\n\n/* CSS 特异性坑(S013 指南):.sp-switch:hover (0,2,0) 会覆盖 .sp-switchOn (0,1,0)\n * → ON 态 hover 绿底消失。必须同特异性且后定义补回。 */\n.sp-switch:hover {\n filter: brightness(1.08);\n}\n\n.sp-switchOn:hover {\n background: var(--sp-safe-green);\n}\n\n.sp-switchBusy {\n opacity: 0.6;\n cursor: default;\n pointer-events: none;\n}\n\n.sp-chev {\n color: var(--dsw-alias-label-dimmed);\n flex: none;\n}\n\n/* ── 自绘 popover(340px 右上角小卡片,替代官方 Modal) ── */\n\n.sp-root {\n position: relative;\n display: inline-flex;\n}\n\n.sp-pop {\n position: absolute;\n top: calc(100% + 8px);\n right: 0;\n width: 340px;\n max-width: calc(100vw - 24px);\n z-index: 2000;\n box-sizing: border-box;\n padding: 14px;\n border: 1px solid var(--dsw-alias-border-l1);\n border-radius: 14px;\n background: var(--dsw-alias-bg-overlay);\n box-shadow: 0 12px 40px rgba(0, 0, 0, 0.5);\n}\n\n.sp-popBody {\n display: flex;\n flex-direction: column;\n gap: 14px;\n max-height: calc(100vh - 140px);\n overflow-y: auto;\n}\n\n/* ── 分组(官方 settings 语言:groupTitle + rows) ── */\n\n.sp-group {\n display: flex;\n flex-direction: column;\n gap: 8px;\n}\n\n.sp-groupHead {\n display: flex;\n align-items: center;\n justify-content: space-between;\n gap: 8px;\n}\n\n.sp-groupTitle {\n margin: 0;\n font-size: 12px;\n line-height: 18px;\n font-weight: 600;\n letter-spacing: 0.02em;\n text-transform: uppercase;\n color: var(--dsw-alias-label-secondary);\n}\n\n.sp-rows {\n display: flex;\n flex-direction: column;\n gap: 6px;\n}\n\n/* 行卡(rowCard:border + radius + padding;值与 label 分离、长内容可换行) */\n.sp-row {\n display: flex;\n align-items: baseline;\n gap: 12px;\n min-width: 0;\n padding: 8px 12px;\n border: 1px solid var(--dsw-alias-border-l1);\n border-radius: 10px;\n background: var(--dsw-alias-bg-layer-2);\n}\n\n.sp-label {\n flex: none;\n width: 76px;\n font-size: 12px;\n font-weight: 500;\n color: var(--dsw-alias-label-secondary);\n white-space: nowrap;\n}\n\n.sp-value {\n flex: 1;\n min-width: 0;\n font-size: 12px;\n line-height: 18px;\n color: var(--dsw-alias-label-primary);\n font-family: var(--ds-font-family-code, monospace);\n word-break: break-all;\n overflow-wrap: anywhere;\n}\n\n/* 安全模式动作区 */\n.sp-actions {\n display: flex;\n align-items: center;\n gap: 12px;\n padding: 8px 12px;\n border: 1px solid var(--dsw-alias-border-l1);\n border-radius: 10px;\n background: var(--dsw-alias-bg-layer-2);\n}\n\n.sp-toggle {\n display: inline-flex;\n align-items: center;\n gap: 6px;\n height: 24px;\n padding: 0 10px;\n border: 1px solid var(--dsw-alias-border-l1);\n border-radius: 6px;\n font-size: 12px;\n cursor: pointer;\n transition: background var(--ds-transition-duration-fast, 0.1s) var(--ds-ease-in-out, ease);\n}\n\n.sp-toggleLg {\n height: 30px;\n padding: 0 16px;\n font-size: 13px;\n}\n\n.sp-toggleOn {\n background: color-mix(in srgb, var(--dsw-alias-state-error-primary) 14%, transparent);\n color: var(--dsw-alias-state-error-primary);\n border-color: color-mix(in srgb, var(--dsw-alias-state-error-primary) 45%, transparent);\n}\n\n.sp-toggleOff {\n background: var(--dsw-alias-interactive-bg-hover);\n color: var(--dsw-alias-label-secondary);\n}\n\n.sp-toggleIcon {\n flex: none;\n}\n\n.sp-toggleDesc {\n flex: 1;\n font-size: 12px;\n line-height: 18px;\n color: var(--dsw-alias-label-tertiary);\n}\n\n/* handyman 运行状态区块 */\n\n.sp-handymenToggle {\n flex: none;\n padding: 2px 10px;\n border: 1px solid var(--dsw-alias-border-l1);\n border-radius: 6px;\n background: transparent;\n color: var(--dsw-alias-label-secondary);\n font-size: 11px;\n cursor: pointer;\n}\n\n.sp-handymenToggle:hover {\n background: var(--dsw-alias-interactive-bg-hover);\n}\n\n.sp-handymenList {\n display: flex;\n flex-direction: column;\n gap: 4px;\n margin-top: 2px;\n}\n\n.sp-handymanItem {\n display: flex;\n flex-direction: column;\n gap: 2px;\n padding: 8px 12px;\n border: 1px solid var(--dsw-alias-border-l1);\n border-radius: 8px;\n min-width: 0;\n}\n\n.sp-handymanHead {\n display: flex;\n align-items: baseline;\n gap: 8px;\n min-width: 0;\n}\n\n.sp-handymanLabel {\n flex: 1;\n min-width: 0;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n font-size: 12px;\n font-weight: 500;\n color: var(--dsw-alias-label-primary);\n}\n\n.sp-handymanRound {\n flex: none;\n font-size: 11px;\n color: var(--dsw-alias-label-dimmed);\n}\n\n.sp-handymanTime {\n flex: none;\n font-size: 11px;\n color: var(--dsw-alias-label-dimmed);\n}\n\n.sp-handymanResp {\n font-size: 11px;\n line-height: 16px;\n color: var(--dsw-alias-label-secondary);\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n\n.sp-handymenMore {\n font-size: 11px;\n color: var(--dsw-alias-label-dimmed);\n padding: 4px 12px;\n}\n\n/* 底部提示(配置入口引导) */\n.sp-hint {\n margin: 0;\n font-size: 12px;\n line-height: 18px;\n color: var(--dsw-alias-label-tertiary);\n word-break: break-word;\n overflow-wrap: anywhere;\n padding: 8px 12px;\n border: 1px dashed var(--dsw-alias-border-l2);\n border-radius: 10px;\n}\n";
|
|
12
12
|
if (typeof document !== "undefined" && document.querySelector("style[data-sp-css=\"SafeModePanel\"]") === null) {
|
|
13
13
|
const tag = document.createElement("style");
|
|
14
14
|
tag.setAttribute("data-sp-css", "SafeModePanel");
|
|
@@ -3263,7 +3263,7 @@ window.__ModuleLoader__.load({
|
|
|
3263
3263
|
}
|
|
3264
3264
|
//#endregion
|
|
3265
3265
|
//#region \0sp-css:/home/yh/home/home-serenity/AI_LAB/dsh-serenity-plugin/hooks/dsh-serenity-hooks/src/client/AccountsEditor
|
|
3266
|
-
const css$2 = "/* AccountsEditor — DSH 设置面板「外部访问」区块(v1.22)\n * 与 SettingsSection ss-* 同设计语言:--dsw-alias-* 语义 token,明暗自适应。 */\n\n.ae-root {\n display: flex;\n flex-direction: column;\n gap: 10px;\n padding: 12px 14px;\n border: 1px solid var(--dsw-alias-border-l2);\n border-radius: 12px;\n font-size: 13px;\n}\n\n/* ── v1.27.5 紧凑化:折叠块(details/summary)── */\n.ae-collapse {\n border: 1px solid var(--dsw-alias-border-l1);\n border-radius: 8px;\n overflow: hidden;\n}\n\n.ae-collapseHead {\n display: flex;\n align-items: baseline;\n gap: 8px;\n padding: 8px 12px;\n cursor: pointer;\n list-style: none;\n user-select: none;\n}\n\n.ae-collapseHead::-webkit-details-marker {\n display: none;\n}\n\n.ae-collapseHead::before {\n content: '▸';\n flex: none;\n font-size: 11px;\n color: var(--dsw-alias-label-dimmed);\n transition: transform 0.15s ease;\n}\n\n.ae-collapse[open] > .ae-collapseHead::before {\n transform: rotate(90deg);\n}\n\n.ae-collapseHead:hover {\n background: var(--dsw-alias-interactive-bg-hover, rgba(128, 128, 128, 0.06));\n}\n\n.ae-collapseDesc {\n font-size: 12px;\n color: var(--dsw-alias-label-tertiary);\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n min-width: 0;\n}\n\n.ae-collapseBody {\n display: flex;\n flex-direction: column;\n gap: 8px;\n padding: 8px 12px 10px;\n border-top: 1px solid var(--dsw-alias-border-l1);\n}\n\n.ae-warn {\n margin: 0;\n font-size: 12px;\n line-height: 18px;\n color: var(--dsw-alias-state-
|
|
3266
|
+
const css$2 = "/* AccountsEditor — DSH 设置面板「外部访问」区块(v1.22)\n * 与 SettingsSection ss-* 同设计语言:--dsw-alias-* 语义 token,明暗自适应。 */\n\n.ae-root {\n display: flex;\n flex-direction: column;\n gap: 10px;\n padding: 12px 14px;\n border: 1px solid var(--dsw-alias-border-l2);\n border-radius: 12px;\n font-size: 13px;\n}\n\n/* ── v1.27.5 紧凑化:折叠块(details/summary)── */\n.ae-collapse {\n border: 1px solid var(--dsw-alias-border-l1);\n border-radius: 8px;\n overflow: hidden;\n}\n\n.ae-collapseHead {\n display: flex;\n align-items: baseline;\n gap: 8px;\n padding: 8px 12px;\n cursor: pointer;\n list-style: none;\n user-select: none;\n}\n\n.ae-collapseHead::-webkit-details-marker {\n display: none;\n}\n\n.ae-collapseHead::before {\n content: '▸';\n flex: none;\n font-size: 11px;\n color: var(--dsw-alias-label-dimmed);\n transition: transform 0.15s ease;\n}\n\n.ae-collapse[open] > .ae-collapseHead::before {\n transform: rotate(90deg);\n}\n\n.ae-collapseHead:hover {\n background: var(--dsw-alias-interactive-bg-hover, rgba(128, 128, 128, 0.06));\n}\n\n.ae-collapseDesc {\n font-size: 12px;\n color: var(--dsw-alias-label-tertiary);\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n min-width: 0;\n}\n\n.ae-collapseBody {\n display: flex;\n flex-direction: column;\n gap: 8px;\n padding: 8px 12px 10px;\n border-top: 1px solid var(--dsw-alias-border-l1);\n}\n\n.ae-warn {\n margin: 0;\n font-size: 12px;\n line-height: 18px;\n color: var(--dsw-alias-state-warn-label);\n word-break: break-word;\n}\n\n.ae-block {\n display: flex;\n flex-direction: column;\n gap: 8px;\n}\n\n.ae-title {\n margin: 0;\n font-size: 12px;\n line-height: 18px;\n font-weight: 600;\n letter-spacing: 0.02em;\n text-transform: uppercase;\n color: var(--dsw-alias-label-secondary);\n}\n\n.ae-desc {\n margin: 0;\n font-size: 12px;\n line-height: 18px;\n color: var(--dsw-alias-label-tertiary);\n word-break: break-word;\n overflow-wrap: anywhere;\n}\n\n.ae-note {\n margin: 0;\n font-size: 12px;\n line-height: 18px;\n color: var(--dsw-alias-label-tertiary);\n}\n\n/* ── 监听设置 / 白名单输入行 ── */\n\n.ae-row {\n display: flex;\n align-items: center;\n gap: 8px;\n}\n\n.ae-label {\n flex: none;\n font-size: 12px;\n font-weight: 500;\n color: var(--dsw-alias-label-secondary);\n white-space: nowrap;\n}\n\n.ae-input {\n flex: 1;\n min-width: 0;\n padding: 6px 10px;\n border: 1px solid var(--dsw-alias-border-l2);\n border-radius: 8px;\n background: var(--dsw-alias-bg-layer-2);\n color: var(--dsw-alias-label-primary);\n font-size: 13px;\n outline: none;\n}\n\n.ae-input:focus {\n border-color: var(--dsw-alias-button-ghost-active-border);\n}\n\n.ae-input::placeholder {\n color: var(--dsw-alias-label-dimmed);\n}\n\n.ae-input.ae-port {\n flex: none;\n width: 76px;\n}\n\n/* ── 账号列表 ── */\n\n.ae-list {\n display: flex;\n flex-direction: column;\n gap: 4px;\n}\n\n.ae-head {\n display: flex;\n gap: 8px;\n font-size: 11px;\n font-weight: 500;\n color: var(--dsw-alias-label-dimmed);\n padding: 0 2px;\n text-transform: uppercase;\n letter-spacing: 0.02em;\n}\n\n.ae-item {\n display: flex;\n gap: 8px;\n align-items: center;\n padding: 6px 2px;\n}\n\n.ae-colUser { flex: 1.2; }\n.ae-colPass { flex: 1.6; }\n.ae-colTotp { flex: 2.2; min-width: 0; }\n.ae-colOp { flex: none; width: 48px; }\n\n/* ── v1.22.4 TOTP 第二因素 ── */\n\n.ae-totpBtn {\n flex: none;\n padding: 4px 10px;\n border: 1px solid var(--dsw-alias-border-l2);\n border-radius: 6px;\n background: transparent;\n color: var(--dsw-alias-label-secondary);\n font-size: 12px;\n cursor: pointer;\n white-space: nowrap;\n}\n\n.ae-totpBtn:hover {\n background: var(--dsw-alias-interactive-bg-hover);\n}\n\n.ae-totpPending {\n display: flex;\n flex-wrap: wrap;\n gap: 6px;\n align-items: center;\n min-width: 0;\n}\n\n/* v1.24.6:TOTP 绑定二维码(扫码即录入;scalable SVG,白底保证扫码识别) */\n.ae-totpQr {\n flex-basis: 100%;\n display: inline-flex;\n justify-content: center;\n padding: 8px;\n background: #fff;\n border: 1px solid var(--dsw-alias-border-l1);\n border-radius: 8px;\n}\n\n.ae-totpQr svg {\n width: 132px;\n height: 132px;\n display: block;\n}\n\n.ae-totpSecret {\n font-family: var(--ds-font-family-code);\n font-size: 11px;\n background: var(--dsw-alias-bg-layer-2);\n border: 1px solid var(--dsw-alias-border-l1);\n border-radius: 4px;\n padding: 2px 6px;\n color: var(--dsw-alias-label-secondary);\n word-break: break-all;\n}\n\n.ae-totpUri {\n font-size: 11px;\n color: var(--dsw-alias-button-info-fill);\n text-decoration: none;\n}\n\n.ae-totpUri:hover {\n text-decoration: underline;\n}\n\n/* v1.24.7:绑定提示(去确认码后——扫码或手动录入,保存即绑定) */\n.ae-totpHint {\n flex-basis: 100%;\n font-size: 11px;\n color: var(--dsw-alias-label-tertiary);\n}\n\n.ae-totpBadge {\n font-size: 12px;\n color: var(--dsw-alias-state-warn-label);\n}\n\n.ae-totpClear {\n display: flex;\n gap: 6px;\n align-items: center;\n}\n\n.ae-check {\n display: flex;\n gap: 8px;\n align-items: center;\n font-size: 12px;\n color: var(--dsw-alias-label-secondary);\n margin-top: 10px;\n cursor: pointer;\n}\n\n.ae-check input {\n accent-color: var(--dsw-alias-button-info-fill);\n}\n\n.ae-del {\n flex: none;\n padding: 4px 10px;\n border: 1px solid var(--dsw-alias-border-l1);\n border-radius: 6px;\n background: transparent;\n color: var(--dsw-alias-state-error-primary);\n font-size: 12px;\n cursor: pointer;\n}\n\n.ae-del:hover {\n background: var(--dsw-alias-interactive-bg-hover-danger);\n}\n\n.ae-add {\n align-self: flex-start;\n padding: 5px 12px;\n border: 1px dashed var(--dsw-alias-border-l2);\n border-radius: 8px;\n background: transparent;\n color: var(--dsw-alias-label-secondary);\n font-size: 12px;\n cursor: pointer;\n}\n\n.ae-add:hover {\n background: var(--dsw-alias-interactive-bg-hover);\n}\n\n.ae-wsSelect {\n margin-top: 8px;\n max-width: 100%;\n cursor: pointer;\n}\n\n/* ── 工作区白名单 chips ── */\n\n.ae-wsList {\n display: flex;\n flex-wrap: wrap;\n gap: 6px;\n}\n\n.ae-wsChip {\n display: inline-flex;\n align-items: center;\n gap: 6px;\n padding: 3px 8px;\n border: 1px solid var(--dsw-alias-border-l2);\n border-radius: 8px;\n background: var(--dsw-alias-interactive-bg-active, rgba(128, 128, 128, 0.14));\n}\n\n.ae-wsPath {\n font-size: 12px;\n color: var(--dsw-alias-label-primary);\n font-family: var(--ds-font-family-code, monospace);\n word-break: break-all;\n}\n\n.ae-wsDel {\n flex: none;\n width: 18px;\n height: 18px;\n border: none;\n border-radius: 50%;\n background: transparent;\n color: var(--dsw-alias-label-dimmed);\n font-size: 13px;\n line-height: 1;\n cursor: pointer;\n}\n\n.ae-wsDel:hover {\n background: var(--dsw-alias-interactive-bg-hover-danger);\n color: var(--dsw-alias-state-error-primary);\n}\n\n/* ── 错误 / 操作区 ── */\n\n.ae-error {\n margin: 0;\n font-size: 12px;\n line-height: 18px;\n color: var(--dsw-alias-state-error-primary);\n word-break: break-word;\n}\n\n.ae-actions {\n display: flex;\n justify-content: flex-end;\n padding-top: 4px;\n border-top: 1px solid var(--dsw-alias-border-l1);\n}\n\n.ae-save {\n padding: 7px 24px;\n border: 0;\n border-radius: 8px;\n background: var(--dsw-alias-button-primary-fill);\n color: var(--dsw-alias-label-primary-foreground);\n font-size: 13px;\n font-weight: 500;\n cursor: pointer;\n}\n\n.ae-save:hover:not(:disabled) {\n filter: brightness(1.08);\n}\n\n.ae-save:disabled {\n opacity: 0.6;\n cursor: default;\n}\n\n/* ── v1.26.2 建议问答页配置(PublicAskEditor:开放容器白名单 chips + key/地址展示)── */\n\n.ae-chips {\n display: flex;\n flex-wrap: wrap;\n gap: 6px;\n margin: 8px 0 4px;\n}\n\n.ae-chip {\n padding: 4px 12px;\n border: 1px solid var(--dsw-alias-border-l1);\n border-radius: 999px;\n background: transparent;\n color: var(--dsw-alias-label-primary);\n font-size: 12px;\n cursor: pointer;\n transition: background 0.15s ease, border-color 0.15s ease;\n}\n\n.ae-chip:hover {\n border-color: var(--dsw-alias-button-ghost-active-border);\n}\n\n.ae-chipOn {\n background: var(--dsw-alias-button-primary-fill);\n border-color: var(--dsw-alias-button-primary-fill);\n color: var(--dsw-alias-label-primary-foreground);\n}\n\n.ae-saved {\n margin-left: 10px;\n font-size: 12px;\n color: var(--dsw-alias-state-success-primary);\n}\n\n.ae-err {\n margin-left: 10px;\n font-size: 12px;\n color: var(--dsw-alias-state-error-primary);\n}\n\n.ae-key {\n font-family: var(--ds-font-family-code);\n font-size: 12px;\n padding: 2px 8px;\n border-radius: 6px;\n background: var(--dsw-alias-bg-layer-2);\n color: var(--dsw-alias-label-primary);\n word-break: break-all;\n user-select: all;\n}\n\n.ae-copy {\n flex: none;\n margin-left: 8px;\n padding: 3px 10px;\n border: 1px solid var(--dsw-alias-border-l1);\n border-radius: 6px;\n background: transparent;\n color: var(--dsw-alias-label-primary);\n font-size: 12px;\n cursor: pointer;\n}\n\n.ae-copy:hover {\n border-color: var(--dsw-alias-button-ghost-active-border);\n color: var(--dsw-alias-label-primary);\n}\n\n/* v1.26.5 轮换 key(警示色:旧 key 立即失效) */\n.ae-rotate {\n color: var(--dsw-alias-state-error-primary);\n border-color: color-mix(in srgb, var(--dsw-alias-state-error-primary) 35%, transparent);\n}\n\n.ae-rotate:hover {\n border-color: var(--dsw-alias-state-error-primary);\n color: var(--dsw-alias-state-error-primary);\n}\n\n.ae-rotate:disabled {\n opacity: 0.6;\n cursor: wait;\n}\n\n.ae-rows {\n display: flex;\n flex-direction: column;\n gap: 6px;\n margin-top: 8px;\n}\n\n.ae-labelDim {\n opacity: 0.65;\n min-width: 96px;\n}\n";
|
|
3267
3267
|
if (typeof document !== "undefined" && document.querySelector("style[data-sp-css=\"AccountsEditor\"]") === null) {
|
|
3268
3268
|
const tag = document.createElement("style");
|
|
3269
3269
|
tag.setAttribute("data-sp-css", "AccountsEditor");
|
|
@@ -3679,7 +3679,7 @@ window.__ModuleLoader__.load({
|
|
|
3679
3679
|
}
|
|
3680
3680
|
//#endregion
|
|
3681
3681
|
//#region \0sp-css:/home/yh/home/home-serenity/AI_LAB/dsh-serenity-plugin/hooks/dsh-serenity-hooks/src/client/PersonaEditor
|
|
3682
|
-
const css$1 = "/* PersonaEditor.css — 彩蛋模式区块(v1.23.1,官方 settings 设计语言:rowCard
|
|
3682
|
+
const css$1 = "/* PersonaEditor.css — 彩蛋模式区块(v1.23.1,官方 settings 设计语言:rowCard 内分组)\n * token 合规(v1.27.14 修复):全部使用 design-platform.css 官方 --dsw-alias-* 词汇;\n * 弃用自造名(text-primary/accent/border/state-success 等)——未定义时深色 fallback\n * 在浅色主题下显示为黑框黑字(用户反馈白色主题通性问题)。 */\n\n.pe-wrap {\n display: flex;\n flex-direction: column;\n gap: 10px;\n width: 100%;\n padding: 2px 0 6px;\n}\n\n.pe-row {\n display: flex;\n flex-direction: column;\n gap: 4px;\n}\n\n.pe-label {\n font-size: 12px;\n color: var(--dsw-alias-label-secondary);\n user-select: none;\n}\n\n.pe-input,\n.pe-textarea {\n width: 100%;\n box-sizing: border-box;\n background: var(--dsw-alias-bg-layer-2);\n color: var(--dsw-alias-label-primary);\n border: 1px solid var(--dsw-alias-border-l2);\n border-radius: 8px;\n font-size: 13px;\n padding: 8px 10px;\n outline: none;\n}\n\n.pe-input:focus,\n.pe-textarea:focus {\n border-color: var(--dsw-alias-button-ghost-active-border);\n}\n\n.pe-textarea {\n resize: vertical;\n min-height: 72px; /* v1.27.5 紧凑化:默认 4 行(原 120px 过高),可拖拽扩大 */\n line-height: 1.5;\n font-family: var(--ds-font-family-code);\n font-size: 12px;\n}\n\n.pe-actions {\n display: flex;\n gap: 8px;\n align-items: center;\n flex-wrap: wrap;\n}\n\n.pe-btn {\n border-radius: 6px;\n padding: 5px 12px;\n font-size: 12px;\n cursor: pointer;\n border: 1px solid var(--dsw-alias-border-l2);\n background: transparent;\n color: var(--dsw-alias-label-primary);\n transition: opacity 0.15s;\n}\n\n.pe-btn:hover:not(:disabled) {\n opacity: 0.85;\n}\n\n.pe-btn:disabled {\n opacity: 0.4;\n cursor: default;\n}\n\n.pe-btnGhost {\n border-color: var(--dsw-alias-border-l2);\n}\n\n.pe-btnPrimary {\n background: var(--dsw-alias-button-primary-fill);\n border-color: transparent;\n color: var(--dsw-alias-label-primary-foreground);\n}\n\n.pe-hint {\n font-size: 12px;\n margin: 0;\n}\n\n.pe-hintOk {\n color: var(--dsw-alias-state-success-primary);\n}\n\n.pe-hintErr {\n color: var(--dsw-alias-state-error-primary);\n}\n";
|
|
3683
3683
|
if (typeof document !== "undefined" && document.querySelector("style[data-sp-css=\"PersonaEditor\"]") === null) {
|
|
3684
3684
|
const tag = document.createElement("style");
|
|
3685
3685
|
tag.setAttribute("data-sp-css", "PersonaEditor");
|
|
@@ -4612,7 +4612,7 @@ Instruction-following style:
|
|
|
4612
4612
|
}
|
|
4613
4613
|
//#endregion
|
|
4614
4614
|
//#region \0sp-css:/home/yh/home/home-serenity/AI_LAB/dsh-serenity-plugin/hooks/dsh-serenity-hooks/src/client/SettingsSection
|
|
4615
|
-
const css = "/* SettingsSection — dsh 原生设置面板 serenity-hooks 简单配置页\n v1.21.1 看齐 dsh 自身:官方 settings 设计语言——\n section > title + intro + group(groupTitle) + rowCard 行卡;\n 全部 --dsw-alias-* token(与 ui-settings-models 同词汇表)。 */\n\n/* 页面容器(官方 .section:flex column gap 12) */\n.ss-section {\n display: flex;\n flex-direction: column;\n gap: 14px;\n max-width: 720px;\n color: var(--dsw-alias-label-primary);\n}\n\n/* 页面标题(官方 .title:16px/24 500) */\n.ss-title {\n margin: 0;\n font-size: 16px;\n line-height: 24px;\n font-weight: 500;\n color: var(--dsw-alias-label-primary);\n}\n\n/* 页面说明(官方 .intro:14px/22 tertiary) */\n.ss-intro {\n margin: 0;\n font-size: 14px;\n line-height: 22px;\n color: var(--dsw-alias-label-tertiary);\n word-break: break-word;\n overflow-wrap: anywhere;\n}\n\n/* 分组(多级标题:分组标题 + 行卡列表) */\n.ss-group {\n display: flex;\n flex-direction: column;\n gap: 8px;\n}\n\n/* 分组标题(二级:12px/18 500 secondary——官方 fieldLabel 风格) */\n.ss-groupTitle {\n margin: 0;\n font-size: 12px;\n line-height: 18px;\n font-weight: 600;\n letter-spacing: 0.02em;\n text-transform: uppercase;\n color: var(--dsw-alias-label-secondary);\n}\n\n/* 行卡列表(官方 .rows:无 marker,gap 8) */\n.ss-rows {\n list-style: none;\n margin: 0;\n padding: 0;\n display: flex;\n flex-direction: column;\n gap: 8px;\n}\n\n/* 行卡(官方 .rowCard:border-l2 r12 padding 12/14) */\n.ss-rowCard {\n border: 1px solid var(--dsw-alias-border-l2);\n border-radius: 12px;\n padding: 12px 14px;\n display: flex;\n align-items: center;\n justify-content: space-between;\n gap: 16px;\n}\n\n/* 行卡文本(标题 + 说明 左列) */\n.ss-rowText {\n display: flex;\n flex-direction: column;\n gap: 3px;\n min-width: 0;\n flex: 1;\n}\n\n/* 行卡标题(官方 .rowName:14px/22 500) */\n.ss-rowName {\n font-size: 14px;\n line-height: 22px;\n font-weight: 500;\n color: var(--dsw-alias-label-primary);\n}\n\n/* 行卡说明(12px/18 tertiary;v1.27.5 紧凑化:默认单行截断,hover title 看全文) */\n.ss-rowDesc {\n font-size: 12px;\n line-height: 18px;\n color: var(--dsw-alias-label-tertiary);\n word-break: break-word;\n overflow-wrap: anywhere;\n display: -webkit-box;\n -webkit-line-clamp: 2;\n -webkit-box-orient: vertical;\n overflow: hidden;\n}\n\n/* 行卡控件(右置) */\n.ss-rowControl {\n flex: none;\n display: flex;\n align-items: center;\n}\n\n/* 开关(官方 token 的自绘 toggle:track 22px,thumb 16px) */\n.ss-switch {\n position: relative;\n flex: none;\n width: 40px;\n height: 22px;\n cursor: pointer;\n}\n\n.ss-switch input {\n position: absolute;\n opacity: 0;\n width: 100%;\n height: 100%;\n margin: 0;\n cursor: pointer;\n}\n\n.ss-switchTrack {\n position: absolute;\n inset: 0;\n border-radius: 11px;\n background: var(--dsw-alias-interactive-bg-active, rgba(128, 128, 128, 0.3));\n transition: background var(--ds-transition-duration-fast, 0.15s) var(--ds-ease-in-out, ease);\n}\n\n.ss-switchTrack::after {\n content: '';\n position: absolute;\n top: 3px;\n left: 3px;\n width: 16px;\n height: 16px;\n border-radius: 50%;\n background: var(--dsw-alias-label-primary, #eee);\n transition: transform var(--ds-transition-duration-fast, 0.15s) var(--ds-ease-in-out, ease);\n}\n\n.ss-switch input:checked + .ss-switchTrack {\n background: var(--dsw-alias-state-success-primary, #22c55e);\n}\n\n.ss-switch input:checked + .ss-switchTrack::after {\n transform: translateX(18px);\n}\n\n.ss-switch input:disabled + .ss-switchTrack {\n opacity: 0.4;\n cursor: default;\n}\n\n/* 阈值行(滑块 + 值) */\n.ss-threshold {\n display: flex;\n align-items: center;\n gap: 12px;\n}\n\n.ss-threshold input[type='range'] {\n width: 160px;\n}\n\n/* Skiff 调试端口输入(number;关闭时跟随 disabled 半透明) */\n.ss-portInput {\n width: 96px;\n padding: 6px 8px;\n border-radius: 8px;\n border: 1px solid var(--dsw-alias-border-l2);\n background: var(--dsw-alias-fill-l1, rgba(128, 128, 128, 0.08));\n color: var(--dsw-alias-label-primary);\n font-size: 13px;\n font-variant-numeric: tabular-nums;\n text-align: right;\n}\n\n.ss-portInput:disabled {\n opacity: 0.4;\n cursor: default;\n}\n\n.ss-value {\n min-width: 40px;\n font-variant-numeric: tabular-nums;\n font-size: 13px;\n font-weight: 600;\n color: var(--dsw-alias-label-primary);\n text-align: right;\n}\n\n/* 自主轨迹「立即唤起」按钮(v1.26.14 调试语义;官方 button 语言:primary 弱化) */\n.ss-wakeBtn {\n flex: none;\n padding: 5px 12px;\n border-radius: 8px;\n border: 1px solid var(--dsw-alias-border-l2);\n background: var(--dsw-alias-interactive-bg-active, rgba(128, 128, 128, 0.12));\n color: var(--dsw-alias-label-primary);\n font-size: 12px;\n line-height: 18px;\n font-weight: 500;\n cursor: pointer;\n transition: opacity var(--ds-transition-duration-fast, 0.15s) ease;\n}\n\n.ss-wakeBtn:hover:not(:disabled) {\n border-color: var(--dsw-alias-state-success-primary, #22c55e);\n color: var(--dsw-alias-state-success-primary, #22c55e);\n}\n\n.ss-wakeBtn:disabled {\n opacity: 0.4;\n cursor: default;\n}\n\n/* 底部帮助行 */\n.ss-hint {\n margin: 0;\n font-size: 12px;\n line-height: 18px;\n color: var(--dsw-alias-label-tertiary);\n word-break: break-word;\n overflow-wrap: anywhere;\n}\n\n/* 降级提示 */\n.ss-degrade {\n margin: 0;\n font-size: 12px;\n line-height: 18px;\n color: var(--dsw-alias-state-warn-label);\n word-break: break-word;\n overflow-wrap: anywhere;\n}\n\n/* ── v1.27.5 紧凑化:折叠组(details/summary)── */\n.ss-collapse {\n border: 1px solid var(--dsw-alias-border-l2);\n border-radius: 12px;\n overflow: hidden;\n}\n\n.ss-collapseHead {\n display: flex;\n align-items: baseline;\n gap: 10px;\n padding: 10px 14px;\n cursor: pointer;\n list-style: none;\n user-select: none;\n}\n\n.ss-collapseHead::-webkit-details-marker {\n display: none;\n}\n\n.ss-collapseHead::before {\n content: '▸';\n flex: none;\n font-size: 11px;\n color: var(--dsw-alias-label-tertiary);\n transition: transform 0.15s ease;\n}\n\n.ss-collapse[open] > .ss-collapseHead::before {\n transform: rotate(90deg);\n}\n\n.ss-collapseHead:hover {\n background: var(--dsw-alias-interactive-bg-hover, rgba(128, 128, 128, 0.06));\n}\n\n.ss-collapseTitle {\n font-size: 13px;\n font-weight: 500;\n color: var(--dsw-alias-label-primary);\n}\n\n.ss-collapseDesc {\n font-size: 12px;\n color: var(--dsw-alias-label-tertiary);\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n min-width: 0;\n}\n\n.ss-collapseBody {\n padding: 10px 14px 14px;\n border-top: 1px solid var(--dsw-alias-border-l1);\n}\n\n/* ── v1.27.5 紧凑化:只读定义列表(dl/dt/dd 2 列网格)── */\n.ss-defList {\n display: grid;\n grid-template-columns: repeat(2, minmax(0, 1fr));\n gap: 6px 20px;\n margin: 0;\n width: 100%;\n min-width: 0;\n}\n\n.ss-defItem {\n display: flex;\n flex-direction: column;\n gap: 1px;\n min-width: 0;\n}\n\n.ss-defTerm {\n font-size: 11px;\n line-height: 16px;\n font-weight: 500;\n letter-spacing: 0.02em;\n text-transform: uppercase;\n color: var(--dsw-alias-label-tertiary);\n}\n\n.ss-defValue {\n margin: 0;\n font-size: 12px;\n line-height: 18px;\n color: var(--dsw-alias-label-primary);\n word-break: break-word;\n overflow-wrap: anywhere;\n display: -webkit-box;\n -webkit-line-clamp: 2;\n -webkit-box-orient: vertical;\n overflow: hidden;\n}\n\n/* ── v1.27.5 紧凑化:开关 + 端口内联行 ── */\n.ss-switchRow {\n display: flex;\n align-items: center;\n gap: 8px;\n}\n\n/* ── v1.27.6 「?」帮助浮层(hover/焦点显示三行以上严谨说明)── */\n.ss-help {\n position: relative;\n display: inline-flex;\n align-items: center;\n margin-top: 2px;\n}\n\n.ss-helpMark {\n flex: none;\n width: 16px;\n height: 16px;\n padding: 0;\n border: 1px solid var(--dsw-alias-border-l2);\n border-radius: 50%;\n background: var(--dsw-alias-interactive-bg-active, rgba(128, 128, 128, 0.12));\n color: var(--dsw-alias-label-tertiary);\n font-size: 10px;\n line-height: 1;\n font-weight: 600;\n cursor: help;\n display: inline-flex;\n align-items: center;\n justify-content: center;\n transition: border-color 0.15s ease, color 0.15s ease;\n}\n\n.ss-helpMark:hover,\n.ss-helpMark:focus-visible {\n border-color: var(--dsw-alias-state-success-primary, #22c55e);\n color: var(--dsw-alias-state-success-primary, #22c55e);\n outline: none;\n}\n\n.ss-helpTip {\n position: absolute;\n left: 0;\n top: calc(100% + 6px);\n z-index: 30;\n width: 300px;\n max-width: 60vw;\n padding: 10px 12px;\n border: 1px solid var(--dsw-alias-border-l2);\n border-radius: 10px;\n background: var(--dsw-alias-surface-raised, #1e1e1e);\n box-shadow: 0 6px 24px rgba(0, 0, 0, 0.28);\n font-size: 12px;\n line-height: 19px;\n font-weight: 400;\n color: var(--dsw-alias-label-primary);\n white-space: pre-line;\n word-break: break-word;\n opacity: 0;\n pointer-events: none;\n transform: translateY(-4px);\n transition: opacity 0.15s ease, transform 0.15s ease;\n}\n\n.ss-help:hover .ss-helpTip,\n.ss-helpMark:focus-visible + .ss-helpTip {\n opacity: 1;\n pointer-events: auto;\n transform: translateY(0);\n}\n\n/* 折叠组内帮助浮层定位修正(左侧行卡不会溢出面板) */\n.ss-rowCard .ss-helpTip {\n left: 0;\n}\n\n/* ── 微信桥(F4c-3,v1.27.0 实验性)── */\n\n/* CCC 选择器(下拉) */\n.ss-select {\n max-width: 280px;\n padding: 6px 8px;\n border-radius: 8px;\n border: 1px solid var(--dsw-alias-border-l2);\n background: var(--dsw-alias-fill-l1, rgba(128, 128, 128, 0.08));\n color: var(--dsw-alias-label-primary);\n font-size: 13px;\n}\n\n/* 扫码绑定按钮(复用 wakeBtn 语言) */\n.ss-loginBtn {\n flex: none;\n padding: 5px 12px;\n border-radius: 8px;\n border: 1px solid var(--dsw-alias-border-l2);\n background: var(--dsw-alias-interactive-bg-active, rgba(128, 128, 128, 0.12));\n color: var(--dsw-alias-label-primary);\n font-size: 12px;\n line-height: 18px;\n font-weight: 500;\n cursor: pointer;\n transition: opacity var(--ds-transition-duration-fast, 0.15s) ease;\n}\n\n.ss-loginBtn:hover:not(:disabled) {\n border-color: var(--dsw-alias-state-success-primary, #22c55e);\n color: var(--dsw-alias-state-success-primary, #22c55e);\n}\n\n.ss-loginBtn:disabled {\n opacity: 0.4;\n cursor: default;\n}\n\n/* 移除账号按钮(危险语义:红边框) */\n.ss-removeBtn {\n flex: none;\n padding: 5px 12px;\n border-radius: 8px;\n border: 1px solid var(--dsw-alias-border-l2);\n background: var(--dsw-alias-interactive-bg-active, rgba(128, 128, 128, 0.12));\n color: var(--dsw-alias-label-primary);\n font-size: 12px;\n line-height: 18px;\n font-weight: 500;\n cursor: pointer;\n transition: opacity var(--ds-transition-duration-fast, 0.15s) ease;\n}\n\n.ss-removeBtn:hover:not(:disabled) {\n border-color: var(--dsw-alias-state-error-primary, #ef4444);\n color: var(--dsw-alias-state-error-primary, #ef4444);\n}\n\n/* 二维码展示行 */\n.ss-qrRow {\n align-items: center;\n}\n\n.ss-qrSvg {\n flex: none;\n width: 120px; /* v1.27.5 紧凑化:160 → 120 */\n height: 120px;\n border-radius: 8px;\n border: 1px solid var(--dsw-alias-border-l2);\n background: #fff; /* 二维码需要白底才能被微信扫出 */\n padding: 6px;\n display: flex;\n align-items: center;\n justify-content: center;\n}\n\n.ss-qrSvg svg {\n width: 100%;\n height: 100%;\n}\n\n/* 路由表 JSON 编辑框 */\n.ss-routesEditor {\n width: 100%;\n box-sizing: border-box;\n margin-top: 6px;\n padding: 8px 10px;\n border-radius: 8px;\n border: 1px solid var(--dsw-alias-border-l2);\n background: var(--dsw-alias-fill-l1, rgba(128, 128, 128, 0.08));\n color: var(--dsw-alias-label-primary);\n font-size: 12px;\n line-height: 18px;\n font-family: var(--ds-font-mono, ui-monospace, SFMono-Regular, Menlo, monospace);\n resize: vertical;\n}\n";
|
|
4615
|
+
const css = "/* SettingsSection — dsh 原生设置面板 serenity-hooks 简单配置页\n v1.21.1 看齐 dsh 自身:官方 settings 设计语言——\n section > title + intro + group(groupTitle) + rowCard 行卡;\n 全部 --dsw-alias-* token(与 ui-settings-models 同词汇表)。 */\n\n/* 页面容器(官方 .section:flex column gap 12) */\n.ss-section {\n display: flex;\n flex-direction: column;\n gap: 14px;\n max-width: 720px;\n color: var(--dsw-alias-label-primary);\n}\n\n/* 页面标题(官方 .title:16px/24 500) */\n.ss-title {\n margin: 0;\n font-size: 16px;\n line-height: 24px;\n font-weight: 500;\n color: var(--dsw-alias-label-primary);\n}\n\n/* 页面说明(官方 .intro:14px/22 tertiary) */\n.ss-intro {\n margin: 0;\n font-size: 14px;\n line-height: 22px;\n color: var(--dsw-alias-label-tertiary);\n word-break: break-word;\n overflow-wrap: anywhere;\n}\n\n/* 分组(多级标题:分组标题 + 行卡列表) */\n.ss-group {\n display: flex;\n flex-direction: column;\n gap: 8px;\n}\n\n/* 分组标题(二级:12px/18 500 secondary——官方 fieldLabel 风格) */\n.ss-groupTitle {\n margin: 0;\n font-size: 12px;\n line-height: 18px;\n font-weight: 600;\n letter-spacing: 0.02em;\n text-transform: uppercase;\n color: var(--dsw-alias-label-secondary);\n}\n\n/* 行卡列表(官方 .rows:无 marker,gap 8) */\n.ss-rows {\n list-style: none;\n margin: 0;\n padding: 0;\n display: flex;\n flex-direction: column;\n gap: 8px;\n}\n\n/* 行卡(官方 .rowCard:border-l2 r12 padding 12/14) */\n.ss-rowCard {\n border: 1px solid var(--dsw-alias-border-l2);\n border-radius: 12px;\n padding: 12px 14px;\n display: flex;\n align-items: center;\n justify-content: space-between;\n gap: 16px;\n}\n\n/* 行卡文本(标题 + 说明 左列) */\n.ss-rowText {\n display: flex;\n flex-direction: column;\n gap: 3px;\n min-width: 0;\n flex: 1;\n}\n\n/* 行卡标题(官方 .rowName:14px/22 500) */\n.ss-rowName {\n font-size: 14px;\n line-height: 22px;\n font-weight: 500;\n color: var(--dsw-alias-label-primary);\n}\n\n/* 行卡说明(12px/18 tertiary;v1.27.5 紧凑化:默认单行截断,hover title 看全文) */\n.ss-rowDesc {\n font-size: 12px;\n line-height: 18px;\n color: var(--dsw-alias-label-tertiary);\n word-break: break-word;\n overflow-wrap: anywhere;\n display: -webkit-box;\n -webkit-line-clamp: 2;\n -webkit-box-orient: vertical;\n overflow: hidden;\n}\n\n/* 行卡控件(右置) */\n.ss-rowControl {\n flex: none;\n display: flex;\n align-items: center;\n}\n\n/* 开关(官方 token 的自绘 toggle:track 22px,thumb 16px) */\n.ss-switch {\n position: relative;\n flex: none;\n width: 40px;\n height: 22px;\n cursor: pointer;\n}\n\n.ss-switch input {\n position: absolute;\n opacity: 0;\n width: 100%;\n height: 100%;\n margin: 0;\n cursor: pointer;\n}\n\n.ss-switchTrack {\n position: absolute;\n inset: 0;\n border-radius: 11px;\n background: var(--dsw-alias-border-l3);\n transition: background var(--ds-transition-duration-fast, 0.15s) var(--ds-ease-in-out, ease);\n}\n\n.ss-switchTrack::after {\n content: '';\n position: absolute;\n top: 3px;\n left: 3px;\n width: 16px;\n height: 16px;\n border-radius: 50%;\n background: var(--dsw-alias-label-primary-foreground);\n transition: transform var(--ds-transition-duration-fast, 0.15s) var(--ds-ease-in-out, ease);\n}\n\n.ss-switch input:checked + .ss-switchTrack {\n background: var(--dsw-alias-brand-primary);\n}\n\n.ss-switch input:checked + .ss-switchTrack::after {\n transform: translateX(18px);\n}\n\n.ss-switch input:disabled + .ss-switchTrack {\n opacity: 0.4;\n cursor: default;\n}\n\n/* 阈值行(滑块 + 值) */\n.ss-threshold {\n display: flex;\n align-items: center;\n gap: 12px;\n}\n\n.ss-threshold input[type='range'] {\n width: 160px;\n}\n\n/* Skiff 调试端口输入(number;关闭时跟随 disabled 半透明) */\n.ss-portInput {\n width: 96px;\n padding: 6px 8px;\n border-radius: 8px;\n border: 1px solid var(--dsw-alias-border-l2);\n background: var(--dsw-alias-bg-layer-2);\n color: var(--dsw-alias-label-primary);\n font-size: 13px;\n font-variant-numeric: tabular-nums;\n text-align: right;\n}\n\n.ss-portInput:disabled {\n opacity: 0.4;\n cursor: default;\n}\n\n.ss-value {\n min-width: 40px;\n font-variant-numeric: tabular-nums;\n font-size: 13px;\n font-weight: 600;\n color: var(--dsw-alias-label-primary);\n text-align: right;\n}\n\n/* 自主轨迹「立即唤起」按钮(v1.26.14 调试语义;官方 button 语言:primary 弱化) */\n.ss-wakeBtn {\n flex: none;\n padding: 5px 12px;\n border-radius: 8px;\n border: 1px solid var(--dsw-alias-border-l2);\n background: var(--dsw-alias-interactive-bg-active, rgba(128, 128, 128, 0.12));\n color: var(--dsw-alias-label-primary);\n font-size: 12px;\n line-height: 18px;\n font-weight: 500;\n cursor: pointer;\n transition: opacity var(--ds-transition-duration-fast, 0.15s) ease;\n}\n\n.ss-wakeBtn:hover:not(:disabled) {\n border-color: var(--dsw-alias-state-success-primary, #22c55e);\n color: var(--dsw-alias-state-success-primary, #22c55e);\n}\n\n.ss-wakeBtn:disabled {\n opacity: 0.4;\n cursor: default;\n}\n\n/* 底部帮助行 */\n.ss-hint {\n margin: 0;\n font-size: 12px;\n line-height: 18px;\n color: var(--dsw-alias-label-tertiary);\n word-break: break-word;\n overflow-wrap: anywhere;\n}\n\n/* 降级提示 */\n.ss-degrade {\n margin: 0;\n font-size: 12px;\n line-height: 18px;\n color: var(--dsw-alias-state-warn-label);\n word-break: break-word;\n overflow-wrap: anywhere;\n}\n\n/* ── v1.27.5 紧凑化:折叠组(details/summary)── */\n.ss-collapse {\n border: 1px solid var(--dsw-alias-border-l2);\n border-radius: 12px;\n overflow: hidden;\n}\n\n.ss-collapseHead {\n display: flex;\n align-items: baseline;\n gap: 10px;\n padding: 10px 14px;\n cursor: pointer;\n list-style: none;\n user-select: none;\n}\n\n.ss-collapseHead::-webkit-details-marker {\n display: none;\n}\n\n.ss-collapseHead::before {\n content: '▸';\n flex: none;\n font-size: 11px;\n color: var(--dsw-alias-label-tertiary);\n transition: transform 0.15s ease;\n}\n\n.ss-collapse[open] > .ss-collapseHead::before {\n transform: rotate(90deg);\n}\n\n.ss-collapseHead:hover {\n background: var(--dsw-alias-interactive-bg-hover, rgba(128, 128, 128, 0.06));\n}\n\n.ss-collapseTitle {\n font-size: 13px;\n font-weight: 500;\n color: var(--dsw-alias-label-primary);\n}\n\n.ss-collapseDesc {\n font-size: 12px;\n color: var(--dsw-alias-label-tertiary);\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n min-width: 0;\n}\n\n.ss-collapseBody {\n padding: 10px 14px 14px;\n border-top: 1px solid var(--dsw-alias-border-l1);\n}\n\n/* ── v1.27.5 紧凑化:只读定义列表(dl/dt/dd 2 列网格)── */\n.ss-defList {\n display: grid;\n grid-template-columns: repeat(2, minmax(0, 1fr));\n gap: 6px 20px;\n margin: 0;\n width: 100%;\n min-width: 0;\n}\n\n.ss-defItem {\n display: flex;\n flex-direction: column;\n gap: 1px;\n min-width: 0;\n}\n\n.ss-defTerm {\n font-size: 11px;\n line-height: 16px;\n font-weight: 500;\n letter-spacing: 0.02em;\n text-transform: uppercase;\n color: var(--dsw-alias-label-tertiary);\n}\n\n.ss-defValue {\n margin: 0;\n font-size: 12px;\n line-height: 18px;\n color: var(--dsw-alias-label-primary);\n word-break: break-word;\n overflow-wrap: anywhere;\n display: -webkit-box;\n -webkit-line-clamp: 2;\n -webkit-box-orient: vertical;\n overflow: hidden;\n}\n\n/* ── v1.27.5 紧凑化:开关 + 端口内联行 ── */\n.ss-switchRow {\n display: flex;\n align-items: center;\n gap: 8px;\n}\n\n/* ── v1.27.6 「?」帮助浮层(hover/焦点显示三行以上严谨说明)── */\n.ss-help {\n position: relative;\n display: inline-flex;\n align-items: center;\n margin-top: 2px;\n}\n\n.ss-helpMark {\n flex: none;\n width: 16px;\n height: 16px;\n padding: 0;\n border: 1px solid var(--dsw-alias-border-l2);\n border-radius: 50%;\n background: var(--dsw-alias-interactive-bg-active, rgba(128, 128, 128, 0.12));\n color: var(--dsw-alias-label-tertiary);\n font-size: 10px;\n line-height: 1;\n font-weight: 600;\n cursor: help;\n display: inline-flex;\n align-items: center;\n justify-content: center;\n transition: border-color 0.15s ease, color 0.15s ease;\n}\n\n.ss-helpMark:hover,\n.ss-helpMark:focus-visible {\n border-color: var(--dsw-alias-state-success-primary, #22c55e);\n color: var(--dsw-alias-state-success-primary, #22c55e);\n outline: none;\n}\n\n.ss-helpTip {\n position: absolute;\n left: 0;\n top: calc(100% + 6px);\n z-index: 30;\n width: 300px;\n max-width: 60vw;\n padding: 10px 12px;\n border: 1px solid var(--dsw-alias-border-l2);\n border-radius: 10px;\n background: var(--dsw-alias-bg-overlay);\n box-shadow: 0 6px 24px rgba(0, 0, 0, 0.28);\n font-size: 12px;\n line-height: 19px;\n font-weight: 400;\n color: var(--dsw-alias-label-primary);\n white-space: pre-line;\n word-break: break-word;\n opacity: 0;\n pointer-events: none;\n transform: translateY(-4px);\n transition: opacity 0.15s ease, transform 0.15s ease;\n}\n\n.ss-help:hover .ss-helpTip,\n.ss-helpMark:focus-visible + .ss-helpTip {\n opacity: 1;\n pointer-events: auto;\n transform: translateY(0);\n}\n\n/* 折叠组内帮助浮层定位修正(左侧行卡不会溢出面板) */\n.ss-rowCard .ss-helpTip {\n left: 0;\n}\n\n/* ── 微信桥(F4c-3,v1.27.0 实验性)── */\n\n/* CCC 选择器(下拉) */\n.ss-select {\n max-width: 280px;\n padding: 6px 8px;\n border-radius: 8px;\n border: 1px solid var(--dsw-alias-border-l2);\n background: var(--dsw-alias-bg-layer-2);\n color: var(--dsw-alias-label-primary);\n font-size: 13px;\n}\n\n/* 扫码绑定按钮(复用 wakeBtn 语言) */\n.ss-loginBtn {\n flex: none;\n padding: 5px 12px;\n border-radius: 8px;\n border: 1px solid var(--dsw-alias-border-l2);\n background: var(--dsw-alias-interactive-bg-active, rgba(128, 128, 128, 0.12));\n color: var(--dsw-alias-label-primary);\n font-size: 12px;\n line-height: 18px;\n font-weight: 500;\n cursor: pointer;\n transition: opacity var(--ds-transition-duration-fast, 0.15s) ease;\n}\n\n.ss-loginBtn:hover:not(:disabled) {\n border-color: var(--dsw-alias-state-success-primary, #22c55e);\n color: var(--dsw-alias-state-success-primary, #22c55e);\n}\n\n.ss-loginBtn:disabled {\n opacity: 0.4;\n cursor: default;\n}\n\n/* 移除账号按钮(危险语义:红边框) */\n.ss-removeBtn {\n flex: none;\n padding: 5px 12px;\n border-radius: 8px;\n border: 1px solid var(--dsw-alias-border-l2);\n background: var(--dsw-alias-interactive-bg-active, rgba(128, 128, 128, 0.12));\n color: var(--dsw-alias-label-primary);\n font-size: 12px;\n line-height: 18px;\n font-weight: 500;\n cursor: pointer;\n transition: opacity var(--ds-transition-duration-fast, 0.15s) ease;\n}\n\n.ss-removeBtn:hover:not(:disabled) {\n border-color: var(--dsw-alias-state-error-primary, #ef4444);\n color: var(--dsw-alias-state-error-primary, #ef4444);\n}\n\n/* 二维码展示行 */\n.ss-qrRow {\n align-items: center;\n}\n\n.ss-qrSvg {\n flex: none;\n width: 120px; /* v1.27.5 紧凑化:160 → 120 */\n height: 120px;\n border-radius: 8px;\n border: 1px solid var(--dsw-alias-border-l2);\n background: #fff; /* 二维码需要白底才能被微信扫出 */\n padding: 6px;\n display: flex;\n align-items: center;\n justify-content: center;\n}\n\n.ss-qrSvg svg {\n width: 100%;\n height: 100%;\n}\n\n/* 路由表 JSON 编辑框 */\n.ss-routesEditor {\n width: 100%;\n box-sizing: border-box;\n margin-top: 6px;\n padding: 8px 10px;\n border-radius: 8px;\n border: 1px solid var(--dsw-alias-border-l2);\n background: var(--dsw-alias-bg-layer-2);\n color: var(--dsw-alias-label-primary);\n font-size: 12px;\n line-height: 18px;\n font-family: var(--ds-font-family-code);\n resize: vertical;\n}\n";
|
|
4616
4616
|
if (typeof document !== "undefined" && document.querySelector("style[data-sp-css=\"SettingsSection\"]") === null) {
|
|
4617
4617
|
const tag = document.createElement("style");
|
|
4618
4618
|
tag.setAttribute("data-sp-css", "SettingsSection");
|
package/lib/index.js
CHANGED
|
@@ -2,9 +2,9 @@ import { t as __exportAll } from "./rolldown-runtime-D7D4PA-g.js";
|
|
|
2
2
|
import { a as findSerenityRoot, c as matchBlacklist, d as readHandymanConfig, i as findGitRoot, l as pathInside, n as SAFE_MODE_MARKER, o as isSafeModeOn, p as resolveInside, r as classifyPath, s as loadSerenityConfig, t as DEFAULT_SERENITY_CONFIG_PATHS, u as readBlacklist } from "./ccc-CfDrlfA7.js";
|
|
3
3
|
import { a as resolveRoleSystemPrompt, i as readSkiffRoles, l as systemPromptSource, r as isSkiffSessionId, s as roleToolWhitelist } from "./skiff-role-Dw7UKCUm.js";
|
|
4
4
|
import { C as splitModel, E as skiffRoleFor, S as requireWhitelistedModel, T as writeProgress, _ as buildRoundPrompt, a as startSkiffDebugServer, b as newStopToken, c as askSkiff, d as skiffMsmGate, f as skiffSessionInfo, g as HANDYMAN_GUIDE, h as unregisterSkiffSession, l as createSkiffAgent, m as skiffTrajectoryEnabled, n as jscSafeJsonText, o as stopSkiffDebugServer, p as skiffSessionSnapshot, r as renderSkiffMarkdown, s as stripThink, t as discoverCccs, u as getSkiffAgent, v as handymanProgressPaths, w as writeFailedStatus, x as readProgress, y as listActiveHandymen } from "./skiff-debug-BRlc17GU.js";
|
|
5
|
-
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-
|
|
5
|
+
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-BUJLsCGS.js";
|
|
6
6
|
import { n as registerSettingsSection, t as readSimpleSettings } from "./settings-section-PzeHStWP.js";
|
|
7
|
-
import { a as
|
|
7
|
+
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-OUCuw0Ws.js";
|
|
8
8
|
import z from "@deepseek-ai/schemastery";
|
|
9
9
|
import { defineTool } from "@deepseek-ai/dsh-tools";
|
|
10
10
|
import { appendFileSync, chmodSync, cpSync, existsSync, mkdirSync, readFileSync, readdirSync, realpathSync, renameSync, rmSync, rmdirSync, statSync, unlinkSync, utimesSync, writeFileSync } from "node:fs";
|
|
@@ -1293,6 +1293,85 @@ safe-mode 由 WebUI 开关控制(写 .serenity-safe-on 标记);黑名单
|
|
|
1293
1293
|
|
|
1294
1294
|
Config:
|
|
1295
1295
|
{ "safeMode": { "blacklist": [".secrets/"] } }
|
|
1296
|
+
|
|
1297
|
+
── 6. skiff.roles(F4 认知子集角色)──
|
|
1298
|
+
Skiff 角色 = 全知全能 trajectory 的任意子集(CCC 定义)。每角色独立模型 + 双白名单
|
|
1299
|
+
(MSM 白名单 msms[] 与非 MSM 工具白名单 tools[],白名单外全隐藏;skill 加载恒可用)。
|
|
1300
|
+
trajectory 纪律子集(session/keeper/rebuild 参与项)默认全关 = 完全独立。
|
|
1301
|
+
systemPrompt 内联 或 systemPromptFile(.md 引用,推荐)——角色会话的完整人格/边界提示词。
|
|
1302
|
+
validate 校验 / apply 生效 / list 查看:acc_msm exec skiff_admin <guide|validate|apply|list>。
|
|
1303
|
+
|
|
1304
|
+
Config:
|
|
1305
|
+
{ "skiff": { "roles": {
|
|
1306
|
+
"qa": {
|
|
1307
|
+
"model": "provider/model",
|
|
1308
|
+
"msms": ["web-search", "vlm-describe"],
|
|
1309
|
+
"tools": ["read", "grep", "glob"],
|
|
1310
|
+
"systemPromptFile": ".opencode/skiff/qa.md"
|
|
1311
|
+
} } } }
|
|
1312
|
+
|
|
1313
|
+
── 7. autopilotTrajectory(自动巡航轨迹)──
|
|
1314
|
+
CCC 定义的一条自主 trajectory——时钟到点自动唤起(前台注入,用户可见可介入)。
|
|
1315
|
+
未配置或 enabled=false → 完全不启动(零资源占用)。多 CCC 独立:每 CCC 自己的配置。
|
|
1316
|
+
唤起消息四段式:轨迹焦点 topPrompt(最先注入,稳定锚)→ 身份锚定 → 先验偏见
|
|
1317
|
+
(CCC 根脚本 biasProvider 输出)→ 任务。目标会话 session 必填(目录须带 --auto 后缀)。
|
|
1318
|
+
诊断/状态/立即唤起:acc_msm exec autopilot-trajectory <all|check|status|diag>。
|
|
1319
|
+
|
|
1320
|
+
Config:
|
|
1321
|
+
{ "autopilotTrajectory": {
|
|
1322
|
+
"enabled": true,
|
|
1323
|
+
"intervalHours": 2,
|
|
1324
|
+
"session": "S151",
|
|
1325
|
+
"biasProvider": "autopilot-bias.ts",
|
|
1326
|
+
"topPrompt": "本轨迹核心目标/纪律/质量要求(CCC 自填,防漂移)",
|
|
1327
|
+
"avoidWakeHours": { "start": 8, "end": 18 }
|
|
1328
|
+
} }
|
|
1329
|
+
|
|
1330
|
+
── 8. weixin(微信桥 F4c-3,含消息记录 hook)──
|
|
1331
|
+
CCC 级微信个人号接入(iLink 协议):dsh 一进程多 CCC,每 CCC 独立对接微信桥。
|
|
1332
|
+
账号/路由/开关在此文件;**bot_token 凭据在 CCC localstore credential scope**
|
|
1333
|
+
(扫码绑定后自动写入,永不进 git 明文面)。
|
|
1334
|
+
路由 user → role:exact 优先,* 通配兜底;role 必须 ∈ 该 CCC skiff.roles。
|
|
1335
|
+
面板(WebUI 设置 → 微信桥)可扫码绑定/移除账号/编辑路由;acc_msm exec weixin-doctor
|
|
1336
|
+
<status|diag|verify> 排查。凭据主动查看:localstore get WEIXIN_<ACCOUNT>_TOKEN。
|
|
1337
|
+
|
|
1338
|
+
Config:
|
|
1339
|
+
{ "weixin": {
|
|
1340
|
+
"enabled": true,
|
|
1341
|
+
"hook": "scripts/weixin-message-hook.ts",
|
|
1342
|
+
"accounts": [{ "accountId": "wechat-1", "name": "家庭助手", "enabled": true }],
|
|
1343
|
+
"routes": [{ "user": "*", "role": "zhaocai" }]
|
|
1344
|
+
} }
|
|
1345
|
+
|
|
1346
|
+
▸ weixin.hook(消息记录 hook,v1.27.13):
|
|
1347
|
+
微信桥每收/发一条消息触发一次 CCC 自写脚本,由 CCC 自行持久化保存(存哪/存成什么
|
|
1348
|
+
/是否入库全归 CCC——ACC 不绑定存储)。未配置 hook → 零变化(不触发)。
|
|
1349
|
+
|
|
1350
|
+
触发(双向):
|
|
1351
|
+
incoming = 用户 → bot:路由命中后、媒体落盘后触发(文本含语音转写;媒体带落盘 relPath)
|
|
1352
|
+
outgoing = bot → 用户:回复发送成功后触发(reply = 用户实际收到的纯文本,已剥离 think)
|
|
1353
|
+
|
|
1354
|
+
脚本约定(事件 JSON 单行经 stdin 传入;bun 优先 node 兜底):
|
|
1355
|
+
#!/usr/bin/env bun 或 node script.js —— 读 process.stdin 整段 JSON.parse
|
|
1356
|
+
const ev = JSON.parse(await new Response(process.stdin).text())
|
|
1357
|
+
ev.event === 'incoming' | 'outgoing'
|
|
1358
|
+
|
|
1359
|
+
事件 schema(**不含任何会话凭据**——context_token/bot_token/token/aes_key 均不出现):
|
|
1360
|
+
{ "event": "incoming", "ts": 1788359941488, "cccRoot": "/path/ccc",
|
|
1361
|
+
"accountId": "wechat-1", "userId": "u1@im.wechat",
|
|
1362
|
+
"sessionId": "skiff-weixin-xxx", "role": "zhaocai",
|
|
1363
|
+
"message": { "text": "你好", "media": [{ "kind": "image", "relPath": "_tmp/weixin-inbound/<hash>/img_x.jpg" }] } }
|
|
1364
|
+
{ "event": "outgoing", "ts": ..., "cccRoot": ..., "accountId": ...,
|
|
1365
|
+
"userId": ..., "sessionId": ..., "role": ...,
|
|
1366
|
+
"reply": "已记录(纯文本)" }
|
|
1367
|
+
|
|
1368
|
+
示例脚本(追加到按日文件——持久化归 CCC 自选:文件/DB/远端均可):
|
|
1369
|
+
const fs = require('node:fs'); const p = '/path/ccc/AGENT_SESSIONS/_weixin-log.jsonl';
|
|
1370
|
+
fs.appendFileSync(p, JSON.stringify(ev) + '\\n');
|
|
1371
|
+
|
|
1372
|
+
执行语义(旁路容忍):异步 fire-and-forget + 15s 超时 kill + 失败仅日志——
|
|
1373
|
+
微信桥消息处理/回复不受 hook 影响;脚本须在 CCC 根内(路径逃逸拒绝)。
|
|
1374
|
+
媒体 relPath 指向 _tmp/weixin-inbound/(临时目录)——需持久保存媒体文件请自行 copy。
|
|
1296
1375
|
`;
|
|
1297
1376
|
function parseRegistry(raw) {
|
|
1298
1377
|
const data = JSON.parse(raw.replace(/^\uFEFF/, ""));
|
|
@@ -1697,7 +1776,7 @@ function renderText$9(value) {
|
|
|
1697
1776
|
}
|
|
1698
1777
|
const msmTool = defineTool({
|
|
1699
1778
|
name: "acc_msm",
|
|
1700
|
-
description: "MSM (Mech & Semi-Mech) framework: list lists registered MSMs (header+flags display); exec executes (600s timeout, path-escape + symlink blocking, injects SERENITY_ROOT/CCC/VERSION env, appends --help TIP on failure; first arg --list/--schema/--format=json is protocol); register/deregister manage the registry (path inside root + script exists + globally unique validation, auto git precision commit); check quality checks DC-M1~M4; guide development manual; ccc-config CCC config reference (handyman.models/sessionKeeper.threshold/localstore.gitTrack/hooks.autoRestoreSession). Reuses the CCC mech-registry.json.",
|
|
1779
|
+
description: "MSM (Mech & Semi-Mech) framework: list lists registered MSMs (header+flags display); exec executes (600s timeout, path-escape + symlink blocking, injects SERENITY_ROOT/CCC/VERSION env, appends --help TIP on failure; first arg --list/--schema/--format=json is protocol); register/deregister manage the registry (path inside root + script exists + globally unique validation, auto git precision commit); check quality checks DC-M1~M4; guide development manual; ccc-config CCC config reference (all sections: handyman.models / sessionKeeper.threshold / localstore.gitTrack / hooks.autoRestoreSession / safeMode.blacklist / skiff.roles / autopilotTrajectory / weixin incl. message hook full guide). Reuses the CCC mech-registry.json.",
|
|
1701
1780
|
parameters: {
|
|
1702
1781
|
action: {
|
|
1703
1782
|
type: "string",
|
|
@@ -6416,11 +6495,11 @@ function registerStatusApi(ctx, opts = {}) {
|
|
|
6416
6495
|
sendJson$1(res, 400, { error: chk.error });
|
|
6417
6496
|
return;
|
|
6418
6497
|
}
|
|
6419
|
-
const { readWeixinSettings } = await import("./weixin-route-
|
|
6498
|
+
const { readWeixinSettings } = await import("./weixin-route-BUJLsCGS.js").then((n) => n.u);
|
|
6420
6499
|
const { weixinBridgeStatus } = await Promise.resolve().then(() => weixin_bridge_exports);
|
|
6421
6500
|
const settings = readWeixinSettings(chk.root);
|
|
6422
6501
|
const bridge = weixinBridgeStatus().find((b) => b.ccc === chk.root);
|
|
6423
|
-
const { readWeixinCredential } = await import("./weixin-route-
|
|
6502
|
+
const { readWeixinCredential } = await import("./weixin-route-BUJLsCGS.js").then((n) => n.u);
|
|
6424
6503
|
const accounts = (settings.accounts ?? []).map((a) => ({
|
|
6425
6504
|
accountId: a.accountId,
|
|
6426
6505
|
name: a.name ?? void 0,
|
|
@@ -6449,10 +6528,10 @@ function registerStatusApi(ctx, opts = {}) {
|
|
|
6449
6528
|
}
|
|
6450
6529
|
const root = chk.root;
|
|
6451
6530
|
if (body.action === "login-start") {
|
|
6452
|
-
const { fetchQRCode } = await import("./weixin-api-
|
|
6531
|
+
const { fetchQRCode } = await import("./weixin-api-OUCuw0Ws.js").then((n) => n.l);
|
|
6453
6532
|
const { randomUUID } = await import("node:crypto");
|
|
6454
6533
|
for (const [key, v] of weixinLogins) if (Date.now() - v.startedAt > WEIXIN_LOGIN_TTL_MS) weixinLogins.delete(key);
|
|
6455
|
-
const { readWeixinSettings } = await import("./weixin-route-
|
|
6534
|
+
const { readWeixinSettings } = await import("./weixin-route-BUJLsCGS.js").then((n) => n.u);
|
|
6456
6535
|
const qr = await fetchQRCode({ botType: readWeixinSettings(root).botType ?? void 0 });
|
|
6457
6536
|
const loginKey = randomUUID();
|
|
6458
6537
|
weixinLogins.set(loginKey, {
|
|
@@ -6473,7 +6552,7 @@ function registerStatusApi(ctx, opts = {}) {
|
|
|
6473
6552
|
sendJson$1(res, 400, { error: "missing accountId" });
|
|
6474
6553
|
return;
|
|
6475
6554
|
}
|
|
6476
|
-
const { removeWeixinAccount } = await import("./weixin-route-
|
|
6555
|
+
const { removeWeixinAccount } = await import("./weixin-route-BUJLsCGS.js").then((n) => n.u);
|
|
6477
6556
|
const { syncCccBridge } = await Promise.resolve().then(() => weixin_bridge_exports);
|
|
6478
6557
|
removeWeixinAccount(root, body.accountId);
|
|
6479
6558
|
syncCccBridge(ctx, root);
|
|
@@ -6486,7 +6565,7 @@ function registerStatusApi(ctx, opts = {}) {
|
|
|
6486
6565
|
sendJson$1(res, 400, { error: "invalid routes (expected [{user, role}, ...])" });
|
|
6487
6566
|
return;
|
|
6488
6567
|
}
|
|
6489
|
-
const { saveWeixinRoutes } = await import("./weixin-route-
|
|
6568
|
+
const { saveWeixinRoutes } = await import("./weixin-route-BUJLsCGS.js").then((n) => n.u);
|
|
6490
6569
|
const { readSkiffRoles } = await import("./skiff-role-Dw7UKCUm.js").then((n) => n.c);
|
|
6491
6570
|
const roles = readSkiffRoles(root);
|
|
6492
6571
|
for (const r of routes) if (!roles.has(r.role)) {
|
|
@@ -6499,7 +6578,7 @@ function registerStatusApi(ctx, opts = {}) {
|
|
|
6499
6578
|
}
|
|
6500
6579
|
if (body.action === "set-enabled") {
|
|
6501
6580
|
const enabled = body.enabled === true;
|
|
6502
|
-
const { readWeixinSettings, setWeixinEnabled } = await import("./weixin-route-
|
|
6581
|
+
const { readWeixinSettings, setWeixinEnabled } = await import("./weixin-route-BUJLsCGS.js").then((n) => n.u);
|
|
6503
6582
|
const { syncCccBridge } = await Promise.resolve().then(() => weixin_bridge_exports);
|
|
6504
6583
|
const settings = readWeixinSettings(root);
|
|
6505
6584
|
if (enabled && (settings.accounts ?? []).length === 0) {
|
|
@@ -6545,8 +6624,8 @@ function registerStatusApi(ctx, opts = {}) {
|
|
|
6545
6624
|
sendJson$1(res, 200, { status: "expired" });
|
|
6546
6625
|
return;
|
|
6547
6626
|
}
|
|
6548
|
-
const { pollQRStatus } = await import("./weixin-api-
|
|
6549
|
-
const { readWeixinSettings } = await import("./weixin-route-
|
|
6627
|
+
const { pollQRStatus } = await import("./weixin-api-OUCuw0Ws.js").then((n) => n.l);
|
|
6628
|
+
const { readWeixinSettings } = await import("./weixin-route-BUJLsCGS.js").then((n) => n.u);
|
|
6550
6629
|
const settings = readWeixinSettings(login.root);
|
|
6551
6630
|
const status = await pollQRStatus({
|
|
6552
6631
|
baseUrl: void 0,
|
|
@@ -6561,7 +6640,7 @@ function registerStatusApi(ctx, opts = {}) {
|
|
|
6561
6640
|
});
|
|
6562
6641
|
return;
|
|
6563
6642
|
}
|
|
6564
|
-
const { upsertWeixinAccount, writeWeixinCredential, nextWeixinAccountId } = await import("./weixin-route-
|
|
6643
|
+
const { upsertWeixinAccount, writeWeixinCredential, nextWeixinAccountId } = await import("./weixin-route-BUJLsCGS.js").then((n) => n.u);
|
|
6565
6644
|
const { syncCccBridge } = await Promise.resolve().then(() => weixin_bridge_exports);
|
|
6566
6645
|
const accountId = nextWeixinAccountId(settings);
|
|
6567
6646
|
upsertWeixinAccount(login.root, {
|
|
@@ -8488,6 +8567,163 @@ qInput.focus()
|
|
|
8488
8567
|
</html>`;
|
|
8489
8568
|
}
|
|
8490
8569
|
//#endregion
|
|
8570
|
+
//#region src/weixin-hook.ts
|
|
8571
|
+
/**
|
|
8572
|
+
* weixin-hook.ts — 微信桥消息记录 hook(F4c-3 扩展,v1.27.13 用户需求)
|
|
8573
|
+
*
|
|
8574
|
+
* 需求:微信桥发生的所有消息记录,支持 hook——允许 CCC 通过自行编写 hook
|
|
8575
|
+
* 脚本进行**持久性保存**(存哪/存成什么/是否入库,全归 CCC 决定——ACC 不绑定存储)。
|
|
8576
|
+
*
|
|
8577
|
+
* 设计(docs/weixin-message-hook-design.md v0.1,三项拍板 H1~H6):
|
|
8578
|
+
* - **双向触发**(H1):incoming(用户→bot,媒体落盘后)+ outgoing(bot→用户,发送成功后)
|
|
8579
|
+
* - **脚本 + stdin JSON**(H2):serenity.json weixin.hook = CCC 根相对脚本路径;
|
|
8580
|
+
* 每次消息事件 spawn 一次,事件 JSON 单行经 stdin 传入(bun 优先 / node 兜底,biasProvider 先例)
|
|
8581
|
+
* - **旁路容忍**(H3):异步 fire-and-forget + 超时 kill + 失败仅日志——微信桥可靠性不受影响
|
|
8582
|
+
* - **不含会话凭据**(H5):事件只有记录所需字段(无 context_token / bot_token——最小暴露面)
|
|
8583
|
+
*
|
|
8584
|
+
* 定位:Mech 纯逻辑(事件构造纯函数 + 执行器确定性),可独立单测。
|
|
8585
|
+
*/
|
|
8586
|
+
/** hook 执行超时(旁路记录足够;超时 kill 防挂死脚本) */
|
|
8587
|
+
const WEIXIN_HOOK_TIMEOUT_MS = 15e3;
|
|
8588
|
+
/** hook stderr/stdout 日志截断(防脚本刷屏) */
|
|
8589
|
+
const HOOK_LOG_MAX = 500;
|
|
8590
|
+
/** 构造 incoming 事件对象(纯函数,可测) */
|
|
8591
|
+
function buildIncomingHookEvent(input) {
|
|
8592
|
+
return {
|
|
8593
|
+
event: "incoming",
|
|
8594
|
+
ts: Date.now(),
|
|
8595
|
+
cccRoot: input.cccRoot,
|
|
8596
|
+
accountId: input.accountId,
|
|
8597
|
+
userId: input.userId,
|
|
8598
|
+
sessionId: input.sessionId,
|
|
8599
|
+
role: input.role,
|
|
8600
|
+
message: {
|
|
8601
|
+
text: input.text,
|
|
8602
|
+
media: input.media
|
|
8603
|
+
}
|
|
8604
|
+
};
|
|
8605
|
+
}
|
|
8606
|
+
/** 构造 outgoing 事件对象(纯函数,可测) */
|
|
8607
|
+
function buildOutgoingHookEvent(input) {
|
|
8608
|
+
return {
|
|
8609
|
+
event: "outgoing",
|
|
8610
|
+
ts: Date.now(),
|
|
8611
|
+
cccRoot: input.cccRoot,
|
|
8612
|
+
accountId: input.accountId,
|
|
8613
|
+
userId: input.userId,
|
|
8614
|
+
sessionId: input.sessionId,
|
|
8615
|
+
role: input.role,
|
|
8616
|
+
reply: input.reply
|
|
8617
|
+
};
|
|
8618
|
+
}
|
|
8619
|
+
/**
|
|
8620
|
+
* 执行一次 hook:spawn CCC 根脚本,事件 JSON 单行经 stdin 传入。
|
|
8621
|
+
* 旁路容忍(H3):超时 kill / 非 0 退出 / spawn 失败 → 仅日志返回 {ok:false},绝不抛(不阻断微信桥)。
|
|
8622
|
+
* 路径逃逸校验:脚本必须解析在 CCC 根内(resolveInside——resolveInside 抛错也吞为 {ok:false})。
|
|
8623
|
+
* 脚本缺失 → {ok:false}(配置了但未实现——日志提示;不视为异常)。
|
|
8624
|
+
*/
|
|
8625
|
+
async function runWeixinHook(root, hookRel, event, timeoutMs = WEIXIN_HOOK_TIMEOUT_MS) {
|
|
8626
|
+
let scriptAbs;
|
|
8627
|
+
try {
|
|
8628
|
+
scriptAbs = resolveInside(root, hookRel);
|
|
8629
|
+
} catch {
|
|
8630
|
+
return {
|
|
8631
|
+
ok: false,
|
|
8632
|
+
detail: `weixin hook 路径逃逸(须在 CCC 根内): ${hookRel}`
|
|
8633
|
+
};
|
|
8634
|
+
}
|
|
8635
|
+
if (!existsSync(scriptAbs)) {
|
|
8636
|
+
console.log(`[serenity-hooks] weixin hook 脚本缺失(配置了 weixin.hook 但文件不存在): ${hookRel}`);
|
|
8637
|
+
return {
|
|
8638
|
+
ok: false,
|
|
8639
|
+
detail: `hook 脚本不存在: ${hookRel}`
|
|
8640
|
+
};
|
|
8641
|
+
}
|
|
8642
|
+
const json = JSON.stringify(event);
|
|
8643
|
+
const runners = [["bun", [scriptAbs]], [process.execPath, [scriptAbs]]];
|
|
8644
|
+
for (const [cmd, args] of runners) try {
|
|
8645
|
+
const res = await runOnce(cmd, args, json, timeoutMs);
|
|
8646
|
+
if (!res.ok && res.code === "ENOENT") continue;
|
|
8647
|
+
return res;
|
|
8648
|
+
} catch {
|
|
8649
|
+
continue;
|
|
8650
|
+
}
|
|
8651
|
+
return {
|
|
8652
|
+
ok: false,
|
|
8653
|
+
detail: "hook 执行失败(bun 与 node 均不可用)"
|
|
8654
|
+
};
|
|
8655
|
+
}
|
|
8656
|
+
/** 单次 spawn 执行(Promise 化 + 超时 kill + 输出截断) */
|
|
8657
|
+
function runOnce(cmd, args, json, timeoutMs) {
|
|
8658
|
+
return new Promise((resolvePromise) => {
|
|
8659
|
+
let settled = false;
|
|
8660
|
+
let stdout = "";
|
|
8661
|
+
let stderr = "";
|
|
8662
|
+
let child = null;
|
|
8663
|
+
try {
|
|
8664
|
+
child = spawn(cmd, args, { stdio: [
|
|
8665
|
+
"pipe",
|
|
8666
|
+
"pipe",
|
|
8667
|
+
"pipe"
|
|
8668
|
+
] });
|
|
8669
|
+
} catch (err) {
|
|
8670
|
+
return resolvePromise({
|
|
8671
|
+
ok: false,
|
|
8672
|
+
code: "SPAWN_ERR",
|
|
8673
|
+
detail: String(err)
|
|
8674
|
+
});
|
|
8675
|
+
}
|
|
8676
|
+
const timer = setTimeout(() => {
|
|
8677
|
+
if (settled) return;
|
|
8678
|
+
settled = true;
|
|
8679
|
+
child?.kill("SIGKILL");
|
|
8680
|
+
resolvePromise({
|
|
8681
|
+
ok: false,
|
|
8682
|
+
detail: `hook 执行超时(>${Math.round(timeoutMs / 1e3)}s,已 kill)`
|
|
8683
|
+
});
|
|
8684
|
+
}, timeoutMs);
|
|
8685
|
+
child.stdin?.on("error", () => {});
|
|
8686
|
+
child.stdin?.write(json);
|
|
8687
|
+
child.stdin?.end();
|
|
8688
|
+
child.stdout?.on("data", (d) => {
|
|
8689
|
+
stdout = (stdout + d.toString()).slice(0, HOOK_LOG_MAX);
|
|
8690
|
+
});
|
|
8691
|
+
child.stderr?.on("data", (d) => {
|
|
8692
|
+
stderr = (stderr + d.toString()).slice(0, HOOK_LOG_MAX);
|
|
8693
|
+
});
|
|
8694
|
+
child.on("error", (err) => {
|
|
8695
|
+
if (settled) return;
|
|
8696
|
+
settled = true;
|
|
8697
|
+
clearTimeout(timer);
|
|
8698
|
+
resolvePromise({
|
|
8699
|
+
ok: false,
|
|
8700
|
+
code: err.code,
|
|
8701
|
+
detail: String(err.message ?? err)
|
|
8702
|
+
});
|
|
8703
|
+
});
|
|
8704
|
+
child.on("close", (code) => {
|
|
8705
|
+
if (settled) return;
|
|
8706
|
+
settled = true;
|
|
8707
|
+
clearTimeout(timer);
|
|
8708
|
+
if (code === 0) {
|
|
8709
|
+
if (stderr.trim() !== "") console.log(`[serenity-hooks] weixin hook stderr: ${stderr.trim()}`);
|
|
8710
|
+
resolvePromise({ ok: true });
|
|
8711
|
+
} else {
|
|
8712
|
+
console.log(`[serenity-hooks] weixin hook exit=${code}${stderr ? ` stderr: ${stderr.trim()}` : ""}`);
|
|
8713
|
+
resolvePromise({
|
|
8714
|
+
ok: false,
|
|
8715
|
+
detail: `hook exit=${code}`
|
|
8716
|
+
});
|
|
8717
|
+
}
|
|
8718
|
+
});
|
|
8719
|
+
});
|
|
8720
|
+
}
|
|
8721
|
+
let activeRunner = runWeixinHook;
|
|
8722
|
+
/** bridge 调用入口(转发 activeRunner——默认真实 spawn;测试可注入捕获) */
|
|
8723
|
+
function invokeWeixinHook(root, hookRel, event) {
|
|
8724
|
+
return activeRunner(root, hookRel, event);
|
|
8725
|
+
}
|
|
8726
|
+
//#endregion
|
|
8491
8727
|
//#region src/weixin-bridge.ts
|
|
8492
8728
|
var weixin_bridge_exports = /* @__PURE__ */ __exportAll({
|
|
8493
8729
|
handleIncoming: () => handleIncoming,
|
|
@@ -8635,6 +8871,7 @@ async function handleIncoming(ctx, root, accountId, cred, msg) {
|
|
|
8635
8871
|
try {
|
|
8636
8872
|
const mediaNotes = [];
|
|
8637
8873
|
const degradedNotes = [];
|
|
8874
|
+
const hookMedia = [];
|
|
8638
8875
|
for (const mediaRef of mediaRefs) {
|
|
8639
8876
|
const mediaType = mediaRef.kind === "image" ? "image_item" : "file_item";
|
|
8640
8877
|
const label = mediaRef.kind === "image" ? "一张图片" : `文件 ${mediaRef.fileName ?? "(未命名)"}`;
|
|
@@ -8643,10 +8880,18 @@ async function handleIncoming(ctx, root, accountId, cred, msg) {
|
|
|
8643
8880
|
mediaType
|
|
8644
8881
|
});
|
|
8645
8882
|
if (!result) {
|
|
8883
|
+
hookMedia.push({
|
|
8884
|
+
kind: mediaRef.kind,
|
|
8885
|
+
relPath: null
|
|
8886
|
+
});
|
|
8646
8887
|
degradedNotes.push(`(用户发送了${label},但下载失败——可请用户重发)`);
|
|
8647
8888
|
continue;
|
|
8648
8889
|
}
|
|
8649
8890
|
if (result.data.length > MEDIA_MAX_BYTES) {
|
|
8891
|
+
hookMedia.push({
|
|
8892
|
+
kind: mediaRef.kind,
|
|
8893
|
+
relPath: null
|
|
8894
|
+
});
|
|
8650
8895
|
degradedNotes.push(`(用户发送了${label},但超过 20MB 大小限制)`);
|
|
8651
8896
|
continue;
|
|
8652
8897
|
}
|
|
@@ -8657,8 +8902,16 @@ async function handleIncoming(ctx, root, accountId, cred, msg) {
|
|
|
8657
8902
|
const abs = join(inboundDir, fname);
|
|
8658
8903
|
writeFileSync(abs, result.data);
|
|
8659
8904
|
const rel = relative(root, abs);
|
|
8905
|
+
hookMedia.push({
|
|
8906
|
+
kind: mediaRef.kind,
|
|
8907
|
+
relPath: rel
|
|
8908
|
+
});
|
|
8660
8909
|
mediaNotes.push(`(用户发送了${mediaRef.kind === "image" ? "一张图片" : `文件 ${fname}`},已保存到 ${rel})`);
|
|
8661
8910
|
} catch {
|
|
8911
|
+
hookMedia.push({
|
|
8912
|
+
kind: mediaRef.kind,
|
|
8913
|
+
relPath: null
|
|
8914
|
+
});
|
|
8662
8915
|
degradedNotes.push(`(媒体保存失败,请重试)`);
|
|
8663
8916
|
}
|
|
8664
8917
|
}
|
|
@@ -8666,15 +8919,38 @@ async function handleIncoming(ctx, root, accountId, cred, msg) {
|
|
|
8666
8919
|
if (text) parts.push(text);
|
|
8667
8920
|
parts.push(...mediaNotes, ...degradedNotes);
|
|
8668
8921
|
const question = parts.join("\n");
|
|
8922
|
+
const hookRel = settings.hook;
|
|
8923
|
+
if (hookRel) invokeWeixinHook(root, hookRel, buildIncomingHookEvent({
|
|
8924
|
+
cccRoot: root,
|
|
8925
|
+
accountId,
|
|
8926
|
+
userId: fromUserId,
|
|
8927
|
+
sessionId,
|
|
8928
|
+
role: roleName,
|
|
8929
|
+
text,
|
|
8930
|
+
media: hookMedia
|
|
8931
|
+
})).catch((err) => {
|
|
8932
|
+
console.log(`[serenity-hooks] weixin hook incoming error: ${err instanceof Error ? err.message : String(err)}`);
|
|
8933
|
+
});
|
|
8669
8934
|
const answer = (await askSkiff(ctx, ref.agent, question, void 0, { includeTrajectory: false })).answer ?? "";
|
|
8670
8935
|
if (answer === "") return;
|
|
8936
|
+
const reply = markdownToPlainText(stripThink(answer));
|
|
8671
8937
|
await sendTextMessage({
|
|
8672
8938
|
baseUrl: cred.baseUrl,
|
|
8673
8939
|
token: cred.token,
|
|
8674
8940
|
toUserId: fromUserId,
|
|
8675
|
-
text:
|
|
8941
|
+
text: reply,
|
|
8676
8942
|
contextToken: msg.context_token
|
|
8677
8943
|
});
|
|
8944
|
+
if (hookRel) invokeWeixinHook(root, hookRel, buildOutgoingHookEvent({
|
|
8945
|
+
cccRoot: root,
|
|
8946
|
+
accountId,
|
|
8947
|
+
userId: fromUserId,
|
|
8948
|
+
sessionId,
|
|
8949
|
+
role: roleName,
|
|
8950
|
+
reply
|
|
8951
|
+
})).catch((err) => {
|
|
8952
|
+
console.log(`[serenity-hooks] weixin hook outgoing error: ${err instanceof Error ? err.message : String(err)}`);
|
|
8953
|
+
});
|
|
8678
8954
|
} finally {
|
|
8679
8955
|
await sendTypingStop(cred, accountId, fromUserId);
|
|
8680
8956
|
}
|
package/lib/msm-ops.d.ts
CHANGED
|
@@ -12,7 +12,7 @@ export type MsmAction = 'list' | 'exec' | 'register' | 'deregister' | 'check' |
|
|
|
12
12
|
export declare const MSM_ACTIONS: readonly MsmAction[];
|
|
13
13
|
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 (acc_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. acc_msm 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 acc_msm register --flags '<json>' (the tool currently parses the name style)\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 (acc_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)\nacc_msm exec <name> --list \u2014 list all MSMs\nacc_msm exec <name> --schema <n> \u2014 view a specific MSM's parameter schema\nacc_msm exec <name> --format=json \u2014 JSON output mode (remaining args passed through losslessly)\n";
|
|
14
14
|
/** CCC 配置参考(对齐 osp ccc-config action) */
|
|
15
|
-
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\nSESSION-KEEPER \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\uFF0Ccc_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";
|
|
15
|
+
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\nSESSION-KEEPER \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\uFF0Ccc_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\uFF1Aacc_msm exec skiff_admin <guide|validate|apply|list>\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\uFF1Aacc_msm exec autopilot-trajectory <all|check|status|diag>\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\uFF1Bacc_msm exec weixin-doctor\n<status|diag|verify> \u6392\u67E5\u3002\u51ED\u636E\u4E3B\u52A8\u67E5\u770B\uFF1Alocalstore get WEIXIN_<ACCOUNT>_TOKEN\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\u56DE\u590D\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\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\" }\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";
|
|
16
16
|
export interface MsmFlag {
|
|
17
17
|
name: string;
|
|
18
18
|
type?: string;
|
|
@@ -331,4 +331,4 @@ function markdownToPlainText(text) {
|
|
|
331
331
|
return result.trim();
|
|
332
332
|
}
|
|
333
333
|
//#endregion
|
|
334
|
-
export {
|
|
334
|
+
export { markdownToPlainText as a, sniffImageExt as c, getUpdates as i, weixin_api_exports as l, downloadMedia as n, sendTextMessage as o, getConfig as r, sendTyping as s, TypingStatus as t };
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* weixin-hook.ts — 微信桥消息记录 hook(F4c-3 扩展,v1.27.13 用户需求)
|
|
3
|
+
*
|
|
4
|
+
* 需求:微信桥发生的所有消息记录,支持 hook——允许 CCC 通过自行编写 hook
|
|
5
|
+
* 脚本进行**持久性保存**(存哪/存成什么/是否入库,全归 CCC 决定——ACC 不绑定存储)。
|
|
6
|
+
*
|
|
7
|
+
* 设计(docs/weixin-message-hook-design.md v0.1,三项拍板 H1~H6):
|
|
8
|
+
* - **双向触发**(H1):incoming(用户→bot,媒体落盘后)+ outgoing(bot→用户,发送成功后)
|
|
9
|
+
* - **脚本 + stdin JSON**(H2):serenity.json weixin.hook = CCC 根相对脚本路径;
|
|
10
|
+
* 每次消息事件 spawn 一次,事件 JSON 单行经 stdin 传入(bun 优先 / node 兜底,biasProvider 先例)
|
|
11
|
+
* - **旁路容忍**(H3):异步 fire-and-forget + 超时 kill + 失败仅日志——微信桥可靠性不受影响
|
|
12
|
+
* - **不含会话凭据**(H5):事件只有记录所需字段(无 context_token / bot_token——最小暴露面)
|
|
13
|
+
*
|
|
14
|
+
* 定位:Mech 纯逻辑(事件构造纯函数 + 执行器确定性),可独立单测。
|
|
15
|
+
*/
|
|
16
|
+
/** hook 执行超时(旁路记录足够;超时 kill 防挂死脚本) */
|
|
17
|
+
export declare const WEIXIN_HOOK_TIMEOUT_MS = 15000;
|
|
18
|
+
/** 媒体引用(hook 事件载荷:kind + 相对 CCC 根路径——CCC 可自行决定是否持久保存媒体) */
|
|
19
|
+
export interface WeixinHookMediaRef {
|
|
20
|
+
kind: 'image' | 'file';
|
|
21
|
+
/** 相对 CCC 根的落盘路径(_tmp/weixin-inbound/...);null = 未落盘 */
|
|
22
|
+
relPath: string | null;
|
|
23
|
+
}
|
|
24
|
+
/** hook 事件公共字段(incoming/outgoing 共享) */
|
|
25
|
+
interface WeixinHookEventBase {
|
|
26
|
+
/** 事件类型:incoming(用户→bot)/ outgoing(bot→用户) */
|
|
27
|
+
event: 'incoming' | 'outgoing';
|
|
28
|
+
/** 事件时间(epoch ms) */
|
|
29
|
+
ts: number;
|
|
30
|
+
/** 触发 CCC 根(多 CCC 一进程可区分) */
|
|
31
|
+
cccRoot: string;
|
|
32
|
+
/** 接收账号(incoming)/ 发送账号(outgoing) */
|
|
33
|
+
accountId: string;
|
|
34
|
+
/** 对端用户(from_user_id,形如 userA@im.wechat) */
|
|
35
|
+
userId: string;
|
|
36
|
+
/** 固定会话 id(同用户长期同一:skiff-weixin-xxx) */
|
|
37
|
+
sessionId: string;
|
|
38
|
+
/** 路由命中的 skiff role */
|
|
39
|
+
role: string;
|
|
40
|
+
}
|
|
41
|
+
/** incoming 事件(用户 → bot):文本(含语音转写)+ 媒体(已落盘 relPath) */
|
|
42
|
+
export interface WeixinHookIncomingEvent extends WeixinHookEventBase {
|
|
43
|
+
event: 'incoming';
|
|
44
|
+
message: {
|
|
45
|
+
/** 文本(含语音服务端转写);无文本 → null */
|
|
46
|
+
text: string | null;
|
|
47
|
+
/** 媒体(图片/文件);无 → [] */
|
|
48
|
+
media: WeixinHookMediaRef[];
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
/** outgoing 事件(bot → 用户):回复文本(已剥离 think) */
|
|
52
|
+
export interface WeixinHookOutgoingEvent extends WeixinHookEventBase {
|
|
53
|
+
event: 'outgoing';
|
|
54
|
+
reply: string;
|
|
55
|
+
}
|
|
56
|
+
/** 全部 hook 事件(判别联合) */
|
|
57
|
+
export type WeixinHookEvent = WeixinHookIncomingEvent | WeixinHookOutgoingEvent;
|
|
58
|
+
/** 事件载荷(JSON 序列化:不含任何会话凭据——H5) */
|
|
59
|
+
export type WeixinHookEventJson = WeixinHookEvent;
|
|
60
|
+
/** incoming 事件构造输入(bridge 提供实参) */
|
|
61
|
+
export interface IncomingHookInput {
|
|
62
|
+
cccRoot: string;
|
|
63
|
+
accountId: string;
|
|
64
|
+
userId: string;
|
|
65
|
+
sessionId: string;
|
|
66
|
+
role: string;
|
|
67
|
+
text: string | null;
|
|
68
|
+
media: WeixinHookMediaRef[];
|
|
69
|
+
}
|
|
70
|
+
/** outgoing 事件构造输入(bridge 提供实参) */
|
|
71
|
+
export interface OutgoingHookInput {
|
|
72
|
+
cccRoot: string;
|
|
73
|
+
accountId: string;
|
|
74
|
+
userId: string;
|
|
75
|
+
sessionId: string;
|
|
76
|
+
role: string;
|
|
77
|
+
reply: string;
|
|
78
|
+
}
|
|
79
|
+
/** 构造 incoming 事件对象(纯函数,可测) */
|
|
80
|
+
export declare function buildIncomingHookEvent(input: IncomingHookInput): WeixinHookIncomingEvent;
|
|
81
|
+
/** 构造 outgoing 事件对象(纯函数,可测) */
|
|
82
|
+
export declare function buildOutgoingHookEvent(input: OutgoingHookInput): WeixinHookOutgoingEvent;
|
|
83
|
+
/** hook 执行结果(测试/日志用;调用方只关心无异常) */
|
|
84
|
+
export interface WeixinHookRunResult {
|
|
85
|
+
ok: boolean;
|
|
86
|
+
detail?: string;
|
|
87
|
+
}
|
|
88
|
+
/**
|
|
89
|
+
* 执行一次 hook:spawn CCC 根脚本,事件 JSON 单行经 stdin 传入。
|
|
90
|
+
* 旁路容忍(H3):超时 kill / 非 0 退出 / spawn 失败 → 仅日志返回 {ok:false},绝不抛(不阻断微信桥)。
|
|
91
|
+
* 路径逃逸校验:脚本必须解析在 CCC 根内(resolveInside——resolveInside 抛错也吞为 {ok:false})。
|
|
92
|
+
* 脚本缺失 → {ok:false}(配置了但未实现——日志提示;不视为异常)。
|
|
93
|
+
*/
|
|
94
|
+
export declare function runWeixinHook(root: string, hookRel: string, event: WeixinHookEvent, timeoutMs?: number): Promise<WeixinHookRunResult>;
|
|
95
|
+
/** 测试辅助:把 WeixinHookEvent 转单行 JSON(脚本侧读取约定) */
|
|
96
|
+
export declare function hookEventToStdinLine(event: WeixinHookEvent): string;
|
|
97
|
+
/** hook runner 签名(可注入——bridge 集成测试捕获事件防真实 spawn flake) */
|
|
98
|
+
export type WeixinHookRunner = (root: string, hookRel: string, event: WeixinHookEvent) => Promise<WeixinHookRunResult>;
|
|
99
|
+
/** 测试辅助:替换 runner(null → 还原真实 spawn 执行);生产零调用 */
|
|
100
|
+
export declare function setWeixinHookRunnerForTest(runner: WeixinHookRunner | null): void;
|
|
101
|
+
/** bridge 调用入口(转发 activeRunner——默认真实 spawn;测试可注入捕获) */
|
|
102
|
+
export declare function invokeWeixinHook(root: string, hookRel: string, event: WeixinHookEvent): Promise<WeixinHookRunResult>;
|
|
103
|
+
export {};
|
|
@@ -340,6 +340,7 @@ function readWeixinSettings(root, paths = DEFAULT_SERENITY_CONFIG_PATHS) {
|
|
|
340
340
|
return {
|
|
341
341
|
enabled: w.enabled === true,
|
|
342
342
|
botType: typeof w.botType === "string" && w.botType !== "" ? w.botType : void 0,
|
|
343
|
+
hook: typeof w.hook === "string" && w.hook.trim() !== "" ? w.hook.trim() : void 0,
|
|
343
344
|
accounts: Array.isArray(w.accounts) ? w.accounts.filter((a) => typeof a === "object" && a !== null && typeof a.accountId === "string" && a.accountId !== "").map((a) => ({
|
|
344
345
|
accountId: a.accountId,
|
|
345
346
|
name: typeof a.name === "string" ? a.name : void 0,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@shgroup/dsh-serenity-hooks",
|
|
3
|
-
"version": "1.27.
|
|
3
|
+
"version": "1.27.14",
|
|
4
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)。",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|