@shgroup/dsh-serenity-hooks 1.51.0 → 1.51.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -80,7 +80,7 @@ dsh plugin --profile web add link:$(pwd)/hooks/dsh-serenity-hooks
80
80
  | `container_admin` | 机务舱:管理子角色、管理小工具注册表、查看全部配置 | 定义"子角色"、注册新小工具、改配置时 |
81
81
  | `im-bridge` | 给 IM 联系人发消息(目前是微信):发文本、发文件、查已配置联系人、查通道状态。每次成功发送自动进工作区的消息记录 | 想让 AI 主动给家人/同事发消息(见 §6.6)。**只在工作区配了微信桥时才出现**,且只能发本工作区的消息 |
82
82
  | `acc-diag` | ACC 运行态诊断:一次调用出全报告——当前有多少会话活着、各自属于哪个工作区、唤醒时钟的武装态与 tick 次数、唤醒登记表的每一条(含状态 / 投递结果 / 补跑窗口) | **ACC 维护者专用**。**默认对所有工作区隐藏**,只有在工作区配置里点名(`exclusiveTools`)才出现 |
83
- | `diagram` | 画图:AI 给一段**标签式源码**(`<diagram>` 根 + `<rect>` / `<arrow>` / `<text>` / `<color>` / `<legend>`),插件**在进程内**把它画成 PNG 并贴进对话——**不启浏览器、不截图**。同时落一份 `.svg`(可读可改)与一份 `.png` 在工作区的 `_tmp/diagram/`。自带四项体检(块重叠 / 标签溢出 / 悬空端点 / 线压字),**只告警不拦图**;写错的图元被跳过并逐条报错,其余照画 | 想把关系图/示意图直接放进对话时。**实验性 demo,默认关**:关着的时候这个工具**根本不注册**(AI 看不到它,不占上下文);开关在设置面板「工具」组。中文能画,是因为字体**随插件内置**(约 3.8 MB) |
83
+ | `diagram` | 画图:AI 给一段**标签式源码**(`<diagram>` 根 + `<rect>` / `<arrow>` / `<text>` / `<color>` / `<legend>`),插件**在进程内**把它画成 PNG 并**贴进对话**(可回看、可被后续轮次引用)——**不启浏览器、不截图、正常路径不落文件**。坐标 = **画布像素、y 向下**(`domain="x0,y0,x1,y1"` 可选);写错的图元**逐条报错并跳过**,其余照画。自带四项体检(块重叠 / 标签溢出 / 悬空端点 / 线压字),**只告警不拦图** | 想把关系图/示意图直接放进对话时。**实验性 demo,默认关**:关着的时候这个工具**根本不注册**(AI 看不到它,不占上下文);开关在设置面板「工具」组,**拨动即时生效**(不必重启)。中英文与常用符号(`①` `⇒` `→` `★`)都能画,是因为**三个字型随插件内置**(汉字 + 拉丁 + 符号,合计约 5 MB) |
84
84
 
85
85
  > **改过名**(旧名已彻底停用,没有兼容别名):下面每组的箭头链是**逐个发布版本**的名字,**末项才是今名**——`cc_fs` → `container_fs` · `cc_git` → `container_git` · `session`+`session_rebuild` → `logbook`(v1.30/1.31)→ `trajectory`(v1.32)→ **`container_trajectory`**(v1.34,今名) · `acc_kit` → `dashboard` · `acc_msm` → `msm`(执行)+ `container_admin`(管理)· `eap`/`neat`/`cce` → `praxis` · `skiff_admin` → `container_admin role` · `autopilot-trajectory` → `trajectory`(v1.32)→ **`container_trajectory`**(v1.34,今名)。老会话里看到旧名,**一律取所在那一组的末项**。
86
86
 
