diff --git a/internal/app/auth_command_test.go b/internal/app/auth_command_test.go index 74db1272..1c2fb641 100644 --- a/internal/app/auth_command_test.go +++ b/internal/app/auth_command_test.go @@ -824,6 +824,10 @@ func (f *authLoginRecommendSequenceCaller) Format() string { return "table" } func (f *authLoginRecommendSequenceCaller) DryRun() bool { return false } +func (f *authLoginRecommendSequenceCaller) Fields() string { return "" } + +func (f *authLoginRecommendSequenceCaller) JQ() string { return "" } + func stringSliceArgEqual(got any, want []string) bool { if got == nil { return len(want) == 0 diff --git a/internal/app/skill_setup.go b/internal/app/skill_setup.go index c07424e3..fc9f230a 100644 --- a/internal/app/skill_setup.go +++ b/internal/app/skill_setup.go @@ -128,6 +128,19 @@ func runSkillSetup(cmd *cobra.Command, _ []string) error { multiSkillNames = ensureMandatorySharedSkill(filtered, allMultiSkillNames) } + // --dry-run:仅预览将安装的内容与目标目录,不写入任何文件、不弹确认。 + if dryRun, _ := cmd.Flags().GetBool("dry-run"); dryRun { + fmt.Fprintf(out, "[DRY-RUN] 预览(不写入任何文件):mode=%s,来源 %s\n", mode, skillSrc) + fmt.Fprintln(out, "将安装到:") + for _, d := range dests { + fmt.Fprintf(out, " - %s\n", d) + } + if mode == skillSetupModeMulti && len(multiSkillNames) > 0 { + fmt.Fprintf(out, "子 skill:%s\n", strings.Join(multiSkillNames, ", ")) + } + return nil + } + if !autoYes { ok, err := confirmSkillSetup(out, mode, skillSrc, dests, multiSkillNames) if err != nil { diff --git a/internal/app/tool_caller_adapter.go b/internal/app/tool_caller_adapter.go index 05b385c0..bd055454 100644 --- a/internal/app/tool_caller_adapter.go +++ b/internal/app/tool_caller_adapter.go @@ -54,6 +54,20 @@ func (a *toolCallerAdapter) DryRun() bool { return a.flags != nil && a.flags.DryRun } +func (a *toolCallerAdapter) Fields() string { + if a.flags != nil { + return a.flags.Fields + } + return "" +} + +func (a *toolCallerAdapter) JQ() string { + if a.flags != nil { + return a.flags.JQ + } + return "" +} + func convertResult(r executor.Result) *edition.ToolResult { resp := r.Response if resp == nil { diff --git a/internal/helpers/aitable.go b/internal/helpers/aitable.go index 05e25e4a..a2ce15b5 100644 --- a/internal/helpers/aitable.go +++ b/internal/helpers/aitable.go @@ -4290,7 +4290,7 @@ parentSectionId 为空串表示该节点在 Base 根目录下。 baseGetPrimaryDocIdCmd.Flags().String("table-id", "", "Table ID,可通过 list_tables 或 get_base 获取 (必填)") baseGetPrimaryDocIdCmd.Flags().String("record-id", "", "记录 ID (必填)") baseCopyCmd.Flags().String("base-id", "", "源 Base ID (必填)") - baseCopyCmd.Flags().String("target-folder-id", "", "目标文件夹 ID (必填), 默认为当前 Base 的父节点") + baseCopyCmd.Flags().String("target-folder-id", "", "目标文件夹 ID (必填, 不传会复制失败)") baseCopyCmd.Flags().Bool("only-struct", false, "是否仅复制结构(不含数据),默认 false 表示完整复制") baseCmd.AddCommand( baseListCmd, baseSearchCmd, baseGetCmd, diff --git a/internal/helpers/attendance.go b/internal/helpers/attendance.go index 1bf2239c..9b4c890c 100644 --- a/internal/helpers/attendance.go +++ b/internal/helpers/attendance.go @@ -2333,7 +2333,7 @@ statsType 统计类型支持:week(周统计)、month(月统计)。`, 子命令: types 查询当前用户假期规则列表 - save-type 创建或更新假期规则(仅支持无额度模式) + update-type 更新假期规则(仅支持无额度模式) balance 查询指定员工假期余额 save-balance 更新员工假期余额 records 查询指定员工假期余额变更记录`, @@ -3136,11 +3136,11 @@ statsType 统计类型支持:week(周统计)、month(月统计)。`, // vacation balance (get_leave_balance_quota) vacationBalanceCmd.Flags().String("users", "", "目标员工 ID 列表,逗号分隔 (必填)") - vacationBalanceCmd.Flags().String("leave-code", "", "假期规则 code (选填,不传则查询所有假期)") + vacationBalanceCmd.Flags().String("leave-code", "", "假期规则 code (必填,服务端要求非空,不传返回 INVALID_PARAMS)") // vacation records (get_leave_balance_records) vacationRecordsCmd.Flags().String("user", "", "指定查询员工 ID (必填)") - vacationRecordsCmd.Flags().String("leave-code", "", "假期规则 code (选填,不传则查询所有假期)") + vacationRecordsCmd.Flags().String("leave-code", "", "假期规则 code (必填,服务端要求非空,不传返回 INVALID_PARAMS)") vacationRecordsCmd.Flags().String("start", "", "查询开始日期,格式 YYYY-MM-DD (必填)") vacationRecordsCmd.Flags().String("end", "", "查询结束日期,格式 YYYY-MM-DD (必填)") diff --git a/internal/helpers/chat.go b/internal/helpers/chat.go index 8cb9ff1c..30cfbbfd 100644 --- a/internal/helpers/chat.go +++ b/internal/helpers/chat.go @@ -3069,7 +3069,7 @@ func newChatCommand() *cobra.Command { chatMessageCreateTextEmotionCmd := &cobra.Command{ Use: "create-text-emotion", Short: "创建文字表情(获取 emotionId)", - Long: `创建一个新的文字表情模板。当 list-emotions 中没有所需表情时,使用此命令创建并获取 emotionId,随后可用于 add-text-emotion。`, + Long: `创建一个新的文字表情模板。当内置表情(见 chat-emoji-list.md)中没有所需表情时,使用此命令创建并获取 emotionId,随后可用于 add-text-emotion。`, Example: ` dws chat message create-text-emotion --emotion-name "赞" --text "nice" dws chat message create-text-emotion --emotion-name "感谢" --text "感谢" --background-id im_bg_5`, RunE: func(cmd *cobra.Command, args []string) error { @@ -3228,12 +3228,21 @@ flow-status 取值:1=处理中(PROCESSING),2=输入中(INPUTTING),3=完成 return err } - // 解析输出路径:如果是目录,从 URL 推断文件名 + // 解析输出路径:目录(已存在的目录,或以分隔符结尾的意图目录)→ 追加推断文件名。 fi, statErr := os.Stat(outputPath) - if statErr == nil && fi.IsDir() { + isDir := (statErr == nil && fi.IsDir()) || + strings.HasSuffix(outputPath, string(os.PathSeparator)) || + strings.HasSuffix(outputPath, "/") + if isDir { filename := inferFilename(resourceURL) outputPath = filepath.Join(outputPath, filename) } + // 确保目标父目录存在,否则 httpGetFile 打开文件会因目录缺失失败。 + if dir := filepath.Dir(outputPath); dir != "" && dir != "." { + if err := os.MkdirAll(dir, 0o755); err != nil { + return fmt.Errorf("创建输出目录失败: %w", err) + } + } // Step 2: HTTP GET 下载文件 deps.Out.PrintInfo(fmt.Sprintf("[2/2] 下载资源到 %s ...", outputPath)) @@ -4316,16 +4325,16 @@ flow-status 取值:1=处理中(PROCESSING),2=输入中(INPUTTING),3=完成 chatGroupAuditJoinValidationCmd := &cobra.Command{ Use: "audit-join-validation", Short: "审批入群验证(通过、拒绝、删除)", - Long: `审批入群验证。支持通过、拒绝、删除、忽略、拒绝并拉黑等操作。 + Long: `审批入群验证。真机实测服务端仅接受 AuditApprove / AuditDelete,其余状态会被拒绝(unsupported audit status)。 status 可选值: - AuditApprove — 通过 - AuditDelete — 删除 - AuditIgnore — 忽略 - AuditRefuse — 拒绝 - AuditBlock — 拒绝且不再接受该用户的申请`, + AuditApprove — 通过(可用) + AuditDelete — 删除(可用) + AuditIgnore — 忽略(服务端拒绝,不可用) + AuditRefuse — 拒绝(服务端拒绝,不可用) + AuditBlock — 拒绝且不再接受该用户的申请(服务端拒绝,不可用)`, Example: ` dws chat group audit-join-validation --group --record-id 123456 --applicant --inviter --status AuditApprove - dws chat group audit-join-validation --group --record-id 123456 --applicant --inviter --status AuditRefuse --description "不符合入群条件" + dws chat group audit-join-validation --group --record-id 123456 --applicant --inviter --status AuditDelete --description "不符合入群条件" # 查询入群验证记录: dws chat group list-join-validations`, RunE: func(cmd *cobra.Command, args []string) error { if err := validateRequiredFlags(cmd, "group", "record-id", "applicant", "inviter", "status"); err != nil { @@ -4352,7 +4361,7 @@ status 可选值: _ = chatGroupAuditJoinValidationCmd.MarkFlagRequired("group") chatGroupAuditJoinValidationCmd.Flags().String("record-id", "", "申请记录 ID (必填)") _ = chatGroupAuditJoinValidationCmd.MarkFlagRequired("record-id") - chatGroupAuditJoinValidationCmd.Flags().String("status", "", "审批动作: AuditApprove/AuditDelete/AuditIgnore/AuditRefuse/AuditBlock (必填)") + chatGroupAuditJoinValidationCmd.Flags().String("status", "", "审批动作,真机仅 AuditApprove/AuditDelete 可用;AuditIgnore/AuditRefuse/AuditBlock 服务端拒绝 (必填)") _ = chatGroupAuditJoinValidationCmd.MarkFlagRequired("status") chatGroupAuditJoinValidationCmd.Flags().String("applicant", "", "申请人 openDingTalkId (必填)") _ = chatGroupAuditJoinValidationCmd.MarkFlagRequired("applicant") diff --git a/internal/helpers/doc.go b/internal/helpers/doc.go index d3e828ad..3f2c8cfc 100644 --- a/internal/helpers/doc.go +++ b/internal/helpers/doc.go @@ -400,7 +400,11 @@ func inferFilename(rawURL string) string { if decoded, err := url.PathUnescape(name); err == nil { name = decoded } - if name != "" { + // 解码后可能含 %2F 还原出的路径分隔符(如 "ddmedia/xxx.png"),只取 + // 末段 base 名,避免拼出调用方未创建的子目录导致写文件失败。 + name = strings.ReplaceAll(name, "\\", "/") + name = filepath.Base(name) + if name != "" && name != "." && name != "/" { return name } } diff --git a/internal/helpers/mail.go b/internal/helpers/mail.go index a350ee7c..f826d0f9 100644 --- a/internal/helpers/mail.go +++ b/internal/helpers/mail.go @@ -104,7 +104,7 @@ func newMailCommand() *cobra.Command { Long: `查询当前用户绑定的所有邮箱地址。 返回字段: - mailboxes 邮箱列表,每条包含邮箱地址、账号类型、所属企业 + emailAccounts 邮箱列表,每条包含邮箱地址(email)、账号类型(type)、所属企业(orgName) 错误说明: domain.notFound 该用户的邮箱不是由钉钉邮箱托管,无法完成操作`, diff --git a/internal/helpers/output.go b/internal/helpers/output.go index dbb44af0..255be4fe 100644 --- a/internal/helpers/output.go +++ b/internal/helpers/output.go @@ -6,8 +6,31 @@ import ( "io" "os" "strings" + + "github.com/DingTalk-Real-AI/dingtalk-workspace-cli/internal/output" ) +// applyGlobalFilter applies the global --jq / --fields output filters to data +// when either is set. Returns handled=true when it wrote the (filtered) output, +// so callers skip their default JSON encoding. The filter helpers live in +// internal/output (the same ones used by `dws api`); the helper Formatter used +// by product commands previously ignored these flags, making them no-ops. +func (f *Formatter) applyGlobalFilter(data any) (handled bool, err error) { + if deps == nil || deps.Caller == nil { + return false, nil + } + jq := strings.TrimSpace(deps.Caller.JQ()) + fields := strings.TrimSpace(deps.Caller.Fields()) + if jq == "" && fields == "" { + return false, nil + } + format := output.Format(strings.TrimSpace(deps.Caller.Format())) + if format == "" { + format = output.FormatJSON + } + return true, output.WriteFiltered(f.w, format, data, fields, jq) +} + // Formatter provides output formatting compatible with the old Wukong CLI. type Formatter struct { w io.Writer @@ -23,6 +46,9 @@ func NewFormatter() *Formatter { // 转义为 \u0026、\u003c、\u003e。对于大多数 CLI 输出场景这是安全的默认行为。 // 如果返回值中包含 URL 等不应被转义的内容,请使用 PrintJSONUnescaped。 func (f *Formatter) PrintJSON(data any) error { + if handled, err := f.applyGlobalFilter(data); handled { + return err + } enc := json.NewEncoder(f.w) enc.SetIndent("", " ") return enc.Encode(data) @@ -38,6 +64,9 @@ func (f *Formatter) PrintJSON(data any) error { // // 影响范围:仅在调用方显式选择时生效,不影响全局 PrintJSON 的行为。 func (f *Formatter) PrintJSONUnescaped(data any) error { + if handled, err := f.applyGlobalFilter(data); handled { + return err + } enc := json.NewEncoder(f.w) enc.SetIndent("", " ") enc.SetEscapeHTML(false) diff --git a/internal/helpers/sheet_export.go b/internal/helpers/sheet_export.go index e66c9fe9..0e924d6c 100644 --- a/internal/helpers/sheet_export.go +++ b/internal/helpers/sheet_export.go @@ -268,7 +268,7 @@ func newExportCmd() *cobra.Command { - 未指定:仅返回 downloadUrl,链接有时效性请尽快下载 支持范围: - 仅支持钉钉在线电子表格(alxs)→ xlsx; + 仅支持钉钉在线电子表格(axls)→ xlsx; 若需导出钉钉文字文档,请使用 dingtalkdoc 侧的导出工具。 权限要求: diff --git a/internal/helpers/sheet_media.go b/internal/helpers/sheet_media.go index 1c31baca..bf118135 100644 --- a/internal/helpers/sheet_media.go +++ b/internal/helpers/sheet_media.go @@ -182,7 +182,13 @@ func runSheetWriteImage(cmd *cobra.Command, _ []string) error { ctx := context.Background() - deps.Out.PrintInfo(fmt.Sprintf("[1/3] 获取附件上传凭证 (%s, %d bytes)...", fileName, fileSize)) + // json 模式下进度提示会污染 stdout(PrintInfo/PrintKeyValue 都写 stdout), + // 使 write_image 的 JSON 响应无法被单独解析。故 json 模式抑制进度。 + jsonMode := deps.Caller.Format() == "json" + + if !jsonMode { + deps.Out.PrintInfo(fmt.Sprintf("[1/3] 获取附件上传凭证 (%s, %d bytes)...", fileName, fileSize)) + } result, err := deps.Caller.CallTool(ctx, "doc", "get_doc_attachment_upload_info", map[string]any{ "nodeId": nodeID, @@ -216,10 +222,11 @@ func runSheetWriteImage(cmd *cobra.Command, _ []string) error { resourceURL, _ = credData["resourceUrl"].(string) } - deps.Out.PrintKeyValue("resourceId", resourceID) - deps.Out.PrintKeyValue("resourceUrl", resourceURL) - - deps.Out.PrintInfo("[2/3] 上传图片到 OSS...") + if !jsonMode { + deps.Out.PrintKeyValue("resourceId", resourceID) + deps.Out.PrintKeyValue("resourceUrl", resourceURL) + deps.Out.PrintInfo("[2/3] 上传图片到 OSS...") + } ossHeaders := map[string]string{ "Content-Type": mimeType, @@ -228,7 +235,9 @@ func runSheetWriteImage(cmd *cobra.Command, _ []string) error { return err } - deps.Out.PrintInfo("[3/3] 写入图片到表格单元格...") + if !jsonMode { + deps.Out.PrintInfo("[3/3] 写入图片到表格单元格...") + } writeArgs := map[string]any{ "nodeId": nodeID, @@ -244,10 +253,13 @@ func runSheetWriteImage(cmd *cobra.Command, _ []string) error { writeArgs["height"] = h } + if jsonMode { + // json 模式:write_image 的 JSON 响应就是唯一 stdout 输出。 + return callMCPTool("write_image", writeArgs) + } if err := callMCPTool("write_image", writeArgs); err != nil { return err } - deps.Out.PrintInfo(fmt.Sprintf("图片已写入表格: %s → %s (resourceId=%s)", fileName, rangeAddress, resourceID)) return nil } diff --git a/internal/helpers/sheet_style.go b/internal/helpers/sheet_style.go index c6fc0278..5676ac51 100644 --- a/internal/helpers/sheet_style.go +++ b/internal/helpers/sheet_style.go @@ -1,6 +1,7 @@ package helpers import ( + "context" "encoding/json" "fmt" "os" @@ -397,6 +398,9 @@ func newRangeBatchSetStyleCmd() *cobra.Command { node := mustGetFlag(cmd, "node") batchPath := mustGetFlag(cmd, "batch") continueOnErr, _ := cmd.Flags().GetBool("continue-on-error") + jsonMode := deps.Caller.Format() == "json" + ctx := context.Background() + var jsonResults []any data, err := os.ReadFile(batchPath) if err != nil { @@ -457,6 +461,34 @@ func newRangeBatchSetStyleCmd() *cobra.Command { continue } fmt.Fprintf(os.Stderr, "[%d/%d] update_range sheet=%s range=%s\n", i+1, total, item.SheetID, item.Range) + if jsonMode { + text, cerr := callMCPToolReturnText(ctx, "update_range", toolArgs) + entry := map[string]any{"index": i + 1, "sheetId": item.SheetID, "range": item.Range} + if cerr != nil { + entry["ok"] = false + entry["error"] = cerr.Error() + jsonResults = append(jsonResults, entry) + cerr = fmt.Errorf("第 %d/%d 条 update_range 失败: %w", i+1, total, cerr) + fmt.Fprintln(os.Stderr, cerr) + failed++ + if firstErr == nil { + firstErr = cerr + } + if !continueOnErr { + break + } + continue + } + var parsed any + if json.Unmarshal([]byte(text), &parsed) == nil { + entry["result"] = parsed + } else { + entry["result"] = text + } + entry["ok"] = true + jsonResults = append(jsonResults, entry) + continue + } if err := callMCPTool("update_range", toolArgs); err != nil { err = fmt.Errorf("第 %d/%d 条 update_range 失败: %w", i+1, total, err) fmt.Fprintln(os.Stderr, err) @@ -470,6 +502,14 @@ func newRangeBatchSetStyleCmd() *cobra.Command { } } fmt.Fprintf(os.Stderr, "batch-set-style 完成:共 %d 条,失败 %d 条\n", total, failed) + if jsonMode { + _ = deps.Out.PrintJSON(map[string]any{ + "total": total, + "failed": failed, + "results": jsonResults, + "success": failed == 0, + }) + } if failed > 0 && !continueOnErr { return firstErr } diff --git a/internal/pat/chmod.go b/internal/pat/chmod.go index 30c03f35..187458c7 100644 --- a/internal/pat/chmod.go +++ b/internal/pat/chmod.go @@ -244,7 +244,7 @@ func newChmodCommand(c edition.ToolCaller) *cobra.Command { Long: `授予指定 scope 的操作权限。 scope 格式: .: - 例: aitable.record:read chat.group:write calendar.event:read + 例: aitable.record:query chat.message:list calendar.event:get grantType 规则: once 一次性,执行一次后自动失效 @@ -271,9 +271,9 @@ agentCode 配置: } return cobra.MinimumNArgs(1)(cmd, args) }, - Example: ` dws pat chmod aitable.record:read --grant-type session --session-id session-xxx + Example: ` dws pat chmod aitable.record:query --grant-type session --session-id session-xxx dws pat chmod chat.message:list --grant-type once - dws pat chmod aitable.record:read aitable.record:write --grant-type permanent --yes + dws pat chmod aitable.record:query aitable.record:create --grant-type permanent --yes dws pat chmod --product calendar --product aitable --grant-type once --dry-run --format json dws pat chmod --products calendar,aitable --grant-type session --session-id session-xxx --yes dws pat chmod --domain calendar --domain chat --grant-type once --yes diff --git a/internal/pat/chmod_test.go b/internal/pat/chmod_test.go index dd22b765..b60529e2 100644 --- a/internal/pat/chmod_test.go +++ b/internal/pat/chmod_test.go @@ -67,6 +67,8 @@ func (f *fakeToolCaller) CallTool(_ context.Context, _ string, toolName string, func (f *fakeToolCaller) Format() string { return "json" } func (f *fakeToolCaller) DryRun() bool { return f.dryRun } +func (f *fakeToolCaller) Fields() string { return "" } +func (f *fakeToolCaller) JQ() string { return "" } type recordedToolCall struct { tool string @@ -94,6 +96,8 @@ func (f *fallbackToolCaller) CallTool(_ context.Context, _ string, toolName stri func (f *fallbackToolCaller) Format() string { return "json" } func (f *fallbackToolCaller) DryRun() bool { return false } +func (f *fallbackToolCaller) Fields() string { return "" } +func (f *fallbackToolCaller) JQ() string { return "" } type fallbackErrorToolCaller struct { calls []recordedToolCall @@ -198,6 +202,8 @@ func (f *fallbackPATContractErrorToolCaller) CallTool(_ context.Context, _ strin func (f *fallbackPATContractErrorToolCaller) Format() string { return "json" } func (f *fallbackPATContractErrorToolCaller) DryRun() bool { return false } +func (f *fallbackPATContractErrorToolCaller) Fields() string { return "" } +func (f *fallbackPATContractErrorToolCaller) JQ() string { return "" } type sequenceToolCaller struct { calls []recordedToolCall @@ -227,6 +233,8 @@ func (s *sequenceToolCaller) CallTool(_ context.Context, _ string, toolName stri func (s *sequenceToolCaller) Format() string { return "json" } func (s *sequenceToolCaller) DryRun() bool { return s.dryRun } +func (s *sequenceToolCaller) Fields() string { return "" } +func (s *sequenceToolCaller) JQ() string { return "" } func stringSliceArgEqual(got any, want []string) bool { gotSlice, ok := got.([]string) @@ -372,7 +380,7 @@ func TestPATHelpDocumentsBatchAuthorization(t *testing.T) { "--dry-run 只返回授权计划", "执行批量授权必须显式", "由服务端默认兜底", - "aitable.record:read aitable.record:write --grant-type permanent --yes", + "aitable.record:query aitable.record:create --grant-type permanent --yes", "dws pat chmod --products calendar,aitable", "dws pat chmod --recommend --grant-type session", } { @@ -2020,3 +2028,12 @@ func TestResolveAgentCodeFromEnv(t *testing.T) { code, src) } } + +func (f *fallbackErrorToolCaller) Fields() string { return "" } +func (f *fallbackErrorToolCaller) JQ() string { return "" } +func (f *fallbackSchemaMismatchToolCaller) Fields() string { return "" } +func (f *fallbackSchemaMismatchToolCaller) JQ() string { return "" } +func (f *fallbackPermissionDeniedToolCaller) Fields() string { return "" } +func (f *fallbackPermissionDeniedToolCaller) JQ() string { return "" } +func (f *fallbackPATErrorToolCaller) Fields() string { return "" } +func (f *fallbackPATErrorToolCaller) JQ() string { return "" } diff --git a/pkg/edition/edition.go b/pkg/edition/edition.go index f42df306..6578eea5 100644 --- a/pkg/edition/edition.go +++ b/pkg/edition/edition.go @@ -53,6 +53,10 @@ type ToolCaller interface { Format() string // DryRun returns true when --dry-run is active. DryRun() bool + // Fields returns the global --fields output projection ("" if unset). + Fields() string + // JQ returns the global --jq output filter expression ("" if unset). + JQ() string } // RuntimeDefaultFn resolves a single runtimeDefault placeholder (e.g. diff --git a/skills/mono/references/best_practices/lite-recipes.md b/skills/mono/references/best_practices/lite-recipes.md index f83c086b..0d4fa85e 100644 --- a/skills/mono/references/best_practices/lite-recipes.md +++ b/skills/mono/references/best_practices/lite-recipes.md @@ -249,7 +249,7 @@ query 未提"听记"但任务产出依赖会议讨论内容时(报告/总结/ - 按文件夹:`folderId:2`(2=收件箱, 1=已发送, 5=草稿, 6=已删除) - 按是否有附件:`hasAttachments:true` - 组合:`from:alice AND subject:周报 AND date>2025-06-01T00:00:00Z` -3. 执行搜索:`mail message search --email <邮箱> --query "" --size 20` +3. 执行搜索:`mail message search --email <邮箱> --query "" --limit 20` 4. 查看详情(按需):`mail message get --email <邮箱> --id ` ### mail-send diff --git a/skills/mono/references/global-reference.md b/skills/mono/references/global-reference.md index 7c5605c3..62b5d167 100644 --- a/skills/mono/references/global-reference.md +++ b/skills/mono/references/global-reference.md @@ -64,8 +64,8 @@ dws recovery finalize --event-id --outcome recovered|failed|handoff - | 标志 | 短名 | 说明 | 默认 | |------|:---:|------|------| | `--format` | `-f` | 输出格式: json / table / raw | json | -| `--jq` | | jq 表达式过滤输出 ⚠️**当前为 no-op,不生效**:仍返回完整 JSON,不要依赖它过滤,改在拿到 JSON 后自行处理 | 无 | -| `--fields` | | 筛选输出字段 ⚠️**当前为 no-op,不生效**:仍返回全量字段 | 无 | +| `--jq` | | jq 表达式过滤输出(如 `.result[].name`)。对产品命令(aitable/chat/mail/... 走 MCP 的)已生效;少数工具命令(auth/config/profile/doctor/schema 等)仍直接编码、暂不过滤 | 无 | +| `--fields` | | 筛选输出字段(逗号分隔)。按**顶层信封键**(data/result/success/status…)或**列表元素字段**投影;取 data 内的嵌套字段(如 baseName)请改用 `--jq '.data.baseName'`,`--fields baseName` 会因顶层无此键返回 `{}`。同 `--jq`:产品命令已生效,个别工具命令暂不生效 | 无 | | `--verbose` | `-v` | 详细日志 | false | | `--debug` | | 调试日志 | false | | `--yes` | `-y` | 跳过确认提示 | false | diff --git a/skills/mono/references/intent-guide.md b/skills/mono/references/intent-guide.md index 7bd2b5c8..5ac2f30a 100644 --- a/skills/mono/references/intent-guide.md +++ b/skills/mono/references/intent-guide.md @@ -68,8 +68,8 @@ | "改群昵称/设置群昵称/我在群里的名字" | 设置个人群昵称 | `chat group update-nick` | `chat group rename` | update-nick 改自己的群昵称;rename 改群名称 | | "群备注/给群加备注/修改群备注" | 设置群备注 | `chat group update-alias` | `chat group rename` | update-alias 设置仅自己可见的备注;rename 改群名称全员可见 | | "隐藏会话/隐藏群聊/隐藏对话" | 隐藏会话 | `chat hide` | `chat mute` | hide 隐藏会话不显示;mute 是免打扰但仍显示 | -| "关闭@所有人通知/屏蔽@all/不接收@所有人" | 关闭 @所有人提醒 | `chat mute-at-all` | `chat mute` | mute-at-all 仅屏蔽 @所有人;mute 是整个会话免打扰 | -| "关闭红包通知/屏蔽红包/不接收红包提醒" | 关闭红包提醒 | `chat mute-red-envelope` | `chat mute` | mute-red-envelope 仅屏蔽红包;mute 是整个会话免打扰 | +| "关闭@所有人通知/屏蔽@all/不接收@所有人" | 关闭 @所有人提醒 | `chat mute-at-all` | `chat mute` | mute-at-all 仅屏蔽 @所有人;mute 是整个会话免打扰。⚠️ 当前服务端稳定返回 1002「系统繁忙」,命令暂不可用,先告知用户到钉钉客户端设置 | +| "关闭红包通知/屏蔽红包/不接收红包提醒" | 关闭红包提醒 | `chat mute-red-envelope` | `chat mute` | mute-red-envelope 仅屏蔽红包;mute 是整个会话免打扰。⚠️ 同 mute-at-all,服务端稳定 1002,暂不可用 | | "解散群/解散群聊" | 解散群聊 | `chat group dismiss` | `chat group quit` | dismiss 是群主解散整个群(不可逆);quit 是当前用户自己退群 | | "新成员看历史/历史消息可见范围" | 设置新成员可见历史消息 | `chat group set-history` | `chat group update-settings` | set-history 控制新成员入群后可见历史消息范围;update-settings 是其他群功能开关 | | "群里有哪些机器人/查看群机器人/列出群机器人" | 查看群内机器人列表 | `chat group bots` | `chat group members` | bots 只列机器人;members 列普通群成员 | diff --git a/skills/mono/references/products/aisearch.md b/skills/mono/references/products/aisearch.md index 2f4668f5..417bfbda 100644 --- a/skills/mono/references/products/aisearch.md +++ b/skills/mono/references/products/aisearch.md @@ -98,7 +98,7 @@ Flags: | 操作 | 从返回中提取 | 用于 | |------|-------------|------| -| `aisearch person` | `userId`(用户ID)、`title`(姓名) | 展示搜索结果、后续操作(发消息/建待办等) | +| `aisearch person` | `userId`(用户ID,= `meta.staffId`/`meta.jobNumber`)、`author`(真实姓名,= `meta.name`);`title` 是花名/显示名,不一定等于姓名 | 展示搜索结果、后续操作(发消息/建待办等) | ## 重名消歧 diff --git a/skills/mono/references/products/aitable/aitable-dashboard-chart.md b/skills/mono/references/products/aitable/aitable-dashboard-chart.md index f904158a..c6e4a1e7 100644 --- a/skills/mono/references/products/aitable/aitable-dashboard-chart.md +++ b/skills/mono/references/products/aitable/aitable-dashboard-chart.md @@ -31,7 +31,7 @@ dws aitable chart get --base-id --dashboard-id --chart- | `dashboard delete` | 删除仪表盘 | `--base-id` `--dashboard-id` `--yes` | — | | `dashboard config-example` | 查看仪表盘配置模板 | 无 | 创建前先调此命令了解 config 结构 | | `dashboard share get` | 获取仪表盘分享配置 | `--base-id` `--dashboard-id` | 可能 404,见上方要点 | -| `dashboard share update` | 更新仪表盘分享配置 | `--base-id` `--dashboard-id` `--enabled` | `--enabled true` 开启(配 `--share-type PUBLIC/ORG`)/ `--enabled false` 关闭;ORG 回显 `shareType="[1]"` | +| `dashboard share update` | 更新仪表盘分享配置 | `--base-id` `--dashboard-id` `--enabled` | `--enabled true` 开启,`--share-type` 用 `ORG`(ORG 回显 `shareType="[1]"`);部分组织禁用了 `PUBLIC` 公开分享;若报 `Illegal argument`,改用 `ORG`(组织内分享)。`--enabled false` 关闭 | ## chart 子命令 @@ -43,7 +43,7 @@ dws aitable chart get --base-id --dashboard-id --chart- | `chart delete` | 删除图表 | `--base-id` `--dashboard-id` `--chart-id` `--yes` | | `chart widgets-example` | 查看图表 widgets 配置模板 | 无 | | `chart share get` | 获取图表分享配置 | `--base-id` `--dashboard-id` `--chart-id` | -| `chart share update` | 更新图表分享配置(`--enabled true/false`,开启配 `--share-type PUBLIC/ORG`) | `--base-id` `--dashboard-id` `--chart-id` `--enabled` | +| `chart share update` | 更新图表分享配置(`--enabled true/false`,开启配 `--share-type`,用 `ORG`;部分组织禁用了 `PUBLIC` 公开分享;报 `Illegal argument` 时改用 `ORG`) | `--base-id` `--dashboard-id` `--chart-id` `--enabled` | > `chart create` 的 `--layout` 是**必填**(12 列网格布局,如 `{"x":0,"y":0,"w":6,"h":4}`);不传本地校验直接拒。`chart update` 的 `--config` 也**必填**——即便只想改 layout,也要带完整 config,否则服务端拒绝。 diff --git a/skills/mono/references/products/attendance.md b/skills/mono/references/products/attendance.md index e4dbd069..e839b773 100644 --- a/skills/mono/references/products/attendance.md +++ b/skills/mono/references/products/attendance.md @@ -89,6 +89,19 @@ Flags: 返回每条记录含:用户 ID、实际打卡时间、打卡地址、打卡经纬度、打卡类型(OnDuty/OffDuty)、定位方式(Map/Wifi/etc)。时间跨度不超过 1 个月。 +### 查询个人某日考勤详情 +``` +Usage: + dws attendance record get [flags] +Example: + dws attendance record get --user 011769261608 --date 2026-03-08 +Flags: + --user string 钉钉用户 ID (必填,别名 --users 亦可) + --date string 查询日期, 格式 YYYY-MM-DD (必填) +``` + +查询单个用户在**某一天**的考勤详情(区别于 `check record`/`check result` 的多人时间段批量查询)。返回 `result` 对象含 `isHasSchedule`(当日是否有排班)、`isRest`(是否休息日)、`isUnSigned`(是否未打卡)、`recordList`(打卡明细)、`approveList`(当日审批单)、`workOvertime`、`workTimeDesc` 等字段。`--user` 只接受**单个** userId;查 userId 用 `dws contact user search --query "姓名"`。 + ### 查询审批单(补卡/加班/请假/出差外出) ``` Usage: @@ -1161,13 +1174,13 @@ dws attendance report query-leave --users userId1,userId2 \ dws attendance vacation types --format json # 查看指定员工假期余额 -dws attendance vacation balance --users userId1,userId2 --format json +dws attendance vacation balance --users userId1,userId2 --leave-code <假期类型code> --format json # 查看指定员工某类假期余额 dws attendance vacation balance --users userId1 --leave-code a1b2c3d4-e5f6-7890-abcd-ef1234567890 --format json # 查看指定员工假期余额变更记录 -dws attendance vacation records --user USER_ID --start 2026-04-01 --end 2026-04-22 --format json +dws attendance vacation records --user USER_ID --leave-code <假期类型code> --start 2026-04-01 --end 2026-04-22 --format json # 更新假期规则名称 dws attendance vacation update-type --leave-code a1b2c3d4-e5f6-7890-abcd-ef1234567890 \ diff --git a/skills/mono/references/products/calendar.md b/skills/mono/references/products/calendar.md index 2eba38f0..0dfae700 100644 --- a/skills/mono/references/products/calendar.md +++ b/skills/mono/references/products/calendar.md @@ -5,7 +5,7 @@ - **二级子命令(必选其一)**:`event`(日程)、`attendee`(参会人)、`room`(会议室)、`busy`(闲忙)、`attachment`(日程附件)、`book`(我能看哪些日历本)、`acl`(我的日历共享给了谁)。`dws calendar` 后**必须**紧跟上述之一;**禁止**只执行 `dws calendar`(无子命令)。 - **个人日程 / 给自己留时间块 / 专注时段**:统一走 **`dws calendar event create`**。当前**没有**单独的 `personal schedule create` / `calendar create` 命令。 - **查日程列表**:`dws calendar event list --start "" --end "" --format json`,或优先使用脚本 `python scripts/calendar_today_agenda.py [today|tomorrow|week]`(见文末「自动化脚本」)。 -- **查用户日历本列表**:`dws calendar book list`(返回主日历 `id == "primary"` 等)。**重要**可以查询他人共享给自己的日历本,根据日历本id可以进一步查询对方的日程信息。 +- **查用户日历本列表**:`dws calendar book list`(返回字段为 `calendarId`,主日历 `calendarId == "primary"` 等;注意无 `id` 字段,按 `.id` 提取会取空)。**重要**可以查询他人共享给自己的日历本,根据日历本id可以进一步查询对方的日程信息。 - **CLI 不存在**独立的 `dws calendar list`;若误跑无子命令的 `dws calendar`,会打印整段 Usage,**切勿**将该段 help 当作工具结果再次塞进对话(会急剧增加 token 与首字延迟)。 - **必须**遵循指令说明进行调用。**绝对禁止**使用虚构指令,使用虚构参数。 @@ -618,7 +618,7 @@ dws calendar event list --start "2026-03-10T14:00:00+08:00" --end "2026-03-10T15 | `event respond` | 响应结果 | — | | `room search` | `rooms[].roomId` | room add 的 --rooms 或 event create 的 --rooms | | `room list-groups` | `groups[].groupId` | room search 的 --group-id | -| `book list` | `id`(如 `primary`) | event list/get 的 --calendar-id, book get/update 的 --id | +| `book list` | `calendarId`(如 `primary`) | event list/get 的 --calendar-id, book get/update 的 --id | | `book get` | 日历详细信息 | — | | `book search` | 匹配的日历列表 | book get/update 的 --id | | `acl list` | `aclId` | acl delete 的 --acl-id | @@ -640,7 +640,7 @@ dws calendar event list --start "2026-03-10T14:00:00+08:00" --end "2026-03-10T15 - **搜房无结果**:在符合早停/用户限定范围内,`room search`(含按分组逐组查)全部返回空或无空闲 → 应**直接向用户报错/说明失败**并结束订房;**禁止**假设 roomId、禁止无合法 `roomId` 时调用 `room add` / `event create --rooms` 试探、禁止用 `event get` 等绕路推断 roomId - **评测 / 自动化断言**:凡涉及 `room add` / `event create --rooms` 的流程,`--rooms` 只能填上游 `room search`(或等价接口)返回 JSON 中的 **`rooms[].roomId`**;不得以会议室展示名、楼层文案或用户口语当作 `roomId` - **附件**:`attachment add` 仅负责挂载,**不上传**文件;fileId 必须先通过钉盘流程取得;`--files` 多附件用 `:` 元素逗号分隔 -- **日历本**:`book list` 返回的 `id` 才是合法 `calendarId`;如无明确说明,`event list` / `event get` 都不要带 `--calendar-id`,让接口默认走 primary 主日历 +- **日历本**:`book list` 返回的 `calendarId` 字段才是合法日历 id(无 `id` 字段);如无明确说明,`event list` / `event get` 都不要带 `--calendar-id`,让接口默认走 primary 主日历 - **已弃用入参**:`--max-results`(event list)在新 MCP schema 中被移除;CLI 仍接受但**不会**透传到 MCP;模型生成命令时**禁止**继续使用 ## 自动化脚本 diff --git a/skills/mono/references/products/chat.md b/skills/mono/references/products/chat.md index bed1c0da..dfdf1f3b 100644 --- a/skills/mono/references/products/chat.md +++ b/skills/mono/references/products/chat.md @@ -334,7 +334,7 @@ Flags: #### 移除用户的指定群身份 — 从用户身上移除指定的群身份(不影响其他群身份) -> ⚠️ 当前不可用:真机服务端对该命令恒返回 1002(系统繁忙),历史回归至今未修,属服务端问题。需要清除某人身份时,用 `chat group-role set-user --role-ids ""`(传空 role-ids 覆盖为无身份)兜底;但 set-user 会清掉该用户的**全部**身份,无法只移除其中一个。 +> ⚠️ 当前不可用:该命令当前调用常失败(服务端返回 1002 系统繁忙),多为服务端侧限制。需要清除某人身份时,用 `chat group-role set-user --role-ids ""`(传空 role-ids 覆盖为无身份)兜底;但 set-user 会清掉该用户的**全部**身份,无法只移除其中一个。 ``` Usage: dws chat group-role remove-user [flags] @@ -729,7 +729,7 @@ Example: dws chat message list-by-sender --sender-open-dingtalk-id --start "2026-03-10T00:00:00+08:00" --end "2026-03-11T00:00:00+08:00" --limit 50 --cursor Flags: --sender-user-id string 发送者 userId(与 --sender-open-dingtalk-id 二选一) - --sender-open-dingtalk-id string 发送者 openDingTalkId(与 --sender-user-id 二选一,适用于无法获取 userId 的场景) + --sender-open-dingtalk-id string 发送者 openDingTalkId(与 --sender-user-id 二选一);该路径当前返回 result:{}、success:null(疑似服务端问题),优先用 --sender-user-id --start string 开始时间,ISO-8601 格式 (必填) --end string 结束时间,ISO-8601 格式 (必填) --limit int 每页返回数量(默认 50) @@ -737,8 +737,8 @@ Flags: 注意: - --sender-user-id 和 --sender-open-dingtalk-id 二者互斥,必须且只能指定其一: - - --sender-user-id 传 userId(企业内部应用常用) - - --sender-open-dingtalk-id 传 openDingTalkId(三方应用或跨组织场景常用,无法获取 userId 时使用) + - --sender-user-id 传 userId(企业内部应用常用,当前唯一可用路径) + - --sender-open-dingtalk-id 传 openDingTalkId(当前返回 result:{}、success:null(疑似服务端问题);即使只有 openDingTalkId,也建议先用 contact 换取 userId 后走 --sender-user-id) - openDingTalkId 获取方式见下方「openDingTalkId 获取方式」小节 - 不需要指定单聊/群聊,返回结果自带会话类型标识 - 时间支持多种 ISO-8601 格式,如 "2026-03-10T00:00:00+08:00"、"2026-03-10 14:00:00"、"2026-03-10" 等 @@ -1025,20 +1025,20 @@ Flags: Usage: dws chat message download-media [flags] Example: - dws chat message download-media --type mediaId --resource-id --message-id --open-conversation-id --output ./downloads/ dws chat message download-media --type mediaId --resource-id --message-id --open-conversation-id --output ./photo.jpg + dws chat message download-media --type mediaId --resource-id --message-id --open-conversation-id --output ./downloads/photo.jpg Flags: --type string 资源类型: mediaId (必填) --resource-id string 资源 ID,mediaId 类型时为消息中的 mediaId 值 (必填) --message-id string 消息 openMessageId (必填) --open-conversation-id string 会话 openConversationId (必填) - --output string 本地保存路径,文件或目录 (必填) + --output string 本地保存路径 (必填),建议传完整文件路径(含文件名) 注意: - resource-id 从 `dws chat message list` 返回的消息内容中获取 mediaId - message-id 从 `dws chat message list` 返回的 openMessageId - open-conversation-id 从 `dws chat search` 获取 openConversationId - - --output 如果指定目录,文件名会从下载 URL 中自动推断 + - --output 建议传完整文件路径(含扩展名,如 ./photo.jpg);只传目录(如 ./downloads/)在旧版本会下载失败,请显式带上文件名 ``` ### search-common (搜索共同群) @@ -1405,7 +1405,7 @@ Flags: #### 关闭/开启 @所有人消息提醒 — 关闭或开启会话中 @所有人的消息通知 -> ⚠️ 当前不可用:真机服务端对该命令恒返回 1002(系统繁忙),历史回归至今未修,属服务端问题。命令本身参数合法,但调用不会生效。 +> ⚠️ 当前不可用:该命令当前调用常失败(服务端返回 1002 系统繁忙),多为服务端侧限制。命令本身参数合法,但调用不会生效。 ``` Usage: dws chat mute-at-all [flags] @@ -1428,7 +1428,7 @@ Flags: #### 关闭/开启红包消息提醒 — 关闭或开启会话中的红包消息通知 -> ⚠️ 当前不可用:真机服务端对该命令恒返回 1002(系统繁忙),历史回归至今未修,属服务端问题。命令本身参数合法,但调用不会生效。 +> ⚠️ 当前不可用:该命令当前调用常失败(服务端返回 1002 系统繁忙),多为服务端侧限制。命令本身参数合法,但调用不会生效。 ``` Usage: dws chat mute-red-envelope [flags] @@ -1518,7 +1518,7 @@ Flags: 注意: - 返回结果包含单聊和群聊,不区分会话类型 - --limit 范围 1-100,默认 100,上限 100;传入 >100 会报错拒绝,不会静默截断 - - 分页当前不可用:真机 hasMore 恒为 false、nextCursor 恒为 null,本命令最多返回 100 条会话,无法用 --cursor 继续翻页取更多 + - 分页当前不可用:当前 hasMore 恒为 false、nextCursor 恒为 null,本命令最多返回 100 条会话,无法用 --cursor 继续翻页取更多 - 与 list-top-conversations 的区别: 本命令返回全部会话(单聊+群聊),list-top-conversations 仅返回置顶会话 ``` @@ -1608,7 +1608,7 @@ Flags: #### 审批入群验证 — 通过、删除单个审核 -真机当前仅 AuditApprove(通过)和 AuditDelete(删除)两个动作可用。 +当前仅 AuditApprove(通过)和 AuditDelete(删除)两个动作可用。 ``` Usage: @@ -1626,7 +1626,7 @@ Flags: --description string 审批说明(可选) 注意: - - status 真机仅支持 AuditApprove(通过) 和 AuditDelete(删除);AuditIgnore(忽略)、AuditRefuse(拒绝)、AuditBlock(拒绝且拉黑) 会被服务端拒绝报 unsupported audit status,属服务端限制 + - status 仅支持 AuditApprove(通过) 和 AuditDelete(删除);AuditIgnore(忽略)、AuditRefuse(拒绝)、AuditBlock(拒绝且拉黑) 会被服务端拒绝报 unsupported audit status,属服务端限制 - record-id、applicant、inviter 可通过 dws chat group list-join-validations 查询获得 ``` diff --git a/skills/mono/references/products/contact.md b/skills/mono/references/products/contact.md index 74036dc8..94680ef8 100644 --- a/skills/mono/references/products/contact.md +++ b/skills/mono/references/products/contact.md @@ -213,7 +213,7 @@ Example: Flags: 无 Notes: - - 无需参数,返回当前企业全部角色列表(labelId、labelName等) + - 无需参数,返回当前企业全部角色,按 `groupName` 分组,每组 `labels[]` 里每个角色是 `{labelId, name}`(字段名是 `name`,不是 `labelName`) - 用于不知道准确角色名称时先浏览全部角色 - 典型场景:用户说“企业所有主管/查所有管理员/财务人员有哪些”→ 先 label list 浏览全部角色,匹配目标角色后 label list-members 获取成员 ``` @@ -360,7 +360,7 @@ dws contact user dismission search --depts 123456,789012 --hide-retirement=false | `user get-self/search` | `orgAuthEmail` | mail message send 的 --to/--cc (跨产品) | | `user get-self/search` | `userId` | profile get 的 --staff-id | | `user profile fields` | `fieldCode` | profile get 的 --fields | -| `label list` | `labelId` / `labelName` | `label get --names` 或 `label list-members --id` | +| `label list` | `labelId` / `name` | `label get --names` 或 `label list-members --id` | | `label get` | `labelId` | `label list-members` 的 --id | | `dept search/list-children` | `deptId` | dept get-info/list-children/list-members 的 --dept/--depts | | `dept search/list-children` | `deptId` | dismission search 的 --depts | diff --git a/skills/mono/references/products/dev.md b/skills/mono/references/products/dev.md index b0a2a9ae..f6143ab3 100644 --- a/skills/mono/references/products/dev.md +++ b/skills/mono/references/products/dev.md @@ -118,11 +118,8 @@ dws dev app robot disable --unified-app-id --format json ## dev app credentials — 凭证 ```bash -# 查询 clientId/clientSecret +# 查询 clientId/clientSecret(credentials 下只有 get 子命令) dws dev app credentials get --unified-app-id --format json - -# 重置凭证(旧 secret 立即失效) -dws dev app credentials reset --unified-app-id --yes --format json ``` --- @@ -147,8 +144,8 @@ dws dev connect --channel opencode \ dws dev connect --daemon \ --robot-client-id --robot-client-secret -# 查看/停止后台连接器 -dws dev connect status --format json +# 查看/停止后台连接器(status/list 忽略全局 --format,出 JSON 用专属 --json) +dws dev connect status --json dws dev connect stop ``` @@ -208,7 +205,7 @@ dws dev app version status --unified-app-id --version-id ## 注意事项 -- **`clientSecret` 只在 `robot result` 返回一次**,务必立即保存;遗失需走 `credentials reset` 重置 +- **`clientSecret` 只在 `robot result` 返回一次**,务必立即保存;dws 只能 `credentials get` 读取,不支持重置,遗失后到开放平台控制台重置密钥 - 改配置后机器人不自动生效,需走 `version create → publish` 才上线 - `hermes`/`openclaw` 渠道走官方建联,`dws dev connect` 不代建机器人,会输出指引后退出 - 应用名在企业内唯一;`app list/get` 用 `--app-key` 过滤但不能定位单应用,定位单应用须用 `--unified-app-id` diff --git a/skills/mono/references/products/doc.md b/skills/mono/references/products/doc.md index 913016ac..fe799e71 100644 --- a/skills/mono/references/products/doc.md +++ b/skills/mono/references/products/doc.md @@ -27,7 +27,7 @@ dws doc permission --help ### 搜索 / 遍历文件(已迁移,不再是 doc 子命令) -> **弃用提示**:`dws doc search` 和 `dws doc list` 已迁移到 `drive` / `wiki`。真机上这两条虽仍能跑,但每次都会打印弃用警告,请改用下面的命令,命令详情见 [drive.md](./drive.md)。 +> **弃用提示**:`dws doc search` 和 `dws doc list` 已迁移到 `drive` / `wiki`。这两条虽仍能跑,但每次都会打印弃用警告,请改用下面的命令,命令详情见 [drive.md](./drive.md)。 | 旧命令(弃用) | 改用 | 场景 | |--------------|------|------| @@ -83,7 +83,7 @@ Flags: ### 创建其他类型文件 (表格/脑图/白板/多维表/画板/文件夹) -> **弃用提示**:`dws doc file create --type` 已弃用,真机每次执行都会打印 `deprecated, use 'dws wiki node create --type '`。创建各类型文件节点改用 `dws wiki node create`(与 intent-guide 一致);创建普通文件夹用 `dws drive mkdir`。 +> **弃用提示**:`dws doc file create --type` 已弃用,执行时会打印 `deprecated, use 'dws wiki node create --type '`。创建各类型文件节点改用 `dws wiki node create`(与 intent-guide 一致);创建普通文件夹用 `dws drive mkdir`。 ``` Usage: @@ -133,7 +133,7 @@ Flags: ### 下载文件到本地(已迁移到 drive) -> **弃用提示**:`dws doc download` 已迁移到 `dws drive download`(真机执行 `doc download` 会打印弃用警告)。下载已有文件(PDF/图片/附件等非在线文档)改用: +> **弃用提示**:`dws doc download` 已迁移到 `dws drive download`(执行 `doc download` 会打印弃用警告)。下载已有文件(PDF/图片/附件等非在线文档)改用: ``` dws drive download --node --output ~/downloads/ @@ -261,11 +261,11 @@ Usage: Example: dws doc comment list --node dws doc comment list --node --type inline --resolve-status unresolved - dws doc comment list --node --page-size 20 --next-token + dws doc comment list --node --limit 20 --cursor Flags: --node string 目标文档的标识,支持传入 URL 或 ID (必填) - --page-size int 每页返回的评论数量,默认 50,最大 50 - --next-token string 分页游标,从上一次请求的返回结果中获取 (首次请求不传) + --limit int 每页返回的评论数量,默认 50,最大 50 + --cursor string 分页游标,从上一次请求的返回结果中获取 (首次请求不传) --type string 按评论类型过滤: global (全文评论) / inline (划词评论) --resolve-status string 按解决状态过滤: resolved (已解决) / unresolved (未解决) ``` diff --git a/skills/mono/references/products/doc/doc-comment.md b/skills/mono/references/products/doc/doc-comment.md index 50cc9dda..0d2f3a3f 100644 --- a/skills/mono/references/products/doc/doc-comment.md +++ b/skills/mono/references/products/doc/doc-comment.md @@ -15,11 +15,11 @@ Usage: Example: dws doc comment list --node dws doc comment list --node --type inline --resolve-status unresolved - dws doc comment list --node --page-size 20 --next-token + dws doc comment list --node --limit 20 --cursor Flags: --node string 目标文档的标识,支持传入 URL 或 ID (必填) - --page-size int 每页返回的评论数量 - --next-token string 下一页 token,从上一次请求的返回结果中获取 (首次请求不传) + --limit int 每页返回的评论数量,默认 50,最大 50 + --cursor string 分页游标,从上一次请求的返回结果中获取 (首次请求不传) --type string 按评论类型过滤: global (全文评论) / inline (划词评论) --resolve-status string 按解决状态过滤: resolved (已解决) / unresolved (未解决) ``` diff --git a/skills/mono/references/products/doc/doc-file-ops.md b/skills/mono/references/products/doc/doc-file-ops.md index cabaaa11..6ed912b2 100644 --- a/skills/mono/references/products/doc/doc-file-ops.md +++ b/skills/mono/references/products/doc/doc-file-ops.md @@ -3,7 +3,7 @@ > **前置条件(MUST READ):** 执行本命令前,必须先用 Read 工具读取以下文件: > 1. [`../doc.md`](../doc.md) — 命令路由 + 场景索引 + 意图判断 + 工作流 -> **弃用提示(文件管理命令正在迁移到 drive / wiki)**:本文所列 `doc` 文件管理命令在真机上虽仍能跑,但执行时会打印弃用警告,请优先改用 `drive` / `wiki` 对应命令: +> **弃用提示(文件管理命令正在迁移到 drive / wiki)**:本文所列 `doc` 文件管理命令虽仍能跑,但执行时会打印弃用警告,请优先改用 `drive` / `wiki` 对应命令: > - `doc download` → **`dws drive download`**(下载已有文件;在线文档导出 docx 仍走 `doc export`) > - `doc folder create` → **`dws drive mkdir`**(个人空间/钉盘)或 **`dws wiki node create --workspace --type folder`**(知识库内) > - `doc upload` / `doc copy` / `doc move` / `doc rename` / `doc delete` 也在同一迁移方向(→ `drive`),命令用法以 `drive` 为准。 diff --git a/skills/mono/references/products/doc/doc-list.md b/skills/mono/references/products/doc/doc-list.md index 21a62bb3..dff79267 100644 --- a/skills/mono/references/products/doc/doc-list.md +++ b/skills/mono/references/products/doc/doc-list.md @@ -1,6 +1,6 @@ # doc list(已弃用 → drive list / wiki node list) -> **弃用提示**:`dws doc list` 已迁移,不再是 `doc` 子命令。真机上它虽仍能跑,但每次都会打印弃用警告: +> **弃用提示**:`dws doc list` 已迁移,不再是 `doc` 子命令。它虽仍能跑,但每次都会打印弃用警告: > `'dws doc list' is deprecated, use 'dws drive list --workspace ' or 'dws wiki node list --workspace '`。 > 遍历文件夹/知识库请改用下面的命令,命令详情见 [`../drive.md`](../drive.md)。 diff --git a/skills/mono/references/products/doc/doc-search.md b/skills/mono/references/products/doc/doc-search.md index 3b259d53..bbd75afc 100644 --- a/skills/mono/references/products/doc/doc-search.md +++ b/skills/mono/references/products/doc/doc-search.md @@ -1,6 +1,6 @@ # doc search(已弃用 → drive search / wiki node search) -> **弃用提示**:`dws doc search` 已迁移,不再是 `doc` 子命令。真机上它虽仍能跑,但每次都会打印弃用警告: +> **弃用提示**:`dws doc search` 已迁移,不再是 `doc` 子命令。它虽仍能跑,但每次都会打印弃用警告: > `'dws doc search' is deprecated, use 'dws drive search' or 'dws wiki node search --workspace '`。 > 搜索文档/文件请改用下面的命令,命令详情见 [`../drive.md`](../drive.md)。 diff --git a/skills/mono/references/products/drive.md b/skills/mono/references/products/drive.md index 30b6a3c8..8f9a1d89 100644 --- a/skills/mono/references/products/drive.md +++ b/skills/mono/references/products/drive.md @@ -514,7 +514,7 @@ dws drive copy --node <源文件dentryUuid> --folder <目标文件夹fileId> --f - 不传 `--space-id` 时默认使用「我的文件」空间 - 不传 `--folder` 时默认操作空间根目录 - `--folder` 只能使用父文件夹的 `dentryUuid`。不要把 `drive info` 返回的数字型 `dentryId` 当作父目录 -- **`--limit` 最大值为 50**,禁止传入超过 50 的值(如 `--limit 100`)。用户要求超过 50 条时,应使用 `--limit 50` 配合 `--cursor` 分页查询 +- **`--limit` 有效上限为 50**:CLI 不做本地校验,传 `--limit 100` 不会报错,但服务端每页最多返回 50 条,超出部分无效。用户要求超过 50 条时,应使用 `--limit 50` 配合 `--cursor` 分页查询 - `--order-by` 支持: `createTime`、`modifyTime`、`name` - **上传文件首选 `dws drive upload` 命令**;手动三步(`upload-info` → HTTP PUT → `commit`)仅用于自定义流式上传等特殊场景。手动三步时 HTTP PUT 必须把 upload-info 返回的 `headers` 全部回传,`Content-Type` 通常要留空;PUT 返回 200 后才能调 `commit`;`uploadId` 有过期时间,过期需重新 `upload-info`;`--folder` 在 upload-info / commit 中要保持一致 - `--file-name` 必须包含扩展名(如 `report.pdf`) diff --git a/skills/mono/references/products/mail.md b/skills/mono/references/products/mail.md index 4a822165..14159224 100644 --- a/skills/mono/references/products/mail.md +++ b/skills/mono/references/products/mail.md @@ -214,7 +214,9 @@ Flags: | 字段 | 类型 | 说明 | |------|------|------| -| `message` | `object` | 邮件完整信息,包含主题、发件人、收件人、正文、附件等 | +| `message` | `object` | 邮件信息,含 subject/from/toRecipients/ccRecipients/markdownBody/conversationId/receivedDateTime/size 等 | + +> **注意:** 即使邮件确有附件,`message get` 返回的 `message` 对象也**不含** `attachments`/`isRead`/`tags` 字段(实测 keys 仅上表所列这些)。要取附件请另用 `dws mail attachment list`。 ### 发送邮件 ``` @@ -1364,6 +1366,8 @@ Flags: --actions string 规则动作 JSON 数组 (必填) ``` +> **⚠️ 实测:** 尽管 `--help` 把 `--conditions` 标为(可选),服务端实际要求它**非空**——省略或传空会返回 `209 SYS_UNKNOWN_ERROR(1001)` 失败,帮助文本承诺的"命中所有邮件"无条件匹配**不可用**。create 请始终传入有效条件。 + **返回字段:** | 字段 | 类型 | 说明 | @@ -1375,11 +1379,11 @@ Flags: #### 更新收信规则 -更新已有的收信规则。**除 `--conditions` 外所有参数均为必填**。 +更新已有的收信规则。**所有参数(含 `--conditions`)均须传入有效值**——见下方警告。 > **建议工作流:** 先通过 `dws mail rule list` 获取当前规则的完整配置,再传入需要修改的字段值。 > -> `--conditions` 为空或不传表示命中所有邮件(无条件匹配)。`--actions` 格式同 create 命令。 +> **⚠️ 实测:** `--help` 称 `--conditions` 为空即命中所有邮件,但该无条件匹配**不可用**——省略或传空会返回 `209 SYS_UNKNOWN_ERROR(1001)` 失败。update 请始终传入有效条件。`--actions` 格式同 create 命令。 ``` Usage: @@ -1399,6 +1403,8 @@ Flags: --actions string 规则动作 JSON 数组 (必填) ``` +> **⚠️ 实测:** 上方 `--conditions` 的"(可选,为空表示命中所有邮件)"是 `--help` 原文,但服务端实际要求它**非空**——省略或传空会返回 `209 SYS_UNKNOWN_ERROR(1001)` 失败。update 须传入有效条件。 + **返回字段:** | 字段 | 类型 | 说明 | diff --git a/skills/mono/references/products/minutes.md b/skills/mono/references/products/minutes.md index eef1999a..dc1ce7f4 100644 --- a/skills/mono/references/products/minutes.md +++ b/skills/mono/references/products/minutes.md @@ -26,7 +26,7 @@ dws minutes │ ├── shared [--query] [--start] [--end] [--max] [--next-token] # 仅查询他人共享给我的听记(不含我自己创建的) │ └── all [--query] [--start] [--end] [--max] [--next-token] # 查询我可访问的所有听记(= mine ∪ shared,覆盖面最广,默认 scope) ├── get # 获取听记详情(get 后必须跟子命令名) -│ ├── info --id # 获取听记基础信息(标题、时长、参与人、状态等) +│ ├── info --id # 获取听记基础信息(标题、时长、开始时间、taskUuid、访问链接;不含参会人) │ ├── summary --id # 获取听记 AI 摘要(结构化纪要) │ ├── transcription --id # 获取听记语音转写原文(逐字稿,支持分页) │ ├── keywords --id # 获取听记关键词 @@ -618,7 +618,7 @@ AI 基于已拉取的转写数据,**本地完成**聚类与摘要(无需新 > **铁律 2:严禁用 AI 摘要里的"参与人/参会人"字段判断某人是否参会** > - AI 摘要中的"参与人"字段往往**只截取最显著的 1-2 个名字**(通常是创建人或发言最多的人),**不是完整参会人列表** > - "AI 摘要参与人只有故愚 → 灵麦没参会" 是典型的错误推理链 -> - 如果一定要列出参会人,应使用 `dws minutes get info` / `get batch` 返回的 `participants` 字段,而不是摘要文本里的描述 +> - 如果一定要列出参会人,只能从 `get transcription` 的转写发言人(`speakerName`/`speakerId`)汇总——`get info` / `get batch` **均不返回** `participants` 结构化字段(get info 只返回 duration/startTime/taskUuid/title/url,get batch 只返回 creatorNick/creatorUnionId/status 等),别指望从它们拿参会名单 > > **铁律 3:通讯录查询是 Step 4 的强制起点,不依赖 Step 3 是否成功** > - Step 2 一旦返回"未命中真名标注",**Step 3 与 Step 4 ① 必须并发触发**,不要等 Step 3 失败再补查 @@ -667,7 +667,7 @@ Step 7: 引导用户替换发言人(调用 speaker replace 写回听记) > - **未命中真名标注 ≠ 目标人物没参会**——绝大多数听记的发言人都是匿名编号,"未命中"是**默认场景**,恰恰是发言人识别功能要解决的问题 > - **不要在转写文本里 grep 目标人名**作为存在性判断——花名/真名通常不会出现在 TA 自己的发言里(参见铁律 1) > - **不要把 `dws minutes get summary` 摘要文本里写到的"参与人"当作完整参会列表**——AI 摘要里的"参与人"字段只截取最显著的 1-2 个名字,不是完整名册(参见铁律 2) -> - 唯一能下"目标人物没参会"结论的场景是:`get info` / `get batch` 返回的结构化 `participants` 字段里**完整列出参会人且不含目标**,且 `dws contact user search` 也搜不到该人 → 才允许告知用户"该人不在参会列表" +> - 听记各接口都拿不到权威的完整参会名册:`get info` / `get batch` **均不返回** `participants` 字段,转写发言人列表又常是匿名编号。因此**几乎无法**仅凭听记数据下"目标人物没参会"的结论;只有当转写发言人已全部实名标注且不含目标、且 `dws contact user search` 也搜不到该人时,才可谨慎告知"未在可见发言人中找到该人" ##### Step 3: 转写原文内推断(优先) @@ -813,7 +813,7 @@ Flags: --ids string 听记 taskUuid 列表,逗号分隔 (必填) ``` -返回字段: 听记标题、时长、参与人列表、创建时间、taskUuid、听记状态 +返回字段: `result.minutesDetails` 数组,每项含 `title`(标题)、`durationMicros`(时长, 微秒)、`startTime`(开始时间, 毫秒)、`taskUuid`、`status`(状态)、`creatorNick`(创建人昵称)、`creatorUnionId`、`size`、`bizType`、`isDeleted`。**不返回参会人列表**。 ### 修改听记标题 ``` @@ -2299,7 +2299,7 @@ Step 2 **[禁止]** 看到全是匿名编号 → 没有继续走 Step 3-4,反 1. **"在转写里搜不到目标人名"绝不构成"没参会"的证据**:花名/真名通常不出现在 TA 自己的发言里,这是默认场景而非例外。99% 的听记发言人都是匿名编号——这正是发言人识别功能要解决的问题。 -2. **AI 摘要的"参与人"字段是低保真信号,不能作为参会判断依据**:`get summary` 返回的是 AI 生成的自然语言摘要,里面提到的"参与人"通常只是最显著的 1-2 人;要拿完整参会列表,应使用 `get info` / `get batch` 返回的结构化 `participants` 字段。 +2. **AI 摘要的"参与人"字段是低保真信号,不能作为参会判断依据**:`get summary` 返回的是 AI 生成的自然语言摘要,里面提到的"参与人"通常只是最显著的 1-2 人。而 `get info` / `get batch` **均不返回**结构化 `participants` 字段,无法从中拿完整参会列表;能拿到的只有 `get transcription` 的转写发言人(常为匿名编号)。 3. **`dws contact user search` 是 Step 4 ① 的必跑项,单次调用就能突破死局**:本案例的整个失败链路只要有 1 次通讯录查询就能立即扭转——拿到"木兰=王佳明,产品设计部"后,Step 5 的角色匹配就有了锚点,再也不会得出"没参会"的错误结论。 diff --git a/skills/mono/references/products/oa.md b/skills/mono/references/products/oa.md index c44c049e..643b0ac7 100644 --- a/skills/mono/references/products/oa.md +++ b/skills/mono/references/products/oa.md @@ -314,7 +314,7 @@ Flags: 用户说"拒绝审批/驳回" → 先 `tasks` 获取 taskId,再 `reject` 用户说"撤回审批/取消审批" → `approval revoke` 用户说"审批记录/操作历史" → `approval records` -用户说"我发起的审批" → `approval list-initiated`(需 --process-code,可从 list-forms 或 detail 获取) +用户说"我发起的审批" → `approval list-initiated`(需 --process-code,可从 list-forms / search-forms / detail 获取) 用户说"有哪些审批表单/可见表单" → `approval list-forms` 用户说"我有哪些待审的任务" → `approval tasks` 用户说"我发起的审批单" -> `approval list-submitted` @@ -351,7 +351,7 @@ dws oa approval records --instance-id --format json # 7. 获取可见审批表单(得到 processCode) dws oa approval list-forms --cursor 0 --limit 100 --format json -# 8. 查看自己发起的审批列表(--process-code 来自 list-forms 或 detail) +# 8. 查看自己发起的审批列表(--process-code 来自 list-forms / search-forms / detail) dws oa approval list-initiated --process-code \ --start "2026-03-10T00:00:00+08:00" --end "2026-03-10T23:59:59+08:00" \ --cursor 0 --limit 20 --format json @@ -383,6 +383,7 @@ dws oa approval oa-cc-noticer --instance-id --users "userId1 | `tasks` | `taskId` | approve / reject / redirect-task 的 --task-id | | `detail` | `processCode` | list-initiated 的 --process-code | | `list-forms` | `processCode` | list-initiated 的 --process-code | +| `search-forms` | `processCode` | list-initiated 的 --process-code | ## 注意事项 @@ -391,7 +392,7 @@ dws oa approval oa-cc-noticer --instance-id --users "userId1 - `redirect-task` 的 `--to-actioner-id` 可通过 `dws contact user search` 获取目标用户 userId - `revoke` 只能撤销自己发起的审批 - `--remark` 审批意见虽为可选,但建议填写以留存审批痕迹 -- `list-initiated` 的 `--process-code` 可从 `list-forms` 或 `detail` 返回中提取 +- `list-initiated` 的 `--process-code` 可从 `list-forms`、`search-forms` 或 `detail` 返回中提取。当 `list-forms` 返回 `processCodeList` 为空(`totalCount -1`)时,用 `search-forms --query <表单名>`(如 `--query 报销`)按名称精准拿 `processCode` 更稳 - `list-initiated` 的 `--start` / `--end` 区间有后端上限(约 120 天)。超过上限会返回误导性的 `business_error: 时间戳无效`(实为区间过长,不是时间格式问题)。跨度大时请拆成多段短区间分别查询 ## 自动化脚本 diff --git a/skills/mono/references/products/sheet.md b/skills/mono/references/products/sheet.md index 567c4278..44c8fb55 100644 --- a/skills/mono/references/products/sheet.md +++ b/skills/mono/references/products/sheet.md @@ -1581,7 +1581,7 @@ Flags: - MCP 返回 `FAILED`:命令立即返回错误并附带失败原因,**禁止自动重试 `dws sheet export`**,告知用户稍后再试 - 轮询 30 次仍 `PROCESSING`:命令返回超时错误,告知用户稍后再试 -**限制**:仅支持钉钉在线电子表格(alxs)→ xlsx。导出钉钉文字文档请使用 `doc` 产品对应的导出工具。 +**限制**:仅支持钉钉在线电子表格(axls)→ xlsx。导出钉钉文字文档请使用 `doc` 产品对应的导出工具。 ### 表格模板(list / search / apply) ``` @@ -2067,7 +2067,7 @@ dws sheet export --node --output ./ - `filter-view delete-criteria` 仅清除指定列的条件,不会删除整个筛选视图。如需删除整个筛选视图,请使用 `filter-view delete` - `filter-view delete-criteria` 如果指定列没有设置筛选条件,调用不会报错 - 筛选视图相关操作需要"可阅读"权限(list / info / list-criteria / get-criteria)或"可编辑"权限(create / update / delete / update-criteria / delete-criteria),不支持跨组织操作 -- ★ `export` 仅支持钉钉在线电子表格(alxs)→ xlsx;传入钉钉文字文档会报 `invalidRequest.document.typeIllegal` +- ★ `export` 仅支持钉钉在线电子表格(axls)→ xlsx;传入钉钉文字文档会报 `invalidRequest.document.typeIllegal` - ★ `export` 为单命令一站式,CLI 内部已自动完成「提交 → 渐进式退避轮询 → 可选下载」,**Agent 不得在外部实现轮询或重试**;命令返回成功后不再调用其他 export 相关命令 - `export` 内置轮询策略:1~5 次间隔 2s、6~10 次间隔 5s、11~20 次间隔 10s、21~30 次间隔 15s,硬上限 30 次(约 5 分钟);超时后命令返回错误,告知用户稍后再试即可 - ★ `export` 命令返回失败或超时时,**禁止自动重调 `dws sheet export`**;直接告知用户导出失败并建议稍后再试 diff --git a/skills/mono/references/products/sheet/sheet-export.md b/skills/mono/references/products/sheet/sheet-export.md index f390efed..891da573 100644 --- a/skills/mono/references/products/sheet/sheet-export.md +++ b/skills/mono/references/products/sheet/sheet-export.md @@ -56,7 +56,7 @@ Flags: - MCP 返回 `FAILED`:命令立即返回错误并附带失败原因,**禁止自动重试 `dws sheet export`**,告知用户稍后再试 - 轮询 30 次仍 `PROCESSING`:命令返回超时错误,告知用户稍后再试 -**限制**:仅支持钉钉在线电子表格(alxs)→ xlsx。导出钉钉文字文档请使用 `doc` 产品对应的导出工具。 +**限制**:仅支持钉钉在线电子表格(axls)→ xlsx。导出钉钉文字文档请使用 `doc` 产品对应的导出工具。 ## 核心工作流 @@ -86,7 +86,7 @@ dws sheet export --node --output ./ ## 注意事项 -- ★ `export` 仅支持钉钉在线电子表格(alxs)→ xlsx;传入钉钉文字文档会报 `invalidRequest.document.typeIllegal` +- ★ `export` 仅支持钉钉在线电子表格(axls)→ xlsx;传入钉钉文字文档会报 `invalidRequest.document.typeIllegal` - ★ `export` 为单命令一站式,CLI 内部已自动完成「提交 → 渐进式退避轮询 → 可选下载」,**Agent 不得在外部实现轮询或重试**;命令返回成功后不再调用其他 export 相关命令 - `export` 内置轮询策略:1~5 次间隔 2s、6~10 次间隔 5s、11~20 次间隔 10s、21~30 次间隔 15s,硬上限 30 次(约 5 分钟);超时后命令返回错误,告知用户稍后再试即可 - ★ `export` 命令返回失败或超时时,**禁止自动重调 `dws sheet export`**;直接告知用户导出失败并建议稍后再试 diff --git a/skills/mono/references/url-patterns.md b/skills/mono/references/url-patterns.md index 26cdb302..452817ca 100644 --- a/skills/mono/references/url-patterns.md +++ b/skills/mono/references/url-patterns.md @@ -90,14 +90,14 @@ Step 3 → 按下方路由规则映射到对应产品 | `contentType=ALIDOC`, `extension=adoc` | `doc` | 按 [doc.md](./products/doc.md) 操作 | | `contentType=ALIDOC`, `extension=axls` | `sheet` | 按 [sheet.md](./products/sheet.md) 操作(仅 `axls` 在线电子表格) | | `contentType=ALIDOC`, `extension=able` | `aitable` | 将 nodeId 作为 baseId,按 [aitable.md](./products/aitable.md) 操作 | -| `contentType=DOCUMENT`, `extension=xlsx` / `xls` / `xlsm` / `csv` | `doc` | 必须用 `dws doc download` 下载到本地处理,禁止走 `sheet`(非在线表格,sheet 命令无法操作) | -| `contentType≠ALIDOC`, `nodeType=file` | `doc` | 调用 `dws doc download` 下载,返回文件下载链接 | +| `contentType=DOCUMENT`, `extension=xlsx` / `xls` / `xlsm` / `csv` | `doc` | 必须用 `dws drive download` 下载到本地处理,禁止走 `sheet`(非在线表格,sheet 命令无法操作) | +| `contentType≠ALIDOC`, `nodeType=file` | `doc` | 调用 `dws drive download` 下载,返回文件下载链接 | | `nodeType=folder` | `doc` | 调用 `dws doc list --folder ` 列出指定文件夹直接子节点列表 | | 以上均不匹配 | — | 告知用户当前暂不支持该类型 | > axls vs xlsx 关键区分: > - `axls`(钉钉在线电子表格,`contentType=ALIDOC`)→ 走 `sheet` 产品线(读/写/筛选/导出等服务端原子操作) -> - `xlsx` / `xls` / `xlsm` / `csv`(上传到文档空间的本地表格文件,`contentType=DOCUMENT`)→ 必须走 `dws doc download` 下载到本地后再解析处理,严禁错误路由到 `sheet` 产品线(sheet 命令只支持在线表格,调用 xlsx 节点会直接报错) +> - `xlsx` / `xls` / `xlsm` / `csv`(上传到文档空间的本地表格文件,`contentType=DOCUMENT`)→ 必须走 `dws drive download` 下载到本地后再解析处理,严禁错误路由到 `sheet` 产品线(sheet 命令只支持在线表格,调用 xlsx 节点会直接报错) > - 用户想把在线表格导出为 xlsx 文件 → 开源 dws CLI 暂未暴露在线表格导出能力(旧动态 schema 曾包含 `submit_export_job` / `query_export_job`,但当前 cobra 未注册),需要在钉钉客户端手动导出 xlsx ### 示例 @@ -110,10 +110,10 @@ dws doc info --node "https://alidocs.dingtalk.com/i/nodes/abc123" --format json dws sheet list --node "https://alidocs.dingtalk.com/i/nodes/abc123" --format json # 返回 contentType≠ALIDOC, extension=xlsx/xls/csv → 本地表格文件,必须下载处理(禁止走 sheet) -dws doc download --node "https://alidocs.dingtalk.com/i/nodes/xlsx456" +dws drive download --node "https://alidocs.dingtalk.com/i/nodes/xlsx456" # 返回 contentType≠ALIDOC, nodeType=file → 普通文件,下载 -dws doc download --node "https://alidocs.dingtalk.com/i/nodes/def456" +dws drive download --node "https://alidocs.dingtalk.com/i/nodes/def456" # 返回 nodeType=folder → 文件夹,列出子节点 dws doc list --folder "https://alidocs.dingtalk.com/i/nodes/ghi789" --format json @@ -138,8 +138,8 @@ dws doc list --folder "https://alidocs.dingtalk.com/i/nodes/ghi789" --format jso | **adoc**(在线文档) | `doc read` | `doc update` / `doc block update` | ⚠️ `doc delete` | ⚠️ `doc export` (→ docx) | ⚠️ `doc permission *` | ⚠️ `doc media download/insert` | | **axls**(在线电子表格) | `sheet range read` / `sheet list` | `sheet range update` / `sheet append` | ⚠️ `doc delete`(节点删除) | `sheet export`(单命令一站式:提交→轮询→下载,可选 `--output` 落盘) | ⚠️ `doc permission *`(节点级,跨产品) | 不适用 | | **able**(在线多维表) | `aitable base get` / `aitable record query` | `aitable record create/update` | ⚠️ `doc delete`(节点删除)或 `aitable base delete --yes` | `aitable export data --scope all --format excel`(取 downloadUrl,`--output` 不落盘) | ⚠️ `doc permission *`(节点级) | `aitable attachment upload` | -| **xlsx / xls / xlsm / csv**(本地表格文件) | `doc download` → 本地用 xlsx skill 解析 | 不支持服务端写(先下载改本地再上传) | ⚠️ `doc delete`(节点删除) | 不需要(本身就是 xlsx) | ⚠️ `doc permission *` | 不适用 | -| **普通文件** (nodeType=file) | `doc download` | 不支持服务端写 | ⚠️ `doc delete` | 不需要 | ⚠️ `doc permission *` | 不适用 | +| **xlsx / xls / xlsm / csv**(本地表格文件) | `drive download` → 本地用 xlsx skill 解析 | 不支持服务端写(先下载改本地再上传) | ⚠️ `doc delete`(节点删除) | 不需要(本身就是 xlsx) | ⚠️ `doc permission *` | 不适用 | +| **普通文件** (nodeType=file) | `drive download` | 不支持服务端写 | ⚠️ `doc delete` | 不需要 | ⚠️ `doc permission *` | 不适用 | | **文件夹** (nodeType=folder) | `doc list --folder ` | `doc create --folder ...` | ⚠️ `doc delete` | 不适用 | ⚠️ `doc permission *` | 不适用 | | **分享短链** `/i/p/` | `read_url` 兜底(外部工具) | 不适用 | 不适用 | 不适用 | 不适用 | 不适用 | diff --git a/skills/mono/scripts/calendar_schedule_meeting.py b/skills/mono/scripts/calendar_schedule_meeting.py index 7de31a3c..6cac50a7 100644 --- a/skills/mono/scripts/calendar_schedule_meeting.py +++ b/skills/mono/scripts/calendar_schedule_meeting.py @@ -100,7 +100,7 @@ def main(): event_id = None if not args.dry_run and isinstance(result, dict): - # event create 真机返回 {result:{id,...}, success:true},id 在 result 内层, + # event create 返回 {result:{id,...}, success:true},id 在 result 内层, # 顶层取 id 恒 None 会导致参会人/订房支路被静默跳过。先解包 result 再取。 inner = result.get('result') if isinstance(result.get('result'), dict) else result event_id = (inner.get('eventId') or inner.get('id') diff --git a/skills/mono/scripts/contact_dept_members.py b/skills/mono/scripts/contact_dept_members.py index f0518de2..f7c70bf1 100644 --- a/skills/mono/scripts/contact_dept_members.py +++ b/skills/mono/scripts/contact_dept_members.py @@ -70,7 +70,7 @@ def main(): print('未找到匹配部门') sys.exit(1) - # dept search 真机返回顶层 deptList;兼容 result 包裹与历史 items/result 键。 + # dept search 返回顶层 deptList;兼容 result 包裹与历史 items/result 键。 if isinstance(dept_data, list): depts = dept_data else: @@ -104,7 +104,7 @@ def main(): print(' 无法获取成员列表') continue - # list-members 真机返回 deptUserList;兼容 result 包裹与历史 userlist 键。 + # list-members 返回 deptUserList;兼容 result 包裹与历史 userlist 键。 if isinstance(members_data, list): members = members_data else: @@ -121,7 +121,7 @@ def main(): continue for m in members: - # list-members 真机每项形如 {"userInfo": {"name":..., "userId":...}}, + # list-members 每项形如 {"userInfo": {"name":..., "userId":...}}, # 成员字段嵌在 userInfo 下;兼容历史扁平结构。 info = m.get('userInfo', m) name = info.get('name') or info.get('userName', '未知') diff --git a/skills/mono/scripts/minutes_extract_todos.py b/skills/mono/scripts/minutes_extract_todos.py index 7ca3d651..10123377 100644 --- a/skills/mono/scripts/minutes_extract_todos.py +++ b/skills/mono/scripts/minutes_extract_todos.py @@ -44,6 +44,58 @@ def run_dws( return None +def todos_from_payload(payload: Any) -> List[dict]: + """解析 `minutes get todos` 返回为待办列表。 + + 返回结构: result.dingtalkTodoList(对象数组, 含 title 等)与 + result.actions(JSON 字符串数组, 每条形如 + {"mark":[],"value":"..."}). 二者无 todos 键。 + 优先取 dingtalkTodoList; 为空时回退解析 actions。 + """ + if isinstance(payload, dict): + inner = payload.get('result', payload) + else: + inner = payload + out: List[dict] = [] + if isinstance(inner, dict): + ding_list = inner.get('dingtalkTodoList') + if isinstance(ding_list, list) and ding_list: + for t in ding_list: + if isinstance(t, dict): + content = t.get('title') or t.get('content') or '' + if content: + out.append({'content': str(content), '_raw': t}) + if out: + return out + actions = inner.get('actions') + if isinstance(actions, list): + for a in actions: + content = '' + if isinstance(a, str): + text = a.strip() + if text.startswith('{'): + try: + parsed = json.loads(text) + content = parsed.get('value') or '' + except json.JSONDecodeError: + content = text + else: + content = text + elif isinstance(a, dict): + content = (a.get('value') or a.get('content') + or a.get('title') or '') + if content: + out.append({'content': str(content)}) + elif isinstance(inner, list): + for t in inner: + if isinstance(t, dict): + content = (t.get('content') or t.get('text') + or t.get('title') or t.get('value') or '') + if content: + out.append({'content': str(content), '_raw': t}) + return out + + def main(): parser = argparse.ArgumentParser( description='从听记中提取待办事项' @@ -82,21 +134,9 @@ def main(): ]) if not todos_data: continue - if isinstance(todos_data, list): - items = todos_data - elif isinstance(todos_data, dict): - inner = todos_data.get('result', todos_data) - if isinstance(inner, dict): - items = inner.get('todos', []) - elif isinstance(inner, list): - items = inner - else: - items = [] - else: - items = [] + items = todos_from_payload(todos_data) for t in items: - if isinstance(t, dict): - t['_source'] = title + t['_source'] = title all_todos.extend(items) print(f"\n📋 听记待办汇总") diff --git a/skills/multi/dingtalk-aisearch/references/aisearch.md b/skills/multi/dingtalk-aisearch/references/aisearch.md index 8a21aa13..f7f33544 100644 --- a/skills/multi/dingtalk-aisearch/references/aisearch.md +++ b/skills/multi/dingtalk-aisearch/references/aisearch.md @@ -98,7 +98,7 @@ Flags: | 操作 | 从返回中提取 | 用于 | |------|-------------|------| -| `aisearch person` | `userId`(用户ID)、`title`(姓名) | 展示搜索结果、后续操作(发消息/建待办等) | +| `aisearch person` | `userId`(用户ID,= `meta.staffId`/`meta.jobNumber`)、`author`(真实姓名,= `meta.name`);`title` 是花名/显示名,不一定等于姓名 | 展示搜索结果、后续操作(发消息/建待办等) | ## 重名消歧 diff --git a/skills/multi/dingtalk-aitable/references/aitable.md b/skills/multi/dingtalk-aitable/references/aitable.md index ac8859ba..904c3837 100644 --- a/skills/multi/dingtalk-aitable/references/aitable.md +++ b/skills/multi/dingtalk-aitable/references/aitable.md @@ -101,6 +101,17 @@ > **创建表单**有两种等价方式:`form create --name "..."`(推荐)或 `view create --view-type FormDesigner --name "..."`。 +### workflow (自动化工作流) → 详见 [aitable-workflow.md](./aitable/aitable-workflow.md) + +| 命令 | 用途 | 必填参数 | 路由提醒 | +|------|------|----------|----------| +| `workflow list` | 列出 Base 下所有工作流 | `--base-id` | 支持 `--limit [1,100]` / `--offset >=0`;list 出参字段叫 `flowId` | +| `workflow get` | 获取单个工作流详情(含 flowSchema) | `--base-id` `--workflow-id` | `--workflow-id` 接受 list 里的 `flowId`(同值) | +| `workflow enable` | 启用工作流 | `--base-id` `--workflow-id` | 返回 `{enabled: true}` 是动作确认;要确认真启用看 list 的 `status` | +| `workflow disable` | 禁用工作流(高危) | `--base-id` `--workflow-id` `--yes` | 影响业务自动化,建议二次确认;status 变 STOP | + +> **当前不支持通过 CLI 新建/修改/删除工作流**,请去 AI 表格 Web 端(数据表页面 → 自动化)配置。 + ### dashboard & chart → 详见 [aitable-dashboard-chart.md](./aitable/aitable-dashboard-chart.md) | 命令 | 用途 | @@ -130,6 +141,35 @@ |------|------|----------| | `template search` | 搜索模板 | `--query` | +### advperm (高级权限/自定义角色) → 详见 [aitable-advperm.md](./aitable/aitable-advperm.md) + +| 命令 | 用途 | 必填参数 | 路由提醒 | +|------|------|----------|----------| +| `advperm enable` | 开启 Base 高级权限总开关 | `--base-id` | 不开启时角色规则不生效 | +| `advperm disable` | 关闭 Base 高级权限总开关(高危) | `--base-id` `--yes` | 关闭后全员回退默认权限 | +| `advperm role-list` | 列出 Base 下所有角色 | `--base-id` | 同时返回自定义角色和系统角色;`roleType == "custom"` 是自定义,前缀 `system_` 是系统角色 | +| `advperm role-get` | 获取单角色完整配置 | `--base-id` `--role-id` | 含 subRoles 与字段/行级规则 | +| `advperm role-create` | 创建自定义角色 | `--base-id` `--name` | 可选 `--sub-roles` 同时指定子角色权限规则 | +| `advperm role-update` | 增量更新自定义角色(PATCH) | `--base-id` `--role-id` | 未传字段不变;`--sub-roles` 按 (targetId,targetType) 合并 | +| `advperm role-delete` | 删除自定义角色 | `--base-id` `--role-id` `--yes` | 不可逆;系统角色禁删;**调用者必须是该 AI 表格的管理员/Owner**,非管理员会得到 401 AUTH_ERROR | + +> 所有写命令(enable/disable/role-create/role-update/role-delete)需要 Base 管理员权限;非管理员只能调 `role-list` / `role-get`(只读)。 +> "角色 ↔ 成员"绑定当前 CLI 不支持,仍需在 AI 表格 Web 端 → Base 设置 → 高级权限面板手动完成。 + +### section (文件夹与节点管理) + +用于在 Base 的导航树中组织 table / dashboard / 表单视图 / 文档等节点(类似文件夹)。操作前建议先用 `section list-nodes` 拿到 nodeId / sectionId 与父级关系。 + +| 命令 | 用途 | 必填参数 | 路由提醒 | +|------|------|----------|----------| +| `section list-nodes` | 列出 Base 下全部节点 | `--base-id` | 返回 `{nodeId, nodeType, parentSectionId, name?}`;是 move-node / reorder 的前置定位命令 | +| `section list-empty` | 列出空文件夹 | `--base-id` | 返回 `{sectionId, name, parentSectionId}`,用于清理导航树 | +| `section create` | 创建文件夹 | `--base-id` `--name` | `--parent-section-id` 不传/空串=根目录;`--index` 指定位置 | +| `section rename` | 重命名文件夹 | `--base-id` `--section-id` `--new-name` | — | +| `section delete` | 删除文件夹 | `--base-id` `--section-id` | 不可逆;建议先 list-empty 确认为空 | +| `section reorder` | 调整文件夹顺序 | `--base-id` `--section-id` `--target-index` | 仅当前父级下调序;跨父级用 move-node | +| `section move-node` | 移动节点(跨父级) | `--base-id` `--node-id` `--new-parent-section-id` | `--new-parent-section-id ""`=移到根;`--target-index` 调全局位置 | + ## 意图判断 用户说"表格/多维表/AI表格": diff --git a/skills/multi/dingtalk-aitable/references/aitable/aitable-dashboard-chart.md b/skills/multi/dingtalk-aitable/references/aitable/aitable-dashboard-chart.md index f904158a..c6e4a1e7 100644 --- a/skills/multi/dingtalk-aitable/references/aitable/aitable-dashboard-chart.md +++ b/skills/multi/dingtalk-aitable/references/aitable/aitable-dashboard-chart.md @@ -31,7 +31,7 @@ dws aitable chart get --base-id --dashboard-id --chart- | `dashboard delete` | 删除仪表盘 | `--base-id` `--dashboard-id` `--yes` | — | | `dashboard config-example` | 查看仪表盘配置模板 | 无 | 创建前先调此命令了解 config 结构 | | `dashboard share get` | 获取仪表盘分享配置 | `--base-id` `--dashboard-id` | 可能 404,见上方要点 | -| `dashboard share update` | 更新仪表盘分享配置 | `--base-id` `--dashboard-id` `--enabled` | `--enabled true` 开启(配 `--share-type PUBLIC/ORG`)/ `--enabled false` 关闭;ORG 回显 `shareType="[1]"` | +| `dashboard share update` | 更新仪表盘分享配置 | `--base-id` `--dashboard-id` `--enabled` | `--enabled true` 开启,`--share-type` 用 `ORG`(ORG 回显 `shareType="[1]"`);部分组织禁用了 `PUBLIC` 公开分享;若报 `Illegal argument`,改用 `ORG`(组织内分享)。`--enabled false` 关闭 | ## chart 子命令 @@ -43,7 +43,7 @@ dws aitable chart get --base-id --dashboard-id --chart- | `chart delete` | 删除图表 | `--base-id` `--dashboard-id` `--chart-id` `--yes` | | `chart widgets-example` | 查看图表 widgets 配置模板 | 无 | | `chart share get` | 获取图表分享配置 | `--base-id` `--dashboard-id` `--chart-id` | -| `chart share update` | 更新图表分享配置(`--enabled true/false`,开启配 `--share-type PUBLIC/ORG`) | `--base-id` `--dashboard-id` `--chart-id` `--enabled` | +| `chart share update` | 更新图表分享配置(`--enabled true/false`,开启配 `--share-type`,用 `ORG`;部分组织禁用了 `PUBLIC` 公开分享;报 `Illegal argument` 时改用 `ORG`) | `--base-id` `--dashboard-id` `--chart-id` `--enabled` | > `chart create` 的 `--layout` 是**必填**(12 列网格布局,如 `{"x":0,"y":0,"w":6,"h":4}`);不传本地校验直接拒。`chart update` 的 `--config` 也**必填**——即便只想改 layout,也要带完整 config,否则服务端拒绝。 diff --git a/skills/multi/dingtalk-attendance/SKILL.md b/skills/multi/dingtalk-attendance/SKILL.md index ec9545a1..6c461421 100644 --- a/skills/multi/dingtalk-attendance/SKILL.md +++ b/skills/multi/dingtalk-attendance/SKILL.md @@ -39,7 +39,7 @@ metadata: ## 评测高频硬约束 -- 当前 dws 已注册全部考勤子命令组(`record` / `check` / `approve` / `shift` / `schedule` / `class` / `adjustment` / `overtime` / `group` / `summary` / `rules` / `selfsetting` / `globalsetting` / `vacation` / `checkin` / `report` / `boss-check`),查询与写操作大多可直接调用后端。**不要再以"开源版只读/不支持写操作"为由拒答。** 创建班次、导入排班、加人入考勤组、保存个人规则设置、设置假期余额等写操作真机可执行,但必须先展示参数摘要并二次确认,再追加 `--yes`(或 `--user-say-yes`)执行;不要在未确认时直接写、也不要伪装成功。个别命令受权限/数据影响返回空或权限错误(如 `report` 系列仅管理员),如实说明即可。 +- 当前 dws 已注册全部考勤子命令组(`record` / `check` / `approve` / `shift` / `schedule` / `class` / `adjustment` / `overtime` / `group` / `summary` / `rules` / `selfsetting` / `globalsetting` / `vacation` / `checkin` / `report` / `boss-check`),查询与写操作大多可直接调用后端。**不要再以"开源版只读/不支持写操作"为由拒答。** 创建班次、导入排班、加人入考勤组、保存个人规则设置、设置假期余额等写操作可执行,但必须先展示参数摘要并二次确认,再追加 `--yes`(或 `--user-say-yes`)执行;不要在未确认时直接写、也不要伪装成功。个别命令受权限/数据影响返回空或权限错误(如 `report` 系列仅管理员),如实说明即可。 - 查询迟到/缺勤名单时,空打卡结果不等于"没人迟到"。必须结合排班、`NotSigned`、`Absenteeism`、无记录人员分别说明;数据缺失要标为"无记录/无法判断",不要归为正常。 - 做部门 Top N 排名时,用户要求前 N 名就必须输出 N 个部门;无打卡记录或无可计算数据的部门按 0 或"无数据"保留在排名中,不能只输出有数据的少数部门。 - `summary` 必须同时传 `--user`、`--date`、`--stats-type`(week/month),缺一返回 C0002。 diff --git a/skills/multi/dingtalk-attendance/references/attendance.md b/skills/multi/dingtalk-attendance/references/attendance.md index 12ac86ab..42e33ba9 100644 --- a/skills/multi/dingtalk-attendance/references/attendance.md +++ b/skills/multi/dingtalk-attendance/references/attendance.md @@ -55,6 +55,19 @@ Flags: 返回每条记录含:用户 ID、实际打卡时间、打卡地址、打卡经纬度、打卡类型(OnDuty/OffDuty)、定位方式(Map/Wifi/etc)。时间跨度不超过 1 个月。 +### 查询个人某日考勤详情 +``` +Usage: + dws attendance record get [flags] +Example: + dws attendance record get --user 011769261608 --date 2026-03-08 +Flags: + --user string 钉钉用户 ID (必填,别名 --users 亦可) + --date string 查询日期, 格式 YYYY-MM-DD (必填) +``` + +查询单个用户在**某一天**的考勤详情(区别于 `check record`/`check result` 的多人时间段批量查询)。返回 `result` 对象含 `isHasSchedule`(当日是否有排班)、`isRest`(是否休息日)、`isUnSigned`(是否未打卡)、`recordList`(打卡明细)、`approveList`(当日审批单)、`workOvertime`、`workTimeDesc` 等字段。`--user` 只接受**单个** userId;查 userId 用 `dws contact user search --query "姓名"`。 + ### 查询审批单 ``` Usage: @@ -1115,13 +1128,13 @@ dws attendance report query-leave --users userId1,userId2 \ dws attendance vacation types --format json # 查看指定员工假期余额 -dws attendance vacation balance --users userId1,userId2 --format json +dws attendance vacation balance --users userId1,userId2 --leave-code <假期类型code> --format json # 查看指定员工某类假期余额 dws attendance vacation balance --users userId1 --leave-code a1b2c3d4-e5f6-7890-abcd-ef1234567890 --format json # 查看指定员工假期余额变更记录 -dws attendance vacation records --user USER_ID --start 2026-04-01 --end 2026-04-22 --format json +dws attendance vacation records --user USER_ID --leave-code <假期类型code> --start 2026-04-01 --end 2026-04-22 --format json # 更新假期规则名称 dws attendance vacation update-type --leave-code a1b2c3d4-e5f6-7890-abcd-ef1234567890 \ diff --git a/skills/multi/dingtalk-calendar/references/calendar.md b/skills/multi/dingtalk-calendar/references/calendar.md index 6c7fe58e..d879a649 100644 --- a/skills/multi/dingtalk-calendar/references/calendar.md +++ b/skills/multi/dingtalk-calendar/references/calendar.md @@ -5,7 +5,7 @@ - **二级子命令(必选其一)**:`event`(日程)、`attendee`(参会人)、`room`(会议室)、`busy`(闲忙)、`attachment`(日程附件)、`book`(用户日历列表)。`dws calendar` 后**必须**紧跟上述之一;**禁止**只执行 `dws calendar`(无子命令)。 - **个人日程 / 给自己留时间块 / 专注时段**:统一走 **`dws calendar event create`**。当前**没有**单独的 `personal schedule create` / `calendar create` 命令。 - **查日程列表**:`dws calendar event list --start "" --end "" --format json`,或优先使用脚本 `python scripts/calendar_today_agenda.py [today|tomorrow|week]`(见文末「自动化脚本」)。 -- **查用户日历本列表**:`dws calendar book list`(返回主日历 `id == "primary"` 等)。**重要**可以查询他人共享给自己的日历本,根据日历本id可以进一步查询对方的日程信息。 +- **查用户日历本列表**:`dws calendar book list`(返回字段为 `calendarId`,主日历 `calendarId == "primary"` 等;注意无 `id` 字段,按 `.id` 提取会取空)。**重要**可以查询他人共享给自己的日历本,根据日历本id可以进一步查询对方的日程信息。 - **CLI 不存在**独立的 `dws calendar list`;若误跑无子命令的 `dws calendar`,会打印整段 Usage,**切勿**将该段 help 当作工具结果再次塞进对话(会急剧增加 token 与首字延迟)。 - **必须**遵循指令说明进行调用。**绝对禁止**使用虚构指令,使用虚构参数。 @@ -511,7 +511,7 @@ dws calendar event list --start "2026-03-10T14:00:00+08:00" --end "2026-03-10T15 | `event respond` | 响应结果 | — | | `room search` | `rooms[].roomId` | room add 的 --rooms 或 event create 的 --rooms | | `room list-groups` | `groups[].groupId` | room search 的 --group-id | -| `book list` | `id`(如 `primary`) | event list/get 的 --calendar-id | +| `book list` | `calendarId`(如 `primary`) | event list/get 的 --calendar-id | | 钉盘上传 | 文件 `fileId` | attachment add 的 --files `:` | ## 注意事项 @@ -529,7 +529,7 @@ dws calendar event list --start "2026-03-10T14:00:00+08:00" --end "2026-03-10T15 - **搜房无结果**:在符合早停/用户限定范围内,`room search`(含按分组逐组查)全部返回空或无空闲 → 应**直接向用户报错/说明失败**并结束订房;**禁止**假设 roomId、禁止无合法 `roomId` 时调用 `room add` / `event create --rooms` 试探、禁止用 `event get` 等绕路推断 roomId - **评测 / 自动化断言**:凡涉及 `room add` / `event create --rooms` 的流程,`--rooms` 只能填上游 `room search`(或等价接口)返回 JSON 中的 **`rooms[].roomId`**;不得以会议室展示名、楼层文案或用户口语当作 `roomId` - **附件**:`attachment add` 仅负责挂载,**不上传**文件;fileId 必须先通过钉盘流程取得;`--files` 多附件用 `:` 元素逗号分隔 -- **日历本**:`book list` 返回的 `id` 才是合法 `calendarId`;如无明确说明,`event list` / `event get` 都不要带 `--calendar-id`,让接口默认走 primary 主日历 +- **日历本**:`book list` 返回的 `calendarId` 字段才是合法日历 id(无 `id` 字段);如无明确说明,`event list` / `event get` 都不要带 `--calendar-id`,让接口默认走 primary 主日历 - **已弃用入参**:`--max-results`(event list)在新 MCP schema 中被移除;CLI 仍接受但**不会**透传到 MCP;模型生成命令时**禁止**继续使用 ## 自动化脚本 diff --git a/skills/multi/dingtalk-calendar/scripts/calendar_free_slot_finder.py b/skills/multi/dingtalk-calendar/scripts/calendar_free_slot_finder.py index 01c9f637..b4bf20ff 100644 --- a/skills/multi/dingtalk-calendar/scripts/calendar_free_slot_finder.py +++ b/skills/multi/dingtalk-calendar/scripts/calendar_free_slot_finder.py @@ -51,30 +51,51 @@ def fmt_iso(dt: datetime) -> str: return dt.strftime('%Y-%m-%dT%H:%M:%S+08:00') +def _extract_time_str(value: Any) -> str: + """时间字段兼容字符串与 {dateTime, date} 对象两种形态""" + if isinstance(value, dict): + return value.get('dateTime') or value.get('date') or '' + return value or '' + + def parse_busy_intervals( data: Any, ) -> List[Tuple[datetime, datetime]]: intervals = [] if not data: return intervals - items = [] + # 兼容 {result: [...]} 包裹结构 + if isinstance(data, dict) and 'result' in data: + data = data['result'] + rows = [] if isinstance(data, list): - items = data + rows = data elif isinstance(data, dict): - inner = data.get('result', data) - if isinstance(inner, list): - items = inner - elif isinstance(inner, dict): - for user_data in inner.values(): - if isinstance(user_data, list): - items.extend(user_data) - elif isinstance(user_data, dict): - items.extend( - user_data.get('busyTimes', []) - ) + rows = list(data.values()) + items = [] + for row in rows: + if isinstance(row, list): + items.extend(row) + elif isinstance(row, dict): + # 新结构: {userId, scheduleItems: [{start, end, status}]} + if isinstance(row.get('scheduleItems'), list): + items.extend(row['scheduleItems']) + elif isinstance(row.get('busyTimes'), list): + items.extend(row['busyTimes']) + elif row.get('startTime') or row.get('start'): + items.append(row) for item in items: - start_str = item.get('startTime') or item.get('start', '') - end_str = item.get('endTime') or item.get('end', '') + if not isinstance(item, dict): + continue + # FREE 时段不算忙 + if str(item.get('status') or '').upper() == 'FREE': + continue + start_str = _extract_time_str( + item.get('startTime') or item.get('start') + ) + end_str = _extract_time_str( + item.get('endTime') or item.get('end') + ) if not start_str or not end_str: continue for fmt in ( @@ -172,6 +193,16 @@ def main(): if args.dry_run: return + # 查询失败必须中止, 否则会把"查不到"当成"全天空闲"给出危险结论 + if data is None: + print('错误:忙闲查询失败,无法给出空闲时段结论', file=sys.stderr) + sys.exit(2) + if isinstance(data, dict) and data.get('success') is False: + print(f"错误:忙闲查询失败: " + f"{data.get('errorMsg') or data.get('errorCode') or data}", + file=sys.stderr) + sys.exit(2) + busy = parse_busy_intervals(data) free = find_free_slots(day_start, day_end, busy, args.duration) @@ -181,6 +212,7 @@ def main(): print(f" 会议时长: {args.duration} 分钟") print(f" 工作时间: {args.start_hour}:00 ~ " f"{args.end_hour}:00") + print(f" 已识别忙时段: {len(busy)} 个") print('=' * 50) if not free: diff --git a/skills/multi/dingtalk-chat/references/chat.md b/skills/multi/dingtalk-chat/references/chat.md index 7a56247a..2595b4e8 100644 --- a/skills/multi/dingtalk-chat/references/chat.md +++ b/skills/multi/dingtalk-chat/references/chat.md @@ -336,7 +336,7 @@ Flags: #### 移除用户的指定群身份 — 从用户身上移除指定的群身份(不影响其他群身份) -> ⚠️ 当前不可用:真机服务端对该命令恒返回 1002(系统繁忙),历史回归至今未修,属服务端问题。需要清除某人身份时,用 `chat group-role set-user --role-ids ""`(传空 role-ids 覆盖为无身份)兜底;但 set-user 会清掉该用户的**全部**身份,无法只移除其中一个。 +> ⚠️ 当前不可用:该命令当前调用常失败(服务端返回 1002 系统繁忙),多为服务端侧限制。需要清除某人身份时,用 `chat group-role set-user --role-ids ""`(传空 role-ids 覆盖为无身份)兜底;但 set-user 会清掉该用户的**全部**身份,无法只移除其中一个。 ``` Usage: dws chat group-role remove-user [flags] @@ -694,7 +694,7 @@ Example: dws chat message list-by-sender --sender-open-dingtalk-id --start "2026-03-10T00:00:00+08:00" --end "2026-03-11T00:00:00+08:00" --limit 50 --cursor Flags: --sender-user-id string 发送者 userId(与 --sender-open-dingtalk-id 二选一) - --sender-open-dingtalk-id string 发送者 openDingTalkId(与 --sender-user-id 二选一,适用于无法获取 userId 的场景) + --sender-open-dingtalk-id string 发送者 openDingTalkId(与 --sender-user-id 二选一);该路径当前返回 result:{}、success:null(疑似服务端问题),优先用 --sender-user-id --start string 开始时间,ISO-8601 格式 (必填) --end string 结束时间,ISO-8601 格式 (必填) --limit int 每页返回数量(默认 50) @@ -702,8 +702,8 @@ Flags: 注意: - --sender-user-id 和 --sender-open-dingtalk-id 二者互斥,必须且只能指定其一: - - --sender-user-id 传 userId(企业内部应用常用) - - --sender-open-dingtalk-id 传 openDingTalkId(三方应用或跨组织场景常用,无法获取 userId 时使用) + - --sender-user-id 传 userId(企业内部应用常用,当前唯一可用路径) + - --sender-open-dingtalk-id 传 openDingTalkId(当前返回 result:{}、success:null(疑似服务端问题);即使只有 openDingTalkId,也建议先用 contact 换取 userId 后走 --sender-user-id) - openDingTalkId 获取方式见下方「openDingTalkId 获取方式」小节 - 不需要指定单聊/群聊,返回结果自带会话类型标识 - 时间支持多种 ISO-8601 格式,如 "2026-03-10T00:00:00+08:00"、"2026-03-10 14:00:00"、"2026-03-10" 等 @@ -990,20 +990,20 @@ Flags: Usage: dws chat message download-media [flags] Example: - dws chat message download-media --type mediaId --resource-id --message-id --open-conversation-id --output ./downloads/ dws chat message download-media --type mediaId --resource-id --message-id --open-conversation-id --output ./photo.jpg + dws chat message download-media --type mediaId --resource-id --message-id --open-conversation-id --output ./downloads/photo.jpg Flags: --type string 资源类型: mediaId (必填) --resource-id string 资源 ID,mediaId 类型时为消息中的 mediaId 值 (必填) --message-id string 消息 openMessageId (必填) --open-conversation-id string 会话 openConversationId (必填) - --output string 本地保存路径,文件或目录 (必填) + --output string 本地保存路径 (必填),建议传完整文件路径(含文件名) 注意: - resource-id 从 `dws chat message list` 返回的消息内容中获取 mediaId - message-id 从 `dws chat message list` 返回的 openMessageId - open-conversation-id 从 `dws chat search` 获取 openConversationId - - --output 如果指定目录,文件名会从下载 URL 中自动推断 + - --output 建议传完整文件路径(含扩展名,如 ./photo.jpg);只传目录(如 ./downloads/)在旧版本会下载失败,请显式带上文件名 ``` ### search-common (搜索共同群) @@ -1387,7 +1387,7 @@ Flags: #### 关闭/开启 @所有人消息提醒 — 关闭或开启会话中 @所有人的消息通知 -> ⚠️ 当前不可用:真机服务端对该命令恒返回 1002(系统繁忙),历史回归至今未修,属服务端问题。命令本身参数合法,但调用不会生效。 +> ⚠️ 当前不可用:该命令当前调用常失败(服务端返回 1002 系统繁忙),多为服务端侧限制。命令本身参数合法,但调用不会生效。 ``` Usage: dws chat mute-at-all [flags] @@ -1405,7 +1405,7 @@ Flags: #### 关闭/开启红包消息提醒 — 关闭或开启会话中的红包消息通知 -> ⚠️ 当前不可用:真机服务端对该命令恒返回 1002(系统繁忙),历史回归至今未修,属服务端问题。命令本身参数合法,但调用不会生效。 +> ⚠️ 当前不可用:该命令当前调用常失败(服务端返回 1002 系统繁忙),多为服务端侧限制。命令本身参数合法,但调用不会生效。 ``` Usage: dws chat mute-red-envelope [flags] @@ -1487,7 +1487,7 @@ Flags: 注意: - 返回结果包含单聊和群聊,不区分会话类型 - --limit 范围 1-100,默认 100,上限 100;传入 >100 会报错拒绝,不会静默截断 - - 分页当前不可用:真机 hasMore 恒为 false、nextCursor 恒为 null,本命令最多返回 100 条会话,无法用 --cursor 继续翻页取更多 + - 分页当前不可用:当前 hasMore 恒为 false、nextCursor 恒为 null,本命令最多返回 100 条会话,无法用 --cursor 继续翻页取更多 - 与 list-top-conversations 的区别: 本命令返回全部会话(单聊+群聊),list-top-conversations 仅返回置顶会话 ``` @@ -1574,7 +1574,7 @@ Flags: #### 审批入群验证 — 通过、删除单个审核 -真机当前仅 AuditApprove(通过)和 AuditDelete(删除)两个动作可用。 +当前仅 AuditApprove(通过)和 AuditDelete(删除)两个动作可用。 ``` Usage: dws chat group audit-join-validation [flags] @@ -1591,7 +1591,7 @@ Flags: --description string 审批说明(可选) 注意: - - status 真机仅支持 AuditApprove(通过) 和 AuditDelete(删除);AuditIgnore(忽略)、AuditRefuse(拒绝)、AuditBlock(拒绝且拉黑) 会被服务端拒绝报 unsupported audit status,属服务端限制 + - status 仅支持 AuditApprove(通过) 和 AuditDelete(删除);AuditIgnore(忽略)、AuditRefuse(拒绝)、AuditBlock(拒绝且拉黑) 会被服务端拒绝报 unsupported audit status,属服务端限制 - record-id、applicant、inviter 可通过 dws chat group list-join-validations 查询获得 ``` diff --git a/skills/multi/dingtalk-chat/scripts/chat_export_messages.py b/skills/multi/dingtalk-chat/scripts/chat_export_messages.py index 54324607..2b77d85e 100644 --- a/skills/multi/dingtalk-chat/scripts/chat_export_messages.py +++ b/skills/multi/dingtalk-chat/scripts/chat_export_messages.py @@ -135,12 +135,21 @@ def main(): if not data: break + # 兼容两种结构: 顶层 messages / {result: {messages, hasMore}} if isinstance(data, list): page_msgs = data has_more = False else: - page_msgs = data.get('messages', data.get('result', [])) - has_more = data.get('hasMore', False) + container = data + inner = data.get('result') + if isinstance(inner, dict): + container = inner + page_msgs = container.get('messages') + if page_msgs is None and isinstance(inner, list): + page_msgs = inner + if not isinstance(page_msgs, list): + page_msgs = [] + has_more = bool(container.get('hasMore', False)) if not page_msgs: break @@ -153,7 +162,9 @@ def main(): break last_msg = page_msgs[-1] - boundary_time = last_msg.get('createAt') or last_msg.get('time', '') + boundary_time = (last_msg.get('createTime') + or last_msg.get('createAt') + or last_msg.get('time', '')) if not boundary_time or boundary_time == current_time: break current_time = boundary_time @@ -163,28 +174,18 @@ def main(): print('未拉取到消息') return - if isinstance(data, list): - messages = data - elif isinstance(data, dict): - inner = data.get('result', data) - if isinstance(inner, dict): - messages = inner.get('messages', inner.get('records', [])) - elif isinstance(inner, list): - messages = inner - else: - messages = [] - else: - messages = [] - if args.output: with open(args.output, 'w', encoding='utf-8') as f: json.dump(all_messages, f, ensure_ascii=False, indent=2) print(f" ✓ 已导出 {len(all_messages)} 条消息到 {args.output}") else: for m in all_messages: - sender = m.get('senderNick') or m.get('sender', '未知') - text = m.get('text') or m.get('content', '') - time_str = m.get('createAt') or m.get('time', '') + # 实际字段: sender/createTime/content (兼容旧字段名) + sender = (m.get('sender') or m.get('senderNick') + or m.get('senderOpenDingTalkId', '未知')) + text = m.get('content') or m.get('text', '') + time_str = (m.get('createTime') or m.get('createAt') + or m.get('time', '')) print(f" [{time_str}] {sender}: {text[:80]}") print(f"\n合计: {len(all_messages)} 条消息 ({page} 页)") diff --git a/skills/multi/dingtalk-chat/scripts/chat_history_with_user.py b/skills/multi/dingtalk-chat/scripts/chat_history_with_user.py index 8068be38..b007c7be 100644 --- a/skills/multi/dingtalk-chat/scripts/chat_history_with_user.py +++ b/skills/multi/dingtalk-chat/scripts/chat_history_with_user.py @@ -135,12 +135,21 @@ def main(): if not data: break + # 兼容两种结构: 顶层 messages / {result: {messages, hasMore}} if isinstance(data, list): page_msgs = data has_more = False else: - page_msgs = data.get('messages', data.get('result', [])) - has_more = data.get('hasMore', False) + container = data + inner = data.get('result') + if isinstance(inner, dict): + container = inner + page_msgs = container.get('messages') + if page_msgs is None and isinstance(inner, list): + page_msgs = inner + if not isinstance(page_msgs, list): + page_msgs = [] + has_more = bool(container.get('hasMore', False)) if not page_msgs: break @@ -153,7 +162,9 @@ def main(): break last_msg = page_msgs[-1] - boundary_time = last_msg.get('createAt') or last_msg.get('time', '') + boundary_time = (last_msg.get('createTime') + or last_msg.get('createAt') + or last_msg.get('time', '')) if not boundary_time or boundary_time == current_time: break current_time = boundary_time @@ -164,28 +175,18 @@ def main(): return # 3. 输出结果 - if isinstance(data, list): - messages = data - elif isinstance(data, dict): - inner = data.get('result', data) - if isinstance(inner, dict): - messages = inner.get('messages', inner.get('records', [])) - elif isinstance(inner, list): - messages = inner - else: - messages = [] - else: - messages = [] - if args.output: with open(args.output, 'w', encoding='utf-8') as f: json.dump(all_messages, f, ensure_ascii=False, indent=2) print(f" ✓ 已导出 {len(all_messages)} 条消息到 {args.output}") else: for m in all_messages: - sender = m.get('senderNick') or m.get('sender', '未知') - text = m.get('text') or m.get('content', '') - time_str = m.get('createAt') or m.get('time', '') + # 实际字段: sender/createTime/content (兼容旧字段名) + sender = (m.get('sender') or m.get('senderNick') + or m.get('senderOpenDingTalkId', '未知')) + text = m.get('content') or m.get('text', '') + time_str = (m.get('createTime') or m.get('createAt') + or m.get('time', '')) print(f" [{time_str}] {sender}: {text[:80]}") print(f"\n合计: {len(all_messages)} 条消息 ({page} 页)") diff --git a/skills/multi/dingtalk-contact/SKILL.md b/skills/multi/dingtalk-contact/SKILL.md index b16ab60d..2008668d 100644 --- a/skills/multi/dingtalk-contact/SKILL.md +++ b/skills/multi/dingtalk-contact/SKILL.md @@ -31,7 +31,7 @@ metadata: | "按 userId 查详情" | `dws contact user get --ids ,,...`(多个并行) | | "按部门名拉成员" | `python scripts/contact_dept_members.py --query "<部门名>"` | | "搜部门" | `dws contact dept search --query "<关键词>"` | -| "部门成员列表" | `dws contact dept list-members --ids ` | +| "部门成员列表" | `dws contact dept list-members --depts ` | | "离职员工/离职名单/已离职" | `dws contact user dismission search`(可加 `--name` / `--start` + `--end` / `--depts`) | | "花名册/员工档案/学历/银行卡/合同" | `dws contact user profile get --staff-id `(先 `profile fields` 查字段) | diff --git a/skills/multi/dingtalk-contact/references/08-directory.md b/skills/multi/dingtalk-contact/references/08-directory.md index dc96bcf2..824cd967 100644 --- a/skills/multi/dingtalk-contact/references/08-directory.md +++ b/skills/multi/dingtalk-contact/references/08-directory.md @@ -6,7 +6,7 @@ ## 专用规则(#8 非 lite 步骤必守) - **角色类查人优先 label**:用户说"角色为XX的员工/XX角色的员工/XX角色的人员""所有主管/主管理员/财务/HR/总经理"等角色类型人员时,**优先** `contact label list` 获取全部角色 → 匹配目标角色 → `contact label list-members --id `;若用户明确指定了角色名称(如"角色为总经理"),则先用 `contact label get --names ` 精确匹配,**若精确匹配无结果,降级 `label list` 模糊匹配**(如用户说"管理员"可匹配到"主管理员"和"子管理员")。 -- **脚本优先**:按部门拉成员**优先** `python scripts/contact_dept_members.py --query "<部门名>"`(`--dry-run` / `--format json`);失败再 `dept search` → `dept list-members --ids`。 +- **脚本优先**:按部门拉成员**优先** `python scripts/contact_dept_members.py --query "<部门名>"`(`--dry-run` / `--format json`);失败再 `dept search` → `dept list-members --depts`。 - **详情链路**:用户要子部门、职位、联系方式、汇报关系等,在 `user search` 之后**必须**再 `contact user get --ids `;禁止仅用 search 的浅表字段交差。 - **`user get` 后部门仍空**:不得过早结束或只建议用户去 App;须在 CLI 能力内尝试 **用户点名的部门** `dept search` + `dept list-members` 等与 `userId` 交叉核对,再结构化汇总「返回中有哪些字段 / 哪些为空及可能原因」。 - **多命中**:`user search` 或 `dept search` 多条时须列候选(姓名、title、部门线索)请用户确认,禁止默认猜一人。具体消歧流程: @@ -35,16 +35,16 @@ | `search-user-by-mobile` | 1. `contact user search-mobile --mobile "<手机号>"` → 按需 `contact user get --ids ` | | `lookup-dept-id` | 1. `contact dept search --query "<部门关键词>"` → 回显 `deptId`(多命中须消歧) | | `list-subdepts` | 1. 已有父 `deptId` → `contact dept list-children --id <父deptId>` 直接取直属子部门列表
2. 只有部门名 → 先 `lookup-dept-id` 取 `deptId`,再 `list-children` | -| `list-dept-members` | 1. **优先** `python scripts/contact_dept_members.py --query "<部门名>"`
2. 备选:`lookup-dept-id` → `contact dept list-members --ids `
3. 若要每人档案字段:对 `userId` 批量 `contact user get --ids …` | -| `list-multi-dept-members` | 1. 对每个部门名 `contact dept search --query "<名>"` → 各 `deptId`
2. `contact dept list-members --ids ,,...`(多部门并行/批量见 conventions)
3. 需要档案再 `contact user get --ids …` | -| `verify-user-dept` | 1. `contact dept search --query "<部门名>"` → `deptId`
2. `contact dept list-members --ids ` 中匹配姓名;或先 `search-user` lite 再 `user get` 核对部门字段 | +| `list-dept-members` | 1. **优先** `python scripts/contact_dept_members.py --query "<部门名>"`
2. 备选:`lookup-dept-id` → `contact dept list-members --depts `
3. 若要每人档案字段:对 `userId` 批量 `contact user get --ids …` | +| `list-multi-dept-members` | 1. 对每个部门名 `contact dept search --query "<名>"` → 各 `deptId`
2. `contact dept list-members --depts ,,...`(多部门并行/批量见 conventions)
3. 需要档案再 `contact user get --ids …` | +| `verify-user-dept` | 1. `contact dept search --query "<部门名>"` → `deptId`
2. `contact dept list-members --depts ` 中匹配姓名;或先 `search-user` lite 再 `user get` 核对部门字段 | ## Full / 多步组合 | Recipe | 行动指南(固定路线) | |--------|---------------------| -| explore-subdepts-and-members | 1. 取父部门 `deptId`:用户给了 ID 直接用;只给名字则 `contact dept search --query "<父部门名>"` → 父 `deptId`(多命中先消歧)
2. **优先** `contact dept list-children --id <父deptId>` 拿到全部直属子 `deptId` 列表;若用户只点名了部分子部门,则改为对每个子部门名 `contact dept search --query "<子部门名>"`
3. 对子 `deptId`:`contact dept list-members --ids ,,...`(多部门按 conventions **并行/批量**)
4. 若还要成员详情:汇总 `userId` → `contact user get --ids …`(≤30 条/批,超出分批 + 用户确认) | +| explore-subdepts-and-members | 1. 取父部门 `deptId`:用户给了 ID 直接用;只给名字则 `contact dept search --query "<父部门名>"` → 父 `deptId`(多命中先消歧)
2. **优先** `contact dept list-children --id <父deptId>` 拿到全部直属子 `deptId` 列表;若用户只点名了部分子部门,则改为对每个子部门名 `contact dept search --query "<子部门名>"`
3. 对子 `deptId`:`contact dept list-members --depts ,,...`(多部门按 conventions **并行/批量**)
4. 若还要成员详情:汇总 `userId` → `contact user get --ids …`(≤30 条/批,超出分批 + 用户确认) | | verify-user-in-dept | 同速查表 `verify-user-dept`;多轮对话中用户追加「是否在某部门」时叠加本路线 | -| cross-level-dept-members | 1. `contact dept search --query "<父部门关键词>"` → 父 `deptId`
2. **优先** `contact dept list-children --id <父deptId>` 枚举全部直属子 `deptId`;用户已点名的子部门则用 `dept search` 精确命中
3. 需要逐层下钻时,对上一步拿到的子 `deptId` 继续 `dept list-children` 递归(注意控制深度,避免一次拉太多)
4. `contact dept list-members --ids ` → 按需 `user get` | +| cross-level-dept-members | 1. `contact dept search --query "<父部门关键词>"` → 父 `deptId`
2. **优先** `contact dept list-children --id <父deptId>` 枚举全部直属子 `deptId`;用户已点名的子部门则用 `dept search` 精确命中
3. 需要逐层下钻时,对上一步拿到的子 `deptId` 继续 `dept list-children` 递归(注意控制深度,避免一次拉太多)
4. `contact dept list-members --depts ` → 按需 `user get` | | user-detail-organization | 1. `contact user search --query "<关键词>"` → `userId`(多结果先消歧)
2. **必须** `contact user get --ids `
3. 若用户同时给出部门语境:叠加 `verify-user-dept` | | batch-users-by-keyword | 1. `contact user search --query "<职位或技能关键词>"` → 多条
2. 提取 `userId`(≤30)→ `contact user get --ids …`
3. 结果过多时汇总或请用户收窄 | diff --git a/skills/multi/dingtalk-contact/references/contact.md b/skills/multi/dingtalk-contact/references/contact.md index e5573f7a..87b923f1 100644 --- a/skills/multi/dingtalk-contact/references/contact.md +++ b/skills/multi/dingtalk-contact/references/contact.md @@ -211,7 +211,7 @@ Example: Flags: 无 Notes: - - 无需参数,返回当前企业全部角色列表(labelId、labelName等) + - 无需参数,返回当前企业全部角色,按 `groupName` 分组,每组 `labels[]` 里每个角色是 `{labelId, name}`(字段名是 `name`,不是 `labelName`) - 用于不知道准确角色名称时先浏览全部角色 - 典型场景:用户说“企业所有主管/查所有管理员/财务人员有哪些”→ 先 label list 浏览全部角色,匹配目标角色后 label list-members 获取成员 ``` @@ -358,7 +358,7 @@ dws contact user dismission search --depts 123456,789012 --hide-retirement=false | `user get-self/search` | `orgAuthEmail` | mail message send 的 --to/--cc (跨产品) | | `user get-self/search` | `userId` | profile get 的 --staff-id | | `user profile fields` | `fieldCode` | profile get 的 --fields | -| `label list` | `labelId` / `labelName` | `label get --names` 或 `label list-members --id` | +| `label list` | `labelId` / `name` | `label get --names` 或 `label list-members --id` | | `label get` | `labelId` | `label list-members` 的 --id | | `dept search/list-children` | `deptId` | dept get-info/list-children/list-members 的 --dept/--depts | | `dept search/list-children` | `deptId` | dismission search 的 --depts | diff --git a/skills/multi/dingtalk-contact/scripts/contact_dept_members.py b/skills/multi/dingtalk-contact/scripts/contact_dept_members.py index a743e657..6280d849 100644 --- a/skills/multi/dingtalk-contact/scripts/contact_dept_members.py +++ b/skills/multi/dingtalk-contact/scripts/contact_dept_members.py @@ -75,7 +75,7 @@ def main(): elif isinstance(dept_data, dict): inner = dept_data.get('result', dept_data) if isinstance(inner, dict): - # dept search 真机返回顶层 deptList;兼容历史 items/depts 键。 + # dept search 返回顶层 deptList;兼容历史 items/depts 键。 depts = (inner.get('deptList') or inner.get('items') or inner.get('depts') @@ -114,7 +114,7 @@ def main(): elif isinstance(members_data, dict): inner = members_data.get('result', members_data) if isinstance(inner, dict): - # list-members 真机返回 deptUserList;兼容历史 userlist/list 键。 + # list-members 返回 deptUserList;兼容历史 userlist/list 键。 members = (inner.get('deptUserList') or inner.get('userlist') or inner.get('list') @@ -130,7 +130,7 @@ def main(): continue for m in members: - # list-members 真机每项形如 {"userInfo": {"name":..., "userId":...}}, + # list-members 每项形如 {"userInfo": {"name":..., "userId":...}}, # 成员字段嵌在 userInfo 下;兼容历史扁平结构。 info = m.get('userInfo', m) name = info.get('name') or info.get('userName', '未知') diff --git a/skills/multi/dingtalk-dev/references/event.md b/skills/multi/dingtalk-dev/references/event.md index fcbe663b..83b459ae 100644 --- a/skills/multi/dingtalk-dev/references/event.md +++ b/skills/multi/dingtalk-dev/references/event.md @@ -14,14 +14,14 @@ - 批量或全量订阅前,先把候选 `eventCode` 列给用户确认,再 `--dry-run` → `--yes`。 - 返回看 `events[].subscribed` 和 `pushType=STREAM`(事件走 Stream 长连推送;connect 与事件订阅的关系见下方「Stream 长连」)。 - `subscribe/unsubscribe` 的 `--event-codes` 必填,返回 `success/operation/unifiedAppId/eventCodes/needsPublish/versionRequiredAction`;失败时补 `errorCode/errorMsg/reason/retryable/action`。 -- 如果服务端返回 `errorCode=STREAM_NOT_CONNECTED`、`reason=STREAM_NOT_CONNECTED`、`retryable=false`、`action=run connect`,先执行 `dev connect` 建联,再重试订阅。 +- 如果订阅失败、返回提示长链接未在线(是泛化错误:`reason=business_error`、`message` 含「长链接未在线」、`server_error_code=-1`;没有 STREAM_NOT_CONNECTED 这类结构化错误码,也没有 action 字段),先执行 `dev connect` 建联,再重试订阅。 ## Stream 长连:connect 与事件订阅的关系 钉钉一个应用就一条 Stream 长连(WebSocket),上面同时承载机器人消息、事件、卡片回调等多种 topic。connect 和 event 在这条长连上分工不同: - `dev connect`:用应用凭证把这条长连建起来并保活,但只注册了机器人消息处理(收 @机器人 → 转发本地 agent),**不消费事件**。 -- `event subscribe/unsubscribe`:只是**配置**操作(配应用订阅哪些事件码),自己不建长连、不收事件;服务端要求应用的 Stream 长连已在线,否则返回 `STREAM_NOT_CONNECTED`。 +- `event subscribe/unsubscribe`:只是**配置**操作(配应用订阅哪些事件码),自己不建长连、不收事件;服务端要求应用的 Stream 长连已在线,否则报错「长链接未在线」(泛化 business_error,不是结构化错误码)。 - 两者唯一关联:先 `dev connect` 把长连建在线,`event subscribe` 才能成功——connect 负责「让长连在线」,subscribe 负责「配置订阅」。 - 当前限制:connect 的长连不处理事件,dws 也没有「收/消费事件」的运行时命令,event 只到「配置订阅」为止。订阅成功后真正的事件推送 dws 暂不消费——要消费得用注册了事件 handler 的 SDK 自接(事件结构走 `dws devdoc article search --query` 查)。 diff --git a/skills/multi/dingtalk-devapp/references/event.md b/skills/multi/dingtalk-devapp/references/event.md index b66e7ce2..1ffd0267 100644 --- a/skills/multi/dingtalk-devapp/references/event.md +++ b/skills/multi/dingtalk-devapp/references/event.md @@ -90,5 +90,5 @@ event subscribe/unsubscribe 订阅变更写入版本元数据 |------|------| | `--event-codes` 缺失 | CLI 直接报错,先 `event list` 取事件码 | | 订阅后 `subscribed` 仍为 false | 灰度应用需先 `version publish` 发布版本 | -| `errorCode=STREAM_NOT_CONNECTED` / `reason=STREAM_NOT_CONNECTED` | 先执行 `dev connect` 建联,再重试订阅 | +| 返回提示「长链接未在线」(泛化错误 `reason=business_error`、`server_error_code=-1`,无 STREAM_NOT_CONNECTED 码) | 先执行 `dev connect` 建联,再重试订阅 | | `ServiceResult.success=false` | 透传 `errorCode/errorMsg` | diff --git a/skills/multi/dingtalk-devdoc/references/devdoc.md b/skills/multi/dingtalk-devdoc/references/devdoc.md index e6e3e239..1569a49b 100644 --- a/skills/multi/dingtalk-devdoc/references/devdoc.md +++ b/skills/multi/dingtalk-devdoc/references/devdoc.md @@ -7,15 +7,16 @@ ### 搜索开发文档 ``` Usage: - dws devdoc article search [flags] + dws devdoc article search [keyword] [flags] Example: + dws devdoc article search "MCP" # 关键词可作位置参数,等价于 --query dws devdoc article search --query "OAuth2 接入" dws devdoc article search --query "消息卡片" --page 2 --size 5 dws devdoc article search --query "机器人" --size 10 Flags: - --query string 搜索关键词 (必填) - --page int 分页页码 (从 1 开始,默认 1) - --size int 分页大小 (默认 10) + --query string 搜索关键词 (必填;也可用位置参数 [keyword] 传) + --page string 分页页码 (从 1 开始,default "1") + --size string 分页大小 (default "10") ``` ## 意图判断 diff --git a/skills/multi/dingtalk-doc/references/doc.md b/skills/multi/dingtalk-doc/references/doc.md index f21ce032..73e3ce70 100644 --- a/skills/multi/dingtalk-doc/references/doc.md +++ b/skills/multi/dingtalk-doc/references/doc.md @@ -27,7 +27,7 @@ dws doc permission --help ### 搜索 / 遍历文件(已迁移,不再是 doc 子命令) -> **弃用提示**:`dws doc search` 和 `dws doc list` 已迁移到 `drive` / `wiki`。真机上这两条虽仍能跑,但每次都会打印弃用警告,请改用下面的命令,命令详情见 dingtalk-drive skill。 +> **弃用提示**:`dws doc search` 和 `dws doc list` 已迁移到 `drive` / `wiki`。这两条虽仍能跑,但每次都会打印弃用警告,请改用下面的命令,命令详情见 dingtalk-drive skill。 | 旧命令(弃用) | 改用 | 场景 | |--------------|------|------| @@ -83,7 +83,7 @@ Flags: ### 创建其他类型文件 (表格/脑图/白板/多维表/画板/文件夹) -> **弃用提示**:`dws doc file create --type` 已弃用,真机每次执行都会打印 `deprecated, use 'dws wiki node create --type '`。创建各类型文件节点改用 `dws wiki node create`(与 intent-guide 一致);创建普通文件夹用 `dws drive mkdir`。 +> **弃用提示**:`dws doc file create --type` 已弃用,执行时会打印 `deprecated, use 'dws wiki node create --type '`。创建各类型文件节点改用 `dws wiki node create`(与 intent-guide 一致);创建普通文件夹用 `dws drive mkdir`。 ``` Usage: @@ -133,7 +133,7 @@ Flags: ### 下载文件到本地(已迁移到 drive) -> **弃用提示**:`dws doc download` 已迁移到 `dws drive download`(真机执行 `doc download` 会打印弃用警告)。下载已有文件(PDF/图片/附件等非在线文档)改用: +> **弃用提示**:`dws doc download` 已迁移到 `dws drive download`(执行 `doc download` 会打印弃用警告)。下载已有文件(PDF/图片/附件等非在线文档)改用: ``` dws drive download --node --output ~/downloads/ @@ -261,11 +261,11 @@ Usage: Example: dws doc comment list --node dws doc comment list --node --type inline --resolve-status unresolved - dws doc comment list --node --page-size 20 --next-token + dws doc comment list --node --limit 20 --cursor Flags: --node string 目标文档的标识,支持传入 URL 或 ID (必填) - --page-size int 每页返回的评论数量,默认 50,最大 50 - --next-token string 分页游标,从上一次请求的返回结果中获取 (首次请求不传) + --limit int 每页返回的评论数量,默认 50,最大 50 + --cursor string 分页游标,从上一次请求的返回结果中获取 (首次请求不传) --type string 按评论类型过滤: global (全文评论) / inline (划词评论) --resolve-status string 按解决状态过滤: resolved (已解决) / unresolved (未解决) ``` diff --git a/skills/multi/dingtalk-doc/references/doc/doc-file-ops.md b/skills/multi/dingtalk-doc/references/doc/doc-file-ops.md index cabaaa11..6ed912b2 100644 --- a/skills/multi/dingtalk-doc/references/doc/doc-file-ops.md +++ b/skills/multi/dingtalk-doc/references/doc/doc-file-ops.md @@ -3,7 +3,7 @@ > **前置条件(MUST READ):** 执行本命令前,必须先用 Read 工具读取以下文件: > 1. [`../doc.md`](../doc.md) — 命令路由 + 场景索引 + 意图判断 + 工作流 -> **弃用提示(文件管理命令正在迁移到 drive / wiki)**:本文所列 `doc` 文件管理命令在真机上虽仍能跑,但执行时会打印弃用警告,请优先改用 `drive` / `wiki` 对应命令: +> **弃用提示(文件管理命令正在迁移到 drive / wiki)**:本文所列 `doc` 文件管理命令虽仍能跑,但执行时会打印弃用警告,请优先改用 `drive` / `wiki` 对应命令: > - `doc download` → **`dws drive download`**(下载已有文件;在线文档导出 docx 仍走 `doc export`) > - `doc folder create` → **`dws drive mkdir`**(个人空间/钉盘)或 **`dws wiki node create --workspace --type folder`**(知识库内) > - `doc upload` / `doc copy` / `doc move` / `doc rename` / `doc delete` 也在同一迁移方向(→ `drive`),命令用法以 `drive` 为准。 diff --git a/skills/multi/dingtalk-doc/references/doc/doc-list.md b/skills/multi/dingtalk-doc/references/doc/doc-list.md index 17566a70..20a45a9f 100644 --- a/skills/multi/dingtalk-doc/references/doc/doc-list.md +++ b/skills/multi/dingtalk-doc/references/doc/doc-list.md @@ -1,6 +1,6 @@ # doc list(已弃用 → drive list / wiki node list) -> **弃用提示**:`dws doc list` 已迁移,不再是 `doc` 子命令。真机上它虽仍能跑,但每次都会打印弃用警告: +> **弃用提示**:`dws doc list` 已迁移,不再是 `doc` 子命令。它虽仍能跑,但每次都会打印弃用警告: > `'dws doc list' is deprecated, use 'dws drive list --workspace ' or 'dws wiki node list --workspace '`。 > 遍历文件夹/知识库请切到 `dingtalk-drive` skill,用下面的命令。 diff --git a/skills/multi/dingtalk-doc/references/doc/doc-search.md b/skills/multi/dingtalk-doc/references/doc/doc-search.md index 7977007c..f7cd6c40 100644 --- a/skills/multi/dingtalk-doc/references/doc/doc-search.md +++ b/skills/multi/dingtalk-doc/references/doc/doc-search.md @@ -1,6 +1,6 @@ # doc search(已弃用 → drive search / wiki node search) -> **弃用提示**:`dws doc search` 已迁移,不再是 `doc` 子命令。真机上它虽仍能跑,但每次都会打印弃用警告: +> **弃用提示**:`dws doc search` 已迁移,不再是 `doc` 子命令。它虽仍能跑,但每次都会打印弃用警告: > `'dws doc search' is deprecated, use 'dws drive search' or 'dws wiki node search --workspace '`。 > 搜索文档/文件请切到 `dingtalk-drive` skill,用下面的命令。 diff --git a/skills/multi/dingtalk-doc/references/url-patterns.md b/skills/multi/dingtalk-doc/references/url-patterns.md index c20a5a25..c6c108db 100644 --- a/skills/multi/dingtalk-doc/references/url-patterns.md +++ b/skills/multi/dingtalk-doc/references/url-patterns.md @@ -90,14 +90,14 @@ Step 3 → 按下方路由规则映射到对应产品 | `contentType=ALIDOC`, `extension=adoc` | `doc` | 按 [doc.md](./products/doc.md) 操作 | | `contentType=ALIDOC`, `extension=axls` | `sheet` | 按 [sheet.md](./products/sheet.md) 操作(仅 `axls` 在线电子表格) | | `contentType=ALIDOC`, `extension=able` | `aitable` | 将 nodeId 作为 baseId,按 [aitable.md](./products/aitable.md) 操作 | -| `contentType=DOCUMENT`, `extension=xlsx` / `xls` / `xlsm` / `csv` | `doc` | 必须用 `dws doc download` 下载到本地处理,禁止走 `sheet`(非在线表格,sheet 命令无法操作) | -| `contentType≠ALIDOC`, `nodeType=file` | `doc` | 调用 `dws doc download` 下载,返回文件下载链接 | +| `contentType=DOCUMENT`, `extension=xlsx` / `xls` / `xlsm` / `csv` | `doc` | 必须用 `dws drive download` 下载到本地处理,禁止走 `sheet`(非在线表格,sheet 命令无法操作) | +| `contentType≠ALIDOC`, `nodeType=file` | `doc` | 调用 `dws drive download` 下载,返回文件下载链接 | | `nodeType=folder` | `doc` | 调用 `dws doc list --folder ` 列出指定文件夹直接子节点列表 | | 以上均不匹配 | — | 告知用户当前暂不支持该类型 | > axls vs xlsx 关键区分: > - `axls`(钉钉在线电子表格,`contentType=ALIDOC`)→ 走 `sheet` 产品线(读/写/筛选/导出等服务端原子操作) -> - `xlsx` / `xls` / `xlsm` / `csv`(上传到文档空间的本地表格文件,`contentType=DOCUMENT`)→ 必须走 `dws doc download` 下载到本地后再解析处理,严禁错误路由到 `sheet` 产品线(sheet 命令只支持在线表格,调用 xlsx 节点会直接报错) +> - `xlsx` / `xls` / `xlsm` / `csv`(上传到文档空间的本地表格文件,`contentType=DOCUMENT`)→ 必须走 `dws drive download` 下载到本地后再解析处理,严禁错误路由到 `sheet` 产品线(sheet 命令只支持在线表格,调用 xlsx 节点会直接报错) > - 用户想把在线表格导出为 xlsx 文件 → 用 `dws sheet export --node <表格ID或URL>`(单命令一站式:提交导出任务→轮询→取 downloadUrl,加 `--output ./x.xlsx` 可直接落盘) ### 示例 @@ -110,10 +110,10 @@ dws doc info --node "https://alidocs.dingtalk.com/i/nodes/abc123" --format json dws sheet list --node "https://alidocs.dingtalk.com/i/nodes/abc123" --format json # 返回 contentType≠ALIDOC, extension=xlsx/xls/csv → 本地表格文件,必须下载处理(禁止走 sheet) -dws doc download --node "https://alidocs.dingtalk.com/i/nodes/xlsx456" +dws drive download --node "https://alidocs.dingtalk.com/i/nodes/xlsx456" # 返回 contentType≠ALIDOC, nodeType=file → 普通文件,下载 -dws doc download --node "https://alidocs.dingtalk.com/i/nodes/def456" +dws drive download --node "https://alidocs.dingtalk.com/i/nodes/def456" # 返回 nodeType=folder → 文件夹,列出子节点 dws doc list --folder "https://alidocs.dingtalk.com/i/nodes/ghi789" --format json @@ -138,8 +138,8 @@ dws doc list --folder "https://alidocs.dingtalk.com/i/nodes/ghi789" --format jso | **adoc**(在线文档) | `doc read` | `doc update` / `doc block update` | ⚠️ `doc delete` | ⚠️ `doc export` (→ docx) | ⚠️ `doc permission *` | ⚠️ `doc media download/insert` | | **axls**(在线电子表格) | `sheet range read` / `sheet list` | `sheet range update` / `sheet append` | ⚠️ `doc delete`(节点删除) | `sheet export`(单命令一站式:提交→轮询→下载,可选 `--output` 落盘) | ⚠️ `doc permission *`(节点级,跨产品) | 不适用 | | **able**(在线多维表) | `aitable base get` / `aitable record query` | `aitable record create/update` | ⚠️ `doc delete`(节点删除)或 `aitable base delete --yes` | `aitable export data --scope all --format excel`(取 downloadUrl,`--output` 不落盘) | ⚠️ `doc permission *`(节点级) | `aitable attachment upload` | -| **xlsx / xls / xlsm / csv**(本地表格文件) | `doc download` → 本地用 xlsx skill 解析 | 不支持服务端写(先下载改本地再上传) | ⚠️ `doc delete`(节点删除) | 不需要(本身就是 xlsx) | ⚠️ `doc permission *` | 不适用 | -| **普通文件** (nodeType=file) | `doc download` | 不支持服务端写 | ⚠️ `doc delete` | 不需要 | ⚠️ `doc permission *` | 不适用 | +| **xlsx / xls / xlsm / csv**(本地表格文件) | `drive download` → 本地用 xlsx skill 解析 | 不支持服务端写(先下载改本地再上传) | ⚠️ `doc delete`(节点删除) | 不需要(本身就是 xlsx) | ⚠️ `doc permission *` | 不适用 | +| **普通文件** (nodeType=file) | `drive download` | 不支持服务端写 | ⚠️ `doc delete` | 不需要 | ⚠️ `doc permission *` | 不适用 | | **文件夹** (nodeType=folder) | `doc list --folder ` | `doc create --folder ...` | ⚠️ `doc delete` | 不适用 | ⚠️ `doc permission *` | 不适用 | | **分享短链** `/i/p/` | `read_url` 兜底(外部工具) | 不适用 | 不适用 | 不适用 | 不适用 | 不适用 | diff --git a/skills/multi/dingtalk-drive/references/drive.md b/skills/multi/dingtalk-drive/references/drive.md index 30b6a3c8..8f9a1d89 100644 --- a/skills/multi/dingtalk-drive/references/drive.md +++ b/skills/multi/dingtalk-drive/references/drive.md @@ -514,7 +514,7 @@ dws drive copy --node <源文件dentryUuid> --folder <目标文件夹fileId> --f - 不传 `--space-id` 时默认使用「我的文件」空间 - 不传 `--folder` 时默认操作空间根目录 - `--folder` 只能使用父文件夹的 `dentryUuid`。不要把 `drive info` 返回的数字型 `dentryId` 当作父目录 -- **`--limit` 最大值为 50**,禁止传入超过 50 的值(如 `--limit 100`)。用户要求超过 50 条时,应使用 `--limit 50` 配合 `--cursor` 分页查询 +- **`--limit` 有效上限为 50**:CLI 不做本地校验,传 `--limit 100` 不会报错,但服务端每页最多返回 50 条,超出部分无效。用户要求超过 50 条时,应使用 `--limit 50` 配合 `--cursor` 分页查询 - `--order-by` 支持: `createTime`、`modifyTime`、`name` - **上传文件首选 `dws drive upload` 命令**;手动三步(`upload-info` → HTTP PUT → `commit`)仅用于自定义流式上传等特殊场景。手动三步时 HTTP PUT 必须把 upload-info 返回的 `headers` 全部回传,`Content-Type` 通常要留空;PUT 返回 200 后才能调 `commit`;`uploadId` 有过期时间,过期需重新 `upload-info`;`--folder` 在 upload-info / commit 中要保持一致 - `--file-name` 必须包含扩展名(如 `report.pdf`) diff --git a/skills/multi/dingtalk-mail/references/mail.md b/skills/multi/dingtalk-mail/references/mail.md index 4a822165..14159224 100644 --- a/skills/multi/dingtalk-mail/references/mail.md +++ b/skills/multi/dingtalk-mail/references/mail.md @@ -214,7 +214,9 @@ Flags: | 字段 | 类型 | 说明 | |------|------|------| -| `message` | `object` | 邮件完整信息,包含主题、发件人、收件人、正文、附件等 | +| `message` | `object` | 邮件信息,含 subject/from/toRecipients/ccRecipients/markdownBody/conversationId/receivedDateTime/size 等 | + +> **注意:** 即使邮件确有附件,`message get` 返回的 `message` 对象也**不含** `attachments`/`isRead`/`tags` 字段(实测 keys 仅上表所列这些)。要取附件请另用 `dws mail attachment list`。 ### 发送邮件 ``` @@ -1364,6 +1366,8 @@ Flags: --actions string 规则动作 JSON 数组 (必填) ``` +> **⚠️ 实测:** 尽管 `--help` 把 `--conditions` 标为(可选),服务端实际要求它**非空**——省略或传空会返回 `209 SYS_UNKNOWN_ERROR(1001)` 失败,帮助文本承诺的"命中所有邮件"无条件匹配**不可用**。create 请始终传入有效条件。 + **返回字段:** | 字段 | 类型 | 说明 | @@ -1375,11 +1379,11 @@ Flags: #### 更新收信规则 -更新已有的收信规则。**除 `--conditions` 外所有参数均为必填**。 +更新已有的收信规则。**所有参数(含 `--conditions`)均须传入有效值**——见下方警告。 > **建议工作流:** 先通过 `dws mail rule list` 获取当前规则的完整配置,再传入需要修改的字段值。 > -> `--conditions` 为空或不传表示命中所有邮件(无条件匹配)。`--actions` 格式同 create 命令。 +> **⚠️ 实测:** `--help` 称 `--conditions` 为空即命中所有邮件,但该无条件匹配**不可用**——省略或传空会返回 `209 SYS_UNKNOWN_ERROR(1001)` 失败。update 请始终传入有效条件。`--actions` 格式同 create 命令。 ``` Usage: @@ -1399,6 +1403,8 @@ Flags: --actions string 规则动作 JSON 数组 (必填) ``` +> **⚠️ 实测:** 上方 `--conditions` 的"(可选,为空表示命中所有邮件)"是 `--help` 原文,但服务端实际要求它**非空**——省略或传空会返回 `209 SYS_UNKNOWN_ERROR(1001)` 失败。update 须传入有效条件。 + **返回字段:** | 字段 | 类型 | 说明 | diff --git a/skills/multi/dingtalk-minutes/references/07-minutes.md b/skills/multi/dingtalk-minutes/references/07-minutes.md index c04b8d20..6a540e57 100644 --- a/skills/multi/dingtalk-minutes/references/07-minutes.md +++ b/skills/multi/dingtalk-minutes/references/07-minutes.md @@ -8,4 +8,4 @@ | meeting-followup | 1. **提取待办优先**:`python scripts/minutes_extract_todos.py [--id ] [--max N]` → 获取行动项列表 无脚本时按「多源并行采集」(见 dws-shared 的 conventions.md)执行 2. 提取行动项 3. `aisearch person --keyword "<姓名>" --dimension name` → 取 `userId` → `todo task create --title "<行动项>" --executors `(每条行动项;批量时通过 `dingtalk-todo` skill:`python scripts/todo_batch_create.py todos.json`,或循环 `dws todo task create`) 4. `chat message send --group --text "<通知内容>"` 通知已创建 | | share-minutes | **拉摘要优先**:`python scripts/minutes_recent_summary.py [--max N] [--output summary.md]` → 获取近期听记摘要 备选手动:1. `minutes list mine` → 取 `taskUuid` 2. 用户选定 3. `minutes get summary --id ` → 取摘要 4. 单聊:`contact user search --query "<姓名>"` → 取 `openDingTalkId` → `chat message send --open-dingtalk-id --text "<摘要内容>"`(推荐);群聊:`--group --text "<摘要内容>"` 发送。仅当无法获取 openDingTalkId 时才用 `--user `(备选) | | browse-minutes | 1. `minutes list all --query "<关键词>" --max ` → 取全量 `taskUuid`(翻页直至无更多;无主题筛选用 `minutes list mine`) 2. **详情/元数据优先** `minutes get batch --ids `(仅 API 上限时拆多批);**摘要专项**且无 batch 字段时再用并行 `get summary` 或 `**python scripts/minutes_recent_summary.py --max `** 3. 汇总展示;**同一用户诉求下避免**「半截 list → 再 list 剩余」「拆 4 条一批 summary 连跑多轮」等无必要拆分 | -| minutes-detail | 1. 确定 `taskUuid`:用户已提供 → 直接用;未提供 → `minutes list mine --max 5` 让用户选 2. 并行拉取四维信息:`minutes get info --id ` `&` `minutes get summary --id ` `&` `minutes get keywords --id ` `&` `minutes get todos --id ` `& wait` 3. 整合输出:基础信息(标题、时间、参与人)→ AI 摘要 → 关键字 → 行动项/待办 4.(可选)用户要求看原文 → `minutes get transcription --id ` | +| minutes-detail | 1. 确定 `taskUuid`:用户已提供 → 直接用;未提供 → `minutes list mine --max 5` 让用户选 2. 依次拉取四维信息(**逐条串行执行,禁止用 `&` 后台与 `wait`——违反 Shell 安全铁律,Windows cmd 下会命令错乱**):`minutes get info --id `、`minutes get summary --id `、`minutes get keywords --id `、`minutes get todos --id ` 3. 整合输出:基础信息(标题、时长、开始时间;get info 不返回参会人)→ AI 摘要 → 关键字 → 行动项/待办 4.(可选)用户要求看原文 → `minutes get transcription --id ` | diff --git a/skills/multi/dingtalk-minutes/references/minutes.md b/skills/multi/dingtalk-minutes/references/minutes.md index 5f9f71b9..cc4c5a8c 100644 --- a/skills/multi/dingtalk-minutes/references/minutes.md +++ b/skills/multi/dingtalk-minutes/references/minutes.md @@ -26,7 +26,7 @@ dws minutes │ ├── shared [--query] [--start] [--end] [--max] [--next-token] # 仅查询他人共享给我的听记(不含我自己创建的) │ └── all [--query] [--start] [--end] [--max] [--next-token] # 查询我可访问的所有听记(= mine ∪ shared,覆盖面最广,默认 scope) ├── get # 获取听记详情(get 后必须跟子命令名) -│ ├── info --id # 获取听记基础信息(标题、时长、参与人、状态等) +│ ├── info --id # 获取听记基础信息(标题、时长、开始时间、taskUuid、访问链接;不含参会人) │ ├── summary --id # 获取听记 AI 摘要(结构化纪要) │ ├── transcription --id # 获取听记语音转写原文(逐字稿,支持分页) │ ├── keywords --id # 获取听记关键词 @@ -615,7 +615,7 @@ AI 基于已拉取的转写数据,**本地完成**聚类与摘要(无需新 > **铁律 2:严禁用 AI 摘要里的"参与人/参会人"字段判断某人是否参会** > - AI 摘要中的"参与人"字段往往**只截取最显著的 1-2 个名字**(通常是创建人或发言最多的人),**不是完整参会人列表** > - "AI 摘要参与人只有故愚 → 灵麦没参会" 是典型的错误推理链 -> - 如果一定要列出参会人,应使用 `dws minutes get info` / `get batch` 返回的 `participants` 字段,而不是摘要文本里的描述 +> - 如果一定要列出参会人,只能从 `get transcription` 的转写发言人(`speakerName`/`speakerId`)汇总——`get info` / `get batch` **均不返回** `participants` 结构化字段(get info 只返回 duration/startTime/taskUuid/title/url,get batch 只返回 creatorNick/creatorUnionId/status 等),别指望从它们拿参会名单 > > **铁律 3:通讯录查询是 Step 4 的强制起点,不依赖 Step 3 是否成功** > - Step 2 一旦返回"未命中真名标注",**Step 3 与 Step 4 ① 必须并发触发**,不要等 Step 3 失败再补查 @@ -664,7 +664,7 @@ Step 7: 引导用户替换发言人(调用 speaker replace 写回听记) > - **未命中真名标注 ≠ 目标人物没参会**——绝大多数听记的发言人都是匿名编号,"未命中"是**默认场景**,恰恰是发言人识别功能要解决的问题 > - **不要在转写文本里 grep 目标人名**作为存在性判断——花名/真名通常不会出现在 TA 自己的发言里(参见铁律 1) > - **不要把 `dws minutes get summary` 摘要文本里写到的"参与人"当作完整参会列表**——AI 摘要里的"参与人"字段只截取最显著的 1-2 个名字,不是完整名册(参见铁律 2) -> - 唯一能下"目标人物没参会"结论的场景是:`get info` / `get batch` 返回的结构化 `participants` 字段里**完整列出参会人且不含目标**,且 `dws contact user search` 也搜不到该人 → 才允许告知用户"该人不在参会列表" +> - 听记各接口都拿不到权威的完整参会名册:`get info` / `get batch` **均不返回** `participants` 字段,转写发言人列表又常是匿名编号。因此**几乎无法**仅凭听记数据下"目标人物没参会"的结论;只有当转写发言人已全部实名标注且不含目标、且 `dws contact user search` 也搜不到该人时,才可谨慎告知"未在可见发言人中找到该人" ##### Step 3: 转写原文内推断(优先) @@ -810,7 +810,7 @@ Flags: --ids string 听记 taskUuid 列表,逗号分隔 (必填) ``` -返回字段: 听记标题、时长、参与人列表、创建时间、taskUuid、听记状态 +返回字段: `result.minutesDetails` 数组,每项含 `title`(标题)、`durationMicros`(时长, 微秒)、`startTime`(开始时间, 毫秒)、`taskUuid`、`status`(状态)、`creatorNick`(创建人昵称)、`creatorUnionId`、`size`、`bizType`、`isDeleted`。**不返回参会人列表**。 ### 修改听记标题 ``` @@ -2239,7 +2239,7 @@ Step 2 **[禁止]** 看到全是匿名编号 → 没有继续走 Step 3-4,反 1. **"在转写里搜不到目标人名"绝不构成"没参会"的证据**:花名/真名通常不出现在 TA 自己的发言里,这是默认场景而非例外。99% 的听记发言人都是匿名编号——这正是发言人识别功能要解决的问题。 -2. **AI 摘要的"参与人"字段是低保真信号,不能作为参会判断依据**:`get summary` 返回的是 AI 生成的自然语言摘要,里面提到的"参与人"通常只是最显著的 1-2 人;要拿完整参会列表,应使用 `get info` / `get batch` 返回的结构化 `participants` 字段。 +2. **AI 摘要的"参与人"字段是低保真信号,不能作为参会判断依据**:`get summary` 返回的是 AI 生成的自然语言摘要,里面提到的"参与人"通常只是最显著的 1-2 人。而 `get info` / `get batch` **均不返回**结构化 `participants` 字段,无法从中拿完整参会列表;能拿到的只有 `get transcription` 的转写发言人(常为匿名编号)。 3. **`dws contact user search` 是 Step 4 ① 的必跑项,单次调用就能突破死局**:本案例的整个失败链路只要有 1 次通讯录查询就能立即扭转——拿到"木兰=王佳明,产品设计部"后,Step 5 的角色匹配就有了锚点,再也不会得出"没参会"的错误结论。 diff --git a/skills/multi/dingtalk-minutes/scripts/minutes_extract_todos.py b/skills/multi/dingtalk-minutes/scripts/minutes_extract_todos.py index 7ca3d651..10123377 100644 --- a/skills/multi/dingtalk-minutes/scripts/minutes_extract_todos.py +++ b/skills/multi/dingtalk-minutes/scripts/minutes_extract_todos.py @@ -44,6 +44,58 @@ def run_dws( return None +def todos_from_payload(payload: Any) -> List[dict]: + """解析 `minutes get todos` 返回为待办列表。 + + 返回结构: result.dingtalkTodoList(对象数组, 含 title 等)与 + result.actions(JSON 字符串数组, 每条形如 + {"mark":[],"value":"..."}). 二者无 todos 键。 + 优先取 dingtalkTodoList; 为空时回退解析 actions。 + """ + if isinstance(payload, dict): + inner = payload.get('result', payload) + else: + inner = payload + out: List[dict] = [] + if isinstance(inner, dict): + ding_list = inner.get('dingtalkTodoList') + if isinstance(ding_list, list) and ding_list: + for t in ding_list: + if isinstance(t, dict): + content = t.get('title') or t.get('content') or '' + if content: + out.append({'content': str(content), '_raw': t}) + if out: + return out + actions = inner.get('actions') + if isinstance(actions, list): + for a in actions: + content = '' + if isinstance(a, str): + text = a.strip() + if text.startswith('{'): + try: + parsed = json.loads(text) + content = parsed.get('value') or '' + except json.JSONDecodeError: + content = text + else: + content = text + elif isinstance(a, dict): + content = (a.get('value') or a.get('content') + or a.get('title') or '') + if content: + out.append({'content': str(content)}) + elif isinstance(inner, list): + for t in inner: + if isinstance(t, dict): + content = (t.get('content') or t.get('text') + or t.get('title') or t.get('value') or '') + if content: + out.append({'content': str(content), '_raw': t}) + return out + + def main(): parser = argparse.ArgumentParser( description='从听记中提取待办事项' @@ -82,21 +134,9 @@ def main(): ]) if not todos_data: continue - if isinstance(todos_data, list): - items = todos_data - elif isinstance(todos_data, dict): - inner = todos_data.get('result', todos_data) - if isinstance(inner, dict): - items = inner.get('todos', []) - elif isinstance(inner, list): - items = inner - else: - items = [] - else: - items = [] + items = todos_from_payload(todos_data) for t in items: - if isinstance(t, dict): - t['_source'] = title + t['_source'] = title all_todos.extend(items) print(f"\n📋 听记待办汇总") diff --git a/skills/multi/dingtalk-minutes/scripts/minutes_list_parse.py b/skills/multi/dingtalk-minutes/scripts/minutes_list_parse.py index 712327a9..d6aa83ea 100644 --- a/skills/multi/dingtalk-minutes/scripts/minutes_list_parse.py +++ b/skills/multi/dingtalk-minutes/scripts/minutes_list_parse.py @@ -17,7 +17,7 @@ def _unwrap_rows(payload: Any) -> List[Any]: return value if isinstance(value, dict): for inner_key in ( - 'items', 'list', 'records', 'minutes', + 'itemList', 'items', 'list', 'records', 'minutes', ): inner = value.get(inner_key) if isinstance(inner, list): @@ -30,7 +30,8 @@ def uuid_title_pairs_from_payload(payload: Any) -> List[Tuple[str, str]]: out: List[Tuple[str, str]] = [] for item in _unwrap_rows(payload): if isinstance(item, dict): - uuid = item.get('taskUuid') or item.get('id') or item.get('task_uuid') + uuid = (item.get('taskUuid') or item.get('uuid') + or item.get('id') or item.get('task_uuid')) if not uuid: continue title = item.get('title') or item.get('name') or '无标题' @@ -53,6 +54,7 @@ def uuid_title_pairs_from_payload(payload: Any) -> List[Tuple[str, str]]: continue uuid = ( parsed.get('taskUuid') + or parsed.get('uuid') or parsed.get('id') or parsed.get('task_uuid') ) diff --git a/skills/multi/dingtalk-minutes/scripts/minutes_recent_summary.py b/skills/multi/dingtalk-minutes/scripts/minutes_recent_summary.py index 24e8ed44..f136f290 100644 --- a/skills/multi/dingtalk-minutes/scripts/minutes_recent_summary.py +++ b/skills/multi/dingtalk-minutes/scripts/minutes_recent_summary.py @@ -93,9 +93,16 @@ def main(): if isinstance(summary_data, str): summary_text = summary_data elif isinstance(summary_data, dict): - summary_text = (summary_data.get('summary') - or summary_data.get('content') - or json.dumps(summary_data, + # 兼容 {result: {fullSummary}} 包裹结构 + inner = summary_data.get('result') + container = (inner if isinstance(inner, dict) + else summary_data) + summary_text = (container.get('fullSummary') + or container.get('summary') + or container.get('content') + or (inner if isinstance(inner, str) + else '') + or json.dumps(container, ensure_ascii=False)) output_lines.append(f"## {i}. {title}\n") diff --git a/skills/multi/dingtalk-oa/references/oa.md b/skills/multi/dingtalk-oa/references/oa.md index c44c049e..643b0ac7 100644 --- a/skills/multi/dingtalk-oa/references/oa.md +++ b/skills/multi/dingtalk-oa/references/oa.md @@ -314,7 +314,7 @@ Flags: 用户说"拒绝审批/驳回" → 先 `tasks` 获取 taskId,再 `reject` 用户说"撤回审批/取消审批" → `approval revoke` 用户说"审批记录/操作历史" → `approval records` -用户说"我发起的审批" → `approval list-initiated`(需 --process-code,可从 list-forms 或 detail 获取) +用户说"我发起的审批" → `approval list-initiated`(需 --process-code,可从 list-forms / search-forms / detail 获取) 用户说"有哪些审批表单/可见表单" → `approval list-forms` 用户说"我有哪些待审的任务" → `approval tasks` 用户说"我发起的审批单" -> `approval list-submitted` @@ -351,7 +351,7 @@ dws oa approval records --instance-id --format json # 7. 获取可见审批表单(得到 processCode) dws oa approval list-forms --cursor 0 --limit 100 --format json -# 8. 查看自己发起的审批列表(--process-code 来自 list-forms 或 detail) +# 8. 查看自己发起的审批列表(--process-code 来自 list-forms / search-forms / detail) dws oa approval list-initiated --process-code \ --start "2026-03-10T00:00:00+08:00" --end "2026-03-10T23:59:59+08:00" \ --cursor 0 --limit 20 --format json @@ -383,6 +383,7 @@ dws oa approval oa-cc-noticer --instance-id --users "userId1 | `tasks` | `taskId` | approve / reject / redirect-task 的 --task-id | | `detail` | `processCode` | list-initiated 的 --process-code | | `list-forms` | `processCode` | list-initiated 的 --process-code | +| `search-forms` | `processCode` | list-initiated 的 --process-code | ## 注意事项 @@ -391,7 +392,7 @@ dws oa approval oa-cc-noticer --instance-id --users "userId1 - `redirect-task` 的 `--to-actioner-id` 可通过 `dws contact user search` 获取目标用户 userId - `revoke` 只能撤销自己发起的审批 - `--remark` 审批意见虽为可选,但建议填写以留存审批痕迹 -- `list-initiated` 的 `--process-code` 可从 `list-forms` 或 `detail` 返回中提取 +- `list-initiated` 的 `--process-code` 可从 `list-forms`、`search-forms` 或 `detail` 返回中提取。当 `list-forms` 返回 `processCodeList` 为空(`totalCount -1`)时,用 `search-forms --query <表单名>`(如 `--query 报销`)按名称精准拿 `processCode` 更稳 - `list-initiated` 的 `--start` / `--end` 区间有后端上限(约 120 天)。超过上限会返回误导性的 `business_error: 时间戳无效`(实为区间过长,不是时间格式问题)。跨度大时请拆成多段短区间分别查询 ## 自动化脚本 diff --git a/skills/multi/dingtalk-sheet/references/sheet.md b/skills/multi/dingtalk-sheet/references/sheet.md index 567c4278..44c8fb55 100644 --- a/skills/multi/dingtalk-sheet/references/sheet.md +++ b/skills/multi/dingtalk-sheet/references/sheet.md @@ -1581,7 +1581,7 @@ Flags: - MCP 返回 `FAILED`:命令立即返回错误并附带失败原因,**禁止自动重试 `dws sheet export`**,告知用户稍后再试 - 轮询 30 次仍 `PROCESSING`:命令返回超时错误,告知用户稍后再试 -**限制**:仅支持钉钉在线电子表格(alxs)→ xlsx。导出钉钉文字文档请使用 `doc` 产品对应的导出工具。 +**限制**:仅支持钉钉在线电子表格(axls)→ xlsx。导出钉钉文字文档请使用 `doc` 产品对应的导出工具。 ### 表格模板(list / search / apply) ``` @@ -2067,7 +2067,7 @@ dws sheet export --node --output ./ - `filter-view delete-criteria` 仅清除指定列的条件,不会删除整个筛选视图。如需删除整个筛选视图,请使用 `filter-view delete` - `filter-view delete-criteria` 如果指定列没有设置筛选条件,调用不会报错 - 筛选视图相关操作需要"可阅读"权限(list / info / list-criteria / get-criteria)或"可编辑"权限(create / update / delete / update-criteria / delete-criteria),不支持跨组织操作 -- ★ `export` 仅支持钉钉在线电子表格(alxs)→ xlsx;传入钉钉文字文档会报 `invalidRequest.document.typeIllegal` +- ★ `export` 仅支持钉钉在线电子表格(axls)→ xlsx;传入钉钉文字文档会报 `invalidRequest.document.typeIllegal` - ★ `export` 为单命令一站式,CLI 内部已自动完成「提交 → 渐进式退避轮询 → 可选下载」,**Agent 不得在外部实现轮询或重试**;命令返回成功后不再调用其他 export 相关命令 - `export` 内置轮询策略:1~5 次间隔 2s、6~10 次间隔 5s、11~20 次间隔 10s、21~30 次间隔 15s,硬上限 30 次(约 5 分钟);超时后命令返回错误,告知用户稍后再试即可 - ★ `export` 命令返回失败或超时时,**禁止自动重调 `dws sheet export`**;直接告知用户导出失败并建议稍后再试 diff --git a/skills/multi/dingtalk-sheet/references/sheet/sheet-export.md b/skills/multi/dingtalk-sheet/references/sheet/sheet-export.md index f390efed..891da573 100644 --- a/skills/multi/dingtalk-sheet/references/sheet/sheet-export.md +++ b/skills/multi/dingtalk-sheet/references/sheet/sheet-export.md @@ -56,7 +56,7 @@ Flags: - MCP 返回 `FAILED`:命令立即返回错误并附带失败原因,**禁止自动重试 `dws sheet export`**,告知用户稍后再试 - 轮询 30 次仍 `PROCESSING`:命令返回超时错误,告知用户稍后再试 -**限制**:仅支持钉钉在线电子表格(alxs)→ xlsx。导出钉钉文字文档请使用 `doc` 产品对应的导出工具。 +**限制**:仅支持钉钉在线电子表格(axls)→ xlsx。导出钉钉文字文档请使用 `doc` 产品对应的导出工具。 ## 核心工作流 @@ -86,7 +86,7 @@ dws sheet export --node --output ./ ## 注意事项 -- ★ `export` 仅支持钉钉在线电子表格(alxs)→ xlsx;传入钉钉文字文档会报 `invalidRequest.document.typeIllegal` +- ★ `export` 仅支持钉钉在线电子表格(axls)→ xlsx;传入钉钉文字文档会报 `invalidRequest.document.typeIllegal` - ★ `export` 为单命令一站式,CLI 内部已自动完成「提交 → 渐进式退避轮询 → 可选下载」,**Agent 不得在外部实现轮询或重试**;命令返回成功后不再调用其他 export 相关命令 - `export` 内置轮询策略:1~5 次间隔 2s、6~10 次间隔 5s、11~20 次间隔 10s、21~30 次间隔 15s,硬上限 30 次(约 5 分钟);超时后命令返回错误,告知用户稍后再试即可 - ★ `export` 命令返回失败或超时时,**禁止自动重调 `dws sheet export`**;直接告知用户导出失败并建议稍后再试 diff --git a/skills/multi/dingtalk-skill/SKILL.md b/skills/multi/dingtalk-skill/SKILL.md index 6a69c8fa..12fba005 100644 --- a/skills/multi/dingtalk-skill/SKILL.md +++ b/skills/multi/dingtalk-skill/SKILL.md @@ -33,10 +33,10 @@ metadata: ## 环境 -- `DWS_SKILL_API_HOST` 覆盖技能 API 地址(默认 `https://aihub.dingtalk.com`) +- `DWS_SKILL_API_HOST` 覆盖技能 API 地址(默认 `https://mcp.dingtalk.com`) ## 兼容提示 - `dws skill find` → 用 `dws skill search --query <关键词>` -- `--scopes` 已废弃(真机打 `Flag --scopes has been deprecated, 请使用 --source 替代` 警告)→ 用 `--source` -- 前置:`skill search` 依赖企业开通技能市场;未开通时真机报「当前企业暂未开放此功能」,属企业级开关,非命令写法问题 +- `--scopes` 已废弃(会打印 `Flag --scopes has been deprecated, 请使用 --source 替代` 警告)→ 用 `--source` +- 前置:`skill search` 依赖企业开通技能市场;未开通时报「当前企业暂未开放此功能」,属企业级开关,非命令写法问题 diff --git a/skills/multi/dingtalk-skill/references/skill.md b/skills/multi/dingtalk-skill/references/skill.md index c1e47f55..68fdcea7 100644 --- a/skills/multi/dingtalk-skill/references/skill.md +++ b/skills/multi/dingtalk-skill/references/skill.md @@ -48,8 +48,8 @@ Flags: --source string 查询范围,空格分隔。备选值:DingtalkMarket(钉钉市场)、OrgInternal(企业内部)。为空默认查市场技能 ``` -> `--scopes` 已废弃(真机会打 `Flag --scopes has been deprecated, 请使用 --source 替代` 警告),一律用 `--source`。 -> 前置:`skill search` 依赖企业开通技能市场;未开通时真机报「当前企业暂未开放此功能」,这是企业级开关,非命令写法问题。 +> `--scopes` 已废弃(会打印 `Flag --scopes has been deprecated, 请使用 --source 替代` 警告),一律用 `--source`。 +> 前置:`skill search` 依赖企业开通技能市场;未开通时报「当前企业暂未开放此功能」,这是企业级开关,非命令写法问题。 返回字段: - `skillId` — 技能 ID(后续 `install` 需要)