diff --git a/CHANGELOG.md b/CHANGELOG.md index 92082cae..9ad33e6e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,10 @@ The format is inspired by [Keep a Changelog](https://keepachangelog.com/) and th - **Sheet imports and Aitable workflow writes** (#624) — adds `dws sheet import` / `sheet import create` for converting local xlsx/xls files into new online sheets, `sheet import get` for polling import tasks, and `dws aitable workflow create/update` for applying validated `workflow-dsl/v1` definitions, with matching reviewed Agent Schema and bundled Skill guidance. - **Official multi-platform Homebrew channel** — stable `Formula/dingtalk-workspace-cli.rb` and keg-only `Formula/dingtalk-workspace-cli-beta.rb` live in this repository and select signed macOS Intel/Apple Silicon or Linux amd64/arm64 artifacts at install time. Stable and beta releases open isolated Formula update PRs after final artifact signing, so beta never replaces the stable Formula. Agent Skills stay under `pkgshare` without mutating the user's home directory, and both tracks are covered by the six-channel post-release verifier. +### Fixed + +- **Sheet and Todo invalid-target failures** — `sheet range read/get` now rejects a null cell-info response instead of printing `null` and exiting successfully, while Todo completion and attachment listing verify that a task exists before calling lenient backend endpoints. Attachment listing is also published through Runtime Schema for schema-first Agent discovery. + ## [1.0.52] - 2026-07-14 This release seals the `v1.0.52` line with personal event subscriptions, a deterministic 22-product Agent command catalog, local user-operation auditing, expanded Open product commands, safer macOS credentials and release signing, and more reliable Connect and IM delivery. diff --git a/internal/app/schema_todo_list_attachment_contract_test.go b/internal/app/schema_todo_list_attachment_contract_test.go new file mode 100644 index 00000000..1acfa0bd --- /dev/null +++ b/internal/app/schema_todo_list_attachment_contract_test.go @@ -0,0 +1,56 @@ +// Copyright 2026 Alibaba Group +// Licensed under the Apache License, Version 2.0 (the "License"); + +package app + +import ( + "bytes" + "encoding/json" + "testing" +) + +func TestTodoListAttachmentDeliveredSchemaMatchesExecutableHelp(t *testing.T) { + const ( + canonicalPath = "todo.list_todo_attachment" + cliPath = "todo task list-attachment" + ) + + root := NewRootCommand() + command := exactCommandForTest(root, cliPath) + if command == nil { + t.Fatalf("executable command %q is missing", cliPath) + } + + var stdout, stderr bytes.Buffer + root.SetOut(&stdout) + root.SetErr(&stderr) + root.SetArgs([]string{"schema", cliPath, "--format", "json"}) + if err := root.Execute(); err != nil { + t.Fatalf("execute embedded schema leaf: %v; stderr=%s", err, stderr.String()) + } + + var tool map[string]any + if err := json.Unmarshal(stdout.Bytes(), &tool); err != nil { + t.Fatalf("decode embedded schema leaf: %v", err) + } + if got := schemaContractString(tool["canonical_path"]); got != canonicalPath { + t.Fatalf("canonical_path = %q, want %q", got, canonicalPath) + } + if got := schemaContractString(tool["primary_cli_path"]); got != cliPath { + t.Fatalf("primary_cli_path = %q, want %q", got, cliPath) + } + if got := schemaContractString(tool["availability"]); got != "available" { + t.Fatalf("availability = %q, want available", got) + } + if problem := schemaHelpFlagCompletenessProblem(canonicalPath, cliPath, command, tool); problem != "" { + t.Fatal(problem) + } + + taskID := schemaContractMap(tool["parameters"])["task-id"] + if taskID == nil { + t.Fatal("delivered Schema is missing --task-id") + } + if required, ok := taskID["required"].(bool); !ok || !required { + t.Fatalf("task-id required = %#v, want true", taskID["required"]) + } +} diff --git a/internal/cli/schema_agent_metadata/index.json b/internal/cli/schema_agent_metadata/index.json index ef643a9b..28d0630b 100644 --- a/internal/cli/schema_agent_metadata/index.json +++ b/internal/cli/schema_agent_metadata/index.json @@ -1,18 +1,18 @@ { "version": 1, - "source_hash": "sha256:53567141622d90e02ce50fe3bc1733357a25b9860a0795eb396d9361f43c41d1", - "surface_hash": "sha256:69e1f9fcd3768d49a2a197c40fb26c0bc07385e868753210761c3ea7f676eadb", + "source_hash": "sha256:d398f66780f4959874c4ffc48be5be08c716ba2daeed8ae3d8690504846a3906", + "surface_hash": "sha256:631b73b174f48cb0cca7c8449e80920739010d0db223dac2855101f4dbd0eda6", "coverage": { "surface_products": 22, "products_with_metadata": 22, - "surface_tools": 568, - "tools_with_metadata": 568, - "tools_with_agent_summary": 568, - "tools_with_use_when": 568, - "tools_with_avoid_when": 568, - "tools_with_examples": 568, - "tools_with_interface_mode": 568, - "unmatched_skill_tools": 125, + "surface_tools": 569, + "tools_with_metadata": 569, + "tools_with_agent_summary": 569, + "tools_with_use_when": 569, + "tools_with_avoid_when": 569, + "tools_with_examples": 569, + "tools_with_interface_mode": 569, + "unmatched_skill_tools": 124, "unreviewed_skill_tools": 7 }, "products": { diff --git a/internal/cli/schema_agent_metadata/todo.json b/internal/cli/schema_agent_metadata/todo.json index d5a55bb1..a1a6c02a 100644 --- a/internal/cli/schema_agent_metadata/todo.json +++ b/internal/cli/schema_agent_metadata/todo.json @@ -3439,6 +3439,265 @@ "需要按完成状态、优先级、角色或截止日期范围查询当前用户待办列表时" ] }, + "todo task list-attachment": { + "agent_summary": "查询待办附件列表", + "agent_summary_source": "dws-agent-selection/todo", + "availability": "available", + "avoid_when": [ + "要上传附件时改用 dws todo task add-attachment", + "要删除附件时先用本命令确认 attachmentId,再改用 dws todo task remove-attachment" + ], + "confirmation": "not_required", + "effect": "read", + "effect_source": "command-verb", + "examples": [ + "dws todo task list-attachment --task-id \u003ctaskId\u003e", + "dws todo task list-attachment --task-id \u003ctaskId\u003e --format json" + ], + "field_provenance": { + "agent_summary": { + "value": "查询待办附件列表", + "source": "internal/cli/schema_hints/selection/todo.json", + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "依据当前 Cobra Help、Todo Skill 与 helper 中的 list_todo_attachment 调用手工审阅;当前 live leaf Schema 缺失,记录失败证据并以源码契约收口。", + "candidates": [ + { + "value": "查询待办附件列表", + "source": "internal/cli/schema_hints/selection/todo.json", + "precedence": "reviewed_explicit", + "selected": true, + "review_reason": "依据当前 Cobra Help、Todo Skill 与 helper 中的 list_todo_attachment 调用手工审阅;当前 live leaf Schema 缺失,记录失败证据并以源码契约收口。" + } + ] + }, + "availability": { + "value": "available", + "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", + "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" + } + ] + }, + "avoid_when": { + "value": [ + "要上传附件时改用 dws todo task add-attachment", + "要删除附件时先用本命令确认 attachmentId,再改用 dws todo task remove-attachment" + ], + "source": "internal/cli/schema_hints/selection/todo.json", + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "依据当前 Cobra Help、Todo Skill 与 helper 中的 list_todo_attachment 调用手工审阅;当前 live leaf Schema 缺失,记录失败证据并以源码契约收口。", + "candidates": [ + { + "value": [ + "要上传附件时改用 dws todo task add-attachment", + "要删除附件时先用本命令确认 attachmentId,再改用 dws todo task remove-attachment" + ], + "source": "internal/cli/schema_hints/selection/todo.json", + "precedence": "reviewed_explicit", + "selected": true, + "review_reason": "依据当前 Cobra Help、Todo Skill 与 helper 中的 list_todo_attachment 调用手工审阅;当前 live leaf Schema 缺失,记录失败证据并以源码契约收口。" + } + ] + }, + "confirmation": { + "value": "not_required", + "source": "risk-default", + "precedence": "inference_or_default", + "resolution": "highest_precedence", + "candidates": [ + { + "value": "not_required", + "source": "risk-default", + "precedence": "inference_or_default", + "selected": true + } + ] + }, + "effect": { + "value": "read", + "source": "command-verb", + "precedence": "inference_or_default", + "resolution": "highest_precedence", + "candidates": [ + { + "value": "read", + "source": "command-verb", + "precedence": "inference_or_default", + "selected": true + } + ] + }, + "examples": { + "value": [ + "dws todo task list-attachment --task-id \u003ctaskId\u003e", + "dws todo task list-attachment --task-id \u003ctaskId\u003e --format json" + ], + "source": "internal/cli/schema_hints/selection/todo.json", + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "依据当前 Cobra Help、Todo Skill 与 helper 中的 list_todo_attachment 调用手工审阅;当前 live leaf Schema 缺失,记录失败证据并以源码契约收口。", + "candidates": [ + { + "value": [ + "dws todo task list-attachment --task-id \u003ctaskId\u003e", + "dws todo task list-attachment --task-id \u003ctaskId\u003e --format json" + ], + "source": "internal/cli/schema_hints/selection/todo.json", + "precedence": "reviewed_explicit", + "selected": true, + "review_reason": "依据当前 Cobra Help、Todo Skill 与 helper 中的 list_todo_attachment 调用手工审阅;当前 live leaf Schema 缺失,记录失败证据并以源码契约收口。" + } + ] + }, + "idempotency": { + "value": "idempotent", + "source": "effect-default", + "precedence": "inference_or_default", + "resolution": "highest_precedence", + "candidates": [ + { + "value": "idempotent", + "source": "effect-default", + "precedence": "inference_or_default", + "selected": true + } + ] + }, + "interface_mode": { + "value": "composite", + "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", + "candidates": [ + { + "value": "composite", + "source": "internal/cli/schema_hints/metadata/todo.json", + "precedence": "reviewed_explicit", + "selected": true, + "review_reason": "dws-tool-metadata/todo marks this tool as reviewed" + } + ] + }, + "interface_reason": { + "value": "Reviewed unpinned remote adapter: this executable CLI wrapper calls a remote helper that is absent from the pinned MCP metadata snapshot; no single pinned semantically equivalent interface_ref can represent the command.", + "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", + "candidates": [ + { + "value": "Reviewed unpinned remote adapter: this executable CLI wrapper calls a remote helper that is absent from the pinned MCP metadata snapshot; no single pinned semantically equivalent interface_ref can represent the command.", + "source": "internal/cli/schema_hints/metadata/todo.json", + "precedence": "reviewed_explicit", + "selected": true, + "review_reason": "dws-tool-metadata/todo marks this tool as reviewed" + } + ] + }, + "interface_ref": { + "value": null, + "source": "internal/cli/schema_hints/metadata/todo.json", + "precedence": "reviewed_explicit", + "resolution": "interface_disposition_matrix", + "review_reason": "final interface mode composite forbids a direct MCP interface_ref", + "candidates": [ + { + "value": null, + "source": "internal/cli/schema_hints/metadata/todo.json", + "precedence": "reviewed_explicit", + "selected": true, + "review_reason": "final interface mode composite forbids a direct MCP interface_ref" + } + ] + }, + "reviewed": { + "value": true, + "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", + "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" + }, + { + "value": true, + "source": "internal/cli/schema_hints/selection/todo.json", + "precedence": "reviewed_explicit", + "selected": false, + "review_reason": "依据当前 Cobra Help、Todo Skill 与 helper 中的 list_todo_attachment 调用手工审阅;当前 live leaf Schema 缺失,记录失败证据并以源码契约收口。" + } + ] + }, + "risk": { + "value": "low", + "source": "effect-default", + "precedence": "inference_or_default", + "resolution": "highest_precedence", + "candidates": [ + { + "value": "low", + "source": "effect-default", + "precedence": "inference_or_default", + "selected": true + } + ] + }, + "use_when": { + "value": [ + "已知 taskId,需要查看该待办当前附件及其 attachmentId、文件名和大小时" + ], + "source": "internal/cli/schema_hints/selection/todo.json", + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "依据当前 Cobra Help、Todo Skill 与 helper 中的 list_todo_attachment 调用手工审阅;当前 live leaf Schema 缺失,记录失败证据并以源码契约收口。", + "candidates": [ + { + "value": [ + "已知 taskId,需要查看该待办当前附件及其 attachmentId、文件名和大小时" + ], + "source": "internal/cli/schema_hints/selection/todo.json", + "precedence": "reviewed_explicit", + "selected": true, + "review_reason": "依据当前 Cobra Help、Todo Skill 与 helper 中的 list_todo_attachment 调用手工审阅;当前 live leaf Schema 缺失,记录失败证据并以源码契约收口。" + } + ] + } + }, + "idempotency": "idempotent", + "interface_mode": "composite", + "interface_reason": "Reviewed unpinned remote adapter: this executable CLI wrapper calls a remote helper that is absent from the pinned MCP metadata snapshot; no single pinned semantically equivalent interface_ref can represent the command.", + "reviewed": true, + "risk": "low", + "source_refs": [ + "CommandRegistry:canonical_path=todo.list_todo_attachment", + "Skill:skills/mono/references/products/todo.md", + "cobra-help:dws todo task list-attachment", + "internal/cli/schema_hints/metadata/todo.json", + "internal/cli/schema_hints/selection/todo.json", + "live-dws-schema:todo.list_todo_attachment#FAILED", + "runtime-call:internal/helpers/todo.go#list_todo_attachment", + "skills/mono/references/products/todo.md" + ], + "use_when": [ + "已知 taskId,需要查看该待办当前附件及其 attachmentId、文件名和大小时" + ] + }, "todo task remove-executor": { "agent_summary": "移除待办执行人", "agent_summary_source": "dws-agent-selection/todo", diff --git a/internal/cli/schema_agent_metadata_audit.json b/internal/cli/schema_agent_metadata_audit.json index 00dbff43..8bb76562 100644 --- a/internal/cli/schema_agent_metadata_audit.json +++ b/internal/cli/schema_agent_metadata_audit.json @@ -1,11 +1,11 @@ { "version": 1, - "source_hash": "sha256:53567141622d90e02ce50fe3bc1733357a25b9860a0795eb396d9361f43c41d1", - "surface_hash": "sha256:69e1f9fcd3768d49a2a197c40fb26c0bc07385e868753210761c3ea7f676eadb", + "source_hash": "sha256:d398f66780f4959874c4ffc48be5be08c716ba2daeed8ae3d8690504846a3906", + "surface_hash": "sha256:631b73b174f48cb0cca7c8449e80920739010d0db223dac2855101f4dbd0eda6", "source_files": 150, "hint_files": 46, "hint_products": 39, - "hint_tools": 1288, + "hint_tools": 1290, "interface_metadata": { "source": "mcp-tools-list+cli-registry", "revision": "4574f7022c32cf4c033e9b7b4156e2fec815fed8", @@ -29,14 +29,14 @@ "coverage": { "surface_products": 22, "products_with_metadata": 22, - "surface_tools": 568, - "tools_with_metadata": 568, - "tools_with_agent_summary": 568, - "tools_with_use_when": 568, - "tools_with_avoid_when": 568, - "tools_with_examples": 568, - "tools_with_interface_mode": 568, - "unmatched_skill_tools": 125, + "surface_tools": 569, + "tools_with_metadata": 569, + "tools_with_agent_summary": 569, + "tools_with_use_when": 569, + "tools_with_avoid_when": 569, + "tools_with_examples": 569, + "tools_with_interface_mode": 569, + "unmatched_skill_tools": 124, "unreviewed_skill_tools": 7 }, "source_products": [ @@ -3944,20 +3944,6 @@ "reason": "旧版 Skill 或说明性引用,当前公开命令面无等价 leaf,禁止词法模糊映射" } }, - { - "tool_path": "todo task list-attachment", - "source": "skills/mono/references/products/todo.md", - "line": 216, - "candidates": [ - "todo task add-attachment", - "todo task add-executor", - "todo task add-participant" - ], - "review": { - "status": "stale", - "reason": "旧版 Skill 或说明性引用,当前公开命令面无等价 leaf,禁止词法模糊映射" - } - }, { "tool_path": "todo task remove-attachment", "source": "skills/mono/references/products/todo.md", @@ -3986,20 +3972,6 @@ "reason": "旧版 Skill 或说明性引用,当前公开命令面无等价 leaf,禁止词法模糊映射" } }, - { - "tool_path": "todo task list-attachment", - "source": "skills/mono/references/products/todo.md", - "line": 375, - "candidates": [ - "todo task add-attachment", - "todo task add-executor", - "todo task add-participant" - ], - "review": { - "status": "stale", - "reason": "旧版 Skill 或说明性引用,当前公开命令面无等价 leaf,禁止词法模糊映射" - } - }, { "tool_path": "todo task remove-attachment", "source": "skills/mono/references/products/todo.md", diff --git a/internal/cli/schema_catalog.json b/internal/cli/schema_catalog.json index 4eeff61a..b9f66e68 100644 --- a/internal/cli/schema_catalog.json +++ b/internal/cli/schema_catalog.json @@ -1,18 +1,18 @@ { "version": 1, - "source_hash": "sha256:9a907efbcea3339bcb3881aad578e77b15f4a3f4a8b53efe4c2791db894bb41a", - "surface_hash": "sha256:69e1f9fcd3768d49a2a197c40fb26c0bc07385e868753210761c3ea7f676eadb", + "source_hash": "sha256:8ad3b1c08185ecdae8bb7539017fd5b57ebb085508c8326149e241d95239af4a", + "surface_hash": "sha256:631b73b174f48cb0cca7c8449e80920739010d0db223dac2855101f4dbd0eda6", "catalog": { "agent_metadata": { "products_with_metadata": 22, "source": "embedded-skill-metadata", - "source_hash": "sha256:53567141622d90e02ce50fe3bc1733357a25b9860a0795eb396d9361f43c41d1", - "surface_hash": "sha256:69e1f9fcd3768d49a2a197c40fb26c0bc07385e868753210761c3ea7f676eadb", + "source_hash": "sha256:d398f66780f4959874c4ffc48be5be08c716ba2daeed8ae3d8690504846a3906", + "surface_hash": "sha256:631b73b174f48cb0cca7c8449e80920739010d0db223dac2855101f4dbd0eda6", "surface_products": 22, - "surface_tools": 568, - "tools_with_agent_summary": 568, - "tools_with_metadata": 568, - "unmatched_skill_tools": 125, + "surface_tools": 569, + "tools_with_agent_summary": 569, + "tools_with_metadata": 569, + "unmatched_skill_tools": 124, "version": 1 }, "count": 22, @@ -17936,7 +17936,7 @@ "id": "todo", "name": "待办任务管理", "runtime": true, - "tool_count": 17, + "tool_count": 18, "tools": [ { "agent_metadata_source": "embedded-skill-metadata", @@ -18294,6 +18294,34 @@ "需要按完成状态、优先级、角色或截止日期范围查询当前用户待办列表时" ] }, + { + "agent_metadata_source": "embedded-skill-metadata", + "agent_summary": "查询待办附件列表", + "agent_summary_source": "dws-agent-selection/todo", + "availability": "available", + "avoid_when": [ + "要上传附件时改用 dws todo task add-attachment", + "要删除附件时先用本命令确认 attachmentId,再改用 dws todo task remove-attachment" + ], + "canonical_path": "todo.list_todo_attachment", + "cli_name": "list-attachment", + "cli_path": "todo task list-attachment", + "confirmation": "not_required", + "description": "查询待办任务的附件列表", + "effect": "read", + "group": "task", + "idempotency": "idempotent", + "interface_mode": "composite", + "interface_reason": "Reviewed unpinned remote adapter: this executable CLI wrapper calls a remote helper that is absent from the pinned MCP metadata snapshot; no single pinned semantically equivalent interface_ref can represent the command.", + "name": "list_todo_attachment", + "primary_cli_path": "todo task list-attachment", + "reviewed": true, + "risk": "low", + "title": "查询待办任务的附件列表", + "use_when": [ + "已知 taskId,需要查看该待办当前附件及其 attachmentId、文件名和大小时" + ] + }, { "agent_metadata_source": "embedded-skill-metadata", "agent_summary": "获取待办评论列表", @@ -19042,7 +19070,7 @@ } ], "source": "embedded-command-catalog", - "tool_count": 568 + "tool_count": 569 }, "tools": { "aisearch.enterprise_person_search": { @@ -424437,6 +424465,424 @@ "需要按完成状态、优先级、角色或截止日期范围查询当前用户待办列表时" ] }, + "todo.list_todo_attachment": { + "agent_metadata_source": "embedded-skill-metadata", + "agent_source_refs": [ + "CommandRegistry:canonical_path=todo.list_todo_attachment", + "Skill:skills/mono/references/products/todo.md", + "cobra-help:dws todo task list-attachment", + "internal/cli/schema_hints/metadata/todo.json", + "internal/cli/schema_hints/selection/todo.json", + "live-dws-schema:todo.list_todo_attachment#FAILED", + "runtime-call:internal/helpers/todo.go#list_todo_attachment", + "skills/mono/references/products/todo.md" + ], + "agent_summary": "查询待办附件列表", + "agent_summary_source": "dws-agent-selection/todo", + "availability": "available", + "avoid_when": [ + "要上传附件时改用 dws todo task add-attachment", + "要删除附件时先用本命令确认 attachmentId,再改用 dws todo task remove-attachment" + ], + "canonical_path": "todo.list_todo_attachment", + "cli_name": "list-attachment", + "cli_path": "todo task list-attachment", + "confirmation": "not_required", + "description": "查询待办任务的附件列表", + "display": "待办任务管理", + "effect": "read", + "effect_source": "command-verb", + "examples": [ + "dws todo task list-attachment --task-id \u003ctaskId\u003e", + "dws todo task list-attachment --task-id \u003ctaskId\u003e --format json" + ], + "field_provenance": { + "agent_summary": { + "candidates": [ + { + "precedence": "reviewed_explicit", + "review_reason": "依据当前 Cobra Help、Todo Skill 与 helper 中的 list_todo_attachment 调用手工审阅;当前 live leaf Schema 缺失,记录失败证据并以源码契约收口。", + "selected": true, + "source": "internal/cli/schema_hints/selection/todo.json", + "value": "查询待办附件列表" + } + ], + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "依据当前 Cobra Help、Todo Skill 与 helper 中的 list_todo_attachment 调用手工审阅;当前 live leaf Schema 缺失,记录失败证据并以源码契约收口。", + "source": "internal/cli/schema_hints/selection/todo.json", + "value": "查询待办附件列表" + }, + "availability": { + "candidates": [ + { + "precedence": "reviewed_explicit", + "review_reason": "dws-tool-metadata/todo marks this tool as reviewed", + "selected": true, + "source": "internal/cli/schema_hints/metadata/todo.json", + "value": "available" + } + ], + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "dws-tool-metadata/todo marks this tool as reviewed", + "source": "internal/cli/schema_hints/metadata/todo.json", + "value": "available" + }, + "avoid_when": { + "candidates": [ + { + "precedence": "reviewed_explicit", + "review_reason": "依据当前 Cobra Help、Todo Skill 与 helper 中的 list_todo_attachment 调用手工审阅;当前 live leaf Schema 缺失,记录失败证据并以源码契约收口。", + "selected": true, + "source": "internal/cli/schema_hints/selection/todo.json", + "value": [ + "要上传附件时改用 dws todo task add-attachment", + "要删除附件时先用本命令确认 attachmentId,再改用 dws todo task remove-attachment" + ] + } + ], + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "依据当前 Cobra Help、Todo Skill 与 helper 中的 list_todo_attachment 调用手工审阅;当前 live leaf Schema 缺失,记录失败证据并以源码契约收口。", + "source": "internal/cli/schema_hints/selection/todo.json", + "value": [ + "要上传附件时改用 dws todo task add-attachment", + "要删除附件时先用本命令确认 attachmentId,再改用 dws todo task remove-attachment" + ] + }, + "canonical_path": { + "candidates": [ + { + "precedence": "command_registry", + "selected": true, + "source": "reviewed_command_registry", + "source_ref": "todo task list-attachment", + "value": "todo.list_todo_attachment" + } + ], + "precedence": "command_registry", + "resolution": "registry_identity", + "source": "reviewed_command_registry", + "source_ref": "todo task list-attachment", + "value": "todo.list_todo_attachment" + }, + "confirmation": { + "candidates": [ + { + "precedence": "inference_or_default", + "selected": true, + "source": "risk-default", + "value": "not_required" + } + ], + "precedence": "inference_or_default", + "resolution": "highest_precedence", + "source": "risk-default", + "value": "not_required" + }, + "description": { + "candidates": [ + { + "precedence": "cobra_help", + "selected": true, + "source": "cobra_help", + "value": "查询待办任务的附件列表" + } + ], + "precedence": "cobra_help", + "resolution": "highest_precedence", + "source": "cobra_help", + "value": "查询待办任务的附件列表" + }, + "effect": { + "candidates": [ + { + "precedence": "inference_or_default", + "selected": true, + "source": "command-verb", + "value": "read" + } + ], + "precedence": "inference_or_default", + "resolution": "highest_precedence", + "source": "command-verb", + "value": "read" + }, + "examples": { + "candidates": [ + { + "precedence": "reviewed_explicit", + "review_reason": "依据当前 Cobra Help、Todo Skill 与 helper 中的 list_todo_attachment 调用手工审阅;当前 live leaf Schema 缺失,记录失败证据并以源码契约收口。", + "selected": true, + "source": "internal/cli/schema_hints/selection/todo.json", + "value": [ + "dws todo task list-attachment --task-id \u003ctaskId\u003e", + "dws todo task list-attachment --task-id \u003ctaskId\u003e --format json" + ] + } + ], + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "依据当前 Cobra Help、Todo Skill 与 helper 中的 list_todo_attachment 调用手工审阅;当前 live leaf Schema 缺失,记录失败证据并以源码契约收口。", + "source": "internal/cli/schema_hints/selection/todo.json", + "value": [ + "dws todo task list-attachment --task-id \u003ctaskId\u003e", + "dws todo task list-attachment --task-id \u003ctaskId\u003e --format json" + ] + }, + "idempotency": { + "candidates": [ + { + "precedence": "inference_or_default", + "selected": true, + "source": "effect-default", + "value": "idempotent" + } + ], + "precedence": "inference_or_default", + "resolution": "highest_precedence", + "source": "effect-default", + "value": "idempotent" + }, + "interface_mode": { + "candidates": [ + { + "precedence": "reviewed_explicit", + "review_reason": "dws-tool-metadata/todo marks this tool as reviewed", + "selected": true, + "source": "internal/cli/schema_hints/metadata/todo.json", + "value": "composite" + } + ], + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "dws-tool-metadata/todo marks this tool as reviewed", + "source": "internal/cli/schema_hints/metadata/todo.json", + "value": "composite" + }, + "interface_reason": { + "candidates": [ + { + "precedence": "reviewed_explicit", + "review_reason": "dws-tool-metadata/todo marks this tool as reviewed", + "selected": true, + "source": "internal/cli/schema_hints/metadata/todo.json", + "value": "Reviewed unpinned remote adapter: this executable CLI wrapper calls a remote helper that is absent from the pinned MCP metadata snapshot; no single pinned semantically equivalent interface_ref can represent the command." + } + ], + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "dws-tool-metadata/todo marks this tool as reviewed", + "source": "internal/cli/schema_hints/metadata/todo.json", + "value": "Reviewed unpinned remote adapter: this executable CLI wrapper calls a remote helper that is absent from the pinned MCP metadata snapshot; no single pinned semantically equivalent interface_ref can represent the command." + }, + "interface_ref": { + "candidates": [ + { + "precedence": "reviewed_explicit", + "review_reason": "final interface mode composite forbids a direct MCP interface_ref", + "selected": true, + "source": "internal/cli/schema_hints/metadata/todo.json", + "value": null + } + ], + "precedence": "reviewed_explicit", + "resolution": "interface_disposition_matrix", + "review_reason": "final interface mode composite forbids a direct MCP interface_ref", + "source": "internal/cli/schema_hints/metadata/todo.json", + "value": null + }, + "reviewed": { + "candidates": [ + { + "precedence": "reviewed_explicit", + "review_reason": "dws-tool-metadata/todo marks this tool as reviewed", + "selected": true, + "source": "internal/cli/schema_hints/metadata/todo.json", + "value": true + }, + { + "precedence": "reviewed_explicit", + "review_reason": "依据当前 Cobra Help、Todo Skill 与 helper 中的 list_todo_attachment 调用手工审阅;当前 live leaf Schema 缺失,记录失败证据并以源码契约收口。", + "selected": false, + "source": "internal/cli/schema_hints/selection/todo.json", + "value": true + } + ], + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "dws-tool-metadata/todo marks this tool as reviewed", + "source": "internal/cli/schema_hints/metadata/todo.json", + "value": true + }, + "risk": { + "candidates": [ + { + "precedence": "inference_or_default", + "selected": true, + "source": "effect-default", + "value": "low" + } + ], + "precedence": "inference_or_default", + "resolution": "highest_precedence", + "source": "effect-default", + "value": "low" + }, + "title": { + "candidates": [ + { + "precedence": "cobra_help", + "selected": true, + "source": "cobra_help", + "value": "查询待办任务的附件列表" + } + ], + "precedence": "cobra_help", + "resolution": "highest_precedence", + "source": "cobra_help", + "value": "查询待办任务的附件列表" + }, + "use_when": { + "candidates": [ + { + "precedence": "reviewed_explicit", + "review_reason": "依据当前 Cobra Help、Todo Skill 与 helper 中的 list_todo_attachment 调用手工审阅;当前 live leaf Schema 缺失,记录失败证据并以源码契约收口。", + "selected": true, + "source": "internal/cli/schema_hints/selection/todo.json", + "value": [ + "已知 taskId,需要查看该待办当前附件及其 attachmentId、文件名和大小时" + ] + } + ], + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "依据当前 Cobra Help、Todo Skill 与 helper 中的 list_todo_attachment 调用手工审阅;当前 live leaf Schema 缺失,记录失败证据并以源码契约收口。", + "source": "internal/cli/schema_hints/selection/todo.json", + "value": [ + "已知 taskId,需要查看该待办当前附件及其 attachmentId、文件名和大小时" + ] + } + }, + "group": "task", + "has_parameters": true, + "idempotency": "idempotent", + "interface_mode": "composite", + "interface_reason": "Reviewed unpinned remote adapter: this executable CLI wrapper calls a remote helper that is absent from the pinned MCP metadata snapshot; no single pinned semantically equivalent interface_ref can represent the command.", + "is_alias": false, + "name": "list_todo_attachment", + "parameter_count": 1, + "parameters": { + "task-id": { + "description": "待办任务 ID (必填)", + "field_provenance": { + "description": { + "candidates": [ + { + "precedence": "cobra_contract", + "selected": true, + "source": "cobra_usage", + "value": "待办任务 ID (必填)" + }, + { + "precedence": "default", + "selected": false, + "source": "default", + "value": "" + } + ], + "precedence": "cobra_contract", + "resolution": "highest_precedence", + "source": "cobra_usage", + "value": "待办任务 ID (必填)" + }, + "property": { + "candidates": [ + { + "precedence": "reviewed_mapping_exclusion", + "review_reason": "Reviewed unpinned adapter: --task-id is nested under todoAttachmentListRequest at runtime, while the immutable pinned MCP snapshot has no interface_ref for todo.list_todo_attachment.", + "selected": true, + "source": "reviewed_mapping_exclusion", + "value": "" + }, + { + "precedence": "inference", + "selected": false, + "source": "flag_name_inference", + "value": "taskId" + } + ], + "precedence": "reviewed_mapping_exclusion", + "resolution": "highest_precedence", + "review_reason": "Reviewed unpinned adapter: --task-id is nested under todoAttachmentListRequest at runtime, while the immutable pinned MCP snapshot has no interface_ref for todo.list_todo_attachment.", + "source": "reviewed_mapping_exclusion", + "value": "" + }, + "required": { + "candidates": [ + { + "precedence": "inference", + "selected": true, + "source": "usage_required_inference", + "value": true + }, + { + "precedence": "default", + "selected": false, + "source": "default", + "value": false + } + ], + "precedence": "inference", + "resolution": "highest_precedence", + "source": "usage_required_inference", + "value": true + }, + "required_when": { + "candidates": [ + { + "precedence": "default", + "selected": true, + "source": "default", + "value": "" + } + ], + "precedence": "default", + "resolution": "highest_precedence", + "source": "default", + "value": "" + }, + "type": { + "candidates": [ + { + "precedence": "cobra_contract", + "selected": true, + "source": "cobra_flag_type", + "value": "string" + } + ], + "precedence": "cobra_contract", + "resolution": "highest_precedence", + "source": "cobra_flag_type", + "value": "string" + } + }, + "required": true, + "type": "string" + } + }, + "path": "todo.list_todo_attachment", + "primary_cli_path": "todo task list-attachment", + "product_id": "todo", + "reviewed": true, + "risk": "low", + "source": "reviewed_command_registry", + "title": "查询待办任务的附件列表", + "use_when": [ + "已知 taskId,需要查看该待办当前附件及其 attachmentId、文件名和大小时" + ] + }, "todo.list_todo_comment": { "agent_metadata_source": "embedded-skill-metadata", "agent_source_refs": [ diff --git a/internal/cli/schema_command_exclusions.json b/internal/cli/schema_command_exclusions.json index b7e23591..60d555fc 100644 --- a/internal/cli/schema_command_exclusions.json +++ b/internal/cli/schema_command_exclusions.json @@ -369,7 +369,6 @@ "oa approval revert-activities", "oa approval revert-task", "oa approval search-forms", - "todo task list-attachment", "todo task list-sub", "todo task remove-attachment" ] diff --git a/internal/cli/schema_command_registry.json b/internal/cli/schema_command_registry.json index 9c8f042b..f019a963 100644 --- a/internal/cli/schema_command_registry.json +++ b/internal/cli/schema_command_registry.json @@ -2293,6 +2293,10 @@ "canonical_path": "todo.add_todo_attachment", "cli_path": "todo task add-attachment" }, + { + "canonical_path": "todo.list_todo_attachment", + "cli_path": "todo task list-attachment" + }, { "canonical_path": "todo.add_task_executors", "cli_path": "todo task add-executor" diff --git a/internal/cli/schema_hints/index.json b/internal/cli/schema_hints/index.json index d360ad81..5fd0180b 100644 --- a/internal/cli/schema_hints/index.json +++ b/internal/cli/schema_hints/index.json @@ -876,10 +876,6 @@ "status": "stale", "reason": "旧版 Skill 或说明性引用,当前公开命令面无等价 leaf,禁止词法模糊映射" }, - "todo task list-attachment": { - "status": "stale", - "reason": "旧版 Skill 或说明性引用,当前公开命令面无等价 leaf,禁止词法模糊映射" - }, "todo task list-sub": { "status": "stale", "reason": "旧版 Skill 或说明性引用,当前公开命令面无等价 leaf,禁止词法模糊映射" diff --git a/internal/cli/schema_hints/metadata/todo.json b/internal/cli/schema_hints/metadata/todo.json index 84c07f49..370056b1 100644 --- a/internal/cli/schema_hints/metadata/todo.json +++ b/internal/cli/schema_hints/metadata/todo.json @@ -76,6 +76,12 @@ "availability": "available", "reviewed": true }, + "todo.list_todo_attachment": { + "interface_mode": "composite", + "availability": "available", + "interface_reason": "Reviewed unpinned remote adapter: this executable CLI wrapper calls a remote helper that is absent from the pinned MCP metadata snapshot; no single pinned semantically equivalent interface_ref can represent the command.", + "reviewed": true + }, "todo.remove_task_executors": { "effect": "write", "risk": "medium", diff --git a/internal/cli/schema_hints/reference-review.json b/internal/cli/schema_hints/reference-review.json index c9246647..2b68d266 100644 --- a/internal/cli/schema_hints/reference-review.json +++ b/internal/cli/schema_hints/reference-review.json @@ -825,10 +825,6 @@ "status": "stale", "reason": "旧版 Skill 或说明性引用,当前公开命令面无等价 leaf,禁止词法模糊映射" }, - "todo task list-attachment": { - "status": "stale", - "reason": "旧版 Skill 或说明性引用,当前公开命令面无等价 leaf,禁止词法模糊映射" - }, "todo task list-sub": { "status": "stale", "reason": "旧版 Skill 或说明性引用,当前公开命令面无等价 leaf,禁止词法模糊映射" diff --git a/internal/cli/schema_hints/runtime-surface-completeness.json b/internal/cli/schema_hints/runtime-surface-completeness.json index cbd0372e..e1f8e92f 100644 --- a/internal/cli/schema_hints/runtime-surface-completeness.json +++ b/internal/cli/schema_hints/runtime-surface-completeness.json @@ -7,7 +7,7 @@ "channel": "open-source" }, "coverage": { - "source_tools": 568, + "source_tools": 569, "matched_tools": 71 }, "tools": { diff --git a/internal/cli/schema_hints/selection/todo.json b/internal/cli/schema_hints/selection/todo.json index 05d40367..4e4bc614 100644 --- a/internal/cli/schema_hints/selection/todo.json +++ b/internal/cli/schema_hints/selection/todo.json @@ -286,6 +286,29 @@ "live-dws-schema:todo.list_todo_comment" ] }, + "todo.list_todo_attachment": { + "agent_summary": "查询待办附件列表", + "use_when": [ + "已知 taskId,需要查看该待办当前附件及其 attachmentId、文件名和大小时" + ], + "avoid_when": [ + "要上传附件时改用 dws todo task add-attachment", + "要删除附件时先用本命令确认 attachmentId,再改用 dws todo task remove-attachment" + ], + "examples": [ + "dws todo task list-attachment --task-id ", + "dws todo task list-attachment --task-id --format json" + ], + "reviewed": true, + "review_reason": "依据当前 Cobra Help、Todo Skill 与 helper 中的 list_todo_attachment 调用手工审阅;当前 live leaf Schema 缺失,记录失败证据并以源码契约收口。", + "source_refs": [ + "CommandRegistry:canonical_path=todo.list_todo_attachment", + "cobra-help:dws todo task list-attachment", + "Skill:skills/mono/references/products/todo.md", + "runtime-call:internal/helpers/todo.go#list_todo_attachment", + "live-dws-schema:todo.list_todo_attachment#FAILED" + ] + }, "todo.remove_task_executors": { "agent_summary": "移除待办执行人", "use_when": [ diff --git a/internal/cli/schema_parameter_bindings.json b/internal/cli/schema_parameter_bindings.json index 0c957082..c97b19d9 100644 --- a/internal/cli/schema_parameter_bindings.json +++ b/internal/cli/schema_parameter_bindings.json @@ -1874,6 +1874,7 @@ "sheet.write_image --mime-type": "local upload metadata", "sheet.write_image --name": "local upload metadata", "todo.add_todo_attachment --file-path": "local upload input used to construct attachmentList", + "todo.list_todo_attachment --task-id": "Reviewed unpinned adapter: --task-id is nested under todoAttachmentListRequest at runtime, while the immutable pinned MCP snapshot has no interface_ref for todo.list_todo_attachment.", "wiki.create_wikiSpace --icon": "runtime extension sends icon, which is absent from the pinned create_wikiSpace metadata", "wiki.delete_document --workspace": "local validation/authorization context; not sent to delete_document", "wiki.list_wikiSpaces --cursor": "composite route maps to pageToken for wiki.list_wikiSpaces or nextToken for drive.list_spaces", diff --git a/internal/helpers/quality_evaluation_regression_test.go b/internal/helpers/quality_evaluation_regression_test.go new file mode 100644 index 00000000..8ee33bc2 --- /dev/null +++ b/internal/helpers/quality_evaluation_regression_test.go @@ -0,0 +1,162 @@ +// Copyright 2026 Alibaba Group +// Licensed under the Apache License, Version 2.0 (the "License"); + +package helpers + +import ( + "context" + "errors" + "io" + "os" + "testing" + + "github.com/DingTalk-Real-AI/dingtalk-workspace-cli/pkg/edition" + "github.com/spf13/cobra" +) + +type qualityEvaluationCall struct { + productID string + toolName string + args map[string]any +} + +type qualityEvaluationCaller struct { + calls []qualityEvaluationCall + dryRun bool +} + +func (c *qualityEvaluationCaller) CallTool(_ context.Context, productID, toolName string, args map[string]any) (*edition.ToolResult, error) { + c.calls = append(c.calls, qualityEvaluationCall{productID: productID, toolName: toolName, args: args}) + switch toolName { + case "get_cell_infos": + return qualityEvaluationTextResult("null"), nil + case "get_todo_detail": + taskID, _ := args["taskId"].(string) + if taskID == "INVALID" { + return nil, errors.New("task not found") + } + if taskID == "MISMATCH" { + return qualityEvaluationTextResult(`{"success":true,"result":{"todoDetailModel":{"taskId":"OTHER"}}}`), nil + } + return qualityEvaluationTextResult(`{"success":true,"result":{"todoDetailModel":{"taskId":"` + taskID + `"}}}`), nil + case "update_todo_done_status": + return qualityEvaluationTextResult(`{"success":true}`), nil + case "list_todo_attachment": + return qualityEvaluationTextResult(`{"success":true,"attachments":[]}`), nil + default: + return qualityEvaluationTextResult(`{}`), nil + } +} + +func (*qualityEvaluationCaller) Format() string { return "json" } +func (c *qualityEvaluationCaller) DryRun() bool { return c.dryRun } +func (*qualityEvaluationCaller) Fields() string { return "" } +func (*qualityEvaluationCaller) JQ() string { return "" } + +func qualityEvaluationTextResult(text string) *edition.ToolResult { + return &edition.ToolResult{Content: []edition.ContentBlock{{Type: "text", Text: text}}} +} + +func executeQualityEvaluationCommand(t *testing.T, product string, caller *qualityEvaluationCaller, cmd *cobra.Command, args ...string) error { + t.Helper() + previousDeps := deps + previousArgs := os.Args + t.Cleanup(func() { + deps = previousDeps + os.Args = previousArgs + }) + + InitDeps(caller) + deps.Out.w = io.Discard + os.Args = append([]string{"dws", product}, args...) + cmd.SilenceErrors = true + cmd.SilenceUsage = true + cmd.SetArgs(args) + return cmd.Execute() +} + +func TestCrossPlatformCoverageSheetRangeReadRejectsNullToolResponse(t *testing.T) { + for _, command := range []string{"read", "get"} { + t.Run(command, func(t *testing.T) { + caller := &qualityEvaluationCaller{} + err := executeQualityEvaluationCommand(t, "sheet", caller, newSheetCommand(), + "range", command, "--node", "INVALID") + if err == nil { + t.Fatal("sheet range command accepted a null MCP response") + } + if len(caller.calls) != 1 || caller.calls[0].toolName != "get_cell_infos" { + t.Fatalf("calls = %#v, want one get_cell_infos call", caller.calls) + } + }) + } +} + +func TestCrossPlatformCoverageTodoCommandsRejectMissingTaskBeforeTargetCall(t *testing.T) { + tests := []struct { + name string + args []string + targetTool string + }{ + {name: "done", args: []string{"task", "done", "--task-id", "INVALID", "--status", "true"}, targetTool: "update_todo_done_status"}, + {name: "done with mismatched task", args: []string{"task", "done", "--task-id", "MISMATCH", "--status", "true"}, targetTool: "update_todo_done_status"}, + {name: "list attachment", args: []string{"task", "list-attachment", "--task-id", "INVALID"}, targetTool: "list_todo_attachment"}, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + caller := &qualityEvaluationCaller{} + err := executeQualityEvaluationCommand(t, "todo", caller, newTodoCommand(), tt.args...) + if err == nil { + t.Fatal("todo command accepted a missing task") + } + if len(caller.calls) != 1 || caller.calls[0].toolName != "get_todo_detail" { + t.Fatalf("calls = %#v, want only get_todo_detail preflight before %s", caller.calls, tt.targetTool) + } + }) + } +} + +func TestCrossPlatformCoverageTodoCommandsPreflightExistingTask(t *testing.T) { + tests := []struct { + name string + args []string + targetTool string + }{ + {name: "done", args: []string{"task", "done", "--task-id", "12345", "--status", "true"}, targetTool: "update_todo_done_status"}, + {name: "list attachment", args: []string{"task", "list-attachment", "--task-id", "12345"}, targetTool: "list_todo_attachment"}, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + caller := &qualityEvaluationCaller{} + if err := executeQualityEvaluationCommand(t, "todo", caller, newTodoCommand(), tt.args...); err != nil { + t.Fatalf("todo command returned error: %v", err) + } + if len(caller.calls) != 2 || caller.calls[0].toolName != "get_todo_detail" || caller.calls[1].toolName != tt.targetTool { + t.Fatalf("calls = %#v, want get_todo_detail then %s", caller.calls, tt.targetTool) + } + }) + } +} + +func TestCrossPlatformCoverageTodoTaskPreflightIsSkippedForDryRun(t *testing.T) { + tests := []struct { + name string + args []string + }{ + {name: "done", args: []string{"task", "done", "--task-id", "12345", "--status", "true"}}, + {name: "list attachment", args: []string{"task", "list-attachment", "--task-id", "12345"}}, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + caller := &qualityEvaluationCaller{dryRun: true} + if err := executeQualityEvaluationCommand(t, "todo", caller, newTodoCommand(), tt.args...); err != nil { + t.Fatalf("todo dry-run returned error: %v", err) + } + if len(caller.calls) != 0 { + t.Fatalf("dry-run made remote calls: %#v", caller.calls) + } + }) + } +} diff --git a/internal/helpers/sheet_info.go b/internal/helpers/sheet_info.go index 2b7fbda0..d20163bc 100644 --- a/internal/helpers/sheet_info.go +++ b/internal/helpers/sheet_info.go @@ -146,6 +146,13 @@ func callMCPToolCellInfos(toolArgs map[string]any) error { deps.Out.PrintRaw(text) return nil } + if parsed == nil { + return &CLIError{ + Code: CodeMCPToolError, + Message: "get_cell_infos returned a null response", + Suggestion: "请确认 --node 指向当前用户可访问的钉钉在线表格", + } + } // 清理空 dataValidation / hyperlink if cells, ok := parsed["cells"].([]any); ok { for _, row := range cells { diff --git a/internal/helpers/todo.go b/internal/helpers/todo.go index c8b3a69a..b90494aa 100644 --- a/internal/helpers/todo.go +++ b/internal/helpers/todo.go @@ -24,6 +24,33 @@ const todoListPageSizeMax = 20 var todoFileMD5Hex = fileMD5Hex +func ensureTodoTaskExists(ctx context.Context, taskID string) error { + text, err := callMCPToolReturnTextOnServer(ctx, "todo", "get_todo_detail", map[string]any{ + "taskId": taskID, + }) + if err != nil { + return fmt.Errorf("待办任务 %q 不存在或不可访问: %w", taskID, err) + } + + var body map[string]any + if err := json.Unmarshal([]byte(text), &body); err != nil { + return fmt.Errorf("校验待办任务 %q 时无法解析详情响应: %w", taskID, err) + } + result, ok := body["result"].(map[string]any) + if !ok { + return fmt.Errorf("待办任务 %q 不存在或详情响应缺少 result", taskID) + } + detail, ok := result["todoDetailModel"].(map[string]any) + if !ok { + return fmt.Errorf("待办任务 %q 不存在或详情响应缺少 todoDetailModel", taskID) + } + returnedTaskID := stringFromJSONScalar(detail["taskId"]) + if returnedTaskID == "" || returnedTaskID != taskID { + return fmt.Errorf("待办任务 %q 不存在或详情响应中的 taskId 不匹配", taskID) + } + return nil +} + func newTodoCommand() *cobra.Command { todoCmd := &cobra.Command{ Use: "todo", @@ -233,8 +260,14 @@ func newTodoCommand() *cobra.Command { if err := validateRequiredFlags(cmd, "task-id", "status"); err != nil { return err } + taskID := mustGetFlag(cmd, "task-id") + if !deps.Caller.DryRun() { + if err := ensureTodoTaskExists(cmd.Context(), taskID); err != nil { + return err + } + } return callMCPTool("update_todo_done_status", map[string]any{ - "taskId": mustGetFlag(cmd, "task-id"), + "taskId": taskID, "isDone": mustGetFlag(cmd, "status"), }) }, @@ -537,10 +570,15 @@ func newTodoCommand() *cobra.Command { if err := validateRequiredFlags(cmd, "task-id"); err != nil { return err } - taskId := mustGetFlag(cmd, "task-id") + taskID := mustGetFlag(cmd, "task-id") + if !deps.Caller.DryRun() { + if err := ensureTodoTaskExists(cmd.Context(), taskID); err != nil { + return err + } + } return callMCPTool("list_todo_attachment", map[string]any{ "todoAttachmentListRequest": map[string]any{ - "taskId": taskId, + "taskId": taskID, }, }) }, diff --git a/internal/helpers/todo_command_edges_more_test.go b/internal/helpers/todo_command_edges_more_test.go index 2dd45bf1..5897a35a 100644 --- a/internal/helpers/todo_command_edges_more_test.go +++ b/internal/helpers/todo_command_edges_more_test.go @@ -97,20 +97,35 @@ func TestTodoSimpleCommandValidationAndSuccessEdges(t *testing.T) { } } - validCases := [][]string{ - {"task", "list-sub", "--task-id", "1"}, - {"task", "done", "--task-id", "1", "--status", "true"}, - {"task", "add-executor", "--task-id", "1", "--executors", "u1,u2"}, - {"task", "remove-executor", "--task-id", "1", "--executors", "u1,u2"}, - {"task", "add-participant", "--task-id", "1", "--participants", "u1,u2"}, - {"task", "remove-participant", "--task-id", "1", "--participants", "u1,u2"}, - {"task", "list-attachment", "--task-id", "1"}, - {"comment", "add", "--task-id", "1", "--content", "hello"}, - {"comment", "list", "--task-id", "1"}, + validCases := []struct { + args []string + steps []scriptedToolStep + }{ + {args: []string{"task", "list-sub", "--task-id", "1"}}, + { + args: []string{"task", "done", "--task-id", "1", "--status", "true"}, + steps: []scriptedToolStep{ + {text: `{"result":{"todoDetailModel":{"taskId":"1"}}}`}, + {text: `{}`}, + }, + }, + {args: []string{"task", "add-executor", "--task-id", "1", "--executors", "u1,u2"}}, + {args: []string{"task", "remove-executor", "--task-id", "1", "--executors", "u1,u2"}}, + {args: []string{"task", "add-participant", "--task-id", "1", "--participants", "u1,u2"}}, + {args: []string{"task", "remove-participant", "--task-id", "1", "--participants", "u1,u2"}}, + { + args: []string{"task", "list-attachment", "--task-id", "1"}, + steps: []scriptedToolStep{ + {text: `{"result":{"todoDetailModel":{"taskId":"1"}}}`}, + {text: `{}`}, + }, + }, + {args: []string{"comment", "add", "--task-id", "1", "--content", "hello"}}, + {args: []string{"comment", "list", "--task-id", "1"}}, } - for _, args := range validCases { - if err := executeTodoEdge(t, &scriptedToolCaller{}, args...); err != nil { - t.Fatalf("execute %v: %v", args, err) + for _, tc := range validCases { + if err := executeTodoEdge(t, &scriptedToolCaller{steps: tc.steps}, tc.args...); err != nil { + t.Fatalf("execute %v: %v", tc.args, err) } } }