@shgroup/dsh-serenity-hooks 1.75.0 → 1.77.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/lib/index.js CHANGED
@@ -1,10 +1,11 @@
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-BVxn97dY.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-rwgzt_0F.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, 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";
4
+ import { A as sessionsRoot, B as localFileStamp, C as namingTitleFor, D as readTrajectoryUid, E as readActiveSessionMd, F as useSession, H as localIdStamp, I as TRAJECTORY_FILENAME, L as TRAJECTORY_LEGACY_FILENAMES, M as showSession, N as summarize, O as resolveSessionByTitle, P as trajectoryDescOf, R as readTrajectoryFile, S as listSessions, T as parseTrajectoryCode, V as localHuman, _ 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 isoLocal } from "./profile-DplLGGqo.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-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";
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-f9WZXcgi.js";
7
+ import { a as listBoundSessionIds, i as carrierState, n as PLUGIN_SOURCE, o as readLastBound, r as appendBound, s as resolveSessionCognitiveTrajectoryLabel, t as ACC_MESSAGE_KIND } from "./message-source-CYa8LbbM.js";
8
+ import { n as hasSessionLogById, r as sessionsRootDir } from "./cleanup-C-92qnDs.js";
8
9
  import { REGISTERED_TOOLS } from "./invariant.js";
9
10
  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
11
  import { a as registerImAccountImpl, i as normalizeAccountChannel, n as getImAccountImpl, r as imAccountChannels } from "./accounts-BHQbakUh.js";
@@ -9595,215 +9596,6 @@ function createCheapWorkerTool(ctx) {
9595
9596
  });
9596
9597
  }
9597
9598
  //#endregion
