@shgroup/dsh-serenity-hooks 1.68.0 → 1.69.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.68.0",
3
+ "version": "1.69.0",
4
4
  "main": "lib/index.js",
5
5
  "description": "宁静号 ACC harness(Native Cordis 插件):给 DSH 装一个「AI 工作区」——14 个工具(container_fs/cognitive_trajectory/dashboard/container_git/msm/praxis/ontology/serenity-skill-creator/cheap-worker/localstore/container_admin;另三个 im-bridge/acc-diag/diagram 按配置条件出现)+ 机械约束(安全模式/工作区围墙/密钥守卫)+ 认知轨迹日志与原地重建 + 网页登录入口/微信桥/子角色/官方 ACP 面/认知轨迹唤醒注册表。适配 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/api.d.ts CHANGED
@@ -34,11 +34,34 @@ interface StatusApiRegistration {
34
34
  configPaths?: string[];
35
35
  }
36
36
  /**
37
- * 从请求参数解析 workspace:优先 sessionId → 会话 header.cwd;其次 workspace 参数;
38
- * 无参 → 遍历 live sessions 找第一个 CCC 会话的 cwd(远程访问时 sessionId 可能缺失,
39
- * 回退 process.cwd()=$HOME 会错误显示"未激活"——v1.22.2 修复);最后进程 cwd。
40
- * 纯逻辑(可单测):resolveWorkspaceCore(sessionCwd, workspaceParam, listCwds)。
37
+ * 从请求参数解析 workspace:sessionId → 会话 header.cwd;其次 workspace 参数;
38
+ * 再次**唯一**的 CCC 会话 cwd;最后进程 cwd。
39
+ *
40
+ * 🔴🔴 **2026-10-18 修缺陷(owner 报「在别的 CCC 点开显示的还是 home-serenity 的信息」)**:
41
+ * 旧实现第 ③ 步是「**遍历 live sessions 找第一个 CCC 会话的 cwd**」—— 那是**跨工作区猜测**:
42
+ * 只要 sessionId 缺失或那条会话查不到(ACP/skiff 会话、尚未 materialize 的会话、远程访问),
43
+ * 它就会**静默返回另一个 CCC 的路径**,于是面板把**别人的轨迹/技能/安全模式**当成你的显示出来。
44
+ * 这比"显示未激活"危险得多 —— 用户会据此做判断(以为自己的 safe-mode 开着、以为那是自己的轨迹)。
45
+ *
46
+ * 修法与既有先例一致(`rebuild.ts` 对"跨轨迹猜测"的裁决:**响亮失败优于静默接错**):
47
+ * **多个候选 ⇒ 不猜**。只有当 live sessions 里的 CCC 根**唯一**时才采用它
48
+ * (那正是 v1.22.2 要救的场景:单 CCC 部署 + 远程访问缺 sessionId);
49
+ * 一旦出现**两个及以上**不同 CCC 根,就退回进程 cwd(→ 面板显示"未激活"),绝不挑一个顶替。
50
+ *
51
+ * @param sessionCwd sessionId 查到的会话 cwd(最权威)
52
+ * @param workspaceParam 显式 workspace 参数
53
+ * @param listCwds 枚举 live sessions 的 cwd(用于"唯一 CCC"兜底)
54
+ * @param fallback 最后兜底(进程 cwd)
41
55
  */
42
56
  export declare function resolveWorkspaceCore(sessionCwd: string | undefined, workspaceParam: string | undefined, listCwds: () => string[], fallback: string): string;
57
+ /**
58
+ * 解析本次请求所属的 workspace。
59
+ * 🔴 2026-10-18:导出给 `registerVoyageApi` 复用(航行页词表按当前 CCC 注入)——
60
+ * **单一真相源**,⛔ 不让调用方另写一份"找 CCC"的逻辑。
61
+ */
62
+ export declare function resolveWorkspace(ctx: Context, params: {
63
+ sessionId?: string;
64
+ workspace?: string;
65
+ }): string;
43
66
  export declare function registerStatusApi(ctx: Context, opts?: StatusApiRegistration): void;
44
67
  export {};
