@shgroup/dsh-serenity-hooks 1.58.2 → 1.59.1
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/dsh.plugin.json +1 -1
- package/lib/acp-auth.d.ts +232 -0
- package/lib/acp-face.d.ts +56 -1
- package/lib/ccc.d.ts +63 -0
- package/lib/client.js +317 -4
- package/lib/index.js +764 -12
- package/lib/localstore-ops-DMs_ovHo.js +282 -0
- package/lib/weixin-route-Aheyq3fN.js +281 -0
- package/package.json +1 -1
- package/lib/weixin-route-B9gU1YXz.js +0 -558
package/dsh.plugin.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"id": "dsh-serenity-hooks",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.59.1",
|
|
4
4
|
"main": "lib/index.js",
|
|
5
5
|
"description": "宁静号 ACC harness(Native Cordis 插件):给 DSH 装一个「AI 工作区」——14 个工具(container_fs/container_trajectory/dashboard/container_git/msm/praxis/ontology/serenity-skill-creator/cheap-worker/localstore/container_admin;另三个 im-bridge/acc-diag/diagram 按配置条件出现)+ 机械约束(安全模式/工作区围墙/密钥守卫)+ 轨迹日志与原地重建 + 网页登录入口/微信桥/子角色/对外问答页/trajectory 唤醒注册表。适配 DSH 0.1.7-rc.1 与 0.2.0-rc.1(双档,deepseek-ai/deepseek-harness)。",
|
|
6
6
|
"engines": {
|
|
@@ -0,0 +1,232 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* acp-auth.ts — ACP 面的**认证 + 身份**层(v1.59.0,owner 2026-10-17 令)
|
|
3
|
+
*
|
|
4
|
+
* ## 它解决什么
|
|
5
|
+
*
|
|
6
|
+
* owner 令(逐字):「将微信桥升级为 IM 桥,每个 CCC 可以选择微信实现或 ACP 实现,
|
|
7
|
+
* 微信实现保留不变向前兼容,**ACP 实现就可以暴露端口并绑定 skiff,并且要随即生成认证用的 token**」。
|
|
8
|
+
*
|
|
9
|
+
* 面 E(`acp-face.ts`)原本**无认证、无 CCC 归属、无角色** —— 本模块补上这三件:
|
|
10
|
+
* ① **token**:每 CCC 一枚,落**该 CCC 的 localstore**(credential scope)。
|
|
11
|
+
* ② **CCC 归属**:连接建立时由 token 反查 ⇒ **服务端权威**(不拿客户端自述的 `cwd` 当身份)。
|
|
12
|
+
* ③ **角色**:该 CCC 的 `im.acp.role` ⇒ 供装配层把 ACP 会话套上 skiff 角色。
|
|
13
|
+
*
|
|
14
|
+
* ## 🔴 架构决策:一面多 token,不是每 CCC 一个端口
|
|
15
|
+
*
|
|
16
|
+
* | | 甲:每 CCC 一个端口 | **乙:一面多 token** ← 选它 |
|
|
17
|
+
* |---|---|---|
|
|
18
|
+
* | 端口 | 随 CCC 数增长 | **固定 3200** |
|
|
19
|
+
* | 身份来源 | 端口号 | **token(一一对应 CCC)** |
|
|
20
|
+
*
|
|
21
|
+
* **判据**:`token → CCC` 是一一对应,而 `cwd` 是**客户端可填的**。
|
|
22
|
+
* ⇒ 用 token 定 CCC 比用 cwd 定 CCC 强:前者**服务端权威**,后者**客户端自述**。
|
|
23
|
+
* ⇒ 连接一建立就知道"这是哪个 CCC",**该连接的所有会话都归它**,角色绑定无需猜。
|
|
24
|
+
* ⚠️ `cwd` 仍要校验(见 `cwdInsideRoot`):防一枚 token 越权到别的 CCC。
|
|
25
|
+
*
|
|
26
|
+
* ## 🔴 为什么认证放在 WS upgrade 层,而不在 ACP 协议里
|
|
27
|
+
*
|
|
28
|
+
* `dsh-acp` 的 `authenticate()` **恒成功**且 `authMethods: []`(读其 `lib/index.js` 逐字),
|
|
29
|
+
* 且它**不导出**可替换的认证缝 ⇒ 改协议层要重写它的接线(P0′ 已实测此路不通)。
|
|
30
|
+
* 而**我们自己持有 HTTP server**(自持面)⇒ 在 `upgrade` **升级之前**校验,
|
|
31
|
+
* 失败即 `401 + destroy` ⇒ **连接根本进不来**。
|
|
32
|
+
*
|
|
33
|
+
* ## 边界
|
|
34
|
+
*
|
|
35
|
+
* 本模块**零 DSH 依赖**(只 `node:crypto` / `node:fs` / `node:path` 与本仓纯逻辑)⇒ 可独立单测。
|
|
36
|
+
*/
|
|
37
|
+
import { type ImSettings, type ImAcpSettings, type ImAcpAccount } from './ccc.js';
|
|
38
|
+
/** 该账户的凭据键(`IM_ACP_TOKEN_<ACCOUNT_ID>`);`accountId` 必填 */
|
|
39
|
+
export declare function acpTokenKeyFor(accountId: string): string;
|
|
40
|
+
/**
|
|
41
|
+
* 该**账户**的 ACP token(**读**;不存在返回 null)。
|
|
42
|
+
*
|
|
43
|
+
* 🔴 **不自动生成** —— 生成是**写操作**,只在显式要求时做(`ensureAcpToken`)。
|
|
44
|
+
* 读路径永远无副作用:否则每次判定都会改 localstore。
|
|
45
|
+
*
|
|
46
|
+
* @param accountId 账户键(必填 —— 凭据按账户分离,没有"CCC 默认 token"这回事)
|
|
47
|
+
*/
|
|
48
|
+
export declare function readAcpToken(root: string, accountId: string): string | null;
|
|
49
|
+
/**
|
|
50
|
+
* 生成(或返回既有)ACP token 并落该 CCC 的 localstore。
|
|
51
|
+
*
|
|
52
|
+
* ⚠️ **幂等**:已有 token 时**原样返回**(不轮换)—— 轮换会让在用的客户端立刻断线,
|
|
53
|
+
* 必须是一次显式动作(`resetAcpToken`)。
|
|
54
|
+
*
|
|
55
|
+
* @param accountId 多账户时指明哪个账户;省略 = 单账户简写键
|
|
56
|
+
*/
|
|
57
|
+
export declare function ensureAcpToken(root: string, accountId: string): string;
|
|
58
|
+
/**
|
|
59
|
+
* 轮换 token(显式动作;返回新值)。旧 token 立即失效。
|
|
60
|
+
* @param accountId 账户键
|
|
61
|
+
*/
|
|
62
|
+
export declare function resetAcpToken(root: string, accountId: string): string;
|
|
63
|
+
/**
|
|
64
|
+
* 常时比较(防时序侧信道)。
|
|
65
|
+
*
|
|
66
|
+
* 🔴 两个长度不等时 `timingSafeEqual` **会抛** ⇒ 先比长度并立刻返回 false。
|
|
67
|
+
* ⚠️ 那一步本身**泄漏长度**(可接受:token 长度固定且公开)。
|
|
68
|
+
*/
|
|
69
|
+
export declare function safeEqual(a: string, b: string): boolean;
|
|
70
|
+
/** token 指纹(诊断面用;不可逆) */
|
|
71
|
+
export declare function tokenFingerprint(token: string): string;
|
|
72
|
+
/**
|
|
73
|
+
* 从一次 HTTP upgrade 请求里取出候选 token。
|
|
74
|
+
*
|
|
75
|
+
* 两种携带方式都认(客户端差异;Agmente 等用具名头,脚本常用查询串):
|
|
76
|
+
* ① `Authorization: Bearer <t>`
|
|
77
|
+
* ② `?token=<t>`(查询串)
|
|
78
|
+
*
|
|
79
|
+
* @returns 取到的 token;两者都无 → null
|
|
80
|
+
*/
|
|
81
|
+
export declare function extractToken(headers: Record<string, unknown>, url: string | undefined): string | null;
|
|
82
|
+
/**
|
|
83
|
+
* 一个"可接受 token 的账户"——**认证闸真正比对的单元**。
|
|
84
|
+
*
|
|
85
|
+
* 🔴 v1.59.1 的关键抽象变更:原先是"CCC ↔ 一枚 token",现在是
|
|
86
|
+
* **"(CCC, 账户)↔ 一枚 token ↔ 一个角色"**。同一个 CCC 可以有多个账户
|
|
87
|
+
* (一台手机、一个脚本各一枚),彼此**互不影响**:撤一个不动另一个。
|
|
88
|
+
*/
|
|
89
|
+
export interface AcpAccount {
|
|
90
|
+
/** 归属 CCC 根 */
|
|
91
|
+
root: string;
|
|
92
|
+
/** 账户键(单账户简写 ⇒ `''`) */
|
|
93
|
+
accountId: string;
|
|
94
|
+
/** 凭据键(localstore) */
|
|
95
|
+
tokenKey: string;
|
|
96
|
+
/** 该账户的 token(**读时现取**;不缓存) */
|
|
97
|
+
token: string;
|
|
98
|
+
/** 该账户绑定的 skiff 角色名 */
|
|
99
|
+
role: string;
|
|
100
|
+
}
|
|
101
|
+
/**
|
|
102
|
+
* 把该 CCC 的配置**展开成账户列表**(读时现算,**无快照**)。
|
|
103
|
+
*
|
|
104
|
+
* 🔴 **`im.acp.accounts` 是必填项** —— 没有它就没有账户,**一个都认证不了**。
|
|
105
|
+
* 刻意**不提供**"单账户简写"退回路径(v1.59.1):那会多出一条永不生效的分支,
|
|
106
|
+
* 而它的唯一作用是把"配置写错了"伪装成"用了简写"。
|
|
107
|
+
*
|
|
108
|
+
* 判据(三条都由调用方以 fail-closed 消费):
|
|
109
|
+
* - `enabled !== false`(缺省 true)
|
|
110
|
+
* - `role` 非空(空 ⇒ 该账户**不出现**在列表里 ⇒ 必然登录失败)
|
|
111
|
+
* - token 存在(不存在 ⇒ 同样不出现)
|
|
112
|
+
*
|
|
113
|
+
* ⚠️ 本函数**纯读,绝不生成 token** —— 生成是写操作,归 `ensureAcpChannelTokens`。
|
|
114
|
+
* 这样 upgrade 路径可以随时现算而不冒"每次连接都写 localstore"的风险。
|
|
115
|
+
*/
|
|
116
|
+
export declare function expandAcpAccounts(root: string, paths?: string[]): AcpAccount[];
|
|
117
|
+
/**
|
|
118
|
+
* **让配置里声明的账户都拥有 token**(写操作;幂等;由装配层与配置变更时调用)。
|
|
119
|
+
*
|
|
120
|
+
* 🔴 owner 令「**随即生成**认证用的 token」的落地点。与 v1.59.0 的差别:
|
|
121
|
+
* 那时只在**启动**时跑一次 ⇒ 加一台设备要重启;现在**任何一次判定前都可以跑**,
|
|
122
|
+
* 且撤账户后不会被"复活"(只对**当前声明里**的账户生成)。
|
|
123
|
+
*
|
|
124
|
+
* @returns 本次**新生成**的账户键列表(已存在的原样返回,不轮换)
|
|
125
|
+
*/
|
|
126
|
+
export declare function ensureAcpChannelTokens(root: string, paths?: string[]): string[];
|
|
127
|
+
/**
|
|
128
|
+
* 校验一枚候选 token,返回它归属的**账户**(未命中返回 null)。
|
|
129
|
+
*
|
|
130
|
+
* @param candidate 客户端出示的 token
|
|
131
|
+
* @param roots 候选 CCC 根集合(由调用方枚举——本模块不认识"CCC 在哪")
|
|
132
|
+
*/
|
|
133
|
+
export declare function resolveTokenAccount(candidate: string, roots: readonly string[], paths?: string[]): AcpAccount | null;
|
|
134
|
+
/**
|
|
135
|
+
* 枚举**有可用 ACP 账户**的 CCC(供准入名单用)。
|
|
136
|
+
*
|
|
137
|
+
* 判据:`im.channel === 'acp'` ∧ `im.acp.enabled === true` ∧ **至少一个账户有 token 且配了角色**。
|
|
138
|
+
* ⚠️ 缺一即不算(fail-closed:宁可连不上,不可放行一个半配置的通道)。
|
|
139
|
+
*/
|
|
140
|
+
export declare function listAcpRoots(roots: readonly string[], paths?: string[]): string[];
|
|
141
|
+
/** 读该 CCC 的 `im` 段(未配置 ⇒ 归一缺省;永不 undefined) */
|
|
142
|
+
export declare function readImSettings(root: string, paths?: string[]): Required<Pick<ImSettings, 'channel'>> & {
|
|
143
|
+
acp: ImAcpSettings;
|
|
144
|
+
};
|
|
145
|
+
/**
|
|
146
|
+
* 该 CCC 是否启用 ACP 入站通道。
|
|
147
|
+
*
|
|
148
|
+
* 🔴 **两个条件都真才算**:`im.channel === 'acp'` **且** `im.acp.enabled === true`。
|
|
149
|
+
* 为什么双键:`channel` 表达"选了哪条路",`enabled` 表达"现在开不开" ⇒
|
|
150
|
+
* 让 owner 能**选好 ACP 但暂时关着**(不用删配置),与既有各面的 `enabled` 范式一致。
|
|
151
|
+
*/
|
|
152
|
+
export declare function acpChannelEnabled(root: string, paths?: string[]): boolean;
|
|
153
|
+
/**
|
|
154
|
+
* 客户端 `session/new` 传来的 `cwd` 是否落在该 token 归属的 CCC 内。
|
|
155
|
+
*
|
|
156
|
+
* 🔴 **为什么必须校验**:token 定的是"哪个 CCC",而 `cwd` 由**客户端**指定。
|
|
157
|
+
* 不校验 ⇒ 一枚 CCC A 的 token 可以把会话开到 CCC B(越权读到另一个 CCC 的上下文)。
|
|
158
|
+
* 这与 CCC 自身的路径围墙(P3)是同一条纪律,只是发生在**接入**这一层。
|
|
159
|
+
*
|
|
160
|
+
* 判据用 `relative()` 而非 `startsWith`(后者会把 `/a/bc` 误判为在 `/a/b` 内)。
|
|
161
|
+
*/
|
|
162
|
+
export declare function cwdInsideRoot(cwd: string, root: string): boolean;
|
|
163
|
+
/**
|
|
164
|
+
* 一次性解析:候选 token → 归属 **账户**(含 CCC 根、账户键、角色)。
|
|
165
|
+
*
|
|
166
|
+
* @returns 账户;token 无效 / 通道未启用 / 无角色 → null(**调用方一律拒绝连接**)
|
|
167
|
+
*/
|
|
168
|
+
export declare function authorizeAcpConnection(candidate: string | null, roots: readonly string[], paths?: string[]): AcpAccount | null;
|
|
169
|
+
/** 单个账户的对外可见形态(**绝不含 token 明文**) */
|
|
170
|
+
export interface AcpAccountStatus {
|
|
171
|
+
accountId: string;
|
|
172
|
+
name: string | null;
|
|
173
|
+
role: string | null;
|
|
174
|
+
enabled: boolean;
|
|
175
|
+
hasToken: boolean;
|
|
176
|
+
tokenFingerprint: string | null;
|
|
177
|
+
/** 该账户此刻**能不能被认证闸接受**(三条判据全真) */
|
|
178
|
+
usable: boolean;
|
|
179
|
+
/** 不能用的原因(能用 ⇒ null)—— 直接指向该改哪个字段 */
|
|
180
|
+
blockedBy: string | null;
|
|
181
|
+
}
|
|
182
|
+
/** 该 CCC 的 ACP 通道对外**可见形态**(诊断面用;**绝不含 token 明文**) */
|
|
183
|
+
export declare function acpChannelStatus(root: string, paths?: string[]): {
|
|
184
|
+
channel: 'weixin' | 'acp';
|
|
185
|
+
enabled: boolean;
|
|
186
|
+
accounts: AcpAccountStatus[];
|
|
187
|
+
};
|
|
188
|
+
/** 只读:该 CCC 的 localstore 是否存在(避免对不存在的 CCC 白跑 token 反查) */
|
|
189
|
+
export declare function hasLocalstore(root: string): boolean;
|
|
190
|
+
/** 只读:localstore 所在目录(诊断路径展示;不创建) */
|
|
191
|
+
export declare function localstoreDir(root: string): string;
|
|
192
|
+
/**
|
|
193
|
+
* 该 CCC 是否"看起来像"一个可用 ACP 宿主(只读低成本预筛)。
|
|
194
|
+
* ⚠️ 与 `acpChannelEnabled` 分开:本函数**不读配置**(只判文件存在),用于快速排除。
|
|
195
|
+
*/
|
|
196
|
+
export declare function looksLikeCcc(root: string): boolean;
|
|
197
|
+
/** 排重后的 token 归属表(诊断用;同一 token 出现在两个账户 ⇒ 响亮暴露,不静默取第一个) */
|
|
198
|
+
export declare function tokenOwnerAudit(roots: readonly string[]): {
|
|
199
|
+
owners: Array<{
|
|
200
|
+
root: string;
|
|
201
|
+
accountId: string;
|
|
202
|
+
fingerprint: string;
|
|
203
|
+
}>;
|
|
204
|
+
duplicates: Array<{
|
|
205
|
+
fingerprint: string;
|
|
206
|
+
owners: string[];
|
|
207
|
+
}>;
|
|
208
|
+
};
|
|
209
|
+
/** 测试辅助:读 store 里该账户键的原始值(绕开 getEntry 的归一) */
|
|
210
|
+
export declare function __rawTokenForTest(root: string, accountId: string): string | undefined;
|
|
211
|
+
/**
|
|
212
|
+
* 写回 `im.acp.accounts`(保留文件里其余所有字段)。
|
|
213
|
+
*
|
|
214
|
+
* 🔴 **只碰这一个键**:`im.channel` / `im.acp.enabled` 归面板总开关与人工配置,
|
|
215
|
+
* 本函数不越权改它们(否则"加一台设备"会顺手把通道开关也改了)。
|
|
216
|
+
*/
|
|
217
|
+
export declare function saveAcpAccounts(root: string, accounts: ImAcpAccount[], paths?: string[]): ImAcpAccount[];
|
|
218
|
+
/**
|
|
219
|
+
* 规范化一条账户声明(面板传入的任意形状 → 合法声明;非法 ⇒ null)。
|
|
220
|
+
*
|
|
221
|
+
* 🔴 返回类型里 `role` 是**必填**(不是 `ImAcpAccount` 的可选形态)——
|
|
222
|
+
* 本函数的契约就是"要么给一个角色齐备的账户,要么给 null"。
|
|
223
|
+
* 这样调用方**不必再判一次** `role` 有没有(那正是会漂的第二处判据)。
|
|
224
|
+
*/
|
|
225
|
+
export declare function normalizeAcpAccount(input: unknown): (ImAcpAccount & {
|
|
226
|
+
accountId: string;
|
|
227
|
+
role: string;
|
|
228
|
+
}) | null;
|
|
229
|
+
/** 增/改一条账户(按 accountId 定位;写入后返回全表) */
|
|
230
|
+
export declare function upsertAcpAccount(root: string, account: ImAcpAccount, paths?: string[]): ImAcpAccount[];
|
|
231
|
+
/** 删一条账户(**同时清掉它的 token**,避免留死键) */
|
|
232
|
+
export declare function removeAcpAccount(root: string, accountId: string, paths?: string[]): ImAcpAccount[];
|
package/lib/acp-face.d.ts
CHANGED
|
@@ -48,6 +48,29 @@
|
|
|
48
48
|
*/
|
|
49
49
|
import type { Context } from 'cordis';
|
|
50
50
|
import { type FaceHandle } from './face-host.js';
|
|
51
|
+
/**
|
|
52
|
+
* 刷新"本机有哪些 CCC"(async,枚举宿主三来源)。
|
|
53
|
+
*
|
|
54
|
+
* 🔴 **失败必须响亮**:v1.59.0 这里是 `void refresh(...)` 且没有 catch ——
|
|
55
|
+
* 枚举失败会让名单清空 ⇒ **所有连接一律 401**,而现场只有一行 stdout 看不到的日志。
|
|
56
|
+
* 这正是本轨记过的「调用没报错 ≠ 已验证」。
|
|
57
|
+
*
|
|
58
|
+
* @returns 本次枚举到的根数(0 = 一个都没发现,日志会说为什么)
|
|
59
|
+
*/
|
|
60
|
+
export declare function refreshAcpRoots(ctx: Context): Promise<number>;
|
|
61
|
+
/** 当前准入名单(诊断用只读快照;**现算**,不是缓存值) */
|
|
62
|
+
export declare function acpRootsSnapshot(): readonly string[];
|
|
63
|
+
/**
|
|
64
|
+
* 测试注入钩子(**生产零调用**):直接置"本机有哪些 CCC 候选根"。
|
|
65
|
+
*
|
|
66
|
+
* ⚠️ 为什么需要:`refreshAcpRoots()` 依赖 `listCccs(ctx)`(要真 CCC 环境)。
|
|
67
|
+
* 而认证闸本身是**纯逻辑**,测试要能单独驱动它 —— 否则"闸真的拦得住吗"这件事
|
|
68
|
+
* 只能靠集成环境验,而集成环境恰恰是最不容易跑的那种。
|
|
69
|
+
*
|
|
70
|
+
* 🔴 v1.59.1:**置的是候选根,不是"放行名单"** —— 每个根会不会被放行,
|
|
71
|
+
* 仍由它自己的 `serenity.json` + localstore **现算**决定(测试照样要走真配置)。
|
|
72
|
+
*/
|
|
73
|
+
export declare function __setAcpRootsForTest(roots: readonly string[] | null): void;
|
|
51
74
|
export declare function acpFaceActive(): boolean;
|
|
52
75
|
export declare function acpFacePort(): number | null;
|
|
53
76
|
/**
|
|
@@ -56,6 +79,34 @@ export declare function acpFacePort(): number | null;
|
|
|
56
79
|
* 由装配层用它决定"起还是停"。
|
|
57
80
|
*/
|
|
58
81
|
export declare function acpFaceEnabled(): boolean;
|
|
82
|
+
/**
|
|
83
|
+
* 把客户端给的 `cwd` 归一化成宿主要求的**绝对路径**(v1.59.2)。
|
|
84
|
+
*
|
|
85
|
+
* ## 为什么需要
|
|
86
|
+
* 宿主 `dsh-acp` 在每次 `session/new` 都有这一行(`lib/index.js` 逐字):
|
|
87
|
+
* ```js
|
|
88
|
+
* if (!isAbsolute(params.cwd)) throw invalidParams(`cwd must be an absolute path: ${params.cwd}`)
|
|
89
|
+
* ```
|
|
90
|
+
* 而**连接归属哪个 CCC 已由 token 决定**(服务端权威)⇒ 让用户再手打一遍绝对路径
|
|
91
|
+
* 是**同一个信息说两遍**,且那第二遍正是最容易打错的一遍。
|
|
92
|
+
*
|
|
93
|
+
* ## 规则(唯一一条)
|
|
94
|
+
* **非绝对路径 ⇒ 一律解释为「本连接归属 CCC 的根」**。
|
|
95
|
+
*
|
|
96
|
+
* | 客户端填 | 送宿主 |
|
|
97
|
+
* |---|---|
|
|
98
|
+
* | `serenity` / `a` / `.` / 空 | `/…/home-serenity`(该 token 的 CCC 根) |
|
|
99
|
+
* | `/…/home-serenity`(已绝对且在根内) | 原样 |
|
|
100
|
+
* | `/…/home-serenity/sub` | 原样(仍在根内) |
|
|
101
|
+
* | `/etc`(绝对但在根外) | **原样透传,交给 `cwdInsideRoot` 拒** |
|
|
102
|
+
*
|
|
103
|
+
* 🔴 **最后一行为什么不在这里直接拒**:越权判据必须**只有一处**(`cwdInsideRoot`,
|
|
104
|
+
* 它也管着角色认领),在这里再判一次就是**两份会漂的实现**。归一化只负责"把非绝对变成绝对"。
|
|
105
|
+
*
|
|
106
|
+
* @param raw 客户端原样给的 `cwd`(可能是 `undefined`)
|
|
107
|
+
* @param cccRoot 本连接归属的 CCC 根
|
|
108
|
+
*/
|
|
109
|
+
export declare function normalizeClientCwd(raw: unknown, cccRoot: string): string;
|
|
59
110
|
/** 面的运行统计(`dashboard` / 诊断面用;**只读快照**) */
|
|
60
111
|
export interface AcpFaceStatus {
|
|
61
112
|
active: boolean;
|
|
@@ -64,12 +115,16 @@ export interface AcpFaceStatus {
|
|
|
64
115
|
enabled: boolean;
|
|
65
116
|
connections: number;
|
|
66
117
|
applied: number;
|
|
118
|
+
/** 🔴 被认证闸拒掉的连接数(v1.59.0):它 > 0 说明**有人在敲门或 token 配错了** */
|
|
119
|
+
rejected: number;
|
|
120
|
+
/** 当前准入的 CCC 数(启用了 ACP 通道 + 已生成 token) */
|
|
121
|
+
authorizedCccs: number;
|
|
67
122
|
errors: Array<{
|
|
68
123
|
at: number;
|
|
69
124
|
applied: boolean;
|
|
70
125
|
error?: string;
|
|
71
126
|
}>;
|
|
72
|
-
/** 🔴
|
|
127
|
+
/** 🔴 认证状态一句话(v1.59.0 取代原"本面无认证") */
|
|
73
128
|
authNote: string;
|
|
74
129
|
}
|
|
75
130
|
export declare function acpFaceStatus(): AcpFaceStatus;
|
package/lib/ccc.d.ts
CHANGED
|
@@ -137,6 +137,16 @@ interface SerenityConfig {
|
|
|
137
137
|
* 未配置或 enabled=false → 该 CCC 桥完全不启动(零资源占用)。
|
|
138
138
|
*/
|
|
139
139
|
weixin?: WeixinSettings;
|
|
140
|
+
/**
|
|
141
|
+
* IM 通道选择(v1.59.0,owner 2026-10-17 令):
|
|
142
|
+
* 「将微信桥升级为 IM 桥,每个 CCC 可以选择微信实现或 ACP 实现,微信实现保留不变向前兼容,
|
|
143
|
+
* ACP 实现就可以暴露端口并绑定 skiff,并且要随即生成认证用的 token」。
|
|
144
|
+
*
|
|
145
|
+
* 🔴 **缺省 = weixin**(未配置的 CCC 行为与今天**逐字相同**)⇒ 向前兼容由**缺省值**保证,
|
|
146
|
+
* 不靠代码分支。选 `acp` 时该 CCC 的入站改走 ACP 面(面 E,端口 3200);
|
|
147
|
+
* 其 `weixin.*` 段**保留**(向前兼容),但**不再驱动入站**;出站 `im-bridge` 不受影响。
|
|
148
|
+
*/
|
|
149
|
+
im?: ImSettings;
|
|
140
150
|
/**
|
|
141
151
|
* 专属工具声明(v1.33,S142 §32.11):**ACC 注册但默认对所有 CCC 隐藏**的工具,
|
|
142
152
|
* 只有在自己这里声明的 CCC 可见(例:`["acc-diag"]` = ACC 负责人的运行态诊断)。
|
|
@@ -241,6 +251,59 @@ export interface WeixinSettings {
|
|
|
241
251
|
*/
|
|
242
252
|
fallbackOnNoSend?: boolean;
|
|
243
253
|
}
|
|
254
|
+
/**
|
|
255
|
+
* IM 通道选择(CCC 级;v1.59.0,owner 2026-10-17 令)。
|
|
256
|
+
*
|
|
257
|
+
* 归属判据(D23「改它要不要发版」):**选哪个通道、绑哪个角色 = CCC 的数据** ⇒ 归此处;
|
|
258
|
+
* **怎么校验 token、怎么把 ACP 会话套上角色 = 机制** ⇒ 归 ACC 源码。
|
|
259
|
+
*/
|
|
260
|
+
export interface ImSettings {
|
|
261
|
+
/**
|
|
262
|
+
* 入站通道选择。`"weixin"`(**缺省**,=现状)| `"acp"`。
|
|
263
|
+
*
|
|
264
|
+
* 🔴 **缺省值即向前兼容**:不写这一项(或整个 `im` 段不存在)的 CCC,行为与升级前逐字相同。
|
|
265
|
+
* ⚠️ **只影响入站**(谁来驱动本 CCC 的 agent);**出站**(`im-bridge` 工具发送)不受本项影响。
|
|
266
|
+
*/
|
|
267
|
+
channel?: 'weixin' | 'acp';
|
|
268
|
+
/** ACP 通道参数(仅 `channel: "acp"` 时有意义) */
|
|
269
|
+
acp?: ImAcpSettings;
|
|
270
|
+
}
|
|
271
|
+
/** ACP 通道参数(CCC 级) */
|
|
272
|
+
export interface ImAcpSettings {
|
|
273
|
+
/**
|
|
274
|
+
* 该 CCC 的 ACP 面开关(**与 `channel: "acp"` 是「与」关系**:两者都真才算启用)。
|
|
275
|
+
* 缺省 false。
|
|
276
|
+
*/
|
|
277
|
+
enabled?: boolean;
|
|
278
|
+
/**
|
|
279
|
+
* **账户声明 —— 必填**(v1.59.1,owner 2026-10-17 令「token 生成和 skiff 绑定都做配置上」)。
|
|
280
|
+
*
|
|
281
|
+
* 🔴 **声明式,照 `weixin.accounts[]` 的形态**:结构(账户与角色)放本文件,
|
|
282
|
+
* 凭据(token)由 `accountId` 派生键 `IM_ACP_TOKEN_<ACCOUNT_ID>` 落 localstore。
|
|
283
|
+
*
|
|
284
|
+
* 判据(关键):**配置是真相源,localstore 只是它的派生凭据**。
|
|
285
|
+
* 声明了账户却还没有 token ⇒ **下次判定前当场生成**(不依赖启动那一次)。
|
|
286
|
+
* 撤销账户 ⇒ 该账户的 token **不再被接受**(即使 localstore 里还留着,也只是死键)。
|
|
287
|
+
*
|
|
288
|
+
* ⚠️ **没有简写退回**:不写本项 ⇒ **一个账户都没有 ⇒ 谁都认证不了**。
|
|
289
|
+
* 刻意如此 —— 多一条永不生效的简写分支,只会把"配置写错了"伪装成"用了简写"。
|
|
290
|
+
*/
|
|
291
|
+
accounts?: ImAcpAccount[];
|
|
292
|
+
}
|
|
293
|
+
/** ACP 账户声明(一台客户端一条;形状对齐 `weixin.accounts[]`) */
|
|
294
|
+
export interface ImAcpAccount {
|
|
295
|
+
/**
|
|
296
|
+
* 本地唯一键(如 `agmente-phone`;凭据键 `IM_ACP_TOKEN_<ACCOUNT_ID>`)。
|
|
297
|
+
* ⚠️ 归一为 **UPPER_SNAKE**(`agmente-phone` → `AGMENTE_PHONE`),与微信桥同规则。
|
|
298
|
+
*/
|
|
299
|
+
accountId: string;
|
|
300
|
+
/** 该账户绑定的 skiff 角色名(**必须 ∈ `skiff.roles`**;缺省 = 拒绝该账户) */
|
|
301
|
+
role?: string;
|
|
302
|
+
/** 该账户开关(缺省 true)——撤一台设备用它,不必删条目 */
|
|
303
|
+
enabled?: boolean;
|
|
304
|
+
/** 展示名(可选,仅用于诊断面) */
|
|
305
|
+
name?: string;
|
|
306
|
+
}
|
|
244
307
|
/** 微信桥账号配置(serenity.json 内;凭据部分在 localstore) */
|
|
245
308
|
export interface WeixinAccountConfig {
|
|
246
309
|
/** 本地唯一键(如 wechat-1;localstore 凭据键 weixin.accounts.<accountId>.*) */
|