9598
- //#region src/trajectory/cleanup.ts
9599
- /**
9600
- * trajectory/cleanup.ts — DSH 平台旧会话自动清理(v1.29,S142 需求③)
9601
- *
9602
- * 背景(实证):DSH 会话物理存储 = `$DSH_HOME/sessions/`(缺省 ~/.dsh/sessions;
9603
- * base bundle cordis.patch.yml root: dshHomePath('sessions'))。磁盘布局:
9604
- * <root>/--<project-slug>--/<encoded-session-id>/session[.vN].jsonl(.zstd)
9605
- * **世代(v1.31.13 补)**:宿主自 DSH 0.1.5 起按世代写文件,且迁移**保留源文件**
9606
- * ⇒ 一个会话目录常见 v0 与 v3 两个文件并存。本模块用 `sessionLogArtifacts()` 统一枚举世代。
9607
- * DSH **无删除会话 API**(PersistenceCoordinator 仅 create/append/load/inspect/borrow,
9608
- * 无 delete/purge;workspace archiveSession 只加归档集不删文件)——物理删除只能直接
9609
- * 删文件。删除后一致性(利好):sessionPersistence.list() = readdir 扫描磁盘现存 →
9610
- * 物理删后 list 不再返回;WorkspaceRegistry 下次启动重建 header index 自动消失。
9611
- *
9612
- * 方案(S142 用户拍板):lastActive 基准 + 手动触发 + 不过滤不归档直接物理删;
9613
- * 安全底线 = live 会话保护跳过(删正在运行的会话 = 灾难)。
9614
- *
9615
- * 本模块纯逻辑(零 ctx 依赖):传入 sessionsRoot + liveIds,可单测。
9616
- */
9617
- var cleanup_exports = /* @__PURE__ */ __exportAll({
9618
- collectEligibleSessions: () => collectEligibleSessions,
9619
- cutoffDaysAgo: () => cutoffDaysAgo,
9620
- findSessionLog: () => findSessionLog,
9621
- hasSessionLogById: () => hasSessionLogById,
9622
- performCleanup: () => performCleanup,
9623
- sessionLogArtifacts: () => sessionLogArtifacts,
9624
- sessionLogBytesById: () => sessionLogBytesById,
9625
- sessionsRootDir: () => sessionsRootDir
9626
- });
9627
- /** DSH 会话 root:env DSH_HOME → ~/.dsh(config-ops globalConfigPath 同款推导) */
9628
- function sessionsRootDir() {
9629
- const dshHome = process.env.DSH_HOME ?? join(process.env.HOME ?? "", ".dsh");
9630
- return join(dshHome, "sessions");
9631
- }
9632
- /** 一天的毫秒数 */
9633
- const DAY_MS = 864e5;
9634
- /**
9635
- * 扫描 sessions root,列出可清理候选:
9636
- * - 递归 `<root>/<project>/<session-id>/session.jsonl(.zstd)`(任意深度下找 *session* 文件)
9637
- * - 过滤 live(liveIds 集合)——安全底线
9638
- * - 过滤 lastActive >= cutoffMs(最后活动未达阈值 → 保留)
9639
- * root 不存在/为空 → 返回 [](非错误)。
9640
- */
9641
- function collectEligibleSessions(root, cutoffMs, liveIds = /* @__PURE__ */ new Set()) {
9642
- if (!existsSync(root)) return [];
9643
- const out = [];
9644
- for (const project of readdirSync(root, { withFileTypes: true })) {
9645
- if (!project.isDirectory()) continue;
9646
- const projectPath = join(root, project.name);
9647
- for (const entry of readdirSync(projectPath, { withFileTypes: true })) {
9648
- if (!entry.isDirectory()) continue;
9649
- const dirPath = join(projectPath, entry.name);
9650
- const artifacts = sessionLogArtifacts(dirPath);
9651
- if (artifacts.length === 0) continue;
9652
- const log = findSessionLog(dirPath);
9653
- if (log === null) continue;
9654
- if (liveIds.has(entry.name)) continue;
9655
- let mtime = Number.NEGATIVE_INFINITY;
9656
- for (const artifact of artifacts) try {
9657
- mtime = Math.max(mtime, statSync(artifact.path).mtimeMs);
9658
- } catch {}
9659
- if (!Number.isFinite(mtime)) continue;
9660
- if (mtime >= cutoffMs) continue;
9661
- out.push({
9662
- id: entry.name,
9663
- project: project.name,
9664
- logPath: log,
9665
- lastActiveMs: mtime
9666
- });
9667
- }
9668
- }
9669
- return out;
9670
- }
9671
- /**
9672
- * 会话日志文件名识别:`session.jsonl` / `session.jsonl.zstd`(= **v0 世代**)/
9673
- * `session.v<N>.jsonl[.zstd]`(= 第 N 世代,DSH 0.1.5 起按世代写文件)。
9674
- * 出处:`@deepseek-ai/dsh-session-persistence-jsonl` 的 `parseSessionFormatLogFilename` 同款形态。
9675
- */
9676
- const SESSION_LOG_RE = /^session(?:\.v(\d+))?\.jsonl(\.zstd)?$/;
9677
- /**
9678
- * 列出会话目录下的**全部日志世代**,按代递增排序(同代内 `.jsonl` 在前、`.zstd` 在后 = 后者更代表"当前书写格式")。
9679
- *
9680
- * 为什么需要(R↓,v1.31.13 修复):宿主自 0.1.5 起改成**按世代写文件**,且迁移**保留源文件**
9681
- * ⇒ 同一会话常见 `session.jsonl.zstd`(v0)与 `session.v3.jsonl.zstd`(v3)**并存**。
9682
- * 旧实现只认前两个名字 → 新格式会话对本模块**完全不可见**(静默 no-op:清理永远不生效、磁盘只涨)。
9683
- */
9684
- function sessionLogArtifacts(sessionDirPath) {
9685
- let names;
9686
- try {
9687
- names = readdirSync(sessionDirPath);
9688
- } catch {
9689
- return [];
9690
- }
9691
- const out = [];
9692
- for (const name of names) {
9693
- const m = SESSION_LOG_RE.exec(name);
9694
- if (m === null) continue;
9695
- out.push({
9696
- name,
9697
- generation: m[1] === void 0 ? 0 : Number(m[1]),
9698
- path: join(sessionDirPath, name)
9699
- });
9700
- }
9701
- return out.sort((a, b) => a.generation - b.generation || (a.name.endsWith(".zstd") ? 1 : 0) - (b.name.endsWith(".zstd") ? 1 : 0));
9702
- }
9703
- /**
9704
- * 在会话目录下找 session 日志文件;无 → null。
9705
- * 多世代并存时返回**最高世代**(= 宿主 `findLog` 选中的当前世代;旧行为对单文件目录完全一致)。
9706
- */
9707
- function findSessionLog(sessionDirPath) {
9708
- const artifacts = sessionLogArtifacts(sessionDirPath);
9709
- const latest = artifacts[artifacts.length - 1];
9710
- return latest === void 0 ? null : latest.path;
9711
- }
9712
- /**
9713
- * 执行清理(用户拍板:直接物理删,不过滤不归档;dryRun 预览不删)。
9714
- * 删除 = rmSync 整个会话目录(recursive + force)。失败逐个记 errors 不中断。
9715
- */
9716
- function performCleanup(root, cutoffMs, liveIds = /* @__PURE__ */ new Set(), opts = {}) {
9717
- const candidates = collectEligibleSessions(root, cutoffMs, liveIds);
9718
- if (opts.dryRun) return {
9719
- candidates,
9720
- result: null
9721
- };
9722
- const result = {
9723
- deleted: [],
9724
- errors: []
9725
- };
9726
- for (const c of candidates) {
9727
- const dir = dirOf(c.logPath);
9728
- try {
9729
- rmSync(dir, {
9730
- recursive: true,
9731
- force: true
9732
- });
9733
- result.deleted.push(c.id);
9734
- } catch (e) {
9735
- result.errors.push({
9736
- id: c.id,
9737
- reason: e.message
9738
- });
9739
- }
9740
- }
9741
- return {
9742
- candidates,
9743
- result
9744
- };
9745
- }
9746
- /** 从日志路径取会话目录(父目录) */
9747
- function dirOf(logPath) {
9748
- return logPath.slice(0, logPath.lastIndexOf("/")) || logPath;
9749
- }
9750
- /**
9751
- * 某一 dsh 会话(目录名 / 会话 id)的**当前世代日志体积**(字节)。
9752
- *
9753
- * 为什么要它(S142 §0y「旧会话累积 ⇒ dsh 内存崩溃」,所有者令):
9754
- * · 宿主恢复/继续一条会话必须**重放全部事件**(`scanLog` → `materializeAppendBatch`)
9755
- * ⇒ **代价 ∝ 日志体积**;
9756
- * · 实测(`_tmp/measure-session-load.mjs`,照宿主 `scanZstdFrames` 复刻):
9757
- * 一条 **72.2 MB**(zstd)的日志 = 35 559 个 frame / 252.6 MB 文本 / 62 680 条事件,
9758
- * **光是读一遍峰值 RSS +1 175 MB**(探针什么都不保留);
9759
- * · 而宿主的解析缓存 `COLD_LOG_MEMO_MAX_ENTRIES = 2` 是**按条数限、不按字节限**的
9760
- * (`dsh-session-persistence-jsonl/lib/index.js:2175`)⇒ 两条巨日志 = 上限内。
9761
- * 🔴 **在本函数之前,ACC 侧没有任何地方知道"绑定的这条会话日志多大"——这个信号根本不存在。**
9762
- * 这是"靠 agent 自觉 rebuild 不够"的机制层原因(keeper 的 K 量的是**模型上下文**,压缩后回落;
9763
- * 而**日志文件只增不减** ⇒ rebuild 提醒永远不会因为"日志 72 MB"触发)。
9764
- *
9765
- * 只认会话目录(含任意世代日志);返回**最高世代**的日志(= 宿主 `findLog` 选中的当前世代)。
9766
- * 未命中 / 不可读 → null(非错误,调用方按"未知"处理)。
9767
- * @param root - DSH 会话 root({@link sessionsRootDir})
9768
- * @param sessionId - 会话目录名(`.bindings.json` 的键,含 `session-` 前缀)或裸 id
9769
- * @returns 字节数 + 日志绝对路径;未命中 → null
9770
- */
9771
- function sessionLogBytesById(root, sessionId) {
9772
- if (sessionId === "" || !existsSync(root)) return null;
9773
- const dirName = sessionId.startsWith("session-") ? sessionId : `session-${sessionId}`;
9774
- for (const project of readdirSync(root, { withFileTypes: true })) {
9775
- if (!project.isDirectory()) continue;
9776
- const log = findSessionLog(join(root, project.name, dirName));
9777
- if (log === null) continue;
9778
- try {
9779
- return {
9780
- bytes: statSync(log).size,
9781
- path: log
9782
- };
9783
- } catch {
9784
- return null;
9785
- }
9786
- }
9787
- return null;
9788
- }
9789
- /**
9790
- * **(c) 绑定一致性兜底**的判据:某会话的日志**是否仍在磁盘上**。
9791
- *
9792
- * 🔴 **fail-closed**:sessions root 本身不存在时**一律返回 true(当作"在")**——
9793
- * 否则换 `DSH_HOME` / 换机器会让"全部绑定看起来都悬空",进而误清整张绑定表。
9794
- * 只有"root 在、而这个会话目录确实不在"才算悬空。
9795
- * @param sessionRoot - DSH 会话 root({@link sessionsRootDir})
9796
- * @param sessionId - 会话目录名(`.bindings.json` 的键)或裸 id
9797
- */
9798
- function hasSessionLogById(sessionRoot, sessionId) {
9799
- if (!existsSync(sessionRoot)) return true;
9800
- return sessionLogBytesById(sessionRoot, sessionId) !== null;
9801
- }
9802
- /** 便捷:默认 N 天前为 cutoff(供 API/调用方) */
9803
- function cutoffDaysAgo(days, nowMs = Date.now()) {
9804
- return nowMs - days * DAY_MS;
9805
- }
9806
- //#endregion
9807
9599
  //#region src/wake/registry.ts