Binary file
@@ -0,0 +1,78 @@
1
+ Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/
2
+ Upstream-Name: DejaVu fonts
3
+ Upstream-Author: Stepan Roh <src@users.sourceforge.net> (original author),
4
+ see /usr/share/doc/fonts-dejavu-core/AUTHORS for full list
5
+ Source: https://dejavu-fonts.github.io/
6
+
7
+ Files: *
8
+ Copyright: Copyright (c) 2003 by Bitstream, Inc. All Rights Reserved.
9
+ Bitstream Vera is a trademark of Bitstream, Inc.
10
+ DejaVu changes are in public domain.
11
+ License: bitstream-vera
12
+ Permission is hereby granted, free of charge, to any person obtaining a copy
13
+ of the fonts accompanying this license ("Fonts") and associated
14
+ documentation files (the "Font Software"), to reproduce and distribute the
15
+ Font Software, including without limitation the rights to use, copy, merge,
16
+ publish, distribute, and/or sell copies of the Font Software, and to permit
17
+ persons to whom the Font Software is furnished to do so, subject to the
18
+ following conditions:
19
+ .
20
+ The above copyright and trademark notices and this permission notice shall
21
+ be included in all copies of one or more of the Font Software typefaces.
22
+ .
23
+ The Font Software may be modified, altered, or added to, and in particular
24
+ the designs of glyphs or characters in the Fonts may be modified and
25
+ additional glyphs or characters may be added to the Fonts, only if the fonts
26
+ are renamed to names not containing either the words "Bitstream" or the word
27
+ "Vera".
28
+ .
29
+ This License becomes null and void to the extent applicable to Fonts or Font
30
+ Software that has been modified and is distributed under the "Bitstream
31
+ Vera" names.
32
+ .
33
+ The Font Software may be sold as part of a larger software package but no
34
+ copy of one or more of the Font Software typefaces may be sold by itself.
35
+ .
36
+ THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
37
+ OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF MERCHANTABILITY,
38
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF COPYRIGHT, PATENT,
39
+ TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL BITSTREAM OR THE GNOME
40
+ FOUNDATION BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, INCLUDING
41
+ ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL DAMAGES,
42
+ WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF
43
+ THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM OTHER DEALINGS IN THE
44
+ FONT SOFTWARE.
45
+ .
46
+ Except as contained in this notice, the names of Gnome, the Gnome
47
+ Foundation, and Bitstream Inc., shall not be used in advertising or
48
+ otherwise to promote the sale, use or other dealings in this Font Software
49
+ without prior written authorization from the Gnome Foundation or Bitstream
50
+ Inc., respectively. For further information, contact: fonts at gnome dot
51
+ org.
52
+
53
+ Files: debian/*
54
+ Copyright: (C) 2005-2006 Peter Cernak <pce@users.sourceforge.net>
55
+ (C) 2006-2011 Davide Viti <zinosat@tiscali.it>
56
+ (C) 2011-2013 Christian Perrier <bubulle@debian.org>
57
+ (C) 2013 Fabian Greffrath <fabian+debian@greffrath.com>
58
+ License: GPL-2+
59
+ This program is free software; you can redistribute it
60
+ and/or modify it under the terms of the GNU General Public
61
+ License as published by the Free Software Foundation; either
62
+ version 2 of the License, or (at your option) any later
63
+ version.
64
+ .
65
+ This program is distributed in the hope that it will be
66
+ useful, but WITHOUT ANY WARRANTY; without even the implied
67
+ warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
68
+ PURPOSE. See the GNU General Public License for more
69
+ details.
70
+ .
71
+ You should have received a copy of the GNU General Public
72
+ License along with this package; if not, write to the Free
73
+ Software Foundation, Inc., 51 Franklin St, Fifth Floor,
74
+ Boston, MA 02110-1301 USA
75
+ .
76
+ On Debian systems, the full text of the GNU General Public
77
+ License version 2 can be found in the file
78
+ /usr/share/common-licenses/GPL-2'.
@@ -0,0 +1,155 @@
1
+ Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/
2
+ Upstream-Name: Noto
3
+ Upstream-Contact: https://github.com/googlei18n/noto-fonts/issues
4
+ http://groups.google.com/group/noto-font
5
+ Source: https://github.com/googlei18n/noto-fonts
6
+
7
+ Files: *
8
+ Copyright:
9
+ 2010,2012-2020, Google Inc.
10
+ 2015-2020, Google LLC.
11
+ License-Grant:
12
+ This Font Software is licensed under the SIL Open Font License,
13
+ Version 1.1.
14
+ License: OFL-1.1
15
+
16
+ Files:
17
+ debian/*
18
+ Copyright:
19
+ 2013-2020, Jonas Smedegaard <dr@jones.dk>
20
+ 2020, Purism, SPC
21
+ 2013, Vasudev Kamath <kamathvasudev@gmail.com>
22
+ License-Grant:
23
+ This program is free software;
24
+ you can redistribute it and/or modify it
25
+ under the terms of the GNU General Public License
26
+ as published by the Free Software Foundation;
27
+ either version 3, or (at your option) any later version.
28
+ License: GPL-3+
29
+
30
+ Files:
31
+ debian/NotoMono/NotoMono-Regular.ttf
32
+ Copyright:
33
+ 2007, Google Inc.
34
+ License-Grant:
35
+ This Font Software is licensed under the SIL Open Font License,
36
+ Version 1.1.
37
+ License: OFL-1.1
38
+
39
+ License: OFL-1.1
40
+ PREAMBLE
41
+ .
42
+ The goals of the Open Font License (OFL) are to stimulate
43
+ worldwide development of collaborative font projects,
44
+ to support the font creation efforts
45
+ of academic and linguistic communities,
46
+ and to provide a free and open framework
47
+ in which fonts may be shared and improved
48
+ in partnership with others.
49
+ .
50
+ The OFL allows the licensed fonts to be used, studied,
51
+ modified and redistributed freely
52
+ as long as they are not sold by themselves.
53
+ The fonts, including any derivative works,
54
+ can be bundled, embedded,
55
+ redistributed and/or sold with any software
56
+ provided that any reserved names are not used
57
+ by derivative works.
58
+ The fonts and derivatives, however,
59
+ cannot be released under any other type of license.
60
+ The requirement for fonts to remain under this license
61
+ does not apply to any document
62
+ created using the fonts or their derivatives.
63
+ .
64
+ DEFINITIONS
65
+ .
66
+ "Font Software" refers to the set of files
67
+ released by the Copyright Holder(s) under this license
68
+ and clearly marked as such.
69
+ This may include source files, build scripts and documentation.
70
+ .
71
+ "Reserved Font Name" refers to any names specified as such
72
+ after the copyright statement(s).
73
+ .
74
+ "Original Version" refers to the collection
75
+ of Font Software components
76
+ as distributed by the Copyright Holder(s).
77
+ .
78
+ "Modified Version" refers to any derivative
79
+ made by adding to, deleting, or substituting --
80
+ in part or in whole --
81
+ any of the components of the Original Version,
82
+ by changing formats
83
+ or by porting the Font Software to a new environment.
84
+ .
85
+ "Author" refers to any designer, engineer, programmer,
86
+ technical writer or other person
87
+ who contributed to the Font Software.
88
+ .
89
+ PERMISSION & CONDITIONS
90
+ .
91
+ Permission is hereby granted, free of charge,
92
+ to any person obtaining a copy of the Font Software,
93
+ to use, study, copy, merge, embed, modify, redistribute,
94
+ and sell modified and unmodified copies of the Font Software,
95
+ subject to the following conditions:
96
+ .
97
+ 1) Neither the Font Software nor any of its individual components,
98
+ in Original or Modified Versions,
99
+ may be sold by itself.
100
+ .
101
+ 2) Original or Modified Versions of the Font Software
102
+ may be bundled, redistributed and/or sold with any software,
103
+ provided that each copy contains the above copyright notice
104
+ and this license.
105
+ These can be included either as stand-alone text files,
106
+ human-readable headers or in the appropriate
107
+ machine-readable metadata fields within text or binary files
108
+ as long as those fields can be easily viewed by the user.
109
+ .
110
+ 3) No Modified Version of the Font Software may use
111
+ the Reserved Font Name(s)
112
+ unless explicit written permission is granted
113
+ by the corresponding Copyright Holder.
114
+ This restriction only applies to the primary font name
115
+ as presented to the users.
116
+ .
117
+ 4) The name(s) of the Copyright Holder(s)
118
+ or the Author(s) of the Font Software
119
+ shall not be used to promote,
120
+ endorse or advertise any Modified Version,
121
+ except to acknowledge the contribution(s)
122
+ of the Copyright Holder(s) and the Author(s)
123
+ or with their explicit written permission.
124
+ .
125
+ 5) The Font Software,
126
+ modified or unmodified, in part or in whole,
127
+ must be distributed entirely under this license,
128
+ and must not be distributed under any other license.
129
+ The requirement for fonts to remain under this license
130
+ does not apply to any document
131
+ created using the Font Software.
132
+ .
133
+ TERMINATION
134
+ .
135
+ This license becomes null and void
136
+ if any of the above conditions are not met.
137
+ .
138
+ DISCLAIMER
139
+ .
140
+ THE FONT SOFTWARE IS PROVIDED "AS IS",
141
+ WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
142
+ INCLUDING BUT NOT LIMITED TO
143
+ ANY WARRANTIES OF MERCHANTABILITY,
144
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
145
+ OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT.
146
+ IN NO EVENT SHALL THE COPYRIGHT HOLDER BE LIABLE
147
+ FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
148
+ INCLUDING ANY GENERAL, SPECIAL, INDIRECT,
149
+ INCIDENTAL, OR CONSEQUENTIAL DAMAGES,
150
+ WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
151
+ ARISING FROM, OUT OF THE USE OR INABILITY TO USE
152
+ THE FONT SOFTWARE OR FROM OTHER DEALINGS IN THE FONT SOFTWARE.
153
+
154
+ License: GPL-3+
155
+ License-Reference: /usr/share/common-licenses/GPL-3
@@ -1,30 +1,89 @@
1
+ # assets/diagram — 出图用的内置字型(**为什么必须随包分发**)
2
+
3
+ 这三个字型是 `diagram` 工具在**光栅化**时必须喂给 resvg-wasm 的,原因是一次**实测**:
4
+
5
+ > 在 docker 里给容器装好中文字体(`fc-list` 认得它)之后,resvg-wasm 渲染中文**仍然得到空白画布**,
6
+ > 且 `loadSystemFonts: true|false` 两份 PNG **逐字节相同** ⇒ **wasm 对系统字体完全不可见**。
7
+ > 只有 `fontBuffers`(把字体字节直接递给它)才画得出字。
8
+ > ⇒ 顺带白拿一条:**输出与机器无关**(同一份内置字型 ⇒ 逐字节可复现)。
9
+
10
+ ## 0. 三个字型各修好了什么(**每一个都由一张实测对照图证明**)
11
+
12
+ | 字型 | 覆盖 | 没有它会怎样(实测) |
13
+ |---|---|---|
14
+ | `DroidSansFallbackFull.ttf` | 汉字 | 中文**全变方框** |
15
+ | `NotoSans-Regular.ttf` | 拉丁 | `diagram` / `parseDsl` 全变方框(Droid 是 **CJK 兜底字型**,**不含拉丁**) |
16
+ | `DejaVuSans.ttf` | 符号 | `① ② ③` / `⇒` / `→` 仍是方框(**前两个都没有这些字形**) |
17
+
18
+ 🔴 **判据的教训**:起初的验收是"有字 vs 无字逐字节不同" —— 它只证明**有墨**,
19
+ **没证明墨是那个字**(方框也是墨)⇒ 拉丁全变方框时它**照样绿**。
20
+ ⇒ 现在每条覆盖声明都配一条**点名字符集**的差分用例:**同长度不同字形必须出不同墨**。
21
+
22
+ ## 1. `DroidSansFallbackFull.ttf`(汉字,3.8 MB,Apache-2.0)
23
+
24
+ ```
1
25
  Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/
2
26
  Upstream-Name: Droid
3
27
  Source: https://android.googlesource.com/platform/frameworks/base/
4
-
5
28
  Files: *
6
29
  Copyright: Copyright © 2006, 2007, 2008, 2009, 2010 Google Corp.
7
30
  Droid is a trademark of Google Corp.
8
31
  License: Apache-2
9
-
10
- Files: debian/*
11
- Copyright: Copyright © 2010, Simon Ochsenreither <simon@ochsenreither.de>
12
- Copyright © 2011-2014 Christian Perrier <bubulle@debian.org>
13
- Copyright © 2013-2014 Vasudev Kamath <kamathvasudev@gmail.com>
14
- License: Apache-2
15
-
16
- License: Apache-2
17
- Licensed under the Apache License, Version 2.0 (the "License");
18
- you may not use this file except in compliance with the License.
19
- You may obtain a copy of the License at
20
- .
21
- http://www.apache.org/licenses/LICENSE-2.0
22
- .
23
- Unless required by applicable law or agreed to in writing, software
24
- distributed under the License is distributed on an "AS IS" BASIS,
25
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
26
- See the License for the specific language governing permissions and
27
- limitations under the License.
28
- .
29
32
  On Debian systems, the complete text of the Apache License Version 2.0
30
33
  can be found in `/usr/share/common-licenses/Apache-2.0'.
34
+ ```
35
+
36
+ 取件处(本机):`/usr/share/fonts/truetype/droid/DroidSansFallbackFull.ttf`(Debian `fonts-droid-fallback`)。
37
+ 许可证全文随包:`LICENSE-Apache-2.0.txt`。
38
+
39
+ ## 2. `NotoSans-Regular.ttf`(拉丁,500 KB,OFL-1.1)—— v1.51.1 新增
40
+
41
+ 🔴 **为什么必须补第二个字型**:`DroidSansFallbackFull` 是**汉字兜底字型**(Android 当年把它与
42
+ `DroidSans.ttf` 配成一对),它**不含拉丁字母** ⇒ v1.51.0 只带它时,图里 `diagram` / `parseDsl` /
43
+ `·` 全部渲染成**方框**(owner 实测图上肉眼可见)。
44
+
45
+ ```
46
+ Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/
47
+ Upstream-Name: Noto
48
+ Source: https://github.com/notofonts/noto-fonts
49
+ License: OFL-1.1
50
+ ```
51
+
52
+ 取件处(本机):`/usr/share/fonts/truetype/noto/NotoSans-Regular.ttf`(Debian `fonts-noto-core`)。
53
+ 许可证全文随包:`LICENSE-OFL-1.1.txt`(即该包的 Debian `copyright` 文件,内含 OFL-1.1 全文与出处)。
54
+
55
+ ## 3. `DejaVuSans.ttf`(符号,742 KB)—— v1.51.1 续(D108)
56
+
57
+ 🔴 **为什么还要第三个**:补完拉丁之后**再看了一眼真图**,发现 `① ② ③` 与 `⇒` / `→` **仍然是方框**
58
+ —— 前两个字型都没有这些字形(`DroidSansFallbackFull` 有汉字但没有象征符号;`NotoSans-Regular`
59
+ 有拉丁与常用标点,但没有带圈数字与箭头)。而本仓自己的文档**满屏 `⇒`** ⇒ 图里出现它是常态。
60
+
61
+ **选型是量出来的**(各喂一个缓冲单独渲染一张对照图,肉眼看):
62
+
63
+ | 候选 | 结果 |
64
+ |---|---|
65
+ | `NotoSansSymbols2-Regular.ttf` | ❌ 只有 `▲ ● ★ ✓ ✗`;`①`/`⇒`/`→` 仍是方框 |
66
+ | `NotoSansSymbols-Regular.ttf` | ❌ 同上量级(未单独出图,覆盖面是 Symbols2 的子集方向) |
67
+ | `DejaVuSans.ttf` | ✅ **全覆盖**:`① ② ③ ⇒ → ← ▲ ● ★ ✓ ✗ ·— ≥ ≠ ×` |
68
+
69
+ ```
70
+ Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/
71
+ Upstream-Name: DejaVu
72
+ Source: https://github.com/dejavu-fonts/dejavu-fonts
73
+ License: Bitstream-Vera
74
+ ```
75
+
76
+ 取件处(本机):`/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf`(Debian `fonts-dejavu-core`)。
77
+ 许可证全文随包:`LICENSE-DejaVu.txt`(即该包的 Debian `copyright` 文件)。
78
+
79
+ ## 4. 家族名与文件必须以同一份事实为准
80
+
81
+ 家族名(`'Noto Sans'` / `'DejaVu Sans'` / `'Droid Sans Fallback'`)、资产路径与 SVG 家族栈的
82
+ **唯一真相源** = `src/diagram/fonts.ts`;`src/diagram/rasterize.ts` 负责按路径把字节读进来喂 wasm。
83
+
84
+ 🔴 **家族名是量出来的**:三个名字取自各文件 `name` 表 `nameID=1`(平台 3 / UTF-16BE)。
85
+ 必须这样量一次的原因:写错名字时 resvg 的**自动回退仍可能把字画出来**(**图上看不出来**)
86
+ ⇒ "栈里的名字是对的"这件事只有直接读字体文件才能证明。
87
+
88
+ 三个资产**都必须在 `package.json` 的 `files` 白名单里**,并被 `dsh-develop pack-check` 的
89
+ `required` 清单硬断言(漏了 = 装得上但画不出字,v1.26.15 事故同族)。
package/dsh.plugin.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "id": "dsh-serenity-hooks",
3
- "version": "1.51.0",
3
+ "version": "1.51.1",
4
4
  "main": "lib/index.js",
5
5
  "description": "宁静号 ACC harness(Native Cordis 插件):给 DSH 装一个「AI 工作区」——12 个工具(container_fs/container_trajectory/dashboard/container_git/msm/praxis/handyman/localstore/container_admin/im-bridge/acc-diag/diagram;后三个按配置条件出现)+ 机械约束(安全模式/工作区围墙/密钥守卫)+ 轨迹日志与原地重建 + 网页登录入口/微信桥/子角色/对外问答页/trajectory 唤醒注册表。适配 DSH 0.1.7-rc.1(deepseek-ai/deepseek-harness)。",
6
6
  "engines": {
@@ -0,0 +1,40 @@
1
+ /**
2
+ * diagram 字体事实源(**唯一真相源**:资产路径 + 家族名 + CSS 家族栈)
3
+ *
4
+ * 为什么单独成模块(R↓):两处都要它,而它们的依赖面**必须**保持不同 ——
5
+ * · `dsl-render.ts` 只生成 SVG 字符串(**纯函数、零 fs**),它需要在 SVG 里写 `font-family` 栈;
6
+ * · `rasterize.ts` 要读字体文件喂给 wasm,并设 `defaultFontFamily`。
7
+ * ⇒ 谁都不该从对方 import(前者会被拖进 `node:fs`/wasm,后者不该定义 SVG 措辞)。
8
+ *
9
+ * ## 为什么是**三个**字型(每一个都由一张实测图证明,不是偏好,也不是"多带点保险")
10
+ *
11
+ * | 字型 | 覆盖 | 它修好了什么(实测) |
12
+ * |---|---|---|
13
+ * | `DroidSansFallbackFull.ttf` | 汉字 | 没有它 ⇒ 中文全是方框(docker 实测:wasm 对系统字体**完全不可见**) |
14
+ * | `NotoSans-Regular.ttf` | 拉丁 | 没有它 ⇒ `diagram` / `parseDsl` 全变方框(Droid 是 **CJK 兜底字型**,**不含拉丁**) |
15
+ * | `DejaVuSans.ttf` | 符号 | 没有它 ⇒ `① ② ③` / `⇒ ⇒` / `→` 仍是方框(**前两个都没有这些字形**,实测对照图见 `docs/diagram-tool-feasibility.md`) |
16
+ *
17
+ * 🔴 **上一版的判据漏洞**(v1.51.1 修的):那条"有字 vs 无字逐字节不同"只证明**有墨**,
18
+ * 没证明**墨是那个字** —— 方框也是墨 ⇒ 拉丁全变方框时它**照样绿**。
19
+ * ⇒ 现在每条覆盖声明都配一条**点名字符集**的差分用例:同长度不同字形 ⇒ 必须出不同墨。
20
+ *
21
+ * ## 家族名是**量出来的**,不是猜的
22
+ * 下表取自各文件 `name` 表 `nameID=1`(平台 3 / UTF-16BE):
23
+ * `Droid Sans Fallback` / `Noto Sans` / `DejaVu Sans`。
24
+ * 写错名字时 resvg 的自动回退仍可能把字画出来(**图上看不出来**)⇒ 名字正确与否只能这样证明。
25
+ */
26
+ export declare const DIAGRAM_CJK_FONT_ASSET = "assets/diagram/DroidSansFallbackFull.ttf";
27
+ export declare const DIAGRAM_LATIN_FONT_ASSET = "assets/diagram/NotoSans-Regular.ttf";
28
+ export declare const DIAGRAM_SYMBOL_FONT_ASSET = "assets/diagram/DejaVuSans.ttf";
29
+ /** 汉字字型的家族名(**字体文件自报的族名**,写错就取不到) */
30
+ export declare const DIAGRAM_CJK_FONT_FAMILY = "Droid Sans Fallback";
31
+ /** 拉丁字型的家族名 */
32
+ export declare const DIAGRAM_LATIN_FONT_FAMILY = "Noto Sans";
33
+ /** 符号字型的家族名 */
34
+ export declare const DIAGRAM_SYMBOL_FONT_FAMILY = "DejaVu Sans";
35
+ /**
36
+ * SVG 里写的家族栈(**顺序即回退顺序**):拉丁优先取 Noto,符号落 DejaVu,其余字形落到 Droid。
37
+ * ⚠️ 末尾的通用名(`sans-serif`)只是兜底惯例:本部署里 resvg **不带系统字体**
38
+ * (`loadSystemFonts: false`)⇒ 它取不到任何东西,真正的兜底是上面三个内置字型。
39
+ */
40
+ export declare const DIAGRAM_FONT_STACK = "'Noto Sans', 'DejaVu Sans', 'Droid Sans Fallback', sans-serif";
@@ -1,11 +1,18 @@
1
- /** 字体资产相对包根的路径(**必须同时出现在 package.json 的 files 白名单里**,pack-check 会核对) */
1
+ /** 汉字字型资产(相对包根的路径)——**必须出现在 package.json 的 files 白名单里**(pack-check 会核对) */
2
2
  export declare const DIAGRAM_FONT_ASSET = "assets/diagram/DroidSansFallbackFull.ttf";
