dsh-plugin-image-tools 0.6.6 → 0.6.7
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/CHANGELOG.md +8 -0
- package/README.en.md +12 -2
- package/README.md +20 -2
- package/docs/banner.svg +25 -0
- package/lib/client.js +30 -15
- package/lib/index.js +16 -7
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,13 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [0.6.7] - 2026-09-11
|
|
4
|
+
|
|
5
|
+
- 修复:多图消息的加载失败降级提示此前会错误标到最后一张图,现逐图对应;
|
|
6
|
+
- 修复:放大层重复打开不再泄漏键盘 / 缩放监听;
|
|
7
|
+
- 修复:聊天内图片条改为叠加插入方式,不干扰 React 渲染;
|
|
8
|
+
- 安全:`save_received_images` 的 `dir` 参数拒绝绝对路径与 `..`(仅允许工作区内相对路径);
|
|
9
|
+
- 加固:图片服务路由错误处理加固。
|
|
10
|
+
|
|
3
11
|
## [0.6.6] - 2026-09-04
|
|
4
12
|
|
|
5
13
|
- 文档:中英 README 新增「兼容性」节——实测于 dsh 0.1.2-rc.1;选择卡 0.6.5 起适配
|
package/README.en.md
CHANGED
|
@@ -1,11 +1,17 @@
|
|
|
1
|
-
|
|
1
|
+
<p align="center">
|
|
2
|
+
<img src="docs/banner.svg" alt="dsh-plugin-image-tools banner" width="100%">
|
|
3
|
+
</p>
|
|
2
4
|
|
|
3
5
|
# dsh-plugin-image-tools
|
|
4
6
|
|
|
5
7
|

|
|
8
|
+

|
|
6
9
|

|
|
10
|
+

|
|
7
11
|