9808
9600
  /**
9809
9601
  * wake/registry.ts — 认知轨迹唤醒注册表(D58,S142 2026-09-13)
@@ -10160,6 +9952,110 @@ function listWakes(root, opts = {}) {
10160
9952
  };
10161
9953
  }
10162
9954
  //#endregion
9955
+ //#region src/trajectory/user-carrier.ts
9956
+ /**
9957
+ * trajectory/user-carrier.ts — **造一条与"用户人工创建"基本一致的 dsh 会话载体**(判决 D171,owner 2026-10-20 裁 B)
9958
+ *
9959
+ * ## 这个文件解决什么(白话)
9960
+ *
9961
+ * `send-now` 在目标没有活跃会话时会**就地建一条**。它一直能建、也能挂工作区,
9962
+ * 但建出来的东西与用户在 UI 里新建的那条**在宿主眼里不是同一种东西**:
9963
+ *
9964
+ * | 宿主投影 | 用户建 | 我们自建(修前实测) |
9965
+ * |---|---|---|
9966
+ * | `title` | 自动生成 | **永远 `null`** |
9967
+ * | `titleInput.count` | ≥1 | **0** |
9968
+ * | 假首轮(对齐声明 + EAP) | 注入 | **被自家递归守卫挡掉** |
9969
+ *
9970
+ * ## 🔴 根因(读宿主源码,不是推断)
9971
+ *
9972
+ * `dsh-session-title` 的 `sessionTitleUserMessageOf()` **第一行**就是:
9973
+ *
9974
+ * ```js
9975
+ * if (event.type !== "user/message" || event.data.source.kind !== "user") return void 0
9976
+ * ```
9977
+ *
9978
+ * 而 ACC 投递的消息一律是 `{ kind: 'plugin:dsh-serenity-hooks' }`(`kernel/message-source.ts`)
9979
+ * ⇒ **宿主从头到尾不把它当"有真人说过话"的会话** ⇒ 自动命名不触发、`titleInput` 恒 0。
9980
+ *
9981
+ * ## owner 的裁决 = B:**模拟一个用户开场白**
9982
+ *
9983
+ * `MessageSourceMap` 里 `user: { kind: 'user' }`,而 `UserMessage.source` 是**任意**
9984
+ * `MessageSource` ⇒ `createUserMessage({ content, source: { kind: 'user' } })` **类型合法、
9985
+ * 零改 DSH**(D4 仍成立)。
9986
+ *
9987
+ * ## 🔴 这是一次**明知故犯的伪造**,代价写在这里,不许悄悄付
9988
+ *
9989
+ * 1. **伪造用户身份**:与 D160「不再造历史」方向相反。范围**仅限**"载体开场白"这一条消息,
9990
+ * 且正文**逐字自证**是模拟的({@link CARRIER_OPENING_MARK})⇒ 将来翻日志的人一眼能看出。
9991
+ * 2. **污染所有者语料**:`session-corpus` 的"所有者发言"判据**必须**排除这条
9992
+ * (判据 = 正文含 {@link CARRIER_OPENING_MARK})⇒ 否则统计"owner 说过什么"会算进一句
9993
+ * 机器写的开场白。**这一条是同批改动,不是待办。**
9994
+ * 3. **与假首轮的先后**:开场白必须让假首轮先注入(同一次 `agent/inbox/inserted` 里
9995
+ * prepend 天然排在真实消息之前),否则 `hasUserMessageHistory` 会把假首轮挡掉。
9996
+ * ⇒ 单测钉死顺序,不靠"应该会"。
9997
+ *
9998
+ * ## 归属(D23)
9999
+ * 机制在 ACC(怎么建、建什么形状);**内容归 CCC**(开场白正文由调用方给)。
10000
+ *
10001
+ * ## 与 `trajectory/carrier.ts` 的关系
10002
+ * 🔴 **两个文件、两件事**,名字刻意区分:`carrier.ts` 回答"**这条载体跑完没有**"(等待空闲/销毁竞速);
10003
+ * 本文件回答"**怎么造一条载体**"。合在一起会让两条互不相关的依赖链卷成一团。
10004
+ */
10005
+ /**
10006
+ * 模拟开场白的**自证标记**(逐字)。
10007
+ *
10008
+ * 🔴 三个用途,缺一不可:
10009
+ * ① 人类翻日志时能一眼看出"这不是真主说的";
10010
+ * ② `session-corpus` 用它**排除**语料(不污染"所有者发言");
10011
+ * ③ 单测反向钉住:真实用户发言里**不会**出现这串 ⇒ 断言"语料里 0 条含它"才有意义。
10012
+ */
10013
+ const CARRIER_OPENING_MARK = "[ACC 代用户开场]";
10014
+ /**
10015
+ * 构造模拟开场白的正文。
10016
+ *
10017
+ * ⚠️ **不含版本号/日期/模型名**(D159:任务书里不得写死版本号 ⇒ 写与版本无关的判据)。
10018
+ * 正文只描述"这是谁、要接着做什么",不描述"哪一版干的"。
10019
+ *
10020
+ * @param code 轨迹编码(`S###` 或自定义)
10021
+ * @param desc 轨迹目录名里那段可读描述(**人起的**,不另造第二真相源)
10022
+ * @returns 一条自证是模拟的开场白正文
10023
+ */
10024
+ function buildCarrierOpening(code, desc) {
10025
+ const label = desc.trim() === "" ? code : `${code}(${desc})`;
10026
+ return [
10027
+ `${CARRIER_OPENING_MARK} 激活认知轨迹 ${label},接着它 SESSION.md 里的工作往下做。`,
10028
+ "",
10029
+ "(这条开场白由 ACC 代为发出。如此做的目的只有一个:让程序性创建的会话与用户在界面里",
10030
+ "人工创建的会话,在宿主侧具有相同的投影 —— 有标题、有首轮对齐声明。它不是任何人的发言。)"
10031
+ ].join("\n");
10032
+ }
10033
+ /**
10034
+ * 投一条"用户形态"的消息进目标 agent(**B 的核心动作**)。
10035
+ *
10036
+ * 🔴 为什么单独成函数而不是就地写:这是**全仓唯一**允许 `kind:'user'` 的地方。
10037
+ * 集中在一处,将来若要收紧(或 owner 改主意回 A),只有一个点要改。
10038
+ *
10039
+ * @param agent 目标 agent(须有 `followup`)
10040
+ * @param text 开场白正文(**必须**由 {@link buildCarrierOpening} 产出,以带上自证标记)
10041
+ * @returns 投递是否成功(失败返回 false,由调用方决定降级 —— 不抛)
10042
+ */
10043
+ function deliverCarrierOpening(agent, text) {
10044
+ if (typeof agent.followup !== "function") return false;
10045
+ try {
10046
+ agent.followup(createUserMessage({
10047
+ content: [{
10048
+ type: "text",
10049
+ text
10050
+ }],
10051
+ source: { kind: "user" }
10052
+ }));
10053
+ return true;
10054
+ } catch {
10055
+ return false;
10056
+ }
10057
+ }
10058
+ //#endregion
10163
10059
  //#region src/cro/core.ts
