@shgroup/dsh-serenity-hooks 1.85.0 → 1.87.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.
@@ -1,6 +1,6 @@
1
- import { r as cccRootForCwd } from "./ccc-roots-VCJ3aYJA.js";
1
+ import { r as cccRootForCwd } from "./ccc-roots-BI_4UVcj.js";
2
2
  import { _ as resolveInside, g as readUtf8 } from "./ccc-CyYJL4F2.js";
3
- import { A as sessionEvents, E as parseTrajectoryCode, b as getActiveSessionInfo, j as sessionsRoot, x as getLastActiveSessionInfo } from "./profile-DnLSb7yv.js";
3
+ import { D as parseTrajectoryCode, M as sessionsRoot, S as getLastActiveSessionInfo, j as sessionEvents, x as getActiveSessionInfo } from "./profile-vHheCJiU.js";
4
4
  import { dirname, join } from "node:path";
5
5
  import { existsSync, mkdirSync, readFileSync, readdirSync, renameSync, statSync, writeFileSync } from "node:fs";
6
6
  import { fileURLToPath } from "node:url";
@@ -1222,4 +1222,4 @@ function replacesDefaultIdentity(profile) {
1222
1222
  return hasFile || hasInline;
1223
1223
  }
1224
1224
  //#endregion
1225
- export { sessionEvents as A, isoLocal as B, listSessions as C, readActiveSessionMd as D, parseTrajectoryCode as E, trajectoryDescOf as F, localHuman as H, useSession as I, TRAJECTORY_FILENAME as L, setActiveSessionInfo as M, showSession as N, readTrajectoryUid as O, summarize as P, TRAJECTORY_LEGACY_FILENAMES as R, listActiveTrajectories as S, parseSessionContextFromEvents as T, localIdStamp as U, localFileStamp as V, createSession as _, readTrajectoryConstraintAt as a, getActiveSessionInfo as b, trajectoryConstraintForSession as c, unregisterTrajectorySession as d, COGNITIVE_TRAJECTORY_ACTIONS as f, clearActiveSessionInfo as g, archiveSession as h, profile_exports as i, sessionsRoot as j, resolveSessionByTitle as k, registerTrajectorySession as l, UID_FILENAME as m, isTrajectoryConstrained as n, readTrajectoryConstraintByCode as o, DEFAULT_SESSION_SCOPE as p, listTrajectoryConstraints as r, replacesDefaultIdentity as s, TRAJECTORY_CONSTRAINT_FILENAME as t, trajectoryDirNameForSession as u, extractSessionMdPathFromText as v, namingTitleFor as w, getLastActiveSessionInfo as x, findSession as y, readTrajectoryFile as z };
1225
+ export { resolveSessionByTitle as A, readTrajectoryFile as B, listActiveTrajectories as C, parseTrajectoryCode as D, parseSessionContextFromEvents as E, summarize as F, localFileStamp as H, trajectoryDescOf as I, useSession as L, sessionsRoot as M, setActiveSessionInfo as N, readActiveSessionMd as O, showSession as P, TRAJECTORY_FILENAME as R, getLastActiveSessionInfo as S, namingTitleFor as T, localHuman as U, isoLocal as V, localIdStamp as W, clearActiveSessionInfo as _, profile_exports as a, findSession as b, replacesDefaultIdentity as c, trajectoryDirNameForSession as d, unregisterTrajectorySession as f, archiveSession as g, UID_FILENAME as h, listTrajectoryConstraints as i, sessionEvents as j, readTrajectoryUid as k, trajectoryConstraintForSession as l, DEFAULT_SESSION_SCOPE as m, findDirNameByCode as n, readTrajectoryConstraintAt as o, COGNITIVE_TRAJECTORY_ACTIONS as p, isTrajectoryConstrained as r, readTrajectoryConstraintByCode as s, TRAJECTORY_CONSTRAINT_FILENAME as t, registerTrajectorySession as u, createSession as v, listSessions as w, getActiveSessionInfo as x, extractSessionMdPathFromText as y, TRAJECTORY_LEGACY_FILENAMES as z };
@@ -172,6 +172,13 @@ function normalizeImSection(w, d) {
172
172
  * ⚠️ 2026-10-19 起它是 {@link readImSettings} 的**薄别名**(签名与返回形状不变):
173
173
  * 旧调用点无需改动,但**配置来源已经统一**——`im` 段优先、`weixin` 段回退。
174
174
  * 保留名字是为了让既有调用点与测试**逐字不动**(P1 是纯重构,读数必须不变)。
175
+ *
176
+ * 🔴 2026-10-22:**返回类型由 `WeixinSettings` 改为 `ImSettings`**。
177
+ * 此前实现返回的就是 `readImSettings(...)`(已是归一形态:`accounts[].channel` 必填),
178
+ * 却把它断言成旧类型 —— 于是类型层"看不见"`channel` 字段,
179
+ * 调用方写 `a.channel` 会报 TS2339,而**运行时它一直在那儿**。
180
+ * ⇒ 那是**类型声明在撒谎**,不是调用方写错。改成真实类型后,多账户/多渠道的读侧才敢用它。
181
+ * ⚠️ 附带效应:`WeixinSettings` 的旧形状调用方若用到旧字段,会在此处暴露(好事,早暴露)。
175
182
  */
176
183
  function readWeixinSettings(root, paths = DEFAULT_SERENITY_CONFIG_PATHS) {
177
184
  return readImSettings(root, paths);
@@ -416,4 +423,4 @@ function setWeixinEnabled(root, enabled) {
416
423
  return next;
417
424
  }
418
425
  //#endregion
419
- export { readFeishuCredential as a, route_exports as c, weixinSessionIdForTrajectory as d, matchWeixinRoute as i, sanitizeFileName as l, extractWeixinText as n, readWeixinCredential as o, hasVoiceItem as r, readWeixinSettings as s, extractWeixinMedia as t, weixinInboundDir as u };
426
+ export { readFeishuCredential as a, readWeixinSettings as c, weixinInboundDir as d, weixinSessionIdForTrajectory as f, matchWeixinRoute as i, route_exports as l, extractWeixinText as n, readImSettings as o, hasVoiceItem as r, readWeixinCredential as s, extractWeixinMedia as t, sanitizeFileName as u };
@@ -21,6 +21,19 @@ export declare function accIdentityText(root: string, configPaths?: string[], en
21
21
  * 不再重复注入同一内容(token 双倍浪费,见 S134 注入方案梳理)。
22
22
  */
23
23
  export declare function accMessage(root: string, configPaths: string[], entrySkillMaxChars: number): UserMessage;
24
+ /**
25
+ * 这条会话的历史里**是否已经有** ACC 身份横幅(= 判据的唯一权威)。
26
+ *
27
+ * 🔴 这是本条修复的核心:**"注入过没有"是会话的属性,不是进程的属性。**
28
+ * 会话历史随会话走(落盘、重启后仍在),进程内状态随进程死。
29
+ *
30
+ * ⚠️ 读不到 events(受限宿主 / 投影未就绪)⇒ 返回 `false`(退回"注入一次")。
31
+ * 这是**偏保守**的选择:多注入一条横幅(可见、可清理)远好于
32
+ * **永远不再注入**(陈旧会话里 ACC 身份彻底缺失,且零信号)。
33
+ *
34
+ * @param session 目标会话(`sessionEvents` 能读的形状)
35
+ */
36
+ export declare function hasAccBanner(session: unknown): boolean;
24
37
  /**
25
38
  * 重启恢复的根会话判定(S134 需求):
26
39
  * 只有"conversation 根会话"才自动恢复最近激活的宁静号会话——
@@ -10,6 +10,7 @@ import type { Context } from 'cordis';
10
10
  import type { Agent } from '@deepseek-ai/dsh-agent';
11
11
  import { type BlacklistRule } from '../kernel/ccc.js';
12
12
  import { type TrajectoryConstraint } from '../trajectory/policy/profile.js';
13
+ import { type VisibilitySource } from './tool-visibility.js';
13
14
  export interface GuardInput {
14
15
  root: string;
15
16
  toolName: string;
@@ -39,6 +40,39 @@ interface GuardDecisionResult {
39
40
  * 优先级:safe-mode bash > 路径越界 > 黑名单命中。
40
41
  */
41
42
  export declare function decideGuard(input: GuardInput): GuardDecisionResult;
43
+ /**
44
+ * 🔴 **工具可见性的唯一发射入口**(方案 C 的落地)。
45
+ *
46
+ * ## 它取代了什么
47
+ *
48
+ * 改之前有六条独立 sync,每条在自己的 per-会话 Map 里放一个 `restrict` disposer,
49
+ * 且都由 `pre-step`(每步)与 `seed`(冷载入)各自调用 ⇒ 一次交互里可能发出多次
50
+ * `tools.restrict` ⇒ 宿主每次发 `tools/change` ⇒ 模型侧看到工具清单**先进后出**
51
+ * (实测症状:`新增 7 个` 紧跟 `移除 7 个`)。
52
+ *
53
+ * ## 为什么合并是对的(不是简化)
54
+ *
55
+ * 宿主 `ToolRuntime.restrict` 契约逐字:**Restrictions intersect**。
56
+ * ⇒ N 次 `deny: Dᵢ` 的净效果 = `⋃Dᵢ`。先求并集只发一次,**结果相同**、只触发一次 `tools/change`。
57
+ * 前提 = **只用 `deny`**(`allow` 的"交集"不满足并集律);本仓 18 处 restrict 全部只发 `deny`。
58
+ *
59
+ * ## 正确性怎么保住(这是本设计被质疑的那一点)
60
+ *
61
+ * 六源**各自独立**算自己的 deny 并**各自 catch**:某一个来源读配置抛错 ⇒ 它返回
62
+ * `ok:false`(不贡献 deny),**其余五源照常生效**。⇒ 合并**不会**把一格的失败放大成六格。
63
+ * 全量计算见 {@link computeVisibilitySources}(纯函数)。
64
+ *
65
+ * @param agent 目标会话
66
+ * @param root CCC 根;`null` ⇒ 不在 CCC 内 ⇒ 隐藏**全部** ACC 工具
67
+ */
68
+ export declare function syncToolVisibility(agent: Agent, root: string | null): void;
69
+ /**
70
+ * 六源 → 各自的 deny 集合(**纯函数:只读配置、不碰宿主**)。
71
+ *
72
+ * 每条来源**自己负责自己的失败**:抛错就 catch 在这里,转成 `ok:false + error`。
73
+ * ⛔ 绝不让一个来源的异常冒泡 —— 那会让另外五源一起失效(正是合并最容易犯的错)。
74
+ */
75
+ export declare function computeVisibilitySources(sessionId: string, root: string | null): VisibilitySource[];
42
76
  /** restrict 诊断状态(status API 暴露;排查 restrict 未生效问题) */
43
77
  interface RestrictDiagnostics {
44
78
  lastKey: string | null;
@@ -51,53 +85,28 @@ interface RestrictDiagnostics {
51
85
  }
52
86
  export declare function getRestrictDiagnostics(): RestrictDiagnostics;
53
87
  /**
54
- * 同步安全模式工具隐藏:标记存在 → `agent.ctx.tools.restrict deny` 隐藏
55
- * **基础表(bash)∪ CCC 声明的 `safeMode.hiddenTools`**;标记消失 → 全部解除。
56
- * pre-step 每步调用 ⇒ 开/关/**改声明**三种变化都实时生效(v1.51.4 起含"改声明")。
88
+ * im-bridge 隐藏诊断(排查"工具为什么不在清单里")。
57
89
  *
58
- * 与 `readSafeModeHiddenTools` 的分工(R↓):本函数负责**形状与失败退化**,reader 只管取值。
59
- * 🔴 退化规则:DSH 契约规定 **未注册的工具名会让整次 restrict 失败**("unknown names … fail")
60
- * ⇒ 绝不能让 CCC 写错一个名字就丢掉 safe-mode 的核心隐藏(bash)。失败时退化为**只隐藏基础表**,
61
- * 并在日志响亮报错;退化态**按期望签名记账**,不会每步重试刷屏。
90
+ * 🔴 方案 C 之后**唯一真相源**是 `seams/tool-visibility.ts` 的全局表。
91
+ * 本函数只**读**它并回答"该会话当前是否隐藏了 im-bridge" ——
92
+ * 不再自己维护第二份状态(那会让诊断与实现各说一套,D164 同族)。
62
93
  */
63
- export declare function syncSafeModeRestriction(agent: Agent, root: string): void;
64
- /** im-bridge 隐藏诊断(排查"工具为什么不在清单里") */
65
94
  export declare function getImBridgeVisibilityDiagnostics(): {
66
95
  hiddenKeys: string[];
67
96
  };
68
97
  /**
69
- * 同步 im-bridge 可见性(v1.31.0,S142 用户拍板 Q3「未配置则不可见」)。
70
- *
71
- * 判据 = 本会话 CCC 是否启用了**任一** IM 通道(`hasEnabledImChannel`,当前只有 weixin):
72
- * - 未启用 → `agent.ctx.tools.restrict({ deny: ['im-bridge'] })`:**从 schema 移除**
73
- * (模型看不到,而不是看得到但调用被拒——用户原话"不配置则不可见")
74
- * - 启用 → 解除隐藏(CCC 配置热更新即时生效)
98
+ * 会话销毁清理(lifecycle 调用)。
75
99
  *
76
- * 与 `syncSafeModeRestriction` 同点调用(pre-step 每步 + 会话就绪),失败不阻断
77
- * (守卫仍兜底;restrict 不可用时最坏是"看得到但通道未配置 → 返回 CHANNEL_NOT_CONFIGURED")。
100
+ * ⚠️ 方案 C 后撤销动作**只在** `forgetToolVisibility` 里做一次(那一次合并 restrict)。
101
+ * 本函数保留为语义占位:重复撤销同一条 restrict 会抛错,故这里刻意不做动作。
78
102
  */
79
- export declare function syncImBridgeVisibility(agent: Agent, root: string): void;
80
- /** 会话销毁清理(lifecycle 调用;防 per-会话 Map 无界增长——review F-08 同族) */
81
- export declare function forgetImBridgeVisibility(sessionId: string): void;
103
+ export declare function forgetImBridgeVisibility(_sessionId: string): void;
82
104
  /** 诊断面:本会话已屏蔽的宿主定时器工具(排查"工具为什么不见了") */
83
105
  export declare function getHostScheduleHiddenDiagnostics(): {
84
106
  hidden: string[];
85
107
  };
86
- /**
87
- * 在 **CCC 内**屏蔽宿主自带的定时器工具(v1.59.5)。
88
- *
89
- * 判据 = **会话是否落在某个 CCC 内**(`root !== null`)——与 `syncCccToolVisibility` 互补:
90
- * 那个管"不在 CCC ⇒ 隐藏**我们自己的**全部工具",本函数管"**在** CCC ⇒ 隐藏**宿主的**定时器工具"。
91
- *
92
- * 幂等;失败不阻断 step(最坏 = 那一个工具仍可见,但那只是"宿主的正常能力没被藏起来",
93
- * 不会让会话坏掉)。逐个名字独立发 restrict ⇒ 任何一个名字不在册都不会连累其余三个。
94
- *
95
- * @param agent 目标会话
96
- * @param root 该会话的 CCC 根;`null` ⇒ 非 CCC ⇒ **不动**(宿主的定时器在别处是正常能力)
97
- */
98
- export declare function syncHostScheduleToolHiding(agent: Agent, root: string | null): void;
99
- /** 会话销毁清理(lifecycle 调用;按 `${sessionId}::` 前缀回收) */
100
- export declare function forgetHostScheduleToolHiding(sessionId: string): void;
108
+ /** 会话销毁清理(lifecycle 调用)——方案 C 后由统一清理撤销,这里保留语义占位。 */
109
+ export declare function forgetHostScheduleToolHiding(_sessionId: string): void;
101
110
  /** 专属工具隐藏诊断(排查"工具为什么不在清单里") */
102
111
  export declare function getExclusiveToolsDiagnostics(): {
103
112
  hidden: string[];
@@ -107,62 +116,17 @@ export declare function getTrajectoryConstraintVisibilityDiagnostics(): {
107
116
  keys: string[];
108
117
  denyByKey: Record<string, string[]>;
109
118
  };
110
- /**
111
- * 同步 **轨迹约束会话的工具面**:把白名单之外的工具**从 schema 移除**(不是"看得到但被拒")。
112
- *
113
- * ## 为什么必须有这条(owner 2026-10-19:「工具清单也要热加载」)
114
- *
115
- * 在它之前,轨迹约束白名单**只由运行时守卫拦截**(`decideGuard` 的 轨迹约束 分支):
116
- * 工具全部出现在模型 schema 里,调用时才被打回。⇒ 两个真实后果:
117
- * ① **模型看得见它不该有的工具** —— 它会去试,试了才撞墙(一轮白烧,且错误文案泛化,
118
- * 看不出"是不在清单里"还是"临时性拒绝");
119
- * ② **配置变动在 schema 上永远不生效** —— 改 `trajectory-constraint.json` 的 `tools` 只影响"调用是否放行"。
120
- *
121
- * ## 判据与降级
122
- *
123
- * - deny 集合 = **`REGISTERED_TOOLS` − 白名单**(只对**我们自己注册的**工具算,
124
- * 不去动宿主的工具 —— 宿主的工具由各自的条件同步管)。
125
- * - ⚠️ **未注册的名字会让整次 restrict 失败**(宿主契约逐字 "unknown names … fail"),
126
- * 而 `diagram` 是按开关注册的 ⇒ 与 `hideAllFor` 同款**两档尝试**(先全覆盖,失败退常驻段)。
127
- * - 读不到约束面(不是 轨迹约束 / 配置删了)⇒ **撤销既有隐藏**(回到"无约束"):
128
- * ⛔ 不能只是 `return` —— 那会把上一步的隐藏**永久留在**一个已不再是 轨迹约束的会话上
129
- * (这正是 owner 说的"删掉 `trajectory-constraint.json` 就该立刻恢复")。
130
- * - 幂等:算出来的 deny 签名不变就跳过(不每步重建 schema)。
131
- *
132
- * @param agent 目标会话
133
- * @param root CCC 根
134
- */
135
- export declare function syncTrajectoryConstraintToolVisibility(agent: Agent, root: string): void;
136
- /** 会话销毁清理(lifecycle 调用) */
137
- export declare function forgetTrajectoryConstraintToolVisibility(sessionId: string): void;
138
- /**
139
- * 同步专属工具可见性(v1.33)。
140
- *
141
- * 判据 = CCC 配置 `exclusiveTools`(`readExclusiveTools`):
142
- * - **未声明** → `agent.ctx.tools.restrict({ deny: [tool] })`:从 schema 移除(模型看不到)
143
- * - **声明** → 解除隐藏(CCC 配置热更新即时生效)
144
- *
145
- * 判据失败按"未声明"处理(fail-closed:专属工具默认隐藏),原因见 `readExclusiveTools` 注释。
146
- * 与 `syncImBridgeVisibility` 同点调用(pre-step 每步 + 会话就绪),失败不阻断一步——
147
- * 最坏情况是"看得到但被拒",而不是让 step 出错。
148
- */
149
- export declare function syncExclusiveToolsVisibility(agent: Agent, root: string): void;
150
- /** 会话销毁清理(lifecycle 调用;本函数按 `${sessionId}::` 前缀回收全部专属工具项) */
151
- export declare function forgetExclusiveToolsVisibility(sessionId: string): void;
119
+ /** 会话销毁清理(lifecycle 调用)。
120
+ * ⚠️ 方案 C 后撤销只在 `forgetToolVisibility` 做一次;本函数保留语义占位。
121
+ * 留着它的价值:lifecycle 的调用点表达"这条会话有轨迹约束可见性状态要清",
122
+ * 删掉调用点会让那件事**从代码里消失**(将来有人重加机制时找不到插点)。 */
123
+ export declare function forgetTrajectoryConstraintToolVisibility(_sessionId: string): void;
124
+ /** 会话销毁清理(lifecycle 调用)——同上,语义占位。 */
125
+ export declare function forgetExclusiveToolsVisibility(_sessionId: string): void;
152
126
  /** 非 CCC 隐藏诊断(排查"工具为什么不见了") */
153
127
  export declare function getNonCccHiddenDiagnostics(): {
154
128
  hidden: string[];
155
129
  };
156
- /**
157
- * 同步「本会话在不在 CCC 内」的工具可见性(v1.56.6)。
158
- *
159
- * @param agent 目标会话的 agent
160
- * @param root 该会话的 CCC 根;`null` ⇒ **不在任何 CCC 内** ⇒ 隐藏全部 ACC 工具
161
- *
162
- * 幂等:同一会话重复调用不会重复 restrict(已是目标态即直接返回)。
163
- * 失败不抛(最坏情况:工具可见,但**调用时**仍被各工具自己的根解析拒绝——既有兜底不变)。
164
- */
165
- export declare function syncCccToolVisibility(agent: Agent, root: string | null): void;
166
130
  /** 会话销毁清理(lifecycle 调用;防 per-会话 Map 无界增长) */
167
131
  export declare function forgetNonCccToolVisibility(sessionId: string): void;
168
132
  interface GuardRegistration {
@@ -0,0 +1,76 @@
1
+ /** 一次 restrict 的 deny 集合来源(每个来源**独立成败**,互不连累) */
2
+ export interface VisibilitySource {
3
+ /** 稳定标识(诊断与去重用;例:`safeMode` / `im-bridge` / `hostSchedule`) */
4
+ readonly id: string;
5
+ /** 本来源要隐藏的工具名(已去重前的数组;由收集器统一去重) */
6
+ readonly deny: readonly string[];
7
+ /**
8
+ * 本来源的判据是否成立。
9
+ * - `false` ⇒ 本来源**不贡献** deny(等价于它"没意见"),**不是**错误。
10
+ * - 读取配置抛错等情况由来源自己决定返回 `false` 还是抛(收集器会捕获并降级)。
11
+ */
12
+ readonly ok: boolean;
13
+ /** `ok=false` 且确实出了错(不是"判据不成立")时的一句话,用于诊断 */
14
+ readonly error?: string;
15
+ }
16
+ /** 一次可见性判决(纯数据,可 JSON 化,便于诊断与测试断言) */
17
+ export interface VisibilityDecision {
18
+ /** 合并后的 deny(已去重、已排序 —— 排序让签名稳定、可比较) */
19
+ readonly deny: string[];
20
+ /** 参与判决的来源(含失败的),用于诊断"谁把工具藏了" */
21
+ readonly sources: readonly VisibilitySource[];
22
+ /** 是否有任一来源报错(决定要不要打日志;**不影响** deny 的计算) */
23
+ readonly hasError: boolean;
24
+ }
25
+ /**
26
+ * 🔴 本模块的**准入契约**:可见性判决只允许用 `deny`。
27
+ *
28
+ * 宿主 `restrict` 同时支持 `allow` 与 `deny`,而**两者不可混用合并**:
29
+ * `allow: A₁` 与 `allow: A₂` 的交集是 `A₁ ∩ A₂`,**不是并集**。
30
+ * ⇒ 若将来某个来源想"只保留某几个工具",它**不能**塞进本模块,
31
+ * 必须自己单独发一条 restrict(那条会与本次的 deny 正常求交)。
32
+ * 该常量是这条契约的**机器可读声明**(测试据此钉住)。
33
+ */
34
+ export declare const TOOL_VISIBILITY_DENY_ONLY = true;
35
+ /** 收集器:把各来源的 deny 合并成一个判决(**纯函数、无副作用**) */
36
+ export declare function collectVisibilityDecision(sources: readonly VisibilitySource[]): VisibilityDecision;
37
+ /** 判决签名(幂等判据:签名相同 ⇒ 目标态已达到 ⇒ **一次 restrict 都不发**) */
38
+ export declare function decisionSignature(decision: VisibilityDecision): string;
39
+ /** 诊断:当前每个会话的可见性状态(排查"某工具为何不可见") */
40
+ export declare function getToolVisibilityDiagnostics(): {
41
+ keys: string[];
42
+ denyByKey: Record<string, string[]>;
43
+ };
44
+ /**
45
+ * 发射一次判决(**唯一的写点**)。
46
+ *
47
+ * 三条纪律:
48
+ * ① **幂等**:签名与当前已发射的一致 ⇒ 直接返回 `'unchanged'`,**不碰宿主**
49
+ * (这正是消除"工具清单闪动"的那一步)。
50
+ * ② **先撤后发**:宿主的 disposer 才是"撤销上一次",先撤再发才不会叠加旧集合。
51
+ * (旧集合是新集合的子集时叠不叠都看不出来,但**不是子集时**会残留 —— 必须撤。)
52
+ * ③ 🔴 **未注册的名字不许炸掉整次发射**:宿主契约"unknown names … fail"。
53
+ * 合并后集合里只要有一个不在册的名字,整次 restrict 就失败 ⇒ 六个机制**全都不生效**。
54
+ * ⇒ 发射前**先逐个探活**(`tools.get(name)`),把不在册的名字剔掉并**如实记账**
55
+ * (剔除的名字进 `dropped`,由调用方决定要不要告警)。
56
+ * ⚠️ 这比旧行为**更保守**:旧行为是"某条 restrict 失败 ⇒ 那一格静默不生效",
57
+ * 新行为是"剔掉那个名字、其余照常生效" —— **等价或更好**,且总有日志。
58
+ *
59
+ * @returns `'unchanged'` / `'applied'` / `'failed'`(`failed` 时 `deny` 保持原样)
60
+ */
61
+ export declare function applyVisibilityDecision(agent: {
62
+ ctx: {
63
+ tools: {
64
+ restrict(f: {
65
+ deny: string[];
66
+ }): () => void;
67
+ get?(n: string): unknown;
68
+ };
69
+ };
70
+ }, sessionId: string, decision: VisibilityDecision): {
71
+ status: 'unchanged' | 'applied' | 'failed';
72
+ dropped: string[];
73
+ error?: string;
74
+ };
75
+ /** 会话销毁清理(防 per-会话 Map 无界增长;lifecycle 调用) */
76
+ export declare function forgetToolVisibility(sessionId: string): void;
@@ -1,8 +1,8 @@
1
- import { n as agentCwdFor, r as cccRootForCwd, s as hostAgents } from "./ccc-roots-VCJ3aYJA.js";
1
+ import { n as agentCwdFor, r as cccRootForCwd, s as hostAgents } from "./ccc-roots-BI_4UVcj.js";
2
2
  import { g as readUtf8, o as isSafeModeOn, p as readCognitiveTrajectorySkills, u as readBlacklist } from "./ccc-CyYJL4F2.js";
3
- import { B as isoLocal, D as readActiveSessionMd, a as readTrajectoryConstraintAt, c as trajectoryConstraintForSession, d as unregisterTrajectorySession$1, j as sessionsRoot, l as registerTrajectorySession$1, p as DEFAULT_SESSION_SCOPE, u as trajectoryDirNameForSession } from "./profile-DnLSb7yv.js";
3
+ import { M as sessionsRoot, O as readActiveSessionMd, V as isoLocal, d as trajectoryDirNameForSession, f as unregisterTrajectorySession$1, l as trajectoryConstraintForSession, m as DEFAULT_SESSION_SCOPE, o as readTrajectoryConstraintAt, u as registerTrajectorySession$1 } from "./profile-vHheCJiU.js";
4
4
  import { a as readGitTrack, i as localstorePath } from "./service-DDmHSglV.js";
5
- import { d as constraintCognitiveTrajectory, m as profileMsmWhitelist, n as PLUGIN_SOURCE, o as readLastBound, p as createProfilePromptReader, u as buildTrajectoryBasePrompt, v as ACC_VERSION } from "./message-source-D7dVNDIh.js";
5
+ import { d as constraintCognitiveTrajectory, m as profileMsmWhitelist, n as PLUGIN_SOURCE, o as readLastBound, p as createProfilePromptReader, u as buildTrajectoryBasePrompt, v as ACC_VERSION } from "./message-source-DRkQwfgh.js";
6
6
  import { basename, dirname, join, resolve } from "node:path";
7
7
  import { chmodSync, existsSync, mkdirSync, readFileSync, readdirSync, renameSync, statSync, writeFileSync } from "node:fs";
8
8
  import { createUserMessage } from "@deepseek-ai/dsh-llm";
@@ -1,4 +1,4 @@
1
- import { f as hostSettings } from "./ccc-roots-VCJ3aYJA.js";
1
+ import { f as hostSettings } from "./ccc-roots-BI_4UVcj.js";
2
2
  //#region src/surface/settings.ts
3
3
  /**
4
4
  * 设置面板命名空间 = **profile 条目 id**(v0.1.7 起;与旧 `settings.yaml` 段名逐字相同)。
@@ -0,0 +1,20 @@
1
+ /**
2
+ * tools/im-docs.ts — 「IM 桥怎么用、怎么配路由」的**手册正文**(2026-10-22)
3
+ *
4
+ * ## 为什么需要这一章(owner 原话)
5
+ *
6
+ * 「我在测试飞书,发现 ACC 没提供 IM 桥使用说明和账户查询方法,**导致 CCC 没办法自己配路由**,
7
+ * 这个要完善下」+「配路由需要的是**账户到轨迹**,轨迹 CCC 知道,**账户 CCC 不知道**,
8
+ * 需要有个能力让 CCC 查询用户配好的账户」。
9
+ *
10
+ * ⇒ 两件事被同时指出来:**没有说明面**(只能猜配置形状)+ **没有查询面**(拿不到对方 id)。
11
+ * 后者已由 `im-bridge action=contacts` 兑现;本章补前者。
12
+ *
13
+ * ## 🔴 写这一章时守的两条纪律
14
+ *
15
+ * 1. **只描述已经建成的东西**(D179):本章出现的每个字段、每个动作,都必须在实现里存在。
16
+ * 写"打算做"的功能进手册 = 教用户按图施工然后把自己锁死。
17
+ * 2. **指针从读者真正打开的那个工具发出**(D178):读这一章的人是 **CCC 的 agent**,
18
+ * 它手边常用的入口是 `im-bridge`(工具描述已指向本章)。⛔ 不能只藏在 container_admin 里。
19
+ */
20
+ export declare const IM_BRIDGE_GUIDE = "# IM \u6865\uFF08im-bridge\uFF09\u2014\u2014 \u600E\u4E48\u7528\u3001\u600E\u4E48\u914D\u3001\u600E\u4E48\u67E5\n\n## \u4E00\u3001\u5B83\u662F\u4EC0\u4E48\n\n\u4E00\u4E2A CCC \u901A\u8FC7 IM \u6E20\u9053\uFF08\u5FAE\u4FE1 / \u98DE\u4E66\uFF09\u8DDF\u5BB6\u4EBA\u6536\u53D1\u6D88\u606F\u7684\u80FD\u529B\u3002\n**\u53EA\u4F5C\u7528\u4E8E\u672C\u5BB9\u5668**\u2014\u2014\u6CA1\u6709\"\u76EE\u6807\u5BB9\u5668\"\u53C2\u6570\uFF08\u8DE8 CCC \u53D1\u9001\u4E0D\u5728\u80FD\u529B\u9762\uFF09\u3002\n\n\u5DE5\u5177\u5728**\u672A\u914D\u7F6E\u4EFB\u4F55 IM \u6E20\u9053\u65F6\u6839\u672C\u4E0D\u53EF\u89C1**\uFF08\u4E0D\u662F\"\u770B\u5F97\u5230\u4F46\u88AB\u62D2\"\uFF09\u3002\n\u21D2 \u4F60\u770B\u5230\u8FD9\u4E2A\u5DE5\u5177\uFF0C\u8BF4\u660E\u672C CCC \u5DF2\u914D\u4E86\u81F3\u5C11\u4E00\u4E2A\u6E20\u9053\u3002\n\n## \u4E8C\u3001\u4E94\u4E2A\u52A8\u4F5C\n\n| \u52A8\u4F5C | \u7528\u9014 | \u5173\u952E\u53C2\u6570 |\n|---|---|---|\n| `send` | \u53D1\u4E00\u6761\u6587\u672C | `user`\uFF08\u5BF9\u65B9\uFF09\uFF0B `text` |\n| `send-file` | \u53D1\u672C\u5BB9\u5668\u5185\u7684\u4E00\u4E2A\u6587\u4EF6 | `user` \uFF0B `file`\uFF08**CCC \u6839\u76F8\u5BF9\u8DEF\u5F84**\uFF09\uFF0B \u53EF\u9009 `caption` |\n| `users` | \u5DF2\u914D\u7684**\u522B\u540D**\u8868 | \u2014 |\n| `status` | \u6E20\u9053\u5065\u5EB7 \uFF0B **\u8D26\u53F7** \uFF0B **\u5F53\u524D\u8DEF\u7531**\uFF08\u542B\u6BCF\u6761\u8DEF\u7531\u7684\u76EE\u6807\u662F\u5426\u5B58\u5728\uFF09 | \u2014 |\n| `contacts` | **\u8DDF\u672C\u5BB9\u5668\u6709\u8FC7\u5F80\u6765\u7684\u4EBA**\uFF08\u542B\u5BF9\u65B9 id\uFF09\uFF0B \u672A\u914D\u8DEF\u7531\u7684 \uFF0F \u914D\u4E86\u5374\u6CA1\u5F80\u6765\u7684 | \u2014 |\n\n\u26A0\uFE0F \u6E20\u9053\u4E0D\u4E00\u5B9A\u652F\u6301\u5168\u90E8\u52A8\u4F5C\uFF08\u4F8B\u5982\u98DE\u4E66\u76EE\u524D\u4E0D\u652F\u6301 `send-file`\uFF0C\u4F1A\u5982\u5B9E\u8FD4\u56DE `ACTION_UNSUPPORTED`\uFF09\u3002\n**\u5982\u5B9E\u8FD4\u56DE\"\u4E0D\u652F\u6301\"\u6BD4\u5047\u88C5\u652F\u6301\u597D**\u2014\u2014\u540E\u8005\u4F1A\u5F97\u5230\u4E00\u6761\"\u53D1\u51FA\u53BB\u4F46\u5BF9\u65B9\u6536\u4E0D\u5230\"\u7684\u9759\u9ED8\u6545\u969C\u3002\n\n## \u4E09\u3001\uD83D\uDD34 \u914D\u8DEF\u7531\uFF1A\u5148 `contacts`\uFF0C\u518D\u5199\uFF0C\u518D `status` \u81EA\u68C0\n\n\u8DEF\u7531\u7684\u5F62\u72B6\uFF08`.opencode/serenity.json` \u7684 `im.routes`\uFF09\uFF1A\n\n```json\n{\n \"im\": {\n \"enabled\": true,\n \"accounts\": [\n { \"accountId\": \"wechat-1\", \"channel\": \"weixin\", \"enabled\": true },\n { \"accountId\": \"feishu-1\", \"channel\": \"feishu\", \"enabled\": true }\n ],\n \"routes\": [\n { \"user\": \"o9cq\u2026@im.wechat\", \"trajectory\": \"zhaocai-xingzai\" },\n { \"user\": \"ou_xxxxxxxx\", \"trajectory\": \"llm-intel\" }\n ]\n }\n}\n```\n\n- `user` = **\u5BF9\u65B9\u5728\u8BE5\u6E20\u9053\u5185\u7684 id**\uFF08\u4E0D\u662F\u6211\u4EEC\u7684\u8D26\u53F7\uFF0C\u4E5F\u4E0D\u662F\u522B\u540D\uFF09\n \u00B7 \u5FAE\u4FE1\uFF1A\u5F62\u5982 `xxx@im.wechat`\n \u00B7 \u98DE\u4E66\uFF1A\u5F62\u5982 `ou_\u2026`\uFF08open_id\uFF09\n- `trajectory` = **\u8BA4\u77E5\u8F68\u8FF9\u7F16\u7801**\uFF08\u76EE\u5F55\u540D\u91CC\u90A3\u4E00\u6BB5\uFF0C\u5982 `S142` / `llm-intel`\uFF09\n- `*` \u53EF\u4F5C\u4E3A `user` \u7684**\u901A\u914D\u515C\u5E95**\uFF08exact \u4F18\u5148\uFF09\n\n### \u90A3\u4E2A `user` id \u4ECE\u54EA\u91CC\u6765 \u2014\u2014 \u8FD9\u662F\u672C\u8282\u5B58\u5728\u7684\u539F\u56E0\n\n**\u4F60\u4E0D\u77E5\u9053\u5BF9\u65B9 id\uFF0C\u800C\u5B83\u4E5F\u4E0D\u5728\u4EFB\u4F55\u914D\u7F6E\u6587\u4EF6\u91CC\u3002** \u5B83\u53EA\u51FA\u73B0\u5728\u804A\u5929\u8BB0\u5F55\u4E2D\u3002\n\u21D2 \u7528 `im-bridge({channel:\"weixin\", action:\"contacts\"})`\uFF0C\u5B83\u4F1A\u7ED9\u51FA\uFF1A\n\n```json\n{\n \"contacts\": [\n { \"channel\": \"weixin\", \"userId\": \"o9cq\u2026@im.wechat\", \"accountIds\": [\"wechat-1\"],\n \"route\": null, \"firstSeen\": \"2026-09-04T\u2026\", \"lastSeen\": \"2026-10-22T\u2026\", \"messages\": 1284 }\n ],\n \"unrouted\": [\"o9cq\u2026@im.wechat\"],\n \"orphanRoutes\": [\"ou_typo_never_seen\"],\n \"sources\": [\"AGENT_SESSIONS/_im-logs/weixin/2026-10-22.jsonl\", \"\u2026\"]\n}\n```\n\n\u4E09\u884C\u8F93\u51FA**\u5404\u6709\u7528\u9014\uFF0C\u7F3A\u4E00\u884C\u8FD9\u4E2A\u52A8\u4F5C\u5C31\u53EA\u5269\"\u5217\u540D\u5B57\"**\uFF1A\n\n| \u5B57\u6BB5 | \u542B\u4E49 | \u4F60\u8981\u505A\u4EC0\u4E48 |\n|---|---|---|\n| `contacts[].userId` | \u5BF9\u65B9 id | **\u590D\u5236\u5B83**\u586B\u8FDB `routes[].user` |\n| `unrouted` | \u26A0\uFE0F \u6709\u5F80\u6765\u3001\u4F46**\u6CA1\u914D\u8DEF\u7531** | \u8FD9\u4E9B\u4EBA\u7684\u6D88\u606F\u73B0\u5728**\u65E0\u5904\u53EF\u53BB** \u21D2 \u8FD9\u5C31\u662F\u8BE5\u914D\u7684 |\n| `orphanRoutes` | \u2753 \u914D\u4E86\u8DEF\u7531\u3001\u4F46\u8BB0\u5F55\u91CC**\u4ECE\u6CA1\u51FA\u73B0\u8FC7** | \u5927\u6982\u7387 **id \u5199\u9519**\uFF08\u6216\u4ECE\u672A\u6709\u4EBA\u53D1\u8FC7\uFF09 |\n| `sources` | \u5B9E\u9645\u8BFB\u8FC7\u7684\u6587\u4EF6 | **\u5426\u5B9A\u6027\u7ED3\u8BBA\u7684\u53EF\u4FE1\u5EA6\u4E0A\u9650** = \u8FD9\u4E2A\u5217\u8868\uFF1B\u5B83\u7A7A\u7740\u5C31\u522B\u4E0B\"\u6CA1\u4EBA\"\u7684\u7ED3\u8BBA |\n\n### \u5199\u5B8C\u600E\u4E48\u81EA\u68C0\n\n`im-bridge({channel:\"weixin\", action:\"status\"})` \u7684 `routes[]` \u6BCF\u6761\u90FD\u5E26 **`targetExists`**\uFF1A\n\n- `true` \u21D2 \u8BE5\u8F68\u8FF9\u76EE\u5F55\u771F\u5B9E\u5B58\u5728\uFF0C\u8DEF\u7531**\u6709\u673A\u4F1A**\u751F\u6548\n- `false` \u21D2 **\u8F68\u8FF9\u7F16\u7801\u5199\u9519\u4E86**\uFF08\u6216\u90A3\u6761\u8F68\u8FF9\u5DF2\u88AB\u5F52\u6863/\u5220\u9664\uFF09\n\n\uD83D\uDD34 \u8FD9\u4E00\u683C\u662F\"\u914D\u4E86\u4E0D\u751F\u6548\"\u7684**\u552F\u4E00\u673A\u68B0\u4FE1\u53F7**\uFF1A\u5199\u9519\u4E00\u4E2A\u7F16\u7801**\u4E0D\u4F1A\u6709\u4EFB\u4F55\u62A5\u9519**\uFF0C\n\u6D88\u606F\u8FDB\u6765\u627E\u4E0D\u5230\u76EE\u6807\u5C31\u9759\u9ED8\u4E0D\u6295\u9012\uFF0C\u4F60\u53EA\u4F1A\u770B\u5230\"\u6CA1\u56DE\u97F3\"\u3002\n\n## \u56DB\u3001\u591A\u8D26\u53F7 / \u591A\u6E20\u9053\n\n\u4E00\u4E2A CCC \u7ED1\u591A\u4E2A\u8D26\u53F7\u662F\u5E38\u6001\uFF08owner 2026-10-22\uFF1A\u300C\u4E00\u4E2A CCC \u7ED1\u5B9A\u591A\u4E2A\u8D26\u6237\u5F88\u5E38\u89C1\u300D\uFF09\u3002\n\n- **\u8D26\u53F7**\u5728 `im.accounts[]`\uFF0C\u6BCF\u6761\u81EA\u5E26 `channel`\uFF08\u7F3A\u7701 `weixin`\uFF09\n- **\u8DEF\u7531\u662F\u901A\u9053\u65E0\u5173\u7684**\uFF08`user \u2192 trajectory`\uFF09\uFF1B\u540C\u4E00\u8F68\u8FF9\u53EF\u4EE5\u88AB\u591A\u4E2A\u6E20\u9053\u7684\u5BF9\u7AEF\u6307\u5411\n- `contacts` \u7684\u6BCF\u6761\u5E26 `accountIds`\uFF08\u8BE5\u5BF9\u7AEF\u88AB\u54EA\u4E9B\u8D26\u53F7\u6536\u8FC7\uFF09\u2014\u2014**\u591A\u8D26\u53F7\u9760\u5B83\u533A\u5206**\n- \u53D1\u6D88\u606F\u65F6\u53EF\u7528 `account` \u53C2\u6570\u6307\u5B9A\u7528\u54EA\u4E2A\u8D26\u53F7\uFF08\u7F3A\u7701\uFF1A\u7B2C\u4E00\u4E2A\u542F\u7528\u4E14\u5DF2\u7ED1\u5B9A\u7684\uFF09\n\n## \u4E94\u3001\u804A\u5929\u8BB0\u5F55\u843D\u5728\u54EA\n\n```\nAGENT_SESSIONS/_im-logs/<channel>/<YYYY-MM-DD>.jsonl\n```\n\n- \u6309**\u6E20\u9053**\u5206\u76EE\u5F55\uFF08`weixin` / `feishu`\uFF09\uFF0C\u6309**\u672C\u5730\u81EA\u7136\u5929**\u5206\u6587\u4EF6\uFF0C\u9010\u884C JSON \u8FFD\u52A0\n- \u8001\u8DEF\u5F84 `AGENT_SESSIONS/_weixin-logs/` \u662F**\u540C\u4E00\u65CF\u7684\u524D\u4EE3**\uFF082026-10-22 \u4E4B\u524D\u7684\u5FAE\u4FE1\u8BB0\u5F55\uFF09\uFF1B\n \u8BFB\u4FA7**\u4E24\u4E2A\u90FD\u8BA4**\uFF0C\u26D4 \u4F46**\u53EA\u5F80\u65B0\u8DEF\u5F84\u5199**\n- \u6BCF\u6761\u4E8B\u4EF6\u5E26 `channel` \u5B57\u6BB5 \u21D2 \u98DE\u4E66\u4E8B\u4EF6\u4E0D\u4F1A\u518D\u6DF7\u8FDB\u5FAE\u4FE1\u76EE\u5F55\uFF08\u6B64\u524D\u4F1A\uFF0C\u76EE\u5F55\u540D\u4F1A\u8BF4\u8C0E\uFF09\n- \u5A92\u4F53\u7684\u6301\u4E45\u526F\u672C\u5728 `_im-logs/<channel>/media/<\u65E5\u671F>/`\n\n\u26A0\uFE0F **\u8BB0\u5F55\u5F52 CCC**\uFF08`im.hook` \u6307\u5411\u672C\u5BB9\u5668\u7684\u811A\u672C\uFF09\uFF0CACC \u4E0D\u8BA4\u8BC6\u5B58\u50A8\u683C\u5F0F \u2014\u2014 \u8FD9\u5C42\u5206\u79BB\u662F\u6709\u610F\u7684\u3002\n\n## \u516D\u3001\u51FA\u9519\u65F6\n\n\u5DE5\u5177\u8FD4\u56DE**\u7A33\u5B9A\u9519\u8BEF\u7801 + \u53EF\u884C\u52A8\u63D0\u793A**\uFF0C\u5E38\u89C1\u7684\uFF1A\n\n| \u7801 | \u610F\u601D | \u600E\u4E48\u6539 |\n|---|---|---|\n| `CHANNEL_NOT_CONFIGURED` | \u672C CCC \u6CA1\u542F\u7528\u8BE5\u6E20\u9053 | \u770B\u63D0\u793A\u91CC\u7684**\u90A3\u4E00\u534A**\uFF08\u603B\u5F00\u5173 vs \u7F3A\u8D26\u53F7\uFF0C\u4E24\u8005\u6539\u6CD5\u4E0D\u540C\uFF09 |\n| `ALIAS_UNKNOWN` | `user` \u65E2\u4E0D\u662F\u5DF2\u914D\u522B\u540D\u3001\u4E5F\u4E0D\u662F\u5408\u6CD5\u7684\u88F8 id | \u5148 `action=users` \u770B\u522B\u540D\uFF0C\u6216\u7528 `contacts` \u62FF\u88F8 id |\n| `ACTION_UNSUPPORTED` | \u8BE5\u6E20\u9053\u6CA1\u5B9E\u73B0\u8FD9\u4E2A\u52A8\u4F5C | \u6362\u52A8\u4F5C\uFF1B\u26D4 \u4E0D\u8981\u91CD\u8BD5 |\n| `FILE_ESCAPE` | `file` \u8DD1\u51FA\u4E86 CCC \u6839 | \u7528\u5BB9\u5668\u5185\u76F8\u5BF9\u8DEF\u5F84 |\n\n## \u4E03\u3001\u8FB9\u754C\uFF08\u660E\u786E\u4E0D\u505A\uFF09\n\n- \u26D4 \u8DE8 CCC \u53D1\u9001\uFF08\u53EA\u4F5C\u7528\u4E8E\u672C\u5BB9\u5668\uFF09\n- \u26D4 \u4FEE\u6539\u914D\u7F6E\uFF08`contacts` \u53EA\u8BFB\uFF1B\u52A0\u8DEF\u7531\u8981\u4EBA/CCC \u81EA\u5DF1\u5199 `serenity.json`\uFF09\n- \u26D4 \u5224\u65AD\"\u5BF9\u65B9\u8BFB\u6CA1\u8BFB\"\u2014\u2014\u8BB0\u5F55\u53EA\u8BC1\u660E**\u5DF2\u53D1\u51FA**\n";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@shgroup/dsh-serenity-hooks",
3
- "version": "1.85.0",
3
+ "version": "1.87.0",
4
4
  "description": "宁静号 ACC harness(Native Cordis 插件)——给 DeepSeek Harness 装一个「AI 工作区」:14 个工具(container_fs/cognitive_trajectory/dashboard/container_git/msm/praxis/ontology/serenity-skill-creator/cheap-worker/localstore/container_admin;另三个 im-bridge/acc-diag/diagram 按配置条件出现)+ 机械约束(安全模式/工作区围墙/密钥守卫/对外输出守卫)+ 认知轨迹日志与原地重建 + 网页登录入口/IM 桥/认知轨迹唤醒注册表。轨迹声明 = 轨迹目录下的 trajectory.json(约束面 + 旧标识 uid + CRO 机器绑定 cro.host;旧名 trajectory-constraint.json / skiff.json 仍在读取族里);轨迹提示词 = 轨迹目录下的 PROMPT.md(一条轨迹「是谁、按什么章程工作」,所有轨迹通用,追加在容器层与质量层之后,不替换它们)。适配 DSH 0.1.7-rc.1 / 0.2.0-rc.1 / 0.2.1-rc.1(三档)。",
5
5
  "license": "MIT",
6
6
  "repository": {