package/lib/client.js CHANGED
@@ -262,11 +262,11 @@ window.__ModuleLoader__.load({
262
262
  className: cx("sp-voyage"),
263
263
  "aria-hidden": "true",
264
264
  children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("iframe", {
265
- src: `/serenity/voyage?card=1&v=${encodeURIComponent(status.accVersion)}`,
265
+ src: `/serenity/voyage?card=1&v=${encodeURIComponent(status.accVersion)}${status.root ? `&workspace=${encodeURIComponent(status.root)}` : ""}`,
266
266
  title: "",
267
267
  loading: "lazy",
268
268
  tabIndex: -1
269
- }, status.accVersion)
269
+ }, `${status.accVersion}|${status.root ?? ""}`)
270
270
  })]
271
271
  })]
272
272
  });
package/lib/index.js CHANGED
@@ -15117,17 +15117,42 @@ function requireWebUi(req, res, message) {
15117
15117
  return false;
15118
15118
  }
15119
15119
  /**
15120
- * 从请求参数解析 workspace:优先 sessionId → 会话 header.cwd;其次 workspace 参数;
15121
- * 无参 → 遍历 live sessions 找第一个 CCC 会话的 cwd(远程访问时 sessionId 可能缺失,
15122
- * 回退 process.cwd()=$HOME 会错误显示"未激活"——v1.22.2 修复);最后进程 cwd。
15123
- * 纯逻辑(可单测):resolveWorkspaceCore(sessionCwd, workspaceParam, listCwds)。
15120
+ * 从请求参数解析 workspace:sessionId → 会话 header.cwd;其次 workspace 参数;
15121
+ * 再次**唯一**的 CCC 会话 cwd;最后进程 cwd。
15122
+ *
15123
+ * 🔴🔴 **2026-10-18 修缺陷(owner 报「在别的 CCC 点开显示的还是 home-serenity 的信息」)**:
15124
+ * 旧实现第 ③ 步是「**遍历 live sessions 找第一个 CCC 会话的 cwd**」—— 那是**跨工作区猜测**:
15125
+ * 只要 sessionId 缺失或那条会话查不到(ACP/skiff 会话、尚未 materialize 的会话、远程访问),
15126
+ * 它就会**静默返回另一个 CCC 的路径**,于是面板把**别人的轨迹/技能/安全模式**当成你的显示出来。
15127
+ * 这比"显示未激活"危险得多 —— 用户会据此做判断(以为自己的 safe-mode 开着、以为那是自己的轨迹)。
15128
+ *
15129
+ * 修法与既有先例一致(`rebuild.ts` 对"跨轨迹猜测"的裁决:**响亮失败优于静默接错**):
15130
+ * **多个候选 ⇒ 不猜**。只有当 live sessions 里的 CCC 根**唯一**时才采用它
15131
+ * (那正是 v1.22.2 要救的场景:单 CCC 部署 + 远程访问缺 sessionId);
15132
+ * 一旦出现**两个及以上**不同 CCC 根,就退回进程 cwd(→ 面板显示"未激活"),绝不挑一个顶替。
15133
+ *
15134
+ * @param sessionCwd sessionId 查到的会话 cwd(最权威)
15135
+ * @param workspaceParam 显式 workspace 参数
15136
+ * @param listCwds 枚举 live sessions 的 cwd(用于"唯一 CCC"兜底)
15137
+ * @param fallback 最后兜底(进程 cwd)
15124
15138
  */
15125
15139
  function resolveWorkspaceCore(sessionCwd, workspaceParam, listCwds, fallback) {
15126
15140
  if (sessionCwd) return sessionCwd;
15127
15141
  if (workspaceParam) return workspaceParam;
15128
- for (const cwd of listCwds()) if (cwd && cccRootForCwd(cwd)) return cwd;
15142
+ const roots = /* @__PURE__ */ new Set();
15143
+ for (const cwd of listCwds()) {
15144
+ if (!cwd) continue;
15145
+ const r = cccRootForCwd(cwd);
15146
+ if (r) roots.add(r);
15147
+ }
15148
+ if (roots.size === 1) return [...roots][0];
15129
15149
  return fallback;
15130
15150
  }
15151
+ /**
15152
+ * 解析本次请求所属的 workspace。
15153
+ * 🔴 2026-10-18:导出给 `registerVoyageApi` 复用(航行页词表按当前 CCC 注入)——
15154
+ * **单一真相源**,⛔ 不让调用方另写一份"找 CCC"的逻辑。
15155
+ */
15131
15156
  function resolveWorkspace(ctx, params) {
15132
15157
  let sessionCwd;
15133
15158
  if (params.sessionId) sessionCwd = (hostSessions(ctx)?.get?.(params.sessionId))?.header?.cwd;
@@ -15652,6 +15677,126 @@ function registerAcpAccountsApi(ctx) {
15652
15677
  });
