@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 +1 -1
- package/assets/diagram/DejaVuSans.ttf +0 -0
- package/assets/diagram/LICENSE-DejaVu.txt +78 -0
- package/assets/diagram/LICENSE-OFL-1.1.txt +155 -0
- package/assets/diagram/NotoSans-Regular.ttf +0 -0
- package/assets/diagram/PROVENANCE.md +80 -21
- package/dsh.plugin.json +1 -1
- package/lib/diagram/fonts.d.ts +40 -0
- package/lib/diagram/rasterize.d.ts +13 -6
- package/lib/index.js +222 -98
- package/lib/{settings-section-cq0eUSd-.js → settings-section-xHABl8nk.js} +5 -1
- package/package.json +5 -1
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
|
|
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
|
|
Binary file
|
|
@@ -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.
|
|
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
|
-
/**
|
|
1
|
+
/** 汉字字型资产(相对包根的路径)——**必须出现在 package.json 的 files 白名单里**(pack-check 会核对) */
|
|
2
2
|
export declare const DIAGRAM_FONT_ASSET = "assets/diagram/DroidSansFallbackFull.ttf";
|
|
3
|
-
/**
|
|
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
|
-
*
|
|
7
|
-
*
|
|
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
|
-
/**
|
|
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
|
|
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-
|
|
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:
|
|
19338
|
-
x1:
|
|
19339
|
-
y1:
|
|
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 === "
|
|
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 + (
|
|
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,
|
|
19693
|
-
s: [cx,
|
|
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,
|
|
19698
|
-
nw: [rx0,
|
|
19699
|
-
se: [rx1,
|
|
19700
|
-
sw: [rx0,
|
|
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="
|
|
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.
|
|
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.
|
|
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
|
-
|
|
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
|
-
|
|
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 =
|
|
19804
|
-
const LY =
|
|
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 +
|
|
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
|
-
*
|
|
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
|
-
/**
|
|
19865
|
-
const DIAGRAM_FONT_ASSET = "assets/diagram/DroidSansFallbackFull.ttf";
|
|
19866
|
-
/** 字体绝对路径(导出以便测试与 pack-check 共用同一判据来源) */
|
|
19937
|
+
/** 汉字字型绝对路径(导出以便测试与 pack-check 共用同一判据来源) */
|
|
19867
19938
|
function diagramFontPath() {
|
|
19868
|
-
return join(packageRootPath(),
|
|
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
|
-
*
|
|
19872
|
-
*
|
|
19873
|
-
* ⇒ 光栅化时必须由这里兜底,否则中文会退化成缺字方框。
|
|
19950
|
+
* 兜底字型的族名 = **汉字字型**(本工具的图以中文为主)。
|
|
19951
|
+
* 两个内置字型的家族名与 CSS 家族栈统一定义在 `fonts.ts`(单一真相源),此处只透出兜底那一个。
|
|
19874
19952
|
*/
|
|
19875
|
-
const DIAGRAM_FONT_FAMILY =
|
|
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
|
-
/**
|
|
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: [
|
|
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 +
|
|
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`
|
|
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
|
-
*
|
|
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`
|
|
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
|
|
19992
|
-
const
|
|
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:
|
|
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: `${
|
|
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 ?? `${
|
|
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: "
|
|
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
|
|
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
|
-
|
|
20061
|
-
|
|
20062
|
-
|
|
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
|
-
|
|
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
|
-
*
|
|
20419
|
-
*
|
|
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
|
-
|
|
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.
|
|
20427
|
-
|
|
20428
|
-
|
|
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 {
|
|
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.
|
|
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",
|