3
- /** 字体绝对路径(导出以便测试与 pack-check 共用同一判据来源) */
3
+ /** 拉丁字型资产(v1.51.1 新增:CJK 字型**不含拉丁** ⇒ 只带一个时英文字母全渲染成方框) */
4
+ export declare const DIAGRAM_FONT_ASSET_LATIN = "assets/diagram/NotoSans-Regular.ttf";
5
+ /** 符号字型资产(v1.51.1 新增:前两个**都没有** `① ② ③` / `⇒` / `→` 的字形 ⇒ 只带前两个时它们仍是方框) */
6
+ export declare const DIAGRAM_FONT_ASSET_SYMBOL = "assets/diagram/DejaVuSans.ttf";
7
+ /** 汉字字型绝对路径(导出以便测试与 pack-check 共用同一判据来源) */
4
8
  export declare function diagramFontPath(): string;
9
+ /** 拉丁字型绝对路径 */
10
+ export declare function diagramLatinFontPath(): string;
11
+ /** 符号字型绝对路径 */
12
+ export declare function diagramSymbolFontPath(): string;
5
13
  /**
6
- * 内置字体的族名。SVG 里声明的 `Noto Sans CJK SC` / `Source Han Sans SC` / `Microsoft YaHei`
7
- * 在本机**一个都不存在**(本机 CJK 覆盖只有 AR PL UMing + Droid Sans Fallback)
8
- * ⇒ 光栅化时必须由这里兜底,否则中文会退化成缺字方框。
14
+ * 兜底字型的族名 = **汉字字型**(本工具的图以中文为主)。
15
+ * 两个内置字型的家族名与 CSS 家族栈统一定义在 `fonts.ts`(单一真相源),此处只透出兜底那一个。
9
16
  */
10
17
  export declare const DIAGRAM_FONT_FAMILY = "Droid Sans Fallback";