|
|
8
12
|
|
|
13
|
+
[中文](./README.md) | **English**
|
|
14
|
+
|
|
9
15
|
**The only plugin in the dsh market supporting image choice cards**: it adds image
|
|
10
16
|
capabilities to the DeepSeek Harness Web GUI, with three tools covering three scenarios —
|
|
11
17
|
the model has you pick images among options, displays images in the reply body, and you
|
|
@@ -66,7 +72,10 @@ the body, so the images appear alongside the text.
|
|
|
66
72
|
workspace files by attachmentId (default directory `received/`), where file/command
|
|
67
73
|
tools can then analyze them (dimensions, pixels, hashes, etc.);
|
|
68
74
|
- Saved filenames prefer the attachment's own safe filename, otherwise they are
|
|
69
|
-
generated as `image-<n>-<timestamp>.<ext
|
|
75
|
+
generated as `image-<n>-<timestamp>.<ext>`;
|
|
76
|
+
- The `dir` parameter accepts only **relative paths inside the workspace**
|
|
77
|
+
(default `received/`); absolute paths and paths containing `..` are rejected
|
|
78
|
+
(security hardening, preventing writes outside the workspace).
|
|
70
79
|
- Text-only questions without images are passed straight through to the native UI
|
|
71
80
|
automatically; the two never interfere.
|
|
72
81
|
|
|
@@ -248,6 +257,7 @@ series can be used together:
|
|
|
248
257
|
| [dsh-plugin-table-zoom](https://www.npmjs.com/package/dsh-plugin-table-zoom) | [GitHub repo](https://github.com/Pasumao/dsh-plugin-table-zoom) | Floating viewer for long chat tables + one-click copy as Markdown |
|
|
249
258
|
| [dsh-plugin-windows-guard](https://www.npmjs.com/package/dsh-plugin-windows-guard) | [GitHub repo](https://github.com/Pasumao/dsh-plugin-windows-guard) | Windows environment safeguards: rules skills + garbled-text detection / dangerous-write blocking / encoding diagnosis & repair |
|
|
250
259
|
| [dsh-plugin-workbench](https://www.npmjs.com/package/dsh-plugin-workbench) | [GitHub repo](https://github.com/Pasumao/dsh-plugin-workbench) | VS Code-style file explorer + editable preview |
|
|
260
|
+
| [dsh-plugin-context-trim](https://www.npmjs.com/package/dsh-plugin-context-trim) | [GitHub repo](https://github.com/Pasumao/dsh-plugin-context-trim) | Per-session injection gate: trim skills / tools / prompt sections per session |
|
|
251
261
|
|
|
252
262
|
> For the rest of the series, see [Pasumao · dsh plugins](https://github.com/Pasumao);
|
|
253
263
|
> if you find them useful, feel free to give a ⭐ on GitHub.
|
package/README.md
CHANGED
|
@@ -1,7 +1,13 @@
|
|
|
1
|
+
<p align="center">
|
|
2
|
+
<img src="docs/banner.svg" alt="dsh-plugin-image-tools banner" width="100%">
|
|
3
|
+
</p>
|
|
4
|
+
|
|
1
5
|
# dsh-plugin-image-tools
|
|
2
6
|
|
|
3
7
|

|
|
8
|
+

|
|
4
9
|

|
|
10
|
+

|
|
5
11
|

|
|
6
12
|
|
|
7
13
|
[**中文**](./README.md) | [English](./README.en.md)
|
|
@@ -53,7 +59,9 @@
|
|
|
53
59
|
`/dsh-plugin-image-tools/attachment/<id>` 路由出字节);
|
|
54
60
|
- 模型看到占位符后调用 `save_received_images`,把图片按 attachmentId 保存为
|
|
55
61
|
工作区文件(默认 `received/`),之后可用文件/命令工具分析(尺寸、像素、哈希等);
|
|
56
|
-
- 保存的文件名优先用附件自带的安全文件名,否则按 `image-<n>-<时间戳>.<ext>`
|
|
62
|
+
- 保存的文件名优先用附件自带的安全文件名,否则按 `image-<n>-<时间戳>.<ext>` 生成;
|
|
63
|
+
- `dir` 参数仅接受**工作区内相对路径**(默认 `received/`),绝对路径与含 `..`
|
|
64
|
+
的路径一律拒绝(安全加固,防止越出工作区写文件)。
|
|
57
65
|
- 纯文字问题不带图片时,客户端自动放行给原生 UI,互不影响。
|
|
58
66
|
|
|
59
67
|
## 目录结构
|
|
@@ -149,6 +157,9 @@ npm install
|
|
|
149
157
|
|
|
150
158
|
## 实现要点(为什么是插件而不是改核心)
|
|
151
159
|
|
|
160
|
+
<details>
|
|
161
|
+
<summary><b>展开实现细节</b></summary>
|
|
162
|
+
|
|
152
163
|
浏览器端消费 `question/requested` 帧时用 zod schema 严格解析,选项对象上的未知
|
|
153
164
|
字段会被剥离;助手消息 content 由模型文本生成,也没有携带结构化图片块的通道。
|
|
154
165
|
所以图片**不能**塞进 option / content 字段。本插件改为:
|
|
@@ -174,8 +185,14 @@ npm install
|
|
|
174
185
|
|
|
175
186
|
详见 `设计说明.md`。
|
|
176
187
|
|
|
188
|
+
</details>
|
|
189
|
+
|
|
177
190
|
## 安全与限制
|
|
178
191
|
|
|
192
|
+
- **llm 能力补丁排他性**:为放行盲模型收图,宿主侧会改写 `llmService.resolveModelInfo`
|
|
193
|
+
(给模型能力补上 image 输入模态)。该 patch 是**排他**的——不能与其它同样改写
|
|
194
|
+
`resolveModelInfo` 的插件共存(后装者会覆盖先装者的补丁);卸载时本插件只在
|
|
195
|
+
当前实现仍是自己的补丁时才还原,不会拆掉其它插件的补丁。
|
|
179
196
|
- 图片字节仅存于进程内存:选择卡图片随问题回答/取消立即释放;
|
|
180
197
|
内嵌图片与附件回显依赖 30 分钟 TTL 清理(需存活到回复渲染完成)。
|
|
181
198
|
- **浏览器缓存兜底**:图片路由的 URL 是内容寻址的(pickId/showId 为 UUID、
|
|
@@ -211,6 +228,7 @@ npm install
|
|
|
211
228
|
| [dsh-plugin-table-zoom](https://www.npmjs.com/package/dsh-plugin-table-zoom) | [GitHub 仓库](https://github.com/Pasumao/dsh-plugin-table-zoom) | 聊天长表格浮窗查看 + 一键复制 Markdown |
|
|
212
229
|
| [dsh-plugin-windows-guard](https://www.npmjs.com/package/dsh-plugin-windows-guard) | [GitHub 仓库](https://github.com/Pasumao/dsh-plugin-windows-guard) | Windows 环境防坑:守则技能 + 乱码检测 / 危险写拦截 / 编码诊断修复 |
|
|
213
230
|
| [dsh-plugin-workbench](https://www.npmjs.com/package/dsh-plugin-workbench) | [GitHub 仓库](https://github.com/Pasumao/dsh-plugin-workbench) | VS Code 风格文件浏览器 + 可编辑预览 |
|
|
231
|
+
| [dsh-plugin-context-trim](https://www.npmjs.com/package/dsh-plugin-context-trim) | [GitHub 仓库](https://github.com/Pasumao/dsh-plugin-context-trim) | 会话注入门控:skill / tool / 提示词段落按会话裁剪 |
|
|
214
232
|
|
|
215
233
|
> 本系列其余插件见 [Pasumao · dsh 插件](https://github.com/Pasumao);觉得好用欢迎到 GitHub 点 ⭐。
|
|
216
234
|
|
|
@@ -219,6 +237,6 @@ npm install
|
|
|
219
237
|
代码与文档由 AI 辅助生成(DeepSeek Harness),均经人工审查与实机验证
|
|
220
238
|
(`npm run smoke`:selfcheck + 假 ctx 服务端全链路 + 假客户端渲染)。
|
|
221
239
|
|
|
222
|
-
##
|
|
240
|
+
## License
|
|
223
241
|
|
|
224
242
|
[MIT](./LICENSE)
|
package/docs/banner.svg
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" width="1200" height="260" viewBox="0 0 1200 260" role="img" aria-label="dsh-plugin-image-tools banner">
|
|
2
|
+
<defs>
|
|
3
|
+
<linearGradient id="bg" x1="0" y1="0" x2="1" y2="1">
|
|
4
|
+
<stop offset="0" stop-color="#0a1830"/>
|
|
5
|
+
<stop offset="1" stop-color="#102a4c"/>
|
|
6
|
+
</linearGradient>
|
|
7
|
+
<radialGradient id="glow" cx="0.85" cy="0.1" r="0.9">
|
|
8
|
+
<stop offset="0" stop-color="#F472B6" stop-opacity="0.22"/>
|
|
9
|
+
<stop offset="1" stop-color="#F472B6" stop-opacity="0"/>
|
|
10
|
+
</radialGradient>
|
|
11
|
+
<linearGradient id="whale" x1="0" y1="0" x2="0" y2="1">
|
|
12
|
+
<stop offset="0" stop-color="#ffffff"/>
|
|
13
|
+
<stop offset="1" stop-color="#cfe3ff"/>
|
|
14
|
+
</linearGradient>
|
|
15
|
+
</defs>
|
|
16
|
+
<rect width="1200" height="260" rx="18" fill="url(#bg)"/>
|
|
17
|
+
<rect width="1200" height="260" rx="18" fill="url(#glow)"/>
|
|
18
|
+
<rect x="0" y="0" width="1200" height="6" rx="3" fill="#F472B6" opacity="0.85"/>
|
|
19
|
+
<g transform="translate(64,80) scale(2.0)">
|
|
20
|
+
<path d="M48.8354 10.0479C48.3232 9.79199 48.1025 10.2798 47.8032 10.5278C47.7007 10.6079 47.6143 10.7119 47.5273 10.8076C46.7793 11.624 45.9048 12.1597 44.7622 12.0957C43.0923 12 41.666 12.5356 40.4058 13.8398C40.1377 12.2319 39.2476 11.272 37.8926 10.6558C37.1836 10.3359 36.4668 10.0156 35.9702 9.31982C35.6235 8.82373 35.5293 8.27197 35.356 7.72754C35.2456 7.3999 35.1353 7.06396 34.7651 7.00781C34.3633 6.94385 34.2056 7.2876 34.0479 7.57568C33.418 8.75195 33.1733 10.0479 33.1973 11.3599C33.2524 14.312 34.4736 16.6641 36.8999 18.3359C37.1758 18.5278 37.2466 18.7197 37.1597 19C36.9946 19.5757 36.7974 20.1357 36.624 20.7119C36.5137 21.0801 36.3486 21.1597 35.9624 21C34.6309 20.4321 33.481 19.5918 32.4644 18.5757C30.7393 16.8721 29.1792 14.9917 27.2334 13.52C26.7764 13.1758 26.3193 12.856 25.8467 12.5518C23.8618 10.584 26.1069 8.96777 26.627 8.77588C27.1704 8.57568 26.8159 7.8877 25.0591 7.896C23.3022 7.90381 21.6953 8.50391 19.647 9.30371C19.3477 9.42383 19.0322 9.51172 18.7095 9.58398C16.8501 9.22363 14.9199 9.14355 12.9033 9.37598C9.10596 9.80762 6.07275 11.6396 3.84326 14.7681C1.16455 18.5278 0.53418 22.7998 1.30664 27.2559C2.11768 31.9521 4.46582 35.8398 8.07373 38.8799C11.8159 42.0322 16.1255 43.5762 21.041 43.2803C24.0269 43.104 27.3516 42.6963 31.1016 39.4561C32.0469 39.936 33.0396 40.1279 34.686 40.272C35.9546 40.3921 37.1758 40.208 38.1211 40.0078C39.6021 39.688 39.4995 38.2881 38.9639 38.0322C34.623 35.9678 35.5762 36.8081 34.71 36.1279C36.9155 33.4639 40.2402 30.6958 41.54 21.728C41.6426 21.0161 41.5557 20.5679 41.54 19.9917C41.5322 19.6396 41.6108 19.5039 42.0049 19.4639C43.0923 19.3359 44.1479 19.0317 45.1167 18.4878C47.9292 16.9199 49.064 14.3438 49.3315 11.2559C49.3711 10.7837 49.3237 10.2959 48.8354 10.0479ZM24.3262 37.8398C20.1196 34.4639 18.0791 33.3521 17.2358 33.3999C16.4482 33.4482 16.5898 34.3682 16.7632 34.9678C16.9443 35.5601 17.1812 35.9683 17.5117 36.4878C17.7402 36.832 17.8979 37.3442 17.2832 37.728C15.9282 38.584 13.5728 37.4399 13.4624 37.3838C10.7207 35.7358 8.42822 33.5601 6.81348 30.584C5.25342 27.7197 4.34766 24.6479 4.19775 21.3677C4.1582 20.5757 4.38672 20.2959 5.15869 20.1519C6.17529 19.96 7.22314 19.9199 8.23926 20.0718C12.5327 20.7119 16.1885 22.6719 19.2529 25.7759C21.002 27.5439 22.3252 29.6558 23.6885 31.7202C25.1377 33.9121 26.6978 36 28.6831 37.7119C29.3843 38.312 29.9434 38.7681 30.479 39.104C28.8643 39.2881 26.1699 39.3281 24.3262 37.8398ZM26.3433 24.6001C26.3433 24.248 26.6191 23.9678 26.9658 23.9678C27.0444 23.9678 27.1152 23.9839 27.1782 24.0078C27.2651 24.04 27.3438 24.0879 27.4067 24.1602C27.5171 24.272 27.5801 24.4321 27.5801 24.6001C27.5801 24.9521 27.3042 25.2319 26.9575 25.2319C26.6108 25.2319 26.3433 24.9521 26.3433 24.6001ZM32.6064 27.8799C32.2046 28.0479 31.8027 28.1919 31.4165 28.208C30.8179 28.2397 30.1641 27.9922 29.8096 27.688C29.2583 27.2158 28.8643 26.9521 28.6987 26.1279C28.6279 25.7759 28.6675 25.2319 28.7305 24.9199C28.8721 24.248 28.7144 23.8159 28.2495 23.4238C27.8716 23.104 27.3911 23.0161 26.8633 23.0161C26.666 23.0161 26.4849 22.9277 26.3511 22.856C26.1304 22.7441 25.9492 22.4639 26.1226 22.1201C26.1777 22.0078 26.4458 21.7358 26.5088 21.688C27.2256 21.272 28.0527 21.4077 28.8169 21.7197C29.5259 22.0161 30.0615 22.5601 30.834 23.3281C31.6216 24.2559 31.7632 24.5117 32.2124 25.208C32.5669 25.752 32.8901 26.312 33.1104 26.9521C33.2446 27.3521 33.0713 27.6802 32.6064 27.8799Z" fill="url(#whale)"/>
|
|
21
|
+
</g>
|
|
22
|
+
<text x="212" y="86" font-family="'Segoe UI','PingFang SC','Microsoft YaHei',sans-serif" font-size="17" font-weight="600" letter-spacing="5" fill="#F472B6">DEEPSEEK HARNESS · DSH PLUGIN</text>
|
|
23
|
+
<text x="212" y="152" font-family="ui-monospace,'Cascadia Code',Consolas,monospace" font-size="46" font-weight="700" fill="#f4f8ff">dsh-plugin-image-tools</text>
|
|
24
|
+
<text x="212" y="204" font-family="'Segoe UI','PingFang SC','Microsoft YaHei',sans-serif" font-size="22" fill="#9fb3c8">图片选择卡 · 回复内嵌图片 · 盲模型收图 · 零 token 本地渲染</text>
|
|
25
|
+
</svg>
|
package/lib/client.js
CHANGED
|
@@ -83,6 +83,7 @@ window.__ModuleLoader__.load({
|
|
|
83
83
|
'.dshpick-lightboxCap{color:var(--dsw-alias-label-primary);font-size:13px;line-height:18px;text-align:center;overflow-wrap:anywhere}',
|
|
84
84
|
'.dshpick-lightboxCap small{display:block;color:var(--dsw-alias-label-secondary);font-size:11px;line-height:15px;margin-top:2px}',
|
|
85
85
|
'.dshimg-inline{max-width:min(100%,560px);max-height:420px;width:auto;height:auto;border-radius:14px;border:1px solid var(--dsw-alias-border-l2-darkmode-thin);background:var(--dsw-alias-interactive-bg-hover);margin:8px 0;display:block;cursor:zoom-in;object-fit:contain}',
|
|
86
|
+
'.dshimg-strip{display:block}',
|
|
86
87
|
'.dshimg-inline[data-failed]{opacity:.45;cursor:default;filter:grayscale(1)}',
|
|
87
88
|
'.dshimg-zoom{cursor:zoom-in}',
|
|
88
89
|
'@media (width<=720px){.dshpick-card{border-radius:16px}.dshpick-cards{grid-template-columns:repeat(auto-fill,minmax(104px,1fr))}.dshpick-lightbox{padding:44px 12px 20px}}'
|
|
@@ -552,10 +553,14 @@ window.__ModuleLoader__.load({
|
|
|
552
553
|
/** 文本节点扫描去重表:node → 上次扫描到的 nodeValue(React 原位更新文本时重新扫描)。 */
|
|
553
554
|
var scannedTexts = typeof WeakMap === 'undefined' ? null : new WeakMap();
|
|
554
555
|
|
|
556
|
+
/** 文本节点 → 已插入的图片条(React 原位更新时复用/重建,不重复插入)。 */
|
|
557
|
+
var stripFor = typeof WeakMap === 'undefined' ? null : new WeakMap();
|
|
558
|
+
|
|
555
559
|
/**
|
|
556
|
-
* 把文本节点里的 dshimg:<id>
|
|
560
|
+
* 把文本节点里的 dshimg:<id> 占位符渲染为图片条(盲模型收图回显)。
|
|
557
561
|
* 点击放大交给 document 级委托处理,这里只建图并做加载失败降级。
|
|
558
|
-
*
|
|
562
|
+
* 不替换、不移除 React 管理的文本节点(replaceChild 会与 React 协调器竞态):
|
|
563
|
+
* 在原节点旁以兄弟节点方式插入自建图片条,原 markdown 文本原样保留。
|
|
559
564
|
*/
|
|
560
565
|
function upgradeBubbleTexts() {
|
|
561
566
|
if (typeof document === 'undefined' || scannedTexts === null || typeof document.createTreeWalker === 'undefined') return;
|
|
@@ -572,25 +577,29 @@ window.__ModuleLoader__.load({
|
|
|
572
577
|
}
|
|
573
578
|
var parent = node.parentNode;
|
|
574
579
|
if (parent === null) continue;
|
|
575
|
-
var
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
if (
|
|
581
|
-
|
|
580
|
+
var strip = stripFor !== null ? stripFor.get(node) : undefined;
|
|
581
|
+
if (strip === undefined || strip === null) {
|
|
582
|
+
strip = document.createElement('span');
|
|
583
|
+
strip.className = 'dshimg-strip';
|
|
584
|
+
strip.dataset.dshimgStrip = '1';
|
|
585
|
+
if (stripFor !== null) stripFor.set(node, strip);
|
|
586
|
+
if (node.nextSibling !== null) parent.insertBefore(strip, node.nextSibling);
|
|
587
|
+
else parent.appendChild(strip);
|
|
588
|
+
}
|
|
589
|
+
while (strip.firstChild !== null) strip.removeChild(strip.firstChild);
|
|
590
|
+
for (var i = 0; i < ids.length; i++) {
|
|
591
|
+
// let:每轮迭代独立绑定,多图加载失败的降级标记不会都落到最后一张。
|
|
592
|
+
let img = document.createElement('img');
|
|
582
593
|
img.className = 'dshimg-inline dshimg-received';
|
|
583
594
|
img.dataset.dshimg = '1';
|
|
584
|
-
img.src = ATTACHMENT_PREFIX +
|
|
595
|
+
img.src = ATTACHMENT_PREFIX + ids[i];
|
|
585
596
|
img.alt = '\u7528\u6237\u53d1\u6765\u7684\u56fe\u7247';
|
|
586
597
|
img.title = '\u7528\u6237\u53d1\u6765\u7684\u56fe\u7247\uff08\u70b9\u51fb\u653e\u5927\uff09';
|
|
587
598
|
img.loading = 'lazy';
|
|
588
599
|
img.addEventListener('error', function () { img.dataset.failed = '1'; });
|
|
589
|
-
|
|
590
|
-
cursor = m.index + m[0].length;
|
|
600
|
+
strip.appendChild(img);
|
|
591
601
|
}
|
|
592
|
-
|
|
593
|
-
parent.replaceChild(frag, node);
|
|
602
|
+
scannedTexts.set(node, value);
|
|
594
603
|
}
|
|
595
604
|
}
|
|
596
605
|
|
|
@@ -695,7 +704,12 @@ window.__ModuleLoader__.load({
|
|
|
695
704
|
function openImageZoom(src, caption) {
|
|
696
705
|
if (typeof document === 'undefined') return;
|
|
697
706
|
var existing = document.querySelector('.dshpick-lightbox[data-dshimg-zoom]');
|
|
698
|
-
if (existing !== null)
|
|
707
|
+
if (existing !== null) {
|
|
708
|
+
// 复用旧实例登记的关闭函数:保证 document/window 监听与 body overflow
|
|
709
|
+
// 成对还原;旧实例没有登记(异常态)才直接摘除。
|
|
710
|
+
if (typeof existing.__dshimgCloseZoom === 'function') existing.__dshimgCloseZoom();
|
|
711
|
+
else existing.remove();
|
|
712
|
+
}
|
|
699
713
|
var labels = zoomLabels();
|
|
700
714
|
var overlay = document.createElement('div');
|
|
701
715
|
overlay.className = 'dshpick-lightbox';
|
|
@@ -841,6 +855,7 @@ window.__ModuleLoader__.load({
|
|
|
841
855
|
document.removeEventListener('keydown', onKey);
|
|
842
856
|
window.removeEventListener('resize', applyZoom);
|
|
843
857
|
};
|
|
858
|
+
overlay.__dshimgCloseZoom = closeZoom;
|
|
844
859
|
var onKey = function (event) {
|
|
845
860
|
if (event.key === 'Escape') closeZoom();
|
|
846
861
|
else if (event.key === '+' || event.key === '=') { event.preventDefault(); zoomBy(1.25); }
|
package/lib/index.js
CHANGED
|
@@ -729,6 +729,10 @@ export function saveReceivedImagesTool(ctx) {
|
|
|
729
729
|
const cwd = sessionCwd(exec?.agent)
|
|
730
730
|
pruneAttachmentRefs()
|
|
731
731
|
const dirName = typeof args.dir === 'string' && args.dir.trim() !== '' ? args.dir.trim() : RECEIVED_DIR
|
|
732
|
+
// 只允许相对路径且不得包含 ..(防越出工作区);非法直接报工具错误。
|
|
733
|
+
if (isAbsolute(dirName) || dirName.includes('..')) {
|
|
734
|
+
throw new Error(`dir 必须是相对会话工作区的相对路径且不得包含 "..",收到:${dirName}`)
|
|
735
|
+
}
|
|
732
736
|
const items = Array.isArray(args.images) ? args.images : []
|
|
733
737
|
if (items.length === 0) throw new Error('images 不能为空')
|
|
734
738
|
|
|
@@ -753,7 +757,7 @@ export function saveReceivedImagesTool(ctx) {
|
|
|
753
757
|
}
|
|
754
758
|
const stored = await ctx.attachments.readImage(ref, exec?.signal)
|
|
755
759
|
const data = Buffer.from(stored.data)
|
|
756
|
-
const targetDir =
|
|
760
|
+
const targetDir = join(cwd, dirName)
|
|
757
761
|
await mkdir(targetDir, { recursive: true })
|
|
758
762
|
const ext = mediaTypeToExt(ref.mediaType)
|
|
759
763
|
const rawName = typeof ref.name === 'string' ? basename(ref.name) : ''
|
|
@@ -788,7 +792,7 @@ export function apply(ctx) {
|
|
|
788
792
|
// /dsh-plugin-image-tools/<pickId>/<index> 选择卡图片
|
|
789
793
|
// /dsh-plugin-image-tools/show/<showId>/<index> 回复内嵌图片
|
|
790
794
|
// /dsh-plugin-image-tools/attachment/<attachmentId> 盲模型收图的附件回显
|
|
791
|
-
|
|
795
|
+
ctx.effect(() => ctx.webServer.register({
|
|
792
796
|
kind: 'prefix',
|
|
793
797
|
path: ROUTE_PREFIX,
|
|
794
798
|
handler: async (req, res) => {
|
|
@@ -844,8 +848,13 @@ export function apply(ctx) {
|
|
|
844
848
|
})
|
|
845
849
|
res.end(image.bytes)
|
|
846
850
|
} catch (error) {
|
|
847
|
-
|
|
848
|
-
res.
|
|
851
|
+
// 响应头已发出时只能掐断连接(writeHead 二次调用会抛 ERR_HTTP_HEADERS_SENT)。
|
|
852
|
+
if (!res.headersSent) {
|
|
853
|
+
res.writeHead(500, { 'content-type': 'text/plain; charset=utf-8' })
|
|
854
|
+
res.end('internal error')
|
|
855
|
+
} else {
|
|
856
|
+
res.destroy()
|
|
857
|
+
}
|
|
849
858
|
}
|
|
850
859
|
}
|
|
851
860
|
}), 'dsh-plugin-image-tools: image route')
|
|
@@ -891,13 +900,13 @@ export function apply(ctx) {
|
|
|
891
900
|
}
|
|
892
901
|
|
|
893
902
|
ctx.effect(() => () => {
|
|
894
|
-
disposeRoute()
|
|
895
903
|
disposeChoice()
|
|
896
904
|
disposeShow()
|
|
897
905
|
disposeSave()
|
|
898
906
|
disposePreStep()
|
|
899
|
-
// 卸载时还原 llm
|
|
900
|
-
|
|
907
|
+
// 卸载时还原 llm 能力补丁:仅当当前实现仍是我们打的补丁(排他 patch,
|
|
908
|
+
// 若其它插件在其后又改写了该方法则不动它,避免拆掉别人的补丁)。
|
|
909
|
+
if (originalResolveModelInfo !== null && llmService.resolveModelInfo === patchedResolveModelInfo) {
|
|
901
910
|
llmService.resolveModelInfo = originalResolveModelInfo
|
|
902
911
|
}
|
|
903
912
|
picks.clear()
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-plugin-image-tools",
|
|
3
3
|
"description": "DSH 图片插件,三个工具覆盖三种场景:ask_user_choice 图片/图文混合选择卡(Web GUI 渲染,可放大查看)+ show_images 回复内嵌图片(图文混排)+ save_received_images 盲模型收图存为工作区文件;聊天栏所有图片点击放大,支持滚轮缩放与拖拽平移。来源支持本地路径 / http(s) URL / base64 data URI。零 token 本地渲染,纯插件实现不改核心包。",
|
|
4
|
-
"version": "0.6.
|
|
4
|
+
"version": "0.6.7",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "lib/index.js",
|
|
7
7
|
"exports": {
|