@shgroup/dsh-serenity-hooks 1.73.0 → 1.75.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.
Files changed (48) hide show
  1. package/README.md +18 -12
  2. package/dsh.plugin.json +2 -2
  3. package/lib/accounts-BHQbakUh.js +33 -0
  4. package/lib/{ccc-CZUOq3NY.js → ccc-CyYJL4F2.js} +9 -9
  5. package/lib/{ccc-roots-BhoA7G-0.js → ccc-roots-BVxn97dY.js} +4 -4
  6. package/lib/client.js +116 -1
  7. package/lib/cro/core.d.ts +11 -1
  8. package/lib/cro/log.d.ts +54 -4
  9. package/lib/guard/seam.d.ts +4 -4
  10. package/lib/host/effect.d.ts +1 -1
  11. package/lib/im/bridge.d.ts +3 -3
  12. package/lib/im/endpoint.d.ts +2 -2
  13. package/lib/im/guard.d.ts +1 -1
  14. package/lib/im/hook.d.ts +1 -1
  15. package/lib/im/inbound-event.d.ts +6 -6
  16. package/lib/im/route.d.ts +25 -3
  17. package/lib/index.d.ts +5 -5
  18. package/lib/index.js +680 -295
  19. package/lib/kernel/ccc-roots.d.ts +4 -4
  20. package/lib/kernel/ccc.d.ts +20 -19
  21. package/lib/kernel/ports.d.ts +1 -1
  22. package/lib/{message-source-ChyVfev9.js → message-source-JhN-qyfn.js} +2 -2
  23. package/lib/patches/opencode-provider.d.ts +1 -1
  24. package/lib/{profile-BIQ2widI.js → profile-Cz4oBXlI.js} +197 -65
  25. package/lib/{route-ItCgAsnz.js → route-COOFLTpU.js} +44 -6
  26. package/lib/seams/guards.d.ts +14 -14
  27. package/lib/seams/keeper.d.ts +1 -1
  28. package/lib/seams/lifecycle.d.ts +2 -2
  29. package/lib/{settings-UcwIPJhM.js → settings-CII0Y597.js} +40 -26
  30. package/lib/surface/faces.d.ts +1 -1
  31. package/lib/surface/http.d.ts +1 -1
  32. package/lib/surface/settings.d.ts +7 -7
  33. package/lib/surface/text.d.ts +3 -3
  34. package/lib/tools/config-docs.d.ts +71 -0
  35. package/lib/tools/container-admin.d.ts +24 -12
  36. package/lib/tools/msm/service.d.ts +18 -0
  37. package/lib/tools/trajectory-constraint-admin.d.ts +30 -0
  38. package/lib/trajectory/carrier.d.ts +1 -1
  39. package/lib/trajectory/core.d.ts +11 -1
  40. package/lib/trajectory/policy/files.d.ts +75 -0
  41. package/lib/trajectory/policy/profile.d.ts +41 -36
  42. package/lib/trajectory/policy/rules.d.ts +33 -20
  43. package/lib/trajectory/policy/window.d.ts +13 -13
  44. package/lib/trajectory/prompt.d.ts +12 -12
  45. package/lib/trajectory/rebuild.d.ts +2 -2
  46. package/lib/trajectory/session.d.ts +49 -49
  47. package/package.json +2 -2
  48. 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-BhoA7G-0.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-CZUOq3NY.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 readSkiffProfileAt, c as registerSkiffSession$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 skiffTrajectoryDirNameFor, m as archiveSession, n as isSkiffSession, o as readSkiffProfileByCode, p as UID_FILENAME, r as listSkiffProfiles, s as skiffProfileForSession, t as SKIFF_PROFILE_FILENAME, u as unregisterSkiffSession$1, v as findSession, w as parseSessionContextFromEvents, x as listActiveTrajectories, y as getActiveSessionInfo } from "./profile-BIQ2widI.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-ItCgAsnz.js";
6
- import { a as buildSkiffBasePrompt, c as profileMsmWhitelist, d as resolveProfileSystemPrompt, f as ACC_VERSION, i as SKIFF_PROFILE_HINT, l as profileSystemPromptSource, n as readSimpleSettings, o as createProfilePromptReader, p as findPackageRoot, r as registerSettingsSection, s as profileCognitiveTrajectory, u as profileToolWhitelist } from "./settings-UcwIPJhM.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-ChyVfev9.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-BVxn97dY.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, B as localHuman, C as namingTitleFor, D as readTrajectoryUid, E as readActiveSessionMd, F as TRAJECTORY_FILENAME, I as TRAJECTORY_LEGACY_FILENAMES, L as readTrajectoryFile, M as showSession, N as summarize, O as resolveSessionByTitle, P as useSession, R as isoLocal, S as listSessions, T as parseTrajectoryCode, V as localIdStamp, _ 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, z as localFileStamp } from "./profile-Cz4oBXlI.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-CII0Y597.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-JhN-qyfn.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 → skiff-core → `@deepseek-ai/dsh-llm`)——
981
- * 后果实测:`acc-extras` / `ops` / `skiff-admin` 三个测试套件因解析不到宿主包而**整文件加载失败**
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`(等待结算)。
@@ -993,6 +994,68 @@ function weixinSendEndpoint() {
993
994
  return endpoint;
994
995
  }
995
996
  //#endregion
997
+ //#region src/tools/config-docs.ts
998
+ /**
999
+ * 渲染**目录**(第一层)。
1000
+ *
1001
+ * 🔴 只调 `summary`,**绝不调 `render`** —— 目录若触发正文,就退化成"整份长文"。
1002
+ * 这条不是性能优化,是**判据**:它保证 `docs` 的输出大小与章数成正比,与正文长度无关。
1003
+ */
1004
+ function renderDocsIndex(chapters) {
1005
+ const lines = [
1006
+ "═══ CCC 配置说明 · 目录 ═══",
1007
+ "",
1008
+ "说明类内容已超过单次返回的合适篇幅 ⇒ 先读目录,再按章取详情。",
1009
+ "",
1010
+ "用法:container_admin config docs <章节>",
1011
+ ""
1012
+ ];
1013
+ for (const ch of chapters) lines.push(` ${ch.id.padEnd(14)}${ch.summary}`);
1014
+ lines.push("");
1015
+ lines.push("旧入口(msm catalog / msm ccc-config / config guide)**降为指针**,只指路、不再返回正文。");
1016
+ return lines.join("\n");
1017
+ }
1018
+ /**
1019
+ * 解析一个章节参数(第二层)。
1020
+ *
1021
+ * 不存在 ⇒ 返回 `null`,由调用方给出**带候选清单**的拒绝 ——
1022
+ * "不知道这个章节" 与 "这一章是空的" 必须可区分(否则用户以为功能没有)。
1023
+ */
1024
+ function findDocsChapter(chapters, id) {
1025
+ const want = id.trim().toLowerCase();
1026
+ if (want === "") return null;
1027
+ return chapters.find((c) => c.id.toLowerCase() === want) ?? null;
1028
+ }
1029
+ /**
1030
+ * **指针文案**(v1.75.0,owner 裁 Q3「指针」)——被降级的旧入口共用这一份。
1031
+ *
1032
+ * ## 为什么要有"指针"这个形态(而不是直接删掉旧入口)
1033
+ *
1034
+ * 旧入口的名字(`msm catalog` / `msm ccc-config` / `config guide`)**已经写在 CCC 侧的
1035
+ * 文档与提示词里**,硬删会让它们全部变成"未知动作",而且**没有任何机械信号**告诉我们
1036
+ * 哪份文档已经说谎了(本仓 D151 同族)。
1037
+ *
1038
+ * ⇒ 保留名字、只换返回内容:调用方能**自己顺着指针找到正文**,而正文仍然只有一份。
1039
+ *
1040
+ * 🔴 **必须只有一处实现**:`container-admin.ts`(config 域)与 `msm/service.ts`(msm 域)
1041
+ * 都要用它。两处各写一份文案 ⇒ 措辞漂移(本仓反复踩过的形态)。
1042
+ *
1043
+ * @param chapter 正文所在的章 id
1044
+ * @param what 这个入口原来叫什么(进标题,让调用方一眼确认自己找对了)
1045
+ */
1046
+ function docsPointer(chapter, what) {
1047
+ return [
1048
+ `═══ ${what} — 正文已统一 ═══`,
1049
+ "",
1050
+ `正文在:container_admin config docs ${chapter}`,
1051
+ "",
1052
+ "为什么改(v1.75.0,owner 裁「指针」):这个内容此前有**两个**正文入口,",
1053
+ "而它们返回同一份常量 ⇒ 两个真相源。现在「一个内容一个正文入口」,本入口只指路。",
1054
+ "",
1055
+ "索引(不知道还有哪些章):container_admin config docs"
1056
+ ].join("\n");
1057
+ }
1058
+ //#endregion
996
1059
  //#region src/tools/msm/service.ts
997
1060
  /**
998
1061
  * tools/msm/service.ts — msm 纯操作层(acc_msm → msm 执行 + container_admin 管理面,v1.30;零 DSH 依赖)
@@ -1068,6 +1131,10 @@ const NPX_BIN = process.platform === "win32" ? "npx.cmd" : "npx";
1068
1131
  * 目录在前、详情各归各——**单一真相源**:本目录只索引"去哪个工具/子命令看详情",不复制详情
1069
1132
  * (避免 container_admin/weixin-doctor 等 guide 全文重复 → 多真相源 → 失同步)。
1070
1133
  * 后续新增功能只需在此加一行(低熵可演进)。
1134
+ *
1135
+ * 🔴 **2026-10-19 v1.75.0 起导出**:它是 `container_admin config docs catalog` 那一章的正文。
1136
+ * `msm catalog` 动作**降为指针**(只指路,不再返回本常量)—— 否则同一份正文会有两个入口,
1137
+ * 而"两个入口返回同一份内容"正是本目录自己反对的形态。
1071
1138
  */
1072
1139
  const ACC_CATALOG = `═══ ACC Usage Catalog ═══
1073
1140
  Directory of ACC capabilities — each area lists where to go for details (guides live with their feature; this catalog only points).
