@nonefire/dsh-drender-plugin 0.0.0-stage → 0.1.2
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/LICENSE +21 -0
- package/README.md +38 -2
- package/cordis.patch.yml +3 -0
- package/lib/client.js +138 -0
- package/lib/index.js +5377 -0
- package/lib/skills/drender-report/SKILL.md +458 -0
- package/package.json +99 -3
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 NoneFire
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -1,3 +1,39 @@
|
|
|
1
|
-
#
|
|
1
|
+
# @nonefire/dsh-drender-plugin
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
为 DeepSeek Harness 提供 drender 适配器,注册 `drender_validate`、`drender_render` 两个工具,并注册运行时 Skill `drender-report`。
|
|
4
|
+
|
|
5
|
+
通用 Skill 源文件为 [`../../../.agents/skills/drender-report/SKILL.md`](../../../.agents/skills/drender-report/SKILL.md)。构建时会复制到 `lib/skills/drender-report/SKILL.md`,请只维护共享源文件。
|
|
6
|
+
|
|
7
|
+
## 环境要求
|
|
8
|
+
|
|
9
|
+
- Node.js 22.13 或更高版本。
|
|
10
|
+
- DSH 已挂载公开的 `@deepseek-ai/dsh-tools`、`@deepseek-ai/dsh-skill` 服务,并启用了供模型加载 Skill 的消费者。
|
|
11
|
+
- 读取 Markdown frontmatter 中声明的 SQLite 与 SQL 文件需要 DSH Session 工作区文件系统服务。
|
|
12
|
+
- 侧栏 HTML 预览需要 Session 工作区;Web profile 还需要 DSH 的 `ui-tool`、`ui-slots`、`ui-renderer` 和 `ui-primitives` 模块。附件服务是可选的。
|
|
13
|
+
|
|
14
|
+
插件仅依赖 DSH 公布的 Cordis、tools、skill、attachment 和客户端 UI API,不导入 DSH 应用源码或私有包。
|
|
15
|
+
|
|
16
|
+
## 安装
|
|
17
|
+
|
|
18
|
+
发布后,将插件安装到所选 profile:
|
|
19
|
+
|
|
20
|
+
```sh
|
|
21
|
+
dsh plugin --profile web add @nonefire/dsh-drender-plugin
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
Renderer、CSV/SQLite 数据源和主题代码均已打进这个插件包,不需要单独安装 drender 的其他 npm 包。图表与 Markdown 解析器等第三方运行时由 npm 自动安装;DSH 自身 API 由宿主提供。在源码开发环境中,将 `cordis.patch.yml` 的插件条目加入 profile 配置;稳定 ID 为 `md-report-dsh`,名称必须与包名及 `src/index.ts` 导出一致。
|
|
25
|
+
|
|
26
|
+
## 工具
|
|
27
|
+
|
|
28
|
+
- `drender_validate`:校验必需 frontmatter、dashboard/panel 结构、组件配置、数据集引用、安全链接和图片资源。诊断包含行号与报告路径。
|
|
29
|
+
- `drender_render`:生成包含主题和所需图表运行时的自包含 HTML,保存 Markdown 源文件;宿主支持时还会生成右侧栏预览和下载附件。若 HTML 存储不可用,工具会返回有长度上限的 Markdown 预览。
|
|
30
|
+
|
|
31
|
+
数据组件从声明的只读 SQLite 查询或显式传入的快照读取。Frontmatter 可声明一个 SQLite 文件,并通过 SQL sidecar、命名 view 或短内联查询声明数据集和 Excalidraw 场景。数据库与 SQL 路径必须位于 Session 工作区内。查询只允许只读语句,每个数据集最多 5,000 行,SQL 文件最多 100 KB,图片输入总量最多 8 MB,数据快照总量上限为 1 MB。
|
|
32
|
+
|
|
33
|
+
`drender_render` 会把渲染后的 HTML 与对应快照嵌入导出文件,因此离线可用。内联 `datasets` 或 `assets` 参数不会写回 Markdown,会产生可移植性提示;可重复使用的报告应保留 Markdown、SQLite 文件、SQL sidecar 和图片资源。
|
|
34
|
+
|
|
35
|
+
## 组件覆盖
|
|
36
|
+
|
|
37
|
+
支持普通 Markdown、自动目录、提示条、12 列 dashboard 面板、正文分栏和折叠详情;KPI、ECharts、Tabulator 数据表、SQL、Mermaid、AntV Infographic、Excalidraw SVG、AntV T8 和声明式图片资源。图表、数据表和 Excalidraw 数据可通过 frontmatter 声明的 SQLite 数据源提供。
|
|
38
|
+
|
|
39
|
+
导出 HTML 内嵌样式、必要的开源可视化运行时和所引用资源,使用基于哈希的内容安全策略。导出文件是数据快照;请勿在其中放入秘密或凭据。
|
package/cordis.patch.yml
ADDED
package/lib/client.js
ADDED
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
window.__ModuleLoader__.load({ id: "@nonefire/dsh-drender-plugin", factory: (require) => { var module = { exports: {} }; var exports = module.exports;
|
|
2
|
+
"use strict";
|
|
3
|
+
var __defProp = Object.defineProperty;
|
|
4
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
5
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
7
|
+
var __export = (target, all) => {
|
|
8
|
+
for (var name in all)
|
|
9
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
10
|
+
};
|
|
11
|
+
var __copyProps = (to, from, except, desc) => {
|
|
12
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
13
|
+
for (let key of __getOwnPropNames(from))
|
|
14
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
15
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
16
|
+
}
|
|
17
|
+
return to;
|
|
18
|
+
};
|
|
19
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
20
|
+
|
|
21
|
+
// plugins/hosts/dsh/src/client/index.ts
|
|
22
|
+
var index_exports = {};
|
|
23
|
+
__export(index_exports, {
|
|
24
|
+
apply: () => apply,
|
|
25
|
+
inject: () => inject
|
|
26
|
+
});
|
|
27
|
+
module.exports = __toCommonJS(index_exports);
|
|
28
|
+
var import_react2 = require("react");
|
|
29
|
+
|
|
30
|
+
// plugins/hosts/dsh/src/client/RenderToolRow.ts
|
|
31
|
+
var import_react = require("react");
|
|
32
|
+
var import_dsh_client_ui_primitives = require("@deepseek-ai/dsh-client-ui-primitives");
|
|
33
|
+
function isRecord(value) {
|
|
34
|
+
return typeof value === "object" && value !== null;
|
|
35
|
+
}
|
|
36
|
+
function readMeta(value) {
|
|
37
|
+
if (!isRecord(value) || value.kind !== "md-report-render") return void 0;
|
|
38
|
+
return {
|
|
39
|
+
kind: "md-report-render",
|
|
40
|
+
status: typeof value.status === "string" ? value.status : "unknown",
|
|
41
|
+
title: typeof value.title === "string" ? value.title : "md-report",
|
|
42
|
+
sourcePath: typeof value.sourcePath === "string" ? value.sourcePath : "",
|
|
43
|
+
sourceWorkspacePath: typeof value.sourceWorkspacePath === "string" ? value.sourceWorkspacePath : "",
|
|
44
|
+
previewPath: typeof value.previewPath === "string" ? value.previewPath : "",
|
|
45
|
+
previewWorkspacePath: typeof value.previewWorkspacePath === "string" ? value.previewWorkspacePath : "",
|
|
46
|
+
fileName: typeof value.fileName === "string" ? value.fileName : "",
|
|
47
|
+
bytes: typeof value.bytes === "number" ? value.bytes : 0
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
function resultText(content) {
|
|
51
|
+
if (!content) return "";
|
|
52
|
+
return content.flatMap((block) => {
|
|
53
|
+
if (!isRecord(block) || block.type !== "text" || typeof block.text !== "string") return [];
|
|
54
|
+
return [block.text];
|
|
55
|
+
}).join("\n");
|
|
56
|
+
}
|
|
57
|
+
function formatBytes(bytes) {
|
|
58
|
+
if (!Number.isFinite(bytes) || bytes <= 0) return "";
|
|
59
|
+
return bytes < 1024 * 1024 ? `${Math.ceil(bytes / 1024)} KB` : `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
|
|
60
|
+
}
|
|
61
|
+
function safeWorkspaceFile(path, extension) {
|
|
62
|
+
return Boolean(path) && !path.startsWith("/") && !path.includes("\\") && !/[\u0000-\u001f\u007f]/.test(path) && extension.test(path) && path.split("/").every((part) => part !== "" && part !== "." && part !== "..");
|
|
63
|
+
}
|
|
64
|
+
function RenderToolRow(props) {
|
|
65
|
+
const [openError, setOpenError] = (0, import_react.useState)("");
|
|
66
|
+
if (props.phase === "preparing") {
|
|
67
|
+
return (0, import_react.createElement)("div", { role: "status" }, "\u51C6\u5907\u6E32\u67D3\u62A5\u544A\u2026");
|
|
68
|
+
}
|
|
69
|
+
if (props.phase === "start") {
|
|
70
|
+
return (0, import_react.createElement)("div", { role: "status" }, "\u6B63\u5728\u6E32\u67D3\u62A5\u544A\u2026");
|
|
71
|
+
}
|
|
72
|
+
const meta = readMeta(props.block.meta);
|
|
73
|
+
const body = resultText(props.block.content);
|
|
74
|
+
if (!meta) {
|
|
75
|
+
return (0, import_react.createElement)("div", { role: props.block.isError ? "alert" : "status" }, body || (props.block.isError ? "\u62A5\u544A\u6E32\u67D3\u5931\u8D25\u3002" : "\u62A5\u544A\u6E32\u67D3\u5DF2\u7ED3\u675F\u3002"));
|
|
76
|
+
}
|
|
77
|
+
const summary = meta.status === "rendered" ? `${meta.title} \xB7 ${formatBytes(meta.bytes) || meta.fileName}` : meta.status === "storage-failed" ? `${meta.title} \xB7 HTML \u5DF2\u751F\u6210\uFF0C\u9884\u89C8\u6587\u4EF6\u4FDD\u5B58\u5931\u8D25` : `${meta.title} \xB7 ${meta.status}`;
|
|
78
|
+
const sourceCanOpen = safeWorkspaceFile(meta.sourceWorkspacePath, /\.(?:md|markdown)$/i);
|
|
79
|
+
const htmlCanOpen = safeWorkspaceFile(meta.previewWorkspacePath, /\.html$/i);
|
|
80
|
+
const openWorkspaceFile = (path, label, ariaLabel) => typeof props.openFile === "function" ? (0, import_react.createElement)(import_dsh_client_ui_primitives.Button, {
|
|
81
|
+
type: "button",
|
|
82
|
+
variant: "outline",
|
|
83
|
+
size: "sm",
|
|
84
|
+
onClick: () => {
|
|
85
|
+
setOpenError("");
|
|
86
|
+
void Promise.resolve().then(() => props.openFile?.(path)).catch(() => {
|
|
87
|
+
setOpenError("DSH \u65E0\u6CD5\u6253\u5F00\u6587\u4EF6\uFF0C\u8BF7\u68C0\u67E5\u4F1A\u8BDD\u5DE5\u4F5C\u533A\u8BBF\u95EE\u6743\u9650\u3002");
|
|
88
|
+
});
|
|
89
|
+
},
|
|
90
|
+
"aria-label": ariaLabel
|
|
91
|
+
}, label) : null;
|
|
92
|
+
if (htmlCanOpen && !props.block.isError && meta.status === "rendered") {
|
|
93
|
+
return (0, import_react.createElement)(
|
|
94
|
+
"div",
|
|
95
|
+
{ className: "md-report-tool-row" },
|
|
96
|
+
(0, import_react.createElement)("span", null, summary),
|
|
97
|
+
meta.sourcePath ? (0, import_react.createElement)("span", null, `\u6E90 Markdown\uFF1A${meta.sourcePath}`) : null,
|
|
98
|
+
meta.previewPath ? (0, import_react.createElement)("span", null, `HTML \u6587\u4EF6\uFF1A${meta.previewPath}`) : null,
|
|
99
|
+
sourceCanOpen ? openWorkspaceFile(meta.sourceWorkspacePath, "\u9884\u89C8 Markdown", `\u5728\u53F3\u4FA7\u9884\u89C8 ${meta.title} \u7684 Markdown \u6E90\u6587\u4EF6`) : null,
|
|
100
|
+
openWorkspaceFile(meta.previewWorkspacePath, "\u9884\u89C8 HTML", `\u5728\u53F3\u4FA7\u9884\u89C8 ${meta.title} \u7684 HTML \u62A5\u544A`) ?? (0, import_react.createElement)("span", null, `\u9884\u89C8\u6587\u4EF6\uFF1A${meta.previewWorkspacePath}`),
|
|
101
|
+
body ? (0, import_react.createElement)(
|
|
102
|
+
"details",
|
|
103
|
+
null,
|
|
104
|
+
(0, import_react.createElement)("summary", null, "\u67E5\u770B\u6E32\u67D3\u6458\u8981"),
|
|
105
|
+
(0, import_react.createElement)("pre", null, body)
|
|
106
|
+
) : null,
|
|
107
|
+
openError ? (0, import_react.createElement)("span", { role: "alert" }, openError) : null
|
|
108
|
+
);
|
|
109
|
+
}
|
|
110
|
+
return (0, import_react.createElement)(
|
|
111
|
+
"div",
|
|
112
|
+
{ className: "md-report-tool-row", role: props.block.isError || meta.status === "storage-failed" ? "alert" : "status" },
|
|
113
|
+
(0, import_react.createElement)("span", null, summary),
|
|
114
|
+
meta.sourcePath ? (0, import_react.createElement)("span", null, `\u6E90 Markdown\uFF1A${meta.sourcePath}`) : null,
|
|
115
|
+
meta.previewPath ? (0, import_react.createElement)("span", null, `HTML \u6587\u4EF6\uFF1A${meta.previewPath}`) : null,
|
|
116
|
+
sourceCanOpen ? openWorkspaceFile(meta.sourceWorkspacePath, "\u9884\u89C8 Markdown", `\u5728\u53F3\u4FA7\u9884\u89C8 ${meta.title} \u7684 Markdown \u6E90\u6587\u4EF6`) : null,
|
|
117
|
+
meta.previewWorkspacePath && !htmlCanOpen ? (0, import_react.createElement)("span", { role: "alert" }, "HTML \u9884\u89C8\u8DEF\u5F84\u65E0\u6548\uFF0C\u672A\u6253\u5F00\u6587\u4EF6\u3002") : null,
|
|
118
|
+
body ? (0, import_react.createElement)(
|
|
119
|
+
"details",
|
|
120
|
+
null,
|
|
121
|
+
(0, import_react.createElement)("summary", null, props.block.isError ? "\u67E5\u770B\u9519\u8BEF\u8BE6\u60C5" : "\u67E5\u770B\u6E32\u67D3\u7ED3\u679C"),
|
|
122
|
+
(0, import_react.createElement)("pre", null, body)
|
|
123
|
+
) : null
|
|
124
|
+
);
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
// plugins/hosts/dsh/src/client/index.ts
|
|
128
|
+
var inject = ["slots"];
|
|
129
|
+
function apply(ctx) {
|
|
130
|
+
ctx.effect(() => ctx.slots.inject(
|
|
131
|
+
"tool.call.toolview",
|
|
132
|
+
() => ctx.slots.register(
|
|
133
|
+
{ name: "tool.call.toolview", key: "drender_render" },
|
|
134
|
+
(props) => (0, import_react2.createElement)(RenderToolRow, props)
|
|
135
|
+
)
|
|
136
|
+
), "md-report: render result card");
|
|
137
|
+
}
|
|
138
|
+
return module.exports; } });
|