@shgroup/dsh-serenity-hooks 1.113.2 → 1.114.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/dsh.plugin.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "id": "dsh-serenity-hooks",
3
- "version": "1.113.2",
3
+ "version": "1.114.0",
4
4
  "main": "lib/index.js",
5
5
  "description": "宁静号 ACC harness(Native Cordis 插件):给 DSH 装一个「AI 工作区」——12 个工具(container_fs/cognitive_trajectory/dashboard/container_git/msm/praxis/ontology/serenity-skill-creator/localstore/container_admin;另两个 im-bridge/diagram 按配置条件出现)+ 机械约束(安全模式/工作区围墙/密钥守卫)+ 认知轨迹日志与原地重建 + 网页登录入口/IM 桥/认知轨迹唤醒注册表。轨迹声明 = 轨迹目录下的 trajectory.json(约束面 + 旧标识 uid + CRO 机器绑定 cro.host;旧名 trajectory-constraint.json / skiff.json 仍在读取族里);轨迹提示词 = 轨迹目录下的 PROMPT.md(一条轨迹「是谁、按什么章程工作」,所有轨迹通用,追加在容器层与质量层之后,不替换它们)。适配 DSH 0.1.7-rc.1 / 0.2.0-rc.1 / 0.2.1-rc.1(三档,deepseek-ai/deepseek-harness)。",