15653
15678
  }
15654
15679
  //#endregion
15680
+ //#region src/voyage-vocab.ts
15681
+ /**
15682
+ * voyage-vocab.ts — 航行页词表改为**按当前 CCC 运行时注入**(纯逻辑,零 DSH 依赖)
15683
+ *
15684
+ * ## 为什么需要它(owner 2026-10-18 报的缺陷)
15685
+ * 资产 `assets/serenity-voyage.html` 的词表是**构建期扫描容器写死的常量**:
15686
+ * `var VOCAB = {"skills":[...],"msms":[...],"zh":[...],"onto":[...],"counts":{...}}`
15687
+ * v2 改版把"写死名单"改成"构建期扫描"是为防名单漂移——方向对,但代价是:
15688
+ * **一份静态资产只能属于一个 CCC**。于是任何别的 CCC 打开动画,看到的都是
15689
+ * **构建它的那个 CCC** 的技能/工具/本体名字与计数。owner 原话:
15690
+ * 「为什么我在别的 CCC 点开显示的还是我们 home-serenity 的信息」「动画也有这个毛病」。
15691
+ *
15692
+ * ## 修法(owner 裁决:运行时按当前 CCC 注入)
15693
+ * 资产**保持静态**(一份文件服务所有 CCC,永不漂);由它的**服务端路由**在返回前
15694
+ * 把 `var VOCAB = {...};` 那一行替换成从该请求所属 CCC 实况读出的词表。
15695
+ *
15696
+ * 三条判据(R↓):
15697
+ * ① **只替换那一行**(锚 = 行首 `var VOCAB = ` 到行尾 `;`)—— 资产里其余 3000+ 行
15698
+ * three.js / 渲染代码**一个字节都不动**。
15699
+ * ② **读不出来就走原样**:锚认不出、或原 VOCAB 解析失败 ⇒ **返回入参本身**,不报错。
15700
+ * 航行页是**装饰**,不该因为某个 CCC 缺目录、或资产结构变了就打不开(旁路容忍铁律)。
15701
+ * ③ **`zh` 不来自容器**:中文隐喻词是作品的语汇装饰,属资产自身 ⇒ 注入时**保留原值**。
15702
+ * 实现上不"重建"整个对象,而是**在原对象上覆盖**四个实况键 —— 未列出的键自动保留。
15703
+ */
15704
+ /** 资产里那一行的锚 —— `var VOCAB = {...};`(`.*?` 非贪婪 + 行尾分号) */
15705
+ const VOCAB_LINE_RE = /^var VOCAB = (\{.*\});[ \t]*$/m;
15706
+ /**
15707
+ * 扫描一个 CCC 的实况词表。**每一项独立容错**:任一来源缺失/读坏 ⇒ 该项为空,其余照常。
15708
+ *
15709
+ * | 字段 | 来源 | 为什么是它 |
15710
+ * |---|---|---|
15711
+ * | `skills` | `.opencode/skills/` 下含 `SKILL.md` 的目录名 | 与状态面板同一条判据 |
15712
+ * | `msms` | `mech-registry.json` 的注册名 | **MSM 的唯一真相源** |
15713
+ * | `onto` | `.opencode/skills/` 下含 `skill.ttl` 的目录名 | 增强型(建模过)的 skill |
15714
+ * | `counts` | 上述长度 + 轨迹数 | 计数与词表同源,否则 HUD 与星场对不上 |
15715
+ *
15716
+ * @param root CCC 根
15717
+ */
15718
+ function scanVoyageLiveVocab(root) {
15719
+ const skills = scanSkillDirs(root, "SKILL.md");
15720
+ const onto = scanSkillDirs(root, "skill.ttl");
15721
+ const msms = scanMsmNames(root);
15722
+ return {
15723
+ skills,
15724
+ msms,
15725
+ onto,
15726
+ counts: {
15727
+ skill: skills.length,
15728
+ msm: msms.length,
15729
+ onto: onto.length,
15730
+ session: scanTrajectoryCount(root)
15731
+ }
15732
+ };
15733
+ }
15734
+ /** 技能名:`.opencode/skills/<name>/<marker>` 存在的目录名(marker = `SKILL.md` 或 `skill.ttl`) */
15735
+ function scanSkillDirs(root, marker) {
15736
+ const dir = join(root, ".opencode", "skills");
15737
+ try {
15738
+ if (!existsSync(dir)) return [];
15739
+ return readdirSync(dir, { withFileTypes: true }).filter((e) => e.isDirectory()).filter((e) => existsSync(join(dir, e.name, marker))).map((e) => e.name).sort();
15740
+ } catch {
15741
+ return [];
15742
+ }
15743
+ }
15744
+ /**
15745
+ * MSM 名:读 `mech-registry.json` 的 `msms[].name`。
15746
+ *
15747
+ * 🔴 **为什么不扫 `scripts/`**:注册表是 MSM 的**唯一真相源**;扫目录会把共享库
15748
+ * (`dev-kit/`、`lib/*`)也算成 MSM —— 本仓对"入口判据"已翻车多次。
15749
+ * ⚠️ 注册表若换了位置 ⇒ **静默降级为空**(装饰页不该因此打不开)。
15750
+ */
15751
+ function scanMsmNames(root) {
15752
+ const candidates = [join(root, ".opencode", "skills", "home-serenity", "references", "mech-registry.json"), join(root, ".opencode", "references", "mech-registry.json")];
15753
+ for (const p of candidates) try {
15754
+ if (!existsSync(p)) continue;
15755
+ const names = (JSON.parse(readFileSync(p, "utf-8")).msms ?? []).map((m) => typeof m?.name === "string" ? m.name : "").filter((n) => n !== "");
15756
+ if (names.length > 0) return [...new Set(names)].sort();
15757
+ } catch {}
15758
+ return [];
15759
+ }
15760
+ /** 轨迹数:`AGENT_SESSIONS/` 下非 `_` / `.` 前缀的目录(与轨迹层"活跃"判据一致) */
15761
+ function scanTrajectoryCount(root) {
15762
+ const dir = join(root, "AGENT_SESSIONS");
15763
+ try {
15764
+ if (!existsSync(dir)) return 0;
15765
+ return readdirSync(dir, { withFileTypes: true }).filter((e) => e.isDirectory()).filter((e) => !e.name.startsWith("_") && !e.name.startsWith(".")).length;
15766
+ } catch {
15767
+ return 0;
15768
+ }
15769
+ }
15770
+ /**
15771
+ * 把实况词表注入资产 HTML。
15772
+ *
15773
+ * 做法 = **在原 VOCAB 对象上覆盖**四个实况键(不重建整个对象)⇒ `zh` 等装饰键自动保留。
15774
+ * 任一环节不成立就**原样返回**(见文件头判据②),因此:
15775
+ * @returns 注入后的 HTML;未注入时**与入参逐字节相同**
15776
+ */
15777
+ function injectVoyageVocab(html, live) {
15778
+ if (live === null) return html;
15779
+ const m = VOCAB_LINE_RE.exec(html);
15780
+ if (!m) return html;
15781
+ let base;
15782
+ try {
15783
+ base = JSON.parse(m[1]);
15784
+ } catch {
15785
+ return html;
15786
+ }
15787
+ const merged = {
15788
+ ...base,
15789
+ skills: live.skills,
15790
+ msms: live.msms,
15791
+ onto: live.onto,
15792
+ counts: {
15793
+ ...typeof base.counts === "object" && base.counts !== null ? base.counts : {},
15794
+ ...live.counts
15795
+ }
15796
+ };
15797
+ return html.replace(VOCAB_LINE_RE, () => `var VOCAB = ${JSON.stringify(merged)};`);
15798
+ }
15799
+ //#endregion
15655
15800
  //#region src/voyage-page.ts
