Files
昭逸 721a40b05e fix(oa): declare attachment result contracts
- add success and failure outcomes for three attachment commands
- define business data schemas and mark downloadUri as sensitive
- migrate attachment commands to unified result output
- verify compact and full Schema result projections
- cover success, malformed response, and tool error paths
to #666
2026-08-18 10:41:21 +08:00

1666 lines
71 KiB
Go
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
package helpers
import (
"bytes"
"encoding/json"
"errors"
"fmt"
"io"
"strconv"
"strings"
"github.com/DingTalk-Real-AI/dingtalk-workspace-cli/internal/cli"
"github.com/DingTalk-Real-AI/dingtalk-workspace-cli/internal/corecmd"
"github.com/DingTalk-Real-AI/dingtalk-workspace-cli/internal/corecmd/contract"
apperrors "github.com/DingTalk-Real-AI/dingtalk-workspace-cli/internal/errors"
"github.com/DingTalk-Real-AI/dingtalk-workspace-cli/internal/output"
"github.com/spf13/cobra"
)
func decodeOARequest(raw string) (map[string]any, error) {
dec := json.NewDecoder(bytes.NewBufferString(raw))
dec.UseNumber()
var request map[string]any
if err := dec.Decode(&request); err != nil || request == nil {
if err != nil {
return nil, err
}
return nil, fmt.Errorf("JSON 请求不能为 null")
}
if err := dec.Decode(new(any)); !errors.Is(err, io.EOF) {
return nil, fmt.Errorf("JSON 请求包含多余内容")
}
return request, nil
}
func oaFormValues(raw string) ([]map[string]string, error) {
var values map[string]string
if err := json.Unmarshal([]byte(raw), &values); err != nil {
return nil, err
}
result := make([]map[string]string, 0, len(values))
for name, value := range values {
result = append(result, map[string]string{"name": name, "value": value})
}
return result, nil
}
func parseOAAttachmentFileInfos(raw string) ([]map[string]any, error) {
decoder := json.NewDecoder(strings.NewReader(raw))
decoder.UseNumber()
var items []map[string]any
if err := decoder.Decode(&items); err != nil {
return nil, fmt.Errorf("--file-infos JSON 解析失败: %w", err)
}
if err := rejectTrailingOAAttachmentJSON(decoder); err != nil {
return nil, err
}
if len(items) < 1 || len(items) > 10 {
return nil, fmt.Errorf("--file-infos 必须包含 1 至 10 个文件")
}
infos := make([]map[string]any, 0, len(items))
for index, item := range items {
for name := range item {
if name != "spaceId" && name != "fileId" {
return nil, fmt.Errorf("--file-infos 第 %d 项包含未知字段 %q", index+1, name)
}
}
spaceValue, ok := item["spaceId"]
if !ok {
return nil, fmt.Errorf("--file-infos 第 %d 项缺少 spaceId", index+1)
}
spaceID, ok := spaceValue.(json.Number)
if !ok {
return nil, fmt.Errorf("--file-infos 第 %d 项 spaceId 必须是数字", index+1)
}
fileValue, ok := item["fileId"]
if !ok {
return nil, fmt.Errorf("--file-infos 第 %d 项缺少 fileId", index+1)
}
fileID, ok := fileValue.(string)
if !ok {
return nil, fmt.Errorf("--file-infos 第 %d 项 fileId 必须是字符串", index+1)
}
fileID = strings.TrimSpace(fileID)
if fileID == "" {
return nil, fmt.Errorf("--file-infos 第 %d 项 fileId 不能为空", index+1)
}
infos = append(infos, map[string]any{"spaceId": spaceID, "fileId": fileID})
}
return infos, nil
}
func rejectTrailingOAAttachmentJSON(decoder *json.Decoder) error {
var extra any
if err := decoder.Decode(&extra); err != io.EOF {
if err == nil {
return fmt.Errorf("--file-infos JSON 包含多余内容")
}
return fmt.Errorf("--file-infos JSON 解析失败: %w", err)
}
return nil
}
func validateOAAttachmentFileInfos(cmd *cobra.Command, _ []string) error {
raw, _ := cmd.Flags().GetString("file-infos")
_, err := parseOAAttachmentFileInfos(raw)
return err
}
func validateOAPreviewFileIDs(cmd *cobra.Command, _ []string) error {
fileIDs, _ := cmd.Flags().GetStringSlice("file-ids")
if len(fileIDs) > 20 {
return fmt.Errorf("--file-ids 最多包含 20 个附件 ID")
}
for index, fileID := range fileIDs {
if strings.TrimSpace(fileID) == "" {
return fmt.Errorf("--file-ids 第 %d 项不能为空", index+1)
}
}
return nil
}
func callOAAttachmentResult(cmd *cobra.Command, tool string, args map[string]any) (output.CommandResult, error) {
data, err := CallMCPToolDataOnServer(cmd.Context(), "oa", tool, args)
if err != nil {
return nil, err
}
response, ok := data.(map[string]any)
if !ok {
return nil, apperrors.NewInternal(fmt.Sprintf("oa/%s 返回值不是 JSON 对象", tool))
}
result, ok := response["result"]
if !ok {
return nil, apperrors.NewInternal(fmt.Sprintf("oa/%s 返回值缺少 result", tool))
}
return output.Success(result), nil
}
func newOAAttachmentCommand() *cobra.Command {
attachmentCmd := &cobra.Command{
Use: "attachment",
Short: "审批附件授权与下载链接",
RunE: groupRunE,
}
downloadURLCmd := NewLeafCommand(LeafSpec{
Use: "download-url",
Short: "获取审批附件下载链接",
Example: " dws oa approval attachment download-url --instance-id <processInstanceId> --file-id <fileId>",
Server: "oa",
Tool: "get_attachment_download_url",
OutputRollout: output.RolloutUnifiedActive,
ResultCall: callOAAttachmentResult,
Flags: []LeafFlag{
{Name: "instance-id", Usage: "审批实例 ID (必填)", Bind: "processInstanceId", Trim: true, Required: true, MarkRequired: true},
{Name: "file-id", Usage: "审批附件文件 ID (必填)", Bind: "fileId", Trim: true, Required: true, MarkRequired: true},
{Name: "with-comment-attachment", Usage: "是否包含评论中的附件", Kind: LeafBool, Bind: "withCommentAttachment"},
},
Safety: contract.SafetySpec{
Effect: "read", Risk: "low",
Confirmation: "not_required", Idempotency: "idempotent",
},
Contract: LeafContract{
Identity: contract.ToolIdentitySpec{
ProductID: "oa",
Name: "get_attachment_download_url",
CanonicalPath: "oa.get_attachment_download_url",
CLIPath: "oa approval attachment download-url",
PrimaryCLIPath: "oa approval attachment download-url",
},
Description: "获取审批附件下载授权并生成临时下载链接",
Result: &contract.ResultSpec{
Outcomes: []contract.ResultOutcome{contract.ResultOutcomeSuccess, contract.ResultOutcomeFailure},
DataSchema: json.RawMessage(`{"type":"object","description":"审批附件临时下载信息","properties":{"spaceId":{"type":"integer","description":"审批附件所在钉盘空间 ID"},"agentId":{"type":"integer","description":"审批应用 Agent ID"},"downloadUri":{"type":"string","description":"带临时授权签名的附件下载链接"},"class":{"type":"string","description":"服务端响应类型标识"},"fileId":{"type":"string","description":"审批附件文件 ID"}},"required":["spaceId","agentId","downloadUri","fileId"],"additionalProperties":true}`),
SensitivePaths: []string{"downloadUri"},
},
Interface: &contract.InterfaceSpec{
Mode: "mcp",
Availability: "available",
Ref: &contract.InterfaceRefSpec{ProductID: "oa", RPCName: "get_attachment_download_url"},
},
Selection: contract.SelectionSpec{
AgentSummary: "获取审批实例中指定附件的临时下载链接",
UseWhen: []string{"已从审批详情获得 processInstanceId 和 fileId,需要获取附件下载链接时"},
AvoidWhen: []string{
"只需查看审批表单和附件元数据时使用 dws oa approval detail",
"需要将附件真正保存到本地时不要误认为本命令会下载文件;它只返回链接",
},
Examples: []string{"dws oa approval attachment download-url --instance-id <processInstanceId> --file-id <fileId>"},
},
Parameters: []contract.ParamDecl{
{Name: "instance-id", Property: "processInstanceId", InterfaceType: "string"},
{Name: "file-id", Property: "fileId", InterfaceType: "string"},
{Name: "with-comment-attachment", Property: "withCommentAttachment", InterfaceType: "boolean"},
},
},
})
authorizeDownloadCmd := NewLeafCommand(LeafSpec{
Use: "authorize-download",
Short: "授权当前用户下载审批钉盘文件",
Long: "批量授权当前用户下载指定的审批钉盘文件。",
Example: ` dws oa approval attachment authorize-download --file-infos '[{"spaceId":27827223951,"fileId":"232271651278"}]'`,
Server: "oa",
Tool: "auth_download_file",
OutputRollout: output.RolloutUnifiedActive,
ResultCall: callOAAttachmentResult,
Flags: []LeafFlag{
{
Name: "file-infos", Usage: "审批钉盘文件信息 JSON 数组 (必填)",
Bind: "fileInfos", Trim: true, Required: true, MarkRequired: true,
Transform: func(raw string) (any, error) {
return parseOAAttachmentFileInfos(raw)
},
},
},
Constraints: []LeafConstraint{{
Kind: corecmd.Custom, Flags: []string{"file-infos"},
Description: "文件信息列表必须包含 1 至 10 项",
}},
Validate: validateOAAttachmentFileInfos,
Safety: contract.SafetySpec{
Effect: "write", Risk: "low",
Confirmation: "not_required", Idempotency: "idempotent",
},
Contract: LeafContract{
Identity: contract.ToolIdentitySpec{
ProductID: "oa",
Name: "auth_download_file",
CanonicalPath: "oa.auth_download_file",
CLIPath: "oa approval attachment authorize-download",
PrimaryCLIPath: "oa approval attachment authorize-download",
},
Description: "批量授权当前用户下载指定的审批钉盘文件",
Result: &contract.ResultSpec{
Outcomes: []contract.ResultOutcome{contract.ResultOutcomeSuccess, contract.ResultOutcomeFailure},
DataSchema: json.RawMessage(`{"type":"boolean","description":"是否成功为当前用户授予审批钉盘文件下载权限"}`),
},
Interface: &contract.InterfaceSpec{
Mode: "mcp",
Availability: "available",
Ref: &contract.InterfaceRefSpec{ProductID: "oa", RPCName: "auth_download_file"},
},
Selection: contract.SelectionSpec{
AgentSummary: "使用审批钉盘 spaceId/fileId 批量授权当前用户下载文件",
UseWhen: []string{"已有一个或多个审批钉盘文件的 spaceId 和 fileId,需要为当前用户取得下载权限时"},
AvoidWhen: []string{
"需要生成单个审批附件下载链接时使用 attachment download-url",
"需要授权在审批单内预览附件时使用 attachment authorize-preview",
},
Examples: []string{`dws oa approval attachment authorize-download --file-infos '[{"spaceId":27827223951,"fileId":"232271651278"}]'`},
},
Parameters: []contract.ParamDecl{
{Name: "file-infos", Property: "fileInfos", InterfaceType: "array"},
},
},
})
authorizePreviewCmd := NewLeafCommand(LeafSpec{
Use: "authorize-preview",
Short: "授权当前用户预览审批附件",
Long: "批量授权当前用户预览审批单中的附件。",
Example: " dws oa approval attachment authorize-preview --instance-id <processInstanceId> --file-ids <fileId1>,<fileId2>",
Server: "oa",
Tool: "auth_preview_attachment",
OutputRollout: output.RolloutUnifiedActive,
ResultCall: callOAAttachmentResult,
Flags: []LeafFlag{
{Name: "instance-id", Usage: "审批实例 ID (必填)", Bind: "processInstanceId", Trim: true, Required: true, MarkRequired: true},
{Name: "file-ids", Usage: "附件 ID 列表,多个用逗号分隔 (必填)", Kind: LeafStringSlice, Bind: "fileIdList", Required: true, MarkRequired: true},
{Name: "with-comment-attachment", Usage: "是否包含评论中的附件", Kind: LeafBool, Bind: "withCommentAttachment"},
},
Constraints: []LeafConstraint{{
Kind: corecmd.Custom, Flags: []string{"file-ids"},
Description: "附件 ID 列表最多包含 20 项且每项不能为空",
}},
Validate: validateOAPreviewFileIDs,
Safety: contract.SafetySpec{
Effect: "write", Risk: "low",
Confirmation: "not_required", Idempotency: "idempotent",
},
Contract: LeafContract{
Identity: contract.ToolIdentitySpec{
ProductID: "oa",
Name: "auth_preview_attachment",
CanonicalPath: "oa.auth_preview_attachment",
CLIPath: "oa approval attachment authorize-preview",
PrimaryCLIPath: "oa approval attachment authorize-preview",
},
Description: "批量授权当前用户预览审批单中的附件",
Result: &contract.ResultSpec{
Outcomes: []contract.ResultOutcome{contract.ResultOutcomeSuccess, contract.ResultOutcomeFailure},
DataSchema: json.RawMessage(`{"type":"object","description":"审批附件预览授权信息","properties":{"spaceId":{"type":"integer","description":"审批附件所在钉盘空间 ID"},"agentId":{"type":"integer","description":"审批应用 Agent ID"},"class":{"type":"string","description":"服务端响应类型标识"}},"required":["spaceId","agentId"],"additionalProperties":true}`),
},
Interface: &contract.InterfaceSpec{
Mode: "mcp",
Availability: "available",
Ref: &contract.InterfaceRefSpec{ProductID: "oa", RPCName: "auth_preview_attachment"},
},
Selection: contract.SelectionSpec{
AgentSummary: "按审批实例和附件 ID 列表授权当前用户预览附件",
UseWhen: []string{"已有 processInstanceId 和附件 fileId 列表,需要在审批场景中批量取得预览权限时"},
AvoidWhen: []string{
"需要下载权限而不是预览权限时使用 attachment authorize-download",
"需要直接获得单个附件临时下载链接时使用 attachment download-url",
},
Examples: []string{"dws oa approval attachment authorize-preview --instance-id <processInstanceId> --file-ids <fileId1>,<fileId2>"},
},
Parameters: []contract.ParamDecl{
{Name: "instance-id", Property: "processInstanceId", InterfaceType: "string"},
{Name: "file-ids", Property: "fileIdList", InterfaceType: "array"},
{Name: "with-comment-attachment", Property: "withCommentAttachment", InterfaceType: "boolean"},
},
},
})
attachmentCmd.AddCommand(downloadURLCmd, authorizeDownloadCmd, authorizePreviewCmd)
return attachmentCmd
}
// ──────────────────────────────────────────────────────────
// dws oa — OA 审批
// MCP tools(tools/list): list_pending_approvals, get_processInstance_detail,
// approve_processInstance, reject_processInstance, revoke_processInstance,
// get_processInstance_records, list_initiated_instances, list_pending_tasks,
// list_user_visible_process, append_task, search_form, oa_ding_user, revert_task,
// get_inst_revert_activities, get_process_schema, forecast_process,
// start_process_instance, get_attachment_download_url, auth_download_file,
// auth_preview_attachment
// ──────────────────────────────────────────────────────────
func newOaCommand() *cobra.Command {
// Product-level Agent routing Decl (migrated from selection/oa.json
// products.oa). Catalog assembly stamps provenance contract_final.
contract.RegisterProductDecl(contract.ProductDecl{
ID: "oa",
Selection: contract.ProductSelectionDecl{
AgentSummary: "查询和处理 OA 审批实例、任务、记录、抄送、评论与附件授权",
UseWhen: []string{
"查看待审、已办、已发起或抄送审批,并执行同意、拒绝、撤销、转交等审批动作时",
"获取审批附件下载链接,或为当前用户授权下载、预览审批附件时",
},
AvoidWhen: []string{
"不要用于普通待办任务或工作日志;需要实时监听未来的审批任务/实例事件时使用 event consume",
},
},
})
root := &cobra.Command{
Use: "oa",
Short: "OA 审批 / 同意 / 拒绝 / 撤销",
Long: `管理钉钉 OA 审批:待办查询、审批详情、同意、拒绝、撤销、操作记录、已发起列表、表单列表与附件授权。`,
RunE: groupRunE,
}
approvalCmd := &cobra.Command{Use: "approval", Short: "审批管理", RunE: groupRunE}
approvalListPendingCmd := &cobra.Command{
Use: "list-pending",
Short: "查询待我处理的审批",
Example: ` dws oa approval list-pending --start "2026-03-10T00:00:00+08:00" --end "2026-03-10T23:59:59+08:00" --query 关键词`,
RunE: func(cmd *cobra.Command, args []string) error {
startMs, err := parseISOTimeToMillis("start", mustGetFlag(cmd, "start"))
if err != nil {
return err
}
endMs, err := parseISOTimeToMillis("end", mustGetFlag(cmd, "end"))
if err != nil {
return err
}
if err := validateTimeRange(startMs, endMs); err != nil {
return err
}
argsMap := map[string]any{
"starTime": float64(startMs),
"endTime": float64(endMs),
}
if v, _ := cmd.Flags().GetString("page"); v != "" {
if n, err := strconv.ParseFloat(v, 64); err == nil {
argsMap["pageNum"] = n
}
}
if v := flagOrFallback(cmd, "limit", "size"); v != "" {
if n, err := strconv.ParseFloat(v, 64); err == nil {
argsMap["pageSize"] = n
}
}
if v, _ := cmd.Flags().GetString("query"); v != "" {
argsMap["query"] = v
}
return callMCPTool("list_pending_approvals", argsMap)
},
}
DeclareLeafMetadata(approvalListPendingCmd, LeafSpec{
Safety: contract.SafetySpec{
Effect: "read", Risk: "low",
Confirmation: "not_required", Idempotency: "idempotent",
},
Contract: LeafContract{
Identity: contract.ToolIdentitySpec{
ProductID: "oa",
Name: "list_pending_approvals",
CanonicalPath: "oa.list_pending_approvals",
CLIPath: "oa approval list-pending",
PrimaryCLIPath: "oa approval list-pending",
},
Description: "查询当前用户待处理的审批单列表",
Interface: &contract.InterfaceSpec{
Mode: "mcp",
Availability: "available",
Ref: &contract.InterfaceRefSpec{ProductID: "oa", RPCName: "list_pending_approvals"},
},
Selection: contract.SelectionSpec{
AgentSummary: "查询当前用户待处理的审批单列表",
UseWhen: []string{"需要查看待我处理的审批单并提取 processInstanceId / 跳转链接时"},
AvoidWhen: []string{
"已知实例只要 taskId 时改用 dws oa approval tasks",
"不要用本命令执行同意/拒绝",
},
Examples: []string{
"dws oa approval list-pending --start \"2026-03-10T00:00:00+08:00\" --end \"2026-03-10T23:59:59+08:00\"",
"dws oa approval list-pending --start \"2026-03-10T00:00:00+08:00\" --end \"2026-03-10T23:59:59+08:00\" --query 关键词",
},
},
Parameters: []contract.ParamDecl{
{Name: "end", Property: "endTime"},
{Name: "limit", Property: "pageSize"},
{Name: "page", Property: "pageNum"},
{Name: "start", Property: "starTime"},
},
},
})
approvalDetailCmd := &cobra.Command{
Use: "detail",
Short: "获取审批实例详情",
Example: ` dws oa approval detail --instance-id <processInstanceId> # 查询 instanceId: dws oa approval list-pending`,
RunE: func(cmd *cobra.Command, args []string) error {
if err := validateRequiredFlags(cmd, "instance-id"); err != nil {
return err
}
return callMCPTool("get_processInstance_detail", map[string]any{
"processInstanceId": mustGetFlag(cmd, "instance-id"),
})
},
}
DeclareLeafMetadata(approvalDetailCmd, LeafSpec{
Safety: contract.SafetySpec{
Effect: "read", Risk: "low",
Confirmation: "not_required", Idempotency: "idempotent",
},
Contract: LeafContract{
Identity: contract.ToolIdentitySpec{
ProductID: "oa",
Name: "get_processInstance_detail",
CanonicalPath: "oa.get_processInstance_detail",
CLIPath: "oa approval detail",
PrimaryCLIPath: "oa approval detail",
},
Description: "获取指定审批实例的详情信息",
Interface: &contract.InterfaceSpec{
Mode: "mcp",
Availability: "available",
Ref: &contract.InterfaceRefSpec{ProductID: "oa", RPCName: "get_processInstance_detail"},
},
Selection: contract.SelectionSpec{
AgentSummary: "获取指定审批实例的详情信息",
UseWhen: []string{"已知 processInstanceId,需要查看表单内容与当前状态详情时"},
AvoidWhen: []string{
"需要操作历史而非表单详情时改用 dws oa approval records",
"该命令不会同意/拒绝/撤销",
},
Examples: []string{
"dws oa approval detail --instance-id <processInstanceId>",
"dws oa approval detail --instance-id <processInstanceId> --format json",
},
},
Parameters: []contract.ParamDecl{
{Name: "instance-id", Property: "processInstanceId"},
},
},
})
approvalApproveCmd := &cobra.Command{
Use: "approve",
Short: "同意审批",
Example: ` dws oa approval approve --instance-id <id> --task-id <taskId> # 查询 instanceId: dws oa approval list-pending; taskId 来自 dws oa approval tasks
dws oa approval approve --instance-id <id> --task-id <taskId> --remark "同意"`,
RunE: func(cmd *cobra.Command, args []string) error {
if err := validateRequiredFlags(cmd, "instance-id", "task-id"); err != nil {
return err
}
taskIdNum, _ := strconv.ParseFloat(mustGetFlag(cmd, "task-id"), 64)
argsMap := map[string]any{
"processInstanceId": mustGetFlag(cmd, "instance-id"),
"taskId": taskIdNum,
}
if v, _ := cmd.Flags().GetString("remark"); v != "" {
argsMap["remark"] = v
}
return callMCPTool("approve_processInstance", argsMap)
},
}
DeclareLeafMetadata(approvalApproveCmd, LeafSpec{
Safety: contract.SafetySpec{
Effect: "write", Risk: "medium",
Confirmation: "not_required", Idempotency: "unknown",
},
Contract: LeafContract{
Identity: contract.ToolIdentitySpec{
ProductID: "oa",
Name: "approve_processInstance",
CanonicalPath: "oa.approve_processInstance",
CLIPath: "oa approval approve",
PrimaryCLIPath: "oa approval approve",
},
Description: "同意审批",
Interface: &contract.InterfaceSpec{
Mode: "mcp",
Availability: "available",
Ref: &contract.InterfaceRefSpec{ProductID: "oa", RPCName: "approve_processInstance"},
},
Selection: contract.SelectionSpec{
AgentSummary: "同意审批",
UseWhen: []string{"已知 processInstanceId 与待办 taskId,用户明确要求同意该审批任务时"},
AvoidWhen: []string{
"实例或 taskId 未确认时不要同意;先用 list-pending / tasks 取 ID",
"要拒绝时改用 dws oa approval reject;要撤销自己发起的单时改用 revoke",
},
Examples: []string{
"dws oa approval approve --instance-id <id> --task-id <taskId>",
"dws oa approval approve --instance-id <id> --task-id <taskId> --remark \"同意\"",
},
},
Parameters: []contract.ParamDecl{
{Name: "instance-id", Property: "processInstanceId"},
},
},
})
approvalRejectCmd := &cobra.Command{
Use: "reject",
Short: "拒绝审批",
Example: ` dws oa approval reject --instance-id <id> --task-id <taskId> --remark "不同意" # 查询 instanceId: dws oa approval list-pending; taskId 来自 dws oa approval tasks`,
RunE: func(cmd *cobra.Command, args []string) error {
if err := validateRequiredFlags(cmd, "instance-id", "task-id"); err != nil {
return err
}
taskIdNum, _ := strconv.ParseFloat(mustGetFlag(cmd, "task-id"), 64)
argsMap := map[string]any{
"processInstanceId": mustGetFlag(cmd, "instance-id"),
"taskId": taskIdNum,
}
if v, _ := cmd.Flags().GetString("remark"); v != "" {
argsMap["remark"] = v
}
return callMCPTool("reject_processInstance", argsMap)
},
}
DeclareLeafMetadata(approvalRejectCmd, LeafSpec{
Safety: contract.SafetySpec{
Effect: "write", Risk: "medium",
Confirmation: "not_required", Idempotency: "unknown",
},
Contract: LeafContract{
Identity: contract.ToolIdentitySpec{
ProductID: "oa",
Name: "reject_processInstance",
CanonicalPath: "oa.reject_processInstance",
CLIPath: "oa approval reject",
PrimaryCLIPath: "oa approval reject",
},
Description: "拒绝审批",
Interface: &contract.InterfaceSpec{
Mode: "mcp",
Availability: "available",
Ref: &contract.InterfaceRefSpec{ProductID: "oa", RPCName: "reject_processInstance"},
},
Selection: contract.SelectionSpec{
AgentSummary: "拒绝审批",
UseWhen: []string{"已知 processInstanceId 与 taskId,用户明确要求拒绝/驳回该审批任务时"},
AvoidWhen: []string{
"要同意时改用 dws oa approval approve",
"实例、taskId 或拒绝原因未确认时不要拒绝",
},
Examples: []string{
"dws oa approval reject --instance-id <id> --task-id <taskId> --remark \"不同意\"",
"dws oa approval reject --instance-id <id> --task-id <taskId> --remark \"不符合要求\" --format json",
},
},
Parameters: []contract.ParamDecl{
{Name: "instance-id", Property: "processInstanceId"},
},
},
})
approvalRevokeCmd := &cobra.Command{
Use: "revoke",
Short: "撤销已发起的审批",
Example: ` dws oa approval revoke --instance-id <id> --yes # 查询 instanceId: dws oa approval list-pending
dws oa approval revoke --instance-id <id> --remark "误发起" --yes`,
RunE: func(cmd *cobra.Command, args []string) error {
if err := validateRequiredFlags(cmd, "instance-id"); err != nil {
return err
}
instanceId := mustGetFlag(cmd, "instance-id")
argsMap := map[string]any{
"processInstanceId": instanceId,
}
if v, _ := cmd.Flags().GetString("remark"); v != "" {
argsMap["remark"] = v
}
return callMCPTool("revoke_processInstance", argsMap)
},
}
DeclareLeafMetadata(approvalRevokeCmd, LeafSpec{
Safety: contract.SafetySpec{
Effect: "write", Risk: "high",
Confirmation: "user_required", Idempotency: "unknown",
},
Contract: LeafContract{
Identity: contract.ToolIdentitySpec{
ProductID: "oa",
Name: "revoke_processInstance",
CanonicalPath: "oa.revoke_processInstance",
CLIPath: "oa approval revoke",
PrimaryCLIPath: "oa approval revoke",
},
Description: "撤销当前用户已发起的审批实例",
Interface: &contract.InterfaceSpec{
Mode: "mcp",
Availability: "available",
Ref: &contract.InterfaceRefSpec{ProductID: "oa", RPCName: "revoke_processInstance"},
},
Selection: contract.SelectionSpec{
AgentSummary: "撤销当前用户已发起的审批实例",
UseWhen: []string{"用户明确要求撤销自己已发起的审批实例,且 processInstanceId 已确认时"},
AvoidWhen: []string{
"不是自己发起的单或 instanceId 未确认时不要撤销",
"要拒绝别人的待办时改用 reject,不要用 revoke",
},
Examples: []string{"dws oa approval revoke --instance-id <instance-id>"},
},
Parameters: []contract.ParamDecl{
{Name: "instance-id", Property: "processInstanceId"},
},
},
})
approvalRecordsCmd := &cobra.Command{
Use: "records",
Short: "获取审批操作记录",
Example: ` dws oa approval records --instance-id <processInstanceId> # 查询 instanceId: dws oa approval list-pending`,
RunE: func(cmd *cobra.Command, args []string) error {
if err := validateRequiredFlags(cmd, "instance-id"); err != nil {
return err
}
return callMCPTool("get_processInstance_records", map[string]any{
"processInstanceId": mustGetFlag(cmd, "instance-id"),
})
},
}
DeclareLeafMetadata(approvalRecordsCmd, LeafSpec{
Safety: contract.SafetySpec{
Effect: "read", Risk: "low",
Confirmation: "not_required", Idempotency: "idempotent",
},
Contract: LeafContract{
Identity: contract.ToolIdentitySpec{
ProductID: "oa",
Name: "get_processInstance_records",
CanonicalPath: "oa.get_processInstance_records",
CLIPath: "oa approval records",
PrimaryCLIPath: "oa approval records",
},
Description: "获取审批操作记录",
Interface: &contract.InterfaceSpec{
Mode: "mcp",
Availability: "available",
Ref: &contract.InterfaceRefSpec{ProductID: "oa", RPCName: "get_processInstance_records"},
},
Selection: contract.SelectionSpec{
AgentSummary: "获取审批操作记录",
UseWhen: []string{"已知 processInstanceId,需要查看谁做了什么审批操作及结果时"},
AvoidWhen: []string{
"需要当前表单详情时改用 dws oa approval detail",
"该命令只读历史,不处理审批",
},
Examples: []string{
"dws oa approval records --instance-id <processInstanceId>",
"dws oa approval records --instance-id <processInstanceId> --format json",
},
},
Parameters: []contract.ParamDecl{
{Name: "instance-id", Property: "processInstanceId"},
},
},
})
approvalListInitiatedCmd := &cobra.Command{
Use: "list-initiated",
Short: "查询审批模板下已发起的审批记录",
Example: ` dws oa approval list-initiated --process-code <code> --start "2026-03-10T00:00:00+08:00" --end "2026-03-10T23:59:59+08:00" --cursor 0 --limit 20 # processCode 来自管理后台配置`,
RunE: func(cmd *cobra.Command, args []string) error {
if err := validateRequiredFlags(cmd, "process-code"); err != nil {
return err
}
startMs, err := parseISOTimeToMillis("start", mustGetFlag(cmd, "start"))
if err != nil {
return err
}
endMs, err := parseISOTimeToMillis("end", mustGetFlag(cmd, "end"))
if err != nil {
return err
}
if err := validateTimeRange(startMs, endMs); err != nil {
return err
}
nextToken, _ := strconv.ParseFloat(flagOrFallback(cmd, "cursor", "next-token"), 64)
maxResults, _ := strconv.ParseFloat(flagOrFallback(cmd, "limit", "max-results"), 64)
return callMCPTool("list_initiated_instances", map[string]any{
"processCode": mustGetFlag(cmd, "process-code"),
"startTime": float64(startMs),
"endTime": float64(endMs),
"nextToken": nextToken,
"maxResults": maxResults,
})
},
}
DeclareLeafMetadata(approvalListInitiatedCmd, LeafSpec{
Safety: contract.SafetySpec{
Effect: "read", Risk: "low",
Confirmation: "not_required", Idempotency: "idempotent",
},
Contract: LeafContract{
Identity: contract.ToolIdentitySpec{
ProductID: "oa",
Name: "list_initiated_instances",
CanonicalPath: "oa.list_initiated_instances",
CLIPath: "oa approval list-initiated",
PrimaryCLIPath: "oa approval list-initiated",
},
Description: "查询当前用户已发起的审批实例列表",
Interface: &contract.InterfaceSpec{
Mode: "mcp",
Availability: "available",
Ref: &contract.InterfaceRefSpec{ProductID: "oa", RPCName: "list_initiated_instances"},
},
Selection: contract.SelectionSpec{
AgentSummary: "查询当前用户已发起的审批实例列表",
UseWhen: []string{"已知 processCode,需要按起止时间查询自己发起的审批实例基础信息时"},
AvoidWhen: []string{
"不知道 processCode 时先用 dws oa approval list-forms",
"要撤销实例时先确认 instanceId 再改用 revoke",
},
Examples: []string{
"dws oa approval list-initiated --process-code <code> --start \"2026-03-10T00:00:00+08:00\" --end \"2026-03-10T23:59:59+08:00\" --cursor 0 --limit 20",
"dws oa approval list-initiated --process-code <code> --start \"2026-03-10T00:00:00+08:00\" --end \"2026-03-10T23:59:59+08:00\" --cursor 0 --limit 20 --format json",
},
},
Parameters: []contract.ParamDecl{
{Name: "cursor", Property: "nextToken"},
{Name: "end", Property: "endTime"},
{Name: "limit", Property: "maxResults"},
{Name: "start", Property: "startTime"},
},
},
})
approvalTasksCmd := &cobra.Command{
Use: "tasks",
Short: "查询待我审批的任务 ID",
Example: ` dws oa approval tasks --instance-id <processInstanceId> # 查询 instanceId: dws oa approval list-pending`,
RunE: func(cmd *cobra.Command, args []string) error {
if err := validateRequiredFlags(cmd, "instance-id"); err != nil {
return err
}
return callMCPTool("list_pending_tasks", map[string]any{
"processInstanceId": mustGetFlag(cmd, "instance-id"),
})
},
}
DeclareLeafMetadata(approvalTasksCmd, LeafSpec{
Safety: contract.SafetySpec{
Effect: "read", Risk: "low",
Confirmation: "not_required", Idempotency: "idempotent",
},
Contract: LeafContract{
Identity: contract.ToolIdentitySpec{
ProductID: "oa",
Name: "list_pending_tasks",
CanonicalPath: "oa.list_pending_tasks",
CLIPath: "oa approval tasks",
PrimaryCLIPath: "oa approval tasks",
},
Description: "查询待我审批的任务Id",
Interface: &contract.InterfaceSpec{
Mode: "mcp",
Availability: "available",
Ref: &contract.InterfaceRefSpec{ProductID: "oa", RPCName: "list_pending_tasks"},
},
Selection: contract.SelectionSpec{
AgentSummary: "查询待我审批的任务Id",
UseWhen: []string{"已知 processInstanceId,需要取得当前用户待办 taskId 以便同意或拒绝时"},
AvoidWhen: []string{
"还不知道实例列表时先用 dws oa approval list-pending",
"本命令只取 taskId,不执行审批",
},
Examples: []string{
"dws oa approval tasks --instance-id <processInstanceId>",
"dws oa approval tasks --instance-id <processInstanceId> --format json",
},
},
Parameters: []contract.ParamDecl{
{Name: "instance-id", Property: "processInstanceId"},
},
},
})
approvalListFormsCmd := &cobra.Command{
Use: "list-forms",
Short: "获取当前用户可见的审批表单列表",
Example: ` dws oa approval list-forms --cursor 0 --limit 100`,
RunE: func(cmd *cobra.Command, args []string) error {
cursor, _ := strconv.ParseFloat(mustGetFlag(cmd, "cursor"), 64)
pageSize, _ := strconv.ParseFloat(flagOrFallback(cmd, "limit", "size"), 64)
return callMCPTool("list_user_visible_process", map[string]any{
"cursor": cursor,
"pageSize": pageSize,
})
},
}
DeclareLeafMetadata(approvalListFormsCmd, LeafSpec{
Safety: contract.SafetySpec{
Effect: "read", Risk: "low",
Confirmation: "not_required", Idempotency: "idempotent",
},
Contract: LeafContract{
Identity: contract.ToolIdentitySpec{
ProductID: "oa",
Name: "list_user_visible_process",
CanonicalPath: "oa.list_user_visible_process",
CLIPath: "oa approval list-forms",
PrimaryCLIPath: "oa approval list-forms",
},
Description: "获取当前用户可见的审批表单列表",
Interface: &contract.InterfaceSpec{
Mode: "mcp",
Availability: "available",
Ref: &contract.InterfaceRefSpec{ProductID: "oa", RPCName: "list_user_visible_process"},
},
Selection: contract.SelectionSpec{
AgentSummary: "获取当前用户可见的审批表单列表",
UseWhen: []string{"需要列出可见审批模板并取得 processCode 时"},
AvoidWhen: []string{"需要实例、任务或操作记录时不要使用;该命令只列可见模板"},
Examples: []string{
"dws oa approval list-forms --cursor 0 --limit 100",
"dws oa approval list-forms --cursor 0 --limit 100 --format json",
},
},
Parameters: []contract.ParamDecl{
{Name: "limit", Property: "pageSize"},
},
},
})
// 模糊搜索表单(按 processCode 或 name 关键字)
approvalSearchFormsCmd := &cobra.Command{
Use: "search-forms",
Short: "按关键字模糊搜索当前用户可见的审批表单",
Example: ` dws oa approval search-forms --query AI
dws oa approval search-forms --query 报销`,
RunE: func(cmd *cobra.Command, args []string) error {
if err := validateRequiredFlags(cmd, "query"); err != nil {
return err
}
return callMCPTool("search_form", map[string]any{
"query": mustGetFlag(cmd, "query"),
})
},
}
// 获取审批任务的被催办人 userId
// 仅返回 userId;发 DING 的 robotCode 由 $DINGTALK_DING_ROBOT_CODE / --robot-code 提供,content 由 agent 撰写。
approvalDingInfoCmd := &cobra.Command{
Use: "ding-info",
Short: "获取审批任务的被催办人 userId(需与 ding message send 串联使用)",
Example: ` dws oa approval ding-info --task-id <taskId>
# 返回的 userId 作为 --users 传入 dws ding message send:
# dws ding message send --robot-code $DINGTALK_DING_ROBOT_CODE --users <userId逗号拼接> --content "请尽快审批"`,
RunE: func(cmd *cobra.Command, args []string) error {
if err := validateRequiredFlags(cmd, "task-id"); err != nil {
return err
}
return callMCPTool("oa_ding_user", map[string]any{
"taskId": mustGetFlag(cmd, "task-id"),
})
},
}
// 已经审批过的
approvalExecutedListCmd := &cobra.Command{
Use: "list-executed",
Short: "获取当前用户已经处理过的审批单列表",
Example: ` dws oa approval list-executed --limit 20 --page 1 --query 关键词`,
RunE: func(cmd *cobra.Command, args []string) error {
pageSize, _ := strconv.ParseFloat(mustGetFlag(cmd, "limit"), 64)
pageNumber, _ := strconv.ParseFloat(mustGetFlag(cmd, "page"), 64)
argsMap := map[string]any{
"pageNumber": pageNumber,
"pageSize": pageSize,
}
if v, _ := cmd.Flags().GetString("query"); v != "" {
argsMap["query"] = v
}
return callMCPTool("get_done_tasks", argsMap)
},
}
DeclareLeafMetadata(approvalExecutedListCmd, LeafSpec{
Safety: contract.SafetySpec{
Effect: "read", Risk: "low",
Confirmation: "not_required", Idempotency: "idempotent",
},
Contract: LeafContract{
Identity: contract.ToolIdentitySpec{
ProductID: "oa",
Name: "get_done_tasks",
CanonicalPath: "oa.get_done_tasks",
CLIPath: "oa approval list-executed",
PrimaryCLIPath: "oa approval list-executed",
},
Description: "获取员工已处理任务列表",
Interface: &contract.InterfaceSpec{
Mode: "mcp",
Availability: "available",
Ref: &contract.InterfaceRefSpec{ProductID: "oa", RPCName: "get_done_tasks"},
},
Selection: contract.SelectionSpec{
AgentSummary: "获取员工已处理任务列表",
UseWhen: []string{"需要查看当前用户已经处理过的审批单列表时"},
AvoidWhen: []string{
"要看待处理单时改用 dws oa approval list-pending",
"要看自己发起或抄送时改用 list-submitted / list-cc",
},
Examples: []string{
"dws oa approval list-executed --limit <pageSize> --page <pageNumber> --query 关键词",
"dws oa approval list-executed --limit <pageSize> --page <pageNumber> --query 关键词 --format json",
},
},
Parameters: []contract.ParamDecl{
{Name: "limit", Property: "pageSize"},
{Name: "page", Property: "pageNumber"},
},
},
})
// 已发起
approvalSubmittedListCmd := &cobra.Command{
Use: "list-submitted",
Short: "获取当前用户已发起的审批单列表",
Example: ` dws oa approval list-submitted --limit 20 --page 1 --query 关键词`,
RunE: func(cmd *cobra.Command, args []string) error {
pageSize, _ := strconv.ParseFloat(mustGetFlag(cmd, "limit"), 64)
pageNumber, _ := strconv.ParseFloat(mustGetFlag(cmd, "page"), 64)
argsMap := map[string]any{
"pageNumber": pageNumber,
"pageSize": pageSize,
}
if v, _ := cmd.Flags().GetString("query"); v != "" {
argsMap["query"] = v
}
return callMCPTool("get_submitted_instances", argsMap)
},
}
DeclareLeafMetadata(approvalSubmittedListCmd, LeafSpec{
Safety: contract.SafetySpec{
Effect: "read", Risk: "low",
Confirmation: "not_required", Idempotency: "idempotent",
},
Contract: LeafContract{
Identity: contract.ToolIdentitySpec{
ProductID: "oa",
Name: "get_submitted_instances",
CanonicalPath: "oa.get_submitted_instances",
CLIPath: "oa approval list-submitted",
PrimaryCLIPath: "oa approval list-submitted",
},
Description: "获取已提交实例列表",
Interface: &contract.InterfaceSpec{
Mode: "mcp",
Availability: "available",
Ref: &contract.InterfaceRefSpec{ProductID: "oa", RPCName: "get_submitted_instances"},
},
Selection: contract.SelectionSpec{
AgentSummary: "获取已提交实例列表",
UseWhen: []string{"需要查看当前用户已提交/发起相关的审批单列表时"},
AvoidWhen: []string{
"要按 processCode 与时间窗列已发起实例时也可对照 list-initiated",
"要处理待办时改用 list-pending / tasks",
},
Examples: []string{
"dws oa approval list-submitted --limit <pageSize> --page <pageNumber> --query 关键词",
"dws oa approval list-submitted --limit <pageSize> --page <pageNumber> --query 关键词 --format json",
},
},
Parameters: []contract.ParamDecl{
{Name: "limit", Property: "pageSize"},
{Name: "page", Property: "pageNumber"},
},
},
})
// 抄送
approvalCcListCmd := &cobra.Command{
Use: "list-cc",
Short: "获取抄送当前用户的审批单列表",
Example: ` dws oa approval list-cc --limit 20 --page 1 --query 关键词`,
RunE: func(cmd *cobra.Command, args []string) error {
pageSize, _ := strconv.ParseFloat(mustGetFlag(cmd, "limit"), 64)
pageNumber, _ := strconv.ParseFloat(mustGetFlag(cmd, "page"), 64)
argsMap := map[string]any{
"pageNumber": pageNumber,
"pageSize": pageSize,
}
if v, _ := cmd.Flags().GetString("query"); v != "" {
argsMap["query"] = v
}
return callMCPTool("get_noticed_instances", argsMap)
},
}
DeclareLeafMetadata(approvalCcListCmd, LeafSpec{
Safety: contract.SafetySpec{
Effect: "read", Risk: "low",
Confirmation: "not_required", Idempotency: "idempotent",
},
Contract: LeafContract{
Identity: contract.ToolIdentitySpec{
ProductID: "oa",
Name: "get_noticed_instances",
CanonicalPath: "oa.get_noticed_instances",
CLIPath: "oa approval list-cc",
PrimaryCLIPath: "oa approval list-cc",
},
Description: "获取抄送用户的列表",
Interface: &contract.InterfaceSpec{
Mode: "mcp",
Availability: "available",
Ref: &contract.InterfaceRefSpec{ProductID: "oa", RPCName: "get_noticed_instances"},
},
Selection: contract.SelectionSpec{
AgentSummary: "获取抄送用户的列表",
UseWhen: []string{"需要查看抄送给当前用户的审批单列表时"},
AvoidWhen: []string{
"要看待处理或自己发起的审批时不要使用",
"该命令只列抄送实例,不执行审批动作",
},
Examples: []string{
"dws oa approval list-cc --limit <pageSize> --page <pageNumber> --query 关键词",
"dws oa approval list-cc --limit <pageSize> --page <pageNumber> --query 关键词 --format json",
},
},
Parameters: []contract.ParamDecl{
{Name: "limit", Property: "pageSize"},
{Name: "page", Property: "pageNumber"},
},
},
})
// 转交任务
approvalTransferCmd := &cobra.Command{
Use: "redirect-task",
Short: "转交审批任务给其他人",
Example: ` dws oa approval redirect-task --task-id <taskId> --to-actioner-id <userId>
dws oa approval redirect-task --task-id <taskId> --to-actioner-id <userId> --remark "请帮忙处理"`,
RunE: func(cmd *cobra.Command, args []string) error {
if err := validateRequiredFlags(cmd, "task-id", "to-actioner-id"); err != nil {
return err
}
argsMap := map[string]any{
"taskId": mustGetFlag(cmd, "task-id"),
"toActionerId": mustGetFlag(cmd, "to-actioner-id"),
}
if v, _ := cmd.Flags().GetString("remark"); v != "" {
argsMap["remark"] = v
}
return callMCPTool("redirect_task", argsMap)
},
}
DeclareLeafMetadata(approvalTransferCmd, LeafSpec{
Safety: contract.SafetySpec{
Effect: "write", Risk: "medium",
Confirmation: "not_required", Idempotency: "unknown",
},
Contract: LeafContract{
Identity: contract.ToolIdentitySpec{
ProductID: "oa",
Name: "redirect_task",
CanonicalPath: "oa.redirect_task",
CLIPath: "oa approval redirect-task",
PrimaryCLIPath: "oa approval redirect-task",
},
Description: "转交审批任务",
Interface: &contract.InterfaceSpec{
Mode: "mcp",
Availability: "available",
Ref: &contract.InterfaceRefSpec{ProductID: "oa", RPCName: "redirect_task"},
},
Selection: contract.SelectionSpec{
AgentSummary: "转交审批任务",
UseWhen: []string{"已知 taskId 与接收人 toActionerId,用户明确要求转交审批任务时"},
AvoidWhen: []string{
"任务、接收人或转交意图未确认时不要转交",
"要自己处理时改用 approve/reject",
},
Examples: []string{
"dws oa approval redirect-task --task-id <taskId> --to-actioner-id <userId>",
"dws oa approval redirect-task --task-id <taskId> --to-actioner-id <userId> --remark \"请帮忙处理\"",
},
},
},
})
// 评论审批实例
approvalCommentCmd := &cobra.Command{
Use: "oa-comments",
Short: "对审批实例添加评论",
Example: ` dws oa approval oa-comments --instance-id <processInstanceId> --content "同意,请尽快处理"`,
RunE: func(cmd *cobra.Command, args []string) error {
if err := validateRequiredFlags(cmd, "instance-id"); err != nil {
return err
}
commentText := flagOrFallback(cmd, "content", "text")
if commentText == "" {
return fmt.Errorf("--content is required")
}
return callMCPTool("dingflow_comments", map[string]any{
"processInstanceId": mustGetFlag(cmd, "instance-id"),
"text": commentText,
})
},
}
DeclareLeafMetadata(approvalCommentCmd, LeafSpec{
Safety: contract.SafetySpec{
Effect: "write", Risk: "medium",
Confirmation: "not_required", Idempotency: "unknown",
},
Contract: LeafContract{
Identity: contract.ToolIdentitySpec{
ProductID: "oa",
Name: "dingflow_comments",
CanonicalPath: "oa.dingflow_comments",
CLIPath: "oa approval oa-comments",
PrimaryCLIPath: "oa approval oa-comments",
},
Description: "用户添加审批评论",
Interface: &contract.InterfaceSpec{
Mode: "mcp",
Availability: "available",
Ref: &contract.InterfaceRefSpec{ProductID: "oa", RPCName: "dingflow_comments"},
},
Selection: contract.SelectionSpec{
AgentSummary: "用户添加审批评论",
UseWhen: []string{"已知 processInstanceId,需要为审批实例添加评论文本时"},
AvoidWhen: []string{
"只需同意/拒绝/转交时改用对应写命令",
"评论内容或实例未确认时不要添加",
},
Examples: []string{
"dws oa approval oa-comments --instance-id <processInstanceId> --content \"同意,请尽快处理\"",
"dws oa approval oa-comments --instance-id <processInstanceId> --content \"同意,请尽快处理\" --format json",
},
},
Parameters: []contract.ParamDecl{
{Name: "content", Property: "text"},
{Name: "instance-id", Property: "processInstanceId"},
},
},
})
// 审批抄送
approvalCcCmd := &cobra.Command{
Use: "oa-cc-noticer",
Short: "对审批实例进行抄送",
Example: ` dws oa approval oa-cc-noticer --instance-id <processInstanceId> --users "68674200835816"
dws oa approval oa-cc-noticer --instance-id <processInstanceId> --users "userId1,userId2" --operator-id "123123"`,
RunE: func(cmd *cobra.Command, args []string) error {
if err := validateRequiredFlags(cmd, "instance-id"); err != nil {
return err
}
userListStr := flagOrFallback(cmd, "users", "user-list")
if userListStr == "" {
return fmt.Errorf("--users is required")
}
userList := strings.Split(userListStr, ",")
argsMap := map[string]any{
"processInstanceId": mustGetFlag(cmd, "instance-id"),
"userList": userList,
}
return callMCPTool("oa_cc_noticer", argsMap)
},
}
DeclareLeafMetadata(approvalCcCmd, LeafSpec{
Safety: contract.SafetySpec{
Effect: "write", Risk: "medium",
Confirmation: "not_required", Idempotency: "unknown",
},
Contract: LeafContract{
Identity: contract.ToolIdentitySpec{
ProductID: "oa",
Name: "oa_cc_noticer",
CanonicalPath: "oa.oa_cc_noticer",
CLIPath: "oa approval oa-cc-noticer",
PrimaryCLIPath: "oa approval oa-cc-noticer",
},
Description: "抄送审批人",
Interface: &contract.InterfaceSpec{
Mode: "mcp",
Availability: "available",
Ref: &contract.InterfaceRefSpec{ProductID: "oa", RPCName: "oa_cc_noticer"},
},
Selection: contract.SelectionSpec{
AgentSummary: "抄送审批人",
UseWhen: []string{"已知 processInstanceId,需要为审批实例追加抄送人时"},
AvoidWhen: []string{
"只需查看抄送列表时改用 dws oa approval list-cc",
"实例与抄送人未确认时不要添加",
},
Examples: []string{
"dws oa approval oa-cc-noticer --instance-id <processInstanceId> --users \"68674200835816\"",
"dws oa approval oa-cc-noticer --instance-id <processInstanceId> --users \"userId1,userId2\" --operator-id \"123123\"",
},
},
Parameters: []contract.ParamDecl{
{Name: "instance-id", Property: "processInstanceId"},
{Name: "users", Property: "userList"},
},
},
})
// 加签任务
approvalAppendTaskCmd := &cobra.Command{
Use: "append-task",
Short: "对审批任务进行加签",
Example: ` dws oa approval append-task --instance-id <processInstanceId> --task-id <taskId> --type before --appender-user-ids "userId1,userId2" --activate-type ALL --agree-all true
dws oa approval append-task --instance-id <processInstanceId> --task-id <taskId> --type Parallel --appender-user-ids "userId1" --activate-type ONE_BY_ONE --agree-all false`,
RunE: func(cmd *cobra.Command, args []string) error {
if err := validateRequiredFlags(cmd, "instance-id", "task-id", "type", "appender-user-ids", "activate-type", "agree-all"); err != nil {
return err
}
typeVal := mustGetFlag(cmd, "type")
if typeVal != "before" && typeVal != "after" && typeVal != "Parallel" {
return fmt.Errorf("--type must be one of: before, after, Parallel, got: %s", typeVal)
}
activateTypeVal := mustGetFlag(cmd, "activate-type")
if activateTypeVal != "ALL" && activateTypeVal != "ONE_BY_ONE" {
return fmt.Errorf("--activate-type must be one of: ALL, ONE_BY_ONE, got: %s", activateTypeVal)
}
appenderUserIdsStr := mustGetFlag(cmd, "appender-user-ids")
appenderUserIds := strings.Split(appenderUserIdsStr, ",")
agreeAll, err := strconv.ParseBool(mustGetFlag(cmd, "agree-all"))
if err != nil {
return fmt.Errorf("--agree-all must be 'true' or 'false', got: %s", mustGetFlag(cmd, "agree-all"))
}
return callMCPTool("append_task", map[string]any{
"processInstanceId": mustGetFlag(cmd, "instance-id"),
"taskId": mustGetFlag(cmd, "task-id"),
"type": typeVal,
"appenderUserIds": appenderUserIds,
"activateType": activateTypeVal,
"agreeAll": agreeAll,
})
},
}
// 获取任务可回退的节点信息
approvalRevertActivitiesCmd := &cobra.Command{
Use: "revert-activities",
Short: "获取审批任务可回退的节点信息(退回前必须调用,获取可回退节点列表)",
Example: ` dws oa approval revert-activities --task-id <taskId>
# 返回可回退节点列表,从中选择 targetActivityId 和 action`,
RunE: func(cmd *cobra.Command, args []string) error {
if err := validateRequiredFlags(cmd, "task-id"); err != nil {
return err
}
taskIdNum, err := strconv.ParseFloat(mustGetFlag(cmd, "task-id"), 64)
if err != nil {
return fmt.Errorf("--task-id must be a number, got: %s", mustGetFlag(cmd, "task-id"))
}
return callMCPTool("get_inst_revert_activities", map[string]any{
"taskId": taskIdNum,
})
},
}
// 退回任务(退回到审批人或发起人)
approvalRevertTaskCmd := &cobra.Command{
Use: "revert-task",
Short: "退回审批任务到指定节点(审批人或发起人)",
Example: ` # 退回到发起人(targetActivityId 固定传 sid-startevent)
dws oa approval revert-task --instance-id <processInstanceId> --task-id <taskId> --target-activity-id sid-startevent --action REVERT_FOR_RESUBMIT --remark "补充说明后重提"
# 退回到某个审批节点(targetActivityId 从审批流程节点信息中获取 activityId)
dws oa approval revert-task --instance-id <processInstanceId> --task-id <taskId> --target-activity-id <activityId> --action REVERT_FOR_APPROVAL --remark "重新审批"`,
RunE: func(cmd *cobra.Command, args []string) error {
if err := validateRequiredFlags(cmd, "instance-id", "task-id", "target-activity-id", "action"); err != nil {
return err
}
action := mustGetFlag(cmd, "action")
if action != "REVERT_FOR_APPROVAL" && action != "REVERT_FOR_RESUBMIT" {
return fmt.Errorf("--action must be one of: REVERT_FOR_APPROVAL, REVERT_FOR_RESUBMIT, got: %s", action)
}
targetActivityId := mustGetFlag(cmd, "target-activity-id")
// 退回发起人时,targetActivityId 固定为 sid-startevent
if action == "REVERT_FOR_RESUBMIT" && targetActivityId != "sid-startevent" {
return fmt.Errorf("--action=REVERT_FOR_RESUBMIT 时 --target-activity-id 必须为 sid-startevent,got: %s", targetActivityId)
}
taskIdNum, err := strconv.ParseFloat(mustGetFlag(cmd, "task-id"), 64)
if err != nil {
return fmt.Errorf("--task-id must be a number, got: %s", mustGetFlag(cmd, "task-id"))
}
inner := map[string]any{
"processInstanceId": mustGetFlag(cmd, "instance-id"),
"taskId": taskIdNum,
"targetActivityId": targetActivityId,
"revertAction": action,
}
if v, _ := cmd.Flags().GetString("remark"); v != "" {
inner["remark"] = v
}
return callMCPTool("revert_task", map[string]any{
"RevertTaskRequest": inner,
})
},
}
approvalFormSchemaCmd := &cobra.Command{
Use: "form-schema", Short: "查询审批模板的表单 Schema",
Example: "dws oa approval form-schema --process-code <processCode>",
RunE: func(cmd *cobra.Command, args []string) error {
if err := validateRequiredFlags(cmd, "process-code"); err != nil {
return err
}
return callMCPTool("get_process_schema", map[string]any{"processCode": mustGetFlag(cmd, "process-code")})
},
}
DeclareLeafMetadata(approvalFormSchemaCmd, LeafSpec{
Safety: contract.SafetySpec{
Effect: "read", Risk: "low",
Confirmation: "not_required", Idempotency: "idempotent",
},
Contract: LeafContract{
Identity: contract.ToolIdentitySpec{
ProductID: "oa",
Name: "get_process_schema",
CanonicalPath: "oa.get_process_schema",
CLIPath: "oa approval form-schema",
PrimaryCLIPath: "oa approval form-schema",
},
Description: "查询审批模板的表单 Schema",
Interface: &contract.InterfaceSpec{
Mode: "mcp",
Availability: "available",
Ref: &contract.InterfaceRefSpec{ProductID: "oa", RPCName: "get_process_schema"},
},
Selection: contract.SelectionSpec{
AgentSummary: "查询审批模板的表单 Schema",
UseWhen: []string{"已从 list-forms 或 search-forms 获得 processCode,需要读取字段、选项和必填规则后再填写审批时"},
AvoidWhen: []string{"只需列出可用模板时使用 list-forms;不要把返回的 Schema 当作可直接提交的实例请求"},
Examples: []string{"dws oa approval form-schema --process-code <processCode>"},
},
Parameters: []contract.ParamDecl{
{Name: "process-code", Property: "processCode"},
},
},
})
approvalForecastCmd := &cobra.Command{
Use: "forecast-process", Short: "根据表单值预测审批流程与自选节点",
Example: "dws oa approval forecast-process --process-code <processCode> --dept-id -1 --form-values '{\"金额\":\"100\"}'",
RunE: func(cmd *cobra.Command, args []string) error {
if raw, _ := cmd.Flags().GetString("request"); raw != "" {
request, err := decodeOARequest(raw)
if err != nil {
return fmt.Errorf("--request JSON 解析失败: %w", err)
}
return callMCPTool("forecast_process", map[string]any{"ProcessForecastPopRequest": request})
}
if err := validateRequiredFlags(cmd, "process-code", "dept-id", "form-values"); err != nil {
return err
}
deptID, err := strconv.ParseInt(mustGetFlag(cmd, "dept-id"), 10, 64)
if err != nil {
return fmt.Errorf("--dept-id 必须为整数: %w", err)
}
values, err := oaFormValues(mustGetFlag(cmd, "form-values"))
if err != nil {
return fmt.Errorf("--form-values JSON 解析失败: %w", err)
}
return callMCPTool("forecast_process", map[string]any{"ProcessForecastPopRequest": map[string]any{"processCode": mustGetFlag(cmd, "process-code"), "deptId": deptID, "formComponentValues": [][]map[string]string{values}}})
},
}
DeclareLeafMetadata(approvalForecastCmd, LeafSpec{
Safety: contract.SafetySpec{
Effect: "read", Risk: "low",
Confirmation: "not_required", Idempotency: "idempotent",
},
Contract: LeafContract{
Identity: contract.ToolIdentitySpec{
ProductID: "oa",
Name: "forecast_process",
CanonicalPath: "oa.forecast_process",
CLIPath: "oa approval forecast-process",
PrimaryCLIPath: "oa approval forecast-process",
},
Description: "根据表单值预测审批流程与自选节点",
Interface: &contract.InterfaceSpec{
Mode: "mcp",
Availability: "available",
Ref: &contract.InterfaceRefSpec{ProductID: "oa", RPCName: "forecast_process"},
},
Selection: contract.SelectionSpec{
AgentSummary: "预测审批流程与自选审批节点",
UseWhen: []string{"已知道 processCode 且已根据表单 Schema 组装字段,需要在发起前确认审批路径或自选节点时"},
AvoidWhen: []string{"需要真正创建审批单时改用 create-instance;未获得字段定义时先用 form-schema"},
Examples: []string{
"dws oa approval forecast-process --process-code <processCode> --dept-id -1 --form-values '{\"金额\":\"100\"}'",
"dws oa approval forecast-process --request '{\"processCode\":\"PROC-xxx\",\"deptId\":-1,\"formComponentValues\":[[{\"name\":\"金额\",\"value\":\"100\"}]]}'",
},
},
// Simple-mode flags are mapping exclusions (encoded inside ProcessForecastPopRequest).
Parameters: []contract.ParamDecl{
{Name: "request", Property: "ProcessForecastPopRequest", InterfaceType: "object"},
},
},
})
approvalCreateCmd := &cobra.Command{
Use: "create-instance", Short: "发起审批实例(需要 --yes 确认)",
Example: "dws oa approval create-instance --process-code <processCode> --form-values '{\"事由\":\"测试\"}' --yes",
RunE: func(cmd *cobra.Command, args []string) error {
if !commandDryRun(cmd) {
yes, _ := cmd.Flags().GetBool("yes")
if !yes {
return fmt.Errorf("发起审批实例会创建真实业务数据;请先核对参数,然后添加 --yes 确认执行")
}
}
var request map[string]any
if raw, _ := cmd.Flags().GetString("request"); raw != "" {
var err error
request, err = decodeOARequest(raw)
if err != nil {
return fmt.Errorf("--request JSON 解析失败: %w", err)
}
} else {
if err := validateRequiredFlags(cmd, "process-code", "form-values"); err != nil {
return err
}
values, err := oaFormValues(mustGetFlag(cmd, "form-values"))
if err != nil {
return fmt.Errorf("--form-values JSON 解析失败: %w", err)
}
request = map[string]any{"processCode": mustGetFlag(cmd, "process-code"), "formComponentValues": values}
if dept, _ := cmd.Flags().GetString("dept-id"); dept != "" {
value, err := strconv.ParseInt(dept, 10, 64)
if err != nil {
return fmt.Errorf("--dept-id 必须为整数: %w", err)
}
request["deptId"] = value
}
if userID, _ := cmd.Flags().GetString("originator-user-id"); userID != "" {
request["originatorUserId"] = userID
}
if rawApprovers, _ := cmd.Flags().GetString("approvers"); rawApprovers != "" {
action, _ := cmd.Flags().GetString("approvers-action-type")
if action != "AND" && action != "OR" && action != "NONE" {
return fmt.Errorf("--approvers-action-type 必须为 AND、OR 或 NONE")
}
request["approvers"] = []map[string]any{{"actionType": action, "userIds": strings.Split(rawApprovers, ",")}}
}
if rawCC, _ := cmd.Flags().GetString("cc-list"); rawCC != "" {
position, _ := cmd.Flags().GetString("cc-position")
if position != "START" && position != "FINISH" && position != "START_FINISH" {
return fmt.Errorf("--cc-position 必须为 START、FINISH 或 START_FINISH")
}
request["ccList"] = strings.Split(rawCC, ",")
request["ccPosition"] = position
}
}
return callMCPTool("start_process_instance", map[string]any{"ProcessInstanceCreationPopRequest": request})
},
}
DeclareLeafMetadata(approvalCreateCmd, LeafSpec{
Safety: contract.SafetySpec{
Effect: "write", Risk: "high",
Confirmation: "user_required", Idempotency: "non_idempotent",
},
Contract: LeafContract{
Identity: contract.ToolIdentitySpec{
ProductID: "oa",
Name: "start_process_instance",
CanonicalPath: "oa.start_process_instance",
CLIPath: "oa approval create-instance",
PrimaryCLIPath: "oa approval create-instance",
},
Description: "发起审批实例(需要 --yes 确认)",
Interface: &contract.InterfaceSpec{
Mode: "mcp",
Availability: "available",
Ref: &contract.InterfaceRefSpec{ProductID: "oa", RPCName: "start_process_instance"},
},
Selection: contract.SelectionSpec{
AgentSummary: "发起新的审批实例",
UseWhen: []string{"用户确认要发起审批,且已查询表单 Schema、核对字段及审批路径后使用"},
AvoidWhen: []string{"只需预测流程时使用 forecast-process;用户尚未确认或字段未按 Schema 核对时不要发起"},
Examples: []string{
"dws oa approval create-instance --process-code <processCode> --form-values '{\"事由\":\"测试\"}'",
"dws oa approval create-instance --request '{\"processCode\":\"PROC-xxx\",\"deptId\":-1,\"formComponentValues\":[{\"name\":\"事由\",\"value\":\"测试\"}],\"targetSelectActioners\":[{\"actionerKey\":\"manual-node\",\"actionerStaffIds\":[\"user-id\"]}]}'",
},
},
// Simple-mode flags are mapping exclusions (encoded inside ProcessInstanceCreationPopRequest).
Parameters: []contract.ParamDecl{
{Name: "request", Property: "ProcessInstanceCreationPopRequest", InterfaceType: "object"},
},
},
})
approvalListPendingCmd.Flags().String("start", "", "开始时间 ISO-8601 (如 2026-03-10T00:00:00+08:00) (必填)")
approvalListPendingCmd.Flags().String("end", "", "结束时间 ISO-8601 (如 2026-03-10T23:59:59+08:00) (必填)")
approvalListPendingCmd.Flags().String("page", "", "分页页码 (可选)")
approvalListPendingCmd.Flags().String("limit", "", "每页大小 (可选)")
approvalListPendingCmd.Flags().String("size", "", "每页大小 (可选)")
approvalListPendingCmd.Flags().Lookup("size").Hidden = true
approvalListPendingCmd.Flags().String("query", "", "关键字搜索(可选)")
approvalDetailCmd.Flags().String("instance-id", "", "审批实例 ID (必填)")
approvalApproveCmd.Flags().String("instance-id", "", "审批实例 ID (必填)")
approvalApproveCmd.Flags().String("task-id", "", "审批任务 ID (必填)")
approvalApproveCmd.Flags().String("remark", "", "审批意见 (可选)")
approvalRejectCmd.Flags().String("instance-id", "", "审批实例 ID (必填)")
approvalRejectCmd.Flags().String("task-id", "", "审批任务 ID (必填)")
approvalRejectCmd.Flags().String("remark", "", "审批意见 (可选)")
approvalRevokeCmd.Flags().String("instance-id", "", "审批实例 ID (必填)")
approvalRevokeCmd.Flags().String("remark", "", "撤销说明 (可选)")
approvalRecordsCmd.Flags().String("instance-id", "", "审批实例 ID (必填)")
approvalListInitiatedCmd.Flags().String("process-code", "", "表单 processCode (必填)")
approvalListInitiatedCmd.Flags().String("start", "", "开始时间 ISO-8601 (如 2026-03-10T00:00:00+08:00) (必填)")
approvalListInitiatedCmd.Flags().String("end", "", "结束时间 ISO-8601 (如 2026-03-10T23:59:59+08:00) (必填)")
approvalListInitiatedCmd.Flags().String("cursor", "0", "分页游标,首次传 0")
approvalListInitiatedCmd.Flags().String("next-token", "0", "分页游标,首次传 0")
approvalListInitiatedCmd.Flags().Lookup("next-token").Hidden = true
approvalListInitiatedCmd.Flags().String("limit", "20", "每页大小,最大 20")
approvalListInitiatedCmd.Flags().String("max-results", "20", "每页大小,最大 20")
approvalListInitiatedCmd.Flags().Lookup("max-results").Hidden = true
approvalTasksCmd.Flags().String("instance-id", "", "审批实例 ID (必填)")
approvalListFormsCmd.Flags().String("cursor", "0", "分页游标(默认 0,翻页传返回的 cursor)")
approvalListFormsCmd.Flags().String("limit", "100", "每页大小(默认 100,最大 100)")
approvalListFormsCmd.Flags().String("size", "100", "每页大小(默认 100,最大 100)")
approvalListFormsCmd.Flags().Lookup("size").Hidden = true
approvalSearchFormsCmd.Flags().String("query", "", "关键字(匹配 processCode 或表单名称)(必填)")
approvalDingInfoCmd.Flags().String("task-id", "", "审批任务 ID (必填)")
approvalExecutedListCmd.Flags().String("page", "1", "分页页码(可选)")
approvalExecutedListCmd.Flags().String("limit", "20", "每页大小(可选)")
approvalExecutedListCmd.Flags().String("query", "", "关键字搜索(可选)")
approvalSubmittedListCmd.Flags().String("page", "1", "分页页码(可选)")
approvalSubmittedListCmd.Flags().String("limit", "20", "每页大小(可选)")
approvalSubmittedListCmd.Flags().String("query", "", "关键字搜索(可选)")
approvalCcListCmd.Flags().String("page", "1", "分页页码(可选)")
approvalCcListCmd.Flags().String("limit", "20", "每页大小(可选)")
approvalCcListCmd.Flags().String("query", "", "关键字搜索(可选)")
approvalTransferCmd.Flags().String("task-id", "", "审批任务 ID (必填)")
approvalTransferCmd.Flags().String("to-actioner-id", "", "转交目标用户 ID (必填)")
approvalTransferCmd.Flags().String("remark", "", "转交说明 (可选)")
approvalCommentCmd.Flags().String("instance-id", "", "审批实例 ID (必填)")
approvalCommentCmd.Flags().String("content", "", "评论内容 (必填)")
approvalCommentCmd.Flags().String("text", "", "评论内容 (必填)")
approvalCommentCmd.Flags().Lookup("text").Hidden = true
approvalCcCmd.Flags().String("instance-id", "", "审批实例 ID (必填)")
approvalCcCmd.Flags().String("users", "", "抄送用户 ID 列表,多个用逗号分隔 (必填)")
approvalCcCmd.Flags().String("user-list", "", "抄送用户 ID 列表,多个用逗号分隔 (必填)")
approvalCcCmd.Flags().Lookup("user-list").Hidden = true
approvalCcCmd.Flags().String("operator-id", "", "操作人 ID (可选)")
approvalAppendTaskCmd.Flags().String("instance-id", "", "审批实例 ID (必填)")
approvalAppendTaskCmd.Flags().String("task-id", "", "审批任务 ID (必填)")
approvalAppendTaskCmd.Flags().String("type", "", "加签类型:before(前加签),after(后加签),Parallel(并加签)(必填)")
approvalAppendTaskCmd.Flags().String("appender-user-ids", "", "被加签用户 ID 列表,多个用逗号分隔 (必填)")
approvalAppendTaskCmd.Flags().String("activate-type", "", "任务激活类型:ALL(或签),ONE_BY_ONE(依次审批)(必填)")
approvalAppendTaskCmd.Flags().String("agree-all", "", "是否需要全部同意,true 或 false (必填)")
approvalRevertActivitiesCmd.Flags().String("task-id", "", "审批任务 ID (必填)")
approvalRevertTaskCmd.Flags().String("instance-id", "", "审批实例 ID (必填)")
approvalRevertTaskCmd.Flags().String("task-id", "", "审批任务 ID (必填)")
approvalRevertTaskCmd.Flags().String("target-activity-id", "", "退回到的节点 ID(退回发起人固定传 sid-startevent)(必填)")
approvalRevertTaskCmd.Flags().String("action", "", "退回方式:REVERT_FOR_APPROVAL(退回到审批人)/ REVERT_FOR_RESUBMIT(退回到发起人)(必填)")
approvalRevertTaskCmd.Flags().String("remark", "", "退回说明 (可选)")
approvalFormSchemaCmd.Flags().String("process-code", "", "审批模板 processCode (必填)")
approvalForecastCmd.Flags().String("process-code", "", "审批模板 processCode(简单模式使用;与 --request 互斥)")
approvalForecastCmd.Flags().String("dept-id", "", "发起人部门 ID(简单模式使用;与 --request 互斥)")
approvalForecastCmd.Flags().String("form-values", "", "表单值 JSON(简单模式使用;与 --request 互斥)")
approvalForecastCmd.Flags().String("request", "", "完整请求 JSON(高级模式;与简单模式参数互斥)")
approvalForecastCmd.MarkFlagsOneRequired("request", "process-code")
approvalForecastCmd.MarkFlagsRequiredTogether("process-code", "dept-id", "form-values")
forecastMutuallyExclusive := make([][]string, 0, 3)
for _, name := range []string{"process-code", "dept-id", "form-values"} {
approvalForecastCmd.MarkFlagsMutuallyExclusive("request", name)
forecastMutuallyExclusive = append(forecastMutuallyExclusive, []string{"request", name})
}
cli.AnnotateRuntimeConstraints(approvalForecastCmd, cli.RuntimeSchemaConstraints{
MutuallyExclusive: forecastMutuallyExclusive,
RequireOneOf: [][]string{{"request", "process-code"}},
RequireTogether: [][]string{{"process-code", "dept-id", "form-values"}},
})
approvalCreateCmd.Flags().String("process-code", "", "审批模板 processCode(简单模式使用;与 --request 互斥)")
approvalCreateCmd.Flags().String("dept-id", "-1", "发起人部门 ID")
approvalCreateCmd.Flags().String("form-values", "", "表单值 JSON(简单模式使用;与 --request 互斥)")
approvalCreateCmd.Flags().String("request", "", "完整请求 JSON(高级模式;与简单模式参数互斥)")
approvalCreateCmd.Flags().String("originator-user-id", "", "审批发起人 userId")
approvalCreateCmd.Flags().String("approvers", "", "审批人 userId 列表,多个用逗号分隔")
approvalCreateCmd.Flags().String("approvers-action-type", "OR", "审批类型:AND、OR 或 NONE")
approvalCreateCmd.Flags().String("cc-list", "", "抄送人 userId 列表,多个用逗号分隔")
approvalCreateCmd.Flags().String("cc-position", "START", "抄送时点:START、FINISH 或 START_FINISH")
approvalCreateCmd.MarkFlagsOneRequired("request", "process-code")
approvalCreateCmd.MarkFlagsRequiredTogether("process-code", "form-values")
createSimpleFlags := []string{"process-code", "dept-id", "form-values", "originator-user-id", "approvers", "approvers-action-type", "cc-list", "cc-position"}
createMutuallyExclusive := make([][]string, 0, len(createSimpleFlags))
for _, name := range createSimpleFlags {
approvalCreateCmd.MarkFlagsMutuallyExclusive("request", name)
createMutuallyExclusive = append(createMutuallyExclusive, []string{"request", name})
}
cli.AnnotateRuntimeConstraints(approvalCreateCmd, cli.RuntimeSchemaConstraints{
MutuallyExclusive: createMutuallyExclusive,
RequireOneOf: [][]string{{"request", "process-code"}},
RequireTogether: [][]string{{"process-code", "form-values"}},
})
approvalCmd.AddCommand(
approvalListPendingCmd,
approvalDetailCmd,
approvalApproveCmd,
approvalRejectCmd,
approvalRevokeCmd,
approvalRecordsCmd,
approvalListInitiatedCmd,
approvalTasksCmd,
approvalListFormsCmd,
approvalSearchFormsCmd,
approvalDingInfoCmd,
approvalExecutedListCmd,
approvalSubmittedListCmd,
approvalCcListCmd,
approvalTransferCmd,
approvalCommentCmd,
approvalCcCmd,
approvalAppendTaskCmd,
approvalRevertActivitiesCmd,
approvalRevertTaskCmd,
approvalFormSchemaCmd,
approvalForecastCmd,
approvalCreateCmd,
)
approvalCmd.AddCommand(newOAAttachmentCommand())
root.AddCommand(approvalCmd)
return root
}