10164
10060
  /**
10165
10061
  * cro/core.ts — CRO(Continuous Re-Occurrence · 持续再发生)机制层(S142 §7.8,2026-09-19)
@@ -11808,6 +11704,21 @@ async function sendToCognitiveTrajectory(ctx, root, target, message, sender) {
11808
11704
  let createdNote = "";
11809
11705
  if ("error" in acquired) {
11810
11706
  if (!acquired.notReady && acquired.noBinding === true) {
11707
+ const carrier = carrierState(root, t.dirName, (id) => {
11708
+ if (hostAgents(ctx)?.get?.(id)) return true;
11709
+ try {
11710
+ return hasSessionLogById(sessionsRootDir(), id);
11711
+ } catch {
11712
+ return true;
11713
+ }
11714
+ });
11715
+ if (carrier.state !== "never") {
11716
+ const why = carrier.state === "occupied" ? `该轨迹曾经由会话 ${carrier.sessionId} 承载,而它此刻不在了` : `该轨迹当前由会话 ${carrier.sessionId} 承载`;
11717
+ return {
11718
+ ok: false,
11719
+ detail: `${t.dirName}:${why} ⇒ **拒绝自动新建载体**(一条轨迹至多一个载体)。\n · 要让它重新跑起来:把那个会话恢复,或用 cognitive_trajectory rebuild 换代(显式 takeover)。\n · 不明所以就先 cognitive_trajectory show ${t.dirName} 看它的交代文件。`
11720
+ };
11721
+ }
11811
11722
  acquired = await createAndBindTarget(ctx, root, t.dirName);
11812
11723
  if ("error" in acquired) return {
11813
11724
  ok: false,
@@ -11939,10 +11850,12 @@ async function createAndBindTarget(ctx, root, dirName) {
11939
11850
  } }, "activate", {
11940
11851
  dirName,
11941
11852
  mdPath: join(root, "AGENT_SESSIONS", dirName, "SESSION.md"),
11942
- note: "auto-created by send-now"
11853
+ note: "auto-created by send-now",
11854
+ createdAsUser: true
11943
11855
  });
11856
+ const openingNote = deliverCarrierOpening(agent, buildCarrierOpening(parseTrajectoryCode(dirName) ?? dirName, trajectoryDescOf(dirName))) ? ", opening-as-user" : ", ⚠️ 开场白未投递";
11944
11857
  const wsNote = workspaceId !== void 0 ? "" : await attachToWorkspace(ctx, root, sessionId);
11945
- const how = `auto-create(${sessionId}${bound ? ", bound" : ", ⚠️ 绑定写入失败"}${workspaceId !== void 0 ? ", via-workspaceId(=用户同一形态)" : ", via-cwd+attach"}${wsNote})`;
11858
+ const how = `auto-create(${sessionId}${bound ? ", bound" : ", ⚠️ 绑定写入失败"}${workspaceId !== void 0 ? ", via-workspaceId(=用户同一形态)" : ", via-cwd+attach"}${openingNote}${wsNote})`;
11946
11859
  return {
11947
11860
  agent,
11948
11861
  how
@@ -12711,6 +12624,12 @@ function createCognitiveTrajectoryTool(ctx) {
12711
12624
  "rebuild requires --summary (next-phase summary ≤20 chars) + optional --note (task focus for the rebuilt self).",
12712
12625
  "send-later requires target (a code or AGENT_SESSIONS dir name) + at (RFC3339 or +30m/+2h) + message.",
12713
12626
  "send-now requires target + message.",
12627
+ "AUTHORIZATION (read this before creating anything): creating a cognitive trajectory or a dsh carrier costs real work,",
12628
+ "and both are meant to serve a human intent — not to be spawned speculatively. Call create only when the user asked for",
12629
+ "a new line of work (or confirmed it), never to \"tidy up\" or to pre-provision something nobody requested.",
12630
+ "send-now creating a missing carrier on demand is likewise acceptable ONLY for a delivery the user actually wants made;",
12631
+ "it is not a way to start work nobody asked for. When in doubt, ask the user instead of creating.",
12632
+ "⚠️ This is a behavioral rule, not a mechanical gate: nothing stops a careless call. Treat it as etiquette with a cost.",
12714
12633
  "uid: READS a legacy TRAJECTORY-UID only — setting one is retired (codes replaced it). Use create --id instead.",
12715
12634
  "The summary is appended to the dsh session title (S###-YYYY-MM-DD-<summary>); the code and date stay server-derived unless you pass --id."
12716
12635
  ].join(" "),
@@ -12747,7 +12666,7 @@ function createCognitiveTrajectoryTool(ctx) {
12747
12666
  },
12748
12667
  desc: {
12749
12668
  type: "string",
12750
- description: "create: short description (any language, ≤5 words; mutually exclusive with issue)"
12669
+ description: "create: short description (any language, ≤5 words; mutually exclusive with issue). 🔴 AUTHORIZATION: create is user-intent-driven — do not create a trajectory the user did not ask for (see the tool description)."
12751
12670
  },
12752
12671
  id: {
12753
12672
  type: "string",
@@ -12783,7 +12702,7 @@ function createCognitiveTrajectoryTool(ctx) {
12783
12702
  },
12784
12703
  message: {
12785
12704
  type: "string",
12786
- description: "send-later/send-now: the single message delivered to the target cognitive trajectory"
12705
+ description: "send-later/send-now: the single message delivered to the target cognitive trajectory. send-now may create and bind a carrier when the target has no live session — acceptable only for a delivery the user actually wants made (user-intent-driven), not for speculatively starting work."
12787
12706
  }
12788
12707
  },
12789
12708
  output: {
@@ -12846,22 +12765,16 @@ function createCognitiveTrajectoryTool(ctx) {
12846
12765
  if (!targetEntry) throw new Error(`Session not found: "${args.name}". Use "list" to see available sessions.`);
12847
12766
  const targetDirName = targetEntry.dirName;
12848
12767
  const currentDir = currentBoundDirName(exec, scope);
12849
- const switching = currentDir !== null && targetDirName !== currentDir;
12850
- const force = args.force === true;
12851
- if (switching && !force) throw new Error(`Session is bound to ${currentDir}. Switching to ${targetDirName} would orphan the current cognitive trajectory. Re-run with --force to switch (or close the current session first).`);
12768
+ if (currentDir !== null && targetDirName !== currentDir) throw new Error(`这个会话已经承载认知轨迹「${currentDir}」,不能改宗到「${targetDirName}」。\n一个会话终身只属于一条认知轨迹(2026-10-20 裁决,--force 已退役)。\n要继续用「${currentDir}」⇒ 不用任何操作;要给「${targetDirName}」开一个载体 ⇒ 新建一条会话再 use。`);
12852
12769
  const active = useSession(root, args.name, scope);
12853
12770
  const info = getActiveSessionInfo(scope);
12854
12771
  if (info) renameDshSessionForActive(ctx, exec, info, args.summary);
12855
12772
  const dsh = agentDshSession(exec);
12856
- if (dsh && info) {
12857
- const prevBound = readLastBound(dsh);
12858
- appendBound(dsh, switching && prevBound ? "switch" : "activate", {
12859
- dirName: info.dirName,
12860
- mdPath: info.mdPath,
12861
- sessionId: info.sessionId,
12862
- ...switching ? { note: "forced switch" } : {}
12863
- });
12864
- }
12773
+ if (dsh && info) appendBound(dsh, "activate", {
12774
+ dirName: info.dirName,
12775
+ mdPath: info.mdPath,
12776
+ sessionId: info.sessionId
12777
+ });
12865
12778
  const advisory = advisoryHint(targetEntry.dirName, join(targetEntry.path, "SESSION.md"));
12866
12779
  const out = {
12867
12780
  dir: active.dir,
@@ -12869,10 +12782,6 @@ function createCognitiveTrajectoryTool(ctx) {
12869
12782
  context: active.context
12870
12783
  };
12871
12784
  if (advisory) out.advisory = advisory;
12872
- if (dsh) {
12873
- const pruned = pruneMissingBindings(root, (id) => !hasSessionLogById(sessionsRootDir(), id));
12874
- if (pruned.length > 0) out.prunedBindings = pruned;
12875
- }
12876
12785
  return out;
12877
12786
  }
12878
12787
  case "rebuild": {
@@ -16083,7 +15992,7 @@ function registerStatusApi(ctx, opts = {}) {
16083
15992
  });
16084
15993
  }
16085
15994
  const { saveWeixinRoutes } = await import("./route-COOFLTpU.js").then((n) => n.c);
16086
- const { findDirNameByCode } = await import("./profile-Cz4oBXlI.js").then((n) => n.i);
15995
+ const { findDirNameByCode } = await import("./profile-DplLGGqo.js").then((n) => n.i);
16087
15996
  for (const r of routes) if (findDirNameByCode(root, r.trajectory) === null) {
16088
15997
  sendJson$2(res, 400, { error: `unknown trajectory: ${r.trajectory} (no such trajectory code in ${root})` });
16089
15998
  return;
@@ -16231,7 +16140,7 @@ function registerStatusApi(ctx, opts = {}) {
16231
16140
  const id = s.id ?? s.header?.id;
16232
16141
  if (id) liveIds.add(id);
16233
16142
  }
16234
- const { sessionsRootDir, collectEligibleSessions, performCleanup, cutoffDaysAgo } = await Promise.resolve().then(() => cleanup_exports);
16143
+ const { sessionsRootDir, collectEligibleSessions, performCleanup, cutoffDaysAgo } = await import("./cleanup-C-92qnDs.js").then((n) => n.t);
16235
16144
  const root = sessionsRootDir();
16236
16145
  if (req.method === "GET") {
16237
16146
  const { candidates } = performCleanup(root, cutoffDaysAgo(days), liveIds, { dryRun: true });
@@ -18290,7 +18199,6 @@ async function queueRebuild(ctx, opts) {
18290
18199
  const boundRec = {
18291
18200
  dirName: basename(dirname(mdPath)),
18292
18201
  mdPath,
18293
- sessionId: sessionName.startsWith("S") ? sessionName : void 0,
18294
18202
  note: "rebuild queued"
18295
18203
  };
18296
18204
  appendBound(session, "rebuild", boundRec);
@@ -21066,7 +20974,7 @@ const MEDIA_MAX_BYTES = 20971520;
21066
20974
  */
