@shgroup/dsh-serenity-hooks 1.72.0 → 1.74.0
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 +12 -11
- package/dsh.plugin.json +2 -2
- package/lib/accounts-BHQbakUh.js +33 -0
- package/lib/{ccc-Dkoa2oW3.js → ccc-CyYJL4F2.js} +9 -9
- package/lib/{ccc-roots-jZYR2GVE.js → ccc-roots-B2t_aM8L.js} +5 -5
- package/lib/client.js +165 -58
- package/lib/{wake/cro.d.ts → cro/core.d.ts} +3 -3
- package/lib/{wake/cro-guide.d.ts → cro/guide.d.ts} +3 -3
- package/lib/{wake/cro-log.d.ts → cro/log.d.ts} +2 -2
- package/lib/{wake/cro-turns.d.ts → cro/turns.d.ts} +2 -2
- package/lib/guard/seam.d.ts +4 -4
- package/lib/host/effect.d.ts +1 -1
- package/lib/{ilink-DEpmE3yi.js → ilink-BMk2w2KY.js} +1 -1
- package/lib/im/bridge.d.ts +36 -65
- package/lib/im/endpoint.d.ts +3 -3
- package/lib/im/feishu-account.d.ts +92 -0
- package/lib/im/guard.d.ts +1 -1
- package/lib/im/hook.d.ts +1 -1
- package/lib/im/inbound-event.d.ts +162 -0
- package/lib/im/route.d.ts +38 -3
- package/lib/index.d.ts +5 -27
- package/lib/index.js +1089 -1208
- package/lib/kernel/ccc-roots.d.ts +4 -4
- package/lib/kernel/ccc.d.ts +19 -19
- package/lib/kernel/ports.d.ts +3 -5
- package/lib/lib-TxscFKeo.js +119355 -0
- package/lib/{message-source-kVoNK0C5.js → message-source-DCpoRHP3.js} +2 -2
- package/lib/patches/opencode-provider.d.ts +1 -1
- package/lib/{profile-DyMJ_bfr.js → profile-BVt84mbi.js} +68 -65
- package/lib/rolldown-runtime-B4iAMlE-.js +35 -0
- package/lib/{route-f7JlinIt.js → route-COOFLTpU.js} +61 -8
- package/lib/seams/guards.d.ts +14 -14
- package/lib/seams/keeper.d.ts +1 -1
- package/lib/seams/lifecycle.d.ts +2 -2
- package/lib/{settings-lt1i3OZv.js → settings-D8hlqxua.js} +49 -84
- package/lib/skills/resource-index.d.ts +1 -1
- package/lib/surface/faces.d.ts +1 -1
- package/lib/surface/http.d.ts +1 -1
- package/lib/surface/settings.d.ts +7 -13
- package/lib/surface/text.d.ts +72 -0
- package/lib/surface/usage.d.ts +2 -2
- package/lib/tools/config-docs.d.ts +53 -0
- package/lib/tools/container-admin.d.ts +19 -12
- package/lib/tools/msm/service.d.ts +7 -0
- package/lib/tools/trajectory-constraint-admin.d.ts +30 -0
- package/lib/{role/worker/idle.d.ts → trajectory/carrier.d.ts} +12 -2
- package/lib/trajectory/core.d.ts +1 -1
- package/lib/{role/worker → trajectory/delegate}/core.d.ts +7 -1
- package/lib/{role/worker → trajectory/delegate}/preset.d.ts +9 -2
- package/lib/trajectory/policy/profile.d.ts +122 -0
- package/lib/trajectory/policy/rules.d.ts +98 -0
- package/lib/{role/skiff/registry.d.ts → trajectory/policy/window.d.ts} +19 -13
- package/lib/trajectory/prompt.d.ts +12 -12
- package/lib/trajectory/rebuild.d.ts +2 -2
- package/lib/{role/skiff/core.d.ts → trajectory/session.d.ts} +58 -49
- package/package.json +3 -2
- package/lib/role/skiff/config.d.ts +0 -79
- package/lib/role/skiff/debug.d.ts +0 -88
- package/lib/role/skiff/profile.d.ts +0 -115
- package/lib/rolldown-runtime-8H4AJuhK.js +0 -14
- package/lib/tools/skiff-admin.d.ts +0 -30
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
2
|
+
* cro/core.ts — CRO(Continuous Re-Occurrence · 持续再发生)机制层(S142 §7.8,2026-09-19)
|
|
3
3
|
*
|
|
4
4
|
* ## 这东西用来干什么(白话)
|
|
5
5
|
*
|
|
@@ -104,7 +104,7 @@ export interface CroSnapshot {
|
|
|
104
104
|
/**
|
|
105
105
|
* 🔴 其中**正在跑轮次**的(设计 §3.2 —— 本机制**唯一需要 ACC 新增的状态**)。
|
|
106
106
|
* ⚠️ `live ≠ 在跑`:一条会话可以 live 而空闲。
|
|
107
|
-
* 宿主没有现成的 turn 运行标志 ⇒ 由 `
|
|
107
|
+
* 宿主没有现成的 turn 运行标志 ⇒ 由 `cro/turns.ts` 用 `agent/session-start` +
|
|
108
108
|
* `agent/turn-stopping` 两个**既有契约事件**夹出来。
|
|
109
109
|
*/
|
|
110
110
|
runningSessionIds: string[];
|
|
@@ -321,7 +321,7 @@ export declare function parseCroHostBinding(raw: string): string | null;
|
|
|
321
321
|
* `AGENT_SESSIONS/` 下每个目录查一次 `<目录>/continuous-re-occurrence.ts`。
|
|
322
322
|
* · **一次 readdir + 每个目录一次 existsSync**:N 条轨迹的代价是 O(N) 次 `stat`,
|
|
323
323
|
* 每 5min 一次 —— 与既有 `listSessions` 同量级,可接受。
|
|
324
|
-
* · 跳过 **`_` 前缀**(`_archived` / `
|
|
324
|
+
* · 跳过 **`_` 前缀**(`_archived` / `_轨迹约束-logs` / `_weixin-logs` = 系统与日志目录)
|
|
325
325
|
* 与 **`.` 前缀**(隐藏)——它们**不是轨迹**,且扫它们纯属浪费。
|
|
326
326
|
* · 🔴 **本函数绝不抛错**(返回 `[]`)——它跑在调度器 tick 内,任何异常都可能影响既有链路
|
|
327
327
|
* (设计 §5 铁律)。目录读不到(不存在 / 权限)⇒ `[]` = "没有轨迹启用 CRO",语义正确。
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
2
|
+
* cro/guide.ts — CRO 编写指南(**ACC 内置**,S142 §7.8,2026-09-19)
|
|
3
3
|
*
|
|
4
4
|
* ## 为什么指南在 ACC 而不在 CCC(所有者令)
|
|
5
5
|
*
|
|
@@ -19,7 +19,7 @@
|
|
|
19
19
|
* 1. 🔴 **必须备注:CRO 程序是可自测的;测试通过再把文件名改成正式名**;
|
|
20
20
|
* 2. 🔴 **要一并给出测试用的参数**。
|
|
21
21
|
*/
|
|
22
|
-
import { type CroSnapshotInput } from './
|
|
22
|
+
import { type CroSnapshotInput } from './core.js';
|
|
23
23
|
/**
|
|
24
24
|
* 样例快照输入 —— **代表性数据**(不是真实某条轨迹的现状)。
|
|
25
25
|
*
|
|
@@ -36,7 +36,7 @@ export declare const CRO_SAMPLE_SNAPSHOT_INPUT: CroSnapshotInput;
|
|
|
36
36
|
*/
|
|
37
37
|
export declare function renderCroSampleSnapshot(): string;
|
|
38
38
|
/** 正式入口文件名(写死于此,供指南与实现共用同一常量来源) */
|
|
39
|
-
export { CRO_FILENAME, CRO_TIMEOUT_MS } from './
|
|
39
|
+
export { CRO_FILENAME, CRO_TIMEOUT_MS } from './core.js';
|
|
40
40
|
/**
|
|
41
41
|
* CRO 编写指南正文(ACC 内置手册;由工具的 guide 动作输出)。
|
|
42
42
|
*
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
2
|
+
* cro/log.ts — CRO 唤起日志(**ACC 侧**,每轨迹一份固定名文件,2026-09-20 S142 §7.16)
|
|
3
3
|
*
|
|
4
4
|
* ## 所有者令(本模块的存在理由)
|
|
5
5
|
*
|
|
@@ -35,7 +35,7 @@
|
|
|
35
35
|
* ⇒ 清理与写入**是同一笔操作** ⇒ **结构上不存在"忘了清"的可能**(比"另设一个每 tick 的清理步"更硬:
|
|
36
36
|
* 那多一个"可能忘了跑"的面)。窗宽是**常量**,**无配置键、无人工清理动作**。
|
|
37
37
|
*
|
|
38
|
-
* ## 🔴 铁律(与 `
|
|
38
|
+
* ## 🔴 铁律(与 `cro/core.ts` 同族)
|
|
39
39
|
*
|
|
40
40
|
* **本模块的每个导出函数都不抛错**:写盘 / 裁剪 / 解析失败一律返回结构化失败,
|
|
41
41
|
* 由调用方只记一行 tick 日志 ⇒ **CRO 流水的问题绝不影响投递与既有链路**(设计 §5)。
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
2
|
+
* cro/turns.ts — 「这条轨迹是否正在跑轮次」的追踪(设计 §3.2,2026-09-19 S142)
|
|
3
3
|
*
|
|
4
4
|
* ## 为什么需要这个模块(CRO 唯一需要 ACC 新增的状态)
|
|
5
5
|
*
|
|
@@ -94,7 +94,7 @@ export declare function __resetCroTurnsForTest(): void;
|
|
|
94
94
|
* 🔴 **本处顺带更正 §2.7 的一处分档**:本文件原被列在"剩余 3 个属**真依赖**档"里,
|
|
95
95
|
* 但**逐符号查证后与那个判断不符** ——
|
|
96
96
|
* · `Context`:`ctx` 只被**结构化访问**(原码本就写着 `ctx as unknown as { on: … }`)
|
|
97
|
-
* ⇒ 该注解未提供任何类型保证(与 `
|
|
97
|
+
* ⇒ 该注解未提供任何类型保证(与 `trajectory/carrier.ts` 同档)。
|
|
98
98
|
* · `Agent`:**导入后从未使用** —— 事件 payload 一律按 `{ agent?: unknown }` 解构,
|
|
99
99
|
* 再交给 `sessionIdOf(...)`(它收 `unknown`)。⇒ **纯粹是死 import**。
|
|
100
100
|
* ⇒ **判据(本件再证一次,且比上一件更硬)**:分档**必须逐符号看"它有没有参与类型检查"**,
|
package/lib/guard/seam.d.ts
CHANGED
|
@@ -5,21 +5,21 @@
|
|
|
5
5
|
* 命中 → `agent.steer(buildRebuke(hits))` 打回重生成(turn 不关闭,模型同轮重答)。
|
|
6
6
|
* 连续命中达 REBUKE_MAX_ROUNDS → 放弃打回(防死循环),输出保留 + 审计日志。
|
|
7
7
|
*
|
|
8
|
-
* 作用范围(v1.26.3
|
|
9
|
-
* (session id
|
|
8
|
+
* 作用范围(v1.26.3 用户拍板):**仅外部面**——轨迹约束 等对外输出会话
|
|
9
|
+
* (session id `轨迹约束-`/`rebuild-` 前缀;v1.22.4 rebuild 新建会话同面)。
|
|
10
10
|
* **本地维护会话(普通 dsh 会话)不检测**——维护会话必然提及机制词
|
|
11
11
|
* (dsh-serenity-hooks/cognitive_trajectory rebuild/mech-registry),打回会瘫痪自身工作。
|
|
12
12
|
*
|
|
13
13
|
* 🔴 **2026-10-15 补注(面 E 退役)**:`acp-` 前缀分支**已成孤儿,但本批不动**。
|
|
14
14
|
* 判据(两条,实测):① **该前缀从来没有生产者** —— 历史 `acp-core.ts` 建会话走的是
|
|
15
|
-
* `
|
|
15
|
+
* `createTrajectoryAgent`,其 id = `` `轨迹约束-${role}-${uuid}` ``(`trajectory/session.ts`)⇒ `acp-` **从不出现**;
|
|
16
16
|
* ② 面 E 退役后连"本可能产生它的模块"也不在了。⇒ 该分支是**恒 false 的死判据**。
|
|
17
17
|
* ⚠️ **仍不擅自删**:这是**运行时字符串比较**,删它属**行为变更**(不是纯清理),
|
|
18
18
|
* 依本轨既有先例(`cheap-worker-` 四处置孤儿,同族、同判据)⇒ **登记待裁**(见 S142 §当前状态「观察」)。
|
|
19
19
|
*/
|
|
20
20
|
import type { Context } from 'cordis';
|
|
21
21
|
import type { Agent } from '@deepseek-ai/dsh-agent';
|
|
22
|
-
/** 读会话最后一个 assistant/message 文本(
|
|
22
|
+
/** 读会话最后一个 assistant/message 文本(trajectory-session 同款)
|
|
23
23
|
* **v1.63.3 起唯一的导出理由消失**:原导出是给 `unattended-seam.ts` 复用的(D77),
|
|
24
24
|
* 该文件已随无人值守功能整条退役 ⇒ 现只有本模块自己用(`isLeak` 的调用点)。
|
|
25
25
|
* ⚠️ 保持 `export` 无害(它有测试覆盖,且将来"同一读法只留一份"的纪律仍成立),
|
package/lib/host/effect.d.ts
CHANGED
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
* - `ctx.effect` 是宿主成员,但**不可假定存在**(测试替身、极旧宿主、HMR 早期阶段)。
|
|
11
11
|
* 直接裸调会让 `apply` 抛错 = 整个 dsh 启动失败(app-boot "plugin(s) failed to load"),
|
|
12
12
|
* 这比"资源没拆卸"严重得多——所以缺失时**响亮降级**(记录 + 返回 false)。
|
|
13
|
-
* - 零依赖叶模块:gateway 等模块只引它,不会因此被拖进
|
|
13
|
+
* - 零依赖叶模块:gateway 等模块只引它,不会因此被拖进 轨迹约束/rebuild 的依赖链。
|
|
14
14
|
*/
|
|
15
15
|
/** 测试辅助:重置"未装配"告警去重(生产零调用) */
|
|
16
16
|
export declare function __resetDisposerWarningsForTest(): void;
|
package/lib/im/bridge.d.ts
CHANGED
|
@@ -3,12 +3,12 @@
|
|
|
3
3
|
*
|
|
4
4
|
* **CCC 级**:dsh 一个进程含多个 CCC,每个 CCC 独立对接微信桥(S142 用户拍板)——
|
|
5
5
|
* 扫描 live CCC → 每 CCC 读 weixin 配置(enabled + accounts + routes)→
|
|
6
|
-
* 启动该 CCC 该账号的 iLink 轮询循环。消息 → weixin-route 路由 →
|
|
6
|
+
* 启动该 CCC 该账号的 iLink 轮询循环。消息 → weixin-route 路由 → 轨迹约束核心直调
|
|
7
7
|
* (session/new + session/prompt,同进程不经网络)。
|
|
8
8
|
*
|
|
9
9
|
* 🔴 **一处陈旧注释的更正(2026-10-15,随面 E 退役一并核实)**:本行原写「→ **AcpServer 直调**
|
|
10
10
|
* (…——acp-core 传输无关设计)」。**该描述从来不是事实** —— 实测 grep 全仓:本文件**从不**
|
|
11
|
-
* `import acp-core`(它 import 的是 `
|
|
11
|
+
* `import acp-core`(它 import 的是 `trajectory-session` / `trajectory-constraint` / `weixin-*`),
|
|
12
12
|
* 而 `acp-core` 的消费者**自始至终只有两个**(`acp-http.ts` + 其测试)。
|
|
13
13
|
* ⇒ 当年那句是**设计意图的误记**,不是依赖关系;面 E 退役后它更是彻底失效。
|
|
14
14
|
* 🔵 判据复述:**注释里的"设计意图"不能当依赖判据**,必须 grep import。
|
|
@@ -21,75 +21,51 @@
|
|
|
21
21
|
* `im/inbound.ts`(机器级机制;本文件只接线)。这是"微信桥上移 ACC 层"的第一步。
|
|
22
22
|
*
|
|
23
23
|
* 外部面纯净(D9/D11 延续):微信桥会话 = 外部面——session/prompt 走
|
|
24
|
-
* includeCognitiveTrajectory:false
|
|
24
|
+
* includeCognitiveTrajectory:false(对外不返回轨迹);轨迹约束角色白名单即授权(G9)。
|
|
25
25
|
*/
|
|
26
26
|
import type { Context } from 'cordis';
|
|
27
27
|
import { type WeixinAccountCredential } from './route.js';
|
|
28
28
|
import { type WeixinMessage } from './ilink.js';
|
|
29
|
-
import { type
|
|
29
|
+
import { resolveInboundTarget, manualOutputFallbackNeeded, type ResolvedInboundTarget } from './inbound-event.js';
|
|
30
|
+
export { manualOutputFallbackNeeded, resolveInboundTarget };
|
|
31
|
+
export type { ResolvedInboundTarget };
|
|
30
32
|
/** 测试辅助:清空 typing_ticket 缓存(生产零调用) */
|
|
31
33
|
export declare function resetWeixinTypingCache(): void;
|
|
32
34
|
/**
|
|
33
|
-
*
|
|
34
|
-
*
|
|
35
|
-
*
|
|
36
|
-
*
|
|
37
|
-
*
|
|
38
|
-
*
|
|
39
|
-
*
|
|
40
|
-
|
|
41
|
-
export declare function manualOutputFallbackNeeded(input: {
|
|
42
|
-
fallbackOnNoSend?: boolean;
|
|
43
|
-
guardActive: boolean;
|
|
44
|
-
sentThisTurn: boolean;
|
|
45
|
-
answer: string;
|
|
46
|
-
}): boolean;
|
|
47
|
-
/**
|
|
48
|
-
* 路由解析结果:目标根 + **轨迹编码** + **该轨迹的 skiff 约束面**。
|
|
49
|
-
* `null` 从本函数出去 ⇒ **不投递**(未命中已留痕 / 目标不存在已响亮出声)。
|
|
35
|
+
* 🔴 **2026-10-19(P2)`runAccountLoop` 已迁出本文件** —— 逐字搬到
|
|
36
|
+
* `im/weixin-account.ts`,成为 `ImAccountImpl` 的一个实现(微信)。
|
|
37
|
+
*
|
|
38
|
+
* 为什么搬:装配处原先把渠道写死在这里(`channel !== 'weixin'` ⇒ 跳过)。
|
|
39
|
+
* owner 定向「飞书和微信一样,只不过是 account 方实现不同」⇒ 起循环这个动作
|
|
40
|
+
* 归**账号自己的实现**,装配处只按 `channel` 查派发表。
|
|
41
|
+
* ⚠️ 依赖(`getUpdates` / `handleIncoming` / `handleMachineIncoming`)通过
|
|
42
|
+
* `installWeixinAccountImpl` **注入**,避免 `bridge ⇄ weixin-account` 互相 import 成环。
|
|
50
43
|
*/
|
|
51
|
-
interface ResolvedInboundTarget {
|
|
52
|
-
root: string;
|
|
53
|
-
/** 目标认知轨迹编码(= 新模型里的"身份";旧模型的角色名已退役) */
|
|
54
|
-
trajectory: string;
|
|
55
|
-
/**
|
|
56
|
-
* 该轨迹的 skiff 约束面。
|
|
57
|
-
* ⚠️ **可能为 `null`**:轨迹存在但没有 `skiff.json` ⇒ 它是一条**普通轨迹**,
|
|
58
|
-
* 微信桥照样可以往里投(投进一条普通认知轨迹是合法的),只是不施加 skiff 约束。
|
|
59
|
-
* ⇒ 这与旧模型的关键差别:旧模型里"角色不存在"是硬错,新模型里"没有 skiff.json"不是错。
|
|
60
|
-
*/
|
|
61
|
-
profile: SkiffProfile | null;
|
|
62
|
-
}
|
|
63
44
|
/**
|
|
64
|
-
*
|
|
65
|
-
*
|
|
66
|
-
*
|
|
67
|
-
*
|
|
68
|
-
*
|
|
69
|
-
*
|
|
70
|
-
* "
|
|
71
|
-
*
|
|
72
|
-
*
|
|
73
|
-
* -
|
|
74
|
-
*
|
|
75
|
-
*
|
|
76
|
-
*
|
|
77
|
-
*
|
|
78
|
-
*
|
|
79
|
-
*
|
|
80
|
-
*
|
|
81
|
-
*
|
|
82
|
-
*
|
|
83
|
-
* **目标不存在(CCC / 角色)⇒ 响亮报错**(`console.error` + 机器级留痕)。两者都
|
|
84
|
-
* **不是**"投给默认角色"。
|
|
45
|
+
* 处理单条**微信**消息:把 iLink 形状的 `msg` 归一成**中立入站事件**,
|
|
46
|
+
* 再交给通道中立的 `handleInboundEvent`(`im/inbound-event.ts`)走主流程。
|
|
47
|
+
*
|
|
48
|
+
* ## 🔴 2026-10-19(B1):本函数已退化为**薄适配器**,主流程不在本文件
|
|
49
|
+
*
|
|
50
|
+
* 原因:`handleIncoming` 原先同时承担两件不同的事 —— ① "iLink 消息长什么样"
|
|
51
|
+
* ② "一条已到达的消息该投到哪、怎么答、怎么记账"。接第二家 IM 时只有 ① 需要新写,
|
|
52
|
+
* 而 ② 应该**被两家共用**。两者混在一个函数里 ⇒ 飞书接进来只能复制一整份主流程。
|
|
53
|
+
*
|
|
54
|
+
* ⇒ 主流程搬到 `im/inbound-event.ts`,本函数只做**形状翻译**:
|
|
55
|
+
* `msg`(iLink)+ `cred`(iLink 凭据)→ `ImInboundEvent` + `ImAccountIo`。
|
|
56
|
+
*
|
|
57
|
+
* ## 为什么保留这个签名(而不是让调用方直接调新的主流程)
|
|
58
|
+
*
|
|
59
|
+
* 全仓约 50 处测试按 iLink 形状驱动本函数,而 B1 的验收判据是「**微信行为逐字不变**」
|
|
60
|
+
* (用例数守恒 + 真机一条微信)。保留签名 ⇒ 那 50 处**逐字不动**,判据可复算。
|
|
61
|
+
*
|
|
62
|
+
* 会话语义(不变):会话 id 由**轨迹编码**派生 ⇒ 同一条轨迹恒同一条 dsh 会话;
|
|
63
|
+
* 进程内已有 → 延续;无 → 固定 id resume-or-create。
|
|
85
64
|
*/
|
|
86
65
|
export declare function handleIncoming(ctx: Context, root: string, accountId: string, cred: WeixinAccountCredential, msg: Pick<WeixinMessage, 'from_user_id' | 'context_token' | 'item_list'>,
|
|
87
66
|
/**
|
|
88
|
-
* **机器级模式**(P0-3 A
|
|
89
|
-
*
|
|
90
|
-
* 起 poller 与路由都已上移机器级(目标 CCC 没配 weixin 段也照样收得到)。
|
|
91
|
-
* ⚠️ **行为开关仍归目标 CCC**(`hook` / `autoReplyWithLastMessage` / `fallbackOnNoSend`)
|
|
92
|
-
* —— 机器级只管"**谁收**"与"**投到哪**",不管"**怎么答**"。
|
|
67
|
+
* **机器级模式**(P0-3 A):目标已解析好(`root` 入参即该目标根)⇒ 不再自行路由,
|
|
68
|
+
* 且**不看本 CCC 的 `weixin.enabled`**。⚠️ 行为开关仍归目标 CCC。
|
|
93
69
|
*/
|
|
94
70
|
preset?: {
|
|
95
71
|
target: ResolvedInboundTarget;
|
|
@@ -99,11 +75,7 @@ preset?: {
|
|
|
99
75
|
*
|
|
100
76
|
* 与 `handleIncoming` 的唯一差别:**目标由机器级订阅表逐条解析,且没有 CCC 级回退**
|
|
101
77
|
* (方案 §3.1:机器级模式下**未命中 = 不投递**,不是"投给默认角色")。
|
|
102
|
-
* 解析出来后把**目标根**当 `root` 交给 `handleIncoming`(并带上 `preset
|
|
103
|
-
* 也不看目标 CCC 的 `weixin.enabled`)。
|
|
104
|
-
*
|
|
105
|
-
* 🔴 为什么"起 poller"与"路由"必须同批(本件即 P0-2 的后半):机器级 poller 收到消息时
|
|
106
|
-
* **不知道它属于哪个 CCC** —— 那个答案是订阅表给的。没有订阅表 ⇒ **能收不能投**。
|
|
78
|
+
* 解析出来后把**目标根**当 `root` 交给 `handleIncoming`(并带上 `preset`)。
|
|
107
79
|
*
|
|
108
80
|
* 🔵 **导出供测试直接驱动**(与 `handleIncoming` 同款:它俩都是"单条消息的入口",
|
|
109
81
|
* 生产路径 `runAccountLoop` 只是循环壳)。
|
|
@@ -212,4 +184,3 @@ export declare function weixinBridgeStatus(): Array<{
|
|
|
212
184
|
* 事件驱动(对齐 autotrajectory v1.26.15)。
|
|
213
185
|
*/
|
|
214
186
|
export declare function registerWeixinBridge(ctx: Context): void;
|
|
215
|
-
export {};
|
package/lib/im/endpoint.d.ts
CHANGED
|
@@ -3,11 +3,11 @@
|
|
|
3
3
|
*
|
|
4
4
|
* 为什么单独一个模块(R↓):地址的读者是 `msm-ops.buildMsmEnv`(把地址注入 MSM 子进程 env),
|
|
5
5
|
* 写者才是 `weixin-send-api`(监听器启停)。若 msm-ops 直接 import weixin-send-api,
|
|
6
|
-
* 静态依赖链会拖进整条微信桥栈(weixin-bridge →
|
|
7
|
-
* 后果实测:`acc-extras` / `ops` / `
|
|
6
|
+
* 静态依赖链会拖进整条微信桥栈(weixin-bridge → trajectory-session → `@deepseek-ai/dsh-llm`)——
|
|
7
|
+
* 后果实测:`acc-extras` / `ops` / `trajectory-constraint-admin` 三个测试套件因解析不到宿主包而**整文件加载失败**
|
|
8
8
|
* (它们只用到 MSM 注册表逻辑)。叶模块把「读」与「写」解耦:读者零依赖,写者自己承担重量。
|
|
9
9
|
*
|
|
10
|
-
* 同款先例:`host/effect.ts`(拆卸登记)、`
|
|
10
|
+
* 同款先例:`host/effect.ts`(拆卸登记)、`trajectory/carrier.ts`(等待结算)。
|
|
11
11
|
*/
|
|
12
12
|
/** 设置当前入口地址(weixin-send-api 启停时调用;null = 未启动) */
|
|
13
13
|
export declare function setWeixinSendEndpoint(value: string | null): void;
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* im/feishu-account.ts — 飞书账号实现(P3,2026-10-19)
|
|
3
|
+
*
|
|
4
|
+
* ## 它凭什么这么短
|
|
5
|
+
*
|
|
6
|
+
* 这是本件最有价值的一点:**P3 只写了这一个文件**。路由 / 会话 / 提问 / 回复 / 记账 /
|
|
7
|
+
* 媒体落盘全都在中立层(`im/inbound-event.ts`,B1)—— 飞书只提供三样:
|
|
8
|
+
* ① 怎么建连接并收消息 ② 一条飞书消息长什么样 ③ 怎么把文本发回去。
|
|
9
|
+
*
|
|
10
|
+
* owner 那句「和微信一样,只不过是 account 方实现不同」在这里是**代码事实**。
|
|
11
|
+
*
|
|
12
|
+
* ## 三条官方约束(照抄会坏,必须各自处理)
|
|
13
|
+
*
|
|
14
|
+
* 1. 🔴 **回调须 3 秒内返回**(官方:超时触发重推)⇒ 回调里**只入队**,处理全异步。
|
|
15
|
+
* 2. 🔴 **超时会重推** ⇒ 必须按 `message_id` **去重**,否则同一句话答两遍。
|
|
16
|
+
* 3. 🔴 **建连成功 ≠ 能收消息** ⇒ 这两件事分开取证(`getConnectionStatus` 只证明前者)。
|
|
17
|
+
*
|
|
18
|
+
* ## 边界(R↓)
|
|
19
|
+
*
|
|
20
|
+
* - ⛔ 不做路由判断、不做会话管理 —— 那是中立层的。
|
|
21
|
+
* - ⛔ 不解析富文本/图片/文件(本期):只取 `message_type === 'text'` 的正文;
|
|
22
|
+
* 其它类型当作"有消息但没文本"交给中立层(它会静默或降级,**不会静默丢连接**)。
|
|
23
|
+
*/
|
|
24
|
+
import type { ImAccountImpl } from './accounts.js';
|
|
25
|
+
import type { ImInboundEvent } from './inbound-event.js';
|
|
26
|
+
/**
|
|
27
|
+
* 飞书 SDK 的最小面(**注入**,不是硬 import)。
|
|
28
|
+
*
|
|
29
|
+
* 🔴 为什么注入(两条理由,一条设计、一条实测):
|
|
30
|
+
* ① **设计**:与微信同款(`installWeixinAccountImpl` 注入 `getUpdates` 等)。
|
|
31
|
+
* 飞书 SDK 是重依赖(protobufjs 等)⇒ 不该成为模块求值时的硬依赖,只在真要起账号时加载。
|
|
32
|
+
* ② **实测**:`vi.mock` 拦不住它 —— 本模块用 `await import('@larksuiteoapi/node-sdk')`,
|
|
33
|
+
* 而该包在 pnpm 下是**外置依赖**,vitest 的动态 import 不被 mock 拦截。实测证据:
|
|
34
|
+
* 测试里跑出了真 SDK 自己的日志 `[error]: ['[ws]', 'invalid appId: cli_x']`
|
|
35
|
+
* ⇒ 若坚持 mock,测的就是"能不能骗过测试装置",不是被测对象。
|
|
36
|
+
* ⇒ 注入后,测试直接给替身,**没有魔法**。
|
|
37
|
+
*/
|
|
38
|
+
export interface FeishuSdk {
|
|
39
|
+
Client: new (p: {
|
|
40
|
+
appId: string;
|
|
41
|
+
appSecret: string;
|
|
42
|
+
}) => {
|
|
43
|
+
im?: {
|
|
44
|
+
v1?: {
|
|
45
|
+
message?: {
|
|
46
|
+
create?: (p: unknown) => Promise<unknown>;
|
|
47
|
+
};
|
|
48
|
+
};
|
|
49
|
+
};
|
|
50
|
+
};
|
|
51
|
+
EventDispatcher: new (p?: unknown) => {
|
|
52
|
+
register: (h: Record<string, (d: never) => unknown>) => unknown;
|
|
53
|
+
};
|
|
54
|
+
WSClient: new (p: {
|
|
55
|
+
appId: string;
|
|
56
|
+
appSecret: string;
|
|
57
|
+
}) => {
|
|
58
|
+
start: (p: {
|
|
59
|
+
eventDispatcher: unknown;
|
|
60
|
+
}) => Promise<void>;
|
|
61
|
+
close: (p?: {
|
|
62
|
+
force?: boolean;
|
|
63
|
+
}) => void;
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* 接入 SDK 加载器(`bridge.ts` 在模块求值时注入真加载器;测试注入替身)。
|
|
68
|
+
* 缺省 = 动态 import 真包 —— 这样生产路径不需要 `bridge.ts` 也能独立跑。
|
|
69
|
+
*/
|
|
70
|
+
export declare function installFeishuSdk(loader: () => Promise<FeishuSdk>): void;
|
|
71
|
+
/** 测试辅助:关掉并清空全部长连接(生产零调用) */
|
|
72
|
+
export declare function __closeAllFeishuConnectionsForTest(): void;
|
|
73
|
+
/** 测试辅助:清空去重表(生产零调用) */
|
|
74
|
+
export declare function __resetFeishuSeenForTest(): void;
|
|
75
|
+
/**
|
|
76
|
+
* 从飞书 `im.message.receive_v1` 事件里取出**中立入站事件**。
|
|
77
|
+
*
|
|
78
|
+
* 🔴 这是 P3 唯一的"形状翻译"处,与 `bridge.ts` 里 iLink 的那一处对称。
|
|
79
|
+
* ⚠️ 用户标识用 **open_id**(不是 chat_id):路由表是"人 → 轨迹",
|
|
80
|
+
* 而 chat_id 在群里会变、在单聊里也与人不是一一对应。
|
|
81
|
+
* @returns 无可用发送者/消息体 ⇒ `null`(调用方丢弃并**出声**,不静默)
|
|
82
|
+
*/
|
|
83
|
+
export declare function feishuEventToInbound(data: Record<string, unknown>): ImInboundEvent | null;
|
|
84
|
+
/** 飞书账号实现(登记名 = `feishu`;与配置里的 `channel` 取值一致) */
|
|
85
|
+
export declare const feishuAccountImpl: ImAccountImpl;
|
|
86
|
+
/**
|
|
87
|
+
* 注册飞书实现(`bridge.ts` 在**模块求值时**调用)。
|
|
88
|
+
*
|
|
89
|
+
* ⚠️ 与微信同款教训(续 220 实测 12 例红):注册必须在**模块求值**时发生,
|
|
90
|
+
* 不能等 `apply` —— 测试与面板会**直接**调 `syncCccBridge` / `syncHumanChannel`。
|
|
91
|
+
*/
|
|
92
|
+
export declare function installFeishuAccountImpl(): void;
|
package/lib/im/guard.d.ts
CHANGED
|
@@ -58,7 +58,7 @@ export declare function buildManualOutputRebuke(session: ManualOutputSession): s
|
|
|
58
58
|
export declare function __resetWeixinOutputGuardForTest(): void;
|
|
59
59
|
/**
|
|
60
60
|
* 装配闸门(index.ts apply 调用)。
|
|
61
|
-
* 只对**已登记的手动模式会话**生效——其他会话(主舱/其他
|
|
61
|
+
* 只对**已登记的手动模式会话**生效——其他会话(主舱/其他 轨迹约束会话)零干预。
|
|
62
62
|
*/
|
|
63
63
|
export declare function registerWeixinOutputGuard(ctx: Context): void;
|
|
64
64
|
export {};
|
package/lib/im/hook.d.ts
CHANGED
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* im/inbound-event.ts — **中立入站事件层**(B1,2026-10-19)
|
|
3
|
+
*
|
|
4
|
+
* ## 这一层解决的是什么
|
|
5
|
+
*
|
|
6
|
+
* `handleIncoming` 原先吃的 `msg` 是 **iLink 形状**(`from_user_id` / `context_token` /
|
|
7
|
+
* `item_list` / `message_type`)。飞书的消息形状与它**没有一处相同**,所以接第二家 IM
|
|
8
|
+
* 的真正工作量不在"建连接",而在**把各家消息归一成同一个东西**。
|
|
9
|
+
*
|
|
10
|
+
* 本文件就是那个"同一个东西",以及消费它的**通道中立主流程**。
|
|
11
|
+
*
|
|
12
|
+
* ## 切法(为什么这样切,而不是改 `handleIncoming` 的签名)
|
|
13
|
+
*
|
|
14
|
+
* 🔴 实测爆炸半径:全仓 **约 50 处**测试直接驱动 `handleIncoming` 的 iLink 形状。
|
|
15
|
+
* 直接改签名 ⇒ 那 50 处同时要改 ⇒ 而 B1 的验收判据恰恰是「**微信行为逐字不变**」
|
|
16
|
+
* (用例数守恒 + 真机一条微信)。判据自己会失去可复算性。
|
|
17
|
+
*
|
|
18
|
+
* ⇒ 切法是**适配器**:
|
|
19
|
+
* - 本文件 `handleInboundEvent()` = 真正的主流程,**零 iLink 概念**;
|
|
20
|
+
* - `bridge.ts` 的 `handleIncoming()` 退化成**薄适配器**:iLink 消息 → 中立事件 + iLink 的 io;
|
|
21
|
+
* - 飞书实现自己造中立事件 + 飞书的 io,调到**同一个** `handleInboundEvent`。
|
|
22
|
+
*
|
|
23
|
+
* 收益:微信侧调用点与测试**逐字不动**;第二条、第三条 IM 只是**新增一个适配器**。
|
|
24
|
+
*
|
|
25
|
+
* ## 边界(R↓:这层刻意**不**管什么)
|
|
26
|
+
*
|
|
27
|
+
* - ⛔ **不管连接怎么建**(长轮询 vs WebSocket 回调)—— 那是各账号实现自己的事。
|
|
28
|
+
* - ⛔ **不管凭据怎么取** —— `io` 与事件都由实现处构造好再交进来。
|
|
29
|
+
* - ✅ 只负责:「一条已到达的消息,该投到哪条认知轨迹、怎么答、怎么记账」。
|
|
30
|
+
*/
|
|
31
|
+
import type { Context } from 'cordis';
|
|
32
|
+
import { type TrajectoryConstraint } from '../trajectory/policy/profile.js';
|
|
33
|
+
/**
|
|
34
|
+
* 一条**已到达**的入站消息(归一后)。
|
|
35
|
+
*
|
|
36
|
+
* 刻意只留**主流程真正用到的**字段 —— 每多留一个通道特定的字段,这一层就多一处
|
|
37
|
+
* 需要为两家分别解释的东西(E↓)。
|
|
38
|
+
*/
|
|
39
|
+
export interface ImInboundEvent {
|
|
40
|
+
/** 发送者在**该通道内**的稳定标识(微信 = from_user_id;飞书 = open_id) */
|
|
41
|
+
userId: string;
|
|
42
|
+
/** 正文(含语音转写)。无文本 ⇒ `null` */
|
|
43
|
+
text: string | null;
|
|
44
|
+
/** 媒体项(图片/文件)。顺序即到达顺序 */
|
|
45
|
+
media: ImMediaItem[];
|
|
46
|
+
/** 有语音但**没有转写文本**(触发降级告知;与 `text` 同时为空时才有意义) */
|
|
47
|
+
hasVoice: boolean;
|
|
48
|
+
/**
|
|
49
|
+
* 回复锚点。微信 = `context_token`(回复必须带上它);飞书**没有这个概念**
|
|
50
|
+
* ⇒ 可选。⛔ 不要在消费侧假设它一定在。
|
|
51
|
+
*/
|
|
52
|
+
replyRef?: string;
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* 一个媒体项:**取字节的动作由通道自己提供**。
|
|
56
|
+
*
|
|
57
|
+
* 为什么把下载放进事件里,而不是让主流程拿着 `item` 去调 `downloadMedia`:
|
|
58
|
+
* 后者是 iLink 的 CDN 协议,飞书是 `im.v1.messageResource.get` —— 两者除了
|
|
59
|
+
* "返回一堆字节"之外没有任何共同点。⇒ 中立层只约定**取字节这个动作**。
|
|
60
|
+
*/
|
|
61
|
+
export interface ImMediaItem {
|
|
62
|
+
kind: 'image' | 'file';
|
|
63
|
+
/** 原始文件名(file 才有;**未净化**,净化在落盘侧做) */
|
|
64
|
+
fileName?: string;
|
|
65
|
+
/** 取字节;失败/不可得 ⇒ `null`(主流程**降级告知**,不静默) */
|
|
66
|
+
fetch(): Promise<{
|
|
67
|
+
data: Buffer;
|
|
68
|
+
fileName?: string;
|
|
69
|
+
} | null>;
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* 账号的**动作面**(发消息 / 打字态)。
|
|
73
|
+
*
|
|
74
|
+
* 与 `ImInboundEvent` 成对:进来的是事件,出去的是动作。两者都不含通道协议细节。
|
|
75
|
+
*/
|
|
76
|
+
export interface ImAccountIo {
|
|
77
|
+
/** 发纯文本(实现内部做各自的长度分片与格式转换) */
|
|
78
|
+
sendText(input: {
|
|
79
|
+
toUserId: string;
|
|
80
|
+
text: string;
|
|
81
|
+
replyRef?: string;
|
|
82
|
+
}): Promise<void>;
|
|
83
|
+
/** "正在输入"起始(可实现不了;飞书就没有这个能力) */
|
|
84
|
+
typingStart?(input: {
|
|
85
|
+
toUserId: string;
|
|
86
|
+
replyRef?: string;
|
|
87
|
+
}): Promise<void>;
|
|
88
|
+
/** "正在输入"结束(异常路径也会调 ⇒ 实现须自行容忍未开始的情形) */
|
|
89
|
+
typingStop?(input: {
|
|
90
|
+
toUserId: string;
|
|
91
|
+
}): Promise<void>;
|
|
92
|
+
}
|
|
93
|
+
/**
|
|
94
|
+
* 路由解析结果:目标根 + **轨迹编码** + **该轨迹的 轨迹约束面**。
|
|
95
|
+
* `null` 从本函数出去 ⇒ **不投递**(未命中已留痕 / 目标不存在已响亮出声)。
|
|
96
|
+
*/
|
|
97
|
+
export interface ResolvedInboundTarget {
|
|
98
|
+
root: string;
|
|
99
|
+
/** 目标认知轨迹编码(= 新模型里的"身份";旧模型的角色名已退役) */
|
|
100
|
+
trajectory: string;
|
|
101
|
+
/**
|
|
102
|
+
* 该轨迹的 轨迹约束面。
|
|
103
|
+
* ⚠️ **可能为 `null`**:轨迹存在但没有 `trajectory-constraint.json` ⇒ 它是一条**普通轨迹**,
|
|
104
|
+
* IM 桥照样可以往里投,只是不施加 轨迹约束 约束。
|
|
105
|
+
*/
|
|
106
|
+
profile: TrajectoryConstraint | null;
|
|
107
|
+
}
|
|
108
|
+
/**
|
|
109
|
+
* **入站路由的单一解析处**(P0-3):机器级订阅表优先 → CCC 级回退;三档语义都在这里落地。
|
|
110
|
+
*
|
|
111
|
+
* 🔴 为什么抽成一个函数而不是写两遍:**两种模式(CCC 起桥 / 机器级起 poller)走的是同一套
|
|
112
|
+
* 判据**,差别只有一格 —— **有没有 CCC 级回退**(`cccFallback`)。写两遍必然分叉。
|
|
113
|
+
*
|
|
114
|
+
* 三档(方案 §3.1,**刻意分档**):
|
|
115
|
+
* - **未命中** ⇒ 不投递 + **留痕**(静默档:这是"表里没有你",不是错误)
|
|
116
|
+
* - **表残缺** ⇒ **响亮出声**(且**独立于是否命中**)
|
|
117
|
+
* - **目标不存在**(CCC / **轨迹**)⇒ **响亮报错**(不静默丢弃)
|
|
118
|
+
*/
|
|
119
|
+
export declare function resolveInboundTarget(ctx: Context, input: {
|
|
120
|
+
account: string;
|
|
121
|
+
user: string;
|
|
122
|
+
cccFallback: {
|
|
123
|
+
root: string;
|
|
124
|
+
trajectory: string;
|
|
125
|
+
} | null;
|
|
126
|
+
}): Promise<ResolvedInboundTarget | null>;
|
|
127
|
+
/**
|
|
128
|
+
* **一条入站消息的主流程**(通道中立)。
|
|
129
|
+
*
|
|
130
|
+
* 职责:路由 → 固定会话 id 创建/延续 → 提问 → 回复(或被闸门拦下后兜底)。
|
|
131
|
+
* 语料(媒体)由事件自带 `fetch()`;出站走的 `io`。
|
|
132
|
+
*
|
|
133
|
+
* 🔴 **本函数没有任何 iLink 概念** —— 这是 B1 的判据。任何 `context_token` /
|
|
134
|
+
* `item_list` / `message_type` 出现在这里,就说明抽缝没抽干净。
|
|
135
|
+
*
|
|
136
|
+
* @param preset 机器级模式:目标已解析好 ⇒ **不再自行路由**,且**不看本 CCC 的 `enabled`**
|
|
137
|
+
*/
|
|
138
|
+
export declare function handleInboundEvent(ctx: Context, root: string, accountId: string, io: ImAccountIo, event: ImInboundEvent, preset?: {
|
|
139
|
+
target: ResolvedInboundTarget;
|
|
140
|
+
}): Promise<void>;
|
|
141
|
+
/**
|
|
142
|
+
* 手动输出模式的**机制标记**(v1.30.10 引入;v1.30.16 按「ACC 管机制、CCC 管措辞」重构)。
|
|
143
|
+
*
|
|
144
|
+
* 边界(R↓):ACC 只提供**机制与数据**——"桥不转发最终文本"这个事实 + 三个已填好的参数值。
|
|
145
|
+
* **纪律措辞归 CCC**:怎么写、必须怎么做、后果是什么,全部写在轨迹的 `PROMPT.md` 里。
|
|
146
|
+
*/
|
|
147
|
+
export declare function manualOutputMarker(accountId: string, userId: string): string;
|
|
148
|
+
/**
|
|
149
|
+
* 手动输出模式的**兜底判定**(纯函数,v1.30.17——把决策矩阵从装配里抽出来,可独立测试)。
|
|
150
|
+
*
|
|
151
|
+
* 四个条件同时成立才兜底:
|
|
152
|
+
* ① CCC 显式开启 `fallbackOnNoSend`(缺省 false = 严格静默)
|
|
153
|
+
* ② 机械闸门**已装配**(未装配时无法判定"是否发送过" → 宁可静默也不冒重复发送风险)
|
|
154
|
+
* ③ 本轮**没有**成功发送(agent 自己发过 → 输出权归 agent,桥不插话)
|
|
155
|
+
* ④ 有最终文本可转发(空文本无意义)
|
|
156
|
+
*/
|
|
157
|
+
export declare function manualOutputFallbackNeeded(input: {
|
|
158
|
+
fallbackOnNoSend?: boolean;
|
|
159
|
+
guardActive: boolean;
|
|
160
|
+
sentThisTurn: boolean;
|
|
161
|
+
answer: string;
|
|
162
|
+
}): boolean;
|
package/lib/im/route.d.ts
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
* 职责(零 DSH 依赖,可独立单测):
|
|
5
5
|
* - 读取 CCC 的 weixin 配置(.opencode/serenity.json weixin 段——结构/路由/开关)
|
|
6
6
|
* - 读取/写入 CCC localstore 的账号凭据(weixin.accounts.<accountId>.token/.baseUrl/.userId)
|
|
7
|
-
* - 微信用户 →
|
|
7
|
+
* - 微信用户 → 轨迹约束会话 id 映射(固定可重建:skiff-weixin-<sha256(userid)>.slice(1, 17);
|
|
8
8
|
* v1.27.3 错位一位避开损坏 log)
|
|
9
9
|
* - 路由匹配(exact user → 通配 * 兜底)
|
|
10
10
|
*
|
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
*/
|
|
14
14
|
import { type WeixinSettings, type WeixinRouteConfig, type WeixinAccountConfig, type ImSettings } from '../kernel/ccc.js';
|
|
15
15
|
import type { WeixinMediaRef, WeixinMessageItem } from './ilink.js';
|
|
16
|
-
/** 判定 sessionId 是否为微信桥会话(**命名形态**判定;权力面判据见 `
|
|
16
|
+
/** 判定 sessionId 是否为微信桥会话(**命名形态**判定;权力面判据见 `isTrajectoryConstrained`) */
|
|
17
17
|
export declare function isWeixinSessionId(sessionId: string | undefined): boolean;
|
|
18
18
|
/**
|
|
19
19
|
* **轨迹编码 → 固定的 dsh 会话 id**(2026-10-19 重做:身份挂在轨迹上,不挂在用户上)。
|
|
@@ -25,7 +25,7 @@ export declare function isWeixinSessionId(sessionId: string | undefined): boolea
|
|
|
25
25
|
* 对不上旧会话的历史工具清单)。⇒ 那不是"轨迹是一等公民",那是"用户是一等公民"。
|
|
26
26
|
*
|
|
27
27
|
* 新派生 = **由轨迹编码决定**:同一条轨迹 ⇒ 恒同一条 dsh 会话(无论谁在跟它说话);
|
|
28
|
-
* 换轨迹 ⇒ 新会话。这正是"
|
|
28
|
+
* 换轨迹 ⇒ 新会话。这正是"轨迹约束是认知轨迹的一种"。
|
|
29
29
|
*
|
|
30
30
|
* ## 连带删掉的东西
|
|
31
31
|
*
|
|
@@ -78,6 +78,33 @@ export declare function readWeixinCredential(root: string, accountId: string): W
|
|
|
78
78
|
export declare function writeWeixinCredential(root: string, accountId: string, cred: WeixinAccountCredential): void;
|
|
79
79
|
/** 清除账号凭据(移除账号时) */
|
|
80
80
|
export declare function clearWeixinCredential(root: string, accountId: string): void;
|
|
81
|
+
/** 飞书账号凭据(appSecret 缺 ⇒ 账号未绑定) */
|
|
82
|
+
export interface FeishuAccountCredential {
|
|
83
|
+
/** 应用 appId(自建应用「凭证与基础信息」页) */
|
|
84
|
+
appId: string;
|
|
85
|
+
/** 应用 appSecret(**凭据**,只在 localstore) */
|
|
86
|
+
appSecret: string;
|
|
87
|
+
}
|
|
88
|
+
/** 读取飞书账号凭据(appId 或 appSecret 缺失 → null——账号未绑定) */
|
|
89
|
+
export declare function readFeishuCredential(root: string, accountId: string): FeishuAccountCredential | null;
|
|
90
|
+
/** 写入飞书账号凭据(面板保存后;两者都必写——只写一半等于没绑) */
|
|
91
|
+
export declare function writeFeishuCredential(root: string, accountId: string, cred: FeishuAccountCredential): void;
|
|
92
|
+
/** 清除飞书账号凭据(移除账号时) */
|
|
93
|
+
export declare function clearFeishuCredential(root: string, accountId: string): void;
|
|
94
|
+
/**
|
|
95
|
+
* **按渠道判「该账号是否已绑定凭据」**(面板读数用;单一处)。
|
|
96
|
+
*
|
|
97
|
+
* 🔴 为什么需要它(2026-10-19 P2 面板实测缺陷):面板原先对所有账号都调
|
|
98
|
+
* `readWeixinCredential`(只看 `WEIXIN_<A>_TOKEN`)。一个**飞书**账号的凭据在
|
|
99
|
+
* `FEISHU_<A>_APPID|SECRET`,走那条读法**必然读到 null** ⇒ 面板把一个配好的飞书账号
|
|
100
|
+
* 显示成「未绑定凭据」,用户据此以为没配上。
|
|
101
|
+
*
|
|
102
|
+
* 判据与装配处**同源**(`im/bridge.ts` 的 `readImCredentialByChannel`):飞书要 appId **且**
|
|
103
|
+
* appSecret,微信只要有 token。两处若各写一份判据,就会重演"两个读数器给出不同答案"。
|
|
104
|
+
*
|
|
105
|
+
* ⚠️ 只回**布尔**,不回任何凭据值 —— 这是 wire 面。
|
|
106
|
+
*/
|
|
107
|
+
export declare function imCredentialBound(root: string, accountId: string, channel: string | undefined): boolean;
|
|
81
108
|
/**
|
|
82
109
|
* 路由匹配:exact user 优先,通配 `*` 兜底。
|
|
83
110
|
* @returns 命中的**轨迹编码**;未命中 → null(无路由 → 消息不回复/忽略)
|
|
@@ -132,5 +159,13 @@ export declare function saveWeixinRoutes(root: string, routes: WeixinRouteConfig
|
|
|
132
159
|
* 移除中间账号后不冲突(wechat-2 被删 → 新账号复用 wechat-2 而非跳到 4)。
|
|
133
160
|
*/
|
|
134
161
|
export declare function nextWeixinAccountId(settings: WeixinSettings): string;
|
|
162
|
+
/**
|
|
163
|
+
* 生成下一个可用飞书账号 id(`feishu-N`,N 取**最小未占用**)。
|
|
164
|
+
*
|
|
165
|
+
* 与 {@link nextWeixinAccountId} 分开而不是加参数:两者的**前缀不同**,
|
|
166
|
+
* 而前缀是给人看的(面板上一眼分清哪些账号来自哪个渠道)。
|
|
167
|
+
* 判据相同(最小未占用,删中间不跳号)。
|
|
168
|
+
*/
|
|
169
|
+
export declare function nextFeishuAccountId(settings: WeixinSettings): string;
|
|
135
170
|
/** 切换总开关 */
|
|
136
171
|
export declare function setWeixinEnabled(root: string, enabled: boolean): WeixinSettings;
|