From 0f2eec145ef71e403a7654510d031548b1332ff0 Mon Sep 17 00:00:00 2001 From: chichuan Date: Tue, 4 Aug 2026 17:10:15 +0800 Subject: [PATCH] docs: add OpenNodes V1 whiteboard protocol --- internal/cli/schema_agent_metadata/index.json | 2 +- .../cli/schema_agent_metadata/whiteboard.json | 27 +- internal/cli/schema_agent_metadata_audit.json | 4 +- internal/cli/schema_catalog/catalog.json | 4 +- .../cli/schema_catalog/tools/whiteboard.json | 27 +- skills/mono/references/products/whiteboard.md | 4 + .../products/whiteboard/open-nodes-v1.md | 45 ++ .../whiteboard/open-nodes-v1/01-overview.md | 57 +++ .../whiteboard/open-nodes-v1/02-query.md | 288 +++++++++++++ .../whiteboard/open-nodes-v1/03-update.md | 249 +++++++++++ .../whiteboard/open-nodes-v1/04-text-style.md | 395 ++++++++++++++++++ .../05-shape-frame-group-connector.md | 186 +++++++++ .../open-nodes-v1/06-vector-icon-path.md | 313 ++++++++++++++ .../07-examples-errors-write-support.md | 270 ++++++++++++ .../whiteboard/open-nodes-v1/08-catalogs.md | 61 +++ .../dingtalk-misc/references/whiteboard.md | 4 + .../references/whiteboard/open-nodes-v1.md | 45 ++ .../whiteboard/open-nodes-v1/01-overview.md | 57 +++ .../whiteboard/open-nodes-v1/02-query.md | 288 +++++++++++++ .../whiteboard/open-nodes-v1/03-update.md | 249 +++++++++++ .../whiteboard/open-nodes-v1/04-text-style.md | 395 ++++++++++++++++++ .../05-shape-frame-group-connector.md | 186 +++++++++ .../open-nodes-v1/06-vector-icon-path.md | 313 ++++++++++++++ .../07-examples-errors-write-support.md | 270 ++++++++++++ .../whiteboard/open-nodes-v1/08-catalogs.md | 61 +++ 25 files changed, 3793 insertions(+), 7 deletions(-) create mode 100644 skills/mono/references/products/whiteboard/open-nodes-v1.md create mode 100644 skills/mono/references/products/whiteboard/open-nodes-v1/01-overview.md create mode 100644 skills/mono/references/products/whiteboard/open-nodes-v1/02-query.md create mode 100644 skills/mono/references/products/whiteboard/open-nodes-v1/03-update.md create mode 100644 skills/mono/references/products/whiteboard/open-nodes-v1/04-text-style.md create mode 100644 skills/mono/references/products/whiteboard/open-nodes-v1/05-shape-frame-group-connector.md create mode 100644 skills/mono/references/products/whiteboard/open-nodes-v1/06-vector-icon-path.md create mode 100644 skills/mono/references/products/whiteboard/open-nodes-v1/07-examples-errors-write-support.md create mode 100644 skills/mono/references/products/whiteboard/open-nodes-v1/08-catalogs.md create mode 100644 skills/multi/dingtalk-misc/references/whiteboard/open-nodes-v1.md create mode 100644 skills/multi/dingtalk-misc/references/whiteboard/open-nodes-v1/01-overview.md create mode 100644 skills/multi/dingtalk-misc/references/whiteboard/open-nodes-v1/02-query.md create mode 100644 skills/multi/dingtalk-misc/references/whiteboard/open-nodes-v1/03-update.md create mode 100644 skills/multi/dingtalk-misc/references/whiteboard/open-nodes-v1/04-text-style.md create mode 100644 skills/multi/dingtalk-misc/references/whiteboard/open-nodes-v1/05-shape-frame-group-connector.md create mode 100644 skills/multi/dingtalk-misc/references/whiteboard/open-nodes-v1/06-vector-icon-path.md create mode 100644 skills/multi/dingtalk-misc/references/whiteboard/open-nodes-v1/07-examples-errors-write-support.md create mode 100644 skills/multi/dingtalk-misc/references/whiteboard/open-nodes-v1/08-catalogs.md diff --git a/internal/cli/schema_agent_metadata/index.json b/internal/cli/schema_agent_metadata/index.json index a7cf09bd..e9e1aa87 100644 --- a/internal/cli/schema_agent_metadata/index.json +++ b/internal/cli/schema_agent_metadata/index.json @@ -1,6 +1,6 @@ { "version": 1, - "source_hash": "sha256:506d4c074744862167b39ecef82d13a2d5ff3ab0faad74673063fd3918c4733b", + "source_hash": "sha256:5d6ddcaa5a1189dff914001e15cc25861098dfd5f4dbbbcefbfce290f1638b78", "surface_hash": "sha256:975584f4331c3b79aff4ee9b1b0f747ae8b67b184e8fb0b54c08996756b836c0", "coverage": { "surface_products": 27, diff --git a/internal/cli/schema_agent_metadata/whiteboard.json b/internal/cli/schema_agent_metadata/whiteboard.json index 3ffbbee9..f563d363 100644 --- a/internal/cli/schema_agent_metadata/whiteboard.json +++ b/internal/cli/schema_agent_metadata/whiteboard.json @@ -80,6 +80,12 @@ "precedence": "reviewed_explicit", "selected": true, "review_reason": "Migrated from dws-wukong e2da8ab947c6 with exact nodeId and partId routing and structured resultJson decoding." + }, + { + "value": "not_required", + "source": "risk-default", + "precedence": "inference_or_default", + "selected": false } ] }, @@ -96,6 +102,12 @@ "precedence": "reviewed_explicit", "selected": true, "review_reason": "Migrated from dws-wukong e2da8ab947c6 with exact nodeId and partId routing and structured resultJson decoding." + }, + { + "value": "read", + "source": "command-verb", + "precedence": "inference_or_default", + "selected": false } ] }, @@ -132,6 +144,12 @@ "precedence": "reviewed_explicit", "selected": true, "review_reason": "Migrated from dws-wukong e2da8ab947c6 with exact nodeId and partId routing and structured resultJson decoding." + }, + { + "value": "idempotent", + "source": "effect-default", + "precedence": "inference_or_default", + "selected": false } ] }, @@ -219,6 +237,12 @@ "precedence": "reviewed_explicit", "selected": true, "review_reason": "Migrated from dws-wukong e2da8ab947c6 with exact nodeId and partId routing and structured resultJson decoding." + }, + { + "value": "low", + "source": "effect-default", + "precedence": "inference_or_default", + "selected": false } ] }, @@ -253,7 +277,8 @@ "cobra-help:dws whiteboard query", "dws-wukong@e2da8ab947c6:wukong/products/whiteboard.go", "internal/cli/schema_hints/metadata/whiteboard.json", - "internal/cli/schema_hints/selection/whiteboard.json" + "internal/cli/schema_hints/selection/whiteboard.json", + "skills/mono/references/products/whiteboard/open-nodes-v1/02-query.md" ], "use_when": [ "已知承载文档 nodeId 和白板 partId,需要检查当前白板节点、布局或写入支持时" diff --git a/internal/cli/schema_agent_metadata_audit.json b/internal/cli/schema_agent_metadata_audit.json index 16cbfb4a..abef6292 100644 --- a/internal/cli/schema_agent_metadata_audit.json +++ b/internal/cli/schema_agent_metadata_audit.json @@ -1,8 +1,8 @@ { "version": 1, - "source_hash": "sha256:506d4c074744862167b39ecef82d13a2d5ff3ab0faad74673063fd3918c4733b", + "source_hash": "sha256:5d6ddcaa5a1189dff914001e15cc25861098dfd5f4dbbbcefbfce290f1638b78", "surface_hash": "sha256:975584f4331c3b79aff4ee9b1b0f747ae8b67b184e8fb0b54c08996756b836c0", - "source_files": 163, + "source_files": 172, "hint_files": 56, "hint_products": 44, "hint_tools": 1852, diff --git a/internal/cli/schema_catalog/catalog.json b/internal/cli/schema_catalog/catalog.json index 185e3c04..ee2b7c4f 100644 --- a/internal/cli/schema_catalog/catalog.json +++ b/internal/cli/schema_catalog/catalog.json @@ -1,12 +1,12 @@ { "version": 1, "surface_hash": "sha256:975584f4331c3b79aff4ee9b1b0f747ae8b67b184e8fb0b54c08996756b836c0", - "source_hash": "sha256:8a264ded070fcabd18d0c5ae67dc693626ac83ae82d382c03fb92ca51d49da1f", + "source_hash": "sha256:9fe4944d8306bfa33f82c3b5b078dd0bb0b53dfaecd224d4a918adbe26bf5d11", "catalog": { "agent_metadata": { "products_with_metadata": 27, "source": "embedded-skill-metadata", - "source_hash": "sha256:506d4c074744862167b39ecef82d13a2d5ff3ab0faad74673063fd3918c4733b", + "source_hash": "sha256:5d6ddcaa5a1189dff914001e15cc25861098dfd5f4dbbbcefbfce290f1638b78", "surface_hash": "sha256:975584f4331c3b79aff4ee9b1b0f747ae8b67b184e8fb0b54c08996756b836c0", "surface_products": 27, "surface_tools": 850, diff --git a/internal/cli/schema_catalog/tools/whiteboard.json b/internal/cli/schema_catalog/tools/whiteboard.json index 824658b7..a14719c4 100644 --- a/internal/cli/schema_catalog/tools/whiteboard.json +++ b/internal/cli/schema_catalog/tools/whiteboard.json @@ -8,7 +8,8 @@ "cobra-help:dws whiteboard query", "dws-wukong@e2da8ab947c6:wukong/products/whiteboard.go", "internal/cli/schema_hints/metadata/whiteboard.json", - "internal/cli/schema_hints/selection/whiteboard.json" + "internal/cli/schema_hints/selection/whiteboard.json", + "skills/mono/references/products/whiteboard/open-nodes-v1/02-query.md" ], "agent_summary": "读取钉钉文档内已有白板的 OpenNodes 内容", "agent_summary_source": "dws-agent-selection/whiteboard", @@ -112,6 +113,12 @@ "selected": true, "source": "internal/cli/schema_hints/metadata/whiteboard.json", "value": "not_required" + }, + { + "precedence": "inference_or_default", + "selected": false, + "source": "risk-default", + "value": "not_required" } ], "precedence": "reviewed_explicit", @@ -142,6 +149,12 @@ "selected": true, "source": "internal/cli/schema_hints/metadata/whiteboard.json", "value": "read" + }, + { + "precedence": "inference_or_default", + "selected": false, + "source": "command-verb", + "value": "read" } ], "precedence": "reviewed_explicit", @@ -178,6 +191,12 @@ "selected": true, "source": "internal/cli/schema_hints/metadata/whiteboard.json", "value": "idempotent" + }, + { + "precedence": "inference_or_default", + "selected": false, + "source": "effect-default", + "value": "idempotent" } ], "precedence": "reviewed_explicit", @@ -265,6 +284,12 @@ "selected": true, "source": "internal/cli/schema_hints/metadata/whiteboard.json", "value": "low" + }, + { + "precedence": "inference_or_default", + "selected": false, + "source": "effect-default", + "value": "low" } ], "precedence": "reviewed_explicit", diff --git a/skills/mono/references/products/whiteboard.md b/skills/mono/references/products/whiteboard.md index 3e0c4987..15127e53 100644 --- a/skills/mono/references/products/whiteboard.md +++ b/skills/mono/references/products/whiteboard.md @@ -3,6 +3,10 @@ `dws whiteboard` 读取和更新已存在于在线文档中的单页白板。创建白板卡片使用 `dws doc whiteboard insert`;删除卡片使用已有的 `dws doc block delete`。 +OpenNodes V1 的完整字段、节点类型、目录枚举和错误语义按需读取 +[协议索引](./whiteboard/open-nodes-v1.md);不要根据本页概要猜测节点字段或 +`geometry`、`catalogId` 等枚举值。 + ## 标准流程 1. 从用户输入或真实文档 JSONML 取得 `nodeId` 和 card `metadata.id`(partId)。 diff --git a/skills/mono/references/products/whiteboard/open-nodes-v1.md b/skills/mono/references/products/whiteboard/open-nodes-v1.md new file mode 100644 index 00000000..322822d2 --- /dev/null +++ b/skills/mono/references/products/whiteboard/open-nodes-v1.md @@ -0,0 +1,45 @@ +# OpenNodes V1(DWS 白板协议索引) + +本目录承载 `dws whiteboard query/update` 使用的 OpenNodes V1 协议。协议按 +调用阶段拆分,Agent 只加载当前任务所需章节,避免一次性读取全部内容。 + +## DWS 使用规则 + +- 只通过 `dws whiteboard query/update` 读写白板。 +- 每次调用必须提供承载白板的文档 `--node` 和目标白板 `--part-id`。 +- DWS 当前只支持单页白板:命令没有 `--page-id`,update 文件禁止包含 + `pageId`。 +- `query` 不接收请求体;CLI 会把返回的 `resultJson` 解析成对象。 +- 白板命令不支持使用全局 `--jq` 或 `--fields` 过滤输出;传入任一参数都会报错, + Agent 直接读取 CLI 返回的结构化 JSON。 +- `update --source` 使用 `overwrite + source` 信封;append 和 overwrite 都是 + 远端写入,获得用户确认后必须通过 `--yes` 显式确认。 +- CLI 只预检 JSON、信封、版本和 `nodes` 数组等外层结构;节点字段、枚举、层级、 + 引用和业务约束由白板服务完整校验。任一层失败都不会保留部分更新。 +- DWS 返回以 `success`、`nodeId`、`partId`、`resultJson` 和可选的 + `resultSummary` 为准。 + +## 按任务读取 + +| 当前任务 | 必读章节 | +|---|---| +| 理解版本、兼容和命令语义 | [01-overview](open-nodes-v1/01-overview.md) | +| 读取或解释 query 结果 | [02-query](open-nodes-v1/02-query.md) | +| 构造 append、overwrite 或清空请求 | [03-update](open-nodes-v1/03-update.md) | +| 写富文本、列表、链接、主题色、渐变或阴影 | [04-text-style](open-nodes-v1/04-text-style.md) | +| 写 shape、便签、frame、group 或 connector | [05-shape-frame-group-connector](open-nodes-v1/05-shape-frame-group-connector.md) | +| 写已上传 Vector、内置 Icon 或自由 Path | [06-vector-icon-path](open-nodes-v1/06-vector-icon-path.md) | +| 处理错误、query 转写或判断 writeSupport | [07-examples-errors-write-support](open-nodes-v1/07-examples-errors-write-support.md) | +| 选择合法 geometry 或 icon catalogId | [08-catalogs](open-nodes-v1/08-catalogs.md) | + +## 强制读取规则 + +- 使用 `shape.geometry` 前必须读取 [08-catalogs](open-nodes-v1/08-catalogs.md), + 不得猜测 geometry。 +- 使用 `icon.catalogId` 前必须读取 [06-vector-icon-path](open-nodes-v1/06-vector-icon-path.md) + 和 [08-catalogs](open-nodes-v1/08-catalogs.md)。 +- 使用 `path` 前必须读取 [06-vector-icon-path](open-nodes-v1/06-vector-icon-path.md), + 不得把它当作通用 SVG Path。 +- Query 结果不能直接作为 update source;转换前必须读取 + [03-update](open-nodes-v1/03-update.md) 和 + [07-examples-errors-write-support](open-nodes-v1/07-examples-errors-write-support.md)。 diff --git a/skills/mono/references/products/whiteboard/open-nodes-v1/01-overview.md b/skills/mono/references/products/whiteboard/open-nodes-v1/01-overview.md new file mode 100644 index 00000000..1b2b497e --- /dev/null +++ b/skills/mono/references/products/whiteboard/open-nodes-v1/01-overview.md @@ -0,0 +1,57 @@ +# DWS OpenNodes V1 协议说明 + +> 本文件是 DWS OpenNodes V1 协议的拆分章节。按需读取入口见 +> [协议索引](../open-nodes-v1.md)。 + +> 协议版本:`schemaVersion = "1.0"`,`catalogVersion = "dml-v1"`。 + +## 1. 协议用途 + +OpenNodes 是 DWS 白板命令使用的语义节点协议,提供两类能力: + +- `dws whiteboard query`:返回稳定、可理解的页面和节点数据。 +- `dws whiteboard update`:接收受约束的节点描述,以 `append` 或 + `overwrite` 模式修改白板。 + +调用方只应依赖本文声明的语义字段和行为: + +- `query` 不修改白板。 +- `update` 全部成功或全部回滚,不返回中间状态。 +- 未声明的存储字段、类型名称和处理过程不属于协议承诺。 + +OpenNodes V1 支持的节点类型、字段和读写范围见第 7 节。 + +DWS 负责身份认证和权限校验。Vector 资源准备使用 `dws doc media upload`, +具体流程见白板命令参考。 + +## 2. 版本与兼容原则 + +| 字段 | 当前值 | 作用 | +| --- | --- | --- | +| `schemaVersion` | `1.0` | 控制文档结构、节点字段和字段语义。 | +| `catalogVersion` | `dml-v1` | 控制允许写入的 DML 几何、连接线标记和内置 icon 目录。 | + +V1 采用严格校验: + +- 必填字段缺失会失败。 +- 未声明字段会失败,不会被静默忽略。 +- query-only 字段出现在 update 中会以 `readOnlyField` 失败。 +- 不支持的节点类型、目录值或引用范围会失败。 +- `null` 不代表“使用默认值”;除非字段类型明确允许,否则会失败。 + +本文列出的请求枚举值都是协议字面量,调用方必须按文档中的大小写和拼写原样传入, +不能自行转换或猜测。响应中未来可能增加可选字段,调用方应忽略不认识的响应字段。 + +调用方必须原样携带当前版本值。新增不兼容结构时应升级 +`schemaVersion`;修改 DML、marker 或 icon 目录时应评估并升级 +`catalogVersion`。 + +## 3. DWS 命令一览 + +| 命令 | 所需权限 | 效果 | +| --- | --- | --- | --- | +| `dws whiteboard query --node ... --part-id ...` | 可查看白板 | 读取单页白板,不修改内容。 | +| `dws whiteboard update --node ... --part-id ... --source ... --yes` | 可编辑白板 | 追加节点或整页重建;所有更新都需先取得用户确认。 | + +DWS 当前只支持文字文档中已有的单页内嵌白板。命令不接收 `pageId`,也不提供 +创建页面、切换页面或按既有节点 ID 局部修改的能力。 diff --git a/skills/mono/references/products/whiteboard/open-nodes-v1/02-query.md b/skills/mono/references/products/whiteboard/open-nodes-v1/02-query.md new file mode 100644 index 00000000..8754fa7a --- /dev/null +++ b/skills/mono/references/products/whiteboard/open-nodes-v1/02-query.md @@ -0,0 +1,288 @@ +# OpenNodes V1 — Query 请求、返回结构和节点公共字段 + +> 本文件是 DWS OpenNodes V1 协议的拆分章节。按需读取入口见 +> [协议索引](../open-nodes-v1.md)。 + +## 4. Query 协议 + +### 4.1 请求 + +```bash +dws whiteboard query \ + --node \ + --part-id \ + --format json +``` + +`query` 不接收请求体或 `pageId`。`--node` 和 `--part-id` 的发现规则见 +[白板命令参考](../../whiteboard.md)。CLI 会把服务返回的 `resultJson` JSON +字符串解析成对象。 + +### 4.2 返回结构 + +```ts +interface OpenNodesDocument { + schemaVersion: "1.0"; + catalogVersion: "dml-v1"; + pages: OpenPage[]; +} + +interface OpenPage { + id: string; + nodes: OpenNode[]; +} +``` + +DWS 当前只支持单页白板,因此 `pages` 固定包含一个页面。调用方仍应从返回值读取 +页面 `id`,但不能把它作为 `pageId` 传给 DWS 命令。 + +母版节点不会作为独立页面返回,而会合并到引用它的页面 `nodes` 中,并带有: + +```json +{ + "source": "master", + "writeSupport": "readOnly", + "unsupportedFeatures": ["node.source.master"] +} +``` + +### 4.3 节点公共字段 + +`type` 的完整公开枚举如下。前一组可由 update 创建,后一组仅供 query 返回: + +```ts +type WritableOpenNodeType = + | "shape" + | "text" + | "connector" + | "stickyNote" + | "frame" + | "group" + | "vector" + | "icon" + | "path"; + +type ReadOnlyOpenNodeType = + | "image" + | "pdf" + | "media" + | "webLink" + | "table" + | "chart" + | "uml" + | "swimlane" + | "mind" + | "timer" + | "placeholder" + | "unknown"; + +type OpenNodeType = WritableOpenNodeType | ReadOnlyOpenNodeType; +``` + +每个 query 节点都包含以下公共字段: + +| 字段 | 类型 | 说明 | +| --- | --- | --- | +| `id` | `string` | 白板中真实、稳定的节点 ID。 | +| `type` | `OpenNodeType` | OpenNodes 公开语义类型,取值见上方枚举。 | +| `parentId` | `string?` | group/frame 父节点 ID。无父节点时省略。 | +| `children` | `string[]?` | group/frame 的直接子节点 ID,由服务端推导。 | +| `x`、`y` | `number` | 有父节点时相对父节点;否则相对页面。单位为 px。 | +| `width`、`height` | `number` | 节点包围盒尺寸,单位为 px。连接线允许其中一个为 `0`。 | +| `angle` | `number` | 归一化到 `[0, 360)` 的角度。 | +| `absoluteBounds` | `OpenBounds` | 页面坐标系中的绝对包围盒。 | +| `layer` | `background \| normal \| foreground` | 节点所在层。 | +| `zIndex` | `number` | 同一父节点、同一 layer 内的非负顺序,值越小越靠后。 | +| `hidden` | `boolean` | 节点是否隐藏。 | +| `locked` | `boolean` | 节点是否锁定。 | +| `source` | `page \| master` | 节点来自当前页面还是母版。 | +| `writeSupport` | `readWrite \| readOnly` | 当前节点能否由 V1 update 表达。 | +| `unsupportedFeatures` | `string[]?` | 只读原因;`readWrite` 节点省略。 | + +公共结构和各节点分支定义如下;分支中的字段含义与写入限制见第 7 节: + +```ts +interface OpenPoint { + x: number; + y: number; +} + +interface OpenBounds { + x: number; + y: number; + width: number; + height: number; + angle: number; +} + +interface OpenNodeBase { + id: string; + type: OpenNodeType; + parentId?: string; + children?: string[]; + x: number; + y: number; + width: number; + height: number; + angle: number; + absoluteBounds: OpenBounds; + layer: "background" | "normal" | "foreground"; + zIndex: number; + hidden: boolean; + locked: boolean; + source: "page" | "master"; + writeSupport: "readWrite" | "readOnly"; + unsupportedFeatures?: string[]; +} + +interface OpenShapeNode extends OpenNodeBase { + type: "shape"; + geometry: `dml:${string}`; + adjustments?: Record; + text?: OpenText; + style?: OpenNodeStyle; +} + +interface OpenTextNode extends OpenNodeBase { + type: "text"; + text: OpenText; + style?: OpenNodeStyle; +} + +interface OpenConnectorNode extends OpenNodeBase { + type: "connector"; + start: OpenConnectorEndpoint; + end: OpenConnectorEndpoint; + routing: OpenConnectorRouting; + waypoints?: OpenPoint[]; + style?: OpenNodeStyle; + resolvedPath: OpenResolvedConnectorPath; +} + +interface OpenStickyNoteNode extends OpenNodeBase { + type: "stickyNote"; + text?: OpenText; + style?: OpenNodeStyle; + creator?: { + displayName?: string; + hasAvatar?: boolean; + }; + tags?: Array<{ + id: string; + text: string; + background: OpenPaint; + }>; +} + +interface OpenFrameNode extends OpenNodeBase { + type: "frame"; + title?: { + text: OpenText; + box: { width: number; height: number }; + }; + style?: OpenNodeStyle; + presentationOrder?: number; + resizeMode: "free" | "fixedAspectRatio"; +} + +interface OpenGroupNode extends OpenNodeBase { + type: "group"; + children: string[]; +} + +interface OpenVectorNode extends OpenNodeBase { + type: "vector"; + resource: OpenVectorResource; +} + +interface OpenIconNode extends OpenNodeBase { + type: "icon"; + catalogId: string; +} + +interface OpenPathNode extends OpenNodeBase { + type: "path"; + path: OpenPathData; + style?: OpenNodeStyle; +} + +interface OpenReadOnlyNode extends OpenNodeBase { + type: ReadOnlyOpenNodeType; + writeSupport: "readOnly"; + unsupportedFeatures: string[]; +} + +type OpenNode = + | OpenShapeNode + | OpenTextNode + | OpenConnectorNode + | OpenStickyNoteNode + | OpenFrameNode + | OpenGroupNode + | OpenVectorNode + | OpenIconNode + | OpenPathNode + | OpenReadOnlyNode; +``` + +query 中 `icon.catalogId` 使用 `string`,是为了让无法映射到当前目录的既有图标仍可 +被诊断;update 只能传入 7.10 节 `OpenIconCatalogId` 中列出的值。 + +### 4.4 Query 示例 + +```json +{ + "schemaVersion": "1.0", + "catalogVersion": "dml-v1", + "pages": [ + { + "id": "page", + "nodes": [ + { + "id": "real-node-id", + "type": "text", + "x": 120, + "y": 80, + "width": 240, + "height": 48, + "angle": 0, + "absoluteBounds": { + "x": 120, + "y": 80, + "width": 240, + "height": 48, + "angle": 0 + }, + "layer": "normal", + "zIndex": 0, + "hidden": false, + "locked": false, + "source": "page", + "writeSupport": "readWrite", + "text": { + "blocks": [ + { + "type": "paragraph", + "horizontalAlign": "left", + "runs": [ + { + "text": "Hello OpenNodes", + "marks": { + "fontSize": 16, + "color": "#223344" + } + } + ] + } + ], + "verticalAlign": "center", + "padding": [2, 4], + "plainText": "Hello OpenNodes", + "writeSupport": "readWrite" + } + } + ] + } + ] +} +``` diff --git a/skills/mono/references/products/whiteboard/open-nodes-v1/03-update.md b/skills/mono/references/products/whiteboard/open-nodes-v1/03-update.md new file mode 100644 index 00000000..3f028cc2 --- /dev/null +++ b/skills/mono/references/products/whiteboard/open-nodes-v1/03-update.md @@ -0,0 +1,249 @@ +# OpenNodes V1 — Update 信封、Append/Overwrite 和公共写入字段 + +> 本文件是 DWS OpenNodes V1 协议的拆分章节。按需读取入口见 +> [协议索引](../open-nodes-v1.md)。 + +## 5. Update 协议 + +### 5.1 请求信封 + +```ts +interface OpenNodesUpdateRequest { + overwrite?: boolean; + source: { + schemaVersion: "1.0"; + catalogVersion: "dml-v1"; + nodes: OpenNodeWrite[]; + }; +} +``` + +字段含义: + +| 字段 | 必填 | 说明 | +| --- | --- | --- | +| `overwrite` | 否 | `false` 或省略为 append;`true` 为 overwrite。 | +| `source.schemaVersion` | 是 | 必须为 `1.0`。 | +| `source.catalogVersion` | 是 | 必须为 `dml-v1`。 | +| `source.nodes` | 是 | 本次创建的节点数组。append 至少一个;overwrite 允许空数组。 | + +`source` 不能直接使用 query 返回的 `OpenNodesDocument`,也不接受 `pages`。 +DWS 不接受 `pageId`;传入会在 CLI 本地校验阶段失败。 + +V1 没有“按真实节点 ID patch 既有节点”的语义。`source.nodes` 中的每一项都会 +创建一个新节点,`id` 仅是本次请求内建立父子关系和连接线引用的临时 ID: +append 是新增节点,overwrite 是整页删除后重新创建。 + +### 5.2 Append 与 Overwrite + +> **注意:** `overwrite: true` 是破坏性操作,会删除当前白板页面的全部自有 +> 节点。调用前应先 query 并确认影响范围;`nodes: []` 会清空页面。不希望删除 +> 既有内容时,应使用 append。 + +| 行为 | append | overwrite | +| --- | --- | --- | +| `overwrite` | `false` 或省略 | `true` | +| 空 `nodes` | 禁止 | 允许,用于清空当前页面 | +| 当前页面旧节点 | 全部保留 | 删除页面自有节点后创建新节点 | +| 母版节点 | 保留 | 保留 | +| 页面级设置 | 保留 | 保留 | +| 原子性 | 全部成功或全部回滚 | 全部成功或全部回滚 | + +overwrite 会替换当前页面的全部自有节点,不要求旧节点本身可由 OpenNodes V1 +写入。因此页面中存在 image、PDF、复杂文本等只读节点,不会单独阻止清空页面。 + +overwrite 会在删除前执行安全预检;以下情况会拒绝执行: + +- 节点被锁定:`lockedNode`。 +- 节点不允许被删除:`deleteForbidden`。 +- 页面包含无法安全保留或清理的关联数据:`unknownMetadataReference`。 +- 目标节点未能完整删除:`deleteFailed`。 + +与被删除节点绑定且有明确清理规则的关联数据会随节点清理;无法安全保留或清理的 +关联数据会使 overwrite 失败。 + +overwrite 只替换当前页面节点,不会重建页面,也不会修改主题等页面级设置。 +白板已有的主题会被保留;白板没有有效主题时也不会自动添加。调用方应使用 +已配置有效主题的白板,或者显式提供不依赖主题的颜色。 + +### 5.3 成功结果 + +```ts +interface DWSWhiteboardUpdateResponse { + success: true; + nodeId: string; + partId: string; + resultJson: { + mode: "append" | "overwrite"; + createdNodeIds: string[]; + idMap: Record; + deletedNodeCount: number; + message: string; + }; +} +``` + +| 字段 | 说明 | +| --- | --- | +| `success` | `true` 表示本次 DWS 调用成功。 | +| `nodeId` | 输入的文档节点 ID。 | +| `partId` | 输入的白板标识。 | +| `resultJson.mode` | 实际执行的模式。 | +| `resultJson.createdNodeIds` | 按请求节点顺序返回真实节点 ID。 | +| `resultJson.idMap` | 请求中显式临时 ID 到真实节点 ID 的映射。 | +| `resultJson.deletedNodeCount` | append 恒为 `0`;overwrite 为删除的页面自有节点数。 | +| `resultJson.message` | 供人阅读的结果摘要,不应作为机器判断依据。 | + +响应可能增加其他可选字段;Agent 不应依赖本节未声明的字段。 + +示例: + +```json +{ + "success": true, + "nodeId": "DOC_NODE_ID", + "partId": "WHITEBOARD_PART_ID", + "resultJson": { + "mode": "append", + "createdNodeIds": ["generated-title-id", "generated-body-id"], + "idMap": { + "title": "generated-title-id", + "body": "generated-body-id" + }, + "deletedNodeCount": 0, + "message": "Created 2 Whiteboard nodes" + } +} +``` + +## 6. Update 公共节点字段 + +V1 可写节点公共字段如下: + +```ts +interface OpenNodeWriteBase { + id?: string; + layer?: "background" | "normal" | "foreground"; + zIndex?: number; + hidden?: boolean; +} + +interface OpenChildNodeWriteBase extends OpenNodeWriteBase { + parentId?: string; +} + +interface OpenSizedNodeWriteBase extends OpenChildNodeWriteBase { + x: number; + y: number; + width: number; + height: number; + angle?: number; +} + +interface OpenShapeNodeWrite extends OpenSizedNodeWriteBase { + type: "shape"; + geometry: `dml:${string}`; + text?: OpenTextWrite; + style?: OpenNodeStyleWrite; +} + +interface OpenTextNodeWrite extends OpenSizedNodeWriteBase { + type: "text"; + text: OpenTextWrite; + style?: OpenNodeStyleWrite; +} + +interface OpenConnectorNodeWrite extends OpenNodeWriteBase { + type: "connector"; + start: OpenConnectorEndpointWrite; + end: OpenConnectorEndpointWrite; + routing: OpenConnectorRouting; + waypoints?: OpenPoint[]; + style?: OpenNodeStyleWrite; +} + +interface OpenStickyNoteNodeWrite extends OpenSizedNodeWriteBase { + type: "stickyNote"; + text?: OpenTextWrite; + style?: OpenNodeStyleWrite; +} + +interface OpenFrameNodeWrite extends OpenNodeWriteBase { + type: "frame"; + x: number; + y: number; + width: number; + height: number; + angle?: 0; + title?: { + text: OpenTextWrite; + box?: { width: number; height: number }; + }; + style?: OpenNodeStyleWrite; + presentationOrder?: number; + resizeMode?: "free" | "fixedAspectRatio"; +} + +interface OpenGroupNodeWrite extends OpenChildNodeWriteBase { + id: string; + type: "group"; + x: number; + y: number; +} + +interface OpenVectorNodeWrite extends OpenSizedNodeWriteBase { + type: "vector"; + resource: OpenManagedVectorResourceWrite; +} + +interface OpenIconNodeWrite extends OpenSizedNodeWriteBase { + type: "icon"; + catalogId: OpenIconCatalogId; +} + +interface OpenPathNodeWrite extends OpenSizedNodeWriteBase { + type: "path"; + path: OpenPathDataWrite; + style?: OpenNodeStyleWrite; +} + +type OpenNodeWrite = + | OpenShapeNodeWrite + | OpenTextNodeWrite + | OpenConnectorNodeWrite + | OpenStickyNoteNodeWrite + | OpenFrameNodeWrite + | OpenGroupNodeWrite + | OpenVectorNodeWrite + | OpenIconNodeWrite + | OpenPathNodeWrite; +``` + +上面的联合类型是 update 的字段白名单。各辅助结构和完整枚举值在第 7 节定义; +未出现在对应分支中的字段不能发送。 + +| 字段 | 规则 | +| --- | --- | +| `id` | 可选的请求级临时 ID;非空、区分大小写、在请求内唯一。被引用时必须提供。 | +| `type` | 必填,必须是 V1 可写类型。 | +| `parentId` | 可选,引用同一请求中 group/frame 的临时 ID。 | +| `x`、`y` | 除 connector 外必填;有父节点时为父节点相对坐标。 | +| `width`、`height` | shape/text/stickyNote/frame/vector/icon/path 必填且大于 `0`;group/connector 只读。 | +| `angle` | 可选,默认 `0`;frame 只允许 `0`;group/connector 只读。 | +| `layer` | 可选;frame 默认 `background`,其他节点默认 `normal`。 | +| `zIndex` | 可选的非负整数;相同值时按请求顺序稳定排序。 | +| `hidden` | 可选布尔值,默认 `false`。 | + +以下 query 字段禁止写回: + +`children`、`absoluteBounds`、`locked`、`source`、`writeSupport`、 +`unsupportedFeatures`。 + +关系规则: + +- `parentId` 只能引用同一请求中的 group 或 frame。 +- frame 和 connector 必须是页面直属节点,不能带 `parentId`。 +- group 可以嵌套,也可以放在 frame 中。 +- `children` 始终由各子节点的 `parentId` 推导。 +- 父子关系不能成环。 +- group 必须有临时 `id`、至少两个直接子节点,且不能全部隐藏。 diff --git a/skills/mono/references/products/whiteboard/open-nodes-v1/04-text-style.md b/skills/mono/references/products/whiteboard/open-nodes-v1/04-text-style.md new file mode 100644 index 00000000..37eb5cf4 --- /dev/null +++ b/skills/mono/references/products/whiteboard/open-nodes-v1/04-text-style.md @@ -0,0 +1,395 @@ +# OpenNodes V1 — 支持矩阵、富文本和样式 + +> 本文件是 DWS OpenNodes V1 协议的拆分章节。按需读取入口见 +> [协议索引](../open-nodes-v1.md)。 + +## 7. 节点类型 + +### 7.1 支持矩阵 + +| `type` | query | update | 主要字段 | +| --- | --- | --- | --- | +| `shape` | 支持 | 支持 | `geometry`、`text?`、`style?` | +| `text` | 支持 | 支持 | `text`、`style?` | +| `connector` | 支持 | 支持 | `start`、`end`、`routing`、`waypoints?`、`style?` | +| `stickyNote` | 支持 | 支持 | `text?`、`style?` | +| `frame` | 支持 | 支持 | `title?`、`style?`、`presentationOrder?`、`resizeMode?` | +| `group` | 支持 | 支持 | 子关系通过 `parentId` 表达 | +| `image` | 支持 | 只读 | 仅公共字段 | +| `vector` | 支持 | 支持 | `resource` | +| `icon` | 支持 | 支持 | `catalogId` | +| `path` | 支持 | 支持 | `path`、`style?` | +| `pdf` | 支持 | 只读 | 仅公共字段 | +| `media` | 支持 | 只读 | 仅公共字段 | +| `webLink` | 支持 | 只读 | 仅公共字段 | +| `table` | 支持 | 只读 | 仅公共字段 | +| `chart` | 支持 | 只读 | 仅公共字段 | +| `uml` | 支持 | 只读 | 仅公共字段 | +| `swimlane` | 支持 | 只读 | 仅公共字段 | +| `mind` | 支持 | 只读 | 仅公共字段 | +| `timer` | 支持 | 只读 | 仅公共字段 | +| `placeholder` | 支持 | 只读 | 仅公共字段 | +| `unknown` | 支持 | 只读 | 未识别或尚未定义独立语义的节点统一映射到此类型 | + +表中标记为只读的类型仍会完整返回公共几何、层级和顺序信息,但 update 提交会以 +`nodeTypeUnsupported` 失败。 + +`timer`、`table`、`webLink` 不支持 V1 update。query 仅返回这些节点的公共几何、 +层级、顺序和诊断字段,不承诺完整业务字段。文字 run 中的 `link` 是富文本能力, +不属于 `webLink` 节点,V1 支持读写。 + +`webLink` 只保留只读查询;OpenNodes V1 不支持创建或重建该节点。 + +### 7.2 Text + +query 和 update 均支持普通段落、无序列表、有序列表、多 block、多 run 和文字 +链接: + +```ts +interface OpenTextRun { + text: string; + marks?: { + fontFamily?: string; + fontSize?: number; + bold?: boolean; + italic?: boolean; + underline?: boolean; + strike?: boolean; + color?: string; + highlight?: string; + }; + link?: { url: string }; +} + +interface OpenTextBlock { + type: "paragraph" | "bulletList" | "orderedList"; + horizontalAlign?: "left" | "center" | "right"; + runs: OpenTextRun[]; +} + +interface OpenTextWrite { + blocks: OpenTextBlock[]; + verticalAlign?: "top" | "center" | "bottom"; + padding?: number | [number, number]; +} + +interface OpenText extends OpenTextWrite { + plainText: string; + writeSupport: "readWrite" | "readOnly"; + unsupportedFeatures?: string[]; +} +``` + +约束: + +- `blocks.length >= 1`,每个 block 的 `type` 必须是 `paragraph`、 + `bulletList` 或 `orderedList`。 +- 每个 block 都必须满足 `runs.length >= 1`。 +- run 的 `text` 不能包含 `\r`、`\n`、U+2028 或 U+2029。换行和列表项使用 + 独立 block 表达;每一段或每个列表项应写成一个 block,而不是把原始换行符 + 放进单个 run。 +- 每个 `bulletList` / `orderedList` block 表示一个列表项;服务端会把连续且同类的 + block 解释为同一个列表中的多个列表项。 +- `link.url` 长度必须为 `1..2048`,不能包含控制字符、`<`、`>`。支持无 scheme + 的相对/裸链接,以及 `http`、`https`、`mailto`、`tel`、`dingtalk` scheme; + `javascript:`、`data:` 等可执行或未知 scheme 会被拒绝。 +- 相邻且 URL 相同的 linked run 会呈现为同一个链接,同时保留各 run 自己的 marks。 +- `fontSize > 0`,padding 各项必须大于等于 `0`。 +- `plainText`、文本级 `writeSupport` 和 `unsupportedFeatures` 是 query-only。 +- text 节点必须提供 `text`;shape 和 stickyNote 的 `text` 可省略。 +- 受支持的 paragraph、列表、链接、多 run 都保持 + `writeSupport = "readWrite"`;未知 list style、非法链接、未支持的 block 或 + run marks 会令文本和所属节点变为 `readOnly`。 + +下面的文本会显示为两个段落,第一段由两个不同样式的 run 组成: + +```json +{ + "blocks": [ + { + "type": "paragraph", + "horizontalAlign": "left", + "runs": [ + { + "text": "OpenNodes ", + "marks": { "fontSize": 18, "bold": true, "color": "#2563EB" } + }, + { + "text": "rich text", + "marks": { "fontSize": 18, "italic": true, "color": "#0F172A" } + } + ] + }, + { + "type": "paragraph", + "horizontalAlign": "right", + "runs": [ + { + "text": "第二段", + "marks": { "fontSize": 16, "underline": true, "color": "#047857" } + } + ] + } + ], + "verticalAlign": "center", + "padding": [4, 8] +} +``` + +同一 `OpenTextWrite` 结构适用于独立 text 节点、shape 文本、stickyNote 文本和 +frame title。 + +下面三个 block 会生成两个无序列表项,其中第二项包含文字链接: + +```json +{ + "blocks": [ + { + "type": "bulletList", + "runs": [{ "text": "准备输入数据" }] + }, + { + "type": "bulletList", + "runs": [ + { + "text": "查看钉钉文档", + "marks": { "underline": true }, + "link": { "url": "https://alidocs.dingtalk.com" } + } + ] + }, + { + "type": "orderedList", + "runs": [{ "text": "执行生成" }] + } + ] +} +``` + +颜色接受以下 CSS 形式:3/4/6/8 位十六进制、颜色名,以及 +`rgb()`、`rgba()`、`hsl()`、`hsla()`、`oklch()`、`lab()`、`lch()`、 +`color()`。字符串最长 128 字符,不能包含控制字符、`<`、`>` 或 `;`。 +这里只接受可独立解析的字面量;依赖外部样式上下文的 `var()`、`calc()`、 +`color-mix()` 等动态表达式不属于 V1。颜色名必须是标准 CSS named color, +任意字母串不会被当成颜色。 + +### 7.3 Style + +query 可表达: + +- `none`、`solid`、`theme`、线性渐变、径向渐变和图片 paint。 +- shadow、blur 和 unknown effect。 + +V1 update 支持 `none`、`solid`、`theme`、线性渐变、九方向径向渐变,以及 +单个自定义 shadow。主题明暗参数和渐变色标位置使用 `0~100` 的百分比, +不会归一化成 `0~1`: + +```ts +type OpenRadialGradientPosition = + | "topLeft" + | "topCenter" + | "topRight" + | "centerLeft" + | "center" + | "centerRight" + | "bottomLeft" + | "bottomCenter" + | "bottomRight"; + +interface OpenColorStop { + offset: number; + color: string; + opacity?: number; +} + +interface OpenImagePaint { + type: "image"; + resource: { + kind: "managed" | "external" | "embedded" | "unresolved"; + resourceId?: string; + }; + intrinsicWidth: number; + intrinsicHeight: number; +} + +type OpenPaint = + | { type: "none" } + | { type: "solid"; color: string; opacity?: number } + | { + type: "theme"; + token: string; + lumMod?: number; + lumOff?: number; + resolvedColor?: string; + } + | { + type: "linearGradient"; + angle: number; + stops: OpenColorStop[]; + } + | { + type: "radialGradient"; + position: OpenRadialGradientPosition | "custom"; + stops: OpenColorStop[]; + } + | OpenImagePaint; + +type OpenEffect = + | { + type: "shadow"; + offsetX: number; + offsetY: number; + blur: number; + color: string; + opacity: number; + } + | { type: "blur"; blur: number } + | { type: "unknown" }; + +interface OpenNodeStyle { + opacity?: number; + fill?: OpenPaint; + stroke?: { + paint: OpenPaint; + width?: number; + dash?: number[]; + lineCap?: "butt" | "round" | "square"; + lineJoin?: "miter" | "round" | "bevel"; + }; + effects?: OpenEffect[]; + writeSupport: "readWrite" | "readOnly"; + unsupportedFeatures?: string[]; +} + +interface OpenColorStopWrite { + offset: number; // [0, 100],百分比 + color: string; + opacity?: number; // [0, 1] +} + +type OpenPaintWrite = + | { type: "none" } + | { type: "solid"; color: string; opacity?: number } + | { + type: "theme"; + token: string; + lumMod?: number; // [0, 100],默认 100 + lumOff?: number; // [0, 100],默认 0 + } + | { + type: "linearGradient"; + angle: number; + stops: OpenColorStopWrite[]; + } + | { + type: "radialGradient"; + position: OpenRadialGradientPosition; + stops: OpenColorStopWrite[]; + }; + +interface OpenShadowEffectWrite { + type: "shadow"; + offsetX: number; + offsetY: number; + blur: number; + color: string; + opacity: number; +} + +interface OpenNodeStyleWrite { + opacity?: number; + fill?: OpenPaintWrite; + stroke?: { + paint: OpenPaintWrite; + width?: number; + dash?: number[]; + lineCap?: "butt" | "round" | "square"; + lineJoin?: "miter" | "round" | "bevel"; + }; + effects?: OpenShadowEffectWrite[]; +} +``` + +约束: + +- opacity 范围为 `[0, 1]`。 +- solid paint 的 `color` 与 `opacity` 在 query 后仍保持独立字段,不会合并为 + 动态 CSS 表达式。 +- theme 的 `token` 必须能在当前白板主题中解析; + `lumMod`、`lumOff` 范围均为 `[0, 100]`。token 不存在时在 + Request graph 阶段返回 `themeTokenNotFound`,不会写出部分节点。 + token 长度为 `1~64`,首尾不能有空白,也不能包含空白、控制字符、 + `<`、`>` 或 `;`。 +- query 的 theme paint 还会返回按当前白板主题计算出的 query-only + `resolvedColor`。调用方写入时只传 `token/lumMod/lumOff`,不传 + `resolvedColor`。 +- 渐变必须至少有一个 stop;`offset` 范围为 `[0, 100]`,单位是百分比。 +- 线性渐变 `angle` 范围为 `[0, 360]`。 +- 径向渐变只接受上述九宫格位置。query 遇到九宫格以外的位置时返回 + `position: "custom"` 并将该节点标为只读。 +- `effects` 最多包含一个 `shadow`;`offsetX/offsetY` 必须是有限数, + `blur >= 0`,shadow opacity 范围为 `[0, 1]`。 +- stroke width 和 dash 各项必须大于等于 `0`。 +- 一旦提供 stroke,`stroke.paint` 必填。 +- 样式级 `writeSupport` 和 `unsupportedFeatures` 是 query-only。 +- 能在当前白板主题中解析且明暗参数合法的 theme paint 可写。无法解析的主题 + token、越界的主题明暗参数、image paint、blur、unknown effect、叠加 effect + 或自定义径向位置会令节点只读;受支持的 theme、渐变和单个 shadow 本身不会 + 令节点只读。 + +主题色示例: + +```json +{ + "fill": { + "type": "theme", + "token": "ac3", + "lumMod": 20, + "lumOff": 80 + }, + "stroke": { + "paint": { + "type": "theme", + "token": "sk1", + "lumMod": 80, + "lumOff": 20 + }, + "width": 2 + } +} +``` + +示例中的 `ac3`、`sk1` 只是主题 token 示例,不是所有白板都可用的全局枚举。 +调用方只能使用已确认可由当前白板主题解析的 token;无法确认时应改用 +`solid` 颜色。 + +DWS 不提供新增、修改或切换主题的命令。当前白板没有有效主题时,应改用 +`solid`。 + +示例: + +```json +{ + "fill": { + "type": "linearGradient", + "angle": 35, + "stops": [ + { "offset": 0, "color": "#1677ff", "opacity": 0.4 }, + { "offset": 100, "color": "#69b1ff" } + ] + }, + "effects": [ + { + "type": "shadow", + "offsetX": 8, + "offsetY": 8, + "blur": 19, + "color": "rgba(93,190,172,1)", + "opacity": 0.5 + } + ] +} +``` + +未传 `style` 时使用节点类型的默认样式。调用方如需稳定的视觉结果,应显式传入 +`fill` 和 `stroke`。 diff --git a/skills/mono/references/products/whiteboard/open-nodes-v1/05-shape-frame-group-connector.md b/skills/mono/references/products/whiteboard/open-nodes-v1/05-shape-frame-group-connector.md new file mode 100644 index 00000000..f687200b --- /dev/null +++ b/skills/mono/references/products/whiteboard/open-nodes-v1/05-shape-frame-group-connector.md @@ -0,0 +1,186 @@ +# OpenNodes V1 — Shape、Text、Sticky note、Frame、Group 和 Connector + +> 本文件是 DWS OpenNodes V1 协议的拆分章节。按需读取入口见 +> [协议索引](../open-nodes-v1.md)。 + +### 7.4 Shape + +shape 必须提供 `geometry`,格式为 `dml:`: + +```json +{ + "id": "shape-1", + "type": "shape", + "x": 100, + "y": 80, + "width": 160, + "height": 100, + "geometry": "dml:roundRect", + "style": { + "fill": { + "type": "solid", + "color": "#DCEEFF" + }, + "stroke": { + "paint": { + "type": "solid", + "color": "#225588" + }, + "width": 2 + } + } +} +``` + +query 可能返回 `adjustments`,但 V1 update 不支持写入;带 adjustments 的 +shape 会标为只读。`dml-v1` 的完整 geometry 目录见附录 A。 + +### 7.5 Text node 与 Sticky note + +text node 使用公共几何、必填 `text` 和可选 `style`。 + +stickyNote 使用公共几何、可选 `text` 和可选 `style`。省略 `text` 时创建空 +便签。query 还可能返回: + +- `creator`:创建者展示信息。 +- `tags`:标签 ID、文本和背景 paint。 + +这两个字段是 query-only;存在 creator 或 tags 的 stickyNote 会被标为只读。 + +### 7.6 Frame + +frame 的 update 结构见第 6 节 `OpenFrameNodeWrite`。 + +约束: + +- frame 必须是页面直属节点,不能带 `parentId`。 +- angle 只允许 `0`。 +- `presentationOrder` 是非负整数,并且不能和已有或本次创建的 frame 冲突。 +- frame 的子节点通过子节点 `parentId` 引用 frame 临时 ID。 +- frame 不能包含 frame 或 connector。 +- frame 默认 layer 为 `background`。 + +### 7.7 Group + +group 的写入字段只有公共字段中的 `id`、`parentId?`、`x`、`y`、`layer?`、 +`zIndex?` 和 `hidden?`。其 width、height、angle 和 children 都由服务端根据 +子节点推导。 + +group 必须: + +- 提供临时 `id`。 +- 至少包含两个直接子节点。 +- 至少有一个直接子节点可见。 + +group 的任一子节点为只读时,query 会把 group 一并标为只读。 + +### 7.8 Connector + +connector 的几何由端点和路由推导,因此 update 不能提供 `x`、`y`、 +`width`、`height` 或 `angle`。 + +query 的连接线结构如下: + +```ts +type OpenConnectorRouting = + | "straight" + | "polyline" + | "curve" + | "orthogonal"; + +interface OpenConnectorMarker { + catalogId: string; +} + +type OpenConnectorAnchor = + | { + mode: "fixed"; + side: "top" | "right" | "bottom" | "left"; + position: OpenPoint; + } + | { + mode: "fixed"; + side: "custom"; + position: OpenPoint; + }; + +type OpenConnectorEndpoint = + | { + type: "point"; + point: OpenPoint; + marker: OpenConnectorMarker; + } + | { + type: "node"; + nodeRef: { scope: "document"; id: string }; + anchor: OpenConnectorAnchor; + resolvedPoint: OpenPoint; + marker: OpenConnectorMarker; + }; + +interface OpenBezierSegment { + start: OpenPoint; + control1: OpenPoint; + control2: OpenPoint; + end: OpenPoint; +} + +type OpenResolvedConnectorPath = + | { type: "polyline"; points: OpenPoint[] } + | { type: "bezier"; segments: OpenBezierSegment[] }; +``` + +update 端点有两种形式: + +```ts +type OpenConnectorEndpointWrite = + | { + type: "point"; + point: { x: number; y: number }; + marker?: { catalogId: "none" | "arrow.open" | "arrow.filled" }; + } + | { + type: "node"; + nodeRef: { scope: "request"; id: string }; + anchor?: + | { mode: "auto" } + | { + mode: "fixed"; + side: "top" | "right" | "bottom" | "left"; + }; + marker?: { catalogId: "none" | "arrow.open" | "arrow.filled" }; + }; +``` + +query 的 marker `catalogId` 使用 `string`,以便返回无法映射的既有 marker; +update 只接受上面列出的 `"none"`、`"arrow.open"`、`"arrow.filled"`。 + +路由规则: + +| `routing` | `waypoints` | +| --- | --- | +| `straight` | 禁止提供,包括空数组。 | +| `polyline` | 必须至少提供一个。 | +| `curve` | 可选。 | +| `orthogonal` | 可选;显式点路径的相邻线段必须水平或垂直。 | + +其他约束: + +- 所有 point 和 waypoint 都使用页面绝对坐标。 +- node 端点只能引用同一请求中的 shape、text、stickyNote、frame、group 或 path。 +- node 端点不能引用隐藏节点、connector 或 query 中既有节点。 +- update 引用范围固定为 `scope: "request"`;query 返回的节点引用范围为 + `scope: "document"`,不能直接回写。 +- 同一连接线的两端不能引用同一个节点。 +- 零长度或无效路径会被拒绝。 +- marker 省略时默认为 `none`。 +- anchor 省略时按 `auto` 处理。 + +query 额外返回服务端解析后的: + +- node 端点 `resolvedPoint`。 +- fixed anchor 的归一化 `position`。 +- `resolvedPath`,类型为 polyline points 或 cubic bezier segments。 +- 由真实路径推导的 `absoluteBounds`。 + +这些解析字段都是 query-only。 diff --git a/skills/mono/references/products/whiteboard/open-nodes-v1/06-vector-icon-path.md b/skills/mono/references/products/whiteboard/open-nodes-v1/06-vector-icon-path.md new file mode 100644 index 00000000..f48b559f --- /dev/null +++ b/skills/mono/references/products/whiteboard/open-nodes-v1/06-vector-icon-path.md @@ -0,0 +1,313 @@ +# OpenNodes V1 — Vector、Icon 和 Path + +> 本文件是 DWS OpenNodes V1 协议的拆分章节。按需读取入口见 +> [协议索引](../open-nodes-v1.md)。 + +### 7.9 Vector(已上传 SVG/矢量资源) + +`vector` 表示已经通过 `dws doc media upload` 获得稳定引用的 SVG/矢量图片。 +OpenNodes 只接收上传结果中的资源引用,不接收本地路径或原始 SVG/XML 内容。 + +上传时必须使用与后续白板更新相同的文档 `nodeId`: + +```bash +dws doc media upload \ + --node \ + --file ./icon.svg \ + --mime-type image/svg+xml \ + --format json +``` + +将上传结果的 `resourceId` 和 `resourceUrl` 分别写入 +`resource.resourceId` 和 `resource.url`。 + +Update 必须提供完整的托管资源信息: + +```ts +interface OpenManagedVectorResourceWrite { + kind: "managed"; + resourceId: string; + url: string; +} +``` + +完整节点结构见第 6 节 `OpenVectorNodeWrite`。 + +`resource` 字段规则: + +| 字段 | 必填 | 规则 | +| --- | --- | --- | +| `kind` | 是 | 当前只允许固定值 `managed`,表示资源已经通过 DWS 上传。 | +| `resourceId` | 是 | 资源稳定 ID,长度 1~256,只允许字母、数字、`.`、`_`、`:`、`-`。 | +| `url` | 是 | 已上传资源地址,最长 4096;必须包含且只能包含一个同值的 `resourceId` 查询参数。 | + +`url` 必须直接使用 `dws doc media upload` 返回的 `resourceUrl`,不得自行拼装 +或修改。 + +以下内容会被拒绝: + +- 原始 SVG/XML、`data:`、`blob:`、`http:` URL。 +- `//host/path` 协议相对地址,以及自行构造或修改的其他相对地址。 +- 含空白、控制字符、反斜杠、fragment 或用户凭证的 URL。 +- URL 缺少 `resourceId`、重复出现 `resourceId`,或者 URL 中 ID 与显式 + `resource.resourceId` 不一致。 +- `resource` 缺失、字段不完整、`kind` 不是 `managed`,或包含未知字段。 + +完整 Append 示例: + +```json +{ + "source": { + "schemaVersion": "1.0", + "catalogVersion": "dml-v1", + "nodes": [ + { + "id": "uploaded-svg-1", + "type": "vector", + "x": 100, + "y": 80, + "width": 240, + "height": 180, + "angle": 0, + "resource": { + "kind": "managed", + "resourceId": "0c1c94e1-f9af-4228-b32f-42bbd1555253", + "url": "https://resources.example.com/assets/opaque-path?resourceId=0c1c94e1-f9af-4228-b32f-42bbd1555253" + } + } + ] + }, + "overwrite": false +} +``` + +示例中的 `resources.example.com` 是占位域名,实际调用必须使用 +`dws doc media upload` 返回的 `resourceUrl`。 + +`resourceId` 同时显式出现并包含在 URL 中,用于校验资源身份与地址是否一致。 +两者必须来自同一次 `dws doc media upload` 结果。 + +Query 的 `resource` 可能是: + +```ts +type OpenVectorResource = + | { kind: "managed"; resourceId: string; url: string } + | { kind: "external" | "embedded" | "unresolved" }; +``` + +- 能识别为托管资源的引用返回完整 `managed` 信息。 +- HTTP(S) 外链但不满足托管资源契约时返回 `external`。 +- `data:`/`blob:` 返回 `embedded`。 +- 其他缺失或无法识别的地址返回 `unresolved`。 +- 非 `managed` 资源会令节点只读,并分别产生 + `vector.resource.external`、`vector.resource.embedded` 或 + `vector.resource.unresolved`。 + +V1 vector update 不接受 `style`。既有节点包含 V1 无法表达的 fill、stroke、 +opacity、effect 或 adjustments 时,query 仍可读取资源和几何,但节点会标为只读。 +不影响资源内容的兼容性装饰不会单独令节点变为只读。 + +DWS 会校验资源引用。上传与 `whiteboard update` 必须使用同一个文档 +`nodeId`;不要跨文档复用资源,也不要使用临时 `uploadUrl`。 + +### 7.10 Icon(内置图标) + +`icon` 表示内置图标。OpenNodes 使用版本化的 `catalogId` 作为稳定标识, +允许值见下列类型定义和附录 B。 + +Update 结构: + +```ts +type OpenIconCatalogId = + | `emoji/${ + | "happy" + | "smile" + | "laugh" + | "fighting" + | "like" + | "ok" + | "please" + | "face-plam" + | "tears-of-joy" + | "cry" + | "question" + | "face-with-sweat" + | "bloody-nose" + | "doggy"}` + | `tools/${ + | "pad" + | "blue-note" + | "yellow-notes" + | "chart" + | "chart-2" + | "pencil" + | "pen" + | "bag" + | "rocket" + | "fire" + | "gold" + | "light" + | "pin" + | "red-flag" + | "tea" + | "island" + | "ball" + | "lucky-fish" + | "coffee" + | "milky-tea" + | "pan"}` + | `priority/priority-${1 | 2 | 3 | 4 | 5 | 6 | 7}` + | `task/${ + | "task-start" + | "task-oct" + | "task-3oct" + | "task-half" + | "task-5oct" + | "task-7oct" + | "task-done"}`; +``` + +完整节点结构见第 6 节 `OpenIconNodeWrite`。 + +完整 Append 示例: + +```json +{ + "source": { + "schemaVersion": "1.0", + "catalogVersion": "dml-v1", + "nodes": [ + { + "id": "pencil-icon", + "type": "icon", + "x": 100, + "y": 80, + "width": 48, + "height": 48, + "catalogId": "tools/pencil" + } + ] + }, + "overwrite": false +} +``` + +规则: + +- `catalogId` 必填,格式为 `/`,并且必须精确命中附录 B 的 + `dml-v1` allowlist;大小写、连字符和历史拼写都不能自行修正。 +- 当前目录有 `emoji`、`tools`、`priority`、`task` 四组,共 49 项。 +- update 只接受 `catalogId`,不接受 `group`、`name`、`style` 或 `resource`。 +- 内置 icon 不需要调用方上传资源,也不需要 `resourceId`。 +- 任意已上传 SVG 或自定义图标应使用 `vector`,并按 7.9 节提供完整 + `resource` 信息,不能伪造一个 icon `catalogId`。 +- icon 可以作为 group/frame 子节点;V1 connector 的 node 端点当前仍不接受 + icon 作为目标。 + +Query 返回相同的 `catalogId`。既有 icon 无法映射到当前目录时,节点仍会以 +`type: "icon"` 返回以便诊断,但 `writeSupport` 为 `readOnly`,原因包含 +`icon.catalogId`。非默认 opacity、filter/effect、text 或 adjustments 同样会令节点 +只读。 + +### 7.11 Path(自由画笔) + +`path` 表示自由画笔轨迹。OpenNodes 保留两组互相独立的尺寸: + +- 节点公共 `width` / `height` 是画布上的实际渲染尺寸,缩放节点时会变化。 +- `path.intrinsicWidth` / `path.intrinsicHeight` 是 SVG path 自身的坐标空间尺寸, + 表示 `path.data` 使用的内部坐标空间。 + +```ts +interface OpenPathData { + data: string; + intrinsicWidth: number; + intrinsicHeight: number; +} + +type OpenPathDataWrite = OpenPathData; +``` + +完整 update 节点结构见第 6 节 `OpenPathNodeWrite`。query 和 update 的 `path` +字段结构相同,但 update 仍必须满足下方命令子集和大小限制。 + +```json +{ + "id": "freehand-stroke", + "type": "path", + "x": 120, + "y": 100, + "width": 500, + "height": 150, + "path": { + "data": "M0,75 Q50,0 100,75 Q150,150 200,75 Q250,0 300,75 Q350,150 400,75 Q450,0 500,75", + "intrinsicWidth": 500, + "intrinsicHeight": 150 + }, + "style": { + "fill": { "type": "none" }, + "stroke": { + "paint": { "type": "solid", "color": "#7C3AED" }, + "width": 10, + "lineCap": "round", + "lineJoin": "round" + } + } +} +``` + +下面是一个仍然只使用 V1 命令子集、但包含 12 段二次贝塞尔曲线的蝴蝶轮廓。 +它显式回到起点,因此不需要使用尚未支持的 `Z`: + +```json +{ + "id": "complex-butterfly-path", + "type": "path", + "x": 1500, + "y": 1215, + "width": 500, + "height": 420, + "path": { + "data": "M250,180 Q210,105 145,70 Q55,25 35,110 Q10,195 125,220 Q35,275 80,355 Q125,415 210,315 Q235,285 250,250 Q265,285 290,315 Q375,415 420,355 Q465,275 375,220 Q490,195 465,110 Q445,25 355,70 Q290,105 250,180", + "intrinsicWidth": 500, + "intrinsicHeight": 420 + }, + "style": { + "opacity": 0.96, + "fill": { + "type": "solid", + "color": "#EDE9FE", + "opacity": 0.72 + }, + "stroke": { + "paint": { + "type": "solid", + "color": "#6D28D9" + }, + "width": 8, + "lineCap": "round", + "lineJoin": "round" + } + } +} +``` + +V1 path 写入约束如下: + +- `data` 必须是一个绝对 `M`,后跟至少一个显式写出的绝对 `Q`;不接受相对命令, + 也不接受 `L`、`C`、`A`、`Z` 等通用 SVG 命令。 +- 所有命令参数必须完整且为有限数值;单节点 `data` 最长 1 MiB,最多 50,000 个 + 命令。 +- `intrinsicWidth` 和 `intrinsicHeight` 必须为有限正数;它们不要求等于节点的 + `width` 和 `height`。 +- 未传 `style` 时,默认使用透明填充、`#222222` 描边、宽度 5,以及 round + line cap/join。需要稳定视觉结果时仍应显式传入 `style`。 +- path 可以作为 group/frame 的子节点,也可以作为同一 update 请求中 connector + 的 node 端点。 + +query 会把其他能够解析的 SVG path 保留为 `type: "path"`,但标记为 +`readOnly`,原因包含 `path.commands`;超出上述 V1 限制时使用 +`path.data.size` 或 `path.commands.limit`。既有 path 带 text、adjustments、非 +`nonzero` fillRule 或 V1 无法表达的样式时也会只读。仅包含不影响画笔几何的 +兼容性信息时,不会因此变为只读。theme stroke 遵循通用 style 契约:query 会 +保留 token 和明暗参数;只要 token 能在当前白板主题中解析,就可以按 theme +paint 回写。 diff --git a/skills/mono/references/products/whiteboard/open-nodes-v1/07-examples-errors-write-support.md b/skills/mono/references/products/whiteboard/open-nodes-v1/07-examples-errors-write-support.md new file mode 100644 index 00000000..1212068d --- /dev/null +++ b/skills/mono/references/products/whiteboard/open-nodes-v1/07-examples-errors-write-support.md @@ -0,0 +1,270 @@ +# OpenNodes V1 — Update 示例、回写规则、错误模型和 writeSupport + +> 本文件是 DWS OpenNodes V1 协议的拆分章节。按需读取入口见 +> [协议索引](../open-nodes-v1.md)。 + +## 8. Update 示例 + +### 8.1 Append 一个文本节点 + +```json +{ + "source": { + "schemaVersion": "1.0", + "catalogVersion": "dml-v1", + "nodes": [ + { + "id": "title", + "type": "text", + "x": 120, + "y": 80, + "width": 240, + "height": 48, + "text": { + "blocks": [ + { + "type": "paragraph", + "horizontalAlign": "left", + "runs": [ + { + "text": "Hello OpenNodes", + "marks": { + "fontSize": 16, + "color": "#223344" + } + } + ] + } + ], + "verticalAlign": "center", + "padding": [2, 4] + } + } + ] + } +} +``` + +### 8.2 Append 两个形状和一条引用连接线 + +```json +{ + "source": { + "schemaVersion": "1.0", + "catalogVersion": "dml-v1", + "nodes": [ + { + "id": "left", + "type": "shape", + "x": 80, + "y": 100, + "width": 120, + "height": 80, + "geometry": "dml:roundRect" + }, + { + "id": "right", + "type": "shape", + "x": 360, + "y": 100, + "width": 120, + "height": 80, + "geometry": "dml:roundRect" + }, + { + "id": "line", + "type": "connector", + "start": { + "type": "node", + "nodeRef": { + "scope": "request", + "id": "left" + }, + "anchor": { + "mode": "fixed", + "side": "right" + } + }, + "end": { + "type": "node", + "nodeRef": { + "scope": "request", + "id": "right" + }, + "anchor": { + "mode": "fixed", + "side": "left" + }, + "marker": { + "catalogId": "arrow.filled" + } + }, + "routing": "straight" + } + ] + } +} +``` + +### 8.3 Overwrite 整页 + +```json +{ + "overwrite": true, + "source": { + "schemaVersion": "1.0", + "catalogVersion": "dml-v1", + "nodes": [ + { + "id": "replacement", + "type": "text", + "x": 120, + "y": 80, + "width": 240, + "height": 48, + "text": { + "blocks": [ + { + "type": "paragraph", + "runs": [ + { + "text": "Replacement content" + } + ] + } + ] + } + } + ] + } +} +``` + +### 8.4 清空当前页面 + +```json +{ + "overwrite": true, + "source": { + "schemaVersion": "1.0", + "catalogVersion": "dml-v1", + "nodes": [] + } +} +``` + +## 9. Query 数据不能直接回写 + +query 是完整可读投影,update 是受约束的创建协议,两者不是对称 JSON: + +| Query 字段/能力 | Update 处理方式 | +| --- | --- | +| 真实 `id` | 只能作为请求级临时 ID;不能引用既有 document 节点。 | +| `children` | 删除,通过子节点 `parentId` 重建。 | +| `absoluteBounds` | 删除;普通节点使用 `x/y/width/height`,connector 使用端点和路由字段。 | +| `locked`、`source`、`writeSupport`、`unsupportedFeatures` | 删除,均为 query-only。 | +| 文本 `plainText` | 删除,由服务端根据 paragraph 和 run 重新计算。 | +| 多 paragraph、列表、多 run、文字链接 | 可以保留;每个 block 必须是受支持类型,且 run 内不能包含原始换行符。 | +| 未知 list style、非法链接或未支持的 block/marks | 需要移除或降级为受支持的 block/run。 | +| theme paint | 保留 `token/lumMod/lumOff`,删除 query-only `resolvedColor`;token 必须能在当前白板主题中解析。 | +| image paint | 需要降级成受支持的 paint,或不更新该节点。 | +| 受支持的 linear/radial gradient、单个 shadow | 可以保留;gradient offset 使用 `0~100`,radial `custom` 不能回写。 | +| blur、unknown 或叠加 effects | 需要删除、降级成单个 shadow,或不更新该节点。 | +| connector `scope: "document"` | 不能回写;改为引用同一请求节点的 `scope: "request"`。 | +| connector `resolvedPoint`、`position`、`resolvedPath` | 删除,均由服务端重新计算。 | +| shape `adjustments` | V1 不支持写入。 | +| stickyNote `creator`、`tags` | V1 不支持写入。 | + +即使 query 节点显示 `writeSupport = "readWrite"`,update 仍会对版本、目录、 +字段和请求关系做完整校验。调用方不应跳过 update 错误处理。 + +## 10. 错误模型 + +### 10.1 顶层错误码 + +| 错误码 | 含义 | +| --- | --- | +| `invalidRequest.whiteboard.schemaInvalid` | JSON、字段或节点 schema 不合法。 | +| `invalidRequest.whiteboard.catalogVersionUnsupported` | catalogVersion 不受支持。 | +| `invalidRequest.whiteboard.validationFailed` | 节点间引用、父子关系或路径关系不合法。 | +| `invalidRequest.whiteboard.emptySource` | append 的 nodes 为空。 | +| `invalidRequest.whiteboard.overwriteUnsafe` | overwrite 安全预检失败。 | + +### 10.2 DWS 错误输出 + +远端校验失败时,DWS 以统一 CLI 错误结构返回: + +```json +{ + "error": { + "category": "api", + "reason": "business_error", + "server_key": "whiteboard", + "server_error_code": "invalidRequest.whiteboard.validationFailed", + "message": "Whiteboard request graph is invalid", + "trace_id": "TRACE_ID" + } +} +``` + +部分服务错误会使用更宽泛的 `invalidRequest.inputArgs.invalid`。Agent 应结合 +`server_error_code` 和 `message` 修正输入;需要排障时保留 `trace_id`。JSON 或 +信封级错误可能由 CLI 本地返回,不一定包含 `server_key` 和 `trace_id`。 + +校验类错误不可通过原样重试恢复。常见原因包括: + +- Schema:缺少字段、未知字段、类型或枚举值错误、提交 query-only 字段、 + 节点类型不支持。 +- 请求关系:临时 ID 重复、引用不存在、父子关系非法、连接线目标不支持、 + 路径退化或主题 token 不存在。 +- Overwrite 预检:锁定节点、禁止删除、关联数据无法安全处理或删除失败。 + +任一阶段失败都不会保留部分更新。 + +## 11. writeSupport 的含义 + +`writeSupport` 表示当前 query 节点是否能由 V1 update 无损表达,不代表用户 +权限,也不代表 overwrite 是否允许移除该既有节点。 + +以下 `unsupportedFeatures` 均为对外返回的诊断枚举值: + +- `node.source.master`、`node.locked`、`node.role`、`node.placeholder`、 + `node.extras`、`node.ability`。 +- `node.type.image`、`node.type.pdf`、`node.type.media`、 + `node.type.webLink`、`node.type.table`、`node.type.chart`、 + `node.type.uml`、`node.type.swimlane`、`node.type.mind`、 + `node.type.timer`、`node.type.placeholder`、`node.type.unknown`。 +- `text.list.unsupported`、`text.link.unsupported`、`text.block.unsupported`、 + `text.lineBreak.unsupported`、`text.marks.unsupported`、 + `text.color.unsupported`、`text.highlight.unsupported`。 +- `style.fill.color`、`style.fill.opacity`、`style.fill.theme.token`、 + `style.fill.theme.unresolved`、`style.fill.theme.modifier`、 + `style.fill.theme.opacity`、 + `style.fill.gradient.angle`、`style.fill.gradient.offset`、 + `style.fill.gradient.color`、`style.fill.gradient.opacity`、 + `style.fill.gradient.position`、`style.fill.image`。 +- `style.stroke.color`、`style.stroke.opacity`、`style.stroke.theme.token`、 + `style.stroke.theme.unresolved`、 + `style.stroke.theme.modifier`、`style.stroke.theme.opacity`、 + `style.stroke.gradient.angle`、`style.stroke.gradient.offset`、 + `style.stroke.gradient.color`、`style.stroke.gradient.opacity`、 + `style.stroke.gradient.position`、`style.stroke.image`。 +- `style.effects`。 +- `shape.adjustments`、`stickyNote.creator`、`stickyNote.tags`。 +- `vector.resource.external`、`vector.resource.embedded`、 + `vector.resource.unresolved`、`vector.fill`、`vector.stroke`、 + `vector.opacity`、`vector.effect`、`vector.adjustments`。 +- `icon.catalogId`、`icon.opacity`、`icon.effect`、`icon.text`、 + `icon.adjustments`。 +- `path.commands`、`path.data.size`、`path.commands.limit`、`path.text`、 + `path.fillRule`、`path.adjustments`。 +- `connector.parent`、`connector.marker.unsupported`、 + `connector.target.unexposed`、`connector.target.unsupported`、 + `connector.anchor.unresolved`、`connector.anchor.custom`、 + `connector.selfLoop`。 +- `group.angle`、`group.children.minimum`、`group.children.hidden`、 + `group.child.readOnly`。 +- `frame.angle`、`frame.child.frame`、`frame.child.readOnly`。 + +调用方应把 `unsupportedFeatures` 当作诊断信息,不应把当前枚举穷举写死为 +业务逻辑。真正可写与否以 `writeSupport` 和 update 校验结果为准。 diff --git a/skills/mono/references/products/whiteboard/open-nodes-v1/08-catalogs.md b/skills/mono/references/products/whiteboard/open-nodes-v1/08-catalogs.md new file mode 100644 index 00000000..cd8b2063 --- /dev/null +++ b/skills/mono/references/products/whiteboard/open-nodes-v1/08-catalogs.md @@ -0,0 +1,61 @@ +# OpenNodes V1 — dml-v1 Geometry 和 Icon 完整目录 + +> 本文件是 DWS OpenNodes V1 协议的拆分章节。按需读取入口见 +> [协议索引](../open-nodes-v1.md)。 + +## 附录 A:dml-v1 geometry 目录 + +写入时在以下名称前加 `dml:`,例如 `rect` 写成 `dml:rect`。当前目录共 +183 项,目录版本由 `catalogVersion = "dml-v1"` 标识。 + +```text +accentBorderCallout1 accentBorderCallout2 accentBorderCallout3 +accentCallout1 accentCallout2 accentCallout3 actionButtonBackPrevious +actionButtonBeginning actionButtonBlank actionButtonDocument actionButtonEnd +actionButtonForwardNext actionButtonHelp actionButtonHome +actionButtonInformation actionButtonMovie actionButtonReturn actionButtonSound +active allGeneralization arc attribute bentArrow bentUpArrow bevel bind blockArc +borderCallout1 borderCallout2 borderCallout3 bracePair bracketPair callout1 +callout2 callout3 can chevron chord circularArrow cloud cloudCallout comment +component control convert corner cube curvedDownArrow curvedLeftArrow +curvedRightArrow curvedUpArrow dataStorage decagon delete diagStripe diamond +dodecagon donut doubleWave downArrow downArrowCallout ellipse ellipseRibbon +ellipseRibbon2 entity entitySet flowChartAlternateProcess flowChartCollate +flowChartConnector flowChartDecision flowChartDelay flowChartDisplay +flowChartDocument flowChartExtract flowChartInputOutput flowChartInternalStorage +flowChartMagneticDisk flowChartMagneticDrum flowChartMagneticTape +flowChartManualInput flowChartManualOperation flowChartMerge +flowChartMultidocument flowChartOffpageConnector flowChartOnlineStorage +flowChartOr flowChartPredefinedProcess flowChartPreparation +flowChartPunchedCard flowChartPunchedTape flowChartSort +flowChartSummingJunction flowChartTerminator foldedCorner frame generalization +halfFrame heart heptagon hexagon history homePlate horizontalDivCircle +horizontalScroll irregularSeal1 irregularSeal2 leftArrow leftArrowCallout +leftBrace leftBracket leftRightArrow leftRightArrowCallout leftRightUpArrow +leftUpArrow lightningBolt mathDivide mathEqual mathMinus mathMultiply +mathNotEqual mathPlus moon multiClass multiValuedAttribute noSmoking node +nonIsoscelesTrapezoid notchedRightArrow octagon parallelogram pentagon person +pie plaque plus quadArrow quadArrowCallout receiveSignal rect relationship +ribbon ribbon2 rightArrow rightArrowCallout rightBrace rightBracket round1Rect +round2DiagRect round2SameRect roundRect rtTriangle smileyFace snip1Rect +snip2DiagRect snip2SameRect snipRoundRect star10 star12 star16 star24 star32 +star4 star5 star6 star7 star8 stripedRightArrow sun teardrop triangle upArrow +upArrowCallout upDownArrow user uturnArrow verticalDivCircle verticalScroll +wave weakEntitySet weakRelationship wedgeEllipseCallout wedgeRectCallout +wedgeRoundRectCallout +``` + +## 附录 B:dml-v1 icon 目录 + +写入时必须使用完整的 `/`。当前共 4 组 49 项,目录版本由 +`catalogVersion = "dml-v1"` 标识。 + +| group | 数量 | name(组成 `group/name`) | +| --- | ---: | --- | +| `emoji` | 14 | `happy`、`smile`、`laugh`、`fighting`、`like`、`ok`、`please`、`face-plam`、`tears-of-joy`、`cry`、`question`、`face-with-sweat`、`bloody-nose`、`doggy` | +| `tools` | 21 | `pad`、`blue-note`、`yellow-notes`、`chart`、`chart-2`、`pencil`、`pen`、`bag`、`rocket`、`fire`、`gold`、`light`、`pin`、`red-flag`、`tea`、`island`、`ball`、`lucky-fish`、`coffee`、`milky-tea`、`pan` | +| `priority` | 7 | `priority-1`、`priority-2`、`priority-3`、`priority-4`、`priority-5`、`priority-6`、`priority-7` | +| `task` | 7 | `task-start`、`task-oct`、`task-3oct`、`task-half`、`task-5oct`、`task-7oct`、`task-done` | + +注意:`emoji/face-plam` 是 V1 保留的历史兼容枚举值,拼写虽然异常但属于协议值; +传入 `emoji/face-palm` 会被拒绝。 diff --git a/skills/multi/dingtalk-misc/references/whiteboard.md b/skills/multi/dingtalk-misc/references/whiteboard.md index cc054591..b76b6b7e 100644 --- a/skills/multi/dingtalk-misc/references/whiteboard.md +++ b/skills/multi/dingtalk-misc/references/whiteboard.md @@ -3,6 +3,10 @@ `dws whiteboard` 只操作已经存在于钉钉在线文档中的单页内嵌白板。创建白板卡片使用 `dws doc whiteboard insert`;普通文档块仍使用 `dws doc block`。 +OpenNodes V1 的完整字段、节点类型、目录枚举和错误语义按需读取 +[协议索引](./whiteboard/open-nodes-v1.md);不要根据本页概要猜测节点字段或 +`geometry`、`catalogId` 等枚举值。 + ## 定位白板 每次操作都需要真实的文档 `nodeId` 和白板 `partId`。缺少 `partId` 时先读取文档 diff --git a/skills/multi/dingtalk-misc/references/whiteboard/open-nodes-v1.md b/skills/multi/dingtalk-misc/references/whiteboard/open-nodes-v1.md new file mode 100644 index 00000000..322822d2 --- /dev/null +++ b/skills/multi/dingtalk-misc/references/whiteboard/open-nodes-v1.md @@ -0,0 +1,45 @@ +# OpenNodes V1(DWS 白板协议索引) + +本目录承载 `dws whiteboard query/update` 使用的 OpenNodes V1 协议。协议按 +调用阶段拆分,Agent 只加载当前任务所需章节,避免一次性读取全部内容。 + +## DWS 使用规则 + +- 只通过 `dws whiteboard query/update` 读写白板。 +- 每次调用必须提供承载白板的文档 `--node` 和目标白板 `--part-id`。 +- DWS 当前只支持单页白板:命令没有 `--page-id`,update 文件禁止包含 + `pageId`。 +- `query` 不接收请求体;CLI 会把返回的 `resultJson` 解析成对象。 +- 白板命令不支持使用全局 `--jq` 或 `--fields` 过滤输出;传入任一参数都会报错, + Agent 直接读取 CLI 返回的结构化 JSON。 +- `update --source` 使用 `overwrite + source` 信封;append 和 overwrite 都是 + 远端写入,获得用户确认后必须通过 `--yes` 显式确认。 +- CLI 只预检 JSON、信封、版本和 `nodes` 数组等外层结构;节点字段、枚举、层级、 + 引用和业务约束由白板服务完整校验。任一层失败都不会保留部分更新。 +- DWS 返回以 `success`、`nodeId`、`partId`、`resultJson` 和可选的 + `resultSummary` 为准。 + +## 按任务读取 + +| 当前任务 | 必读章节 | +|---|---| +| 理解版本、兼容和命令语义 | [01-overview](open-nodes-v1/01-overview.md) | +| 读取或解释 query 结果 | [02-query](open-nodes-v1/02-query.md) | +| 构造 append、overwrite 或清空请求 | [03-update](open-nodes-v1/03-update.md) | +| 写富文本、列表、链接、主题色、渐变或阴影 | [04-text-style](open-nodes-v1/04-text-style.md) | +| 写 shape、便签、frame、group 或 connector | [05-shape-frame-group-connector](open-nodes-v1/05-shape-frame-group-connector.md) | +| 写已上传 Vector、内置 Icon 或自由 Path | [06-vector-icon-path](open-nodes-v1/06-vector-icon-path.md) | +| 处理错误、query 转写或判断 writeSupport | [07-examples-errors-write-support](open-nodes-v1/07-examples-errors-write-support.md) | +| 选择合法 geometry 或 icon catalogId | [08-catalogs](open-nodes-v1/08-catalogs.md) | + +## 强制读取规则 + +- 使用 `shape.geometry` 前必须读取 [08-catalogs](open-nodes-v1/08-catalogs.md), + 不得猜测 geometry。 +- 使用 `icon.catalogId` 前必须读取 [06-vector-icon-path](open-nodes-v1/06-vector-icon-path.md) + 和 [08-catalogs](open-nodes-v1/08-catalogs.md)。 +- 使用 `path` 前必须读取 [06-vector-icon-path](open-nodes-v1/06-vector-icon-path.md), + 不得把它当作通用 SVG Path。 +- Query 结果不能直接作为 update source;转换前必须读取 + [03-update](open-nodes-v1/03-update.md) 和 + [07-examples-errors-write-support](open-nodes-v1/07-examples-errors-write-support.md)。 diff --git a/skills/multi/dingtalk-misc/references/whiteboard/open-nodes-v1/01-overview.md b/skills/multi/dingtalk-misc/references/whiteboard/open-nodes-v1/01-overview.md new file mode 100644 index 00000000..1b2b497e --- /dev/null +++ b/skills/multi/dingtalk-misc/references/whiteboard/open-nodes-v1/01-overview.md @@ -0,0 +1,57 @@ +# DWS OpenNodes V1 协议说明 + +> 本文件是 DWS OpenNodes V1 协议的拆分章节。按需读取入口见 +> [协议索引](../open-nodes-v1.md)。 + +> 协议版本:`schemaVersion = "1.0"`,`catalogVersion = "dml-v1"`。 + +## 1. 协议用途 + +OpenNodes 是 DWS 白板命令使用的语义节点协议,提供两类能力: + +- `dws whiteboard query`:返回稳定、可理解的页面和节点数据。 +- `dws whiteboard update`:接收受约束的节点描述,以 `append` 或 + `overwrite` 模式修改白板。 + +调用方只应依赖本文声明的语义字段和行为: + +- `query` 不修改白板。 +- `update` 全部成功或全部回滚,不返回中间状态。 +- 未声明的存储字段、类型名称和处理过程不属于协议承诺。 + +OpenNodes V1 支持的节点类型、字段和读写范围见第 7 节。 + +DWS 负责身份认证和权限校验。Vector 资源准备使用 `dws doc media upload`, +具体流程见白板命令参考。 + +## 2. 版本与兼容原则 + +| 字段 | 当前值 | 作用 | +| --- | --- | --- | +| `schemaVersion` | `1.0` | 控制文档结构、节点字段和字段语义。 | +| `catalogVersion` | `dml-v1` | 控制允许写入的 DML 几何、连接线标记和内置 icon 目录。 | + +V1 采用严格校验: + +- 必填字段缺失会失败。 +- 未声明字段会失败,不会被静默忽略。 +- query-only 字段出现在 update 中会以 `readOnlyField` 失败。 +- 不支持的节点类型、目录值或引用范围会失败。 +- `null` 不代表“使用默认值”;除非字段类型明确允许,否则会失败。 + +本文列出的请求枚举值都是协议字面量,调用方必须按文档中的大小写和拼写原样传入, +不能自行转换或猜测。响应中未来可能增加可选字段,调用方应忽略不认识的响应字段。 + +调用方必须原样携带当前版本值。新增不兼容结构时应升级 +`schemaVersion`;修改 DML、marker 或 icon 目录时应评估并升级 +`catalogVersion`。 + +## 3. DWS 命令一览 + +| 命令 | 所需权限 | 效果 | +| --- | --- | --- | --- | +| `dws whiteboard query --node ... --part-id ...` | 可查看白板 | 读取单页白板,不修改内容。 | +| `dws whiteboard update --node ... --part-id ... --source ... --yes` | 可编辑白板 | 追加节点或整页重建;所有更新都需先取得用户确认。 | + +DWS 当前只支持文字文档中已有的单页内嵌白板。命令不接收 `pageId`,也不提供 +创建页面、切换页面或按既有节点 ID 局部修改的能力。 diff --git a/skills/multi/dingtalk-misc/references/whiteboard/open-nodes-v1/02-query.md b/skills/multi/dingtalk-misc/references/whiteboard/open-nodes-v1/02-query.md new file mode 100644 index 00000000..8754fa7a --- /dev/null +++ b/skills/multi/dingtalk-misc/references/whiteboard/open-nodes-v1/02-query.md @@ -0,0 +1,288 @@ +# OpenNodes V1 — Query 请求、返回结构和节点公共字段 + +> 本文件是 DWS OpenNodes V1 协议的拆分章节。按需读取入口见 +> [协议索引](../open-nodes-v1.md)。 + +## 4. Query 协议 + +### 4.1 请求 + +```bash +dws whiteboard query \ + --node \ + --part-id \ + --format json +``` + +`query` 不接收请求体或 `pageId`。`--node` 和 `--part-id` 的发现规则见 +[白板命令参考](../../whiteboard.md)。CLI 会把服务返回的 `resultJson` JSON +字符串解析成对象。 + +### 4.2 返回结构 + +```ts +interface OpenNodesDocument { + schemaVersion: "1.0"; + catalogVersion: "dml-v1"; + pages: OpenPage[]; +} + +interface OpenPage { + id: string; + nodes: OpenNode[]; +} +``` + +DWS 当前只支持单页白板,因此 `pages` 固定包含一个页面。调用方仍应从返回值读取 +页面 `id`,但不能把它作为 `pageId` 传给 DWS 命令。 + +母版节点不会作为独立页面返回,而会合并到引用它的页面 `nodes` 中,并带有: + +```json +{ + "source": "master", + "writeSupport": "readOnly", + "unsupportedFeatures": ["node.source.master"] +} +``` + +### 4.3 节点公共字段 + +`type` 的完整公开枚举如下。前一组可由 update 创建,后一组仅供 query 返回: + +```ts +type WritableOpenNodeType = + | "shape" + | "text" + | "connector" + | "stickyNote" + | "frame" + | "group" + | "vector" + | "icon" + | "path"; + +type ReadOnlyOpenNodeType = + | "image" + | "pdf" + | "media" + | "webLink" + | "table" + | "chart" + | "uml" + | "swimlane" + | "mind" + | "timer" + | "placeholder" + | "unknown"; + +type OpenNodeType = WritableOpenNodeType | ReadOnlyOpenNodeType; +``` + +每个 query 节点都包含以下公共字段: + +| 字段 | 类型 | 说明 | +| --- | --- | --- | +| `id` | `string` | 白板中真实、稳定的节点 ID。 | +| `type` | `OpenNodeType` | OpenNodes 公开语义类型,取值见上方枚举。 | +| `parentId` | `string?` | group/frame 父节点 ID。无父节点时省略。 | +| `children` | `string[]?` | group/frame 的直接子节点 ID,由服务端推导。 | +| `x`、`y` | `number` | 有父节点时相对父节点;否则相对页面。单位为 px。 | +| `width`、`height` | `number` | 节点包围盒尺寸,单位为 px。连接线允许其中一个为 `0`。 | +| `angle` | `number` | 归一化到 `[0, 360)` 的角度。 | +| `absoluteBounds` | `OpenBounds` | 页面坐标系中的绝对包围盒。 | +| `layer` | `background \| normal \| foreground` | 节点所在层。 | +| `zIndex` | `number` | 同一父节点、同一 layer 内的非负顺序,值越小越靠后。 | +| `hidden` | `boolean` | 节点是否隐藏。 | +| `locked` | `boolean` | 节点是否锁定。 | +| `source` | `page \| master` | 节点来自当前页面还是母版。 | +| `writeSupport` | `readWrite \| readOnly` | 当前节点能否由 V1 update 表达。 | +| `unsupportedFeatures` | `string[]?` | 只读原因;`readWrite` 节点省略。 | + +公共结构和各节点分支定义如下;分支中的字段含义与写入限制见第 7 节: + +```ts +interface OpenPoint { + x: number; + y: number; +} + +interface OpenBounds { + x: number; + y: number; + width: number; + height: number; + angle: number; +} + +interface OpenNodeBase { + id: string; + type: OpenNodeType; + parentId?: string; + children?: string[]; + x: number; + y: number; + width: number; + height: number; + angle: number; + absoluteBounds: OpenBounds; + layer: "background" | "normal" | "foreground"; + zIndex: number; + hidden: boolean; + locked: boolean; + source: "page" | "master"; + writeSupport: "readWrite" | "readOnly"; + unsupportedFeatures?: string[]; +} + +interface OpenShapeNode extends OpenNodeBase { + type: "shape"; + geometry: `dml:${string}`; + adjustments?: Record; + text?: OpenText; + style?: OpenNodeStyle; +} + +interface OpenTextNode extends OpenNodeBase { + type: "text"; + text: OpenText; + style?: OpenNodeStyle; +} + +interface OpenConnectorNode extends OpenNodeBase { + type: "connector"; + start: OpenConnectorEndpoint; + end: OpenConnectorEndpoint; + routing: OpenConnectorRouting; + waypoints?: OpenPoint[]; + style?: OpenNodeStyle; + resolvedPath: OpenResolvedConnectorPath; +} + +interface OpenStickyNoteNode extends OpenNodeBase { + type: "stickyNote"; + text?: OpenText; + style?: OpenNodeStyle; + creator?: { + displayName?: string; + hasAvatar?: boolean; + }; + tags?: Array<{ + id: string; + text: string; + background: OpenPaint; + }>; +} + +interface OpenFrameNode extends OpenNodeBase { + type: "frame"; + title?: { + text: OpenText; + box: { width: number; height: number }; + }; + style?: OpenNodeStyle; + presentationOrder?: number; + resizeMode: "free" | "fixedAspectRatio"; +} + +interface OpenGroupNode extends OpenNodeBase { + type: "group"; + children: string[]; +} + +interface OpenVectorNode extends OpenNodeBase { + type: "vector"; + resource: OpenVectorResource; +} + +interface OpenIconNode extends OpenNodeBase { + type: "icon"; + catalogId: string; +} + +interface OpenPathNode extends OpenNodeBase { + type: "path"; + path: OpenPathData; + style?: OpenNodeStyle; +} + +interface OpenReadOnlyNode extends OpenNodeBase { + type: ReadOnlyOpenNodeType; + writeSupport: "readOnly"; + unsupportedFeatures: string[]; +} + +type OpenNode = + | OpenShapeNode + | OpenTextNode + | OpenConnectorNode + | OpenStickyNoteNode + | OpenFrameNode + | OpenGroupNode + | OpenVectorNode + | OpenIconNode + | OpenPathNode + | OpenReadOnlyNode; +``` + +query 中 `icon.catalogId` 使用 `string`,是为了让无法映射到当前目录的既有图标仍可 +被诊断;update 只能传入 7.10 节 `OpenIconCatalogId` 中列出的值。 + +### 4.4 Query 示例 + +```json +{ + "schemaVersion": "1.0", + "catalogVersion": "dml-v1", + "pages": [ + { + "id": "page", + "nodes": [ + { + "id": "real-node-id", + "type": "text", + "x": 120, + "y": 80, + "width": 240, + "height": 48, + "angle": 0, + "absoluteBounds": { + "x": 120, + "y": 80, + "width": 240, + "height": 48, + "angle": 0 + }, + "layer": "normal", + "zIndex": 0, + "hidden": false, + "locked": false, + "source": "page", + "writeSupport": "readWrite", + "text": { + "blocks": [ + { + "type": "paragraph", + "horizontalAlign": "left", + "runs": [ + { + "text": "Hello OpenNodes", + "marks": { + "fontSize": 16, + "color": "#223344" + } + } + ] + } + ], + "verticalAlign": "center", + "padding": [2, 4], + "plainText": "Hello OpenNodes", + "writeSupport": "readWrite" + } + } + ] + } + ] +} +``` diff --git a/skills/multi/dingtalk-misc/references/whiteboard/open-nodes-v1/03-update.md b/skills/multi/dingtalk-misc/references/whiteboard/open-nodes-v1/03-update.md new file mode 100644 index 00000000..3f028cc2 --- /dev/null +++ b/skills/multi/dingtalk-misc/references/whiteboard/open-nodes-v1/03-update.md @@ -0,0 +1,249 @@ +# OpenNodes V1 — Update 信封、Append/Overwrite 和公共写入字段 + +> 本文件是 DWS OpenNodes V1 协议的拆分章节。按需读取入口见 +> [协议索引](../open-nodes-v1.md)。 + +## 5. Update 协议 + +### 5.1 请求信封 + +```ts +interface OpenNodesUpdateRequest { + overwrite?: boolean; + source: { + schemaVersion: "1.0"; + catalogVersion: "dml-v1"; + nodes: OpenNodeWrite[]; + }; +} +``` + +字段含义: + +| 字段 | 必填 | 说明 | +| --- | --- | --- | +| `overwrite` | 否 | `false` 或省略为 append;`true` 为 overwrite。 | +| `source.schemaVersion` | 是 | 必须为 `1.0`。 | +| `source.catalogVersion` | 是 | 必须为 `dml-v1`。 | +| `source.nodes` | 是 | 本次创建的节点数组。append 至少一个;overwrite 允许空数组。 | + +`source` 不能直接使用 query 返回的 `OpenNodesDocument`,也不接受 `pages`。 +DWS 不接受 `pageId`;传入会在 CLI 本地校验阶段失败。 + +V1 没有“按真实节点 ID patch 既有节点”的语义。`source.nodes` 中的每一项都会 +创建一个新节点,`id` 仅是本次请求内建立父子关系和连接线引用的临时 ID: +append 是新增节点,overwrite 是整页删除后重新创建。 + +### 5.2 Append 与 Overwrite + +> **注意:** `overwrite: true` 是破坏性操作,会删除当前白板页面的全部自有 +> 节点。调用前应先 query 并确认影响范围;`nodes: []` 会清空页面。不希望删除 +> 既有内容时,应使用 append。 + +| 行为 | append | overwrite | +| --- | --- | --- | +| `overwrite` | `false` 或省略 | `true` | +| 空 `nodes` | 禁止 | 允许,用于清空当前页面 | +| 当前页面旧节点 | 全部保留 | 删除页面自有节点后创建新节点 | +| 母版节点 | 保留 | 保留 | +| 页面级设置 | 保留 | 保留 | +| 原子性 | 全部成功或全部回滚 | 全部成功或全部回滚 | + +overwrite 会替换当前页面的全部自有节点,不要求旧节点本身可由 OpenNodes V1 +写入。因此页面中存在 image、PDF、复杂文本等只读节点,不会单独阻止清空页面。 + +overwrite 会在删除前执行安全预检;以下情况会拒绝执行: + +- 节点被锁定:`lockedNode`。 +- 节点不允许被删除:`deleteForbidden`。 +- 页面包含无法安全保留或清理的关联数据:`unknownMetadataReference`。 +- 目标节点未能完整删除:`deleteFailed`。 + +与被删除节点绑定且有明确清理规则的关联数据会随节点清理;无法安全保留或清理的 +关联数据会使 overwrite 失败。 + +overwrite 只替换当前页面节点,不会重建页面,也不会修改主题等页面级设置。 +白板已有的主题会被保留;白板没有有效主题时也不会自动添加。调用方应使用 +已配置有效主题的白板,或者显式提供不依赖主题的颜色。 + +### 5.3 成功结果 + +```ts +interface DWSWhiteboardUpdateResponse { + success: true; + nodeId: string; + partId: string; + resultJson: { + mode: "append" | "overwrite"; + createdNodeIds: string[]; + idMap: Record; + deletedNodeCount: number; + message: string; + }; +} +``` + +| 字段 | 说明 | +| --- | --- | +| `success` | `true` 表示本次 DWS 调用成功。 | +| `nodeId` | 输入的文档节点 ID。 | +| `partId` | 输入的白板标识。 | +| `resultJson.mode` | 实际执行的模式。 | +| `resultJson.createdNodeIds` | 按请求节点顺序返回真实节点 ID。 | +| `resultJson.idMap` | 请求中显式临时 ID 到真实节点 ID 的映射。 | +| `resultJson.deletedNodeCount` | append 恒为 `0`;overwrite 为删除的页面自有节点数。 | +| `resultJson.message` | 供人阅读的结果摘要,不应作为机器判断依据。 | + +响应可能增加其他可选字段;Agent 不应依赖本节未声明的字段。 + +示例: + +```json +{ + "success": true, + "nodeId": "DOC_NODE_ID", + "partId": "WHITEBOARD_PART_ID", + "resultJson": { + "mode": "append", + "createdNodeIds": ["generated-title-id", "generated-body-id"], + "idMap": { + "title": "generated-title-id", + "body": "generated-body-id" + }, + "deletedNodeCount": 0, + "message": "Created 2 Whiteboard nodes" + } +} +``` + +## 6. Update 公共节点字段 + +V1 可写节点公共字段如下: + +```ts +interface OpenNodeWriteBase { + id?: string; + layer?: "background" | "normal" | "foreground"; + zIndex?: number; + hidden?: boolean; +} + +interface OpenChildNodeWriteBase extends OpenNodeWriteBase { + parentId?: string; +} + +interface OpenSizedNodeWriteBase extends OpenChildNodeWriteBase { + x: number; + y: number; + width: number; + height: number; + angle?: number; +} + +interface OpenShapeNodeWrite extends OpenSizedNodeWriteBase { + type: "shape"; + geometry: `dml:${string}`; + text?: OpenTextWrite; + style?: OpenNodeStyleWrite; +} + +interface OpenTextNodeWrite extends OpenSizedNodeWriteBase { + type: "text"; + text: OpenTextWrite; + style?: OpenNodeStyleWrite; +} + +interface OpenConnectorNodeWrite extends OpenNodeWriteBase { + type: "connector"; + start: OpenConnectorEndpointWrite; + end: OpenConnectorEndpointWrite; + routing: OpenConnectorRouting; + waypoints?: OpenPoint[]; + style?: OpenNodeStyleWrite; +} + +interface OpenStickyNoteNodeWrite extends OpenSizedNodeWriteBase { + type: "stickyNote"; + text?: OpenTextWrite; + style?: OpenNodeStyleWrite; +} + +interface OpenFrameNodeWrite extends OpenNodeWriteBase { + type: "frame"; + x: number; + y: number; + width: number; + height: number; + angle?: 0; + title?: { + text: OpenTextWrite; + box?: { width: number; height: number }; + }; + style?: OpenNodeStyleWrite; + presentationOrder?: number; + resizeMode?: "free" | "fixedAspectRatio"; +} + +interface OpenGroupNodeWrite extends OpenChildNodeWriteBase { + id: string; + type: "group"; + x: number; + y: number; +} + +interface OpenVectorNodeWrite extends OpenSizedNodeWriteBase { + type: "vector"; + resource: OpenManagedVectorResourceWrite; +} + +interface OpenIconNodeWrite extends OpenSizedNodeWriteBase { + type: "icon"; + catalogId: OpenIconCatalogId; +} + +interface OpenPathNodeWrite extends OpenSizedNodeWriteBase { + type: "path"; + path: OpenPathDataWrite; + style?: OpenNodeStyleWrite; +} + +type OpenNodeWrite = + | OpenShapeNodeWrite + | OpenTextNodeWrite + | OpenConnectorNodeWrite + | OpenStickyNoteNodeWrite + | OpenFrameNodeWrite + | OpenGroupNodeWrite + | OpenVectorNodeWrite + | OpenIconNodeWrite + | OpenPathNodeWrite; +``` + +上面的联合类型是 update 的字段白名单。各辅助结构和完整枚举值在第 7 节定义; +未出现在对应分支中的字段不能发送。 + +| 字段 | 规则 | +| --- | --- | +| `id` | 可选的请求级临时 ID;非空、区分大小写、在请求内唯一。被引用时必须提供。 | +| `type` | 必填,必须是 V1 可写类型。 | +| `parentId` | 可选,引用同一请求中 group/frame 的临时 ID。 | +| `x`、`y` | 除 connector 外必填;有父节点时为父节点相对坐标。 | +| `width`、`height` | shape/text/stickyNote/frame/vector/icon/path 必填且大于 `0`;group/connector 只读。 | +| `angle` | 可选,默认 `0`;frame 只允许 `0`;group/connector 只读。 | +| `layer` | 可选;frame 默认 `background`,其他节点默认 `normal`。 | +| `zIndex` | 可选的非负整数;相同值时按请求顺序稳定排序。 | +| `hidden` | 可选布尔值,默认 `false`。 | + +以下 query 字段禁止写回: + +`children`、`absoluteBounds`、`locked`、`source`、`writeSupport`、 +`unsupportedFeatures`。 + +关系规则: + +- `parentId` 只能引用同一请求中的 group 或 frame。 +- frame 和 connector 必须是页面直属节点,不能带 `parentId`。 +- group 可以嵌套,也可以放在 frame 中。 +- `children` 始终由各子节点的 `parentId` 推导。 +- 父子关系不能成环。 +- group 必须有临时 `id`、至少两个直接子节点,且不能全部隐藏。 diff --git a/skills/multi/dingtalk-misc/references/whiteboard/open-nodes-v1/04-text-style.md b/skills/multi/dingtalk-misc/references/whiteboard/open-nodes-v1/04-text-style.md new file mode 100644 index 00000000..37eb5cf4 --- /dev/null +++ b/skills/multi/dingtalk-misc/references/whiteboard/open-nodes-v1/04-text-style.md @@ -0,0 +1,395 @@ +# OpenNodes V1 — 支持矩阵、富文本和样式 + +> 本文件是 DWS OpenNodes V1 协议的拆分章节。按需读取入口见 +> [协议索引](../open-nodes-v1.md)。 + +## 7. 节点类型 + +### 7.1 支持矩阵 + +| `type` | query | update | 主要字段 | +| --- | --- | --- | --- | +| `shape` | 支持 | 支持 | `geometry`、`text?`、`style?` | +| `text` | 支持 | 支持 | `text`、`style?` | +| `connector` | 支持 | 支持 | `start`、`end`、`routing`、`waypoints?`、`style?` | +| `stickyNote` | 支持 | 支持 | `text?`、`style?` | +| `frame` | 支持 | 支持 | `title?`、`style?`、`presentationOrder?`、`resizeMode?` | +| `group` | 支持 | 支持 | 子关系通过 `parentId` 表达 | +| `image` | 支持 | 只读 | 仅公共字段 | +| `vector` | 支持 | 支持 | `resource` | +| `icon` | 支持 | 支持 | `catalogId` | +| `path` | 支持 | 支持 | `path`、`style?` | +| `pdf` | 支持 | 只读 | 仅公共字段 | +| `media` | 支持 | 只读 | 仅公共字段 | +| `webLink` | 支持 | 只读 | 仅公共字段 | +| `table` | 支持 | 只读 | 仅公共字段 | +| `chart` | 支持 | 只读 | 仅公共字段 | +| `uml` | 支持 | 只读 | 仅公共字段 | +| `swimlane` | 支持 | 只读 | 仅公共字段 | +| `mind` | 支持 | 只读 | 仅公共字段 | +| `timer` | 支持 | 只读 | 仅公共字段 | +| `placeholder` | 支持 | 只读 | 仅公共字段 | +| `unknown` | 支持 | 只读 | 未识别或尚未定义独立语义的节点统一映射到此类型 | + +表中标记为只读的类型仍会完整返回公共几何、层级和顺序信息,但 update 提交会以 +`nodeTypeUnsupported` 失败。 + +`timer`、`table`、`webLink` 不支持 V1 update。query 仅返回这些节点的公共几何、 +层级、顺序和诊断字段,不承诺完整业务字段。文字 run 中的 `link` 是富文本能力, +不属于 `webLink` 节点,V1 支持读写。 + +`webLink` 只保留只读查询;OpenNodes V1 不支持创建或重建该节点。 + +### 7.2 Text + +query 和 update 均支持普通段落、无序列表、有序列表、多 block、多 run 和文字 +链接: + +```ts +interface OpenTextRun { + text: string; + marks?: { + fontFamily?: string; + fontSize?: number; + bold?: boolean; + italic?: boolean; + underline?: boolean; + strike?: boolean; + color?: string; + highlight?: string; + }; + link?: { url: string }; +} + +interface OpenTextBlock { + type: "paragraph" | "bulletList" | "orderedList"; + horizontalAlign?: "left" | "center" | "right"; + runs: OpenTextRun[]; +} + +interface OpenTextWrite { + blocks: OpenTextBlock[]; + verticalAlign?: "top" | "center" | "bottom"; + padding?: number | [number, number]; +} + +interface OpenText extends OpenTextWrite { + plainText: string; + writeSupport: "readWrite" | "readOnly"; + unsupportedFeatures?: string[]; +} +``` + +约束: + +- `blocks.length >= 1`,每个 block 的 `type` 必须是 `paragraph`、 + `bulletList` 或 `orderedList`。 +- 每个 block 都必须满足 `runs.length >= 1`。 +- run 的 `text` 不能包含 `\r`、`\n`、U+2028 或 U+2029。换行和列表项使用 + 独立 block 表达;每一段或每个列表项应写成一个 block,而不是把原始换行符 + 放进单个 run。 +- 每个 `bulletList` / `orderedList` block 表示一个列表项;服务端会把连续且同类的 + block 解释为同一个列表中的多个列表项。 +- `link.url` 长度必须为 `1..2048`,不能包含控制字符、`<`、`>`。支持无 scheme + 的相对/裸链接,以及 `http`、`https`、`mailto`、`tel`、`dingtalk` scheme; + `javascript:`、`data:` 等可执行或未知 scheme 会被拒绝。 +- 相邻且 URL 相同的 linked run 会呈现为同一个链接,同时保留各 run 自己的 marks。 +- `fontSize > 0`,padding 各项必须大于等于 `0`。 +- `plainText`、文本级 `writeSupport` 和 `unsupportedFeatures` 是 query-only。 +- text 节点必须提供 `text`;shape 和 stickyNote 的 `text` 可省略。 +- 受支持的 paragraph、列表、链接、多 run 都保持 + `writeSupport = "readWrite"`;未知 list style、非法链接、未支持的 block 或 + run marks 会令文本和所属节点变为 `readOnly`。 + +下面的文本会显示为两个段落,第一段由两个不同样式的 run 组成: + +```json +{ + "blocks": [ + { + "type": "paragraph", + "horizontalAlign": "left", + "runs": [ + { + "text": "OpenNodes ", + "marks": { "fontSize": 18, "bold": true, "color": "#2563EB" } + }, + { + "text": "rich text", + "marks": { "fontSize": 18, "italic": true, "color": "#0F172A" } + } + ] + }, + { + "type": "paragraph", + "horizontalAlign": "right", + "runs": [ + { + "text": "第二段", + "marks": { "fontSize": 16, "underline": true, "color": "#047857" } + } + ] + } + ], + "verticalAlign": "center", + "padding": [4, 8] +} +``` + +同一 `OpenTextWrite` 结构适用于独立 text 节点、shape 文本、stickyNote 文本和 +frame title。 + +下面三个 block 会生成两个无序列表项,其中第二项包含文字链接: + +```json +{ + "blocks": [ + { + "type": "bulletList", + "runs": [{ "text": "准备输入数据" }] + }, + { + "type": "bulletList", + "runs": [ + { + "text": "查看钉钉文档", + "marks": { "underline": true }, + "link": { "url": "https://alidocs.dingtalk.com" } + } + ] + }, + { + "type": "orderedList", + "runs": [{ "text": "执行生成" }] + } + ] +} +``` + +颜色接受以下 CSS 形式:3/4/6/8 位十六进制、颜色名,以及 +`rgb()`、`rgba()`、`hsl()`、`hsla()`、`oklch()`、`lab()`、`lch()`、 +`color()`。字符串最长 128 字符,不能包含控制字符、`<`、`>` 或 `;`。 +这里只接受可独立解析的字面量;依赖外部样式上下文的 `var()`、`calc()`、 +`color-mix()` 等动态表达式不属于 V1。颜色名必须是标准 CSS named color, +任意字母串不会被当成颜色。 + +### 7.3 Style + +query 可表达: + +- `none`、`solid`、`theme`、线性渐变、径向渐变和图片 paint。 +- shadow、blur 和 unknown effect。 + +V1 update 支持 `none`、`solid`、`theme`、线性渐变、九方向径向渐变,以及 +单个自定义 shadow。主题明暗参数和渐变色标位置使用 `0~100` 的百分比, +不会归一化成 `0~1`: + +```ts +type OpenRadialGradientPosition = + | "topLeft" + | "topCenter" + | "topRight" + | "centerLeft" + | "center" + | "centerRight" + | "bottomLeft" + | "bottomCenter" + | "bottomRight"; + +interface OpenColorStop { + offset: number; + color: string; + opacity?: number; +} + +interface OpenImagePaint { + type: "image"; + resource: { + kind: "managed" | "external" | "embedded" | "unresolved"; + resourceId?: string; + }; + intrinsicWidth: number; + intrinsicHeight: number; +} + +type OpenPaint = + | { type: "none" } + | { type: "solid"; color: string; opacity?: number } + | { + type: "theme"; + token: string; + lumMod?: number; + lumOff?: number; + resolvedColor?: string; + } + | { + type: "linearGradient"; + angle: number; + stops: OpenColorStop[]; + } + | { + type: "radialGradient"; + position: OpenRadialGradientPosition | "custom"; + stops: OpenColorStop[]; + } + | OpenImagePaint; + +type OpenEffect = + | { + type: "shadow"; + offsetX: number; + offsetY: number; + blur: number; + color: string; + opacity: number; + } + | { type: "blur"; blur: number } + | { type: "unknown" }; + +interface OpenNodeStyle { + opacity?: number; + fill?: OpenPaint; + stroke?: { + paint: OpenPaint; + width?: number; + dash?: number[]; + lineCap?: "butt" | "round" | "square"; + lineJoin?: "miter" | "round" | "bevel"; + }; + effects?: OpenEffect[]; + writeSupport: "readWrite" | "readOnly"; + unsupportedFeatures?: string[]; +} + +interface OpenColorStopWrite { + offset: number; // [0, 100],百分比 + color: string; + opacity?: number; // [0, 1] +} + +type OpenPaintWrite = + | { type: "none" } + | { type: "solid"; color: string; opacity?: number } + | { + type: "theme"; + token: string; + lumMod?: number; // [0, 100],默认 100 + lumOff?: number; // [0, 100],默认 0 + } + | { + type: "linearGradient"; + angle: number; + stops: OpenColorStopWrite[]; + } + | { + type: "radialGradient"; + position: OpenRadialGradientPosition; + stops: OpenColorStopWrite[]; + }; + +interface OpenShadowEffectWrite { + type: "shadow"; + offsetX: number; + offsetY: number; + blur: number; + color: string; + opacity: number; +} + +interface OpenNodeStyleWrite { + opacity?: number; + fill?: OpenPaintWrite; + stroke?: { + paint: OpenPaintWrite; + width?: number; + dash?: number[]; + lineCap?: "butt" | "round" | "square"; + lineJoin?: "miter" | "round" | "bevel"; + }; + effects?: OpenShadowEffectWrite[]; +} +``` + +约束: + +- opacity 范围为 `[0, 1]`。 +- solid paint 的 `color` 与 `opacity` 在 query 后仍保持独立字段,不会合并为 + 动态 CSS 表达式。 +- theme 的 `token` 必须能在当前白板主题中解析; + `lumMod`、`lumOff` 范围均为 `[0, 100]`。token 不存在时在 + Request graph 阶段返回 `themeTokenNotFound`,不会写出部分节点。 + token 长度为 `1~64`,首尾不能有空白,也不能包含空白、控制字符、 + `<`、`>` 或 `;`。 +- query 的 theme paint 还会返回按当前白板主题计算出的 query-only + `resolvedColor`。调用方写入时只传 `token/lumMod/lumOff`,不传 + `resolvedColor`。 +- 渐变必须至少有一个 stop;`offset` 范围为 `[0, 100]`,单位是百分比。 +- 线性渐变 `angle` 范围为 `[0, 360]`。 +- 径向渐变只接受上述九宫格位置。query 遇到九宫格以外的位置时返回 + `position: "custom"` 并将该节点标为只读。 +- `effects` 最多包含一个 `shadow`;`offsetX/offsetY` 必须是有限数, + `blur >= 0`,shadow opacity 范围为 `[0, 1]`。 +- stroke width 和 dash 各项必须大于等于 `0`。 +- 一旦提供 stroke,`stroke.paint` 必填。 +- 样式级 `writeSupport` 和 `unsupportedFeatures` 是 query-only。 +- 能在当前白板主题中解析且明暗参数合法的 theme paint 可写。无法解析的主题 + token、越界的主题明暗参数、image paint、blur、unknown effect、叠加 effect + 或自定义径向位置会令节点只读;受支持的 theme、渐变和单个 shadow 本身不会 + 令节点只读。 + +主题色示例: + +```json +{ + "fill": { + "type": "theme", + "token": "ac3", + "lumMod": 20, + "lumOff": 80 + }, + "stroke": { + "paint": { + "type": "theme", + "token": "sk1", + "lumMod": 80, + "lumOff": 20 + }, + "width": 2 + } +} +``` + +示例中的 `ac3`、`sk1` 只是主题 token 示例,不是所有白板都可用的全局枚举。 +调用方只能使用已确认可由当前白板主题解析的 token;无法确认时应改用 +`solid` 颜色。 + +DWS 不提供新增、修改或切换主题的命令。当前白板没有有效主题时,应改用 +`solid`。 + +示例: + +```json +{ + "fill": { + "type": "linearGradient", + "angle": 35, + "stops": [ + { "offset": 0, "color": "#1677ff", "opacity": 0.4 }, + { "offset": 100, "color": "#69b1ff" } + ] + }, + "effects": [ + { + "type": "shadow", + "offsetX": 8, + "offsetY": 8, + "blur": 19, + "color": "rgba(93,190,172,1)", + "opacity": 0.5 + } + ] +} +``` + +未传 `style` 时使用节点类型的默认样式。调用方如需稳定的视觉结果,应显式传入 +`fill` 和 `stroke`。 diff --git a/skills/multi/dingtalk-misc/references/whiteboard/open-nodes-v1/05-shape-frame-group-connector.md b/skills/multi/dingtalk-misc/references/whiteboard/open-nodes-v1/05-shape-frame-group-connector.md new file mode 100644 index 00000000..f687200b --- /dev/null +++ b/skills/multi/dingtalk-misc/references/whiteboard/open-nodes-v1/05-shape-frame-group-connector.md @@ -0,0 +1,186 @@ +# OpenNodes V1 — Shape、Text、Sticky note、Frame、Group 和 Connector + +> 本文件是 DWS OpenNodes V1 协议的拆分章节。按需读取入口见 +> [协议索引](../open-nodes-v1.md)。 + +### 7.4 Shape + +shape 必须提供 `geometry`,格式为 `dml:`: + +```json +{ + "id": "shape-1", + "type": "shape", + "x": 100, + "y": 80, + "width": 160, + "height": 100, + "geometry": "dml:roundRect", + "style": { + "fill": { + "type": "solid", + "color": "#DCEEFF" + }, + "stroke": { + "paint": { + "type": "solid", + "color": "#225588" + }, + "width": 2 + } + } +} +``` + +query 可能返回 `adjustments`,但 V1 update 不支持写入;带 adjustments 的 +shape 会标为只读。`dml-v1` 的完整 geometry 目录见附录 A。 + +### 7.5 Text node 与 Sticky note + +text node 使用公共几何、必填 `text` 和可选 `style`。 + +stickyNote 使用公共几何、可选 `text` 和可选 `style`。省略 `text` 时创建空 +便签。query 还可能返回: + +- `creator`:创建者展示信息。 +- `tags`:标签 ID、文本和背景 paint。 + +这两个字段是 query-only;存在 creator 或 tags 的 stickyNote 会被标为只读。 + +### 7.6 Frame + +frame 的 update 结构见第 6 节 `OpenFrameNodeWrite`。 + +约束: + +- frame 必须是页面直属节点,不能带 `parentId`。 +- angle 只允许 `0`。 +- `presentationOrder` 是非负整数,并且不能和已有或本次创建的 frame 冲突。 +- frame 的子节点通过子节点 `parentId` 引用 frame 临时 ID。 +- frame 不能包含 frame 或 connector。 +- frame 默认 layer 为 `background`。 + +### 7.7 Group + +group 的写入字段只有公共字段中的 `id`、`parentId?`、`x`、`y`、`layer?`、 +`zIndex?` 和 `hidden?`。其 width、height、angle 和 children 都由服务端根据 +子节点推导。 + +group 必须: + +- 提供临时 `id`。 +- 至少包含两个直接子节点。 +- 至少有一个直接子节点可见。 + +group 的任一子节点为只读时,query 会把 group 一并标为只读。 + +### 7.8 Connector + +connector 的几何由端点和路由推导,因此 update 不能提供 `x`、`y`、 +`width`、`height` 或 `angle`。 + +query 的连接线结构如下: + +```ts +type OpenConnectorRouting = + | "straight" + | "polyline" + | "curve" + | "orthogonal"; + +interface OpenConnectorMarker { + catalogId: string; +} + +type OpenConnectorAnchor = + | { + mode: "fixed"; + side: "top" | "right" | "bottom" | "left"; + position: OpenPoint; + } + | { + mode: "fixed"; + side: "custom"; + position: OpenPoint; + }; + +type OpenConnectorEndpoint = + | { + type: "point"; + point: OpenPoint; + marker: OpenConnectorMarker; + } + | { + type: "node"; + nodeRef: { scope: "document"; id: string }; + anchor: OpenConnectorAnchor; + resolvedPoint: OpenPoint; + marker: OpenConnectorMarker; + }; + +interface OpenBezierSegment { + start: OpenPoint; + control1: OpenPoint; + control2: OpenPoint; + end: OpenPoint; +} + +type OpenResolvedConnectorPath = + | { type: "polyline"; points: OpenPoint[] } + | { type: "bezier"; segments: OpenBezierSegment[] }; +``` + +update 端点有两种形式: + +```ts +type OpenConnectorEndpointWrite = + | { + type: "point"; + point: { x: number; y: number }; + marker?: { catalogId: "none" | "arrow.open" | "arrow.filled" }; + } + | { + type: "node"; + nodeRef: { scope: "request"; id: string }; + anchor?: + | { mode: "auto" } + | { + mode: "fixed"; + side: "top" | "right" | "bottom" | "left"; + }; + marker?: { catalogId: "none" | "arrow.open" | "arrow.filled" }; + }; +``` + +query 的 marker `catalogId` 使用 `string`,以便返回无法映射的既有 marker; +update 只接受上面列出的 `"none"`、`"arrow.open"`、`"arrow.filled"`。 + +路由规则: + +| `routing` | `waypoints` | +| --- | --- | +| `straight` | 禁止提供,包括空数组。 | +| `polyline` | 必须至少提供一个。 | +| `curve` | 可选。 | +| `orthogonal` | 可选;显式点路径的相邻线段必须水平或垂直。 | + +其他约束: + +- 所有 point 和 waypoint 都使用页面绝对坐标。 +- node 端点只能引用同一请求中的 shape、text、stickyNote、frame、group 或 path。 +- node 端点不能引用隐藏节点、connector 或 query 中既有节点。 +- update 引用范围固定为 `scope: "request"`;query 返回的节点引用范围为 + `scope: "document"`,不能直接回写。 +- 同一连接线的两端不能引用同一个节点。 +- 零长度或无效路径会被拒绝。 +- marker 省略时默认为 `none`。 +- anchor 省略时按 `auto` 处理。 + +query 额外返回服务端解析后的: + +- node 端点 `resolvedPoint`。 +- fixed anchor 的归一化 `position`。 +- `resolvedPath`,类型为 polyline points 或 cubic bezier segments。 +- 由真实路径推导的 `absoluteBounds`。 + +这些解析字段都是 query-only。 diff --git a/skills/multi/dingtalk-misc/references/whiteboard/open-nodes-v1/06-vector-icon-path.md b/skills/multi/dingtalk-misc/references/whiteboard/open-nodes-v1/06-vector-icon-path.md new file mode 100644 index 00000000..f48b559f --- /dev/null +++ b/skills/multi/dingtalk-misc/references/whiteboard/open-nodes-v1/06-vector-icon-path.md @@ -0,0 +1,313 @@ +# OpenNodes V1 — Vector、Icon 和 Path + +> 本文件是 DWS OpenNodes V1 协议的拆分章节。按需读取入口见 +> [协议索引](../open-nodes-v1.md)。 + +### 7.9 Vector(已上传 SVG/矢量资源) + +`vector` 表示已经通过 `dws doc media upload` 获得稳定引用的 SVG/矢量图片。 +OpenNodes 只接收上传结果中的资源引用,不接收本地路径或原始 SVG/XML 内容。 + +上传时必须使用与后续白板更新相同的文档 `nodeId`: + +```bash +dws doc media upload \ + --node \ + --file ./icon.svg \ + --mime-type image/svg+xml \ + --format json +``` + +将上传结果的 `resourceId` 和 `resourceUrl` 分别写入 +`resource.resourceId` 和 `resource.url`。 + +Update 必须提供完整的托管资源信息: + +```ts +interface OpenManagedVectorResourceWrite { + kind: "managed"; + resourceId: string; + url: string; +} +``` + +完整节点结构见第 6 节 `OpenVectorNodeWrite`。 + +`resource` 字段规则: + +| 字段 | 必填 | 规则 | +| --- | --- | --- | +| `kind` | 是 | 当前只允许固定值 `managed`,表示资源已经通过 DWS 上传。 | +| `resourceId` | 是 | 资源稳定 ID,长度 1~256,只允许字母、数字、`.`、`_`、`:`、`-`。 | +| `url` | 是 | 已上传资源地址,最长 4096;必须包含且只能包含一个同值的 `resourceId` 查询参数。 | + +`url` 必须直接使用 `dws doc media upload` 返回的 `resourceUrl`,不得自行拼装 +或修改。 + +以下内容会被拒绝: + +- 原始 SVG/XML、`data:`、`blob:`、`http:` URL。 +- `//host/path` 协议相对地址,以及自行构造或修改的其他相对地址。 +- 含空白、控制字符、反斜杠、fragment 或用户凭证的 URL。 +- URL 缺少 `resourceId`、重复出现 `resourceId`,或者 URL 中 ID 与显式 + `resource.resourceId` 不一致。 +- `resource` 缺失、字段不完整、`kind` 不是 `managed`,或包含未知字段。 + +完整 Append 示例: + +```json +{ + "source": { + "schemaVersion": "1.0", + "catalogVersion": "dml-v1", + "nodes": [ + { + "id": "uploaded-svg-1", + "type": "vector", + "x": 100, + "y": 80, + "width": 240, + "height": 180, + "angle": 0, + "resource": { + "kind": "managed", + "resourceId": "0c1c94e1-f9af-4228-b32f-42bbd1555253", + "url": "https://resources.example.com/assets/opaque-path?resourceId=0c1c94e1-f9af-4228-b32f-42bbd1555253" + } + } + ] + }, + "overwrite": false +} +``` + +示例中的 `resources.example.com` 是占位域名,实际调用必须使用 +`dws doc media upload` 返回的 `resourceUrl`。 + +`resourceId` 同时显式出现并包含在 URL 中,用于校验资源身份与地址是否一致。 +两者必须来自同一次 `dws doc media upload` 结果。 + +Query 的 `resource` 可能是: + +```ts +type OpenVectorResource = + | { kind: "managed"; resourceId: string; url: string } + | { kind: "external" | "embedded" | "unresolved" }; +``` + +- 能识别为托管资源的引用返回完整 `managed` 信息。 +- HTTP(S) 外链但不满足托管资源契约时返回 `external`。 +- `data:`/`blob:` 返回 `embedded`。 +- 其他缺失或无法识别的地址返回 `unresolved`。 +- 非 `managed` 资源会令节点只读,并分别产生 + `vector.resource.external`、`vector.resource.embedded` 或 + `vector.resource.unresolved`。 + +V1 vector update 不接受 `style`。既有节点包含 V1 无法表达的 fill、stroke、 +opacity、effect 或 adjustments 时,query 仍可读取资源和几何,但节点会标为只读。 +不影响资源内容的兼容性装饰不会单独令节点变为只读。 + +DWS 会校验资源引用。上传与 `whiteboard update` 必须使用同一个文档 +`nodeId`;不要跨文档复用资源,也不要使用临时 `uploadUrl`。 + +### 7.10 Icon(内置图标) + +`icon` 表示内置图标。OpenNodes 使用版本化的 `catalogId` 作为稳定标识, +允许值见下列类型定义和附录 B。 + +Update 结构: + +```ts +type OpenIconCatalogId = + | `emoji/${ + | "happy" + | "smile" + | "laugh" + | "fighting" + | "like" + | "ok" + | "please" + | "face-plam" + | "tears-of-joy" + | "cry" + | "question" + | "face-with-sweat" + | "bloody-nose" + | "doggy"}` + | `tools/${ + | "pad" + | "blue-note" + | "yellow-notes" + | "chart" + | "chart-2" + | "pencil" + | "pen" + | "bag" + | "rocket" + | "fire" + | "gold" + | "light" + | "pin" + | "red-flag" + | "tea" + | "island" + | "ball" + | "lucky-fish" + | "coffee" + | "milky-tea" + | "pan"}` + | `priority/priority-${1 | 2 | 3 | 4 | 5 | 6 | 7}` + | `task/${ + | "task-start" + | "task-oct" + | "task-3oct" + | "task-half" + | "task-5oct" + | "task-7oct" + | "task-done"}`; +``` + +完整节点结构见第 6 节 `OpenIconNodeWrite`。 + +完整 Append 示例: + +```json +{ + "source": { + "schemaVersion": "1.0", + "catalogVersion": "dml-v1", + "nodes": [ + { + "id": "pencil-icon", + "type": "icon", + "x": 100, + "y": 80, + "width": 48, + "height": 48, + "catalogId": "tools/pencil" + } + ] + }, + "overwrite": false +} +``` + +规则: + +- `catalogId` 必填,格式为 `/`,并且必须精确命中附录 B 的 + `dml-v1` allowlist;大小写、连字符和历史拼写都不能自行修正。 +- 当前目录有 `emoji`、`tools`、`priority`、`task` 四组,共 49 项。 +- update 只接受 `catalogId`,不接受 `group`、`name`、`style` 或 `resource`。 +- 内置 icon 不需要调用方上传资源,也不需要 `resourceId`。 +- 任意已上传 SVG 或自定义图标应使用 `vector`,并按 7.9 节提供完整 + `resource` 信息,不能伪造一个 icon `catalogId`。 +- icon 可以作为 group/frame 子节点;V1 connector 的 node 端点当前仍不接受 + icon 作为目标。 + +Query 返回相同的 `catalogId`。既有 icon 无法映射到当前目录时,节点仍会以 +`type: "icon"` 返回以便诊断,但 `writeSupport` 为 `readOnly`,原因包含 +`icon.catalogId`。非默认 opacity、filter/effect、text 或 adjustments 同样会令节点 +只读。 + +### 7.11 Path(自由画笔) + +`path` 表示自由画笔轨迹。OpenNodes 保留两组互相独立的尺寸: + +- 节点公共 `width` / `height` 是画布上的实际渲染尺寸,缩放节点时会变化。 +- `path.intrinsicWidth` / `path.intrinsicHeight` 是 SVG path 自身的坐标空间尺寸, + 表示 `path.data` 使用的内部坐标空间。 + +```ts +interface OpenPathData { + data: string; + intrinsicWidth: number; + intrinsicHeight: number; +} + +type OpenPathDataWrite = OpenPathData; +``` + +完整 update 节点结构见第 6 节 `OpenPathNodeWrite`。query 和 update 的 `path` +字段结构相同,但 update 仍必须满足下方命令子集和大小限制。 + +```json +{ + "id": "freehand-stroke", + "type": "path", + "x": 120, + "y": 100, + "width": 500, + "height": 150, + "path": { + "data": "M0,75 Q50,0 100,75 Q150,150 200,75 Q250,0 300,75 Q350,150 400,75 Q450,0 500,75", + "intrinsicWidth": 500, + "intrinsicHeight": 150 + }, + "style": { + "fill": { "type": "none" }, + "stroke": { + "paint": { "type": "solid", "color": "#7C3AED" }, + "width": 10, + "lineCap": "round", + "lineJoin": "round" + } + } +} +``` + +下面是一个仍然只使用 V1 命令子集、但包含 12 段二次贝塞尔曲线的蝴蝶轮廓。 +它显式回到起点,因此不需要使用尚未支持的 `Z`: + +```json +{ + "id": "complex-butterfly-path", + "type": "path", + "x": 1500, + "y": 1215, + "width": 500, + "height": 420, + "path": { + "data": "M250,180 Q210,105 145,70 Q55,25 35,110 Q10,195 125,220 Q35,275 80,355 Q125,415 210,315 Q235,285 250,250 Q265,285 290,315 Q375,415 420,355 Q465,275 375,220 Q490,195 465,110 Q445,25 355,70 Q290,105 250,180", + "intrinsicWidth": 500, + "intrinsicHeight": 420 + }, + "style": { + "opacity": 0.96, + "fill": { + "type": "solid", + "color": "#EDE9FE", + "opacity": 0.72 + }, + "stroke": { + "paint": { + "type": "solid", + "color": "#6D28D9" + }, + "width": 8, + "lineCap": "round", + "lineJoin": "round" + } + } +} +``` + +V1 path 写入约束如下: + +- `data` 必须是一个绝对 `M`,后跟至少一个显式写出的绝对 `Q`;不接受相对命令, + 也不接受 `L`、`C`、`A`、`Z` 等通用 SVG 命令。 +- 所有命令参数必须完整且为有限数值;单节点 `data` 最长 1 MiB,最多 50,000 个 + 命令。 +- `intrinsicWidth` 和 `intrinsicHeight` 必须为有限正数;它们不要求等于节点的 + `width` 和 `height`。 +- 未传 `style` 时,默认使用透明填充、`#222222` 描边、宽度 5,以及 round + line cap/join。需要稳定视觉结果时仍应显式传入 `style`。 +- path 可以作为 group/frame 的子节点,也可以作为同一 update 请求中 connector + 的 node 端点。 + +query 会把其他能够解析的 SVG path 保留为 `type: "path"`,但标记为 +`readOnly`,原因包含 `path.commands`;超出上述 V1 限制时使用 +`path.data.size` 或 `path.commands.limit`。既有 path 带 text、adjustments、非 +`nonzero` fillRule 或 V1 无法表达的样式时也会只读。仅包含不影响画笔几何的 +兼容性信息时,不会因此变为只读。theme stroke 遵循通用 style 契约:query 会 +保留 token 和明暗参数;只要 token 能在当前白板主题中解析,就可以按 theme +paint 回写。 diff --git a/skills/multi/dingtalk-misc/references/whiteboard/open-nodes-v1/07-examples-errors-write-support.md b/skills/multi/dingtalk-misc/references/whiteboard/open-nodes-v1/07-examples-errors-write-support.md new file mode 100644 index 00000000..1212068d --- /dev/null +++ b/skills/multi/dingtalk-misc/references/whiteboard/open-nodes-v1/07-examples-errors-write-support.md @@ -0,0 +1,270 @@ +# OpenNodes V1 — Update 示例、回写规则、错误模型和 writeSupport + +> 本文件是 DWS OpenNodes V1 协议的拆分章节。按需读取入口见 +> [协议索引](../open-nodes-v1.md)。 + +## 8. Update 示例 + +### 8.1 Append 一个文本节点 + +```json +{ + "source": { + "schemaVersion": "1.0", + "catalogVersion": "dml-v1", + "nodes": [ + { + "id": "title", + "type": "text", + "x": 120, + "y": 80, + "width": 240, + "height": 48, + "text": { + "blocks": [ + { + "type": "paragraph", + "horizontalAlign": "left", + "runs": [ + { + "text": "Hello OpenNodes", + "marks": { + "fontSize": 16, + "color": "#223344" + } + } + ] + } + ], + "verticalAlign": "center", + "padding": [2, 4] + } + } + ] + } +} +``` + +### 8.2 Append 两个形状和一条引用连接线 + +```json +{ + "source": { + "schemaVersion": "1.0", + "catalogVersion": "dml-v1", + "nodes": [ + { + "id": "left", + "type": "shape", + "x": 80, + "y": 100, + "width": 120, + "height": 80, + "geometry": "dml:roundRect" + }, + { + "id": "right", + "type": "shape", + "x": 360, + "y": 100, + "width": 120, + "height": 80, + "geometry": "dml:roundRect" + }, + { + "id": "line", + "type": "connector", + "start": { + "type": "node", + "nodeRef": { + "scope": "request", + "id": "left" + }, + "anchor": { + "mode": "fixed", + "side": "right" + } + }, + "end": { + "type": "node", + "nodeRef": { + "scope": "request", + "id": "right" + }, + "anchor": { + "mode": "fixed", + "side": "left" + }, + "marker": { + "catalogId": "arrow.filled" + } + }, + "routing": "straight" + } + ] + } +} +``` + +### 8.3 Overwrite 整页 + +```json +{ + "overwrite": true, + "source": { + "schemaVersion": "1.0", + "catalogVersion": "dml-v1", + "nodes": [ + { + "id": "replacement", + "type": "text", + "x": 120, + "y": 80, + "width": 240, + "height": 48, + "text": { + "blocks": [ + { + "type": "paragraph", + "runs": [ + { + "text": "Replacement content" + } + ] + } + ] + } + } + ] + } +} +``` + +### 8.4 清空当前页面 + +```json +{ + "overwrite": true, + "source": { + "schemaVersion": "1.0", + "catalogVersion": "dml-v1", + "nodes": [] + } +} +``` + +## 9. Query 数据不能直接回写 + +query 是完整可读投影,update 是受约束的创建协议,两者不是对称 JSON: + +| Query 字段/能力 | Update 处理方式 | +| --- | --- | +| 真实 `id` | 只能作为请求级临时 ID;不能引用既有 document 节点。 | +| `children` | 删除,通过子节点 `parentId` 重建。 | +| `absoluteBounds` | 删除;普通节点使用 `x/y/width/height`,connector 使用端点和路由字段。 | +| `locked`、`source`、`writeSupport`、`unsupportedFeatures` | 删除,均为 query-only。 | +| 文本 `plainText` | 删除,由服务端根据 paragraph 和 run 重新计算。 | +| 多 paragraph、列表、多 run、文字链接 | 可以保留;每个 block 必须是受支持类型,且 run 内不能包含原始换行符。 | +| 未知 list style、非法链接或未支持的 block/marks | 需要移除或降级为受支持的 block/run。 | +| theme paint | 保留 `token/lumMod/lumOff`,删除 query-only `resolvedColor`;token 必须能在当前白板主题中解析。 | +| image paint | 需要降级成受支持的 paint,或不更新该节点。 | +| 受支持的 linear/radial gradient、单个 shadow | 可以保留;gradient offset 使用 `0~100`,radial `custom` 不能回写。 | +| blur、unknown 或叠加 effects | 需要删除、降级成单个 shadow,或不更新该节点。 | +| connector `scope: "document"` | 不能回写;改为引用同一请求节点的 `scope: "request"`。 | +| connector `resolvedPoint`、`position`、`resolvedPath` | 删除,均由服务端重新计算。 | +| shape `adjustments` | V1 不支持写入。 | +| stickyNote `creator`、`tags` | V1 不支持写入。 | + +即使 query 节点显示 `writeSupport = "readWrite"`,update 仍会对版本、目录、 +字段和请求关系做完整校验。调用方不应跳过 update 错误处理。 + +## 10. 错误模型 + +### 10.1 顶层错误码 + +| 错误码 | 含义 | +| --- | --- | +| `invalidRequest.whiteboard.schemaInvalid` | JSON、字段或节点 schema 不合法。 | +| `invalidRequest.whiteboard.catalogVersionUnsupported` | catalogVersion 不受支持。 | +| `invalidRequest.whiteboard.validationFailed` | 节点间引用、父子关系或路径关系不合法。 | +| `invalidRequest.whiteboard.emptySource` | append 的 nodes 为空。 | +| `invalidRequest.whiteboard.overwriteUnsafe` | overwrite 安全预检失败。 | + +### 10.2 DWS 错误输出 + +远端校验失败时,DWS 以统一 CLI 错误结构返回: + +```json +{ + "error": { + "category": "api", + "reason": "business_error", + "server_key": "whiteboard", + "server_error_code": "invalidRequest.whiteboard.validationFailed", + "message": "Whiteboard request graph is invalid", + "trace_id": "TRACE_ID" + } +} +``` + +部分服务错误会使用更宽泛的 `invalidRequest.inputArgs.invalid`。Agent 应结合 +`server_error_code` 和 `message` 修正输入;需要排障时保留 `trace_id`。JSON 或 +信封级错误可能由 CLI 本地返回,不一定包含 `server_key` 和 `trace_id`。 + +校验类错误不可通过原样重试恢复。常见原因包括: + +- Schema:缺少字段、未知字段、类型或枚举值错误、提交 query-only 字段、 + 节点类型不支持。 +- 请求关系:临时 ID 重复、引用不存在、父子关系非法、连接线目标不支持、 + 路径退化或主题 token 不存在。 +- Overwrite 预检:锁定节点、禁止删除、关联数据无法安全处理或删除失败。 + +任一阶段失败都不会保留部分更新。 + +## 11. writeSupport 的含义 + +`writeSupport` 表示当前 query 节点是否能由 V1 update 无损表达,不代表用户 +权限,也不代表 overwrite 是否允许移除该既有节点。 + +以下 `unsupportedFeatures` 均为对外返回的诊断枚举值: + +- `node.source.master`、`node.locked`、`node.role`、`node.placeholder`、 + `node.extras`、`node.ability`。 +- `node.type.image`、`node.type.pdf`、`node.type.media`、 + `node.type.webLink`、`node.type.table`、`node.type.chart`、 + `node.type.uml`、`node.type.swimlane`、`node.type.mind`、 + `node.type.timer`、`node.type.placeholder`、`node.type.unknown`。 +- `text.list.unsupported`、`text.link.unsupported`、`text.block.unsupported`、 + `text.lineBreak.unsupported`、`text.marks.unsupported`、 + `text.color.unsupported`、`text.highlight.unsupported`。 +- `style.fill.color`、`style.fill.opacity`、`style.fill.theme.token`、 + `style.fill.theme.unresolved`、`style.fill.theme.modifier`、 + `style.fill.theme.opacity`、 + `style.fill.gradient.angle`、`style.fill.gradient.offset`、 + `style.fill.gradient.color`、`style.fill.gradient.opacity`、 + `style.fill.gradient.position`、`style.fill.image`。 +- `style.stroke.color`、`style.stroke.opacity`、`style.stroke.theme.token`、 + `style.stroke.theme.unresolved`、 + `style.stroke.theme.modifier`、`style.stroke.theme.opacity`、 + `style.stroke.gradient.angle`、`style.stroke.gradient.offset`、 + `style.stroke.gradient.color`、`style.stroke.gradient.opacity`、 + `style.stroke.gradient.position`、`style.stroke.image`。 +- `style.effects`。 +- `shape.adjustments`、`stickyNote.creator`、`stickyNote.tags`。 +- `vector.resource.external`、`vector.resource.embedded`、 + `vector.resource.unresolved`、`vector.fill`、`vector.stroke`、 + `vector.opacity`、`vector.effect`、`vector.adjustments`。 +- `icon.catalogId`、`icon.opacity`、`icon.effect`、`icon.text`、 + `icon.adjustments`。 +- `path.commands`、`path.data.size`、`path.commands.limit`、`path.text`、 + `path.fillRule`、`path.adjustments`。 +- `connector.parent`、`connector.marker.unsupported`、 + `connector.target.unexposed`、`connector.target.unsupported`、 + `connector.anchor.unresolved`、`connector.anchor.custom`、 + `connector.selfLoop`。 +- `group.angle`、`group.children.minimum`、`group.children.hidden`、 + `group.child.readOnly`。 +- `frame.angle`、`frame.child.frame`、`frame.child.readOnly`。 + +调用方应把 `unsupportedFeatures` 当作诊断信息,不应把当前枚举穷举写死为 +业务逻辑。真正可写与否以 `writeSupport` 和 update 校验结果为准。 diff --git a/skills/multi/dingtalk-misc/references/whiteboard/open-nodes-v1/08-catalogs.md b/skills/multi/dingtalk-misc/references/whiteboard/open-nodes-v1/08-catalogs.md new file mode 100644 index 00000000..cd8b2063 --- /dev/null +++ b/skills/multi/dingtalk-misc/references/whiteboard/open-nodes-v1/08-catalogs.md @@ -0,0 +1,61 @@ +# OpenNodes V1 — dml-v1 Geometry 和 Icon 完整目录 + +> 本文件是 DWS OpenNodes V1 协议的拆分章节。按需读取入口见 +> [协议索引](../open-nodes-v1.md)。 + +## 附录 A:dml-v1 geometry 目录 + +写入时在以下名称前加 `dml:`,例如 `rect` 写成 `dml:rect`。当前目录共 +183 项,目录版本由 `catalogVersion = "dml-v1"` 标识。 + +```text +accentBorderCallout1 accentBorderCallout2 accentBorderCallout3 +accentCallout1 accentCallout2 accentCallout3 actionButtonBackPrevious +actionButtonBeginning actionButtonBlank actionButtonDocument actionButtonEnd +actionButtonForwardNext actionButtonHelp actionButtonHome +actionButtonInformation actionButtonMovie actionButtonReturn actionButtonSound +active allGeneralization arc attribute bentArrow bentUpArrow bevel bind blockArc +borderCallout1 borderCallout2 borderCallout3 bracePair bracketPair callout1 +callout2 callout3 can chevron chord circularArrow cloud cloudCallout comment +component control convert corner cube curvedDownArrow curvedLeftArrow +curvedRightArrow curvedUpArrow dataStorage decagon delete diagStripe diamond +dodecagon donut doubleWave downArrow downArrowCallout ellipse ellipseRibbon +ellipseRibbon2 entity entitySet flowChartAlternateProcess flowChartCollate +flowChartConnector flowChartDecision flowChartDelay flowChartDisplay +flowChartDocument flowChartExtract flowChartInputOutput flowChartInternalStorage +flowChartMagneticDisk flowChartMagneticDrum flowChartMagneticTape +flowChartManualInput flowChartManualOperation flowChartMerge +flowChartMultidocument flowChartOffpageConnector flowChartOnlineStorage +flowChartOr flowChartPredefinedProcess flowChartPreparation +flowChartPunchedCard flowChartPunchedTape flowChartSort +flowChartSummingJunction flowChartTerminator foldedCorner frame generalization +halfFrame heart heptagon hexagon history homePlate horizontalDivCircle +horizontalScroll irregularSeal1 irregularSeal2 leftArrow leftArrowCallout +leftBrace leftBracket leftRightArrow leftRightArrowCallout leftRightUpArrow +leftUpArrow lightningBolt mathDivide mathEqual mathMinus mathMultiply +mathNotEqual mathPlus moon multiClass multiValuedAttribute noSmoking node +nonIsoscelesTrapezoid notchedRightArrow octagon parallelogram pentagon person +pie plaque plus quadArrow quadArrowCallout receiveSignal rect relationship +ribbon ribbon2 rightArrow rightArrowCallout rightBrace rightBracket round1Rect +round2DiagRect round2SameRect roundRect rtTriangle smileyFace snip1Rect +snip2DiagRect snip2SameRect snipRoundRect star10 star12 star16 star24 star32 +star4 star5 star6 star7 star8 stripedRightArrow sun teardrop triangle upArrow +upArrowCallout upDownArrow user uturnArrow verticalDivCircle verticalScroll +wave weakEntitySet weakRelationship wedgeEllipseCallout wedgeRectCallout +wedgeRoundRectCallout +``` + +## 附录 B:dml-v1 icon 目录 + +写入时必须使用完整的 `/`。当前共 4 组 49 项,目录版本由 +`catalogVersion = "dml-v1"` 标识。 + +| group | 数量 | name(组成 `group/name`) | +| --- | ---: | --- | +| `emoji` | 14 | `happy`、`smile`、`laugh`、`fighting`、`like`、`ok`、`please`、`face-plam`、`tears-of-joy`、`cry`、`question`、`face-with-sweat`、`bloody-nose`、`doggy` | +| `tools` | 21 | `pad`、`blue-note`、`yellow-notes`、`chart`、`chart-2`、`pencil`、`pen`、`bag`、`rocket`、`fire`、`gold`、`light`、`pin`、`red-flag`、`tea`、`island`、`ball`、`lucky-fish`、`coffee`、`milky-tea`、`pan` | +| `priority` | 7 | `priority-1`、`priority-2`、`priority-3`、`priority-4`、`priority-5`、`priority-6`、`priority-7` | +| `task` | 7 | `task-start`、`task-oct`、`task-3oct`、`task-half`、`task-5oct`、`task-7oct`、`task-done` | + +注意:`emoji/face-plam` 是 V1 保留的历史兼容枚举值,拼写虽然异常但属于协议值; +传入 `emoji/face-palm` 会被拒绝。