21067
20975
  async function resolveCccRootByAlias(ctx, alias) {
21068
20976
  try {
21069
- const { listCccs } = await import("./ccc-roots-BVxn97dY.js").then((n) => n.a);
20977
+ const { listCccs } = await import("./ccc-roots-rwgzt_0F.js").then((n) => n.a);
21070
20978
  const entries = await listCccs(ctx);
21071
20979
  const byName = entries.find((e) => e.name === alias);
21072
20980
  if (byName) return byName.root;
@@ -21134,7 +21042,7 @@ async function resolveInboundTarget(ctx, input) {
21134
21042
  });
21135
21043
  return null;
21136
21044
  }
21137
- const { findDirNameByCode } = await import("./profile-Cz4oBXlI.js").then((n) => n.i);
21045
+ const { findDirNameByCode } = await import("./profile-DplLGGqo.js").then((n) => n.i);
21138
21046
  if (findDirNameByCode(root, target.role) === null) {
21139
21047
  const detail = `目标 CCC "${target.ccc}" 内没有编码为 "${target.role}" 的认知轨迹`;
21140
21048
  console.error(`[serenity-hooks] ✗ human-channel: ${detail}(account=${input.account} user=${input.user})—— 该消息**未投递**,请改订阅表里的目标编码`);
@@ -21946,7 +21854,7 @@ function syncHumanChannel(ctx) {
21946
21854
  (async () => {
21947
21855
  try {
21948
21856
  stopMachineLoops();
21949
- const { listCccs } = await import("./ccc-roots-BVxn97dY.js").then((n) => n.a);
21857
+ const { listCccs } = await import("./ccc-roots-rwgzt_0F.js").then((n) => n.a);
21950
21858
  for (const entry of await listCccs(ctx)) syncCccBridge(ctx, entry.root);
21951
21859
  } catch {}
21952
21860
  })();
@@ -22210,7 +22118,7 @@ function matchCcc(input, candidates) {
22210
22118
  }
22211
22119
  /** 组装候选列表(`ccc-roots.listCccs` 投影 + `.serenity` 名;动态 import 保持本模块静态依赖轻量) */
22212
22120
  async function collectCandidates(ctx) {
22213
- const { listCccs } = await import("./ccc-roots-BVxn97dY.js").then((n) => n.a);
22121
+ const { listCccs } = await import("./ccc-roots-rwgzt_0F.js").then((n) => n.a);
22214
22122
  const entries = await listCccs(ctx, { defaultRoot: process.cwd() });
22215
22123
  const seen = /* @__PURE__ */ new Set();
22216
22124
  const out = [];
@@ -25077,11 +24985,9 @@ function apply(ctx, config) {
25077
24985
  (async () => {
25078
24986
  try {
25079
24987
  const domain = await openBindingDomain(ctx);
25080
- setBindingStore(bindingStore(domain));
24988
+ bindingStore(domain);
25081
24989
  if (!domain) console.log("[serenity-hooks] §0L 存储域不可用 → 绑定继续走 CCC 内 .bindings.json(向前兼容回落)");
25082
- } catch {
25083
- setBindingStore(null);
25084
- }
24990
+ } catch {}
25085
24991
  })();
25086
24992
  if (config.tools) {
25087
24993
  ctx.tools.register(ccFsTool);