@@ -1083,18 +1150,19 @@ Directory of ACC capabilities — each area lists where to go for details (guide
1083
1150
  acc_msm 已并入 msm(执行+发现)与 container_admin(管理)——msm("<name>", ["<args>"]);inspect=true 查用法
1084
1151
  cheap-worker(杂工 agent 编排——guide 子命令:cheap-worker guide)
1085
1152
  专属工具(**默认对所有 CCC 隐藏**,只有在自己 exclusiveTools 里点名的 CCC 可见)→ acc-diag(运行态诊断)
1086
- ④ 角色与对外面 → container_admin role(Skiff 认知子集角色:guide/validate/apply/list——skiff_admin 已并入)
1087
- 3099 Skiff 调试问答页(人工调试面)。外部程序化接入用官方 dsh --profile acp
1153
+ ④ 轨迹约束与对外面 → container_admin config(轨迹约束四动作 guide/validate/apply/list——**原 role 域已并入 config**,旧名作过渡别名仍可用;说明类配置走**两层菜单** config docs 目录 / config docs <章节> 详情)
1154
+ 外部程序化接入用官方 dsh --profile acp
1088
1155
  + 现成 ACP 客户端;本插件不提供 ACP 面
1089
1156
  ⑤ 自主与接入 → cognitive_trajectory send-now(跨轨迹的**即时**递送,**不排队**:live 目标在跑就当场注入当前轮、空闲则立即起轮;非 live 冷载入——等效于直接 wake;**有同步回执**)
1090
1157
  cognitive_trajectory send-later(跨轨迹的"未来时刻 + 一条 message";fire-and-forget,无回执无回收)
1091
1158
  weixin 微信桥(配置/扫码/路由/消息 hook——CCC 侧 weixin-doctor MSM:msm("weixin-doctor", ["guide"]))
1092
- ⑥ CCC 配置总览 → container_admin msm ccc-config(9 段:cheap-worker/sessionKeeper/localstore/hooks/safeMode/skiff/cognitiveTrajectory/weixin/**exclusiveTools**)
1159
+ ⑥ CCC 配置总览 → container_admin config docs config(按章详情——9 段全表)
1160
+ 或 container_admin msm ccc-config(同一份正文的旧入口,保留)
1093
1161
  ⑦ 注册表与安全 → mech-registry.json 由 container_admin msm register/deregister 管理(写保护——不可直接编辑)
1094
1162
  dashboard health 输出 registry 完整性检查;损坏恢复指引见 health registry.issues
1095
1163
  CCC 级开关:exclusiveTools(声明专属工具)/ safeMode.blacklist(写路径黑名单)
1096
1164
  / safeMode.hiddenTools(**安全模式开启期间**额外隐藏的工具,v1.51.4)
1097
- CCC 级 skill 供给:cognitiveTrajectory.skills(v1.44.0——**本 CCC 每条轨迹都带**,含 skiff 会话;与轨迹自己的 frontmatter 声明取并集)
1165
+ CCC 级 skill 供给:cognitiveTrajectory.skills(v1.44.0——**本 CCC 每条轨迹都带**,含 轨迹约束会话;与轨迹自己的 frontmatter 声明取并集)
1098
1166
 
1099
1167
  每区:一句话定位 + 详细入口(工具名 / guide 子命令)。详情永远以对应工具的 guide/ccc-config 为单一真相源。
1100
1168
  `;
@@ -1151,7 +1219,12 @@ msm("<name>", ["--list"]) — list all MSMs
1151
1219
  msm("<name>", ["--schema", "<n>"]) — view a specific MSM's parameter schema
1152
1220
  msm("<name>", ["--format=json"]) — JSON output mode (remaining args passed through losslessly)
1153
1221
  `;
1154
- /** CCC 配置参考(对齐 osp ccc-config action) */
1222
+ /**
1223
+ * CCC 配置参考(对齐 osp ccc-config action)。
1224
+ *
1225
+ * 🔴 **2026-10-19 起导出**:`container_admin config docs config` 的两层菜单把它作为
1226
+ * 「config」章正文返回。**不另写一份**——同一段说明有第二份副本就会漂(本轨反复踩过)。
1227
+ */
1155
1228
  const CCC_CONFIG_REFERENCE = `═══ CCC Configuration Reference ═══
1156
1229
 
1157
1230
  CCC-level features are configured in .opencode/serenity.json.
@@ -1227,15 +1300,15 @@ safe-mode 由 WebUI 开关控制(写 .serenity-safe-on 标记);黑名单
1227
1300
  Config:
1228
1301
  { "safeMode": { "blacklist": [".secrets/"], "hiddenTools": ["workflow", "job_kill"] } }
1229
1302
 
1230
- ── 6. skiff.roles(F4 认知子集角色)──
1231
- Skiff 角色 = 全知全能 cognitive trajectory 的任意子集(CCC 定义)。每角色独立模型 + 双白名单
1303
+ ── 6. 轨迹约束.roles(F4 认知子集角色)──
1304
+ 轨迹约束角色 = 全知全能 cognitive trajectory 的任意子集(CCC 定义)。每角色独立模型 + 双白名单
1232
1305
  (MSM 白名单 msms[] 与非 MSM 工具白名单 tools[],白名单外全隐藏;skill 加载恒可用)。
1233
1306
  cognitive trajectory 纪律子集(session/keeper/rebuild 参与项)默认全关 = 完全独立。
1234
1307
  systemPrompt 内联 或 systemPromptFile(.md 引用,推荐)——角色会话的完整人格/边界提示词。
1235
- validate 校验 / apply 生效 / list 查看:container_admin role <guide|validate|apply|list>(skiff_admin 已并入)。
1308
+ validate 校验 / apply 生效 / list 查看:container_admin config <guide|validate|apply|list>(原 role 域已并入 config,旧名作过渡别名仍可用)。
1236
1309
 
1237
1310
  Config:
1238
- { "skiff": { "roles": {
1311
+ { "轨迹约束": { "roles": {
1239
1312
  "qa": {
1240
1313
  "model": "provider/model",
1241
1314
  "msms": ["web-search", "vlm-describe"],
@@ -1255,12 +1328,12 @@ CCC 的一条 cognitive trajectory = 持久身体(SESSION.md)+ 时间轴(A
1255
1328
  运行态诊断(ACC 负责人专用):acc-diag(① live 运行态 / ①b 唤醒时钟 / ③ 唤醒注册表)。
1256
1329
 
1257
1330
  ▸ cognitiveTrajectory.skills(v1.44.0,**CCC 级 skill 供给**;owner 令 2026-09-20「要求实现我的诉求……
1258
- 注入是动态注入就行;create 先不触发吧;**skiff 也支持**」):
1331
+ 注入是动态注入就行;create 先不触发吧;**轨迹约束也支持**」):
1259
1332
  **本 CCC 的每条轨迹都带这些 skill**——声明的 skill 全文在该轨迹**被绑定期间**注入系统提示词
1260
1333
  (**动态注入**:每请求重新求值、不缓存 ⇒ 改配置或改 frontmatter **即时生效**,无需重新 use、无需重启)。
1261
1334
  两条声明来源**并集**(不是二选一):本键 **cognitiveTrajectory.skills**(容器给的底座,**在前**)
1262
1335
  + 轨迹自己 SESSION.md 顶部 frontmatter 的 skills:(这条轨迹额外的,**追加**;同名去重)。
1263
- ✅ **含 skiff 角色会话**(旁路的是 ACC 身份与纪律;skill 是"工作资料",照给)。
1336
+ ✅ **含 轨迹约束角色会话**(旁路的是 ACC 身份与纪律;skill 是"工作资料",照给)。
1264
1337
  守卫:名字先过安全校验才用于拼路径(防穿越)|**合并后**统一 32 KB 上限截断|找不到响亮写 [缺失]。
1265
1338
  ⚠️ 与 create **无关**(create 刻意不夺绑定,U6)⇒ 注入的门是"**存在绑定**"(cognitive_trajectory use)。
1266
1339
  失败语义:未配置 / 字段缺失 / 配置损坏 ⇒ **空数组**(= 无 CCC 级供给;轨迹级声明照旧生效)。
@@ -1268,7 +1341,7 @@ CCC 的一条 cognitive trajectory = 持久身体(SESSION.md)+ 时间轴(A
1268
1341
 
1269
1342
  Config:
1270
1343
  { "cognitiveTrajectory": {
1271
- "skills": ["acc-session", "cce"], // v1.44.0:本 CCC 的每条轨迹都带(含 skiff 会话)
1344
+ "skills": ["acc-session", "cce"], // v1.44.0:本 CCC 的每条轨迹都带(含 轨迹约束会话)
1272
1345
  "autopilot": { // 原 autopilotTrajectory 段,字段不变(旧键仍回退读)
1273
1346
  "enabled": true,
1274
1347
  "intervalHours": 2,
@@ -1282,7 +1355,7 @@ CCC 的一条 cognitive trajectory = 持久身体(SESSION.md)+ 时间轴(A
1282
1355
  CCC 级微信个人号接入(iLink 协议):dsh 一进程多 CCC,每 CCC 独立对接微信桥。
1283
1356
  账号/路由/开关在此文件;**bot_token 凭据在 CCC localstore credential scope**
1284
1357
  (扫码绑定后自动写入,永不进 git 明文面)。
1285
- 路由 user → role:exact 优先,* 通配兜底;role 必须 ∈ 该 CCC skiff.roles。
1358
+ 路由 user → role:exact 优先,* 通配兜底;role 必须 ∈ 该 CCC 轨迹约束.roles。
1286
1359
  面板(WebUI 设置 → 微信桥)可扫码绑定/移除账号/编辑路由;msm("weixin-doctor", ["status"|"diag"|"verify"|"guide"]) 排查 + 大而全指南。凭据主动查看:localstore get WEIXIN_<ACCOUNT>_TOKEN。
1287
1360
  ▸ 主动发送(v1.31.0:**ACC 工具 im-bridge**):调
1288
1361
  im-bridge({channel:"weixin", action:"send", user:"<别名|id>", text:"<文本>"}) 给用户发消息——
@@ -1290,7 +1363,7 @@ CCC 级微信个人号接入(iLink 协议):dsh 一进程多 CCC,每 CCC
1290
1363
  成功后触发 outgoing hook(source="proactive")。文件用 action:"send-file" + file:"<CCC 内路径>"
1291
1364
  (逃逸/缺失/超 20MB 拒绝)+ 可选 caption。**可见性**:本 CCC 配置了 IM 通道
1292
1365
  (weixin.enabled=true)才出现在模型工具清单里;未配置 → 由 guards 逐 agent
1293
- tools.restrict({deny:['im-bridge']}) 移除(不是"看得到但被拒")。skiff 角色另需在
1366
+ tools.restrict({deny:['im-bridge']}) 移除(不是"看得到但被拒")。轨迹约束角色另需在
1294
1367
  roles.<role>.tools 白名单列出 im-bridge。
1295
1368
  历史(v1.30.9~v1.30.x):CCC 侧 MSM weixin-send(走 loopback HTTP 入口 3082)——
1296
1369
  v1.31.0 已退役(脚本删除 + 注册表注销);3082 入口保留给**非 agent 调用者**。
@@ -1545,7 +1618,7 @@ function runMsm(root, args) {
1545
1618
  return `${header}\n` + lines.join("\n");
1546
1619
  }
1547
1620
  case "guide": return { guide: MSM_GUIDE };
1548
- case "catalog": return { catalog: ACC_CATALOG };
1621
+ case "catalog": return { catalog: docsPointer("catalog", "ACC 能力目录") };
1549
1622
  case "exec": {
1550
1623
  const { entry, businessArgs, fmtJson, hasHelp, protocol } = prepareExec(root, args);
1551
1624
  const p = protocolResult(protocol);
@@ -1669,7 +1742,7 @@ function runMsm(root, args) {
1669
1742
  }
1670
1743
  throw new Error(`MSM not registered: "${name}"`);
1671
1744
  }
1672
- case "ccc-config": return CCC_CONFIG_REFERENCE;
1745
+ case "ccc-config": return docsPointer("config", "CCC 配置参考");
1673
1746
  case "check": return checkRegistryQuality(root);
1674
1747
  default: throw new Error(`Unknown action: ${args.action}`);
1675
1748
  }
@@ -1834,7 +1907,7 @@ async function runMsmAsync(root, args) {
1834
1907
  * **轨迹提示词** = 一条认知轨迹的**可编辑章程声明**,由 ACC 按固定顺序装配进系统提示词。
1835
1908
  * 三个关键词各是一条判据:
1836
1909
  * - **可编辑**:住在 CCC(盘上),改它**不发版**(D23 归属判据的直接推论)
1837
- * - **声明**:它声明**身份/章程/方法论**,**不声明能力**(能力面归 `skiff.json` 白名单与工具 schema)
1910
+ * - **声明**:它声明**身份/章程/方法论**,**不声明能力**(能力面归 `trajectory-constraint.json` 白名单与工具 schema)
1838
1911
  * - **固定顺序**:装配顺序由 ACC 定死(见 `seams/system-prompt.ts` 的 ⑧ 位)
1839
1912
  *
1840
1913
  * ## 为什么取代「人格」
@@ -1849,13 +1922,13 @@ async function runMsmAsync(root, args) {
1849
1922
  *
1850
1923
  * - **跟着轨迹走**:`archive` 时整个目录搬走,章程不会掉队(旧形态 `.opencode/skiff/<name>.md`
1851
1924
  * 与轨迹**分居两处** —— 多条轨迹共用一份文件正是那个后果)。
1852
- * - **每条轨迹都能有**:不只是 skiff。普通轨迹同样需要"我是谁、按什么章程"。
1853
- * - 与 `SESSION.md`(身体:延续/日志)、`skiff.json`(约束面:能力)三件并列,
1925
+ * - **每条轨迹都能有**:不只是 轨迹约束。普通轨迹同样需要"我是谁、按什么章程"。
1926
+ * - 与 `SESSION.md`(身体:延续/日志)、`trajectory-constraint.json`(约束面:能力)三件并列,
1854
1927
  * 各管一件事,不合并(合并会让"改能力"与"改身份"互相牵连)。
1855
1928
  *
1856
1929
  * ## 兼容(不静默坏)
1857
1930
  *
1858
- * `PROMPT.md` 不存在时**不报错**(普通轨迹常态)。skiff 的旧字段 `systemPromptFile`
1931
+ * `PROMPT.md` 不存在时**不报错**(普通轨迹常态)。轨迹约束的旧字段 `systemPromptFile`
1859
1932
  * 在兼容期仍被读取——优先级见 {@link resolveTrajectoryPrompt}。
1860
1933
  */
1861
1934
  /** 轨迹提示词的固定文件名(**判据就是它存在**——不是目录名前缀,也不是配置里的一行) */
@@ -1867,9 +1940,9 @@ function trajectoryPromptPath(root, dirName) {
1867
1940
  /**
1868
1941
  * 该轨迹**有没有**轨迹提示词(单一判据)。
1869
1942
  *
1870
- * 用途:兼容期的"让位"判定 —— 若已有 `PROMPT.md`,skiff 旧字段 `systemPromptFile`
1871
- * 必须**让位**,否则同一份章程会在两处各注入一遍(第 ⑧ 段 + `serenity-skiff` 段)。
1872
- * 🔴 判据只有这一处:别在 skiff 那边自己拼路径再判(两处会漂)。
1943
+ * 用途:兼容期的"让位"判定 —— 若已有 `PROMPT.md`,轨迹约束 旧字段 `systemPromptFile`
1944
+ * 必须**让位**,否则同一份章程会在两处各注入一遍(第 ⑧ 段 + `serenity-轨迹约束` 段)。
1945
+ * 🔴 判据只有这一处:别在 轨迹约束那边自己拼路径再判(两处会漂)。
1873
1946
  */
1874
1947
  function hasTrajectoryPrompt(root, dirName) {
1875
1948
  try {
@@ -2903,7 +2976,7 @@ function verifyTotpCode(secretBase32, code, nowSeconds = nowEpochSeconds()) {
2903
2976
  /**
2904
2977
  * 集中端口表。
2905
2978
  * A=宿主主面(插件不监听)/ B=网关(可配,默认关)/ C=微信发送(默认开,仅 loopback)
2906
- * / D=Skiff 调试(默认关)。
2979
+ * / D=轨迹约束调试(默认关)。
2907
2980
  */
2908
2981
  const FACE_PORTS = {
2909
2982
  main: {
@@ -3263,7 +3336,7 @@ function toolsBlock() {
3263
3336
  " 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
3337
  " 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
3338
  " 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 (Skiff roles: guide/validate/apply/list) / msm (register/deregister/check/guide/catalog/ccc-config) / config (view)",
3339
+ " container_admin — container administration (the maintenance bay): role (轨迹约束 roles: guide/validate/apply/list) / msm (register/deregister/check/guide/catalog/ccc-config) / config (view)",
3267
3340
  "",
3268
3341
  "MSM call (registered CCC MSMs, deterministic Mech & Semi-Mech):",
3269
3342
  " Execute: msm(\"<name>\", [\"<arg1>\", \"<arg2>\"]) — run a registered MSM directly (partial name returns matching candidates)",
@@ -3763,7 +3836,7 @@ function registerEntrySkillSection(agent, root) {
3763
3836
  *
3764
3837
  * **声明来源(两条,并集)**:
3765
3838
  * · **CCC 级**(v1.44.0,owner 令)= `.opencode/serenity.json` 的 `cognitiveTrajectory.skills`
3766
- * ——**本 CCC 的每条轨迹都带**(含 skiff 角色会话),是容器给的底座;
3839
+ * ——**本 CCC 的每条轨迹都带**(含 轨迹约束角色会话),是容器给的底座;
3767
3840
  * · **轨迹级** = 该会话绑定的轨迹(`readLastBound(session).mdPath` = 它的 `SESSION.md`)
3768
3841
  * 顶部 frontmatter 的 `skills:`——这条轨迹额外的。
3769
3842
  * 合并规则与顺序见 `cognitive-trajectory-skills.mergeSkillNames`(CCC 级在前,同名去重)。
@@ -3960,36 +4033,36 @@ Before calling cheap-worker, load eap (acc-eap skill) and design the "scale-up c
3960
4033
  //#endregion
3961
4034
  //#region src/trajectory/session.ts
3962
4035
  /** `provider/model` → agentOptions(由 delegate/core 提供,保持单一实现) */
3963
- const skiffAgents = /* @__PURE__ */ new Map();
4036
+ const trajectoryAgents = /* @__PURE__ */ new Map();
3964
4037
  /** 查 sessionId 的活体 agent(进程内会话延续用;未注册/已清理 → undefined) */
3965
- function getSkiffAgent(sessionId) {
3966
- return skiffAgents.get(sessionId);
4038
+ function getTrajectoryAgent(sessionId) {
4039
+ return trajectoryAgents.get(sessionId);
3967
4040
  }
3968
- /** 该会话当前绑定的 skiff 约束面(不是 skiff → null)。**守门与提示词的唯一查询入口。** */
3969
- function skiffProfileFor(root, sessionId) {
4041
+ /** 该会话当前绑定的 轨迹约束面(不是 轨迹约束 → null)。**守门与提示词的唯一查询入口。** */
4042
+ function trajectoryConstraintFor(root, sessionId) {
3970
4043
  if (sessionId === void 0) return null;
3971
- return skiffProfileForSession(root, sessionId, skiffTrajectoryDirNameFor(sessionId));
4044
+ return trajectoryConstraintForSession(root, sessionId, trajectoryDirNameForSession(sessionId));
3972
4045
  }
3973
- function registerSkiffSession(sessionId, trajectoryDirName, ccc, agent) {
3974
- skiffAgents.set(sessionId, agent);
3975
- registerSkiffSession$1(sessionId, trajectoryDirName, ccc);
4046
+ function registerTrajectorySession(sessionId, trajectoryDirName, ccc, agent) {
4047
+ trajectoryAgents.set(sessionId, agent);
4048
+ registerTrajectorySession$1(sessionId, trajectoryDirName, ccc);
3976
4049
  }
3977
- function unregisterSkiffSession(sessionId) {
3978
- skiffAgents.delete(sessionId);
3979
- unregisterSkiffSession$1(sessionId);
4050
+ function unregisterTrajectorySession(sessionId) {
4051
+ trajectoryAgents.delete(sessionId);
4052
+ unregisterTrajectorySession$1(sessionId);
3980
4053
  }
3981
4054
  /**
3982
- * 该 skiff 会话的**工作台路径** = 它自己轨迹的 SESSION.md。
4055
+ * 该 轨迹约束会话的**工作台路径** = 它自己轨迹的 SESSION.md。
3983
4056
  *
3984
- * 🔴 与旧版的差别:旧版会在这里**自动新建**一个 SESSION(`ensureSkiffSession`);
3985
- * 新版**没有新建这个动作** —— 轨迹就是工作台,`skiff.json` 与 `SESSION.md` 同处一个目录。
4057
+ * 🔴 与旧版的差别:旧版会在这里**自动新建**一个 SESSION(`ensureTrajectorySession`);
4058
+ * 新版**没有新建这个动作** —— 轨迹就是工作台,`trajectory-constraint.json` 与 `SESSION.md` 同处一个目录。
3986
4059
  * ⇒ 语义变成纯粹的"查询:我自己的身体在哪"。
3987
4060
  *
3988
- * @returns SESSION.md 绝对路径;不是 skiff / 文件不在 → null
4061
+ * @returns SESSION.md 绝对路径;不是 轨迹约束 / 文件不在 → null
3989
4062
  */
3990
- function skiffWorkspaceMdPath(root, sessionId) {
4063
+ function trajectoryWorkspaceMdPath(root, sessionId) {
3991
4064
  if (sessionId === void 0) return null;
3992
- const dirName = skiffTrajectoryDirNameFor(sessionId);
4065
+ const dirName = trajectoryDirNameForSession(sessionId);
3993
4066
  if (dirName === null) return null;
3994
4067
  const md = join(sessionsRoot(root), dirName, "SESSION.md");
3995
4068
  return existsSync(md) ? md : null;
@@ -3997,12 +4070,12 @@ function skiffWorkspaceMdPath(root, sessionId) {
3997
4070
  /**
3998
4071
  * 系统提示词中的**工作台纪律块**(S142 用户:只给路径 LLM 不会主动用)。
3999
4072
  *
4000
- * SESSION = 工作台 —— skiff 与主舱同一套机制(**零特调**,用户拍板):
4073
+ * SESSION = 工作台 —— 轨迹约束与主舱同一套机制(**零特调**,用户拍板):
4001
4074
  * SESSION.md 是持久记忆载体、rebuild 自动从本 SESSION 续接。
4002
4075
  * 注入 agent 系统提示词(用户对话面不可见)。
4003
4076
  *
4004
4077
  * v1.30.13(S142 诊断 D5):写入纪律**不点名具体工具** —— 旧文案写死 "with write/edit",
4005
- * 而某个 skiff 的工具白名单里根本没有 write/edit ⇒ 指令指向它没有的工具
4078
+ * 而某个 轨迹约束的工具白名单里根本没有 write/edit ⇒ 指令指向它没有的工具
4006
4079
  * (注入纪律与能力面矛盾,模型只能猜)。现文案改为「用本轨迹被授权的写入通道」。
4007
4080
  */
4008
4081
  function workspaceCognitiveTrajectoryLine(mdPath) {
@@ -4030,10 +4103,10 @@ const workspaceSectionAgents = /* @__PURE__ */ new WeakSet();
4030
4103
  /**
4031
4104
  * 把**工作台纪律块**挂到 agent 的系统提示词段(单一注入点)。
4032
4105
  *
4033
- * 用户原话(S142,2026-09-08):"微信桥的注入机制,每个用户消息都会注入,skiff 本身也会注入,
4034
- * 这样就重复,能否微信桥情况下,注入内容直接取 skiff 的,这样不用配两遍"。
4106
+ * 用户原话(S142,2026-09-08):"微信桥的注入机制,每个用户消息都会注入,轨迹约束本身也会注入,
4107
+ * 这样就重复,能否微信桥情况下,注入内容直接取 轨迹约束的,这样不用配两遍"。
4035
4108
  *
4036
- * 现形态:工作台行**只**经本函数挂系统提示词段(name `serenity-skiff-workspace`,
4109
+ * 现形态:工作台行**只**经本函数挂系统提示词段(name `serenity-轨迹约束-workspace`,
4037
4110
  * 动态 `text()` 每轮按 scope 读当前活跃 mdPath)。
4038
4111
  *
4039
4112
  * @returns true = 段已在位;false = 该 agent 挂不上(调用方降级为 question 前缀注入)
@@ -4045,39 +4118,39 @@ function ensureWorkspacePromptSection(agent, scope, root) {
4045
4118
  if (typeof api?.section !== "function") return false;
4046
4119
  try {
4047
4120
  api.section({
4048
- name: "serenity-skiff-workspace",
4121
+ name: "serenity-轨迹约束-workspace",
4049
4122
  order: -55,
4050
4123
  text: () => {
4051
- const mdPath = skiffWorkspaceMdPath(root, scope);
4124
+ const mdPath = trajectoryWorkspaceMdPath(root, scope);
4052
4125
  return mdPath ? workspaceCognitiveTrajectoryLine(mdPath) : "";
4053
4126
  }
4054
4127
  });
4055
4128
  workspaceSectionAgents.add(agent);
4056
4129
  return true;
4057
4130
  } catch (err) {
4058
- console.warn(`[serenity-hooks] skiff 工作台提示词段注册失败(回退 question 注入): ${String(err?.message ?? err)}`);
4131
+ console.warn(`[serenity-hooks] 轨迹约束 工作台提示词段注册失败(回退 question 注入): ${String(err?.message ?? err)}`);
4059
4132
  return false;
4060
4133
  }
4061
4134
  }
4062
- /** skiff agent 挂载的 DSH preset(平台工具面由 preset 决定;白名单再按轨迹过滤) */
4063
- const SKIFF_PRESET = "standard";
4135
+ /** 轨迹约束 agent 挂载的 DSH preset(平台工具面由 preset 决定;白名单再按轨迹过滤) */
4136
+ const TRAJECTORY_PRESET = "standard";
4064
4137
  /**
4065
- * 创建(或复用)一个 skiff 会话。
4138
+ * 创建(或复用)一个 轨迹约束会话。
4066
4139
  *
4067
4140
  * **固定 sessionId 的 resume-or-create**(v1.27.2):指定 sessionId 且磁盘已有持久化 log →
4068
4141
  * `ctx.agents.resume`(DSH 持久化语义:sessionId 即身份,历史 + turn 续号延续);
4069
4142
  * 无 log(首次)→ `ctx.agents.create`。不指定 → 随机 id 恒 create(调试页路径)。
4070
4143
  *
4071
- * @param profile 该 skiff 的约束面(来自 `skiff.json`)
4144
+ * @param profile 该 轨迹约束的约束面(来自 `trajectory-constraint.json`)
4072
4145
  * @param sessionId 指定会话 id(可选;微信桥等外部面用固定 id 实现"轨迹 ↔ 会话"长期映射)
4073
4146
  */
4074
- async function createSkiffAgent(ctx, root, profile, defaultModel, sessionId) {
4075
- if (!ctx.agents) throw new Error("skiff: ctx.agents unavailable");
4147
+ async function createTrajectoryAgent(ctx, root, profile, defaultModel, sessionId) {
4148
+ if (!ctx.agents) throw new Error("trajectory constraint: ctx.agents unavailable");
4076
4149
  const model = profile.model?.trim() || defaultModel || "";
4077
- const id = sessionId ?? `skiff-${profile.code}-${randomUUID()}`;
4150
+ const id = sessionId ?? `轨迹约束-${profile.code}-${randomUUID()}`;
4078
4151
  const setup = async (agentCtx) => {
4079
4152
  try {
4080
- await agentCtx.get("agentPresets")?.mount?.(agentCtx, SKIFF_PRESET);
4153
+ await agentCtx.get("agentPresets")?.mount?.(agentCtx, TRAJECTORY_PRESET);
4081
4154
  } catch {}
4082
4155
  };
4083
4156
  const handle = await createOrResumeAgent(ctx, id, root, model, setup, sessionId !== void 0);
@@ -4087,7 +4160,7 @@ async function createSkiffAgent(ctx, root, profile, defaultModel, sessionId) {
4087
4160
  const livePrompt = () => {
4088
4161
  let live = profile;
4089
4162
  try {
4090
- const fresh = readSkiffProfileAt(join(sessionsRoot(root), profile.dirName), root);
4163
+ const fresh = readTrajectoryConstraintAt(join(sessionsRoot(root), profile.dirName), root);
4091
4164
  if (fresh) live = fresh;
4092
4165
  } catch {}
4093
4166
  const wantPath = live.systemPromptFile?.trim();
@@ -4100,23 +4173,23 @@ async function createSkiffAgent(ctx, root, profile, defaultModel, sessionId) {
4100
4173
  livePrompt();
4101
4174
  try {
4102
4175
  agent.ctx.systemPrompt.section({
4103
- name: "serenity-skiff",
4176
+ name: "serenity-轨迹约束",
4104
4177
  order: -60,
4105
4178
  text: () => {
4106
4179
  const legacy = hasTrajectoryPrompt(root, profile.dirName) ? "" : livePrompt();
4107
- return [buildSkiffBasePrompt(profile.code), legacy].filter(Boolean).join("\n");
4180
+ return [buildTrajectoryBasePrompt(profile.code), legacy].filter(Boolean).join("\n");
4108
4181
  }
4109
4182
  });
4110
4183
  } catch (err) {
4111
- console.warn(`[serenity-hooks] skiff 系统提示词注册失败: ${String(err?.message ?? err)}`);
4184
+ console.warn(`[serenity-hooks] 轨迹约束 系统提示词注册失败: ${String(err?.message ?? err)}`);
4112
4185
  }
4113
4186
  if (agent?.session) try {
4114
4187
  registerCognitiveTrajectorySkillSection(agent, root);
4115
4188
  } catch (err) {
4116
- console.warn(`[serenity-hooks] skiff 轨迹 skill 注入注册失败(不影响 agent 创建): ${String(err?.message ?? err)}`);
4189
+ console.warn(`[serenity-hooks] 轨迹约束轨迹 skill 注入注册失败(不影响 agent 创建): ${String(err?.message ?? err)}`);
4117
4190
  }
4118
- if (skiffWorkspaceMdPath(root, id) !== null) ensureWorkspacePromptSection(agent, id, root);
4119
- registerSkiffSession(id, profile.dirName, root, agent);
4191
+ if (trajectoryWorkspaceMdPath(root, id) !== null) ensureWorkspacePromptSection(agent, id, root);
4192
+ registerTrajectorySession(id, profile.dirName, root, agent);
4120
4193
  return {
4121
4194
  handle,
4122
4195
  agent,
@@ -4155,7 +4228,7 @@ async function createOrResumeAgent(ctx, id, root, model, setup, fixedId) {
4155
4228
  sessionId: id,
4156
4229
  meta: {
4157
4230
  cwd: root,
4158
- agentPreset: SKIFF_PRESET
4231
+ agentPreset: TRAJECTORY_PRESET
4159
4232
  },
4160
4233
  setup,
4161
4234
  ...model ? { agentOptions: splitModel(model) } : {}
@@ -4170,7 +4243,7 @@ async function createOrResumeAgent(ctx, id, root, model, setup, fixedId) {
4170
4243
  sessionId: id,
4171
4244
  meta: {
4172
4245
  cwd: root,
4173
- agentPreset: SKIFF_PRESET
4246
+ agentPreset: TRAJECTORY_PRESET
4174
4247
  },
4175
4248
  setup,
4176
4249
  ...model ? { agentOptions: splitModel(model) } : {}
@@ -4191,14 +4264,14 @@ async function createOrResumeAgent(ctx, id, root, model, setup, fixedId) {
4191
4264
  if (liveNow) return liveReuseRef(liveNow, setup);
4192
4265
  const msg = err instanceof Error ? err.message : String(err);
4193
4266
  const stack = err instanceof Error ? err.stack ?? "" : "";
4194
- console.log(`[serenity-hooks] skiff resume 失败降级 create (id=${id}): ${msg}`);
4195
- if (stack) console.log(`[serenity-hooks] skiff resume stack:\n${stack.slice(0, 1200)}`);
4267
+ console.log(`[serenity-hooks] 轨迹约束 resume 失败降级 create (id=${id}): ${msg}`);
4268
+ if (stack) console.log(`[serenity-hooks] 轨迹约束 resume stack:\n${stack.slice(0, 1200)}`);
4196
4269
  return {
4197
4270
  ...await ctx.agents.create({
4198
4271
  sessionId: id,
4199
4272
  meta: {
4200
4273
  cwd: root,
4201
- agentPreset: SKIFF_PRESET
4274
+ agentPreset: TRAJECTORY_PRESET
4202
4275
  },
4203
4276
  setup,
4204
4277
  ...model ? { agentOptions: splitModel(model) } : {}
@@ -4262,7 +4335,7 @@ async function waitForTurnAssistant(ctx, agent, beforeAssistantCount, opts = {})
4262
4335
  *
4263
4336
  * @param options.timeoutMs 本轮等待上限(测试注入;生产用缺省)
4264
4337
  */
4265
- async function askSkiff(ctx, agent, question, options) {
4338
+ async function askTrajectory(ctx, agent, question, options) {
4266
4339
  const beforeAssistant = countAssistantMessages(agent.session);
4267
4340
  agent.followup(createUserMessage({
4268
4341
  content: [{
@@ -4277,29 +4350,29 @@ async function askSkiff(ctx, agent, question, options) {
4277
4350
  };
4278
4351
  }
4279
4352
  /**
4280
- * skiff 会话的轨迹纪律参与判定:**不是 skiff 恒 true**(正常参与);
4281
- * skiff 按 `skiff.json` 的 `cognitiveTrajectory` 子集决定。
4353
+ * 轨迹约束会话的轨迹纪律参与判定:**不是 轨迹约束恒 true**(正常参与);
4354
+ * 轨迹约束 按 `trajectory-constraint.json` 的 `cognitiveTrajectory` 子集决定。
4282
4355
  *
4283
- * 注册表缺失(进程刚重启、绑定还没读)⇒ 由 `skiffProfileForSession` 回落持久绑定;
4284
- * 两者都不中 ⇒ 判"不是 skiff" ⇒ 恒 true(正常参与)。**fail-open 是有意的**:
4356
+ * 注册表缺失(进程刚重启、绑定还没读)⇒ 由 `trajectoryConstraintForSession` 回落持久绑定;
4357
+ * 两者都不中 ⇒ 判"不是 轨迹约束" ⇒ 恒 true(正常参与)。**fail-open 是有意的**:
4285
4358
  * 纪律参与项的失败不该让会话失去 keeper/rebuild(那是能力倒退,比多跑一次更糟)。
4286
4359
  */
4287
- function skiffCognitiveTrajectoryEnabled(root, sessionId, key) {
4360
+ function trajectoryConstraintParticipates(root, sessionId, key) {
4288
4361
  if (sessionId === void 0) return true;
4289
- const profile = skiffProfileFor(root, sessionId);
4362
+ const profile = trajectoryConstraintFor(root, sessionId);
4290
4363
  if (profile === null) return true;
4291
- return profileCognitiveTrajectory(profile)[key];
4364
+ return constraintCognitiveTrajectory(profile)[key];
4292
4365
  }
4293
4366
  /**
4294
- * `msm` 工具的 skiff 门控:非 skiff 恒放行;skiff 会话——
4367
+ * `msm` 工具的 轨迹约束门控:非 轨迹约束恒放行;轨迹约束会话——
4295
4368
  * exec 非白名单 MSM 拒绝(**不列名单**)、register/deregister 必拒、
4296
4369
  * list 白名单过滤、check/guide/ccc-config 只读放行。
4297
4370
  */
4298
- function skiffMsmGate(root, sessionId, action, name) {
4371
+ function trajectoryConstraintMsmGate(root, sessionId, action, name) {
4299
4372
  if (sessionId === void 0) return {};
4300
- const profile = skiffProfileFor(root, sessionId);
4373
+ const profile = trajectoryConstraintFor(root, sessionId);
4301
4374
  if (profile === null) return {};
4302
- if (action === "register" || action === "deregister") return { reject: "register/deregister is not allowed in skiff sessions" };
4375
+ if (action === "register" || action === "deregister") return { reject: "register/deregister is not allowed in trajectory-constraint sessions" };
4303
4376
  if (action === "exec") {
4304
4377
  if (!name || !profile.msms.includes(name)) return { reject: "MSM not allowed" };
4305
4378
  return {};
@@ -4386,7 +4459,7 @@ const msmTool = defineTool({
4386
4459
  if (!root) throw new Error(NO_CCC_FROM_AGENT_CWD);
4387
4460
  const sessionId = agentSessionId$1(exec);
4388
4461
  const name = args.name;
4389
- const gate = skiffMsmGate(root, sessionId, name ? "exec" : "list", name);
4462
+ const gate = trajectoryConstraintMsmGate(root, sessionId, name ? "exec" : "list", name);
4390
4463
  if (gate.reject) throw new Error(gate.reject);
4391
4464
  if (!name || name.trim() === "") return gate.whitelist ? "MSMs allowed in this role:\n" + [...gate.whitelist].join("\n") : buildMsmIndex(root);
4392
4465
  const clean = name.trim();
@@ -10324,6 +10397,16 @@ function croScriptPath(root, dirName) {
10324
10397
  * —— 程序还没跑,没有快照可读。⇒ 只能读文件。(这也是"机制属 ACC"的体现:
10325
10398
  * ACC 在调用用户程序前先判定自己该不该调用。)
10326
10399
  *
10400
+ * ## 🆕 2026-10-19 owner 裁 Q2:**声明文件里的 `cro.host` 优先,独立文件回落**
10401
+ *
10402
+ * 收拢后机器名住在 `<轨迹目录>/trajectory.json` 的 `cro.host` 字段里。
10403
+ * 判据(**两代并读,不硬切**):**声明面里有合法 `cro.host` ⇒ 用它;否则回落 `cro-host-binding` 文件**。
10404
+ *
10405
+ * 🔴 **为什么这里绝不能硬切**(比别处更硬的一条,逐字保留原裁定):
10406
+ * 硬切之后,**第二台机器**读到的是「没绑」而不是「绑给了别人」——
10407
+ * 两者都导致"不跑",**表面结果相同,但判据退化**(失去可诊断性)。
10408
+ * ⇒ 只要还有一台机器可能装着旧版本,两条路都必须能读。
10409
+ *
10327
10410
  * @param root CCC 根
10328
10411
  * @param dirName 轨迹目录名
10329
10412
  * @param selfHost 本机机器名(**注入** ⇒ 可测;生产调用方传 `hostname()`)
@@ -10343,6 +10426,8 @@ function resolveCroHostBinding(root, dirName, selfHost) {
10343
10426
  detail: `绑定文件路径非法(已忽略):${String(err?.message ?? err)}`
10344
10427
  };
10345
10428
  }
10429
+ const fromJson = readCroHostFromDeclaration(root, dirName);
10430
+ if (fromJson !== null) return verdictFor(fromJson, self);
10346
10431
  let raw;
10347
10432
  try {
10348
10433
  if (!existsSync(fileAbs)) return {
@@ -10350,7 +10435,7 @@ function resolveCroHostBinding(root, dirName, selfHost) {
10350
10435
  bound: null,
10351
10436
  self,
10352
10437
  code: "not-bound",
10353
- detail: `未绑定机器(缺 ${CRO_HOST_FILENAME})⇒ 按"必须指定"不运行`
10438
+ detail: `未绑定机器(缺 ${CRO_HOST_FILENAME},且 ${TRAJECTORY_FILENAME} 里也没有 cro.host)⇒ 按"必须指定"不运行`
10354
10439
  };
10355
10440
  const st = statSync(fileAbs);
10356
10441
  if (st.size > CRO_HOST_FILE_MAX) return {
@@ -10378,6 +10463,37 @@ function resolveCroHostBinding(root, dirName, selfHost) {
10378
10463
  code: "not-bound",
10379
10464
  detail: `绑定文件为空(只有注释/空行)⇒ 按"必须指定"不运行`
10380
10465
  };
10466
+ return verdictFor(bound, self);
10467
+ }
10468
+ /**
10469
+ * 从**声明文件**(`trajectory.json` 族)里取 `cro.host`(取不到 ⇒ `null`)。
10470
+ *
10471
+ * 🔴 **永不抛**:本函数跑在 tick 内(旁路容忍铁律);坏 JSON / 结构不符一律按"没给"处理,
10472
+ * 于是**自然回落到独立文件那条路** —— 这正是两代并读想要的降级方向。
10473
+ *
10474
+ * @param root CCC 根
10475
+ * @param dirName 轨迹目录名
10476
+ * @returns 机器名;没给 ⇒ `null`
10477
+ */
10478
+ function readCroHostFromDeclaration(root, dirName) {
10479
+ try {
10480
+ const cro = readTrajectoryFile(join(sessionsRoot(root), dirName))?.data?.["cro"];
10481
+ if (!cro || typeof cro !== "object" || Array.isArray(cro)) return null;
10482
+ const host = cro["host"];
10483
+ if (typeof host !== "string") return null;
10484
+ const trimmed = host.trim();
10485
+ return trimmed === "" ? null : trimmed;
10486
+ } catch {
10487
+ return null;
10488
+ }
10489
+ }
10490
+ /**
10491
+ * 已解析出的机器名 → 判定结果(三种态的唯一收敛点)。
10492
+ *
10493
+ * 🔴 **抽出来的理由**:两条读取路(声明文件 / 独立文件)必须给出**同一种**判定语义;
10494
+ * 各写一份迟早会漂("声明面绑给别人"与"文件面绑给别人"在日志里长得不一样就糟了)。
10495
+ */
10496
+ function verdictFor(bound, self) {
10381
10497
  if (bound === self) return {
10382
10498
  allowed: true,
10383
10499
  bound,
@@ -10419,7 +10535,7 @@ function parseCroHostBinding(raw) {
10419
10535
  * `AGENT_SESSIONS/` 下每个目录查一次 `<目录>/continuous-re-occurrence.ts`。
10420
10536
  * · **一次 readdir + 每个目录一次 existsSync**:N 条轨迹的代价是 O(N) 次 `stat`,
10421
10537
  * 每 5min 一次 —— 与既有 `listSessions` 同量级,可接受。
10422
- * · 跳过 **`_` 前缀**(`_archived` / `_skiff-logs` / `_weixin-logs` = 系统与日志目录)
10538
+ * · 跳过 **`_` 前缀**(`_archived` / `_轨迹约束-logs` / `_weixin-logs` = 系统与日志目录)
10423
10539
  * 与 **`.` 前缀**(隐藏)——它们**不是轨迹**,且扫它们纯属浪费。
10424
10540
  * · 🔴 **本函数绝不抛错**(返回 `[]`)——它跑在调度器 tick 内,任何异常都可能影响既有链路
10425
10541
  * (设计 §5 铁律)。目录读不到(不存在 / 权限)⇒ `[]` = "没有轨迹启用 CRO",语义正确。
@@ -10914,12 +11030,36 @@ function registerCroTurnTracking(ctx) {
10914
11030
  * | 文件 | 谁写 | 主语 | 回答 |
10915
11031
  * |---|---|---|---|
10916
11032
  * | `<轨迹目录>/cro-state.json`(CCC 侧程序自建,名字自定) | **CCC 的 CRO 程序** | 程序 | 「**我判了什么**」(含"没叫"的判定、心跳阈值、环境谓词) |
10917
- * | `<轨迹目录>/cro-wake-log.json`(**本模块**) | **ACC** | ACC | 「**我把什么送出去了、送成没有**」 |
11033
+ * | `<轨迹目录>/cro-wake-log.log`(**本模块**) | **ACC** | ACC | 「**我把什么送出去了、送成没有**」 |
10918
11034
  *
10919
11035
  * **内容不重叠**:本模块**不复制**判定细节(不存 `lastDecision` / 阈值 / 谓词);
10920
11036
  * 而程序**不可能**知道投递结果(CRO 投递是 fire-and-forget)。
10921
11037
  * ⇒ 两者**互补**:完整审计 = 两者合看(本模块 = 投递面|程序状态 = 判定面)。
10922
11038
  *
11039
+ * ## 🔴 形态 = 纯文本行,不是 JSON(owner 2026-10-19 裁 Q1)
11040
+ *
11041
+ * owner 原话:「**日志不要写 json,不合适,日志直接写纯文本 `.log` 就行**」。
11042
+ * ⇒ 一行一条,**tab 分隔**(终端里按制表位对齐,`grep` / `cut` / `awk` 直接可用):
11043
+ *
11044
+ * ```text
11045
+ * # ACC CRO 唤起流水(纯文本 · tab 分隔 · 时间 / 结果 / tick / 方式 / 理由 / 提示词摘要)
11046
+ * 2026-10-19T22:15:03+08:00 ok tick=82 已投递(live) reason=心跳:距上次 44m prompt=17:请先整理你的 SESSION.md
11047
+ * ```
11048
+ *
11049
+ * 🔴 **`#` 开头是注释 / 表头行,解析时跳过** —— 纯文本档案缺了表头,下一个人(或半年后的我)
11050
+ * 打开只看到一堆制表符,那是把 R 推给读者。
11051
+ * 🔴 **坏行跳过而非报错**(与 JSON 版的 `asEntry()` 同口径):手改 / 半行 / 截断都不得让整档失效。
11052
+ *
11053
+ * ## 🔴 两代并读(**不硬切**,本容器反复栽过的形态)
11054
+ *
11055
+ * 旧名 `cro-wake-log.json` **仍然读**(存量轨迹里可能正有那份);
11056
+ * **只写新 `.log`**。读侧判据 = **「新档在就只读新档,否则回落旧档」**。
11057
+ *
11058
+ * 🔴 **首次写入 = 迁移,不是覆盖**:那一刻 `loadCroWakeLog` 读到的仍是旧档条目,
11059
+ * 它们与新条目一起过裁剪、一起落进新档 ⇒ **窗内的旧记录不会凭空消失**;
11060
+ * 而旧档此后不再被读(新档已在)⇒ **不会重复计入**,也不需要删除动作。
11061
+ * ⚠️ 这条**只能靠读源码知道**(写出来的档看不出它继承过谁)⇒ 故在此明写。
11062
+ *
10923
11063
  * ## 🔴 轮转(所有者明示「程序化的」)
10924
11064
  *
10925
11065
  * **写入时按窗裁剪**:每次落盘只保留 `at >= now - CRO_LOG_RETENTION_MS` 的条目,
@@ -10938,21 +11078,107 @@ function registerCroTurnTracking(ctx) {
10938
11078
  * 最坏(某程序每 5min 都叫)⇒ 288 条/天 ⇒ 两天 **≤ ~600 条、< 100 KB** —— 轮转**自带上限**。
10939
11079
  */
10940
11080
  /** 固定文件名(所有者令:「文件名固定即可」)——与程序自建的 `cro-state.json` 并排、主语分明 */
10941
- const CRO_LOG_FILENAME = "cro-wake-log.json";
11081
+ const CRO_LOG_FILENAME = "cro-wake-log.log";
11082
+ /**
11083
+ * 🔴 **读取族 = 新名在前、旧名在后**(认族,不认"当前叫什么")。
11084
+ *
11085
+ * 本容器已两次栽在"上游改名、下游写死旧名":`skiff.json` → `trajectory-constraint.json` 那次,
11086
+ * 症状是「写不进去」,而真因是「文件名对不上」,两者在读数上完全不像。
11087
+ * ⇒ 上游再改名只往族里加一项。
11088
+ */
11089
+ const CRO_LOG_LEGACY_FILENAMES = ["cro-wake-log.json"];
10942
11090
  /** 保留窗 = 2 日(所有者令「只保留仅两日」);**常量** ⇒ 「这个时间是程序化的」 */
10943
11091
  const CRO_LOG_RETENTION_MS = 1728e5;
10944
- const CRO_LOG_VERSION = 1;
10945
- /** 日志绝对路径 */
11092
+ /** 纯文本档案的表头行(解析时跳过;人打开时先看到它) */
11093
+ const CRO_LOG_HEADER = "# ACC CRO 唤起流水(纯文本 · tab 分隔 · 时间 / 结果 / tick / 方式 / 理由 / 提示词摘要)";
11094
+ /** 日志绝对路径(**写入目标** = 新名) */
10946
11095
  function croWakeLogPath(root, dirName) {
10947
11096
  return join(sessionsRoot(root), dirName, CRO_LOG_FILENAME);
10948
11097
  }
11098
+ /** 旧档绝对路径(**只读**,用于两代并读) */
11099
+ function croWakeLogLegacyPath(root, dirName, filename) {
11100
+ return join(sessionsRoot(root), dirName, filename);
11101
+ }
10949
11102
  function emptyLog() {
10950
11103
  return {
10951
- version: CRO_LOG_VERSION,
11104
+ version: 1,
10952
11105
  entries: []
10953
11106
  };
10954
11107
  }
10955
- /** 条目形状校验(容忍手改:字段缺失/类型不符 → 丢弃该条,不阻断整档) */
11108
+ /**
11109
+ * 抹掉会破坏**行结构**的字符(tab / CR / LF)。
11110
+ *
11111
+ * 🔴 只抹这三个:它们是**分隔符**,混进来会让一行变成两行或错位;
11112
+ * 其余字符(`|`、`]`、中文标点)**原样保留** —— 那些是内容,抹了就是把 E 降了。
11113
+ */
11114
+ function oneline(text) {
11115
+ return text.replace(/[\t\r\n]+/g, " ");
11116
+ }
11117
+ /** 一条记录 → 纯文本行(tab 分隔、字段带前缀便于人读) */
11118
+ function formatCroLogLine(entry) {
11119
+ const reason = entry.reason === null ? "" : oneline(entry.reason);
11120
+ const head = oneline(entry.promptDigest.head);
11121
+ return [
11122
+ oneline(entry.at),
11123
+ entry.ok ? "ok" : "fail",
11124
+ `tick=${entry.tick}`,
11125
+ oneline(entry.detail),
11126
+ `reason=${reason}`,
11127
+ `prompt=${entry.promptDigest.length}:${head}`
11128
+ ].join(" ");
11129
+ }
11130
+ /**
11131
+ * 纯文本行 → 记录(**坏行返回 null 而不是抛**)。
11132
+ *
11133
+ * 判据:字段数不足 / 时间不可解析 / `ok` 标记不认识 ⇒ `null`(跳过该行,其余照读)。
11134
+ * 🔴 `at` 必须**可被 `Date.parse` 解析**才收 —— 那是裁剪的前提,
11135
+ * 收一条解析不了的时间等于让它在此档里永久驻留(轮转对它失效)。
11136
+ */
11137
+ function parseCroLogLine(line) {
11138
+ if (line === "" || line.startsWith("#")) return null;
11139
+ const parts = line.split(" ");
11140
+ if (parts.length < 6) return null;
11141
+ const at = parts[0];
11142
+ if (Number.isNaN(Date.parse(at))) return null;
11143
+ const mark = parts[1];
11144
+ if (mark !== "ok" && mark !== "fail") return null;
11145
+ const tickM = /^tick=(\d+)$/.exec(parts[2]);
11146
+ if (!tickM) return null;
11147
+ const reasonRaw = parts[4].startsWith("reason=") ? parts[4].slice(7) : "";
11148
+ const promptM = /^prompt=(\d+):([\s\S]*)$/.exec(parts[5]);
11149
+ return {
11150
+ at,
11151
+ ok: mark === "ok",
11152
+ detail: parts[3],
11153
+ tick: Number(tickM[1]),
11154
+ reason: reasonRaw === "" ? null : reasonRaw,
11155
+ promptDigest: {
11156
+ length: promptM ? Number(promptM[1]) : 0,
11157
+ head: promptM ? promptM[2] : ""
11158
+ }
11159
+ };
11160
+ }
11161
+ /** 全文 → 记录(跳过表头与坏行;**永不抛**) */
11162
+ function parseCroLogText(text) {
11163
+ const entries = [];
11164
+ for (const line of text.split("\n")) {
11165
+ const e = parseCroLogLine(line);
11166
+ if (e) entries.push(e);
11167
+ }
11168
+ return entries;
11169
+ }
11170
+ /** JSON 旧档 → 记录(字段缺失/类型不符 ⇒ 丢弃该条,不阻断整档) */
11171
+ function parseCroLogJson(text) {
11172
+ const parsed = JSON.parse(text);
11173
+ const raw = Array.isArray(parsed.entries) ? parsed.entries : [];
11174
+ const entries = [];
11175
+ for (const item of raw) {
11176
+ const e = asEntry(item);
11177
+ if (e) entries.push(e);
11178
+ }
11179
+ return entries;
11180
+ }
11181
+ /** 旧档条目形状校验(容忍手改) */
10956
11182
  function asEntry(value) {
10957
11183
  const e = value;
10958
11184
  if (!e || typeof e !== "object") return null;
@@ -10982,27 +11208,20 @@ function summarizeCroPrompt(prompt) {
10982
11208
  }
10983
11209
  /**
10984
11210
  * 读取日志(**永不抛**;读坏 ⇒ 当作空档 + 返回 error 文本)。
11211
+ *
11212
+ * 🔴 **两代并读的判据**:新档在 ⇒ **只读新档**;否则按族回落旧档。
11213
+ * 这样首次写入之后旧档自然退场,**不需要删除动作、也不会重复计入**。
11214
+ *
10985
11215
  * @param root CCC 根
10986
11216
  * @param dirName 轨迹目录名
10987
11217
  */
10988
11218
  function loadCroWakeLog(root, dirName) {
10989
11219
  const path = croWakeLogPath(root, dirName);
10990
- if (!existsSync(path)) return {
10991
- log: emptyLog(),
10992
- error: null
10993
- };
10994
- try {
10995
- const parsed = JSON.parse(readFileSync(path, "utf-8"));
10996
- const raw = Array.isArray(parsed.entries) ? parsed.entries : [];
10997
- const entries = [];
10998
- for (const item of raw) {
10999
- const e = asEntry(item);
11000
- if (e) entries.push(e);
11001
- }
11220
+ if (existsSync(path)) try {
11002
11221
  return {
11003
11222
  log: {
11004
- version: CRO_LOG_VERSION,
11005
- entries
11223
+ version: 1,
11224
+ entries: parseCroLogText(readFileSync(path, "utf-8"))
11006
11225
  },
11007
11226
  error: null
11008
11227
  };
@@ -11012,6 +11231,28 @@ function loadCroWakeLog(root, dirName) {
11012
11231
  error: `CRO 流水解析失败(${String(err?.message ?? err)})`
11013
11232
  };
11014
11233
  }
11234
+ for (const legacy of CRO_LOG_LEGACY_FILENAMES) {
11235
+ const legacyPath = croWakeLogLegacyPath(root, dirName, legacy);
11236
+ if (!existsSync(legacyPath)) continue;
11237
+ try {
11238
+ return {
11239
+ log: {
11240
+ version: 1,
11241
+ entries: parseCroLogJson(readFileSync(legacyPath, "utf-8"))
11242
+ },
11243
+ error: null
11244
+ };
11245
+ } catch (err) {
11246
+ return {
11247
+ log: emptyLog(),
11248
+ error: `CRO 流水解析失败(${String(err?.message ?? err)})`
11249
+ };
11250
+ }
11251
+ }
11252
+ return {
11253
+ log: emptyLog(),
11254
+ error: null
11255
+ };
11015
11256
  }
11016
11257
  /**
11017
11258
  * 按窗裁剪(**纯函数** ⇒ 可穷举测试)。
@@ -11040,6 +11281,10 @@ function pruneCroLogEntries(entries, nowMs, windowMs = CRO_LOG_RETENTION_MS) {
11040
11281
  dropped
11041
11282
  };
11042
11283
  }
11284
+ /** 记录集 → 落盘全文(表头 + 逐行 + 末尾换行) */
11285
+ function renderCroLogText(entries) {
11286
+ return `${[CRO_LOG_HEADER, ...entries.map(formatCroLogLine)].join("\n")}\n`;
11287
+ }
11043
11288
  /**
11044
11289
  * 追加一条唤起记录并按窗裁剪(**本模块主入口;永不抛**)。
11045
11290
  *
@@ -11070,10 +11315,7 @@ function appendCroWakeLog(root, dirName, input, nowMs = Date.now()) {
11070
11315
  const { kept, pruned, dropped } = pruneCroLogEntries([...loaded.log.entries, entry], nowMs);
11071
11316
  mkdirSync(dirname(path), { recursive: true });
11072
11317
  const tmp = `${path}.tmp`;
11073
- writeFileSync(tmp, `${JSON.stringify({
11074
- version: CRO_LOG_VERSION,
11075
- entries: kept
11076
- }, null, 2)}\n`, "utf-8");
11318
+ writeFileSync(tmp, renderCroLogText(kept), "utf-8");
11077
11319
  renameSync(tmp, path);
11078
11320
  return {
11079
11321
  ok: true,
@@ -12815,59 +13057,64 @@ const localstoreTool = defineTool({
12815
13057
  }
12816
13058
  });
12817
13059
  //#endregion
12818
- //#region src/tools/skiff-admin.ts
12819
- /** Skiff 定义教程:概念 / `skiff.json` schema / 双白名单 / 轨迹纪律 / 微信路由 */
12820
- const SKIFF_GUIDE = `═══ Skiff Guide (2026-10-19 redesigned) ═══
13060
+ //#region src/tools/trajectory-constraint-admin.ts
13061
+ /** 轨迹约束 定义教程:概念 / `trajectory.json` schema / 双白名单 / 轨迹纪律 / 微信路由 */
13062
+ const TRAJECTORY_CONSTRAINT_GUIDE = `═══ 轨迹约束 Guide (2026-10-19 redesigned) ═══
12821
13063
 
12822
- **A skiff IS a cognitive trajectory — the one whose directory contains \`${SKIFF_PROFILE_FILENAME}\`.**
13064
+ **A 轨迹约束 IS a cognitive trajectory — the one whose directory contains \`${TRAJECTORY_CONSTRAINT_FILENAME}\`.**
12823
13065
  That is the whole rule. No role table, no name registry: put the file there and the trajectory
12824
- becomes a skiff; delete it and it goes back to being an ordinary trajectory (takes effect at once).
13066
+ becomes a 轨迹约束; delete it and it goes back to being an ordinary trajectory (takes effect at once).
12825
13067
 
12826
13068
  AGENT_SESSIONS/<YYYY-MM-DD--<code>--<desc>>/
12827
13069
  ├─ SESSION.md ← its persistent body (this IS the workspace — nothing is auto-created)
12828
- ├─ ${SKIFF_PROFILE_FILENAME} ← its constraints (this file is what makes it a skiff)
12829
- └─ TRAJECTORY-UID ← legacy, read-only (superseded by the code)
13070
+ ├─ PROMPT.md ← its charter (trajectory-level prompt; all trajectories may have one)
13071
+ ├─ ${TRAJECTORY_CONSTRAINT_FILENAME} ← its declaration (this file is what makes it a 轨迹约束)
13072
+ └─ continuous-re-occurrence.ts ← optional CRO program (spawned, never imported)
13073
+
13074
+ The older names ${TRAJECTORY_LEGACY_FILENAMES.map((n) => `\`${n}\``).join(" / ")} are still READ
13075
+ (the family is checked in that order) — writing goes to \`${TRAJECTORY_FILENAME}\` only.
12830
13076
 
12831
- ── ${SKIFF_PROFILE_FILENAME} schema ──
13077
+ ── ${TRAJECTORY_CONSTRAINT_FILENAME} schema ──
12832
13078
  {
12833
13079
  "model": "provider/model", // optional; falls back to cheap-worker.defaultModel
12834
13080
  "tools": ["read","grep","msm",...], // non-MSM toolbar whitelist — anything not listed is REMOVED from the schema
12835
13081
  "msms": ["msm-a","msm-b"], // MSM whitelist — msm exec may only run these; register/deregister always refused
12836
13082
  "cognitiveTrajectory": { "session": true, "keeper": true, "rebuild": true }, // defaults TRUE (see below)
12837
- "systemPromptFile": ".opencode/skiff/<role>.md", // recommended: relative to CCC root, hot-reloads on save
12838
- "systemPrompt": "..." // inline fallback; systemPromptFile wins when both are set
13083
+ "systemPromptFile": ".opencode/skiff/<role>.md", // relative to CCC root, hot-reloads on save
13084
+ "systemPrompt": "...", // inline fallback; systemPromptFile wins when both are set
13085
+ "uid": "my-handle", // optional legacy alias (read-only; the code is the identity now)
13086
+ "cro": { "host": "<machine name>" } // optional: which machine may run this trajectory's CRO program
12839
13087
  }
12840
13088
 
12841
13089
  ── Two whitelists, both default-deny ──
12842
- - \`tools: []\` + non-empty \`msms\` = pure-MSM skiff (the usual knowledge-QA shape)
13090
+ - \`tools: []\` + non-empty \`msms\` = pure-MSM 轨迹约束 (the usual knowledge-QA shape)
12843
13091
  - non-empty \`msms\` ⇒ the \`msm\` tool is added automatically (the only entry point for MSMs)
12844
13092
  - anything outside both lists is invisible in the schema, not merely rejected at call time
12845
13093
 
12846
13094
  ── cognitiveTrajectory: why the defaults are TRUE ──
12847
- The workspace of a skiff is its own SESSION.md. Setting these to false would cut it off from
13095
+ The workspace of a 轨迹约束 is its own SESSION.md. Setting these to false would cut it off from
12848
13096
  its own body, so \`session\`/\`keeper\`/\`rebuild\` all default to true. Set false explicitly only
12849
13097
  when you deliberately want that participation off.
12850
13098
 
12851
13099
  ── WeChat routing: user → trajectory code ──
12852
13100
  \`weixin.routes\`: \`[{ "user": "<wx id or *>", "trajectory": "<code>" }]\`
12853
13101
  A route pointing at a code with no such trajectory is a hard error (the message is NOT delivered).
12854
- A trajectory WITHOUT \`${SKIFF_PROFILE_FILENAME}\` is still a legal target — it is just an ordinary trajectory.
13102
+ A trajectory WITHOUT \`${TRAJECTORY_CONSTRAINT_FILENAME}\` is still a legal target — it is just an ordinary trajectory.
12855
13103
 
12856
13104
  ── Operating it ──
12857
- Debug page: Settings → Serenity → enable the Skiff block → http://127.0.0.1:<debugPort>
12858
- Everything is read live: saving \`${SKIFF_PROFILE_FILENAME}\` takes effect on the next turn, no restart.
13105
+ Everything is read live: saving \`${TRAJECTORY_CONSTRAINT_FILENAME}\` takes effect on the next turn, no restart.
12859
13106
  (The prompt FILE hot-reloads by mtime; the JSON itself is re-read every turn.)
12860
- After editing, run container_admin role apply for an explicit validation + confirmation pass.
12861
- Skill loading is always available to skiff sessions (skills are not whitelisted — they are read-only knowledge).`;
12862
- /** 校验当前 CCC 的 skiff 约束面:msms 均已注册 / model ∈ cheap-worker.models / 提示词非空 */
12863
- function validateSkiffConfig(root) {
12864
- const profiles = listSkiffProfiles(root);
13107
+ After editing, run container_admin config apply for an explicit validation + confirmation pass.
13108
+ Skill loading is always available to 轨迹约束 sessions (skills are not whitelisted — they are read-only knowledge).`;
13109
+ /** 校验当前 CCC 的 轨迹约束面:msms 均已注册 / model ∈ cheap-worker.models / 提示词非空 */
13110
+ function validateTrajectoryConstraint(root) {
13111
+ const profiles = listTrajectoryConstraints(root);
12865
13112
  const issues = [];
12866
13113
  if (profiles.length === 0) return {
12867
13114
  ok: true,
12868
13115
  issues: [],
12869
- roleCount: 0,
12870
- note: `no skiff trajectories in this CCC (no trajectory has ${SKIFF_PROFILE_FILENAME} — zero impact)`
13116
+ constraintCount: 0,
13117
+ note: `no 轨迹约束 trajectories in this CCC (no trajectory has ${TRAJECTORY_CONSTRAINT_FILENAME} — zero impact)`
12871
13118
  };
12872
13119
  const registered = new Set(loadMsmEntries(root).map((e) => e.name));
12873
13120
  const hc = readCheapWorkerConfig(root);
@@ -12875,7 +13122,7 @@ function validateSkiffConfig(root) {
12875
13122
  for (const p of profiles) {
12876
13123
  if (!(hasTrajectoryPrompt(root, p.dirName) || (() => {
12877
13124
  try {
12878
- return resolveProfileSystemPrompt(root, p).trim() !== "";
13125
+ return resolveConstraintSystemPrompt(root, p).trim() !== "";
12879
13126
  } catch (err) {
12880
13127
  issues.push(`trajectory "${p.code}": ${String(err?.message ?? err)}`);
12881
13128
  return true;
@@ -12887,15 +13134,15 @@ function validateSkiffConfig(root) {
12887
13134
  return {
12888
13135
  ok: issues.length === 0,
12889
13136
  issues,
12890
- roleCount: profiles.length
13137
+ constraintCount: profiles.length
12891
13138
  };
12892
13139
  }
12893
- /** 列出当前 CCC 的 skiff 轨迹(编码 / 模型 / msms / tools / 纪律摘要 / 提示词来源) */
12894
- function listSkiffRoles(root) {
12895
- const profiles = listSkiffProfiles(root);
13140
+ /** 列出当前 CCC 的轨迹约束(编码 / 模型 / msms / tools / 纪律摘要 / 提示词来源) */
13141
+ function listTrajectoryConstraintSummary(root) {
13142
+ const profiles = listTrajectoryConstraints(root);
12896
13143
  if (profiles.length === 0) return {
12897
13144
  roles: [],
12898
- note: `no skiff trajectories in this CCC (no trajectory has ${SKIFF_PROFILE_FILENAME})`
13145
+ note: `no 轨迹约束 trajectories in this CCC (no trajectory has ${TRAJECTORY_CONSTRAINT_FILENAME})`
12899
13146
  };
12900
13147
  const items = profiles.map((p) => ({
12901
13148
  name: p.code,
@@ -12903,8 +13150,8 @@ function listSkiffRoles(root) {
12903
13150
  model: p.model ?? "(cheap-worker default)",
12904
13151
  msms: p.msms,
12905
13152
  tools: p.tools,
12906
- cognitiveTrajectory: profileCognitiveTrajectory(p),
12907
- promptSource: profileSystemPromptSource(p),
13153
+ cognitiveTrajectory: constraintCognitiveTrajectory(p),
13154
+ promptSource: constraintSystemPromptSource(p, hasTrajectoryPrompt(root, p.dirName)),
12908
13155
  ...p.systemPromptFile ? { systemPromptFile: p.systemPromptFile } : {}
12909
13156
  }));
12910
13157
  return {
@@ -12913,29 +13160,29 @@ function listSkiffRoles(root) {
12913
13160
  };
12914
13161
  }
12915
13162
  /**
12916
- * 应用当前 skiff 配置(S142 用户:改了 json 应该有个生效机制)。
13163
+ * 应用当前 轨迹约束 配置(S142 用户:改了 json 应该有个生效机制)。
12917
13164
  *
12918
13165
  * 语义:约束面是**每轮实时读取**的 ⇒ apply 不是"推送",而是**显式校验 + 应用确认**:
12919
13166
  * ① 校验合法(复用 validate:msms 注册 / model 白名单 / 提示词非空)
12920
13167
  * ② 非法 → 报告问题清单,不应用(提示修复后重跑 apply)
12921
13168
  * ③ 合法 → 应用确认:绑定 CCC + 轨迹清单 + 说明(下一轮即生效)
12922
13169
  */
12923
- function applySkiffConfig(root) {
12924
- const validation = validateSkiffConfig(root);
12925
- const profiles = listSkiffProfiles(root);
13170
+ function applyTrajectoryConstraint(root) {
13171
+ const validation = validateTrajectoryConstraint(root);
13172
+ const profiles = listTrajectoryConstraints(root);
12926
13173
  if (!validation.ok) return {
12927
13174
  applied: false,
12928
13175
  cccRoot: root,
12929
- roleCount: profiles.length,
13176
+ constraintCount: profiles.length,
12930
13177
  issues: validation.issues,
12931
- hint: "fix the issues above, then run container_admin role apply again"
13178
+ hint: "fix the issues above, then run container_admin config apply again"
12932
13179
  };
12933
13180
  return {
12934
13181
  applied: true,
12935
13182
  cccRoot: root,
12936
- roleCount: profiles.length,
13183
+ constraintCount: profiles.length,
12937
13184
  roles: profiles.map((p) => p.code),
12938
- note: `constraints are read live from each trajectory's ${SKIFF_PROFILE_FILENAME} — they take effect on the next turn (no restart); ${SKIFF_PROFILE_HINT}`
13185
+ 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
13186
  };
12940
13187
  }
12941
13188
  //#endregion
@@ -12943,34 +13190,96 @@ function applySkiffConfig(root) {
12943
13190
  /**
12944
13191
  * container-admin.ts — container_admin(v1.30:机务舱——容器管理面统一入口)
12945
13192
  *
12946
- * 合并自:
12947
- * · skiff_admin(F4 Skiff 认知子集角色:guide/validate/apply/list)
12948
- * · acc_msm 管理面(register/deregister/check/guide/catalog/ccc-config)
12949
- * —— msm 工具只管执行+发现;一切「管理容器」的操作归本工具
13193
+ * ## 域结构(2026-10-19 owner 两条令后:**两域**)
12950
13194
  *
12951
- * 语义(用户拍板"skiff_admin 及系列 admin 合并成 container_admin"):
13195
+ * | 域 | 管什么 |
13196
+ * |---|---|
13197
+ * | `config` | **本 CCC 的全部配置面**:说明目录/按章详情 + 轨迹约束(原 `role` 域,已并入) |
13198
+ * | `msm` | MSM 注册表管理 + 手册(register/deregister/check/guide/catalog/ccc-config) |
13199
+ *
13200
+ * 🔴 **`role` 域已并入 `config`**(owner 2026-10-19 裁「1 并入」)。为什么不是三个平行域:
13201
+ * `role` 管的本来就是"某条轨迹的约束面"——**一组配置**,与 `config` 是同一抽象层的两半。
13202
+ * 分着放的代价是:用户在 `config` 里看不到"轨迹怎么约束",得先知道 `role` 这个词存在。
13203
+ * ⚠️ **旧 `role` 域仍被接受**(过渡别名,指向同一实现)——存量 CCC 的文档/脚本里到处是
13204
+ * `container_admin role validate`(实测本仓 30 处),硬切会让它们全部变成"未知域"。
13205
+ * 别名**只保留读取路径**:schema 的 `enum` 只列新名,文案只教新名。
13206
+ *
13207
+ * ## 三个动作名为什么是这三个(P3 的实现形态)
13208
+ * - `config docs`(无参)→ **使用说明目录**(第一层;只索引不复制)
13209
+ * - `config docs <chapter>` → **使用说明详情**(第二层;按章获得)
13210
+ * - `config view` → 配置**现值**(原来是唯一动作;现在是"看当前填了什么")
13211
+ *
13212
+ * 归属(D23):**机制在 ACC**(有哪几章、怎么路由、怎么校验),**内容在 CCC**(填什么)。
13213
+ * 语义(用户拍板"轨迹约束_admin 及系列 admin 合并成 container_admin"):
12952
13214
  * 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
13215
  */
12962
13216
  function agentSessionId(exec) {
12963
13217
  return exec.agent?.session?.id ?? "";
12964
13218
  }
13219
+ /**
13220
+ * `config view` 的**现值摘要**(v1.75.0 起:不再回整份说明正文)。
13221
+ *
13222
+ * 语义边界(这正是它和 `docs config` 的分工):
13223
+ * - 本函数回答「**你这个 CCC 现在填了什么**」⇒ 只报有值的键,且值超长截断。
13224
+ * - 它回答不了「**这个键该怎么填**」⇒ 那是 `docs config` 那一章。
13225
+ *
13226
+ * 🔴 为什么必须截断:现值里可能有很长的字段(`topPrompt` 实测数百字符)。
13227
+ * 不截断会让 "看现值" 退化成 "又返回一坨正文" —— 那就回到了统一前的问题。
13228
+ *
13229
+ * ⚠️ 返回类型是 `JsonValue`(工具 output schema 的契约)⇒ 必须**转成纯 JSON 可表达形态**,
13230
+ * 不能直接把 `Record<string, unknown>` 塞回去(`unknown` 里可能有函数/undefined,
13231
+ * 那会让序列化层行为不确定)。
13232
+ */
13233
+ function viewConfigSummary(root) {
13234
+ let cfg;
13235
+ try {
13236
+ cfg = loadSerenityConfig(root);
13237
+ } catch (err) {
13238
+ return { error: `配置读取失败:${String(err?.message ?? err)}` };
13239
+ }
13240
+ const MAX_STR = 120;
13241
+ const MAX_ARRAY = 12;
13242
+ const MAX_KEYS = 12;
13243
+ /** 递归转成纯 JSON 可表达值(超长按上限截断,并标注原始规模) */
13244
+ const toJson = (v) => {
13245
+ if (v === null || v === void 0) return null;
13246
+ if (typeof v === "string") return v.length > MAX_STR ? `${v.slice(0, MAX_STR)}…(共 ${v.length} 字符)` : v;
13247
+ if (typeof v === "number") return Number.isFinite(v) ? v : String(v);
13248
+ if (typeof v === "boolean") return v;
13249
+ if (Array.isArray(v)) {
13250
+ const head = v.slice(0, MAX_ARRAY).map(toJson);
13251
+ return v.length > MAX_ARRAY ? [...head, `…(共 ${v.length} 项)`] : head;
13252
+ }
13253
+ if (typeof v === "object") {
13254
+ const obj = v;
13255
+ const keys = Object.keys(obj);
13256
+ const out = {};
13257
+ for (const k of keys.slice(0, MAX_KEYS)) out[k] = toJson(obj[k]);
13258
+ if (keys.length > MAX_KEYS) out["…"] = `另有 ${keys.length - MAX_KEYS} 个键`;
13259
+ return out;
13260
+ }
13261
+ return String(v);
13262
+ };
13263
+ return {
13264
+ cccRoot: root,
13265
+ sections: Object.keys(cfg),
13266
+ values: toJson(cfg),
13267
+ note: "这是**现值摘要**(只报有值的键、超长已截断)。每段怎么填、填错会怎样 ⇒ container_admin config docs config"
13268
+ };
13269
+ }
12965
13270
  function renderText$1(value) {
12966
13271
  return [{
12967
13272
  type: "text",
12968
13273
  text: typeof value === "string" ? value : JSON.stringify(value, null, 2)
12969
13274
  }];
12970
13275
  }
13276
+ /** 过渡别名表:旧域 → 新域(只读兼容;文案不教它) */
13277
+ const DOMAIN_ALIASES = { role: "config" };
12971
13278
  /** 合法 domain 名(拒绝文案与 schema enum 同源——不得各写一份) */
12972
13279
  const DOMAIN_NAMES = Object.keys({
12973
- role: [
13280
+ config: [
13281
+ "docs",
13282
+ "view",
12974
13283
  "guide",
12975
13284
  "validate",
12976
13285
  "apply",
@@ -12983,23 +13292,71 @@ const DOMAIN_NAMES = Object.keys({
12983
13292
  "guide",
12984
13293
  "catalog",
12985
13294
  "ccc-config"
12986
- ],
12987
- config: ["view"]
13295
+ ]
12988
13296
  });
13297
+ /**
13298
+ * 说明目录的章节(**说明类内容的唯一注册处**)。
13299
+ *
13300
+ * 🔴 两条纪律:
13301
+ * ① **加一章 = 在这里加一项**(`render` 惰性求值)——说明类内容增长时**不新增工具/子命令**。
13302
+ * ② **正文不复制**:每章的真相源就在它自己的函数里(`CCC_CONFIG_REFERENCE` /
13303
+ * `TRAJECTORY_CONSTRAINT_GUIDE` / `ACC_CATALOG`)——本表只做**切分与路由**。
13304
+ *
13305
+ * ## 🔴 2026-10-19 v1.75.0:说明面统一(owner 裁 Q3 = 指针)
13306
+ *
13307
+ * 统一前有 **5 个并列入口**,其中两组返回**同一份正文**:
13308
+ * `docs config` ≡ `msm ccc-config`|`docs trajectory-constraint` ≡ `config guide`|
13309
+ * `msm catalog`(域挂错了:它描述整个 ACC,却挂在 MSM 注册表下)。
13310
+ *
13311
+ * ⇒ 统一后的分工(**一个内容一个正文入口**):
13312
+ * - **`config docs <chapter>`** = 唯一正文入口
13313
+ * - `msm ccc-config` / `config guide` = **指针**(指向对应 chapter,不再返回正文)
13314
+ * - `msm catalog` = **指针**(指向 `catalog` 章)
13315
+ *
13316
+ * 判据依据:`praxis` 已经做对了这件事 —— `praxis eap` 返回正文、`praxis` 无参返回目录,
13317
+ * **没有第二个入口读同一份 EAP**。本表把同一形态搬到 config 域。
13318
+ */
13319
+ const DOCS_CHAPTERS = [
13320
+ {
13321
+ id: "catalog",
13322
+ summary: "ACC 能力总目录——7 个能力分区,每区一句话 + 去哪个子命令看详情",
13323
+ render: () => ACC_CATALOG
13324
+ },
13325
+ {
13326
+ id: "config",
13327
+ summary: "CCC 配置全表——9 段各是什么、怎么填、填错会怎样(.opencode/serenity.json)",
13328
+ render: () => CCC_CONFIG_REFERENCE
13329
+ },
13330
+ {
13331
+ id: "trajectory-constraint",
13332
+ summary: "轨迹约束:一条轨迹怎么被约束(trajectory.json 的 schema + 双白名单)",
13333
+ render: () => TRAJECTORY_CONSTRAINT_GUIDE
13334
+ }
13335
+ ];
13336
+ /**
13337
+ * 指针与章目的关系(v1.75.0):
13338
+ * 被降级的旧入口(`msm catalog` / `msm ccc-config` / `config guide`)统一走
13339
+ * `config-docs.ts` 的 `docsPointer()` —— **只有一处实现**,避免两域各写一份文案而漂移。
13340
+ */
12989
13341
  function createContainerAdminTool(ctx) {
12990
13342
  return defineTool({
12991
13343
  name: "container_admin",
12992
- description: "Container administration (the ship's maintenance bay — one entry for managing the CCC): role — Skiff cognitive-subset roles (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); config — view CCC configuration. Execution of registered MSMs belongs to the msm tool; this tool manages the container.",
13344
+ 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
13345
  parameters: {
12994
13346
  domain: {
12995
13347
  type: "string",
12996
13348
  enum: DOMAIN_NAMES,
12997
13349
  required: true,
12998
- description: "Management domain: role (Skiff roles) / msm (MSM registry + manuals) / config (CCC config view)"
13350
+ description: "Management domain: config (CCC configuration + 轨迹约束) / msm (MSM registry + manuals)"
12999
13351
  },
13000
13352
  action: {
13001
13353
  type: "string",
13002
- description: "Domain subcommand (see domain descriptions; e.g. role: validate/apply/list/guide; msm: register/deregister/check/guide/catalog/ccc-config)"
13354
+ description: "Domain subcommand. config: docs / view / guide / validate / apply / list; msm: register / deregister / check / guide / catalog / ccc-config"
13355
+ },
13356
+ /** 第二层菜单参数:`config docs <chapter>`(缺省 = 目录) */
13357
+ chapter: {
13358
+ type: "string",
13359
+ description: "config docs only: chapter id (omit for the index; e.g. config / trajectory-constraint)"
13003
13360
  },
13004
13361
  name: {
13005
13362
  type: "string",
@@ -13038,18 +13395,38 @@ function createContainerAdminTool(ctx) {
13038
13395
  const root = cccRootForExec(exec);
13039
13396
  if (!root) throw new Error(NO_CCC_FROM_AGENT_CWD);
13040
13397
  const sessionId = agentSessionId(exec);
13041
- const domain = args.domain;
13398
+ const rawDomain = String(args.domain ?? "");
13399
+ const domain = DOMAIN_ALIASES[rawDomain] ?? rawDomain;
13042
13400
  const action = args.action ?? "";
13401
+ if (!DOMAIN_NAMES.includes(domain)) throw new Error(`container_admin requires domain: ${DOMAIN_NAMES.join(" | ")}`);
13043
13402
  if (domain === "msm" && (action === "register" || action === "deregister")) {
13044
- const gate = skiffMsmGate(root, sessionId, action, args.name);
13403
+ const gate = trajectoryConstraintMsmGate(root, sessionId, action, args.name);
13045
13404
  if (gate.reject) throw new Error(gate.reject);
13046
13405
  }
13047
- if (domain === "role") switch (action) {
13048
- case "guide": return { guide: SKIFF_GUIDE };
13049
- case "validate": return validateSkiffConfig(root);
13050
- case "apply": return applySkiffConfig(root);
13051
- case "list": return listSkiffRoles(root);
13052
- default: throw new Error(`container_admin role requires action: guide | validate | apply | list`);
13406
+ if (domain === "config") switch (action) {
13407
+ case "docs": {
13408
+ const chapter = args.chapter?.trim() ?? "";
13409
+ if (chapter === "") return {
13410
+ docs: renderDocsIndex(DOCS_CHAPTERS),
13411
+ chapters: DOCS_CHAPTERS.map((c) => c.id)
13412
+ };
13413
+ const found = findDocsChapter(DOCS_CHAPTERS, chapter);
13414
+ 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)`);
13415
+ return {
13416
+ chapter: found.id,
13417
+ docs: found.render()
13418
+ };
13419
+ }
13420
+ case "view":
13421
+ case "": return {
13422
+ config: viewConfigSummary(root),
13423
+ docs: "完整配置说明(每段怎么填、填错会怎样)→ container_admin config docs config"
13424
+ };
13425
+ case "guide": return { guide: docsPointer("trajectory-constraint", "轨迹约束教程") };
13426
+ case "validate": return validateTrajectoryConstraint(root);
13427
+ case "apply": return applyTrajectoryConstraint(root);
13428
+ case "list": return listTrajectoryConstraintSummary(root);
13429
+ default: throw new Error(`container_admin config requires action: docs | view | guide | validate | apply | list`);
13053
13430
  }
13054
13431
  if (domain === "msm") {
13055
13432
  const mgmtActions = [
@@ -13072,7 +13449,6 @@ function createContainerAdminTool(ctx) {
13072
13449
  usage: args.usage
13073
13450
  });
13074
13451
  }
13075
- if (domain === "config") return { config: runMsm(root, { action: "ccc-config" }) };
13076
13452
  throw new Error(`container_admin requires domain: ${DOMAIN_NAMES.join(" | ")}`);
13077
13453
  }
13078
13454
  });
@@ -13324,7 +13700,7 @@ const SENSITIVE_CREDENTIAL_FILES = /* @__PURE__ */ new Set(["localstore.json"]);
13324
13700
  * MSM 注册表写保护(需求⑤b S142 用户拍板:"msm注册的文件需要写保护起来,避免CCC意外写坏搞崩自己"):
13325
13701
  * mech-registry.json 是 CCC 执行层地基(坏 → loadMsmEntries 抛 → container_admin/output-guard 全崩,
13326
13702
  * register 也崩 → 自锁)。**写 deny、读 allow**(与 localstore 读 deny 语义区分 R6——注册表是
13327
- * 结构核心不是秘密,需被 output-guard/skiff-admin 读取建 MSM 词表)。
13703
+ * 结构核心不是秘密,需被 output-guard/trajectory-constraint-admin 读取建 MSM 词表)。
13328
13704
  *
13329
13705
  * 单级化(需求⑤a)后**唯一合法注册表 = cccName 聚合档**
13330
13706
  * `.opencode/skills/<cccName>/references/mech-registry.json`(cccName = .serenity 首行)。
@@ -13362,15 +13738,15 @@ function isWriteTool(toolName, action) {
13362
13738
  */
13363
13739
  function decideGuard(input) {
13364
13740
  const { root, toolName, safeModeOn, blacklist, pathArg, action } = input;
13365
- if (input.skiffProfile !== void 0 && input.skiffProfile !== null) {
13366
- const profile = input.skiffProfile;
13741
+ if (input.trajectoryConstraint !== void 0 && input.trajectoryConstraint !== null) {
13742
+ const profile = input.trajectoryConstraint;
13367
13743
  if (toolName === "skill") return { kind: "allow" };
13368
13744
  if (profile.msms.includes(toolName)) return {
13369
13745
  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
13746
  kind: "deny"
13371
13747
  };
13372
13748
  if (!profileToolWhitelist(profile).has(toolName)) return {
13373
- deny: "tool not allowed in this skiff trajectory",
13749
+ deny: "tool not allowed in this 轨迹约束 trajectory",
13374
13750
  kind: "deny"
13375
13751
  };
13376
13752
  }
@@ -13655,17 +14031,17 @@ const exclusiveRestrictions = /* @__PURE__ */ new Map();
13655
14031
  * 每次 pre-step 都重算白名单,只有**算出来的结果变了**才重新发 restrict
13656
14032
  * —— 配置不变就不重复发(restrict 会重建 schema,没必要每步都做)。
13657
14033
  */
13658
- const skiffRoleRestrictions = /* @__PURE__ */ new Map();
14034
+ const trajectoryConstraintRestrictions = /* @__PURE__ */ new Map();
13659
14035
  /**
13660
- * 同步 **skiff 会话的工具面**:把白名单之外的工具**从 schema 移除**(不是"看得到但被拒")。
14036
+ * 同步 **轨迹约束会话的工具面**:把白名单之外的工具**从 schema 移除**(不是"看得到但被拒")。
13661
14037
  *
13662
14038
  * ## 为什么必须有这条(owner 2026-10-19:「工具清单也要热加载」)
13663
14039
  *
13664
- * 在它之前,skiff 白名单**只由运行时守卫拦截**(`decideGuard` 的 skiff 分支):
14040
+ * 在它之前,轨迹约束白名单**只由运行时守卫拦截**(`decideGuard` 的 轨迹约束 分支):
13665
14041
  * 工具全部出现在模型 schema 里,调用时才被打回。⇒ 两个真实后果:
13666
14042
  * ① **模型看得见它不该有的工具** —— 它会去试,试了才撞墙(一轮白烧,且错误文案泛化,
13667
14043
  * 看不出"是不在清单里"还是"临时性拒绝");
13668
- * ② **配置变动在 schema 上永远不生效** —— 改 `skiff.json` 的 `tools` 只影响"调用是否放行"。
14044
+ * ② **配置变动在 schema 上永远不生效** —— 改 `trajectory-constraint.json` 的 `tools` 只影响"调用是否放行"。
13669
14045
  *
13670
14046
  * ## 判据与降级
13671
14047
  *
@@ -13673,24 +14049,24 @@ const skiffRoleRestrictions = /* @__PURE__ */ new Map();
13673
14049
  * 不去动宿主的工具 —— 宿主的工具由各自的条件同步管)。
13674
14050
  * - ⚠️ **未注册的名字会让整次 restrict 失败**(宿主契约逐字 "unknown names … fail"),
13675
14051
  * 而 `diagram` 是按开关注册的 ⇒ 与 `hideAllFor` 同款**两档尝试**(先全覆盖,失败退常驻段)。
13676
- * - 读不到约束面(不是 skiff / 配置删了)⇒ **撤销既有隐藏**(回到"无约束"):
13677
- * ⛔ 不能只是 `return` —— 那会把上一步的隐藏**永久留在**一个已不再是 skiff 的会话上
13678
- * (这正是 owner 说的"删掉 `skiff.json` 就该立刻恢复")。
14052
+ * - 读不到约束面(不是 轨迹约束 / 配置删了)⇒ **撤销既有隐藏**(回到"无约束"):
14053
+ * ⛔ 不能只是 `return` —— 那会把上一步的隐藏**永久留在**一个已不再是 轨迹约束的会话上
14054
+ * (这正是 owner 说的"删掉 `trajectory-constraint.json` 就该立刻恢复")。
13679
14055
  * - 幂等:算出来的 deny 签名不变就跳过(不每步重建 schema)。
13680
14056
  *
13681
14057
  * @param agent 目标会话
13682
14058
  * @param root CCC 根
13683
14059
  */
13684
- function syncSkiffRoleToolVisibility(agent, root) {
14060
+ function syncTrajectoryConstraintToolVisibility(agent, root) {
13685
14061
  const sessionId = agent.session.id ?? "global";
13686
- const profile = skiffProfileForSession(root, sessionId);
13687
- const existing = skiffRoleRestrictions.get(sessionId);
14062
+ const profile = trajectoryConstraintForSession(root, sessionId);
14063
+ const existing = trajectoryConstraintRestrictions.get(sessionId);
13688
14064
  if (profile === null) {
13689
14065
  if (existing !== void 0) {
13690
14066
  try {
13691
14067
  existing.dispose();
13692
14068
  } catch {}
13693
- skiffRoleRestrictions.delete(sessionId);
14069
+ trajectoryConstraintRestrictions.delete(sessionId);
13694
14070
  }
13695
14071
  return;
13696
14072
  }
@@ -13706,7 +14082,7 @@ function syncSkiffRoleToolVisibility(agent, root) {
13706
14082
  try {
13707
14083
  existing.dispose();
13708
14084
  } catch {}
13709
- skiffRoleRestrictions.delete(sessionId);
14085
+ trajectoryConstraintRestrictions.delete(sessionId);
13710
14086
  }
13711
14087
  return;
13712
14088
  }
@@ -13715,10 +14091,10 @@ function syncSkiffRoleToolVisibility(agent, root) {
13715
14091
  try {
13716
14092
  existing.dispose();
13717
14093
  } catch {}
13718
- skiffRoleRestrictions.delete(sessionId);
14094
+ trajectoryConstraintRestrictions.delete(sessionId);
13719
14095
  }
13720
14096
  const dispose = agent.ctx.tools.restrict({ deny: [...deny] });
13721
- skiffRoleRestrictions.set(sessionId, {
14097
+ trajectoryConstraintRestrictions.set(sessionId, {
13722
14098
  signature,
13723
14099
  deny,
13724
14100
  dispose
@@ -13730,13 +14106,13 @@ function syncSkiffRoleToolVisibility(agent, root) {
13730
14106
  }
13731
14107
  }
13732
14108
  /** 会话销毁清理(lifecycle 调用) */
13733
- function forgetSkiffRoleToolVisibility(sessionId) {
13734
- const existing = skiffRoleRestrictions.get(sessionId);
14109
+ function forgetTrajectoryConstraintToolVisibility(sessionId) {
14110
+ const existing = trajectoryConstraintRestrictions.get(sessionId);
13735
14111
  if (!existing) return;
13736
14112
  try {
13737
14113
  existing.dispose();
13738
14114
  } catch {}
13739
- skiffRoleRestrictions.delete(sessionId);
14115
+ trajectoryConstraintRestrictions.delete(sessionId);
13740
14116
  }
13741
14117
  /**
13742
14118
  * 同步专属工具可见性(v1.33)。
@@ -13931,7 +14307,7 @@ function registerGuards(ctx, opts = {}) {
13931
14307
  const pathArg = extractPathArg(exec);
13932
14308
  const action = extractAction(exec);
13933
14309
  const sessionId = exec?.agent?.session?.id;
13934
- const skiffProfile = skiffProfileForSession(root, sessionId);
14310
+ const trajectoryConstraint = trajectoryConstraintForSession(root, sessionId);
13935
14311
  return decideGuard({
13936
14312
  root,
13937
14313
  toolName: exec.name,
@@ -13939,7 +14315,7 @@ function registerGuards(ctx, opts = {}) {
13939
14315
  blacklist,
13940
14316
  pathArg,
13941
14317
  action,
13942
- skiffProfile
14318
+ trajectoryConstraint
13943
14319
  });
13944
14320
  };
13945
14321
  ctx.on("tools/pre-execute", async (exec, next) => {
@@ -14176,7 +14552,7 @@ function createEpochPromotion(promoteEvents, requiredSignals = 1, maxRoundsFallb
14176
14552
  const loopSid = session.id;
14177
14553
  const loopCwd = session.header?.cwd ?? process.cwd();
14178
14554
  const loopRoot = cccRootForCwd(loopCwd);
14179
- if (typeof loopSid === "string" && (loopSid.startsWith("cheap-worker-") || loopRoot !== null && isSkiffSession(loopRoot, loopSid))) return {
14555
+ if (typeof loopSid === "string" && (loopSid.startsWith("cheap-worker-") || loopRoot !== null && isTrajectoryConstrained(loopRoot, loopSid))) return {
14180
14556
  boundary: -1,
14181
14557
  promoted: true
14182
14558
  };
@@ -14295,7 +14671,7 @@ function registerBootstrap(ctx) {
14295
14671
  const depth = session?.header?.delegationDepth ?? 0;
14296
14672
  const sid = typeof session?.id === "string" ? session.id : void 0;
14297
14673
  if (sid === void 0) return;
14298
- if (sid.startsWith("cheap-worker-") || isSkiffSession(root, sid)) return;
14674
+ if (sid.startsWith("cheap-worker-") || isTrajectoryConstrained(root, sid)) return;
14299
14675
  if (depth === 0) {
14300
14676
  if (hasUserMessageHistory(session)) return;
14301
14677
  } else if (anchoredSessions.has(sid)) return;
@@ -14609,7 +14985,7 @@ function forgetLogbookCompactionState(scope) {
14609
14985
  * 解析**活跃** SESSION.md 绝对路径(缓存 60s)。
14610
14986
  *
14611
14987
  * 候选链(刻意比 rebuild 的完整链短——这里每个工具调用都要跑,贵候选不划算):
14612
- * ① `getActiveSessionInfo(scope).mdPath`(`cognitive_trajectory use` / skiff 绑定 / 重启恢复都会写)
14988
+ * ① `getActiveSessionInfo(scope).mdPath`(`cognitive_trajectory use` / 轨迹约束 绑定 / 重启恢复都会写)
14613
14989
  * ② `readLastBound(session).mdPath`(`.bindings.json` 权威绑定——重启后内存空时兜底)
14614
14990
  * 两者都拿不到 → null(**不猜**:宁可不提醒,也不去猜别的轨迹——对齐 v1.30.13 D4 的教训)。
14615
14991
  */
@@ -14738,10 +15114,10 @@ function registerKeeper(ctx, opts = {}) {
14738
15114
  const root = cccRootForCwd(cwd);
14739
15115
  if (!root) return next();
14740
15116
  const sessionId = exec.agent?.session?.id;
14741
- const skiffKeeper = skiffCognitiveTrajectoryEnabled(root, sessionId, "keeper");
14742
- const skiffRebuild = skiffCognitiveTrajectoryEnabled(root, sessionId, "rebuild");
15117
+ const participatesKeeper = trajectoryConstraintParticipates(root, sessionId, "keeper");
15118
+ const participatesRebuild = trajectoryConstraintParticipates(root, sessionId, "rebuild");
14743
15119
  const tracker = trackerFor(exec);
14744
- const shouldRemind = skiffKeeper ? tracker.step(exec.name) : false;
15120
+ const shouldRemind = participatesKeeper ? tracker.step(exec.name) : false;
14745
15121
  const downstream = await next();
14746
15122
  try {
14747
15123
  recordToolUsage(root, exec.name, exec.arguments);
@@ -14754,7 +15130,7 @@ function registerKeeper(ctx, opts = {}) {
14754
15130
  text: reminderText(code)
14755
15131
  });
14756
15132
  }
14757
- if (skiffRebuild) {
15133
+ if (participatesRebuild) {
14758
15134
  const session = exec.agent?.session;
14759
15135
  if (session) {
14760
15136
  const pressure = readContextPressure(ctx, session);
@@ -14774,7 +15150,7 @@ function registerKeeper(ctx, opts = {}) {
14774
15150
  }
14775
15151
  }
14776
15152
  }
14777
- if (skiffCognitiveTrajectoryEnabled(root, sessionId, "session")) {
15153
+ if (trajectoryConstraintParticipates(root, sessionId, "session")) {
14778
15154
  const limitKB = readSessionMdMaxKB(root, opts.configPaths);
14779
15155
  if (limitKB > 0) {
14780
15156
  const scope = sessionId ?? "global";
@@ -14797,7 +15173,7 @@ function registerKeeper(ctx, opts = {}) {
14797
15173
  } else compactionStates.delete(scope);
14798
15174
  }
14799
15175
  }
14800
- if (skiffCognitiveTrajectoryEnabled(root, sessionId, "session")) {
15176
+ if (trajectoryConstraintParticipates(root, sessionId, "session")) {
14801
15177
  const scope = sessionId ?? "global";
14802
15178
  const session = exec.agent?.session;
14803
15179
  const mdPath = resolveActiveSessionMdPath(root, scope, session);
@@ -14905,7 +15281,7 @@ function shouldAutoRestore(agent, root) {
14905
15281
  if (session.header?.parentSession) return false;
14906
15282
  if (session.id?.startsWith("cheap-worker-")) return false;
14907
15283
  const cccRoot = root !== void 0 ? root : cccRootForCwd(session.header?.cwd ?? process.cwd());
14908
- if (cccRoot && isSkiffSession(cccRoot, session.id)) return false;
15284
+ if (cccRoot && isTrajectoryConstrained(cccRoot, session.id)) return false;
14909
15285
  return true;
14910
15286
  }
14911
15287
  /**
@@ -14928,7 +15304,7 @@ function registerContext(ctx, opts = {}) {
14928
15304
  } catch {}
14929
15305
  if (!root) return;
14930
15306
  const key = agentKey(agent);
14931
- if (isSkiffSession(root, key)) return;
15307
+ if (isTrajectoryConstrained(root, key)) return;
14932
15308
  registerEntrySkillSection(agent, root);
14933
15309
  registerCognitiveTrajectorySkillSection(agent, root);
14934
15310
  const scope = agentScope(agent);
@@ -15012,9 +15388,9 @@ function registerContext(ctx, opts = {}) {
15012
15388
  const key = agentKey(agent);
15013
15389
  const downstream = await next();
15014
15390
  if (root) try {
15015
- syncSkiffRoleToolVisibility(agent, root);
15391
+ syncTrajectoryConstraintToolVisibility(agent, root);
15016
15392
  } catch {}
15017
- if (root && isSkiffSession(root, key)) return downstream;
15393
+ if (root && isTrajectoryConstrained(root, key)) return downstream;
15018
15394
  try {
15019
15395
  syncCccToolVisibility(agent, root);
15020
15396
  } catch {}
@@ -15063,7 +15439,7 @@ function registerCompactRetention(ctx, opts = {}) {
15063
15439
  const cwd = agent.session?.header?.cwd ?? process.cwd();
15064
15440
  const root = cccRootForCwd(cwd);
15065
15441
  if (!root) return;
15066
- if (isSkiffSession(root, String(session.id ?? ""))) return;
15442
+ if (isTrajectoryConstrained(root, String(session.id ?? ""))) return;
15067
15443
  try {
15068
15444
  agent.inject(accMessage(root, configPaths, entrySkillMaxChars));
15069
15445
  } catch {}
@@ -15404,7 +15780,7 @@ function requireWebUi(req, res, message) {
15404
15780
  *
15405
15781
  * 🔴🔴 **2026-10-18 修缺陷(owner 报「在别的 CCC 点开显示的还是 home-serenity 的信息」)**:
15406
15782
  * 旧实现第 ③ 步是「**遍历 live sessions 找第一个 CCC 会话的 cwd**」—— 那是**跨工作区猜测**:
15407
- * 只要 sessionId 缺失或那条会话查不到(ACP/skiff 会话、尚未 materialize 的会话、远程访问),
15783
+ * 只要 sessionId 缺失或那条会话查不到(ACP/轨迹约束会话、尚未 materialize 的会话、远程访问),
15408
15784
  * 它就会**静默返回另一个 CCC 的路径**,于是面板把**别人的轨迹/技能/安全模式**当成你的显示出来。
15409
15785
  * 这比"显示未激活"危险得多 —— 用户会据此做判断(以为自己的 safe-mode 开着、以为那是自己的轨迹)。
15410
15786
  *
@@ -15618,17 +15994,22 @@ function registerStatusApi(ctx, opts = {}) {
15618
15994
  sendJson$2(res, 400, { error: chk.error });
15619
15995
  return;
15620
15996
  }
15621
- const { readWeixinSettings } = await import("./route-ItCgAsnz.js").then((n) => n.c);
15997
+ const { readWeixinSettings } = await import("./route-COOFLTpU.js").then((n) => n.c);
15622
15998
  const { weixinBridgeStatus } = await Promise.resolve().then(() => bridge_exports);
15623
15999
  const settings = readWeixinSettings(chk.root);
15624
16000
  const bridge = weixinBridgeStatus().find((b) => b.ccc === chk.root);
15625
- const { readWeixinCredential } = await import("./route-ItCgAsnz.js").then((n) => n.c);
15626
- const accounts = (settings.accounts ?? []).map((a) => ({
15627
- accountId: a.accountId,
15628
- name: a.name ?? void 0,
15629
- enabled: a.enabled !== false,
15630
- bound: readWeixinCredential(chk.root, a.accountId) !== null
15631
- }));
16001
+ const { imCredentialBound } = await import("./route-COOFLTpU.js").then((n) => n.c);
16002
+ const { normalizeAccountChannel } = await import("./accounts-BHQbakUh.js").then((n) => n.t);
16003
+ const accounts = (settings.accounts ?? []).map((a) => {
16004
+ const channel = normalizeAccountChannel(a.channel);
16005
+ return {
16006
+ accountId: a.accountId,
16007
+ name: a.name ?? void 0,
16008
+ enabled: a.enabled !== false,
16009
+ channel,
16010
+ bound: imCredentialBound(chk.root, a.accountId, channel)
16011
+ };
16012
+ });
15632
16013
  sendJson$2(res, 200, {
15633
16014
  enabled: settings.enabled === true,
15634
16015
  botType: settings.botType ?? void 0,
@@ -15654,7 +16035,7 @@ function registerStatusApi(ctx, opts = {}) {
15654
16035
  const { fetchQRCode } = await import("./ilink-BMk2w2KY.js").then((n) => n.a);
15655
16036
  const { randomUUID } = await import("node:crypto");
15656
16037
  for (const [key, v] of weixinLogins) if (Date.now() - v.startedAt > WEIXIN_LOGIN_TTL_MS) weixinLogins.delete(key);
15657
- const { readWeixinSettings } = await import("./route-ItCgAsnz.js").then((n) => n.c);
16038
+ const { readWeixinSettings } = await import("./route-COOFLTpU.js").then((n) => n.c);
15658
16039
  const qr = await fetchQRCode({ botType: readWeixinSettings(root).botType ?? void 0 });
15659
16040
  const loginKey = randomUUID();
15660
16041
  weixinLogins.set(loginKey, {
@@ -15675,7 +16056,7 @@ function registerStatusApi(ctx, opts = {}) {
15675
16056
  sendJson$2(res, 400, { error: "missing accountId" });
15676
16057
  return;
15677
16058
  }
15678
- const { removeWeixinAccount } = await import("./route-ItCgAsnz.js").then((n) => n.c);
16059
+ const { removeWeixinAccount } = await import("./route-COOFLTpU.js").then((n) => n.c);
15679
16060
  const { syncCccBridge } = await Promise.resolve().then(() => bridge_exports);
15680
16061
  removeWeixinAccount(root, body.accountId);
15681
16062
  syncCccBridge(ctx, root);
@@ -15701,8 +16082,8 @@ function registerStatusApi(ctx, opts = {}) {
15701
16082
  trajectory: code
15702
16083
  });
15703
16084
  }
15704
- const { saveWeixinRoutes } = await import("./route-ItCgAsnz.js").then((n) => n.c);
15705
- const { findDirNameByCode } = await import("./profile-BIQ2widI.js").then((n) => n.i);
16085
+ const { saveWeixinRoutes } = await import("./route-COOFLTpU.js").then((n) => n.c);
16086
+ const { findDirNameByCode } = await import("./profile-Cz4oBXlI.js").then((n) => n.i);
15706
16087
  for (const r of routes) if (findDirNameByCode(root, r.trajectory) === null) {
15707
16088
  sendJson$2(res, 400, { error: `unknown trajectory: ${r.trajectory} (no such trajectory code in ${root})` });
15708
16089
  return;
@@ -15713,7 +16094,7 @@ function registerStatusApi(ctx, opts = {}) {
15713
16094
  }
15714
16095
  if (body.action === "set-enabled") {
15715
16096
  const enabled = body.enabled === true;
15716
- const { readWeixinSettings, setWeixinEnabled } = await import("./route-ItCgAsnz.js").then((n) => n.c);
16097
+ const { readWeixinSettings, setWeixinEnabled } = await import("./route-COOFLTpU.js").then((n) => n.c);
15717
16098
  const { syncCccBridge } = await Promise.resolve().then(() => bridge_exports);
15718
16099
  const settings = readWeixinSettings(root);
15719
16100
  if (enabled && (settings.accounts ?? []).length === 0) {
@@ -15729,6 +16110,35 @@ function registerStatusApi(ctx, opts = {}) {
15729
16110
  sendJson$2(res, 200, { enabled });
15730
16111
  return;
15731
16112
  }
16113
+ if (body.action === "add-feishu-account") {
16114
+ const appId = (body.appId ?? "").trim();
16115
+ const appSecret = (body.appSecret ?? "").trim();
16116
+ if (appId === "" || appSecret === "") {
16117
+ sendJson$2(res, 400, { error: "appId 与 appSecret 都必须填(只填一个等于没绑)" });
16118
+ return;
16119
+ }
16120
+ const { readWeixinSettings, upsertWeixinAccount, writeFeishuCredential, nextFeishuAccountId } = await import("./route-COOFLTpU.js").then((n) => n.c);
16121
+ const { syncCccBridge } = await Promise.resolve().then(() => bridge_exports);
16122
+ const before = readWeixinSettings(root);
16123
+ const accountId = (body.accountId ?? "").trim() !== "" ? body.accountId.trim() : nextFeishuAccountId(before);
16124
+ upsertWeixinAccount(root, {
16125
+ accountId,
16126
+ name: (body.name ?? "").trim() !== "" ? body.name.trim() : void 0,
16127
+ channel: "feishu",
16128
+ enabled: true
16129
+ });
16130
+ writeFeishuCredential(root, accountId, {
16131
+ appId,
16132
+ appSecret
16133
+ });
16134
+ if (before.enabled) syncCccBridge(ctx, root);
16135
+ sendJson$2(res, 200, {
16136
+ accountId,
16137
+ channel: "feishu",
16138
+ saved: true
16139
+ });
16140
+ return;
16141
+ }
15732
16142
  sendJson$2(res, 400, { error: `unsupported action: ${body.action ?? ""}` });
15733
16143
  } catch (err) {
15734
16144
  sendJson$2(res, 400, { error: err.message ?? String(err) });
@@ -15757,7 +16167,7 @@ function registerStatusApi(ctx, opts = {}) {
15757
16167
  return;
15758
16168
  }
15759
16169
  const { pollQRStatus } = await import("./ilink-BMk2w2KY.js").then((n) => n.a);
15760
- const { readWeixinSettings } = await import("./route-ItCgAsnz.js").then((n) => n.c);
16170
+ const { readWeixinSettings } = await import("./route-COOFLTpU.js").then((n) => n.c);
15761
16171
  const settings = readWeixinSettings(login.root);
15762
16172
  const status = await pollQRStatus({
15763
16173
  baseUrl: void 0,
@@ -15772,7 +16182,7 @@ function registerStatusApi(ctx, opts = {}) {
15772
16182
  });
15773
16183
  return;
15774
16184
  }
15775
- const { upsertWeixinAccount, writeWeixinCredential, nextWeixinAccountId } = await import("./route-ItCgAsnz.js").then((n) => n.c);
16185
+ const { upsertWeixinAccount, writeWeixinCredential, nextWeixinAccountId } = await import("./route-COOFLTpU.js").then((n) => n.c);
15776
16186
  const { syncCccBridge } = await Promise.resolve().then(() => bridge_exports);
15777
16187
  const accountId = nextWeixinAccountId(settings);
15778
16188
  upsertWeixinAccount(login.root, {
@@ -16214,7 +16624,7 @@ const HOST_SERVICES = [
16214
16624
  name: "get",
16215
16625
  kind: "function"
16216
16626
  }],
16217
- impact: "skiff/cheap-worker 无法创建或复用 agent",
16627
+ impact: "轨迹约束/cheap-worker 无法创建或复用 agent",
16218
16628
  required: true
16219
16629
  },
16220
16630
  {
@@ -16637,7 +17047,7 @@ function summarizeHostContract(report) {
16637
17047
  *
16638
17048
  * ## 现状 → 本模块要收掉什么(取证:`…/acc-c4c5-current-state.md` §①/§③ 3.1)
16639
17049
  *
16640
- * 插件自己 spawn 了**三个** node:http 监听器(B 3081 网关 / C 3082 微信发送 / D 3099 Skiff 调试)。
17050
+ * 插件自己 spawn 了**三个** node:http 监听器(B 3081 网关 / C 3082 微信发送 / D 3099 轨迹约束调试)。
16641
17051
  * ⚠️ **2026-10-15 变更**:原为四个(含 **E 3100 ACP+问答**),E 已随 owner「砍掉外部问答」令**整面退役**
16642
17052
  * ⇒ 仍保有面 B/C/D 三个;本段保留"当年是四个"的记述,因为下方"两套拆卸"的病史由它解释。
16643
17053
  * 此前四处各写一份同款样板:`createServer` + `server.once('error')` +
@@ -17808,9 +18218,9 @@ function sessionNameFromMdPath(mdPath) {
17808
18218
  * → ②b **权威绑定**(`AGENT_SESSIONS/.bindings.json`,本会话持久绑定)→ ③ surface 首条锚点
17809
18219
  * (events 异常时兜底)。
17810
18220
  *
17811
- * v1.30.13(S142 用户"微信桥 + skiff 会话锚定要准确",诊断 D4):**移除**原 ④「全局最新未完成
18221
+ * v1.30.13(S142 用户"微信桥 + 轨迹约束会话锚定要准确",诊断 D4):**移除**原 ④「全局最新未完成
17812
18222
  * 会话」(`findLatestActiveSessionMd`)。理由(R↓):④ 是**跨轨迹**猜测——候选①②③全空时,
17813
- * 它会把 rebuild 静默接到 AGENT_SESSIONS 里最新的另一个会话(临时 skiff 会话 persistent=false、
18223
+ * 它会把 rebuild 静默接到 AGENT_SESSIONS 里最新的另一个会话(临时 轨迹约束会话 persistent=false、
17814
18224
  * 重启后首条消息前、新会话首条消息都命中该形态),而"接错轨迹"比重建失败危险得多
17815
18225
  * (错误轨迹被继续写入 + 正确轨迹静默丢失)。现在全部候选失败 → 返回 null → 调用方
17816
18226
  * 报错引导用户显式 `cognitive_trajectory use`(响亮失败优于静默接错)。
@@ -19856,9 +20266,9 @@ const MECHANISM_WORDS = [
19856
20266
  "拦截缝",
19857
20267
  "装配层",
19858
20268
  "acp-core",
19859
- "skiff-core",
19860
- "skiff-registry",
19861
- "skiff-role",
20269
+ "trajectory-session",
20270
+ "trajectory-window",
20271
+ "trajectory-constraint",
19862
20272
  "output-guard"
19863
20273
  ];
19864
20274
  /**
@@ -19958,20 +20368,20 @@ const rebukeStates = /* @__PURE__ */ new Map();
19958
20368
  /**
19959
20369
  * 外部面会话判定(v1.26.3 用户拍板:仅外部面检测,本地维护会话豁免)。
19960
20370
  *
19961
- * 🔴 **2026-10-19 判据更换**:skiff 那一条从「sessionId 前缀 `skiff-`」改为
19962
- * **「该会话绑定的轨迹有 `skiff.json`」**(owner 重做令)。前缀判据在新模型下是
19963
- * **错的**——skiff 的身份是轨迹,不再由 id 前缀表达;继续用前缀会漏掉所有
19964
- * "挂在 skiff 轨迹上、但 id 不带前缀"的会话(典型 = 微信桥按轨迹派生的会话 id)。
20371
+ * 🔴 **2026-10-19 判据更换**:轨迹约束那一条从「sessionId 前缀 `轨迹约束-`」改为
20372
+ * **「该会话绑定的轨迹有 `trajectory-constraint.json`」**(owner 重做令)。前缀判据在新模型下是
20373
+ * **错的**——轨迹约束的身份是轨迹,不再由 id 前缀表达;继续用前缀会漏掉所有
20374
+ * "挂在 轨迹约束轨迹上、但 id 不带前缀"的会话(典型 = 微信桥按轨迹派生的会话 id)。
19965
20375
  *
19966
- * ⚠️ `rebuild-` 前缀保留(那是另一件事:重建会话同属对外面,与 skiff 无关);
20376
+ * ⚠️ `rebuild-` 前缀保留(那是另一件事:重建会话同属对外面,与 轨迹约束 无关);
19967
20377
  * `acp-` 分支保留但**已是死判据**(无生产者;面 E 已于 2026-10-15 退役)——
19968
20378
  * 理由与"为何不擅自删"见文件头注释。
19969
20379
  */
19970
20380
  function isExternalFaceSession(sessionId, root) {
19971
20381
  if (!sessionId) return false;
19972
- return isSkiffSession(root, sessionId) || sessionId.startsWith("acp-") || sessionId.startsWith("rebuild-");
20382
+ return isTrajectoryConstrained(root, sessionId) || sessionId.startsWith("acp-") || sessionId.startsWith("rebuild-");
19973
20383
  }
19974
- /** 读会话最后一个 assistant/message 文本(skiff-core 同款)
20384
+ /** 读会话最后一个 assistant/message 文本(trajectory-session 同款)
19975
20385
  * **v1.63.3 起唯一的导出理由消失**:原导出是给 `unattended-seam.ts` 复用的(D77),
19976
20386
  * 该文件已随无人值守功能整条退役 ⇒ 现只有本模块自己用(`isLeak` 的调用点)。
19977
20387
  * ⚠️ 保持 `export` 无害(它有测试覆盖,且将来"同一读法只留一份"的纪律仍成立),
@@ -20445,31 +20855,6 @@ function invokeWeixinHook(root, hookRel, event) {
20445
20855
  return activeRunner(root, hookRel, event);
20446
20856
  }
20447
20857
  //#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
20858
  //#region src/im/weixin-account.ts
20474
20859
  /** 轮询间隔(getupdates 失败后重试;成功 = 立即续,长轮询本身 hold 35s) */
20475
20860
  const POLL_RETRY_MS = 3e3;
@@ -20546,7 +20931,7 @@ function installWeixinAccountImpl(d) {
20546
20931
  const manualSessions = /* @__PURE__ */ new Map();
20547
20932
  /**
20548
20933
  * 本轮是否已成功发送(**由桥在每轮开始时清空** `clearSentThisTurn`;闸门只置位不清空——
20549
- * v1.30.17 起桥要在 `askSkiff` 返回后读它决定是否兜底,故 turn-stopping 不再结算清空)。
20934
+ * v1.30.17 起桥要在 `askTrajectory` 返回后读它决定是否兜底,故 turn-stopping 不再结算清空)。
20550
20935
  */
20551
20936
  const sentThisTurn = /* @__PURE__ */ new Set();
20552
20937
  /** 连续打回计数(成功发送即清零) */
@@ -20614,7 +20999,7 @@ function buildManualOutputRebuke(session) {
20614
20999
  }
20615
21000
  /**
20616
21001
  * 装配闸门(index.ts apply 调用)。
20617
- * 只对**已登记的手动模式会话**生效——其他会话(主舱/其他 skiff 会话)零干预。
21002
+ * 只对**已登记的手动模式会话**生效——其他会话(主舱/其他 轨迹约束会话)零干预。
20618
21003
  */
20619
21004
  function registerWeixinOutputGuard(ctx) {
20620
21005
  try {
@@ -20681,7 +21066,7 @@ const MEDIA_MAX_BYTES = 20971520;
20681
21066
  */
20682
21067
  async function resolveCccRootByAlias(ctx, alias) {
20683
21068
  try {
20684
- const { listCccs } = await import("./ccc-roots-BhoA7G-0.js").then((n) => n.a);
21069
+ const { listCccs } = await import("./ccc-roots-BVxn97dY.js").then((n) => n.a);
20685
21070
  const entries = await listCccs(ctx);
20686
21071
  const byName = entries.find((e) => e.name === alias);
20687
21072
  if (byName) return byName.root;
@@ -20749,7 +21134,7 @@ async function resolveInboundTarget(ctx, input) {
20749
21134
  });
20750
21135
  return null;
20751
21136
  }
20752
- const { findDirNameByCode } = await import("./profile-BIQ2widI.js").then((n) => n.i);
21137
+ const { findDirNameByCode } = await import("./profile-Cz4oBXlI.js").then((n) => n.i);
20753
21138
  if (findDirNameByCode(root, target.role) === null) {
20754
21139
  const detail = `目标 CCC "${target.ccc}" 内没有编码为 "${target.role}" 的认知轨迹`;
20755
21140
  console.error(`[serenity-hooks] ✗ human-channel: ${detail}(account=${input.account} user=${input.user})—— 该消息**未投递**,请改订阅表里的目标编码`);
@@ -20763,7 +21148,7 @@ async function resolveInboundTarget(ctx, input) {
20763
21148
  });
20764
21149
  return null;
20765
21150
  }
20766
- const profile = readSkiffProfileByCode(root, target.role);
21151
+ const profile = readTrajectoryConstraintByCode(root, target.role);
20767
21152
  return {
20768
21153
  root,
20769
21154
  trajectory: target.role,
@@ -20814,13 +21199,13 @@ async function handleInboundEvent(ctx, root, accountId, io, event, preset) {
20814
21199
  }
20815
21200
  try {
20816
21201
  const sessionId = weixinSessionIdForTrajectory(trajectory);
20817
- const existing = getSkiffAgent(sessionId);
21202
+ const existing = getTrajectoryAgent(sessionId);
20818
21203
  const hc = readCheapWorkerConfig(root);
20819
21204
  const ref = existing ? {
20820
21205
  agent: existing,
20821
21206
  sessionId,
20822
21207
  resumed: true
20823
- } : await createSkiffAgent(ctx, root, profile ?? {
21208
+ } : await createTrajectoryAgent(ctx, root, profile ?? {
20824
21209
  dirName: trajectory,
20825
21210
  code: trajectory,
20826
21211
  path: "",
@@ -20885,7 +21270,7 @@ async function handleInboundEvent(ctx, root, accountId, io, event, preset) {
20885
21270
  }
20886
21271
  }
20887
21272
  const parts = [];
20888
- const workspaceMd = skiffWorkspaceMdPath(root, sessionId) ?? getActiveSessionInfo(sessionId)?.mdPath ?? null;
21273
+ const workspaceMd = trajectoryWorkspaceMdPath(root, sessionId) ?? getActiveSessionInfo(sessionId)?.mdPath ?? null;
20889
21274
  if (workspaceMd) {
20890
21275
  if (!ensureWorkspacePromptSection(ref.agent, sessionId, root)) parts.push(workspaceCognitiveTrajectoryLine(workspaceMd));
20891
21276
  }
@@ -20915,7 +21300,7 @@ async function handleInboundEvent(ctx, root, accountId, io, event, preset) {
20915
21300
  })).catch((err) => {
20916
21301
  console.log(`[serenity-hooks] im hook incoming error: ${err instanceof Error ? err.message : String(err)}`);
20917
21302
  });
20918
- const answer = (await askSkiff(ctx, ref.agent, question)).answer ?? "";
21303
+ const answer = (await askTrajectory(ctx, ref.agent, question)).answer ?? "";
20919
21304
  if (manualOutput) {
20920
21305
  if (!manualOutputFallbackNeeded({
20921
21306
  fallbackOnNoSend: settings.fallbackOnNoSend,
@@ -21561,7 +21946,7 @@ function syncHumanChannel(ctx) {
21561
21946
  (async () => {
21562
21947
  try {
21563
21948
  stopMachineLoops();
21564
- const { listCccs } = await import("./ccc-roots-BhoA7G-0.js").then((n) => n.a);
21949
+ const { listCccs } = await import("./ccc-roots-BVxn97dY.js").then((n) => n.a);
21565
21950
  for (const entry of await listCccs(ctx)) syncCccBridge(ctx, entry.root);
21566
21951
  } catch {}
21567
21952
  })();
@@ -21825,7 +22210,7 @@ function matchCcc(input, candidates) {
21825
22210
  }
21826
22211
  /** 组装候选列表(`ccc-roots.listCccs` 投影 + `.serenity` 名;动态 import 保持本模块静态依赖轻量) */
21827
22212
  async function collectCandidates(ctx) {
21828
- const { listCccs } = await import("./ccc-roots-BhoA7G-0.js").then((n) => n.a);
22213
+ const { listCccs } = await import("./ccc-roots-BVxn97dY.js").then((n) => n.a);
21829
22214
  const entries = await listCccs(ctx, { defaultRoot: process.cwd() });
21830
22215
  const seen = /* @__PURE__ */ new Set();
21831
22216
  const out = [];
@@ -22045,7 +22430,7 @@ function sessionIdOf(value) {
22045
22430
  /** 单会话内存态清理(幂等;无对应状态即 no-op) */
22046
22431
  function cleanupSessionState(sessionId) {
22047
22432
  try {
22048
- unregisterSkiffSession(sessionId);
22433
+ unregisterTrajectorySession(sessionId);
22049
22434
  } catch {}
22050
22435
  try {
22051
22436
  clearActiveSessionInfo(sessionId);
@@ -22066,7 +22451,7 @@ function cleanupSessionState(sessionId) {
22066
22451
  forgetHostScheduleToolHiding(sessionId);
22067
22452
  } catch {}
22068
22453
  try {
22069
- forgetSkiffRoleToolVisibility(sessionId);
22454
+ forgetTrajectoryConstraintToolVisibility(sessionId);
22070
22455
  } catch {}
22071
22456
  try {
22072
22457
  forgetLogbookCompactionState(sessionId);
@@ -22097,7 +22482,7 @@ function registerLifecycle(ctx) {
22097
22482
  stopAllBridges();
22098
22483
  } catch {}
22099
22484
  };
22100
- registerDisposer(ctx, "self-started resources (gateway/weixin/skiff-debug faces + weixin bridges)", disposeAll);
22485
+ registerDisposer(ctx, "self-started resources (gateway/weixin/trajectory-debug faces + weixin bridges)", disposeAll);
22101
22486
  }
22102
22487
  //#endregion
22103
22488
  //#region src/patches/web-fetch.ts
@@ -22691,7 +23076,7 @@ async function applyOpencodeAutoConfigOnce(ctx, env) {
22691
23076
  *
22692
23077
  * 为什么要重试:`llm-pi-ai` 只在**它自己的** `ctx.inject(['settings'])` 回调里注册命名空间,
22693
23078
  * 与本插件 apply **无先后保证**(两者都依赖 settings 服务)。首次读取拿到 undefined 是常态而非错误
22694
- * (与 skiff 的 CCC root 重试同款问题、同款处置:v1.30.13 实证"只在 apply 时试一次 → 永不重试")。
23079
+ * (与 轨迹约束的 CCC root 重试同款问题、同款处置:v1.30.13 实证"只在 apply 时试一次 → 永不重试")。
22695
23080
  */
22696
23081
  const NAMESPACE_RETRY_DELAYS_MS$1 = [
22697
23082
  1e3,
@@ -22703,7 +23088,7 @@ const NAMESPACE_RETRY_DELAYS_MS$1 = [
22703
23088
  /**
22704
23089
  * 装配 opencode 路由自动配置。
22705
23090
  *
22706
- * 触发面(三层,与 skiff 同款;R↓:单点触发在同装载竞态下必然漏):
23091
+ * 触发面(三层,与 轨迹约束 同款;R↓:单点触发在同装载竞态下必然漏):
22707
23092
  * ① apply 时立即试一次
22708
23093
  * ② 命名空间尚未注册 → 退避重试(1s/3s/8s/20s/40s)
22709
23094
  * ③ `settings/updated` → 再试(用户热改 settings.yaml 加路由时自愈;自身写入触发的复评是幂等空转)
@@ -24298,7 +24683,7 @@ function renderDsl(model) {
24298
24683
  * 🔴 **为什么 PNG 是必需的**:宿主的对话内图片通路**白名单只有光栅格式**
24299
24684
  * (`image/png|jpeg|webp|gif`,见 `dsh-attachment/types.d.ts`)⇒ **SVG 进不去**。
24300
24685
  *
24301
- * 🔴 **开关**:由 `index.ts` 门控(**关时不注册** ⇒ 工具根本不进模型工具清单),同 skiff 口径。
24686
+ * 🔴 **开关**:由 `index.ts` 门控(**关时不注册** ⇒ 工具根本不进模型工具清单),同 轨迹约束 口径。
24302
24687
  *
24303
24688
  * ## 🔴 两条**宿主契约**(2026-09-29 现场取证;第 ① 条是本文件第一版写错的地方)
24304
24689
  *
@@ -24769,7 +25154,7 @@ function apply(ctx, config) {
24769
25154
  * ① 听宿主的**真事件** `settings/document-updated`(ns = 我们那张设置页)——顺带说明为什么不用
24770
25155
  * 我们自己的 `serenity/settings-changed`:那个事件**全仓没有任何 `emit` 点**(只有 `ctx.on` 侧
24771
25156
  * 在用)=**死通道**,照着它接线等于接了个永远不响的铃;
24772
- * ② 兜底再挂 `agent/created` / `session/created`(与 skiff 同款做法,那两条在实测里确实会 fire)
25157
+ * ② 兜底再挂 `agent/created` / `session/created`(与 轨迹约束 同款做法,那两条在实测里确实会 fire)
24773
25158
  * —— 宿主事件面若换名,至少还有这一条能自愈;
24774
25159
  * ③ 启动时同步一次(apply 时按当前配置定初始态)。
24775
25160
  * 🔴 **关的时候要注销**(不是"留着但不响应"):与"关时不注册"同一口径 —— 工具不出现在模型清单里。