15656
15801
  const VOYAGE_PATH = "/serenity/voyage";
15657
15802
  /**
@@ -15688,6 +15833,9 @@ function sendJson$2(res, status, body) {
15688
15833
  /**
15689
15834
  * 注册 `/serenity/voyage`(GET)。
15690
15835
  * @param opts.assetPath 覆盖资产路径(测试用;生产不传)
15836
+ * @param opts.resolveRoot 解析本次请求所属 CCC 根(生产由 api.ts 的 resolveWorkspace 提供)。
15837
+ * 🔴 **2026-10-18 新增**:词表按请求所属 CCC **运行时注入**(owner 报「在别的 CCC 点开
15838
+ * 显示的还是 home-serenity 的信息」)。不传 ⇒ 不注入(保持资产原样 = 旧行为)。
15691
15839
  */
15692
15840
  function registerVoyageApi(ctx, opts = {}) {
15693
15841
  ctx.webServer.register({
@@ -15705,9 +15853,18 @@ function registerVoyageApi(ctx, opts = {}) {
15705
15853
  sendJson$2(res, 500, { error: `voyage asset unavailable: ${err?.message ?? String(err)}` });
15706
15854
  return;
15707
15855
  }
15856
+ let root = null;
15857
+ try {
15858
+ root = opts.resolveRoot?.(req) ?? null;
15859
+ } catch {
15860
+ root = null;
15861
+ }
15862
+ if (root !== null) try {
15863
+ html = injectVoyageVocab(html, scanVoyageLiveVocab(root));
15864
+ } catch {}
15708
15865
  res.statusCode = 200;
15709
15866
  res.setHeader("content-type", "text/html; charset=utf-8");
15710
- res.setHeader("cache-control", "private, max-age=86400");
15867
+ res.setHeader("cache-control", root !== null ? "no-cache" : "private, max-age=86400");
15711
15868
  res.setHeader("content-length", String(Buffer.byteLength(html)));
15712
15869
  res.end(html);
15713
15870
  }
@@ -25465,7 +25622,14 @@ function apply(ctx, config) {
25465
25622
  });
25466
25623
  if (config.api) {
25467
25624
  registerStatusApi(ctx, { configPaths: config.serenityConfigPaths });
25468
- registerVoyageApi(ctx);
25625
+ registerVoyageApi(ctx, { resolveRoot: (req) => {
25626
+ const url = new URL(req.url ?? "/", "http://localhost");
25627
+ const ws = resolveWorkspace(ctx, {
25628
+ sessionId: url.searchParams.get("sessionId") ?? void 0,
25629
+ workspace: url.searchParams.get("workspace") ?? void 0
25630
+ });
25631
+ return cccRootForCwd(ws);
25632
+ } });
25469
25633
  }
25470
25634
  registerSettingsSection(ctx, config);
25471
25635
  if (config.tools) registerDiagramTool(ctx);
@@ -21,6 +21,7 @@
21
21
  * 技能/公理/MSM/本体词元与实测容器规模数字。
22
22
  */
23
23
  import type { Context } from 'cordis';
24
+ import type { IncomingMessage } from 'node:http';
24
25
  export declare const VOYAGE_PATH = "/serenity/voyage";
25
26
  /**
26
27
  * 资产相对包根的路径。
@@ -38,7 +39,11 @@ export declare function resetVoyageCache(): void;
38
39
  /**
39
40
  * 注册 `/serenity/voyage`(GET)。
40
41
  * @param opts.assetPath 覆盖资产路径(测试用;生产不传)
42
+ * @param opts.resolveRoot 解析本次请求所属 CCC 根(生产由 api.ts 的 resolveWorkspace 提供)。
43
+ * 🔴 **2026-10-18 新增**:词表按请求所属 CCC **运行时注入**(owner 报「在别的 CCC 点开
44
+ * 显示的还是 home-serenity 的信息」)。不传 ⇒ 不注入(保持资产原样 = 旧行为)。
41
45
  */
42
46
  export declare function registerVoyageApi(ctx: Context, opts?: {
43
47
  assetPath?: string;
48
+ resolveRoot?: (req: IncomingMessage) => string | null;
44
49
  }): void;
@@ -0,0 +1,51 @@
1
+ /**
2
+ * voyage-vocab.ts — 航行页词表改为**按当前 CCC 运行时注入**(纯逻辑,零 DSH 依赖)
3
+ *
4
+ * ## 为什么需要它(owner 2026-10-18 报的缺陷)
5
+ * 资产 `assets/serenity-voyage.html` 的词表是**构建期扫描容器写死的常量**:
6
+ * `var VOCAB = {"skills":[...],"msms":[...],"zh":[...],"onto":[...],"counts":{...}}`
7
+ * v2 改版把"写死名单"改成"构建期扫描"是为防名单漂移——方向对,但代价是:
8
+ * **一份静态资产只能属于一个 CCC**。于是任何别的 CCC 打开动画,看到的都是
9
+ * **构建它的那个 CCC** 的技能/工具/本体名字与计数。owner 原话:
10
+ * 「为什么我在别的 CCC 点开显示的还是我们 home-serenity 的信息」「动画也有这个毛病」。
11
+ *
12
+ * ## 修法(owner 裁决:运行时按当前 CCC 注入)
13
+ * 资产**保持静态**(一份文件服务所有 CCC,永不漂);由它的**服务端路由**在返回前
14
+ * 把 `var VOCAB = {...};` 那一行替换成从该请求所属 CCC 实况读出的词表。
15
+ *
16
+ * 三条判据(R↓):
17
+ * ① **只替换那一行**(锚 = 行首 `var VOCAB = ` 到行尾 `;`)—— 资产里其余 3000+ 行
18
+ * three.js / 渲染代码**一个字节都不动**。
19
+ * ② **读不出来就走原样**:锚认不出、或原 VOCAB 解析失败 ⇒ **返回入参本身**,不报错。
20
+ * 航行页是**装饰**,不该因为某个 CCC 缺目录、或资产结构变了就打不开(旁路容忍铁律)。
21
+ * ③ **`zh` 不来自容器**:中文隐喻词是作品的语汇装饰,属资产自身 ⇒ 注入时**保留原值**。
22
+ * 实现上不"重建"整个对象,而是**在原对象上覆盖**四个实况键 —— 未列出的键自动保留。
23
+ */
24
+ /** 从 CCC 实况读出的词表(只含**会变**的四个键;`zh` 与其它装饰键不在其中) */
25
+ export interface VoyageLiveVocab {
26
+ skills: string[];
27
+ msms: string[];
28
+ onto: string[];
29
+ counts: Record<string, number>;
30
+ }
31
+ /**
32
+ * 扫描一个 CCC 的实况词表。**每一项独立容错**:任一来源缺失/读坏 ⇒ 该项为空,其余照常。
33
+ *
34
+ * | 字段 | 来源 | 为什么是它 |
35
+ * |---|---|---|
36
+ * | `skills` | `.opencode/skills/` 下含 `SKILL.md` 的目录名 | 与状态面板同一条判据 |
37
+ * | `msms` | `mech-registry.json` 的注册名 | **MSM 的唯一真相源** |
38
+ * | `onto` | `.opencode/skills/` 下含 `skill.ttl` 的目录名 | 增强型(建模过)的 skill |
39
+ * | `counts` | 上述长度 + 轨迹数 | 计数与词表同源,否则 HUD 与星场对不上 |
40
+ *
41
+ * @param root CCC 根
42
+ */
43
+ export declare function scanVoyageLiveVocab(root: string): VoyageLiveVocab;
44
+ /**
45
+ * 把实况词表注入资产 HTML。
46
+ *
47
+ * 做法 = **在原 VOCAB 对象上覆盖**四个实况键(不重建整个对象)⇒ `zh` 等装饰键自动保留。
48
+ * 任一环节不成立就**原样返回**(见文件头判据②),因此:
49
+ * @returns 注入后的 HTML;未注入时**与入参逐字节相同**
50
+ */
51
+ export declare function injectVoyageVocab(html: string, live: VoyageLiveVocab | null): string;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@shgroup/dsh-serenity-hooks",
3
- "version": "1.68.0",
3
+ "version": "1.69.0",
4
4
  "description": "宁静号 ACC harness(Native Cordis 插件)——给 DeepSeek Harness 装一个「AI 工作区」:14 个工具(container_fs/cognitive_trajectory/dashboard/container_git/msm/praxis/ontology/serenity-skill-creator/cheap-worker/localstore/container_admin;另三个 im-bridge/acc-diag/diagram 按配置条件出现)+ 机械约束(安全模式/工作区围墙/密钥守卫/对外输出守卫)+ 认知轨迹日志与原地重建 + 网页登录入口/微信桥/子角色/官方 ACP 面/认知轨迹唤醒注册表。适配 DSH 0.1.7-rc.1 / 0.2.0-rc.1 / 0.2.1-rc.1(三档)。",
5
5
  "license": "MIT",
6
6
  "repository": {