@shgroup/dsh-serenity-hooks 1.73.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-CZUOq3NY.js → ccc-CyYJL4F2.js} +9 -9
- package/lib/{ccc-roots-BhoA7G-0.js → ccc-roots-B2t_aM8L.js} +4 -4
- package/lib/client.js +116 -1
- package/lib/cro/core.d.ts +1 -1
- package/lib/guard/seam.d.ts +4 -4
- package/lib/host/effect.d.ts +1 -1
- package/lib/im/bridge.d.ts +3 -3
- package/lib/im/endpoint.d.ts +2 -2
- package/lib/im/guard.d.ts +1 -1
- package/lib/im/hook.d.ts +1 -1
- package/lib/im/inbound-event.d.ts +6 -6
- package/lib/im/route.d.ts +25 -3
- package/lib/index.d.ts +5 -5
- package/lib/index.js +360 -265
- package/lib/kernel/ccc-roots.d.ts +4 -4
- package/lib/kernel/ccc.d.ts +19 -19
- package/lib/kernel/ports.d.ts +1 -1
- package/lib/{message-source-ChyVfev9.js → message-source-DCpoRHP3.js} +2 -2
- package/lib/patches/opencode-provider.d.ts +1 -1
- package/lib/{profile-BIQ2widI.js → profile-BVt84mbi.js} +59 -59
- package/lib/{route-ItCgAsnz.js → route-COOFLTpU.js} +44 -6
- 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-UcwIPJhM.js → settings-D8hlqxua.js} +40 -26
- package/lib/surface/faces.d.ts +1 -1
- package/lib/surface/http.d.ts +1 -1
- package/lib/surface/settings.d.ts +7 -7
- package/lib/surface/text.d.ts +3 -3
- 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/trajectory/carrier.d.ts +1 -1
- package/lib/trajectory/core.d.ts +1 -1
- package/lib/trajectory/policy/profile.d.ts +36 -36
- package/lib/trajectory/policy/rules.d.ts +33 -20
- package/lib/trajectory/policy/window.d.ts +13 -13
- package/lib/trajectory/prompt.d.ts +12 -12
- package/lib/trajectory/rebuild.d.ts +2 -2
- package/lib/trajectory/session.d.ts +49 -49
- package/package.json +2 -2
- package/lib/tools/skiff-admin.d.ts +0 -30
package/lib/index.js
CHANGED
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
import { i as __toESM, n as __exportAll } from "./rolldown-runtime-B4iAMlE-.js";
|
|
2
|
-
import { c as hostInjected, d as hostSessions, f as hostSettings, h as hostWebServer, i as cccRootForExec, l as hostLlm, m as hostWeb, n as agentCwdFor, o as listCccs, p as hostSubagents, r as cccRootForCwd, s as hostAgents, t as NO_CCC_FROM_AGENT_CWD, u as hostService } from "./ccc-roots-
|
|
3
|
-
import { _ as resolveInside, a as findSerenityRoot$1, c as matchBlacklist, d as readCccName$1, f as readCheapWorkerConfig, g as readUtf8, h as readSafeModeHiddenTools, i as findGitRoot, l as pathInside, m as readExclusiveTools, n as SAFE_MODE_MARKER, o as isSafeModeOn, p as readCognitiveTrajectorySkills, r as classifyPath, s as loadSerenityConfig, t as DEFAULT_SERENITY_CONFIG_PATHS, u as readBlacklist } from "./ccc-
|
|
4
|
-
import { A as sessionsRoot, C as namingTitleFor, D as readTrajectoryUid, E as readActiveSessionMd, F as isoLocal, I as localFileStamp, L as localHuman, M as showSession, N as summarize, O as resolveSessionByTitle, P as useSession, R as localIdStamp, S as listSessions, T as parseTrajectoryCode, _ as extractSessionMdPathFromText, a as
|
|
5
|
-
import { _ as runLocalStore, a as readFeishuCredential, d as weixinSessionIdForTrajectory, f as LOCALSTORE_SCOPES, g as readStore, h as readGitTrack, i as matchWeixinRoute, l as sanitizeFileName$1, m as localstorePath, n as extractWeixinText, o as readWeixinCredential, p as checkLocalstoreGitCompliance, r as hasVoiceItem, s as readWeixinSettings, t as extractWeixinMedia, u as weixinInboundDir } from "./route-
|
|
6
|
-
import { a as
|
|
7
|
-
import { a as pruneMissingBindings, c as setBindingStore, i as listBoundSessionIds, n as PLUGIN_SOURCE, o as readLastBound, r as appendBound, s as resolveSessionCognitiveTrajectoryLabel, t as ACC_MESSAGE_KIND } from "./message-source-
|
|
2
|
+
import { c as hostInjected, d as hostSessions, f as hostSettings, h as hostWebServer, i as cccRootForExec, l as hostLlm, m as hostWeb, n as agentCwdFor, o as listCccs, p as hostSubagents, r as cccRootForCwd, s as hostAgents, t as NO_CCC_FROM_AGENT_CWD, u as hostService } from "./ccc-roots-B2t_aM8L.js";
|
|
3
|
+
import { _ as resolveInside, a as findSerenityRoot$1, c as matchBlacklist, d as readCccName$1, f as readCheapWorkerConfig, g as readUtf8, h as readSafeModeHiddenTools, i as findGitRoot, l as pathInside, m as readExclusiveTools, n as SAFE_MODE_MARKER, o as isSafeModeOn, p as readCognitiveTrajectorySkills, r as classifyPath, s as loadSerenityConfig, t as DEFAULT_SERENITY_CONFIG_PATHS, u as readBlacklist } from "./ccc-CyYJL4F2.js";
|
|
4
|
+
import { A as sessionsRoot, C as namingTitleFor, D as readTrajectoryUid, E as readActiveSessionMd, F as isoLocal, I as localFileStamp, L as localHuman, M as showSession, N as summarize, O as resolveSessionByTitle, P as useSession, R as localIdStamp, S as listSessions, T as parseTrajectoryCode, _ as extractSessionMdPathFromText, a as readTrajectoryConstraintAt, c as registerTrajectorySession$1, d as COGNITIVE_TRAJECTORY_ACTIONS, f as DEFAULT_SESSION_SCOPE, g as createSession, h as clearActiveSessionInfo, j as setActiveSessionInfo, k as sessionEvents$1, l as trajectoryDirNameForSession, m as archiveSession, n as isTrajectoryConstrained, o as readTrajectoryConstraintByCode, p as UID_FILENAME, r as listTrajectoryConstraints, s as trajectoryConstraintForSession, t as TRAJECTORY_CONSTRAINT_FILENAME, u as unregisterTrajectorySession$1, v as findSession, w as parseSessionContextFromEvents, x as listActiveTrajectories, y as getActiveSessionInfo } from "./profile-BVt84mbi.js";
|
|
5
|
+
import { _ as runLocalStore, a as readFeishuCredential, d as weixinSessionIdForTrajectory, f as LOCALSTORE_SCOPES, g as readStore, h as readGitTrack, i as matchWeixinRoute, l as sanitizeFileName$1, m as localstorePath, n as extractWeixinText, o as readWeixinCredential, p as checkLocalstoreGitCompliance, r as hasVoiceItem, s as readWeixinSettings, t as extractWeixinMedia, u as weixinInboundDir } from "./route-COOFLTpU.js";
|
|
6
|
+
import { a as buildTrajectoryBasePrompt, c as createProfilePromptReader, d as resolveConstraintSystemPrompt, f as ACC_VERSION, i as TRAJECTORY_CONSTRAINT_HINT, l as profileMsmWhitelist, n as readSimpleSettings, o as constraintCognitiveTrajectory, p as findPackageRoot, r as registerSettingsSection, s as constraintSystemPromptSource, u as profileToolWhitelist } from "./settings-D8hlqxua.js";
|
|
7
|
+
import { a as pruneMissingBindings, c as setBindingStore, i as listBoundSessionIds, n as PLUGIN_SOURCE, o as readLastBound, r as appendBound, s as resolveSessionCognitiveTrajectoryLabel, t as ACC_MESSAGE_KIND } from "./message-source-DCpoRHP3.js";
|
|
8
8
|
import { REGISTERED_TOOLS } from "./invariant.js";
|
|
9
9
|
import { c as sendTextMessage, i as getUpdates, l as sendTyping, n as downloadMedia, o as markdownToPlainText, r as getConfig, s as sendFileMessage, t as TypingStatus, u as sniffImageExt } from "./ilink-BMk2w2KY.js";
|
|
10
|
+
import { a as registerImAccountImpl, i as normalizeAccountChannel, n as getImAccountImpl, r as imAccountChannels } from "./accounts-BHQbakUh.js";
|
|
10
11
|
import { createRequire } from "node:module";
|
|
11
12
|
import z from "@deepseek-ai/schemastery";
|
|
12
13
|
import { defineTool } from "@deepseek-ai/dsh-tools";
|
|
@@ -977,8 +978,8 @@ const gitTool = defineTool({
|
|
|
977
978
|
*
|
|
978
979
|
* 为什么单独一个模块(R↓):地址的读者是 `msm-ops.buildMsmEnv`(把地址注入 MSM 子进程 env),
|
|
979
980
|
* 写者才是 `weixin-send-api`(监听器启停)。若 msm-ops 直接 import weixin-send-api,
|
|
980
|
-
* 静态依赖链会拖进整条微信桥栈(weixin-bridge →
|
|
981
|
-
* 后果实测:`acc-extras` / `ops` / `
|
|
981
|
+
* 静态依赖链会拖进整条微信桥栈(weixin-bridge → trajectory-session → `@deepseek-ai/dsh-llm`)——
|
|
982
|
+
* 后果实测:`acc-extras` / `ops` / `trajectory-constraint-admin` 三个测试套件因解析不到宿主包而**整文件加载失败**
|
|
982
983
|
* (它们只用到 MSM 注册表逻辑)。叶模块把「读」与「写」解耦:读者零依赖,写者自己承担重量。
|
|
983
984
|
*
|
|
984
985
|
* 同款先例:`host/effect.ts`(拆卸登记)、`trajectory/carrier.ts`(等待结算)。
|
|
@@ -1083,18 +1084,19 @@ Directory of ACC capabilities — each area lists where to go for details (guide
|
|
|
1083
1084
|
acc_msm 已并入 msm(执行+发现)与 container_admin(管理)——msm("<name>", ["<args>"]);inspect=true 查用法
|
|
1084
1085
|
cheap-worker(杂工 agent 编排——guide 子命令:cheap-worker guide)
|
|
1085
1086
|
专属工具(**默认对所有 CCC 隐藏**,只有在自己 exclusiveTools 里点名的 CCC 可见)→ acc-diag(运行态诊断)
|
|
1086
|
-
④
|
|
1087
|
-
|
|
1087
|
+
④ 轨迹约束与对外面 → container_admin config(轨迹约束四动作 guide/validate/apply/list——**原 role 域已并入 config**,旧名作过渡别名仍可用;说明类配置走**两层菜单** config docs 目录 / config docs <章节> 详情)
|
|
1088
|
+
外部程序化接入用官方 dsh --profile acp
|
|
1088
1089
|
+ 现成 ACP 客户端;本插件不提供 ACP 面
|
|
1089
1090
|
⑤ 自主与接入 → cognitive_trajectory send-now(跨轨迹的**即时**递送,**不排队**:live 目标在跑就当场注入当前轮、空闲则立即起轮;非 live 冷载入——等效于直接 wake;**有同步回执**)
|
|
1090
1091
|
cognitive_trajectory send-later(跨轨迹的"未来时刻 + 一条 message";fire-and-forget,无回执无回收)
|
|
1091
1092
|
weixin 微信桥(配置/扫码/路由/消息 hook——CCC 侧 weixin-doctor MSM:msm("weixin-doctor", ["guide"]))
|
|
1092
|
-
⑥ CCC 配置总览 → container_admin
|
|
1093
|
+
⑥ CCC 配置总览 → container_admin config docs config(按章详情——9 段全表)
|
|
1094
|
+
或 container_admin msm ccc-config(同一份正文的旧入口,保留)
|
|
1093
1095
|
⑦ 注册表与安全 → mech-registry.json 由 container_admin msm register/deregister 管理(写保护——不可直接编辑)
|
|
1094
1096
|
dashboard health 输出 registry 完整性检查;损坏恢复指引见 health registry.issues
|
|
1095
1097
|
CCC 级开关:exclusiveTools(声明专属工具)/ safeMode.blacklist(写路径黑名单)
|
|
1096
1098
|
/ safeMode.hiddenTools(**安全模式开启期间**额外隐藏的工具,v1.51.4)
|
|
1097
|
-
CCC 级 skill 供给:cognitiveTrajectory.skills(v1.44.0——**本 CCC 每条轨迹都带**,含
|
|
1099
|
+
CCC 级 skill 供给:cognitiveTrajectory.skills(v1.44.0——**本 CCC 每条轨迹都带**,含 轨迹约束会话;与轨迹自己的 frontmatter 声明取并集)
|
|
1098
1100
|
|
|
1099
1101
|
每区:一句话定位 + 详细入口(工具名 / guide 子命令)。详情永远以对应工具的 guide/ccc-config 为单一真相源。
|
|
1100
1102
|
`;
|
|
@@ -1151,7 +1153,12 @@ msm("<name>", ["--list"]) — list all MSMs
|
|
|
1151
1153
|
msm("<name>", ["--schema", "<n>"]) — view a specific MSM's parameter schema
|
|
1152
1154
|
msm("<name>", ["--format=json"]) — JSON output mode (remaining args passed through losslessly)
|
|
1153
1155
|
`;
|
|
1154
|
-
/**
|
|
1156
|
+
/**
|
|
1157
|
+
* CCC 配置参考(对齐 osp ccc-config action)。
|
|
1158
|
+
*
|
|
1159
|
+
* 🔴 **2026-10-19 起导出**:`container_admin config docs config` 的两层菜单把它作为
|
|
1160
|
+
* 「config」章正文返回。**不另写一份**——同一段说明有第二份副本就会漂(本轨反复踩过)。
|
|
1161
|
+
*/
|
|
1155
1162
|
const CCC_CONFIG_REFERENCE = `═══ CCC Configuration Reference ═══
|
|
1156
1163
|
|
|
1157
1164
|
CCC-level features are configured in .opencode/serenity.json.
|
|
@@ -1227,15 +1234,15 @@ safe-mode 由 WebUI 开关控制(写 .serenity-safe-on 标记);黑名单
|
|
|
1227
1234
|
Config:
|
|
1228
1235
|
{ "safeMode": { "blacklist": [".secrets/"], "hiddenTools": ["workflow", "job_kill"] } }
|
|
1229
1236
|
|
|
1230
|
-
── 6.
|
|
1231
|
-
|
|
1237
|
+
── 6. 轨迹约束.roles(F4 认知子集角色)──
|
|
1238
|
+
轨迹约束角色 = 全知全能 cognitive trajectory 的任意子集(CCC 定义)。每角色独立模型 + 双白名单
|
|
1232
1239
|
(MSM 白名单 msms[] 与非 MSM 工具白名单 tools[],白名单外全隐藏;skill 加载恒可用)。
|
|
1233
1240
|
cognitive trajectory 纪律子集(session/keeper/rebuild 参与项)默认全关 = 完全独立。
|
|
1234
1241
|
systemPrompt 内联 或 systemPromptFile(.md 引用,推荐)——角色会话的完整人格/边界提示词。
|
|
1235
|
-
validate 校验 / apply 生效 / list 查看:container_admin
|
|
1242
|
+
validate 校验 / apply 生效 / list 查看:container_admin config <guide|validate|apply|list>(原 role 域已并入 config,旧名作过渡别名仍可用)。
|
|
1236
1243
|
|
|
1237
1244
|
Config:
|
|
1238
|
-
{ "
|
|
1245
|
+
{ "轨迹约束": { "roles": {
|
|
1239
1246
|
"qa": {
|
|
1240
1247
|
"model": "provider/model",
|
|
1241
1248
|
"msms": ["web-search", "vlm-describe"],
|
|
@@ -1255,12 +1262,12 @@ CCC 的一条 cognitive trajectory = 持久身体(SESSION.md)+ 时间轴(A
|
|
|
1255
1262
|
运行态诊断(ACC 负责人专用):acc-diag(① live 运行态 / ①b 唤醒时钟 / ③ 唤醒注册表)。
|
|
1256
1263
|
|
|
1257
1264
|
▸ cognitiveTrajectory.skills(v1.44.0,**CCC 级 skill 供给**;owner 令 2026-09-20「要求实现我的诉求……
|
|
1258
|
-
注入是动态注入就行;create
|
|
1265
|
+
注入是动态注入就行;create 先不触发吧;**轨迹约束也支持**」):
|
|
1259
1266
|
**本 CCC 的每条轨迹都带这些 skill**——声明的 skill 全文在该轨迹**被绑定期间**注入系统提示词
|
|
1260
1267
|
(**动态注入**:每请求重新求值、不缓存 ⇒ 改配置或改 frontmatter **即时生效**,无需重新 use、无需重启)。
|
|
1261
1268
|
两条声明来源**并集**(不是二选一):本键 **cognitiveTrajectory.skills**(容器给的底座,**在前**)
|
|
1262
1269
|
+ 轨迹自己 SESSION.md 顶部 frontmatter 的 skills:(这条轨迹额外的,**追加**;同名去重)。
|
|
1263
|
-
✅ **含
|
|
1270
|
+
✅ **含 轨迹约束角色会话**(旁路的是 ACC 身份与纪律;skill 是"工作资料",照给)。
|
|
1264
1271
|
守卫:名字先过安全校验才用于拼路径(防穿越)|**合并后**统一 32 KB 上限截断|找不到响亮写 [缺失]。
|
|
1265
1272
|
⚠️ 与 create **无关**(create 刻意不夺绑定,U6)⇒ 注入的门是"**存在绑定**"(cognitive_trajectory use)。
|
|
1266
1273
|
失败语义:未配置 / 字段缺失 / 配置损坏 ⇒ **空数组**(= 无 CCC 级供给;轨迹级声明照旧生效)。
|
|
@@ -1268,7 +1275,7 @@ CCC 的一条 cognitive trajectory = 持久身体(SESSION.md)+ 时间轴(A
|
|
|
1268
1275
|
|
|
1269
1276
|
Config:
|
|
1270
1277
|
{ "cognitiveTrajectory": {
|
|
1271
|
-
"skills": ["acc-session", "cce"], // v1.44.0:本 CCC 的每条轨迹都带(含
|
|
1278
|
+
"skills": ["acc-session", "cce"], // v1.44.0:本 CCC 的每条轨迹都带(含 轨迹约束会话)
|
|
1272
1279
|
"autopilot": { // 原 autopilotTrajectory 段,字段不变(旧键仍回退读)
|
|
1273
1280
|
"enabled": true,
|
|
1274
1281
|
"intervalHours": 2,
|
|
@@ -1282,7 +1289,7 @@ CCC 的一条 cognitive trajectory = 持久身体(SESSION.md)+ 时间轴(A
|
|
|
1282
1289
|
CCC 级微信个人号接入(iLink 协议):dsh 一进程多 CCC,每 CCC 独立对接微信桥。
|
|
1283
1290
|
账号/路由/开关在此文件;**bot_token 凭据在 CCC localstore credential scope**
|
|
1284
1291
|
(扫码绑定后自动写入,永不进 git 明文面)。
|
|
1285
|
-
路由 user → role:exact 优先,* 通配兜底;role 必须 ∈ 该 CCC
|
|
1292
|
+
路由 user → role:exact 优先,* 通配兜底;role 必须 ∈ 该 CCC 轨迹约束.roles。
|
|
1286
1293
|
面板(WebUI 设置 → 微信桥)可扫码绑定/移除账号/编辑路由;msm("weixin-doctor", ["status"|"diag"|"verify"|"guide"]) 排查 + 大而全指南。凭据主动查看:localstore get WEIXIN_<ACCOUNT>_TOKEN。
|
|
1287
1294
|
▸ 主动发送(v1.31.0:**ACC 工具 im-bridge**):调
|
|
1288
1295
|
im-bridge({channel:"weixin", action:"send", user:"<别名|id>", text:"<文本>"}) 给用户发消息——
|
|
@@ -1290,7 +1297,7 @@ CCC 级微信个人号接入(iLink 协议):dsh 一进程多 CCC,每 CCC
|
|
|
1290
1297
|
成功后触发 outgoing hook(source="proactive")。文件用 action:"send-file" + file:"<CCC 内路径>"
|
|
1291
1298
|
(逃逸/缺失/超 20MB 拒绝)+ 可选 caption。**可见性**:本 CCC 配置了 IM 通道
|
|
1292
1299
|
(weixin.enabled=true)才出现在模型工具清单里;未配置 → 由 guards 逐 agent
|
|
1293
|
-
tools.restrict({deny:['im-bridge']}) 移除(不是"看得到但被拒"
|
|
1300
|
+
tools.restrict({deny:['im-bridge']}) 移除(不是"看得到但被拒")。轨迹约束角色另需在
|
|
1294
1301
|
roles.<role>.tools 白名单列出 im-bridge。
|
|
1295
1302
|
历史(v1.30.9~v1.30.x):CCC 侧 MSM weixin-send(走 loopback HTTP 入口 3082)——
|
|
1296
1303
|
v1.31.0 已退役(脚本删除 + 注册表注销);3082 入口保留给**非 agent 调用者**。
|
|
@@ -1834,7 +1841,7 @@ async function runMsmAsync(root, args) {
|
|
|
1834
1841
|
* **轨迹提示词** = 一条认知轨迹的**可编辑章程声明**,由 ACC 按固定顺序装配进系统提示词。
|
|
1835
1842
|
* 三个关键词各是一条判据:
|
|
1836
1843
|
* - **可编辑**:住在 CCC(盘上),改它**不发版**(D23 归属判据的直接推论)
|
|
1837
|
-
* - **声明**:它声明**身份/章程/方法论**,**不声明能力**(能力面归 `
|
|
1844
|
+
* - **声明**:它声明**身份/章程/方法论**,**不声明能力**(能力面归 `trajectory-constraint.json` 白名单与工具 schema)
|
|
1838
1845
|
* - **固定顺序**:装配顺序由 ACC 定死(见 `seams/system-prompt.ts` 的 ⑧ 位)
|
|
1839
1846
|
*
|
|
1840
1847
|
* ## 为什么取代「人格」
|
|
@@ -1849,13 +1856,13 @@ async function runMsmAsync(root, args) {
|
|
|
1849
1856
|
*
|
|
1850
1857
|
* - **跟着轨迹走**:`archive` 时整个目录搬走,章程不会掉队(旧形态 `.opencode/skiff/<name>.md`
|
|
1851
1858
|
* 与轨迹**分居两处** —— 多条轨迹共用一份文件正是那个后果)。
|
|
1852
|
-
* - **每条轨迹都能有**:不只是
|
|
1853
|
-
* - 与 `SESSION.md`(身体:延续/日志)、`
|
|
1859
|
+
* - **每条轨迹都能有**:不只是 轨迹约束。普通轨迹同样需要"我是谁、按什么章程"。
|
|
1860
|
+
* - 与 `SESSION.md`(身体:延续/日志)、`trajectory-constraint.json`(约束面:能力)三件并列,
|
|
1854
1861
|
* 各管一件事,不合并(合并会让"改能力"与"改身份"互相牵连)。
|
|
1855
1862
|
*
|
|
1856
1863
|
* ## 兼容(不静默坏)
|
|
1857
1864
|
*
|
|
1858
|
-
* `PROMPT.md`
|
|
1865
|
+
* `PROMPT.md` 不存在时**不报错**(普通轨迹常态)。轨迹约束的旧字段 `systemPromptFile`
|
|
1859
1866
|
* 在兼容期仍被读取——优先级见 {@link resolveTrajectoryPrompt}。
|
|
1860
1867
|
*/
|
|
1861
1868
|
/** 轨迹提示词的固定文件名(**判据就是它存在**——不是目录名前缀,也不是配置里的一行) */
|
|
@@ -1867,9 +1874,9 @@ function trajectoryPromptPath(root, dirName) {
|
|
|
1867
1874
|
/**
|
|
1868
1875
|
* 该轨迹**有没有**轨迹提示词(单一判据)。
|
|
1869
1876
|
*
|
|
1870
|
-
* 用途:兼容期的"让位"判定 —— 若已有 `PROMPT.md
|
|
1871
|
-
* 必须**让位**,否则同一份章程会在两处各注入一遍(第 ⑧ 段 + `serenity
|
|
1872
|
-
* 🔴 判据只有这一处:别在
|
|
1877
|
+
* 用途:兼容期的"让位"判定 —— 若已有 `PROMPT.md`,轨迹约束 旧字段 `systemPromptFile`
|
|
1878
|
+
* 必须**让位**,否则同一份章程会在两处各注入一遍(第 ⑧ 段 + `serenity-轨迹约束` 段)。
|
|
1879
|
+
* 🔴 判据只有这一处:别在 轨迹约束那边自己拼路径再判(两处会漂)。
|
|
1873
1880
|
*/
|
|
1874
1881
|
function hasTrajectoryPrompt(root, dirName) {
|
|
1875
1882
|
try {
|
|
@@ -2903,7 +2910,7 @@ function verifyTotpCode(secretBase32, code, nowSeconds = nowEpochSeconds()) {
|
|
|
2903
2910
|
/**
|
|
2904
2911
|
* 集中端口表。
|
|
2905
2912
|
* A=宿主主面(插件不监听)/ B=网关(可配,默认关)/ C=微信发送(默认开,仅 loopback)
|
|
2906
|
-
* / D
|
|
2913
|
+
* / D=轨迹约束调试(默认关)。
|
|
2907
2914
|
*/
|
|
2908
2915
|
const FACE_PORTS = {
|
|
2909
2916
|
main: {
|
|
@@ -3263,7 +3270,7 @@ function toolsBlock() {
|
|
|
3263
3270
|
" cheap-worker — delegate work to a worker agent on the CCC-configured model; one serial child per task, returns its final text. `jobs=[...]` fans out in parallel (cap 10, results keep your order). No loop, no completion code, no progress file. The model is NOT selectable per call (read from the CCC config); a worker's tool set excludes cheap-worker itself",
|
|
3264
3271
|
" serenity-skill-creator — creator for serenity-skills (this container's enhanced skill standard): guide (authoring/optimization method — read first) / create <name> (scaffold SKILL.md + skill.ttl + references/, base-chain import path computed) / check <name> (A1 structural + A2 base-chain; three states: modeled-ok / plain-skill / modeled-but-check-could-not-run) / review <name> (C tier: axiom-vs-text compliance question list, half-mechanical) / index <name> (preview the second-layer resource list the model will see) / status (which skills are modeled). A skill is \"modeled\" iff it has a file named exactly skill.ttl next to SKILL.md; a plain skill is NOT an anomaly. Only `create` writes, and it refuses an existing directory",
|
|
3265
3272
|
" localstore — ACC local credential/config storage (CCC-root localstore.json; git policy localstore.gitTrack default deny)",
|
|
3266
|
-
" container_admin — container administration (the maintenance bay): role (
|
|
3273
|
+
" container_admin — container administration (the maintenance bay): role (轨迹约束 roles: guide/validate/apply/list) / msm (register/deregister/check/guide/catalog/ccc-config) / config (view)",
|
|
3267
3274
|
"",
|
|
3268
3275
|
"MSM call (registered CCC MSMs, deterministic Mech & Semi-Mech):",
|
|
3269
3276
|
" Execute: msm(\"<name>\", [\"<arg1>\", \"<arg2>\"]) — run a registered MSM directly (partial name returns matching candidates)",
|
|
@@ -3763,7 +3770,7 @@ function registerEntrySkillSection(agent, root) {
|
|
|
3763
3770
|
*
|
|
3764
3771
|
* **声明来源(两条,并集)**:
|
|
3765
3772
|
* · **CCC 级**(v1.44.0,owner 令)= `.opencode/serenity.json` 的 `cognitiveTrajectory.skills`
|
|
3766
|
-
* ——**本 CCC 的每条轨迹都带**(含
|
|
3773
|
+
* ——**本 CCC 的每条轨迹都带**(含 轨迹约束角色会话),是容器给的底座;
|
|
3767
3774
|
* · **轨迹级** = 该会话绑定的轨迹(`readLastBound(session).mdPath` = 它的 `SESSION.md`)
|
|
3768
3775
|
* 顶部 frontmatter 的 `skills:`——这条轨迹额外的。
|
|
3769
3776
|
* 合并规则与顺序见 `cognitive-trajectory-skills.mergeSkillNames`(CCC 级在前,同名去重)。
|
|
@@ -3960,36 +3967,36 @@ Before calling cheap-worker, load eap (acc-eap skill) and design the "scale-up c
|
|
|
3960
3967
|
//#endregion
|
|
3961
3968
|
//#region src/trajectory/session.ts
|
|
3962
3969
|
/** `provider/model` → agentOptions(由 delegate/core 提供,保持单一实现) */
|
|
3963
|
-
const
|
|
3970
|
+
const trajectoryAgents = /* @__PURE__ */ new Map();
|
|
3964
3971
|
/** 查 sessionId 的活体 agent(进程内会话延续用;未注册/已清理 → undefined) */
|
|
3965
|
-
function
|
|
3966
|
-
return
|
|
3972
|
+
function getTrajectoryAgent(sessionId) {
|
|
3973
|
+
return trajectoryAgents.get(sessionId);
|
|
3967
3974
|
}
|
|
3968
|
-
/** 该会话当前绑定的
|
|
3969
|
-
function
|
|
3975
|
+
/** 该会话当前绑定的 轨迹约束面(不是 轨迹约束 → null)。**守门与提示词的唯一查询入口。** */
|
|
3976
|
+
function trajectoryConstraintFor(root, sessionId) {
|
|
3970
3977
|
if (sessionId === void 0) return null;
|
|
3971
|
-
return
|
|
3978
|
+
return trajectoryConstraintForSession(root, sessionId, trajectoryDirNameForSession(sessionId));
|
|
3972
3979
|
}
|
|
3973
|
-
function
|
|
3974
|
-
|
|
3975
|
-
|
|
3980
|
+
function registerTrajectorySession(sessionId, trajectoryDirName, ccc, agent) {
|
|
3981
|
+
trajectoryAgents.set(sessionId, agent);
|
|
3982
|
+
registerTrajectorySession$1(sessionId, trajectoryDirName, ccc);
|
|
3976
3983
|
}
|
|
3977
|
-
function
|
|
3978
|
-
|
|
3979
|
-
|
|
3984
|
+
function unregisterTrajectorySession(sessionId) {
|
|
3985
|
+
trajectoryAgents.delete(sessionId);
|
|
3986
|
+
unregisterTrajectorySession$1(sessionId);
|
|
3980
3987
|
}
|
|
3981
3988
|
/**
|
|
3982
|
-
* 该
|
|
3989
|
+
* 该 轨迹约束会话的**工作台路径** = 它自己轨迹的 SESSION.md。
|
|
3983
3990
|
*
|
|
3984
|
-
* 🔴 与旧版的差别:旧版会在这里**自动新建**一个 SESSION(`
|
|
3985
|
-
* 新版**没有新建这个动作** —— 轨迹就是工作台,`
|
|
3991
|
+
* 🔴 与旧版的差别:旧版会在这里**自动新建**一个 SESSION(`ensureTrajectorySession`);
|
|
3992
|
+
* 新版**没有新建这个动作** —— 轨迹就是工作台,`trajectory-constraint.json` 与 `SESSION.md` 同处一个目录。
|
|
3986
3993
|
* ⇒ 语义变成纯粹的"查询:我自己的身体在哪"。
|
|
3987
3994
|
*
|
|
3988
|
-
* @returns SESSION.md 绝对路径;不是
|
|
3995
|
+
* @returns SESSION.md 绝对路径;不是 轨迹约束 / 文件不在 → null
|
|
3989
3996
|
*/
|
|
3990
|
-
function
|
|
3997
|
+
function trajectoryWorkspaceMdPath(root, sessionId) {
|
|
3991
3998
|
if (sessionId === void 0) return null;
|
|
3992
|
-
const dirName =
|
|
3999
|
+
const dirName = trajectoryDirNameForSession(sessionId);
|
|
3993
4000
|
if (dirName === null) return null;
|
|
3994
4001
|
const md = join(sessionsRoot(root), dirName, "SESSION.md");
|
|
3995
4002
|
return existsSync(md) ? md : null;
|
|
@@ -3997,12 +4004,12 @@ function skiffWorkspaceMdPath(root, sessionId) {
|
|
|
3997
4004
|
/**
|
|
3998
4005
|
* 系统提示词中的**工作台纪律块**(S142 用户:只给路径 LLM 不会主动用)。
|
|
3999
4006
|
*
|
|
4000
|
-
* SESSION = 工作台 ——
|
|
4007
|
+
* SESSION = 工作台 —— 轨迹约束与主舱同一套机制(**零特调**,用户拍板):
|
|
4001
4008
|
* SESSION.md 是持久记忆载体、rebuild 自动从本 SESSION 续接。
|
|
4002
4009
|
* 注入 agent 系统提示词(用户对话面不可见)。
|
|
4003
4010
|
*
|
|
4004
4011
|
* v1.30.13(S142 诊断 D5):写入纪律**不点名具体工具** —— 旧文案写死 "with write/edit",
|
|
4005
|
-
* 而某个
|
|
4012
|
+
* 而某个 轨迹约束的工具白名单里根本没有 write/edit ⇒ 指令指向它没有的工具
|
|
4006
4013
|
* (注入纪律与能力面矛盾,模型只能猜)。现文案改为「用本轨迹被授权的写入通道」。
|
|
4007
4014
|
*/
|
|
4008
4015
|
function workspaceCognitiveTrajectoryLine(mdPath) {
|
|
@@ -4030,10 +4037,10 @@ const workspaceSectionAgents = /* @__PURE__ */ new WeakSet();
|
|
|
4030
4037
|
/**
|
|
4031
4038
|
* 把**工作台纪律块**挂到 agent 的系统提示词段(单一注入点)。
|
|
4032
4039
|
*
|
|
4033
|
-
* 用户原话(S142,2026-09-08):"
|
|
4034
|
-
* 这样就重复,能否微信桥情况下,注入内容直接取
|
|
4040
|
+
* 用户原话(S142,2026-09-08):"微信桥的注入机制,每个用户消息都会注入,轨迹约束本身也会注入,
|
|
4041
|
+
* 这样就重复,能否微信桥情况下,注入内容直接取 轨迹约束的,这样不用配两遍"。
|
|
4035
4042
|
*
|
|
4036
|
-
* 现形态:工作台行**只**经本函数挂系统提示词段(name `serenity
|
|
4043
|
+
* 现形态:工作台行**只**经本函数挂系统提示词段(name `serenity-轨迹约束-workspace`,
|
|
4037
4044
|
* 动态 `text()` 每轮按 scope 读当前活跃 mdPath)。
|
|
4038
4045
|
*
|
|
4039
4046
|
* @returns true = 段已在位;false = 该 agent 挂不上(调用方降级为 question 前缀注入)
|
|
@@ -4045,39 +4052,39 @@ function ensureWorkspacePromptSection(agent, scope, root) {
|
|
|
4045
4052
|
if (typeof api?.section !== "function") return false;
|
|
4046
4053
|
try {
|
|
4047
4054
|
api.section({
|
|
4048
|
-
name: "serenity
|
|
4055
|
+
name: "serenity-轨迹约束-workspace",
|
|
4049
4056
|
order: -55,
|
|
4050
4057
|
text: () => {
|
|
4051
|
-
const mdPath =
|
|
4058
|
+
const mdPath = trajectoryWorkspaceMdPath(root, scope);
|
|
4052
4059
|
return mdPath ? workspaceCognitiveTrajectoryLine(mdPath) : "";
|
|
4053
4060
|
}
|
|
4054
4061
|
});
|
|
4055
4062
|
workspaceSectionAgents.add(agent);
|
|
4056
4063
|
return true;
|
|
4057
4064
|
} catch (err) {
|
|
4058
|
-
console.warn(`[serenity-hooks]
|
|
4065
|
+
console.warn(`[serenity-hooks] 轨迹约束 工作台提示词段注册失败(回退 question 注入): ${String(err?.message ?? err)}`);
|
|
4059
4066
|
return false;
|
|
4060
4067
|
}
|
|
4061
4068
|
}
|
|
4062
|
-
/**
|
|
4063
|
-
const
|
|
4069
|
+
/** 轨迹约束 agent 挂载的 DSH preset(平台工具面由 preset 决定;白名单再按轨迹过滤) */
|
|
4070
|
+
const TRAJECTORY_PRESET = "standard";
|
|
4064
4071
|
/**
|
|
4065
|
-
* 创建(或复用)一个
|
|
4072
|
+
* 创建(或复用)一个 轨迹约束会话。
|
|
4066
4073
|
*
|
|
4067
4074
|
* **固定 sessionId 的 resume-or-create**(v1.27.2):指定 sessionId 且磁盘已有持久化 log →
|
|
4068
4075
|
* `ctx.agents.resume`(DSH 持久化语义:sessionId 即身份,历史 + turn 续号延续);
|
|
4069
4076
|
* 无 log(首次)→ `ctx.agents.create`。不指定 → 随机 id 恒 create(调试页路径)。
|
|
4070
4077
|
*
|
|
4071
|
-
* @param profile 该
|
|
4078
|
+
* @param profile 该 轨迹约束的约束面(来自 `trajectory-constraint.json`)
|
|
4072
4079
|
* @param sessionId 指定会话 id(可选;微信桥等外部面用固定 id 实现"轨迹 ↔ 会话"长期映射)
|
|
4073
4080
|
*/
|
|
4074
|
-
async function
|
|
4075
|
-
if (!ctx.agents) throw new Error("
|
|
4081
|
+
async function createTrajectoryAgent(ctx, root, profile, defaultModel, sessionId) {
|
|
4082
|
+
if (!ctx.agents) throw new Error("轨迹约束: ctx.agents unavailable");
|
|
4076
4083
|
const model = profile.model?.trim() || defaultModel || "";
|
|
4077
|
-
const id = sessionId ??
|
|
4084
|
+
const id = sessionId ?? `轨迹约束-${profile.code}-${randomUUID()}`;
|
|
4078
4085
|
const setup = async (agentCtx) => {
|
|
4079
4086
|
try {
|
|
4080
|
-
await agentCtx.get("agentPresets")?.mount?.(agentCtx,
|
|
4087
|
+
await agentCtx.get("agentPresets")?.mount?.(agentCtx, TRAJECTORY_PRESET);
|
|
4081
4088
|
} catch {}
|
|
4082
4089
|
};
|
|
4083
4090
|
const handle = await createOrResumeAgent(ctx, id, root, model, setup, sessionId !== void 0);
|
|
@@ -4087,7 +4094,7 @@ async function createSkiffAgent(ctx, root, profile, defaultModel, sessionId) {
|
|
|
4087
4094
|
const livePrompt = () => {
|
|
4088
4095
|
let live = profile;
|
|
4089
4096
|
try {
|
|
4090
|
-
const fresh =
|
|
4097
|
+
const fresh = readTrajectoryConstraintAt(join(sessionsRoot(root), profile.dirName), root);
|
|
4091
4098
|
if (fresh) live = fresh;
|
|
4092
4099
|
} catch {}
|
|
4093
4100
|
const wantPath = live.systemPromptFile?.trim();
|
|
@@ -4100,23 +4107,23 @@ async function createSkiffAgent(ctx, root, profile, defaultModel, sessionId) {
|
|
|
4100
4107
|
livePrompt();
|
|
4101
4108
|
try {
|
|
4102
4109
|
agent.ctx.systemPrompt.section({
|
|
4103
|
-
name: "serenity
|
|
4110
|
+
name: "serenity-轨迹约束",
|
|
4104
4111
|
order: -60,
|
|
4105
4112
|
text: () => {
|
|
4106
4113
|
const legacy = hasTrajectoryPrompt(root, profile.dirName) ? "" : livePrompt();
|
|
4107
|
-
return [
|
|
4114
|
+
return [buildTrajectoryBasePrompt(profile.code), legacy].filter(Boolean).join("\n");
|
|
4108
4115
|
}
|
|
4109
4116
|
});
|
|
4110
4117
|
} catch (err) {
|
|
4111
|
-
console.warn(`[serenity-hooks]
|
|
4118
|
+
console.warn(`[serenity-hooks] 轨迹约束 系统提示词注册失败: ${String(err?.message ?? err)}`);
|
|
4112
4119
|
}
|
|
4113
4120
|
if (agent?.session) try {
|
|
4114
4121
|
registerCognitiveTrajectorySkillSection(agent, root);
|
|
4115
4122
|
} catch (err) {
|
|
4116
|
-
console.warn(`[serenity-hooks]
|
|
4123
|
+
console.warn(`[serenity-hooks] 轨迹约束轨迹 skill 注入注册失败(不影响 agent 创建): ${String(err?.message ?? err)}`);
|
|
4117
4124
|
}
|
|
4118
|
-
if (
|
|
4119
|
-
|
|
4125
|
+
if (trajectoryWorkspaceMdPath(root, id) !== null) ensureWorkspacePromptSection(agent, id, root);
|
|
4126
|
+
registerTrajectorySession(id, profile.dirName, root, agent);
|
|
4120
4127
|
return {
|
|
4121
4128
|
handle,
|
|
4122
4129
|
agent,
|
|
@@ -4155,7 +4162,7 @@ async function createOrResumeAgent(ctx, id, root, model, setup, fixedId) {
|
|
|
4155
4162
|
sessionId: id,
|
|
4156
4163
|
meta: {
|
|
4157
4164
|
cwd: root,
|
|
4158
|
-
agentPreset:
|
|
4165
|
+
agentPreset: TRAJECTORY_PRESET
|
|
4159
4166
|
},
|
|
4160
4167
|
setup,
|
|
4161
4168
|
...model ? { agentOptions: splitModel(model) } : {}
|
|
@@ -4170,7 +4177,7 @@ async function createOrResumeAgent(ctx, id, root, model, setup, fixedId) {
|
|
|
4170
4177
|
sessionId: id,
|
|
4171
4178
|
meta: {
|
|
4172
4179
|
cwd: root,
|
|
4173
|
-
agentPreset:
|
|
4180
|
+
agentPreset: TRAJECTORY_PRESET
|
|
4174
4181
|
},
|
|
4175
4182
|
setup,
|
|
4176
4183
|
...model ? { agentOptions: splitModel(model) } : {}
|
|
@@ -4191,14 +4198,14 @@ async function createOrResumeAgent(ctx, id, root, model, setup, fixedId) {
|
|
|
4191
4198
|
if (liveNow) return liveReuseRef(liveNow, setup);
|
|
4192
4199
|
const msg = err instanceof Error ? err.message : String(err);
|
|
4193
4200
|
const stack = err instanceof Error ? err.stack ?? "" : "";
|
|
4194
|
-
console.log(`[serenity-hooks]
|
|
4195
|
-
if (stack) console.log(`[serenity-hooks]
|
|
4201
|
+
console.log(`[serenity-hooks] 轨迹约束 resume 失败降级 create (id=${id}): ${msg}`);
|
|
4202
|
+
if (stack) console.log(`[serenity-hooks] 轨迹约束 resume stack:\n${stack.slice(0, 1200)}`);
|
|
4196
4203
|
return {
|
|
4197
4204
|
...await ctx.agents.create({
|
|
4198
4205
|
sessionId: id,
|
|
4199
4206
|
meta: {
|
|
4200
4207
|
cwd: root,
|
|
4201
|
-
agentPreset:
|
|
4208
|
+
agentPreset: TRAJECTORY_PRESET
|
|
4202
4209
|
},
|
|
4203
4210
|
setup,
|
|
4204
4211
|
...model ? { agentOptions: splitModel(model) } : {}
|
|
@@ -4262,7 +4269,7 @@ async function waitForTurnAssistant(ctx, agent, beforeAssistantCount, opts = {})
|
|
|
4262
4269
|
*
|
|
4263
4270
|
* @param options.timeoutMs 本轮等待上限(测试注入;生产用缺省)
|
|
4264
4271
|
*/
|
|
4265
|
-
async function
|
|
4272
|
+
async function askTrajectory(ctx, agent, question, options) {
|
|
4266
4273
|
const beforeAssistant = countAssistantMessages(agent.session);
|
|
4267
4274
|
agent.followup(createUserMessage({
|
|
4268
4275
|
content: [{
|
|
@@ -4277,29 +4284,29 @@ async function askSkiff(ctx, agent, question, options) {
|
|
|
4277
4284
|
};
|
|
4278
4285
|
}
|
|
4279
4286
|
/**
|
|
4280
|
-
*
|
|
4281
|
-
*
|
|
4287
|
+
* 轨迹约束会话的轨迹纪律参与判定:**不是 轨迹约束恒 true**(正常参与);
|
|
4288
|
+
* 轨迹约束 按 `trajectory-constraint.json` 的 `cognitiveTrajectory` 子集决定。
|
|
4282
4289
|
*
|
|
4283
|
-
* 注册表缺失(进程刚重启、绑定还没读)⇒ 由 `
|
|
4284
|
-
* 两者都不中 ⇒ 判"不是
|
|
4290
|
+
* 注册表缺失(进程刚重启、绑定还没读)⇒ 由 `trajectoryConstraintForSession` 回落持久绑定;
|
|
4291
|
+
* 两者都不中 ⇒ 判"不是 轨迹约束" ⇒ 恒 true(正常参与)。**fail-open 是有意的**:
|
|
4285
4292
|
* 纪律参与项的失败不该让会话失去 keeper/rebuild(那是能力倒退,比多跑一次更糟)。
|
|
4286
4293
|
*/
|
|
4287
|
-
function
|
|
4294
|
+
function trajectoryConstraintParticipates(root, sessionId, key) {
|
|
4288
4295
|
if (sessionId === void 0) return true;
|
|
4289
|
-
const profile =
|
|
4296
|
+
const profile = trajectoryConstraintFor(root, sessionId);
|
|
4290
4297
|
if (profile === null) return true;
|
|
4291
|
-
return
|
|
4298
|
+
return constraintCognitiveTrajectory(profile)[key];
|
|
4292
4299
|
}
|
|
4293
4300
|
/**
|
|
4294
|
-
* `msm` 工具的
|
|
4301
|
+
* `msm` 工具的 轨迹约束门控:非 轨迹约束恒放行;轨迹约束会话——
|
|
4295
4302
|
* exec 非白名单 MSM 拒绝(**不列名单**)、register/deregister 必拒、
|
|
4296
4303
|
* list 白名单过滤、check/guide/ccc-config 只读放行。
|
|
4297
4304
|
*/
|
|
4298
|
-
function
|
|
4305
|
+
function trajectoryConstraintMsmGate(root, sessionId, action, name) {
|
|
4299
4306
|
if (sessionId === void 0) return {};
|
|
4300
|
-
const profile =
|
|
4307
|
+
const profile = trajectoryConstraintFor(root, sessionId);
|
|
4301
4308
|
if (profile === null) return {};
|
|
4302
|
-
if (action === "register" || action === "deregister") return { reject: "register/deregister is not allowed in
|
|
4309
|
+
if (action === "register" || action === "deregister") return { reject: "register/deregister is not allowed in 轨迹约束 sessions" };
|
|
4303
4310
|
if (action === "exec") {
|
|
4304
4311
|
if (!name || !profile.msms.includes(name)) return { reject: "MSM not allowed" };
|
|
4305
4312
|
return {};
|
|
@@ -4386,7 +4393,7 @@ const msmTool = defineTool({
|
|
|
4386
4393
|
if (!root) throw new Error(NO_CCC_FROM_AGENT_CWD);
|
|
4387
4394
|
const sessionId = agentSessionId$1(exec);
|
|
4388
4395
|
const name = args.name;
|
|
4389
|
-
const gate =
|
|
4396
|
+
const gate = trajectoryConstraintMsmGate(root, sessionId, name ? "exec" : "list", name);
|
|
4390
4397
|
if (gate.reject) throw new Error(gate.reject);
|
|
4391
4398
|
if (!name || name.trim() === "") return gate.whitelist ? "MSMs allowed in this role:\n" + [...gate.whitelist].join("\n") : buildMsmIndex(root);
|
|
4392
4399
|
const clean = name.trim();
|
|
@@ -10419,7 +10426,7 @@ function parseCroHostBinding(raw) {
|
|
|
10419
10426
|
* `AGENT_SESSIONS/` 下每个目录查一次 `<目录>/continuous-re-occurrence.ts`。
|
|
10420
10427
|
* · **一次 readdir + 每个目录一次 existsSync**:N 条轨迹的代价是 O(N) 次 `stat`,
|
|
10421
10428
|
* 每 5min 一次 —— 与既有 `listSessions` 同量级,可接受。
|
|
10422
|
-
* · 跳过 **`_` 前缀**(`_archived` / `
|
|
10429
|
+
* · 跳过 **`_` 前缀**(`_archived` / `_轨迹约束-logs` / `_weixin-logs` = 系统与日志目录)
|
|
10423
10430
|
* 与 **`.` 前缀**(隐藏)——它们**不是轨迹**,且扫它们纯属浪费。
|
|
10424
10431
|
* · 🔴 **本函数绝不抛错**(返回 `[]`)——它跑在调度器 tick 内,任何异常都可能影响既有链路
|
|
10425
10432
|
* (设计 §5 铁律)。目录读不到(不存在 / 权限)⇒ `[]` = "没有轨迹启用 CRO",语义正确。
|
|
@@ -12815,20 +12822,20 @@ const localstoreTool = defineTool({
|
|
|
12815
12822
|
}
|
|
12816
12823
|
});
|
|
12817
12824
|
//#endregion
|
|
12818
|
-
//#region src/tools/
|
|
12819
|
-
/**
|
|
12820
|
-
const
|
|
12825
|
+
//#region src/tools/trajectory-constraint-admin.ts
|
|
12826
|
+
/** 轨迹约束 定义教程:概念 / `trajectory-constraint.json` schema / 双白名单 / 轨迹纪律 / 微信路由 */
|
|
12827
|
+
const TRAJECTORY_CONSTRAINT_GUIDE = `═══ 轨迹约束 Guide (2026-10-19 redesigned) ═══
|
|
12821
12828
|
|
|
12822
|
-
**A
|
|
12829
|
+
**A 轨迹约束 IS a cognitive trajectory — the one whose directory contains \`${TRAJECTORY_CONSTRAINT_FILENAME}\`.**
|
|
12823
12830
|
That is the whole rule. No role table, no name registry: put the file there and the trajectory
|
|
12824
|
-
becomes a
|
|
12831
|
+
becomes a 轨迹约束; delete it and it goes back to being an ordinary trajectory (takes effect at once).
|
|
12825
12832
|
|
|
12826
12833
|
AGENT_SESSIONS/<YYYY-MM-DD--<code>--<desc>>/
|
|
12827
12834
|
├─ SESSION.md ← its persistent body (this IS the workspace — nothing is auto-created)
|
|
12828
|
-
├─ ${
|
|
12835
|
+
├─ ${TRAJECTORY_CONSTRAINT_FILENAME} ← its constraints (this file is what makes it a 轨迹约束)
|
|
12829
12836
|
└─ TRAJECTORY-UID ← legacy, read-only (superseded by the code)
|
|
12830
12837
|
|
|
12831
|
-
── ${
|
|
12838
|
+
── ${TRAJECTORY_CONSTRAINT_FILENAME} schema ──
|
|
12832
12839
|
{
|
|
12833
12840
|
"model": "provider/model", // optional; falls back to cheap-worker.defaultModel
|
|
12834
12841
|
"tools": ["read","grep","msm",...], // non-MSM toolbar whitelist — anything not listed is REMOVED from the schema
|
|
@@ -12839,35 +12846,34 @@ becomes a skiff; delete it and it goes back to being an ordinary trajectory (tak
|
|
|
12839
12846
|
}
|
|
12840
12847
|
|
|
12841
12848
|
── Two whitelists, both default-deny ──
|
|
12842
|
-
- \`tools: []\` + non-empty \`msms\` = pure-MSM
|
|
12849
|
+
- \`tools: []\` + non-empty \`msms\` = pure-MSM 轨迹约束 (the usual knowledge-QA shape)
|
|
12843
12850
|
- non-empty \`msms\` ⇒ the \`msm\` tool is added automatically (the only entry point for MSMs)
|
|
12844
12851
|
- anything outside both lists is invisible in the schema, not merely rejected at call time
|
|
12845
12852
|
|
|
12846
12853
|
── cognitiveTrajectory: why the defaults are TRUE ──
|
|
12847
|
-
The workspace of a
|
|
12854
|
+
The workspace of a 轨迹约束 is its own SESSION.md. Setting these to false would cut it off from
|
|
12848
12855
|
its own body, so \`session\`/\`keeper\`/\`rebuild\` all default to true. Set false explicitly only
|
|
12849
12856
|
when you deliberately want that participation off.
|
|
12850
12857
|
|
|
12851
12858
|
── WeChat routing: user → trajectory code ──
|
|
12852
12859
|
\`weixin.routes\`: \`[{ "user": "<wx id or *>", "trajectory": "<code>" }]\`
|
|
12853
12860
|
A route pointing at a code with no such trajectory is a hard error (the message is NOT delivered).
|
|
12854
|
-
A trajectory WITHOUT \`${
|
|
12861
|
+
A trajectory WITHOUT \`${TRAJECTORY_CONSTRAINT_FILENAME}\` is still a legal target — it is just an ordinary trajectory.
|
|
12855
12862
|
|
|
12856
12863
|
── Operating it ──
|
|
12857
|
-
|
|
12858
|
-
Everything is read live: saving \`${SKIFF_PROFILE_FILENAME}\` takes effect on the next turn, no restart.
|
|
12864
|
+
Everything is read live: saving \`${TRAJECTORY_CONSTRAINT_FILENAME}\` takes effect on the next turn, no restart.
|
|
12859
12865
|
(The prompt FILE hot-reloads by mtime; the JSON itself is re-read every turn.)
|
|
12860
|
-
After editing, run container_admin
|
|
12861
|
-
Skill loading is always available to
|
|
12862
|
-
/** 校验当前 CCC 的
|
|
12863
|
-
function
|
|
12864
|
-
const profiles =
|
|
12866
|
+
After editing, run container_admin config apply for an explicit validation + confirmation pass.
|
|
12867
|
+
Skill loading is always available to 轨迹约束 sessions (skills are not whitelisted — they are read-only knowledge).`;
|
|
12868
|
+
/** 校验当前 CCC 的 轨迹约束面:msms 均已注册 / model ∈ cheap-worker.models / 提示词非空 */
|
|
12869
|
+
function validateTrajectoryConstraint(root) {
|
|
12870
|
+
const profiles = listTrajectoryConstraints(root);
|
|
12865
12871
|
const issues = [];
|
|
12866
12872
|
if (profiles.length === 0) return {
|
|
12867
12873
|
ok: true,
|
|
12868
12874
|
issues: [],
|
|
12869
|
-
|
|
12870
|
-
note: `no
|
|
12875
|
+
constraintCount: 0,
|
|
12876
|
+
note: `no 轨迹约束 trajectories in this CCC (no trajectory has ${TRAJECTORY_CONSTRAINT_FILENAME} — zero impact)`
|
|
12871
12877
|
};
|
|
12872
12878
|
const registered = new Set(loadMsmEntries(root).map((e) => e.name));
|
|
12873
12879
|
const hc = readCheapWorkerConfig(root);
|
|
@@ -12875,7 +12881,7 @@ function validateSkiffConfig(root) {
|
|
|
12875
12881
|
for (const p of profiles) {
|
|
12876
12882
|
if (!(hasTrajectoryPrompt(root, p.dirName) || (() => {
|
|
12877
12883
|
try {
|
|
12878
|
-
return
|
|
12884
|
+
return resolveConstraintSystemPrompt(root, p).trim() !== "";
|
|
12879
12885
|
} catch (err) {
|
|
12880
12886
|
issues.push(`trajectory "${p.code}": ${String(err?.message ?? err)}`);
|
|
12881
12887
|
return true;
|
|
@@ -12887,15 +12893,15 @@ function validateSkiffConfig(root) {
|
|
|
12887
12893
|
return {
|
|
12888
12894
|
ok: issues.length === 0,
|
|
12889
12895
|
issues,
|
|
12890
|
-
|
|
12896
|
+
constraintCount: profiles.length
|
|
12891
12897
|
};
|
|
12892
12898
|
}
|
|
12893
|
-
/** 列出当前 CCC
|
|
12894
|
-
function
|
|
12895
|
-
const profiles =
|
|
12899
|
+
/** 列出当前 CCC 的轨迹约束(编码 / 模型 / msms / tools / 纪律摘要 / 提示词来源) */
|
|
12900
|
+
function listTrajectoryConstraintSummary(root) {
|
|
12901
|
+
const profiles = listTrajectoryConstraints(root);
|
|
12896
12902
|
if (profiles.length === 0) return {
|
|
12897
12903
|
roles: [],
|
|
12898
|
-
note: `no
|
|
12904
|
+
note: `no 轨迹约束 trajectories in this CCC (no trajectory has ${TRAJECTORY_CONSTRAINT_FILENAME})`
|
|
12899
12905
|
};
|
|
12900
12906
|
const items = profiles.map((p) => ({
|
|
12901
12907
|
name: p.code,
|
|
@@ -12903,8 +12909,8 @@ function listSkiffRoles(root) {
|
|
|
12903
12909
|
model: p.model ?? "(cheap-worker default)",
|
|
12904
12910
|
msms: p.msms,
|
|
12905
12911
|
tools: p.tools,
|
|
12906
|
-
cognitiveTrajectory:
|
|
12907
|
-
promptSource:
|
|
12912
|
+
cognitiveTrajectory: constraintCognitiveTrajectory(p),
|
|
12913
|
+
promptSource: constraintSystemPromptSource(p, hasTrajectoryPrompt(root, p.dirName)),
|
|
12908
12914
|
...p.systemPromptFile ? { systemPromptFile: p.systemPromptFile } : {}
|
|
12909
12915
|
}));
|
|
12910
12916
|
return {
|
|
@@ -12913,51 +12919,91 @@ function listSkiffRoles(root) {
|
|
|
12913
12919
|
};
|
|
12914
12920
|
}
|
|
12915
12921
|
/**
|
|
12916
|
-
* 应用当前
|
|
12922
|
+
* 应用当前 轨迹约束 配置(S142 用户:改了 json 应该有个生效机制)。
|
|
12917
12923
|
*
|
|
12918
12924
|
* 语义:约束面是**每轮实时读取**的 ⇒ apply 不是"推送",而是**显式校验 + 应用确认**:
|
|
12919
12925
|
* ① 校验合法(复用 validate:msms 注册 / model 白名单 / 提示词非空)
|
|
12920
12926
|
* ② 非法 → 报告问题清单,不应用(提示修复后重跑 apply)
|
|
12921
12927
|
* ③ 合法 → 应用确认:绑定 CCC + 轨迹清单 + 说明(下一轮即生效)
|
|
12922
12928
|
*/
|
|
12923
|
-
function
|
|
12924
|
-
const validation =
|
|
12925
|
-
const profiles =
|
|
12929
|
+
function applyTrajectoryConstraint(root) {
|
|
12930
|
+
const validation = validateTrajectoryConstraint(root);
|
|
12931
|
+
const profiles = listTrajectoryConstraints(root);
|
|
12926
12932
|
if (!validation.ok) return {
|
|
12927
12933
|
applied: false,
|
|
12928
12934
|
cccRoot: root,
|
|
12929
|
-
|
|
12935
|
+
constraintCount: profiles.length,
|
|
12930
12936
|
issues: validation.issues,
|
|
12931
|
-
hint: "fix the issues above, then run container_admin
|
|
12937
|
+
hint: "fix the issues above, then run container_admin config apply again"
|
|
12932
12938
|
};
|
|
12933
12939
|
return {
|
|
12934
12940
|
applied: true,
|
|
12935
12941
|
cccRoot: root,
|
|
12936
|
-
|
|
12942
|
+
constraintCount: profiles.length,
|
|
12937
12943
|
roles: profiles.map((p) => p.code),
|
|
12938
|
-
note: `constraints are read live from each trajectory's ${
|
|
12944
|
+
note: `constraints are read live from each trajectory's ${TRAJECTORY_CONSTRAINT_FILENAME} — they take effect on the next turn (no restart); ${TRAJECTORY_CONSTRAINT_HINT}`
|
|
12939
12945
|
};
|
|
12940
12946
|
}
|
|
12941
12947
|
//#endregion
|
|
12948
|
+
//#region src/tools/config-docs.ts
|
|
12949
|
+
/**
|
|
12950
|
+
* 渲染**目录**(第一层)。
|
|
12951
|
+
*
|
|
12952
|
+
* 🔴 只调 `summary`,**绝不调 `render`** —— 目录若触发正文,就退化成"整份长文"。
|
|
12953
|
+
* 这条不是性能优化,是**判据**:它保证 `docs` 的输出大小与章数成正比,与正文长度无关。
|
|
12954
|
+
*/
|
|
12955
|
+
function renderDocsIndex(chapters) {
|
|
12956
|
+
const lines = [
|
|
12957
|
+
"═══ CCC 配置说明 · 目录 ═══",
|
|
12958
|
+
"",
|
|
12959
|
+
"说明类内容已超过单次返回的合适篇幅 ⇒ 先读目录,再按章取详情。",
|
|
12960
|
+
"",
|
|
12961
|
+
"用法:container_admin config docs <章节>",
|
|
12962
|
+
""
|
|
12963
|
+
];
|
|
12964
|
+
for (const ch of chapters) lines.push(` ${ch.id.padEnd(14)}${ch.summary}`);
|
|
12965
|
+
lines.push("");
|
|
12966
|
+
lines.push("相关:container_admin msm catalog(工具/能力目录——与本章录互指,不互相复制)");
|
|
12967
|
+
return lines.join("\n");
|
|
12968
|
+
}
|
|
12969
|
+
/**
|
|
12970
|
+
* 解析一个章节参数(第二层)。
|
|
12971
|
+
*
|
|
12972
|
+
* 不存在 ⇒ 返回 `null`,由调用方给出**带候选清单**的拒绝 ——
|
|
12973
|
+
* "不知道这个章节" 与 "这一章是空的" 必须可区分(否则用户以为功能没有)。
|
|
12974
|
+
*/
|
|
12975
|
+
function findDocsChapter(chapters, id) {
|
|
12976
|
+
const want = id.trim().toLowerCase();
|
|
12977
|
+
if (want === "") return null;
|
|
12978
|
+
return chapters.find((c) => c.id.toLowerCase() === want) ?? null;
|
|
12979
|
+
}
|
|
12980
|
+
//#endregion
|
|
12942
12981
|
//#region src/tools/container-admin.ts
|
|
12943
12982
|
/**
|
|
12944
12983
|
* container-admin.ts — container_admin(v1.30:机务舱——容器管理面统一入口)
|
|
12945
12984
|
*
|
|
12946
|
-
*
|
|
12947
|
-
* · skiff_admin(F4 Skiff 认知子集角色:guide/validate/apply/list)
|
|
12948
|
-
* · acc_msm 管理面(register/deregister/check/guide/catalog/ccc-config)
|
|
12949
|
-
* —— msm 工具只管执行+发现;一切「管理容器」的操作归本工具
|
|
12985
|
+
* ## 域结构(2026-10-19 owner 两条令后:**两域**)
|
|
12950
12986
|
*
|
|
12951
|
-
*
|
|
12987
|
+
* | 域 | 管什么 |
|
|
12988
|
+
* |---|---|
|
|
12989
|
+
* | `config` | **本 CCC 的全部配置面**:说明目录/按章详情 + 轨迹约束(原 `role` 域,已并入) |
|
|
12990
|
+
* | `msm` | MSM 注册表管理 + 手册(register/deregister/check/guide/catalog/ccc-config) |
|
|
12991
|
+
*
|
|
12992
|
+
* 🔴 **`role` 域已并入 `config`**(owner 2026-10-19 裁「1 并入」)。为什么不是三个平行域:
|
|
12993
|
+
* `role` 管的本来就是"某条轨迹的约束面"——**一组配置**,与 `config` 是同一抽象层的两半。
|
|
12994
|
+
* 分着放的代价是:用户在 `config` 里看不到"轨迹怎么约束",得先知道 `role` 这个词存在。
|
|
12995
|
+
* ⚠️ **旧 `role` 域仍被接受**(过渡别名,指向同一实现)——存量 CCC 的文档/脚本里到处是
|
|
12996
|
+
* `container_admin role validate`(实测本仓 30 处),硬切会让它们全部变成"未知域"。
|
|
12997
|
+
* 别名**只保留读取路径**:schema 的 `enum` 只列新名,文案只教新名。
|
|
12998
|
+
*
|
|
12999
|
+
* ## 三个动作名为什么是这三个(P3 的实现形态)
|
|
13000
|
+
* - `config docs`(无参)→ **使用说明目录**(第一层;只索引不复制)
|
|
13001
|
+
* - `config docs <chapter>` → **使用说明详情**(第二层;按章获得)
|
|
13002
|
+
* - `config view` → 配置**现值**(原来是唯一动作;现在是"看当前填了什么")
|
|
13003
|
+
*
|
|
13004
|
+
* 归属(D23):**机制在 ACC**(有哪几章、怎么路由、怎么校验),**内容在 CCC**(填什么)。
|
|
13005
|
+
* 语义(用户拍板"轨迹约束_admin 及系列 admin 合并成 container_admin"):
|
|
12952
13006
|
* container = CCC(具体认知容器);admin = 管理操作。
|
|
12953
|
-
* 类比 The Manifest(清单维护——工具注册表)+ Crew(角色管理)+ 机务(配置/手册)。
|
|
12954
|
-
*
|
|
12955
|
-
* 参数极简(LLM 直觉):domain(管理域)+ action(该域子命令)+ 少量业务参数。
|
|
12956
|
-
*
|
|
12957
|
-
* v1.34.1(⑥ C6a):由 `export const containerAdminTool` 改为**工厂**
|
|
12958
|
-
* `createContainerAdminTool(ctx)`——原为 autopilot 域传进程内运行态事实(全局闸 / live 会话 /
|
|
12959
|
-
* agent 可解析性)。该域已于 2026-09-15 随 ACC 侧 autopilot 整段退场删除(所有者裁决 (a)):
|
|
12960
|
-
* `ctx` 现**无消费者**,工厂形态保留只是为了让装配点签名不再变动(见 index.ts 注册处)。
|
|
12961
13007
|
*/
|
|
12962
13008
|
function agentSessionId(exec) {
|
|
12963
13009
|
return exec.agent?.session?.id ?? "";
|
|
@@ -12968,9 +13014,13 @@ function renderText$1(value) {
|
|
|
12968
13014
|
text: typeof value === "string" ? value : JSON.stringify(value, null, 2)
|
|
12969
13015
|
}];
|
|
12970
13016
|
}
|
|
13017
|
+
/** 过渡别名表:旧域 → 新域(只读兼容;文案不教它) */
|
|
13018
|
+
const DOMAIN_ALIASES = { role: "config" };
|
|
12971
13019
|
/** 合法 domain 名(拒绝文案与 schema enum 同源——不得各写一份) */
|
|
12972
13020
|
const DOMAIN_NAMES = Object.keys({
|
|
12973
|
-
|
|
13021
|
+
config: [
|
|
13022
|
+
"docs",
|
|
13023
|
+
"view",
|
|
12974
13024
|
"guide",
|
|
12975
13025
|
"validate",
|
|
12976
13026
|
"apply",
|
|
@@ -12983,23 +13033,43 @@ const DOMAIN_NAMES = Object.keys({
|
|
|
12983
13033
|
"guide",
|
|
12984
13034
|
"catalog",
|
|
12985
13035
|
"ccc-config"
|
|
12986
|
-
]
|
|
12987
|
-
config: ["view"]
|
|
13036
|
+
]
|
|
12988
13037
|
});
|
|
13038
|
+
/**
|
|
13039
|
+
* 说明目录的章节(P3 两层菜单的**唯一注册处**)。
|
|
13040
|
+
*
|
|
13041
|
+
* 🔴 加一章 = 在这里加一项(`render` 惰性求值)。**正文不复制**:
|
|
13042
|
+
* 每章的真相源就在它自己的函数里(`CCC_CONFIG_REFERENCE` / `TRAJECTORY_CONSTRAINT_GUIDE`)——
|
|
13043
|
+
* 本表只做**切分与路由**,避免"同一段说明有第二份会漂的副本"。
|
|
13044
|
+
*/
|
|
13045
|
+
const DOCS_CHAPTERS = [{
|
|
13046
|
+
id: "config",
|
|
13047
|
+
summary: "CCC 配置全表——9 段各是什么、怎么填、填错会怎样(.opencode/serenity.json)",
|
|
13048
|
+
render: () => CCC_CONFIG_REFERENCE
|
|
13049
|
+
}, {
|
|
13050
|
+
id: "trajectory-constraint",
|
|
13051
|
+
summary: "轨迹约束:一条轨迹怎么被约束(trajectory-constraint.json 的 schema + 双白名单)",
|
|
13052
|
+
render: () => TRAJECTORY_CONSTRAINT_GUIDE
|
|
13053
|
+
}];
|
|
12989
13054
|
function createContainerAdminTool(ctx) {
|
|
12990
13055
|
return defineTool({
|
|
12991
13056
|
name: "container_admin",
|
|
12992
|
-
description: "Container administration (the ship's maintenance bay — one entry for managing the CCC):
|
|
13057
|
+
description: "Container administration (the ship's maintenance bay — one entry for managing the CCC): config — CCC configuration: docs (usage-manual index) / docs <chapter> (one chapter in full) / view (current values) + 轨迹约束 (guide/validate/apply/list); msm — MSM registry management (register/deregister/check quality DC-M1~M4) + manuals (guide dev manual / catalog ACC capability directory / ccc-config CCC configuration reference). Execution of registered MSMs belongs to the msm tool; this tool manages the container.",
|
|
12993
13058
|
parameters: {
|
|
12994
13059
|
domain: {
|
|
12995
13060
|
type: "string",
|
|
12996
13061
|
enum: DOMAIN_NAMES,
|
|
12997
13062
|
required: true,
|
|
12998
|
-
description: "Management domain:
|
|
13063
|
+
description: "Management domain: config (CCC configuration + 轨迹约束) / msm (MSM registry + manuals)"
|
|
12999
13064
|
},
|
|
13000
13065
|
action: {
|
|
13001
13066
|
type: "string",
|
|
13002
|
-
description: "Domain subcommand
|
|
13067
|
+
description: "Domain subcommand. config: docs / view / guide / validate / apply / list; msm: register / deregister / check / guide / catalog / ccc-config"
|
|
13068
|
+
},
|
|
13069
|
+
/** 第二层菜单参数:`config docs <chapter>`(缺省 = 目录) */
|
|
13070
|
+
chapter: {
|
|
13071
|
+
type: "string",
|
|
13072
|
+
description: "config docs only: chapter id (omit for the index; e.g. config / trajectory-constraint)"
|
|
13003
13073
|
},
|
|
13004
13074
|
name: {
|
|
13005
13075
|
type: "string",
|
|
@@ -13038,18 +13108,35 @@ function createContainerAdminTool(ctx) {
|
|
|
13038
13108
|
const root = cccRootForExec(exec);
|
|
13039
13109
|
if (!root) throw new Error(NO_CCC_FROM_AGENT_CWD);
|
|
13040
13110
|
const sessionId = agentSessionId(exec);
|
|
13041
|
-
const
|
|
13111
|
+
const rawDomain = String(args.domain ?? "");
|
|
13112
|
+
const domain = DOMAIN_ALIASES[rawDomain] ?? rawDomain;
|
|
13042
13113
|
const action = args.action ?? "";
|
|
13114
|
+
if (!DOMAIN_NAMES.includes(domain)) throw new Error(`container_admin requires domain: ${DOMAIN_NAMES.join(" | ")}`);
|
|
13043
13115
|
if (domain === "msm" && (action === "register" || action === "deregister")) {
|
|
13044
|
-
const gate =
|
|
13116
|
+
const gate = trajectoryConstraintMsmGate(root, sessionId, action, args.name);
|
|
13045
13117
|
if (gate.reject) throw new Error(gate.reject);
|
|
13046
13118
|
}
|
|
13047
|
-
if (domain === "
|
|
13048
|
-
case "
|
|
13049
|
-
|
|
13050
|
-
|
|
13051
|
-
|
|
13052
|
-
|
|
13119
|
+
if (domain === "config") switch (action) {
|
|
13120
|
+
case "docs": {
|
|
13121
|
+
const chapter = args.chapter?.trim() ?? "";
|
|
13122
|
+
if (chapter === "") return {
|
|
13123
|
+
docs: renderDocsIndex(DOCS_CHAPTERS),
|
|
13124
|
+
chapters: DOCS_CHAPTERS.map((c) => c.id)
|
|
13125
|
+
};
|
|
13126
|
+
const found = findDocsChapter(DOCS_CHAPTERS, chapter);
|
|
13127
|
+
if (found === null) throw new Error(`container_admin config docs: unknown chapter "${chapter}". Available: ${DOCS_CHAPTERS.map((c) => c.id).join(" | ")} (omit the chapter for the index)`);
|
|
13128
|
+
return {
|
|
13129
|
+
chapter: found.id,
|
|
13130
|
+
docs: found.render()
|
|
13131
|
+
};
|
|
13132
|
+
}
|
|
13133
|
+
case "view":
|
|
13134
|
+
case "": return { config: runMsm(root, { action: "ccc-config" }) };
|
|
13135
|
+
case "guide": return { guide: TRAJECTORY_CONSTRAINT_GUIDE };
|
|
13136
|
+
case "validate": return validateTrajectoryConstraint(root);
|
|
13137
|
+
case "apply": return applyTrajectoryConstraint(root);
|
|
13138
|
+
case "list": return listTrajectoryConstraintSummary(root);
|
|
13139
|
+
default: throw new Error(`container_admin config requires action: docs | view | guide | validate | apply | list`);
|
|
13053
13140
|
}
|
|
13054
13141
|
if (domain === "msm") {
|
|
13055
13142
|
const mgmtActions = [
|
|
@@ -13072,7 +13159,6 @@ function createContainerAdminTool(ctx) {
|
|
|
13072
13159
|
usage: args.usage
|
|
13073
13160
|
});
|
|
13074
13161
|
}
|
|
13075
|
-
if (domain === "config") return { config: runMsm(root, { action: "ccc-config" }) };
|
|
13076
13162
|
throw new Error(`container_admin requires domain: ${DOMAIN_NAMES.join(" | ")}`);
|
|
13077
13163
|
}
|
|
13078
13164
|
});
|
|
@@ -13324,7 +13410,7 @@ const SENSITIVE_CREDENTIAL_FILES = /* @__PURE__ */ new Set(["localstore.json"]);
|
|
|
13324
13410
|
* MSM 注册表写保护(需求⑤b S142 用户拍板:"msm注册的文件需要写保护起来,避免CCC意外写坏搞崩自己"):
|
|
13325
13411
|
* mech-registry.json 是 CCC 执行层地基(坏 → loadMsmEntries 抛 → container_admin/output-guard 全崩,
|
|
13326
13412
|
* register 也崩 → 自锁)。**写 deny、读 allow**(与 localstore 读 deny 语义区分 R6——注册表是
|
|
13327
|
-
* 结构核心不是秘密,需被 output-guard/
|
|
13413
|
+
* 结构核心不是秘密,需被 output-guard/trajectory-constraint-admin 读取建 MSM 词表)。
|
|
13328
13414
|
*
|
|
13329
13415
|
* 单级化(需求⑤a)后**唯一合法注册表 = cccName 聚合档**
|
|
13330
13416
|
* `.opencode/skills/<cccName>/references/mech-registry.json`(cccName = .serenity 首行)。
|
|
@@ -13362,15 +13448,15 @@ function isWriteTool(toolName, action) {
|
|
|
13362
13448
|
*/
|
|
13363
13449
|
function decideGuard(input) {
|
|
13364
13450
|
const { root, toolName, safeModeOn, blacklist, pathArg, action } = input;
|
|
13365
|
-
if (input.
|
|
13366
|
-
const profile = input.
|
|
13451
|
+
if (input.trajectoryConstraint !== void 0 && input.trajectoryConstraint !== null) {
|
|
13452
|
+
const profile = input.trajectoryConstraint;
|
|
13367
13453
|
if (toolName === "skill") return { kind: "allow" };
|
|
13368
13454
|
if (profile.msms.includes(toolName)) return {
|
|
13369
13455
|
deny: `"${toolName}" is a registered MSM, not a direct tool in this trajectory — call it through the msm tool: msm("${toolName}", ["<args>"]) (pass "--help" or inspect=true for usage)`,
|
|
13370
13456
|
kind: "deny"
|
|
13371
13457
|
};
|
|
13372
13458
|
if (!profileToolWhitelist(profile).has(toolName)) return {
|
|
13373
|
-
deny: "tool not allowed in this
|
|
13459
|
+
deny: "tool not allowed in this 轨迹约束 trajectory",
|
|
13374
13460
|
kind: "deny"
|
|
13375
13461
|
};
|
|
13376
13462
|
}
|
|
@@ -13655,17 +13741,17 @@ const exclusiveRestrictions = /* @__PURE__ */ new Map();
|
|
|
13655
13741
|
* 每次 pre-step 都重算白名单,只有**算出来的结果变了**才重新发 restrict
|
|
13656
13742
|
* —— 配置不变就不重复发(restrict 会重建 schema,没必要每步都做)。
|
|
13657
13743
|
*/
|
|
13658
|
-
const
|
|
13744
|
+
const trajectoryConstraintRestrictions = /* @__PURE__ */ new Map();
|
|
13659
13745
|
/**
|
|
13660
|
-
* 同步
|
|
13746
|
+
* 同步 **轨迹约束会话的工具面**:把白名单之外的工具**从 schema 移除**(不是"看得到但被拒")。
|
|
13661
13747
|
*
|
|
13662
13748
|
* ## 为什么必须有这条(owner 2026-10-19:「工具清单也要热加载」)
|
|
13663
13749
|
*
|
|
13664
|
-
*
|
|
13750
|
+
* 在它之前,轨迹约束白名单**只由运行时守卫拦截**(`decideGuard` 的 轨迹约束 分支):
|
|
13665
13751
|
* 工具全部出现在模型 schema 里,调用时才被打回。⇒ 两个真实后果:
|
|
13666
13752
|
* ① **模型看得见它不该有的工具** —— 它会去试,试了才撞墙(一轮白烧,且错误文案泛化,
|
|
13667
13753
|
* 看不出"是不在清单里"还是"临时性拒绝");
|
|
13668
|
-
* ② **配置变动在 schema 上永远不生效** —— 改 `
|
|
13754
|
+
* ② **配置变动在 schema 上永远不生效** —— 改 `trajectory-constraint.json` 的 `tools` 只影响"调用是否放行"。
|
|
13669
13755
|
*
|
|
13670
13756
|
* ## 判据与降级
|
|
13671
13757
|
*
|
|
@@ -13673,24 +13759,24 @@ const skiffRoleRestrictions = /* @__PURE__ */ new Map();
|
|
|
13673
13759
|
* 不去动宿主的工具 —— 宿主的工具由各自的条件同步管)。
|
|
13674
13760
|
* - ⚠️ **未注册的名字会让整次 restrict 失败**(宿主契约逐字 "unknown names … fail"),
|
|
13675
13761
|
* 而 `diagram` 是按开关注册的 ⇒ 与 `hideAllFor` 同款**两档尝试**(先全覆盖,失败退常驻段)。
|
|
13676
|
-
* - 读不到约束面(不是
|
|
13677
|
-
* ⛔ 不能只是 `return` —— 那会把上一步的隐藏**永久留在**一个已不再是
|
|
13678
|
-
* (这正是 owner 说的"删掉 `
|
|
13762
|
+
* - 读不到约束面(不是 轨迹约束 / 配置删了)⇒ **撤销既有隐藏**(回到"无约束"):
|
|
13763
|
+
* ⛔ 不能只是 `return` —— 那会把上一步的隐藏**永久留在**一个已不再是 轨迹约束的会话上
|
|
13764
|
+
* (这正是 owner 说的"删掉 `trajectory-constraint.json` 就该立刻恢复")。
|
|
13679
13765
|
* - 幂等:算出来的 deny 签名不变就跳过(不每步重建 schema)。
|
|
13680
13766
|
*
|
|
13681
13767
|
* @param agent 目标会话
|
|
13682
13768
|
* @param root CCC 根
|
|
13683
13769
|
*/
|
|
13684
|
-
function
|
|
13770
|
+
function syncTrajectoryConstraintToolVisibility(agent, root) {
|
|
13685
13771
|
const sessionId = agent.session.id ?? "global";
|
|
13686
|
-
const profile =
|
|
13687
|
-
const existing =
|
|
13772
|
+
const profile = trajectoryConstraintForSession(root, sessionId);
|
|
13773
|
+
const existing = trajectoryConstraintRestrictions.get(sessionId);
|
|
13688
13774
|
if (profile === null) {
|
|
13689
13775
|
if (existing !== void 0) {
|
|
13690
13776
|
try {
|
|
13691
13777
|
existing.dispose();
|
|
13692
13778
|
} catch {}
|
|
13693
|
-
|
|
13779
|
+
trajectoryConstraintRestrictions.delete(sessionId);
|
|
13694
13780
|
}
|
|
13695
13781
|
return;
|
|
13696
13782
|
}
|
|
@@ -13706,7 +13792,7 @@ function syncSkiffRoleToolVisibility(agent, root) {
|
|
|
13706
13792
|
try {
|
|
13707
13793
|
existing.dispose();
|
|
13708
13794
|
} catch {}
|
|
13709
|
-
|
|
13795
|
+
trajectoryConstraintRestrictions.delete(sessionId);
|
|
13710
13796
|
}
|
|
13711
13797
|
return;
|
|
13712
13798
|
}
|
|
@@ -13715,10 +13801,10 @@ function syncSkiffRoleToolVisibility(agent, root) {
|
|
|
13715
13801
|
try {
|
|
13716
13802
|
existing.dispose();
|
|
13717
13803
|
} catch {}
|
|
13718
|
-
|
|
13804
|
+
trajectoryConstraintRestrictions.delete(sessionId);
|
|
13719
13805
|
}
|
|
13720
13806
|
const dispose = agent.ctx.tools.restrict({ deny: [...deny] });
|
|
13721
|
-
|
|
13807
|
+
trajectoryConstraintRestrictions.set(sessionId, {
|
|
13722
13808
|
signature,
|
|
13723
13809
|
deny,
|
|
13724
13810
|
dispose
|
|
@@ -13730,13 +13816,13 @@ function syncSkiffRoleToolVisibility(agent, root) {
|
|
|
13730
13816
|
}
|
|
13731
13817
|
}
|
|
13732
13818
|
/** 会话销毁清理(lifecycle 调用) */
|
|
13733
|
-
function
|
|
13734
|
-
const existing =
|
|
13819
|
+
function forgetTrajectoryConstraintToolVisibility(sessionId) {
|
|
13820
|
+
const existing = trajectoryConstraintRestrictions.get(sessionId);
|
|
13735
13821
|
if (!existing) return;
|
|
13736
13822
|
try {
|
|
13737
13823
|
existing.dispose();
|
|
13738
13824
|
} catch {}
|
|
13739
|
-
|
|
13825
|
+
trajectoryConstraintRestrictions.delete(sessionId);
|
|
13740
13826
|
}
|
|
13741
13827
|
/**
|
|
13742
13828
|
* 同步专属工具可见性(v1.33)。
|
|
@@ -13931,7 +14017,7 @@ function registerGuards(ctx, opts = {}) {
|
|
|
13931
14017
|
const pathArg = extractPathArg(exec);
|
|
13932
14018
|
const action = extractAction(exec);
|
|
13933
14019
|
const sessionId = exec?.agent?.session?.id;
|
|
13934
|
-
const
|
|
14020
|
+
const trajectoryConstraint = trajectoryConstraintForSession(root, sessionId);
|
|
13935
14021
|
return decideGuard({
|
|
13936
14022
|
root,
|
|
13937
14023
|
toolName: exec.name,
|
|
@@ -13939,7 +14025,7 @@ function registerGuards(ctx, opts = {}) {
|
|
|
13939
14025
|
blacklist,
|
|
13940
14026
|
pathArg,
|
|
13941
14027
|
action,
|
|
13942
|
-
|
|
14028
|
+
trajectoryConstraint
|
|
13943
14029
|
});
|
|
13944
14030
|
};
|
|
13945
14031
|
ctx.on("tools/pre-execute", async (exec, next) => {
|
|
@@ -14176,7 +14262,7 @@ function createEpochPromotion(promoteEvents, requiredSignals = 1, maxRoundsFallb
|
|
|
14176
14262
|
const loopSid = session.id;
|
|
14177
14263
|
const loopCwd = session.header?.cwd ?? process.cwd();
|
|
14178
14264
|
const loopRoot = cccRootForCwd(loopCwd);
|
|
14179
|
-
if (typeof loopSid === "string" && (loopSid.startsWith("cheap-worker-") || loopRoot !== null &&
|
|
14265
|
+
if (typeof loopSid === "string" && (loopSid.startsWith("cheap-worker-") || loopRoot !== null && isTrajectoryConstrained(loopRoot, loopSid))) return {
|
|
14180
14266
|
boundary: -1,
|
|
14181
14267
|
promoted: true
|
|
14182
14268
|
};
|
|
@@ -14295,7 +14381,7 @@ function registerBootstrap(ctx) {
|
|
|
14295
14381
|
const depth = session?.header?.delegationDepth ?? 0;
|
|
14296
14382
|
const sid = typeof session?.id === "string" ? session.id : void 0;
|
|
14297
14383
|
if (sid === void 0) return;
|
|
14298
|
-
if (sid.startsWith("cheap-worker-") ||
|
|
14384
|
+
if (sid.startsWith("cheap-worker-") || isTrajectoryConstrained(root, sid)) return;
|
|
14299
14385
|
if (depth === 0) {
|
|
14300
14386
|
if (hasUserMessageHistory(session)) return;
|
|
14301
14387
|
} else if (anchoredSessions.has(sid)) return;
|
|
@@ -14609,7 +14695,7 @@ function forgetLogbookCompactionState(scope) {
|
|
|
14609
14695
|
* 解析**活跃** SESSION.md 绝对路径(缓存 60s)。
|
|
14610
14696
|
*
|
|
14611
14697
|
* 候选链(刻意比 rebuild 的完整链短——这里每个工具调用都要跑,贵候选不划算):
|
|
14612
|
-
* ① `getActiveSessionInfo(scope).mdPath`(`cognitive_trajectory use` /
|
|
14698
|
+
* ① `getActiveSessionInfo(scope).mdPath`(`cognitive_trajectory use` / 轨迹约束 绑定 / 重启恢复都会写)
|
|
14613
14699
|
* ② `readLastBound(session).mdPath`(`.bindings.json` 权威绑定——重启后内存空时兜底)
|
|
14614
14700
|
* 两者都拿不到 → null(**不猜**:宁可不提醒,也不去猜别的轨迹——对齐 v1.30.13 D4 的教训)。
|
|
14615
14701
|
*/
|
|
@@ -14738,10 +14824,10 @@ function registerKeeper(ctx, opts = {}) {
|
|
|
14738
14824
|
const root = cccRootForCwd(cwd);
|
|
14739
14825
|
if (!root) return next();
|
|
14740
14826
|
const sessionId = exec.agent?.session?.id;
|
|
14741
|
-
const
|
|
14742
|
-
const
|
|
14827
|
+
const 轨迹约束Keeper = trajectoryConstraintParticipates(root, sessionId, "keeper");
|
|
14828
|
+
const 轨迹约束Rebuild = trajectoryConstraintParticipates(root, sessionId, "rebuild");
|
|
14743
14829
|
const tracker = trackerFor(exec);
|
|
14744
|
-
const shouldRemind =
|
|
14830
|
+
const shouldRemind = 轨迹约束Keeper ? tracker.step(exec.name) : false;
|
|
14745
14831
|
const downstream = await next();
|
|
14746
14832
|
try {
|
|
14747
14833
|
recordToolUsage(root, exec.name, exec.arguments);
|
|
@@ -14754,7 +14840,7 @@ function registerKeeper(ctx, opts = {}) {
|
|
|
14754
14840
|
text: reminderText(code)
|
|
14755
14841
|
});
|
|
14756
14842
|
}
|
|
14757
|
-
if (
|
|
14843
|
+
if (轨迹约束Rebuild) {
|
|
14758
14844
|
const session = exec.agent?.session;
|
|
14759
14845
|
if (session) {
|
|
14760
14846
|
const pressure = readContextPressure(ctx, session);
|
|
@@ -14774,7 +14860,7 @@ function registerKeeper(ctx, opts = {}) {
|
|
|
14774
14860
|
}
|
|
14775
14861
|
}
|
|
14776
14862
|
}
|
|
14777
|
-
if (
|
|
14863
|
+
if (trajectoryConstraintParticipates(root, sessionId, "session")) {
|
|
14778
14864
|
const limitKB = readSessionMdMaxKB(root, opts.configPaths);
|
|
14779
14865
|
if (limitKB > 0) {
|
|
14780
14866
|
const scope = sessionId ?? "global";
|
|
@@ -14797,7 +14883,7 @@ function registerKeeper(ctx, opts = {}) {
|
|
|
14797
14883
|
} else compactionStates.delete(scope);
|
|
14798
14884
|
}
|
|
14799
14885
|
}
|
|
14800
|
-
if (
|
|
14886
|
+
if (trajectoryConstraintParticipates(root, sessionId, "session")) {
|
|
14801
14887
|
const scope = sessionId ?? "global";
|
|
14802
14888
|
const session = exec.agent?.session;
|
|
14803
14889
|
const mdPath = resolveActiveSessionMdPath(root, scope, session);
|
|
@@ -14905,7 +14991,7 @@ function shouldAutoRestore(agent, root) {
|
|
|
14905
14991
|
if (session.header?.parentSession) return false;
|
|
14906
14992
|
if (session.id?.startsWith("cheap-worker-")) return false;
|
|
14907
14993
|
const cccRoot = root !== void 0 ? root : cccRootForCwd(session.header?.cwd ?? process.cwd());
|
|
14908
|
-
if (cccRoot &&
|
|
14994
|
+
if (cccRoot && isTrajectoryConstrained(cccRoot, session.id)) return false;
|
|
14909
14995
|
return true;
|
|
14910
14996
|
}
|
|
14911
14997
|
/**
|
|
@@ -14928,7 +15014,7 @@ function registerContext(ctx, opts = {}) {
|
|
|
14928
15014
|
} catch {}
|
|
14929
15015
|
if (!root) return;
|
|
14930
15016
|
const key = agentKey(agent);
|
|
14931
|
-
if (
|
|
15017
|
+
if (isTrajectoryConstrained(root, key)) return;
|
|
14932
15018
|
registerEntrySkillSection(agent, root);
|
|
14933
15019
|
registerCognitiveTrajectorySkillSection(agent, root);
|
|
14934
15020
|
const scope = agentScope(agent);
|
|
@@ -15012,9 +15098,9 @@ function registerContext(ctx, opts = {}) {
|
|
|
15012
15098
|
const key = agentKey(agent);
|
|
15013
15099
|
const downstream = await next();
|
|
15014
15100
|
if (root) try {
|
|
15015
|
-
|
|
15101
|
+
syncTrajectoryConstraintToolVisibility(agent, root);
|
|
15016
15102
|
} catch {}
|
|
15017
|
-
if (root &&
|
|
15103
|
+
if (root && isTrajectoryConstrained(root, key)) return downstream;
|
|
15018
15104
|
try {
|
|
15019
15105
|
syncCccToolVisibility(agent, root);
|
|
15020
15106
|
} catch {}
|
|
@@ -15063,7 +15149,7 @@ function registerCompactRetention(ctx, opts = {}) {
|
|
|
15063
15149
|
const cwd = agent.session?.header?.cwd ?? process.cwd();
|
|
15064
15150
|
const root = cccRootForCwd(cwd);
|
|
15065
15151
|
if (!root) return;
|
|
15066
|
-
if (
|
|
15152
|
+
if (isTrajectoryConstrained(root, String(session.id ?? ""))) return;
|
|
15067
15153
|
try {
|
|
15068
15154
|
agent.inject(accMessage(root, configPaths, entrySkillMaxChars));
|
|
15069
15155
|
} catch {}
|
|
@@ -15404,7 +15490,7 @@ function requireWebUi(req, res, message) {
|
|
|
15404
15490
|
*
|
|
15405
15491
|
* 🔴🔴 **2026-10-18 修缺陷(owner 报「在别的 CCC 点开显示的还是 home-serenity 的信息」)**:
|
|
15406
15492
|
* 旧实现第 ③ 步是「**遍历 live sessions 找第一个 CCC 会话的 cwd**」—— 那是**跨工作区猜测**:
|
|
15407
|
-
* 只要 sessionId 缺失或那条会话查不到(ACP
|
|
15493
|
+
* 只要 sessionId 缺失或那条会话查不到(ACP/轨迹约束会话、尚未 materialize 的会话、远程访问),
|
|
15408
15494
|
* 它就会**静默返回另一个 CCC 的路径**,于是面板把**别人的轨迹/技能/安全模式**当成你的显示出来。
|
|
15409
15495
|
* 这比"显示未激活"危险得多 —— 用户会据此做判断(以为自己的 safe-mode 开着、以为那是自己的轨迹)。
|
|
15410
15496
|
*
|
|
@@ -15618,17 +15704,22 @@ function registerStatusApi(ctx, opts = {}) {
|
|
|
15618
15704
|
sendJson$2(res, 400, { error: chk.error });
|
|
15619
15705
|
return;
|
|
15620
15706
|
}
|
|
15621
|
-
const { readWeixinSettings } = await import("./route-
|
|
15707
|
+
const { readWeixinSettings } = await import("./route-COOFLTpU.js").then((n) => n.c);
|
|
15622
15708
|
const { weixinBridgeStatus } = await Promise.resolve().then(() => bridge_exports);
|
|
15623
15709
|
const settings = readWeixinSettings(chk.root);
|
|
15624
15710
|
const bridge = weixinBridgeStatus().find((b) => b.ccc === chk.root);
|
|
15625
|
-
const {
|
|
15626
|
-
const
|
|
15627
|
-
|
|
15628
|
-
|
|
15629
|
-
|
|
15630
|
-
|
|
15631
|
-
|
|
15711
|
+
const { imCredentialBound } = await import("./route-COOFLTpU.js").then((n) => n.c);
|
|
15712
|
+
const { normalizeAccountChannel } = await import("./accounts-BHQbakUh.js").then((n) => n.t);
|
|
15713
|
+
const accounts = (settings.accounts ?? []).map((a) => {
|
|
15714
|
+
const channel = normalizeAccountChannel(a.channel);
|
|
15715
|
+
return {
|
|
15716
|
+
accountId: a.accountId,
|
|
15717
|
+
name: a.name ?? void 0,
|
|
15718
|
+
enabled: a.enabled !== false,
|
|
15719
|
+
channel,
|
|
15720
|
+
bound: imCredentialBound(chk.root, a.accountId, channel)
|
|
15721
|
+
};
|
|
15722
|
+
});
|
|
15632
15723
|
sendJson$2(res, 200, {
|
|
15633
15724
|
enabled: settings.enabled === true,
|
|
15634
15725
|
botType: settings.botType ?? void 0,
|
|
@@ -15654,7 +15745,7 @@ function registerStatusApi(ctx, opts = {}) {
|
|
|
15654
15745
|
const { fetchQRCode } = await import("./ilink-BMk2w2KY.js").then((n) => n.a);
|
|
15655
15746
|
const { randomUUID } = await import("node:crypto");
|
|
15656
15747
|
for (const [key, v] of weixinLogins) if (Date.now() - v.startedAt > WEIXIN_LOGIN_TTL_MS) weixinLogins.delete(key);
|
|
15657
|
-
const { readWeixinSettings } = await import("./route-
|
|
15748
|
+
const { readWeixinSettings } = await import("./route-COOFLTpU.js").then((n) => n.c);
|
|
15658
15749
|
const qr = await fetchQRCode({ botType: readWeixinSettings(root).botType ?? void 0 });
|
|
15659
15750
|
const loginKey = randomUUID();
|
|
15660
15751
|
weixinLogins.set(loginKey, {
|
|
@@ -15675,7 +15766,7 @@ function registerStatusApi(ctx, opts = {}) {
|
|
|
15675
15766
|
sendJson$2(res, 400, { error: "missing accountId" });
|
|
15676
15767
|
return;
|
|
15677
15768
|
}
|
|
15678
|
-
const { removeWeixinAccount } = await import("./route-
|
|
15769
|
+
const { removeWeixinAccount } = await import("./route-COOFLTpU.js").then((n) => n.c);
|
|
15679
15770
|
const { syncCccBridge } = await Promise.resolve().then(() => bridge_exports);
|
|
15680
15771
|
removeWeixinAccount(root, body.accountId);
|
|
15681
15772
|
syncCccBridge(ctx, root);
|
|
@@ -15701,8 +15792,8 @@ function registerStatusApi(ctx, opts = {}) {
|
|
|
15701
15792
|
trajectory: code
|
|
15702
15793
|
});
|
|
15703
15794
|
}
|
|
15704
|
-
const { saveWeixinRoutes } = await import("./route-
|
|
15705
|
-
const { findDirNameByCode } = await import("./profile-
|
|
15795
|
+
const { saveWeixinRoutes } = await import("./route-COOFLTpU.js").then((n) => n.c);
|
|
15796
|
+
const { findDirNameByCode } = await import("./profile-BVt84mbi.js").then((n) => n.i);
|
|
15706
15797
|
for (const r of routes) if (findDirNameByCode(root, r.trajectory) === null) {
|
|
15707
15798
|
sendJson$2(res, 400, { error: `unknown trajectory: ${r.trajectory} (no such trajectory code in ${root})` });
|
|
15708
15799
|
return;
|
|
@@ -15713,7 +15804,7 @@ function registerStatusApi(ctx, opts = {}) {
|
|
|
15713
15804
|
}
|
|
15714
15805
|
if (body.action === "set-enabled") {
|
|
15715
15806
|
const enabled = body.enabled === true;
|
|
15716
|
-
const { readWeixinSettings, setWeixinEnabled } = await import("./route-
|
|
15807
|
+
const { readWeixinSettings, setWeixinEnabled } = await import("./route-COOFLTpU.js").then((n) => n.c);
|
|
15717
15808
|
const { syncCccBridge } = await Promise.resolve().then(() => bridge_exports);
|
|
15718
15809
|
const settings = readWeixinSettings(root);
|
|
15719
15810
|
if (enabled && (settings.accounts ?? []).length === 0) {
|
|
@@ -15729,6 +15820,35 @@ function registerStatusApi(ctx, opts = {}) {
|
|
|
15729
15820
|
sendJson$2(res, 200, { enabled });
|
|
15730
15821
|
return;
|
|
15731
15822
|
}
|
|
15823
|
+
if (body.action === "add-feishu-account") {
|
|
15824
|
+
const appId = (body.appId ?? "").trim();
|
|
15825
|
+
const appSecret = (body.appSecret ?? "").trim();
|
|
15826
|
+
if (appId === "" || appSecret === "") {
|
|
15827
|
+
sendJson$2(res, 400, { error: "appId 与 appSecret 都必须填(只填一个等于没绑)" });
|
|
15828
|
+
return;
|
|
15829
|
+
}
|
|
15830
|
+
const { readWeixinSettings, upsertWeixinAccount, writeFeishuCredential, nextFeishuAccountId } = await import("./route-COOFLTpU.js").then((n) => n.c);
|
|
15831
|
+
const { syncCccBridge } = await Promise.resolve().then(() => bridge_exports);
|
|
15832
|
+
const before = readWeixinSettings(root);
|
|
15833
|
+
const accountId = (body.accountId ?? "").trim() !== "" ? body.accountId.trim() : nextFeishuAccountId(before);
|
|
15834
|
+
upsertWeixinAccount(root, {
|
|
15835
|
+
accountId,
|
|
15836
|
+
name: (body.name ?? "").trim() !== "" ? body.name.trim() : void 0,
|
|
15837
|
+
channel: "feishu",
|
|
15838
|
+
enabled: true
|
|
15839
|
+
});
|
|
15840
|
+
writeFeishuCredential(root, accountId, {
|
|
15841
|
+
appId,
|
|
15842
|
+
appSecret
|
|
15843
|
+
});
|
|
15844
|
+
if (before.enabled) syncCccBridge(ctx, root);
|
|
15845
|
+
sendJson$2(res, 200, {
|
|
15846
|
+
accountId,
|
|
15847
|
+
channel: "feishu",
|
|
15848
|
+
saved: true
|
|
15849
|
+
});
|
|
15850
|
+
return;
|
|
15851
|
+
}
|
|
15732
15852
|
sendJson$2(res, 400, { error: `unsupported action: ${body.action ?? ""}` });
|
|
15733
15853
|
} catch (err) {
|
|
15734
15854
|
sendJson$2(res, 400, { error: err.message ?? String(err) });
|
|
@@ -15757,7 +15877,7 @@ function registerStatusApi(ctx, opts = {}) {
|
|
|
15757
15877
|
return;
|
|
15758
15878
|
}
|
|
15759
15879
|
const { pollQRStatus } = await import("./ilink-BMk2w2KY.js").then((n) => n.a);
|
|
15760
|
-
const { readWeixinSettings } = await import("./route-
|
|
15880
|
+
const { readWeixinSettings } = await import("./route-COOFLTpU.js").then((n) => n.c);
|
|
15761
15881
|
const settings = readWeixinSettings(login.root);
|
|
15762
15882
|
const status = await pollQRStatus({
|
|
15763
15883
|
baseUrl: void 0,
|
|
@@ -15772,7 +15892,7 @@ function registerStatusApi(ctx, opts = {}) {
|
|
|
15772
15892
|
});
|
|
15773
15893
|
return;
|
|
15774
15894
|
}
|
|
15775
|
-
const { upsertWeixinAccount, writeWeixinCredential, nextWeixinAccountId } = await import("./route-
|
|
15895
|
+
const { upsertWeixinAccount, writeWeixinCredential, nextWeixinAccountId } = await import("./route-COOFLTpU.js").then((n) => n.c);
|
|
15776
15896
|
const { syncCccBridge } = await Promise.resolve().then(() => bridge_exports);
|
|
15777
15897
|
const accountId = nextWeixinAccountId(settings);
|
|
15778
15898
|
upsertWeixinAccount(login.root, {
|
|
@@ -16214,7 +16334,7 @@ const HOST_SERVICES = [
|
|
|
16214
16334
|
name: "get",
|
|
16215
16335
|
kind: "function"
|
|
16216
16336
|
}],
|
|
16217
|
-
impact: "
|
|
16337
|
+
impact: "轨迹约束/cheap-worker 无法创建或复用 agent",
|
|
16218
16338
|
required: true
|
|
16219
16339
|
},
|
|
16220
16340
|
{
|
|
@@ -16637,7 +16757,7 @@ function summarizeHostContract(report) {
|
|
|
16637
16757
|
*
|
|
16638
16758
|
* ## 现状 → 本模块要收掉什么(取证:`…/acc-c4c5-current-state.md` §①/§③ 3.1)
|
|
16639
16759
|
*
|
|
16640
|
-
* 插件自己 spawn 了**三个** node:http 监听器(B 3081 网关 / C 3082 微信发送 / D 3099
|
|
16760
|
+
* 插件自己 spawn 了**三个** node:http 监听器(B 3081 网关 / C 3082 微信发送 / D 3099 轨迹约束调试)。
|
|
16641
16761
|
* ⚠️ **2026-10-15 变更**:原为四个(含 **E 3100 ACP+问答**),E 已随 owner「砍掉外部问答」令**整面退役**
|
|
16642
16762
|
* ⇒ 仍保有面 B/C/D 三个;本段保留"当年是四个"的记述,因为下方"两套拆卸"的病史由它解释。
|
|
16643
16763
|
* 此前四处各写一份同款样板:`createServer` + `server.once('error')` +
|
|
@@ -17808,9 +17928,9 @@ function sessionNameFromMdPath(mdPath) {
|
|
|
17808
17928
|
* → ②b **权威绑定**(`AGENT_SESSIONS/.bindings.json`,本会话持久绑定)→ ③ surface 首条锚点
|
|
17809
17929
|
* (events 异常时兜底)。
|
|
17810
17930
|
*
|
|
17811
|
-
* v1.30.13(S142 用户"微信桥 +
|
|
17931
|
+
* v1.30.13(S142 用户"微信桥 + 轨迹约束会话锚定要准确",诊断 D4):**移除**原 ④「全局最新未完成
|
|
17812
17932
|
* 会话」(`findLatestActiveSessionMd`)。理由(R↓):④ 是**跨轨迹**猜测——候选①②③全空时,
|
|
17813
|
-
* 它会把 rebuild 静默接到 AGENT_SESSIONS 里最新的另一个会话(临时
|
|
17933
|
+
* 它会把 rebuild 静默接到 AGENT_SESSIONS 里最新的另一个会话(临时 轨迹约束会话 persistent=false、
|
|
17814
17934
|
* 重启后首条消息前、新会话首条消息都命中该形态),而"接错轨迹"比重建失败危险得多
|
|
17815
17935
|
* (错误轨迹被继续写入 + 正确轨迹静默丢失)。现在全部候选失败 → 返回 null → 调用方
|
|
17816
17936
|
* 报错引导用户显式 `cognitive_trajectory use`(响亮失败优于静默接错)。
|
|
@@ -19856,9 +19976,9 @@ const MECHANISM_WORDS = [
|
|
|
19856
19976
|
"拦截缝",
|
|
19857
19977
|
"装配层",
|
|
19858
19978
|
"acp-core",
|
|
19859
|
-
"
|
|
19860
|
-
"
|
|
19861
|
-
"
|
|
19979
|
+
"trajectory-session",
|
|
19980
|
+
"trajectory-window",
|
|
19981
|
+
"trajectory-constraint",
|
|
19862
19982
|
"output-guard"
|
|
19863
19983
|
];
|
|
19864
19984
|
/**
|
|
@@ -19958,20 +20078,20 @@ const rebukeStates = /* @__PURE__ */ new Map();
|
|
|
19958
20078
|
/**
|
|
19959
20079
|
* 外部面会话判定(v1.26.3 用户拍板:仅外部面检测,本地维护会话豁免)。
|
|
19960
20080
|
*
|
|
19961
|
-
* 🔴 **2026-10-19
|
|
19962
|
-
* **「该会话绑定的轨迹有 `
|
|
19963
|
-
*
|
|
19964
|
-
* "挂在
|
|
20081
|
+
* 🔴 **2026-10-19 判据更换**:轨迹约束那一条从「sessionId 前缀 `轨迹约束-`」改为
|
|
20082
|
+
* **「该会话绑定的轨迹有 `trajectory-constraint.json`」**(owner 重做令)。前缀判据在新模型下是
|
|
20083
|
+
* **错的**——轨迹约束的身份是轨迹,不再由 id 前缀表达;继续用前缀会漏掉所有
|
|
20084
|
+
* "挂在 轨迹约束轨迹上、但 id 不带前缀"的会话(典型 = 微信桥按轨迹派生的会话 id)。
|
|
19965
20085
|
*
|
|
19966
|
-
* ⚠️ `rebuild-` 前缀保留(那是另一件事:重建会话同属对外面,与
|
|
20086
|
+
* ⚠️ `rebuild-` 前缀保留(那是另一件事:重建会话同属对外面,与 轨迹约束 无关);
|
|
19967
20087
|
* `acp-` 分支保留但**已是死判据**(无生产者;面 E 已于 2026-10-15 退役)——
|
|
19968
20088
|
* 理由与"为何不擅自删"见文件头注释。
|
|
19969
20089
|
*/
|
|
19970
20090
|
function isExternalFaceSession(sessionId, root) {
|
|
19971
20091
|
if (!sessionId) return false;
|
|
19972
|
-
return
|
|
20092
|
+
return isTrajectoryConstrained(root, sessionId) || sessionId.startsWith("acp-") || sessionId.startsWith("rebuild-");
|
|
19973
20093
|
}
|
|
19974
|
-
/** 读会话最后一个 assistant/message 文本(
|
|
20094
|
+
/** 读会话最后一个 assistant/message 文本(trajectory-session 同款)
|
|
19975
20095
|
* **v1.63.3 起唯一的导出理由消失**:原导出是给 `unattended-seam.ts` 复用的(D77),
|
|
19976
20096
|
* 该文件已随无人值守功能整条退役 ⇒ 现只有本模块自己用(`isLeak` 的调用点)。
|
|
19977
20097
|
* ⚠️ 保持 `export` 无害(它有测试覆盖,且将来"同一读法只留一份"的纪律仍成立),
|
|
@@ -20445,31 +20565,6 @@ function invokeWeixinHook(root, hookRel, event) {
|
|
|
20445
20565
|
return activeRunner(root, hookRel, event);
|
|
20446
20566
|
}
|
|
20447
20567
|
//#endregion
|
|
20448
|
-
//#region src/im/accounts.ts
|
|
20449
|
-
const impls = /* @__PURE__ */ new Map();
|
|
20450
|
-
/** 注册账号实现(`index.ts` 装配时调用;重复 channel 覆盖——测试与热重载友好) */
|
|
20451
|
-
function registerImAccountImpl(impl) {
|
|
20452
|
-
impls.set(impl.channel, impl);
|
|
20453
|
-
}
|
|
20454
|
-
/** 取实现(未注册 ⇒ `null`,由调用方**响亮出声**,不要静默跳过) */
|
|
20455
|
-
function getImAccountImpl(channel) {
|
|
20456
|
-
return impls.get(channel) ?? null;
|
|
20457
|
-
}
|
|
20458
|
-
/** 已注册的实现名(诊断与错误文案用;顺序 = 注册顺序) */
|
|
20459
|
-
function imAccountChannels() {
|
|
20460
|
-
return [...impls.keys()];
|
|
20461
|
-
}
|
|
20462
|
-
/**
|
|
20463
|
-
* 账号行的渠道归一(**单一处**)。
|
|
20464
|
-
*
|
|
20465
|
-
* 缺省 `weixin`:老配置平移到 `im` 段时不必补这个字段——owner 的"统一配置"
|
|
20466
|
-
* 要能直接吃旧数据(与 `route.ts` 的 `normalizeImSection` 保持同一口径)。
|
|
20467
|
-
*/
|
|
20468
|
-
function normalizeAccountChannel(channel) {
|
|
20469
|
-
const v = (channel ?? "").trim();
|
|
20470
|
-
return v === "" ? "weixin" : v;
|
|
20471
|
-
}
|
|
20472
|
-
//#endregion
|
|
20473
20568
|
//#region src/im/weixin-account.ts
|
|
20474
20569
|
/** 轮询间隔(getupdates 失败后重试;成功 = 立即续,长轮询本身 hold 35s) */
|
|
20475
20570
|
const POLL_RETRY_MS = 3e3;
|
|
@@ -20546,7 +20641,7 @@ function installWeixinAccountImpl(d) {
|
|
|
20546
20641
|
const manualSessions = /* @__PURE__ */ new Map();
|
|
20547
20642
|
/**
|
|
20548
20643
|
* 本轮是否已成功发送(**由桥在每轮开始时清空** `clearSentThisTurn`;闸门只置位不清空——
|
|
20549
|
-
* v1.30.17 起桥要在 `
|
|
20644
|
+
* v1.30.17 起桥要在 `askTrajectory` 返回后读它决定是否兜底,故 turn-stopping 不再结算清空)。
|
|
20550
20645
|
*/
|
|
20551
20646
|
const sentThisTurn = /* @__PURE__ */ new Set();
|
|
20552
20647
|
/** 连续打回计数(成功发送即清零) */
|
|
@@ -20614,7 +20709,7 @@ function buildManualOutputRebuke(session) {
|
|
|
20614
20709
|
}
|
|
20615
20710
|
/**
|
|
20616
20711
|
* 装配闸门(index.ts apply 调用)。
|
|
20617
|
-
* 只对**已登记的手动模式会话**生效——其他会话(主舱/其他
|
|
20712
|
+
* 只对**已登记的手动模式会话**生效——其他会话(主舱/其他 轨迹约束会话)零干预。
|
|
20618
20713
|
*/
|
|
20619
20714
|
function registerWeixinOutputGuard(ctx) {
|
|
20620
20715
|
try {
|
|
@@ -20681,7 +20776,7 @@ const MEDIA_MAX_BYTES = 20971520;
|
|
|
20681
20776
|
*/
|
|
20682
20777
|
async function resolveCccRootByAlias(ctx, alias) {
|
|
20683
20778
|
try {
|
|
20684
|
-
const { listCccs } = await import("./ccc-roots-
|
|
20779
|
+
const { listCccs } = await import("./ccc-roots-B2t_aM8L.js").then((n) => n.a);
|
|
20685
20780
|
const entries = await listCccs(ctx);
|
|
20686
20781
|
const byName = entries.find((e) => e.name === alias);
|
|
20687
20782
|
if (byName) return byName.root;
|
|
@@ -20749,7 +20844,7 @@ async function resolveInboundTarget(ctx, input) {
|
|
|
20749
20844
|
});
|
|
20750
20845
|
return null;
|
|
20751
20846
|
}
|
|
20752
|
-
const { findDirNameByCode } = await import("./profile-
|
|
20847
|
+
const { findDirNameByCode } = await import("./profile-BVt84mbi.js").then((n) => n.i);
|
|
20753
20848
|
if (findDirNameByCode(root, target.role) === null) {
|
|
20754
20849
|
const detail = `目标 CCC "${target.ccc}" 内没有编码为 "${target.role}" 的认知轨迹`;
|
|
20755
20850
|
console.error(`[serenity-hooks] ✗ human-channel: ${detail}(account=${input.account} user=${input.user})—— 该消息**未投递**,请改订阅表里的目标编码`);
|
|
@@ -20763,7 +20858,7 @@ async function resolveInboundTarget(ctx, input) {
|
|
|
20763
20858
|
});
|
|
20764
20859
|
return null;
|
|
20765
20860
|
}
|
|
20766
|
-
const profile =
|
|
20861
|
+
const profile = readTrajectoryConstraintByCode(root, target.role);
|
|
20767
20862
|
return {
|
|
20768
20863
|
root,
|
|
20769
20864
|
trajectory: target.role,
|
|
@@ -20814,13 +20909,13 @@ async function handleInboundEvent(ctx, root, accountId, io, event, preset) {
|
|
|
20814
20909
|
}
|
|
20815
20910
|
try {
|
|
20816
20911
|
const sessionId = weixinSessionIdForTrajectory(trajectory);
|
|
20817
|
-
const existing =
|
|
20912
|
+
const existing = getTrajectoryAgent(sessionId);
|
|
20818
20913
|
const hc = readCheapWorkerConfig(root);
|
|
20819
20914
|
const ref = existing ? {
|
|
20820
20915
|
agent: existing,
|
|
20821
20916
|
sessionId,
|
|
20822
20917
|
resumed: true
|
|
20823
|
-
} : await
|
|
20918
|
+
} : await createTrajectoryAgent(ctx, root, profile ?? {
|
|
20824
20919
|
dirName: trajectory,
|
|
20825
20920
|
code: trajectory,
|
|
20826
20921
|
path: "",
|
|
@@ -20885,7 +20980,7 @@ async function handleInboundEvent(ctx, root, accountId, io, event, preset) {
|
|
|
20885
20980
|
}
|
|
20886
20981
|
}
|
|
20887
20982
|
const parts = [];
|
|
20888
|
-
const workspaceMd =
|
|
20983
|
+
const workspaceMd = trajectoryWorkspaceMdPath(root, sessionId) ?? getActiveSessionInfo(sessionId)?.mdPath ?? null;
|
|
20889
20984
|
if (workspaceMd) {
|
|
20890
20985
|
if (!ensureWorkspacePromptSection(ref.agent, sessionId, root)) parts.push(workspaceCognitiveTrajectoryLine(workspaceMd));
|
|
20891
20986
|
}
|
|
@@ -20915,7 +21010,7 @@ async function handleInboundEvent(ctx, root, accountId, io, event, preset) {
|
|
|
20915
21010
|
})).catch((err) => {
|
|
20916
21011
|
console.log(`[serenity-hooks] im hook incoming error: ${err instanceof Error ? err.message : String(err)}`);
|
|
20917
21012
|
});
|
|
20918
|
-
const answer = (await
|
|
21013
|
+
const answer = (await askTrajectory(ctx, ref.agent, question)).answer ?? "";
|
|
20919
21014
|
if (manualOutput) {
|
|
20920
21015
|
if (!manualOutputFallbackNeeded({
|
|
20921
21016
|
fallbackOnNoSend: settings.fallbackOnNoSend,
|
|
@@ -21561,7 +21656,7 @@ function syncHumanChannel(ctx) {
|
|
|
21561
21656
|
(async () => {
|
|
21562
21657
|
try {
|
|
21563
21658
|
stopMachineLoops();
|
|
21564
|
-
const { listCccs } = await import("./ccc-roots-
|
|
21659
|
+
const { listCccs } = await import("./ccc-roots-B2t_aM8L.js").then((n) => n.a);
|
|
21565
21660
|
for (const entry of await listCccs(ctx)) syncCccBridge(ctx, entry.root);
|
|
21566
21661
|
} catch {}
|
|
21567
21662
|
})();
|
|
@@ -21825,7 +21920,7 @@ function matchCcc(input, candidates) {
|
|
|
21825
21920
|
}
|
|
21826
21921
|
/** 组装候选列表(`ccc-roots.listCccs` 投影 + `.serenity` 名;动态 import 保持本模块静态依赖轻量) */
|
|
21827
21922
|
async function collectCandidates(ctx) {
|
|
21828
|
-
const { listCccs } = await import("./ccc-roots-
|
|
21923
|
+
const { listCccs } = await import("./ccc-roots-B2t_aM8L.js").then((n) => n.a);
|
|
21829
21924
|
const entries = await listCccs(ctx, { defaultRoot: process.cwd() });
|
|
21830
21925
|
const seen = /* @__PURE__ */ new Set();
|
|
21831
21926
|
const out = [];
|
|
@@ -22045,7 +22140,7 @@ function sessionIdOf(value) {
|
|
|
22045
22140
|
/** 单会话内存态清理(幂等;无对应状态即 no-op) */
|
|
22046
22141
|
function cleanupSessionState(sessionId) {
|
|
22047
22142
|
try {
|
|
22048
|
-
|
|
22143
|
+
unregisterTrajectorySession(sessionId);
|
|
22049
22144
|
} catch {}
|
|
22050
22145
|
try {
|
|
22051
22146
|
clearActiveSessionInfo(sessionId);
|
|
@@ -22066,7 +22161,7 @@ function cleanupSessionState(sessionId) {
|
|
|
22066
22161
|
forgetHostScheduleToolHiding(sessionId);
|
|
22067
22162
|
} catch {}
|
|
22068
22163
|
try {
|
|
22069
|
-
|
|
22164
|
+
forgetTrajectoryConstraintToolVisibility(sessionId);
|
|
22070
22165
|
} catch {}
|
|
22071
22166
|
try {
|
|
22072
22167
|
forgetLogbookCompactionState(sessionId);
|
|
@@ -22097,7 +22192,7 @@ function registerLifecycle(ctx) {
|
|
|
22097
22192
|
stopAllBridges();
|
|
22098
22193
|
} catch {}
|
|
22099
22194
|
};
|
|
22100
|
-
registerDisposer(ctx, "self-started resources (gateway/weixin/
|
|
22195
|
+
registerDisposer(ctx, "self-started resources (gateway/weixin/trajectory-debug faces + weixin bridges)", disposeAll);
|
|
22101
22196
|
}
|
|
22102
22197
|
//#endregion
|
|
22103
22198
|
//#region src/patches/web-fetch.ts
|
|
@@ -22691,7 +22786,7 @@ async function applyOpencodeAutoConfigOnce(ctx, env) {
|
|
|
22691
22786
|
*
|
|
22692
22787
|
* 为什么要重试:`llm-pi-ai` 只在**它自己的** `ctx.inject(['settings'])` 回调里注册命名空间,
|
|
22693
22788
|
* 与本插件 apply **无先后保证**(两者都依赖 settings 服务)。首次读取拿到 undefined 是常态而非错误
|
|
22694
|
-
* (与
|
|
22789
|
+
* (与 轨迹约束的 CCC root 重试同款问题、同款处置:v1.30.13 实证"只在 apply 时试一次 → 永不重试")。
|
|
22695
22790
|
*/
|
|
22696
22791
|
const NAMESPACE_RETRY_DELAYS_MS$1 = [
|
|
22697
22792
|
1e3,
|
|
@@ -22703,7 +22798,7 @@ const NAMESPACE_RETRY_DELAYS_MS$1 = [
|
|
|
22703
22798
|
/**
|
|
22704
22799
|
* 装配 opencode 路由自动配置。
|
|
22705
22800
|
*
|
|
22706
|
-
* 触发面(三层,与
|
|
22801
|
+
* 触发面(三层,与 轨迹约束 同款;R↓:单点触发在同装载竞态下必然漏):
|
|
22707
22802
|
* ① apply 时立即试一次
|
|
22708
22803
|
* ② 命名空间尚未注册 → 退避重试(1s/3s/8s/20s/40s)
|
|
22709
22804
|
* ③ `settings/updated` → 再试(用户热改 settings.yaml 加路由时自愈;自身写入触发的复评是幂等空转)
|
|
@@ -24298,7 +24393,7 @@ function renderDsl(model) {
|
|
|
24298
24393
|
* 🔴 **为什么 PNG 是必需的**:宿主的对话内图片通路**白名单只有光栅格式**
|
|
24299
24394
|
* (`image/png|jpeg|webp|gif`,见 `dsh-attachment/types.d.ts`)⇒ **SVG 进不去**。
|
|
24300
24395
|
*
|
|
24301
|
-
* 🔴 **开关**:由 `index.ts` 门控(**关时不注册** ⇒ 工具根本不进模型工具清单),同
|
|
24396
|
+
* 🔴 **开关**:由 `index.ts` 门控(**关时不注册** ⇒ 工具根本不进模型工具清单),同 轨迹约束 口径。
|
|
24302
24397
|
*
|
|
24303
24398
|
* ## 🔴 两条**宿主契约**(2026-09-29 现场取证;第 ① 条是本文件第一版写错的地方)
|
|
24304
24399
|
*
|
|
@@ -24769,7 +24864,7 @@ function apply(ctx, config) {
|
|
|
24769
24864
|
* ① 听宿主的**真事件** `settings/document-updated`(ns = 我们那张设置页)——顺带说明为什么不用
|
|
24770
24865
|
* 我们自己的 `serenity/settings-changed`:那个事件**全仓没有任何 `emit` 点**(只有 `ctx.on` 侧
|
|
24771
24866
|
* 在用)=**死通道**,照着它接线等于接了个永远不响的铃;
|
|
24772
|
-
* ② 兜底再挂 `agent/created` / `session/created`(与
|
|
24867
|
+
* ② 兜底再挂 `agent/created` / `session/created`(与 轨迹约束 同款做法,那两条在实测里确实会 fire)
|
|
24773
24868
|
* —— 宿主事件面若换名,至少还有这一条能自愈;
|
|
24774
24869
|
* ③ 启动时同步一次(apply 时按当前配置定初始态)。
|
|
24775
24870
|
* 🔴 **关的时候要注销**(不是"留着但不响应"):与"关时不注册"同一口径 —— 工具不出现在模型清单里。
|