fix: address contract review feedback

This commit is contained in:
Dennis
2026-07-28 12:09:57 +08:00
parent 00305d941d
commit 69d813afef
26 changed files with 670 additions and 223 deletions
+9 -6
View File
@@ -12071,7 +12071,8 @@
"availability": "available",
"avoid_when": [
"改正文标题/章节标题改用 doc block update",
"不要用 update 或重新 create 来改名"
"不要用 update 或重新 create 来改名",
"文件或文件夹重命名优先用 dws drive rename,由该命令读取真实节点类型和当前扩展名"
],
"confirmation": "not_required",
"effect": "write",
@@ -12121,7 +12122,8 @@
"avoid_when": {
"value": [
"改正文标题/章节标题改用 doc block update",
"不要用 update 或重新 create 来改名"
"不要用 update 或重新 create 来改名",
"文件或文件夹重命名优先用 dws drive rename,由该命令读取真实节点类型和当前扩展名"
],
"source": "internal/cli/schema_hints/selection/doc.json",
"precedence": "reviewed_explicit",
@@ -12131,7 +12133,8 @@
{
"value": [
"改正文标题/章节标题改用 doc block update",
"不要用 update 或重新 create 来改名"
"不要用 update 或重新 create 来改名",
"文件或文件夹重命名优先用 dws drive rename,由该命令读取真实节点类型和当前扩展名"
],
"source": "internal/cli/schema_hints/selection/doc.json",
"precedence": "reviewed_explicit",
@@ -12277,7 +12280,7 @@
},
"use_when": {
"value": [
"用户要改文档/文件在列表与链接中展示的名称时;即使名称带常见扩展名,CLI 也会去掉一层再交给保留原扩展名的服务端"
"用户要改在线文档在列表与链接中展示的名称时"
],
"source": "internal/cli/schema_hints/selection/doc.json",
"precedence": "reviewed_explicit",
@@ -12286,7 +12289,7 @@
"candidates": [
{
"value": [
"用户要改文档/文件在列表与链接中展示的名称时;即使名称带常见扩展名,CLI 也会去掉一层再交给保留原扩展名的服务端"
"用户要改在线文档在列表与链接中展示的名称时"
],
"source": "internal/cli/schema_hints/selection/doc.json",
"precedence": "reviewed_explicit",
@@ -12318,7 +12321,7 @@
"structured-hint:internal/cli/schema_hints/selection-review.json#doc.rename_document"
],
"use_when": [
"用户要改文档/文件在列表与链接中展示的名称时;即使名称带常见扩展名,CLI 也会去掉一层再交给保留原扩展名的服务端"
"用户要改在线文档在列表与链接中展示的名称时"
]
},
"doc search": {
+20 -17
View File
@@ -7083,12 +7083,13 @@
]
},
"drive rename": {
"agent_summary": "修改文档空间中文档或文件的名称,并避免服务端保留原扩展名导致双扩展名",
"agent_summary": "安全重命名文档空间中的文档、文件或文件夹,并按节点真实扩展名避免双后缀",
"agent_summary_source": "dws-agent-selection/drive",
"availability": "available",
"avoid_when": [
"要改正文里的标题/章节 H1 改用 dws doc block update,不要用 rename",
"只要复制或移动改用 copy/move"
"只要复制或移动改用 copy/move",
"dry-run 不读取节点元数据,输出名称尚未做基于当前扩展名的规范化"
],
"confirmation": "not_required",
"effect": "write",
@@ -7098,14 +7099,14 @@
],
"field_provenance": {
"agent_summary": {
"value": "修改文档空间中文档或文件的名称,并避免服务端保留原扩展名导致双扩展名",
"value": "安全重命名文档空间中的文档、文件或文件夹,并按节点真实扩展名避免双后缀",
"source": "internal/cli/schema_hints/selection/drive.json",
"precedence": "reviewed_explicit",
"resolution": "highest_precedence",
"review_reason": "人工审阅:结合本机 dws schema 实时 MCP description/parameters(经 interface_ref)、产品 Skill、Cobra Long 与 Runtime 确认门禁,按飞书风格重写选型文案;不改变命令身份、参数契约或接口绑定。",
"candidates": [
{
"value": "修改文档空间中文档或文件的名称,并避免服务端保留原扩展名导致双扩展名",
"value": "安全重命名文档空间中的文档、文件或文件夹,并按节点真实扩展名避免双后缀",
"source": "internal/cli/schema_hints/selection/drive.json",
"precedence": "reviewed_explicit",
"selected": true,
@@ -7118,14 +7119,14 @@
"source": "internal/cli/schema_hints/metadata/drive.json",
"precedence": "reviewed_explicit",
"resolution": "highest_precedence",
"review_reason": "The reviewed CLI normalizes one familiar extension from --name because rename_document preserves the node's existing extension.",
"review_reason": "The reviewed CLI reads node metadata before mutation and strips only the exact current extension for non-folder nodes, avoiding both duplicate extensions and dotted-folder corruption without a suffix whitelist.",
"candidates": [
{
"value": "available",
"source": "internal/cli/schema_hints/metadata/drive.json",
"precedence": "reviewed_explicit",
"selected": true,
"review_reason": "The reviewed CLI normalizes one familiar extension from --name because rename_document preserves the node's existing extension."
"review_reason": "The reviewed CLI reads node metadata before mutation and strips only the exact current extension for non-folder nodes, avoiding both duplicate extensions and dotted-folder corruption without a suffix whitelist."
},
{
"value": "available",
@@ -7138,7 +7139,8 @@
"avoid_when": {
"value": [
"要改正文里的标题/章节 H1 改用 dws doc block update,不要用 rename",
"只要复制或移动改用 copy/move"
"只要复制或移动改用 copy/move",
"dry-run 不读取节点元数据,输出名称尚未做基于当前扩展名的规范化"
],
"source": "internal/cli/schema_hints/selection/drive.json",
"precedence": "reviewed_explicit",
@@ -7148,7 +7150,8 @@
{
"value": [
"要改正文里的标题/章节 H1 改用 dws doc block update,不要用 rename",
"只要复制或移动改用 copy/move"
"只要复制或移动改用 copy/move",
"dry-run 不读取节点元数据,输出名称尚未做基于当前扩展名的规范化"
],
"source": "internal/cli/schema_hints/selection/drive.json",
"precedence": "reviewed_explicit",
@@ -7224,14 +7227,14 @@
"source": "internal/cli/schema_hints/metadata/drive.json",
"precedence": "reviewed_explicit",
"resolution": "highest_precedence",
"review_reason": "The reviewed CLI normalizes one familiar extension from --name because rename_document preserves the node's existing extension.",
"review_reason": "The reviewed CLI reads node metadata before mutation and strips only the exact current extension for non-folder nodes, avoiding both duplicate extensions and dotted-folder corruption without a suffix whitelist.",
"candidates": [
{
"value": "mcp",
"source": "internal/cli/schema_hints/metadata/drive.json",
"precedence": "reviewed_explicit",
"selected": true,
"review_reason": "The reviewed CLI normalizes one familiar extension from --name because rename_document preserves the node's existing extension."
"review_reason": "The reviewed CLI reads node metadata before mutation and strips only the exact current extension for non-folder nodes, avoiding both duplicate extensions and dotted-folder corruption without a suffix whitelist."
},
{
"value": "mcp",
@@ -7246,14 +7249,14 @@
"source": "internal/cli/schema_hints/metadata/drive.json",
"precedence": "reviewed_explicit",
"resolution": "highest_precedence",
"review_reason": "The reviewed CLI normalizes one familiar extension from --name because rename_document preserves the node's existing extension.",
"review_reason": "The reviewed CLI reads node metadata before mutation and strips only the exact current extension for non-folder nodes, avoiding both duplicate extensions and dotted-folder corruption without a suffix whitelist.",
"candidates": [
{
"value": "doc.rename_document",
"source": "internal/cli/schema_hints/metadata/drive.json",
"precedence": "reviewed_explicit",
"selected": true,
"review_reason": "The reviewed CLI normalizes one familiar extension from --name because rename_document preserves the node's existing extension."
"review_reason": "The reviewed CLI reads node metadata before mutation and strips only the exact current extension for non-folder nodes, avoiding both duplicate extensions and dotted-folder corruption without a suffix whitelist."
},
{
"value": "doc.rename_document",
@@ -7268,14 +7271,14 @@
"source": "internal/cli/schema_hints/metadata/drive.json",
"precedence": "reviewed_explicit",
"resolution": "highest_precedence",
"review_reason": "The reviewed CLI normalizes one familiar extension from --name because rename_document preserves the node's existing extension.",
"review_reason": "The reviewed CLI reads node metadata before mutation and strips only the exact current extension for non-folder nodes, avoiding both duplicate extensions and dotted-folder corruption without a suffix whitelist.",
"candidates": [
{
"value": true,
"source": "internal/cli/schema_hints/metadata/drive.json",
"precedence": "reviewed_explicit",
"selected": true,
"review_reason": "The reviewed CLI normalizes one familiar extension from --name because rename_document preserves the node's existing extension."
"review_reason": "The reviewed CLI reads node metadata before mutation and strips only the exact current extension for non-folder nodes, avoiding both duplicate extensions and dotted-folder corruption without a suffix whitelist."
},
{
"value": true,
@@ -7302,7 +7305,7 @@
},
"use_when": {
"value": [
"用户要重命名钉盘/文档空间中的文件或文档显示名称时;--name 带常见扩展名也可,CLI 会自动去掉一层"
"用户要重命名钉盘/文档空间中的文件、文档或文件夹时;实际执行会读取节点类型和当前扩展名,仅去掉完全匹配的一层后缀"
],
"source": "internal/cli/schema_hints/selection/drive.json",
"precedence": "reviewed_explicit",
@@ -7311,7 +7314,7 @@
"candidates": [
{
"value": [
"用户要重命名钉盘/文档空间中的文件或文档显示名称时;--name 带常见扩展名也可,CLI 会自动去掉一层"
"用户要重命名钉盘/文档空间中的文件、文档或文件夹时;实际执行会读取节点类型和当前扩展名,仅去掉完全匹配的一层后缀"
],
"source": "internal/cli/schema_hints/selection/drive.json",
"precedence": "reviewed_explicit",
@@ -7342,7 +7345,7 @@
"structured-hint:internal/cli/schema_hints/selection-review.json#drive.rename_document"
],
"use_when": [
"用户要重命名钉盘/文档空间中的文件或文档显示名称时;--name 带常见扩展名也可,CLI 会自动去掉一层"
"用户要重命名钉盘/文档空间中的文件、文档或文件夹时;实际执行会读取节点类型和当前扩展名,仅去掉完全匹配的一层后缀"
]
},
"drive search": {
@@ -1,6 +1,6 @@
{
"version": 1,
"source_hash": "sha256:696955f889cbb3a843a1afc91ee511cd54cf1dd5d96b0dabc20516f59719eecd",
"source_hash": "sha256:f71480e58eda8f2be041e07d7faf6fb20f5c21f861995d3c4dacc311b46c3e81",
"surface_hash": "sha256:4cf8460240b19f896c3a330c69982cbb5f57aa8576cdf30373172082eea893ed",
"coverage": {
"surface_products": 26,
+21 -18
View File
@@ -2324,11 +2324,12 @@
]
},
"todo +remind": {
"agent_summary": "给自己创建一条带截止/提醒时间的待办",
"agent_summary": "给自己创建一条带可选截止时间的待办",
"agent_summary_source": "dws-agent-selection/todo",
"availability": "available",
"avoid_when": [
"需要该 Shortcut 未公开的底层参数、原始响应或不同执行语义时,改用对应原子命令"
"需要该 Shortcut 未公开的底层参数、原始响应或不同执行语义时,改用对应原子命令",
"用户需要独立提醒规则时改用 dws todo task add-reminder;不要把 --at 解释成提醒时间"
],
"confirmation": "user_required",
"effect": "write",
@@ -2338,14 +2339,14 @@
],
"field_provenance": {
"agent_summary": {
"value": "给自己创建一条带截止/提醒时间的待办",
"value": "给自己创建一条带可选截止时间的待办",
"source": "internal/cli/schema_hints/selection/todo.json",
"precedence": "reviewed_explicit",
"resolution": "highest_precedence",
"review_reason": "Agent-authored and reviewed from the built-in Shortcut intent, executable Cobra path, and declared outcome; it affects selection only and does not alter execution or safety facts.",
"candidates": [
{
"value": "给自己创建一条带截止/提醒时间的待办",
"value": "给自己创建一条带可选截止时间的待办",
"source": "internal/cli/schema_hints/selection/todo.json",
"precedence": "reviewed_explicit",
"selected": true,
@@ -2371,7 +2372,8 @@
},
"avoid_when": {
"value": [
"需要该 Shortcut 未公开的底层参数、原始响应或不同执行语义时,改用对应原子命令"
"需要该 Shortcut 未公开的底层参数、原始响应或不同执行语义时,改用对应原子命令",
"用户需要独立提醒规则时改用 dws todo task add-reminder;不要把 --at 解释成提醒时间"
],
"source": "internal/cli/schema_hints/selection/todo.json",
"precedence": "reviewed_explicit",
@@ -2380,7 +2382,8 @@
"candidates": [
{
"value": [
"需要该 Shortcut 未公开的底层参数、原始响应或不同执行语义时,改用对应原子命令"
"需要该 Shortcut 未公开的底层参数、原始响应或不同执行语义时,改用对应原子命令",
"用户需要独立提醒规则时改用 dws todo task add-reminder;不要把 --at 解释成提醒时间"
],
"source": "internal/cli/schema_hints/selection/todo.json",
"precedence": "reviewed_explicit",
@@ -2546,7 +2549,7 @@
},
"use_when": {
"value": [
"当你想给自己记一件事、并(可选)设一个截止/提醒时间,又不想先查自己的 userId 时使用;内部先解析当前登录用户的 userId,再显式设置 executorIds,--at 会按 ISO8601 解析为截止时间。会真实创建待办。"
"当你想给自己记一件事、并(可选)设一个截止时间,又不想先查自己的 userId 时使用;内部先解析当前登录用户的 userId,再显式设置 executorIds,--at 只会按 ISO8601 写入截止时间 dueTime,不会创建独立提醒规则。会真实创建待办。"
],
"source": "internal/cli/schema_hints/selection/todo.json",
"precedence": "reviewed_explicit",
@@ -2555,7 +2558,7 @@
"candidates": [
{
"value": [
"当你想给自己记一件事、并(可选)设一个截止/提醒时间,又不想先查自己的 userId 时使用;内部先解析当前登录用户的 userId,再显式设置 executorIds,--at 会按 ISO8601 解析为截止时间。会真实创建待办。"
"当你想给自己记一件事、并(可选)设一个截止时间,又不想先查自己的 userId 时使用;内部先解析当前登录用户的 userId,再显式设置 executorIds,--at 只会按 ISO8601 写入截止时间 dueTime,不会创建独立提醒规则。会真实创建待办。"
],
"source": "internal/cli/schema_hints/selection/todo.json",
"precedence": "reviewed_explicit",
@@ -2578,7 +2581,7 @@
"internal/cli/schema_hints/selection/todo.json"
],
"use_when": [
"当你想给自己记一件事、并(可选)设一个截止/提醒时间,又不想先查自己的 userId 时使用;内部先解析当前登录用户的 userId,再显式设置 executorIds,--at 会按 ISO8601 解析为截止时间。会真实创建待办。"
"当你想给自己记一件事、并(可选)设一个截止时间,又不想先查自己的 userId 时使用;内部先解析当前登录用户的 userId,再显式设置 executorIds,--at 只会按 ISO8601 写入截止时间 dueTime,不会创建独立提醒规则。会真实创建待办。"
]
},
"todo +todo-done": {
@@ -8604,14 +8607,14 @@
"source": "internal/cli/schema_hints/metadata/todo.json",
"precedence": "reviewed_explicit",
"resolution": "highest_precedence",
"review_reason": "dws-tool-metadata/todo marks this tool as reviewed",
"review_reason": "The reviewed CLI rejects malformed or structurally invalid non-empty reminder rules before the reset mutation, while preserving omission and [] as explicit clear forms.",
"candidates": [
{
"value": "available",
"source": "internal/cli/schema_hints/metadata/todo.json",
"precedence": "reviewed_explicit",
"selected": true,
"review_reason": "dws-tool-metadata/todo marks this tool as reviewed"
"review_reason": "The reviewed CLI rejects malformed or structurally invalid non-empty reminder rules before the reset mutation, while preserving omission and [] as explicit clear forms."
},
{
"value": "available",
@@ -8714,14 +8717,14 @@
"source": "internal/cli/schema_hints/metadata/todo.json",
"precedence": "reviewed_explicit",
"resolution": "highest_precedence",
"review_reason": "dws-tool-metadata/todo marks this tool as reviewed",
"review_reason": "The reviewed CLI rejects malformed or structurally invalid non-empty reminder rules before the reset mutation, while preserving omission and [] as explicit clear forms.",
"candidates": [
{
"value": "mcp",
"source": "internal/cli/schema_hints/metadata/todo.json",
"precedence": "reviewed_explicit",
"selected": true,
"review_reason": "dws-tool-metadata/todo marks this tool as reviewed"
"review_reason": "The reviewed CLI rejects malformed or structurally invalid non-empty reminder rules before the reset mutation, while preserving omission and [] as explicit clear forms."
},
{
"value": "mcp",
@@ -8750,14 +8753,14 @@
"source": "internal/cli/schema_hints/metadata/todo.json",
"precedence": "reviewed_explicit",
"resolution": "highest_precedence",
"review_reason": "dws-tool-metadata/todo marks this tool as reviewed",
"review_reason": "The reviewed CLI rejects malformed or structurally invalid non-empty reminder rules before the reset mutation, while preserving omission and [] as explicit clear forms.",
"candidates": [
{
"value": true,
"source": "internal/cli/schema_hints/metadata/todo.json",
"precedence": "reviewed_explicit",
"selected": true,
"review_reason": "dws-tool-metadata/todo marks this tool as reviewed"
"review_reason": "The reviewed CLI rejects malformed or structurally invalid non-empty reminder rules before the reset mutation, while preserving omission and [] as explicit clear forms."
},
{
"value": true,
@@ -8784,7 +8787,7 @@
},
"use_when": {
"value": [
"需要清除或整体替换待办提醒规则时(可不传 reminder-rules 以清除);成功响应仅作为写入回执"
"需要清除或整体替换待办提醒规则时(不传 reminder-rules 或传 [] 可清除);非空规则会在远端调用前严格校验,成功响应仅作为写入回执"
],
"source": "internal/cli/schema_hints/selection/todo.json",
"precedence": "reviewed_explicit",
@@ -8793,7 +8796,7 @@
"candidates": [
{
"value": [
"需要清除或整体替换待办提醒规则时(可不传 reminder-rules 以清除);成功响应仅作为写入回执"
"需要清除或整体替换待办提醒规则时(不传 reminder-rules 或传 [] 可清除);非空规则会在远端调用前严格校验,成功响应仅作为写入回执"
],
"source": "internal/cli/schema_hints/selection/todo.json",
"precedence": "reviewed_explicit",
@@ -8823,7 +8826,7 @@
"structured-hint:internal/cli/schema_hints/selection-review.json#todo.reset_todo_reminder"
],
"use_when": [
"需要清除或整体替换待办提醒规则时(可不传 reminder-rules 以清除);成功响应仅作为写入回执"
"需要清除或整体替换待办提醒规则时(不传 reminder-rules 或传 [] 可清除);非空规则会在远端调用前严格校验,成功响应仅作为写入回执"
]
},
"todo task update": {
@@ -1,6 +1,6 @@
{
"version": 1,
"source_hash": "sha256:696955f889cbb3a843a1afc91ee511cd54cf1dd5d96b0dabc20516f59719eecd",
"source_hash": "sha256:f71480e58eda8f2be041e07d7faf6fb20f5c21f861995d3c4dacc311b46c3e81",
"surface_hash": "sha256:4cf8460240b19f896c3a330c69982cbb5f57aa8576cdf30373172082eea893ed",
"source_files": 160,
"hint_files": 54,
@@ -3890,7 +3890,7 @@
{
"tool_path": "todo task remove-attachment",
"source": "skills/mono/references/products/todo.md",
"line": 438,
"line": 440,
"candidates": [
"todo task add-attachment",
"todo task add-executor",
@@ -3904,7 +3904,7 @@
{
"tool_path": "todo task list-sub",
"source": "skills/mono/references/products/todo.md",
"line": 441,
"line": 443,
"candidates": [
"todo task add-attachment",
"todo task add-executor",
+101 -73
View File
@@ -1,12 +1,12 @@
{
"version": 1,
"source_hash": "sha256:a0e007ce9500f18abfca09ad2de259b8b574b8150c9c79ccc7f3d7b1ffe66f7d",
"source_hash": "sha256:91a6b7040ccc8fa16dce388a1ea8ac2aae808819af2e6e998f65f363685ca325",
"surface_hash": "sha256:4cf8460240b19f896c3a330c69982cbb5f57aa8576cdf30373172082eea893ed",
"catalog": {
"agent_metadata": {
"products_with_metadata": 26,
"source": "embedded-skill-metadata",
"source_hash": "sha256:696955f889cbb3a843a1afc91ee511cd54cf1dd5d96b0dabc20516f59719eecd",
"source_hash": "sha256:f71480e58eda8f2be041e07d7faf6fb20f5c21f861995d3c4dacc311b46c3e81",
"surface_hash": "sha256:4cf8460240b19f896c3a330c69982cbb5f57aa8576cdf30373172082eea893ed",
"surface_products": 26,
"surface_tools": 813,
@@ -15008,7 +15008,8 @@
"availability": "available",
"avoid_when": [
"改正文标题/章节标题改用 doc block update",
"不要用 update 或重新 create 来改名"
"不要用 update 或重新 create 来改名",
"文件或文件夹重命名优先用 dws drive rename,由该命令读取真实节点类型和当前扩展名"
],
"canonical_path": "doc.rename_document",
"cli_name": "rename",
@@ -15029,7 +15030,7 @@
"risk": "medium",
"title": "重命名文档/文件",
"use_when": [
"用户要改文档/文件在列表与链接中展示的名称时;即使名称带常见扩展名,CLI 也会去掉一层再交给保留原扩展名的服务端"
"用户要改在线文档在列表与链接中展示的名称时"
]
},
{
@@ -16613,18 +16614,19 @@
},
{
"agent_metadata_source": "embedded-skill-metadata",
"agent_summary": "修改文档空间中文档或文件的名称,并避免服务端保留原扩展名导致双扩展名",
"agent_summary": "安全重命名文档空间中的文档、文件或文件夹,并按节点真实扩展名避免双后缀",
"agent_summary_source": "dws-agent-selection/drive",
"availability": "available",
"avoid_when": [
"要改正文里的标题/章节 H1 改用 dws doc block update,不要用 rename",
"只要复制或移动改用 copy/move"
"只要复制或移动改用 copy/move",
"dry-run 不读取节点元数据,输出名称尚未做基于当前扩展名的规范化"
],
"canonical_path": "drive.rename_document",
"cli_name": "rename",
"cli_path": "drive rename",
"confirmation": "not_required",
"description": "修改文档空间中文档或文件的名称。服务端会保留节点的原扩展名;\n如果 --name 带常见文件扩展名,CLI 会自动去掉一层,避免出现 report.txt.txt。\n\n权限要求: 对文档有\"编辑\"权限。",
"description": "修改文档空间中文档、文件或文件夹的名称。\n实际执行前会读取节点类型和当前扩展名:文件的新名称仅在末尾扩展名与当前扩展名一致时去掉一层,\n避免 report.txt.txt;文件夹和扩展名不匹配的名称保持不变。dry-run 不读取远端元数据,因此保留输入名称。\n\n权限要求: 对文档有\"编辑\"权限。",
"effect": "write",
"idempotency": "unknown",
"interface_mode": "mcp",
@@ -16639,7 +16641,7 @@
"risk": "medium",
"title": "重命名文件/文档",
"use_when": [
"用户要重命名钉盘/文档空间中的文件或文档显示名称时;--name 带常见扩展名也可,CLI 会自动去掉一层"
"用户要重命名钉盘/文档空间中的文件、文档或文件夹时;实际执行会读取节点类型和当前扩展名,仅去掉完全匹配的一层后缀"
]
},
{
@@ -24777,7 +24779,7 @@
"cli_name": "reset-reminder",
"cli_path": "todo task reset-reminder",
"confirmation": "not_required",
"description": "整体替换待办提醒规则;不传 --reminder-rules 时清除提醒。\n\n当前上游没有提醒规则查询接口,task get/list 也不返回 reminderRules;\n成功响应是写入回执,不代表 CLI 能再次读取并核验最终规则。",
"description": "整体替换待办提醒规则;不传 --reminder-rules 时清除提醒。显式传值必须是合法 JSON 数组,\n每条规则必须按 baseTime 提供 dueDateOffset 或 reminderTimeStamp;非法输入会在远端调用前失败。\n\n当前上游没有提醒规则查询接口,task get/list 也不返回 reminderRules;\n成功响应是写入回执,不代表 CLI 能再次读取并核验最终规则。",
"effect": "write",
"group": "task",
"idempotency": "unknown",
@@ -24793,7 +24795,7 @@
"risk": "medium",
"title": "重置待办提醒",
"use_when": [
"需要清除或整体替换待办提醒规则时(可不传 reminder-rules 以清除);成功响应仅作为写入回执"
"需要清除或整体替换待办提醒规则时(不传 reminder-rules 或传 [] 可清除);非空规则会在远端调用前严格校验,成功响应仅作为写入回执"
]
},
{
@@ -25032,11 +25034,12 @@
},
{
"agent_metadata_source": "embedded-skill-metadata",
"agent_summary": "给自己创建一条带截止/提醒时间的待办",
"agent_summary": "给自己创建一条带可选截止时间的待办",
"agent_summary_source": "dws-agent-selection/todo",
"availability": "available",
"avoid_when": [
"需要该 Shortcut 未公开的底层参数、原始响应或不同执行语义时,改用对应原子命令"
"需要该 Shortcut 未公开的底层参数、原始响应或不同执行语义时,改用对应原子命令",
"用户需要独立提醒规则时改用 dws todo task add-reminder;不要把 --at 解释成提醒时间"
],
"canonical_path": "todo.shortcut_remind",
"cli_name": "+remind",
@@ -25053,7 +25056,7 @@
"risk": "medium",
"title": "给自己创建一条带可选截止时间的待办",
"use_when": [
"当你想给自己记一件事、并(可选)设一个截止/提醒时间,又不想先查自己的 userId 时使用;内部先解析当前登录用户的 userId,再显式设置 executorIds,--at 会按 ISO8601 解析为截止时间。会真实创建待办。"
"当你想给自己记一件事、并(可选)设一个截止时间,又不想先查自己的 userId 时使用;内部先解析当前登录用户的 userId,再显式设置 executorIds,--at 只会按 ISO8601 写入截止时间 dueTime,不会创建独立提醒规则。会真实创建待办。"
]
},
{
@@ -352880,7 +352883,8 @@
"availability": "available",
"avoid_when": [
"改正文标题/章节标题改用 doc block update",
"不要用 update 或重新 create 来改名"
"不要用 update 或重新 create 来改名",
"文件或文件夹重命名优先用 dws drive rename,由该命令读取真实节点类型和当前扩展名"
],
"canonical_path": "doc.rename_document",
"cli_name": "rename",
@@ -352941,7 +352945,8 @@
"source": "internal/cli/schema_hints/selection/doc.json",
"value": [
"改正文标题/章节标题改用 doc block update",
"不要用 update 或重新 create 来改名"
"不要用 update 或重新 create 来改名",
"文件或文件夹重命名优先用 dws drive rename,由该命令读取真实节点类型和当前扩展名"
]
}
],
@@ -352951,7 +352956,8 @@
"source": "internal/cli/schema_hints/selection/doc.json",
"value": [
"改正文标题/章节标题改用 doc block update",
"不要用 update 或重新 create 来改名"
"不要用 update 或重新 create 来改名",
"文件或文件夹重命名优先用 dws drive rename,由该命令读取真实节点类型和当前扩展名"
]
},
"canonical_path": {
@@ -353173,7 +353179,7 @@
"selected": true,
"source": "internal/cli/schema_hints/selection/doc.json",
"value": [
"用户要改文档/文件在列表与链接中展示的名称时;即使名称带常见扩展名,CLI 也会去掉一层再交给保留原扩展名的服务端"
"用户要改在线文档在列表与链接中展示的名称时"
]
}
],
@@ -353182,7 +353188,7 @@
"review_reason": "人工审阅:结合本机 dws schema 实时 MCP description/parameters(经 interface_ref)、产品 Skill、Cobra Long 与 Runtime 确认门禁,按飞书风格重写选型文案;不改变命令身份、参数契约或接口绑定。",
"source": "internal/cli/schema_hints/selection/doc.json",
"value": [
"用户要改文档/文件在列表与链接中展示的名称时;即使名称带常见扩展名,CLI 也会去掉一层再交给保留原扩展名的服务端"
"用户要改在线文档在列表与链接中展示的名称时"
]
}
},
@@ -353425,7 +353431,7 @@
"source": "reviewed_command_registry",
"title": "重命名文档/文件",
"use_when": [
"用户要改文档/文件在列表与链接中展示的名称时;即使名称带常见扩展名,CLI 也会去掉一层再交给保留原扩展名的服务端"
"用户要改在线文档在列表与链接中展示的名称时"
]
},
"doc.reply_comment": {
@@ -390427,18 +390433,19 @@
"structured-hint:internal/cli/schema_hints/products/drive.json",
"structured-hint:internal/cli/schema_hints/selection-review.json#drive.rename_document"
],
"agent_summary": "修改文档空间中文档或文件的名称,并避免服务端保留原扩展名导致双扩展名",
"agent_summary": "安全重命名文档空间中的文档、文件或文件夹,并按节点真实扩展名避免双后缀",
"agent_summary_source": "dws-agent-selection/drive",
"availability": "available",
"avoid_when": [
"要改正文里的标题/章节 H1 改用 dws doc block update,不要用 rename",
"只要复制或移动改用 copy/move"
"只要复制或移动改用 copy/move",
"dry-run 不读取节点元数据,输出名称尚未做基于当前扩展名的规范化"
],
"canonical_path": "drive.rename_document",
"cli_name": "rename",
"cli_path": "drive rename",
"confirmation": "not_required",
"description": "修改文档空间中文档或文件的名称。服务端会保留节点的原扩展名;\n如果 --name 带常见文件扩展名,CLI 会自动去掉一层,避免出现 report.txt.txt。\n\n权限要求: 对文档有\"编辑\"权限。",
"description": "修改文档空间中文档、文件或文件夹的名称。\n实际执行前会读取节点类型和当前扩展名:文件的新名称仅在末尾扩展名与当前扩展名一致时去掉一层,\n避免 report.txt.txt;文件夹和扩展名不匹配的名称保持不变。dry-run 不读取远端元数据,因此保留输入名称。\n\n权限要求: 对文档有\"编辑\"权限。",
"display": "钉盘文件管理",
"effect": "write",
"effect_source": "command-verb",
@@ -390453,20 +390460,20 @@
"review_reason": "人工审阅:结合本机 dws schema 实时 MCP description/parameters(经 interface_ref)、产品 Skill、Cobra Long 与 Runtime 确认门禁,按飞书风格重写选型文案;不改变命令身份、参数契约或接口绑定。",
"selected": true,
"source": "internal/cli/schema_hints/selection/drive.json",
"value": "修改文档空间中文档或文件的名称,并避免服务端保留原扩展名导致双扩展名"
"value": "安全重命名文档空间中的文档、文件或文件夹,并按节点真实扩展名避免双后缀"
}
],
"precedence": "reviewed_explicit",
"resolution": "highest_precedence",
"review_reason": "人工审阅:结合本机 dws schema 实时 MCP description/parameters(经 interface_ref)、产品 Skill、Cobra Long 与 Runtime 确认门禁,按飞书风格重写选型文案;不改变命令身份、参数契约或接口绑定。",
"source": "internal/cli/schema_hints/selection/drive.json",
"value": "修改文档空间中文档或文件的名称,并避免服务端保留原扩展名导致双扩展名"
"value": "安全重命名文档空间中的文档、文件或文件夹,并按节点真实扩展名避免双后缀"
},
"availability": {
"candidates": [
{
"precedence": "reviewed_explicit",
"review_reason": "The reviewed CLI normalizes one familiar extension from --name because rename_document preserves the node's existing extension.",
"review_reason": "The reviewed CLI reads node metadata before mutation and strips only the exact current extension for non-folder nodes, avoiding both duplicate extensions and dotted-folder corruption without a suffix whitelist.",
"selected": true,
"source": "internal/cli/schema_hints/metadata/drive.json",
"value": "available"
@@ -390480,7 +390487,7 @@
],
"precedence": "reviewed_explicit",
"resolution": "highest_precedence",
"review_reason": "The reviewed CLI normalizes one familiar extension from --name because rename_document preserves the node's existing extension.",
"review_reason": "The reviewed CLI reads node metadata before mutation and strips only the exact current extension for non-folder nodes, avoiding both duplicate extensions and dotted-folder corruption without a suffix whitelist.",
"source": "internal/cli/schema_hints/metadata/drive.json",
"value": "available"
},
@@ -390493,7 +390500,8 @@
"source": "internal/cli/schema_hints/selection/drive.json",
"value": [
"要改正文里的标题/章节 H1 改用 dws doc block update,不要用 rename",
"只要复制或移动改用 copy/move"
"只要复制或移动改用 copy/move",
"dry-run 不读取节点元数据,输出名称尚未做基于当前扩展名的规范化"
]
}
],
@@ -390503,7 +390511,8 @@
"source": "internal/cli/schema_hints/selection/drive.json",
"value": [
"要改正文里的标题/章节 H1 改用 dws doc block update,不要用 rename",
"只要复制或移动改用 copy/move"
"只要复制或移动改用 copy/move",
"dry-run 不读取节点元数据,输出名称尚未做基于当前扩展名的规范化"
]
},
"canonical_path": {
@@ -390517,7 +390526,7 @@
},
{
"precedence": "reviewed_manual",
"review_reason": "The reviewed CLI normalizes one familiar extension from --name because rename_document preserves the node's existing extension.",
"review_reason": "The reviewed CLI reads node metadata before mutation and strips only the exact current extension for non-folder nodes, avoiding both duplicate extensions and dotted-folder corruption without a suffix whitelist.",
"selected": false,
"source": "reviewed_manual_hint",
"source_ref": "drive rename",
@@ -390550,7 +390559,7 @@
"precedence": "cobra_help",
"selected": true,
"source": "cobra_help",
"value": "修改文档空间中文档或文件的名称。服务端会保留节点的原扩展名;\n如果 --name 带常见文件扩展名,CLI 会自动去掉一层,避免出现 report.txt.txt。\n\n权限要求: 对文档有\"编辑\"权限。"
"value": "修改文档空间中文档、文件或文件夹的名称。\n实际执行前会读取节点类型和当前扩展名:文件的新名称仅在末尾扩展名与当前扩展名一致时去掉一层,\n避免 report.txt.txt;文件夹和扩展名不匹配的名称保持不变。dry-run 不读取远端元数据,因此保留输入名称。\n\n权限要求: 对文档有\"编辑\"权限。"
},
{
"precedence": "mcp_metadata",
@@ -390562,7 +390571,7 @@
"precedence": "cobra_help",
"resolution": "highest_precedence",
"source": "cobra_help",
"value": "修改文档空间中文档或文件的名称。服务端会保留节点的原扩展名;\n如果 --name 带常见文件扩展名,CLI 会自动去掉一层,避免出现 report.txt.txt。\n\n权限要求: 对文档有\"编辑\"权限。"
"value": "修改文档空间中文档、文件或文件夹的名称。\n实际执行前会读取节点类型和当前扩展名:文件的新名称仅在末尾扩展名与当前扩展名一致时去掉一层,\n避免 report.txt.txt;文件夹和扩展名不匹配的名称保持不变。dry-run 不读取远端元数据,因此保留输入名称。\n\n权限要求: 对文档有\"编辑\"权限。"
},
"effect": {
"candidates": [
@@ -390616,7 +390625,7 @@
"candidates": [
{
"precedence": "reviewed_explicit",
"review_reason": "The reviewed CLI normalizes one familiar extension from --name because rename_document preserves the node's existing extension.",
"review_reason": "The reviewed CLI reads node metadata before mutation and strips only the exact current extension for non-folder nodes, avoiding both duplicate extensions and dotted-folder corruption without a suffix whitelist.",
"selected": true,
"source": "internal/cli/schema_hints/metadata/drive.json",
"value": "mcp"
@@ -390630,7 +390639,7 @@
],
"precedence": "reviewed_explicit",
"resolution": "highest_precedence",
"review_reason": "The reviewed CLI normalizes one familiar extension from --name because rename_document preserves the node's existing extension.",
"review_reason": "The reviewed CLI reads node metadata before mutation and strips only the exact current extension for non-folder nodes, avoiding both duplicate extensions and dotted-folder corruption without a suffix whitelist.",
"source": "internal/cli/schema_hints/metadata/drive.json",
"value": "mcp"
},
@@ -390638,7 +390647,7 @@
"candidates": [
{
"precedence": "reviewed_explicit",
"review_reason": "The reviewed CLI normalizes one familiar extension from --name because rename_document preserves the node's existing extension.",
"review_reason": "The reviewed CLI reads node metadata before mutation and strips only the exact current extension for non-folder nodes, avoiding both duplicate extensions and dotted-folder corruption without a suffix whitelist.",
"selected": true,
"source": "internal/cli/schema_hints/metadata/drive.json",
"value": {
@@ -390655,7 +390664,7 @@
],
"precedence": "reviewed_explicit",
"resolution": "highest_precedence",
"review_reason": "The reviewed CLI normalizes one familiar extension from --name because rename_document preserves the node's existing extension.",
"review_reason": "The reviewed CLI reads node metadata before mutation and strips only the exact current extension for non-folder nodes, avoiding both duplicate extensions and dotted-folder corruption without a suffix whitelist.",
"source": "internal/cli/schema_hints/metadata/drive.json",
"value": {
"product_id": "doc",
@@ -390680,7 +390689,7 @@
"candidates": [
{
"precedence": "reviewed_explicit",
"review_reason": "The reviewed CLI normalizes one familiar extension from --name because rename_document preserves the node's existing extension.",
"review_reason": "The reviewed CLI reads node metadata before mutation and strips only the exact current extension for non-folder nodes, avoiding both duplicate extensions and dotted-folder corruption without a suffix whitelist.",
"selected": true,
"source": "internal/cli/schema_hints/metadata/drive.json",
"value": true
@@ -390695,7 +390704,7 @@
],
"precedence": "reviewed_explicit",
"resolution": "highest_precedence",
"review_reason": "The reviewed CLI normalizes one familiar extension from --name because rename_document preserves the node's existing extension.",
"review_reason": "The reviewed CLI reads node metadata before mutation and strips only the exact current extension for non-folder nodes, avoiding both duplicate extensions and dotted-folder corruption without a suffix whitelist.",
"source": "internal/cli/schema_hints/metadata/drive.json",
"value": true
},
@@ -390741,7 +390750,7 @@
"selected": true,
"source": "internal/cli/schema_hints/selection/drive.json",
"value": [
"用户要重命名钉盘/文档空间中的文件或文档显示名称时;--name 带常见扩展名也可,CLI 会自动去掉一层"
"用户要重命名钉盘/文档空间中的文件、文档或文件夹时;实际执行会读取节点类型和当前扩展名,仅去掉完全匹配的一层后缀"
]
}
],
@@ -390750,7 +390759,7 @@
"review_reason": "人工审阅:结合本机 dws schema 实时 MCP description/parameters(经 interface_ref)、产品 Skill、Cobra Long 与 Runtime 确认门禁,按飞书风格重写选型文案;不改变命令身份、参数契约或接口绑定。",
"source": "internal/cli/schema_hints/selection/drive.json",
"value": [
"用户要重命名钉盘/文档空间中的文件或文档显示名称时;--name 带常见扩展名也可,CLI 会自动去掉一层"
"用户要重命名钉盘/文档空间中的文件、文档或文件夹时;实际执行会读取节点类型和当前扩展名,仅去掉完全匹配的一层后缀"
]
}
},
@@ -390767,22 +390776,22 @@
"parameter_count": 2,
"parameters": {
"name": {
"description": "新显示名称;服务端保留节点原扩展名,若传入常见文件扩展名,CLI 会自动去掉一层以避免双扩展名",
"description": "新显示名称;实际执行前读取节点类型与当前扩展名,仅对非文件夹且末尾后缀与当前扩展名一致的名称去掉一层,避免双扩展名",
"field_provenance": {
"description": {
"candidates": [
{
"precedence": "reviewed_manual",
"review_reason": "The reviewed CLI normalizes one familiar extension from --name because rename_document preserves the node's existing extension.",
"review_reason": "The reviewed CLI reads node metadata before mutation and strips only the exact current extension for non-folder nodes, avoiding both duplicate extensions and dotted-folder corruption without a suffix whitelist.",
"selected": true,
"source": "reviewed_manual_hint",
"value": "新显示名称;服务端保留节点原扩展名,若传入常见文件扩展名,CLI 会自动去掉一层以避免双扩展名"
"value": "新显示名称;实际执行前读取节点类型与当前扩展名,仅对非文件夹且末尾后缀与当前扩展名一致的名称去掉一层,避免双扩展名"
},
{
"precedence": "cobra_contract",
"selected": false,
"source": "cobra_usage",
"value": "新名称 (必填;服务端保留原扩展名,传入常见扩展名时 CLI 会自动去掉一层)"
"value": "新名称 (必填;实际执行时仅去掉与节点当前扩展名完全匹配的一层后缀)"
},
{
"precedence": "mcp_metadata",
@@ -390799,9 +390808,9 @@
],
"precedence": "reviewed_manual",
"resolution": "highest_precedence",
"review_reason": "The reviewed CLI normalizes one familiar extension from --name because rename_document preserves the node's existing extension.",
"review_reason": "The reviewed CLI reads node metadata before mutation and strips only the exact current extension for non-folder nodes, avoiding both duplicate extensions and dotted-folder corruption without a suffix whitelist.",
"source": "reviewed_manual_hint",
"value": "新显示名称;服务端保留节点原扩展名,若传入常见文件扩展名,CLI 会自动去掉一层以避免双扩展名"
"value": "新显示名称;实际执行前读取节点类型与当前扩展名,仅对非文件夹且末尾后缀与当前扩展名一致的名称去掉一层,避免双扩展名"
},
"property": {
"candidates": [
@@ -391001,7 +391010,7 @@
"source": "reviewed_command_registry",
"title": "重命名文件/文档",
"use_when": [
"用户要重命名钉盘/文档空间中的文件或文档显示名称时;--name 带常见扩展名也可,CLI 会自动去掉一层"
"用户要重命名钉盘/文档空间中的文件、文档或文件夹时;实际执行会读取节点类型和当前扩展名,仅去掉完全匹配的一层后缀"
]
},
"drive.search_files": {
@@ -566362,7 +566371,7 @@
"cli_name": "reset-reminder",
"cli_path": "todo task reset-reminder",
"confirmation": "not_required",
"description": "整体替换待办提醒规则;不传 --reminder-rules 时清除提醒。\n\n当前上游没有提醒规则查询接口,task get/list 也不返回 reminderRules;\n成功响应是写入回执,不代表 CLI 能再次读取并核验最终规则。",
"description": "整体替换待办提醒规则;不传 --reminder-rules 时清除提醒。显式传值必须是合法 JSON 数组,\n每条规则必须按 baseTime 提供 dueDateOffset 或 reminderTimeStamp;非法输入会在远端调用前失败。\n\n当前上游没有提醒规则查询接口,task get/list 也不返回 reminderRules;\n成功响应是写入回执,不代表 CLI 能再次读取并核验最终规则。",
"display": "待办任务管理",
"effect": "write",
"effect_source": "command-verb",
@@ -566391,7 +566400,7 @@
"candidates": [
{
"precedence": "reviewed_explicit",
"review_reason": "dws-tool-metadata/todo marks this tool as reviewed",
"review_reason": "The reviewed CLI rejects malformed or structurally invalid non-empty reminder rules before the reset mutation, while preserving omission and [] as explicit clear forms.",
"selected": true,
"source": "internal/cli/schema_hints/metadata/todo.json",
"value": "available"
@@ -566405,7 +566414,7 @@
],
"precedence": "reviewed_explicit",
"resolution": "highest_precedence",
"review_reason": "dws-tool-metadata/todo marks this tool as reviewed",
"review_reason": "The reviewed CLI rejects malformed or structurally invalid non-empty reminder rules before the reset mutation, while preserving omission and [] as explicit clear forms.",
"source": "internal/cli/schema_hints/metadata/todo.json",
"value": "available"
},
@@ -566441,6 +566450,14 @@
"source": "reviewed_command_registry",
"source_ref": "todo task reset-reminder",
"value": "todo.reset_todo_reminder"
},
{
"precedence": "reviewed_manual",
"review_reason": "The reviewed CLI rejects malformed or structurally invalid non-empty reminder rules before the reset mutation, while preserving omission and [] as explicit clear forms.",
"selected": false,
"source": "reviewed_manual_hint",
"source_ref": "todo task reset-reminder",
"value": "todo.reset_todo_reminder"
}
],
"precedence": "command_registry",
@@ -566469,7 +566486,7 @@
"precedence": "cobra_help",
"selected": true,
"source": "cobra_help",
"value": "整体替换待办提醒规则;不传 --reminder-rules 时清除提醒。\n\n当前上游没有提醒规则查询接口,task get/list 也不返回 reminderRules;\n成功响应是写入回执,不代表 CLI 能再次读取并核验最终规则。"
"value": "整体替换待办提醒规则;不传 --reminder-rules 时清除提醒。显式传值必须是合法 JSON 数组,\n每条规则必须按 baseTime 提供 dueDateOffset 或 reminderTimeStamp;非法输入会在远端调用前失败。\n\n当前上游没有提醒规则查询接口,task get/list 也不返回 reminderRules;\n成功响应是写入回执,不代表 CLI 能再次读取并核验最终规则。"
},
{
"precedence": "mcp_metadata",
@@ -566481,7 +566498,7 @@
"precedence": "cobra_help",
"resolution": "highest_precedence",
"source": "cobra_help",
"value": "整体替换待办提醒规则;不传 --reminder-rules 时清除提醒。\n\n当前上游没有提醒规则查询接口,task get/list 也不返回 reminderRules;\n成功响应是写入回执,不代表 CLI 能再次读取并核验最终规则。"
"value": "整体替换待办提醒规则;不传 --reminder-rules 时清除提醒。显式传值必须是合法 JSON 数组,\n每条规则必须按 baseTime 提供 dueDateOffset 或 reminderTimeStamp;非法输入会在远端调用前失败。\n\n当前上游没有提醒规则查询接口,task get/list 也不返回 reminderRules;\n成功响应是写入回执,不代表 CLI 能再次读取并核验最终规则。"
},
"effect": {
"candidates": [
@@ -566537,7 +566554,7 @@
"candidates": [
{
"precedence": "reviewed_explicit",
"review_reason": "dws-tool-metadata/todo marks this tool as reviewed",
"review_reason": "The reviewed CLI rejects malformed or structurally invalid non-empty reminder rules before the reset mutation, while preserving omission and [] as explicit clear forms.",
"selected": true,
"source": "internal/cli/schema_hints/metadata/todo.json",
"value": "mcp"
@@ -566551,7 +566568,7 @@
],
"precedence": "reviewed_explicit",
"resolution": "highest_precedence",
"review_reason": "dws-tool-metadata/todo marks this tool as reviewed",
"review_reason": "The reviewed CLI rejects malformed or structurally invalid non-empty reminder rules before the reset mutation, while preserving omission and [] as explicit clear forms.",
"source": "internal/cli/schema_hints/metadata/todo.json",
"value": "mcp"
},
@@ -566593,7 +566610,7 @@
"candidates": [
{
"precedence": "reviewed_explicit",
"review_reason": "dws-tool-metadata/todo marks this tool as reviewed",
"review_reason": "The reviewed CLI rejects malformed or structurally invalid non-empty reminder rules before the reset mutation, while preserving omission and [] as explicit clear forms.",
"selected": true,
"source": "internal/cli/schema_hints/metadata/todo.json",
"value": true
@@ -566608,7 +566625,7 @@
],
"precedence": "reviewed_explicit",
"resolution": "highest_precedence",
"review_reason": "dws-tool-metadata/todo marks this tool as reviewed",
"review_reason": "The reviewed CLI rejects malformed or structurally invalid non-empty reminder rules before the reset mutation, while preserving omission and [] as explicit clear forms.",
"source": "internal/cli/schema_hints/metadata/todo.json",
"value": true
},
@@ -566654,7 +566671,7 @@
"selected": true,
"source": "internal/cli/schema_hints/selection/todo.json",
"value": [
"需要清除或整体替换待办提醒规则时(可不传 reminder-rules 以清除);成功响应仅作为写入回执"
"需要清除或整体替换待办提醒规则时(不传 reminder-rules 或传 [] 可清除);非空规则会在远端调用前严格校验,成功响应仅作为写入回执"
]
}
],
@@ -566663,7 +566680,7 @@
"review_reason": "结合本机 dws schema 实时描述、Skill 与 Cobra 手写选型;pinned schema_mcp_metadata 仅对照。",
"source": "internal/cli/schema_hints/selection/todo.json",
"value": [
"需要清除或整体替换待办提醒规则时(可不传 reminder-rules 以清除);成功响应仅作为写入回执"
"需要清除或整体替换待办提醒规则时(不传 reminder-rules 或传 [] 可清除);非空规则会在远端调用前严格校验,成功响应仅作为写入回执"
]
}
},
@@ -566681,15 +566698,22 @@
"parameter_count": 2,
"parameters": {
"reminder-rules": {
"description": "提醒规则 JSON 数组 (可选,为空则清除提醒)",
"description": "提醒规则 JSON 数组;不传表示清除,显式传值必须为对象数组,且每条按 baseTime 提供整数 dueDateOffset 或 ISO8601 reminderTimeStamp",
"field_provenance": {
"description": {
"candidates": [
{
"precedence": "cobra_contract",
"precedence": "reviewed_manual",
"review_reason": "The reviewed CLI rejects malformed or structurally invalid non-empty reminder rules before the reset mutation, while preserving omission and [] as explicit clear forms.",
"selected": true,
"source": "reviewed_manual_hint",
"value": "提醒规则 JSON 数组;不传表示清除,显式传值必须为对象数组,且每条按 baseTime 提供整数 dueDateOffset 或 ISO8601 reminderTimeStamp"
},
{
"precedence": "cobra_contract",
"selected": false,
"source": "cobra_usage",
"value": "提醒规则 JSON 数组 (可选,为空则清除提醒)"
"value": "提醒规则 JSON 数组 (不传则清除;显式传值必须合法)"
},
{
"precedence": "default",
@@ -566698,10 +566722,11 @@
"value": ""
}
],
"precedence": "cobra_contract",
"precedence": "reviewed_manual",
"resolution": "highest_precedence",
"source": "cobra_usage",
"value": "提醒规则 JSON 数组 (可选,为空则清除提醒)"
"review_reason": "The reviewed CLI rejects malformed or structurally invalid non-empty reminder rules before the reset mutation, while preserving omission and [] as explicit clear forms.",
"source": "reviewed_manual_hint",
"value": "提醒规则 JSON 数组;不传表示清除,显式传值必须为对象数组,且每条按 baseTime 提供整数 dueDateOffset 或 ISO8601 reminderTimeStamp"
},
"property": {
"candidates": [
@@ -566875,7 +566900,7 @@
"source": "reviewed_command_registry",
"title": "重置待办提醒",
"use_when": [
"需要清除或整体替换待办提醒规则时(可不传 reminder-rules 以清除);成功响应仅作为写入回执"
"需要清除或整体替换待办提醒规则时(不传 reminder-rules 或传 [] 可清除);非空规则会在远端调用前严格校验,成功响应仅作为写入回执"
]
},
"todo.shortcut_assign": {
@@ -571429,11 +571454,12 @@
"internal/cli/schema_hints/metadata/todo.json",
"internal/cli/schema_hints/selection/todo.json"
],
"agent_summary": "给自己创建一条带截止/提醒时间的待办",
"agent_summary": "给自己创建一条带可选截止时间的待办",
"agent_summary_source": "dws-agent-selection/todo",
"availability": "available",
"avoid_when": [
"需要该 Shortcut 未公开的底层参数、原始响应或不同执行语义时,改用对应原子命令"
"需要该 Shortcut 未公开的底层参数、原始响应或不同执行语义时,改用对应原子命令",
"用户需要独立提醒规则时改用 dws todo task add-reminder;不要把 --at 解释成提醒时间"
],
"canonical_path": "todo.shortcut_remind",
"cli_name": "+remind",
@@ -571454,14 +571480,14 @@
"review_reason": "Agent-authored and reviewed from the built-in Shortcut intent, executable Cobra path, and declared outcome; it affects selection only and does not alter execution or safety facts.",
"selected": true,
"source": "internal/cli/schema_hints/selection/todo.json",
"value": "给自己创建一条带截止/提醒时间的待办"
"value": "给自己创建一条带可选截止时间的待办"
}
],
"precedence": "reviewed_explicit",
"resolution": "highest_precedence",
"review_reason": "Agent-authored and reviewed from the built-in Shortcut intent, executable Cobra path, and declared outcome; it affects selection only and does not alter execution or safety facts.",
"source": "internal/cli/schema_hints/selection/todo.json",
"value": "给自己创建一条带截止/提醒时间的待办"
"value": "给自己创建一条带可选截止时间的待办"
},
"availability": {
"candidates": [
@@ -571487,7 +571513,8 @@
"selected": true,
"source": "internal/cli/schema_hints/selection/todo.json",
"value": [
"需要该 Shortcut 未公开的底层参数、原始响应或不同执行语义时,改用对应原子命令"
"需要该 Shortcut 未公开的底层参数、原始响应或不同执行语义时,改用对应原子命令",
"用户需要独立提醒规则时改用 dws todo task add-reminder;不要把 --at 解释成提醒时间"
]
}
],
@@ -571496,7 +571523,8 @@
"review_reason": "Agent-authored and reviewed from the built-in Shortcut intent, executable Cobra path, and declared outcome; it affects selection only and does not alter execution or safety facts.",
"source": "internal/cli/schema_hints/selection/todo.json",
"value": [
"需要该 Shortcut 未公开的底层参数、原始响应或不同执行语义时,改用对应原子命令"
"需要该 Shortcut 未公开的底层参数、原始响应或不同执行语义时,改用对应原子命令",
"用户需要独立提醒规则时改用 dws todo task add-reminder;不要把 --at 解释成提醒时间"
]
},
"canonical_path": {
@@ -571706,7 +571734,7 @@
"selected": true,
"source": "internal/cli/schema_hints/selection/todo.json",
"value": [
"当你想给自己记一件事、并(可选)设一个截止/提醒时间,又不想先查自己的 userId 时使用;内部先解析当前登录用户的 userId,再显式设置 executorIds,--at 会按 ISO8601 解析为截止时间。会真实创建待办。"
"当你想给自己记一件事、并(可选)设一个截止时间,又不想先查自己的 userId 时使用;内部先解析当前登录用户的 userId,再显式设置 executorIds,--at 只会按 ISO8601 写入截止时间 dueTime,不会创建独立提醒规则。会真实创建待办。"
]
}
],
@@ -571715,7 +571743,7 @@
"review_reason": "Agent-authored and reviewed from the built-in Shortcut intent, executable Cobra path, and declared outcome; it affects selection only and does not alter execution or safety facts.",
"source": "internal/cli/schema_hints/selection/todo.json",
"value": [
"当你想给自己记一件事、并(可选)设一个截止/提醒时间,又不想先查自己的 userId 时使用;内部先解析当前登录用户的 userId,再显式设置 executorIds,--at 会按 ISO8601 解析为截止时间。会真实创建待办。"
"当你想给自己记一件事、并(可选)设一个截止时间,又不想先查自己的 userId 时使用;内部先解析当前登录用户的 userId,再显式设置 executorIds,--at 只会按 ISO8601 写入截止时间 dueTime,不会创建独立提醒规则。会真实创建待办。"
]
}
},
@@ -571916,7 +571944,7 @@
"source": "reviewed_command_registry",
"title": "给自己创建一条带可选截止时间的待办",
"use_when": [
"当你想给自己记一件事、并(可选)设一个截止/提醒时间,又不想先查自己的 userId 时使用;内部先解析当前登录用户的 userId,再显式设置 executorIds,--at 会按 ISO8601 解析为截止时间。会真实创建待办。"
"当你想给自己记一件事、并(可选)设一个截止时间,又不想先查自己的 userId 时使用;内部先解析当前登录用户的 userId,再显式设置 executorIds,--at 只会按 ISO8601 写入截止时间 dueTime,不会创建独立提醒规则。会真实创建待办。"
]
},
"todo.shortcut_todo_done": {
@@ -206,10 +206,10 @@
"reviewed": true,
"parameters": {
"name": {
"description": "新显示名称;服务端保留节点原扩展名,若传入常见文件扩展名,CLI 会自动去掉一层以避免双扩展名"
"description": "新显示名称;实际执行前读取节点类型与当前扩展名,仅对非文件夹且末尾后缀与当前扩展名一致的名称去掉一层,避免双扩展名"
}
},
"review_reason": "The reviewed CLI normalizes one familiar extension from --name because rename_document preserves the node's existing extension.",
"review_reason": "The reviewed CLI reads node metadata before mutation and strips only the exact current extension for non-folder nodes, avoiding both duplicate extensions and dotted-folder corruption without a suffix whitelist.",
"cli_path": "drive rename"
},
"drive.search_files": {
+8 -1
View File
@@ -126,7 +126,14 @@
"todo.reset_todo_reminder": {
"interface_mode": "mcp",
"availability": "available",
"reviewed": true
"reviewed": true,
"parameters": {
"reminder-rules": {
"description": "提醒规则 JSON 数组;不传表示清除,显式传值必须为对象数组,且每条按 baseTime 提供整数 dueDateOffset 或 ISO8601 reminderTimeStamp"
}
},
"review_reason": "The reviewed CLI rejects malformed or structurally invalid non-empty reminder rules before the reset mutation, while preserving omission and [] as explicit clear forms.",
"cli_path": "todo task reset-reminder"
},
"todo.update_todo_done_status": {
"interface_mode": "mcp",
+3 -2
View File
@@ -582,11 +582,12 @@
"doc.rename_document": {
"agent_summary": "兼容入口:重命名文档或文件;文件重命名能力已迁移到 drive。",
"use_when": [
"用户要改文档/文件在列表与链接中展示的名称时;即使名称带常见扩展名,CLI 也会去掉一层再交给保留原扩展名的服务端"
"用户要改在线文档在列表与链接中展示的名称时"
],
"avoid_when": [
"改正文标题/章节标题改用 doc block update",
"不要用 update 或重新 create 来改名"
"不要用 update 或重新 create 来改名",
"文件或文件夹重命名优先用 dws drive rename,由该命令读取真实节点类型和当前扩展名"
],
"examples": [
"dws doc rename --node <DOC_ID> --name \"新名称\" --format json"
@@ -545,13 +545,14 @@
]
},
"drive.rename_document": {
"agent_summary": "修改文档空间中文档或文件的名称,并避免服务端保留原扩展名导致双扩展名",
"agent_summary": "安全重命名文档空间中的文档、文件或文件夹,并按节点真实扩展名避免双后缀",
"use_when": [
"用户要重命名钉盘/文档空间中的文件或文档显示名称时;--name 带常见扩展名也可,CLI 会自动去掉一层"
"用户要重命名钉盘/文档空间中的文件、文档或文件夹时;实际执行会读取节点类型和当前扩展名,仅去掉完全匹配的一层后缀"
],
"avoid_when": [
"要改正文里的标题/章节 H1 改用 dws doc block update,不要用 rename",
"只要复制或移动改用 copy/move"
"只要复制或移动改用 copy/move",
"dry-run 不读取节点元数据,输出名称尚未做基于当前扩展名的规范化"
],
"examples": [
"dws drive rename --node <ID> --name \"新名称\" --format json"
@@ -360,7 +360,7 @@
"todo.reset_todo_reminder": {
"agent_summary": "整体替换或清除待办提醒规则(上游不支持规则读回)",
"use_when": [
"需要清除或整体替换待办提醒规则时(可不传 reminder-rules 以清除);成功响应仅作为写入回执"
"需要清除或整体替换待办提醒规则时(不传 reminder-rules 或传 [] 可清除);非空规则会在远端调用前严格校验,成功响应仅作为写入回执"
],
"avoid_when": [
"只需追加一条提醒时改用 dws todo task add-reminder",
@@ -606,12 +606,13 @@
]
},
"todo.shortcut_remind": {
"agent_summary": "给自己创建一条带截止/提醒时间的待办",
"agent_summary": "给自己创建一条带可选截止时间的待办",
"use_when": [
"当你想给自己记一件事、并(可选)设一个截止/提醒时间,又不想先查自己的 userId 时使用;内部先解析当前登录用户的 userId,再显式设置 executorIds,--at 会按 ISO8601 解析为截止时间。会真实创建待办。"
"当你想给自己记一件事、并(可选)设一个截止时间,又不想先查自己的 userId 时使用;内部先解析当前登录用户的 userId,再显式设置 executorIds,--at 只会按 ISO8601 写入截止时间 dueTime,不会创建独立提醒规则。会真实创建待办。"
],
"avoid_when": [
"需要该 Shortcut 未公开的底层参数、原始响应或不同执行语义时,改用对应原子命令"
"需要该 Shortcut 未公开的底层参数、原始响应或不同执行语义时,改用对应原子命令",
"用户需要独立提醒规则时改用 dws todo task add-reminder;不要把 --at 解释成提醒时间"
],
"examples": [
"dws todo +remind --task \"交周报\" --at 2026-03-10T18:00:00+08:00"
-26
View File
@@ -14,9 +14,6 @@
package helpers
import (
"path/filepath"
"strings"
"github.com/DingTalk-Real-AI/dingtalk-workspace-cli/internal/cli"
"github.com/DingTalk-Real-AI/dingtalk-workspace-cli/internal/output"
"github.com/spf13/cobra"
@@ -52,26 +49,3 @@ func commandBoolFlag(cmd *cobra.Command, name string) bool {
}
return false
}
// renameBaseName removes one familiar file extension before calling
// rename_document. That service preserves the node's current extension, so
// forwarding "report.txt" for a .txt file would otherwise produce
// "report.txt.txt". Unknown dotted suffixes are preserved because they may be
// part of a display name (for example "release.v2").
func renameBaseName(name string) string {
trimmed := strings.TrimSpace(name)
extension := strings.ToLower(strings.TrimPrefix(filepath.Ext(trimmed), "."))
if _, ok := renamePreservedExtensions[extension]; !ok {
return trimmed
}
return strings.TrimSuffix(trimmed, filepath.Ext(trimmed))
}
var renamePreservedExtensions = map[string]struct{}{
"7z": {}, "able": {}, "adoc": {}, "adraw": {}, "aform": {}, "amind": {},
"appt": {}, "awbd": {}, "axls": {}, "csv": {}, "doc": {}, "docx": {},
"gif": {}, "gz": {}, "jpeg": {}, "jpg": {}, "json": {}, "md": {},
"mp3": {}, "mp4": {}, "pdf": {}, "png": {}, "ppt": {}, "pptx": {},
"rar": {}, "svg": {}, "tar": {}, "txt": {}, "wav": {}, "webp": {},
"xls": {}, "xlsx": {}, "xml": {}, "zip": {},
}
+169 -24
View File
@@ -6,6 +6,8 @@ package helpers
import (
"bytes"
"context"
"encoding/json"
"errors"
"io"
"strings"
"testing"
@@ -15,13 +17,23 @@ import (
)
type contractDefectCaller struct {
dryRun bool
calls []guardedMutationCall
dryRun bool
calls []guardedMutationCall
responses map[string]string
errors map[string]error
}
func (c *contractDefectCaller) CallTool(_ context.Context, productID, toolName string, args map[string]any) (*edition.ToolResult, error) {
c.calls = append(c.calls, guardedMutationCall{productID: productID, toolName: toolName, args: args})
return &edition.ToolResult{Content: []edition.ContentBlock{{Type: "text", Text: `{}`}}}, nil
key := productID + "/" + toolName
if err := c.errors[key]; err != nil {
return nil, err
}
text := c.responses[key]
if text == "" {
text = `{}`
}
return &edition.ToolResult{Content: []edition.ContentBlock{{Type: "text", Text: text}}}, nil
}
func (*contractDefectCaller) Format() string { return "json" }
@@ -85,42 +97,175 @@ func TestDocVersionRevertDryRunSkipsRemotePreflightAndEmitsPreview(t *testing.T)
}
}
func TestRenameDocumentRemovesOnePreservedExtension(t *testing.T) {
func TestDocRenamePreservesCallerProvidedDisplayName(t *testing.T) {
caller := &contractDefectCaller{}
if _, err := executeContractDefectCommand(t, caller, newDocCommand,
"rename", "--node", "node-1", "--name", "release.v2"); err != nil {
t.Fatalf("doc rename returned error: %v", err)
}
if len(caller.calls) != 1 {
t.Fatalf("tool calls = %#v, want one rename call", caller.calls)
}
if got := caller.calls[0].args["newName"]; got != "release.v2" {
t.Fatalf("newName = %#v, want release.v2", got)
}
}
func TestDriveRenameUsesNodeTypeAndCurrentExtension(t *testing.T) {
tests := []struct {
name string
build func() *cobra.Command
args []string
name string
metadata string
nodeID string
wantFileID string
requested string
wantNewName string
}{
{
name: "doc",
build: newDocCommand,
args: []string{"rename", "--node", "node-1", "--name", "report.txt"},
name: "file strips matching extension outside old whitelist",
metadata: `{"result":{"type":"file","extension":"heic"}}`,
nodeID: "https://alidocs.dingtalk.com/i/nodes/node-1",
wantFileID: "node-1",
requested: "photo.HEIC",
wantNewName: "photo",
},
{
name: "drive",
build: newDriveCommand,
args: []string{"rename", "--node", "node-1", "--name", "report.txt"},
name: "folder preserves dotted display name",
metadata: `{"result":{"type":"folder","extension":"v2"}}`,
requested: "release.v2",
wantNewName: "release.v2",
},
{
name: "different current extension is preserved",
metadata: `{"result":{"nodeType":"file","fileExtension":"txt"}}`,
requested: "report.final",
wantNewName: "report.final",
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
caller := &contractDefectCaller{}
if _, err := executeContractDefectCommand(t, caller, test.build, test.args...); err != nil {
t.Fatalf("rename returned error: %v", err)
caller := &contractDefectCaller{responses: map[string]string{
"drive/get_file_info": test.metadata,
}}
nodeID := test.nodeID
if nodeID == "" {
nodeID = "node-1"
}
if len(caller.calls) != 1 {
t.Fatalf("tool calls = %#v, want one rename call", caller.calls)
if _, err := executeContractDefectCommand(t, caller, newDriveCommand,
"rename", "--node", nodeID, "--name", test.requested); err != nil {
t.Fatalf("drive rename returned error: %v", err)
}
if got := caller.calls[0].args["newName"]; got != "report" {
t.Fatalf("newName = %#v, want report", got)
if len(caller.calls) != 2 {
t.Fatalf("tool calls = %#v, want metadata read plus rename", caller.calls)
}
if caller.calls[0].productID != "drive" || caller.calls[0].toolName != "get_file_info" {
t.Fatalf("first call = %#v, want drive/get_file_info", caller.calls[0])
}
wantFileID := test.wantFileID
if wantFileID == "" {
wantFileID = nodeID
}
if got := caller.calls[0].args["fileId"]; got != wantFileID {
t.Fatalf("metadata fileId = %#v, want %q", got, wantFileID)
}
if caller.calls[1].productID != "doc" || caller.calls[1].toolName != "rename_document" {
t.Fatalf("second call = %#v, want doc/rename_document", caller.calls[1])
}
if got := caller.calls[1].args["newName"]; got != test.wantNewName {
t.Fatalf("newName = %#v, want %q", got, test.wantNewName)
}
})
}
}
if got := renameBaseName("release.v2"); got != "release.v2" {
t.Fatalf("unknown dotted suffix = %q, want release.v2", got)
func TestDriveRenameMetadataFailuresDoNotMutate(t *testing.T) {
tests := []struct {
name string
responses map[string]string
errors map[string]error
}{
{
name: "metadata call error",
errors: map[string]error{"drive/get_file_info": errors.New("metadata unavailable")},
},
{
name: "invalid metadata JSON",
responses: map[string]string{"drive/get_file_info": `{`},
},
{
name: "metadata result missing",
responses: map[string]string{"drive/get_file_info": `{}`},
},
}
if got := renameBaseName("REPORT.XLSX"); got != "REPORT" {
t.Fatalf("case-insensitive extension normalization = %q, want REPORT", got)
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
caller := &contractDefectCaller{responses: test.responses, errors: test.errors}
_, err := executeContractDefectCommand(t, caller, newDriveCommand,
"rename", "--node", "node-1", "--name", "report.txt")
if err == nil {
t.Fatal("drive rename returned nil error")
}
if len(caller.calls) != 1 || caller.calls[0].toolName != "get_file_info" {
t.Fatalf("tool calls = %#v, want metadata read only", caller.calls)
}
})
}
}
func TestDriveRenameDryRunDefersMetadataNormalization(t *testing.T) {
caller := &contractDefectCaller{dryRun: true}
output, err := executeContractDefectCommand(t, caller, newDriveCommand,
"rename", "--node", "node-1", "--name", "photo.heic", "--dry-run")
if err != nil {
t.Fatalf("drive rename dry-run returned error: %v", err)
}
if len(caller.calls) != 0 {
t.Fatalf("dry-run tool calls = %#v, want none", caller.calls)
}
if !strings.Contains(output, `"newName": "photo.heic"`) {
t.Fatalf("dry-run output = %q, want unmodified input name", output)
}
}
func TestDriveRenameBaseNameConservativeEdges(t *testing.T) {
tests := []struct {
name string
nodeType string
extension string
want string
}{
{name: ".txt", nodeType: "file", extension: "txt", want: ".txt"},
{name: "report.txt", nodeType: "directory", extension: "txt", want: "report.txt"},
{name: "report.txt", nodeType: "dir", extension: "txt", want: "report.txt"},
{name: " report.txt ", nodeType: "file", extension: "", want: "report.txt"},
{name: "report.txt", nodeType: "file", extension: ".txt", want: "report"},
}
for _, test := range tests {
if got := driveRenameBaseName(test.name, test.nodeType, test.extension); got != test.want {
t.Fatalf("driveRenameBaseName(%q, %q, %q) = %q, want %q",
test.name, test.nodeType, test.extension, got, test.want)
}
}
}
func TestDriveInfoRestoresFileSizeWhenDocMetadataIsNull(t *testing.T) {
caller := &contractDefectCaller{responses: map[string]string{
"drive/get_file_info": `{"result":{"fileId":"node-1","extension":"adoc","fileSize":93682}}`,
"doc/get_document_info": `{"result":{"title":"Doc","fileSize":null}}`,
}}
output, err := executeContractDefectCommand(t, caller, newDriveCommand,
"info", "--node", "node-1")
if err != nil {
t.Fatalf("drive info returned error: %v", err)
}
var payload struct {
Result struct {
FileSize int64 `json:"fileSize"`
} `json:"result"`
}
if err := json.Unmarshal([]byte(output), &payload); err != nil {
t.Fatalf("decode drive info output %q: %v", output, err)
}
if payload.Result.FileSize != 93682 {
t.Fatalf("fileSize = %d, want 93682", payload.Result.FileSize)
}
}
+1 -1
View File
@@ -1548,7 +1548,7 @@ WARNING: --mode overwrite 为破坏性写入,会清空原文档全部内容。
}
return callMCPTool("rename_document", map[string]any{
"nodeId": nodeID,
"newName": renameBaseName(flagOrFallback(cmd, "name", "title")),
"newName": flagOrFallback(cmd, "name", "title"),
})
},
}
+60 -6
View File
@@ -19,6 +19,51 @@ import (
// get_upload_info, commit_upload
// ──────────────────────────────────────────────────────────
func driveRenameBaseName(name, nodeType, currentExtension string) string {
trimmed := strings.TrimSpace(name)
switch strings.ToLower(strings.TrimSpace(nodeType)) {
case "folder", "dir", "directory":
return trimmed
}
extension := strings.TrimLeft(strings.TrimSpace(currentExtension), ".")
if extension == "" {
return trimmed
}
suffix := "." + extension
if len(trimmed) <= len(suffix) || !strings.EqualFold(trimmed[len(trimmed)-len(suffix):], suffix) {
return trimmed
}
return trimmed[:len(trimmed)-len(suffix)]
}
func resolveDriveRenameName(ctx context.Context, nodeID, name string) (string, error) {
fileID := nodeID
if parsedNodeID := extractNodeIDFromDocURL(nodeID); parsedNodeID != "" {
fileID = parsedNodeID
}
text, err := callMCPToolReturnTextOnServer(ctx, "drive", "get_file_info", map[string]any{
"fileId": fileID,
})
if err != nil {
return "", fmt.Errorf("无法读取节点元数据,未执行重命名: %w", err)
}
var response map[string]any
if err := json.Unmarshal([]byte(text), &response); err != nil {
return "", fmt.Errorf("无法解析节点元数据,未执行重命名: %w", err)
}
result, ok := response["result"].(map[string]any)
if !ok {
return "", fmt.Errorf("节点元数据缺少 result,未执行重命名")
}
return driveRenameBaseName(
name,
firstStringField(result, "type", "nodeType", "fileType"),
firstStringField(result, "extension", "fileExtension", "ext"),
), nil
}
func runDriveUpload(cmd *cobra.Command, _ []string) error {
filePath := mustGetFlag(cmd, "file")
if filePath == "" {
@@ -834,8 +879,9 @@ func newDriveCommand() *cobra.Command {
driveRenameCmd := &cobra.Command{
Use: "rename",
Short: "重命名文件/文档",
Long: `修改文档空间中文档或文件的名称。服务端会保留节点的原扩展名;
如果 --name 带常见文件扩展名,CLI 会自动去掉一层,避免出现 report.txt.txt。
Long: `修改文档空间中文档、文件或文件夹的名称。
实际执行前会读取节点类型和当前扩展名:文件的新名称仅在末尾扩展名与当前扩展名一致时去掉一层,
避免 report.txt.txt;文件夹和扩展名不匹配的名称保持不变。dry-run 不读取远端元数据,因此保留输入名称。
权限要求: 对文档有"编辑"权限。`,
Example: ` dws drive rename --node DOC_ID --name "新名称"`,
@@ -847,14 +893,21 @@ func newDriveCommand() *cobra.Command {
if err != nil {
return err
}
newName := flagOrFallback(cmd, "name", "title")
if !commandDryRun(cmd) {
newName, err = resolveDriveRenameName(cmd.Context(), nodeID, newName)
if err != nil {
return err
}
}
return callMCPToolOnServer("doc", "rename_document", map[string]any{
"nodeId": nodeID,
"newName": renameBaseName(flagOrFallback(cmd, "name", "title")),
"newName": newName,
})
},
}
driveRenameCmd.Flags().String("node", "", "文档/文件 ID 或 URL (必填)")
driveRenameCmd.Flags().String("name", "", "新名称 (必填;服务端保留原扩展名,传入常见扩展名时 CLI 会自动去掉一层)")
driveRenameCmd.Flags().String("name", "", "新名称 (必填;实际执行时仅去掉与节点当前扩展名完全匹配的一层后缀)")
driveStatsCmd := &cobra.Command{
Use: "stats",
@@ -1384,7 +1437,7 @@ func driveInfoWithDocFallback(fileID string, driveArgs map[string]any) error {
ctx := context.Background()
// Step 1: 调用 drive get_file_info
driveText, err := callMCPToolReturnText(ctx, "get_file_info", driveArgs)
driveText, err := callMCPToolReturnTextOnServer(ctx, "drive", "get_file_info", driveArgs)
if err != nil {
return err
}
@@ -1445,7 +1498,8 @@ func driveInfoWithDocFallback(fileID string, driveArgs map[string]any) error {
driveOnlyFields := []string{"dentryId", "path", "fileSize", "extension", "type"}
for _, field := range driveOnlyFields {
if val, ok := driveResult[field]; ok {
if _, exists := docResult[field]; !exists {
current, exists := docResult[field]
if (!exists || current == nil) && val != nil {
docResult[field] = val
}
}
+80 -25
View File
@@ -481,7 +481,8 @@ reset-reminder 写入的提醒规则。提醒写命令的成功响应只能作
todoTaskUpdateReminderCmd := &cobra.Command{
Use: "reset-reminder",
Short: "重置待办提醒",
Long: `整体替换待办提醒规则;不传 --reminder-rules 时清除提醒。
Long: `整体替换待办提醒规则;不传 --reminder-rules 时清除提醒。显式传值必须是合法 JSON 数组,
每条规则必须按 baseTime 提供 dueDateOffset 或 reminderTimeStamp;非法输入会在远端调用前失败。
当前上游没有提醒规则查询接口,task get/list 也不返回 reminderRules;
成功响应是写入回执,不代表 CLI 能再次读取并核验最终规则。`,
@@ -493,28 +494,14 @@ reset-reminder 写入的提醒规则。提醒写命令的成功响应只能作
if err := validateRequiredFlags(cmd, "task-id"); err != nil {
return err
}
var reminderRules []any
if v, _ := cmd.Flags().GetString("reminder-rules"); v != "" {
var att []any
if err := json.Unmarshal([]byte(v), &att); err == nil && len(att) > 0 {
for i, item := range att {
m, ok := item.(map[string]any)
if !ok {
continue
}
if m["baseTime"] == "customTime" {
if ts, ok := m["reminderTimeStamp"].(string); ok && ts != "" {
ms, err := parseISOTimeToMillis("reminderTimeStamp", ts)
if err != nil {
return err
}
m["reminderTimeStamp"] = ms
att[i] = m
}
}
}
reminderRules = att
var reminderRules []map[string]any
if cmd.Flags().Changed("reminder-rules") {
value, _ := cmd.Flags().GetString("reminder-rules")
parsedRules, err := parseTodoReminderRules(value)
if err != nil {
return err
}
reminderRules = parsedRules
}
return callMCPTool("reset_todo_reminder", map[string]any{
"todoReminderUpdateRequest": map[string]any{
@@ -686,7 +673,7 @@ reset-reminder 写入的提醒规则。提醒写命令的成功响应只能作
todoTaskAddReminderCmd.Flags().String("reminder-time-stamp", "", "自定义提醒时间 ISO-8601 (如 2026-03-10T18:00:00+08:00,baseTime=customTime 时必填)")
todoTaskUpdateReminderCmd.Flags().String("task-id", "", "待办任务 ID (必填)")
todoTaskUpdateReminderCmd.Flags().String("reminder-rules", "", "提醒规则 JSON 数组 (可选,为空则清除提醒)")
todoTaskUpdateReminderCmd.Flags().String("reminder-rules", "", "提醒规则 JSON 数组 (不传则清除;显式传值必须合法)")
todoTaskListSubCmd.Flags().String("task-id", "", "待办任务 ID (必填)")
todoTaskListAttachmentCmd.Flags().String("task-id", "", "待办任务 ID (必填)")
todoTaskAddAttachment.Flags().String("task-id", "", "待办任务 ID (必填)")
@@ -934,13 +921,81 @@ func rejectUnsupportedTodoReminderFlags(cmd *cobra.Command) error {
return nil
}
return &CLIError{
Code: CodeMissingParam,
Code: CodeInvalidParam,
Message: fmt.Sprintf("todo 当前不支持独立 reminder 参数: %s", strings.Join(unsupported, ", ")),
Suggestion: "请先用 --due 设置截止时间;需要独立提醒时,再用 dws todo task add-reminder。当前上游不支持读取提醒规则,不能用 task get/list 验证写入结果。",
Suggestion: "需要截止时间请改用 --due;需要独立提醒请用 dws todo task add-reminder。dueTime 模式要求待办已有截止时间,customTime 模式直接传 --reminder-time-stamp,不必先设置 --due。当前上游不支持读取提醒规则,不能用 task get/list 验证写入结果。",
Operation: "todo.task.reminder",
}
}
func parseTodoReminderRules(raw string) ([]map[string]any, error) {
value := strings.TrimSpace(raw)
if value == "" {
return nil, invalidTodoReminderRules(CodeInvalidParam, "显式传入的 --reminder-rules 不能为空;清除提醒请省略该参数或传 []", nil)
}
if !json.Valid([]byte(value)) {
return nil, invalidTodoReminderRules(CodeInvalidJSON, "--reminder-rules 必须是合法 JSON 数组", nil)
}
var rules []map[string]any
if err := unmarshalJSONUseNumber(value, &rules); err != nil {
return nil, invalidTodoReminderRules(CodeInvalidParam, "--reminder-rules 必须是由对象组成的 JSON 数组", err)
}
if rules == nil {
return nil, invalidTodoReminderRules(CodeInvalidParam, "--reminder-rules 不能是 null;清除提醒请省略该参数或传 []", nil)
}
for i, rule := range rules {
position := i + 1
if rule == nil {
return nil, invalidTodoReminderRules(CodeInvalidParam, fmt.Sprintf("--reminder-rules 第 %d 条必须是对象", position), nil)
}
baseTime, ok := rule["baseTime"].(string)
baseTime = strings.TrimSpace(baseTime)
if !ok || baseTime == "" {
return nil, invalidTodoReminderRules(CodeInvalidParam, fmt.Sprintf("--reminder-rules 第 %d 条缺少字符串 baseTime", position), nil)
}
rule["baseTime"] = baseTime
switch baseTime {
case "dueTime":
offset, ok := rule["dueDateOffset"].(json.Number)
if !ok {
return nil, invalidTodoReminderRules(CodeInvalidParam, fmt.Sprintf("--reminder-rules 第 %d 条在 baseTime=dueTime 时必须提供整数 dueDateOffset", position), nil)
}
parsed, err := strconv.ParseInt(offset.String(), 10, 64)
if err != nil {
return nil, invalidTodoReminderRules(CodeInvalidParam, fmt.Sprintf("--reminder-rules 第 %d 条的 dueDateOffset 必须是整数", position), err)
}
rule["dueDateOffset"] = parsed
case "customTime":
timestamp, ok := rule["reminderTimeStamp"].(string)
timestamp = strings.TrimSpace(timestamp)
if !ok || timestamp == "" {
return nil, invalidTodoReminderRules(CodeInvalidParam, fmt.Sprintf("--reminder-rules 第 %d 条在 baseTime=customTime 时必须提供 ISO8601 字符串 reminderTimeStamp", position), nil)
}
parsed, err := parseISOTimeToMillis("reminderTimeStamp", timestamp)
if err != nil {
return nil, invalidTodoReminderRules(CodeInvalidParam, fmt.Sprintf("--reminder-rules 第 %d 条的 reminderTimeStamp 无效", position), err)
}
rule["reminderTimeStamp"] = parsed
default:
return nil, invalidTodoReminderRules(CodeInvalidParam, fmt.Sprintf("--reminder-rules 第 %d 条的 baseTime 必须是 dueTime 或 customTime", position), nil)
}
}
return rules, nil
}
func invalidTodoReminderRules(code, message string, cause error) error {
return &CLIError{
Code: code,
Message: message,
Suggestion: "使用 dueTime + 整数 dueDateOffset,或 customTime + ISO8601 reminderTimeStamp;清除提醒请省略 --reminder-rules 或传 []。",
Operation: "todo.task.reset-reminder.reminder-rules",
Cause: cause,
}
}
// todoListAutoPage 当 size > 20 时自动分页请求并合并结果。pageStr 为起始页码,wantSize 为期望条数。
func todoListAutoPage(cmd *cobra.Command, pageStr string, wantSize int) error {
ctx := context.Background()
@@ -137,6 +137,8 @@ func TestCrossPlatformCoverageTodoUpdateReminderAndDetailEdges(t *testing.T) {
{"task", "update", "--task-id", "1", "--due", "bad"},
{"task", "add-reminder", "--task-id", "1", "--base-time", "customTime", "--reminder-time-stamp", "bad"},
{"task", "reset-reminder", "--task-id", "1", "--reminder-rules", `[{"baseTime":"customTime","reminderTimeStamp":"bad"}]`},
{"task", "reset-reminder", "--task-id", "1", "--reminder-rules", `not-json`},
{"task", "reset-reminder", "--task-id", "1", "--reminder-rules", `[1,{"baseTime":"dueTime"},{"baseTime":"customTime","reminderTimeStamp":"` + validDate + `"}]`},
}
for _, args := range errorCases {
if err := executeTodoEdge(t, &scriptedToolCaller{}, args...); err == nil {
@@ -148,8 +150,6 @@ func TestCrossPlatformCoverageTodoUpdateReminderAndDetailEdges(t *testing.T) {
{"task", "update", "--task-id", "1", "--priority", "bad"},
{"task", "add-reminder", "--task-id", "1", "--base-time", "dueTime", "--due-date-offset", "-10"},
{"task", "add-reminder", "--task-id", "1", "--base-time", "customTime", "--reminder-time-stamp", validDate},
{"task", "reset-reminder", "--task-id", "1", "--reminder-rules", `not-json`},
{"task", "reset-reminder", "--task-id", "1", "--reminder-rules", `[1,{"baseTime":"dueTime"},{"baseTime":"customTime","reminderTimeStamp":"` + validDate + `"}]`},
}
for _, args := range validCases {
if err := executeTodoEdge(t, &scriptedToolCaller{}, args...); err != nil {
+115 -1
View File
@@ -33,6 +33,10 @@ func (*todoReminderCaller) Fields() string { return "" }
func (*todoReminderCaller) JQ() string { return "" }
func runTodoReminderCommand(t *testing.T, args ...string) (*todoReminderCaller, error) {
return runTodoTaskCommandForReminderTests(t, "add-reminder", args...)
}
func runTodoTaskCommandForReminderTests(t *testing.T, leaf string, args ...string) (*todoReminderCaller, error) {
t.Helper()
previousDeps := deps
t.Cleanup(func() { deps = previousDeps })
@@ -44,7 +48,7 @@ func runTodoReminderCommand(t *testing.T, args ...string) (*todoReminderCaller,
cmd := newTodoCommand()
cmd.SilenceErrors = true
cmd.SilenceUsage = true
cmd.SetArgs(append([]string{"task", "add-reminder"}, args...))
cmd.SetArgs(append([]string{"task", leaf}, args...))
return caller, cmd.Execute()
}
@@ -137,6 +141,116 @@ func TestTodoAddReminderMapsReviewedModes(t *testing.T) {
}
}
func TestTodoResetReminderRejectsInvalidNonEmptyRulesBeforeCallingTool(t *testing.T) {
tests := []struct {
name string
value string
wantErr string
}{
{name: "explicit empty", value: "", wantErr: "不能为空"},
{name: "whitespace", value: " ", wantErr: "不能为空"},
{name: "malformed JSON", value: `[`, wantErr: "合法 JSON 数组"},
{name: "object instead of array", value: `{}`, wantErr: "由对象组成的 JSON 数组"},
{name: "scalar item", value: `[1]`, wantErr: "由对象组成的 JSON 数组"},
{name: "null array", value: `null`, wantErr: "不能是 null"},
{name: "null item", value: `[null]`, wantErr: "第 1 条必须是对象"},
{name: "missing base time", value: `[{}]`, wantErr: "缺少字符串 baseTime"},
{name: "non-string base time", value: `[{"baseTime":1}]`, wantErr: "缺少字符串 baseTime"},
{name: "due time missing offset", value: `[{"baseTime":"dueTime"}]`, wantErr: "必须提供整数 dueDateOffset"},
{name: "due time string offset", value: `[{"baseTime":"dueTime","dueDateOffset":"-30"}]`, wantErr: "必须提供整数 dueDateOffset"},
{name: "due time fractional offset", value: `[{"baseTime":"dueTime","dueDateOffset":-1.5}]`, wantErr: "dueDateOffset 必须是整数"},
{name: "custom time missing timestamp", value: `[{"baseTime":"customTime"}]`, wantErr: "必须提供 ISO8601 字符串"},
{name: "custom time numeric timestamp", value: `[{"baseTime":"customTime","reminderTimeStamp":1}]`, wantErr: "必须提供 ISO8601 字符串"},
{name: "custom time invalid timestamp", value: `[{"baseTime":"customTime","reminderTimeStamp":"tomorrow"}]`, wantErr: "reminderTimeStamp 无效"},
{name: "unknown base time", value: `[{"baseTime":"deadline"}]`, wantErr: "baseTime 必须是 dueTime 或 customTime"},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
caller, err := runTodoTaskCommandForReminderTests(t, "reset-reminder",
"--task-id", "task-smoke", "--reminder-rules", test.value)
if err == nil || !strings.Contains(err.Error(), test.wantErr) {
t.Fatalf("error = %v, want containing %q", err, test.wantErr)
}
if len(caller.calls) != 0 {
t.Fatalf("tool calls = %#v, want none", caller.calls)
}
})
}
}
func TestTodoResetReminderMapsValidatedRulesAndExplicitClears(t *testing.T) {
tests := []struct {
name string
args []string
wantRules []map[string]any
}{
{
name: "omitted rules clear with null",
args: []string{"--task-id", "task-smoke"},
wantRules: nil,
},
{
name: "explicit empty array clears with array",
args: []string{"--task-id", "task-smoke", "--reminder-rules", `[]`},
wantRules: []map[string]any{},
},
{
name: "valid rules are normalized",
args: []string{
"--task-id", "task-smoke",
"--reminder-rules",
`[{"baseTime":"dueTime","dueDateOffset":-30},{"baseTime":"customTime","reminderTimeStamp":"2026-03-10T18:00:00+08:00","label":"keep"}]`,
},
wantRules: []map[string]any{
{"baseTime": "dueTime", "dueDateOffset": int64(-30)},
{"baseTime": "customTime", "reminderTimeStamp": int64(1773136800000), "label": "keep"},
},
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
caller, err := runTodoTaskCommandForReminderTests(t, "reset-reminder", test.args...)
if err != nil {
t.Fatalf("todo reset-reminder returned error: %v", err)
}
if len(caller.calls) != 1 || caller.calls[0].toolName != "reset_todo_reminder" {
t.Fatalf("tool calls = %#v, want one reset_todo_reminder call", caller.calls)
}
request, ok := caller.calls[0].args["todoReminderUpdateRequest"].(map[string]any)
if !ok {
t.Fatalf("request = %#v, want object", caller.calls[0].args["todoReminderUpdateRequest"])
}
if request["taskId"] != "task-smoke" {
t.Fatalf("taskId = %#v, want task-smoke", request["taskId"])
}
rules, ok := request["reminderRules"].([]map[string]any)
if !ok {
t.Fatalf("reminderRules = %#v, want []map[string]any", request["reminderRules"])
}
if !reflect.DeepEqual(rules, test.wantRules) {
t.Fatalf("reminderRules = %#v, want %#v", rules, test.wantRules)
}
})
}
}
func TestUnsupportedRemindAtGuidanceDoesNotRequireDueForCustomTime(t *testing.T) {
caller, err := runTodoTaskCommandForReminderTests(t, "create",
"--title", "x", "--executors", "user-1", "--remind-at", "2026-03-10T18:00:00+08:00")
if err == nil {
t.Fatal("todo create --remind-at returned nil error")
}
if !strings.Contains(err.Error(), "customTime") ||
!strings.Contains(err.Error(), "不必先设置 --due") {
t.Fatalf("error = %v, want customTime guidance without due requirement", err)
}
if len(caller.calls) != 0 {
t.Fatalf("tool calls = %#v, want none", caller.calls)
}
}
func TestTodoRoleTypesCSVExampleUsesRuntimeParser(t *testing.T) {
got, err := parseRoleTypes("creator,executor")
if err != nil {
@@ -40,6 +40,8 @@ func (f *platformCoverageCaller) CallTool(_ context.Context, product, tool strin
switch product + "/" + tool {
case "contact/search_contact_by_key_word":
text = `{"result":[{"userId":"u1","name":"张三","openDingTalkId":"open1"}]}`
case "contact/get_current_user_profile":
text = `{"result":{"userId":"u1"}}`
case "im/search_groups":
text = `{"result":[{"openConversationId":"cid-1","title":"项目冲刺"}]}`
}
@@ -6,6 +6,8 @@ package smart
import (
"strings"
"testing"
"github.com/DingTalk-Real-AI/dingtalk-workspace-cli/internal/helpers"
)
func TestRemindShortcutDoesNotAdvertiseDueTimeAsReminder(t *testing.T) {
@@ -18,3 +20,49 @@ func TestRemindShortcutDoesNotAdvertiseDueTimeAsReminder(t *testing.T) {
}
}
}
func TestRemindShortcutWritesAtOnlyAsDueTime(t *testing.T) {
fake := &platformCoverageCaller{}
helpers.InitDeps(fake)
root := newPlatformCoverageRoot()
root.SetArgs([]string{
"todo", "+remind",
"--task", "交周报",
"--at", "2026-03-10T18:00:00+08:00",
"--yes",
})
if err := root.Execute(); err != nil {
t.Fatalf("execute +remind: %v", err)
}
if len(fake.calls) != 2 {
t.Fatalf("tool calls = %#v, want profile read plus todo create", fake.calls)
}
call := fake.calls[1]
if call.product != "todo" || call.tool != "create_personal_todo" {
t.Fatalf("create call = %s/%s, want todo/create_personal_todo", call.product, call.tool)
}
request, ok := call.args["PersonalTodoCreateVO"].(map[string]any)
if !ok {
t.Fatalf("PersonalTodoCreateVO = %#v, want object", call.args["PersonalTodoCreateVO"])
}
if got := request["dueTime"]; got != int64(1773136800000) {
t.Fatalf("dueTime = %#v, want 1773136800000", got)
}
if _, exists := request["reminderRules"]; exists {
t.Fatalf("unexpected reminderRules in %#v", request)
}
}
func TestRemindShortcutRejectsInvalidAtBeforeTodoCreate(t *testing.T) {
fake := &platformCoverageCaller{}
helpers.InitDeps(fake)
root := newPlatformCoverageRoot()
root.SetArgs([]string{"todo", "+remind", "--task", "交周报", "--at", "tomorrow", "--yes"})
err := root.Execute()
if err == nil || !strings.Contains(err.Error(), "--at 时间格式无效") {
t.Fatalf("error = %v, want invalid --at validation", err)
}
if len(fake.calls) != 1 || fake.calls[0].tool != "get_current_user_profile" {
t.Fatalf("tool calls = %#v, want profile read only", fake.calls)
}
}
+1 -1
View File
@@ -425,7 +425,7 @@ Flags:
--name string 新名称 (仅 rename 必填)
```
> **rename 只传主名,不要带扩展名**:服务端会按文件原扩展名自动补一个后缀。若 `--name` 里已带扩展名(如 `报告.txt`),回读会变成双扩展名 `报告.txt.txt`。正确做法:`dws drive rename --node <ID> --name "报告"`(不含 `.txt`),系统自动补回 `报告.txt`。
> **rename 会按真实节点元数据处理扩展名**:实际执行前先读取节点类型和当前扩展名。文件的新名称若以当前扩展名结尾,只去掉完全匹配的一层再交给会保留原后缀的服务端;文件夹(包括 `release.v2` 这类带点名称)保持原样,也不再依赖扩展名白名单。`--dry-run` 不读取远端元数据,因此预览保留输入名称。
权限要求:copy 需对源文档有"阅读"权限且对目标文件夹有"编辑"权限;move 需对源文档有"管理"权限且对目标文件夹有"编辑"权限;rename 需对文档有"编辑"权限。
+5 -2
View File
@@ -268,7 +268,7 @@ Example:
dws todo task reset-reminder --task-id <taskId>
dws todo task reset-reminder --task-id <taskId> --reminder-rules '[{"dueDateOffset":-30,"baseTime":"dueTime"},{"reminderTimeStamp":"2026-03-10T18:00:00+08:00","baseTime":"customTime"}]'
Flags:
--reminder-rules string 提醒规则 JSON 数组 (可选,为空则清除提醒)
--reminder-rules string 提醒规则 JSON 数组 (不传则清除;显式传值必须合法)
--task-id string 待办任务 ID (必填)
```
@@ -291,6 +291,8 @@ JSON 数组,每个元素为一条提醒规则,支持两种 `baseTime` 模式
```
以上表示两条提醒规则:第一条在截止时间前 30 分钟提醒,第二条在指定时间(ISO-8601)提醒。
不传 `--reminder-rules` 或显式传 `[]` 表示清除提醒。其他显式值必须是合法对象数组:每条规则必须提供 `baseTime`;`dueTime` 必须带整数 `dueDateOffset`,`customTime` 必须带 ISO-8601 `reminderTimeStamp`。非法 JSON、`null`、标量元素或缺失模式字段都会在调用远端前报错,不会静默按 `null` 清空提醒。
### 给待办打标签
```
Usage:
@@ -472,7 +474,8 @@ dws todo task list-sub --task-id <taskId> --format json
- `task add-participant` / `task remove-participant` 用于管理待办的参与人,`--participants` 支持逗号分隔的多个 userId
- 执行人 (executor) 与参与人 (participant) 的区别:执行人负责完成待办,参与人仅关注待办进度
- `task add-reminder` 用于为待办添加提醒,`--base-time` 支持 `dueTime`(基于截止时间偏移,待办必须有截止时间)和 `customTime`(自定义时间戳)两种模式
- `task reset-reminder` 用于重置待办提醒规则,不传 `--reminder-rules` 则清除所有提醒
- `customTime` 直接使用 `--reminder-time-stamp`,不要求先设置 `--due`;只有 `dueTime` 模式依赖待办截止时间
- `task reset-reminder` 用于重置待办提醒规则,不传 `--reminder-rules` 或传 `[]` 则清除所有提醒;其他值会严格校验,非法输入不会发出远端调用
- `task add-attachment` / `list-attachment` / `remove-attachment` 三条附件命令均可用;`add-attachment` 会真实上传文件,勿用于试探性调用,先确认待办存在
- 附件 ID 的取法:`add-attachment` 从 `result.attachmentIds[]` 取,`list-attachment` 从顶层 `attachments[].attachmentId` 取;`remove-attachment` 用 `--attachment-id` + `--yes`
- 子待办 ID 只能从 `task list-sub` 的顶层 `subTasks[].taskId` 取;`task get` 的 `result.todoDetailModel.subTodos[]` 没有 taskId 字段
+2 -2
View File
@@ -55,7 +55,7 @@ metadata:
| "上传本地文件" | `dws drive upload --file ./report.pdf [--folder <fileId>]` |
| "覆盖钉盘或知识库里的已有文件" | `dws drive upload --file ./updated.pdf --node <fileId> --dry-run`,确认后加 `--yes` |
| "建文件夹" | `dws drive mkdir --name "<名称>" [--folder <fileId>]` |
| "复制 / 移动 / 重命名" | `dws drive copy` / `move` / `rename --node <fileId> --name "<主名>"` |
| "复制 / 移动 / 重命名" | `dws drive copy` / `move` / `rename --node <fileId> --name "<新名称>"` |
| "删除文件 / 移到回收站(需确认)" | `dws drive delete --node <fileId> --yes` |
| "回收站 / 还原" | `dws drive recycle list` / `recycle restore --id <recycleItemId>` |
| "公开 / 取消公开 / 查公开状态" | `dws drive publish set` / `unset` / `get --node <fileId>` |
@@ -65,7 +65,7 @@ metadata:
- 找文件优先用 `drive search --query "<关键词>"`(不知道位置时全局搜);只有需要逐层浏览时才用 `drive list`。命中后必须 `drive info --node <fileId> --format json` 回读元数据。
- **ID 字段选择**:`drive list` 返回同时有 `dentryId`(纯数字)和 `fileId`(UUID 格式)。所有 `--node` 和 `--folder` 参数**必须用 `fileId`**,纯数字 `dentryId` 会被拒绝。
- `drive list` 默认 `--limit 20`,最大 50;要更多用 `--cursor` 翻页,不要因参数边界报错反复重试。
- `rename` 的 `--name` **只传主名,不带扩展名**;服务端会按原扩展名自动补后缀,带了扩展名会变成双扩展名(如 `报告.txt` → `报告.txt.txt`)。
- `rename` 实际执行前读取节点类型和当前扩展名;仅对非文件夹且末尾后缀与当前扩展名完全匹配的名称去掉一层,避免双扩展名。带点文件夹名保持原样;`--dry-run` 不读取元数据,预览保留输入名称。
- `drive download` 需要 `--output` 指定本地保存路径或目录;不要省略必填输出位置。
- 删除、覆盖、移动、公开(publish set/unset)等破坏性操作必须先确认;上传、创建文件夹、下载后要读回或列目录验证。
- `drive upload --node <fileId>` 覆盖已有文件,`--node` 与新建文件使用的 `--folder` 互斥;先 `--dry-run`,获得明确确认后再加 `--yes`。
@@ -425,7 +425,7 @@ Flags:
--name string 新名称 (仅 rename 必填)
```
> **rename 只传主名,不要带扩展名**:服务端会按文件原扩展名自动补一个后缀。若 `--name` 里已带扩展名(如 `报告.txt`),回读会变成双扩展名 `报告.txt.txt`。正确做法:`dws drive rename --node <ID> --name "报告"`(不含 `.txt`),系统自动补回 `报告.txt`。
> **rename 会按真实节点元数据处理扩展名**:实际执行前先读取节点类型和当前扩展名。文件的新名称若以当前扩展名结尾,只去掉完全匹配的一层再交给会保留原后缀的服务端;文件夹(包括 `release.v2` 这类带点名称)保持原样,也不再依赖扩展名白名单。`--dry-run` 不读取远端元数据,因此预览保留输入名称。
权限要求:copy 需对源文档有"阅读"权限且对目标文件夹有"编辑"权限;move 需对源文档有"管理"权限且对目标文件夹有"编辑"权限;rename 需对文档有"编辑"权限。
+2
View File
@@ -71,6 +71,8 @@ metadata:
- `--id` / `--ids` 是隐藏兼容别名,文档和生成命令统一写 `--task-id`,减少模型漂移。
- 优先级映射:低=10,普通=20,较高/高/重要=30,紧急/最高/P0/马上处理=40;不要把"较高"写成 40。
- 截止时间必须是 ISO-8601。相对日期按当前日期计算;例如周五说"下周二"就是紧接下一个自然周的周二,不要再加一周。
- 独立提醒使用 `task add-reminder`:`dueTime` 依赖待办已有截止时间,`customTime` 直接传 `--reminder-time-stamp`,不要求先设置 `--due`。隐藏兼容参数 `--remind-at` 不可用。
- `task reset-reminder` 不传 `--reminder-rules` 或传 `[]` 表示清除;其他显式值必须是合法对象数组并通过模式字段校验,非法输入禁止发出远端调用。
- 附件命令 `task add-attachment` / `list-attachment` / `remove-attachment` 均可用:`add-attachment --task-id <taskId> --file-path <本地文件>`(真实上传,先确认待办存在,返回 `result.attachmentIds`);`list-attachment --task-id <taskId>`(返回顶层 `attachments[].attachmentId`);`remove-attachment --task-id <taskId> --attachment-id <id> --yes`(不可逆,先确认)。
- `tag add` 只是把标签关联到任务;`tag delete` 删除标签定义且不可恢复,必须先确认再加 `--yes`。`tag add` 最多传 2 个标签编码。
- 创建、标记完成、重开、删除后必须 `task get` 或对应 `task list --status ...` 验证,不要只凭创建返回或口头计划结束。
@@ -267,7 +267,7 @@ Example:
dws todo task reset-reminder --task-id <taskId>
dws todo task reset-reminder --task-id <taskId> --reminder-rules '[{"dueDateOffset":-30,"baseTime":"dueTime"},{"reminderTimeStamp":"2026-03-10T18:00:00+08:00","baseTime":"customTime"}]'
Flags:
--reminder-rules string 提醒规则 JSON 数组 (可选,为空则清除提醒)
--reminder-rules string 提醒规则 JSON 数组 (不传则清除;显式传值必须合法)
--task-id string 待办任务 ID (必填)
```
@@ -290,6 +290,8 @@ JSON 数组,每个元素为一条提醒规则,支持两种 `baseTime` 模式
```
以上表示两条提醒规则:第一条在截止时间前 30 分钟提醒,第二条在指定时间(ISO-8601)提醒。
不传 `--reminder-rules` 或显式传 `[]` 表示清除提醒。其他显式值必须是合法对象数组:每条规则必须提供 `baseTime`;`dueTime` 必须带整数 `dueDateOffset`,`customTime` 必须带 ISO-8601 `reminderTimeStamp`。非法 JSON、`null`、标量元素或缺失模式字段都会在调用远端前报错,不会静默按 `null` 清空提醒。
### 给待办打标签
```
Usage:
@@ -470,7 +472,8 @@ dws todo task list-sub --task-id <taskId> --format json
- `task add-participant` / `task remove-participant` 用于管理待办的参与人,`--participants` 支持逗号分隔的多个 userId
- 执行人 (executor) 与参与人 (participant) 的区别:执行人负责完成待办,参与人仅关注待办进度
- `task add-reminder` 用于为待办添加提醒,`--base-time` 支持 `dueTime`(基于截止时间偏移,待办必须有截止时间)和 `customTime`(自定义时间戳)两种模式
- `task reset-reminder` 用于重置待办提醒规则,不传 `--reminder-rules` 则清除所有提醒
- `customTime` 直接使用 `--reminder-time-stamp`,不要求先设置 `--due`;只有 `dueTime` 模式依赖待办截止时间
- `task reset-reminder` 用于重置待办提醒规则,不传 `--reminder-rules` 或传 `[]` 则清除所有提醒;其他值会严格校验,非法输入不会发出远端调用
- `task add-attachment` / `list-attachment` / `remove-attachment` 三条附件命令均可用;`add-attachment` 会真实上传文件,勿用于试探性调用,先确认待办存在
- 附件 ID 的取法:`add-attachment` 从 `result.attachmentIds[]` 取,`list-attachment` 从顶层 `attachments[].attachmentId` 取;`remove-attachment` 用 `--attachment-id` + `--yes`
- 子待办 ID 只能从 `task list-sub` 的顶层 `subTasks[].taskId` 取;`task get` 的 `result.todoDetailModel.subTodos[]` 没有 taskId 字段