@shgroup/dsh-serenity-hooks 1.74.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.
- package/README.md +12 -7
- package/dsh.plugin.json +2 -2
- package/lib/{ccc-roots-B2t_aM8L.js → ccc-roots-BVxn97dY.js} +1 -1
- package/lib/cro/core.d.ts +10 -0
- package/lib/cro/log.d.ts +54 -4
- package/lib/index.js +387 -97
- package/lib/kernel/ccc.d.ts +3 -2
- package/lib/{message-source-DCpoRHP3.js → message-source-JhN-qyfn.js} +2 -2
- package/lib/{profile-BVt84mbi.js → profile-Cz4oBXlI.js} +148 -16
- package/lib/{settings-D8hlqxua.js → settings-CII0Y597.js} +1 -1
- package/lib/tools/config-docs.d.ts +18 -0
- package/lib/tools/container-admin.d.ts +5 -0
- package/lib/tools/msm/service.d.ts +11 -0
- package/lib/tools/trajectory-constraint-admin.d.ts +2 -2
- package/lib/trajectory/core.d.ts +10 -0
- package/lib/trajectory/policy/files.d.ts +75 -0
- package/lib/trajectory/policy/profile.d.ts +7 -2
- package/lib/trajectory/policy/window.d.ts +4 -4
- package/lib/trajectory/session.d.ts +10 -10
- package/package.json +2 -2
package/lib/index.js
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
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-
|
|
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
3
|
import { _ as resolveInside, a as findSerenityRoot$1, c as matchBlacklist, d as readCccName$1, f as readCheapWorkerConfig, g as readUtf8, h as readSafeModeHiddenTools, i as findGitRoot, l as pathInside, m as readExclusiveTools, n as SAFE_MODE_MARKER, o as isSafeModeOn, p as readCognitiveTrajectorySkills, r as classifyPath, s as loadSerenityConfig, t as DEFAULT_SERENITY_CONFIG_PATHS, u as readBlacklist } from "./ccc-CyYJL4F2.js";
|
|
4
|
-
import { A as sessionsRoot, C as namingTitleFor, D as readTrajectoryUid, E as readActiveSessionMd, F as
|
|
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
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-
|
|
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-
|
|
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
10
|
import { a as registerImAccountImpl, i as normalizeAccountChannel, n as getImAccountImpl, r as imAccountChannels } from "./accounts-BHQbakUh.js";
|
|
@@ -994,6 +994,68 @@ function weixinSendEndpoint() {
|
|
|
994
994
|
return endpoint;
|
|
995
995
|
}
|
|
996
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
|
|
997
1059
|
//#region src/tools/msm/service.ts
|
|
998
1060
|
/**
|
|
999
1061
|
* tools/msm/service.ts — msm 纯操作层(acc_msm → msm 执行 + container_admin 管理面,v1.30;零 DSH 依赖)
|
|
@@ -1069,6 +1131,10 @@ const NPX_BIN = process.platform === "win32" ? "npx.cmd" : "npx";
|
|
|
1069
1131
|
* 目录在前、详情各归各——**单一真相源**:本目录只索引"去哪个工具/子命令看详情",不复制详情
|
|
1070
1132
|
* (避免 container_admin/weixin-doctor 等 guide 全文重复 → 多真相源 → 失同步)。
|
|
1071
1133
|
* 后续新增功能只需在此加一行(低熵可演进)。
|
|
1134
|
+
*
|
|
1135
|
+
* 🔴 **2026-10-19 v1.75.0 起导出**:它是 `container_admin config docs catalog` 那一章的正文。
|
|
1136
|
+
* `msm catalog` 动作**降为指针**(只指路,不再返回本常量)—— 否则同一份正文会有两个入口,
|
|
1137
|
+
* 而"两个入口返回同一份内容"正是本目录自己反对的形态。
|
|
1072
1138
|
*/
|
|
1073
1139
|
const ACC_CATALOG = `═══ ACC Usage Catalog ═══
|
|
1074
1140
|
Directory of ACC capabilities — each area lists where to go for details (guides live with their feature; this catalog only points).
|
|
@@ -1552,7 +1618,7 @@ function runMsm(root, args) {
|
|
|
1552
1618
|
return `${header}\n` + lines.join("\n");
|
|
1553
1619
|
}
|
|
1554
1620
|
case "guide": return { guide: MSM_GUIDE };
|
|
1555
|
-
case "catalog": return { catalog:
|
|
1621
|
+
case "catalog": return { catalog: docsPointer("catalog", "ACC 能力目录") };
|
|
1556
1622
|
case "exec": {
|
|
1557
1623
|
const { entry, businessArgs, fmtJson, hasHelp, protocol } = prepareExec(root, args);
|
|
1558
1624
|
const p = protocolResult(protocol);
|
|
@@ -1676,7 +1742,7 @@ function runMsm(root, args) {
|
|
|
1676
1742
|
}
|
|
1677
1743
|
throw new Error(`MSM not registered: "${name}"`);
|
|
1678
1744
|
}
|
|
1679
|
-
case "ccc-config": return
|
|
1745
|
+
case "ccc-config": return docsPointer("config", "CCC 配置参考");
|
|
1680
1746
|
case "check": return checkRegistryQuality(root);
|
|
1681
1747
|
default: throw new Error(`Unknown action: ${args.action}`);
|
|
1682
1748
|
}
|
|
@@ -4079,7 +4145,7 @@ const TRAJECTORY_PRESET = "standard";
|
|
|
4079
4145
|
* @param sessionId 指定会话 id(可选;微信桥等外部面用固定 id 实现"轨迹 ↔ 会话"长期映射)
|
|
4080
4146
|
*/
|
|
4081
4147
|
async function createTrajectoryAgent(ctx, root, profile, defaultModel, sessionId) {
|
|
4082
|
-
if (!ctx.agents) throw new Error("
|
|
4148
|
+
if (!ctx.agents) throw new Error("trajectory constraint: ctx.agents unavailable");
|
|
4083
4149
|
const model = profile.model?.trim() || defaultModel || "";
|
|
4084
4150
|
const id = sessionId ?? `轨迹约束-${profile.code}-${randomUUID()}`;
|
|
4085
4151
|
const setup = async (agentCtx) => {
|
|
@@ -4306,7 +4372,7 @@ function trajectoryConstraintMsmGate(root, sessionId, action, name) {
|
|
|
4306
4372
|
if (sessionId === void 0) return {};
|
|
4307
4373
|
const profile = trajectoryConstraintFor(root, sessionId);
|
|
4308
4374
|
if (profile === null) return {};
|
|
4309
|
-
if (action === "register" || action === "deregister") return { reject: "register/deregister is not allowed in
|
|
4375
|
+
if (action === "register" || action === "deregister") return { reject: "register/deregister is not allowed in trajectory-constraint sessions" };
|
|
4310
4376
|
if (action === "exec") {
|
|
4311
4377
|
if (!name || !profile.msms.includes(name)) return { reject: "MSM not allowed" };
|
|
4312
4378
|
return {};
|
|
@@ -10331,6 +10397,16 @@ function croScriptPath(root, dirName) {
|
|
|
10331
10397
|
* —— 程序还没跑,没有快照可读。⇒ 只能读文件。(这也是"机制属 ACC"的体现:
|
|
10332
10398
|
* ACC 在调用用户程序前先判定自己该不该调用。)
|
|
10333
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
|
+
*
|
|
10334
10410
|
* @param root CCC 根
|
|
10335
10411
|
* @param dirName 轨迹目录名
|
|
10336
10412
|
* @param selfHost 本机机器名(**注入** ⇒ 可测;生产调用方传 `hostname()`)
|
|
@@ -10350,6 +10426,8 @@ function resolveCroHostBinding(root, dirName, selfHost) {
|
|
|
10350
10426
|
detail: `绑定文件路径非法(已忽略):${String(err?.message ?? err)}`
|
|
10351
10427
|
};
|
|
10352
10428
|
}
|
|
10429
|
+
const fromJson = readCroHostFromDeclaration(root, dirName);
|
|
10430
|
+
if (fromJson !== null) return verdictFor(fromJson, self);
|
|
10353
10431
|
let raw;
|
|
10354
10432
|
try {
|
|
10355
10433
|
if (!existsSync(fileAbs)) return {
|
|
@@ -10357,7 +10435,7 @@ function resolveCroHostBinding(root, dirName, selfHost) {
|
|
|
10357
10435
|
bound: null,
|
|
10358
10436
|
self,
|
|
10359
10437
|
code: "not-bound",
|
|
10360
|
-
detail: `未绑定机器(缺 ${CRO_HOST_FILENAME})⇒ 按"必须指定"不运行`
|
|
10438
|
+
detail: `未绑定机器(缺 ${CRO_HOST_FILENAME},且 ${TRAJECTORY_FILENAME} 里也没有 cro.host)⇒ 按"必须指定"不运行`
|
|
10361
10439
|
};
|
|
10362
10440
|
const st = statSync(fileAbs);
|
|
10363
10441
|
if (st.size > CRO_HOST_FILE_MAX) return {
|
|
@@ -10385,6 +10463,37 @@ function resolveCroHostBinding(root, dirName, selfHost) {
|
|
|
10385
10463
|
code: "not-bound",
|
|
10386
10464
|
detail: `绑定文件为空(只有注释/空行)⇒ 按"必须指定"不运行`
|
|
10387
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) {
|
|
10388
10497
|
if (bound === self) return {
|
|
10389
10498
|
allowed: true,
|
|
10390
10499
|
bound,
|
|
@@ -10921,12 +11030,36 @@ function registerCroTurnTracking(ctx) {
|
|
|
10921
11030
|
* | 文件 | 谁写 | 主语 | 回答 |
|
|
10922
11031
|
* |---|---|---|---|
|
|
10923
11032
|
* | `<轨迹目录>/cro-state.json`(CCC 侧程序自建,名字自定) | **CCC 的 CRO 程序** | 程序 | 「**我判了什么**」(含"没叫"的判定、心跳阈值、环境谓词) |
|
|
10924
|
-
* | `<轨迹目录>/cro-wake-log.
|
|
11033
|
+
* | `<轨迹目录>/cro-wake-log.log`(**本模块**) | **ACC** | ACC | 「**我把什么送出去了、送成没有**」 |
|
|
10925
11034
|
*
|
|
10926
11035
|
* **内容不重叠**:本模块**不复制**判定细节(不存 `lastDecision` / 阈值 / 谓词);
|
|
10927
11036
|
* 而程序**不可能**知道投递结果(CRO 投递是 fire-and-forget)。
|
|
10928
11037
|
* ⇒ 两者**互补**:完整审计 = 两者合看(本模块 = 投递面|程序状态 = 判定面)。
|
|
10929
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
|
+
*
|
|
10930
11063
|
* ## 🔴 轮转(所有者明示「程序化的」)
|
|
10931
11064
|
*
|
|
10932
11065
|
* **写入时按窗裁剪**:每次落盘只保留 `at >= now - CRO_LOG_RETENTION_MS` 的条目,
|
|
@@ -10945,21 +11078,107 @@ function registerCroTurnTracking(ctx) {
|
|
|
10945
11078
|
* 最坏(某程序每 5min 都叫)⇒ 288 条/天 ⇒ 两天 **≤ ~600 条、< 100 KB** —— 轮转**自带上限**。
|
|
10946
11079
|
*/
|
|
10947
11080
|
/** 固定文件名(所有者令:「文件名固定即可」)——与程序自建的 `cro-state.json` 并排、主语分明 */
|
|
10948
|
-
const CRO_LOG_FILENAME = "cro-wake-log.
|
|
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"];
|
|
10949
11090
|
/** 保留窗 = 2 日(所有者令「只保留仅两日」);**常量** ⇒ 「这个时间是程序化的」 */
|
|
10950
11091
|
const CRO_LOG_RETENTION_MS = 1728e5;
|
|
10951
|
-
|
|
10952
|
-
|
|
11092
|
+
/** 纯文本档案的表头行(解析时跳过;人打开时先看到它) */
|
|
11093
|
+
const CRO_LOG_HEADER = "# ACC CRO 唤起流水(纯文本 · tab 分隔 · 时间 / 结果 / tick / 方式 / 理由 / 提示词摘要)";
|
|
11094
|
+
/** 日志绝对路径(**写入目标** = 新名) */
|
|
10953
11095
|
function croWakeLogPath(root, dirName) {
|
|
10954
11096
|
return join(sessionsRoot(root), dirName, CRO_LOG_FILENAME);
|
|
10955
11097
|
}
|
|
11098
|
+
/** 旧档绝对路径(**只读**,用于两代并读) */
|
|
11099
|
+
function croWakeLogLegacyPath(root, dirName, filename) {
|
|
11100
|
+
return join(sessionsRoot(root), dirName, filename);
|
|
11101
|
+
}
|
|
10956
11102
|
function emptyLog() {
|
|
10957
11103
|
return {
|
|
10958
|
-
version:
|
|
11104
|
+
version: 1,
|
|
10959
11105
|
entries: []
|
|
10960
11106
|
};
|
|
10961
11107
|
}
|
|
10962
|
-
/**
|
|
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
|
+
/** 旧档条目形状校验(容忍手改) */
|
|
10963
11182
|
function asEntry(value) {
|
|
10964
11183
|
const e = value;
|
|
10965
11184
|
if (!e || typeof e !== "object") return null;
|
|
@@ -10989,27 +11208,20 @@ function summarizeCroPrompt(prompt) {
|
|
|
10989
11208
|
}
|
|
10990
11209
|
/**
|
|
10991
11210
|
* 读取日志(**永不抛**;读坏 ⇒ 当作空档 + 返回 error 文本)。
|
|
11211
|
+
*
|
|
11212
|
+
* 🔴 **两代并读的判据**:新档在 ⇒ **只读新档**;否则按族回落旧档。
|
|
11213
|
+
* 这样首次写入之后旧档自然退场,**不需要删除动作、也不会重复计入**。
|
|
11214
|
+
*
|
|
10992
11215
|
* @param root CCC 根
|
|
10993
11216
|
* @param dirName 轨迹目录名
|
|
10994
11217
|
*/
|
|
10995
11218
|
function loadCroWakeLog(root, dirName) {
|
|
10996
11219
|
const path = croWakeLogPath(root, dirName);
|
|
10997
|
-
if (
|
|
10998
|
-
log: emptyLog(),
|
|
10999
|
-
error: null
|
|
11000
|
-
};
|
|
11001
|
-
try {
|
|
11002
|
-
const parsed = JSON.parse(readFileSync(path, "utf-8"));
|
|
11003
|
-
const raw = Array.isArray(parsed.entries) ? parsed.entries : [];
|
|
11004
|
-
const entries = [];
|
|
11005
|
-
for (const item of raw) {
|
|
11006
|
-
const e = asEntry(item);
|
|
11007
|
-
if (e) entries.push(e);
|
|
11008
|
-
}
|
|
11220
|
+
if (existsSync(path)) try {
|
|
11009
11221
|
return {
|
|
11010
11222
|
log: {
|
|
11011
|
-
version:
|
|
11012
|
-
entries
|
|
11223
|
+
version: 1,
|
|
11224
|
+
entries: parseCroLogText(readFileSync(path, "utf-8"))
|
|
11013
11225
|
},
|
|
11014
11226
|
error: null
|
|
11015
11227
|
};
|
|
@@ -11019,6 +11231,28 @@ function loadCroWakeLog(root, dirName) {
|
|
|
11019
11231
|
error: `CRO 流水解析失败(${String(err?.message ?? err)})`
|
|
11020
11232
|
};
|
|
11021
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
|
+
};
|
|
11022
11256
|
}
|
|
11023
11257
|
/**
|
|
11024
11258
|
* 按窗裁剪(**纯函数** ⇒ 可穷举测试)。
|
|
@@ -11047,6 +11281,10 @@ function pruneCroLogEntries(entries, nowMs, windowMs = CRO_LOG_RETENTION_MS) {
|
|
|
11047
11281
|
dropped
|
|
11048
11282
|
};
|
|
11049
11283
|
}
|
|
11284
|
+
/** 记录集 → 落盘全文(表头 + 逐行 + 末尾换行) */
|
|
11285
|
+
function renderCroLogText(entries) {
|
|
11286
|
+
return `${[CRO_LOG_HEADER, ...entries.map(formatCroLogLine)].join("\n")}\n`;
|
|
11287
|
+
}
|
|
11050
11288
|
/**
|
|
11051
11289
|
* 追加一条唤起记录并按窗裁剪(**本模块主入口;永不抛**)。
|
|
11052
11290
|
*
|
|
@@ -11077,10 +11315,7 @@ function appendCroWakeLog(root, dirName, input, nowMs = Date.now()) {
|
|
|
11077
11315
|
const { kept, pruned, dropped } = pruneCroLogEntries([...loaded.log.entries, entry], nowMs);
|
|
11078
11316
|
mkdirSync(dirname(path), { recursive: true });
|
|
11079
11317
|
const tmp = `${path}.tmp`;
|
|
11080
|
-
writeFileSync(tmp,
|
|
11081
|
-
version: CRO_LOG_VERSION,
|
|
11082
|
-
entries: kept
|
|
11083
|
-
}, null, 2)}\n`, "utf-8");
|
|
11318
|
+
writeFileSync(tmp, renderCroLogText(kept), "utf-8");
|
|
11084
11319
|
renameSync(tmp, path);
|
|
11085
11320
|
return {
|
|
11086
11321
|
ok: true,
|
|
@@ -12823,7 +13058,7 @@ const localstoreTool = defineTool({
|
|
|
12823
13058
|
});
|
|
12824
13059
|
//#endregion
|
|
12825
13060
|
//#region src/tools/trajectory-constraint-admin.ts
|
|
12826
|
-
/** 轨迹约束 定义教程:概念 / `trajectory
|
|
13061
|
+
/** 轨迹约束 定义教程:概念 / `trajectory.json` schema / 双白名单 / 轨迹纪律 / 微信路由 */
|
|
12827
13062
|
const TRAJECTORY_CONSTRAINT_GUIDE = `═══ 轨迹约束 Guide (2026-10-19 redesigned) ═══
|
|
12828
13063
|
|
|
12829
13064
|
**A 轨迹约束 IS a cognitive trajectory — the one whose directory contains \`${TRAJECTORY_CONSTRAINT_FILENAME}\`.**
|
|
@@ -12832,8 +13067,12 @@ becomes a 轨迹约束; delete it and it goes back to being an ordinary trajecto
|
|
|
12832
13067
|
|
|
12833
13068
|
AGENT_SESSIONS/<YYYY-MM-DD--<code>--<desc>>/
|
|
12834
13069
|
├─ SESSION.md ← its persistent body (this IS the workspace — nothing is auto-created)
|
|
12835
|
-
├─
|
|
12836
|
-
|
|
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.
|
|
12837
13076
|
|
|
12838
13077
|
── ${TRAJECTORY_CONSTRAINT_FILENAME} schema ──
|
|
12839
13078
|
{
|
|
@@ -12841,8 +13080,10 @@ becomes a 轨迹约束; delete it and it goes back to being an ordinary trajecto
|
|
|
12841
13080
|
"tools": ["read","grep","msm",...], // non-MSM toolbar whitelist — anything not listed is REMOVED from the schema
|
|
12842
13081
|
"msms": ["msm-a","msm-b"], // MSM whitelist — msm exec may only run these; register/deregister always refused
|
|
12843
13082
|
"cognitiveTrajectory": { "session": true, "keeper": true, "rebuild": true }, // defaults TRUE (see below)
|
|
12844
|
-
"systemPromptFile": ".opencode/skiff/<role>.md", //
|
|
12845
|
-
"systemPrompt": "..."
|
|
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
|
|
12846
13087
|
}
|
|
12847
13088
|
|
|
12848
13089
|
── Two whitelists, both default-deny ──
|
|
@@ -12945,39 +13186,6 @@ function applyTrajectoryConstraint(root) {
|
|
|
12945
13186
|
};
|
|
12946
13187
|
}
|
|
12947
13188
|
//#endregion
|
|
12948
|
-
//#region src/tools/config-docs.ts
|
|
12949
|
-
/**
|
|
12950
|
-
* 渲染**目录**(第一层)。
|
|
12951
|
-
*
|
|
12952
|
-
* 🔴 只调 `summary`,**绝不调 `render`** —— 目录若触发正文,就退化成"整份长文"。
|
|
12953
|
-
* 这条不是性能优化,是**判据**:它保证 `docs` 的输出大小与章数成正比,与正文长度无关。
|
|
12954
|
-
*/
|
|
12955
|
-
function renderDocsIndex(chapters) {
|
|
12956
|
-
const lines = [
|
|
12957
|
-
"═══ CCC 配置说明 · 目录 ═══",
|
|
12958
|
-
"",
|
|
12959
|
-
"说明类内容已超过单次返回的合适篇幅 ⇒ 先读目录,再按章取详情。",
|
|
12960
|
-
"",
|
|
12961
|
-
"用法:container_admin config docs <章节>",
|
|
12962
|
-
""
|
|
12963
|
-
];
|
|
12964
|
-
for (const ch of chapters) lines.push(` ${ch.id.padEnd(14)}${ch.summary}`);
|
|
12965
|
-
lines.push("");
|
|
12966
|
-
lines.push("相关:container_admin msm catalog(工具/能力目录——与本章录互指,不互相复制)");
|
|
12967
|
-
return lines.join("\n");
|
|
12968
|
-
}
|
|
12969
|
-
/**
|
|
12970
|
-
* 解析一个章节参数(第二层)。
|
|
12971
|
-
*
|
|
12972
|
-
* 不存在 ⇒ 返回 `null`,由调用方给出**带候选清单**的拒绝 ——
|
|
12973
|
-
* "不知道这个章节" 与 "这一章是空的" 必须可区分(否则用户以为功能没有)。
|
|
12974
|
-
*/
|
|
12975
|
-
function findDocsChapter(chapters, id) {
|
|
12976
|
-
const want = id.trim().toLowerCase();
|
|
12977
|
-
if (want === "") return null;
|
|
12978
|
-
return chapters.find((c) => c.id.toLowerCase() === want) ?? null;
|
|
12979
|
-
}
|
|
12980
|
-
//#endregion
|
|
12981
13189
|
//#region src/tools/container-admin.ts
|
|
12982
13190
|
/**
|
|
12983
13191
|
* container-admin.ts — container_admin(v1.30:机务舱——容器管理面统一入口)
|
|
@@ -13008,6 +13216,57 @@ function findDocsChapter(chapters, id) {
|
|
|
13008
13216
|
function agentSessionId(exec) {
|
|
13009
13217
|
return exec.agent?.session?.id ?? "";
|
|
13010
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
|
+
}
|
|
13011
13270
|
function renderText$1(value) {
|
|
13012
13271
|
return [{
|
|
13013
13272
|
type: "text",
|
|
@@ -13036,21 +13295,49 @@ const DOMAIN_NAMES = Object.keys({
|
|
|
13036
13295
|
]
|
|
13037
13296
|
});
|
|
13038
13297
|
/**
|
|
13039
|
-
*
|
|
13040
|
-
*
|
|
13041
|
-
* 🔴
|
|
13042
|
-
*
|
|
13043
|
-
*
|
|
13044
|
-
|
|
13045
|
-
|
|
13046
|
-
|
|
13047
|
-
|
|
13048
|
-
|
|
13049
|
-
|
|
13050
|
-
|
|
13051
|
-
|
|
13052
|
-
|
|
13053
|
-
|
|
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
|
+
*/
|
|
13054
13341
|
function createContainerAdminTool(ctx) {
|
|
13055
13342
|
return defineTool({
|
|
13056
13343
|
name: "container_admin",
|
|
@@ -13131,8 +13418,11 @@ function createContainerAdminTool(ctx) {
|
|
|
13131
13418
|
};
|
|
13132
13419
|
}
|
|
13133
13420
|
case "view":
|
|
13134
|
-
case "": return {
|
|
13135
|
-
|
|
13421
|
+
case "": return {
|
|
13422
|
+
config: viewConfigSummary(root),
|
|
13423
|
+
docs: "完整配置说明(每段怎么填、填错会怎样)→ container_admin config docs config"
|
|
13424
|
+
};
|
|
13425
|
+
case "guide": return { guide: docsPointer("trajectory-constraint", "轨迹约束教程") };
|
|
13136
13426
|
case "validate": return validateTrajectoryConstraint(root);
|
|
13137
13427
|
case "apply": return applyTrajectoryConstraint(root);
|
|
13138
13428
|
case "list": return listTrajectoryConstraintSummary(root);
|
|
@@ -14824,10 +15114,10 @@ function registerKeeper(ctx, opts = {}) {
|
|
|
14824
15114
|
const root = cccRootForCwd(cwd);
|
|
14825
15115
|
if (!root) return next();
|
|
14826
15116
|
const sessionId = exec.agent?.session?.id;
|
|
14827
|
-
const
|
|
14828
|
-
const
|
|
15117
|
+
const participatesKeeper = trajectoryConstraintParticipates(root, sessionId, "keeper");
|
|
15118
|
+
const participatesRebuild = trajectoryConstraintParticipates(root, sessionId, "rebuild");
|
|
14829
15119
|
const tracker = trackerFor(exec);
|
|
14830
|
-
const shouldRemind =
|
|
15120
|
+
const shouldRemind = participatesKeeper ? tracker.step(exec.name) : false;
|
|
14831
15121
|
const downstream = await next();
|
|
14832
15122
|
try {
|
|
14833
15123
|
recordToolUsage(root, exec.name, exec.arguments);
|
|
@@ -14840,7 +15130,7 @@ function registerKeeper(ctx, opts = {}) {
|
|
|
14840
15130
|
text: reminderText(code)
|
|
14841
15131
|
});
|
|
14842
15132
|
}
|
|
14843
|
-
if (
|
|
15133
|
+
if (participatesRebuild) {
|
|
14844
15134
|
const session = exec.agent?.session;
|
|
14845
15135
|
if (session) {
|
|
14846
15136
|
const pressure = readContextPressure(ctx, session);
|
|
@@ -15793,7 +16083,7 @@ function registerStatusApi(ctx, opts = {}) {
|
|
|
15793
16083
|
});
|
|
15794
16084
|
}
|
|
15795
16085
|
const { saveWeixinRoutes } = await import("./route-COOFLTpU.js").then((n) => n.c);
|
|
15796
|
-
const { findDirNameByCode } = await import("./profile-
|
|
16086
|
+
const { findDirNameByCode } = await import("./profile-Cz4oBXlI.js").then((n) => n.i);
|
|
15797
16087
|
for (const r of routes) if (findDirNameByCode(root, r.trajectory) === null) {
|
|
15798
16088
|
sendJson$2(res, 400, { error: `unknown trajectory: ${r.trajectory} (no such trajectory code in ${root})` });
|
|
15799
16089
|
return;
|
|
@@ -20776,7 +21066,7 @@ const MEDIA_MAX_BYTES = 20971520;
|
|
|
20776
21066
|
*/
|
|
20777
21067
|
async function resolveCccRootByAlias(ctx, alias) {
|
|
20778
21068
|
try {
|
|
20779
|
-
const { listCccs } = await import("./ccc-roots-
|
|
21069
|
+
const { listCccs } = await import("./ccc-roots-BVxn97dY.js").then((n) => n.a);
|
|
20780
21070
|
const entries = await listCccs(ctx);
|
|
20781
21071
|
const byName = entries.find((e) => e.name === alias);
|
|
20782
21072
|
if (byName) return byName.root;
|
|
@@ -20844,7 +21134,7 @@ async function resolveInboundTarget(ctx, input) {
|
|
|
20844
21134
|
});
|
|
20845
21135
|
return null;
|
|
20846
21136
|
}
|
|
20847
|
-
const { findDirNameByCode } = await import("./profile-
|
|
21137
|
+
const { findDirNameByCode } = await import("./profile-Cz4oBXlI.js").then((n) => n.i);
|
|
20848
21138
|
if (findDirNameByCode(root, target.role) === null) {
|
|
20849
21139
|
const detail = `目标 CCC "${target.ccc}" 内没有编码为 "${target.role}" 的认知轨迹`;
|
|
20850
21140
|
console.error(`[serenity-hooks] ✗ human-channel: ${detail}(account=${input.account} user=${input.user})—— 该消息**未投递**,请改订阅表里的目标编码`);
|
|
@@ -21656,7 +21946,7 @@ function syncHumanChannel(ctx) {
|
|
|
21656
21946
|
(async () => {
|
|
21657
21947
|
try {
|
|
21658
21948
|
stopMachineLoops();
|
|
21659
|
-
const { listCccs } = await import("./ccc-roots-
|
|
21949
|
+
const { listCccs } = await import("./ccc-roots-BVxn97dY.js").then((n) => n.a);
|
|
21660
21950
|
for (const entry of await listCccs(ctx)) syncCccBridge(ctx, entry.root);
|
|
21661
21951
|
} catch {}
|
|
21662
21952
|
})();
|
|
@@ -21920,7 +22210,7 @@ function matchCcc(input, candidates) {
|
|
|
21920
22210
|
}
|
|
21921
22211
|
/** 组装候选列表(`ccc-roots.listCccs` 投影 + `.serenity` 名;动态 import 保持本模块静态依赖轻量) */
|
|
21922
22212
|
async function collectCandidates(ctx) {
|
|
21923
|
-
const { listCccs } = await import("./ccc-roots-
|
|
22213
|
+
const { listCccs } = await import("./ccc-roots-BVxn97dY.js").then((n) => n.a);
|
|
21924
22214
|
const entries = await listCccs(ctx, { defaultRoot: process.cwd() });
|
|
21925
22215
|
const seen = /* @__PURE__ */ new Set();
|
|
21926
22216
|
const out = [];
|