11
18
  export interface RasterResult {
@@ -22,7 +29,7 @@ export interface RasterResult {
22
29
  * @param width 输出像素宽(矢量无损放大;高度按 SVG 自身比例推出)
23
30
  */
24
31
  export declare function rasterizeSvg(svg: string, width: number): Promise<RasterResult>;
25
- /** 测试用:清本模块的缓存(**字体缓冲 + 就绪 promise**)。
32
+ /** 测试用:清本模块的缓存(**三个字体缓冲 + 就绪 promise**)。
26
33
  * ⚠️ **wasm 本身无法"重置"**(`initWasm` 进程级一次性)⇒ 清掉后下一次 `rasterizeSvg`
27
34
  * 会**重新走一遍 init 并命中共用态**("already initialized" 由 `ensureReady` 吞掉)。 */
28
35
  export declare function resetRasterCache(): void;
package/lib/index.js CHANGED
@@ -6,7 +6,7 @@ import { A as localIdStamp, C as setActiveSessionInfo, D as isoLocal, E as useSe
6
6
  import { a as readWeixinCredential, c as weixinInboundDir, d as LOCALSTORE_SCOPES, f as checkLocalstoreGitCompliance, g as runLocalStore, h as readStore, i as matchWeixinRoute, l as weixinSessionIdFor, m as readGitTrack, n as extractWeixinText, o as readWeixinSettings, p as localstorePath, r as hasVoiceItem, s as sanitizeFileName$1, t as extractWeixinMedia } from "./weixin-route-7BtOiy4X.js";
7
7
  import { a as unregisterSkiffSession$1, i as skiffSessionSnapshot$1, n as skiffRoleFor$1, o as ACC_VERSION, r as skiffSessionInfo$1, t as registerSkiffSession$1 } from "./skiff-registry-BjIX_KD1.js";
8
8
  import { a as readSkiffRoles, c as roleMsmWhitelist, d as systemPromptSource, f as trajectorySubset, i as isSkiffSessionId, l as roleToolWhitelist, n as buildSkiffBasePrompt, o as resolveRoleSystemPrompt, r as createRolePromptReader, s as resolveSkiffKind } from "./skiff-role-DQZ6eR51.js";
9
- import { a as FACE_PORTS, c as MECHANISM_PORTS, i as ACP_HTTP_PORT, l as SKIFF_DEBUG_PORT, n as registerSettingsSection, o as GATEWAY_PORT, s as MAIN_WEB_PORT, t as readSimpleSettings, u as WEIXIN_SEND_PORT } from "./settings-section-cq0eUSd-.js";
9
+ import { a as ACP_HTTP_PORT, c as MAIN_WEB_PORT, d as WEIXIN_SEND_PORT, l as MECHANISM_PORTS, n as readSimpleSettings, o as FACE_PORTS, r as registerSettingsSection, s as GATEWAY_PORT, u as SKIFF_DEBUG_PORT } from "./settings-section-xHABl8nk.js";
10
10
  import { a as markdownToPlainText, c as sendTyping, i as getUpdates, l as sniffImageExt, n as downloadMedia, o as sendFileMessage, r as getConfig, s as sendTextMessage, t as TypingStatus } from "./weixin-api-BrRcPiQs.js";
11
11
  import { createRequire } from "node:module";
12
12
  import z from "@deepseek-ai/schemastery";
@@ -10358,7 +10358,7 @@ function registerStatusApi(ctx, opts = {}) {
10358
10358
  return;
10359
10359
  }
10360
10360
  const settings = readAdvancedSettings();
10361
- const { readSimpleSettings } = await import("./settings-section-cq0eUSd-.js").then((n) => n.r);
10361
+ const { readSimpleSettings } = await import("./settings-section-xHABl8nk.js").then((n) => n.i);
10362
10362
  const simple = readSimpleSettings();
10363
10363
  const allowed = settings.publicAsk.allowed;
10364
10364
  const port = simple.acpHttpPort ?? ACP_HTTP_PORT;
@@ -19330,13 +19330,26 @@ function lineAt(src, idx) {
19330
19330
  function parseDsl(source) {
19331
19331
  const src = stripComments(source);
19332
19332
  const errors = [];
19333
+ /**
19334
+ * 🔴 **默认坐标系 = 画布像素、y 向下**(v1.51.1 修的真缺陷,见下)。
19335
+ *
19336
+ * v1.51.0 的默认值是从旧 mini-SVG 移植时**原样带过来**的:`0~100 × −19~72 且 y 向上`
19337
+ * —— 而工具描述里**只写了 `box="x0,y0,x1,y1"`,没写量程与单位**。
19338
+ * 模型按常识写画布像素(`box="100,100,400,200"`)⇒ 渲染器把它塞进那个 100 宽的域里
19339
+ * ⇒ **所有坐标飞出画布**(实测 SVG:`x=990 y=-919 w=2940`),而 `errors`/`lint` **全空**
19340
+ * ⇒ 图是空白却一声不吭,模型只能反复试错(owner 实测「别的 CCC 根本用不起来」的根因)。
19341
+ *
19342
+ * ⇒ 现在:**默认域 = `0,0,width,height`**(`width/height` 未写时 1000×780)⇒ 1 单位 ≈ 1 像素量级,
19343
+ * `y` 越大越靠下(`dsl-render.ts` 的 `py()` 同批改成 y 向下)。
19344
+ * `domain="x0,y0,x1,y1"` 仍然保留 —— 需要"数学式坐标窗口"时显式写它(**y 也向下**,口径只有一种)。
19345
+ */
19333
19346
  const model = {
19334
19347
  W: 1e3,
19335
19348
  H: 780,
19336
19349
  x0: 0,
19337
- y0: -19,
19338
- x1: 100,
19339
- y1: 72,
19350
+ y0: 0,
19351
+ x1: 1e3,
19352
+ y1: 780,
19340
19353
  pad: 10,
19341
19354
  title: "",
19342
19355
  subtitle: "",
@@ -19421,6 +19434,11 @@ function parseDsl(source) {
19421
19434
  model.x1 = d.x1;
19422
19435
  model.y1 = d.y1;
19423
19436
  } else fail(tagIndex, ln, `domain 不是 4 个数字:${attrs.domain}(已忽略)`);
19437
+ } else {
19438
+ model.x0 = 0;
19439
+ model.y0 = 0;
19440
+ model.x1 = model.W;
19441
+ model.y1 = model.H;
19424
19442
  }
19425
19443
  continue;
19426
19444
  }
@@ -19459,7 +19477,7 @@ function parseDsl(source) {
19459
19477
  continue;
19460
19478
  }
19461
19479
  const label = attrs.label;
19462
- const id = attrs.id || label || `rect${rectOrdinal}`;
19480
+ const id = attrs.id || attrs.name || label || `rect${rectOrdinal}`;
19463
19481
  if (ids.has(id)) {
19464
19482
  fail(tagIndex, ln, `rect id 重复:${id}`);
19465
19483
  continue;
@@ -19511,7 +19529,7 @@ function parseDsl(source) {
19511
19529
  x,
19512
19530
  y,
19513
19531
  text: content,
19514
- align: al === "center" || al === "right" ? al : "left",
19532
+ align: al === "center" || al === "middle" ? "center" : al === "right" || al === "end" ? "right" : "left",
19515
19533
  size: num(attrs.size) ?? 12.5,
19516
19534
  ln
19517
19535
  };
@@ -19527,6 +19545,44 @@ function parseDsl(source) {
19527
19545
  };
19528
19546
  }
19529
19547
  //#endregion
19548
+ //#region src/diagram/fonts.ts
19549
+ /**
19550
+ * diagram 字体事实源(**唯一真相源**:资产路径 + 家族名 + CSS 家族栈)
19551
+ *
19552
+ * 为什么单独成模块(R↓):两处都要它,而它们的依赖面**必须**保持不同 ——
19553
+ * · `dsl-render.ts` 只生成 SVG 字符串(**纯函数、零 fs**),它需要在 SVG 里写 `font-family` 栈;
19554
+ * · `rasterize.ts` 要读字体文件喂给 wasm,并设 `defaultFontFamily`。
19555
+ * ⇒ 谁都不该从对方 import(前者会被拖进 `node:fs`/wasm,后者不该定义 SVG 措辞)。
19556
+ *
19557
+ * ## 为什么是**三个**字型(每一个都由一张实测图证明,不是偏好,也不是"多带点保险")
19558
+ *
19559
+ * | 字型 | 覆盖 | 它修好了什么(实测) |
19560
+ * |---|---|---|
19561
+ * | `DroidSansFallbackFull.ttf` | 汉字 | 没有它 ⇒ 中文全是方框(docker 实测:wasm 对系统字体**完全不可见**) |
19562
+ * | `NotoSans-Regular.ttf` | 拉丁 | 没有它 ⇒ `diagram` / `parseDsl` 全变方框(Droid 是 **CJK 兜底字型**,**不含拉丁**) |
19563
+ * | `DejaVuSans.ttf` | 符号 | 没有它 ⇒ `① ② ③` / `⇒ ⇒` / `→` 仍是方框(**前两个都没有这些字形**,实测对照图见 `docs/diagram-tool-feasibility.md`) |
19564
+ *
19565
+ * 🔴 **上一版的判据漏洞**(v1.51.1 修的):那条"有字 vs 无字逐字节不同"只证明**有墨**,
19566
+ * 没证明**墨是那个字** —— 方框也是墨 ⇒ 拉丁全变方框时它**照样绿**。
19567
+ * ⇒ 现在每条覆盖声明都配一条**点名字符集**的差分用例:同长度不同字形 ⇒ 必须出不同墨。
19568
+ *
19569
+ * ## 家族名是**量出来的**,不是猜的
19570
+ * 下表取自各文件 `name` 表 `nameID=1`(平台 3 / UTF-16BE):
19571
+ * `Droid Sans Fallback` / `Noto Sans` / `DejaVu Sans`。
19572
+ * 写错名字时 resvg 的自动回退仍可能把字画出来(**图上看不出来**)⇒ 名字正确与否只能这样证明。
19573
+ */
19574
+ const DIAGRAM_CJK_FONT_ASSET = "assets/diagram/DroidSansFallbackFull.ttf";
19575
+ const DIAGRAM_LATIN_FONT_ASSET = "assets/diagram/NotoSans-Regular.ttf";
19576
+ const DIAGRAM_SYMBOL_FONT_ASSET = "assets/diagram/DejaVuSans.ttf";
19577
+ /** 汉字字型的家族名(**字体文件自报的族名**,写错就取不到) */
19578
+ const DIAGRAM_CJK_FONT_FAMILY = "Droid Sans Fallback";
19579
+ /**
19580
+ * SVG 里写的家族栈(**顺序即回退顺序**):拉丁优先取 Noto,符号落 DejaVu,其余字形落到 Droid。
19581
+ * ⚠️ 末尾的通用名(`sans-serif`)只是兜底惯例:本部署里 resvg **不带系统字体**
19582
+ * (`loadSystemFonts: false`)⇒ 它取不到任何东西,真正的兜底是上面三个内置字型。
19583
+ */
19584
+ const DIAGRAM_FONT_STACK = `'Noto Sans', 'DejaVu Sans', '${DIAGRAM_CJK_FONT_FAMILY}', sans-serif`;
19585
+ //#endregion
19530
19586
  //#region src/diagram/dsl-render.ts
19531
19587
  const TITLE_BAND = 68;
19532
19588
  const EPS = .35;
@@ -19669,7 +19725,12 @@ function renderDsl(model) {
19669
19725
  const scaleX = (W - 2 * pad) / (x1 - x0 || 1);
19670
19726
  const scaleY = (H - TITLE_BAND - pad) / (y1 - y0 || 1);
19671
19727
  const px = (x) => r2(pad + (x - x0) * scaleX);
19672
- const py = (y) => r2(TITLE_BAND + (y1 - y) * scaleY);
19728
+ const py = (y) => r2(TITLE_BAND + (y - y0) * scaleY);
19729
+ /** 绘图窗口 = 声明的域(默认就是画布)。出窗口的图元**报错并跳过**,不静默画到看不见的地方。 */
19730
+ const tolX = Math.abs(x1 - x0) * .01;
19731
+ const tolY = Math.abs(y1 - y0) * .01;
19732
+ const outside = (x, y) => x < x0 - tolX || x > x1 + tolX || y < y0 - tolY || y > y1 + tolY;
19733
+ const windowText = `坐标窗口 x ${r2(x0)}..${r2(x1)} / y ${r2(y0)}..${r2(y1)}(y 向下;不写 domain 时=画布)`;
19673
19734
  const resolve = (token) => {
19674
19735
  const t = String(token ?? "").trim();
19675
19736
  const np = NUM_PAIR_RE.exec(t);
@@ -19689,15 +19750,15 @@ function renderDsl(model) {
19689
19750
  const cx = (rx0 + rx1) / 2;
19690
19751
  const cy = (ry0 + ry1) / 2;
19691
19752
  const base = {
19692
- n: [cx, ry1],
19693
- s: [cx, ry0],
19753
+ n: [cx, ry0],
19754
+ s: [cx, ry1],
19694
19755
  e: [rx1, cy],
19695
19756
  w: [rx0, cy],
19696
19757
  c: [cx, cy],
19697
- ne: [rx1, ry1],
19698
- nw: [rx0, ry1],
19699
- se: [rx1, ry0],
19700
- sw: [rx0, ry0]
19758
+ ne: [rx1, ry0],
19759
+ nw: [rx0, ry0],
19760
+ se: [rx1, ry1],
19761
+ sw: [rx0, ry1]
19701
19762
  }[mt[2] ?? ""];
19702
19763
  if (!base) return null;
19703
19764
  const dx = mt[3] === void 0 ? 0 : Number(mt[3]);
@@ -19724,7 +19785,7 @@ function renderDsl(model) {
19724
19785
  const headEls = [], rectEls = [], segEls = [];
19725
19786
  const textEls = [], legendEls = [], l3 = [], l4 = [];
19726
19787
  let marks = 0;
19727
- headEls.push(`<svg xmlns="http://www.w3.org/2000/svg" width="${W}" height="${H}" viewBox="0 0 ${W} ${H}" font-family="-apple-system, 'Noto Sans CJK SC', 'Source Han Sans SC', 'Microsoft YaHei', sans-serif">`);
19788
+ headEls.push(`<svg xmlns="http://www.w3.org/2000/svg" width="${W}" height="${H}" viewBox="0 0 ${W} ${H}" font-family="${DIAGRAM_FONT_STACK}">`);
19728
19789
  headEls.push(`<rect width="${W}" height="${H}" fill="#fbfaf7"/>`);
19729
19790
  marks += 2;
19730
19791
  if (m.title) headEls.push(`<text x="10" y="34" font-size="21" font-weight="bold" fill="#111">${esc(m.title)}</text>`);
@@ -19732,10 +19793,14 @@ function renderDsl(model) {
19732
19793
  if (m.subtitle) headEls.push(`<text x="10" y="58" font-size="13" fill="#666">${esc(m.subtitle)}</text>`);
19733
19794
  if (m.subtitle) marks += 1;
19734
19795
  for (const r of rects) {
19796
+ if (outside(n(r.x0, 0), n(r.y0, 0)) || outside(n(r.x1, 0), n(r.y1, 0))) {
19797
+ errors.push(`矩形「${r.label ?? r.id}」在绘图窗口外:box=${r2(n(r.x0, 0))},${r2(n(r.y0, 0))},${r2(n(r.x1, 0))},${r2(n(r.y1, 0))} / ${windowText} ⇒ 该图元已跳过`);
19798
+ continue;
19799
+ }
19735
19800
  const X = px(n(r.x0, 0));
19736
- const Y = py(n(r.y1, 0));
19801
+ const Y = py(n(r.y0, 0));
19737
19802
  const bw = r2(px(n(r.x1, 0)) - X);
19738
- const bh = r2(py(n(r.y0, 0)) - Y);
19803
+ const bh = r2(py(n(r.y1, 0)) - Y);
19739
19804
  rectEls.push(`<rect x="${X}" y="${Y}" width="${bw}" height="${bh}" rx="2" fill="${esc(col(r.color, DEF_FILL))}" stroke="#ffffff" stroke-width="1.5"/>`);
19740
19805
  marks += 1;
19741
19806
  if (!r.label) continue;
@@ -19751,7 +19816,12 @@ function renderDsl(model) {
19751
19816
  const b = resolve(s.to);
19752
19817
  if (!a || !b) {
19753
19818
  const bad = !a ? s.from : s.to;
19754
- errors.push(`第 ${lineNo(s, i)} 行 ${kind}:锚点引用解析不了(${esc(bad)});已知 id = ${ids.join("、")} ⇒ 该图元已跳过`);
19819
+ const shown = ids.slice(0, 8).join("、");
19820
+ errors.push(`第 ${lineNo(s, i)} 行 ${kind}:锚点引用解析不了(${esc(bad)});已知 id = ${shown}${ids.length > 8 ? ` …(共 ${ids.length} 个)` : ""};rect 要能被引用请写 id="…"(name="…" 亦可)⇒ 该图元已跳过`);
19821
+ continue;
19822
+ }
19823
+ if (outside(a.x, a.y) || outside(b.x, b.y)) {
19824
+ errors.push(`第 ${lineNo(s, i)} 行 ${kind}:端点在绘图窗口外 (${r2(a.x)},${r2(a.y)})→(${r2(b.x)},${r2(b.y)}) / ${windowText} ⇒ 该图元已跳过`);
19755
19825
  continue;
19756
19826
  }
19757
19827
  const X0 = px(a.x);
@@ -19794,19 +19864,21 @@ function renderDsl(model) {
19794
19864
  }
19795
19865
  }
19796
19866
  for (const t of texts) {
19797
- const anchor = t.anchor === "start" || t.anchor === "end" ? t.anchor : "middle";
19867
+ if (outside(n(t.x, 0), n(t.y, 0))) {
19868
+ errors.push(`文字「${t.text}」在绘图窗口外:(x ${r2(n(t.x, 0))}, y ${r2(n(t.y, 0))}) / ${windowText} ⇒ 该图元已跳过`);
19869
+ continue;
19870
+ }
19871
+ const anchor = t.anchor === "left" || t.anchor === "start" ? "start" : t.anchor === "right" || t.anchor === "end" ? "end" : "middle";
19798
19872
  textEls.push(`<text x="${px(n(t.x, 0))}" y="${py(n(t.y, 0))}" font-size="${n(t.size, DEF_SIZE)}" fill="${esc(col(t.color, "#4a4a4a"))}" text-anchor="${anchor}" dominant-baseline="middle">${esc(t.text)}</text>`);
19799
19873
  marks += 1;
19800
19874
  }
19801
19875
  if (m.legend) {
19802
19876
  const size = n(m.legend.size, DEF_SIZE);
19803
- const LX = r2(n(m.legend.x, 0));
19804
- const LY = r2(n(m.legend.y, 0));
19805
- legendEls.push(`<text x="${LX}" y="${LY}" font-size="${size + 1}" font-weight="bold" fill="#333">阵营</text>`);
19806
- marks += 1;
19877
+ const LX = px(n(m.legend.x, 0));
19878
+ const LY = py(n(m.legend.y, 0));
19807
19879
  const entries = Object.entries(colors);
19808
19880
  for (let i = 0; i < entries.length; i++) {
19809
- const row = LY + 20 + i * (size + 6);
19881
+ const row = LY + i * (size + 6);
19810
19882
  legendEls.push(`<rect x="${LX}" y="${r2(row - 8)}" width="12" height="12" rx="2" fill="${esc(entries[i][1])}"/>`);
19811
19883
  legendEls.push(`<text x="${r2(LX + 18)}" y="${r2(row)}" font-size="${size}" fill="#333" dominant-baseline="middle">${esc(entries[i][0])}</text>`);
19812
19884
  marks += 2;
@@ -19841,7 +19913,8 @@ function renderDsl(model) {
19841
19913
  * ⇒ **wasm 对系统字体完全不可见**。只有 `fontBuffers` 才画得出字。
19842
19914
  * ⇒ 顺带白拿一个好处:**输出与机器无关**(同一份内置字体 ⇒ 逐字节可复现,S↑ 稳定)。
19843
19915
  *
19844
- * 字体资产随包分发:`assets/diagram/DroidSansFallbackFull.ttf`(3.8 MB,Apache-2.0,含许可证与出处)。
19916
+ * 字体资产随包分发(三个,各有各的覆盖,见 `fonts.ts` 的表):汉字 3.8 MB(Apache-2.0)/
19917
+ * 拉丁 0.5 MB(OFL-1.1)/符号 0.75 MB(DejaVu 许可证),各自带许可证与出处。
19845
19918
  */
19846
19919
  /**
19847
19920
  * 包根 = **向上找到含 `package.json` 的那一级**。
@@ -19861,20 +19934,27 @@ function packageRootPath() {
19861
19934
  }
19862
19935
  return join(start, "..");
19863
19936
  }
19864
- /** 字体资产相对包根的路径(**必须同时出现在 package.json 的 files 白名单里**,pack-check 会核对) */
19865
- const DIAGRAM_FONT_ASSET = "assets/diagram/DroidSansFallbackFull.ttf";
19866
- /** 字体绝对路径(导出以便测试与 pack-check 共用同一判据来源) */
19937
+ /** 汉字字型绝对路径(导出以便测试与 pack-check 共用同一判据来源) */
19867
19938
  function diagramFontPath() {
19868
- return join(packageRootPath(), DIAGRAM_FONT_ASSET);
19939
+ return join(packageRootPath(), DIAGRAM_CJK_FONT_ASSET);
19940
+ }
19941
+ /** 拉丁字型绝对路径 */
19942
+ function diagramLatinFontPath() {
19943
+ return join(packageRootPath(), DIAGRAM_LATIN_FONT_ASSET);
19944
+ }
19945
+ /** 符号字型绝对路径 */
19946
+ function diagramSymbolFontPath() {
19947
+ return join(packageRootPath(), DIAGRAM_SYMBOL_FONT_ASSET);
19869
19948
  }
19870
19949
  /**
19871
- * 内置字体的族名。SVG 里声明的 `Noto Sans CJK SC` / `Source Han Sans SC` / `Microsoft YaHei`
19872
- * 在本机**一个都不存在**(本机 CJK 覆盖只有 AR PL UMing + Droid Sans Fallback)
19873
- * ⇒ 光栅化时必须由这里兜底,否则中文会退化成缺字方框。
19950
+ * 兜底字型的族名 = **汉字字型**(本工具的图以中文为主)。
19951
+ * 两个内置字型的家族名与 CSS 家族栈统一定义在 `fonts.ts`(单一真相源),此处只透出兜底那一个。
19874
19952
  */
19875
- const DIAGRAM_FONT_FAMILY = "Droid Sans Fallback";
19953
+ const DIAGRAM_FONT_FAMILY = DIAGRAM_CJK_FONT_FAMILY;
19876
19954
  let wasmReady = null;
19877
19955
  let fontBuf = null;
19956
+ let latinBuf = null;
19957
+ let symbolBuf = null;
19878
19958
  /**
19879
19959
  * wasm 模块路径:**先解析 JS 入口再取同目录的 `.wasm`**。
19880
19960
  * 不直接 resolve `.wasm` 的原因:包的 `exports` 表未必暴露该子路径(解析失败会抛,
@@ -19891,11 +19971,19 @@ function ensureReady() {
19891
19971
  });
19892
19972
  return wasmReady;
19893
19973
  }
19894
- /** 字体只读一次(3.8 MB,进程内缓存) */
19974
+ /** 三个字型各只读一次(3.8 MB + 0.5 MB + 0.75 MB,进程内缓存) */
19895
19975
  function fontBuffer() {
19896
19976
  if (fontBuf === null) fontBuf = readFileSync(diagramFontPath());
19897
19977
  return fontBuf;
19898
19978
  }
19979
+ function latinFontBuffer() {
19980
+ if (latinBuf === null) latinBuf = readFileSync(diagramLatinFontPath());
19981
+ return latinBuf;
19982
+ }
19983
+ function symbolFontBuffer() {
19984
+ if (symbolBuf === null) symbolBuf = readFileSync(diagramSymbolFontPath());
19985
+ return symbolBuf;
19986
+ }
19899
19987
  /**
19900
19988
  * SVG → PNG。
19901
19989
  * @param width 输出像素宽(矢量无损放大;高度按 SVG 自身比例推出)
@@ -19909,7 +19997,11 @@ async function rasterizeSvg(svg, width) {
19909
19997
  value: width
19910
19998
  },
19911
19999
  font: {
19912
- fontBuffers: [fontBuffer()],
20000
+ fontBuffers: [
20001
+ latinFontBuffer(),
20002
+ symbolFontBuffer(),
20003
+ fontBuffer()
20004
+ ],
19913
20005
  loadSystemFonts: false,
19914
20006
  defaultFontFamily: DIAGRAM_FONT_FAMILY
19915
20007
  }
@@ -19931,27 +20023,30 @@ async function rasterizeSvg(svg, width) {
19931
20023
  * (注意**不借助浏览器生成截图**)」。权威设计 = 插件仓 `docs/diagram-tool-feasibility.md`。
19932
20024
  *
19933
20025
  * 链路(全部进程内,**不起浏览器**):标签源码 → `parseDsl` → `renderDsl`(SVG + 四项体检)
19934
- * → `rasterizeSvg`(resvg-wasm + **内置字体**)→ PNG → 落 CCC `_tmp/diagram/` + 图片块进对话。
20026
+ * → `rasterizeSvg`(resvg-wasm + **内置字型**)→ PNG → **图片块进对话**。
20027
+ *
20028
+ * 🔴 **不落文件**(owner 2026-09-29 明确):「这个 diagram 生成文件干啥,没有任何意义啊,
20029
+ * 直接生成给用户看就好了」⇒ 正常路径**一份文件都不写**(图走附件的 durable 引用直进对话记录,
20030
+ * 可回看、可被后续轮次引用、可转微信 —— 这才是"给人看"的落点)。
20031
+ * ⚠️ **唯一的例外**:图**贴不进对话**时(附件服务不在/部署不收 png/当前路由不声明图像输入/
20032
+ * 存字节失败),才把 PNG 落到 `<CCC>/_tmp/diagram/` 并**在回执里给出路径与原因** ——
20033
+ * 否则那一次就真的什么都没有了("降级不吞回执"同一条纪律)。
19935
20034
  *
19936
20035
  * 🔴 **为什么 PNG 是必需的**:宿主的对话内图片通路**白名单只有光栅格式**
19937
- * (`image/png|jpeg|webp|gif`,见 `dsh-attachment/types.d.ts`)⇒ **SVG 进不去**,
19938
- * "产出 SVG 就自动显示"确定不成立。右侧栏走 SVG 是二期方案。
20036
+ * (`image/png|jpeg|webp|gif`,见 `dsh-attachment/types.d.ts`)⇒ **SVG 进不去**。
19939
20037
  *
19940
- * 🔴 **开关**:由 `index.ts` 在 registration 处门控(**关时不注册** ⇒ 工具根本不进模型工具清单,
19941
- * 不占 token、不会被误调)—— 同 `skiff`/`acp` 的"未开启零资源占用"口径。
20038
+ * 🔴 **开关**:由 `index.ts` 门控(**关时不注册** ⇒ 工具根本不进模型工具清单),同 skiff/acp 口径。
19942
20039
  *
19943
20040
  * ## 🔴 两条**宿主契约**(2026-09-29 现场取证;第 ① 条是本文件第一版写错的地方)
19944
20041
  *
19945
20042
  * ① **`output.render` 是「同步投影」,宿主不 await 它**:`@deepseek-ai/dsh-tools` 的
19946
20043
  * `createSuccessResult()` 直接 `rendered = tool.output.render(args, value)`,紧接着
19947
20044
  * `snapshotJsonValue(rendered)` —— 返回一个 Promise 会被判「**非 lossless JSON**」⇒
19948
- * **整条工具调用失败**(图片与文字回执**一起**丢掉)。⚠️ 第一版把 async 实现 cast 成同步,
19949
- * 于是"拿不到图片块仍返回文字回执"那段自兜底**根本走不到**(它以为的失败模式不是真失败模式)。
20045
+ * **整条工具调用失败**(图片与文字回执**一起**丢掉)。
19950
20046
  * ② **图片块带的 `attachment` 必须是附件服务的 durable 引用**(`ImageAttachmentRef`),
19951
20047
  * 而存字节是 async ⇒ **只能分两段**:`execute` 里 `saveImage`(async,把引用拼进**纯 JSON** 的返回值),
19952
20048
  * `render` 里**同步**装配 `{ type: 'image', attachment: … }`。
19953
- * 这正是平台自家 `read_image` 的形态(本机 live 安装 `dsh-tool-fs` 取证:`imageRefFromValue` +
19954
- * `render: (_args, value) => imageReadContent(value)`),本文件照该形态实现。
20049
+ * 这正是平台自家 `read_image` 的形态(本机 live 安装 `dsh-tool-fs` 取证),本文件照该形态实现。
19955
20050
  *
19956
20051
  * ⚠️ 因此 render 里那个 `as unknown as ContentBlock` 是**必要的**(本包不引 `@deepseek-ai/dsh-attachment`,
19957
20052
  * 它不是 peerDep ⇒ 拿不到 `AttachmentId` 的 brand),而它**同时让类型检查对字段名变瞎**(判据 ㉜)⇒
@@ -19959,7 +20054,7 @@ async function rasterizeSvg(svg, width) {
19959
20054
  */
19960
20055
  /** 输出宽度缺省(矢量无损放大;高度按图自身比例推出) */
19961
20056
  const DEFAULT_WIDTH = 1200;
19962
- /** 时间戳:ISO → `YYYYMMDD-HHMMSS`(本地时区不参与,仅用于文件名排序) */
20057
+ /** 时间戳:ISO → `YYYYMMDD-HHMMSS`(仅降级落盘的文件名用) */
19963
20058
  function stamp() {
19964
20059
  return (/* @__PURE__ */ new Date()).toISOString().replace(/[-:]/g, "").replace("T", "-").slice(0, 15);
19965
20060
  }
@@ -19968,7 +20063,7 @@ function stamp() {
19968
20063
  *
19969
20064
  * 🔴 为什么这道门必须过(而不是"先附上试试"):图片块进的是**工具结果内容**,
19970
20065
  * 若路由不收图,最坏情形是**整轮请求**被装配层拒掉 —— 比"没图"坏得多。
19971
- * ⚠️ 与 `read_image` 唯一的差别:**这里不抛错**(图已落盘,改用文字给路径)。
20066
+ * ⚠️ 与 `read_image` 唯一的差别:**这里不抛错**(改走"降级落盘 + 如实说明")。
19972
20067
  */
19973
20068
  async function routeDeclaresImage(ctx, exec) {
19974
20069
  const agent = exec.agent;
@@ -19981,39 +20076,46 @@ async function routeDeclaresImage(ctx, exec) {
19981
20076
  const modes = (await llm.resolveModelInfo(provider, model, signal))?.inputModalities;
19982
20077
  return Array.isArray(modes) && modes.includes("image");
19983
20078
  }
20079
+ /** 降级落盘(**只在"贴不进对话"时走**):PNG 落 `<CCC>/_tmp/diagram/`,返回相对路径或 `null` */
20080
+ function saveFallbackPng(exec, png, base) {
20081
+ const root = cccRootForExec(exec);
20082
+ if (root === null) return null;
20083
+ try {
20084
+ const rel = join("_tmp", "diagram");
20085
+ const dir = join(root, rel);
20086
+ mkdirSync(dir, { recursive: true });
20087
+ writeFileSync(join(dir, `${base}.png`), png);
20088
+ return join(rel, `${base}.png`);
20089
+ } catch {
20090
+ return null;
20091
+ }
20092
+ }
19984
20093
  /**
19985
- * 落成**可进对话的图片块**所需的 durable 引用(async 段 —— 见文件头契约 ①)。
20094
+ * 把 PNG 落成**可进对话的图片块**所需的 durable 引用(async 段 —— 见文件头契约 ①)。
19986
20095
  *
19987
- * 三条降级**都不抛错、只把原因带回**(图已落盘,文字回执里给路径:
19988
- * 工具的价值不允许"附不上图"把整条回执一起带走):
20096
+ * 四条降级**都不抛错**(图的价值不允许"附不上图"把整条回执一起带走),且都**如实带回原因**;
20097
+ * 降级时顺手把 PNG 落盘一次(见文件头"唯一的例外"):
19989
20098
  * ① 附件服务不在(未挂载) ② 部署不收 `image/png` ③ 当前路由未声明图像输入 ④ `saveImage` 自身报错。
19990
20099
  */
19991
- async function attachPng(ctx, exec, png, name) {
19992
- const attachments = ctx.get?.("attachments");
19993
- if (attachments === void 0 || typeof attachments.saveImage !== "function") return {
19994
- image: null,
19995
- imageSkip: "附件服务未挂载(ctx.attachments 不在)"
19996
- };
19997
- if (!attachments.imageLimits.mediaTypes.includes("image/png")) return {
20100
+ async function resolveImage(ctx, exec, png, base) {
20101
+ const skip = (reason) => ({
19998
20102
  image: null,
19999
- imageSkip: "本部署不收 image/png"
20000
- };
20103
+ imageSkip: reason,
20104
+ fallbackRel: saveFallbackPng(exec, png, base)
20105
+ });
20106
+ const attachments = ctx.get?.("attachments");
20107
+ if (attachments === void 0 || typeof attachments.saveImage !== "function") return skip("附件服务未挂载(ctx.attachments 不在)");
20108
+ if (!attachments.imageLimits.mediaTypes.includes("image/png")) return skip("本部署不收 image/png");
20001
20109
  try {
20002
- if (!await routeDeclaresImage(ctx, exec)) return {
20003
- image: null,
20004
- imageSkip: "当前模型路由未声明图像输入(同 read_image 的门)"
20005
- };
20110
+ if (!await routeDeclaresImage(ctx, exec)) return skip("当前模型路由未声明图像输入(同 read_image 的门)");
20006
20111
  } catch (err) {
20007
- return {
20008
- image: null,
20009
- imageSkip: `模型路由未能解析:${String(err?.message ?? err)}`
20010
- };
20112
+ return skip(`模型路由未能解析:${String(err?.message ?? err)}`);
20011
20113
  }
20012
20114
  try {
20013
20115
  const ref = await attachments.saveImage({
20014
20116
  data: png,
20015
20117
  mediaType: "image/png",
20016
- name: `${name}.png`
20118
+ name: `${base}.png`
20017
20119
  });
20018
20120
  return {
20019
20121
  image: {
@@ -20022,21 +20124,19 @@ async function attachPng(ctx, exec, png, name) {
20022
20124
  bytes: ref.bytes,
20023
20125
  width: ref.width,
20024
20126
  height: ref.height,
20025
- name: ref.name ?? `${name}.png`
20127
+ name: ref.name ?? `${base}.png`
20026
20128
  },
20027
- imageSkip: null
20129
+ imageSkip: null,
20130
+ fallbackRel: null
20028
20131
  };
20029
20132
  } catch (err) {
20030
- return {
20031
- image: null,
20032
- imageSkip: `附件服务拒收:${String(err?.message ?? err)}`
20033
- };
20133
+ return skip(`附件服务拒收:${String(err?.message ?? err)}`);
20034
20134
  }
20035
20135
  }
20036
20136
  function createDiagramTool(ctx) {
20037
20137
  return defineTool({
20038
20138
  name: "diagram",
20039
- description: "Render a diagram from tag-format source into an image (in-process, no browser). Pass the source directly: a <diagram> root with <rect box=\"x0,y0,x1,y1\" label fill/>, <line>/<arrow from to> (endpoints may reference anchors like 德国.e or 波兰.n+3,0), <text x y align>, <color name value/>, <legend>. Returns the image in the conversation plus any authoring errors and four layout warnings (overlapping boxes / label overflow / dangling endpoints / a line crossing a label). A warning never blocks the image; an errored element is skipped and the rest still renders. Both the SVG and the PNG are written under _tmp/diagram/ in this container.",
20139
+ description: "Draw a diagram and show it to the user IN THE CONVERSATION (in-process, no browser, no files written). Pass the source directly as tag format. Geometry: coordinates are canvas pixels with y growing DOWNWARD; the drawing window defaults to the canvas (0,0 to width,height) — only pass domain=\"x0,y0,x1,y1\" if you want your own window. Tags: <rect id=\"A\" box=\"x0,y0,x1,y1\" label=\"...\" fill=\"#hex|colorName\"/> (name= works as an alias of id), <arrow from=\"A.e\" to=\"B.w\"/> or <line>, endpoints use anchors n/s/e/w/ne/nw/se/sw/c and may be offset like A.n+3,0, or a raw \"x,y\" pair; <text x=\"..\" y=\"..\" align=\"left|center|right\">text</text>, <color name=\"x\" value=\"#hex\"/>, <legend x=\"..\" y=\"..\"/>, <title>/<subtitle>. Returns the image itself plus any authoring errors and four layout warnings (overlapping boxes / label overflow / dangling endpoints / a line crossing a label). Elements whose coordinates fall outside the drawing window are reported as errors and skipped; the rest still render. Example:\n<diagram width=\"1000\" height=\"600\" title=\"三层\">\n <rect id=\"a\" box=\"100,60,400,140\" label=\"接入层\" fill=\"#dbe7f6\"/>\n <rect id=\"b\" box=\"100,200,400,280\" label=\"服务层\" fill=\"#dbe7f6\"/>\n <arrow from=\"a.s\" to=\"b.n\"/>\n</diagram>",
20040
20140
  parameters: {
20041
20141
  source: {
20042
20142
  type: "string",
@@ -20049,7 +20149,7 @@ function createDiagramTool(ctx) {
20049
20149
  },
20050
20150
  name: {
20051
20151
  type: "string",
20052
- description: "Optional short name used in the output file name"
20152
+ description: "Optional short name (attachment label; also used if a degraded fallback file is written)"
20053
20153
  }
20054
20154
  },
20055
20155
  output: {
@@ -20057,9 +20157,10 @@ function createDiagramTool(ctx) {
20057
20157
  render: (_args, value) => {
20058
20158
  const v = value;
20059
20159
  const lines = [`图已生成(${v.marks} 个图元 / ${v.pxW}×${v.pxH} px / ${v.bytes} B PNG / 光栅化 ${v.ms} ms)`];
20060
- lines.push(`SVG: ${v.svgRel}`);
20061
- lines.push(`PNG: ${v.pngRel}`);
20062
- if (v.imageSkip !== null) lines.push(`⚠️ 未附图片块:${v.imageSkip}(PNG 已落盘,路径见上)`);
20160
+ if (v.imageSkip !== null) {
20161
+ lines.push(`⚠️ 未能把图贴进对话:${v.imageSkip}`);
20162
+ lines.push(v.fallbackRel === null ? "(本会话解析不到 CCC 根,连降级落盘也没做 ⇒ 这一次没有可看的东西)" : `已降级落盘:${v.fallbackRel}(可直接打开,或用 read_image 读它)`);
20163
+ }
20063
20164
  if (v.errors.length > 0) {
20064
20165
  lines.push(`✗ 源码错误 ${v.errors.length} 条(图已尽力渲染;下列图元已跳过)`);
20065
20166
  for (const e of v.errors) lines.push(` [err] ${e}`);
@@ -20078,25 +20179,16 @@ function createDiagramTool(ctx) {
20078
20179
  }
20079
20180
  },
20080
20181
  async execute(args, exec) {
20081
- const root = cccRootForExec(exec);
20082
- if (root === null) throw new Error("diagram: 找不到 CCC 根(本会话缺 header.cwd)—— 产物必须落在容器内");
20083
20182
  const src = typeof args.source === "string" ? args.source : "";
20084
20183
  const name = typeof args.name === "string" && args.name.trim() !== "" ? args.name.trim() : "diagram";
20085
20184
  const width = typeof args.width === "number" && Number.isFinite(args.width) && args.width > 0 ? Math.round(args.width) : DEFAULT_WIDTH;
20086
20185
  const parsed = parseDsl(src);
20087
20186
  const rendered = renderDsl(parsed.model);
20088
20187
  const { png, ms, width: pxW, height: pxH } = await rasterizeSvg(rendered.svg, width);
20089
- const rel = join("_tmp", "diagram");
20090
- const dir = join(root, rel);
20091
- mkdirSync(dir, { recursive: true });
20092
20188
  const base = `${stamp()}-${name.replace(/[^\w\u4e00-\u9fa5-]/g, "_")}`;
20093
- writeFileSync(join(dir, `${base}.svg`), rendered.svg);
20094
- writeFileSync(join(dir, `${base}.png`), png);
20095
- const { image, imageSkip } = await attachPng(ctx, exec, png, base);
20189
+ const { image, imageSkip, fallbackRel } = await resolveImage(ctx, exec, png, base);
20096
20190
  return {
20097
20191
  name: base,
20098
- svgRel: join(rel, `${base}.svg`),
20099
- pngRel: join(rel, `${base}.png`),
20100
20192
  lint: rendered.lint,
20101
20193
  errors: [...parsed.errors, ...rendered.errors],
20102
20194
  marks: rendered.marks,
@@ -20105,7 +20197,8 @@ function createDiagramTool(ctx) {
20105
20197
  pxW,
20106
20198
  pxH,
20107
20199
  image,
20108
- imageSkip
20200
+ imageSkip,
20201
+ fallbackRel
20109
20202
  };
20110
20203
  }
20111
20204
  });
@@ -20414,19 +20507,50 @@ function apply(ctx, config) {
20414
20507
  * 归属与边界(E↑):**机制归 ACC**(工具面 + 开关 + 光栅化),**图的语义归调用方**
20415
20508
  * (标签语法与四项体检在 `src/diagram/`,权威设计 = 插件仓 `docs/diagram-tool-feasibility.md`)。
20416
20509
  *
20417
- * 🔴 **两条被实测钉住的接线前提**(改这里之前先读):
20418
- * ① 必须**晚于** `registerSettingsSection`(开关值经 `readSimpleSettings()`,源在那里接上);
20419
- * ② 注册失败**不得**成为启动单点(同 `hostContractReport` 的口径),但要**响亮**——
20420
- * 静默失败会让"拨了开关没反应"看起来像开关坏了(判据:**失败必须可见**)。
20510
+ * 🔴 **v1.51.1 修的真缺陷(owner 实测:「我把开关开了,工具没出来」)**:
20511
+ * 面板拨开关**只改配置、不重跑 `apply`**(铁证 = 插件重启才刷新"武装于";`cordis.patch.yml` 里
20512
+ * `diagramEnabled: true` 已落盘而工具没注册)⇒ 旧实现"apply 时判一次"等于**开关要重启才生效**。
20513
+ * ⇒ 现在改成**反应式**:
20514
+ * ① 听宿主的**真事件** `settings/document-updated`(ns = 我们那张设置页)——顺带说明为什么不用
20515
+ * 我们自己的 `serenity/settings-changed`:那个事件**全仓没有任何 `emit` 点**(只有 `ctx.on` 侧
20516
+ * 在用)=**死通道**,照着它接线等于接了个永远不响的铃;
20517
+ * ② 兜底再挂 `agent/created` / `session/created`(与 skiff/acp 同款做法,那两条在实测里确实会 fire)
20518
+ * —— 宿主事件面若换名,至少还有这一条能自愈;
20519
+ * ③ 启动时同步一次(apply 时按当前配置定初始态)。
20520
+ * 🔴 **关的时候要注销**(不是"留着但不响应"):与"关时不注册"同一口径 —— 工具不出现在模型清单里。
20421
20521
  * @param ctx - 插件上下文
20422
20522
  */
20423
20523
  function registerDiagramTool(ctx) {
20424
- if (!readSimpleSettings().diagramEnabled) return;
20524
+ /** 当前注册的 disposer(`null` = 未注册)。宿主 `ctx.tools.register` **返回注销器**。 */
20525
+ let dispose = null;
20526
+ const sync = () => {
20527
+ const on = readSimpleSettings().diagramEnabled;
20528
+ if (on && dispose === null) {
20529
+ try {
20530
+ dispose = ctx.tools.register(createDiagramTool(ctx));
20531
+ } catch (err) {
20532
+ console.error(`[serenity-hooks] ✗ diagram 工具注册失败: ${String(err?.message ?? err)}`);
20533
+ }
20534
+ return;
20535
+ }
20536
+ if (!on && dispose !== null) {
20537
+ try {
20538
+ dispose();
20539
+ } catch {}
20540
+ dispose = null;
20541
+ }
20542
+ };
20425
20543
  try {
20426
- ctx.tools.register(createDiagramTool(ctx));
20427
- } catch (err) {
20428
- console.error(`[serenity-hooks] ✗ diagram 工具注册失败: ${String(err?.message ?? err)}`);
20429
- }
20544
+ ctx.on("settings/document-updated", (ns) => {
20545
+ if (String(ns) === "serenity-hooks") sync();
20546
+ });
20547
+ } catch {}
20548
+ for (const eventName of ["agent/created", "session/created"]) try {
20549
+ ctx.on(eventName, () => {
20550
+ sync();
20551
+ });
20552
+ } catch {}
20553
+ sync();
20430
20554
  }
20431
20555
  /**
20432
20556
  * F4 Skiff 调试服务装配:启停 = 人工(设置面板 Skiff 区块开关,settings 持久化)。
@@ -68,6 +68,10 @@ var settings_section_exports = /* @__PURE__ */ __exportAll({
68
68
  simpleSettingsFromConfig: () => simpleSettingsFromConfig,
69
69
  unwrapVolatile: () => unwrapVolatile
70
70
  });
71
+ /**
72
+ * 设置面板命名空间 = **profile 条目 id**(v0.1.7 起;与旧 `settings.yaml` 段名逐字相同)。
73
+ */
74
+ const SERENITY_SETTINGS_NS = "serenity-hooks";
71
75
  /** 进程级默认(无 Config 可读时的兜底;与 Config 各字段的缺省一致) */
72
76
  function defaultSimpleSettings() {
73
77
  return {
@@ -218,4 +222,4 @@ function registerSettingsSection(ctx, config) {
218
222
  } catch {}
219
223
  }
220
224
  //#endregion
221
- export { FACE_PORTS as a, MECHANISM_PORTS as c, ACP_HTTP_PORT as i, SKIFF_DEBUG_PORT as l, registerSettingsSection as n, GATEWAY_PORT as o, settings_section_exports as r, MAIN_WEB_PORT as s, readSimpleSettings as t, WEIXIN_SEND_PORT as u };
225
+ export { ACP_HTTP_PORT as a, MAIN_WEB_PORT as c, WEIXIN_SEND_PORT as d, settings_section_exports as i, MECHANISM_PORTS as l, readSimpleSettings as n, FACE_PORTS as o, registerSettingsSection as r, GATEWAY_PORT as s, SERENITY_SETTINGS_NS as t, SKIFF_DEBUG_PORT as u };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@shgroup/dsh-serenity-hooks",
3
- "version": "1.51.0",
3
+ "version": "1.51.1",
4
4
  "description": "宁静号 ACC harness(Native Cordis 插件)——给 DeepSeek Harness 装一个「AI 工作区」:12 个工具(container_fs/container_trajectory/dashboard/container_git/msm/praxis/handyman/localstore/container_admin/im-bridge/acc-diag/diagram)+ 机械约束(安全模式/工作区围墙/密钥守卫/对外输出守卫)+ 工作日志与原地重建 + 网页登录入口/微信桥/子角色/对外问答页/trajectory 唤醒注册表。适配 DSH 0.1.7-rc.1。",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -33,6 +33,10 @@
33
33
  "assets/README.md",
34
34
  "assets/diagram/DroidSansFallbackFull.ttf",
35
35
  "assets/diagram/LICENSE-Apache-2.0.txt",
36
+ "assets/diagram/NotoSans-Regular.ttf",
37
+ "assets/diagram/LICENSE-OFL-1.1.txt",
38
+ "assets/diagram/DejaVuSans.ttf",
39
+ "assets/diagram/LICENSE-DejaVu.txt",
36
40
  "assets/diagram/PROVENANCE.md",
37
41
  "cordis.patch.yml",
38
42
  "dsh.plugin.json",