@nonefire/dsh-drender-plugin 0.0.0-stage → 0.1.3

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 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,102 @@
1
- # Temporary Holding Version
1
+ # @nonefire/dsh-drender-plugin
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ **让 DSH Agent 把数据和分析要求转成可校验、可复核、可离线分享的标准化报告。**
4
+
5
+ 本插件将 [drender](https://github.com/NoneFire/drender) 接入 DeepSeek Harness(DSH),实现 `md-report/1` 报告格式。Agent 使用 Markdown 声明指标、数据和组件,插件负责校验文档、读取允许的数据源,并生成交互式 HTML 报告或仪表盘。
6
+
7
+ 它主要解决三件事:同一指标在卡片、图表和正文中保持同一口径;在渲染前发现格式和引用错误;把分析结果交付为不依赖 CDN 或数据库连接的 HTML 文件。
8
+
9
+ ## 效果预览
10
+
11
+ 组件图鉴展示报告组件、指标 token、分栏布局和并排图表的实际渲染效果。可直接查看图鉴 HTML、Markdown 源文件和经营仪表盘示例:
12
+
13
+ - [组件图鉴 HTML](https://github.com/NoneFire/drender/blob/main/examples/template-gallery.html) · [图鉴 Markdown 源文件](https://github.com/NoneFire/drender/blob/main/examples/template-gallery.md)
14
+ - [经营仪表盘 HTML](https://github.com/NoneFire/drender/blob/main/examples/dashboard.html)
15
+
16
+ ![drender 组件图鉴预览,展示报告布局、指标 token 和多种图表组件](https://raw.githubusercontent.com/NoneFire/drender/main/examples/assets/template-gallery-preview.png)
17
+
18
+ GitHub 会显示 HTML 文件源码;下载后用浏览器打开即可查看交互效果。完整组件说明和更多示例见 [仓库 README](https://github.com/NoneFire/drender#实际效果)。
19
+
20
+ ## 插件提供什么
21
+
22
+ 安装后,DSH Agent 可使用两个工具和一个 Skill:
23
+
24
+ - **`drender_validate`**:校验 `md-report/1` frontmatter、组件字段、指标和数据集引用、布局结构、安全链接及图片资源。错误诊断包含代码、行号和字段路径,便于 Agent 修复后再渲染。
25
+ - **`drender_render`**:校验并渲染报告,返回 HTML 预览路径、源 Markdown 路径、数据集和组件摘要;宿主支持时还提供侧栏预览和下载附件。
26
+ - **`drender-report` Skill**:引导 Agent 先明确报告问题,再选择指标、查询、图表和版式,并遵守 `md-report/1` 语法。
27
+
28
+ ## 工作流程
29
+
30
+ 1. Agent 根据用户的问题和数据源编写 `md-report/1` Markdown。
31
+ 2. Agent 调用 `drender_validate` 检查文档结构和引用,并根据诊断修正错误。
32
+ 3. Agent 调用 `drender_render`。宿主只读加载 frontmatter 中声明的 SQLite 数据,将查询结果物化后传给 renderer。
33
+ 4. DSH 展示或提供下载自包含 HTML;Markdown 源文件和其声明的数据库、SQL sidecar、图片可保留用于复现。
34
+
35
+ ```text
36
+ 问题 + 工作区数据
37
+ ↓
38
+ Agent 编写 md-report/1 Markdown
39
+ ↓
40
+ drender_validate → drender_render
41
+ ↓
42
+ 可复核的源文件 + 交互式离线 HTML
43
+ ```
44
+
45
+ ## 安装
46
+
47
+ 将插件安装到 DSH Web profile:
48
+
49
+ ```sh
50
+ dsh plugin --profile web add @nonefire/dsh-drender-plugin
51
+ ```
52
+
53
+ 插件包内已包含 renderer、数据源和主题模块。用户只需安装这个 DSH 插件包;ECharts、Mermaid、AntV Infographic、AntV T8、Tabulator 等第三方运行时依赖由 npm 自动安装。
54
+
55
+ ## 输入与数据来源
56
+
57
+ 两个工具都接受以下一种报告来源:
58
+
59
+ - `markdown`:完整的 Markdown 文本,必须包含 `md-report/1` YAML frontmatter。
60
+ - `path`:Session 工作区内的 `.md` 或 `.markdown` 文件路径。
61
+
62
+ 两者只能提供其一。`drender_render` 还接受可选 `fontSize`,范围为 12–20 CSS 像素,默认值为 14。
63
+
64
+ 可复现报告应在 frontmatter 声明工作区相对路径的 SQLite 数据库、查询文件、图片和场景。插件只读执行声明的 SQLite 查询;查询可以来自 SQL sidecar、SQLite view 或短内联语句。一次性调用也可通过 `datasets` 和 `assets` 参数直接传入数据快照或图片;这些参数不会写回 Markdown,重复使用时应改为声明工作区文件。
65
+
66
+ 插件包也导出 `readCsvDataset`、`parseCsvDataset` 和 `loadSqliteDataset` 等数据源函数,供宿主集成或应用代码使用。
67
+
68
+ ## 报告与组件
69
+
70
+ 支持 report 和 dashboard 两种模式,包括:
71
+
72
+ - **数据组件:** 指标 token、KPI、ECharts 图表、排行榜、透视表、Tabulator 数据表、发现摘要、指标口径和数据质量说明。
73
+ - **说明组件:** SQL 代码块、Mermaid 流程图、AntV Infographic、AntV T8 叙述、Excalidraw 场景和声明式图片。
74
+ - **版式组件:** 自动目录、提示条、折叠详情、2–6 列正文分栏和 12 列 dashboard 面板栅格;图表可放在分栏内并排显示。
75
+
76
+ 通过 `metrics` 声明业务指标,再从 KPI、图表或正文引用,可共享格式和口径。渲染器只把报告实际使用的组件运行时、主题、数据快照和声明资源放入 HTML,文件可离线打开。
77
+
78
+ ## 运行要求
79
+
80
+ - Node.js 22.13 或更高版本。
81
+ - DSH profile 提供公开的 `tools` 与 `skills` 服务,并启用 Skill 加载。
82
+ - 读取 frontmatter 声明的 SQLite、SQL 和图片文件需要 Session 工作区 filesystem 服务。
83
+ - Web profile 的侧栏 UI 需要 DSH `ui-tool`、`ui-slots`、`ui-renderer` 和 `ui-primitives` 模块。
84
+ - 附件服务是可选的;没有附件服务时,若 Session 工作区可写,HTML 仍可通过预览路径访问。
85
+
86
+ 插件依赖 DSH 公布的 Cordis、tools、skill、attachment 和客户端 UI API,不导入 DSH 应用源码或私有包。
87
+
88
+ ## 数据限制与安全
89
+
90
+ - SQLite 查询使用只读连接,只允许只读语句;数据库和 SQL 文件路径必须位于当前 Session 工作区。
91
+ - 每个数据集最多 5,000 行,SQL 文件最多 100 KB,报告数据快照总量最多 1 MB。
92
+ - 图片输入总量最多 8 MB,且必须通过 frontmatter 声明工作区相对路径与媒体类型。
93
+ - 渲染器拒绝原始 HTML 和不安全链接,不运行用户 JavaScript,也不在浏览器里执行 SQL。
94
+ - 导出的 HTML 内含数据快照,收到文件的人可以检查这些数据;不要把秘密或凭据放入报告。
95
+
96
+ ## 开发
97
+
98
+ 通用 Skill 源文件位于 [`.agents/skills/drender-report/SKILL.md`](../../../.agents/skills/drender-report/SKILL.md)。构建会把它复制到 `lib/skills/drender-report/SKILL.md`;请只维护共享源文件。
99
+
100
+ DSH 插件条目见 [`cordis.patch.yml`](cordis.patch.yml),稳定 ID 为 `md-report-dsh`,名称与包名及 `src/index.ts` 导出保持一致。
101
+
102
+ 完整组件图鉴、格式说明和示例见 [drender 仓库](https://github.com/NoneFire/drender)。
@@ -0,0 +1,3 @@
1
+ - insert:
2
+ - id: md-report-dsh
3
+ name: '@nonefire/dsh-drender-plugin'
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; } });