Files
539d10f80f feat: 合并 pre-mcp-discovery 分支能力到 main(服务发现保持生产) (#391)
将 test/pre-mcp-discovery 分支上多位同学的能力改动整体合入 main,
服务发现端点保持生产 https://mcp.dingtalk.com,未携带分支上的预发改动。

主要内容:
- 修雨域(chat / report / todo / contact / mail)对齐 wukong 基线 (#355)
- DWS 内容生产能力增强 (#387)、文档导出与 OA 对齐 (#362 #388 #390)
- envelope 注册 cli.Aliases 为 cobra aliases、命令结构与 JSON 解析优化
- skills 多包同步 wukong 对齐内容(attendance/calendar/minutes/oa/sheet 等)
- product/oa 进一步对齐 wukong

排除项:未携带 566b4e1(test: point service discovery to pre-mcp.dingtalk.com);
以下 4 个常量保持生产:skill_command.go / auth/endpoints.go / cli/loader.go / market/registry.go。

Co-authored-by: john <32427341+wqyenjoy@users.noreply.github.com>
Co-authored-by: wxianfeng <wang.fl1429@gmail.com>
2026-06-02 21:04:12 +08:00

115 lines
4.4 KiB
Go

// Copyright 2026 Alibaba Group
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// todo_hooks.go — CLI-side validators for the `todo` product. The envelope
// describes PersonalTodoCreateVO.parentId as a plain string flag (`--parent-id`)
// and the upstream MCP tool create_personal_sub_todo silently accepts any
// non-empty value: when a non-numeric string slips through, the server treats
// the missing numeric parent as "no parent" and creates an *orphan* root-level
// todo instead of failing. The auto-test
// todo/test_03_todo_create_sub.py::test_create_sub_todo_invalid_parent_id
// expects the CLI to reject the invalid value before it ever reaches MCP.
//
// The wukong reference implementation already does the same check inside its
// hand-written cobra RunE (see dws-wukong/wukong/products/todo.go ~line 90:
// strconv.ParseInt + CLIError with "父待办 ID 必须是纯数字, 当前值: ..."). The
// open-source CLI is envelope-driven, so we attach the equivalent guard as a
// PreRunE hook here. Empty parent-id is intentionally NOT validated here —
// envelope already marks it required, so cobra's MarkFlagRequired handles the
// missing case with the standard "required flag(s) ... not set" message.
package compat
import (
"fmt"
"strconv"
"strings"
apperrors "github.com/DingTalk-Real-AI/dingtalk-workspace-cli/internal/errors"
"github.com/spf13/cobra"
)
// todoToolsWithNumericParentId lists every todo toolName whose --parent-id
// must be coerced to a pure-numeric long. Today only create_personal_sub_todo
// needs this; if future tools (e.g. add_sub_todo) join, append here.
var todoToolsWithNumericParentId = map[string]bool{
"create_personal_sub_todo": true,
}
// installTodoHook wires todo-specific PreRunE validators onto leaf commands
// emitted by BuildDynamicCommands. It is a no-op for non-todo products and
// for todo tools that do not need extra client-side checks.
//
// The hook chain preserves the cmd.PreRunE that NewDirectCommand already
// installed (currently validateRequireTogether) by invoking it first.
func installTodoHook(cmd *cobra.Command, canonicalProduct, toolName string) {
if cmd == nil {
return
}
if strings.TrimSpace(canonicalProduct) != "todo" {
return
}
if !todoToolsWithNumericParentId[toolName] {
return
}
original := cmd.PreRunE
cmd.PreRunE = func(c *cobra.Command, args []string) error {
if original != nil {
if err := original(c, args); err != nil {
return err
}
}
return validateTodoParentIdNumeric(c)
}
}
// validateTodoParentIdNumeric inspects --parent-id; if non-empty it must
// parse as int64. Empty values are passed through so cobra's MarkFlagRequired
// (driven by the envelope's `"required": true`) still owns the missing-flag
// error message, matching the existing UX for other required flags.
//
// Error wording mirrors wukong (dws-wukong/wukong/products/todo.go ~L97) so
// agents and humans see a stable message across both editions. The
// apperrors.NewValidation wrapper guarantees stderr renders as
// "Error: [VALIDATION] ..." (PrintHumanAt) or `{"error":{...}}` (PrintJSON),
// both of which satisfy the auto-test substring assertion
// `"error" in result.stderr.lower()`.
func validateTodoParentIdNumeric(cmd *cobra.Command) error {
if cmd == nil {
return nil
}
flag := cmd.Flags().Lookup("parent-id")
if flag == nil {
return nil
}
raw, err := cmd.Flags().GetString("parent-id")
if err != nil {
// Flag exists but type is not string — defensive no-op, do not block.
return nil
}
v := strings.TrimSpace(raw)
if v == "" {
return nil
}
if _, parseErr := strconv.ParseInt(v, 10, 64); parseErr != nil {
return apperrors.NewValidation(
fmt.Sprintf("父待办 ID 必须是纯数字, 当前值: %s", v),
apperrors.WithReason("invalid_parent_id"),
apperrors.WithHint("请通过 'dws todo task list' 获取正确的父待办任务 ID。"),
apperrors.WithOperation("todo.task.create-sub.parent-id"),
)
}
return nil
}