6
6
  "engines": {
package/lib/index.js CHANGED
@@ -6,14 +6,14 @@ import { n as checkLocalstoreGitCompliance, o as readStore, s as runLocalStore,
6
6
  import { d as findPackageRoot, i as constraintSystemPromptSource, l as resolveConstraintSystemPrompt, r as constraintCognitiveTrajectory, t as TRAJECTORY_CONSTRAINT_HINT, u as ACC_VERSION } from "./rules-DKlpuFN_.js";
7
7
  import { C as docsPointer, E as setWeixinSendEndpoint, S as runMsmAsync, T as renderDocsIndex, _ as CCC_CONFIG_REFERENCE, a as setSafeMode, b as loadMsmEntries, d as forgetNonCccToolVisibility, g as ACC_CATALOG, h as forgetToolVisibility, i as readDshVersion, m as syncToolVisibility, n as summarizeHostContract, o as listOpencodeSkillDirs, p as registerGuards, r as getStatus, s as loadOpencodeSkill, t as hostContractReport, w as findDocsChapter, x as runMsm, y as findEntry } from "./contract-DdI7euZI.js";
8
8
  import { c as replacesDefaultIdentity, i as listTrajectoryConstraints, l as trajectoryCodeForSession, n as findDirNameByCode, o as readTrajectoryConstraintAt, r as isTrajectoryConstrained, s as readTrajectoryConstraintByCode, t as TRAJECTORY_CONSTRAINT_FILENAME } from "./profile-Djqz4Noy.js";
9
- import { A as truncateContent, C as verifyTotpCode, D as buildResourceIndex, E as SKILL_ONTOLOGY_FILE, M as hasTrajectoryPrompt, O as buildResourceNameSuffix, S as MECHANISM_PORTS, T as REFERENCE_DIR, _ as projectKnownWorkspaces, a as trajectoryConstraintFor, b as verifyPassword, c as trajectoryWorkspaceMdPath, d as splitModel, f as registerCognitiveTrajectorySkillSection, g as migrateLegacyLocalstore, h as applyWirePatch, i as getTrajectoryAgent, j as TRAJECTORY_PROMPT_FILENAME, k as isSafeSkillName, l as unregisterTrajectorySession, m as registerEntrySkillSectionGlobal, n as deliverToTrajectory, o as trajectoryConstraintMsmGate, p as registerEntrySkillSection, r as ensureWorkspacePromptSection, s as trajectoryConstraintParticipates, t as createTrajectoryAgent, u as workspaceCognitiveTrajectoryLine, v as readAdvancedSettings, w as recordToolUsage, x as FACE_PORTS, y as toWire } from "./session-C7-l8Kzs.js";
10
- import { a as findTrajectoryOfSession, c as resolveSessionCognitiveTrajectoryLabel, n as PLUGIN_SOURCE, r as appendBound, s as readLastBound, t as ACC_MESSAGE_KIND } from "./message-source-DLei3MqR.js";
11
- import { a as DEFAULT_LOOKBACK_MS, c as scanInterruptedSessions, d as registerDisposer, f as buildCroSnapshot, g as addWake, h as WAKE_CATCH_UP_MS, l as listRunningSessionIds, m as CARRIER_OPENING_MARK, o as createResumeOne, r as sendToCognitiveTrajectory, s as projectDirNameForCwd, t as registerWakeScheduler, u as registerCroTurnTracking } from "./scheduler-CB06u0Qc.js";
9
+ import { A as truncateContent, C as verifyTotpCode, D as buildResourceIndex, E as SKILL_ONTOLOGY_FILE, M as hasTrajectoryPrompt, O as buildResourceNameSuffix, S as MECHANISM_PORTS, T as REFERENCE_DIR, _ as projectKnownWorkspaces, a as trajectoryConstraintFor, b as verifyPassword, c as trajectoryWorkspaceMdPath, d as splitModel, f as registerCognitiveTrajectorySkillSection, g as migrateLegacyLocalstore, h as applyWirePatch, i as getTrajectoryAgent, j as TRAJECTORY_PROMPT_FILENAME, k as isSafeSkillName, l as unregisterTrajectorySession, m as registerEntrySkillSectionGlobal, n as deliverToTrajectory, o as trajectoryConstraintMsmGate, p as registerEntrySkillSection, r as ensureWorkspacePromptSection, s as trajectoryConstraintParticipates, t as createTrajectoryAgent, u as workspaceCognitiveTrajectoryLine, v as readAdvancedSettings, w as recordToolUsage, x as FACE_PORTS, y as toWire } from "./session-BVBI5jch.js";
10
+ import { a as ACC_MESSAGE_KIND, d as readLastBound, f as resolveSessionCognitiveTrajectoryLabel, i as toMessage, l as findTrajectoryOfSession, n as deliver, o as PLUGIN_SOURCE, r as makeInjection, s as appendBound } from "./injection-B5jZxemg.js";
11
+ import { a as DEFAULT_LOOKBACK_MS, c as scanInterruptedSessions, d as registerDisposer, f as buildCroSnapshot, g as addWake, h as WAKE_CATCH_UP_MS, l as listRunningSessionIds, m as CARRIER_OPENING_MARK, o as createResumeOne, r as sendToCognitiveTrajectory, s as projectDirNameForCwd, t as registerWakeScheduler, u as registerCroTurnTracking } from "./scheduler-BUM3tE0e.js";
12
12
  import { i as sessionsRootDir } from "./cleanup-CPl1SSaK.js";
13
13
  import { n as readSimpleSettings, r as registerSettingsSection } from "./settings-DibHCfEU.js";
14
14
  import { a as readFeishuCredential, c as readWeixinSettings, d as weixinInboundDir, f as weixinSessionIdForTrajectory, i as matchWeixinRoute, n as extractWeixinText, r as hasVoiceItem, s as readWeixinCredential, t as extractWeixinMedia, u as sanitizeFileName$1 } from "./route-D-W3O2PF.js";
15
15
  import { i as runImBridge, n as isImWiredTrajectory, r as registerImChannel, t as imChannelIds } from "./channels-DWZlcsE0.js";
16
- import { i as subscriptionStats, n as registerTrajectorySubscriptions, r as subscriptionEval, t as defaultSubscriptionDeps } from "./subscriptions-D05XffxT.js";
16
+ import { i as subscriptionStats, n as registerTrajectorySubscriptions, r as subscriptionEval, t as defaultSubscriptionDeps } from "./subscriptions-B9w4ppMk.js";
17
17
  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-CsZ-f64q.js";
18
18
  import { a as registerImAccountImpl, i as normalizeAccountChannel, n as getImAccountImpl, r as imAccountChannels } from "./accounts-BHQbakUh.js";
19
19
  import { createRequire } from "node:module";
@@ -24,7 +24,6 @@ import { appendFileSync, chmodSync, cpSync, existsSync, mkdirSync, readFileSync,
24
24
  import { execFileSync, spawn } from "node:child_process";
25
25
  import { platform } from "node:os";
26
26
  import { fileURLToPath } from "node:url";
27
- import { createUserMessage } from "@deepseek-ai/dsh-llm";
28
27
  import { createHash, randomBytes } from "node:crypto";
29
28
  import { Resvg, initWasm } from "@resvg/resvg-wasm";
30
29
  import * as zlib from "node:zlib";
@@ -516,7 +515,7 @@ const KIT_ACTIONS = [
516
515
  async function runKit(root, args, hostCtx) {
517
516
  switch (args.action) {
518
517
  case "health": {
519
- const { containerStatus } = await import("./observation-Cgmm6EKE.js");
518
+ const { containerStatus } = await import("./observation-qmSI2bug.js");
520
519
  const s = containerStatus({
521
520
  root,
522
521
  hostCtx
@@ -562,7 +561,7 @@ async function runKit(root, args, hostCtx) {
562
561
  }
563
562
  case "diag": {
564
563
  if (hostCtx === void 0) throw new Error("diag requires the plugin context (live session data unavailable without it)");
565
- const { runDiag, renderDiag } = await import("./service-BgkK71X9.js");
564
+ const { runDiag, renderDiag } = await import("./service-KNzeVxEh.js");
566
565
  return renderDiag(await runDiag(hostCtx, root ?? ""));
567
566
  }
568
567
  default: throw new Error(`Unknown action: ${args.action}`);
@@ -6936,13 +6935,13 @@ function createCognitiveTrajectoryTool(ctx) {
6936
6935
  const resumeOne = createResumeOne({
6937
6936
  agents,
6938
6937
  sessionController: sc,
6939
- makeMessage: (text) => createUserMessage({
6940
- content: [{
6941
- type: "text",
6942
- text
6943
- }],
6944
- source: PLUGIN_SOURCE
6945
- })
6938
+ makeMessage: (text) => toMessage(makeInjection({
6939
+ origin: "acc",
6940
+ form: "notice",
6941
+ summary: "中断兜底继续(工具面)",
6942
+ text,
6943
+ delivery: "wake-now"
6944
+ }))
6946
6945
  });
6947
6946
  const outcomes = [];
6948
6947
  for (const hit of scan.sessions) try {
@@ -8663,10 +8662,15 @@ function registerKeeper(ctx, opts = {}) {
8663
8662
  } else rebuildTodoStates.delete(scope);
8664
8663
  }
8665
8664
  if (blocks.length === 0) return downstream;
8666
- const reminderAll = createUserMessage({
8667
- content: blocks,
8668
- source: PLUGIN_SOURCE
8669
- });
8665
+ const blockTexts = blocks.map((b) => b.text ?? "");
8666
+ const reminderAll = toMessage(makeInjection({
8667
+ origin: "acc",
8668
+ form: "notice",
8669
+ summary: `keeper 提醒 ${blocks.length} 条`,
8670
+ text: blockTexts.join("\n"),
8671
+ blocks: blockTexts,
8672
+ delivery: "this-step"
8673
+ }));
8670
8674
  if (downstream.kind === "block") return {
8671
8675
  kind: "block",
8672
8676
  feedback: downstream.feedback,
@@ -8917,12 +8921,32 @@ function messageText(content) {
8917
8921
  return parts.join("\n");
8918
8922
  }
8919
8923
  /**
8920
- * 这条消息**是不是用户直接说的话**(结构性判据 + 冒充标记排除)。
8924
+ * 一条消息**是不是用户直接说的话**(结构性判据 + 冒充标记排除)。
8925
+ *
8926
+ * ## 🔴 DII M3(2026-11-04):判据升级为**读 `source.origin`**
8927
+ *
8928
+ * 修前:`kind === 'user'` + **正文标记黑名单**(`CARRIER_OPENING_MARK` / `ASSISTANT_PREFIX`)。
8929
+ * 那是"事后辨认"——它不完备:`corpus` 那条链的 20+ 条措辞黑名单就是同一个病(`heads` 子命令
8930
+ * 因此成了"量漏网家族"的工具)。
8921
8931
  *
8922
- * @returns `'quote'` 收录 / `'not-user'` 不是用户发言(ACC 注入等)/ `'fake-user'` 冒充 user 的生产者
8932
+ * 修后:**先读 `source.origin`**(DII 把它投影进 source,见 `kernel/message-source.ts`):
8933
+ * | `origin` | 含义 | 结论 |
8934
+ * |---|---|---|
8935
+ * | 缺席 + `kind==='user'` | **真人直说**(ACC 的 `sourceOf` 对 origin:'user' **刻意不写该字段**) | `quote` |
8936
+ * | `'acc'` | ACC 产的(唤醒/提醒/开场白/rebuild 锚点…) | `not-user` |
8937
+ * | `'agent'` | 另一个 agent 产的(subagent 任务书等) | `not-user` |
8938
+ *
8939
+ * ⇒ **两个长期"冒充真人"的生产者(载体开场白/rebuild 锚点)现在出生即分格**,
8940
+ * 不再需要正文标记参与判定。见下方 `USER_QUOTE_FAKE_MARKERS` 的处置说明。
8941
+ *
8942
+ * @returns `'quote'` 收录 / `'not-user'` 不是用户发言(ACC 注入等)/
8943
+ * `'fake-user'` **未带 origin 的旧日志里**靠正文标记认出是冒充者(过渡期兜底)
8923
8944
  */
8924
8945
  function classifyUserMessage(source, text) {
8925
- if (source?.kind !== "user") return "not-user";
8946
+ const src = source;
8947
+ const kind = src?.kind;
8948
+ if (src?.origin === "acc" || src?.origin === "agent") return "not-user";
8949
+ if (kind !== "user") return "not-user";
8926
8950
  for (const marker of USER_QUOTE_FAKE_MARKERS) if (text.startsWith(marker)) return "fake-user";
8927
8951
  return "quote";
8928
8952
  }
@@ -9087,16 +9111,25 @@ function accIdentityText(root, configPaths = DEFAULT_SERENITY_CONFIG_PATHS, entr
9087
9111
  * 完整身份(ACC 5 块 + CCE + Constraints + EAP + SKILL 全文 + Session 块)由**系统提示词层**
9088
9112
  * (systemPrompt.section,每轮 prompt 装配自动注入,含 subagent)承担——对话消息流/压缩重注入
9089
9113
  * 不再重复注入同一内容(token 双倍浪费,见 S134 注入方案梳理)。
9114
+ *
9115
+ * 🔴 **DII M2 · 注入点 #1**(2026-11-04):改成走 `Injection` 声明层。
9116
+ * 六个轴里**新增的是 `form:'instructions'` 与 `origin:'acc'`** —— 这两格以前只活在注释里。
9117
+ * ⚠️ **行为零变更**:`sourceOf()` 对 `origin:'acc'` + `form:'instructions'` 产出的
9118
+ * `kind` 仍是 `plugin:dsh-serenity-hooks`;只多带一个 `form` 字段(宿主 `ContextFormed` 支持它)。
9119
+ *
9120
+ * ⚠️ 本函数有**两个调用点**,通道不同(I3 的实例):
9121
+ * · `:480` 走 waterfall 返回 —— 无唤醒
9122
+ * · `:354` 走 `agent.inject()` —— 排队给下一个 pre-step,**也不唤醒**
9123
+ * 两者意图同为 `this-step`,故本函数的 `delivery` 声明成 `this-step`;由调用方各自接通道。
9090
9124
  */
9091
9125
  function accMessage(root, configPaths, entrySkillMaxChars) {
9092
- const content = [{
9093
- type: "text",
9094
- text: accIdentityText(root, configPaths, entrySkillMaxChars)
9095
- }];
9096
- return createUserMessage({
9097
- content,
9098
- source: PLUGIN_SOURCE
9099
- });
9126
+ const header = accIdentityText(root, configPaths, entrySkillMaxChars);
9127
+ return toMessage(makeInjection({
9128
+ origin: "acc",
9129
+ form: "instructions",
9130
+ text: header,
9131
+ delivery: "this-step"
9132
+ }));
9100
9133
  }
9101
9134
  /**
9102
9135
  * 每 agent 是否已注入过 —— **只作为本进程内的快路径**,⛔ 不是判据。
@@ -11685,6 +11718,13 @@ function performRebuild(session, pending, meter) {
11685
11718
  * 但宿主在**下一次打开该会话**时整份日志校验失败抛 `SessionPersistenceCorruptionError`
11686
11719
  * → **会话永久打不开**(用户报的"会话老是损坏")。实证见 S142 §26.2(真实日志 seq 5066)。
11687
11720
  * 正确形状与 `seams/bootstrap.ts:306-311` 的锚定消息一致。
11721
+ *
11722
+ * 🔴 **DII M2 · 注入点 #10**(2026-11-04):**这一处是 I1 最需要被看见的一格**。
11723
+ * 它是 ACC 产的消息(`origin:'acc'`),但宿主校验**强制**要求 `kind:'user'`
11724
+ * (缺了它会话打不开,见上)。⇒ 旧的 `source: { kind: 'user' }` 是**没办法的办法**:
11725
+ * 它丢掉了"谁产生的"这一信息。现在 `Injection` 记着 `origin:'acc'`,语料/语录
11726
+ * 那一侧(M3)就能按 `origin` 正确排除它 —— **不必再靠正文标记猜**。
11727
+ * ⚠️ **宿主 kind 仍是 `'user'`,一字不改**:这一格只把"我是谁"补回来,不改落点。
11688
11728
  */
11689
11729
  id: `rebuild-anchor-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`,
11690
11730
  role: "user",
@@ -11692,7 +11732,7 @@ function performRebuild(session, pending, meter) {
11692
11732
  type: "text",
11693
11733
  text: pending.anchor
11694
11734
  }],
11695
- source: { kind: "user" }
11735
+ source: REBUILD_ANCHOR_SOURCE
11696
11736
  }, {
11697
11737
  surfaceOp: {
11698
11738
  op: "replace",
@@ -11703,6 +11743,7 @@ function performRebuild(session, pending, meter) {
11703
11743
  });
11704
11744
  return true;
11705
11745
  }
11746
+ const REBUILD_ANCHOR_SOURCE = { kind: "user" };
11706
11747
  /**
11707
11748
  * 重建后重命名 dsh 会话标题(需求② S142 用户拍板:rebuild 后标题带新阶段概括)。
11708
11749
  * 标题 = S###-YYYY-MM-DD-<summary>——S### 与日期从持久轨迹目录名派生(不信任 LLM),
@@ -11759,13 +11800,13 @@ function registerRebuildTurnHook(ctx) {
11759
11800
  const meter = tokenMeter && typeof tokenMeter.estimateMessage === "function" ? { estimateMessage: (m) => tokenMeter.estimateMessage(m) } : void 0;
11760
11801
  if (performRebuild(agent.session, pending, meter)) {
11761
11802
  renameAfterRebuild(ctx, agent, pending);
11762
- agent.steer(createUserMessage({
11763
- content: [{
11764
- type: "text",
11765
- text: `${eventToken("rebuild")} The conversation has been cleared and rebuilt. Follow the anchor instructions above now: read the persistent cognitive trajectory (SESSION.md) and continue the work automatically from the last checkpoint.`
11766
- }],
11767
- source: PLUGIN_SOURCE
11768
- }));
11803
+ deliver(makeInjection({
11804
+ origin: "acc",
11805
+ form: "instructions",
11806
+ summary: "rebuild 后自动继续",
11807
+ text: `${eventToken("rebuild")} The conversation has been cleared and rebuilt. Follow the anchor instructions above now: read the persistent cognitive trajectory (SESSION.md) and continue the work automatically from the last checkpoint.`,
11808
+ delivery: "wake-now"
11809
+ }), agent);
11769
11810
  writeRebuildDiag(resolveSerenityRootFor(agent), {
11770
11811
  sessionId: id,
11771
11812
  event: "rebuilt"
@@ -13742,13 +13783,13 @@ function registerOutputGuardHook(ctx) {
13742
13783
  }
13743
13784
  console.log(`[serenity-hooks] output-guard: rebuke ${st.consecutive}/3 (agent=${id}, turn=${payload.turn ?? "?"}, hits=${hits.length})`);
13744
13785
  try {
13745
- agent.steer(createUserMessage({
13746
- content: [{
13747
- type: "text",
13748
- text: buildRebuke(hits)
13749
- }],
13750
- source: PLUGIN_SOURCE
13751
- }));
13786
+ deliver(makeInjection({
13787
+ origin: "acc",
13788
+ form: "notice",
13789
+ summary: `输出守卫打回(${hits.length} 处敏感词)`,
13790
+ text: buildRebuke(hits),
13791
+ delivery: "wake-now"
13792
+ }), agent);
13752
13793
  } catch (error) {
13753
13794
  console.warn(`[serenity-hooks] output-guard steer failed: ${String(error?.message ?? error)}`);
13754
13795
  }
@@ -14419,13 +14460,13 @@ function registerWeixinOutputGuard(ctx) {
14419
14460
  }
14420
14461
  rebukeCounts.set(sessionId, count);
14421
14462
  try {
14422
- agent.steer(createUserMessage({
14423
- content: [{
14424
- type: "text",
14425
- text: buildManualOutputRebuke(session)
14426
- }],
14427
- source: PLUGIN_SOURCE
14428
- }));
14463
+ deliver(makeInjection({
14464
+ origin: "acc",
14465
+ form: "notice",
14466
+ summary: `IM 输出闸门打回(第 ${count} 次)`,
14467
+ text: buildManualOutputRebuke(session),
14468
+ delivery: "wake-now"
14469
+ }), agent);
14429
14470
  console.log(`[serenity-hooks] weixin 输出闸门:打回 ${count}/2(session=${sessionId}, turn=${payload.turn ?? "?"})`);
14430
14471
  } catch (error) {
14431
14472
  console.warn(`[serenity-hooks] ✗ weixin 输出闸门 steer 失败: ${String(error?.message ?? error)}`);
@@ -1,6 +1,7 @@
1
1
  import { r as cccRootForCwd } from "./ccc-roots-CsSSQjSS.js";
2
2
  import { T as sessionsRoot, a as readBindDeclaration, b as parseTrajectoryCode, h as getLastActiveSessionInfo, i as listBindDeclarations, m as getActiveSessionInfo, n as findTrajectoryHolding, r as isBindUsable, t as claimBinding, w as sessionEvents } from "./bind-file-B0TuOP89.js";
3
3
  import { join } from "node:path";
4
+ import { createUserMessage } from "@deepseek-ai/dsh-llm";
4
5
  //#region src/trajectory/bound.ts
5
6
  /**
6
7
  * trajectory/bound.ts — 「会话 ↔ 认知轨迹」绑定(**2026-10-20 重做:归属 = 轨迹自己的交代**)
@@ -255,4 +256,193 @@ const ACC_MESSAGE_KIND = "plugin:dsh-serenity-hooks";
255
256
  /** ACC 注入消息的统一来源值(各缝共用;需要 `form` 时按 `ContextFormed` 另加字段)。 */
256
257
  const PLUGIN_SOURCE = { kind: ACC_MESSAGE_KIND };
257
258
  //#endregion
258
- export { findTrajectoryOfSession as a, resolveSessionCognitiveTrajectoryLabel as c, carrierState as i, setBindingStore as l, PLUGIN_SOURCE as n, listBoundSessionIds as o, appendBound as r, readLastBound as s, ACC_MESSAGE_KIND as t };
259
+ //#region src/kernel/injection.ts
260
+ /**
261
+ * kernel/injection.ts — 动态提示词注入架构(DII)· 声明层
262
+ *
263
+ * ## 为什么有这个文件(问题陈述)
264
+ *
265
+ * 全仓有 **14 处**把消息注入进模型的调用点。它们共享同一个动作
266
+ * `createUserMessage({ source: PLUGIN_SOURCE })`,却在 **6 条互不相干的属性轴上各自取值**,
267
+ * 而这些取值**只存在于各处的注释与代码里** —— 没有任何数据结构能回答「这条消息是什么」。
268
+ *
269
+ * 两个已发生的真实故障,都是这条缺失的直接后果:
270
+ * ① **subagent 任务书被当成用户发言**(v1.113.2 修)—— 当时判据只有 `kind === 'user'`,
271
+ * 而「谁产生的」没有别的地方记着;
272
+ * ② **两个生产者刻意冒充 `kind:'user'`**(载体开场白 / rebuild 锚点)—— 没有「声明我是谁」
273
+ * 的正规通道 ⇒ 只能改 `kind` 这个**共享字段**去蹭宿主行为。
274
+ *
275
+ * ⇒ 本文件把 6 条轴变成**一等字段**。**M1 阶段零接线**:这里定义形状与声明表,
276
+ * 但**没有任何调用点**改用它 ⇒ 行为零变更(可回滚 = 删掉本文件)。
277
+ *
278
+ * ## 🔴 两条落地约束(不是设计选择,是宿主类型的硬事实,均经探针实测)
279
+ *
280
+ * **① `kind:'user'` 装不下 `form`。** 宿主 `MessageSourceMap` 的 `user` 支逐字就是
281
+ * `{ kind: 'user' }`(无 `& ContextFormed`)⇒ `{kind:'user', form:'relay'}` 会被 tsc 拒
282
+ * (`TS2353: 'form' does not exist in type '{ kind: "user"; }'`)。故 {@link Injection.form}
283
+ * **不是无条件必填**:`origin === 'user'` 时它必须缺席。
284
+ *
285
+ * **② 宿主对「另一个 agent 的消息」用专用 kind,不蹭 `kind:'user'`。**
286
+ * `dsh-subagent` 给相邻 agent 的消息用 `kind:'agent-message'` + `form:'relay'`。
287
+ * 我们对应地让 `origin:'agent'` 落在**我们自有**的 `plugin:dsh-serenity-hooks` 上
288
+ * (它**带了** `& ContextFormed`,见 `kernel/message-source.ts`)+ `form:'relay'`。
289
+ *
290
+ * ⚠️ **刻意不新造 kind**(曾考虑 `plugin:dsh-serenity-hooks:agent-relay`):新 kind 会让
291
+ * 「subagent 消息」在界面里**从用户气泡变成注入行**(宿主 UI 按 `kind !== 'user'` 分流),
292
+ * 那是行为可见变化而收益为零 —— 判据(`origin`)已经在 `Injection` 里了,不需要再动宿主身份。
293
+ *
294
+ * ## 🔴 一个必须记下的类型洞(对 M1 的写法有直接影响)
295
+ *
296
+ * 同一句 `{kind:'user', form:'relay'}`:
297
+ * · 作 `const x: MessageSource = …` ⇒ **红**(excess property check 生效)
298
+ * · 作 `createUserMessage({…, source: {…}})` 的内联实参 ⇒ **绿**(泛型参数位不触发该检查)
299
+ * ⇒ 我们 14 处注入点**全都走后者**。所以「类型系统替我守住 form」在现状下**是假的**。
300
+ * ⇒ 本文件的 {@link deliver} **接收/返回 `MessageSource` 注解类型**,⛔ 不许内联字面量 ——
301
+ * 否则这个洞会原封不动地传下去。
302
+ *
303
+ * @see references/dii-injection-architecture-proposal.md(§3.5 两条纠正的取证全文)
304
+ */
305
+ /**
306
+ * 语料归属:真人说的话与宿主/ACC 注入算语料;**另一个 agent 的话不算**
307
+ * (这是 v1.113.2 那次污染的判据,只是从「事后排除」变成「出生即分格」)。
308
+ */
309
+ function corpusOf(origin) {
310
+ return origin !== "agent";
311
+ }
312
+ /**
313
+ * 用户语录归属:**只有真人直说**才算。
314
+ *
315
+ * 🔴 这条正是 `_user-quotes` 那条链上两次缺陷的共同根:
316
+ * · subagent 任务书混入(v1.113.2)—— 它 `origin:'agent'` ⇒ 此处自动 false;
317
+ * · 载体开场白 / rebuild 锚点冒充 `kind:'user'` —— 它们 `origin:'acc'` ⇒ 此处自动 false。
318
+ * ⇒ M3 阶段这两个调用点就能把各自的排除表/标记删掉。
319
+ */
320
+ function userQuoteOf(origin) {
321
+ return origin === "user";
322
+ }
323
+ /**
324
+ * 把 `origin`(+`form`)映射成宿主的 `MessageSource`。
325
+ *
326
+ * 🔴 **返回类型显式标注为 `MessageSource`** —— 这是本文件躲开「泛型内联不检查」那个洞的唯一办法。
327
+ *
328
+ * | origin | 宿主 kind | form |
329
+ * |---|---|---|
330
+ * | `user` | `'user'`(宿主那一支,**无 form**) | ⛔ 必须缺席 |
331
+ * | `acc` | `plugin:dsh-serenity-hooks` | 可选 |
332
+ * | `agent`| `plugin:dsh-serenity-hooks` + `form:'relay'` | **re​lay(强制)** |
333
+ */
334
+ function sourceOf(inj) {
335
+ if (inj.origin === "user") {
336
+ if (inj.form !== void 0) throw new Error(`Injection: origin:'user' 不允许带 form(宿主 MessageSourceMap 的 user 支装不下 ContextFormed)。收到 form='${inj.form}'。`);
337
+ return { kind: "user" };
338
+ }
339
+ if (inj.origin === "agent") return {
340
+ kind: ACC_MESSAGE_KIND,
341
+ form: "relay",
342
+ origin: "agent"
343
+ };
344
+ return inj.form === void 0 ? {
345
+ kind: ACC_MESSAGE_KIND,
346
+ origin: "acc"
347
+ } : inj.form === "notice" ? {
348
+ kind: ACC_MESSAGE_KIND,
349
+ form: "notice",
350
+ summary: inj.summary ?? "",
351
+ origin: "acc"
352
+ } : inj.form === "snapshot" ? {
353
+ kind: ACC_MESSAGE_KIND,
354
+ form: "snapshot",
355
+ sections: [{
356
+ name: "acc",
357
+ text: inj.text
358
+ }],
359
+ origin: "acc"
360
+ } : {
361
+ kind: ACC_MESSAGE_KIND,
362
+ form: inj.form,
363
+ origin: "acc"
364
+ };
365
+ }
366
+ /** 检查一条 `Injection` 是否自洽。不合则抛 —— ⛔ 不用「返回 false 让人忘查」。 */
367
+ function assertInjection(inj) {
368
+ if (inj.text.trim() === "") throw new Error("Injection: text 不能为空(空注入没有接收者)");
369
+ if (inj.origin === "user" && inj.form !== void 0) throw new Error(`Injection: origin:'user' 不允许带 form(收到 '${inj.form}')`);
370
+ if (inj.form === "notice" && (inj.summary === void 0 || inj.summary.trim() === "")) throw new Error(`Injection: form:'notice' 必须给 summary(宿主 ContextFormed 的构成要件)`);
371
+ if (inj.corpus !== corpusOf(inj.origin)) throw new Error(`Injection 违反 I2:corpus=${inj.corpus} 与 origin='${inj.origin}' 的推导值不符`);
372
+ if (inj.userQuote !== userQuoteOf(inj.origin)) throw new Error(`Injection 违反 I2:userQuote=${inj.userQuote} 与 origin='${inj.origin}' 的推导值不符`);
373
+ if (inj.blocks !== void 0) {
374
+ if (inj.blocks.length === 0) throw new Error("Injection: blocks 为空数组时不该设它(用单块 text 即可)");
375
+ if (inj.blocks.some((b) => typeof b !== "string")) throw new Error("Injection: blocks 的每一项都必须是字符串");
376
+ if (inj.blocks.join("\n") !== inj.text) throw new Error("Injection: blocks.join(\"\\n\") 必须逐字等于 text(否则同一事实存两份,必然漂移)");
377
+ }
378
+ }
379
+ /** 构造一条 `Injection`,**归属字段自动推导**(调用点只需给 origin/text/delivery/form)。 */
380
+ function makeInjection(spec) {
381
+ return {
382
+ ...spec,
383
+ corpus: spec.corpus ?? corpusOf(spec.origin),
384
+ userQuote: spec.userQuote ?? userQuoteOf(spec.origin)
385
+ };
386
+ }
387
+ /**
388
+ * 把一条 `Injection` 变成**宿主消息对象**,但不投递。
389
+ *
390
+ * 🔴 为什么必须单独有它(而不是让 `deliver` 包办全部 5 种通道):
391
+ * 有三种通道**根本不能由「调一个方法」完成** ——
392
+ * · `waterfall`:要**返回** `{kind:'enter', messages:[…]}` 给宿主;
393
+ * · `additionalContexts`:要**返回**一个数组给下游;
394
+ * · 直写 session event:要 append 一条事件。
395
+ * 这三处的调用方**必须拿到消息对象**。若 `deliver` 把它们也吃掉,就只能靠"回调"骗禁用名,
396
+ * 反而比现在更难读。⇒ **`toMessage` 管"造",`deliver` 管"投"**,两者都过同一个
397
+ * `assertInjection` + `sourceOf`,所以六条轴的取值仍然只有一处定义。
398
+ */
399
+ function toMessage(inj) {
400
+ assertInjection(inj);
401
+ return createUserMessage({
402
+ content: contentOf(inj),
403
+ source: sourceOf(inj)
404
+ });
405
+ }
406
+ /**
407
+ * 内容块:有 `blocks` 就逐块给(**保住原始块数**),否则单块。
408
+ * 🔴 这是「多块注入物迁进本模型后形态不变」的唯一实现点。
409
+ */
410
+ function contentOf(inj) {
411
+ return (inj.blocks ?? [inj.text]).map((t) => ({
412
+ type: "text",
413
+ text: t
414
+ }));
415
+ }
416
+ /**
417
+ * 把一条 `Injection` 投给目标 agent。
418
+ *
419
+ * ⚠️ 只处理**能由「调一个方法」完成**的两种通道(`wake-now`→`steer`、`next-turn`→`followup`)。
420
+ * 另外三种(`this-step` / `face-replace` / waterfall)用 {@link toMessage} 由调用方自己接 ——
421
+ * 它们要么要返回值、要么要 append 事件,硬塞进本函数只会得到更难读的回调。
422
+ * 传进来这两种会**显式抛错**,⛔ 不静默降级成别的通道(失败面清单 #2)。
423
+ *
424
+ * @returns 实际走的通道名
425
+ */
426
+ function deliver(inj, target) {
427
+ assertInjection(inj);
428
+ if (inj.delivery === "wake-now") {
429
+ if (typeof target.steer !== "function") throw new Error(`deliver: delivery='wake-now' 需要 steer,但目标没有该方法`);
430
+ target.steer(toMessageUnchecked(inj));
431
+ return "steer";
432
+ }
433
+ if (inj.delivery === "next-turn") {
434
+ if (typeof target.followup !== "function") throw new Error(`deliver: delivery='next-turn' 需要 followup,但目标没有该方法`);
435
+ target.followup(toMessageUnchecked(inj));
436
+ return "followup";
437
+ }
438
+ throw new Error(`deliver: delivery='${inj.delivery}' 不走本函数 —— 'this-step' 是 waterfall 的 additionalContexts、'face-replace' 是直写 session event,两者用 toMessage() 由调用方接(它们需要返回值或 append 事件,不是调方法)。`);
439
+ }
440
+ /** 内部:已经 assert 过的路径复用,避免 `deliver` 里二次校验(保持一次来源)。 */
441
+ function toMessageUnchecked(inj) {
442
+ return createUserMessage({
443
+ content: contentOf(inj),
444
+ source: sourceOf(inj)
445
+ });
446
+ }
447
+ //#endregion
448
+ export { ACC_MESSAGE_KIND as a, carrierState as c, readLastBound as d, resolveSessionCognitiveTrajectoryLabel as f, toMessage as i, findTrajectoryOfSession as l, deliver as n, PLUGIN_SOURCE as o, setBindingStore as p, makeInjection as r, appendBound as s, assertInjection as t, listBoundSessionIds as u };
@@ -0,0 +1,101 @@
1
+ /**
2
+ * kernel/injection-sites.ts — DII 的 **14 处注入点声明表**(M1 交付物,**零接线**)
3
+ *
4
+ * ## 这个文件是什么
5
+ *
6
+ * 一张**静态的**登记表:全仓每一处「把消息注入进模型」的地方,在 6 条轴上各取什么值。
7
+ * 它**不执行任何东西**,也没有调用点读它(M1 零接线)。
8
+ *
9
+ * ## 为什么它比类型定义更值钱
10
+ *
11
+ * `injection.ts` 定的是**形状**;本文件定的是**现状的取值**。两者合起来才回答那句核心问题:
12
+ * **「这条注入消息到底是什么」** —— 而这正是当初两个故障缺失的那一格里装着的东西
13
+ * (subagent 任务书被当成真人发言 / 两个生产者冒充 `kind:'user'`)。
14
+ *
15
+ * 它同时是 **M2 的施工清单**:逐处迁移时,从这里取那一行的六轴取值,改成 `makeInjection(...)`
16
+ * + `deliver(...)`,迁完把该行 `migrated` 打勾。**一张表驱动 14 次独立提交**。
17
+ *
18
+ * ## 判据(M2 完成后必须成立,可机械验)
19
+ *
20
+ * - `grep -rn "createUserMessage({"" src/` 应**只剩两处**,且都能解释:
21
+ * · `kernel/injection.ts` 里的两处(同一个实现,`toMessage` 与它的 unchecked 版本)
22
+ * · `trajectory/user-carrier.ts` 一处 —— **owner 裁决 B 强制 `kind:'user'`**(见下注 1)
23
+ * - 本表所有行的 `migrated` 全为 `true`(⚠️ 只在**真机核过形态**之后才置 true)。
24
+ *
25
+ * ## ⚠️ 三处**不受本表管辖**的例外(如实标注,⛔ 不是漏登记)
26
+ *
27
+ * 1. **`trajectory/user-carrier.ts:112` 的载体开场白** —— 它就是上表 #11,但它的
28
+ * `kind:'user'` 是 **owner 裁决(方案 B)** 直接要求的(让宿主自动命名生效),
29
+ * **不是本架构能改的**。⇒ 迁移时它只能改「声明」,`kind` 必须仍是 `user`。
30
+ * 2. **`trajectory/rebuild.ts:419` 直写会话面** —— 宿主校验强制要求 `kind:'user'`,同上。
31
+ * 3. `seams/context.ts` 的 `accMessage` 走 waterfall 返回,不是 `createUserMessage` 的普通投递。
32
+ *
33
+ * @see references/dii-injection-architecture-proposal.md §二(逐条盘点的取证)
34
+ */
35
+ import type { InjectionDelivery, InjectionOrigin } from './injection.js';
36
+ import type { ContextForm } from '@deepseek-ai/dsh-llm';
37
+ /**
38
+ * 一处注入点的登记项。
39
+ *
40
+ * ⚠️ 与 {@link import('./injection.js').Injection} 的区别:`Injection` 是**一次投递**的完整声明;
41
+ * 本项是**一个代码位置的静态属性**(它可能每次投递时取值不同,故 `text` 不在此,`,
42
+ * 而以 `textFrom` 记「正文由哪个构造器产出」)。
43
+ */
44
+ export interface InjectionSite {
45
+ /** 与提案 §2.1 的编号一致(1~14),供 M2 逐条对照。 */
46
+ readonly id: number;
47
+ /** 人类可读的名字。 */
48
+ readonly name: string;
49
+ /** 代码位置(`文件:行`,**行号是某版本的实测读数**,M2 迁移后以符号为准)。 */
50
+ readonly at: string;
51
+ /** 正文由哪个构造器产出(`null` = 就地拼字符串)。 */
52
+ readonly textFrom: string | null;
53
+ /** A 轴:来源。 */
54
+ readonly origin: InjectionOrigin;
55
+ /** B 轴:形态。`undefined` = 现状未声明(这正是要补的)。 */
56
+ readonly form: ContextForm | undefined;
57
+ /** C+D 轴:投递意图。 */
58
+ readonly delivery: InjectionDelivery;
59
+ /** 现状用的**通道**(实现),用于 M2 对比「意图 vs 实现」有没有错配。 */
60
+ readonly channel: 'followup' | 'steer' | 'additionalContexts' | 'waterfall' | 'session-event';
61
+ /** 现状是否**唤醒**(M2 必须逐个对比,⛔ 尤其不能把「不唤醒」改成「唤醒」)。 */
62
+ readonly wakes: boolean;
63
+ /** 现状的宿主 `kind`。 */
64
+ readonly kind: string;
65
+ /**
66
+ * 代码是否已改走声明层(`makeInjection` / `deliver` / `toMessage`)。
67
+ * 🔴 **它只代表"代码改了 + 单测绿",⛔ 不代表"真机形态已核"** —— 后者是下一格。
68
+ */
69
+ readonly migrated: boolean;
70
+ /**
71
+ * **真机**形态是否已核(部署后看实际投递)。
72
+ *
73
+ * 🔴 为什么必须与 `migrated` 分成两格(2026-11-04 M2 自纠):
74
+ * 本表最初的判据写「`migrated` 只在真机验过之后才置 true」。但 M2 施工时发现,
75
+ * 那时**没有任何一行能置 true**(真机验证要等发布部署),于是这一格退化成**永远 false** ——
76
+ * 一个恒 false 的字段**什么都没记着**。⇒ 拆成两格:代码进度(立即可知)与真机进度(要等部署)。
77
+ * ⚠️ 判据:**`migrated && !realMachineVerified` 的行,正是"已改、待真机核"的待办清单**。
78
+ */
79
+ readonly realMachineVerified: boolean;
80
+ }
81
+ /**
82
+ * 注入点(与提案 §2.1 逐条对应;**M2 施工时从 14 订正到 16**,见下注)。
83
+ *
84
+ * 🔴 **2026-11-04 M2 施工订正(诚实记录)**:原盘点写「14 处」**少了两处** ——
85
+ * `tools/cognitive-trajectory.ts:939` 的 `makeMessage`(工具的 askTrajectory 走的工厂)
86
+ * 与 `trajectory/interrupted.ts:723` 的 `deps.makeMessage`(依赖注入的工厂)。
87
+ * 漏它们的原因很具体:**它们不直接调 `createUserMessage`,而是提供一个工厂函数**
88
+ * ⇒ `grep createUserMessage` 看不见它们。这正是"判据的扫描域必须自己说出来"(D259)。
89
+ * ⚠️ 若按 14 处施工,`grep` 判据**永远不会绿**,而且**没人会知道为什么**。
90
+ *
91
+ * 🔴 **读法**:`form: undefined` 的行 = 现状**没有声明形态**,这是 DII 要补的第一件事;
92
+ * `channel` 与 `delivery` 不一致的行,就是 M2 里最需要盯的(D 轴与 C 轴耦合的地方)。
93
+ */
94
+ export declare const INJECTION_SITES: readonly InjectionSite[];
95
+ /**
96
+ * M2 施工顺序(**由风险从低到高**,⛔ 不是按编号)。
97
+ *
98
+ * 理由:先迁「行为无差别、无外部观察者」的那些,把 `deliver()` 的形状压稳,
99
+ * 再动「一旦错就打断正在跑的会话」的那些。
100
+ */
101
+ export declare const MIGRATION_ORDER: readonly number[];
@@ -0,0 +1,162 @@
1
+ /**
2
+ * kernel/injection.ts — 动态提示词注入架构(DII)· 声明层
3
+ *
4
+ * ## 为什么有这个文件(问题陈述)
5
+ *
6
+ * 全仓有 **14 处**把消息注入进模型的调用点。它们共享同一个动作
7
+ * `createUserMessage({ source: PLUGIN_SOURCE })`,却在 **6 条互不相干的属性轴上各自取值**,
8
+ * 而这些取值**只存在于各处的注释与代码里** —— 没有任何数据结构能回答「这条消息是什么」。
9
+ *
10
+ * 两个已发生的真实故障,都是这条缺失的直接后果:
11
+ * ① **subagent 任务书被当成用户发言**(v1.113.2 修)—— 当时判据只有 `kind === 'user'`,
12
+ * 而「谁产生的」没有别的地方记着;
13
+ * ② **两个生产者刻意冒充 `kind:'user'`**(载体开场白 / rebuild 锚点)—— 没有「声明我是谁」
14
+ * 的正规通道 ⇒ 只能改 `kind` 这个**共享字段**去蹭宿主行为。
15
+ *
16
+ * ⇒ 本文件把 6 条轴变成**一等字段**。**M1 阶段零接线**:这里定义形状与声明表,
17
+ * 但**没有任何调用点**改用它 ⇒ 行为零变更(可回滚 = 删掉本文件)。
18
+ *
19
+ * ## 🔴 两条落地约束(不是设计选择,是宿主类型的硬事实,均经探针实测)
20
+ *
21
+ * **① `kind:'user'` 装不下 `form`。** 宿主 `MessageSourceMap` 的 `user` 支逐字就是
22
+ * `{ kind: 'user' }`(无 `& ContextFormed`)⇒ `{kind:'user', form:'relay'}` 会被 tsc 拒
23
+ * (`TS2353: 'form' does not exist in type '{ kind: "user"; }'`)。故 {@link Injection.form}
24
+ * **不是无条件必填**:`origin === 'user'` 时它必须缺席。
25
+ *
26
+ * **② 宿主对「另一个 agent 的消息」用专用 kind,不蹭 `kind:'user'`。**
27
+ * `dsh-subagent` 给相邻 agent 的消息用 `kind:'agent-message'` + `form:'relay'`。
28
+ * 我们对应地让 `origin:'agent'` 落在**我们自有**的 `plugin:dsh-serenity-hooks` 上
29
+ * (它**带了** `& ContextFormed`,见 `kernel/message-source.ts`)+ `form:'relay'`。
30
+ *
31
+ * ⚠️ **刻意不新造 kind**(曾考虑 `plugin:dsh-serenity-hooks:agent-relay`):新 kind 会让
32
+ * 「subagent 消息」在界面里**从用户气泡变成注入行**(宿主 UI 按 `kind !== 'user'` 分流),
33
+ * 那是行为可见变化而收益为零 —— 判据(`origin`)已经在 `Injection` 里了,不需要再动宿主身份。
34
+ *
35
+ * ## 🔴 一个必须记下的类型洞(对 M1 的写法有直接影响)
36
+ *
37
+ * 同一句 `{kind:'user', form:'relay'}`:
38
+ * · 作 `const x: MessageSource = …` ⇒ **红**(excess property check 生效)
39
+ * · 作 `createUserMessage({…, source: {…}})` 的内联实参 ⇒ **绿**(泛型参数位不触发该检查)
40
+ * ⇒ 我们 14 处注入点**全都走后者**。所以「类型系统替我守住 form」在现状下**是假的**。
41
+ * ⇒ 本文件的 {@link deliver} **接收/返回 `MessageSource` 注解类型**,⛔ 不许内联字面量 ——
42
+ * 否则这个洞会原封不动地传下去。
43
+ *
44
+ * @see references/dii-injection-architecture-proposal.md(§3.5 两条纠正的取证全文)
45
+ */
46
+ import type { ContextForm, MessageSource, UserMessage } from '@deepseek-ai/dsh-llm';
47
+ /** 六条属性轴里「来源」那一轴的三档取值。 */
48
+ export type InjectionOrigin = 'user' | 'acc' | 'agent';
49
+ /**
50
+ * 投递意图(**意图不是通道** —— 这是 I3)。
51
+ *
52
+ * 同一个意图在不同上下文可落不同通道:`wake-now` 在活会话走 `steer`、在冷会话走 `followup`。
53
+ * 通道的选择只发生在 {@link deliver} 里,⛔ 调用点不许自己挑通道。
54
+ */
55
+ export type InjectionDelivery =
56
+ /** 当场进当前轮(模型同一次生成里就看到)。对应宿主 `agent.steer`。 */
57
+ 'wake-now'
58
+ /** 起新的一轮(排队等本轮结束)。对应宿主 `agent.followup`。 */
59
+ | 'next-turn'
60
+ /** 挂到本步、**不唤醒**。对应 `additionalContexts`。 */
61
+ | 'this-step'
62
+ /** 改会话面本身(rebuild 专用)。对应直写 session event。 */
63
+ | 'face-replace';
64
+ /**
65
+ * 一条注入消息的**完整声明**。六个字段 = 六条轴。
66
+ *
67
+ * 不变量(由 {@link assertInjection} 机械检查):
68
+ * - **I1** `origin` 与宿主 `kind` 是映射,不是同义词(见 {@link sourceOf})。
69
+ * - **I2** `corpus` / `userQuote` 由 `origin` 推导,⛔ 不许调用点各自决定。
70
+ * - **I3** `delivery` 是意图,通道是实现(见 {@link deliver})。
71
+ */
72
+ export interface Injection {
73
+ /** A 来源:谁产生的。**替代「靠 `kind` 猜」**。 */
74
+ readonly origin: InjectionOrigin;
75
+ /**
76
+ * B 形态:宿主 `ContextForm` 六选一。
77
+ * 🔴 **`origin:'user'` 时必须缺席**(宿主 `user` 支装不下 form,见文件头注 ①)。
78
+ */
79
+ readonly form?: ContextForm;
80
+ /** `form:'notice'` 时**必须**给:一句话说明发生了什么(宿主的构成要件,非装饰)。 */
81
+ readonly summary?: string;
82
+ /** 正文(由各注入物自己的构造器产出,含各自契约标记)。 */
83
+ readonly text: string;
84
+ /**
85
+ * 当一条消息**本来就是多个内容块**时,逐块给出它们的顺序。
86
+ *
87
+ * 🔴 为什么必须有这一格(M2 #2 实测教训):`keeper` 的提醒是 CHECKPOINT / REBUILD /
88
+ * SESSION 体积**各一个块**合成一条消息。若为了迁进本模型而把它们 `join('\n')` 成一块,
89
+ * 消息的**内容块数量就变了** —— 模型看到的是"一个含换行的大块"而不是"三个块"。
90
+ * 那是**形态变更**,违反 M2「该注入物形态一字不变」的铁律。
91
+ * ⇒ 有这一格,`text` 仍是规范文本(供 `summary` 与断言用,且**必须等于**这些块的拼接)。
92
+ * ⛔ **不是**两套真相源:`assertInjection` 机械核对 `text === blocks.join('\n')`。
93
+ */
94
+ readonly blocks?: readonly string[];
95
+ /** C+D 投递意图:唤醒语义**显式**,不再隐含在通道里。 */
96
+ readonly delivery: InjectionDelivery;
97
+ /** E 归属:算不算语料。(由 {@link corpusOf} 从 `origin` 推导) */
98
+ readonly corpus: boolean;
99
+ /** F 归属:算不算用户语录。(由 {@link userQuoteOf} 从 `origin` 推导) */
100
+ readonly userQuote: boolean;
101
+ /** 去重键(**可选**;异步 MSM 结果**必须**带,⛔ 不许用正文哈希)。 */
102
+ readonly dedupeKey?: string;
103
+ }
104
+ /**
105
+ * 语料归属:真人说的话与宿主/ACC 注入算语料;**另一个 agent 的话不算**
106
+ * (这是 v1.113.2 那次污染的判据,只是从「事后排除」变成「出生即分格」)。
107
+ */
108
+ export declare function corpusOf(origin: InjectionOrigin): boolean;
109
+ /**
110
+ * 用户语录归属:**只有真人直说**才算。
111
+ *
112
+ * 🔴 这条正是 `_user-quotes` 那条链上两次缺陷的共同根:
113
+ * · subagent 任务书混入(v1.113.2)—— 它 `origin:'agent'` ⇒ 此处自动 false;
114
+ * · 载体开场白 / rebuild 锚点冒充 `kind:'user'` —— 它们 `origin:'acc'` ⇒ 此处自动 false。
115
+ * ⇒ M3 阶段这两个调用点就能把各自的排除表/标记删掉。
116
+ */
117
+ export declare function userQuoteOf(origin: InjectionOrigin): boolean;
118
+ /**
119
+ * 把 `origin`(+`form`)映射成宿主的 `MessageSource`。
120
+ *
121
+ * 🔴 **返回类型显式标注为 `MessageSource`** —— 这是本文件躲开「泛型内联不检查」那个洞的唯一办法。
122
+ *
123
+ * | origin | 宿主 kind | form |
124
+ * |---|---|---|
125
+ * | `user` | `'user'`(宿主那一支,**无 form**) | ⛔ 必须缺席 |
126
+ * | `acc` | `plugin:dsh-serenity-hooks` | 可选 |
127
+ * | `agent`| `plugin:dsh-serenity-hooks` + `form:'relay'` | **re​lay(强制)** |
128
+ */
129
+ export declare function sourceOf(inj: Injection): MessageSource;
130
+ /** 检查一条 `Injection` 是否自洽。不合则抛 —— ⛔ 不用「返回 false 让人忘查」。 */
131
+ export declare function assertInjection(inj: Injection): void;
132
+ /** 构造一条 `Injection`,**归属字段自动推导**(调用点只需给 origin/text/delivery/form)。 */
133
+ export declare function makeInjection(spec: Omit<Injection, 'corpus' | 'userQuote'> & Partial<Pick<Injection, 'corpus' | 'userQuote'>>): Injection;
134
+ /** `deliver` 需要的最小 agent 形状(只取用到的两个方法,便于单测替身)。 */
135
+ export interface DeliverTarget {
136
+ steer?: (msg: UserMessage) => void;
137
+ followup?: (msg: UserMessage) => void;
138
+ }
139
+ /**
140
+ * 把一条 `Injection` 变成**宿主消息对象**,但不投递。
141
+ *
142
+ * 🔴 为什么必须单独有它(而不是让 `deliver` 包办全部 5 种通道):
143
+ * 有三种通道**根本不能由「调一个方法」完成** ——
144
+ * · `waterfall`:要**返回** `{kind:'enter', messages:[…]}` 给宿主;
145
+ * · `additionalContexts`:要**返回**一个数组给下游;
146
+ * · 直写 session event:要 append 一条事件。
147
+ * 这三处的调用方**必须拿到消息对象**。若 `deliver` 把它们也吃掉,就只能靠"回调"骗禁用名,
148
+ * 反而比现在更难读。⇒ **`toMessage` 管"造",`deliver` 管"投"**,两者都过同一个
149
+ * `assertInjection` + `sourceOf`,所以六条轴的取值仍然只有一处定义。
150
+ */
151
+ export declare function toMessage(inj: Injection): UserMessage;
152
+ /**
153
+ * 把一条 `Injection` 投给目标 agent。
154
+ *
155
+ * ⚠️ 只处理**能由「调一个方法」完成**的两种通道(`wake-now`→`steer`、`next-turn`→`followup`)。
156
+ * 另外三种(`this-step` / `face-replace` / waterfall)用 {@link toMessage} 由调用方自己接 ——
157
+ * 它们要么要返回值、要么要 append 事件,硬塞进本函数只会得到更难读的回调。
158
+ * 传进来这两种会**显式抛错**,⛔ 不静默降级成别的通道(失败面清单 #2)。
159
+ *
160
+ * @returns 实际走的通道名
161
+ */
162
+ export declare function deliver(inj: Injection, target: DeliverTarget): 'steer' | 'followup';
@@ -15,11 +15,37 @@
15
15
  * 使同一生产者在旧数据与新数据里**只有一个 kind**(否则下游与我方读取面都要处理两种)。
16
16
  */
17
17
  import type { ContextFormed, MessageSource } from '@deepseek-ai/dsh-llm';
18
+ /**
19
+ * ACC 注入消息在 `source` 上额外携带的字段 —— **DII M3 的落点**(2026-11-04)。
20
+ *
21
+ * ## 为什么必须把 `origin` 投影进 `source`(而不是只留在内存的 Injection 里)
22
+ *
23
+ * `Injection` 是**投递那一刻**的声明对象;而所有消费方(语料、用户语录、面板读数)
24
+ * 读的是**落盘后的会话日志**,那里只有 `source`。⇒ 声明若不落到 `source` 上,
25
+ * 消费方就永远看不见它,M3 也就无从"改读字段、删黑名单"。
26
+ *
27
+ * ## 宿主允许吗(**实测,非推断**)
28
+ *
29
+ * ① `createMessage` 走 `structuredClone(input)` ⇒ 自定义字段被原样保留;
30
+ * ② 落盘校验 `assertMessageEventShape`(`dsh-session/lib/types/index.js:266-271`)
31
+ * 只要求 `source.kind` 是**非空字符串**,**不排斥额外字段**。
32
+ * ⇒ 已用临时探针实测三点:`createUserMessage` 后存活 / 冻结后仍可读 / JSON 往返后仍在。
33
+ *
34
+ * ## 与 `kind` 的关系(I1)
35
+ *
36
+ * `kind` 回答"落点是谁",`origin` 回答"真正的生产者是谁"——**两者刻意可以不等**:
37
+ * 载体开场白与 rebuild 锚点被宿主强制 `kind:'user'`,但它们的 `origin` 是 `'acc'`。
38
+ * 那正是这两个生产者长期"冒充真人"的根源;有了 `origin`,消费方终于能分辨。
39
+ */
40
+ export interface AccOriginField {
41
+ /** 真正的生产者:真人 / ACC / 另一个 agent。 */
42
+ readonly origin?: 'user' | 'acc' | 'agent';
43
+ }
18
44
  declare module '@deepseek-ai/dsh-llm' {
19
45
  interface MessageSourceMap {
20
46
  'plugin:dsh-serenity-hooks': {
21
47
  kind: 'plugin:dsh-serenity-hooks';
22
- } & ContextFormed;
48
+ } & ContextFormed & AccOriginField;
23
49
  }
24
50
  }
25
51
  /** ACC 注入消息的 kind —— 唤醒 / 打回 / keeper 提醒 / 锚定轮 / rebuild 交接等一律同值。 */
@@ -1,7 +1,7 @@
1
1
  import { d as readCccName, i as findGitRoot, l as pathInside, s as loadSerenityConfig, t as DEFAULT_SERENITY_CONFIG_PATHS } from "./ccc-x94FCzOv.js";
2
2
  import { u as ACC_VERSION } from "./rules-DKlpuFN_.js";
3
3
  import { i as readDshVersion, t as hostContractReport, v as checkRegistryQuality } from "./contract-DdI7euZI.js";
4
- import { _ as listWakes, i as wakeSchedulerState } from "./scheduler-CB06u0Qc.js";
4
+ import { _ as listWakes, i as wakeSchedulerState } from "./scheduler-BUM3tE0e.js";
5
5
  import { resolve } from "node:path";
6
6
  import { existsSync, readFileSync, statSync } from "node:fs";
7
7
  //#region src/surface/observation.ts
@@ -2,7 +2,7 @@ import { n as __exportAll } from "./rolldown-runtime-B4iAMlE-.js";
2
2
  import { c as hostAgents, o as listCccs, u as hostService } from "./ccc-roots-CsSSQjSS.js";
3
3
  import { _ as resolveInside } from "./ccc-x94FCzOv.js";
4
4
  import { F as isoLocal, L as localHuman, P as readTrajectoryFile, R as localIdStamp, T as sessionsRoot, b as parseTrajectoryCode, j as TRAJECTORY_FILENAME, k as trajectoryDescOf, p as findSession, v as namingTitleFor } from "./bind-file-B0TuOP89.js";
5
- import { i as carrierState, n as PLUGIN_SOURCE, o as listBoundSessionIds, r as appendBound } from "./message-source-DLei3MqR.js";
5
+ import { c as carrierState, i as toMessage, n as deliver, r as makeInjection, s as appendBound, t as assertInjection, u as listBoundSessionIds } from "./injection-B5jZxemg.js";
6
6
  import { i as sessionsRootDir, n as findSessionLog, r as hasSessionLogById } from "./cleanup-CPl1SSaK.js";
7
7
  import { n as readSimpleSettings } from "./settings-DibHCfEU.js";
8
8
  import { basename, dirname, join } from "node:path";
@@ -418,6 +418,16 @@ function listWakes(root, opts = {}) {
418
418
  * 本文件回答"**怎么造一条载体**"。合在一起会让两条互不相关的依赖链卷成一团。
419
419
  */
420
420
  /**
421
+ * 载体开场白的宿主 `source` —— **`kind:'user'` 是硬要求**(宿主 `dsh-session-title` 的
422
+ * `sessionTitleUserMessageOf()` 第一行就把非 `'user'` 的 source 排除,见文件头注)。
423
+ *
424
+ * 🔴 与 `Injection` 的关系(DII M2 #11):它是**声明 `origin:'acc'` 但落点强制 `'user'`**
425
+ * 的唯一一处 —— `origin` 与宿主 `kind` 在此**刻意不相等**(I1:「映射,不是同义词」)。
426
+ * ⛔ 不要为了"统一"而把它改成 `sourceOf()` 的产出:那会让宿主不再自动命名,
427
+ * 而"让宿主自动命名"正是本文件存在的全部理由(owner 裁决 B)。
428
+ */
429
+ const CARRIER_OPENING_SOURCE = { kind: "user" };
430
+ /**
421
431
  * 模拟开场白的**自证标记**(逐字)。
422
432
  *
423
433
  * 🔴 三个用途,缺一不可:
@@ -471,12 +481,17 @@ function buildCarrierOpening(code, desc) {
471
481
  function deliverCarrierOpening(agent, text) {
472
482
  if (typeof agent.followup !== "function") return false;
473
483
  try {
484
+ assertInjection(makeInjection({
485
+ origin: "acc",
486
+ text,
487
+ delivery: "next-turn"
488
+ }));
474
489
  agent.followup(createUserMessage({
475
490
  content: [{
476
491
  type: "text",
477
492
  text
478
493
  }],
479
- source: { kind: "user" }
494
+ source: CARRIER_OPENING_SOURCE
480
495
  }));
481
496
  return true;
482
497
  } catch {
@@ -2707,31 +2722,6 @@ function createClock(opts) {
2707
2722
  }
2708
2723
  //#endregion
2709
2724
  //#region src/wake/scheduler.ts
2710
- /**
2711
- * wake/scheduler.ts — 认知轨迹唤醒调度器(D58/D59/D60;S142 2026-09-13)
2712
- *
2713
- * 一个**中心调度器**(60s tick,2026-11-02 owner 令「整体都是 60 秒」),处理 CCC 唤醒注册表里
2714
- * 到期的条目:把「未来时刻 + 一条 message」投递给目标认知轨迹,**fire-and-forget**
2715
- * (无阻塞、无等待、无回执——发起方不 await,调度器不替模型结果负责)。
2716
- *
2717
- * 与 autopilot 的关系(D59):**两个 tick 各自独立**。autopilot 是每 CCC 单例的
2718
- * 周期自唤醒,逻辑一行不碰;本模块只处理注册表的一次性唤醒条目。
2719
- *
2720
- * 投递路径(P-1 证据链见 SESSION.md §30.6):
2721
- * 1. target(S### / 目录名)→ 目录名 + SESSION.md
2722
- * 2. 目标 dsh 会话 id:`.bindings.json` 里绑定该目录名的会话(按绑定时间倒序,权威)
2723
- * → 回退:live 会话标题匹配
2724
- * 3. agent:**live 优先**(`ctx.agents.get`)→ 冷会话走 `ctx.sessionController.resolveAgent`
2725
- * (宿主实现:live 优先 + resume 去重 + 由会话元数据恢复 preset)
2726
- * → `sessionController` 缺席 ⇒ 仅投递 live 目标,**不静默**(v1.39.3:**归为"环境未就绪"**
2727
- * 而非"投递失败",见 `WakeDeliveryResult.notReady` —— 启动那一拍早于懒服务就绪是常态)
2728
- * 4. 投递:`agent.followup(...)`("Queue an ordinary follow-up turn and wake the driver")
2729
- *
2730
- * 守卫(2026-09-21 起**本钟无闸**):🔴 **恒武装**——原 `wakeSchedulerEnabled` 已砍,
2731
- * 本钟不再有「可被误关的开关」;旧配置残留该键**静默忽略**,且**不**回退 `autopilotEnabled`|
2732
- * CRO 阶段另有**独立**总闸 `croEnabled`(只停 CRO,**不影响**本注册表投递)|
2733
- * 补跑窗口 2h(超窗置 missed 留痕)|同 tick 串行|唤起后维持 live(人类可介入)。
2734
- */
2735
2725
  var scheduler_exports = /* @__PURE__ */ __exportAll({
2736
2726
  buildCroWakeText: () => buildCroWakeText,
2737
2727
  buildSendText: () => buildSendText,
@@ -2907,13 +2897,13 @@ async function deliverWake(ctx, root, entry) {
2907
2897
  notReady: acquired.notReady === true
2908
2898
  };
2909
2899
  try {
2910
- acquired.agent.followup(createUserMessage({
2911
- content: [{
2912
- type: "text",
2913
- text: buildWakeText(entry, target)
2914
- }],
2915
- source: PLUGIN_SOURCE
2916
- }));
2900
+ deliver(makeInjection({
2901
+ origin: "acc",
2902
+ form: "notice",
2903
+ summary: `到点唤醒(来自 ${entry.target})`,
2904
+ text: buildWakeText(entry, target),
2905
+ delivery: "next-turn"
2906
+ }), acquired.agent);
2917
2907
  } catch (err) {
2918
2908
  return {
2919
2909
  ok: false,
@@ -3012,13 +3002,13 @@ async function sendToCognitiveTrajectory(ctx, root, target, message, sender) {
3012
3002
  };
3013
3003
  }
3014
3004
  const agent = acquired.agent;
3015
- const make = () => createUserMessage({
3016
- content: [{
3017
- type: "text",
3018
- text: buildSendText(t, message, sender)
3019
- }],
3020
- source: PLUGIN_SOURCE
3021
- });
3005
+ const make = () => toMessage(makeInjection({
3006
+ origin: "acc",
3007
+ form: "notice",
3008
+ summary: `即时消息(来自 ${sender ?? "未知"})`,
3009
+ text: buildSendText(t, message, sender),
3010
+ delivery: "wake-now"
3011
+ }));
3022
3012
  const freshCarrier = createdNote !== "";
3023
3013
  if (freshCarrier || agent.status === "idle") {
3024
3014
  try {
@@ -3383,13 +3373,13 @@ async function deliverCroWake(ctx, root, dirName, prompt, reason) {
3383
3373
  notReady: acquired.notReady === true
3384
3374
  };
3385
3375
  try {
3386
- acquired.agent.followup(createUserMessage({
3387
- content: [{
3388
- type: "text",
3389
- text: buildCroWakeText(dirName, mdPath, prompt, reason)
3390
- }],
3391
- source: PLUGIN_SOURCE
3392
- }));
3376
+ deliver(makeInjection({
3377
+ origin: "acc",
3378
+ form: "notice",
3379
+ summary: `CRO 唤起(${dirName})`,
3380
+ text: buildCroWakeText(dirName, mdPath, prompt, reason),
3381
+ delivery: "next-turn"
3382
+ }), acquired.agent);
3393
3383
  } catch (err) {
3394
3384
  return {
3395
3385
  ok: false,
@@ -3681,13 +3671,13 @@ async function runInterruptedSafetyNet(ctx, root, log) {
3681
3671
  const resumeOne = createResumeOne({
3682
3672
  agents,
3683
3673
  sessionController: sc,
3684
- makeMessage: (text) => createUserMessage({
3685
- content: [{
3686
- type: "text",
3687
- text
3688
- }],
3689
- source: PLUGIN_SOURCE
3690
- })
3674
+ makeMessage: (text) => toMessage(makeInjection({
3675
+ origin: "acc",
3676
+ form: "notice",
3677
+ summary: "中断兜底继续",
3678
+ text,
3679
+ delivery: "wake-now"
3680
+ }))
3691
3681
  });
3692
3682
  log.push(`· ${root}: 中断兜底 ${tickTag} → 扫 ${scan.scanned} 个 · 命中 ${scan.sessions.length} · 排除在跑 ${liveIds.size} 个`);
3693
3683
  for (const hit of scan.sessions) try {
@@ -20,6 +20,16 @@ export declare function accIdentityText(root: string, configPaths?: string[], en
20
20
  * 完整身份(ACC 5 块 + CCE + Constraints + EAP + SKILL 全文 + Session 块)由**系统提示词层**
21
21
  * (systemPrompt.section,每轮 prompt 装配自动注入,含 subagent)承担——对话消息流/压缩重注入
22
22
  * 不再重复注入同一内容(token 双倍浪费,见 S134 注入方案梳理)。
23
+ *
24
+ * 🔴 **DII M2 · 注入点 #1**(2026-11-04):改成走 `Injection` 声明层。
25
+ * 六个轴里**新增的是 `form:'instructions'` 与 `origin:'acc'`** —— 这两格以前只活在注释里。
26
+ * ⚠️ **行为零变更**:`sourceOf()` 对 `origin:'acc'` + `form:'instructions'` 产出的
27
+ * `kind` 仍是 `plugin:dsh-serenity-hooks`;只多带一个 `form` 字段(宿主 `ContextFormed` 支持它)。
28
+ *
29
+ * ⚠️ 本函数有**两个调用点**,通道不同(I3 的实例):
30
+ * · `:480` 走 waterfall 返回 —— 无唤醒
31
+ * · `:354` 走 `agent.inject()` —— 排队给下一个 pre-step,**也不唤醒**
32
+ * 两者意图同为 `this-step`,故本函数的 `delivery` 声明成 `this-step`;由调用方各自接通道。
23
33
  */
24
34
  export declare function accMessage(root: string, configPaths: string[], entrySkillMaxChars: number): UserMessage;
25
35
  /**
@@ -1,10 +1,10 @@
1
1
  import { d as hostSessions, r as cccRootForCwd } from "./ccc-roots-CsSSQjSS.js";
2
2
  import { m as readTrajectorySubscriptions } from "./ccc-x94FCzOv.js";
3
3
  import { F as isoLocal, w as sessionEvents } from "./bind-file-B0TuOP89.js";
4
- import { h as WAKE_CATCH_UP_MS, p as listCroCognitiveTrajectories } from "./scheduler-CB06u0Qc.js";
4
+ import { h as WAKE_CATCH_UP_MS, p as listCroCognitiveTrajectories } from "./scheduler-BUM3tE0e.js";
5
5
  import { n as readSimpleSettings } from "./settings-DibHCfEU.js";
6
- import { i as subscriptionStats, r as subscriptionEval } from "./subscriptions-D05XffxT.js";
7
- import { containerClocks, containerWakes } from "./observation-Cgmm6EKE.js";
6
+ import { i as subscriptionStats, r as subscriptionEval } from "./subscriptions-B9w4ppMk.js";
7
+ import { containerClocks, containerWakes } from "./observation-qmSI2bug.js";
8
8
  //#region src/trajectory/live.ts
9
9
  /**
10
10
  * trajectory/live.ts — 进程内 live 会话取数 + 精简诊断
@@ -4,10 +4,9 @@ import { F as isoLocal, T as sessionsRoot, s as DEFAULT_SESSION_SCOPE, x as read
4
4
  import { a as readGitTrack, i as localstorePath } from "./service-Dv-8E2bI.js";
5
5
  import { a as createProfilePromptReader, n as buildTrajectoryBasePrompt, o as profileMsmWhitelist, r as constraintCognitiveTrajectory, u as ACC_VERSION } from "./rules-DKlpuFN_.js";
6
6
  import { d as registerTrajectorySession$1, f as trajectoryDirNameForSession, o as readTrajectoryConstraintAt, p as unregisterTrajectorySession$1, u as trajectoryConstraintForSession } from "./profile-Djqz4Noy.js";
7
- import { n as PLUGIN_SOURCE, s as readLastBound } from "./message-source-DLei3MqR.js";
7
+ import { d as readLastBound, i as toMessage, r as makeInjection } from "./injection-B5jZxemg.js";
8
8
  import { basename, dirname, join, resolve } from "node:path";
9
9
  import { chmodSync, existsSync, mkdirSync, readFileSync, readdirSync, renameSync, statSync, writeFileSync } from "node:fs";
10
- import { createUserMessage } from "@deepseek-ai/dsh-llm";
11
10
  import { createHmac, randomBytes, randomUUID, scryptSync, timingSafeEqual } from "node:crypto";
12
11
  //#region src/trajectory/prompt.ts
13
12
  /**
@@ -2452,13 +2451,13 @@ async function createOrResumeAgent(ctx, id, root, model, setup, fixedId) {
2452
2451
  */
2453
2452
  function deliverToTrajectory(agent, text) {
2454
2453
  const a = agent;
2455
- const msg = createUserMessage({
2456
- content: [{
2457
- type: "text",
2458
- text
2459
- }],
2460
- source: PLUGIN_SOURCE
2461
- });
2454
+ const msg = toMessage(makeInjection({
2455
+ origin: "acc",
2456
+ form: "notice",
2457
+ summary: "IM 入站消息",
2458
+ text,
2459
+ delivery: "wake-now"
2460
+ }));
2462
2461
  if (a.status === "running" && typeof a.steer === "function") {
2463
2462
  a.steer(msg);
2464
2463
  return { how: "steer" };
@@ -1,7 +1,7 @@
1
1
  import { r as cccRootForCwd } from "./ccc-roots-CsSSQjSS.js";
2
2
  import { m as readTrajectorySubscriptions } from "./ccc-x94FCzOv.js";
3
3
  import { L as localHuman, n as findTrajectoryHolding } from "./bind-file-B0TuOP89.js";
4
- import { d as registerDisposer } from "./scheduler-CB06u0Qc.js";
4
+ import { d as registerDisposer } from "./scheduler-BUM3tE0e.js";
5
5
  //#region src/trajectory/subscriptions.ts
6
6
  /**
7
7
  * trajectory/subscriptions.ts — 轨迹消息订阅(v1.107.0,owner 2026-11-01 令)
@@ -306,7 +306,7 @@ function defaultSubscriptionDeps(ctx) {
306
306
  readSubs: (root) => readTrajectorySubscriptions(root),
307
307
  dirForSession: (root, sessionId) => findTrajectoryHolding(`${root}/AGENT_SESSIONS`, sessionId),
308
308
  deliver: async (root, target, message) => {
309
- const { sendToCognitiveTrajectory } = await import("./scheduler-CB06u0Qc.js").then((n) => n.n);
309
+ const { sendToCognitiveTrajectory } = await import("./scheduler-BUM3tE0e.js").then((n) => n.n);
310
310
  const res = await sendToCognitiveTrajectory(ctx, root, target, message, "trajectory-subscription");
311
311
  return {
312
312
  ok: res.ok,
@@ -144,6 +144,12 @@ export interface UserQuoteWriteResult {
144
144
  written: number;
145
145
  /** 因冒充标记被排除几条 */
146
146
  excludedFake: number;
147
+ /**
148
+ * ⚠️ **DII M3 后它只在"旧形态日志"上非零**(2026-11-04):
149
+ * 新代码产的消息都带 `source.origin`,冒充者由结构判据排除(并入 `not-user`);
150
+ * 只有**升级前落盘的旧消息**(没有 origin 字段)才会走到标记兜底并计入这里。
151
+ * 🔴 **所以它是个迁移进度读数**:>0 ⇒ 还有旧形态在场;=0 ⇒ 新判据已全覆盖。
152
+ */
147
153
  /** 因重复 id 被跳过几条 */
148
154
  skippedDuplicate: number;
149
155
  /** 台账日期文件绝对路径(written > 0 时才有意义) */
@@ -195,9 +201,26 @@ export declare function isSubagentSession(session: unknown): boolean;
195
201
  */
196
202
  export declare function messageText(content: readonly ContentBlock[] | undefined): string;
197
203
  /**
198
- * 这条消息**是不是用户直接说的话**(结构性判据 + 冒充标记排除)。
204
+ * 一条消息**是不是用户直接说的话**(结构性判据 + 冒充标记排除)。
199
205
  *
200
- * @returns `'quote'` 收录 / `'not-user'` 不是用户发言(ACC 注入等)/ `'fake-user'` 冒充 user 的生产者
206
+ * ## 🔴 DII M3(2026-11-04):判据升级为**读 `source.origin`**
207
+ *
208
+ * 修前:`kind === 'user'` + **正文标记黑名单**(`CARRIER_OPENING_MARK` / `ASSISTANT_PREFIX`)。
209
+ * 那是"事后辨认"——它不完备:`corpus` 那条链的 20+ 条措辞黑名单就是同一个病(`heads` 子命令
210
+ * 因此成了"量漏网家族"的工具)。
211
+ *
212
+ * 修后:**先读 `source.origin`**(DII 把它投影进 source,见 `kernel/message-source.ts`):
213
+ * | `origin` | 含义 | 结论 |
214
+ * |---|---|---|
215
+ * | 缺席 + `kind==='user'` | **真人直说**(ACC 的 `sourceOf` 对 origin:'user' **刻意不写该字段**) | `quote` |
216
+ * | `'acc'` | ACC 产的(唤醒/提醒/开场白/rebuild 锚点…) | `not-user` |
217
+ * | `'agent'` | 另一个 agent 产的(subagent 任务书等) | `not-user` |
218
+ *
219
+ * ⇒ **两个长期"冒充真人"的生产者(载体开场白/rebuild 锚点)现在出生即分格**,
220
+ * 不再需要正文标记参与判定。见下方 `USER_QUOTE_FAKE_MARKERS` 的处置说明。
221
+ *
222
+ * @returns `'quote'` 收录 / `'not-user'` 不是用户发言(ACC 注入等)/
223
+ * `'fake-user'` **未带 origin 的旧日志里**靠正文标记认出是冒充者(过渡期兜底)
201
224
  */
202
225
  export declare function classifyUserMessage(source: unknown, text: string): 'quote' | 'not-user' | 'fake-user';
203
226
  /** 本地时区 ISO8601(与 `cro/log.ts` 等处同款口径:不引第三方库,用 Date 的本地字段) */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@shgroup/dsh-serenity-hooks",
3
- "version": "1.113.2",
3
+ "version": "1.114.0",
4
4
  "description": "宁静号 ACC harness(Native Cordis 插件)——给 DeepSeek Harness 装一个「AI 工作区」:12 个工具(container_fs/cognitive_trajectory/dashboard/container_git/msm/praxis/ontology/serenity-skill-creator/localstore/container_admin;另两个 im-bridge/diagram 按配置条件出现)+ 机械约束(安全模式/工作区围墙/密钥守卫/对外输出守卫)+ 认知轨迹日志与原地重建 + 网页登录入口/IM 桥/认知轨迹唤醒注册表。轨迹声明 = 轨迹目录下的 trajectory.json(约束面 + 旧标识 uid + CRO 机器绑定 cro.host;旧名 trajectory-constraint.json / skiff.json 仍在读取族里);轨迹提示词 = 轨迹目录下的 PROMPT.md(一条轨迹「是谁、按什么章程工作」,所有轨迹通用,追加在容器层与质量层之后,不替换它们)。适配 DSH 0.1.7-rc.1 / 0.2.0-rc.1 / 0.2.1-rc.1(三档)。",
5
5
  "license": "MIT",
6
6
  "repository": {