From 56116bf99e79451fd41fb8a434a34b033eb733c6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=8E=89=E6=BE=9C?= Date: Wed, 15 Jul 2026 01:17:45 +0800 Subject: [PATCH] feat(skills): align Wukong multi-skill docs --- skills/multi/dingtalk-agoal/SKILL.md | 34 +- .../multi/dingtalk-agoal/references/agoal.md | 268 +- skills/multi/dingtalk-aisearch/SKILL.md | 52 +- .../dingtalk-aisearch/references/aisearch.md | 2 +- .../references/intent-guide.md | 14 + .../references/lite-recipes.md | 30 + skills/multi/dingtalk-aitable/SKILL.md | 82 +- .../references/06-data-analytics.md | 10 +- .../references/_common/conventions.md | 23 +- .../dingtalk-aitable/references/aitable.md | 293 +- .../aitable/aitable-advperm-index.md | 6 + .../aitable/aitable-attachment-index.md | 5 + .../references/aitable/aitable-attachment.md | 6 +- .../references/aitable/aitable-base-index.md | 10 + .../aitable/aitable-best-practices.md | 2 +- .../references/aitable/aitable-cell-value.md | 16 +- .../aitable/aitable-command-index.md | 273 ++ .../references/aitable/aitable-complex-ops.md | 39 + .../references/aitable/aitable-context.md | 13 + .../aitable/aitable-dashboard-chart.md | 16 +- .../aitable/aitable-dashboard-index.md | 6 + .../aitable/aitable-export-import-index.md | 6 + .../aitable/aitable-export-import.md | 29 - .../references/aitable/aitable-field-index.md | 9 + .../references/aitable/aitable-filter-sort.md | 69 +- .../references/aitable/aitable-form-index.md | 8 + .../references/aitable/aitable-form.md | 8 +- .../references/aitable/aitable-intent.md | 93 + .../references/aitable/aitable-notes.md | 8 + .../references/aitable/aitable-primary-doc.md | 4 +- .../aitable/aitable-record-create.md | 1 + .../aitable/aitable-record-history.md | 25 +- .../aitable/aitable-record-index.md | 13 + .../aitable/aitable-record-query.md | 46 + .../references/aitable/aitable-related.md | 5 + .../references/aitable/aitable-scripts.md | 10 + .../references/aitable/aitable-table-index.md | 8 + .../references/aitable/aitable-uri.md | 17 + .../aitable/aitable-url-to-baseid.md | 11 + .../references/aitable/aitable-view-config.md | 18 +- .../references/aitable/aitable-view-extras.md | 4 +- .../references/aitable/aitable-view-index.md | 6 + .../aitable/aitable-workflow-index.md | 5 + .../references/aitable/aitable-workflow.md | 193 +- .../references/field-rules.md | 2 +- .../references/intent-guide.md | 10 + .../references/url-patterns.md | 129 + .../scripts/aitable_export_via_task.py | 13 +- .../scripts/aitable_import_via_task.py | 8 +- .../scripts/bulk_add_fields.py | 37 +- .../scripts/import_records.py | 15 +- skills/multi/dingtalk-attendance/SKILL.md | 40 +- .../references/attendance-commands.md | 998 +++++++ .../references/attendance-constraints.md | 10 + .../references/attendance-context.md | 13 + .../references/attendance-index.md | 11 + .../references/attendance-intent.md | 62 + .../references/attendance-notes.md | 107 + .../references/attendance-report.md | 28 +- .../references/attendance-routing.md | 55 + .../references/attendance-schedule.md | 95 +- .../references/attendance-scripts.md | 21 + .../references/attendance-vacation.md | 2 +- .../references/attendance-workflow.md | 148 + .../references/attendance.md | 1324 +-------- .../references/intent-guide.md | 7 + .../scripts/attendance_my_record.py | 20 +- .../scripts/attendance_report_checkin.py | 4 +- .../scripts/attendance_report_common.py | 2 +- .../scripts/attendance_report_daily.py | 4 +- .../scripts/attendance_report_detail.py | 4 +- .../scripts/attendance_report_monthly.py | 4 +- .../scripts/attendance_schedule_export.py | 2 +- .../scripts/attendance_schedule_import.py | 2 +- .../scripts/attendance_vacation_balance.py | 4 +- skills/multi/dingtalk-calendar/SKILL.md | 53 +- .../references/03-meeting.md | 27 +- .../references/calendar-index.md | 7 + .../dingtalk-calendar/references/calendar.md | 264 +- .../references/intent-guide.md | 10 + .../references/lite-recipes.md | 27 + .../scripts/calendar_free_slot_finder.py | 64 +- skills/multi/dingtalk-chat/SKILL.md | 81 +- .../dingtalk-chat/references/01-messaging.md | 12 +- .../references/chat-admin-commands.md | 417 +++ .../dingtalk-chat/references/chat-commands.md | 10 + .../references/chat-composite.md | 139 + .../dingtalk-chat/references/chat-context.md | 27 + .../references/chat-conversation-commands.md | 206 ++ .../references/chat-group-commands.md | 484 ++++ .../dingtalk-chat/references/chat-index.md | 13 + .../dingtalk-chat/references/chat-intent.md | 163 ++ .../references/chat-message-file-commands.md | 625 +++++ .../dingtalk-chat/references/chat-notes.md | 67 + .../dingtalk-chat/references/chat-related.md | 32 + .../dingtalk-chat/references/chat-scripts.md | 9 + .../dingtalk-chat/references/chat-workflow.md | 35 + skills/multi/dingtalk-chat/references/chat.md | 2197 +-------------- .../dingtalk-chat/references/intent-guide.md | 85 + .../dingtalk-chat/references/lite-recipes.md | 7 + .../scripts/chat_export_messages.py | 80 +- .../scripts/chat_history_with_user.py | 72 +- skills/multi/dingtalk-contact/SKILL.md | 64 +- .../references/08-directory.md | 18 +- .../references/_common/conventions.md | 44 + .../references/contact-index.md | 6 + .../dingtalk-contact/references/contact.md | 27 +- .../references/intent-guide.md | 13 + .../references/lite-recipes.md | 30 + .../scripts/contact_dept_members.py | 34 +- .../dingtalk-dev/references/dev-index.md | 12 + skills/multi/dingtalk-dev/references/event.md | 1 + .../multi/dingtalk-devapp/references/event.md | 94 - skills/multi/dingtalk-devdoc/SKILL.md | 10 +- .../dingtalk-devdoc/references/devdoc.md | 50 +- .../references/intent-guide.md | 7 + skills/multi/dingtalk-ding/SKILL.md | 17 +- skills/multi/dingtalk-ding/references/ding.md | 95 +- .../dingtalk-ding/references/intent-guide.md | 13 + .../dingtalk-ding/references/lite-recipes.md | 7 + skills/multi/dingtalk-doc/SKILL.md | 98 +- .../dingtalk-doc/references/04-document.md | 122 +- .../references/_common/conventions.md | 23 +- skills/multi/dingtalk-doc/references/doc.md | 1077 +------- .../dingtalk-doc/references/doc/doc-block.md | 7 +- .../references/doc/doc-command-index.md | 167 ++ .../references/doc/doc-comment.md | 4 - .../references/doc/doc-context.md | 25 + .../dingtalk-doc/references/doc/doc-create.md | 14 +- .../dingtalk-doc/references/doc/doc-export.md | 50 +- .../references/doc/doc-extra-index.md | 7 + .../references/doc/doc-file-ops.md | 4 - .../references/doc/doc-help-query.md | 25 + .../dingtalk-doc/references/doc/doc-import.md | 135 +- .../references/doc/doc-info-index.md | 7 + .../dingtalk-doc/references/doc/doc-info.md | 17 +- .../dingtalk-doc/references/doc/doc-intent.md | 136 + .../references/doc/doc-media-index.md | 7 + .../dingtalk-doc/references/doc/doc-media.md | 6 +- .../references/doc/doc-permission.md | 4 - .../references/doc/doc-prerequisites.md | 15 + .../dingtalk-doc/references/doc/doc-read.md | 5 +- .../references/doc/doc-related.md | 8 + .../references/doc/doc-scenarios.md | 22 + .../references/doc/doc-template.md | 54 + .../dingtalk-doc/references/doc/doc-update.md | 9 +- .../dingtalk-doc/references/doc/doc-uri.md | 10 + .../references/doc/doc-version.md | 49 + .../references/doc/doc-workflow.md | 77 + .../references/doc/doc-write-index.md | 7 + .../doc/style/doc-create-workflow.md | 2 +- .../doc/style/doc-update-workflow.md | 2 +- .../references/format/doc-jsonml-index.md | 9 + .../dingtalk-doc/references/intent-guide.md | 19 + .../dingtalk-doc/references/lite-recipes.md | 44 + .../dingtalk-doc/references/url-patterns.md | 20 +- .../scripts/doc_create_and_write.py | 2 +- skills/multi/dingtalk-drive/SKILL.md | 83 +- .../dingtalk-drive/references/drive-index.md | 5 + .../multi/dingtalk-drive/references/drive.md | 194 +- .../dingtalk-drive/references/intent-guide.md | 17 + .../dingtalk-drive/references/lite-recipes.md | 44 + .../dingtalk-drive/scripts/drive_tree_list.py | 37 +- skills/multi/dingtalk-live/SKILL.md | 9 +- skills/multi/dingtalk-mail/SKILL.md | 66 +- .../multi/dingtalk-mail/references/09-mail.md | 31 +- .../references/_common/conventions.md | 44 + .../dingtalk-mail/references/intent-guide.md | 8 + .../dingtalk-mail/references/lite-recipes.md | 45 + .../references/mail-command-index.md | 78 + .../references/mail-common-errors.md | 9 + .../dingtalk-mail/references/mail-context.md | 22 + .../references/mail-default-mailbox-rules.md | 21 + .../mail-draft-directory-commands.md | 259 ++ .../mail-folder-attachment-tag-commands.md | 291 ++ .../dingtalk-mail/references/mail-index.md | 11 + .../dingtalk-mail/references/mail-intent.md | 41 + .../references/mail-message-commands.md | 240 ++ .../dingtalk-mail/references/mail-never-do.md | 15 + .../dingtalk-mail/references/mail-notes.md | 19 + .../references/mail-policy-commands.md | 388 +++ .../references/mail-thread-actions.md | 453 ++++ .../dingtalk-mail/references/mail-workflow.md | 85 + skills/multi/dingtalk-mail/references/mail.md | 1822 +------------ .../scripts/mail_send_with_cc.py | 24 +- .../scripts/mail_unread_summary.py | 25 +- skills/multi/dingtalk-minutes/SKILL.md | 53 +- .../dingtalk-minutes/references/07-minutes.md | 85 +- .../references/10-minutes-speaker-match.md | 145 + .../references/11-minutes-speaker-correct.md | 152 ++ .../references/intent-guide.md | 7 + .../references/lite-recipes.md | 56 + .../references/minutes-commands.md | 889 ++++++ .../references/minutes-error.md | 91 + .../references/minutes-index.md | 13 + .../references/minutes-intent.md | 414 +++ .../references/minutes-overview.md | 121 + .../references/minutes-workflow.md | 89 + .../dingtalk-minutes/references/minutes.md | 2401 +---------------- .../scripts/minutes_extract_todos.py | 68 +- .../scripts/minutes_list_parse.py | 6 +- .../scripts/minutes_recent_summary.py | 13 +- skills/multi/dingtalk-oa/SKILL.md | 19 +- .../multi/dingtalk-oa/references/oa-index.md | 5 + skills/multi/dingtalk-oa/references/oa.md | 128 +- skills/multi/dingtalk-report/SKILL.md | 47 +- .../references/05-reporting.md | 14 +- .../references/_common/conventions.md | 44 + .../references/intent-guide.md | 11 + .../references/lite-recipes.md | 25 + .../references/report-index.md | 6 + .../dingtalk-report/references/report.md | 76 +- .../scripts/report_received_today.py | 184 +- skills/multi/dingtalk-sheet/SKILL.md | 36 +- .../references/_common/conventions.md | 44 + .../dingtalk-sheet/references/intent-guide.md | 12 + .../multi/dingtalk-sheet/references/sheet.md | 2150 +-------------- .../sheet/sheet-batch-operations.md | 68 +- .../references/sheet/sheet-capabilities.md | 7 + .../references/sheet/sheet-chart.md | 58 +- .../sheet/sheet-conditional-format.md | 13 +- .../sheet/sheet-dimension-operations.md | 55 +- .../references/sheet/sheet-dropdown.md | 8 +- .../references/sheet/sheet-edit-rules.md | 12 + .../references/sheet/sheet-export-index.md | 7 + .../references/sheet/sheet-export.md | 16 +- .../references/sheet/sheet-filter-view.md | 5 +- .../references/sheet/sheet-filter.md | 6 +- .../references/sheet/sheet-format-index.md | 7 + .../references/sheet/sheet-formula.md | 117 + .../sheet/sheet-hard-constraints.md | 21 + .../references/sheet/sheet-import.md | 18 + .../references/sheet/sheet-media-image.md | 17 +- .../references/sheet/sheet-nodeid.md | 5 + .../references/sheet/sheet-pivot-table.md | 311 ++- .../references/sheet/sheet-query-index.md | 6 + .../references/sheet/sheet-quick-ref.md | 16 + .../sheet/sheet-range-operations.md | 12 +- .../references/sheet/sheet-read-data.md | 16 +- .../references/sheet/sheet-read-index.md | 6 + .../references/sheet/sheet-ref-index.md | 24 + .../references/sheet/sheet-scope.md | 16 + .../references/sheet/sheet-style-format.md | 6 +- .../references/sheet/sheet-templates.md | 52 + .../references/sheet/sheet-triggers.md | 10 + .../references/sheet/sheet-url-paste.md | 12 + .../references/sheet/sheet-url-to-node-id.md | 10 + .../references/sheet/sheet-visual-index.md | 7 + .../references/sheet/sheet-workbook.md | 54 +- .../references/sheet/sheet-write-data.md | 27 +- .../references/sheet/sheet-write-index.md | 7 + .../dingtalk-sheet/references/url-patterns.md | 26 +- skills/multi/dingtalk-skill/SKILL.md | 35 +- .../multi/dingtalk-skill/references/skill.md | 133 +- skills/multi/dingtalk-todo/SKILL.md | 56 +- .../multi/dingtalk-todo/references/02-task.md | 22 +- .../references/_common/conventions.md | 44 + .../dingtalk-todo/references/intent-guide.md | 11 + .../dingtalk-todo/references/lite-recipes.md | 23 + .../dingtalk-todo/references/todo-index.md | 6 + skills/multi/dingtalk-todo/references/todo.md | 75 +- skills/multi/dingtalk-wiki/SKILL.md | 92 +- .../dingtalk-wiki/references/intent-guide.md | 12 + .../dingtalk-wiki/references/lite-recipes.md | 44 + .../dingtalk-wiki/references/wiki-index.md | 5 + skills/multi/dingtalk-wiki/references/wiki.md | 331 ++- skills/multi/dws-shared/SKILL.md | 11 + .../best_practices/_common/conventions.md | 45 + .../_common/recipe-conventions.md | 67 + .../references/capability-limits.md | 19 + .../dws-shared/references/error-codes.md | 111 + .../dws-shared/references/field-rules.md | 104 + .../dws-shared/references/global-reference.md | 89 + .../dws-shared/references/intent-guide.md | 530 ++++ .../dws-shared/references/shared-index.md | 12 + .../dws-shared/references/url-patterns.md | 129 + test/skill_e2e/skill_e2e_test.go | 137 +- test/skill_static/skill_static_test.go | 256 +- 278 files changed, 14727 insertions(+), 13541 deletions(-) create mode 100644 skills/multi/dingtalk-aisearch/references/intent-guide.md create mode 100644 skills/multi/dingtalk-aisearch/references/lite-recipes.md create mode 100644 skills/multi/dingtalk-aitable/references/aitable/aitable-advperm-index.md create mode 100644 skills/multi/dingtalk-aitable/references/aitable/aitable-attachment-index.md create mode 100644 skills/multi/dingtalk-aitable/references/aitable/aitable-base-index.md create mode 100644 skills/multi/dingtalk-aitable/references/aitable/aitable-command-index.md create mode 100644 skills/multi/dingtalk-aitable/references/aitable/aitable-complex-ops.md create mode 100644 skills/multi/dingtalk-aitable/references/aitable/aitable-context.md create mode 100644 skills/multi/dingtalk-aitable/references/aitable/aitable-dashboard-index.md create mode 100644 skills/multi/dingtalk-aitable/references/aitable/aitable-export-import-index.md create mode 100644 skills/multi/dingtalk-aitable/references/aitable/aitable-field-index.md create mode 100644 skills/multi/dingtalk-aitable/references/aitable/aitable-form-index.md create mode 100644 skills/multi/dingtalk-aitable/references/aitable/aitable-intent.md create mode 100644 skills/multi/dingtalk-aitable/references/aitable/aitable-notes.md create mode 100644 skills/multi/dingtalk-aitable/references/aitable/aitable-record-index.md create mode 100644 skills/multi/dingtalk-aitable/references/aitable/aitable-related.md create mode 100644 skills/multi/dingtalk-aitable/references/aitable/aitable-scripts.md create mode 100644 skills/multi/dingtalk-aitable/references/aitable/aitable-table-index.md create mode 100644 skills/multi/dingtalk-aitable/references/aitable/aitable-uri.md create mode 100644 skills/multi/dingtalk-aitable/references/aitable/aitable-url-to-baseid.md create mode 100644 skills/multi/dingtalk-aitable/references/aitable/aitable-view-index.md create mode 100644 skills/multi/dingtalk-aitable/references/aitable/aitable-workflow-index.md create mode 100644 skills/multi/dingtalk-aitable/references/intent-guide.md create mode 100644 skills/multi/dingtalk-aitable/references/url-patterns.md create mode 100644 skills/multi/dingtalk-attendance/references/attendance-commands.md create mode 100644 skills/multi/dingtalk-attendance/references/attendance-constraints.md create mode 100644 skills/multi/dingtalk-attendance/references/attendance-context.md create mode 100644 skills/multi/dingtalk-attendance/references/attendance-index.md create mode 100644 skills/multi/dingtalk-attendance/references/attendance-intent.md create mode 100644 skills/multi/dingtalk-attendance/references/attendance-notes.md create mode 100644 skills/multi/dingtalk-attendance/references/attendance-routing.md create mode 100644 skills/multi/dingtalk-attendance/references/attendance-scripts.md create mode 100644 skills/multi/dingtalk-attendance/references/attendance-workflow.md create mode 100644 skills/multi/dingtalk-attendance/references/intent-guide.md create mode 100644 skills/multi/dingtalk-calendar/references/calendar-index.md create mode 100644 skills/multi/dingtalk-calendar/references/intent-guide.md create mode 100644 skills/multi/dingtalk-calendar/references/lite-recipes.md create mode 100644 skills/multi/dingtalk-chat/references/chat-admin-commands.md create mode 100644 skills/multi/dingtalk-chat/references/chat-commands.md create mode 100644 skills/multi/dingtalk-chat/references/chat-composite.md create mode 100644 skills/multi/dingtalk-chat/references/chat-context.md create mode 100644 skills/multi/dingtalk-chat/references/chat-conversation-commands.md create mode 100644 skills/multi/dingtalk-chat/references/chat-group-commands.md create mode 100644 skills/multi/dingtalk-chat/references/chat-index.md create mode 100644 skills/multi/dingtalk-chat/references/chat-intent.md create mode 100644 skills/multi/dingtalk-chat/references/chat-message-file-commands.md create mode 100644 skills/multi/dingtalk-chat/references/chat-notes.md create mode 100644 skills/multi/dingtalk-chat/references/chat-related.md create mode 100644 skills/multi/dingtalk-chat/references/chat-scripts.md create mode 100644 skills/multi/dingtalk-chat/references/chat-workflow.md create mode 100644 skills/multi/dingtalk-chat/references/intent-guide.md create mode 100644 skills/multi/dingtalk-chat/references/lite-recipes.md create mode 100644 skills/multi/dingtalk-contact/references/_common/conventions.md create mode 100644 skills/multi/dingtalk-contact/references/contact-index.md create mode 100644 skills/multi/dingtalk-contact/references/intent-guide.md create mode 100644 skills/multi/dingtalk-contact/references/lite-recipes.md create mode 100644 skills/multi/dingtalk-dev/references/dev-index.md delete mode 100644 skills/multi/dingtalk-devapp/references/event.md create mode 100644 skills/multi/dingtalk-devdoc/references/intent-guide.md create mode 100644 skills/multi/dingtalk-ding/references/intent-guide.md create mode 100644 skills/multi/dingtalk-ding/references/lite-recipes.md create mode 100644 skills/multi/dingtalk-doc/references/doc/doc-command-index.md create mode 100644 skills/multi/dingtalk-doc/references/doc/doc-context.md create mode 100644 skills/multi/dingtalk-doc/references/doc/doc-extra-index.md create mode 100644 skills/multi/dingtalk-doc/references/doc/doc-help-query.md create mode 100644 skills/multi/dingtalk-doc/references/doc/doc-info-index.md create mode 100644 skills/multi/dingtalk-doc/references/doc/doc-intent.md create mode 100644 skills/multi/dingtalk-doc/references/doc/doc-media-index.md create mode 100644 skills/multi/dingtalk-doc/references/doc/doc-prerequisites.md create mode 100644 skills/multi/dingtalk-doc/references/doc/doc-related.md create mode 100644 skills/multi/dingtalk-doc/references/doc/doc-scenarios.md create mode 100644 skills/multi/dingtalk-doc/references/doc/doc-template.md create mode 100644 skills/multi/dingtalk-doc/references/doc/doc-uri.md create mode 100644 skills/multi/dingtalk-doc/references/doc/doc-version.md create mode 100644 skills/multi/dingtalk-doc/references/doc/doc-workflow.md create mode 100644 skills/multi/dingtalk-doc/references/doc/doc-write-index.md create mode 100644 skills/multi/dingtalk-doc/references/format/doc-jsonml-index.md create mode 100644 skills/multi/dingtalk-doc/references/intent-guide.md create mode 100644 skills/multi/dingtalk-doc/references/lite-recipes.md create mode 100644 skills/multi/dingtalk-drive/references/drive-index.md create mode 100644 skills/multi/dingtalk-drive/references/intent-guide.md create mode 100644 skills/multi/dingtalk-drive/references/lite-recipes.md create mode 100644 skills/multi/dingtalk-mail/references/_common/conventions.md create mode 100644 skills/multi/dingtalk-mail/references/intent-guide.md create mode 100644 skills/multi/dingtalk-mail/references/lite-recipes.md create mode 100644 skills/multi/dingtalk-mail/references/mail-command-index.md create mode 100644 skills/multi/dingtalk-mail/references/mail-common-errors.md create mode 100644 skills/multi/dingtalk-mail/references/mail-context.md create mode 100644 skills/multi/dingtalk-mail/references/mail-default-mailbox-rules.md create mode 100644 skills/multi/dingtalk-mail/references/mail-draft-directory-commands.md create mode 100644 skills/multi/dingtalk-mail/references/mail-folder-attachment-tag-commands.md create mode 100644 skills/multi/dingtalk-mail/references/mail-index.md create mode 100644 skills/multi/dingtalk-mail/references/mail-intent.md create mode 100644 skills/multi/dingtalk-mail/references/mail-message-commands.md create mode 100644 skills/multi/dingtalk-mail/references/mail-never-do.md create mode 100644 skills/multi/dingtalk-mail/references/mail-notes.md create mode 100644 skills/multi/dingtalk-mail/references/mail-policy-commands.md create mode 100644 skills/multi/dingtalk-mail/references/mail-thread-actions.md create mode 100644 skills/multi/dingtalk-mail/references/mail-workflow.md create mode 100644 skills/multi/dingtalk-minutes/references/10-minutes-speaker-match.md create mode 100644 skills/multi/dingtalk-minutes/references/11-minutes-speaker-correct.md create mode 100644 skills/multi/dingtalk-minutes/references/intent-guide.md create mode 100644 skills/multi/dingtalk-minutes/references/lite-recipes.md create mode 100644 skills/multi/dingtalk-minutes/references/minutes-commands.md create mode 100644 skills/multi/dingtalk-minutes/references/minutes-error.md create mode 100644 skills/multi/dingtalk-minutes/references/minutes-index.md create mode 100644 skills/multi/dingtalk-minutes/references/minutes-intent.md create mode 100644 skills/multi/dingtalk-minutes/references/minutes-overview.md create mode 100644 skills/multi/dingtalk-minutes/references/minutes-workflow.md create mode 100644 skills/multi/dingtalk-oa/references/oa-index.md create mode 100644 skills/multi/dingtalk-report/references/_common/conventions.md create mode 100644 skills/multi/dingtalk-report/references/intent-guide.md create mode 100644 skills/multi/dingtalk-report/references/lite-recipes.md create mode 100644 skills/multi/dingtalk-report/references/report-index.md create mode 100644 skills/multi/dingtalk-sheet/references/_common/conventions.md create mode 100644 skills/multi/dingtalk-sheet/references/intent-guide.md create mode 100644 skills/multi/dingtalk-sheet/references/sheet/sheet-capabilities.md create mode 100644 skills/multi/dingtalk-sheet/references/sheet/sheet-edit-rules.md create mode 100644 skills/multi/dingtalk-sheet/references/sheet/sheet-export-index.md create mode 100644 skills/multi/dingtalk-sheet/references/sheet/sheet-format-index.md create mode 100644 skills/multi/dingtalk-sheet/references/sheet/sheet-formula.md create mode 100644 skills/multi/dingtalk-sheet/references/sheet/sheet-hard-constraints.md create mode 100644 skills/multi/dingtalk-sheet/references/sheet/sheet-import.md create mode 100644 skills/multi/dingtalk-sheet/references/sheet/sheet-nodeid.md create mode 100644 skills/multi/dingtalk-sheet/references/sheet/sheet-query-index.md create mode 100644 skills/multi/dingtalk-sheet/references/sheet/sheet-quick-ref.md create mode 100644 skills/multi/dingtalk-sheet/references/sheet/sheet-read-index.md create mode 100644 skills/multi/dingtalk-sheet/references/sheet/sheet-ref-index.md create mode 100644 skills/multi/dingtalk-sheet/references/sheet/sheet-scope.md create mode 100644 skills/multi/dingtalk-sheet/references/sheet/sheet-templates.md create mode 100644 skills/multi/dingtalk-sheet/references/sheet/sheet-triggers.md create mode 100644 skills/multi/dingtalk-sheet/references/sheet/sheet-url-paste.md create mode 100644 skills/multi/dingtalk-sheet/references/sheet/sheet-url-to-node-id.md create mode 100644 skills/multi/dingtalk-sheet/references/sheet/sheet-visual-index.md create mode 100644 skills/multi/dingtalk-sheet/references/sheet/sheet-write-index.md create mode 100644 skills/multi/dingtalk-todo/references/_common/conventions.md create mode 100644 skills/multi/dingtalk-todo/references/intent-guide.md create mode 100644 skills/multi/dingtalk-todo/references/lite-recipes.md create mode 100644 skills/multi/dingtalk-todo/references/todo-index.md create mode 100644 skills/multi/dingtalk-wiki/references/intent-guide.md create mode 100644 skills/multi/dingtalk-wiki/references/lite-recipes.md create mode 100644 skills/multi/dingtalk-wiki/references/wiki-index.md create mode 100644 skills/multi/dws-shared/references/best_practices/_common/conventions.md create mode 100644 skills/multi/dws-shared/references/best_practices/_common/recipe-conventions.md create mode 100644 skills/multi/dws-shared/references/capability-limits.md create mode 100644 skills/multi/dws-shared/references/error-codes.md create mode 100644 skills/multi/dws-shared/references/field-rules.md create mode 100644 skills/multi/dws-shared/references/global-reference.md create mode 100644 skills/multi/dws-shared/references/intent-guide.md create mode 100644 skills/multi/dws-shared/references/shared-index.md create mode 100644 skills/multi/dws-shared/references/url-patterns.md diff --git a/skills/multi/dingtalk-agoal/SKILL.md b/skills/multi/dingtalk-agoal/SKILL.md index 6f501853..5a180a7f 100644 --- a/skills/multi/dingtalk-agoal/SKILL.md +++ b/skills/multi/dingtalk-agoal/SKILL.md @@ -1,6 +1,6 @@ --- name: dingtalk-agoal -description: 钉钉 Agoal 目标管理。Use when 用户说 目标管理/Agoal/战略解码/经营合约/计分卡/OKR/目标模板/周月报提交统计/跟催。Distinct from dingtalk-todo(待办任务)、dingtalk-report(日志)。命令前缀:dws agoal。 +description: 钉钉 Agoal 目标管理与经营目标跟进。Use when 用户说目标管理/战略解码/经营合约/计分卡/用户目标/OKR拆解/更新目标/查看经营合约/目标进展/目标负责人/周月报统计/周报提交情况/跟催/迟交/未提交。Distinct from dingtalk-todo(个人任务待办)、dingtalk-report(日报周报内容)、dingtalk-oa(审批流)。命令前缀:dws agoal。 cli_version: ">=0.2.14" metadata: category: product @@ -10,34 +10,32 @@ metadata: - dws --- -# 钉钉 Agoal Skill +# 钉钉目标管理 Skill > 🧪 **EXPERIMENTAL · 试验版 / Preview** — multi 模式当前未达 stable 标准。全部 dingtalk-* skill 已通过 dispatch verifier,但接口、命名、跨 skill 引用后续可能调整;生产 / 共享环境请优先使用 mono 模式(`dws skill setup --mode mono`)。问题请提 issue 反馈。 -> **PREREQUISITE:** Read the `dws-shared` skill first for auth, global flags, product routing, URL preflight, error codes, and safety rules. The `dws` binary must be on PATH. +## 前置条件 — 执行操作前必读 + +> **`use_skill(dws-shared)`** — 认证、全局参数(`--format json` / `--yes`)、错误码、URL 模板、跨产品消歧、安全规则与 capability 边界。**执行任何 `dws` 命令前先读;** 单产品的清晰命令可直接用本 skill。 -> ⚠️ **命令可用性以当前 dws 二进制为准**。服务发现已下线,本文档随内置 skill 发布;如果 `dws --help` 不存在,说明当前版本未暴露该命令。若命令存在但调用失败,请按错误中的 endpoint 或 tool 提示确认静态端点目录和后端工具注册。实际调用前可用 `dws --help` 或 `--dry-run` 验证。 - > 命令参考:[agoal.md](references/agoal.md)。 ## 意图表 | 用户说 | 命令 | |--------|------| -| "查战略解码 / OGSM" | `dws agoal strategy list --scope-type --scope-id ` | -| "看战略解码详情" | `dws agoal strategy detail --profile-id ` | -| "更新战略解码" | 先 detail,基于完整数据修改后 `dws agoal strategy update ...` | -| "查经营合约 / KPI合约" | `dws agoal contract list/detail/fields` | -| "更新经营合约" | 先 detail,基于完整 dimensions 修改后 `dws agoal contract update ...` | -| "查计分卡" | `dws agoal scorecard detail --selected-time --dept-id ` | -| "我的目标 / 个人目标" | `dws agoal user rules` → `dws agoal user objectives` | -| "周月报提交统计 / 跟催 / 迟交 / 未提交" | `dws agoal report list-statistics` → `dws agoal report submit-detail` | -| "目标模板" | `dws agoal obj-template list/create-or-update` | +| "查战略解码 / 战略列表" | `dws agoal strategy list` | +| "查战略详情" | `dws agoal strategy detail --profile-id ` | +| "更新战略解码" | 先 `strategy detail` 获取完整内容,再 `strategy update` 覆盖更新 | +| "查经营合约" | `dws agoal contract list` / `contract detail` | +| "更新经营合约" | 先 `contract detail` 获取完整内容,再 `contract update` 覆盖更新 | +| "查计分卡" | `dws agoal scorecard detail` / `scorecard entity-detail` | +| "更新计分卡" | 先查详情,再按 [agoal.md](references/agoal.md) 覆盖更新 | +| "查周月报统计/提交情况/跟催" | `dws agoal report list-statistics` / `report submit-detail` | -## 写操作硬约束 +## 硬约束 -- `strategy update` / `contract update` / `scorecard update` / `obj-template create-or-update` 都是覆盖式写入。 -- 必须先读取现有详情或模板列表,在原数据基础上修改;禁止只传局部字段覆盖。 -- 执行前必须向用户展示目标对象、修改字段和最终命令参数摘要,并等待确认。 +- `strategy update`、`contract update`、`scorecard update` 都是覆盖式更新,必须先查询详情,在返回数据基础上修改后再提交。 +- 所有命令带 `--format json`;涉及写操作时回查确认。 diff --git a/skills/multi/dingtalk-agoal/references/agoal.md b/skills/multi/dingtalk-agoal/references/agoal.md index 1f1ad332..029166c2 100644 --- a/skills/multi/dingtalk-agoal/references/agoal.md +++ b/skills/multi/dingtalk-agoal/references/agoal.md @@ -14,64 +14,260 @@ Agoal 是钉钉目标管理工具,支持战略解码、经营合约、计分 |------|------|----------|------| | `strategy list` | 获取战略解码列表 | `--scope-type` `--scope-id` | scopeType: DEPT/PERSONAL;scope-id 为 scope-type 对应的部门 id 或用户 id | | `strategy detail` | 获取战略解码详情 | `--profile-id` | 根据战略解码 id 查询 | -| `strategy update` | 更新战略解码 | `--profile-id` `--content` | 覆盖逻辑,必须基于查询返回的老数据修改后再传入;`--content` 为 JSON 数组 | +| `strategy update` | 更新战略解码 | `--profile-id` `--content` | **覆盖逻辑,必须基于查询返回的老数据修改后再传入**;`--content` 为 JSON 数组 | + +> **`strategy update` 是覆盖式更新**:一定要先 `strategy detail` 获取完整数据,在原数据基础上修改后再传入,会根据战略解码id进行对应的修改。 + +#### strategy update --content 实体字段说明 + +| 字段 | 说明 | +|------|------| +| `id` | 实体 id | +| `title` | 标题对象,如 `{"title":"标题文本"}` | +| `linkEntityId` | 所属实体 ID(查询接口中有值时必须回传) | +| `entityType` | 类型枚举:`OGSM_OBJECTIVE`(目的)、`OGSM_GOAL`(目标)、`OGSM_STRATEGY`(策略)、`OGSM_MEASUREMENT`(衡量标准)、`OGSM_TACTICS`(行动方案) | +| `status` | 状态枚举:`NORMAL`(正常)、`PRE_PUBLISH_THEN_UPDATE`(预发布更新)、`PRE_PUBLISH_THEN_CREATE`(预发布新增)、`PRE_PUBLISH_THEN_DELETE`(预发布删除) | +| `supporters` | 承接人数组 `[{type, dingId, staffId}]`;type: `USER`/`DEPARTMENT`;staffId 在 type=USER 时必填 | +| `indicators` | 关键指标 id 字符串数组 | +| `linkSources` | 资源关联数组 `[{id, linkType, linkId, source, objectId, keyResultId}]`;linkType: project/task/campaign/product/doc/standard;source: teambition | +| `executors` | 人员 dingId 字符串数组 | +| `teams` | 部门 dingId 字符串数组 | ### contract (经营合约管理) | 命令 | 用途 | 必填参数 | 备注 | |------|------|----------|------| | `contract list` | 获取经营合约列表 | `--scope-type` `--scope-id` | scopeType: DEPT/PERSONAL;scope-id 为 scope-type 对应的部门 id 或用户 id | -| `contract fields` | 获取经营合约字段列表 | - | 获取组织下经营合约的字段配置 | +| `contract fields` | 获取经营合约字段列表 | — | 获取组织下经营合约的字段配置 | | `contract detail` | 获取经营合约详情 | `--contract-id` | 根据合约 id 查询 | -| `contract update` | 更新经营合约 | `--contract-id` `--dimensions` | 覆盖逻辑;可选 `--audit-config`、`--objective-template` | +| `contract update` | 更新经营合约 | `--contract-id` `--dimensions` | **覆盖逻辑**;可选 `--audit-config`、`--objective-template` | + +> **`contract update` 同样是覆盖式更新**:必须基于 `contract detail` 返回的数据修改后再传入。 + +#### contract update --dimensions 维度字段说明 + +| 字段 | 说明 | +|------|------| +| `id` | 维度 id | +| `title` | 维度名称 | +| `description` | 维度描述 | +| `weight` | 维度权重 | +| `objectives` | 目标列表 | +| `dimensionConfig` | 维度配置 | +| `children` | 子维度列表 | + +#### contract update 可选参数 + +| 参数 | 说明 | 示例 | +|------|------|------| +| `--audit-config` | 审批配置 JSON | `{"needAudit":true,"processTemplateId":"TPL_ID"}` | +| `--objective-template` | 合约模板 JSON | `{"id":"TPL_ID","title":"模板名称"}` | ### scorecard (计分卡管理) | 命令 | 用途 | 必填参数 | 备注 | |------|------|----------|------| -| `scorecard detail` | 获取计分卡详情 | `--selected-time` `--dept-id` | selectedTime 为 ISO-8601 字符串 | +| `scorecard detail` | 获取计分卡详情 | `--selected-time` `--dept-id` | selectedTime 为 ISO-8601 字符串,如 `"2026-01-01T00:00:00+08:00"` | | `scorecard entity-detail` | 获取计分卡实体详情 | `--sc-id` `--entity-id` | 根据计分卡 id 和实体 id 查询 | | `scorecard update` | 更新计分卡 | `--dept-id` `--selected-time` `--id` `--tracking-period-type` `--content` | trackingPeriodType: MONTHLY/QUARTERLY | -### user / report / obj-template +#### selectedTime 时间说明 -| 命令 | 用途 | 必填参数 | -|------|------|----------| -| `user rules` | 获取用户规则周期列表 | - | -| `user objectives` | 查询用户目标列表 | `--user-id` `--rule-id` `--period-ids` | -| `report list-statistics` | 获取周月报数据跟催列表 | - | -| `report submit-detail` | 获取周月报规则提交详情 | `--template-id` `--submit-state` | -| `obj-template list` | 获取目标模板列表 | - | -| `obj-template create-or-update` | 新增或更新目标模板 | `--dimensions` | +`--selected-time` 接受 ISO-8601 字符串,传入对应周期起始时刻: + +- **2026年** → `"2026-01-01T00:00:00+08:00"` +- **2026年5月** → `"2026-05-01T00:00:00+08:00"` + +#### scorecard update --content 维度字段说明 + +| 字段 | 说明 | +|------|------| +| `id` | 维度 id | +| `title` | 维度名称 | +| `items` | 指标或关键事项列表 | + +items 每项包含: + +| 字段 | 说明 | +|------|------| +| `id` | 实体 id | +| `title` | 名称 | +| `reference` | 参考信息 | +| `start` | 起始值 | +| `target` | 目标值 | +| `executors` | 负责人列表,每项包含 `openId` | + +#### trackingPeriodType 枚举 + +| 值 | 说明 | +|------|------| +| `MONTHLY` | 月度追踪 | +| `QUARTERLY` | 季度追踪 | + +### user (用户目标管理) + +| 命令 | 用途 | 必填参数 | 备注 | +|------|------|----------|------| +| `user rules` | 获取用户规则周期列表 | — | 可选 `--user-id`,不传则默认取操作人自己 | +| `user objectives` | 查询用户目标列表 | `--user-id` `--rule-id` `--period-ids` | `--period-ids` 为逗号分隔的周期 id 列表 | + +### obj-template (目标模板管理) + +| 命令 | 用途 | 必填参数 | 备注 | +|------|------|----------|------| +| `obj-template list` | 获取目标模板列表 | — | 可选 `--keyword` 搜索关键词、`--page` 页码、`--page-size` 每页数量 | +| `obj-template create-or-update` | 新增或更新目标模板 | `--dimensions` | **覆盖逻辑**;新增时 `--title` 必填;更新时 `--template-id` 必填,dimensions 必须基于老数据修改 | + +> **`obj-template create-or-update` 同样是覆盖式更新**:更新时必须基于 `obj-template list` 返回的数据修改后再传入。新增时建议先参考已存在的模板数据再构建 dimensions。 + +#### obj-template create-or-update 参数说明 + +| 参数 | 说明 | 类型 | +|------|------|------| +| `--template-id` | 模板 id(更新时必填) | string | +| `--title` | 模板标题(新增时必填) | string | +| `--objective-weight` | 是否启用目标权重 | bool | +| `--dimension-weight` | 是否启用维度权重 | bool | +| `--compute-by-weight` | 维度是否参与计算 | bool | +| `--dimensions` | 模板关联的维度 JSON 字符串 | string | + +### report (周月报管理) + +| 命令 | 用途 | 必填参数 | 备注 | +|------|------|----------|------| +| `report list-statistics` | 获取周月报数据跟催列表 | — | 返回各规则的人员提交情况统计(按时/迟交/未提交人数);可选 `--keyword` 搜索规则名称 | +| `report submit-detail` | 获取周月报规则提交详情 | `--template-id` `--submit-state` | submitState: `ON_TIME`(按时)/`LATE`(迟交)/`NOT_SUBMITTED`(未提交);可选 `--query-date`(ISO-8601)、`--page`、`--page-size`、`--keyword`(搜索员工名称) | + +#### report submit-detail --submit-state 枚举 + +| 值 | 说明 | +|------|------| +| `ON_TIME` | 按时提交 | +| `LATE` | 迟交 | +| `NOT_SUBMITTED` | 未提交 | ## 意图判断 -- "战略解码/战略目标/OGSM" → `strategy list/detail/update` -- "经营合约/合约/KPI合约" → `contract list/fields/detail/update` -- "计分卡/scorecard/绩效看板" → `scorecard detail/entity-detail/update` -- "目标/OKR/我的目标/个人目标" → `user rules/objectives` -- "目标模板/模板管理" → `obj-template list/create-or-update` -- "周月报/周报统计/提交情况/跟催/迟交/未提交" → `report list-statistics/submit-detail` +用户说"战略解码/战略目标/OGSM": +- 查看/列表 → `strategy list` +- 详情 → `strategy detail` +- 修改/更新 → `strategy update`(先查后改) -## 常用命令 +用户说"经营合约/合约/KPI合约": +- 查看/列表 → `contract list` +- 字段配置 → `contract fields` +- 详情 → `contract detail` +- 修改/更新 → `contract update`(先查后改) + +用户说"计分卡/scorecard/绩效看板": +- 查看详情 → `scorecard detail` +- 实体详情 → `scorecard entity-detail` +- 修改/更新 → `scorecard update` + +用户说"目标/OKR/我的目标/个人目标": +- 规则周期 → `user rules` +- 目标列表 → `user objectives` + +用户说"目标模板/模板管理": +- 查看模板列表 → `obj-template list` +- 新增模板 → `obj-template create-or-update --title "模板名称"` +- 更新模板 → `obj-template create-or-update --template-id TPL_ID` + +用户说"周月报/周报统计/提交情况/跟催/迟交/未提交": +- 查看提交统计列表 → `report list-statistics` +- 查看某规则的提交详情(按时/迟交/未提交人员明细) → `report submit-detail` + +## 核心工作流 ```bash -dws agoal strategy list --scope-type DEPT --scope-id DEPT_ID --format json -dws agoal strategy detail --profile-id PROFILE_ID --format json -dws agoal contract list --scope-type PERSONAL --scope-id USER_ID --format json -dws agoal contract fields --format json -dws agoal contract detail --contract-id CONTRACT_ID --format json -dws agoal scorecard detail --selected-time "2026-01-01T00:00:00+08:00" --dept-id DEPT_ID --format json -dws agoal user rules --user-id USER_ID --format json -dws agoal user objectives --user-id USER_ID --rule-id RULE_ID --period-ids "period1,period2" --format json -dws agoal report list-statistics --format json -dws agoal report submit-detail --template-id TPL_ID --submit-state ON_TIME --format json -dws agoal obj-template list --keyword "业绩" --format json +# 1. 查看战略解码列表(按部门) +dws agoal strategy list --scope-type DEPT --scope-id DEPT_ID + +# 1.1 查看战略解码列表(按个人) +dws agoal strategy list --scope-type PERSONAL --scope-id USER_ID + +# 2. 查看战略解码详情 +dws agoal strategy detail --profile-id PROFILE_ID + +# 3. 更新战略解码(基于详情返回的数据修改后传入) +dws agoal strategy update --profile-id PROFILE_ID \ + --content '[{"id":"entity1","title":{"title":"新目标"},"entityType":"OGSM_OBJECTIVE","status":"NORMAL","executors":["dingId1"]}]' + +# 4. 查看经营合约列表(按个人) +dws agoal contract list --scope-type PERSONAL --scope-id USER_ID + +# 4.1 查看经营合约列表(按部门) +dws agoal contract list --scope-type DEPT --scope-id DEPT_ID + +# 5. 查看经营合约字段列表 +dws agoal contract fields + +# 6. 查看经营合约详情 +dws agoal contract detail --contract-id CONTRACT_ID + +# 7 更新经营合约(基于详情返回的数据修改后传入) +dws agoal contract update --contract-id CONTRACT_ID \ + --dimensions '[{"id":"dim1","title":"维度名称","objectives":[...]}]' + +# 8. 查看计分卡详情 +dws agoal scorecard detail --selected-time "2025-01-01T00:00:00+08:00" --dept-id DEPT_ID + +# 9. 查看计分卡实体详情 +dws agoal scorecard entity-detail --sc-id SC_ID --entity-id ENTITY_ID + +# 10. 更新计分卡 +dws agoal scorecard update --dept-id DEPT_ID --selected-time "2025-01-01T00:00:00+08:00" --id SC_ID --tracking-period-type MONTHLY --content '[{"id":"dim1","title":"业绩","items":[{"id":"item1","title":"收入","target":"100"}]}]' + +# 11. 查看用户规则 → 提取 ruleId 和 periodId +dws agoal user rules --user-id USER_ID + +# 12. 查看用户目标 +dws agoal user objectives --user-id USER_ID --rule-id RULE_ID --period-ids "period1,period2" + +# 13. 查看周月报提交统计列表 +dws agoal report list-statistics + +# 13.1 按关键词搜索规则 +dws agoal report list-statistics --keyword "周报规则" + +# 14. 查看某规则的按时提交详情 +dws agoal report submit-detail --template-id TPL_ID --submit-state ON_TIME + +# 14.1 查看迟交详情(带分页和日期) +dws agoal report submit-detail --template-id TPL_ID --submit-state LATE --query-date "2026-06-18T00:00:00+08:00" --page 1 --page-size 20 + +# 15. 获取目标模板列表 +dws agoal obj-template list + +# 15.1 搜索目标模板 +dws agoal obj-template list --keyword "业绩" + +# 16. 新增目标模板 +dws agoal obj-template create-or-update --title "业绩模板" --objective-weight --dimension-weight --dimensions '[...]' + +# 16.1 更新目标模板 +dws agoal obj-template create-or-update --template-id TPL_ID --title "业绩模板" --dimensions '[...]' ``` -## 安全规则 +## 上下文传递表 -- 所有 update / create-or-update 命令都是覆盖逻辑:必须先用对应 detail/list 查询完整数据,在原数据基础上修改后再传入。 -- 写入类命令执行前必须展示变更摘要并等待用户确认。 -- `--selected-time` / `--query-date` 接受 ISO-8601 字符串。 -- `--period-ids` 为逗号分隔字符串。 +| 操作 | 从返回中提取 | 用于 | +|------|-------------|------| +| `strategy list` | `profileId` | `strategy detail` / `strategy update` 的 `--profile-id` | +| `strategy detail` | 完整实体数据 | `strategy update` 的 `--content`(基于此修改) | +| `contract list` | `contractId` | `contract detail` / `contract update` 的 `--contract-id` | +| `contract detail` | 完整维度数据 | `contract update` 的 `--dimensions`(基于此修改) | +| `scorecard detail` | `scId`、`entityId` | `scorecard entity-detail` / `scorecard update` 的 `--id` | +| `user rules` | `ruleId`、`periodIds` | `user objectives` 的 `--rule-id` `--period-ids` | +| `report list-statistics` | `templateId`(列表项中) | `report submit-detail` 的 `--template-id` | +| `obj-template list` | `templateId` | `obj-template create-or-update` 的 `--template-id`(更新时) | + +## 注意事项 + +- **所有 update 命令都是覆盖逻辑**:必须先用对应的 detail/list 查询到完整数据,在原数据基础上修改后再传入,否则未传入的数据会被删除 +- 所有命令支持可选参数 `--request-id` +- `--scope-type` 仅支持 `DEPT`(按部门)和 `PERSONAL`(按个人)两种 +- `--selected-time` 接受 ISO-8601 字符串(如 `"2026-01-01T00:00:00+08:00"`) +- `--period-ids` 为逗号分隔的字符串,如 `"period1,period2"` +- `report submit-detail` 的 `--query-date` 接受 ISO-8601 字符串(如 `"2026-06-18T00:00:00+08:00"`),不传则默认当天 +- `report submit-detail` 的 `--page` 和 `--page-size` 默认为 1 和 10,不传时由服务端使用默认值 diff --git a/skills/multi/dingtalk-aisearch/SKILL.md b/skills/multi/dingtalk-aisearch/SKILL.md index e22c3dd8..00681136 100644 --- a/skills/multi/dingtalk-aisearch/SKILL.md +++ b/skills/multi/dingtalk-aisearch/SKILL.md @@ -1,6 +1,6 @@ --- name: dingtalk-aisearch -description: AI 搜问 - 搜人首选入口(按姓名/部门/职位/职责/上下级/手机号/工号维度)。Use when 用户说 找同事/找人/谁负责XX/XX的负责人是谁/查上级/查下级/团队成员/XX工号是谁/XX手机号。Distinct from dingtalk-contact(精确按 userId 查详情)。命令前缀:dws aisearch。 +description: AI 搜问——跨源语义检索/发现(只做找到/定位,不做读写;具体读写各归 doc/report/mail/contact)。Use when 找同事/谁负责/查上下级/查工号手机号、跨文档消息邮件听记的内容检索、"我发过/收到过"行为回溯。模糊找人拿 userId 后精确查走 dingtalk-contact。命令前缀:dws aisearch。 cli_version: ">=0.2.14" metadata: category: product @@ -10,17 +10,16 @@ metadata: - dws --- -# 钉钉 AI 搜问(搜人)Skill +# 钉钉 AI 搜问 Skill > 🧪 **EXPERIMENTAL · 试验版 / Preview** — multi 模式当前未达 stable 标准。全部 dingtalk-* skill 已通过 dispatch verifier,但接口、命名、跨 skill 引用后续可能调整;生产 / 共享环境请优先使用 mono 模式(`dws skill setup --mode mono`)。问题请提 issue 反馈。 -> **PREREQUISITE:** Read the `dws-shared` skill first for auth, global flags, product routing, URL preflight, error codes, and safety rules. The `dws` binary must be on PATH. +## 前置条件 — 执行操作前必读 + +> **`use_skill(dws-shared)`** — 认证、全局参数(`--format json` / `--yes`)、错误码、URL 模板、跨产品消歧、安全规则与 capability 边界。**执行任何 `dws` 命令前先读;** 单产品的清晰命令可直接用本 skill。 -> ⚠️ **命令可用性以当前 dws 二进制为准**。服务发现已下线,本文档随内置 skill 发布;如果 `dws --help` 不存在,说明当前版本未暴露该命令。若命令存在但调用失败,请按错误中的 endpoint 或 tool 提示确认静态端点目录和后端工具注册。实际调用前可用 `dws --help` 或 `--dry-run` 验证。 - - > 命令参考:[aisearch.md](references/aisearch.md)。 ## 意图表 @@ -32,8 +31,44 @@ metadata: | "张三的上级 / 下级" | `dws aisearch person --keyword "张三" --dimension supervisor`(或 `subordinate`) | | "X 部门有哪些人" | `dws aisearch person --keyword "<部门>" --dimension department` | | "工号 12345 是谁 / 138xxxx 手机号是谁" | `dws aisearch person --keyword "<工号>" --dimension jobNumber` / `dws aisearch person --keyword "<手机号>" --dimension phone` | +| "最近 OKR 相关邮件 / 项目相关文档" | `dws aisearch enterprise --queries "<主题>" --types mail/document --time-range "<时间>"` | +| "我发过/创建过/分享过/收到过什么" | `dws aisearch behavior --queries "<主题>" --behavior-type <动作> --direction <方向>` | -## 评测高频硬约束 +## 标准 SOP(必遵流程) + +> 命中以下意图**必须**按对应 SOP 顺序执行;**禁止**跳步、替换命令、编造 flag/ID。每条命令必须带 `--format json`,执行后必须按"解析"步取真实字段,不得凭返回结构猜测。 + +### SOP-1 搜人 → 拿 userId(search-person) + +**触发**:找人/谁负责/查上下级/部门成员/工号或手机号反查。 + +1. **定维度(必须)**:姓名→`name`、"谁负责 XX"→`duty`、部门成员→`department`、上级/下级→`supervisor`/`subordinate`、工号→`jobNumber`、手机号→`phone`;不确定→`all`。`--keyword` 必须按用户原文**完整保真**,切勿截断、改昵称、扩同音字。 +2. **执行(必须)**:`dws aisearch person --keyword "<完整值>" --dimension <维度> --format json`。 +3. **解析(必须)**:从 JSON 取 `userId` / `openDingTalkId`;**多候选必须输出让用户选,禁止默认取第一个、禁止编造**未返回的人员字段。 +4. **衔接(必须)**:要邮箱/部门/职位/主管等详情 → 切 `dingtalk-contact` 执行 `dws contact user get --ids --format json`;发消息 → `dingtalk-chat`;发 DING → `dingtalk-ding`。 +5. **失败(必须)**:未命中最多换 1 个维度重试一次(如 `name`→`department`/`jobNumber`/`phone`),仍保留完整目标值;仍无果**必须如实告知**。 + +**禁止**:用半截姓名扩大搜索、跳过 `--format json`、取首个候选、凭空补全人员信息。 + +### SOP-2 跨源搜内容(search-content) + +**触发**:跨文档/邮件/消息按主题找内容。 + +1. **执行(必须)**:`dws aisearch enterprise --queries "<主题>" --types --time-range "<时间>" --format json`;多主题逗号分隔。 +2. **衔接(必须)**:按命中来源切下游读写——文档→`dingtalk-doc`、邮件→`dingtalk-mail`、消息→`dingtalk-chat`。**aisearch 只负责"找到",不做读写。** + +**禁止**:把 aisearch 当作读写入口、跳过下游 skill 直接改数据。 + +### SOP-3 行为回溯(search-behavior) + +**触发**:"我发过/收到过/创建过/分享过什么"。 + +1. **执行(必须)**:`dws aisearch behavior --queries "<主题>" --behavior-type <动作> --direction <方向> --format json`。 +2. **衔接(必须)**:按记录类型切对应 skill 操作;aisearch 不做读写。 + +**禁止**:编造行为结果、跳过 `--format json`。 + +## 高频硬约束 - 搜索目标必须完整保真:姓名、工号、手机号、部门名按用户原文完整传入 `--keyword`,严禁自行截断、拆字、改昵称或扩展同音字。 - 首次未命中时最多换维度重试一次(如 name → department/jobNumber/phone),仍必须保留完整目标值;不要用半截姓名扩大搜索。 @@ -46,3 +81,6 @@ metadata: - 拿到 userId 后查详情 / 部门 → 切到 `dingtalk-contact` - 拿到 userId 发消息 → 切到 `dingtalk-chat` - 拿到 userId 发 DING → 切到 `dingtalk-ding` +## 局部意图与 Recipe + +- [局部意图消歧](references/intent-guide.md);[Lite Recipe](references/lite-recipes.md)。 diff --git a/skills/multi/dingtalk-aisearch/references/aisearch.md b/skills/multi/dingtalk-aisearch/references/aisearch.md index f7f33544..8a21aa13 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,= `meta.staffId`/`meta.jobNumber`)、`author`(真实姓名,= `meta.name`);`title` 是花名/显示名,不一定等于姓名 | 展示搜索结果、后续操作(发消息/建待办等) | +| `aisearch person` | `userId`(用户ID)、`title`(姓名) | 展示搜索结果、后续操作(发消息/建待办等) | ## 重名消歧 diff --git a/skills/multi/dingtalk-aisearch/references/intent-guide.md b/skills/multi/dingtalk-aisearch/references/intent-guide.md new file mode 100644 index 00000000..c38b26a0 --- /dev/null +++ b/skills/multi/dingtalk-aisearch/references/intent-guide.md @@ -0,0 +1,14 @@ +# aisearch 局部意图消歧 + +本文件从单 Skill `intent-guide.md` 拆分而来,仅保留与本产品相关的跨产品消歧规则。 + +| 用户说... | 真实意图 | 应该用 | 不要用 | 理由 | +|---|---|---|---|---| +| "张三在哪个部门/查一下同事工号" | 通讯录精确查询 | #8 `contact` | #5 汇报 / #4 文档 | 需要 userId、手机号、部门 ID 等精确信息时用 contact | +| "找一下张三/搜同事/找人" | AI搜人(首选) | `aisearch person` | `contact user search` | 搜人首选 aisearch,支持姓名/部门/职责/上下级维度;精确查 userId/手机号用 contact | +| "五道的上级是谁/谁负责XX/XX的下属有谁" | AI语义搜人 | `aisearch person` | `contact` | 涉及上下级、职责、负责人等语义维度搜索,用 aisearch | +| "222020这个工号是谁/查工号" | 按工号搜人 | `aisearch person --dimension jobNumber` | `contact` | 工号查人走 aisearch,dimension=jobNumber | +| "13800138000是谁/查手机号" | 按手机号搜人 | `aisearch person --dimension phone` | `contact` | 手机号查人走 aisearch,dimension=phone | +| "搜一下智能化方案/最近 OKR 相关邮件/最近发版相关消息" | 搜企业知识内容 | `aisearch enterprise` | `doc search` / `mail search` / `chat message search` | 跨文档、消息、日程、听记、邮件等企业内容语义检索走 enterprise;具体 `queries/types/time-range` 抽槽见 `aisearch.md` | +| "我发给某人的消息/邮件/文档/今天我干了什么" | 搜行为记录 | `aisearch behavior` | `chat` / `mail` / `doc` / `report` | 关注“谁对什么做过什么”,走 behavior;具体 `behavior-type/direction/chat-scope` 抽槽见 `aisearch.md` | +| "把这段文字翻译成英文/translate this" | 通用文本翻译 | `chat text translate` | `doc` / `aisearch` | 纯文本翻译,不是文档编辑或语义搜索 | diff --git a/skills/multi/dingtalk-aisearch/references/lite-recipes.md b/skills/multi/dingtalk-aisearch/references/lite-recipes.md new file mode 100644 index 00000000..b0594321 --- /dev/null +++ b/skills/multi/dingtalk-aisearch/references/lite-recipes.md @@ -0,0 +1,30 @@ +# aisearch Lite Recipe + +本文件从单 Skill `lite-recipes.md` 拆分而来,仅保留与本产品相关的轻量流程。 + +## #8 通讯录 + +### get-contact-self + +`contact user get-self` → 当前用户 userId、部门、主管等 + +### search-person + +**搜人首选入口**。凡是“找人/搜人/找同事/谁负责/上级/下级/负责人/团队成员”均优先用 `aisearch person`: + +1. 从用户问题中提取 keyword(人名/业务关键词)和 dimension(维度),规则见 [aisearch.md](./aisearch.md)。 +2. `aisearch person --keyword "<关键词>" --dimension <维度>` +3. 结果中提取 `userId` 和 `title`(姓名)展示给用户。 +4. 若需要 userId 做后续操作(发消息/建待办),可直接使用结果中的 `userId`。 +5. **重名消歧**:多人同名时禁止默认选第一个,须追加 `contact user get --ids` 获取部门/职位后请用户确认,详见 [08-directory.md](../../dingtalk-contact/references/08-directory.md)「多命中」。 + +### search-user + +仅在以下**精确查询**场景使用,搜人请优先用 `search-person`: + +- 需要获取 userId 给其他产品使用(发消息/建待办/约日程) +- 已有 userId 需查完整详情(`contact user get --ids`) + +1. `aisearch person --keyword "<姓名>" --dimension name` → `userId`;**多命中须列出候选请用户确认**。 +2. **重名消歧**:多人同名时禁止默认选第一个,须追加 `contact user get --ids` 获取部门/职位后请用户确认,详见 [08-directory.md](../../dingtalk-contact/references/08-directory.md)「多命中」。 +3. 需详情时:`contact user get --ids `(多人可 `--ids id1,id2,...`) diff --git a/skills/multi/dingtalk-aitable/SKILL.md b/skills/multi/dingtalk-aitable/SKILL.md index d663acf0..c5ddda5b 100644 --- a/skills/multi/dingtalk-aitable/SKILL.md +++ b/skills/multi/dingtalk-aitable/SKILL.md @@ -1,6 +1,6 @@ --- name: dingtalk-aitable -description: 钉钉 AI 表格(多维表)。Use when 用户说 AI表格/多维表/数据表/base/table/建表/查记录/写数据/字段/记录增删改查/筛选/排序/公式/模板搜索/批量导入CSV或JSON/导出/仪表盘/图表/上传附件到表格/按字段类型建表。Distinct from 主 dws skill 的 dws sheet(电子表格/单元格读写/公式)、dws doc(文档编辑)。命令前缀:dws aitable。 +description: 钉钉 AI 表格(多维表)。Use when 用户说 AI表格/多维表/数据表/base/table/建表/查记录/写数据/字段/记录增删改查/筛选/排序/公式/模板搜索/批量导入CSV或JSON/导出/仪表盘/图表/上传附件到表格/按字段类型建表。不做电子表格单元格读写(走 dingtalk-sheet)、文档编辑(走 dingtalk-doc);听记待办入表见配方 minutes-todo-to-aitable。命令前缀:dws aitable。 cli_version: ">=0.2.14" metadata: category: product @@ -14,14 +14,14 @@ metadata: > 🧪 **EXPERIMENTAL · 试验版 / Preview** — multi 模式当前未达 stable 标准。全部 dingtalk-* skill 已通过 dispatch verifier,但接口、命名、跨 skill 引用后续可能调整;生产 / 共享环境请优先使用 mono 模式(`dws skill setup --mode mono`)。问题请提 issue 反馈。 -> **PREREQUISITE:** Read the root `dws` skill first for auth, global flags, product routing, URL preflight, error codes, and safety rules. The `dws` binary must be on PATH. +## 前置条件 — 执行操作前必读 + +> **`use_skill(dws-shared)`** — 认证、全局参数(`--format json` / `--yes`)、错误码、URL 模板、跨产品消歧、安全规则与 capability 边界。**执行任何 `dws` 命令前先读;** 单产品的清晰命令可直接用本 skill。 -> ⚠️ **命令可用性以当前 dws 二进制为准**。服务发现已下线,本文档随内置 skill 发布;如果 `dws --help` 不存在,说明当前版本未暴露该命令。若命令存在但调用失败,请按错误中的 endpoint 或 tool 提示确认静态端点目录和后端工具注册。实际调用前可用 `dws --help` 或 `--dry-run` 验证。 - - > 命令参考:[aitable.md](references/aitable.md);复杂命令按需加载 `references/aitable/*.md`;剧本:[06-data-analytics.md](references/06-data-analytics.md)。 +> 高频模块:[Base](references/aitable/aitable-base-index.md) · [Table](references/aitable/aitable-table-index.md) · [Field](references/aitable/aitable-field-index.md) · [Record](references/aitable/aitable-record-index.md) · [View](references/aitable/aitable-view-index.md) · [Form](references/aitable/aitable-form-index.md) · [Workflow](references/aitable/aitable-workflow-index.md)。 ## 意图表 @@ -40,13 +40,76 @@ metadata: | "仪表盘 / 图表" | 先读 `references/aitable/aitable-dashboard-chart.md` | | "上传附件到记录" | 先读 `references/aitable/aitable-attachment.md`;可用 `python scripts/upload_attachment.py --base-id --file ` | +## 标准 SOP(必遵流程) + +> 命中以下意图**必须**按对应 SOP 顺序执行;**禁止**跳步、替换命令、编造 flag/ID。每条命令必须带 `--format json`,执行后必须按"解析"步取真实字段,不得凭返回结构猜测。`baseId`/`tableId`/`fieldId`/`recordId` 一律先查后用,**禁止默认/编造**。 + +### SOP-1 定位 Base 与 Table(list / search → table get) + +**触发**:找/打开某张 AI 表格、不知 baseId 或 tableId。 + +1. **选源(必须)**:有名称/关键词 → `dws aitable base search --query "<名称>"`;列最近访问 → `dws aitable base list`。`base list` 仅返回最近访问,不是全部,**禁止**当作全量清单。 +2. **执行(必须)**:`dws aitable base search --query "<完整名>" --format json`(或 `dws aitable base list --format json`)。 +3. **解析(必须)**:从 JSON 取真实 `baseId`;**多候选必须输出让用户选,禁止默认取第一个**。 +4. **取 tableId(必须)**:`dws aitable table get --base-id --format json` → 从 `tables[].tableId` 取目标表 ID,连同该表 `fields[]` 的 `fieldId/fieldName/type` 与 `views[]` 的 `viewId` 一并记下。 +5. **失败(必须)**:`base list` 为空或不命中 → 换 `base search --query` 关键词重试一次;仍无果**必须如实告知**,禁止臆造 baseId/tableId。 + +**禁止**:跳过 `table get` 直接用字段名写记录、用模糊名匹配当 baseId、用旧会话里的 ID 不再校验。 + +### SOP-2 拿字段定义(field get,写记录/改字段前置) + +**触发**:建/改/写记录、改字段名或 options、按字段类型拼写入参前。 + +1. **前置(必须)**:先按 SOP-1 拿到 `baseId` + `tableId`。 +2. **执行(必须)**:`dws aitable field get --base-id --table-id --format json`(仅展开需要的字段时加 `--field-ids fld1,fld2`,单次最多 10 个)。 +3. **解析(必须)**:取每个目标字段的 `fieldId`、`type`、`config`(如 singleSelect/multipleSelect 的 `options[].id|name`);写入 cells 的 key **必须用 `fieldId`**,不是字段中文名;select 字段过滤/写入传**选项名称字面量**,不传 option ID。 +4. **衔接(必须)**:拿到字段定义 → 进入 SOP-3 写记录、或 `dws aitable field update --field-id --name <新名>|--config --format json` 改字段。 +5. **失败(必须)**:字段不存在或类型不符 → 重新 `field get` 核对,**禁止**凭旧名称/旧类型继续写入。 + +**禁止**:用字段中文名当 cells key、跳过 `field get` 直接 `record create/update`、对 select 字段传 option ID 当写入值。 + +### SOP-3 写/批量写记录(record create) + +**触发**:新增记录、批量加数据、CSV/JSON 入表。 + +1. **前置(必须)**:SOP-1 取 `baseId`/`tableId` + SOP-2 取 `fieldId`/类型。 +2. **执行(必须)**:`dws aitable record create --base-id --table-id --records '[{"cells":{"":<值>}}]' --format json`;单次最多 100 条,超长用 `--records-file ./data.json`。 +3. **写入格式(必须)**:按 `record create --help` 类型表严格传值(text→字符串、number→数值、singleSelect→"选项名"、date→RFC3339、url→`{"text","link"}`、group→`{"cid"}` 等);`filterUp`/`lookup` 字段只读不可写。 +4. **解析(必须)**:从返回取真实 `recordId`;名称被系统加后缀或字段重命名时,**后续一律用返回的新 ID**,禁止继续按原名称猜。 +5. **失败(必须)**:类型/格式错误按返回报错修正后重试,**禁止**降级丢弃字段;不确定格式先 `field get` 复核 config。 + +**禁止**:编造 fieldId/recordId、跳过 `field get` 凭中文名写、把 URL 字符串直接塞给 url 字段。 + +### SOP-4 查/筛/排记录(record query) + +**触发**:查记录、按条件筛选、排序、取关联记录、定位待改/待删的 recordId。 + +1. **前置(必须)**:SOP-1 拿 `baseId`/`tableId`。 +2. **执行(必须)**:`dws aitable record query --base-id --table-id --format json`;已知 ID 直取加 `--record-ids rec1,rec2`(忽略 filters/sort,单次≤100)。 +3. **筛选/排序(必须)**:`--filters` 最外层必须 `{"operator":"and|or","operands":[...]}`,select 字段值传**选项名字面量**;日期只能用 `date_eq/before/after/not_before/not_after`,范围用 `not_before`+`not_after` 组合,**禁止** `eq`/区间/相对时间。`--sort` 用 `[{"fieldId":"..","direction":"asc|desc"}]`(**必须用 `direction`**)。公式/引用/关联字段默认不返回,需显式 `--field-ids` 指定。 +4. **解析(必须)**:取真实 `recordId` 与字段值;分页用 `--cursor`,全表用 `--all --page-limit N`。 +5. **衔接(必须)**:拿到 recordId → SOP-5 更新、`record delete --record-ids --yes` 删除(删前确认)。 + +**禁止**:用字段名做 filter/sort key、对日期用 `eq`、漏掉 `direction` 用旧 `order` 字段、用本地过滤替代服务端 filter。 + +### SOP-5 更新记录(record update) + +**触发**:改记录字段值、批量更新状态、单字段重命名需求之外的记录改动。 + +1. **前置(必须)**:SOP-1 拿 `baseId`/`tableId`;SOP-2 拿字段类型;SOP-4 拿目标 `recordId`。 +2. **执行(必须)**:`dws aitable record update --base-id --table-id --records '[{"recordId":"recXXX","cells":{"":<新值>}}]' --format json`(每条必含 `recordId`+`cells`,单次≤100;超长用 `--records-file`);只传需改字段,未传保持原值。 +3. **解析(必须)**:写入格式同 SOP-3;返回核对真实 recordId 与受影响字段。 +4. **失败(必须)**:recordId 不存在或类型不符 → 回 SOP-4 重新定位,**禁止**编造 ID 强写。 + +**禁止**:省略 `recordId`、用字段中文名当 cells key、凭空猜测 recordId 直接 update。 + ## 危险操作 `base delete` / `table delete` / `field delete` / `record delete` 不可逆,必须先向用户确认再加 `--yes`。 -## 评测高频硬约束 +## 高频硬约束 -- 创建/改字段/写记录是多轮连续任务时,不能在"让我执行/先获取 ID"后停下;必须实际调用对应 `dws aitable` 命令并验证结果。 +- 创建 Base、改字段或写记录时,固定按“获取真实 ID → 执行写命令 → 回读验证”顺序完成,禁止跳过任一步。 - 字段重命名使用 `dws aitable field update --base-id --table-id --field-id --name "<新名称>" --format json`;先 `field get` 找真实 `fieldId`,不要猜字段名能直接更新。 - 写记录前必须 `field get` 获取 `fieldId` 与类型;`record create/update` 的 `cells` key 用 `fieldId`,不是字段中文名。长 JSON 使用 `--records-file`。 - 表或字段创建返回名称被系统自动加后缀时,后续必须使用返回的真实 `tableId`/`fieldId`,不要继续按原名称猜。 @@ -59,4 +122,7 @@ metadata: ## 跨产品协作 -- 单元格 / 工作表 / 公式 → 走主 `dws` skill 的 `sheet` 产品路由(`dws sheet`) +- 单元格 / 工作表 / 公式 → 切到 `dingtalk-sheet`(命令前缀:`dws sheet`) +## 局部意图与 Recipe + +- [局部意图消歧](references/intent-guide.md)。 diff --git a/skills/multi/dingtalk-aitable/references/06-data-analytics.md b/skills/multi/dingtalk-aitable/references/06-data-analytics.md index 3fd9b0bc..41e8d5bf 100644 --- a/skills/multi/dingtalk-aitable/references/06-data-analytics.md +++ b/skills/multi/dingtalk-aitable/references/06-data-analytics.md @@ -4,10 +4,8 @@ | Recipe | 行动指南(固定路线) | |--------|-------------------| -| read-aitable | 1. `aitable base search --query "<表格名>"` → 取 `baseId`/`tableId`
2. `aitable field get --base-id --table-id ` → 取 `fieldId`
3. **取记录(按场景分流)**:
  • 数据统计/分析/全量汇总 → `aitable record query --base-id --table-id --all`(自动翻页,**禁止凭单页数据做统计**)
  • 大表保险 → 加 `--page-limit 100`(默认 50 页/5000 条,0 = 无限制)
  • 单纯预览前几条 → `aitable record query --base-id --table-id --limit 30`(不加 --all)
  • 筛选时 `--filters` 格式见 [aitable-filter-sort.md](../products/aitable/aitable-filter-sort.md)
4. **检查输出契约**:`hasMore=true` 时数据被截断,必须用 `--cursor ` 续拉;`partial=true` 时表示中途某页失败(保留已拉数据,可重试)
5. 总结数据 | -| generate-data-report | 1. 同 read-aitable 步骤 1-3(**必须用 --all 防漏数据**)
2. 按[「多源并行采集」](_common/conventions.md#多源并行采集公共模式)执行 → 补充背景
3. `doc create --name "<报告名>" --content "<分析报告>"` | -| create-aitable-record | **写入路径分流**(关键决策):
  • **追加到已有表**(场景:把这批数据加到「成员表」)→ `python scripts/import_records.py data.csv\|data.json [batch_size]`(走 `record create`,需要列名匹配字段名)
  • **新建表**(场景:把这个 Excel 变成多维表)→ `python scripts/aitable_import_via_task.py `(脚本内置 prepare→OSS PUT→import data 全流程和正确的头处理,**禁止自己写 PUT**)。注意 `aitable import upload` 没有 `--file` flag、也不代做 PUT,只准备导入;能一站式完成的是上面的脚本
  • **单条/少量手动**:1. `aitable base search` → `baseId`/`tableId`;2. `aitable field get` → `fieldId` 与类型;3. `aitable record create --records '[{"cells":{"":"值"}}]'` | -| update-aitable-record | 1. `aitable base search --query "<表格名>"` → 取 `baseId`/`tableId`
2. **取目标 record**:
  • 已知少量 recordId → `aitable record query --record-ids `
  • 按条件批量改 → `aitable record query --base-id --table-id --filters '' --all`(**用 --all 防止漏改**)
3. **先展示让用户确认要改的 record 列表**
4. `aitable record update --base-id --table-id --records '[{"recordId":"","cells":{...}}]'`(单次 ≤30 条) | +| read-aitable | 1. `aitable base search --query "<表格名>"` → 取 `baseId`/`tableId`
2. `aitable field get --base-id --table-id ` → 取 `fieldId`
3. `aitable record query --base-id --table-id ` → 取记录(分页)
  需要筛选时 `--filters` 格式见 [aitable-filter-sort.md](./aitable/aitable-filter-sort.md),根节点必须是 `{"operator":"and\|or","operands":[...]}`
4. 总结数据 | +| generate-data-report | 1. 同 read-aitable 步骤 1-3
2. 按[「多源并行采集」](_common/conventions.md#多源并行采集公共模式)执行 → 补充背景
3. `doc create --name "<报告名>" --content "<分析报告>"` | +| create-aitable-record | **批量导入优先**:`python scripts/import_records.py data.csv\|data.json [batch_size]`(自动分批创建)
单条/少量:1. `aitable base search --query "<表格名>"` → 取 `baseId`/`tableId`
2. `aitable field get --base-id --table-id ` → 取 `fieldId` 与类型
3. `aitable record create --base-id --table-id --records '[{"cells":{"":"值"}}]'` | +| update-aitable-record | 1. `aitable base search --query "<表格名>"` → 取 `baseId`/`tableId`
2. `aitable record query --base-id --table-id ` → 取 `recordId`,**先展示让用户确认**
3. `aitable record update --base-id --table-id --records '[{"recordId":"","cells":{...}}]'` | | search-aitable-template | 1. `aitable template search --query "<关键词>"` → 取 `templateId`
2. 用户选定
3. `aitable base create --name "<表格名>" --template-id ` → 取 `baseId` | -| export-aitable-to-xlsx | 1. `aitable base search --query "<表格名>"` → 取 `baseId`
2. **按场景选 scope**:
  • 全表+附件 → `aitable export data --base-id --scope all --export-format excel_and_attachment --output ./.xlsx`
  • 单表(仅 xlsx)→ `--scope table --table-id --export-format excel`
  • 单视图 → `--scope view --table-id --view-id `
3. CLI 内置渐进式退避轮询 + 自动落盘,**不要自己写 GET downloadUrl**
4. 大表超时(默认 5 分钟):加 `--timeout-sec 900` 或拿到 `taskId` 后 `aitable export data --task-id --output ./.xlsx` 续等
5. 与悟空脚本路径并存:复杂场景(多 base 批量 / 按视图组合)请用 `python scripts/aitable_export_via_task.py --scope all\|table\|view` | -| primary-doc-from-record | 当 AI 表格用文档作主键字段时,从 record 跳到其主文档:
1. `aitable record query` 取 `recordId`
2. `aitable base get-primary-doc-id --base-id --table-id --record-id ` → 取主键文档 `dentryUuid`
3. 凭 `dentryUuid` 调 `doc read --node ` 拿文档内容 | diff --git a/skills/multi/dingtalk-aitable/references/_common/conventions.md b/skills/multi/dingtalk-aitable/references/_common/conventions.md index 93e65f8c..75491aab 100644 --- a/skills/multi/dingtalk-aitable/references/_common/conventions.md +++ b/skills/multi/dingtalk-aitable/references/_common/conventions.md @@ -1,6 +1,6 @@ # 业务域通用规范 -> 仅服务本仓库已迁入的文档与 AI 表格行动指南。安全门控、危险操作确认、`--format json` 等已在根 skill 中定义,此处不重复。 +> 仅服务本 skill 已迁入的行动指南。安全门控、危险操作确认、`--format json` 等已在本 skill 的 `SKILL.md` 中定义,此处不重复。 ## 批量查询规范 @@ -9,7 +9,8 @@ | 1 | **并行查详情**:拿到多个 ID 后,用 `&` 合并到同一条 Shell 命令并行执行 + `wait`,**严禁逐条串行** | | 2 | **翻页**:分页接口须拉全直至无更多 | | 3 | **优先批量 API**:有批量接口则用批量;无则按 #1 并行 | -| 4 | **列表少轮次**:带条件搜索/列表 → 一次采全详情;**禁止**无新参数时重复同一 `list` / `search` | +| 4 | **群消息**:必须先 `chat search --query` 得 `openConversationId`,再 `chat message list --group`;多群同条命令并行 | +| 5 | **列表少轮次**:带条件搜索/列表 → 一次采全详情;**禁止**无新参数时重复同一 `list` / `search` | ## 多源并行采集(公共模式) @@ -26,10 +27,18 @@ | 字段 | 来源 | 传递给 | |------|------|--------| -| `nodeId` | `doc search` | `doc read/update/copy/move/rename --node` | -| `nodeId` | `doc list` 中的 folder 类型节点 / `doc folder create` | `doc list --folder`、`doc create --folder`、`doc upload --folder`、`doc copy/move --folder` | +| `taskUuid` | `minutes list` | `minutes get summary/info/batch --id(s)` | +| `userId` | `aisearch person` / `contact user search` / `contact dept list-members` | `contact user get --ids`、`todo --executors`、`calendar --users` | +| `deptId` | `contact dept search` | `contact dept list-members --depts`;多子部门时对每个子部门分别 `dept search` 取 id | +| `nodeId` | `drive search` / `wiki node search` | `doc read/update --node`、`drive copy/move/rename/delete --node` | +| `nodeId` | `wiki node list` 中的 folder 类型节点 / `wiki node create --type folder` | `wiki node list --folder`、`wiki node create --folder`、`drive upload --folder`、`drive copy/move --folder` | +| `eventId` | `calendar event list` | `calendar event get/update --id` | +| `processInstanceId` | `oa approval list-*` | `oa approval detail/approve --instance-id` | +| `openConversationId` | `chat search` | `chat message list/send --group` | +| `todoTaskId` | `todo task list` | `todo task update/done --task-id` | +| `reportId` | `report inbox list` / `report outbox list` | `report entry get/stats --report-id` | | `baseId` / `tableId` | `aitable base search` | `aitable record query --base-id --table-id` | -| `dentryUuid` | `drive list` / `drive mkdir` | `drive info/download --file-id`、`drive list/mkdir/upload --parent-id` | -| `workspaceId` | `wiki space search/list/create` | `doc list/search/create --workspace`、`wiki member * --workspace` | +| `dentryUuid` | `drive list` / `drive mkdir` | `drive info/download --node`、`drive list/mkdir/upload --folder` | +| `dentryId` | `drive info` 的数字字段 | 仅用于 `chat message send --dentry-id` | -**ID 边界硬约束**:遇到 `drive --parent-id`、`doc --folder`、`doc --node` 时,只能使用 `dentryUuid` / `nodeId` / 文档 URL。若当前上下文只有数字型 `dentryId`,必须先重新 `drive list` / `doc list` / `doc search` 获取正确 ID,不能把该数字直接代入后续命令。 +**ID 边界硬约束**:`dentryId` 通常是纯数字,只表示聊天文件消息需要的钉盘条目数字 ID;它不是父目录 ID。遇到 `drive --node/--folder`、`doc --node/--folder`、`wiki node --folder` 时,只能使用 `dentryUuid` / `nodeId` / 文档 URL。若当前上下文只有数字型 `dentryId`,必须先重新 `drive list` / `drive search` / `wiki node list` 获取正确 ID,不能把该数字直接代入后续命令。 diff --git a/skills/multi/dingtalk-aitable/references/aitable.md b/skills/multi/dingtalk-aitable/references/aitable.md index 904c3837..532a3bb5 100644 --- a/skills/multi/dingtalk-aitable/references/aitable.md +++ b/skills/multi/dingtalk-aitable/references/aitable.md @@ -1,282 +1,21 @@ -# AI表格 (aitable) 命令参考 +# AI 表格 (aitable) 命令参考 -> **渐进式文档**:本文件为路由层(索引 + 意图判断),各命令的详细参数、示例和踩坑说明在 [aitable/](./aitable/) 目录下按需加载。 +> 渐进式入口:先读命令索引,再只加载命中的模块参考。 -## 文档地址 (URI) +## 按需加载 -| 资源 | URI 格式 | -|------|----------| -| Base 文档 | `https://alidocs.dingtalk.com/i/nodes/{baseId}` | -| 模板预览 | `https://docs.dingtalk.com/table/template/{templateId}` | +| 任务 | 参考 | +|---|---| +| URL 与 baseId | [URI](./aitable/aitable-uri.md) · [URL 转 baseId](./aitable/aitable-url-to-baseid.md) | +| Base / Table / Field | [Base](./aitable/aitable-base-index.md) · [Table](./aitable/aitable-table-index.md) · [Field](./aitable/aitable-field-index.md) | +| Record / View / Form | [Record](./aitable/aitable-record-index.md) · [View](./aitable/aitable-view-index.md) · [Form](./aitable/aitable-form-index.md) | +| Workflow / Dashboard / Import | [Workflow](./aitable/aitable-workflow-index.md) · [Dashboard](./aitable/aitable-dashboard-index.md) · [导入导出](./aitable/aitable-export-import-index.md) | +| Attachment / Advanced Permission | [附件](./aitable/aitable-attachment-index.md) · [高级权限](./aitable/aitable-advperm-index.md) | +| 完整命令速查 | [命令索引](./aitable/aitable-command-index.md) | +| 复杂操作与路由 | [复杂操作](./aitable/aitable-complex-ops.md) · [意图判断](./aitable/aitable-intent.md) | +| 标准流程与上下文 | [核心工作流](./aitable/aitable-workflow.md) · [上下文传递](./aitable/aitable-context.md) | +| 注意事项、脚本、产品边界 | [注意事项](./aitable/aitable-notes.md) · [自动化脚本](./aitable/aitable-scripts.md) · [相关产品](./aitable/aitable-related.md) | -> **操作后请返回文档 URI**:每次执行 base list/search/create/get 操作后,从返回数据中提取 `baseId`,拼接为 `https://alidocs.dingtalk.com/i/nodes/{baseId}` 返回给用户。 -> 补充:如果 URL 不是来自 `aitable` 命令返回,而是用户直接贴的原始 `alidocs` URL,先按 [链接规范](../url-patterns.md#alidocs-url-类型探测流程) probe,确认是 `able` 后再按 AI 表格处理。 +## 创建 AI 表格 -## 命令索引表 - -### base (Base 管理) - -| 命令 | 用途 | 必填参数 | 路由提醒 | -|------|------|----------|----------| -| `base list` | 列出最近访问的 Base | — | 仅返回最近访问过的,优先用 `base search` | -| `base search` | 按名称搜索 Base(别名 `aitable search`) | — | `--query` help 标必填但实际可省略:不传时返回最近访问的 Base 列表。`--keyword` 是 `--query` 的隐藏别名,同义 | -| `base get` | 获取 Base 信息(含 tables 列表) | `--base-id` | 用户给 URL 时提取末尾 ID | -| `base copy` | 复制整个 Base 到目标文件夹 | `--base-id` `--target-folder-id` | 默认全量复制;`--only-struct` 仅复制结构不含数据 | -| `base get-primary-doc-id` | 获取某记录的主键文档 ID | `--base-id` `--table-id` `--record-id` | 等价 `record primary-doc-get` 的取 ID 视角 | -| `base create` | 创建 Base | `--name` | 创建后直接用返回的 baseId | -| `base update` | 更新 Base 名称 | `--base-id` `--name` | — | -| `base delete` | 删除 Base | `--base-id` | 不可逆 | - -### table (数据表管理) - -| 命令 | 用途 | 必填参数 | 路由提醒 | -|------|------|----------|----------| -| `table get` | 获取表结构(字段+视图目录) | `--base-id` | 不传 `--table-ids` 返回全部表 | -| `table list` | 获取数据表(`table get` 的别名) | `--base-id` | 与 `table get` 等价 | -| `table create` | 创建数据表 | `--base-id` `--name` | `--fields` 为 JSON 数组;**可传空数组 `[]`**(默认值即 `[]`),此时服务端自动补一个名为"标题"的 primaryDoc 首列;单次最多 15 个字段 | -| `table update` | 重命名表 | `--base-id` `--table-id` `--name` | — | -| `table delete` | 删除表 | `--base-id` `--table-id` | 不可逆 | - -### field (字段管理) → 详见 [aitable-field.md](./aitable/aitable-field.md)、[field-properties](./aitable/aitable-field-properties.md) - -| 命令 | 用途 | 必填参数 | 路由提醒 | -|------|------|----------|----------| -| `field get` | 获取字段完整配置 | `--base-id` `--table-id` | 按需展开少量字段 | -| `field list` | 获取字段信息(`field get` 的别名) | `--base-id` `--table-id` | 与 `field get` 等价 | -| `field create` | 创建字段 | `--base-id` `--table-id` + (`--name --type` 或 `--fields`) | 支持单字段/批量模式 | -| `field update` | 更新字段名/配置 | `--base-id` `--table-id` `--field-id` | 不可变更字段类型 | -| `field delete` | 删除字段 | `--base-id` `--table-id` `--field-id` | 不可逆 | -| `field search-options` | 搜索单选/多选字段的选项 | `--base-id` `--table-id` `--field-id` | 仅 singleSelect/multipleSelect;`--keyword` 模糊过滤,不传返回全部 | - -### record (记录管理) - -| 命令 | 用途 | 必读 reference | 路由提醒 | -|------|------|----------------|----------| -| `record query` | 查询/搜索记录 | [aitable-record-query.md](./aitable/aitable-record-query.md) | 先 `table get` 拿 fieldId;`--all` 自动翻页;filters 结构见 reference;`--query`(隐藏别名 `--keyword`)全文搜索 | -| `record list` | 获取记录(`record query` 的别名) | [aitable-record-query.md](./aitable/aitable-record-query.md) | 与 `record query` 等价 | -| `record get` | 按 ID 取记录(`record query --record-ids` 的窄别名) | [aitable-record-query.md](./aitable/aitable-record-query.md) | 已知 recordId 时首选;必填 `--record-ids`(单次最多 100 条);未暴露 filters/sort/query/cursor/limit | -| `record query-empty` | 查询完全没填用户字段的空行 | — | `--base-id` `--table-id`;`--limit` 扫描预算 [1,100],`--cursor` 翻页 | -| `record create` | 新增记录 | [aitable-record-create.md](./aitable/aitable-record-create.md) | cells key 必须是 fieldId 不是字段名;单次最多 100 条 | -| `record update` | 更新记录 | [aitable-record-update.md](./aitable/aitable-record-update.md) | 需先 query 拿 recordId;只传需改字段;**没有** `--record-id` `--cells` flag | -| `record batch-update` | 把同一份 cells 批量应用到多条记录 | [aitable-record-update.md](./aitable/aitable-record-update.md) | `--record-ids`(≤100)+ `--cells` 共享 patch | -| `record upsert` | 批量创建或更新(有 recordId 走更新,无则创建) | [aitable-record-upsert.md](./aitable/aitable-record-upsert.md) | `--records`/`--records-file`;单次最多 100 条 | -| `record delete` | 删除记录 | [aitable-record-delete.md](./aitable/aitable-record-delete.md) | 不可逆,需先 query 确认 | -| `record share-url` | 批量获取记录分享链接 | [aitable-record-share.md](./aitable/aitable-record-share.md) | `--record-ids`(逗号分隔,单次最多 20 条) | -| `record history-list` | 查询单条记录的变更历史 | [aitable-record-history.md](./aitable/aitable-record-history.md) | `--record-id` 单条;`--offset`/`--limit`(≤50) | -| `record primary-doc-get` | 查询记录的主键文档 nodeId | [aitable-primary-doc.md](./aitable/aitable-primary-doc.md) | 无文档时返回 `no record` 错误 | -| `record primary-doc-create` | 为记录创建主键文档 | [aitable-primary-doc.md](./aitable/aitable-primary-doc.md) | 幂等;`--field-id` 须 primaryDoc 类型 | - -### view (视图管理) - -| 命令 | 用途 | 必填参数 | 路由提醒 | -|------|------|----------|----------| -| `view get` | 获取视图配置 | `--base-id` `--table-id` | 不传 `--view-ids` 返回全部视图 | -| `view create` | 创建视图 | `--base-id` `--table-id` `--view-type` | 类型: Grid/Kanban/Gantt/Calendar/Gallery/FormDesigner | -| `view update` | 更新视图(**调整字段顺序的入口**) | `--base-id` `--table-id` `--view-id` | `visibleFieldIds` 重排字段顺序 | -| `view delete` | 删除视图 | `--base-id` `--table-id` `--view-id` | 不可删最后一个/锁定视图 | - -> **"移动字段/调整字段顺序"** 在 AI 表格里没有 `field reorder` 命令,必须通过 `view update --config '{"visibleFieldIds":[...]}'` 完成。 - -> **view update --config 支持的 key 白名单**(传入其他 key 会报错): -> - `visibleFieldIds` — 视图可见字段列表及顺序(首列字段必须保留在第一位) -> - `filter` — 筛选规则列表 -> - `sort` — 排序规则列表 -> - `group` — 分组规则列表 -> - `fieldWidths` — 列宽映射(仅 Grid 视图有效) -> -> 不支持 `formInfo`、`requiredFields`、`conditionalRules` 等 FormDesigner 高级配置,这些 key 会被服务端忽略。 - -### form (表单管理) → 详见 [aitable-form.md](./aitable/aitable-form.md) - -| 命令 | 用途 | 必填参数 | 路由提醒 | -|------|------|----------|----------| -| `form list` | 列出表单视图 | `--base-id` `--table-id` | 每条含 viewId/name;新建表单无 title 且 createdAt=0,改过后才有 | -| `form get` | 按 viewId 取单个表单详情 | `--base-id` `--table-id` `--view-id` | 客户端按 viewId 过滤,`data` 即该表单对象;viewId 不存在报 not found | -| `form create` | 创建表单视图 | `--base-id` `--table-id` `--name` | 等价 `view create --view-type FormDesigner` | -| `form update` | 更新表单配置 | `--base-id` `--table-id` `--view-id` | title/name/description 至少一项 | -| `form delete` | 删除表单 | `--base-id` `--table-id` `--view-id` | 不可逆 | -| `form field list/update/hide` | 表单字段管理 | — | 详情见子文档 | -| `form questions create/delete` | 题目管理(=field create/delete) | — | 详情见子文档 | -| `form share get/update` | 表单分享配置 | — | 详情见子文档 | - -> **创建表单**有两种等价方式:`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) - -| 命令 | 用途 | -|------|------| -| `dashboard get/create/update/delete` | 仪表盘管理 | -| `dashboard config-example` | 查看仪表盘配置模板 | -| `chart get/create/update/delete` | 图表管理 | -| `chart widgets-example` | 查看图表 widgets 配置模板 | - -### export & import → 详见 [aitable-export-import.md](./aitable/aitable-export-import.md) - -| 命令 | 用途 | -|------|------| -| `export data` | 导出数据(异步两阶段轮询) | -| `import upload` | 申请文件导入上传凭证 | -| `import data` | 触发导入 | - -### attachment → 详见 [aitable-attachment.md](./aitable/aitable-attachment.md) - -| 命令 | 用途 | 路由提醒 | -|------|------|----------| -| `attachment upload` | 准备附件上传凭证 | 不要用钉盘 drive 上传! | - -### template (模板搜索) - -| 命令 | 用途 | 必填参数 | -|------|------|----------| -| `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表格": -- 查看/查找/列表 → `base search`(优先)或 `base list`(仅浏览最近访问) -- 详情 → `base get` -- 创建 → `base create` -- 修改 → `base update` -- 删除 → `base delete` - -用户说"数据表/子表/table": -- 查看 → `table get` -- 创建 → `table create` -- 重命名 → `table update` -- 删除 → `table delete` - -用户说"字段/列/column": -- 查看 → `field get` -- 添加 → `field create`(读 [aitable-field.md](./aitable/aitable-field.md)) -- 修改 → `field update` -- 删除 → `field delete` - -用户说"记录/行/数据/row": -- 查看/搜索 → `record query`(读 [aitable-record-query.md](./aitable/aitable-record-query.md)) -- 已知 recordId 反查字段值 → `record get`(按 ID 取专用,等价 `record query --record-ids`) -- 添加/写入 → `record create`(读 [aitable-record-create.md](./aitable/aitable-record-create.md)) -- 修改/更新 → `record update`(读 [aitable-record-update.md](./aitable/aitable-record-update.md)) -- 删除 → `record delete` - -用户说"表单/问卷/form/收集信息" → 读 [aitable-form.md](./aitable/aitable-form.md) - -用户说"筛选/过滤/filter" → 读 [aitable-filter-sort.md](./aitable/aitable-filter-sort.md) - -用户说"统计/分析/聚合/TOP N/全量" → 读 [aitable-data-analysis-sop.md](./aitable/aitable-data-analysis-sop.md) - -用户说"公式/formula/计算字段/派生指标" → 读 [aitable-formula-guide.md](./aitable/aitable-formula-guide.md) - -用户说"查找引用/lookup/filterUp/跨表" → 读 [aitable-formula-guide.md](./aitable/aitable-formula-guide.md)(§5.4 跨表引用) - -用户说"仪表盘/图表/chart" → 读 [aitable-dashboard-chart.md](./aitable/aitable-dashboard-chart.md) - -用户说"附件/上传文件" → 读 [aitable-attachment.md](./aitable/aitable-attachment.md) - -用户说"导入/导出/import/export" → 读 [aitable-export-import.md](./aitable/aitable-export-import.md) - -用户说"模板" → `template search` - -命令报错/操作失败 → 读 [aitable-error-recovery.md](./aitable/aitable-error-recovery.md) - -**关键区分**: base=表格文件, table=数据表, field=列, record=行 - -## 核心工作流 - -```bash -# 1. 搜索/列出 Base — 提取 baseId -dws aitable base search --query "项目" --format json - -# 2. 获取 Base 信息 — 提取 tableId -dws aitable base get --base-id --format json - -# 3. 获取表结构 — 提取 fieldId -dws aitable table get --base-id --table-id --format json - -# 4. 查询记录 -dws aitable record query --base-id --table-id --format json - -# 5. 新增记录 (cells 用 fieldId 作 key) -dws aitable record create --base-id --table-id \ - --records '[{"cells":{"fldXXX":"值"}}]' --format json -``` - -## 上下文传递表 - -| 操作 | 从返回中提取 | 用于 | -|------|-------------|------| -| `base list/search` | `baseId` | 所有后续命令的 --base-id,拼接文档 URI | -| `base create` | `baseId` | 后续命令 + 文档 URI | -| `base get` | `tables[].tableId` | --table-id | -| `table get` | `fields[].fieldId` | record 操作的 cells key, field get/update/delete | -| `record query` | `recordId` | record update/delete;按 ID 反查字段值用 `record get` | -| `template search` | `templateId` | base create --template-id,拼接模板预览 URI | - -## URL → baseId 提取 - -用户提供 `https://alidocs.dingtalk.com/i/nodes/{baseId}` 链接时: -1. 提取 `/nodes/` 后的路径段作为 `baseId` -2. 去掉尾部的查询参数(`?` 及其后内容) -3. 传入 `--base-id` 参数 - -> 如果该 URL 来自 `dws aitable` 返回或已在当前链路 probe 过,可直接复用; -> 如果是用户直接提供的原始 `alidocs` URL,则先按 [链接规范](../url-patterns.md#alidocs-url-类型探测流程) probe,确认 `extension=able` 后再继续。 - -## 注意事项 - -- 所有操作使用 ID(baseId/tableId/fieldId/recordId),不使用名称 -- records 的 cells key 是 fieldId,不是字段名称 -- cells 写入/读取格式见 [aitable-cell-value.md](./aitable/aitable-cell-value.md) -- 最佳实践见 [aitable-best-practices.md](./aitable/aitable-best-practices.md) - -## 自动化脚本 - -| 脚本 | 场景 | -|------|------| -| [bulk_add_fields.py](../../scripts/bulk_add_fields.py) | 批量添加字段 | -| [import_records.py](../../scripts/import_records.py) | 从 JSON/CSV 批量导入记录 | -| [aitable_export_via_task.py](../../scripts/aitable_export_via_task.py) | 文件导出(export_data 轮询 + 下载) | -| [upload_attachment.py](../../scripts/upload_attachment.py) | 上传附件到 AI 表格记录 | - -## 相关产品 - -- [doc](./doc.md) — 富文本文档编辑,不是结构化数据表格 +创建 Base 先读 [Base 索引](./aitable/aitable-base-index.md);创建数据表再读 [Table 索引](./aitable/aitable-table-index.md)。 diff --git a/skills/multi/dingtalk-aitable/references/aitable/aitable-advperm-index.md b/skills/multi/dingtalk-aitable/references/aitable/aitable-advperm-index.md new file mode 100644 index 00000000..61d89780 --- /dev/null +++ b/skills/multi/dingtalk-aitable/references/aitable/aitable-advperm-index.md @@ -0,0 +1,6 @@ +# 高级权限与自定义角色 + +| 命令 | 用途 | 详见 | +|---|---|---| +| `advperm enable/disable` | 高级权限总开关 | [`aitable-advperm.md`](./aitable-advperm.md) | +| `advperm role-list/get/create/update/delete` | 自定义角色管理 | [`aitable-advperm.md`](./aitable-advperm.md) | diff --git a/skills/multi/dingtalk-aitable/references/aitable/aitable-attachment-index.md b/skills/multi/dingtalk-aitable/references/aitable/aitable-attachment-index.md new file mode 100644 index 00000000..0c63516e --- /dev/null +++ b/skills/multi/dingtalk-aitable/references/aitable/aitable-attachment-index.md @@ -0,0 +1,5 @@ +# 附件 + +| 命令 | 用途 | 详见 | +|---|---|---| +| `attachment upload` | 准备附件上传凭证 | [`aitable-attachment.md`](./aitable-attachment.md) | diff --git a/skills/multi/dingtalk-aitable/references/aitable/aitable-attachment.md b/skills/multi/dingtalk-aitable/references/aitable/aitable-attachment.md index 48ae3306..45e9c2ae 100644 --- a/skills/multi/dingtalk-aitable/references/aitable/aitable-attachment.md +++ b/skills/multi/dingtalk-aitable/references/aitable/aitable-attachment.md @@ -1,6 +1,8 @@ # attachment — 附件上传 > **STOP — 不要使用钉盘 (drive) 上传!** 钉盘 fileId 无法写入 attachment 字段。必须使用以下流程。 +> +> **STOP — 严禁在 record create/update 的 cells 里直接传图片 URL!** 直传 `{"url":"https://..."}` 会导致服务端同步下载图片,批量写入时触发 TIMEOUT_ERROR。正确做法:先 `attachment upload` 获取 `fileToken`,再用 `{"fileToken":"ft_xxx"}` 写入。 ## 准备附件上传 @@ -38,10 +40,8 @@ dws aitable record create --base-id --table-id \ dws aitable attachment upload --base-id --file-name report.pdf --size 204800 --format json # → 返回 uploadUrl、fileToken -# 2. PUT 上传(Content-Type 必须与文件类型一致,不能留空) -# 留空或用 curl 默认的 application/x-www-form-urlencoded 都会被 OSS 拒为 403 +# 2. PUT 上传(Content-Type 必须是文件的具体 MIME type) curl -X PUT "" -H "Content-Type: application/pdf" --data-binary @report.pdf -# 例:.txt → text/plain,.png → image/png,.xlsx → application/vnd.openxmlformats-officedocument.spreadsheetml.sheet # 3. 写入记录 dws aitable record update --base-id --table-id \ diff --git a/skills/multi/dingtalk-aitable/references/aitable/aitable-base-index.md b/skills/multi/dingtalk-aitable/references/aitable/aitable-base-index.md new file mode 100644 index 00000000..504be3e3 --- /dev/null +++ b/skills/multi/dingtalk-aitable/references/aitable/aitable-base-index.md @@ -0,0 +1,10 @@ +# Base 管理 + +| 命令 | 用途 | 必读参数 | +|---|---|---| +| `base list` | 列出最近访问的 Base | — | +| `base search` | 按名称搜索 Base | `--query` | +| `base get` | 获取 Base 信息(含 tables 列表) | `--base-id` | +| `base create` | 创建 Base | `--name` | +| `base update` | 更新 Base 名称 | `--base-id` `--name` | +| `base delete` | 删除 Base | `--base-id` | diff --git a/skills/multi/dingtalk-aitable/references/aitable/aitable-best-practices.md b/skills/multi/dingtalk-aitable/references/aitable/aitable-best-practices.md index a851af83..db63f634 100644 --- a/skills/multi/dingtalk-aitable/references/aitable/aitable-best-practices.md +++ b/skills/multi/dingtalk-aitable/references/aitable/aitable-best-practices.md @@ -26,7 +26,7 @@ | 全量拉取/统计 | `record query --all` | 不要手动循环 cursor | | 全量导出为文件 | `export data` | 不要 `--all` 拉全量再写文件 | | 批量写入 | `record create`(分批 100 条) | 不要一次传超过 100 条 | -| 附件上传 | `attachment upload` + `record update` | 不要在 cells 里伪造附件值 | +| 附件/图片上传 | `attachment upload` 获取 fileToken → `record create/update` 用 fileToken 写入 | **严禁直接传图片 URL 到附件字段**(服务端同步下载会超时) | | 文件级导入 | `import upload` + `import data` | 不要手动解析 xlsx 再逐条写入 | ## 4. 创建/修改后回读确认 diff --git a/skills/multi/dingtalk-aitable/references/aitable/aitable-cell-value.md b/skills/multi/dingtalk-aitable/references/aitable/aitable-cell-value.md index 9f747bf9..bc3a8125 100644 --- a/skills/multi/dingtalk-aitable/references/aitable/aitable-cell-value.md +++ b/skills/multi/dingtalk-aitable/references/aitable/aitable-cell-value.md @@ -86,11 +86,16 @@ {"fldDateId": "2026-03-15T09:00+08:00"} ``` -**读取**:RFC3339 字符串 +**读取**:RFC3339 字符串(带时区) ```json {"fldDateId": "2026-03-15T09:00:00+08:00"} ``` +**过滤**(`record query --filters`):日期字段**只能用日期专用操作符** `date_eq` / `before` / `after` / `not_before` / `not_after` / `exist` / `un_exist`,比较值用日期字符串(如 `"2026-03-15"`)。 +- ❌ 通用 `eq` / `ne` / `gt` / `gte` / `lt` / `lte` / `contain` 对日期字段无效,会静默返回 0 条; +- ❌ 不支持区间 `date_between` 与相对 `from_now`(CLI 会直接拒绝),范围查询用 `not_before` + `not_after` 组合。 +- 详见 [aitable-filter-sort.md](./aitable-filter-sort.md) §日期字段过滤。 + --- ### currency(货币) @@ -236,15 +241,14 @@ ### attachment(附件) -**写入**:对象数组,支持 `fileToken` 或 `url` 形式 +**写入**:对象数组,**必须使用 `fileToken`** ```json {"fldAttachId": [{"fileToken": "ft_xxx"}]} -{"fldAttachId": [{"url": "https://example.com/file.pdf"}]} ``` -> ⚠️ **必须先通过 [attachment upload 流程](./aitable-attachment.md) 获取 `fileToken`**。 -> URL 形式是 best-effort 异步转存,不保证立即可用。 +> ⚠️ **必须先通过 [attachment upload 流程](./aitable-attachment.md) 上传文件获取 `fileToken`,再将 `fileToken` 写入 cells。** +> ❌ **严禁直接传 `{"url": "https://..."}` 形式写入附件/图片字段** — 服务端会同步下载图片,10 条记录即触发 TIMEOUT_ERROR 超时。 > 写入会**整体覆盖**原附件列表,不是追加。 **读取**:对象数组(含下载链接、文件名、大小) @@ -335,7 +339,7 @@ |------|----------| | cells key 用字段名称 `"课程名称"` | 用 fieldId `"fldXXX"` | | progress 写入 `75` | 写入 `0.75`(范围 0~1) | -| attachment 直接传文件路径 | 必须先 upload 获取 fileToken | +| attachment 直接传文件路径或图片 URL | 必须先 `attachment upload` 获取 fileToken,再用 fileToken 写入(直传 URL 会超时) | | user 字段传用户名字符串 | 传对象数组 `[{"userId":"...", "corpId":"..."}]` | | group 字段用 `openConversationId` | 用 `cid` | | singleSelect 传 option id 字符串 | 传 name 字符串或 `{"id":"...", "name":"..."}` 对象 | diff --git a/skills/multi/dingtalk-aitable/references/aitable/aitable-command-index.md b/skills/multi/dingtalk-aitable/references/aitable/aitable-command-index.md new file mode 100644 index 00000000..f89f0998 --- /dev/null +++ b/skills/multi/dingtalk-aitable/references/aitable/aitable-command-index.md @@ -0,0 +1,273 @@ +# 命令索引表 + +## 命令索引表 + +### base (Base 管理) → 详见 [aitable-base-index.md](./aitable-base-index.md) + +| 命令 | 用途 | 必填参数 | 路由提醒 | +|------|------|----------|----------| +| `base list` | 列出最近访问的 Base | — | 仅返回最近访问过的,优先用 `base search` | +| `base search` | 按名称搜索 Base | `--query` | 关键词 ≥2 字符 | +| `base get` | 获取 Base 信息(含 tables 列表) | `--base-id` | 用户给 URL 时提取末尾 ID | +| `base create` | 创建 Base | `--name` | 创建后直接用返回的 baseId;**默认新建的 base 自带一个空白「数据表」(含 3 行空记录)和一个空白仪表盘**,如需干净的空 base,传 `--template-id 1743` | +| `base update` | 更新 Base 名称 | `--base-id` `--name` | — | +| `base delete` | 删除 Base | `--base-id` | 不可逆 | + +### table (数据表管理) → 详见 [aitable-table-index.md](./aitable-table-index.md) + +| 命令 | 用途 | 必填参数 | 路由提醒 | +|------|------|----------|----------| +| `table get` | 获取表结构(字段+视图目录) | `--base-id` | 不传 `--table-ids` 返回全部表 | +| `table create` | 创建数据表 | `--base-id` `--name` `--fields` | fields 为 JSON 数组,至少 1 个 | +| `table update` | 修改表名 / 备注 / 行命名规则 | `--base-id` `--table-id` + 三选一(`--name` / `--description` / `--record-name-key`) | `--record-name-key` 是固定枚举(如 task/project/event/customer/ji_lu 等),非字段 ID | +| `table delete` | 删除表 | `--base-id` `--table-id` | 不可逆 | + +### field (字段管理) → 详见 [aitable-field-index.md](./aitable-field-index.md) + +| 命令 | 用途 | 必填参数 | 路由提醒 | +|------|------|----------|----------| +| `field get` | 获取字段完整配置 | `--base-id` `--table-id` | 按需展开少量字段 | +| `field create` | 创建字段 | `--base-id` `--table-id` + (`--name --type` 或 `--fields`) | 支持单字段/批量模式 | +| `field update` | 更新字段名/配置 | `--base-id` `--table-id` `--field-id` | 不可变更字段类型 | +| `field delete` | 删除字段 | `--base-id` `--table-id` `--field-id` | 不可逆 | + +#### 搜索字段选项 +``` +Usage: + dws aitable field search-options [flags] +Example: + dws aitable field search-options --base-id --table-id --field-id + dws aitable field search-options --base-id --table-id --field-id --keyword 已完成 + dws aitable field search-options --base-id --table-id --field-id --limit 100 +Flags: + --base-id string Base ID (必填) + --field-id string 目标字段 ID,必须是 singleSelect / multipleSelect 类型 (必填) + --keyword string 模糊搜索关键词,大小写不敏感、contains 匹配 option name;不传返回全部 + --limit int 返回的最大 option 数量,默认 3000(全量),最大 3000 + --table-id string Table ID (必填) +``` + +仅适用于 **singleSelect / multipleSelect** 字段。其他类型(text/number/date/...)调用会返回错误。 + +适用场景: +- options 较多,只想要含某关键词的子集(避免 `field get` 拉取整个字段配置带回所有 options)。 +- 写入 record 前预览选项 id ↔ name 的映射,确认要使用的选项确实存在。 + +> **写 record 时**:`record create / update` 对 singleSelect/multipleSelect 可直接传 option **name**,不需要用本命令。本命令主要用于 **filter** 写法(filters 优先用 option **id**)或选项较多需要精确定位时。 + +### record (记录管理) → 详见 [aitable-record-index.md](./aitable-record-index.md) + +| 命令 | 用途 | 必读 reference | 路由提醒 | +|------|------|----------------|----------| +| `record query` | 查询/搜索记录 | [aitable-record-query.md](./aitable-record-query.md) | 先 `table get` 拿 fieldId;`--all` 自动翻页;filters 结构见 reference | +| `record get` | 按 ID 取记录(`record query --record-ids` 的窄别名) | [aitable-record-query.md](./aitable-record-query.md) | 已知 recordId 时首选;必填 `--record-ids`(单次最多 100 条);未暴露 filters/sort/query/cursor/limit | +| `record create` | 新增记录 | [aitable-record-create.md](./aitable-record-create.md) | cells key 必须是 fieldId 不是字段名;单次最多 100 条 | +| `record update` | 更新记录(每条独立 cells) | [aitable-record-update.md](./aitable-record-update.md) | 需先 query 拿 recordId;只传需改字段;`--records` 是 `[{recordId,cells},...]` 数组 | +| `record batch-update` | 批量更新(同一 cells 应用到多条 recordId) | [aitable-record-update.md](./aitable-record-update.md)、[aitable-cell-value.md](./aitable-cell-value.md) | 适合"统一标记完成/统一改负责人"等共享 patch 场景;`--cells` 是 JSON object(key=fieldId,value 按字段类型见 cell-value.md),与 record update 的单条 cells 结构完全一致;必填 `--record-ids` `--cells`;单次最多 100 条 | +| `record delete` | 删除记录 | [aitable-record-delete.md](./aitable-record-delete.md) | 不可逆,需先 query 确认 | +| `record history-list` | 查询单条记录的变更历史 | [aitable-record-history.md](./aitable-record-history.md) | 必填 `--record-id`;分页 `--offset --limit`,limit 范围 [1,50] 默认 20 | +| `record query-empty` | 查询完全没填用户字段的空行 | [aitable-record-query.md](./aitable-record-query.md) | 一页扫描 `--limit` [1,100] 默认 100;扫完前需用 `--cursor` 翻页(nextCursor 为空才表扫完) | +| `record share-url` | 批量获取记录分享链接 | [aitable-record-share.md](./aitable-record-share.md) | 必填 `--record-ids`(CSV,单次最多 20 条);可选 `--view-id` 带视图上下文 | +| `record upsert` | 批量创建或更新(按 recordId 是否存在自动拆分) | [aitable-record-upsert.md](./aitable-record-upsert.md) | --records 同 record update 格式;带 recordId 走 update,不带走 create;单次最多 100 | +| `record primary-doc-get` | 查询记录的主键文档 nodeId | [aitable-primary-doc.md](./aitable-primary-doc.md) | 返回的 nodeId 可直接用于 `dws doc read/update --node` | +| `record primary-doc-create` | 为记录创建主键文档(幂等) | [aitable-primary-doc.md](./aitable-primary-doc.md) | fieldId 必须是 primaryDoc 类型;已存在则返回已有 nodeId | + +### view (视图管理) → 详见 [aitable-view-index.md](./aitable-view-index.md) + +| 命令 | 用途 | 必填参数 | 路由提醒 | +|------|------|----------|----------| +| `view get` | 获取视图配置(不传子命令) | `--base-id` `--table-id` | 不传 `--view-ids` 返回全部视图 | +| `view get ` | 获取视图某个属性 | `--view-id` | 12 个:card/timebar/aggregate/filter/sort/group/visible-fields/field-widths(详见 [aitable-view-config.md](./aitable-view-config.md))+ lock/frozen-cols/row-height/fill-color-rule(详见 [aitable-view-extras.md](./aitable-view-extras.md)) | +| `view list` | 列出全部视图(`view get` 的别名) | `--base-id` `--table-id` | 与 `view get` 完全等价 | +| `view create` | 创建视图 | `--base-id` `--table-id` `--view-type` | 类型: Grid/Kanban/Gantt/Calendar/Gallery/FormDesigner;**Gantt 创建后必须 `view update timebar` 绑定日期字段** | +| `view update` | 整体更新视图 / 多属性合并更新 | `--base-id` `--table-id` `--view-id` | 可传 `--name --desc --config '{...}'`,**`--config` 路径继续保留** | +| `view update ` | 按属性局部更新(推荐)| `--view-id` + typed flag / `--json` | 12 个:card/timebar/aggregate/field-widths/visible-fields/filter/sort/group/name + frozen-cols/row-height/fill-color-rule | +| `view lock [--off]` | 锁定/解锁视图 | `--base-id` `--table-id` `--view-id` | 默认锁定;`--off` 解锁。详见 [aitable-view-extras.md](./aitable-view-extras.md) | +| `view duplicate` | 复制视图 | `--base-id` `--table-id` `--view-id` | 可选 `--new-name`;保留源视图全部配置。详见 [aitable-view-extras.md](./aitable-view-extras.md) | +| `view delete` | 删除视图 | `--base-id` `--table-id` `--view-id` | 不可删最后一个/锁定视图 | + +> **优先用 `view get ` / `view update ` 子命令**:每个属性独立命令,typed flag 友好,agent 不必拼 JSON。**`view update --config '{...}'` 仍可用**,适合一次性多属性更新或脚本场景。 + +> **属性按 attr 分类,决定该读哪份子文档**: +> - card / timebar / aggregate / filter / sort / group / visible-fields / field-widths → [aitable-view-config.md](./aitable-view-config.md) +> - lock / frozen-cols / row-height / fill-color-rule / duplicate → [aitable-view-extras.md](./aitable-view-extras.md) +> 后一类**不能**塞进 `view update --config '{...}'`,必须用各自专属子命令;如果错传 `flags` / `frozenColCount` / `cellHeight` / `conditionalFormats` 等 key 进 `--config`,CLI 会在 stderr 提示应改用的命令。 + +> **`view update --config` 支持的 9 个 key**: +> `visibleFieldIds` / `filter` / `sort` / `group` / `fieldWidths`(Grid) / `aggregate`(Grid) / `kanbanCard`(Kanban) / `ganttTimebar`(Gantt) / `galleryCard`(Gallery)。 +> filter/sort/group 必须传**数组**格式(与 `record query --filters` 的对象格式不同;CLI 会自动容错)。其他 key 会被服务端忽略并打 warning。 + +### form (表单管理) → 详见 [aitable-form-index.md](./aitable-form-index.md) + +| 命令 | 用途 | 必填参数 | 路由提醒 | +|------|------|----------|----------| +| `form list` | 列出表单视图 | `--base-id` `--table-id` | 详情见 [aitable-form.md](./aitable-form.md) | +| `form get` | 按 viewId 取单个表单详情 | `--base-id` `--table-id` `--view-id` | — | +| `form create` | 创建表单视图 | `--base-id` `--table-id` `--name` | — | +| `form update` | 更新表单配置 | `--base-id` `--table-id` `--view-id` | title/name/description 至少一项 | +| `form delete` | 删除表单 | `--base-id` `--table-id` `--view-id` | 不可逆 | +| `form field list/update/hide` | 表单字段管理 | — | 详情见子文档 | +| `form questions create/delete` | 题目管理(=field create/delete) | — | 详情见子文档 | +| `form share get/update` | 表单分享配置 | — | 详情见子文档 | + +> **创建表单**有两种等价方式:`form create --name "..."`(推荐)或 `view create --view-type FormDesigner --name "..."`。 + +### workflow (自动化工作流) → 详见 [aitable-workflow-index.md](./aitable-workflow-index.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-index.md](./aitable-dashboard-index.md) + +| 命令 | 用途 | +|------|------| +| `dashboard get/create/update/delete` | 仪表盘管理 | +| `dashboard config-example` | 查看仪表盘配置模板 | +| `dashboard arrange` | 自动重排仪表盘图表布局(智能填满网格,避免空缺) | +| `chart get/create/update/delete` | 图表管理 | +| `chart widgets-example` | 查看图表 widgets 配置模板 | + +### export & import → 详见 [aitable-export-import-index.md](./aitable-export-import-index.md) + +| 命令 | 用途 | +|------|------| +| `export data` | 导出数据(异步两阶段轮询) | +| `import upload` | 申请文件导入上传凭证 | +| `import data` | 触发导入 | + +### attachment → 详见 [aitable-attachment-index.md](./aitable-attachment-index.md) + +| 命令 | 用途 | 路由提醒 | +|------|------|----------| +| `attachment upload` | 准备附件上传凭证 | 不要用钉盘 drive 上传! | + +### template (模板搜索) + +| 命令 | 用途 | 必填参数 | +|------|------|----------| +| `template search` | 搜索模板 | `--query` | + +### advperm (高级权限/自定义角色) → 详见 [aitable-advperm-index.md](./aitable-advperm-index.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 | + +> **角色 CRUD 已全支持**:create/get/list/update/delete 都可走 CLI。 +> 所有写命令(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 与父级关系。 + +#### 创建文件夹 +``` +Usage: + dws aitable section create [flags] +Example: + dws aitable section create --base-id --name 我的文件夹 + dws aitable section create --base-id --name 子文件夹 --parent-section-id --index 0 +Flags: + --base-id string Base ID (必填) + --name string 文件夹名称 (必填) + --parent-section-id string 父文件夹 ID;不传或空字符串表示创建在 Base 根目录下 + --index int 在父文件夹下的目标位置(0-based);不传则追加到末尾 +``` + +返回 `data.sectionId` 与 `data.name`。 + +#### 重命名文件夹 +``` +Usage: + dws aitable section rename [flags] +Example: + dws aitable section rename --base-id --section-id --new-name 新名称 +Flags: + --base-id string Base ID (必填) + --section-id string 目标文件夹 ID (必填) + --new-name string 新的文件夹名称 (必填) +``` + +#### 删除文件夹 +``` +Usage: + dws aitable section delete [flags] +Example: + dws aitable section delete --base-id --section-id +Flags: + --base-id string Base ID (必填) + --section-id string 目标文件夹 ID (必填) +``` + +> **注意**:删除不可逆;删除前可先用 `section list-empty` 确认是否为空文件夹。 + +#### 调整文件夹顺序 +``` +Usage: + dws aitable section reorder [flags] +Example: + dws aitable section reorder --base-id --section-id --target-index 0 +Flags: + --base-id string Base ID (必填) + --section-id string 目标文件夹 ID (必填) + --target-index int 目标位置(0-based)(必填) +``` + +> 在**当前父文件夹下**调整展示顺序。跨父级移动请用 `section move-node`。 + +#### 列出空文件夹 +``` +Usage: + dws aitable section list-empty [flags] +Example: + dws aitable section list-empty --base-id +Flags: + --base-id string Base ID (必填) +``` + +返回 `data.items: [{sectionId, name, parentSectionId}]` 与 `data.total`,用于清理或诊断导航树(parentSectionId 为空串表示在根目录下)。 + +#### 列出全部节点 +``` +Usage: + dws aitable section list-nodes [flags] +Example: + dws aitable section list-nodes --base-id +Flags: + --base-id string Base ID (必填) +``` + +返回 `data.items: [{nodeId, nodeType, parentSectionId, name?}]` 与 `data.total`,涵盖文件夹 / AI 表格 / 表单视图 / 仪表盘 / 文档 / 查询视图。 + +> **与其他命令的关联**:是 `section move-node` / `section reorder` 的前置定位命令——先用它拿到 nodeId 与 parentSectionId。 + +#### 移动节点 +``` +Usage: + dws aitable section move-node [flags] +Example: + dws aitable section move-node --base-id --node-id --new-parent-section-id + dws aitable section move-node --base-id --node-id --new-parent-section-id "" --target-index 0 +Flags: + --base-id string Base ID (必填) + --node-id string 要移动的节点 ID(文件夹/AI表格/表单视图/仪表盘/文档/查询视图)(必填) + --new-parent-section-id string 目标父文件夹 ID;空字符串表示移到 Base 根目录 (必填) + --target-index int Base 内节点的全局位置(0-based);不传则不调整 +``` + +> 服务端自动识别节点类型,无需区分文件夹与非文件夹。返回 `data.nodeId / newParentSectionId / nodeType`。 +> 对文件夹节点带 `--target-index` 时会先 move 再 reorder,中间失败会返回 `MOVE_OK_REORDER_FAILED`,可用 `section reorder` 重试。 diff --git a/skills/multi/dingtalk-aitable/references/aitable/aitable-complex-ops.md b/skills/multi/dingtalk-aitable/references/aitable/aitable-complex-ops.md new file mode 100644 index 00000000..6d8f0c3a --- /dev/null +++ b/skills/multi/dingtalk-aitable/references/aitable/aitable-complex-ops.md @@ -0,0 +1,39 @@ +# 复杂操作 + +## 复杂操作 + +### 仪表盘 / 图表(建议顺序) + +```bash +# 1) 先看配置模板(JSONC) +dws aitable dashboard config-example --format json +dws aitable chart widgets-example --format json + +# 2) 先拿 dashboard,再拿 chart 详情 +dws aitable dashboard get --base-id --dashboard-id --format json +dws aitable chart get --base-id --dashboard-id --chart-id --format json +``` + +要点: + +- `dashboard get` 返回的 `charts[].chartId` 可直接给 `chart get` 使用。 +- `dashboard share get` 可能返回 `404`(资源不存在或未开通),需按可重试错误处理,不要误判为参数拼错。 +- `chart share get` 可正常返回 `enabled/shareUrl`,用于分享状态判断。 + +### 导出数据(两阶段轮询) + +`export data` 常见为异步任务:首次调用可能只返回 `taskId`,需要继续轮询。 + +```bash +# 第一步:创建任务(按 scope 传必要参数) +dws aitable export data --base-id --scope table --table-id --format excel --timeout-ms 1000 + +# 第二步:拿 taskId 继续轮询,直到返回 downloadUrl +dws aitable export data --base-id --task-id --timeout-ms 3000 +``` + +参数约束 + +- `scope=all`:只需 `base-id` +- `scope=table`:必须 `table-id` +- `scope=view`:必须同时 `table-id + view-id` diff --git a/skills/multi/dingtalk-aitable/references/aitable/aitable-context.md b/skills/multi/dingtalk-aitable/references/aitable/aitable-context.md new file mode 100644 index 00000000..bbcf5e2d --- /dev/null +++ b/skills/multi/dingtalk-aitable/references/aitable/aitable-context.md @@ -0,0 +1,13 @@ +# 上下文传递表 + +## 上下文传递表 + +| 操作 | 从返回中提取 | 用于 | +|------|-------------|------| +| `base list/search` | `baseId` | 所有后续命令的 --base-id,拼接文档 URI | +| `base create` | `baseId` | 后续命令 + 文档 URI | +| `base get` | `tables[].tableId` | --table-id,拼接指定数据表 URI | +| `table create` | `tableId` | 后续命令 + 拼接指定数据表 URI | +| `table get` | `fields[].fieldId` | record 操作的 cells key, field get/update/delete | +| `record query` | `recordId` | record update/delete;按 ID 反查字段值用 `record get` | +| `template search` | `templateId` | base create --template-id,拼接模板预览 URI | 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 c6e4a1e7..94d576e2 100644 --- a/skills/multi/dingtalk-aitable/references/aitable/aitable-dashboard-chart.md +++ b/skills/multi/dingtalk-aitable/references/aitable/aitable-dashboard-chart.md @@ -15,10 +15,8 @@ dws aitable chart get --base-id --dashboard-id --chart- ## 要点 - `dashboard get` 返回的 `charts[].chartId` 可直接给 `chart get` 使用 -- `dashboard share get` 可能返回 `404`(`retryable:true`)——从未分享、甚至刚开启分享后立即查都可能 404;`data` 为 `{}`。按可重试错误处理,不要误判为参数拼错。它有时也会返回 `success + data`(如关闭分享之后),行为不稳定,别用它当"是否已分享"的唯一判据。 -- `chart share get` 稳定返回 `success + data`(含 `enabled` 等),可用于分享状态判断;从未分享时 `enabled=false`,不会 404。 -- ⚠️ **`dashboard share update` 开启 ORG 分享后返回的 `shareType` 是 `"[1]"`(未映射回 `ORG`,服务端已知问题)**;`chart share update --share-type ORG` 则正确返回 `shareType="ORG"`。判断 dashboard 是否 ORG 分享时对 `"[1]"` 做兼容。 -- `chart share update` / `dashboard share update` 的 `--enabled` 是字符串 flag:`--enabled false`(空格)和 `--enabled=false` 都能正确关闭分享。 +- `dashboard share get` 可能返回 `404`(资源不存在或未开通),需按可重试错误处理,不要误判为参数拼错 +- `chart share get` 可正常返回 `enabled/shareUrl`,用于分享状态判断 ## dashboard 子命令 @@ -27,25 +25,19 @@ dws aitable chart get --base-id --dashboard-id --chart- | `dashboard get` | 获取仪表盘详情(含 charts 列表) | `--base-id` `--dashboard-id` | — | | `dashboard create` | 创建仪表盘 | `--base-id` + (`--config` 或 `--name`) | `--name` 简化版创建空看板;`--config` 传完整 JSON | | `dashboard update` | 更新仪表盘 | `--base-id` `--dashboard-id` + (`--config` 或 `--name`) | `--name` 仅改名;`--config` 更新完整配置 | -| `dashboard arrange` | 自动重排仪表盘图表布局 | `--base-id` `--dashboard-id` | 让服务端重新排布 charts 位置 | | `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` 用 `ORG`(ORG 回显 `shareType="[1]"`);部分组织禁用了 `PUBLIC` 公开分享;若报 `Illegal argument`,改用 `ORG`(组织内分享)。`--enabled false` 关闭 | +| `dashboard arrange` | 自动重排图表布局 | `--base-id` `--dashboard-id` | 把图表按行铺满网格,避免某行只占半幅、留下大片空白;返回 `{totalColumns, layout, alignedChartCount}` | ## chart 子命令 | 命令 | 用途 | 必填参数 | |------|------|----------| | `chart get` | 获取图表详情 | `--base-id` `--dashboard-id` `--chart-id` | -| `chart create` | 创建图表 | `--base-id` `--dashboard-id` `--config` `--layout` | +| `chart create` | 创建图表 | `--base-id` `--dashboard-id` `--config` | | `chart update` | 更新图表配置 | `--base-id` `--dashboard-id` `--chart-id` `--config` | | `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`,用 `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-aitable/references/aitable/aitable-dashboard-index.md b/skills/multi/dingtalk-aitable/references/aitable/aitable-dashboard-index.md new file mode 100644 index 00000000..473bd567 --- /dev/null +++ b/skills/multi/dingtalk-aitable/references/aitable/aitable-dashboard-index.md @@ -0,0 +1,6 @@ +# 仪表盘与图表 + +| 命令 | 用途 | 详见 | +|---|---|---| +| `dashboard get/create/update/delete/arrange/config-example` | 仪表盘管理 | [`aitable-dashboard-chart.md`](./aitable-dashboard-chart.md) | +| `chart get/create/update/delete/widgets-example` | 图表管理 | [`aitable-dashboard-chart.md`](./aitable-dashboard-chart.md) | diff --git a/skills/multi/dingtalk-aitable/references/aitable/aitable-export-import-index.md b/skills/multi/dingtalk-aitable/references/aitable/aitable-export-import-index.md new file mode 100644 index 00000000..ca6b192e --- /dev/null +++ b/skills/multi/dingtalk-aitable/references/aitable/aitable-export-import-index.md @@ -0,0 +1,6 @@ +# 导入导出 + +| 命令 | 用途 | 详见 | +|---|---|---| +| `export data` | 导出数据 | [`aitable-export-import.md`](./aitable-export-import.md) | +| `import upload/data` | 触发导入 | [`aitable-export-import.md`](./aitable-export-import.md) | diff --git a/skills/multi/dingtalk-aitable/references/aitable/aitable-export-import.md b/skills/multi/dingtalk-aitable/references/aitable/aitable-export-import.md index 0d6e8317..126e0425 100644 --- a/skills/multi/dingtalk-aitable/references/aitable/aitable-export-import.md +++ b/skills/multi/dingtalk-aitable/references/aitable/aitable-export-import.md @@ -113,32 +113,3 @@ dws aitable import data --import-id --table-id --format js > **导入数据无法整体撤销**:文件一旦导入成功,数据即写入表中,没有"撤销导入"操作。如需清理导入的测试数据,只能手动通过 `record delete` 逐条或批量删除记录;如果是新建表模式导入的,可以直接 `table delete` 删除整张表。因此: > - 测试/验证场景建议导入到**独立的测试表或测试 Base**,用完后整体删除 > - 如果用户明确表示不想导入测试数据或要求先预览内容再决定,应先解析文件内容展示给用户确认,而非直接导入 - ---- - -## 导入数据:两条链路怎么选 - -用户给文件让导入 AI 表格时,路径选择决定成败: - -> ⚠️ **`import upload` 没有 `--file` flag**(传了会报 unknown flag)。它只申请上传凭证,必填 `--file-name` + `--file-size`,拿到 `uploadUrl` 后要**自己 curl PUT 上传文件**(Content-Type 留空,见上文三步流程),再 `import data`。想省事直接用 `aitable_import_via_task.py` 脚本,它把这三步包好了。 - -| 用户原话 | 链路 | 命令 / 脚本 | 行为 | -|---------|------|------------|------| -| "把这个 Excel 导入到 AI 表格"(无指定目标表) | **文件导入任务** | `python scripts/aitable_import_via_task.py `(推荐)或手动三步 `import upload --file-name x.xlsx --file-size <字节>` → curl PUT → `import data --import-id ` | 服务端解析文件,**新建数据表**,自动识别表头 | -| "把这个 Excel 导入新表 / 自动建表" | 同上 | 同上 | 同上 | -| "把这批记录追加到已有的『成员表』里" | **记录批量写入** | `python scripts/import_records.py ` | 走 `record create`,**写入已有 tableId**,需要字段名匹配 | -| "Excel 列名和表字段对不上但要追加" | 文件导入 + 追加 + 字段映射 | 三步导入后 `import data --import-id --table-id --field-mapping '{"目标":"源"}'` | 服务端按映射追加 | - -## 大表 / 长任务超时续等 - -单次等待窗口很短:`export data` 只有 `--timeout-ms`(默认且**上限 30000 = 30 秒**,没有 `--timeout-sec`,传了会报 unknown flag);`import data` 用 `--timeout`(秒,默认且推荐最大值 30)。窗口内没跑完,命令会返回 `taskId` / `importId`,用同命令带 ID 反复续等即可: - -```bash -# 续等导出:拿到 downloadUrl 后再 curl 下载(见下方警告) -dws aitable export data --base-id --task-id --timeout-ms 30000 - -# 续等导入 -dws aitable import data --import-id -``` - -> ⚠️ **`--output` 不会保存导出的 xlsx**:`--output` 是隐藏的全局 flag,作用是把命令的 **JSON 输出**写到文件,实测只生成一个 0 字节文件,不会下载导出内容。正确做法是从 `export data` 返回里取 `downloadUrl`,再 `curl -L "" -o out.xlsx` 下载。想省事直接用 `aitable_export_via_task.py` 脚本(它负责轮询 + 下载)。 diff --git a/skills/multi/dingtalk-aitable/references/aitable/aitable-field-index.md b/skills/multi/dingtalk-aitable/references/aitable/aitable-field-index.md new file mode 100644 index 00000000..c4f9469d --- /dev/null +++ b/skills/multi/dingtalk-aitable/references/aitable/aitable-field-index.md @@ -0,0 +1,9 @@ +# 字段管理 + +| 命令 | 用途 | 详见 | +|---|---|---| +| `field get` | 获取字段完整配置 | [`aitable-field.md`](./aitable-field.md) | +| `field create` | 创建字段 | [`aitable-field.md`](./aitable-field.md) | +| `field update` | 更新字段名/配置 | [`aitable-field.md`](./aitable-field.md) | +| `field delete` | 删除字段 | [`aitable-field.md`](./aitable-field.md) | +| `field search-options` | 搜索字段选项 | [`aitable-field-properties.md`](./aitable-field-properties.md) | diff --git a/skills/multi/dingtalk-aitable/references/aitable/aitable-filter-sort.md b/skills/multi/dingtalk-aitable/references/aitable/aitable-filter-sort.md index 8819a31f..8fabf8b4 100644 --- a/skills/multi/dingtalk-aitable/references/aitable/aitable-filter-sort.md +++ b/skills/multi/dingtalk-aitable/references/aitable/aitable-filter-sort.md @@ -1,5 +1,7 @@ # filters & sort — 筛选排序语法参考 +> 视图(view)配置的 filter/sort/group **整体写入**请优先用 `view update filter` / `view update sort` / `view update group` 子命令,详见 [aitable-view-config.md](./aitable-view-config.md)。本文件聚焦于 `record query --filters` 与 view config filter 的语法和差异。 + ## filters 结构规范 ### 强制规则 @@ -49,11 +51,34 @@ CLI 同时兼容两种子条件写法(推荐格式 A): | `exist` / `un_exist` | 有值 / 为空 | `["fieldId"]`(无需第二项) | | `any_of` / `none_of` / `all_of` | 包含任一 / 不包含任一 / 全包含(多选字段) | `["fieldId", "optionName"]` | | `date_eq` / `before` / `after` | 日期等于 / 早于 / 晚于 | `["fieldId", "dateStr"]` | -| `not_before` / `not_after` | 不早于 / 不晚于 | `["fieldId", "dateStr"]` | -| `from_now` | 从现在起 N 天内 | `["fieldId", "天数"]` | -| `date_between` | 日期区间 | `["fieldId", "[startTs, endTs]"]` | +| `not_before` / `not_after` | 不早于(≥) / 不晚于(≤) | `["fieldId", "2026-05-22"]` | > **操作符拼写必须严格匹配上表**,CLI 会在调用前校验,错误拼写会被拒绝。 +> +> **没有 `date_between`(区间)操作符**,也**不支持 `from_now`**——date 字段不支持区间/相对过滤,传了会被 CLI 拒绝。范围查询用 `not_before` + `not_after` 组合,见下方专节。 + +### 日期字段过滤(date / 创建时间 / 修改时间) + +日期类字段的过滤规则与其它字段**不同**,是线上反馈最高频的踩坑点。**经集成测试实测**确认的规则: + +1. **只能用日期专用操作符**:`date_eq` / `before` / `after` / `not_before` / `not_after` / `exist` / `un_exist`(与前端筛选 UI 的「等于 / 早于 / 晚于 / 早于或等于 / 晚于或等于 / 不为空 / 为空」一一对应)。 +2. **比较值用日期字符串**,如 `"2026-05-22"`(也接受 RFC3339 / 毫秒时间戳,内部统一转成毫秒比较)。读取返回的是带时区 RFC3339(如 `"2026-05-22T00:00:00+08:00"`)。 +3. **通用操作符 `eq` / `ne` / `gt` / `gte` / `lt` / `lte` / `contain` 对 date 字段无效**——无论传 ISO 字符串还是毫秒时间戳,都会**静默返回 0 条**。这是后端 date 字段的比较规则,不是 bug,CLI 也无法在本地拦截(不知道字段类型),务必用对操作符。 +4. **没有区间操作符 `date_between`**,也**不支持 `from_now`(相对天数)**——均会静默返回 0 条,CLI 已直接拒绝。范围查询用 `not_before`(≥起点)+ `not_after`(≤终点)两个条件 `and` 组合。 + +| 需求 | 操作符 | 示例 operands | +|------|--------|--------------| +| 等于某天 | `date_eq` | `["fldDate", "2026-05-22"]` | +| 早于 / 晚于(不含当天) | `before` / `after` | `["fldDate", "2026-05-22"]` | +| 不早于(≥) / 不晚于(≤) | `not_before` / `not_after` | `["fldDate", "2026-05-22"]` | +| 有值 / 为空 | `exist` / `un_exist` | `["fldDate"]` | + +**日期区间查询(替代 between)**——查 `2026-05-01 ~ 2026-05-31`(含端点): + +```bash +dws aitable record query --base-id X --table-id Y \ + --filters '{"operator":"and","operands":[{"operator":"not_before","operands":["fldDate","2026-05-01"]},{"operator":"not_after","operands":["fldDate","2026-05-31"]}]}' +``` ### 常见错误拼写(CLI 会自动提示纠正) @@ -104,3 +129,41 @@ dws aitable record query --base-id X --table-id Y \ ```bash --sort '[{"fieldId":"fldPriority","direction":"desc"},{"fieldId":"fldCreatedAt","direction":"asc"}]' ``` + +--- + +## view update --config 中的 filter / sort 格式 + +> **重要区分**:`record query --filters` 和 `view update --config` 中的 filter **格式不同**! + +| 场景 | filter 格式 | 说明 | +|------|-------------|------| +| `record query --filters` | **对象**:`{"operator":"and","operands":[...]}` | 直接传最外层逻辑对象 | +| `view update --config` 的 filter | **数组**:`[{"operator":"and","operands":[...]}]` | 外面多一层数组包裹 | +| `view update --config` 的 sort | **数组**:`[{"fieldId":"X","direction":"asc"}]` | 与 record query --sort 一致 | + +### 正确示例 + +```bash +# view update 设置筛选(filter 是数组) +dws aitable view update --base-id X --table-id Y --view-id Z \ + --config '{"filter":[{"operator":"and","operands":[{"operator":"eq","operands":["fldStatus","待处理"]}]}]}' + +# view update 设置排序(sort 是数组) +dws aitable view update --base-id X --table-id Y --view-id Z \ + --config '{"sort":[{"fieldId":"fldPriority","direction":"desc"}]}' + +# 同时设置 filter + sort + visibleFieldIds +dws aitable view update --base-id X --table-id Y --view-id Z \ + --config '{"filter":[{"operator":"and","operands":[{"operator":"eq","operands":["fldStatus","进行中"]}]}],"sort":[{"fieldId":"fldDate","direction":"asc"}],"visibleFieldIds":["fld1","fld2","fld3"]}' +``` + +### CLI 自动容错 + +CLI 会自动修正以下常见错误格式(不会报错,但建议直接使用正确格式): + +| 错误写法 | CLI 自动修正为 | +|----------|---------------| +| `"filter":{"operator":"and",...}` (对象) | `"filter":[{"operator":"and",...}]` (数组) | +| `"sort":{"fieldId":"X","direction":"asc"}` (对象) | `"sort":[{"fieldId":"X","direction":"asc"}]` (数组) | +| 子条件用 MCP 简写 `{"fieldId":"X","operator":"eq","value":"Y"}` | 自动转为 `{"operator":"eq","operands":["X","Y"]}` | diff --git a/skills/multi/dingtalk-aitable/references/aitable/aitable-form-index.md b/skills/multi/dingtalk-aitable/references/aitable/aitable-form-index.md new file mode 100644 index 00000000..78aaf4e7 --- /dev/null +++ b/skills/multi/dingtalk-aitable/references/aitable/aitable-form-index.md @@ -0,0 +1,8 @@ +# 表单管理 + +| 命令 | 用途 | 详见 | +|---|---|---| +| `form list/get/create/update/delete` | 表单视图管理 | [`aitable-form.md`](./aitable-form.md) | +| `form field list/update/hide` | 表单字段管理 | [`aitable-form.md`](./aitable-form.md) | +| `form questions create/delete` | 题目管理 | [`aitable-form.md`](./aitable-form.md) | +| `form share get/update` | 表单分享配置 | [`aitable-form.md`](./aitable-form.md) | diff --git a/skills/multi/dingtalk-aitable/references/aitable/aitable-form.md b/skills/multi/dingtalk-aitable/references/aitable/aitable-form.md index 2aaedba6..2b126d8f 100644 --- a/skills/multi/dingtalk-aitable/references/aitable/aitable-form.md +++ b/skills/multi/dingtalk-aitable/references/aitable/aitable-form.md @@ -50,8 +50,8 @@ dws aitable form share get --base-id BASE_ID --table-id TABLE_ID --view-id VIEW_ | 命令 | 用途 | 必填参数 | 说明 | |------|------|----------|------| -| `form list` | 列出表单视图 | `--base-id` `--table-id` | 每条返回 viewId/name;新建表单**无 title 且 createdAt=0**,改过(form update)后才出现 title 和真实 createdAt | -| `form get` | 按 viewId 取单个表单 | `--base-id` `--table-id` `--view-id` | 客户端按 viewId 过滤后 `data` **即该表单对象**(不是 formViews 数组);viewId 不存在返回 `form view ... not found` 错误 | +| `form list` | 列出表单视图 | `--base-id` `--table-id` | 返回 viewId/name/title/createdAt | +| `form get` | 按 viewId 取单个表单 | `--base-id` `--table-id` `--view-id` | 内部基于 list_form_views 过滤 | | `form create` | 创建表单视图 | `--base-id` `--table-id` `--name` | viewType=FormDesigner | | `form update` | 更新表单 | `--base-id` `--table-id` `--view-id` | `--title`/`--name`(等价)和 `--description` 至少传一项;同时传 title/name 时 title 优先 | | `form delete` | 删除表单 | `--base-id` `--table-id` `--view-id` `--yes` | 不可逆 | @@ -115,6 +115,6 @@ dws aitable form share update --base-id BASE_ID --table-id TABLE_ID --view-id VI ## 返回结构补充 -- `form list` 返回 `data.formViews[]`,**每条含** `viewId/name`(+ `createdAt`);**新建表单没有 `title` 字段且 `createdAt=0`**,只有在 `form update` 碰过之后才会出现 `title` 和真实 `createdAt`。`shareFormUuid` 不在此返回,请用 `form share get` 单独获取。 -- `form get` 的 `data` **就是命中的那一条表单对象**(如 `{viewId, name, createdAt, title?, shareFormUuid?}`),不是 `formViews` 数组。Agent 直接读 `data.viewId` / `data.name` 即可,**不要**再取 `data.formViews[0]`。服务端的 viewIds 过滤参数当前不生效,CLI 在客户端按 viewId 精确筛出单条;传了不存在的 viewId 会返回 `form view not found in table` 错误。 +- `form list` 返回 `data.formViews[]`,**每条仅含** `viewId/name/title/createdAt`;`shareFormUuid` 不在此返回,请用 `form share get` 单独获取。 +- `form get` 返回结构与 `form list` 完全一致(`data.formViews[]`),仅含一条记录(与请求 viewId 一致)。Agent 提取时仍走 `data.formViews[0]`。 - `form field list` 仅返回**未隐藏**的字段;`hidden=true` 的字段不在此返回,如需查看全部字段请用 `field get`。 diff --git a/skills/multi/dingtalk-aitable/references/aitable/aitable-intent.md b/skills/multi/dingtalk-aitable/references/aitable/aitable-intent.md new file mode 100644 index 00000000..244998dc --- /dev/null +++ b/skills/multi/dingtalk-aitable/references/aitable/aitable-intent.md @@ -0,0 +1,93 @@ +# 意图判断 + +## 意图判断 + +用户说"表格/多维表/AI表格": +- 查看/查找/列表 → `base search`(优先)或 `base list`(仅浏览最近访问) +- 详情 → `base get` +- 创建 → `base create` +- 修改 → `base update` +- 删除 → `base delete` + +用户说"数据表/子表/table": +- 查看 → `table get` +- 创建 → `table create` +- 重命名 / 改备注 / 改行命名规则 → `table update`(三选一:`--name` / `--description` / `--record-name-key`) +- 用户说"行命名规则/记录别名/卡片显示成 task/project/event 这种" → `table update --record-name-key <枚举键>`,**中文 → 枚举键**对照见 [aitable-record-name-key.md](./aitable-record-name-key.md) +- 删除 → `table delete` + +用户说"字段/列/column": +- 查看 → `field get` +- 添加 → `field create`(读 [aitable-field.md](./aitable-field.md)) +- 修改 → `field update` +- 删除 → `field delete` + +用户说"记录/行/数据/row": +- 查看/搜索 → `record query`(读 [aitable-record-query.md](./aitable-record-query.md)) +- 找空行 / 没填东西的行 → `record query-empty`(读 [aitable-record-query.md](./aitable-record-query.md)) +- 已知 recordId 反查字段值 → `record get`(按 ID 取专用,等价 `record query --record-ids`) +- 添加/写入 → `record create`(读 [aitable-record-create.md](./aitable-record-create.md)) +- 修改/更新(每条独立 cells) → `record update`(读 [aitable-record-update.md](./aitable-record-update.md)) +- **批量更新同一字段值**(统一标记/统一改值) → `record batch-update --record-ids ... --cells '{...}'` +- 删除 → `record delete` +- **查记录的字段变更历史 / 操作审计** → `record history-list`(读 [aitable-record-history.md](./aitable-record-history.md)) +- **取记录分享链接 / 把这行发给同事** → `record share-url`(读 [aitable-record-share.md](./aitable-record-share.md)) +- **不知道有没有 → 有就改、没有就建** → `record upsert`(读 [aitable-record-upsert.md](./aitable-record-upsert.md)) + +用户说"视图/view": +- 列出/查看全部视图 → `view list`(或 `view get` 不传 --view-ids,二者等价) +- 看某个视图详情 → `view get --view-ids ` +- 创建 → `view create` +- 修改(含"调整字段顺序/隐藏字段") → `view update --config '{"visibleFieldIds":[...]}'` +- 修改某一项配置(filter/sort/group/card/timebar/aggregate 等)→ `view update `(读 [aitable-view-config.md](./aitable-view-config.md)) +- 锁定 / 冻结列 / 行高 / 数据高亮规则 / 复制视图 → 读 [aitable-view-extras.md](./aitable-view-extras.md) +- 删除 → `view delete` + +用户说"锁定视图/解锁视图/lock view" → `view lock` / `view lock --off`,详见 [aitable-view-extras.md](./aitable-view-extras.md) + +用户说"冻结列/冻结首列/frozen columns" → `view update frozen-cols --count N`,详见 [aitable-view-extras.md](./aitable-view-extras.md) + +用户说"行高/单元格高度/紧凑模式/cell height" → `view update row-height --cell-height N`(合法档位 32/56/88/128),详见 [aitable-view-extras.md](./aitable-view-extras.md) + +用户说"数据高亮/条件格式/单元格上色/fill color rule" → `view update fill-color-rule --json '[...]'`,详见 [aitable-view-extras.md](./aitable-view-extras.md) + +用户说"复制视图/duplicate view" → `view duplicate --view-id ... [--new-name ...]`,详见 [aitable-view-extras.md](./aitable-view-extras.md) + +用户说"筛选/过滤/filter" → 读 [aitable-filter-sort.md](./aitable-filter-sort.md) + +用户说"统计/分析/聚合/TOP N/全量" → 读 [aitable-data-analysis-sop.md](./aitable-data-analysis-sop.md) + +用户说"公式/formula/计算字段/派生指标" → 读 [aitable-formula-guide.md](./aitable-formula-guide.md) + +用户说"查找引用/lookup/filterUp/跨表" → 读 [aitable-formula-guide.md](./aitable-formula-guide.md)(§5.4 跨表引用) + +用户说"表单/form/收集表/问卷/催办填写" → 读 [aitable-form.md](./aitable-form.md) + +用户说"自动化/工作流/流程/触发/automation/workflow" → 读 [aitable-workflow.md](./aitable-workflow.md) +- 看 Base 里有哪些流程 / 哪些在跑 → `workflow list`(看 `recordCount` / `runningCount`) +- 看某个流程具体配置(触发条件、动作步骤) → `workflow get` +- 启用流程 → `workflow enable` +- 临时停掉流程(调试 / 数据迁移)→ `workflow disable --yes` +- **新建 / 修改 / 删除流程**:当前不支持,引导用户到 AI 表格 Web 端 → 数据表 → 自动化 面板手动完成 + +用户说"仪表盘/图表/chart" → 读 [aitable-dashboard-chart.md](./aitable-dashboard-chart.md) + +用户说"仪表盘排版乱了/图表对不齐/重新排布/自动布局/美化仪表盘" → `dashboard arrange`(读 [aitable-dashboard-chart.md](./aitable-dashboard-chart.md)) + +用户说"附件/上传文件" → 读 [aitable-attachment.md](./aitable-attachment.md) + +用户说"导入/导出/import/export" → 读 [aitable-export-import.md](./aitable-export-import.md) + +用户说"模板" → `template search` + +用户说"高级权限/角色/权限控制/谁能看/谁能改" → 读 [aitable-advperm.md](./aitable-advperm.md) +- 开/关高级权限 → `advperm enable` / `advperm disable --yes` +- 看角色配置 → `advperm role-list` 或 `advperm role-get` +- 建角色(可同时指定子角色权限) → `advperm role-create --name ... --sub-roles '[...]'` +- 改角色名 / 改子角色权限(PATCH 语义,未传字段不变) → `advperm role-update --role-id ... [--name ...] [--sub-roles '[...]']` +- 删角色 → `advperm role-delete --yes` +- **角色 ↔ 成员绑定**:当前 CLI 不支持,仍需在 AI 表格 Web 端面板手动完成 + +命令报错/操作失败 → 读 [aitable-error-recovery.md](./aitable-error-recovery.md) + +**关键区分**: base=表格文件, table=数据表, field=列, record=行 diff --git a/skills/multi/dingtalk-aitable/references/aitable/aitable-notes.md b/skills/multi/dingtalk-aitable/references/aitable/aitable-notes.md new file mode 100644 index 00000000..5b38658d --- /dev/null +++ b/skills/multi/dingtalk-aitable/references/aitable/aitable-notes.md @@ -0,0 +1,8 @@ +# 注意事项 + +## 注意事项 + +- 所有操作使用 ID(baseId/tableId/fieldId/recordId),不使用名称 +- records 的 cells key 是 fieldId,不是字段名称 +- cells 写入/读取格式见 [aitable-cell-value.md](./aitable-cell-value.md) +- 最佳实践见 [aitable-best-practices.md](./aitable-best-practices.md) diff --git a/skills/multi/dingtalk-aitable/references/aitable/aitable-primary-doc.md b/skills/multi/dingtalk-aitable/references/aitable/aitable-primary-doc.md index 8ed2198d..eefda277 100644 --- a/skills/multi/dingtalk-aitable/references/aitable/aitable-primary-doc.md +++ b/skills/multi/dingtalk-aitable/references/aitable/aitable-primary-doc.md @@ -17,9 +17,7 @@ dws aitable record primary-doc-get --base-id BASE_ID --table-id TABLE_ID --recor - `--table-id`(必填):Table ID - `--record-id`(必填):Record ID -**返回:** `data.nodeId` — 主键文档的 nodeId,可直接传给 `dws doc read/update` 的 `--node` 参数。 - -> **该记录尚未创建主键文档时**:不会返回 `nodeId: null`,而是 `status=error`、`data={}`,`error={code:"-1", message:"no record", type:"SYSTEM_ERROR", retryable:true}`。要判断"有没有主键文档",看是否命中这个 `no record` 错误,而不是判断 `nodeId` 是否为 null。需要文档时改用 `primary-doc-create`(幂等,已存在则直接返回)。 +**返回:** `data.nodeId` — 主键文档的 nodeId,可直接传给 `dws doc read/update` 的 `--node` 参数。若该记录尚未创建主键文档,`nodeId` 为 null。 ### 创建主键文档 diff --git a/skills/multi/dingtalk-aitable/references/aitable/aitable-record-create.md b/skills/multi/dingtalk-aitable/references/aitable/aitable-record-create.md index eb737228..bf605899 100644 --- a/skills/multi/dingtalk-aitable/references/aitable/aitable-record-create.md +++ b/skills/multi/dingtalk-aitable/references/aitable/aitable-record-create.md @@ -27,6 +27,7 @@ Flags: | cells key 用字段名 | ❌ cells key 必须是 fieldId(如 `fldXXX`),不是字段名称(如 `"课程名称"`) | | 不先获取 fieldId | ❌ 必须先 `table get` 获取 fieldId,再写入记录 | | 单次超 100 条 | ❌ 单次最多 100 条,超过需分批 | +| 附件/图片字段直传 URL | ❌ 严禁 `{"url":"https://..."}` — 会触发 TIMEOUT_ERROR。必须先 `attachment upload` 获取 `fileToken`,再用 `{"fileToken":"ft_xxx"}` 写入。详见 [aitable-attachment.md](./aitable-attachment.md) | ## 正确流程 diff --git a/skills/multi/dingtalk-aitable/references/aitable/aitable-record-history.md b/skills/multi/dingtalk-aitable/references/aitable/aitable-record-history.md index 20154b98..f02d3d87 100644 --- a/skills/multi/dingtalk-aitable/references/aitable/aitable-record-history.md +++ b/skills/multi/dingtalk-aitable/references/aitable/aitable-record-history.md @@ -25,11 +25,12 @@ dws aitable record history-list \ "data": { "histories": [ { - "type": "row", // 变更类型,实测均为 "row"(行级变更) - "action": "updateRecords", // 操作动作:appendRow(新增行) / updateRecords(更新记录) - "newValue": "{\"fldX\":{\"dataType\":\"STRING\",\"value\":\"改后值\"}}", // 变更后的值(JSON 字符串,按 fieldId 组织) - "oldValue": "{\"fldX\":{\"dataType\":\"STRING\",\"value\":\"改前值\"}}", // 变更前的值(appendRow 新增行时无此字段) + "type": "field_change", // 变更类型 + "action": "update", // 操作动作: create / update / delete + "newValue": "{\"...\":\"...\"}", // 变更后的值(JSON 字符串) + "oldValue": "{\"...\":\"...\"}", // 变更前的值(JSON 字符串) "operateTime": 1733123456789, // 操作时间(毫秒级时间戳) + "typeChangedFields": "{...}", // 类型变更的字段信息(JSON 字符串) "version": 7 // 版本号(单调递增) } ] @@ -37,14 +38,14 @@ dws aitable record history-list \ } ``` -`newValue` / `oldValue` 是 JSON 字符串(不是 JSON 对象),需要二次 `JSON.parse` 才能拿到结构化值;解析后是 `{fieldId: {dataType, value}}` 结构。`appendRow`(新增行)事件没有 `oldValue`。实测返回里**没有** `typeChangedFields` 字段。 +`newValue` / `oldValue` / `typeChangedFields` 是 JSON 字符串(不是 JSON 对象),需要二次 `JSON.parse` 才能拿到结构化值。 ## 字段含义速查 | 字段 | 用途 | |------|------| -| `type` | 实测均为 `row`(行级变更),服务端未按 `record_create` / `field_change` 细分。 | -| `action` | 底层操作名:`appendRow`(新增行)/ `updateRecords`(更新记录)。按"动作"统计时以这两个值为准。 | +| `type` | 高层分类:`record_create` / `field_change` / `record_delete` 等。先按 type 过滤大类。 | +| `action` | 三态:`create` / `update` / `delete`。比 type 粗,但便于按"动作"统计。 | | `version` | 单调递增整数;同一 record 越新值越大。**用作"上一条 vs 这一条"的稳定排序键**。 | | `operateTime` | 毫秒时间戳;可格式化成可读时间。多条同 version 的极端场景用 operateTime 兜底排序。 | @@ -73,22 +74,20 @@ dws aitable record history-list --base-id BASE --table-id TBL --record-id REC -- ```bash dws aitable record history-list --base-id BASE --table-id TBL --record-id REC --limit 50 --format json \ - | jq '[.data.histories[] | select(.action == "updateRecords")][0].oldValue' + | jq '[.data.histories[] | select(.action == "update")][0].oldValue' ``` -### 4. 只看更新事件(排除新增行) +### 4. 找出删除事件(如果存在 delete history) ```bash dws aitable record history-list --base-id BASE --table-id TBL --record-id REC --format json \ - | jq '.data.histories[] | select(.action == "updateRecords") | {version, operateTime}' + | jq '.data.histories[] | select(.action == "delete") | {version, operateTime}' ``` -> 记录被 `record delete` 删除后,其历史不再返回(`histories` 为空数组),无法通过本命令回溯删除事件。 - ## 注意事项 - 一次只能查一条 record;如需批量审计多条记录请循环调用。 -- 仅返回**新增行**(appendRow)与**字段值变更**(updateRecords);记录被删除后其历史不再可查(返回空)。视图、字段定义、表结构变更不在此 history 里。 +- 仅返回**字段值变更**与**记录生命周期事件**;视图、字段定义、表结构变更不在此 history 里。 - 历史保留时长由 server 决定,过老的记录可能不再返回。 ## 与其他 record 命令的关系 diff --git a/skills/multi/dingtalk-aitable/references/aitable/aitable-record-index.md b/skills/multi/dingtalk-aitable/references/aitable/aitable-record-index.md new file mode 100644 index 00000000..8b68f123 --- /dev/null +++ b/skills/multi/dingtalk-aitable/references/aitable/aitable-record-index.md @@ -0,0 +1,13 @@ +# 记录管理 + +| 命令 | 用途 | 详见 | +|---|---|---| +| `record query` | 查询/搜索记录 | [`aitable-record-query.md`](./aitable-record-query.md) | +| `record get` | 按 ID 取记录 | [`aitable-record-query.md`](./aitable-record-query.md) | +| `record create` | 新增记录 | [`aitable-record-create.md`](./aitable-record-create.md) | +| `record update` | 更新记录 | [`aitable-record-update.md`](./aitable-record-update.md) | +| `record batch-update` | 批量更新 | [`aitable-record-update.md`](./aitable-record-update.md) | +| `record delete` | 删除记录 | [`aitable-record-delete.md`](./aitable-record-delete.md) | +| `record history-list` | 查询单条记录的变更历史 | [`aitable-record-history.md`](./aitable-record-history.md) | +| `record share-url` | 批量获取记录分享链接 | [`aitable-record-share.md`](./aitable-record-share.md) | +| `record upsert` | 批量创建或更新 | [`aitable-record-upsert.md`](./aitable-record-upsert.md) | diff --git a/skills/multi/dingtalk-aitable/references/aitable/aitable-record-query.md b/skills/multi/dingtalk-aitable/references/aitable/aitable-record-query.md index 8d04ab1b..20ed92da 100644 --- a/skills/multi/dingtalk-aitable/references/aitable/aitable-record-query.md +++ b/skills/multi/dingtalk-aitable/references/aitable/aitable-record-query.md @@ -72,3 +72,49 @@ dws aitable record query --base-id X --table-id Y --all --cursor "上次返回 - `--sort` 用 `"order":"desc"` → 必须用 `"direction":"desc"` - 不加 `--field-ids` 拉全字段 → 大表响应体积过大 - 全量拉取后在 context 里手动统计 → 应优先用 `--filters` 服务端过滤 + +## record query-empty — 找空行 + +`record query-empty` 是与 `record query` 平行的独立子命令,专门按表内顺序扫描出"完全没填用户字段"的空行。 + +```bash +dws aitable record query-empty --base-id BASE_ID --table-id TABLE_ID +``` + +| flag | 说明 | +|------|------| +| `--base-id` / `--base` | 必填 | +| `--table-id` | 必填 | +| `--limit` | 单次**扫描预算**(不是返回数);范围 [1, 100],默认 100 | +| `--cursor` | 分页游标。响应中 `nextCursor` 非空 → 用它翻页继续扫;nextCursor 为空(或不存在)→ 已扫完整表 | + +返回结构: + +```jsonc +{ "data": { "records": [...], "nextCursor": "..." } } +``` + +### 关键语义 + +1. **`--limit` 是扫描预算不是返回数**:可能扫了 100 条但全部非空,本页 `records: []`。 +2. **本页空 records ≠ 全表无空行**:必须看 `nextCursor`,nextCursor 还在就要继续翻。 +3. **空行定义**:除系统字段(recordId / 创建人 / 创建时间 / 修改人 / 修改时间)外,所有 cell 都是 null、空字符串、空集合或空 Map。一般是用户在 UI 上"插入空行"产生的。 + +### 典型用法 + +```bash +# 扫一页,看本页有没有空行 +dws aitable record query-empty --base-id BASE --table-id TBL + +# 翻页 +dws aitable record query-empty --base-id BASE --table-id TBL --cursor <上次的nextCursor> + +# 把整表扫完(手动循环 cursor) +NC="" +while : ; do + R=$(dws aitable record query-empty --base-id BASE --table-id TBL ${NC:+--cursor "$NC"} --format json) + echo "$R" | jq '.data.records[] | .recordId' + NC=$(echo "$R" | jq -r '.data.nextCursor // empty') + [ -z "$NC" ] && break +done +``` diff --git a/skills/multi/dingtalk-aitable/references/aitable/aitable-related.md b/skills/multi/dingtalk-aitable/references/aitable/aitable-related.md new file mode 100644 index 00000000..dd2282d5 --- /dev/null +++ b/skills/multi/dingtalk-aitable/references/aitable/aitable-related.md @@ -0,0 +1,5 @@ +# 相关产品 + +## 相关产品 + +- [doc](../../../dingtalk-doc/references/doc.md) — 富文本文档编辑,不是结构化数据表格 diff --git a/skills/multi/dingtalk-aitable/references/aitable/aitable-scripts.md b/skills/multi/dingtalk-aitable/references/aitable/aitable-scripts.md new file mode 100644 index 00000000..18e5718c --- /dev/null +++ b/skills/multi/dingtalk-aitable/references/aitable/aitable-scripts.md @@ -0,0 +1,10 @@ +# 自动化脚本 + +## 自动化脚本 + +| 脚本 | 场景 | +|------|------| +| [bulk_add_fields.py](../../scripts/bulk_add_fields.py) | 批量添加字段 | +| [import_records.py](../../scripts/import_records.py) | 从 JSON/CSV 批量导入记录 | +| [aitable_export_via_task.py](../../scripts/aitable_export_via_task.py) | 文件导出(export_data 轮询 + 下载) | +| [upload_attachment.py](../../scripts/upload_attachment.py) | 上传附件到 AI 表格记录 | diff --git a/skills/multi/dingtalk-aitable/references/aitable/aitable-table-index.md b/skills/multi/dingtalk-aitable/references/aitable/aitable-table-index.md new file mode 100644 index 00000000..39fc31c1 --- /dev/null +++ b/skills/multi/dingtalk-aitable/references/aitable/aitable-table-index.md @@ -0,0 +1,8 @@ +# 数据表管理 + +| 命令 | 用途 | 必读参数 | +|---|---|---| +| `table get` | 获取表结构(字段+视图目录) | `--base-id` | +| `table create` | 创建数据表 | `--base-id` `--name` `--fields` | +| `table update` | 修改表名 / 备注 / 行命名规则 | `--base-id` `--table-id` | +| `table delete` | 删除表 | `--base-id` `--table-id` | diff --git a/skills/multi/dingtalk-aitable/references/aitable/aitable-uri.md b/skills/multi/dingtalk-aitable/references/aitable/aitable-uri.md new file mode 100644 index 00000000..e208b572 --- /dev/null +++ b/skills/multi/dingtalk-aitable/references/aitable/aitable-uri.md @@ -0,0 +1,17 @@ +# 文档地址 (URI) + +## 文档地址 (URI) + +| 资源 | URI 格式 | +|------|----------| +| Base 文档 | `https://alidocs.dingtalk.com/i/nodes/{baseId}` | +| 指定数据表 | `https://alidocs.dingtalk.com/i/nodes/{baseId}?iframeQuery=sheetId%3D{tableId}` | +| 指定数据表+视图 | `https://alidocs.dingtalk.com/i/nodes/{baseId}?iframeQuery=sheetId%3D{tableId}%26viewId%3D{viewId}` | +| 模板预览 | `https://docs.dingtalk.com/table/template/{templateId}` | + +> **操作后请返回文档 URI**:返回链接时必须带上当前操作的数据表 tableId,让用户点击后直接看到目标数据表,而不是落在空白的默认表。 +> - 已知 tableId + viewId 时(view create 返回、view get 中提取):拼接 `https://alidocs.dingtalk.com/i/nodes/{baseId}?iframeQuery=sheetId%3D{tableId}%26viewId%3D{viewId}` +> - 已知 tableId 时(table create 返回、base get 中提取、record 操作所用的 tableId):拼接 `https://alidocs.dingtalk.com/i/nodes/{baseId}?iframeQuery=sheetId%3D{tableId}` +> - 仅有 baseId、无明确 tableId 时(如 base list/search):拼接 `https://alidocs.dingtalk.com/i/nodes/{baseId}` +> +> 补充:如果 URL 不是来自 `aitable` 命令返回,而是用户直接贴的原始 `alidocs` URL,先按 [链接规范](../url-patterns.md#alidocs-url-类型探测流程) probe,确认是 `able` 后再按 AI 表格处理。 diff --git a/skills/multi/dingtalk-aitable/references/aitable/aitable-url-to-baseid.md b/skills/multi/dingtalk-aitable/references/aitable/aitable-url-to-baseid.md new file mode 100644 index 00000000..2ed51eb1 --- /dev/null +++ b/skills/multi/dingtalk-aitable/references/aitable/aitable-url-to-baseid.md @@ -0,0 +1,11 @@ +# URL → baseId 提取 + +## URL → baseId 提取 + +用户提供 `https://alidocs.dingtalk.com/i/nodes/{baseId}` 链接时: +1. 提取 `/nodes/` 后的路径段作为 `baseId` +2. 去掉尾部的查询参数(`?` 及其后内容) +3. 传入 `--base-id` 参数 + +> 如果该 URL 来自 `dws aitable` 返回或已在当前链路 probe 过,可直接复用; +> 如果是用户直接提供的原始 `alidocs` URL,则先按 [链接规范](../url-patterns.md#alidocs-url-类型探测流程) probe,确认 `extension=able` 后再继续。 diff --git a/skills/multi/dingtalk-aitable/references/aitable/aitable-view-config.md b/skills/multi/dingtalk-aitable/references/aitable/aitable-view-config.md index d2423374..6b22ec03 100644 --- a/skills/multi/dingtalk-aitable/references/aitable/aitable-view-config.md +++ b/skills/multi/dingtalk-aitable/references/aitable/aitable-view-config.md @@ -79,21 +79,21 @@ dws aitable view update timebar --view-id GANTT_ID --official-holiday=true ### view update aggregate(仅 Grid) -值是 `map[fieldId]→AggregateAction string`。**设置**聚合可用;**清除**聚合当前无效(见下方警告)。 +值是 `map[fieldId]→AggregateAction string`;传 null 清除某个字段聚合。 | flag | 类型 | 说明 | |------|------|------| | `--field-id` | string | 配合 `--action` 设置**单字段**聚合 | | `--action` | string | `SUM`/`AVG`/`MAX`/`MIN`/`MEDIAN`/`RANGE`/`TOTAL`/`DISTINCT`/`EXIST`/`UN_EXIST`/`CHECKED`/`EARLIEST_DATE` 等(按字段类型可用) | -| `--clear-field-id` | string (CSV) | 一/多个字段 ID,本意是清除其聚合,但**当前服务端不支持清除,静默无效**(见下方警告) | +| `--clear-field-id` | string (CSV) | 一/多个字段 ID,清除其聚合 | | `--json` | JSON | 完整 aggregate map | ```bash dws aitable view update aggregate --view-id GRID_ID --field-id fldX --action SUM +dws aitable view update aggregate --view-id GRID_ID --clear-field-id fldA,fldB +dws aitable view update aggregate --view-id GRID_ID --json '{"fldX":"AVG","fldY":null}' ``` -> ⚠️ **当前无法清除已设置的聚合(服务端限制)**:`--clear-field-id fldA,fldB` 与 `--json '{"fldX":null}'` 两种清除写法都返回 `success`,但用 `view get aggregate` 复核会发现聚合**原样不动**——是静默无效,不是真的清掉。这是服务端没有清除语义所致,直至服务端修复前不要依赖它。改聚合方式可行(重新 `--action` 覆盖成别的),只是无法回到"无聚合"。 - ### view update field-widths(仅 Grid) | flag | 类型 | @@ -108,11 +108,9 @@ dws aitable view update field-widths --view-id GRID_ID --json '{"fldA":120,"fldB ### view update visible-fields(通用) -整组替换可见字段列表与顺序,同时兼作**隐藏/显示**入口。首列字段(primaryDoc)必须保留在数组第一位。 +整组替换可见字段列表与顺序。首列字段(primaryDoc)必须保留在数组第一位。 -> **传入的列表既定顺序又定可见性**:传一个比当前 columns 短的列表,缺失的字段会被真正隐藏(该字段在 `view list` 的 `custom.hiddenFields` 里变 `true`);再传回全量列表即可解除隐藏(`hiddenFields` 变 `false`)。 -> -> ⚠️ **查隐藏状态别看这里**:`view get visible-fields` 返回的数组**包含已隐藏字段**(列的完整顺序),看不出谁被隐藏。要确认隐藏状态,读 `view get`(view list)里该视图的 `custom.hiddenFields`(`{fieldId: true|false}`)。 +> ⚠️ 注意:服务端**只接受 reorder,不接受真"隐藏字段"**——如果传入的列表比当前 columns 短,缺失的字段不会被隐藏。需要真正隐藏字段请到 AI 表格 Web UI。 | flag | 类型 | |------|------| @@ -120,9 +118,7 @@ dws aitable view update field-widths --view-id GRID_ID --json '{"fldA":120,"fldB | `--json` | string 数组 JSON(与 `--field-ids` 同传时 `--json` 优先) | ```bash -# 只保留首列和 fldA,其余字段被隐藏 -dws aitable view update visible-fields --view-id VIEW_ID --field-ids fldPrimary,fldA -# 传回全量列表解除隐藏 +dws aitable view update visible-fields --view-id VIEW_ID --field-ids fldPrimary,fldA,fldB dws aitable view update visible-fields --view-id VIEW_ID --json '["fldPrimary","fldA","fldB"]' ``` diff --git a/skills/multi/dingtalk-aitable/references/aitable/aitable-view-extras.md b/skills/multi/dingtalk-aitable/references/aitable/aitable-view-extras.md index c9174158..23db8059 100644 --- a/skills/multi/dingtalk-aitable/references/aitable/aitable-view-extras.md +++ b/skills/multi/dingtalk-aitable/references/aitable/aitable-view-extras.md @@ -54,7 +54,7 @@ dws aitable view update frozen-cols --view-id VIEW_ID --count 0 # 查询当前冻结列数 dws aitable view get frozen-cols --view-id VIEW_ID --format json -# → {"data": {..., "count": 1}} 未显式设置时整个 count 键缺失(不是返回 null) +# → {"data": {..., "count": 1}} count 为 null 表示视图未显式设置 ``` `--count` 必须 ≥ 0;负数会被拒绝。 @@ -69,7 +69,7 @@ dws aitable view update row-height --view-id VIEW_ID --cell-height 56 # 查询当前行高 dws aitable view get row-height --view-id VIEW_ID --format json -# → {"data": {..., "cellHeight": 56}} 未显式设置时整个 cellHeight 键缺失(不是返回 null;前端按 32 渲染) +# → {"data": {..., "cellHeight": 56}} cellHeight 为 null 表示视图未显式设置(前端按 32 渲染) ``` ## 数据高亮规则(条件填色,仅 Grid) diff --git a/skills/multi/dingtalk-aitable/references/aitable/aitable-view-index.md b/skills/multi/dingtalk-aitable/references/aitable/aitable-view-index.md new file mode 100644 index 00000000..b962437e --- /dev/null +++ b/skills/multi/dingtalk-aitable/references/aitable/aitable-view-index.md @@ -0,0 +1,6 @@ +# 视图管理 + +| 命令 | 用途 | 详见 | +|---|---|---| +| `view get/list/create/update/delete/duplicate/lock` | 视图本体与基本属性 | [`aitable-view-config.md`](./aitable-view-config.md) | +| `view update ` | 按属性局部更新 | [`aitable-view-config.md`](./aitable-view-config.md) / [`aitable-view-extras.md`](./aitable-view-extras.md) | diff --git a/skills/multi/dingtalk-aitable/references/aitable/aitable-workflow-index.md b/skills/multi/dingtalk-aitable/references/aitable/aitable-workflow-index.md new file mode 100644 index 00000000..1f426124 --- /dev/null +++ b/skills/multi/dingtalk-aitable/references/aitable/aitable-workflow-index.md @@ -0,0 +1,5 @@ +# 自动化工作流 + +| 命令 | 用途 | 详见 | +|---|---|---| +| `workflow list/get/enable/disable` | 工作流管理 | [`aitable-workflow.md`](./aitable-workflow.md) | diff --git a/skills/multi/dingtalk-aitable/references/aitable/aitable-workflow.md b/skills/multi/dingtalk-aitable/references/aitable/aitable-workflow.md index a223155a..8dd17f83 100644 --- a/skills/multi/dingtalk-aitable/references/aitable/aitable-workflow.md +++ b/skills/multi/dingtalk-aitable/references/aitable/aitable-workflow.md @@ -1,180 +1,21 @@ -# workflow — 自动化工作流管理 +# 核心工作流 -启停 / 查看 / 列出 Base 下的自动化工作流(在 AI 表格 Web 端配置的 "当 X 时自动 Y" 流程)。 -适用场景:用户问 "停掉这个流程"、"看下都有哪些自动化流程"、"流程 X 的配置是什么"。 - -## 命令一览 - -| 命令 | 用途 | -|------|------| -| `workflow list` | 列出 Base 下所有工作流(含状态/创建人/最后修改时间),支持分页 | -| `workflow get` | 获取单个工作流详情(含 flowSchema 完整节点定义) | -| `workflow enable` | 启用指定工作流(按配置的触发条件自动执行) | -| `workflow disable` | 禁用指定工作流(高危,建议 `--yes` 二次确认) | - -> 所有子命令的 `--base-id` 必填(可用隐藏别名 `--base`)。 -> 当前**不支持通过 CLI 新建工作流**,请在 AI 表格 Web 端配置好后用 `workflow list` 拿到 ID 再启停。 - -## 命令详情 - -### workflow list — 列出工作流 +## 核心工作流 ```bash -dws aitable workflow list --base-id BASE_ID --format json -dws aitable workflow list --base-id BASE_ID --limit 50 --offset 100 +# 1. 搜索/列出 Base — 提取 baseId +dws aitable base search --query "项目" --format json + +# 2. 获取 Base 信息 — 提取 tableId +dws aitable base get --base-id --format json + +# 3. 获取表结构 — 提取 fieldId +dws aitable table get --base-id --table-id --format json + +# 4. 查询记录 +dws aitable record query --base-id --table-id --format json + +# 5. 新增记录 (cells 用 fieldId 作 key) +dws aitable record create --base-id --table-id \ + --records '[{"cells":{"fldXXX":"值"}}]' --format json ``` - -| flag | 说明 | -|------|------| -| `--base-id` | 必填 | -| `--limit` | 可选,分页大小 `[1, 100]`,不传走服务端默认 20 | -| `--offset` | 可选,分页偏移量 `>= 0`,不传走服务端默认 0 | - -返回结构: - -```json -{ - "data": { - "list": [ - { - "flowId": "G-FLOW-XXXXXX", // ★ 注意字段名是 flowId - "name": "流程1", - "description": "当创建记录时,就更新记录", - "status": "RUNNING", // RUNNING / STOP - "creatorStaffId": "281493", - "lastModifier": { "name": "李普阳", "staffId": "281493" }, - "gmtModified": 1780318540000, - "versionId": "G-FLOW-VER-XXXXXX", - "icons": ["..."], // 触发器+动作的图标 - "isSubFlow": false, - "opPermissions": { "canEdit": true } - } - ], - "recordCount": 1, // Base 下总数 - "runningCount": 1 // RUNNING 状态的数量 - } -} -``` - -**注意**: -- 标识字段服务端在 `list` 里叫 **`flowId`**,但在 `enable` / `disable` 出参里叫 **`workflowId`**。CLI `--workflow-id` 传任一即可(同值)。 -- `status` 是字符串枚举:`RUNNING`(启用中)/ `STOP`(已禁用),**不是** boolean。 -- `runningCount` 是当前 Base 下 status=RUNNING 的工作流数,方便快速判断「有几个流程在跑」。 - -### workflow get — 获取单个工作流详情 - -```bash -dws aitable workflow get --base-id BASE_ID --workflow-id WORKFLOW_ID --format json -``` - -| flag | 说明 | -|------|------| -| `--base-id` | 必填 | -| `--workflow-id` | 必填,对应 list 出参里的 `flowId` | - -返回完整工作流配置: - -```json -{ - "data": { - "name": "流程1", - "namespace": "...", - "status": "RUNNING", - "versionId": "G-FLOW-VER-XXXXXX", - "versionNo": 14, - "versionStatus": "...", - "accessor": {...}, // 访问者信息 - "corpId": "...", - "flowAttribute": {...}, // 流程顶层属性 - "flowSchema": {...}, // ★ 流程节点定义(触发器/动作/分支等) - "gmtCreate": 1780317804000, - "gmtModified": 1780318540000 - } -} -``` - -`flowSchema` 是完整的节点 DAG,结构因流程而异(条件触发器 vs 定时触发器、单分支 vs 多分支等)。agent 应按需读取关心字段,不要试图建静态 schema。 - -### workflow enable — 启用工作流 - -```bash -dws aitable workflow enable --base-id BASE_ID --workflow-id WORKFLOW_ID --format json -``` - -返回 `{workflowId, enabled: true}` —— **`enabled: true` 是动作确认,不是当前状态查询**。要确认真启用了,必须再 `workflow list` 看 `status` 是否变成 `"RUNNING"` 或 `runningCount` 是否加 1。 - -### workflow disable — 禁用工作流(高危) - -```bash -dws aitable workflow disable --base-id BASE_ID --workflow-id WORKFLOW_ID --yes --format json -``` - -返回 `{workflowId, disabled: true}` —— 同样是动作确认。禁用后该工作流不再自动触发。 - -**风险**:直接影响业务自动化(如停掉「记录创建后自动发通知」会让通知断流)。建议: -- 操作前先 `workflow get` 留底当前配置 -- 脚本场景显式传 `--yes`;交互场景让用户在 prompt 中再次确认 - -## 能力边界 - -| 能力 | 状态 | -|------|------| -| 列出工作流 | ✅ | -| 看工作流详情(含 flowSchema) | ✅ | -| 启用/禁用 | ✅ | -| 新建工作流 | ❌ 当前不支持,请去 AI 表格 Web 端 → 数据表 → 自动化 创建 | -| 修改工作流配置 | ❌ 同上,需 Web UI 编辑 | -| 删除工作流 | ❌ 同上 | -| 查看运行历史/执行日志 | ❌ 暂未开放 | -| 手动触发/单次运行 | ❌ 暂未开放 | - -## 错误码速查 - -| 场景 | code | type | 备注 | -|------|------|------|------| -| `workflow-id` 不存在调 get | `GET_WORKFLOW_ERROR` | `SYSTEM_ERROR` | message 可能为 null,先 `workflow list` 核对 ID | -| `workflow-id` 不存在调 enable | `ENABLE_WORKFLOW_ERROR` | `SYSTEM_ERROR` | message 含 "场域中不存在该 namespace" | -| `workflow-id` 不存在调 disable | `DISABLE_WORKFLOW_ERROR` | `SYSTEM_ERROR` | 同上 | -| `--limit` < 1 或 > 100 | (CLI 层拦截) | — | `--limit 必须在 [1, 100] 范围内,got N` | -| `--offset` < 0 | (CLI 层拦截) | — | `--offset 必须 >= 0,got N` | - -> 拿到 `*_WORKFLOW_ERROR / SYSTEM_ERROR` 时,先 `workflow list` 自查目标 ID 是否还存在、是否在当前 Base 下。 - -## 典型工作流 - -### 看看 Base 里有哪些自动化在跑 - -```bash -dws aitable workflow list --base-id BASE_ID --format json | jq '.data | {total: .recordCount, running: .runningCount, items: .list | map({name, status, flowId})}' -``` - -### 临时停掉某个流程做调试 - -```bash -# 1. 留底当前状态 -dws aitable workflow get --base-id BASE_ID --workflow-id WORKFLOW_ID --format json > /tmp/wf-backup.json - -# 2. 禁用 -dws aitable workflow disable --base-id BASE_ID --workflow-id WORKFLOW_ID --yes --format json - -# 3. 调试做完后重启 -dws aitable workflow enable --base-id BASE_ID --workflow-id WORKFLOW_ID --format json - -# 4. 确认 status=RUNNING -dws aitable workflow list --base-id BASE_ID --format json | jq '.data.list[] | select(.flowId == "WORKFLOW_ID") | .status' -``` - -### 批量关掉某个 Base 下所有 workflow(调试 / 迁移前清场) - -```bash -for WF in $(dws aitable workflow list --base-id BASE_ID --limit 100 --format json | jq -r '.data.list[] | select(.status == "RUNNING") | .flowId'); do - dws aitable workflow disable --base-id BASE_ID --workflow-id "$WF" --yes --format json | jq .status -done -``` - -## 注意事项 - -- `--workflow-id` 接受的就是 `list` 返回里的 `flowId`(同值,CLI 屏蔽了服务端字段名差异)。 -- enable / disable 出参里的 `enabled` / `disabled` 是 **动作确认 flag**,不是当前状态字段。要确认真生效请走 `workflow list` 查 `status`。 -- `workflow get` 的 `flowSchema` 结构随触发器/动作类型变化,不要假设固定字段。 -- 新建/修改/删除工作流目前必须在 AI 表格 Web 端(数据表页面 → 自动化)完成。 diff --git a/skills/multi/dingtalk-aitable/references/field-rules.md b/skills/multi/dingtalk-aitable/references/field-rules.md index 4e393732..7f1fd6ec 100644 --- a/skills/multi/dingtalk-aitable/references/field-rules.md +++ b/skills/multi/dingtalk-aitable/references/field-rules.md @@ -83,7 +83,7 @@ dws aitable table create --base-id --name "产品图片" \ ## 记录写入格式(record create / record update) -> 各字段类型的完整写入/读取格式规范请参考:[aitable-cell-value.md](./products/aitable/aitable-cell-value.md) +> 各字段类型的完整写入/读取格式规范请参考:[aitable-cell-value.md](./aitable/aitable-cell-value.md) > > 该文件是 cellValue 格式的 **source of truth**,包含所有字段类型的详细示例和注意事项。 diff --git a/skills/multi/dingtalk-aitable/references/intent-guide.md b/skills/multi/dingtalk-aitable/references/intent-guide.md new file mode 100644 index 00000000..a1428eaf --- /dev/null +++ b/skills/multi/dingtalk-aitable/references/intent-guide.md @@ -0,0 +1,10 @@ +# aitable 局部意图消歧 + +本文件从单 Skill `intent-guide.md` 拆分而来,仅保留与本产品相关的跨产品消歧规则。 + +| 用户说... | 真实意图 | 应该用 | 不要用 | 理由 | +|---|---|---|---|---| +| "帮我建一个项目跟踪表" | 创建数据表格 | `aitable` | `doc` / `sheet` | 涉及结构化数据/行列操作,不是富文本文档或电子表格 | +| "帮我写个项目周报" | 创建钉钉文档 | `doc` | `aitable` | 富文本内容创作,不是数据表 | +| "创建一个电子表格" | 创建表格文档 | `sheet` | `aitable` | Excel 式表格/单元格操作,不是多维表记录 | +| "帮我读一下表格 A1:D10 的数据" | 读取单元格数据 | `sheet` | `aitable` | 按单元格区域读写,不是按记录查询 | diff --git a/skills/multi/dingtalk-aitable/references/url-patterns.md b/skills/multi/dingtalk-aitable/references/url-patterns.md new file mode 100644 index 00000000..9498faa0 --- /dev/null +++ b/skills/multi/dingtalk-aitable/references/url-patterns.md @@ -0,0 +1,129 @@ +# URL 格式与处理规范 + +## alidocs URL 分流决策(必须首先执行) + +收到 `alidocs.dingtalk.com` URL 时,**必须按以下顺序判断,禁止跳过**: + +1. URL 路径含 `/i/p/` → **分享短链**,禁止调用 `dws doc` 任何子命令 → 按下方 [分享短链处理](#分享短链处理) 执行 +2. URL 路径含 `/i/nodes/` → **节点链接**,需探测类型 → 按下方 [alidocs URL 类型探测流程](#alidocs-url-类型探测流程) 执行 +3. URL 路径含 `/spreadsheetv2/` → **电子表格直链**,直接路由到 `sheet`,将完整 URL 原样传给 `--node` 参数 +4. URL 路径含 `/document/edit` 或 `/document/preview` 且 query 参数包含 `dentryKey` → **文档链接**,直接路由到 `doc`,将完整 URL 原样传给 `--node` 参数(URL 中不一定有 `type=d`,只需匹配路径和 `dentryKey` 参数即可) +5. 其他 alidocs URL 格式 → 告知用户当前暂不支持该链接格式 + +--- + +## 已知 URL 格式 + +需要自行拼接链接时,只能使用以下模板: + +| 产品 | 用途 | URL 格式 | ID 来源 | +|------|------|----------|---------| +| `aitable` | AI表格 Base 链接 | `https://alidocs.dingtalk.com/i/nodes/{baseId}` | `base list/search/create/get` 返回的 `baseId` | +| `aitable` | AI表格指定数据表链接 | `https://alidocs.dingtalk.com/i/nodes/{baseId}?iframeQuery=sheetId%3D{tableId}` | `baseId` + `table create/get` 或 `base get` 返回的 `tableId` | +| `aitable` | AI表格指定数据表+视图链接 | `https://alidocs.dingtalk.com/i/nodes/{baseId}?iframeQuery=sheetId%3D{tableId}%26viewId%3D{viewId}` | `baseId` + `tableId` + `view create/get` 返回的 `viewId` | +| `aitable` | AI表格模板预览 | `https://docs.dingtalk.com/table/template/{templateId}` | `template search` 返回的 `templateId` | +| `doc` | 文档链接 | `https://alidocs.dingtalk.com/i/nodes/{dentryUuid}` | `doc` 命令返回的 `dentryUuid` | +| `sheet` | 电子表格链接 | `https://alidocs.dingtalk.com/i/nodes/{dentryUuid}` | `sheet create` 返回的 `dentryUuid` | +| `sheet` | 电子表格直链 | `https://alidocs.dingtalk.com/spreadsheetv2/{key}/...?dentryKey={key}&type=s` | 用户提供的完整 URL,直接传给 `--node` | +| `doc` | 文档链接(edit/preview) | `https://alidocs.dingtalk.com/document/{edit\|preview}?...&dentryKey={key}` | 用户提供的完整 URL,直接传给 `--node` | +| `minutes` | 听记链接 | `https://shanji.dingtalk.com/app/transcribes/{taskUuid}` | `list mine/shared` 返回的 `taskUuid` | + +不在此表中的产品,禁止自行拼接 URL。命令返回中包含完整链接时直接使用,否则告知用户无法提供。 + +## 分享短链处理 + +`alidocs.dingtalk.com/i/p/{shortKey}` 是钉钉文档的**对外分享短链**,`dws doc` 命令无法解析此格式。 + +### 识别规则 + +URL 路径中包含 `/i/p/` 即为分享短链(无论后面是否还有子路径),例如: +- `https://alidocs.dingtalk.com/i/p/Y7kmbokZp3pgGLq2` +- `https://alidocs.dingtalk.com/i/p/Y7kmbokZp3pgGLq2/docs/AY39rGpMPmeVNpXZevZm8OZkXKnaoNQ7` +- `https://alidocs.dingtalk.com/i/p/AbCdEfGh1234` +- `https://alidocs.dingtalk.com/i/p/AbCdEfGh1234/sheets/XYZ789` + +> **关键**:只要 URL 中出现 `/i/p/`,无论后面跟什么子路径(`/docs/...`、`/sheets/...` 等),都属于分享短链,一律禁止调用 `dws doc`。 + +### 处理方式 + +**不要调用 `dws doc` 任何子命令**(包括 `doc info`、`doc read` 等),`dws` 无法解析此格式。 + +- **需要获取文档内容时**:使用 `read_url` 工具直接读取该链接 +- **其他操作(如移动、复制、权限管理等)**:告知用户此链接为分享短链,无法直接执行复制、移动、权限管理等操作。如需保存该文档内容,建议用户在钉钉客户端中打开该页面,手动复制文本内容,然后可通过 `dws doc create` 创建一篇新文档并将内容写入 + +``` +# 需要读取文档内容时(无论 /i/p/ 后面有没有子路径,都用 read_url) +read_url("https://alidocs.dingtalk.com/i/p/Y7kmbokZp3pgGLq2") +read_url("https://alidocs.dingtalk.com/i/p/Y7kmbokZp3pgGLq2/docs/AY39rGpMPmeVNpXZevZm8OZkXKnaoNQ7") + +# 禁止(以下全部会失败,dws 无法解析任何含 /i/p/ 的 URL) +dws doc info --node "https://alidocs.dingtalk.com/i/p/Y7kmbokZp3pgGLq2" --format json +dws doc read --node "https://alidocs.dingtalk.com/i/p/Y7kmbokZp3pgGLq2/docs/AY39rGpMPmeVNpXZevZm8OZkXKnaoNQ7" --format json +``` + +### 当 `read_url` 返回内容不完整时 + +钉钉文档分享页是动态渲染的,`read_url` 可能只能获取到页面标题等有限信息,无法获取文档正文。此时**禁止猜测原因**(如"权限不足""文档为空""文档已删除"等),**禁止建议用户"提供 `/i/nodes/` 格式链接"**(分享短链和节点链接是不同体系,普通用户无法自行转换)。应直接告知用户: + +> 这个链接是钉钉文档的分享短链,由于页面是动态渲染的,我无法通过该链接直接获取文档的完整正文内容。 +> +> 你可以: +> 1. 在钉钉客户端中打开该文档,将正文内容复制粘贴给我 +> 2. 如果文档已保存在你的文档空间中,可以告诉我文档名称,我通过 `dws drive search` 搜索后再读取 + +--- + +## alidocs URL 类型探测流程 + +`alidocs.dingtalk.com/i/nodes/{id}` 是钉钉文档空间的统一 URL,可能指向**文档、电子表格、多维表、文件、文件夹**等不同类型。**禁止仅凭 URL 就假定为文档**,必须先探测类型再路由到正确的产品。 + +### 探测步骤 + +``` +Step 1 → dws doc info --node "" --format json +Step 2 → 从返回中提取 contentType、extension、nodeType 字段 +Step 3 → 按下方路由规则映射到对应产品 +``` + +### 路由映射表 + +| 条件 | 路由到产品 | 后续操作 | +|------|-----------|---------| +| `contentType=ALIDOC`, `extension=adoc` | `doc` | 加载 `dingtalk-doc` 操作内容 | +| `contentType=ALIDOC`, `extension=axls` | `sheet` | 加载 `dingtalk-sheet` 操作(仅 `axls` 在线电子表格) | +| `contentType=ALIDOC`, `extension=able` | `aitable` | 将 nodeId 作为 baseId,加载 `dingtalk-aitable` 操作 | +| `contentType=DOCUMENT`, `extension=xlsx` / `xls` / `xlsm` / `csv` | `drive` | 必须用 `dws drive download` 下载到本地处理,禁止走 `sheet`(非在线表格,sheet 命令无法操作) | +| `contentType≠ALIDOC`, `nodeType=file` | `drive` | 调用 `dws drive download` 下载,返回文件下载链接 | +| `nodeType=folder` | `drive` / `wiki` | 调用 `dws drive list --workspace ` 或 `dws wiki node list` 列出子节点 | +| 以上均不匹配 | — | 告知用户当前暂不支持该类型 | + +> axls vs xlsx 关键区分: +> - `axls`(钉钉在线电子表格,`contentType=ALIDOC`)→ 走 `sheet` 产品线(读/写/筛选/导出等服务端原子操作) +> - `xlsx` / `xls` / `xlsm` / `csv`(上传到文档空间的本地表格文件,`contentType=DOCUMENT`)→ 必须走 `dws drive download` 下载到本地后再解析处理,严禁错误路由到 `sheet` 产品线(sheet 命令只支持在线表格,调用 xlsx 节点会直接报错) +> - 用户想把在线表格导出为 xlsx 文件 → 用 `dws sheet export`(输入是 `axls`,输出是 xlsx,这是 axls → xlsx 的格式转换,不属于 xlsx 读取场景) + +### 示例 + +```bash +# 用户传入: https://alidocs.dingtalk.com/i/nodes/abc123 +dws doc info --node "https://alidocs.dingtalk.com/i/nodes/abc123" --format json + +# 返回 contentType=ALIDOC, extension=axls → 在线电子表格,路由到 sheet +dws sheet list --node "https://alidocs.dingtalk.com/i/nodes/abc123" --format json + +# 返回 contentType≠ALIDOC, extension=xlsx/xls/csv → 本地表格文件,必须下载处理(禁止走 sheet) +dws drive download --node "https://alidocs.dingtalk.com/i/nodes/xlsx456" + +# 返回 contentType≠ALIDOC, nodeType=file → 普通文件,下载 +dws drive download --node "https://alidocs.dingtalk.com/i/nodes/def456" + +# 返回 nodeType=folder → 文件夹,列出子节点 +dws drive list --workspace --format json +``` + +### 何时可跳过探测 + +当用户指令中已明确指定产品(如"帮我读这个文档"、"看下这个表格的数据"),可结合用户意图**跳过探测**直接路由。仅在以下情况**必须执行探测**: +- 用户只粘贴 URL,无其他上下文 +- 用户指令与 URL 实际类型可能不一致(如说"文档"但实际是表格) +- 用户直接粘贴的是原始 `alidocs` URL,且没有上游命令返回来确认类型 diff --git a/skills/multi/dingtalk-aitable/scripts/aitable_export_via_task.py b/skills/multi/dingtalk-aitable/scripts/aitable_export_via_task.py index abee2023..caa66f80 100644 --- a/skills/multi/dingtalk-aitable/scripts/aitable_export_via_task.py +++ b/skills/multi/dingtalk-aitable/scripts/aitable_export_via_task.py @@ -90,10 +90,8 @@ def build_start_args(args: argparse.Namespace) -> list[str]: args.scope, "--format", args.export_format, - # CLI 的 --timeout-ms 是单次等待上限(毫秒,最大 30000);脚本自身的 - # --timeout-sec 用于整体轮询/子进程超时,二者语义不同,不能混用。 "--timeout-ms", - "30000", + str(args.timeout_ms), ] if args.table_id: cmd.extend(["--table-id", args.table_id]) @@ -109,8 +107,9 @@ def main() -> None: parser.add_argument("--table-id", help="scope=table/view 时必填") parser.add_argument("--view-id", help="scope=view 时必填") parser.add_argument("--export-format", default="excel", choices=sorted(ALLOWED_FORMATS), help="导出格式") - parser.add_argument("--timeout-sec", type=int, default=300, help="CLI 内置轮询整体超时(秒),默认 300(5 分钟)") - parser.add_argument("--max-polls", type=int, default=10, help="(兼容旧参数,开源 CLI 内置轮询不再使用)") + parser.add_argument("--timeout-ms", type=int, default=1000, help="单次等待毫秒数,默认 1000") + parser.add_argument("--poll-timeout-ms", type=int, default=3000, help="轮询等待毫秒数,默认 3000") + parser.add_argument("--max-polls", type=int, default=10, help="最大轮询次数,默认 10") parser.add_argument("--output", help="本地保存路径(不传则按 fileName 保存到当前目录)") parser.add_argument("--dws", default="dws", help="dws 可执行文件路径,默认 dws") parser.add_argument("--no-download", action="store_true", help="仅返回 downloadUrl,不下载文件") @@ -155,9 +154,9 @@ def main() -> None: "--task-id", task_id, "--timeout-ms", - "30000", + str(args.poll_timeout_ms), ], - timeout_sec=max(120, args.timeout_sec + 60), + timeout_sec=max(120, int(args.poll_timeout_ms / 1000) + 60), ) if rc2 != 0: fail(f"export_data 轮询失败: {err2 or out2}", rc2) diff --git a/skills/multi/dingtalk-aitable/scripts/aitable_import_via_task.py b/skills/multi/dingtalk-aitable/scripts/aitable_import_via_task.py index 566be970..fea1ad5e 100644 --- a/skills/multi/dingtalk-aitable/scripts/aitable_import_via_task.py +++ b/skills/multi/dingtalk-aitable/scripts/aitable_import_via_task.py @@ -77,7 +77,7 @@ def main() -> None: parser = argparse.ArgumentParser(description="通过文件导入任务导入 AI 表格") parser.add_argument("base_id", help="目标 AI 表格 baseId") parser.add_argument("file_path", help="待导入文件路径(.csv/.xlsx/.xls)") - parser.add_argument("--timeout-sec", type=int, default=300, help="CLI 内置轮询整体超时(秒),默认 300(5 分钟)") + parser.add_argument("--timeout", type=int, default=30, help="import_data 等待秒数,默认 30") parser.add_argument("--dws", default="dws", help="dws 可执行文件路径,默认 dws") args = parser.parse_args() @@ -140,14 +140,12 @@ def main() -> None: "data", "--import-id", import_id, - # import data 的 --timeout 单位是秒、最大 30;脚本的 --timeout-sec - # 是整体子进程预算,不能直接透传,这里用 CLI 允许的最大值。 "--timeout", - "30", + str(args.timeout), "--format", "json", ], - timeout_sec=max(120, args.timeout_sec + 30), + timeout_sec=max(120, args.timeout + 30), ) if rc2 != 0: fail(f"import_data 调用失败: {err2 or out2}", rc2) diff --git a/skills/multi/dingtalk-aitable/scripts/bulk_add_fields.py b/skills/multi/dingtalk-aitable/scripts/bulk_add_fields.py index 78c5bdfc..3ba88313 100644 --- a/skills/multi/dingtalk-aitable/scripts/bulk_add_fields.py +++ b/skills/multi/dingtalk-aitable/scripts/bulk_add_fields.py @@ -29,14 +29,14 @@ JsonData = Union[List[Any], Dict[str, Any]] MAX_FILE_SIZE = 10 * 1024 * 1024 ALLOWED_FILE_EXTENSIONS = ['.json'] -RESOURCE_ID_PATTERN = re.compile(r'^[A-Za-z0-9_-]{6,128}$') +RESOURCE_ID_PATTERN = re.compile(r'^[A-Za-z0-9_-]{8,128}$') ALLOWED_FIELD_TYPES = { 'text', 'number', 'singleSelect', 'multipleSelect', 'date', 'currency', 'user', 'department', 'group', 'progress', 'rating', 'checkbox', 'attachment', 'url', 'richText', 'telephone', 'email', 'idCard', - 'barcode', 'geolocation', 'primaryDoc', 'formula', 'unidirectionalLink', - 'bidirectionalLink', 'creator', 'lastModifier', 'createdTime', - 'lastModifiedTime', + 'barcode', 'geolocation', 'address', 'primaryDoc', 'formula', + 'unidirectionalLink', 'bidirectionalLink', 'lookup', 'filterUp', + 'creator', 'lastModifier', 'createdTime', 'lastModifiedTime', } FIELD_TYPE_ALIASES = { 'phone': 'telephone', @@ -121,9 +121,32 @@ def validate_field_config(field: Dict[str, Any]) -> Tuple[bool, str]: ) if field_type in {'unidirectionalLink', 'bidirectionalLink'}: - linked_sheet_id = (config or {}).get('linkedSheetId') - if not linked_sheet_id or not validate_resource_id(linked_sheet_id): - return False, '关联字段必须提供合法的 config.linkedSheetId' + linked_table_id = (config or {}).get('linkedTableId') + if not linked_table_id or not validate_resource_id(linked_table_id): + return False, ( + '关联字段必须提供合法的 config.linkedTableId(目标 Table ID)' + ) + + if field_type == 'lookup': + cfg = config or {} + if not cfg.get('associateField'): + return False, 'lookup 必须提供 config.associateField(本表关联字段的 fieldId)' + if not cfg.get('valuesField'): + return False, 'lookup 必须提供 config.valuesField(关联目标表中要取值的字段 fieldId)' + if not cfg.get('aggregator'): + return False, 'lookup 必须提供 config.aggregator(SUM/AVERAGE/COUNT/MAX/MIN/CONCATENATE)' + + if field_type == 'filterUp': + cfg = config or {} + if not cfg.get('targetSheet'): + return False, 'filterUp 必须提供 config.targetSheet(目标 Table ID)' + filters = cfg.get('filters') + if not filters or not isinstance(filters, list): + return False, 'filterUp 必须提供 config.filters(至少一条筛选规则)' + if not cfg.get('valuesField'): + return False, 'filterUp 必须提供 config.valuesField(目标表中要取值的字段 fieldId)' + if not cfg.get('aggregator'): + return False, 'filterUp 必须提供 config.aggregator(SUM/AVERAGE/COUNT/MAX/MIN/CONCATENATE)' return True, '' diff --git a/skills/multi/dingtalk-aitable/scripts/import_records.py b/skills/multi/dingtalk-aitable/scripts/import_records.py index a76f78d2..a275806e 100644 --- a/skills/multi/dingtalk-aitable/scripts/import_records.py +++ b/skills/multi/dingtalk-aitable/scripts/import_records.py @@ -11,6 +11,13 @@ - JSON 支持两种格式: 1. [{"cells": {"fldxxx": "value"}}, ...] 2. [{"fldxxx": "value"}, ...] # 会自动包装成 cells + +⚠️ CSV 自动类型转换风险: + CSV 读入的所有 cell 都是 string,本脚本会尝试自动识别 'true'/'false'/数字 + 并转成对应类型(避免 text 字段塞入纯文本数字)。但当 fieldId 对应的字段是 + text / telephone / idCard / barcode 这类"字符串形数字"字段时,自动转 int / float + 会让 server 拒绝(字段类型不匹配)。这种情况建议改用 JSON 格式(自己显式控制类型), + 或在 CSV 写入前给字段值前缀加引号 / 改为非纯数字。 """ import sys @@ -28,7 +35,7 @@ RecordDict = Dict[str, str] MAX_FILE_SIZE = 50 * 1024 * 1024 ALLOWED_CSV_EXTENSIONS = ['.csv'] ALLOWED_JSON_EXTENSIONS = ['.json'] -RESOURCE_ID_PATTERN = re.compile(r'^[A-Za-z0-9_-]{6,128}$') +RESOURCE_ID_PATTERN = re.compile(r'^[A-Za-z0-9_-]{8,128}$') MAX_RECORDS_PER_BATCH = 100 DEFAULT_BATCH_SIZE = 50 @@ -131,9 +138,7 @@ def normalize_record(record: Dict[str, Any]) -> Dict[str, Any]: return {'cells': normalized} -def validate_record( - record: Dict[str, Any], headers: List[str] -) -> Tuple[bool, str]: +def validate_record(record: Dict[str, Any]) -> Tuple[bool, str]: if not isinstance(record, dict): return False, '记录必须是对象' normalized = normalize_record(record) @@ -238,7 +243,7 @@ def import_from_json( return False for i, record in enumerate(records): - valid, error = validate_record(record, []) + valid, error = validate_record(record) if not valid: print(f"错误:记录 #{i+1} 格式无效:{error}") return False diff --git a/skills/multi/dingtalk-attendance/SKILL.md b/skills/multi/dingtalk-attendance/SKILL.md index 29c152e8..2f658da9 100644 --- a/skills/multi/dingtalk-attendance/SKILL.md +++ b/skills/multi/dingtalk-attendance/SKILL.md @@ -1,6 +1,6 @@ --- name: dingtalk-attendance -description: 钉钉考勤。Use when 用户说 考勤/打卡记录/查打卡/查班次/考勤汇总/考勤规则/出勤情况/请假加班补卡/排班/考勤组/假期余额。命令前缀:dws attendance。支持查询与写操作(班次、排班、考勤组、个人规则设置、假期规则/余额等),写操作需二次确认后再加 --yes。 +description: 钉钉考勤、打卡、排班与考勤报表。Use when 用户说考勤/打卡记录/查打卡/排班/班次/考勤报表/导出考勤/出勤汇总/考勤明细/迟到早退统计/全员考勤数据/某月考勤统计/考勤表格/考勤Excel。不做日程会议(走 dingtalk-calendar)、日报周报(走 dingtalk-report)、审批请假流程(走 dingtalk-oa)。命令前缀:dws attendance。 cli_version: ">=0.2.14" metadata: category: product @@ -14,38 +14,40 @@ metadata: > 🧪 **EXPERIMENTAL · 试验版 / Preview** — multi 模式当前未达 stable 标准。全部 dingtalk-* skill 已通过 dispatch verifier,但接口、命名、跨 skill 引用后续可能调整;生产 / 共享环境请优先使用 mono 模式(`dws skill setup --mode mono`)。问题请提 issue 反馈。 -> **PREREQUISITE:** Read the `dws-shared` skill first for auth, global flags, product routing, URL preflight, error codes, and safety rules. The `dws` binary must be on PATH. +## 前置条件 — 执行操作前必读 + +> **`use_skill(dws-shared)`** — 认证、全局参数(`--format json` / `--yes`)、错误码、URL 模板、跨产品消歧、安全规则与 capability 边界。**执行任何 `dws` 命令前先读;** 单产品的清晰命令可直接用本 skill。 -> ⚠️ **命令可用性以当前 dws 二进制为准**。服务发现已下线,本文档随内置 skill 发布;如果 `dws --help` 不存在,说明当前版本未暴露该命令。若命令存在但调用失败,请按错误中的 endpoint 或 tool 提示确认静态端点目录和后端工具注册。实际调用前可用 `dws --help` 或 `--dry-run` 验证。 +> 渐进式参考:[attendance-index.md](references/attendance-index.md)。日期范围或“签到”场景必须先读 [attendance-routing.md](references/attendance-routing.md);报表导出再读 [attendance-report.md](references/attendance-report.md)。 - -> 命令参考:[attendance.md](references/attendance.md)。 +> 旧路径兼容入口:[attendance.md](references/attendance.md)。 ## 意图表 | 用户说 | 命令 | |--------|------| -| "查我 / 某人 某天的打卡" | `dws attendance record get --user --date ` | -| "查考勤组 / 考勤规则 / 打卡范围" | `dws attendance rules --date ` | -| "查班次 / 排班 / 谁今天上什么班" | `dws attendance shift list --users --start --end ` | -| "考勤统计 / 周月汇总 / 出勤天数" | `dws attendance summary --user --date --stats-type week\|month` | -| "请假 / 加班 / 补卡 / 出差 记录或提交链接" | `dws attendance approve list` / `dws attendance approve templates --type <类型>` | -| "班次 / 补卡规则 / 加班规则 / 考勤组 查询与修改" | `class` / `adjustment` / `overtime` / `group` 子命令 | -| "排班 / 假期规则 / 假期余额 / 签到 / 报表" | `schedule` / `vacation` / `checkin` / `report` 子命令 | +| "查我自己的打卡 / 某天考勤" | `python scripts/attendance_my_record.py 2026-03-08` 或 `dws attendance record get --user --date ` | +| "查团队排班" | `python scripts/attendance_team_shift.py --users --from --to ` | +| "导出考勤报表 / 月度汇总 / 考勤明细 / 每日统计" | **必须先读 [attendance-report.md](references/attendance-report.md)** 强制门禁后选择 `attendance_report_{detail,monthly,daily}.py` | +| "创建班次 / 设置班次" | 先读 [attendance.md](references/attendance-commands.md) 的 `class create`,确认后执行 | +| "导入排班 / 安排排班" | 先读 [attendance.md](references/attendance-commands.md) 的 `schedule import`,确认后执行 | +| "加入/移出考勤组 / 更新考勤组成员" | `dws attendance group update-members ...`(需确认) | -> 完整命令集(含写操作与参数)见 [references/attendance.md](references/attendance.md)。 +## 高频硬约束 -## 评测高频硬约束 - -- 当前 dws 已注册全部考勤子命令组(`record` / `check` / `approve` / `shift` / `schedule` / `class` / `adjustment` / `overtime` / `group` / `summary` / `rules` / `selfsetting` / `globalsetting` / `vacation` / `checkin` / `report` / `boss-check`),查询与写操作大多可直接调用后端。**不要再以"开源版只读/不支持写操作"为由拒答。** 创建班次、导入排班、加人入考勤组、保存个人规则设置、设置假期余额等写操作可执行,但必须先展示参数摘要并二次确认,再追加 `--yes`(或 `--user-say-yes`)执行;不要在未确认时直接写、也不要伪装成功。个别命令受权限/数据影响返回空或权限错误(如 `report` 系列仅管理员),如实说明即可。 +- 不要在读完 [attendance.md](references/attendance-commands.md) 前判断"CLI 不支持"。`class create`、`schedule import`、`group update-members`、`group update` 都是已支持写操作,但必须先展示摘要并等用户确认。 - 查询迟到/缺勤名单时,空打卡结果不等于"没人迟到"。必须结合排班、`NotSigned`、`Absenteeism`、无记录人员分别说明;数据缺失要标为"无记录/无法判断",不要归为正常。 - 做部门 Top N 排名时,用户要求前 N 名就必须输出 N 个部门;无打卡记录或无可计算数据的部门按 0 或"无数据"保留在排名中,不能只输出有数据的少数部门。 -- `summary` 必须同时传 `--user`、`--date`、`--stats-type`(week/month),缺一返回 C0002。 -- `shift list` 时间跨度不超过 7 天,最多 50 人;`record get` 一次只查一个用户一天。 -- 所有 dws 命令带 `--format json`,时间/日期按命令要求分别使用 `YYYY-MM-DD` 或 `yyyy-MM-dd HH:mm:ss`。 +- 处理请假/补卡/加班审批时,先用考勤审批模板或 OA 查询确认可操作范围;没有直接提交接口时返回可点击提交链接并说明无法代填提交,不要假装已提交。 +- 更新考勤组成员固定按 `aisearch/contact` 解析 userId → `group search` 解析 groupId → 展示变更摘要并确认 → `group update-members` → `group filtered-get --member` 回查。 +- 所有 dws 命令带 `--format json`,时间/日期按命令要求分别使用 `YYYY-MM-DD` 或 reference 指定格式。 ## 跨产品协作 - 拿到 userId 前先用 `dingtalk-aisearch` 解析人名 +- 报表导出涉及多人 / 多月 → 脚本内部自动分批 + 切片,输出 xlsx +## 局部意图与 Recipe + +- [局部意图消歧](references/intent-guide.md)。 diff --git a/skills/multi/dingtalk-attendance/references/attendance-commands.md b/skills/multi/dingtalk-attendance/references/attendance-commands.md new file mode 100644 index 00000000..8ebecc9e --- /dev/null +++ b/skills/multi/dingtalk-attendance/references/attendance-commands.md @@ -0,0 +1,998 @@ +# 命令总览 + +## 命令总览 + + +### 查询打卡结果 +``` +Usage: + dws attendance check result [flags] +Example: + dws attendance check result --users userId1,userId2 --start 2026-04-01 --end 2026-04-30 --limit 50 +Flags: + --start string 起始日期, 格式 YYYY-MM-DD (必填) + --end string 结束日期, 格式 YYYY-MM-DD, 不超过 1 个月 (必填) + --limit int 分页大小, 默认 100, 范围 1-1000 (可选) + --offset int 分页偏移量, 默认 0 (可选) + --users string 用户 ID 列表, 逗号分隔, 最多 100 个 (必填) +``` + +返回每条记录含:用户 ID、工作日期、时间结果(Normal/Late/Early/Absenteeism/NotSigned)、位置结果、计划打卡时间、实际打卡时间、打卡流水 ID。时间跨度不超过 1 个月,最多 100 人。 + +### 查询打卡流水 +``` +Usage: + dws attendance check record [flags] +Example: + dws attendance check record --users userId1 --start 2026-04-01 --end 2026-04-30 +Flags: + --start string 起始日期, 格式 YYYY-MM-DD (必填) + --end string 结束日期, 格式 YYYY-MM-DD, 不超过 1 个月 (必填) + --users string 用户 ID 列表, 逗号分隔 (必填) +``` + +返回每条记录含:用户 ID、实际打卡时间、打卡地址、打卡经纬度、打卡类型(OnDuty/OffDuty)、定位方式(Map/Wifi/etc)。时间跨度不超过 1 个月。 + +### 查询审批单(补卡/加班/请假/出差外出) +``` +Usage: + dws attendance approve list [flags] +Example: + dws attendance approve list --users userId1 --types overtime,leave --start 2026-04-01 --end 2026-04-30 + dws attendance approve list --users userId1 --types trip --start 2026-04-01 --end 2026-04-30 # 同时返回出差与外出 + dws attendance approve list --users userId1 --types 加班,请假,补卡 --start 2026-04-01 --end 2026-04-30 +Flags: + --start string 起始日期, 格式 YYYY-MM-DD (必填) + --end string 结束日期, 格式 YYYY-MM-DD (必填) + --types string 审批类型, 逗号分隔: overtime/加班、trip/travel/business_trip/出差/外出、leave/请假、patch/repair-check/补卡 (必填) + --users string 用户 ID 列表, 逗号分隔 (必填) +``` + +审批类型映射(关键词 → bizType): +- `overtime` / `加班` → `1` +- `trip` / `travel` / `business_trip` / `business-trip` / `出差` / `外出` → `2`(**服务端查询接口 bizType=2 同时覆盖出差与外出,两者合并为同一类、不再细分**;传入任一别名都会返回这两类记录) +- `leave` / `请假` → `3` +- `patch` / `repair-check` / `repair_check` / `补卡` → `4` + +> 查询不区分外出与出差,如果需要在提交入口区分外出(`TRAVEL`)与出差(`OUT`),请改用 `dws attendance approve templates --type travel|out`。 + +返回每条记录含:用户 ID、审批标签、审批子类型、审批类型、生效时间、时长、时长单位、流程实例 ID。 + +### 查询补卡/请假/加班/外出/出差审批提交链接 (必须走引导流程) +``` +Usage: + dws attendance approve templates [flags] +Example: + dws attendance approve templates --type leave + dws attendance approve templates --type REPAIR_CHECK + dws attendance approve templates --type 加班 + dws attendance approve templates --type travel # 外出,等价 --type TRAVEL + dws attendance approve templates --type 出差 # 出差,等价 --type OUT +Flags: + --type string 审批类型:repair-check/patch/补卡、leave/请假、overtime/加班、travel/外出、out/trip/出差,或 REPAIR_CHECK/LEAVE/OVERTIME/TRAVEL/OUT(必填) +``` + +当用户提到需要提交补卡、请假、加班、外出或出差时,优先使用该命令查询考勤审批表单模板提交链接,并引导用户点击返回的 `submitUrl` 提交。 +`corpId` 和 `opUserId` 由系统参数自动注入,无需通过命令参数传入。 +审批类型映射:补卡=`REPAIR_CHECK`,请假=`LEAVE`,加班=`OVERTIME`,外出=`TRAVEL`,出差=`OUT`(`trip` / `business_trip` / `business-trip` 亦映射为 `OUT`)。返回结果为列表,每条记录包含 `approveType`、`formName`、`processCode`、`submitUrl`。 +#### 引导用户自主选择合适的表单模板流程 +如果返回多个表单模板,必须将多个可用模板都返回给用户,并引导用户根据实际场景自主选择合适的模板提交: +- 请假场景:可根据 `formName` 将与用户请假类型更匹配的模板放在前面展示。例如用户明确说年假、事假、病假、调休时,将名称中包含对应假期类型的模板靠前;如果用户只泛化表达“请假”,将名称最通用的请假模板靠前,例如“请假”“员工请假”“通用请假”等,避免把专项或特殊场景模板放在最前。 +- 补卡/加班场景:可将名称与“补卡”或“加班”最直接匹配的模板放在前面展示。 +- 回复用户时不要直接裸露任何 `submitUrl`,所有返回的表单模板都必须使用 Markdown 可点击链接格式展示:`[formName](submitUrl)`,例如 `[员工请假](https://...)`。如存在更匹配的模板,可以放在列表前面,但不要只返回推荐模板,必须同时返回其它可用模板供用户选择,且每个模板都应是用户可直接点击的 Markdown 链接。 + +### 导入排班记录(排班 = 为员工安排工作日期和班次, 写场景接口,必须走二次确认流程) +``` +Usage: + dws attendance schedule import [flags] +Example: + dws attendance schedule import --group-id 123456 \ + --schedules '[{"userId":"user001","classId":123,"workDate":"2026-04-22","checkBeginTime":"09:00","checkEndTime":"18:00"}]' \ + --yes +Flags: + --group-id string 考勤组(必填,传入考勤组ID) + --schedules string 排班记录 JSON 数组(必填) + --yes 跳过确认提示 +``` + +为排班制考勤组导入排班记录。`--schedules` 为 JSON 数组,每条记录包含: +- `userId`: 员工ID +- `classId`: 班次ID +- `workDate`: 工作日期(YYYY-MM-DD),如 2026-04-22 +- `checkBeginTime`: 开始打卡时间 +- `checkEndTime`: 结束打卡时间 +- `isRest`: 是否休息日 Y/N(可选) + +#### AI 调用 `schedule import` 的二次确认流程 + +`schedule import` 是写操作,会为考勤组导入或变更员工排班。AI 调用时必须按以下流程执行,不得在未确认的情况下直接导入: + +1. **识别写操作**:用户表达“导入排班 / 设置排班 / 安排排班 / 给员工排班 / 批量排班”等意图时,命中 `schedule import`。 +2. **收集必要参数**:必须明确 `--group-id` 和 `--schedules`,并确认排班记录中的 `userId`、`classId`、`workDate`、`checkBeginTime`、`checkEndTime`、`isRest` 等字段。 +3. **展示导入摘要并反问确认**:向用户展示考勤组 ID、导入员工数量、涉及日期范围、班次 ID 列表,以及排班记录明细摘要,并询问是否确认执行导入。 +4. **用户确认后再执行导入**:只有用户明确确认后,才可以执行 `dws attendance schedule import ... --format json`。 + +确认话术示例: + +```text +即将导入排班记录,请确认: +- 考勤组 ID: +- 员工数量: +- 日期范围: ~ +- 班次 ID: +- 排班明细: + - : -,班次 + +是否确认执行导入? +``` + +如用户明确要求跳过确认,或命令中明确包含全局 `--yes`,可跳过二次确认。 + +### 获取排班记录 + +**禁止直接调用 `dws attendance schedule get`。必须先 `read_file` 读取 [attendance-schedule.md](./attendance-schedule.md) 后按其中的「排班查询导出工作流」执行。** + +- 任何"查询排班"、"查看排班"、"XX考勤组的排班"、"X月份的排班"场景,**一律走 attendance-schedule.md 工作流**,由脚本 `attendance_schedule_export.py` 统一处理 +- 脚本自动处理:分批查询(超 20 人自动分批)、userId→姓名转换、classId→班次名称转换、排班表格式 Excel 输出 +- 违反后果:人数多时接口超时/报错、输出裸 userId 和 classId 用户看不懂、无排班表格式 +- **此处不提供 `schedule get` 的 Usage/Flags,防止绕过工作流直接拼命令。完整参数由 attendance-schedule.md 工作流中的脚本内部使用。** + +### 查询当前用户可管理的所有班次详情 +``` +Usage: + dws attendance class search [flags] +Example: + dws attendance class search + dws attendance class search --query "早班" --filter-type MINE_OWN + dws attendance class search --page 1 --limit 50 +Flags: + --filter-type string 班次类型: ALL 全部班次 / MINE_OWN 我负责的 (可选) + --query string 班次名称关键字, 模糊搜索 (可选) + --page int 页码, 从 1 开始 (可选, 默认 1) + --limit int 每页条数, 最大 200 (可选, 默认 20) +``` + +### 查询班次详情 +``` +Usage: + dws attendance class get [flags] +Example: + dws attendance class get --class-id 1170996821 +Flags: + --class-id int 班次 ID (必填) +``` + +根据班次 ID 查询该班次的完整详细信息。班次 ID 可从 `class search` 返回结果中提取,也有可能来源于用户手动输入。 + +### 创建班次 (写场景接口,必须走二次确认流程) +**强制执行流程**:此命令为写操作,Agent 调用时必须遵守以下流程: +1. 先向用户展示待执行操作的完整参数摘要,包括班次名称、上下班时间、休息时段等 +2. 展示变更摘要并等待用户明确确认 +3. 用户确认后,再传全局 `--yes` 执行命令 + +**禁止未经用户确认直接执行或自动添加 `--yes`。** +``` +Usage: + dws attendance class create [flags] +Example: + dws attendance class create --name "早班" --class-vo '{"sections":[{"times":[{"checkType":"OnDuty","checkTime":"08:00","across":0},{"checkType":"OffDuty","checkTime":"17:00","across":0}]}]}' --timeout 10 + # 带休息时段(12:00-13:00 午休) + dws attendance class create --name "测试CLI" --class-vo '{"sections":[{"times":[{"checkType":"OnDuty","checkTime":"09:00","across":0},{"checkType":"OffDuty","checkTime":"18:00","across":0}]}],"setting":{"topRestTimeList":[{"checkType":"OnDuty","checkTime":"12:00","across":0},{"checkType":"OffDuty","checkTime":"13:00","across":0}]}}' --timeout 10 +Flags: + --name string 班次名称 (必填) + --owner string 班次负责人 userId (可选) + --class-vo string 完整 TopAtClassVO JSON 字符串, 包含 sections 等复杂子对象 (必填) +``` + +创建一个新班次。`--name` 和 `--class-vo`(包含 `sections`)必填。`sections` 定义班次的上下班时间段,支持多段上下班,每段包含 `times` 数组(有且只能有两个对象:上班+下班)。由于保存班次耗时较久,建议加 `--timeout 10`。 + +`checkTime` 字段统一使用 "HH:mm" 格式(如 "09:00"、"17:30"),CLI 自动转换为服务端所需格式。 + +`--class-vo` 支持字段: +- `name`(string, 必填) `owner`(string, 可选) +- `sections`([]object, 必填): 每个对象含 `times`([]object),每个 time 含 `checkType`(OnDuty/OffDuty, 必填) `checkTime`("HH:mm", 必填) `across`(0/1, 必填) `freeCheck`(bool) `beginMin`(number, -1不限制) `endMin`(number, -1不限制) +- `setting`(object, 可选): `seriousLateMinutes`(严重迟到分钟) `absenteeismLateMinutes`(旷工迟到分钟) `attendDays`(出勤天数) `topRestTimeList`([]object, 仅单段上下班时可用,最多3段: checkType/checkTime("HH:mm")/across) + +### 更新班次 (写场景接口,必须走二次确认流程) +**强制执行流程**:此命令为写操作,Agent 调用时必须遵守以下流程: +1. 先向用户展示待执行操作的完整参数摘要,包括班次 ID、要修改的字段含义及新值 +2. 展示变更摘要并等待用户明确确认 +3. 用户确认后,再传全局 `--yes` 执行命令 + +**禁止未经用户确认直接执行或自动添加 `--yes`。** +``` +Usage: + dws attendance class update [flags] +Example: + dws attendance class update --class-id 1170996821 --name "新早班" --timeout 10 + dws attendance class update --class-id 1170996821 --class-vo '{"sections":[{"times":[{"checkType":"OnDuty","checkTime":"08:30","across":0},{"checkType":"OffDuty","checkTime":"17:30","across":0}]}]}' --timeout 10 + # 带休息时段(12:00-13:00 午休) + dws attendance class update --class-id 1170996821 --class-vo '{"sections":[{"times":[{"checkType":"OnDuty","checkTime":"09:00","across":0},{"checkType":"OffDuty","checkTime":"18:00","across":0}]}],"setting":{"topRestTimeList":[{"checkType":"OnDuty","checkTime":"12:00","across":0},{"checkType":"OffDuty","checkTime":"13:00","across":0}]}}' --timeout 10 +Flags: + --class-id int 班次 ID (必填) + --name string 班次名称 (可选,不传则保持原值) + --owner string 班次负责人 userId (可选,不传则保持原值) + --class-vo string 完整 TopAtClassVO JSON 字符串,用于修改复杂子对象 (可选) +``` + +更新班次配置。`--class-id` 必填,其余均可选,仅需对要修改的字段进行赋值,未传字段会自动从已有配置补充。小改用单字段 flag(如 `--name`);修改上下班时间、休息时段等复杂子对象时用 `--class-vo` 传入完整 JSON。`--class-vo` 与单字段 flag 同时传入时,单字段 flag 优先级更高。 + +`checkTime` 字段统一使用 "HH:mm" 格式(如 "09:00"、"17:30"),CLI 自动转换为服务端所需格式。 + +`--class-vo` 支持字段(均可选,只需包含要修改的字段): +- `name`(string) `owner`(string) +- `sections`([]object): 每个对象含 `times`([]object),每个 time 含 `checkType`(OnDuty/OffDuty) `checkTime`("HH:mm") `across`(0/1) `freeCheck`(bool) `beginMin`(number, -1不限制) `endMin`(number, -1不限制) +- `setting`(object): `seriousLateMinutes` `absenteeismLateMinutes` `attendDays` `topRestTimeList`([]object: checkType/checkTime("HH:mm")/across) + +由于保存班次耗时较久,建议加 `--timeout 10`。 + +### 分页查询补卡规则,支持按名称搜素 +``` +Usage: + dws attendance adjustment search [flags] +Example: + dws attendance adjustment search --page 1 --limit 20 + dws attendance adjustment search --query "标准" --page 1 --limit 50 +Flags: + --page int 页码, 从 1 开始 (必填, 默认 1) + --query string 补卡规则名称关键字, 模糊搜索 (可选) + --limit int 每页条数, 200 以内 (必填, 默认 20) +``` + +### 查询补卡规则详情 +``` +Usage: + dws attendance adjustment get [flags] +Example: + dws attendance adjustment get --adjustment-id 12345 +Flags: + --adjustment-id int 补卡规则主键 ID (必填) +``` + +根据补卡规则主键 ID 查询对应的补卡规则详情。主键 ID 可从 `adjustment search` 返回结果中提取,也有可能来源于用户手动输入。**注意:已被删除或被更新覆盖的补卡规则无法查询到。** + +### 分页查询加班规则,支持按名称搜素 +``` +Usage: + dws attendance overtime search [flags] +Example: + dws attendance overtime search --page 1 --limit 20 + dws attendance overtime search --query "节假日" --page 1 --limit 50 +Flags: + --page int 页码, 从 1 开始 (必填, 默认 1) + --query string 加班规则名称关键字, 模糊搜索 (可选) + --limit int 每页条数, 200 以内 (必填, 默认 20) +``` + +### 查询加班规则详情 +``` +Usage: + dws attendance overtime get [flags] +Example: + dws attendance overtime get --overtime-id 12345 +Flags: + --overtime-id int 加班规则主键 ID (必填) +``` + +根据加班规则主键 ID 查询对应的加班规则详情。主键 ID 可从 `overtime search` 返回结果中提取,也有可能来源于用户手动输入。**已被删除或更新覆盖的加班规则也可以查到。** + +### 查询考勤组列表 +``` +Usage: + dws attendance group search [flags] +Example: + dws attendance group search --query "研发" + dws attendance group search --type FIXED --limit 50 + dws attendance group search --page 1 --limit 20 +Flags: + --query string 考勤组名称关键字, 模糊搜索 (可选) + --page int 页码, 从 1 开始 (必填, 默认 1) + --limit int 每页条数, 200 以内 (必填, 默认 20) + --query-ble 是否查询蓝牙设备列表 (可选, 默认 false) + --query-position 是否查询地理定位和 Wifi 名称 (可选, 默认 false) + --type string 考勤组类型: FIXED 固定班制 / TURN 排班制 / NONE 自由工时 (可选) +``` + +### 查询考勤组全量信息 +``` +Usage: + dws attendance group get [flags] +Example: + dws attendance group get --group-id 123456 +Flags: + --group-id int 考勤组 ID (必填) +``` + +根据考勤组 ID 查询该考勤组的全量信息。考勤组 ID 可从 `group search` 返回结果中提取,也有可能来源于用户手动输入。如果只需查询成员、打卡地址、蓝牙、Wifi 子集,请使用 `group filtered-get` 以节省查询成本。 +返回结果中如含成员 userId 列表,必须调用 `dws contact user get --ids ,,...`(支持逗号分隔传多个 ID),将 userId 转换为员工姓名后再输出;不得直接输出裸 userId + +### 按需查询考勤组部分信息 +``` +Usage: + dws attendance group filtered-get [flags] +Example: + dws attendance group filtered-get --group-id 123456 --member + dws attendance group filtered-get --group-id 123456 --position --wifi +Flags: + --group-id int 考勤组 ID (必填) + --member 是否查询考勤组成员信息 (可选, 默认 false) + --position 是否查询打卡地址 (可选, 默认 false) + --wifi 是否查询打卡 Wifi (可选, 默认 false) + --bles 是否查询打卡蓝牙 (可选, 默认 false) +``` + +强烈建议在仅需查询成员、打卡地址、蓝牙、Wifi 时调用该命令,避免全量查询带来的性能开销。考勤组 ID 可从 `group search` 返回结果中提取,也有可能来源于用户手动输入。 +返回结果中如含成员 userId 列表,必须调用 `dws contact user get --ids ,,...`(支持逗号分隔传多个 ID),将 userId 转换为员工姓名后再输出;不得直接输出裸 userId + +### 更新考勤组成员 (写场景接口,必须走二次确认流程) +**强制执行流程**:此命令为写操作,Agent 调用时必须遵守以下流程: +1. 先向用户展示待执行操作的完整参数摘要,包括考勤组 ID、要添加/移除的成员列表 +2. 展示变更摘要并等待用户明确确认 +3. 用户确认后,再传全局 `--yes` 执行命令 + +**禁止未经用户确认直接执行或自动添加 `--yes`。** + +``` +Usage: + dws attendance group update-members [flags] +Example: + dws attendance group update-members --group-id 123456 --add-users userId1,userId2 + dws attendance group update-members --group-id 123456 --remove-users userId1 + dws attendance group update-members --group-id 123456 --add-depts deptId1 --remove-users userId2 +Flags: + --group-id int 考勤组 ID (必填) + --add-users string 添加考勤人员 userId 列表, 逗号分隔, 最多 20 个 (可选) + --remove-users string 删除考勤人员 userId 列表, 逗号分隔, 最多 20 个 (可选) + --add-extra-users string 添加无需考勤的人员 userId 列表, 逗号分隔, 最多 20 个 (可选) + --remove-extra-users string 删除无需考勤的成员 userId 列表, 逗号分隔, 最多 20 个 (可选) + --add-depts string 添加考勤部门 ID 列表, 逗号分隔, 最多 20 个 (可选),若要添加全公司,根部门id为-1 + --remove-depts string 删除考勤部门 ID 列表, 逗号分隔, 最多 20 个 (可选),全公司根部门id为-1 +``` + +对指定考勤组的成员进行增删操作。--group-id 必填,其余参数均为可选,但至少需要传入一个变更项,否则命令拒绝执行。每次调用各参数最多传 20 个 ID。"无需考勤"人员指考勤组内豁免打卡的成员(如高管)。 + +### 更新考勤组配置 (写场景接口,必须走二次确认流程) +**强制执行流程**:此命令为写操作,Agent 调用时必须遵守以下流程: +1. 先向用户展示待执行操作的完整参数摘要,包括考勤组 ID、要修改的字段含义及新值 +2. 展示变更摘要并等待用户明确确认 +3. 用户确认后,再传全局 `--yes` 执行命令 + +**禁止未经用户确认直接执行或自动添加 `--yes`。** +``` +Usage: + dws attendance group update [flags] +Example: + dws attendance group update --group-id 123456 --name "研发考勤组" --timeout 10 + dws attendance group update --group-id 123456 --owner userId1 --timeout 10 + dws attendance group update --group-id 123456 --classIds '[1374234767]' --timeout 10 + dws attendance group update --group-id 123456 --group-vo '{"positions":[{"title":"总部","address":"北京市...","latitude":39.9,"longitude":116.4,"offset":200}]}' --timeout 10 +Flags: + --group-id int 考勤组 ID (必填) + --name string 考勤组名称 (可选) + --type string 考勤组类型:FIXED 固定班制 / TURN 排班制 / NONE 自由工时 (可选) + --owner string 考勤组主负责人 userId (可选) + --enable-outside-check 是否允许外勤打卡 true/false (可选) + --classIds string 所选班次 id 列表, JSON 数组格式, 如 '[123,456]' (可选) + --group-vo string 完整 groupVO JSON 字符串, 用于修改复杂子对象 (可选) +``` + +更新考勤组配置。--group-id 必填,其余均可选,但至少需指定一个修改项。仅需对要修改的字段进行赋値,其余字段会自动从已有配置补充。小改用单字段 flag;修改打卡地址、wifi、蓝牙设备、循环排班等复杂子对象时用 `--group-vo` 传入完整 JSON。`--group-vo` 与单字段 flag 同时传入时,单字段 flag 优先级更高。 + +`--group-vo` 支持字段(均可选,只需包含要修改的字段): +- 基础:`name`(名称) `type`(FIXED/TURN/NONE) `owner`(主负责人 userId) `managerList`([]string 子负责人) `skipHolidays`(bool,只在固定班制和自由工时生效) `defaultGroup`(bool) `classIds`([]number,所选班次 id,只有固定班制和排班制才有班次,自由工时没有) +- 打卡范围:`trimDistance`(微调距离) `enablePositionOfGps/Wifi/Ble`(bool) +- 打卡地址:`positions`([]对象: title/address/latitude/longitude/offset,其中 offset 为该地址允许的打卡范围米) +- Wifi:`wifis`([]对象: ssid/macAddr/groupId) +- 蓝牙:`bleDeviceVOList`([]对象: name/deviceUid/sn/productType/devServId) +- 外勤:`enableOutsideCheck`(bool) `enableOutsideCameraCheck/Remark/Apply`(bool) `outsideCheckApproveMode`(NO_NEED_APPROVE/APPROVE_FIRST/CHECK_FIRST/APPROVE_EVERYTIME) `outSideCheckApplyType`(1全天/2上班/3下班) `forbidHideOutSideAddress`(bool) `enableOutSideUpdateNormalCheck`(下班时允许外勤卡更新内勤卡) `enableOnDutyNormalUpdateOutsideCheck`(上班时允许内勤卡更新外勤卡) +- 打卡方式:`enableCameraCheck/openCameraCheck` `openFaceCheck` `enableFaceStrictMode` `enableFaceBeauty`(bool) `onlyMachineCheck`(bool) `permitMaxBeaconCount`(number) `disableCheckWhenRest`(bool,休息日打卡需审批,只在固定班制和排班制生效) +- 固定班制设置(FIXED):`defaultClassId`(number) `workDayClassList`([]number,共7个值代表周日到周六每天的班次id,为0表示当天休息,如[0,1279240003,0,0,0,0,0]表示只有周一上班) +- 排班制设置(TURN):`disableCheckWithoutSchedule`(bool,true=未排班时禁止打卡) `enableEmpSelectClass`(未排班时员工可选班次) `enableScheduleAutoMatch`(未排班时系统自动匹配) + - 循环排班(非必填,不设置则由管理员手动排班):`cycleDays`(number) `startCycleDate`(时间戳,毫秒) `cycleScheduleList`([]对象: cycleName/groupId/isValid(Y/N)/itemList[{classId/className/isValid}]) +- 自由工时设置(NONE):`workDays`([1-7],1=周一7=周日) `freeCheckDayStartMinOffset`(number,距0点分钟数) `freeCheckCoreTime`(最短工作时长,分钟) `freeCheckDemandWorkMinutes`(要求打卡时长,分钟) `freeCheckSettingVO`(对象: freeCheckType(CYCLE上下班交替/MAX_TIME_UPDATE最大时间打卡)/freeWorkDayLackSwitch/freeOnDutyLackMinOffset/freeOffDutyLackMinOffset/delimitOffsetMinutesBetweenDays/freeCheckGapVO{onOffCheckGapMinutes/offOnCheckGapMinutes}) `freeGroupSpecialDayVO`(对象: specialOnDutyDays[]/specialOffDutyDays[]) + +### 创建考勤组 (写场景接口,必须走二次确认流程) +**强制执行流程**:此命令为写操作,Agent 调用时必须遵守以下流程: +1. 先向用户展示待执行操作的完整参数摘要,包括考勤组名称、类型、班次列表等 +2. 展示变更摘要并等待用户明确确认 +3. 用户确认后,再传全局 `--yes` 执行命令 + +**禁止未经用户确认直接执行或自动添加 `--yes`。** +``` +Usage: + dws attendance group create [flags] +Example: + dws attendance group create --name "研发考勤组" --type FIXED --group-vo '{"defaultClassId":1170996821,"workDayClassList":[0,1170996821,0,0,0,0,0]}' --timeout 10 + dws attendance group create --name "自由工时分组" --type NONE --timeout 10 +Flags: + --name string 考勤组名称 (必填) + --type string 考勤组类型:FIXED 固定班制 / TURN 排班制 / NONE 自由工时 (必填) + --owner string 考勤组主负责人 userId (可选) + --group-vo string 完整 groupVO JSON 字符串,用于传入复杂子对象 (可选) +``` + +创建一个新的考勤组。`--name` 和 `--type` 必填,`--type` 必须为 FIXED/TURN/NONE 之一。 + +**条件必填(type=FIXED 固定班制时)**:`--group-vo` 必须包含 `workDayClassList`(工作日班次列表,不能为空,共 7 个值,代表周日到周六每天的班次 ID,为 0 表示当天休息)和 `defaultClassId`(默认班次 ID,不能为 null)。 + +`--group-vo` 可用字段说明(与 `group update` 的 `--group-vo` 一致,均可选,只需包含要设置的字段): + +【基础信息】 + id number 考勤组 id(创建时无需传入,由服务端分配) + name string 考勤组名称(必填) + type string 考勤组类型:FIXED(固定班制)/ TURN(排班制)/ NONE(自由工时)(必填) + owner string 考勤组主负责人 userId + managerList []string 考勤组子负责人 userId 列表 + skipHolidays bool 节假日自动排休(只有固定班制和自由工时考勤组生效) + defaultGroup bool 是否默认考勤组 + classIds []number 所选班次 id 列表(只有固定班制和排班制才有班次,自由工时没有) + +【打卡范围与定位】 + trimDistance number 定位允许微调距离(米) + enablePositionOfGps bool 打卡是否允许开启 GPS 定位 + enablePositionOfWifi bool 打卡是否允许开启 Wifi 定位 + enablePositionOfBle bool 打卡是否允许开启蓝牙定位 + enableMacCheck bool 开启 MAC 地址校验 + checkDistanceType string 打卡距离类型:NORMAL(正常) / OUTER_ADDRESS(仅允许在外勤地址打卡) + wifiCompanyId number 允许打卡的 Wifi 公司 ID + +【打卡地址】 + positions []object 打卡地址列表,每个对象字段: + title string 地址名称 + address string 详细地址 + latitude number 纬度 + longitude number 经度 + offset number 该地址允许的打卡范围(米) + +【打卡 Wifi】 + wifis []object 打卡 Wifi 列表,每个对象字段: + ssid string Wifi 名称 + macAddr string MAC 地址 + groupId number 所属考勤组 ID + +【蓝牙设备】 + bleDeviceVOList []object 蓝牙设备列表,每个对象字段: + name string 设备名称 + deviceUid string 设备 ID + sn string 序列号 + productType string 产品类型 + devServId number 设备服务 ID + +【外勤打卡设置】 + enableOutsideCheck bool 是否允许外勤打卡 + openOutsideCameraCheck bool 外勤打卡是否开启拍照 + enableOutsideRemark bool 是否允许外勤备注 + enableOutsideApply bool 外勤打卡是否需审批 + forbidHideOutSideAddress bool 禁止隐藏外勤打卡地址 + enableOutSideUpdateNormalCheck bool 下班时允许外勤卡更新内勤卡 + enableOnDutyNormalUpdateOutsideCheck bool 上班时允许内勤卡更新外勤卡 + outsideCheckApproveMode string 外勤审批模式:NO_NEED_APPROVE / APPROVE_FIRST / CHECK_FIRST / APPROVE_EVERYTIME + outSideCheckApplyType number 外勤打卡申请类型:1 全天 / 2 上班 / 3 下班 + +【打卡方式】 + openCameraCheck bool 是否开启拍照打卡 + enableFaceStrictMode bool 是否开启人脸严格模式 + enableFaceBeauty bool 是否开启人脸美颜 + onlyMachineCheck bool 是否仅允许考勤机打卡 + permitMaxBeaconCount number 允许最大蓝牙信标数量 + disableCheckWhenRest bool 休息日打卡需审批(只在固定班制和排班制生效) + +【固定班制设置(FIXED)】 + defaultClassId number 默认班次 ID + workDayClassList []number 工作日班次列表,共 7 个值代表周日到周六每天的班次 id,为 0 表示当天休息。 + 例如 [0,1279240003,0,0,0,0,0] 表示只有周一上班 + +【排班制设置(TURN)】 + disableCheckWithoutSchedule bool 未排班时禁止打卡 + enableEmpSelectClass bool 未排班时员工可选班次 + enableScheduleAutoMatch bool 未排班时系统自动匹配 + cycleDays number 循环排班周期天数 + startCycleDate number 循环排班开始日期(时间戳,毫秒) + cycleScheduleList []object 循环排班列表: + cycleName string 循环排班名称 + groupId number 考勤组 ID + isValid string 是否生效:Y/N + itemList []object 循环排班明细: + classId number 班次 ID + className string 班次名称 + isValid string 是否生效:Y/N + +【自由工时设置(NONE)】 + workDays []number 工作日,[1-7],1=周一,7=周日 + freeCheckDayStartMinOffset number 距 0 点分钟数 + freeCheckCoreTime number 最短工作时长(分钟) + freeCheckDemandWorkMinutes number 要求打卡时长(分钟) + freeCheckSettingVO object 自由打卡设置: + freeCheckType string 打卡类型:CYCLE(上下班交替)/ MAX_TIME_UPDATE(最大时间打卡) + freeWorkDayLackSwitch bool 工作日缺卡开关 + freeOnDutyLackMinOffset number 上班缺卡分钟数偏移 + freeOffDutyLackMinOffset number 下班缺卡分钟数偏移 + delimitOffsetMinutesBetweenDays number 跨天切割分钟数偏移 + freeCheckGapVO object 打卡间隔: + onOffCheckGapMinutes number 上班到下班最小间隔(分钟) + offOnCheckGapMinutes number 下班到上班最小间隔(分钟) + freeGroupSpecialDayVO object 特殊日期设置: + specialOnDutyDays []number 特殊上班日期 + specialOffDutyDays []number 特殊休息日期 + +当使用 `--group-vo` 传入完整 JSON 时,`--name`、`--type`、`--owner` 仍可覆写 JSON 中的同名字段。由于保存考勤组耗时较久,建议加 `--timeout 10`。 + +> **创建成功后的跳转链接**:`group create` 调用成功后,CLI 会在标准输出之后额外打印一条钉钉 PC 端跳转链接(仅在响应中同时包含 `corpId` 与 `groupId`/`id` 时输出),格式为 `dingtalk://dingtalkclient/page/link?url=https%3A%2F%2Fhrmregister.dingtalk.com%2Fsubapp%2Fattend%2Findex%3Fcode%3Dattend%26corpId%3D{corpId}%26ddtab%3Dtrue%26from%3Dattend%23%2FgroupModify%3Fid%3D{groupId}`,用于用户在钉钉 PC 客户端一键打开该考勤组详情页验证创建结果。Agent 遇到该跳转链接时应原样呈现给用户,不要对链接进行二次编码或裁剪。 + +> **新建考勤组并同时添加成员的工作流**:`group create` 不支持在创建时直接传入成员列表。若用户需要在新建考勤组后立即添加成员,必须先执行 `group create` 创建考勤组,从返回结果中提取 `groupId`,再执行 `group update-members --group-id --add-users ...` 完成成员添加。 + +### 查询某个人的考勤统计摘要 +``` +Usage: + dws attendance summary [flags] +Example: + dws attendance summary --user USER_ID --date 2026-03-12 --stats-type week +Flags: + --date string 查询日期,格式 YYYY-MM-DD 或 YYYY-MM-DD HH:mm:ss(必填) + --stats-type string 统计类型:week(周统计)/ month(月统计)(必填) + --user string 钉钉用户 ID(必填) +``` + +### 查询考勤组与考勤规则 +``` +Usage: + dws attendance rules [flags] +Example: + dws attendance rules --date 2026-03-14 + dws attendance rules --date "2026-03-14 09:00:00" +Flags: + --date string 考勤日期, 格式 YYYY-MM-DD 或 yyyy-MM-dd HH:mm:ss (必填) +``` + +查询考勤组/考勤规则。例如:我属于哪个考勤组、打卡范围是什么、弹性工时怎么算。 + +### 查询个人规则设置 +``` +Usage: + dws attendance selfsetting get [flags] +Example: + dws attendance selfsetting get --setting-scene checkRemind --user --format json + dws attendance selfsetting get --setting-scene fastCheck --user --format json +Flags: + --setting-scene string 查询设置项: checkRemind/fastCheck/checkResultNotify/lackRemind/personalAttendStatNotify/bossAttendStatNotify (必填) + --user string 查询用户 ID (必填) +``` + +调用 MCP 工具 query_self_setting 查询个人规则设置,包括打卡提醒、极速打卡、打卡结果通知、缺卡提醒、个人考勤统计通知、团队考勤统计通知等设置项。MCP 入参 `userId` 必填;CLI 的 `--user` 也必填,必须显式传入目标用户 ID。认证信息 `corpId` 和 `opUserId` 由当前登录上下文自动注入,无需手动传入。 + +`--setting-scene` 枚举值: +- `checkRemind`: 打卡提醒 +- `fastCheck`: 极速打卡 +- `checkResultNotify`: 打卡结果通知 +- `lackRemind`: 缺卡提醒 +- `personalAttendStatNotify`: 个人考勤统计通知 +- `bossAttendStatNotify`: 团队考勤统计通知 + +返回 `ServiceResult`,包含 `success`、`code`、`message`、`result`。`result` 可能根据 `--setting-scene` 仅返回对应设置项相关字段。常见字段包括: +- `checkRemind`: `checkRemindSetting`、`checkRemindUserOnDuty`、`checkRemindUserOffDuty`、`enableOndutyCheckRemindOfPc`、`enableOffdutyCheckRemindOfPc` +- `fastCheck`: `ondutyCheckType`、`offdutyCheckType`、`ondutyRemindStartMin`、`ondutyRemindEndMin`、`offdutyRemindStartMin`、`offdutyRemindEndMin`、`fastCheckLateNeedConfirm`、`canUpdateOffDuty`、`voiceRemindSwitch`、`vibrationRemindSwitch` +- `checkResultNotify`: `checkResultMsg`, 取值 0 表示关闭, 1 表示开启 +- `lackRemind`: `lackSendTodoMsg`、`lackRemindUser`, 取值 0 表示关闭, `null` 或 1 表示开启 +- `personalAttendStatNotify`: `personDailyReportSwitch`、`personWeekReportType`、`personMonthReportType` +- `bossAttendStatNotify`: `bossPushStartMin`、`bossWeekReportType`、`bossMonthReportType` + +其中周报/月报通知渠道枚举值:0 表示全关闭,1 表示工作通知,2 表示钉邮,3 表示工作通知和钉邮。 + +### 更新保存个人规则设置 (写场景接口,必须走二次确认流程) +**强制执行流程**:此命令为写操作,Agent 调用时必须遵守以下流程: +1. 先向用户展示待执行操作的完整参数摘要,包括目标用户、设置场景、当前值、新值和最终命令参数 +2. 展示变更摘要并等待用户明确确认 +3. 用户确认后,再传全局 `--yes` 执行命令 + +**禁止未经用户确认直接执行或自动添加 `--yes`。** + +``` +Usage: + dws attendance selfsetting save [flags] +Example: + # 开启打卡结果通知(执行前必须展示变更摘要并获得用户明确确认,确认后再追加 --yes) + dws attendance selfsetting save --setting-scene checkResultNotify --user --check-result-msg 1 --yes --format json + # 更新极速打卡设置(执行前必须展示变更摘要并获得用户明确确认,确认后再追加 --yes) + dws attendance selfsetting save --setting-scene fastCheck --user --onduty-check-type 3 --voice-remind-switch=true --yes --format json + # 更新打卡提醒设置(执行前必须展示变更摘要并获得用户明确确认,确认后再追加 --yes) + dws attendance selfsetting save --setting-scene checkRemind --user --check-remind-user-on-duty=false \ + --check-remind-setting '{"onDutyRemind":{"openRemind":true,"remindMinutes":10}}' --yes --format json +Flags: + --setting-scene string 更新设置项: checkRemind/fastCheck/checkResultNotify/lackRemind/personalAttendStatNotify/bossAttendStatNotify (必填) + --user string 更新用户 ID (必填) + --check-remind-setting string 打卡提醒 DING 渠道设置 JSON + --check-remind-user-on-duty 打卡提醒工作通知渠道:用户个人上班打卡提醒开关 + --check-remind-user-off-duty 打卡提醒工作通知渠道:用户个人下班打卡提醒开关 + --enable-onduty-check-remind-of-pc PC 端弹窗渠道:上班打卡提醒开关 + --enable-offduty-check-remind-of-pc PC 端弹窗渠道:下班打卡提醒开关 + --onduty-check-type int 上班极速打卡方式:1 提醒打卡,2 不提醒且不自动打卡,3 自动打卡 + --offduty-check-type int 下班极速打卡方式:1 提醒打卡,2 不提醒且不自动打卡,3 自动打卡 + --onduty-remind-start-min int 上班打卡提醒开始时间,单位:分钟 + --onduty-remind-end-min int 上班打卡提醒结束时间,单位:分钟 + --offduty-remind-start-min int 下班打卡提醒开始时间,单位:分钟 + --offduty-remind-end-min int 下班打卡提醒结束时间,单位:分钟 + --fast-check-late-need-confirm 迟到时是否需要二次确认 + --can-update-off-duty 是否允许用户更新下班打卡设置 + --voice-remind-switch 极速打卡提示音开关 + --vibration-remind-switch 极速打卡震动提醒开关 + --check-result-msg int 打卡结果通知开关:0 关闭,1 开启 + --lack-send-todo-msg int 缺卡提醒待办渠道:0 关闭,null 或 1 开启 + --lack-remind-user int 缺卡提醒工作通知渠道:0 关闭,null 或 1 开启 + --person-daily-report-switch int 个人考勤统计日报推送开关:0 关闭,1 开启 + --person-week-report-type int 个人考勤统计周报通知渠道:0 全关闭,1 工作通知,2 钉邮,3 工作通知和钉邮 + --person-month-report-type int 个人考勤统计月报通知渠道:0 全关闭,1 工作通知,2 钉邮,3 工作通知和钉邮 + --boss-push-start-min int 团队考勤统计日报推送开始时间,单位:分钟;-1 表示关闭日报推送 + --boss-week-report-type int 团队考勤统计周报通知渠道:0 全关闭,1 工作通知,2 钉邮,3 工作通知和钉邮 + --boss-month-report-type int 团队考勤统计月报通知渠道:0 全关闭,1 工作通知,2 钉邮,3 工作通知和钉邮 + --yes 用户已确认,跳过交互式确认提示 + 传入前必须已获得用户明确确认 +``` + +调用 MCP 工具 save_self_setting 更新保存个人规则设置,请求体封装在 `RuleMcpSaveSelfSettingRequest` 中。`settingScene` 必填;MCP 入参 `userId` 必填,CLI 的 `--user` 也必填,必须显式传入目标用户 ID。认证信息 `corpId` 和 `opUserId` 由当前登录上下文自动注入,无需手动传入。 + +`selfsetting save` 必须按 `--setting-scene` 传入对应场景的字段,且至少一个字段有值: +- `checkRemind`: `checkRemindSetting`、`checkRemindUserOnDuty`、`checkRemindUserOffDuty`、`enableOndutyCheckRemindOfPc`、`enableOffdutyCheckRemindOfPc` +- `fastCheck`: `ondutyCheckType`、`offdutyCheckType`、`ondutyRemindStartMin`、`ondutyRemindEndMin`、`offdutyRemindStartMin`、`offdutyRemindEndMin`、`fastCheckLateNeedConfirm`、`canUpdateOffDuty`、`voiceRemindSwitch`、`vibrationRemindSwitch` +- `checkResultNotify`: `checkResultMsg` +- `lackRemind`: `lackSendTodoMsg`、`lackRemindUser` +- `personalAttendStatNotify`: `personDailyReportSwitch`、`personWeekReportType`、`personMonthReportType` +- `bossAttendStatNotify`: `bossPushStartMin`、`bossWeekReportType`、`bossMonthReportType` + +返回 `ServiceResult`,包含 `success`、`code`、`message`、`result`。其中 `result` 为 boolean,表示保存是否成功。 + +#### 强制执行流程:Agent 调用 `selfsetting save` + +`selfsetting save` 是写操作,会修改用户个人规则设置。Agent 调用时 **必须按以下流程执行**,**禁止**在未确认的情况下直接提交: + +1. **识别写操作**:用户表达“更新个人规则设置 / 保存打卡提醒 / 修改极速打卡 / 关闭缺卡提醒 / 开启打卡结果通知 / 设置个人考勤统计通知 / 设置团队考勤统计通知”等意图时,命中 `selfsetting save`。 +2. **收集必要参数**:必须明确 `--user`、`--setting-scene`,以及对应场景下将要修改的字段和值。 +3. **前置查询当前设置**:执行 `dws attendance selfsetting get --setting-scene --user --format json`,获取当前配置,用于确认目标用户和当前值。 +4. **展示待写入数据并等待确认**:向用户展示目标用户、设置场景、要更新的字段、当前值、新值和最终命令参数摘要,并等待用户明确确认。 +5. **用户确认后再执行保存**:**只有用户明确确认后**,才可以追加全局 `--yes` 执行 `dws attendance selfsetting save ... --yes --format json`。 + +确认话术示例: + +```text +即将更新个人考勤规则设置,请确认: +- 用户 ID: +- 设置场景:checkResultNotify +- 修改内容: + - 打卡结果通知(checkResultMsg):关闭 → 开启 + +是否确认执行更新? +``` + +禁止在未获得用户明确确认前执行保存;禁止为了推进流程自动添加全局 `--yes`。即使用户在最初需求中表达“直接改/不用问”,也必须先展示完整参数摘要并获得明确确认,才允许追加 `--yes` 执行。 + +### 查询全局规则设置(仅管理员) +``` +Usage: + dws attendance globalsetting get [flags] +Example: + dws attendance globalsetting get --scope 企业 --setting-scene checkRemind --format json + dws attendance globalsetting get --scope 全公司 --setting-scene bossAttendStatNotify --format json +Flags: + --setting-scene string 查询设置项: checkRemind/fastCheck/checkResultNotify/lackRemind/personalAttendStatNotify/bossAttendStatNotify (必填) + --scope string 全局范围确认,必须明确输入:企业/全公司/所有人(必填) +``` + +调用 MCP 工具 `query_global_setting` 查询全局规则设置,请求体封装在 `RuleMcpQueryGlobalSettingRequest` 中。`settingScene` 必填;CLI 必须通过 `--scope` 明确输入 `企业`、`全公司` 或 `所有人`,用于确认查询的是全局范围。认证信息 `corpId` 和 `opUserId` 由当前登录上下文自动注入,无需手动传入。该接口仅管理员可以调用。 + +返回 `ServiceResult`,包含 `success`、`code`、`message`、`result`。`result` 常见字段包括: +- `checkRemindCorp`: 打卡提醒企业总开关 +- `checkRemindPcCorp`: 打卡提醒 PC 端企业总开关 +- `fastCheckCorp`: 极速打卡企业总开关 +- `enableCheckCertPush`: 打卡结果通知企业总开关 +- `lackRemindCorp`: 缺卡提醒企业总开关 +- `enablePersonalDailyReport`: 个人考勤统计通知日报企业总开关 +- `enablePersonalWeeklyReport`: 个人考勤统计通知周报企业开关,钉邮渠道 +- `enablePersonalWeeklyReportCard`: 个人考勤统计通知周报企业开关,工作通知渠道 +- `enablePersonalMonthlyReport`: 个人考勤统计通知月报企业总开关 +- `bossDailyReportType`: 团队考勤统计通知日报发送渠道类型,0 全关闭,1 开启 +- `bossWeeklyReportType`: 团队考勤统计通知周报发送渠道类型,0 全关闭,1 工作通知,2 钉邮,3 工作通知和钉邮 +- `bossMonthlyReportType`: 团队考勤统计通知月报发送渠道类型,0 全关闭,1 工作通知,2 钉邮,3 工作通知和钉邮 + +### 更新保存全局规则设置(写场景接口,仅管理员,必须走二次确认流程) +**强制执行流程**:此命令为写操作,Agent 调用时必须先向用户展示待执行操作的完整参数摘要,等待用户明确确认后,才能追加 `--yes` 执行。 + +``` +Usage: + dws attendance globalsetting save [flags] +Example: + dws attendance globalsetting save --scope 企业 --setting-scene checkRemind --check-remind-corp=true --yes --format json + dws attendance globalsetting save --scope 全公司 --setting-scene fastCheck --fast-check-corp=false --yes --format json + dws attendance globalsetting save --scope 所有人 --setting-scene bossAttendStatNotify --boss-daily-report-type 1 --boss-weekly-report-type 3 --yes --format json +Flags: + --setting-scene string 更新设置项: checkRemind/fastCheck/checkResultNotify/lackRemind/personalAttendStatNotify/bossAttendStatNotify (必填) + --scope string 全局范围确认,必须明确输入:企业/全公司/所有人(必填) + --check-remind-corp 打卡提醒企业总开关 + --check-remind-pc-corp 打卡提醒 PC 端弹窗企业总开关 + --fast-check-corp 极速打卡企业总开关 + --enable-check-cert-push 打卡结果通知企业总开关 + --lack-remind-corp 缺卡提醒企业总开关 + --enable-personal-daily-report 个人考勤统计通知日报企业总开关 + --enable-personal-weekly-report 个人考勤统计通知周报企业开关,钉邮渠道 + --enable-personal-weekly-report-card 个人考勤统计通知周报企业开关,工作通知渠道 + --enable-personal-monthly-report 个人考勤统计通知月报企业总开关 + --boss-daily-report-type int 团队考勤统计通知日报发送渠道类型:0 全关闭,1 开启 + --boss-weekly-report-type int 团队考勤统计通知周报发送渠道类型:0 全关闭,1 工作通知,2 钉邮,3 工作通知和钉邮 + --boss-monthly-report-type int 团队考勤统计通知月报发送渠道类型:0 全关闭,1 工作通知,2 钉邮,3 工作通知和钉邮 + --yes 用户已确认,跳过交互式确认提示 +``` + +调用 MCP 工具 `save_global_setting` 更新保存全局规则设置,请求体封装在 `RuleMcpSaveGlobalSettingRequest` 中。`settingScene` 必填;CLI 必须通过 `--scope` 明确输入 `企业`、`全公司` 或 `所有人`,用于确认更新的是全局范围。认证信息 `corpId` 和 `opUserId` 由当前登录上下文自动注入,无需手动传入。该接口仅管理员可以调用。 + +`globalsetting save` 必须按 `--setting-scene` 传入对应场景的字段,且至少一个字段有值: +- `checkRemind`: `checkRemindCorp`、`checkRemindPcCorp` +- `fastCheck`: `fastCheckCorp` +- `checkResultNotify`: `enableCheckCertPush` +- `lackRemind`: `lackRemindCorp` +- `personalAttendStatNotify`: `enablePersonalDailyReport`、`enablePersonalWeeklyReport`、`enablePersonalWeeklyReportCard`、`enablePersonalMonthlyReport` +- `bossAttendStatNotify`: `bossDailyReportType`、`bossWeeklyReportType`、`bossMonthlyReportType` + +返回 `ServiceResult`,其中 `result` 为 boolean,表示保存是否成功。 + +### 获取企业考勤字段列表(仅管理员) +``` +Usage: + dws attendance report columns +Example: + dws attendance report columns +``` + +根据操作者的列权限,过滤并返回其有权查看的考勤字段列表。操作者必须是管理员,否则返回权限错误。 + +### 根据字段查询考勤数据(仅管理员) +``` +Usage: + dws attendance report query-data [flags] +Example: + dws attendance report query-data \ + --users userId1,userId2 --columns 1001,1002 --start "2026-03-01 00:00:00" --end "2026-03-31 23:59:59" +Flags: + --columns string 字段 ID 列表, 逗号分隔, 可通过 report columns 获取(必填) + --end string 结束日期, 格式 yyyy-MM-dd HH:mm:ss(必填) + --start string 开始日期, 格式 yyyy-MM-dd HH:mm:ss(必填) + --users string 目标用户 ID 列表, 逗号分隔, 最多 20 人(必填) +``` + +根据字段查询考勤数据,含列权限过滤和用户查看权限校验。--users 最多 20 人,--start 到 --end 不超过 32 天。 + +### 查询用户假期数据(仅管理员) +``` +Usage: + dws attendance report query-leave [flags] +Example: + dws attendance report query-leave \ + --users userId1,userId2 --leave-names 年假,病假 --start "2026-03-01 00:00:00" --end "2026-03-31 23:59:59" +Flags: + --end string 结束日期, 格式 yyyy-MM-dd HH:mm:ss(必填) + --leave-names string 假期类型名称列表, 逗号分隔, 不填则查询所有假期类型(选填) + --start string 开始日期, 格式 yyyy-MM-dd HH:mm:ss(必填) + --users string 目标用户 ID 列表, 逗号分隔, 最多 20 人(必填) +``` + +查询用户假期数据,含用户查看权限校验。--users 最多 20 人,--start 到 --end 不超过 32 天。 + +### 查询当前用户假期规则列表 +``` +Usage: + dws attendance vacation types +Example: + dws attendance vacation types +Flags: + 无 +``` + +调用 MCP 工具 get_leave_types 查询当前用户可用的假期规则列表。例如:年假、事假、病假等假期类型及对应规则。请求体封装在 McpLeaveTypeRequest 中,认证信息(corpId、opUserId)由系统自动注入,无需手动传入。 + +### 查询指定员工假期余额 +``` +Usage: + dws attendance vacation balance [flags] +Example: + dws attendance vacation balance --users userId1,userId2 --leave-code a1b2c3d4-e5f6-7890-abcd-ef1234567890 +Flags: + --users string 目标员工 ID 列表, 逗号分隔 (必填) + --leave-code string 假期规则 code (选填,不传则查询所有假期规则余额) +``` + +调用 MCP 工具 get_leave_balance_quota 查询指定员工的假期余额。例如:查询某员工年假还剩多少、病假额度等。`--leave-code` 可通过 `vacation types` 获取;不传 `--leave-code` 时查询所有假期规则余额。认证信息(corpId、opUserId)由系统自动注入。 + +如用户需要“所有假期规则余额 / 导出假期余额列表 / 所有假期规则余额 Excel / 按截图样式导出假期余额”,必须先读取 [attendance-vacation.md](./attendance-vacation.md),再按其中工作流调用脚本生成 Excel。 + +### 查询指定员工假期余额变更记录 +``` +Usage: + dws attendance vacation records [flags] +Example: + dws attendance vacation records --user USER_ID --leave-code a1b2c3d4-e5f6-7890-abcd-ef1234567890 --start 2026-04-01 --end 2026-04-22 +Flags: + --user string 指定查询员工 ID (必填) + --leave-code string 假期规则 code (必填, 不传则无法查询) + --start string 查询开始日期, 格式 YYYY-MM-DD (必填) + --end string 查询结束日期, 格式 YYYY-MM-DD (必填) +``` + +调用 MCP 工具 get_leave_balance_records 查询指定员工的假期余额变更记录。例如:查询某员工年假变更历史、请假扣减记录等。`--leave-code` 可通过 `vacation types` 获取。认证信息(corpId、opUserId)由系统自动注入。 + +### 更新假期规则(写场景接口,必须走二次确认流程) + +**强制执行流程**:此命令为写操作,Agent 调用时必须遵守以下流程: +1. 先向用户展示待执行操作的完整参数摘要 +2. 展示变更摘要并等待用户明确确认 +3. 用户确认后,再传 `--user-say-yes=true` 执行命令 + +**禁止未经用户确认直接执行或自动添加 `--user-say-yes=true`。** + +``` +Usage: + dws attendance vacation update-type [flags] +Example: + # 更新假期规则名称 + dws attendance vacation update-type --leave-code a1b2c3d4-e5f6-7890-abcd-ef1234567890 --name "事假(修改版)" + + # 更新假期单位 + dws attendance vacation update-type --leave-code a1b2c3d4-e5f6-7890-abcd-ef1234567890 --unit hour --per-hours 8 + + # 改为指定部门可见 + dws attendance vacation update-type --leave-code a1b2c3d4-e5f6-7890-abcd-ef1234567890 --visibility-rules '[{"type":"dept","visible":["1","2","3"]}]' + + # 改为全公司可见(哨兵约定:必须显式传 "-1",空数组 [] 不生效) + dws attendance vacation update-type --leave-code a1b2c3d4-e5f6-7890-abcd-ef1234567890 --visibility-rules '[{"type":"dept","visible":["-1"]}]' +Flags: + --leave-code string 假期编码(必填) + --name string 假期名称(可选) + --unit string 假期单位:day/halfDay/hour(可选) + --paid bool 是否带薪假期(可选,默认 false) + --per-hours int 一天折算小时数(可选) + --when-can-leave string 新员工请假规则:entry/formal(可选) + --visibility-rules string 适用范围规则 JSON 数组(可选) + - 不传:不修改原有可见范围 + - 改为指定范围:[{"type":"staff|dept|label","visible":["id1",...]}, ...] + - 改为全公司可见(哨兵):必须显式传 [{"type":"dept","visible":["-1"]}] + - 空数组 []、[{}]、visible 为空等 → CLI 报错(HSF 端会静默忽略) + --user-say-yes 用户已确认,跳过交互式确认提示 + Agent 调用时传 true 前必须完成用户二次确认 +``` + +调用 MCP 工具 save_leave_type 更新已有假期规则。`--leave-code` 必填,指定要更新的假期规则编码。其他字段均为可选,仅需传入要修改的字段。除 `--leave-code` 外,必须至少传入一个更新字段。 + +**`--visibility-rules` HSF 反序列化约定(必读)**: + +| 入参形态 | 业务语义 | CLI / 服务端处理 | +|---|---|---| +| 不传 | 不修改可见范围 | 服务端保留原有 `visibilityRules` | +| `[{"type":"dept","visible":["-1"]}]`(哨兵) | 改为全公司可见 | 服务端落库为空数组(全公司语义) | +| `[{"type":"staff","visible":["uid1"]}, ...]` 等有效规则 | 改为指定可见范围 | 覆盖原有 `visibilityRules` | +| 空数组 `[]` / `[{}]` / `visible` 为空 | — (反例) | **CLI 直接报错**,避免 HSF 端被静默忽略 | + +- `type` 可取 `dept`(部门 ID,`-1` 是根部门 = 全公司哨兵)、`staff`(userId)、`label`(角色 ID)。 +- 哨兵识别采用宽松匹配:传入列表中任意一条规则满足 `type=dept` 且 `visible` 包含 `"-1"`,即视为全公司可见,其它规则被忽略。 +- 若意图是“改为全公司可见”,**必须**显式传哨兵值 `["-1"]`,**不能**传空数组 `[]`。 + +#### 强制执行流程:Agent 调用 `vacation update-type` + +`vacation update-type` 是写操作,会修改假期规则配置。Agent 调用时 **必须按以下流程执行**,**禁止**在未确认的情况下直接提交: + +1. **识别写操作**:用户表达"更新假期规则 / 修改假期类型 / 编辑假期规则"等意图时,命中 `vacation update-type`。 +2. **收集必要参数**:必须明确 `--leave-code`,以及至少一个更新字段(`--name`、`--unit`、`--paid`、`--per-hours`、`--when-can-leave`、`--visibility-rules`)。 +3. **前置查询当前规则**:需先调用 `vacation types` 确认该规则是否存在及当前配置。 +4. **展示待写入数据并等待确认**:向用户展示假期编码、要更新的字段及新值,并询问是否确认执行。**必须等待用户明确确认**。 +5. **用户确认后再执行保存**:**只有用户明确确认后**,才可以传 `--user-say-yes=true` 执行 `dws attendance vacation update-type ... --format json`。 + +确认话术示例: + +```text +即将更新假期规则,请确认: +- 假期编码:a1b2c3d4-e5f6-7890-abcd-ef1234567890 +- 更新内容: + - 名称:事假 → 事假(修改版) + +是否确认执行更新? +``` + +如用户明确要求跳过确认,可传 `--user-say-yes=true`;否则默认必须等待确认。 + +#### 强制执行流程:Agent 调用 `vacation save-balance` + +`vacation save-balance` 是写操作,会直接替换员工的假期余额(SET 接口,而非 ADD)。Agent 调用时 **必须按以下流程执行**,**禁止**在未确认的情况下直接提交: + +1. **识别写操作**:用户表达"设置假期余额 / 调整假期额度 / 更新假期余额 / 增加假期余额 / 发放年假 / 给员工加年假"等意图时,命中 `vacation save-balance`。 +2. **前置查询当前余额**:必须先调用 `vacation balance --users --leave-code ` 获取当前余额,因为这是 SET 接口,传入值会直接替换而非累加。 +3. **收集必要参数**:必须明确 `--target`(目标员工)、`--leave-code`(假期编码)、`--num`(新余额数量)、`--reason`(变更原因),以及可选参数 `--start/--end`(有效期)。 +4. **计算变更并展示确认**:向用户展示目标员工、假期类型、当前余额、新余额、差额(增加或减少)、变更原因、有效期等,并询问是否确认执行。**必须等待用户明确确认**。 +5. **用户确认后再执行保存**:**只有用户明确确认后**,才可以传 `--user-say-yes=true` 执行 `dws attendance vacation save-balance ... --format json`。 + +确认话术示例: + +**设置余额场景**: +```text +即将设置员工假期余额,请确认: +- 目标员工:张三(user001) +- 假期类型:年假(leaveCode: a1b2c3d4-e5f6-7890-abcd-ef1234567890) +- 当前余额:5 天 +- 新余额:8 天 +- 变更差额:+3 天(增加) +- 变更原因:年度发放 +- 有效期:2024-01-01 至 2024-12-31 + +是否确认执行设置? +``` + +**减少余额场景**: +```text +即将设置员工假期余额,请确认: +- 目标员工:李四(user002) +- 假期类型:年假(leaveCode: a1b2c3d4-e5f6-7890-abcd-ef1234567890) +- 当前余额:10 天 +- 新余额:2 天 +- 变更差额:-8 天(减少) +- 变更原因:请假扣减 + +注意:此操作将大幅减少余额,请确认是否继续? + +是否确认执行设置? +``` + +如用户明确要求跳过确认,可传 `--user-say-yes=true`;否则默认必须等待确认。 + +### 设置员工假期余额 +``` +Usage: + dws attendance vacation save-balance [flags] +Example: + # 设置员工年假余额为8天 + dws attendance vacation save-balance --target user001 \ + --leave-code a1b2c3d4-e5f6-7890-abcd-ef1234567890 --num 8 --reason "年度发放" + + # 设置带有效期的假期余额 + dws attendance vacation save-balance --target user001 \ + --leave-code a1b2c3d4-e5f6-7890-abcd-ef1234567890 --num 8 --reason "年度发放" \ + --start 2024-01-01 --end 2024-12-31 +Flags: + --target string 目标员工工号(必填) + --leave-code string 假期编码(必填) + --num string 余额数量,如8天传8,7.5天传7.5(必填) + --reason string 变更原因,最长100字符(必填) + --start string 有效期开始日期 YYYY-MM-DD(可选) + --end string 有效期结束日期 YYYY-MM-DD(可选) + --user-say-yes 用户已确认,跳过交互式确认提示 + Agent 调用时传 true 前必须完成用户二次确认 +``` + +**重要:这是设置(SET)接口,传入的值会替换当前余额,而非增加(ADD)。**余额数量在传递给 MCP 时会自动乘以 100(如 8 天传 800)。执行前会展示待写入数据,需用户确认后提交。 + +### 查询指定员工的签到记录 +``` +Usage: + dws attendance checkin records [flags] +Example: + dws attendance checkin records \ + --operator-staff-id op001 --staff-ids user001,user002 --start "2026-04-01 00:00:00" --end "2026-04-07 00:00:00" +Flags: + --end string 结束时间, 格式 yyyy-MM-dd HH:mm:ss(必填) + --operator-corp-id string 操作者企业 ID(必填) + --operator-staff-id string 操作者员userID(必填) + --staff-ids string 目标员工userID 列表, 逗号分隔(必填),员工数最多100个人 + --start string 开始时间, 格式 yyyy-MM-dd HH:mm:ss(必填),开始到结束时间限制在7天 +``` + +调用 MCP 工具 get_checkin_record 查询指定员工在一段时间内的签到记录。权限说明:Boss/超级管理员可查看全公司员工,子管理员可查看管理范围内员工,部门主管可查看所管理部门员工,普通员工只能查询自己。接口单次最多返回100条签到记录。 + +返回结构:`result.list` 为签到记录数组,每条记录包含以下字段(按 sortedProps 顺序): +- `corpId`(string):企业 ID +- `name`(string):用户名称 +- `userId`(string):用户 ID +- `timestamp`(number):签到时间 +- `place`(string):签到地点 +- `detailPlace`(string):签到详细地点 +- `longitude`(number):签到地点经度 +- `latitude`(number):签到地点纬度 +- `remark`(string):备注信息 +- `imageList`(string[]):图片列表 +- `customers`(string):拜访客户 +- `checkinType`(string):签到类型 +- `mobileId`(string):设备 ID + +顶层还包含 `success`(boolean)和 `arguments`(object[])字段。 diff --git a/skills/multi/dingtalk-attendance/references/attendance-constraints.md b/skills/multi/dingtalk-attendance/references/attendance-constraints.md new file mode 100644 index 00000000..7397b708 --- /dev/null +++ b/skills/multi/dingtalk-attendance/references/attendance-constraints.md @@ -0,0 +1,10 @@ +# 严格约束 + +## 严格约束 +- 不要凭历史记忆复用 userId / classId / leaveCode / groupId / instanceId 等任何 ID,每次必须从当次命令返回值中提取 +- 不要猜测命令,先查询明确命令,不要使用记忆中未经证实(即不存在于本.md文件中的指令)或执行失败(指令传参有误或flag不正确)的的命令 +- 制定 plan 并自我审查,严格按 plan 执行 +- 涉及超过 3 条记录的聚合(求和、分组、计数、排序、跨字段计算)时必须落 Python 脚本处理,禁止用大模型口算或目测。脚本里如果用到 mcp,先提前看下 mcp 返回的结构,避免执行异常 +- 遇到时长字段时,注意区分单位是秒、分钟还是小时 +- 遇到意图不清晰的场景不要猜测,主动询问用户明确意图 +- 如果查询结果很多时,不要自作主张省略,必须明确告知用户或者用表格或展示所有。 diff --git a/skills/multi/dingtalk-attendance/references/attendance-context.md b/skills/multi/dingtalk-attendance/references/attendance-context.md new file mode 100644 index 00000000..2901b02e --- /dev/null +++ b/skills/multi/dingtalk-attendance/references/attendance-context.md @@ -0,0 +1,13 @@ +# 上下文传递表 + +## 上下文传递表 +| 操作 | 提取 | 用于 | +|------|------|------| +| `contact user get-self` | `userId` | summary 的 --user | +| `rules` | `groupId` | schedule import 的 --group-id | +| `schedule import` | `classId` | schedule import 的 schedules 中的 classId | +| `contact user search` | `userId` | schedule import/get 的 userId | + +| `contact user get-self` / `contact user search` | `userId` | summary 的 --user, vacation records 的 --user;selfsetting get/save 的 --user(必填) | +| 当前登录上下文 | `corpId`, `opUserId` | selfsetting get/save 自动补齐 MCP 入参, CLI 不需要传 `--corp-id` / `--op-user` | +| `vacation types` | `leaveCode` | vacation balance 的 --leave-code, vacation records 的 --leave-code | diff --git a/skills/multi/dingtalk-attendance/references/attendance-index.md b/skills/multi/dingtalk-attendance/references/attendance-index.md new file mode 100644 index 00000000..01c5b06c --- /dev/null +++ b/skills/multi/dingtalk-attendance/references/attendance-index.md @@ -0,0 +1,11 @@ +# 考勤能力索引 + +| 主题 | 详见 | +|---|---| +| 日期范围计算、签到与打卡分流 | [`attendance-routing.md`](./attendance-routing.md) | +| 打卡/记录/排班/补卡/加班等核心命令 | [`attendance-commands.md`](./attendance-commands.md) | +| 排班规则/班次/排班表 | [`attendance-schedule.md`](./attendance-schedule.md) | +| 报表/统计/查询 | [`attendance-report.md`](./attendance-report.md) | +| 假期/调休/余额 | [`attendance-vacation.md`](./attendance-vacation.md) | +| 意图、工作流与上下文 | [`attendance-intent.md`](./attendance-intent.md) · [`attendance-workflow.md`](./attendance-workflow.md) · [`attendance-context.md`](./attendance-context.md) | +| 注意事项、脚本与严格约束 | [`attendance-notes.md`](./attendance-notes.md) · [`attendance-scripts.md`](./attendance-scripts.md) · [`attendance-constraints.md`](./attendance-constraints.md) | diff --git a/skills/multi/dingtalk-attendance/references/attendance-intent.md b/skills/multi/dingtalk-attendance/references/attendance-intent.md new file mode 100644 index 00000000..3084d9cb --- /dev/null +++ b/skills/multi/dingtalk-attendance/references/attendance-intent.md @@ -0,0 +1,62 @@ +# 意图判断 + +## 意图判断 + +用户说"签到记录/签到数据/签到明细/外勤签到/导出签到" → `checkin records`(查询签到记录)。如果用户意图是**导出签到报表/签到Excel**,则走下方的签到报表导出工作流。 + - **优先级**:只要用户句中含"签到"二字,就走本条,不要走 `check record`。"签到" ≠ "打卡",签到是外勤场景的独立功能 +用户说"打卡记录/出勤/考勤" → `check record` +用户说"指定用户打卡结果/考勤结果/迟到早退/缺卡异常" → `check result` +用户说"指定用户打卡流水/打卡详情/打卡时间地点/打卡记录详情" → `check record` +用户说"审批单/请假记录/加班记录/出差记录/补卡记录" → `approve list` +用户说"查询排班记录/获取排班详情/查看排班/排班表/导出排班/导出排班表/排班导出/XX考勤组的排班" → **必须先 `read_file` 读取 [attendance-schedule.md](./attendance-schedule.md) 后按其中的「排班查询导出工作流」执行**。 + - **优先级**:只要用户句中含"排班"二字且意图是查看/导出,就走本条,不要走 `attendance-report.md`。"导出排班表" ≠ "导出考勤报表" + - **严禁**绕过 `attendance-schedule.md` 直接调用 `dws attendance schedule get` 命令 + - 脚本自动处理:分批查询(超 20 人自动分批)、userId→姓名转换、classId→班次名称转换、排班表格式 Excel 输出 +用户说"排班/导入排班/安排排班/设置排班/安排班次/调班/换班/排休" → **必须先 `read_file` 读取 [attendance-schedule.md](./attendance-schedule.md) 后按其中的「排班导入工作流」执行**。 + - **严禁**绕过 `attendance-schedule.md` 直接调用 `dws attendance schedule import` 命令 + - **严禁**仅凭命令 `--help` 或本文件中的命令参考自行组装排班命令 + - 该文档定义了:考勤组类型校验(必须为 TURN)、班次校验(必须属于该考勤组)、排班回显确认、错误处理等约束,缺一不可 + - 违反约束的后果:排错班次、排错人员、排班数据覆盖无法回退 +用户说"班次定义/班次列表/有哪些班次/我负责的班次" → `class search`(返回结果已包含全量属性,无需再调 get) +用户说"班次详情/某个班次的具体信息" → `class search --name "..."`(search 直出,直接返回详情)。`class get` 仅在需要按已知 classId 精确查询时使用 +用户说"更新班次/修改班次/班次改名/修改上下班时间" → `class update` +用户说"补卡规则/补卡设置" → `adjustment search`(返回结果已包含全量属性,无需再调 get) +用户说"补卡规则详情/某条补卡规则的具体信息" → `adjustment search --name "..."`(search 直出)。`adjustment get` 仅在需要按已知 adjustmentId 精确查询时使用 +用户说"加班规则/加班设置/加班计算" → `overtime search`(返回结果已包含全量属性,无需再调 get) +用户说"加班规则详情/某条加班规则的具体信息" → `overtime search --name "..."`(search 直出)。如需查已删除/被覆盖的历史记录 → `overtime get` +用户说"考勤组列表/有哪些考勤组" → `group search` +用户说"考勤组详情/全量考勤组信息" → `group get`,若返回结果中含成员 userId 列表,则对每个 userId 调用 `dws contact user get --user-ids `(或等价通讯录查询),在最终输出中展示员工姓名而非裸 userId +用户说"考勤组成员/打卡地址/打卡wifi/打卡蓝牙" → `group filtered-get`(按需查询,节省成本),若返回结果中含成员 userId 列表,则对每个 userId 调用 `dws contact user get --user-ids `(或等价通讯录查询),在最终输出中展示员工姓名而非裸 userId +用户说"更新考勤组成员/添加考勤人员/删除考勤人员/添加考勤部门/删除考勤部门/加入考勤组/移出考勤组/设置无需考勤/取消无需考勤" → `group update-members` +用户说"修改考勤组/更新考勤组配置/考勤组改名/改变考勤组绑定的班次/修改打卡范围/设置考勤组负责人" → `group update` +用户说"创建考勤组/新建考勤组/添加考勤组" → `group create` +用户说"查询某人的考勤汇总/考勤统计/周统计/月统计" → `summary` +用户说"考勤组/考勤规则/打卡规则" → `rules` +用户说"查询个人规则设置/查看打卡提醒/查看极速打卡/查看缺卡提醒/查看打卡结果通知/查看个人考勤统计通知/查看团队考勤统计通知" → `selfsetting get` +用户说"更新个人规则设置/保存打卡提醒/修改极速打卡/关闭缺卡提醒/开启打卡结果通知/设置个人考勤统计通知/设置团队考勤统计通知" → `selfsetting save` +用户说"考勤字段/考勤列" → `report columns` +用户说"考勤数据/查询考勤报表数据" → `report query-data`(单次查询场景,非导出) + **导出签到记录/签到报表/签到数据导出/签到明细/外勤签到导出** → **必须先 `read_file` 读取 [attendance-report.md](./attendance-report.md) 后按其中的工作流执行(报表类型为"签到报表")**。 + - 触发关键词:签到记录导出、签到报表、签到数据、签到明细、外勤签到 + - 数据来源为 `attendance checkin records`(签到接口),非 `report query-data` + - **严禁**绕过 `attendance-report.md` 直接调用 `python scripts/attendance_report_checkin.py` + **导出考勤/导出报表/生成考勤报表/出勤汇总导出/考勤明细导出/迟到早退统计导出/全员考勤数据导出/月度考勤报表/考勤表格/考勤 Excel** → **必须先 `read_file` 读取 [attendance-report.md](./attendance-report.md) 后按其中的工作流执行**。 + - **排除**:如果用户说的是"导出**排班**表"/"导出**排班**"/"**排班**导出",这属于**排班查询导出**,应路由到 [attendance-schedule.md](./attendance-schedule.md),而非本条。判断标准:句中含"排班"二字 → 走排班;不含"排班"或明确说"考勤报表/考勤数据/出勤统计" → 走报表。 + - **严禁**绕过 `attendance-report.md` 直接调用 `python scripts/attendance_report_*.py` 任何脚本 + - **严禁**仅凭脚本 `--help` 或本文件"自动化脚本"表格里的脚本路径就推断参数自行组装命令 + - 该文档定义了:报表类型默认值、列选择策略(`--column-keywords`)、阶段 1 人员获取流程、错误处理、输出摘要规范,缺一不可 + - 违反约束的后果:报表数据不全、列错位、人员遗漏、用户得到错误结果 + **导出补卡记录/导出请假记录/导出出差记录/导出外出记录/补卡导出/请假导出/出差导出/外出导出/考勤审批记录导出/请假明细/补卡明细/出差明细/外出明细** → **必须先 `read_file` 读取 [attendance-report.md](./attendance-report.md) 后按其中的工作流执行(报表类型为"考勤记录")**。 + - 触发关键词:补卡记录、请假记录、出差记录、外出记录、审批记录导出、请假明细导出、补卡明细导出 + - 判断标准:句中含"补卡/请假/出差/外出"且含"记录/导出/明细/报表" → 走 attendance-report.md 的"考勤记录"类型 + - **严禁**绕过 `attendance-report.md` 直接调用 `python scripts/attendance_report_record.py` + - 该文档定义了:考勤记录子类型(leave/trip/out/patch)选择策略、阶段 1 人员获取流程、阶段 3 脚本调用规范 +用户说"假期数据/年假/病假/请假记录" → `report query-leave` +用户说"假期/我的假期/假期规则" → `vacation types` +用户说"病假余额/年假余额/事假剩余假期"等查询指定假期规则的余额 → `vacation balance` +用户说"导出假期余额/假期余额列表/所有假期规则余额/假期余额 Excel/年假病假调休余额导出"等全部假期规则余额的查询 → **必须先 `read_file` 读取 [attendance-vacation.md](./attendance-vacation.md) 后按其中工作流执行** +用户说"假期变更/假期记录/请假扣减" → `vacation records` +用户说"更新假期规则/修改假期类型/编辑假期规则" → `vacation update-type --leave-code ` +用户说"设置假期余额/调整假期额度/更新假期余额" → 先调用 `vacation balance` 获取当前余额,计算修改后的值,再调用 `vacation save-balance` +用户说"增加假期余额/发放年假/给员工加年假" → 先调用 `vacation balance` 获取当前余额,加上要增加的天数,再调用 `vacation save-balance` 设置新总额度 +用户说"签到/签到记录" → `checkin records` diff --git a/skills/multi/dingtalk-attendance/references/attendance-notes.md b/skills/multi/dingtalk-attendance/references/attendance-notes.md new file mode 100644 index 00000000..bee4d7c6 --- /dev/null +++ b/skills/multi/dingtalk-attendance/references/attendance-notes.md @@ -0,0 +1,107 @@ +# 注意事项 + +## 注意事项 +**Agent 使用引导**: +- 执行 vacation 子命令前,**必须先调用** `dws attendance vacation --help` 查看完整子命令列表和参数说明 +- 新增命令可能不在 Agent 缓存中,直接猜测命令会失败 +- 正确流程:查看帮助 → 选择命令 → 查看命令详细参数(如 `dws attendance vacation update-type --help`)→ 执行 + +- `record get` 的 `--date` 格式: YYYY-MM-DD(如 `2026-03-08`),CLI 自动转换为毫秒时间戳 +- `shift list` 查询班次信息,`--start/--end` 使用 YYYY-MM-DD 格式,间隔不超过 7 天 +- `schedule import` 导入排班记录 — 必须通过 [attendance-schedule.md](./attendance-schedule.md) 排班导入工作流执行 +- `schedule get` 查询排班记录 — 必须通过 [attendance-schedule.md](./attendance-schedule.md) 排班查询导出工作流执行(脚本自动分批、姓名转换、排班表格式导出) +- `schedule import` 是写操作,AI 调用时必须先展示导入摘要并引导用户二次确认;用户明确确认后才允许执行。用户明确要求跳过确认或命令包含全局 `--yes` 时,可跳过二次确认 +- `class search` 所有参数均为可选,不填时返回全部可管理班次(默认第 1 页,每页 20 条) +- **概念区分**:班次是员工当天打卡安排;排班是为排班制考勤组导入的排班记录;班次定义是考勤管理员创建的工作时间规则 +- `class get` 的 `--class-id` 必填,班次 ID 可从 `class search` 结果中提取 +- `class search` 返回结果已包含全量属性,无需再调用 `class get`;`class get` 仅在需要按已知 classId 精确查询时使用 +- `class update` 的 `--class-id` 必填,其余均可选,仅需对要修改的字段赋值,未传字段会自动从已有配置补充;由于保存班次耗时较久,建议加 `--timeout 10` +- `adjustment search` 返回结果已包含全量属性,无需再调用 `adjustment get`;`adjustment get` 仅在需要按已知 adjustmentId 精确查询时使用 +- `overtime search` 返回结果已包含全量属性,无需再调用 `overtime get`;`overtime get` 仅在需要按已知 overtimeId 查询时使用(包括已删除/被覆盖的历史记录) +- `adjustment search` / `overtime search` 分页字段为 `--page` 和 `--limit`,不传时自动使用默认值 1 / 20 +- `group search` 的分页字段为 `--page` 和 `--limit`,不传时自动使用默认值 1 / 20 +- `group get` 的 `--group-id` 必填,返回考勤组全量字段;如仅需成员/地址/蓝牙/Wifi,优先使用 `group filtered-get` 节省成本。**返回结果中如含成员 userId 列表,必须调用 `dws contact user get --ids ,,...`(支持逗号分隔传多个 ID),将 userId 转换为员工姓名后再输出;不得直接输出裸 userId。** +- `group update-members` 的 --group-id 必填,其余参数均可选,但至少需传一个变更项;各参数每次最多 20 个 ID;`--add-extra-users` 和 `--remove-extra-users` 操作的是"无需考勤"豁免名单,不影响考勤组主成员列表 +- `group update` 的 --group-id 必填,其余均可选,至少需指定一个修改项;仅需对要修改的字段赋値,未传字段会从已有配置自动补充;修改打卡地址/wifi/蓝牙等复杂子对象时用 `--group-vo` 传入完整 JSON;`--group-vo` 与单字段 flag 同时传入时单字段 flag 优先级更高 +- `group create` 的 `--name` 和 `--type` 必填,`--type` 必须为 FIXED/TURN/NONE 之一;type=FIXED 时 `--group-vo` 必须包含 `workDayClassList`(非空)和 `defaultClassId`(非 null);由于保存考勤组耗时较久,建议加 `--timeout 10` +- `group filtered-get` 的 `--group-id` 必填,`--member/--position/--wifi/--bles` 均可选,默认 false。**返回结果中如含成员 userId 列表,必须调用 `dws contact user get --ids ,,...`(支持逗号分隔传多个 ID),将 userId 转换为员工姓名后再输出;不得直接输出裸 userId。** +- `summary` 必须同时传 `--user`、`--date`、`--stats-type`(`week` 周统计 / `month` 月统计);`--date` 支持 `YYYY-MM-DD` 或 `YYYY-MM-DD HH:mm:ss` +- `rules` 的 `--date` 支持 YYYY-MM-DD 或 yyyy-MM-dd HH:mm:ss 两种格式 +- `selfsetting get/save` 的 `--setting-scene` 必须是 `checkRemind`、`fastCheck`、`checkResultNotify`、`lackRemind`、`personalAttendStatNotify`、`bossAttendStatNotify` 之一 +- `selfsetting get/save` 的 MCP 入参 `userId` 为必填;CLI 的 `--user` 也必填,必须显式传入目标用户 ID +- `selfsetting save` 必须传入与 `--setting-scene` 对应的至少一个设置字段;不同场景的字段不能混用 +- `selfsetting save` 是敏感写操作,必须先执行 `selfsetting get` 查询当前值,并向用户展示目标用户、设置场景、修改字段、“当前值 → 新值”和最终命令参数摘要,等待用户明确确认;确认后才允许追加全局 `--yes` 执行保存。禁止未经确认直接执行或自动添加 `--yes` +- `selfsetting get/save` 不需要传 `--corp-id` / `--op-user`,`corpId` 和 `opUserId` 由当前登录上下文自动补齐 +- `report columns` 无需额外参数,corpId 和 operatorId 由系统自动传入 +- `report query-data` 和 `report query-leave` 的 `--start/--end` 格式: yyyy-MM-dd HH:mm:ss,间隔不超过 32 天,最多 20 人 +- report 系列接口仅对管理员开放 +- 用户 ID 需从 `contact user get-self` 或 `aisearch person` 获取 +- 考勤组 ID 需从 `rules` 命令返回结果中获取 +- `vacation types` 无需任何参数,认证信息自动注入 +- `vacation balance` 的 `--users` 为目标员工 ID 列表,逗号分隔;`--leave-code` 选填,可通过 `vacation types` 获取 +- `vacation records` 的 `--start/--end` 使用 YYYY-MM-DD 格式,CLI 自动转换为毫秒时间戳;`--leave-code` 选填 +- `vacation balance` 和 `vacation records` 的认证参数(corpId、opUserId)由系统自动注入,无需手动传入 +- `vacation update-type` 的 `--leave-code` 必填;其他字段均为可选,但至少需传一个更新字段 +- `vacation update-type` 的 `--visibility-rules` 为 JSON 数组字符串,**HSF 反序列化无法区分「未传」与「空数组 `[]`」**,所以约定了哨兵语义: + - 不传 → 不修改可见范围 + - `[{"type":"dept","visible":["-1"]}]` → **哨兵**,改为全公司可见(服务端落库为空) + - `[{"type":"staff|dept|label|employee_type","visible":["id1",...]}, ...]` → 改为指定范围 + - 空数组 `[]`、`[{}]`、`visible` 为空等无效写法 → **CLI 报错**(服务端会静默忽略,故在 CLI 提前拦截) + - **「清空可见范围」必须显式传哨兵值 `["-1"]`,不能用 `[]`** +- `vacation save-balance` 是 **SET 接口**而非 ADD 接口:传入值会直接替换当前余额,而非累加 +- `vacation save-balance` 的 `--num` 输入为实际天数(如 8 或 7.5),内部会乘以 100 传给 MCP(如 800 或 750) +- `vacation save-balance` 执行前需先调用 `vacation balance` 查询当前余额,再计算新值,避免误操作 +- `vacation save-balance` 的 `--start/--end` 使用 YYYY-MM-DD 格式,CLI 自动转换为毫秒时间戳 +- `vacation update-type` 和 `vacation save-balance` 执行前会展示待写入数据,需用户输入 yes/y 确认后提交 +- 假期编码为 UUID 格式字符串,可通过 `vacation types` 命令查询获取 + +### 改签打卡记录 + +**命令**: `dws attendance boss-check` + +**功能**: 改签打卡记录,管理员可修改员工的打卡时间、打卡结果等信息。 + +**强制执行流程**: 此命令为写操作,Agent 调用时必须遵守以下流程: +1. 先向用户展示待执行操作的完整参数摘要 +2. 展示变更摘要并等待用户明确确认 +3. 用户确认后,再传 `--user-say-yes=true` 执行命令 +4. **禁止**未经用户确认直接执行或自动添加 `--user-say-yes=true` + +**参数**: +| 参数 | 必填 | 说明 | 来源 | +|------|------|------|------| +| --plan-id | y* | 排班ID(与 --result-id 二选一) | `dws attendance schedule get` 返回的 `id` 字段 | +| --result-id | y* | 打卡结果ID(与 --plan-id 二选一,优先使用) | **暂不支持**(record get 未返回此字段) | +| --time | n | 新打卡时间,格式 yyyy-MM-dd HH:mm | - | +| --result | n | 打卡结果枚举值 | - | +| --absent-min | n | 缺勤时长(分钟) | - | +| --remark | n | 备注,最长500字符 | - | +| --user-say-yes | n | 用户已确认,跳过交互式确认提示 | - | + +**获取 planId 步骤**: +1. 查询排班记录:`dws attendance schedule get --userIdList USER_ID --workDateBegin DATE --workDateEnd DATE` +2. 从返回结果中找到对应打卡类型(OnDuty=上班,OffDuty=下班)的记录 +3. 使用该记录的 `id` 字段作为 `--plan-id` 参数 +4. 示例返回:`{"id": 948964045503, "checkType": "OffDuty", ...}` → `--plan-id 948964045503` + +**打卡结果枚举值**: +- Normal: 正常 +- TimesResultA: 迟到 +- TimesResultB: 早退 +- TimesResultC: 缺卡 +- TimesResultD: 迟到+早退 +- TimesResultE: 缺卡+早退 +- TimesResultF: 迟到+缺卡 + +**示例**: +```bash +# 步骤1:获取排班记录的 planId +dws attendance schedule get --userIdList 03642229451220076 --workDateBegin 2026-05-13 --workDateEnd 2026-05-13 -f json + +# 步骤2:使用返回的 id 作为 --plan-id 改签 +# 假设返回 id: 948964045503 (OffDuty 下班打卡) +dws attendance boss-check --plan-id 948964045503 --result Normal --user-say-yes + +# 同时修改打卡时间 +dws attendance boss-check --plan-id 948964045503 --time "2026-05-13 18:00" --result Normal --yes +``` diff --git a/skills/multi/dingtalk-attendance/references/attendance-report.md b/skills/multi/dingtalk-attendance/references/attendance-report.md index 43d54547..2a215191 100644 --- a/skills/multi/dingtalk-attendance/references/attendance-report.md +++ b/skills/multi/dingtalk-attendance/references/attendance-report.md @@ -11,7 +11,7 @@ 1. 未按"阶段 1"做人员解析 → `--users` 传入部门 ID 而非员工 userId,脚本虽内置回退但会浪费一次失败的接口调用 2. 未按"阶段 0"判断报表类型 → 用户说"汇总"被理解成"明细",导致输出粒度错误 3. 未按"列选择"判断是否传 `--column-keywords` → 用户要"迟到情况报表"被输出成全字段默认报表 -4. 未按"错误处理"规则处理无权限错误(errorCode 6001「无权限操作」/ `AUTH_ERROR`)/ `HSF_ILLEGALPARAMS` → 把环境错误当成业务错误反馈给用户 +4. 未按"错误处理"规则处理 403 / `HSF_ILLEGALPARAMS` → 把环境错误当成业务错误反馈给用户 5. 未按"阶段 4"返回结果 → 把 Excel 内容贴在对话里,或者裸 userId 直接输出 **执行前自检(必须能在心中回答)**: @@ -22,7 +22,7 @@ 如果上述任何一项答不出,**回到本文档对应章节重新阅读**,禁止凭记忆/想象组装命令。 -**前提**:当前用户必须是钉钉管理员,否则 report 系列接口返回业务权限错误(`server_error_code` 为 `AUTH_ERROR`、errorCode `6001`「无权限操作」),**不是 HTTP 403**。提示需要管理员权限,不要重试。 +**前提**:当前用户必须是钉钉管理员,否则 report 系列接口返回 403 权限错误。 ## 核心原则 @@ -46,7 +46,7 @@ Agent 解析用户意图(报表类型、人员范围、时间范围、关注 - 所有 `dws` 命令必须携带 `--format json` - 时间参数 `--start` / `--end` 格式必须为 `yyyy-MM-dd HH:mm:ss` - 字段 ID 与字段名的映射必须从 `report columns` 实时建立,禁止硬编码 -- 任何接口失败(含无权限错误 6001/`AUTH_ERROR`)必须向用户清晰报错,禁止静默吞掉 +- 任何接口失败(含 403)必须向用户清晰报错,禁止静默吞掉 ## 涉及工具 @@ -102,10 +102,10 @@ Agent 解析用户意图(报表类型、人员范围、时间范围、关注 | 用户说 | 应路由到 | |--------|---------| | "今天打卡了吗" | `dws attendance record get`(单次查询,非报表) | -| "帮我排班" | [attendance-schedule.md](./attendance-schedule.md) 排班工作流(排班,非报表) | +| "帮我排班" | `dws attendance schedule import`(排班写入,非报表;执行前确认) | | "我的假期还剩多少" | `dws attendance vacation balance`(单次查询) | | "帮我请假" | `dws oa`(审批流程,非报表) | -| "我这个月的考勤怎么样" | `dws attendance summary`(个人统计,非报表) | +| "我这个月的考勤怎么样" | `dws attendance summary --user --date --stats-type month`(个人统计,非报表) | ## 工作流 @@ -246,7 +246,7 @@ python scripts/attendance_report_checkin.py \ - 脚本依赖 `openpyxl`,若未安装需先 `pip install openpyxl` - 脚本摘要输出到 stdout,进度日志输出到 stderr -- 首次调试可加 `--inspect` 参数查看首条记录原始结构(`attendance_report_detail.py` / `attendance_report_monthly.py` / `attendance_report_daily.py` / `attendance_report_checkin.py` 支持;**`attendance_report_record.py` 无 `--inspect` 参数**) +- 首次调试可加 `--inspect` 参数查看首条记录原始结构 - 脚本执行失败(exit ≠ 0)时,stderr 中有具体错误信息 ### 阶段 4: 返回结果给用户 @@ -488,11 +488,11 @@ python scripts/attendance_report_checkin.py \ | 错误 | 原因 | 处理方式 | |------|------|---------| -| 权限错误(errorCode 6001「无权限操作」/ `AUTH_ERROR`,非 HTTP 403) | 当前账号非管理员 | 提示需要管理员权限,不要重试 | +| 权限错误(403) | 当前账号非管理员 | 提示需要管理员权限,不要重试 | | userId 无效 | 用户 ID 错误或已离职 | 脚本跳过并在摘要中标注 | | 时间区间超长 | 接口可能性能不佳 | 提示"超过 1 年的数据建议分阶段导出" | | openpyxl 未安装 | 环境缺包 | 输出 `pip install openpyxl` 安装提示 | -| 脚本执行失败 | 接口异常/配置问题 | 将 stderr 错误信息转告用户,可加 `--inspect` 重试(`attendance_report_record.py` 不支持 `--inspect`) | +| 脚本执行失败 | 接口异常/配置问题 | 将 stderr 错误信息转告用户,可加 `--inspect` 重试 | ## 使用示例 @@ -611,9 +611,9 @@ python scripts/attendance_report_checkin.py \ | 脚本 | 报表类型 | 数据来源 | CLI 参数 | |------|---------|---------|---------| -| [attendance_report_detail.py](../../scripts/attendance_report_detail.py) | 明细 | `check result` + `check record` | `--users --start --end [--out]` | -| [attendance_report_monthly.py](../../scripts/attendance_report_monthly.py) | 月度汇总(默认) | `report columns` + `report query-data` | `--users --start --end [--column-keywords] [--out]` | -| [attendance_report_daily.py](../../scripts/attendance_report_daily.py) | 每日统计 | `report columns` + `report query-data` | `--users --start --end [--column-keywords] [--out]` | -| [attendance_report_record.py](../../scripts/attendance_report_record.py) | 考勤记录 | `attendance approve list` + `oa approval detail` | `--type --users --start --end [--out]` | -| [attendance_report_checkin.py](../../scripts/attendance_report_checkin.py) | 签到报表 | `attendance checkin records` | `--users --start --end [--out]` | -| [attendance_report_common.py](../../scripts/attendance_report_common.py) | 公共模块(不可单独执行) | — | — | +| [attendance_report_detail.py](../scripts/attendance_report_detail.py) | 明细 | `check result` + `check record` | `--users --start --end [--out]` | +| [attendance_report_monthly.py](../scripts/attendance_report_monthly.py) | 月度汇总(默认) | `report columns` + `report query-data` | `--users --start --end [--column-keywords] [--out]` | +| [attendance_report_daily.py](../scripts/attendance_report_daily.py) | 每日统计 | `report columns` + `report query-data` | `--users --start --end [--column-keywords] [--out]` | +| [attendance_report_record.py](../scripts/attendance_report_record.py) | 考勤记录 | `attendance approve list` + `oa approval detail` | `--type --users --start --end [--out]` | +| [attendance_report_checkin.py](../scripts/attendance_report_checkin.py) | 签到报表 | `attendance checkin records` | `--users --start --end [--out]` | +| [attendance_report_common.py](../scripts/attendance_report_common.py) | 公共模块(不可单独执行) | — | — | diff --git a/skills/multi/dingtalk-attendance/references/attendance-routing.md b/skills/multi/dingtalk-attendance/references/attendance-routing.md new file mode 100644 index 00000000..1a27c629 --- /dev/null +++ b/skills/multi/dingtalk-attendance/references/attendance-routing.md @@ -0,0 +1,55 @@ +# 日期范围与签到优先路由 + +> **【必读】日期范围严格计算规则 — 所有含 --start/--end 或 --from/--to 的命令均适用** +> +> **禁止随意猜测日期范围,必须按以下规则精确计算后再组装命令参数:** +> +> | 用户表达 | --start(含)| --end(含)| 说明 | +> |---------|------------|------------|------| +> | 本周 / 这周 / 当周 | 本周**周一** | 本周**周日** | 周一为一周的第一天,周日为最后一天 | +> | 上周 / 上一周 | 上周**周一** | 上周**周日** | 同上,往前推一周 | +> | 本月 / 这个月 / 当月 | 本月 **1 日** | 本月**最后一天**(28/29/30/31)| 必须计算当月实际天数 | +> | 上月 / 上个月 | 上月 **1 日** | 上月**最后一天** | 同上,往前推一个月 | +> | 今天 / 今日 | **今天** | **今天** | start == end | +> | 昨天 | **昨天** | **昨天** | start == end | +> +> **必须遵守:** +> 1. **本周一定是周一到周日**,严禁将"本周"解释为周一到周五或其他任意范围。 +> 2. **本月一定是 1 日到该月实际最后一天**,严禁将"本月"截断为 1 日到 28 日或其他固定天数。 +> 3. 计算日期时必须参考当前系统时间,不得使用硬编码或模糊估算。 +> 4. 如果用户指定了具体日期范围(如"4月1日到4月15日"),直接使用用户给定的范围,不套用上述规则。 +> 5. 日期格式统一按各命令要求:`shift list` / `check result` 等使用 `YYYY-MM-DD`;`report query-data` / `report query-leave` 等使用 `yyyy-MM-dd HH:mm:ss`(start 取当天 `00:00:00`,end 取当天 `23:59:59`)。 + +## 签到(checkin)— 优先路由 + +> **只要用户句中含"签到"二字(签到记录/签到数据/签到明细/外勤签到/导出签到/签到报表),就走本节,不要走 `check record`(打卡流水)。"签到" ≠ "打卡",签到是外勤场景的独立功能。** + +### 导出 vs 查询 判断规则(必须先判断) + +| 用户说 | 路由 | +|--------|------| +| 含"导出"/"报表"/"Excel"/"表格"/"生成"中**任何一个** + 含"签到" | → **导出签到报表**(走脚本) | +| 不含上述导出关键词,仅"查签到"/"签到记录"/"看看签到" | → **查询签到记录**(走命令) | + +> **示例**:"帮我导出最近一个月的签到记录" → 含"导出" → 走报表脚本 +> "查一下张三的签到记录" → 不含导出关键词 → 走查询命令 + +- **导出签到报表** → **必须先 `read_file` 读取 [attendance-report.md](./attendance-report.md) 后按其中的工作流执行(报表类型为"签到报表")**,对应脚本 `attendance_report_checkin.py` + - **严禁**绕过 `attendance-report.md` 直接调用脚本 + - **严禁**自己手动分段调用 `dws attendance checkin records` 来拼数据,必须用脚本 +- **查询签到记录**(无需导出,仅查看) → `dws attendance checkin records`(数据源为 MCP 工具 `get_checkin_record`) + +签到记录返回字段(`result.list` 数组,按 sortedProps 顺序): +- `corpId`(string):企业 ID +- `name`(string):用户名称 +- `userId`(string):用户 ID +- `timestamp`(number):签到时间 +- `place`(string):签到地点 +- `detailPlace`(string):签到详细地点 +- `longitude`(number):签到地点经度 +- `latitude`(number):签到地点纬度 +- `remark`(string):备注信息 +- `imageList`(string[]):图片列表 +- `customers`(string):拜访客户 +- `checkinType`(string):签到类型 +- `mobileId`(string):设备 ID diff --git a/skills/multi/dingtalk-attendance/references/attendance-schedule.md b/skills/multi/dingtalk-attendance/references/attendance-schedule.md index 05bcc8a3..a27f2025 100644 --- a/skills/multi/dingtalk-attendance/references/attendance-schedule.md +++ b/skills/multi/dingtalk-attendance/references/attendance-schedule.md @@ -52,7 +52,7 @@ Agent 解析用户意图(考勤组、员工、日期范围、班次安排) - 禁止在未确认考勤组类型为排班制(TURN)的情况下执行排班 - 禁止在班次未经校验的情况下执行排班 - 禁止跳过用户确认直接执行排班 -- **禁止在未向用户展示完整排班明细表格(含每天的班次名称)的情况下弹出确认卡片**。用户必须先看到"谁、哪天、上什么班"才能做出确认决策 +- **禁止在未向用户展示完整排班明细表格(含每天的班次名称)的情况下请求确认**。用户必须先看到"谁、哪天、上什么班"才能做出确认决策 - 禁止编造任何字段值或用户姓名 - 禁止直接输出裸 userId,脚本已内置 userId → 姓名转换 - **禁止直接输出裸 classId 数字**,必须展示班次名称(如"早班"),用户不理解 classId 是什么 @@ -118,7 +118,7 @@ dws attendance rules --date <今天日期> --format json 如果是给指定员工排班,先查该员工的 userId,再查其考勤组。 -**步骤 0b — 查询考勤组详情和关联班次(必须在 ask_question 之前完成)**: +**步骤 0b — 查询考勤组详情和关联班次(必须在收集班次选择前完成)**: ```bash # 获取考勤组详情(含绑定的班次列表) @@ -132,7 +132,7 @@ dws attendance group get --group-id --format json - 考勤组类型:`groupVO.type`(必须为 TURN) - 关联班次列表:`groupVO.shiftVOList[].shiftSetting.{shiftId, shiftName}` -**步骤 0c — 收集缺失参数(ask_question 卡片交互)**: +**步骤 0c — 收集缺失参数**: 拿到考勤组和关联班次后,再向用户收集缺失的参数。排班所需的四个参数: - **考勤组**:已在 0a 自动获取,无需询问 @@ -140,59 +140,12 @@ dws attendance group get --group-id --format json - **员工范围**(必填):指定员工姓名 / 部门 / 考勤组全员 / "给我排班" - **日期范围**(必填):具体日期 / 日期范围(如"下周"、"5月19日到5月23日") -**只收集真正缺失的参数**,用户已经提供的不要重复询问。将缺失参数**合并到一次 `ask_question` 调用中**。 - -示例:用户说"帮我排班",Agent 应先自动查询考勤组和关联班次(步骤 0a + 0b),然后只询问日期范围和班次: - - ``` - // ===== 步骤 0a + 0b 已完成,此时你已经拿到了以下真实数据 ===== - // groupId = 实际的考勤组ID - // groupName = 实际的考勤组名称 - // shiftVOList = 考勤组关联的班次列表(来自 dws attendance group get 的返回) - - // 从 shiftVOList 构建班次选项(伪代码): - shiftOptions = [] - for each shift in groupVO.shiftVOList: - shiftOptions.append({ id: String(shift.shiftSetting.shiftId), label: shift.shiftSetting.shiftName }) - shiftOptions.append({ id: "rest", label: "排休" }) - - // 如果考勤组只关联了一个班次,直接使用,不需要询问用户 - if shiftVOList.length == 1: - selectedShift = shiftVOList[0] // 自动选定,跳过班次选择 - - // 构建日期选项(必须填入实际计算的日期): - todayStr = 当天日期(YYYY-MM-DD) - thisWeekEnd = 本周日日期 - nextWeekStart = 下周一日期 - nextWeekEnd = 下周日日期 - - ask_question({ - title: "排班参数确认", - questions: [ - { - id: "date_range", - prompt: "请选择排班日期范围", - options: [ - { id: "this_week", label: "本周剩余时间(" + todayStr + "-" + thisWeekEnd + ")" }, - { id: "next_week", label: "下周(" + nextWeekStart + "-" + nextWeekEnd + ")" }, - { id: "custom", label: "自定义日期范围" } - ] - }, - { - id: "shift", - prompt: "请选择班次(以下为您考勤组「" + groupName + "」关联的班次)", - options: shiftOptions // 直接使用上面从 shiftVOList 动态构建的选项,严禁替换为任何硬编码值 - } - ] - }) - ``` - - **⚠️ 班次选项严禁硬编码**:上述伪代码中的 `shiftOptions` 必须在运行时从 `shiftVOList` 动态生成。禁止在 `ask_question` 中写入任何固定的班次名称(如"早班"、"晚班"、"正常班"、"全部排XX班"等)。如果你发现自己在 options 里手写班次名,说明你做错了。 +**只收集真正缺失的参数**,用户已经提供的不要重复询问;多个缺失参数可集中询问。日期选项必须展示实际计算后的日期范围,班次选项必须在运行时从 `shiftVOList` 动态生成,并以真实 `shiftId` 作为值、真实 `shiftName` 作为展示名称;可额外提供“排休”。只有一个关联班次时直接使用,无需再次选择。 **动态选项原则(严格执行)**: - 班次选项**必须且只能**来自考勤组的 `shiftVOList`,**严禁编造任何班次名称**(如"正常班"、"早班"、"晚班"、"全部排XX班"等都是编造) - 每个班次选项的 `id` 必须是 `shiftVOList` 中的真实 `shiftId`,`label` 必须是真实的 `shiftName` - - 如果 `shiftVOList` 为空,降级从 `groupVO.classIds` + `class search` 按 ID 精确查询(不是全局搜索) + - 如果 `shiftVOList` 为空,降级从 `groupVO.classIds` + `class get` 按 ID 精确查询(不是全局搜索) - **只收集缺失的参数**:用户已经提供的参数不要重复询问 - **如果考勤组只有一个班次,直接使用该班次,不需要询问用户选择** - **禁止用 `class search` 全局搜索来给用户展示班次选项**——全局班次列表包含不属于该考勤组的班次,用户选了也会被校验拒绝 @@ -251,21 +204,21 @@ dws attendance group filtered-get --group-id --member --format json **禁止调用 `dws attendance class search` 全局搜索班次**——全局搜索会返回不属于该考勤组的班次,即使用户指定了班次名称,也必须在 `shiftVOList` 中匹配,而不是全局搜索。 **校验规则**: -1. 用户在 `ask_question` 卡片中选择的班次,其 `shiftId` 必须存在于 `shiftVOList` 中(卡片选项本身就是从 `shiftVOList` 构建的,所以天然满足) +1. 用户选择的班次 `shiftId` 必须存在于 `shiftVOList` 中;所有候选都必须由 `shiftVOList` 动态生成 2. 如果用户通过自然语言指定了班次名称(如"排早班"),必须在 `shiftVOList` 中**按名称模糊匹配**,找到对应的 `shiftId` 3. 如果用户指定的班次不在 `shiftVOList` 中,**必须拒绝**,并列出该考勤组关联的全部班次让用户重新选择 4. 仅当 `shiftVOList` 为空时,才降级从 `groupVO.classIds` + `dws attendance class get` 按 ID 精确查询(仍然不是全局搜索) **提取信息**:班次 ID(`classId` / `shiftId`)、班次名称(`shiftName`) -### 阶段 4: 回显排班内容并使用 ask_question 卡片确认 +### 阶段 4: 回显排班内容并获得明确确认 -> **[硬性门禁]** 必须先用普通文本向用户展示**完整的排班明细表格**(包含每个人、每天、具体班次名称),用户看到排班明细后,才能弹出 `ask_question` 确认卡片。 -> **禁止在用户还不知道"谁、哪天、上什么班"的情况下就弹确认卡片**——这等于让用户盲签,体验极差。 +> **[硬性门禁]** 必须先向用户展示**完整的排班明细表格**(包含每个人、每天、具体班次名称),再获得用户明确确认。 +> **禁止在用户还不知道"谁、哪天、上什么班"的情况下请求确认**。 -**步骤 4a — 展示排班明细(必须在确认卡片之前)**: +**步骤 4a — 展示排班明细(必须在请求确认之前)**: -在调用 `ask_question` 之前,**必须先**用普通文本向用户展示排班内容。表格中**必须包含班次名称**(如"早班 09:00-18:00"),不能只展示 classId 数字: +请求确认前,**必须先**向用户展示排班内容。表格中**必须包含班次名称**(如"早班 09:00-18:00"),不能只展示 classId 数字: ``` 排班预览 @@ -291,27 +244,9 @@ dws attendance group filtered-get --group-id --member --format json - [ ] 排休的记录标注了"排休" - [ ] 表格涵盖了所有待排班的员工和日期 -**步骤 4b — 使用 `ask_question` 卡片确认(必须在展示表格之后)**: +**步骤 4b — 获得明确确认(必须在展示表格之后)**: -``` -ask_question({ - title: "排班执行确认", - questions: [ - { - id: "confirm_execute", - prompt: "以上排班将覆盖所选日期的现有排班记录,确认执行吗?", - options: [ - { id: "yes", label: "确认执行" }, - { id: "no", label: "取消" } - ] - } - ] -}) -``` - -**处理用户选择**: -- 用户选择 **"确认执行"** → 进入阶段 5 -- 用户选择 **"取消"** → 终止流程,提示"已取消排班操作" +说明排班将覆盖所选日期的现有记录,并等待用户明确确认。确认后进入阶段 5;用户取消或未明确同意时不执行导入。 ### 阶段 5: 调用脚本执行排班 @@ -508,8 +443,8 @@ python scripts/attendance_schedule_import.py \ | 脚本 | 用途 | CLI 参数 | |------|------|---------| -| [attendance_schedule_import.py](../../scripts/attendance_schedule_import.py) | 排班导入(含校验、回显、执行) | `--group-id --schedules --confirm` | -| [attendance_schedule_export.py](../../scripts/attendance_schedule_export.py) | 排班查询导出(分批查询、排班表 Excel) | `--users --start --end [--output]` | +| [attendance_schedule_import.py](../scripts/attendance_schedule_import.py) | 排班导入(含校验、回显、执行) | `--group-id --schedules --confirm` | +| [attendance_schedule_export.py](../scripts/attendance_schedule_export.py) | 排班查询导出(分批查询、排班表 Excel) | `--users --start --end [--output]` | --- diff --git a/skills/multi/dingtalk-attendance/references/attendance-scripts.md b/skills/multi/dingtalk-attendance/references/attendance-scripts.md new file mode 100644 index 00000000..660531e3 --- /dev/null +++ b/skills/multi/dingtalk-attendance/references/attendance-scripts.md @@ -0,0 +1,21 @@ +# 自动化脚本 + +## 自动化脚本 + +| 脚本 | 场景 | 用法 | +|------|------|------| +| [attendance_my_record.py](../scripts/attendance_my_record.py) | 查看我今天/指定日期的考勤记录 | `python attendance_my_record.py today` | +| [attendance_team_shift.py](../scripts/attendance_team_shift.py) | 查询团队成员本周排班 | `python attendance_team_shift.py --users userId1,userId2` | +| [attendance_report_common.py](../scripts/attendance_report_common.py) | 考勤报表导出公共模块(不可单独执行) | — | +| [attendance_vacation_balance.py](../scripts/attendance_vacation_balance.py) | 假期余额列表 Excel 导出 | **禁止直接调用**,必须先读 [attendance-vacation.md](./attendance-vacation.md) 按工作流执行 | +| attendance_report_detail.py | 考勤报表 — **明细粒度** | **禁止直接调用**,必须先读 [attendance-report.md](./attendance-report.md) 按工作流执行 | +| attendance_report_monthly.py | 考勤报表 — **月度汇总** | **禁止直接调用**,必须先读 [attendance-report.md](./attendance-report.md) 按工作流执行 | +| attendance_report_daily.py | 考勤报表 — **每日统计** | **禁止直接调用**,必须先读 [attendance-report.md](./attendance-report.md) 按工作流执行 | +| attendance_report_record.py | 考勤报表 — **考勤记录**(补卡/请假/出差/外出) | **禁止直接调用**,必须先读 [attendance-report.md](./attendance-report.md) 按工作流执行 | +| attendance_schedule_import.py | 排班导入(含校验、回显、执行) | **禁止直接调用**,必须先读 [attendance-schedule.md](./attendance-schedule.md) 按工作流执行 | +| attendance_schedule_export.py | 排班查询导出(分批查询、排班表 Excel) | **禁止直接调用**,必须先读 [attendance-schedule.md](./attendance-schedule.md) 按工作流执行 | + +> 说明: +> - `attendance_report_*.py` 四个脚本由 [attendance-report.md](./attendance-report.md) 工作流编排使用:detail/monthly/daily 自动处理 `--users` 超过 20 人分批、`--start/--end` 超过 32 天按月切片;record 自包含数据查询+解析+Excel 生成(补卡/请假/出差/外出审批记录) +> - `attendance_schedule_import.py` 由 [attendance-schedule.md](./attendance-schedule.md) 排班导入工作流编排使用,自动处理考勤组校验、班次校验、排班回显确认 +> - `attendance_schedule_export.py` 由 [attendance-schedule.md](./attendance-schedule.md) 排班查询导出工作流编排使用,自动处理分批查询(超 20 人自动分批)、userId→姓名转换、classId→班次名称转换、输出排班表格式 Excel diff --git a/skills/multi/dingtalk-attendance/references/attendance-vacation.md b/skills/multi/dingtalk-attendance/references/attendance-vacation.md index 6c59e739..afeee1eb 100644 --- a/skills/multi/dingtalk-attendance/references/attendance-vacation.md +++ b/skills/multi/dingtalk-attendance/references/attendance-vacation.md @@ -137,4 +137,4 @@ python scripts/attendance_vacation_balance.py \ | 脚本 | 场景 | CLI 参数 | |------|------|---------| -| [attendance_vacation_balance.py](../../scripts/attendance_vacation_balance.py) | 假期余额列表 Excel 导出;脚本按假期名称关键词筛选规则,并逐个 `leaveCode` 调用余额查询 | `--users [--leave-keywords] [--out] [--inspect]` | +| [attendance_vacation_balance.py](../scripts/attendance_vacation_balance.py) | 假期余额列表 Excel 导出;脚本按假期名称关键词筛选规则,并逐个 `leaveCode` 调用余额查询 | `--users [--leave-keywords] [--out] [--inspect]` | diff --git a/skills/multi/dingtalk-attendance/references/attendance-workflow.md b/skills/multi/dingtalk-attendance/references/attendance-workflow.md new file mode 100644 index 00000000..f30f03ed --- /dev/null +++ b/skills/multi/dingtalk-attendance/references/attendance-workflow.md @@ -0,0 +1,148 @@ +# 核心工作流 + +## 核心工作流 + +```bash +# 导入排班记录 +dws attendance schedule import --group-id 123456 \ + --schedules '[{"userId":"user001","classId":123,"workDate":"2026-04-22","checkBeginTime":"09:00","checkEndTime":"18:00"}]' \ + --yes --format json + +# 获取排班记录 — 禁止直接调用,必须走 attendance-schedule.md 排班查询导出工作流 +# python scripts/attendance_schedule_export.py --users user001,user002 --start 2026-04-01 --end 2026-04-30 + +# 查询可管理的班次列表 +dws attendance class search --format json +dws attendance class search --query "早班" --filter-type MINE_OWN --format json + +# 查询班次详情 +dws attendance class get --class-id 1170996821 --format json + +# 更新班次 +dws attendance class update --class-id 1170996821 --name "新早班" --timeout 10 --format json +dws attendance class update --class-id 1170996821 --class-vo '{"sections":[{"times":[{"checkType":"OnDuty","checkTime":"08:30","across":0},{"checkType":"OffDuty","checkTime":"17:30","across":0}]}]}' --timeout 10 --format json + +# 查询补卡规则 +dws attendance adjustment search --page 1 --limit 20 --format json +dws attendance adjustment search --query "标准" --page 1 --limit 20 --format json + +# 查询补卡规则详情 +dws attendance adjustment get --adjustment-id 12345 --format json + +# 查询加班规则 +dws attendance overtime search --page 1 --limit 20 --format json + +# 查询加班规则详情 +dws attendance overtime get --overtime-id 12345 --format json + +# 查询考勤组列表 +dws attendance group search --query "研发" --page 1 --limit 20 --format json +dws attendance group search --type FIXED --page 1 --limit 20 --format json + +# 查询考勤组全量信息 +dws attendance group get --group-id 123456 --format json + +# 按需查询考勤组成员/地址/蓝牙/Wifi +dws attendance group filtered-get --group-id 123456 --member --format json +dws attendance group filtered-get --group-id 123456 --position --wifi --format json + +# 更新考勤组成员 +dws attendance group update-members --group-id 123456 --add-users userId1,userId2 --timeout 10 --format json +dws attendance group update-members --group-id 123456 --remove-users userId1 --timeout 10 --format json +dws attendance group update-members --group-id 123456 --add-depts deptId1 --remove-users userId2 --timeout 10 --format json + +# 更新考勤组配置 +dws attendance group update --group-id 123456 --name "研发考勤组" --timeout 10 --format json +dws attendance group update --group-id 123456 --classIds '[1374234767]' --timeout 10 --format json +dws attendance group update --group-id 123456 --group-vo '{"positions":[{"title":"总部","address":"北京市","latitude":39.9,"longitude":116.4,"offset":200}]}' --timeout 10 --format json + +# 创建考勤组 +dws attendance group create --name "研发考勤组" --type FIXED --group-vo '{"defaultClassId":1170996821,"workDayClassList":[0,1170996821,0,0,0,0,0]}' --timeout 10 --format json +dws attendance group create --name "自由工时分组" --type NONE --timeout 10 --format json + +# 查看考勤统计摘要 +dws attendance summary --user --date 2026-03-12 --stats-type week --format json + +# 查看考勤组和规则 +dws attendance rules --date 2026-03-14 --format json + +# 查看指定用户的打卡提醒设置 +dws attendance selfsetting get --setting-scene checkRemind --user --format json + +# 查看指定用户的极速打卡设置 +dws attendance selfsetting get --setting-scene fastCheck --user --format json + +# 开启指定用户的打卡结果通知 +dws attendance selfsetting save --setting-scene checkResultNotify --user --check-result-msg 1 --format json + +# 更新指定用户的极速打卡设置 +dws attendance selfsetting save --setting-scene fastCheck --user \ + --onduty-check-type 3 --voice-remind-switch=true --format json + +# 获取考勤字段列表(管理员) +dws attendance report columns --format json + +# 根据字段查询考勤数据(管理员) +dws attendance report query-data --users userId1,userId2 \ + --columns 1001,1002 --start "2026-03-01 00:00:00" --end "2026-03-31 23:59:59" --format json + +# 查询用户假期数据(管理员) +dws attendance report query-leave --users userId1,userId2 \ + --leave-names 年假,病假 --start "2026-03-01 00:00:00" --end "2026-03-31 23:59:59" --format json + +# 查看假期规则列表 +dws attendance vacation types --format json + +# 查看指定员工假期余额 +dws attendance vacation balance --users userId1,userId2 --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 update-type --leave-code a1b2c3d4-e5f6-7890-abcd-ef1234567890 \ + --name "事假(修改版)" --format json + +# 更新假期单位 +dws attendance vacation update-type --leave-code a1b2c3d4-e5f6-7890-abcd-ef1234567890 \ + --unit hour --per-hours 8 --format json + +# 改为指定部门可见 +dws attendance vacation update-type --leave-code a1b2c3d4-e5f6-7890-abcd-ef1234567890 \ + --visibility-rules '[{"type":"dept","visible":["1","2","3"]}]' --format json + +# 改为全公司可见(哨兵约定:必须显式传 "-1",不能传空数组 []) +dws attendance vacation update-type --leave-code a1b2c3d4-e5f6-7890-abcd-ef1234567890 \ + --visibility-rules '[{"type":"dept","visible":["-1"]}]' --format json + +# 设置员工假期余额完整流程 +# 1. 查询当前余额 +dws attendance vacation balance --users user001 \ + --leave-code a1b2c3d4-e5f6-7890-abcd-ef1234567890 --format json + +# 2. 根据查询结果计算新值(如当前5天,要设置为8天) + +# 3. 执行设置(SET操作,会替换当前余额) +dws attendance vacation save-balance --target user001 \ + --leave-code a1b2c3d4-e5f6-7890-abcd-ef1234567890 \ + --num 8 --reason "年度发放" --start 2024-01-01 --end 2024-12-31 --format json + +# 增加员工假期余额完整流程(ADD场景) +# 1. 查询当前余额(假设返回5天) +dws attendance vacation balance --users user001 \ + --leave-code a1b2c3d4-e5f6-7890-abcd-ef1234567890 --format json + +# 2. 计算增加后的新值(5 + 3 = 8天) + +# 3. 设置新总额度 +dws attendance vacation save-balance --target user001 \ + --leave-code a1b2c3d4-e5f6-7890-abcd-ef1234567890 \ + --num 8 --reason "绩效奖励发放3天" --format json + +# 查询签到记录 +dws attendance checkin records --operator-staff-id op001 --staff-ids user001,user002 \ + --start "2026-04-01 00:00:00" --end "2026-04-07 00:00:00" --format json +``` diff --git a/skills/multi/dingtalk-attendance/references/attendance.md b/skills/multi/dingtalk-attendance/references/attendance.md index 86e20d66..3015c566 100644 --- a/skills/multi/dingtalk-attendance/references/attendance.md +++ b/skills/multi/dingtalk-attendance/references/attendance.md @@ -1,1317 +1,11 @@ # 考勤 (attendance) 命令参考 -> **【命令可用性提示】** 当前 dws 已注册全部考勤子命令组(`record` / `check` / `approve` / `shift` / `schedule` / `class` / `adjustment` / `overtime` / `group` / `summary` / `rules` / `selfsetting` / `globalsetting` / `vacation` / `checkin` / `report` / `boss-check`)。查询与写操作大多可直接调用后端,不会再返回 `unknown command`,不要以"开源版不支持"为由拒答。个别命令返回受账号权限和组织数据影响:`report` 系列仅管理员可用;非管理员或数据为空时可能返回空列表或权限错误。执行前可用 `dws --help` 或 `--dry-run` 验证参数。 - -> **【必读】日期范围严格计算规则 — 所有含 --start/--end 或 --from/--to 的命令均适用** -> -> **禁止随意猜测日期范围,必须按以下规则精确计算后再组装命令参数:** -> -> | 用户表达 | --start(含)| --end(含)| 说明 | -> |---------|------------|------------|------| -> | 本周 / 这周 / 当周 | 本周**周一** | 本周**周日** | 周一为一周的第一天,周日为最后一天 | -> | 上周 / 上一周 | 上周**周一** | 上周**周日** | 同上,往前推一周 | -> | 本月 / 这个月 / 当月 | 本月 **1 日** | 本月**最后一天**(28/29/30/31)| 必须计算当月实际天数 | -> | 上月 / 上个月 | 上月 **1 日** | 上月**最后一天** | 同上,往前推一个月 | -> | 今天 / 今日 | **今天** | **今天** | start == end | -> | 昨天 | **昨天** | **昨天** | start == end | -> -> **必须遵守:** -> 1. **本周一定是周一到周日**,严禁将"本周"解释为周一到周五或其他任意范围。 -> 2. **本月一定是 1 日到该月实际最后一天**,严禁将"本月"截断为 1 日到 28 日或其他固定天数。 -> 3. 计算日期时必须参考当前系统时间,不得使用硬编码或模糊估算。 -> 4. 如果用户指定了具体日期范围(如"4月1日到4月15日"),直接使用用户给定的范围,不套用上述规则。 -> 5. 日期格式统一按各命令要求:`shift list` / `check result` 等使用 `YYYY-MM-DD`;`report query-data` / `report query-leave` 等使用 `yyyy-MM-dd HH:mm:ss`(start 取当天 `00:00:00`,end 取当天 `23:59:59`)。 - -## 命令总览 - - -### 查询打卡结果 -``` -Usage: - dws attendance check result [flags] -Example: - dws attendance check result --users userId1,userId2 --start 2026-04-01 --end 2026-04-30 --limit 50 -Flags: - --start string 起始日期, 格式 YYYY-MM-DD (必填) - --end string 结束日期, 格式 YYYY-MM-DD, 不超过 1 个月 (必填) - --limit int 分页大小, 默认 100, 范围 1-1000 (可选) - --offset int 分页偏移量, 默认 0 (可选) - --users string 用户 ID 列表, 逗号分隔, 最多 100 个 (必填) -``` - -返回每条记录含:用户 ID、工作日期、时间结果(Normal/Late/Early/Absenteeism/NotSigned)、位置结果、计划打卡时间、实际打卡时间、打卡流水 ID。时间跨度不超过 1 个月,最多 100 人。 - -### 查询打卡流水 -``` -Usage: - dws attendance check record [flags] -Example: - dws attendance check record --users userId1 --start 2026-04-01 --end 2026-04-30 -Flags: - --start string 起始日期, 格式 YYYY-MM-DD (必填) - --end string 结束日期, 格式 YYYY-MM-DD, 不超过 1 个月 (必填) - --users string 用户 ID 列表, 逗号分隔 (必填) -``` - -返回每条记录含:用户 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: - dws attendance approve list [flags] -Example: - dws attendance approve list --users userId1 --types overtime,leave --start 2026-04-01 --end 2026-04-30 -Flags: - --start string 起始日期, 格式 YYYY-MM-DD (必填) - --end string 结束日期, 格式 YYYY-MM-DD (必填) - --types string 审批类型, 逗号分隔: overtime/trip/leave/patch (必填) - --users string 用户 ID 列表, 逗号分隔 (必填) -``` - -审批类型映射:overtime=加班, trip=出差, leave=请假, patch=补卡。返回每条记录含:用户 ID、审批标签、审批子类型、审批类型、生效时间、时长、时长单位、流程实例 ID。 - -### 查询补卡/请假/加班审批提交链接 (必须走引导流程) -``` -Usage: - dws attendance approve templates [flags] -Example: - dws attendance approve templates --type leave - dws attendance approve templates --type REPAIR_CHECK - dws attendance approve templates --type 加班 -Flags: - --type string 审批类型:repair-check/patch/补卡、leave/请假、overtime/加班,或 REPAIR_CHECK/LEAVE/OVERTIME(必填) -``` - -当用户提到需要提交补卡、请假或加班时,优先使用该命令查询考勤审批表单模板提交链接,并引导用户点击返回的 `submitUrl` 提交。 -`corpId` 和 `opUserId` 由系统参数自动注入,无需通过命令参数传入。 -审批类型映射:补卡=`REPAIR_CHECK`,请假=`LEAVE`,加班=`OVERTIME`。返回结果为列表,每条记录包含 `approveType`、`formName`、`processCode`、`submitUrl`。 -#### 引导用户自主选择合适的表单模板流程 -如果返回多个表单模板,必须将多个可用模板都返回给用户,并引导用户根据实际场景自主选择合适的模板提交: -- 请假场景:可根据 `formName` 将与用户请假类型更匹配的模板放在前面展示。例如用户明确说年假、事假、病假、调休时,将名称中包含对应假期类型的模板靠前;如果用户只泛化表达“请假”,将名称最通用的请假模板靠前,例如“请假”“员工请假”“通用请假”等,避免把专项或特殊场景模板放在最前。 -- 补卡/加班场景:可将名称与“补卡”或“加班”最直接匹配的模板放在前面展示。 -- 回复用户时不要直接裸露任何 `submitUrl`,所有返回的表单模板都必须使用 Markdown 可点击链接格式展示:`[formName](submitUrl)`,例如 `[员工请假](https://...)`。如存在更匹配的模板,可以放在列表前面,但不要只返回推荐模板,必须同时返回其它可用模板供用户选择,且每个模板都应是用户可直接点击的 Markdown 链接。 - -### 导入排班记录(排班 = 为员工安排工作日期和班次, 写场景接口,必须走二次确认流程) -``` -Usage: - dws attendance schedule import [flags] -Example: - dws attendance schedule import --group-id 123456 \ - --schedules '[{"userId":"user001","classId":123,"workDate":"2026-04-22","isRest":"N"}]' \ - --yes -Flags: - --group-id string 考勤组(必填,传入考勤组ID);--help 主名为 --groupId,--group-id 为别名 - --schedules string 排班记录 JSON 数组(必填);--help 主名为 --scheduleVOS,--schedules 为别名 - --yes 跳过确认提示;--help 主名为 --user-say-yes,--yes 为别名 -``` - -为排班制考勤组导入排班记录。`--schedules` 为 JSON 数组,每条记录包含: -- `userId`: 员工ID(必填) -- `classId`: 班次ID(必填) -- `workDate`: 工作日期(YYYY-MM-DD),如 2026-04-22(必填) -- `isRest`: 是否休息日 Y/N(**必填**,服务端要求传入) -- `checkBeginTime` / `checkEndTime`: 开始/结束打卡时间(可传,但当前不会进入后端 payload,实际打卡时段以 `classId` 对应班次为准) - -#### AI 调用 `schedule import` 的二次确认流程 - -`schedule import` 是写操作,会为考勤组导入或变更员工排班。AI 调用时必须按以下流程执行,不得在未确认的情况下直接导入: - -1. **识别写操作**:用户表达“导入排班 / 设置排班 / 安排排班 / 给员工排班 / 批量排班”等意图时,命中 `schedule import`。 -2. **收集必要参数**:必须明确 `--group-id` 和 `--schedules`,并确认排班记录中的 `userId`、`classId`、`workDate`、`checkBeginTime`、`checkEndTime`、`isRest` 等字段。 -3. **展示导入摘要并反问确认**:向用户展示考勤组 ID、导入员工数量、涉及日期范围、班次 ID 列表,以及排班记录明细摘要,并询问是否确认执行导入。 -4. **用户确认后再执行导入**:只有用户明确确认后,才可以执行 `dws attendance schedule import ... --format json`。 - -确认话术示例: - -```text -即将导入排班记录,请确认: -- 考勤组 ID: -- 员工数量: -- 日期范围: ~ -- 班次 ID: -- 排班明细: - - : -,班次 - -是否确认执行导入? -``` - -如用户明确要求跳过确认,或命令中明确包含全局 `--yes`,可跳过二次确认。 - -### 获取排班记录 - -**禁止直接调用 `dws attendance schedule get`。必须先 `read_file` 读取 [attendance-schedule.md](./attendance-schedule.md) 后按其中的「排班查询导出工作流」执行。** - -- 任何"查询排班"、"查看排班"、"XX考勤组的排班"、"X月份的排班"场景,**一律走 attendance-schedule.md 工作流**,由脚本 `attendance_schedule_export.py` 统一处理 -- 脚本自动处理:分批查询(超 20 人自动分批)、userId→姓名转换、classId→班次名称转换、排班表格式 Excel 输出 -- 违反后果:人数多时接口超时/报错、输出裸 userId 和 classId 用户看不懂、无排班表格式 -- **此处不提供 `schedule get` 的 Usage/Flags,防止绕过工作流直接拼命令。完整参数由 attendance-schedule.md 工作流中的脚本内部使用。** - -### 查询当前用户可管理的所有班次详情 -``` -Usage: - dws attendance class search [flags] -Example: - dws attendance class search - dws attendance class search --query "早班" --filter-type MINE_OWN - dws attendance class search --page 1 --limit 50 -Flags: - --filter-type string 班次类型: ALL 全部班次 / MINE_OWN 我负责的 (可选) - --query string 班次名称关键字, 模糊搜索 (可选) - --page int 页码, 从 1 开始 (可选, 默认 1) - --limit int 每页条数, 最大 200 (可选, 默认 20) -``` - -### 查询班次详情 -``` -Usage: - dws attendance class get [flags] -Example: - dws attendance class get --class-id 1170996821 -Flags: - --class-id int 班次 ID (必填) -``` - -根据班次 ID 查询该班次的完整详细信息。班次 ID 可从 `class search` 返回结果中提取,也有可能来源于用户手动输入。 - -### 创建班次 (写场景接口,必须走二次确认流程) -**强制执行流程**:此命令为写操作,Agent 调用时必须遵守以下流程: -1. 先向用户展示待执行操作的完整参数摘要,包括班次名称、上下班时间、休息时段等 -2. 使用 `ask_human` 或返回待确认状态,等待用户明确确认 -3. 用户确认后,再传全局 `--yes` 执行命令 - -**禁止未经用户确认直接执行或自动添加 `--yes`。** -``` -Usage: - dws attendance class create [flags] -Example: - dws attendance class create --name "早班" --class-vo '{"sections":[{"times":[{"checkType":"OnDuty","checkTime":"08:00","across":0},{"checkType":"OffDuty","checkTime":"17:00","across":0}]}]}' --timeout 10 - # 带休息时段(12:00-13:00 午休) - dws attendance class create --name "测试CLI" --class-vo '{"sections":[{"times":[{"checkType":"OnDuty","checkTime":"09:00","across":0},{"checkType":"OffDuty","checkTime":"18:00","across":0}]}],"setting":{"topRestTimeList":[{"checkType":"OnDuty","checkTime":"12:00","across":0},{"checkType":"OffDuty","checkTime":"13:00","across":0}]}}' --timeout 10 -Flags: - --name string 班次名称 (必填) - --owner string 班次负责人 userId (可选) - --class-vo string 完整 TopAtClassVO JSON 字符串, 包含 sections 等复杂子对象 (必填) -``` - -创建一个新班次。`--name` 和 `--class-vo`(包含 `sections`)必填。`sections` 定义班次的上下班时间段,支持多段上下班,每段包含 `times` 数组(有且只能有两个对象:上班+下班)。由于保存班次耗时较久,建议加 `--timeout 10`。 - -`checkTime` 字段统一使用 "HH:mm" 格式(如 "09:00"、"17:30"),CLI 自动转换为服务端所需格式。 - -`--class-vo` 支持字段: -- `name`(string, 必填) `owner`(string, 可选) -- `sections`([]object, 必填): 每个对象含 `times`([]object),每个 time 含 `checkType`(OnDuty/OffDuty, 必填) `checkTime`("HH:mm", 必填) `across`(0/1, 必填) `freeCheck`(bool) `beginMin`(number, -1不限制) `endMin`(number, -1不限制) -- `setting`(object, 可选): `seriousLateMinutes`(严重迟到分钟) `absenteeismLateMinutes`(旷工迟到分钟) `attendDays`(出勤天数) `topRestTimeList`([]object, 仅单段上下班时可用,最多3段: checkType/checkTime("HH:mm")/across) - -### 更新班次 (写场景接口,必须走二次确认流程) -**强制执行流程**:此命令为写操作,Agent 调用时必须遵守以下流程: -1. 先向用户展示待执行操作的完整参数摘要,包括班次 ID、要修改的字段含义及新值 -2. 使用 `ask_human` 或返回待确认状态,等待用户明确确认 -3. 用户确认后,再传全局 `--yes` 执行命令 - -**禁止未经用户确认直接执行或自动添加 `--yes`。** -``` -Usage: - dws attendance class update [flags] -Example: - dws attendance class update --class-id 1170996821 --name "新早班" --timeout 10 - dws attendance class update --class-id 1170996821 --class-vo '{"sections":[{"times":[{"checkType":"OnDuty","checkTime":"08:30","across":0},{"checkType":"OffDuty","checkTime":"17:30","across":0}]}]}' --timeout 10 - # 带休息时段(12:00-13:00 午休) - dws attendance class update --class-id 1170996821 --class-vo '{"sections":[{"times":[{"checkType":"OnDuty","checkTime":"09:00","across":0},{"checkType":"OffDuty","checkTime":"18:00","across":0}]}],"setting":{"topRestTimeList":[{"checkType":"OnDuty","checkTime":"12:00","across":0},{"checkType":"OffDuty","checkTime":"13:00","across":0}]}}' --timeout 10 -Flags: - --class-id int 班次 ID (必填) - --name string 班次名称 (可选,不传则保持原值) - --owner string 班次负责人 userId (可选,不传则保持原值) - --class-vo string 完整 TopAtClassVO JSON 字符串,用于修改复杂子对象 (可选) -``` - -更新班次配置。`--class-id` 必填,其余均可选,仅需对要修改的字段进行赋值,未传字段会自动从已有配置补充。小改用单字段 flag(如 `--name`);修改上下班时间、休息时段等复杂子对象时用 `--class-vo` 传入完整 JSON。`--class-vo` 与单字段 flag 同时传入时,单字段 flag 优先级更高。 - -`checkTime` 字段统一使用 "HH:mm" 格式(如 "09:00"、"17:30"),CLI 自动转换为服务端所需格式。 - -`--class-vo` 支持字段(均可选,只需包含要修改的字段): -- `name`(string) `owner`(string) -- `sections`([]object): 每个对象含 `times`([]object),每个 time 含 `checkType`(OnDuty/OffDuty) `checkTime`("HH:mm") `across`(0/1) `freeCheck`(bool) `beginMin`(number, -1不限制) `endMin`(number, -1不限制) -- `setting`(object): `seriousLateMinutes` `absenteeismLateMinutes` `attendDays` `topRestTimeList`([]object: checkType/checkTime("HH:mm")/across) - -由于保存班次耗时较久,建议加 `--timeout 10`。 - -### 分页查询补卡规则,支持按名称搜素 -``` -Usage: - dws attendance adjustment search [flags] -Example: - dws attendance adjustment search --page 1 --limit 20 - dws attendance adjustment search --query "标准" --page 1 --limit 50 -Flags: - --page int 页码, 从 1 开始 (必填, 默认 1) - --query string 补卡规则名称关键字, 模糊搜索 (可选) - --limit int 每页条数, 200 以内 (必填, 默认 20) -``` - -### 查询补卡规则详情 -``` -Usage: - dws attendance adjustment get [flags] -Example: - dws attendance adjustment get --adjustment-id 12345 -Flags: - --adjustment-id int 补卡规则主键 ID (必填) -``` - -**注意:本命令当前拿不到有效的补卡规则详情,不要依赖它。** 服务端对任意 `--adjustment-id`(含不存在的 ID)都返回同一个 `{"success":true,"有效期类型":"..."}`,不返回规则明细,也无法据此判断规则是否存在;且 `adjustment search` 返回的默认补卡规则 `entityVO.id` 为 `null`,`search → get` 取 id 的链路走不通。补卡规则内容请直接看 `adjustment search` 的返回结果。 - -### 分页查询加班规则,支持按名称搜素 -``` -Usage: - dws attendance overtime search [flags] -Example: - dws attendance overtime search --page 1 --limit 20 - dws attendance overtime search --query "节假日" --page 1 --limit 50 -Flags: - --page int 页码, 从 1 开始 (必填, 默认 1) - --query string 加班规则名称关键字, 模糊搜索 (可选) - --limit int 每页条数, 200 以内 (必填, 默认 20) -``` - -### 查询加班规则详情 -``` -Usage: - dws attendance overtime get [flags] -Example: - dws attendance overtime get --overtime-id 12345 -Flags: - --overtime-id int 加班规则主键 ID (必填) -``` - -根据加班规则主键 ID 查询对应的加班规则详情。主键 ID 可从 `overtime search` 返回结果中提取,也有可能来源于用户手动输入。**已被删除或更新覆盖的加班规则也可以查到。** - -### 查询考勤组列表 -``` -Usage: - dws attendance group search [flags] -Example: - dws attendance group search --query "研发" - dws attendance group search --type FIXED --limit 50 - dws attendance group search --page 1 --limit 20 -Flags: - --query string 考勤组名称关键字, 模糊搜索 (可选) - --page int 页码, 从 1 开始 (必填, 默认 1) - --limit int 每页条数, 200 以内 (必填, 默认 20) - --query-ble 是否查询蓝牙设备列表 (可选, 默认 false) - --query-position 是否查询地理定位和 Wifi 名称 (可选, 默认 false) - --type string 考勤组类型: FIXED 固定班制 / TURN 排班制 / NONE 自由工时 (可选) -``` - -### 查询考勤组全量信息 -``` -Usage: - dws attendance group get [flags] -Example: - dws attendance group get --group-id 123456 -Flags: - --group-id int 考勤组 ID (必填) -``` - -根据考勤组 ID 查询该考勤组的全量信息。考勤组 ID 可从 `group search` 返回结果中提取,也有可能来源于用户手动输入。如果只需查询成员、打卡地址、蓝牙、Wifi 子集,请使用 `group filtered-get` 以节省查询成本。 -返回结果中如含成员 userId 列表,必须调用 `dws contact user get --ids ,,...`(支持逗号分隔传多个 ID),将 userId 转换为员工姓名后再输出;不得直接输出裸 userId - -### 按需查询考勤组部分信息 -``` -Usage: - dws attendance group filtered-get [flags] -Example: - dws attendance group filtered-get --group-id 123456 --member - dws attendance group filtered-get --group-id 123456 --position --wifi -Flags: - --group-id int 考勤组 ID (必填) - --member 是否查询考勤组成员信息 (可选, 默认 false) - --position 是否查询打卡地址 (可选, 默认 false) - --wifi 是否查询打卡 Wifi (可选, 默认 false) - --bles 是否查询打卡蓝牙 (可选, 默认 false) -``` - -强烈建议在仅需查询成员、打卡地址、蓝牙、Wifi 时调用该命令,避免全量查询带来的性能开销。考勤组 ID 可从 `group search` 返回结果中提取,也有可能来源于用户手动输入。 -返回结果中如含成员 userId 列表,必须调用 `dws contact user get --ids ,,...`(支持逗号分隔传多个 ID),将 userId 转换为员工姓名后再输出;不得直接输出裸 userId - -### 更新考勤组成员 (写场景接口,必须走二次确认流程) -**强制执行流程**:此命令为写操作,Agent 调用时必须遵守以下流程: -1. 先向用户展示待执行操作的完整参数摘要,包括考勤组 ID、要添加/移除的成员列表 -2. 使用 `ask_human` 或返回待确认状态,等待用户明确确认 -3. 用户确认后,再传全局 `--yes` 执行命令 - -**禁止未经用户确认直接执行或自动添加 `--yes`。** - -``` -Usage: - dws attendance group update-members [flags] -Example: - dws attendance group update-members --group-id 123456 --add-users userId1,userId2 - dws attendance group update-members --group-id 123456 --remove-users userId1 - dws attendance group update-members --group-id 123456 --add-depts deptId1 --remove-users userId2 -Flags: - --group-id int 考勤组 ID (必填) - --add-users string 添加考勤人员 userId 列表, 逗号分隔, 最多 20 个 (可选) - --remove-users string 删除考勤人员 userId 列表, 逗号分隔, 最多 20 个 (可选) - --add-extra-users string 添加无需考勤的人员 userId 列表, 逗号分隔, 最多 20 个 (可选) - --remove-extra-users string 删除无需考勤的成员 userId 列表, 逗号分隔, 最多 20 个 (可选) - --add-depts string 添加考勤部门 ID 列表, 逗号分隔, 最多 20 个 (可选) - --remove-depts string 删除考勤部门 ID 列表, 逗号分隔, 最多 20 个 (可选) -``` - -对指定考勤组的成员进行增删操作。--group-id 必填,其余参数均为可选,但至少需要传入一个变更项,否则命令拒绝执行。每次调用各参数最多传 20 个 ID。"无需考勤"人员指考勤组内豁免打卡的成员(如高管)。 - -### 更新考勤组配置 (写场景接口,必须走二次确认流程) -**强制执行流程**:此命令为写操作,Agent 调用时必须遵守以下流程: -1. 先向用户展示待执行操作的完整参数摘要,包括考勤组 ID、要修改的字段含义及新值 -2. 使用 `ask_human` 或返回待确认状态,等待用户明确确认 -3. 用户确认后,再传全局 `--yes` 执行命令 - -**禁止未经用户确认直接执行或自动添加 `--yes`。** -``` -Usage: - dws attendance group update [flags] -Example: - dws attendance group update --group-id 123456 --name "研发考勤组" --timeout 10 - dws attendance group update --group-id 123456 --owner userId1 --timeout 10 - dws attendance group update --group-id 123456 --classIds '[1374234767]' --timeout 10 - dws attendance group update --group-id 123456 --group-vo '{"positions":[{"title":"总部","address":"北京市...","latitude":39.9,"longitude":116.4,"offset":200}]}' --timeout 10 -Flags: - --group-id int 考勤组 ID (必填) - --name string 考勤组名称 (可选) - --type string 考勤组类型:FIXED 固定班制 / TURN 排班制 / NONE 自由工时 (可选) - --owner string 考勤组主负责人 userId (可选) - --enable-outside-check 是否允许外勤打卡 true/false (可选) - --classIds string 所选班次 id 列表, JSON 数组格式, 如 '[123,456]' (可选) - --group-vo string 完整 groupVO JSON 字符串, 用于修改复杂子对象 (可选) -``` - -更新考勤组配置。--group-id 必填,其余均可选,但至少需指定一个修改项。仅需对要修改的字段进行赋値,其余字段会自动从已有配置补充。小改用单字段 flag;修改打卡地址、wifi、蓝牙设备、循环排班等复杂子对象时用 `--group-vo` 传入完整 JSON。`--group-vo` 与单字段 flag 同时传入时,单字段 flag 优先级更高。 - -`--group-vo` 支持字段(均可选,只需包含要修改的字段): -- 基础:`name`(名称) `type`(FIXED/TURN/NONE) `owner`(主负责人 userId) `managerList`([]string 子负责人) `skipHolidays`(bool,只在固定班制和自由工时生效) `defaultGroup`(bool) `classIds`([]number,所选班次 id,只有固定班制和排班制才有班次,自由工时没有) -- 打卡范围:`trimDistance`(微调距离) `enablePositionOfGps/Wifi/Ble`(bool) -- 打卡地址:`positions`([]对象: title/address/latitude/longitude/offset,其中 offset 为该地址允许的打卡范围米) -- Wifi:`wifis`([]对象: ssid/macAddr/groupId) -- 蓝牙:`bleDeviceVOList`([]对象: name/deviceUid/sn/productType/devServId) -- 外勤:`enableOutsideCheck`(bool) `enableOutsideCameraCheck/Remark/Apply`(bool) `outsideCheckApproveMode`(NO_NEED_APPROVE/APPROVE_FIRST/CHECK_FIRST/APPROVE_EVERYTIME) `outSideCheckApplyType`(1全天/2上班/3下班) `forbidHideOutSideAddress`(bool) `enableOutSideUpdateNormalCheck`(下班时允许外勤卡更新内勤卡) `enableOnDutyNormalUpdateOutsideCheck`(上班时允许内勤卡更新外勤卡) -- 打卡方式:`enableCameraCheck/openCameraCheck` `openFaceCheck` `enableFaceStrictMode` `enableFaceBeauty`(bool) `onlyMachineCheck`(bool) `permitMaxBeaconCount`(number) `disableCheckWhenRest`(bool,休息日打卡需审批,只在固定班制和排班制生效) -- 固定班制设置(FIXED):`defaultClassId`(number) `workDayClassList`([]number,共7个值代表周日到周六每天的班次id,为0表示当天休息,如[0,1279240003,0,0,0,0,0]表示只有周一上班) -- 排班制设置(TURN):`disableCheckWithoutSchedule`(bool,true=未排班时禁止打卡) `enableEmpSelectClass`(未排班时员工可选班次) `enableScheduleAutoMatch`(未排班时系统自动匹配) - - 循环排班(非必填,不设置则由管理员手动排班):`cycleDays`(number) `startCycleDate`(时间戳,毫秒) `cycleScheduleList`([]对象: cycleName/groupId/isValid(Y/N)/itemList[{classId/className/isValid}]) -- 自由工时设置(NONE):`workDays`([1-7],1=周一7=周日) `freeCheckDayStartMinOffset`(number,距0点分钟数) `freeCheckCoreTime`(最短工作时长,分钟) `freeCheckDemandWorkMinutes`(要求打卡时长,分钟) `freeCheckSettingVO`(对象: freeCheckType(CYCLE上下班交替/MAX_TIME_UPDATE最大时间打卡)/freeWorkDayLackSwitch/freeOnDutyLackMinOffset/freeOffDutyLackMinOffset/delimitOffsetMinutesBetweenDays/freeCheckGapVO{onOffCheckGapMinutes/offOnCheckGapMinutes}) `freeGroupSpecialDayVO`(对象: specialOnDutyDays[]/specialOffDutyDays[]) - -### 创建考勤组 (写场景接口,必须走二次确认流程) -**强制执行流程**:此命令为写操作,Agent 调用时必须遵守以下流程: -1. 先向用户展示待执行操作的完整参数摘要,包括考勤组名称、类型、班次列表等 -2. 使用 `ask_human` 或返回待确认状态,等待用户明确确认 -3. 用户确认后,再传全局 `--yes` 执行命令 - -**禁止未经用户确认直接执行或自动添加 `--yes`。** -``` -Usage: - dws attendance group create [flags] -Example: - dws attendance group create --name "研发考勤组" --type FIXED --group-vo '{"defaultClassId":1170996821,"workDayClassList":[0,1170996821,0,0,0,0,0]}' --timeout 10 - dws attendance group create --name "自由工时分组" --type NONE --timeout 10 -Flags: - --name string 考勤组名称 (必填) - --type string 考勤组类型:FIXED 固定班制 / TURN 排班制 / NONE 自由工时 (必填) - --owner string 考勤组主负责人 userId (可选) - --group-vo string 完整 groupVO JSON 字符串,用于传入复杂子对象 (可选) -``` - -创建一个新的考勤组。`--name` 和 `--type` 必填,`--type` 必须为 FIXED/TURN/NONE 之一。 - -**条件必填(type=FIXED 固定班制时)**:`--group-vo` 必须包含 `workDayClassList`(工作日班次列表,不能为空,共 7 个值,代表周日到周六每天的班次 ID,为 0 表示当天休息)和 `defaultClassId`(默认班次 ID,不能为 null)。 - -`--group-vo` 可用字段说明(与 `group update` 的 `--group-vo` 一致,均可选,只需包含要设置的字段): - -【基础信息】 - id number 考勤组 id(创建时无需传入,由服务端分配) - name string 考勤组名称(必填) - type string 考勤组类型:FIXED(固定班制)/ TURN(排班制)/ NONE(自由工时)(必填) - owner string 考勤组主负责人 userId - managerList []string 考勤组子负责人 userId 列表 - skipHolidays bool 节假日自动排休(只有固定班制和自由工时考勤组生效) - defaultGroup bool 是否默认考勤组 - classIds []number 所选班次 id 列表(只有固定班制和排班制才有班次,自由工时没有) - -【打卡范围与定位】 - trimDistance number 定位允许微调距离(米) - enablePositionOfGps bool 打卡是否允许开启 GPS 定位 - enablePositionOfWifi bool 打卡是否允许开启 Wifi 定位 - enablePositionOfBle bool 打卡是否允许开启蓝牙定位 - enableMacCheck bool 开启 MAC 地址校验 - checkDistanceType string 打卡距离类型:NORMAL(正常) / OUTER_ADDRESS(仅允许在外勤地址打卡) - wifiCompanyId number 允许打卡的 Wifi 公司 ID - -【打卡地址】 - positions []object 打卡地址列表,每个对象字段: - title string 地址名称 - address string 详细地址 - latitude number 纬度 - longitude number 经度 - offset number 该地址允许的打卡范围(米) - -【打卡 Wifi】 - wifis []object 打卡 Wifi 列表,每个对象字段: - ssid string Wifi 名称 - macAddr string MAC 地址 - groupId number 所属考勤组 ID - -【蓝牙设备】 - bleDeviceVOList []object 蓝牙设备列表,每个对象字段: - name string 设备名称 - deviceUid string 设备 ID - sn string 序列号 - productType string 产品类型 - devServId number 设备服务 ID - -【外勤打卡设置】 - enableOutsideCheck bool 是否允许外勤打卡 - openOutsideCameraCheck bool 外勤打卡是否开启拍照 - enableOutsideRemark bool 是否允许外勤备注 - enableOutsideApply bool 外勤打卡是否需审批 - forbidHideOutSideAddress bool 禁止隐藏外勤打卡地址 - enableOutSideUpdateNormalCheck bool 下班时允许外勤卡更新内勤卡 - enableOnDutyNormalUpdateOutsideCheck bool 上班时允许内勤卡更新外勤卡 - outsideCheckApproveMode string 外勤审批模式:NO_NEED_APPROVE / APPROVE_FIRST / CHECK_FIRST / APPROVE_EVERYTIME - outSideCheckApplyType number 外勤打卡申请类型:1 全天 / 2 上班 / 3 下班 - -【打卡方式】 - openCameraCheck bool 是否开启拍照打卡 - enableFaceStrictMode bool 是否开启人脸严格模式 - enableFaceBeauty bool 是否开启人脸美颜 - onlyMachineCheck bool 是否仅允许考勤机打卡 - permitMaxBeaconCount number 允许最大蓝牙信标数量 - disableCheckWhenRest bool 休息日打卡需审批(只在固定班制和排班制生效) - -【固定班制设置(FIXED)】 - defaultClassId number 默认班次 ID - workDayClassList []number 工作日班次列表,共 7 个值代表周日到周六每天的班次 id,为 0 表示当天休息。 - 例如 [0,1279240003,0,0,0,0,0] 表示只有周一上班 - -【排班制设置(TURN)】 - disableCheckWithoutSchedule bool 未排班时禁止打卡 - enableEmpSelectClass bool 未排班时员工可选班次 - enableScheduleAutoMatch bool 未排班时系统自动匹配 - cycleDays number 循环排班周期天数 - startCycleDate number 循环排班开始日期(时间戳,毫秒) - cycleScheduleList []object 循环排班列表: - cycleName string 循环排班名称 - groupId number 考勤组 ID - isValid string 是否生效:Y/N - itemList []object 循环排班明细: - classId number 班次 ID - className string 班次名称 - isValid string 是否生效:Y/N - -【自由工时设置(NONE)】 - workDays []number 工作日,[1-7],1=周一,7=周日 - freeCheckDayStartMinOffset number 距 0 点分钟数 - freeCheckCoreTime number 最短工作时长(分钟) - freeCheckDemandWorkMinutes number 要求打卡时长(分钟) - freeCheckSettingVO object 自由打卡设置: - freeCheckType string 打卡类型:CYCLE(上下班交替)/ MAX_TIME_UPDATE(最大时间打卡) - freeWorkDayLackSwitch bool 工作日缺卡开关 - freeOnDutyLackMinOffset number 上班缺卡分钟数偏移 - freeOffDutyLackMinOffset number 下班缺卡分钟数偏移 - delimitOffsetMinutesBetweenDays number 跨天切割分钟数偏移 - freeCheckGapVO object 打卡间隔: - onOffCheckGapMinutes number 上班到下班最小间隔(分钟) - offOnCheckGapMinutes number 下班到上班最小间隔(分钟) - freeGroupSpecialDayVO object 特殊日期设置: - specialOnDutyDays []number 特殊上班日期 - specialOffDutyDays []number 特殊休息日期 - -当使用 `--group-vo` 传入完整 JSON 时,`--name`、`--type`、`--owner` 仍可覆写 JSON 中的同名字段。由于保存考勤组耗时较久,建议加 `--timeout 10`。 - -> **新建考勤组并同时添加成员的工作流**:`group create` 不支持在创建时直接传入成员列表。若用户需要在新建考勤组后立即添加成员,必须先执行 `group create` 创建考勤组,从返回结果中提取 `groupId`,再执行 `group update-members --group-id --add-users ...` 完成成员添加。 - -### 查询某个人的考勤统计摘要 -``` -Usage: - dws attendance summary [flags] -Example: - dws attendance summary --user USER_ID --date 2026-03-12 --stats-type week - dws attendance summary --user USER_ID --date "2026-03-12 15:00:00" --stats-type month -Flags: - --date string 查询日期, 格式 YYYY-MM-DD 或 YYYY-MM-DD HH:mm:ss (必填) - --stats-type string 统计类型: week 周统计 / month 月统计 (必填) - --user string 钉钉用户 ID (必填) -``` - -`summary` 必须同时传 `--user`、`--date`、`--stats-type`,缺一即报错(如 C0002)。`--stats-type` 只能是 `week`(周统计)或 `month`(月统计)。 - -### 查询考勤组与考勤规则 -``` -Usage: - dws attendance rules [flags] -Example: - dws attendance rules --date 2026-03-14 - dws attendance rules --date "2026-03-14 09:00:00" -Flags: - --date string 考勤日期, 格式 YYYY-MM-DD 或 yyyy-MM-dd HH:mm:ss (必填) -``` - -查询考勤组/考勤规则。例如:我属于哪个考勤组、打卡范围是什么、弹性工时怎么算。 - -### 查询个人规则设置 -``` -Usage: - dws attendance selfsetting get [flags] -Example: - dws attendance selfsetting get --setting-scene checkRemind --user --format json - dws attendance selfsetting get --setting-scene fastCheck --user --format json -Flags: - --setting-scene string 查询设置项: checkRemind/fastCheck/checkResultNotify/lackRemind/personalAttendStatNotify/bossAttendStatNotify (必填) - --user string 查询用户 ID (必填) -``` - -调用 MCP 工具 query_self_setting 查询个人规则设置,包括打卡提醒、极速打卡、打卡结果通知、缺卡提醒、个人考勤统计通知、团队考勤统计通知等设置项。MCP 入参 `userId` 必填;CLI 的 `--user` 也必填,必须显式传入目标用户 ID。认证信息 `corpId` 和 `opUserId` 由当前登录上下文自动注入,无需手动传入。 - -`--setting-scene` 枚举值: -- `checkRemind`: 打卡提醒 -- `fastCheck`: 极速打卡 -- `checkResultNotify`: 打卡结果通知 -- `lackRemind`: 缺卡提醒 -- `personalAttendStatNotify`: 个人考勤统计通知 -- `bossAttendStatNotify`: 团队考勤统计通知 - -返回 `ServiceResult`,包含 `success`、`code`、`message`、`result`。`result` 可能根据 `--setting-scene` 仅返回对应设置项相关字段。常见字段包括: -- `checkRemind`: `checkRemindSetting`、`checkRemindUserOnDuty`、`checkRemindUserOffDuty`、`enableOndutyCheckRemindOfPc`、`enableOffdutyCheckRemindOfPc` -- `fastCheck`: `ondutyCheckType`、`offdutyCheckType`、`ondutyRemindStartMin`、`ondutyRemindEndMin`、`offdutyRemindStartMin`、`offdutyRemindEndMin`、`fastCheckLateNeedConfirm`、`canUpdateOffDuty`、`voiceRemindSwitch`、`vibrationRemindSwitch` -- `checkResultNotify`: `checkResultMsg`, 取值 0 表示关闭, 1 表示开启 -- `lackRemind`: `lackSendTodoMsg`、`lackRemindUser`, 取值 0 表示关闭, `null` 或 1 表示开启 -- `personalAttendStatNotify`: `personDailyReportSwitch`、`personWeekReportType`、`personMonthReportType` -- `bossAttendStatNotify`: `bossPushStartMin`、`bossWeekReportType`、`bossMonthReportType` - -其中周报/月报通知渠道枚举值:0 表示全关闭,1 表示工作通知,2 表示钉邮,3 表示工作通知和钉邮。 - -### 更新保存个人规则设置 (写场景接口,必须走二次确认流程) -**强制执行流程**:此命令为写操作,Agent 调用时必须遵守以下流程: -1. 先向用户展示待执行操作的完整参数摘要,包括目标用户、设置场景、当前值、新值和最终命令参数 -2. 使用 `ask_human` 或返回待确认状态,等待用户明确确认 -3. 用户确认后,再传全局 `--yes` 执行命令 - -**禁止未经用户确认直接执行或自动添加 `--yes`。** - -``` -Usage: - dws attendance selfsetting save [flags] -Example: - # 开启打卡结果通知(Agent 调用时,必须先完成 ask_human 二次确认,确认后再追加 --yes 执行) - dws attendance selfsetting save --setting-scene checkResultNotify --user --check-result-msg 1 --yes --format json - # 更新极速打卡设置(Agent 调用时,必须先完成 ask_human 二次确认,确认后再追加 --yes 执行) - dws attendance selfsetting save --setting-scene fastCheck --user --onduty-check-type 3 --voice-remind-switch=true --yes --format json - # 更新打卡提醒设置(Agent 调用时,必须先完成 ask_human 二次确认,确认后再追加 --yes 执行) - dws attendance selfsetting save --setting-scene checkRemind --user --check-remind-user-on-duty=false \ - --check-remind-setting '{"onDutyRemind":{"openRemind":true,"remindMinutes":10}}' --yes --format json -Flags: - --setting-scene string 更新设置项: checkRemind/fastCheck/checkResultNotify/lackRemind/personalAttendStatNotify/bossAttendStatNotify (必填) - --user string 更新用户 ID (必填) - --check-remind-setting string 打卡提醒 DING 渠道设置 JSON - --check-remind-user-on-duty 打卡提醒工作通知渠道:用户个人上班打卡提醒开关 - --check-remind-user-off-duty 打卡提醒工作通知渠道:用户个人下班打卡提醒开关 - --enable-onduty-check-remind-of-pc PC 端弹窗渠道:上班打卡提醒开关 - --enable-offduty-check-remind-of-pc PC 端弹窗渠道:下班打卡提醒开关 - --onduty-check-type int 上班极速打卡方式:1 提醒打卡,2 不提醒且不自动打卡,3 自动打卡 - --offduty-check-type int 下班极速打卡方式:1 提醒打卡,2 不提醒且不自动打卡,3 自动打卡 - --onduty-remind-start-min int 上班打卡提醒开始时间,单位:分钟 - --onduty-remind-end-min int 上班打卡提醒结束时间,单位:分钟 - --offduty-remind-start-min int 下班打卡提醒开始时间,单位:分钟 - --offduty-remind-end-min int 下班打卡提醒结束时间,单位:分钟 - --fast-check-late-need-confirm 迟到时是否需要二次确认 - --can-update-off-duty 是否允许用户更新下班打卡设置 - --voice-remind-switch 极速打卡提示音开关 - --vibration-remind-switch 极速打卡震动提醒开关 - --check-result-msg int 打卡结果通知开关:0 关闭,1 开启 - --lack-send-todo-msg int 缺卡提醒待办渠道:0 关闭,null 或 1 开启 - --lack-remind-user int 缺卡提醒工作通知渠道:0 关闭,null 或 1 开启 - --person-daily-report-switch int 个人考勤统计日报推送开关:0 关闭,1 开启 - --person-week-report-type int 个人考勤统计周报通知渠道:0 全关闭,1 工作通知,2 钉邮,3 工作通知和钉邮 - --person-month-report-type int 个人考勤统计月报通知渠道:0 全关闭,1 工作通知,2 钉邮,3 工作通知和钉邮 - --boss-push-start-min int 团队考勤统计日报推送开始时间,单位:分钟;-1 表示关闭日报推送 - --boss-week-report-type int 团队考勤统计周报通知渠道:0 全关闭,1 工作通知,2 钉邮,3 工作通知和钉邮 - --boss-month-report-type int 团队考勤统计月报通知渠道:0 全关闭,1 工作通知,2 钉邮,3 工作通知和钉邮 - --yes 用户已确认,跳过交互式确认提示 - Agent 调用时传入前必须已完成 ask_human 二次确认 -``` - -调用 MCP 工具 save_self_setting 更新保存个人规则设置,请求体封装在 `RuleMcpSaveSelfSettingRequest` 中。`settingScene` 必填;MCP 入参 `userId` 必填,CLI 的 `--user` 也必填,必须显式传入目标用户 ID。认证信息 `corpId` 和 `opUserId` 由当前登录上下文自动注入,无需手动传入。 - -`selfsetting save` 必须按 `--setting-scene` 传入对应场景的字段,且至少一个字段有值: -- `checkRemind`: `checkRemindSetting`、`checkRemindUserOnDuty`、`checkRemindUserOffDuty`、`enableOndutyCheckRemindOfPc`、`enableOffdutyCheckRemindOfPc` -- `fastCheck`: `ondutyCheckType`、`offdutyCheckType`、`ondutyRemindStartMin`、`ondutyRemindEndMin`、`offdutyRemindStartMin`、`offdutyRemindEndMin`、`fastCheckLateNeedConfirm`、`canUpdateOffDuty`、`voiceRemindSwitch`、`vibrationRemindSwitch` -- `checkResultNotify`: `checkResultMsg` -- `lackRemind`: `lackSendTodoMsg`、`lackRemindUser` -- `personalAttendStatNotify`: `personDailyReportSwitch`、`personWeekReportType`、`personMonthReportType` -- `bossAttendStatNotify`: `bossPushStartMin`、`bossWeekReportType`、`bossMonthReportType` - -返回 `ServiceResult`,包含 `success`、`code`、`message`、`result`。其中 `result` 为 boolean,表示保存是否成功。 - -#### 强制执行流程:Agent 调用 `selfsetting save` - -`selfsetting save` 是写操作,会修改用户个人规则设置。Agent 调用时 **必须按以下流程执行**,**禁止**在未确认的情况下直接提交: - -1. **识别写操作**:用户表达“更新个人规则设置 / 保存打卡提醒 / 修改极速打卡 / 关闭缺卡提醒 / 开启打卡结果通知 / 设置个人考勤统计通知 / 设置团队考勤统计通知”等意图时,命中 `selfsetting save`。 -2. **收集必要参数**:必须明确 `--user`、`--setting-scene`,以及对应场景下将要修改的字段和值。 -3. **前置查询当前设置**:执行 `dws attendance selfsetting get --setting-scene --user --format json`,获取当前配置,用于确认目标用户和当前值。 -4. **展示待写入数据并等待确认**:向用户展示目标用户、设置场景、要更新的字段、当前值、新值和最终命令参数摘要。必须调用 `ask_human` 或返回待确认状态,并等待用户明确确认。 -5. **用户确认后再执行保存**:**只有用户明确确认后**,才可以追加全局 `--yes` 执行 `dws attendance selfsetting save ... --yes --format json`。 - -确认话术示例: - -```text -即将更新个人考勤规则设置,请确认: -- 用户 ID: -- 设置场景:checkResultNotify -- 修改内容: - - 打卡结果通知(checkResultMsg):关闭 → 开启 - -是否确认执行更新? -``` - -禁止在未获得用户明确确认前执行保存;禁止为了推进流程自动添加全局 `--yes`。即使用户在最初需求中表达“直接改/不用问”,Agent 也必须先通过 `ask_human` 或待确认状态展示完整参数摘要并获得明确确认后,才允许追加 `--yes` 执行。 - -### 查询全局规则设置(仅管理员) -``` -Usage: - dws attendance globalsetting get [flags] -Example: - dws attendance globalsetting get --scope 企业 --setting-scene checkRemind --format json - dws attendance globalsetting get --scope 全公司 --setting-scene bossAttendStatNotify --format json -Flags: - --setting-scene string 查询设置项: checkRemind/fastCheck/checkResultNotify/lackRemind/personalAttendStatNotify/bossAttendStatNotify (必填) - --scope string 全局范围确认,必须明确输入:企业/全公司/所有人(必填) -``` - -调用 MCP 工具 `query_global_setting` 查询全局规则设置,请求体封装在 `RuleMcpQueryGlobalSettingRequest` 中。`settingScene` 必填;CLI 必须通过 `--scope` 明确输入 `企业`、`全公司` 或 `所有人`,用于确认查询的是全局范围。认证信息 `corpId` 和 `opUserId` 由当前登录上下文自动注入,无需手动传入。该接口仅管理员可以调用。 - -返回 `ServiceResult`,包含 `success`、`code`、`message`、`result`。`result` 常见字段包括: -- `checkRemindCorp`: 打卡提醒企业总开关 -- `checkRemindPcCorp`: 打卡提醒 PC 端企业总开关 -- `fastCheckCorp`: 极速打卡企业总开关 -- `enableCheckCertPush`: 打卡结果通知企业总开关 -- `lackRemindCorp`: 缺卡提醒企业总开关 -- `enablePersonalDailyReport`: 个人考勤统计通知日报企业总开关 -- `enablePersonalWeeklyReport`: 个人考勤统计通知周报企业开关,钉邮渠道 -- `enablePersonalWeeklyReportCard`: 个人考勤统计通知周报企业开关,工作通知渠道 -- `enablePersonalMonthlyReport`: 个人考勤统计通知月报企业总开关 -- `bossDailyReportType`: 团队考勤统计通知日报发送渠道类型,0 全关闭,1 开启 -- `bossWeeklyReportType`: 团队考勤统计通知周报发送渠道类型,0 全关闭,1 工作通知,2 钉邮,3 工作通知和钉邮 -- `bossMonthlyReportType`: 团队考勤统计通知月报发送渠道类型,0 全关闭,1 工作通知,2 钉邮,3 工作通知和钉邮 - -### 更新保存全局规则设置(写场景接口,仅管理员,必须走二次确认流程) -**强制执行流程**:此命令为写操作,Agent 调用时必须先向用户展示待执行操作的完整参数摘要,等待用户明确确认后,才能追加 `--yes` 执行。 - -``` -Usage: - dws attendance globalsetting save [flags] -Example: - dws attendance globalsetting save --scope 企业 --setting-scene checkRemind --check-remind-corp=true --yes --format json - dws attendance globalsetting save --scope 全公司 --setting-scene fastCheck --fast-check-corp=false --yes --format json - dws attendance globalsetting save --scope 所有人 --setting-scene bossAttendStatNotify --boss-daily-report-type 1 --boss-weekly-report-type 3 --yes --format json -Flags: - --setting-scene string 更新设置项: checkRemind/fastCheck/checkResultNotify/lackRemind/personalAttendStatNotify/bossAttendStatNotify (必填) - --scope string 全局范围确认,必须明确输入:企业/全公司/所有人(必填) - --check-remind-corp 打卡提醒企业总开关 - --check-remind-pc-corp 打卡提醒 PC 端弹窗企业总开关 - --fast-check-corp 极速打卡企业总开关 - --enable-check-cert-push 打卡结果通知企业总开关 - --lack-remind-corp 缺卡提醒企业总开关 - --enable-personal-daily-report 个人考勤统计通知日报企业总开关 - --enable-personal-weekly-report 个人考勤统计通知周报企业开关,钉邮渠道 - --enable-personal-weekly-report-card 个人考勤统计通知周报企业开关,工作通知渠道 - --enable-personal-monthly-report 个人考勤统计通知月报企业总开关 - --boss-daily-report-type int 团队考勤统计通知日报发送渠道类型:0 全关闭,1 开启 - --boss-weekly-report-type int 团队考勤统计通知周报发送渠道类型:0 全关闭,1 工作通知,2 钉邮,3 工作通知和钉邮 - --boss-monthly-report-type int 团队考勤统计通知月报发送渠道类型:0 全关闭,1 工作通知,2 钉邮,3 工作通知和钉邮 - --yes 用户已确认,跳过交互式确认提示 -``` - -调用 MCP 工具 `save_global_setting` 更新保存全局规则设置,请求体封装在 `RuleMcpSaveGlobalSettingRequest` 中。`settingScene` 必填;CLI 必须通过 `--scope` 明确输入 `企业`、`全公司` 或 `所有人`,用于确认更新的是全局范围。认证信息 `corpId` 和 `opUserId` 由当前登录上下文自动注入,无需手动传入。该接口仅管理员可以调用。 - -`globalsetting save` 必须按 `--setting-scene` 传入对应场景的字段,且至少一个字段有值: -- `checkRemind`: `checkRemindCorp`、`checkRemindPcCorp` -- `fastCheck`: `fastCheckCorp` -- `checkResultNotify`: `enableCheckCertPush` -- `lackRemind`: `lackRemindCorp` -- `personalAttendStatNotify`: `enablePersonalDailyReport`、`enablePersonalWeeklyReport`、`enablePersonalWeeklyReportCard`、`enablePersonalMonthlyReport` -- `bossAttendStatNotify`: `bossDailyReportType`、`bossWeeklyReportType`、`bossMonthlyReportType` - -返回 `ServiceResult`,其中 `result` 为 boolean,表示保存是否成功。 - -### 获取企业考勤字段列表(仅管理员) -``` -Usage: - dws attendance report columns -Example: - dws attendance report columns -``` - -根据操作者的列权限,过滤并返回其有权查看的考勤字段列表。操作者必须是管理员,否则返回权限错误。 - -### 根据字段查询考勤数据(仅管理员) -``` -Usage: - dws attendance report query-data [flags] -Example: - dws attendance report query-data \ - --users userId1,userId2 --columns 1001,1002 --start "2026-03-01 00:00:00" --end "2026-03-31 23:59:59" -Flags: - --columns string 字段 ID 列表, 逗号分隔, 可通过 report columns 获取(必填) - --end string 结束日期, 格式 yyyy-MM-dd HH:mm:ss(必填) - --start string 开始日期, 格式 yyyy-MM-dd HH:mm:ss(必填) - --users string 目标用户 ID 列表, 逗号分隔, 最多 20 人(必填) -``` - -根据字段查询考勤数据,含列权限过滤和用户查看权限校验。--users 最多 20 人,--start 到 --end 不超过 32 天。 - -### 查询用户假期数据(仅管理员) -``` -Usage: - dws attendance report query-leave [flags] -Example: - dws attendance report query-leave \ - --users userId1,userId2 --leave-names 年假,病假 --start "2026-03-01 00:00:00" --end "2026-03-31 23:59:59" -Flags: - --end string 结束日期, 格式 yyyy-MM-dd HH:mm:ss(必填) - --leave-names string 假期类型名称列表, 逗号分隔, 不填则查询所有假期类型(选填) - --start string 开始日期, 格式 yyyy-MM-dd HH:mm:ss(必填) - --users string 目标用户 ID 列表, 逗号分隔, 最多 20 人(必填) -``` - -查询用户假期数据,含用户查看权限校验。--users 最多 20 人,--start 到 --end 不超过 32 天。 - -### 查询当前用户假期规则列表 -``` -Usage: - dws attendance vacation types -Example: - dws attendance vacation types -Flags: - 无 -``` - -调用 MCP 工具 get_leave_types 查询当前用户可用的假期规则列表。例如:年假、事假、病假等假期类型及对应规则。请求体封装在 McpLeaveTypeRequest 中,认证信息(corpId、opUserId)由系统自动注入,无需手动传入。 - -### 查询指定员工假期余额 -``` -Usage: - dws attendance vacation balance [flags] -Example: - dws attendance vacation balance --users userId1,userId2 --leave-code a1b2c3d4-e5f6-7890-abcd-ef1234567890 -Flags: - --users string 目标员工 ID 列表, 逗号分隔 (必填) - --leave-code string 假期规则 code (必填,服务端要求非空,不传返回 INVALID_PARAMS) -``` - -调用 MCP 工具 get_leave_balance_quota 查询指定员工的假期余额。例如:查询某员工年假还剩多少、病假额度等。`--leave-code` 可通过 `vacation types` 获取。**注意:`--help` 虽标"选填",但服务端要求 `leaveCode` 非空,实际必填;不传会返回 `INVALID_PARAMS`(corpId、opUserId、leaveCode、targetUserIds 不能为空)。** 若要一次查所有假期规则余额,必须走 [attendance-vacation.md](./attendance-vacation.md) 工作流脚本逐个规则遍历。认证信息(corpId、opUserId)由系统自动注入。 - -如用户需要“所有假期规则余额 / 导出假期余额列表 / 所有假期规则余额 Excel / 按截图样式导出假期余额”,必须先读取 [attendance-vacation.md](./attendance-vacation.md),再按其中工作流调用脚本生成 Excel。 - -### 查询指定员工假期余额变更记录 -``` -Usage: - dws attendance vacation records [flags] -Example: - dws attendance vacation records --user USER_ID --leave-code a1b2c3d4-e5f6-7890-abcd-ef1234567890 --start 2026-04-01 --end 2026-04-22 -Flags: - --user string 指定查询员工 ID (必填) - --leave-code string 假期规则 code (必填, 不传则无法查询) - --start string 查询开始日期, 格式 YYYY-MM-DD (必填) - --end string 查询结束日期, 格式 YYYY-MM-DD (必填) -``` - -调用 MCP 工具 get_leave_balance_records_v2 查询指定员工的假期余额变更记录。例如:查询某员工年假变更历史、请假扣减记录等。`--leave-code` 必填,可通过 `vacation types` 获取。认证信息(corpId、opUserId)由系统自动注入。 - -### 更新假期规则(写场景接口,必须走二次确认流程) - -**强制执行流程**:此命令为写操作,Agent 调用时必须遵守以下流程: -1. 先向用户展示待执行操作的完整参数摘要 -2. 使用 `ask_human` 或返回待确认状态,等待用户明确确认 -3. 用户确认后,再传 `--user-say-yes=true` 执行命令 - -**禁止未经用户确认直接执行或自动添加 `--user-say-yes=true`。** - -``` -Usage: - dws attendance vacation update-type [flags] -Example: - # 更新假期规则名称 - dws attendance vacation update-type --leave-code a1b2c3d4-e5f6-7890-abcd-ef1234567890 --name "事假(修改版)" - - # 更新假期单位 - dws attendance vacation update-type --leave-code a1b2c3d4-e5f6-7890-abcd-ef1234567890 --unit hour --per-hours 8 - - # 更新适用范围 - dws attendance vacation update-type --leave-code a1b2c3d4-e5f6-7890-abcd-ef1234567890 --visibility-rules '[{"type":"dept","visible":["1","2","3"]}]' -Flags: - --leave-code string 假期编码(必填) - --name string 假期名称(可选) - --unit string 假期单位:day/halfDay/hour(可选) - --paid bool 是否带薪假期(可选,默认 false) - --per-hours int 一天折算小时数(可选) - --when-can-leave string 新员工请假规则:entry/formal(可选) - --visibility-rules string 适用范围规则 JSON 数组(可选) - --user-say-yes 用户已确认,跳过交互式确认提示 - Agent 调用时传 true 前必须完成用户二次确认 -``` - -调用 MCP 工具 save_leave_type 更新已有假期规则。`--leave-code` 必填,指定要更新的假期规则编码。其他字段均为可选,仅需传入要修改的字段。除 `--leave-code` 外,必须至少传入一个更新字段。 - -#### 强制执行流程:Agent 调用 `vacation update-type` - -`vacation update-type` 是写操作,会修改假期规则配置。Agent 调用时 **必须按以下流程执行**,**禁止**在未确认的情况下直接提交: - -1. **识别写操作**:用户表达"更新假期规则 / 修改假期类型 / 编辑假期规则"等意图时,命中 `vacation update-type`。 -2. **收集必要参数**:必须明确 `--leave-code`,以及至少一个更新字段(`--name`、`--unit`、`--paid`、`--per-hours`、`--when-can-leave`、`--visibility-rules`)。 -3. **前置查询当前规则**:需先调用 `vacation types` 确认该规则是否存在及当前配置。 -4. **展示待写入数据并等待确认**:向用户展示假期编码、要更新的字段及新值,并询问是否确认执行。**必须等待用户明确确认**。 -5. **用户确认后再执行保存**:**只有用户明确确认后**,才可以传 `--user-say-yes=true` 执行 `dws attendance vacation update-type ... --format json`。 - -确认话术示例: - -```text -即将更新假期规则,请确认: -- 假期编码:a1b2c3d4-e5f6-7890-abcd-ef1234567890 -- 更新内容: - - 名称:事假 → 事假(修改版) - -是否确认执行更新? -``` - -如用户明确要求跳过确认,可传 `--user-say-yes=true`;否则默认必须等待确认。 - -#### 强制执行流程:Agent 调用 `vacation save-balance` - -`vacation save-balance` 是写操作,会直接替换员工的假期余额(SET 接口,而非 ADD)。Agent 调用时 **必须按以下流程执行**,**禁止**在未确认的情况下直接提交: - -1. **识别写操作**:用户表达"设置假期余额 / 调整假期额度 / 更新假期余额 / 增加假期余额 / 发放年假 / 给员工加年假"等意图时,命中 `vacation save-balance`。 -2. **前置查询当前余额**:必须先调用 `vacation balance --users --leave-code ` 获取当前余额,因为这是 SET 接口,传入值会直接替换而非累加。 -3. **收集必要参数**:必须明确 `--target`(目标员工)、`--leave-code`(假期编码)、`--num`(新余额数量)、`--reason`(变更原因),以及可选参数 `--start/--end`(有效期)。 -4. **计算变更并展示确认**:向用户展示目标员工、假期类型、当前余额、新余额、差额(增加或减少)、变更原因、有效期等,并询问是否确认执行。**必须等待用户明确确认**。 -5. **用户确认后再执行保存**:**只有用户明确确认后**,才可以传 `--user-say-yes=true` 执行 `dws attendance vacation save-balance ... --format json`。 - -确认话术示例: - -**设置余额场景**: -```text -即将设置员工假期余额,请确认: -- 目标员工:张三(user001) -- 假期类型:年假(leaveCode: a1b2c3d4-e5f6-7890-abcd-ef1234567890) -- 当前余额:5 天 -- 新余额:8 天 -- 变更差额:+3 天(增加) -- 变更原因:年度发放 -- 有效期:2024-01-01 至 2024-12-31 - -是否确认执行设置? -``` - -**减少余额场景**: -```text -即将设置员工假期余额,请确认: -- 目标员工:李四(user002) -- 假期类型:年假(leaveCode: a1b2c3d4-e5f6-7890-abcd-ef1234567890) -- 当前余额:10 天 -- 新余额:2 天 -- 变更差额:-8 天(减少) -- 变更原因:请假扣减 - -注意:此操作将大幅减少余额,请确认是否继续? - -是否确认执行设置? -``` - -如用户明确要求跳过确认,可传 `--user-say-yes=true`;否则默认必须等待确认。 - -### 设置员工假期余额 -``` -Usage: - dws attendance vacation save-balance [flags] -Example: - # 设置员工年假余额为8天 - dws attendance vacation save-balance --target user001 \ - --leave-code a1b2c3d4-e5f6-7890-abcd-ef1234567890 --num 8 --reason "年度发放" - - # 设置带有效期的假期余额 - dws attendance vacation save-balance --target user001 \ - --leave-code a1b2c3d4-e5f6-7890-abcd-ef1234567890 --num 8 --reason "年度发放" \ - --start 2024-01-01 --end 2024-12-31 -Flags: - --target string 目标员工工号(必填) - --leave-code string 假期编码(必填) - --num string 余额数量,如8天传8,7.5天传7.5(必填) - --reason string 变更原因,最长100字符(必填) - --start string 有效期开始日期 YYYY-MM-DD(可选) - --end string 有效期结束日期 YYYY-MM-DD(可选) - --user-say-yes 用户已确认,跳过交互式确认提示 - Agent 调用时传 true 前必须完成用户二次确认 -``` - -**重要:这是设置(SET)接口,传入的值会替换当前余额,而非增加(ADD)。**余额数量在传递给 MCP 时会自动乘以 100(如 8 天传 800)。执行前会展示待写入数据,需用户确认后提交。 - -### 查询指定员工的签到记录 -``` -Usage: - dws attendance checkin records [flags] -Example: - dws attendance checkin records \ - --operator-corp-id corp001 --operator-staff-id op001 --staff-ids user001,user002 --start "2026-04-01 00:00:00" --end "2026-04-07 00:00:00" -Flags: - --end string 结束时间, 格式 yyyy-MM-dd HH:mm:ss(必填) - --operator-corp-id string 操作者企业 ID(必填) - --operator-staff-id string 操作者员userID(必填) - --staff-ids string 目标员工userID 列表, 逗号分隔(必填),员工数最多100个人 - --start string 开始时间, 格式 yyyy-MM-dd HH:mm:ss(必填),开始到结束时间限制在7天 -``` - -调用 MCP 工具 get_checkin_record 查询指定员工在一段时间内的签到记录。权限说明:Boss/超级管理员可查看全公司员工,子管理员可查看管理范围内员工,部门主管可查看所管理部门员工,普通员工只能查询自己。接口单次最多返回100条签到记录。 - -## 意图判断 - -用户说"打卡记录/出勤/考勤" → `check record` -用户说"指定用户打卡结果/考勤结果/迟到早退/缺卡异常" → `check result` -用户说"指定用户打卡流水/打卡详情/打卡时间地点/打卡记录详情" → `check record` -用户说"审批单/请假记录/加班记录/出差记录/补卡记录" → `approve list` -用户说"查询排班记录/获取排班详情/查看排班/排班表/导出排班/导出排班表/排班导出/XX考勤组的排班" → **必须先 `read_file` 读取 [attendance-schedule.md](./attendance-schedule.md) 后按其中的「排班查询导出工作流」执行**。 - - **优先级**:只要用户句中含"排班"二字且意图是查看/导出,就走本条,不要走 `attendance-report.md`。"导出排班表" ≠ "导出考勤报表" - - **严禁**绕过 `attendance-schedule.md` 直接调用 `dws attendance schedule get` 命令 - - 脚本自动处理:分批查询(超 20 人自动分批)、userId→姓名转换、classId→班次名称转换、排班表格式 Excel 输出 -用户说"排班/导入排班/安排排班/设置排班/安排班次/调班/换班/排休" → **必须先 `read_file` 读取 [attendance-schedule.md](./attendance-schedule.md) 后按其中的「排班导入工作流」执行**。 - - **严禁**绕过 `attendance-schedule.md` 直接调用 `dws attendance schedule import` 命令 - - **严禁**仅凭命令 `--help` 或本文件中的命令参考自行组装排班命令 - - 该文档定义了:考勤组类型校验(必须为 TURN)、班次校验(必须属于该考勤组)、排班回显确认、错误处理等约束,缺一不可 - - 违反约束的后果:排错班次、排错人员、排班数据覆盖无法回退 -用户说"班次定义/班次列表/有哪些班次/我负责的班次" → `class search`(返回结果已包含全量属性,无需再调 get) -用户说"班次详情/某个班次的具体信息" → `class search --name "..."`(search 直出,直接返回详情)。`class get` 仅在需要按已知 classId 精确查询时使用 -用户说"更新班次/修改班次/班次改名/修改上下班时间" → `class update` -用户说"补卡规则/补卡设置" → `adjustment search`(返回结果已包含全量属性,无需再调 get) -用户说"补卡规则详情/某条补卡规则的具体信息" → `adjustment search --name "..."`(search 直出,已含全量属性)。**不要用 `adjustment get`**:当前服务端对任意 id 都只返回"有效期类型"、拿不到规则明细 -用户说"加班规则/加班设置/加班计算" → `overtime search`(返回结果已包含全量属性,无需再调 get) -用户说"加班规则详情/某条加班规则的具体信息" → `overtime search --name "..."`(search 直出)。如需查已删除/被覆盖的历史记录 → `overtime get` -用户说"考勤组列表/有哪些考勤组" → `group search` -用户说"考勤组详情/全量考勤组信息" → `group get`,若返回结果中含成员 userId 列表,则对每个 userId 调用 `dws contact user get --user-ids `(或等价通讯录查询),在最终输出中展示员工姓名而非裸 userId -用户说"考勤组成员/打卡地址/打卡wifi/打卡蓝牙" → `group filtered-get`(按需查询,节省成本),若返回结果中含成员 userId 列表,则对每个 userId 调用 `dws contact user get --user-ids `(或等价通讯录查询),在最终输出中展示员工姓名而非裸 userId -用户说"更新考勤组成员/添加考勤人员/删除考勤人员/添加考勤部门/删除考勤部门/加入考勤组/移出考勤组/设置无需考勤/取消无需考勤" → `group update-members` -用户说"修改考勤组/更新考勤组配置/考勤组改名/改变考勤组绑定的班次/修改打卡范围/设置考勤组负责人" → `group update` -用户说"创建考勤组/新建考勤组/添加考勤组" → `group create` -用户说"考勤汇总/统计" → `summary` -用户说"考勤组/考勤规则/打卡规则" → `rules` -用户说"查询个人规则设置/查看打卡提醒/查看极速打卡/查看缺卡提醒/查看打卡结果通知/查看个人考勤统计通知/查看团队考勤统计通知" → `selfsetting get` -用户说"更新个人规则设置/保存打卡提醒/修改极速打卡/关闭缺卡提醒/开启打卡结果通知/设置个人考勤统计通知/设置团队考勤统计通知" → `selfsetting save` -用户说"考勤字段/考勤列" → `report columns` -用户说"考勤数据/查询考勤报表数据" → `report query-data`(单次查询场景,非导出) - **导出考勤/导出报表/生成考勤报表/出勤汇总导出/考勤明细导出/迟到早退统计导出/全员考勤数据导出/月度考勤报表/考勤表格/考勤 Excel** → **必须先 `read_file` 读取 [attendance-report.md](./attendance-report.md) 后按其中的工作流执行**。 - - **排除**:如果用户说的是"导出**排班**表"/"导出**排班**"/"**排班**导出",这属于**排班查询导出**,应路由到 [attendance-schedule.md](./attendance-schedule.md),而非本条。判断标准:句中含"排班"二字 → 走排班;不含"排班"或明确说"考勤报表/考勤数据/出勤统计" → 走报表。 - - **严禁**绕过 `attendance-report.md` 直接调用 `python scripts/attendance_report_*.py` 任何脚本 - - **严禁**仅凭脚本 `--help` 或本文件"自动化脚本"表格里的脚本路径就推断参数自行组装命令 - - 该文档定义了:报表类型默认值、列选择策略(`--column-keywords`)、阶段 1 人员获取流程、错误处理、输出摘要规范,缺一不可 - - 违反约束的后果:报表数据不全、列错位、人员遗漏、用户得到错误结果 -用户说"假期数据/年假/病假/请假记录" → `report query-leave` -用户说"假期/我的假期/假期规则" → `vacation types` -用户说"病假余额/年假余额/事假剩余假期"等查询指定假期规则的余额 → `vacation balance` -用户说"导出假期余额/假期余额列表/所有假期规则余额/假期余额 Excel/年假病假调休余额导出"等全部假期规则余额的查询 → **必须先 `read_file` 读取 [attendance-vacation.md](./attendance-vacation.md) 后按其中工作流执行** -用户说"假期变更/假期记录/请假扣减" → `vacation records` -用户说"更新假期规则/修改假期类型/编辑假期规则" → `vacation update-type --leave-code ` -用户说"设置假期余额/调整假期额度/更新假期余额" → 先调用 `vacation balance` 获取当前余额,计算修改后的值,再调用 `vacation save-balance` -用户说"增加假期余额/发放年假/给员工加年假" → 先调用 `vacation balance` 获取当前余额,加上要增加的天数,再调用 `vacation save-balance` 设置新总额度 -用户说"签到/签到记录" → `checkin records` - -## 核心工作流 - -```bash -# 导入排班记录 -dws attendance schedule import --group-id 123456 \ - --schedules '[{"userId":"user001","classId":123,"workDate":"2026-04-22","isRest":"N"}]' \ - --yes --format json - -# 获取排班记录 — 禁止直接调用,必须走 attendance-schedule.md 排班查询导出工作流 -# python scripts/attendance_schedule_export.py --users user001,user002 --start 2026-04-01 --end 2026-04-30 - -# 查询可管理的班次列表 -dws attendance class search --format json -dws attendance class search --query "早班" --filter-type MINE_OWN --format json - -# 查询班次详情 -dws attendance class get --class-id 1170996821 --format json - -# 更新班次 -dws attendance class update --class-id 1170996821 --name "新早班" --timeout 10 --format json -dws attendance class update --class-id 1170996821 --class-vo '{"sections":[{"times":[{"checkType":"OnDuty","checkTime":"08:30","across":0},{"checkType":"OffDuty","checkTime":"17:30","across":0}]}]}' --timeout 10 --format json - -# 查询补卡规则 -dws attendance adjustment search --page 1 --limit 20 --format json -dws attendance adjustment search --query "标准" --page 1 --limit 20 --format json - -# 查询补卡规则详情 -dws attendance adjustment get --adjustment-id 12345 --format json - -# 查询加班规则 -dws attendance overtime search --page 1 --limit 20 --format json - -# 查询加班规则详情 -dws attendance overtime get --overtime-id 12345 --format json - -# 查询考勤组列表 -dws attendance group search --query "研发" --page 1 --limit 20 --format json -dws attendance group search --type FIXED --page 1 --limit 20 --format json - -# 查询考勤组全量信息 -dws attendance group get --group-id 123456 --format json - -# 按需查询考勤组成员/地址/蓝牙/Wifi -dws attendance group filtered-get --group-id 123456 --member --format json -dws attendance group filtered-get --group-id 123456 --position --wifi --format json - -# 更新考勤组成员 -dws attendance group update-members --group-id 123456 --add-users userId1,userId2 --timeout 10 --format json -dws attendance group update-members --group-id 123456 --remove-users userId1 --timeout 10 --format json -dws attendance group update-members --group-id 123456 --add-depts deptId1 --remove-users userId2 --timeout 10 --format json - -# 更新考勤组配置 -dws attendance group update --group-id 123456 --name "研发考勤组" --timeout 10 --format json -dws attendance group update --group-id 123456 --classIds '[1374234767]' --timeout 10 --format json -dws attendance group update --group-id 123456 --group-vo '{"positions":[{"title":"总部","address":"北京市","latitude":39.9,"longitude":116.4,"offset":200}]}' --timeout 10 --format json - -# 创建考勤组 -dws attendance group create --name "研发考勤组" --type FIXED --group-vo '{"defaultClassId":1170996821,"workDayClassList":[0,1170996821,0,0,0,0,0]}' --timeout 10 --format json -dws attendance group create --name "自由工时分组" --type NONE --timeout 10 --format json - -# 查看考勤统计摘要 -dws attendance summary --user --date 2026-03-12 --stats-type week --format json - -# 查看考勤组和规则 -dws attendance rules --date 2026-03-14 --format json - -# 查看指定用户的打卡提醒设置 -dws attendance selfsetting get --setting-scene checkRemind --user --format json - -# 查看指定用户的极速打卡设置 -dws attendance selfsetting get --setting-scene fastCheck --user --format json - -# 开启指定用户的打卡结果通知 -dws attendance selfsetting save --setting-scene checkResultNotify --user --check-result-msg 1 --format json - -# 更新指定用户的极速打卡设置 -dws attendance selfsetting save --setting-scene fastCheck --user \ - --onduty-check-type 3 --voice-remind-switch=true --format json - -# 获取考勤字段列表(管理员) -dws attendance report columns --format json - -# 根据字段查询考勤数据(管理员) -dws attendance report query-data --users userId1,userId2 \ - --columns 1001,1002 --start "2026-03-01 00:00:00" --end "2026-03-31 23:59:59" --format json - -# 查询用户假期数据(管理员) -dws attendance report query-leave --users userId1,userId2 \ - --leave-names 年假,病假 --start "2026-03-01 00:00:00" --end "2026-03-31 23:59:59" --format json - -# 查看假期规则列表 -dws attendance vacation types --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 --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 \ - --name "事假(修改版)" --format json - -# 更新假期单位 -dws attendance vacation update-type --leave-code a1b2c3d4-e5f6-7890-abcd-ef1234567890 \ - --unit hour --per-hours 8 --format json - -# 更新适用范围 -dws attendance vacation update-type --leave-code a1b2c3d4-e5f6-7890-abcd-ef1234567890 \ - --visibility-rules '[{"type":"dept","visible":["1","2","3"]}]' --format json - -# 设置员工假期余额完整流程 -# 1. 查询当前余额 -dws attendance vacation balance --users user001 \ - --leave-code a1b2c3d4-e5f6-7890-abcd-ef1234567890 --format json - -# 2. 根据查询结果计算新值(如当前5天,要设置为8天) - -# 3. 执行设置(SET操作,会替换当前余额) -dws attendance vacation save-balance --target user001 \ - --leave-code a1b2c3d4-e5f6-7890-abcd-ef1234567890 \ - --num 8 --reason "年度发放" --start 2024-01-01 --end 2024-12-31 --format json - -# 增加员工假期余额完整流程(ADD场景) -# 1. 查询当前余额(假设返回5天) -dws attendance vacation balance --users user001 \ - --leave-code a1b2c3d4-e5f6-7890-abcd-ef1234567890 --format json - -# 2. 计算增加后的新值(5 + 3 = 8天) - -# 3. 设置新总额度 -dws attendance vacation save-balance --target user001 \ - --leave-code a1b2c3d4-e5f6-7890-abcd-ef1234567890 \ - --num 8 --reason "绩效奖励发放3天" --format json - -# 查询签到记录 -dws attendance checkin records --operator-corp-id corp001 --operator-staff-id op001 --staff-ids user001,user002 \ - --start "2026-04-01 00:00:00" --end "2026-04-07 00:00:00" --format json -``` - -## 上下文传递表 -| 操作 | 提取 | 用于 | -|------|------|------| -| `contact user get-self` | `userId` | summary 的 --user | -| `rules` | `groupId` | schedule import 的 --group-id | -| `schedule import` | `classId` | schedule import 的 schedules 中的 classId | -| `contact user search` | `userId` | schedule import/get 的 userId | - -| `contact user get-self` / `contact user search` | `userId` | summary 的 --user, vacation records 的 --user;selfsetting get/save 的 --user(必填) | -| 当前登录上下文 | `corpId`, `opUserId` | selfsetting get/save 自动补齐 MCP 入参, CLI 不需要传 `--corp-id` / `--op-user` | -| `vacation types` | `leaveCode` | vacation balance 的 --leave-code, vacation records 的 --leave-code | -## 注意事项 -**Agent 使用引导**: -- 执行 vacation 子命令前,**必须先调用** `dws attendance vacation --help` 查看完整子命令列表和参数说明 -- 新增命令可能不在 Agent 缓存中,直接猜测命令会失败 -- 正确流程:查看帮助 → 选择命令 → 查看命令详细参数(如 `dws attendance vacation update-type --help`)→ 执行 - -- `record get` 的 `--date` 格式: YYYY-MM-DD(如 `2026-03-08`),CLI 自动转换为毫秒时间戳 -- `shift list` 查询班次信息,`--start/--end` 使用 YYYY-MM-DD 格式,间隔不超过 7 天 -- `schedule import` 导入排班记录 — 必须通过 [attendance-schedule.md](./attendance-schedule.md) 排班导入工作流执行 -- `schedule get` 查询排班记录 — 必须通过 [attendance-schedule.md](./attendance-schedule.md) 排班查询导出工作流执行(脚本自动分批、姓名转换、排班表格式导出) -- `schedule import` 是写操作,AI 调用时必须先展示导入摘要并引导用户二次确认;用户明确确认后才允许执行。用户明确要求跳过确认或命令包含全局 `--yes` 时,可跳过二次确认 -- `class search` 所有参数均为可选,不填时返回全部可管理班次(默认第 1 页,每页 20 条) -- **概念区分**:班次是员工当天打卡安排;排班是为排班制考勤组导入的排班记录;班次定义是考勤管理员创建的工作时间规则 -- `class get` 的 `--class-id` 必填,班次 ID 可从 `class search` 结果中提取 -- `class search` 返回结果已包含全量属性,无需再调用 `class get`;`class get` 仅在需要按已知 classId 精确查询时使用 -- `class update` 的 `--class-id` 必填,其余均可选,仅需对要修改的字段赋值,未传字段会自动从已有配置补充;由于保存班次耗时较久,建议加 `--timeout 10` -- `adjustment search` 返回结果已包含全量属性;`adjustment get` 当前服务端对任意 id 都只返回"有效期类型"、无规则明细,不可用,补卡规则详情一律看 `adjustment search` 返回 -- `overtime search` 返回结果已包含全量属性,无需再调用 `overtime get`;`overtime get` 仅在需要按已知 overtimeId 查询时使用(包括已删除/被覆盖的历史记录) -- `adjustment search` / `overtime search` 分页字段为 `--page` 和 `--limit`,不传时自动使用默认值 1 / 20 -- `group search` 的分页字段为 `--page` 和 `--limit`,不传时自动使用默认值 1 / 20 -- `group get` 的 `--group-id` 必填,返回考勤组全量字段;如仅需成员/地址/蓝牙/Wifi,优先使用 `group filtered-get` 节省成本。**返回结果中如含成员 userId 列表,必须调用 `dws contact user get --ids ,,...`(支持逗号分隔传多个 ID),将 userId 转换为员工姓名后再输出;不得直接输出裸 userId。** -- `group update-members` 的 --group-id 必填,其余参数均可选,但至少需传一个变更项;各参数每次最多 20 个 ID;`--add-extra-users` 和 `--remove-extra-users` 操作的是"无需考勤"豁免名单,不影响考勤组主成员列表 -- `group update` 的 --group-id 必填,其余均可选,至少需指定一个修改项;仅需对要修改的字段赋値,未传字段会从已有配置自动补充;修改打卡地址/wifi/蓝牙等复杂子对象时用 `--group-vo` 传入完整 JSON;`--group-vo` 与单字段 flag 同时传入时单字段 flag 优先级更高 -- `group create` 的 `--name` 和 `--type` 必填,`--type` 必须为 FIXED/TURN/NONE 之一;type=FIXED 时 `--group-vo` 必须包含 `workDayClassList`(非空)和 `defaultClassId`(非 null);由于保存考勤组耗时较久,建议加 `--timeout 10` -- `group filtered-get` 的 `--group-id` 必填,`--member/--position/--wifi/--bles` 均可选,默认 false。**返回结果中如含成员 userId 列表,必须调用 `dws contact user get --ids ,,...`(支持逗号分隔传多个 ID),将 userId 转换为员工姓名后再输出;不得直接输出裸 userId。** -- `summary` 必须同时传 `--user`、`--date`、`--stats-type`(week 周统计 / month 月统计),三者缺一即报错;`--date` 支持 `YYYY-MM-DD` 或 `yyyy-MM-dd HH:mm:ss` -- `rules` 的 `--date` 支持 YYYY-MM-DD 或 yyyy-MM-dd HH:mm:ss 两种格式 -- `selfsetting get/save` 的 `--setting-scene` 必须是 `checkRemind`、`fastCheck`、`checkResultNotify`、`lackRemind`、`personalAttendStatNotify`、`bossAttendStatNotify` 之一 -- `selfsetting get/save` 的 MCP 入参 `userId` 为必填;CLI 的 `--user` 也必填,必须显式传入目标用户 ID -- `selfsetting save` 必须传入与 `--setting-scene` 对应的至少一个设置字段;不同场景的字段不能混用 -- `selfsetting save` 是敏感写操作,AI 调用时必须先执行 `selfsetting get` 查询当前值,并向用户展示目标用户、设置场景、修改字段、“当前值 → 新值”和最终命令参数摘要;必须调用 `ask_human` 或返回待确认状态等待用户明确确认;用户确认后才允许追加全局 `--yes` 执行保存。禁止未经确认直接执行或自动添加 `--yes` -- `selfsetting get/save` 不需要传 `--corp-id` / `--op-user`,`corpId` 和 `opUserId` 由当前登录上下文自动补齐 -- `report columns` 无需额外参数,corpId 和 operatorId 由系统自动传入 -- `report query-data` 和 `report query-leave` 的 `--start/--end` 格式: yyyy-MM-dd HH:mm:ss,间隔不超过 32 天,最多 20 人 -- report 系列接口仅对管理员开放 -- 用户 ID 需从 `contact user get-self` 或 `aisearch person` 获取 -- 考勤组 ID 需从 `rules` 命令返回结果中获取 -- `vacation types` 无需任何参数,认证信息自动注入 -- `vacation balance` 的 `--users` 为目标员工 ID 列表,逗号分隔;`--leave-code` 服务端要求非空、实际必填(`--help` 标"选填"不准),不传返回 `INVALID_PARAMS`,可通过 `vacation types` 获取 -- `vacation records` 的 `--start/--end` 使用 YYYY-MM-DD 格式,CLI 自动转换为毫秒时间戳;`--leave-code` 必填(不传无法查询),底层 MCP 工具为 `get_leave_balance_records_v2` -- `vacation balance` 和 `vacation records` 的认证参数(corpId、opUserId)由系统自动注入,无需手动传入 -- `vacation update-type` 的 `--leave-code` 必填;其他字段均为可选,但至少需传一个更新字段 -- `vacation update-type` 的 `--visibility-rules` 为 JSON 数组字符串,格式:`[{"type":"dept","visible":["1","2","3"]}]`,type 可取值 staff/label/dept -- `vacation save-balance` 是 **SET 接口**而非 ADD 接口:传入值会直接替换当前余额,而非累加 -- `vacation save-balance` 的 `--num` 输入为实际天数(如 8 或 7.5),内部会乘以 100 传给 MCP(如 800 或 750) -- `vacation save-balance` 执行前需先调用 `vacation balance` 查询当前余额,再计算新值,避免误操作 -- `vacation save-balance` 的 `--start/--end` 使用 YYYY-MM-DD 格式,CLI 自动转换为毫秒时间戳 -- `vacation update-type` 和 `vacation save-balance` 执行前会展示待写入数据,需用户输入 yes/y 确认后提交 -- 假期编码为 UUID 格式字符串,可通过 `vacation types` 命令查询获取 - -### 改签打卡记录 - -**命令**: `dws attendance boss-check` - -**功能**: 改签打卡记录,管理员可修改员工的打卡时间、打卡结果等信息。 - -**强制执行流程**: 此命令为写操作,Agent 调用时必须遵守以下流程: -1. 先向用户展示待执行操作的完整参数摘要 -2. 使用 `ask_human` 或返回待确认状态,等待用户明确确认 -3. 用户确认后,再传 `--user-say-yes=true` 执行命令 -4. **禁止**未经用户确认直接执行或自动添加 `--user-say-yes=true` - -**参数**: -| 参数 | 必填 | 说明 | 来源 | -|------|------|------|------| -| --plan-id | y* | 排班ID(与 --result-id 二选一) | `dws attendance schedule get` 返回的 `id` 字段 | -| --result-id | y* | 打卡结果ID(与 --plan-id 二选一,优先使用) | **暂不支持**(record get 未返回此字段) | -| --time | n | 新打卡时间,格式 yyyy-MM-dd HH:mm | - | -| --result | n | 打卡结果枚举值 | - | -| --absent-min | n | 缺勤时长(分钟) | - | -| --remark | n | 备注,最长500字符 | - | -| --user-say-yes | n | 用户已确认,跳过交互式确认提示 | - | - -**获取 planId 步骤**: -1. 查询排班记录:`dws attendance schedule get --userIdList USER_ID --workDateBegin DATE --workDateEnd DATE` -2. 从返回结果中找到对应打卡类型(OnDuty=上班,OffDuty=下班)的记录 -3. 使用该记录的 `id` 字段作为 `--plan-id` 参数 -4. 示例返回:`{"id": 948964045503, "checkType": "OffDuty", ...}` → `--plan-id 948964045503` - -**打卡结果枚举值**: -- Normal: 正常 -- TimesResultA: 迟到 -- TimesResultB: 早退 -- TimesResultC: 缺卡 -- TimesResultD: 迟到+早退 -- TimesResultE: 缺卡+早退 -- TimesResultF: 迟到+缺卡 - -**示例**: -```bash -# 步骤1:获取排班记录的 planId -dws attendance schedule get --userIdList 03642229451220076 --workDateBegin 2026-05-13 --workDateEnd 2026-05-13 -f json - -# 步骤2:使用返回的 id 作为 --plan-id 改签 -# 假设返回 id: 948964045503 (OffDuty 下班打卡) -dws attendance boss-check --plan-id 948964045503 --result Normal --user-say-yes - -# 同时修改打卡时间 -dws attendance boss-check --plan-id 948964045503 --time "2026-05-13 18:00" --result Normal --yes -``` - -## 自动化脚本 - -| 脚本 | 场景 | 用法 | -|------|------|------| -| [attendance_my_record.py](../../scripts/attendance_my_record.py) | 查看我今天/指定日期的考勤记录 | `python attendance_my_record.py today` | -| [attendance_team_shift.py](../../scripts/attendance_team_shift.py) | 查询团队成员本周排班 | `python attendance_team_shift.py --users userId1,userId2` | -| [attendance_report_common.py](../../scripts/attendance_report_common.py) | 考勤报表导出公共模块(不可单独执行) | — | -| [attendance_vacation_balance.py](../../scripts/attendance_vacation_balance.py) | 假期余额列表 Excel 导出 | **禁止直接调用**,必须先读 [attendance-vacation.md](./attendance-vacation.md) 按工作流执行 | -| attendance_report_detail.py | 考勤报表 — **明细粒度** | **禁止直接调用**,必须先读 [attendance-report.md](./attendance-report.md) 按工作流执行 | -| attendance_report_monthly.py | 考勤报表 — **月度汇总** | **禁止直接调用**,必须先读 [attendance-report.md](./attendance-report.md) 按工作流执行 | -| attendance_report_daily.py | 考勤报表 — **每日统计** | **禁止直接调用**,必须先读 [attendance-report.md](./attendance-report.md) 按工作流执行 | -| attendance_schedule_import.py | 排班导入(含校验、回显、执行) | **禁止直接调用**,必须先读 [attendance-schedule.md](./attendance-schedule.md) 按工作流执行 | -| attendance_schedule_export.py | 排班查询导出(分批查询、排班表 Excel) | **禁止直接调用**,必须先读 [attendance-schedule.md](./attendance-schedule.md) 按工作流执行 | - -> 说明: -> - `attendance_report_*.py` 三个脚本由 [attendance-report.md](./attendance-report.md) 工作流编排使用,自动处理 `--users` 超过 20 人分批、`--start/--end` 超过 32 天按月切片,输出 `attendance_report___<粒度>.xlsx` -> - `attendance_schedule_import.py` 由 [attendance-schedule.md](./attendance-schedule.md) 排班导入工作流编排使用,自动处理考勤组校验、班次校验、排班回显确认 -> - `attendance_schedule_export.py` 由 [attendance-schedule.md](./attendance-schedule.md) 排班查询导出工作流编排使用,自动处理分批查询(超 20 人自动分批)、userId→姓名转换、classId→班次名称转换、输出排班表格式 Excel - -## 严格约束 -- 不要凭历史记忆复用 userId / classId / leaveCode / groupId / instanceId 等任何 ID,每次必须从当次命令返回值中提取 -- 不要猜测命令,先查询明确命令,不要使用记忆中未经证实(即不存在于本.md文件中的指令)或执行失败(指令传参有误或flag不正确)的的命令 -- 制定 plan 并自我审查,严格按 plan 执行 -- 涉及超过 3 条记录的聚合(求和、分组、计数、排序、跨字段计算)时必须落 Python 脚本处理,禁止用大模型口算或目测。脚本里如果用到 mcp,先提前看下 mcp 返回的结构,避免执行异常 -- 遇到时长字段时,注意区分单位是秒、分钟还是小时 -- 遇到意图不清晰的场景不要猜测,主动询问用户明确意图 -- 如果查询结果很多时,不要自作主张省略,必须明确告知用户或者用表格或展示所有。 +> 渐进式兼容入口:新任务优先从 [考勤能力索引](./attendance-index.md) 选择专题。 + +| 任务 | 参考 | +|---|---| +| 日期范围与签到优先路由 | [attendance-routing.md](./attendance-routing.md) | +| 核心命令 | [attendance-commands.md](./attendance-commands.md) | +| 排班、报表、假期 | [排班](./attendance-schedule.md) · [报表](./attendance-report.md) · [假期](./attendance-vacation.md) | +| 意图与流程 | [意图判断](./attendance-intent.md) · [核心工作流](./attendance-workflow.md) | +| 上下文、注意事项、脚本、约束 | [上下文](./attendance-context.md) · [注意事项](./attendance-notes.md) · [脚本](./attendance-scripts.md) · [严格约束](./attendance-constraints.md) | diff --git a/skills/multi/dingtalk-attendance/references/intent-guide.md b/skills/multi/dingtalk-attendance/references/intent-guide.md new file mode 100644 index 00000000..7a6bc6b8 --- /dev/null +++ b/skills/multi/dingtalk-attendance/references/intent-guide.md @@ -0,0 +1,7 @@ +# attendance 局部意图消歧 + +本文件从单 Skill `intent-guide.md` 拆分而来,仅保留与本产品相关的跨产品消歧规则。 + +| 用户说... | 真实意图 | 应该用 | 不要用 | 理由 | +|---|---|---|---|---| +| "查/提交 请假/加班/外出/出差/补卡 审批单" | 考勤业务审批单 | `attendance approve`(查询走 `attendance approve list`;提交走 `attendance approve templates --type leave\ | overtime\ | repair-check\ | diff --git a/skills/multi/dingtalk-attendance/scripts/attendance_my_record.py b/skills/multi/dingtalk-attendance/scripts/attendance_my_record.py index 88f6cd97..89898507 100644 --- a/skills/multi/dingtalk-attendance/scripts/attendance_my_record.py +++ b/skills/multi/dingtalk-attendance/scripts/attendance_my_record.py @@ -48,25 +48,7 @@ def get_my_user_id(dry_run: bool = False) -> Optional[str]: return '' if not data or not isinstance(data, dict): return None - # 兼容两种结构: - # 1) 顶层直接给 userId - # 2) {result: [{orgEmployeeModel: {userId}}]} 包裹 - uid = data.get('userId') or data.get('userid') - if uid: - return uid - inner = data.get('result') - if isinstance(inner, dict): - inner = [inner] - if isinstance(inner, list): - for item in inner: - if not isinstance(item, dict): - continue - emp = item.get('orgEmployeeModel') - if isinstance(emp, dict) and emp.get('userId'): - return emp['userId'] - if item.get('userId'): - return item['userId'] - return None + return data.get('userId') or data.get('userid') def main(): diff --git a/skills/multi/dingtalk-attendance/scripts/attendance_report_checkin.py b/skills/multi/dingtalk-attendance/scripts/attendance_report_checkin.py index 6b362c7c..4b5f86be 100644 --- a/skills/multi/dingtalk-attendance/scripts/attendance_report_checkin.py +++ b/skills/multi/dingtalk-attendance/scripts/attendance_report_checkin.py @@ -3,7 +3,7 @@ 考勤报表导出 — 签到记录粒度 [AI Agent 强制门禁] 调用本脚本前必须先阅读: - dingtalk-workspace/references/products/attendance-report.md + references/attendance-report.md 本脚本是"签到报表导出工作流"的执行末端,工作流完整定义在 attendance-report.md。 @@ -142,7 +142,7 @@ def parse_args() -> argparse.Namespace: parser = argparse.ArgumentParser( description=( "导出签到报表 — 签到记录粒度。" - "[强制] AI Agent 必须先读 references/products/attendance-report.md 再调用本脚本。" + "[强制] AI Agent 必须先读 references/attendance-report.md 再调用本脚本。" ), ) parser.add_argument("--users", required=True, diff --git a/skills/multi/dingtalk-attendance/scripts/attendance_report_common.py b/skills/multi/dingtalk-attendance/scripts/attendance_report_common.py index 34dce90b..da292ad4 100644 --- a/skills/multi/dingtalk-attendance/scripts/attendance_report_common.py +++ b/skills/multi/dingtalk-attendance/scripts/attendance_report_common.py @@ -4,7 +4,7 @@ [AI Agent 强制门禁] 本模块不可单独执行,且任何调用方脚本 (attendance_report_detail/monthly/daily.py)执行前都必须先阅读: - dingtalk-workspace/references/products/attendance-report.md + references/attendance-report.md 工作流细节、报表类型判断、人员获取、列选择、错误处理等约束 全部在 attendance-report.md,禁止凭本脚本源码或 --help 自行组装命令。 diff --git a/skills/multi/dingtalk-attendance/scripts/attendance_report_daily.py b/skills/multi/dingtalk-attendance/scripts/attendance_report_daily.py index 05e82732..fc07e49e 100644 --- a/skills/multi/dingtalk-attendance/scripts/attendance_report_daily.py +++ b/skills/multi/dingtalk-attendance/scripts/attendance_report_daily.py @@ -3,7 +3,7 @@ 考勤报表导出 — 每日统计粒度 ⛔ 【AI Agent 强制门禁】调用本脚本前必须先阅读: - dingtalk-workspace/references/products/attendance-report.md + references/attendance-report.md 本脚本仅是"考勤报表导出工作流"的执行末端,工作流完整定义在 attendance-report.md, 包含但不限于: @@ -113,7 +113,7 @@ def parse_args() -> argparse.Namespace: p = argparse.ArgumentParser( description=( "导出考勤报表 — 每日统计粒度。" - "⛔ AI Agent 必须先读 references/products/attendance-report.md 再调用本脚本," + "⛔ AI Agent 必须先读 references/attendance-report.md 再调用本脚本," "禁止凭 --help 或脚本路径自行拼命令。" ), ) diff --git a/skills/multi/dingtalk-attendance/scripts/attendance_report_detail.py b/skills/multi/dingtalk-attendance/scripts/attendance_report_detail.py index c3086ad7..e6a94cfb 100644 --- a/skills/multi/dingtalk-attendance/scripts/attendance_report_detail.py +++ b/skills/multi/dingtalk-attendance/scripts/attendance_report_detail.py @@ -8,7 +8,7 @@ [AI Agent 强制门禁] 调用本脚本前必须先阅读: - dingtalk-workspace/references/products/attendance-report.md + references/attendance-report.md 本脚本仅是"考勤报表导出工作流"的执行末端,工作流完整定义在 attendance-report.md, 包含但不限于: @@ -88,7 +88,7 @@ def parse_args() -> argparse.Namespace: p = argparse.ArgumentParser( description=( "导出考勤报表 — 明细粒度(打卡记录)。" - "[强制] AI Agent 必须先读 references/products/attendance-report.md 再调用本脚本," + "[强制] AI Agent 必须先读 references/attendance-report.md 再调用本脚本," "禁止凭 --help 或脚本路径自行拼命令。" ), ) diff --git a/skills/multi/dingtalk-attendance/scripts/attendance_report_monthly.py b/skills/multi/dingtalk-attendance/scripts/attendance_report_monthly.py index b9323b2b..61fdb684 100644 --- a/skills/multi/dingtalk-attendance/scripts/attendance_report_monthly.py +++ b/skills/multi/dingtalk-attendance/scripts/attendance_report_monthly.py @@ -3,7 +3,7 @@ 考勤报表导出 — 月度汇总粒度 [AI Agent 强制门禁] 调用本脚本前必须先阅读: - dingtalk-workspace/references/products/attendance-report.md + references/attendance-report.md 本脚本仅是"考勤报表导出工作流"的执行末端,工作流完整定义在 attendance-report.md, 包含但不限于: @@ -108,7 +108,7 @@ def parse_args() -> argparse.Namespace: p = argparse.ArgumentParser( description=( "导出考勤报表 — 月度汇总粒度。" - "[强制] AI Agent 必须先读 references/products/attendance-report.md 再调用本脚本," + "[强制] AI Agent 必须先读 references/attendance-report.md 再调用本脚本," "禁止凭 --help 或脚本路径自行拼命令。" ), ) diff --git a/skills/multi/dingtalk-attendance/scripts/attendance_schedule_export.py b/skills/multi/dingtalk-attendance/scripts/attendance_schedule_export.py index 279ce6c8..db2cac02 100644 --- a/skills/multi/dingtalk-attendance/scripts/attendance_schedule_export.py +++ b/skills/multi/dingtalk-attendance/scripts/attendance_schedule_export.py @@ -3,7 +3,7 @@ 考勤排班查询导出脚本 [AI Agent 强制门禁] 本脚本执行前必须先阅读: - dingtalk-workspace/references/products/attendance-schedule.md + references/attendance-schedule.md 职责: 1. 分批查询排班记录(支持大量用户自动分批) diff --git a/skills/multi/dingtalk-attendance/scripts/attendance_schedule_import.py b/skills/multi/dingtalk-attendance/scripts/attendance_schedule_import.py index 5d914e07..2ca45322 100644 --- a/skills/multi/dingtalk-attendance/scripts/attendance_schedule_import.py +++ b/skills/multi/dingtalk-attendance/scripts/attendance_schedule_import.py @@ -3,7 +3,7 @@ 考勤排班导入脚本 [AI Agent 强制门禁] 本脚本执行前必须先阅读: - dingtalk-workspace/references/products/attendance-schedule.md + references/attendance-schedule.md 排班工作流、参数校验、班次校验、回显确认等约束全部在 attendance-schedule.md,禁止凭本脚本源码或 --help 自行组装命令。 diff --git a/skills/multi/dingtalk-attendance/scripts/attendance_vacation_balance.py b/skills/multi/dingtalk-attendance/scripts/attendance_vacation_balance.py index 675368e7..dcb1f1c0 100644 --- a/skills/multi/dingtalk-attendance/scripts/attendance_vacation_balance.py +++ b/skills/multi/dingtalk-attendance/scripts/attendance_vacation_balance.py @@ -3,7 +3,7 @@ 假期余额 Excel 导出脚本。 [AI Agent 强制门禁] 调用本脚本前必须先阅读: - dingtalk-workspace/references/products/attendance-vacation.md + references/attendance-vacation.md 本脚本负责: 1. 通过 dws attendance vacation types 获取假期规则列表,用于确定列顺序 @@ -86,7 +86,7 @@ def parse_args() -> argparse.Namespace: parser = argparse.ArgumentParser( description=( "导出假期余额 Excel。AI Agent 必须先读 " - "references/products/attendance-vacation.md 再调用本脚本。" + "references/attendance-vacation.md 再调用本脚本。" ), ) parser.add_argument("--users", required=True, help="userId 或 deptId 列表,逗号分隔") diff --git a/skills/multi/dingtalk-calendar/SKILL.md b/skills/multi/dingtalk-calendar/SKILL.md index fb965c78..49c12cf4 100644 --- a/skills/multi/dingtalk-calendar/SKILL.md +++ b/skills/multi/dingtalk-calendar/SKILL.md @@ -1,6 +1,6 @@ --- name: dingtalk-calendar -description: 钉钉日历与会议室。Use when 用户说 约会议/查日程/订会议室/查闲忙/加参会人/改期/取消会议/今天的日程/本周日程/共同空闲。视频会议发起/邀请入会/会中控制当前 CLI 不支持,应提示在钉钉客户端操作。Distinct from dingtalk-minutes(听记)、dingtalk-todo(待办)。命令前缀:dws calendar。 +description: 钉钉日历与会议室。Use when 用户说 约会议/查日程/订会议室/查闲忙/加参会人/改期/取消会议/今天的日程/本周日程/共同空闲。视频会议发起/邀请入会/会中控制当前 CLI 不支持,应提示在钉钉客户端操作;AI 听记走 dingtalk-minutes,待办任务走 dingtalk-todo。命令前缀:dws calendar。 cli_version: ">=0.2.14" metadata: category: product @@ -14,13 +14,12 @@ metadata: > 🧪 **EXPERIMENTAL · 试验版 / Preview** — multi 模式当前未达 stable 标准。全部 dingtalk-* skill 已通过 dispatch verifier,但接口、命名、跨 skill 引用后续可能调整;生产 / 共享环境请优先使用 mono 模式(`dws skill setup --mode mono`)。问题请提 issue 反馈。 -> **PREREQUISITE:** Read the `dws-shared` skill first for auth, global flags, product routing, URL preflight, error codes, and safety rules. The `dws` binary must be on PATH. +## 前置条件 — 执行操作前必读 + +> **`use_skill(dws-shared)`** — 认证、全局参数(`--format json` / `--yes`)、错误码、URL 模板、跨产品消歧、安全规则与 capability 边界。**执行任何 `dws` 命令前先读;** 单产品的清晰命令可直接用本 skill。 -> ⚠️ **命令可用性以当前 dws 二进制为准**。服务发现已下线,本文档随内置 skill 发布;如果 `dws --help` 不存在,说明当前版本未暴露该命令。若命令存在但调用失败,请按错误中的 endpoint 或 tool 提示确认静态端点目录和后端工具注册。实际调用前可用 `dws --help` 或 `--dry-run` 验证。 - - > 命令参考:[calendar.md](references/calendar.md);剧本:[03-meeting.md](references/03-meeting.md)。 ## 意图表 @@ -30,17 +29,52 @@ metadata: | "今天 / 明天 / 本周日程" | `python scripts/calendar_today_agenda.py [today\|tomorrow\|week]` | | "约会议(含参会人 + 会议室)" | `python scripts/calendar_schedule_meeting.py --title "<主题>" --start "<起>" --end "<止>" [--users ] [--book-room]` | | "多人共同空闲" | `python scripts/calendar_free_slot_finder.py --users --date ` | -| "查闲忙" | `dws calendar busy search --users --start "" --end ""` | +| "查闲忙" | `dws calendar event list --start "" --end ""` | | "加参会人" / "订房" / "取消" | `dws calendar attendee add` / `room add` / `event delete` | +## 标准 SOP(必遵流程) + +> 命中以下意图**必须**按对应 SOP 顺序执行;**禁止**跳步、替换命令、编造 userId/eventId。每条命令必须带 `--format json`,时间参数**必须**是 ISO-8601(如 `2026-07-03T14:00:00+08:00`)。 + +### SOP-1 查日程(list-events) + +**触发**:今天/明天/本周日程/我有什么会/某时段日程。 + +1. **首选脚本(必须)**:`python scripts/calendar_today_agenda.py today|tomorrow|week`(聚合今日议程)。 +2. **降级 CLI(必须)**:脚本不可用时 `dws calendar event list --start "<起始ISO>" --end "<结束ISO>" --format json`;不传 `--start/--end` 默认查今天(00:00:00~23:59:59)。`hasMore=true` 用 `--limit`/翻页。 +3. **解析(必须)**:取真实 `eventId`、`attendees[]`、`start/end`;按需抽取,**禁止**把整段 JSON 原样贴出。 + +**禁止**:用 `event list` 替代闲忙查询(查闲忙走 SOP-3)、编造时间窗口、用非 ISO 时间格式。 + +### SOP-2 建日程(create-event) + +**触发**:建日程/约会议/加日程。 + +1. **解析与会人(必须)**:对每个姓名 `dws aisearch person --keyword "<姓名>" --dimension name --format json` 取 `userId`,多人逗号拼接。 +2. **执行(必须)**:`dws calendar event create --summary "<主题>" --start "" --end "" --attendees --format json`(按需加 `--location`/`--description`/`--rooms`)。 +3. **验证(必须)**:从返回取 `eventId`,可 `dws calendar event list --start "" --end "" --format json` 复核。 + +**禁止**:跳过与会人 userId 解析直接传姓名、编造会议室 roomId。 + +### SOP-3 查闲忙(check-busy) + +**触发**:某人/会议室是否有空/找空闲时段/避免冲突。 + +1. **解析对象(必须)**:姓名 → `dws aisearch person --keyword "<姓名>" --dimension name --format json` 取 `userId`;会议室用 `roomId`。 +2. **收敛时段(必须)**:`--start`/`--end` **必须**由用户给出或明确收敛;时段不明确**必须先追问**,**禁止**默认全天窗口。 +3. **执行(必须)**:`dws calendar busy search --users --start "" --end "" --format json`(查会议室换 `--rooms `,可同时传)。**禁止**用 `event list` 扫日程替代闲忙查询。 +4. **空闲时段(必须)**:找共同空闲用 `python scripts/calendar_free_slot_finder.py`。 + +**禁止**:用 `event list` 冒充 `busy search`、未确认时段就默认全天查询。 + ## 执行硬约束 - 多轮日程任务必须保留 `eventId`,后续加人、移人、订房、换房、改描述、删除都基于同一个 `eventId` 执行;不要重新创建重复日程。 -- 用户明确说"帮我订一个空闲会议室"时,`room search` 返回可用会议室后直接选择第一个可预订且不需要自定义审批的 `roomId` 执行 `room add`;不要把选择权抛回用户导致任务停住。 +- 用户明确授权“任意空闲会议室”时,`room search` 返回候选后选择第一个可预订且不需要自定义审批的 `roomId` 执行 `room add`;用户给了地点、容量或设备条件时按条件过滤。 - 已有日程订房:`dws calendar room search --start ... --end ... --format json` → `dws calendar room add --event --rooms --format json` → `event get` 或 `room/busy` 验证。 - 换会议室:先 `room delete --event --rooms `,再 `room add --event --rooms `,最后回查;不要只更新 `--location`。 - 参会人变化用 `attendee add/delete`,日程描述变化用 `event update --desc`,删除日程用 `event delete --id`。用户当前消息已明确要求删除/取消时可直接执行;否则先确认。 -- 脚本失败或参数不完整时,立即降级到明确的 `dws calendar event/attendee/room` 命令,不要停在"我要查看用法"。 +- 脚本失败或参数不完整时,查明缺失参数后改用明确的 `dws calendar event/attendee/room` 命令完成同一流程。 - 所有 dws 命令带 `--format json`;查询时间必须显式 `--start` / `--end`。 ## 跨产品协作 @@ -52,3 +86,6 @@ metadata: ## 注意 `schedule-meeting` 必须读 [03-meeting.md](references/03-meeting.md) 中的「两准则」「搜房失败硬门禁」,禁止假设 `roomId`。 +## 局部意图与 Recipe + +- [局部意图消歧](references/intent-guide.md);[Lite Recipe](references/lite-recipes.md)。 diff --git a/skills/multi/dingtalk-calendar/references/03-meeting.md b/skills/multi/dingtalk-calendar/references/03-meeting.md index 6479ed36..5427befb 100644 --- a/skills/multi/dingtalk-calendar/references/03-meeting.md +++ b/skills/multi/dingtalk-calendar/references/03-meeting.md @@ -1,7 +1,7 @@ # 会议管理(日程与会议室) -> **lite 速查表**含 `list-today-meetings`、`check-users-busy`;视频会议 `start-conference` 当前 CLI 不支持。列表类操作须遵循 [calendar.md](./calendar.md) **「CLI 命令树与黄金路径」**,禁止无子命令的 `dws calendar` 或臆造 `calendar list`(见该文 **「反模式(禁止)」**)。**`schedule-meeting` 不做内联**:须读本文件 **「两准则」「搜房失败硬门禁」** 及下表 **schedule-meeting** 行全文。 -> **听记、会后待办、摘要分享** 见 `dingtalk-minutes` sub-skill 的 `references/07-minutes.md`。 +> lite recipe(`list-today-meetings`、`check-users-busy`)见 [lite-recipes.md](./lite-recipes.md);视频会议发起、邀请入会与会中控制当前 CLI 不支持,请在钉钉客户端操作。列表类操作须遵循 [calendar.md](./calendar.md) **「CLI 命令树与黄金路径」**,禁止无子命令的 `dws calendar` 或臆造 `calendar list`(见该文 **「反模式(禁止)」**)。预约会议须遵循本文件的时段、会议室与失败收束规则。 +> **听记、会后待办、摘要分享** 见 `dingtalk-minutes/references/07-minutes.md`。 ## 日程与会议室两准则(强制) @@ -22,24 +22,15 @@ ### 搜房失败硬门禁(园区/范围搜尽仍无 roomId) -在用户限定的园区、楼宇、楼层或评测固定分组内,已按早停规则**逐组 `room search` 查完**仍得不到任何有效 `rooms[].roomId`(或无任何空闲房)→ **立即停止**,向用户**明确报错/失败结论**(例如:该时段在指定范围内未检索到可预订会议室或无法获得 roomId),**本回合订房流程结束**。 +在用户限定的园区、楼宇、楼层或固定分组内,已按早停规则**逐组 `room search` 查完**仍得不到任何有效 `rooms[].roomId`(或无任何空闲房)→ **立即停止**,向用户**明确报错/失败结论**(例如:该时段在指定范围内未检索到可预订会议室或无法获得 roomId),当前预订链路结束。 -**用户汇报硬门禁**:一旦触发上面的失败条件,**下一条对外输出必须直接面向用户汇报结果**,不得继续在会话里自言自语式地延长推理。允许的后续只有两类: -1. **失败汇报**:明确说明“指定范围/指定会议室在该时段未找到可预订会议室,因此当前无法完成预订” -2. **确认放宽条件**:仅在需要继续推进时,明确问用户是否放宽地点范围、改时间或接受不订会议室 +**失败处理**:明确说明“指定范围/指定会议室在该时段未找到可预订会议室,因此当前无法完成预订”。只有用户同意放宽地点范围、改时间或接受不订会议室后,才可按新条件继续。 -以下表述/行为视为**违例**:继续写“让我再试一次”“也许是 Mock/测试环境”“可能存在预设 roomId 映射”“我去别的 group 看看”“我换个时间验证一下”“我先看看脚本/示例还能不能推断出 roomId”。 +空结果只表示当前授权范围内没有返回合法 `roomId`;不要推测隐藏映射、环境差异或未经用户授权的新搜索范围。 以下行为**一律禁止**(与是否「想多试一次」无关):编造/假设 `roomId` 格式做「预订测试」;在**没有**合法 `roomId` 时调用 `room add` 试探错误详情;拉 `event get` / 日程详情等试图**绕开** `room search` 推断 roomId;换无关园区、扩大关键词、换工具名做未经用户授权的新搜索。 -**失败后强制回读**:若出现以下任一信号,下一步**必须重新读取本文件本节与 `schedule-meeting` recipe**,不得沿着当前假设继续试: -1. 连续 **2 次** `room search` 空结果/无 `roomId` -2. 任意一次 `roomId invalid` -3. 已开始尝试「换园区 / 换楼栋 / 看 event 详情 / 猜 roomId」 - -回读后只允许二选一: -1. **报错收束**:已搜尽允许范围/整园仍无 `roomId` 或无空房 -2. **用户确认**:明确询问是否放宽范围、换时间,或接受不订会议室 +出现空结果、`roomId invalid` 或授权范围查尽时,停止当前预订链路;不得沿原假设重复搜索。后续只能报告失败,或在用户确认新范围/新时间后重新搜索。 | # | 规范 | |---|------| @@ -47,10 +38,10 @@ | 2 | **要限范围/具名**:先 `list-groups` 解析允许的 `group-id`。若用户说的是同一地点(同园区/楼栋/楼层),应优先锁定**最相关的承载 group** 并只查它;该 group 无结果即可按该地点无房收束,不再试别的无关 group。仅当用户明确给出多个允许地点时,才分别对这些 group 各 **1 次** `room search --available` 再 `room add` | | 3 | **禁止**:无新信息时反复 `--verbose`、反复切 `--available`、父组子组试探、在最相关 group 无结果后改搜别的同级/异地 group、超 100 条后仍根分组或未授权区域全量搜;**禁止**对 `room search` 使用不存在的 `--query` | | 4 | **`roomId` 门禁**:`room add --rooms` **只能**填 `room search` 返回 JSON 中的 `rooms[].roomId`;**禁止**将用户说的会议室名、编号文案、或「假 UUID / 试数字」当作 `roomId` | -| 5 | **全量无结果即收束**:在用户允许的搜索范围内(含**整园/全 campus** 若用户或评测要求已逐组查尽)仍无任何可用会议室或有效 `roomId` → **直接报错/告知失败并结束订房**,且**下一条消息必须汇报给用户**;**不得**假设 ID、不得用 `room add` 试探、不得绕路查日程、不得继续自说自话分析 Mock/测试环境 | -| 6 | **失败触发回读**:连续 2 次空结果、任意一次 `roomId invalid`、或开始换园区/绕路时 → **必须回读本节**;回读后只允许「报错收束」或「向用户确认是否放宽条件」 | +| 5 | **授权范围无结果即收束**:在用户允许的搜索范围内仍无可用会议室或有效 `roomId`,说明当前范围不可预订;不得假设 ID、用 `room add` 试探或绕路推断 | +| 6 | **无效 ID 恢复**:遇到 `roomId invalid` 时停止预订,回到 `room search` 的真实返回;若范围已查尽则报告无可用房,否则先确认是否放宽地点或时间 | | Recipe | 行动指南(固定路线) | | ------------------ | ------------------- | -| schedule-meeting | **见上文「两准则」**、**「搜房失败硬门禁」**。**未给时段且仅说「发起/开个会」**→ 不走本 recipe;当前 CLI 不支持实时视频会议,告知用户请在钉钉客户端操作。**未给时段但有预约意图**("安排""约""定"等词):追问具体开始/结束时间。**已有时段后**,按固定顺序执行:1. `dws calendar event create` 建日程;2. 有参会人则 `dws calendar attendee add`;3. 再处理会议室。**无明确会议室范围**:可直接 `python scripts/calendar_schedule_meeting.py --title "<主题>" --start "<起始>" --end "<结束>" [--users ] [--book-room] [--dry-run]`。**有明确范围(某楼/层)**:先 `dws calendar room list-groups`,锁定该地点**最相关的承载 group**;若只有一个地点,`--room-group-id` 应只传这个最相关 group,**不要**把同楼内多个楼层 group 打包传入碰运气。只有用户明确给出多个允许地点时,才把这些 `group-id` 一并传给 `python scripts/calendar_schedule_meeting.py ... --book-room --room-group-id ""`。**用户点名具体会议室**:须手工 `dws calendar room search --start "" --end "" --group-id [--available] --format json`(**无** `--query`),在 JSON 中匹配名称取 **`rooms[].roomId` 唯一真值** → `dws calendar room add --event --rooms `;**不得**把用户输入的会议室名当 `roomId`。**一旦连续 2 次空结果 / 任意一次 `roomId invalid`**:**必须回读本节并立即收束判断**;若整园/限定范围内搜尽仍无 roomId 或无空房 → **下一条消息必须直接向用户汇报失败结论**;否则只能向用户确认是否放宽范围/改时间。**禁止**假设 roomId、禁止无 ID 调用 `room add`、禁止用日程详情绕路、禁止继续猜测 Mock/测试环境。细则见「会议室搜索早停」。 | +| schedule-meeting | **未给时段且仅说“发起/开个会”**时不走本 recipe;当前 CLI 不支持实时视频会议,应告知用户在钉钉客户端操作。有预约日程意图但缺时段时先确认开始和结束时间。时段明确后固定执行:1. `calendar event create`;2. 有参会人则 `calendar attendee add`;3. 再处理会议室。无明确地点范围可用 `calendar_schedule_meeting.py --book-room`;有楼宇/楼层范围时先 `room list-groups` 锁定最相关 group;点名会议室时用 `room search` 返回的 `rooms[].roomId` 调 `room add`。授权范围内无空房则报告不可预订;需要扩大范围或改时间时先取得用户同意。 | | reschedule-meeting | 1. `calendar event list --start "<起始ISO>" --end "<结束ISO>"` → 取 `eventId` 2. `calendar event update --id --start "<新起始ISO>" --end "<新结束ISO>"` 更新时间 3. `chat search --query "<群名>"` → 取 `openConversationId` → `chat message send --group --text "<变更通知>"` 通知变更 | diff --git a/skills/multi/dingtalk-calendar/references/calendar-index.md b/skills/multi/dingtalk-calendar/references/calendar-index.md new file mode 100644 index 00000000..3cef2c2b --- /dev/null +++ b/skills/multi/dingtalk-calendar/references/calendar-index.md @@ -0,0 +1,7 @@ +# 日历能力索引 + +| 主题 | 详见 | +|---|---| +| 日程/会议/会议室/闲忙 | [`calendar.md`](./calendar.md) | +| 会议最佳实践 | [`03-meeting.md`](./03-meeting.md) | +| 轻量流程 | [`lite-recipes.md`](./lite-recipes.md) | diff --git a/skills/multi/dingtalk-calendar/references/calendar.md b/skills/multi/dingtalk-calendar/references/calendar.md index d879a649..f1e01fa3 100644 --- a/skills/multi/dingtalk-calendar/references/calendar.md +++ b/skills/multi/dingtalk-calendar/references/calendar.md @@ -2,18 +2,17 @@ ## CLI 命令树与黄金路径 -- **二级子命令(必选其一)**:`event`(日程)、`attendee`(参会人)、`room`(会议室)、`busy`(闲忙)、`attachment`(日程附件)、`book`(用户日历列表)。`dws calendar` 后**必须**紧跟上述之一;**禁止**只执行 `dws calendar`(无子命令)。 +- **二级子命令(必选其一)**:`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`(返回字段为 `calendarId`,主日历 `calendarId == "primary"` 等;注意无 `id` 字段,按 `.id` 提取会取空)。**重要**可以查询他人共享给自己的日历本,根据日历本id可以进一步查询对方的日程信息。 +- **查用户日历本列表**:`dws calendar book list`(返回主日历 `id == "primary"` 等)。**重要**可以查询他人共享给自己的日历本,根据日历本id可以进一步查询对方的日程信息。 - **CLI 不存在**独立的 `dws calendar list`;若误跑无子命令的 `dws calendar`,会打印整段 Usage,**切勿**将该段 help 当作工具结果再次塞进对话(会急剧增加 token 与首字延迟)。 - **必须**遵循指令说明进行调用。**绝对禁止**使用虚构指令,使用虚构参数。 ## 反模式(禁止) -1. **禁止**执行 `dws calendar` 且不带二级子命令(会刷出大量帮助文本)。合法二级子命令:`event` / `attendee` / `room` / `busy` / `attachment` / `book`。 +1. **禁止**执行 `dws calendar` 且不带二级子命令(会刷出大量帮助文本)。合法二级子命令:`event` / `attendee` / `room` / `busy` / `attachment` / `book` / `acl`。 2. **禁止**使用不存在的子命令试探(如臆造 `dws calendar list`);需要日程列表时一律使用 **`dws calendar event list`**(带 `--start` / `--end`,见下文「查询日程列表」示例);需要日历本列表时使用 **`dws calendar book list`**。 3. **禁止**将完整 `--help`/Usage 输出作为「观察」重复提交给模型;若误触,应直接改用本节黄金路径中的合法命令并重试。 -4. **禁止**继续使用已弃用的 `--max-results` 参数 —— 它们仍可被解析但**会被丢弃**,不再透传到 MCP,模型生成命令时也不要再带。 5. **禁止**为已有日程重新创建日程来预订会议室。若日程已存在(同一会话中刚创建、或用户明确指向某日程),必须使用 `room add --event <已有EVENT_ID> --rooms ` 追加会议室,**绝不能**再调一次 `event create --rooms`(会创建重复日程)。 6. **禁止**用 `--location` 替代会议室预订。`--location` 是纯文本地点备注字段,填入会议室名称**不会**完成任何预订或占用。预订会议室必须通过 `room add --rooms ` 或 `event create --rooms `,roomId 来自 `room search` 返回;`--location` 与 `--rooms` 是两个独立字段,用途完全不同。 7. **禁止**只传 `--recurrence-*` 部分 flag **并不是彼此独立的参数**:只传其中一项(比如只改 `--recurrence-count`、只设 `--recurrence-type`)会让服务端收到不完整的 recurrence 结构,CLI 现已前置校验并直接拒绝这类调用。**修改已有周期日程的任何一个循环字段时,都必须重新提供完整的 pattern+range 字段集合**——必要时先 `event get` 读取现有 `recurrence`,再在命令中整体重传。 @@ -30,6 +29,9 @@ 响应状态(response):参会人对日程的回应,包括:未响应、接受、待定、拒绝。 忙闲时间(busy):查询用户在指定时间段的忙闲状态,查询会议室在指定时间段的预定状态,用于会议时间协调。 会议室(room):room是 会议室 ,room可视为日程的资源类参会人,需要加入日程完成预订。注意和location区分,location只是地点,和room不同。 +访问控制 (acl):用户可通过设置acl将自己的日历访问权限授予给他人(即:共享日历),授予后,他人就可以查看日历下的日程信息。通过 `dws calendar acl list` 可查看当前要用户已经授权出去的权限。若 privilege >= reader,可查询此日历下的日程数据。若 privilege >= writer ,可操作(创建/修改/删除/响应等)此日历下的日程数据。 +共享日历:将自己日历的访问权限授予给他人,用于协作。通过 `dws calendar book list` 查询当前用户日历列表,其中type == `shared` 即他人共享给当前用户的日历。 +订阅日历:创建一个公共日历供他人订阅,他人订阅后即可查询日历下的日程数据。通过 `dws calendar book list` 查询当前用户日历列表,其中type == `subscribed` 即当前用户已订阅的日历。注意:订阅日历下的日程无参会人概念,无法执行 attendee 相关指令,也无法添加会议室。 ## 命令概览 @@ -61,7 +63,6 @@ dws calendar room add [flags] dws calendar room delete [flags] ``` > room是会议室,用于线下开会场景。 -> **组织限制**:部分企业使用自建会议室系统,未接入钉钉会议室能力。此时 `room search` / `room list-groups` 会返回业务错误 `400056`("所选组织不支持预定钉钉会议室")。这是组织级配置限制,不是命令用法问题;应直接告知用户该组织需在钉钉客户端手动预订会议室,不要重试或换参。 ### busy 相关三级子命令 ``` @@ -75,10 +76,26 @@ dws calendar busy search [flags] dws calendar attachment add [flags] ``` -### book 相关三级子命令 +### book 相关三级子命令(查"我能看哪些日历本") ``` -# 查询当前用户的所有日历,结果范围:用户自己的日历、已订阅的公共/团队日历、他人共享的日历。 +# 查询我拥有和可访问的所有日历本(含他人共享给我的) dws calendar book list [flags] +# 查询指定日历本信息 +dws calendar book get [flags] +# 按名称模糊搜索日历本 +dws calendar book search [flags] +# 更新日历本信息(需 owner 权限) +dws calendar book update [flags] +``` + +### acl 相关三级子命令(查"我的日历共享给了谁") +``` +# 查询我的日历共享给了哪些人、各自什么权限 +dws calendar acl list [flags] +# 把我的日历共享给某人 +dws calendar acl add [flags] +# 取消我的日历对某人的共享 +dws calendar acl delete [flags] ``` > **说明**: 可以通过 --help 进一步查看指令明细,也可以继续查看下一节 命令总览 @@ -90,17 +107,22 @@ Usage: dws calendar event list [flags] Example: dws calendar event list --start "2026-03-10T14:00:00+08:00" --end "2026-03-10T18:00:00+08:00" + dws calendar event list --start "2026-03-10T00:00:00+08:00" --end "2026-03-31T23:59:59+08:00" --limit 50 dws calendar event list --calendar-id primary + dws calendar event list --cursor "" Flags: --calendar-id string 日历 ID (默认 primary 主日历,仅在查询其他日历本时填写;通过 `book list` 获取) + --cursor string 分页游标 (从上一次返回的 nextCursor 获取,首次查询无需传入) --end string 结束时间 ISO-8601 (例如 2026-03-10T18:00:00+08:00) + --limit int 每页返回条数 (默认 100,最大 100) --start string 开始时间 ISO-8601 (例如 2026-03-10T14:00:00+08:00) ``` -> `--max-results` 已弃用:MCP 不再支持,CLI 仍接受但参数会被丢弃,结果固定最多返回 100 条。 - **默认行为**:不传 `--start` / `--end` 时,默认返回今天的日程(00:00:00 ~ 23:59:59)。 **权限**:查询共享日历下的日程时,至少要有reader权限。 +**分页**:单次最多返回 `--limit` 指定的条数(默认/最大 100);当结果超过 limit 时,返回体包含 `nextCursor` 字段。首次查询无需传 `--cursor`,仅在翻页时将上一次返回的 `nextCursor` 作为 `--cursor` 传入。 + +当前用户的个人日程使用 `dws calendar event list` 查询;参数包含 `--limit` 和 `--cursor` 分页。 ### 获取日程详情 ``` @@ -130,10 +152,17 @@ Example: dws calendar event create --title "每日站会" \ --start "2026-03-10T09:00:00+08:00" --end "2026-03-10T09:30:00+08:00" \ --recurrence-type daily --recurrence-interval 1 --recurrence-range-type numbered --recurrence-count 10 + dws calendar event create --title "团队周会" \ + --start "2026-03-10T14:00:00+08:00" --end "2026-03-10T15:00:00+08:00" \ + --calendar-id # 在指定日历本下创建日程 + dws calendar event create --title "重要会议" \ + --start "2026-03-10T14:00:00+08:00" --end "2026-03-10T15:00:00+08:00" \ + --remind-minutes 5,10 # 开始前5分钟和10分钟各提醒一次 Flags: --title string 日程标题 (必填,最大2048字符) --start string 开始时间 ISO-8601 (必填,例如 2026-03-10T14:00:00+08:00) --end string 结束时间 ISO-8601 (必填,例如 2026-03-10T15:00:00+08:00) + --calendar-id string 日历 ID (可选,默认 primary 主日历;仅在共享/订阅日历本下创建时填写,通过 `book list` 获取) --timezone string 时区 IANA 格式 (例如 Asia/Shanghai,默认 Asia/Shanghai) --desc string 日程描述 (最大5000字符) --attendees string 参会人 userId 列表,逗号分隔 (最多500人) 日程组织人自动放入参会人列表,无需传入userId @@ -153,6 +182,7 @@ Flags: --rich-text-desc string html格式的富文本类型日程描述,用于复杂内容的展示 --location string 地点信息(纯文本备注,如‘3号楼A区’;**不等于**预订会议室) --free-busy string 此日程的忙碌状态,默认值为busy。busy - 在忙闲视图中,此日程时间段为忙碌; free - 此日程不占用忙闲 + --remind-minutes string 日程开始前提醒,逗号分隔分钟数 (可选,例如 5,10,15 表示开始前5/10/15分钟提醒;不传则默认15分钟提醒) ``` > **说明**:个人日程也走 `event create`。如果只是给自己安排时间,不传 `--attendees` / `--open-dingtalk-ids` 即可。 @@ -166,8 +196,10 @@ Example: dws calendar event update --id --desc "新描述" --timezone Asia/Tokyo dws calendar event update --id --recurrence-type daily --recurrence-interval 1 \ --recurrence-range-type numbered --recurrence-count 5 + dws calendar event update --id --calendar-id --title "新标题" # 修改其他日历本下的日程 Flags: --id string 日程 ID (必填) + --calendar-id string 日历 ID (可选,默认 primary 主日历;指定其他日历本时填写,可通过 `book list` 获取) --title string 新标题 --start string 新开始时间 ISO-8601 --end string 新结束时间 ISO-8601 @@ -199,8 +231,10 @@ Usage: dws calendar event delete [flags] Example: dws calendar event delete --id --yes + dws calendar event delete --id --calendar-id --yes # 删除其他日历本下的日程 Flags: - --id string 日程 ID (必填) + --id string 日程 ID (必填) + --calendar-id string 日历 ID (可选,默认 primary 主日历;指定其他日历本时填写,可通过 `book list` 获取) ``` ### 查看参会人 @@ -209,8 +243,10 @@ Usage: dws calendar attendee list [flags] Example: dws calendar attendee list --event + dws calendar attendee list --event --calendar-id # 查看其他日历本下日程的参会人 Flags: - --event string 日程 ID (必填) + --event string 日程 ID (必填) + --calendar-id string 日历 ID (可选,默认 primary 主日历;指定其他日历本时填写,可通过 `book list` 获取, 注意:订阅日历下的日程无参会人,因此不可查看) ``` ### 添加参会人 @@ -220,10 +256,12 @@ Usage: Example: dws calendar attendee add --event --attendees , dws calendar attendee add --event --attendees --optional + dws calendar attendee add --event --attendees --calendar-id # 给其他日历本下的日程添加参会人 Flags: - --event string 日程 ID (必填) - --attendees string 参会人 userId 列表,逗号分隔 (必填,最多500人) - --optional 参会人可选 (默认必选参会人) + --event string 日程 ID (必填) + --attendees string 参会人 userId 列表,逗号分隔 (必填,最多500人) + --optional 参会人可选 (默认必选参会人) + --calendar-id string 日历 ID (可选,默认 primary 主日历;指定其他日历本时填写,可通过 `book list` 获取,注意:订阅日历下的日程无参会人,因此不可添加) ``` ### 移除参会人 @@ -235,36 +273,54 @@ Usage: dws calendar attendee delete [flags] Example: dws calendar attendee delete --event --attendees --yes + dws calendar attendee delete --event --attendees --calendar-id --yes # 移除其他日历本下日程的参会人 Flags: - --event string 日程 ID (必填) - --attendees string 参会人 userId 列表,逗号分隔 (必填) + --event string 日程 ID (必填) + --attendees string 参会人 userId 列表,逗号分隔 (必填) + --calendar-id string 日历 ID (可选,默认 primary 主日历;指定其他日历本时填写,可通过 `book list` 获取,注意:订阅日历下的日程无参会人,因此不可移除) ``` ### 搜索会议室 -> 此指令可用于搜索当前用户可用的会议室。**注意**,大部分会议室**仅在工作时间**可用,如果检索时间不在工作时间可能查不到任何结果。 +> 此指令支持两种模式:**按名称搜索**(不传 --start/--end)和**按时间段搜索可用会议室**(传 --start/--end 或不传任何参数)。 > 此指令搜索到的会议室结果中,有两个值需要注意: - - customApprovalProcess: true - 表示该会议室设置了自定义审批流程,只能通过客户端完成预订。 - - supportRecurring: true - 表示该会议室支持循环预定;false - 表示不支持循环预定,直接加入到循环日程会失败。 +> - customApprovalProcess: true - 表示该会议室设置了自定义审批流程,只能通过客户端完成预订。 +> - supportRecurring: true - 表示该会议室支持循环预定;false - 表示不支持循环预定,直接加入到循环日程会失败。 + +**模式路由规则**: +- **按名称搜索**:仅传 `--room-name`,不传 `--start`/`--end` → 返回所有匹配名称的会议室,**不检查可用性**。适用于「找到某个会议室」的场景。 +- **按时间段搜索可用会议室**:传 `--start`/`--end`,或不传任何参数 → 返回指定时间段内**可用**的会议室。不传时间时默认当前时间起 1 小时。 ``` Usage: dws calendar room search [flags] Example: + # 按名称搜索(不检查可用性,返回所有匹配的会议室) + dws calendar room search --room-name 永澄亭 # 注意:用户即使说「永澄亭会议室」,也应仅传「永澄亭」 + + # 按时间段搜索可用会议室 dws calendar room search --start "2026-03-10T14:00:00+08:00" --end "2026-03-10T15:00:00+08:00" dws calendar room search --start "2026-03-10T14:00:00+08:00" --end "2026-03-10T15:00:00+08:00" --group-id - dws calendar room search --room-name 永澄亭 # 注意:用户即使说「永澄亭会议室」,也应仅传「永澄亭」 dws calendar room search # 不传 --start/--end 时默认当前时间起 1 小时 + + # 名称 + 时间段:搜索指定名称的可用会议室 + dws calendar room search --room-name 永澄亭 --start "2026-03-10T14:00:00+08:00" --end "2026-03-10T15:00:00+08:00" + + # 分页(仅按时间段搜索时有效) + dws calendar room search --start "2026-03-10T14:00:00+08:00" --end "2026-03-10T15:00:00+08:00" --limit 20 --page 0 Flags: --start string 开始时间 ISO-8601 (可选,不传则默认当前时间+1分钟缓冲) --end string 结束时间 ISO-8601 (可选,不传则默认当前时间+1 小时) - --group-id string 会议室分组ID(可选,留空查根目录;超100条时需按分组查询) - --room-name string 按会议室名称过滤(可选,服务端模糊匹配;传入前必须由调用方精简,只保留核心专名) + --group-id string 会议室分组ID(可选,留空查根目录;超100条时需按分组查询;仅按时间段搜索时有效) + --room-name string 会议室名称(按名称搜索时必填;按时间段搜索时可选,用于过滤) + --limit string 页大小 (可选,不填默认 100,超过 100 按 100 处理;仅按时间段搜索时有效) + --page string 分页起始位置 (可选,不填默认 0;仅按时间段搜索时有效) ``` > **时间约束(API 限制)**:`start` 必须是未来的时间(服务端校验:start can not less current time)。 > - 若传入的 `--start` 早于当前时间,CLI 会自动修正为 `now + 1min`,调用方无需额外处理。 > - 若传入的 `--end` 早于当前时间,CLI 直接报错——无法检索已过去的时间段。 > - **最佳实践**:调用方在组装时间参数时应确保 start/end 都是未来时间;若不确定,可省略 `--start`/`--end` 让 CLI 使用默认值(当前时间起 1 小时)。 +> - **注意**:时间约束仅在「按时间段搜索」模式下生效。按名称搜索(仅传 `--room-name`)不受时间约束。 **名称过滤使用规范**:`--room-name` 适用于用户说「预定永澄亭」「约西湖厅」这类按名找会议室的场景。 - **服务端是模糊匹配,但匹配词越精简命中率越高**,关键疗法:**调用方必须在调用 CLI 前自行精简名称,CLI 不会再做任何删减**。 @@ -274,13 +330,16 @@ Flags: - 用户:「西湖厅有空吗」 → `--room-name 西湖厅`(本身就是专名,不删即可) - 用户:「预定贡嘎山大会议室」 → `--room-name 贡嘎山` -**优先路径**:当用户给了会议室中文名时,**优先用 `room search --room-name <核心专名> --start <开始时间> --end <结束时间>`**代替「先走 `list-groups`再遍历」的旧流程。`--room-name` 可与 `--group-id` 同时使用,表示「在指定分组内按名称过滤」。若返回空列表,再降级使用 `list-groups` 定位分组再查。 +**优先路径**: +- 当用户仅给出会议室名称、未指定时间段时,用 `room search --room-name <核心专名>` 快速找到会议室(不检查可用性)。 +- 当用户给出会议室名称且指定了时间段时,用 `room search --room-name <核心专名> --start <开始时间> --end <结束时间>` 查询该名称的可用会议室。 +- 若返回空列表,再降级使用 `list-groups` 定位分组再查。`--room-name` 可与 `--group-id` 同时使用(仅按时间段搜索时),表示「在指定分组内按名称过滤」。 **`roomId` 与用户说的话不是一回事**:用户说的「C6-4-06-N 贡嘎山」等是**展示名/编号文案**,**绝不能**直接填进 `room add --rooms`。`--rooms` 只接受上一步 `room search`(或同类接口)返回 JSON 里的 **`rooms[].roomId`**。形态上多为**小写十六进制串**(长度以接口为准,例如 `e6b7b65b8b30fb707afcf6c3b699f028003e6834fdd7fee7`)。含**中文、空格、连字符拼接的楼层编号**、或凭空调 UUID/纯数字「试格式」——一律视为非法,必须先搜房再取返回字段。 > 如果知道roomId,想查该会议室的预订记录,直接用dws calendar busy search 指令 ---- +--- ### 预定会议室 ``` @@ -288,9 +347,11 @@ Usage: dws calendar room add [flags] Example: dws calendar room add --event --rooms + dws calendar room add --event --rooms --calendar-id # 给其他日历本下的日程预定会议室 Flags: - --event string 日程 ID (必填) - --rooms string 会议室 ID 列表 (必填) + --event string 日程 ID (必填) + --rooms string 会议室 ID 列表 (必填) + --calendar-id string 日历 ID (可选,默认 primary 主日历;指定其他日历本时填写,可通过 `book list` 获取,注意:订阅日历下的日程不可添加会议室) ``` > room是会议室,用于线下开会场景。将room加入到日程完成预订 > 重复性日程,预订会议室时,必须设置 循环结束时间(recurrence-end-date),noEnd 或者 指定循环次数 都无法完成预定。 @@ -305,9 +366,11 @@ Usage: dws calendar room delete [flags] Example: dws calendar room delete --event --rooms --yes + dws calendar room delete --event --rooms --calendar-id --yes # 移除其他日历本下日程的会议室 Flags: - --event string 日程 ID (必填) - --rooms string 会议室 ID 列表 (必填) + --event string 日程 ID (必填) + --rooms string 会议室 ID 列表 (必填) + --calendar-id string 日历 ID (可选,默认 primary 主日历;指定其他日历本时填写,可通过 `book list` 获取。注意:订阅日历下的日程不可添加会议室) ``` ### 会议室分组列表 @@ -328,24 +391,104 @@ Usage: dws calendar attachment add [flags] Example: dws calendar attachment add --event --files :report.pdf,:slides.pptx + dws calendar attachment add --event --files :report.pdf --calendar-id # 给其他日历本下的日程添加附件 Flags: - --event string 日程 ID (必填) - --files string 附件列表,格式 :,多项逗号分隔 (必填) + --event string 日程 ID (必填) + --files string 附件列表,格式 :,多项逗号分隔 (必填) + --calendar-id string 日历 ID (可选,默认 primary 主日历;指定其他日历本时填写,可通过 `book list` 获取,注意:订阅日历下的日程不可添加附件) ``` > 上传文件得到 `fileId` 需配合钉盘相关流程;本命令只负责把已上传的文件挂载到日程上。 -### 查询用户日历列表 +### 查询我能看的所有日历本 ``` Usage: dws calendar book list [flags] Example: dws calendar book list ``` -> 通过此接口可查询当前用户的日历列表,包含 主日历本、他人共享的日历、订阅的公共/团队日历。 -> 共享日历本中有来自 xxx 的,且权限大于reader,那么通过 `event list --calendar-id `可查到xxx完整的日程安排 +> 查询"我能看哪些日历本",包含:我的主日历、他人共享**给我**的日历、订阅的公共/团队日历。注意区分:`acl list` 是查"我的日历共享**给了谁**",方向相反。 +> 共享日历本中有来自 xxx 的,且权限大于reader,那么通过 `event list --calendar-id `可查到xxx完整的日程安排。 > 主日历 `id` 固定为 `primary`,绝大多数日程操作都默认走主日历,只有当用户明确要求查/写其他日历本时才需要带 `--calendar-id`。 +### 查询指定日历本 +``` +Usage: + dws calendar book get [flags] +Example: + dws calendar book get --id primary + dws calendar book get --id CALENDAR_ID +Flags: + --id string 日历 ID (必填,主日历固定为 primary) +``` + +> **说明**:根据日历 id 查询指定日历的详细信息。用户主日历本 id 固定为 `primary`。 + +### 搜索日历本 +``` +Usage: + dws calendar book search [flags] +Example: + dws calendar book search --query "项目" + dws calendar book search --query "团队周报" +Flags: + --query string 按日历本名称模糊检索 (必填) +``` + +> **说明**:搜索当前用户拥有的日历本,支持按日历本名模糊搜索。获取全部日历请使用 `book list`。 + +### 更新日历本 +``` +Usage: + dws calendar book update [flags] +Example: + dws calendar book update --id CALENDAR_ID --summary "新日历名" + dws calendar book update --id CALENDAR_ID --desc "日历描述" +Flags: + --id string 日历 ID (必填) + --summary string 日历标题 + --desc string 日历描述 +``` + +> **说明**:更新日历信息,最低权限要求:privilege == "owner"。注意:用户主日历本 以及 他人共享的日历本 **不支持更新**。 + +### 查询我的日历共享给了谁 +``` +Usage: + dws calendar acl list [flags] +Example: + dws calendar acl list +``` + +> **说明**:查询"我的日历共享给了哪些人、各自什么权限"(即主日历的访问控制列表)。注意区分:`book list` 是查"我能看哪些日历本",方向相反。 + +### 把我的日历共享给某人 +``` +Usage: + dws calendar acl add [flags] +Example: + dws calendar acl add --user USER_ID --privilege reader + dws calendar acl add --user USER_ID --privilege writer --no-notification +Flags: + --user string 授予权限的目标用户 ID (必填) + --privilege string 授予的日历权限 (必填): free_busy_reader(查看忙闲)|title_reader(查看标题)|reader(查看详情)|writer(创建和编辑) + --no-notification 不向被授权用户发送提醒 (默认发送) +``` + +> **说明**:把我的日历共享给指定用户,授予对方相应权限。`--privilege` 可选值:`free_busy_reader`(查看忙闲)、`title_reader`(查看标题)、`reader`(查看详情)、`writer`(创建和编辑)。 + +### 取消我的日历对某人的共享 +``` +Usage: + dws calendar acl delete [flags] +Example: + dws calendar acl delete --acl-id ACL_ID +Flags: + --acl-id string 已授予权限的 ID (必填,可通过 acl list 查询) +``` + +> **说明**:取消我的日历对某人的共享(撤回已授予的访问权限)。aclId 可通过 `acl list` 获取。 + ### 查询用户 / 会议室闲忙状态 ``` Usage: @@ -398,9 +541,11 @@ Example: dws calendar event respond --id --status accepted dws calendar event respond --id --status declined dws calendar event respond --id --status tentative + dws calendar event respond --id --status accepted --calendar-id # 响应其他日历本下的日程 Flags: - --id string 日程 ID (必填) - --status string 响应状态: needsAction(未操作)|accepted(接受)|declined(拒绝)|tentative(暂定) (必填) + --id string 日程 ID (必填) + --status string 响应状态: needsAction(未操作)|accepted(接受)|declined(拒绝)|tentative(暂定) (必填) + --calendar-id string 日历 ID (可选,默认 primary 主日历;指定其他日历本时填写,可通过 `book list` 获取。注意:订阅日历下的日程无参会人,因此不可响应) ``` > **说明**:作为日程参会人,设置自己的响应状态(接受、拒绝、暂定)。`--status` 可选值:`needsAction`(未操作,默认值)、`accepted`(接受)、`declined`(拒绝)、`tentative`(暂定)。 @@ -410,7 +555,7 @@ Flags: 用户说"日程/会议/约会/日历": - 查看 → `event list` - 详情 → `event get` -- 创建/约/给自己留时间块/个人日程 → `event create`(带参会人时加 `--attendees`,循环日程加 `--recurrence-*`) +- 创建/约/给自己留时间块/个人日程 → `event create`(带参会人时加 `--attendees`,循环日程加 `--recurrence-*`,自定义提醒加 `--remind-minutes`) - 修改/改时间/改描述 → `event update`(支持修改标题、时间、描述、时区、循环规则) - 取消/删除 → `event delete` - 推荐时间/什么时候有空/协调时间 → `event suggest` @@ -422,13 +567,14 @@ Flags: - 移除 → `attendee delete --attendees ` 用户说"会议室/订会议室": -- 哪个空闲 → `room search` -- 按名找会议室(如「永澄亭」「永澄亭会议室」「约西湖厅」)→ 先在模型层精简名称(剔除「会议室」等通用后缀),再用 `room search --room-name <核心专名>` +- 哪个空闲 → `room search`(默认查当前时间起 1 小时内可用会议室) +- 按名找会议室(如「永澄亭」「永澄亭会议室」「约西湖厅」,未提时间段)→ 先在模型层精简名称(剔除「会议室」等通用后缀),再用 `room search --room-name <核心专名>`(按名称搜索,不检查可用性) +- 按名找可用会议室(如「永澄亭下午 2 点有空吗」)→ `room search --room-name <核心专名> --start <开始时间> --end <结束时间>`(按名称+时间段搜索可用会议室) - 预订 - 给已有日程订会议室 → `room add --event <已有EVENT_ID> --rooms ` - 创建新日程并订会议室 → `event create --rooms`(仅当日程尚不存在时) - 取消预定 → `room delete` -- 分组 → `room list-groups`,取 groupId 后 `room search --group-id`(可再叠加 `--room-name` 在分组内过滤) +- 分组 → `room list-groups`,取 groupId 后 `room search --group-id`(需配合 `--start`/`--end` 按时间段搜索;可再叠加 `--room-name` 在分组内过滤) 用户说"有空吗/忙不忙/闲忙": - 查询用户闲忙 → `busy search --users ` @@ -438,8 +584,18 @@ Flags: 用户说"日程附件/给会议加文件/上传日程材料": - 添加 → `attachment add`(先用钉盘上传得 fileId,再 `attachment add --files :`) -用户说"我有几个日历/查所有日历/共享日历本": -- 列表 → `book list`(主日历 id 固定为 `primary`) +用户说"我有几个日历/查所有日历/别人共享给我的日历/他人共享给我的日历本": +- 列表 → `book list`(返回用户拥有和订阅的所有日历本,包括他人共享给自己的;主日历 id 固定为 `primary`) +- 查指定日历本 → `book get --id ` +- 按名称搜索日历本 → `book search --query "关键词"` +- 修改日历本名称/描述 → `book update --id --summary "新名"` + +用户说"我的日历共享给了谁/谁能看我日历/日历权限/取消共享/把日历分享给xxx": +- 查看我共享出去的情况(即谁有权访问我的日历) → `acl list` +- 把我的日历共享给他人 → `acl add --user --privilege reader` +- 取消我的日历对某人的共享 → `acl delete --acl-id `(aclId 来自 `acl list`) + +> **易混淆辨析**:`book list` 查的是"我能看哪些日历本"(包含别人共享**给我**的);`acl list` 查的是"我的日历共享**给了谁**"(我的主日历的访问控制列表)。两者方向相反,不可混用。 用户说"查下xxx的日程安排": - 查询是否有共享关系 -> `book list` @@ -506,12 +662,16 @@ dws calendar event list --start "2026-03-10T14:00:00+08:00" --end "2026-03-10T15 | 操作 | 从返回中提取 | 用于 | |------|-------------|------| | `event create` | `eventId` | attendee/room/attachment 操作的 --event | -| `event list` | `events[].eventId` | event get/update/delete/respond 的 --id | +| `event list` | `events[].eventId`, `nextCursor` | event get/update/delete/respond 的 --id;下一页 --cursor | | `event suggest` | 推荐的时间段 | event create 的 --start/--end | | `event respond` | 响应结果 | — | | `room search` | `rooms[].roomId` | room add 的 --rooms 或 event create 的 --rooms | | `room list-groups` | `groups[].groupId` | room search 的 --group-id | -| `book list` | `calendarId`(如 `primary`) | event list/get 的 --calendar-id | +| `book list` | `id`(如 `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 | +| `acl add` | 新增的权限记录 | — | | 钉盘上传 | 文件 `fileId` | attachment add 的 --files `:` | ## 注意事项 @@ -527,20 +687,22 @@ dws calendar event list --start "2026-03-10T14:00:00+08:00" --end "2026-03-10T15 - `room list-groups` 支持 `--limit` / `--page` 分页(schema 类型为字符串) - **`event create --rooms` / `room add --rooms` 的唯一合法来源**:最近一次(同一会话、同一时段窗口)`room search` 返回体中的 `roomId`;禁止把用户自然语言会议室名当 `roomId` 传入(否则会 `roomId invalid` 等错误) - **搜房无结果**:在符合早停/用户限定范围内,`room search`(含按分组逐组查)全部返回空或无空闲 → 应**直接向用户报错/说明失败**并结束订房;**禁止**假设 roomId、禁止无合法 `roomId` 时调用 `room add` / `event create --rooms` 试探、禁止用 `event get` 等绕路推断 roomId -- **评测 / 自动化断言**:凡涉及 `room add` / `event create --rooms` 的流程,`--rooms` 只能填上游 `room search`(或等价接口)返回 JSON 中的 **`rooms[].roomId`**;不得以会议室展示名、楼层文案或用户口语当作 `roomId` +- **自动化校验**:凡涉及 `room add` / `event create --rooms` 的流程,`--rooms` 只能填上游 `room search`(或等价接口)返回 JSON 中的 **`rooms[].roomId`**;不得以会议室展示名、楼层文案或用户口语当作 `roomId` - **附件**:`attachment add` 仅负责挂载,**不上传**文件;fileId 必须先通过钉盘流程取得;`--files` 多附件用 `:` 元素逗号分隔 -- **日历本**:`book list` 返回的 `calendarId` 字段才是合法日历 id(无 `id` 字段);如无明确说明,`event list` / `event get` 都不要带 `--calendar-id`,让接口默认走 primary 主日历 -- **已弃用入参**:`--max-results`(event list)在新 MCP schema 中被移除;CLI 仍接受但**不会**透传到 MCP;模型生成命令时**禁止**继续使用 +- **日历本**:`book list` 返回的 `id` 才是合法 `calendarId`;如无明确说明,`event list` / `event get` 都不要带 `--calendar-id`,让接口默认走 primary 主日历 +- **分页查询**:`event list` / `list-mine` 支持 `--limit`(控制每页条数,默认/最大 100)和 `--cursor`(翻页游标);**首次查询无需传 `--cursor`**,仅当返回体中包含 `nextCursor` 时,将其作为 `--cursor` 传入可获取下一页 +- **日程提醒**:`event create` 支持 `--remind-minutes` 设置开始前提醒,逗号分隔多个分钟数(如 `--remind-minutes 5,10,15`),不传则默认15分钟提醒 +- **会议室分页**:`room search` 支持 `--limit`(每页条数,默认100,最大100)和 `--page`(分页起始位置,默认0),与 `room list-groups` 分页风格一致 ## 自动化脚本 | 脚本 | 场景 | 用法 | |------|------|------| -| [calendar_today_agenda.py](../../scripts/calendar_today_agenda.py) | 查看今天/明天/本周日程安排 | `python calendar_today_agenda.py today` | -| [calendar_schedule_meeting.py](../../scripts/calendar_schedule_meeting.py) | 一键创建日程+添加参会人+预定会议室;搜房失败时输出明确原因并返回非零退出码 | `python calendar_schedule_meeting.py --title "复盘会" --start "2026-03-15T14:00" --end "2026-03-15T15:00" --users userId1 --book-room` | -| [calendar_free_slot_finder.py](../../scripts/calendar_free_slot_finder.py) | 查询多人共同空闲时段 | `python calendar_free_slot_finder.py --users userId1,userId2 --date 2026-03-15` | +| [calendar_today_agenda.py](../scripts/calendar_today_agenda.py) | 查看今天/明天/本周日程安排 | `python calendar_today_agenda.py today` | +| [calendar_schedule_meeting.py](../scripts/calendar_schedule_meeting.py) | 一键创建日程+添加参会人+预定会议室;搜房失败时输出明确原因并返回非零退出码 | `python calendar_schedule_meeting.py --title "复盘会" --start "2026-03-15T14:00" --end "2026-03-15T15:00" --users userId1 --book-room` | +| [calendar_free_slot_finder.py](../scripts/calendar_free_slot_finder.py) | 查询多人共同空闲时段 | `python calendar_free_slot_finder.py --users userId1,userId2 --date 2026-03-15` | ## 相关产品 -- conference 视频会议 — 当前 CLI 不支持发起/预约/邀请入会/会中控制;请在钉钉客户端操作 -- [contact](./contact.md) — 搜索同事 userId,用于 attendee add --attendees +- conference(视频会议预约) — 仅视频会议预约(返回入会链接),不含参会人/会议室管理 +- [contact](../../dingtalk-contact/references/contact.md) — 搜索同事 userId,用于 attendee add --attendees diff --git a/skills/multi/dingtalk-calendar/references/intent-guide.md b/skills/multi/dingtalk-calendar/references/intent-guide.md new file mode 100644 index 00000000..c5236af7 --- /dev/null +++ b/skills/multi/dingtalk-calendar/references/intent-guide.md @@ -0,0 +1,10 @@ +# calendar 局部意图消歧 + +本文件从单 Skill `intent-guide.md` 拆分而来,仅保留与本产品相关的跨产品消歧规则。 + +| 用户说... | 真实意图 | 应该用 | 不要用 | 理由 | +|---|---|---|---|---| +| "给自己留一个明天下午的时间块/建个个人日程" | 创建个人日程 | `calendar event create` | `todo` | 个人 schedule 仍属于日历事件,不是待办 | +| "帮我预约一个视频会议" / "开个会" / "发起会议" | 视频会议 | 钉钉客户端(当前 CLI 不支持) | `calendar` | 不要把实时视频会议诉求改写成日历日程 | +| "帮我建一个明天下午的日程" | 日历日程 | `calendar` | — | 日历日程管理(可含参与者/会议室);视频会议当前 CLI 不支持 | +| "明早 9 点提醒我提交周报" | 创建个人待办,但需先声明 reminder 边界 | `todo` | `calendar` | todo 当前只支持 dueTime 截止时间,不支持独立精确 reminder | diff --git a/skills/multi/dingtalk-calendar/references/lite-recipes.md b/skills/multi/dingtalk-calendar/references/lite-recipes.md new file mode 100644 index 00000000..4d50f237 --- /dev/null +++ b/skills/multi/dingtalk-calendar/references/lite-recipes.md @@ -0,0 +1,27 @@ +# calendar Lite Recipe + +本文件从单 Skill `lite-recipes.md` 拆分而来,仅保留与本产品相关的轻量流程。 + +## #3 会议日程 + +### list-today-meetings + +**优先**:`python scripts/calendar_today_agenda.py [today|tomorrow|week]` +备选:`dws calendar event list --start "<今日起始ISO>" --end "<今日结束ISO>"`(须加 `--format json`) + +### check-users-busy + +查询多人在某时段内的闲忙(**busy**,不是用 `event list` 扫日程): + +1. 解析用户:对每个姓名执行 `aisearch person --keyword "<姓名>" --dimension name` → `userId`;多人将 `userId` 用英文逗号拼接(无空格或按 [calendar.md](./calendar.md) `busy search` 要求)。 +2. 确认时段:用户须给出或可收敛为明确的 `--start` / `--end`(ISO-8601);若未给出,**先追问**起止时间,禁止用任意默认全天窗口代替用户意图。 +3. 执行:`dws calendar busy search --users --start "" --end "" --format json` + +详见 [calendar.md](./calendar.md) 中「查询用户闲忙状态」。 + +### 视频会议能力边界 + +当前 CLI 不提供视频会议发起、预约、入会邀请或会中控制。用户只说“开个会/发起会议”且未给日程时段时,直接说明请在钉钉客户端操作,不要构造视频会议命令。 + +- 有具体时间(如“明天 3 点开会”)或明确预约日程 → 走 [03-meeting.md](./03-meeting.md) 的 `schedule-meeting` +- 只有实时视频会议诉求 → 说明当前 CLI 不支持,请在钉钉客户端发起 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 b4bf20ff..01c9f637 100644 --- a/skills/multi/dingtalk-calendar/scripts/calendar_free_slot_finder.py +++ b/skills/multi/dingtalk-calendar/scripts/calendar_free_slot_finder.py @@ -51,51 +51,30 @@ 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 - # 兼容 {result: [...]} 包裹结构 - if isinstance(data, dict) and 'result' in data: - data = data['result'] - rows = [] - if isinstance(data, list): - rows = data - elif isinstance(data, dict): - 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) + if isinstance(data, list): + items = 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', []) + ) for item in items: - 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') - ) + start_str = item.get('startTime') or item.get('start', '') + end_str = item.get('endTime') or item.get('end', '') if not start_str or not end_str: continue for fmt in ( @@ -193,16 +172,6 @@ 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) @@ -212,7 +181,6 @@ 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/SKILL.md b/skills/multi/dingtalk-chat/SKILL.md index e9f1bcfc..398f8d13 100644 --- a/skills/multi/dingtalk-chat/SKILL.md +++ b/skills/multi/dingtalk-chat/SKILL.md @@ -1,6 +1,6 @@ --- name: dingtalk-chat -description: 钉钉群聊与消息。Use when 用户提到 发消息/单聊/群聊/建群/拉人进群/改群名/搜索群/群成员管理/@消息/收藏消息/撤回消息/机器人群发/Webhook通知/发图片或文件到群。Distinct from dingtalk-ding(紧急DING消息/短信/电话)、dingtalk-mail(邮件)、dingtalk-edu-group(班级群)。命令前缀:dws chat。 +description: 钉钉群聊与消息。Use when 用户提到 发消息/单聊/群聊/建群/拉人进群/改群名/搜索群/群成员管理/@消息/收藏消息/撤回消息/机器人群发/Webhook通知/发图片或文件到群/标记未读/清除红点/置顶消息/全部群列表。不做紧急 DING/短信/电话(走 dingtalk-ding)、邮件(走 dingtalk-mail)、班级群(走 dingtalk-edu-group)。命令前缀:dws chat。 cli_version: ">=0.2.14" metadata: category: product @@ -14,21 +14,22 @@ metadata: > 🧪 **EXPERIMENTAL · 试验版 / Preview** — multi 模式当前未达 stable 标准。全部 dingtalk-* skill 已通过 dispatch verifier,但接口、命名、跨 skill 引用后续可能调整;生产 / 共享环境请优先使用 mono 模式(`dws skill setup --mode mono`)。问题请提 issue 反馈。 -> **PREREQUISITE:** Read the `dws-shared` skill first for auth, global flags, product routing, URL preflight, error codes, and safety rules. The `dws` binary must be on PATH. +## 前置条件 — 执行操作前必读 + +> **`use_skill(dws-shared)`** — 认证、全局参数(`--format json` / `--yes`)、错误码、URL 模板、跨产品消歧、安全规则与 capability 边界。**执行任何 `dws` 命令前先读;** 单产品的清晰命令可直接用本 skill。 -> ⚠️ **命令可用性以当前 dws 二进制为准**。服务发现已下线,本文档随内置 skill 发布;如果 `dws --help` 不存在,说明当前版本未暴露该命令。若命令存在但调用失败,请按错误中的 endpoint 或 tool 提示确认静态端点目录和后端工具注册。实际调用前可用 `dws --help` 或 `--dry-run` 验证。 +> 渐进式参考:[chat-index.md](references/chat-index.md);表情:[chat-emoji-list.md](references/chat-emoji-list.md);剧本:[01-messaging.md](references/01-messaging.md)。先按索引选择命令族。 - -> 命令参考:[chat.md](references/chat.md);表情:[chat-emoji-list.md](references/chat-emoji-list.md);剧本:[01-messaging.md](references/01-messaging.md)。 +> 旧路径兼容入口:[chat.md](references/chat.md)。 ## 意图表 | 用户说 | 命令 | |--------|------| -| "发消息给张三" | `dws chat message send --open-dingtalk-id --title "<标题>" --text "<内容>"` | -| "发到XX群" | `dws chat search --query "<群名>"` → `dws chat message send --group --title "<标题>" --text "<内容>"` | +| "发消息给张三" | `dws chat message send --open-dingtalk-id --text "<内容>"` | +| "发到XX群" | `dws chat search --query "<群名>"` → `dws chat message send --group --text "<内容>"` | | "建群" / "拉人进群" | `dws chat group create` / `dws chat group members add` | | "改群名" / "踢人" | `dws chat group rename` / `dws chat group members remove --yes`(踢人不可逆,确认目标后加 --yes;踢群主会被 CLI 拦截,需先 `transfer-owner`)| | "@我消息" | `dws chat message list-mentions` | @@ -39,8 +40,69 @@ metadata: | "Webhook 推一条" | `dws chat message send-by-webhook --token --title "<标题>" --text "<内容>"` | | "撤回我发的消息" | `dws chat message recall`(撤回当前用户发送的消息)| | "撤回机器人消息" | `dws chat message recall-by-bot --robot-code --group --keys `(撤回机器人发的)| +| "标记未读 / 清除红点 / 全部已读" | `dws chat mark-unread` / `dws chat clear-red-point` / `dws chat clear-all-red-point` | +| "置顶某条消息 / 取消消息置顶" | `dws chat message set-top-msg` / `dws chat message unset-top-msg` | +| "我加入的所有群 / 全部群列表" | `dws chat group list-all` | -> **注**:`chat message send` 的 `--title` 可选(不传时用正文首行作标题);`send-by-bot` / `send-by-webhook` 的 `--title` 必填。 +## 标准 SOP(必遵流程) + +> 命中以下意图**必须**按对应 SOP 顺序执行;**禁止**跳步、替换命令、编造 ID。每条命令必须带 `--format json`,执行后必须按"解析"步取真实字段(`openDingTalkId` / `openConversationId` / `clientMsgId`)。 + +### SOP-1 发消息(send-message) + +**触发**:发消息/单聊/通知某人/发到群里。 + +1. **解析收件人(必须)**:人名 → 先 `dws aisearch person --keyword "<姓名>" --dimension name --format json` 取 `openDingTalkId`(优先)或 `userId`;群名 → 先 `dws chat search --query "<群名>" --format json` 取 `openConversationId`。 +2. **执行(必须)**:单聊 `dws chat message send --open-dingtalk-id --text "<内容>" --format json`(只有拿不到 `openDingTalkId` 时才用 `--user `);群聊 `dws chat message send --group --text "<内容>" --format json`。 +3. **验证(必须)**:从返回取 `messageId` / `openMessageId` / `clientMsgId` 备用(撤回要用);返回非 `success` 必须如实报错,不要谎报已发。 + +**禁止**:把人名/群名直接当 ID 传入、跳过 `aisearch person`/`chat search` 解析、跳过 `--format json`、未发送成功就答复"已发送"。 + +### SOP-2 建群(create-group) + +**触发**:建群/拉人进群/新建讨论组。 + +1. **解析成员(必须)**:对每个成员 `dws aisearch person --keyword "<姓名>" --dimension name --format json` 取 `userId`,多人英文逗号拼接。 +2. **执行(必须)**:`dws chat group create --name "<群名>" --users --format json`;外部群加 `--type EXTERNAL`,话题圈加 `--thread`。 +3. **验证(必须)**:从返回取 `openConversationId`,可用 `dws chat search --query "<群名>" --format json` 复核。 + +**禁止**:跳过成员 userId 解析直接传姓名、编造 `openConversationId`。 + +### SOP-3 Webhook 推送(send-by-webhook) + +**触发**:用机器人群 webhook 推一条消息。 + +1. **执行(必须)**:`dws chat message send-by-webhook --token --title "<标题>" --text "<内容>" --format json`。 +2. **@ 人(必须)**:需要 @ 时,`--text` 中**必须**先包含对应 `@userId` / `@手机号` / `@10`,再配合 `--at-users` / `--at-mobiles` / `--at-all`;否则 @ 不生效。 + +**禁止**:只传 `--at-users` 而 `--text` 里不含 `@<标识>`。 + +### SOP-4 共同群查询(search-common-group) + +**触发**:"我和 XX 的共同群"。 + +1. **取昵称(必须)**:先 `dws contact user get-self --format json` 取自己昵称;对方昵称从历史/上下文取,拿不到必须先问用户。 +2. **执行(必须)**:`dws chat search-common --nicks "<昵称1>,<昵称2>" --limit 20 --cursor 0 --format json`;`hasMore=true` 时**必须**用 `nextCursor` 翻页,不要停在第一页。 + +**禁止**:跳过昵称解析、忽略 `hasMore` 不翻页。 + +### SOP-5 红点 / 未读管理(manage-red-point) + +**触发**:标记未读/清除红点/全部已读。 + +1. **执行(必须)**:标记某会话未读 `dws chat mark-unread --conversation-id --format json`;清除某会话红点 `dws chat clear-red-point --conversation-id --format json`;全部已读 `dws chat clear-all-red-point --format json`。 +2. **取会话 ID(必须)**:`openConversationId` 拿不准时先 `dws chat group list-all --format json` 或 `dws chat search --query "<群名>" --format json`,**禁止**编造。 + +**禁止**:未确认会话就批量"全部已读"(破坏性,必须先与用户确认)。 + +### SOP-6 特别关注消息(focus-messages) + +**触发**:"特别关注的人最近发了什么/聊了什么"。 + +1. **执行(必须)**:`dws chat message list-focused --limit 50 --format json`,直接基于返回答复。 +2. **边界(必须)**:只有用户终点是"我关注了谁"这种**人员列表**时,才切 `dingtalk-contact` 关系查询。 + +**禁止**:用普通 `message list` 冒充 focused、把人员列表需求硬塞进 chat。 ## 跨产品协作 @@ -48,3 +110,6 @@ metadata: - 要发图片/文件 → 先 `dt_media_upload` 上传 → `python scripts/extract_media_id.py ""` 提取 mediaId → 再用 `--media-id` - 紧急升级(应用内/短信/电话)→ 切到 `dingtalk-ding` - 发邮件 → 切到 `dingtalk-mail` +## 局部意图与 Recipe + +- [局部意图消歧](references/intent-guide.md);[Lite Recipe](references/lite-recipes.md)。 diff --git a/skills/multi/dingtalk-chat/references/01-messaging.md b/skills/multi/dingtalk-chat/references/01-messaging.md index bac5ddf7..b9d1c70f 100644 --- a/skills/multi/dingtalk-chat/references/01-messaging.md +++ b/skills/multi/dingtalk-chat/references/01-messaging.md @@ -1,16 +1,14 @@ # 消息沟通 -> **安全红线**:收件人搜索返回多条/零条结果、或名称不完全匹配时,**必须列出候选项让用户确认**,严禁自动选择第一条。即使支持机器人消息撤回 `chat message recall-by-bot`,对方可能已读,发错代价高。禁止自行发送"测试消息"验证通道。 - -> lite recipe 见 `dws-shared/references/best_practices/_common/lite-recipes.md`。 +> lite recipe(`send-message`、`create-group`、`send-by-webhook`、`search-common-group`、`focus-messages`)见 [lite-recipes.md](./lite-recipes.md)。 | Recipe | 行动指南(固定路线) | |--------|-------------------| -| query-group-chat | **优先**:`python scripts/chat_export_messages.py --query "<群名>" --time "" [--no-forward] [--limit N] [--output messages.json]`(自动搜群+翻页+导出)
备选:1. `chat search --query "<群名>"` → 取 `openConversationId`
2. `chat message list --group --time ""` → 取消息列表
3. **翻页**:`hasMore=true` 时取本页最后 `createTime` 作为下次 `--time`,重复至 `hasMore=false`
4. `--direction older` 拉给定时间**之前**的消息(`newer` 拉之后)
5. 合并全部消息后总结 | -| query-private-chat | **优先**:`python scripts/chat_history_with_user.py --name "<姓名>" --time "" [--no-forward] [--limit N] [--output messages.json]`(自动搜人+翻页+导出)
备选:1. `aisearch person --keyword "<姓名>" --dimension name` → 取 `userId`
2. `chat message list-direct --user --time ""` → 取消息列表
3. **翻页**:同 query-group-chat
4. 合并全部消息后总结 | -| escalate-ding | 三级升级:
1. `ding message send --robot-code --type app --users --content "<内容>"`(必填项见 `dingtalk-ding/references/ding.md`)
2. `chat message send --group --text "<内容>"` 群里提醒(可选 `--title` / `@` 见 [chat.md](./chat.md))
3. `todo task create --title "<标题>" --executors --priority 40` 建紧急待办
前置:`aisearch person --keyword "<姓名>" --dimension name` → 取 `userId`;`chat search --query "<群名>"` → 取 `openConversationId` | +| query-group-chat | **优先**:`python scripts/chat_export_messages.py --query "<群名>" --time "" [--no-forward] [--limit N] [--output messages.json]`(自动搜群+翻页+导出)
备选:1. `chat search --query "<群名>"` → 取 `openConversationId`
2. `chat message list --group --time ""` → 取消息列表
3. **翻页**:`hasMore=true` 时取本页最后 `createTime` 作为下次 `--time`,重复至 `hasMore=false`
4. `--forward=false` 拉给定时间**之前**的消息
5. 合并全部消息后总结 | +| query-private-chat | **优先**:`python scripts/chat_history_with_user.py --name "<姓名>" --time "" [--no-forward] [--limit N] [--output messages.json]`(自动搜人+翻页+导出)
备选:1. `aisearch person --keyword "<姓名>" --dimension name` → 取 `userId`
2. `chat message list --user --time ""` → 取消息列表
3. **翻页**:同 query-group-chat
4. 合并全部消息后总结 | +| escalate-ding | 三级升级:
1. `ding message send --robot-code --type app --users --content "<内容>"`(必填项见 `dingtalk-ding`)
2. `chat message send --group --text "<内容>"` 群里提醒(可选 `--title` / `@` 见 [chat.md](./chat-commands.md))
3. `todo task create --title "<标题>" --executors --priority 40` 建紧急待办
前置:`aisearch person --keyword "<姓名>" --dimension name` → 取 `userId`;`chat search --query "<群名>"` → 取 `openConversationId` | | send-by-bot | **多群批量优先**:`python scripts/bot_broadcast.py --robot-code --chats , --title "<标题>" --text "<内容>"`
单群:1. `chat bot search` → 取 `robotCode`
2. `chat search --query "<群名>"` → 取 `openConversationId`
3. `chat message send-by-bot --robot-code --group --title "<标题>" --text "<内容>"` | | forward-message | 1. `chat search --query "<群名>"` → 取 `openConversationId` → `chat message list --group --time "<起始时间>"` 拉源消息
2. `contact user search --query "<姓名>"` → 取 `openDingTalkId`(推荐);或 `chat search --query "<群名>"` → 取目标 `openConversationId`
3. `chat message send --open-dingtalk-id --text "<内容>"`(推荐)或 `--group --text "<内容>"` 发送。仅当无法获取 openDingTalkId 时才用 `--user `(备选) | | search-common-group | `chat search-common --nicks "<昵称1>,<昵称2>" --limit 20 --cursor 0`(`--match-mode AND`=全在/`OR`=任一在,翻页:`hasMore=true` 时用 `nextCursor`)
用户说"我和XX的共同群" → nicks 包含"我"时,需先 `contact user get-self` 取自己昵称再拼接 | -| focus-messages | **零参数一行命令**:`chat message list-focused --limit 50`(拉特别关注人发的消息聚合)
触发 query:`"我特别关注的人最近发了什么消息"`、`"关注的人最近聊了啥"`、`"星标联系人最近的动态"`
**强消歧**:query 含动词【发/说/聊/讲】或名词【消息/聊天/动态】 → **必须**走本命令,**不要**先去拉 `contact relation list-my-followings`;仅当用户终点是"人员列表"(如"我关注了谁")才走 `relation list-my-followings`(详见 `dingtalk-contact/references/contact.md` 意图判断)
翻页:`hasMore=true` 时用 `nextCursor` 作为下次 `--cursor`
按人精控(可选):先 `contact relation list-my-followings` 取 `openDingTalkId`,再 `chat message list-by-sender --sender-open-dingtalk-id --start --end ` | +| focus-messages | **零参数一行命令**:`chat message list-focused --limit 50`(拉特别关注人发的消息聚合)
触发 query:`"我特别关注的人最近发了什么消息"`、`"关注的人最近聊了啥"`、`"星标联系人最近的动态"`
**强消歧**:query 含动词【发/说/聊/讲】或名词【消息/聊天/动态】 → **必须**走本命令,**不要**先去拉 `contact relation list-my-followings`;仅当用户终点是"人员列表"(如"我关注了谁")才走 `relation list-my-followings`(详见 `dingtalk-contact` 易混淆硬规则)
翻页:`hasMore=true` 时用 `nextCursor` 作为下次 `--cursor`
按人精控(可选):先 `contact relation list-my-followings` 取 `openDingTalkId`,再 `chat message list-by-sender --sender-open-dingtalk-id --start --end ` | | favorite-message | 收藏:先从消息查询结果取得 `openMessageId` 和 `openConversationId`,再执行 `chat message add-favorite --open-message-id --open-conversation-id `
取消收藏:对应执行 `remove-favorite`,不会删除原消息
查询收藏:`chat message list-favorites [--cursor N] [--size 20]`,`hasMore=true` 时继续传 `nextCursor` | diff --git a/skills/multi/dingtalk-chat/references/chat-admin-commands.md b/skills/multi/dingtalk-chat/references/chat-admin-commands.md new file mode 100644 index 00000000..53c84811 --- /dev/null +++ b/skills/multi/dingtalk-chat/references/chat-admin-commands.md @@ -0,0 +1,417 @@ +# 机器人、分组与会话状态命令 + +### bot (机器人管理) + +#### 搜索【我创建的】机器人 — 仅返回当前用户自己创建的机器人 + +范围: 仅限当前登录用户自己创建的机器人(不含他人创建、官方机器人)。 +返回字段: 没有 openDingTalkId,如果需要给机器人发单聊消息请用 find。 +典型触发词: "我创建的机器人""我的机器人""我自己的机器人""我做的机器人""查看我的机器人"。 + +``` +Usage: + dws chat bot search [flags] +Example: + dws chat bot search --page 1 + dws chat bot search --page 1 --size 10 --name "日报" +Flags: + --name string 按名称搜索 + --page int 页码,从1开始 (默认 1) + --size int 每页条数 (默认 50),别名: --limit +``` + +#### 搜索【全部可用】机器人 — 含他人创建/官方机器人,额外返回 openDingTalkId + +范围: 当前用户可用的全部机器人(含他人创建、官方机器人)。 +返回字段: 额外返回 openDingTalkId(可用于给机器人发单聊消息),search 没有此字段。 +典型触发词: "搜索机器人""找一个机器人""帮我找 XXX 机器人""所有可用机器人""查机器人"。 + +``` +Usage: + dws chat bot find [flags] +Example: + dws chat bot find --query "日报" + dws chat bot find --query "日报" --limit 20 + dws chat bot find --query "日报" --limit 20 --cursor <上次返回的 nextCursor> +Flags: + --query string 搜索关键词 (必填) + --limit int 每页返回数量(默认 20) + --cursor string 分页游标(首次调用不传,翻页时传上次返回的 nextCursor) + +注意: + - cursor 必须用上次返回的 nextCursor 字符串原值,不要传 "0" 或其他数字字面量 + (服务端 String 类型,但网关会把数字字符串 auto-coerce 回 Integer 导致 PARAM_ERROR) +``` + +search 与 find 选择指南: + +| 维度 | `chat bot search` | `chat bot find` | +|------|-------------------|-----------------| +| 范围 | 仅我创建的机器人 | 全部可用机器人(含他人/官方) | +| 额外返回 openDingTalkId | 无 | 有(可用于给机器人发单聊消息) | +| 触发词 | "我创建的""我的""我自己的" | "搜索机器人""找机器人""查机器人" | + +### category (会话分组管理) + +#### 获取用户自定义会话分组 +``` +Usage: + dws chat category list +Example: + dws chat category list + # 返回当前用户的所有自定义会话分组 +``` + +#### 拉取指定分组下的会话列表 +``` +Usage: + dws chat category list-conversations [flags] +Example: + dws chat category list-conversations --category-id <分组ID> + # 分组ID 可通过 dws chat category list 获取 +Flags: + --category-id int 会话分组 ID (必填) + --exclude-muted 是否排除已设置免打扰的会话(默认 false) +``` + +#### 创建用户自定义会话分组 +``` +Usage: + dws chat category create [flags] +Example: + dws chat category create --title "工作群" + dws chat category create --title "项目组" +Flags: + --title string 分组名称 (必填) +``` + +#### 创建智能会话分组 — 可指定群名称关键词和群内成员作为匹配规则 +``` +Usage: + dws chat category create-smart [flags] +Example: + dws chat category create-smart --name "工作群" + dws chat category create-smart --name "项目组" --keywords "项目,开发" + dws chat category create-smart --name "团队群" --members openDingTalkId1,openDingTalkId2 + dws chat category create-smart --name "重点群" --keywords "重点" --members openDingTalkId1 +Flags: + --name string 分组名称 (必填) + --keywords string 群名称关键词列表,逗号分隔(可选) + --members string 群内成员 openDingTalkId 列表,逗号分隔(可选) +``` + +#### 删除用户自定义会话分组 +``` +Usage: + dws chat category delete [flags] +Example: + dws chat category delete --category-id <分组ID> + # 分组ID 可通过 dws chat category list 获取 +Flags: + --category-id int 会话分组 ID (必填) +``` + +#### 更新用户自定义会话分组的名称 +``` +Usage: + dws chat category rename [flags] +Example: + dws chat category rename --category-id <分组ID> --title "新名称" + # 分组ID 可通过 dws chat category list 获取 +Flags: + --category-id int 会话分组 ID (必填) + --title string 新的分组名称 (必填) +``` + +#### 将会话移动到指定的自定义分组中 +``` +Usage: + dws chat category add-conv [flags] +Example: + dws chat category add-conv --group --category-ids 123,456 + # 分组ID 可通过 dws chat category list 获取 + # 查询群 ID: dws chat search --query "群名" +Flags: + --group string 会话 openConversationId (必填) + --category-ids string 目标分组 ID 列表,逗号分隔 (必填) +``` + +#### 将会话从指定的自定义分组中移出 +``` +Usage: + dws chat category remove-conv [flags] +Example: + dws chat category remove-conv --group --category-ids 123,456 + # 分组ID 可通过 dws chat category list 获取 + # 查询群 ID: dws chat search --query "群名" +Flags: + --group string 会话 openConversationId (必填) + --category-ids string 目标分组 ID 列表,逗号分隔 (必填) +``` + +### mute (会话免打扰) + +#### 会话消息免打扰 — 开启或关闭会话消息免打扰(支持单聊和群聊) +``` +Usage: + dws chat mute [flags] +Example: + dws chat mute --conversation-id + dws chat mute --conversation-id --off + # 查询群 ID: dws chat search --query "群名" + # 查询单聊会话 ID: dws chat conversation-info --user +Flags: + --conversation-id string 会话 openConversationId (必填,支持单聊/群聊) + --id string --conversation-id 的别名 + --chat string --conversation-id 的别名 + --off 关闭免打扰(不传则开启免打扰) + +注意: + - 默认行为是开启免打扰,传 --off 则关闭免打扰 + - 支持单聊和群聊,openConversationId 可通过 chat search(群聊)或 chat conversation-info(单聊)获取 +``` + +### hide (隐藏会话) + +#### 隐藏会话 — 在会话列表中隐藏指定会话(支持单聊/群聊),收到新消息时会重新出现 +``` +Usage: + dws chat hide [flags] +Example: + dws chat hide --conversation-id + dws chat hide --id + # 查询群 ID: dws chat search --query "群名" + # 查询单聊会话 ID: dws chat conversation-info --user +Flags: + --conversation-id string 会话 openConversationId (必填,支持单聊/群聊) + --id string --conversation-id 的别名 + --chat string --conversation-id 的别名 + +注意: + - 隐藏后会话不再显示在列表中,收到新消息时会重新出现 + - 支持单聊和群聊,openConversationId 可通过 chat search(群聊)或 chat conversation-info(单聊)获取 +``` + + +### mute-at-all (关闭@所有人通知) + +#### 关闭/开启 @所有人消息提醒 — 关闭或开启会话中 @所有人的消息通知 +``` +Usage: + dws chat mute-at-all [flags] +Example: + dws chat mute-at-all --conversation-id + dws chat mute-at-all --conversation-id --off + # 查询群 ID: dws chat search --query "群名" +Flags: + --conversation-id string 会话 openConversationId (必填,支持单聊/群聊) + --id string --conversation-id 的别名 + --chat string --conversation-id 的别名 + --off 恢复接收 @所有人通知(不传则关闭通知) + +注意: + - 默认行为是关闭 @所有人通知,传 --off 则恢复接收通知 + - 支持单聊和群聊,openConversationId 可通过 chat search(群聊)或 chat conversation-info(单聊)获取 +``` + +### mute-red-envelope (关闭红包通知) + +#### 关闭/开启红包消息提醒 — 关闭或开启会话中的红包消息通知 +``` +Usage: + dws chat mute-red-envelope [flags] +Example: + dws chat mute-red-envelope --conversation-id + dws chat mute-red-envelope --conversation-id --off + # 查询群 ID: dws chat search --query "群名" +Flags: + --conversation-id string 会话 openConversationId (必填,支持单聊/群聊) + --id string --conversation-id 的别名 + --chat string --conversation-id 的别名 + --off 恢复接收红包通知(不传则关闭通知) + +注意: + - 默认行为是关闭红包通知,传 --off 则恢复接收通知 + - 支持单聊和群聊,openConversationId 可通过 chat search(群聊)或 chat conversation-info(单聊)获取 +``` + +### mark-unread (标记会话为未读) + +#### 标记会话为未读 — 将指定会话标记为未读状态 +``` +Usage: + dws chat mark-unread [flags] +Example: + dws chat mark-unread --conversation-id + dws chat mark-unread --id +Flags: + --conversation-id string 会话 openConversationId (必填,支持群聊/单聊) + --id string --conversation-id 的别名 + --chat string --conversation-id 的别名 + +注意: + - 支持群聊和单聊,openConversationId 可通过 chat search(群聊)或 chat conversation-info(单聊)获取 + - 标记未读后会话列表中会显示未读状态 +``` + +### clear-red-point (清除会话红点) + +#### 清除会话红点 — 清除指定会话的未读红点 +``` +Usage: + dws chat clear-red-point [flags] +Example: + dws chat clear-red-point --conversation-id + dws chat clear-red-point --id +Flags: + --conversation-id string 会话 openConversationId (必填,支持群聊/单聊) + --id string --conversation-id 的别名 + --chat string --conversation-id 的别名 + +注意: + - 支持群聊和单聊,openConversationId 可通过 chat search(群聊)或 chat conversation-info(单聊)获取 + - 清除红点后该会话不再显示未读标记 +``` + +### clear-all-red-point (红点清零) + +#### 清除所有会话红点 — 一键全部已读 +``` +Usage: + dws chat clear-all-red-point +Example: + dws chat clear-all-red-point + +注意: + - 无需任何参数,直接清除当前用户所有会话的未读红点 + - 等效于“全部已读”操作 +``` + +### list-all-conversations (全部会话列表) + +#### 分页获取全部会话列表 — 获取当前用户的所有会话 +``` +Usage: + dws chat list-all-conversations [flags] +Example: + dws chat list-all-conversations + dws chat list-all-conversations --limit 50 + dws chat list-all-conversations --limit 100 --cursor + dws chat list-all-conversations --exclude-muted +Flags: + --limit int 每页数量(默认 1000) + --cursor int64 分页游标(首次不传或传 0,翻页传 nextCursor) + --exclude-muted 是否排除已免打扰会话(默认 false) + +注意: + - 返回结果包含单聊和群聊,不区分会话类型 + - 翻页: hasMore=true 时用返回的 nextCursor 作为下次 --cursor + - 与 list-top-conversations 的区别: 本命令返回全部会话(单聊+群聊),list-top-conversations 仅返回置顶会话 +``` + +### clear-messages (清空会话聊天记录) + +#### 清空会话聊天记录 — 清空当前用户指定会话的消息 +``` +Usage: + dws chat clear-messages [flags] +Example: + dws chat clear-messages --conversation-id + dws chat clear-messages --id +Flags: + --conversation-id string 会话 openConversationId (必填,支持群聊/单聊) + --id string --conversation-id 的别名 + --chat string --conversation-id 的别名 + +注意: + - 仅清空当前用户视角的消息,不影响其他成员 + - openConversationId 可通过 chat search(群聊)或 chat conversation-info(单聊)获取 +``` + +### mark-read (标记消息已读) + +#### 标记消息已读 — 将指定消息及之前的消息标记为已读 +``` +Usage: + dws chat mark-read [flags] +Example: + dws chat mark-read --conversation-id --message-id + dws chat mark-read --id --message-id +Flags: + --conversation-id string 会话 openConversationId (必填,支持群聊/单聊) + --id string --conversation-id 的别名 + --chat string --conversation-id 的别名 + --message-id string 消息 openMessageId (必填) + +注意: + - 标记该消息及之前的所有消息为已读 + - openConversationId 可通过 chat search(群聊)或 chat conversation-info(单聊)获取 + - openMessageId 可通过 chat message list 获取 +``` + +### group list-all (分页拉取所有群) + +#### 分页拉取我所有群列表 — 获取当前用户加入的所有群聊 +``` +Usage: + dws chat group list-all [flags] +Example: + dws chat group list-all + dws chat group list-all --limit 50 + dws chat group list-all --limit 100 --cursor +Flags: + --limit int 每页返回数量(默认 100,最大 200) + --cursor string 分页游标(首次不传,翻页传返回的 nextCursor) + +注意: + - 与 `chat group list-my-groups` 区别: list-all 返回用户加入的所有群;list-my-groups 仅返回用户作为群主/管理员的群 + - 分页: hasMore=true 时用返回的 nextCursor 作为下次 --cursor +``` + +### group list-join-validations (分页拉取入群验证记录) + +#### 分页拉取入群验证记录 — 获取当前用户的所有入群验证记录 + +包括自己被拒绝的记录以及作为审批者的记录。 + +``` +Usage: + dws chat group list-join-validations [flags] +Example: + dws chat group list-join-validations + dws chat group list-join-validations --limit 30 + dws chat group list-join-validations --limit 20 --cursor +Flags: + --limit int 单页数量(默认 20,最大 50) + --cursor string 分页游标(首次不传,翻页传返回的 nextCursor) + +注意: + - 分页: hasMore=true 时用返回的 nextCursor 作为下次 --cursor + - cursor 首次拉取不传或传 null 时从当前时间开始拉 +``` + +### group audit-join-validation (审批入群验证) + +#### 审批入群验证 — 通过、拒绝、删除单个审核 + +支持通过、拒绝、删除、忽略、拒绝并拉黑等操作。 + +``` +Usage: + dws chat group audit-join-validation [flags] +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 list-join-validations +Flags: + --group string 群 openConversationId (必填) + --record-id int64 申请记录 ID (必填) + --applicant string 申请人 openDingTalkId (必填) + --inviter string 邀请人 openDingTalkId (必填) + --status string 审批动作: AuditApprove/AuditDelete/AuditIgnore/AuditRefuse/AuditBlock (必填) + --description string 审批说明(可选) + +注意: + - status 可选值: AuditApprove(通过), AuditDelete(删除), AuditIgnore(忽略), AuditRefuse(拒绝), AuditBlock(拒绝且拉黑) + - record-id、applicant、inviter 可通过 dws chat group list-join-validations 查询获得 +``` diff --git a/skills/multi/dingtalk-chat/references/chat-commands.md b/skills/multi/dingtalk-chat/references/chat-commands.md new file mode 100644 index 00000000..e9449718 --- /dev/null +++ b/skills/multi/dingtalk-chat/references/chat-commands.md @@ -0,0 +1,10 @@ +# 聊天命令路由 + +> `dws im` 等价于 `dws chat`。按命令族只加载一份详细参考。 + +| 命令族 | 详见 | +|---|---| +| 群创建/设置/成员/角色/搜索 | [`chat-group-commands.md`](./chat-group-commands.md) | +| 消息发送/回复/转发/撤回与会话文件 | [`chat-message-file-commands.md`](./chat-message-file-commands.md) | +| 置顶会话、媒体下载、共同群、会话详情 | [`chat-conversation-commands.md`](./chat-conversation-commands.md) | +| 机器人、会话分组、免打扰、红点与入群审批 | [`chat-admin-commands.md`](./chat-admin-commands.md) | diff --git a/skills/multi/dingtalk-chat/references/chat-composite.md b/skills/multi/dingtalk-chat/references/chat-composite.md new file mode 100644 index 00000000..a58183e8 --- /dev/null +++ b/skills/multi/dingtalk-chat/references/chat-composite.md @@ -0,0 +1,139 @@ +# 复合工作流 + +## 复合工作流 + +### 机器人发消息后撤回(完整流程) + +`recall-by-bot` 通过机器人接口撤回机器人发出的消息(需要 `--robot-code` + `--keys`)。`chat message recall` 通过 IM 接口撤回当前用户自己发出的消息(需要 `--conversation-id` + `--msg-id`)。 + +```bash +# Step 1: 查我的机器人 — 提取 robot-code +dws chat bot search --format json + +# Step 2: 用机器人发消息 — 提取返回中的 processQueryKey +dws chat message send-by-bot --robot-code --group \ + --title "通知" --text "内容" --format json + +# Step 3: 用同一个 robot-code + processQueryKey 撤回 +dws chat message recall-by-bot --robot-code --group \ + --keys --format json +``` + +### 机器人发群消息(含机器人不在群内的处理) + +机器人通过 `send-by-bot --group` 发群消息时,如果返回"机器人不存在"错误,说明该机器人尚未加入目标群,需先邀请进群再发送。 + +```bash +# Step 1: 查我的机器人 — 提取 robot-code +dws chat bot search --format json + +# Step 2: 尝试发送,若报"机器人不存在"则执行 Step 3 +dws chat message send-by-bot --robot-code --group \ + --title "通知" --text "内容" --format json + +# Step 3: 邀请机器人进群 +dws chat group members add-bot --group --robot-code + +# Step 4: 重新发送 +dws chat message send-by-bot --robot-code --group \ + --title "通知" --text "内容" --format json +``` + +### 给机器人发单聊消息(必须先用 find 拿 openDingTalkId) + +给机器人发单聊消息时,必须先用 `chat bot find` 搜索机器人拿到 `openDingTalkId`,再用 `chat message send --open-dingtalk-id` 发送。不能用 `chat bot search`,因为 search 不返回 `openDingTalkId`。 + +```bash +# Step 1: 搜索机器人 — 提取 openDingTalkId(必须用 find,search 没有此字段) +dws chat bot find --query "玉澜" --format json + +# Step 2: 用 openDingTalkId 发单聊消息 +dws chat message send --open-dingtalk-id --text "你好" --format json +``` + +### 机器人 @指定人发群消息 + +通过 `--at-user-ids` 传入 userId 列表或 `--at-open-dingtalk-ids` 传入 openDingtalkId 列表来 @指定成员,多个用逗号分隔。`--text` 中需包含 `@userId` 或 `@openDingtalkId` 文本(不要用尖括号,不要用姓名)。通过 `--at-all` @所有人。 + +```bash +# Step 1: 搜人获取 userId +dws contact user search --query "张三" --format json + +# Step 2: 用 userId 发送并 @(注意 text 中 @userId) +dws chat message send-by-bot --robot-code --group \ + --at-user-ids userId1,userId2 \ + --title "提醒" --text "@userId1 @userId2 请查收本周报告" --format json + +# 或者用 openDingtalkId 发送并 @ +dws chat message send-by-bot --robot-code --group \ + --at-open-dingtalk-ids openDingtalkId1,openDingtalkId2 \ + --title "提醒" --text "@openDingtalkId1 @openDingtalkId2 请查收本周报告" --format json + +# @所有人 +dws chat message send-by-bot --robot-code --group \ + --at-all --title "通知" --text "请所有人注意" --format json +``` + + +### 发送图片 / 文件(统一一条命令) + +**`dws chat message send --msg-type file --file-path <本地路径>`** 适用于所有发图片/文件场景,任意扩展名。CLI 内部完成上传与发送,无需任何前置工具调用。 + +```bash +# 群聊 +dws chat message send --group --msg-type file --file-path ./screenshot.png --format json +dws chat message send --group --msg-type file --file-path ./report.pdf --format json + +# 单聊(推荐 --open-dingtalk-id) +dws chat message send --open-dingtalk-id --msg-type file --file-path ./screenshot.png --format json +``` + +**带文字说明**:在上一步发完文件后,再补一条文本消息即可。不要尝试把文字塞进 `--msg-type file` 命令(该命令不读 `--text`)。 + +```bash +dws chat message send --open-dingtalk-id --msg-type file --file-path ./screenshot.png --format json +dws chat message send --open-dingtalk-id --text "这是本周数据汇总" --format json +``` + +**旧链路(仅当上游已经持有 `dt_media_upload` 返回的 mediaId 时才用)**: + +```bash +dws chat message send --group --msg-type image --media-id "@lQLPD4JNnliqBq3NBQDNA8Cw" --format json +``` + +#### 创建并推送流式卡片 — 向群聊或单聊发送流式卡片消息 + +群聊传 --group,单聊传 --receiver,二者互斥。 + +**注意:send-card 必须和 update-card 搭配使用。** 创建卡片时无需传入内容,后续通过 update-card 更新内容,最后一次更新必须将 --flow-status 设为 3(finish),否则卡片会一直处于"生成中"的加载状态。 +flow-status 取值:1=处理中(PROCESSING),2=输入中(INPUTTING),3=完成(FINISH),4=执行中(EXECUTING),5=错误(ERROR)。 +``` +Usage: + dws chat message send-card [flags] +Example: + dws chat message send-card --group + dws chat message send-card --receiver + # 查询群 ID: dws chat search --query "群名" + # 查询人员: dws contact user search --keyword "姓名" --format json +Flags: + --group string 群聊 openConversationId(群聊时必填,与 --receiver 互斥) + --receiver string 单聊接收者 openDingTalkId(单聊时必填,与 --group 互斥) +``` + +#### 流式更新卡片内容 — 更新已发送的流式卡片内容 + +--biz-id 为 send-card 返回的业务 ID,--flow-status 控制流式状态。 +flow-status 取值:1=处理中(PROCESSING),2=输入中(INPUTTING),3=完成(FINISH),4=执行中(EXECUTING),5=错误(ERROR)。 + +**最后一次更新必须将 --flow-status 设为 3(finish),否则卡片会一直处于"生成中"的加载状态。** +``` +Usage: + dws chat message update-card [flags] +Example: + dws chat message update-card --biz-id --content "更新的卡片内容" --flow-status 2 + dws chat message update-card --biz-id --content "最终内容" --flow-status 3 +Flags: + --biz-id string 卡片业务 ID (必填) + --content string 卡片消息内容 (必填) + --flow-status int 流式状态 (必填) +``` diff --git a/skills/multi/dingtalk-chat/references/chat-context.md b/skills/multi/dingtalk-chat/references/chat-context.md new file mode 100644 index 00000000..9add1c19 --- /dev/null +++ b/skills/multi/dingtalk-chat/references/chat-context.md @@ -0,0 +1,27 @@ +# 上下文传递表 + +## 上下文传递表 + +| 操作 | 从返回中提取 | 用于 | +|------|-------------|------| +| `chat search` | `openConversationId` | message send/list、group members 等的 --group | +| `chat group create` | `openConversationId` | 同上 | +| `chat message list-all` | `nextCursor` | 下次 list-all 的 --cursor | +| `aisearch person` | `userId` | message send 的 --user、send-by-bot 的 --users、send-by-bot 的 --at-user-ids、list-by-sender 的 --sender-user-id | +| `contact user search` | `openDingTalkId` | message send 的 --at-open-dingtalk-ids、--open-dingtalk-id、send-by-bot 的 --open-dingtalk-ids、send-by-bot 的 --at-open-dingtalk-ids、list-by-sender 的 --sender-open-dingtalk-id、message list 的 --open-dingtalk-id | +| `chat bot search` | `robotCode` | send-by-bot / recall-by-bot 的 --robot-code(仅我创建的机器人,无 openDingTalkId) | +| `chat bot find` | `openDingTalkId` | 给机器人发单聊消息(全部可用机器人,额外返回 openDingTalkId) | +| `chat message send-by-bot` | `processQueryKey` | recall-by-bot 的 --keys | +| `chat message send` | `openTaskId` | query-send-status 的 --open-task-id | +| `chat message list` | `openMessageId` | recall 的 --msg-id | +| `chat message search` | `nextCursor` | 下次 message search 的 --cursor | +| `chat message search-advanced` | `nextCursor` | 下次 message search-advanced 的 --cursor | +| `chat search-common` | `openConversationId` | message send/list 等的 --group | +| `chat message list` | `openMsgId` | message read-status 的 --message-id | +| `chat group-role list` | `openRoleId` | group-role update/remove/set-user/remove-user 的 --role-id | +| `chat message create-text-emotion` | `emotionId` | add-text-emotion 的 --emotion-id | +| `chat category list` | `categoryId` | category list-conversations 的 --category-id | +| `chat group get-by-group-id` | `openConversationId` | 同 chat search,将群号转为 openConversationId | +| `chat message send-card` | `bizId` | update-card 的 --biz-id | +| `chat message list` | `openMessageId` | message reply 的 --ref-msg-id、message forward 的 --msg-id | +| `chat search` | `openConversationId` | set-top 的 --conversation-id、group-mute / group-mute-member 的 --group | diff --git a/skills/multi/dingtalk-chat/references/chat-conversation-commands.md b/skills/multi/dingtalk-chat/references/chat-conversation-commands.md new file mode 100644 index 00000000..d248fb9a --- /dev/null +++ b/skills/multi/dingtalk-chat/references/chat-conversation-commands.md @@ -0,0 +1,206 @@ +# 会话查询与媒体命令 + +### list-top-conversations (置顶会话) + +#### 拉取置顶会话列表 + +拉取当前用户的置顶会话列表。分页参数 --limit 指定每页数量,--cursor 传分页游标(首次不传或传 0)。返回结果中 hasMore=true 时用 nextCursor 作为下次 --cursor 继续翻页。 +``` +Usage: + dws chat list-top-conversations [flags] +Example: + dws chat list-top-conversations --limit 1000 + dws chat list-top-conversations --limit 1000 --cursor +Flags: + --limit int 每页返回数量(默认 1000) + --cursor int64 分页游标(首次不传或传 0,翻页传 nextCursor) + --exclude-muted 是否排除已设置免打扰的会话(默认 false) + +注意: + - 用户询问"置顶会话"时,直接调用此命令返回置顶会话列表即可 + - 用户询问"置顶消息"时,需两步:先调用此命令拉取置顶会话列表获取各会话的 openConversationId,再用 `chat message list --group ` 分别拉取每个会话内的消息 + - 翻页:hasMore=true 时,用返回的 nextCursor 作为下次 --cursor +``` + +### download-media (下载消息资源) + +#### 下载消息中的资源(图片/视频/语音等)到本地 + +下载聊天消息中的图片、视频、语音等资源到本地文件。流程:先获取下载 URL,再 HTTP GET 下载。 +``` +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 +Flags: + --type string 资源类型: mediaId (必填) + --resource-id string 资源 ID,mediaId 类型时为消息中的 mediaId 值 (必填) + --message-id string 消息 openMessageId (必填) + --open-conversation-id string 会话 openConversationId (必填) + --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 中自动推断 +``` + +### search-common (搜索共同群) + +#### 搜索共同群 — 查询指定人共同所在的群聊 + +根据昵称列表搜索共同群聊。--nicks 指定要搜索的人员昵称(逗号分隔,必填)。--match-mode 控制匹配模式:AND 表示所有人都在群里,OR 表示任一人在群里(默认 AND)。分页参数 --limit(默认 20)和 --cursor(默认 "0")始终传递;hasMore=true 时用返回的 nextCursor 作为下次 --cursor 继续翻页。 +``` +Usage: + dws chat search-common [flags] +Example: + dws chat search-common --nicks "风雷,山乔" --limit 20 --cursor 0 + dws chat search-common --nicks "天鸡,乐函" --match-mode OR --limit 20 --cursor 0 + dws chat search-common --nicks "风雷,山乔,天鸡" --limit 10 --cursor +Flags: + --nicks string 要搜索的昵称列表,逗号分隔 (必填) + --match-mode string 匹配模式:AND=所有人都在群里,OR=任一人在群里(默认 AND) + --limit int 每页返回数量(默认 20) + --cursor string 分页游标(默认 "0",翻页传 nextCursor) + --exclude-muted 是否排除已设置免打扰的群聊(默认 false) + +注意: + - --nicks 传人员昵称(花名),逗号分隔,如 "风雷,山乔" + - --match-mode AND 表示群里必须包含所有指定的人;OR 表示包含任意一人即可 + - 翻页:hasMore=true 时,用返回的 nextCursor 作为下次 --cursor +``` + +### conversation-info (获取会话基础信息) + +#### 获取会话基础信息 + +获取指定会话的基础信息。发送本地文件消息请优先使用 `dws chat message send --msg-type file --file-path <本地文件>`,CLI 不再要求调用方获取或传递 spaceId。 +``` +Usage: + dws chat conversation-info [flags] +Example: + dws chat conversation-info --group --format json + dws chat conversation-info --user --format json + dws chat conversation-info --open-dingtalk-id --format json +Flags: + --group string 群聊 openConversationId(群聊时使用) + --user string 单聊对方 userId(单聊时使用) + --open-dingtalk-id string 单聊对方 openDingTalkId(单聊时使用) + +注意: + - --group、--user、--open-dingtalk-id 互斥,必须且只能指定其一 + - --group 的别名: --id, --chat, --conversation-id (均可替代 --group) + - 文件发送不再依赖调用方读取 newCSpaceIdIM;使用 `dws chat message send --msg-type file --file-path <本地文件>` 会自动上传到会话文件空间 +``` + +#### 合并转发多条消息 — 将多条消息合并后转发到目标会话(源/目标会话均支持单聊/群聊) +``` +Usage: + dws chat message combine-forward [flags] +Example: + dws chat message combine-forward --src-conversation-id --msg-ids ,, --dest-conversation-id + dws chat message combine-forward --src-conversation-id --msg-ids , --dest-conversation-id --uuid +Flags: + --src-conversation-id string 源会话 openConversationId (必填) + --msg-ids string 源消息 openMessageId 列表,逗号分隔 (必填) + --dest-conversation-id string 目标会话 openConversationId (必填) + --uuid string 幂等键(可选) + +注意: + - 与 chat message forward 区别: forward 转单条,combine-forward 合并多条为一条转发 + - --msg-ids 多个消息 ID 用逗号分隔,无顺序要求 +``` + +#### 转发话题消息 — 将话题消息从源会话转发到目标会话 +``` +Usage: + dws chat message forward-topic [flags] +Example: + dws chat message forward-topic --src-msg-id --src-conversation-id --src-thread-id --dest-conversation-id +Flags: + --src-msg-id string 源消息 openMessageId (必填,要转发的消息) + --src-conversation-id string 源会话 openConversationId (必填,消息所在的会话) + --src-thread-id string 话题 ID (必填,格式: convThread + 加密后的convThreadId) + --dest-conversation-id string 目标会话 openConversationId (必填,转发到的会话) + +注意: + - 与 chat message forward 区别: forward 转发普通单条消息,forward-topic 专用于转发话题消息 + - --src-thread-id 格式为 "convThread" + 加密后的 convThreadId,可通过 dws chat message list 返回的话题信息获取 +``` + +#### 钉住某条消息(Pin) — 将指定消息设置为钉住状态 +``` +Usage: + dws chat message set-pin-msg [flags] +Example: + dws chat message set-pin-msg --open-conversation-id --msg-id +Flags: + --open-conversation-id string (必填)会话 openConversationId(支持群聊/单聊) + --msg-id string (必填)消息 openMessageId + +注意: + - 钉住消息后,会话成员均可在会话中看到被钉住的消息 +``` + +#### 取消钉住某条消息(Unpin) — 取消指定消息的钉住状态 +``` +Usage: + dws chat message unset-pin-msg [flags] +Example: + dws chat message unset-pin-msg --open-conversation-id --msg-id +Flags: + --open-conversation-id string (必填)会话 openConversationId(支持群聊/单聊) + --msg-id string (必填)消息 openMessageId + +注意: + - 取消钉住后消息仍保留在会话中,只是不再被标记为钉住状态 +``` + +#### 拉取某个会话中钉住的消息列表 — 拉取指定会话中被钉住的消息列表 +``` +Usage: + dws chat message list-pin-msg [flags] +Example: + dws chat message list-pin-msg --open-conversation-id + dws chat message list-pin-msg --open-conversation-id --size 50 + dws chat message list-pin-msg --open-conversation-id --cursor --size 20 +Flags: + --open-conversation-id string (必填)会话 openConversationId(支持群聊/单聊) + --cursor string (选填)分页游标,首次不传,翻页时传上次返回的 nextCursor + --size int (选填)一次拉取的消息数量(默认 20,最大 100) + +注意: + - 与 `chat message list` 区别: list-pin-msg 只返回被钉住的消息;list 拉取全部消息 + - 分页: hasMore=true 时,用返回的 nextCursor 作为下次 --cursor 继续翻页 +``` + +#### 置顶某条消息 — 将指定消息设置为置顶状态 +``` +Usage: + dws chat message set-top-msg [flags] +Example: + dws chat message set-top-msg --open-conversation-id --msg-id +Flags: + --open-conversation-id string (必填)会话 openConversationId(支持群聊/单聊) + --msg-id string (必填)消息 openMessageId + +注意: + - 置顶消息后,会话成员可在会话顶部看到该条消息 + - 与 `chat set-top`(会话置顶)不同:set-top-msg 是将会话内某条消息置顶;set-top 是将整个会话在列表中置顶 +``` + +#### 取消置顶某条消息 — 取消指定消息的置顶状态 +``` +Usage: + dws chat message unset-top-msg [flags] +Example: + dws chat message unset-top-msg --open-conversation-id --msg-id +Flags: + --open-conversation-id string (必填)会话 openConversationId(支持群聊/单聊) + --msg-id string (必填)消息 openMessageId + +注意: + - 取消置顶后消息仍保留在会话中,只是不再被标记为置顶状态 +``` diff --git a/skills/multi/dingtalk-chat/references/chat-group-commands.md b/skills/multi/dingtalk-chat/references/chat-group-commands.md new file mode 100644 index 00000000..79d71f55 --- /dev/null +++ b/skills/multi/dingtalk-chat/references/chat-group-commands.md @@ -0,0 +1,484 @@ +# 群组、群身份与群搜索命令 + +## 命令总览 + +### group (群组管理) + +#### 创建群 — 支持内部群/外部群/普通群/话题圈,当前登录用户自动成为群主 + +创建一个群聊,支持指定群名称、初始成员列表、群类型和话题模式。默认创建内部群。 + +``` +Usage: + dws chat group create [flags] +Example: + dws chat group create --name "Q1 项目冲刺群" --users userId1,userId2,userId3 + dws chat group create --name "外部合作群" --users userId1,userId2 --type EXTERNAL + dws chat group create --name "话题圈" --users userId1,userId2 --thread + # 指定他人为群主 + dws chat group create --name "项目群" --users userId1,userId2 --owner +Flags: + --name string 群名称 (必填) + --users string 成员 userId 或 openDingTalkId(可混传),用户本身会自动加入无需包含,逗号分隔,不超过20个 (必填) + --type string 群类型: INTERNAL(内部群,默认)/EXTERNAL(外部群)/NORMAL(普通群) + --thread 开启话题模式,将创建话题圈 + --owner string 群主 openDingTalkId(可选,未指定时默认操作者为群主) +``` + +#### 查看群成员列表 — 分页查询指定群聊的成员 +``` +Usage: + dws chat group members [flags] +Example: + dws chat group members --id +Flags: + --cursor string 分页游标,首次从 0 开始 + --id string 群 ID / openconversation_id (必填) +``` + +#### 添加群成员 — 向指定群聊添加成员,需传入群 ID 与用户 ID 列表 +``` +Usage: + dws chat group members add [flags] +Example: + dws chat group members add --id --users userId1,userId2 +Flags: + --id string 群 ID / openconversation_id (必填) + --users string 要添加的用户 userId 列表,逗号分隔 (必填) +``` + +#### 移除群成员 — 从指定群聊中移除成员,需传入群 ID 与待移除的用户 ID 列表 +``` +Usage: + dws chat group members remove [flags] +Example: + dws chat group members remove --id --users userId1,userId2 +Flags: + --id string 群 ID / openconversation_id (必填) + --users string 要移除的用户 userId 列表,逗号分隔 (必填) +``` + +#### 将机器人添加到群中 — 将自定义机器人添加到当前用户有管理权限的群聊中,如果没有权限则会报错 +``` +Usage: + dws chat group members add-bot [flags] +Example: + dws chat group members add-bot --robot-code --id +Flags: + --id string 群聊 openConversationId (必填) + --robot-code string 机器人 Code (必填) +``` + +#### 从群内移除机器人 — 将指定机器人从群聊中移除,需要群管理员或群主权限 +``` +Usage: + dws chat group members remove-bot [flags] +Example: + dws chat group members remove-bot --id --bot-id + # 查询群 ID: dws chat search --query "群名" + # 查询群内机器人: dws chat group bots --group +Flags: + --id string 群聊 openConversationId (必填) + --bot-id string 机器人 openBotId (必填) +``` + +#### 批量查看群成员详情 — 根据成员 openDingTalkId 列表批量查询群成员详情信息 +``` +Usage: + dws chat group members list-by-ids [flags] +Example: + dws chat group members list-by-ids --id --users openDingTalkId1,openDingTalkId2 + # 查询群 ID: dws chat search --query "群名" + # 查询 openDingTalkId: dws contact user search --query "姓名" +Flags: + --id string 群 ID / openConversationId (必填) + --users string 成员 openDingTalkId 列表,逗号分隔 (必填) +``` + +#### 更新群名称 +``` +Usage: + dws chat group rename [flags] +Example: + dws chat group rename --id --name "新群名" +Flags: + --id string 群 ID / openconversation_id (必填) + --name string 修改后的群名称 (必填) +``` + +#### 根据群号获取群聊信息 — 当用户只提供了数字群号而非 openConversationId 时,用此命令转换 +``` +Usage: + dws chat group get-by-group-id [flags] +Example: + dws chat group get-by-group-id --group-id 12345678 + # 群号为数字类型的群ID +Flags: + --group-id int 群号 (必填,数字类型) +``` + +#### 转让群主 — 将群主身份转让给群内其他成员 +``` +Usage: + dws chat group transfer-owner [flags] +Example: + dws chat group transfer-owner --group --new-owner + dws chat group transfer-owner --group --user + # 查询群 ID: dws chat search --query "群名" + # 查询人员: dws contact user search --keyword "姓名" --format json +Flags: + --group string 群聊 openConversationId (必填) + --new-owner string 新群主 openDingTalkId + --user string 新群主 userId +``` + +#### 获取群邀请链接 — 获取指定群聊的邀请加入链接 + +可选 --expires-seconds 指定链接有效期(秒),0 表示永久有效,不传则使用服务端默认值。 +``` +Usage: + dws chat group invite-url [flags] +Example: + dws chat group invite-url --group + dws chat group invite-url --group --expires-seconds 86400 + dws chat group invite-url --group --expires-seconds 0 + # 查询群 ID: dws chat search --query "群名" +Flags: + --group string 群聊 openConversationId (必填) + --expires-seconds int64 链接有效期(秒),0 表示永久有效,不传使用服务端默认值 +``` + +#### 分享群聊链接到会话 — 将指定群的邀请链接分享到另一个会话或单聊用户 + +--target 和 --receiver 二选一:--target 指定目标会话,--receiver 指定单聊用户。 +``` +Usage: + dws chat group share-invite [flags] +Example: + dws chat group share-invite --source <被分享群openConversationId> --target <目标会话openConversationId> + dws chat group share-invite --source <被分享群openConversationId> --receiver <接收者openDingTalkId> + dws chat group share-invite --source --target --expires-seconds 86400 + # 查询群 ID: dws chat search --query "群名" +Flags: + --source string 被分享群的 openConversationId (必填) + --target string 接收分享消息的会话 openConversationId(与 --receiver 二选一) + --receiver string 接收分享消息的单聊用户 openDingTalkId(与 --target 二选一) + --expires-seconds int64 链接有效期(秒),0 表示永久有效,不传使用服务端默认值 + --uuid string 消息幂等键(可选) +``` + +#### 退出群聊 — 当前用户退出指定群聊 +``` +Usage: + dws chat group quit [flags] +Example: + dws chat group quit --group + # 查询群 ID: dws chat search --query "群名" +Flags: + --group string 群聊 openConversationId (必填) +``` + +#### 更新群头像 — 更新指定群聊的群头像 +``` +Usage: + dws chat group update-icon [flags] +Example: + dws chat group update-icon --group --icon-media-id + # 查询群 ID: dws chat search --query "群名" +Flags: + --group string 群聊 openConversationId (必填) + --icon-media-id string 群头像 mediaId (必填) +``` + +#### 更新群设置 — 更新指定群聊的设置项 + +--setting-key 指定设置项,--status 指定值(0=关闭,1=开启)。 + +支持的 settingKey: + authority、joinValidation、onlyAdminCanAtAll、searchable、addFriendForbidden、 + toolbarStatus、pluginCustomizeVerify、onlyAdminCanDING、allMembersCanCreateMcsConf、 + onlyAdminCanSetMsgTop、onlyAdminCanPinMsg、onlyAdminCanSendFile、 + allMembersCanCreateCalendar、groupEmailDisabled、groupRedEnvelopeSwitch、 + groupLiveAuthority、groupBillAuthority +``` +Usage: + dws chat group update-settings [flags] +Example: + dws chat group update-settings --group --setting-key searchable --status 1 + dws chat group update-settings --group --setting-key onlyAdminCanAtAll --status 0 + # 查询群 ID: dws chat search --query "群名" +Flags: + --group string 群聊 openConversationId (必填) + --setting-key string 群设置项 key (必填) + --status int 设置值: 0=关闭, 1=开启 (必填) +``` + +#### 设置用户在群内的群昵称 — 设置当前用户在指定群聊内的个人群昵称 +``` +Usage: + dws chat group update-nick [flags] +Example: + dws chat group update-nick --group --nick "我的群昵称" + # 查询群 ID: dws chat search --query "群名" +Flags: + --group string 群聊 openConversationId (必填) + --nick string 个人群昵称 (必填) +``` + +#### 设置群备注 — 设置当前用户对指定群聊的备注名称(仅自己可见) +``` +Usage: + dws chat group update-alias [flags] +Example: + dws chat group update-alias --group --alias-title "项目A群" + # 查询群 ID: dws chat search --query "群名" +Flags: + --group string 群聊 openConversationId (必填) + --alias-title string 群备注标题 (必填) +``` + +#### 查看群内所有机器人 — 获取指定群聊中的所有机器人列表 +``` +Usage: + dws chat group bots [flags] +Example: + dws chat group bots --group + # 查询群 ID: dws chat search --query "群名" +Flags: + --group string 群聊 openConversationId (必填) +``` + +#### 解散群聊 — 解散指定群聊,操作不可逆,需要群主权限 +``` +Usage: + dws chat group dismiss [flags] +Example: + dws chat group dismiss --group + # 查询群 ID: dws chat search --query "群名" +Flags: + --group string 群聊 openConversationId (必填) +``` + +#### 设置新成员入群可查看历史消息选项 — 控制新加入成员可见的历史消息范围 +``` +Usage: + dws chat group set-history [flags] +Example: + dws chat group set-history --group --option RECENT_100 + dws chat group set-history --group --option FORBIDDEN + # 查询群 ID: dws chat search --query "群名" +Flags: + --group string 群聊 openConversationId (必填) + --option string 可见范围: FORBIDDEN | RECENT_100 | ALL (必填) + +注意: + - FORBIDDEN:禁止查看历史消息(默认安全策略) + - RECENT_100:可查看最近 100 条消息(最常用) + - ALL:可查看全部历史消息(开放性最高) +``` + +#### 拉取我创建/管理的群 — 查询当前用户作为群主或管理员的群列表 + +> 重要限制:此命令只能拉取到用户作为「群主」或「管理员」的群,无法获取普通成员身份的群聊。如果用户要找的群不在结果中,必须改用 `dws chat search --query "关键词"` 按名称搜索。 + +可通过 --role 过滤角色:OWNER 仅群主、ADMIN 仅管理员,不传则返回全部。可通过 --limit 限制返回数量,不传则返回所有符合条件的群。 +``` +Usage: + dws chat group list-my-groups [flags] +Example: + dws chat group list-my-groups + dws chat group list-my-groups --role OWNER + dws chat group list-my-groups --role ADMIN --limit 10 +Flags: + --role string 角色过滤: OWNER(仅群主) / ADMIN(仅管理员),不传返回全部 + --limit int 最多返回群数量,不传返回全部 + --exclude-muted 是否排除已设置免打扰的群聊(默认 false) + +注意: + - 底层先拉取最近 1000 条会话,剔除单聊和话题圈后筛选出群主/管理员的群 + - 内部群会校验 orgId 归属 + - 不传 --role 时返回群主 + 管理员的所有群 +``` + +#### 发布群公告 — 在指定群聊中发布群公告,支持 Markdown、定时发布 + +正文为 Markdown 格式。支持标题、加粗、斜体、删除线、行内代码、链接、代码块、有序/无序/任务列表、表格、引用、分割线、图片、段落、换行。 +``` +Usage: + dws chat group notice create [flags] +Example: + dws chat group notice create --group --content "今晚 22 点系统维护,请提前保存工作内容" + dws chat group notice create --group --content "# 重要通知\n请大家查收" --sticky --send-ding + dws chat group notice create --group --content "明早九点例会" --run-at "2026-07-03T09:00:00+08:00" + # 查询群 ID: dws chat search --query "群名" +Flags: + --group string 群聊 openConversationId (必填) + --content string 公告正文,Markdown 格式 (必填) + --sticky 是否吊顶置顶(默认 false) + --send-ding 是否发 DING 提醒(默认 false) + --run-at string 定时发布时间 ISO-8601(如 2026-07-03T09:00:00+08:00,传入则定时发布) + +注意: + - 下划线/字体色/背景色/字号为编辑器专属能力,Markdown 无对应语法,无法通过本命令表达 + - --run-at 建议带时区偏移,不带偏移时按北京时区处理 +``` + +#### 修改群公告 — 整体替换指定群公告的内容 +``` +Usage: + dws chat group notice edit [flags] +Example: + dws chat group notice edit --group --notice-id --content "更新后的公告内容" + dws chat group notice edit --group --notice-id --content "更新后的公告内容" --sticky --send-ding + # 查询公告 ID: dws chat group notice list --group +Flags: + --group string 群聊 openConversationId (必填) + --notice-id string 群公告 dataId (必填) + --content string 公告新正文,Markdown 格式 (必填) + --sticky 是否吊顶置顶(不传按 false 处理) + --send-ding 是否发 DING 提醒(默认 false) + +注意: + - 修改会整体替换原公告正文,需传入完整的新内容 +``` + +#### 查看群公告详情 — 查询单条群公告的详细信息 +``` +Usage: + dws chat group notice get [flags] +Example: + dws chat group notice get --group --notice-id + # 查询公告 ID: dws chat group notice list --group +Flags: + --group string 群聊 openConversationId (必填) + --notice-id string 群公告 dataId (必填) + +注意: + - 返回正文摘要、吊顶状态、发布者、已读人数/应收人数、点赞/评论数、是否可编辑、是否已读、是否定时公告等信息 +``` + +#### 查看群公告列表 — 分页拉取指定群聊的群公告列表 +``` +Usage: + dws chat group notice list [flags] +Example: + dws chat group notice list --group + dws chat group notice list --group --limit 20 --cursor + dws chat group notice list --group --scheduled + # 查询群 ID: dws chat search --query "群名" +Flags: + --group string 群聊 openConversationId (必填) + --limit int 每页返回数量(默认 10,最大 100) + --cursor string 分页游标(首次不传,翻页传返回的 nextPageCursor) + --scheduled 是否查询定时公告列表(默认 false,查询已发布公告) + +注意: + - 分页: hasMore=true 时用返回的 nextPageCursor 作为下次 --cursor + - 默认查询已发布公告,传 --scheduled 查询尚未到发布时间的定时公告 +``` + +### group-role (群身份管理) + +#### 查看群身份列表 — 拉取指定群聊的自定义群身份列表 +``` +Usage: + dws chat group-role list [flags] +Example: + dws chat group-role list --group +Flags: + --group string 群聊 openConversationId (必填) +``` + +#### 添加群身份 — 在指定群中创建一个新的自定义群身份 +``` +Usage: + dws chat group-role add [flags] +Example: + dws chat group-role add --group --name "管理员" +Flags: + --group string 群聊 openConversationId (必填) + --name string 群身份名称 (必填) +``` + +#### 更新群身份名称 — 修改指定群身份的名称 +``` +Usage: + dws chat group-role update [flags] +Example: + dws chat group-role update --group --role-id --name "新名称" +Flags: + --group string 群聊 openConversationId (必填) + --role-id string 群身份 openRoleId,由 group-role list 返回 (必填) + --name string 群身份新名称 (必填) +``` + +#### 删除群身份 — 删除指定群聊中的某个自定义群身份 +``` +Usage: + dws chat group-role remove [flags] +Example: + dws chat group-role remove --group --role-id +Flags: + --group string 群聊 openConversationId (必填) + --role-id string 群身份 openRoleId,由 group-role list 返回 (必填) +``` + +#### 设置用户群身份 — 覆盖指定用户在群中的全部群身份(传空则清除所有身份) +``` +Usage: + dws chat group-role set-user [flags] +Example: + dws chat group-role set-user --group --user --role-ids roleId1,roleId2 + # 查询人员: dws contact user search --keyword "姓名" --format json + # 查询 role-id: dws chat group-role list --group +Flags: + --group string 群聊 openConversationId (必填) + --user string 用户 userId(必填) + --role-ids string 群身份 openRoleId 列表,逗号分隔 (必填),传空字符串则清除该用户所有群身份 +``` + +#### 移除用户的指定群身份 — 从用户身上移除指定的群身份(不影响其他群身份) +``` +Usage: + dws chat group-role remove-user [flags] +Example: + dws chat group-role remove-user --group --user --role-ids roleId1,roleId2 +Flags: + --group string 群聊 openConversationId (必填) + --user string 用户 userId(必填) + --role-ids string 要移除的群身份 openRoleId 列表,逗号分隔 (必填) +``` + +#### 查询群成员的群身份 — 查询指定群成员当前持有的所有群身份 +``` +Usage: + dws chat group-role query-user [flags] +Example: + dws chat group-role query-user --group --user +Flags: + --group string 群聊 openConversationId (必填) + --user string 用户 userId(必填) +``` + +### search (搜索群聊) + +#### 根据关键词搜索群聊 — 分页返回匹配群聊列表 + +hasMore=true 时用返回的 nextCursor 作为下次 --cursor 继续翻页。 + +**注意:** +1. query 不要拆分得太细,应使用群名称中连续的核心词作为关键词(如群名"项目冲刺群"应搜"项目冲刺"而非拆成"项目"+"冲刺"分别搜索)。 +2. 当搜索结果返回多个群聊时,应列出候选群让用户确认目标群聊,不要自行假定并直接进行后续操作。 + +``` +Usage: + dws chat search [flags] +Example: + dws chat search --query "项目冲刺" + dws chat search --query "项目冲刺" --limit 20 --cursor 0 +Flags: + --query string 搜索关键词 (必填) + --limit int 每页返回数量(默认 20) + --cursor string 分页游标(默认 "0",翻页传 nextCursor) + --exclude-muted 是否排除已设置免打扰的群聊(默认 false) +``` diff --git a/skills/multi/dingtalk-chat/references/chat-index.md b/skills/multi/dingtalk-chat/references/chat-index.md new file mode 100644 index 00000000..c8e0e91d --- /dev/null +++ b/skills/multi/dingtalk-chat/references/chat-index.md @@ -0,0 +1,13 @@ +# 聊天能力索引 + +| 主题 | 详见 | +|---|---| +| 群创建/设置/成员/角色/搜索 | [`chat-group-commands.md`](./chat-group-commands.md) | +| 消息发送/回复/转发/撤回与会话文件 | [`chat-message-file-commands.md`](./chat-message-file-commands.md) | +| 会话查询、媒体下载、共同群 | [`chat-conversation-commands.md`](./chat-conversation-commands.md) | +| 机器人、会话分组、免打扰、红点与入群审批 | [`chat-admin-commands.md`](./chat-admin-commands.md) | +| 意图与工作流 | [`chat-intent.md`](./chat-intent.md) · [`chat-workflow.md`](./chat-workflow.md) · [`chat-composite.md`](./chat-composite.md) | +| 上下文、注意事项与相关产品 | [`chat-context.md`](./chat-context.md) · [`chat-notes.md`](./chat-notes.md) · [`chat-related.md`](./chat-related.md) | +| 表情/表情列表 | [`chat-emoji-list.md`](./chat-emoji-list.md) | +| 通用消息流程 | [`01-messaging.md`](./01-messaging.md) | +| 轻量流程 | [`lite-recipes.md`](./lite-recipes.md) | diff --git a/skills/multi/dingtalk-chat/references/chat-intent.md b/skills/multi/dingtalk-chat/references/chat-intent.md new file mode 100644 index 00000000..a4ee2266 --- /dev/null +++ b/skills/multi/dingtalk-chat/references/chat-intent.md @@ -0,0 +1,163 @@ +# 意图判断 + +## 意图判断 + +用户说"我特别关注的人最近发了什么消息/关注的人最近聊了啥/星标联系人最近的动态" → `chat message list-focused`(零参数一行命令) +用户说"某人发给我的消息/指定发送者的消息/某人最近的消息" → `chat message list-by-sender --sender-user-id ` 或 `--sender-open-dingtalk-id `(跨单聊+群聊) +用户说"和某人的单聊聊天记录/拉某人单聊历史" → `chat message list --user ` 或 `--open-dingtalk-id ` +用户说"某个群的聊天记录" → `chat message list --group ` +用户说"我最近所有消息/我今天的消息" → `chat message list-all --start --end ` +用户说"@我的消息/提及我的" → `chat message list-mentions --start --end ` +用户说"搜索消息里的关键词/包含XX的消息" → `chat message search-advanced --query "<关键词>"`(首选,严格超集) +用户说"我和某人的共同群" → `chat search-common --nicks "<昵称1>,<昵称2>"` +用户说"未读会话列表" → `chat message list-unread-conversations` +用户说"群里某条话题的回复" → `chat message list-topic-replies --group --topic-id ` +用户说"回复话题/往话题里发消息" → 先用 `chat message list` 拉取获得 openConvThreadId,再 `chat message send --group --text "..."` +用户说"置顶会话/查看置顶" → `chat list-top-conversations` 列会话 → 再 `chat message list --group ` 拉消息(两步) +用户说"置顶某条消息/把这条消息置顶/消息置顶" → `chat message set-top-msg` +用户说"取消置顶消息/取消消息置顶" → `chat message unset-top-msg` + +用户说"建群/创建群聊" → `chat group create` +用户说"建外部群/创建外部群" → `chat group create --type EXTERNAL` +用户说"建普通群" → `chat group create --type NORMAL` +用户说"创建话题圈/建话题群" → `chat group create --thread` +用户说"建群并指定群主/让某某当群主" → `chat group create --owner ` +用户说"搜索群/找群" → `chat search` +用户说"我创建的群/我管理的群/我是群主的群/我当管理员的群" → `chat group list-my-groups` +用户说"群成员/看群里有谁" → `chat group members` +用户说"拉人进群/加群成员" → `chat group members add` +用户说"踢人/移除群成员" → `chat group members remove` +用户说"加机器人到群" → `chat group members add-bot` +用户说"批量查群成员详情/按ID查群成员/查看指定成员信息" → `chat group members list-by-ids` +用户说"改群名" → `chat group rename` +用户说"聊天记录/会话消息/拉取会话" → `chat message list` +用户说"某人发给我的消息/指定发送者/某人的消息" → `chat message list-by-sender`(用户未明确说"单聊"时优先使用,跨单聊/群聊) +用户说"拉取和某人的单聊记录/单聊消息" → `chat message list --user`(用户明确说"单聊"时使用) +用户说"@我的消息/at我的/提及我的" → `chat message list-mentions` +用户说"未读消息会话/未读会话列表/我的未读会话" → `chat message list-unread-conversations` +用户说"发群消息(以个人身份)" → `chat message send --group` +用户说"发单聊消息(以个人身份)" → `chat message send --user`(有 userId 时)或 `chat message send --open-dingtalk-id`(有 openDingTalkId 时) +用户说"机器人发消息/机器人群发" → `chat message send-by-bot` +用户说"撤回我发的消息/撤回消息" → `chat message recall`(通过 IM 接口撤回当前用户自己发出的消息,需要 openConversationId + openMessageId) +用户说"撤回机器人发的消息/机器人撤回消息" → `chat message recall-by-bot`(通过机器人接口撤回机器人发出的消息,需要 robot-code + processQueryKey) +用户说"Webhook 发消息/告警消息" → `chat message send-by-webhook` +用户说"话题回复/群话题消息回复/拉取话题回复" → `chat message list-topic-replies` +用户说"所有消息/全部会话消息/拉取全部消息/时间范围内消息/我的消息/我今天的消息/查我的钉钉消息/最近的消息" → `chat message list-all` +用户说"特别关注人的消息/关注的人的消息/星标联系人的消息" → `chat message list-focused` +用户说"消息已读未读/谁看了消息/查读状态/消息读取状态" → `chat message read-status` +用户说"查看我的机器人" → `chat bot search` +用户说"搜索消息/查找关键词/搜一下消息里的XX" → 优先使用 `chat message search-advanced`(推荐首选,严格超集);仅在简单关键词搜索且无其他维度需求时可用 `chat message search` +用户说"多维度搜索/按发送者搜索/按人搜消息/指定多个群搜索/@我的消息搜索" → `chat message search-advanced`(推荐首选,支持多维度组合搜索) +用户说"查询消息发送状态/消息发没发成功/消息状态" → `chat message query-send-status` +用户说"我和XX的共同群/我们都在哪些群/查共同群" → `chat search-common` +用户说"置顶会话/我的置顶/查看置顶会话" → `chat list-top-conversations` +用户说"置顶某条消息/把这条消息置顶" → `chat message set-top-msg` +用户说"取消置顶消息/取消消息置顶" → `chat message unset-top-msg` +用户说"查看会话分组/自定义分组" → `chat category list` +用户说"某个分组下的会话/分组会话列表" → `chat category list-conversations` +用户说"创建会话分组/新建分组/添加分组" → `chat category create` +用户说"删除会话分组/移除分组" → `chat category delete` +用户说"重命名分组/修改分组名称/更新分组名" → `chat category rename` +用户说"把会话加到分组/将群移入分组/添加会话到分组" → `chat category add-conv` +用户说"把会话从分组移出/将群从分组移除/从分组中删除会话" → `chat category remove-conv` +用户说"根据群号查群信息/群号查群/群号转openConversationId" → `chat group get-by-group-id`(当用户发消息时只提供了群号,用此工具将群号转为 openConversationId,再调用发消息接口) +用户说"查看群身份/群的自定义身份列表" → `chat group-role list` +用户说"创建/添加群身份" → `chat group-role add` +用户说"修改/更新群身份名称" → `chat group-role update` +用户说"删除群身份" → `chat group-role remove` +用户说"给某人设置群身份/设定用户的群身份" → `chat group-role set-user` +用户说"移除某人的群身份/撤销群身份" → `chat group-role remove-user` +用户说"查询某人的群身份/某人在群里有什么身份" → `chat group-role query-user` +用户说"转让群主/换群主/群主转让" → `chat group transfer-owner` +用户说"群邀请链接/入群链接/加群链接" → `chat group invite-url` +用户说"分享群链接/把群分享给某人/群链接发到某群" → `chat group share-invite` +用户说"创建智能分组/新建智能会话分组" → `chat category create-smart` +用户说"批量查消息/按ID查消息/根据消息ID查" → `chat message list-by-ids` +用户说"emoji回应/表情回应/给消息加表情" → `chat message add-emoji` +用户说"取消emoji回应/移除表情回应" → `chat message remove-emoji` +用户说"文字表情回应/添加文字表情" → `chat message add-text-emotion` +用户说"取消文字表情回应/移除文字表情" → `chat message remove-text-emotion` +用户说"创建文字表情/新建文字表情" → `chat message create-text-emotion` +用户说"免打扰/消息免打扰/静音/开启免打扰/关闭免打扰" → `chat mute` +用户说"引用回复/回复消息/引用消息回复" → `chat message reply` +用户说"转发消息/转发一条消息/把消息转发到另一个群" → `chat message forward` +用户说"合并转发/批量转发/合并转发多条消息" → `chat message combine-forward` +用户说"转发话题/转发话题消息/话题转发到另一个群" → `chat message forward-topic` +用户说"群机器人列表/群里有哪些机器人/查看群机器人" → `chat group bots` +用户说"从群里移除机器人/踢出机器人" → `chat group members remove-bot` +用户说"搜索机器人/找机器人/查机器人/帮我找XXX机器人" → `chat bot find`(全部可用机器人,额外返回 openDingTalkId 可发单聊) +用户说"给机器人发单聊/给机器人发消息/跟机器人聊天" → 必须先 `chat bot find`(拿 openDingTalkId)→ 再 `chat message send --open-dingtalk-id`(search 没有 openDingTalkId,无法发单聊) +用户说"我创建的机器人/我的机器人/我自己的机器人/查看我的机器人" → `chat bot search`(仅我创建的机器人,无 openDingTalkId) +用户说"解散群/解散群聊" → `chat group dismiss` +用户说"设置历史消息/新成员看历史/新成员可见消息" → `chat group set-history` +用户说"置顶会话/取消置顶/会话置顶" → `chat set-top`(设置/取消置顶),`chat list-top-conversations`(查看置顶列表) +用户说"全员禁言/群禁言/解除禁言" → `chat group-mute` +用户说"禁言某人/指定成员禁言/解除某人禁言" → `chat group-mute-member` +用户说"设管理员/取消管理员/设置群管理员" → `chat group set-admin` +用户说"改群昵称/设置群昵称/我在群里的名字" → `chat group update-nick` +用户说"群备注/给群加备注/修改群备注" → `chat group update-alias` +用户说"隐藏会话/隐藏群聊/隐藏对话" → `chat hide` +用户说"关闭@所有人通知/屏蔽@所有人/不接收@all" → `chat mute-at-all` +用户说"开启@所有人通知/恢复@所有人提醒" → `chat mute-at-all --off` +用户说"关闭红包通知/屏蔽红包/不接收红包提醒" → `chat mute-red-envelope` +用户说"开启红包通知/恢复红包提醒" → `chat mute-red-envelope --off` + +关键区分: +- `chat search` — 搜**群/会话名**返回 `openConversationId`,**不**搜消息内容;要搜消息内容请用 `chat message search-advanced`(首选)/ `chat message search` / `list-by-sender` / `list-all`,**勿混淆** +- `chat message list` — 拉取指定会话的消息(需指定 --group 或 --user),按时间点 + 方向翻页 +- `chat message list --user` — list 的单聊模式,拉取与指定用户的单聊记录(用户明确说"单聊""私聊"时使用) +- `chat message list-by-sender` — 搜索指定发送者发给我的消息,跨所有会话(单聊+群聊均包含,用户只说"某人发的消息"时优先使用) +- `chat message list-mentions` — 拉取 @我 的消息(跨单聊/群聊,可选指定群) +- `chat message list-unread-conversations` — 拉取当前用户存在未读消息的会话列表(可选 `--count`) +- `chat message read-status` — 查询指定消息的已读/未读状态(仅消息发送者可查询自己发的消息,需指定 --group 和 --message-id,可选 --target-open-dingtalk-ids 查特定人) +- `chat message list-all` — 拉取当前用户所有会话的消息,按时间范围 + cursor 分页。只要用户没有指定某个具体的会话(如某个群名、某个人名),即使提到"单聊消息""群聊消息"等笼统范围,也应路由到此命令 +- `chat message list-topic-replies` — 拉取群话题的回复消息列表 +- `chat message list-focused` — 拉取特别关注人的消息,cursor 分页 +- `chat list-top-conversations` — 拉取置顶会话列表(用户询问"置顶会话"时路由到此),cursor 分页 +- `chat message send` — 以当前用户身份发消息(群聊或单聊),text 为位置参数;发图片/本地文件统一用 `--msg-type file --file-path <本地路径>`(任意扩展名一条命令直发);`--msg-type image --media-id` 仅作旧链路兼容(上游已持有 mediaId 时使用) +- `chat message search` — 按关键词搜索消息内容(跨所有会话,可选指定群) +- `chat search-common` — 搜索共同群,查询指定人共同所在的群聊(AND=所有人都在,OR=任一人在) +- `chat message send-by-bot` — 以**机器人**身份发消息(群聊或单聊),text 为 --text flag +- `chat message send-by-webhook` — 通过**自定义机器人 Webhook** 发群消息 +- `chat message recall-by-bot` — 通过**机器人接口**撤回机器人发出的消息,需要 `--robot-code` + `--keys`(发送时返回的 processQueryKey);传 `--group` 为群聊撤回,不传为单聊撤回 +- `chat message recall` — 通过 **IM 接口**撤回当前用户自己发出的消息,需要 `--conversation-id`(openConversationId)+ `--msg-id`(openMessageId,可通过 `chat message list` 获取);群聊单聊均通过 `--conversation-id` 区分 +- `chat message query-send-status` — 查询个人发送的消息的发送状态(需 send 返回的 openTaskId) +- `chat message search-advanced` — 多维度搜索消息(支持关键词、发送者、@我、@指定人、多个会话等维度组合,与 `search` 的区别:`search` 仅支持关键词且必填,`search-advanced` 所有参数均可选) +- `chat message list-by-ids` — 根据消息 ID 批量查询消息(最多 50 条) +- `chat message add-emoji` / `remove-emoji` — 对消息添加/移除 emoji 表情回应 +- `chat message add-text-emotion` / `remove-text-emotion` — 对消息添加/移除文字表情回应 +- `chat message create-text-emotion` — 创建文字表情模板,返回 emotionId 供 add-text-emotion 使用 +- `chat category list` — 获取用户自定义会话分组列表 +- `chat category list-conversations` — 拉取指定分组下的会话列表 +- `chat category create` — 创建用户自定义会话分组 +- `chat category delete` — 删除用户自定义会话分组 +- `chat category rename` — 更新用户自定义会话分组的名称 +- `chat category add-conv` — 将会话移动到指定的自定义分组中 +- `chat category remove-conv` — 将会话从指定的自定义分组中移出 +- `chat mute` — 开启/关闭会话消息免打扰(默认开启,--off 关闭) +- `chat group transfer-owner` — 转让群主 +- `chat group invite-url` — 获取群邀请链接 +- `chat group share-invite` — 分享群聊链接到会话(--target 指定目标会话,--receiver 指定单聊用户,二选一) +- `chat category create-smart` — 创建智能会话分组(可指定群名称关键词和成员作为匹配规则) +- `chat message reply` — 引用回复消息(在群聊中引用某条消息并回复文字) +- `chat message forward` — 转发单条消息(将一条消息从源会话转发到目标会话) +- `chat set-top` — 设置/取消会话置顶(默认置顶,--off 取消) +- `chat message set-top-msg` — 置顶会话内某条消息(与 chat set-top 不同:后者是会话列表置顶,前者是消息置顶) +- `chat message unset-top-msg` — 取消会话内某条消息的置顶状态 +- `chat group-mute` — 全员禁言/取消全员禁言(默认禁言,--off 取消) +- `chat group-mute-member` — 指定群成员禁言/取消禁言(需指定 --users 和 --mute-time) +- `chat group set-admin` — 设置/取消群管理员(默认设为管理员,--off 取消) +- `chat group update-nick` — 设置用户在群内的群昵称 +- `chat group update-alias` — 设置群备注(仅自己可见) +- `chat hide` — 在会话列表中隐藏会话(支持单聊/群聊,收到新消息时重新出现) +- `chat mute-at-all` — 关闭/开启 @所有人消息提醒(默认关闭,--off 恢复) +- `chat mute-red-envelope` — 关闭/开启红包消息提醒(默认关闭,--off 恢复) + +## openDingTalkId 获取方式 + +多个命令参数需要 openDingTalkId(如 --open-dingtalk-id、--at-open-dingtalk-ids、--sender-open-dingtalk-id),统一获取方式如下: + +1. 若知道姓名:`dws contact user search --query "姓名"` → 直接从结果中获取 openDingTalkId +2. 若只有 userId:先 `dws contact user get --ids ` 获取姓名 → 再 `dws contact user search --query "姓名"` 获取 openDingTalkId + +openDingTalkId 为当前用户视角下的目标用户唯一标识,不可跨用户共享。 diff --git a/skills/multi/dingtalk-chat/references/chat-message-file-commands.md b/skills/multi/dingtalk-chat/references/chat-message-file-commands.md new file mode 100644 index 00000000..92038b09 --- /dev/null +++ b/skills/multi/dingtalk-chat/references/chat-message-file-commands.md @@ -0,0 +1,625 @@ +# 消息与会话文件命令 + +### message (会话消息管理) + +#### 拉取会话消息内容 — 拉取指定群聊或单聊的会话消息内容 + +--group 指定群聊,--user 指定单聊用户(通过 userId),--open-dingtalk-id 指定单聊用户(通过 openDingTalkId),三者互斥。推荐使用 --direction 控制时间方向:newer 表示从给定时间往现在拉,older 表示从给定时间往以前拉。--forward 为旧兼容参数:true 等价 newer,false 等价 older。hasMore=true 时用结果中的边界 createTime 作为下次 --time 翻页。引用回复消息会带 `quotedMessage`;被引用的原消息是合并转发或图片时,类型与内容也会保留在引用上下文中。 +``` +Usage: + dws chat message list [flags] +Example: + dws chat message list --group --time "2025-03-01 00:00:00" + dws chat message list --user --time "2025-03-01 00:00:00" --limit 50 + dws chat message list --open-dingtalk-id --time "2025-03-01 00:00:00" --limit 50 + dws chat message list --group --time "2025-03-01 00:00:00" --direction newer + dws chat message list --group --time "2025-03-01 00:00:00" --direction older +Flags: + --direction string 时间方向: newer=从给定时间往现在拉,older=从给定时间往以前拉(推荐) + --forward 旧兼容参数: true 等价 --direction newer,false 等价 --direction older (default true) + --group string 群聊 openconversation_id(群聊时必填) + --limit int 返回数量,不传则不限制 + --time string 开始时间,格式: yyyy-MM-dd HH:mm:ss (必填) + --user string 单聊用户 userId(单聊时与 --open-dingtalk-id 二选一) + --open-dingtalk-id string 单聊用户 openDingTalkId(单聊时与 --user 二选一,适用于三方应用等无法获取 userId 的场景) + +注意: + - --group、--user、--open-dingtalk-id 三者互斥,只需指定其一:群聊用 --group,单聊用 --user 或 --open-dingtalk-id + - --user 和 --open-dingtalk-id 都是发起单聊消息拉取,区别在于用不同格式的用户标识: + - --user 传 userId(企业内部应用常用) + - --open-dingtalk-id 传 openDingTalkId(三方应用或跨组织场景常用,无法获取 userId 时使用) + - --group 的别名: --id, --chat, --conversation-id (均可替代 --group) + - 时间方向:优先使用 --direction newer/older;仅为兼容旧调用保留 --forward。两者同时传入且语义冲突时会报错 + - 翻页:hasMore=true 时,用结果中的边界 createTime 作为下次 --time + - 处理引用回复时读取 `quotedMessage`,不要只看回复正文;合并转发与图片引用的原消息内容也在该上下文中 + - 话题圈消息拉取流程:如果返回的会话消息中包含 openConvThreadId 字段,说明是话题类消息。要获取完整的话题内容,需要两步操作:(1) 先通过 dws chat message list 拉取话题主消息(即话题帖子本身);(2) 再调用 dws chat message list-topic-replies --group --topic-id 分页拉取该话题下的所有回复消息。只有话题主消息 + 回复列表合在一起,才是一条话题的完整内容。 +``` + +#### 以当前用户身份发送消息 — --group 群聊 / --user 或 --open-dingtalk-id 单聊 + +**重要:该接口会真实发送消息到目标会话,不可用于测试或试探性调用。调用前必须确认消息内容和接收对象无误。** + +--group 指定群聊 openConversationId 发群消息;--user 指定用户 userId 发单聊;--open-dingtalk-id 指定用户 openDingTalkId 发单聊。三者只能选其一,不能同时指定。纯文本/Markdown 单聊传 --user 时直接走 userId 发送能力,不需要先手动查询 openDingTalkId。推荐使用 --text flag 传递消息内容(也支持位置参数)。可选 --title 作为消息标题。 +若用户只提供了数字群号而非 openConversationId,需先调用 `chat group get-by-group-id` 将群号转为 openConversationId,再传入 --group。 +--群聊时可选 --at-all @所有人,或 --at-open-dingtalk-ids 指定成员(仅群聊时生效)。 +**发送图片或本地文件 — 一条命令直发** + +收到本地路径就直接传给 `--file-path`,CLI 自动完成上传与发送,**无需任何前置工具调用**(不要再走 `dt_media_upload` / `extract_media_id.py` / `drive upload` 任何前置链路)。`.png/.jpg/.pdf/.mp4/.zip` 等任意类型一律: + +```bash +# 群聊 +dws chat message send --group --msg-type file --file-path /local/path/to/file.png --format json +# 单聊(推荐 --open-dingtalk-id,userId 也可) +dws chat message send --open-dingtalk-id --msg-type file --file-path /local/path/to/file.png --format json +``` + +> 单条命令完成上传 + 发送,没有"先 dt_media_upload 再 send"两步流程。带文字说明再额外发一条 `--text "..."` 即可。 + +``` +Usage: + dws chat message send [flags] [] + +Example: + # 文本/Markdown + dws chat message send --group --text "hello" + dws chat message send --user --text "请查收" + dws chat message send --open-dingtalk-id --text "请查收" + dws chat message send --group --title "周报提醒" --text "请大家本周五前提交周报" + # 幂等发送(24h 内相同 uuid 不重复投递) + dws chat message send --group --text "hello" --uuid "unique-id-123" + # 话题回复(拉消息返回的 openConvThreadId 作为 --group 传入) + dws chat message send --group --text "回复话题内容" + # @ 群成员 + dws chat message send --group --at-all "<@all> 请大家注意" + dws chat message send --group --at-open-dingtalk-ids odt1,odt2 "<@odt1> <@odt2> 请查收" + # 发送本地图片(直接传路径,CLI 自动上传并发送) + dws chat message send --group --msg-type file --file-path ./screenshot.png + # 发送本地文件(同一条命令,任意扩展名) + dws chat message send --group --msg-type file --file-path ./report.pdf + # 旧链路(仅当上游已用 dt_media_upload 拿到 mediaId 时使用) + dws chat message send --group --msg-type image --media-id + # 发送位置消息(需先通过 dt_media_upload 上传地图缩略图获取 mediaId) + dws chat message send --group --msg-type location --latitude <纬度> --longitude <经度> --location-name <地址名称> --map-thumbnail-url "@mediaId" + # 分享联系人名片 + dws chat message send --group --msg-type profile --contact-id +Flags: + --text string 消息内容(推荐使用,也可用位置参数) + --group string 群聊 openconversation_id(群聊时必填) + --user string 单聊接收人 userId(单聊时与 --open-dingtalk-id 二选一) + --open-dingtalk-id string 单聊接收人 openDingTalkId(单聊时与 --user 二选一) + --title string 消息标题(可选,默认「消息」) + --at-all @所有人(仅群聊时生效,可选,默认 false) + --at-open-dingtalk-ids string @指定成员的 openDingTalkId 列表,逗号分隔(仅群聊时生效,可选) + --media-id string 图片 mediaId(仅旧链路:`--msg-type image` 时使用;新场景请直接用 `--msg-type file --file-path`) + --msg-type string 消息类型: image/file/location/profile(推荐统一用 `file --file-path`;image+media-id 仅作旧链路兼容) + --dentry-id int64 旧链路兼容:文件 dentryId(与 --space-id 成对传入时跳过自动上传) + --space-id int64 旧链路兼容:空间 ID(与 --dentry-id 成对传入时跳过自动上传) + --file-name string 旧链路兼容:文件名 + --file-type string 旧链路兼容:文件类型/扩展名 + --file-path string 本地文件路径(msgType=file 时可直接上传发送;旧链路中作为 content.filePath) + --file-size int64 旧链路兼容:文件大小,单位字节 + --uuid string 幂等 UUID,相同 uuid 在 24h 内不会重复发送(可选) + --latitude string 纬度,如 30.271321(msgType=location 时必填) + --longitude string 经度,如 120.007878(msgType=location 时必填) + --location-name string 地址名称,如 阿里集团-钉钉(msgType=location 时必填) + --map-thumbnail-url string 地图缩略图 mediaId,格式 @mediaId(msgType=location 时必填,需先通过 dt_media_upload 上传图片获取) + --contact-id string 要分享的联系人 openDingTalkId(msgType=profile 时必填) + +注意: + - --text 和位置参数二选一,--text 优先 + - --group、--user、--open-dingtalk-id 三者互斥,只需指定其一:群聊用 --group,单聊用 --user 或 --open-dingtalk-id + - **话题回复**:话题圈拉消息(`chat message list`)返回的 openConvThreadId 可直接作为 --group 的值传入,即可往该话题内发送回复消息。注意 openConvThreadId 仅从消息列表接口的返回值中获取,禁止自行拼接或猜测 + - 纯文本/Markdown 单聊发送时 `--user` 和 `--open-dingtalk-id` 都可用;传 `--user` 时直接走 userId 发送能力 + - --group 的别名: --id, --chat, --conversation-id (均可替代 --group) + - --at-all 和 --at-open-dingtalk-ids 仅在 --group 群聊时生效,单聊时无效;当设置--at-all时,消息内容中一定要包含对应的占位符<@all>;当设置--at-open-dingtalk-ids openDingTalkId1,openDingTalkId2时,消息内容中一定要包含对应格式的占位符<@openDingTalkId1> <@openDingTalkId2> + - **换行符**:消息内容按 Markdown 渲染,换行有两层要求,缺一不可: + 1. 必须使用**真实换行符**(Unicode `U+000A`),而非字面量字符串 `\n`(反斜杠 + 字母 n)。程序或大模型构造参数时,须确保已正确反转义;否则全部内容会渲染在同一行 + 2. Markdown 规范下**单个换行不产生换行效果**。需要换行时请使用:段落分隔(连续两个真实换行符 `\n\n`)、行尾两个空格 + 真实换行符(硬换行 `
`),或直接写 HTML 的 `
` 标签 + - **发图片/文件统一走 `--msg-type file --file-path <本地路径>`**(任意扩展名 png/jpg/pdf/mp4/zip…),CLI 内部一条命令完成上传 + 发送,无需 `dt_media_upload` / `extract_media_id` / `drive upload` 等前置步骤 + - 旧链路兼容:仅当上游已经通过 `dt_media_upload` 拿到 `@lQL...` 形式的 mediaId 时,才使用 `--msg-type image --media-id`;新代码与新指引一律用 file-path 路径 + - 富媒体消息的单聊优先使用 `--open-dingtalk-id`;传 `--user` 时 CLI 会尝试解析成 openDingTalkId 后发送 + - --uuid 用于幂等发送,传入相同 uuid 在 24h 内不会重复投递消息(可选,群聊和单聊均支持) + - **发送位置消息**:`--msg-type location --latitude <纬度> --longitude <经度> --location-name <地址名称> --map-thumbnail-url @mediaId`;地图缩略图需先通过 `dt_media_upload` 上传图片获取 mediaId + - **分享联系人名片**:`--msg-type profile --contact-id `;将指定联系人的名片分享到群聊或单聊 +``` + +#### 查询消息发送状态 — 查询以当前用户身份发送的消息的发送状态 + +查询以当前用户身份发送的消息的发送状态。需要传入发送消息时返回的 openTaskId。 +``` +Usage: + dws chat message query-send-status [flags] +Example: + dws chat message query-send-status --open-task-id + # openTaskId 由 dws chat message send 返回 +Flags: + --open-task-id string 消息发送任务 ID (必填) + +注意: + - openTaskId 由 `dws chat message send` 发送消息成功后返回 + - 用于确认消息是否已成功发送或获取发送失败的原因 +``` + +#### 撤回消息 — 撤回自己或他人的消息 + +撤回消息。支持撤回自己发送的消息,也支持在群聊中以群主/管理员身份撤回他人的消息。与 `recall-by-bot` 的区别:本命令通过 IM 接口撤回用户消息,`recall-by-bot` 通过机器人接口撤回机器人发出的消息(需要 robot-code + processQueryKey)。 +``` +Usage: + dws chat message recall [flags] +Example: + dws chat message recall --conversation-id --msg-id + # 群主/管理员撤回他人消息:直接传目标消息 ID 即可 + # 查询会话 ID: dws chat search --query "群名" + # 消息 ID 可通过 dws chat message list 获取 +Flags: + --conversation-id string 会话 openConversationId (必填,支持单聊/群聊,别名: --group / --id / --chat) + --msg-id string 消息 openMessageId (必填) + +注意: + - --conversation-id 的别名: --group, --id, --chat (均可替代 --conversation-id) + - 消息 ID 可通过 `dws chat message list` 命令获取 + - 支持撤回自己发出的消息,也支持群主/管理员撤回群内他人的消息 + - 与 `recall-by-bot` 的区别:本命令通过 IM 接口撤回用户消息(需要 openConversationId + openMessageId),`recall-by-bot` 通过机器人接口撤回机器人发出的消息(需要 robot-code + processQueryKey) +``` + +#### 机器人发送消息(--group 群聊 / --users 单聊) + +**重要:该接口会真实发送消息到目标会话,不可用于测试或试探性调用。调用前必须确认消息内容和接收对象无误。** + +群聊:传 --group 指定群;单聊:传 --users 指定用户列表,二者只能选其一,不能同时指定。--text 支持 Markdown。群聊时可选 --at-user-ids @指定成员。 + +如果用户明确要求"用机器人/机器人身份/robot"发送,必须使用本命令,严禁改用 `chat message send` 以当前用户身份发送。 + +**重要**:机器人发群消息前,必须确认该机器人已在目标群中。若机器人不在群内会报错"机器人不存在",需先执行 `dws chat group members add-bot --id --robot-code ` 将机器人加入群聊后再发送。 +``` +Usage: + dws chat message send-by-bot [flags] +Example: + dws chat message send-by-bot --robot-code --group --title "日报" --text "## 今日完成..." + dws chat message send-by-bot --robot-code --users userId1,userId2 --title "提醒" --text "请提交周报" + dws chat message send-by-bot --robot-code --open-dingtalk-ids openDingtalkId1,openDingtalkId2 --title "提醒" --text "请提交周报" + dws chat message send-by-bot --robot-code --group --at-user-ids userId1,userId2 --title "提醒" --text "@userId1 @userId2 请查收本周报告" + dws chat message send-by-bot --robot-code --group --at-open-dingtalk-ids openDingtalkId1,openDingtalkId2 --title "提醒" --text "@openDingtalkId1 @openDingtalkId2 请查收本周报告" + dws chat message send-by-bot --robot-code --group --at-all --title "通知" --text "请所有人注意" +Flags: + --group string 群聊 openConversationId(群聊时必填) + --robot-code string 机器人 Code (必填) + --text string 消息内容 Markdown (必填) + --title string 消息标题 (必填) + --users string 用户 userId 列表,逗号分隔,最多20个(单聊时必填) + --open-dingtalk-ids string 用户 openDingtalkId 列表,逗号分隔(单聊时可替代 --users,可选) + --at-user-ids string @指定成员的 userId 列表,逗号分隔(仅群聊时生效,可选) + --at-open-dingtalk-ids string @指定成员的 openDingtalkId 列表,逗号分隔(仅群聊时生效,可选) + --at-all @所有人(可选),服务端接收字符串 true/false + +注意: + - 用户明确要求机器人发送时,必须使用 `chat message send-by-bot`;严禁使用 `chat message send` 以用户身份代发 + - --group 与 --users/--open-dingtalk-ids 互斥,必须且只能指定其一 + - --group 的别名: --id, --chat, --conversation-id (均可替代 --group) + - --at-user-ids 仅在 --group 群聊时生效,单聊时无效;设置时 --text 中需包含 @userId 对应文本 + - --at-open-dingtalk-ids 仅在 --group 群聊时生效,单聊时无效;设置时 --text 中需包含 @openDingtalkId 对应文本 + - --at-all @所有人,仅群聊时生效;只需带上 --at-all flag 即可,服务端会自动处理 + - userId 获取方式:`dws contact user search --query "姓名"` 搜人获取 userId + - **换行符**:--text 按 Markdown 渲染,换行规则同 `chat message send`: + 1. 必须使用**真实换行符**(`U+000A`),而非字面量 `\n`,否则全部内容会渲染在同一行 + 2. 单个换行不产生换行效果,需用空行(`\n\n`)做段落分隔,或行尾两空格 + 换行/`
` 做硬换行 +``` + +#### 机器人撤回消息(--group 群聊 / 不传为单聊) + +群聊:传 --group 与 --keys;单聊:仅传 --keys。--keys 为发送时返回的 processQueryKey 列表,逗号分隔。 +``` +Usage: + dws chat message recall-by-bot [flags] +Example: + dws chat message recall-by-bot --robot-code --group --keys + dws chat message recall-by-bot --robot-code --keys key1,key2 +Flags: + --group string 群聊 openConversationId(群聊撤回时必填) + --keys string 消息 processQueryKey 列表,逗号分隔 (必填) + --robot-code string 机器人 Code (必填) +``` + +#### 自定义机器人 Webhook 发送群消息 + +@ 人时需在 --text 中包含 @userId 或 @手机号,否则 @ 不生效;@所有人时需在 --text 中包含 @10 并带上 --at-all。 +``` +Usage: + dws chat message send-by-webhook [flags] +Example: + dws chat message send-by-webhook --token --title "告警" --text "CPU 超 90% @10" --at-all + dws chat message send-by-webhook --token --title "test" --text "hi @118785" --at-users 118785 +Flags: + --at-all @ 所有人(需在 --text 中包含 @10) + --at-mobiles string @ 指定手机号,逗号分隔 + --at-users string @ 指定用户,逗号分隔(需在 text 中包含 @userId) + --text string 消息内容 (必填) + --title string 消息标题 (必填) + --token string Webhook Token (必填) + +注意: + - **换行符**:--text 按 Markdown 渲染,换行规则同 `chat message send`: + 1. 必须使用**真实换行符**(`U+000A`),而非字面量 `\n`,否则全部内容会渲染在同一行 + 2. 单个换行不产生换行效果,需用空行(`\n\n`)做段落分隔,或行尾两空格 + 换行/`
` 做硬换行 +``` + +#### 拉取群话题回复消息列表 + +查询指定群聊中某条话题消息的全部回复。--group 指定群会话 ID,--topic-id 指定话题 ID(由 dws chat message list 返回)。 +``` +Usage: + dws chat message list-topic-replies [flags] +Example: + dws chat message list-topic-replies --group --topic-id + dws chat message list-topic-replies --group --topic-id --time "2025-03-01 00:00:00" --limit 20 + dws chat message list-topic-replies --group --topic-id --time "2025-03-01 00:00:00" --direction newer +Flags: + --group string 群会话 openconversationId (必填) + --topic-id string 话题 ID,由 dws chat message list 返回 (必填) + --time string 开始时间,格式: yyyy-MM-dd HH:mm:ss(可选) + --limit int 返回数量(默认 50) + --direction string 时间方向: newer=从给定时间往现在拉,older=从给定时间往以前拉(推荐,默认 older) + --forward 旧兼容参数: true 等价 --direction newer,false 等价 --direction older(默认 false) +``` + +#### 拉取指定时间范围内当前用户的所有会话消息 — 分页拉取当前登录用户在指定时间范围内的所有会话消息 + +--start 和 --end 限定时间范围,--limit 指定每页数量,--cursor 传分页游标(首页传 "0",后续从响应中的 nextCursor 获取)。服务端按 cursor 分页返回,hasMore=true 时用返回的 nextCursor 值作为下次 --cursor 继续翻页。 +``` +Usage: + dws chat message list-all [flags] +Example: + dws chat message list-all --start "2025-03-01 00:00:00" --end "2025-03-31 23:59:59" --limit 50 + dws chat message list-all --start "2025-03-01 00:00:00" --end "2025-03-31 23:59:59" --limit 50 --cursor "abc123token" +Flags: + --start string 起始时间,格式: yyyy-MM-dd HH:mm:ss (必填) + --end string 结束时间,格式: yyyy-MM-dd HH:mm:ss (必填) + --limit int 每页返回数量(默认 50) + --cursor string 分页游标(首页传 "0",后续从响应中的 nextCursor 获取) + +注意: + - 四个参数每次请求都会传递给服务端,cursor 首页传 "0" + - 与 chat message list 的区别:list 拉取指定单个会话(群聊或单聊)的消息,list-all 拉取当前用户所有会话的消息 + - 翻页:hasMore=true 时,用响应中的 nextCursor 值作为下次 --cursor 参数继续翻页 + - 时间格式统一为 yyyy-MM-dd HH:mm:ss + - 权限/权益错误不是空结果;应把返回的 `friendly_hint` 与 `action_url` 展示给用户,不要继续盲目翻页 +``` + +#### 收藏消息 — 为当前用户收藏指定会话中的一条消息 +``` +Usage: + dws chat message add-favorite [flags] +Example: + dws chat message add-favorite --open-message-id --open-conversation-id +Flags: + --open-message-id string 消息 openMessageId (必填) + --open-conversation-id string 消息所在会话 openConversationId (必填) +``` + +#### 取消收藏消息 — 移除当前用户的收藏标记,不删除原消息 +``` +Usage: + dws chat message remove-favorite [flags] +Example: + dws chat message remove-favorite --open-message-id --open-conversation-id +Flags: + --open-message-id string 消息 openMessageId (必填) + --open-conversation-id string 消息所在会话 openConversationId (必填) +``` + +#### 查询收藏消息 — 分页查询当前用户收藏的消息 +``` +Usage: + dws chat message list-favorites [flags] +Example: + dws chat message list-favorites + dws chat message list-favorites --cursor 20 --size 20 +Flags: + --cursor int 数字分页游标,默认 0;翻页时传上次返回的 nextCursor + --size int 一次拉取的收藏数量,默认 20,范围 1-100 + +注意: + - 首次请求可省略分页参数,CLI 会自动使用 cursor=0、size=20 + - hasMore=true 时,将 nextCursor 作为下一次的 --cursor +``` + +#### 拉取指定发送者的消息 — 搜索特定人发送给我的消息(包含单聊和群聊) + +> 推荐优先使用 `chat message search-advanced --user/--users`(userId)或 `--sender-ids`(openDingTalkId),它还能叠加关键词/群/at 等过滤条件。本命令保留给需要旧 list-by-sender 返回结构的场景。 + +搜索特定人发送给我的消息,返回结果包含单聊和群聊标识。--sender-user-id 指定发送者 userId,--sender-open-dingtalk-id 指定发送者 openDingTalkId,二者互斥。分页参数 --limit(默认 50)和 --cursor(默认 "0")始终传递;hasMore=true 时用返回的 nextCursor 作为下次 --cursor 继续翻页。 +``` +Usage: + dws chat message list-by-sender [flags] +Example: + dws chat message list-by-sender --sender-user-id --start "2026-03-10T00:00:00+08:00" --end "2026-03-11T00:00:00+08:00" --limit 50 --cursor 0 + 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 0 + dws chat message list-by-sender --sender-user-id --start "2026-03-10T00:00:00+08:00" --end "2026-03-10T23:59:59+08:00" --limit 20 --cursor 0 + 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 的场景) + --start string 开始时间,ISO-8601 格式 (必填) + --end string 结束时间,ISO-8601 格式 (必填) + --limit int 每页返回数量(默认 50) + --cursor string 分页游标(默认 "0",翻页传 nextCursor) + +注意: + - --sender-user-id 和 --sender-open-dingtalk-id 二者互斥,必须且只能指定其一: + - --sender-user-id 传 userId(企业内部应用常用) + - --sender-open-dingtalk-id 传 openDingTalkId(三方应用或跨组织场景常用,无法获取 userId 时使用) + - openDingTalkId 获取方式见下方「openDingTalkId 获取方式」小节 + - 不需要指定单聊/群聊,返回结果自带会话类型标识 + - 时间支持多种 ISO-8601 格式,如 "2026-03-10T00:00:00+08:00"、"2026-03-10 14:00:00"、"2026-03-10" 等 + - 翻页:hasMore=true 时,用返回的 nextCursor 作为下次 --cursor +``` + +#### 拉取 @我 的消息 — 搜索时间范围内 @我 的消息 + +> 推荐使用 `chat message search-advanced --at-me`,它还能叠加关键词/群/发送者等过滤条件。本命令适用于仅需拉取 @我 消息的简单场景。 + +搜索时间范围内 @我 的消息,可选指定群聊。返回结果包含单聊和群聊标识。分页参数 --limit(默认 50)和 --cursor(默认 "0")始终传递;hasMore=true 时用返回的 nextCursor 作为下次 --cursor 继续翻页。 +``` +Usage: + dws chat message list-mentions [flags] +Example: + dws chat message list-mentions --start "2026-03-10T00:00:00+08:00" --end "2026-03-11T00:00:00+08:00" --limit 50 --cursor 0 + dws chat message list-mentions --start "2026-04-01T00:00:00+08:00" --end "2026-04-14T00:00:00+08:00" --limit 20 --cursor 0 + dws chat message list-mentions --group --start "2026-03-10T00:00:00+08:00" --end "2026-03-11T00:00:00+08:00" --limit 50 --cursor 0 + dws chat message list-mentions --start "2026-03-10T00:00:00+08:00" --end "2026-03-11T00:00:00+08:00" --limit 50 --cursor +Flags: + --group string 群聊 openconversation_id(可选,不传则查全部) + --start string 开始时间,ISO-8601 格式 (必填) + --end string 结束时间,ISO-8601 格式 (必填) + --limit int 每页返回数量(默认 50) + --cursor string 分页游标(默认 "0",翻页传 nextCursor) + +注意: + - --group 可选,不传则查询所有会话中 @我 的消息;传入则只查指定群聊 + - --group 的别名: --id, --chat, --conversation-id (均可替代 --group) + - 时间支持多种 ISO-8601 格式,如 "2026-03-10T00:00:00+08:00"、"2026-03-10 14:00:00"、"2026-03-10" 等 + - 翻页:hasMore=true 时,用返回的 nextCursor 作为下次 --cursor +``` + +#### 拉取特别关注人的消息 + +拉取当前用户特别关注人的消息。分页参数 --limit 指定每页数量,--cursor 传分页游标(首次不传或传 0)。返回结果中 hasMore=true 时用 nextCursor 作为下次 --cursor 继续翻页。 +``` +Usage: + dws chat message list-focused [flags] +Example: + dws chat message list-focused --limit 50 + dws chat message list-focused --limit 20 --cursor +Flags: + --limit int 每页返回数量(默认 50) + --cursor int64 分页游标(首次不传或传 0,翻页传 nextCursor) + +注意: + - 首次调用不传 --cursor 或传 0,后续翻页传 nextCursor +``` + +#### 获取未读会话列表 + +获取当前用户有未读消息的会话信息。可选通过 `--count` 限制返回条数,不传则使用服务端默认值。 +``` +Usage: + dws chat message list-unread-conversations [flags] +Example: + dws chat message list-unread-conversations + dws chat message list-unread-conversations --count 20 +Flags: + --count int 返回未读会话条数(可选) + --exclude-muted 是否排除已设置免打扰的会话(默认 false) +``` + +#### 查询消息的已读/未读状态 + +查询指定会话中消息的已读/未读状态(仅消息发送者可查询自己发出的消息)。--conversation-id 指定会话 openConversationId(群聊或单聊均可),--message-id 指定消息 ID(由 dws chat message list 返回的 openMessageId,必须是当前用户发送的消息)。目标用户 userId 使用 --user/--users;目标用户 openDingTalkId 使用 --target-open-dingtalk-ids;不传目标用户则返回所有接收者的状态。 +``` +Usage: + dws chat message read-status [flags] +Example: + dws chat message read-status --conversation-id --message-id + dws chat message read-status --conversation-id --message-id --user userId1,userId2 + dws chat message read-status --conversation-id --message-id --users userId1,userId2 + dws chat message read-status --conversation-id --message-id --target-open-dingtalk-ids openDingTalkId1,openDingTalkId2 +Flags: + --conversation-id string 会话 openConversationId (必填,群聊或单聊均可) + --message-id string 消息 openMessageId,由 chat message list 返回 (必填,必须是当前用户发送的消息) + --user string 目标用户 userId,支持逗号分隔(可选,不传则查所有接收者) + --users string 目标用户 userId 列表,逗号分隔(可选,不传则查所有接收者) + --target-open-dingtalk-ids string 目标用户 openDingTalkId 列表,逗号分隔(可选,不传则查所有接收者) + +注意: + - 仅消息发送者可查询自己发出的消息的已读/未读状态,查询他人发的消息会报错 + - --conversation-id 的别名: --group, --id, --chat (均可替代 --conversation-id) + - --message-id 从 dws chat message list 返回的消息列表中获取(字段名 openMessageId) + - --user / --users 传目标用户 userId + - --target-open-dingtalk-ids 不传时返回该消息所有接收者的已读状态;传入则只返回指定 openDingTalkId 用户的状态 +``` + +#### 按关键词搜索消息 — 在当前用户的会话中按关键词搜索消息 + +> 推荐优先使用 `chat message search-advanced`,它是本命令的严格超集:query 可选(非必填)、支持多个会话(非单个)、还能叠加发送者/at 等维度过滤。 + +按关键词搜索消息内容。--query 指定搜索关键词(必填)。可选 --group 限定搜索某个会话,不传则搜索所有会话。时间参数 --start/--end(ISO-8601)限定搜索时间范围。分页参数 --limit(默认 100)和 --cursor(默认 "0")始终传递;hasMore=true 时用返回的 nextCursor 作为下次 --cursor 继续翻页。 +``` +Usage: + dws chat message search [flags] +Example: + dws chat message search --query "changefree" --start "2026-04-01T00:00:00+08:00" --end "2026-04-15T00:00:00+08:00" --limit 50 --cursor 0 + dws chat message search --query "codereview" --group --start "2026-04-01T00:00:00+08:00" --end "2026-04-15T00:00:00+08:00" --limit 100 --cursor 0 + dws chat message search --query "链接" --start "2026-04-15T00:00:00+08:00" --end "2026-04-16T00:00:00+08:00" --limit 100 --cursor +Flags: + --query string 搜索关键词 (必填) + --group string 群聊 openconversation_id(可选,不传则搜索所有会话) + --start string 开始时间,ISO-8601 格式 (必填) + --end string 结束时间,ISO-8601 格式 (必填) + --limit int 每页返回数量(默认 100) + --cursor string 分页游标(默认 "0",翻页传 nextCursor) + +注意: + - --group 可选,不传则搜索所有会话中的消息;传入则只搜索指定会话 + - --group 的别名: --id, --chat, --conversation-id (均可替代 --group) + - 时间支持多种 ISO-8601 格式,如 "2026-03-10T00:00:00+08:00"、"2026-03-10 14:00:00"、"2026-03-10" 等 + - 翻页:hasMore=true 时,用返回的 nextCursor 作为下次 --cursor +``` + +#### 多维度搜索消息(推荐首选) — 支持按关键词、发送者、@我、@指定人、指定会话、时间范围、消息类型、会话类型等多维度搜索 + +> 推荐:这是消息搜索的首选接口。它可以完全替代 `chat message search`(query 可选 vs 必填,支持多个会话 vs 单个),大部分替代 `chat message list-by-sender`(通过 --user/--users 按 userId 搜索发送者,或通过 --sender-ids 按 openDingTalkId 搜索)和 `chat message list-mentions`(通过 --at-me 搜索@我的消息)。仅在拉取「特别关注人」消息时需要退回 `list-focused`。 + +支持按关键词、发送者、@我、@指定人、指定会话、时间范围、消息类型、会话类型等多维度搜索消息。发送者 userId 使用 --user/--users;发送者或 @ 人的 openDingTalkId 使用 --sender-ids/--at-ids。所有参数均为可选,至少指定一个搜索条件。 +``` +Usage: + dws chat message search-advanced [flags] +Example: + dws chat message search-advanced --query "周报" --start "2026-04-01T00:00:00+08:00" --end "2026-04-15T00:00:00+08:00" + dws chat message search-advanced --user --start "2026-04-01T00:00:00+08:00" --end "2026-04-15T00:00:00+08:00" + dws chat message search-advanced --users , --start "2026-04-01T00:00:00+08:00" --end "2026-04-15T00:00:00+08:00" + dws chat message search-advanced --sender-ids , --start "2026-04-01T00:00:00+08:00" --end "2026-04-15T00:00:00+08:00" + dws chat message search-advanced --at-me --start "2026-04-01T00:00:00+08:00" --end "2026-04-15T00:00:00+08:00" + dws chat message search-advanced --at-ids , --conversation-ids , --limit 50 --cursor 0 + dws chat message search-advanced --conversation-ids <单聊openConversationId> --query "合同" --start "2026-04-01T00:00:00+08:00" --end "2026-04-15T00:00:00+08:00" + dws chat message search-advanced --message-type file --search-conv-type group_chat --query "附件" + dws chat message search-advanced --only-robot-messages --query "通知" + # 查询群 ID: dws chat search --query "群名" + # 查询单聊会话 ID: dws chat conversation-info --user + # 查询人员: dws contact user search --keyword "姓名" --format json +Flags: + --query string 搜索关键词(可选) + --user string 发送者 userId,支持逗号分隔(可选) + --users string 发送者 userId 列表,逗号分隔(可选) + --sender-ids string 发送者 openDingTalkId 列表,逗号分隔(可选) + --at-me 只搜索 @我 的消息(可选,默认 false) + --at-ids string @指定人的 openDingTalkId 列表,逗号分隔(可选) + --conversation-ids string 会话 openConversationId 列表,逗号分隔(可选,群聊或单聊均可,不传则搜索所有会话) + --message-type string 消息类型筛选(可选,支持 file/image/video/audio/link) + --search-conv-type string 会话类型筛选(可选,single_chat=单聊, group_chat=群聊) + --start string 开始时间,ISO-8601 格式(可选) + --end string 结束时间,ISO-8601 格式(可选) + --cursor string 分页游标(默认 "0") + --limit int 每页返回数量(默认 100) + --only-robot-messages 仅搜索机器人消息(可选,默认 false) + --conversation-ids 的别名: --groups + +注意: + - 所有参数均为可选,但至少需要指定一个搜索条件 + - --user / --users 传发送者 userId + - --sender-ids 和 --at-ids 传 openDingTalkId + - --conversation-ids 可指定多个会话 ID(群聊或单聊均可),逗号分隔,不传则搜索所有会话 + - 群聊 openConversationId 通过 `dws chat search --query "群名"` 获取 + - 单聊 openConversationId 通过 `dws chat conversation-info --user ` 或 `--open-dingtalk-id ` 获取 + - 时间支持多种 ISO-8601 格式,如 "2026-03-10T00:00:00+08:00"、"2026-03-10 14:00:00"、"2026-03-10" 等 + - 翻页:hasMore=true 时,用返回的 nextCursor 作为下次 --cursor + - 替代关系:完全替代 search(严格超集);大部分替代 list-by-sender(--user 覆盖按 userId 搜索发送者,--sender-ids 覆盖按 openDingTalkId 搜索)和 list-mentions(--at-me 覆盖核心功能);不能替代 list-focused(「特别关注」是独立维度) +``` + +#### 根据消息 ID 批量查询消息 +``` +Usage: + dws chat message list-by-ids [flags] +Example: + dws chat message list-by-ids --msg-ids msgId1,msgId2,msgId3 + # 最多传 50 条消息 ID +Flags: + --msg-ids string 消息 ID 列表,逗号分隔,最多 50 条 (必填) +``` + +#### 表情回应选择策略 + +> 贴表情时,优先查 [chat-emoji-list.md](chat-emoji-list.md) 中的默认表情名称(共 199 个,如「赞」「鼓掌」「感谢」等): +> - 命中 → 使用 `add-emoji --emoji `(直接贴 emoji) +> - 未命中 → 先 `create-text-emotion` 创建文字表情获取 emotionId,再 `add-text-emotion` 贴文字表情 + +#### 对消息添加 emoji 表情回应 +``` +Usage: + dws chat message add-emoji [flags] +Example: + dws chat message add-emoji --conversation-id --msg-id --emoji "赞" + dws chat message add-emoji --conversation-id --msg-id --emoji "鼓掌" + # --emoji 的值必须是 chat-emoji-list.md 中的 name(中文名),如:赞、鼓掌、感谢、微笑 等 + # 查询会话 ID: dws chat search --query "群名" +Flags: + --conversation-id string 会话 openConversationId (必填,支持单聊/群聊,别名: --group / --id / --chat) + --msg-id string 消息 openMsgId (必填) + --emoji string emoji 表情名称,必须是默认表情列表中的 name 值 (必填,参见 chat-emoji-list.md) +``` + +#### 移除消息的 emoji 表情回应 +``` +Usage: + dws chat message remove-emoji [flags] +Example: + dws chat message remove-emoji --conversation-id --msg-id --emoji "赞" + # 查询会话 ID: dws chat search --query "群名" +Flags: + --conversation-id string 会话 openConversationId (必填,支持单聊/群聊,别名: --group / --id / --chat) + --msg-id string 消息 openMsgId (必填) + --emoji string emoji 表情名称,必须是默认表情列表中的 name 值 (必填,参见 chat-emoji-list.md) +``` + +#### 对消息添加文字表情回应(当默认表情列表中没有所需表情时使用) +``` +Usage: + dws chat message add-text-emotion [flags] +Example: + dws chat message add-text-emotion --conversation-id --msg-id --emotion-id --emotion-name "赞" --text "nice" --background-id im_bg_5 +Flags: + --conversation-id string 会话 openConversationId (必填,支持单聊/群聊,别名: --group / --id / --chat) + --msg-id string 消息 openMsgId (必填) + --emotion-id string 表情 ID (必填,通过 create-text-emotion 或已知表情获取) + --emotion-name string 表情名称 (必填) + --text string 文字内容 (必填) + --background-id string 背景 ID (必填) +``` + +#### 移除消息的文字表情回应 +``` +Usage: + dws chat message remove-text-emotion [flags] +Example: + dws chat message remove-text-emotion --conversation-id --msg-id --emotion-id --emotion-name "赞" --text "nice" --background-id +Flags: + --conversation-id string 会话 openConversationId (必填,支持单聊/群聊,别名: --group / --id / --chat) + --msg-id string 消息 openMsgId (必填) + --emotion-id string 表情 ID (必填) + --emotion-name string 表情名称 (必填) + --text string 文字内容 (必填) + --background-id string 背景 ID (必填) +``` + +#### 创建文字表情(获取 emotionId)— 当 chat-emoji-list.md 中没有所需表情时,先创建再贴 +``` +Usage: + dws chat message create-text-emotion [flags] +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 +Flags: + --emotion-name string 表情名称 (必填) + --text string 文字内容 (必填) + --background-id string 背景 ID(可选,不传则由服务端默认分配) + +注意: + - 创建后返回 emotionId,可用于 add-text-emotion 命令 + - 如果已有合适的表情,无需创建新的 +``` + +#### 批量拉取消息的表情回复和文字回复 + +根据消息 ID 列表批量查询消息的表情回复和文字回复信息。 +``` +Usage: + dws chat message list-emotion-replies [flags] +Example: + dws chat message list-emotion-replies --msg-ids msgId1,msgId2,msgId3 + # 消息 ID 可通过 dws chat message list 获取 +Flags: + --msg-ids string 消息 ID 列表,逗号分隔 (必填) + +注意: + - 支持批量查询多条消息的表情和文字回复 + - 消息 ID 可通过 `dws chat message list` 命令获取 +``` diff --git a/skills/multi/dingtalk-chat/references/chat-notes.md b/skills/multi/dingtalk-chat/references/chat-notes.md new file mode 100644 index 00000000..62e3ba04 --- /dev/null +++ b/skills/multi/dingtalk-chat/references/chat-notes.md @@ -0,0 +1,67 @@ +# 注意事项 + +## 注意事项 + +- **发消息前参数审查(必须执行)**: + - 发消息(`chat message send`、`send-by-bot`、`send-by-webhook`、`send-card`、`reply`、`forward`)是严肃操作,一旦发错人/发错群会导致严重问题,因此在执行发送之前,agent 必须对所有参数进行内部审查 + - 审查方式:将即将发送的**全部参数**(收件人/群、消息内容、@对象、消息类型等)与用户的**原始需求**逐一对比,确认每个参数都能从原始需求中找到明确依据 + - 如果存在任何不明确、有歧义或原始需求中未提及的参数(例如:用户没说发给谁、没说发到哪个群、消息内容与用户意图有出入、不确定是否需要 @某人等),**必须先向用户确认**,严禁自行假设或补全 + - 典型需要确认的场景:用户只说了"发个消息"但没指定群/人;用户的描述可匹配多个群或多个联系人;消息文本由 agent 组织而非用户原文提供时需确认措辞 +- uuid 幂等参数(发消息最佳实践): + - 发消息时建议始终带上 `--uuid` 参数,传入用户自行生成的唯一标识(如 UUID v4),用于幂等控制 + - 如果发送失败需要重试,重试时 `--uuid` 必须与首次发送保持一致,服务端据此去重,避免重复发消息 + - 如果不传 `--uuid`,每次调用都视为新消息,重试可能导致消息重复发送 + - 此参数适用于 `chat message send`(群聊和单聊均支持) +- `--group` 为群聊会话 ID (openconversation_id),可从群搜索或群聊信息中获取 +- `chat message send` 的 text 是位置参数(恰好 1 个),非 flag;群聊用 `--group`,单聊用 `--user`(userId)或 `--open-dingtalk-id`(openDingTalkId),三者互斥;纯文本/Markdown 单聊传 `--user` 时直接走 userId 发送能力;`--at-all`、`--at-open-dingtalk-ids` 仅在 `--group` 群聊时生效;**发图片/本地文件统一 `--msg-type file --file-path <本地路径>`**(任意扩展名 png/jpg/pdf/mp4/zip… 一条命令直发,CLI 内部完成上传);`--msg-type image --media-id` 仅作旧链路兼容 +- `chat message list-all` 的四个参数(--start、--end、--limit、--cursor)每次请求都必须传递;翻页时用响应中的 nextCursor 值作为下次 --cursor +- `chat message list` 的 `--group`、`--user`、`--open-dingtalk-id` 三者互斥,必须且只能指定其一 +- `chat message list-by-sender` 不需要指定单聊/群聊,返回结果自带会话类型标识;`--sender-user-id`(userId)与 `--sender-open-dingtalk-id`(openDingTalkId)二选一;时间用 `--start`/`--end`(ISO-8601),分页用 `--limit`/`--cursor` +- `chat message list-mentions` 可选 `--group` 指定群聊,不传则查全部;时间用 `--start`/`--end`(ISO-8601),分页用 `--limit`/`--cursor` +- `chat message list-unread-conversations` 获取当前用户未读会话列表,可选 `--count` 指定返回条数 +- `chat message search` 按关键词搜索消息内容,`--query` 必填,可选 `--group` 限定搜索某个会话;时间用 `--start`/`--end`(ISO-8601),分页用 `--limit`(默认 100)/`--cursor` +- `chat message read-status` 查询指定消息的已读/未读状态,仅消息发送者可查询自己发出的消息;`--group`、`--message-id` 必填;目标用户 userId 用 `--user`/`--users`,openDingTalkId 用 `--target-open-dingtalk-ids`,不传则查所有接收者 +- `chat search-common` 搜索共同群,`--nicks` 传人员昵称(逗号分隔),`--match-mode` AND/OR 控制匹配逻辑,分页用 `--limit`(默认 20)/`--cursor` +- `chat list-top-conversations` 拉取置顶会话列表,分页用 `--limit`(默认 1000)/`--cursor`;用户询问"置顶会话"时路由到此命令 +- `--user` 和 `--open-dingtalk-id` 本质上都是发起单聊操作,只是用户标识格式不同:userId 为企业内部应用常用标识,openDingTalkId 为三方应用或跨组织场景下的用户标识,服务端对两种 ID 的解析逻辑不同 +- `--time` 格式: `yyyy-MM-dd HH:mm:ss`,为拉取消息的起始时间点;拉取方向优先使用 `--direction newer|older`:`newer` 从给定时间往现在拉,`older` 从给定时间往以前拉;`--forward` 为旧兼容参数,`true` 等价 `newer`,`false` 等价 `older`;`--limit` 控制数量 +- `chat search` 挂在 `chat` 下(非 `chat group` 下),路径为 `dws chat search` +- `send-by-bot` 群聊传 `--group`,单聊传 `--users` 或 `--open-dingtalk-ids`,与 `--group` 互斥且必选其一;群聊时可选 `--at-user-ids` @指定成员(传 userId 列表)或 `--at-open-dingtalk-ids` @指定成员(传 openDingtalkId 列表),content 中需包含对应 @标识;`--at-all` @所有人;群聊场景如果返回"机器人不存在"错误,需先通过 `chat group members add-bot --group --robot-code ` 将机器人邀请进群后再发送 +- `recall-by-bot` 群聊传 `--group` + `--keys`,单聊仅传 `--keys`(不传 `--group` 即为单聊撤回) +- `send-by-webhook` 支持 `--at-all`、`--at-mobiles`、`--at-users` 进行 @ 操作,但需在 `--text` 中包含 `@userId` 或 `@手机号` 才能生效;`--at-all` @所有人时需在 `--text` 中包含 `@10` +- `chat group-role` 系列命令用于管理群的自定义身份标签:`list` 查列表,`add` 创建,`update` 改名,`remove` 删除;`set-user` 覆盖某人全部身份(传空 --role-ids 则清除),`remove-user` 仅移除指定身份,`query-user` 查询某人当前身份;用户用 `--user ` +- 消息**换行符**(`send` / `send-by-bot` / `send-by-webhook` 的 `--text`)有两层要求:(1) 必须是**真实换行符** `U+000A`,不是字面量 `\n`;(2) Markdown 规范下单换行不生效,需用空行 `\n\n`(段落分隔)或行尾两空格 + 换行 / `
`(硬换行) +- `chat group transfer-owner` 转让群主,需传 --group(openConversationId);新群主 userId 用 `--user`,openDingTalkId 用 `--new-owner` +- `chat group invite-url` 获取群邀请链接,需传 --group(openConversationId),可选 --expires-seconds 指定有效期(秒,0=永久) +- `chat group share-invite` 分享群聊链接到会话,需传 --source(被分享群 openConversationId),--target(目标会话)和 --receiver(单聊用户)二选一,可选 --expires-seconds 和 --uuid +- `chat category create-smart` 创建智能会话分组,需传 --name(分组名称),可选 --keywords(群名称关键词)和 --members(群内成员 openDingTalkId) +- `chat group quit` 退出群聊,需传 --group(openConversationId) +- `chat group update-icon` 更新群头像,需传 --group(openConversationId)和 --icon-media-id(mediaId) +- `chat group update-settings` 更新群设置,需传 --group(openConversationId)、--setting-key(设置项 key)、--status(0=关闭 1=开启) +- `chat message send-card` 创建并推送流式卡片,群聊传 --group,单聊传 --receiver,二者互斥;不传 content,后续通过 update-card 更新内容 +- `chat message update-card` 流式更新卡片内容,需传 --biz-id(创建卡片返回的业务 ID)、--content、--flow-status +- `chat message list-by-ids` 根据消息 ID 批量查询,--msg-ids 逗号分隔,最多 50 条 +- `chat message add-emoji` / `remove-emoji` 需传 --group(openConversationId)、--msg-id(openMsgId)、--emoji(表情名称) +- `chat message add-text-emotion` / `remove-text-emotion` 需传 --group、--msg-id、--emotion-id、--emotion-name、--text、--background-id,六个参数全部必填 +- `chat message create-text-emotion` 创建文字表情模板,返回 emotionId;--background-id 可选,不传由服务端默认分配 +- `chat category list` 无需参数;`category list-conversations` 需传 --category-id(通过 category list 获取) +- `chat mute` 默认开启免打扰,传 --off 关闭;--conversation-id / --id / --chat 三个别名均可用于传入会话 ID +- `chat message reply` 引用回复消息(**单聊/群聊均可**),需传 --conversation-id(openConversationId,单聊与群聊使用同一字段)、--ref-msg-id(被引用消息 openMessageId)、--ref-sender(被引用消息发送者 openDingTalkId)、--text(回复内容);目前回复类型仅支持 text +- `chat message forward` 转发单条消息(**源/目标会话均支持单聊/群聊**,常见组合:群→群、群→单、单→群、单→单),需传 --src-conversation-id(源会话 openConversationId)、--msg-id(源消息 openMessageId)、--dest-conversation-id(目标会话 openConversationId) +- `chat message forward-topic` 转发话题消息,需传 --src-msg-id(源消息 openMessageId)、--src-conversation-id(源会话 openConversationId)、--src-thread-id(话题 ID,格式: convThread + 加密后的convThreadId)、--dest-conversation-id(目标会话 openConversationId) +- `chat set-top` 设置/取消会话置顶(单聊/群聊均可),需传 --conversation-id(openConversationId,单聊与群聊使用同一字段),默认置顶,传 --off 取消 +- `chat message set-top-msg` 置顶会话内某条消息,需传 --open-conversation-id(openConversationId)、--msg-id(openMessageId);与 chat set-top 区别:后者是会话列表置顶,前者是消息置顶 +- `chat message unset-top-msg` 取消置顶会话内某条消息,需传 --open-conversation-id(openConversationId)、--msg-id(openMessageId) +- `chat message reply` 以当前用户身份引用回复,与 `chat message send` 的用户身份发送语义一致 +- **如何获取 openConversationId**(如果上层已有则直接使用,不必再查): + - 群聊:`dws chat search --query "群名"` + - 单聊:`dws chat conversation-info --user ` 或 `dws chat conversation-info --open-dingtalk-id `(人员信息可通过 `dws contact user search --keyword "姓名" --format json` 获取) +- `chat group-mute` 全员禁言/取消全员禁言,需传 --group(openConversationId),默认禁言,传 --off 取消 +- `chat group-mute-member` 指定群成员禁言,需传 --group、--user/--users(userId,逗号分隔)、--mute-time(毫秒,仅禁言时必填,支持 300000/3600000/86400000/604800000/2592000000),传 --off 解除禁言 +- `chat group set-admin` 设置/取消群管理员,需传 --group(openConversationId)、--user/--users(userId,逗号分隔),默认设为管理员,传 --off 取消 +- `chat group update-nick` 设置用户在群内的群昵称,需传 --group(openConversationId)、--nick(群昵称) +- `chat group update-alias` 设置群备注,需传 --group(openConversationId)、--alias-title(备注名称),仅自己可见 +- `chat hide` 隐藏会话,需传 --conversation-id(openConversationId,支持单聊/群聊),隐藏后不显示在列表中,收到新消息时重新出现 +- `chat mute-at-all` 关闭/开启 @所有人消息提醒,需传 --conversation-id(openConversationId),默认关闭通知,传 --off 恢复接收 +- `chat mute-red-envelope` 关闭/开启红包消息提醒,需传 --conversation-id(openConversationId),默认关闭通知,传 --off 恢复接收 +- `chat group members list-by-ids` 根据成员 openDingTalkId 批量查询群成员详情,需传 --id(openConversationId)、--users(openDingTalkId 列表,逗号分隔) diff --git a/skills/multi/dingtalk-chat/references/chat-related.md b/skills/multi/dingtalk-chat/references/chat-related.md new file mode 100644 index 00000000..894e845b --- /dev/null +++ b/skills/multi/dingtalk-chat/references/chat-related.md @@ -0,0 +1,32 @@ +# 相关产品 + +## 相关产品 + +- [contact](../../dingtalk-contact/references/contact.md) — 搜索同事/好友,获取 userId 用于 --user、send-by-bot --users、send-by-bot --at-user-ids、list-by-sender --sender-user-id;获取 openDingTalkId 用于 message send 的 --at-open-dingtalk-ids、--open-dingtalk-id、send-by-bot --open-dingtalk-ids、send-by-bot --at-open-dingtalk-ids、list-by-sender 的 --sender-open-dingtalk-id +- [drive](../../dingtalk-drive/references/drive.md) — 云盘文件管理;chat 纯文件发送优先用 `chat message send --msg-type file --file-path` + +### text translate (文本翻译) + +#### 翻译文本内容 — 将指定文本翻译成目标语言 +``` +Usage: + dws chat text translate [flags] +Example: + dws chat text translate --query "你好世界" --to en_US + dws chat text translate --query "Hello World" --to zh_CN + dws chat text translate --query "Bonjour" --to ja_JP +Flags: + --query string 待翻译的文本内容 (必填) + --to string 目标语言代码 (必填,默认 en_US) +``` + +支持的目标语言代码: +en_US, zh_CN, zh_TW, zh_HK, ja_JP, ko_KR, vi_VN, th_TH, +id_ID, ms_MY, es_419, fr_FR, pt_BR, tr_TR, ru_RU, de_DE, +hi_IN, hu_HU, pl_PL, sv_SE, fi_FI, cs_CZ, ar_SA, tl_PH, +he_IL, nl_NL, lo_LA, it_IT + +注意: +- `--to` 参数必须使用上述支持的语言代码,大小写敏感 +- 默认目标语言为 en_US(美式英语) +- 返回结果为翻译后的纯文本内容 diff --git a/skills/multi/dingtalk-chat/references/chat-scripts.md b/skills/multi/dingtalk-chat/references/chat-scripts.md new file mode 100644 index 00000000..0ab1f5a9 --- /dev/null +++ b/skills/multi/dingtalk-chat/references/chat-scripts.md @@ -0,0 +1,9 @@ +# 自动化脚本 + +## 自动化脚本 + +| 脚本 | 场景 | 用法 | +|------|------|------| +| [chat_export_messages.py](../scripts/chat_export_messages.py) | 导出群聊消息到 JSON 文件 | `python chat_export_messages.py --query "项目冲刺" --time "2026-03-10 00:00:00"` | +| [chat_history_with_user.py](../scripts/chat_history_with_user.py) | 查询与某人的单聊聊天记录 | `python chat_history_with_user.py --name "张三" --time "2026-03-10 00:00:00"` | +| [extract_media_id.py](../scripts/extract_media_id.py) | **旧链路** — 仅当上游已通过 `dt_media_upload` 拿到 URL 时使用,从该 URL 提取 mediaId | `python extract_media_id.py ""`(输出如 @lQLPxxx,用于 `--msg-type image --media-id`;新场景请直接用 `--msg-type file --file-path`) | diff --git a/skills/multi/dingtalk-chat/references/chat-workflow.md b/skills/multi/dingtalk-chat/references/chat-workflow.md new file mode 100644 index 00000000..c7100b6d --- /dev/null +++ b/skills/multi/dingtalk-chat/references/chat-workflow.md @@ -0,0 +1,35 @@ +# 核心工作流 + +## 核心工作流 + +```bash +# 1. 搜索群 — 提取 openconversation_id +dws chat search --query "项目冲刺" --format json + +# 2. 拉取群消息 +dws chat message list --group --time "2025-03-01 00:00:00" --format json + +# 2b. 拉取未读会话列表 +dws chat message list-unread-conversations --count 20 --format json + +# 3. 以个人身份发送群消息 +dws chat message send --group --title "周报提醒" "请大家本周五前提交周报" --format json + +# 4. 以个人身份单聊(通过 userId) +dws chat message send --user "你好" --format json + +# 4b. 以个人身份单聊(通过 openDingTalkId,三方应用等无法获取 userId 时使用) +dws chat message send --open-dingtalk-id "你好" --format json + +# 5. 机器人发群消息(Markdown) +dws chat message send-by-bot --robot-code \ + --group --title "日报" --text "## 今日完成..." --format json + +# 6. 机器人单聊发消息 +dws chat message send-by-bot --robot-code \ + --users userId1,userId2 --title "提醒" --text "请提交周报" --format json + +# 7. Webhook 发告警 +dws chat message send-by-webhook --token \ + --title "告警" --text "CPU 超 90% @10" --at-all --format json +``` diff --git a/skills/multi/dingtalk-chat/references/chat.md b/skills/multi/dingtalk-chat/references/chat.md index 94a7acc1..3025c514 100644 --- a/skills/multi/dingtalk-chat/references/chat.md +++ b/skills/multi/dingtalk-chat/references/chat.md @@ -1,2191 +1,10 @@ # 会话与群聊 (chat) 命令参考 -## 命令总览 - -### group (群组管理) - -#### 创建群 — 当前登录用户自动成为群主 -``` -Usage: - dws chat group create [flags] -Example: - dws chat group create --name "Q1 项目冲刺群" --users userId1,userId2,userId3 -Flags: - --users string 成员 userId 列表,用户本身会自动加入,无需包含,逗号分隔,不超过20个 (必填) - --name string 群名称 (必填) -``` - -#### 查看群成员列表 — 分页查询指定群聊的成员 -``` -Usage: - dws chat group members [flags] -Example: - dws chat group members --id -Flags: - --cursor string 分页游标,首次从 0 开始 - --id string 群 ID / openconversation_id (必填) -``` - -#### 添加群成员 — 向指定群聊添加成员,需传入群 ID 与用户 ID 列表 -``` -Usage: - dws chat group members add [flags] -Example: - dws chat group members add --id --users userId1,userId2 -Flags: - --id string 群 ID / openconversation_id (必填) - --users string 要添加的用户 userId 列表,逗号分隔 (必填) -``` - -#### 移除群成员 — 从指定群聊中移除成员,需传入群 ID 与待移除的用户 ID 列表 -``` -Usage: - dws chat group members remove [flags] -Example: - dws chat group members remove --id --users userId1,userId2 -Flags: - --id string 群 ID / openconversation_id (必填) - --users string 要移除的用户 userId 列表,逗号分隔 (必填) -``` - -> 移除对象是群主时 CLI 会拦截并报错,提示先 `chat group transfer-owner` 转让群主(查不到群主时放行)。 - -#### 将机器人添加到群中 — 将自定义机器人添加到当前用户有管理权限的群聊中,如果没有权限则会报错 -``` -Usage: - dws chat group members add-bot [flags] -Example: - dws chat group members add-bot --robot-code --id -Flags: - --id string 群聊 openConversationId (必填) - --robot-code string 机器人 Code (必填) -``` - -#### 从群内移除机器人 — 将指定机器人从群聊中移除,需要群管理员或群主权限 -``` -Usage: - dws chat group members remove-bot [flags] -Example: - dws chat group members remove-bot --id --bot-id - # 查询群 ID: dws chat search --query "群名" - # 查询群内机器人: dws chat group bots --group -Flags: - --id string 群聊 openConversationId (必填) - --bot-id string 机器人 openBotId (必填) -``` - -#### 根据成员 ID 批量查询群成员详情 — 传入成员 openDingTalkId 列表批量查询 -``` -Usage: - dws chat group members list-by-ids [flags] -Example: - dws chat group members list-by-ids --id --users openDingTalkId1,openDingTalkId2 - # 查询群 ID: dws chat search --query "群名" - # 查询 openDingTalkId: dws contact user search --query "姓名" -Flags: - --id string 群 ID / openConversationId (必填) - --users string 成员 openDingTalkId 列表,逗号分隔 (必填) -``` - -#### 更新群名称 -``` -Usage: - dws chat group rename [flags] -Example: - dws chat group rename --id --name "新群名" -Flags: - --id string 群 ID / openconversation_id (必填) - --name string 修改后的群名称 (必填) -``` - -#### 根据群号获取群聊信息 — 当用户只提供了数字群号而非 openConversationId 时,用此命令转换 -``` -Usage: - dws chat group get-by-group-id [flags] -Example: - dws chat group get-by-group-id --group-id 12345678 - # 群号为数字类型的群ID -Flags: - --group-id int 群号 (必填,数字类型) -``` - -#### 转让群主 — 将群主身份转让给群内其他成员 -``` -Usage: - dws chat group transfer-owner [flags] -Example: - dws chat group transfer-owner --group --new-owner - dws chat group transfer-owner --group --user - # 查询群 ID: dws chat search --query "群名" - # 查询人员: dws aisearch person --keyword "姓名" --dimension name -Flags: - --group string 群聊 openConversationId (必填) - --new-owner string 新群主 openDingTalkId - --user string 新群主 userId -``` - -#### 获取群邀请链接 — 获取指定群聊的邀请加入链接 - -可选 --expires-seconds 指定链接有效期(秒),0 表示永久有效,不传则使用服务端默认值。 -``` -Usage: - dws chat group invite-url [flags] -Example: - dws chat group invite-url --group - dws chat group invite-url --group --expires-seconds 86400 - dws chat group invite-url --group --expires-seconds 0 - # 查询群 ID: dws chat search --query "群名" -Flags: - --group string 群聊 openConversationId (必填) - --expires-seconds int64 链接有效期(秒),0 表示永久有效,不传使用服务端默认值 -``` - -#### 分享群聊链接到会话 — 将指定群的邀请链接分享到另一个会话或单聊用户 - -`--target` 和 `--receiver` 二选一:`--target` 指定目标会话,`--receiver` 指定单聊用户。 - -``` -Usage: - dws chat group share-invite [flags] -Example: - dws chat group share-invite --source <被分享群openConversationId> --target <目标会话openConversationId> - dws chat group share-invite --source <被分享群openConversationId> --receiver <接收者openDingTalkId> - dws chat group share-invite --source --target --expires-seconds 86400 - # 查询群 ID: dws chat search --query "群名" -Flags: - --source string 被分享群的 openConversationId (必填) - --target string 接收分享消息的会话 openConversationId(与 --receiver 二选一) - --receiver string 接收分享消息的单聊用户 openDingTalkId(与 --target 二选一) - --expires-seconds int64 链接有效期(秒),0 表示永久有效,不传使用服务端默认值 - --uuid string 消息幂等键(可选) -``` - -#### 退出群聊 — 当前用户退出指定群聊 -``` -Usage: - dws chat group quit [flags] -Example: - dws chat group quit --group - # 查询群 ID: dws chat search --query "群名" -Flags: - --group string 群聊 openConversationId (必填) -``` -> ⚠️ 唯一群主 quit 会直接成功,产生无主群,之后对该群做 dismiss / members 等管理操作会报 listRoles null(11056) 无法管理。与 `chat group members remove` 移除群主有本地拦截不同,quit 无此防护。作为唯一群主想退群,应先 `chat group transfer-owner` 转让群主,或 `chat group dismiss` 解散群。 - -#### 更新群头像 — 更新指定群聊的群头像 -``` -Usage: - dws chat group update-icon [flags] -Example: - dws chat group update-icon --group --icon-media-id - # 查询群 ID: dws chat search --query "群名" -Flags: - --group string 群聊 openConversationId (必填) - --icon-media-id string 群头像 mediaId (必填) -``` - -> `--icon-media-id` 必须是 `@` 开头的媒体 ID(如 dt_media_upload 返回值),非法格式会本地报错。 -> ⚠️ 本地格式校验只查前缀。格式合法但不真实存在的 mediaId 服务端仍会静默返回成功,头像并不会真正更新。务必用 `dt_media_upload` / `chat media upload` 上传真实图片拿到的 mediaId。 - -#### 更新群设置 — 更新指定群聊的设置项 - ---setting-key 指定设置项,--status 指定值(0=关闭,1=开启)。 - -支持的 settingKey: - authority、joinValidation、onlyAdminCanAtAll、searchable、addFriendForbidden、 - toolbarStatus、pluginCustomizeVerify、onlyAdminCanDING、allMembersCanCreateMcsConf、 - onlyAdminCanSetMsgTop、onlyAdminCanPinMsg、onlyAdminCanSendFile、 - allMembersCanCreateCalendar、groupEmailDisabled、groupRedEnvelopeSwitch、 - groupLiveAuthority、groupBillAuthority -``` -Usage: - dws chat group update-settings [flags] -Example: - dws chat group update-settings --group --setting-key searchable --status 1 - dws chat group update-settings --group --setting-key onlyAdminCanAtAll --status 0 - # 查询群 ID: dws chat search --query "群名" -Flags: - --group string 群聊 openConversationId (必填) - --setting-key string 群设置项 key (必填) - --status int 设置值: 0=关闭, 1=开启 (必填) -``` - -#### 设置群备注 — 给群设置只有自己可见的备注标题 -``` -Usage: - dws chat group update-alias [flags] -Example: - dws chat group update-alias --group --alias-title "项目A群" - # 查询群 ID: dws chat search --query "群名" -Flags: - --group string 群聊 openConversationId (必填) - --alias-title string 群备注标题 (必填) -``` - -#### 设置我在群内的群昵称 — 设置当前用户在指定群里显示的昵称 -``` -Usage: - dws chat group update-nick [flags] -Example: - dws chat group update-nick --group --nick "我的群昵称" - # 查询群 ID: dws chat search --query "群名" -Flags: - --group string 群聊 openConversationId (必填) - --nick string 个人群昵称 (必填) -``` - -#### 查看群内所有机器人 — 获取指定群聊中的所有机器人列表 -``` -Usage: - dws chat group bots [flags] -Example: - dws chat group bots --group - # 查询群 ID: dws chat search --query "群名" -Flags: - --group string 群聊 openConversationId (必填) -``` - -#### 解散群聊 — 解散指定群聊,操作不可逆,需要群主权限 -``` -Usage: - dws chat group dismiss [flags] -Example: - dws chat group dismiss --group - # 查询群 ID: dws chat search --query "群名" -Flags: - --group string 群聊 openConversationId (必填) -``` - -#### 设置新成员入群可查看历史消息选项 — 控制新加入成员可见的历史消息范围 -``` -Usage: - dws chat group set-history [flags] -Example: - dws chat group set-history --group --option RECENT_100 - dws chat group set-history --group --option FORBIDDEN - # 查询群 ID: dws chat search --query "群名" -Flags: - --group string 群聊 openConversationId (必填) - --option string 可见范围: FORBIDDEN | RECENT_100 | ALL (必填) - -注意: - - FORBIDDEN:禁止查看历史消息(默认安全策略) - - RECENT_100:可查看最近 100 条消息(最常用) - - ALL:可查看全部历史消息(开放性最高) -``` - -#### 拉取我创建/管理的群 — 查询当前用户作为群主或管理员的群列表 - -可通过 --role 过滤角色:OWNER 仅群主、ADMIN 仅管理员,不传则返回全部。可通过 --limit 限制返回数量,不传则返回所有符合条件的群。 -``` -Usage: - dws chat group list-my-groups [flags] -Example: - dws chat group list-my-groups - dws chat group list-my-groups --role OWNER - dws chat group list-my-groups --role ADMIN --limit 10 -Flags: - --role string 角色过滤: OWNER(仅群主) / ADMIN(仅管理员),不传返回全部 - --limit int 最多返回群数量,不传返回全部 - -注意: - - 底层先拉取最近 1000 条会话,剔除单聊和话题圈后筛选出群主/管理员的群 - - 内部群会校验 orgId 归属 - - 不传 --role 时返回群主 + 管理员的所有群 -``` - -### group notice (群公告管理) - -#### 发布群公告 — 在指定群聊中发布群公告,支持 Markdown、定时发布 - -``` -Usage: - dws chat group notice create [flags] -Example: - dws chat group notice create --group --content "今晚 22 点系统维护,请提前保存工作内容" - dws chat group notice create --group --content "# 重要通知\n请大家查收" --sticky --send-ding - dws chat group notice create --group --content "明早九点例会" --run-at "2026-07-03T09:00:00+08:00" -Flags: - --group string 群聊 openConversationId (必填) - --content string 公告正文,Markdown 格式 (必填) - --sticky 是否吊顶置顶(默认 false) - --send-ding 是否发 DING 提醒(默认 false) - --run-at string 定时发布时间 ISO-8601(传入则定时发布) -``` - -#### 修改群公告 — 整体替换指定群公告的内容 -``` -Usage: - dws chat group notice edit [flags] -Example: - dws chat group notice edit --group --notice-id --content "更新后的公告内容" -Flags: - --group string 群聊 openConversationId (必填) - --notice-id string 群公告 dataId (必填) - --content string 公告新正文,Markdown 格式 (必填) - --sticky 是否吊顶置顶 - --send-ding 是否发 DING 提醒 -``` - -#### 查看群公告详情 -``` -Usage: - dws chat group notice get [flags] -Example: - dws chat group notice get --group --notice-id -Flags: - --group string 群聊 openConversationId (必填) - --notice-id string 群公告 dataId (必填) -``` - -#### 查看群公告列表 -``` -Usage: - dws chat group notice list [flags] -Example: - dws chat group notice list --group - dws chat group notice list --group --limit 20 --cursor - dws chat group notice list --group --scheduled -Flags: - --group string 群聊 openConversationId (必填) - --limit int 每页返回数量(默认 10,最大 100) - --cursor string 分页游标 - --scheduled 是否查询定时公告列表 -``` - -### group-role (群身份管理) - -#### 查看群身份列表 — 拉取指定群聊的自定义群身份列表 -``` -Usage: - dws chat group-role list [flags] -Example: - dws chat group-role list --group -Flags: - --group string 群聊 openConversationId (必填) -``` - -#### 添加群身份 — 在指定群中创建一个新的自定义群身份 -``` -Usage: - dws chat group-role add [flags] -Example: - dws chat group-role add --group --name "管理员" -Flags: - --group string 群聊 openConversationId (必填) - --name string 群身份名称 (必填) -``` - -#### 更新群身份名称 — 修改指定群身份的名称 -``` -Usage: - dws chat group-role update [flags] -Example: - dws chat group-role update --group --role-id --name "新名称" -Flags: - --group string 群聊 openConversationId (必填) - --role-id string 群身份 openRoleId,由 group-role list 返回 (必填) - --name string 群身份新名称 (必填) -``` - -#### 删除群身份 — 删除指定群聊中的某个自定义群身份 -``` -Usage: - dws chat group-role remove [flags] -Example: - dws chat group-role remove --group --role-id -Flags: - --group string 群聊 openConversationId (必填) - --role-id string 群身份 openRoleId,由 group-role list 返回 (必填) -``` - -#### 设置用户群身份 — 覆盖指定用户在群中的全部群身份(传空则清除所有身份) -``` -Usage: - dws chat group-role set-user [flags] -Example: - dws chat group-role set-user --group --user --role-ids roleId1,roleId2 - # 查询人员: dws aisearch person --keyword "姓名" --dimension name - # 查询 role-id: dws chat group-role list --group -Flags: - --group string 群聊 openConversationId (必填) - --user string 用户 userId(必填) - --role-ids string 群身份 openRoleId 列表,逗号分隔 (必填),传空字符串则清除该用户所有群身份 -``` - -#### 移除用户的指定群身份 — 从用户身上移除指定的群身份(不影响其他群身份) - -> ⚠️ 当前不可用:该命令当前调用常失败(服务端返回 1002 系统繁忙),多为服务端侧限制。需要清除某人身份时,用 `chat group-role set-user --role-ids ""`(传空 role-ids 覆盖为无身份)兜底;但 set-user 会清掉该用户的**全部**身份,无法只移除其中一个。 -``` -Usage: - dws chat group-role remove-user [flags] -Example: - dws chat group-role remove-user --group --user --role-ids roleId1,roleId2 -Flags: - --group string 群聊 openConversationId (必填) - --user string 用户 userId(必填) - --role-ids string 要移除的群身份 openRoleId 列表,逗号分隔 (必填) -``` - -#### 查询群成员的群身份 — 查询指定群成员当前持有的所有群身份 -``` -Usage: - dws chat group-role query-user [flags] -Example: - dws chat group-role query-user --group --user -Flags: - --group string 群聊 openConversationId (必填) - --user string 用户 userId(必填) -``` - -### search (搜索群聊) - -#### 根据关键词搜索群聊 — 分页返回匹配群聊列表 - -hasMore=true 时用返回的 nextCursor 作为下次 --cursor 继续翻页。 - -**注意:** -1. query 不要拆分得太细,应使用群名称中连续的核心词作为关键词(如群名"项目冲刺群"应搜"项目冲刺"而非拆成"项目"+"冲刺"分别搜索)。 -2. 当搜索结果返回多个群聊时,应列出候选群让用户确认目标群聊,不要自行假定并直接进行后续操作。 - -``` -Usage: - dws chat search [flags] -Example: - dws chat search --query "项目冲刺" - dws chat search --query "项目冲刺" --limit 20 --cursor 0 -Flags: - --query string 搜索关键词 (必填) - --limit int 每页返回数量(默认 20) - --cursor string 分页游标(默认 "0",翻页传 nextCursor) - --exclude-muted 是否排除已设置免打扰的群聊(默认 false) -``` - -### data-auth (数据授权) - -#### 跨组织数据授权 — 授予 chat 跨组织消息拉取权限 - -用于跨组织消息拉取等数据访问场景,不用于发送、撤回、群管理等操作。该命令调用 `chat_permission_grant`,固定使用数据授权类别:`scope=chat.data:cross-org`、`grantCategory=data`。 - -``` -Usage: - dws chat data-auth cross-org [flags] - -Example: - dws chat data-auth cross-org --target-org-id 439446171 --format json - dws chat data-auth cross-org --target-org-id 439446171 --agentCode wukong --grant-type timed --ttl 24h --format json - dws chat data-auth cross-org --all --grant-type timed --ttl 24h --format json - -Flags: - --target-org-id string 目标组织 ID(与 --all 二选一) - --all 授权所有目标组织 - --agentCode string Agent 标识,默认 wukong - --grant-type string 授权策略: once|session|timed|permanent (default "timed") - --ttl string timed 授权有效期,如 1h/4h/24h/7d (default "24h") - --session-id string session 授权的会话标识 - -注意: - - --target-org-id 和 --all 必须且只能指定一个。 - - --grant-type=session 时必须传 --session-id。 - - --grant-type=timed 时必须传 --ttl。 -``` - -### message (会话消息管理) - -#### 拉取会话消息内容 — 拉取指定群聊或单聊的会话消息内容 - ---group 指定群聊,--user 指定单聊用户(通过 userId),--open-dingtalk-id 指定单聊用户(通过 openDingTalkId),三者互斥。用 --direction 控制时间方向:newer=从给定时间往现在拉,older=从给定时间往以前拉。hasMore=true 时用结果中的边界 createTime 作为下次 --time 翻页。引用回复消息会带 `quotedMessage`;被引用的原消息是合并转发或图片时,其类型与内容也会保留在引用上下文中。 -``` -Usage: - dws chat message list [flags] -Example: - dws chat message list --group --time "2025-03-01 00:00:00" - dws chat message list --user --time "2025-03-01 00:00:00" --limit 50 - dws chat message list --open-dingtalk-id --time "2025-03-01 00:00:00" --limit 50 - dws chat message list --group --time "2025-03-01 00:00:00" --direction older -Flags: - --direction string 时间方向: newer=从给定时间往现在拉,older=从给定时间往以前拉(推荐) - --group string 群聊 openconversation_id(群聊时必填) - --limit int 返回数量,不传则不限制 - --time string 开始时间,格式: yyyy-MM-dd HH:mm:ss (必填) - --user string 单聊用户 userId(单聊时与 --open-dingtalk-id 二选一) - --open-dingtalk-id string 单聊用户 openDingTalkId(单聊时与 --user 二选一,适用于三方应用等无法获取 userId 的场景) - -注意: - - --group、--user、--open-dingtalk-id 三者互斥,只需指定其一:群聊用 --group,单聊用 --user 或 --open-dingtalk-id - - --user 和 --open-dingtalk-id 都是发起单聊消息拉取,区别在于用不同格式的用户标识: - - --user 传 userId(企业内部应用常用) - - --open-dingtalk-id 传 openDingTalkId(三方应用或跨组织场景常用,无法获取 userId 时使用) - - --group 的别名: --id, --chat, --conversation-id (均可替代 --group) - - 翻页:hasMore=true 时,用结果中的边界 createTime 作为下次 --time - - 处理引用回复时读取 quotedMessage,不要只看回复正文;合并转发与图片引用的原消息内容也在该上下文中 - - 话题圈消息拉取流程:如果返回的会话消息中包含 openConvThreadId 字段,说明是话题类消息。要获取完整的话题内容,需要两步操作:(1) 先通过 dws chat message list 拉取话题主消息(即话题帖子本身);(2) 再调用 dws chat message list-topic-replies --group --topic-id 分页拉取该话题下的所有回复消息。只有话题主消息 + 回复列表合在一起,才是一条话题的完整内容。 -``` - -#### 以当前用户身份发送消息 — --group 群聊 / --user 或 --open-dingtalk-id 单聊 - -**重要:该接口会真实发送消息到目标会话,不可用于测试或试探性调用。调用前必须确认消息内容和接收对象无误。** - ---group 指定群聊 openConversationId 发群消息;--user 指定用户 userId 发单聊;--open-dingtalk-id 指定用户 openDingTalkId 发单聊。三者只能选其一,不能同时指定。纯文本/Markdown 单聊传 --user 时直接走 userId 发送能力,不需要先手动查询 openDingTalkId。推荐使用 --text flag 传递消息内容(也支持位置参数)。可选 --title 作为消息标题。 -若用户只提供了数字群号而非 openConversationId,需先调用 `chat group get-by-group-id` 将群号转为 openConversationId,再传入 --group。 ---群聊时可选 --at-all @所有人,或 --at-open-dingtalk-ids 指定成员(仅群聊时生效)。 ---富媒体消息:通过 --msg-type 指定类型(image/file/audio/video),audio/video 是 file 的语义别名,发给服务端仍按 file 发送;必须根据文件扩展名判断 msgType 后再发送。 -``` -Usage: - dws chat message send [flags] [] - -富媒体消息 msgType 决策(必须按此规则判断,不可跳过): - 文件扩展名 → msgType → 发送参数 - .jpg/.jpeg/.png/.gif/.bmp/.webp → image → dt_media_upload 上传 → `python scripts/extract_media_id.py ` 提取 mediaId → --msg-type image --media-id - .mp3/.wav/.m4a/.aac/.flac → audio(file) → 本地 --file-path 或 conversation-info/drive 上传 → --msg-type audio - .mp4/.mov/.avi/.mkv/.webm → video(file) → 本地 --file-path 或 conversation-info/drive 上传 → --msg-type video - 其他所有(.pdf/.doc/.xls/.zip 等) → file → 本地 --file-path 或 conversation-info/drive 上传 → --msg-type file - -Example: - dws chat message send --group --text "hello" - dws chat message send --user --text "请查收" - dws chat message send --open-dingtalk-id --text "请查收" - dws chat message send --group "hello" - dws chat message send --group --title "周报提醒" --text "请大家本周五前提交周报" - # 幂等发送(24h 内相同 uuid 不重复投递) - dws chat message send --group --text "hello" --uuid "unique-id-123" - dws chat message send --group --at-all "<@all> 请大家注意" - dws chat message send --group --at-open-dingtalk-ids openDingTalkId1,openDingTalkId2 "<@openDingTalkId1> <@openDingTalkId2> 请查收" - # 发送图片 - dws chat message send --group --msg-type image --media-id - # 发送文件/音频/视频(audio/video 是 file 的语义别名) - # 先 dws chat conversation-info --group 获取 spaceId(取 newCSpaceIdIM) - # 再 dws drive upload --file <文件> --space-id 上传 - # 再 dws drive info --file-id --space-id 获取 dentryId - dws chat message send --group --msg-type file --dentry-id --space-id 24557356340 --file-name "report.pdf" --file-type "pdf" --file-path "/report.pdf" --file-size 234724 - dws chat message send --group --msg-type audio --file-path ./recording.mp3 - dws chat message send --group --msg-type video --file-path ./demo.mp4 -Flags: - --text string 消息内容(推荐使用,也可用位置参数) - --group string 群聊 openconversation_id(群聊时必填) - --user string 单聊接收人 userId(单聊时与 --open-dingtalk-id 二选一) - --open-dingtalk-id string 单聊接收人 openDingTalkId(单聊时与 --user 二选一) - --title string 消息标题(可选,默认「消息」) - --at-all @所有人(仅群聊时生效,可选,默认 false) - --at-open-dingtalk-ids string @指定成员的 openDingTalkId 列表,逗号分隔(仅群聊时生效,可选) - --media-id string 图片 mediaId(dt_media_upload 上传后用 `python scripts/extract_media_id.py ` 提取,仅 msgType=image) - --msg-type string 消息类型: image/file/audio/video(audio/video 是 file 别名;image 用 mediaId,file/audio/video 用钉盘上传) - --dentry-id int64 钉盘文件 dentryId(msgType=file 时必填,通过 drive info 获取) - --space-id int64 钉盘空间 ID(msgType=file 时必填) - --file-name string 文件名(msgType=file 时必填) - --file-type string 文件类型/扩展名(msgType=file 时必填) - --file-path string 文件路径(msgType=file 时必填) - --file-size int64 文件大小,单位字节(msgType=file 时必填) - --uuid string 幂等 UUID,相同 uuid 在 24h 内不会重复发送(可选) - --ai-tag 消息是否带 AI 发送角标(可选,默认 true) - -注意: - - --text 和位置参数二选一,--text 优先 - - --group、--user、--open-dingtalk-id 三者互斥,只需指定其一:群聊用 --group,单聊用 --user 或 --open-dingtalk-id - - 纯文本/Markdown 单聊发送时 `--user` 和 `--open-dingtalk-id` 都可用;传 `--user` 时直接走 userId 发送能力 - - --group 的别名: --id, --chat, --conversation-id (均可替代 --group) - - --at-all 和 --at-open-dingtalk-ids 仅在 --group 群聊时生效,单聊时无效;当设置--at-all时,消息内容中一定要包含对应的占位符<@all>;当设置--at-open-dingtalk-ids openDingTalkId1,openDingTalkId2时,消息内容中一定要包含对应格式的占位符<@openDingTalkId1> <@openDingTalkId2> - - **换行符**:消息内容按 Markdown 渲染,换行有两层要求,缺一不可: - 1. 必须使用**真实换行符**(Unicode `U+000A`),而非字面量字符串 `\n`(反斜杠 + 字母 n)。程序或大模型构造参数时,须确保已正确反转义;否则全部内容会渲染在同一行 - 2. Markdown 规范下**单个换行不产生换行效果**。需要换行时请使用:段落分隔(连续两个真实换行符 `\n\n`)、行尾两个空格 + 真实换行符(硬换行 `
`),或直接写 HTML 的 `
` 标签 - - 富媒体消息类型与参数对应关系: - - image(图片):--msg-type image --media-id - - audio/video:file 的语义别名,发给服务端仍是 file;可直接传本地 --file-path,或先 conversation-info 获取 spaceId → drive upload --space-id 上传 → drive info 获取 dentryId → --msg-type audio 或 --msg-type video --dentry-id --space-id --file-name --file-type --file-path --file-size - - file(文档/压缩包等其他非图片文件):可直接传本地 --file-path,或先 conversation-info 获取 spaceId → drive upload --space-id 上传 → drive info 获取 dentryId → --msg-type file --dentry-id --space-id --file-name --file-type --file-path --file-size - - mediaId 通过 dt_media_upload 上传获得,必须用脚本提取:`python scripts/extract_media_id.py ""`(输出如 @lQLPxxx,直接用于 --media-id)。禁止手动从 URL 中截取或拼接 mediaId,手动解析会因 URL 格式不稳定导致尺寸后缀残留 - - --uuid 用于幂等发送,传入相同 uuid 在 24h 内不会重复投递消息(可选,群聊和单聊均支持) - - 富媒体消息的单聊优先使用 `--open-dingtalk-id`;传 `--user` 时 CLI 会尝试解析成 openDingTalkId 后发送 - - 发送文件/媒体消息时,必须先根据文件扩展名判断 msgType:图片(.jpg/.png/.gif/.bmp/.webp)→image,音频(.mp3/.wav/.m4a/.aac/.flac)→audio,视频(.mp4/.mov/.avi/.mkv/.webm)→video,其他所有→file;不可跳过此判断步骤。audio/video 是 file 别名,不代表不支持音频/视频 - - 20MB 降级:图片超过 20MB 时 dt_media_upload 会失败,必须降级走钉盘上传 + Markdown 嵌入方式发送(参见「发送图片+文字消息」章节)。音频/视频/文件走钉盘上传无 20MB 限制 - - 发送文字 + 文件混合消息时的完整流程:除了将文件以 Markdown 链接内嵌到文字消息中发送一条 md 消息外,还必须额外逐个发送独立的文件消息(--msg-type file),确保接收方可以直接下载原始文件。即:先发一条包含文字和文件链接的 md 消息,再对每个涉及的文件各发一条 --msg-type file 的文件消息 -``` - -#### 查询消息发送状态 — 查询以当前用户身份发送的消息的发送状态 - -查询以当前用户身份发送的消息的发送状态。需要传入发送消息时返回的 openTaskId。 -``` -Usage: - dws chat message query-send-status [flags] -Example: - dws chat message query-send-status --open-task-id - # openTaskId 由 dws chat message send 返回 -Flags: - --open-task-id string 消息发送任务 ID (必填) - -注意: - - openTaskId 由 `dws chat message send` 发送消息成功后返回 - - 用于确认消息是否已成功发送或获取发送失败的原因 - - 返回结果中含发送成功消息的 openMessageId,可用于后续 recall(撤回)、read-status(查已读)等命令 -``` - -#### 撤回消息 — 撤回当前用户自己发出的消息 - -撤回当前用户以个人身份发送的消息。需要指定会话 ID(openConversationId)和消息 ID(openMessageId)。与 `recall-by-bot` 的区别:本命令通过 IM 接口撤回用户自己发出的消息,`recall-by-bot` 通过机器人接口撤回机器人发出的消息(需要 robot-code + processQueryKey)。 -``` -Usage: - dws chat message recall [flags] -Example: - dws chat message recall --conversation-id --msg-id - # 查询会话 ID: dws chat search --query "群名" - # 消息 ID 可通过 dws chat message list 获取 -Flags: - --conversation-id string 会话 openConversationId (必填,支持单聊/群聊,别名: --group / --id / --chat) - --msg-id string 消息 openMessageId (必填) - -注意: - - --conversation-id 的别名: --group, --id, --chat (均可替代 --conversation-id) - - 消息 ID 可通过 `dws chat message list` 命令获取 - - 仅支持撤回当前用户以个人身份发出的消息,不能撤回他人发送的消息,也不能撤回机器人发出的消息 - - 与 `recall-by-bot` 的区别:本命令通过 IM 接口撤回用户自己发出的消息(需要 openConversationId + openMessageId),`recall-by-bot` 通过机器人接口撤回机器人发出的消息(需要 robot-code + processQueryKey) -``` - -#### 机器人发送消息(--group 群聊 / --users 单聊) - -**重要:该接口会真实发送消息到目标会话,不可用于测试或试探性调用。调用前必须确认消息内容和接收对象无误。** - -群聊:传 --group 指定群;单聊:传 --users 指定用户列表,二者只能选其一,不能同时指定。--text 支持 Markdown。群聊时可选 --at-user-ids @指定成员。 - -如果用户明确要求"用机器人/机器人身份/robot"发送,必须使用本命令,严禁改用 `chat message send` 以当前用户身份发送。 - -**重要**:机器人发群消息前,必须确认该机器人已在目标群中。若机器人不在群内会报错"机器人不存在",需先执行 `dws chat group members add-bot --id --robot-code ` 将机器人加入群聊后再发送。 -``` -Usage: - dws chat message send-by-bot [flags] -Example: - dws chat message send-by-bot --robot-code --group --title "日报" --text "## 今日完成..." - dws chat message send-by-bot --robot-code --users userId1,userId2 --title "提醒" --text "请提交周报" - dws chat message send-by-bot --robot-code --open-dingtalk-ids openDingtalkId1,openDingtalkId2 --title "提醒" --text "请提交周报" - dws chat message send-by-bot --robot-code --group --at-user-ids userId1,userId2 --title "提醒" --text "@userId1 @userId2 请查收本周报告" - dws chat message send-by-bot --robot-code --group --at-open-dingtalk-ids openDingtalkId1,openDingtalkId2 --title "提醒" --text "@openDingtalkId1 @openDingtalkId2 请查收本周报告" - dws chat message send-by-bot --robot-code --group --at-all --title "通知" --text "请所有人注意" -Flags: - --group string 群聊 openConversationId(群聊时必填) - --robot-code string 机器人 Code (必填) - --text string 消息内容 Markdown (必填) - --title string 消息标题 (必填) - --users string 用户 userId 列表,逗号分隔,最多20个(单聊时必填) - --open-dingtalk-ids string 用户 openDingtalkId 列表,逗号分隔(单聊时可替代 --users,可选) - --at-user-ids string @指定成员的 userId 列表,逗号分隔(仅群聊时生效,可选) - --at-open-dingtalk-ids string @指定成员的 openDingtalkId 列表,逗号分隔(仅群聊时生效,可选) - --at-all @所有人(可选),服务端接收字符串 true/false - -注意: - - 用户明确要求机器人发送时,必须使用 `chat message send-by-bot`;严禁使用 `chat message send` 以用户身份代发 - - --group 与 --users/--open-dingtalk-ids 互斥,必须且只能指定其一 - - --group 的别名: --id, --chat, --conversation-id (均可替代 --group) - - --at-user-ids 仅在 --group 群聊时生效,单聊时无效;设置时 --text 中需包含 @userId 对应文本 - - --at-open-dingtalk-ids 仅在 --group 群聊时生效,单聊时无效;设置时 --text 中需包含 @openDingtalkId 对应文本 - - --at-all @所有人,仅群聊时生效;只需带上 --at-all flag 即可,服务端会自动处理 - - userId 获取方式:`dws contact user search --query "姓名"` 搜人获取 userId - - **换行符**:--text 按 Markdown 渲染,换行规则同 `chat message send`: - 1. 必须使用**真实换行符**(`U+000A`),而非字面量 `\n`,否则全部内容会渲染在同一行 - 2. 单个换行不产生换行效果,需用空行(`\n\n`)做段落分隔,或行尾两空格 + 换行/`
` 做硬换行 -``` - -#### 机器人撤回消息(--group 群聊 / 不传为单聊) - -群聊:传 --group 与 --keys;单聊:仅传 --keys。--keys 为发送时返回的 processQueryKey 列表,逗号分隔。 -``` -Usage: - dws chat message recall-by-bot [flags] -Example: - dws chat message recall-by-bot --robot-code --group --keys - dws chat message recall-by-bot --robot-code --keys key1,key2 -Flags: - --group string 群聊 openConversationId(群聊撤回时必填) - --keys string 消息 processQueryKey 列表,逗号分隔 (必填) - --robot-code string 机器人 Code (必填) -``` - -#### 自定义机器人 Webhook 发送群消息 - -@ 人时需在 --text 中包含 @userId 或 @手机号,否则 @ 不生效;@所有人时需在 --text 中包含 @10 并带上 --at-all。 -``` -Usage: - dws chat message send-by-webhook [flags] -Example: - dws chat message send-by-webhook --token --title "告警" --text "CPU 超 90% @10" --at-all - dws chat message send-by-webhook --token --title "test" --text "hi @118785" --at-users 118785 -Flags: - --at-all @ 所有人(需在 --text 中包含 @10) - --at-mobiles string @ 指定手机号,逗号分隔 - --at-users string @ 指定用户,逗号分隔(需在 text 中包含 @userId) - --text string 消息内容 (必填) - --title string 消息标题 (必填) - --token string Webhook Token (必填) - -注意: - - **换行符**:--text 按 Markdown 渲染,换行规则同 `chat message send`: - 1. 必须使用**真实换行符**(`U+000A`),而非字面量 `\n`,否则全部内容会渲染在同一行 - 2. 单个换行不产生换行效果,需用空行(`\n\n`)做段落分隔,或行尾两空格 + 换行/`
` 做硬换行 -``` - -#### 拉取群话题回复消息列表 - -查询指定群聊中某条话题消息的全部回复。--group 指定群会话 ID,--topic-id 指定话题 ID(由 dws chat message list 返回)。 -``` -Usage: - dws chat message list-topic-replies [flags] -Example: - dws chat message list-topic-replies --group --topic-id - dws chat message list-topic-replies --group --topic-id --time "2025-03-01 00:00:00" --limit 20 -Flags: - --group string 群会话 openconversationId (必填) - --topic-id string 话题 ID,由 dws chat message list 返回 (必填) - --time string 开始时间,格式: yyyy-MM-dd HH:mm:ss(可选) - --limit int 返回数量(默认 50) - --direction string 时间方向: newer=从给定时间往现在拉,older=从给定时间往以前拉(推荐,默认 older) -``` - -#### 拉取指定时间范围内当前用户的所有会话消息 — 分页拉取当前登录用户在指定时间范围内的所有会话消息 - ---start 和 --end 限定时间范围,--limit 指定每页数量,--cursor 传分页游标(首页传 "0",后续从响应中的 nextCursor 获取)。服务端按 cursor 分页返回,hasMore=true 时用返回的 nextCursor 值作为下次 --cursor 继续翻页。若当前账号没有消息搜索权益,CLI 会透传服务端的友好提示与开通入口。 -``` -Usage: - dws chat message list-all [flags] -Example: - dws chat message list-all --start "2025-03-01 00:00:00" --end "2025-03-31 23:59:59" --limit 50 - dws chat message list-all --start "2025-03-01 00:00:00" --end "2025-03-31 23:59:59" --limit 50 --cursor "abc123token" -Flags: - --start string 起始时间,格式: yyyy-MM-dd HH:mm:ss (必填) - --end string 结束时间,格式: yyyy-MM-dd HH:mm:ss (必填) - --limit int 每页返回数量(默认 50) - --cursor string 分页游标(首页传 "0",后续从响应中的 nextCursor 获取) - -注意: - - 四个参数每次请求都会传递给服务端,cursor 首页传 "0" - - 与 chat message list 的区别:list 拉取指定单个会话(群聊或单聊)的消息,list-all 拉取当前用户所有会话的消息 - - 翻页:hasMore=true 时,用响应中的 nextCursor 值作为下次 --cursor 参数继续翻页 - - 时间格式统一为 yyyy-MM-dd HH:mm:ss - - 权限/权益错误不是空结果;应把返回的 friendly_hint 与 action_url 展示给用户,不要继续盲目翻页 -``` - -#### 拉取指定发送者的消息 — 搜索特定人发送给我的消息(包含单聊和群聊) - -> 推荐优先使用 `chat message search-advanced --user/--users`(userId)或 `--sender-ids`(openDingTalkId),它还能叠加关键词/群/at 等过滤条件。本命令保留给需要旧 list-by-sender 返回结构的场景。 - -搜索特定人发送给我的消息,返回结果包含单聊和群聊标识。--sender-user-id 指定发送者 userId,--sender-open-dingtalk-id 指定发送者 openDingTalkId,二者互斥。分页参数 --limit(默认 50)和 --cursor(默认 "0")始终传递;hasMore=true 时用返回的 nextCursor 作为下次 --cursor 继续翻页。 -``` -Usage: - dws chat message list-by-sender [flags] -Example: - dws chat message list-by-sender --sender-user-id --start "2026-03-10T00:00:00+08:00" --end "2026-03-11T00:00:00+08:00" --limit 50 --cursor 0 - 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 0 - dws chat message list-by-sender --sender-user-id --start "2026-03-10T00:00:00+08:00" --end "2026-03-10T23:59:59+08:00" --limit 20 --cursor 0 - 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 二选一);该路径当前返回 result:{}、success:null(疑似服务端问题),优先用 --sender-user-id - --start string 开始时间,ISO-8601 格式 (必填) - --end string 结束时间,ISO-8601 格式 (必填) - --limit int 每页返回数量(默认 50) - --cursor string 分页游标(默认 "0",翻页传 nextCursor) - -注意: - - --sender-user-id 和 --sender-open-dingtalk-id 二者互斥,必须且只能指定其一: - - --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" 等 - - 翻页:hasMore=true 时,用返回的 nextCursor 作为下次 --cursor -``` - -#### 拉取 @我 的消息 — 搜索时间范围内 @我 的消息 - -> 推荐使用 `chat message search-advanced --at-me`,它还能叠加关键词/群/发送者等过滤条件。本命令适用于仅需拉取 @我 消息的简单场景。 - -搜索时间范围内 @我 的消息,可选指定群聊。返回结果包含单聊和群聊标识。分页参数 --limit(默认 50)和 --cursor(默认 "0")始终传递;hasMore=true 时用返回的 nextCursor 作为下次 --cursor 继续翻页。 -``` -Usage: - dws chat message list-mentions [flags] -Example: - dws chat message list-mentions --start "2026-03-10T00:00:00+08:00" --end "2026-03-11T00:00:00+08:00" --limit 50 --cursor 0 - dws chat message list-mentions --start "2026-04-01T00:00:00+08:00" --end "2026-04-14T00:00:00+08:00" --limit 20 --cursor 0 - dws chat message list-mentions --group --start "2026-03-10T00:00:00+08:00" --end "2026-03-11T00:00:00+08:00" --limit 50 --cursor 0 - dws chat message list-mentions --start "2026-03-10T00:00:00+08:00" --end "2026-03-11T00:00:00+08:00" --limit 50 --cursor -Flags: - --group string 群聊 openconversation_id(可选,不传则查全部) - --start string 开始时间,ISO-8601 格式 (必填) - --end string 结束时间,ISO-8601 格式 (必填) - --limit int 每页返回数量(默认 50) - --cursor string 分页游标(默认 "0",翻页传 nextCursor) - -注意: - - --group 可选,不传则查询所有会话中 @我 的消息;传入则只查指定群聊 - - --group 的别名: --id, --chat, --conversation-id (均可替代 --group) - - 时间支持多种 ISO-8601 格式,如 "2026-03-10T00:00:00+08:00"、"2026-03-10 14:00:00"、"2026-03-10" 等 - - 翻页:hasMore=true 时,用返回的 nextCursor 作为下次 --cursor -``` - -#### 拉取特别关注人的消息 - -拉取当前用户特别关注人的消息。分页参数 --limit 指定每页数量,--cursor 传分页游标(首次不传或传 0)。返回结果中 hasMore=true 时用 nextCursor 作为下次 --cursor 继续翻页。 -``` -Usage: - dws chat message list-focused [flags] -Example: - dws chat message list-focused --limit 50 - dws chat message list-focused --limit 20 --cursor -Flags: - --limit int 每页返回数量(默认 50) - --cursor int64 分页游标(首次不传或传 0,翻页传 nextCursor) - -注意: - - 首次调用不传 --cursor 或传 0,后续翻页传 nextCursor -``` - -#### 获取未读会话列表 - -获取当前用户有未读消息的会话信息。可选通过 `--count` 限制返回条数,不传则使用服务端默认值。 -``` -Usage: - dws chat message list-unread-conversations [flags] -Example: - dws chat message list-unread-conversations - dws chat message list-unread-conversations --count 20 - dws chat message list-unread-conversations --exclude-muted -Flags: - --count int 返回未读会话条数(可选) - --exclude-muted 是否排除已设置免打扰的会话(默认 false) -``` - -#### 查询消息的已读/未读状态 - -查询指定会话中消息的已读/未读状态(仅消息发送者可查询自己发出的消息)。--conversation-id 指定会话 openConversationId(群聊或单聊均可),--message-id 指定消息 ID(由 dws chat message list 返回的 openMessageId,必须是当前用户发送的消息)。目标用户 userId 使用 --user/--users;目标用户 openDingTalkId 使用 --target-open-dingtalk-ids;不传目标用户则返回所有接收者的状态。 -``` -Usage: - dws chat message read-status [flags] -Example: - dws chat message read-status --conversation-id --message-id - dws chat message read-status --conversation-id --message-id --user userId1,userId2 - dws chat message read-status --conversation-id --message-id --users userId1,userId2 - dws chat message read-status --conversation-id --message-id --target-open-dingtalk-ids openDingTalkId1,openDingTalkId2 -Flags: - --conversation-id string 会话 openConversationId (必填,群聊或单聊均可) - --message-id string 消息 openMessageId,由 chat message list 返回 (必填,必须是当前用户发送的消息) - --user string 目标用户 userId,支持逗号分隔(可选,不传则查所有接收者) - --users string 目标用户 userId 列表,逗号分隔(可选,不传则查所有接收者) - --target-open-dingtalk-ids string 目标用户 openDingTalkId 列表,逗号分隔(可选,不传则查所有接收者) - -注意: - - 仅消息发送者可查询自己发出的消息的已读/未读状态,查询他人发的消息会报错 - - --conversation-id 的别名: --group, --id, --chat (均可替代 --conversation-id) - - --message-id 从 dws chat message list 返回的消息列表中获取(字段名 openMessageId) - - --user / --users 传目标用户 userId - - --target-open-dingtalk-ids 不传时返回该消息所有接收者的已读状态;传入则只返回指定 openDingTalkId 用户的状态 -``` - -#### 按关键词搜索消息 — 在当前用户的会话中按关键词搜索消息 - -> 推荐优先使用 `chat message search-advanced`,它是本命令的严格超集:query 可选(非必填)、支持多个会话(非单个)、还能叠加发送者/at 等维度过滤。 - -按关键词搜索消息内容。--query 指定搜索关键词(必填)。可选 --group 限定搜索某个会话,不传则搜索所有会话。时间参数 --start/--end(ISO-8601)限定搜索时间范围。分页参数 --limit(默认 100)和 --cursor(默认 "0")始终传递;hasMore=true 时用返回的 nextCursor 作为下次 --cursor 继续翻页。 -``` -Usage: - dws chat message search [flags] -Example: - dws chat message search --query "changefree" --start "2026-04-01T00:00:00+08:00" --end "2026-04-15T00:00:00+08:00" --limit 50 --cursor 0 - dws chat message search --query "codereview" --group --start "2026-04-01T00:00:00+08:00" --end "2026-04-15T00:00:00+08:00" --limit 100 --cursor 0 - dws chat message search --query "链接" --start "2026-04-15T00:00:00+08:00" --end "2026-04-16T00:00:00+08:00" --limit 100 --cursor -Flags: - --query string 搜索关键词 (必填) - --group string 群聊 openconversation_id(可选,不传则搜索所有会话) - --start string 开始时间,ISO-8601 格式 (必填) - --end string 结束时间,ISO-8601 格式 (必填) - --limit int 每页返回数量(默认 100) - --cursor string 分页游标(默认 "0",翻页传 nextCursor) - -注意: - - --group 可选,不传则搜索所有会话中的消息;传入则只搜索指定会话 - - --group 的别名: --id, --chat, --conversation-id (均可替代 --group) - - 时间支持多种 ISO-8601 格式,如 "2026-03-10T00:00:00+08:00"、"2026-03-10 14:00:00"、"2026-03-10" 等 - - 翻页:hasMore=true 时,用返回的 nextCursor 作为下次 --cursor -``` - -#### 多维度搜索消息(推荐首选) — 支持按关键词、发送者、@我、@指定人、指定会话、时间范围等多维度搜索 - -> 推荐:这是消息搜索的首选接口。它可以完全替代 `chat message search`(query 可选 vs 必填,支持多个会话 vs 单个),大部分替代 `chat message list-by-sender`(通过 --user/--users 按 userId 搜索发送者,或通过 --sender-ids 按 openDingTalkId 搜索)和 `chat message list-mentions`(通过 --at-me 搜索@我的消息)。仅在拉取「特别关注人」消息时需要退回 `list-focused`。 - -支持按关键词、发送者、@我、@指定人、指定会话、时间范围等多维度搜索消息。发送者 userId 使用 --user/--users;发送者或 @ 人的 openDingTalkId 使用 --sender-ids/--at-ids。所有参数均为可选,至少指定一个搜索条件。 -``` -Usage: - dws chat message search-advanced [flags] -Example: - dws chat message search-advanced --query "周报" --start "2026-04-01T00:00:00+08:00" --end "2026-04-15T00:00:00+08:00" - dws chat message search-advanced --user --start "2026-04-01T00:00:00+08:00" --end "2026-04-15T00:00:00+08:00" - dws chat message search-advanced --users , --start "2026-04-01T00:00:00+08:00" --end "2026-04-15T00:00:00+08:00" - dws chat message search-advanced --sender-ids , --start "2026-04-01T00:00:00+08:00" --end "2026-04-15T00:00:00+08:00" - dws chat message search-advanced --at-me --start "2026-04-01T00:00:00+08:00" --end "2026-04-15T00:00:00+08:00" - dws chat message search-advanced --at-ids , --conversation-ids , --limit 50 --cursor 0 - dws chat message search-advanced --conversation-ids <单聊openConversationId> --query "合同" --start "2026-04-01T00:00:00+08:00" --end "2026-04-15T00:00:00+08:00" - # 查询群 ID: dws chat search --query "群名" - # 查询单聊会话 ID: dws chat conversation-info --user - # 查询人员: dws aisearch person --keyword "姓名" --dimension name -Flags: - --query string 搜索关键词(可选) - --user string 发送者 userId,支持逗号分隔(可选) - --users string 发送者 userId 列表,逗号分隔(可选) - --sender-ids string 发送者 openDingTalkId 列表,逗号分隔(可选) - --at-me 只搜索 @我 的消息(可选,默认 false) - --at-ids string @指定人的 openDingTalkId 列表,逗号分隔(可选) - --conversation-ids string 会话 openConversationId 列表,逗号分隔(可选,群聊或单聊均可,不传则搜索所有会话) - --start string 开始时间,ISO-8601 格式(可选) - --end string 结束时间,ISO-8601 格式(可选) - --cursor string 分页游标(默认 "0") - --limit int 每页返回数量(默认 100) - --conversation-ids 的别名: --groups - -注意: - - 所有参数均为可选,但至少需要指定一个搜索条件 - - --user / --users 传发送者 userId - - --sender-ids 和 --at-ids 传 openDingTalkId - - --conversation-ids 可指定多个会话 ID(群聊或单聊均可),逗号分隔,不传则搜索所有会话 - - 群聊 openConversationId 通过 `dws chat search --query "群名"` 获取 - - 单聊 openConversationId 通过 `dws chat conversation-info --user ` 或 `--open-dingtalk-id ` 获取 - - 时间支持多种 ISO-8601 格式,如 "2026-03-10T00:00:00+08:00"、"2026-03-10 14:00:00"、"2026-03-10" 等 - - 翻页:hasMore=true 时,用返回的 nextCursor 作为下次 --cursor - - 替代关系:完全替代 search(严格超集);大部分替代 list-by-sender(--user 覆盖按 userId 搜索发送者,--sender-ids 覆盖按 openDingTalkId 搜索)和 list-mentions(--at-me 覆盖核心功能);不能替代 list-focused(「特别关注」是独立维度) -``` - -#### 根据消息 ID 批量查询消息 -``` -Usage: - dws chat message list-by-ids [flags] -Example: - dws chat message list-by-ids --msg-ids msgId1,msgId2,msgId3 - # 最多传 50 条消息 ID -Flags: - --msg-ids string 消息 ID 列表,逗号分隔,最多 50 条 (必填) -``` - -#### 表情回应选择策略 - -> 贴表情时,优先查 [chat-emoji-list.md](chat-emoji-list.md) 中的默认表情名称(共 199 个,如「赞」「鼓掌」「感谢」等): -> - 命中 → 使用 `add-emoji --emoji `(直接贴 emoji) -> - 未命中 → 先 `create-text-emotion` 创建文字表情获取 emotionId,再 `add-text-emotion` 贴文字表情 - -#### 对消息添加 emoji 表情回应 -``` -Usage: - dws chat message add-emoji [flags] -Example: - dws chat message add-emoji --conversation-id --msg-id --emoji "赞" - dws chat message add-emoji --conversation-id --msg-id --emoji "鼓掌" - # --emoji 的值必须是 chat-emoji-list.md 中的 name(中文名),如:赞、鼓掌、感谢、微笑 等 - # 查询会话 ID: dws chat search --query "群名" -Flags: - --conversation-id string 会话 openConversationId (必填,支持单聊/群聊,别名: --group / --id / --chat) - --msg-id string 消息 openMsgId (必填) - --emoji string emoji 表情名称,必须是默认表情列表中的 name 值 (必填,参见 chat-emoji-list.md) -``` - -#### 移除消息的 emoji 表情回应 -``` -Usage: - dws chat message remove-emoji [flags] -Example: - dws chat message remove-emoji --conversation-id --msg-id --emoji "赞" - # 查询会话 ID: dws chat search --query "群名" -Flags: - --conversation-id string 会话 openConversationId (必填,支持单聊/群聊,别名: --group / --id / --chat) - --msg-id string 消息 openMsgId (必填) - --emoji string emoji 表情名称,必须是默认表情列表中的 name 值 (必填,参见 chat-emoji-list.md) -``` - -#### 对消息添加文字表情回应(当默认表情列表中没有所需表情时使用) -``` -Usage: - dws chat message add-text-emotion [flags] -Example: - dws chat message add-text-emotion --conversation-id --msg-id --emotion-id --emotion-name "赞" --text "nice" --background-id im_bg_5 -Flags: - --conversation-id string 会话 openConversationId (必填,支持单聊/群聊,别名: --group / --id / --chat) - --msg-id string 消息 openMsgId (必填) - --emotion-id string 表情 ID (必填,通过 create-text-emotion 或已知表情获取) - --emotion-name string 表情名称 (必填) - --text string 文字内容 (必填) - --background-id string 背景 ID (必填) -``` - -#### 移除消息的文字表情回应 -``` -Usage: - dws chat message remove-text-emotion [flags] -Example: - dws chat message remove-text-emotion --conversation-id --msg-id --emotion-id --emotion-name "赞" --text "nice" --background-id -Flags: - --conversation-id string 会话 openConversationId (必填,支持单聊/群聊,别名: --group / --id / --chat) - --msg-id string 消息 openMsgId (必填) - --emotion-id string 表情 ID (必填) - --emotion-name string 表情名称 (必填) - --text string 文字内容 (必填) - --background-id string 背景 ID (必填) -``` - -#### 创建文字表情(获取 emotionId)— 当 chat-emoji-list.md 中没有所需表情时,先创建再贴 -``` -Usage: - dws chat message create-text-emotion [flags] -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 -Flags: - --emotion-name string 表情名称 (必填) - --text string 文字内容 (必填) - --background-id string 背景 ID(可选,不传则由服务端默认分配) - -注意: - - 创建后返回 emotionId,可用于 add-text-emotion 命令 - - 如果已有合适的表情,无需创建新的 -``` - -#### 批量拉取消息的表情回复和文字回复 - -根据消息 ID 列表批量查询消息的表情回复和文字回复信息。 - -``` -Usage: - dws chat message list-emotion-replies [flags] -Example: - dws chat message list-emotion-replies --msg-ids msgId1,msgId2,msgId3 - # 消息 ID 可通过 dws chat message list 获取 -Flags: - --msg-ids string 消息 ID 列表,逗号分隔 (必填) -``` - -### list-top-conversations (置顶会话) - -#### 拉取置顶会话列表 - -拉取当前用户的置顶会话列表。分页参数 --limit 指定每页数量,--cursor 传分页游标(首次不传或传 0)。返回结果中 hasMore=true 时用 nextCursor 作为下次 --cursor 继续翻页。 -``` -Usage: - dws chat list-top-conversations [flags] -Example: - dws chat list-top-conversations --limit 1000 - dws chat list-top-conversations --limit 1000 --cursor -Flags: - --limit int 每页返回数量(默认 1000) - --cursor int64 分页游标(首次不传或传 0,翻页传 nextCursor) - --exclude-muted 是否排除已设置免打扰的会话(默认 false) - -注意: - - 用户询问"置顶会话"时,直接调用此命令返回置顶会话列表即可 - - 用户询问"置顶消息"时,需两步:先调用此命令拉取置顶会话列表获取各会话的 openConversationId,再用 `chat message list --group ` 分别拉取每个会话内的消息 - - 翻页:hasMore=true 时,用返回的 nextCursor 作为下次 --cursor -``` - -### download-media (下载消息资源) - -#### 下载消息中的资源(图片/视频/语音等)到本地 - -下载聊天消息中的图片、视频、语音等资源到本地文件。流程:先获取下载 URL,再 HTTP GET 下载。 -``` -Usage: - dws chat message download-media [flags] -Example: - 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 本地保存路径 (必填),建议传完整文件路径(含文件名) - -注意: - - resource-id 从 `dws chat message list` 返回的消息内容中获取 mediaId - - message-id 从 `dws chat message list` 返回的 openMessageId - - open-conversation-id 从 `dws chat search` 获取 openConversationId - - --output 建议传完整文件路径(含扩展名,如 ./photo.jpg);只传目录(如 ./downloads/)在旧版本会下载失败,请显式带上文件名 -``` - -### search-common (搜索共同群) - -#### 搜索共同群 — 查询指定人共同所在的群聊 - -根据昵称列表搜索共同群聊。--nicks 指定要搜索的人员昵称(逗号分隔,必填)。--match-mode 控制匹配模式:AND 表示所有人都在群里,OR 表示任一人在群里(默认 AND)。分页参数 --limit(默认 20)和 --cursor(默认 "0")始终传递;hasMore=true 时用返回的 nextCursor 作为下次 --cursor 继续翻页。 -``` -Usage: - dws chat search-common [flags] -Example: - dws chat search-common --nicks "风雷,山乔" --limit 20 --cursor 0 - dws chat search-common --nicks "天鸡,乐函" --match-mode OR --limit 20 --cursor 0 - dws chat search-common --nicks "风雷,山乔,天鸡" --limit 10 --cursor -Flags: - --nicks string 要搜索的昵称列表,逗号分隔 (必填) - --match-mode string 匹配模式:AND=所有人都在群里,OR=任一人在群里(默认 AND) - --limit int 每页返回数量(默认 20) - --cursor string 分页游标(默认 "0",翻页传 nextCursor) - --exclude-muted 是否排除已设置免打扰的群聊(默认 false) - -注意: - - --nicks 传人员昵称(花名),逗号分隔,如 "风雷,山乔" - - --match-mode AND 表示群里必须包含所有指定的人;OR 表示包含任意一人即可 - - 翻页:hasMore=true 时,用返回的 nextCursor 作为下次 --cursor -``` - -### conversation-info (获取会话基础信息) - -#### 获取会话基础信息 — 含会话关联的钉盘共享空间 ID - -获取指定会话的基础信息,包含会话关联的钉盘共享空间 ID (newCSpaceIdIM)。发送文件消息前需先调用此命令获取 spaceId,再用 drive upload --space-id 上传文件到共享空间。 -``` -Usage: - dws chat conversation-info [flags] -Example: - dws chat conversation-info --group --format json - dws chat conversation-info --user --format json - dws chat conversation-info --open-dingtalk-id --format json -Flags: - --group string 群聊 openConversationId(群聊时使用) - --user string 单聊对方 userId(单聊时使用) - --open-dingtalk-id string 单聊对方 openDingTalkId(单聊时使用) - -注意: - - --group、--user、--open-dingtalk-id 互斥,必须且只能指定其一 - - --group 的别名: --id, --chat, --conversation-id (均可替代 --group) - - 返回值中的 newCSpaceIdIM 为会话共享空间 ID,用于 drive upload --space-id 参数 - - 上传到共享空间的文件对方才能打开,上传到个人空间的文件对方无法访问 -``` - -#### 引用回复消息 — 引用某条消息并回复文字(单聊/群聊均可) -``` -Usage: - dws chat message reply [flags] -Example: - dws chat message reply --conversation-id --ref-msg-id --ref-sender --text "收到,马上处理" - # 被引用消息的 openMessageId、发送者 openDingTalkId 通过 dws chat message list 获取 -Flags: - --conversation-id string 会话 openConversationId (必填,支持单聊/群聊) - --ref-msg-id string 被引用的消息 openMessageId (必填) - --ref-sender string 被引用消息的发送者 openDingTalkId (必填) - --text string 回复内容 (必填) - --ai-tag 消息是否带 AI 发送角标(可选,默认 true) - --uuid string 幂等键(可选) - -注意: - - 以当前用户身份引用回复,语义同 chat message send;目前回复类型仅支持 text -``` - -#### 转发单条消息 — 将一条消息从源会话转发到目标会话(源/目标均支持单聊/群聊) -``` -Usage: - dws chat message forward [flags] -Example: - dws chat message forward --src-conversation-id --msg-id --dest-conversation-id -Flags: - --src-conversation-id string 源会话 openConversationId (必填) - --msg-id string 源消息 openMessageId (必填) - --dest-conversation-id string 目标会话 openConversationId (必填) - --uuid string 幂等键(可选) - -注意: - - 与 combine-forward 区别: forward 转单条,combine-forward 合并多条为一条转发 -``` - -#### 转发话题消息 — 将一条话题消息转发到目标会话 -``` -Usage: - dws chat message forward-topic [flags] -Example: - dws chat message forward-topic --src-conversation-id --src-msg-id --src-thread-id --dest-conversation-id -Flags: - --src-conversation-id string 源会话 openConversationId (必填,消息所在的会话) - --src-msg-id string 源消息 openMessageId (必填,要转发的消息) - --src-thread-id string 话题 ID (必填,格式: convThread + 加密后的 convThreadId,即 message list 返回的 openConvThreadId) - --dest-conversation-id string 目标会话 openConversationId (必填,转发到的会话) -``` - -#### 置顶消息 — 将指定消息置顶到会话顶部 -``` -Usage: - dws chat message set-top-msg [flags] -Example: - dws chat message set-top-msg --open-conversation-id --msg-id -Flags: - --open-conversation-id string 会话 openConversationId (必填,支持群聊/单聊) - --msg-id string 消息 openMessageId (必填) -``` - -#### 取消置顶消息 — 取消会话顶部的置顶消息 -``` -Usage: - dws chat message unset-top-msg [flags] -Example: - dws chat message unset-top-msg --open-conversation-id --msg-id -Flags: - --open-conversation-id string 会话 openConversationId (必填,支持群聊/单聊) - --msg-id string 消息 openMessageId (必填) -``` - -#### 合并转发多条消息 — 将多条消息合并后转发到目标会话(源/目标会话均支持单聊/群聊) -``` -Usage: - dws chat message combine-forward [flags] -Example: - dws chat message combine-forward --src-conversation-id --msg-ids ,, --dest-conversation-id - dws chat message combine-forward --src-conversation-id --msg-ids , --dest-conversation-id --uuid -Flags: - --src-conversation-id string 源会话 openConversationId (必填) - --msg-ids string 源消息 openMessageId 列表,逗号分隔 (必填) - --dest-conversation-id string 目标会话 openConversationId (必填) - --uuid string 幂等键(可选) - -注意: - - 与 chat message forward 区别: forward 转单条,combine-forward 合并多条为一条转发 - - --msg-ids 多个消息 ID 用逗号分隔,无顺序要求 -``` - -#### 钉住某条消息(Pin) — 将指定消息设置为钉住状态 -``` -Usage: - dws chat message set-pin-msg [flags] -Example: - dws chat message set-pin-msg --open-conversation-id --msg-id -Flags: - --open-conversation-id string (必填)会话 openConversationId(支持群聊/单聊) - --msg-id string (必填)消息 openMessageId - -注意: - - 钉住消息后,会话成员均可在会话中看到被钉住的消息 -``` - -#### 取消钉住某条消息(Unpin) — 取消指定消息的钉住状态 -``` -Usage: - dws chat message unset-pin-msg [flags] -Example: - dws chat message unset-pin-msg --open-conversation-id --msg-id -Flags: - --open-conversation-id string (必填)会话 openConversationId(支持群聊/单聊) - --msg-id string (必填)消息 openMessageId - -注意: - - 取消钉住后消息仍保留在会话中,只是不再被标记为钉住状态 -``` - -#### 拉取某个会话中钉住的消息列表 — 拉取指定会话中被钉住的消息列表 -``` -Usage: - dws chat message list-pin-msg [flags] -Example: - dws chat message list-pin-msg --open-conversation-id - dws chat message list-pin-msg --open-conversation-id --size 50 - dws chat message list-pin-msg --open-conversation-id --cursor --size 20 -Flags: - --open-conversation-id string (必填)会话 openConversationId(支持群聊/单聊) - --cursor string (选填)分页游标,首次不传,翻页时传上次返回的 nextCursor - --size int (选填)一次拉取的消息数量(默认 20,最大 100) - -注意: - - 与 `chat message list` 区别: list-pin-msg 只返回被钉住的消息;list 拉取全部消息 - - 分页: hasMore=true 时,用返回的 nextCursor 作为下次 --cursor 继续翻页 -``` - -#### 收藏消息 — 为当前用户收藏指定会话中的一条消息 -``` -Usage: - dws chat message add-favorite [flags] -Example: - dws chat message add-favorite --open-message-id --open-conversation-id -Flags: - --open-message-id string 消息 openMessageId (必填) - --open-conversation-id string 消息所在会话 openConversationId (必填) -``` - -#### 取消收藏消息 — 移除当前用户的收藏标记,不删除原消息 -``` -Usage: - dws chat message remove-favorite [flags] -Example: - dws chat message remove-favorite --open-message-id --open-conversation-id -Flags: - --open-message-id string 消息 openMessageId (必填) - --open-conversation-id string 消息所在会话 openConversationId (必填) -``` - -#### 查询收藏消息 — 分页查询当前用户收藏的消息 -``` -Usage: - dws chat message list-favorites [flags] -Example: - dws chat message list-favorites - dws chat message list-favorites --size 50 - dws chat message list-favorites --cursor 20 --size 20 -Flags: - --cursor int 数字分页游标,默认 0;翻页时传上次返回的 nextCursor - --size int 一次拉取的收藏数量,默认 20,范围 1-100 - -注意: - - 首次请求可省略分页参数,CLI 会自动向 Open 服务传入 cursor=0、size="20" - - hasMore=true 时,将 nextCursor 作为下一次的 --cursor -``` - -### bot (机器人管理) - -#### 搜索【我创建的】机器人 — 仅返回当前用户自己创建的机器人 - -范围: 仅限当前登录用户自己创建的机器人(不含他人创建、官方机器人)。 -返回字段: 没有 openDingTalkId,如果需要给机器人发单聊消息请用 find。 -典型触发词: "我创建的机器人""我的机器人""我自己的机器人""我做的机器人""查看我的机器人"。 - -``` -Usage: - dws chat bot search [flags] -Example: - dws chat bot search --page 1 - dws chat bot search --page 1 --size 10 --name "日报" -Flags: - --name string 按名称搜索 - --page int 页码,从1开始 (默认 1) - --size int 每页条数 (默认 50),别名: --limit -``` - -#### 搜索【全部可用】机器人 — 含他人创建/官方机器人,额外返回机器人 openDingTalkId - -范围: 当前用户可用的全部机器人(含他人创建、官方机器人)。 -返回字段: 结果在 `result.bots[]` 中,每项含 `botOpenDingTalkId`(机器人的 openDingTalkId,用于给机器人发单聊消息)和 `name`。注意字段名是 `botOpenDingTalkId`,不是 `openDingTalkId`;search 没有此字段。 -典型触发词: "搜索机器人""找一个机器人""帮我找 XXX 机器人""所有可用机器人""查机器人"。 - -``` -Usage: - dws chat bot find [flags] -Example: - dws chat bot find --query "日报" - dws chat bot find --query "日报" --limit 20 - dws chat bot find --query "日报" --limit 20 --cursor <上次返回的 nextCursor> -Flags: - --query string 搜索关键词 (必填) - --limit int 每页返回数量(默认 20) - --cursor string 分页游标(首次调用不传,翻页时传上次返回的 nextCursor) - -注意: - - cursor 必须用上次返回的 nextCursor 字符串原值,不要传 "0" 或其他数字字面量 - (服务端 String 类型,但网关会把数字字符串 auto-coerce 回 Integer 导致 PARAM_ERROR) -``` - -search 与 find 选择指南: - -| 维度 | `chat bot search` | `chat bot find` | -|------|-------------------|-----------------| -| 范围 | 仅我创建的机器人 | 全部可用机器人(含他人/官方) | -| 额外返回机器人 openDingTalkId | 无 | 有,字段名 `botOpenDingTalkId`(可用于给机器人发单聊消息) | -| 触发词 | "我创建的""我的""我自己的" | "搜索机器人""找机器人""查机器人" | - -### category (会话分组管理) - -#### 获取用户自定义会话分组 -``` -Usage: - dws chat category list -Example: - dws chat category list - # 返回当前用户的所有自定义会话分组 -``` - -#### 拉取指定分组下的会话列表 -``` -Usage: - dws chat category list-conversations [flags] -Example: - dws chat category list-conversations --category-id <分组ID> - dws chat category list-conversations --category-id <分组ID> --exclude-muted - # 分组ID 可通过 dws chat category list 获取 -Flags: - --category-id int 会话分组 ID (必填) - --exclude-muted 是否排除已设置免打扰的会话(默认 false) -``` - -#### 创建会话分组 -``` -Usage: - dws chat category create [flags] -Example: - dws chat category create --title "工作群" -Flags: - --title string 分组名称 (必填) -``` - -#### 创建智能会话分组 — 可指定群名称关键词和群内成员作为匹配规则 -``` -Usage: - dws chat category create-smart [flags] -Example: - dws chat category create-smart --name "工作群" - dws chat category create-smart --name "项目组" --keywords "项目,开发" - dws chat category create-smart --name "团队群" --members openDingTalkId1,openDingTalkId2 - dws chat category create-smart --name "重点群" --keywords "重点" --members openDingTalkId1 -Flags: - --name string 分组名称 (必填) - --keywords string 群名称关键词列表,逗号分隔(可选) - --members string 群内成员 openDingTalkId 列表,逗号分隔(可选) -``` - -#### 删除会话分组 -``` -Usage: - dws chat category delete [flags] -Example: - dws chat category delete --category-id <分组ID> - # 分组ID 可通过 dws chat category list 获取 -Flags: - --category-id int 会话分组 ID (必填) -``` - -#### 重命名会话分组 -``` -Usage: - dws chat category rename [flags] -Example: - dws chat category rename --category-id <分组ID> --title "新名称" - # 分组ID 可通过 dws chat category list 获取 -Flags: - --category-id int 会话分组 ID (必填) - --title string 新的分组名称 (必填) -``` - -#### 将会话加入分组 -``` -Usage: - dws chat category add-conv [flags] -Example: - dws chat category add-conv --group --category-ids 123,456 - # 分组ID 可通过 dws chat category list 获取 - # 查询群 ID: dws chat search --query "群名" -Flags: - --group string 会话 openConversationId (必填) - --category-ids string 目标分组 ID 列表,逗号分隔 (必填) -``` - -#### 将会话移出分组 -``` -Usage: - dws chat category remove-conv [flags] -Example: - dws chat category remove-conv --group --category-ids 123,456 - # 分组ID 可通过 dws chat category list 获取 - # 查询群 ID: dws chat search --query "群名" -Flags: - --group string 会话 openConversationId (必填) - --category-ids string 目标分组 ID 列表,逗号分隔 (必填) -``` - -### text (文本内容处理) - -#### 翻译文本内容 -``` -Usage: - dws chat text translate [flags] -Example: - dws chat text translate --query "你好世界" --to en_US - dws chat text translate --query "Hello World" --to zh_CN - dws chat text translate --query "Bonjour" --to ja_JP -Flags: - --query string 待翻译的文本内容 (必填) - --to string 目标语言代码 (必填) -``` - -支持的目标语言代码:`en_US`, `zh_CN`, `zh_TW`, `zh_HK`, `ja_JP`, `ko_KR`, `vi_VN`, `th_TH`, `id_ID`, `ms_MY`, `es_419`, `fr_FR`, `pt_BR`, `tr_TR`, `ru_RU`, `de_DE`, `hi_IN`, `hu_HU`, `pl_PL`, `sv_SE`, `fi_FI`, `cs_CZ`, `ar_SA`, `tl_PH`, `he_IL`, `nl_NL`, `lo_LA`, `it_IT`。 - -### mute (会话免打扰) - -#### 会话消息免打扰 — 开启或关闭会话消息免打扰(支持单聊和群聊) -``` -Usage: - dws chat mute [flags] -Example: - dws chat mute --conversation-id - dws chat mute --conversation-id --off - # 查询群 ID: dws chat search --query "群名" - # 查询单聊会话 ID: dws chat conversation-info --user -Flags: - --conversation-id string 会话 openConversationId (必填,支持单聊/群聊) - --id string --conversation-id 的别名 - --chat string --conversation-id 的别名 - --off 关闭免打扰(不传则开启免打扰) - -注意: - - 默认行为是开启免打扰,传 --off 则关闭免打扰 - - 支持单聊和群聊,openConversationId 可通过 chat search(群聊)或 chat conversation-info(单聊)获取 -``` - -### media (上传媒体获取 mediaId) - -#### 上传图片/媒体获取 mediaId — 用于 chat message send --msg-type image 等 - -⚠️ 前置条件:本命令需要应用凭证。必须已通过 `dws auth login --client-id --client-secret ` 登录,或设置环境变量 `DWS_CLIENT_ID` / `DWS_CLIENT_SECRET`;否则报"缺少应用凭证"。这与其他 chat 命令走用户登录态不同。 -``` -Usage: - dws chat media upload [flags] -Example: - dws chat media upload --file ./screenshot.png - dws chat media upload --file ./photo.jpg --type image -Flags: - --file string 本地文件路径 (必填) - --type string 媒体类型: image/voice/video/file(默认 image) - -注意: - - 返回的 mediaId 可直接用于 chat message send --msg-type image --media-id -``` - -### hide (隐藏会话) - -#### 隐藏会话 — 在会话列表中隐藏指定会话(支持单聊/群聊),收到新消息时会重新出现 -``` -Usage: - dws chat hide [flags] -Example: - dws chat hide --conversation-id - dws chat hide --id -Flags: - --conversation-id string 会话 openConversationId (必填,支持单聊/群聊) - --id string --conversation-id 的别名 - --chat string --conversation-id 的别名 - -注意: - - 隐藏后会话不再显示在列表中,收到新消息时会重新出现 - - openConversationId 可通过 chat search(群聊)或 chat conversation-info(单聊)获取 -``` - -### mute-at-all (关闭@所有人通知) - -#### 关闭/开启 @所有人消息提醒 — 关闭或开启会话中 @所有人的消息通知 - -> ⚠️ 当前不可用:该命令当前调用常失败(服务端返回 1002 系统繁忙),多为服务端侧限制。命令本身参数合法,但调用不会生效。 -``` -Usage: - dws chat mute-at-all [flags] -Example: - dws chat mute-at-all --conversation-id - dws chat mute-at-all --conversation-id --off -Flags: - --conversation-id string 会话 openConversationId (必填,支持单聊/群聊) - --id string --conversation-id 的别名 - --chat string --conversation-id 的别名 - --off 恢复接收 @所有人通知(不传则关闭通知) -``` - -### mute-red-envelope (关闭红包通知) - -#### 关闭/开启红包消息提醒 — 关闭或开启会话中的红包消息通知 - -> ⚠️ 当前不可用:该命令当前调用常失败(服务端返回 1002 系统繁忙),多为服务端侧限制。命令本身参数合法,但调用不会生效。 -``` -Usage: - dws chat mute-red-envelope [flags] -Example: - dws chat mute-red-envelope --conversation-id - dws chat mute-red-envelope --conversation-id --off -Flags: - --conversation-id string 会话 openConversationId (必填,支持单聊/群聊) - --id string --conversation-id 的别名 - --chat string --conversation-id 的别名 - --off 恢复接收红包通知(不传则关闭通知) -``` - -### mark-unread (标记会话为未读) - -#### 标记会话为未读 — 将指定会话标记为未读状态 -``` -Usage: - dws chat mark-unread [flags] -Example: - dws chat mark-unread --conversation-id - dws chat mark-unread --id -Flags: - --conversation-id string 会话 openConversationId (必填,支持群聊/单聊) - --id string --conversation-id 的别名 - --chat string --conversation-id 的别名 - -注意: - - openConversationId 可通过 chat search(群聊)或 chat conversation-info(单聊)获取 - - API 返回成功,但该未读状态在 API 侧观察不到:list-all-conversations 返回的 unreadPoint 不会随之变化,只有钉钉客户端 UI 上能看到未读标记 -``` - -### clear-red-point (清除会话红点) - -#### 清除会话红点 — 清除指定会话的未读红点 -``` -Usage: - dws chat clear-red-point [flags] -Example: - dws chat clear-red-point --conversation-id - dws chat clear-red-point --id -Flags: - --conversation-id string 会话 openConversationId (必填,支持群聊/单聊) - --id string --conversation-id 的别名 - --chat string --conversation-id 的别名 - -注意: - - 清除红点后该会话不再显示未读标记 -``` - -### clear-all-red-point (红点清零) - -#### 清除所有会话红点 — 一键全部已读 -``` -Usage: - dws chat clear-all-red-point -Example: - dws chat clear-all-red-point - -注意: - - 无需任何参数,直接清除当前用户所有会话的未读红点,等效于"全部已读" -``` - -### list-all-conversations (全部会话列表) - -#### 分页获取全部会话列表 — 获取当前用户的所有会话 -``` -Usage: - dws chat list-all-conversations [flags] -Example: - dws chat list-all-conversations - dws chat list-all-conversations --limit 50 - dws chat list-all-conversations --exclude-muted -Flags: - --limit int 每页数量(1-100,默认 100);传 >100 会被明确拒绝 - --cursor int 分页游标(首次不传或传 0,翻页传 nextCursor) - --exclude-muted 是否排除已免打扰会话(默认 false) - -注意: - - 返回结果包含单聊和群聊,不区分会话类型 - - --limit 范围 1-100,默认 100,上限 100;传入 >100 会报错拒绝,不会静默截断 - - 分页当前不可用:当前 hasMore 恒为 false、nextCursor 恒为 null,本命令最多返回 100 条会话,无法用 --cursor 继续翻页取更多 - - 与 list-top-conversations 的区别: 本命令返回全部会话(单聊+群聊),list-top-conversations 仅返回置顶会话 -``` - -### clear-messages (清空会话聊天记录) - -#### 清空会话聊天记录 — 清空当前用户指定会话的消息 -``` -Usage: - dws chat clear-messages [flags] -Example: - dws chat clear-messages --conversation-id - dws chat clear-messages --id -Flags: - --conversation-id string 会话 openConversationId (必填,支持群聊/单聊) - --id string --conversation-id 的别名 - --chat string --conversation-id 的别名 - -注意: - - 仅清空当前用户视角的消息,不影响其他成员 -``` - -### mark-read (标记消息已读) - -#### 标记消息已读 — 将指定消息及之前的消息标记为已读 -``` -Usage: - dws chat mark-read [flags] -Example: - dws chat mark-read --conversation-id --message-id - dws chat mark-read --id --message-id -Flags: - --conversation-id string 会话 openConversationId (必填,支持群聊/单聊) - --id string --conversation-id 的别名 - --chat string --conversation-id 的别名 - --message-id string 消息 openMessageId (必填) - -注意: - - 标记该消息及之前的所有消息为已读 - - openMessageId 可通过 chat message list 获取 -``` - -### group list-all (分页拉取所有群) - -#### 分页拉取我所有群列表 — 获取当前用户加入的所有群聊 -``` -Usage: - dws chat group list-all [flags] -Example: - dws chat group list-all - dws chat group list-all --limit 50 - dws chat group list-all --limit 100 --cursor -Flags: - --limit int 每页返回数量(默认 100,最大 200) - --cursor string 分页游标(首次不传,翻页传返回的 nextCursor) - -注意: - - 与 chat group list-my-groups 区别: list-all 返回用户加入的所有群;list-my-groups 仅返回用户作为群主/管理员的群 - - 分页: hasMore=true 时用返回的 nextCursor 作为下次 --cursor - - ⚠️ 存在同步盲区:新建群后较长时间(实测 15 分钟后全量翻页仍查不到)内不会出现在 list-all 里;而 chat group list-my-groups / chat search 能立即查到。要确认刚建的群,用 list-my-groups 或 search,别依赖 list-all -``` - -### group list-join-validations (分页拉取入群验证记录) - -#### 分页拉取入群验证记录 — 获取当前用户的所有入群验证记录 - -包括自己被拒绝的记录以及作为审批者的记录。 -``` -Usage: - dws chat group list-join-validations [flags] -Example: - dws chat group list-join-validations - dws chat group list-join-validations --limit 30 - dws chat group list-join-validations --limit 20 --cursor -Flags: - --limit int 单页数量(默认 20,最大 50) - --cursor string 分页游标(首次不传,翻页传返回的 nextCursor) - -注意: - - 分页: hasMore=true 时用返回的 nextCursor 作为下次 --cursor - - cursor 首次拉取不传或传 null 时从当前时间开始拉 -``` - -### group audit-join-validation (审批入群验证) - -#### 审批入群验证 — 通过、删除单个审核 - -当前仅 AuditApprove(通过)和 AuditDelete(删除)两个动作可用。 -``` -Usage: - dws chat group audit-join-validation [flags] -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 AuditDelete - # 查询入群验证记录: dws chat group list-join-validations -Flags: - --group string 群 openConversationId (必填) - --record-id string 申请记录 ID (必填) - --applicant string 申请人 openDingTalkId (必填) - --inviter string 邀请人 openDingTalkId (必填) - --status string 审批动作: AuditApprove(通过) / AuditDelete(删除) (必填) - --description string 审批说明(可选) - -注意: - - status 仅支持 AuditApprove(通过) 和 AuditDelete(删除);AuditIgnore(忽略)、AuditRefuse(拒绝)、AuditBlock(拒绝且拉黑) 会被服务端拒绝报 unsupported audit status,属服务端限制 - - record-id、applicant、inviter 可通过 dws chat group list-join-validations 查询获得 -``` - -## 意图判断 - -用户说"我特别关注的人最近发了什么消息/关注的人最近聊了啥/星标联系人最近的动态" → `chat message list-focused`(零参数一行命令) -用户说"某人发给我的消息/指定发送者的消息/某人最近的消息" → `chat message list-by-sender --sender-user-id ` 或 `--sender-open-dingtalk-id `(跨单聊+群聊) -用户说"和某人的单聊聊天记录/拉某人单聊历史" → `chat message list --user ` 或 `--open-dingtalk-id ` -用户说"某个群的聊天记录" → `chat message list --group ` -用户说"我最近所有消息/我今天的消息" → `chat message list-all --start --end ` -用户说"@我的消息/提及我的" → `chat message list-mentions --start --end ` -用户说"搜索消息里的关键词/包含XX的消息" → `chat message search-advanced --query "<关键词>"`(首选,严格超集) -用户说"我和某人的共同群" → `chat search-common --nicks "<昵称1>,<昵称2>"` -用户说"未读会话列表" → `chat message list-unread-conversations` -用户说"群里某条话题的回复" → `chat message list-topic-replies --group --topic-id ` -用户说"置顶会话/置顶消息" → `chat list-top-conversations` 列会话 → 再 `chat message list --group ` 拉消息(两步) - -用户说"建群/创建群聊" → `chat group create` -用户说"搜索群/找群" → `chat search` -用户说"我创建的群/我管理的群/我是群主的群/我当管理员的群" → `chat group list-my-groups` -用户说"群成员/看群里有谁" → `chat group members` -用户说"拉人进群/加群成员" → `chat group members add` -用户说"踢人/移除群成员" → `chat group members remove` -用户说"加机器人到群" → `chat group members add-bot` -用户说"改群名" → `chat group rename` -用户说"设置群备注/给群加备注" → `chat group update-alias` -用户说"改我在群里的昵称/设置群昵称" → `chat group update-nick` -用户说"批量查群成员信息/按ID查群成员" → `chat group members list-by-ids` -用户说"聊天记录/会话消息/拉取会话" → `chat message list` -用户说"某人发给我的消息/指定发送者/某人的消息" → `chat message list-by-sender`(用户未明确说"单聊"时优先使用,跨单聊/群聊) -用户说"拉取和某人的单聊记录/单聊消息" → `chat message list --user`(用户明确说"单聊"时使用) -用户说"@我的消息/at我的/提及我的" → `chat message list-mentions` -用户说"未读消息会话/未读会话列表/我的未读会话" → `chat message list-unread-conversations` -用户说"发群消息(以个人身份)" → `chat message send --group` -用户说"发单聊消息(以个人身份)" → `chat message send --user`(有 userId 时)或 `chat message send --open-dingtalk-id`(有 openDingTalkId 时) -用户说"机器人发消息/机器人群发" → `chat message send-by-bot` -用户说"撤回我发的消息/撤回消息" → `chat message recall`(通过 IM 接口撤回当前用户自己发出的消息,需要 openConversationId + openMessageId) -用户说"撤回机器人发的消息/机器人撤回消息" → `chat message recall-by-bot`(通过机器人接口撤回机器人发出的消息,需要 robot-code + processQueryKey) -用户说"Webhook 发消息/告警消息" → `chat message send-by-webhook` -用户说"话题回复/群话题消息回复/拉取话题回复" → `chat message list-topic-replies` -用户说"所有消息/全部会话消息/拉取全部消息/时间范围内消息/我的消息/我今天的消息/查我的钉钉消息/最近的消息" → `chat message list-all` -用户说"特别关注人的消息/关注的人的消息/星标联系人的消息" → `chat message list-focused` -用户说"消息已读未读/谁看了消息/查读状态/消息读取状态" → `chat message read-status` -用户说"查看我的机器人" → `chat bot search` -用户说"搜索消息/查找关键词/搜一下消息里的XX" → 优先使用 `chat message search-advanced`(推荐首选,严格超集);仅在简单关键词搜索且无其他维度需求时可用 `chat message search` -用户说"多维度搜索/按发送者搜索/按人搜消息/指定多个群搜索/@我的消息搜索" → `chat message search-advanced`(推荐首选,支持多维度组合搜索) -用户说"查询消息发送状态/消息发没发成功/消息状态" → `chat message query-send-status` -用户说"我和XX的共同群/我们都在哪些群/查共同群" → `chat search-common` -用户说"置顶会话/置顶消息/我的置顶/查看置顶" → `chat list-top-conversations` -用户说"查看会话分组/自定义分组" → `chat category list` -用户说"某个分组下的会话/分组会话列表" → `chat category list-conversations` -用户说"新建会话分组/创建分组" → `chat category create` -用户说"创建智能分组/新建智能会话分组" → `chat category create-smart` -用户说"删除会话分组" → `chat category delete` -用户说"重命名分组/改分组名" → `chat category rename` -用户说"把会话加入分组/会话归类到分组" → `chat category add-conv` -用户说"把会话移出分组/从分组移除会话" → `chat category remove-conv` -用户说"根据群号查群信息/群号查群/群号转openConversationId" → `chat group get-by-group-id`(当用户发消息时只提供了群号,用此工具将群号转为 openConversationId,再调用发消息接口) -用户说"查看群身份/群的自定义身份列表" → `chat group-role list` -用户说"创建/添加群身份" → `chat group-role add` -用户说"修改/更新群身份名称" → `chat group-role update` -用户说"删除群身份" → `chat group-role remove` -用户说"给某人设置群身份/设定用户的群身份" → `chat group-role set-user` -用户说"移除某人的群身份/撤销群身份" → `chat group-role remove-user` -用户说"查询某人的群身份/某人在群里有什么身份" → `chat group-role query-user` -用户说"转让群主/换群主/群主转让" → `chat group transfer-owner` -用户说"群邀请链接/入群链接/加群链接" → `chat group invite-url` -用户说"分享群链接/把群分享给某人/群链接发到某群" → `chat group share-invite` -用户说"群公告/发布公告/修改公告/查看公告/定时公告" → `chat group notice create/edit/get/list` -用户说"批量查消息/按ID查消息/根据消息ID查" → `chat message list-by-ids` -用户说"批量查消息回复/表情回复/文字回复/消息回应列表" → `chat message list-emotion-replies` -用户说"emoji回应/表情回应/给消息加表情" → `chat message add-emoji` -用户说"取消emoji回应/移除表情回应" → `chat message remove-emoji` -用户说"文字表情回应/添加文字表情" → `chat message add-text-emotion` -用户说"取消文字表情回应/移除文字表情" → `chat message remove-text-emotion` -用户说"创建文字表情/新建文字表情" → `chat message create-text-emotion` -用户说"免打扰/消息免打扰/静音/开启免打扰/关闭免打扰" → `chat mute` -用户说"隐藏会话/隐藏群聊/隐藏对话" → `chat hide` -用户说"关闭@所有人通知/屏蔽@所有人/不接收@all" → `chat mute-at-all`(当前服务端 1002 不可用) -用户说"开启@所有人通知/恢复@所有人提醒" → `chat mute-at-all --off`(当前服务端 1002 不可用) -用户说"关闭红包通知/屏蔽红包/不接收红包提醒" → `chat mute-red-envelope`(当前服务端 1002 不可用) -用户说"开启红包通知/恢复红包提醒" → `chat mute-red-envelope --off`(当前服务端 1002 不可用) -用户说"标记会话未读/标为未读" → `chat mark-unread` -用户说"标记已读/把消息标成已读" → `chat mark-read` -用户说"清除红点/去掉某个会话的未读红点" → `chat clear-red-point` -用户说"全部已读/一键清除红点/红点清零" → `chat clear-all-red-point` -用户说"我的所有会话/全部会话列表" → `chat list-all-conversations` -用户说"清空聊天记录/清空会话消息" → `chat clear-messages` -用户说"我加入的所有群/我的全部群列表" → `chat group list-all` -用户说"入群验证记录/谁申请进群" → `chat group list-join-validations` -用户说"审批入群/通过入群申请/删除入群申请" → `chat group audit-join-validation`(仅 AuditApprove/AuditDelete 可用) -用户说"引用回复/回复消息/引用消息回复" → `chat message reply` -用户说"转发消息/转发一条消息/把消息转发到另一个群" → `chat message forward` -用户说"合并转发/批量转发/合并转发多条消息" → `chat message combine-forward` -用户说"转发话题/转发话题消息" → `chat message forward-topic` -用户说"置顶消息/把消息置顶" → `chat message set-top-msg` -用户说"取消置顶消息/撤销消息置顶" → `chat message unset-top-msg` -用户说"上传图片拿mediaId/上传媒体" → `chat media upload` -用户说"群机器人列表/群里有哪些机器人/查看群机器人" → `chat group bots` -用户说"从群里移除机器人/踢出机器人" → `chat group members remove-bot` -用户说"搜索机器人/找机器人/查机器人/帮我找XXX机器人" → `chat bot find`(全部可用机器人,额外返回 botOpenDingTalkId 可发单聊) -用户说"给机器人发单聊/给机器人发消息/跟机器人聊天" → 必须先 `chat bot find`(拿 openDingTalkId)→ 再 `chat message send --open-dingtalk-id`(search 没有 openDingTalkId,无法发单聊) -用户说"我创建的机器人/我的机器人/我自己的机器人/查看我的机器人" → `chat bot search`(仅我创建的机器人,无 openDingTalkId) -用户说"解散群/解散群聊" → `chat group dismiss` -用户说"设置历史消息/新成员看历史/新成员可见消息" → `chat group set-history` -用户说"置顶会话/取消置顶/会话置顶" → `chat set-top`(设置/取消置顶),`chat list-top-conversations`(查看置顶列表) -用户说"全员禁言/群禁言/解除禁言" → `chat group-mute` -用户说"禁言某人/指定成员禁言/解除某人禁言" → `chat group-mute-member` -用户说"设管理员/取消管理员/设置群管理员" → `chat group set-admin` -用户说"翻译文本/把这段话翻译成英文/翻译成中文" → `chat text translate` - -关键区分: -- `chat search` — 搜**群/会话名**返回 `openConversationId`,**不**搜消息内容;要搜消息内容请用 `chat message search-advanced`(首选)/ `chat message search` / `list-by-sender` / `list-all`,**勿混淆** -- `chat message list` — 拉取指定会话的消息(需指定 --group 或 --user),按时间点 + 方向翻页 -- `chat message list --user` — list 的单聊模式,拉取与指定用户的单聊记录(用户明确说"单聊""私聊"时使用) -- `chat message list-by-sender` — 搜索指定发送者发给我的消息,跨所有会话(单聊+群聊均包含,用户只说"某人发的消息"时优先使用) -- `chat message list-mentions` — 拉取 @我 的消息(跨单聊/群聊,可选指定群) -- `chat message list-unread-conversations` — 拉取当前用户存在未读消息的会话列表(可选 `--count`) -- `chat message read-status` — 查询指定消息的已读/未读状态(仅消息发送者可查询自己发的消息,需指定 --group 和 --message-id,可选 --target-open-dingtalk-ids 查特定人) -- `chat message list-all` — 拉取当前用户所有会话的消息,按时间范围 + cursor 分页。只要用户没有指定某个具体的会话(如某个群名、某个人名),即使提到"单聊消息""群聊消息"等笼统范围,也应路由到此命令 -- `chat message list-topic-replies` — 拉取群话题的回复消息列表 -- `chat message list-focused` — 拉取特别关注人的消息,cursor 分页 -- `chat list-top-conversations` — 拉取置顶会话列表(用户询问"置顶会话"或"置顶消息"时路由到此),cursor 分页 -- `chat message send` — 以当前用户身份发消息(群聊或单聊),text 为位置参数;支持 --msg-type 发送富媒体消息:image(图片)、file/audio/video(audio/video 是 file 别名),图片的 mediaId 通过 dt_media_upload 上传获得,其他文件可直接用 --file-path 或先获取会话共享空间再上传钉盘 -- `chat message search` — 按关键词搜索消息内容(跨所有会话,可选指定群) -- `chat search-common` — 搜索共同群,查询指定人共同所在的群聊(AND=所有人都在,OR=任一人在) -- `chat message send-by-bot` — 以**机器人**身份发消息(群聊或单聊),text 为 --text flag -- `chat message send-by-webhook` — 通过**自定义机器人 Webhook** 发群消息 -- `chat message recall-by-bot` — 通过**机器人接口**撤回机器人发出的消息,需要 `--robot-code` + `--keys`(发送时返回的 processQueryKey);传 `--group` 为群聊撤回,不传为单聊撤回 -- `chat message recall` — 通过 **IM 接口**撤回当前用户自己发出的消息,需要 `--conversation-id`(openConversationId)+ `--msg-id`(openMessageId,可通过 `chat message list` 获取);群聊单聊均通过 `--conversation-id` 区分 -- `chat message query-send-status` — 查询个人发送的消息的发送状态(需 send 返回的 openTaskId) -- `chat message search-advanced` — 多维度搜索消息(支持关键词、发送者、@我、@指定人、多个会话等维度组合,与 `search` 的区别:`search` 仅支持关键词且必填,`search-advanced` 所有参数均可选) -- `chat message list-by-ids` — 根据消息 ID 批量查询消息(最多 50 条) -- `chat message add-emoji` / `remove-emoji` — 对消息添加/移除 emoji 表情回应 -- `chat message list-emotion-replies` — 批量拉取消息的表情回复和文字回复 -- `chat message add-text-emotion` / `remove-text-emotion` — 对消息添加/移除文字表情回应 -- `chat message create-text-emotion` — 创建文字表情模板,返回 emotionId 供 add-text-emotion 使用 -- `chat category list` — 获取用户自定义会话分组列表 -- `chat category list-conversations` — 拉取指定分组下的会话列表 -- `chat category create-smart` — 创建智能会话分组(可指定群名称关键词和成员作为匹配规则) -- `chat mute` — 开启/关闭会话消息免打扰(默认开启,--off 关闭) -- `chat group transfer-owner` — 转让群主 -- `chat group invite-url` — 获取群邀请链接 -- `chat group share-invite` — 分享群聊链接到会话 -- `chat group notice create/edit/get/list` — 群公告发布、修改、详情、列表 -- `chat message reply` — 引用回复消息(在群聊中引用某条消息并回复文字) -- `chat message forward` — 转发单条消息(将一条消息从源会话转发到目标会话) -- `chat set-top` — 设置/取消会话置顶(默认置顶,--off 取消) -- `chat group-mute` — 全员禁言/取消全员禁言(默认禁言,--off 取消) -- `chat group-mute-member` — 指定群成员禁言/取消禁言(需指定 --users 和 --mute-time) -- `chat group set-admin` — 设置/取消群管理员(默认设为管理员,--off 取消) - -## openDingTalkId 获取方式 - -多个命令参数需要 openDingTalkId(如 --open-dingtalk-id、--at-open-dingtalk-ids、--sender-open-dingtalk-id),统一获取方式如下: - -1. 若知道姓名:`dws contact user search --query "姓名"` → 直接从结果中获取 openDingTalkId -2. 若只有 userId:先 `dws contact user get --ids ` 获取姓名 → 再 `dws contact user search --query "姓名"` 获取 openDingTalkId - -openDingTalkId 为当前用户视角下的目标用户唯一标识,不可跨用户共享。 - -## 核心工作流 - -```bash -# 1. 搜索群 — 提取 openconversation_id -dws chat search --query "项目冲刺" --format json - -# 2. 拉取群消息 -dws chat message list --group --time "2025-03-01 00:00:00" --format json - -# 2b. 拉取未读会话列表 -dws chat message list-unread-conversations --count 20 --format json - -# 3. 以个人身份发送群消息 -dws chat message send --group --title "周报提醒" "请大家本周五前提交周报" --format json - -# 4. 以个人身份单聊(通过 userId) -dws chat message send --user "你好" --format json - -# 4b. 以个人身份单聊(通过 openDingTalkId,三方应用等无法获取 userId 时使用) -dws chat message send --open-dingtalk-id "你好" --format json - -# 5. 机器人发群消息(Markdown) -dws chat message send-by-bot --robot-code \ - --group --title "日报" --text "## 今日完成..." --format json - -# 6. 机器人单聊发消息 -dws chat message send-by-bot --robot-code \ - --users userId1,userId2 --title "提醒" --text "请提交周报" --format json - -# 7. Webhook 发告警 -dws chat message send-by-webhook --token \ - --title "告警" --text "CPU 超 90% @10" --at-all --format json -``` - -## 复合工作流 - -### 机器人发消息后撤回(完整流程) - -`recall-by-bot` 通过机器人接口撤回机器人发出的消息(需要 `--robot-code` + `--keys`)。`chat message recall` 通过 IM 接口撤回当前用户自己发出的消息(需要 `--conversation-id` + `--msg-id`)。 - -```bash -# Step 1: 查我的机器人 — 提取 robot-code -dws chat bot search --format json - -# Step 2: 用机器人发消息 — 提取返回中的 processQueryKey -dws chat message send-by-bot --robot-code --group \ - --title "通知" --text "内容" --format json - -# Step 3: 用同一个 robot-code + processQueryKey 撤回 -dws chat message recall-by-bot --robot-code --group \ - --keys --format json -``` - -### 机器人发群消息(含机器人不在群内的处理) - -机器人通过 `send-by-bot --group` 发群消息时,如果返回"机器人不存在"错误,说明该机器人尚未加入目标群,需先邀请进群再发送。 - -```bash -# Step 1: 查我的机器人 — 提取 robot-code -dws chat bot search --format json - -# Step 2: 尝试发送,若报"机器人不存在"则执行 Step 3 -dws chat message send-by-bot --robot-code --group \ - --title "通知" --text "内容" --format json - -# Step 3: 邀请机器人进群 -dws chat group members add-bot --group --robot-code - -# Step 4: 重新发送 -dws chat message send-by-bot --robot-code --group \ - --title "通知" --text "内容" --format json -``` - -### 给机器人发单聊消息(必须先用 find 拿 openDingTalkId) - -给机器人发单聊消息时,必须先用 `chat bot find` 搜索机器人拿到 `openDingTalkId`,再用 `chat message send --open-dingtalk-id` 发送。不能用 `chat bot search`,因为 search 不返回 `openDingTalkId`。 - -```bash -# Step 1: 搜索机器人 — 提取 openDingTalkId(必须用 find,search 没有此字段) -dws chat bot find --query "玉澜" --format json - -# Step 2: 用 openDingTalkId 发单聊消息 -dws chat message send --open-dingtalk-id --text "你好" --format json -``` - -### 机器人 @指定人发群消息 - -通过 `--at-user-ids` 传入 userId 列表或 `--at-open-dingtalk-ids` 传入 openDingtalkId 列表来 @指定成员,多个用逗号分隔。`--text` 中需包含 `@userId` 或 `@openDingtalkId` 文本(不要用尖括号,不要用姓名)。通过 `--at-all` @所有人。 - -```bash -# Step 1: 搜人获取 userId -dws contact user search --query "张三" --format json - -# Step 2: 用 userId 发送并 @(注意 text 中 @userId) -dws chat message send-by-bot --robot-code --group \ - --at-user-ids userId1,userId2 \ - --title "提醒" --text "@userId1 @userId2 请查收本周报告" --format json - -# 或者用 openDingtalkId 发送并 @ -dws chat message send-by-bot --robot-code --group \ - --at-open-dingtalk-ids openDingtalkId1,openDingtalkId2 \ - --title "提醒" --text "@openDingtalkId1 @openDingtalkId2 请查收本周报告" --format json - -# @所有人 -dws chat message send-by-bot --robot-code --group \ - --at-all --title "通知" --text "请所有人注意" --format json -``` - - -### 发送图片+文字 / 文件+文字消息(跨产品: drive → chat) - -- **图片+文字**:图片**必须**通过 `dt_media_upload` 工具(非 dws 命令,是 agent 可调用的独立 tool)上传获取 mediaId,然后用 Markdown 嵌入方式发送。**禁止**使用钉盘上传图片。 -- **文件+文字**:文件通过钉盘上传 + Markdown 嵌入方式发送。 - -纯发图片/文件(不带文字)的完整流程见 [intent-guide.md](./01-messaging.md) 对应章节。 - -```bash -# === 图片+文字 === -# Step 1: 调用 dt_media_upload 工具上传图片(这是一个独立的 tool,不是 dws 命令) -# dt_media_upload 会返回 mediaId(如 @lQLPxxx) -# 提取 mediaId 可使用脚本: python extract_media_id.py "<返回的URL>" - -# Step 2: 用 Markdown 语法发送(mediaId 作为图片引用) -dws chat message send --group \ - --text "![截图](mediaId) 这是本周的数据汇总" --format json - -# === 文件+文字 === -# Step 1: 上传文件到钉盘 -dws drive upload --file "报告.pdf" --format json - -# Step 2: 获取下载链接 -dws drive download --file-id --format json - -# Step 3: 用 Markdown 语法发送 -dws chat message send --group \ - --text "[报告.pdf](下载链接) 这是季度报告" --format json -``` - -#### 创建并推送流式卡片 — 向群聊或单聊发送流式卡片消息 - -群聊传 --group,单聊传 --receiver,二者互斥。 - -**注意:send-card 必须和 update-card 搭配使用。** 创建卡片时无需传入内容,后续通过 update-card 更新内容,最后一次更新必须将 --flow-status 设为 3(finish),否则卡片会一直处于"生成中"的加载状态。 -flow-status 取值:1=处理中(PROCESSING),2=输入中(INPUTTING),3=完成(FINISH),4=执行中(EXECUTING),5=错误(ERROR)。 -``` -Usage: - dws chat message send-card [flags] -Example: - dws chat message send-card --group - dws chat message send-card --receiver - # 查询群 ID: dws chat search --query "群名" - # 查询人员: dws aisearch person --keyword "姓名" --dimension name -Flags: - --group string 群聊 openConversationId(群聊时必填,与 --receiver 互斥) - --receiver string 单聊接收者 openDingTalkId(单聊时必填,与 --group 互斥) -``` - -#### 流式更新卡片内容 — 更新已发送的流式卡片内容 - ---biz-id 为 send-card 返回的业务 ID,--flow-status 控制流式状态。 -flow-status 取值:1=处理中(PROCESSING),2=输入中(INPUTTING),3=完成(FINISH),4=执行中(EXECUTING),5=错误(ERROR)。 - -**最后一次更新必须将 --flow-status 设为 3(finish),否则卡片会一直处于"生成中"的加载状态。** -``` -Usage: - dws chat message update-card [flags] -Example: - dws chat message update-card --biz-id --content "更新的卡片内容" --flow-status 2 - dws chat message update-card --biz-id --content "最终内容" --flow-status 3 -Flags: - --biz-id string 卡片业务 ID (必填) - --content string 卡片消息内容 (必填) - --flow-status int 流式状态 (必填) -``` - -## 上下文传递表 - -| 操作 | 从返回中提取 | 用于 | -|------|-------------|------| -| `chat search` | `openConversationId` | message send/list、group members 等的 --group | -| `chat group create` | `openConversationId` | 同上 | -| `chat message list-all` | `nextCursor` | 下次 list-all 的 --cursor | -| `aisearch person` | `userId` | message send 的 --user、send-by-bot 的 --users、send-by-bot 的 --at-user-ids、list-by-sender 的 --sender-user-id | -| `aisearch person` → `contact user get` | `openDingTalkId` | message send 的 --at-open-dingtalk-ids、--open-dingtalk-id、send-by-bot 的 --open-dingtalk-ids、send-by-bot 的 --at-open-dingtalk-ids、list-by-sender 的 --sender-open-dingtalk-id、message list 的 --open-dingtalk-id | -| `chat bot search` | `robotCode` | send-by-bot / recall-by-bot 的 --robot-code(仅我创建的机器人,无 openDingTalkId) | -| `chat bot find` | `botOpenDingTalkId` | 给机器人发单聊消息(send --open-dingtalk-id;字段名是 botOpenDingTalkId,非 openDingTalkId) | -| `chat message send-by-bot` | `processQueryKey` | recall-by-bot 的 --keys | -| `chat message send` | `openTaskId` | query-send-status 的 --open-task-id | -| `chat message list` | `openMessageId` | recall 的 --msg-id | -| `chat message search` | `nextCursor` | 下次 message search 的 --cursor | -| `chat message search-advanced` | `nextCursor` | 下次 message search-advanced 的 --cursor | -| `chat search-common` | `openConversationId` | message send/list 等的 --group | -| `chat conversation-info` | `newCSpaceIdIM` | drive upload 的 --space-id(发送文件消息前获取共享空间) | -| `chat message list` | `openMsgId` | message read-status 的 --message-id | -| `chat group-role list` | `openRoleId` | group-role update/remove/set-user/remove-user 的 --role-id | -| `chat message create-text-emotion` | `emotionId` | add-text-emotion 的 --emotion-id | -| `chat category list` | `categoryId` | category list-conversations 的 --category-id | -| `chat group get-by-group-id` | `openConversationId` | 同 chat search,将群号转为 openConversationId | -| `chat message send-card` | `bizId` | update-card 的 --biz-id | -| `drive download` | 下载链接 | message send 的 Markdown 图片/链接语法 | -| `chat message list` | `openMessageId` | message reply 的 --ref-msg-id、message forward 的 --msg-id | -| `chat search` | `openConversationId` | set-top 的 --conversation-id、group-mute / group-mute-member 的 --group | - -## 注意事项 - -- **发消息前参数审查(必须执行)**: - - 发消息(`chat message send`、`send-by-bot`、`send-by-webhook`、`send-card`、`reply`、`forward`)是严肃操作,一旦发错人/发错群会导致严重问题,因此在执行发送之前,agent 必须对所有参数进行内部审查 - - 审查方式:将即将发送的**全部参数**(收件人/群、消息内容、@对象、消息类型等)与用户的**原始需求**逐一对比,确认每个参数都能从原始需求中找到明确依据 - - 如果存在任何不明确、有歧义或原始需求中未提及的参数(例如:用户没说发给谁、没说发到哪个群、消息内容与用户意图有出入、不确定是否需要 @某人等),**必须先向用户确认**,严禁自行假设或补全 - - 典型需要确认的场景:用户只说了"发个消息"但没指定群/人;用户的描述可匹配多个群或多个联系人;消息文本由 agent 组织而非用户原文提供时需确认措辞 -- uuid 幂等参数(发消息最佳实践): - - 发消息时建议始终带上 `--uuid` 参数,传入用户自行生成的唯一标识(如 UUID v4),用于幂等控制 - - 如果发送失败需要重试,重试时 `--uuid` 必须与首次发送保持一致,服务端据此去重,避免重复发消息 - - 如果不传 `--uuid`,每次调用都视为新消息,重试可能导致消息重复发送 - - 此参数适用于 `chat message send`(群聊和单聊均支持) -- `--group` 为群聊会话 ID (openconversation_id),可从群搜索或群聊信息中获取 -- `chat message send` 的 text 是位置参数(恰好 1 个),非 flag;群聊用 `--group`,单聊用 `--user`(userId)或 `--open-dingtalk-id`(openDingTalkId),三者互斥;纯文本/Markdown 单聊传 `--user` 时直接走 userId 发送能力;`--at-all`、`--at-open-dingtalk-ids` 仅在 `--group` 群聊时生效;富媒体消息通过 `--msg-type` 指定类型(image/file/audio/video),必须显式指定;发送文件/媒体消息时,必须先根据文件扩展名判断 msgType:图片→image,音频→audio,视频→video,其他→file,不可跳过此判断 -- `chat message list-all` 的四个参数(--start、--end、--limit、--cursor)每次请求都必须传递;翻页时用响应中的 nextCursor 值作为下次 --cursor -- `chat message list` 的 `--group`、`--user`、`--open-dingtalk-id` 三者互斥,必须且只能指定其一 -- `chat message list-by-sender` 不需要指定单聊/群聊,返回结果自带会话类型标识;`--sender-user-id`(userId)与 `--sender-open-dingtalk-id`(openDingTalkId)二选一;时间用 `--start`/`--end`(ISO-8601),分页用 `--limit`/`--cursor` -- `chat message list-mentions` 可选 `--group` 指定群聊,不传则查全部;时间用 `--start`/`--end`(ISO-8601),分页用 `--limit`/`--cursor` -- `chat message list-unread-conversations` 获取当前用户未读会话列表,可选 `--count` 指定返回条数 -- `chat message search` 按关键词搜索消息内容,`--query` 必填,可选 `--group` 限定搜索某个会话;时间用 `--start`/`--end`(ISO-8601),分页用 `--limit`(默认 100)/`--cursor` -- `chat message read-status` 查询指定消息的已读/未读状态,仅消息发送者可查询自己发出的消息;`--group`、`--message-id` 必填;目标用户 userId 用 `--user`/`--users`,openDingTalkId 用 `--target-open-dingtalk-ids`,不传则查所有接收者 -- `chat search-common` 搜索共同群,`--nicks` 传人员昵称(逗号分隔),`--match-mode` AND/OR 控制匹配逻辑,分页用 `--limit`(默认 20)/`--cursor` -- `chat list-top-conversations` 拉取置顶会话列表,分页用 `--limit`(默认 1000)/`--cursor`;用户询问"置顶会话"或"置顶消息"时均路由到此命令 -- `--user` 和 `--open-dingtalk-id` 本质上都是发起单聊操作,只是用户标识格式不同:userId 为企业内部应用常用标识,openDingTalkId 为三方应用或跨组织场景下的用户标识,服务端对两种 ID 的解析逻辑不同 -- `--time` 格式: `yyyy-MM-dd HH:mm:ss`,为拉取消息的起始时间点;`--direction` 控制方向(newer=从给定时间往现在拉,older=从给定时间往以前拉),`--limit` 控制数量 -- `chat search` 挂在 `chat` 下(非 `chat group` 下),路径为 `dws chat search` -- `send-by-bot` 群聊传 `--group`,单聊传 `--users` 或 `--open-dingtalk-ids`,与 `--group` 互斥且必选其一;群聊时可选 `--at-user-ids` @指定成员(传 userId 列表)或 `--at-open-dingtalk-ids` @指定成员(传 openDingtalkId 列表),content 中需包含对应 @标识;`--at-all` @所有人;群聊场景如果返回"机器人不存在"错误,需先通过 `chat group members add-bot --group --robot-code ` 将机器人邀请进群后再发送 -- `recall-by-bot` 群聊传 `--group` + `--keys`,单聊仅传 `--keys`(不传 `--group` 即为单聊撤回) -- `send-by-webhook` 支持 `--at-all`、`--at-mobiles`、`--at-users` 进行 @ 操作,但需在 `--text` 中包含 `@userId` 或 `@手机号` 才能生效;`--at-all` @所有人时需在 `--text` 中包含 `@10` -- `chat group-role` 系列命令用于管理群的自定义身份标签:`list` 查列表,`add` 创建,`update` 改名,`remove` 删除;`set-user` 覆盖某人全部身份(传空 --role-ids 则清除),`remove-user` 仅移除指定身份,`query-user` 查询某人当前身份;用户用 `--user ` -- 消息**换行符**(`send` / `send-by-bot` / `send-by-webhook` 的 `--text`)有两层要求:(1) 必须是**真实换行符** `U+000A`,不是字面量 `\n`;(2) Markdown 规范下单换行不生效,需用空行 `\n\n`(段落分隔)或行尾两空格 + 换行 / `
`(硬换行) -- `chat group transfer-owner` 转让群主,需传 --group(openConversationId);新群主 userId 用 `--user`,openDingTalkId 用 `--new-owner` -- `chat group invite-url` 获取群邀请链接,需传 --group(openConversationId),可选 --expires-seconds 指定有效期(秒,0=永久) -- `chat group quit` 退出群聊,需传 --group(openConversationId) -- `chat group update-icon` 更新群头像,需传 --group(openConversationId)和 --icon-media-id(mediaId) -- `chat group update-settings` 更新群设置,需传 --group(openConversationId)、--setting-key(设置项 key)、--status(0=关闭 1=开启) -- `chat message send-card` 创建并推送流式卡片,群聊传 --group,单聊传 --receiver,二者互斥;不传 content,后续通过 update-card 更新内容 -- `chat message update-card` 流式更新卡片内容,需传 --biz-id(创建卡片返回的业务 ID)、--content、--flow-status -- `chat message list-by-ids` 根据消息 ID 批量查询,--msg-ids 逗号分隔,最多 50 条 -- `chat message add-emoji` / `remove-emoji` 需传 --group(openConversationId)、--msg-id(openMsgId)、--emoji(表情名称) -- `chat message add-text-emotion` / `remove-text-emotion` 需传 --group、--msg-id、--emotion-id、--emotion-name、--text、--background-id,六个参数全部必填 -- `chat message create-text-emotion` 创建文字表情模板,返回 emotionId;--background-id 可选,不传由服务端默认分配 -- `chat category list` 无需参数;`category list-conversations` 需传 --category-id(通过 category list 获取) -- `chat mute` 默认开启免打扰,传 --off 关闭;--conversation-id / --id / --chat 三个别名均可用于传入会话 ID -- `chat message reply` 引用回复消息(**单聊/群聊均可**),需传 --conversation-id(openConversationId,单聊与群聊使用同一字段)、--ref-msg-id(被引用消息 openMessageId)、--ref-sender(被引用消息发送者 openDingTalkId)、--text(回复内容);目前回复类型仅支持 text -- `chat message forward` 转发单条消息(**源/目标会话均支持单聊/群聊**,常见组合:群→群、群→单、单→群、单→单),需传 --src-conversation-id(源会话 openConversationId)、--msg-id(源消息 openMessageId)、--dest-conversation-id(目标会话 openConversationId) -- `chat set-top` 设置/取消会话置顶(**单聊/群聊均可**),需传 --conversation-id(openConversationId,单聊与群聊使用同一字段),默认置顶,传 --off 取消 -- `chat message reply` 以当前用户身份引用回复,与 `chat message send` 的用户身份发送语义一致 -- **如何获取 openConversationId**(如果上层已有则直接使用,不必再查): - - 群聊:`dws chat search --query "群名"` - - 单聊:`dws chat conversation-info --user ` 或 `dws chat conversation-info --open-dingtalk-id `(人员信息可通过 `dws aisearch person --keyword "姓名" --dimension name` 获取) -- `chat group-mute` 全员禁言/取消全员禁言,需传 --group(openConversationId),默认禁言,传 --off 取消 -- `chat group-mute-member` 指定群成员禁言,需传 --group、--user/--users(userId,逗号分隔,CLI 自动解析为 openDingTalkId)、--mute-time(毫秒,仅禁言时必填,支持 300000/3600000/86400000/604800000/2592000000),传 --off 解除禁言;禁言群主会被服务端拒绝 -- `chat group set-admin` 设置/取消群管理员,需传 --group(openConversationId)、--user/--users(userId,逗号分隔),默认设为管理员,传 --off 取消 - -## 自动化脚本 - -| 脚本 | 场景 | 用法 | -|------|------|------| -| [chat_export_messages.py](../scripts/chat_export_messages.py) | 导出群聊消息到 JSON 文件 | `python chat_export_messages.py --query "项目冲刺" --time "2026-03-10 00:00:00"` | -| [chat_history_with_user.py](../scripts/chat_history_with_user.py) | 查询与某人的单聊聊天记录 | `python chat_history_with_user.py --name "张三" --time "2026-03-10 00:00:00"` | -| [extract_media_id.py](../scripts/extract_media_id.py) | 从 dt_media_upload URL 提取 mediaId | `python extract_media_id.py ""`(输出如 @lQLPxxx,直接用于 --media-id) | - -## 相关产品 - -- [contact](../../dingtalk-contact/references/contact.md) — 搜索同事/好友,获取 userId 用于 --user、send-by-bot --users、send-by-bot --at-user-ids、list-by-sender --sender-user-id;获取 openDingTalkId 用于 message send 的 --at-open-dingtalk-ids、--open-dingtalk-id、send-by-bot --open-dingtalk-ids、send-by-bot --at-open-dingtalk-ids、list-by-sender 的 --sender-open-dingtalk-id -- [drive](../../dingtalk-drive/references/drive.md) — 上传文件获取下载链接,用于 Markdown 图片/文件消息 +> 渐进式兼容入口:新任务优先从 [聊天能力索引](./chat-index.md) 选择专题。 + +| 任务 | 参考 | +|---|---| +| 命令路由 | [chat-index.md](./chat-index.md);兼容入口见 [chat-commands.md](./chat-commands.md) | +| 意图与标准流程 | [意图判断](./chat-intent.md) · [核心工作流](./chat-workflow.md) · [复合工作流](./chat-composite.md) | +| 上下文与约束 | [上下文](./chat-context.md) · [注意事项](./chat-notes.md) | +| 表情、脚本、相关产品 | [表情](./chat-emoji-list.md) · [脚本](./chat-scripts.md) · [相关产品](./chat-related.md) | diff --git a/skills/multi/dingtalk-chat/references/intent-guide.md b/skills/multi/dingtalk-chat/references/intent-guide.md new file mode 100644 index 00000000..10278125 --- /dev/null +++ b/skills/multi/dingtalk-chat/references/intent-guide.md @@ -0,0 +1,85 @@ +# chat 局部意图消歧 + +本文件从单 Skill `intent-guide.md` 拆分而来,仅保留与本产品相关的跨产品消歧规则。 + +| 用户说... | 真实意图 | 应该用 | 不要用 | 理由 | +|---|---|---|---|---| +| "通知群里的人都来开会" | 个人身份群发 | `chat message send` | `chat message send-by-bot` | 以个人身份向群发消息 | +| "让机器人每天推送日报" | 机器人定时推送 | `chat message send-by-bot` | `chat message send` | 需要机器人身份定期发送 | +| "CPU 超过 90% 自动告警" | Webhook 告警 | `chat message send-by-webhook` | `chat message send-by-bot` | 系统告警场景,需自定义 Webhook | +| "拉取一下上周项目群的聊天记录" | 拉取会话消息 | `chat message list` | — | 拉取指定群聊的消息列表 | +| "看看张三发给我的消息" | 按发送者查询消息 | `chat message list-by-sender` | `chat message list --user` | 用户未明确说"单聊"时优先用 list-by-sender(跨单聊/群聊) | +| "拉取和张三的单聊记录" | 拉取单聊消息 | `chat message list --user` | `chat message list-by-sender` | 用户明确说"单聊"时用 list --user | +| "谁@了我/查看提及我的消息" | 查询@我的消息 | `chat message list-mentions` | `chat message list-all` | 都是跨会话时间范围查询,但 list-mentions 只返回@我的消息 | +| "查看我今天的所有消息" | 全量会话消息 | `chat message list-all` | `chat message list` | 用户未指定具体会话时用 list-all(跨所有会话),指定了具体群或人时用 list | +| "搜一下消息里的changefree链接" | 消息搜索 | `chat message search-advanced`(首选) | `chat search` | 推荐首选 search-advanced,它是 search 的严格超集(keyword 可选、支持多群、可叠加发送者/at 维度) | +| "按发送者搜索/指定多个群搜索/多维度搜消息" | 多维度搜索消息 | `chat message search-advanced`(首选) | `chat message search` | 推荐首选,支持关键词、发送者、@我、多个会话等维度组合 | +| "消息发没发成功/查询消息发送状态" | 查询消息发送状态 | `chat message query-send-status` | — | 需要 send 返回的 openTaskId | +| "撤回我发的消息/撤回消息/群主撤回消息/管理员撤回他人消息" | 撤回消息 | `chat message recall` | `chat message recall-by-bot` | recall 撤回个人消息或群主/管理员撤回他人消息,recall-by-bot 撤回机器人消息 | +| "未读消息会话/未读会话列表/我的未读会话" | 未读会话列表 | `chat message list-unread-conversations` | `chat message read-status` | list-unread-conversations 查哪些会话有未读;read-status 查具体消息的已读状态 | +| "谁看了这条消息/消息已读未读/查读状态" | 查询消息已读状态 | `chat message read-status` | `chat message list-unread-conversations` | read-status 查具体消息的已读人员;list-unread-conversations 查未读会话列表 | +| "查看消息的表情回复/拉取消息回复/消息的文字回复" | 批量拉取消息表情回复和文字回复 | `chat message list-emotion-replies` | — | 根据消息 ID 批量查询表情回复和文字回复 | +| "我和风雷的共同群/我们都在哪些群" | 搜索共同群 | `chat search-common` | `chat search` | search-common 按人员搜共同群,chat search 按群名搜索 | +| "查看会话分组/自定义分组" | 获取会话分组 | `chat category list` | — | 获取用户自定义的会话分组列表 | +| "某个分组下有哪些会话" | 分组下会话列表 | `chat category list-conversations` | — | 需先通过 category list 获取分组 ID | +| "创建会话分组/新建分组" | 创建自定义分组 | `chat category create` | — | 需指定 --title 分组名称 | +| "删除会话分组/移除分组" | 删除自定义分组 | `chat category delete` | — | 需指定 --category-id | +| "重命名分组/修改分组名称" | 更新分组名称 | `chat category rename` | — | 需指定 --category-id 和 --title | +| "把会话加到分组/将群移入分组" | 会话加入分组 | `chat category add-conv` | `chat category remove-conv` | add-conv 加入;remove-conv 移出 | +| "把会话从分组移出/从分组中删除" | 会话移出分组 | `chat category remove-conv` | `chat category add-conv` | remove-conv 移出;add-conv 加入 | +| "创建智能分组/新建智能会话分组/按关键词分组" | 创建智能会话分组 | `chat category create-smart` | `chat category create` | create-smart 按关键词/成员智能匹配;create 手动创建普通分组 | +| "分享群链接/把群分享给某人/群链接发到某群" | 分享群聊链接到会话 | `chat group share-invite` | `chat group invite-url` | share-invite 直接分享到目标会话/用户;invite-url 只获取链接不发送 | +| "根据群号查群信息/群号转openConversationId" | 群号查群聊信息 | `chat group get-by-group-id` | `chat search` | 已知数字群号时直接查;用户发消息只给了群号时,先用此工具将群号转为 openConversationId | +| "我创建的群/我管理的群/我是群主的群/我当管理员的群" | 拉取我创建/管理的群 | `chat group list-my-groups` | `chat search` | list-my-groups 按角色(群主/管理员)过滤当前用户的群;chat search 按关键词搜全部群 | +| "引用消息回复/回复那条消息" | 引用回复消息 | `chat message reply` | `chat message send` | reply 引用指定消息回复;send 普通发消息不引用 | +| "转发消息/把消息转到另一个群" | 转发单条消息 | `chat message forward` | `chat message send` | forward 转发已有消息到目标会话;send 是发新消息 | +| "置顶会话/取消置顶" | 设置/取消会话置顶 | `chat set-top` | `chat list-top-conversations` | set-top 设置或取消置顶;list-top-conversations 查看置顶列表 | +| "全员禁言/群禁言" | 全员禁言或解除 | `chat group-mute` | `chat group-mute-member` | group-mute 全员禁言或解除;group-mute-member 指定成员禁言 | +| "禁言某人/指定成员禁言" | 指定成员禁言 | `chat group-mute-member` | `chat group-mute` | group-mute-member 指定成员禁言/解禁;group-mute 全员禁言 | +| "设管理员/取消管理员" | 设置群管理员 | `chat group set-admin` | `chat group invite` | set-admin 设置或取消管理员角色;invite 是邀请入群 | +| "退群/退出群聊/离开群" | 退出群聊 | `chat group quit` | `chat group members remove` | quit 是当前用户自己退群;members remove 是管理员踢别人 | +| "改群头像/更新群头像" | 更新群头像 | `chat group update-icon` | `chat group rename` | update-icon 改群头像;rename 改群名称 | +| "改群设置/群设置开关/群权限/入群许可/禁止私聊" | 更新群设置 | `chat group update-settings` | `chat group set-admin` | update-settings 改群功能开关;set-admin 是管理员角色操作 | +| "改群昵称/设置群昵称/我在群里的名字" | 设置个人群昵称 | `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 是整个会话免打扰 | +| "解散群/解散群聊" | 解散群聊 | `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 列普通群成员 | +| "从群里移除机器人/踢机器人" | 移除群内机器人 | `chat group members remove-bot` | `chat group members add-bot` | remove-bot 通过 openBotId 移除;add-bot 通过 robotCode 添加 | +| "批量查群成员详情/按ID查群成员/查看指定成员信息" | 批量查询群成员详情 | `chat group members list-by-ids` | `chat group members` | list-by-ids 根据 openDingTalkId 列表查询指定成员详情;members 分页查询全部成员列表 | +| "标记未读/设为未读/会话标未读" | 标记会话为未读 | `chat mark-unread` | `chat clear-red-point` | mark-unread 标记为未读;clear-red-point 清除红点(相反操作) | +| "清除红点/已读/取消未读" | 清除会话红点 | `chat clear-red-point` | `chat mark-unread` | clear-red-point 清除单个会话红点;mark-unread 标记未读(相反操作) | +| "全部已读/一键清除所有未读/红点清零" | 清除所有会话红点 | `chat clear-all-red-point` | `chat clear-red-point` | clear-all-red-point 清除所有会话红点;clear-red-point 只清单个会话 | +| "所有会话/全部会话列表/我的会话" | 分页获取全部会话 | `chat list-all-conversations` | `chat list-top-conversations` | list-all-conversations 返回所有会话;list-top-conversations 仅返回置顶会话 | +| "清空聊天记录/删除会话消息/清空消息" | 清空会话聊天记录 | `chat clear-messages` | `chat clear-red-point` | clear-messages 清空聊天记录;clear-red-point 仅清除未读红点 | +| "标记已读/消息已读/读了" | 标记消息已读 | `chat mark-read` | `chat clear-red-point` | mark-read 标记指定消息及之前的消息为已读;clear-red-point 仅清除红点不标记消息 | +| "我加入的所有群/拉取全部群列表/我的群" | 分页拉取所有群 | `chat group list-all` | `chat group list-my-groups` | list-all 返回用户加入的所有群;list-my-groups 仅返回用户作为群主/管理员的群 | +| "入群申请记录/谁申请入群/群申请列表/查看入群审批" | 拉取入群验证记录 | `chat group list-join-validations` | `chat group list-all` | list-join-validations 拉取入群验证/审批记录;list-all 拉取群列表 | +| "通过入群申请/拒绝入群/审批入群/同意加群" | 审批入群验证 | `chat group audit-join-validation` | `chat group list-join-validations` | audit-join-validation 执行审批操作;list-join-validations 仅查询记录 | +| "发群公告/在群里发公告/群公告置顶" | 发布群公告 | `chat group notice create` | `blackboard create` | notice create 是群维度公告(发到指定群聊);blackboard 是企业公告(面向全员,不可撤回) | +| "改群公告/修改群公告/更新群公告" | 修改群公告 | `chat group notice edit` | `chat group notice create` | edit 整体替换已有公告内容(需 dataId);create 发布新公告 | +| "查群公告/看群公告/群公告列表/群里有什么公告" | 查看群公告列表 | `chat group notice list` | `blackboard list` | notice list 查指定群的公告;blackboard list 查企业公告 | +| "群公告详情/公告已读人数/谁读了公告" | 查看群公告详情 | `chat group notice get` | `chat group notice list` | get 返回单条公告详情(含已读人数、点赞/评论数);list 分页拉公告列表 | +| "搜索机器人/找机器人/查机器人/帮我找XXX机器人" | 搜索全部可用机器人 | `chat bot find` | `chat bot search` | find 返回全部可用机器人(含他人/官方),额外返回 openDingTalkId(可用于给机器人发单聊消息);search 仅返回我创建的 | +| "给机器人发单聊/给机器人发消息/跟机器人聊天" | 给机器人发单聊消息 | `chat bot find` → `chat message send --open-dingtalk-id` | `chat bot search` | 必须先用 find 拿 openDingTalkId(search 没有此字段),再用 send --open-dingtalk-id 发单聊 | +| "我创建的机器人/我的机器人/我自己的机器人/查看我的机器人" | 搜索我创建的机器人 | `chat bot search` | `chat bot find` | search 仅返回当前用户自己创建的机器人(返回 robotCode + robotName,无 openDingTalkId);find 返回全部可用机器人 | +| "合并转发/批量转发/合并转发多条消息" | 合并转发多条消息 | `chat message combine-forward` | `chat message forward` | combine-forward 合并多条为一条转发;forward 转发单条消息 | +| "转发话题/转发话题消息/话题转发到另一个群" | 转发话题消息 | `chat message forward-topic` | `chat message forward` | forward-topic 专用于转发话题消息(需要话题ID);forward 转发普通单条消息 | +| "发卡片消息/推送流式卡片" | 创建并推送流式卡片 | `chat message send-card` | `chat message send` | send-card 发流式卡片;send 发普通文本/Markdown 消息 | +| "更新卡片/流式更新卡片" | 流式更新卡片内容 | `chat message update-card` | `chat message send-card` | update-card 更新已有卡片;send-card 创建新卡片 | +| "钉住消息/Pin消息/置顶消息到会话" | 钉住消息 | `chat message set-pin-msg` | `chat set-top` | set-pin-msg 钉住单条消息(Pin);set-top 置顶整个会话 | +| "取消钉住/Unpin消息" | 取消钉住消息 | `chat message unset-pin-msg` | `chat message set-pin-msg` | unset-pin-msg 取消钉住;set-pin-msg 设置钉住 | +| "查看钉住的消息/Pin列表/钉住消息列表" | 拉取钉住消息列表 | `chat message list-pin-msg` | `chat message list` | list-pin-msg 只返回被钉住的消息;list 拉取全部消息 | +| "置顶某条消息/把这条消息置顶" | 置顶消息 | `chat message set-top-msg` | `chat set-top` | set-top-msg 置顶会话内某条消息;set-top 置顶整个会话在列表中 | +| "取消置顶消息/取消消息置顶" | 取消置顶消息 | `chat message unset-top-msg` | `chat message set-top-msg` | unset-top-msg 取消置顶;set-top-msg 设置置顶 | +| "DING消息/查DING/DING历史" | 查询 DING 消息列表 | `ding message list` | `chat message list` | ding 是独立顶层命令;ding message list 查 DING 消息;chat message list 查普通聊天消息 | +| "DING接收状态/谁收到了DING" | DING 接收状态 | `ding message receiver-status` | `chat message read-status` | ding 是独立顶层命令;receiver-status 查 DING 接收;chat message read-status 查普通消息已读 | +| "发DING/DING通知" | 发送 DING 消息 | `ding message send` | `chat message send` | DING 是钉钉的强提醒(应用内/短信/电话),独立顶层命令;普通群消息用 chat | +| "撤回DING" | 撤回 DING 消息 | `ding message recall` | `chat message recall` | DING 撤回独立命令;chat recall 是撤回普通聊天消息 | +| "搜一下智能化方案/最近 OKR 相关邮件/最近发版相关消息" | 搜企业知识内容 | `aisearch enterprise` | `doc search` / `mail search` / `chat message search` | 跨文档、消息、日程、听记、邮件等企业内容语义检索走 enterprise;具体 `queries/types/time-range` 抽槽见 `aisearch.md` | +| "我发给某人的消息/邮件/文档/今天我干了什么" | 搜行为记录 | `aisearch behavior` | `chat` / `mail` / `doc` / `report` | 关注“谁对什么做过什么”,走 behavior;具体 `behavior-type/direction/chat-scope` 抽槽见 `aisearch.md` | +| "把这段文字翻译成英文/translate this" | 通用文本翻译 | `chat text translate` | `doc` / `aisearch` | 纯文本翻译,不是文档编辑或语义搜索 | +| "帮我把这个文档翻译成日文" | 文档内容翻译 | 先 `doc get` 再 `chat text translate` | `chat text translate` 直接传文件 | translate 仅支持纯文本,需先提取文档内容 | diff --git a/skills/multi/dingtalk-chat/references/lite-recipes.md b/skills/multi/dingtalk-chat/references/lite-recipes.md new file mode 100644 index 00000000..082f4b56 --- /dev/null +++ b/skills/multi/dingtalk-chat/references/lite-recipes.md @@ -0,0 +1,7 @@ +# chat Lite Recipe + +本文件从单 Skill `lite-recipes.md` 拆分而来,仅保留与本产品相关的轻量流程。 + +## #1 消息沟通 + +所有消息沟通相关的命令详情、参数说明、意图路由和复合工作流,请查阅 [chat.md](./chat-commands.md)。 diff --git a/skills/multi/dingtalk-chat/scripts/chat_export_messages.py b/skills/multi/dingtalk-chat/scripts/chat_export_messages.py index 2b77d85e..da7e50c4 100644 --- a/skills/multi/dingtalk-chat/scripts/chat_export_messages.py +++ b/skills/multi/dingtalk-chat/scripts/chat_export_messages.py @@ -16,11 +16,20 @@ import sys import json +import datetime import subprocess import argparse from typing import List, Any, Optional +def timestamp_to_datetime(ts_ms: int) -> str: + """将毫秒时间戳转换为 yyyy-MM-dd HH:mm:ss 格式""" + dt = datetime.datetime.fromtimestamp( + ts_ms / 1000, tz=datetime.timezone(datetime.timedelta(hours=8)) + ) + return dt.strftime("%Y-%m-%d %H:%M:%S") + + def run_dws( args: List[str], dry_run: bool = False, ) -> Optional[Any]: @@ -53,12 +62,20 @@ def search_group( return '' if not data: return None + if isinstance(data, dict) and data.get('success') is False: + print(f"搜索失败: {data.get('errorMsg', '未知错误')}", file=sys.stderr) + return None if isinstance(data, list): groups = data elif isinstance(data, dict): inner = data.get('result', data) if isinstance(inner, dict): - groups = inner.get('items', inner.get('groups', [])) + groups = ( + inner.get('value') + or inner.get('groups') + or inner.get('items') + or [] + ) elif isinstance(inner, list): groups = inner else: @@ -135,21 +152,32 @@ def main(): if not data: break - # 兼容两种结构: 顶层 messages / {result: {messages, hasMore}} + if isinstance(data, dict) and data.get('success') is False: + print(f"拉取失败: {data.get('errorMsg', '未知错误')}", file=sys.stderr) + break + if isinstance(data, list): page_msgs = data has_more = False - else: - container = data - inner = data.get('result') + next_cursor = None + elif isinstance(data, dict): + inner = data.get('result', data) if isinstance(inner, dict): - container = inner - page_msgs = container.get('messages') - if page_msgs is None and isinstance(inner, list): + page_msgs = inner.get('messages', []) + has_more = inner.get('hasMore', False) + next_cursor = inner.get('nextCursor') + elif isinstance(inner, list): page_msgs = inner - if not isinstance(page_msgs, list): + has_more = False + next_cursor = None + else: page_msgs = [] - has_more = bool(container.get('hasMore', False)) + has_more = False + next_cursor = None + else: + page_msgs = [] + has_more = False + next_cursor = None if not page_msgs: break @@ -161,13 +189,19 @@ def main(): if not has_more: break - last_msg = page_msgs[-1] - 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 + # 翻页:优先用 nextCursor(毫秒精度,转为时间字符串),降级用最后消息的 createTime + if next_cursor: + current_time = timestamp_to_datetime(next_cursor) + else: + last_msg = page_msgs[-1] + 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 print(f" 翻页 {page}: 已累计 {len(all_messages)} 条, 继续...") if not all_messages: @@ -180,12 +214,14 @@ def main(): print(f" ✓ 已导出 {len(all_messages)} 条消息到 {args.output}") else: for m in all_messages: - # 实际字段: sender/createTime/content (兼容旧字段名) - sender = (m.get('sender') or m.get('senderNick') - or m.get('senderOpenDingTalkId', '未知')) + sender = ( + m.get('sender') or m.get('senderNick') or '未知' + ) text = m.get('content') or m.get('text', '') - time_str = (m.get('createTime') or m.get('createAt') - or m.get('time', '')) + 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 b007c7be..8ef9c398 100644 --- a/skills/multi/dingtalk-chat/scripts/chat_history_with_user.py +++ b/skills/multi/dingtalk-chat/scripts/chat_history_with_user.py @@ -15,11 +15,20 @@ import sys import json +import datetime import subprocess import argparse from typing import List, Any, Optional +def timestamp_to_datetime(ts_ms: int) -> str: + """将毫秒时间戳转换为 yyyy-MM-dd HH:mm:ss 格式""" + dt = datetime.datetime.fromtimestamp( + ts_ms / 1000, tz=datetime.timezone(datetime.timedelta(hours=8)) + ) + return dt.strftime("%Y-%m-%d %H:%M:%S") + + def run_dws( args: List[str], dry_run: bool = False, ) -> Optional[Any]: @@ -122,7 +131,7 @@ def main(): '--format', 'json', ] if args.no_forward: - cmd_args.append('--forward=false') + cmd_args.extend(['--forward', 'false']) page_limit = min(int(remaining), 200) if args.limit > 0 else 0 if page_limit > 0: cmd_args.extend(['--limit', str(page_limit)]) @@ -135,21 +144,32 @@ def main(): if not data: break - # 兼容两种结构: 顶层 messages / {result: {messages, hasMore}} + if isinstance(data, dict) and data.get('success') is False: + print(f"拉取失败: {data.get('errorMsg', '未知错误')}", file=sys.stderr) + break + if isinstance(data, list): page_msgs = data has_more = False - else: - container = data - inner = data.get('result') + next_cursor = None + elif isinstance(data, dict): + inner = data.get('result', data) if isinstance(inner, dict): - container = inner - page_msgs = container.get('messages') - if page_msgs is None and isinstance(inner, list): + page_msgs = inner.get('messages', []) + has_more = inner.get('hasMore', False) + next_cursor = inner.get('nextCursor') + elif isinstance(inner, list): page_msgs = inner - if not isinstance(page_msgs, list): + has_more = False + next_cursor = None + else: page_msgs = [] - has_more = bool(container.get('hasMore', False)) + has_more = False + next_cursor = None + else: + page_msgs = [] + has_more = False + next_cursor = None if not page_msgs: break @@ -161,13 +181,19 @@ def main(): if not has_more: break - last_msg = page_msgs[-1] - 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 + # 翻页:优先用 nextCursor(毫秒精度,转为时间字符串),降级用最后消息的 createTime + if next_cursor: + current_time = timestamp_to_datetime(next_cursor) + else: + last_msg = page_msgs[-1] + 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 print(f" 翻页 {page}: 已累计 {len(all_messages)} 条, 继续...") if not all_messages: @@ -181,12 +207,14 @@ def main(): print(f" ✓ 已导出 {len(all_messages)} 条消息到 {args.output}") else: for m in all_messages: - # 实际字段: sender/createTime/content (兼容旧字段名) - sender = (m.get('sender') or m.get('senderNick') - or m.get('senderOpenDingTalkId', '未知')) + sender = ( + m.get('sender') or m.get('senderNick') or '未知' + ) text = m.get('content') or m.get('text', '') - time_str = (m.get('createTime') or m.get('createAt') - or m.get('time', '')) + 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 1ae39906..fc0c1e80 100644 --- a/skills/multi/dingtalk-contact/SKILL.md +++ b/skills/multi/dingtalk-contact/SKILL.md @@ -1,6 +1,6 @@ --- name: dingtalk-contact -description: 钉钉通讯录精确查询(按 userId 查详情、部门搜索、部门成员列表、查自己信息、离职员工查询、花名册档案)。Use when 用户说 查部门/部门成员/我的信息/按工号查/按 userId 查/orgAuthEmail/离职员工/离职名单/花名册。Distinct from dingtalk-aisearch(模糊搜人首选:找同事/查上下级/谁负责)。命令前缀:dws contact。 +description: 钉钉通讯录精确查询(按 userId 查详情、部门搜索、部门成员列表、角色 label、查自己信息)。Use when 用户说 查部门/部门成员/我的信息/按工号查/按 userId 查/orgAuthEmail/角色ID/角色成员/管理员角色/财务人员/HR人员。模糊找人(找同事/查上下级/谁负责)先走 dingtalk-aisearch,拿到 userId 再用本 skill 查详情。命令前缀:dws contact。 cli_version: ">=0.2.14" metadata: category: product @@ -14,13 +14,12 @@ metadata: > 🧪 **EXPERIMENTAL · 试验版 / Preview** — multi 模式当前未达 stable 标准。全部 dingtalk-* skill 已通过 dispatch verifier,但接口、命名、跨 skill 引用后续可能调整;生产 / 共享环境请优先使用 mono 模式(`dws skill setup --mode mono`)。问题请提 issue 反馈。 -> **PREREQUISITE:** Read the `dws-shared` skill first for auth, global flags, product routing, URL preflight, error codes, and safety rules. The `dws` binary must be on PATH. +## 前置条件 — 执行操作前必读 + +> **`use_skill(dws-shared)`** — 认证、全局参数(`--format json` / `--yes`)、错误码、URL 模板、跨产品消歧、安全规则与 capability 边界。**执行任何 `dws` 命令前先读;** 单产品的清晰命令可直接用本 skill。 -> ⚠️ **命令可用性以当前 dws 二进制为准**。服务发现已下线,本文档随内置 skill 发布;如果 `dws --help` 不存在,说明当前版本未暴露该命令。若命令存在但调用失败,请按错误中的 endpoint 或 tool 提示确认静态端点目录和后端工具注册。实际调用前可用 `dws --help` 或 `--dry-run` 验证。 - - > 命令参考:[contact.md](references/contact.md);剧本:[08-directory.md](references/08-directory.md)。 ## 意图表 @@ -31,20 +30,65 @@ metadata: | "按 userId 查详情" | `dws contact user get --ids ,,...`(多个并行) | | "按部门名拉成员" | `python scripts/contact_dept_members.py --query "<部门名>"` | | "搜部门" | `dws contact dept search --query "<关键词>"` | -| "部门成员列表" | `dws contact dept list-members --depts ` | -| "离职员工/离职名单/已离职" | `dws contact user dismission search`(可加 `--name` / `--start` + `--end` / `--depts`) | -| "花名册/员工档案/学历/银行卡/合同" | `dws contact user profile get --staff-id `(先 `profile fields` 查字段) | +| "部门成员列表" | `dws contact dept list-members --ids ` | +| "列出企业角色 / 有哪些角色" | `dws contact label list` | +| "按角色名查角色ID" | `dws contact label get --names "<角色名>"` | +| "查某角色下有哪些成员" | `dws contact label list-members --id ` | -## 评测高频硬约束 +## 标准 SOP(必遵流程) + +> 命中以下意图**必须**按对应 SOP 顺序执行;**禁止**跳步、替换命令、编造 userId。每条命令必须带 `--format json`。模糊找人**首选** `dingtalk-aisearch`;contact 负责 userId→详情补全与精确查询。 + +### SOP-1 搜人(search-person) + +**触发**:找人/搜同事/谁负责/上级/下级/团队成员/按部门找人。 + +1. **切 aisearch(必须)**:模糊找人优先走 `dingtalk-aisearch`:`dws aisearch person --keyword "<关键词>" --dimension <维度> --format json`(姓名→`name`、负责人→`duty`、部门→`department`、上下级→`supervisor`/`subordinate`)。 +2. **解析(必须)**:从结果取 `userId`、`title`;**多人同名禁止默认选第一个**,必须批量 `dws contact user get --ids --format json` 拿部门/职位后让用户确认。 +3. **补详情(必须)**:要完整部门/职位/邮箱/主管时 `dws contact user get --ids --format json`。 + +**禁止**:在 contact 里做模糊搜人(应切 aisearch)、默认取首个候选、编造人员字段。 + +### SOP-2 精确查人/补详情(search-user) + +**触发**:已有 userId 要查完整详情,或要拿 userId 给下游(发消息/建待办/约日程)。 + +1. **拿 userId(必须)**:`dws aisearch person --keyword "<姓名>" --dimension name --format json` → `userId`;多命中必须列候选请用户确认。 +2. **查详情(必须)**:`dws contact user get --ids --format json`,按返回字段(`orgEmployeeModel` 下部门/职位/邮箱)答复。 + +**禁止**:用模糊关键词直接调 `contact user search` 凑数、编造未返回字段。 + +### SOP-3 查自己(get-contact-self) + +**触发**:我的信息/我的 userId/我的部门。 + +1. **执行(必须)**:`dws contact user get-self --format json`,取 `orgEmployeeModel.userId` / `orgUserName` / `depts[].deptName` / 主管等。 + +**禁止**:把自己 userId 写死或猜测。 + +### SOP-4 查部门 / 角色(dept-and-relation) + +**触发**:部门列表/部门成员/角色/角色成员。 + +1. **执行(必须)**:搜部门 `dws contact dept search --keyword "<部门名>" --format json`;某部门下子部门 `dws contact dept list-children --dept <父部门ID> --format json`;部门成员 `dws contact dept list-members --depts <部门ID> --format json`;部门详情 `dws contact dept get-info --dept <部门ID> --format json`。角色:`dws contact label list` / `dws contact label get --names "<角色名>"` / `dws contact label list-members --id `。 +2. **补详情(必须)**:拿到 userId 后用 `contact user get --ids` 补部门/职位;上下级关系优先经 `dingtalk-aisearch` 的 `supervisor`/`subordinate` 维度。 + +**禁止**:使用不存在的 `contact dept list`(已废弃/歧义)、编造 deptId/labelId、跳过 aisearch 维度直接猜上下级。 + +## 高频硬约束 - 通讯录问题必须调用 `dws contact` 或 `dws aisearch` 获取实时结果;严禁只读 `USER.md`、环境身份或静态上下文后直接回答。 - 查自己用 `dws contact user get-self --format json`,不要把 `me/self/current` 当作 `userId` 传给 `user get`。 - 精确找人、按工号、按手机号:先用 `dws aisearch person --keyword "<完整输入>" --dimension name/jobNumber/phone --format json` 或对应 `contact user search/search-mobile`;拿到 `userId` 后必须 `dws contact user get --ids --format json` 补部门/职位/邮箱。 -- 查询直属主管/上下级时,如果 `contact user get` 没返回明确主管字段,必须继续 `dws aisearch person --keyword "<完整姓名或工号>" --dimension supervisor --format json`,不要停在"可能需要进一步查询"。 +- 查询直属主管/上下级时,如果 `contact user get` 没返回明确主管字段,改用 `dws aisearch person --keyword "<完整姓名或工号>" --dimension supervisor --format json`。 - 多个同名候选时,批量 `contact user get --ids id1,id2,... --format json` 获取部门/职位后再消歧;不要默认取第一个。 +- 用户查询企业角色、角色ID、角色成员,或“管理员/财务/HR/主管”等角色类型人员时,走 `contact label list/get/list-members`;不要用 `dept list-members` 筛字段替代。 ## 跨产品协作 - 模糊找人(姓名 / 上下级 / 谁负责 / 工号 / 手机号)→ 切到 `dingtalk-aisearch` - 拿到 email 发邮件 → 切到 `dingtalk-mail` - 拿到 userId 发消息 → 切到 `dingtalk-chat` +## 局部意图与 Recipe + +- [局部意图消歧](references/intent-guide.md);[Lite Recipe](references/lite-recipes.md)。 diff --git a/skills/multi/dingtalk-contact/references/08-directory.md b/skills/multi/dingtalk-contact/references/08-directory.md index 824cd967..2d2cb742 100644 --- a/skills/multi/dingtalk-contact/references/08-directory.md +++ b/skills/multi/dingtalk-contact/references/08-directory.md @@ -1,7 +1,7 @@ # 通讯录(组织架构) -> **SKILL.md** 中 #8 仅内联 2 条 **lite**:`get-contact-self`、`search-user`。下列 recipe、专用规则与消歧请在命中 #8 且**超出**上述 lite 时阅读本文。 -> 产品命令见 [contact.md](./contact.md)。通用批量/并行见 `dws-shared/references/best_practices/_common/conventions.md`。 +> **SKILL.md** 中 #8 内联 3 条 **lite**:`get-contact-self`、`search-person`、`search-user`。下列 recipe、专用规则与消歧请在命中 #8 且**超出**上述 lite 时阅读本文。 +> 产品命令见 [contact.md](./contact.md)。通用批量/并行见 [_common/conventions.md](_common/conventions.md)。 ## 专用规则(#8 非 lite 步骤必守) @@ -15,17 +15,17 @@ 3. 将「姓名 + 部门 + 职位」列表展示给用户,请用户确认选择哪一位 4. 使用用户确认的 `userId` 继续后续操作 > **根因**:`user search` 和 `aisearch person` 均不返回部门信息,无法仅凭搜索结果区分同名用户。**必须**追加 `contact user get` 获取部门信息才能消歧。 -- **批量**:多个 `userId` 用 `contact user get --ids id1,id2,...`;多部门成员列表按需并行,遵守单次批量上限与 `dws-shared/references/best_practices/_common/conventions.md`。 -- **子部门枚举**:已知父部门 `deptId` 时**优先** `contact dept list-children --id <父deptId>` 直接拿到完整子部门列表;只知道部门名时先 `contact dept search --query "<父部门名>"` 取 `deptId` 再 `list-children`;用户明示的子部门名(无需枚举)可直接 `dept search` 命中。多子部门展开见 `explore-subdepts-and-members`。 +- **批量**:多个 `userId` 用 `contact user get --ids id1,id2,...`;多部门成员列表按需并行,遵守单次批量上限与 [_common/conventions.md](_common/conventions.md)。 +- **子部门枚举**:已知父部门 `deptId` 时**优先** `contact dept list-children --dept <父deptId>` 直接拿到完整子部门列表;只知道部门名时先 `contact dept search --query "<父部门名>"` 取 `deptId` 再 `list-children`;用户明示的子部门名(无需枚举)可直接 `dept search` 命中。多子部门展开见 `explore-subdepts-and-members`。 ## 与其他场景消歧 - **按角色/职位类型查人(主管/管理员/财务等)** → 优先 `contact label list` + `label list-members`;label 精确命中角色维度,返回完整名单;aisearch 是语义模糊搜索不保证完整性。 -- **搜人/找人/找同事/查工号/查手机号** → 首选 **`aisearch person`**(AI 语义搜索,支持姓名/部门/职责/上下级/手机号/工号维度),见 `dingtalk-aisearch/references/aisearch.md`。 +- **搜人/找人/找同事/查工号/查手机号** → 首选 **`aisearch person`**(AI 语义搜索,支持姓名/部门/职责/上下级/手机号/工号维度),见 `dingtalk-aisearch`。 - **需要 userId 做后续操作 / 按手机号查 / 按 userId 查详情** → `contact`(精确查询)。 - **纯查部门与子部门成员 / 验证归属 / 组织关系** → `contact`。 - **终点是发消息、待办、日程** → 先用 `search-person` 或 `search-user` 取 `userId`,再进入 #1 / #2 / #3。 -- **联系客户 + 发邮件** → 先用 `contact` 取 `orgAuthEmail`,再走 `dingtalk-mail/references/mail.md`。 +- **联系客户 + 发邮件** → 先用 `contact` 取 `orgAuthEmail`,再走 `dingtalk-mail`。 ## Recipe 速查(本表步骤,非 SKILL lite) @@ -34,7 +34,7 @@ | `lookup-label-members` | 1. `contact label list` → 浏览全部角色,匹配目标角色的 labelId
2. `contact label list-members --id ` → 该角色下的成员列表 | | `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-subdepts` | 1. 已有父 `deptId` → `contact dept list-children --dept <父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 --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` 核对部门字段 | @@ -43,8 +43,8 @@ | 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 --depts ,,...`(多部门按 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 --dept <父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 --depts ` → 按需 `user get` | +| cross-level-dept-members | 1. `contact dept search --query "<父部门关键词>"` → 父 `deptId`
2. **优先** `contact dept list-children --dept <父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/_common/conventions.md b/skills/multi/dingtalk-contact/references/_common/conventions.md new file mode 100644 index 00000000..75491aab --- /dev/null +++ b/skills/multi/dingtalk-contact/references/_common/conventions.md @@ -0,0 +1,44 @@ +# 业务域通用规范 + +> 仅服务本 skill 已迁入的行动指南。安全门控、危险操作确认、`--format json` 等已在本 skill 的 `SKILL.md` 中定义,此处不重复。 + +## 批量查询规范 + +| # | 规范 | +|---|------| +| 1 | **并行查详情**:拿到多个 ID 后,用 `&` 合并到同一条 Shell 命令并行执行 + `wait`,**严禁逐条串行** | +| 2 | **翻页**:分页接口须拉全直至无更多 | +| 3 | **优先批量 API**:有批量接口则用批量;无则按 #1 并行 | +| 4 | **群消息**:必须先 `chat search --query` 得 `openConversationId`,再 `chat message list --group`;多群同条命令并行 | +| 5 | **列表少轮次**:带条件搜索/列表 → 一次采全详情;**禁止**无新参数时重复同一 `list` / `search` | + +## 多源并行采集(公共模式) + +> recipe 引用方式:`按「多源并行采集」执行(关键词=,时间=至)`。 + +- 同条 Shell:`&` 并行 + `wait`;分页须采全。 +- 只保留与主题相关的数据,无关丢弃。 +- 有批量详情接口优先;否则并行拉详情(见上表 #1)。 +- 具体采哪些产品列表由对应 **行动指南 recipe** 与当前产品参考决定;不要引入本文档未覆盖的产品路线。 + +## 字段术语与 ID 传递 + +> list 返回 JSON 后,必须提取下表字段传给后续命令。**禁止用其他字段替代。** + +| 字段 | 来源 | 传递给 | +|------|------|--------| +| `taskUuid` | `minutes list` | `minutes get summary/info/batch --id(s)` | +| `userId` | `aisearch person` / `contact user search` / `contact dept list-members` | `contact user get --ids`、`todo --executors`、`calendar --users` | +| `deptId` | `contact dept search` | `contact dept list-members --depts`;多子部门时对每个子部门分别 `dept search` 取 id | +| `nodeId` | `drive search` / `wiki node search` | `doc read/update --node`、`drive copy/move/rename/delete --node` | +| `nodeId` | `wiki node list` 中的 folder 类型节点 / `wiki node create --type folder` | `wiki node list --folder`、`wiki node create --folder`、`drive upload --folder`、`drive copy/move --folder` | +| `eventId` | `calendar event list` | `calendar event get/update --id` | +| `processInstanceId` | `oa approval list-*` | `oa approval detail/approve --instance-id` | +| `openConversationId` | `chat search` | `chat message list/send --group` | +| `todoTaskId` | `todo task list` | `todo task update/done --task-id` | +| `reportId` | `report inbox list` / `report outbox list` | `report entry get/stats --report-id` | +| `baseId` / `tableId` | `aitable base search` | `aitable record query --base-id --table-id` | +| `dentryUuid` | `drive list` / `drive mkdir` | `drive info/download --node`、`drive list/mkdir/upload --folder` | +| `dentryId` | `drive info` 的数字字段 | 仅用于 `chat message send --dentry-id` | + +**ID 边界硬约束**:`dentryId` 通常是纯数字,只表示聊天文件消息需要的钉盘条目数字 ID;它不是父目录 ID。遇到 `drive --node/--folder`、`doc --node/--folder`、`wiki node --folder` 时,只能使用 `dentryUuid` / `nodeId` / 文档 URL。若当前上下文只有数字型 `dentryId`,必须先重新 `drive list` / `drive search` / `wiki node list` 获取正确 ID,不能把该数字直接代入后续命令。 diff --git a/skills/multi/dingtalk-contact/references/contact-index.md b/skills/multi/dingtalk-contact/references/contact-index.md new file mode 100644 index 00000000..b7220362 --- /dev/null +++ b/skills/multi/dingtalk-contact/references/contact-index.md @@ -0,0 +1,6 @@ +# 通讯录能力索引 + +| 主题 | 详见 | +|---|---| +| 用户/部门/组织关系 | [`contact.md`](./contact.md) | +| 通讯录最佳实践 | [`08-directory.md`](./08-directory.md) | diff --git a/skills/multi/dingtalk-contact/references/contact.md b/skills/multi/dingtalk-contact/references/contact.md index 87b923f1..77286340 100644 --- a/skills/multi/dingtalk-contact/references/contact.md +++ b/skills/multi/dingtalk-contact/references/contact.md @@ -36,16 +36,15 @@ Example: dws contact user search --query "张三" Flags: --query string 搜索关键词 (必填) -Returns: (顶层 `result` 为列表,每项包含以下字段) +Returns: (列表,每项包含以下字段) name string 成员姓名 nick string 成员昵称 - flowerName string 花名(无花名时为 null) userId string 成员 ID(仅同事关系时返回) title string 员工职位(仅同事关系时返回) openDingTalkId string 当前用户视角下的目标用户唯一标识,不可跨用户共享;可用于发消息等好友关系场景的操作 ``` -> **CAUTION:** 多人同名时禁止默认选第一个 — `user search` 不返回部门信息,须追加 `contact user get --ids userId1,userId2,...` 获取部门/职位后请用户确认。详见 [08-directory.md](./08-directory.md)「多命中」。 +> **CAUTION:** 多人同名时禁止默认选第一个 — `user search` 不返回部门信息,须追加 `contact user get --ids userId1,userId2,...` 获取部门/职位后请用户确认。详见 [08-directory.md](08-directory.md)「多命中」。 #### 按手机号搜索用户 ``` @@ -83,8 +82,6 @@ Flags: 查询花名册有权限的字段列表,根据当前用户查询花名册有权限的字段列表。认证信息(corpId、optUserId)由系统自动注入,无需手动传入。 -> **前置条件:需操作人具备花名册管理权限。** 无权限时返回业务错误 `操作人无花名册管理权限`(非命令写法问题,不要改参数)。 - #### 查询员工花名册字段信息(个人档案) ``` Usage: @@ -101,8 +98,6 @@ Flags: 花名册字段包含:试用/转正信息、个人/家庭信息、学历信息、银行卡/合同信息、紧急联系人和其他企业自定义信息。 > **与 `contact user get` 的区别**:`user get` 返回组织管理信息(部门、主管、管理员权限),`user profile get` 返回个人档案信息(学历、家庭、银行卡等)。 -> -> **前置条件:需操作人具备花名册管理权限。** 无权限时返回业务错误 `操作人无花名册管理权限`(非命令写法问题,不要改参数)。 ### dismission (离职员工) @@ -129,8 +124,6 @@ Flags: 查询离职员工列表,支持按员工姓名、离职日期范围、部门进行过滤。认证信息(corpId、optUserId)由系统自动注入,无需手动传入。 `--start` 和 `--end` 必须同时设置或同时不设置,不允许只传其中一个。 -> **前置条件:需操作人具备已离职人员花名册管理权限。** 无权限时返回业务错误 `操作人无已离职人员花名册管理权限`(非命令写法问题,不要改参数)。 - ### dept (部门查询) #### 搜索部门 @@ -141,9 +134,6 @@ Example: dws contact dept search --query "技术部" Flags: --query string 搜索关键词 (必填) -Returns: (顶层 key 为 `deptList`,非 `result`,为列表,每项包含以下字段) - deptId int 部门 ID - deptName string 部门名称(命中关键词会用 … 高亮包裹,需自行去标签) ``` #### 获取部门详情 @@ -188,14 +178,11 @@ Example: dws contact dept list-members --depts 1 # 根部门 Flags: --depts string 部门 ID 列表,逗号分隔 (必填) -Returns: (顶层 key 为 `deptUserList`,非 `result`,为列表,每项形如 { "userInfo": { "name": ..., "userId": ... } }) - userInfo.name string 成员姓名 - userInfo.userId string 成员 ID Notes: - **钉钉根部门 `deptId=1`**;查根部门直属成员用 `--depts 1` - 仅返回**本部门**直接成员,**不含下级部门**成员;需含下级请先 `dept list-children` 枚举子部门,再对子 deptId 分别/合并调用 `list-members` - 受组织架构可见性控制;`--depts` 支持逗号分隔批量查询多个部门 - - 跨层级成员展开见 [08-directory.md](./08-directory.md) 的 `cross-level-dept-members` recipe + - 跨层级成员展开见 [08-directory.md](08-directory.md) 的 `cross-level-dept-members` recipe ``` ### label (角色查询) @@ -211,7 +198,7 @@ Example: Flags: 无 Notes: - - 无需参数,返回当前企业全部角色,按 `groupName` 分组,每组 `labels[]` 里每个角色是 `{labelId, name}`(字段名是 `name`,不是 `labelName`) + - 无需参数,返回当前企业全部角色列表(labelId、labelName等) - 用于不知道准确角色名称时先浏览全部角色 - 典型场景:用户说“企业所有主管/查所有管理员/财务人员有哪些”→ 先 label list 浏览全部角色,匹配目标角色后 label list-members 获取成员 ``` @@ -289,7 +276,7 @@ Notes: > [!IMPORTANT] > **易混淆硬规则**:`relation list-my-followings` **只**返回"我特别关注的人员列表"(一组 openDingTalkId),**不**返回任何消息内容。 > -> **禁止路由到本命令的场景**(query 中同时包含『关注/特别关注/星标』和以下任一消息域动词/名词时,必须路由到 [`chat message list-focused`](../../dingtalk-chat/references/chat.md)): +> **禁止路由到本命令的场景**(query 中同时包含『关注/特别关注/星标』和以下任一消息域动词/名词时,必须路由到 [`chat message list-focused`](../../dingtalk-chat/references/chat-commands.md)): > - 动词类:**发**了什么、**说**了什么/啥、**聊**了什么、**讲**了什么 > - 名词类:**消息**、**聊天**、**动态**、**最新内容** > @@ -300,7 +287,7 @@ Notes: > - "我特别关注的人**最近发了什么消息**" → `chat message list-focused`(含"发""消息") > - "我关注的人**最近都说了啥**" → `chat message list-focused`(含"说") -组合场景(多子部门、跨层级成员、强消歧)见 [08-directory.md](./08-directory.md)。 +组合场景(多子部门、跨层级成员、强消歧)见 [08-directory.md](08-directory.md)。 ## 核心工作流 @@ -358,7 +345,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` / `name` | `label get --names` 或 `label list-members --id` | +| `label list` | `labelId` / `labelName` | `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/references/intent-guide.md b/skills/multi/dingtalk-contact/references/intent-guide.md new file mode 100644 index 00000000..17836d4b --- /dev/null +++ b/skills/multi/dingtalk-contact/references/intent-guide.md @@ -0,0 +1,13 @@ +# contact 局部意图消歧 + +本文件从单 Skill `intent-guide.md` 拆分而来,仅保留与本产品相关的跨产品消歧规则。 + +| 用户说... | 真实意图 | 应该用 | 不要用 | 理由 | +|---|---|---|---|---| +| "张三在哪个部门/查一下同事工号" | 通讯录精确查询 | #8 `contact` | #5 汇报 / #4 文档 | 需要 userId、手机号、部门 ID 等精确信息时用 contact | +| "研发部的详细信息/部门信息" | 查部门详情 | `contact dept get-info` | `contact dept list-members` | 查部门属性(ID、名称、人数)用 get-info;查成员列表用 list-members | +| "研发部有多少人" | 查部门人数 | `contact dept get-info` | `contact dept list-members` | 问人数用 get-info(返回 memberCount);问有哪些人用 list-members | +| "找一下张三/搜同事/找人" | AI搜人(首选) | `aisearch person` | `contact user search` | 搜人首选 aisearch,支持姓名/部门/职责/上下级维度;精确查 userId/手机号用 contact | +| "五道的上级是谁/谁负责XX/XX的下属有谁" | AI语义搜人 | `aisearch person` | `contact` | 涉及上下级、职责、负责人等语义维度搜索,用 aisearch | +| "222020这个工号是谁/查工号" | 按工号搜人 | `aisearch person --dimension jobNumber` | `contact` | 工号查人走 aisearch,dimension=jobNumber | +| "13800138000是谁/查手机号" | 按手机号搜人 | `aisearch person --dimension phone` | `contact` | 手机号查人走 aisearch,dimension=phone | diff --git a/skills/multi/dingtalk-contact/references/lite-recipes.md b/skills/multi/dingtalk-contact/references/lite-recipes.md new file mode 100644 index 00000000..032ed75d --- /dev/null +++ b/skills/multi/dingtalk-contact/references/lite-recipes.md @@ -0,0 +1,30 @@ +# contact Lite Recipe + +本文件从单 Skill `lite-recipes.md` 拆分而来,仅保留与本产品相关的轻量流程。 + +## #8 通讯录 + +### get-contact-self + +`contact user get-self` → 当前用户 userId、部门、主管等 + +### search-person + +**搜人首选入口**。凡是“找人/搜人/找同事/谁负责/上级/下级/负责人/团队成员”均优先用 `aisearch person`: + +1. 从用户问题中提取 keyword(人名/业务关键词)和 dimension(维度),规则见 [aisearch.md](../../dingtalk-aisearch/references/aisearch.md)。 +2. `aisearch person --keyword "<关键词>" --dimension <维度>` +3. 结果中提取 `userId` 和 `title`(姓名)展示给用户。 +4. 若需要 userId 做后续操作(发消息/建待办),可直接使用结果中的 `userId`。 +5. **重名消歧**:多人同名时禁止默认选第一个,须追加 `contact user get --ids` 获取部门/职位后请用户确认,详见 [08-directory.md](../../dingtalk-contact/references/08-directory.md)「多命中」。 + +### search-user + +仅在以下**精确查询**场景使用,搜人请优先用 `search-person`: + +- 需要获取 userId 给其他产品使用(发消息/建待办/约日程) +- 已有 userId 需查完整详情(`contact user get --ids`) + +1. `aisearch person --keyword "<姓名>" --dimension name` → `userId`;**多命中须列出候选请用户确认**。 +2. **重名消歧**:多人同名时禁止默认选第一个,须追加 `contact user get --ids` 获取部门/职位后请用户确认,详见 [08-directory.md](../../dingtalk-contact/references/08-directory.md)「多命中」。 +3. 需详情时:`contact user get --ids `(多人可 `--ids id1,id2,...`) diff --git a/skills/multi/dingtalk-contact/scripts/contact_dept_members.py b/skills/multi/dingtalk-contact/scripts/contact_dept_members.py index 6280d849..519afacf 100644 --- a/skills/multi/dingtalk-contact/scripts/contact_dept_members.py +++ b/skills/multi/dingtalk-contact/scripts/contact_dept_members.py @@ -8,20 +8,12 @@ """ import sys -import re import json import subprocess import argparse from typing import List, Any, Optional -def strip_highlight(text: str) -> str: - """去除 dept search 返回名称中的 … 高亮标签。""" - if not isinstance(text, str): - return text - return re.sub(r'', '', text) - - def run_dws( args: List[str], dry_run: bool = False, ) -> Optional[Any]: @@ -75,11 +67,7 @@ def main(): elif isinstance(dept_data, dict): inner = dept_data.get('result', dept_data) if isinstance(inner, dict): - # dept search 返回顶层 deptList;兼容历史 items/depts 键。 - depts = (inner.get('deptList') - or inner.get('items') - or inner.get('depts') - or []) + depts = inner.get('items', inner.get('depts', [])) elif isinstance(inner, list): depts = inner else: @@ -92,9 +80,7 @@ def main(): for dept in depts: dept_id = dept.get('id') or dept.get('deptId') - dept_name = strip_highlight( - dept.get('name') or dept.get('deptName', '未知') - ) + dept_name = dept.get('name') or dept.get('deptName', '未知') if not dept_id: continue @@ -114,11 +100,8 @@ def main(): elif isinstance(members_data, dict): inner = members_data.get('result', members_data) if isinstance(inner, dict): - # list-members 返回 deptUserList;兼容历史 userlist/list 键。 - members = (inner.get('deptUserList') - or inner.get('userlist') - or inner.get('list') - or []) + members = inner.get('userlist', + inner.get('list', [])) elif isinstance(inner, list): members = inner else: @@ -130,12 +113,9 @@ def main(): continue for m in members: - # list-members 每项形如 {"userInfo": {"name":..., "userId":...}}, - # 成员字段嵌在 userInfo 下;兼容历史扁平结构。 - info = m.get('userInfo', m) - name = info.get('name') or info.get('userName', '未知') - title = info.get('title') or info.get('position', '') - uid = info.get('userId') or info.get('userid', '') + name = m.get('name') or m.get('userName', '未知') + title = m.get('title') or m.get('position', '') + uid = m.get('userId') or m.get('userid', '') line = f" 👤 {name}" if title: line += f" ({title})" diff --git a/skills/multi/dingtalk-dev/references/dev-index.md b/skills/multi/dingtalk-dev/references/dev-index.md new file mode 100644 index 00000000..af24e42c --- /dev/null +++ b/skills/multi/dingtalk-dev/references/dev-index.md @@ -0,0 +1,12 @@ +# 开发者能力索引 + +| 主题 | 详见 | +|---|---| +| 应用管理 | [`app.md`](./app.md) | +| 凭证/鉴权 | [`credentials.md`](./credentials.md) | +| 权限管理 | [`permission.md`](./permission.md) | +| 机器人 | [`robot.md`](./robot.md) | +| 长连接 | [`connect.md`](./connect.md) | +| 事件订阅 | [`event.md`](./event.md) | +| 成员管理 | [`member.md`](./member.md) | +| 最佳实践 | [`recipes.md`](./recipes.md) | diff --git a/skills/multi/dingtalk-dev/references/event.md b/skills/multi/dingtalk-dev/references/event.md index 83b459ae..a7313bd3 100644 --- a/skills/multi/dingtalk-dev/references/event.md +++ b/skills/multi/dingtalk-dev/references/event.md @@ -14,6 +14,7 @@ - 批量或全量订阅前,先把候选 `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`。 +- `subscribe/unsubscribe` 返回 `needsPublish=true` 或非空 `versionRequiredAction` 时,按 [version.md](version.md) 继续 `version create → check-approval → publish → status`;进入 `RELEASE` 后重新 `event list`,确认目标 `events[].subscribed` 与本次操作一致。进入审核态则报告待审批;需要选择审批人时停下让用户选择。 - 如果订阅失败、返回提示长链接未在线(是泛化错误:`reason=business_error`、`message` 含「长链接未在线」、`server_error_code=-1`;没有 STREAM_NOT_CONNECTED 这类结构化错误码,也没有 action 字段),先执行 `dev connect` 建联,再重试订阅。 ## Stream 长连:connect 与事件订阅的关系 diff --git a/skills/multi/dingtalk-devapp/references/event.md b/skills/multi/dingtalk-devapp/references/event.md deleted file mode 100644 index 1ffd0267..00000000 --- a/skills/multi/dingtalk-devapp/references/event.md +++ /dev/null @@ -1,94 +0,0 @@ -# 事件订阅 - -管理开放平台企业内部应用的事件订阅:列出可订阅事件、按事件码列表批量订阅/退订。 - -> 所有命令通过 `--unified-app-id` 定位应用;`corpId` / `userId` 由 MCP 系统上下文注入,CLI 不传。订阅/退订是写操作,先 `--dry-run`,确认后再 `--yes`。 - -## 可订阅事件列表 - -**搜索优先**:定位事件码时优先用 `--keyword` 搜索;只有用户明确要「全部事件」时才不带 `--keyword` 翻全量。 - -```bash -# 首选:按关键词搜索定位事件码 -dws dev app event list --unified-app-id --keyword 通讯录 --page-size 20 --format json -# 兜底:用户明确要全部事件时才翻全量 -dws dev app event list --unified-app-id --page-size 20 --format json -``` - -MCP tool: `list_dev_app_events`。`--keyword` 按事件码或事件名称模糊匹配。返回 `pushType=STREAM`、`events[]`、`hasMore`、`nextCursor`、`pageSize`;`hasMore=true` 时下一页继续传 `--cursor `,逐页处理。`events[]` 每项含: - -| 字段 | 含义 | -|------|------| -| `eventCode` | 事件码,订阅/退订时传入,如 `user_add_org` | -| `eventName` | 事件名称,如「通讯录用户增加」 | -| `subscribed` | 当前是否已订阅 | - -常见事件码示例:`user_add_org`(通讯录用户增加) / `user_modify_org`(用户更改) / `user_leave_org`(用户离职) / `org_dept_create`(部门创建) / `org_dept_modify`(部门修改) / `org_dept_remove`(部门删除)。以 `event list` 实际返回为准。 - -## 订阅事件 - -```bash -dws dev app event subscribe --unified-app-id --event-codes user_add_org,org_dept_modify --dry-run --format json -dws dev app event subscribe --unified-app-id --event-codes user_add_org,org_dept_modify --yes --format json -``` - -MCP tool: `subscribe_dev_app_events`(入参 `unifiedAppId` + 必填数组 `eventCodes`,一次订阅多个事件码)。成功返回 `success/operation=SUBSCRIBE/unifiedAppId/eventCodes/needsPublish/versionRequiredAction`;失败时补 `errorCode/errorMsg/reason/retryable/action`。 - -## 退订事件 - -```bash -dws dev app event unsubscribe --unified-app-id --event-codes user_add_org,org_dept_modify --dry-run --format json -dws dev app event unsubscribe --unified-app-id --event-codes user_add_org,org_dept_modify --yes --format json -``` - -MCP tool: `unsubscribe_dev_app_events`(入参 `unifiedAppId` + 必填数组 `eventCodes`,一次退订多个事件码)。成功返回 `success/operation=UNSUBSCRIBE/unifiedAppId/eventCodes/needsPublish/versionRequiredAction`;失败时补 `errorCode/errorMsg/reason/retryable/action`。 - -## 字段映射 - -| CLI | MCP | 必填 | 说明 | -|-----|-----|------|------| -| `--unified-app-id` | `unifiedAppId` | 是 | 统一应用 ID | -| `--keyword` | `keyword` | list 可选 | 事件搜索关键词,匹配事件码或事件名称 | -| `--cursor` | `cursor` | list 可选 | 服务端返回的下一页游标 | -| `--page-size` | `pageSize` | list 可选 | 单页数量 | -| `--event-codes` | `eventCodes` | subscribe/unsubscribe 必填 | 事件码列表,逗号分隔,取自 `event list` | - -## 灰度应用需发布版本生效(重要) - -对**灰度统一应用**,`subscribe`/`unsubscribe` 只会把变更**暂存到版本元数据**,不会立即生效,需后续走版本发布链路: - -```text -event subscribe/unsubscribe 订阅变更写入版本元数据 - → version create 创建新版本 - → version check-approval 预检审批 - → version publish 发布后订阅变更才生效 - → version status 轮询发布/审批状态 -``` - -详见版本发布文档。 - -## 单步流程 - -```text -1. 定位事件码(搜索优先) - dws dev app event list --unified-app-id --keyword <关键词> --format json - → 从返回里挑 eventCode - → 用户明确要全部事件时,才不带 --keyword 翻全量(逐页) - -2. 订阅(批量/全量前先列候选 eventCode 给用户确认) - dws dev app event subscribe --unified-app-id --event-codes , --dry-run --format json - → 确认后加 --yes - -3. 验证 - dws dev app event list --unified-app-id --keyword <关键词> --format json - → 确认对应事件 subscribed=true(灰度应用需先发布版本) -``` - -## 错误处理 - -| 情况 | 处理 | -|------|------| -| `--event-codes` 缺失 | CLI 直接报错,先 `event list` 取事件码 | -| 订阅后 `subscribed` 仍为 false | 灰度应用需先 `version publish` 发布版本 | -| 返回提示「长链接未在线」(泛化错误 `reason=business_error`、`server_error_code=-1`,无 STREAM_NOT_CONNECTED 码) | 先执行 `dev connect` 建联,再重试订阅 | -| `ServiceResult.success=false` | 透传 `errorCode/errorMsg` | diff --git a/skills/multi/dingtalk-devdoc/SKILL.md b/skills/multi/dingtalk-devdoc/SKILL.md index a3e50ad7..47156997 100644 --- a/skills/multi/dingtalk-devdoc/SKILL.md +++ b/skills/multi/dingtalk-devdoc/SKILL.md @@ -14,13 +14,12 @@ metadata: > 🧪 **EXPERIMENTAL · 试验版 / Preview** — multi 模式当前未达 stable 标准。全部 dingtalk-* skill 已通过 dispatch verifier,但接口、命名、跨 skill 引用后续可能调整;生产 / 共享环境请优先使用 mono 模式(`dws skill setup --mode mono`)。问题请提 issue 反馈。 -> **PREREQUISITE:** Read the `dws-shared` skill first for auth, global flags, product routing, URL preflight, error codes, and safety rules. The `dws` binary must be on PATH. +## 前置条件 — 执行操作前必读 + +> **`use_skill(dws-shared)`** — 认证、全局参数(`--format json` / `--yes`)、错误码、URL 模板、跨产品消歧、安全规则与 capability 边界。**执行任何 `dws` 命令前先读;** 单产品的清晰命令可直接用本 skill。 -> ⚠️ **命令可用性以当前 dws 二进制为准**。服务发现已下线,本文档随内置 skill 发布;如果 `dws --help` 不存在,说明当前版本未暴露该命令。若命令存在但调用失败,请按错误中的 endpoint 或 tool 提示确认静态端点目录和后端工具注册。实际调用前可用 `dws --help` 或 `--dry-run` 验证。 - - > 命令参考:[devdoc.md](references/devdoc.md)。 ## 意图表 @@ -34,3 +33,6 @@ metadata: ## 跨产品协作 - 钉钉云文档(个人 / 企业内文档)→ 切到 `dingtalk-doc` +## 局部意图与 Recipe + +- [局部意图消歧](references/intent-guide.md)。 diff --git a/skills/multi/dingtalk-devdoc/references/devdoc.md b/skills/multi/dingtalk-devdoc/references/devdoc.md index 1569a49b..a542b62c 100644 --- a/skills/multi/dingtalk-devdoc/references/devdoc.md +++ b/skills/multi/dingtalk-devdoc/references/devdoc.md @@ -1,49 +1,23 @@ -# 开放平台文档 (devdoc) 命令参考 +# devdoc — 开放平台文档 -搜索钉钉**开放平台**开发文档,用于回答开发者关于 OpenAPI、字段、错误码、接入指南、配额等技术问题。 - -## 命令总览 - -### 搜索开发文档 +## 搜索开放平台文档 ``` Usage: - dws devdoc article search [keyword] [flags] + dws devdoc article search [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 + dws devdoc article search --query "OAuth2 接入" --page 1 --size 10 --format json Flags: - --query string 搜索关键词 (必填;也可用位置参数 [keyword] 传) - --page string 分页页码 (从 1 开始,default "1") - --size string 分页大小 (default "10") + --query string 搜索关键词 (必填) + --page string 页码 (默认 1) + --size string 每页数量 (默认 10) ``` ## 意图判断 -用户问开放平台 API / 字段 / 错误码 / SDK / 鉴权 / 回调 / 配额相关的技术细节: -- 走 `devdoc article search`,把用户问的关键短语作为 `--query` +- 用户说"开发文档 / API 文档 / 接口文档 / 调用报错" → `devdoc article search` -关键区分: -- devdoc(钉钉**开放平台**开发者文档,面向研发) vs doc(钉钉在线文档,面向普通用户内容) -- devdoc 只做搜索,不做读取;命中条目返回标题、摘要、文档链接,由 Agent 引用链接或进一步浏览 +## 上下文传递 -## 核心工作流 - -```bash -# 开发者问"OAuth2 怎么接" -dws devdoc article search --query "OAuth2 接入" --format json - -# 命中结果多时翻页 -dws devdoc article search --query "消息卡片" --page 2 --size 5 --format json - -# 查错误码 / 字段含义 -dws devdoc article search --query "errcode 40078" --format json -``` - -## 注意事项 - -- `--query` 必填;建议传用户原话里的关键名词(API 名、错误码、能力名),不要过度改写 -- 返回按相关性排序,默认 `--size 10`;要拿更多结果时先翻页,再考虑换关键词 -- 命中结果里的链接是钉钉开放平台公开文档,可直接给用户做参考 -- 不要把 devdoc 用来查业务数据(那是 aitable / doc / report 的事);devdoc 只查**官方开发者文档** +| 操作 | 从返回中提取 | 用于 | +|------|-------------|------| +| `devdoc article search` | 文档链接 | 直接展示给用户 | diff --git a/skills/multi/dingtalk-devdoc/references/intent-guide.md b/skills/multi/dingtalk-devdoc/references/intent-guide.md new file mode 100644 index 00000000..36b1d57b --- /dev/null +++ b/skills/multi/dingtalk-devdoc/references/intent-guide.md @@ -0,0 +1,7 @@ +# devdoc 局部意图消歧 + +本文件从单 Skill `intent-guide.md` 拆分而来,仅保留与本产品相关的跨产品消歧规则。 + +| 用户说... | 真实意图 | 应该用 | 不要用 | 理由 | +|---|---|---|---|---| +| "搜一下 OAuth2 接入文档" | 搜索开发文档 | `devdoc` | `doc search` | 搜索开放平台技术文档,不是钉钉内部内容 | diff --git a/skills/multi/dingtalk-ding/SKILL.md b/skills/multi/dingtalk-ding/SKILL.md index 912d33e6..e19ace39 100644 --- a/skills/multi/dingtalk-ding/SKILL.md +++ b/skills/multi/dingtalk-ding/SKILL.md @@ -1,6 +1,6 @@ --- name: dingtalk-ding -description: DING 紧急消息(应用内 / 短信 / 电话)。Use when 用户说 DING一下/紧急通知/电话DING/短信DING/必达消息/电话叫人。Distinct from dingtalk-chat(普通群聊消息)、dingtalk-outbound-call(企业外呼)。命令前缀:dws ding。 +description: DING 紧急消息(应用内 / 短信 / 电话)。Use when 用户说 DING一下/紧急通知/电话DING/短信DING/必达消息/电话叫人/以我的名义发DING/个人DING/撤回DING。不做普通群聊消息(走 dingtalk-chat)、企业外呼(走 dingtalk-outbound-call)。命令前缀:dws ding。 cli_version: ">=0.2.14" metadata: category: product @@ -14,13 +14,12 @@ metadata: > 🧪 **EXPERIMENTAL · 试验版 / Preview** — multi 模式当前未达 stable 标准。全部 dingtalk-* skill 已通过 dispatch verifier,但接口、命名、跨 skill 引用后续可能调整;生产 / 共享环境请优先使用 mono 模式(`dws skill setup --mode mono`)。问题请提 issue 反馈。 -> **PREREQUISITE:** Read the `dws-shared` skill first for auth, global flags, product routing, URL preflight, error codes, and safety rules. The `dws` binary must be on PATH. +## 前置条件 — 执行操作前必读 + +> **`use_skill(dws-shared)`** — 认证、全局参数(`--format json` / `--yes`)、错误码、URL 模板、跨产品消歧、安全规则与 capability 边界。**执行任何 `dws` 命令前先读;** 单产品的清晰命令可直接用本 skill。 -> ⚠️ **命令可用性以当前 dws 二进制为准**。服务发现已下线,本文档随内置 skill 发布;如果 `dws --help` 不存在,说明当前版本未暴露该命令。若命令存在但调用失败,请按错误中的 endpoint 或 tool 提示确认静态端点目录和后端工具注册。实际调用前可用 `dws --help` 或 `--dry-run` 验证。 - - > 命令参考:[ding.md](references/ding.md)。 ## 意图表 @@ -30,9 +29,15 @@ metadata: | "DING 张三" / "应用内紧急通知" | `dws ding message send --type app --users --content "<内容>"` | | "短信 DING" | `dws ding message send --type sms --users --content "<内容>"` | | "电话 DING" / "电话叫人" | `dws ding message send --type call --users --content "<内容>"` | -| "撤回 DING" | `dws ding message recall --id ` | +| "撤回 DING" | `dws ding message recall --robot-code --id ` | +| "以我的名义发 DING / 个人 DING" | `dws ding message send-personal --users --content "<内容>"` | +| "以我的名义撤回 DING" | `dws ding message recall-personal --id ` | +| "DING 历史 / 接收状态" | `dws ding message list` / `dws ding message receiver-status` | ## 跨产品协作 - 接收人是人名 → 先用 `dingtalk-aisearch` 拿 `userId` - 普通通知(不需必达)→ 切到 `dingtalk-chat` +## 局部意图与 Recipe + +- [局部意图消歧](references/intent-guide.md);[Lite Recipe](references/lite-recipes.md)。 diff --git a/skills/multi/dingtalk-ding/references/ding.md b/skills/multi/dingtalk-ding/references/ding.md index 6b45c656..64c297b2 100644 --- a/skills/multi/dingtalk-ding/references/ding.md +++ b/skills/multi/dingtalk-ding/references/ding.md @@ -34,12 +34,12 @@ Flags: Usage: dws ding message list [flags] Example: - dws ding message list --type ALL + dws ding message list dws ding message list --type UNREAD dws ding message list --type SEND --cursor 10 Flags: --cursor int 分页游标 (首次传 0, 翻页传返回的 nextCursor) - --type string 消息类型: ALL / UNREAD / SEND / NEW_COMMENT / DELETED (默认 ALL; 服务端不接受空值, CLI 不传时会自动按 ALL 查询, 故裸跑 `ding message list` 即可) + --type string 消息类型: ALL / UNREAD / SEND / NEW_COMMENT / DELETED (可选, 不传返回全部) ``` ### 查看 DING 接收状态 @@ -48,40 +48,119 @@ Usage: dws ding message receiver-status [flags] Example: dws ding message receiver-status --ding-id - # 查询 dingId: dws ding message list --type ALL + # 查询 dingId: dws ding message list Flags: --ding-id string DING 消息 openDingId (必填) ``` +### 以用户身份发送 DING — 以当前用户身份(非机器人)发送 DING 消息 +``` +Usage: + dws ding message send-personal [flags] +Example: + dws ding message send-personal --users openDingTalkId1,openDingTalkId2 --content "请查看" + dws ding message send-personal --type call --users openDingTalkId1 --content "紧急告警" + # 查询 openDingTalkId: dws contact user search --query "姓名" +Flags: + --users string 接收者 openDingTalkId 列表,逗号分隔 (必填) + --content string DING 内容 (必填) + --type string 提醒类型: app/sms/call (默认 app) + --uuid string 幂等唯一标识(可选,不传由服务端生成) + +注意: + - 与 `ding message send`(机器人身份)不同:send-personal 以当前用户身份发送,无需 --robot-code + - 接收者使用 openDingTalkId(非 userId),可通过 `dws contact user search --query "姓名"` 获取 + - sms/call 类型有通信费用,使用前需和用户确认 +``` + +### 以用户身份撤回 DING — 以当前用户身份撤回已发送的 DING 消息 +``` +Usage: + dws ding message recall-personal [flags] +Example: + dws ding message recall-personal --id + # 查询 openDingId: dws ding message list +Flags: + --id string DING 消息 openDingId (必填) + +注意: + - 与 `ding message recall`(机器人身份)不同:recall-personal 以当前用户身份撤回,无需 --robot-code + - openDingId 可通过 `dws ding message list` 或 `send-personal` 返回值获取 +``` + +### 消息转 DING — 将聊天消息转为 DING 通知发送给指定接收者 +``` +Usage: + dws ding message send-by-message [flags] +Example: + dws ding message send-by-message --group --message-id --users id1,id2 + dws ding message send-by-message --group --message-id --users id1 --type sms + # 查询 openDingTalkId: dws contact user search --query "姓名" + # 查询 openConversationId: dws chat search --keyword "群名" +Flags: + --group string 原消息所在会话 openConversationId (必填) + --message-id string 原消息 openMessageId (必填) + --users string 接收者 openDingTalkId 列表,逗号分隔 (必填) + --type string 提醒类型: app/sms/call (默认 app) + --uuid string 幂等唯一标识(可选,不传由服务端生成) + +注意: + - 与 `send-personal` 不同: send-by-message 是将已有聊天消息转发为 DING,需要指定原消息的会话和消息 ID + - 接收者使用 openDingTalkId,可通过 `dws contact user search --query "姓名"` 获取 + - sms/call 类型有通信费用,使用前需和用户确认 +``` + ## 意图判断 用户说"DING 一下/紧急通知/电话提醒" → `message send` +用户说"以我的名义 DING/个人发 DING/用户身份 DING" → `message send-personal` +用户说"消息转 DING/把这条消息 DING 给某人/转发为 DING" → `message send-by-message` 用户说"撤回 DING" → `message recall` +用户说"以我的名义撤回 DING/个人撤回 DING" → `message recall-personal` 用户说"DING 消息/查 DING/DING 历史/我的 DING" → `message list` 用户说"DING 接收状态/谁收到了 DING/DING 已读" → `message receiver-status` 关键区分: +- `ding message send`(机器人身份,需 --robot-code) vs `ding message send-personal`(用户身份,无需 robot-code) vs `ding message send-by-message`(消息转 DING,需指定原消息) +- `ding message recall`(机器人身份) vs `ding message recall-personal`(用户身份) - ding(紧急提醒, 支持电话/短信) vs bot(常规群/单聊消息) - sms/call 类型有通信费用 ## 核心工作流 ```bash -# 应用内 DING (免费) +# 机器人身份: 应用内 DING (免费) dws ding message send --robot-code --type app --users userId1,userId2 --content "请查看" --format json -# 电话 DING (紧急, 有成本!) +# 机器人身份: 电话 DING (紧急, 有成本!) dws ding message send --robot-code --type call --users userId1 --content "紧急告警" --format json -# 撤回 +# 机器人身份: 撤回 dws ding message recall --robot-code --id --format json + +# 用户身份: 应用内 DING +dws ding message send-personal --users openDingTalkId1,openDingTalkId2 --content "请查看" --format json + +# 用户身份: 电话 DING (紧急, 有成本!) +dws ding message send-personal --type call --users openDingTalkId1 --content "紧急告警" --format json + +# 用户身份: 消息转 DING +dws ding message send-by-message --group --message-id --users openDingTalkId1,openDingTalkId2 --format json + +# 用户身份: 撤回 +dws ding message recall-personal --id --format json ``` ## 上下文传递表 | 操作 | 提取 | 用于 | |------|------|------| | `message send` | `openDingId` | message recall 的 --id | +| `message send-personal` | `openDingId` | message recall-personal 的 --id | | `message list` | `openDingId` | message receiver-status 的 --ding-id | +| `message send-by-message` | `openDingId` | message recall-personal 的 --id | ## 注意事项 -- `--robot-code` 从钉钉开放平台 **应用管理 → 机器人** 中获取,也可设环境变量 `DINGTALK_DING_ROBOT_CODE` +- `--robot-code` 从钉钉开放平台 应用管理 → 机器人 中获取,也可设环境变量 `DINGTALK_DING_ROBOT_CODE` +- `send` / `recall` 是机器人身份,需要 --robot-code;`send-personal` / `recall-personal` / `send-by-message` 是用户身份,无需 robot-code +- `send` 接收者使用 userId;`send-personal` / `send-by-message` 接收者使用 openDingTalkId(可通过 `dws contact user search --query "姓名"` 获取) +- `send-by-message` 是将已有聊天消息转发为 DING,需指定 --group 和 --message-id - sms/call 类型有通信费用,使用前需和用户确认 -- 默认 `--type app`(应用内 DING,免费) +- 默认 `--type app` 为应用内 DING(免费) diff --git a/skills/multi/dingtalk-ding/references/intent-guide.md b/skills/multi/dingtalk-ding/references/intent-guide.md new file mode 100644 index 00000000..23e0da9a --- /dev/null +++ b/skills/multi/dingtalk-ding/references/intent-guide.md @@ -0,0 +1,13 @@ +# ding 局部意图消歧 + +本文件从单 Skill `intent-guide.md` 拆分而来,仅保留与本产品相关的跨产品消歧规则。 + +| 用户说... | 真实意图 | 应该用 | 不要用 | 理由 | +|---|---|---|---|---| +| "DING消息/查DING/DING历史" | 查询 DING 消息列表 | `ding message list` | `chat message list` | ding 是独立顶层命令;ding message list 查 DING 消息;chat message list 查普通聊天消息 | +| "DING接收状态/谁收到了DING" | DING 接收状态 | `ding message receiver-status` | `chat message read-status` | ding 是独立顶层命令;receiver-status 查 DING 接收;chat message read-status 查普通消息已读 | +| "发DING/DING通知" | 发送 DING 消息 | `ding message send` | `chat message send` | DING 是钉钉的强提醒(应用内/短信/电话),独立顶层命令;普通群消息用 chat | +| "撤回DING" | 撤回 DING 消息 | `ding message recall` | `chat message recall` | DING 撤回独立命令;chat recall 是撤回普通聊天消息 | +| "以我的名义发DING/个人发DING/用户身份DING" | 以用户身份发 DING | `ding message send-personal` | `ding message send` | send-personal 以用户身份发送,无需 robot-code;send 以机器人身份发送 | +| "以我的名义撤回DING/个人撤回DING" | 以用户身份撤回 DING | `ding message recall-personal` | `ding message recall` | recall-personal 以用户身份撤回;recall 以机器人身份撤回 | +| "消息转DING/把这条消息DING给某人/转发为DING" | 消息转 DING | `ding message send-by-message` | `ding message send-personal` | send-by-message 是将已有消息转为 DING,需指定原消息;send-personal 是直接发新 DING | diff --git a/skills/multi/dingtalk-ding/references/lite-recipes.md b/skills/multi/dingtalk-ding/references/lite-recipes.md new file mode 100644 index 00000000..2e4b7ef0 --- /dev/null +++ b/skills/multi/dingtalk-ding/references/lite-recipes.md @@ -0,0 +1,7 @@ +# ding Lite Recipe + +本文件从单 Skill `lite-recipes.md` 拆分而来,仅保留与本产品相关的轻量流程。 + +## #1 消息沟通 + +所有消息沟通相关的命令详情、参数说明、意图路由和复合工作流,请查阅 [chat.md](../../dingtalk-chat/references/chat-commands.md)。 diff --git a/skills/multi/dingtalk-doc/SKILL.md b/skills/multi/dingtalk-doc/SKILL.md index babac6f9..5a693762 100644 --- a/skills/multi/dingtalk-doc/SKILL.md +++ b/skills/multi/dingtalk-doc/SKILL.md @@ -1,6 +1,6 @@ --- name: dingtalk-doc -description: 钉钉文档(云文档)。Use when 用户说 写文档/读文档/创建文档/编辑文档/搜文档/文档块/分块编辑/Markdown 写入/上传文件到文档。Distinct from dingtalk-drive(钉盘文件存储)、dingtalk-aitable(数据表格)、dingtalk-wiki(知识库空间)。命令前缀:dws doc。 +description: 钉钉文档内容操作(内容层)。Use when 用户说 写文档/读文档/创建文档内容/编辑文档内容/文档块/分块编辑/Markdown 写入/评论/媒体附件/导出文档/导出 PDF/导出 Markdown/版本历史/保存版本/回滚版本。搜文件/列文件/上传下载/复制移动/重命名/权限管理走 dingtalk-drive;知识库/空间/节点创建列表搜索走 dingtalk-wiki;文档里嵌入的电子表格/多维表格先用本 skill 提取 token 再切到 dingtalk-sheet/dingtalk-aitable。命令前缀:dws doc。 cli_version: ">=0.2.14" metadata: category: product @@ -14,25 +14,21 @@ metadata: > 🧪 **EXPERIMENTAL · 试验版 / Preview** — multi 模式当前未达 stable 标准。全部 dingtalk-* skill 已通过 dispatch verifier,但接口、命名、跨 skill 引用后续可能调整;生产 / 共享环境请优先使用 mono 模式(`dws skill setup --mode mono`)。问题请提 issue 反馈。 -> **PREREQUISITE:** Read the `dws-shared` skill first for auth, global flags, product routing, URL preflight, error codes, and safety rules. The `dws` binary must be on PATH. +## 前置条件 — 执行操作前必读 + +> **`use_skill(dws-shared)`** — 认证、全局参数(`--format json` / `--yes`)、错误码、URL 模板、跨产品消歧、安全规则与 capability 边界。**执行任何 `dws` 命令前先读;** 单产品的清晰命令可直接用本 skill。 -> ⚠️ **命令可用性以当前 dws 二进制为准**。服务发现已下线,本文档随内置 skill 发布;如果 `dws --help` 不存在,说明当前版本未暴露该命令。若命令存在但调用失败,请按错误中的 endpoint 或 tool 提示确认静态端点目录和后端工具注册。实际调用前可用 `dws --help` 或 `--dry-run` 验证。 - - -> 命令参考:[doc.md](references/doc.md);剧本:[04-document.md](references/04-document.md);URL 识别与类型探测:[url-patterns.md](references/url-patterns.md)。 - -## URL 预检(含 alidocs URL 时必读) - -输入含 `alidocs.dingtalk.com` URL 时,该域名下存在多种路径格式:`/i/p/...`(分享短链)、`/i/nodes/...`(节点链接,类型需探测)、`/spreadsheetv2/...`(电子表格直链)、`/document/edit|preview?dentryKey=...`(文档链接)等,每种处理流程不同。**必须先读 [url-patterns.md](references/url-patterns.md) 中的「alidocs URL 分流决策」**,按规则识别 URL 类型后再选择对应命令;其中 `/document/edit|preview?dentryKey=...` 直接路由到 `doc`,将完整 URL 原样传给 `--node`,**不要**提取 `dentryKey` 当裸 nodeId。 +> 命令参考:[doc.md](references/doc.md);剧本:[04-document.md](references/04-document.md)。 +> 高频模块:[阅读/元信息](references/doc/doc-info-index.md) · [创建/写入/块编辑](references/doc/doc-write-index.md) · [媒体/评论/导出](references/doc/doc-media-index.md) · [导入/版本/模板](references/doc/doc-extra-index.md) · [JSONML](references/format/doc-jsonml-index.md)。 ## 参数硬约束 - 创建文档只用 `--name`,不要写 `--title`。 - 目标文件夹只用 `--folder <文档文件夹nodeId或URL>`,不要写 `--parent` / `--parent-node` / `--parent-id`。 - 目标知识库只用 `--workspace `,不要写 `--space-id` / `--spaceId`。 -- 文档内容:`create` / `update` 都只接 `--content` / `--content-file`,不要写 `--markdown`。 +- 文档内容只用 `--content` / `--content-file`,不要写 `--markdown`。 - 复杂内容(换行、表格、代码块、长 Markdown)先写临时 `.md`,再用 `--content-file`,不要把大段 Markdown 塞进命令行。 - 每次 `create` / `update` / `block insert` / `media insert` 后必须 `dws doc read` 或 `dws doc block list` 回读关键内容。 @@ -42,23 +38,84 @@ metadata: |--------|------| | "创建文档(短内容)" | `dws doc create --name "<标题>" --content "<内容>"` | | "创建+写入(长内容自动分块)" | `python scripts/doc_create_and_write.py --name "<标题>" --content "<内容>" [--mode append\|overwrite]` | -| "搜文档"(全局) | `dws drive search --query "<关键词>"`(`doc search` 已弃用;切到 `dingtalk-drive`) | -| "在某知识库里搜文档" | `dws wiki node search --workspace --query "<关键词>"` | +| "搜文档 / 找文档" | `dws drive search --query "<关键词>"` → 取 `nodeId` 后 `dws doc read --node ` | | "读文档内容" | `dws doc read --node ` | | "更新文档内容 / 分块追加" | `dws doc update --node --content "<分块>" --mode append` | | "删除块" | `dws doc block delete`(需用户确认) | | "更新文档评论" | `dws doc comment update --node --comment-key --content "<内容>"` | | "删除文档评论" | `dws doc comment delete --node --comment-key --yes`(需用户确认) | +| "导出 docx / markdown / pdf" | `dws doc export --node --export-format --output ` | +| "导入本地文件为在线文档" | `dws doc import --file --folder --name "<标题>" --format json`(详见 `references/doc/doc-import.md`) | +| "查模板 / 套用模板创建文档" | `dws doc template list|search|apply`(详见 `references/doc/doc-template.md`) | +| "保存版本 / 查看版本历史 / 回滚版本" | `dws doc version save/list/revert` | -## 评测/多步文档短路径 +## 标准 SOP(必遵流程) + +> 命中以下意图**必须**按对应 SOP 顺序执行;**禁止**跳步、替换命令、编造 nodeId/blockId。每条命令必须带 `--format json`,执行后必须按"验证"步回读真实字段。文件类操作(上传/下载/复制/移动)切 `dingtalk-drive`;知识库节点管理切 `dingtalk-wiki`。 + +### SOP-1 查找并读取文档(query-doc) + +**触发**:查文档/读文档/某文档在哪/搜文档内容。 + +1. **定位(必须)**:`dws drive search --query "<关键词>" --format json` → 取 `nodeId`(拿不准是文档还是文件时,先 `dws doc info --node --format json` 判类型)。 +2. **读取(必须)**:`dws doc read --node --format json`;大文档**必须**只抽取用户需要的章节,**禁止**无差别全文展开。 + +**禁止**:跳过 `drive search` 直接猜 nodeId、把整篇文档原样贴给用户。 + +### SOP-2 创建文档并写入(create-doc) + +**触发**:新建文档/写一篇/建文字文档。 + +1. **执行(必须)**:`dws doc create --name "<标题>" --folder --content-file --format json`(长/多行内容用 `--content-file`,不要用 `--content` 拼长串)。 +2. **验证(必须)**:从返回取 `nodeId`,立即 `dws doc read --node --format json` 回读正文;仅需确认类型、名称或补取链接时再用 `doc info`。 + +**完成条件**:创建结果返回真实 `nodeId`,且正文回读成功;`--folder` 只能使用文档文件夹 nodeId,不能传空间 ID。 + +### SOP-3 覆盖/追加内容(write-content) + +**触发**:覆盖写/追加内容/改文档正文。 + +1. **执行(必须)**:覆盖 `dws doc update --node --mode overwrite --content-file --yes --format json`;追加 `--mode append`。破坏性覆盖**必须**带 `--yes`,且建议先 `--dry-run` 预览。 +2. **验证(必须)**:写后 `dws doc read --node --format json` 抽取受影响段落核对。 + +**禁止**:不加 `--yes` 反复重试覆盖、跳过 `--dry-run` 直接覆盖未确认的长文档。 + +### SOP-4 导出 / 下载(export-doc) + +**触发**:导出文档/下载文档/转 PDF·Markdown。 + +1. **判类型(必须)**:先 `dws doc info --node --format json`;在线文档 → `dws doc export --node --format --format json`;普通文件 → 切 `dingtalk-drive` 用 `dws drive download --node --output `。 + +**禁止**:不分类型一律走 `doc export`(普通文件会失败)、跳过 `doc info` 判断。 + +### SOP-5 块级编辑(block-edit) + +**触发**:插引用块/代码块/表格/分栏/图片/附件,或删除某块。 + +1. **固定顺序(必须)**:`dws doc block list --node --format json` → 选真实 `blockId` → `dws doc block insert|update|delete --node --block-id ...` → 再次 `doc block list` 验证。 +2. **删除(必须)**:删除块**必须**已有用户明确删除意图或二次确认。 +3. **复杂块(必须)**:插入引用/代码/表格/分栏/附件/图片前,先按 [写入索引](references/doc/doc-write-index.md) 与 [JSONML 索引](references/format/doc-jsonml-index.md) 确认对应命令和元素结构;写后按第 1 步回读验证。 + +**禁止**:编造 blockId、未确认就删除。命令帮助只用于确认参数,操作结果必须来自实际命令与回读。 +### SOP-6 导入本地文件为在线文档(import-file) + +**触发**:导入 Word / Excel / Markdown / 本地文件为在线文档。 + +1. **判类型(必须)**:确认用户意图是“导入为在线文档”,不是“上传到钉盘”。仅上传存储时切 `dingtalk-drive`。 +2. **执行(必须)**:`dws doc import --file --folder --name "<标题>" --format json`;复杂参数和限制见 [doc-import.md](references/doc/doc-import.md)。 +3. **验证(必须)**:拿到返回 `nodeId` 后执行 `dws doc info --node --format json`,必要时 `dws doc read --node --format json` 抽样核对内容。 + +**禁止**:把上传文件到钉盘误当成 doc import;不知道目标文件夹 nodeId 时先切 `dingtalk-drive`/`dingtalk-wiki` 查询。 + + +## 多步文档短路径 -- 知识库「评测记录」下按日期文件夹执行:`dws wiki space search --keyword "评测记录" --format json` → `dws wiki node list --workspace --format json`(或 `dws drive list --workspace `;`doc list` 已弃用)→ 找 `评测-doc-YYYYMMDD`;不存在则 `dws wiki node create --workspace --name "评测-doc-YYYYMMDD" --type folder --format json`(`doc folder create` 已弃用)。 - 在目标文件夹创建文字文档:`dws doc create --name "<标题>" --folder --content-file --format json`。拿到 `nodeId` 后立即回读。 - 块级编辑固定顺序:`doc block list --node ` → 选 `blockId` → `doc block insert/update/delete` → `doc block list` 验证。删除块必须已有用户明确删除意图或二次确认。 -- 插入引用块、代码块、表格、分栏、附件、图片时,优先读 [doc.md](references/doc.md) 对应小节,不要只停在"准备查看 help"。说出"我将插入..."后必须立即执行对应 terminal 调用。 -- 用户要求多个子文档/附件/块操作时,按 checklist 串行完成;最后一条 assistant 消息不能停在"接下来我要...",必须有实际工具调用或明确失败原因。 -- 用户说"下载文件"(已有文件)时用 `dws drive download --node ... --output `(`doc download` 已弃用,切到 `dingtalk-drive`);用户说"导出在线文档为 docx"时用 `doc export --node ... --output `(内容级命令,未迁移)。 -- 所有 dws 命令带 `--format json`;仅参数不确定时查 `--help`,不要把完整 help 当成最终结果。 +- 插入引用块、代码块、表格、分栏、附件、图片时,按 [写入索引](references/doc/doc-write-index.md) 选择命令与结构,并在写后回读。 +- 多个子文档、附件或块操作按依赖顺序执行;每一步复用上一条命令返回的真实 ID,最后统一回读受影响内容。 +- 用户说"读取并下载/导出"时,先 `doc info --node ... --format json` 判断类型:在线文档用 `doc export`,普通文件切到 `dingtalk-drive` 用 `drive download`。 +- 所有 dws 命令带 `--format json`;仅参数不确定时查 `--help`,并以实际命令结果完成验证。 ## 危险操作 @@ -70,3 +127,6 @@ metadata: - 知识库空间管理 → 切到 `dingtalk-wiki` - 数据表 → 切到 `dingtalk-aitable` - 长篇报告生成(多源采集 + 写文档)→ 此 skill 提供 `doc_create_and_write.py` 脚本 +## 局部意图与 Recipe + +- [局部意图消歧](references/intent-guide.md);[Lite Recipe](references/lite-recipes.md)。 diff --git a/skills/multi/dingtalk-doc/references/04-document.md b/skills/multi/dingtalk-doc/references/04-document.md index c884341e..7a7ed50a 100644 --- a/skills/multi/dingtalk-doc/references/04-document.md +++ b/skills/multi/dingtalk-doc/references/04-document.md @@ -1,81 +1,30 @@ # 文档知识 -> 通用规范见 [_common/conventions.md](_common/conventions.md)。 +## 模板保形生成 + +已有 alidocs 文档需要“按模板生成、复刻或生成同形态变体”时,优先在原生文档层复制,避免 `doc read` 转成 Markdown 后重建导致样式丢失: + +1. `dws drive copy --node --folder --format json` 复制原文档。 +2. 从返回中取得副本 `nodeId`,再用 `dws drive rename --node --name "<新名称>" --format json` 重命名。 +3. 用 `dws doc block list --node --format json` 定位需要替换的块,仅在副本上执行 `doc block update`。 +4. 回读副本的关键块,确认文字已替换且原布局、表格与样式仍保留。 + +不要用 `doc read + doc create` 重建要求保形的模板文档。 + +> lite recipe 见 [SKILL.md 速查表](../SKILL.md)。 | Recipe | 行动指南(固定路线) | |--------|-------------------| -| write-doc | 1. 按[「多源并行采集」](_common/conventions.md#多源并行采集公共模式)执行
2. **先把内容写入临时文件**(Linux/Mac `/tmp/.md`,Windows `%TEMP%\.md`)—— 含多行/表格/长文本必须走文件,不要把 markdown 直接作为命令行字符串
3. **单步创建**(< 200KB):`doc create --name "<文档名>" --content-file [--folder ] [--workspace ]`(`--folder` 只传文档文件夹 nodeId / alidocs 文件夹 URL,不传数字 dentryId)
4. **超长兜底**(> 200KB):**必须先向用户提示截断风险**(详见下方「分块 append 截断风险提示」),用户确认后再执行:`doc create --name "<文档名>" [--folder/--workspace]` → `nodeId` → 按段落切 ≤200KB 片段(不断表格) → 每片 `doc update --node --content-file --mode append`
5. **回读校验**(必须):所有写入完成后,执行 `doc read --node ` 回读文档,校验关键标题/段落是否完整写入(详见下方「doc update 回读校验规范」)
备选(仅短内容 <2KB 且无换行/表格):`doc create --name "..." --content "..."` | -| search-docs-and-share | 1. `doc search --query "<关键词>"` → 取 `nodeId` + 标题建索引(不读全文)
2. `doc read --node `(追问按需,最多 2 篇) | -| create-knowledge-base | 1. 创建知识库空间取 `WS_ID`
2. `doc create --name "<文档名>" --workspace ` → 取 `nodeId`
3. `doc list --workspace ` 确认 | +| import-file | 1. **直接执行** `dws doc import --file <本地文件路径> --format json`(一条命令完成上传+转换+创建)
2. 从返回中提取 `documentUrl` 并告知用户
3. **禁止先 Read 文件内容再 `doc create` + `doc update`**——`doc import` 是服务端格式转换,客户端无需解析文件内容
4. 可选参数:`--folder <文件夹ID>` 指定目标文件夹、`--workspace <知识库ID>` 指定目标知识库、`--name "文档名"` 自定义名称
5. 格式映射:docx/doc→文档, xlsx/xls→表格, xmind/mark→脑图, md/txt→文档
6. 超时或中断时 CLI 返回 `taskId`,用 `dws doc import get --task-id ` 手动查询
详见 [doc-import.md](./doc/doc-import.md) | +| write-doc | 0. 阅读 [doc-create-workflow.md](./doc/style/doc-create-workflow.md) 的 §前置必读 + §关键词速查表,锁定文档类型和起稿路径:**决策型/含对比的知识沉淀型/用户要求美观 → JSONML 起稿**(`.json`);执行型/说明型 → Markdown 起稿(`.md`)
1. 按选定路径执行 doc-create-workflow.md(JSONML 路径有骨架范例可直接复制修改)
2. `doc create --content-file /tmp/.json --content-format jsonml`(或 `.md` + `--content-format markdown`)
3. 大内容默认依赖 DWS 自动分片;只有 `CONTENT_TRUNCATED`、部分写入失败或回读发现缺失时,才按 workflow 的恢复流程手工补片
4. **回读校验(必须)**:所有写入完成后,执行 `doc read --node `,校验关键标题/段落/表格是否完整写入 | +| search-docs-and-share | 1. `drive search --query "<关键词>"` → 取 `nodeId` + 标题建索引(不读全文)
2. `doc read --node `(追问按需,最多 2 篇) | +| create-knowledge-base | 1. 创建知识库空间取 `WS_ID`
2. `wiki node create --workspace --name "<文档名>"` → 取 `nodeId`
3. `wiki node list --workspace ` 确认 | | migrate-doc | 1. `doc read --node <源nodeId>` → 取正文并写入临时文件 `.md`
2. `doc create --name "<文档名>" --folder --content-file .md` → 取新 `nodeId`(`--folder` 只传文档文件夹 nodeId / alidocs 文件夹 URL,不传数字 dentryId;正文 <200KB 单步到位)
2a. 若正文 >200KB:**必须先向用户提示截断风险**(详见下方「分块 append 截断风险提示」),用户确认后再执行:`doc create --name "<文档名>" --folder ` → `nodeId` → 按段落切片 → 每片 `doc update --node --content-file --mode append`
3. **回读校验**:`doc read --node ` 校验内容完整性 | -| update-doc-section | **优先块级精修(保形)**:1. `doc search --query "<关键词>"` → 取 `nodeId`
2. `doc block list --node --format json` 定位目标块取 `blockId`
3. `doc block update --node --block-id --text "<替换内容>" --format json` 局部修改(不破坏富格式)
4. **回读校验**:`doc read --node ` 确认目标章节已更新
**兜底**:仅当无法定位块且用户确认整篇替换时,先 `doc update --node --content-file .md --mode overwrite --dry-run` 预览,再加 `--yes` 执行;overwrite 会丢富格式(颜色/字号/合并单元格等),详见下方 template-based-generation §"为什么避免 overwrite" | -| template-based-generation | **触发**:用户给已有 alidocs URL + "参照/按模板/复刻/同样模板生成 X 月份的" → **保形复制,禁止重写**。固定走下方 [template-based-generation](#template-based-generation) 章节:`doc copy` 保形复制 → `doc rename` → `doc block list/update` 局部改副本。禁止 `doc read + doc create` 重写链。 | +| update-doc-section | 1. `drive search --query "<关键词>"` → 取 `nodeId`
2. **形态选择(按 [doc-update-workflow.md §1.3](./doc/style/doc-update-workflow.md) 优先级)**:目标段落含 callout / 分栏 / 颜色 / @人 / 附件 / 嵌套结构 → 走 `jsonml-node-edit`;纯文本替换且确认无富结构 → 继续本 recipe
3. `doc read --node ` 定位目标章节
4. `doc update --node --content "<替换内容>" --mode overwrite`
5. **回读校验**:`doc read --node ` 确认 overwrite 未被降级为 append、内容完整无截断
**overwrite 须用户确认**;完整改写流程见 [doc-update-workflow.md](./doc/style/doc-update-workflow.md) | +| rewrite-doc | 1. 阅读并执行 [doc-update-workflow.md](./doc/style/doc-update-workflow.md):先看 §1.3 编辑形态优先级(**JSONML 首选**),再按 §3 速查表选路径,跳 §4 对应小节执行
2. 单块改写 / 含富结构 → §4.4 路径 B;多处保真改写或改 root → §4.4 路径 A;纯文本骨架重写 → §4.5 markdown
3. 整篇 overwrite 前必须按 workflow §4.5 向用户提示风险并等待确认
4. **回读校验(必须)**:按 workflow §6 的校验要点逐项核查;@人、附件、图片等保真要素必须原样保留
**适用场景**:用户提供已有 nodeId/链接,需要改写、润色、章节补充、段落形态转换、整篇重写 | | doc-to-message | 1. `doc read --node ` → 取正文(大文档只摘要+链接)
2. `contact user search --query "<姓名>"` → 取 `openDingTalkId`(推荐);或 `chat search --query "<群名>"` → 取 `openConversationId`
3. `chat message send --open-dingtalk-id --text "<内容>"`(推荐)或 `--group --text "<内容>"` 发送。仅当无法获取 openDingTalkId 时才用 `--user `(备选) | -| delete-old-doc | 1. `doc search --query "<关键词>"` 或 `doc list --folder ` 取 `nodeId`
2. **必须先向用户展示要删除的文档标题/路径**,等用户确认
3. `doc delete --node --yes`(注意:是 `doc delete`,不是 `doc block delete`;前者删整篇,后者删块)
4. 验证:`doc info --node ` 应返回 not-found | -| export-doc-as-docx | 1. `doc search --query "<关键词>"` → 取 `nodeId`
2. `doc export --node --output /tmp/.docx --timeout-sec 600`(CLI 内置渐进式退避轮询,**不要自己拼 GET downloadUrl**)
3. 落盘失败兜底:`doc export get --job-id --output /tmp/.docx` 续等
4. (可选)`doc-to-message` 把 docx 路径作为附件发给用户 | -| grant-doc-access | 1. `doc search --query "<关键词>"` → 取 `nodeId`
2. `contact user search --query "<姓名>"` → 取 `userId`(**注意**: doc permission 用 `userId`,不是 `openDingTalkId`)
3. **节点级**授权:`doc permission add --node --user --role EDITOR`
  role 取值: MANAGER / EDITOR / DOWNLOADER / READER(**不要传 OWNER**)
  单次最多 30 个 userId
4. 查权限:`doc permission list --node ` 确认
5. **不要混淆**: 给"知识库"加成员用 `wiki member add --workspace`(容器级);给"单个文档"加权限用 `doc permission add --node`(节点级)| -| insert-image-to-doc | 1. `doc search --query "<关键词>"` → 取 `nodeId`
2. `doc media insert --node --file ./image.png`(3 步一体化:获取凭证 → PUT OSS → 插入块;图片 ≤20MB 自动作为内联图片,其他作为附件块)
3. (可选)`doc read --node ` 回读确认
**禁止**: 自己拿 uploadUrl 写 PUT —— 90%+ 会因 Content-Type 未清空触发 SignatureDoesNotMatch | -| download-doc-attachment | 1. `doc block list --node ` → 找到 `blockType=attachment` 的块取 `resourceId`
2. `doc media download --node --resource-id ` → 返回 OSS 临时 URL(**注意 expirationSeconds**)
3. 调用方自行 GET 该 URL 落盘 | - ---- - -## template-based-generation - -### 触发条件 - -用户给已有 alidocs URL,并要求: -- "参照这个生成同样的" -- "按模板生成" -- "复刻这个" -- "同样的模板,X 月份的" -- "仿照这个文档" - -### 核心原则 - -**保形优于重写**。adoc 富格式(行高、单元格背景色、字号、渐变文字色、表格列宽、合并单元格)在 markdown 层无表达,`doc read → create_file → doc create` 会做两次 lossy projection(先 adoc→markdown,再 markdown→adoc),必然丢失这些属性。 - -正确做法:**在 adoc 层保形复制(`doc copy`)**,再局部改写需要替换的块(`doc block update`),保留所有富格式。 - -### 固定路线 - -```bash -# 1. probe 模板节点,确认是 ALIDOC(adoc 富文档) -dws doc info --node <模板NODE或URL> --format json - -# 2. 在 adoc 层保形复制(不经过 markdown 投影) -dws doc copy --node <模板NODE或URL> --folder <目标文件夹NODE或URL> --format json -# → 取返回的副本 nodeId - -# 3. 重命名副本 -dws doc rename --node <副本nodeId> --name "<新文档名>" --format json - -# 4. 列出所有块定位要改的内容 -dws doc block list --node <副本nodeId> --format json -# → 记录 blockId 和现有文本 - -# 5. 逐块局部改写(只改月份/日期/姓名/指标等局部变量,不动结构) -dws doc block update --node <副本nodeId> --block-id --text "<新内容>" --format json - -# 6. 回读确认 -dws doc read --node <副本nodeId> -``` - -### 为什么避免 overwrite - -| 场景 | overwrite 后果 | -|------|---------------| -| 模板带表格背景色 | 全部变白 | -| 标题字号 24pt 加粗渐变 | 退化为普通 H1 | -| 合并单元格的表格 | 拆成独立单元格 | -| 嵌入图片 | 链接保留但样式重置 | - -**用 copy+block update 全部可保留**。 - -### 禁止反模式 - -- ❌ doc read + doc create 重写模板文档(两次 lossy projection) -- ❌ 先 `dws doc copy`,再删除副本退回 create 重写链路 -- ❌ 用 `dws doc update --mode overwrite --yes` 把含富格式的 adoc 整篇覆盖成 markdown +| lossless-doc-edit | 1. `doc read --node --content-format jsonml --output /tmp/doc.json` → 获取完整 JSONML 结构(输出含 `revision`,并发敏感时记下来;默认改写不需要)
2. 解析 JSON 文件,修改 `jsonml` 数组中的目标节点(节点结构参见 [doc-jsonml-schema.md](./doc/format/doc-jsonml-schema.md))
3. 将修改后的内容写回临时文件 `/tmp/doc_modified.json`,格式为 `{"jsonml": [...]}`
4. `doc update --node --content-file /tmp/doc_modified.json --content-format jsonml --mode overwrite`(默认不做并发检查;担心多 agent 同时改时加 `--revision <第 1 步拿到的 N>` 触发并发检查,版本不一致返回 `VersionConflict` 时回到第 1 步重读重写)
5. **回读校验**:`doc read --node --content-format jsonml` 确认写入成功
**适用场景**:保留样式、精准插入特定节点类型、改属性不动文本;普通文本编辑仍优先用 markdown 模式。完整 JSONML 改写流程见 [doc-update-workflow.md §4.4](./doc/style/doc-update-workflow.md) | +| jsonml-node-edit | 1. `doc block list --node --content-format jsonml` → 获取 JSONML 节点列表(含 uuid)
2. 根据 uuid 定位目标节点
3. `doc block list --node --content-format jsonml --block-id ` → 读取完整子树
4. 修改 JSONML 节点内容(节点结构参见 [doc-jsonml-schema.md](./doc/format/doc-jsonml-schema.md),可复制范例见 [doc-jsonml-cookbook.md](./doc/format/doc-jsonml-cookbook.md))
5. `doc block update --node --block-id --content-format jsonml --element '<修改后的 JSONML>'` → 写回
**适用场景**:只改一个 block 的结构/样式,无需全文回写;写入端默认 normalize(自动补 uuid、裸字符串包成 canonical 文本),可用 `--no-fix-jsonml` 关闭全部修复;`--fix-jsonml` 额外启用 JSON 语法修复(推荐 agent 调用) | --- @@ -106,32 +55,15 @@ dws doc read --node <副本nodeId> ## doc update 回读校验规范 -### 为什么需要回读校验 +**所有 `doc update`(含 overwrite 和 append)执行后都必须回读校验**——返回 `success=true` 不等于内容真的写入完整。 -`doc update` 在以下场景可能产生**静默失败**,返回 `success=true` 但实际写入不完整: +完整规范(静默失败场景、校验流程、异常处理路径、"先清空再重建"命令样板)见 [doc-update-workflow.md §6「回读验收」](./doc/style/doc-update-workflow.md)。 -- **overwrite 降级为 append**:大文档 overwrite 模式被后端静默降级,导致旧内容未清除、新内容追加在末尾 -- **分块 append 内容截断**:超长文档分片写入时,部分片段丢失或顺序错乱 -- **编码/通道问题**:PowerShell 等特殊终端下 UTF-8 内容传输乱码 - -### 校验流程 - -在 **每次 `doc update` 完成后**(无论 overwrite 还是 append 模式),必须执行以下步骤: - -1. **回读文档**:`doc read --node ` -2. **关键内容校验**:检查回读结果是否包含预期的关键标题、段落首句、表格标记等 -3. **异常处理**: - - 若内容缺失或截断 → 向用户报告具体差异,建议重试或手动修复 - - 若 overwrite 后仍残留旧内容 → 提示用户 overwrite 可能被降级,建议先清空再 append - - **禁止**在未回读的情况下直接向用户报告"已完成" - -### 示例 +最小流程: +```bash +dws doc update --node --content-file /tmp/new-content.md --mode overwrite +dws doc read --node # 校验关键标题、段落首句、表格、@人、附件 ``` -# 更新文档 -doc update --node abc123 --content-file /tmp/new-content.md --mode overwrite -# 回读校验 -doc read --node abc123 -# → 检查输出是否包含预期内容的关键标题和段落 -``` +**禁止**在未回读的情况下向用户报告「已完成」。 diff --git a/skills/multi/dingtalk-doc/references/_common/conventions.md b/skills/multi/dingtalk-doc/references/_common/conventions.md index 93e65f8c..75491aab 100644 --- a/skills/multi/dingtalk-doc/references/_common/conventions.md +++ b/skills/multi/dingtalk-doc/references/_common/conventions.md @@ -1,6 +1,6 @@ # 业务域通用规范 -> 仅服务本仓库已迁入的文档与 AI 表格行动指南。安全门控、危险操作确认、`--format json` 等已在根 skill 中定义,此处不重复。 +> 仅服务本 skill 已迁入的行动指南。安全门控、危险操作确认、`--format json` 等已在本 skill 的 `SKILL.md` 中定义,此处不重复。 ## 批量查询规范 @@ -9,7 +9,8 @@ | 1 | **并行查详情**:拿到多个 ID 后,用 `&` 合并到同一条 Shell 命令并行执行 + `wait`,**严禁逐条串行** | | 2 | **翻页**:分页接口须拉全直至无更多 | | 3 | **优先批量 API**:有批量接口则用批量;无则按 #1 并行 | -| 4 | **列表少轮次**:带条件搜索/列表 → 一次采全详情;**禁止**无新参数时重复同一 `list` / `search` | +| 4 | **群消息**:必须先 `chat search --query` 得 `openConversationId`,再 `chat message list --group`;多群同条命令并行 | +| 5 | **列表少轮次**:带条件搜索/列表 → 一次采全详情;**禁止**无新参数时重复同一 `list` / `search` | ## 多源并行采集(公共模式) @@ -26,10 +27,18 @@ | 字段 | 来源 | 传递给 | |------|------|--------| -| `nodeId` | `doc search` | `doc read/update/copy/move/rename --node` | -| `nodeId` | `doc list` 中的 folder 类型节点 / `doc folder create` | `doc list --folder`、`doc create --folder`、`doc upload --folder`、`doc copy/move --folder` | +| `taskUuid` | `minutes list` | `minutes get summary/info/batch --id(s)` | +| `userId` | `aisearch person` / `contact user search` / `contact dept list-members` | `contact user get --ids`、`todo --executors`、`calendar --users` | +| `deptId` | `contact dept search` | `contact dept list-members --depts`;多子部门时对每个子部门分别 `dept search` 取 id | +| `nodeId` | `drive search` / `wiki node search` | `doc read/update --node`、`drive copy/move/rename/delete --node` | +| `nodeId` | `wiki node list` 中的 folder 类型节点 / `wiki node create --type folder` | `wiki node list --folder`、`wiki node create --folder`、`drive upload --folder`、`drive copy/move --folder` | +| `eventId` | `calendar event list` | `calendar event get/update --id` | +| `processInstanceId` | `oa approval list-*` | `oa approval detail/approve --instance-id` | +| `openConversationId` | `chat search` | `chat message list/send --group` | +| `todoTaskId` | `todo task list` | `todo task update/done --task-id` | +| `reportId` | `report inbox list` / `report outbox list` | `report entry get/stats --report-id` | | `baseId` / `tableId` | `aitable base search` | `aitable record query --base-id --table-id` | -| `dentryUuid` | `drive list` / `drive mkdir` | `drive info/download --file-id`、`drive list/mkdir/upload --parent-id` | -| `workspaceId` | `wiki space search/list/create` | `doc list/search/create --workspace`、`wiki member * --workspace` | +| `dentryUuid` | `drive list` / `drive mkdir` | `drive info/download --node`、`drive list/mkdir/upload --folder` | +| `dentryId` | `drive info` 的数字字段 | 仅用于 `chat message send --dentry-id` | -**ID 边界硬约束**:遇到 `drive --parent-id`、`doc --folder`、`doc --node` 时,只能使用 `dentryUuid` / `nodeId` / 文档 URL。若当前上下文只有数字型 `dentryId`,必须先重新 `drive list` / `doc list` / `doc search` 获取正确 ID,不能把该数字直接代入后续命令。 +**ID 边界硬约束**:`dentryId` 通常是纯数字,只表示聊天文件消息需要的钉盘条目数字 ID;它不是父目录 ID。遇到 `drive --node/--folder`、`doc --node/--folder`、`wiki node --folder` 时,只能使用 `dentryUuid` / `nodeId` / 文档 URL。若当前上下文只有数字型 `dentryId`,必须先重新 `drive list` / `drive search` / `wiki node list` 获取正确 ID,不能把该数字直接代入后续命令。 diff --git a/skills/multi/dingtalk-doc/references/doc.md b/skills/multi/dingtalk-doc/references/doc.md index 5ea2862a..8305de3d 100644 --- a/skills/multi/dingtalk-doc/references/doc.md +++ b/skills/multi/dingtalk-doc/references/doc.md @@ -1,1065 +1,26 @@ # 文档 (doc) 命令参考 -## 查询命令帮助 - -当你不确定某个命令的具体参数、格式或可选项时,**优先执行 `--help` 查询**,不要猜测参数名或凭记忆编造。 - -```bash -# 查看 doc 下所有子命令 -dws doc --help - -# 查看具体命令的完整参数说明 -dws doc read --help -dws doc create --help -dws doc block insert --help - -# 查看子命令组下的所有命令 -dws doc block --help -dws doc permission --help -``` - -规则: -- 参数名不确定时 → 先 `--help`,再调用 -- 报错 "unknown flag" 时 → `--help` 确认正确的 flag 名称 -- 不确定某个功能是否存在时 → `dws doc --help` 查看命令列表 - -## 命令总览 - -### 搜索 / 遍历文件(已迁移,不再是 doc 子命令) - -> **弃用提示**:`dws doc search` 和 `dws doc list` 已迁移到 `drive` / `wiki`。这两条虽仍能跑,但每次都会打印弃用警告,请改用下面的命令,命令详情见 dingtalk-drive skill。 - -| 旧命令(弃用) | 改用 | 场景 | -|--------------|------|------| -| `dws doc search --query "<关键词>"` | `dws drive search --query "<关键词>"` | 全局搜文档/文件(跨钉盘+文档空间聚合) | -| `dws doc search --workspace-ids ` | `dws wiki node search --workspace --query "<关键词>"` | 指定知识库内搜索 | -| `dws doc list` | `dws drive list` | 遍历「我的文档」/钉盘根目录 | -| `dws doc list --folder ` | `dws drive list --folder ` | 遍历指定文件夹 | -| `dws doc list --workspace ` | `dws drive list --workspace ` 或 `dws wiki node list --workspace ` | 遍历知识库 | - -拿到 `nodeId` 后,`doc read` / `doc info` / `doc update` / `doc block` 等内容级命令照常用。 - -### 获取文档元信息 -``` -Usage: - dws doc info [flags] -Example: - dws doc info --node - dws doc info --node "https://alidocs.dingtalk.com/i/nodes/" - dws doc info --node "https://alidocs.dingtalk.com/document/edit?dentryKey=" - dws doc info --node "https://alidocs.dingtalk.com/document/preview?dentryKey=" -Flags: - --node string 文档 ID 或 URL (必填) -``` - -### 读取文档内容 -``` -Usage: - dws doc read [flags] -Example: - dws doc read --node - dws doc read --node "https://alidocs.dingtalk.com/i/nodes/" - dws doc read --node "https://alidocs.dingtalk.com/document/edit?dentryKey=" - dws doc read --node "https://alidocs.dingtalk.com/document/preview?dentryKey=" -Flags: - --node string 文档 ID 或 URL (必填) -``` - -### 创建文档 -``` -Usage: - dws doc create [flags] -Example: - dws doc create --name "项目周报" - dws doc create --name "Q1 总结" --content "# Q1 总结" --folder - dws doc create --name "知识库文档" --workspace -Flags: - --name string 文档名称 (必填) - --folder string 父文件夹 nodeId / URL - --workspace string Workspace ID - --content string 初始 Markdown 内容 - --content-file string 从文件读取初始 Markdown -``` - -### 创建其他类型文件 (表格/脑图/白板/多维表/画板/文件夹) - -> **弃用提示**:`dws doc file create --type` 已弃用,执行时会打印 `deprecated, use 'dws wiki node create --type '`。创建各类型文件节点改用 `dws wiki node create`(与 intent-guide 一致);创建普通文件夹用 `dws drive mkdir`。 - -``` -Usage: - dws wiki node create [flags] -Example: - dws wiki node create --workspace --name "数据统计" --type axls - dws wiki node create --workspace --name "思维导图" --type amind - dws wiki node create --workspace --name "方案目录" --type folder --folder -Flags: - --workspace string 目标知识库 ID (必填 — wiki node create 不支持不带 workspace 创建) - --name string 文件名称 (必填) - --type string 文件类型: adoc=文档, axls=表格, appt=演示, adraw=白板, amind=脑图, able=多维表, folder=文件夹 - --folder string 父节点 ID (选填) -``` - -> 说明:`wiki node create` **必须**带 `--workspace`(知识库上下文),不能像旧 `doc file create` 那样在「我的文档」个人空间直接建。要在个人空间/钉盘建普通文件夹,用 `dws drive mkdir --name "<名称>"`(见 dingtalk-drive skill)。 - -### 更新文档内容 -``` -Usage: - dws doc update [flags] -Example: - dws doc update --node --content "# 追加内容" --mode append - dws doc update --node --content "# 完整替换" --mode overwrite -Flags: - --node string 文档 nodeId / URL - --content string Markdown 内容 - --content-file string 从文件读取 Markdown - --mode string 更新模式: append / overwrite -``` - -### 上传文件到钉钉文档或钉钉知识库 -``` -Usage: - dws doc upload [flags] -Example: - dws doc upload --file ./report.pdf - dws doc upload --file ./slides.pptx --name "Q1汇报.pptx" --folder - dws doc upload --file ./data.xlsx --workspace --convert -Flags: - --file string 本地文件路径 (必填) - --name string 文件显示名称 (默认使用文件名) - --folder string 目标文件夹 ID 或 URL - --workspace string 目标知识库 ID - --convert 是否转换为钉钉在线文档 -``` - -### 导入本地文件为在线文档 - -详见 [doc/doc-import.md](./doc/doc-import.md)。 - -``` -Usage: - dws doc import [flags] - dws doc import get [flags] -Example: - dws doc import --file ./report.docx - dws doc import --file ./notes.md --folder - dws doc import --file ./data.xlsx --workspace - dws doc import get --task-id -Flags: - --file string 本地文件路径 (必填) - --folder string 目标文件夹 ID 或 URL - --workspace string 目标知识库 ID 或 URL - --name string 导入后文档名称 (默认取文件名) - --task-id string 导入任务 ID (import get 必填) -``` - -### 下载文件到本地(已迁移到 drive) - -> **弃用提示**:`dws doc download` 已迁移到 `dws drive download`(执行 `doc download` 会打印弃用警告)。下载已有文件(PDF/图片/附件等非在线文档)改用: - -``` -dws drive download --node --output ~/downloads/ -dws drive download --node --output ./report.pdf -``` - -> 注意区分:**在线文档(ALIDOC)导出为 docx** 仍走 `doc export`(内容级命令,未迁移);`download` 只下载已有文件。命令详情见 dingtalk-drive skill。 - -### 创建文件夹(已迁移) - -> **弃用提示**:`dws doc folder create` 已弃用。创建文件夹改用: -> - 个人空间/钉盘 → `dws drive mkdir --name "<名称>" [--folder <父节点>]` -> - 知识库内 → `dws wiki node create --workspace --name "<名称>" --type folder` -> -> 命令详情见 dingtalk-drive skill。 - -### 复制文档/文件 -``` -Usage: - dws doc copy [flags] -Example: - dws doc copy --node --folder - dws doc copy --node --workspace - dws doc copy --node "https://alidocs.dingtalk.com/i/nodes/" --folder -Flags: - --node string 源文档/文件 ID 或 URL (必填) - --folder string 目标文件夹 ID 或 URL - --workspace string 目标知识库 ID 或 URL (不传 --folder 时复制到该知识库根目录) -``` - -### 移动文档/文件 -``` -Usage: - dws doc move [flags] -Example: - dws doc move --node --folder - dws doc move --node --workspace -Flags: - --node string 源文档/文件 ID 或 URL (必填) - --folder string 目标文件夹 ID 或 URL - --workspace string 目标知识库 ID 或 URL (不传 --folder 时移动到该知识库根目录) -``` - -### 重命名文档/文件 -``` -Usage: - dws doc rename [flags] -Example: - dws doc rename --node --name "新名称" - dws doc rename --node "https://alidocs.dingtalk.com/i/nodes/" --name "项目周报 v2" -Flags: - --node string 文档/文件 ID 或 URL (必填) - --name string 新名称 (必填) -``` - -### 查询块元素 -``` -Usage: - dws doc block list [flags] -Example: - dws doc block list --node - dws doc block list --node --start-index 0 --end-index 5 - dws doc block list --node --block-type heading -Flags: - --node string 文档 ID 或 URL (必填) - --start-index int 起始位置 (从 0 开始) - --end-index int 终止位置 (含) - --block-type string 按块类型过滤 -``` - -### 插入块元素 -``` -Usage: - dws doc block insert [flags] -Example: - dws doc block insert --node --text "这是一段文字" - dws doc block insert --node --heading "二级标题" --level 2 - dws doc block insert --node --element '{"blockType":"paragraph","paragraph":{"text":"内容"}}' - dws doc block insert --node --text "在此处之前插入" --ref-block --where before -Flags: - --node string 文档 ID 或 URL (必填) - --text string 快捷: 段落文本内容 - --heading string 快捷: 标题文本 - --level int 标题级别 1-6 (配合 --heading,默认 1) - --element string 块元素 JSON (高级) - --index int 参照位置索引 (从 0 开始) - --where string 插入方向: before / after (默认 after) - --ref-block string 参照块 ID (优先级高于 --index) -``` - -### 更新块元素 -``` -Usage: - dws doc block update [flags] -Example: - dws doc block update --node --block-id --text "新内容" - dws doc block update --node --block-id --element '{"blockType":"heading","heading":{"text":"新标题","level":1}}' -Flags: - --node string 文档 ID 或 URL (必填) - --block-id string 目标块 ID (必填) - --text string 快捷: 段落文本内容 - --heading string 快捷: 标题文本 - --level int 标题级别 1-6 (配合 --heading,默认 1) - --element string 块元素 JSON (高级) -``` - -### 删除块元素 - -> **CAUTION:** 不可逆操作 — 执行前必须向用户确认。 - -``` -Usage: - dws doc block delete [flags] -Example: - dws doc block delete --node --block-id --yes -Flags: - --node string 文档 ID 或 URL (必填) - --block-id string 目标块 ID (必填) -``` - -### 查询文档评论列表 -``` -Usage: - dws doc comment list [flags] -Example: - dws doc comment list --node - dws doc comment list --node --type inline --resolve-status unresolved - dws doc comment list --node --limit 20 --cursor -Flags: - --node string 目标文档的标识,支持传入 URL 或 ID (必填) - --limit int 每页返回的评论数量,默认 50,最大 50 - --cursor string 分页游标,从上一次请求的返回结果中获取 (首次请求不传) - --type string 按评论类型过滤: global (全文评论) / inline (划词评论) - --resolve-status string 按解决状态过滤: resolved (已解决) / unresolved (未解决) -``` - -### 创建文档评论 -``` -Usage: - dws doc comment create [flags] -Example: - dws doc comment create --node --content "这里需要修改" - dws doc comment create --node --content "请review" --mention uid1,uid2 -Flags: - --node string 目标文档的标识,支持传入 URL 或 ID (必填) - --content string 评论的文字内容,纯文本 (必填) - --mention string 被 @ 的用户 uid 列表,逗号分隔 -``` - -### 创建划词评论 (内联评论) -``` -Usage: - dws doc comment create-inline [flags] -Example: - dws doc comment create-inline --node --block-id --start 0 --end 10 --content "这里需要修改" - dws doc comment create-inline --node --block-id --start 5 --end 20 --content "建议调整" --selected-text "被选中的原文" - dws doc comment create-inline --node --block-id --start 0 --end 10 --content "请review" --mention uid1,uid2 -Flags: - --node string 目标文档的标识,支持传入 URL 或 ID (必填) - --block-id string 评论锚定所在的块 ID (必填,可通过 dws doc block list 获取) - --start int 选中文本在块内的起始字符偏移量,从 0 开始 (必填) - --end int 选中文本在块内的结束字符偏移量,必须大于 start (必填) - --content string 评论的文字内容,纯文本 (必填) - --selected-text string 选中文本内容,填写后评论列表会展示「引用原文:xxx」 - --mention string 被 @ 的用户 uid 列表,逗号分隔 -``` - -### 回复文档评论 -``` -Usage: - dws doc comment reply [flags] -Example: - dws doc comment reply --node --comment-key --content "同意" - dws doc comment reply --node --comment-key --content "比心" --emoji - dws doc comment reply --node --comment-key --content "请确认" --mention uid1,uid2 -Flags: - --node string 目标文档的标识,支持传入 URL 或 ID (必填) - --content string 回复的文字内容,表情回复时填写表情名称 (必填) - --comment-key string 被回复评论的 commentKey,格式: {13位毫秒时间戳}{32位UUID},可从 list/create 结果获取 (必填) - --emoji 设为 true 时作为表情贴图回复 (默认 false) - --mention string 被 @ 的用户 uid 列表,逗号分隔 -``` - -### 更新文档评论 -```text -Usage: - dws doc comment update --node --comment-key --content [--mention uid1,uid2] -``` - -`commentKey` 从 `comment list/create/create-inline` 返回中获取。只有显式传入 `--mention` 时才更新被 @ 用户列表。 - -### 删除文档评论 -```text -Usage: - dws doc comment delete --node --comment-key --yes -``` - -删除评论不可恢复,必须先获得用户明确确认,再添加全局 `--yes`。 - -### 文件内容获取路由规则 - -> 当用户请求"分析/查看/读取某个文件内容"时,**必须先调用 `dws doc info` 获取文件元数据**,再根据返回的 `contentType` 和 `extension` 字段选择对应链路: - -| contentType | extension | 操作 | 命令 | -|-------------|-----------|------|------| -| ALIDOC | adoc | 在线获取 Markdown 内容 | `dws doc read --node ` | -| ALIDOC | axls | 在线读取表格数据 | `dws sheet list --node ` → `dws sheet range read --node ` | -| ALIDOC | able | 在线查询多维表格记录 | `dws aitable table get --base-id ` → `dws aitable record query --base-id --table-id ` | -| 非 ALIDOC | — | **不支持在线分析** | 告知用户需下载到本地后查看 | - -**关键规则**:非 ALIDOC 类型文件(PDF/Word/图片/视频等)不支持在线分析,用户可以选择下载后本地查看。 - -### 格式保留度声明(adoc ↔ markdown lossy projection) - -`dws doc read --format json` 返回的 `markdown` 字段是钉钉 adoc 文档的**有损投影**。源 adoc 中以下富格式属性在 markdown 这一层**没有对应表达,会被丢弃**: - -- 行高(row height) -- 单元格背景色(cell background color) -- 字体大小(font size) -- 字体颜色的**渐变形态**(`radial-gradient` / `linear-gradient` — 文本会以 `` 字面保留,但 CSS `color` 不接受渐变,浏览器无法渲染,**字符串保留但语义已死**) -- 块级缩进 / 表格列宽 -- 合并单元格状态 -- checkbox 视觉样式(颜色、形状) -- 任何 SVG / 嵌入式画板的渲染细节 - -**硬指引(任何 contentType=ALIDOC 文档都适用)**: - -- 当用户要求"按模板生成同形态变体 / 参照这个生成 / 复刻 / 仿照这个文档"时,**禁止**走 doc read → create_file → doc create 链路——这条链路会做两次 lossy projection(adoc → markdown 丢一次,markdown → adoc 重建再丢一次),最终交付物只有文本内容,富格式全部失真。 -- 正确路径:`dws doc copy`(adoc 层面整文档保形复制)+ `dws doc rename` + `dws doc block list / dws doc block update`(在副本上做局部修改)。详见 [best_practices/04-document.md](../best_practices/04-document.md#template-based-generation) 的 `template-based-generation` recipe。 -- 当用户提到 `行高 / 颜色 / 字号 / 表格样式 / 周末高亮` 等富格式诉求时,agent 必须在动手前显式声明能力边界:"这些属性 markdown 无法表达,需走 copy + block update 路径",避免静默走破坏链。 - -### 内容写入管道(create / update 共用) - -> **关键原则**:CLI 内置自动分片。超长内容(>10000 字符)自动按 markdown 结构切分后逐片写入,对调用方透明。写入完成后由调用方自行决定是否回读确认。 - -#### 输入方式选择 - -| 场景 | 推荐方式 | 说明 | -|------|---------|------| -| 短文本(<2KB,无换行/表格/特殊字符) | `--content "..."` | 字面量传入,最简单 | -| 长文本(≥2KB)、含换行、含表格 | `--content-file ./file.md` | **必须**用文件路径,避免 shell escape 和截断 | -| 含特殊字符(`"`、`\`、`$`、`` ` ``) | `--content-file ./file.md` | 字面量传入会被 shell 转义破坏 | -| 管道/heredoc 输入 | `--content -` 或 `cat file \| dws doc ...` | 从 stdin 读取 | - -#### 自动分片行为 - -当内容超过 10000 字符时,CLI 自动执行: -1. **create**: 先创建空文档拿 `nodeId`,再按 markdown 标题边界切分后逐片 append -2. **update (overwrite)**: 第一片用 overwrite,后续片用 append -3. **update (append)**: 所有片段用 append - -分片策略按优先级:H1 标题 → H2 标题 → H3 标题 → 空行(段落边界)→ 硬切(保留表格/代码块完整性) - -如果某片写入超时,自动将分片大小减半重试(最小 5000 字符,低于此值报错)。 - -#### 输出格式 - -写入成功后输出 JSON(混合 `[INFO]` 进度行): - -```json -{"success": true, "nodeId": "xxx", "chunksWritten": 3} -``` - -| 字段 | 说明 | -|------|------| -| `nodeId` | 文档节点 ID,可用于后续读取或追加 | -| `chunksWritten` | 实际写入的分片数(1 = 单次写入) | - -#### 内容完整性验证(必读) - -CLI **不会**自动执行回读验证。**Agent 必须在文档写入完成后主动回读确认**——这是硬约束,不是建议: - -1. 使用 `dws doc read --node ` 读取写入后的文档内容 -2. 检查关键段落是否完整、顺序是否正确 -3. 如发现内容缺失或异常,使用 `dws doc update --mode append` 补写缺失部分 - -> **何时回读**:每次 create / update 操作完成后**必须**回读。 -> - 单次写入(≤10000 字符):写完立即回读一次 -> - 分片写入(>10000 字符):所有分片写完后回读一次全文,校验关键标题与段落完整性 -> - 破坏性 overwrite(`--mode overwrite --yes`):**必须**回读,确认 overwrite 未被后端静默降级为 append(详见 [best_practices/04-document.md "doc update 回读校验规范"](../best_practices/04-document.md#doc-update-回读校验规范)) -> - 连续多次编辑同一文档:可在全部编辑完成后统一回读一次 -> -> **禁止**在未回读的情况下直接向用户报告"已完成 / 更新成功"。Agent 自陈"完成"必须基于回读结果,而非工具调用的 `success=true` 字段。 - -#### 进度输出示例 - -``` -[INFO] 内容较长 (25000 字符),自动分片写入... -[INFO] 已创建空文档 (nodeId=abc123),开始分片写入... -[INFO] 写入分片 (1/3),10000 字符... -[INFO] 写入分片 (2/3),10000 字符... -[INFO] 写入分片 (3/3),5000 字符... -[INFO] 全部 3 个分片写入完成 -{"success": true, "nodeId": "abc123", "chunksWritten": 3} -``` - -#### CONTENT_TRUNCATED 错误 - -当分片写入持续超时且减半到最小阈值仍失败时,返回 `CONTENT_TRUNCATED` 错误码。应对策略: -1. 检查网络和后端服务状态 -2. 已写入的部分内容可通过 `dws doc read --node ` 查看 -3. 从断点处手动用 `dws doc update --mode append` 继续追加 - -### 删除文档/文件到回收站 - -> **CAUTION:** 不可逆操作 — 执行前必须向用户确认。 - -``` -Usage: - dws doc delete [flags] -Example: - dws doc delete --node --format json # 查询 nodeId: dws drive search --query "..." 或 dws drive list -Flags: - --node string 文档/文件 ID 或 URL (必填) -``` - -权限要求: 对文档有"管理"权限。 - -### 下载文档附件 -``` -Usage: - dws doc media download [flags] -Example: - dws doc media download --node --resource-id - dws doc media download --node "https://alidocs.dingtalk.com/i/nodes/xxx" --resource-id -Flags: - --node string 目标文档的标识,支持传入 URL 或 ID (必填) - --resource-id string 附件资源 ID,可通过 dws doc block list 获取 (必填) -``` - -### 上传附件并插入文档 -``` -Usage: - dws doc media insert [flags] -Example: - dws doc media insert --node --file ./report.pdf - dws doc media insert --node --file ./data.bin --name "数据文件.dat" --mime-type application/octet-stream - dws doc media insert --node --file ./image.png --ref-block --where before -Flags: - --node string 目标文档的标识,支持传入 URL 或 ID (必填) - --file string 本地文件路径 (必填) - --name string 附件显示名称 (默认使用文件名) - --mime-type string 文件 MIME 类型 (默认根据扩展名推断) - --index int 插入位置索引 - --where string 相对位置: before / after (配合 --ref-block) - --ref-block string 参考块 ID (配合 --where) -``` - -### 添加文档权限(节点级授权) -``` -Usage: - dws doc permission add [flags] -Example: - dws doc permission add --node --users uid1 --role READER - dws doc permission add --node --users uid1,uid2 --role EDITOR - dws doc permission add --node "https://alidocs.dingtalk.com/i/nodes/" --users uid1 --role MANAGER -Flags: - --node string 目标文档/文件夹的 ID 或 URL (必填) - --users strings 被授权的用户 userId 列表,逗号分隔 (必填,单次最多 30 个) - --role string 授予的角色 (必填,大小写敏感,必须全大写): MANAGER (管理者) / EDITOR (可编辑) / DOWNLOADER (可下载) / READER (可阅读) - --workspace string 所属知识库 ID (选填,仅用于辅助构造返回的 docUrl,业务实际依赖 nodeId) -``` - -> **重要约束**: -> - 仅支持 USER 类型授权。 -> - 角色枚举严格大写:MANAGER / EDITOR / DOWNLOADER / READER(OWNER 不可通过此接口添加)。 -> - 操作者需在该节点具备「可编辑(EDITOR)」及以上角色(OWNER / MANAGER / EDITOR)。 -> - 授权对象是文档节点本身,不需要也不应该用 `wiki member add`(那个是知识库容器级授权)。 - -### 修改文档权限(节点级) -``` -Usage: - dws doc permission update [flags] -Example: - dws doc permission update --node --users uid1 --role EDITOR - dws doc permission update --node --users uid1,uid2 --role READER -Flags: - --node string 目标文档/文件夹的 ID 或 URL (必填) - --users strings 目标用户 userId 列表,逗号分隔 (必填,单次最多 30 个) - --role string 新角色 (必填,大小写敏感,必须全大写): MANAGER / EDITOR / DOWNLOADER / READER - --workspace string 所属知识库 ID (选填) -``` - -### 列出文档权限(节点级) -``` -Usage: - dws doc permission list [flags] -Example: - dws doc permission list --node - dws doc permission list --node --limit 50 - dws doc permission list --node --filter-role EDITOR -Flags: - --node string 目标文档/文件夹的 ID 或 URL (必填) - --workspace string 所属知识库 ID (选填) - --limit int 返回数量上限,最大 200 (默认 50) - --filter-role string 按角色过滤: MANAGER / EDITOR / DOWNLOADER / READER (选填) -``` - -> 接口不支持游标分页,使用 `--limit` 一次性拉取。 - -### 导出在线文档为 docx(一体化命令) -``` -Usage: - dws doc export [flags] -Example: - dws doc export --node "https://alidocs.dingtalk.com/i/nodes/xxx" --output ./exported.docx - dws doc export --node --output ~/downloads/ -Flags: - --node string 要导出的文档标识,支持文档 URL 或 dentryUuid (必填) - --output string 本地保存路径,文件路径或目录 (必填) - --export-format string 导出格式,当前仅支持 docx (默认) -``` - -CLI 内部自动完成:提交导出任务 → 渐进式退避轮询(最多约 5 分钟)→ 成功后自动下载文件。 -**只需一条命令,无需手动轮询。** - -### 查询导出任务结果(手动兜底) -``` -Usage: - dws doc export get [flags] -Example: - dws doc export get --job-id -Flags: - --job-id string 导出任务 ID (必填) -``` - -仅在 `dws doc export` 超时或中断后,用于手动查询任务状态。通常不需要调用。 - - -## URL 识别与 DOC_ID 提取 - -当用户输入包含钉钉文档 URL 时,**必须先识别并提取 DOC_ID**,再判断意图。 - -补充:如果这是用户直接提供的原始 `alidocs` URL,必须先按 [链接规范](../url-patterns.md#alidocs-url-类型探测流程) probe 一次确认真实类型,再判断是否继续走 `doc`。 - -### 支持的 URL 格式 - -| 格式 | 示例 | DOC_ID 提取方式 | -|------|------|----------------| -| `alidocs.dingtalk.com/i/nodes/{id}` | `https://alidocs.dingtalk.com/i/nodes/9E05BDRVQePjzLkZt2p2vE7kV63zgkYA` | 取 URL 路径最后一段:`9E05BDRVQePjzLkZt2p2vE7kV63zgkYA` | -| `alidocs.dingtalk.com/i/nodes/{id}?queryParams` | `https://alidocs.dingtalk.com/i/nodes/abc123?doc_type=wiki_doc` | 忽略 query 参数,取路径最后一段:`abc123` | -| `alidocs.dingtalk.com/document/{edit\|preview}?...&dentryKey={key}` | `https://alidocs.dingtalk.com/document/edit?dentryKey=wo1g3x54FzVEJ5yE` | **不要提取 `dentryKey` 单独使用**,必须将完整 URL 原样传给 `--node` | - -### 提取规则 - -1. 匹配 URL 中 `alidocs.dingtalk.com` 域名 -2. 路径为 `/i/nodes/{id}` 时,取 URL path 的最后一段作为 DOC_ID(去掉 query string 和 fragment) -3. 路径为 `/document/edit` 或 `/document/preview` 且 query 含 `dentryKey` 时,**禁止**提取 `dentryKey` 当 DOC_ID;将整段 URL 原样传给 `--node`,CLI 会自动解析(追踪参数如 `utm_source`、`chInfo` 也不必清理) -4. 提取出的 DOC_ID 可直接用于所有 `--node` 参数,也可将完整 URL 传给 `--node`(CLI 会自动解析) - -### ID 边界与参数映射 - -- `nodeId` 是 `doc` 命令的统一节点标识;文档、文件夹、文件都通过 `nodeId` 或完整 `alidocs` URL 传给 `--node` / `--folder`。 -- `dentryUuid` 是 `alidocs` URL `/i/nodes/{dentryUuid}` 的最后一段,在 `doc` 场景中等价于可传入 CLI 的 `nodeId`;不要把它改写成数字 ID。 -- `dentryId` 通常是纯数字,**不是** `doc` 的 `nodeId`,也不是 `doc --folder` 的目标文件夹 ID;不要把数字 `dentryId` 当作 `--node`、`--folder`,也不要当作隐藏兼容 alias(如 `--parent-id` / `--parent-folder-id`)的取值。 -- 目标父文件夹的 canonical flag 是 `--folder `,目标知识库使用 `--workspace `。CLI 可能接受 `--parent-id` / `--parent-folder-id` 作为隐藏兼容 alias,但 skill 正文和示例应统一教 `--folder`,不要把 drive/chat 链路里的数字 parentId 搬到 doc 命令。 -- 如果上下文只有数字 `dentryId`,但用户要读、改、移动、复制、重命名文档,先通过 `drive search` / `drive list` / 用户提供的 `alidocs` URL 获取 `nodeId` / `dentryUuid`,不要用数字 `dentryId` 重试为父目录参数。 - -### 处理流程 - -``` -用户输入含 alidocs.dingtalk.com URL - → 若是用户直接提供的原始 URL,先按链接规范做 probe - → 提取 DOC_ID(URL 路径最后一段) - → 结合用户意图选择命令(doc 默认 read,folder 默认 drive list,file 默认 drive download) - → 将 DOC_ID 传给 --node 参数 -``` +> 渐进式入口:先按任务选择下表中的参考,不要一次性加载全部文档。 + +## 按需加载 + +| 任务 | 参考 | +|---|---| +| URL、前置条件、帮助查询 | [URI](./doc/doc-uri.md) · [前置条件](./doc/doc-prerequisites.md) · [帮助](./doc/doc-help-query.md) | +| 阅读 / 元信息 | [阅读与元信息索引](./doc/doc-info-index.md) | +| 创建 / 写入 / 块编辑 | [写入索引](./doc/doc-write-index.md) | +| 附件 / 评论 / 导出 | [媒体与导出索引](./doc/doc-media-index.md) | +| 导入 / 版本 / 模板 | [扩展能力索引](./doc/doc-extra-index.md) | +| 完整命令速查 | [命令索引表](./doc/doc-command-index.md) | +| 场景路由与意图判断 | [场景索引](./doc/doc-scenarios.md) · [意图判断](./doc/doc-intent.md) | +| 标准操作流程 | [核心工作流](./doc/doc-workflow.md) · [上下文传递](./doc/doc-context.md) | +| 排版与 JSONML | [JSONML 索引](./format/doc-jsonml-index.md) | +| 相关产品边界 | [相关产品](./doc/doc-related.md) | ## 意图判断 -用户说"找文档/搜文档/最近文档": -- 搜索 → `dws drive search`(全局)或 `dws wiki node search --workspace `(指定知识库) -- 浏览 → `dws drive list`(`doc search` / `doc list` 已弃用,见本文顶部「搜索 / 遍历文件」) +按 [doc-intent.md](./doc/doc-intent.md) 判断 `doc`、`drive`、`wiki`、`sheet` 与 `aitable` 的边界。 -用户说"看文档/读内容/文档内容": -- 读取 → `read` (需文档 ID 或 URL) -- 元信息 → `info` +## 模板管理 -用户说"写文档/创建文档": -- 新建文字文档(adoc)→ `doc create` -- 追加内容 → `update --mode append` -- 整篇覆盖替换 → 先 `update --mode overwrite --dry-run` 预览,用户确认后再 `update --mode overwrite --yes` -- 指定父目录时,只有明确的文档文件夹 `nodeId` / `alidocs` 文件夹 URL 才能放入 `--folder`;上一步若只返回了纯数字 `dentryId`、`spaceId` 或 drive `parent-id`,不要把它传给 doc 的 `--folder` - -> 严禁把「创建表格」路由到 `doc create`: -> - 用户说"创建表格/新建表格/建个电子表格/在线表格" → 走 [`dws sheet create`](./sheet.md)(axls 在线电子表格) -> - 用户说"创建多维表格/新建 AI 表格/建 base/数据库表" → 走 [`dws aitable base create`](./aitable.md)(able 多维表格) - -用户说"新建脑图/白板/多维表/演示文稿": -- 用 `dws wiki node create --workspace --type ` 指定类型 (axls/amind/adraw/able/appt);`doc file create` 已弃用 -- 注意 `wiki node create` 必须带 `--workspace`(知识库上下文) - -用户说"建文件夹/新建目录": -- 个人空间/钉盘 → `dws drive mkdir --name "<名称>"` -- 知识库内 → `dws wiki node create --workspace --type folder`(`doc folder create` / `doc file create --type folder` 已弃用) - -用户说"上传文件/传文件/上传到文档/上传到知识库": -- 上传 → `upload`(需本地文件路径) -- 上传并转换 → `upload --convert` - -用户说"导入文件/导入为在线文档/导入 Word/导入 Excel/导入 xmind/导入 Markdown/把本地文件转在线文档": -- 导入并转换为在线文档 → `doc import --file <本地路径>` -- 支持 docx/doc/xlsx/xls/md/txt/xmind/mark,文件大小不超过 20MB -- 如果用户指定知识库或文件夹,补充 `--workspace` 或 `--folder` -- 不要把本地文件内容先读出来再 `doc create/update`;应直接使用 `doc import` - -用户说"下载/导出/下载到本地/导出文档/导出为Word/导出为docx/把文档导出来": -- **必须先判断目标文件类型**,再决定走 `doc export` 还是 `drive download`: - - 在线文档(alidocs/adoc)→ **`doc export`**(内容级命令,格式转换后导出为 docx,未迁移) - - 已有文件(PDF、图片、附件、视频等非在线文档)→ **`drive download`**(`doc download` 已弃用) -- 判断方法: - 1. 用户明确说"导出文档"/"导出为 Word/docx" → 直接走 `doc export` - 2. 用户明确说"下载 PDF/图片/附件" → 直接走 `drive download` - 3. 不确定时,先用 `doc info --node ` 查询节点信息,根据 `contentType` 判断: - - `contentType` = `ALIDOC` → 走 `doc export` - - `contentType` = `DOCUMENT`/`IMAGE`/`VIDEO` 等 → 走 `drive download` - -> **严禁将"导出文档"直接路由到 `drive download`**。`drive download` 只能下载已有文件(原样下载),`doc export` 是将在线文档格式转换后导出为 docx,两者完全不同。 - -用户说"复制文档/拷贝一份/复制到": -- 复制 → `copy` (需源 --node 和目标 --folder/--workspace) - -用户说"移动文档/搬到/移到/转移文件": -- 移动 → `move` (需源 --node 和目标 --folder/--workspace) - -用户说"重命名/rename/改名/改文档名/修改文档名称/修改文档标题/把这个文档叫做...": -- 重命名 → `doc rename`(需文档 ID 或 URL + 新名称) -- 只要意图是修改文档在列表和链接中展示的名称,统一路由到 `dws doc rename --node --name "新名称"`;不要走 `drive`、`doc update` 或重新 `doc create` -- 只有用户明确说"正文里的标题/章节标题/段落标题/H1 标题"时,才走 `block update` - -用户说"删除文档/删掉这个文件/移到回收站/丢掉这篇文档": -- 删除节点 → `delete`(危险操作,需 `--yes` 确认;需文档 ID 或 URL) - -用户说"插入附件/上传附件到文档/往文档里加文件/加附件": -- 插入附件 → `media insert`(需文档 ID 或 URL + 本地文件路径) - -用户说"插入图片/加图片/放张图/嵌入图片/往文档里插图": -- 插入图片 → `media insert`(需文档 ID 或 URL + 本地图片文件路径) -- 注意:图片也是通过 `media insert` 作为附件块插入文档,不是通过 `block insert` - -用户说"下载附件/获取附件/取出文档里的附件": -- 下载附件 → `media download`(需文档 ID 或 URL + 资源 ID) - -用户说"编辑块/改段落/插入标题/删除块": -- 查看结构 → `block list` -- 插入 → `block insert` -- 修改 → `block update` -- 删除 → `block delete` - -用户说"给某人开权限/分享给某人/授权某文档/把这篇文档给 xxx 看": -- 新增权限 → `permission add`(需 `--node` + `--user` + `--role`) -- 修改权限 → `permission update` -- 查看谁有权限 → `permission list` - -> **关键区分**: -> - "把**某篇文档**授权给某人" → `doc permission add`(节点级,包括「我的文档」下的文档都支持) -> - "把**某个知识库**整体授权给某人" → `wiki member add`(容器级,但**「我的文档」个人空间不支持**) - -> 补充:如果用户直接粘贴的是原始 `alidocs` URL,先按 [链接规范](../url-patterns.md#alidocs-url-类型探测流程) probe;只有 probe 确认是 `adoc` / `file` / `folder` 后,才继续按下列意图执行。 - -**用户直接粘贴文档 URL(无其他指令)**: -- 默认 → `read`(读取文档内容) -- 如 URL 明显是文件夹 → `drive list`(列出文件夹内容;doc list 已弃用) - -**用户粘贴 URL + 附加指令**: -- "帮我看看这个文档" → `read` -- "这个文档的信息" → `info` -- "往这个文档追加内容" → `update --mode append` -- "把这个文档标题改成 X" / "这个文档改名为 X" → `rename` -- "把正文里的一级标题/章节标题改成 X" → `block update` - -关键区分: doc(文档编辑/阅读) vs aitable(数据表格操作) vs drive(钉盘文件管理) - -用户说"上传文件/传文件/上传到文档/上传到知识库": -- 上传 → `upload`(需本地文件路径) -- 上传并转换 → `upload --convert` - -用户说"下载文件/导出文件/下载到本地": -- 下载 → `download`(需文件节点 ID 或 URL) - -用户说"编辑块/改段落/插入标题/删除块": -- 查看结构 → `block list` -- 插入 → `block insert` -- 修改 → `block update` -- 删除 → `block delete` - -**用户直接粘贴文档 URL(无其他指令)**: -- 默认 → `read`(读取文档内容) -- 如 URL 明显是文件夹 → `drive list`(列出文件夹内容;doc list 已弃用) - -**用户粘贴 URL + 附加指令**: -- "帮我看看这个文档" → `read` -- "这个文档的信息" → `info` -- "往这个文档追加内容" → `update --mode append` -- "编辑这个文档的标题" → `block update` - -关键区分: doc(文档编辑/阅读) vs aitable(数据表格操作) vs drive(钉盘文件管理) - -## 核心工作流 - -```bash -# ── 工作流 1: 浏览并阅读文档 ── - -# 1. 浏览我的文档/钉盘根目录(doc list 已弃用 → drive list) -dws drive list --format json - -# 2. 浏览子文件夹 -dws drive list --folder --format json - -# 3. 获取文档元信息 (标题、类型、权限) -dws doc info --node --format json - -# 4. 读取文档内容 (Markdown 格式) -dws doc read --node --format json - -# ── 工作流 2: 创建文档并写入内容 ── - -# 1. (可选) 创建文件夹 — 提取 nodeId(doc folder create 已弃用 → drive mkdir) -dws drive mkdir --name "项目资料" --format json - -# 2. 创建文档 — 提取 nodeId -dws doc create --name "项目周报" --folder --format json - -# 3. 写入内容 (追加模式) -dws doc update --node --content "# 本周总结\n\n- 完成了 A\n- 推进了 B" --mode append --format json - -# ── 工作流 3: 一步创建带内容的文档 ── - -dws doc create --name "会议纪要" --content "# 会议纪要\n\n## 议题\n\n1. ..." --format json - -# ── 工作流 4: 上传本地文件到钉钉文档/知识库 ── - -# 1. 上传到"我的文档"根目录 -dws doc upload --file ./report.pdf - -# 2. 上传到指定文件夹 -dws doc upload --file ./slides.pptx --name "Q1汇报.pptx" --folder - -# 3. 上传到知识库并转换为在线文档 -dws doc upload --file ./data.xlsx --workspace --convert - -# ── 工作流 5: 下载/导出文件到本地 ── -# 必须先用 info 判断文件类型,再决定用 drive download 还是 doc export: -# - contentType 为 ALIDOC(在线文档)→ 用 doc export -# - contentType 为 DOCUMENT/IMAGE/VIDEO 等(已有文件)→ 用 drive download - -# 步骤 1: 查询文件类型 -dws doc info --node --format json -# 根据返回的 contentType 字段判断: - -# 如果是已有文件 (非 ALIDOC),用 drive download(doc download 已弃用): -dws drive download --node --output ~/downloads/ - -# 如果是在线文档 (ALIDOC),用 doc export: -dws doc export --node --output ~/downloads/ - -# ── 工作流 5a: 导入本地文件为在线文档 ── - -# 导入到默认位置 -dws doc import --file ./report.docx --format json - -# 导入到指定文件夹 -dws doc import --file ./notes.md --folder --format json - -# 导入到知识库 -dws doc import --file ./data.xlsx --workspace --format json - -# 如果导入命令超时或中断,可用 import get 手动查询任务状态: -# dws doc import get --task-id - -# ── 工作流 6: 上传附件并插入文档 ── - -# media insert 自动完成三步流程: -# 1. 获取附件上传凭证 (get_doc_attachment_upload_info) -# 2. HTTP PUT 上传文件到 OSS -# 3. 插入附件块到文档 (insert_document_block) - -# 1. 基本用法: 插入本地文件到文档 -dws doc media insert --node --file ./report.pdf - -# 2. 指定附件显示名称 -dws doc media insert --node --file ./data.xlsx --name "Q1数据报表.xlsx" - -# 3. 指定 MIME 类型 (文件扩展名无法推断时) -dws doc media insert --node --file ./data.bin --name "导出数据.dat" --mime-type application/octet-stream - -# 4. 在指定块之前插入附件 -dws doc media insert --node --file ./image.png --ref-block --where before - -# 5. 完整流程: 创建文档 → 写入内容 → 插入附件 -dws doc create --name "项目报告" --content "# 项目报告\n\n以下为相关附件:" --format json -# 提取 nodeId 后: -dws doc media insert --node --file ./design.pdf -dws doc media insert --node --file ./timeline.xlsx --name "项目时间线.xlsx" - -# ── 工作流 7: 块级精细编辑 ── - -# 1. 查看文档块结构 — 获取 blockId -dws doc block list --node --format json - -# 2. 在文档末尾插入段落 -dws doc block insert --node --text "新增内容" - -# 3. 在指定块之前插入标题 -dws doc block insert --node --heading "新章节" --level 2 --ref-block --where before - -# 4. 更新某个块的内容 -dws doc block update --node --block-id --text "修改后的内容" - -# 5. 删除块 -dws doc block delete --node --block-id --yes - -# ── 工作流 8: 复制/移动/重命名文档 ── - -# 获取 nodeId 的三种方式(按场景选择,无需全部执行): -# 方式 A: 用户直接提供文档 URL — 直接传给 --node,无需额外查询 -# 方式 B: 搜索文档 — 从返回中提取 nodeId(doc search 已弃用 → drive search) -dws drive search --query "项目周报" --format json -# 方式 C: 浏览文件夹 — 从返回中提取 nodeId(doc list 已弃用 → drive list) -dws drive list --folder --format json -# 注意: 这里的 是文件夹 nodeId/dentryUuid 或文件夹 URL,不是数字 dentryId;示例统一使用 canonical --folder。 - -# 复制文档到指定文件夹(--node 支持 ID 或 URL) -dws doc copy --node --folder --format json - -# 复制到目标知识库根目录(不传 --folder 时) -dws doc copy --node --workspace --format json - -# 移动文档到指定文件夹 -dws doc move --node --folder --format json - -# 移动到目标知识库根目录(不传 --folder 时) -dws doc move --node --workspace --format json - -# 重命名文档 -dws doc rename --node --name "新名称" --format json - -# 删除文档到回收站(危险操作:必须先向用户确认,用户同意后才加 --yes 执行) -# 正确流程:1.向用户展示"即将删除「文档名」到回收站" → 2.等用户确认 → 3.执行下面命令 -dws doc delete --node --yes --format json - -# ── 工作流 9: 文档评论管理 ── - -# 1. 查看文档的所有评论 -dws doc comment list --node --format json - -# 2. 在文档上创建评论 -dws doc comment create --node --content "这里需要补充数据来源" --format json - -# 3. 创建评论并 @ 相关人 -# 先搜索用户: dws contact user search --query "张三" --format json → 提取 userId -# 再将 userId 传入 --mention -dws doc comment create --node --content "请确认这部分内容" --mention , --format json - -# 4. 更新某条评论(可选 --mention) -dws doc comment update --node --comment-key --content "已按最新数据修正" --format json - -# 5. 删除某条评论(不可恢复,必须先确认) -dws doc comment delete --node --comment-key --yes --format json - -# 6. 回复某条评论(commentKey 从 list 或 create 返回中获取) -dws doc comment reply --node --comment-key --content "已修改" --format json - -# 7. 用表情回复评论 -dws doc comment reply --node --comment-key --content "比心" --emoji --format json - -# 8. 创建划词评论(针对文档中某段选中文本) -# 先获取块列表: dws doc block list --node --format json → 提取 blockId 和文本内容 -# 确定选中文本在块内的起始偏移量 (start) 和结束偏移量 (end) -dws doc comment create-inline --node --block-id --start 0 --end 10 --content "这里需要修改" --format json - -# 9. 创建划词评论并附带引用原文 + @ 相关人 -dws doc comment create-inline --node --block-id --start 5 --end 20 --content "请确认这部分" --selected-text "被选中的原文内容" --mention , --format json - -# ── 工作流 10: 导出在线文档为 docx ── - -# 一条命令自动完成(提交→轮询→下载),无需手动编排 -dws doc export --node --output ./exported.docx - -# 如果导出命令超时或中断,可用 export get 手动查询任务状态: -# dws doc export get --job-id -``` - -## 上下文传递表 - -| 操作 | 从返回中提取 | 用于 | -|------|-------------|------| -| `drive list`(原 `doc list`,已弃用) | `nodes[].nodeId` / folder 类型 `nodeId` | read / info / update / block 的 --node;folder 用作 `--folder` | -| `drive search`(原 `doc search`,已弃用) | 文档 `nodeId` / URL / `createTime` / `creatorUid` | read / info / update 的 --node;创建时间与创建者信息 | -| `create` | `nodeId` | update / block 操作的 --node | -| `import` | `nodeId` / `documentUrl` / `documentName` / `documentType`;中断时提取 `taskId` | 后续 read / info / sheet 操作;中断后用 `doc import get --task-id` | -| `drive mkdir`(原 `doc folder create`,已弃用) | `nodeId` | create 的 --folder | -| `block list` | `blockId` | block insert 的 --ref-block, block update/delete 的 --block-id | -| `comment list` | `commentList[].commentKey` | comment reply/update/delete 的 --comment-key | -| `comment create` / `comment create-inline` | `commentKey` | comment reply/update/delete 的 --comment-key | -| `block list` | `blockId` + 文本内容 | comment create-inline 的 --block-id 及 --start/--end 计算 | -| `contact user search` | `userId` | comment create / create-inline / reply / update 的 --mention | -| `wiki node create`(原 `doc file create`,已弃用) | `nodeId` | 后续 read / update / block 操作的 --node(仅 adoc 支持 read/update,axls/amind 等类型用各自产品的命令) | -| `copy` / `move` | 新 `nodeId`(copy)或原 nodeId(move) | 后续 read / info 等的 --node | - -## nodeId 多格式说明 - -所有 `--node` 参数同时支持以下格式,系统自动识别: -- **文档 ID**: 字母数字字符串,如 `9E05BDRVQePjzLkZt2p2vE7kV63zgkYA` -- **文档 URL**: `https://alidocs.dingtalk.com/i/nodes/{dentryUuid}`,如 `https://alidocs.dingtalk.com/i/nodes/9E05BDRVQePjzLkZt2p2vE7kV63zgkYA` -- **文档链接(edit/preview)**: `https://alidocs.dingtalk.com/document/{edit|preview}?...&dentryKey={key}`(必须传入完整 URL,不要提取其中的 query 参数单独使用) - -以下命令效果相同: -```bash -dws doc read --node 9E05BDRVQePjzLkZt2p2vE7kV63zgkYA -dws doc read --node "https://alidocs.dingtalk.com/i/nodes/9E05BDRVQePjzLkZt2p2vE7kV63zgkYA" -dws doc read --node "https://alidocs.dingtalk.com/document/edit?dentryKey=wo1g3x54FzVEJ5yE" -dws doc read --node "https://alidocs.dingtalk.com/document/preview?cid=74993670680&type=d&docKey=Pd6l2Z7V8ZWydl7M&dentryKey=rBGBr2r1HmwanAGW" -``` - -> **注意**:`document/edit` 和 `document/preview` 格式 URL 中的 `dentryKey` 参数值不是合法的独立 nodeId,禁止提取后单独使用,必须传入完整 URL。URL 中可能包含 `utm_source`、`chInfo` 等追踪参数,无需手动去除,直接传入完整 URL 即可。 - -`--folder` 参数同样支持文件夹 URL 或 ID。 - -## 长 Markdown 写入 - -**核心规则**:含多行、表格、`\n` 或长度 >2KB 的 Markdown **必须**通过 `--content-file` 或 `--content -`(stdin)传入,禁止直接作为 `--content` 命令行字符串——shell escape 会破坏换行和表格,且命令行长度受限。 - -`dws doc create` 和 `dws doc update` 支持两种内容来源(`--content-file` 优先于 `--content`): - -| 形式 | 说明 | -|------|------| -| `--content "..."` | 字面量(仅推荐短文本 <2KB 且无换行/表格) | -| `--content -` | 从 stdin 读取(可配合 heredoc/pipe) | -| `--content-file path` | 从文件读取(UTF-8),推荐 | - -### 短/中等长度(< 200KB)— 单步创建 - -```bash -# 1. 把内容写入 UTF-8 文本文件: -# Linux/Mac: /tmp/.md;Windows: %TEMP%\.md -# 2. 一步创建+写入: -dws doc create --name "<文档名>" --content-file [--folder ] [--workspace ] -``` - -### 超长(> 200KB 兜底)— 创建空文档 + 分片追加 - -```bash -# 1. 创建空文档拿 nodeId -dws doc create --name "<文档名>" [--folder/--workspace] # → nodeId - -# 2. 按 markdown 标题或段落边界切成 ≤200KB 的片段(不要切断表格) -# 3. 逐个追加: -dws doc update --node --content-file --mode append -``` - -### stdin 变体 - -```bash -# pipe -cat report.md | dws doc update --node --content - --mode append - -# heredoc(真实换行,含表格) -dws doc update --node --mode append --content - <<'EOF' -## 追加段落 - -| 列1 | 列2 | -|---|---| -| a | b | -EOF -``` - -## 注意事项 - -- `export` 是一体化命令,一条命令自动完成提交→轮询→下载,**无需手动编排轮询**。CLI 内部使用渐进式退避轮询(最多约 5 分钟) -- `export` 超时或中断后,CLI 会输出 `jobId`,可用 `dws doc export get --job-id ` 手动查询任务状态 -- `export` 当前仅支持钉钉在线文档 (alidocs) 导出为 `docx`;当前开源 CLI 未暴露在线表格导出命令,在线表格数据读取走 `dws sheet range read --node ` -- `update --mode overwrite` 会**清空原内容后重写**,⚠️ 谨慎使用;必须先 `--dry-run` 预览并在用户确认后加 `--yes`;默认 `--mode append`(追加)更安全 -- `read` 返回 Markdown 格式的文档内容,仅限有"下载"权限的文档 -- `read` 返回的内容中,文档里的附件会以 OSS 临时下载链接形式给出(如 `https://alidocs2.oss-cn-zhangjiakou.aliyuncs.com/res/.../att/.ext?Expires=...`),该链接会过期。链接过期后,可从 URL 路径中提取 ``(即 `/att/` 后、扩展名前的 UUID 部分),然后使用 `media download --node --resource-id ` 重新获取下载链接 -- `create` 不传 `--folder` 和 `--workspace` 时,默认创建在"我的文档"根目录 -- `create` 只能建"文档"(adoc);要建表格/脑图/白板/多维表/演示,用 `dws wiki node create --workspace --type `(`doc file create` 已弃用);建普通文件夹用 `dws drive mkdir` -- `block list/insert/update/delete` 是块级精细编辑,适合结构化修改;简单内容追加建议用 `update --mode append` -- `block insert` 优先使用 `--text` 或 `--heading` 快捷方式;复杂块类型 (table, callout 等) 使用 `--element` JSON -- `--content` 参数中的换行必须使用**真实换行符**(即实际的换行字符,Unicode `U+000A`),而不是字面量字符串 `\n`(反斜杠加字母 n)。在通过程序或大模型构造此参数时,请确保字符串在发送前已正确反转义。如果传入的是两个字符的字面量 `\n`,所有内容将渲染在同一行,导致标题、段落和表格格式全部错乱。**含多行/表格/长文本时优先用 `--content-file path.md` 或 `--content -`(stdin),不经过 shell escape,换行和表格都保持原样**(详见下方「长 Markdown 写入」)。 -- 块类型包括: paragraph, heading, blockquote, callout, columns, orderedList, unorderedList, table, sheet, attachment, slot -- 关键区分: doc(文档内容级操作) vs wiki(知识库空间级管理) vs aitable(数据表格操作) vs drive(钉盘文件管理) -- wiki 是知识库容器,doc 是知识库中的文档内容;需要 `workspaceId` 时,先用 `dws wiki space list/search` 获取,再传给 doc 的 `--workspace` 参数 -- `doc upload vs drive upload`:用户提到"知识库/文档空间/workspace" → `doc upload`;提到"钉盘/网盘/我的文件" → `drive upload`;未明确目标时默认 `drive upload` -- `upload` 支持上传任意类型文件 (PDF、Office、图片等) 到钉钉文档空间或知识库;`--convert` 可将 Office 文件转换为钉钉在线文档 -- `upload` 是三步自动完成的流程 (获取凭证 → OSS 上传 → 提交入库),无需手动分步操作 -- `download` 是两步自动完成的流程 (获取下载链接 → HTTP GET 下载),支持自动推断文件名;`--output` 可指定文件路径或目录 -- `media insert` 是三步自动完成的流程 (获取附件上传凭证 → OSS 上传 → 插入附件块到文档),无需手动分步操作 -- `media insert` 的 `--mime-type` 可选,不指定时根据文件扩展名自动推断;支持常见文件类型 (PDF、Office、图片、视频、压缩包等) -- `media insert` 与 `upload` 的区别:`upload` 将文件上传到文档空间/知识库作为独立文件;`media insert` 将文件作为附件块插入到文档正文中 -- `media download` 用于获取文档正文中附件的临时下载链接,`--resource-id` 可通过 `block list` 返回的 attachment 块获取 -- `copy` 需要对源节点有"阅读"权限,且对目标文件夹有"编辑"权限 -- `move` 需要对源节点有"管理"权限,且对目标文件夹有"编辑"权限;移动后原位置的文档将不再存在 -- `rename` 需要对文档有"编辑"权限 -- `copy` / `move` 不传 `--folder` 时,`--workspace` 表示放到知识库根目录;两者都不传则回落到"我的文档" -- `comment create` 是全文评论;`comment create-inline` 是划词评论,必须先 `block list` 拿到 `blockId` 并确定 `--start` / `--end` 偏移(按块内纯文本字符算,从 0 开始) - -## 自动化脚本 - -| 脚本 | 场景 | 用法 | -|------|------|------| -| [doc_create_and_write.py](../../scripts/doc_create_and_write.py) | 创建文档并写入 Markdown 内容 | `python doc_create_and_write.py --name "周报" --content "# 本周总结"` | - -## 相关产品 - -- [aitable](./aitable.md) — 结构化数据表格(行列/字段/记录),不是富文本文档 -- [drive](./drive.md) — 钉盘文件存储/上传/下载,不是文档内容编辑 -- [report](./report.md) — 钉钉日志系统(日报/周报模版),不是在线文档 +模板列表、搜索与套用见 [doc-template.md](./doc/doc-template.md)。 diff --git a/skills/multi/dingtalk-doc/references/doc/doc-block.md b/skills/multi/dingtalk-doc/references/doc/doc-block.md index 04e97ac2..277e9829 100644 --- a/skills/multi/dingtalk-doc/references/doc/doc-block.md +++ b/skills/multi/dingtalk-doc/references/doc/doc-block.md @@ -1,10 +1,6 @@ # doc block(块级精细编辑:list / insert / update / delete) -> **前置条件(MUST READ):** 执行本命令前,必须先用 Read 工具读取以下文件: -> 1. [`../doc.md`](../doc.md) — 命令路由 + 场景索引 + 意图判断 + 工作流 -> 2. [`./style/doc-update-workflow.md`](./style/doc-update-workflow.md) — 改写流程(编辑形态优先级、JSONML validator 行为) -> 3. [`./format/doc-jsonml-cookbook.md`](./format/doc-jsonml-cookbook.md) — JSONML 范例(含 callout / 分栏 / 表格 / 标题等节点的完整命令) -> 4. [`./format/doc-jsonml-schema.md`](./format/doc-jsonml-schema.md) — JSONML 节点结构字段定义 +> **按需参考**:复杂或保真改写见 [更新工作流](./style/doc-update-workflow.md);涉及 callout、分栏、复杂表格等 JSONML 节点时再读取 [范例](./format/doc-jsonml-cookbook.md) 与 [结构定义](./format/doc-jsonml-schema.md)。 > > **同任务常配合**:[`doc-update.md`](./doc-update.md)(整篇 overwrite / 末尾追加纯文本)/ [`./format/doc-jsonml-cookbook.md`](./format/doc-jsonml-cookbook.md)(JSONML 复制范例) @@ -260,7 +256,6 @@ dws doc block insert --node --content-format jsonml --parent-block 命令名 → 单文件,按需加载子文档。复杂任务请优先看下方 §场景索引。 + +### 阅读 / 元信息 + +| 命令 | 用途 | 必填参数 | 详见 | +|------|------|----------|------| +| `doc info` | 文档元信息(含 contentType / extension) | `--node` | [`doc/doc-info.md`](./doc-info.md) | +| `doc read` | 读取正文(markdown 或 jsonml) | `--node` | [`doc/doc-read.md`](./doc-read.md) | + +### 创建 / 写入 / 块编辑 + +| 命令 | 用途 | 必填参数 | 详见 | +|------|------|----------|------| +| `doc create` | 创建文字文档(adoc) | `--name` | [`doc/doc-create.md`](./doc-create.md) | +| `doc update` | 整篇 / 段落级更新(markdown / jsonml) | `--node` `--mode` | [`doc/doc-update.md`](./doc-update.md) | +| `doc block list/insert/update/delete` | 块级精细编辑(含 JSONML 节点操作) | `--node` (+ `--block-id`) | [`doc/doc-block.md`](./doc-block.md) | + +### 附件 / 评论 / 导出 + +| 命令 | 用途 | 必填参数 | 详见 | +|------|------|----------|------| +| `doc media insert/download` | 附件 / 图片插入与下载 | `--node` `--file` 或 `--resource-id` | [`doc/doc-media.md`](./doc-media.md) | +| `doc comment list/create/reply/create-inline` | 文档评论与划词评论 | `--node` (+ ...) | [`doc/doc-comment.md`](./doc-comment.md) | +| `doc export` / `doc export get` | 在线文档导出 docx/markdown/pdf | `--node` `--output` | [`doc/doc-export.md`](./doc-export.md) | + +### 导入 + +| 命令 | 用途 | 必填参数 | 详见 | +|------|------|----------|------| +| `doc import` / `doc import get` | 本地文件导入为在线文档(docx→文档 / xlsx→表格 / xmind→脑图 / md→文档) | `--file` | [`doc/doc-import.md`](./doc-import.md) | + +### 版本管理 + +| 命令 | 用途 | 必填参数 | 详见 | +|------|------|----------|------| +| `doc version save` | 手动保存文档版本快照 | `--node` | 本节 | +| `doc version list` | 查看文档历史版本列表 | `--node` | 本节 | +| `doc version revert` | 回滚文档到指定版本 | `--node` `--version` `--yes` | 本节 | + +### 模板管理 + +| 命令 | 用途 | 必填参数 | 详见 | +|------|------|----------|------| +| `doc template list` | 获取文档模板列表 | 无 | 本节 | +| `doc template search` | 搜索文档模板 | `--query` | 本节 | +| `doc template apply` | 应用文档模板创建新文档 | `--template-id` | 本节 | + +#### 手动保存文档版本 + +``` +Usage: + dws doc version save [flags] +Example: + dws doc version save --node +Flags: + --node string 文档 ID 或 URL (必填) +``` + +> 仅支持 adoc 类型文档。保存后会创建一个 USER_SAVE 类型的版本记录。 + +#### 查看文档历史版本列表 + +``` +Usage: + dws doc version list [flags] +Example: + dws doc version list --node + dws doc version list --node --limit 10 +Flags: + --node string 文档 ID 或 URL (必填) + --limit int 返回版本数量上限 (可选) + --cursor string 分页游标 (可选) +``` + +#### 回滚文档到指定版本 + +> **CAUTION:** 不可逆操作 — 执行前必须向用户确认。 + +``` +Usage: + dws doc version revert [flags] +Example: + dws doc version revert --node --version 3 --yes +Flags: + --node string 文档 ID 或 URL (必填) + --version int 目标版本号 (必填,从 version list 获取) + --yes 跳过确认提示 (非交互终端必须传) +``` + +#### 获取文档模板列表 + +``` +Usage: + dws doc template list [flags] +Example: + dws doc template list + dws doc template list --source MY + dws doc template list --source PUBLIC + dws doc template list --limit 20 +Flags: + --source string 模板来源: MY(我的模版)/PUBLIC(公开模版),不传默认 MY + --limit int 返回数量上限 (可选) + --cursor string 分页游标 (可选) +``` + +#### 搜索文档模板 + +``` +Usage: + dws doc template search [flags] +Example: + dws doc template search --query "周报" + dws doc template search --query "会议纪要" --limit 10 + dws doc template search --query "项目" --source PUBLIC +Flags: + --query string 搜索关键词 (必填) + --source string 模板来源: MY(我的模版)/PUBLIC(公开模版),不传默认 MY + --limit int 返回数量上限 (可选) + --cursor string 分页游标 (可选) +``` + +#### 应用文档模板 + +``` +Usage: + dws doc template apply [flags] +Example: + dws doc template apply --template-id TPL_ID --name "我的周报" + dws doc template apply --template-id TPL_ID --name "项目方案" --folder FOLDER_ID +Flags: + --template-id string 模板 ID (必填,从 template list/search 获取) + --name string 新文档名称 (可选) + --folder string 目标文件夹 ID (可选) + --workspace string 知识库 ID (可选) +``` + +### 文件操作(已迁移,以下命令 deprecated) + +> **迁移提示**:文件管理命令已按产品领域架构重新归属,旧命令在过渡期内仍可使用,运行时会输出 deprecated 警告。 + +| 旧命令 | 推荐命令 | 详见 | +|--------|----------|------| +| `doc upload` | `dws drive upload` | [`drive.md`](../../../dingtalk-drive/references/drive.md) | +| `doc download` | `dws drive download` | [`drive.md`](../../../dingtalk-drive/references/drive.md) | +| `doc copy` | `dws drive copy` | [`drive.md`](../../../dingtalk-drive/references/drive.md) | +| `doc move` | `dws drive move` | [`drive.md`](../../../dingtalk-drive/references/drive.md) | +| `doc rename` | `dws drive rename` | [`drive.md`](../../../dingtalk-drive/references/drive.md) | +| `doc delete` | `dws drive delete` | [`drive.md`](../../../dingtalk-drive/references/drive.md) | +| `doc folder create` | `dws drive folder create` | [`drive.md`](../../../dingtalk-drive/references/drive.md) | +| `doc file create` | `dws wiki node create --type ` | [`wiki.md`](../../../dingtalk-wiki/references/wiki.md) | +| `doc permission *` | `dws drive permission *` | [`drive.md`](../../../dingtalk-drive/references/drive.md) | +| `doc list` | `dws drive list --workspace` / `dws wiki node list` | [`drive.md`](../../../dingtalk-drive/references/drive.md) / [`wiki.md`](../../../dingtalk-wiki/references/wiki.md) | +| `doc search` | `dws drive search` / `dws wiki node search` | [`drive.md`](../../../dingtalk-drive/references/drive.md) / [`wiki.md`](../../../dingtalk-wiki/references/wiki.md) | + +### 排版规范 / JSONML 参考 + +| 资源 | 用途 | 详见 | +|------|------|------| +| 创建工作流 | 标题、位置、骨架、回读校验 | [`doc/style/doc-create-workflow.md`](./style/doc-create-workflow.md) | +| 改写工作流 | 编辑形态优先级、分片 append、回读验收 | [`doc/style/doc-update-workflow.md`](./style/doc-update-workflow.md) | +| 排版规范 | 文档类型 / 骨架 / 元素边界 / 颜色语义 | [`doc/style/doc-style-guideline.md`](./style/doc-style-guideline.md) | +| JSONML 范例 | 所有节点类型的可复制命令 | [`doc/format/doc-jsonml-cookbook.md`](./format/doc-jsonml-cookbook.md) | +| JSONML 节点结构 | 字段定义 + JSON Schema | [`doc/format/doc-jsonml-schema.md`](./format/doc-jsonml-schema.md) | diff --git a/skills/multi/dingtalk-doc/references/doc/doc-comment.md b/skills/multi/dingtalk-doc/references/doc/doc-comment.md index 711b89a2..2c9407f4 100644 --- a/skills/multi/dingtalk-doc/references/doc/doc-comment.md +++ b/skills/multi/dingtalk-doc/references/doc/doc-comment.md @@ -1,8 +1,5 @@ # doc comment(文档评论:list / create / reply / update / delete / create-inline) -> **前置条件(MUST READ):** 执行本命令前,必须先用 Read 工具读取以下文件: -> 1. [`../doc.md`](../doc.md) — 命令路由 + 场景索引 + 意图判断 + 工作流 -> > **同任务常配合**:`dws contact user search`(查 `--mention` 用 userId)/ [`doc-block.md`](./doc-block.md)(划词评论必须先取 blockId 与 paragraph 文本) --- @@ -171,6 +168,5 @@ dws doc comment create-inline --node --block-id --start 5 -- ## 参考 -- [`../doc.md` §意图判断](../doc.md#意图判断)(如何路由到本命令族) - [`./doc-block.md`](./doc-block.md)(取 blockId 与块内文本以计算划词偏移) - `dws contact user search`(取 mention 用的 userId,跨产品命令) diff --git a/skills/multi/dingtalk-doc/references/doc/doc-context.md b/skills/multi/dingtalk-doc/references/doc/doc-context.md new file mode 100644 index 00000000..a5aa3972 --- /dev/null +++ b/skills/multi/dingtalk-doc/references/doc/doc-context.md @@ -0,0 +1,25 @@ +# 上下文传递表 + +## 上下文传递表 + +| 操作 | 从返回中提取 | 用于 | +|------|-------------|------| +| `drive search` / `wiki node search` | 文档 `nodeId` / URL | doc read / info / update 等所有 `--node` 入参 | + +| `drive list` / `wiki node list` | `nodes[].nodeId` | doc read / info / update / block 操作的 --node | +| `create` | `nodeId` | update / block 操作的 --node | +| `block list` | `blockId` | block insert 的 --ref-block, block update/delete 的 --block-id | +| `read --content-format jsonml` | `revision` | update --content-format jsonml 的 --revision(可选,并发检查时使用) | +| `media insert` | `resourceId` | 附件已插入文档,可通过 block list 查看附件块 | +| `media download` | 附件下载链接 `downloadUrl` | 下载文档中的附件资源 | +| `block list` | attachment 块的 `resourceId` | media download 的 --resource-id | +| `comment list` | `commentList[].commentKey` | comment reply 的 --comment-key | +| `comment create` | `commentKey` | comment reply 的 --comment-key | +| `comment create-inline` | `commentKey` | comment reply 的 --comment-key | +| `block list` | `blocks[].element.id` | comment create-inline 的 --block-id | +| `block list` | `blocks[].element.paragraph.text` | 计算 create-inline 的 --start / --end 偏移量 | +| `contact user search` | `userId` | comment create/reply/create-inline 的 --mention;drive permission 的 --user | +| `version list` | `version`(版本号) | version revert 的 --version | +| `version save` | 版本信息 | 确认保存成功 | +| `import` | `documentUrl` / `documentName` / `documentType` | 导入完成后的在线文档地址和名称 | +| `import` (中断) | `taskId` | `import get` 的 `--task-id` | diff --git a/skills/multi/dingtalk-doc/references/doc/doc-create.md b/skills/multi/dingtalk-doc/references/doc/doc-create.md index f3f311f3..89e00f50 100644 --- a/skills/multi/dingtalk-doc/references/doc/doc-create.md +++ b/skills/multi/dingtalk-doc/references/doc/doc-create.md @@ -1,18 +1,13 @@ # doc create(创建文档) -> **前置条件(MUST READ):** 执行本命令前,必须先用 Read 工具读取以下文件: -> 1. [`../doc.md`](../doc.md) — 命令路由 + 场景索引 + 意图判断 + 工作流 -> 2. [`./style/doc-create-workflow.md`](./style/doc-create-workflow.md) — 创建工作流(标题、位置、骨架、回读校验) -> 3. [`./style/doc-style-guideline.md`](./style/doc-style-guideline.md) — 排版规范(草稿元素清单、骨架样板) -> 4. [`./doc-update.md` §内容写入管道](./doc-update.md#内容写入管道createupdate-共用) — 长内容自动分片、`--content-file` vs `--content` 选择 -> 5. [`./format/doc-jsonml-cookbook.md`](./format/doc-jsonml-cookbook.md) — 仅当使用 `--content-format jsonml` 时必读 +> **按需参考**:长内容、目标文件夹或回读验收见 [创建工作流](./style/doc-create-workflow.md) 与 [内容写入管道](./doc-update.md);用户要求正式排版时读取 [样式规范](./style/doc-style-guideline.md);仅在使用 `--content-format jsonml` 时读取 [JSONML 范例](./format/doc-jsonml-cookbook.md)。 ## 创建路由前置判断(必看) > `dws doc create` 只能创建在线文字文档(adoc),**不要**用它承接所有「新建 xxx」请求。收到「创建/新建」类需求时,必须先按文件类型分流: > -> - 用户说「创建表格 / 新建表格 / 建个电子表格 / 在线表格 / 销售数据表」等 → 走 [`dws sheet create`](../sheet.md#创建钉钉表格文档)(钉钉在线电子表格 `axls`),**不要**走 `doc create` -> - 用户说「创建多维表格 / 新建 AI 表格 / 建个 base / 数据库表」等 → 走 [`dws aitable base create`](../aitable.md#创建-ai-表格)(多维表格 `able`),**不要**走 `doc create` +> - 用户说「创建表格 / 新建表格 / 建个电子表格 / 在线表格 / 销售数据表」等 → 走 [`dws sheet create`](../../../dingtalk-sheet/references/sheet.md#创建钉钉表格文档)(钉钉在线电子表格 `axls`),**不要**走 `doc create` +> - 用户说「创建多维表格 / 新建 AI 表格 / 建个 base / 数据库表」等 → 走 [`dws aitable base create`](../../../dingtalk-aitable/references/aitable.md#创建-ai-表格)(多维表格 `able`),**不要**走 `doc create` > - 用户说「创建文档 / 新建文档 / 写篇文档 / 会议纪要 / 周报 / 方案」等文字型内容 → 才走 `dws doc create` > > 一句话口诀:表格 → sheet/aitable;文档 → doc。 @@ -43,7 +38,7 @@ Flags: - **`--name` 是 H1**:正文从 `##` 开始;正文内不要再写 `#` 一级标题(除非确需且已说明动机)。 - 不传 `--folder` 和 `--workspace` 时,默认创建在「我的文档」根目录。 - `--folder` 仅接受文档文件夹 `nodeId` / `dentryUuid` / alidocs 文件夹 URL;**禁止**传入 drive `dentryId`、`parentId`、`spaceId` 这类纯数字 ID。 -- 输入方式选择见 [`./doc-update.md` §内容写入管道](./doc-update.md#内容写入管道createupdate-共用)(与 update 共用)。短文本字面量可 `--content`,多行/表格/特殊字符必须 `--content-file` 或 `--content -`。 +- 输入方式选择见 [`./doc-update.md` §内容写入管道](./doc-update.md)(与 update 共用)。短文本字面量可 `--content`,多行/表格/特殊字符必须 `--content-file` 或 `--content -`。 - 长内容(>30000 字符)CLI 自动分片:先创建空文档拿 `nodeId`,再按 markdown 标题边界切分后逐片 append;调用方无需手动编排。 ## 上下文传递 @@ -89,7 +84,6 @@ dws doc create --name "<文档名>" --content-file /tmp/.json --content-fo ## 参考 -- [`../doc.md` §意图判断](../doc.md#意图判断)(如何路由到本命令) - [`./doc-update.md`](./doc-update.md)(写入管道、长 markdown、追加段落、回读补救) - [`./style/doc-create-workflow.md`](./style/doc-create-workflow.md)(创建流程 + 回读验收) - [`./style/doc-style-guideline.md`](./style/doc-style-guideline.md)(草稿排版规范) diff --git a/skills/multi/dingtalk-doc/references/doc/doc-export.md b/skills/multi/dingtalk-doc/references/doc/doc-export.md index 0abbeedf..d7b3c723 100644 --- a/skills/multi/dingtalk-doc/references/doc/doc-export.md +++ b/skills/multi/dingtalk-doc/references/doc/doc-export.md @@ -1,13 +1,10 @@ -# doc export(在线文档导出为 docx) - -> **前置条件(MUST READ):** 执行本命令前,必须先用 Read 工具读取以下文件: -> 1. [`../doc.md`](../doc.md) — 命令路由 + 场景索引 + 意图判断 + 工作流 +# doc export(在线文档导出为 docx/markdown/pdf) > **路由前置判断**:用户说「下载/导出」时**必须**先用 [`./doc-info.md`](./doc-info.md) `info --node --format json` 查 `contentType`: > - `contentType` 为 `ALIDOC`(在线文档)→ **必须用 `export`**,禁止用 `download` -> - `contentType` 为 `DOCUMENT`/`IMAGE`/`VIDEO` 等(已有文件)→ 用 `dws drive download`(详见 [`../drive.md`](../drive.md)) +> - `contentType` 为 `DOCUMENT`/`IMAGE`/`VIDEO` 等(已有文件)→ 用 `dws drive download`(详见 [`../drive.md`](../../../dingtalk-drive/references/drive.md)) > -> `drive download` 只能下载**已有文件**(原样下载),`export` 是将**在线文档格式转换**后导出为 docx,两者完全不同。 +> `drive download` 只能下载**已有文件**(原样下载),`export` 是将**在线文档格式转换**后导出为 docx、markdown 或 pdf,两者完全不同。 --- @@ -19,10 +16,11 @@ Usage: Example: dws doc export --node "https://alidocs.dingtalk.com/i/nodes/xxx" --output ./exported.docx dws doc export --node --output ~/downloads/ + dws doc export --node --export-format markdown --output ./exported.md Flags: - --node string 要导出的文档标识,支持文档 URL 或 dentryUuid (必填) - --output string 本地保存路径,文件路径或目录 (必填) - --export-format string 导出格式,当前仅支持 docx (默认) + --node string 要导出的文档标识,支持文档 URL 或 dentryUuid (必填) + --output string 本地保存路径,文件路径或目录 (必填) + --export-format string 导出格式,支持 docx(默认)、markdown(或 md)和 pdf ``` CLI 内部自动完成:提交导出任务 → 渐进式退避轮询(最多约 5 分钟)→ 成功后自动下载文件。 @@ -47,8 +45,9 @@ Flags: - `export` 是一体化命令,一条命令自动完成提交→轮询→下载,**无需手动编排轮询**。CLI 内部使用渐进式退避轮询(最多约 5 分钟)。 - `export` 超时或中断后,CLI 会输出 `jobId`,可用 `dws doc export get --job-id ` 手动查询任务状态。 -- `export` 当前仅支持钉钉在线文档(alidocs,`contentType=ALIDOC`)导出为 `docx`,**在线表格导出请使用其他命令**。 -- `--output` 既可以是文件完整路径,也可以是目录(CLI 自动按文档名生成 `.docx`)。 +- `export` 支持钉钉在线文档(alidocs,`contentType=ALIDOC`)导出为 `docx`、`markdown` 或 `pdf`,**在线表格导出请使用其他命令**。 +- `--export-format` 支持 `docx`(默认)、`markdown`(或 `md`)和 `pdf` 三种格式。 +- `--output` 既可以是文件完整路径,也可以是目录(CLI 自动按文档名生成 `.docx`、`.md` 或 `.pdf`)。 ## 上下文传递 @@ -60,12 +59,24 @@ Flags: ## 常用模板 ```bash -# 一体化导出(最常用) +# 一体化导出为 docx(最常用) dws doc export --node --output ./exported.docx -# 输出到目录(自动按文档名命名) +# 导出为 markdown +dws doc export --node --export-format markdown --output ./exported.md + +# 使用 md 缩写导出为 markdown +dws doc export --node --export-format md --output ./exported.md + +# 导出为 pdf +dws doc export --node --export-format pdf --output ./exported.pdf + +# 输出到目录(自动按文档名命名,docx 格式) dws doc export --node --output ~/downloads/ +# 输出到目录(markdown 格式,自动生成 .md 扩展名) +dws doc export --node --export-format markdown --output ~/downloads/ + # alidocs URL 直传 dws doc export --node "https://alidocs.dingtalk.com/i/nodes/" --output ./exported.docx @@ -73,8 +84,17 @@ dws doc export --node "https://alidocs.dingtalk.com/i/nodes/" --output dws doc export get --job-id --format json ``` +## 输出规范 + +导出完成后的输出必须遵循以下规则: + +- **仅返回本地文件路径**:导出成功后,直接告知用户文件已保存到本地的具体路径 +- **禁止上传 CDN**:不要在导出完成后调用已下线的 `chat file upload` 或其他上传命令将文件上传到钉钉 CDN +- **禁止输出下载链接**:不得生成或展示 `down.dingtalk.com` 等 CDN 下载链接 +- 正确示例:`文档已导出到: ./文档名.docx` +- 错误示例:上传文件并展示 CDN 下载链接 + ## 参考 -- [`../doc.md` §意图判断](../doc.md#意图判断)(如何路由到本命令) - [`./doc-info.md`](./doc-info.md)(前置:判断 contentType=ALIDOC 才走 export) -- [`../drive.md`](../drive.md)(非 ALIDOC 文件用 `dws drive download`) +- [`../drive.md`](../../../dingtalk-drive/references/drive.md)(非 ALIDOC 文件用 `dws drive download`) diff --git a/skills/multi/dingtalk-doc/references/doc/doc-extra-index.md b/skills/multi/dingtalk-doc/references/doc/doc-extra-index.md new file mode 100644 index 00000000..9bd6c2c4 --- /dev/null +++ b/skills/multi/dingtalk-doc/references/doc/doc-extra-index.md @@ -0,0 +1,7 @@ +# 导入 / 版本 / 模板 + +| 命令 | 用途 | 详见 | +|---|---|---| +| `doc import` / `doc import get` | 本地文件导入为在线文档 | [`doc-import.md`](./doc-import.md) | +| `doc version save/list/revert` | 版本快照、历史列表、回滚 | [`doc-version.md`](./doc-version.md) | +| `doc template list/search/apply` | 模板查询与应用 | [`doc-template.md`](./doc-template.md) | 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 6ed912b2..bd4f2923 100644 --- a/skills/multi/dingtalk-doc/references/doc/doc-file-ops.md +++ b/skills/multi/dingtalk-doc/references/doc/doc-file-ops.md @@ -1,8 +1,5 @@ # doc 文件操作(upload / download / copy / move / rename / delete + folder create) -> **前置条件(MUST READ):** 执行本命令前,必须先用 Read 工具读取以下文件: -> 1. [`../doc.md`](../doc.md) — 命令路由 + 场景索引 + 意图判断 + 工作流 - > **弃用提示(文件管理命令正在迁移到 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`**(知识库内) @@ -222,7 +219,6 @@ dws doc folder create --name "项目资料" --workspace --format json ## 参考 -- [`../doc.md` §意图判断](../doc.md#意图判断)(如何路由到本命令族) - [`./doc-info.md`](./doc-info.md)(前置:判断 contentType 决定 download / export) - [`./doc-export.md`](./doc-export.md)(在线文档格式转换导出 docx) - [`./doc-media.md`](./doc-media.md)(文件作为附件块插入文档正文,与 upload 区分) diff --git a/skills/multi/dingtalk-doc/references/doc/doc-help-query.md b/skills/multi/dingtalk-doc/references/doc/doc-help-query.md new file mode 100644 index 00000000..31aec05d --- /dev/null +++ b/skills/multi/dingtalk-doc/references/doc/doc-help-query.md @@ -0,0 +1,25 @@ +# 查询命令帮助 + +## 查询命令帮助 + +当你不确定某个命令的具体参数、格式或可选项时,**优先执行 `--help` 查询**,不要猜测参数名或凭记忆编造。 + +```bash +# 查看 doc 下所有子命令 +dws doc --help + +# 查看具体命令的完整参数说明 +dws doc read --help +dws doc create --help +dws doc block insert --help + +# 查看子命令组下的所有命令 +dws doc block --help +dws doc media --help +``` + +规则: + +- 参数名不确定时 → 先 `--help`,再调用 +- 报错 "unknown flag" 时 → `--help` 确认正确的 flag 名称 +- 不确定某个功能是否存在时 → `dws doc --help` 查看命令列表 diff --git a/skills/multi/dingtalk-doc/references/doc/doc-import.md b/skills/multi/dingtalk-doc/references/doc/doc-import.md index bd6291de..ca9f4d19 100644 --- a/skills/multi/dingtalk-doc/references/doc/doc-import.md +++ b/skills/multi/dingtalk-doc/references/doc/doc-import.md @@ -1,53 +1,112 @@ -# 本地文件导入为在线文档 (doc import) +# doc import(本地文件导入为在线文档) -## 使用场景 +> **支持的文件格式**:docx, doc, xlsx, xls, md, txt, xmind, mark +> **文件大小限制**:20MB -用户说"导入 Word/Excel/Markdown/xmind 到钉钉文档"、"把本地文件转成在线文档"、"导入到知识库/文件夹"时,使用 `dws doc import`。 +--- -不要先读取文件内容再调用 `doc create` 或 `doc update`。`doc import` 会按文件格式走导入任务,保留更完整的原始结构。 +## doc import(一体化命令) -## 命令 - -```bash -dws doc import --file ./report.docx --format json -dws doc import --file ./notes.md --folder --format json -dws doc import --file ./data.xlsx --workspace --format json -dws doc import --file ./draft.md --name "项目周报" --format json +``` +Usage: + dws doc import [flags] +Example: + dws doc import --file ./report.docx + dws doc import --file ./notes.md --folder + dws doc import --file ./data.xlsx --workspace + dws doc import --file ./draft.md --name "项目周报" +Flags: + --file string 本地文件路径 (必填) + --folder string 目标文件夹 ID 或 URL (可选,与 --workspace 至少传一个时优先) + --workspace string 目标知识库 ID 或 URL (可选,不传时默认导入"知识库—我的文档"根目录) + --name string 导入后文档名称 (可选,默认取文件名不含扩展名) ``` -```bash -dws doc import get --task-id --format json +> 注意:`--folder` 接受文件夹的 dentryUuid(nodeId)或 alidocs 文件夹 URL。 +> 如果通过 `dws doc info` 获取信息,应使用返回的 `nodeId` 字段, +> 而非 `folderId`(后者是父文件夹 ID)。 +> 当 URL 指向的节点 `nodeType` 不是 "folder" 时,应提示用户检查链接。 + +CLI 内部自动完成:创建导入会话 → 上传文件到 OSS → 确认导入触发转换 → 渐进式退避轮询(最多约 5 分钟)。 +**只需一条命令,无需手动轮询。** + +--- + +## doc import get(手动兜底查询任务) + +``` +Usage: + dws doc import get [flags] +Example: + dws doc import get --task-id +Flags: + --task-id string 导入任务 ID (必填) ``` -## 参数 +仅在 `dws doc import` 超时或中断后,用于手动查询任务状态。通常不需要调用。 -| 参数 | 说明 | -|------|------| -| `--file` | 本地文件路径,必填 | -| `--folder` | 目标文件夹 ID 或 URL,可选 | -| `--workspace` | 目标知识库 ID 或 URL,可选 | -| `--name` / `-n` | 导入后的文档名称;不传时使用文件名 | -| `--task-id` | `import get` 查询导入任务时必填 | +## 关键说明 -支持格式:docx、doc、xlsx、xls、md、txt、xmind、mark。文件大小上限 20MB。 - -## 工作流 - -1. 确认本地文件存在且格式受支持。 -2. 如果用户指定目标知识库或文件夹,传 `--workspace` 或 `--folder`;未指定时导入到默认位置。 -3. 执行 `dws doc import --file ... --format json`。 -4. 正常情况下 CLI 会自动提交、上传并轮询导入任务。 -5. 如果命令超时或中断,从输出中提取 `taskId`,再执行 `dws doc import get --task-id --format json`。 +- `import` 是一体化命令,一条命令自动完成创建会话→上传→确认→轮询,**无需手动编排**。CLI 内部使用渐进式退避轮询(最多约 5 分钟)。 +- `import` 超时或中断后,CLI 会输出 `taskId`,可用 `dws doc import get --task-id ` 手动查询任务状态。 +- 支持的文件格式:docx, doc, xlsx, xls, md, txt, xmind, mark(共 8 种)。 +- 文件大小限制:20MB。超过限制时 CLI 会直接报错,不会发起网络请求。 +- `--folder` 和 `--workspace` 均不传时,默认导入到用户的"我的文档"根目录。 +- `--folder` 优先级高于 `--workspace`:两者都传时以 `--folder` 为准。 ## 上下文传递 -| 操作 | 从返回中提取 | 用于 | -|------|-------------|------| -| `doc import` | `nodeId` / `documentUrl` / `documentName` / `documentType` | 后续 `doc read` / `doc info` / `sheet` 操作 | -| `doc import` 中断 | `taskId` | `doc import get --task-id` 查询任务状态 | +| 从返回中提取 | 用于 | +|-------------|------| +| `documentUrl` | 导入完成后的在线文档地址(可直接分享给用户) | +| `documentName` | 导入后的文档名称 | +| `documentType` | 文档类型(DOC / SHEET / MIND) | +| 中断时返回的 `taskId` | `import get` 的 `--task-id` | -## 注意事项 +## 输出规范 -- `--folder` 和 `--workspace` 都是目标位置参数;用户明确指定文件夹时优先使用 `--folder`。 -- 导入 Excel 后通常得到在线表格,后续数据读取和编辑走 `dws sheet ...`。 -- 导入 Markdown 或 Word 后通常得到在线文档,后续内容读取和编辑走 `dws doc ...`。 +导入完成后的输出必须遵循以下规则: + +- **返回在线文档 URL**:导入成功后,直接告知用户文档已导入并提供在线文档链接 +- **输出格式**:`文件已导入为在线文档: ` +- 正确示例:`文件已导入为在线文档: https://alidocs.dingtalk.com/i/nodes/xxx` + +## 格式与文档类型映射 + +| 文件扩展名 | 导入后文档类型 | 说明 | +|-----------|--------------|------| +| `.docx` / `.doc` | DOC(文字文档) | Word 文档导入为在线文档 | +| `.xlsx` / `.xls` | SHEET(电子表格) | Excel 文件导入为在线表格 | +| `.md` / `.txt` | DOC(文字文档) | Markdown/纯文本导入为在线文档 | +| `.xmind` | MIND(脑图) | XMind 文件导入为在线脑图 | +| `.mark` | MIND(脑图) | Mark 格式导入为在线脑图 | + +## 常用模板 + +```bash +# 导入 Word 文档(最常用) +dws doc import --file ./report.docx --format json + +# 导入 Markdown 文件 +dws doc import --file ./notes.md --format json + +# 导入到指定文件夹 +dws doc import --file ./data.xlsx --folder --format json + +# 导入到知识库根目录 +dws doc import --file ./plan.docx --workspace --format json + +# 自定义文档名称 +dws doc import --file ./draft.md --name "项目方案" --format json + +# 导入 XMind 脑图文件 +dws doc import --file ./mindmap.xmind --format json +dws doc import --file ./mindmap.xmind --workspace --format json + +# 兜底:超时或中断后手动查任务 +dws doc import get --task-id --format json +``` + +## 参考 + +- [`./doc-info.md`](./doc-info.md)(前置:理解文档标识) diff --git a/skills/multi/dingtalk-doc/references/doc/doc-info-index.md b/skills/multi/dingtalk-doc/references/doc/doc-info-index.md new file mode 100644 index 00000000..766948e2 --- /dev/null +++ b/skills/multi/dingtalk-doc/references/doc/doc-info-index.md @@ -0,0 +1,7 @@ +# 阅读 / 元信息 + +| 命令 | 用途 | 详见 | +|---|---|---| +| `doc info` | 文档元信息(含 contentType / extension) | [`doc-info.md`](./doc-info.md) | +| `doc read` | 读取正文(markdown 或 jsonml) | [`doc-read.md`](./doc-read.md) | +| `doc list` | 已废弃,改用 `dws drive list` / `dws wiki node list` | [`drive.md`](../../../dingtalk-drive/references/drive.md) | diff --git a/skills/multi/dingtalk-doc/references/doc/doc-info.md b/skills/multi/dingtalk-doc/references/doc/doc-info.md index 49b7a682..685ff74b 100644 --- a/skills/multi/dingtalk-doc/references/doc/doc-info.md +++ b/skills/multi/dingtalk-doc/references/doc/doc-info.md @@ -1,8 +1,6 @@ # doc info(获取文档元信息 + URL 解析) -> **前置条件(MUST READ):** 执行本命令前,必须先用 Read 工具读取以下文件: -> 1. [`../doc.md`](../doc.md) — 命令路由 + 场景索引 + 意图判断 + 工作流 -> 2. [`../../url-patterns.md`](../../url-patterns.md) — 仅当用户原始 `alidocs` URL 需要 probe 时 +> 用户给出原始 `alidocs` URL 且需要 probe 时,按需读取 [URL 规则](../url-patterns.md)。 > > **同任务常配合**:`dws drive search` / `dws wiki node search`(先定位 nodeId)/ [`doc-read.md`](./doc-read.md)(确认是 ALIDOC 后读正文) @@ -27,8 +25,8 @@ Flags: | contentType | extension | 操作 | 命令 | |-------------|-----------|------|------| | ALIDOC | adoc | 在线获取 Markdown 内容 | `dws doc read --node ` | -| ALIDOC | axls | 在线读取表格数据 | `dws sheet get-all-sheets` → `dws sheet get-range` | -| ALIDOC | able | 在线查询多维表格记录 | `dws aitable get-tables` → `dws aitable query-records` | +| ALIDOC | axls | 在线读取表格数据 | `dws sheet list --node ` → `dws sheet range read --node --sheet-id ` | +| ALIDOC | able | 在线查询多维表格记录 | `dws aitable table list --base-id ` → `dws aitable record query --base-id --table-id ` | | 非 ALIDOC | — | **不支持在线分析** | 告知用户需下载到本地后查看 | **关键规则**:非 ALIDOC 类型文件(PDF/Word/图片/视频等)不支持在线分析,用户可以选择下载后本地查看。 @@ -37,7 +35,7 @@ Flags: 当用户输入包含钉钉文档 URL 时,**必须先识别并提取 DOC_ID**,再判断意图。 -补充:如果这是用户直接提供的原始 `alidocs` URL,必须先按 [链接规范](../../url-patterns.md#alidocs-url-类型探测流程) probe 一次确认真实类型,再判断是否继续走 `doc`。 +补充:如果这是用户直接提供的原始 `alidocs` URL,必须先按 [链接规范](../url-patterns.md#alidocs-url-类型探测流程) probe 一次确认真实类型,再判断是否继续走 `doc`。 ### 支持的 URL 格式 @@ -51,7 +49,7 @@ Flags: 1. 匹配 URL 中 `alidocs.dingtalk.com` 域名 2. 取 URL path 的最后一段作为 DOC_ID(去掉 query string 和 fragment) 3. 提取出的 DOC_ID 可直接用于所有 `--node` 参数,也可将完整 URL 传给 `--node`(CLI 会自动解析) -4. 对用户直接提供的原始 `alidocs` URL,先按 [链接规范](../../url-patterns.md#alidocs-url-类型探测流程) 执行 probe;只有 probe 确认是 `adoc` / `file` / `folder` 时,才继续走 `doc` +4. 对用户直接提供的原始 `alidocs` URL,先按 [链接规范](../url-patterns.md#alidocs-url-类型探测流程) 执行 probe;只有 probe 确认是 `adoc` / `file` / `folder` 时,才继续走 `doc` ## ID 边界与参数映射 @@ -144,7 +142,6 @@ dws doc info --node # 错误 ## 参考 -- [`../doc.md` §意图判断](../doc.md#意图判断)(如何路由到本命令) -- `dws drive search` / `dws wiki node search`(前置:定位 nodeId 的搜索入口,详见 [`../drive.md`](../drive.md) / [`../wiki.md`](../wiki.md)) +- `dws drive search` / `dws wiki node search`(前置:定位 nodeId 的搜索入口,详见 [`../drive.md`](../../../dingtalk-drive/references/drive.md) / [`../wiki.md`](../../../dingtalk-wiki/references/wiki.md)) - [`./doc-read.md`](./doc-read.md)(contentType=ALIDOC + extension=adoc 的后续命令) -- [`../../url-patterns.md`](../../url-patterns.md)(用户原始 alidocs URL 的 probe 流程) +- [`../../url-patterns.md`](../url-patterns.md)(用户原始 alidocs URL 的 probe 流程) diff --git a/skills/multi/dingtalk-doc/references/doc/doc-intent.md b/skills/multi/dingtalk-doc/references/doc/doc-intent.md new file mode 100644 index 00000000..04300157 --- /dev/null +++ b/skills/multi/dingtalk-doc/references/doc/doc-intent.md @@ -0,0 +1,136 @@ +# 意图判断 + +## 意图判断 + +用户说"找文档/搜文档/最近文档": + +- 全局搜索 → `dws drive search --query "<关键词>"`(聚合钉盘+文档空间) +- 空间内搜索 → `dws wiki node search --workspace --keyword "<关键词>"` +- 遍历文件夹 → `dws drive list --workspace ` 或 `dws wiki node list --workspace ` +- 最近访问/最近编辑 → `dws drive recent`(见 [`drive.md`](../../../dingtalk-drive/references/drive.md)) + +用户说"看文档/读内容/文档内容": + +- 读取 → `read`(需文档 ID 或 URL) +- 元信息 → `info` + +用户说"写文档/创建文档": + +- 新建文字文档(adoc)→ `doc create` +- 追加内容 → `update --mode append` +- 覆盖替换 → `update --mode overwrite` +- 指定父目录时,只有明确的文档文件夹 `nodeId` / `alidocs` 文件夹 URL 才能放入 `--folder`;上一步若只返回了纯数字 `dentryId`、`spaceId` 或 drive `parent-id`,不要把它传给 doc 的 `--folder` + +> 严禁把「创建表格」路由到 `doc create`: +> +> - 用户说"创建表格/新建表格/建个电子表格/在线表格" → 走 [`dws sheet create`](../../../dingtalk-sheet/references/sheet.md#创建钉钉表格文档)(axls 在线电子表格) +> - 用户说"创建多维表格/新建 AI 表格/建 base/数据库表" → 走 [`dws aitable base create`](../../../dingtalk-aitable/references/aitable.md#创建-ai-表格)(able 多维表格) + +用户说"建文件夹/新建目录": + +- 创建 → `dws drive folder create` + +用户说"上传文件/传文件/上传到文档/上传到知识库": + +- 上传 → `dws drive upload`(需本地文件路径) + +用户说"下载/导出/下载到本地/导出文档/导出为Word/导出为docx/把文档导出来": + +- **必须先判断目标文件类型**,再决定走 `export` 还是 `download`: + - 在线文档 (alidocs/adoc) → **`doc export`**(导出是内容层操作,仅对 adoc 有意义) + - 已有文件(PDF、图片、附件、视频等非在线文档) → **`dws drive download`** +- 判断方法: + 1. 如果用户明确说了"导出文档"、"导出为Word/docx" → 直接走 `doc export` + 2. 如果用户明确说了"下载PDF/图片/附件" → 直接走 `drive download` + 3. 不确定时,先用 `drive info --node ` 查询节点信息,根据返回的 `contentType` 字段判断: + - `contentType` 为 `ALIDOC` → 走 `doc export` + - `contentType` 为 `DOCUMENT`/`IMAGE`/`VIDEO` 等 → 走 `drive download` + +> **严禁将"导出文档"直接路由到 `download`**。`download` 只能下载已有文件(原样下载),`export` 是将在线文档格式转换后导出为 docx 或 markdown 或 pdf,两者完全不同。 + +用户说"导入文件/导入docx/导入Word/导入Excel/导入脑图/导入xmind/导入思维导图/把文件转成文档/文件转在线文档/导入到知识库/打开本地文件": + +- **直接调用 `dws doc import --file <文件路径>`**,一条命令自动完成上传+转换+创建在线文档,**无需先读取文件内容再手动创建** +- 支持格式与映射:docx/doc→文档, xlsx/xls→表格, xmind/mark→脑图, md/txt→文档 +- 文件大小限制:20MB +- 导入后会自动转换为钉钉在线文档,返回在线文档 URL + +> **严禁先 Read 文件再 `doc create` + `doc update`**。`doc import` 是一体化命令,服务端负责格式转换,客户端无需解析文件内容。 + +用户说"复制文档/拷贝文件/复制到": + +- 复制 → `dws drive copy` + +用户说"移动文档/搬到/移到/转移文件": + +- 移动 → `dws drive move` + +用户说"重命名/rename/改名/改文档名/修改文档名称/修改文档标题/把这个文档叫做...": + +- 重命名 → `dws drive rename --node --name "新名称"` +- 只有用户明确说"正文里的标题/章节标题/段落标题/H1 标题"时,才走 `block update`。 + +用户说"删除文档/删掉这个文件/移到回收站/丢掉这篇文档": + +- 删除节点 → `dws drive delete` + +用户说"插入附件/上传附件到文档/往文档里加文件/加附件": + +- 插入附件 → `media insert`(需文档 ID 或 URL + 本地文件路径) + +用户说"插入图片/加图片/放张图/嵌入图片/往文档里插图": + +- 插入图片 → `media insert`(需文档 ID 或 URL + 本地图片文件路径) +- 注意:图片也是通过 `media insert` 作为附件块插入文档,不是通过 `block insert` + +用户说"下载附件/获取附件/取出文档里的附件": + +- 下载附件 → `media download`(需文档 ID 或 URL + 资源 ID) + +用户说"编辑块/改段落/插入标题/删除块": + +- 查看结构 → `block list` +- 插入 → `block insert` +- 修改 → `block update` +- 删除 → `block delete` + +用户说"保存版本/创建版本快照/手动保存一下版本": + +- 保存版本 → `version save`(需文档 ID 或 URL) + +用户说"版本历史/版本列表/查看修改历史/历史版本": + +- 查看版本列表 → `version list`(需文档 ID 或 URL) + +用户说"回滚版本/恢复到某个版本/版本回退/回退到之前的版本": + +- 回滚到指定版本 → `version revert`(需文档 ID 或 URL + 版本号,危险操作需确认) + +用户说"给某人开权限/分享给某人/授权某文档/把这篇文档给 xxx 看": + +- 新增权限 → `dws drive permission add` +- 修改权限 → `dws drive permission update` +- 查看谁有权限 → `dws drive permission list` +- 移除权限 → `dws drive permission remove` + +> **关键区分**: +> +> - "把**某篇文档**授权给某人" → `drive permission add`(节点级,包括「我的文档」下的文档都支持) +> - "把**某个知识库**整体授权给某人" → `wiki member add`(容器级,但**「我的文档」个人空间不支持**) + +> 补充:如果用户直接粘贴的是原始 `alidocs` URL,先按 [链接规范](../url-patterns.md#alidocs-url-类型探测流程) probe;只有 probe 确认是 `adoc` / `file` / `folder` 后,才继续按下列意图执行。 + +**用户直接粘贴文档 URL(无其他指令)**: + +- 默认 → `read`(读取文档内容) +- 如 URL 明显是文件夹 → `list`(列出文件夹内容) + +**用户粘贴 URL + 附加指令**: + +- "帮我看看这个文档" → `read` +- "这个文档的信息" → `info` +- "往这个文档追加内容" → `update --mode append` +- "把这个文档标题改成 X" / "这个文档改名为 X" → `dws drive rename` +- "把正文里的一级标题/章节标题改成 X" → `block update` + +关键区分: doc(文档编辑/阅读) vs aitable(数据表格操作) vs drive(钉盘文件管理) diff --git a/skills/multi/dingtalk-doc/references/doc/doc-media-index.md b/skills/multi/dingtalk-doc/references/doc/doc-media-index.md new file mode 100644 index 00000000..2c5c0aa7 --- /dev/null +++ b/skills/multi/dingtalk-doc/references/doc/doc-media-index.md @@ -0,0 +1,7 @@ +# 附件 / 评论 / 导出 + +| 命令 | 用途 | 详见 | +|---|---|---| +| `doc media insert/download` | 附件 / 图片插入与下载 | [`doc-media.md`](./doc-media.md) | +| `doc comment list/create/reply/create-inline` | 文档评论与划词评论 | [`doc-comment.md`](./doc-comment.md) | +| `doc export` / `doc export get` | 在线文档导出 docx/markdown/pdf | [`doc-export.md`](./doc-export.md) | diff --git a/skills/multi/dingtalk-doc/references/doc/doc-media.md b/skills/multi/dingtalk-doc/references/doc/doc-media.md index 3be8600b..0771ec2b 100644 --- a/skills/multi/dingtalk-doc/references/doc/doc-media.md +++ b/skills/multi/dingtalk-doc/references/doc/doc-media.md @@ -1,8 +1,5 @@ # doc media(附件 / 图片:download / insert) -> **前置条件(MUST READ):** 执行本命令前,必须先用 Read 工具读取以下文件: -> 1. [`../doc.md`](../doc.md) — 命令路由 + 场景索引 + 意图判断 + 工作流 - > ⚠️ **图片插入硬规则**: > - 图片来源如果是钉盘/文档空间中的文件,**必须先下载到本地**(`dws drive download --node <图片nodeId> --output /tmp/xxx.png`),再执行 `media insert` > - **禁止**把钉盘/文档节点 URL(如 `alidocs.dingtalk.com/i/nodes/...`)写进 Markdown `![](...)` 图片语法——这些是页面链接,不是可渲染的图片资源 @@ -92,7 +89,6 @@ dws doc media download --node --resource-id ## 参考 -- [`../doc.md` §意图判断](../doc.md#意图判断)(如何路由到本命令族) - [`./doc-block.md`](./doc-block.md)(block list 取 attachment 的 resourceId) -- [`../drive.md`](../drive.md)(独立文件上传:`dws drive upload`) +- [`../drive.md`](../../../dingtalk-drive/references/drive.md)(独立文件上传:`dws drive upload`) - [`./style/doc-style-guideline.md` §4.9 附件与图片](./style/doc-style-guideline.md)(图示与附件使用规范) diff --git a/skills/multi/dingtalk-doc/references/doc/doc-permission.md b/skills/multi/dingtalk-doc/references/doc/doc-permission.md index 647c5f24..48e6a223 100644 --- a/skills/multi/dingtalk-doc/references/doc/doc-permission.md +++ b/skills/multi/dingtalk-doc/references/doc/doc-permission.md @@ -1,8 +1,5 @@ # doc permission(文档权限:add / update / list) -> **前置条件(MUST READ):** 执行本命令前,必须先用 Read 工具读取以下文件: -> 1. [`../doc.md`](../doc.md) — 命令路由 + 场景索引 + 意图判断 + 工作流 - > **关键区分**: > - "把**某篇文档**授权给某人" → `doc permission add`(节点级,包括「我的文档」下的文档都支持) > - "把**某个知识库**整体授权给某人" → `wiki member add`(容器级,但**「我的文档」个人空间不支持**) @@ -112,6 +109,5 @@ dws doc permission add --node "https://alidocs.dingtalk.com/i/nodes/" ## 参考 -- [`../doc.md` §意图判断](../doc.md#意图判断)(如何路由到本命令族) - `dws contact user search`(取 userId 的入口,跨产品命令) - [`wiki.md`](../../../dingtalk-wiki/references/wiki.md)(知识库容器级授权 `wiki member add`,与本节点级权限相区分) diff --git a/skills/multi/dingtalk-doc/references/doc/doc-prerequisites.md b/skills/multi/dingtalk-doc/references/doc/doc-prerequisites.md new file mode 100644 index 00000000..f1dc5358 --- /dev/null +++ b/skills/multi/dingtalk-doc/references/doc/doc-prerequisites.md @@ -0,0 +1,15 @@ +# 文档参考选择 + +只读取当前操作需要的参考,不要从命令 leaf 反向加载聚合 router: + +| 场景 | 参考 | +|---|---| +| 解析 URL、确认 nodeId 或文档类型 | [doc-info.md](./doc-info.md) | +| 创建普通 Markdown 文档 | [doc-create.md](./doc-create.md) | +| 长内容创建、分片或正文完整性验收 | [doc-create-workflow.md](./style/doc-create-workflow.md) | +| 追加、覆盖或并发安全更新 | [doc-update.md](./doc-update.md) | +| 块级精修 | [doc-block.md](./doc-block.md) | +| 正式排版或统一风格 | [doc-style-guideline.md](./style/doc-style-guideline.md) | +| JSONML、callout、分栏或复杂表格 | [doc-jsonml-cookbook.md](./format/doc-jsonml-cookbook.md) | + +阅读、评论、权限、附件和导出等场景直接选择对应命令参考,无需先加载创建/更新工作流。 diff --git a/skills/multi/dingtalk-doc/references/doc/doc-read.md b/skills/multi/dingtalk-doc/references/doc/doc-read.md index 93bdcf26..c3a3a72b 100644 --- a/skills/multi/dingtalk-doc/references/doc/doc-read.md +++ b/skills/multi/dingtalk-doc/references/doc/doc-read.md @@ -1,8 +1,6 @@ # doc read(读取文档内容) -> **前置条件(MUST READ):** 执行本命令前,必须先用 Read 工具读取以下文件: -> 1. [`../doc.md`](../doc.md) — 命令路由 + 场景索引 + 意图判断 + 工作流 -> 2. [`./format/doc-jsonml-cookbook.md`](./format/doc-jsonml-cookbook.md) — 仅当使用 `--content-format jsonml` 时必读 +> 仅当需要解释或编辑 JSONML 结构时,按需读取 [JSONML 范例](./format/doc-jsonml-cookbook.md)。 > > **同任务常配合**:[`doc-info.md`](./doc-info.md)(先解析 URL,确认 contentType=ALIDOC、extension=adoc)/ [`doc-update.md`](./doc-update.md)(读后改写)/ [`doc-block.md`](./doc-block.md)(块级精修前先读结构) @@ -77,7 +75,6 @@ dws doc read --node --content-format jsonml --output /tmp/doc.json ## 参考 -- [`../doc.md` §意图判断](../doc.md#意图判断)(如何路由到本命令) - [`./doc-info.md`](./doc-info.md)(前置:判断 contentType / extension) - [`./doc-update.md`](./doc-update.md)(读后改写) - [`./format/doc-jsonml-cookbook.md`](./format/doc-jsonml-cookbook.md) / [`./format/doc-jsonml-schema.md`](./format/doc-jsonml-schema.md)(JSONML 节点结构) diff --git a/skills/multi/dingtalk-doc/references/doc/doc-related.md b/skills/multi/dingtalk-doc/references/doc/doc-related.md new file mode 100644 index 00000000..63ac1f27 --- /dev/null +++ b/skills/multi/dingtalk-doc/references/doc/doc-related.md @@ -0,0 +1,8 @@ +# 相关产品 + +## 相关产品 + +- [wiki](../../../dingtalk-wiki/references/wiki.md) — 知识库空间级管理(创建/查询/列出/搜索知识库),doc 中的文档存储在 wiki 知识库中 +- [aitable](../../../dingtalk-aitable/references/aitable.md) — 结构化数据表格(行列/字段/记录),不是富文本文档 +- [drive](../../../dingtalk-drive/references/drive.md) — 钉盘文件存储/上传/下载,不是文档内容编辑 +- [report](../../../dingtalk-report/references/report.md) — 钉钉日志系统(日报/周报模版),不是在线文档 diff --git a/skills/multi/dingtalk-doc/references/doc/doc-scenarios.md b/skills/multi/dingtalk-doc/references/doc/doc-scenarios.md new file mode 100644 index 00000000..b82bd13d --- /dev/null +++ b/skills/multi/dingtalk-doc/references/doc/doc-scenarios.md @@ -0,0 +1,22 @@ +# 场景索引 + +## 场景索引 + +> 任务驱动的入口:知道任务但不确定要读哪些命令文件时,按本表**一次性 Read 文件组**,不要逐个跳。 + +| 任务场景 | 一次性读取 | 主命令 | +|---------|-----------|--------| +| 定位 nodeId / URL 解析 | [`doc-info.md`](./doc-info.md)(搜索请用 `dws drive search` / `dws wiki node search`;遍历请用 `dws drive list` / `dws wiki node list`) | `drive search` / `wiki node search` | +| 阅读已有文档 | [`doc-info.md`](./doc-info.md) + [`doc-read.md`](./doc-read.md) | read | +| 创建新文档 | [`doc-create.md`](./doc-create.md) + [`doc-update.md`](./doc-update.md)(写入管道)+ [`style/doc-create-workflow.md`](./style/doc-create-workflow.md) + [`style/doc-style-guideline.md`](./style/doc-style-guideline.md) | create | +| 创建文档且包含图片/截图/图文并茂 | [`doc-create.md`](./doc-create.md) + [`doc-media.md`](./doc-media.md) + [`style/doc-create-workflow.md`](./style/doc-create-workflow.md) + [`style/doc-style-guideline.md`](./style/doc-style-guideline.md) | create → media insert | +| 局部改写 / 段落替换(保真) | [`doc-read.md`](./doc-read.md) + [`doc-update.md`](./doc-update.md) + [`doc-block.md`](./doc-block.md) + [`format/doc-jsonml-cookbook.md`](./format/doc-jsonml-cookbook.md) + [`style/doc-update-workflow.md`](./style/doc-update-workflow.md) | block update | +| 整篇 overwrite(可选并发检查) | [`doc-read.md`](./doc-read.md) + [`doc-update.md`](./doc-update.md) + [`format/doc-jsonml-schema.md`](./format/doc-jsonml-schema.md) + [`style/doc-update-workflow.md`](./style/doc-update-workflow.md) | update | +| 插入富 block(callout / 分栏 / 表格) | [`doc-block.md`](./doc-block.md) + [`format/doc-jsonml-cookbook.md`](./format/doc-jsonml-cookbook.md) + [`style/doc-style-guideline.md`](./style/doc-style-guideline.md) | block insert | +| 上传图片 / 附件 | [`doc-media.md`](./doc-media.md) | media insert | +| 评论 / 划词评论(含 @人) | [`doc-comment.md`](./doc-comment.md)(+ `dws contact user search` 取 mention 用 userId) | comment create | +| 文档分享 / 节点级权限 | [`drive.md`](../../../dingtalk-drive/references/drive.md)(已迁移:`dws drive permission add/update/list/remove`) | `drive permission` | +| 导出 PDF / DOCX / Markdown | [`doc-info.md`](./doc-info.md) + [`doc-export.md`](./doc-export.md) | export | +| 导入本地文件为在线文档 | [`doc-info.md`](./doc-info.md) + [`doc-import.md`](./doc-import.md) | import | +| 文件下载 / 上传 / 移动 / 重命名 / 复制 | [`drive.md`](../../../dingtalk-drive/references/drive.md)(已迁移:`dws drive upload/download/copy/move/rename/delete`) | `drive *` | +| 版本管理(保存/列出/回滚) | 本文件 §版本管理 | `version save/list/revert` | diff --git a/skills/multi/dingtalk-doc/references/doc/doc-template.md b/skills/multi/dingtalk-doc/references/doc/doc-template.md new file mode 100644 index 00000000..840a0f83 --- /dev/null +++ b/skills/multi/dingtalk-doc/references/doc/doc-template.md @@ -0,0 +1,54 @@ +# 模板管理 + +| 命令 | 用途 | 必填参数 | +|---|---|---| +| `doc template list` | 获取文档模板列表 | 无 | +| `doc template search` | 搜索文档模板 | `--query` | +| `doc template apply` | 应用文档模板创建新文档 | `--template-id` | + +#### 获取文档模板列表 + +``` +Usage: + dws doc template list [flags] +Example: + dws doc template list + dws doc template list --source MY + dws doc template list --source PUBLIC + dws doc template list --limit 20 +Flags: + --source string 模板来源: MY(我的模版)/PUBLIC(公开模版),不传默认 MY + --limit int 返回数量上限 (可选) + --cursor string 分页游标 (可选) +``` + +#### 搜索文档模板 + +``` +Usage: + dws doc template search [flags] +Example: + dws doc template search --query "周报" + dws doc template search --query "会议纪要" --limit 10 + dws doc template search --query "项目" --source PUBLIC +Flags: + --query string 搜索关键词 (必填) + --source string 模板来源: MY(我的模版)/PUBLIC(公开模版),不传默认 MY + --limit int 返回数量上限 (可选) + --cursor string 分页游标 (可选) +``` + +#### 应用文档模板 + +``` +Usage: + dws doc template apply [flags] +Example: + dws doc template apply --template-id TPL_ID --name "我的周报" + dws doc template apply --template-id TPL_ID --name "项目方案" --folder FOLDER_ID +Flags: + --template-id string 模板 ID (必填,从 template list/search 获取) + --name string 新文档名称 (可选) + --folder string 目标文件夹 ID (可选) + --workspace string 知识库 ID (可选) +``` diff --git a/skills/multi/dingtalk-doc/references/doc/doc-update.md b/skills/multi/dingtalk-doc/references/doc/doc-update.md index 34d1097b..cd105b4f 100644 --- a/skills/multi/dingtalk-doc/references/doc/doc-update.md +++ b/skills/multi/dingtalk-doc/references/doc/doc-update.md @@ -1,10 +1,6 @@ # doc update(更新文档内容) -> **前置条件(MUST READ):** 执行本命令前,必须先用 Read 工具读取以下文件: -> 1. [`../doc.md`](../doc.md) — 命令路由 + 场景索引 + 意图判断 + 工作流 -> 2. [`./style/doc-update-workflow.md`](./style/doc-update-workflow.md) — 改写流程(编辑形态优先级、分片 append、回读验收) -> 3. [`./style/doc-style-guideline.md`](./style/doc-style-guideline.md) — 排版规范 -> 4. [`./format/doc-jsonml-cookbook.md`](./format/doc-jsonml-cookbook.md) — 仅当使用 `--content-format jsonml` 时必读 +> **按需参考**:复杂改写、分片或保真编辑见 [更新工作流](./style/doc-update-workflow.md);用户要求统一排版时见 [样式规范](./style/doc-style-guideline.md);仅在使用 `--content-format jsonml` 时读取 [JSONML 范例](./format/doc-jsonml-cookbook.md)。 > > **同任务常配合**:[`doc-read.md`](./doc-read.md)(改写前必读,jsonml 模式拿当前结构;担心被并发覆盖时再取 revision)/ [`doc-block.md`](./doc-block.md)(单 block 改写优先;本命令更适合追加 / 整篇 overwrite) @@ -170,7 +166,7 @@ dws doc update --node --content-file --mode overwrite --content-f dws doc update --node --content-file --mode append --content-format markdown ``` -> **注意**:分块 append 存在静默失败风险(部分片段返回 success 但实际未写入),执行前**必须**向用户发出截断风险提示并等待确认。完整规范见 [`../../best_practices/04-document.md` «分块 append 截断风险提示»](../../best_practices/04-document.md)。 +> **注意**:分块 append 存在静默失败风险(部分片段返回 success 但实际未写入),执行前**必须**向用户发出截断风险提示并等待确认。完整规范见 [`../../best_practices/04-document.md` «分块 append 截断风险提示»](../04-document.md)。 ### stdin 变体 @@ -228,7 +224,6 @@ EOF ## 参考 -- [`../doc.md` §意图判断](../doc.md#意图判断)(如何路由到本命令) - [`./doc-read.md`](./doc-read.md)(改写前必读;jsonml 模式拿当前结构,担心并发时再取 revision) - [`./doc-block.md`](./doc-block.md)(单 block 改写更精准) - [`./style/doc-update-workflow.md`](./style/doc-update-workflow.md)(编辑形态优先级、分片 append 风险、回读验收) diff --git a/skills/multi/dingtalk-doc/references/doc/doc-uri.md b/skills/multi/dingtalk-doc/references/doc/doc-uri.md new file mode 100644 index 00000000..2305b7b9 --- /dev/null +++ b/skills/multi/dingtalk-doc/references/doc/doc-uri.md @@ -0,0 +1,10 @@ +# 文档地址 (URI) + +## 文档地址 (URI) + +| 资源 | URI 格式 | +|------|----------| +| 文档节点 | `https://alidocs.dingtalk.com/i/nodes/{dentryUuid}` | +| edit / preview 链接 | `https://alidocs.dingtalk.com/document/{edit\|preview}?...&dentryKey={key}` | + +> **操作后请返回文档 URI**:每次执行 create / read / update 等操作后,从返回数据中提取 `docUrl` 直接返回;缺失时用 `doc info --node ` 补查。 diff --git a/skills/multi/dingtalk-doc/references/doc/doc-version.md b/skills/multi/dingtalk-doc/references/doc/doc-version.md new file mode 100644 index 00000000..22d5f36b --- /dev/null +++ b/skills/multi/dingtalk-doc/references/doc/doc-version.md @@ -0,0 +1,49 @@ +# 版本管理 + +| 命令 | 用途 | 必填参数 | +|---|---|---| +| `doc version save` | 手动保存文档版本快照 | `--node` | +| `doc version list` | 查看文档历史版本列表 | `--node` | +| `doc version revert` | 回滚文档到指定版本(不可逆) | `--node` `--version` `--yes` | + +#### 手动保存文档版本 + +``` +Usage: + dws doc version save [flags] +Example: + dws doc version save --node +Flags: + --node string 文档 ID 或 URL (必填) +``` + +> 仅支持 adoc 类型文档。保存后会创建一个 USER_SAVE 类型的版本记录。 + +#### 查看文档历史版本列表 + +``` +Usage: + dws doc version list [flags] +Example: + dws doc version list --node + dws doc version list --node --limit 10 +Flags: + --node string 文档 ID 或 URL (必填) + --limit int 返回版本数量上限 (可选) + --cursor string 分页游标 (可选) +``` + +#### 回滚文档到指定版本 + +> **CAUTION:** 不可逆操作 — 执行前必须向用户确认。 + +``` +Usage: + dws doc version revert [flags] +Example: + dws doc version revert --node --version 3 --yes +Flags: + --node string 文档 ID 或 URL (必填) + --version int 目标版本号 (必填,从 version list 获取) + --yes 跳过确认提示 (非交互终端必须传) +``` diff --git a/skills/multi/dingtalk-doc/references/doc/doc-workflow.md b/skills/multi/dingtalk-doc/references/doc/doc-workflow.md new file mode 100644 index 00000000..0665bd1d --- /dev/null +++ b/skills/multi/dingtalk-doc/references/doc/doc-workflow.md @@ -0,0 +1,77 @@ +# 核心工作流 + +## 核心工作流 + +> 步骤性指引。"读哪些文件"组合参见上方 §场景索引;命令详细参数参见对应子文件。 + +```bash +# ── 工作流 1: 定位并阅读文档 ── +dws drive search --query "<关键词>" --format json # 1. 搜索定位 nodeId(或 wiki node search) +dws doc info --node --format json # 2. 元信息(含 contentType) +dws doc read --node --format json # 3. 读 markdown 正文 + +# ── 工作流 2: 创建文档(含分片自动写入)── +dws drive folder create --name "项目资料" --format json # 1. (可选) 创建文件夹 +dws doc create --name "项目周报" --content-file /tmp/x.md \ + --folder --format json # 2. 创建 + 写入 +dws doc read --node --format json # 3. 回读校验(必须) + +# ── 工作流 3: 局部改写(保真,首选 JSONML)── +dws doc block list --node --content-format jsonml # 1. 取 uuid +dws doc block list --node --content-format jsonml --block-id # 2. 读子树 +dws doc block update --node --block-id \ + --content-format jsonml --element '[...]' # 3. 写回(uuid 必须 == --block-id) +dws doc read --node --content-format jsonml # 4. 回读 + +# ── 工作流 4: 整篇 overwrite(仅在新骨架重写时)── +dws doc read --node --content-format jsonml --output /tmp/doc.json # 1. 读出当前 JSONML +# 修改 /tmp/doc.json 中的 jsonml 数组 +dws doc update --node --content-file /tmp/doc.json \ + --content-format jsonml --mode overwrite # 2. 写回(默认不做并发检查) +dws doc read --node --content-format jsonml # 3. 回读 +# 担心被并发覆盖时,可加 --revision 触发并发检查(详见 doc-update.md) + +# ── 工作流 5: 上传独立文件 vs 插入附件到文档正文 ── +dws drive upload --file ./report.pdf --folder # 上传作为独立文件(存储层) +dws doc media insert --node --file ./report.pdf # 上传并作为附件块插入正文(内容层) + +# ── 工作流 6: 导入本地文件为在线文档 ── +dws doc import --file ./report.docx --format json # 一体化导入(推荐) +dws doc import --file ./notes.md --folder --format json # 指定目标文件夹 +dws doc import --file ./data.xlsx --workspace --format json # 指定目标知识库 + +# ── 工作流 7: 下载 vs 导出(先 info 判 contentType)── +dws doc info --node --format json # 必须先查 contentType +dws doc export --node --output ~/downloads/ # contentType=ALIDOC 走 export(内容层) +dws drive download --node --output ~/downloads/ # contentType≠ALIDOC 走 drive download(存储层) + +# ── 工作流 8: 评论 + 划词(@人 需 userId)── +dws contact user search --query "张三" --format json # 取 userId +dws doc comment create --node --content "请确认" --mention --format json +dws doc block list --node --format json # 划词需先取 blockId / paragraph.text +dws doc comment create-inline --node --block-id \ + --start 0 --end 10 --content "建议调整" --selected-text "原文" --format json + +# ── 工作流 9: 权限授予(节点级,已迁移到 drive)── +dws contact user search --query "张三" --format json # 取 userId +dws drive permission add --node --user , --role EDITOR --format json +dws drive permission list --node --format json # 校验 + +# ── 工作流 10: 文件操作(已迁移到 drive)── +# 第一步:获取 nodeId +# 方式 A(优先):用户直接提供 URL / nodeId → 直接传 --node +# 方式 B:按关键字找:dws drive search --query "项目周报" --format json +# 方式 C:按文件夹遍历:dws drive list --workspace --format json +# 第二步:执行 +dws drive copy --node --folder --format json +dws drive move --node --folder --format json +dws drive rename --node --name "新名称" --format json +dws drive delete --node --yes --format json +``` + +```bash +# ── 工作流 11: 文档版本管理 ── +dws doc version save --node --format json # 1. 手动保存版本快照 +dws doc version list --node --format json # 2. 查看版本列表(提取 version 号) +dws doc version revert --node --version --yes --format json # 3. 回滚到指定版本(危险操作,需 --yes) +``` diff --git a/skills/multi/dingtalk-doc/references/doc/doc-write-index.md b/skills/multi/dingtalk-doc/references/doc/doc-write-index.md new file mode 100644 index 00000000..d5320d5e --- /dev/null +++ b/skills/multi/dingtalk-doc/references/doc/doc-write-index.md @@ -0,0 +1,7 @@ +# 创建 / 写入 / 块编辑 + +| 命令 | 用途 | 必读参考 | +|---|---|---| +| `doc create` | 创建文字文档(adoc) | [`doc-create.md`](./doc-create.md) + [`style/doc-create-workflow.md`](./style/doc-create-workflow.md) + [`style/doc-style-guideline.md`](./style/doc-style-guideline.md) | +| `doc update` | 整篇 / 段落级更新(markdown / jsonml) | [`doc-update.md`](./doc-update.md) + [`style/doc-update-workflow.md`](./style/doc-update-workflow.md) | +| `doc block list/insert/update/delete` | 块级精细编辑 | [`doc-block.md`](./doc-block.md) + [`format/doc-jsonml-cookbook.md`](./format/doc-jsonml-cookbook.md) | diff --git a/skills/multi/dingtalk-doc/references/doc/style/doc-create-workflow.md b/skills/multi/dingtalk-doc/references/doc/style/doc-create-workflow.md index e055629e..4856887f 100644 --- a/skills/multi/dingtalk-doc/references/doc/style/doc-create-workflow.md +++ b/skills/multi/dingtalk-doc/references/doc/style/doc-create-workflow.md @@ -226,7 +226,7 @@ callout 可通过 `"showstk": true, "sticker": "图标名"` 配置顶部贴纸 > **脚手架策略警示**:Markdown 无法表达分栏/callout/色彩表头,拉回的 JSONML 只有纯文本骨架。精修阶段不是“在现有结构上加色”,而是“参照 RFC/Spec 重组结构”。 -> **MUST READ**:动手写 JSONML 前,必须先用 Read 工具读取 [doc-jsonml-cookbook.md](../format/doc-jsonml-cookbook.md) — 其中 §决策型文档骨架范例 有可直接复制修改的完整模板。 +> 使用 JSONML 创建复杂结构前,按需读取 [doc-jsonml-cookbook.md](../format/doc-jsonml-cookbook.md);其中 §决策型文档骨架范例 有可复用模板。 > 节点类型和属性的权威定义见 [doc-jsonml-schema.md](../format/doc-jsonml-schema.md)。 ### ⚠️ JSONML 降级约束 diff --git a/skills/multi/dingtalk-doc/references/doc/style/doc-update-workflow.md b/skills/multi/dingtalk-doc/references/doc/style/doc-update-workflow.md index 45d1cb88..88d8c68f 100644 --- a/skills/multi/dingtalk-doc/references/doc/style/doc-update-workflow.md +++ b/skills/multi/dingtalk-doc/references/doc/style/doc-update-workflow.md @@ -258,7 +258,7 @@ dws doc update --node --content-file /tmp/-full.md --mode overwri 当一次性追加内容 **超过 200KB** 时,必须拆分为多片 `--mode append`,并在执行第一片**之前**向用户发出截断风险提示等待确认。 -完整规范(提示话术模板、触发条件、失败处理)见 [04-document.md «分块 append 截断风险提示»](../../../best_practices/04-document.md)。 +完整规范(提示话术模板、触发条件、失败处理)见 [04-document.md «分块 append 截断风险提示»](../../04-document.md)。 update 场景下的额外约束: diff --git a/skills/multi/dingtalk-doc/references/format/doc-jsonml-index.md b/skills/multi/dingtalk-doc/references/format/doc-jsonml-index.md new file mode 100644 index 00000000..85b64374 --- /dev/null +++ b/skills/multi/dingtalk-doc/references/format/doc-jsonml-index.md @@ -0,0 +1,9 @@ +# 排版规范 / JSONML + +| 资源 | 用途 | 详见 | +|---|---|---| +| 创建工作流 | 标题、位置、骨架、回读校验 | [`../doc/style/doc-create-workflow.md`](../doc/style/doc-create-workflow.md) | +| 改写工作流 | 编辑形态优先级、分片 append、回读验收 | [`../doc/style/doc-update-workflow.md`](../doc/style/doc-update-workflow.md) | +| 排版规范 | 文档类型 / 骨架 / 元素边界 / 颜色语义 | [`../doc/style/doc-style-guideline.md`](../doc/style/doc-style-guideline.md) | +| JSONML 范例 | 所有节点类型的可复制命令 | [`doc-jsonml-cookbook.md`](../doc/format/doc-jsonml-cookbook.md) | +| JSONML 节点结构 | 字段定义 + JSON Schema | [`doc-jsonml-schema.md`](../doc/format/doc-jsonml-schema.md) | diff --git a/skills/multi/dingtalk-doc/references/intent-guide.md b/skills/multi/dingtalk-doc/references/intent-guide.md new file mode 100644 index 00000000..6d6d8cd9 --- /dev/null +++ b/skills/multi/dingtalk-doc/references/intent-guide.md @@ -0,0 +1,19 @@ +# doc 局部意图消歧 + +本文件从单 Skill `intent-guide.md` 拆分而来,仅保留与本产品相关的跨产品消歧规则。 + +| 用户说... | 真实意图 | 应该用 | 不要用 | 理由 | +|---|---|---|---|---| +| "搜一下 OAuth2 接入文档" | 搜索开发文档 | `devdoc` | `doc search` | 搜索开放平台技术文档,不是钉钉内部内容 | +| "帮我建一个项目跟踪表" | 创建数据表格 | `aitable` | `doc` / `sheet` | 涉及结构化数据/行列操作,不是富文本文档或电子表格 | +| "帮我写个项目周报" | 创建钉钉文档 | `doc` | `aitable` | 富文本内容创作,不是数据表 | +| "参照这个生成同样的 / 按模板生成 / 复刻 X / 同样的模板 X 月份的" + 已有 alidocs URL | 模板保形生成同形态变体 | `drive copy + drive rename + doc block update` → 见 [模板保形生成](./04-document.md#模板保形生成) | `doc read + doc create`(重写链) | adoc → markdown 是有损投影,read+create 会丢行高/单元格背景色/字号;copy 在 adoc 层保形复制后只在副本上局部修改 | +| "这个 alidocs 表格链接帮我看下"(粘贴原始 URL) | 先 probe 节点类型 | `dws doc info --node` → 按 `extension` 路由 | 直接调 `sheet` | `alidocs/i/nodes/{id}` 可能是文档/axls/able/xlsx 等,禁止凭 URL 猜类型 | +| "帮我记一下明天要做的事" | 创建个人待办 | `todo` | `doc` | 个人待办提醒,非文档内容 | +| "在知识库里创建一个文档" | 创建空文件实体 | `wiki node create --type adoc` | `doc create` | 空间内创建节点归 wiki;doc create 是向已有文档写入内容,不是创建文件节点 | +| "帮我看看收到的日报" | 收到的日志 | `report` | `doc` | 钉钉日志系统(日报/周报),不是文档 | +| "整理一下XX项目的所有讨论" | 跨源主题归档 | #5 generate-topic-report | #4 write-doc | #4 侧重单篇文档创作;按主题跨听记/群消息汇总属于工作汇报 | +| "搜一下智能化方案/最近 OKR 相关邮件/最近发版相关消息" | 搜企业知识内容 | `aisearch enterprise` | `doc search` / `mail search` / `chat message search` | 跨文档、消息、日程、听记、邮件等企业内容语义检索走 enterprise;具体 `queries/types/time-range` 抽槽见 `aisearch.md` | +| "我发给某人的消息/邮件/文档/今天我干了什么" | 搜行为记录 | `aisearch behavior` | `chat` / `mail` / `doc` / `report` | 关注“谁对什么做过什么”,走 behavior;具体 `behavior-type/direction/chat-scope` 抽槽见 `aisearch.md` | +| "把这段文字翻译成英文/translate this" | 通用文本翻译 | `chat text translate` | `doc` / `aisearch` | 纯文本翻译,不是文档编辑或语义搜索 | +| "帮我把这个文档翻译成日文" | 文档内容翻译 | 先 `doc get` 再 `chat text translate` | `chat text translate` 直接传文件 | translate 仅支持纯文本,需先提取文档内容 | diff --git a/skills/multi/dingtalk-doc/references/lite-recipes.md b/skills/multi/dingtalk-doc/references/lite-recipes.md new file mode 100644 index 00000000..19479051 --- /dev/null +++ b/skills/multi/dingtalk-doc/references/lite-recipes.md @@ -0,0 +1,44 @@ +# doc Lite Recipe + +本文件从单 Skill `lite-recipes.md` 拆分而来,仅保留与本产品相关的轻量流程。 + +## #4 文档知识 + +### query-doc + +1. 全局搜索:`drive search --query "<关键词>"` → `nodeId`(聚合钉盘+文档空间) +2. 空间内搜索:`wiki node search --workspace --keyword "<关键词>"` → `nodeId` +3. `doc read --node `(按需;大文档只抽章节) + +### list-folder-docs + +`drive list --workspace ` 或 `wiki node list --workspace ` + +### import-file + +将本地文件导入为钉钉在线文档。**一条命令完成上传+格式转换+创建**,无需先读取文件内容。 + +```bash +dws doc import --file ./report.docx --format json +``` + +1. 确认文件路径(用户提供的本地文件路径) +2. 执行:`dws doc import --file <文件路径> --format json`(可选 `--folder <文件夹ID>` / `--workspace <知识库ID>` / `--name "文档名"`) +3. 从返回中提取 `documentUrl`,告知用户导入完成并提供链接 +4. 超时或中断时 CLI 返回 `taskId`,用 `dws doc import get --task-id --format json` 手动查询 + +**`--folder` 参数传值规则**: +- 首选路径:用户提供 alidocs URL 时,直接将完整 URL 传入 `--folder`,无需先调 `doc info` +- 预检路径:若需确认 URL 指向的是文件夹,可先调 `dws doc info --node `: + - `nodeType == "folder"` → 使用 `nodeId` 或原始 URL 作为 `--folder` 值 + - `nodeType` 不是 folder → 提示用户:该链接指向的不是文件夹 +- 禁止:不得使用 `doc info` 返回的 `folderId` 字段作为 `--folder` 的值(`folderId` 是父文件夹 ID,非当前节点 ID) + +格式与文档类型映射: +- `.docx` / `.doc` → 文字文档(DOC) +- `.xlsx` / `.xls` → 电子表格(SHEET) +- `.xmind` / `.mark` → 脑图(MIND) +- `.md` / `.txt` → 文字文档(DOC) + +> **禁止先 Read 文件再 `doc create` + `doc update`**。`doc import` 是服务端格式转换,客户端无需解析文件内容。 +> 详见 [./doc/doc-import.md](./doc/doc-import.md)。 diff --git a/skills/multi/dingtalk-doc/references/url-patterns.md b/skills/multi/dingtalk-doc/references/url-patterns.md index 8c5bc8f6..96276e26 100644 --- a/skills/multi/dingtalk-doc/references/url-patterns.md +++ b/skills/multi/dingtalk-doc/references/url-patterns.md @@ -19,6 +19,8 @@ | 产品 | 用途 | URL 格式 | ID 来源 | |------|------|----------|---------| | `aitable` | AI表格 Base 链接 | `https://alidocs.dingtalk.com/i/nodes/{baseId}` | `base list/search/create/get` 返回的 `baseId` | +| `aitable` | AI表格指定数据表链接 | `https://alidocs.dingtalk.com/i/nodes/{baseId}?iframeQuery=sheetId%3D{tableId}` | `baseId` + `table create/get` 或 `base get` 返回的 `tableId` | +| `aitable` | AI表格指定数据表+视图链接 | `https://alidocs.dingtalk.com/i/nodes/{baseId}?iframeQuery=sheetId%3D{tableId}%26viewId%3D{viewId}` | `baseId` + `tableId` + `view create/get` 返回的 `viewId` | | `aitable` | AI表格模板预览 | `https://docs.dingtalk.com/table/template/{templateId}` | `template search` 返回的 `templateId` | | `doc` | 文档链接 | `https://alidocs.dingtalk.com/i/nodes/{dentryUuid}` | `doc` 命令返回的 `dentryUuid` | | `sheet` | 电子表格链接 | `https://alidocs.dingtalk.com/i/nodes/{dentryUuid}` | `sheet create` 返回的 `dentryUuid` | @@ -67,7 +69,7 @@ dws doc read --node "https://alidocs.dingtalk.com/i/p/Y7kmbokZp3pgGLq2/docs/AY39 > > 你可以: > 1. 在钉钉客户端中打开该文档,将正文内容复制粘贴给我 -> 2. 如果文档已保存在你的文档空间中,可以告诉我文档名称,我通过 `dws doc search` 搜索后再读取 +> 2. 如果文档已保存在你的文档空间中,可以告诉我文档名称,我通过 `dws drive search` 搜索后再读取 --- @@ -87,18 +89,18 @@ 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 drive download` 下载到本地处理,禁止走 `sheet`(非在线表格,sheet 命令无法操作) | -| `contentType≠ALIDOC`, `nodeType=file` | `doc` | 调用 `dws drive download` 下载,返回文件下载链接 | -| `nodeType=folder` | `doc` | 调用 `dws doc list --folder ` 列出指定文件夹直接子节点列表 | +| `contentType=ALIDOC`, `extension=adoc` | `doc` | 加载 `dingtalk-doc` 操作内容 | +| `contentType=ALIDOC`, `extension=axls` | `sheet` | 加载 `dingtalk-sheet` 操作(仅 `axls` 在线电子表格) | +| `contentType=ALIDOC`, `extension=able` | `aitable` | 将 nodeId 作为 baseId,加载 `dingtalk-aitable` 操作 | +| `contentType=DOCUMENT`, `extension=xlsx` / `xls` / `xlsm` / `csv` | `drive` | 必须用 `dws drive download` 下载到本地处理,禁止走 `sheet`(非在线表格,sheet 命令无法操作) | +| `contentType≠ALIDOC`, `nodeType=file` | `drive` | 调用 `dws drive download` 下载,返回文件下载链接 | +| `nodeType=folder` | `drive` / `wiki` | 调用 `dws drive list --workspace ` 或 `dws wiki node list` 列出子节点 | | 以上均不匹配 | — | 告知用户当前暂不支持该类型 | > axls vs xlsx 关键区分: > - `axls`(钉钉在线电子表格,`contentType=ALIDOC`)→ 走 `sheet` 产品线(读/写/筛选/导出等服务端原子操作) > - `xlsx` / `xls` / `xlsm` / `csv`(上传到文档空间的本地表格文件,`contentType=DOCUMENT`)→ 必须走 `dws drive download` 下载到本地后再解析处理,严禁错误路由到 `sheet` 产品线(sheet 命令只支持在线表格,调用 xlsx 节点会直接报错) -> - 用户想把在线表格导出为 xlsx 文件 → 用 `dws sheet export --node <表格ID或URL>`(单命令一站式:提交导出任务→轮询→取 downloadUrl,加 `--output ./x.xlsx` 可直接落盘) +> - 用户想把在线表格导出为 xlsx 文件 → 用 `dws sheet export`(输入是 `axls`,输出是 xlsx,这是 axls → xlsx 的格式转换,不属于 xlsx 读取场景) ### 示例 @@ -116,7 +118,7 @@ dws drive download --node "https://alidocs.dingtalk.com/i/nodes/xlsx456" 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 +dws drive list --workspace --format json ``` ### 何时可跳过探测 diff --git a/skills/multi/dingtalk-doc/scripts/doc_create_and_write.py b/skills/multi/dingtalk-doc/scripts/doc_create_and_write.py index 367e4b93..fbf52f8d 100644 --- a/skills/multi/dingtalk-doc/scripts/doc_create_and_write.py +++ b/skills/multi/dingtalk-doc/scripts/doc_create_and_write.py @@ -96,7 +96,7 @@ def main(): if not content: print('错误:需要 --content 或 --content-file') sys.exit(1) - chunk_size = 30000 + chunk_size = 10000 create_args = ['doc', 'create', '--name', args.name, '--format', 'json'] if args.folder: diff --git a/skills/multi/dingtalk-drive/SKILL.md b/skills/multi/dingtalk-drive/SKILL.md index 9fdc7251..d36ba10f 100644 --- a/skills/multi/dingtalk-drive/SKILL.md +++ b/skills/multi/dingtalk-drive/SKILL.md @@ -1,6 +1,6 @@ --- name: dingtalk-drive -description: 钉盘文件存储。Use when 用户说 钉盘/上传文件/下载文件/文件夹/查文件/创建文件夹。Distinct from dingtalk-doc(钉钉文档内容编辑)、dingtalk-wiki(知识库空间)。命令前缀:dws drive。 +description: 钉钉文件管理(存储层)。Use when 用户说 钉盘/上传文件/下载文件/文件夹/查文件/找文件/全局搜索文件/复制/移动/重命名/删除/回收站/还原删除文件/权限管理/普通文件下载。任何文件类型都适用;文档内容编辑走 dingtalk-doc,知识库空间和空间内节点管理走 dingtalk-wiki。命令前缀:dws drive。 cli_version: ">=0.2.14" metadata: category: product @@ -14,27 +14,24 @@ metadata: > 🧪 **EXPERIMENTAL · 试验版 / Preview** — multi 模式当前未达 stable 标准。全部 dingtalk-* skill 已通过 dispatch verifier,但接口、命名、跨 skill 引用后续可能调整;生产 / 共享环境请优先使用 mono 模式(`dws skill setup --mode mono`)。问题请提 issue 反馈。 -> **PREREQUISITE:** Read the `dws-shared` skill first for auth, global flags, product routing, URL preflight, error codes, and safety rules. The `dws` binary must be on PATH. +## 前置条件 — 执行操作前必读 + +> **`use_skill(dws-shared)`** — 认证、全局参数(`--format json` / `--yes`)、错误码、URL 模板、跨产品消歧、安全规则与 capability 边界。**执行任何 `dws` 命令前先读;** 单产品的清晰命令可直接用本 skill。 -> ⚠️ **命令可用性以当前 dws 二进制为准**。服务发现已下线,本文档随内置 skill 发布;如果 `dws --help` 不存在,说明当前版本未暴露该命令。若命令存在但调用失败,请按错误中的 endpoint 或 tool 提示确认静态端点目录和后端工具注册。实际调用前可用 `dws --help` 或 `--dry-run` 验证。 - - > 命令参考:[drive.md](references/drive.md)。 ## 意图表 | 用户说 | 命令 | |--------|------| -| "看钉盘文件 / 文件夹列表" | `dws drive list --limit 20 [--folder ]` | -| "按名字搜文件(不知道在哪)" | `dws drive search --query "<关键词>"` | -| "看钉盘空间 / 团队文件 / 有哪些 space" | `dws wiki space list --type orgSpace`(`drive list-spaces` 已 deprecated) | -| "最近访问 / 最近编辑的文档" | `dws drive recent` | +| "看钉盘文件 / 文件夹列表" | `dws drive list [--folder ]` | | "钉盘目录树" | `python scripts/drive_tree_list.py --depth 2` | | "查文件元数据" | `dws drive info --node ` | | "查阅读/编辑/评论/下载等节点统计" | `dws drive stats --node ` | | "创建文件快捷方式" | `dws drive shortcut --node [--folder ] [--workspace ]` | +| "搜文件 / 找文件" | `dws drive search --query "<关键词>"` | | "下载文件" | `dws drive download --node --output ` | | "上传本地文件" | `dws drive upload --file ./report.pdf [--folder ]` | | "建文件夹" | `dws drive mkdir --name "<名称>" [--folder ]` | @@ -43,18 +40,72 @@ metadata: | "回收站 / 还原" | `dws drive recycle list` / `recycle restore --id ` | | "公开 / 取消公开 / 查公开状态" | `dws drive publish set` / `unset` / `get --node ` | -## 评测高频硬约束 +## 标准 SOP(必遵流程) + +> 命中以下意图**必须**按对应 SOP 顺序执行;**禁止**跳步、替换命令、编造 dentryUuid/nodeId。每条命令必须带 `--format json`。破坏性操作(删除/移动/覆盖/公开)**必须**先与用户确认。 + +### SOP-1 找文件(find-file) + +**触发**:找文件/搜文件/我的文件/最近文件/某文档在哪。 + +1. **选源(必须)**:最近访问 → `dws drive recent --limit --format json`(翻页用上次返回的 `nextCursor` 传 `--cursor`);按内容/名称全局搜 → `dws drive search --query "<关键词>" --format json`;浏览某目录 → `dws drive list --folder --format json`。 +2. **解析(必须)**:取真实 `fileId`(UUID 格式);多候选让用户确认,**禁止**默认取第一个。 +3. **下钻(必须)**:根目录没命中时,进入最相关文件夹继续 `drive list --folder `,必要时 `python scripts/drive_tree_list.py --depth 2` 递归,**禁止**只看根目录就放弃。 +4. **回读元数据(必须)**:命中后 `dws drive info --node --format json` 确认类型(在线文档 vs 普通文件)。 + +**禁止**:编造 dentryUuid、只看根目录放弃、用 `drive list` 替代 `drive search` 做全局查找。 + +### SOP-2 上传 / 下载(upload-download) + +**触发**:上传文件/下载文件/传到钉盘。 + +1. **上传(必须)**:`dws drive upload --file <本地路径> [--folder ] --format json`;返回取 `fileId`,`drive info --node` 回读确认。 +2. **下载(必须)**:先 `drive info --node ` 判断类型——在线文档(ALIDOC 等)切 `dingtalk-doc` 用 `doc export`;普通文件 `dws drive download --node --output <本地路径>`。 + +**禁止**:对在线文档用 `drive download`(会失败)、上传后不回读。 + +### SOP-3 文件夹 / 复制 / 移动 / 重命名(folder-ops) + +**触发**:建文件夹/复制/移动/重命名。 + +1. **执行(必须)**:建文件夹 `dws drive mkdir --name "<名称>" [--folder ]`;复制 `drive copy --node --folder <目标fileId>`;移动 `drive move --node --folder <目标fileId>`;重命名 `drive rename --node --name "<新主名>"`。全部 `--format json`。 +2. **验证(必须)**:操作后 `drive info --node <新fileId>` 或 `drive list --folder <目标fileId>` 回读。 + +**禁止**:未确认就移动/覆盖他人文件、跳过回读。 + +### SOP-4 回收站(recycle) + +**触发**:删文件/回收站/还原。 + +1. **删除(必须)**:`dws drive delete --node --yes --format json`(**必须**先与用户确认)。 +2. **还原(必须)**:`dws drive recycle list --format json` 取 `recycleItemId` → `dws drive recycle restore --id --format json`。 + +**禁止**:未确认就删除、把 `dentryUuid` 当 `recycleItemId` 传给 restore。 + +### SOP-5 互联网公开(publish) + +**触发**:互联网公开/取消公开/查公开状态。 + +1. **执行(必须)**:查状态 `dws drive publish get --node --format json`;开启公开 `dws drive publish set --node --yes`(**[危险]** 必须用户确认);关闭公开 `dws drive publish unset --node --yes`。 +2. **边界(必须)**:对外公开前**必须**与用户确认边界与后果。 + +**禁止**:未确认就 `publish set`、跳过 `--yes`。 + +## 高频硬约束 -- 找文件优先用 `drive search --query "<关键词>"`(不知道位置时全局搜);只有需要逐层浏览时才用 `drive list`。命中后必须 `drive info --node --format json` 回读元数据。 - **ID 字段选择**:`drive list` 返回同时有 `dentryId`(纯数字)和 `fileId`(UUID 格式)。所有 `--node` 和 `--folder` 参数**必须用 `fileId`**,纯数字 `dentryId` 会被拒绝。 -- `drive list` 默认 `--limit 20`,最大 50;要更多用 `--cursor` 翻页,不要因参数边界报错反复重试。 -- `rename` 的 `--name` **只传主名,不带扩展名**;服务端会按原扩展名自动补后缀,带了扩展名会变成双扩展名(如 `报告.txt` → `报告.txt.txt`)。 -- `drive download` 需要 `--output` 指定本地保存路径或目录;不要省略必填输出位置。 -- 删除、覆盖、移动、公开(publish set/unset)等破坏性操作必须先确认;上传、创建文件夹、下载后要读回或列目录验证。 -- `shortcut` 会创建新节点,执行后必须用 `drive list` 回读目标位置;`stats` 是只读操作。 +- 查找文件不要只看根目录后放弃;根目录没命中时,进入最相关的目标文件夹继续 `drive list --folder `,必要时用目录树脚本递归到合理深度。 +- `drive list` 默认 `--limit 20`,自动化场景里保守使用 `--limit 50` 以内并处理 `nextToken` 翻页;不要因为参数边界报错反复重试。 +- 全局找文件优先 `drive search --query`;指定目录浏览用 `drive list`,命中后必须 `drive info --node --format json` 回读元数据。 +- `rename` 的 `--name` **只传主名,不带扩展名**;服务端会按原扩展名自动补后缀,带扩展名会变成双扩展名。 +- `drive download` 必须用 `--output` 指定本地保存路径或目录。`shortcut` 创建新节点后必须用 `drive list` 回读目标位置;`stats` 是只读操作。 +- 删除、覆盖、移动等破坏性操作必须确认;上传、创建文件夹、下载后要读回或列目录验证。 - 所有 `dws drive` 命令加 `--format json`。 ## 跨产品协作 - 文件内容编辑(钉钉文档)→ 切到 `dingtalk-doc` - 知识库空间 → 切到 `dingtalk-wiki` +## 局部意图与 Recipe + +- [局部意图消歧](references/intent-guide.md);[Lite Recipe](references/lite-recipes.md)。 diff --git a/skills/multi/dingtalk-drive/references/drive-index.md b/skills/multi/dingtalk-drive/references/drive-index.md new file mode 100644 index 00000000..758d24ef --- /dev/null +++ b/skills/multi/dingtalk-drive/references/drive-index.md @@ -0,0 +1,5 @@ +# 钉盘能力索引 + +| 主题 | 详见 | +|---|---| +| 文件上传/下载/复制/移动/删除/权限 | [`drive.md`](./drive.md) | diff --git a/skills/multi/dingtalk-drive/references/drive.md b/skills/multi/dingtalk-drive/references/drive.md index 48507ba7..634d5bca 100644 --- a/skills/multi/dingtalk-drive/references/drive.md +++ b/skills/multi/dingtalk-drive/references/drive.md @@ -1,7 +1,5 @@ # 钉盘 (drive) 命令参考 -钉盘 = DingTalk Drive,用于云端文件存储 / 上传 / 下载 / 目录管理。不是在线文档编辑;要编辑文档请用 [doc](./doc.md)。 - ## 查询命令帮助 当你不确定某个命令的具体参数、格式或可选项时,**优先执行 `--help` 查询**,不要猜测参数名或凭记忆编造。 @@ -37,33 +35,26 @@ Example: Flags: --limit int 每页返回数量,默认 20,最大 50 (可选) --cursor string 分页游标,首次不传 (可选) - --order string 排序方向: asc|desc,默认 desc (可选,仅钉盘) - --order-by string 排序字段: createTime|modifyTime|name (可选,仅钉盘) + --order string 排序方向: asc|desc,默认 desc (可选) + --order-by string 排序字段: createTime|modifyTime|name (可选) --folder string 父节点 ID (dentryUuid),不传则列出空间根目录 (可选) - --space-id string 钉盘空间 ID (纯数字),不传则使用「我的文件」对应 spaceId (可选) - --workspace string 文档空间/知识库 ID (加密 string 或 URL),传入则路由到文档空间 (可选) - --thumbnail 是否返回缩略图信息 (可选,仅钉盘) + --space-id string 空间 ID,不传则使用「我的文件」对应 spaceId (可选) + --thumbnail 是否返回缩略图信息 (可选) ``` -> 统一入口:默认列钉盘空间(`--space-id` 纯数字);传 `--workspace` 时路由到文档空间/知识库列表。 - ### 获取钉盘空间列表 -> **Deprecated**:推荐改用 `dws wiki space list --type orgSpace` / `--type mySpace`(见 [wiki.md](./wiki.md))。本命令仍可用,仅作兼容保留。 -> 适用场景:复制/移动文件到「我的文件」或团队空间根目录时,先取 `rootFolderId`;或者枚举用户可访问的团队空间。 - ``` Usage: dws drive list-spaces [flags] Example: - dws wiki space list --type orgSpace # 推荐 - dws wiki space list --type mySpace # 推荐 - dws drive list-spaces # deprecated + dws drive list-spaces + dws drive list-spaces --space-type mySpace dws drive list-spaces --space-type orgSpace --limit 20 --cursor Flags: --space-type string 空间类型: orgSpace=企业空间(默认), mySpace=我的文件 (可选) --limit int 每页返回数量 (默认 20,最大 50),仅 spaceType 为 orgSpace 时有效 - --cursor string 分页游标,仅企业空间支持分页 (可选) + --cursor string 分页游标,仅企业空间支持分页 (可选) ``` spaceType 筛选规则: @@ -73,13 +64,13 @@ spaceType 筛选规则: 返回字段说明: - `spaceId` — 空间 ID,用于 `list`/`info`/`upload` 等命令的 `--space-id` - `spaceName` — 空间名称(如"全员文件夹"、"我的文件") -- `rootFolderId` — 空间根目录的 dentryUuid,可作为 `drive copy/move` 的 `--folder` 参数 +- `rootFolderId` — 空间根目录的 dentryUuid,可作为 `doc copy/move` 的 `--folder` 参数 - `spaceType` — 空间类型(如 `orgSpace`) - `nextToken` — 若不为空,表示还有更多空间可查询(仅企业空间) ### 搜索钉盘文件/文件夹/空间 -按关键词全局搜索文件,默认同时搜索钉盘和文档空间,合并返回结果。不同于 `list`(需要明确的 spaceId/folder 逐层遍历),`search` 用于不知道具体位置、只记得名称/关键词的场景。 +按关键词在钉盘中搜索文件、文件夹或团队空间。不同于 `list`(需要明确的 spaceId/parentId 逐层遍历),`search` 用于不知道具体位置、只记得名称/关键词的场景。 ``` Usage: @@ -93,7 +84,7 @@ Example: dws drive search --query "报告" --limit 30 --cursor Flags: --query string 搜索关键词 (必填) - --target string 搜索范围: all(默认,聚合钉盘+文档空间) | file(仅钉盘文件) | space(仅钉盘空间) (可选) + --target string 搜索目标: all(默认) | file | space (可选) --file-types strings 按文件内容类型过滤,逗号分隔: alidoc,document,image,video,audio,archive (仅 target=file/all 生效) --extensions strings 按文件扩展名过滤,不含点号,逗号分隔 (如 pdf,docx,adoc) --creator-uids strings 按创建者用户 ID 过滤,逗号分隔 @@ -105,12 +96,12 @@ Flags: --cursor string 分页游标,从上次返回的 nextCursor 获取 (可选) ``` -搜索范围 (`--target`) 选择规则: -- `all`(默认):同时搜钉盘文件与文档空间,聚合返回 — 不确定目标位置时使用 -- `file`:只搜钉盘文件 / 文件夹,支持 `--file-types` / `--extensions` 过滤 — 明确是找钉盘文件时使用 -- `space`:只搜钉盘团队空间 — 明确知道空间名、需快速定位空间 spaceId/rootFolderId 时使用 +搜索目标 (`--target`) 选择规则: +- `all`(默认):同时搜文件与空间,返回混合结果 — 不确定目标是文件还是空间时使用 +- `file`:只搜文件 / 文件夹,支持 `--file-types` / `--extensions` 过滤 — 明确是找文件时使用 +- `space`:只搜团队空间 — 明确知道空间名、需快速定位空间 spaceId/rootFolderId 时使用 -结果中 `source` 字段区分来源:`drive` / `doc`。如果需要在某个知识库内搜索,请使用 `dws wiki node search --workspace `。 +返回结果中 `type` 字段区分:`SPACE`(空间)、`FILE`(普通文件)、`FOLDER`(文件夹)、`ALIDOC`(钉钉在线文档)。 > **提示**:结果按相关性排序,首页未命中时优先调整关键词 / 补充 `--file-types`/`--extensions` 缩小范围 / 加上时间范围,而非反复翻页。 @@ -154,7 +145,7 @@ Usage: Example: dws drive info --node Flags: - --node string 节点 ID (dentryUuid) (必填) + --node string 节点 ID (dentryUuid) (必填) --space-id string 节点所属空间 ID (可选) ``` @@ -187,12 +178,12 @@ Example: | extension | 文件类型 | 操作 | 命令 | |-----------|---------|------|------| -| adoc | 在线文档 | 在线获取 Markdown 内容 | `dws doc read --node ` | -| axls | 在线表格 | 在线读取表格数据 | `dws sheet list` → `dws sheet range read` | -| able | 多维表格 | 在线查询记录 | `dws aitable base get` → `dws aitable record query` | -| 其他(pdf/docx/txt/png 等) | 普通文件 | **不支持在线分析**,需用户主动下载后本地查看 | `dws drive download --node --output ` | +| adoc | 在线文档 | 在线获取 Markdown 内容 | `dws doc read --node ` | +| axls | 在线表格 | 在线读取表格数据 | `dws sheet list --node ` → `dws sheet range read --node --sheet-id ` | +| able | 多维表格 | 在线查询记录 | `dws aitable table list --base-id ` → `dws aitable record query --base-id --table-id ` | +| 其他(pdf/docx/txt/png 等) | 普通文件 | **不支持在线分析**,需用户主动下载后本地查看 | `dws drive download` | -### 下载钉盘文件到本地 +### 下载文件到本地 下载流程一步到位:获取下载 URL → HTTP GET 下载文件二进制内容到本地。 @@ -203,7 +194,7 @@ Example: dws drive download --node --output ./report.pdf dws drive download --node --output ~/downloads/ Flags: - --node string 文件 ID (dentryUuid) (必填) + --node string 文件 ID (dentryUuid) (必填) --output string 本地保存路径 (必填),可以是文件路径或目录;如果指定目录,文件名从下载 URL 中自动推断 --space-id string 文件所属空间 ID (可选) ``` @@ -219,16 +210,14 @@ Example: dws drive mkdir --name "项目资料" dws drive mkdir --name "子目录" --folder Flags: - --name string 文件夹名称,最长 50 字符 (必填) - --folder string 父节点 ID (dentryUuid),不传则在空间根目录下创建 (可选) - --space-id string 目标空间 ID,不传则使用「我的文件」 (可选) + --name string 文件夹名称,最长 50 字符 (必填) + --folder string 父节点 ID (dentryUuid),不传则在空间根目录下创建 (可选) + --space-id string 目标空间 ID,不传则使用「我的文件」 (可选) ``` -> `mkdir` 在钉盘空间创建文件夹;要在文档空间/知识库中创建文件夹,用 `dws wiki node create --type folder --workspace `(见 [wiki.md](./wiki.md))。 - ### 上传本地文件到钉盘 -> **注意:** 上传文件首选 `dws drive upload` 一条命令(内部自动完成三步流程),不要手动走 `upload-info` + `curl` + `commit` 三步。 +> **注意:** 上传文件必须使用 `dws drive upload` 命令,禁止使用 `upload-info` + `curl` + `commit` 三步流程。 ``` Usage: @@ -240,47 +229,12 @@ Example: Flags: --file string 本地文件路径 (必填) --file-name string 文件显示名称 (默认使用文件名) - --folder string 父节点 ID (dentryUuid),不传则上传到空间根目录 (可选) - --space-id string 目标钉盘空间 ID,不传则使用「我的文件」 (可选) - --workspace string 目标知识库 ID,传入时路由到文档空间上传 (可选) - --convert 是否转换为钉钉在线文档 (仅文档空间上传时生效) - --mime-type string 文件 MIME 类型,不传则自动推断 (可选) -``` - -`upload` 命令内部自动完成三步流程(获取凭证 → OSS PUT → 提交入库),无需手动分步操作。上传到知识库/文档空间时加 `--workspace` 参数。 - -### 获取上传凭证 (手动三步·仅特殊场景) - -> 仅当需要自定义流式上传、无法使用 `upload` 一条命令时才走手动三步:`upload-info` → HTTP PUT → `commit`。 - -``` -Usage: - dws drive upload-info [flags] -Example: - dws drive upload-info --file-name "report.pdf" --file-size 102400 - dws drive upload-info --file-name "slides.pptx" --file-size 512000 --folder -Flags: - --file-name string 文件名含后缀 (必填) - --file-size int 文件大小,单位字节 (必填) - --mime-type string MIME 类型 (可选,服务端会自动推断) - --folder string 父节点 ID (dentryUuid),不传则上传到空间根目录 (可选) --space-id string 目标空间 ID,不传则使用「我的文件」 (可选) + --mime-type string 文件 MIME 类型,不传则自动推断 (可选) + --folder string 父节点 ID (dentryUuid),不传则上传到空间根目录 (可选) ``` -### 提交上传 (手动三步·第三步) - -``` -Usage: - dws drive commit [flags] -Example: - dws drive commit --file-name "report.pdf" --file-size 102400 --upload-id -Flags: - --file-name string 文件名含后缀 (必填,须与 upload-info 一致) - --file-size int 文件大小,单位字节 (必填,须与 upload-info 一致) - --upload-id string upload-info 返回的 uploadId (必填) - --folder string 父节点 ID (dentryUuid),须与 upload-info 一致 (可选) - --space-id string 空间 ID,不传则使用「我的文件」 (可选) -``` +`upload` 命令内部自动完成三步流程(获取凭证 → OSS PUT → 提交入库),无需手动分步操作。 ### 删除文件/文件夹到回收站 @@ -292,15 +246,10 @@ Usage: Example: dws drive delete --node --format json # 查询 fileId: dws drive list Flags: - --node string 文件/文件夹 ID (dentryUuid),即 drive list 返回的 fileId (必填) - -Global Flags: - --yes 跳过二次确认 (危险操作,建议先与用户确认) + --node string 文件/文件夹 ID (dentryUuid),即 drive list 返回的 fileId (必填) ``` -> 由当前二进制静态注册(路由到 doc 服务的 `delete_document` tool);如果当前版本未暴露,调用前可用 `dws drive delete --help` 验证。 注意:`--node` 使用的是 `drive list` 返回结果中的 `fileId` 字段(即 `dentryUuid`),**不是** `dentryId` 字段。 -删除是软删除(进回收站),但仍需用户明确确认;不要在自动化脚本里默认带 `--yes`。 ### 查看回收站文件列表 @@ -333,7 +282,7 @@ Flags: 用户说"我的文件/钉盘/网盘/云盘" → `list` 用户说"最近访问/最近打开/最近编辑/最近文档" → `recent`(默认仅最近访问,`--operate-type 1` 仅最近编辑,`--operate-type 0,1` 全部) -用户说"钉盘空间/团队文件/有哪些空间/空间列表/团队文件列表" → `wiki space list --type orgSpace`(`drive list-spaces` 已 deprecated) +用户说"钉盘空间/团队文件/有哪些空间/空间列表/团队文件列表" → `list-spaces` 用户说"搜索钉盘文件/钉盘里找个文件/查找某个钉盘文件/钉盘中搜索" → `search` 用户说"文件详情/文件信息" → `info` 用户说"文件阅读量/编辑量/评论数/下载数/节点统计" → `stats` @@ -353,9 +302,9 @@ Flags: 关键区分: drive(文件管理) vs doc(文档内容读写) vs wiki(空间管理) -**drive search vs wiki node search**: 用户提到"钉盘/网盘/我的文件里搜" → `drive search`;提到"知识库/文档空间/workspace 里搜" → `wiki node search`;未明确目标时 `drive search`(全局聚合搜索)。 +**drive search vs wiki node search**: 用户提到"钉盘/网盘/我的文件里搜" → `drive search`;提到"知识库/文档空间/workspace 里搜" → `wiki node search`;未明确目标时优先问明。 -**drive upload vs doc upload**: 文件上传统一走 `drive upload`。上传到知识库/文档空间时加 `--workspace` 参数。 +**drive upload**: 文件上传统一走 `drive upload`。上传到知识库/文档空间时加 `--workspace` 参数。 **drive permission vs wiki member**: "给某篇文档/文件授权" → `drive permission add`(节点级);"给某个知识库整体加成员" → `wiki member add`(空间级) @@ -363,8 +312,6 @@ Flags: **导出文档/导出为Word**: 导出是内容层操作,走 `doc export`,不属于 drive。 -把图片/文件发到群里一般直接用 `chat message send --msg-type file --file-path <本地路径>`(见 [chat.md](./chat.md)),无需先经 drive 上传。 - ## 核心工作流 ```bash @@ -383,7 +330,7 @@ dws drive download --node --output /tmp/ --format json # 5. 创建文件夹 dws drive mkdir --name "项目资料" --format json -# 6. 上传文件(首选 upload 命令,自动完成三步流程) +# 6. 上传文件(必须使用 upload 命令,禁止手动分步操作) dws drive upload --file ./报告.pdf --format json dws drive upload --file ./报告.pdf --folder --format json @@ -398,7 +345,7 @@ dws drive recycle restore --id --format json ## 文档空间管理命令 -> 以下命令操作的是**文档空间**(知识库 / 我的文档),底层路由到 doc 服务。 +> 以下命令操作的是**文档空间**(知识库 / 我的文档),底层路由到 doc MCP server。 > 与钉盘命令(list / mkdir / upload 等)的区别:钉盘命令操作钉盘空间(spaceId 纯数字),文档空间命令操作知识库/我的文档(workspaceId 加密 string)。 ### 复制/移动/重命名文件 @@ -415,21 +362,18 @@ Flags: --name string 新名称 (仅 rename 必填) ``` -> **rename 只传主名,不要带扩展名**:服务端会按文件原扩展名自动补一个后缀。若 `--name` 里已带扩展名(如 `报告.txt`),回读会变成双扩展名 `报告.txt.txt`。正确做法:`dws drive rename --node --name "报告"`(不含 `.txt`),系统自动补回 `报告.txt`。 - -权限要求:copy 需对源文档有"阅读"权限且对目标文件夹有"编辑"权限;move 需对源文档有"管理"权限且对目标文件夹有"编辑"权限;rename 需对文档有"编辑"权限。 - > **字段选择**:`drive list` 返回中有 `dentryId`(数字格式)和 `fileId`(UUID 格式),**必须使用 `fileId`(UUID 格式)**作为 `--node` 和 `--folder` 参数值。 ### 创建文件夹(文档空间) -drive 没有独立的文档空间建文件夹命令,在知识库/文档空间中创建文件夹走: - -```bash -dws wiki node create --type folder --name "文件夹名" --workspace --format json ``` - -详见 [wiki.md](./wiki.md)。 +Usage: + dws drive folder create --name "文件夹名" +Flags: + --name string 名称 (必填) + --folder string 父文件夹 nodeId + --workspace string 目标知识库 ID +``` ### 权限管理(文档节点级) @@ -443,18 +387,17 @@ Usage: dws drive permission remove --node --users uid1 Flags: --node string 目标节点 ID 或 URL (必填) - --users string 用户 userId 列表,逗号分隔 (add/update/remove 必填) - --role string 角色: MANAGER / EDITOR / DOWNLOADER / READER (add/update 必填) - --workspace string 知识库 ID (选填) + --users string 用户 userId 列表,逗号分隔 + --role string 角色: MANAGER / EDITOR / DOWNLOADER / READER --limit int 返回成员数上限 (仅 list,默认 30,最大 200) - --filter-role string 按角色过滤: OWNER / MANAGER / EDITOR / DOWNLOADER / READER (仅 list) + --filter-role string 按角色过滤 (仅 list) ``` ### 文件互联网公开发布 管理文件的互联网公开发布状态。公开后任何人通过链接即可访问,无需登录钉钉。操作者需要是文件的管理员或拥有者。 -> **`publish set` 和 `publish unset` 为 [危险] 操作,执行前需要向用户确认。确认后传入 `--yes` 跳过交互式确认。** +> **`publish set` 和 `publish unset` 为 [危险] 操作,执行前需要确认。必须传入 `--yes` 跳过交互式确认。** ``` Usage: @@ -474,7 +417,7 @@ Flags: 子命令说明: - `publish set` — [危险] 设置文件为互联网公开,可选指定公开权限 - `publish unset` — [危险] 关闭文件互联网公开 -- `publish get` — 查询文件当前的公开发布状态 +- `publish get`(别名 `status`)— 查询文件当前的公开发布状态 返回字段说明: - `published` — true=已公开,false=未公开 @@ -488,28 +431,27 @@ Flags: | 目标位置 | 参数传递方式 | 前置步骤 | |---------|-----------|---------| -| 未指定目标(默认) | `--folder ` | 先 `dws wiki space list --type mySpace` 获取「我的文件」的 `rootFolderId` | -| 知识库空间根目录 | `--workspace ` | 无需额外步骤,直接传入 workspaceId | -| 钉盘 space 根目录 | `--folder ` | 先 `dws wiki space list --type orgSpace` 获取目标 space 的 `rootFolderId` | -| 钉盘 space 下的子文件夹 | `--folder ` | 先 `dws drive list --space-id ` 逐层浏览,获取目标文件夹的 `fileId`(dentryUuid 格式) | +| 未指定目标(默认) | `--folder ` | 先 `dws drive list-spaces --space-type mySpace` 获取「我的文件」的 `rootFolderId` | +| 知识库空间根目录 | `--workspace ` | 无需额外步骤 | +| 钉盘 space 根目录 | `--folder ` | 先 `dws drive list-spaces` 获取目标 space 的 `rootFolderId` | +| 钉盘 space 下的子文件夹 | `--folder ` | 先 `dws drive list --space-id ` 逐层浏览 | ### 工作流示例 ```bash -# ── 场景默认: 用户未指定目标位置 → 复制/移动到「我的文件」根目录 ── -dws drive list --space-id --format json # 获取源文件 dentryUuid -dws wiki space list --type mySpace --format json # 获取「我的文件」rootFolderId +# ── 场景 默认: 复制/移动到「我的文件」根目录 ── +dws drive list --space-id --format json +dws drive list-spaces --space-type mySpace --format json dws drive copy --node <源文件dentryUuid> --folder <我的文件rootFolderId> --format json -# ── 场景 A: 复制钉盘文件到知识库空间根目录 ── +# ── 场景 A: 复制到知识库空间根目录 ── dws drive copy --node <源文件dentryUuid> --workspace --format json -# ── 场景 B: 移动钉盘文件到另一个钉盘 space 根目录 ── -dws wiki space list --type orgSpace --format json +# ── 场景 B: 移动到另一个钉盘 space 根目录 ── +dws drive list-spaces --format json dws drive move --node <源文件dentryUuid> --folder <目标space的rootFolderId> --format json -# 注意:移动到其他 space 时只传 --folder,不传 --workspace -# ── 场景 C: 复制钉盘文件到钉盘 space 下的子文件夹 ── +# ── 场景 C: 复制到钉盘子文件夹 ── dws drive list --space-id --format json dws drive copy --node <源文件dentryUuid> --folder <目标文件夹fileId> --format json ``` @@ -535,14 +477,15 @@ dws drive copy --node <源文件dentryUuid> --folder <目标文件夹fileId> --f > **重要**:`drive list` 返回结果中同时包含 `dentryId` 和 `fileId` 两个字段。所有需要传 `--node` 的命令(info / download / delete)必须使用 `fileId`(即 dentryUuid),**不要使用** `dentryId`。 + ## 注意事项 - 不传 `--space-id` 时默认使用「我的文件」空间 - 不传 `--folder` 时默认操作空间根目录 -- `--folder` 只能使用父文件夹的 `dentryUuid`。不要把 `drive info` 返回的数字型 `dentryId` 当作父目录 -- **`--limit` 有效上限为 50**:CLI 不做本地校验,传 `--limit 100` 不会报错,但服务端每页最多返回 50 条,超出部分无效。用户要求超过 50 条时,应使用 `--limit 50` 配合 `--cursor` 分页查询 +- `--folder` 只能使用父文件夹的 `dentryUuid`。不要把 `drive info` 返回的数字型 `dentryId` 当作父目录;`dentryId` 只用于 `chat message send --dentry-id` +- **`--limit` 最大值为 50**,禁止传入超过 50 的值(如 `--limit 100`)。用户要求超过 50 条时,应使用 `--limit 50` 配合 `--cursor` 分页查询,不要直接传大于 50 的值 - `--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 中要保持一致 +- **上传文件必须使用 `dws drive upload` 命令**,禁止使用 `upload-info` + `curl` + `commit` 三步手动流程 - `--file-name` 必须包含扩展名(如 `report.pdf`) - `download` 需要指定 `--output`,CLI 会把文件保存到本地路径或目录 - 文件名规则:头尾不能有空格;不能含 `*`、`"`、`<`、`>`、`|`、制表符;不能以 `.` 结尾 @@ -550,12 +493,13 @@ dws drive copy --node <源文件dentryUuid> --folder <目标文件夹fileId> --f ## 自动化脚本 -| 脚本 | 场景 | 用法 | -|------|------|------| -| [drive_tree_list.py](../../scripts/drive_tree_list.py) | 递归列出钉盘目录树结构 | `python drive_tree_list.py --depth 2` | + +| 脚本 | 场景 | 用法 | +| ------------------------------------------------------ | ----------- | ------------------------------------- | +| [drive_tree_list.py](../scripts/drive_tree_list.py) | 递归列出钉盘目录树结构 | `python drive_tree_list.py --depth 2` | + ## 相关产品 -- [doc](./doc.md) — 文档内容读写(Markdown/块级编辑/导出),不是文件存储 -- [wiki](./wiki.md) — 知识库/空间管理层(空间列表、节点创建、空间内搜索、成员管理) -- [chat](./chat.md) — 发送图片/文件消息用 `chat message send --msg-type file --file-path` +- [doc](../../dingtalk-doc/references/doc.md) — 文档内容读写/知识库空间,不是文件存储 +- [chat](../../dingtalk-chat/references/chat-commands.md) — 上传文件到 drive 后可通过 Markdown 语法发送图片/文件消息 diff --git a/skills/multi/dingtalk-drive/references/intent-guide.md b/skills/multi/dingtalk-drive/references/intent-guide.md new file mode 100644 index 00000000..e6911e5d --- /dev/null +++ b/skills/multi/dingtalk-drive/references/intent-guide.md @@ -0,0 +1,17 @@ +# drive 局部意图消歧 + +本文件从单 Skill `intent-guide.md` 拆分而来,仅保留与本产品相关的跨产品消歧规则。 + +| 用户说... | 真实意图 | 应该用 | 不要用 | 理由 | +|---|---|---|---|---| +| "参照这个生成同样的 / 按模板生成 / 复刻 X / 同样的模板 X 月份的" + 已有 alidocs URL | 模板保形生成同形态变体 | `drive copy + drive rename + doc block update` → 见 [模板保形生成](../../dingtalk-doc/references/04-document.md#模板保形生成) | `doc read + doc create`(重写链) | adoc → markdown 是有损投影,read+create 会丢行高/单元格背景色/字号;copy 在 adoc 层保形复制后只在副本上局部修改 | +| "读一下这个 xlsx 的数据" / xlsx 节点链接 | 下载本地表格文件 | `dws drive download --node` | `sheet range read` | xlsx / xls / xlsm / csv 是上传的本地文件(`contentType=DOCUMENT`),sheet 命令只支持在线表格,必须下载后本地解析 | +| "把这个在线表格导出为 xlsx 文件" | 在线表格格式转换 | `dws sheet export` | `dws drive download` | `export` 是 axls → xlsx 的导出转换;`download` 只能下载已有的 xlsx 节点 | +| "帮我把这个文件传到网盘" | 钉盘上传 | `drive upload` | — | 文件上传是存储层操作,归 drive | +| "上传文件到钉盘/我的文件" | 钉盘上传 | `drive upload` | — | 提到"钉盘/网盘/我的文件"→ drive | +| "上传文件"(未指定目标) | 默认钉盘 | `drive upload` | — | 未明确目标时默认上传到钉盘 | +| "帮我看看知识库里的文件" | 知识库节点列表 | `wiki node list --workspace` | `drive list` | 明确"知识库"上下文 → wiki node list | +| "列出钉盘团队空间" | 列出钉盘空间 | `wiki space list --type orgSpace` | `drive list-spaces` | 空间管理归 wiki,drive list-spaces 已 deprecated | +| "在知识库里搜方案" | 空间内搜索 | `wiki node search --workspace` | `drive search` | 指定了空间上下文 → wiki node search | +| "搜一下有没有叫XX的文件" | 全局搜索 | `drive search` | `wiki node search` | 未指定空间 → drive search 全局聚合搜索 | +| "整理一下XX项目的所有讨论" | 跨源主题归档 | #5 generate-topic-report | #4 write-doc | #4 侧重单篇文档创作;按主题跨听记/群消息汇总属于工作汇报 | diff --git a/skills/multi/dingtalk-drive/references/lite-recipes.md b/skills/multi/dingtalk-drive/references/lite-recipes.md new file mode 100644 index 00000000..139687b2 --- /dev/null +++ b/skills/multi/dingtalk-drive/references/lite-recipes.md @@ -0,0 +1,44 @@ +# drive Lite Recipe + +本文件从单 Skill `lite-recipes.md` 拆分而来,仅保留与本产品相关的轻量流程。 + +## #4 文档知识 + +### query-doc + +1. 全局搜索:`drive search --query "<关键词>"` → `nodeId`(聚合钉盘+文档空间) +2. 空间内搜索:`wiki node search --workspace --keyword "<关键词>"` → `nodeId` +3. `doc read --node `(按需;大文档只抽章节) + +### list-folder-docs + +`drive list --workspace ` 或 `wiki node list --workspace ` + +### import-file + +将本地文件导入为钉钉在线文档。**一条命令完成上传+格式转换+创建**,无需先读取文件内容。 + +```bash +dws doc import --file ./report.docx --format json +``` + +1. 确认文件路径(用户提供的本地文件路径) +2. 执行:`dws doc import --file <文件路径> --format json`(可选 `--folder <文件夹ID>` / `--workspace <知识库ID>` / `--name "文档名"`) +3. 从返回中提取 `documentUrl`,告知用户导入完成并提供链接 +4. 超时或中断时 CLI 返回 `taskId`,用 `dws doc import get --task-id --format json` 手动查询 + +**`--folder` 参数传值规则**: +- 首选路径:用户提供 alidocs URL 时,直接将完整 URL 传入 `--folder`,无需先调 `doc info` +- 预检路径:若需确认 URL 指向的是文件夹,可先调 `dws doc info --node `: + - `nodeType == "folder"` → 使用 `nodeId` 或原始 URL 作为 `--folder` 值 + - `nodeType` 不是 folder → 提示用户:该链接指向的不是文件夹 +- 禁止:不得使用 `doc info` 返回的 `folderId` 字段作为 `--folder` 的值(`folderId` 是父文件夹 ID,非当前节点 ID) + +格式与文档类型映射: +- `.docx` / `.doc` → 文字文档(DOC) +- `.xlsx` / `.xls` → 电子表格(SHEET) +- `.xmind` / `.mark` → 脑图(MIND) +- `.md` / `.txt` → 文字文档(DOC) + +> **禁止先 Read 文件再 `doc create` + `doc update`**。`doc import` 是服务端格式转换,客户端无需解析文件内容。 +> 详见 [./doc/doc-import.md](../../dingtalk-doc/references/doc/doc-import.md)。 diff --git a/skills/multi/dingtalk-drive/scripts/drive_tree_list.py b/skills/multi/dingtalk-drive/scripts/drive_tree_list.py index 95ccb1c2..a7e3439b 100644 --- a/skills/multi/dingtalk-drive/scripts/drive_tree_list.py +++ b/skills/multi/dingtalk-drive/scripts/drive_tree_list.py @@ -3,16 +3,10 @@ 递归列出钉盘目录树结构(可指定深度) 用法: - python drive_tree_list.py # 列出根目录 - python drive_tree_list.py --depth 2 # 递归 2 层 - python drive_tree_list.py --folder # 指定目录 (传 drive list 返回的 fileId) + python drive_tree_list.py # 列出根目录 + python drive_tree_list.py --depth 2 # 递归 2 层 + python drive_tree_list.py --parent-id # 指定目录 python drive_tree_list.py --dry-run - -说明: - `dws drive list` 返回的每个 item 有两个 ID: - - dentryId:纯数字串,`--folder` 不接受,别用它递归; - - fileId:字母数字串,即 CLI 所称 dentryUuid,`--folder` 只认它。 - 递归子目录必须用 fileId 作为 `--folder` 的值。 """ import sys @@ -44,13 +38,13 @@ def run_dws( def list_dir( - folder: str = '', dry_run: bool = False, + parent_id: str = '', dry_run: bool = False, ) -> list: cmd_args = [ - 'drive', 'list', '--limit', '50', '--format', 'json', + 'drive', 'list', '--max', '50', '--format', 'json', ] - if folder: - cmd_args.extend(['--folder', folder]) + if parent_id: + cmd_args.extend(['--parent-id', parent_id]) data = run_dws(cmd_args, dry_run=dry_run) if not data: return [] @@ -75,7 +69,7 @@ def print_tree( name = item.get('name') or item.get('fileName', '?') item_type = item.get('type') or item.get('dentryType', '') is_dir = str(item_type).lower() in ( - 'folder', 'directory', '1', + 'folder', 'directory', '1', 'FOLDER' ) icon = '📁' if is_dir else '📄' size_str = '' @@ -93,10 +87,10 @@ def print_tree( if is_dir and depth < max_depth: child_prefix = prefix + (' ' if is_last else '│ ') - # `--folder` 只认 fileId (dentryUuid),不认纯数字 dentryId - folder_id = item.get('fileId', '') - if folder_id: - children = list_dir(folder_id, dry_run=dry_run) + dentry_id = (item.get('dentryUuid') + or item.get('id', '')) + if dentry_id: + children = list_dir(dentry_id, dry_run=dry_run) print_tree( children, depth + 1, max_depth, child_prefix, dry_run, @@ -108,8 +102,7 @@ def main(): description='递归列出钉盘目录树' ) parser.add_argument( - '--folder', default='', - help='起始目录 ID (传 drive list 返回的 fileId)', + '--parent-id', default='', help='起始目录 ID' ) parser.add_argument( '--depth', type=int, default=1, @@ -119,10 +112,10 @@ def main(): args = parser.parse_args() args.depth = min(args.depth, 5) - root_name = args.folder or '我的文件' + root_name = args.parent_id or '我的文件' print(f"📁 {root_name}") - items = list_dir(args.folder, dry_run=args.dry_run) + items = list_dir(args.parent_id, dry_run=args.dry_run) if args.dry_run: return if not items: diff --git a/skills/multi/dingtalk-live/SKILL.md b/skills/multi/dingtalk-live/SKILL.md index d86e0b6b..c0cdd9cd 100644 --- a/skills/multi/dingtalk-live/SKILL.md +++ b/skills/multi/dingtalk-live/SKILL.md @@ -1,6 +1,6 @@ --- name: dingtalk-live -description: 钉钉直播。Use when 用户说 直播/我的直播/直播列表。命令前缀:dws live。 +description: 钉钉直播列表与直播记录查询。Use when 用户说直播/我的直播/直播列表/查直播/直播回放/直播记录。视频会议/会议控制当前 CLI 不支持,应提示在钉钉客户端操作;AI 听记/转写摘要走 dingtalk-minutes,群消息走 dingtalk-chat。命令前缀:dws live。 cli_version: ">=0.2.14" metadata: category: product @@ -14,13 +14,12 @@ metadata: > 🧪 **EXPERIMENTAL · 试验版 / Preview** — multi 模式当前未达 stable 标准。全部 dingtalk-* skill 已通过 dispatch verifier,但接口、命名、跨 skill 引用后续可能调整;生产 / 共享环境请优先使用 mono 模式(`dws skill setup --mode mono`)。问题请提 issue 反馈。 -> **PREREQUISITE:** Read the `dws-shared` skill first for auth, global flags, product routing, URL preflight, error codes, and safety rules. The `dws` binary must be on PATH. +## 前置条件 — 执行操作前必读 + +> **`use_skill(dws-shared)`** — 认证、全局参数(`--format json` / `--yes`)、错误码、URL 模板、跨产品消歧、安全规则与 capability 边界。**执行任何 `dws` 命令前先读;** 单产品的清晰命令可直接用本 skill。 -> ⚠️ **命令可用性以当前 dws 二进制为准**。服务发现已下线,本文档随内置 skill 发布;如果 `dws --help` 不存在,说明当前版本未暴露该命令。若命令存在但调用失败,请按错误中的 endpoint 或 tool 提示确认静态端点目录和后端工具注册。实际调用前可用 `dws --help` 或 `--dry-run` 验证。 - - > 命令参考:[live.md](references/live.md)。 ## 意图表 diff --git a/skills/multi/dingtalk-mail/SKILL.md b/skills/multi/dingtalk-mail/SKILL.md index 70b83814..c66611b8 100644 --- a/skills/multi/dingtalk-mail/SKILL.md +++ b/skills/multi/dingtalk-mail/SKILL.md @@ -1,6 +1,6 @@ --- name: dingtalk-mail -description: 钉钉邮箱。Use when 用户说 发邮件/查邮件/回邮件/转发邮件/未读邮件/邮件搜索。Distinct from dingtalk-chat(钉钉消息)、dingtalk-ding(紧急通知)。命令前缀:dws mail。 +description: 钉钉邮箱读写、搜索、回复与转发。Use when 用户说发邮件/查邮件/回邮件/转发邮件/未读邮件/邮件搜索/邮箱附件。一句话解析联系人并确认发送邮件走配方 dingtalk-one-click-email;不做钉钉消息(走 dingtalk-chat)、紧急通知(走 dingtalk-ding)。命令前缀:dws mail。 cli_version: ">=0.2.14" metadata: category: product @@ -14,31 +14,79 @@ metadata: > 🧪 **EXPERIMENTAL · 试验版 / Preview** — multi 模式当前未达 stable 标准。全部 dingtalk-* skill 已通过 dispatch verifier,但接口、命名、跨 skill 引用后续可能调整;生产 / 共享环境请优先使用 mono 模式(`dws skill setup --mode mono`)。问题请提 issue 反馈。 -> **PREREQUISITE:** Read the `dws-shared` skill first for auth, global flags, product routing, URL preflight, error codes, and safety rules. The `dws` binary must be on PATH. +## 前置条件 — 执行操作前必读 + +> **`use_skill(dws-shared)`** — 认证、全局参数(`--format json` / `--yes`)、错误码、URL 模板、跨产品消歧、安全规则与 capability 边界。**执行任何 `dws` 命令前先读;** 单产品的清晰命令可直接用本 skill。 -> ⚠️ **命令可用性以当前 dws 二进制为准**。服务发现已下线,本文档随内置 skill 发布;如果 `dws --help` 不存在,说明当前版本未暴露该命令。若命令存在但调用失败,请按错误中的 endpoint 或 tool 提示确认静态端点目录和后端工具注册。实际调用前可用 `dws --help` 或 `--dry-run` 验证。 +> 渐进式参考:[mail-index.md](references/mail-index.md)。复杂搜索、附件、批量处理、草稿等多步邮件场景参考:[09-mail.md](references/09-mail.md)。先按索引选择专题。 - -> 命令参考:[mail.md](references/mail.md)。复杂搜索、附件、批量处理、草稿等多步邮件场景参考:[09-mail.md](references/09-mail.md)。 +> 旧路径兼容入口:[mail.md](references/mail.md)。 ## 意图表 | 用户说 | 命令 | |--------|------| -| "发邮件给 a@b.com" | `dws mail message send --from <自己邮箱> --to a@b.com --subject "<标题>" --content "<正文>"`(正文规范 flag 是 `--content`,`--body` 为隐藏别名) | +| "发邮件给 a@b.com" | `dws mail mailbox list --format json` → `dws mail message send --from <邮箱> --to a@b.com --subject "<标题>" --content "<正文>" --format json` | +| "回复 / 全部回复 / 转发" | `dws mail message reply` / `reply-all` / `forward` | | "今天未读邮件" | `python scripts/mail_unread_summary.py` | | "带抄送发送" | `python scripts/mail_send_with_cc.py --to a@b.com --cc c@d.com --subject "<标题>" --body "<正文>"` | -## 评测高频硬约束 +## 标准 SOP(必遵流程) -- 用户要"完整内容/看看这封邮件/正文"时,`message search` 命中后必须继续调用 `dws mail message get --email <邮箱> --id --format json`;不要只列候选后停下。 +> 命中以下意图**必须**按对应 SOP 顺序执行;**禁止**跳步、替换命令、编造 email/messageId。每条命令必须带 `--format json`。收件邮箱**必须**真实解析,**禁止**猜测。 + +### SOP-1 拿邮箱(get-mailbox) + +**触发**:我的邮箱/发件需要邮箱/查邮件需要邮箱。 + +1. **执行(必须)**:`dws mail mailbox list --format json`,取自己的 `email`(默认选企业邮箱 `type:ORG`);查**他人**邮箱用 `dws mail user search --keyword "<姓名>" --format json`,**禁止**用 `mailbox list` 查他人。 + +**禁止**:把 `mailbox list` 当作他人邮箱查询、猜测邮箱地址。 + +### SOP-2 查 / 搜邮件(search-mail) + +**触发**:查邮件/搜邮件/某主题邮件/某人发的邮件。 + +1. **拿邮箱(必须)**:先按 SOP-1 取 `email`;用户已明确提供可跳过。 +2. **执行(必须)**:浏览文件夹 `dws mail message list --email <邮箱> --limit --format json`;KQL 搜索 `dws mail message search --email <邮箱> --query "" --limit 20 --format json`(KQL 如 `subject:周报`、`from:alice@x.com`、`folderId:2`、`hasAttachments:true`,**只通过 `--query` 传**)。 +3. **取正文(必须)**:`dws mail message get --email <邮箱> --id --format json`;`messageId` 从列表/搜索结果取,**禁止**编造。 + +**禁止**:把 KQL 拆成多个 flag、跳过 `message list/search` 直接猜 messageId。 + +### SOP-3 发邮件(send-mail) + +**触发**:发邮件/写邮件/群发。 + +1. **发件邮箱(必须)**:`dws mail mailbox list` 取自己邮箱。 +2. **收件邮箱(必须)**:地址直接用;姓名按 [mail-message-commands.md](references/mail-message-commands.md) “查找他人邮箱地址”流程(`mail user search` 等)获取,**禁止**猜测。 +3. **执行(必须)**:`dws mail message send --from <发件邮箱> --to <收件邮箱> --subject "<主题>" --content "<正文>" --format json`;按需 `--cc`/`--attachment`/`--inline-attachment`。 +4. **验证(必须)**:从返回取 `internetMessageId`,可 `dws mail message verify --email <发件邮箱> --internet-message-id --format json` 查发送状态。 + +**完成条件**:收件邮箱来自真实解析结果,且发送返回或 `message verify` 显示成功。 + +### SOP-4 回复 / 转发(reply-forward) + +**触发**:回复邮件/回复全部/转发。 + +1. **拿邮箱 + 原邮件(必须)**:SOP-1 取邮箱;用户未给 `messageId` 时**必须**先走 SOP-2 定位原邮件 `messageId`。 +2. **执行(必须)**:回复 `dws mail message reply --from <邮箱> --id --content "<正文>" --format json`;回复全部用 `reply-all`;转发 `dws mail message forward --from <邮箱> --to <收件邮箱> --id --content "<附言>" --format json`。 + +**禁止**:未定位原邮件就回复/转发、编造 messageId。 + +## 高频硬约束 + +- 用户要“完整内容 / 看看这封邮件 / 正文”时,`message search` 只负责定位 messageId,随后用 `dws mail message get --email <邮箱> --id --format json` 获取正文。 - 搜到多封邮件时,若用户给了明确主题、附件名、发件人或时间线索,先选最匹配的一封执行 `message get`;只有同等候选无法判断时才询问用户。 -- 写入类操作(发送)按安全策略确认;只读查看、搜索不需要确认。 +- 附件链路固定三步:`message search` → `attachment list --email <邮箱> --id ` → `attachment download --email <邮箱> --message-id --attachment-id --name <文件名>`;不存在批量下载命令。 +- 写入类操作(发送、回复、转发、删除、批量移动)按安全策略确认;只读查看、搜索、附件列表、下载不需要确认。 - 所有 `dws mail` 命令加 `--format json`,并复用同一封邮件的 `messageId`,不要重新搜索导致目标漂移。 ## 跨产品协作 - 收件人是人名 → 先用 `dingtalk-contact` 取 `orgAuthEmail` - 钉钉内消息 → 切到 `dingtalk-chat` +## 局部意图与 Recipe + +- [局部意图消歧](references/intent-guide.md);[Lite Recipe](references/lite-recipes.md)。 diff --git a/skills/multi/dingtalk-mail/references/09-mail.md b/skills/multi/dingtalk-mail/references/09-mail.md index 5ac7a50f..d0b64cc9 100644 --- a/skills/multi/dingtalk-mail/references/09-mail.md +++ b/skills/multi/dingtalk-mail/references/09-mail.md @@ -1,13 +1,13 @@ # 邮件 -> **SKILL.md** 中 #9 内联 4 条 **lite**:`mail-list-mailbox`、`mail-search`、`mail-send`、`mail-reply-forward`,见 `dws-shared/references/best_practices/_common/lite-recipes.md`。下列 recipe、专用规则与消歧请在命中 #9 且**超出**上述 lite 时阅读本文。 -> 产品命令见 [mail.md](./mail.md)。通用批量/并行见 `dws-shared/references/best_practices/_common/conventions.md`。 +> 4 条 **lite**(`mail-list-mailbox`、`mail-search`、`mail-send`、`mail-reply-forward`)见 [lite-recipes.md](./lite-recipes.md)。下列 recipe、专用规则与消歧请在超出 lite 场景时阅读本文。 +> 产品命令见 [mail.md](./mail-command-index.md)。通用批量/并行见 [_common/conventions.md](_common/conventions.md)。 ## 专用规则(#9 非 lite 步骤必守) -- **KQL 语法强制**:邮件搜索的查询条件**只能**通过 `--query` 参数以 KQL 语法传入(如 `subject:周报`),**禁止臆造** `--subject`、`--sender`、`--from-address` 等不存在的 flag。详见 [mail.md](./mail.md) 中 KQL 查询字段说明。 -- **邮箱地址前置**:大部分邮件命令需要 `--email` 或 `--from` 参数,执行前**必须**先通过 `mail mailbox list` 获取当前用户邮箱(返回字段是 `emailAccounts`),禁止猜测邮箱地址。 -- **查找他人邮箱**:需要获取某人邮箱地址时,**不要用 `mailbox list`**(只返回自己的),必须走三路并发查询流程(见 [mail.md](./mail.md) 中「查找他人邮箱地址」章节)。 +- **KQL 语法强制**:邮件搜索的查询条件**只能**通过 `--query` 参数以 KQL 语法传入(如 `subject:周报`),**禁止臆造** `--subject`、`--sender`、`--from-address` 等不存在的 flag。详见 [mail-message-commands.md](./mail-message-commands.md) 中 KQL 查询字段说明。 +- **邮箱地址前置**:大部分邮件命令需要 `--email` 或 `--from` 参数,执行前**必须**先通过 `mail mailbox list` 获取当前用户邮箱,禁止猜测邮箱地址。 +- **查找他人邮箱**:需要获取某人邮箱地址时,**不要用 `mailbox list`**(只返回自己的),必须走三路并发查询流程(见 [mail-message-commands.md](./mail-message-commands.md) 中「查找他人邮箱地址」章节)。 - **附件下载三步走**:先 `message search` 搜索邮件获取 messageId,再 `attachment list` 获取附件 ID 和文件名,最后逐个 `attachment download` 下载。**不存在 `download_batch` / `download_all` 等批量下载命令,禁止编造**。 - **危险操作确认**:`batch-delete` 执行前必须向用户确认,同意后加 `--yes`。 @@ -24,33 +24,22 @@ | Recipe | 步骤 | |--------|------| | `mail-get` | `mail message get --email <邮箱> --id ` → 查看邮件完整内容(含正文) | -| `mail-message-list` | `mail message list --email <邮箱>` → 列出文件夹中的邮件(默认收件箱);`--folder-id ` 指定文件夹、`--limit ` 分页 | | `mail-folder-list` | `mail folder list --email <邮箱>` → 列举文件夹;`--folder ` 查子文件夹 | -| `mail-folder-manage` | `mail folder create/update/delete`:创建/重命名/删除文件夹(先 `folder list` 取 `folders[].id`) | | `mail-tag-list` | `mail tag list --email <邮箱>` → 列举邮件标签 | -| `mail-tag-manage` | `mail tag create/update/delete`:创建/重命名/删除标签(先 `tag list` 取 `tags[].id`,系统标签不可改删) | | `mail-thread-get` | `mail thread get --email <邮箱> --id ` → 获取会话(邮件线程)详情 | -| `mail-thread-list` | `mail thread list --email <邮箱> --folder --limit ` → 列出文件夹下的会话 | -| `mail-thread-manage` | `mail thread update/batch-update`(标记已读未读/加移标签)、`thread trash/batch-trash`(移入已删除,须 `--yes`) | | `mail-attachment-list` | `mail attachment list --email <邮箱> --id ` → 列举指定邮件的附件 | | `mail-attachment-download` | 1. `mail attachment list --email <邮箱> --id ` → 取附件 `id` 和 `name`
2. `mail attachment download --email <邮箱> --message-id --attachment-id --name <文件名>` | | `mail-batch-move` | `mail message batch-move --email <邮箱> --ids --folder `(常用 folderId: 2=收件箱, 6=已删除) | -| `mail-batch-delete` | `mail message batch-delete --email <邮箱> --ids `(**危险操作,须先确认**;仅移入已删除文件夹,非物理永久删除) | -| `mail-batch-update` | `mail message batch-update --email <邮箱> --ids --action markRead/markUnread/addTags/removeTags`(加移标签需 `--tags`) | -| `mail-verify` | `mail message verify --email <邮箱> --internet-message-id ` → 查发送状态(`sendStatus` 在 `message.sendStatus`) | -| `mail-draft-create` | `mail draft create --from <邮箱> --subject "<标题>"` → 取草稿 ID `result.message.id`(可选 `--to`、`--content`、`--cc`) | +| `mail-batch-delete` | `mail message batch-delete --email <邮箱> --ids --yes`(**危险操作,须先确认**) | +| `mail-draft-create` | `mail draft create --from <邮箱> --subject "<标题>"` → 取 `messageId`(可选 `--to`、`--content`、`--cc`) | | `mail-draft-update` | `mail draft update --from <邮箱> --id --subject "<新标题>"`(可选 `--content`、`--to`、`--cc`) | | `mail-draft-send` | `mail draft send --from <邮箱> --id ` | -| `mail-template` | `mail template create/list/get/update/delete`(草稿模板 `--is-draft` 不出现在 list,只能 get;非草稿模板不可 update) | -| `mail-contact` | `mail contact create/list/update/batch-delete`:管理个人联系人(非通讯录搜人,搜人用 `user search`) | -| `mail-auto-reply-get` | `mail auto-reply get --email <邮箱>` → 获取自动回复配置 | -| `mail-rule` | `mail rule list/create/update/delete/adjust`:管理个人收信规则 | ## Full / 多步组合 | Recipe | 行动指南(固定路线) | |--------|---------------------| -| search-and-download-attachment | 1. `mail mailbox list` → 从 `emailAccounts` 取邮箱
2. `mail message search --email <邮箱> --query "" --limit 20` → 取 `messageId` 列表
3. 对每封邮件执行 `mail attachment list --email <邮箱> --id ` → 列出附件取 `id` 和 `name`
4. 对每个附件逐个执行 `mail attachment download --email <邮箱> --message-id --attachment-id --name <文件名>`(**仅支持逐个下载,不存在批量下载命令**) | +| search-and-download-attachment | 1. `mail mailbox list` → 取邮箱
2. `mail message search --email <邮箱> --query "" --limit 20` → 取 `messageId` 列表
3. 对每封邮件执行 `mail attachment list --email <邮箱> --id ` → 列出附件取 `id` 和 `name`
4. 对每个附件逐个执行 `mail attachment download --email <邮箱> --message-id --attachment-id --name <文件名>`(**仅支持逐个下载,不存在批量下载命令**) | | search-reply-forward | 1. `mail mailbox list` → 取邮箱
2. `mail message search --email <邮箱> --query "" --limit 10` → 取 `messageId`
3. 展示搜索结果供用户选择
4. 按用户指示执行 reply / reply-all / forward(参见 lite `mail-reply-forward`) | -| batch-mail-cleanup | 1. `mail mailbox list` → 从 `emailAccounts` 取邮箱
2. `mail message search --email <邮箱> --query "" --limit 100` → 取多个 `messageId`
3. 展示列表供用户确认
4. `mail message batch-move --email <邮箱> --ids --folder 6 ` 或 `batch-delete` **都是移入「已删除」文件夹,并非物理永久删除**(对已在已删除文件夹的邮件再执行返回 success 但无效;CLI 无永久删除路径,需在客户端手动清空) | -| send-to-person-by-name | 1. `mail mailbox list` → 取发件邮箱
2. 走「查找他人邮箱地址」三路并发查询获取收件人邮箱(见 [mail.md](./mail.md))
3. `mail message send --from <发件邮箱> --to <收件邮箱> --subject "<标题>" --content "<内容>"` | +| batch-mail-cleanup | 1. `mail mailbox list` → 取邮箱
2. `mail message search --email <邮箱> --query "" --limit 100` → 取多个 `messageId`
3. 展示列表供用户确认
4. `mail message batch-move --email <邮箱> --ids --folder 6 ` 移到已删除;或 `batch-delete` 永久删除 | +| send-to-person-by-name | 1. `mail mailbox list` → 取发件邮箱
2. 走「查找他人邮箱地址」三路并发查询获取收件人邮箱(见 [mail-message-commands.md](./mail-message-commands.md))
3. `mail message send --from <发件邮箱> --to <收件邮箱> --subject "<标题>" --content "<内容>"` | diff --git a/skills/multi/dingtalk-mail/references/_common/conventions.md b/skills/multi/dingtalk-mail/references/_common/conventions.md new file mode 100644 index 00000000..75491aab --- /dev/null +++ b/skills/multi/dingtalk-mail/references/_common/conventions.md @@ -0,0 +1,44 @@ +# 业务域通用规范 + +> 仅服务本 skill 已迁入的行动指南。安全门控、危险操作确认、`--format json` 等已在本 skill 的 `SKILL.md` 中定义,此处不重复。 + +## 批量查询规范 + +| # | 规范 | +|---|------| +| 1 | **并行查详情**:拿到多个 ID 后,用 `&` 合并到同一条 Shell 命令并行执行 + `wait`,**严禁逐条串行** | +| 2 | **翻页**:分页接口须拉全直至无更多 | +| 3 | **优先批量 API**:有批量接口则用批量;无则按 #1 并行 | +| 4 | **群消息**:必须先 `chat search --query` 得 `openConversationId`,再 `chat message list --group`;多群同条命令并行 | +| 5 | **列表少轮次**:带条件搜索/列表 → 一次采全详情;**禁止**无新参数时重复同一 `list` / `search` | + +## 多源并行采集(公共模式) + +> recipe 引用方式:`按「多源并行采集」执行(关键词=,时间=至)`。 + +- 同条 Shell:`&` 并行 + `wait`;分页须采全。 +- 只保留与主题相关的数据,无关丢弃。 +- 有批量详情接口优先;否则并行拉详情(见上表 #1)。 +- 具体采哪些产品列表由对应 **行动指南 recipe** 与当前产品参考决定;不要引入本文档未覆盖的产品路线。 + +## 字段术语与 ID 传递 + +> list 返回 JSON 后,必须提取下表字段传给后续命令。**禁止用其他字段替代。** + +| 字段 | 来源 | 传递给 | +|------|------|--------| +| `taskUuid` | `minutes list` | `minutes get summary/info/batch --id(s)` | +| `userId` | `aisearch person` / `contact user search` / `contact dept list-members` | `contact user get --ids`、`todo --executors`、`calendar --users` | +| `deptId` | `contact dept search` | `contact dept list-members --depts`;多子部门时对每个子部门分别 `dept search` 取 id | +| `nodeId` | `drive search` / `wiki node search` | `doc read/update --node`、`drive copy/move/rename/delete --node` | +| `nodeId` | `wiki node list` 中的 folder 类型节点 / `wiki node create --type folder` | `wiki node list --folder`、`wiki node create --folder`、`drive upload --folder`、`drive copy/move --folder` | +| `eventId` | `calendar event list` | `calendar event get/update --id` | +| `processInstanceId` | `oa approval list-*` | `oa approval detail/approve --instance-id` | +| `openConversationId` | `chat search` | `chat message list/send --group` | +| `todoTaskId` | `todo task list` | `todo task update/done --task-id` | +| `reportId` | `report inbox list` / `report outbox list` | `report entry get/stats --report-id` | +| `baseId` / `tableId` | `aitable base search` | `aitable record query --base-id --table-id` | +| `dentryUuid` | `drive list` / `drive mkdir` | `drive info/download --node`、`drive list/mkdir/upload --folder` | +| `dentryId` | `drive info` 的数字字段 | 仅用于 `chat message send --dentry-id` | + +**ID 边界硬约束**:`dentryId` 通常是纯数字,只表示聊天文件消息需要的钉盘条目数字 ID;它不是父目录 ID。遇到 `drive --node/--folder`、`doc --node/--folder`、`wiki node --folder` 时,只能使用 `dentryUuid` / `nodeId` / 文档 URL。若当前上下文只有数字型 `dentryId`,必须先重新 `drive list` / `drive search` / `wiki node list` 获取正确 ID,不能把该数字直接代入后续命令。 diff --git a/skills/multi/dingtalk-mail/references/intent-guide.md b/skills/multi/dingtalk-mail/references/intent-guide.md new file mode 100644 index 00000000..9a012701 --- /dev/null +++ b/skills/multi/dingtalk-mail/references/intent-guide.md @@ -0,0 +1,8 @@ +# mail 局部意图消歧 + +本文件从单 Skill `intent-guide.md` 拆分而来,仅保留与本产品相关的跨产品消歧规则。 + +| 用户说... | 真实意图 | 应该用 | 不要用 | 理由 | +|---|---|---|---|---| +| "搜一下智能化方案/最近 OKR 相关邮件/最近发版相关消息" | 搜企业知识内容 | `aisearch enterprise` | `doc search` / `mail search` / `chat message search` | 跨文档、消息、日程、听记、邮件等企业内容语义检索走 enterprise;具体 `queries/types/time-range` 抽槽见 `aisearch.md` | +| "我发给某人的消息/邮件/文档/今天我干了什么" | 搜行为记录 | `aisearch behavior` | `chat` / `mail` / `doc` / `report` | 关注“谁对什么做过什么”,走 behavior;具体 `behavior-type/direction/chat-scope` 抽槽见 `aisearch.md` | diff --git a/skills/multi/dingtalk-mail/references/lite-recipes.md b/skills/multi/dingtalk-mail/references/lite-recipes.md new file mode 100644 index 00000000..eb23dc5d --- /dev/null +++ b/skills/multi/dingtalk-mail/references/lite-recipes.md @@ -0,0 +1,45 @@ +# mail Lite Recipe + +本文件从单 Skill `lite-recipes.md` 拆分而来,仅保留与本产品相关的轻量流程。 + +## #9 邮件 + +### mail-list-mailbox + +查询当前用户自己的可用邮箱地址列表。**仅返回自己的邮箱**,不能查他人邮箱(查他人邮箱请走 [mail-message-commands.md](./mail-message-commands.md) 中「查找他人邮箱地址」三路并发查询流程)。 + +`mail mailbox list` + +### mail-search + +搜索邮件。**必须使用 KQL 语法通过 `--query` 传递查询条件**,禁止臆造 `--subject`、`--from` 等不存在的 flag。 + +1. 获取邮箱地址:`mail mailbox list` → 取用户邮箱地址。若用户已提供邮箱可跳过。 +2. 构造 KQL 查询:根据用户意图将搜索条件转为 KQL 表达式(详见 [mail-message-commands.md](./mail-message-commands.md) 中 KQL 查询字段说明)。 + - 按主题:`subject:周报`、`subject:"项目 进展"`(含空格须加双引号) + - 按发件人:`from:alice@company.com` 或 `from:"张三"` + - 按日期:`date>2025-06-01T00:00:00Z`(ISO8601 格式,必须含时间部分) + - 按文件夹:`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 "" --limit 20` +4. 查看详情(按需):`mail message get --email <邮箱> --id ` + +### mail-send + +发送邮件。 + +1. 获取邮箱地址:`mail mailbox list` → 取用户邮箱作为 `--from`。 +2. 确定收件人:用户直接提供邮箱地址 → 直接使用;用户提供姓名或工号 → 走「查找他人邮箱地址」三路并发流程(见 [mail-message-commands.md](./mail-message-commands.md))。 +3. 发送:`mail message send --from <发件邮箱> --to <收件邮箱> --subject "<主题>" --content "<正文>"`(可选 `--cc`、`--attachment`、`--inline-attachment`)。 + +### mail-reply-forward + +回复或转发邮件。 + +1. 获取邮箱地址:`mail mailbox list` → 取用户邮箱。 +2. 定位原始邮件:若用户未提供 messageId → 先用 `mail-search` 搜索定位。 +3. 执行: + - 回复:`mail message reply --from <邮箱> --id `(可选 `--to`、`--subject`、`--content`) + - 回复全部:`mail message reply-all --from <邮箱> --id `(可选 `--to`、`--subject`、`--content`) + - 转发:`mail message forward --from <邮箱> --to <收件邮箱> --id `(可选 `--subject`、`--content`) diff --git a/skills/multi/dingtalk-mail/references/mail-command-index.md b/skills/multi/dingtalk-mail/references/mail-command-index.md new file mode 100644 index 00000000..9c638211 --- /dev/null +++ b/skills/multi/dingtalk-mail/references/mail-command-index.md @@ -0,0 +1,78 @@ +# 命令速查目录 + +## 按需加载详细参数 + +| 命令族 | 详见 | +|---|---| +| 邮箱选择、邮件列表/搜索/读取/发送 | [`mail-message-commands.md`](./mail-message-commands.md) | +| 文件夹、附件、标签 | [`mail-folder-attachment-tag-commands.md`](./mail-folder-attachment-tag-commands.md) | +| 会话、回复/转发、批量动作、撤回 | [`mail-thread-actions.md`](./mail-thread-actions.md) | +| 草稿、用户搜索、模板、联系人 | [`mail-draft-directory-commands.md`](./mail-draft-directory-commands.md) | +| 自动回复、黑白名单、收信规则 | [`mail-policy-commands.md`](./mail-policy-commands.md) | + +## 命令速查目录 + +| 命令 | 功能简述 | +|------|----------| +| `dws mail mailbox list` | 查询**当前用户自己**的可用邮箱列表 | +| `dws mail mailbox profile` | 获取用户邮箱详细信息(容量、别名等) | +| `dws mail message list` | 列出指定文件夹中的邮件(默认收件箱) | +| `dws mail message search` | 搜索邮件(KQL 语法,按主题/发件人/日期等) | +| `dws mail message get` | 查看邮件完整内容(含正文) | +| `dws mail message send` | 发送邮件(支持附件/内联图片) | +| `dws mail message reply` | 回复邮件(支持附件/内联图片) | +| `dws mail message reply-all` | 回复所有人(支持附件/内联图片) | +| `dws mail message forward` | 转发邮件(支持附件/内联图片) | +| `dws mail message batch-move` | 批量移动邮件到指定文件夹 | +| `dws mail message batch-delete` | 批量删除邮件 | +| `dws mail message batch-update` | 批量修改邮件状态(标记已读/未读/添加标签/移除标签) | +| `dws mail message batch-get` | 批量获取邮件详情(最多 20 封) | +| `dws mail message verify` | 根据 internetMessageId 查询邮件发送状态 | +| `dws mail sent-message recall` | 撤回已发送的邮件(仅支持同组织内未读邮件) | +| `dws mail sent-message recall-detail` | 查询邮件撤回进度 | +| `dws mail draft create` | 创建草稿(保留在草稿箱,不发送) | +| `dws mail draft update` | 更新草稿内容(保留在草稿箱,不发送) | +| `dws mail draft send` | 发送草稿箱中已有的草稿 | +| `dws mail folder list` | 列举邮件文件夹 | +| `dws mail folder create` | 创建邮件文件夹 | +| `dws mail folder delete` | 删除邮件文件夹 | +| `dws mail folder update` | 更新邮件文件夹名称 | +| `dws mail attachment list` | 列举指定邮件的所有附件 | +| `dws mail attachment download` | 下载邮件附件到本地(**仅支持逐个下载,不支持批量下载**) | +| `dws mail tag list` | 列举邮件标签 | +| `dws mail tag create` | 创建邮件标签 | +| `dws mail tag delete` | 删除邮件标签 | +| `dws mail tag update` | 更新邮件标签名称 | +| `dws mail thread list` | 列出指定邮箱、指定文件夹下的邮件会话 | +| `dws mail thread get` | 获取会话详情 | +| `dws mail thread update` | 修改单个邮件会话的状态或标签(标记已读/未读/添加标签/移除标签) | +| `dws mail thread batch-update` | 批量修改邮件会话的状态或标签(单次最多 100 个) | +| `dws mail thread trash` | 将单个邮件会话移动到已删除文件夹(不会永久删除) | +| `dws mail thread batch-trash` | 将多个邮件会话批量移动到已删除文件夹(单次最多 100 个,不会永久删除) | +| `dws mail user search` | 搜索通讯录用户(**按姓名或工号查他人邮箱**,不是搜邮件) | +| `dws mail template create` | 创建邮件模板 | +| `dws mail template list` | 列举邮件模板 | +| `dws mail template get` | 获取邮件模板详情 | +| `dws mail template update` | 更新邮件模板 | +| `dws mail template delete` | 删除邮件模板 | +| `dws mail contact create` | 创建个人邮件联系人(添加到自己的联系人列表) | +| `dws mail contact list` | 列举个人邮件联系人(查看自己保存的联系人,**不是搜索通讯录用户**) | +| `dws mail contact update` | 更新个人邮件联系人信息 | +| `dws mail contact batch-delete` | 批量删除个人邮件联系人 | +| `dws mail auto-reply get` | 获取用户的自动回复配置 | +| `dws mail auto-reply update` | 更新/设置用户的自动回复配置 | +| `dws mail allow-list list` | 列出个人收信白名单 | +| `dws mail allow-list add` | 添加个人收信白名单 | +| `dws mail allow-list remove` | 移除个人收信白名单 | +| `dws mail block-list list` | 列出个人收信黑名单 | +| `dws mail block-list add` | 添加个人收信黑名单 | +| `dws mail block-list remove` | 移除个人收信黑名单 | +| `dws mail rule list` | 列出个人收信规则 | +| `dws mail rule create` | 创建个人收信规则 | +| `dws mail rule update` | 更新个人收信规则 | +| `dws mail rule delete` | 删除个人收信规则 | +| `dws mail rule adjust` | 调整收信规则排序 | + +> **查找他人邮箱**(如「获取严龙的邮箱」)→ **不要用 `mailbox list`**,应走三路并发查询,详见「查找他人邮箱地址」章节。 + +--- diff --git a/skills/multi/dingtalk-mail/references/mail-common-errors.md b/skills/multi/dingtalk-mail/references/mail-common-errors.md new file mode 100644 index 00000000..7f010fae --- /dev/null +++ b/skills/multi/dingtalk-mail/references/mail-common-errors.md @@ -0,0 +1,9 @@ +# 通用错误说明 + +## 通用错误说明 + +以下错误适用于所有 mail 命令。 + +| 错误标识 | 含义 | 处理建议 | +|----------|------|----------| +| `domain.notFound` | 该用户的邮箱不是由钉钉邮箱托管,无法完成操作 | 确认邮箱是否已开通钉钉企业邮箱服务 | diff --git a/skills/multi/dingtalk-mail/references/mail-context.md b/skills/multi/dingtalk-mail/references/mail-context.md new file mode 100644 index 00000000..5c8c6b83 --- /dev/null +++ b/skills/multi/dingtalk-mail/references/mail-context.md @@ -0,0 +1,22 @@ +# 上下文传递表 + +## 上下文传递表 + +| 操作 | 从返回中提取 | 用于 | +|------|-------------|------| +| `mailbox list` | 邮箱地址 | message search/get/send/thread get 的 --email/--from | +| `message search` | `messageId` | message get 的 --id | +| `message search` | `conversationId` | thread get 的 --id | +| `message search` | `messageId` | attachment list 的 --id | +| `attachment list` | `attachments[].id` / `attachments[].name` | attachment download 的 --attachment-id / --name | +| `message get` | `conversationId` | thread get 的 --id | +| `folder list` | `folders[].id` | folder create 的 --folder;folder delete/update 的 --id | +| `folder create` | `result.folder.id` | 后续创建子文件夹或移动邮件时作为 --folder;更新/删除该文件夹时作为 --id | +| `folder list` | `folders[].id` | thread list 的 --folder | +| `thread list` | `conversations[].id` | thread get/update/trash/batch-update/batch-trash 的 --id/--ids | +| `tag list` | `tags[].id` | thread update/batch-update 的 --tag-ids | +| `tag list` | `tags[].id` | tag create 的 --parent-id;tag delete/update 的 --id | +| `tag create` | `result.tag.id` | 后续创建子标签时作为 --parent-id;更新/删除该标签时作为 --id | +| `aisearch person` → `contact user get` / `contact user search` / `mail user search` | 用户邮箱 (orgAuthEmail / email) | message send 的 --to/--cc(三路并发,取先到结果) | +| `user search` | 用户邮箱 (email) | message send 的 --to/--cc | +| `message send` / `draft send` / `message reply` / `message reply-all` / `message forward` | `internetMessageId` | `message verify` 的 --internet-message-id | diff --git a/skills/multi/dingtalk-mail/references/mail-default-mailbox-rules.md b/skills/multi/dingtalk-mail/references/mail-default-mailbox-rules.md new file mode 100644 index 00000000..3ade2717 --- /dev/null +++ b/skills/multi/dingtalk-mail/references/mail-default-mailbox-rules.md @@ -0,0 +1,21 @@ +# 默认邮箱选择规则(重要) + +## 默认邮箱选择规则(重要) + +所有 mail 相关命令,**除非用户明确要求使用个人邮箱,否则一律默认使用企业邮箱**。 + +**适用范围:** 任何需要传入 `--email` / `--from` / `--sender` 参数的 mail 子命令一律适用。 + +**默认选择策略:** + +1. 调用 `dws mail mailbox list --format json` 获取当前用户的所有邮箱。 +2. 从返回的 `mailboxes` 中**优先选择企业邮箱**(账号类型为企业邮箱、域名非 `@dingtalk.com` 的邮箱),将其作为 `--email` / `--from` 的默认值。 +3. 仅当用户在指令中**明确指定**「用我的个人邮箱」「用 dingtalk.com 邮箱」「用我的私人邮箱」等表述时,才选择个人邮箱(`@dingtalk.com` 域名)。 +4. 若用户同时拥有多个企业邮箱(如分属多家公司),优先选择与当前会话上下文匹配的企业邮箱;若仍无法判断,向用户确认后再操作。 +5. 若用户**仅拥有个人邮箱**(无企业邮箱),可直接使用个人邮箱,但需注意 `mail user search` 等仅企业邮箱可用的命令会因权限报错,需走「查找他人邮箱地址」章节的替代路径。 + +**触发个人邮箱的关键词举例:** 「我的个人邮箱」「私人邮箱」「dingtalk.com 邮箱」「@dingtalk 的邮箱」「我的 personal 邮箱」。 + +> 该规则覆盖文档后续所有命令示例:示例中虽以 `user@company.com` 等占位邮箱书写,实际执行时**必须按上述策略动态选择企业邮箱**,不要直接照抄示例中的邮箱字面量,更不要默认使用 `@dingtalk.com` 个人邮箱。 + +--- diff --git a/skills/multi/dingtalk-mail/references/mail-draft-directory-commands.md b/skills/multi/dingtalk-mail/references/mail-draft-directory-commands.md new file mode 100644 index 00000000..1da0423d --- /dev/null +++ b/skills/multi/dingtalk-mail/references/mail-draft-directory-commands.md @@ -0,0 +1,259 @@ +# 草稿、用户、模板与联系人命令 + +### 创建草稿 +``` +Usage: + dws mail draft create [flags] +Example: + dws mail draft create --from user@company.com --to colleague@company.com \ + --subject "草稿标题" --content "草稿正文" + dws mail draft create --from user@company.com --subject "草稿标题" + dws mail draft create --from user@company.com --subject "带附件草稿" \ + --content "见附件" --attachment ./report.pdf + dws mail draft create --from user@company.com --subject "带图片草稿" \ + --content "图表:[inline:chart.png]" --inline-attachment ./chart.png +Flags: + --from string 发件人邮箱 (必填),别名: --sender + --subject string 邮件标题 (必填) + --to string 收件人列表(可选,有确定收件人时才传) + --cc string 抄送人列表(可选,有确定抄送人时才传) + --content string 邮件正文(可选,有正文内容时才传),别名: --body + --attachment stringArray 附件文件路径,可多次指定 (可选) + --inline-attachment stringArray 内联图片路径,可多次指定,cid 自动生成 (可选) +``` + +> **注意:** `--to`、`--cc`、`--content` 均为可选参数,**仅在用户明确提供对应信息时才传入**。若用户未指定收件人,不要传 `--to ""`(空字符串)。 + +**附件说明:** + +指定 `--attachment` 或 `--inline-attachment` 时,CLI 自动完成草稿创建和附件上传,**草稿保留在草稿箱,不会发送**。内联图片用法同 `message send`(`--content` 中使用 `[inline:文件名]` 占位符)。 + +**返回字段:** + +| 字段 | 类型 | 说明 | +|------|------|------| +| `messageId` | `string` | 新建草稿的邮件 ID | + +### 更新草稿 +``` +Usage: + dws mail draft update [flags] +Example: + dws mail draft update --from user@company.com --id --subject "新标题" --content "新正文" + dws mail draft update --from user@company.com --id --content "见附件" --attachment ./report.pdf + dws mail draft update --from user@company.com --id \ + --content "图表:[inline:chart.png]" --inline-attachment ./chart.png +Flags: + --from string 发件人邮箱 (必填),别名: --sender + --id string 草稿邮件 ID (必填) + --to string 收件人列表(可选) + --cc string 抄送人列表(可选) + --subject string 邮件标题(可选) + --content string 邮件正文(可选),别名: --body + --attachment stringArray 附件文件路径,可多次指定 (可选) + --inline-attachment stringArray 内联图片路径,可多次指定,cid 自动生成 (可选) +``` + +**附件说明:** + +指定 `--attachment` 或 `--inline-attachment` 时,CLI 自动完成草稿更新和附件上传,**草稿保留在草稿箱,不会发送**。内联图片用法同 `message send`(`--content` 中使用 `[inline:文件名]` 占位符)。 + +### 发送草稿 +``` +Usage: + dws mail draft send [flags] +Example: + dws mail draft send --from user@company.com --id +Flags: + --from string 发件人邮箱 (必填),别名: --sender + --id string 草稿邮件 ID (必填) +``` + +将草稿箱中已有的草稿发送出去。草稿 ID 来自 `draft create` 或 `message search`(`folderId:5`)的返回结果。 + +### 搜索邮箱用户(通讯录) +``` +Usage: + dws mail user search [flags] +Example: + dws mail user search --keyword "张三" + dws mail user search --email user@company.com --keyword "张三" + dws mail user search --email user@company.com --keyword "alice" --limit 10 + dws mail user search --email user@company.com --keyword "alice" --cursor + dws mail user search --email user@company.com --employee-no "E123456" +Flags: + --email string 搜索目标邮箱地址 (可选) + --keyword string 搜索关键词(未提供 --employee-no 时为必填) + --employee-no string 按工号搜索用户;提供此参数时 keyword 不再必填 + --cursor string 分页游标,取自响应中的 nextCursor 字段(可选) + --limit string 每页返回数量(可选),别名: --size +``` + +> **重要区别(三个容易混淆的命令):** +> - `mail user search` — 搜索**企业通讯录用户**(按姓名/关键词或工号找人),用于获取某人的邮箱地址。需要企业邮箱权限。 +> - `mail contact list` — 列举**个人联系人**(用户自己保存/创建的联系人列表),不需要关键词,返回自己的联系人。 +> - `mail message search` — 搜索**邮件内容**(按 KQL 语法搜邮件,如主题、发件人、日期等) +> +> 不要混淆:查找"某人的邮箱地址"用 `user search`;查看"自己保存的联系人"用 `contact list`;查找"某封邮件"用 `message search`。 +> +> 仅企业邮箱(非 `@dingtalk.com` 个人邮箱)可使用 `user search`;使用个人邮箱调用将因无权限而报错。 +> +> `--keyword` 与 `--employee-no` 至少需要提供一个;当提供 `--employee-no` 时,`--keyword` 不再是必填字段。 + +**返回字段:** + +| 字段 | 类型 | 说明 | +|------|------|------| +| `users` | `List[]` | 匹配的用户列表,每条包含用户 ID、邮箱地址、姓名、昵称、工号、职位、工作地 | +| `nextCursor` | `string` | 下一页游标,传入 `--cursor` 翻页 | +| `hasMore` | `boolean` | 是否还有更多数据 | + +**user 对象字段:** + +| 字段 | 类型 | 说明 | +|------|------|------| +| `id` | `string` | 用户 ID | +| `email` | `string` | 展示使用的邮件地址 | +| `name` | `string` | 用户名(人名) | +| `nickname` | `string` | 用户昵称(或者花名) | +| `employeeNo` | `string` | 工号 | +| `jobTitle` | `string` | 职位 | +| `workLocation` | `string` | 工作地 | + +### 创建邮件模板 +``` +Usage: + dws mail template create [flags] +Example: + dws mail template create --email user@company.com --name "周报模板" --subject "周报" --content "本周工作总结..." + dws mail template create --email user@company.com --from user@company.com --name "通知模板" --subject "通知" --content "..." --to a@x.com,b@x.com --cc c@x.com +Flags: + --email string 用户邮箱地址 (必填) + --from string 模板发件人邮箱 (可选) + --subject string 模板邮件标题 (必填) + --content string 模板邮件正文 (必填),别名: --body + --name string 模板名称 (必填) + --to string 模板收件人列表,逗号分隔 (可选) + --cc string 模板抄送人列表,逗号分隔 (可选) + --is-draft 是否为草稿模板 (可选,默认 false) +``` + +> **草稿模板说明:** 传入 `--is-draft` 创建的模板为草稿模板,草稿模板支持后续通过 `template update` 修改内容。**非草稿模板创建后不可修改**(`template update` 仅对草稿模板有效)。 + +### 列举邮件模板 +``` +Usage: + dws mail template list [flags] +Example: + dws mail template list --email user@company.com --limit 20 + dws mail template list --email user@company.com --limit 20 --cursor +Flags: + --email string 用户邮箱地址 (必填) + --cursor string 分页游标,取自响应中的 nextCursor 字段 (可选) + --limit string 每页返回数量 (必填),别名: --size +``` + +### 获取邮件模板详情 +``` +Usage: + dws mail template get [flags] +Example: + dws mail template get --email user@company.com --id +Flags: + --email string 用户邮箱地址 (必填) + --id string 模板唯一标识 (必填) +``` + +### 更新邮件模板 + +> **重要限制:** `template update` **仅对草稿模板有效**。只有通过 `template create --is-draft` 创建的草稿模板才支持更新,非草稿模板调用 update 会返回 `Invalid parameter` 错误。 + +``` +Usage: + dws mail template update [flags] +Example: + dws mail template update --email user@company.com --id --subject "新标题" --content "新正文" + dws mail template update --email user@company.com --id --name "新模板名" +Flags: + --email string 用户邮箱地址 (必填) + --id string 模板唯一标识 (必填,必须是草稿模板的 ID) + --from string 模板发件人邮箱 (可选) + --subject string 模板邮件标题 (可选) + --content string 模板邮件正文 (可选),别名: --body + --name string 模板名称 (可选) + --to string 模板收件人列表,逗号分隔 (可选) + --cc string 模板抄送人列表,逗号分隔 (可选) +``` + +### 删除邮件模板 +``` +Usage: + dws mail template delete [flags] +Example: + dws mail template delete --email user@company.com --id +Flags: + --email string 用户邮箱地址 (必填) + --id string 模板唯一标识 (必填) +``` + +> **特殊字符注意:** `--contact-id` 等 ID 参数的值可能包含 `$`、`!` 等 shell 特殊字符。在终端手动执行时,**必须用单引号**包裹这类参数值(如 `--contact-id '101_0:DzzzzyJqO10$---.hp5uBuR'`),双引号会导致 `$` 被 shell 变量展开,使 ID 值被篡改从而报错。通过 MCP 协议(JSON 传参)调用时无此问题。 + +### 创建邮件联系人 +``` +Usage: + dws mail contact create [flags] +Example: + dws mail contact create --email user@company.com --contact-email colleague@company.com --display-name "张三" + dws mail contact create --email user@company.com --contact-email colleague@company.com --first-name "三" --last-name "张" +Flags: + --email string 用户邮箱地址 (必填) + --contact-email string 联系人邮箱地址 (必填) + --first-name string 联系人名 (可选) + --middle-name string 联系人中间名 (可选) + --last-name string 联系人姓 (可选) + --display-name string 联系人显示名称 (可选) +``` + +### 列举邮件联系人 +``` +Usage: + dws mail contact list [flags] +Example: + dws mail contact list --email user@company.com --limit 20 + dws mail contact list --email user@company.com --limit 20 --cursor +Flags: + --email string 用户邮箱地址 (必填) + --cursor string 分页游标,取自响应中的 nextCursor 字段 (可选) + --limit string 每页返回数量 (必填),别名: --size +``` + +### 更新邮件联系人 +``` +Usage: + dws mail contact update [flags] +Example: + dws mail contact update --email user@company.com --contact-id --display-name "李四" + dws mail contact update --email user@company.com --contact-id --contact-email new@company.com --first-name "四" --last-name "李" +Flags: + --email string 用户邮箱地址 (必填) + --contact-id string 联系人唯一标识 (必填) + --contact-email string 联系人邮箱地址 (可选) + --first-name string 联系人名 (可选) + --middle-name string 联系人中间名 (可选) + --last-name string 联系人姓 (可选) + --display-name string 联系人显示名称 (可选) +``` + +### 批量删除邮件联系人 + +> **特殊字符注意:** `--contact-ids` 的值可能包含 `$`、`!` 等 shell 特殊字符。在终端手动执行时,**必须用单引号**包裹这类参数值(如 `--contact-ids '101_0:DzzzzyJqO10$---.hp5uBuR'`),双引号会导致 `$` 被 shell 变量展开,使 ID 值被篡改从而报错。通过 MCP 协议(JSON 传参)调用时无此问题。 + +``` +Usage: + dws mail contact batch-delete [flags] +Example: + dws mail contact batch-delete --email user@company.com --contact-ids , +Flags: + --email string 用户邮箱地址 (必填) + --contact-ids string 要删除的联系人 ID 列表,逗号分隔 (必填) +``` diff --git a/skills/multi/dingtalk-mail/references/mail-folder-attachment-tag-commands.md b/skills/multi/dingtalk-mail/references/mail-folder-attachment-tag-commands.md new file mode 100644 index 00000000..be18c6bb --- /dev/null +++ b/skills/multi/dingtalk-mail/references/mail-folder-attachment-tag-commands.md @@ -0,0 +1,291 @@ +# 邮件文件夹、附件与标签命令 + +### 列举邮件文件夹 +``` +Usage: + dws mail folder list [flags] +Example: + dws mail folder list --email user@company.com + dws mail folder list --email user@company.com --folder +Flags: + --email string 邮件所属邮箱地址 (必填) + --folder string 父文件夹唯一标识,不传则返回顶层文件夹 (可选),别名: --folder-id +``` + +不传 `--folder` 返回顶层文件夹列表;传入则返回该文件夹的子文件夹列表。 + +**返回字段(`folders` 数组):** + +| 字段 | 类型 | 说明 | +|------|------|------| +| `id` | `string` | 文件夹唯一标识 | +| `displayName` | `string` | 文件夹显示名称 | +| `parentFolderId` | `string` | 父文件夹 ID | +| `childFolderCount` | `int` | 子文件夹数量 | +| `totalItemCount` | `int` | 邮件总数 | +| `unreadItemCount` | `int` | 未读邮件数量 | + +### 创建邮件文件夹 +``` +Usage: + dws mail folder create [flags] +Example: + dws mail folder create --email user@company.com --name "项目资料" + dws mail folder create --email user@company.com --name "子文件夹" --folder +Flags: + --email string 邮件所属邮箱地址 (必填) + --name string 新建邮件文件夹名称 (必填) + --folder string 父文件夹 ID,不传则创建顶层文件夹 (可选) +``` + +不传 `--folder` 创建顶层文件夹;传入 `--folder` 时创建指定父文件夹下的子文件夹。 + +> **重要:** `--folder` 必须填写父文件夹 ID,不是文件夹名称。父文件夹 ID 来自 `dws mail folder list --email <邮箱>` 返回的 `folders[].id`。 + +**返回 JSON:** + +```json +{ + "success": true, + "result": { + "folder": { + "id": "104", + "displayName": "项目资料", + "parentFolderId": "0", + "childFolderCount": 0, + "totalItemCount": 0, + "unreadItemCount": 0, + "extensions": {} + } + } +} +``` + +**返回字段:** + +| 字段 | 类型 | 说明 | +|------|------|------| +| `success` | `boolean` | 是否创建成功 | +| `result.folder.id` | `string` | 文件夹唯一标识 | +| `result.folder.displayName` | `string` | 文件夹显示名称 | +| `result.folder.parentFolderId` | `string` | 父文件夹 ID | +| `result.folder.childFolderCount` | `number` | 子文件夹数量 | +| `result.folder.totalItemCount` | `number` | 邮件总数 | +| `result.folder.unreadItemCount` | `number` | 未读邮件数量 | +| `result.folder.extensions` | `object` | 文件夹扩展信息 | + +### 删除邮件文件夹 +``` +Usage: + dws mail folder delete [flags] +Example: + dws mail folder delete --email user@company.com --id +Flags: + --email string 邮件所属邮箱地址 (必填) + --id string 要删除的邮件文件夹 ID (必填) +``` + +`--id` 必须填写要删除的文件夹 ID,不是文件夹名称。文件夹 ID 来自 `dws mail folder list --email <邮箱>` 返回的 `folders[].id`,或来自 `folder create` 返回的 `result.folder.id`。 + +**返回 JSON:** + +```json +{ + "success": true, + "result": {} +} +``` +### 更新邮件文件夹 +``` +Usage: + dws mail folder update [flags] +Example: + dws mail folder update --email user@company.com --id --name "新文件夹名" +Flags: + --email string 邮件所属邮箱地址 (必填) + --id string 要更新的邮件文件夹 ID (必填) + --name string 更新后的邮件文件夹名称 (必填) +``` + +`--id` 必须填写要更新的文件夹 ID,不是文件夹名称;`--name` 是更新后的文件夹名称。若用户只给出原文件夹名称,必须先调用 `folder list` 找到对应 `folders[].id`,再执行 update。 + +**返回 JSON:** + +```json +{ + "success": true, + "result": {} +} +``` + +### 列举邮件附件 + +> **重要:** 不存在 `attachment download_batch` / `download_all` 等批量下载命令。如需下载多封邮件的所有附件,必须按以下流程逐个下载:1) `message search` 搜索邮件获取 messageId 列表 → 2) 对每封邮件 `attachment list` 获取 attachmentId + name → 3) 对每个附件逐个调用 `attachment download`。 + +``` +Usage: + dws mail attachment list [flags] +Example: + dws mail attachment list --email user@company.com --id +Flags: + --email string 用户邮箱地址 (必填) + --id string 邮件唯一标识 messageId (必填) +``` + +列出指定邮件的所有附件信息。 + +**返回字段(`attachments` 数组):** + +| 字段 | 类型 | 说明 | +|------|------|------| +| `id` | `string` | 附件唯一标识 | +| `name` | `string` | 附件文件名 | +| `contentType` | `string` | 附件 MIME 类型 | +| `size` | `int` | 附件大小(字节) | + +### 下载邮件附件 + +> **重要:** `attachment download` 每次只能下载**一个**附件。不存在 `download_batch` / `download_all` / `batch_download` 等批量下载命令,不要编造不存在的命令。如需下载多封邮件的所有附件,必须循环执行:对每封邮件先 `attachment list` 获取附件列表,再对每个附件逐个调用 `attachment download`。 + +``` +Usage: + dws mail attachment download [flags] +Example: + # 先列出附件获取 id 和 name + dws mail attachment list --email user@company.com --id + # 再下载指定附件到当前目录(每次只能下载一个附件) + dws mail attachment download --email user@company.com --message-id --attachment-id --name report.pdf + # 下载到指定目录 + dws mail attachment download --email user@company.com --message-id --attachment-id --name img.png --output /tmp +Flags: + --email string 用户邮箱地址 (必填) + --message-id string 邮件唯一标识 messageId (必填) + --attachment-id string 附件唯一标识,取自 attachment list 的 id 字段 (必填) + --name string 保存到本地的文件名,取自 attachment list 的 name 字段 (必填) + --output string 保存目录,默认为当前目录 +``` + +下载指定邮件的某个附件到本地。CLI 自动执行以下编排流程: + +1. 调用 `create_download_session`,从响应的 `downloadUrl` 字段获取完整下载地址 +2. 通过 HTTP GET 下载附件内容并保存到本地 + +> **注意:** `--name` 和 `--attachment-id` 均来自 `attachment list` 的返回结果,建议先执行 `attachment list` 再执行 `attachment download`。 + +### 列举邮件标签 +``` +Usage: + dws mail tag list [flags] +Example: + dws mail tag list --email user@company.com +Flags: + --email string 用户的邮箱地址 (必填) +``` + +列出指定邮箱下的所有邮件标签,返回标签的 ID 和元信息。 + +**返回字段(`tags` 数组):** + +| 字段 | 类型 | 说明 | +|------|------|------| +| `id` | `string` | 标签唯一标识 | +| `name` | `string` | 标签显示名称 | +| `parentId` | `string` | 父标签 ID | +| `totalItemCount` | `int` | 标签下邮件总数 | +| `unreadItemCount` | `int` | 标签下未读邮件数量 | + +### 创建邮件标签 +``` +Usage: + dws mail tag create [flags] +Example: + dws mail tag create --email user@company.com --name "项目资料" + dws mail tag create --email user@company.com --name "子标签" --parent-id +Flags: + --email string 用户的邮箱地址 (必填) + --name string 新建邮件标签名称 (必填) + --parent-id string 父标签 ID,不传则创建顶层标签 (可选) +``` + +不传 `--parent-id` 创建顶层标签;传入 `--parent-id` 时创建指定父标签下的子标签。 + +> **重要:** `--parent-id` 必须填写父标签 ID,不是标签名称。父标签 ID 来自 `dws mail tag list --email <邮箱>` 返回的 `tags[].id`。 + +**返回 JSON:** + +```json +{ + "success": true, + "result": { + "tag": { + "id": "tag-001", + "name": "项目资料", + "parentId": "0", + "totalItemCount": 0, + "unreadItemCount": 0, + "extensions": {} + } + } +} +``` + +**返回字段:** + +| 字段 | 类型 | 说明 | +|------|------|------| +| `success` | `boolean` | 是否创建成功 | +| `result.tag.id` | `string` | 标签唯一标识 | +| `result.tag.name` | `string` | 标签显示名称 | +| `result.tag.parentId` | `string` | 父标签 ID | +| `result.tag.totalItemCount` | `number` | 标签下邮件总数 | +| `result.tag.unreadItemCount` | `number` | 标签下未读邮件数量 | +| `result.tag.extensions` | `object` | 标签扩展信息 | + +### 删除邮件标签 +``` +Usage: + dws mail tag delete [flags] +Example: + dws mail tag delete --email user@company.com --id +Flags: + --email string 用户的邮箱地址 (必填) + --id string 要删除的邮件标签 ID (必填) +``` + +`--id` 必须填写要删除的标签 ID,不是标签名称。标签 ID 来自 `dws mail tag list --email <邮箱>` 返回的 `tags[].id`,或来自 `tag create` 返回的 `result.tag.id`。 + +只能删除用户自定义标签,系统标签不能删除。 + +**返回 JSON:** + +```json +{ + "success": true, + "result": {} +} +``` + +### 更新邮件标签 +``` +Usage: + dws mail tag update [flags] +Example: + dws mail tag update --email user@company.com --id --name "新标签名" +Flags: + --email string 用户的邮箱地址 (必填) + --id string 要更新的邮件标签 ID (必填) + --name string 更新后的邮件标签名称 (必填) +``` + +`--id` 必须填写要更新的标签 ID,不是标签名称;`--name` 是更新后的标签名称。若用户只给出原标签名称,必须先调用 `tag list` 找到对应 `tags[].id`,再执行 update。 + +只能更新用户自定义标签,系统标签不能更新。 + +**返回 JSON:** + +```json +{ + "success": true, + "result": {} +} +``` diff --git a/skills/multi/dingtalk-mail/references/mail-index.md b/skills/multi/dingtalk-mail/references/mail-index.md new file mode 100644 index 00000000..2e21b985 --- /dev/null +++ b/skills/multi/dingtalk-mail/references/mail-index.md @@ -0,0 +1,11 @@ +# 邮件能力索引 + +| 主题 | 详见 | +|---|---| +| 命令速查与详细命令族 | [`mail-command-index.md`](./mail-command-index.md) | +| 默认邮箱选择 | [`mail-default-mailbox-rules.md`](./mail-default-mailbox-rules.md) | +| 意图判断 | [`mail-intent.md`](./mail-intent.md) | +| 工作流与上下文 | [`mail-workflow.md`](./mail-workflow.md) · [`mail-context.md`](./mail-context.md) | +| 错误、禁止项与注意事项 | [`mail-common-errors.md`](./mail-common-errors.md) · [`mail-never-do.md`](./mail-never-do.md) · [`mail-notes.md`](./mail-notes.md) | +| 通用邮件流程 | [`09-mail.md`](./09-mail.md) | +| 轻量流程 | [`lite-recipes.md`](./lite-recipes.md) | diff --git a/skills/multi/dingtalk-mail/references/mail-intent.md b/skills/multi/dingtalk-mail/references/mail-intent.md new file mode 100644 index 00000000..2f5824f5 --- /dev/null +++ b/skills/multi/dingtalk-mail/references/mail-intent.md @@ -0,0 +1,41 @@ +# 意图判断 + +## 意图判断 + +用户说"我的邮箱/邮箱地址" → `mailbox list`(**仅限查询自己的邮箱,不能查他人**) +用户说"获取/查找/得到 某人的邮箱地址" → **不是 `mailbox list`**,走三路并发查询流程(见「查找他人邮箱地址」章节) +用户说"找邮件/搜邮件/查邮件" → `message search` +用户说"看邮件/打开邮件/邮件内容" → 先 `message search` 获取 messageId,再 `message get` +用户说"发邮件/写邮件" → 先 `mailbox list` 获取发件地址,再 `message send` +用户说“给(某人名字)发邮件” / “查询某人发给我的邮件” / “查询发给某人的邮件” / 任何涉及按人名查找邮箱的场景 → + **第一步**:并发同时发起以下三路查询,取最先返回有效邮箱的结果;若三路均无有效邮箱,请用户提供,禁止臆测: + 1. `aisearch person --keyword <姓名>` → `contact user get --ids `,提取 `orgAuthEmail` + 2. `mail user search --email <当前邮箱> --keyword <姓名>`,提取 `users[].email`(仅企业邮箱可用;若已知工号,可改用 `--employee-no <工号>`) + 3. `contact user search --keyword <姓名>`,提取用户邮箱字段 + **第二步**:用获得的目标邮箱拼入 KQL(如 `from:` 或 `to:`)执行 `message search`,或用于 `message send` +用户说"发带附件的邮件/发邮件附件" → 先 `mailbox list` 获取发件地址,再 `message send --attachment <文件路径>` +用户说"给(某人名字)发邮件" → 先 `aisearch person` 获取 userId,再 `contact user get` 获取收件人邮箱,再 `message send` +用户说"查看附件/邮件附件/有什么附件" → 先 `message search` 获取 messageId,再 `attachment list` +用户说"下载附件/保存附件/把附件存到本地/把所有附件下载到..." → 先 `message search` 获取 messageId,再 `attachment list` 获取 attachmentId 和 name,最后逐个 `attachment download`(**不支持批量下载,不存在 download_batch/download_all 命令,必须逐个下载**) +用户说"把XX邮件的所有附件都下载" / "批量下载附件" / "下载4月所有发票邮件的附件" → **不存在批量下载命令**,必须按以下流程循环执行:1) `message search` 搜索匹配邮件获取 messageId 列表 → 2) 对每封邮件 `attachment list` 获取 attachmentId + name → 3) 对每个附件逐个调用 `attachment download`。不要编造 `download_batch` / `download_all` / `batch_download` 等不存在的命令 +用户说"创建邮件文件夹/新建邮箱文件夹/新建邮件目录" → `folder create` +用户说"在某个邮件文件夹下创建子文件夹" → 先 `folder list` 找到父文件夹 ID,再 `folder create --folder `;禁止把父文件夹名称直接填给 `--folder` +用户说"删除邮件文件夹/删除邮箱文件夹/删除邮件目录" → 先确认要删除的文件夹 ID;如果用户只给名称,先 `folder list` 找到 `folders[].id`,再 `folder delete --id ` +用户说"重命名邮件文件夹/修改邮箱文件夹名称/更新邮件目录名称" → 先确认要更新的文件夹 ID;如果用户只给原名称,先 `folder list` 找到 `folders[].id`,再 `folder update --id --name <新名称>` +用户说"查看邮件标签/列出邮箱标签/查看邮箱 label" → `tag list` +用户说"创建邮件标签/新建邮箱标签/新增 label" → `tag create` +用户说"在某个邮件标签下创建子标签" → 先 `tag list` 找到父标签 ID,再 `tag create --parent-id `;禁止把父标签名称直接填给 `--parent-id` +用户说"删除邮件标签/删除邮箱标签/删除 label" → 先确认要删除的标签 ID;如果用户只给名称,先 `tag list` 找到 `tags[].id`,再 `tag delete --id ` +用户说"重命名邮件标签/修改邮箱标签名称/更新 label 名称" → 先确认要更新的标签 ID;如果用户只给原名称,先 `tag list` 找到 `tags[].id`,再 `tag update --id --name <新名称>` +用户说"列出邮件会话/查看会话列表/查看某个文件夹里的邮件会话" → 先确认邮箱地址和文件夹 ID;如果只有文件夹名称,先 `folder list` 找到 `folders[].id`,再 `thread list --folder ` +用户说"查看会话/获取会话/看这封邮件的会话详情" → 如果已有会话 ID,直接 `thread get`;如果只有邮件线索,先 `message search` 或 `message get` 获取 `conversationId`,再 `thread get` +用户说"标记会话已读/未读/给会话加标签/移除会话标签" → 用 `thread update`;如果是多条会话,用 `thread batch-update`;标签操作必须先有标签 ID +用户说"删除会话/把会话放入已删除/批量删除会话" → 单条用 `thread trash`,多条用 `thread batch-trash`;传入的是会话 ID,不是邮件 ID +用户说"搜索/查找/联系 邮箱用户/联系人/某人的邮箱地址" → `user search`(搜索通讯录人员,不是搜邮件内容) +用户说"发送草稿/把草稿发出去/发这封草稿" → 先 `message search --query "folderId:5"` 找到草稿 messageId,再 `draft send` +用户说"邮件发出去了吗/查邮件发送状态/确认邮件是否发送成功/邮件投递结果" → 用发送类命令返回的 `internetMessageId`,调用 `message verify` 查询 `sendStatus` +用户说"翻页继续搜索联系人/通讯录" → `user search --cursor `(注意:不是 `message search`) + +**`user search` vs `message search` 关键区别:** +- `user search`:搜索的是**人**(通讯录联系人),入参是 `--keyword 姓名` 或 `--employee-no 工号`,返回用户信息 +- `message search`:搜索的是**邮件**(邮件内容),入参是 `--query KQL表达式`,返回邮件列表 diff --git a/skills/multi/dingtalk-mail/references/mail-message-commands.md b/skills/multi/dingtalk-mail/references/mail-message-commands.md new file mode 100644 index 00000000..5d87f3cb --- /dev/null +++ b/skills/multi/dingtalk-mail/references/mail-message-commands.md @@ -0,0 +1,240 @@ +# 邮箱与邮件命令 + +## 命令总览 + +### 查询可用邮箱地址 +> **注意:** 仅返回当前登录用户**自己的**邮箱列表,不能用于查找他人邮箱。查找他人邮箱请使用三路并发流程(见"查找他人邮箱地址"章节)。 +``` +Usage: + dws mail mailbox list [flags] +Example: + dws mail mailbox list +``` + +**返回字段:** + +| 字段 | 类型 | 说明 | +|------|------|------| +| `mailboxes` | `List[]` | 邮箱列表,每条包含邮箱地址、账号类型、所属企业 | + +### 获取用户邮箱信息 + +``` +Usage: + dws mail mailbox profile [flags] +Example: + dws mail mailbox profile --email user@company.com +Flags: + --email string 用户的邮箱地址 (必填) +``` + +**返回字段:** + +| 字段 | 类型 | 说明 | +|------|------|------| +| `email` | `string` | 邮箱地址 | +| `emailAliases` | `string[]` | 邮件地址别名列表 | +| `name` | `string` | 用户名 | +| `nickname` | `string` | 用户昵称 | +| `displayName` | `string` | 用户显示名 | +| `mboxSize` | `string` | 邮箱容量(字节) | +| `mboxSizeUsed` | `string` | 已使用的邮箱容量(字节) | +| `createdTime` | `string` | 创建时间 | +| `modifiedTime` | `string` | 修改时间 | + +### 查找他人邮箱地址(通讯录查人) + +> **这不是 `mailbox list`。** 当需要获取**某人**的邮箱地址时,必须走以下三路并发查询,取最先返回有效邮箱的结果。禁止臆测邮箱地址。 + +**触发场景:** 用户说「获取/查找/得到 某人的邮箱地址」、「给某人发邮件」、「某人发给我的邮件」等任何涉及按姓名找邮箱的场景。 + +**三路并发查询流程:** + +```bash +# 同时发起以下三路,取最先返回有效邮箱的结果 +# 路径 1:aisearch + contact user get +dws aisearch person --keyword "姓名" --dimension name --format json +# → 取 userId,再执行: +dws contact user get --ids --format json +# → 提取 orgAuthEmail 字段 + +# 路径 2:mail user search(仅企业邮箱可用,个人邮箱会报权限错误可忽略;已知工号时可用 --employee-no 替代 --keyword) +dws mail user search --email <当前邮箱> --keyword "姓名" --format json +# 或按工号查询:dws mail user search --email <当前邮箱> --employee-no "工号" --format json +# → 提取 users[].email + +# 路径 3:contact user search +dws contact user search --keyword "姓名" --format json +# → 提取用户邮箱字段 +``` + +若三路均无有效邮箱,必须请用户手动提供,**严禁臆测**。 + +### 列出文件夹中的邮件 +> **注意:** `message list` 用于按文件夹列出邮件;若需根据主题/发件人/日期等条件精确搜索,请使用 `message search`。 +``` +Usage: + dws mail message list [flags] +Example: + dws mail message list --email user@company.com + dws mail message list --email user@company.com --folder-id 1 + dws mail message list --email user@company.com --folder-id 2 --limit 50 + dws mail message list --email user@company.com --cursor +Flags: + --email string 邮件所属邮箱地址 (必填) + --folder-id string 文件夹 ID(1=已发送, 2=收件箱, 3=垃圾邮件, 5=草稿, 6=已删除),默认为收件箱,别名: --folder + --limit string 每页返回数量(最大限制 100, 默认 20),别名: --size, --page-size + --cursor string 邮件的起始偏移标识, 其值取自响应中的nextCursor字段。""表示从头开始 +``` + +**返回字段:** + +| 字段 | 类型 | 说明 | +|------|------|------| +| `messages` | `List[]` | 邮件列表,每条包含邮件 ID 及元信息(不含正文) | +| `total` | `int32` | 符合条件的总邮件数 | +| `nextCursor` | `string` | 下一页游标,传入 `--cursor` 翻页;值为 `$` 表示已到达列表尾部 | + +### 搜索邮件 (KQL 语法) +``` +Usage: + dws mail message search [flags] +Example: + dws mail message search --email user@company.com --query "subject:\"周报\"" --limit 20 + dws mail message search --email user@company.com --query "from:alice AND date>2025-06-01T00:00:00Z" --limit 10 +Flags: + --cursor string 邮件的起始偏移标识, 其值取自响应中的nextCursor字段。""表示从头开始 + --email string 搜索目标邮箱地址 (必填) + --query string KQL 查询表达式 (必填), 其中 date 格式需遵循 ISO8601 规范 + --limit string 每页返回数量(最大限制 100, 默认 20),别名: --size, --page-size +``` + +KQL 查询字段: date, size, tag, folderId, isRead, hasAttachments, subject, attachname, body, from, to +常用文件夹 ID: 1=已发送, 2=收件箱, 3=垃圾邮件, 5=草稿, 6=已删除 + +### KQL 查询字段说明 + +| 字段 | 类型 | 说明 | 正确示例 | 错误示例 | +|------|------|------|----------|----------| +| `date` | ISO8601 日期时间 | 邮件日期,支持 `>` `<` `>=` `<=` 比较运算符 | `date>2025-06-01T00:00:00Z` | `date>2025-06-01`(缺少时间部分) | +| `size` | 整数(字节数) | 邮件大小,支持 `>` `<` `>=` `<=` 比较运算符 | `size>1024` | `size>"1024"`(值不需要引号) | +| `tag` | 字符串 | 邮件标签 | `tag:important` | `tag:""` | +| `folderId` | 整数 | 文件夹 ID(1=已发送, 2=收件箱, 3=垃圾邮件, 5=草稿, 6=已删除) | `folderId:2` | `folderId:"收件箱"`(必须用数字 ID) | +| `isRead` | 布尔 `true`/`false` | 是否已读 | `isRead:false` | `isRead:0`、`isRead:"false"`(不支持数字或字符串形式) | +| `hasAttachments` | 布尔 `true`/`false` | 是否有附件 | `hasAttachments:true` | `hasAttachments:yes` | +| `subject` | 字符串 | 邮件主题,含空格须加双引号 | `subject:周报`、`subject:"项目 进展"` | `subject:项目 进展`(含空格未加引号) | +| `attachname` | 字符串 | 附件文件名,含空格须加双引号 | `attachname:report.pdf`、`attachname:"月度 报告.xlsx"` | `attachname:月度 报告.xlsx`(含空格未加引号) | +| `body` | 字符串 | 邮件正文内容,含空格须加双引号 | `body:会议纪要`、`body:"Q1 总结"` | `body:Q1 总结`(含空格未加引号) | +| `from` | 字符串(邮件地址或名称) | 发件人,支持:纯邮件地址、纯名称(含空格须加双引号)、`"名称<邮件地址>"` 格式 | `from:alice@company.com`、`from:"张 三"`、`from:"alice"` | `from:张 三`(含空格未加引号) | +| `to` | 字符串(邮件地址或名称) | 收件人,支持:纯邮件地址、纯名称(含空格须加双引号)、`"名称<邮件地址>"` 格式 | `to:bob@company.com`、`to:"李 四"`、`to:"alice"` | `to:李 四`(含空格未加引号) | + +**组合查询说明:** +- 支持 `AND` / `OR` / `NOT` 逻辑运算符(大写) +- 括号用于分组:`(from:alice OR from:bob) AND folderId:2` +- 排除特定文件夹:`(NOT folderId:3) AND (NOT folderId:6)` + +### message search 返回值说明 + +| 字段 | 类型 | 说明 | +|------|------|------| +| `messages` | `List[]` | 邮件列表,每条包含邮件 ID 及元信息(不含正文) | +| `total` | `int32` | 符合条件的总邮件数 | +| `nextCursor` | `string` | 下一页游标,传入 `--cursor` 翻页;值为 `$` 表示已到达列表尾部 | + +**翻页示例:** +```bash +# 第一页 +dws mail message search --email user@company.com --query "folderId:2" --limit 20 --format json +# 取返回中的 nextCursor,传入下一次请求(nextCursor="$" 时停止) +dws mail message search --email user@company.com --query "folderId:2" --limit 20 --cursor --format json +``` + +### 查看邮件完整内容 +``` +Usage: + dws mail message get [flags] +Example: + dws mail message get --email user@company.com --id +Flags: + --email string 邮件所属邮箱地址 (必填) + --id string 邮件 ID (必填) +``` + +**返回字段:** + +| 字段 | 类型 | 说明 | +|------|------|------| +| `message` | `object` | 邮件完整信息,包含主题、发件人、收件人、正文、附件等 | + +### 批量获取邮件详情 + +``` +Usage: + dws mail message batch-get [flags] +Example: + dws mail message batch-get --email user@company.com --ids , + dws mail message batch-get --email user@company.com --ids ,, +Flags: + --email string 邮件所属邮箱地址 (必填) + --ids string 要获取的邮件 ID 列表,逗号分隔,最多 20 个 (必填) +``` + +单次最多获取 20 封邮件。 + +**返回 JSON:** + +```json +{ + "success": true, + "messages": [ + { "subject": "...", "from": "...", "to": [...], "body": "...", ... }, + { "subject": "...", "from": "...", "to": [...], "body": "...", ... } + ] +} +``` + +> **注意:** 如果某个邮件 ID 获取失败,整个命令会报错并中止。建议先通过 `message search` 或 `message list` 确认邮件 ID 有效后再批量获取。 + +### 发送邮件 +``` +Usage: + dws mail message send [flags] +Example: + dws mail message send --from user@company.com --to colleague@company.com \ + --subject "周报" --content "本周完成任务A和任务B" + dws mail message send --from user@company.com --to colleague@company.com \ + --subject "周报" --content "见附件" --attachment ./report.pdf + dws mail message send --from user@company.com --to colleague@company.com \ + --subject "周报" --content "见附件" --attachment ./a.pdf --attachment ./b.xlsx + dws mail message send --from user@company.com --to colleague@company.com \ + --subject "图表周报" --content "图表如下:[inline:chart.png]" --inline-attachment ./chart.png + dws mail message send --from user@company.com --to colleague@company.com \ + --subject "带图文档" --content "见附件,图表:[inline:img.png]" --attachment ./doc.pdf --inline-attachment ./img.png +Flags: + --content string 邮件正文 (必填),别名: --body + --cc string 抄送人列表 + --from string 发件人邮箱 (必填),别名: --sender + --subject string 邮件标题 (必填) + --to string 收件人列表 (必填) + --attachment stringArray 附件文件路径,可多次指定 (可选) + --inline-attachment stringArray 内联图片路径,可多次指定,cid 自动生成 (可选) +``` + +**附件发送说明:** + +当指定 `--attachment` 或 `--inline-attachment` 时,CLI 自动执行以下编排流程: + +1. 创建邮件草稿(若有内联图片,正文自动转为 HTML 并注入 `` 标签) +2. 为每个普通附件调用 `create_upload_session`(`isInline=false`),从响应的 `uploadUrl` 字段获取完整上传地址,HTTP POST 上传文件内容 +3. 为每个内联图片调用 `create_upload_session`(`isInline=true`,传入 contentId),从响应的 `uploadUrl` 字段获取完整上传地址,HTTP POST 上传文件内容 +4. 调用 `send_draft` 发送草稿 + +> **注意:** 附件必须通过 `--attachment` / `--inline-attachment` 参数传入,**严禁使用钉钉媒体存储(media upload)上传附件**。 + +**内联图片说明(`--inline-attachment`):** + +- 仅支持图片类型:`jpg` / `jpeg` / `png` / `gif` / `webp` / `bmp` / `svg` +- CLI 自动生成 contentId,格式:`inline-{文件名(不含扩展名)}-{序号}@alimail.com`,例:`inline-chart-1@alimail.com` +- 在 `--content` 中使用占位符 `[inline:文件名]` 引用图片,CLI 自动替换为 `` 标签 +- 若 content 中没有对应占位符,内联图片会自动追加到正文末尾 +- 非图片类型(PDF、视频、音频等)请改用 `--attachment` diff --git a/skills/multi/dingtalk-mail/references/mail-never-do.md b/skills/multi/dingtalk-mail/references/mail-never-do.md new file mode 100644 index 00000000..5881a597 --- /dev/null +++ b/skills/multi/dingtalk-mail/references/mail-never-do.md @@ -0,0 +1,15 @@ +# 严格禁止 (NEVER DO) + +## 严格禁止 (NEVER DO) +- 明确禁止猜测、假设、推断发件人和收件人邮箱 +- 无法获取邮箱时,请用户提供并确认,不要通过假设或其他方式继续执行 +- **严禁在用户未明确指定使用个人邮箱时,默认选择 `@dingtalk.com` 个人邮箱作为 `--email` / `--from`**;默认必须从 `mailbox list` 中挑选企业邮箱 +- **涉及带附件的邮件操作时,严禁上传到钉钉媒体存储(media upload)**;必须使用对应命令的 `--attachment` / `--inline-attachment` 参数,由 CLI 内部完成附件处理 +- **严禁编造不存在的批量下载命令**(如 `attachment download_batch`、`attachment download_all`、`attachment batch_download` 等)。下载附件只有 `attachment download` 一条命令,每次只能下载一个附件;需要批量下载时必须循环调用 +- **严禁把文件夹名称当作 `folder create --folder` 的值**;`--folder` 只能填父文件夹 ID,父文件夹 ID 必须来自 `folder list` 的 `folders[].id` +- **严禁把文件夹名称当作 `folder delete/update --id` 的值**;`--id` 只能填要操作的文件夹 ID,文件夹 ID 必须来自 `folder list` 的 `folders[].id` 或 `folder create` 的 `result.folder.id` +- **严禁把标签名称当作 `tag create --parent-id` 的值**;`--parent-id` 只能填父标签 ID,父标签 ID 必须来自 `tag list` 的 `tags[].id` +- **严禁把标签名称当作 `tag delete/update --id` 的值**;`--id` 只能填要操作的标签 ID,标签 ID 必须来自 `tag list` 的 `tags[].id` 或 `tag create` 的 `result.tag.id` +- **严禁更新或删除系统标签**;`tag update/delete` 只适用于用户自定义标签 +- **严禁把文件夹名称当作 `thread list --folder` 的值**;`--folder` 只能填文件夹 ID,文件夹 ID 必须来自 `folder list` 的 `folders[].id` +- **严禁把邮件 ID 当作 `thread get/update/trash/batch-update/batch-trash` 的会话 ID**;这些命令需要 conversationId,可来自 `thread list` 的 `conversations[].id` 或邮件结果中的 `conversationId` diff --git a/skills/multi/dingtalk-mail/references/mail-notes.md b/skills/multi/dingtalk-mail/references/mail-notes.md new file mode 100644 index 00000000..e8d7dcc3 --- /dev/null +++ b/skills/multi/dingtalk-mail/references/mail-notes.md @@ -0,0 +1,19 @@ +# 注意事项 + +## 注意事项 + +- `mailbox list` 返回用户所有邮箱(含个人和企业),每条记录包含邮箱地址、账号类型、所属企业。**默认一律选择企业邮箱**(除非用户明确指定使用个人邮箱);若有多个企业邮箱可选,优先匹配用户当前所在企业的那一个;仍无法判断时向用户确认后再操作。详见文档顶部「默认邮箱选择规则」章节 +- `message search` 返回邮件 ID 和元信息(不含正文),需 `message get` 获取完整内容 +- KQL 查询支持 AND/OR/NOT 组合,字段值含空格时需用双引号 +- `--cc` 抄送人支持多人,逗号分隔 +- 收件人邮箱获取:用户只知道同事名字时,**并发**同时执行以下三路查询,取最先返回有效邮箱的结果,无需等待其他路完成: + 1. `dws aisearch person --keyword "名字" --dimension name` → `dws contact user get --ids `,提取 `orgAuthEmail` + 2. `dws mail user search --email <发件人邮箱> --keyword "名字"`,提取 `users[].email`(仅企业邮箱账号可调用,个人 @dingtalk.com 邮箱会报权限错误可忽略;若已知工号,可改用 `--employee-no <工号>`) + 3. `dws contact user search --keyword "名字"`,提取用户邮箱字段 + 若三路均无有效邮箱,请用户手动提供收件人邮箱,严禁臆测和假设 +- `thread list --folder` 的值必须是文件夹 ID,不是文件夹显示名称;不知道文件夹 ID 时,先调用 `folder list` 查 `folders[].id` +- `thread get/update/trash/batch-update/batch-trash` 使用的是会话 ID(conversationId),不是邮件 ID;会话 ID 可来自 `thread list` 的 `conversations[].id`,也可来自 `message search` 或 `message get` 返回的 `conversationId` +- `thread update` / `thread batch-update` 仅支持 `markRead`、`markUnread`、`addTags`、`removeTags`;标签操作必须传 `--tag-ids` +- `user search` 仅支持企业邮箱(非 `@dingtalk.com` 个人邮箱),使用个人邮箱将因无权限报错;搜到的用户邮箱(`email` 字段)可直接用于 `message send` 的 `--to`/`--cc` 参数 +- `folder create --folder` 的值必须是父文件夹 ID,不是文件夹显示名称;不知道父文件夹 ID 时,先调用 `folder list` 查 `folders[].id` +- `folder delete/update --id` 的值必须是目标文件夹 ID,不是文件夹显示名称;不知道目标文件夹 ID 时,先调用 `folder list` 查 `folders[].id` diff --git a/skills/multi/dingtalk-mail/references/mail-policy-commands.md b/skills/multi/dingtalk-mail/references/mail-policy-commands.md new file mode 100644 index 00000000..2196ef9b --- /dev/null +++ b/skills/multi/dingtalk-mail/references/mail-policy-commands.md @@ -0,0 +1,388 @@ +# 自动回复、名单与收信规则命令 + +### 获取自动回复配置 + +获取当前用户的邮件自动回复配置,包括是否启用、生效时间、回复范围和回复内容。 + +``` +Usage: + dws mail auto-reply get [flags] +Example: + dws mail auto-reply get --email user@company.com +Flags: + --email string 用户的邮箱地址 (必填) +``` + +**返回字段:** + +| 字段 | 类型 | 说明 | +|------|------|------| +| `enabled` | bool | 是否启用自动回复 (true=启用, false=禁用) | +| `startTime` | string | 自动回复开始时间 | +| `endTime` | string | 自动回复结束时间 | +| `scope` | string | 回复范围: "contact"(仅联系人) 或 "all"(所有人) | +| `content` | string | 自动回复内容 | + +### 更新自动回复配置 + +更新或设置用户的邮件自动回复配置。所有参数均为必填。建议先通过 `auto-reply get` 获取当前配置,再传入需要修改的字段值。 + +``` +Usage: + dws mail auto-reply update [flags] +Example: + dws mail auto-reply update --email user@company.com --enabled true \ + --start "2026/07/01 09:00:00 +0800" --end "2026/07/07 18:00:00 +0800" \ + --scope all --content "出差中,请稍后联系" + dws mail auto-reply update --email user@company.com --enabled false \ + --start "2026/07/01 09:00:00 +0800" --end "2026/07/07 18:00:00 +0800" \ + --scope all --content "已关闭自动回复" +Flags: + --email string 用户的邮箱地址 (必填) + --enabled string 是否启用自动回复: true/false (必填) + --start string 自动回复开始时间,格式: YYYY/MM/DD HH:MM:SS +ZZZZ (必填) + --end string 自动回复结束时间,格式: YYYY/MM/DD HH:MM:SS +ZZZZ (必填) + --scope string 回复范围: contact(仅联系人)/all(所有人) (必填) + --content string 自动回复内容 (必填) +``` + +**返回字段:** + +| 字段 | 类型 | 说明 | +|------|------|------| +| `success` | boolean | 更新是否成功 | +| `result` | object | 更新结果,成功时为空对象 | +| `errorCode` | string | 错误码(仅失败时存在) | +| `errorMsg` | string | 错误信息(仅失败时存在) | + +### 个人收信白名单管理 + +#### 列出白名单 + +``` +Usage: + dws mail allow-list list [flags] +Example: + dws mail allow-list list --email user@company.com +Flags: + --email string 用户的邮箱地址 (必填) +``` + +**返回字段:** + +| 字段 | 类型 | 说明 | +|------|------|------| +| `total` | number | 白名单总数 | +| `entries` | string[] | 白名单地址列表(邮件地址如 `123@domain.com`,域名如 `@domain.com`,域名前需加 `@`) | +| `success` | boolean | 调用是否成功 | +| `errorCode` | string | 错误码(仅失败时存在) | +| `errorMsg` | string | 错误信息(仅失败时存在) | + +#### 添加白名单 + +``` +Usage: + dws mail allow-list add [flags] +Example: + dws mail allow-list add --email user@company.com --entries a@b.com,@spam.com +Flags: + --email string 用户的邮箱地址 (必填) + --entries string 逗号分隔的地址列表,支持邮件地址(如123@domain.com)或域名(如@domain.com) +``` + +#### 移除白名单 + +``` +Usage: + dws mail allow-list remove [flags] +Example: + dws mail allow-list remove --email user@company.com --entries a@b.com,@spam.com +Flags: + --email string 用户的邮箱地址 (必填) + --entries string 逗号分隔的地址列表,支持邮件地址(如123@domain.com)或域名(如@domain.com) +``` + +### 个人收信黑名单管理 + +#### 列出黑名单 + +``` +Usage: + dws mail block-list list [flags] +Example: + dws mail block-list list --email user@company.com +Flags: + --email string 用户的邮箱地址 (必填) +``` + +**返回字段:** + +| 字段 | 类型 | 说明 | +|------|------|------| +| `total` | number | 黑名单总数 | +| `entries` | string[] | 黑名单地址列表(邮件地址如 `123@domain.com`,域名如 `@domain.com`,域名前需加 `@`) | +| `success` | boolean | 调用是否成功 | +| `errorCode` | string | 错误码(仅失败时存在) | +| `errorMsg` | string | 错误信息(仅失败时存在) | + +#### 添加黑名单 + +``` +Usage: + dws mail block-list add [flags] +Example: + dws mail block-list add --email user@company.com --entries spam@bad.com,@junk.com +Flags: + --email string 用户的邮箱地址 (必填) + --entries string 逗号分隔的地址列表,支持邮件地址(如123@domain.com)或域名(如@domain.com) +``` + +#### 移除黑名单 + +``` +Usage: + dws mail block-list remove [flags] +Example: + dws mail block-list remove --email user@company.com --entries spam@bad.com,@junk.com +Flags: + --email string 用户的邮箱地址 (必填) + --entries string 逗号分隔的地址列表,支持邮件地址(如123@domain.com)或域名(如@domain.com) +``` + +### 收信规则管理 + +#### 列出收信规则 + +列出当前用户的所有收信规则,包括规则名称、启用状态、条件、动作和排序。 + +``` +Usage: + dws mail rule list [flags] +Example: + dws mail rule list --email user@company.com +Flags: + --email string 用户的邮箱地址 (必填) +``` + +**返回字段:** + +| 字段 | 类型 | 说明 | +|------|------|------| +| `total` | int | 规则总数 | +| `rules` | List[] | 规则列表 | +| `rules[].id` | string | 规则 ID | +| `rules[].name` | string | 规则名称 | +| `rules[].enabled` | bool | 是否启用 | +| `rules[].conditions` | List[] | 规则条件列表 | +| `rules[].actions` | List[] | 规则动作列表 | +| `rules[].order` | int | 规则排序 | + +#### 创建收信规则 + +创建一条新的收信规则。支持设置规则名称、启用状态、匹配条件和执行动作。 + +> **`--conditions` 和 `--actions`** 为 JSON 数组字符串。 + +**界面与参数对应关系:** + +| 界面元素 | CLI 参数 / JSON 字段 | 说明 | +|----------|---------------------|------| +| 规则名称 | `--name` | 必填,规则的显示名称 | +| 如果满足以下「全部」条件 | `--conditions` | 多个条件之间为 **AND** 关系(即所有条件都满足才触发) | +| ├ 对象下拉(发件人) | `object: "from"` | 条件匹配的对象,可选值见下方 | +| ├ 操作下拉(包含) | `operation: "include"` | 匹配方式,可选值见下方 | +| └ 关键词输入框 | `keyword: "a@test.com"` | 匹配的具体值 | +| 执行以下操作 | `--actions` | 条件满足后执行的动作列表 | +| ├ 动作下拉(移动到文件夹) | `action: "ActSavetoFolder"` | 动作类型,可选值见下方 | +| └ 参数选择(收件箱) | `parameters: ["2"]` | 动作的参数,如目标文件夹 ID | + +**条件逻辑说明:** + +- `--conditions` 数组中的多个条件之间为 **AND(且)** 关系,即所有条件都满足才触发规则 +- 同一个条件对象(如 `from`)内部的 `or` 数组中多个表达式之间为 **OR(或)** 关系 +- 同一个 `and` 数组中的多个子条件之间为 **AND(且)** 关系 + +**条件对象 (object) 与合法操作类型 (operation) 组合:** + +| object | 合法 operation | 说明 | +|--------|---------------|------| +| `from` | `include`(包含), `exclude`(不包含), `oneof`(是联系人之一), `noneof`(不是联系人之一) | 匹配发件人地址或名称 | +| `to` | `include`(包含), `exclude`(不包含), `oneof`(是联系人之一), `noneof`(不是联系人之一) | 匹配收件人地址或名称 | +| `subject` | `include`(包含), `exclude`(不包含) | 匹配邮件主题 | +| `attachment` | `exist`(是否存在附件) | keyword="1" 表示有附件,keyword="0" 表示无附件 | +| `x-aliyun-size` | `greater`(大于), `less`(小于) | 邮件大小,单位为 **字节(Bytes)**(1KB=1024, 1MB=1048576);可组合使用表示范围区间 | + +**操作类型 (operation) 详细说明:** + +| 值 | 界面显示 | 适用 object | 说明 | +|----|---------|------------|------| +| `include` | 包含 | from, to, subject | 字段包含关键词 | +| `exclude` | 不包含 | from, to, subject | 字段不包含关键词 | +| `oneof` | 是联系人之一 | from, to | 字段值在给定联系人列表中 | +| `noneof` | 不是联系人之一 | from, to | 字段值不在给定联系人列表中 | +| `greater` | 大于 | x-aliyun-size | 数值大于阈值,单位字节(Bytes) | +| `less` | 小于 | x-aliyun-size | 数值小于阈值,单位字节(Bytes) | +| `exist` | 存在 | attachment | keyword="1" 表示有附件,keyword="0" 表示无附件 | + +**动作类型 (action) 可选值:** + +| 值 | 界面显示 | parameters 说明 | 前置依赖 | +|----|---------|----------------|----------| +| `ActSavetoFolder` | 移动到文件夹 | 目标文件夹 ID,如 `["2"]`(2=收件箱) | 需先通过 `dws mail folder list` 获取文件夹 ID | +| `ActFlagMail` | 标记标签 | 标签 ID 列表,逗号分隔,如 `["102,11,1"]` | 需先通过 `dws mail tag list` 获取标签 ID | +| `ActFlagMail2` | 标记已读 | `"asread"`(标记已读),服务端仅支持标记已读,不支持标记未读 | 无 | +| `ActReply` | 自动回复 | 回复内容文本,如 `["感谢您的来信"]` | 无 | + +**条件 JSON 结构说明:** + +每个条件由 `object`(匹配对象)和 `or`(OR 表达式列表)组成,`or` 内嵌 `and`(AND 条件列表)。 + +| 字段 | 说明 | +|------|------| +| `object` | 条件对象,取值及合法 operation 见上方组合表 | +| `or` | OR 表达式列表,同一 object 下多个 or 项之间为 **OR** 关系 | +| `and` | AND 条件列表,同一 or 项内多个 and 子条件之间为 **AND** 关系 | +| `operation` | 操作类型,必须与 object 合法组合(见上方组合表) | +| `keyword` | 关键词/阈值;attachment+exist 时 "1"=有附件/"0"=无附件;x-aliyun-size 时单位为字节(Bytes),如 1KB=1024, 1MB=1048576 | +| `ignoreCase` | 是否忽略大小写(布尔值,仅 from/to/subject + include/exclude 时需要) | + +**完整 conditions JSON 示例:** + +```json +[ + {"object":"from","or":[ + {"and":[{"operation":"oneof","keyword":"a@test.com","ignoreCase":true}]}, + {"and":[{"operation":"oneof","keyword":"b@test.com","ignoreCase":true}]} + ]}, + {"object":"subject","or":[{"and":[{"operation":"include","keyword":"报告","ignoreCase":true}]}]}, + {"object":"attachment","or":[{"and":[{"operation":"exist","keyword":"1"}]}]}, + {"object":"x-aliyun-size","or":[{"and":[{"operation":"greater","keyword":"1024"},{"operation":"less","keyword":"10240"}]}]} +] +``` + +> 上例表示:发件人是 a@test.com **或** b@test.com **且** 主题包含"报告" **且** 有附件 **且** 大小在 1KB(1024字节)~10KB(10240字节) 之间。 +> +> **同一 object 下匹配多个值的 OR 写法:** 在 `or` 数组中放多个 `and` 项(每个 `and` 对应一个匹配值),而非在一个 `and` 中放多个条件。例如上方 `from` 条件中,两个邮箱地址分别作为独立的 `and` 项放在 `or` 数组中,表示"满足任一即可"。 + +**完整 actions JSON 示例:** + +```json +[ + {"action":"ActSavetoFolder","parameters":["2"]}, + {"action":"ActFlagMail","parameters":["102,11,1"]}, + {"action":"ActFlagMail2","parameters":["asread"]}, + {"action":"ActReply","parameters":["感谢您的来信,我将尽快回复"]} +] +``` + +> **注意:** 使用 `ActSavetoFolder` 前需先通过 `dws mail folder list` 获取文件夹 ID;使用 `ActFlagMail` 前需先通过 `dws mail tag list` 获取标签 ID。 + +``` +Usage: + dws mail rule create [flags] +Example: + dws mail rule create --email user@company.com --name "VIP邮件标记" --enabled true \ + --conditions '[{"object":"from","or":[{"and":[{"operation":"include","keyword":"vip@company.com","ignoreCase":true}]}]}]' \ + --actions '[{"action":"ActFlagMail2","parameters":["asread"]}]' + dws mail rule create --email user@company.com --name "大附件归档" \ + --conditions '[{"object":"x-aliyun-size","or":[{"and":[{"operation":"greater","keyword":"10485760"}]}]}]' \ + --actions '[{"action":"ActSavetoFolder","parameters":["6"]}]' +Flags: + --email string 用户的邮箱地址 (必填) + --name string 规则名称 (必填) + --enabled string 是否启用: true/false (必填) + --conditions string 规则条件 JSON 数组 (可选) + --actions string 规则动作 JSON 数组 (必填) +``` + +**返回字段:** + +| 字段 | 类型 | 说明 | +|------|------|------| +| `success` | bool | 创建是否成功 | +| `errorCode` | string | 错误码 | +| `errorMsg` | string | 错误消息 | +| `id` | string | 新建规则 ID | + +#### 更新收信规则 + +更新已有的收信规则。**除 `--conditions` 外所有参数均为必填**。 + +> **建议工作流:** 先通过 `dws mail rule list` 获取当前规则的完整配置,再传入需要修改的字段值。 +> +> `--conditions` 为空或不传表示命中所有邮件(无条件匹配)。`--actions` 格式同 create 命令。 + +``` +Usage: + dws mail rule update [flags] +Example: + dws mail rule update --email user@company.com --id --name "新规则名" --enabled true \ + --actions '[{"action":"ActSavetoFolder","parameters":["6"]}]' + dws mail rule update --email user@company.com --id --name "全量归档" --enabled false \ + --conditions '[{"object":"subject","or":[{"and":[{"operation":"include","keyword":"报告","ignoreCase":true}]}]}]' \ + --actions '[{"action":"ActSavetoFolder","parameters":["6"]}]' +Flags: + --email string 用户的邮箱地址 (必填) + --id string 规则 ID (必填) + --name string 规则名称 (必填) + --enabled string 是否启用: true/false (必填) + --conditions string 规则条件 JSON 数组 (可选,为空表示命中所有邮件) + --actions string 规则动作 JSON 数组 (必填) +``` + +**返回字段:** + +| 字段 | 类型 | 说明 | +|------|------|------| +| `success` | bool | 更新是否成功 | +| `errorCode` | string | 错误码 | +| `errorMsg` | string | 错误信息 | +| `result` | object | 更新结果 | + +#### 删除收信规则 + +删除指定的收信规则。 + +``` +Usage: + dws mail rule delete [flags] +Example: + dws mail rule delete --email user@company.com --id +Flags: + --email string 用户的邮箱地址 (必填) + --id string 规则 ID (必填) +``` + +**返回字段:** + +| 字段 | 类型 | 说明 | +|------|------|------| +| `success` | bool | 删除是否成功 | +| `errorCode` | string | 错误码 | +| `errorMsg` | string | 错误信息 | +| `result` | object | 删除结果 | + +#### 调整收信规则排序 + +调整指定收信规则的排序位置,向上(up)或向下(down)移动。 + +``` +Usage: + dws mail rule adjust [flags] +Example: + dws mail rule adjust --email user@company.com --id --direction up + dws mail rule adjust --email user@company.com --id --direction down +Flags: + --email string 用户的邮箱地址 (必填) + --id string 规则 ID (必填) + --direction string 调整方向: up/down (必填) +``` + +**返回字段:** + +| 字段 | 类型 | 说明 | +|------|------|------| +| `success` | bool | 调整是否成功 | +| `errorCode` | string | 错误码 | +| `errorMsg` | string | 错误消息 | +| `result` | object | 调整结果 | diff --git a/skills/multi/dingtalk-mail/references/mail-thread-actions.md b/skills/multi/dingtalk-mail/references/mail-thread-actions.md new file mode 100644 index 00000000..29077927 --- /dev/null +++ b/skills/multi/dingtalk-mail/references/mail-thread-actions.md @@ -0,0 +1,453 @@ +# 邮件会话与消息动作命令 + +### 列出邮件会话 + +``` +Usage: + dws mail thread list [flags] +Example: + dws mail thread list --email user@company.com --folder --limit 10 + dws mail thread list --email user@company.com --folder 104 --limit 20 --cursor +Flags: + --email string 会话所属邮箱地址 (必填) + --folder string 邮件文件夹 ID,不是文件夹名称 (必填) + --limit int 本次列出的会话数,最大 100 (必填) + --cursor string 分页游标,首次请求可不传 (可选) + --start string 开始 UTC 时间字符串,如 2024-01-01T00:00:00Z (可选) + --end string 结束 UTC 时间字符串,如 2024-12-31T23:59:59Z (可选) + --ascending 是否按时间升序;不传由服务端默认排序 (可选) +``` + +`--folder` 必须填写文件夹 ID,不是文件夹名称。若用户只给出“收件箱/已删除/某个自定义文件夹”这类名称,必须先调用 `folder list` 找到对应 `folders[].id`,再执行 `thread list`。 + +**返回 JSON:** + +```json +{ + "success": true, + "result": { + "conversations": [ + { + "id": "conversationId", + "subject": "会话主题", + "summary": "会话摘要", + "lastModifiedDateTime": "2024-02-06T01:05:07Z", + "messageCount": 1, + "tags": [], + "senders": [ + { + "email": "sender@example.com", + "name": "发件人" + } + ], + "isRead": true, + "priority": "PRY_NORMAL", + "flag": "FLAG_NONE", + "hasAttachments": false + } + ], + "nextCursor": "", + "hasMore": false + } +} +``` + +### 获取会话详情 +``` +Usage: + dws mail thread get [flags] +Example: + dws mail thread get --email user@company.com --id +Flags: + --email string 会话所属邮箱地址 (必填) + --id string 会话唯一标识 conversationId (必填) +``` + +**返回字段(`conversation` 对象):** + +| 字段 | 类型 | 说明 | +|------|------|------| +| `id` | `string` | 会话唯一标识 | +| `subject` | `string` | 会话主题 | +| `summary` | `string` | 会话摘要信息 | +| `lastModifiedDateTime` | `string (date-time)` | 会话最后修改时间 | +| `messageCount` | `int32` | 会话邮件数量 | +| `tags` | `array[string]` | 会话 tag 信息 | +| `senders` | `List[{email, name}]` | 会话发件人列表 | +| `isRead` | `boolean` | 会话是否已读(全部已读/未读) | +| `priority` | `string` | 会话重要性,取会话内邮件最高优先级(`PRY_HIGH` / `PRY_NORMAL`) | +| `flag` | `string` | 会话标识,取会话内最近邮件的标识(`FLAG_NONE` / `FLAG_REPLY` / `FLAG_FORWARD`) | +| `hasAttachments` | `boolean` | 会话是否包含附件(不含 inline 资源) | + +### 修改邮件会话状态 + +``` +Usage: + dws mail thread update [flags] +Example: + dws mail thread update --email user@company.com --id --action markRead + dws mail thread update --email user@company.com --id --action addTags --tag-ids 1,2 +Flags: + --email string 会话所属邮箱地址 (必填) + --id string 会话唯一标识 conversationId (必填) + --action string 操作类型:markRead、markUnread、addTags、removeTags (必填) + --tag-ids string 标签 ID 列表,多个用英文逗号分隔;addTags/removeTags 时必填 (可选) +``` + +`--id` 必须填写会话 ID,不是邮件 ID。会话 ID 可通过 `thread list` 的 `conversations[].id` 获取。 + +支持的 `--action`: + +| action | 说明 | 是否需要 `--tag-ids` | +|--------|------|----------------------| +| `markRead` | 标记会话为已读 | 否 | +| `markUnread` | 标记会话为未读 | 否 | +| `addTags` | 给会话增加标签 | 是 | +| `removeTags` | 从会话移除标签 | 是 | + +**常用标签 ID:** + +| 标签 ID | 名称 | 图标 | +|---------|------|------| +| `1` | 跟进事项 | 小红旗 | +| `2` | 完成事项 | 绿色小勾 | +| `11` | 重要 | 星标 | + +成功时返回: + +```json +{ + "success": true, + "result": {} +} +``` + +### 批量修改邮件会话状态 + +``` +Usage: + dws mail thread batch-update [flags] +Example: + dws mail thread batch-update --email user@company.com --ids , --action markUnread + dws mail thread batch-update --email user@company.com --ids , --action removeTags --tag-ids 11 +Flags: + --email string 会话所属邮箱地址 (必填) + --ids string 会话 ID 列表,多个用英文逗号分隔,最多 100 个 (必填) + --action string 操作类型:markRead、markUnread、addTags、removeTags (必填) + --tag-ids string 标签 ID 列表,多个用英文逗号分隔;addTags/removeTags 时必填 (可选) +``` + +`--ids` 必须填写会话 ID 列表,不是邮件 ID 列表,最多 100 个。 + +成功时返回: + +```json +{ + "success": true, + "result": {} +} +``` + +### [危险] 删除邮件会话 + +``` +Usage: + dws mail thread trash [flags] +Example: + dws mail thread trash --email user@company.com --id --yes +Flags: + --email string 会话所属邮箱地址 (必填) + --id string 要删除的会话 ID (必填) + --yes 跳过确认提示,直接执行 (可选) +``` + +> ⚠️ **危险操作**:此命令会将邮件会话移动到已删除文件夹。建议先通过 `thread get` 确认目标会话后再执行。 + +将指定邮件会话移动到已删除文件夹,不会永久删除邮件。`--id` 必须填写会话 ID,不是邮件 ID。默认需要用户确认,传入 `--yes` 可跳过确认。 + +成功时返回: + +```json +{ + "success": true, + "result": {} +} +``` + +### [危险] 批量删除邮件会话 + +``` +Usage: + dws mail thread batch-trash [flags] +Example: + dws mail thread batch-trash --email user@company.com --ids , --yes +Flags: + --email string 会话所属邮箱地址 (必填) + --ids string 要删除的会话 ID 列表,多个用英文逗号分隔,最多 100 个 (必填) + --yes 跳过确认提示,直接执行 (可选) +``` + +> ⚠️ **危险操作**:此命令会批量将邮件会话移动到已删除文件夹。建议先通过 `thread list` 确认目标会话后再执行。 + +将指定邮件会话批量移动到已删除文件夹,单次最多 100 个会话。不会永久删除邮件。`--ids` 必须填写会话 ID 列表,不是邮件 ID 列表。默认需要用户确认,传入 `--yes` 可跳过确认。 + +成功时返回: + +```json +{ + "success": true, + "result": {} +} +``` + +### 回复邮件 +``` +Usage: + dws mail message reply [flags] +Example: + dws mail message reply --from user@company.com --id + dws mail message reply --from user@company.com --id --subject "Re: 周报" --content "已收到,谢谢!" +Flags: + --from string 发件人邮箱 (必填),别名: --sender + --to string 收件人列表(可选) + --id string 要回复的邮件 ID (必填) + --subject string 回复邮件标题(可选) + --content string 回复正文(可选),别名: --body + --attachment stringArray 附件文件路径,可多次指定 (可选) + --inline-attachment stringArray 内联图片路径,可多次指定,cid 自动生成 (可选) +``` + +**附件发送说明:** + +当指定 `--attachment` 或 `--inline-attachment` 时,CLI 自动执行以下编排流程: + +1. 调用 `create_reply_draft` 创建回复草稿(若有内联图片,正文自动转为 HTML 并注入 `` 标签) +2. 为每个普通附件创建上传会话并上传(`isInline=false`) +3. 为每个内联图片创建上传会话并上传(`isInline=true`,传入自动生成的 contentId) +4. 发送草稿 + +**返回字段:** + +| 字段 | 类型 | 说明 | +|------|------|------| +| `messageId` | `string` | 新生成的回复邮件 ID | + +### 回复所有人 +``` +Usage: + dws mail message reply-all [flags] +Example: + dws mail message reply-all --from user@company.com --id + dws mail message reply-all --from user@company.com --id --subject "Re: 周报" --content "感谢大家的参与!" +Flags: + --from string 发件人邮箱 (必填),别名: --sender + --to string 收件人列表(可选,包含发件人及所有原始收件人) + --id string 要回复的邮件 ID (必填) + --subject string 回复邮件标题(可选) + --content string 回复正文(可选),别名: --body + --attachment stringArray 附件文件路径,可多次指定 (可选) + --inline-attachment stringArray 内联图片路径,可多次指定,cid 自动生成 (可选) +``` + +**附件发送说明:** + +当指定 `--attachment` 或 `--inline-attachment` 时,CLI 自动执行以下编排流程: + +1. 调用 `create_replyall_draft` 创建回复全部草稿(若有内联图片,正文自动转为 HTML 并注入 `` 标签) +2. 为每个普通附件创建上传会话并上传(`isInline=false`) +3. 为每个内联图片创建上传会话并上传(`isInline=true`,传入自动生成的 contentId) +4. 发送草稿 + +**返回字段:** + +| 字段 | 类型 | 说明 | +|------|------|------| +| `messageId` | `string` | 新生成的回复邮件 ID | + +### 转发邮件 +``` +Usage: + dws mail message forward [flags] +Example: + dws mail message forward --from user@company.com --id + dws mail message forward --from user@company.com --to colleague@company.com --id --subject "Fwd: 周报" +Flags: + --from string 发件人邮箱 (必填),别名: --sender + --to string 转发收件人列表(可选) + --id string 要转发的邮件 ID (必填) + --subject string 转发邮件标题(可选) + --content string 转发附言(可选),别名: --body + --attachment stringArray 附件文件路径,可多次指定 (可选) + --inline-attachment stringArray 内联图片路径,可多次指定,cid 自动生成 (可选) +``` + +**附件发送说明:** + +当指定 `--attachment` 或 `--inline-attachment` 时,CLI 自动执行以下编排流程: + +1. 调用 `create_forward_draft` 创建转发草稿(若有内联图片,正文自动转为 HTML 并注入 `` 标签) +2. 为每个普通附件创建上传会话并上传(`isInline=false`) +3. 为每个内联图片创建上传会话并上传(`isInline=true`,传入自动生成的 contentId) +4. 发送草稿 + +**返回字段:** + +| 字段 | 类型 | 说明 | +|------|------|------| +| `messageId` | `string` | 新生成的转发邮件 ID | + +### 批量移动邮件到指定文件夹 +``` +Usage: + dws mail message batch-move [flags] +Example: + dws mail message batch-move --email user@company.com --ids , --folder 6 +Flags: + --email string 邮件所属邮箱地址 (必填) + --ids string 要移动的邮件 ID 列表,逗号分隔 (必填) + --folder string 目标文件夹 ID (必填) +``` + +常用文件夹 ID: 1=已发送, 2=收件箱, 3=垃圾邮件, 5=草稿, 6=已删除 + + +### 批量删除邮件 +``` +Usage: + dws mail message batch-delete [flags] +Example: + dws mail message batch-delete --email user@company.com --ids , +Flags: + --email string 邮件所属邮箱地址 (必填) + --ids string 要删除的邮件 ID 列表,逗号分隔 (必填) +``` + +### 批量修改邮件状态 + +批量修改邮件的已读状态或标签,通过 `--action` 指定操作类型。 + +**支持的操作类型(--action):** + +| action | 说明 | 需要的额外参数 | +|--------|------|---------------| +| `markRead` | 标记邮件为已读 | 无 | +| `markUnread` | 标记邮件为未读 | 无 | +| `addTags` | 给邮件增加标签 | `--tags`(标签 ID 列表,必填) | +| `removeTags` | 从邮件移除标签 | `--tags`(标签 ID 列表,必填) | + +**常用标签 ID:** + +| 标签 ID | 名称 | 图标 | +|---------|------|------| +| `1` | 跟进事项 | 小红旗 | +| `2` | 完成事项 | 绿色小勾 | +| `11` | 重要 | 星标 | + +``` +Usage: + dws mail message batch-update [flags] +Example: + dws mail message batch-update --email user@company.com --ids , --action markRead + dws mail message batch-update --email user@company.com --ids , --action addTags --tags 1,2 + dws mail message batch-update --email user@company.com --ids , --action removeTags --tags 11 +Flags: + --email string 邮件所属邮箱地址 (必填) + --ids string 要修改的邮件 ID 列表,逗号分隔 (必填) + --action string 操作类型: markRead/markUnread/addTags/removeTags (必填) + --tags string 标签 ID 列表,逗号分隔 (action 为 addTags/removeTags 时必填) +``` + +> **查询标签 ID:** 使用 `dws mail tag list --email <邮箱>` 可查看所有可用标签及其 ID。 + +### 查询邮件发送状态 +``` +Usage: + dws mail message verify [flags] +Example: + dws mail message verify --email user@company.com --internet-message-id +Flags: + --email string 邮件所属邮箱地址 (必填) + --internet-message-id string 邮件的 internetMessageId (必填),取自发送类命令返回值 +``` + +根据 `internetMessageId` 查询某封邮件当前的发送投递状态。 + +> **internetMessageId 来源:** `message send` / `draft send` / `message reply` / `message reply-all` / `message forward` 等发送类命令的返回值中均会带 `internetMessageId` 字段,可直接传入此命令查询发送结果。 + +**返回字段:** + +| 字段 | 类型 | 说明 | +|------|------|------| +| `message` | `object` | 邮件完整信息 | +| `sendStatus` | `string` | 发送状态,取值见下表 | + +**`sendStatus` 取值说明:** + +| 值 | 含义 | +|------|------| +| `none` | 未发送 | +| `posting` | 投递中 | +| `partial_success` | 部分成功(部分收件人投递成功) | +| `success` | 发送成功 | +| `failed` | 发送失败 | +| `unknown` | 未知状态 | + +### [危险] 撤回已发送的邮件 + +``` +Usage: + dws mail sent-message recall [flags] +Example: + dws mail sent-message recall --email user@company.com --id --subject "邮件主题" --yes +Flags: + --email string 发件人邮箱地址 (必填) + --id string 要撤回的邮件 ID (必填) + --subject string 邮件主题 (必填) + --yes 跳过确认提示,直接执行 (可选) +``` + +> ⚠️ **危险操作**:此命令会撤回已发送的邮件。仅支持撤回同组织内未读邮件。 + +**返回字段:** + +| 字段 | 类型 | 说明 | +|------|------|------| +| `id` | `string` | 撤回任务 ID(可用于 recall-detail 查询进度) | +| `success` | `boolean` | 接口调用是否成功 | +| `errorCode` | `string` | 错误码(仅失败时存在) | +| `errorMsg` | `string` | 错误信息(仅失败时存在) | + +### 查询邮件撤回进度 + +``` +Usage: + dws mail sent-message recall-detail [flags] +Example: + dws mail sent-message recall-detail --email user@company.com --id +Flags: + --email string 用户的邮箱地址 (必填) + --id string 撤回任务 ID (必填),由 recall 命令返回 +``` + +根据撤回任务 ID 查询邮件撤回的详细进度。撤回任务 ID 来源:`sent-message recall` 命令返回值中的 `id` 字段。 + +**返回字段:** + +| 字段 | 类型 | 说明 | +|------|------|------| +| `success` | `boolean` | 调用是否成功 | +| `id` | `string` | 任务 ID | +| `status` | `string` | 任务状态(见下方枚举) | +| `errorCode` | `string` | 错误码(仅失败时存在) | +| `errorMsg` | `string` | 错误信息(仅失败时存在) | + +**任务状态枚举:** + +| 状态值 | 说明 | +|--------|------| +| `UNINITED` | 未初始化 | +| `SUBMITTED` | 已提交 | +| `RUNNING` | 执行中 | +| `FINISHED` | 已完成 | +| `CANCELED` | 已取消 | +| `FAILED` | 失败 | diff --git a/skills/multi/dingtalk-mail/references/mail-workflow.md b/skills/multi/dingtalk-mail/references/mail-workflow.md new file mode 100644 index 00000000..5e01d7af --- /dev/null +++ b/skills/multi/dingtalk-mail/references/mail-workflow.md @@ -0,0 +1,85 @@ +# 核心工作流 + +## 核心工作流 + +```bash +# 1. 查看可用邮箱 — 提取邮箱地址 +dws mail mailbox list --format json + +# 1b. 创建顶层邮件文件夹 +dws mail folder create --email user@company.com --name "项目资料" --format json + +# 1c. 创建子文件夹 — 先通过 folder list 获取父文件夹 id,再传给 --folder +dws mail folder list --email user@company.com --format json +dws mail folder create --email user@company.com --name "子文件夹" --folder --format json + +# 1d. 更新或删除邮件文件夹 — 先通过 folder list 获取目标文件夹 id,再传给 --id +dws mail folder list --email user@company.com --format json +dws mail folder update --email user@company.com --id --name "新文件夹名" --format json +dws mail folder delete --email user@company.com --id --format json + +# 1e. 创建顶层邮件标签 +dws mail tag create --email user@company.com --name "项目资料" --format json + +# 1f. 创建子标签 — 先通过 tag list 获取父标签 id,再传给 --parent-id +dws mail tag list --email user@company.com --format json +dws mail tag create --email user@company.com --name "子标签" --parent-id --format json + +# 1g. 更新或删除邮件标签 — 先通过 tag list 获取目标标签 id,再传给 --id +dws mail tag list --email user@company.com --format json +dws mail tag update --email user@company.com --id --name "新标签名" --format json +dws mail tag delete --email user@company.com --id --format json + +# 1h. 列出文件夹中的邮件会话 — 先通过 folder list 获取文件夹 id,再传给 --folder +dws mail folder list --email user@company.com --format json +dws mail thread list --email user@company.com --folder --limit 10 --format json + +# 1i. 修改或删除邮件会话 — 先通过 thread list 获取 conversationId +dws mail thread update --email user@company.com --id --action markRead --format json +dws mail thread batch-update --email user@company.com --ids , --action markUnread --format json +dws mail thread trash --email user@company.com --id --format json +dws mail thread batch-trash --email user@company.com --ids , --format json + +# 2. 搜索邮件 — 提取 messageId +dws mail message search --email user@company.com \ + --query "subject:\"周报\" AND date>2025-06-01T00:00:00Z" --limit 10 --format json + +# 3. 查看邮件详情 +dws mail message get --email user@company.com --id --format json + +# 4. 发送邮件(纯文本) +dws mail message send --from user@company.com --to colleague@company.com \ + --subject "周报" --content "本周完成…" --format json + +# 4b. 发送带附件的邮件(自动编排:创建草稿→上传附件→发送草稿) +dws mail message send --from user@company.com --to colleague@company.com \ + --subject "周报" --content "见附件" --attachment ./report.pdf --format json + +# 4c. 发送带内联图片的邮件(正文自动转 HTML, 标签自动注入) +dws mail message send --from user@company.com --to colleague@company.com \ + --subject "图表周报" --content "本周图表如下:[inline:chart.png]" \ + --inline-attachment ./chart.png --format json + +# 5. 下载邮件附件到本地(每次只能下载一个附件,不支持批量下载) +# 步骤 5.1:搜索匹配的邮件,获取 messageId 列表 +# 示例:下载4月所有发票邮件的附件 +dws mail message search --email user@company.com \ + --query "subject:发票 AND date>2025-04-01T00:00:00Z AND date<2025-05-01T00:00:00Z AND hasAttachments:true" --limit 50 --format json + +# 步骤 5.2:对每封邮件,列出附件获取 attachmentId 和 name +# (对搜索结果中的每封邮件都要执行一次) +dws mail attachment list --email user@company.com --id --format json + +# 步骤 5.3:对每个附件逐个下载(没有批量下载命令,必须循环调用) +dws mail attachment download --email user@company.com \ + --message-id --attachment-id --name report.pdf --output ~/invoices/ + +# 6. 获取邮件所属会话详情(thread) +# 步骤 6.1:先通过 message search 或 message get 获取邮件中的 conversationId +dws mail message search --email user@company.com \ + --query "subject:\"周报\"" --limit 5 --format json +# 从返回的邮件列表中提取 conversationId 字段 + +# 步骤 6.2:用 conversationId 获取会话详情 +dws mail thread get --email user@company.com --id --format json +``` diff --git a/skills/multi/dingtalk-mail/references/mail.md b/skills/multi/dingtalk-mail/references/mail.md index bea9ebdb..5fb9330a 100644 --- a/skills/multi/dingtalk-mail/references/mail.md +++ b/skills/multi/dingtalk-mail/references/mail.md @@ -1,1816 +1,10 @@ # 邮箱 (mail) 命令参考 -## 命令速查目录 - -| 命令 | 功能简述 | -|------|----------| -| `dws mail mailbox list` | 查询**当前用户自己**的可用邮箱列表 | -| `dws mail mailbox profile` | 获取用户邮箱详细信息(容量、别名等) | -| `dws mail message list` | 列出指定文件夹中的邮件(默认收件箱) | -| `dws mail message search` | 搜索邮件(KQL 语法,按主题/发件人/日期等) | -| `dws mail message get` | 查看邮件完整内容(含正文) | -| `dws mail message send` | 发送邮件(支持附件/内联图片) | -| `dws mail message reply` | 回复邮件(支持附件/内联图片) | -| `dws mail message reply-all` | 回复所有人(支持附件/内联图片) | -| `dws mail message forward` | 转发邮件(支持附件/内联图片) | -| `dws mail message batch-move` | 批量移动邮件到指定文件夹 | -| `dws mail message batch-delete` | 批量删除邮件 | -| `dws mail message batch-update` | 批量修改邮件状态(标记已读/未读/添加标签/移除标签) | -| `dws mail message batch-get` | 批量获取邮件详情(最多 20 封) | -| `dws mail message verify` | 根据 internetMessageId 查询邮件发送状态 | -| `dws mail sent-message recall` | 撤回已发送的邮件(仅支持同组织内未读邮件) | -| `dws mail sent-message recall-detail` | 查询邮件撤回进度 | -| `dws mail draft create` | 创建草稿(保留在草稿箱,不发送) | -| `dws mail draft update` | 更新草稿内容(保留在草稿箱,不发送) | -| `dws mail draft send` | 发送草稿箱中已有的草稿 | -| `dws mail folder list` | 列举邮件文件夹 | -| `dws mail folder create` | 创建邮件文件夹 | -| `dws mail folder delete` | 删除邮件文件夹 | -| `dws mail folder update` | 更新邮件文件夹名称 | -| `dws mail attachment list` | 列举指定邮件的所有附件 | -| `dws mail attachment download` | 下载邮件附件到本地(**仅支持逐个下载,不支持批量下载**) | -| `dws mail tag list` | 列举邮件标签 | -| `dws mail tag create` | 创建邮件标签 | -| `dws mail tag delete` | 删除邮件标签 | -| `dws mail tag update` | 更新邮件标签名称 | -| `dws mail thread list` | 列出指定邮箱、指定文件夹下的邮件会话 | -| `dws mail thread get` | 获取会话详情 | -| `dws mail thread update` | 修改单个邮件会话的状态或标签(标记已读/未读/添加标签/移除标签) | -| `dws mail thread batch-update` | 批量修改邮件会话的状态或标签(单次最多 100 个) | -| `dws mail thread trash` | 将单个邮件会话移动到已删除文件夹(不会永久删除) | -| `dws mail thread batch-trash` | 将多个邮件会话批量移动到已删除文件夹(单次最多 100 个,不会永久删除) | -| `dws mail user search` | 搜索通讯录用户(**按姓名查他人邮箱**,不是搜邮件) | -| `dws mail template create` | 创建邮件模板 | -| `dws mail template list` | 列举邮件模板 | -| `dws mail template get` | 获取邮件模板详情 | -| `dws mail template update` | 更新邮件模板 | -| `dws mail template delete` | 删除邮件模板 | -| `dws mail contact create` | 创建个人邮件联系人(添加到自己的联系人列表) | -| `dws mail contact list` | 列举个人邮件联系人(查看自己保存的联系人,**不是搜索通讯录用户**) | -| `dws mail contact update` | 更新个人邮件联系人信息 | -| `dws mail contact batch-delete` | 批量删除个人邮件联系人 | -| `dws mail auto-reply get` | 获取用户的自动回复配置 | -| `dws mail auto-reply update` | 更新/设置用户的自动回复配置 | -| `dws mail allow-list list` | 列出个人收信白名单 | -| `dws mail allow-list add` | 添加个人收信白名单 | -| `dws mail allow-list remove` | 移除个人收信白名单 | -| `dws mail block-list list` | 列出个人收信黑名单 | -| `dws mail block-list add` | 添加个人收信黑名单 | -| `dws mail block-list remove` | 移除个人收信黑名单 | -| `dws mail rule list` | 列出个人收信规则 | -| `dws mail rule create` | 创建个人收信规则 | -| `dws mail rule update` | 更新个人收信规则 | -| `dws mail rule delete` | 删除个人收信规则 | -| `dws mail rule adjust` | 调整收信规则排序 | - -> **查找他人邮箱**(如「获取严龙的邮箱」)→ **不要用 `mailbox list`**,应走三路并发查询,详见「查找他人邮箱地址」章节。 - ---- - -## 默认邮箱选择规则(重要) - -所有 mail 相关命令,**除非用户明确要求使用个人邮箱,否则一律默认使用企业邮箱**。 - -**适用范围:** 任何需要传入 `--email` / `--from` / `--sender` 参数的 mail 子命令一律适用。 - -**默认选择策略:** - -1. 调用 `dws mail mailbox list --format json` 获取当前用户的所有邮箱。 -2. 从返回的 `emailAccounts` 中**优先选择企业邮箱**(账号类型为企业邮箱、域名非 `@dingtalk.com` 的邮箱),将其作为 `--email` / `--from` 的默认值。 -3. 仅当用户在指令中**明确指定**「用我的个人邮箱」「用 dingtalk.com 邮箱」「用我的私人邮箱」等表述时,才选择个人邮箱(`@dingtalk.com` 域名)。 -4. 若用户同时拥有多个企业邮箱(如分属多家公司),优先选择与当前会话上下文匹配的企业邮箱;若仍无法判断,向用户确认后再操作。 -5. 若用户**仅拥有个人邮箱**(无企业邮箱),可直接使用个人邮箱,但需注意 `mail user search` 等仅企业邮箱可用的命令会因权限报错,需走「查找他人邮箱地址」章节的替代路径。 - -**触发个人邮箱的关键词举例:** 「我的个人邮箱」「私人邮箱」「dingtalk.com 邮箱」「@dingtalk 的邮箱」「我的 personal 邮箱」。 - -> 该规则覆盖文档后续所有命令示例:示例中虽以 `user@company.com` 等占位邮箱书写,实际执行时**必须按上述策略动态选择企业邮箱**,不要直接照抄示例中的邮箱字面量,更不要默认使用 `@dingtalk.com` 个人邮箱。 - ---- - -## 命令总览 - -### 查询可用邮箱地址 -> **注意:** 仅返回当前登录用户**自己的**邮箱列表,不能用于查找他人邮箱。查找他人邮箱请使用三路并发流程(见"查找他人邮箱地址"章节)。 -``` -Usage: - dws mail mailbox list [flags] -Example: - dws mail mailbox list -``` - -**返回字段:** - -| 字段 | 类型 | 说明 | -|------|------|------| -| `emailAccounts` | `List[]` | 邮箱列表,每条包含 `email`(邮箱地址)、`orgName`(所属企业,个人邮箱为 null)、`type`(`ORG`=企业邮箱 / `PERSONAL`=个人邮箱) | - -### 获取用户邮箱信息 - -``` -Usage: - dws mail mailbox profile [flags] -Example: - dws mail mailbox profile --email user@company.com -Flags: - --email string 用户的邮箱地址 (必填) -``` - -**返回字段:** - -| 字段 | 类型 | 说明 | -|------|------|------| -| `email` | string | 邮箱地址 | -| `emailAliases` | List[] | 邮件地址别名列表 | -| `name` / `nickname` / `displayName` | string | 用户名、昵称、显示名 | -| `mboxSize` / `mboxSizeUsed` | int64 | 邮箱容量与已用容量(字节) | -| `createdTime` / `modifiedTime` | string | 创建时间与更新时间 | - -### 查找他人邮箱地址(通讯录查人) - -> **这不是 `mailbox list`。** 当需要获取**某人**的邮箱地址时,必须走以下三路并发查询,取最先返回有效邮箱的结果。禁止臆测邮箱地址。 - -**触发场景:** 用户说「获取/查找/得到 某人的邮箱地址」、「给某人发邮件」、「某人发给我的邮件」等任何涉及按姓名找邮箱的场景。 - -**三路并发查询流程:** - -```bash -# 同时发起以下三路,取最先返回有效邮箱的结果 -# 路径 1:aisearch + contact user get -dws aisearch person --keyword "姓名" --dimension name --format json -# → 取 userId,再执行: -dws contact user get --ids --format json -# → 提取 orgAuthEmail 字段 - -# 路径 2:mail user search(仅企业邮箱可用,个人邮箱会报权限错误可忽略) -dws mail user search --email <当前邮箱> --keyword "姓名" --format json -# → 提取 users[].email - -# 路径 3:contact user search -dws contact user search --keyword "姓名" --format json -# → 提取用户邮箱字段 -``` - -若三路均无有效邮箱,必须 `ask_human` 请用户手动提供,**严禁臆测**。 - -### 列出文件夹中的邮件 -> **注意:** `message list` 用于按文件夹列出邮件;若需根据主题/发件人/日期等条件精确搜索,请使用 `message search`。 -``` -Usage: - dws mail message list [flags] -Example: - dws mail message list --email user@company.com - dws mail message list --email user@company.com --folder-id 1 - dws mail message list --email user@company.com --folder-id 2 --limit 50 - dws mail message list --email user@company.com --cursor -Flags: - --email string 邮件所属邮箱地址 (必填) - --folder-id string 文件夹 ID(1=已发送, 2=收件箱, 3=垃圾邮件, 5=草稿, 6=已删除),默认为收件箱 - --limit string 每页返回数量(最大限制 100, 默认 20) - --cursor string 邮件的起始偏移标识, 其值取自响应中的nextCursor字段。""表示从头开始 -``` - -**返回字段:** - -| 字段 | 类型 | 说明 | -|------|------|------| -| `messages` | `List[]` | 邮件列表,每条包含邮件 ID 及元信息(不含正文) | -| `total` | `int32` | 符合条件的总邮件数 | -| `nextCursor` | `string` | 下一页游标,传入 `--cursor` 翻页;值为 `$` 表示已到达列表尾部 | - -### 搜索邮件 (KQL 语法) -``` -Usage: - dws mail message search [flags] -Example: - dws mail message search --email user@company.com --query "subject:\"周报\"" --limit 20 - dws mail message search --email user@company.com --query "from:alice AND date>2025-06-01T00:00:00Z" --limit 10 -Flags: - --cursor string 邮件的起始偏移标识, 其值取自响应中的nextCursor字段。""表示从头开始 - --email string 搜索目标邮箱地址 (必填) - --query string KQL 查询表达式 (必填), 其中 date 格式需遵循 ISO8601 规范 - --limit string 每页返回数量(最大限制 100, 默认 20) -``` - -KQL 查询字段: date, size, tag, folderId, isRead, hasAttachments, subject, attachname, body, from, to -常用文件夹 ID: 1=已发送, 2=收件箱, 3=垃圾邮件, 5=草稿, 6=已删除 - -### KQL 查询字段说明 - -| 字段 | 类型 | 说明 | 正确示例 | 错误示例 | -|------|------|------|----------|----------| -| `date` | ISO8601 日期时间 | 邮件日期,支持 `>` `<` `>=` `<=` 比较运算符 | `date>2025-06-01T00:00:00Z` | `date>2025-06-01`(缺少时间部分) | -| `size` | 整数(字节数) | 邮件大小,支持 `>` `<` `>=` `<=` 比较运算符 | `size>1024` | `size>"1024"`(值不需要引号) | -| `tag` | 字符串 | 邮件标签 | `tag:important` | `tag:""` | -| `folderId` | 整数 | 文件夹 ID(1=已发送, 2=收件箱, 3=垃圾邮件, 5=草稿, 6=已删除) | `folderId:2` | `folderId:"收件箱"`(必须用数字 ID) | -| `isRead` | 布尔 `true`/`false` | 是否已读 | `isRead:false` | `isRead:0`、`isRead:"false"`(不支持数字或字符串形式) | -| `hasAttachments` | 布尔 `true`/`false` | 是否有附件 | `hasAttachments:true` | `hasAttachments:yes` | -| `subject` | 字符串 | 邮件主题,含空格须加双引号 | `subject:周报`、`subject:"项目 进展"` | `subject:项目 进展`(含空格未加引号) | -| `attachname` | 字符串 | 附件文件名,含空格须加双引号 | `attachname:report.pdf`、`attachname:"月度 报告.xlsx"` | `attachname:月度 报告.xlsx`(含空格未加引号) | -| `body` | 字符串 | 邮件正文内容,含空格须加双引号 | `body:会议纪要`、`body:"Q1 总结"` | `body:Q1 总结`(含空格未加引号) | -| `from` | 字符串(邮件地址或名称) | 发件人,支持:纯邮件地址、纯名称(含空格须加双引号)、`"名称<邮件地址>"` 格式 | `from:alice@company.com`、`from:"张 三"`、`from:"alice"` | `from:张 三`(含空格未加引号) | -| `to` | 字符串(邮件地址或名称) | 收件人,支持:纯邮件地址、纯名称(含空格须加双引号)、`"名称<邮件地址>"` 格式 | `to:bob@company.com`、`to:"李 四"`、`to:"alice"` | `to:李 四`(含空格未加引号) | - -**组合查询说明:** -- 支持 `AND` / `OR` / `NOT` 逻辑运算符(大写) -- 括号用于分组:`(from:alice OR from:bob) AND folderId:2` -- 排除特定文件夹:`(NOT folderId:3) AND (NOT folderId:6)` - -### message search 返回值说明 - -| 字段 | 类型 | 说明 | -|------|------|------| -| `messages` | `List[]` | 邮件列表,每条包含邮件 ID 及元信息(不含正文) | -| `total` | `int32` | 符合条件的总邮件数 | -| `nextCursor` | `string` | 下一页游标,传入 `--cursor` 翻页;值为 `$` 表示已到达列表尾部 | - -**翻页示例:** -```bash -# 第一页 -dws mail message search --email user@company.com --query "folderId:2" --limit 20 --format json -# 取返回中的 nextCursor,传入下一次请求(nextCursor="$" 时停止) -dws mail message search --email user@company.com --query "folderId:2" --limit 20 --cursor --format json -``` - -### 查看邮件完整内容 -``` -Usage: - dws mail message get [flags] -Example: - dws mail message get --email user@company.com --id -Flags: - --email string 邮件所属邮箱地址 (必填) - --id string 邮件 ID (必填) -``` - -**返回字段:** - -| 字段 | 类型 | 说明 | -|------|------|------| -| `message` | `object` | 邮件信息,含 subject/from/toRecipients/ccRecipients/markdownBody/conversationId/receivedDateTime/size 等 | - -> **注意:** 即使邮件确有附件,`message get` 返回的 `message` 对象也**不含** `attachments`/`isRead`/`tags` 字段(实测 keys 仅上表所列这些)。要取附件请另用 `dws mail attachment list`。 - -### 批量获取邮件详情 - -``` -Usage: - dws mail message batch-get [flags] -Example: - dws mail message batch-get --email user@company.com --ids , - dws mail message batch-get --email user@company.com --ids ,, -Flags: - --email string 邮件所属邮箱地址 (必填) - --ids string 要获取的邮件 ID 列表,逗号分隔,最多 20 个 (必填) -``` - -单次最多获取 20 封邮件。CLI 会逐个调用 `get_email_by_message_id`,并以 `messages` 数组聚合返回。 - -### 发送邮件 -``` -Usage: - dws mail message send [flags] -Example: - dws mail message send --from user@company.com --to colleague@company.com \ - --subject "周报" --content "本周完成任务A和任务B" - dws mail message send --from user@company.com --to colleague@company.com \ - --subject "周报" --content "见附件" --attachment ./report.pdf - dws mail message send --from user@company.com --to colleague@company.com \ - --subject "周报" --content "见附件" --attachment ./a.pdf --attachment ./b.xlsx - dws mail message send --from user@company.com --to colleague@company.com \ - --subject "图表周报" --content "图表如下:[inline:chart.png]" --inline-attachment ./chart.png - dws mail message send --from user@company.com --to colleague@company.com \ - --subject "带图文档" --content "见附件,图表:[inline:img.png]" --attachment ./doc.pdf --inline-attachment ./img.png -Flags: - --content string 邮件正文 (必填) - --cc string 抄送人列表 - --from string 发件人邮箱 (必填),别名: --sender - --subject string 邮件标题 (必填) - --to string 收件人列表 (必填) - --attachment stringArray 附件文件路径,可多次指定 (可选) - --inline-attachment stringArray 内联图片路径,可多次指定,cid 自动生成 (可选) -``` - -**附件发送说明:** - -当指定 `--attachment` 或 `--inline-attachment` 时,CLI 自动执行以下编排流程: - -1. 创建邮件草稿(若有内联图片,正文自动转为 HTML 并注入 `` 标签) -2. 为每个普通附件调用 `create_upload_session`(`isInline=false`),从响应的 `uploadUrl` 字段获取完整上传地址,HTTP POST 上传文件内容 -3. 为每个内联图片调用 `create_upload_session`(`isInline=true`,传入 contentId),从响应的 `uploadUrl` 字段获取完整上传地址,HTTP POST 上传文件内容 -4. 调用 `send_draft` 发送草稿 - -> **注意:** 附件必须通过 `--attachment` / `--inline-attachment` 参数传入,**严禁使用钉钉媒体存储(media upload)上传附件**。 - -**内联图片说明(`--inline-attachment`):** - -- 仅支持图片类型:`jpg` / `jpeg` / `png` / `gif` / `webp` / `bmp` / `svg` -- CLI 自动生成 contentId,格式:`inline-{文件名(不含扩展名)}-{序号}@alimail.com`,例:`inline-chart-1@alimail.com` -- 在 `--content` 中使用占位符 `[inline:文件名]` 引用图片,CLI 自动替换为 `` 标签 -- 若 body 中没有对应占位符,内联图片会自动追加到正文末尾 -- 非图片类型(PDF、视频、音频等)请改用 `--attachment` - -### 列举邮件文件夹 -``` -Usage: - dws mail folder list [flags] -Example: - dws mail folder list --email user@company.com - dws mail folder list --email user@company.com --folder -Flags: - --email string 邮件所属邮箱地址 (必填) - --folder string 父文件夹唯一标识,不传则返回顶层文件夹 (可选) -``` - -不传 `--folder` 返回顶层文件夹列表;传入则返回该文件夹的子文件夹列表。 - -**返回字段(`folders` 数组):** - -| 字段 | 类型 | 说明 | -|------|------|------| -| `id` | `string` | 文件夹唯一标识 | -| `displayName` | `string` | 文件夹显示名称 | -| `parentFolderId` | `string` | 父文件夹 ID | -| `childFolderCount` | `int` | 子文件夹数量 | -| `totalItemCount` | `int` | 邮件总数 | -| `unreadItemCount` | `int` | 未读邮件数量 | - -### 创建邮件文件夹 -``` -Usage: - dws mail folder create [flags] -Example: - dws mail folder create --email user@company.com --name "项目资料" - dws mail folder create --email user@company.com --name "子文件夹" --folder -Flags: - --email string 邮件所属邮箱地址 (必填) - --name string 新建邮件文件夹名称 (必填) - --folder string 父文件夹 ID,不传则创建顶层文件夹 (可选) -``` - -不传 `--folder` 创建顶层文件夹;传入 `--folder` 时创建指定父文件夹下的子文件夹。 - -> **重要:** `--folder` 必须填写父文件夹 ID,不是文件夹名称。父文件夹 ID 来自 `dws mail folder list --email <邮箱>` 返回的 `folders[].id`。 - -**返回 JSON:** - -```json -{ - "success": true, - "result": { - "folder": { - "id": "104", - "displayName": "项目资料", - "parentFolderId": "0", - "childFolderCount": 0, - "totalItemCount": 0, - "unreadItemCount": 0, - "extensions": {} - } - } -} -``` - -**返回字段:** - -| 字段 | 类型 | 说明 | -|------|------|------| -| `success` | `boolean` | 是否创建成功 | -| `result.folder.id` | `string` | 文件夹唯一标识 | -| `result.folder.displayName` | `string` | 文件夹显示名称 | -| `result.folder.parentFolderId` | `string` | 父文件夹 ID | -| `result.folder.childFolderCount` | `number` | 子文件夹数量 | -| `result.folder.totalItemCount` | `number` | 邮件总数 | -| `result.folder.unreadItemCount` | `number` | 未读邮件数量 | -| `result.folder.extensions` | `object` | 文件夹扩展信息 | - -### 删除邮件文件夹 -``` -Usage: - dws mail folder delete [flags] -Example: - dws mail folder delete --email user@company.com --id -Flags: - --email string 邮件所属邮箱地址 (必填) - --id string 要删除的邮件文件夹 ID (必填) -``` - -`--id` 必须填写要删除的文件夹 ID,不是文件夹名称。文件夹 ID 来自 `dws mail folder list --email <邮箱>` 返回的 `folders[].id`,或来自 `folder create` 返回的 `result.folder.id`。 - -**返回 JSON:** - -```json -{ - "success": true -} -``` - -### 更新邮件文件夹 -``` -Usage: - dws mail folder update [flags] -Example: - dws mail folder update --email user@company.com --id --name "新文件夹名" -Flags: - --email string 邮件所属邮箱地址 (必填) - --id string 要更新的邮件文件夹 ID (必填) - --name string 更新后的邮件文件夹名称 (必填) -``` - -`--id` 必须填写要更新的文件夹 ID,不是文件夹名称;`--name` 是更新后的文件夹名称。若用户只给出原文件夹名称,必须先调用 `folder list` 找到对应 `folders[].id`,再执行 update。 - -**返回 JSON:** - -```json -{ - "success": true -} -``` - -### 列举邮件附件 - -> **重要:** 不存在 `attachment download_batch` / `download_all` 等批量下载命令。如需下载多封邮件的所有附件,必须按以下流程逐个下载:1) `message search` 搜索邮件获取 messageId 列表 → 2) 对每封邮件 `attachment list` 获取 attachmentId + name → 3) 对每个附件逐个调用 `attachment download`。 - -``` -Usage: - dws mail attachment list [flags] -Example: - dws mail attachment list --email user@company.com --id -Flags: - --email string 用户邮箱地址 (必填) - --id string 邮件唯一标识 messageId (必填) -``` - -列出指定邮件的所有附件信息。 - -**返回字段(`attachments` 数组):** - -| 字段 | 类型 | 说明 | -|------|------|------| -| `id` | `string` | 附件唯一标识 | -| `name` | `string` | 附件文件名 | -| `isInline` | `bool` | 是否为内联附件(`true`=正文内联图片,`false`=普通附件) | -| `size` | `int` | 附件大小(字节) | - -### 下载邮件附件 - -> **重要:** `attachment download` 每次只能下载**一个**附件。不存在 `download_batch` / `download_all` / `batch_download` 等批量下载命令,不要编造不存在的命令。如需下载多封邮件的所有附件,必须循环执行:对每封邮件先 `attachment list` 获取附件列表,再对每个附件逐个调用 `attachment download`。 - -``` -Usage: - dws mail attachment download [flags] -Example: - # 先列出附件获取 id 和 name - dws mail attachment list --email user@company.com --id - # 再下载指定附件到当前目录(每次只能下载一个附件) - dws mail attachment download --email user@company.com --message-id --attachment-id --name report.pdf - # 下载到指定目录 - dws mail attachment download --email user@company.com --message-id --attachment-id --name img.png --output /tmp -Flags: - --email string 用户邮箱地址 (必填) - --message-id string 邮件唯一标识 messageId (必填) - --attachment-id string 附件唯一标识,取自 attachment list 的 id 字段 (必填) - --name string 保存到本地的文件名,取自 attachment list 的 name 字段 (必填) - --output string 保存目录,默认为当前目录 -``` - -下载指定邮件的某个附件到本地。CLI 自动执行以下编排流程: - -1. 调用 `create_download_session`,从响应的 `downloadUrl` 字段获取完整下载地址 -2. 通过 HTTP GET 下载附件内容并保存到本地 - -> **注意:** `--name` 和 `--attachment-id` 均来自 `attachment list` 的返回结果,建议先执行 `attachment list` 再执行 `attachment download`。 - -### 列举邮件标签 -``` -Usage: - dws mail tag list [flags] -Example: - dws mail tag list --email user@company.com -Flags: - --email string 用户的邮箱地址 (必填) -``` - -列出指定邮箱下的所有邮件标签,返回标签的 ID 和元信息。 - -**返回字段(`tags` 数组):** - -| 字段 | 类型 | 说明 | -|------|------|------| -| `id` | `string` | 标签唯一标识 | -| `name` | `string` | 标签显示名称 | -| `parentId` | `string` | 父标签 ID | -| `totalItemCount` | `int` | 标签下邮件总数 | -| `unreadItemCount` | `int` | 标签下未读邮件数量 | - -### 创建邮件标签 -``` -Usage: - dws mail tag create [flags] -Example: - dws mail tag create --email user@company.com --name "项目资料" - dws mail tag create --email user@company.com --name "子标签" --parent-id -Flags: - --email string 用户的邮箱地址 (必填) - --name string 新建邮件标签名称 (必填) - --parent-id string 父标签 ID,不传则创建顶层标签 (可选) -``` - -不传 `--parent-id` 创建顶层标签;传入 `--parent-id` 时创建指定父标签下的子标签。 - -> **重要:** `--parent-id` 必须填写父标签 ID,不是标签名称。父标签 ID 来自 `dws mail tag list --email <邮箱>` 返回的 `tags[].id`。 - -**返回 JSON:** - -```json -{ - "success": true, - "result": { - "tag": { - "id": "tag-001", - "name": "项目资料", - "parentId": "0", - "totalItemCount": 0, - "unreadItemCount": 0, - "extensions": {} - } - } -} -``` - -**返回字段:** - -| 字段 | 类型 | 说明 | -|------|------|------| -| `success` | `boolean` | 是否创建成功 | -| `result.tag.id` | `string` | 标签唯一标识 | -| `result.tag.name` | `string` | 标签显示名称 | -| `result.tag.parentId` | `string` | 父标签 ID | -| `result.tag.totalItemCount` | `number` | 标签下邮件总数 | -| `result.tag.unreadItemCount` | `number` | 标签下未读邮件数量 | -| `result.tag.extensions` | `object` | 标签扩展信息 | - -### 删除邮件标签 -``` -Usage: - dws mail tag delete [flags] -Example: - dws mail tag delete --email user@company.com --id -Flags: - --email string 用户的邮箱地址 (必填) - --id string 要删除的邮件标签 ID (必填) -``` - -`--id` 必须填写要删除的标签 ID,不是标签名称。标签 ID 来自 `dws mail tag list --email <邮箱>` 返回的 `tags[].id`,或来自 `tag create` 返回的 `result.tag.id`。 - -只能删除用户自定义标签,系统标签不能删除。 - -**返回 JSON:** - -```json -{ - "success": true -} -``` - -### 更新邮件标签 -``` -Usage: - dws mail tag update [flags] -Example: - dws mail tag update --email user@company.com --id --name "新标签名" -Flags: - --email string 用户的邮箱地址 (必填) - --id string 要更新的邮件标签 ID (必填) - --name string 更新后的邮件标签名称 (必填) -``` - -`--id` 必须填写要更新的标签 ID,不是标签名称;`--name` 是更新后的标签名称。若用户只给出原标签名称,必须先调用 `tag list` 找到对应 `tags[].id`,再执行 update。 - -只能更新用户自定义标签,系统标签不能更新。 - -**返回 JSON:** - -```json -{ - "success": true -} -``` - -### 列出邮件会话 - -``` -Usage: - dws mail thread list [flags] -Example: - dws mail thread list --email user@company.com --folder --limit 10 - dws mail thread list --email user@company.com --folder 104 --limit 20 --cursor -Flags: - --email string 会话所属邮箱地址 (必填) - --folder string 邮件文件夹 ID,不是文件夹名称 (必填) - --limit int 本次列出的会话数,最大 100 (必填) - --cursor string 分页游标,首次请求可不传 (可选) - --start string 开始 UTC 时间字符串,如 2024-01-01T00:00:00Z (可选) - --end string 结束 UTC 时间字符串,如 2024-12-31T23:59:59Z (可选) - --ascending 是否按时间升序;不传由服务端默认排序 (可选) -``` - -`--folder` 必须填写文件夹 ID,不是文件夹名称。若用户只给出“收件箱/已删除/某个自定义文件夹”这类名称,必须先调用 `folder list` 找到对应 `folders[].id`,再执行 `thread list`。 - -**返回 JSON:** - -```json -{ - "success": true, - "result": { - "conversations": [ - { - "id": "conversationId", - "subject": "会话主题", - "summary": "会话摘要", - "lastModifiedDateTime": "2024-02-06T01:05:07Z", - "messageCount": 1, - "tags": [], - "senders": [ - { - "email": "sender@example.com", - "name": "发件人" - } - ], - "isRead": true, - "priority": "PRY_NORMAL", - "flag": "FLAG_NONE", - "hasAttachments": false - } - ], - "nextCursor": "", - "hasMore": false - } -} -``` - -### 获取会话详情 -``` -Usage: - dws mail thread get [flags] -Example: - dws mail thread get --email user@company.com --id -Flags: - --email string 会话所属邮箱地址 (必填) - --id string 会话唯一标识 conversationId (必填) -``` - -**返回字段(`conversation` 对象):** - -| 字段 | 类型 | 说明 | -|------|------|------| -| `id` | `string` | 会话唯一标识 | -| `subject` | `string` | 会话主题 | -| `summary` | `string` | 会话摘要信息 | -| `lastModifiedDateTime` | `string (date-time)` | 会话最后修改时间 | -| `messageCount` | `int32` | 会话邮件数量 | -| `tags` | `array[string]` | 会话 tag 信息 | -| `senders` | `List[{email, name}]` | 会话发件人列表 | -| `isRead` | `boolean` | 会话是否已读(全部已读/未读) | -| `priority` | `string` | 会话重要性,取会话内邮件最高优先级(`PRY_HIGH` / `PRY_NORMAL`) | -| `hasAttachments` | `boolean` | 会话是否包含附件(不含 inline 资源) | -| `messages` | `List[]` | 会话内的邮件列表;默认即返回,但各邮件字段(正文、收件人等)多为 null,需在 `--select` 中额外指定才有值 | - -> **注意:** `thread get` 返回的 `conversation` **不含 `flag` 字段**(`flag` 仅出现在 `thread list` 的会话项中)。会话标识请从 `thread list` 获取。 - -### 修改邮件会话状态 - -``` -Usage: - dws mail thread update [flags] -Example: - dws mail thread update --email user@company.com --id --action markRead - dws mail thread update --email user@company.com --id --action addTags --tag-ids 1,2 -Flags: - --email string 会话所属邮箱地址 (必填) - --id string 会话唯一标识 conversationId (必填) - --action string 操作类型:markRead、markUnread、addTags、removeTags (必填) - --tag-ids string 标签 ID 列表,多个用英文逗号分隔;addTags/removeTags 时必填 (可选) -``` - -`--id` 必须填写会话 ID,不是邮件 ID。会话 ID 可通过 `thread list` 的 `conversations[].id` 获取。 - -支持的 `--action`: - -| action | 说明 | 是否需要 `--tag-ids` | -|--------|------|----------------------| -| `markRead` | 标记会话为已读 | 否 | -| `markUnread` | 标记会话为未读 | 否 | -| `addTags` | 给会话增加标签 | 是 | -| `removeTags` | 从会话移除标签 | 是 | - -成功时返回: - -```json -{ - "success": true -} -``` - -### 批量修改邮件会话状态 - -``` -Usage: - dws mail thread batch-update [flags] -Example: - dws mail thread batch-update --email user@company.com --ids , --action markUnread - dws mail thread batch-update --email user@company.com --ids , --action removeTags --tag-ids 11 -Flags: - --email string 会话所属邮箱地址 (必填) - --ids string 会话 ID 列表,多个用英文逗号分隔,最多 100 个 (必填) - --action string 操作类型:markRead、markUnread、addTags、removeTags (必填) - --tag-ids string 标签 ID 列表,多个用英文逗号分隔;addTags/removeTags 时必填 (可选) -``` - -`--ids` 必须填写会话 ID 列表,不是邮件 ID 列表,最多 100 个。 - -成功时返回: - -```json -{ - "success": true -} -``` - -### [危险] 删除邮件会话 - -``` -Usage: - dws mail thread trash [flags] -Example: - dws mail thread trash --email user@company.com --id --yes -Flags: - --email string 会话所属邮箱地址 (必填) - --id string 要删除的会话 ID (必填) - --yes 确认执行此危险操作 (必填) -``` - -> ⚠️ **危险操作**:此命令会将邮件会话移动到已删除文件夹。建议先通过 `thread get` 确认目标会话后再执行。 - -将指定邮件会话移动到已删除文件夹,不会永久删除邮件。`--id` 必须填写会话 ID,不是邮件 ID。执行前需向用户确认,确认后传入 `--yes` 执行。 - -成功时返回: - -```json -{ - "success": true, - "result": {} -} -``` - -### [危险] 批量删除邮件会话 - -``` -Usage: - dws mail thread batch-trash [flags] -Example: - dws mail thread batch-trash --email user@company.com --ids , --yes -Flags: - --email string 会话所属邮箱地址 (必填) - --ids string 要删除的会话 ID 列表,多个用英文逗号分隔,最多 100 个 (必填) - --yes 确认执行此危险操作 (必填) -``` - -> ⚠️ **危险操作**:此命令会批量将邮件会话移动到已删除文件夹。建议先通过 `thread list` 确认目标会话后再执行。 - -将指定邮件会话批量移动到已删除文件夹,单次最多 100 个会话。不会永久删除邮件。`--ids` 必须填写会话 ID 列表,不是邮件 ID 列表。执行前需向用户确认,确认后传入 `--yes` 执行。 - -成功时返回: - -```json -{ - "success": true, - "result": {} -} -``` - -### 回复邮件 -``` -Usage: - dws mail message reply [flags] -Example: - dws mail message reply --from user@company.com --id - dws mail message reply --from user@company.com --id --subject "Re: 周报" --content "已收到,谢谢!" -Flags: - --from string 发件人邮箱 (必填),别名: --sender - --to string 收件人列表(可选) - --id string 要回复的邮件 ID (必填) - --subject string 回复邮件标题(可选) - --content string 回复正文(可选) - --attachment stringArray 附件文件路径,可多次指定 (可选) - --inline-attachment stringArray 内联图片路径,可多次指定,cid 自动生成 (可选) -``` - -**附件发送说明:** - -当指定 `--attachment` 或 `--inline-attachment` 时,CLI 自动执行以下编排流程: - -1. 调用 `create_reply_draft` 创建回复草稿(若有内联图片,正文自动转为 HTML 并注入 `` 标签) -2. 为每个普通附件创建上传会话并上传(`isInline=false`) -3. 为每个内联图片创建上传会话并上传(`isInline=true`,传入自动生成的 contentId) -4. 发送草稿 - -**返回字段:** - -| 字段 | 类型 | 说明 | -|------|------|------| -| `messageId` | `string` | 新生成的回复邮件 ID | - -### 回复所有人 -``` -Usage: - dws mail message reply-all [flags] -Example: - dws mail message reply-all --from user@company.com --id - dws mail message reply-all --from user@company.com --id --subject "Re: 周报" --content "感谢大家的参与!" -Flags: - --from string 发件人邮箱 (必填),别名: --sender - --to string 收件人列表(可选,包含发件人及所有原始收件人) - --id string 要回复的邮件 ID (必填) - --subject string 回复邮件标题(可选) - --content string 回复正文(可选) - --attachment stringArray 附件文件路径,可多次指定 (可选) - --inline-attachment stringArray 内联图片路径,可多次指定,cid 自动生成 (可选) -``` - -**附件发送说明:** - -当指定 `--attachment` 或 `--inline-attachment` 时,CLI 自动执行以下编排流程: - -1. 调用 `create_replyall_draft` 创建回复全部草稿(若有内联图片,正文自动转为 HTML 并注入 `` 标签) -2. 为每个普通附件创建上传会话并上传(`isInline=false`) -3. 为每个内联图片创建上传会话并上传(`isInline=true`,传入自动生成的 contentId) -4. 发送草稿 - -**返回字段:** - -| 字段 | 类型 | 说明 | -|------|------|------| -| `messageId` | `string` | 新生成的回复邮件 ID | - -### 转发邮件 -``` -Usage: - dws mail message forward [flags] -Example: - dws mail message forward --from user@company.com --id - dws mail message forward --from user@company.com --to colleague@company.com --id --subject "Fwd: 周报" -Flags: - --from string 发件人邮箱 (必填),别名: --sender - --to string 转发收件人列表(可选) - --id string 要转发的邮件 ID (必填) - --subject string 转发邮件标题(可选) - --content string 转发附言(可选) - --attachment stringArray 附件文件路径,可多次指定 (可选) - --inline-attachment stringArray 内联图片路径,可多次指定,cid 自动生成 (可选) -``` - -**附件发送说明:** - -当指定 `--attachment` 或 `--inline-attachment` 时,CLI 自动执行以下编排流程: - -1. 调用 `create_forward_draft` 创建转发草稿(若有内联图片,正文自动转为 HTML 并注入 `` 标签) -2. 为每个普通附件创建上传会话并上传(`isInline=false`) -3. 为每个内联图片创建上传会话并上传(`isInline=true`,传入自动生成的 contentId) -4. 发送草稿 - -**返回字段:** - -| 字段 | 类型 | 说明 | -|------|------|------| -| `messageId` | `string` | 新生成的转发邮件 ID | - -### 批量移动邮件到指定文件夹 -``` -Usage: - dws mail message batch-move [flags] -Example: - dws mail message batch-move --email user@company.com --ids , --folder 6 -Flags: - --email string 邮件所属邮箱地址 (必填) - --ids string 要移动的邮件 ID 列表,逗号分隔 (必填) - --folder string 目标文件夹 ID (必填) -``` - -常用文件夹 ID: 1=已发送, 2=收件箱, 3=垃圾邮件, 5=草稿, 6=已删除 - - -### 批量删除邮件 -``` -Usage: - dws mail message batch-delete [flags] -Example: - dws mail message batch-delete --email user@company.com --ids , -Flags: - --email string 邮件所属邮箱地址 (必填) - --ids string 要删除的邮件 ID 列表,逗号分隔 (必填) -``` - -### 批量修改邮件状态 - -批量修改邮件的已读状态或标签,通过 `--action` 指定操作类型。 - -**支持的操作类型(--action):** - -| action | 说明 | 需要的额外参数 | -|--------|------|---------------| -| `markRead` | 标记邮件为已读 | 无 | -| `markUnread` | 标记邮件为未读 | 无 | -| `addTags` | 给邮件增加标签 | `--tags`(标签 ID 列表,必填) | -| `removeTags` | 从邮件移除标签 | `--tags`(标签 ID 列表,必填) | - -**常用标签 ID:** - -| 标签 ID | 名称 | 图标 | -|---------|------|------| -| `1` | 跟进事项 | 小红旗 | -| `2` | 完成事项 | 绿色小勾 | -| `11` | 重要 | 星标 | - -``` -Usage: - dws mail message batch-update [flags] -Example: - dws mail message batch-update --email user@company.com --ids , --action markRead - dws mail message batch-update --email user@company.com --ids , --action addTags --tags 1,2 - dws mail message batch-update --email user@company.com --ids , --action removeTags --tags 11 -Flags: - --email string 邮件所属邮箱地址 (必填) - --ids string 要修改的邮件 ID 列表,逗号分隔 (必填) - --action string 操作类型: markRead/markUnread/addTags/removeTags (必填) - --tags string 标签 ID 列表,逗号分隔 (action 为 addTags/removeTags 时必填) -``` - -> **查询标签 ID:** 使用 `dws mail tag list --email <邮箱>` 可查看所有可用标签及其 ID。 - -### 查询邮件发送状态 -``` -Usage: - dws mail message verify [flags] -Example: - dws mail message verify --email user@company.com --internet-message-id -Flags: - --email string 邮件所属邮箱地址 (必填) - --internet-message-id string 邮件的 internetMessageId (必填),取自发送类命令返回值 -``` - -根据 `internetMessageId` 查询某封邮件当前的发送投递状态。 - -> **internetMessageId 来源:** `message send` / `draft send` / `message reply` / `message reply-all` / `message forward` 等发送类命令的返回值中均会带 `internetMessageId` 字段,可直接传入此命令查询发送结果。 - -**返回字段:** - -| 字段 | 类型 | 说明 | -|------|------|------| -| `message` | `object` | 邮件完整信息 | -| `message.sendStatus` | `string` | 发送状态,**嵌在 `message` 对象内部**(非顶层字段),取值见下表 | - -> **注意:** 顶层只有 `message` 和 `success` 两个字段,`sendStatus` 位于 `message.sendStatus`,不是与 `message` 平级的顶层字段。 - -**`sendStatus` 取值说明:** - -| 值 | 含义 | -|------|------| -| `none` | 未发送 | -| `posting` | 投递中 | -| `partial_success` | 部分成功(部分收件人投递成功) | -| `success` | 发送成功 | -| `failed` | 发送失败 | -| `unknown` | 未知状态 | - -### [危险] 撤回已发送的邮件 - -``` -Usage: - dws mail sent-message recall [flags] -Example: - dws mail sent-message recall --email user@company.com --id --subject "邮件主题" --yes -Flags: - --email string 发件人邮箱地址 (必填) - --id string 要撤回的邮件 ID (必填) - --subject string 邮件主题 (必填) - --yes 跳过确认提示,直接执行 (可选) -``` - -> CAUTION: 此命令会撤回已发送邮件。仅支持撤回同组织内未读邮件,执行前必须确认目标邮件、主题和影响范围。 - -**返回字段:** - -| 字段 | 类型 | 说明 | -|------|------|------| -| `id` | string | 撤回任务 ID,可用于 `recall-detail` 查询进度 | -| `success` | boolean | 接口调用是否成功 | -| `errorCode` / `errorMsg` | string | 错误码和错误信息(仅失败时存在) | - -### 查询邮件撤回进度 - -``` -Usage: - dws mail sent-message recall-detail [flags] -Example: - dws mail sent-message recall-detail --email user@company.com --id -Flags: - --email string 用户的邮箱地址 (必填) - --id string 撤回任务 ID (必填),由 recall 命令返回 -``` - -根据撤回任务 ID 查询邮件撤回进度。任务状态包括 `UNINITED` / `SUBMITTED` / `RUNNING` / `FINISHED` / `CANCELED` / `FAILED`。 - -**返回字段:** - -| 字段 | 类型 | 说明 | -|------|------|------| -| `id` | string | 撤回任务 ID | -| `status` | string | 任务状态 | -| `totalCount` / `succeededCount` / `failedCount` | int | 总数、成功数、失败数 | -| `details` | List[] | 每封邮件的撤回结果 | - -### 创建草稿 -``` -Usage: - dws mail draft create [flags] -Example: - dws mail draft create --from user@company.com --to colleague@company.com \ - --subject "草稿标题" --content "草稿正文" - dws mail draft create --from user@company.com --subject "草稿标题" - dws mail draft create --from user@company.com --subject "带附件草稿" \ - --content "见附件" --attachment ./report.pdf - dws mail draft create --from user@company.com --subject "带图片草稿" \ - --content "图表:[inline:chart.png]" --inline-attachment ./chart.png -Flags: - --from string 发件人邮箱 (必填),别名: --sender - --subject string 邮件标题 (必填) - --to string 收件人列表(可选,有确定收件人时才传) - --cc string 抄送人列表(可选,有确定抄送人时才传) - --content string 邮件正文(可选,有正文内容时才传) - --attachment stringArray 附件文件路径,可多次指定 (可选) - --inline-attachment stringArray 内联图片路径,可多次指定,cid 自动生成 (可选) -``` - -> **注意:** `--to`、`--cc`、`--content` 均为可选参数,**仅在用户明确提供对应信息时才传入**。若用户未指定收件人,不要传 `--to ""`(空字符串)。 - -**附件说明:** - -指定 `--attachment` 或 `--inline-attachment` 时,CLI 自动完成草稿创建和附件上传,**草稿保留在草稿箱,不会发送**。内联图片用法同 `message send`(`--content` 中使用 `[inline:文件名]` 占位符)。 - -**返回字段:** - -| 字段 | 类型 | 说明 | -|------|------|------| -| `result.message.id` | `string` | 新建草稿的邮件 ID(**嵌在 `result.message` 内**,无顶层 `messageId` 字段);`result.message` 还含 `internetMessageId`、`folderId`(草稿箱为 `5`)、`subject`、`from` 等 | - -### 更新草稿 -``` -Usage: - dws mail draft update [flags] -Example: - dws mail draft update --from user@company.com --id --subject "新标题" --content "新正文" - dws mail draft update --from user@company.com --id --content "见附件" --attachment ./report.pdf - dws mail draft update --from user@company.com --id \ - --content "图表:[inline:chart.png]" --inline-attachment ./chart.png -Flags: - --from string 发件人邮箱 (必填),别名: --sender - --id string 草稿邮件 ID (必填) - --to string 收件人列表(可选) - --cc string 抄送人列表(可选) - --subject string 邮件标题(可选) - --content string 邮件正文(可选) - --attachment stringArray 附件文件路径,可多次指定 (可选) - --inline-attachment stringArray 内联图片路径,可多次指定,cid 自动生成 (可选) -``` - -**附件说明:** - -指定 `--attachment` 或 `--inline-attachment` 时,CLI 自动完成草稿更新和附件上传,**草稿保留在草稿箱,不会发送**。内联图片用法同 `message send`(`--content` 中使用 `[inline:文件名]` 占位符)。 - -### 发送草稿 -``` -Usage: - dws mail draft send [flags] -Example: - dws mail draft send --from user@company.com --id -Flags: - --from string 发件人邮箱 (必填),别名: --sender - --id string 草稿邮件 ID (必填) -``` - -将草稿箱中已有的草稿发送出去。草稿 ID 来自 `draft create` 或 `message search`(`folderId:5`)的返回结果。 - -### 搜索邮箱用户(通讯录) -``` -Usage: - dws mail user search [flags] -Example: - dws mail user search --keyword "张三" - dws mail user search --email user@company.com --keyword "张三" - dws mail user search --email user@company.com --keyword "alice" --limit 10 - dws mail user search --email user@company.com --keyword "alice" --cursor -Flags: - --email string 搜索目标邮箱地址 (可选) - --keyword string 搜索关键词(未提供 --employee-no 时为必填) - --employee-no string 按工号搜索用户;提供此参数时 keyword 不再必填 (可选) - --cursor string 分页游标,取自响应中的 nextCursor 字段(可选) - --limit string 每页返回数量(可选) -``` - -> **重要区别:** -> - `mail user search` — 搜索**通讯录联系人/邮箱用户**(按姓名/关键词找人),用于获取某人的邮箱地址 -> - `mail message search` — 搜索**邮件内容**(按 KQL 语法搜邮件,如主题、发件人、日期等) -> -> 不要混淆:查找"某人的邮箱地址"用 `user search`;查找"某封邮件"用 `message search`。 -> -> 仅企业邮箱(非 `@dingtalk.com` 个人邮箱)可使用 `user search`;使用个人邮箱调用将因无权限而报错。 - -**返回字段:** - -| 字段 | 类型 | 说明 | -|------|------|------| -| `users` | `List[]` | 匹配的用户列表,每条包含用户 ID、邮箱地址、姓名、昵称、工号、职位、工作地 | -| `nextCursor` | `string` | 下一页游标,传入 `--cursor` 翻页 | -| `hasMore` | `boolean` | 是否还有更多数据 | - -**user 对象字段:** - -| 字段 | 类型 | 说明 | -|------|------|------| -| `id` | `string` | 用户 ID | -| `email` | `string` | 展示使用的邮件地址 | -| `name` | `string` | 用户名(人名) | -| `nickname` | `string` | 用户昵称(或者花名) | -| `employeeNo` | `string` | 工号 | -| `jobTitle` | `string` | 职位 | -| `workLocation` | `string` | 工作地 | - -### 创建邮件模板 -``` -Usage: - dws mail template create [flags] -Example: - dws mail template create --email user@company.com --name "周报模板" --subject "周报" --content "本周工作总结..." - dws mail template create --email user@company.com --from user@company.com --name "通知模板" --subject "通知" --content "..." --to a@x.com,b@x.com --cc c@x.com -Flags: - --email string 用户邮箱地址 (必填) - --from string 模板发件人邮箱 (可选) - --subject string 模板邮件标题 (必填) - --content string 模板邮件正文 (必填) - --name string 模板名称 (必填) - --to string 模板收件人列表,逗号分隔 (可选) - --cc string 模板抄送人列表,逗号分隔 (可选) - --is-draft 是否为草稿模板 (可选,默认 false) -``` - -> **草稿模板说明:** 传入 `--is-draft` 创建的模板为草稿模板,草稿模板支持后续通过 `template update` 修改内容。**非草稿模板创建后不可修改**(`template update` 仅对草稿模板有效)。 -> -> **草稿模板不出现在 `template list`(重要):** 实测 `template list` **只返回非草稿模板**;草稿模板(`--is-draft`)不在列表里,只能用 `template get --id <模板ID>` 直接获取。因此**不要用 `template list` 是否出现来判断草稿模板是否创建成功**——`template create` 返回 `success:true` 即已创建。 -> -> **模板 ID 前缀区分类型:** `template create` 返回的 `id` 前缀标识类型——非草稿模板为 `1:0:` 开头(如 `1:0:8bbeea56-...`),草稿模板为 `0:0:` 开头(如 `0:0:e3e2d134-...`)。 - -### 列举邮件模板 -``` -Usage: - dws mail template list [flags] -Example: - dws mail template list --email user@company.com --limit 20 - dws mail template list --email user@company.com --limit 20 --cursor -Flags: - --email string 用户邮箱地址 (必填) - --cursor string 分页游标,取自响应中的 nextCursor 字段 (可选) - --limit string 每页返回数量 (必填) -``` - -### 获取邮件模板详情 -``` -Usage: - dws mail template get [flags] -Example: - dws mail template get --email user@company.com --id -Flags: - --email string 用户邮箱地址 (必填) - --id string 模板唯一标识 (必填) -``` - -### 更新邮件模板 - -> ⚠️ 服务端限制:仅草稿模板(创建时带 `--is-draft`)可更新;非草稿模板更新会返回 1001 Invalid parameter。需要修改非草稿模板时,建议删除后用 `--is-draft` 重建。 - -> **重要限制:** `template update` **仅对草稿模板有效**。只有通过 `template create --is-draft` 创建的草稿模板才支持更新,非草稿模板调用 update 会返回 `Invalid parameter` 错误。 - -``` -Usage: - dws mail template update [flags] -Example: - dws mail template update --email user@company.com --id --subject "新标题" --content "新正文" - dws mail template update --email user@company.com --id --name "新模板名" -Flags: - --email string 用户邮箱地址 (必填) - --id string 模板唯一标识 (必填,必须是草稿模板的 ID) - --from string 模板发件人邮箱 (可选) - --subject string 模板邮件标题 (可选) - --content string 模板邮件正文 (可选) - --name string 模板名称 (可选) - --to string 模板收件人列表,逗号分隔 (可选) - --cc string 模板抄送人列表,逗号分隔 (可选) -``` - -### 删除邮件模板 -``` -Usage: - dws mail template delete [flags] -Example: - dws mail template delete --email user@company.com --id -Flags: - --email string 用户邮箱地址 (必填) - --id string 模板唯一标识 (必填) -``` - -> **特殊字符注意:** `--contact-id` 等 ID 参数的值可能包含 `$`、`!` 等 shell 特殊字符。在终端手动执行时,**必须用单引号**包裹这类参数值(如 `--contact-id '101_0:DzzzzyJqO10$---.hp5uBuR'`),双引号会导致 `$` 被 shell 变量展开,使 ID 值被篡改从而报错。通过 MCP 协议(JSON 传参)调用时无此问题。 - -### 创建邮件联系人 -``` -Usage: - dws mail contact create [flags] -Example: - dws mail contact create --email user@company.com --contact-email colleague@company.com --display-name "张三" - dws mail contact create --email user@company.com --contact-email colleague@company.com --first-name "三" --last-name "张" -Flags: - --email string 用户邮箱地址 (必填) - --contact-email string 联系人邮箱地址 (必填) - --first-name string 联系人名 (可选) - --middle-name string 联系人中间名 (可选) - --last-name string 联系人姓 (可选) - --display-name string 联系人显示名称 (可选) -``` - -### 列举邮件联系人 -``` -Usage: - dws mail contact list [flags] -Example: - dws mail contact list --email user@company.com --limit 20 - dws mail contact list --email user@company.com --limit 20 --cursor -Flags: - --email string 用户邮箱地址 (必填) - --cursor string 分页游标,取自响应中的 nextCursor 字段 (可选) - --limit string 每页返回数量 (必填) -``` - -> **重要区别:** `contact list` 列举的是**个人联系人**(用户自己保存/创建的联系人列表),不需要关键词;搜索**企业通讯录用户**(按姓名找人查邮箱)请用 `user search`。 - -### 更新邮件联系人 -``` -Usage: - dws mail contact update [flags] -Example: - dws mail contact update --email user@company.com --contact-id --display-name "李四" - dws mail contact update --email user@company.com --contact-id --contact-email new@company.com --first-name "四" --last-name "李" -Flags: - --email string 用户邮箱地址 (必填) - --contact-id string 联系人唯一标识 (必填) - --contact-email string 联系人邮箱地址 (可选) - --first-name string 联系人名 (可选) - --middle-name string 联系人中间名 (可选) - --last-name string 联系人姓 (可选) - --display-name string 联系人显示名称 (可选) -``` - -### 批量删除邮件联系人 - -> **特殊字符注意:** `--contact-ids` 的值可能包含 `$`、`!` 等 shell 特殊字符。在终端手动执行时,**必须用单引号**包裹这类参数值(如 `--contact-ids '101_0:DzzzzyJqO10$---.hp5uBuR'`),双引号会导致 `$` 被 shell 变量展开,使 ID 值被篡改从而报错。通过 MCP 协议(JSON 传参)调用时无此问题。 - -``` -Usage: - dws mail contact batch-delete [flags] -Example: - dws mail contact batch-delete --email user@company.com --contact-ids , -Flags: - --email string 用户邮箱地址 (必填) - --contact-ids string 要删除的联系人 ID 列表,逗号分隔 (必填) -``` - -### 获取自动回复配置 - -获取当前用户的邮件自动回复配置,包括是否启用、生效时间、回复范围和回复内容。 - -``` -Usage: - dws mail auto-reply get [flags] -Example: - dws mail auto-reply get --email user@company.com -Flags: - --email string 用户的邮箱地址 (必填) -``` - -**返回字段:** - -| 字段 | 类型 | 说明 | -|------|------|------| -| `enabled` | bool | 是否启用自动回复 (true=启用, false=禁用) | -| `startTime` | string | 自动回复开始时间 | -| `endTime` | string | 自动回复结束时间 | -| `scope` | string | 回复范围: "contact"(仅联系人) 或 "all"(所有人) | -| `content` | string | 自动回复内容 | - -### 更新自动回复配置 - -更新或设置用户的邮件自动回复配置。建议先通过 `auto-reply get` 获取当前配置,再传入需要修改的字段值。 - -``` -Usage: - dws mail auto-reply update [flags] -Example: - dws mail auto-reply update --email user@company.com --enabled true \ - --start "2026/07/01 09:00:00 +0800" --end "2026/07/07 18:00:00 +0800" \ - --scope all --content "出差中,请稍后联系" - dws mail auto-reply update --email user@company.com --enabled false \ - --start "2026/07/01 09:00:00 +0800" --end "2026/07/07 18:00:00 +0800" \ - --scope all --content "已关闭自动回复" -Flags: - --email string 用户的邮箱地址 (必填) - --enabled string 是否启用自动回复: true/false (必填) - --start string 自动回复开始时间,格式: YYYY/MM/DD HH:MM:SS +ZZZZ (必填) - --end string 自动回复结束时间,格式: YYYY/MM/DD HH:MM:SS +ZZZZ (必填) - --scope string 回复范围: contact(仅联系人)/all(所有人) (必填) - --content string 自动回复内容 (必填) -``` - -### 个人收信白名单管理 - -``` -Usage: - dws mail allow-list list [flags] - dws mail allow-list add [flags] - dws mail allow-list remove [flags] -Examples: - dws mail allow-list list --email user@company.com - dws mail allow-list add --email user@company.com --entries a@b.com,@example.com - dws mail allow-list remove --email user@company.com --entries a@b.com,@example.com -Flags: - --email string 用户的邮箱地址 (必填) - --entries string 逗号分隔的地址列表,支持邮件地址或 @domain.com 域名(add/remove 必填) -``` - -### 个人收信黑名单管理 - -``` -Usage: - dws mail block-list list [flags] - dws mail block-list add [flags] - dws mail block-list remove [flags] -Examples: - dws mail block-list list --email user@company.com - dws mail block-list add --email user@company.com --entries spam@bad.com,@junk.com - dws mail block-list remove --email user@company.com --entries spam@bad.com,@junk.com -Flags: - --email string 用户的邮箱地址 (必填) - --entries string 逗号分隔的地址列表,支持邮件地址或 @domain.com 域名(add/remove 必填) -``` - -### 收信规则管理 - -#### 列出收信规则 - -列出当前用户的所有收信规则,包括规则名称、启用状态、条件、动作和排序。 - -``` -Usage: - dws mail rule list [flags] -Example: - dws mail rule list --email user@company.com -Flags: - --email string 用户的邮箱地址 (必填) -``` - -**返回字段:** - -| 字段 | 类型 | 说明 | -|------|------|------| -| `total` | int | 规则总数 | -| `rules` | List[] | 规则列表 | -| `rules[].id` | string | 规则 ID | -| `rules[].name` | string | 规则名称 | -| `rules[].enabled` | bool | 是否启用 | -| `rules[].conditions` | List[] | 规则条件列表 | -| `rules[].actions` | List[] | 规则动作列表 | -| `rules[].order` | null | 排序字段;实测存量与新建规则该字段**恒为 `null`**,不返回有效排序值,不要依赖它判断规则顺序 | - -#### 创建收信规则 - -创建一条新的收信规则。支持设置规则名称、启用状态、匹配条件和执行动作。 - -> **`--conditions` 和 `--actions`** 为 JSON 数组字符串。 - -**界面与参数对应关系:** - -| 界面元素 | CLI 参数 / JSON 字段 | 说明 | -|----------|---------------------|------| -| 规则名称 | `--name` | 必填,规则的显示名称 | -| 如果满足以下「全部」条件 | `--conditions` | 多个条件之间为 **AND** 关系(即所有条件都满足才触发) | -| ├ 对象下拉(发件人) | `object: "from"` | 条件匹配的对象,可选值见下方 | -| ├ 操作下拉(包含) | `operation: "include"` | 匹配方式,可选值见下方 | -| └ 关键词输入框 | `keyword: "a@test.com"` | 匹配的具体值 | -| 执行以下操作 | `--actions` | 条件满足后执行的动作列表 | -| ├ 动作下拉(移动到文件夹) | `action: "ActSavetoFolder"` | 动作类型,可选值见下方 | -| └ 参数选择(收件箱) | `parameters: ["2"]` | 动作的参数,如目标文件夹 ID | - -**条件逻辑说明:** - -- `--conditions` 数组中的多个条件之间为 **AND(且)** 关系,即所有条件都满足才触发规则 -- 同一个条件对象(如 `from`)内部的 `or` 数组中多个表达式之间为 **OR(或)** 关系 -- 同一个 `and` 数组中的多个子条件之间为 **AND(且)** 关系 - -**条件对象 (object) 与合法操作类型 (operation) 组合:** - -| object | 合法 operation | 说明 | -|--------|---------------|------| -| `from` | `include`(包含), `exclude`(不包含), `oneof`(是联系人之一), `noneof`(不是联系人之一) | 匹配发件人地址或名称 | -| `to` | `include`(包含), `exclude`(不包含), `oneof`(是联系人之一), `noneof`(不是联系人之一) | 匹配收件人地址或名称 | -| `subject` | `include`(包含), `exclude`(不包含) | 匹配邮件主题 | -| `attachment` | `exist`(是否存在附件) | keyword="1" 表示有附件,keyword="0" 表示无附件 | -| `x-aliyun-size` | `greater`(大于), `less`(小于) | 邮件大小,单位为 **字节(Bytes)**(1KB=1024, 1MB=1048576);可组合使用表示范围区间 | - -**操作类型 (operation) 详细说明:** - -| 值 | 界面显示 | 适用 object | 说明 | -|----|---------|------------|------| -| `include` | 包含 | from, to, subject | 字段包含关键词 | -| `exclude` | 不包含 | from, to, subject | 字段不包含关键词 | -| `oneof` | 是联系人之一 | from, to | 字段值在给定联系人列表中 | -| `noneof` | 不是联系人之一 | from, to | 字段值不在给定联系人列表中 | -| `greater` | 大于 | x-aliyun-size | 数值大于阈值,单位字节(Bytes) | -| `less` | 小于 | x-aliyun-size | 数值小于阈值,单位字节(Bytes) | -| `exist` | 存在 | attachment | keyword="1" 表示有附件,keyword="0" 表示无附件 | - -**动作类型 (action) 可选值:** - -| 值 | 界面显示 | parameters 说明 | 前置依赖 | -|----|---------|----------------|----------| -| `ActSavetoFolder` | 移动到文件夹 | 目标文件夹 ID,如 `["2"]`(2=收件箱) | 需先通过 `dws mail folder list` 获取文件夹 ID | -| `ActFlagMail` | 标记标签 | 标签 ID 列表,逗号分隔,如 `["102,11,1"]` | 需先通过 `dws mail tag list` 获取标签 ID | -| `ActFlagMail2` | 标记已读 | `"asread"`(标记已读),服务端仅支持标记已读,不支持标记未读 | 无 | -| `ActReply` | 自动回复 | 回复内容文本,如 `["感谢您的来信"]` | 无 | - -**条件 JSON 结构说明:** - -每个条件由 `object`(匹配对象)和 `or`(OR 表达式列表)组成,`or` 内嵌 `and`(AND 条件列表)。 - -| 字段 | 说明 | -|------|------| -| `object` | 条件对象,取值及合法 operation 见上方组合表 | -| `or` | OR 表达式列表,同一 object 下多个 or 项之间为 **OR** 关系 | -| `and` | AND 条件列表,同一 or 项内多个 and 子条件之间为 **AND** 关系 | -| `operation` | 操作类型,必须与 object 合法组合(见上方组合表) | -| `keyword` | 关键词/阈值;attachment+exist 时 "1"=有附件/"0"=无附件;x-aliyun-size 时单位为字节(Bytes),如 1KB=1024, 1MB=1048576 | -| `ignoreCase` | 是否忽略大小写(布尔值,仅 from/to/subject + include/exclude 时需要) | - -**完整 conditions JSON 示例:** - -```json -[ - {"object":"from","or":[ - {"and":[{"operation":"oneof","keyword":"a@test.com","ignoreCase":true}]}, - {"and":[{"operation":"oneof","keyword":"b@test.com","ignoreCase":true}]} - ]}, - {"object":"subject","or":[{"and":[{"operation":"include","keyword":"报告","ignoreCase":true}]}]}, - {"object":"attachment","or":[{"and":[{"operation":"exist","keyword":"1"}]}]}, - {"object":"x-aliyun-size","or":[{"and":[{"operation":"greater","keyword":"1024"},{"operation":"less","keyword":"10240"}]}]} -] -``` - -> 上例表示:发件人是 a@test.com **或** b@test.com **且** 主题包含"报告" **且** 有附件 **且** 大小在 1KB(1024字节)~10KB(10240字节) 之间。 -> -> **同一 object 下匹配多个值的 OR 写法:** 在 `or` 数组中放多个 `and` 项(每个 `and` 对应一个匹配值),而非在一个 `and` 中放多个条件。例如上方 `from` 条件中,两个邮箱地址分别作为独立的 `and` 项放在 `or` 数组中,表示"满足任一即可"。 - -**完整 actions JSON 示例:** - -```json -[ - {"action":"ActSavetoFolder","parameters":["2"]}, - {"action":"ActFlagMail","parameters":["102,11,1"]}, - {"action":"ActFlagMail2","parameters":["asread"]}, - {"action":"ActReply","parameters":["感谢您的来信,我将尽快回复"]} -] -``` - -> **注意:** 使用 `ActSavetoFolder` 前需先通过 `dws mail folder list` 获取文件夹 ID;使用 `ActFlagMail` 前需先通过 `dws mail tag list` 获取标签 ID。 - -``` -Usage: - dws mail rule create [flags] -Example: - dws mail rule create --email user@company.com --name "VIP邮件标记" --enabled true \ - --conditions '[{"object":"from","or":[{"and":[{"operation":"include","keyword":"vip@company.com","ignoreCase":true}]}]}]' \ - --actions '[{"action":"ActFlagMail2","parameters":["asread"]}]' - dws mail rule create --email user@company.com --name "大附件归档" --enabled true \ - --conditions '[{"object":"x-aliyun-size","or":[{"and":[{"operation":"greater","keyword":"10485760"}]}]}]' \ - --actions '[{"action":"ActSavetoFolder","parameters":["6"]}]' -Flags: - --email string 用户的邮箱地址 (必填) - --name string 规则名称 (必填) - --enabled string 是否启用: true/false (必填) - --conditions string 规则条件 JSON 数组 (可选) - --actions string 规则动作 JSON 数组 (必填) -``` - -> **⚠️ 实测:** 尽管 `--help` 把 `--conditions` 标为(可选),服务端实际要求它**非空**——省略或传空会返回 `209 SYS_UNKNOWN_ERROR(1001)` 失败,帮助文本承诺的"命中所有邮件"无条件匹配**不可用**。create 请始终传入有效条件。 - -**返回字段:** - -| 字段 | 类型 | 说明 | -|------|------|------| -| `success` | bool | 创建是否成功 | -| `id` | string | 新建规则 ID | - -> 实测 `rule create` 成功仅返回 `{id, success}`,**不返回** `errorCode` / `errorMsg`。 - -#### 更新收信规则 - -更新已有的收信规则。**所有参数(含 `--conditions`)均须传入有效值**——见下方警告。 - -> **建议工作流:** 先通过 `dws mail rule list` 获取当前规则的完整配置,再传入需要修改的字段值。 -> -> **⚠️ 实测:** `--help` 称 `--conditions` 为空即命中所有邮件,但该无条件匹配**不可用**——省略或传空会返回 `209 SYS_UNKNOWN_ERROR(1001)` 失败。update 请始终传入有效条件。`--actions` 格式同 create 命令。 - -``` -Usage: - dws mail rule update [flags] -Example: - dws mail rule update --email user@company.com --id --name "新规则名" --enabled true \ - --actions '[{"action":"ActSavetoFolder","parameters":["6"]}]' - dws mail rule update --email user@company.com --id --name "全量归档" --enabled false \ - --conditions '[{"object":"subject","or":[{"and":[{"operation":"include","keyword":"报告","ignoreCase":true}]}]}]' \ - --actions '[{"action":"ActSavetoFolder","parameters":["6"]}]' -Flags: - --email string 用户的邮箱地址 (必填) - --id string 规则 ID (必填) - --name string 规则名称 (必填) - --enabled string 是否启用: true/false (必填) - --conditions string 规则条件 JSON 数组 (可选,为空表示命中所有邮件) - --actions string 规则动作 JSON 数组 (必填) -``` - -> **⚠️ 实测:** 上方 `--conditions` 的"(可选,为空表示命中所有邮件)"是 `--help` 原文,但服务端实际要求它**非空**——省略或传空会返回 `209 SYS_UNKNOWN_ERROR(1001)` 失败。update 须传入有效条件。 - -**返回字段:** - -| 字段 | 类型 | 说明 | -|------|------|------| -| `success` | bool | 更新是否成功 | -| `result` | object | 更新结果(实测为空对象 `{}`) | - -> 实测 `rule update` 成功仅返回 `{result:{}, success}`,**不返回** `errorCode` / `errorMsg`。 - -#### 删除收信规则 - -删除指定的收信规则。 - -``` -Usage: - dws mail rule delete [flags] -Example: - dws mail rule delete --email user@company.com --id -Flags: - --email string 用户的邮箱地址 (必填) - --id string 规则 ID (必填) -``` - -**返回字段:** - -| 字段 | 类型 | 说明 | -|------|------|------| -| `success` | bool | 删除是否成功 | -| `result` | object | 删除结果(实测为空对象 `{}`) | - -> 实测 `rule delete` 成功仅返回 `{result:{}, success}`,**不返回** `errorCode` / `errorMsg`。 - -#### 调整收信规则排序 - -调整指定收信规则的排序位置,向上(up)或向下(down)移动。 - -``` -Usage: - dws mail rule adjust [flags] -Example: - dws mail rule adjust --email user@company.com --id --direction up - dws mail rule adjust --email user@company.com --id --direction down -Flags: - --email string 用户的邮箱地址 (必填) - --id string 规则 ID (必填) - --direction string 调整方向: up/down (必填) -``` - -**返回字段:** - -| 字段 | 类型 | 说明 | -|------|------|------| -| `success` | bool | 调整是否成功 | -| `result` | object | 调整结果(实测为空对象 `{}`) | - -> 实测 `rule adjust` 成功仅返回 `{result:{}, success}`,**不返回** `errorCode` / `errorMsg`。 - -## 通用错误说明 - -以下错误适用于所有 mail 命令。 - -| 错误标识 | 含义 | 处理建议 | -|----------|------|----------| -| `domain.notFound` | 该用户的邮箱不是由钉钉邮箱托管,无法完成操作 | 确认邮箱是否已开通钉钉企业邮箱服务 | - -## 意图判断 - -用户说"我的邮箱/邮箱地址" → `mailbox list`(**仅限查询自己的邮箱,不能查他人**) -用户说"邮箱详情/邮箱容量/邮箱别名/邮箱 profile" → `mailbox profile --email <邮箱>` -用户说"获取/查找/得到 某人的邮箱地址" → **不是 `mailbox list`**,走三路并发查询流程(见「查找他人邮箱地址」章节) -用户说"找邮件/搜邮件/查邮件" → `message search` -用户说"看邮件/打开邮件/邮件内容" → 先 `message search` 获取 messageId,再 `message get` -用户说"批量查看邮件详情/批量打开这些邮件/按多个邮件ID取详情" → `message batch-get --ids `(单次最多 20 个) -用户说"发邮件/写邮件" → 先 `mailbox list` 获取发件地址,再 `message send` -用户说“给(某人名字)发邮件” / “查询某人发给我的邮件” / “查询发给某人的邮件” / 任何涉及按人名查找邮箱的场景 → - **第一步**:并发同时发起以下三路查询,取最先返回有效邮箱的结果;若三路均无有效邮箱,ask_human 请用户提供,禁止臆测: - 1. `aisearch person --keyword <姓名>` → `contact user get --ids `,提取 `orgAuthEmail` - 2. `mail user search --email <当前邮箱> --keyword <姓名>`,提取 `users[].email`(仅企业邮箱可用) - 3. `contact user search --keyword <姓名>`,提取用户邮箱字段 - **第二步**:用获得的目标邮箱拼入 KQL(如 `from:` 或 `to:`)执行 `message search`,或用于 `message send` -用户说"发带附件的邮件/发邮件附件" → 先 `mailbox list` 获取发件地址,再 `message send --attachment <文件路径>` -用户说"给(某人名字)发邮件" → 先 `aisearch person` 获取 userId,再 `contact user get` 获取收件人邮箱,再 `message send` -用户说"查看附件/邮件附件/有什么附件" → 先 `message search` 获取 messageId,再 `attachment list` -用户说"下载附件/保存附件/把附件存到本地/把所有附件下载到..." → 先 `message search` 获取 messageId,再 `attachment list` 获取 attachmentId 和 name,最后逐个 `attachment download`(**不支持批量下载,不存在 download_batch/download_all 命令,必须逐个下载**) -用户说"把XX邮件的所有附件都下载" / "批量下载附件" / "下载4月所有发票邮件的附件" → **不存在批量下载命令**,必须按以下流程循环执行:1) `message search` 搜索匹配邮件获取 messageId 列表 → 2) 对每封邮件 `attachment list` 获取 attachmentId + name → 3) 对每个附件逐个调用 `attachment download`。不要编造 `download_batch` / `download_all` / `batch_download` 等不存在的命令 -用户说"创建邮件文件夹/新建邮箱文件夹/新建邮件目录" → `folder create` -用户说"在某个邮件文件夹下创建子文件夹" → 先 `folder list` 找到父文件夹 ID,再 `folder create --folder `;禁止把父文件夹名称直接填给 `--folder` -用户说"删除邮件文件夹/删除邮箱文件夹/删除邮件目录" → 先确认要删除的文件夹 ID;如果用户只给名称,先 `folder list` 找到 `folders[].id`,再 `folder delete --id ` -用户说"重命名邮件文件夹/修改邮箱文件夹名称/更新邮件目录名称" → 先确认要更新的文件夹 ID;如果用户只给原名称,先 `folder list` 找到 `folders[].id`,再 `folder update --id --name <新名称>` -用户说"查看邮件标签/列出邮箱标签/查看邮箱 label" → `tag list` -用户说"创建邮件标签/新建邮箱标签/新增 label" → `tag create` -用户说"在某个邮件标签下创建子标签" → 先 `tag list` 找到父标签 ID,再 `tag create --parent-id `;禁止把父标签名称直接填给 `--parent-id` -用户说"删除邮件标签/删除邮箱标签/删除 label" → 先确认要删除的标签 ID;如果用户只给名称,先 `tag list` 找到 `tags[].id`,再 `tag delete --id ` -用户说"重命名邮件标签/修改邮箱标签名称/更新 label 名称" → 先确认要更新的标签 ID;如果用户只给原名称,先 `tag list` 找到 `tags[].id`,再 `tag update --id --name <新名称>` -用户说"列出邮件会话/查看会话列表/查看某个文件夹里的邮件会话" → 先确认邮箱地址和文件夹 ID;如果只有文件夹名称,先 `folder list` 找到 `folders[].id`,再 `thread list --folder ` -用户说"标记会话已读/未读/给会话加标签/移除会话标签" → 用 `thread update`;如果是多条会话,用 `thread batch-update`;标签操作必须先有标签 ID -用户说"删除会话/把会话放入已删除/批量删除会话" → 单条用 `thread trash`,多条用 `thread batch-trash`;传入的是会话 ID,不是邮件 ID -用户说"批量标记邮件已读/未读/给邮件加标签/移除邮件标签" → `message batch-update --action markRead/markUnread/addTags/removeTags`(操作对象是邮件 ID;会话级操作用 `thread update/batch-update`) -用户说"邮件发出去了吗/查邮件发送状态/确认邮件是否发送成功/邮件投递结果" → 用发送类命令返回的 `internetMessageId`,调用 `message verify` 查询 `sendStatus` -用户说"撤回邮件/撤回已发送邮件" → `sent-message recall --email <邮箱> --id --subject "<主题>" --yes`(危险操作,先确认) -用户说"查询邮件撤回进度/撤回任务状态" → `sent-message recall-detail --email <邮箱> --id ` -用户说"设置/更新/关闭自动回复" → `auto-reply update`(所有字段必填,先 `auto-reply get` 获取当前配置) -用户说"收信白名单/添加白名单/移除白名单" → `allow-list list/add/remove` -用户说"收信黑名单/添加黑名单/移除黑名单" → `block-list list/add/remove` -用户说"查看会话/获取会话/看这封邮件的会话" → 先 `message search` 或 `message get` 获取邮件中的 `conversationId`,再 `thread get` -用户说"搜索/查找/联系 邮箱用户/联系人/某人的邮箱地址" → `user search`(搜索通讯录人员,不是搜邮件内容) -用户说"发送草稿/把草稿发出去/发这封草稿" → 先 `message search --query "folderId:5"` 找到草稿 messageId,再 `draft send` -用户说"翻页继续搜索联系人/通讯录" → `user search --cursor `(注意:不是 `message search`) - -**`user search` vs `message search` 关键区别:** -- `user search`:搜索的是**人**(通讯录联系人),入参是 `--keyword 姓名`,返回用户信息 -- `message search`:搜索的是**邮件**(邮件内容),入参是 `--query KQL表达式`,返回邮件列表 - - -## 严格禁止 (NEVER DO) -- 明确禁止猜测、假设、推断发件人和收件人邮箱 -- 无法获取邮箱时,强引导ask_human,由用户确认,不要通过假设或其他方式继续执行 -- **严禁在用户未明确指定使用个人邮箱时,默认选择 `@dingtalk.com` 个人邮箱作为 `--email` / `--from`**;默认必须从 `mailbox list` 中挑选企业邮箱 -- **涉及带附件的邮件操作时,严禁上传到钉钉媒体存储(media upload)**;必须使用对应命令的 `--attachment` / `--inline-attachment` 参数,由 CLI 内部完成附件处理 -- **严禁编造不存在的批量下载命令**(如 `attachment download_batch`、`attachment download_all`、`attachment batch_download` 等)。下载附件只有 `attachment download` 一条命令,每次只能下载一个附件;需要批量下载时必须循环调用 -- **严禁把文件夹名称当作 `folder create --folder` 的值**;`--folder` 只能填父文件夹 ID,父文件夹 ID 必须来自 `folder list` 的 `folders[].id` -- **严禁把文件夹名称当作 `folder delete/update --id` 的值**;`--id` 只能填要操作的文件夹 ID,文件夹 ID 必须来自 `folder list` 的 `folders[].id` 或 `folder create` 的 `result.folder.id` -- **严禁把标签名称当作 `tag create --parent-id` 的值**;`--parent-id` 只能填父标签 ID,父标签 ID 必须来自 `tag list` 的 `tags[].id` -- **严禁把标签名称当作 `tag delete/update --id` 的值**;`--id` 只能填要操作的标签 ID,标签 ID 必须来自 `tag list` 的 `tags[].id` 或 `tag create` 的 `result.tag.id` -- **严禁更新或删除系统标签**;`tag update/delete` 只适用于用户自定义标签 -- **严禁把文件夹名称当作 `thread list --folder` 的值**;`--folder` 只能填文件夹 ID,文件夹 ID 必须来自 `folder list` 的 `folders[].id` -- **严禁把邮件 ID 当作 `thread get/update/trash/batch-update/batch-trash` 的会话 ID**;这些命令需要 conversationId,可来自 `thread list` 的 `conversations[].id` 或邮件结果中的 `conversationId` - -## 核心工作流 - -```bash -# 1. 查看可用邮箱 — 提取邮箱地址 -dws mail mailbox list --format json - -# 1b. 创建顶层邮件文件夹 -dws mail folder create --email user@company.com --name "项目资料" --format json - -# 1c. 创建子文件夹 — 先通过 folder list 获取父文件夹 id,再传给 --folder -dws mail folder list --email user@company.com --format json -dws mail folder create --email user@company.com --name "子文件夹" --folder --format json - -# 1d. 更新或删除邮件文件夹 — 先通过 folder list 获取目标文件夹 id,再传给 --id -dws mail folder list --email user@company.com --format json -dws mail folder update --email user@company.com --id --name "新文件夹名" --format json -dws mail folder delete --email user@company.com --id --format json - -# 1e. 创建顶层邮件标签 -dws mail tag create --email user@company.com --name "项目资料" --format json - -# 1f. 创建子标签 — 先通过 tag list 获取父标签 id,再传给 --parent-id -dws mail tag list --email user@company.com --format json -dws mail tag create --email user@company.com --name "子标签" --parent-id --format json - -# 1g. 更新或删除邮件标签 — 先通过 tag list 获取目标标签 id,再传给 --id -dws mail tag list --email user@company.com --format json -dws mail tag update --email user@company.com --id --name "新标签名" --format json -dws mail tag delete --email user@company.com --id --format json - -# 1h. 列出文件夹中的邮件会话 — 先通过 folder list 获取文件夹 id,再传给 --folder -dws mail folder list --email user@company.com --format json -dws mail thread list --email user@company.com --folder --limit 10 --format json - -# 1i. 修改或删除邮件会话 — 先通过 thread list 获取 conversationId -dws mail thread update --email user@company.com --id --action markRead --format json -dws mail thread batch-update --email user@company.com --ids , --action markUnread --format json -dws mail thread trash --email user@company.com --id --yes --format json -dws mail thread batch-trash --email user@company.com --ids , --yes --format json - -# 2. 搜索邮件 — 提取 messageId -dws mail message search --email user@company.com \ - --query "subject:\"周报\" AND date>2025-06-01T00:00:00Z" --limit 10 --format json - -# 3. 查看邮件详情 -dws mail message get --email user@company.com --id --format json - -# 4. 发送邮件(纯文本) -dws mail message send --from user@company.com --to colleague@company.com \ - --subject "周报" --content "本周完成…" --format json - -# 4b. 发送带附件的邮件(自动编排:创建草稿→上传附件→发送草稿) -dws mail message send --from user@company.com --to colleague@company.com \ - --subject "周报" --content "见附件" --attachment ./report.pdf --format json - -# 4c. 发送带内联图片的邮件(正文自动转 HTML, 标签自动注入) -dws mail message send --from user@company.com --to colleague@company.com \ - --subject "图表周报" --content "本周图表如下:[inline:chart.png]" \ - --inline-attachment ./chart.png --format json - -# 5. 下载邮件附件到本地(每次只能下载一个附件,不支持批量下载) -# 步骤 5.1:搜索匹配的邮件,获取 messageId 列表 -# 示例:下载4月所有发票邮件的附件 -dws mail message search --email user@company.com \ - --query "subject:发票 AND date>2025-04-01T00:00:00Z AND date<2025-05-01T00:00:00Z AND hasAttachments:true" --limit 50 --format json - -# 步骤 5.2:对每封邮件,列出附件获取 attachmentId 和 name -# (对搜索结果中的每封邮件都要执行一次) -dws mail attachment list --email user@company.com --id --format json - -# 步骤 5.3:对每个附件逐个下载(没有批量下载命令,必须循环调用) -dws mail attachment download --email user@company.com \ - --message-id --attachment-id --name report.pdf --output ~/invoices/ - -# 5. 获取邮件所属会话详情(thread) -# 步骤 5.1:先通过 message search 或 message get 获取邮件中的 conversationId -dws mail message search --email user@company.com \ - --query "subject:\"周报\"" --limit 5 --format json -# 从返回的邮件列表中提取 conversationId 字段 - -# 步骤 5.2:用 conversationId 获取会话详情 -dws mail thread get --email user@company.com --id --format json - -# 步骤 5.3(可选):同时返回会话内所有邮件列表 -dws mail thread get --email user@company.com --id --select messages --format json -``` - -## 上下文传递表 - -| 操作 | 从返回中提取 | 用于 | -|------|-------------|------| -| `mailbox list` | 邮箱地址 | message search/get/send/thread get 的 --email/--from | -| `mailbox profile` | `emailAliases` / `mboxSize` / `mboxSizeUsed` | 展示邮箱别名与容量信息 | -| `message search` | `messageId` | message get 的 --id | -| `message search` | `messageId` | message batch-get 的 --ids | -| `message search` | `conversationId` | thread get 的 --id | -| `message search` | `messageId` | attachment list 的 --id | -| `attachment list` | `attachments[].id` / `attachments[].name` | attachment download 的 --attachment-id / --name | -| `message get` | `conversationId` | thread get 的 --id | -| `folder list` | `folders[].id` | folder create 的 --folder;folder delete/update 的 --id | -| `folder create` | `result.folder.id` | 后续创建子文件夹或移动邮件时作为 --folder;更新/删除该文件夹时作为 --id | -| `folder list` | `folders[].id` | thread list 的 --folder | -| `thread list` | `conversations[].id` | thread get/update/trash/batch-update/batch-trash 的 --id/--ids | -| `tag list` | `tags[].id` | message batch-update 的 --tags;thread update/batch-update 的 --tag-ids | -| `tag list` | `tags[].id` | tag create 的 --parent-id;tag delete/update 的 --id | -| `tag create` | `result.tag.id` | 后续创建子标签时作为 --parent-id;更新/删除该标签时作为 --id | -| `aisearch person` → `contact user get` / `contact user search` / `mail user search` | 用户邮箱 (orgAuthEmail / email) | message send 的 --to/--cc(三路并发,取先到结果) | -| `user search` | 用户邮箱 (email) | message send 的 --to/--cc | -| `message send` / `draft send` / `message reply` / `message reply-all` / `message forward` | `internetMessageId` | `message verify` 的 --internet-message-id | -| `sent-message recall` | `id` | sent-message recall-detail 的 --id | -| `auto-reply get` | `enabled` / `startTime` / `endTime` / `scope` / `content` | auto-reply update 的当前配置基线 | - -## 注意事项 - -- `mailbox list` 返回用户所有邮箱(含个人和企业),每条记录包含邮箱地址、账号类型、所属企业。**默认一律选择企业邮箱**(除非用户明确指定使用个人邮箱);若有多个企业邮箱可选,优先匹配用户当前所在企业的那一个;仍无法判断时向用户确认后再操作。详见文档顶部「默认邮箱选择规则」章节 -- `message search` 返回邮件 ID 和元信息(不含正文),需 `message get` 获取完整内容 -- KQL 查询支持 AND/OR/NOT 组合,字段值含空格时需用双引号 -- `--cc` 抄送人支持多人,逗号分隔 -- 收件人邮箱获取:用户只知道同事名字时,**并发**同时执行以下三路查询,取最先返回有效邮箱的结果,无需等待其他路完成: - 1. `dws aisearch person --keyword "名字" --dimension name` → `dws contact user get --ids `,提取 `orgAuthEmail` - 2. `dws mail user search --email <发件人邮箱> --keyword "名字"`,提取 `users[].email`(仅企业邮箱账号可调用,个人 @dingtalk.com 邮箱会报权限错误可忽略) - 3. `dws contact user search --keyword "名字"`,提取用户邮箱字段 - 若三路均无有效邮箱,必须 ask_human 请用户手动提供收件人邮箱,严禁臆测和假设 -- `thread get` 无法直接通过邮箱地址查询会话列表,**必须先有 conversationId**;conversationId 来自 `message search` 或 `message get` 返回的邮件字段 `conversationId` -- `thread get` 默认即返回会话内 `messages` 列表,但列表中每封邮件的正文(`body`)、收件人(`toRecipients`)等字段默认为 null;需在 `--select` 中额外指定才有值,如 `--select messages,internetMessageId`(多个字段用英文逗号分隔) -- `thread get` 返回的 `conversation` 不含 `flag` 字段,会话标识请从 `thread list` 获取 -- `user search` 仅支持企业邮箱(非 `@dingtalk.com` 个人邮箱),使用个人邮箱将因无权限报错;搜到的用户邮箱(`email` 字段)可直接用于 `message send` 的 `--to`/`--cc` 参数 -- `thread list --folder` 的值必须是文件夹 ID,不是文件夹显示名称;不知道文件夹 ID 时,先调用 `folder list` 查 `folders[].id` -- `thread get/update/trash/batch-update/batch-trash` 使用的是会话 ID(conversationId),不是邮件 ID;会话 ID 可来自 `thread list` 的 `conversations[].id`,也可来自 `message search` 或 `message get` 返回的 `conversationId` -- `thread update` / `thread batch-update` 仅支持 `markRead`、`markUnread`、`addTags`、`removeTags`;标签操作必须传 `--tag-ids` -- `folder create --folder` 的值必须是父文件夹 ID,不是文件夹显示名称;不知道父文件夹 ID 时,先调用 `folder list` 查 `folders[].id` -- `folder delete/update --id` 的值必须是目标文件夹 ID,不是文件夹显示名称;不知道目标文件夹 ID 时,先调用 `folder list` 查 `folders[].id` +> 渐进式兼容入口:新任务优先从 [邮件能力索引](./mail-index.md) 选择专题。 + +| 任务 | 参考 | +|---|---| +| 命令速查与模块入口 | [mail-command-index.md](./mail-command-index.md);从该索引按命令族加载详细参数 | +| 邮箱选择与意图 | [默认邮箱规则](./mail-default-mailbox-rules.md) · [意图判断](./mail-intent.md) | +| 工作流与上下文 | [核心工作流](./mail-workflow.md) · [上下文传递](./mail-context.md) | +| 错误、禁止项、注意事项 | [通用错误](./mail-common-errors.md) · [严格禁止](./mail-never-do.md) · [注意事项](./mail-notes.md) | diff --git a/skills/multi/dingtalk-mail/scripts/mail_send_with_cc.py b/skills/multi/dingtalk-mail/scripts/mail_send_with_cc.py index b8ad2a31..207f64f6 100644 --- a/skills/multi/dingtalk-mail/scripts/mail_send_with_cc.py +++ b/skills/multi/dingtalk-mail/scripts/mail_send_with_cc.py @@ -55,18 +55,6 @@ def validate_emails(emails_str: str) -> bool: return True -def unwrap_result(data: Any) -> Any: - while isinstance(data, dict): - for key in ('result', 'content', 'data'): - nested = data.get(key) - if isinstance(nested, (dict, list)): - data = nested - break - else: - return data - return data - - def get_my_email(dry_run: bool = False) -> Optional[str]: data = run_dws([ 'mail', 'mailbox', 'list', '--format', 'json', @@ -75,16 +63,6 @@ def get_my_email(dry_run: bool = False) -> Optional[str]: return '' if not data: return None - data = unwrap_result(data) - if isinstance(data, dict) and isinstance(data.get('emailAccounts'), list): - accounts = data['emailAccounts'] - # 优先企业邮箱(type=ORG),否则取第一个 - for acc in accounts: - if isinstance(acc, dict) and acc.get('type') == 'ORG' and acc.get('email'): - return acc['email'] - if accounts and isinstance(accounts[0], dict): - return accounts[0].get('email') - return None if isinstance(data, list) and data: item = data[0] return (item.get('email') or item.get('address') @@ -121,7 +99,7 @@ def main(): '--from', from_email or '', '--to', args.to, '--subject', args.subject, - '--content', args.body, + '--body', args.body, '--format', 'json', ] if args.cc: diff --git a/skills/multi/dingtalk-mail/scripts/mail_unread_summary.py b/skills/multi/dingtalk-mail/scripts/mail_unread_summary.py index 5588db97..7005d787 100644 --- a/skills/multi/dingtalk-mail/scripts/mail_unread_summary.py +++ b/skills/multi/dingtalk-mail/scripts/mail_unread_summary.py @@ -39,18 +39,6 @@ def run_dws( return None -def unwrap_result(data: Any) -> Any: - while isinstance(data, dict): - for key in ('result', 'content', 'data'): - nested = data.get(key) - if isinstance(nested, (dict, list)): - data = nested - break - else: - return data - return data - - def get_my_email(dry_run: bool = False) -> Optional[str]: data = run_dws([ 'mail', 'mailbox', 'list', '--format', 'json', @@ -59,16 +47,6 @@ def get_my_email(dry_run: bool = False) -> Optional[str]: return '' if not data: return None - data = unwrap_result(data) - if isinstance(data, dict) and isinstance(data.get('emailAccounts'), list): - accounts = data['emailAccounts'] - # 优先企业邮箱(type=ORG),否则取第一个 - for acc in accounts: - if isinstance(acc, dict) and acc.get('type') == 'ORG' and acc.get('email'): - return acc['email'] - if accounts and isinstance(accounts[0], dict): - return accounts[0].get('email') - return None if isinstance(data, list) and data: item = data[0] return (item.get('email') or item.get('address') @@ -102,7 +80,7 @@ def main(): 'mail', 'message', 'search', '--email', email or '', '--query', kql, - '--limit', str(args.size), + '--size', str(args.size), '--format', 'json', ], dry_run=args.dry_run) @@ -112,7 +90,6 @@ def main(): print('未查到邮件') return - data = unwrap_result(data) messages = (data if isinstance(data, list) else data.get('items', data.get('messages', []))) diff --git a/skills/multi/dingtalk-minutes/SKILL.md b/skills/multi/dingtalk-minutes/SKILL.md index 31847179..282460ad 100644 --- a/skills/multi/dingtalk-minutes/SKILL.md +++ b/skills/multi/dingtalk-minutes/SKILL.md @@ -1,6 +1,6 @@ --- name: dingtalk-minutes -description: 钉钉 AI 听记。Use when 用户说 听记/会议录音/会议纪要/AI摘要/转写/关键字/听记标题/会后待办提取/分享听记。Distinct from dingtalk-calendar(日程)、dingtalk-report(日报)、dingtalk-doc(普通文档)。命令前缀:dws minutes。URL `shanji.dingtalk.com` 一定路由到此 skill。 +description: 钉钉 AI 听记查看、摘要、转写、待办提取与分享。Use when 用户说听记/会议录音/会议纪要/AI摘要/转写/关键字/听记标题/会后待办提取/分享听记。本地音视频转纪要/逐字稿也优先用本 skill,不要用 ffmpeg/whisper 本地转写。不做听记生成报告文档(走配方 minutes-report-to-doc)、听记补充到已有文档(走配方 minutes-to-doc)、日程(走 dingtalk-calendar)。命令前缀:dws minutes。 cli_version: ">=0.2.14" metadata: category: product @@ -14,14 +14,15 @@ metadata: > 🧪 **EXPERIMENTAL · 试验版 / Preview** — multi 模式当前未达 stable 标准。全部 dingtalk-* skill 已通过 dispatch verifier,但接口、命名、跨 skill 引用后续可能调整;生产 / 共享环境请优先使用 mono 模式(`dws skill setup --mode mono`)。问题请提 issue 反馈。 -> **PREREQUISITE:** Read the `dws-shared` skill first for auth, global flags, product routing, URL preflight, error codes, and safety rules. The `dws` binary must be on PATH. +## 前置条件 — 执行操作前必读 + +> **`use_skill(dws-shared)`** — 认证、全局参数(`--format json` / `--yes`)、错误码、URL 模板、跨产品消歧、安全规则与 capability 边界。**执行任何 `dws` 命令前先读;** 单产品的清晰命令可直接用本 skill。 -> ⚠️ **命令可用性以当前 dws 二进制为准**。服务发现已下线,本文档随内置 skill 发布;如果 `dws --help` 不存在,说明当前版本未暴露该命令。若命令存在但调用失败,请按错误中的 endpoint 或 tool 提示确认静态端点目录和后端工具注册。实际调用前可用 `dws --help` 或 `--dry-run` 验证。 +> 渐进式参考:[minutes-index.md](references/minutes-index.md);剧本:[07-minutes.md](references/07-minutes.md)。先按索引选择专题,不要一次性加载全部听记参考。 - -> 命令参考:[minutes.md](references/minutes.md);剧本:[07-minutes.md](references/07-minutes.md)。 +> 旧路径兼容入口:[minutes.md](references/minutes.md)。 ## 意图表 @@ -31,11 +32,45 @@ metadata: | "查某段时间/最近/本周/上月的听记" | `dws minutes list all --start "" --end "" [--query "<关键词>"]` | | "看一篇听记摘要" | `dws minutes get summary --id ` | | "看转写 / 原文" | `dws minutes get transcription --id ` | +| "下载/导出最近一周听记原文/逐字稿" | `dws minutes list all --start "" --end ""` → 逐条 `dws minutes get transcription --id ` | | "近期听记摘要合并" | `python scripts/minutes_recent_summary.py --max 5` | | "提取会议待办" | `python scripts/minutes_extract_todos.py --id ` | | "改听记标题" | `dws minutes update title --id --title "<新标题>"` | -## 评测高频硬约束 +## 标准 SOP(必遵流程) + +> 命中以下意图**必须**按对应 SOP 顺序执行;**禁止**跳步、替换命令、编造 taskUuid。每条命令必须带 `--format json`。锁定目标**必须**按 `taskUuid + title + organizationName + 时间` 精准匹配,**禁止**凭标题相似度乱选。 + +### SOP-1 查听记列表(query-minutes) + +**触发**:查听记/会议记录/某会议的纪要/按关键词或时间找听记。 + +1. **选 scope(必须·铁律)**:`mine`=我创建/发起的;`shared`=他人共享给我的;`all`=我可访问的全部(含 mine+shared)。用户说"我能访问/可见/所有/我的听记"等覆盖语义**一律 `all`**;只有明确"我创建的/我发起的/我录的"才用 `mine`。 +2. **执行(必须)**:`dws minutes list all|mine|shared --format json`;按关键词加 `--query "<关键词>"`,按时间加 `--start "" --end ""`,限制条数 `--limit `(`--max` 兼容别名),翻页用 `--cursor `(`--next-token` 兼容别名)。 +3. **解析(必须)**:从 `itemList[]` 取真实 `taskUuid` + `title` + 时间;多候选必须让用户确认,**禁止**默认取第一条。 + +**禁止**:把 `mine` 当全量、凭标题相似度锁定、跳过 `--format json`。 + +### SOP-2 取听记详情(get-minute-detail) + +**触发**:要摘要/转写原文/关键词/待办/基础信息/音频。 + +1. **前置(必须)**:先按 SOP-1 拿到目标 `taskUuid`。 +2. **执行(必须·按需选一)**:摘要 `dws minutes get summary --id `;转写原文 `dws minutes get transcription --id `(返回 `cursor`/`nextToken` 时**必须**继续 `--cursor` 翻页拉全);关键词 `get keywords`;待办 `get todos`;基础信息 `get info`;音频地址 `get audio`;批量 `get batch --ids `。全部带 `--format json`。 +3. **解析(必须)**:`--id`/`--uuid`/`--task-uuid` 三者等价,**推荐统一 `--id`**;转写/摘要按用户需要抽取,**禁止**无差别全文贴出。 + +**禁止**:编造 taskUuid、转写有分页不翻完就总结、跳过 SOP-1 直接猜 ID。 + +### SOP-3 听记转文档 / 待办回写(handoff) + +**触发**:把会议纪要转成文档/把听记待办建到待办系统。 + +1. **转文档(必须)**:取 `minutes get summary`/`transcription` 内容 → 切 `dingtalk-doc` 用 `dws doc create --content-file ` 落盘(或用 `dingtalk-products-skills/minutes-to-doc` recipe skill)。 +2. **待办回写(必须)**:取 `minutes get todos` 的待办项 → 切 `dingtalk-todo` 按 SOP-1 解析执行者 userId 后 `todo task create`。 + +**禁止**:在 minutes 内直接写文档/建待办(应切对应 skill)。 + +## 高频硬约束 - `shanji.dingtalk.com` URL 必须走 `dws minutes`,禁止用浏览器或 `read_file` 打开链接。自动提取 taskUuid 后调用 `get info/summary/transcription/todos`。 - 用户给了时间线索(今天、本周、上周、上月、最近 N 天、某日期范围)时,必须自行计算 `--start` / `--end`,格式用 ISO-8601,如 `2026-05-11T00:00:00+08:00`。不要反问用户时间范围。 @@ -43,10 +78,14 @@ metadata: - 不要全量拉取后本地过滤时间。时间范围和关键词能服务端过滤时必须放进同一条 `list all --start --end --query`。 - 列表为空时按顺序兜底:同范围 `list all` → 去掉关键词但保留时间范围 → 明确告知无数据。禁止用模板或虚构听记内容继续生成纪要/周报。 - 生成纪要、文档、待办、周报前,必须先完成 `list` → 选定真实 `taskUuid` → `get summary`;需要原文或行动项时继续 `get transcription` / `get todos`。前置数据没拿到就停止并说明卡点。 +- 用户明确说"下载/导出/文件"且对象是"听记原文/逐字稿/转写/完整记录"时,不能降级为摘要汇总:必须 `list all --start --end` 找到范围内听记;列表为空则明确无数据;列表非空则对每条听记逐一 `get transcription` 并翻页到结束,交付内容必须包含真实转写段落。可附标题、时间等元数据,但不能只给 summary,也不能要求用户再指定某一条。 - 所有 dws 命令带 `--format json`,不要用 shell 管道、重定向、`head`、`grep`、`jq`。 ## 跨产品协作 -- 提取的待办批量建任务 → 切到 `dingtalk-todo`(`scripts/todo_batch_create.py`) +- 提取的待办批量建任务 → 切到 `dingtalk-todo` 的 batch-create-todo recipe;批量脚本仅在 `dingtalk-todo` sub-skill 内可用,未切换前不要在当前 skill 运行。 - 摘要发给同事 → 切到 `dingtalk-chat` - 日程 / 会议室 → 切到 `dingtalk-calendar` +## 局部意图与 Recipe + +- [局部意图消歧](references/intent-guide.md);[Lite Recipe](references/lite-recipes.md)。 diff --git a/skills/multi/dingtalk-minutes/references/07-minutes.md b/skills/multi/dingtalk-minutes/references/07-minutes.md index 6a540e57..78bf3ab7 100644 --- a/skills/multi/dingtalk-minutes/references/07-minutes.md +++ b/skills/multi/dingtalk-minutes/references/07-minutes.md @@ -1,11 +1,78 @@ -# 听记与会后 +# 听记与会后工作流 -> lite(`list-minutes`、`get-minutes-summary`、`get-minutes-transcription`)见本 skill 的 [SKILL.md](../SKILL.md)。full recipe 见下表。 -> 日程、订会议室、`schedule-meeting` 在 `dingtalk-calendar` skill 的 03-meeting.md。产品命令见 [minutes.md](./minutes.md)。 +> 产品命令路由见 [minutes-index.md](./minutes-index.md),scope、taskUuid 与对象选择规则见 [minutes-overview.md](./minutes-overview.md)。 -| Recipe | 行动指南(固定路线) | -| ---------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| 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. 依次拉取四维信息(**逐条串行执行,禁止用 `&` 后台与 `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 ` | +## 通用前置 + +1. 用户未提供 taskUuid 或 URL 时,先用 `minutes list all` 定位。 +2. 同时核对标题、组织、时间与 taskUuid;多候选时让用户确认。 +3. 有关键词或时间条件时使用服务端 `--query`、`--start`、`--end` 过滤。 +4. 后续调用复用已确认的 taskUuid;不要因某字段为空切换到其他听记。 +5. 所有命令带 `--format json`,并从真实返回中提取下一步参数。 + +## 工作流索引 + +| 场景 | 固定路线 | +|---|---| +| 查询与浏览 | `list all` → 选择 taskUuid → 按需 `get info/summary/keywords/todos` | +| 多篇基础信息 | 分别定位 taskUuid → `get batch --ids ...` → 保留成功项并说明失败项 | +| 分享摘要 | `get summary` → 解析接收人/群 → 切到 `dingtalk-chat` 发送 | +| 会议待办 | `get todos` → 解析执行人 userId → 切到 `dingtalk-todo` 创建任务 | +| 原文导出 | `list all --start --end` → 每篇 `get transcription` 翻页拉全 → 输出真实转写 | +| 按标签浏览 | `tag list` → 匹配 tagId → `tag query --tag-id` | +| 发言人识别 | 获取转写与参会人 → 形成候选 → 用户确认 → `speaker replace` | +| 修改标题/摘要 | 先定位唯一 taskUuid → `update title/summary` → 回读验证 | + +## 查询与详情 + +```bash +dws minutes list all --query "<关键词>" --start "" --end "" --format json +dws minutes get info --id --format json +dws minutes get summary --id --format json +dws minutes get keywords --id --format json +dws minutes get todos --id --format json +``` + +只需列表时停在 `list`;用户要正文或结论时继续 `get summary`;用户要逐字稿或原话时继续 `get transcription` 并翻页至结束。 + +## 批量详情 + +1. 对每个标题或关键词执行 `list all --query`,记录唯一 taskUuid。 +2. 使用 `minutes get batch --ids --format json`。 +3. 部分失败时展示成功结果,并列出失败 ID 与原因。 +4. 批量接口整体不可用时,逐条执行 `get info`,不要漏掉后续条目。 + +## 原文导出 + +1. 计算用户时间范围,传入 `list all --start --end`。 +2. 对范围内每篇听记调用 `get transcription --id `。 +3. 返回分页 token 时继续翻页,直到 token 为空。 +4. 交付内容必须包含真实转写段落;可以附标题、时间和组织,但不能只交 summary。 +5. 范围内无听记时直接说明无数据,不生成模板内容代替。 + +## 会议待办 + +1. `minutes get todos --id ` 获取真实行动项。 +2. 对责任人使用 `aisearch person` 或 `contact user search` 获取 userId。 +3. 切到 `dingtalk-todo`,逐条或批量创建任务。 +4. 原数据未给责任人或截止时间时,先请用户确认,不自行补造。 + +## 发言人处理 + +- 发言人匹配见 [10-minutes-speaker-match.md](./10-minutes-speaker-match.md)。 +- 发言人纠偏见 [11-minutes-speaker-correct.md](./11-minutes-speaker-correct.md)。 +- 未确认身份前只展示候选和依据;确认后再调用 `speaker replace` 写回。 +- 判断某人是否参会时,结合参会人列表、通讯录和转写线索,不用文本中是否出现姓名作为唯一依据。 + +## 空数据与错误 + +- `list` 为空时,保留同一时间范围,先去掉过窄关键词再查一次;仍为空则明确说明。 +- 权限失败时说明目标听记未共享给当前用户,不换 ID 绕过。 +- 多步流程某一步失败时停止依赖该结果的后续写操作,并说明具体失败点。 +- 多数据源任务分别声明各来源的取数状态;缺失来源不得用推测补齐。 + +## 忠实性 + +- 标题、组织、时间、时长、责任人和统计数字均以工具返回为准。 +- 源数据没有行动项、责任人或数字时,不生成对应事实。 +- 汇总前确认每条结论都能追溯到已读取的 summary、transcription 或 todos。 diff --git a/skills/multi/dingtalk-minutes/references/10-minutes-speaker-match.md b/skills/multi/dingtalk-minutes/references/10-minutes-speaker-match.md new file mode 100644 index 00000000..729a46ec --- /dev/null +++ b/skills/multi/dingtalk-minutes/references/10-minutes-speaker-match.md @@ -0,0 +1,145 @@ +# 听记发言人智能匹配 + +> 从钉钉听记中定向提取并总结指定人物的发言内容。 +> 听记产品命令见 [minutes.md](./minutes-commands.md);跨产品检索/消息/文档能力按需加载 `dingtalk-aisearch`、`dingtalk-chat`、`dingtalk-doc`。 + +| Recipe | 行动指南(固定路线) | +| ------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| minutes-speaker-summarize | 1. **读取转写**:有 URL → 提取 `taskUuid` → `minutes get transcription --id `;有时间/关键词 → `minutes list all` 筛选 → 获取转写;无信息 → 必须询问 2. **声纹标注检查**:已标注真实姓名 → 直接跳 Step 6;仅匿名编号(发言人1/2/3)→ 继续 3. **转写原文推断**:利用称呼、自我介绍、上下文指代、发言顺序推断,高置信度 → 跳 Step 6;无法确定 → 继续 4. **并发身份推断**:`aisearch person --keyword "<姓名>" --dimension name` & `chat message list` 取近期 IM & `drive search --query "<姓名>"` 前 3 篇 `& wait`;两路以上一致才下结论 5. **置信度判断**:>70% → 直接输出;≤70% → 展示 TOP3 候选音频片段让用户选(最多一次,"都不是"则终止) 6. **结构化总结输出**:核心观点 + 问题/关注点 + Action Item + 立场态度;输出后追问是否替换发言人标注 | + +--- + +## 触发条件 + +**TRIGGER** when ANY of the following are true: +- 用户提供了听记来源(URL / 时间 / 关键词可定位)**且**指定了一个具体人名(花名/真名/职位称谓均可),目标是获取该人在会议中说了什么 +- 用户从 `minutes-detail` 的发言人列表中选定某位发言人,要求查看其内容总结 +- 用户贴了听记链接,发言人未完全识别(存在匿名编号),需要做批量智能匹配 + +**DO NOT TRIGGER** when: +- 用户只要整体纪要,未指定特定人物 → 用 `minutes get summary` +- 用户想纠错发言人标注 → 告知在听记详情页 GUI 操作 +- 用户想提取待办 → `meeting-followup` recipe + +--- + +## 详细步骤 + +### Step 1:读取听记转写 + +- 有 URL → 从 URL 提取 `taskUuid` → `dws minutes get transcription --id ` +- 有时间/关键词 → `dws minutes list all` 筛选 → 获取转写 +- 什么都没给 → **必须询问** + +### Step 2:声纹标注检查 + +- **已标注真实姓名** → 直接跳 Step 6 +- **仅有匿名编号**(发言人1/2/3)→ 继续 Step 3 + +### Step 3:转写原文推断(优先) + +充分利用转写文本中的称呼、自我介绍、上下文指代、发言顺序、内容特征进行推断。能从原文高置信度确定发言人 → 直接跳 Step 6。无法确定 → 继续 Step 4。 + +### Step 4:并发身份推断 + +**同时**发起多路查询,不串行等待: + +| 优先级 | 路径 | 做什么 | +| ------ | ------------------ | ---------------------------------------------------------------------------------------------------- | +| ① 最高 | 日程参与人 | 从听记 info 提取会议时间 → `dws calendar event list --start <会议开始-30min> --end <会议结束+30min>` → 匹配同时段日程 → `dws calendar participant list --event ` → 获取参与人姓名列表,与转写中匿名发言人做数量/顺序对照 | +| ② 高 | 通讯录 + 组织架构 | `dws contact user search --query <姓名> --format json` → 部门/职级/上级/汇报关系 | +| ③ 高 | 聊天记录 | `dws chat message list` 获取与目标人的近期 IM 消息 → 工作内容/语言风格/职责线索 | +| ④ 兜底 | 本人文档 | `dws drive search --query <姓名>` 取前 3 篇标题+摘要 → 角色精确信号 | + +**日程参与人匹配规则**(路径 ① 专用): +- 日程参与人数 = 转写发言人数 → 高置信度,按发言顺序/内容特征一一对应 +- 日程参与人数 > 转写发言人数 → 有人未发言,需结合角色推断缩小范围 +- 日程参与人数 < 转写发言人数 → 有人未在日程中,需补充其他路径 +- **未找到同时段日程** → 主动引导用户:「未找到与该听记时间匹配的日程,是否可以提供会议对应的日程链接或参会人员名单?这将帮助更准确地识别发言人」 + +**判定规则**: +- 日程参与人 + 通讯录两路信号一致 → 确认身份,置信度最高 +- 通讯录 + 聊天两路信号一致 → 确认角色,置信度高,通常无需再查文档 +- 仅一路可用或信号矛盾 → 补查文档兜底,两路以上一致才下结论 + +> 部门名 ≠ 角色,不能用「产品设计部」直接推断是产品设计师;聊天记录往往最直接反映日常工作内容。 + +**文档类型与角色对照**(仅在文档兜底时参考): +- PRD/需求文档/商业化方案 → 产品经理 +- 视觉规范/原型说明/交互评审 → 产品设计师 +- 技术方案/架构设计 → 研发工程师 +- 分析报告/底表说明 → 数据分析师 + +### Step 5:置信度分支 + +基于 Step 4 角色,在转写中匹配发言模式(产品经理提需求/定优先级,研发讲技术约束,管理者高占比做决策,设计师讲交互细节)。 + +#### 分支 A:置信度 > 70% → 直接输出总结 + +无需任何确认,直接进入 Step 6。 + +#### 分支 B:置信度 ≤ 70% → TOP3 候选音频(仅一次) + +列出置信度最高的 TOP3 候选发言人(必须来自不同发言人,不重复),每人附一段代表性转写片段: + +> 以下是本次会议中可能是 [人名] 的候选,请确认: +> +> **候选 1**(置信度最高) +> 对应文字:"[转写文本片段]" +> +> **候选 2** +> 对应文字:"[转写文本片段]" +> +> **候选 3** +> 对应文字:"[转写文本片段]" +> +> 请问 [人名] 是哪一位?(1 / 2 / 3 / 都不是) + +- 用户选 1/2/3 → 进入 Step 6 +- 用户说"都不是" → 告知无法确认,建议在听记详情页手动标注,**不再重复** + +### Step 6:总结输出 + +提取该发言人的全部发言,结合会议上下文综合总结: + +``` +[人名] 在本次会议中的发言总结 + +**核心观点** +- 观点1 +- 观点2 + +**提出的问题 / 关注点**(如有) +- ... + +**Action Item / 承诺事项**(如有) +- ... + +**立场 / 态度**(如有明确倾向) +- ... +``` + +输出完成后追问: + +> 要在本篇听记里把「发言人 X」替换成「[人名]」吗?替换后系统会记住声纹特征,下次会议自动识别。[替换] [暂不] + +--- + +## 核心约束 + +- **禁止暴露 Speaker_X 编号**:追问中用「发言人 X」等用户可感知的描述,不暴露内部格式 +- **禁止碰运气播音频**:必须先做身份预推断,再定向选片段 +- **声纹库命中时直接输出**:不做任何额外确认 +- **置信度 > 70% 直接输出**:不需要确认 +- **音频确认最多一次**:用户说"都不是"则终止,不重复 +- **先完成任务,后引导替换**:总结输出完成后再追问替换,不打断主流程 + +--- + +## 常见陷阱 + +- 部门名 ≠ 角色:不能用部门名称代替角色判断 +- 聊天记录是快速判断工作内容的捷径,优先于文档 +- 单篇文档不够:至少看 3 篇,单篇可能是跨角色协作产出 +- 访谈记录 PM 和 UXR 都会写,不能单独判断角色 +- TOP3 候选必须来自不同发言人,展示前做去重检查 diff --git a/skills/multi/dingtalk-minutes/references/11-minutes-speaker-correct.md b/skills/multi/dingtalk-minutes/references/11-minutes-speaker-correct.md new file mode 100644 index 00000000..3843a954 --- /dev/null +++ b/skills/multi/dingtalk-minutes/references/11-minutes-speaker-correct.md @@ -0,0 +1,152 @@ +# 听记发言人识别与标注 (speaker-correct) + +> 触发条件:用户提供了一篇钉钉听记(URL 或 ID),表达了识别/标注/替换发言人的意图。即使用户只说"帮我标注发言人"也应触发本技能。 + +## 执行流程 + +### Step 1:查同时段日程,获取参会人名单 + +1. 从听记元数据获取录音**开始时间**(精确到分钟)。 +2. 调用日历接口查询该时间点前后 30 分钟内的日程:`dws calendar event list --start --end --format json` +3. 若匹配到日程,提取参会人名单(姓名/花名),输出: + > 根据录音时间,匹配到日程「{日程名称}」,参会人为:{姓名1}、{姓名2}、{姓名3}... + > 未识别的发言人大概率来自以上名单。 +4. 未匹配到日程 → 跳过,直接进入 Step 2。 + +### Step 2:展示发言人状态表格 + +列出所有发言人及其当前标注状态: + +| # | 当前标注 | 状态 | +|---|---------|------| +| 1 | 张三 | 已识别 | +| 2 | 发言人 2 | 未标注 | +| 3 | 发言人 3 | 未标注 | +| 4 | 发言人 4 | 未标注,发言较少 | + +### Step 3:让用户选择识别方式 + +``` +你希望如何识别未标注的发言人? + +A. 听音识人 — 为每位未识别发言人裁剪一段音频片段,你听后辨认是谁 +B. 手动设置 — 直接告诉我对应关系,如「发言人2:王五, 发言人3:赵六」 +C. 智能匹配 — 提供参会人名单或其他信息,我来尝试推断 +``` + +**等待用户选择后再执行后续操作,不提前切片。** + +--- + +#### 选项 A:听音识人(用户选择后执行) + +为每位未标注发言人裁剪一段代表性音频片段。 + +**片段选择策略:** + +1. **语义质量过滤**(先排除低质量段落): + - 排除含明显口吃/重复词的段落(同一字/词连续出现 2 次及以上,如"你你你"、"跨跨跨") + - 排除文本字数 < 8 字的段落 + - 排除纯单字/双字回应("嗯"、"对"、"是"等) + - 排除句意明显不完整的段落(以"就是"、"然后"、"比如说"等连接词结尾) + +2. **综合评分排序**,取得分最高的段落: + - **评分公式**:`score = 时长(秒) x 语义完整度系数` + - 语义完整度系数:`1.0`(句意完整)/ `0.8`(略显口语化)/ `0.5`(不完整,一般已被过滤) + - 两段得分相差 <= 20% 时,优先选语义系数更高的 + - 截取时长 = `min(该段时长, 10秒)`,**不足 5 秒的片段跳过** + +3. **兜底逻辑(发言较少时)**:若过滤后无满足条件的单段,执行**片段合并**: + - 收集该发言人所有时长 >= 1 秒的片段(按时间顺序),用 ffmpeg `-filter_complex` 合并为一个文件 + - 告知用户:「发言人 X 发言较少,已将所有片段拼接在一起,方便你听取辨认。」 + - 若合并后总时长仍不足 3 秒,放弃,告知用户:「发言人 X 发言太少,建议结合参会人名单自行标注。」 + +**执行命令示例:** +```bash +ffmpeg -y -ss 202 -i audio.mp3 -t 8 -c copy tmp/发言人2_片段.mp3 -loglevel error +ffmpeg -y -ss 465 -i audio.mp3 -t 10 -c copy tmp/发言人3_片段.mp3 -loglevel error +``` + +**输出文件命名:** `tmp/发言人{编号}_片段.mp3` + +切片完成后,通过钉钉单聊**逐个发送**音频文件给用户(使用 `dws` 技能发送文件消息),不输出表格、不输出下载链接。全部发送完毕后,仅用一句话提示用户听完后回复对应关系,例如:「以上是各发言人的音频片段,听完后告诉我对应关系,如「发言人2:王五, 发言人3:赵六」。」 + +#### 选项 B:手动设置(用户选择后执行) + +直接等待用户输入对应关系(如「发言人2:王五, 发言人3:赵六」),收到后进入 Step 4 批量替换。 + +#### 选项 C:智能匹配(用户选择后执行) + +若 Step 1 已获取参会人名单,结合发言内容特征尝试推断;否则请用户补充名单后推断。 + +**查询参会人名单信息——同时发起三路查询,不串行等待:** + +| 优先级 | 路径 | 做什么 | +|--------|------|--------| +| ① 最高 | 通讯录 + 组织架构 | `dws aisearch person --keyword <姓名> --dimension name`(或 `dws contact user search --query <姓名> --format json`)→ 部门/职级/上级/汇报关系 | +| ② 高 | 聊天记录 | `dws chat message list` 获取与目标人的近期 IM 消息 → 工作内容/语言风格/职责线索 | +| ③ 兜底 | 本人文档 | `dws drive search --query <姓名>` 取前 3 篇标题+摘要 → 角色精确信号 | + +**判定规则:** +- 通讯录 + 聊天两路信号一致 → 确认角色,置信度高,通常无需再查文档 +- 仅一路可用或信号矛盾 → 补查文档兜底,两路以上一致才下结论 + +> 部门名 ≠ 角色,不能用「产品设计部」直接推断是产品设计师;聊天记录往往最直接反映日常工作内容。 + +**文档类型与角色对照**(仅在文档兜底时参考): +- PRD / 需求文档 / 商业化方案 → 产品经理 +- 视觉规范 / 原型说明 / 交互评审 → 产品设计师 +- 技术方案 / 架构设计 → 研发工程师 +- 分析报告 / 底表说明 → 数据分析师 + +**置信度分析与输出:** + +基于上述信号,在转写中匹配发言模式(产品经理提需求/定优先级,研发讲技术约束,管理者高占比做决策,设计师讲交互细节): + +- **分支 A:置信度 > 70%** → 输出匹配结果,进入 Step 4 执行替换 +- **分支 B:置信度 < 70%** → 不输出结果,请用户补充信息或改用「听音识人 / 手动设置」 + +--- + +### Step 4:通讯录查询 + 执行替换 + +确认对应关系后,**必须先查询通讯录获取 dingUid**: + +1. 调用 `dws contact user search --query "<姓名>" --format json` +2. 从返回结果提取 `userId`(长整型数字,即 dingUid) +3. 根据匹配结果执行替换: + +| 情况 | 处理 | +|------|------| +| 唯一匹配(如 userId=123456789) | `dws minutes speaker replace --id --from "发言人X" --to "<姓名>" --target-uid 123456789 --format json` | +| 多个匹配 | 列出候选(姓名 + 部门 + userId)让用户选择后执行 | +| 无匹配 | 提示通讯录未找到,执行不带 `--target-uid` 的替换:`dws minutes speaker replace --id --from "发言人X" --to "<姓名>" --format json` | + +### Step 5:追问 + +替换成功后: +> 还有其他发言人需要我帮你识别和替换吗? + +--- + +## 注意事项 + +- **保护内部处理细节**:不要向用户暴露本地文件路径、ffmpeg 命令、内部评分或时间戳选择逻辑。代表性音频发送完成后,请用户回复发言人与姓名的对应关系。 +- **不输出表格和链接**:不向用户展示音频文件列表表格、不输出任何下载链接,音频一律通过钉钉单聊文件消息直接发送 +- **不做身份画像分析**:不提供语言风格、决策权等文字画像 +- **串行处理**:逐条执行裁剪命令,不使用 shell `&` 并行 +- **全已识别情况**:若所有发言人均已识别,直接告知「所有发言人已识别,无需修改」 +- **通讯录查询必跑**:替换前必须调用 `dws contact user search` 获取 userId,严禁跳过 +- **依赖工具**:需要 `ffmpeg` 可用,以及能够访问听记原始音频的权限(通过 `dws` 技能获取) + +## 异常处理 + +| 情况 | 处理方式 | +|------|----------| +| 无法获取原始音频 | 告知权限不足或链接失效,建议用户本地提供音频文件或选择手动设置 | +| 未匹配到日程 | 跳过参会人提示,直接展示发言人表格 | +| 通讯录查不到目标人 | 执行不带 `--target-uid` 的替换,告知用户仅修改显示名 | +| 未找到未识别发言人 | 告知用户所有发言人均已识别 | +| speaker replace 报权限错误 | 告知用户需听记创建者/协作者权限,引导联系原作者 | +| ffmpeg 不可用 | 告知用户环境缺少 ffmpeg,无法执行切片,建议选择手动设置或智能匹配 | +| 切片失败 | 告知具体哪位发言人切片失败,重试或跳过该发言人 | diff --git a/skills/multi/dingtalk-minutes/references/intent-guide.md b/skills/multi/dingtalk-minutes/references/intent-guide.md new file mode 100644 index 00000000..47c7b2a1 --- /dev/null +++ b/skills/multi/dingtalk-minutes/references/intent-guide.md @@ -0,0 +1,7 @@ +# minutes 局部意图消歧 + +本文件从单 Skill `intent-guide.md` 拆分而来,仅保留与本产品相关的跨产品消歧规则。 + +| 用户说... | 真实意图 | 应该用 | 不要用 | 理由 | +|---|---|---|---|---| +| "把最近几次关于XX的会议汇总成报告" | 按主题汇总多次听记 | #5 generate-topic-report | #7 meeting-followup | #7 是单次会议听记跟进;多次会议按主题汇总属于工作汇报 | diff --git a/skills/multi/dingtalk-minutes/references/lite-recipes.md b/skills/multi/dingtalk-minutes/references/lite-recipes.md new file mode 100644 index 00000000..359ff5e8 --- /dev/null +++ b/skills/multi/dingtalk-minutes/references/lite-recipes.md @@ -0,0 +1,56 @@ +# minutes Lite Recipe + +> 完整命令路由见 [minutes-index.md](./minutes-index.md),高级会后流程见 [07-minutes.md](./07-minutes.md)。 + +## minutes-query + +- 默认范围:`minutes list all --format json` +- 仅自己创建:`minutes list mine --format json` +- 仅他人共享:`minutes list shared --format json` +- 关键词与时间:在同一条 `list` 命令中使用 `--query`、`--start`、`--end` +- 单篇详情:`get info/summary/keywords/todos --id ` +- 多篇基础信息:`get batch --ids ` +- 原文:`get transcription --id `,分页 token 非空时继续 + +对象选择必须使用本次 `list` 返回的 taskUuid,并同时核对标题、组织和时间。多候选时让用户确认。 + +## minutes-edit + +- 修改标题:`minutes update title --id --title "<新标题>"` +- 修改摘要:`minutes update summary --id --content "<内容>"` +- 替换文字:`minutes replace-text --id --search "<旧文字>" --replace "<新文字>"` +- 添加热词:`minutes hot-word add --words "词1,词2"` +- 替换发言人:确认对应关系后执行 `minutes speaker replace` + +所有写操作先定位唯一 taskUuid,完成后回读验证。 + +## minutes-tag + +1. `minutes tag list --format json` 获取真实 tagId。 +2. 按名称匹配标签。 +3. `minutes tag query --tag-id --format json`;有分页时继续传 `--cursor`。 + +## minutes-permission + +- 添加成员:`minutes permission add --ids --member-uids --policy <0-4>` +- 移除成员:`minutes permission remove --ids --member-uids ` + +成员 ID 先通过通讯录查询,禁止编造。 + +## minutes-upload + +```bash +dws minutes upload create --file-name "meeting.mp3" --file-size --format json +dws minutes upload complete --session-id --format json +dws minutes upload cancel --session-id --format json +``` + +使用 `create` 返回的 sessionId 和上传地址;文件大小单位是字节。 + +## 数据深度 + +- 只要列表时不要额外拉取转写。 +- 要摘要时读取 `get summary`。 +- 要原话、逐字稿或沟通细节时读取 `get transcription` 并翻页至结束。 +- 要待办时读取 `get todos`;缺少责任人或期限时先确认。 +- 多数据源任务分别读取各来源并如实标注缺失项。 diff --git a/skills/multi/dingtalk-minutes/references/minutes-commands.md b/skills/multi/dingtalk-minutes/references/minutes-commands.md new file mode 100644 index 00000000..027448be --- /dev/null +++ b/skills/multi/dingtalk-minutes/references/minutes-commands.md @@ -0,0 +1,889 @@ +# 听记命令参考 + +## 命令总览 + +### 查询我创建的听记列表 +``` +Usage: + dws minutes list mine [flags] +Example: + dws minutes list mine + dws minutes list mine --max 10 + dws minutes list mine --max 10 --next-token + dws minutes list mine --query "周会" +Flags: + --max float 查询的听记篇数 (默认 10) + --next-token string 分页 token (首页留空,后续填写前次返回的 nextToken) + --query string 关键字筛选 (可选) + --start string 开始时间 ISO-8601 (可选) + --end string 结束时间 ISO-8601 (可选) +``` + +查询我创建的听记列表,支持 `--max` 和 `--next-token` 分页,支持按关键字和时间范围筛选。 + +### 查询他人共享给我的听记列表 +``` +Usage: + dws minutes list shared [flags] +Example: + dws minutes list shared + dws minutes list shared --max 20 + dws minutes list shared --max 5 --next-token +Flags: + --max float 查询的听记篇数 (默认 10) + --next-token string 分页 token (首页留空,后续填写前次返回的 nextToken) + --query string 关键字筛选 (可选) + --start string 开始时间 ISO-8601 (可选) + --end string 结束时间 ISO-8601 (可选) +``` + +查询他人共享给我的听记列表,支持 `--max` 和 `--next-token` 分页,支持按关键字和时间范围筛选。 + +### 查询我有权限访问的所有听记列表 +``` +Usage: + dws minutes list all [flags] +Example: + dws minutes list all + dws minutes list all --max 20 + dws minutes list all --query "周会" --max 20 + dws minutes list all --start "2026-03-01T00:00:00+08:00" --end "2026-03-20T23:59:59+08:00" + dws minutes list all --max 10 --next-token +Flags: + --end string 结束时间 ISO-8601 (可选) + --query string 关键字筛选 (可选) + --max float 查询的听记篇数 (默认 10) + --next-token string 分页 token (首页留空,后续填写前次返回的 nextToken) + --start string 开始时间 ISO-8601 (可选) +``` + +查询我有权限访问的所有听记列表(包括我创建的、他人共享给我的等所有有权限的听记)。支持按关键字和时间范围筛选。时间范围和关键字为可选参数,不传则返回所有有权限的听记。支持使用 `--max` 和 `--next-token` 进行分页查询。 + +### 获取听记基础信息 +``` +Usage: + dws minutes get info [flags] +Example: + dws minutes get info --id +Flags: + --id string 听记 taskUuid (必填),取值逻辑参考 ## 注意事项 +``` + +返回字段: 创建人、开始时间、截止时间、听记标题、听记访问链接URL + +**发言人信息(按需读取)**: + +`get info` 只返回听记元信息,不要求额外读取转写。仅当用户询问发言人列表、某人的发言、身份识别或姓名纠偏时,再读取转写或调用 speaker summary: + +1. 从 `speakerName` / `speakerId` 收集请求范围内的不同发言人。 +2. 已有真实姓名时直接展示;匿名标识统一展示为「发言人 1」「发言人 2」,不要暴露 `Speaker_X` 等内部格式。 +3. 需要识别匿名发言人时,可按用户提供的映射、日程参会人或 [智能匹配流程](10-minutes-speaker-match.md) 生成候选;任何 `speaker replace` 写回都必须先展示映射并取得用户确认。 +4. 普通详情、摘要、关键词或待办请求不触发发言人识别流程。 + +### 获取听记 AI 摘要 +``` +Usage: + dws minutes get summary [flags] +Example: + dws minutes get summary --id +Flags: + --id string 听记 taskUuid (必填),取值逻辑参考 ## 注意事项 +``` + +返回 Markdown 格式摘要,涵盖会议主题、核心结论、关键讨论点等 + +### 获取听记关键字列表 +``` +Usage: + dws minutes get keywords [flags] +Example: + dws minutes get keywords --id +Flags: + --id string 听记 taskUuid (必填),取值逻辑参考 ## 注意事项 +``` + +### 获取听记语音转写原文 +``` +Usage: + dws minutes get transcription [flags] +Example: + dws minutes get transcription --id + dws minutes get transcription --id --direction 1 + dws minutes get transcription --id --next-token --format json +Flags: + --direction string 排序方向: 0=正序, 1=倒序 (默认 0) + --id string 听记 taskUuid (必填),取值逻辑参考 ## 注意事项 + --next-token string 下一页的token 首次查询可空 后续查询需填写前次请求返回的nextToken +``` + +每条记录包含: 发言人信息、转写文本、对应时间戳 + +**关键:转写接口每次最多返回 50 段,必须自动翻页才能拿到完整原文!** + +> **这是最高频的 silent failure:** `get transcription` 单次调用**最多只返回 50 段**。一篇 30 分钟会议的转写通常有 150-400 段,如果不翻页就只能看到前 1/3 甚至 1/8 的内容。 +> +> **分页机制(必须掌握):** +> - 首次调用:`dws minutes get transcription --id --format json`(不传 `--next-token`) +> - 检查返回 JSON 中是否包含 `nextToken` 字段(非空字符串) +> - 如果有 `nextToken`:继续调用并传入 `--next-token <上次返回的nextToken值>` +> - 循环直到返回中**不再包含 `nextToken`**(或 `nextToken` 为空),表示所有段落已拉取完毕 +> - **拼合所有页的段落**后,才算拿到了完整转写原文 +> +> **自动翻页伪代码(AI 必须内化此逻辑):** +> ``` +> all_paragraphs = [] +> next_token = "" # 首次为空 +> loop: +> if next_token == "": +> result = dws minutes get transcription --id --format json +> else: +> result = dws minutes get transcription --id --next-token --format json +> all_paragraphs.append(result.paragraphs) +> next_token = result.nextToken # 可能为空或不存在 +> if next_token 为空或不存在: +> break # 全部拉取完毕 +> # 此时 all_paragraphs 才是完整转写原文 +> ``` +> +> **典型错误(导致“总结整篇听记”只覆盖前 50 段):** +> - [错误] 只调用一次 `get transcription`,看到返回了内容就以为拿全了 → 实际只有前 50 段 +> - [错误] 看到返回 JSON 里有 `nextToken` 字段但不知道这是什么、不知道要传 `--next-token` → 默默丢弃了后续页 +> - [错误] 用 `--help` 查参数但在 Windows 下输出被截断,没看到 `--next-token` 参数 → 以为不支持分页 +> - [正确] **正确做法:无条件按上述循环逻辑自动翻页,直到 nextToken 为空** + +**转写原文拉取策略:** + +- 用户要求完整原文、完整逐字稿或基于全文分析时,按 `nextToken` 连续翻页直到 token 为空,再合并所有段落。 +- 用户只要求摘要、关键词、待办或基础信息时,不读取转写。 +- 用户只要求特定发言人或特定时间段时,优先使用 speaker summary 或只拉取覆盖目标范围所需的数据。 +- 不能把第一页当成全文,也不能用未覆盖目标范围的部分转写下完整结论;结果输出按用户目标摘要或提取,避免无差别粘贴全文。 +> +> **用户意图为"按发言人分析"时的优化路径:** +> - 如果用户的最终目的是"区分发言人"/"看某人讲了什么",**不一定需要拉取全部转写原文** +> - 可优先使用 `dws minutes get summary --id ` 获取结构化摘要(含发言人信息) +> - 或使用 `dws minutes speaker summary get --ids ` 直接获取按发言人维度的摘要 +> - 仅当用户明确要求"逐字稿"/"完整原文"/"每句话"时,才需完整翻页拉取 + +2. **用户未明确要求查看原文时 → 不要主动拉取转写原文** + - 示例:"查一下和悟空相关的听记"、"帮我看看这个听记的摘要"、"这个会议讲了什么" + - 这些场景下用户的意图是查列表、看摘要等,**不需要也不应该**把大量转写原文全部拉出来,否则会造成信息过载和不必要的性能开销 + - 如果用户问"这个会议讲了什么",应优先使用 `get summary` 返回摘要,而非 `get transcription` + +**判断原则:只有用户的意图明确指向"原文/转写/逐字稿/录音文字"时,才调用 `get transcription`;其他场景(查列表、看摘要、看待办等)严禁自动附带拉取转写原文。** + +**重要 — 转写原文返回后默认按"时间线"组织各段落,AI 必须主动引导发言人聚类与关联(必须严格遵守):** + +`get transcription` 默认返回的段落是按"时间戳正序/倒序"穿插展示的(每条记录包含 `speakerNick + 时间戳 + 文本`),**对人不友好**——同一发言人的内容散落在多个时间点,难以快速看清"某个人主要讲了什么"。因此,AI **在拉取完成(含分页全部完成)后**必须主动启动以下"发言人聚类 → 关键词模糊匹配 → 引导确认 → 调用 `speaker replace`"四阶段工作流: + +#### 阶段 1:拉取完成后主动询问"是否按发言人聚类" +拉取(含自动翻页)结束后,AI 必须**主动**追问用户一次(不要默认强制聚类,避免用户只想看时间线原文时被打扰): + +> "已拉取完整转写原文(共 N 段,X 个发言人)。当前默认按时间线返回。是否需要我帮你**按发言人分组聚类**,并提取每位发言人的**核心发言要点**?" + +- 用户确认("好/可以/需要/聚类一下/按发言人分一下"等)→ 进入阶段 2 +- 用户拒绝 → 直接展示时间线原文,结束流程,**不再追问** + +#### 阶段 2:按发言人聚类 + 提取核心内容 +AI 基于已拉取的转写数据,**本地完成**聚类与摘要(无需新调用 dws 命令),输出结构如下: + +``` +[发言人] 发言人1(共 12 段,约 1820 字) + 核心要点: + - 介绍 Q3 战略规划与组织调整方向 + - 强调 AI 化转型的三个关键里程碑 + - 提出对供应链效率的具体改进目标 + +[发言人] 发言人2(共 8 段,约 960 字) + 核心要点: + - 汇报当前业务的财务数据与利润率 + - 分析竞品在华东市场的最新动作 + +[发言人] 张三(共 5 段,约 410 字) + 核心要点: + - 同步研发团队的招聘进度 + - 提出对测试资源的支持诉求 +``` + +**约束:** +- 聚类必须包含**全部发言人**(包括"发言人1/发言人2"占位符与已关联真实姓名的发言人,便于后续替换) +- 每位发言人**最多列出 3-5 条**核心要点,避免冗长 +- 输出后必须**主动追问**用户: + +> "如果你能告诉我『某某人主要讲了什么』(例如『李总主要讲了战略规划』『王经理主要负责供应链』),我可以根据关键词帮你**自动匹配**对应的发言人,并把『发言人1/发言人2』替换成真实姓名。" + +#### 阶段 3:基于用户提供的关键词做模糊匹配 +当用户提供形如『某某人主要讲了 XX』的输入时(例如『李总主要讲了战略规划』『拾光负责供应链效率改进』),AI 必须: + +1. **提取关键词**:从用户输入中抽取核心实体/主题词(如『战略规划』『供应链效率』『AI 化转型』),可允许多关键词 +2. **在阶段 2 已聚类的发言人核心要点中做模糊匹配**: + - 优先匹配「核心要点」中包含或语义相近的发言人 + - 必要时回看该发言人的原始转写文本进行二次确认 + - 支持**同义词/近义词**容忍(如『战略』~『规划』、『供应链』~『物流』) +3. **匹配结果分级处理:** + - **唯一高置信匹配**(一个发言人显著命中)→ 进入阶段 4 引导确认 + - **多个候选**(2 个及以上发言人都有部分命中)→ 列出全部候选,让用户选择,例如: + > "根据关键词『战略规划』,我匹配到 2 个可能的候选:① 发言人1(命中『战略规划/AI 化转型』)② 发言人3(命中『战略方向』)。你说的『李总』更可能是哪一位?" + - **无匹配** → 如实告知用户,并请其补充更具体的关键词或直接给出"发言人编号 → 真实姓名"的映射,例如: + > "暂未在转写中匹配到与『战略规划』强相关的发言人。可以再描述得更具体一些,或者直接告诉我『发言人 X 就是李总』,我来帮你替换。" + +#### 阶段 4:引导用户确认关联,并调用 `speaker replace` 完成替换 +匹配到唯一候选后,AI **不要直接替换**,必须先**显式追问用户确认**: + +> "我找到『发言人1』很可能就是你说的『李总』(命中关键词:战略规划、AI 化转型)。是否需要我把这篇听记里的『发言人1』全部替换为『李总』? +> 确认后我会执行:`dws minutes speaker replace --id --from "发言人1" --to "李总"`" + +- 用户确认 → 立即调用 `dws minutes speaker replace --id --from "发言人1" --to "李总" --format json`,并在执行成功后告知用户:"已将本篇听记的『发言人1』全部替换为『李总』,纪要与待办中的发言人也已同步更新。" +- 用户希望同时关联通讯录 → 引导用户提供钉钉 UID,调用时附加 `--target-uid ` +- 用户拒绝 → 不替换,可询问是否还有其他发言人需要关联,否则结束流程 + +**严禁的行为:** +- [禁止] 拉取完转写后直接输出大段时间线原文就结束,不主动引导聚类 +- [禁止] 用户提供"某某人讲了 XX"后,AI 自己默认替换而不向用户二次确认 +- [禁止] 未执行 `speaker replace` 就把映射视为已写回听记 +- [禁止] 模糊匹配置信度不足时仍然给出唯一答案,不让用户参与挑选候选 + +#### 发言人识别与总结执行链路(用户指定人名查发言时必须遵循) + +**触发条件**:用户同时提供了听记来源(URL/时间/关键词)和一个具体人名,目标是获取该人在会议中说了什么。例如"帮我看看这个听记里张三说了什么""李总在今天的会上提了哪些观点"。 + +> **【重要】** **核心铁律(必须刻在脑子里,违反任何一条都视为严重错误):** +> +> **铁律 1:严禁在转写文本里 grep "目标人名" 字符串作为存在性判断依据** +> - 花名/真名通常**不会**出现在 TA 自己的发言里,发言里出现的"X"只意味着"`说话人 ≠ X`"或"说话人在叫 X" +> - 在转写中搜不到"灵麦"**完全不能得出"灵麦没参会"的结论**——99% 的发言人都显示为匿名编号"发言人1/2/3" +> - 正确做法是把"目标人名"作为**身份信息**去通讯录查询(Step 4 ①),而不是当作发言内容字符串去 grep +> +> **铁律 2:严禁用 AI 摘要里的"参与人/参会人"字段判断某人是否参会** +> - AI 摘要中的"参与人"字段往往**只截取最显著的 1-2 个名字**(通常是创建人或发言最多的人),**不是完整参会人列表** +> - "AI 摘要参与人只有故愚 → 灵麦没参会" 是典型的错误推理链 +> - 如果一定要列出参会人,应使用 `dws minutes get info` / `get batch` 返回的 `participants` 字段,而不是摘要文本里的描述 +> +> **铁律 3:通讯录查询是 Step 4 的强制起点,不依赖 Step 3 是否成功** +> - Step 2 一旦返回"未命中真名标注",**Step 3 与 Step 4 ① 必须并发触发**,不要等 Step 3 失败再补查 +> - 通讯录查询 1 个调用就能锁定"目标人物的部门 + 职级 + 上级"——这是身份推断**最低成本、最高收益**的信号源 +> - 跳过通讯录直接得出"找不到 X"的结论 = 100% 失败链路 +> +> **铁律 4:找不到字面匹配 ≠ 不存在** +> - "发言人识别"功能存在的本质就是**根据角色特征把匿名编号映射到真实人**——"找不到字面匹配"恰恰是这个功能要解决的问题,而不是退出的理由 +> - 一旦想说"找不到 X"前,先自问:通讯录查了吗?文档查了吗?聊天记录查了吗?基于角色在转写里做模式匹配了吗?四个全是"没"则禁止给"找不到"的结论 + +**完整执行链路:** + +``` +Step 1: 定位听记并读取转写原文 + ↓ +Step 2: 声纹标注检查 + ├─ [目标人名已被系统标注] → 直接跳 Step 6 + └─ [仅有匿名编号"发言人1/2/3"] ↓ +Step 3: 转写原文内推断(优先,能判断就不走外部查询) + ├─ [高置信命中] → 直接跳 Step 6 + └─ [无法确定] ↓ +Step 4: 多路并发身份推断 + ↓ +Step 5: 定向匹配 + 置信度分支 + ├─ 置信度 ≥ 50% → 展示文本片段请用户确认 + └─ 置信度 < 50% → 提供候选片段请用户辨认 + ↓ +Step 6: 结构化总结输出 + ↓ +Step 7: 引导用户替换发言人(调用 speaker replace 写回听记) +``` + +##### Step 1: 定位听记并读取转写原文 + +- **有 URL** → 从 URL 提取 taskUuid → `dws minutes get transcription --id --format json`(自动翻页拉取全部) +- **有时间/关键词** → `dws minutes list all --query "关键词" --start "..." --end "..." --format json` 筛选 → 获取 taskUuid → 拉取转写 +- **什么都没给** → **必须询问用户**提供听记链接、时间或关键词 + +##### Step 2: 声纹标注检查 + +检查转写原文中目标人物是否已被系统识别并标注了真实姓名(即 `speakerNick` 直接就是用户提到的人名): +- **已标注**(某条 `speakerNick` 字面就是"木兰")→ 直接跳到 Step 6,零确认步骤 +- **仅有匿名编号**(发言人1/发言人2/发言人3)→ **必须**继续 Step 3 与 Step 4,**严禁在此处退出** + +> **【重要】** **关键认知(违反则案例 7 重现):** +> - **未命中真名标注 ≠ 目标人物没参会**——绝大多数听记的发言人都是匿名编号,"未命中"是**默认场景**,恰恰是发言人识别功能要解决的问题 +> - **不要在转写文本里 grep 目标人名**作为存在性判断——花名/真名通常不会出现在 TA 自己的发言里(参见铁律 1) +> - **不要把 `dws minutes get summary` 摘要文本里写到的"参与人"当作完整参会列表**——AI 摘要里的"参与人"字段只截取最显著的 1-2 个名字,不是完整名册(参见铁律 2) +> - 唯一能下"目标人物没参会"结论的场景是:`get info` / `get batch` 返回的结构化 `participants` 字段里**完整列出参会人且不含目标**,且 `dws contact user search` 也搜不到该人 → 才允许告知用户"该人不在参会列表" + +##### Step 3: 转写原文内推断(优先) + +**核心原则:先基于转写原文做逻辑推断,能直接判断就不调外部接口。** + +充分利用转写文本中的所有可用信息进行综合推断: +- **称呼线索**:其他人称呼"张总/李工/王老师"等 +- **自我介绍**:"我是 XX 部门的""我负责 XX" +- **上下文指代**:前文提到"张三你来说一下",紧接着的发言人大概率是张三 +- **发言内容特征**:用户说"李总负责战略",而某发言人大量讨论战略方向 +- **发言顺序**:主持人/领导通常先发言或总结性发言 + +只要能从原文**高置信度**地确定发言人,直接跳 Step 6 输出总结。如果仅凭原文无法确定,继续 Step 4。 + +##### Step 4: 多路并发身份推断 + +> **【重要】** **强制规则(违反则案例 7 重现):** +> - **路径 ① 通讯录查询是必跑项,不依赖 Step 3 是否成功**——Step 2 一旦判定"未命中真名标注",**Step 3 与 Step 4 ① 必须并发触发**,**严禁等 Step 3 失败再补查** +> - 通讯录查询单次调用即可拿到"部门 + 职级 + 上级 + 真名"——这是身份推断**最低成本、最高收益**的信号源 +> - 路径 ②③④ 是**增量信号**,按需触发(如通讯录返回的部门是"产品设计部"这类多角色部门时,再补查 ② 文档) + +**触发顺序:** + +| 阶段 | 必跑 / 可选 | 触发时机 | +|------|-------------|----------| +| 路径 ① 通讯录组织架构 | **必跑** | Step 2 判定"未命中真名"后立即并发触发,与 Step 3 同时进行 | +| 路径 ② 本人创建的文档 | 可选 | ① 返回的部门是"产品设计部"这类多角色部门,需要更精确的角色信号时 | +| 路径 ③ 近期日程类型 | 可选 | ① ② 都不充分,需要补充职能边界判断时 | +| 路径 ④ 聊天记录 | 可选 | ①②③ 都不充分,需要语言风格/工作内容线索作为最后一道印证时 | + +| 路径 | 命令 | 得到什么 | +|------|------|----------| +| ① 通讯录组织架构 | `dws contact user search --keyword "目标人名"` → 部门/职级/上级/真名 | 职能大类(技术/产品/设计/管理)+ 是否存在该人 | +| ② 本人创建的文档 | `dws doc search --keyword "目标人名/真名"` 至少获取 3 篇标题 | 角色精确信号(PM写PRD、研发写技术方案、设计师写视觉规范)| +| ③ 近期日程类型 | `dws calendar event list` | 职能边界(参加什么类型的会)| +| ④ 聊天记录 | `dws chat message list` 获取与目标人的近期 IM 消息 | 语言风格/工作内容/职责线索 | + +**判定规则**: +- ① 命中(通讯录里搜到该人)→ 至少能拿到"部门 + 职级"信号,置信度起步 ≥ 30% +- ① + ② / ③ / ④ 任一路印证 → 置信度 ≥ 50% +- 两路以上独立信号一致 → 置信度 ≥ 70% +- ① 完全搜不到该人(且通讯录工具本身可用、未报错)→ 才允许告知用户"该人不在通讯录中,请确认花名是否正确" + +> **关键约束 1**:部门名 ≠ 角色("产品设计部"里有 PM、设计师、研究员),必须结合文档产出等信号区分。 +> +> **关键约束 2**:① 通讯录查询调用一次就能锁定身份范围,**严禁省略**。常见的失败模式是:在转写文本里反复 grep 目标人名找不到 → 直接放弃 → 告诉用户"找不到"——这是 100% 错误链路(参见案例 7)。 + +##### Step 5: 定向匹配 + 置信度分支 + +基于 Step 4 推断的角色,在转写原文中寻找匹配的发言模式: + +| 角色 | 典型发言特征 | +|------|----------| +| 产品经理 | 提需求、讲用户场景、定优先级 | +| 研发 | 技术约束、方案评估、排查问题 | +| 管理者 | 发言占比高、最终决策、分配任务 | +| 设计师 | 视觉方案、交互细节、体验讨论 | + +**分支 A:置信度 ≥ 50%(文本确认)** + +选取最具代表性的连续片段(≥ 2 句完整句子,避免"嗯/对/好"等纯语气词),展示给用户: + +> "根据分析,以下发言最可能是 [人名] 的: +> 「[片段内容]」 +> 确认是 TA 吗?" + +- 用户确认 → Step 6 +- 用户否认 → 换下一候选(最多 3 个) +- 3 个全否 → 告知无法仅通过文本确认,建议在听记详情页播放录音辅助辨认 + +**分支 B:置信度 < 50%(多候选展示)** + +当身份推断把握不足时,列出所有候选发言人及其代表性片段,让用户挑选: + +> "无法确定哪位是 [人名]。以下是几位候选发言人的代表性内容: +> ① 发言人1:「[片段]」 +> ② 发言人3:「[片段]」 +> 哪位更像 [人名]?或者你可以在听记详情页播放录音辅助确认。" + +- 用户选定 → Step 6 +- 用户无法确认 → 告知可在听记详情页点击对应段落播放原始录音来辨认,结束流程 + +##### Step 6: 结构化总结输出 + +提取已确认的该发言人的全部发言,结合会议上下文进行综合总结。根据实际内容灵活组织输出结构,例如: + +``` +[人名] 在本次会议中的发言总结(共 N 段,约 X 字) + +核心观点: +- 观点1... +- 观点2... +- 观点3... + +关键决策/结论: +- ... + +提出的待办/行动项: +- ... +``` + +##### Step 7: 引导用户替换发言人(必须执行,不可跳过) + +总结输出完成后,如果该发言人在转写中仍显示为匿名编号(如"发言人1"),**必须主动引导用户替换**: + +> "目前这篇听记中 [人名] 的发言仍显示为『发言人X』。要我帮你把听记里的『发言人X』全部替换为『[人名]』吗?替换后纪要和待办中的发言人也会同步更新。 +> 确认后我会执行:`dws minutes speaker replace --id --from "发言人X" --to "[人名]"`" + +- 用户确认 → **先主动调用通讯录模糊查询** `dws contact user search --query "[人名]" --format json` 获取该人员的 userId(长整型 dingUid): + - 唯一匹配 → 告知用户匹配结果,确认后执行 `dws minutes speaker replace --id --from "发言人X" --to "[人名]" --target-uid --format json` + - 多个匹配 → 列出候选(姓名+部门+userId)让用户选择后执行 + - 无匹配 → 提示用户未在通讯录中找到此人,执行不带 `--target-uid` 的替换:`dws minutes speaker replace --id --from "发言人X" --to "[人名]" --format json` +- 用户拒绝 → 不替换,询问是否还有其他发言人需要处理 + +**追问是否还有其他人需要识别:** + +> "还有其他发言人需要我帮你识别和替换吗?比如告诉我『某某人主要讲了什么内容』,我可以帮你匹配。" + +**严禁的行为:** +- [禁止] 总结完就结束,不引导用户替换发言人——用户下次看听记时发言人还是"发言人1",体验极差 +- [禁止] AI 自行决定替换而不向用户确认 +- [禁止] 未执行 `speaker replace` 就把发言人名称视为已更新 + +### 获取听记中提取的待办事项 +``` +Usage: + dws minutes get todos [flags] +Example: + dws minutes get todos --id +Flags: + --id string 听记 taskUuid (必填),取值逻辑参考 ## 注意事项 +``` + +每条记录包含: 待办内容、待办唯一ID、参与人信息、待办时间 + +### 批量查询听记详情 +``` +Usage: + dws minutes get batch [flags] +Example: + dws minutes get batch --ids uuid1,uuid2,uuid3 +Flags: + --ids string 听记 taskUuid 列表,逗号分隔 (必填) +``` + +返回字段: 听记标题、时长、参与人列表、创建时间、taskUuid、听记状态 + +**批量查询容错规则:** +> - `get batch` 部分条目权限失败时,**展示成功条目的完整信息 + 列出失败条目的 id 与错误原因**,不要因部分失败就整批放弃改为逐个查询 +> - `get batch` 整体报错(如接口级权限/参数问题)时,**降级为逐个 `get info`**,但必须在最终输出中说明"批量接口不可用,已逐个查询",且所有条目都必须尝试 +> - **严禁**:① 部分失败就整批丢弃改逐个查,不展示已成功的数据;② 某一条失败就放弃后续条目;③ 降级后不说明降级原因 + +### 修改听记标题 +``` +Usage: + dws minutes update title [flags] +Example: + dws minutes update title --id --title "Q2 复盘会议" +Flags: + --id string 听记 taskUuid (必填),取值逻辑参考 ## 注意事项 + --title string 新标题 (必填) +``` + +### 发起听记(开始录音) +``` +Usage: + dws minutes record start [flags] +Example: + dws minutes record start + dws minutes record start --session-id +Flags: + --session-id string AI 助理会话 ID (可选) +``` + +### 暂停听记录音 +``` +Usage: + dws minutes record pause [flags] +Example: + dws minutes record pause --id + dws minutes record pause --id --session-id +Flags: + --id string 听记 taskUuid (必填) + --session-id string AI 助理会话 ID (可选) +``` + +### 恢复听记录音 +``` +Usage: + dws minutes record resume [flags] +Example: + dws minutes record resume --id + dws minutes record resume --id --session-id +Flags: + --id string 听记 taskUuid (必填) + --session-id string AI 助理会话 ID (可选) +``` + +### 结束听记录音 +``` +Usage: + dws minutes record stop [flags] +Example: + dws minutes record stop --id + dws minutes record stop --id --session-id +Flags: + --id string 听记 taskUuid (必填) + --session-id string AI 助理会话 ID (可选) +``` + +### 更新纪要内容 +``` +Usage: + dws minutes update summary [flags] +Example: + dws minutes update summary --id --content "新的纪要内容" +Flags: + --id string 听记 taskUuid (必填) + --content string 新的纪要内容 (必填) +``` + +用传入的摘要文本全量覆盖听记的纪要内容,不触发 AI 重新生成。适用于用户手动编辑或 AI Agent 修改纪要的场景。 + +**重要 — 修改纪要的完整流程(必须严格执行):** +当用户要求"精简纪要/优化纪要/修改纪要内容"时,**必须完成以下三步,缺一不可**: +1. **读取**:先调用 `get summary --id ` 获取当前纪要原文 +2. **修改**:AI 根据用户要求对纪要内容进行修改(如精简、重新整理、格式优化等),但必须遵守以下约束: + - **图片必须保留**:原文中的所有 Markdown 图片(如 `![alt](url)`)必须完整保留,不得删除、漏掉、替换为纯文本或打乱语义位置 + - **仅优化文本内容**:可以调整标题层级、段落结构、列表与措辞,但不得破坏图片与对应上下文的关联关系 +3. **校验**:写回前必须执行 Markdown 格式检查,确保输出结构合理、可渲染、无明显格式错误(如未闭合代码块、列表层级混乱、标题层级异常等) +4. **写回**:将修改后的完整纪要内容通过 `update summary --id --content "修改后的完整纪要"` **写回听记**,确保修改持久化 + +**严禁只读取和修改纪要而不调用 `update summary` 写回**,否则用户看到的仍然是原始纪要,修改不会生效。 + +### 创建思维导图 +``` +Usage: + dws minutes mind-graph create [flags] +Example: + dws minutes mind-graph create --id +Flags: + --id string 听记 taskUuid (必填) +``` + +触发创建听记思维导图任务。触发成功后,可通过 `mind-graph status` 轮询任务状态。状态:0=进行中,1=成功,2=失败。 + +**重要:当用户要求"生成思维导图/创建脑图"时,必须调用此命令(`mind-graph create`),严禁自行生成 HTML 或其他格式的思维导图。** 思维导图由服务端专业引擎生成,AI 不应尝试自己构造思维导图内容。 + +### 查询思维导图状态 +``` +Usage: + dws minutes mind-graph status [flags] +Example: + dws minutes mind-graph status --id +Flags: + --id string 听记 taskUuid (必填) +``` + +查询指定听记的思维导图生成状态。返回任务状态:0=进行中,1=成功,2=失败。如果没有返回任务状态,也视为成功。 + +### 替换发言人 +``` +Usage: + dws minutes speaker replace [flags] +Example: + dws minutes speaker replace --id --from "张三" --to "李四" + dws minutes speaker replace --id --from "张三" --to "李四" --target-uid +Flags: + --id string 听记 taskUuid (必填) + --from string 源发言人昵称 (必填) + --to string 目标发言人昵称 (必填) + --target-uid string 目标发言人钉钉 UID (可选) +``` + +批量替换听记转写中指定发言人,将源发言人(speakerNick)精确匹配的所有段落替换为目标发言人。支持同时替换 nickName 和 subSpeakerNickname 两种匹配方式,并自动更新纪要、待办中的发言人信息。 + +**重要:** +- 此命令支持替换**任意发言人**,包括已关联通讯录信息的发言人(如"张三"、"李四"等真实姓名),不仅限于"发言人1"之类的占位符 +- `--from` 填写当前听记中显示的发言人名称(无论是"发言人1"还是真实姓名),`--to` 填写要替换成的目标名称 +- 如果用户希望将发言人关联到通讯录中的具体联系人,可通过 `--target-uid` 传入目标用户的钉钉 UID + +### 触发创建发言人段落总结任务 +``` +Usage: + dws minutes speaker summary create [flags] +Example: + dws minutes speaker summary create --ids + dws minutes speaker summary create --task-uuids +Flags: + --ids string 听记 taskUuid 列表,逗号分隔 (必填) + --task-uuids string --ids 的别名,同样接受逗号分隔的 taskUuid 列表 +``` + +触发创建发言人的段落总结任务,将听记中每位发言人的所有发言内容汇总总结。触发后需调用 `speaker summary get` 查询总结结果。 + +> **参数别名说明**:`--ids` 和 `--task-uuids` 等价,均可使用。推荐统一用 `--ids`。 + +### 查询发言人段落总结结果 +``` +Usage: + dws minutes speaker summary get [flags] +Example: + dws minutes speaker summary get --ids + dws minutes speaker summary get --task-uuids +Flags: + --ids string 听记 taskUuid 列表,逗号分隔 (必填) + --task-uuids string --ids 的别名,同样接受逗号分隔的 taskUuid 列表 +``` + +查询发言人段落总结任务的结果,返回每位发言人的发言汇总。需先调用 `speaker summary create` 触发任务。 + +> **参数别名说明**:`--ids` 和 `--task-uuids` 等价,均可使用。推荐统一用 `--ids`。 + +**speaker summary 异步轮询策略(必须严格遵守):** + +`speaker summary create` 只是触发后端异步任务,**不会立即返回总结结果**。AI 必须按以下策略轮询 `speaker summary get`: + +1. 调用 `speaker summary create --ids ` 触发任务 +2. **等待至少 5 秒**后,首次调用 `speaker summary get --ids ` 查询结果 +3. 如果返回结果为空(无内容),**继续等待 5 秒**后重试 +4. **最大轮询次数不超过 20 次**(即最长等待约 100 秒) +5. 如果 20 次轮询后仍然为空,视为**任务未完成或无内容**,告知用户:"发言人段落总结任务可能仍在处理中,请稍后再试。" + +``` +伪代码: +speaker summary create --ids +wait 5s +for i in 1..20: + result = speaker summary get --ids + if result 不为空: + break # 拿到结果,继续后续流程 + wait 5s +if 仍为空: + 告知用户任务可能仍在处理中 +``` + +**严禁**: +- 调用 `create` 后立即调用 `get`(不等待)——后端异步任务需要时间生成 +- 只轮询 1-2 次就放弃——正常任务可能需要 10-30 秒 +- 轮询超过 20 次——避免无限等待 + +**speaker summary 典型应用场景 — 通过发言主题匹配发言人:** + +当用户只知道某个人的发言主题或关联内容(如"讲战略规划的那个人是谁"),但不知道对应的发言人编号时,可以通过 speaker summary 获取每位发言人的段落总结,再用关键词匹配确认身份。 + +**完整链路(发言人段落总结 → 关键词匹配 → 确认 → 替换):** + +1. **确定听记 taskUuid**:从 `list mine/all` 或用户提供的 URL 中获取 +2. **触发发言人段落总结**:`dws minutes speaker summary create --ids --format json` +3. **延迟轮询获取结果**:等待 5s 后调用 `dws minutes speaker summary get --ids --format json`,最多轮询 20 次 +4. **AI 按发言人分组展示总结**:将每位发言人的段落总结以结构化方式呈现给用户 +5. **关键词匹配**:从用户描述中抽取关键词(如"战略规划"、"供应链"),在各发言人的段落总结中做模糊匹配 +6. **引导用户确认**: + - 唯一高置信命中 → "『发言人1』的段落总结涉及『战略规划、AI化转型』,很可能就是你说的『李总』,确认替换吗?" + - 多候选 → 列出所有命中的发言人编号 + 匹配的总结关键词,让用户挑选 + - 无匹配 → 请用户补充更具体的关键词 +7. **用户确认后执行替换**:`dws minutes speaker replace --id --from "发言人1" --to "李总"` + +**自然语言触发示例:** + +| Query | AI 处理策略 | +|-------|-----------| +| "帮我看看这个会议里每个人主要说了什么" | speaker summary create → 轮询 get → 按发言人分组展示 | +| "讲战略规划的那个人是谁" | speaker summary create → 轮询 get → 关键词匹配 → 引导确认 | +| "帮我把每个发言人的核心观点总结一下" | speaker summary create → 轮询 get → 结构化输出 | +| "我想知道会上谁讲了供应链相关内容" | speaker summary create → 轮询 get → "供应链"关键词匹配 | +| "那个讲招聘进度的人应该是张三" | speaker summary create → 轮询 get → "招聘进度"匹配 → 确认 → speaker replace | + +> **与 `get transcription` 聚类方案的区别**:`speaker summary` 是后端 AI 生成的结构化总结,信息密度更高、匹配更精准;而 `get transcription` 聚类是 AI 在本地按 speakerNick 分组原文再提取要点,适合需要看原文细节的场景。两者可配合使用:先用 speaker summary 快速定位,再用 transcription 看具体原文。 + +### 添加个人热词 +``` +Usage: + dws minutes hot-word add [flags] +Example: + dws minutes hot-word add --words "钉钉" + dws minutes hot-word add --words "OKR,钉钉,Copilot" +Flags: + --words string 要添加的热词,多个用逗号分隔 (必填) +``` + +添加听记个人热词,用于优化语音识别中专有名词、人名等的识别准确率。支持一次添加多个热词(逗号分隔),每个热词长度不超过 10 个汉字或 5 个英文单词。 + +### 查询我的热词列表 +``` +Usage: + dws minutes hot-word list +Example: + dws minutes hot-word list +``` + +查询当前用户配置的所有听记热词列表。无需传入额外参数,系统自动识别当前用户身份。返回用户已添加的全部热词,适用于查看已有热词、去重检查等场景。 + +### 查找替换听记文字 +``` +Usage: + dws minutes replace-text [flags] +Example: + dws minutes replace-text --id --search "旧文字" --replace "新文字" +Flags: + --id string 听记 taskUuid (必填) + --search string 要查找的文字 (必填) + --replace string 替换为的新文字 (必填) +``` + +把听记中所有出现的原文字替换为目标文字,包括转写段落和纪要摘要中出现的原文字都会被替换。区分大小写,精确匹配。 + +**重要 — 执行前必须检查特殊字符并提示用户确认:** +用户输入的原始文本(`--search`)或目标文本(`--replace`)中可能携带特殊字符(如引号 `"` `'` `"` `"`、书名号 `《》`、括号 `【】()()`、星号 `*`、反斜杠 `\`、换行符、Markdown 格式符号等)。这些特殊字符在转写原文中**通常不存在**,会导致精确匹配失败(替换 0 处)。 + +AI **必须在执行 `replace-text` 之前**检查用户输入,若发现特殊字符,**先向用户确认**: + +> "我注意到你输入的文本中包含特殊字符(如 `…`),转写原文中通常不会包含这些字符,直接匹配可能找不到。建议去掉特殊字符后再替换: +> +> - 原始文本:`XXX` → 建议改为:`YYY` +> - 目标文本:`AAA` → 建议改为:`BBB` +> +> 使用去掉特殊字符的版本执行替换?[是] [否,使用原始输入]" + +- 用户确认 → 使用清理后的文本执行替换 +- 用户选择原始输入 → 按原样执行,尊重用户意图 +- **严禁不提示就自行去掉特殊字符**——用户可能确实需要匹配带特殊字符的文本 + +**重要 — 执行后必须主动引导用户添加热词(避免长期反复识别错):** +`replace-text` 仅修正**当前这一篇听记**的文字,**不会影响后续新听记的语音识别结果**。如果用户替换的是一个**长期容易被识别错的专有名词、人名、产品名**(如把"付工"改成"悟空"、把"非书"改成"飞书"),AI **必须在 `replace-text` 成功后主动追问用户**: + +> "我已经把这篇听记里的『旧文字』替换为『新文字』。如果这个词以后也容易被识别错,建议把它加到个人热词里,后续新听记就不会再识别错了。要我现在帮你执行 `dws minutes hot-word add --words "新文字"` 吗?" + +用户确认后立即调用 `hot-word add`。**严禁只做替换不引导**——这会让用户每次都要手动改一次,体验非常差。 + +### 创建文件上传会话或者文件转听记或者链接转听记 +``` +Usage: + dws minutes upload create [flags] +Example: + dws minutes upload create --file-name "meeting.mp4" --file-size 102400 + dws minutes upload create --file-name "meeting.mp4" --file-size 102400 --title "周会录音" + dws minutes upload create --file-name "meeting.mp4" --file-size 102400 --input-language "zh" --enable-message-card +Flags: + --file-name string 文件名(含后缀),如 meeting.mp4 (必填) + --file-size int 文件大小(字节)(必填,正整数) + --title string 听记标题,不传时默认使用文件名去掉后缀 (可选) + --template-id string 纪要生成使用的模板 ID (可选) + --input-language string ASR 识别的源语言 (可选) + --enable-message-card 是否推送闪记卡片消息 (可选,默认 false) +``` + +创建文件上传会话,获取预签名上传 URL。调用方拿到 URL 后,直接用 HTTP PUT 将文件上传到该 URL。必须与 `upload complete` 配合使用: +1. 调用 `upload create` 获取预签名上传 URL 和 sessionId +2. HTTP PUT 预签名上传 URL 上传文件(不带 HEADER) +3. 调用 `upload complete` 传入 sessionId 完成创建 + +### 完成文件上传并创建听记 +``` +Usage: + dws minutes upload complete [flags] +Example: + dws minutes upload complete --session-id +Flags: + --session-id string 上传会话 ID,来自 upload create 返回的 sessionId (必填) +``` + +文件上传完成后,调用此命令创建听记。必须在 `upload create` 之后、预签名 URL 上传完成后调用。幂等:同一 sessionId 重复调用直接返回已有任务,不会重复创建。 + +### 取消文件上传会话 +``` +Usage: + dws minutes upload cancel [flags] +Example: + dws minutes upload cancel --session-id +Flags: + --session-id string 要取消的会话 sessionId (必填) +``` + +取消 `upload create` 创建的上传会话,释放服务端资源。用于在上传前或上传失败后取消会话。 + +### 批量添加听记成员并设置权限 +``` +Usage: + dws minutes permission add [flags] +Example: + dws minutes permission add --ids --member-uids 123456,789012 --policy 3 + dws minutes permission add --ids --member-uids 123456 --policy 2 --cover + dws minutes permission add --ids --member-uids 123456 --policy 3 --sub-resources "OrigContent,Summary" + dws minutes permission add --uuids --member-uids 123456 --policy 3 + dws minutes permission add --task-uuids --member-uids 123456 --policy 3 +Flags: + --ids string 听记 taskUuid 列表,逗号分隔 (必填) + --uuids string --ids 的别名 + --task-uuids string --ids 的别名 + --member-uids string 成员钉钉 UID 列表,逗号分隔 (必填) + --policy int 权限类型: 0=管理员, 1=所有者, 2=可编辑, 3=可查看/下载, 4=仅查看 (必填) + --cover 是否覆盖已有权限 (可选,默认 false) + --sub-resources string 权限子模块,逗号分隔: OrigContent/Summary/Analysis/Note (可选) +``` + +批量给多个听记增加成员,并设置成员的权限。 + +**权限类型说明:** + +| --policy 值 | 含义 | 说明 | +|------------|------|------| +| 0 | 管理员 | 可管理听记的所有设置和成员权限 | +| 1 | 所有者 | 听记的所有者,拥有最高权限 | +| 2 | 可编辑 | 可编辑听记的纪要、待办等内容 | +| 3 | 可查看/下载 | 可查看和下载听记内容,不可编辑 | +| 4 | 仅查看 | 仅可查看听记内容,不可下载 | + +**权限子模块说明(--sub-resources,可选):** + +| 子模块 | 含义 | +|--------|------| +| OrigContent | 原始内容(转写原文) | +| Summary | 纪要(AI 摘要) | +| Analysis | 分析 | +| Note | 笔记 | + +不传 `--sub-resources` 时,默认对所有子模块生效。传入后仅对指定的子模块授权。 + +**典型使用场景:** +- 会议结束后将听记共享给未参会的同事查看 +- 给团队成员批量授权某几篇听记的编辑权限 +- 限制只共享纪要而不共享原始转写内容 + +### 批量移除听记成员权限 +``` +Usage: + dws minutes permission remove [flags] +Example: + dws minutes permission remove --ids --member-uids 123456,789012 + dws minutes permission remove --ids --member-uids 123456 + dws minutes permission remove --uuids --member-uids 123456 + dws minutes permission remove --task-uuids --member-uids 123456 +Flags: + --ids string 听记 taskUuid 列表,逗号分隔 (必填) + --uuids string --ids 的别名 + --task-uuids string --ids 的别名 + --member-uids string 成员钉钉 UID 列表,逗号分隔 (必填) +``` + +批量移除多个听记的成员权限。移除后,对应成员将失去对这些听记的访问权限。 + +**注意事项:** +- 移除权限后,该成员将无法再访问对应的听记内容 +- 如果成员是听记的创建者(所有者),无法通过此命令移除其权限 +- 建议在移除前先确认成员的当前权限,避免误操作 + +### 查询我的听记标签/分组列表 +``` +Usage: + dws minutes tag list +``` + +查询当前用户在听记页面手动创建的所有标签或分组列表。无需传入额外参数,系统自动识别当前用户身份。 +返回所有标签/分组的列表,每条记录包含 tagId 和标签名称。获取到 tagId 后,可使用 `dws minutes tag query --tag-id ` 查询该标签下的听记列表。 + +**注意事项:** +- 标签/分组在听记页面手动创建,此命令仅提供查询能力 +- 返回的 tagId 用于后续 `tag query` 命令的 `--tag-id` 参数 + +### 根据标签ID查询听记列表 +``` +Usage: + dws minutes tag query [flags] +Example: + dws minutes tag query --tag-id + dws minutes tag query --tag-id --limit 20 + dws minutes tag query --tag-id --limit 10 --cursor +Flags: + --tag-id string 标签/分组 ID,可通过 tag list 获取 (必填) + --limit float 每页数据条数 (默认 10) + --cursor string 分页 token (首页留空) +``` + +根据用户的标签或分组 ID 查询该标签下的听记列表。支持分页查询。 +tagId 可通过 `dws minutes tag list` 获取。 + +**注意事项:** +- `--tag-id` 必填,值来自 `dws minutes tag list` 返回的 tagId +- 支持分页,使用 `--cursor` 传入上一次返回的 nextToken +- 标签/分组在听记页面手动创建,不支持通过 CLI 创建标签 diff --git a/skills/multi/dingtalk-minutes/references/minutes-error.md b/skills/multi/dingtalk-minutes/references/minutes-error.md new file mode 100644 index 00000000..7a093c00 --- /dev/null +++ b/skills/multi/dingtalk-minutes/references/minutes-error.md @@ -0,0 +1,91 @@ +# 听记错误诊断与恢复 + +> 命令层级与对象选择见 [minutes-overview.md](./minutes-overview.md),详细参数见 [minutes-commands.md](./minutes-commands.md)。 + +## 错误决策表 + +| 错误现象 | 分类 | 处理 | +|---|---|---| +| `taskUuid is invalid` / errcode 300 | ID 无效,不可重试 | 停止复用该 ID;用 `minutes list all` 重新定位真实 taskUuid | +| `permission denied` / 403 | 权限不足,不可重试 | 告知用户联系创建者共享;不要更换参数绕过 | +| `not found` / 404 / `P_DataNotFound` | 资源不存在,不可重试 | 说明听记可能已删除或过期;用 `list all` 查找候选 | +| stdout 为空且无结构化错误 | 临时服务或鉴权异常,可重试 | 原命令重试一次;仍为空则说明服务暂不可用 | +| 网络超时 / 连接失败 | 瞬时错误,可重试 | 原命令重试一次;仍失败则报告网络问题 | +| `result: null` 或内容尚未生成 | 服务端处理中 | 说明摘要/转写仍在生成,稍后再查 | +| `unknown command` | 命令层级错误 | 查对应 `--help`,修正为 `get `、`list ` 等合法结构 | +| `unknown flag` | 参数名错误 | 查具体子命令 `--help`,只修正参数,不在别名之间盲试 | +| `cannot parse time` | 时间格式错误 | 使用 ISO-8601(推荐带时区)或命令支持的日期格式 | +| upload `business error` | 文件、大小、格式或并发会话问题 | 核对文件名、真实字节数、格式与未结束 session;修正后重试一次 | + +## taskUuid 恢复 + +- 无效 taskUuid 不会因重试变为有效。 +- 用户未提供 taskUuid 或 URL 时,使用 `minutes list all --query "<关键词>" --format json`。 +- 同时核对标题、组织和时间;多候选时请用户确认。 +- 不从历史对话、示例或编码字符串拼接 ID。 +- `list all` 无结果时,保留时间范围并放宽关键词;仍无结果则明确说明。 + +## 命令层级恢复 + +| 错误写法 | 正确写法 | +|---|---| +| `minutes info --id ...` | `minutes get info --id ...` | +| `minutes get --id ...` | `minutes get info/summary/transcription ...` | +| `minutes list --start ...` | `minutes list all --start ...` | +| `minutes summary --id ...` | `minutes get summary --id ...` | + +`list` 后必须带 `mine`、`shared` 或 `all`;默认使用 `all`。 + +## 转写分页 + +1. 首次执行 `minutes get transcription --id --format json`。 +2. 返回分页 token 时,将该 token 传入下一次调用。 +3. token 为空或不存在时结束。 +4. 某个 token 返回空内容且没有新 token 时停止,保留已拉取内容并说明覆盖范围。 +5. 用户指定时间段时,确认分页结果覆盖该时间段后再分析。 + +不要使用 shell 管道截断或过滤转写;用 dws 分页参数并在返回 JSON 中处理。 + +## 服务端筛选 + +有关键词或时间条件时,把条件放进同一条列表命令: + +```bash +dws minutes list all --query "<关键词>" --start "" --end "" --format json +``` + +无结果时按顺序处理: + +1. 保留时间范围并放宽关键词。 +2. 必要时扩大用户允许的时间窗。 +3. 列出近似候选供确认。 +4. 仍无结果时明确说明,不生成虚构内容。 + +## 权限与批量查询 + +- `get batch` 部分成功时,保留成功数据并列出失败 ID 与原因。 +- 批量接口整体不可用时,逐条降级为 `get info`。 +- 权限失败不改变目标 taskUuid;不要切到另一篇听记替代。 +- 用户需要他人听记时,可先检查 `list shared` 或请创建者共享。 + +## Upload 恢复 + +固定流程: + +1. `upload create --file-name ... --file-size ` 获取 `sessionId` 与上传地址。 +2. 使用返回的地址上传原文件。 +3. `upload complete --session-id ` 完成。 +4. 需要放弃时执行 `upload cancel --session-id `。 + +`--file-size` 使用真实字节数。已有活动 session 时先完成或取消,不创建并发会话。 + +## 跨平台执行 + +每条 dws 命令独立执行,不依赖 `head`、`grep`、`jq`、shell 后台符号或重定向。筛选、分页和输出格式优先使用 CLI 自身参数。 + +## 自动化脚本 + +| 脚本 | 场景 | +|---|---| +| [minutes_recent_summary.py](../scripts/minutes_recent_summary.py) | 获取近期听记摘要并合并 | +| [minutes_extract_todos.py](../scripts/minutes_extract_todos.py) | 提取听记待办 | diff --git a/skills/multi/dingtalk-minutes/references/minutes-index.md b/skills/multi/dingtalk-minutes/references/minutes-index.md new file mode 100644 index 00000000..13638126 --- /dev/null +++ b/skills/multi/dingtalk-minutes/references/minutes-index.md @@ -0,0 +1,13 @@ +# 听记能力索引 + +| 主题 | 详见 | +|---|---| +| 命令层级、scope 选择、指令树 | [`minutes-overview.md`](./minutes-overview.md) | +| 常规查询、摘要、关键词及命令参数 | [`minutes-commands.md`](./minutes-commands.md) | +| 意图判断 | [`minutes-intent.md`](./minutes-intent.md) | +| 核心工作流与上下文传递 | [`minutes-workflow.md`](./minutes-workflow.md) | +| 错误恢复、搜索与翻页最佳实践 | [`minutes-error.md`](./minutes-error.md) | +| 发言人识别/匹配 | [`10-minutes-speaker-match.md`](./10-minutes-speaker-match.md) | +| 发言人纠偏 | [`11-minutes-speaker-correct.md`](./11-minutes-speaker-correct.md) | +| 轻量流程 | [`lite-recipes.md`](./lite-recipes.md) | +| 业务规则参考 | [`07-minutes.md`](./07-minutes.md) | diff --git a/skills/multi/dingtalk-minutes/references/minutes-intent.md b/skills/multi/dingtalk-minutes/references/minutes-intent.md new file mode 100644 index 00000000..2797b49b --- /dev/null +++ b/skills/multi/dingtalk-minutes/references/minutes-intent.md @@ -0,0 +1,414 @@ +# 听记意图判断 + +## 意图判断 + +### 发起听记 +用户说"开始听记/开始录音/发起一个听记/启动听记/开始听记录这次会议/我要开始听记了/录音开始/我要听记" → `record start` + +**自然语言示例:** +- "开始听记开启听记" +- "开始录音" +- "发起一个听记" +- "启动听记" +- "开始听记录这次会议" +- "我要开始听记了" +- "录音开始" + +### 暂停/继续录制 +用户说"暂停一下/暂停录音" → `record pause` +用户说"继续/继续录音" → `record resume` + +**自然语言示例:** +- "暂停一下" +- "继续" + +### 结束录制 +用户说"结束听记/结束录音/停止录音/结束" → `record stop` + +**自然语言示例:** +- "结束听记" +- "结束录音" +- "停止" + +### 文件转听记 +用户说"把文件转成听记/上传音频文件/上传录音/把这个 mp3 转成听记/帮我把会议录音转写一下/把附件里的音频文件做转写" → `upload create` → HTTP PUT → `upload complete` + +**自然语言示例:** +- "把这个文件转写生成纪要" +- "帮我把这段录音转成文字" +- "上传一个音频文件,帮我转写" +- "我有一个录音文件,帮我生成听记" +- "把这个 mp3 转成听记" +- "帮我把会议录音转写一下" +- "这是昨天的录音,帮我整理成纪要" +- "把附件里的音频文件做转写" + +用户说"取消上传" → `upload cancel` + +### 查询我的热词列表 +用户说"看看我的热词/我加了哪些热词/热词列表/查热词" → `hot-word list` + +**自然语言示例:** +- "我之前加过哪些热词" +- "看看我的热词列表" +- "查一下我配置的热词" +- "查一下我听记的热词" + +### 添加热词 +用户说"加热词/添加热词/设置热词/把某个词加到热词里/这个词总是识别错" → `hot-word add` + +**自然语言示例:** +- "转写不准,应该是'悟空'而不是'付工'" +- "帮我把'钉钉'加到热词里" +- "这个词总是识别错,加一下热词" +- "'飞书'总被识别成'非书',加个热词" +- "帮我设置一下热词,'悟空'经常识别不对" +- "我们公司有个产品叫'魔镜',加一下热词避免识别错" +- "把'AI听记'加进热词库" +- "这个专有名词总转写错,帮我加到热词" + +### 查找替换 +用户说"把所有的A替换成B/查找替换/批量替换文字" → `replace-text`,**执行成功后必须主动引导用户:"要不要把『新文字』加到热词里,避免后续新听记再识别错?" 用户确认后再调用 `hot-word add`** + +**自然语言示例:** +- "把所有的A,替换成B" +- "把听记里的'付工'都改成'悟空'" +- "这一篇里把'非书'替换成'飞书'" + +**正确的对话节奏(必须遵守):** +1. **检查特殊字符**:检查用户提供的原始文本和目标文本是否包含特殊字符(引号 `"` `'` `"` `"`、书名号 `《》`、括号 `【】()()`、星号 `*`、反斜杠 `\`、Markdown 格式符号等)。若包含,先提示用户确认: + > "你输入的文本中包含特殊字符 `…`,转写原文中通常不包含这些字符,直接匹配可能替换不到。建议去掉特殊字符后替换:原始文本 `XXX` → 建议 `YYY`。使用去掉特殊字符的版本?[是] [否,使用原始输入]" + - 用户确认 → 使用清理后文本 + - 用户选择原始输入 → 按原样执行 + - **严禁不提示就自行去掉特殊字符** +2. 执行 `replace-text` 完成本篇替换 +3. 立即追问:"如果这个词以后也容易被识别错,建议加个热词,后续新听记就不会再错了。要现在加吗?" +4. 用户确认 → 调用 `hot-word add --words "<新文字>"` +5. 用户拒绝 → 直接结束,不再追问 + +### 修改说话人 +用户说"替换发言人/修改发言人/换发言人名字/把说话人改成某某/发言人标注错了" → `speaker replace` + +**关键:支持替换任意发言人,包括已关联通讯录信息的真实姓名(如"张三"、"拾光"等),不限于"发言人1"等占位符。** 用户提到"把 XX 改成 YY"时,XX 即为 `--from`,YY 即为 `--to`。 + +**默认引导通讯录查询获取 dingUid(推荐流程):** + +替换发言人时,`--target-uid` 参数可以将发言人关联到钉钉通讯录真实身份。**AI 应默认引导用户通过通讯录模糊查询获取目标人员的 dingUid**,而非让用户自行提供。流程如下: + +1. 用户提供目标姓名(如"张三")后,**AI 主动调用通讯录模糊查询**: + ``` + dws contact user search --query "张三" --format json + ``` +2. 从返回结果中提取匹配的人员列表,每条包含 `userId`(长整型数字,即 dingUid)和姓名: + - **唯一匹配** → 直接向用户确认:"通讯录中找到『张三(userId: 123456789)』,确认将发言人替换为此人并关联通讯录身份?" + - **多个匹配** → 列出候选让用户选择: + > 通讯录中找到多个匹配,请选择目标人员: + > + > | # | 姓名 | 部门 | userId | + > |---|------|------|--------| + > | 1 | 张三 | 产品部 | 123456789 | + > | 2 | 张三丰 | 技术部 | 987654321 | + > + > 请输入序号选择。 + - **无匹配** → 提示用户:"通讯录中未找到『张三』,可以直接替换发言人名称(不关联通讯录身份),或提供更精确的姓名重新查询。直接替换?[是] [重新查询]" +3. 获取到 userId 后,在 `speaker replace` 中附加 `--target-uid `,实现发言人关联通讯录身份 + +> **注意**:`dws contact user search` 返回的 `userId` 是**长整型数字**(如 `123456789`),这就是 `--target-uid` 需要的值。不要与 `staffId`、`unionId` 等其他 ID 混淆。 + +**发言人映射模式识别(高频场景):** + +用户经常使用如下格式批量告知发言人对应关系: +- `发言人1:张三` +- `发言人1:张三`(中文冒号) +- `发言人1->张三` +- `发言人1 -> 张三` +- `发言人1 = 张三` +- 多条换行或逗号分隔:`发言人1:张三, 发言人2:李四, 发言人3:王五` + +**AI 识别到上述模式时,必须执行以下流程:** + +1. **解析映射关系**:提取所有 `源发言人 → 目标姓名` 的配对 +2. **批量通讯录查询**:对每个目标姓名调用 `dws contact user search --query "<姓名>" --format json`,获取 userId(dingUid)。将查询结果汇总后一并向用户确认 +3. **向用户确认**:展示解析结果(含通讯录匹配)并请求确认,格式如下: + +> 我识别到以下发言人对应关系,并已从通讯录中匹配到对应人员: +> +> | 当前标注 | 替换为 | 通讯录匹配 | userId | +> |---------|--------|-----------|--------| +> | 发言人1 | 张三 | 张三(产品部) | 123456789 | +> | 发言人2 | 李四 | 李四(技术部) | 987654321 | +> +> 替换后,发言人将关联到通讯录真实身份,该听记中所有对应的发言段落标注、纪要和待办中的发言人信息都会同步更新。 +> +> 确认执行?[确认] [取消] + +4. **用户确认后**,逐条执行 `speaker replace`(附带 `--target-uid`): + ``` + dws minutes speaker replace --id --from "发言人1" --to "张三" --target-uid 123456789 --format json + dws minutes speaker replace --id --from "发言人2" --to "李四" --target-uid 987654321 --format json + ``` + 如果某个姓名未在通讯录中找到匹配,则该条不带 `--target-uid`: + ``` + dws minutes speaker replace --id --from "发言人3" --to "王五" --format json + ``` +5. **全部完成后**,主动追问是否需要同时执行文本替换(`replace-text`),因为纪要正文中可能也残留了"发言人1"等文字: + +> 发言人标注已全部替换完成。纪要正文中如果还有残留的「发言人1」「发言人2」等文字,是否也一并替换?[是] [否] + +6. 用户确认后,对每条映射执行 `replace-text`: + ``` + dws minutes replace-text --id --search "发言人1" --replace "张三" --format json + ``` + +**禁止行为:** +- 禁止不确认就直接执行替换(发言人替换不可逆) +- 禁止只执行 `speaker replace` 不引导 `replace-text`(纪要正文可能有残留) +- 禁止只执行 `replace-text` 不执行 `speaker replace`(转写段落的发言人标注不会被 replace-text 修改) +- 禁止跳过通讯录查询直接要求用户提供 dingUid——应由 AI 主动查询并引导选择 + +**自然语言示例:** +- "把这一段内容的说话人全部改成张三" +- "帮我把发言人1改成李总" +- "这个说话人标注错了,改成王伟" +- "把所有'发言人2'替换成'小明'" +- "说话人识别错了,帮我修改一下" +- "把这段的说话人改成'拾贝'" +- "发言人1是我,帮我把名字改过来" +- "把'未知说话人'全都改成张总" +- "把这里面的发言人1,替换成拾光" +- "张三其实是李四,帮我改一下" +- "发言人1:张三 发言人2:李四 发言人3:王五" +- "发言人1->张三, 发言人2->李四" +- "发言人1是张三,发言人2是李四" + +### 查某人在听记中说了什么(发言人识别与总结) + +**触发条件**(必须同时满足): +1. 用户提供了听记来源(URL / 时间 / 关键词可定位到具体听记) +2. 用户指定了一个具体人名(花名/真名/关系称谓均可) +3. 用户的目标是获取该人在会议中说了什么 + +**不触发**(走其他链路): +- 用户只要整体纪要、未指定特定人物 → `get summary` +- 用户直接说"把发言人1改成张三" → `speaker replace`(无需走推断链路) +- 用户想提取待办 → `get todos` + +用户说"帮我看看某人说了什么/某人在会上提了哪些观点/某人有什么发言" → 走**发言人识别与总结**完整链路(详见 [获取听记语音转写原文](./minutes-commands.md#获取听记语音转写原文) 章节中的"发言人识别与总结执行链路") + +**自然语言示例:** + +| Query | 关键线索 | +|-------|----------| +| "帮我看看这个听记里张三说了什么" + URL | 听记URL + 人名 | +| "李总在今天的会上提了哪些观点" | 时间(今天) + 人名 + 观点 | +| "上周产品评审里王五有什么发言" | 时间(上周) + 关键词(产品评审) + 人名 | +| "这个会议里我老板说了啥" | 听记上下文 + 关系称谓(需解析为人名) | +| "帮我找一下设计师小陈在访谈里的观点" | 角色提示(设计师) + 人名 + 场景 | + +- "帮我看看这个听记里张三说了什么" +- "李总在今天的会上提了哪些观点" +- "上周产品评审里王五有什么发言" +- "这个会议里我老板说了啥" +- "帮我找一下设计师小陈在访谈里的观点" +- "拾光在这个会议里讲了什么" +- "帮我总结一下这次会议里李总的核心发言" +- "王经理今天开会说了啥重点" + +**关键约束(必须遵守):** +- 这是一个**完整的识别 → 推断 → 确认 → 总结 → 引导替换**链路,不要只做总结就结束,**必须引导用户将「发言人1」等占位符替换为真实姓名** +- 完整执行链路见 [获取听记语音转写原文](./minutes-commands.md#获取听记语音转写原文) 章节中的"发言人识别与总结执行链路" + +### 通过关键词模糊匹配确认发言人(与 `get transcription` 联动) +当用户在拉取完转写原文(且已按发言人聚类)之后,提供形如『某某人主要讲了 XX』『某某人负责 YY』『某某人这次的核心是 ZZ』的描述时,**不要直接做替换**,而是按下面的链路推进: + +1. **从用户输入中抽取关键词**(如『战略规划』『供应链效率』『AI 化转型』『招聘进度』) +2. **在已聚类的发言人核心要点中做模糊匹配**(支持同义词与近义词) +3. **匹配结果引导用户确认:** + - 唯一高置信命中 → 提示用户:"『发言人1』很可能就是你说的『李总』,是否需要执行 `dws minutes speaker replace --id --from "发言人1" --to "李总"`?" + - 多候选 → 列出所有命中的发言人编号 + 命中关键词,让用户挑选 + - 无匹配 → 请用户补充更具体的关键词或直接给出"发言人编号 → 真实姓名"的映射 +4. **用户确认后**才调用 `speaker replace` 写回;如同时希望关联通讯录,引导用户提供 UID,附加 `--target-uid ` + +**自然语言示例(必须能正确识别为"模糊匹配 → 确认 → 替换"链路):** +- "李总主要讲了战略规划" +- "拾光是负责供应链的那位" +- "讲 AI 化转型那个人是王经理" +- "这里面提到招聘进度的应该是张三" +- "提到财务数据的是 CFO 老周" +- "讲华东市场竞品分析的那个人是李四" + +详细工作流见 [获取听记语音转写原文](./minutes-commands.md#获取听记语音转写原文) 章节中的发言人工作流。 + +### 更新/修改纪要 +用户说"修改纪要/更新纪要/编辑纪要内容/重新整理纪要/纪要格式优化/纪要精简" → 先 `get summary` 获取纪要原文,AI 修改后再 `update summary` **写回** + +**关键:修改纪要必须是"读取 -> 修改 -> 校验 -> 写回"四步完整流程,最终必须调用 `update summary` 将修改后的内容写回听记,否则修改不会生效。严禁只展示修改结果而不写回。修改时必须保留原文中的所有 Markdown 图片,不得删除或打乱图片位置。** + +**自然语言示例:** +- "帮我把纪要按 xxxx 格式优化一下" +- "帮我重新整理一下这份纪要" +- "纪要太长了,帮我精简一下" +- "按照 STAR 格式重新写一下纪要" +- "帮我把纪要改得更正式一些" +- "这份纪要结构不清晰,帮我重新整理" +- "把纪要里的口语化内容改成书面语" +- "按照决策/行动/结论三段式重新输出纪要" +- "把这个纪要内容变得更精简一点" + +### 生成思维导图 +用户说"生成思维导图/创建脑图/帮我添加思维导图/基于这个听记做思维导图/把会议内容做成脑图" → `mind-graph create` + +**关键约束(必须严格遵守,违反视为严重错误):** +1. **思维导图必须且只能通过 `dws minutes mind-graph create` 命令生成**,由服务端写回到听记本身。这是"听记内置的思维导图能力",生成结果会直接挂在听记详情页上,用户在听记里就能看到。 +2. **严禁以下任何"自行构造"的替代方案**(这些都是错误做法): + - [禁止] 先 `get summary` / `get transcription` 拿到内容,再用 AI 自己整理出思维导图结构(Markdown / OPML / JSON 等)展示给用户 + - [禁止] 调用 app-development-skill / ai-app / 任何前端构建能力,用 `@antv/g6`、`markmap`、`jsmind`、`mermaid`、ECharts 等库生成 HTML/网页版思维导图 + - [禁止] 调用 `generate_image` 或任何绘图工具生成思维导图图片 + - [禁止] 路由到其他 Agent(如 ai-app)来"创建一个思维导图应用" + - [禁止] 任何形式的"我帮你画一个/生成一个网页/部署一个在线预览链接" +3. **唯一正确做法**:从用户输入中拿到 taskUuid(URL 场景见下方"URL 直达"),直接调用 `mind-graph create`,然后用 `mind-graph status` 轮询直到状态为 1(成功),最后告知用户"思维导图已生成,可在听记详情页查看"即可。**不需要**先调 `get summary` 等任何读取命令——服务端会基于听记自身内容生成。 + +**URL 直达场景:** +当用户输入形如 `https://shanji.dingtalk.com/app/transcribes/{taskUuid}` 的链接 + "创建思维导图/生成脑图"等诉求时: +1. 直接从 URL 路径末段提取 taskUuid +2. **直接** `dws minutes mind-graph create --id --format json` +3. 用 `mind-graph status` 轮询 +4. 严禁中间插入 `get summary` / `get transcription` 等任何"先读取内容"的步骤,更严禁基于读到的内容自行构造思维导图 + +**自然语言示例:** +- "帮我添加思维导图" +- "生成思维导图" +- "帮我创建一个脑图" +- "把这个听记做成思维导图" +- "生成这个会议的思维导图" +- "基于这个听记创建思维导图" +- "把会议内容整理成脑图" +- "<听记URL> 创建思维导图" +- "<听记URL> 帮我生成脑图" + +用户说"思维导图状态/脑图进度/思维导图好了吗" → `mind-graph status` + +### 查询听记列表 +用户说"我创建的听记/我发起的听记/我自己录的听记" → `list mine`(**仅**返回我自己创建的,不含他人共享给我的) +用户说"别人给我的听记/共享听记/共享给我的" → `list shared`(**仅**返回他人共享给我的,不含我创建的) +用户说"我可访问的听记/我能看到的听记/我有权限的听记/所有听记/全部听记" → `list all`(返回我可访问的**所有**听记 = mine + shared) +用户说"我的听记" → `list all`(**注意**:"我的听记"是模糊表述,用户通常期望看到所有可访问的听记,而不仅是自己创建的,默认走 `all`;仅当上下文明确表示"我自己创建/发起的"时才走 `mine`) +用户说"某时间段内的听记/按时间查听记/按关键词查听记" → 根据所属范围选择 `list mine`/`list shared`/`list all`,附加 `--start`、`--end`、`--query` 参数 + +**自然语言示例:** +- "查一下我最近的听记" +- "帮我找一下上周的听记" +- "有没有关于'周会'的听记" +- "看看别人共享给我的听记" +- "我这个月的听记有哪些" +- "帮我从今天听记里找需求评审" +- "上月听记里有没有提到OKR" +- "从我的听记里搜一下技术方案" +- "这周有没有关于复盘的会" +- "帮我找上周关于项目排期的听记" + +> **路由提示**: +> - 用户说"从我创建的/我发起的听记里找XX" → `list mine --query` +> - 用户说"我的听记里找XX"/"我可访问的听记"/"帮我找关于XX的听记" → `list all --query`("我的"是模糊表述,默认走 `all` 覆盖最广) +> - **仅当**用户明确强调"我自己创建/录制/发起的"时才走 `mine` +> - 含时间词(今天/上周/上月/这周/本月)时,必须同时附加 `--start` + `--end` 参数 + +### 获取听记内容 +用户说"听记详情/听记信息" → `get info` +用户说"摘要/总结/会议纪要/纪要" → `get summary` +用户说"关键字/关键词" → `get keywords` +用户说"原文/转写/录音文字/逐字稿" → `get transcription`(需要完整原文时按 `nextToken` 自动翻页;需要发言人归属时再进入 [发言人工作流](./minutes-commands.md#获取听记语音转写原文)) +用户说"会议待办/听记待办/待办事项" → `get todos` +用户说"批量查询/查多个听记" → `get batch` +用户说"音频地址/音频链接/录音文件/下载录音/音频下载/视频文件/媒体地址" → `get audio` + +**转写原文拉取时机判断(必须遵守):** +- **明确要看原文** → 调用 `get transcription`,需要完整原文时按 `nextToken` 拉取到空;输出按用户目标摘要或提取,避免无差别粘贴全文 + - 示例:"帮我看看转写原文"、"分析一下这篇听记的原文"、"把逐字稿发给我" +- **未明确要看原文** → **不要**调用 `get transcription`,用其他更合适的命令响应 + - 示例:"查一下和悟空相关的听记" → 应走 `list`,不需要拉原文 + - 示例:"这个会议讲了什么" → 应走 `get summary`,不需要拉原文 + - 示例:"这个听记有哪些待办" → 应走 `get todos`,不需要拉原文 + +**自然语言示例:** +- "帮我看看这个听记的摘要" +- "这个会议讲了什么" +- "帮我看看会议的转写原文" +- "分析一下这篇听记的转写原文" → **默认拉全部** +- "这个听记有哪些待办" +- "帮我提取一下会议的关键词" +- "帮我看看这个听记的基本信息" +- "把这个听记的录音文件给我" +- "帮我下载这个听记的音频" +- "这个听记的音频地址是什么" +- "我想下载这次会议的录音" +- "帮我拿一下这个听记的视频文件" +- "给我这个听记的媒体下载链接" + +### 添加/管理听记成员权限 +用户说"把某人加到这个听记中/共享听记给某人/给某人添加听记权限/把听记分享给同事/让某人也能看这个听记" → `permission add` +用户说"添加参会者/批量添加参会者/添加成员/批量添加成员/加参会人/加入成员" → `permission add`(**注意:听记模块没有独立的"添加参会者/成员"命令,统一走权限接口**) +用户说"移除某人的权限/取消共享/不让某人看这个听记了" → `permission remove` + +**自然语言示例:** +- "把张三加到这个听记中" +- "帮我把这个听记共享给李四" +- "让小王也能看这个听记" +- "给团队的同事们添加这个听记的查看权限" +- "把这几个听记共享给 UID 123456 的同事,设置为可编辑" +- "帮我把这个听记分享给参会的人" +- "给这个听记加个协作者" +- "把听记的编辑权限给我同事" +- "添加张三为这个听记的参会者" +- "帮我批量添加参会者:张三、李四、王五" +- "给这个听记添加几个成员" +- "批量添加成员到这个听记" +- "把张三加为参会人" +- "移除张三对这个听记的权限" +- "取消小王对这个听记的访问" +- "不让某人看这个听记了" + +**路由规则:** +- 用户提到"加/添加/共享/分享/让...看/让...访问"等增加权限语义 → `permission add` +- 用户提到"添加参会者/加参会者/批量添加参会者/添加成员/加成员/批量添加成员/加参会人/添加参会人"等成员语义 → `permission add`(听记没有独立的"添加成员"接口,**统一走权限接口**) +- 用户提到"移除/取消/删除/不让...看"等移除权限语义 → `permission remove` +- 如果用户未指定权限类型,默认使用 `--policy 4`(可查看/下载/编辑) +- 如果用户未提供 member-uids,需要先引导用户提供目标成员的钉钉 UID(可通过 `dws contact user search --query "姓名"` 查询) + +**典型执行链路:** +1. 用户说"把张三加到这个听记中" → AI 需获取张三的 UID +2. 调用 `dws contact user search --query "张三" --format json` 获取 UID +3. 调用 `dws minutes permission add --ids --member-uids --policy 4 --format json` + +### 听记标签/分组查询 +用户说"我的听记标签/听记分组/听记有哪些标签/有哪些分组" → `tag list` +用户说"查看某个标签下的听记/这个标签下有哪些听记/按标签查听记" → `tag query` + +**自然语言示例:** +- "我的听记有哪些标签" +- "帮我看看听记的分组列表" +- "查一下'周会'标签下有哪些听记" +- "按标签筛选听记" + +**路由规则:** +- 用户提到"标签列表/分组列表/有哪些标签/有哪些分组" → `tag list` +- 用户提到"按标签查/某个标签下的听记/标签筛选" → 先 `tag list` 获取 tagId,再 `tag query --tag-id ` +- 如果用户直接提供了 tagId → 直接 `tag query --tag-id ` +- 如果用户提供标签名称而非 tagId → 先 `tag list` 查找对应 tagId,再 `tag query` + +**典型执行链路:** +1. 用户说"帮我看看'周会'标签下的听记" → AI 先获取标签列表 +2. 调用 `dws minutes tag list --format json` → 从返回中按名称匹配找到 tagId +3. 调用 `dws minutes tag query --tag-id --format json` + +### 修改听记标题 +用户说"改听记标题/重命名听记/修改标题" → `update title` + +**自然语言示例:** +- "把这个听记的标题改成'Q2 复盘会议'" +- "帮我重命名一下这个听记" + +### URL 识别 +- **格式**: `https://shanji.dingtalk.com/app/transcribes/{taskUuid}` +- **示例**: `https://shanji.dingtalk.com/app/transcribes/76327569643231383535353939365f3436383537393431335f32` +用户传入听记 URL(如 `https://shanji.dingtalk.com/app/transcribes/xxx`),从 URL 提取 taskUuid,再执行对应的 get/update 操作 diff --git a/skills/multi/dingtalk-minutes/references/minutes-overview.md b/skills/multi/dingtalk-minutes/references/minutes-overview.md new file mode 100644 index 00000000..f1dc7ea7 --- /dev/null +++ b/skills/multi/dingtalk-minutes/references/minutes-overview.md @@ -0,0 +1,121 @@ +# 听记命令层级与路由 + +> 本文只保留命令导航和跨命令不变量。具体参数见 [minutes-commands.md](./minutes-commands.md),错误恢复见 [minutes-error.md](./minutes-error.md)。 + +## Scope 选择 + +`dws minutes list` 后必须带 scope: + +| Scope | 含义 | 何时使用 | +|---|---|---| +| `mine` | 仅自己创建或发起的听记 | 用户明确说“我创建的 / 我发起的 / 我录的” | +| `shared` | 仅他人共享给自己的听记 | 用户明确限定共享来源 | +| `all` | 当前用户可访问的全部听记(mine + shared) | 默认;用户说“我能看到的 / 可访问的 / 有权限的 / 所有听记” | + +不要使用裸 `dws minutes list`。按标题、关键词、参会人或时间定位听记时,默认用 `list all`,避免遗漏共享内容。 + +## 指令树 + +```text +dws minutes +├── list [--query] [--start] [--end] [--limit] [--cursor] +├── get +│ ├── info --id +│ ├── summary --id +│ ├── transcription --id [--cursor ] +│ ├── keywords --id +│ ├── todos --id +│ ├── audio --id +│ └── batch --ids +├── update +│ ├── title --id --title "..." +│ └── summary --id --content "..." +├── record +│ ├── start +│ ├── pause --id +│ ├── resume --id +│ └── stop --id +├── speaker +│ ├── replace --id --from "..." --to "..." +│ └── summary --ids +├── mind-graph --id +├── hot-word +├── replace-text --id --search "..." --replace "..." +├── upload +├── permission +└── tag +``` + +## 意图路由 + +| 用户意图 | 命令 | +|---|---| +| 列出、搜索、按时间找听记 | `minutes list all [--query] [--start] [--end]` | +| 查看会议讲了什么 | `minutes get summary` | +| 查看逐字稿、原话、沟通细节 | `minutes get transcription`,翻页至结束 | +| 查看关键词、待办或基础信息 | `minutes get keywords/todos/info` | +| 批量查看多篇基础信息 | `minutes get batch --ids ...` | +| 修改标题或纪要 | `minutes update title/summary` | +| 生成脑图 | `minutes mind-graph create` 后轮询 `status` | +| 替换文字或发言人 | `minutes replace-text` / `minutes speaker replace` | +| 本地音视频转听记 | `minutes upload create` → 上传 → `complete` | + +## taskUuid 与对象选择 + +1. 用户给 taskUuid 时直接使用;用户给听记 URL 时先按下节提取 taskUuid。 +2. 用户只给标题、关键词或时间时,先 `list all` 定位;禁止凭历史上下文或记忆填写 ID。 +3. 多候选时同时核对 `taskUuid`、`title`、`organizationName` 和时间。无法唯一确定时列出候选让用户确认。 +4. “最近一次某类会议”先按主题过滤,再在同主题候选中按时间取最新;不要只取全列表中时间最新的一条。 +5. “最长 / 最短”按返回的 `durationMicros` 数值比较,不按标题或自然语言印象判断。 +6. 锁定 taskUuid 后,后续 summary、keywords、transcription、update 必须复用同一个 ID。某字段为空时如实说明,不要悄悄切换到另一篇听记。 +7. 标题日期与创建日期可能不同。精确日期未命中时,先保留主题词并放宽时间窗,再列候选确认。 +8. 多篇详情优先使用 `get batch`;部分条目失败时保留成功结果并逐条说明失败原因,整体失败时再降级为 `get info`。 + +## 转写分页与忠实性 + +- 用户明确要“原文 / 逐字稿 / 原话 / 沟通细节”时,调用 `get transcription` 并持续传入返回的分页 token,直到 token 为空。 +- 用户只要摘要、列表、关键词或待办时,不要附带拉取完整转写。 +- 用户指定时间区间时,确认已拉取到目标时间段后再分析;未覆盖目标区间时继续翻页或明确说明数据不足。 +- 基于听记生成报告或统计时,只使用实际返回的数据。源数据没有责任人、行动项或数字时,不要补造。 +- 用户列出多个数据源时,分别读取每个来源;某来源失败时说明缺失,不用其他来源推断精确事实。 + +## URL → taskUuid + +| 输入形式 | 提取规则 | +|---|---| +| `.../app/transcribes/` | 取 `/transcribes/` 后、查询串前的路径段 | +| `.../meeting/minutes?taskUuid=` | 取 `taskUuid` 查询参数 | +| 带 `minutesId=` 的聊天链接 | 取 `minutesId` 查询参数 | +| 纯 taskUuid | 原样使用 | + +提取后把纯 taskUuid 传给 `--id`;不要把完整 URL 传给 `--id`,也不要要求用户手工提取。 + +## 常见错误写法 + +| 错误 | 正确 | +|---|---| +| `minutes info --id ...` | `minutes get info --id ...` | +| `minutes summary --id ...` | `minutes get summary --id ...` | +| `minutes detail --id ...` | 使用 `get info`、`get summary` 或 `get transcription` | +| `minutes list` | `minutes list all` 或显式 `mine/shared` | +| `minutes get transcription --url ` | 提取 taskUuid 后传 `--id` | +| `minutes upload create --json ...` | 使用 `--file-name` 与 `--file-size` | +| `minutes get transcription ... \| head` | 单独执行 dws 命令并解析 JSON | + +## 参数差异 + +| 语义 | minutes 参数 | 兼容参数 | +|---|---|---| +| 单页条数 | `--limit` | `--max` | +| 分页标识 | `--cursor` | `--next-token` | +| 听记 ID | `--id` | `--uuid` / `--task-uuid` | +| 起止时间 | `--start` / `--end` | 无 | + +不要把 calendar 的 `--start-time/--end-time` 或其他产品参数带入 minutes。 + +## 权限与上传 + +- 权限错误表示当前用户不能访问该听记,不要通过更换参数或命令绕过。告知用户联系创建者共享权限。 +- 批量查询部分无权限时,展示成功条目并列出失败 ID 与原因。 +- 上传固定按 `upload create` → 使用返回的上传地址上传文件 → `upload complete`。需要取消时使用创建阶段返回的 `sessionId`。 +- `--file-size` 单位是字节,使用真实文件大小,不做估算。 diff --git a/skills/multi/dingtalk-minutes/references/minutes-workflow.md b/skills/multi/dingtalk-minutes/references/minutes-workflow.md new file mode 100644 index 00000000..7b776d06 --- /dev/null +++ b/skills/multi/dingtalk-minutes/references/minutes-workflow.md @@ -0,0 +1,89 @@ +# 听记核心工作流与上下文 + +## 核心工作流 + +```bash +# 0. 发起听记(开始录音) +dws minutes record start --format json + +# 1. 查看我的听记列表 — 提取 taskUuid +dws minutes list mine --format json +dws minutes list mine --max 10 --next-token --format json +dws minutes list mine --query "周会" --format json + +# 1b. 查看共享给我的听记 +dws minutes list shared --max 20 --format json +dws minutes list shared --query "日报" --format json + +# 1c. 查看我有权限访问的所有听记(支持关键字和时间范围筛选) +dws minutes list all --format json +dws minutes list all --query "周会" --start "2026-03-01T00:00:00+08:00" --end "2026-03-20T23:59:59+08:00" --format json + +# 2. 获取 AI 摘要 +dws minutes get summary --id --format json + +# 3. 查看完整转写原文(拉完后默认按时间线返回,AI 必须主动追问"是否按发言人聚类") +dws minutes get transcription --id --format json +# 3a. 用户确认聚类 → AI 在本地按 speakerNick 分组并提取核心要点(无需新调用 dws) +# 3b. 用户提供"某某人讲了 XX" → AI 模糊匹配关键词后,引导确认替换发言人 +dws minutes speaker replace --id --from "发言人1" --to "李总" --format json + +# 4. 提取待办事项 +dws minutes get todos --id --format json + +# 4b. 获取音频/视频地址(用于下载或播放原始媒体文件) +dws minutes get audio --id --format json + +# 5. 修改标题 +dws minutes update title --id --title "新标题" --format json + +# 6. 更新纪要内容 +dws minutes update summary --id --content "新的纪要内容" --format json + +# 7. 录音控制(基于 start 返回的 taskUuid) +dws minutes record pause --id --format json +dws minutes record resume --id --format json +dws minutes record stop --id --format json + +# 8. 思维导图 +dws minutes mind-graph create --id --format json +dws minutes mind-graph status --id --format json + +# 9. 替换发言人 +dws minutes speaker replace --id --from "张三" --to "李四" --format json + +# 9b. 发言人段落总结(异步:create 触发 → 延迟 5s → 轮询 get,最多 20 次) +dws minutes speaker summary create --ids --format json +# 等待至少 5 秒... +dws minutes speaker summary get --ids --format json +# 若返回为空,继续等待 5s 后重试,最多 20 次 + +# 10. 添加个人热词 +dws minutes hot-word add --words "OKR,钉钉,Copilot" --format json + +# 11. 查找替换听记文字 +dws minutes replace-text --id --search "旧文字" --replace "新文字" --format json + +# 12. 文件上传转听记(三步流程) +# 12a. 创建上传会话,获取预签名 URL 和 sessionId +dws minutes upload create --file-name "meeting.mp4" --file-size 102400 --format json +# 12b. 用 HTTP PUT 上传文件到预签名 URL(不带 HEADER) +curl -X PUT "" -T "/path/to/meeting.mp4" +# 12c. 通知服务端上传完成,创建听记 +dws minutes upload complete --session-id --format json +# 12d.(可选)取消上传会话 +dws minutes upload cancel --session-id --format json +``` + +## 上下文传递表 + +| 操作 | 从返回中提取 | 用于 | +|------|-------------|------| +| `list mine` | `taskUuid`、`nextToken` | get/update 的 --id;翻页时 --next-token | +| `list shared` | `taskUuid`、`nextToken` | get/update 的 --id;翻页时 --next-token | +| `list all` | `taskUuid`、`nextToken` | get/update 的 --id;翻页时 --next-token | +| `get batch` | 各听记 `taskUuid` | 进一步查询详情 | +| `get audio` | 音频/视频 OSS 地址 | 用 HTTP GET 下载录音文件 / 在浏览器播放 | +| `record start` | `taskUuid`/`uuid` | record pause/resume/stop 的 --id | +| `upload create` | `sessionId`、`presignedUrl` | HTTP PUT 上传文件;upload complete/cancel 的 --session-id | +| `mind-graph create` | 任务状态 | mind-graph status 轮询 | diff --git a/skills/multi/dingtalk-minutes/references/minutes.md b/skills/multi/dingtalk-minutes/references/minutes.md index cc4c5a8c..3eed6075 100644 --- a/skills/multi/dingtalk-minutes/references/minutes.md +++ b/skills/multi/dingtalk-minutes/references/minutes.md @@ -1,2394 +1,11 @@ -# AI听记 (minutes) 命令参考 +# AI 听记 (minutes) 命令参考 -## 命令层级结构(防混淆,必须先读) +> 渐进式兼容入口:新任务优先从 [听记能力索引](./minutes-index.md) 选择专题。 -minutes 模块的命令是**两级或三级结构**,不同层级之间不能混用、跳级或遗漏: - -> **默认 scope 规则**:`dws minutes list` 后**必须**跟 scope 子命令。若用户未明确指定查询范围,**一律默认补 `all`**(查询我可访问的所有听记 = 我创建的 + 他人共享给我的,覆盖面最广)。 -> -> **`list` 不带 scope 的陷阱(0519 P2 Golden Case 提炼)**: -> - 裸 `dws minutes list`(不跟 mine/shared/all)**不会返回任何听记数据**——它只打印该命令的帮助/用法信息(Usage、Available Commands: all/mine/shared、Flags),等于什么都没查到 -> - 必须带 scope 子命令(`dws minutes list all` / `list mine` / `list shared`)才会真正返回听记列表 -> - **AI 严禁使用裸 `dws minutes list`**,必须始终带 scope 子命令 -> - 如果 LLM 不确定用哪个 scope,**一律用 `all`** -> -> **scope 语义辨析(高频误判场景)**: -> - `mine` = **仅**我自己创建/发起的听记(范围最窄) -> - `shared` = **仅**他人共享给我的听记(不含我自己创建的) -> - `all` = 我可访问的**所有**听记(mine + shared 的并集,范围最广) -> -> **关键区分**:用户说"我可访问的"/"我能看到的"/"我有权限的"/"所有听记" → 选 `all`(不是 `mine`!);用户说"我自己创建的"/"我发起的"/"我录的" → 才选 `mine`。 - -``` -dws minutes -├── list # 查询听记列表(list 后必须跟 mine / shared / all) -│ ├── mine [--query] [--start] [--end] [--max] [--next-token] # 仅查询我自己创建/发起的听记(不含他人共享给我的) -│ ├── shared [--query] [--start] [--end] [--max] [--next-token] # 仅查询他人共享给我的听记(不含我自己创建的) -│ └── all [--query] [--start] [--end] [--max] [--next-token] # 查询我可访问的所有听记(= mine ∪ shared,覆盖面最广,默认 scope) -├── get # 获取听记详情(get 后必须跟子命令名) -│ ├── info --id # 获取听记基础信息(标题、时长、开始时间、taskUuid、访问链接;不含参会人) -│ ├── summary --id # 获取听记 AI 摘要(结构化纪要) -│ ├── transcription --id # 获取听记语音转写原文(逐字稿,支持分页) -│ ├── keywords --id # 获取听记关键词 -│ ├── todos --id # 获取听记中的待办事项(Action Items) -│ ├── audio --id # 获取听记音频/视频下载地址 -│ └── batch --ids # 批量获取多篇听记的基础信息 -├── update # 修改听记内容(update 后必须跟子命令名) -│ ├── title --id --title "..." # 修改听记标题 -│ └── summary --id --content "..." # 修改听记摘要内容 -├── record # 录音控制(record 后必须跟子命令名) -│ ├── start # 开始录音,发起新听记 -│ ├── pause --id # 暂停录音 -│ ├── resume --id # 恢复录音 -│ └── stop --id # 停止录音,结束听记 -├── speaker # 发言人管理 -│ ├── replace --id --from "..." --to "..." # 替换发言人名称(如"发言人1"→"张三") -│ └── summary # 按发言人生成/获取摘要 -│ ├── create --ids # 创建按发言人维度的摘要 -│ └── get --ids # 获取按发言人维度的摘要 -├── mind-graph # 脑图管理 -│ ├── create --id # 根据听记内容生成脑图 -│ └── status --id # 查询脑图生成状态 -├── hot-word # 个人热词管理 -│ ├── add --words "..." # 添加热词(提升语音识别准确率) -│ └── list # 查询我的热词列表 -├── replace-text --id --search "..." --replace "..." # 替换转写文本中的错误文字 -├── upload # 音频上传(上传本地音频文件生成听记) -│ ├── create --file-name "..." --file-size # 创建上传会话,获取上传地址 -│ ├── complete --session-id # 确认上传完成 -│ └── cancel --session-id # 取消上传会话 -└── permission # 权限管理(添加/移除听记成员权限) - ├── add --ids --member-uids --policy <0-4> [--cover] [--sub-resources "..."] # 添加成员权限 - └── remove --ids --member-uids # 移除成员权限 -``` - -**高频错误命令 vs 正确命令对照(真实 badcase 提炼):** - -| 错误写法 | 错在哪 | 正确写法 | -|------------|--------|------------| -| `dws minutes info --id ` | `info` 不是顶层子命令,应在 `get` 下 | `dws minutes get info --id ` | -| `dws minutes summary --id ` | `summary` 不是顶层子命令,应在 `get` 下 | `dws minutes get summary --id ` | -| `dws minutes transcription --id ` | `transcription` 不是顶层子命令,应在 `get` 下 | `dws minutes get transcription --id ` | -| `dws minutes get --uuid ` | `get` 后缺少子命令(info/summary/transcription 等) | `dws minutes get summary --id ` | -| `dws minutes get --task-uuid ` | 同上,`get` 后缺子命令。注意:`--task-uuid` 在子命令层是合法别名,但 `get` 层级不认识它 | `dws minutes get info --id ` | -| `dws minutes detail --id ` | `detail` 不是合法子命令——**不存在此命令**。LLM 高频幻觉 | `dws minutes get info --id ` | -| `dws minutes list --start "2026-04-01"` | `list` 后缺少 scope(mine/shared/all),**缺省时默认补 `all`** | `dws minutes list all --start "2026-04-01"` | -| `dws minutes list all --start-time "..."` | 参数名是 `--start` 不是 `--start-time` | `dws minutes list all --start "2026-04-01"` | -| `dws minutes list all --end-time "..."` | 参数名是 `--end` 不是 `--end-time` | `dws minutes list all --end "2026-04-30"` | -| `dws minutes list --page-size 10` | `--page-size` 不存在,分页用 `--max`;且 `list` 后缺 scope | `dws minutes list mine --max 10` | -| `dws minutes list --date-range 2026-05-04 2026-05-10` | `--date-range` 不存在,时间范围用 `--start` + `--end` 两个参数;且 `list` 后缺 scope | `dws minutes list mine --start "2026-05-04T00:00:00+08:00" --end "2026-05-10T23:59:59+08:00"` | -| `dws minutes list mine --limit 10` | 不报错——`--limit` 已注册为 `--max` 的合法别名,两者完全等价(`--limit 3` 与 `--max 3` 返回相同条数) | `dws minutes list mine --max 10`(或 `--limit 10`,二选一即可) | -| `dws minutes upload create --json '{"fileName":...}'` | `--json` 不存在,cli 不接受 JSON 作为输入格式 | `dws minutes upload create --file-name "xxx.mp3" --file-size 61565431` | -| `dws minutes upload create -f json '{"fileName":...}'` | `-f json` / `--format json` 是**输出格式**控制,不是输入参数 | `dws minutes upload create --file-name "xxx.mp3" --file-size 61565431 --format json` | -| `dws minutes get transcription --id \| head -c 2000` | Windows 沙箱无 `head` 命令,**严禁使用 shell 管道截断** | `dws minutes get transcription --id --format json`(由 AI 在内存中截断处理) | -| `dws minutes get summary --id "https://shanji.dingtalk.com/app/transcribes/xxx"` | `--id` 只接受 taskUuid(hex 字符串),**不接受完整 URL**。AI 必须自动提取,不要让用户手动抠 | AI 自动从 URL 提取 taskUuid,再 `dws minutes get summary --id `(详见「URL → taskUuid 自动提取规则」) | -| `dws minutes get transcription --id "https://shanji.dingtalk.com/meeting/minutes?taskUuid=xxx"` | 同上,`--id` 不接受 URL,且这是 `?taskUuid=` 格式的 URL | AI 自动提取 `taskUuid=` 后的值,再 `dws minutes get transcription --id --format json` | -| `dws minutes get transcription --id ` 只调用一次就"总结整篇听记" | **单次调用最多返回 50 段**,不翻页就只能看到前 1/3~1/8 的内容 | **必须自动循环翻页**:检查返回中的 `nextToken`,非空则继续调用 `--next-token `,直到拉完(详见「转写接口分页机制」) | -| `dws minutes get summary --id A & dws minutes get summary --id B & wait` | Windows cmd 下 `&` 是命令分隔符不是 background,`wait` 不存在 | 逐条串行调用,或让 AI 在内存中合并结果;**严禁使用 shell 并行/管道/重定向** | -| `dws minutes get transcription --id --next-token ` 报 unknown flag | **不会报错**——`--next-token` 是 `get transcription` 的合法参数(见命令总览) | 确保写法正确:`dws minutes get transcription --id --next-token --format json` | -| `dws minutes get transcription --id --page-token ` | `--page-token` 不存在,正确参数名是 `--next-token` | `dws minutes get transcription --id --next-token ` | -| `dws minutes transcribe --url <听记url>` | `transcribe` 不是合法子命令,`--url` 参数也不存在。LLM 凭印象编造 | 先从 URL 提取 taskUuid(见「URL → taskUuid 自动提取规则」),再 `dws minutes get transcription --id --format json` | -| `dws minutes get transcription --url <听记url>` | `--url` 参数不存在,`--id` 只接受纯 taskUuid | 同上:从 URL 提取 taskUuid 后用 `--id` | -| `dws minutes summary --uuid ` | `summary` 不是顶层子命令(应在 `get` 下),且顶层不识别 `--uuid`。**0519 高频错误** | `dws minutes get summary --id ` | -| `dws minutes list`(不跟 scope) | `list` 后**必须**跟 scope(mine/shared/all)。裸 `list` 只打印帮助信息,不返回任何听记数据 | `dws minutes list all`(默认 scope) | -| `dws report inbox --format json`(无时间窗口) | `inbox` 是兼容入口,等价于 `inbox list`;仍必须带 `--start` / `--end` | `dws report inbox list --start "2026-05-06T00:00:00+08:00" --end "2026-05-13T23:59:59+08:00" --format json` | -| `dws report inbox list --format json`(不带 --start) | inbox list **必须**带 `--start` / `--end` 参数,否则报 "flag --start is required" | `dws report inbox list --start "2026-05-06T00:00:00+08:00" --end "2026-05-13T23:59:59+08:00" --format json` | - -> **参数别名说明**:`--id`、`--uuid`、`--task-uuid` 三者等价,均可使用。推荐统一用 `--id`,但 `--uuid` 和 `--task-uuid` 也是合法别名,不会报错。 - -**跨产品参数规约映射(dws-cli-param-spec 对齐):** - -> minutes 模块参数与跨产品规约 Primary 的映射关系: -> -> | 语义 | 规约 Primary | minutes 当前 CLI 实际参数 | alias 注册状态 | 说明 | -> |------|-------------|------------------------|--------------|------| -> | 单页大小 (Group 13) | `--limit` | `--max`(`--limit` 为合法别名) | `--limit` 已注册 | minutes 推荐 `--max`,但 `--limit` 也可用,两者等价 | -> | 续页标识 (Group 14) | `--cursor` | `--next-token` | `--next-token` 在 alias 池中 | minutes 用 `--next-token`,合法 alias | -> | 搜索关键词 (Group 11) | `--query` | `--query` | 已对齐 | 无差异 | -> | 起始时间 (Group 15) | `--start` | `--start` | 已对齐 | 无差异 | -> | 结束时间 (Group 16) | `--end` | `--end` | 已对齐 | 无差异 | -> -> **跨模块参数名差异速查(防止混用):** -> - 分页大小:minutes 推荐 `--max`(`--limit` 也是合法别名,等价),aitable/calendar/chat/drive 用 `--limit` -> - 续页标识:minutes 用 `--next-token`,aitable/drive/doc 用 `--cursor` -> - 起止时间:minutes/report 用 `--start`/`--end`,calendar 用 `--start-time`/`--end-time`(跨模块最高频错误) - -**铁律:禁止编造 taskUuid(0512 P0 Golden Case 提炼)** - -用户未直接提供 taskUuid / URL 时,**必须**先调用 `dws minutes list mine`(或 `list all`)获取列表,从返回结果中按标题/参会人/时间匹配选出正确条目,再用其 taskUuid 调用后续命令。**绝不允许凭空编造 hex 字符串作为 --id 的值。** - -典型错误链路: -``` -用户: "帮我看一下上次和 Tony 开会的听记摘要" -错误: LLM 直接编造: dws minutes get summary --id "7632756964..." -- 这个 id 是捏造的 -正确: 先 dws minutes list mine → 从结果按标题/参会人匹配 → 再 get summary --id <真实id> -``` - -**意图→命令决策表(0512 P0 Golden Case 提炼):** - -| 用户意图关键词 | 对应命令 | 说明 | -|--------------|---------|------| -| 总结、摘要、重点、概述、主要内容、按模板整理 | `get summary` | 返回结构化摘要 | -| 逐字稿、全文、原文、完整记录、转写、录音文字 | `get transcription` | 返回完整转写原文 | -| "帮我看/总结某次会议"(未给 id) | 先 `list all`(默认) | 按标题/时间匹配后再 get | -| "我可访问的听记"/"我能看到的"/"我有权限的"/"所有听记" | `list all` | **不是 `list mine`!** `all` = 我可访问的全部(mine + shared),`mine` 仅限我自己创建的 | -| "我的听记"(模糊表述) | `list all` | "我的"通常指用户可见的全部听记,默认走 `all`;仅当明确说"我创建/发起的"时才 `mine` | -| "我自己创建的听记"/"我发起的听记"/"我录的听记" | `list mine` | 明确限定创建者是自己时才走 `mine` | -| "上上周/两周前的会议" | `list all --start --end` | 需正确计算时间范围 | -| 添加参会者、批量添加参会者、添加成员、批量添加成员、加参会人 | `permission add` | 听记无独立"添加成员"接口,统一走权限接口 | - -**多步流程错误传播规则(0512/0514 P0 Golden Case 提炼):** - -> 当执行 list → get summary/transcription → 套模板/写文档 等多步流程时: -> 1. 任何一步 dws 命令返回错误,**必须立即告知用户**卡在哪一步、具体错误信息 -> 2. **严禁**跳过失败步骤假装成功 -> 3. **严禁**用虚构数据填补失败步骤的空缺 -> 4. 如果是权限问题 (NoPermission),建议用户检查听记是否为自己创建或已被共享 -> 5. 最终交付物中需注明哪些数据成功获取、哪些被权限/参数阻断 -> 6. **严禁多步流程中途放弃**(0514 P0 新增):前置步骤(如 `contact user get-self` 获取 userId)成功后,**必须**继续执行后续步骤(如 `calendar event list`、`minutes list mine`)。不能只完成前置步骤就停下来,回复"我打算帮你查…"却不实际执行 - -**诉求偏移防护规则(0514 P1 Golden Case 提炼):** - -> 用户明确要求"基于听记内容做 XX"(如生成会议纪要、汇报材料、周报)时: -> 1. **必须先实际调用** `dws minutes list` → `get summary/transcription` 获取真实数据 -> 2. **严禁**跳过数据获取步骤直接"创建 skill"或"生成模板"——用户要的是**基于真实听记数据**的输出,不是空壳工具 -> 3. 如果数据获取失败,应告知用户具体原因,而非转向其他不相关的操作 -> -> **典型错误链路(0514 P1 bug 复现):** -> ``` -> 用户: "基于钉钉听记内容重新生成 Word 格式会议纪要" -> [错误] 模型创建了一个 skill 模板,但从未调用 dws minutes get summary/transcription 获取真实数据 -> [正确] 先 dws minutes list mine → 从结果选匹配条目 → get summary --id → 基于返回数据生成纪要 -> ``` - -**跨模块组合注意事项(周报生成等场景,0512/0513/0514 P0 Golden Case 提炼):** - -> - 周报生成等场景需并行拉取 minutes / report / calendar / todo / doc 多类数据 -> - **各模块参数名独立**,不可互相套用(如 doc 的参数不适用于 minutes) -> - **跨模块参数名交叉污染是 0514 最高频错误**(3/7 个 case 涉及):切换模块时必须重新确认该模块的参数名,不要从上一个模块"顺手带过来"。典型错误对:`--start-time`(calendar) vs `--start`(minutes/report)、`--task-uuid`(无) vs `--id`(minutes) -> - **禁止**使用管道命令 `| jq`,Windows/macOS 客户端不保证 jq 可用,应由 LLM 直接解析 JSON -> - `report inbox list` **必须**带 `--start` / `--end` 参数(格式 `2026-05-04T00:00:00+08:00`),否则报错;`report outbox list` 不传时间窗口默认最近 20 天 -> - `dws report inbox` 是兼容入口,仍可执行但已 deprecated;新计划必须写 `dws report inbox list --start ...` -> - `report inbox list` 不传 `--start` 会直接报错 "flag --start is required",**不要让用户手动补**——AI 应自动推断最近 7 天范围填入 -> - 旧命令 `report list / inbox / sent / created / detail / create / stats / template detail` 仍可执行但已 deprecated,新计划一律使用 `inbox list / outbox list / entry get / entry submit / entry stats / template get` -> - doc create 参数名是 `--content`(非 `--markdown`) - -**钉钉听记 URL → taskUuid 自动提取规则(0513/0514 P0 Golden Case 提炼):** - -> **`--id` 参数只接受纯 taskUuid(hex 字符串),绝不接受完整 URL。** 但用户几乎总是直接粘贴完整 URL 过来,AI **必须自动完成 URL → taskUuid 的提取**,对用户完全透明——用户不需要知道"要抠出 hex 字符串"。 -> -> **严禁**: -> - 直接把完整 URL 传入 `--id`(会报错) -> - 要求用户自己从 URL 中手动提取 taskUuid(用户体验极差,这是 AI 应自动完成的工作) -> - 把 URL 中的 query parameter(如 `?taskUuid=xxx`)和 path segment(如 `/transcribes/xxx`)搞混 -> -> **已知的钉钉听记 URL 格式全表(AI 必须全部识别):** -> -> | URL 格式 | taskUuid 提取方式 | 示例 | -> |---------|-----------------|------| -> | `https://shanji.dingtalk.com/app/transcribes/` | 取 `/transcribes/` 后面的**整段路径** | `.../transcribes/7632756964323839...5f32` → `7632756964323839...5f32` | -> | `https://shanji.dingtalk.com/meeting/minutes?taskUuid=` | 取 `taskUuid=` 后面的 query parameter 值 | `...?taskUuid=7632756964323839...5f32` → `7632756964323839...5f32` | -> | `https://shanji.dingtalk.com/app/transcribes/?xxx=yyy` | 取 `/transcribes/` 和 `?` 之间的部分(忽略 query string) | `.../transcribes/763275...5f32?from=share` → `763275...5f32` | -> | 纯 hex 字符串(无 URL 前缀) | 直接使用,无需提取 | `7632756964323839...5f32` → 直接传 `--id` | -> -> **提取伪代码(AI 必须内化此逻辑):** -> ``` -> input = 用户提供的字符串(可能是 URL,也可能是纯 uuid) -> -> if input 包含 "/transcribes/": -> taskUuid = input 中 "/transcribes/" 之后、"?" 之前(如有)的部分 -> elif input 包含 "taskUuid=": -> taskUuid = input 中 "taskUuid=" 之后、"&" 之前(如有)的部分 -> elif input 匹配纯 hex 字符串模式 (只含 0-9a-f 且长度 > 20): -> taskUuid = input # 已经是纯 uuid -> else: -> 告知用户格式不识别,请提供听记链接或 taskUuid -> -> # 然后使用: dws minutes get summary --id --format json -> ``` -> -> **典型错误链路(0514 P0 bug 复现):** -> ``` -> 用户: "帮我看看这个听记 https://shanji.dingtalk.com/app/transcribes/7632756964323839..." -> -> [错误 1] dws minutes get summary --id "https://shanji.dingtalk.com/app/transcribes/763..." → 报错 invalid id -> [错误 2] "请你把 URL 中的 taskUuid 提取出来给我" → 不应该让用户手动做 -> [错误 3] AI 把整个 URL decode 当作 taskUuid → 格式不对 -> [正确] AI 自动提取 → dws minutes get summary --id "7632756964323839..." --format json -> ``` -> -> **多链接批量场景**:用户一次性粘贴多个 URL 时,逐一提取 taskUuid,按顺序串行调用对应的 get 命令。**严禁使用 shell `&` 并行或 `|` 管道**。 - -**权限错误处理策略(0513 P0 Golden Case 提炼):** - -> `get info` / `get summary` / `get transcription` / `get batch` 访问他人发起的听记时,可能报以下权限错误: -> -> | 错误信息 | 含义 | AI 应如何处理 | -> |---------|------|-------------| -> | `[AUTH_PERMISSION_DENIED] Permission denied` | 当前用户对该听记无权限(不是创建者,且未被共享) | 告知用户:"该听记由他人创建且未共享给你。请联系听记创建者在钉钉听记详情页将你加为共享者,然后再次尝试。" | -> | `[MCP_TOOL_ERROR] B_PERMISSION_NoPermission` | 同上,后端 dingOpenErrcode=14000011 | 同上处理 | -> | `[UNCLASSIFIED] user is not minutes creator` | batch 接口单条权限失败 | 告知用户哪几条 id 是自己的(可正常获取)、哪几条无权限,**不要整批放弃** | -> -> **关键原则**: -> - 权限报错 ≠ 命令写法有误——**不要**因为权限失败就去改参数名或换命令路径 -> - batch 调用中如果部分成功部分权限失败,应**展示成功的结果** + **列出失败的 id 和原因** -> - `list shared` 返回的听记是**已被共享给我的**,这些 id 后续调用 get 不应报权限错误 -> - `list all` 返回的所有 id 都是**当前有权限的**,可放心调用 - -**Shell 命令安全规则(0513 P0 Golden Case 强化):** - -> dws cli 运行在用户本地设备(Windows/macOS/Linux),**严禁使用任何 shell 特性**: -> -> | 禁止写法 | 原因 | 正确替代 | -> |---------|------|----------| -> | `dws ... \| head -20` | Windows cmd 无 `head` | 由 AI 在内存中截断 | -> | `dws ... \| grep "xxx"` | Windows cmd 无 `grep` | 由 AI 在返回 JSON 中筛选 | -> | `dws ... 2>&1 \| head` | 混合重定向+管道 | 直接读 dws 输出 | -> | `dws A & dws B & wait` | cmd 下 `&` 是分隔符不是 background | 逐条串行执行 | -> | `dws ... > output.txt` | 不确定用户 shell 是否支持 | 由 AI 直接处理返回结果 | -> -> **铁律:所有 dws 命令必须单条、独立、无管道地执行,AI 自行处理输出内容。** - -**Upload 模块错误诊断(0513 P0 Golden Case 提炼):** - -> `dws minutes upload create` 失败时的常见原因与处理: -> -> | 错误表现 | 真实原因 | 处理方式 | -> |---------|---------|---------| -> | `business error: success=false`(无详细信息) | 默认输出不含详细错误,需检查是否有并发会话 | 告知用户:"上传失败,可能原因包括:① 已有一个进行中的上传会话未完成/取消;② 文件名含特殊字符;③ 文件格式不支持。" | -> | `concurrent_limit: user already has an active upload session` | 前一次上传会话未结束(未 complete 也未 cancel) | 需要先 `dws minutes upload cancel --session-id ` 取消上一个会话。若 session-id 未知,建议用户在钉钉客户端手动取消或等待超时 | -> | `missing required flag(s): --session-id` | cancel 命令必须传 session-id | 从之前 upload create 的返回中提取 session-id;若丢失则暂无法取消 | -> -> **上传流程三步走**(AI 必须按此顺序执行,不可跳步): -> 1. `dws minutes upload create --file-name "xxx.mp3" --file-size <字节数> --format json` → 获取 `sessionId` + `presignedUrl` -> 2. 将文件通过 `presignedUrl` 直传(由客户端侧完成,AI 不介入) -> 3. `dws minutes upload complete --session-id --format json` → 确认上传完成 -> -> **注意**:`--file-size` 单位是**字节**(不是 KB/MB),AI 不要帮用户估算大小,应让用户确认文件实际字节数。 - -## 指令树 - -``` -dws minutes -├── list # 二级:list 后必须跟 mine / shared / all -│ ├── mine [--query] [--start] [--end] [--max] [--next-token] -│ ├── shared [--query] [--start] [--end] [--max] [--next-token] -│ └── all [--query] [--start] [--end] [--max] [--next-token] -├── get # 二级:get 后必须跟子命令名 -│ ├── info --id -│ ├── summary --id -│ ├── transcription --id -│ ├── keywords --id -│ ├── todos --id -│ ├── audio --id -│ └── batch --ids -├── update # 二级:update 后必须跟子命令名 -│ ├── title --id --title "..." -│ └── summary --id --content "..." -├── record # 二级:record 后必须跟子命令名 -│ ├── start -│ ├── pause --id -│ ├── resume --id -│ └── stop --id -├── speaker -│ ├── replace --id --from "..." --to "..." -│ └── summary -│ ├── create --ids -│ └── get --ids -├── mind-graph -│ ├── create --id -│ └── status --id -├── hot-word -│ ├── add --words "..." -│ └── list -├── replace-text --id --search "..." --replace "..." -├── upload -│ ├── create --file-name "..." --file-size -│ ├── complete --session-id -│ └── cancel --session-id -└── permission - ├── add --ids --member-uids --policy <0-4> [--cover] [--sub-resources "..."] - └── remove --ids --member-uids -``` - -## 命令总览 - -### 查询我创建的听记列表 -``` -Usage: - dws minutes list mine [flags] -Example: - dws minutes list mine - dws minutes list mine --max 10 - dws minutes list mine --max 10 --next-token - dws minutes list mine --query "周会" -Flags: - --max float 查询的听记篇数 (默认 10) - --next-token string 分页 token (首页留空,后续填写前次返回的 nextToken) - --query string 关键字筛选 (可选) - --start string 开始时间 ISO-8601 (可选) - --end string 结束时间 ISO-8601 (可选) -``` - -查询我创建的听记列表,支持 `--max` 和 `--next-token` 分页,支持按关键字和时间范围筛选。 - -### 查询他人共享给我的听记列表 -``` -Usage: - dws minutes list shared [flags] -Example: - dws minutes list shared - dws minutes list shared --max 20 - dws minutes list shared --max 5 --next-token -Flags: - --max float 查询的听记篇数 (默认 10) - --next-token string 分页 token (首页留空,后续填写前次返回的 nextToken) - --query string 关键字筛选 (可选) - --start string 开始时间 ISO-8601 (可选) - --end string 结束时间 ISO-8601 (可选) -``` - -查询他人共享给我的听记列表,支持 `--max` 和 `--next-token` 分页,支持按关键字和时间范围筛选。 - -### 查询我有权限访问的所有听记列表 -``` -Usage: - dws minutes list all [flags] -Example: - dws minutes list all - dws minutes list all --max 20 - dws minutes list all --query "周会" --max 20 - dws minutes list all --start "2026-03-01T00:00:00+08:00" --end "2026-03-20T23:59:59+08:00" - dws minutes list all --max 10 --next-token -Flags: - --end string 结束时间 ISO-8601 (可选) - --query string 关键字筛选 (可选) - --max float 查询的听记篇数 (默认 10) - --next-token string 分页 token (首页留空,后续填写前次返回的 nextToken) - --start string 开始时间 ISO-8601 (可选) -``` - -查询我有权限访问的所有听记列表(包括我创建的、他人共享给我的等所有有权限的听记)。支持按关键字和时间范围筛选。时间范围和关键字为可选参数,不传则返回所有有权限的听记。支持使用 `--max` 和 `--next-token` 进行分页查询。 - -### 获取听记基础信息 -``` -Usage: - dws minutes get info [flags] -Example: - dws minutes get info --id -Flags: - --id string 听记 taskUuid (必填),取值逻辑参考 ## 注意事项 -``` - -返回字段(result 内): `duration`(时长, 秒)、`startTime`(开始时间, 毫秒时间戳)、`endTime`(结束时间, 仅较长会议返回, 短录音无此字段)、`taskUuid`(听记 ID)、`title`(听记标题)、`url`(听记访问链接)。注意: **不返回创建人(creator)字段**。 - -**发言人列表输出规范(查询详情后必须执行)**: - -调用 `get info` 和 `get transcription`(首页即可)后,**必须**从返回数据中提取并展示发言人列表: - -1. **提取发言人**:从转写数据的 `speakerName` / `speakerId` 字段收集所有不同的发言人 -2. **分类展示**: - - **已识别**:声纹已标注真实姓名的发言人(直接显示姓名) - - **未识别**:仅有匿名编号的发言人(显示为「发言人 1」「发言人 2」等) -3. **引导逻辑(存在未识别发言人时)**: - - 展示发言人列表后,**必须**向用户提供以下选项: - - > 本次听记共有 M 位发言人,其中 N 位尚未识别身份: - > - > | # | 当前标注 | 发言占比 | - > |---|---------|---------| - > | 1 | 张三(已识别) | 35% | - > | 2 | 发言人 1(未识别) | 30% | - > | 3 | 发言人 2(未识别) | 20% | - > | 4 | 李四(已识别) | 15% | - > - > 你可以: - > 1. **智能匹配** — 我来帮你推断未识别的发言人身份(基于通讯录、聊天记录等) - > 2. **提供日程/参会人** — 告诉我这次会议对应的日程链接或参会人名单,我用成员列表来匹配 - > 3. **手动设置** — 直接告诉我对应关系,如「发言人1:王五, 发言人2:赵六」 - > 4. **跳过** — 暂不处理发言人识别 - - 根据用户选择分别处理: - - 选 1 → 进入 `minutes-speaker-summarize` recipe 的智能推断流程(见 [10-minutes-speaker-match.md](../best_practices/10-minutes-speaker-match.md)) - - 选 2 → 请用户提供日程链接或参会人姓名列表: - - 有日程链接 → 从链接提取 eventId → `dws calendar participant list --event ` 获取参与人 → 与转写发言人数量/顺序对照匹配 → 展示匹配结果请用户确认 → 确认后逐条执行 `speaker replace` - - 有参会人名单(用户直接列出姓名)→ 与转写中匿名发言人做数量对照 → 结合发言内容/角色推断匹配 → 展示匹配结果请用户确认 → 确认后逐条执行 `speaker replace` - - 选 3 → 用户提供映射关系(如「发言人1:张三」格式)→ 按「发言人映射模式识别」流程执行(见下方"修改说话人"章节) - - 选 4 → 正常结束,不做发言人处理 - -4. **引导逻辑(所有发言人均已识别时)**: - - > 所有发言人均已识别。是否需要查看某位发言人的详细发言总结?请输入姓名。 - - 用户选定 → 进入 `minutes-speaker-summarize` recipe;用户不需要 → 正常结束。 - -> **禁止**:跳过发言人列表直接输出摘要;将匿名编号的内部格式(Speaker_X)暴露给用户;未识别发言人时不给任何引导就结束。 - -### 获取听记 AI 摘要 -``` -Usage: - dws minutes get summary [flags] -Example: - dws minutes get summary --id -Flags: - --id string 听记 taskUuid (必填),取值逻辑参考 ## 注意事项 -``` - -返回 Markdown 格式摘要,涵盖会议主题、核心结论、关键讨论点等 - -### 获取听记关键字列表 -``` -Usage: - dws minutes get keywords [flags] -Example: - dws minutes get keywords --id -Flags: - --id string 听记 taskUuid (必填),取值逻辑参考 ## 注意事项 -``` - -### 获取听记语音转写原文 -``` -Usage: - dws minutes get transcription [flags] -Example: - dws minutes get transcription --id - dws minutes get transcription --id --direction 1 - dws minutes get transcription --id --next-token --format json -Flags: - --direction string 排序方向: 0=正序, 1=倒序 (默认 0) - --id string 听记 taskUuid (必填),取值逻辑参考 ## 注意事项 - --next-token string 下一页的token 首次查询可空 后续查询需填写前次请求返回的nextToken -``` - -每条记录包含: 发言人信息、转写文本、对应时间戳 - -**关键:转写接口每次最多返回 50 段,必须自动翻页才能拿到完整原文!** - -> **这是最高频的 silent failure:** `get transcription` 单次调用**最多只返回 50 段**。一篇 30 分钟会议的转写通常有 150-400 段,如果不翻页就只能看到前 1/3 甚至 1/8 的内容。 -> -> **分页机制(必须掌握):** -> - 首次调用:`dws minutes get transcription --id --format json`(不传 `--next-token`) -> - 检查返回 JSON 中是否包含 `nextToken` 字段(非空字符串) -> - 如果有 `nextToken`:**必须立即发起下一次调用**,传入 `--next-token <上次返回的nextToken值>` -> - 循环直到返回中**不再包含 `nextToken`**(或 `nextToken` 为空),表示所有段落已拉取完毕 -> - **拼合所有页的段落**后,才算拿到了完整转写原文 -> -> **自动翻页伪代码(AI 必须内化此逻辑):** -> ``` -> all_paragraphs = [] -> next_token = "" # 首次为空 -> loop: -> if next_token == "": -> result = dws minutes get transcription --id --format json -> else: -> result = dws minutes get transcription --id --next-token --format json -> all_paragraphs.append(result.paragraphs) -> next_token = result.nextToken # 可能为空或不存在 -> if next_token 为空或不存在: -> break # 全部拉取完毕 -> # 此时 all_paragraphs 才是完整转写原文 -> ``` -> -> **典型错误(导致"总结整篇听记"只看到前 50 段的 P0 bug):** -> - [错误] 只调用一次 `get transcription`,看到返回了内容就以为拿全了 → 实际只有前 50 段 -> - [错误] 看到返回 JSON 里有 `nextToken` 字段但不知道这是什么、不知道要传 `--next-token` → 默默丢弃了后续页 -> - [错误] 用 `--help` 查参数但在 Windows 下输出被截断,没看到 `--next-token` 参数 → 以为不支持分页 -> - [正确] **正确做法:无条件按上述循环逻辑自动翻页,直到 nextToken 为空** - -**重要 — 转写原文拉取策略(AI 必须严格遵守):** - -转写原文数据量通常很大,**是否拉取、拉取多少**需要根据用户意图智能判断: - -1. **用户明确要求查看/分析转写原文时 → 默认拉取全部原文**(自动翻页,不需要用户手动说"第一页") - - 示例:"帮我看看转写原文"、"分析一下这篇听记的原文"、"把逐字稿给我"、"转写内容是什么" - - 实现:首次调用不传 `--next-token`,如果返回中包含 `nextToken`,**自动继续调用**直到拉取完所有页,最终拼合后展示给用户 - - **字符上限保护**:在循环拉取过程中,如果已累积的转写文本总量**超过 12000 字符(1.2w)**,必须**暂停自动翻页**,向用户提示当前已处理的字符数已达到上限,并询问是否继续拉取后续分页内容。用户确认后才继续拉取,用户拒绝则停止并展示已拉取的内容 - -**超长会议翻页优化策略(0519 P0 Golden Case 提炼 — 70min 会议翻页 15+ 次):** - -> 当会议时长超过 45 分钟时,转写原文通常需要 **10-20 次翻页**(每次 50 段 ≈ 23k 字),每次 CLI 调用都消耗 LLM 上下文 token,极度浪费。 -> -> **AI 必须遵守的翻页效率规则:** -> 1. **翻页次数预估**:首次调用返回后,如果 `nextToken` 非空,AI 应根据已拉取段落数和会议总时长估算剩余翻页次数 -> 2. **超过 5 次翻页时**:自动启用"静默累积模式"——不在每次翻页时都向用户汇报进度,直接循环拉取并在内存中累积 -> 3. **超过 10 次翻页时**:在第 10 次翻页完成后暂停,主动告知用户: -> > "这篇听记较长,已拉取前 500 段(约 X 字符),预计还需 N 次翻页。是否继续拉取完整原文,还是基于已有内容先做分析?" -> 4. **严禁**:翻页中途放弃后改用 shell 重定向(如 `> tmp/file.json`)——这违反 Shell 命令安全规则 -> 5. **严禁**:因翻页次数多就跳过后续页,在摘要中只基于前几页内容得出结论 -> -> **用户意图为"按发言人分析"时的优化路径:** -> - 如果用户的最终目的是"区分发言人"/"看某人讲了什么",**不一定需要拉取全部转写原文** -> - 可优先使用 `dws minutes get summary --id ` 获取结构化摘要(含发言人信息) -> - 或使用 `dws minutes speaker summary get --ids ` 直接获取按发言人维度的摘要 -> - 仅当用户明确要求"逐字稿"/"完整原文"/"每句话"时,才需完整翻页拉取 - -2. **用户未明确要求查看原文时 → 不要主动拉取转写原文** - - 示例:"查一下和悟空相关的听记"、"帮我看看这个听记的摘要"、"这个会议讲了什么" - - 这些场景下用户的意图是查列表、看摘要等,**不需要也不应该**把大量转写原文全部拉出来,否则会造成信息过载和不必要的性能开销 - - 如果用户问"这个会议讲了什么",应优先使用 `get summary` 返回摘要,而非 `get transcription` - -**判断原则:只有用户的意图明确指向"原文/转写/逐字稿/录音文字"时,才调用 `get transcription`;其他场景(查列表、看摘要、看待办等)严禁自动附带拉取转写原文。** - -**重要 — 转写原文返回后默认按"时间线"组织各段落,AI 必须主动引导发言人聚类与关联(必须严格遵守):** - -`get transcription` 默认返回的段落是按"时间戳正序/倒序"穿插展示的(每条记录包含 `speakerNick + 时间戳 + 文本`),**对人不友好**——同一发言人的内容散落在多个时间点,难以快速看清"某个人主要讲了什么"。因此,AI **在拉取完成(含分页全部完成)后**必须主动启动以下"发言人聚类 → 关键词模糊匹配 → 引导确认 → 调用 `speaker replace`"四阶段工作流: - -#### 阶段 1:拉取完成后主动询问"是否按发言人聚类" -拉取(含自动翻页)结束后,AI 必须**主动**追问用户一次(不要默认强制聚类,避免用户只想看时间线原文时被打扰): - -> "已拉取完整转写原文(共 N 段,X 个发言人)。当前默认按时间线返回。是否需要我帮你**按发言人分组聚类**,并提取每位发言人的**核心发言要点**?" - -- 用户确认("好/可以/需要/聚类一下/按发言人分一下"等)→ 进入阶段 2 -- 用户拒绝 → 直接展示时间线原文,结束流程,**不再追问** - -#### 阶段 2:按发言人聚类 + 提取核心内容 -AI 基于已拉取的转写数据,**本地完成**聚类与摘要(无需新调用 dws 命令),输出结构如下: - -``` -[发言人] 发言人1(共 12 段,约 1820 字) - 核心要点: - - 介绍 Q3 战略规划与组织调整方向 - - 强调 AI 化转型的三个关键里程碑 - - 提出对供应链效率的具体改进目标 - -[发言人] 发言人2(共 8 段,约 960 字) - 核心要点: - - 汇报当前业务的财务数据与利润率 - - 分析竞品在华东市场的最新动作 - -[发言人] 张三(共 5 段,约 410 字) - 核心要点: - - 同步研发团队的招聘进度 - - 提出对测试资源的支持诉求 -``` - -**约束:** -- 聚类必须包含**全部发言人**(包括"发言人1/发言人2"占位符与已关联真实姓名的发言人,便于后续替换) -- 每位发言人**最多列出 3-5 条**核心要点,避免冗长 -- 输出后必须**主动追问**用户: - -> "如果你能告诉我『某某人主要讲了什么』(例如『李总主要讲了战略规划』『王经理主要负责供应链』),我可以根据关键词帮你**自动匹配**对应的发言人,并把『发言人1/发言人2』替换成真实姓名。" - -#### 阶段 3:基于用户提供的关键词做模糊匹配 -当用户提供形如『某某人主要讲了 XX』的输入时(例如『李总主要讲了战略规划』『拾光负责供应链效率改进』),AI 必须: - -1. **提取关键词**:从用户输入中抽取核心实体/主题词(如『战略规划』『供应链效率』『AI 化转型』),可允许多关键词 -2. **在阶段 2 已聚类的发言人核心要点中做模糊匹配**: - - 优先匹配「核心要点」中包含或语义相近的发言人 - - 必要时回看该发言人的原始转写文本进行二次确认 - - 支持**同义词/近义词**容忍(如『战略』~『规划』、『供应链』~『物流』) -3. **匹配结果分级处理:** - - **唯一高置信匹配**(一个发言人显著命中)→ 进入阶段 4 引导确认 - - **多个候选**(2 个及以上发言人都有部分命中)→ 列出全部候选,让用户选择,例如: - > "根据关键词『战略规划』,我匹配到 2 个可能的候选:① 发言人1(命中『战略规划/AI 化转型』)② 发言人3(命中『战略方向』)。你说的『李总』更可能是哪一位?" - - **无匹配** → 如实告知用户,并请其补充更具体的关键词或直接给出"发言人编号 → 真实姓名"的映射,例如: - > "暂未在转写中匹配到与『战略规划』强相关的发言人。可以再描述得更具体一些,或者直接告诉我『发言人 X 就是李总』,我来帮你替换。" - -#### 阶段 4:引导用户确认关联,并调用 `speaker replace` 完成替换 -匹配到唯一候选后,AI **不要直接替换**,必须先**显式追问用户确认**: - -> "我找到『发言人1』很可能就是你说的『李总』(命中关键词:战略规划、AI 化转型)。是否需要我把这篇听记里的『发言人1』全部替换为『李总』? -> 确认后我会执行:`dws minutes speaker replace --id --from "发言人1" --to "李总"`" - -- 用户确认 → 立即调用 `dws minutes speaker replace --id --from "发言人1" --to "李总" --format json`,并在执行成功后告知用户:"已将本篇听记的『发言人1』全部替换为『李总』,纪要与待办中的发言人也已同步更新。" -- 用户希望同时关联通讯录 → 引导用户提供钉钉 UID,调用时附加 `--target-uid ` -- 用户拒绝 → 不替换,可询问是否还有其他发言人需要关联,否则结束流程 - -**严禁的行为:** -- [禁止] 拉取完转写后直接输出大段时间线原文就结束,不主动引导聚类 -- [禁止] 用户提供"某某人讲了 XX"后,AI 自己默认替换而不向用户二次确认 -- [禁止] 把"发言人1 → 李总"这种映射只在 AI 回复中口头说说,而**不实际调用** `speaker replace` 写回听记 -- [禁止] 模糊匹配置信度不足时仍然给出唯一答案,不让用户参与挑选候选 - -#### 发言人识别与总结执行链路(用户指定人名查发言时必须遵循) - -**触发条件**:用户同时提供了听记来源(URL/时间/关键词)和一个具体人名,目标是获取该人在会议中说了什么。例如"帮我看看这个听记里张三说了什么""李总在今天的会上提了哪些观点"。 - -> **【重要】** **核心铁律(必须刻在脑子里,违反任何一条都视为严重错误):** -> -> **铁律 1:严禁在转写文本里 grep "目标人名" 字符串作为存在性判断依据** -> - 花名/真名通常**不会**出现在 TA 自己的发言里,发言里出现的"X"只意味着"`说话人 ≠ X`"或"说话人在叫 X" -> - 在转写中搜不到"灵麦"**完全不能得出"灵麦没参会"的结论**——99% 的发言人都显示为匿名编号"发言人1/2/3" -> - 正确做法是把"目标人名"作为**身份信息**去通讯录查询(Step 4 ①),而不是当作发言内容字符串去 grep -> -> **铁律 2:严禁用 AI 摘要里的"参与人/参会人"字段判断某人是否参会** -> - AI 摘要中的"参与人"字段往往**只截取最显著的 1-2 个名字**(通常是创建人或发言最多的人),**不是完整参会人列表** -> - "AI 摘要参与人只有故愚 → 灵麦没参会" 是典型的错误推理链 -> - 如果一定要列出参会人,只能从 `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 失败再补查 -> - 通讯录查询 1 个调用就能锁定"目标人物的部门 + 职级 + 上级"——这是身份推断**最低成本、最高收益**的信号源 -> - 跳过通讯录直接得出"找不到 X"的结论 = 100% 失败链路 -> -> **铁律 4:找不到字面匹配 ≠ 不存在** -> - "发言人识别"功能存在的本质就是**根据角色特征把匿名编号映射到真实人**——"找不到字面匹配"恰恰是这个功能要解决的问题,而不是退出的理由 -> - 一旦想说"找不到 X"前,先自问:通讯录查了吗?文档查了吗?聊天记录查了吗?基于角色在转写里做模式匹配了吗?四个全是"没"则禁止给"找不到"的结论 - -**完整执行链路:** - -``` -Step 1: 定位听记并读取转写原文 - ↓ -Step 2: 声纹标注检查 - ├─ [目标人名已被系统标注] → 直接跳 Step 6 - └─ [仅有匿名编号"发言人1/2/3"] ↓ -Step 3: 转写原文内推断(优先,能判断就不走外部查询) - ├─ [高置信命中] → 直接跳 Step 6 - └─ [无法确定] ↓ -Step 4: 多路并发身份推断 - ↓ -Step 5: 定向匹配 + 置信度分支 - ├─ 置信度 ≥ 50% → 展示文本片段请用户确认 - └─ 置信度 < 50% → 提供候选片段请用户辨认 - ↓ -Step 6: 结构化总结输出 - ↓ -Step 7: 引导用户替换发言人(调用 speaker replace 写回听记) -``` - -##### Step 1: 定位听记并读取转写原文 - -- **有 URL** → 从 URL 提取 taskUuid → `dws minutes get transcription --id --format json`(自动翻页拉取全部) -- **有时间/关键词** → `dws minutes list all --query "关键词" --start "..." --end "..." --format json` 筛选 → 获取 taskUuid → 拉取转写 -- **什么都没给** → **必须询问用户**提供听记链接、时间或关键词 - -##### Step 2: 声纹标注检查 - -检查转写原文中目标人物是否已被系统识别并标注了真实姓名(即 `speakerNick` 直接就是用户提到的人名): -- **已标注**(某条 `speakerNick` 字面就是"木兰")→ 直接跳到 Step 6,零确认步骤 -- **仅有匿名编号**(发言人1/发言人2/发言人3)→ **必须**继续 Step 3 与 Step 4,**严禁在此处退出** - -> **【重要】** **关键认知(违反则案例 7 重现):** -> - **未命中真名标注 ≠ 目标人物没参会**——绝大多数听记的发言人都是匿名编号,"未命中"是**默认场景**,恰恰是发言人识别功能要解决的问题 -> - **不要在转写文本里 grep 目标人名**作为存在性判断——花名/真名通常不会出现在 TA 自己的发言里(参见铁律 1) -> - **不要把 `dws minutes get summary` 摘要文本里写到的"参与人"当作完整参会列表**——AI 摘要里的"参与人"字段只截取最显著的 1-2 个名字,不是完整名册(参见铁律 2) -> - 听记各接口都拿不到权威的完整参会名册:`get info` / `get batch` **均不返回** `participants` 字段,转写发言人列表又常是匿名编号。因此**几乎无法**仅凭听记数据下"目标人物没参会"的结论;只有当转写发言人已全部实名标注且不含目标、且 `dws contact user search` 也搜不到该人时,才可谨慎告知"未在可见发言人中找到该人" - -##### Step 3: 转写原文内推断(优先) - -**核心原则:先基于转写原文做逻辑推断,能直接判断就不调外部接口。** - -充分利用转写文本中的所有可用信息进行综合推断: -- **称呼线索**:其他人称呼"张总/李工/王老师"等 -- **自我介绍**:"我是 XX 部门的""我负责 XX" -- **上下文指代**:前文提到"张三你来说一下",紧接着的发言人大概率是张三 -- **发言内容特征**:用户说"李总负责战略",而某发言人大量讨论战略方向 -- **发言顺序**:主持人/领导通常先发言或总结性发言 - -只要能从原文**高置信度**地确定发言人,直接跳 Step 6 输出总结。如果仅凭原文无法确定,继续 Step 4。 - -##### Step 4: 多路并发身份推断 - -> **【重要】** **强制规则(违反则案例 7 重现):** -> - **路径 ① 通讯录查询是必跑项,不依赖 Step 3 是否成功**——Step 2 一旦判定"未命中真名标注",**Step 3 与 Step 4 ① 必须并发触发**,**严禁等 Step 3 失败再补查** -> - 通讯录查询单次调用即可拿到"部门 + 职级 + 上级 + 真名"——这是身份推断**最低成本、最高收益**的信号源 -> - 路径 ②③④ 是**增量信号**,按需触发(如通讯录返回的部门是"产品设计部"这类多角色部门时,再补查 ② 文档) - -**触发顺序:** - -| 阶段 | 必跑 / 可选 | 触发时机 | -|------|-------------|----------| -| 路径 ① 通讯录组织架构 | **必跑** | Step 2 判定"未命中真名"后立即并发触发,与 Step 3 同时进行 | -| 路径 ② 本人创建的文档 | 可选 | ① 返回的部门是"产品设计部"这类多角色部门,需要更精确的角色信号时 | -| 路径 ③ 近期日程类型 | 可选 | ① ② 都不充分,需要补充职能边界判断时 | -| 路径 ④ 聊天记录 | 可选 | ①②③ 都不充分,需要语言风格/工作内容线索作为最后一道印证时 | - -| 路径 | 命令 | 得到什么 | -|------|------|----------| -| ① 通讯录组织架构 | `dws contact user search --keyword "目标人名"` → 部门/职级/上级/真名 | 职能大类(技术/产品/设计/管理)+ 是否存在该人 | -| ② 本人创建的文档 | `dws doc search --keyword "目标人名/真名"` 至少获取 3 篇标题 | 角色精确信号(PM写PRD、研发写技术方案、设计师写视觉规范)| -| ③ 近期日程类型 | `dws calendar event list` | 职能边界(参加什么类型的会)| -| ④ 聊天记录 | `dws chat message list` 获取与目标人的近期 IM 消息 | 语言风格/工作内容/职责线索 | - -**判定规则**: -- ① 命中(通讯录里搜到该人)→ 至少能拿到"部门 + 职级"信号,置信度起步 ≥ 30% -- ① + ② / ③ / ④ 任一路印证 → 置信度 ≥ 50% -- 两路以上独立信号一致 → 置信度 ≥ 70% -- ① 完全搜不到该人(且通讯录工具本身可用、未报错)→ 才允许告知用户"该人不在通讯录中,请确认花名是否正确" - -> **关键约束 1**:部门名 ≠ 角色("产品设计部"里有 PM、设计师、研究员),必须结合文档产出等信号区分。 -> -> **关键约束 2**:① 通讯录查询调用一次就能锁定身份范围,**严禁省略**。常见的失败模式是:在转写文本里反复 grep 目标人名找不到 → 直接放弃 → 告诉用户"找不到"——这是 100% 错误链路(参见案例 7)。 - -##### Step 5: 定向匹配 + 置信度分支 - -基于 Step 4 推断的角色,在转写原文中寻找匹配的发言模式: - -| 角色 | 典型发言特征 | -|------|----------| -| 产品经理 | 提需求、讲用户场景、定优先级 | -| 研发 | 技术约束、方案评估、排查问题 | -| 管理者 | 发言占比高、最终决策、分配任务 | -| 设计师 | 视觉方案、交互细节、体验讨论 | - -**分支 A:置信度 ≥ 50%(文本确认)** - -选取最具代表性的连续片段(≥ 2 句完整句子,避免"嗯/对/好"等纯语气词),展示给用户: - -> "根据分析,以下发言最可能是 [人名] 的: -> 「[片段内容]」 -> 确认是 TA 吗?" - -- 用户确认 → Step 6 -- 用户否认 → 换下一候选(最多 3 个) -- 3 个全否 → 告知无法仅通过文本确认,建议在听记详情页播放录音辅助辨认 - -**分支 B:置信度 < 50%(多候选展示)** - -当身份推断把握不足时,列出所有候选发言人及其代表性片段,让用户挑选: - -> "无法确定哪位是 [人名]。以下是几位候选发言人的代表性内容: -> ① 发言人1:「[片段]」 -> ② 发言人3:「[片段]」 -> 哪位更像 [人名]?或者你可以在听记详情页播放录音辅助确认。" - -- 用户选定 → Step 6 -- 用户无法确认 → 告知可在听记详情页点击对应段落播放原始录音来辨认,结束流程 - -##### Step 6: 结构化总结输出 - -提取已确认的该发言人的全部发言,结合会议上下文进行综合总结。根据实际内容灵活组织输出结构,例如: - -``` -[人名] 在本次会议中的发言总结(共 N 段,约 X 字) - -核心观点: -- 观点1... -- 观点2... -- 观点3... - -关键决策/结论: -- ... - -提出的待办/行动项: -- ... -``` - -##### Step 7: 引导用户替换发言人(必须执行,不可跳过) - -总结输出完成后,如果该发言人在转写中仍显示为匿名编号(如"发言人1"),**必须主动引导用户替换**: - -> "目前这篇听记中 [人名] 的发言仍显示为『发言人X』。要我帮你把听记里的『发言人X』全部替换为『[人名]』吗?替换后纪要和待办中的发言人也会同步更新。 -> 确认后我会执行:`dws minutes speaker replace --id --from "发言人X" --to "[人名]"`" - -- 用户确认 → **先主动调用通讯录模糊查询** `dws contact user search --query "[人名]" --format json` 获取该人员的 userId(长整型 dingUid): - - 唯一匹配 → 告知用户匹配结果,确认后执行 `dws minutes speaker replace --id --from "发言人X" --to "[人名]" --target-uid --format json` - - 多个匹配 → 列出候选(姓名+部门+userId)让用户选择后执行 - - 无匹配 → 提示用户未在通讯录中找到此人,执行不带 `--target-uid` 的替换:`dws minutes speaker replace --id --from "发言人X" --to "[人名]" --format json` -- 用户拒绝 → 不替换,询问是否还有其他发言人需要处理 - -**追问是否还有其他人需要识别:** - -> "还有其他发言人需要我帮你识别和替换吗?比如告诉我『某某人主要讲了什么内容』,我可以帮你匹配。" - -**严禁的行为:** -- [禁止] 总结完就结束,不引导用户替换发言人——用户下次看听记时发言人还是"发言人1",体验极差 -- [禁止] AI 自行决定替换而不向用户确认 -- [禁止] 只在回复中口头说"发言人1就是张三",但不实际调用 `speaker replace` 写回听记 - -### 获取听记中提取的待办事项 -``` -Usage: - dws minutes get todos [flags] -Example: - dws minutes get todos --id -Flags: - --id string 听记 taskUuid (必填),取值逻辑参考 ## 注意事项 -``` - -每条记录包含: 待办内容、待办唯一ID、参与人信息、待办时间 - -### 批量查询听记详情 -``` -Usage: - dws minutes get batch [flags] -Example: - dws minutes get batch --ids uuid1,uuid2,uuid3 -Flags: - --ids string 听记 taskUuid 列表,逗号分隔 (必填) -``` - -返回字段: `result.minutesDetails` 数组,每项含 `title`(标题)、`durationMicros`(时长, 微秒)、`startTime`(开始时间, 毫秒)、`taskUuid`、`status`(状态)、`creatorNick`(创建人昵称)、`creatorUnionId`、`size`、`bizType`、`isDeleted`。**不返回参会人列表**。 - -### 修改听记标题 -``` -Usage: - dws minutes update title [flags] -Example: - dws minutes update title --id --title "Q2 复盘会议" -Flags: - --id string 听记 taskUuid (必填),取值逻辑参考 ## 注意事项 - --title string 新标题 (必填) -``` - -### 发起听记(开始录音) -``` -Usage: - dws minutes record start [flags] -Example: - dws minutes record start - dws minutes record start --session-id -Flags: - --session-id string AI 助理会话 ID (可选) -``` - -### 暂停听记录音 -``` -Usage: - dws minutes record pause [flags] -Example: - dws minutes record pause --id - dws minutes record pause --id --session-id -Flags: - --id string 听记 taskUuid (必填) - --session-id string AI 助理会话 ID (可选) -``` - -### 恢复听记录音 -``` -Usage: - dws minutes record resume [flags] -Example: - dws minutes record resume --id - dws minutes record resume --id --session-id -Flags: - --id string 听记 taskUuid (必填) - --session-id string AI 助理会话 ID (可选) -``` - -### 结束听记录音 -``` -Usage: - dws minutes record stop [flags] -Example: - dws minutes record stop --id - dws minutes record stop --id --session-id -Flags: - --id string 听记 taskUuid (必填) - --session-id string AI 助理会话 ID (可选) -``` - -### 更新纪要内容 -``` -Usage: - dws minutes update summary [flags] -Example: - dws minutes update summary --id --content "新的纪要内容" -Flags: - --id string 听记 taskUuid (必填) - --content string 新的纪要内容 (必填) -``` - -用传入的摘要文本全量覆盖听记的纪要内容,不触发 AI 重新生成。适用于用户手动编辑或 AI Agent 修改纪要的场景。 - -**重要 — 修改纪要的完整流程(必须严格执行):** -当用户要求"精简纪要/优化纪要/修改纪要内容"时,**必须完成以下三步,缺一不可**: -1. **读取**:先调用 `get summary --id ` 获取当前纪要原文 -2. **修改**:AI 根据用户要求对纪要内容进行修改(如精简、重新整理、格式优化等),但必须遵守以下约束: - - **图片必须保留**:原文中的所有 Markdown 图片(如 `![alt](url)`)必须完整保留,不得删除、漏掉、替换为纯文本或打乱语义位置 - - **仅优化文本内容**:可以调整标题层级、段落结构、列表与措辞,但不得破坏图片与对应上下文的关联关系 -3. **校验**:写回前必须执行 Markdown 格式检查,确保输出结构合理、可渲染、无明显格式错误(如未闭合代码块、列表层级混乱、标题层级异常等) -4. **写回**:将修改后的完整纪要内容通过 `update summary --id --content "修改后的完整纪要"` **写回听记**,确保修改持久化 - -**严禁只读取和修改纪要而不调用 `update summary` 写回**,否则用户看到的仍然是原始纪要,修改不会生效。 - -### 创建思维导图 -``` -Usage: - dws minutes mind-graph create [flags] -Example: - dws minutes mind-graph create --id -Flags: - --id string 听记 taskUuid (必填) -``` - -触发创建听记思维导图任务。触发成功后,可通过 `mind-graph status` 轮询任务状态。状态:0=进行中,1=成功,2=失败。 - -**重要:当用户要求"生成思维导图/创建脑图"时,必须调用此命令(`mind-graph create`),严禁自行生成 HTML 或其他格式的思维导图。** 思维导图由服务端专业引擎生成,AI 不应尝试自己构造思维导图内容。 - -### 查询思维导图状态 -``` -Usage: - dws minutes mind-graph status [flags] -Example: - dws minutes mind-graph status --id -Flags: - --id string 听记 taskUuid (必填) -``` - -查询指定听记的思维导图生成状态。返回任务状态:0=进行中,1=成功,2=失败。如果没有返回任务状态,也视为成功。 - -### 替换发言人 -``` -Usage: - dws minutes speaker replace [flags] -Example: - dws minutes speaker replace --id --from "张三" --to "李四" - dws minutes speaker replace --id --from "张三" --to "李四" --target-uid -Flags: - --id string 听记 taskUuid (必填) - --from string 源发言人昵称 (必填) - --to string 目标发言人昵称 (必填) - --target-uid string 目标发言人钉钉 UID (可选) -``` - -批量替换听记转写中指定发言人,将源发言人(speakerNick)精确匹配的所有段落替换为目标发言人。支持同时替换 nickName 和 subSpeakerNickname 两种匹配方式,并自动更新纪要、待办中的发言人信息。 - -**重要:** -- 此命令支持替换**任意发言人**,包括已关联通讯录信息的发言人(如"张三"、"李四"等真实姓名),不仅限于"发言人1"之类的占位符 -- `--from` 填写当前听记中显示的发言人名称(无论是"发言人1"还是真实姓名),`--to` 填写要替换成的目标名称 -- 如果用户希望将发言人关联到通讯录中的具体联系人,可通过 `--target-uid` 传入目标用户的钉钉 UID - -### 触发创建发言人段落总结任务 -``` -Usage: - dws minutes speaker summary create [flags] -Example: - dws minutes speaker summary create --ids - dws minutes speaker summary create --task-uuids -Flags: - --ids string 听记 taskUuid 列表,逗号分隔 (必填) - --task-uuids string --ids 的别名,同样接受逗号分隔的 taskUuid 列表 -``` - -触发创建发言人的段落总结任务,将听记中每位发言人的所有发言内容汇总总结。触发后需调用 `speaker summary get` 查询总结结果。 - -> **参数别名说明**:`--ids` 和 `--task-uuids` 等价,均可使用。推荐统一用 `--ids`。 - -### 查询发言人段落总结结果 -``` -Usage: - dws minutes speaker summary get [flags] -Example: - dws minutes speaker summary get --ids - dws minutes speaker summary get --task-uuids -Flags: - --ids string 听记 taskUuid 列表,逗号分隔 (必填) - --task-uuids string --ids 的别名,同样接受逗号分隔的 taskUuid 列表 -``` - -查询发言人段落总结任务的结果,返回每位发言人的发言汇总。需先调用 `speaker summary create` 触发任务。 - -> **参数别名说明**:`--ids` 和 `--task-uuids` 等价,均可使用。推荐统一用 `--ids`。 - -**speaker summary 异步轮询策略(必须严格遵守):** - -`speaker summary create` 只是触发后端异步任务,**不会立即返回总结结果**。AI 必须按以下策略轮询 `speaker summary get`: - -1. 调用 `speaker summary create --ids ` 触发任务 -2. **等待至少 5 秒**后,首次调用 `speaker summary get --ids ` 查询结果 -3. 如果返回结果为空(无内容),**继续等待 5 秒**后重试 -4. **最大轮询次数不超过 20 次**(即最长等待约 100 秒) -5. 如果 20 次轮询后仍然为空,视为**任务未完成或无内容**,告知用户:"发言人段落总结任务可能仍在处理中,请稍后再试。" - -``` -伪代码: -speaker summary create --ids -wait 5s -for i in 1..20: - result = speaker summary get --ids - if result 不为空: - break # 拿到结果,继续后续流程 - wait 5s -if 仍为空: - 告知用户任务可能仍在处理中 -``` - -**严禁**: -- 调用 `create` 后立即调用 `get`(不等待)——后端异步任务需要时间生成 -- 只轮询 1-2 次就放弃——正常任务可能需要 10-30 秒 -- 轮询超过 20 次——避免无限等待 - -**speaker summary 典型应用场景 — 通过发言主题匹配发言人:** - -当用户只知道某个人的发言主题或关联内容(如"讲战略规划的那个人是谁"),但不知道对应的发言人编号时,可以通过 speaker summary 获取每位发言人的段落总结,再用关键词匹配确认身份。 - -**完整链路(发言人段落总结 → 关键词匹配 → 确认 → 替换):** - -1. **确定听记 taskUuid**:从 `list mine/all` 或用户提供的 URL 中获取 -2. **触发发言人段落总结**:`dws minutes speaker summary create --ids --format json` -3. **延迟轮询获取结果**:等待 5s 后调用 `dws minutes speaker summary get --ids --format json`,最多轮询 20 次 -4. **AI 按发言人分组展示总结**:将每位发言人的段落总结以结构化方式呈现给用户 -5. **关键词匹配**:从用户描述中抽取关键词(如"战略规划"、"供应链"),在各发言人的段落总结中做模糊匹配 -6. **引导用户确认**: - - 唯一高置信命中 → "『发言人1』的段落总结涉及『战略规划、AI化转型』,很可能就是你说的『李总』,确认替换吗?" - - 多候选 → 列出所有命中的发言人编号 + 匹配的总结关键词,让用户挑选 - - 无匹配 → 请用户补充更具体的关键词 -7. **用户确认后执行替换**:`dws minutes speaker replace --id --from "发言人1" --to "李总"` - -**自然语言触发示例:** - -| Query | AI 处理策略 | -|-------|-----------| -| "帮我看看这个会议里每个人主要说了什么" | speaker summary create → 轮询 get → 按发言人分组展示 | -| "讲战略规划的那个人是谁" | speaker summary create → 轮询 get → 关键词匹配 → 引导确认 | -| "帮我把每个发言人的核心观点总结一下" | speaker summary create → 轮询 get → 结构化输出 | -| "我想知道会上谁讲了供应链相关内容" | speaker summary create → 轮询 get → "供应链"关键词匹配 | -| "那个讲招聘进度的人应该是张三" | speaker summary create → 轮询 get → "招聘进度"匹配 → 确认 → speaker replace | - -> **与 `get transcription` 聚类方案的区别**:`speaker summary` 是后端 AI 生成的结构化总结,信息密度更高、匹配更精准;而 `get transcription` 聚类是 AI 在本地按 speakerNick 分组原文再提取要点,适合需要看原文细节的场景。两者可配合使用:先用 speaker summary 快速定位,再用 transcription 看具体原文。 - -### 添加个人热词 -``` -Usage: - dws minutes hot-word add [flags] -Example: - dws minutes hot-word add --words "钉钉" - dws minutes hot-word add --words "OKR,钉钉,Copilot" -Flags: - --words string 要添加的热词,多个用逗号分隔 (必填) -``` - -添加听记个人热词,用于优化语音识别中专有名词、人名等的识别准确率。支持一次添加多个热词(逗号分隔),每个热词长度不超过 10 个汉字或 5 个英文单词。 - -### 查询我的热词列表 -``` -Usage: - dws minutes hot-word list -Example: - dws minutes hot-word list -``` - -查询当前用户配置的所有听记热词列表。无需传入额外参数,系统自动识别当前用户身份。返回用户已添加的全部热词,适用于查看已有热词、去重检查等场景。 - -### 查找替换听记文字 -``` -Usage: - dws minutes replace-text [flags] -Example: - dws minutes replace-text --id --search "旧文字" --replace "新文字" -Flags: - --id string 听记 taskUuid (必填) - --search string 要查找的文字 (必填) - --replace string 替换为的新文字 (必填) -``` - -把听记中所有出现的原文字替换为目标文字,包括转写段落和纪要摘要中出现的原文字都会被替换。区分大小写,精确匹配。 - -**重要 — 执行前必须检查特殊字符并提示用户确认:** -用户输入的原始文本(`--search`)或目标文本(`--replace`)中可能携带特殊字符(如引号 `"` `'` `"` `"`、书名号 `《》`、括号 `【】()()`、星号 `*`、反斜杠 `\`、换行符、Markdown 格式符号等)。这些特殊字符在转写原文中**通常不存在**,会导致精确匹配失败(替换 0 处)。 - -AI **必须在执行 `replace-text` 之前**检查用户输入,若发现特殊字符,**先向用户确认**: - -> "我注意到你输入的文本中包含特殊字符(如 `…`),转写原文中通常不会包含这些字符,直接匹配可能找不到。建议去掉特殊字符后再替换: -> -> - 原始文本:`XXX` → 建议改为:`YYY` -> - 目标文本:`AAA` → 建议改为:`BBB` -> -> 使用去掉特殊字符的版本执行替换?[是] [否,使用原始输入]" - -- 用户确认 → 使用清理后的文本执行替换 -- 用户选择原始输入 → 按原样执行,尊重用户意图 -- **严禁不提示就自行去掉特殊字符**——用户可能确实需要匹配带特殊字符的文本 - -**重要 — 执行后必须主动引导用户添加热词(避免长期反复识别错):** -`replace-text` 仅修正**当前这一篇听记**的文字,**不会影响后续新听记的语音识别结果**。如果用户替换的是一个**长期容易被识别错的专有名词、人名、产品名**(如把"付工"改成"悟空"、把"非书"改成"飞书"),AI **必须在 `replace-text` 成功后主动追问用户**: - -> "我已经把这篇听记里的『旧文字』替换为『新文字』。如果这个词以后也容易被识别错,建议把它加到个人热词里,后续新听记就不会再识别错了。要我现在帮你执行 `dws minutes hot-word add --words "新文字"` 吗?" - -用户确认后立即调用 `hot-word add`。**严禁只做替换不引导**——这会让用户每次都要手动改一次,体验非常差。 - -### 创建文件上传会话或者文件转听记或者链接转听记 -``` -Usage: - dws minutes upload create [flags] -Example: - dws minutes upload create --file-name "meeting.mp4" --file-size 102400 - dws minutes upload create --file-name "meeting.mp4" --file-size 102400 --title "周会录音" - dws minutes upload create --file-name "meeting.mp4" --file-size 102400 --input-language "zh" --enable-message-card -Flags: - --file-name string 文件名(含后缀),如 meeting.mp4 (必填) - --file-size int 文件大小(字节)(必填,正整数) - --title string 听记标题,不传时默认使用文件名去掉后缀 (可选) - --template-id string 纪要生成使用的模板 ID (可选) - --input-language string ASR 识别的源语言 (可选) - --enable-message-card 是否推送闪记卡片消息 (可选,默认 false) -``` - -创建文件上传会话,获取预签名上传 URL。调用方拿到 URL 后,直接用 HTTP PUT 将文件上传到该 URL。必须与 `upload complete` 配合使用: -1. 调用 `upload create` 获取预签名上传 URL 和 sessionId -2. HTTP PUT 预签名上传 URL 上传文件(不带 HEADER) -3. 调用 `upload complete` 传入 sessionId 完成创建 - -### 完成文件上传并创建听记 -``` -Usage: - dws minutes upload complete [flags] -Example: - dws minutes upload complete --session-id -Flags: - --session-id string 上传会话 ID,来自 upload create 返回的 sessionId (必填) -``` - -文件上传完成后,调用此命令创建听记。必须在 `upload create` 之后、预签名 URL 上传完成后调用。幂等:同一 sessionId 重复调用直接返回已有任务,不会重复创建。 - -### 取消文件上传会话 -``` -Usage: - dws minutes upload cancel [flags] -Example: - dws minutes upload cancel --session-id -Flags: - --session-id string 要取消的会话 sessionId (必填) -``` - -取消 `upload create` 创建的上传会话,释放服务端资源。用于在上传前或上传失败后取消会话。 - -### 批量添加听记成员并设置权限 -``` -Usage: - dws minutes permission add [flags] -Example: - dws minutes permission add --ids --member-uids 123456,789012 --policy 3 - dws minutes permission add --ids --member-uids 123456 --policy 2 --cover - dws minutes permission add --ids --member-uids 123456 --policy 3 --sub-resources "OrigContent,Summary" - dws minutes permission add --uuids --member-uids 123456 --policy 3 - dws minutes permission add --task-uuids --member-uids 123456 --policy 3 -Flags: - --ids string 听记 taskUuid 列表,逗号分隔 (必填) - --uuids string --ids 的别名 - --task-uuids string --ids 的别名 - --member-uids string 成员钉钉 UID 列表,逗号分隔 (必填) - --policy int 权限类型: 0=管理员, 1=所有者, 2=可编辑, 3=可查看/下载, 4=仅查看 (必填) - --cover 是否覆盖已有权限 (可选,默认 false) - --sub-resources string 权限子模块,逗号分隔: OrigContent/Summary/Analysis/Note (可选) -``` - -批量给多个听记增加成员,并设置成员的权限。 - -**权限类型说明:** - -| --policy 值 | 含义 | 说明 | -|------------|------|------| -| 0 | 管理员 | 可管理听记的所有设置和成员权限 | -| 1 | 所有者 | 听记的所有者,拥有最高权限 | -| 2 | 可编辑 | 可编辑听记的纪要、待办等内容 | -| 3 | 可查看/下载 | 可查看和下载听记内容,不可编辑 | -| 4 | 仅查看 | 仅可查看听记内容,不可下载 | - -**权限子模块说明(--sub-resources,可选):** - -| 子模块 | 含义 | -|--------|------| -| OrigContent | 原始内容(转写原文) | -| Summary | 纪要(AI 摘要) | -| Analysis | 分析 | -| Note | 笔记 | - -不传 `--sub-resources` 时,默认对所有子模块生效。传入后仅对指定的子模块授权。 - -**典型使用场景:** -- 会议结束后将听记共享给未参会的同事查看 -- 给团队成员批量授权某几篇听记的编辑权限 -- 限制只共享纪要而不共享原始转写内容 - -### 批量移除听记成员权限 -``` -Usage: - dws minutes permission remove [flags] -Example: - dws minutes permission remove --ids --member-uids 123456,789012 - dws minutes permission remove --ids --member-uids 123456 - dws minutes permission remove --uuids --member-uids 123456 - dws minutes permission remove --task-uuids --member-uids 123456 -Flags: - --ids string 听记 taskUuid 列表,逗号分隔 (必填) - --uuids string --ids 的别名 - --task-uuids string --ids 的别名 - --member-uids string 成员钉钉 UID 列表,逗号分隔 (必填) -``` - -批量移除多个听记的成员权限。移除后,对应成员将失去对这些听记的访问权限。 - -**注意事项:** -- 移除权限后,该成员将无法再访问对应的听记内容 -- 如果成员是听记的创建者(所有者),无法通过此命令移除其权限 -- 建议在移除前先确认成员的当前权限,避免误操作 - -## 意图判断 - -### 发起听记 -用户说"开始听记/开始录音/发起一个听记/启动听记/开始听记录这次会议/我要开始听记了/录音开始/我要听记" → `record start` - -**自然语言示例:** -- "开始听记开启听记" -- "开始录音" -- "发起一个听记" -- "启动听记" -- "开始听记录这次会议" -- "我要开始听记了" -- "录音开始" - -### 暂停/继续录制 -用户说"暂停一下/暂停录音" → `record pause` -用户说"继续/继续录音" → `record resume` - -**自然语言示例:** -- "暂停一下" -- "继续" - -### 结束录制 -用户说"结束听记/结束录音/停止录音/结束" → `record stop` - -**自然语言示例:** -- "结束听记" -- "结束录音" -- "停止" - -### 文件转听记 -用户说"把文件转成听记/上传音频文件/上传录音/把这个 mp3 转成听记/帮我把会议录音转写一下/把附件里的音频文件做转写" → `upload create` → HTTP PUT → `upload complete` - -**自然语言示例:** -- "把这个文件转写生成纪要" -- "帮我把这段录音转成文字" -- "上传一个音频文件,帮我转写" -- "我有一个录音文件,帮我生成听记" -- "把这个 mp3 转成听记" -- "帮我把会议录音转写一下" -- "这是昨天的录音,帮我整理成纪要" -- "把附件里的音频文件做转写" - -用户说"取消上传" → `upload cancel` - -### 查询我的热词列表 -用户说"看看我的热词/我加了哪些热词/热词列表/查热词" → `hot-word list` - -**自然语言示例:** -- "我之前加过哪些热词" -- "看看我的热词列表" -- "查一下我配置的热词" -- "查一下我听记的热词" - -### 添加热词 -用户说"加热词/添加热词/设置热词/把某个词加到热词里/这个词总是识别错" → `hot-word add` - -**自然语言示例:** -- "转写不准,应该是'悟空'而不是'付工'" -- "帮我把'钉钉'加到热词里" -- "这个词总是识别错,加一下热词" -- "'飞书'总被识别成'非书',加个热词" -- "帮我设置一下热词,'悟空'经常识别不对" -- "我们公司有个产品叫'魔镜',加一下热词避免识别错" -- "把'AI听记'加进热词库" -- "这个专有名词总转写错,帮我加到热词" - -### 查找替换 -用户说"把所有的A替换成B/查找替换/批量替换文字" → `replace-text`,**执行成功后必须主动引导用户:"要不要把『新文字』加到热词里,避免后续新听记再识别错?" 用户确认后再调用 `hot-word add`** - -**自然语言示例:** -- "把所有的A,替换成B" -- "把听记里的'付工'都改成'悟空'" -- "这一篇里把'非书'替换成'飞书'" - -**正确的对话节奏(必须遵守):** -1. **检查特殊字符**:检查用户提供的原始文本和目标文本是否包含特殊字符(引号 `"` `'` `"` `"`、书名号 `《》`、括号 `【】()()`、星号 `*`、反斜杠 `\`、Markdown 格式符号等)。若包含,先提示用户确认: - > "你输入的文本中包含特殊字符 `…`,转写原文中通常不包含这些字符,直接匹配可能替换不到。建议去掉特殊字符后替换:原始文本 `XXX` → 建议 `YYY`。使用去掉特殊字符的版本?[是] [否,使用原始输入]" - - 用户确认 → 使用清理后文本 - - 用户选择原始输入 → 按原样执行 - - **严禁不提示就自行去掉特殊字符** -2. 执行 `replace-text` 完成本篇替换 -3. 立即追问:"如果这个词以后也容易被识别错,建议加个热词,后续新听记就不会再错了。要现在加吗?" -4. 用户确认 → 调用 `hot-word add --words "<新文字>"` -5. 用户拒绝 → 直接结束,不再追问 - -### 修改说话人 -用户说"替换发言人/修改发言人/换发言人名字/把说话人改成某某/发言人标注错了" → `speaker replace` - -**关键:支持替换任意发言人,包括已关联通讯录信息的真实姓名(如"张三"、"拾光"等),不限于"发言人1"等占位符。** 用户提到"把 XX 改成 YY"时,XX 即为 `--from`,YY 即为 `--to`。 - -**默认引导通讯录查询获取 dingUid(推荐流程):** - -替换发言人时,`--target-uid` 参数可以将发言人关联到钉钉通讯录真实身份。**AI 应默认引导用户通过通讯录模糊查询获取目标人员的 dingUid**,而非让用户自行提供。流程如下: - -1. 用户提供目标姓名(如"张三")后,**AI 主动调用通讯录模糊查询**: - ``` - dws contact user search --query "张三" --format json - ``` -2. 从返回结果中提取匹配的人员列表,每条包含 `userId`(长整型数字,即 dingUid)和姓名: - - **唯一匹配** → 直接向用户确认:"通讯录中找到『张三(userId: 123456789)』,确认将发言人替换为此人并关联通讯录身份?" - - **多个匹配** → 列出候选让用户选择: - > 通讯录中找到多个匹配,请选择目标人员: - > - > | # | 姓名 | 部门 | userId | - > |---|------|------|--------| - > | 1 | 张三 | 产品部 | 123456789 | - > | 2 | 张三丰 | 技术部 | 987654321 | - > - > 请输入序号选择。 - - **无匹配** → 提示用户:"通讯录中未找到『张三』,可以直接替换发言人名称(不关联通讯录身份),或提供更精确的姓名重新查询。直接替换?[是] [重新查询]" -3. 获取到 userId 后,在 `speaker replace` 中附加 `--target-uid `,实现发言人关联通讯录身份 - -> **注意**:`dws contact user search` 返回的 `userId` 是**长整型数字**(如 `123456789`),这就是 `--target-uid` 需要的值。不要与 `staffId`、`unionId` 等其他 ID 混淆。 - -**发言人映射模式识别(高频场景):** - -用户经常使用如下格式批量告知发言人对应关系: -- `发言人1:张三` -- `发言人1:张三`(中文冒号) -- `发言人1->张三` -- `发言人1 -> 张三` -- `发言人1 = 张三` -- 多条换行或逗号分隔:`发言人1:张三, 发言人2:李四, 发言人3:王五` - -**AI 识别到上述模式时,必须执行以下流程:** - -1. **解析映射关系**:提取所有 `源发言人 → 目标姓名` 的配对 -2. **批量通讯录查询**:对每个目标姓名调用 `dws contact user search --query "<姓名>" --format json`,获取 userId(dingUid)。将查询结果汇总后一并向用户确认 -3. **向用户确认**:展示解析结果(含通讯录匹配)并请求确认,格式如下: - -> 我识别到以下发言人对应关系,并已从通讯录中匹配到对应人员: -> -> | 当前标注 | 替换为 | 通讯录匹配 | userId | -> |---------|--------|-----------|--------| -> | 发言人1 | 张三 | 张三(产品部) | 123456789 | -> | 发言人2 | 李四 | 李四(技术部) | 987654321 | -> -> 替换后,发言人将关联到通讯录真实身份,该听记中所有对应的发言段落标注、纪要和待办中的发言人信息都会同步更新。 -> -> 确认执行?[确认] [取消] - -4. **用户确认后**,逐条执行 `speaker replace`(附带 `--target-uid`): - ``` - dws minutes speaker replace --id --from "发言人1" --to "张三" --target-uid 123456789 --format json - dws minutes speaker replace --id --from "发言人2" --to "李四" --target-uid 987654321 --format json - ``` - 如果某个姓名未在通讯录中找到匹配,则该条不带 `--target-uid`: - ``` - dws minutes speaker replace --id --from "发言人3" --to "王五" --format json - ``` -5. **全部完成后**,主动追问是否需要同时执行文本替换(`replace-text`),因为纪要正文中可能也残留了"发言人1"等文字: - -> 发言人标注已全部替换完成。纪要正文中如果还有残留的「发言人1」「发言人2」等文字,是否也一并替换?[是] [否] - -6. 用户确认后,对每条映射执行 `replace-text`: - ``` - dws minutes replace-text --id --search "发言人1" --replace "张三" --format json - ``` - -**禁止行为:** -- 禁止不确认就直接执行替换(发言人替换不可逆) -- 禁止只执行 `speaker replace` 不引导 `replace-text`(纪要正文可能有残留) -- 禁止只执行 `replace-text` 不执行 `speaker replace`(转写段落的发言人标注不会被 replace-text 修改) -- 禁止跳过通讯录查询直接要求用户提供 dingUid——应由 AI 主动查询并引导选择 - -**自然语言示例:** -- "把这一段内容的说话人全部改成张三" -- "帮我把发言人1改成李总" -- "这个说话人标注错了,改成王伟" -- "把所有'发言人2'替换成'小明'" -- "说话人识别错了,帮我修改一下" -- "把这段的说话人改成'拾贝'" -- "发言人1是我,帮我把名字改过来" -- "把'未知说话人'全都改成张总" -- "把这里面的发言人1,替换成拾光" -- "张三其实是李四,帮我改一下" -- "发言人1:张三 发言人2:李四 发言人3:王五" -- "发言人1->张三, 发言人2->李四" -- "发言人1是张三,发言人2是李四" - -### 查某人在听记中说了什么(发言人识别与总结) - -**触发条件**(必须同时满足): -1. 用户提供了听记来源(URL / 时间 / 关键词可定位到具体听记) -2. 用户指定了一个具体人名(花名/真名/关系称谓均可) -3. 用户的目标是获取该人在会议中说了什么 - -**不触发**(走其他链路): -- 用户只要整体纪要、未指定特定人物 → `get summary` -- 用户直接说"把发言人1改成张三" → `speaker replace`(无需走推断链路) -- 用户想提取待办 → `get todos` - -用户说"帮我看看某人说了什么/某人在会上提了哪些观点/某人有什么发言" → 走**发言人识别与总结**完整链路(详见 [获取听记语音转写原文](#获取听记语音转写原文) 章节中的"发言人识别与总结执行链路") - -**自然语言示例:** - -| Query | 关键线索 | -|-------|----------| -| "帮我看看这个听记里张三说了什么" + URL | 听记URL + 人名 | -| "李总在今天的会上提了哪些观点" | 时间(今天) + 人名 + 观点 | -| "上周产品评审里王五有什么发言" | 时间(上周) + 关键词(产品评审) + 人名 | -| "这个会议里我老板说了啥" | 听记上下文 + 关系称谓(需解析为人名) | -| "帮我找一下设计师小陈在访谈里的观点" | 角色提示(设计师) + 人名 + 场景 | - -- "帮我看看这个听记里张三说了什么" -- "李总在今天的会上提了哪些观点" -- "上周产品评审里王五有什么发言" -- "这个会议里我老板说了啥" -- "帮我找一下设计师小陈在访谈里的观点" -- "拾光在这个会议里讲了什么" -- "帮我总结一下这次会议里李总的核心发言" -- "王经理今天开会说了啥重点" - -**关键约束(必须遵守):** -- 这是一个**完整的识别 → 推断 → 确认 → 总结 → 引导替换**链路,不要只做总结就结束,**必须引导用户将「发言人1」等占位符替换为真实姓名** -- 完整执行链路见 [获取听记语音转写原文](#获取听记语音转写原文) 章节中的"发言人识别与总结执行链路" - -### 通过关键词模糊匹配确认发言人(与 `get transcription` 联动) -当用户在拉取完转写原文(且已按发言人聚类)之后,提供形如『某某人主要讲了 XX』『某某人负责 YY』『某某人这次的核心是 ZZ』的描述时,**不要直接做替换**,而是按下面的链路推进: - -1. **从用户输入中抽取关键词**(如『战略规划』『供应链效率』『AI 化转型』『招聘进度』) -2. **在已聚类的发言人核心要点中做模糊匹配**(支持同义词与近义词) -3. **匹配结果引导用户确认:** - - 唯一高置信命中 → 提示用户:"『发言人1』很可能就是你说的『李总』,是否需要执行 `dws minutes speaker replace --id --from "发言人1" --to "李总"`?" - - 多候选 → 列出所有命中的发言人编号 + 命中关键词,让用户挑选 - - 无匹配 → 请用户补充更具体的关键词或直接给出"发言人编号 → 真实姓名"的映射 -4. **用户确认后**才调用 `speaker replace` 写回;如同时希望关联通讯录,引导用户提供 UID,附加 `--target-uid ` - -**自然语言示例(必须能正确识别为"模糊匹配 → 确认 → 替换"链路):** -- "李总主要讲了战略规划" -- "拾光是负责供应链的那位" -- "讲 AI 化转型那个人是王经理" -- "这里面提到招聘进度的应该是张三" -- "提到财务数据的是 CFO 老周" -- "讲华东市场竞品分析的那个人是李四" - -详细工作流见 [获取听记语音转写原文](#获取听记语音转写原文) 章节中的"四阶段工作流"。 - -### 更新/修改纪要 -用户说"修改纪要/更新纪要/编辑纪要内容/重新整理纪要/纪要格式优化/纪要精简" → 先 `get summary` 获取纪要原文,AI 修改后再 `update summary` **写回** - -**关键:修改纪要必须是"读取 -> 修改 -> 校验 -> 写回"四步完整流程,最终必须调用 `update summary` 将修改后的内容写回听记,否则修改不会生效。严禁只展示修改结果而不写回。修改时必须保留原文中的所有 Markdown 图片,不得删除或打乱图片位置。** - -**自然语言示例:** -- "帮我把纪要按 xxxx 格式优化一下" -- "帮我重新整理一下这份纪要" -- "纪要太长了,帮我精简一下" -- "按照 STAR 格式重新写一下纪要" -- "帮我把纪要改得更正式一些" -- "这份纪要结构不清晰,帮我重新整理" -- "把纪要里的口语化内容改成书面语" -- "按照决策/行动/结论三段式重新输出纪要" -- "把这个纪要内容变得更精简一点" - -### 生成思维导图 -用户说"生成思维导图/创建脑图/帮我添加思维导图/基于这个听记做思维导图/把会议内容做成脑图" → `mind-graph create` - -**关键约束(必须严格遵守,违反视为严重错误):** -1. **思维导图必须且只能通过 `dws minutes mind-graph create` 命令生成**,由服务端写回到听记本身。这是"听记内置的思维导图能力",生成结果会直接挂在听记详情页上,用户在听记里就能看到。 -2. **严禁以下任何"自行构造"的替代方案**(这些都是错误做法): - - [禁止] 先 `get summary` / `get transcription` 拿到内容,再用 AI 自己整理出思维导图结构(Markdown / OPML / JSON 等)展示给用户 - - [禁止] 调用 app-development-skill / ai-app / 任何前端构建能力,用 `@antv/g6`、`markmap`、`jsmind`、`mermaid`、ECharts 等库生成 HTML/网页版思维导图 - - [禁止] 调用 `generate_image` 或任何绘图工具生成思维导图图片 - - [禁止] 路由到其他 Agent(如 ai-app)来"创建一个思维导图应用" - - [禁止] 任何形式的"我帮你画一个/生成一个网页/部署一个在线预览链接" -3. **唯一正确做法**:从用户输入中拿到 taskUuid(URL 场景见下方"URL 直达"),直接调用 `mind-graph create`,然后用 `mind-graph status` 轮询直到状态为 1(成功),最后告知用户"思维导图已生成,可在听记详情页查看"即可。**不需要**先调 `get summary` 等任何读取命令——服务端会基于听记自身内容生成。 - -**URL 直达场景:** -当用户输入形如 `https://shanji.dingtalk.com/app/transcribes/{taskUuid}` 的链接 + "创建思维导图/生成脑图"等诉求时: -1. 直接从 URL 路径末段提取 taskUuid -2. **直接** `dws minutes mind-graph create --id --format json` -3. 用 `mind-graph status` 轮询 -4. 严禁中间插入 `get summary` / `get transcription` 等任何"先读取内容"的步骤,更严禁基于读到的内容自行构造思维导图 - -**自然语言示例:** -- "帮我添加思维导图" -- "生成思维导图" -- "帮我创建一个脑图" -- "把这个听记做成思维导图" -- "生成这个会议的思维导图" -- "基于这个听记创建思维导图" -- "把会议内容整理成脑图" -- "<听记URL> 创建思维导图" -- "<听记URL> 帮我生成脑图" - -用户说"思维导图状态/脑图进度/思维导图好了吗" → `mind-graph status` - -### 查询听记列表 -用户说"我创建的听记/我发起的听记/我自己录的听记" → `list mine`(**仅**返回我自己创建的,不含他人共享给我的) -用户说"别人给我的听记/共享听记/共享给我的" → `list shared`(**仅**返回他人共享给我的,不含我创建的) -用户说"我可访问的听记/我能看到的听记/我有权限的听记/所有听记/全部听记" → `list all`(返回我可访问的**所有**听记 = mine + shared) -用户说"我的听记" → `list all`(**注意**:"我的听记"是模糊表述,用户通常期望看到所有可访问的听记,而不仅是自己创建的,默认走 `all`;仅当上下文明确表示"我自己创建/发起的"时才走 `mine`) -用户说"某时间段内的听记/按时间查听记/按关键词查听记" → 根据所属范围选择 `list mine`/`list shared`/`list all`,附加 `--start`、`--end`、`--query` 参数 - -**自然语言示例:** -- "查一下我最近的听记" -- "帮我找一下上周的听记" -- "有没有关于'周会'的听记" -- "看看别人共享给我的听记" -- "我这个月的听记有哪些" -- "帮我从今天听记里找需求评审" -- "上月听记里有没有提到OKR" -- "从我的听记里搜一下技术方案" -- "这周有没有关于复盘的会" -- "帮我找上周关于项目排期的听记" - -> **路由提示**: -> - 用户说"从我创建的/我发起的听记里找XX" → `list mine --query` -> - 用户说"我的听记里找XX"/"我可访问的听记"/"帮我找关于XX的听记" → `list all --query`("我的"是模糊表述,默认走 `all` 覆盖最广) -> - **仅当**用户明确强调"我自己创建/录制/发起的"时才走 `mine` -> - 含时间词(今天/上周/上月/这周/本月)时,必须同时附加 `--start` + `--end` 参数 - -### 获取听记内容 -用户说"听记详情/听记信息" → `get info` -用户说"摘要/总结/会议纪要/纪要" → `get summary` -用户说"关键字/关键词" → `get keywords` -用户说"原文/转写/录音文字/逐字稿" → `get transcription`(**默认拉取全部原文**,自动翻页直到拉完,**拉完后必须主动询问用户是否按发言人聚类**,详见 [获取听记语音转写原文](#获取听记语音转写原文) 的"四阶段工作流") -用户说"会议待办/听记待办/待办事项" → `get todos` -用户说"批量查询/查多个听记" → `get batch` -用户说"音频地址/音频链接/录音文件/下载录音/音频下载/视频文件/媒体地址" → `get audio` - -**转写原文拉取时机判断(必须遵守):** -- **明确要看原文** → 调用 `get transcription`,自动翻页拉取所有原文;**累积超过 12000 字符时暂停,询问用户是否继续** - - 示例:"帮我看看转写原文"、"分析一下这篇听记的原文"、"把逐字稿发给我" -- **未明确要看原文** → **不要**调用 `get transcription`,用其他更合适的命令响应 - - 示例:"查一下和悟空相关的听记" → 应走 `list`,不需要拉原文 - - 示例:"这个会议讲了什么" → 应走 `get summary`,不需要拉原文 - - 示例:"这个听记有哪些待办" → 应走 `get todos`,不需要拉原文 - -**自然语言示例:** -- "帮我看看这个听记的摘要" -- "这个会议讲了什么" -- "帮我看看会议的转写原文" -- "分析一下这篇听记的转写原文" → **默认拉全部** -- "这个听记有哪些待办" -- "帮我提取一下会议的关键词" -- "帮我看看这个听记的基本信息" -- "把这个听记的录音文件给我" -- "帮我下载这个听记的音频" -- "这个听记的音频地址是什么" -- "我想下载这次会议的录音" -- "帮我拿一下这个听记的视频文件" -- "给我这个听记的媒体下载链接" - -### 添加/管理听记成员权限 -用户说"把某人加到这个听记中/共享听记给某人/给某人添加听记权限/把听记分享给同事/让某人也能看这个听记" → `permission add` -用户说"添加参会者/批量添加参会者/添加成员/批量添加成员/加参会人/加入成员" → `permission add`(**注意:听记模块没有独立的"添加参会者/成员"命令,统一走权限接口**) -用户说"移除某人的权限/取消共享/不让某人看这个听记了" → `permission remove` - -**自然语言示例:** -- "把张三加到这个听记中" -- "帮我把这个听记共享给李四" -- "让小王也能看这个听记" -- "给团队的同事们添加这个听记的查看权限" -- "把这几个听记共享给 UID 123456 的同事,设置为可编辑" -- "帮我把这个听记分享给参会的人" -- "给这个听记加个协作者" -- "把听记的编辑权限给我同事" -- "添加张三为这个听记的参会者" -- "帮我批量添加参会者:张三、李四、王五" -- "给这个听记添加几个成员" -- "批量添加成员到这个听记" -- "把张三加为参会人" -- "移除张三对这个听记的权限" -- "取消小王对这个听记的访问" -- "不让某人看这个听记了" - -**路由规则:** -- 用户提到"加/添加/共享/分享/让...看/让...访问"等增加权限语义 → `permission add` -- 用户提到"添加参会者/加参会者/批量添加参会者/添加成员/加成员/批量添加成员/加参会人/添加参会人"等成员语义 → `permission add`(听记没有独立的"添加成员"接口,**统一走权限接口**) -- 用户提到"移除/取消/删除/不让...看"等移除权限语义 → `permission remove` -- 如果用户未指定权限类型,默认使用 `--policy 4`(可查看/下载/编辑) -- 如果用户未提供 member-uids,需要先引导用户提供目标成员的钉钉 UID(可通过 `dws contact user search --query "姓名"` 查询) - -**典型执行链路:** -1. 用户说"把张三加到这个听记中" → AI 需获取张三的 UID -2. 调用 `dws contact user search --query "张三" --format json` 获取 UID -3. 调用 `dws minutes permission add --ids --member-uids --policy 4 --format json` - -### 修改听记标题 -用户说"改听记标题/重命名听记/修改标题" → `update title` - -**自然语言示例:** -- "把这个听记的标题改成'Q2 复盘会议'" -- "帮我重命名一下这个听记" - -### URL 识别 -- **格式**: `https://shanji.dingtalk.com/app/transcribes/{taskUuid}` -- **示例**: `https://shanji.dingtalk.com/app/transcribes/76327569643231383535353939365f3436383537393431335f32` -用户传入听记 URL(如 `https://shanji.dingtalk.com/app/transcribes/xxx`),从 URL 提取 taskUuid,再执行对应的 get/update 操作 - -## 核心工作流 - -```bash -# 0. 发起听记(开始录音) -dws minutes record start --format json - -# 1. 查看我的听记列表 — 提取 taskUuid -dws minutes list mine --format json -dws minutes list mine --max 10 --next-token --format json -dws minutes list mine --query "周会" --format json - -# 1b. 查看共享给我的听记 -dws minutes list shared --max 20 --format json -dws minutes list shared --query "日报" --format json - -# 1c. 查看我有权限访问的所有听记(支持关键字和时间范围筛选) -dws minutes list all --format json -dws minutes list all --query "周会" --start "2026-03-01T00:00:00+08:00" --end "2026-03-20T23:59:59+08:00" --format json - -# 2. 获取 AI 摘要 -dws minutes get summary --id --format json - -# 3. 查看完整转写原文(拉完后默认按时间线返回,AI 必须主动追问"是否按发言人聚类") -dws minutes get transcription --id --format json -# 3a. 用户确认聚类 → AI 在本地按 speakerNick 分组并提取核心要点(无需新调用 dws) -# 3b. 用户提供"某某人讲了 XX" → AI 模糊匹配关键词后,引导确认替换发言人 -dws minutes speaker replace --id --from "发言人1" --to "李总" --format json - -# 4. 提取待办事项 -dws minutes get todos --id --format json - -# 4b. 获取音频/视频地址(用于下载或播放原始媒体文件) -dws minutes get audio --id --format json - -# 5. 修改标题 -dws minutes update title --id --title "新标题" --format json - -# 6. 更新纪要内容 -dws minutes update summary --id --content "新的纪要内容" --format json - -# 7. 录音控制(基于 start 返回的 taskUuid) -dws minutes record pause --id --format json -dws minutes record resume --id --format json -dws minutes record stop --id --format json - -# 8. 思维导图 -dws minutes mind-graph create --id --format json -dws minutes mind-graph status --id --format json - -# 9. 替换发言人 -dws minutes speaker replace --id --from "张三" --to "李四" --format json - -# 9b. 发言人段落总结(异步:create 触发 → 延迟 5s → 轮询 get,最多 20 次) -dws minutes speaker summary create --ids --format json -# 等待至少 5 秒... -dws minutes speaker summary get --ids --format json -# 若返回为空,继续等待 5s 后重试,最多 20 次 - -# 10. 添加个人热词 -dws minutes hot-word add --words "OKR,钉钉,Copilot" --format json - -# 11. 查找替换听记文字 -dws minutes replace-text --id --search "旧文字" --replace "新文字" --format json - -# 12. 文件上传转听记(三步流程) -# 12a. 创建上传会话,获取预签名 URL 和 sessionId -dws minutes upload create --file-name "meeting.mp4" --file-size 102400 --format json -# 12b. 用 HTTP PUT 上传文件到预签名 URL(不带 HEADER) -curl -X PUT "" -T "/path/to/meeting.mp4" -# 12c. 通知服务端上传完成,创建听记 -dws minutes upload complete --session-id --format json -# 12d.(可选)取消上传会话 -dws minutes upload cancel --session-id --format json -``` - -## 上下文传递表 - -| 操作 | 从返回中提取 | 用于 | -|------|-------------|------| -| `list mine` | `taskUuid`、`nextToken` | get/update 的 --id;翻页时 --next-token | -| `list shared` | `taskUuid`、`nextToken` | get/update 的 --id;翻页时 --next-token | -| `list all` | `taskUuid`、`nextToken` | get/update 的 --id;翻页时 --next-token | -| `get batch` | 各听记 `taskUuid` | 进一步查询详情 | -| `get audio` | 音频/视频 OSS 地址 | 用 HTTP GET 下载录音文件 / 在浏览器播放 | -| `record start` | `taskUuid`/`uuid` | record pause/resume/stop 的 --id | -| `upload create` | `sessionId`、`presignedUrl` | HTTP PUT 上传文件;upload complete/cancel 的 --session-id | -| `mind-graph create` | 任务状态 | mind-graph status 轮询 | - -## 错误响应诊断指南 - -当 `get info` / `get summary` / `get transcription` / `get todos` / `get keywords` / `get audio` 返回异常时,按以下决策表快速判断原因并决定下一步动作,避免盲目重试浪费轮次: - -### 错误快速决策表 - -| 错误现象 | 可能原因 | 正确处理 | 禁止动作 | -|----------|----------|----------|----------| -| `dingOpenErrcode=300` 且 `error_msg` 含 "taskUuid is invalid" | taskUuid 格式错误、不存在、或从上下文猜测/拼凑而来 | **立即停止当前 uuid,切换到 list 策略**:调用 `dws minutes list mine --max 10 --format json` 获取真实 uuid 列表,让用户选择或自动匹配最相关的一条。**严禁用同一个无效 uuid 重试**——真实案例中模型用同一个错 uuid 重试了 20 次全部失败 | 禁止用同一个无效 ID 重试哪怕 1 次;禁止从历史对话/文档/链接中猜测 uuid;禁止从十六进制编码字符串里截取数字拼凑新 uuid | -| stdout 完全为空,error_msg 也为空 | 鉴权过期 / 服务端临时不可用 | 最多重试 1 次;仍为空则告知用户「服务暂时不可用,请稍后再试」 | 禁止连续重试超过 2 次 | -| `dingOpenErrcode=403` 或含 "permission" / "forbidden" | 无权限访问该听记 | 告知用户无权限,建议联系听记创建者共享权限 | 禁止重试(权限问题不会因重试改变) | -| `dingOpenErrcode=404` 或含 "not found" | 听记已被删除或不存在 | 告知用户该听记不存在或已被删除 | 禁止重试 | -| 返回 JSON 但关键字段(如 `result`)为 null | 听记处理中(ASR/AI 摘要尚未完成) | 告知用户听记内容正在生成中,建议等待 1-2 分钟后再查询 | 禁止立即重试(处理需要时间) | -| 网络超时 / 连接错误 | 网络不稳定 | 最多重试 1 次 | 禁止连续重试超过 2 次 | - -### 重试约束(必须遵守) - -- 同一个 taskUuid + 同一个命令,最多重试 **1 次**(总计最多调用 2 次) -- 重试前必须检查:错误是否属于「可重试」类别(仅 stdout 为空 / 网络超时 属于可重试) -- `dingOpenErrcode=300/403/404` 均为「不可重试」错误,立即停止并给出明确诊断 -- 如果第一次调用返回了结构化错误(含 errcode),禁止换一个别名 flag(如 --task-uuid 换 --id)重试——问题不在 flag 名称 - -### Flag 别名兼容说明 - -所有需要传入 taskUuid 的子命令(`get info/summary/keywords/transcription/todos/audio`、`mind-graph create/status` 等)均支持以下 flag 名称,自动降级到 `--id`: - -| Flag 名称 | 状态 | 说明 | -|-----------|------|------| -| `--id` | 推荐 | 唯一的正式 flag 名称,文档和示例中统一使用 | -| `--url` | 隐藏别名 | 兼容听记 URL 传入场景 | -| `--task-uuid` | 隐藏别名 | 兼容钉钉 OpenAPI 原生字段名 taskUuid | -| `--uuid` | 隐藏别名 | 兼容通用 UUID 命名习惯 | - -传入 `--task-uuid` / `--uuid` / `--url` 时,CLI 会自动降级到 `--id` 处理,无需报错后用 `--id` 重试。 - -## 关键词搜索与筛选最佳实践 - -### 服务端筛选优先原则(必须遵守) - -`list mine` / `list shared` / `list all` 均支持 `--query`(关键词)、`--start`(开始时间)、`--end`(结束时间)三个筛选参数,筛选在服务端完成,效率远高于全量拉取后本地过滤。 - -**禁止全量拉取后本地过滤**:当用户提供了搜索关键词或时间范围时,必须使用 `--query` / `--start` / `--end` 参数在服务端筛选,严禁先 `list all` 拉全量再在 prompt 中用 Python/grep 做本地关键词匹配。 - -### 自然语言到参数映射表 - -| 用户表述 | 映射参数 | 示例命令 | -|----------|----------|----------| -| "今天的听记" | `--start` 今日 00:00 `--end` 当前时间 | `dws minutes list all --start "2026-05-11T00:00:00+08:00" --end "2026-05-11T23:59:59+08:00"` | -| "上周的听记" | `--start` 上周一 00:00 `--end` 上周日 23:59 | `dws minutes list all --start "2026-05-04T00:00:00+08:00" --end "2026-05-10T23:59:59+08:00"` | -| "上月的听记" | `--start` 上月 1 日 `--end` 上月最后一天 | `dws minutes list all --start "2026-04-01T00:00:00+08:00" --end "2026-04-30T23:59:59+08:00"` | -| "最近的听记" | 不传时间参数,使用默认排序 | `dws minutes list mine --max 10` | -| "关于XX的听记" | `--query "XX"` | `dws minutes list all --query "双叶汽车"` | -| "找XX相关的听记" | `--query "XX"` | `dws minutes list all --query "安利"` | -| "本月关于XX的听记" | `--query` + `--start` + `--end` | `dws minutes list all --query "ROI" --start "2026-05-01T00:00:00+08:00" --end "2026-05-31T23:59:59+08:00"` | -| "帮我从今天听记里找XX" | `--query` + `--start` + `--end`(今日范围) | `dws minutes list mine --query "需求评审" --start "2026-05-11T00:00:00+08:00" --end "2026-05-11T23:59:59+08:00"` | -| "上月听记里有没有提到XX" | `--query` + `--start` + `--end`(上月范围) | `dws minutes list mine --query "OKR" --start "2026-04-01T00:00:00+08:00" --end "2026-04-30T23:59:59+08:00"` | -| "从我的听记里搜XX" | `--query "XX"`(走 `list mine`) | `dws minutes list mine --query "技术方案"` | -| "这周有没有关于XX的会" | `--query` + `--start` + `--end`(本周范围) | `dws minutes list mine --query "复盘" --start "2026-05-05T00:00:00+08:00" --end "2026-05-11T23:59:59+08:00"` | - -### 组合筛选示例 - -```bash -# 按关键词搜索 -dws minutes list all --query "神威数智需求变更沟通会议" --format json - -# 按时间范围搜索 -dws minutes list all --start "2026-04-01T00:00:00+08:00" --end "2026-04-30T23:59:59+08:00" --format json - -# 关键词 + 时间范围组合 -dws minutes list all --query "精益生产" --start "2026-04-01T00:00:00+08:00" --end "2026-04-30T23:59:59+08:00" --format json - -# 按关键词搜索共享听记 -dws minutes list shared --query "安利" --max 20 --format json - -# 从我的听记里按关键词 + 今日时间范围搜索("帮我从今天听记里找需求评审") -dws minutes list mine --query "需求评审" --start "2026-05-11T00:00:00+08:00" --end "2026-05-11T23:59:59+08:00" --format json - -# 从我的听记里按关键词 + 上月时间范围搜索("上月听记里有没有提到OKR") -dws minutes list mine --query "OKR" --start "2026-04-01T00:00:00+08:00" --end "2026-04-30T23:59:59+08:00" --format json - -# 从我的听记里仅按关键词搜索("从我的听记里搜技术方案") -dws minutes list mine --query "技术方案" --format json -``` - -### 搜索无结果时的 Fallback 策略 - -1. `--query` 无结果 --> 尝试缩短关键词(如「神威数智需求变更沟通会议」缩短为「神威数智」) -2. 仍无结果 --> 扩大搜索范围(`list mine` 换为 `list all`) -3. 仍无结果 --> 放宽时间范围(扩大 `--start` / `--end`) -4. 最终无结果 --> 如实告知用户未找到匹配的听记,建议用户确认关键词或时间范围 - -## 转写翻页异常处理 - -### 翻页空响应防御规则(必须遵守) - -`get transcription` 使用 `--next-token` 进行分页查询。在自动翻页过程中,可能遇到以下异常情况: - -| 异常现象 | 含义 | 处理方式 | -|----------|------|----------| -| 返回 JSON 中不包含 `nextToken` 字段 | 已到达最后一页,无更多数据 | 正常终止翻页,拼合已拉取内容 | -| `nextToken` 字段值为空字符串 `""` | 等同于无 nextToken,已到达最后一页 | 正常终止翻页 | -| 使用同一个 `next-token` 值连续 2 次返回 stdout 为空 | 服务端临时异常或该 token 已失效 | 立即终止翻页,不再重试;基于已拉取的内容进行分析 | -| 返回 JSON 但 `paragraphList` 为空数组 `[]` | 当前页无内容(可能是中间空页) | 如果有 nextToken 则继续翻页;如果无 nextToken 则终止 | - -### 翻页流程伪代码 - -``` -已累积文本 = "" -当前token = ""(首页不传) -连续空响应计数 = 0 -上一次token = null - -loop: - 调用 get transcription --id [--next-token 当前token] - - if stdout 为空 or 返回无效: - if 当前token == 上一次token: - 连续空响应计数 += 1 - if 连续空响应计数 >= 2: - 终止翻页,输出已累积内容 - break - else: - 连续空响应计数 = 1 - 上一次token = 当前token - continue - - 连续空响应计数 = 0 - 拼合本页转写文本到已累积文本 - - if len(已累积文本) > 12000: - 暂停翻页 - 询问用户:"当前已拉取约 N 字符,已达上限,是否继续?" - if 用户拒绝: break - - if 返回中无 nextToken 或 nextToken 为空: - break(最后一页) - - 上一次token = 当前token - 当前token = 返回的 nextToken -``` - -### 严禁的翻页行为 - -- [禁止] 同一个 next-token 值连续重试超过 2 次 -- [禁止] 翻页失败后换用不同的 flag 名(如 --next-token 换成 --nextToken)重试 -- [禁止] 累积超过 12000 字符后不暂停,继续拉取所有页 -- [禁止] 拉到空页就立即放弃全部已累积内容,应基于已有内容进行分析 - -## 注意事项 -- `taskUuid` 是听记的唯一标识,所有 get/update 操作均以此为入参 -- `record start` 对应 MCP 工具 `execute_listening_note_command` 的 `cmd=create`,通常会返回可继续控制录音的 `taskUuid/uuid` -- `record pause` / `record resume` / `record stop` 对应 `cmd=pause/resume/end`,需要传入 `--id`(映射 MCP 入参 `uuid`) -- 如果用户传入听记 URL(格式: `https://shanji.dingtalk.com/app/transcribes/`),直接从路径末段提取 taskUuid 作为 `--id` 参数,无需再调用 list 查询 -- `list mine`、`list shared`、`list all` 统一走 `list_by_keyword_and_time_range` 链路,通过 `belongingConditionId` 区分(`created` / `shared` / `noLimit`) -- 三个 list 命令均支持 `--max`、`--next-token` 分页及 `--query`、`--start`、`--end` 筛选 -- `list mine`、`list shared` 默认每页 20 条,`list all` 默认每页 10 条 -- `get summary` 返回 AI 生成的结构化 Markdown 摘要 -- `get transcription` 的 `--direction` 控制时间排序: 0=正序(默认), 1=倒序;当用户明确要求查看/分析转写原文时,默认自动翻页拉取全部原文(不需要用户手动说"拉第一页"),如果用户意图不是专门看原文(如查列表、看摘要),则不应主动调用此命令 -- `get transcription` 默认按"时间线"返回各段落,**拉完后 AI 必须主动追问用户"是否需要按发言人分组聚类并提取核心内容"**;用户确认后 AI 在本地完成聚类与摘要,并进一步引导用户通过关键词模糊匹配(如"李总主要讲了战略规划")确认"发言人编号 ↔ 真实姓名"的映射,最终调用 `speaker replace` 写回。完整工作流见对应命令章节的"四阶段工作流" -- `get batch` 支持一次查询多个听记,用逗号分隔 taskUuid -- `get audio` 返回听记原始音频/视频文件的 OSS 地址,操作人需拥有该听记"读"权限及以上;以下场景不返回地址:听记已被删除、A1 无痕模式听记、临存过期的听记(媒体未准备好或临时存储已过期) -- `update summary` 全量覆盖纪要内容,不触发 AI 重新生成;适用于手动编辑或 AI Agent 修改纪要 -- `mind-graph create` 触发异步任务,需通过 `mind-graph status` 轮询状态(0=进行中,1=成功,2=失败) -- `speaker replace` 精确匹配源发言人昵称,替换所有段落并自动更新纪要和待办中的发言人信息 -- `hot-word add` 支持逗号分隔批量添加,每个热词不超过 10 个汉字或 5 个英文单词 -- `replace-text` 区分大小写精确匹配,同时替换转写段落和纪要摘要中的文字 -- 文件上传流程为三步:`upload create` → HTTP PUT 上传 → `upload complete`;`upload complete` 幂等,同一 sessionId 重复调用不会重复创建 -- `upload create` 返回的 `presignedUrl` 用于 HTTP PUT 上传文件,上传时不需要带任何 HEADER -- 所有需要 taskUuid 的子命令均支持 --task-uuid / --uuid / --url 作为 --id 的隐藏别名,传入后自动降级,无需报错重试 -- 同一个 taskUuid + 同一个命令,最多重试 1 次(总计最多调用 2 次),dingOpenErrcode=300/403/404 为不可重试错误 -- 当用户提供关键词或时间范围时,必须使用 --query / --start / --end 在服务端筛选,严禁全量拉取后本地过滤。**严禁在 dws 命令后拼接 shell 管道做本地过滤**(如 `| grep "关键词"` / `| head -50` / `python -c "import json;..."` / `> /tmp/xxx.txt && grep ...`),这些写法在 Windows 沙箱里 100% 失败(`head`/`grep`/`wait` 不是 Windows 内置命令),即使在 macOS 上也会因 list 本身失败导致整个管道链路断掉。正确做法是始终使用 `--query` / `--start` / `--end` / `--max` 等 cli 内置参数在服务端完成筛选 -- `dws minutes list` 后面**必须跟 `mine` / `shared` / `all` 子命令**,不能直接 `dws minutes list --start ... --end ...`(会报 `unknown flag: --start`)。正确写法:`dws minutes list mine --start ... --end ...` 或 `dws minutes list all --query "关键词" --start ... --end ...` -- get transcription 翻页时,同一个 next-token 连续返回空 2 次即终止翻页,不再重试 -- **跨平台兼容性**:悟空运行环境可能是 Windows cmd / PowerShell / macOS bash,**严禁在 dws 命令中使用任何依赖特定 shell 的工具或管道**,包括但不限于:`| head`、`| grep`、`| tail`、`| wc`、`& wait`、`> %TEMP%\xxx`、`timeout /t 5`、`Start-Sleep`、`python -c "..."` 等。所有数据筛选、截断、过滤都必须通过 dws cli 自身的参数完成(`--query`/`--start`/`--end`/`--max`/`--next-token`) - -### 错误恢复策略(AI 遇到以下错误时必须自动执行对应恢复动作) - -| 错误类型 | 错误信息特征 | 恢复动作 | 严禁行为 | -|----------|-------------|----------|----------| -| 命令结构错误 | `unknown command "info"` / `unknown command "get"` (后无子命令) / `error[validation]: unknown flag: --start` (在 `list` 而非 `list mine/shared/all` 上) | **立即参照本文档顶部"命令层级结构"纠正**。常见错误:① `dws minutes info` → 应为 `dws minutes get info`;② `dws minutes get --id` → `get` 后缺子命令,应为 `get info/summary/transcription` 等;③ `dws minutes list --start` → `list` 后缺 scope,应为 `list mine/shared/all --start` | 严禁在命令结构报错后只换参数名不修正命令层级;严禁把 `get` 当作独立命令使用(后面必须跟 info/summary/transcription/keywords/todos/audio/batch) | -| 参数名错误 | `unknown flag: --task-uuid` / `unknown flag: --uuid` / `unknown flag: --start-time` / `unknown flag: --end-time` | 立即调用 `dws minutes <子命令> --help` 查询正确参数名;minutes 模块统一用 `--id`,时间统一用 `--start` / `--end`(不是 `--start-time` / `--end-time`) | 严禁用同一个错误的参数名重试;严禁在不同子命令间尝试 --uuid / --task-uuid / --id 三种名称反复试错;严禁凭记忆猜测参数名,必须查 --help | -| UUID 无效 | `taskUuid is invalid` / `dingOpenErrcode=300` | **第一时间切换策略**:调用 `dws minutes list mine --max 10 --format json` 获取真实可用的 uuid 列表,让用户选择或自动匹配最相关的一条。**真实案例中模型用同一个错 uuid 重试了 20 次全部失败——这是 minutes 模块失败率最高的错误模式,必须零容忍** | 严禁用同一个无效 uuid 重试哪怕 1 次(errcode=300 是不可重试错误);严禁从历史对话/文档/链接中猜测 uuid;严禁从十六进制编码字符串里截取部分数字拼凑新 uuid;严禁不切 list 就放弃 | -| 命令返回空 stdout | `get summary` / `get transcription` 返回空内容(stdout 为空,error_msg 也为空) | 1) 先调用 `dws minutes get info --id --format json` 确认听记是否存在且状态正常;2) 如果 info 也为空或报错,说明 uuid 本身有问题,回退到 list 命令重新获取 | 严禁在 stdout 为空时重复调用同一命令超过 2 次;严禁把空返回当作"没有内容"直接告知用户而不做任何排查 | -| 翻页 next-token 返回空 | `get transcription --next-token xxx` 返回空内容 | 同一个 next-token 返回空 **1 次**即视为到达末尾(has_more=false),立即终止翻页,基于已累积内容继续处理 | 严禁同一个 next-token 连续重试超过 2 次;严禁换不同的 next-token 值盲目尝试 | -| 时间格式解析失败 | `cannot parse time` / `parse fail` | 标准格式三选一:`2026-03-23T14:00:00+08:00`(ISO-8601 带时区)/ `2026-03-23 14:00:00`(无时区默认 +08:00)/ `2026-03-23`(纯日期) | 严禁使用 Unix 时间戳、`2026/03/23`、`Mar 23, 2026` 等非标准格式 | -| 权限不足 | `AUTH_PERMISSION_DENIED` / `Permission denied` / `user is not minutes creator` | 1) 如果是 `get transcription` 报权限错误,**自动降级**尝试 `get summary`(摘要通常不需要下载权限);2) 如果降级也失败,引导用户:"这份听记是别人分享给你的,你可以让原作者将你加为协作者,或者用 `dws minutes list shared` 查看共享给你的听记";3) 如果需要其他人的听记内容,可先用 `dws minutes list shared` 确认有无共享记录 | 严禁权限报错后用同一 uuid 反复重试;严禁只回复"Permission denied"而不给用户可操作的下一步 | -| 资源不存在 | `P_DataNotFound` / `dingOpenErrcode=404` | 与 `taskUuid is invalid`(uuid 格式错误)**区分对待**:`P_DataNotFound` 表示 uuid 格式正确但对应的听记不存在(可能已被删除或过期)。告知用户"该听记可能已被删除或已过期",然后建议用 `dws minutes list mine` 或 `list all` 查找替代 | 严禁把 `P_DataNotFound` 和 `taskUuid is invalid` 混为一谈;严禁 `P_DataNotFound` 后还换 uuid 格式重试 | -| 认证过期 | `not_authenticated` / `未登录` / `认证信息已失效` | 直接**重试刚才失败的命令**(最多两次),系统会自动刷新或重新授权;不要让用户重新提问。**注意**:长会话(>100 步)中 token 可能中途过期,这不是用户的问题 | 严禁认证过期后直接放弃整个任务;严禁让用户自己处理认证 | -| 上传业务报错 | `business error: success=false` / `Server Code: ERROR`(出现在 `upload create`) | 1) 检查文件名是否含特殊字符(建议用纯英文/数字文件名重试);2) 检查 `--file-size` 单位是否正确(必须是**字节**,不是 KB/MB);3) 确认音频格式是否支持(mp3/mp4/wav/m4a/aac);4) 最多重试 **1 次**,仍然失败则如实告知用户"上传服务暂时不可用",给出错误 Trace ID 供排查 | 严禁同一参数重试超过 2 次;严禁在错误信息未变化时反复重试期望随机成功;严禁尝试 `--json` / `-f json '{"...":...}'` 等不存在的输入格式——`--format json` / `-f json` 仅控制**输出格式**,不是输入参数 | - -### 间接意图识别(用户 query 未直接提及听记命令时的调用策略) - -当用户的 query 不是直接要求"查听记/看摘要",而是隐含需要听记数据的高阶任务时,AI 必须自动拆解并先完成听记数据采集。 - -> **【核心判断原则】** 用户 query 中只要涉及"会议/讨论/沟通"等**口头交流**产出的信息,听记就是不可替代的数据源。即使用户没说"听记"二字,只要任务的完成依赖于"会议中说了什么",就必须走 `dws minutes` 采集。**文档、日程、聊天记录无法替代听记**——文档是书面产出,日程只有标题和时间,聊天记录是文字沟通,唯有听记才包含会议的完整发言内容和 AI 摘要。 - -#### 间接意图 query 模式速查表 - -| 用户 query 模式 | 典型真实 query 示例 | 隐含的听记需求 | 正确的第一步 | -|----------------|-------------------|---------------|-------------| -| **报告/分析类**:要求基于某个主题或业务重新撰写报告 | "从悟空的商业模式分析,重新写一下市场感知报告" | 用户的报告素材来源于历史会议讨论,需要从听记中提取相关主题的会议内容作为写作输入 | `dws minutes list all --query "<主题关键词>" --max 20 --format json` → 对匹配的听记逐篇 `get summary` 提取素材 → 再基于素材撰写报告 | -| **分类总结类**:要求按类别(问题/事故/AI工作等)汇总工作成果 | "根据我的工作情况总结,分成问题应收、异常事故处理、ai相关工作和其他几部分总结";"最新文档啥也没有啊,我要你根据我的工作情况总结" | 用户需要的"工作情况"不仅存在于文档中——会议讨论、评审结论、决策记录都在听记里。尤其当用户明确说"文档啥也没有"时,听记是**唯一剩余的结构化工作记录来源** | `dws minutes list mine --start <起始日期> --end <截止日期> --format json` → 逐篇 `get summary` → 按用户要求的分类维度(问题应收/事故处理/AI工作/其他)对摘要内容进行归类整理 | -| **多源聚合类**:用户明确要求补充听记/会议数据源,或抱怨当前数据源不全 | "你这个好像只是基于日程,我要的是我的对话、私聊、群聊、日程、会议、文档修改等相关的所有动作";"根据今天的聊天记录和听记总结工作日报" | 用户**点名要求**多数据源聚合,其中"会议"对应的数据源就是听记。即使 query 中还提到了聊天、日程、文档等,听记侧的数据采集**不能被省略** | 听记侧**必跑**:`dws minutes list mine --start <起始日期> --end <截止日期> --format json` → 逐篇 `get summary`;再并行采集其他数据源(聊天/日程/文档)→ 最终汇总 | -| **Skill 封装类**:要求把数据聚合逻辑封装成可复用的 skill 或脚本 | "把这个封装成日报skill";"做成自动化脚本" | 封装之前必须先**验证底层数据获取链路是否通畅**,不能跳过数据验证直接写 skill 框架 | 先用 `dws minutes list mine --start --format json` 验证听记数据可达且格式正确,再设计 skill 结构 | -| **日报/周报/月报类**:要求生成工作汇报 | "帮我写日报/周报";"帮我生成本周工作汇报" | 近期听记摘要是工作记录的核心来源之一 | `dws minutes list mine --start <起始日期> --end <截止日期> --format json` → 逐篇 `get summary` → 汇总写入报告 | -| **客户/项目类**:按客户名或项目名搜索相关信息 | "外资背景的客户信息总结";"某某客户相关的所有信息" | 需要按关键词搜索听记 | `dws minutes list all --query "<客户名/项目名>" --format json` → `get summary` | -| **时间范围批量类**:梳理一段时间内的所有会议记录 | "梳理 4 月至今所有会议记录";"本周所有听记" | 明确时间范围的批量听记获取 | `dws minutes list mine --start "2026-04-01T00:00:00+08:00" --end "2026-05-08T23:59:59+08:00" --max 50 --format json` | - -#### 间接意图识别的三条铁律 - -**铁律 A:用户的任务包含"会议/讨论/沟通"类信息需求 → 听记采集是必跑前置步骤,不可跳过** -- 即使用户 query 中没有出现"听记"二字,只要任务完成需要"会议上说了什么/讨论了什么/达成了什么结论",就必须走 `dws minutes` 采集 -- 典型信号词:报告、分析、总结、汇总、复盘、回顾、工作情况、商业模式、市场感知、客户沟通 - -**铁律 B:用户说"文档/数据啥也没有" → 听记的优先级反而更高** -- 当用户明确反馈文档不足时,听记(会议记录)往往是**唯一还有内容的结构化数据源** -- 此时 AI 的正确反应不是"那我也没办法",而是主动切到听记侧采集:"让我先看看你最近的听记记录中有没有相关内容。" - -**铁律 C:多源聚合场景中,听记侧 0 调用 = 严重失败** -- 当用户 query 中同时提到"聊天记录 + 听记 / 会议 + 文档"等多个数据源时,每个被提及的数据源都必须有对应的采集动作 -- 听记侧的采集不能因为"其他数据源已经够了"而被省略——用户点名要了就必须给 - -## 自动化脚本 - -| 脚本 | 场景 | 用法 | -|------|------|------| -| [minutes_recent_summary.py](../../scripts/minutes_recent_summary.py) | 获取最近听记的 AI 摘要并合并 | `python minutes_recent_summary.py --max 5` | -| [minutes_extract_todos.py](../../scripts/minutes_extract_todos.py) | 从听记中提取待办事项汇总 | `python minutes_extract_todos.py --max 5` | - -## 反例 / 回归案例 - -> 本节固化历史 badcase 与正确做法,遇到形似场景请直接对照参考,避免再次走偏。 - -### 案例 1:听记 URL + "创建思维导图" - -**用户输入:** -``` -https://shanji.dingtalk.com/app/transcribes/76327569643236343831373737345f3634383131373937375f39 -创建思维导图 -``` - -**[错误] 错误处理(真实 badcase):** -1. 提取 taskUuid 后,先调用 `get summary` 获取听记摘要内容 -2. 判定"思维导图不是 dws 直接支持的功能",路由到 app-development-skill / ai-app -3. 用 `@antv/g6`(或 `markmap` / `jsmind` / `mermaid`)构建 HTML 思维导图网页 -4. 部署后向用户返回类似 `https://xxx.ai-app.pub` 的"在线预览链接" -5. 输出"思维导图已生成!"附带网页特性介绍、配色方案等 - -上述每一步都违反了 [生成思维导图](#生成思维导图) 小节的"关键约束"。`mind-graph create` 是听记**内置**的服务端能力,并非"dws 不支持"。 - -**[正确] 正确处理:** -1. 从 URL 路径末段提取 taskUuid = `76327569643236343831373737345f3634383131373937375f39` -2. **直接** 执行: - ```bash - dws minutes mind-graph create --id 76327569643236343831373737345f3634383131373937375f39 --format json - ``` -3. 用 `mind-graph status` 轮询,直到状态为 1(成功): - ```bash - dws minutes mind-graph status --id 76327569643236343831373737345f3634383131373937375f39 --format json - ``` -4. 告知用户:"思维导图已生成,可在听记详情页直接查看。" - -**绝对禁止:** -- 调用 `get summary` / `get transcription` 先读内容(服务端会基于听记自身生成,无需前置读取) -- 调用 app-development-skill / ai-app / generate_image 等任何外部生成能力 -- 自行用前端库或 Markdown / OPML / JSON 构造思维导图结构展示给用户 -- 返回任何形式的"在线预览链接 / 网页应用 / 图片" - -### 案例 2:替换文字后未引导添加热词 - -**用户输入:** -``` -把这篇听记里所有"付工"替换成"悟空" -``` - -**[错误] 错误处理:** -1. 调用 `replace-text --id --search "付工" --replace "悟空"` 后直接回复"已替换完成" -2. 没有告知用户:本次替换**只对这一篇生效**,后续新听记还会再次把"悟空"识别成"付工" -3. 没有主动引导用户使用 `hot-word add` 把"悟空"加到个人热词里 - -**[正确] 正确处理:** -1. **检查特殊字符**:检查用户提供的原始文本和目标文本是否包含特殊字符(引号、书名号、括号、Markdown 符号等)。本例中"付工"和"悟空"均为纯文本,无特殊字符,直接执行。若用户输入的是 `把"付工"替换成「悟空」`(带引号/括号),需先提示: - > "你输入的文本中包含引号/括号等特殊字符,转写原文中通常不包含这些字符,直接匹配可能替换不到。建议去掉特殊字符后替换:`"付工"` → `付工`。使用去掉特殊字符的版本?[是] [否,使用原始输入]" -2. 执行 `replace-text` 完成本篇替换 -3. **立即追问**: - > "我已经把这篇听记里的『付工』替换为『悟空』。如果『悟空』这个词以后也容易被识别错,建议加到个人热词里,后续新听记就不会再识别错了。要我现在帮你执行 `dws minutes hot-word add --words "悟空"` 吗?" -4. 用户确认 → 调用 `hot-word add --words "悟空"`;用户拒绝 → 结束,不再追问 - -### 案例 3:不必要地拉取全部转写原文 - -**用户输入:** -``` -查一下和悟空相关的听记 -``` - -**[错误] 错误处理:** -1. 调用 `list mine --query "悟空"` 查到听记列表 -2. 对每条听记依次调用 `get transcription` 拉取全部转写原文 -3. 把大量原文全部展示给用户 - -用户只是想查一下列表,根本不需要看转写原文。大量拉取原文既造成不必要的性能开销,也会让用户被信息淹没。 - -**[正确] 正确处理:** -1. 调用 `list mine --query "悟空"` 或 `list all --query "悟空"` 返回听记列表 -2. 直接把列表结果展示给用户 -3. 不调用 `get transcription`——因为用户没有要求看原文 - -**另一组对比:** - -**用户输入:** -``` -帮我分析一下这篇听记的转写原文 -``` - -**[错误] 错误处理:** -1. 调用 `get transcription --id ` 只拉了第一页就停了 -2. 展示部分原文,告诉用户"如果要看更多请传入 next-token" - -用户说的是"分析转写原文",意图很明确是要看完整原文,不应该让用户手动翻页。 - -**[正确] 正确处理:** -1. 调用 `get transcription --id `,拿到第一页和 `nextToken` -2. **自动继续调用** `get transcription --id --next-token ` -3. 每次拼合后检查累积字符数: - - **未超过 12000 字符** → 继续自动翻页 - - **超过 12000 字符** → 暂停,提示用户:"当前已拉取约 X 字符的转写内容,已达到单次处理上限。是否继续拉取后续内容?" -4. 用户确认继续 → 接着翻页;用户拒绝 → 停止翻页,基于已拉取内容进行分析展示 - -### 案例 4:拉完转写后只输出时间线原文,未引导发言人聚类与替换 - -**用户输入:** -``` -帮我把这篇听记的转写原文拉出来分析一下 -``` - -**[错误] 错误处理(真实 badcase):** -1. 调用 `get transcription` 自动翻页拉完全部原文 -2. 直接把按时间戳穿插的"发言人1: ... / 发言人2: ... / 发言人1: ..."大段原文全部丢给用户,结束流程 -3. 用户后续追问"李总主要讲了什么",AI 又要重新读一遍原文才能回答 -4. 即使用户后来说"发言人1 就是李总",AI 也只是在自己回复里口头改一改,**不调用 `speaker replace`**,听记本身的发言人映射没有任何变化 - -按时间线穿插的原文对人**极其不友好**——同一个发言人的内容散落在不同时间点,用户很难看清"某个人讲了什么"。而且听记里的『发言人1/发言人2』占位符如果一直不被替换为真实姓名,用户每次回看都要靠脑补才能对上号。 - -**[正确] 正确处理:** -1. 调用 `get transcription` 自动翻页拉完全部原文(注意 12000 字符上限保护) -2. **拉完后立即追问**:"已拉取完整转写原文(共 N 段,X 个发言人)。当前默认按时间线返回。是否需要我帮你**按发言人分组聚类**,并提取每位发言人的**核心发言要点**?" -3. 用户确认 → AI **本地完成聚类**(按 `speakerNick` 分组),每位发言人输出 3-5 条核心要点,再追问:"如果你能告诉我『某某人主要讲了什么』(如『李总主要讲了战略规划』),我可以根据关键词帮你**自动匹配**对应的发言人,并把『发言人1/发言人2』替换成真实姓名。" -4. 用户回复『李总主要讲了战略规划』→ AI 抽取关键词『战略规划』,在已聚类的各发言人核心要点里做模糊匹配;找到唯一高置信候选『发言人1』→ 引导确认:"『发言人1』很可能就是你说的『李总』(命中关键词:战略规划、AI 化转型)。是否需要把这篇听记里的『发言人1』全部替换为『李总』?确认后我会执行 `dws minutes speaker replace --id --from "发言人1" --to "李总"`" -5. 用户确认 → **立即调用** `dws minutes speaker replace --id --from "发言人1" --to "李总" --format json`,执行成功后告知用户"已替换,纪要与待办中的发言人也已同步更新" - -**绝对禁止:** -- 拉完转写就只丢一大段时间线原文给用户,不做聚类、不主动引导 -- 用户提供"某某人讲了 XX"后,AI 在自己脑内/回复里"假装替换"了,但**不实际调用** `speaker replace` -- 在置信度不足(多候选)时仍然给出唯一答案,不让用户参与挑选 -- 把『发言人1 → 李总』的映射写到用户回复里就完事,听记本身没有任何写回 - -### 案例 5:用户查某人在听记中说了什么,总结完不引导替换发言人 - -**用户输入:** -``` -帮我看看这个听记里张三说了什么 -https://shanji.dingtalk.com/app/transcribes/76327569643231383535353939365f3436383537393431335f32 -``` - -**[错误] 错误处理(真实 badcase):** -1. 从 URL 提取 taskUuid,调用 `get transcription` 拉取转写原文 -2. 发现转写中只有"发言人1/发言人2/发言人3",没有"张三" -3. AI 凭原文推断"发言人2 可能是张三"(因为内容提到了产品需求),直接把发言人2 的内容当作张三的输出给用户 -4. 总结输出完就结束了,**没有引导用户确认推断是否正确** -5. **没有引导用户替换发言人**——听记本身还是显示"发言人2",下次用户打开听记还是看不出谁是张三 - -上述做法有两个严重问题:① 推断结果未经用户确认就当作事实输出,可能张冠李戴;② 即使推断正确,也没有调用 `speaker replace` 写回听记,用户下次看还是"发言人2"。 - -**[正确] 正确处理:** -1. 从 URL 提取 taskUuid,调用 `get transcription` 自动翻页拉取全部转写 -2. **Step 2 声纹标注检查**:检查转写中是否已有"张三"作为 speakerNick → 本例中没有,只有匿名编号 -3. **Step 3 转写原文推断**:在原文中寻找线索——例如其他人说"张三你来汇报一下",紧接着"发言人2"开始发言;或者"发言人2"的内容大量涉及产品需求(与用户描述的张三角色吻合) -4. 推断出"发言人2"可能是张三后,**必须向用户确认**: - > "根据转写内容分析,以下发言最可能是张三的: - > 「接下来我汇报一下 Q3 的产品规划,主要有三个方向...」 - > 确认是张三吗?" -5. 用户确认 → **Step 6 结构化总结输出**:提取发言人2 的全部发言,输出张三的核心观点、关键决策、待办等 -6. **Step 7 引导替换发言人**(必须执行): - > "目前这篇听记中张三的发言仍显示为『发言人2』。要我帮你把听记里的『发言人2』全部替换为『张三』吗?替换后纪要和待办中的发言人也会同步更新。" -7. 用户确认 → **先通讯录查询获取 dingUid**:调用 `dws contact user search --query "张三" --format json` - - 唯一匹配(如返回 userId=123456789)→ 执行 `dws minutes speaker replace --id --from "发言人2" --to "张三" --target-uid 123456789 --format json` - - 多个匹配 → 列出候选(姓名+部门+userId)让用户选择后执行 - - 无匹配 → 提示用户通讯录未找到,执行不带 `--target-uid` 的替换:`dws minutes speaker replace --id --from "发言人2" --to "张三" --format json` -8. 替换成功后追问:"还有其他发言人需要我帮你识别和替换吗?" - -**绝对禁止:** -- 推断"发言人X 是张三"后不向用户确认就直接输出总结——可能张冠李戴 -- 总结完就结束,不引导替换发言人——用户下次看听记还是"发言人2" -- 只在回复里说"发言人2 就是张三"但不调用 `speaker replace`——听记本身没有任何变化 -- Step 3 推断不出来时直接告知"找不到张三"就结束——应继续走 Step 4 多路并发推断(通讯录/文档/日程/聊天记录) - -### 案例 6:通过通讯录 + 部门角色 + 转写线索三路印证推断发言人(真实复盘) - -> 这是一次**完整走完 Step 1~7 全流程**的真实案例,重点演示 Step 4 多路并发身份推断如何与 Step 3 转写原文线索互相印证,以及如何识别"花名相似但不是同一人"的陷阱。 - -**用户输入:** -``` -https://shanji.dingtalk.com/app/transcribes/ 分析下木兰讲了什么 -``` - -**[正确] 完整执行链路:** - -**Step 1:定位听记并读取转写** -- 从 URL 末段提取 taskUuid -- `dws minutes get transcription --id --format json` 自动翻页拉取全部(注意 12000 字符上限保护) - -**Step 2:声纹标注检查** -- 转写中所有 `speakerNick` 都是匿名编号(发言人1/2/3/4),**未命中** → 进入 Step 3 - -**Step 3:转写原文内推断(同时并发启动 Step 4,不串行等待)** - -先做粗粒度的发言人画像,列出每位发言人的发言量、主题、关键互动信号: - -| 发言人 | 发言特征 | 互斥线索(说明 TA "不是谁")| -|--------|----------|------| -| 发言人1 | 发言较多,集中在 UI/交互设计:A/B 面切换、按钮位置、页面层级、设备号承接页 | 1013s 提到「**木风**也给了一些各种状态」→ 木风 ≠ 发言人1 | -| 发言人2 | 发言最多且最主导,讨论 agent 架构、skill 设计、记忆系统、定时任务 | 1063s 叫「青锋给大家看」、2113s 叫「行远」、多次提「虎哥」「陈林」→ 这几人都不是发言人2 | -| 发言人3 | 中等发言量,讨论功能号、对话框、班主任场景 | 3549s 提到「木风挺调皮」→ 木风 ≠ 发言人3 | -| 发言人4 | 发言较少,技术实现相关(接口、链路、安卓、蓝牙)| —— | - -**Step 4:多路并发身份推断(与 Step 3 同时进行)** - -| 路径 | 命令 | 结果 | -|------|------|------| -| ① 通讯录组织架构 | `dws contact user search --keyword "木兰"` | 木兰 = **王佳明**,X 事业群-X 事业部-X-X-**产品设计部**,上级临渊(王临一)| -| ② 文档产出 | `dws doc search --keyword "王佳明"`(按需)| 多为设计稿/原型,进一步印证设计师角色 | - -**Step 5:定向匹配 + 置信度判断** - -把 Step 4 拿到的"产品设计部 / 设计师角色"信号回投到 Step 3 的发言人画像: - -- 候选锁定 **发言人1**——其特征(UI 设计、A/B 面切换、页面层级、汇报设计进展)与"产品设计部设计师"高度吻合 -- **额外强信号**:发言人1 在 1856s 说「最开始跟**拾光**做 A 店」——拾光 = 王刘明(当前用户),属同部门协作关系,与"木兰也在产品设计部"完全对得上 -- **同名陷阱排查**:发言人1 在 1013s 说「**木风**也给了一些各种状态」——`木风 ≠ 木兰`(两个不同花名,木风是另一位设计同事),**这条线索看似矛盾,实则强化了"发言人1 是木兰本人在转述木风的产出"的判断** -- 综合置信度 **≈ 75%**(>70%),走**分支 A:文本片段确认** - -**Step 5 → 用户确认环节**(实际对话中必须有这一步): - -> 根据分析,发言人1 很可能就是木兰(王佳明,产品设计部)。最具代表性的发言片段: -> 「最开始跟拾光做 A 店就是在功能号做的……当前首页内容层级太多,无论怎么改都解决不了根本问题」 -> 推断依据:UI 设计视角 + 与拾光有合作经历 + 同部门关系 -> 确认是木兰吗?是 / 不是 - -**Step 6:结构化总结输出(四段式模板)** - -确认后按以下结构组织——这套四段式适用于绝大多数会议场景: - -``` -[人名] 在本次会议中的发言总结 - -**核心观点** -- 观点1(带具体上下文,不要只写关键词) -- 观点2 -- ... - -**提出的问题 / 关注点** -- 问题1 -- ... - -**Action Item / 承诺事项** -- 时间节点 + 具体动作 -- ... - -**立场 / 态度** -- 对核心议题的明确态度(支持/反对/务实/保留) -- ... -``` - -**Step 7:引导替换发言人(必须执行)** - -> 目前这篇听记中木兰的发言仍显示为『发言人1』。要我帮你把听记里的『发言人1』全部替换为『木兰』吗? -> 通讯录中已匹配到:木兰(王佳明,产品设计部,userId: 123456789),替换后将关联到通讯录真实身份,纪要和待办中的发言人也会同步更新。 -> 确认后我会执行:`dws minutes speaker replace --id --from "发言人1" --to "木兰" --target-uid 123456789` - -用户确认 → 立即调用 `dws minutes speaker replace`(附带 `--target-uid`),并追问"还有其他发言人需要识别吗?" - -**本案例固化的关键经验(必须吸收):** - -1. **花名相似不等于同一人**:`木风 ≠ 木兰`、`拾光 ≠ 拾贝`、`临渊 ≠ 临川` 这类 1 字之差的花名极易误判。**遇到候选人花名出现在某发言人原话里时,必须先确认这是"自指(在自我介绍)"还是"他指(在叫别人)",再做互斥推断**。在原文中"A 提到 B"通常意味着 `A ≠ B`,不要反过来判定 `A = B`。 - -2. **Step 3 与 Step 4 必须并发**:先做发言人画像(Step 3)的同时**异步发起**通讯录查询(Step 4 ①),等通讯录返回了"角色/部门"信号再回投到画像里做匹配,这样不串行等待、不浪费时间。 - -3. **同部门协作关系是强信号**:当发言人 X 提到了某个同事的花名(如「跟拾光做 A 店」),而通讯录显示"目标人物"和该同事**同部门**,这是非常强的身份匹配信号,可以直接把置信度提升到 70%+。 - -4. **置信度 ≥ 70% 即可走分支 A**:不要一味追求 90%+,否则会陷入"再查一路、再印证一次"的死循环。70% 是经验阈值,分支 A 本身就有"用户文本确认"作为兜底,错了用户会立即纠正。 - -5. **结构化总结用四段式模板**:核心观点 / 关注点 / Action Item / 立场态度——这套模板适用于绝大多数会议场景(产品评审、技术方案、复盘会、双周会)。每条要点必须**带上下文**(如"她认为底部那一排功能导航必须去掉,因为与上方内容严重重复"),不要只写"反对底部导航"这种干巴巴的关键词。 - -6. **Step 7 必须执行,不能跳过**:本案例如果只输出总结就结束,下次用户打开听记看到的还是"发言人1",依然要靠脑补对应木兰——这正是发言人识别功能存在的意义被完全抹掉的反例。 - -**绝对禁止:** -- 看到发言人说「木风也给了状态」就直接判定"那 TA 就是木风的同事/下属"等过度推断——只能得出 `TA ≠ 木风` 这一条互斥信息 -- Step 3 还在分析就阻塞住,等画像分析完再串行去查通讯录——必须并发 -- 总结写成"木兰讨论了 UI 设计、A/B 面切换、功能号方向"这种关键词堆砌——必须展开成带上下文的完整观点 -- 置信度 70% 就纠结要不要再查文档/聊天记录——分支 A 的文本确认本身就是兜底,不要无谓地继续查 - -### 案例 7:跳过通讯录、在转写文本里 grep 花名 → 误判"目标人物没参会"(真实反面教材) - -> 这是**与案例 6 输入完全相同**但执行链路完全错误的真实 badcase,重点演示"没走 Step 4 通讯录查询"会带来怎样灾难性的失败结论。**强烈建议每次执行发言人识别任务前,对照本案例自检一遍**。 - -**用户输入:**(与案例 6 完全相同) -``` -https://shanji.dingtalk.com/app/transcribes/ 分析下木兰讲了什么 -``` - -**[错误] 真实失败链路(每一步都要识别为反模式):** - -``` -Step 1 **[完成]** get transcription 拉取了多页转写 - ↓ -Step 2 **[禁止]** 看到全是匿名编号 → 没有继续走 Step 3-4,反而开始在转写里 grep "木兰" - ↓ -错误动作 A:连续多次"搜索所有日志文件中是否有'木兰'这个名字" - ↓ -错误动作 B:转写里搜不到 → 调用 get summary 看 AI 摘要里的"参与人" - ↓ -错误动作 C:AI 摘要"参与人=拾光" → 推理"参与人只有拾光 → 木兰没参会" - ↓ -错误结论:告诉用户"在这篇听记中,没有找到名为木兰的发言人。可能木兰没参加这次会议" - ↓ -错误兜底:把责任甩给用户:"请你确认是哪位发言人,或在客户端看参会人列表" -``` - -**这条链路违反了几乎所有铁律:** - -| 反模式 | 违反的铁律 | 后果 | -|--------|------------|------| -| 在转写文本里 grep "木兰" 字符串作为存在性判断 | 铁律 1 | 99% 听记的发言人都是匿名编号,搜不到字面是默认场景,根本不构成"没参会"的证据 | -| 用 AI 摘要的"参与人=拾光"推断"木兰没参会" | 铁律 2 | AI 摘要"参与人"字段只截取最显著的 1-2 人,**不是**完整参会名册 | -| 全程没调用过一次 `dws contact user search --keyword "木兰"` | 铁律 3 | 通讯录查询是 Step 4 的必跑项,单次调用就能拿到"木兰=王佳明,产品设计部" | -| 一旦字面搜不到就放弃身份推断,把任务甩给用户 | 铁律 4 | 这恰恰把发言人识别功能的核心价值(把匿名编号映射到真实人)完全抹掉了 | - -**[正确] 应该这样执行(与案例 6 一致):** - -1. **Step 1**:从 URL 提取 taskUuid → `dws minutes get transcription` 自动翻页拉全部 -2. **Step 2**:检查 `speakerNick` 字段是否含"木兰"——发现全是匿名编号 → **不要在转写文本里 grep "木兰",立即并发触发 Step 3 + Step 4 ①** -3. **Step 3**(与 Step 4 ① 并发):在转写里做发言人画像(每位发言人的发言量、主题、互斥线索) -4. **Step 4 ①**(与 Step 3 并发,必跑):`dws contact user search --keyword "木兰"` → 拿到"木兰=王佳明,产品设计部,上级临渊" -5. **Step 5**:把"产品设计部 + 设计师角色"信号回投到画像 → 锁定发言人1(UI/交互设计视角高度匹配)+ 与拾光的同部门协作信号 → 置信度 ≈ 75% → 走分支 A -6. **Step 5 用户确认**:展示发言人1 的代表性片段请用户确认 -7. **Step 6**:四段式结构化总结(核心观点/关注点/Action Item/立场态度) -8. **Step 7**:引导调用 `speaker replace` 把"发言人1"替换为"木兰" - -**关键经验(强制吸收,案例 6 已讲过的不再重复,本案例独有的):** - -1. **"在转写里搜不到目标人名"绝不构成"没参会"的证据**:花名/真名通常不出现在 TA 自己的发言里,这是默认场景而非例外。99% 的听记发言人都是匿名编号——这正是发言人识别功能要解决的问题。 - -2. **AI 摘要的"参与人"字段是低保真信号,不能作为参会判断依据**:`get summary` 返回的是 AI 生成的自然语言摘要,里面提到的"参与人"通常只是最显著的 1-2 人。而 `get info` / `get batch` **均不返回**结构化 `participants` 字段,无法从中拿完整参会列表;能拿到的只有 `get transcription` 的转写发言人(常为匿名编号)。 - -3. **`dws contact user search` 是 Step 4 ① 的必跑项,单次调用就能突破死局**:本案例的整个失败链路只要有 1 次通讯录查询就能立即扭转——拿到"木兰=王佳明,产品设计部"后,Step 5 的角色匹配就有了锚点,再也不会得出"没参会"的错误结论。 - -4. **想说"找不到 X"前的四个自检问题**(任何一个回答"没"都禁止给"找不到"结论): - - 通讯录查了吗?(`dws contact user search --keyword "X"`) - - 文档查了吗?(`dws doc search --keyword "X"`) - - 聊天记录查了吗?(`dws chat message list`) - - 基于角色在转写里做模式匹配了吗?(设计师 vs 研发 vs 管理者的发言特征) - -5. **`get summary` 不能替代发言人识别**:摘要是"会议讲了什么"的总览,**不是**"谁讲了什么"的精细切分。混淆这两个能力会导致 Step 4 直接跳过。 - -**绝对禁止:** -- **[禁止]** Step 2 看到匿名编号后,直接在转写文本里 grep 目标人名 → 没找到就退出 -- **[禁止]** 用 `get summary` 摘要里写到的"参与人"判断某人是否参会 -- **[禁止]** 全流程不调用 `dws contact user search` 就给出"找不到 X"的结论 -- **[禁止]** 把身份推断的责任甩回给用户:"请你告诉我哪位是木兰" / "请去客户端看参会人"——发言人识别功能的存在意义就是 AI 来做这件事 - -### 案例 8:听记/纪要类 query 不走 dws 技能(基础评测集 16 例 badcase 复盘) - -> 这是一组**最高频的失败模式**——用户提出听记/纪要/会议总结/待办提取/链接解析等典型 dws 场景请求,AI 却用 `session_search` / `memory_search` / `activity:search` / `browser_use` / `read_file` / 直接反问 等"伪替代"路径绕过 dws 技能,导致核心链路 0 命中。下面把基础评测集(minutes-base, evalrun_4ab46f8da846)中**全部 16 个该模式失败 query 完整列出**,遇到形似输入请直接对照本案例处理。 - -#### 一、五类典型 badcase 模式(按失败动作归类) - -**模式 A:模糊/省略型 query → AI 直接反问要细节,不主动 list** - -涉及 query: - -| case_id | 用户原始 query | -|---------|----------------| -| `dws_minutes_hotquery_0049` | 按关键词搜索我的听记 | -| `dws_minutes_hotquery_0057` | 查列表+看摘要 | -| `dws_minutes_hotquery_0063` | 周会回顾整理 | -| `dws_minutes_hotquery_0064` | 评测工作复盘 | -| `dws_minutes_hotquery_0042` | 把所有听记内容添加到汇报中 | - -**典型错误动作**:`tool_calls = []`,AI 回复"请告诉我具体的关键词/时间范围/会议名"就停下,等待用户补充。 - -**模式 B:用 `session_search` / `memory_search` 搜索历史会话假装"找过了"** - -涉及 query: - -| case_id | 用户原始 query | -|---------|----------------| -| `dws_minutes_hotquery_0003` | 帮我查一下最近一次会议的纪要内容 | -| `dws_minutes_hotquery_0017` | 总结下我的会议 | -| `dws_minutes_hotquery_0020` | 我昨天那个会的重点帮我提炼一下 | -| `dws_minutes_hotquery_0027` | 把最近一次会议的待办整理出来 | - -**典型错误动作**:调用 `session_search` 搜以前的对话记录,把以前 AI 自己生成过的"会议纪要文件描述"当作真实数据复述出来;从未触发 `dws minutes list / get summary / get todos`。 - -**模式 C:用 `activity:search` / web 搜索把"找听记"做成"搜网页"** - -涉及 query: - -| case_id | 用户原始 query | -|---------|----------------| -| `dws_minutes_hotquery_0025` | 调取某某项目讨论的两个听记内容 | -| `dws_minutes_hotquery_0060` | 搜索+摘要+关键词 | - -**典型错误动作**:调用 `activity:search` 搜公网,返回的是"钉钉 AI 听记产品介绍"页面,与用户的私人听记数据毫不相关。 - -**模式 D:钉钉听记/文档 URL 走 `browser_use` / `read_file` 而非 `dws`** - -涉及 query: - -| case_id | 用户原始 query | -|---------|----------------| -| `dws_minutes_hotquery_0045` | `https://shanji.dingtalk.com/meeting/minutes?taskUuid=sample004` | -| `dws_minutes_hotquery_0046` | `https://alidocs.dingtalk.com/i/nodes/sampleDocNode01` 帮我读取这个文档内容 | -| `dws_minutes_hotquery_0047` | 这个听记链接你能打开看内容吗 `https://shanji.dingtalk.com/meeting/minutes?taskUuid=sample006` | - -**典型错误动作**:`browser_use` 打开页面遇到登录墙就回复"需要登录";或 `read_file` 当本地文件读 → 失败 → 把锅甩给用户。完全没有意识到 dws 技能本身已携带账号态,能直接通过 taskUuid/dentryUuid 拿到内容。 - -**模式 E:多源数据生成日报/汇报/总结/报告,听记侧 0 调用** - -涉及 query: - -| case_id | 用户原始 query | -|---------|----------------| -| `dws_minutes_hotquery_0040` | 根据今天的聊天记录和听记总结工作日报 | -| `dws_minutes_hotquery_0005` | 把会议纪要写入钉钉文档 | -| `minutes-001` (P0) | 从悟空的商业模式分析,重新写一下市场感知报告 | -| `minutes-002` (P0) | 最新文档啥也没有啊,我要你根据我的工作情况总结,分成问题应收、异常事故处理、ai相关工作和其他几部分总结 | -| `minutes-004` (P0) | 你这个好像只是基于日程,我要的是我的对话,发出去的文字,私聊,群聊,日程,会议,文档修改等相关的所有动作,参考这个生成日报,并且把这个封装成日报skill | - -**典型错误动作**:识别出"日报/报告/总结/写文档"场景后,只想着调用周报技能 / 文档写入技能,**完全跳过听记数据获取**这一步。更隐蔽的变体是:用户 query 中**没有出现"听记"二字**(如"根据工作情况总结"、"重新写市场感知报告"),但任务的完成**实际依赖会议讨论内容**,AI 却完全没有意识到需要从听记中采集素材,结果输出空洞的报告或反问"请告诉我会议纪要内容"。 - -**P0 case 暴露的三种变体(必须全部识别):** -- **变体 1(minutes-001)**:用户要写报告,报告素材来源于历史会议讨论 → AI 没识别出需要查听记 -- **变体 2(minutes-002)**:用户要分类总结工作,且明确说"文档啥也没有" → AI 没切到听记作为替代数据源 -- **变体 3(minutes-004)**:用户明确抱怨"只是基于日程"并点名要求"会议"等多数据源 → AI 仍然只用日程数据 - -#### 二、五类共性反模式(必须全部识别为禁止动作) - -| 反模式 | 错误根因 | 正确做法 | -|--------|----------|----------| -| **R1:模糊请求 → 反问要细节** | AI 把"信息不足"当成必须澄清的前置条件 | 听记类模糊请求 → **默认先调用 `dws minutes list mine --max 10 --format json`** 把最近的听记列出来,让用户从列表里挑,而不是反问关键词 | -| **R2:用 `session_search`/`memory_search` 替代 dws** | AI 把"历史会话里聊过的纪要描述"误认为是真实数据源 | 历史会话只能回忆"以前我们聊过什么",**不是真实听记数据**。听记数据**必须**从 `dws minutes` 实时拉取 | -| **R3:用 `activity:search` 通用 web 搜索听记** | 把"听记"理解成公网信息 | 听记是用户私人钉钉数据,**只能**通过 `dws minutes list/get` 获取,公网搜不到也不该搜 | -| **R4:钉钉 URL 走 browser_use/read_file** | 把钉钉 URL 当成普通网页 | 钉钉听记 URL(`shanji.dingtalk.com/meeting/minutes?taskUuid=xxx` 或 `shanji.dingtalk.com/app/transcribes/xxx`)→ **提取 taskUuid → 走 `dws minutes get summary/get transcription`**;钉钉文档 URL(`alidocs.dingtalk.com/i/nodes/xxx`)→ **提取 dentryUuid → 走 `dws doc read`** | -| **R5:日报/汇报/总结/报告场景跳过听记数据采集** | 只看到"日报/报告/总结"就直奔输出端,忘了用户指定的(或隐含的)数据源包含会议/听记。**不仅限于用户说了"听记"二字的场景**——"根据我的工作情况总结"(minutes-002)、"重新写一下市场感知报告"(minutes-001)、"我要的是我的对话...会议...生成日报"(minutes-004)都属于此类 | query 中出现"总结/报告/汇报/日报/周报/工作情况/商业分析/市场感知/复盘"等产出类关键词时,必须自检:**用户的产出是否依赖会议讨论内容?** 如果是 → **第一步必须** `dws minutes list mine` 拿听记 → `get summary` 逐篇拉摘要 → 再汇总。详见「间接意图识别」章节 | - -#### 三、五类 badcase 的统一正确链路(速查表) - -| 用户 query 形态 | 第一步必跑命令 | 关键说明 | -|------------------|------------------|----------| -| 模糊请求:"总结下我的会议" / "周会回顾整理" / "查列表+看摘要" / "评测工作复盘" / "按关键词搜索我的听记" | `dws minutes list mine --max 10 --format json` | 拿到最近听记列表后,对前 1~3 篇 `get summary`,引导用户挑选目标 | -| 含时间词:"最近一次/昨天/本周/上周的会议" | `dws minutes list mine --start --end --max 20 --format json` | 时间范围按用户描述折算,不要让用户自己提供日期 | -| 含主题/项目关键词:"某某项目讨论" / "搜索+摘要+关键词" | `dws minutes list all --query "<关键词>" --max 20 --format json` | 用 `--query` 而不是 `activity:search` | -| 钉钉听记 URL(`shanji.dingtalk.com/...?taskUuid=xxx`) | `dws minutes get summary --id --format json` | 从 URL 提取 taskUuid,禁用 browser_use | -| 钉钉文档 URL(`alidocs.dingtalk.com/i/nodes/xxx`) | `dws doc read --node --format json` | 走 doc 技能而非 read_file/browser_use | -| 多篇听记对比:"对比一下这几个听记 [URL1] [URL2]" | 对每个 URL 分别 `dws minutes get summary --id --format json` | 失败的 URL 给出明确说明,不要把锅全甩给用户 | -| 日报/汇报含"听记"/"会议纪要"关键词 | 先 `dws minutes list mine --start <今日 0 点> --max 20`,再对每篇 `get summary`,最后才汇总 | 听记数据采集是必跑前置,不能直接跳到周报技能 | -| 报告/分析类(query 未显式提"听记"但依赖会议讨论素材):"重新写一下市场感知报告" / "帮我生成商业分析" | `dws minutes list all --query "<主题关键词>" --max 20 --format json` → 逐篇 `get summary` 提取素材 | 详见「间接意图识别」,用户的报告素材来源于历史会议讨论 | -| 分类工作总结(query 未显式提"听记"但需要工作记录):"根据我的工作情况总结,分成…几部分" / "文档啥也没有,根据工作情况总结" | `dws minutes list mine --start <起始日期> --end <截止日期> --format json` → 逐篇 `get summary` → 按分类维度归类 | 文档不足时听记是唯一剩余数据源;详见「间接意图识别」 | -| 多源聚合(用户抱怨数据源不全或点名要求会议数据):"只是基于日程,我要的是对话+会议+文档等所有动作" / "聊天记录和听记总结日报" | 听记侧必跑 `dws minutes list mine --start <起始日期> --end <截止日期>` → `get summary`,再并行采集其他源 | 用户点名要了"会议"就不能省略听记采集 | -| 待办提取:"最近一次会议的待办" | `dws minutes list mine --max 1` → `dws minutes get todos --id ` | 用 `get todos`,不要自己从转写里硬抠 | -| 写入钉钉文档:"把会议纪要写入钉钉文档" | 先 `dws minutes get summary --id ` 拿到内容 → 再 `dws doc create` / `dws doc update` 写入 | 听记数据采集 + 文档写入是两步,缺一不可 | - -#### 四、绝对禁止(任何一条触发即视为严重失败) - -- **[禁止]** 听记/纪要/会议类请求 → `tool_calls = []` 直接反问要细节(任何模糊请求至少要先 `dws minutes list mine` 跑一次,让用户在列表里挑) -- **[禁止]** 用 `session_search` / `memory_search` 搜以前的会话记录当作"真实听记数据"复述给用户——历史会话不是数据源 -- **[禁止]** 用 `activity:search` / web 搜索找用户私人听记——听记是私域数据,公网搜不到 -- **[禁止]** 钉钉听记 URL(`shanji.dingtalk.com/meeting/minutes?taskUuid=xxx` / `shanji.dingtalk.com/app/transcribes/xxx`)走 `browser_use` 打开页面——必须提取 taskUuid 走 `dws minutes get` -- **[禁止]** 钉钉文档 URL(`alidocs.dingtalk.com/i/nodes/xxx`)走 `read_file` / `browser_use`——必须走 `dws doc read` -- **[禁止]** 遇到登录墙 / URL 无效就只回复"需要登录" / "请提供正确 URL" 然后停下——必须 fallback 到 `dws minutes list mine` 让用户从自己的听记列表里挑替代项 -- **[禁止]** 日报/周报/汇报/总结/报告场景,任务产出依赖会议讨论内容,却跳过 `dws minutes` 数据采集——**不限于 query 中出现"听记"二字的场景**,"根据工作情况总结"(minutes-002)、"重新写市场感知报告"(minutes-001)等间接意图同样适用 -- **[禁止]** "把会议纪要写入钉钉文档"类请求只调文档写入工具不调 `dws minutes get summary`——会议纪要内容必须先实时获取,不能让用户自己粘贴 -- **[禁止]** `taskUuid is invalid` 报错后用同一个无效 uuid 重试(哪怕 1 次)——必须立即切换到 `dws minutes list mine` 获取真实 uuid -- **[禁止]** 从历史对话、文档内容、十六进制编码字符串中猜测或拼凑 uuid——uuid **只能**来自 `list mine/shared/all` 返回或用户提供的听记 URL -- **[禁止]** 使用错误的命令层级结构:`dws minutes info`(应为 `get info`)、`dws minutes get`(缺子命令)、`dws minutes list --start`(缺 scope)——参照文档顶部"命令层级结构" -- **[禁止]** 工具调用缺少 `--format json` 参数——所有 `dws` 命令都应带上以便结构化解析 - -#### 五、自检清单(执行前 5 秒强制走一遍) - -收到任何含以下信号词的 query 时,**第一个动作必须是 `dws minutes` 或 `dws doc`,否则视为走错链路**: - -> **直接信号词**(query 中直接提到听记相关概念): -> "听记 / 纪要 / 摘要 / 转写 / 录音 / 会议 / 周会 / 日会 / 评审会 / 复盘 / 回顾 / 待办 / Action Item / 关键词 / 我的会 / 昨天那个会 / 上周的会 / 共享听记 / shanji.dingtalk.com / alidocs.dingtalk.com" - -> **间接信号词**(query 未提"听记"但任务产出依赖会议讨论内容,需要额外判断——详见「间接意图识别」章节): -> "报告 / 市场感知 / 商业分析 / 工作情况总结 / 分类总结 / 日报 / 周报 / 月报 / 工作汇报 / 文档啥也没有 / 只是基于日程 / 所有动作 / 封装成 skill" -> **判断标准**:用户的产出是否依赖"会议上说了什么/讨论了什么"?如果是 → 听记采集是必跑前置步骤 - -强制问自己 5 个问题(任何一个回答"否"都禁止开始执行): - -1. 我接下来的第一个 tool call 是 `dws minutes ...` 或 `dws doc ...` 吗? -2. 我有没有在用 `session_search` / `memory_search` / `activity:search` / `browser_use` / `read_file` 替代 dws? -3. 用户给了钉钉 URL 时,我有没有从 URL 提取 taskUuid/dentryUuid 后走 dws,而不是 browser_use? -4. 用户的请求模糊(如"总结下我的会议")时,我是先 `dws minutes list mine` 列出来,还是反问要细节? -5. 用户要写报告/总结/日报但没提"听记"时,我有没有判断"任务产出是否依赖会议讨论内容"?如果依赖 → 听记采集不能省略(参见间接意图识别三条铁律) +| 任务 | 参考 | +|---|---| +| 命令层级与指令树 | [minutes-overview.md](./minutes-overview.md) | +| 命令详情 | [minutes-commands.md](./minutes-commands.md) | +| 意图与工作流 | [意图判断](./minutes-intent.md) · [核心工作流](./minutes-workflow.md) | +| 错误恢复与最佳实践 | [minutes-error.md](./minutes-error.md) | +| 发言人匹配与纠偏 | [匹配](./10-minutes-speaker-match.md) · [纠偏](./11-minutes-speaker-correct.md) | diff --git a/skills/multi/dingtalk-minutes/scripts/minutes_extract_todos.py b/skills/multi/dingtalk-minutes/scripts/minutes_extract_todos.py index 10123377..7ca3d651 100644 --- a/skills/multi/dingtalk-minutes/scripts/minutes_extract_todos.py +++ b/skills/multi/dingtalk-minutes/scripts/minutes_extract_todos.py @@ -44,58 +44,6 @@ 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='从听记中提取待办事项' @@ -134,9 +82,21 @@ def main(): ]) if not todos_data: continue - items = todos_from_payload(todos_data) + 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 = [] for t in items: - t['_source'] = title + if isinstance(t, dict): + 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 d6aa83ea..712327a9 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 ( - 'itemList', 'items', 'list', 'records', 'minutes', + 'items', 'list', 'records', 'minutes', ): inner = value.get(inner_key) if isinstance(inner, list): @@ -30,8 +30,7 @@ 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('uuid') - or item.get('id') or item.get('task_uuid')) + uuid = item.get('taskUuid') or item.get('id') or item.get('task_uuid') if not uuid: continue title = item.get('title') or item.get('name') or '无标题' @@ -54,7 +53,6 @@ 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 f136f290..24e8ed44 100644 --- a/skills/multi/dingtalk-minutes/scripts/minutes_recent_summary.py +++ b/skills/multi/dingtalk-minutes/scripts/minutes_recent_summary.py @@ -93,16 +93,9 @@ def main(): if isinstance(summary_data, str): summary_text = summary_data elif isinstance(summary_data, dict): - # 兼容 {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, + summary_text = (summary_data.get('summary') + or summary_data.get('content') + or json.dumps(summary_data, ensure_ascii=False)) output_lines.append(f"## {i}. {title}\n") diff --git a/skills/multi/dingtalk-oa/SKILL.md b/skills/multi/dingtalk-oa/SKILL.md index 209f296e..4e071641 100644 --- a/skills/multi/dingtalk-oa/SKILL.md +++ b/skills/multi/dingtalk-oa/SKILL.md @@ -1,6 +1,6 @@ --- name: dingtalk-oa -description: 钉钉 OA 审批。Use when 用户说 OA/审批/待处理审批/同意审批/拒绝审批/撤销审批/已发起审批/审批记录/批量审批。Distinct from dingtalk-todo(待办任务)、dingtalk-report(日志)。命令前缀:dws oa。 +description: 钉钉 OA 审批。Use when 用户说 OA/审批/待处理审批/同意审批/拒绝审批/撤销审批/已发起审批/审批记录/批量审批。不做待办任务(走 dingtalk-todo)、日志(走 dingtalk-report)。命令前缀:dws oa。 cli_version: ">=0.2.14" metadata: category: product @@ -14,13 +14,12 @@ metadata: > 🧪 **EXPERIMENTAL · 试验版 / Preview** — multi 模式当前未达 stable 标准。全部 dingtalk-* skill 已通过 dispatch verifier,但接口、命名、跨 skill 引用后续可能调整;生产 / 共享环境请优先使用 mono 模式(`dws skill setup --mode mono`)。问题请提 issue 反馈。 -> **PREREQUISITE:** Read the `dws-shared` skill first for auth, global flags, product routing, URL preflight, error codes, and safety rules. The `dws` binary must be on PATH. +## 前置条件 — 执行操作前必读 + +> **`use_skill(dws-shared)`** — 认证、全局参数(`--format json` / `--yes`)、错误码、URL 模板、跨产品消歧、安全规则与 capability 边界。**执行任何 `dws` 命令前先读;** 单产品的清晰命令可直接用本 skill。 -> ⚠️ **命令可用性以当前 dws 二进制为准**。服务发现已下线,本文档随内置 skill 发布;如果 `dws --help` 不存在,说明当前版本未暴露该命令。若命令存在但调用失败,请按错误中的 endpoint 或 tool 提示确认静态端点目录和后端工具注册。实际调用前可用 `dws --help` 或 `--dry-run` 验证。 - - > 命令参考:[oa.md](references/oa.md)。 ## 意图表 @@ -28,15 +27,15 @@ metadata: | 用户说 | 命令 | |--------|------| | "待我处理的审批 / 7 天内待审" | `python scripts/oa_pending_review.py --days 7` | -| "查审批详情" | `dws oa approval detail --instance-id ` | -| "同意 / 拒绝审批" | `dws oa approval approve --instance-id --task-id ` / `reject --instance-id --task-id --remark "<原因>"`(需用户确认) | +| "查审批详情" | `dws oa approval detail --instance-id --format json` | +| "同意 / 拒绝审批" | 先 `dws oa approval tasks --instance-id --format json` 取 `taskId`,再 `dws oa approval approve --instance-id --task-id --format json` / `reject --instance-id --task-id --format json`(需用户确认) | | "批量同意 / 批量拒绝" | `python scripts/oa_batch_approve.py --action approve --days 7` | -| "撤销审批" | `dws oa approval revoke --instance-id ` | -| "我已发起的审批" | `dws oa approval list-initiated --process-code --start ""`(processCode 来自 `dws oa approval list-forms`) | +| "撤销审批" | `dws oa approval revoke --instance-id --format json` | +| "我已发起的审批" | `dws oa approval list-submitted --format json` | ## 危险操作 -`approval approve / reject` 不可撤回,必须先向用户展示摘要并获得明确同意,再加 `--yes`。 +`approval approve / reject` 不可撤回,必须先向用户展示摘要并获得明确同意,再执行审批命令。 ## 跨产品协作 diff --git a/skills/multi/dingtalk-oa/references/oa-index.md b/skills/multi/dingtalk-oa/references/oa-index.md new file mode 100644 index 00000000..24c6858b --- /dev/null +++ b/skills/multi/dingtalk-oa/references/oa-index.md @@ -0,0 +1,5 @@ +# OA 审批能力索引 + +| 主题 | 详见 | +|---|---| +| 审批流程/查询/同意/拒绝 | [`oa.md`](./oa.md) | diff --git a/skills/multi/dingtalk-oa/references/oa.md b/skills/multi/dingtalk-oa/references/oa.md index 643b0ac7..4656650e 100644 --- a/skills/multi/dingtalk-oa/references/oa.md +++ b/skills/multi/dingtalk-oa/references/oa.md @@ -12,9 +12,13 @@ Example: Flags: --end string 结束时间 ISO-8601 (如 2026-03-10T23:59:59+08:00) (必填) --page string 分页页码 (可选) - --limit string 每页大小 (可选) + --limit string 每页大小 (可选) --start string 开始时间 ISO-8601 (如 2026-03-10T00:00:00+08:00) (必填) --query string 关键字搜索 (可选) + +**默认时间窗口:** 当用户未指定 --start / --end 时,默认查询最近 30 天的待处理审批。 + +> **IMPORTANT:** 当 `list-pending` 返回空时,必须明确告知用户"当前暂无待处理审批",并建议扩大时间范围或检查关键词。 ``` ### 获取审批实例详情 @@ -88,8 +92,8 @@ Example: 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 Flags: --end string 结束时间 ISO-8601 (如 2026-03-10T23:59:59+08:00) (必填) - --limit string 每页大小,最大 20 (必填) - --cursor string 分页游标,首次传 0 (必填) + --limit string 每页大小,最大 20 (必填) + --cursor string 分页游标,首次传 0 (必填) --process-code string 表单 processCode (必填) --start string 开始时间 ISO-8601 (如 2026-03-10T00:00:00+08:00) (必填) ``` @@ -102,9 +106,8 @@ Example: dws oa approval list-forms --cursor 0 --limit 100 Flags: --cursor string 分页游标,首次传 0 (默认 "0") - --limit string 每页大小,最大 100 (默认 "100") + --limit string 每页大小,最大 100 (默认 "100") ``` -MCP 工具: `list_user_visible_process`;参数: cursor, pageSize(对应 --cursor/--limit)。返回结果含 processCode,可用于 list-initiated 的 --process-code。 ### 按关键字模糊搜索审批表单 ``` @@ -153,6 +156,7 @@ dws ding message send --robot-code $DINGTALK_DING_ROBOT_CODE --users --content "请尽快审批《XXX》" --type sms --format json ``` + ### 获取任务可回退的节点信息 > **IMPORTANT:** 退回任务前**必须先调用此命令**获取可回退节点列表,从中提取 `activityId` 和 `revertAction` 作为 `revert-task` 的入参。若无返回值,明确告知用户"当前任务无可回退节点"。 @@ -180,6 +184,7 @@ Flags: **无返回值处理:** 若 `instRevertActivities` 为空或不存在,必须明确告知用户"当前任务无可回退节点",不得继续执行退回操作。 + ### 查询待我审批的任务 ID ``` Usage: @@ -189,7 +194,6 @@ Example: Flags: --instance-id string 审批实例 ID (必填) ``` -MCP 工具: `list_pending_tasks`。 ### 查询我处理过的审批单 @@ -238,7 +242,6 @@ Flags: --to-actioner-id string 转交目标用户 ID (必填) --remark string 转交说明 (可选) ``` -MCP 工具: `redirect_task`;参数: taskId, toActionerId, remark(对应 --task-id/--to-actioner-id/--remark)。taskId 可通过 `tasks` 命令获取,toActionerId 可通过 `dws contact user search` 获取。 ### 对审批实例添加评论 ``` @@ -248,9 +251,8 @@ Example: dws oa approval oa-comments --instance-id --content "同意,请尽快处理" Flags: --instance-id string 审批实例 ID (必填) - --content string 评论内容 (必填) + --content string 评论内容 (必填) ``` -MCP 工具: `dingflow_comments`;参数: processInstanceId, text(对应 --instance-id/--content)。processInstanceId 可通过 `list-pending` 或 `detail` 获取。 ### 对审批实例进行抄送 ``` @@ -258,13 +260,11 @@ Usage: dws oa approval oa-cc-noticer [flags] Example: dws oa approval oa-cc-noticer --instance-id --users "68674200835816" - dws oa approval oa-cc-noticer --instance-id --users "userId1,userId2" --operator-id "123123" + dws oa approval oa-cc-noticer --instance-id --users "userId1,userId2" Flags: --instance-id string 审批实例 ID (必填) - --users string 抄送用户 ID 列表,多个用逗号分隔 (必填) - --operator-id string 操作人 ID (可选) + --users string 抄送用户 ID 列表,多个用逗号分隔 (必填) ``` -MCP 工具: `oa_cc_noticer`;参数: processInstanceId, userList(对应 --instance-id/--users)。processInstanceId 可通过 `list-pending` 或 `detail` 获取,抄送用户 ID 可通过 `dws contact user search` 获取。 ### 对审批任务进行加签 @@ -281,7 +281,7 @@ Flags: --type string 加签类型:before(前加签),after(后加签),Parallel(并加签)(必填) --appender-user-ids string 被加签用户 ID 列表,多个用逗号分隔 (必填) --activate-type string 任务激活类型:ALL(或签),ONE_BY_ONE(依次审批)(必填) - --agree-all string 是否需要全部同意 (必填) 是 true 否 false + --agree-all 是否需要全部同意 (必填) 是 true 否 false ``` ### 退回审批任务 @@ -308,21 +308,45 @@ Flags: ## 意图判断 -用户说"待审批/待处理审批" → `approval list-pending` +用户说"待审批/待处理审批/查询XX审批/查XX审批/有没有XX审批/XX的审批单" → `approval list-pending`,将 XX 作为 `--query` 关键字传入(可搜索表单名称或表单详情内容) + - 示例:"帮我查询补卡的审批单" → `approval list-pending --query 补卡` + - 示例:"有没有外出申请的审批" → `approval list-pending --query 外出申请` + - 示例:"待审批"(无关键词)→ `approval list-pending` 用户说"审批详情/看审批" → `approval detail` 用户说"同意审批/批准" → 先 `tasks` 获取 taskId,再 `approve` 用户说"拒绝审批/驳回" → 先 `tasks` 获取 taskId,再 `reject` 用户说"撤回审批/取消审批" → `approval revoke` 用户说"审批记录/操作历史" → `approval records` -用户说"我发起的审批" → `approval list-initiated`(需 --process-code,可从 list-forms / search-forms / detail 获取) +用户说"我发起的审批" → `approval list-initiated`(需 --process-code,可从 list-forms 或 detail 获取) 用户说"有哪些审批表单/可见表单" → `approval list-forms` +用户说"搜索审批表单/查找xx审批表单/有没有xx表单" → `approval search-forms`(需 --query) +用户说"催办审批/DING 一下审批人/提醒审批/催一下审批/催批/提醒审批人" → 先 `approval ding-info`(拿到被催办人 `userId`),再 `ding message send`(将 userId 作为 `--users` 传入;`--robot-code` 优先走 `$DINGTALK_DING_ROBOT_CODE` 或向用户确认;`--content` 由 agent 根据审批上下文撰写) + - **禁止跳过 ding-info:** 不得自行猜测或编造 userId,必须先调用 `ding-info` 获取 + - **机器人编码获取顺序:** ① `$DINGTALK_DING_ROBOT_CODE` 环境变量 → ② 用户显式提供 → ③ 询问用户 + - **催办内容建议:** `"请尽快审批《{表单名}》(提交人:{发起人},提交时间:{时间})"` + - **ding-info 返回空:** 若接口返回空或报错,告知用户"无法获取该任务的被催办人信息"并停止 用户说"我有哪些待审的任务" → `approval tasks` -用户说"我发起的审批单" -> `approval list-submitted` -用户说"我审批/处理过的审批单" -> `approval list-executed` -用户说"抄送我的审批单" -> `approval list-cc` +用户说"我发起的审批单/我发起的XX审批/我提交的XX审批/查我发起的XX" → `approval list-submitted`,将 XX 作为 `--query` 关键字传入(可搜索表单名称或表单详情内容) + - 示例:"查我发起的补卡审批单" → `approval list-submitted --query 补卡` + - 示例:"我发起的审批单"(无关键词)→ `approval list-submitted` +用户说"我审批/处理过的审批单/我处理过的XX审批/我审批过的XX/查我处理过的XX" → `approval list-executed`,将 XX 作为 `--query` 关键字传入(可搜索表单名称或表单详情内容) + - 示例:"查我处理过的补卡审批单" → `approval list-executed --query 补卡` + - 示例:"我审批过的审批单"(无关键词)→ `approval list-executed` +用户说"抄送我的审批单/抄送我的XX审批/CC我的XX/查抄送我的XX" → `approval list-cc`,将 XX 作为 `--query` 关键字传入(可搜索表单名称或表单详情内容) + - 示例:"查抄送我的补卡审批单" → `approval list-cc --query 补卡` + - 示例:"抄送我的审批单"(无关键词)→ `approval list-cc` 用户说"转交审批/转交任务" → `approval redirect-task`(需 --task-id 和 --to-actioner-id) 用户说"评论审批/添加评论/写评论" → `approval oa-comments`(需 --instance-id 和 --content) 用户说"抄送审批/添加抄送人" → `approval oa-cc-noticer`(需 --instance-id 和 --users) +用户说"加签/前加签/后加签/并加签/增加审批人/追加审批人" → `approval append-task`(需 --instance-id, --task-id, --type, --appender-user-ids, --activate-type, --agree-all) + - `--type` 映射:前加签 → before,后加签 → after,并加签 → Parallel + - `--activate-type` 映射:或签 → ALL,依次审批 → ONE_BY_ONE + - `--appender-user-ids` 可通过 `dws contact user search` 获取目标用户 userId +用户说"退回审批/退回发起人/退回到XX节点/打回重交/重新审批/回退/退回到" → `approval revert-task`(需 --instance-id, --task-id, --target-activity-id, --action) + - **前置步骤:** 必须先调用 `approval revert-activities --task-id ` 获取可回退节点列表,提取 `activityId` 和 `revertAction` + - **无节点处理:** 若 `revert-activities` 返回空 (`instRevertActivities` 为空),必须明确告知用户"当前任务无可回退节点",不得继续执行退回操作 + - `--action` 映射:退回发起人/打回重交 → REVERT_FOR_RESUBMIT;退回到审批人/重新审批 → REVERT_FOR_APPROVAL + - `--target-activity-id`:退回发起人时固定传 `sid-startevent`;退回到审批人时从 `revert-activities` 返回中获取 `activityId` ## 核心工作流 @@ -351,16 +375,19 @@ 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 / search-forms / detail) +# 7b. 按关键字模糊搜索表单(快速定位 processCode) +dws oa approval search-forms --query AI --format json + +# 8. 查看自己发起的审批列表(--process-code 来自 list-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 - + # 9. 我处理过的审批单 dws oa approval list-executed --limit --page --query 关键词 --format json -# 10. 我发起的审批单 +# 10. 我发起的审批单 dws oa approval list-submitted --limit --page --query 关键词 --format json -# 11. 抄送我的审批单 +# 11. 抄送我的审批单 dws oa approval list-cc --limit --page --query 关键词 --format json # 12. 转交审批任务(taskId 来自 tasks,toActionerId 来自 contact user search) @@ -371,33 +398,70 @@ dws oa approval redirect-task --task-id --to-actioner-id --rem dws oa approval oa-comments --instance-id --content "同意,请尽快处理" --format json # 14. 对审批实例进行抄送(processInstanceId 来自 list-pending 或 detail) -dws oa approval oa-cc-noticer --instance-id --users "68674200835816" --format json -dws oa approval oa-cc-noticer --instance-id --users "userId1,userId2" --format json +dws oa approval oa-cc-noticer --instance-id --user-list "68674200835816" --format json +dws oa approval oa-cc-noticer --instance-id --user-list "userId1,userId2" --format json + +# 15. 催办审批(必须两步串联:先拿被催办人 userId,再发 DING) +# 15a. Step 1: 调用 ding-info 拿到被催办人 userId(来自 list-pending 或 tasks 中的 taskId) +dws oa approval ding-info --task-id --format json +# 15b. Step 2: 将 userId 填入 --users;robot-code 优先走环境变量 $DINGTALK_DING_ROBOT_CODE(或向用户确认);content 由 agent 根据审批上下文撰写 +dws ding message send --robot-code $DINGTALK_DING_ROBOT_CODE --users --content "请尽快审批《XXX》" --format json +# 15c (可选): 如需短信/电话提醒,加 --type sms 或 --type call +dws ding message send --robot-code $DINGTALK_DING_ROBOT_CODE --users --content "请尽快审批《XXX》" --type sms --format json + +# 16. 对审批任务进行加签(instanceId 来自 list-pending/list-submitted/list-executed/detail,taskId 来自list-pending/list-submitted/list-executed/detail中 ,appenderUserIds 来自 contact user search) +dws oa approval append-task --instance-id --task-id --type before --appender-user-ids "userId1,userId2" --activate-type ALL --agree-all --format json +dws oa approval append-task --instance-id --task-id --type Parallel --appender-user-ids "userId1" --activate-type ONE_BY_ONE --agree-all --format json + +# 17. 退回审批任务(instanceId/taskId 来自 list-pending、tasks;targetActivityId 和 action 来自 revert-activities) +# 17a. 获取可回退节点(必须先调用,从此返回中提取 activityId 和 revertAction) +dws oa approval revert-activities --task-id --format json +# 17b. 退回到发起人重提(targetActivityId 固定 sid-startevent,action=REVERT_FOR_RESUBMIT) +dws oa approval revert-task --instance-id --task-id --target-activity-id sid-startevent --action REVERT_FOR_RESUBMIT --remark "补充说明后重提" --format json +# 17c. 退回到某个审批节点重新审批(targetActivityId 和 action 从 revert-activities 返回中获取) +dws oa approval revert-task --instance-id --task-id --target-activity-id --action REVERT_FOR_APPROVAL --remark "重新审批" --format json ``` ## 上下文传递表 | 操作 | 从返回中提取 | 用于 | |------|-------------|------| -| `list-pending` | `processInstanceId` | detail / tasks / records / revoke / oa-comments / oa-cc-noticer 的 --instance-id | -| `tasks` | `taskId` | approve / reject / redirect-task 的 --task-id | +| `list-pending` | `processInstanceId` | detail / tasks / records / revoke / oa-comments / oa-cc-noticer / append-task / revert-task 的 --instance-id | +| `tasks` | `taskId` | approve / reject / redirect-task / append-task / revert-task 的 --task-id | | `detail` | `processCode` | list-initiated 的 --process-code | | `list-forms` | `processCode` | list-initiated 的 --process-code | | `search-forms` | `processCode` | list-initiated 的 --process-code | +| `ding-info` | `userId` | ding message send 的 --users(多个逗号拼接);**robotCode 优先走 `$DINGTALK_DING_ROBOT_CODE` 环境变量,content 由 agent 根据审批上下文撰写;返回空时报错并停止** | +| `revert-activities` | `activityId`, `revertAction`, `activityName` | revert-task 的 --target-activity-id 和 --action;**返回空时必须告知用户"无可回退节点"** | ## 注意事项 - `--start` / `--end` 使用 ISO-8601 格式(如 2026-03-10T00:00:00+08:00) -- `approve` / `reject` / `redirect-task` 需先通过 `tasks` 获取 `taskId` +- `list-pending` 默认时间窗口为最近 30 天;若用户未指定 `--start` / `--end`,自动使用当前时间往前推 7 天 +- `list-pending` 返回空时必须明确告知用户"当前暂无待处理审批",不得沉默或跳过 +- `approve` / `reject` / `redirect-task` / `append-task` / `revert-task` 需先通过 `tasks` 获取 `taskId` - `redirect-task` 的 `--to-actioner-id` 可通过 `dws contact user search` 获取目标用户 userId +- `append-task` 的 `--appender-user-ids` 可通过 `dws contact user search` 获取目标用户 userId 但不能是自己 +- `append-task` 的 `--type` 值:before(前加签)、after(后加签)、Parallel(并加签) +- `append-task` 的 `--activate-type` 值:ALL(或签)、ONE_BY_ONE(依次审批) +- `append-task` 的 `--agree-all` 值:true(需要全部同意)、false(不需要全部同意) +- `revert-task` 的 `--action` 值:REVERT_FOR_APPROVAL(退回到某审批节点)、REVERT_FOR_RESUBMIT(退回到发起人) +- `revert-task` 退回前**必须先调用** `revert-activities --task-id ` 获取可回退节点列表 +- `revert-task` 的 `--target-activity-id` 和 `--action` **必须来自** `revert-activities` 返回,禁止自行编造或猜测 +- `revert-activities` 返回空 (`instRevertActivities` 为空) 时,必须明确告知用户"当前任务无可回退节点",禁止继续执行退回 +- `revert-task` 是不可撤回操作,执行前必须向用户确认退回方式及目标节点 - `revoke` 只能撤销自己发起的审批 - `--remark` 审批意见虽为可选,但建议填写以留存审批痕迹 -- `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: 时间戳无效`(实为区间过长,不是时间格式问题)。跨度大时请拆成多段短区间分别查询 +- `list-initiated` 的 `--process-code` 可从 `list-forms`、`search-forms` 或 `detail` 返回中提取 +- 已知表单名称关键字时优先用 `search-forms`;需枚举全部表单时用 `list-forms` +- 催办必须两步串联:`ding-info` 仅返回被催办人 `userId`,不返回 robotCode/content;需再调用 `dws ding message send`,其中 `--robot-code` 优先使用环境变量 `$DINGTALK_DING_ROBOT_CODE`,若无则向用户确认;`--content` 由 agent 根据审批上下文撰写催办文案;**严禁跳过 `ding-info` 直接猜测 userId** +- 催办文案建议格式:`"请尽快审批《{表单名}》(提交人:{发起人},提交时间:{时间})"` +- `ding-info` 返回空或报错时,必须明确告知用户"无法获取该任务的被催办人信息"并停止 +- DING 默认发应用内提醒(无成本);如需短信/电话提醒可加 `--type sms` 或 `--type call`(有成本,建议向用户确认) ## 自动化脚本 | 脚本 | 场景 | 用法 | |------|------|------| -| [oa_pending_review.py](../../scripts/oa_pending_review.py) | 查看待审批列表+逐条显示详情 | `python oa_pending_review.py --days 7` | -| [oa_batch_approve.py](../../scripts/oa_batch_approve.py) | 批量同意/拒绝审批项 | `python oa_batch_approve.py --action approve --days 7` | +| [oa_pending_review.py](../scripts/oa_pending_review.py) | 查看待审批列表+逐条显示详情 | `python oa_pending_review.py --days 7` | +| [oa_batch_approve.py](../scripts/oa_batch_approve.py) | 批量同意/拒绝审批项 | `python oa_batch_approve.py --action approve --days 7` | diff --git a/skills/multi/dingtalk-report/SKILL.md b/skills/multi/dingtalk-report/SKILL.md index 2b6a7cf8..614eda43 100644 --- a/skills/multi/dingtalk-report/SKILL.md +++ b/skills/multi/dingtalk-report/SKILL.md @@ -14,38 +14,55 @@ metadata: > 🧪 **EXPERIMENTAL · 试验版 / Preview** — multi 模式当前未达 stable 标准。全部 dingtalk-* skill 已通过 dispatch verifier,但接口、命名、跨 skill 引用后续可能调整;生产 / 共享环境请优先使用 mono 模式(`dws skill setup --mode mono`)。问题请提 issue 反馈。 -> **PREREQUISITE:** Read the `dws-shared` skill first for auth, global flags, product routing, URL preflight, error codes, and safety rules. The `dws` binary must be on PATH. +## 前置条件 — 执行操作前必读 + +> **`use_skill(dws-shared)`** — 认证、全局参数(`--format json` / `--yes`)、错误码、URL 模板、跨产品消歧、安全规则与 capability 边界。**执行任何 `dws` 命令前先读;** 单产品的清晰命令可直接用本 skill。 -> ⚠️ **命令可用性以当前 dws 二进制为准**。服务发现已下线,本文档随内置 skill 发布;如果 `dws --help` 不存在,说明当前版本未暴露该命令。若命令存在但调用失败,请按错误中的 endpoint 或 tool 提示确认静态端点目录和后端工具注册。实际调用前可用 `dws --help` 或 `--dry-run` 验证。 - - > 命令参考:[report.md](references/report.md);剧本:[05-reporting.md](references/05-reporting.md)。 ## 意图表 | 用户说 | 命令 | |--------|------| -| "今天收到的日志" | `python scripts/report_received_today.py` | -| "看日志模版" | `dws report template list` → `dws report template get --name "<模版名>"` | -| "提交日报 / 周报(按模版)" | `dws report entry submit --template-id --contents-file ` | -| "我收到的日志" | `dws report inbox list --start --end --cursor 0 --size 20` | -| "我已发送的日志" | `dws report outbox list --cursor 0 --size 20` | -| "查看某条日志正文" | `dws report entry get --report-id ` | -| "日志已读统计" | `dws report entry stats --report-id ` | +| "今天 / 最近 / 最近一周收到的日志" | `dws report inbox list --start "" --end "" --cursor 0 --size 20 --format json` | +| "看日志模版" | `dws report template list --format json` → `dws report template get --name "<模版名>" --format json` | +| "提交日报 / 周报(按模版)" | `dws report entry submit --template-id --contents-file --format json` | +| "我已发送 / 我创建 / 我发过的日志" | `dws report outbox list --cursor 0 --size 20 --format json` | +| "看日志正文 / 总结多篇日志" | 列表取 `reportId` 后逐篇 `dws report entry get --report-id --format json` | +| "日志已读统计" | `dws report entry stats --report-id --format json` | | "生成日报 / 周报 / 月报 / 主题报告" | 见 [05-reporting.md](references/05-reporting.md) recipe | -> 已废弃的旧命令 `report list` / `report detail` / `report sent` / `report stats` / `report create` / `report template detail` 均标 [deprecated],请一律改用上表的 `inbox list` / `outbox list` / `entry get` / `entry stats` / `entry submit` / `template get`。 +## Lite Recipes + +### view-report-inbox + +1. 收到的日志:把用户时间词转换为 Asia/Shanghai 的完整 ISO 起止时间,再执行 `dws report inbox list --start "" --end "" --cursor 0 --size 20 --format json`;用户只说“最近 / 近期 / 最近收到 / 最近一周”时默认最近 7 天。 +2. 我发过 / 我创建 / 已发送:第一条查询必须用 `dws report outbox list --cursor 0 --size 20 --format json`;有时间范围时补 `--start` / `--end`。 +3. 按发件人过滤收件箱:先 `dws aisearch person --keyword "<姓名>" --dimension name --format json` 取 `userId/staffId`,再给 `inbox list` 加 `--sender-user-ids `;空结果必须说明未找到该发件人的日志,不得改选其他人。 +4. 用户要正文、详情、汇总或总结多篇日志时,对选中的每篇日志逐条执行 `dws report entry get --report-id --format json`;不足 5 篇按实际数量说明。 + +### check-report-read-status + +`dws report entry stats --report-id --format json`;`reportId` 来自列表结果或用户明确提供的 ID,不要用标题猜测。 ## 日志查询硬约束 -- 查“收到的日志”必须用 `dws report inbox list --start "" --end "" --cursor 0 --size 20 --format json`,并把“今天 / 最近 30 天”等时间词先展开成完整 ISO 起止时间;查“我发出的日志”用 `dws report outbox list --cursor 0 --size 20 --format json`。 -- 列表返回后,后续 `entry get` / `entry stats` 必须复用同一个 `reportId`(在返回的 `_internalDetailCommands[].command` 里);不要重新挑选、猜测或改用标题。 -- 用户要正文时用 `dws report entry get --report-id `;用户要已读/统计时用 `dws report entry stats --report-id `。 +- 第一条查询命令必须按视角选新路径:收到/收件箱/别人发给我 → `dws report inbox list`;已发/我创建/我发过 → `dws report outbox list`。不要先生成 `report list` / `report sent` / `report detail` / `report stats` 等 deprecated alias。 +- `--size` 最大只能传 20;需要更多结果时按 `cursor` 分页,禁止传 50/100。 +- 查“收到的日志”必须传 `--start` / `--end`,时间一律使用 Asia/Shanghai 的 `YYYY-MM-DDT00:00:00+08:00` 到 `YYYY-MM-DDT23:59:59+08:00`;禁止 UTC `Z` / `date -u`。 +- 用户说“最近 / 近期 / 最近收到”但未给具体范围时,默认最近 7 天;“最近一周”同样展开为最近 7 天。 +- 按发件人过滤收件箱时,先用 `dingtalk-aisearch` 查人得到 `userId/staffId`,再调用 `dws report inbox list ... --sender-user-ids --format json`;没有匹配结果时必须说明未找到该发件人的日志,不得改选其他发件人。 +- 列表返回后,后续 `entry get` / `entry stats` 必须复用同一个 `reportId`;不要重新挑选、猜测或改用标题。 +- 用户要正文、详情、汇总或总结多篇日志时,必须对选中的每篇日志逐一调用 `dws report entry get --report-id --format json`,不要把 list 当正文接口。 +- CSV / 群聊导出的“日志”、系统日志、聊天记录核验不属于钉钉 OA 日志;不要用本 skill 强行处理,应转到文件/群聊/表格相关 skill。 ## 跨产品协作 - 日报内容来源(待办 / 听记 / OA / 邮件 / 群消息)→ 多源采集,按 dws-shared 的 conventions.md 并行执行 - 把汇总写文档 → 切到 `dingtalk-doc`(`dws doc create` + `dws doc update --mode append`) - 注意:`submit-report` 走 report 模版提交,**不要**走 doc 写文档 +## 局部意图与 Recipe + +- [局部意图消歧](references/intent-guide.md);[Lite Recipe](references/lite-recipes.md)。 diff --git a/skills/multi/dingtalk-report/references/05-reporting.md b/skills/multi/dingtalk-report/references/05-reporting.md index f1e0e971..af075ade 100644 --- a/skills/multi/dingtalk-report/references/05-reporting.md +++ b/skills/multi/dingtalk-report/references/05-reporting.md @@ -1,6 +1,6 @@ # 工作汇报 -> lite recipe 见 `dws-shared/references/best_practices/_common/lite-recipes.md`。 +> lite recipe(`view-report-inbox`、`check-report-read-status`)见 [SKILL.md](../SKILL.md#lite-recipes)。 ## 路径分歧(先判定后选 recipe) @@ -21,9 +21,9 @@ | Recipe | 行动指南(固定路线) | |--------|-------------------| -| query-report | **0. 前置判定**:query 含「查日志 / 看日志 / 我发过的日志 / 收到的日志 / 日志详情」且语义指向钉钉日志 OA 应用?是 → 直接走 `dws report`;否 → 先按 doc/report 分歧澄清
1. 用户说「我发过 / 我创建」→ `report outbox list --cursor 0 --size 20 --format json`;用户说「收到 / 别人发给我」→ `report inbox list --start "" --end "" --cursor 0 --size 20 --format json`
2. 时间 flag 只允许 `--start` / `--end`,禁止 `--start-date` / `--end-date` / `--date`;不要只传裸日期,必须展开完整 ISO;不要先查 `help`,不要预先登录;只有命令返回认证错误时才处理认证
3. 从列表返回中取 `reportId`(在 `_internalDetailCommands[].command` 里)留给内部后续调用;如果用户已直接提供 `reportId`,跳过列表
4. 面向用户展示列表时直接原样输出返回里的 `agentDisplayMarkdown`(表头 `日期 | 标题 | 发送人 | 状态 | 钉钉链接`),或基于 `result[]` 的五个中文字段拼表;不要把日志 ID 作为主列;已读状态字段缺失则不展示,不要编造
5. 用户要看正文时再执行 `report entry get --report-id --format json`;用户要统计 / 已读情况时执行 `report entry stats --report-id --format json`
**不要把 list 当正文接口**;查询正文必须补 `entry get` | -| generate-daily-report | 1. 按「多源并行采集」(见 `dws-shared/references/best_practices/_common/conventions.md`)执行(时间=今日)
2. 交叉汇总并把日报内容写入临时文件 `.md`(UTF-8,真实换行)
3. **创建文档**:`doc create --name "<日报名>" --content-file .md`(> 200KB 按 `dingtalk-doc/references/04-document.md` 的 write-doc 兜底走 create 空 → 循环 update) | -| generate-weekly-report | 1. 按「多源并行采集」(见 `dws-shared/references/best_practices/_common/conventions.md`)执行(时间=本周)
2. 交叉对比并把周报内容写入临时文件 `.md`
3. **创建文档**:`doc create --name "<周报名>" --content-file .md`(兜底同上) | -| submit-report | **0. 前置判定**:query 含「钉钉日志 / OA 周报模板 / 我的钉钉日志」等强信号?是 → 继续;否 → 切换到 `generate-weekly-report` 或 `generate-daily-report`(走 dws doc)
1. 按「多源并行采集」(见 `dws-shared/references/best_practices/_common/conventions.md`)执行(时间=当日)
2. `report template list --format json` → 取 `report_template_id`(顶层 `items[]` 下,无 `result` 包裹)
3. `report template get --name "<模版名>" --format json` → 取 `result.report_template_fields[]`,每项含 `field_name`/`field_sort`/`field_type`
4. **把 contents 写入当前工作目录下的临时文件**(避免 shell 引号问题;路径必须在 CWD 之下,不能用 /tmp):每项含 `key`/`sort`/`content`/`contentType`/`type` 五个字段,**严格映射** `field_name → key`、`field_sort → sort`、`field_type → type`,再填 `content` 与 `contentType`
5. `report entry submit --template-id --contents-file (或 echo "$json" | ... --contents -)--format json` → CLI 会在提交成功后自动反查详情并追加 `dingtalkOpenMarkdownLink` / `dingtalkOpenUrl` / `dingtalkOpenLink` 字段;取返回的 `reportId` 与钉钉打开链接
6. final reply 优先直接使用 `dingtalkOpenMarkdownLink`,让用户点击跳转钉钉客户端查看 / 修改;仅当 submit 返回中缺少 `dingtalkOpenUrl` 时,才手动执行 `report entry get --report-id --format json` 补取 `result.url`,再包装成 `[在钉钉中查看日志](result.url)`
**不要走 doc 写文档**;**禁止跳过 2/3 步**直接 submit;**禁止把 raw `dingtalk://...` URL 直接粘到回复**,必须包成 markdown link | -| generate-monthly-report | 1. 按「多源并行采集」(见 `dws-shared/references/best_practices/_common/conventions.md`)执行(时间=当月)
2. `report outbox list --start "<月初ISO>" --end "<月末ISO>" --cursor 0 --size 20 --format json`(单次跨度 ≤ 20 天,跨月需分窗滚动)→ 取当月已提交日志
3. 按周分段归纳并把月报内容写入临时文件 `.md`
4. **创建文档**:`doc create --name "<月报名>" --content-file .md`(兜底同上) | -| generate-topic-report | 1. 提取主题关键词;推断时间范围("最近"默认近 30 天)
2. 按「多源并行采集」(见 `dws-shared/references/best_practices/_common/conventions.md`)执行
3. 按时间线排列,交叉归纳核心结论/决策/行动项/未解决问题/演进脉络,并把内容写入临时文件 `.md`
4. **创建文档**:`doc create --name "<报告名>" --content-file .md`(兜底同上) | +| query-report | **0. 前置判定**:query 含「查日志 / 看日志 / 我发过的日志 / 收到的日志 / 日志详情」且语义指向钉钉日志 OA 应用?是 → 直接走 `dws report`;CSV 附件、群聊导出日志、系统日志或聊天记录核验不属于 OA 日志,应转到文件/群聊/表格相关 skill;其余歧义先按 doc/report 分歧澄清
1. 第一条有效查询必须按视角选择新命令:用户说「我发过 / 我创建 / 已发送」→ `report outbox list --cursor 0 --size 20 --format json`;用户说「收到 / 收件箱 / 别人发给我 / 最近收到」→ `report inbox list --start "" --end "" --cursor 0 --size 20 --format json`;不要先生成 `report list` / `report sent` 等 deprecated alias,也不要用 inbox 代替 outbox
2. 时间 flag 只允许 `--start` / `--end`;裸日期必须展开完整 ISO + `+08:00`;禁止 `--start-date` / `--end-date` / `--date`、UTC `Z`、`date -u`;用户只说「最近 / 近期 / 最近收到 / 最近一周」默认最近 7 天;`--size` 最大 20,更多结果按 `cursor` 分页,禁止传 50/100
3. 按发件人查收件箱时,先 `aisearch person --keyword "<姓名>" --dimension name --format json` 取 `userId/staffId`,再给 `inbox list` 加 `--sender-user-ids `;如果列表中找不到目标发件人或目标标记日志,必须说明不可见 / 未找到,不得改选其他发件人或其他日志
4. 从列表返回中取 `reportId` 留给内部后续调用;如果用户已直接提供 `reportId`,跳过列表;面向用户展示列表时基于 `result[]` 拼 Markdown 表:`日期 | 标题 | 发送人 | 状态 | 钉钉链接`,不要把日志 ID 作为主列,缺失字段不编造
5. 用户要正文、详情、汇总、总结多篇日志或检查内容时,必须对选中的每篇日志逐条执行 `report entry get --report-id --format json`;例如“总结最近收到的 5 篇”应先 list 取前 5 篇(不足 5 篇按实际数量说明),再执行相同数量的 `entry get`;用户要统计 / 已读情况时执行 `report entry stats --report-id --format json`
**不要把 inbox list/outbox list 当正文接口**;查询正文必须补 `entry get`
**不要再生成** `report list` / `report sent` / `report detail` / `report stats`(deprecated alias,仍能跑但会打 stderr 警告) | +| generate-daily-report | 1. 按[「多源并行采集」](_common/conventions.md#多源并行采集公共模式)执行(时间=今日)
2. 交叉汇总并把日报内容写入临时文件 `.md`(UTF-8,真实换行)
3. **创建文档**:`doc create --name "<日报名>" --content-file .md`(> 200KB 按 write-doc 兜底(见 `dingtalk-doc/references/04-document.md`) 走 create 空 → 循环 update) | +| generate-weekly-report | 1. 按[「多源并行采集」](_common/conventions.md#多源并行采集公共模式)执行(时间=本周)
2. 交叉对比并把周报内容写入临时文件 `.md`
3. **创建文档**:`doc create --name "<周报名>" --content-file .md`(兜底同上) | +| submit-report | **0. 前置判定**:query 含「钉钉日志 / OA 周报模板 / 我的钉钉日志」等强信号?是 → 继续;否 → 切换到 `generate-weekly-report` 或 `generate-daily-report`(走 dws doc)
1. 按[「多源并行采集」](_common/conventions.md#多源并行采集公共模式)执行(时间=当日)
2. `report template list --format json` → 取 `report_template_id`
3. `report template get --name "<模版名>" --format json` → 取 `result.report_template_fields[]`,每项含 `field_name`/`field_sort`/`field_type`
4. **把 contents 写入临时文件**(避免 shell 引号问题):每项含 `key`/`sort`/`content`/`contentType`/`type` 五个字段,**严格映射** `field_name → key`、`field_sort → sort`、`field_type → type`,再填 `content` 与 `contentType`
5. `report entry submit --template-id --contents-file .json --format json` → CLI 会在提交成功后自动反查详情并追加 `dingtalkOpenMarkdownLink` / `dingtalkOpenUrl` / `dingtalkOpenLink` 字段;取返回的 `reportId` 与钉钉打开链接
6. final reply 优先直接使用 `dingtalkOpenMarkdownLink`,让用户点击跳转钉钉客户端查看 / 修改;仅当 submit 返回中缺少 `dingtalkOpenUrl` 时,才手动执行 `report entry get --report-id --format json` 补取 `result.url`,再包装成 `[在钉钉中查看日志](result.url)`
**不要走 doc 写文档**;**禁止跳过 2/3 步**直接 submit;**禁止把 raw `dingtalk://...` URL 直接粘到回复**,必须包成 markdown link
**不要再生成** `report template detail` / `report create`(deprecated alias,仍能跑但会打 stderr 警告) | +| generate-monthly-report | 1. 按[「多源并行采集」](_common/conventions.md#多源并行采集公共模式)执行(时间=当月)
2. `report outbox list --start "<月初ISO>" --end "<月末ISO>"` → 取当月已提交日志
3. 按周分段归纳并把月报内容写入临时文件 `.md`
4. **创建文档**:`doc create --name "<月报名>" --content-file .md`(兜底同上) | +| generate-topic-report | 1. 提取主题关键词;推断时间范围("最近"默认近 30 天)
2. 按[「多源并行采集」](_common/conventions.md#多源并行采集公共模式)执行
3. 按时间线排列,交叉归纳核心结论/决策/行动项/未解决问题/演进脉络,并把内容写入临时文件 `.md`
4. **创建文档**:`doc create --name "<报告名>" --content-file .md`(兜底同上) | diff --git a/skills/multi/dingtalk-report/references/_common/conventions.md b/skills/multi/dingtalk-report/references/_common/conventions.md new file mode 100644 index 00000000..75491aab --- /dev/null +++ b/skills/multi/dingtalk-report/references/_common/conventions.md @@ -0,0 +1,44 @@ +# 业务域通用规范 + +> 仅服务本 skill 已迁入的行动指南。安全门控、危险操作确认、`--format json` 等已在本 skill 的 `SKILL.md` 中定义,此处不重复。 + +## 批量查询规范 + +| # | 规范 | +|---|------| +| 1 | **并行查详情**:拿到多个 ID 后,用 `&` 合并到同一条 Shell 命令并行执行 + `wait`,**严禁逐条串行** | +| 2 | **翻页**:分页接口须拉全直至无更多 | +| 3 | **优先批量 API**:有批量接口则用批量;无则按 #1 并行 | +| 4 | **群消息**:必须先 `chat search --query` 得 `openConversationId`,再 `chat message list --group`;多群同条命令并行 | +| 5 | **列表少轮次**:带条件搜索/列表 → 一次采全详情;**禁止**无新参数时重复同一 `list` / `search` | + +## 多源并行采集(公共模式) + +> recipe 引用方式:`按「多源并行采集」执行(关键词=,时间=至)`。 + +- 同条 Shell:`&` 并行 + `wait`;分页须采全。 +- 只保留与主题相关的数据,无关丢弃。 +- 有批量详情接口优先;否则并行拉详情(见上表 #1)。 +- 具体采哪些产品列表由对应 **行动指南 recipe** 与当前产品参考决定;不要引入本文档未覆盖的产品路线。 + +## 字段术语与 ID 传递 + +> list 返回 JSON 后,必须提取下表字段传给后续命令。**禁止用其他字段替代。** + +| 字段 | 来源 | 传递给 | +|------|------|--------| +| `taskUuid` | `minutes list` | `minutes get summary/info/batch --id(s)` | +| `userId` | `aisearch person` / `contact user search` / `contact dept list-members` | `contact user get --ids`、`todo --executors`、`calendar --users` | +| `deptId` | `contact dept search` | `contact dept list-members --depts`;多子部门时对每个子部门分别 `dept search` 取 id | +| `nodeId` | `drive search` / `wiki node search` | `doc read/update --node`、`drive copy/move/rename/delete --node` | +| `nodeId` | `wiki node list` 中的 folder 类型节点 / `wiki node create --type folder` | `wiki node list --folder`、`wiki node create --folder`、`drive upload --folder`、`drive copy/move --folder` | +| `eventId` | `calendar event list` | `calendar event get/update --id` | +| `processInstanceId` | `oa approval list-*` | `oa approval detail/approve --instance-id` | +| `openConversationId` | `chat search` | `chat message list/send --group` | +| `todoTaskId` | `todo task list` | `todo task update/done --task-id` | +| `reportId` | `report inbox list` / `report outbox list` | `report entry get/stats --report-id` | +| `baseId` / `tableId` | `aitable base search` | `aitable record query --base-id --table-id` | +| `dentryUuid` | `drive list` / `drive mkdir` | `drive info/download --node`、`drive list/mkdir/upload --folder` | +| `dentryId` | `drive info` 的数字字段 | 仅用于 `chat message send --dentry-id` | + +**ID 边界硬约束**:`dentryId` 通常是纯数字,只表示聊天文件消息需要的钉盘条目数字 ID;它不是父目录 ID。遇到 `drive --node/--folder`、`doc --node/--folder`、`wiki node --folder` 时,只能使用 `dentryUuid` / `nodeId` / 文档 URL。若当前上下文只有数字型 `dentryId`,必须先重新 `drive list` / `drive search` / `wiki node list` 获取正确 ID,不能把该数字直接代入后续命令。 diff --git a/skills/multi/dingtalk-report/references/intent-guide.md b/skills/multi/dingtalk-report/references/intent-guide.md new file mode 100644 index 00000000..4f19e0c6 --- /dev/null +++ b/skills/multi/dingtalk-report/references/intent-guide.md @@ -0,0 +1,11 @@ +# report 局部意图消歧 + +本文件从单 Skill `intent-guide.md` 拆分而来,仅保留与本产品相关的跨产品消歧规则。 + +| 用户说... | 真实意图 | 应该用 | 不要用 | 理由 | +|---|---|---|---|---| +| "帮我看看收到的日报" | 收到的日志 | `report` | `doc` | 钉钉日志系统(日报/周报),不是文档 | +| "帮我创建一个待办提醒" | 个人待办 | `todo` | `report` | 个人任务提醒,不是日志汇报 | +| "把最近几次关于XX的会议汇总成报告" | 按主题汇总多次听记 | #5 generate-topic-report | #7 meeting-followup | #7 是单次会议听记跟进;多次会议按主题汇总属于工作汇报 | +| "整理一下XX项目的所有讨论" | 跨源主题归档 | #5 generate-topic-report | #4 write-doc | #4 侧重单篇文档创作;按主题跨听记/群消息汇总属于工作汇报 | +| "我发给某人的消息/邮件/文档/今天我干了什么" | 搜行为记录 | `aisearch behavior` | `chat` / `mail` / `doc` / `report` | 关注“谁对什么做过什么”,走 behavior;具体 `behavior-type/direction/chat-scope` 抽槽见 `aisearch.md` | diff --git a/skills/multi/dingtalk-report/references/lite-recipes.md b/skills/multi/dingtalk-report/references/lite-recipes.md new file mode 100644 index 00000000..0931801a --- /dev/null +++ b/skills/multi/dingtalk-report/references/lite-recipes.md @@ -0,0 +1,25 @@ +# report Lite Recipe + +本文件从单 Skill `lite-recipes.md` 拆分而来,仅保留与本产品相关的轻量流程。 + +## #5 工作汇报 + +### query-report-list + +1. 收到的日志:先把用户时间词转成起止时间,再执行 `report inbox list --start "" --end "" --cursor 0 --size 20 --format json`。用户只说“最近/近期/最近收到”时默认最近 7 天。 +2. 我发过/我创建的日志:首条查询必须用 `report outbox list --cursor 0 --size 20 --format json`;如用户指定时间,补 `--start "" --end ""`。 +3. 按发件人过滤收件箱:先 `aisearch person --keyword "<姓名>" --dimension name --format json` 取 `userId/staffId`,再加 `--sender-user-ids `;空结果必须说明未找到该发件人的日志,不得改选其他人。 +4. 面向用户时必须基于 `result[]` 拼 Markdown 表,表头固定为 `日期 | 标题 | 发送人 | 状态 | 钉钉链接`;每条 `result[]` 都会带这五个中文字段,不要把 `reportId` / `日志ID` 作为主列。 +5. 用户要正文、详情、统计、汇总或总结多篇日志时,必须用内部保留的 `reportId` 逐篇执行 `report entry get --report-id --format json` 或 `report entry stats --report-id --format json`;选前 5 篇时调用次数应等于实际选中篇数。 + +时间 flag 硬约束:只允许 `--start` / `--end`;禁止 `--start-date` / `--end-date` / `--date`。不要只传 `2026-05-04`,必须展开成 `2026-05-04T00:00:00+08:00` 这种完整 ISO;禁止 UTC `Z` / `date -u`。 + +硬约束:`report inbox list` 是收到的日志(别人发给我),`report outbox list` 是我创建/发出的日志(我发给别人)。不要混淆方向;不要回答"API 不支持收到的日志"。 + +> 旧命令兼容:`report list` / `report inbox` / `report sent` / `report created` / `report detail` / `report stats` 仍可执行,但已 deprecated,stderr 会打废弃提醒,新计划一律使用 `inbox list` / `outbox list` / `entry get` / `entry stats`。 + +禁止:不要先查 help,不要为了格式化列表创建脚本;不要传 `--size 50/100`。`report inbox` 可作为兼容入口使用,但新计划优先写规范命令 `report inbox list --start "" --end "" --cursor 0 --size 20 --format json`。 + +### check-report-read-status + +`report entry stats --report-id ` → 已读/未读 diff --git a/skills/multi/dingtalk-report/references/report-index.md b/skills/multi/dingtalk-report/references/report-index.md new file mode 100644 index 00000000..d26827a2 --- /dev/null +++ b/skills/multi/dingtalk-report/references/report-index.md @@ -0,0 +1,6 @@ +# 日志能力索引 + +| 主题 | 详见 | +|---|---| +| 日志模板/查询/提交/已读统计 | [`report.md`](./report.md) | +| 汇报最佳实践 | [`05-reporting.md`](./05-reporting.md) | diff --git a/skills/multi/dingtalk-report/references/report.md b/skills/multi/dingtalk-report/references/report.md index c4c6e37a..f97e6bd3 100644 --- a/skills/multi/dingtalk-report/references/report.md +++ b/skills/multi/dingtalk-report/references/report.md @@ -1,6 +1,6 @@ # 日志 (report) 命令参考 -> **注意**:钉钉日志 = 「OA 周报应用」(按模版填报 / 收件箱 / 已发列表 / 统计),不是通用日志或记录系统。`dws report` 与其别名 `dws log` 自动由 envelope 注册——`dws report --help` 输出会列出全部别名,文档不再单独声明。 +> 命令别名: `dws log` 等价于 `dws report`(注意:此处 `log` 特指 OA 周报应用,不是通用日志/记录) ## 载体辨义(首屏必读) @@ -22,8 +22,8 @@ - `dws report inbox list` = 列出**我收到**的日报(别人发给我的)。 - `dws report outbox list` = 列出**我发出**的日报(我创建或提交的)。 -- `dws report entry get --report-id ` = 读取单份日报正文 + 钉钉跳转链接。 -- `dws report entry stats --report-id ` = 读取单份日报的已读统计。 +- `dws report entry get --report-id --format json` = 读取单份日报正文 + 钉钉跳转链接。 +- `dws report entry stats --report-id --format json` = 读取单份日报的已读统计。 - `dws report entry submit --template-id ... --contents-file ...` = 按模版提交一份新日报。 - `dws report template list` = 列出可用日报模版。 - `dws report template get --name "<模版名>"` = 读取单个模版的字段定义(contents 拼装来源)。 @@ -31,29 +31,46 @@ | 用户意图 | 第一条有效指令 | 后续动作 | |----------|----------------|----------| | 查我发过的日志 / 我创建的日志 | `dws report outbox list --cursor 0 --size 20 --format json` | 从返回里取 `reportId`,再执行 `dws report entry get --report-id --format json` | -| 查我收到的日志 / 别人发给我的日志 | `dws report inbox list --start "" --end "" --cursor 0 --size 20 --format json` | 必须先按用户时间词补齐完整 ISO 起止时间;取 `reportId` 后调用 `entry get` | +| 查我收到的日志 / 别人发给我的日志 | `dws report inbox list --start "" --end "" --cursor 0 --size 20 --format json` | 必须先按用户时间词补齐完整 ISO 起止时间;用户只说“最近/近期/最近收到”时默认最近 7 天;取 `reportId` 后调用 `entry get` | +| 按发件人查收到的日志 | 先 `dws aisearch person --keyword "<姓名>" --dimension name --format json`,再 `dws report inbox list ... --sender-user-ids --format json` | 只能在收件箱中过滤发件人;没有匹配时说明未找到,不得改选其他发件人 | | 查看某条日志正文 / 日志详情 | `dws report entry get --report-id --format json` | 如果用户没给 `reportId`,先用 `outbox list` 或 `inbox list` 找候选 | +| 总结最近收到的日志 / 汇总多篇日志内容 | 先 `dws report inbox list ... --size 20 --format json`,选前 5 篇或实际返回数量,再对每个 `reportId` 逐条执行 `dws report entry get --report-id --format json` | 不得只调用一次 list 后直接总结;不得把多篇合并成一次 detail 调用 | | 查某条日志统计 / 已读统计 | `dws report entry stats --report-id --format json` | 如果用户没给 `reportId`,先用 `outbox list` 或 `inbox list` 找候选 | | 查日志模版 / 有哪些周报模板 | `dws report template list --format json` | 需要字段定义时继续 `dws report template get --name "<模版名>" --format json` | | 提交 / 填写 / 创建钉钉日志 | `dws report template list --format json` | 再 `template get` 取字段定义,最后 `entry submit`;禁止直接编 contents | +### 句式映射补充(Agent 必须遵守) + +| 用户说法 | 必须执行 | 禁止行为 | +|----------|---------|----------| +| "有哪些日志模板 / 日报模板 / 模板列表 / 有什么模板可以用" | `dws report template list --format json` | 禁止 ai-seek / enterprise_search 等通用搜索代理绕行 | +| "日报模板 / 周报模板"(指定类型) | 先 `dws report template list --format json`,再从返回结果中按名称包含「日报」或「周报」筛选 | 不得虚构模板;不得跳过 `template list` 直接编造模板名 | +| "今天收到的日志 / 最近收到的日志 / 别人发我的日志" | `dws report inbox list --start ... --end ... --cursor 0 --size 20 --format json`;“最近/近期/最近收到”默认最近 7 天 | 禁止 grep_search / session_search / ai-seek 替代 | +| "过山发给我的日志 / 按发件人查收件箱" | 先 `dws aisearch person --keyword "<姓名>" --dimension name --format json` 取 ID,再 `dws report inbox list ... --sender-user-ids --format json` | 禁止用 `outbox list --user-id` 查他人已发日志;禁止返回其他发件人的日志 | +| "总结最近收到的 5 篇日志 / 合并多篇日志内容" | 先 `inbox list` 获取候选,再对选中的每个 `reportId` 逐条 `dws report entry get --report-id --format json` | 禁止停在 skill 激活或反问时间范围;不足 5 篇按实际数量总结并说明 | +| "日志模板详情 / 模板具体结构 / 模板有哪些字段" | 两步:① `dws report template list --format json` 获取模板名 → ② `dws report template get --name "<模版名>" --format json` | 禁止跳过第①步直接猜模板名调 `template get` | +| "日志ID xxx 详情 / 查一下日志编号 xxx" | 直接 `dws report entry get --report-id --format json` | 禁止反问用户确认 ID 是否正确;返回不存在则如实告知,不得虚构 | +| "我创建的 / 我发过的 / 已发的日志" | `dws report outbox list --cursor 0 --size 20 --format json` | 禁止用 `dws report inbox list` 替代 | + 效率约束: - 不要先调用 `dws report --help` / `dws report inbox list --help`。本页已经给出可执行命令。 - 查询收到的日志统一使用 `dws report inbox list`;查询发出的日志统一使用 `dws report outbox list`。 - 不要为了格式化结果创建脚本。直接从 JSON 里抽取关键字段,返回用户可读列表。 -- 对"最近一周"这类常用查询,直接把当前日期换算成 7 天窗口后执行一次 `inbox list`;如返回分页标记再继续翻页。 +- 对"最近 / 近期 / 最近收到 / 最近一周"这类常用查询,直接把当前日期换算成最近 7 天窗口后执行一次 `inbox list`;如返回分页标记再继续翻页。 +- `--size` 最大只能传 20;需要更多结果时按 `cursor` 分页,禁止传 50/100。 时间窗口默认规则: - `outbox list` 不传 `--start` / `--end` 时 CLI 默认最近 20 天,适合"我发过的日志 / 最近日志"。 -- `inbox list` 必须传 `--start` / `--end`;用户说"最近一周/今天/本周/昨天"时,Agent 必须先转成 `YYYY-MM-DDT00:00:00+08:00` 到 `YYYY-MM-DDT23:59:59+08:00`。 +- `inbox list` 必须传 `--start` / `--end`;用户说"最近/近期/最近收到/最近一周/今天/本周/昨天"时,Agent 必须先转成 `YYYY-MM-DDT00:00:00+08:00` 到 `YYYY-MM-DDT23:59:59+08:00`,其中“最近/近期/最近收到”默认最近 7 天。 - 查更早日志时每次窗口不要超过 20 天,按窗口滚动查询。 时间参数硬约束: - 只允许使用 `--start` 和 `--end` 两个 flag;禁止写 `--start-date`、`--end-date`、`--date`、`startDate`、`endDate`。 - 时间值推荐使用完整 ISO-8601 + 时区格式:`YYYY-MM-DDTHH:mm:ss+08:00`。 +- 必须使用 Asia/Shanghai 的 `+08:00` 时区;禁止用 UTC `Z`、`date -u` 或无时区裸时间。 - Agent 不要只传裸日期;即使 CLI 能兼容 `YYYY-MM-DD`,生成命令时也必须展开成当天起止时间,避免工具层把日期误解析成普通字符串。 - "最近一周"固定展开为:起始日 `T00:00:00+08:00`,结束日 `T23:59:59+08:00`。 @@ -81,6 +98,15 @@ dws report entry get --report-id --format json dws report inbox list --start "" --end "" --cursor 0 --size 20 --format json dws report entry get --report-id --format json +# 按发件人过滤收件箱 +dws aisearch person --keyword "<姓名>" --dimension name --format json +dws report inbox list --start "" --end "" --sender-user-ids --cursor 0 --size 20 --format json + +# 总结最近收到的多篇日志 +dws report inbox list --start "<最近7天起点+08:00>" --end "<当天终点+08:00>" --cursor 0 --size 20 --format json +dws report entry get --report-id <第1篇reportId> --format json +dws report entry get --report-id <第2篇reportId> --format json + # 已知 reportId dws report entry get --report-id --format json ``` @@ -101,7 +127,7 @@ dws report entry get --report-id --format json | 查看详情 | 从 `_internalDetailCommands[].command` 取内部详情命令;面向用户展示为“查看详情”,不要展示命令本身 | | 钉钉链接 | 详情接口返回 `dingtalkOpenMarkdownLink` / `dingtalkOpenUrl` / `result.url` 后,展示成 markdown 链接 | -CLI 列表命令只返回 JSON-first 数据,不把 Markdown 表作为裸文本直接输出。`report inbox list` / `report outbox list` 会对当前页 `result[]` 中所有带 `reportId` 的日志逐条调用 `dws report entry get --report-id --format json` 补齐展示字段;返回 JSON 会携带 `agentDisplayRequired=true`、`agentDisplayRequiredColumns`、`agentDisplayLinkColumn=钉钉链接`、`agentDisplayMarkdownRequired=true`、`agentDisplayMarkdown`、`agentDisplayMarkdownHeader` 等字段,这些是面向 Agent 的强制展示合同,不是可选建议。Agent 必须在 final reply 直接原样输出 `agentDisplayMarkdown`,让客户端按 Markdown 渲染出带可点击 `钉钉链接` 列的表格;不要自行重组列,不要改表头。 +CLI 列表命令只返回 JSON-first 数据,不把 Markdown 表作为裸文本直接输出。`report inbox list` / `report outbox list` 用于定位候选日志和展示列表;返回 JSON 可能携带 `agentDisplayRequired=true`、`agentDisplayRequiredColumns`、`agentDisplayLinkColumn=钉钉链接`、`agentDisplayMarkdownRequired=true`、`agentDisplayMarkdown`、`agentDisplayMarkdownHeader` 等字段,这些是面向 Agent 的强制展示合同,不是可选建议。用户只要列表时,Agent 必须在 final reply 直接原样输出 `agentDisplayMarkdown`,让客户端按 Markdown 渲染出带可点击 `钉钉链接` 列的表格;不要自行重组列,不要改表头。用户要正文、详情、统计、汇总或总结多篇日志时,列表只负责提供 `reportId`,必须再显式逐条调用 `dws report entry get` 或 `dws report entry stats`。 展示列按方向区分:`inbox list` 是收件箱,只展示 `日期 | 标题 | 发送人 | 状态 | 钉钉链接`,**禁止返回或展示日志正文、完整内容或日志内容摘要**;`outbox list` 是发件箱,可展示 `日期 | 标题 | 发送人 | 状态 | 日志内容 | 钉钉链接`,其中 `日志内容` 来自详情补齐,方便用户快速确认自己发出的内容。凡用户说"列出 / 找到 / 查询 / 搜索 / 看看日志",默认都要在 final reply 渲染 Markdown 表;只有用户明确表示"不关心列表内容 / 只要原始 JSON / 只要数量 / 只要 ID"时,才可以不渲染表。推荐用户可读输出: @@ -116,8 +142,8 @@ CLI 列表命令只返回 JSON-first 数据,不把 Markdown 表作为裸文本 - 详情阶段:`entry get` 返回里如果有 `dingtalkOpenMarkdownLink`,优先把操作列替换为该 markdown 链接;否则用 `dingtalkOpenUrl` 或 `result.url` 包成 `[在钉钉中查看日志](url)`。 - `钉钉链接` 是强制列:禁止省略、改名、合并到标题里,也禁止改成不含链接列的摘要表。 - 不要在用户表格里展示 `_internalDetailCommands`、raw `reportId`、raw `dingtalk://...`。链接必须是 markdown 可点击文本。 -- `dws report inbox list` 默认会对当前页所有可查看日志自动补 `entry get`,但只返回日期、标题、发送人、状态与可点击钉钉链接;需要正文时必须显式调用 `dws report entry get --report-id ... --format json`。 -- `dws report outbox list` 默认会对当前页所有可查看日志自动补 `entry get`,并可把 `日志内容` 纳入发件箱表格展示。 +- `dws report inbox list` 列表阶段只用于展示和定位;需要正文、详情、汇总或总结时必须显式调用 `dws report entry get --report-id ... --format json`。 +- `dws report outbox list` 列表阶段只用于展示和定位;即使返回中带摘要或部分内容,用户要求正文、详情或统计时仍必须继续调用 `entry get` / `entry stats`。 只有在用户明确要求"给我日志 ID / 方便我后续查询"时,才额外展示 `reportId`。否则 final reply 应保留可读信息,并说明"需要看正文或打开钉钉,我可以继续打开某一条"。 @@ -206,7 +232,7 @@ Usage: dws report entry get [flags] Example: # 先通过 report inbox list / outbox list / entry submit 取得 reportId,再查正文与跳转链接 - dws report entry get --report-id + dws report entry get --report-id --format json Flags: --report-id string 日志 ID (必填) ``` @@ -225,11 +251,11 @@ Flags: Usage: dws report inbox list [flags] Example: - dws report inbox list --start "2026-03-10T00:00:00+08:00" --end "2026-03-10T23:59:59+08:00" --cursor 0 --size 20 - dws report inbox list --start "2026-03-10T00:00:00+08:00" --end "2026-03-10T23:59:59+08:00" --sender-user-ids staffId1,staffId2 + dws report inbox list --start "2026-03-10T00:00:00+08:00" --end "2026-03-10T23:59:59+08:00" --cursor 0 --size 20 --format json + dws report inbox list --start "2026-03-10T00:00:00+08:00" --end "2026-03-10T23:59:59+08:00" --sender-user-ids staffId1,staffId2 --format json # 禁止:--start-date / --end-date / --date # 从列表结果提取 reportId 后,再查正文 - dws report entry get --report-id + dws report entry get --report-id --format json Flags: --cursor int 分页游标,首次传 0 (默认 0) --end string 结束时间 ISO-8601 (如 2026-03-10T23:59:59+08:00) (必填) @@ -239,14 +265,14 @@ Flags: ``` -`inbox list` 默认返回 JSON-first 结构:不会把 Markdown 表作为裸文本直接输出,但 JSON 里会返回 `agentDisplayMarkdown`。Agent 必须在 final reply 直接原样输出 `agentDisplayMarkdown`,让客户端按 Markdown 渲染出表格;不要基于个人偏好重新摘要。表头固定且必须逐字为 `| 日期 | 标题 | 发送人 | 状态 | 钉钉链接 |`,并按返回顺序展示(CLI 已按创建时间从近到远排序)。返回 JSON 中的 `agentDisplayRequired=true`、`agentDisplayRequiredColumns`、`agentDisplayLinkColumn=钉钉链接`、`agentDisplayMarkdownRequired=true`、`agentDisplayMarkdownHeader` 都是强制展示合同。凡用户说"列出 / 找到 / 查询 / 搜索 / 看看日志",final reply 默认必须渲染这张 Markdown 表;只有用户明确表示"不关心列表内容 / 只要原始 JSON / 只要数量 / 只要 ID"时,才可以不渲染表。`result[]` 每项都包含且只展示这五个字段:`日期`、`标题`、`发送人`、`状态`、`钉钉链接`。`钉钉链接` 是强制可点击 markdown 链接列,禁止省略、改名、合并到标题里;`inbox list` 禁止返回或展示 `日志内容` / `日志内容摘要`。`reportId` 不在主结果里,只通过 `_internalDetailCommands` 保留给 Agent 后续调用 `entry get` / `entry stats`;final reply 禁止展示 `_internalDetailCommands`、毫秒时间戳、raw ID 或日志正文。 +`inbox list` 默认返回 JSON-first 结构:不会把 Markdown 表作为裸文本直接输出,但 JSON 里会返回 `agentDisplayMarkdown`。Agent 必须在 final reply 直接原样输出 `agentDisplayMarkdown`,让客户端按 Markdown 渲染出表格;不要基于个人偏好重新摘要。表头固定且必须逐字为 `| 日期 | 标题 | 发送人 | 状态 | 钉钉链接 |`,并按返回顺序展示(CLI 已按创建时间从近到远排序)。返回 JSON 中的 `agentDisplayRequired=true`、`agentDisplayRequiredColumns`、`agentDisplayLinkColumn=钉钉链接`、`agentDisplayMarkdownRequired=true`、`agentDisplayMarkdownHeader` 都是强制展示合同。凡用户说"列出 / 找到 / 查询 / 搜索 / 看看日志",final reply 默认必须渲染这张 Markdown 表;只有用户明确表示"不关心列表内容 / 只要原始 JSON / 只要数量 / 只要 ID"时,才可以不渲染表。`result[]` 每项都包含且只展示这五个字段:`日期`、`标题`、`发送人`、`状态`、`钉钉链接`。`钉钉链接` 是强制可点击 markdown 链接列,禁止省略、改名、合并到标题里;`inbox list` 禁止返回或展示 `日志内容` / `日志内容摘要`。Agent 必须从 `result[]` 或 `_internalDetailCommands` 保留每条日志的 `reportId` 和时间字段,用于后续 `entry get` / `entry stats`;final reply 禁止展示 `_internalDetailCommands`、毫秒时间戳、raw ID 或日志正文。 ### 读取单份日报的已读统计 ``` Usage: dws report entry stats [flags] Example: - dws report entry stats --report-id + dws report entry stats --report-id --format json Flags: --report-id string 日志 ID (必填) ``` @@ -257,11 +283,11 @@ Flags: Usage: dws report outbox list [flags] Example: - dws report outbox list --cursor 0 --size 20 - dws report outbox list --cursor 0 --size 20 --start "2026-03-10T00:00:00+08:00" --end "2026-03-10T23:59:59+08:00" - dws report outbox list --cursor 0 --size 20 --template-name "日报" + dws report outbox list --cursor 0 --size 20 --format json + dws report outbox list --cursor 0 --size 20 --start "2026-03-10T00:00:00+08:00" --end "2026-03-10T23:59:59+08:00" --format json + dws report outbox list --cursor 0 --size 20 --template-name "日报" --format json # 从列表结果提取 reportId 后,再查正文 - dws report entry get --report-id + dws report entry get --report-id --format json Flags: --cursor int 分页游标,首次传 0 (默认 0) --size int 每页条数,最大 20 (默认 20),别名: --limit @@ -288,7 +314,7 @@ Flags: 读取已有日志内容时,统一按下面两步走,不要把列表接口当正文接口: 1. `dws report inbox list ...` 或 `dws report outbox list ...`,先拿到目标日志的 `reportId` -2. `dws report entry get --report-id `,再读取正文和字段明细 +2. `dws report entry get --report-id --format json`,再读取正文和字段明细 适用场景: @@ -343,8 +369,8 @@ dws report outbox list --cursor 0 --size 20 --format json | 操作 | 从返回中提取 | 用于 | |------|-------------|------| -| `template list` | template 名称(`items[].report_template_name`;顶层键只有 `items` 和 `success`,**无 `result` 包裹**) | `template get` 的 --name | -| `template list` | `items[].report_template_id` | `entry submit` 的 --template-id | +| `template list` | template 名称(result.items[].report_template_name) | `template get` 的 --name | +| `template list` | `report_template_id` | `entry submit` 的 --template-id | | `template get` | `result.report_template_fields[].field_name` / `field_sort` / `field_type` | 拼 `entry submit` 的 --contents JSON(按下表映射)| | `entry submit` | `reportId`、`dingtalkOpenMarkdownLink`、`dingtalkOpenUrl`(CLI 自动反查详情后追加)| final reply 优先直接使用 `dingtalkOpenMarkdownLink`;需要结构化展示时用 `dingtalkOpenLink.title` + `dingtalkOpenLink.url` | | `inbox list` / `outbox list` | `reportId` | `entry get` / `entry stats` 的 --report-id | @@ -376,11 +402,11 @@ dws report outbox list --cursor 0 --size 20 --format json | Code | ExitCode | 真实含义 | 建议动作 | |------|---------|---------|---------| | `INPUT_INVALID_JSON` | 3 | `--contents` 或 `--contents-file` 内容非合法 JSON | 检查 JSON 数组结构,每项必须是 object,含 `key`/`sort`/`content`/`contentType`/`type` 五个字段 | -| `INPUT_FILE_NOT_FOUND` | 3 | `--contents-file` 指向的文件不存在(路径必须在当前工作目录 CWD 之下)| 确认文件存在且位于 CWD 内;把 contents JSON 写到 CWD 下的相对路径再传 | -| `INPUT_INVALID_PATH` | 3 | `--contents-file` 路径越出当前工作目录(如 `/tmp`、`os.tmpdir()`、`../` 或指向目录外的符号链接)| **不要**用 `/tmp` / `os.tmpdir()` / `../`;把 contents JSON 写到当前工作目录之下再传 | +| `INPUT_FILE_NOT_FOUND` | 3 | `--contents-file` 路径不存在 / sandbox OS 风格不匹配(macOS 路径在 Windows 沙箱)| 先确认 sandbox OS 与路径风格;改写到 `os.tmpdir()` 等可移植目录 | | `INPUT_MISSING_PARAM` | 3 | `--template-id` / `--contents` 必填缺失 | 显式传值;从 `template list` 取合法 templateId | | `INPUT_TOO_LARGE` | 3 | contents JSON 超过 10MB 限制 | **不支持分批次提交**。需精简内容或拆分为多个独立日志分别提交 | -| `MCP_TOOL_ERROR` / `[UNCLASSIFIED] business error` | 1 | 服务端业务错,`success=false`、`code=1`。覆盖:submit 时 templateId 错 / 字段名错 / 字段值错 / contents 空(`server_error_code: PARAM_ERROR`);`entry get` / `entry stats` 传了不存在的 reportId(`server_error_code: BUSINESS_ERROR`,technical_detail 含 `report not exist`);`template get` 传了不存在的模版名(`server_error_code: PARAM_ERROR`)| 查看 `server_error_code` / `technical_detail`;reportId / 模版名找不到时先用 `inbox list` / `outbox list` / `template list` 重新取合法值,不要猜;submit 类错误按链路重走 `template list → template get → entry submit`,连续 ≥ 2 次仍失败必须停止重试,降级 final_reply | +| `MCP_TOOL_ERROR` | 1 | 服务端业务错(含 `server_error_code: PARAM_ERROR`,覆盖 templateId 错 / 字段名错 / 字段值错 / contents 空等多种形态)| 查看 `server_error_code` / `technical_detail`;服务端不区分具体子错因,按提交链路重新走 `template list → template get → entry submit`;连续 ≥ 2 次仍失败必须停止重试,降级 final_reply | +| `RESOURCE_NOT_FOUND` | 1 | reportId / templateId 在服务端找不到 | 用 `list` 或 `template list` 重新获取 | ## 何时停止重试 diff --git a/skills/multi/dingtalk-report/scripts/report_received_today.py b/skills/multi/dingtalk-report/scripts/report_received_today.py index 7fd87617..e7e66211 100644 --- a/skills/multi/dingtalk-report/scripts/report_received_today.py +++ b/skills/multi/dingtalk-report/scripts/report_received_today.py @@ -2,14 +2,9 @@ """ 查看今天收到的日志列表及详情 -基于 `dws report inbox list` + `dws report entry get`(旧的 report list / report detail -已废弃)。inbox list 返回的 result[] 使用中文展示键:日期 / 标题 / 发送人 / 状态 / 钉钉链接; -reportId 不在 result[] 里,只在 _internalDetailCommands[].command 中,按页与 result[] 同序对应。 - 用法: python report_received_today.py - python report_received_today.py --days 3 # 最近 3 天 - python report_received_today.py --detail # 额外拉取每条正文 (entry get) + python report_received_today.py --days 3 # 最近 3 天 python report_received_today.py --dry-run """ @@ -18,7 +13,7 @@ import json import subprocess import argparse from datetime import datetime, timedelta -from typing import List, Any, Optional, Tuple +from typing import List, Any, Optional def run_dws( @@ -42,118 +37,111 @@ def run_dws( return None -def iso_start(dt: datetime) -> str: - return dt.strftime('%Y-%m-%dT00:00:00+08:00') - - -def iso_end(dt: datetime) -> str: - return dt.strftime('%Y-%m-%dT23:59:59+08:00') - - -def report_id_from_command(cmd: str) -> str: - parts = cmd.split() - if '--report-id' in parts: - i = parts.index('--report-id') - if i + 1 < len(parts): - return parts[i + 1] - return '' - - -def fetch_inbox( - start: str, end: str, dry_run: bool, -) -> List[Tuple[dict, str]]: - """按 cursor 翻页拉全 inbox;返回 (result_item, reportId) 列表。""" - pairs: List[Tuple[dict, str]] = [] - cursor = 0 - while True: - data = run_dws([ - 'report', 'inbox', 'list', - '--start', start, - '--end', end, - '--cursor', str(cursor), - '--size', '20', - '--format', 'json', - ], dry_run=dry_run) - if dry_run or not isinstance(data, dict): - return pairs - items = data.get('result') or [] - cmds = data.get('_internalDetailCommands') or [] - for idx, item in enumerate(items): - rid = '' - if idx < len(cmds): - rid = report_id_from_command( - cmds[idx].get('command', '') - ) - pairs.append((item, rid)) - if data.get('hasMore') and data.get('nextCursor') is not None: - cursor = data['nextCursor'] - else: - break - return pairs - - -def print_detail(rid: str) -> None: - detail = run_dws([ - 'report', 'entry', 'get', - '--report-id', rid, '--format', 'json', - ]) - if not isinstance(detail, dict): - return - result = detail.get('result') - if not isinstance(result, dict): - return - for c in (result.get('report_content') or [])[:3]: - key = c.get('key', '') - val = c.get('value', '') - if key and val: - print(f" {key}: {str(val).strip()[:60]}") +def to_iso(dt: datetime) -> str: + return dt.strftime('%Y-%m-%dT%H:%M:%S+08:00') def main(): - parser = argparse.ArgumentParser(description='查看收到的日志') - parser.add_argument( - '--days', type=int, default=1, help='查询天数 (默认 1,即今天)' + parser = argparse.ArgumentParser( + description='查看收到的日志' ) parser.add_argument( - '--detail', action='store_true', help='额外拉取每条正文' + '--days', type=int, default=1, help='查询天数 (默认 1)' ) parser.add_argument('--dry-run', action='store_true') args = parser.parse_args() + if args.days < 1: + parser.error('--days must be >= 1') now = datetime.now() - start_dt = now - timedelta(days=max(args.days - 1, 0)) - start = iso_start(start_dt) - end = iso_end(now) + start = now - timedelta(days=args.days - 1) + start = start.replace(hour=0, minute=0, second=0, microsecond=0) + end = now.replace(hour=23, minute=59, second=59, microsecond=0) label = '今天' if args.days == 1 else f'最近 {args.days} 天' - print(f'查看{label}收到的日志...\n') + print(f'📓 查看{label}收到的日志...\n') + + data = run_dws([ + 'report', 'inbox', 'list', + '--start', to_iso(start), + '--end', to_iso(end), + '--cursor', '0', + '--size', '20', + '--format', 'json', + ], dry_run=args.dry_run) - pairs = fetch_inbox(start, end, args.dry_run) if args.dry_run: return - if not pairs: - print(' 暂无收到的日志') + if not data: + print('未查到日志') return - print(f"{label}日志 ({len(pairs)} 条)") + if isinstance(data, list): + reports = data + elif isinstance(data, dict): + inner = data.get('result', data) + if isinstance(inner, dict): + reports = inner.get('report_list', + inner.get('reports', [])) + elif isinstance(inner, list): + reports = inner + else: + reports = [] + else: + reports = [] + if not reports: + print(' ✅ 暂无收到的日志') + return + + print(f"📓 {label}日志 ({len(reports)} 条)") print('=' * 50) - for item, rid in pairs: - title = item.get('标题') or '日志' - sender = item.get('发送人') or '未知' - date = item.get('日期') or '' - status = item.get('状态') or '' - link = item.get('钉钉链接') or '' + for r in reports: + if not isinstance(r, dict): + print(f"\n 📝 {r}") + continue + rid = r.get('reportId') or r.get('id', '') + creator = r.get('creatorName') or r.get('creator', '未知') + template = r.get('templateName') or r.get('template', '') + create_time = r.get('createTime', '') + if isinstance(create_time, (int, float)): + create_time = datetime.fromtimestamp( + create_time / 1000 + ).strftime('%Y-%m-%d %H:%M') - print(f"\n {title} - {sender}") - print(f" 时间: {date}") - if status: - print(f" 状态: {status}") - if link: - print(f" 链接: {link}") + print(f"\n 📝 {template or '日志'} - {creator}") + print(f" 时间: {create_time}") + print(f" ID: {rid}") - if args.detail and rid: - print_detail(rid) + if rid: + detail = run_dws([ + 'report', 'entry', 'get', + '--report-id', rid, '--format', 'json', + ]) + if detail and isinstance(detail, dict): + result = detail.get('result', detail) + if isinstance(result, dict): + contents = ( + result.get('contents') + or result.get('report_content') + or result.get('reportContent') + or result.get('reportContents') + or [] + ) + else: + contents = [] + for c in contents[:3]: + if not isinstance(c, dict): + continue + key = c.get('key') or c.get('title', '') + val = ( + c.get('value') + or c.get('content') + or c.get('text') + or '' + ) + if key and val: + print(f" {key}: {str(val)[:60]}") if __name__ == '__main__': diff --git a/skills/multi/dingtalk-sheet/SKILL.md b/skills/multi/dingtalk-sheet/SKILL.md index c7fbed9e..70eb1a43 100644 --- a/skills/multi/dingtalk-sheet/SKILL.md +++ b/skills/multi/dingtalk-sheet/SKILL.md @@ -1,6 +1,6 @@ --- name: dingtalk-sheet -description: 钉钉电子表格。Use when 用户说 电子表格/工作表/单元格读写/单元格追加/查找/公式/超链接/插入图片/浮动图片/sheet。Distinct from dingtalk-aitable(AI表格/多维表/字段类型)、dingtalk-doc(普通文档)。命令前缀:dws sheet。 +description: 钉钉电子表格。Use when 用户说 电子表格/工作表/单元格读写/单元格追加/查找/公式/超链接/插入图片/浮动图片/sheet。不做 AI表格/多维表/字段类型(走 dingtalk-aitable)、普通文档编辑(走 dingtalk-doc)。命令前缀:dws sheet。 cli_version: ">=0.2.14" metadata: category: product @@ -14,18 +14,14 @@ metadata: > 🧪 **EXPERIMENTAL · 试验版 / Preview** — multi 模式当前未达 stable 标准。全部 dingtalk-* skill 已通过 dispatch verifier,但接口、命名、跨 skill 引用后续可能调整;生产 / 共享环境请优先使用 mono 模式(`dws skill setup --mode mono`)。问题请提 issue 反馈。 -> **PREREQUISITE:** Read the `dws-shared` skill first for auth, global flags, product routing, URL preflight, error codes, and safety rules. The `dws` binary must be on PATH. +## 前置条件 — 执行操作前必读 + +> **`use_skill(dws-shared)`** — 认证、全局参数(`--format json` / `--yes`)、错误码、URL 模板、跨产品消歧、安全规则与 capability 边界。**执行任何 `dws` 命令前先读;** 单产品的清晰命令可直接用本 skill。 -> ⚠️ **命令可用性以当前 dws 二进制为准**。服务发现已下线,本文档随内置 skill 发布;如果 `dws --help` 不存在,说明当前版本未暴露该命令。若命令存在但调用失败,请按错误中的 endpoint 或 tool 提示确认静态端点目录和后端工具注册。实际调用前可用 `dws --help` 或 `--dry-run` 验证。 - - -> 命令参考:[sheet.md](references/sheet.md);URL 识别与类型探测:[url-patterns.md](references/url-patterns.md)。 - -## URL 预检(含 alidocs URL 时必读) - -输入含 `alidocs.dingtalk.com` URL 时,该域名下存在多种路径格式:`/i/p/...`(分享短链)、`/i/nodes/...`(节点链接,类型需探测)、`/spreadsheetv2/...`(电子表格直链,直接路由到 `sheet`)、`/document/edit|preview?dentryKey=...`(文档链接,路由到 `dingtalk-doc`)等。**必须先读 [url-patterns.md](references/url-patterns.md) 中的「alidocs URL 分流决策」**,按规则识别 URL 类型;仅当确认是在线电子表格(`/spreadsheetv2/...` 或 `i/nodes/` 且 probe 出 `contentType=ALIDOC` + `extension=axls`)时,才继续走本 skill 的命令。 +> 命令参考:[sheet.md](references/sheet.md)。 +> 高频模块:[读取](references/sheet/sheet-read-index.md) · [写入](references/sheet/sheet-write-index.md) · [格式](references/sheet/sheet-format-index.md) · [查询](references/sheet/sheet-query-index.md) · [可视化](references/sheet/sheet-visual-index.md) · [导入导出](references/sheet/sheet-export-index.md)。 ## 意图表 @@ -40,9 +36,14 @@ metadata: | "创建透视表 / 数据透视" | `dws sheet pivot-table create --node --source "'Sheet1'!A1:D100" --properties ''` | | "查看透视表" | `dws sheet pivot-table list --node --sheet-id ` | | "显示 / 隐藏网格线" | `dws sheet show-gridline|hide-gridline --node --sheet-id ` | +| "写入超链接" | `dws sheet range update --node --sheet-id --range A1 --values '[[{"type":"text","text":"显示文本","hyperlink":{"type":"path","link":"https://..."}}]]'` | | "追加一行" | `dws sheet append --node --sheet-id --values '[[..]]'` | -| "查找 / 替换" | `dws sheet find --node --sheet-id --find "<关键词>"` / `dws sheet replace --node --sheet-id --find "<旧值>" --replacement "<新值>"` | -| "插入图片到单元格" | `dws sheet write-image --node --sheet-id --range A1:A1 --file <本地图片路径>`(CLI 自动上传本地图片并写入单元格;没有 --resource-id/--resource-url 这两个 flag) | +| "查找 / 替换" | `dws sheet find --node --sheet-id --query "<关键词>"` / `dws sheet replace --node --sheet-id --find "<旧值>" --replacement "<新值>"` | +| "精确匹配搜索 / 完全等于" | `dws sheet find --node --sheet-id --query "<关键词>" --match-entire-cell` | +| "搜索公式文本" | `dws sheet find --node --sheet-id --query "<公式片段>" --match-formula` | +| "正则搜索 / 不区分大小写" | `dws sheet find --node --sheet-id --query "" --use-regexp --match-case=false` | +| "插入图片到单元格" | `dws sheet write-image --node --sheet-id --range A1 --file <图片路径>` | +| "创建浮动图片" | 先 `dws sheet media-upload --node --file <图片路径>` 获取 `resourceUrl`,再 `dws sheet create-float-image --node --sheet-id --src "" --range A1 --width <宽> --height <高>` | ## URL 与 ID 前置 @@ -51,14 +52,19 @@ metadata: - 写入类命令必须先用 `dws sheet list --node --format json` 取得真实 `sheetId`;不要猜 `Sheet1`、`sheet1`、`0`。 - 所有 sheet 子命令使用 `--node` 参数;不要写 `--node-id`、`--file-id` 或把 JSON 字段名 `id` 当成节点值传入。 -## 评测高频硬约束 +## 高频硬约束 - `sheet create` 后必须从返回结果提取真实 `nodeId` 或文档 URL,后续 `range update/read` 原样传给 `--node`;如果返回里同时有 `nodeId` 和 `url`,优先用 `nodeId`。 -- 写入区域前先 `sheet list --node --format json` 获取真实 `sheetId`;写入后立即 `range read` 同一区域验证。 +- 写入区域前先 `sheet list --node --format json` 获取真实 `sheetId`。`range update` 返回的 `success` / `updatedRows` / `updatedCells` / `message` 可作为写入确认;仅在任务需要内容校验且用户未禁止额外读取时调用 `range read`。 - `range update` 的 `--values` 必须是二维 JSON,行列数量与 `--range` 完全一致;批量样式用 `range batch-set-style --batch `。 -- 同一参数错误最多纠正 2 次;若持续 `nodeId 格式不合法`,回到 `doc info --node ` 或 `sheet create` 返回重新取 ID,不要无意义重试。 +- 整格超链接只能用 `range update` 的 cell-level `hyperlink` 字段:`{"type":"text","text":"显示文本","hyperlink":{"type":"path","link":"https://..."}}`。不要走 `doc`,不要用 `--hyperlinks`,不要把整格链接写成 richText 片段链接。 +- 搜索必须用 `sheet find`,不要用 `range read` 后本地过滤;“精确/完全匹配/等于”必须加 `--match-entire-cell`,“搜公式”必须加 `--match-formula`,“正则且不区分大小写”必须同时加 `--use-regexp --match-case=false`。 +- 出现 `nodeId 格式不合法` 时,回到 `doc info --node ` 或 `sheet create` 的返回重新提取真实 ID,禁止复用无效值重试。 ## 跨产品协作 - 多维表 / 字段类型 → 切到 `dingtalk-aitable` - 把表数据写进文档 → 切到 `dingtalk-doc` +## 局部意图与 Recipe + +- [局部意图消歧](references/intent-guide.md)。 diff --git a/skills/multi/dingtalk-sheet/references/_common/conventions.md b/skills/multi/dingtalk-sheet/references/_common/conventions.md new file mode 100644 index 00000000..75491aab --- /dev/null +++ b/skills/multi/dingtalk-sheet/references/_common/conventions.md @@ -0,0 +1,44 @@ +# 业务域通用规范 + +> 仅服务本 skill 已迁入的行动指南。安全门控、危险操作确认、`--format json` 等已在本 skill 的 `SKILL.md` 中定义,此处不重复。 + +## 批量查询规范 + +| # | 规范 | +|---|------| +| 1 | **并行查详情**:拿到多个 ID 后,用 `&` 合并到同一条 Shell 命令并行执行 + `wait`,**严禁逐条串行** | +| 2 | **翻页**:分页接口须拉全直至无更多 | +| 3 | **优先批量 API**:有批量接口则用批量;无则按 #1 并行 | +| 4 | **群消息**:必须先 `chat search --query` 得 `openConversationId`,再 `chat message list --group`;多群同条命令并行 | +| 5 | **列表少轮次**:带条件搜索/列表 → 一次采全详情;**禁止**无新参数时重复同一 `list` / `search` | + +## 多源并行采集(公共模式) + +> recipe 引用方式:`按「多源并行采集」执行(关键词=,时间=至)`。 + +- 同条 Shell:`&` 并行 + `wait`;分页须采全。 +- 只保留与主题相关的数据,无关丢弃。 +- 有批量详情接口优先;否则并行拉详情(见上表 #1)。 +- 具体采哪些产品列表由对应 **行动指南 recipe** 与当前产品参考决定;不要引入本文档未覆盖的产品路线。 + +## 字段术语与 ID 传递 + +> list 返回 JSON 后,必须提取下表字段传给后续命令。**禁止用其他字段替代。** + +| 字段 | 来源 | 传递给 | +|------|------|--------| +| `taskUuid` | `minutes list` | `minutes get summary/info/batch --id(s)` | +| `userId` | `aisearch person` / `contact user search` / `contact dept list-members` | `contact user get --ids`、`todo --executors`、`calendar --users` | +| `deptId` | `contact dept search` | `contact dept list-members --depts`;多子部门时对每个子部门分别 `dept search` 取 id | +| `nodeId` | `drive search` / `wiki node search` | `doc read/update --node`、`drive copy/move/rename/delete --node` | +| `nodeId` | `wiki node list` 中的 folder 类型节点 / `wiki node create --type folder` | `wiki node list --folder`、`wiki node create --folder`、`drive upload --folder`、`drive copy/move --folder` | +| `eventId` | `calendar event list` | `calendar event get/update --id` | +| `processInstanceId` | `oa approval list-*` | `oa approval detail/approve --instance-id` | +| `openConversationId` | `chat search` | `chat message list/send --group` | +| `todoTaskId` | `todo task list` | `todo task update/done --task-id` | +| `reportId` | `report inbox list` / `report outbox list` | `report entry get/stats --report-id` | +| `baseId` / `tableId` | `aitable base search` | `aitable record query --base-id --table-id` | +| `dentryUuid` | `drive list` / `drive mkdir` | `drive info/download --node`、`drive list/mkdir/upload --folder` | +| `dentryId` | `drive info` 的数字字段 | 仅用于 `chat message send --dentry-id` | + +**ID 边界硬约束**:`dentryId` 通常是纯数字,只表示聊天文件消息需要的钉盘条目数字 ID;它不是父目录 ID。遇到 `drive --node/--folder`、`doc --node/--folder`、`wiki node --folder` 时,只能使用 `dentryUuid` / `nodeId` / 文档 URL。若当前上下文只有数字型 `dentryId`,必须先重新 `drive list` / `drive search` / `wiki node list` 获取正确 ID,不能把该数字直接代入后续命令。 diff --git a/skills/multi/dingtalk-sheet/references/intent-guide.md b/skills/multi/dingtalk-sheet/references/intent-guide.md new file mode 100644 index 00000000..78ee32b2 --- /dev/null +++ b/skills/multi/dingtalk-sheet/references/intent-guide.md @@ -0,0 +1,12 @@ +# sheet 局部意图消歧 + +本文件从单 Skill `intent-guide.md` 拆分而来,仅保留与本产品相关的跨产品消歧规则。 + +| 用户说... | 真实意图 | 应该用 | 不要用 | 理由 | +|---|---|---|---|---| +| "帮我建一个项目跟踪表" | 创建数据表格 | `aitable` | `doc` / `sheet` | 涉及结构化数据/行列操作,不是富文本文档或电子表格 | +| "创建一个电子表格" | 创建表格文档 | `sheet` | `aitable` | Excel 式表格/单元格操作,不是多维表记录 | +| "帮我读一下表格 A1:D10 的数据" | 读取单元格数据 | `sheet` | `aitable` | 按单元格区域读写,不是按记录查询 | +| "这个 alidocs 表格链接帮我看下"(粘贴原始 URL) | 先 probe 节点类型 | `dws doc info --node` → 按 `extension` 路由 | 直接调 `sheet` | `alidocs/i/nodes/{id}` 可能是文档/axls/able/xlsx 等,禁止凭 URL 猜类型 | +| "读一下这个 xlsx 的数据" / xlsx 节点链接 | 下载本地表格文件 | `dws drive download --node` | `sheet range read` | xlsx / xls / xlsm / csv 是上传的本地文件(`contentType=DOCUMENT`),sheet 命令只支持在线表格,必须下载后本地解析 | +| "把这个在线表格导出为 xlsx 文件" | 在线表格格式转换 | `dws sheet export` | `dws drive download` | `export` 是 axls → xlsx 的导出转换;`download` 只能下载已有的 xlsx 节点 | diff --git a/skills/multi/dingtalk-sheet/references/sheet.md b/skills/multi/dingtalk-sheet/references/sheet.md index a7310028..327096c4 100644 --- a/skills/multi/dingtalk-sheet/references/sheet.md +++ b/skills/multi/dingtalk-sheet/references/sheet.md @@ -1,2145 +1,17 @@ # 电子表格 (sheet) 命令参考 -## 适用范围(重要) +> 渐进式入口:先根据任务选择对应索引或专题,不要一次性加载全部表格参考。 -`sheet` 产品线仅支持钉钉在线电子表格(`contentType=ALIDOC`、`extension=axls`),不支持上传的 `xlsx` / `xls` / `xlsm` / `csv` 等本地表格文件。 +## 按需加载 -| 文件类型 | 处理方式 | -|---------|---------| -| 在线电子表格(`axls`) | 走 `sheet` 全部命令(读/写/筛选/合并/导出等服务端原子操作) | -| `xlsx` / `xls` / `xlsm` / `csv` 等本地表格文件 | 必须用 `dws doc download --node --output <路径>` 先下载到本地再解析处理,禁止调用任何 `sheet` 子命令(sheet 底层 MCP 工具仅认 `axls`,传入 xlsx 节点会直接报错) | -| 想把在线表格导出为 xlsx | 用 `dws sheet export` ——输入是 `axls`,输出是 xlsx(axls → xlsx 的格式转换) | +| 任务 | 参考 | +|---|---| +| 适用范围与 URL/ID | [适用范围](./sheet/sheet-scope.md) · [URL 转 nodeId](./sheet/sheet-url-to-node-id.md) · [nodeId 说明](./sheet/sheet-nodeid.md) | +| 场景与完整命令路由 | [场景速查](./sheet/sheet-quick-ref.md) · [Reference 索引](./sheet/sheet-ref-index.md) | +| 读取、写入、格式、查询、可视化、导出 | [读](./sheet/sheet-read-index.md) · [写](./sheet/sheet-write-index.md) · [格式](./sheet/sheet-format-index.md) · [查询](./sheet/sheet-query-index.md) · [可视化](./sheet/sheet-visual-index.md) · [导出](./sheet/sheet-export-index.md) | +| 编辑约束与能力边界 | [编辑总则](./sheet/sheet-edit-rules.md) · [高频硬约束](./sheet/sheet-hard-constraints.md) · [能力边界](./sheet/sheet-capabilities.md) | +| 导入与模板 | [导入](./sheet/sheet-import.md) · [模板](./sheet/sheet-templates.md) | -> 用户直接粘贴原始 `alidocs` URL 时必须先 probe:先执行 `dws doc info --node --format json`,按 [链接规范](../url-patterns.md#alidocs-url-类型探测流程) 校验 `contentType` 和 `extension`: -> - 仅当 `contentType=ALIDOC` 且 `extension=axls` 时,才继续走 `sheet` -> - 如果是 `xlsx` / `xls` / `xlsm` / `csv`,立即转向 `dws doc download`,并告知用户"这是本地表格文件,已为你下载到本地处理" +## 创建钉钉表格文档 -## URL 识别与 NODE_ID 提取 - -当用户输入包含钉钉文档 URL 时,必须先识别并提取 NODE_ID,再判断意图。 - -硬性规则:对用户直接给出的原始 `alidocs` URL,不允许直接走 `sheet` 命令,必须先执行: - -```bash -dws doc info --node "" --format json -``` - -根据返回路由: -- `contentType=ALIDOC` + `extension=axls` → 继续走 `sheet` -- `contentType≠ALIDOC` + `extension=xlsx` / `xls` / `xlsm` / `csv` → 转向 `dws doc download --node --output <路径>`,禁止调用任何 sheet 子命令 -- 其他类型 → 按 [链接规范](../url-patterns.md#alidocs-url-类型探测流程) 路由 - -补充:如果这是用户直接提供的原始 `alidocs` URL,先按 [链接规范](../url-patterns.md#alidocs-url-类型探测流程) probe 一次确认真实类型,再判断是否继续走 `sheet`。 - -### 支持的 URL 格式 - -| 格式 | 示例 | NODE_ID 提取方式 | -|------|------|----------------| -| `alidocs.dingtalk.com/i/nodes/{id}` | `https://alidocs.dingtalk.com/i/nodes/9E05BDRVQePjzLkZt2p2vE7kV63zgkYA` | 取 URL 路径最后一段 | -| `alidocs.dingtalk.com/i/nodes/{id}?queryParams` | `https://alidocs.dingtalk.com/i/nodes/abc123?doc_type=wiki_doc` | 忽略 query 参数,取路径最后一段 | -| `alidocs.dingtalk.com/spreadsheetv2/{key}/...` | `https://alidocs.dingtalk.com/spreadsheetv2/vKJngl50tJN1v3a3/...?dentryKey=vKJngl50tJN1v3a3&type=s` | **不要提取 path segment**,将完整 URL 直接传给 `--node` 参数,由 MCP 服务端解析 | - -### 提取规则 - -1. 匹配 URL 中 `alidocs.dingtalk.com` 域名 -2. **判断 URL 路径格式**: - - 路径包含 `/i/nodes/` → 取 URL path 的最后一段作为 NODE_ID(去掉 query string 和 fragment) - - 路径包含 `/spreadsheetv2/` → **不要提取 path segment**,必须将完整 URL 原样传给 `--node` 参数(因为 path 中的短 ID 不是合法的 nodeId,MCP 服务端会自行解析完整 URL) -3. 对于 `/i/nodes/` 格式,提取出的 NODE_ID 可直接用于所有 `--node` 参数,也可将完整 URL 传给 `--node`(CLI 会自动解析) -4. 对用户直接提供的原始 `alidocs` URL,先按 [链接规范](../url-patterns.md#alidocs-url-类型探测流程) probe;只有 probe 确认 `contentType=ALIDOC` 且 `extension=axls` 时,才继续留在 `sheet`;如果 `extension=xlsx` / `xls` / `xlsm` / `csv`,必须转向 `dws doc download`,不能走任何 sheet 命令 - -## 查询命令帮助 - -当你不确定某个命令的具体参数、格式或可选项时,**优先执行 `--help` 查询**,不要猜测参数名或凭记忆编造。 - -```bash -# 查看 sheet 下所有子命令 -dws sheet --help - -# 查看具体命令的完整参数说明 -dws sheet range update --help -dws sheet filter-view create --help -dws sheet insert-dimension --help - -# 查看子命令组下的所有命令 -dws sheet range --help -dws sheet filter-view --help -``` - -规则: -- 参数名不确定时 → 先 `--help`,再调用 -- 报错 "unknown flag" 时 → `--help` 确认正确的 flag 名称 -- 不确定某个功能是否存在时 → `dws sheet --help` 查看命令列表 - -## 命令速查表 - -| 命令 | 用途 | -|------|------| -| `sheet create` | 创建钉钉表格文档 | -| `sheet list` | 获取全部工作表列表 | -| `sheet info` | 获取指定工作表详情 | -| `sheet new` | 新建工作表 | -| `sheet update` | 更新工作表属性(标题/位置/隐藏/冻结) | -| `sheet copy` | 复制工作表 | -| `sheet show-gridline` | 显示工作表网格线 | -| `sheet hide-gridline` | 隐藏工作表网格线 | -| `sheet range read` | 读取工作表数据(别名: range get) | -| `sheet range update` | 更新指定区域内容(值/公式/超链接) | -| `sheet range clear` | 清除区域(值/格式/全部) | -| `sheet range sort` | 对区域排序 | -| `sheet range fill` | 自动填充区域 | -| `sheet range copy-to` | 复制区域到目标位置 | -| `sheet range move-to` | 移动区域到目标位置 | -| `sheet range batch-clear` | 批量清除多个区域(原子事务) | -| `sheet batch-update` | 批量执行多个写操作(原子事务) | -| `sheet csv-get` | 以 CSV 格式读取区域数据 | -| `sheet table-get` | 读取结构化 table 数据(别名: table-read) | -| `sheet range set-style` | 设置单元格样式 | -| `sheet range batch-set-style` | 按配置文件批量设置样式 | -| `sheet find` | 搜索单元格内容 | -| `sheet append` | 在末尾追加数据行 | -| `sheet csv-put` | 将 CSV 数据写入指定位置(纯值,自动扩容) | -| `sheet table-put` | 写入一个或多个结构化 table(别名: table-write) | -| `sheet pivot-table list` | 列出透视表或获取指定透视表详情 | -| `sheet pivot-table create` | 创建原生透视表 | -| `sheet pivot-table update` | 更新透视表配置 | -| `sheet pivot-table delete` | 删除透视表(不可逆,删除前必须确认) | -| `sheet delete-sheet` | 删除工作表(不可逆,删除前必须确认) | -| `sheet replace` | 全局查找替换文本 | -| `sheet merge-cells` | 合并单元格 | -| `sheet unmerge-cells` | 取消合并单元格 | -| `sheet set-dropdown` | 设置下拉列表 | -| `sheet get-dropdown` | 获取下拉列表配置 | -| `sheet delete-dropdown` | 删除下拉列表 | -| `sheet insert-dimension` | 在指定位置插入行或列 | -| `sheet delete-dimension` | 删除指定位置的行或列 | -| `sheet update-dimension` | 更新行/列属性(显隐/行高/列宽) | -| `sheet move-dimension` | 移动行或列到指定位置 | -| `sheet add-dimension` | 在末尾追加空行或空列 | -| `sheet group-dimension` | 创建行/列分组 | -| `sheet ungroup-dimension` | 取消行/列分组 | -| `sheet media-upload` | 上传附件到表格 | -| `sheet write-image` | 上传图片并写入单元格 | -| `sheet create-float-image` | 创建浮动图片 | -| `sheet get-float-image` | 获取浮动图片详情 | -| `sheet list-float-images` | 列出工作表所有浮动图片 | -| `sheet update-float-image` | 更新浮动图片属性 | -| `sheet delete-float-image` | 删除浮动图片 | -| `sheet export` | 导出表格为 xlsx | -| `sheet filter get` | 获取全局筛选信息 | -| `sheet filter create` | 创建全局筛选 | -| `sheet filter delete` | 删除全局筛选 | -| `sheet filter update` | 批量更新筛选条件 | -| `sheet filter clear-criteria` | 清除单列筛选条件 | -| `sheet filter sort` | 筛选排序 | -| `sheet filter-view list` | 获取所有筛选视图 | -| `sheet filter-view create` | 创建筛选视图 | -| `sheet filter-view update` | 更新筛选视图属性 | -| `sheet filter-view delete` | 删除筛选视图 | -| `sheet filter-view update-criteria` | 更新筛选视图列条件 | -| `sheet filter-view delete-criteria` | 删除筛选视图列条件 | -| `sheet filter-view info` | 获取单个筛选视图详情 | -| `sheet filter-view list-criteria` | 列出筛选视图所有列条件 | -| `sheet filter-view get-criteria` | 获取单列筛选条件详情 | -| `sheet cond-format list` | 获取条件格式规则 | -| `sheet cond-format create` | 创建条件格式规则 | -| `sheet cond-format update` | 更新条件格式规则 | -| `sheet cond-format delete` | 删除条件格式规则 | -| `sheet chart list` | 获取浮动图表 | -| `sheet chart create` | 创建浮动图表 | -| `sheet chart update` | 更新浮动图表 | -| `sheet chart delete` | 删除浮动图表 | -| `sheet template list` | 获取表格模板列表 | -| `sheet template search` | 搜索表格模板 | -| `sheet template apply` | 应用模板创建新表格文档 | - -> 不确定参数?对任意命令执行 `dws sheet <命令> --help` 查看完整用法。 - -## 子文档索引(更多命令详细参考) - -本文档覆盖高频命令。以下命令的完整参数、示例与工作流在 `sheet/` 子目录,需要时按主题查阅: - -| 主题 | 子文档 | 覆盖命令 | -|------|--------|---------| -| 表格与工作表管理 | [sheet/sheet-workbook.md](./sheet/sheet-workbook.md) | create / list / info / new / update / copy / show-gridline / hide-gridline / delete-sheet | -| 透视表 | [sheet/sheet-pivot-table.md](./sheet/sheet-pivot-table.md) | pivot-table list / create / update / delete | -| 写入数据 | [sheet/sheet-write-data.md](./sheet/sheet-write-data.md) | table-put / range update(对象协议详解)/ append / csv-put | -| 读取数据 | [sheet/sheet-read-data.md](./sheet/sheet-read-data.md) | table-get / range read / csv-get | -| 区域操作 | [sheet/sheet-range-operations.md](./sheet/sheet-range-operations.md) | range clear / sort / fill / copy-to / move-to | -| 批量操作 | [sheet/sheet-batch-operations.md](./sheet/sheet-batch-operations.md) | range batch-clear / batch-update | -| 行列操作 | [sheet/sheet-dimension-operations.md](./sheet/sheet-dimension-operations.md) | insert / delete / update / move / add-dimension / group-dimension / ungroup-dimension | -| 样式与合并 | [sheet/sheet-style-format.md](./sheet/sheet-style-format.md) | range set-style / batch-set-style / merge-cells / unmerge-cells | -| 条件格式 | [sheet/sheet-conditional-format.md](./sheet/sheet-conditional-format.md) | cond-format list / create / update / delete | -| 浮动图表 | [sheet/sheet-chart.md](./sheet/sheet-chart.md) | chart list / create / update / delete | -| 下拉列表 | [sheet/sheet-dropdown.md](./sheet/sheet-dropdown.md) | set / get / delete-dropdown | -| 媒体与图片 | [sheet/sheet-media-image.md](./sheet/sheet-media-image.md) | media-upload / write-image / 浮动图片 | -| 查找替换 | [sheet/sheet-search-replace.md](./sheet/sheet-search-replace.md) | find / replace | -| 全局筛选 | [sheet/sheet-filter.md](./sheet/sheet-filter.md) | filter get / create / delete / update / clear-criteria / sort | -| 筛选视图 | [sheet/sheet-filter-view.md](./sheet/sheet-filter-view.md) | filter-view 全系列 | -| 导出 | [sheet/sheet-export.md](./sheet/sheet-export.md) | export | - -> `template list/search/apply` 无独立子文档,直接执行 `dws sheet template <子命令> --help` 查看用法。 - -## 意图判断 - -### 表格与工作表管理 - -用户说"创建表格/新建电子表格": -- 创建表格文档 → `create` - -用户说"看工作表/有哪些工作表/表格结构": -- 列出工作表 → `list` -- 工作表详情 → `info` - -用户说"加工作表/新增Sheet": -- 新建工作表 → `new` - -用户说"修改工作表名称/重命名工作表/移动工作表位置/隐藏工作表/显示工作表/冻结行/冻结列/取消冻结/更新工作表属性": -- 更新工作表属性 → `update` -- 重命名工作表 → `update --title "新名称"` -- 移动工作表位置 → `update --index N` -- 隐藏工作表 → `update --hidden` -- 显示工作表 → `update --hidden=false` -- 冻结行列 → `update --frozen-row-count N --frozen-column-count M` -- 取消冻结 → `update --frozen-row-count 0 --frozen-column-count 0` - -用户说"复制工作表/拷贝工作表/克隆工作表/工作表副本": -- 复制工作表 → `copy` -- 复制并指定名称 → `copy --title "副本名称"` -- 复制并指定位置 → `copy --index N` - -### 数据读写 - -用户说"读数据/看表格内容": -- 读取数据 → `range read` - -用户说"写数据/填表/更新单元格/写入公式": -- 更新数据 → `range update` -- 【强制】`--sheet-id` 必填:即使是单工作表也不能省略,不要参照 `range read` 的默认行为;未知时先执行 `dws sheet list --node --format json` 获取 `sheetId`,禁止凭空臆测为 `Sheet1`、`sheet1`、`0`、`default` 等 -- 注意:如果用户的目的是替换文本、移动行列或追加空行空列,请勿使用 `range update`,必须使用对应的专用命令(`replace`/`move-dimension`/`add-dimension`) -- **批量纯值写入优先用 `csv-put`**:当写入场景同时满足以下条件时,必须优先使用 `csv-put` 而非 `range update`:(1) 写入的是纯值(不含公式、超链接);(2) 数据量较大(超过 5 行或超过 20 个单元格);(3) 数据来源为表格/CSV 文本/结构化文本。`csv-put` 无需手动构造二维 JSON 数组,直接传 CSV 文本即可,更简洁高效且支持自动扩容 - -用户说"追加数据/添加行/在末尾加数据/新增记录": -- 追加数据 → `append` - -用户说"批量写入CSV/导入CSV/CSV写入表格/把CSV贴到表格里": -- 写入 CSV → `csv-put` -- 与 `range update` 的区别:`csv-put` 接受 CSV 文本直接写入,无需手动构造二维 JSON 数组;适合大批量纯值写入 -- 与 `append` 的区别:`csv-put` 写入指定位置(--start-cell),`append` 在末尾追加 - -用户说"搜索/查找/找单元格/搜内容/精确搜索/精确匹配/完全匹配/全字匹配": -- 搜索单元格 → `find` -- 精确匹配(只匹配完全等于的,不匹配包含的) → `find --match-entire-cell` -- 正则搜索 → `find --use-regexp` -- 搜索公式 → `find --match-formula` -- 不要用 `range read` 读取全量数据后在客户端过滤来替代 `find`,必须使用 `find` 命令的服务端搜索能力 - -用户说"替换/查找替换/全局替换/批量替换/把A替换成B/把所有的X改成Y": -- 查找替换 → `replace` -- 精确匹配后替换(只替换内容完全等于的单元格) → `replace --match-entire-cell` -- 正则替换 → `replace --use-regexp` -- 删除匹配内容 → `replace --replacement ""` -- 请勿用 `find` + `range update`、`range read` + `range update` 等组合来模拟替换,`replace` 是服务端原子操作,效率更高且返回替换计数 - -### 行列操作 - -用户说"插入行/插入列/在某行前插入/在某列前插入": -- 插入行或列 → `insert-dimension` -- 在末尾追加 → `append`(insert-dimension 不支持末尾追加) - -用户说"删除行/删除列/删掉第几行/删掉某列/移除行/移除列": -- 删除行或列 → `delete-dimension` -- 仅清空内容但保留行/列 → `range clear`(整片区域清除,比逐格写空更简洁) - -用户说"隐藏行/隐藏列/显示行/显示列/设置行高/设置列宽/调整行高/调整列宽/行列属性": -- 隐藏/显示行或列 → `update-dimension --hidden` / `--hidden=false` -- 设置行高/列宽 → `update-dimension --pixel-size` -- 同时修改尺寸与显隐 → `update-dimension --pixel-size --hidden` - -用户说"移动行/移动列/调整行顺序/调整列顺序/行列拖拽/把第N行移到第M行": -- 移动行或列 → `move-dimension` -- 请勿用 `range read` + `range update` 读取再重写来模拟移动,`move-dimension` 是原子操作,能保留格式和合并状态 - -用户说"追加空行/追加空列/增加行数/增加列数/扩展表格/在末尾加空行": -- 追加空行/空列 → `add-dimension` -- 注意与 `append`(追加数据行)区分:`add-dimension` 追加的是空行/空列,`append` 追加的是带数据的行 -- 请勿用 `range update` 写空数据来模拟追加,`add-dimension` 直接扩展表格维度 - -用户说"创建行分组/创建列分组/折叠几行/折叠几列/取消行列分组": -- 创建分组 → `group-dimension --range <整行或整列范围>` -- 取消分组 → `ungroup-dimension --range <整行或整列范围>` -- `--range` 只能传整行或整列范围,如 `3:7`、`C:F`、`Sheet1!3:7`,不要传 `A1:C5` - -### 单元格格式 - -用户说"设置样式/改颜色/设背景色/加粗/居中/换行/字体颜色/字号": -- 设置单元格样式 → `range set-style` -- 批量设置不同 range 的样式 → `range batch-set-style --batch ./styles.json`(内部顺序循环调 `update_range`) -- 请勿用 `range update --values` 写空/重写来模拟样式变更;也请勿把样式变更混在 `range update` 里、再故意清空 `--values` - -用户说"设置数字格式/改成百分比/用人民币显示/按日期显示/文本格式/保留几位小数": -- 设置数字格式 → `range set-style --number-format <格式代码>`(如 `0%` / `¥#,##0.00` / `yyyy/m/d` / `@`) -- 请勿用 `range update` 传递数字格式,`range update` 仅负责写入值与超链接;数字格式属于单元格样式,统一走 `range set-style` - -用户说"合并单元格/合并/合并区域/按行合并/按列合并": -- 合并所有单元格 → `merge-cells`(默认 mergeAll) -- 按行合并 → `merge-cells --merge-type mergeRows` -- 按列合并 → `merge-cells --merge-type mergeColumns` - -用户说"取消合并/拆分单元格/还原合并": -- 取消合并单元格 → `unmerge-cells` - -### 下拉列表 - -用户说"设置下拉列表/下拉选项/下拉菜单/添加下拉/配置下拉": -- 设置下拉列表 → `set-dropdown` -- 设置多选下拉 → `set-dropdown --multi-select` - -用户说"查看下拉列表/获取下拉配置/下拉列表有哪些选项": -- 获取下拉列表配置 → `get-dropdown` - -用户说"删除下拉列表/移除下拉/取消下拉/清除下拉": -- 删除下拉列表 → `delete-dropdown` - -### 媒体上传 - -用户说"上传附件/传文件到表格/上传文件到表格/上传到表格": -- 上传附件 → `media-upload`(需表格 ID 或 URL + 本地文件路径) -- 用户指定了上传后的名称 → `media-upload --name "自定义名称"` -- `media-upload` 的 `--name` 参数用于指定附件在表格中显示的名称(不改变本地文件名);不传时默认使用本地文件名 - -用户说"写入图片/插入图片/加图片/放图片到单元格/嵌入图片到表格": -- 写入图片 → `write-image`(需表格 ID + 工作表 ID + 单元格范围 + 本地图片路径) -- 禁止使用 `range update` 写入图片,因为 `update_range` 的 MCP 工具不支持图片类型参数,调用必定失败。必须使用 `write-image` 命令 -- 用户指定了图片尺寸 → `write-image --width N --height M` - -### 浮动图片 - -用户说"浮动图片/悬浮图片/在表格上放一张图/加个浮动的图": -- 创建浮动图片 → 先 `media-upload` 上传图片获取 `resourceUrl`,再 `create-float-image` -- 浮动图片悬浮于单元格之上,不占用单元格内容,与 `write-image`(写入单元格内部的图片)不同 - -用户说"查看浮动图片/有哪些浮动图片/浮动图片列表": -- 列出所有浮动图片 → `list-float-images` -- 查看某个浮动图片详情 → `get-float-image` - -用户说"移动浮动图片/调整浮动图片大小/修改浮动图片/更新浮动图片": -- 更新浮动图片属性 → `update-float-image`(可更新锚点位置、尺寸、偏移量、图片资源路径) - -用户说"删除浮动图片/移除浮动图片": -- 删除浮动图片 → `delete-float-image` - -关键区分:`write-image`(单元格内嵌图片,占据单元格内容)vs `create-float-image`(浮动图片,悬浮于单元格之上,不占内容) - -### 筛选视图 - -用户说"筛选/过滤/只看某些值/只显示满足条件的行/筛选数据/创建筛选/删除筛选/设置筛选条件/清除筛选/排序": -- 查看当前筛选 → `filter get` -- 创建筛选 → `filter create` -- 删除筛选 → `filter delete` -- 批量设置多列条件 → `filter update` -- 清除某一列条件 → `filter clear-criteria` -- 按列排序 → `filter sort` -- **区分全局筛选与筛选视图**:如果用户说"筛选视图"则走 `filter-view` 系列;如果只说"筛选/过滤/只看"则默认走全局 `filter` 系列 -- **禁止替代方案**:当用户要求"筛选/只看/仅保留某些行"时,必须通过 `filter create` / `filter update` 创建真实的筛选器。禁止用"删除不符合条件的行"或"新建工作表只放符合条件的行"来代替——这些做法会让原数据丢失或不可恢复 - -用户说"筛选视图/查看筛选视图/有哪些筛选视图/筛选视图列表": -- 获取所有筛选视图 → `filter-view list` - -用户说"筛选视图详情/查看某个筛选视图/筛选视图信息/筛选视图配置": -- 获取单个筛选视图详情 → `filter-view info` - -用户说"创建筛选视图/新建筛选视图/添加筛选视图": -- 创建筛选视图 → `filter-view create` - -用户说"更新筛选视图/修改筛选视图/改筛选视图名称/改筛选视图范围": -- 更新筛选视图属性 → `filter-view update` - -用户说"删除筛选视图/移除筛选视图": -- 删除筛选视图 → `filter-view delete` - -用户说"设置筛选条件/添加筛选条件/配置筛选视图条件/按值筛选/按条件筛选/按颜色筛选": -- 设置筛选视图列条件 → `filter-view update-criteria` - -用户说"查看筛选条件/有哪些筛选条件/筛选视图设了什么条件/列出筛选条件": -- 列出所有列条件 → `filter-view list-criteria` -- 查看某一列的条件 → `filter-view get-criteria --column N` - -用户说"清除筛选条件/移除筛选条件/取消筛选条件": -- 清除筛选视图列条件 → `filter-view delete-criteria` -- 注意与 `filter-view delete`(删除整个筛选视图)区分:`delete-criteria` 仅清除指定列的条件,不删除筛选视图本身 - -### 导出 - -用户说"导出/下载xlsx/存为Excel/存成表格文件/把表格变成xlsx/导出表格/下载表格/导出为 excel": -- 导出表格 → `export`(单命令一站式,内部自动完成提交、轮询、可选下载) -- 仅需传 `--node`,可选 `--output` 指定本地文件/目录(不传则返回 downloadUrl) -- 需要落盘到本地 → `dws sheet export --node --output `,命令自动下载 xlsx -- 禁止用 `range read` 全量读取后自行拼接 xlsx 来模拟导出,必须使用 `export` 命令(服务端原子导出,保留格式/合并/公式等属性) -- 禁止在 AI Agent 侧实现轮询或重试,CLI 内部已按渐进式退避策略完成(最多 30 次约 5 分钟) - -### URL 粘贴场景 - -用户直接粘贴表格 URL(无其他指令): -- 先 probe:`dws doc info --node --format json` 校验 `contentType` 和 `extension` -- `extension=axls` → `list`(列出工作表)+ `range read`(读取第一个工作表数据) -- `extension=xlsx` / `xls` / `xlsm` / `csv` → 转 `dws doc download --node --output ./`,告知用户"这是本地表格文件,已为你下载到本地",然后基于本地文件继续后续处理 - -用户粘贴 URL + 附加指令: -- 已 probe 为 `axls` 时: - - "帮我看看这个表格有什么数据" → `range read` - - "这个表格有哪些工作表" → `list` - - "往这个表格写入数据" → `range update` - - "帮我找一下表格里的XXX" → `find` -- probe 为 xlsx/xls/xlsm/csv 时:无论用户说"读数据/查看/分析",先走 `dws doc download` 下载到本地,由用户或后续步骤对本地 xlsx 进行解析,严禁调用 `sheet list` / `range read` 等命令 - -关键区分: sheet(电子表格/单元格读写) vs aitable(AI多维表/结构化记录) vs doc(文档编辑/阅读) - -## 关键注意事项 - -以下是最容易出错的规则,**必须严格遵守**: - -- ★ **`--sheet-id` 获取规范(强制)**:`sheetId` 未知时必须先通过 `dws sheet list --node --format json` 查询,禁止凭空编造(如臆测为 `Sheet1`、`sheet1`、`0`、`default` 等) -- ★ **`range update` 单元格协议(强制)**:`--values` 是二维 JSON 数组,**每个单元格必须是 object**(如 `{"type":"text","text":"张三"}`),不再支持裸值 `"张三"` / `90` / `null`。裸值会报错「不支持原始值……每个单元格必须是 object」。数字/布尔也写成字符串 object(如 `{"type":"text","text":"90"}`),服务端自动识别类型。超链接写在单元格 object 的 `hyperlink` 字段,**没有 `--hyperlinks` flag**(传了报 unknown flag) -- ★ **`range update` 维度校验(强制)**:`--values` 的行列数必须与 `--range` 完全一致。例如 `--range "A1:C3"` → `--values` 必须是 3×3 的 object 数组 -- ★ **`range update` 清空规范(强制)**:清空单个单元格用 `{"type":"text","text":""}`;清空整片区域用 `range clear`。跳过某格保留原值用 `{}` 空对象 -- ★ **单次调用上限(强制)**:`range update` / `set-style` 行数 ≤ 1000,单元格总数建议 ≤ 5000(硬限 30000) -- ★ **大批量纯值写入用 `csv-put` 不用 `range update`**:当写入纯值(无公式/超链接)且数据量较大时(>5 行或 >20 单元格),必须使用 `csv-put`。`csv-put` 接受 CSV 文本直接写入,无需构造二维 JSON 数组,支持自动扩容,更简洁高效。仅在需要写入公式、超链接、或仅更新少量单元格时才使用 `range update` -- ★ **搜索用 `find` 不用 `range read`**:`find` 是服务端搜索,禁止用 `range read` 全量读取后客户端过滤 -- ★ **替换用 `replace` 不用 `range update`**:`replace` 是服务端原子操作,返回替换计数 -- ★ **移动用 `move-dimension` 不用 `range update`**:原子操作,保留格式和合并状态 -- ★ **单元格图片用 `write-image` 不用 `range update`**:`update_range` MCP 不支持图片参数,调用必失败 -- ★ **浮动图片用 `create-float-image` 不用 `write-image`**:两者用途不同——`write-image` 写入单元格内部,`create-float-image` 创建悬浮于单元格之上的浮动图片;`--src` 必须来自 `media-upload` 的 `resourceUrl` -- ★ **`export` 禁止自行轮询/重试**:CLI 内部已完成渐进式退避轮询(最多 30 次约 5 分钟),失败时直接告知用户稍后再试 -- ★ **关键区分**:sheet(在线电子表格/单元格读写) vs aitable(AI多维表/结构化记录/字段定义) vs doc(文档编辑/阅读) - -> 完整注意事项请参见本文档末尾「注意事项(完整版)」章节。 - -## 命令详细参考 - -> 以下为各命令的完整 Usage、Flags 和示例。参数不确定时也可直接执行 `dws sheet <命令> --help` 在线查看。 - -### 创建钉钉表格文档 -``` -Usage: - dws sheet create [flags] -Example: - dws sheet create --name "销售数据" - dws sheet create --name "Q1 数据" --folder - dws sheet create --name "知识库表格" --workspace -Flags: - --name string 表格名称 (必填) - --folder string 目标文件夹 ID (dentryUuid 格式) 或 URL;禁止传入纯数字 dentryId - --workspace string 目标知识库 ID -``` - -> **ID 格式约束**:`--folder` 只接受 UUID 格式的 `fileId`(如 `ZgpG2NdyVXYOR2D5UGDok65MJMwvDqPk`)或 alidocs 文件夹 URL。`drive list` 返回中有 `dentryId`(纯数字,如 `218595998810`)和 `fileId`(UUID 格式)两个字段,**必须使用 `fileId`,禁止使用 `dentryId`**,传入纯数字会导致命令失败。 - -### 获取全部工作表列表 -``` -Usage: - dws sheet list [flags] -Example: - dws sheet list --node - dws sheet list --node "https://alidocs.dingtalk.com/i/nodes/" -Flags: - --node string 表格文档 ID 或 URL (必填) -``` - -### 获取指定工作表详情 -``` -Usage: - dws sheet info [flags] -Example: - dws sheet info --node - dws sheet info --node --sheet-id - dws sheet info --node --sheet-id "Sheet1" -Flags: - --node string 表格文档 ID 或 URL (必填) - --sheet-id string 工作表 ID 或名称 (不传则返回第一个工作表) -``` - -### 新建工作表 -``` -Usage: - dws sheet new [flags] -Example: - dws sheet new --node --name "Sheet2" - dws sheet new --node --name "数据汇总" -Flags: - --node string 表格文档 ID (必填) - --name string 工作表名称 (必填) -``` - -### 更新工作表属性 -``` -Usage: - dws sheet update [flags] -Example: - # 改名 + 调整冻结 - dws sheet update --node --sheet-id --title "汇总表" --frozen-row-count 2 --frozen-column-count 1 - - # 隐藏工作表 - dws sheet update --node --sheet-id --hidden=true - - # 显示工作表 - dws sheet update --node --sheet-id --hidden=false - - # 移动工作表到第一个位置 - dws sheet update --node --sheet-id --index 0 - - # 取消冻结 - dws sheet update --node --sheet-id --frozen-row-count 0 --frozen-column-count 0 -Flags: - --node string 表格文档 ID 或 URL (必填) - --sheet-id string 工作表 ID 或名称 (必填) - --name string 新名称,最长 100 字符,不能包含 / \ ? * [ ] : - --title string --name 的别名(兼容) - --index int 新位置(从 0 开始) - --hidden --hidden=true 隐藏,--hidden=false 取消隐藏 - --tab-color string 工作表标签颜色,Hex 如 #FF0000;传空字符串清除颜色 - --frozen-row-count int 冻结行数,0 表示取消冻结 - --frozen-column-count int 冻结列数,0 表示取消冻结 -``` - -更新工作表名称、位置、隐藏状态、标签颜色、冻结行列。 -`--name`(别名 `--title`)/ `--index` / `--hidden` / `--tab-color` / `--frozen-row-count` / `--frozen-column-count` 至少提供一个;多个属性可同时传入,将在同一次请求中更新。 - -注意: -- 至少需要保留一个可见的工作表,不能将所有工作表都隐藏 -- 冻结行数/列数不能超过工作表的总行数/列数 - -### 复制工作表 -``` -Usage: - dws sheet copy [flags] -Example: - # 按默认位置复制 - dws sheet copy --node --sheet-id - - # 指定副本名称和位置 - dws sheet copy --node --sheet-id --title "销售副本" --index 2 - - # 只指定名称 - dws sheet copy --node --sheet-id --title "备份" -Flags: - --node string 表格文档 ID 或 URL (必填) - --sheet-id string 源工作表 ID 或名称 (必填) - --title string 副本名称,最长 100 字符,不能包含 / \ ? * [ ] : (不传则系统自动生成) - --index int 副本位置(从 0 开始)(不传则放在源工作表之后) -``` - -复制指定工作表,在同一表格中创建一个副本。 -复制操作会将源工作表的所有内容(包括数据、格式、公式等)完整复制到新工作表中。 -传 `--index` 时,CLI 会先复制,再追加一次位置更新,把副本移动到目标索引。 -名称与已有工作表重复时系统会自动重命名。 - -### 读取工作表数据 -``` -Usage: - dws sheet range read [flags] # 别名: dws sheet range get -Example: - dws sheet range read --node - dws sheet range read --node --sheet-id - dws sheet range read --node --sheet-id "Sheet1" --range "A1:D10" - dws sheet range read --node --range "Sheet1!A1:D10" - - # 使用 get 别名,与 read 等价 - dws sheet range get --node --sheet-id --range "A1:D10" - - # 指定取值模式:原始值 / 公式文本 - dws sheet range read --node --value-render-option raw_value - dws sheet range read --node --value-render-option formula -Flags: - --node string 表格文档 ID 或 URL (必填) - --sheet-id string 工作表 ID 或名称 (不传则默认第一个工作表) - --range string 读取范围,A1 表示法 (如 A1:D10,不传则读取全部数据) - --value-render-option string 取值模式: formatted_value(格式化展示值,默认) | raw_value(原始值) | formula(公式文本,无公式回退原始值) -``` - -**超时处理建议**:读取大范围数据时若出现超时或响应过慢,请主动缩小 `--range` 查询范围,**建议单次读取的单元格数量控制在 5000 个以内**(例如 50 行 × 100 列、100 行 × 50 列)。对于大表可采用分页读取策略: -- 先通过 `info` 获取 `nonEmptyRange.range` / `nonEmptyRange.lastRow` / `nonEmptyRange.lastColumn` 确定数据边界(空表时这些字段为 null) -- 按行分批读取,如 `A1:J500`、`A501:J1000`、`A1001:J1500` …… -- 避免不传 `--range` 直接读取整个大工作表 - -### 更新工作表指定区域内容 -``` -Usage: - dws sheet range update [flags] -Example: - # 写入文本(每个单元格必须是 object) - dws sheet range update --node --sheet-id --range "A1:B2" \ - --values '[[{"type":"text","text":"姓名"},{"type":"text","text":"分数"}],[{"type":"text","text":"张三"},{"type":"text","text":"90"}]]' - - # 写入公式(text 以 = 开头识别为公式) - dws sheet range update --node --sheet-id --range "C2" \ - --values '[[{"type":"text","text":"=A2&B2"}]]' - - # 写入单元格级超链接(写在 cell 的 hyperlink 字段,没有 --hyperlinks flag) - dws sheet range update --node --sheet-id --range "A1" \ - --values '[[{"type":"text","text":"钉钉","hyperlink":{"type":"path","link":"https://dingtalk.com"}}]]' - - # 只更新部分单元格:用 {} 空对象占位保留原值 - dws sheet range update --node --sheet-id --range "A1:B1" \ - --values '[[{"type":"text","text":"新值"},{}]]' - - # 清空单个单元格(text 为空字符串) - dws sheet range update --node --sheet-id --range "A1" \ - --values '[[{"type":"text","text":""}]]' - - # 清空整片区域请改用 range clear - dws sheet range clear --node --sheet-id --range "A1:B3" -Flags: - --node string 表格文档 ID (必填) - --sheet-id string 工作表 ID 或名称 (必填) - --range string 目标单元格区域地址,如 A1:B3 (必填) - --values string 单元格内容,二维 JSON 数组 (必填);每个元素必须是 object -``` - -**单元格对象协议(重要)**:`--values` 每个元素必须是 object,支持以下形态: -- `{"type":"text","text":"内容"}`:普通文本;`text` 以 `=` 开头识别为公式;`text` 为 `""` 清空该格;数字/布尔写成字符串(`{"type":"text","text":"100"}`),服务端自动识别类型 -- `{"type":"text","text":"内容","cellStyles":{...}}`:文本 + 整格样式(`fontWeight`/`fontColor`/`backgroundColor`/`numberFormat` 等) -- `{"type":"richText","texts":[...]}`:富文本,子项可为 text/link/attachment/image,片段样式写在子项 `style` -- `{"type":"text","text":"钉钉","hyperlink":{"type":"path","link":"https://..."}}`:整格超链接(`type` 可为 `path`/`sheet`/`range`,或 `{"type":"none"}` 清除) -- `{"dataValidation":{...}}`:写下拉/复选框数据验证 -- `{}`:空对象,跳过该格保留原值(只改部分单元格时占位用) - -不再支持裸值(`"张三"` / `90` / `null`),原样传入会报「不支持原始值……每个单元格必须是 object」。 - -**单次调用建议**:行数 ≤ 1000,单元格总数(行×列)≤ 5000;超过时请拆分多次调用。 - -**何时该用 `csv-put` 替代**:如果你准备用 `range update` 写入纯值(不含公式和超链接),且数据量超过 5 行或 20 个单元格,应改用 `csv-put`——它接受 CSV 文本直接写入,无需手动拼装 object 数组,且支持自动扩容行列。仅在需要写入公式(`=SUM(...)`)、超链接、富文本、或修改少量单元格时才使用 `range update`。 - -**范围职责**:`range update` 负责写入单元格的值、超链接、per-cell 样式与数据验证。批量刷整片区域的统一样式或数字格式(百分比 / 货币 / 日期 / 文本等)请使用 `dws sheet range set-style --number-format <格式代码>`。 - -### 设置单元格样式 -``` -Usage: - dws sheet range set-style [flags] -Example: - # 给 A1:B3 打上黄底粗体居中 - dws sheet range set-style --node --sheet-id --range "A1:B3" \ - --bg-color "#FFF2CC" --font-weight bold --h-align center - - # 给 C1:C5 逐单元格设置不同背景色 - dws sheet range set-style --node --sheet-id --range "C1:C5" \ - --bg-colors-json '[["#FF0000"],["#00FF00"],["#0000FF"],["#FFFF00"],["#FF00FF"]]' - - # 整片 range 启用自动换行 - dws sheet range set-style --node --sheet-id --range "A1:E10" --word-wrap autoWrap -Flags: - --node string 表格文档 ID 或 URL (必填) - --sheet-id string 工作表 ID 或名称 (必填) - --range string 目标区域,如 A1:B3 (必填) - --bg-color string 背景色(#RRGGBB),一键刷整个 range;与 --bg-colors-json 二选一 - --bg-colors-json string 背景色二维 JSON 数组,维度需与 --range 一致 - --font-size int 字号,一键刷整个 range;与 --font-sizes-json 二选一 - --font-sizes-json string 字号二维 JSON 数组 - --h-align string 水平对齐:left/center/right/general - --h-aligns-json string 水平对齐二维 JSON 数组 - --v-align string 垂直对齐:top/middle/bottom - --v-aligns-json string 垂直对齐二维 JSON 数组 - --font-color string 字体颜色(#RRGGBB) - --font-colors-json string 字体颜色二维 JSON 数组 - --font-weight string 字体粗细:bold/normal - --font-weights-json string 字体粗细二维 JSON 数组 - --word-wrap string 换行方式:overflow/clip/autoWrap(整个 range 共用) - --number-format string 数字格式,如 General/@/#,##0/0%/yyyy/m/d -``` - -**特性说明**: -- 每个样式维度提供两种写法,二选一:`--xxx`(单值刷整个 range,CLI 本地展开为二维数组)vs `--xxx-json`(逐单元格指定,维度需与 `--range` 完全一致) -- 至少需传入一个样式参数。单次调用建议:行数 ≤ 1000,单元格总数 ≤ 5000 -- 枚举值按驼峰书写:`autoWrap`、`bold`、`normal`、`center` 等 - -### 批量设置单元格样式 -``` -Usage: - dws sheet range batch-set-style [flags] -Example: - dws sheet range batch-set-style --node --batch ./styles.json - dws sheet range batch-set-style --node --batch ./styles.json --continue-on-error -Flags: - --node string 表格文档 ID 或 URL (必填) - --batch string 批次配置 JSON 文件路径 (必填) - --continue-on-error 遇到失败时继续执行后续条目(默认遇错即停) -``` - -配置文件格式(JSON 数组,每个元素一条批次项): -```json -[ - { - "sheetId": "Sheet1", - "range": "A1:B3", - "bgColor": "#FFF2CC", - "fontSize": 12, - "hAlign": "center", - "vAlign": "middle", - "fontColor": "#333333", - "fontWeight": "bold", - "wordWrap": "autoWrap", - "numberFormat": "General" - }, - { - "sheetId": "Sheet1", - "range": "C1:C5", - "bgColorsJson": "[[\"#FF0000\"],[\"#00FF00\"],[\"#0000FF\"],[\"#FFFF00\"],[\"#FF00FF\"]]" - } -] -``` - -**特性说明**: -- CLI 侧顺序循环逐条调用 `update_range`(非服务端批量),运行时输出 `[N/M]` 进度 -- 每条记录执行与 `set-style` 一致的校验:至少一项样式字段 + rows ≤ 1000 + rows×cols ≤ 30000 + 枚举合法 -- 默认遇错即停(返回非 0),`--continue-on-error` 时所有条目跑完再返回首个错误 - -### 在工作表中搜索单元格内容 -``` -Usage: - dws sheet find [flags] -Example: - # 基本搜索 - dws sheet find --node --sheet-id --find "销售额" - - # 在指定范围内搜索 - dws sheet find --node --sheet-id --find "合计" --range "A1:D100" - - # 正则表达式搜索(不区分大小写) - dws sheet find --node --sheet-id --find "^total" --use-regexp --match-case=false - - # 精确匹配整个单元格内容 - dws sheet find --node --sheet-id --find "完成" --match-entire-cell - - # 搜索公式文本 - dws sheet find --node --sheet-id --find "SUM" --match-formula -Flags: - --node string 表格文档 ID 或 URL (必填) - --sheet-id string 工作表 ID 或名称 (必填) - --find string 搜索文本 (必填) - --range string 搜索范围,A1 表示法 (如 A1:D10) - --match-case 区分大小写 (默认 true) - --match-entire-cell 精确匹配整个单元格内容 - --use-regexp 启用正则表达式搜索 - --match-formula 搜索公式文本而非显示值 - --include-hidden 包含隐藏单元格 -``` - -### 在工作表末尾追加数据 -``` -Usage: - dws sheet append [flags] -Example: - dws sheet append --node --sheet-id --values '[["张三","销售部",50000]]' - dws sheet append --node --sheet-id "Sheet1" \ - --values '[["李四","市场部",38000],["王五","销售部",62000]]' -Flags: - --node string 表格文档 ID 或 URL (必填) - --sheet-id string 工作表 ID 或名称 (必填) - --values string 追加数据,二维 JSON 数组 (必填) -``` - -`--values` 为二维 JSON 数组,外层每个元素代表一行,内层每个元素代表一个单元格值。 -追加的数据列数应与工作表已有数据的列数保持一致。 - -### 将 CSV 数据写入指定位置 -``` -Usage: - dws sheet csv-put [flags] -Example: - # 内联多行 CSV:必须用 $'...' 让 \n 变成真换行;普通单引号 '...\n...' 里的 \n 是字面量,会写成一行含字面 \n 的错误数据 - dws sheet csv-put --node --sheet-id --start-cell A1 \ - --csv $'name,score\nAlice,95\nBob,87' - - # 多行数据推荐用 @文件,最稳妥 - dws sheet csv-put --node --sheet-id --start-cell B2 \ - --csv @data.csv --allow-overwrite - - cat data.csv | dws sheet csv-put --node --sheet-id \ - --start-cell A1 --csv - - - dws sheet csv-put --node --sheet-id --start-cell A1 \ - --csv @data.csv --dry-run -Flags: - --node string 表格文档 ID 或 URL (必填) - --sheet-id string 工作表 ID 或名称 (必填) - --csv string CSV 文本、@文件路径 或 - 表示 stdin (必填) - --start-cell string 起始单元格,A1 表示法 (必填) - --allow-overwrite 允许覆盖已有数据 (默认 false) -``` - -将 RFC 4180 格式的 CSV 文本写入指定工作表的指定单元格位置。 -- 只写纯值,不支持公式/样式/批注。`=` 开头的内容当文本处理,不会被解析为公式 -- 数字/日期/百分数由表格引擎自动识别类型(如 `95` 存为数字,`2025-03-01` 存为日期) -- 自动扩容行列:CSV 数据超出当前工作表维度时自动追加行/列 -- 目标区域如含合并单元格,合并将被打散,值正常写入 -- `--allow-overwrite` 默认 false,目标区域有数据时需显式传 `--allow-overwrite` 才能覆盖 -- `--csv` 支持三种输入:直接传文本、`@filepath` 从本地文件读取、`-` 从 stdin 管道读取 -- CSV 文本上限 2M 字符,单元格总数上限 30000 -- 特殊字符处理:CLI 会自动过滤 `\r`(Windows 换行符)和 BOM(UTF-8 文件头标记),Excel/Windows 导出的 CSV 可直接使用;如 CSV 数据中含零宽字符(U+200B 等)或 Bidi 控制符,CLI 会拒绝并报错 - -### 在指定位置插入行或列 -``` -Usage: - dws sheet insert-dimension [flags] -Example: - # 在第 3 行之前插入 2 行 - dws sheet insert-dimension --node --sheet-id --dimension ROWS --position "3" --length 2 - - # 在 A 列之前插入 1 列 - dws sheet insert-dimension --node --sheet-id --dimension COLUMNS --position "A" --length 1 - - # 使用工作表前缀(忽略 --sheet-id) - dws sheet insert-dimension --node --sheet-id --dimension ROWS --position "Sheet1!3" --length 5 - - # 在 AB 列之前插入 3 列 - dws sheet insert-dimension --node --sheet-id --dimension COLUMNS --position "AB" --length 3 -Flags: - --node string 表格文档 ID 或 URL (必填) - --sheet-id string 工作表 ID 或名称 (必填) - --dimension string 插入维度: ROWS 或 COLUMNS (必填) - --position string 插入位置,A1 表示法 (必填)。ROWS 时为行号如 "3";COLUMNS 时为列字母如 "A" - --length string 插入数量,正整数 (必填),最大 5000 -``` - -在钉钉表格指定工作表的指定位置之前插入若干空行或空列。 -`--dimension ROWS` 时,`--position` 为 1-based 行号字符串;`--dimension COLUMNS` 时,`--position` 为列字母。 -支持在 `--position` 中携带工作表前缀(如 `Sheet1!3`),此时忽略 `--sheet-id`。 -若需要在末尾追加行/列,请使用 `append` 命令。 - -### 删除指定位置的行或列 - -> **CAUTION:** 不可逆操作 — 执行前必须向用户确认。 - -``` -Usage: - dws sheet delete-dimension [flags] -Example: - # 从第 3 行开始删除 2 行 - dws sheet delete-dimension --node --sheet-id --dimension ROWS --position "3" --length 2 - - # 从 A 列开始删除 1 列 - dws sheet delete-dimension --node --sheet-id --dimension COLUMNS --position "A" --length 1 - - # 使用工作表前缀(忽略 --sheet-id) - dws sheet delete-dimension --node --sheet-id --dimension ROWS --position "Sheet1!3" --length 5 - - # 从 AB 列开始删除 3 列 - dws sheet delete-dimension --node --sheet-id --dimension COLUMNS --position "AB" --length 3 -Flags: - --node string 表格文档 ID 或 URL (必填) - --sheet-id string 工作表 ID 或名称 (必填) - --dimension string 删除维度: ROWS 或 COLUMNS (必填) - --position string 删除起始位置,A1 表示法 (必填)。ROWS 时为行号如 "3";COLUMNS 时为列字母如 "A" - --length string 删除数量,正整数 (必填),最大 5000 -``` - -在钉钉表格指定工作表中,从指定位置起删除若干连续的行或列。 -`--dimension ROWS` 时,`--position` 为 1-based 行号字符串;`--dimension COLUMNS` 时,`--position` 为列字母。 -支持在 `--position` 中携带工作表前缀(如 `Sheet1!3`),此时忽略 `--sheet-id`。 -删除后后续的行/列会向前移动填补空位;若需要仅清空内容但保留行/列占位,请使用 `range clear`(整片区域清除,比逐格写空更简洁)。 - -### 更新指定范围行/列属性 -``` -Usage: - dws sheet update-dimension [flags] -Example: - # 隐藏第 3~4 行 - dws sheet update-dimension --node --sheet-id --dimension ROWS --start-index "3" --length 2 --hidden - - # 显示 A~B 列 - dws sheet update-dimension --node --sheet-id --dimension COLUMNS --start-index "A" --length 2 --hidden=false - - # 设置第 1~5 行行高为 40px - dws sheet update-dimension --node --sheet-id --dimension ROWS --start-index "1" --length 5 --pixel-size 40 - - # 设置 C 列列宽为 200px 并隐藏 - dws sheet update-dimension --node --sheet-id --dimension COLUMNS --start-index "C" --length 1 --pixel-size 200 --hidden - - # 使用工作表前缀(忽略 --sheet-id) - dws sheet update-dimension --node --sheet-id --dimension ROWS --start-index "Sheet1!3" --length 2 --hidden -Flags: - --node string 表格文档 ID 或 URL (必填) - --sheet-id string 工作表 ID 或名称 (必填) - --dimension string 更新维度: ROWS 或 COLUMNS (必填) - --start-index string 起始位置,A1 表示法 (必填)。ROWS 时为行号如 "3";COLUMNS 时为列字母如 "A" - --length string 更新数量,正整数 (必填),最大 5000 - --hidden 是否隐藏 (true=隐藏, false=显示),与 --pixel-size 至少填其一 - --pixel-size int 行高或列宽(像素),ROWS 时为行高,COLUMNS 时为列宽,与 --hidden 至少填其一 -``` - -批量更新钉钉表格指定工作表中连续多行/多列的属性,支持设置显隐状态(hidden)与行高/列宽(pixelSize)。 -`--dimension ROWS` 时,`--start-index` 为 1-based 行号字符串;`--dimension COLUMNS` 时,`--start-index` 为列字母。 -支持在 `--start-index` 中携带工作表前缀(如 `Sheet1!3`),此时忽略 `--sheet-id`。 -`--hidden` 与 `--pixel-size` 至少必须提供一个。当同时提供时,将先应用尺寸再应用显隐,任一失败整体失败。 -`--pixel-size` 单位为像素,`dimension=ROWS` 时表示行高、`dimension=COLUMNS` 时表示列宽。 - -### 合并单元格 -``` -Usage: - dws sheet merge-cells [flags] -Example: - # 合并所有单元格(默认) - dws sheet merge-cells --node --sheet-id --range "A1:B3" - - # 按行合并 - dws sheet merge-cells --node --sheet-id --range "A1:C3" --merge-type mergeRows - - # 按列合并 - dws sheet merge-cells --node --sheet-id --range "A1:C3" --merge-type mergeColumns - - # 使用带工作表前缀的范围(忽略 --sheet-id) - dws sheet merge-cells --node --sheet-id --range "Sheet1!A1:B3" -Flags: - --node string 表格文档 ID 或 URL (必填) - --sheet-id string 工作表 ID 或名称 (必填) - --range string 目标单元格区域地址,如 A1:B3 (必填) - --merge-type string 合并方式: mergeAll(默认)/mergeRows/mergeColumns -``` - -支持三种合并方式: -- `mergeAll`(默认):合并所有单元格,将选定区域内的所有单元格合并成一个 -- `mergeRows`:按行合并,在选定区域内将同一行相邻的单元格合并 -- `mergeColumns`:按列合并,在选定区域内将同一列相邻的单元格合并 - -注意:合并时只保留左上角单元格的值,其他单元格的值会被丢弃。 -`--range` 支持带工作表前缀的写法(如 `Sheet1!A1:B3`),此时将优先使用前缀解析出的工作表,忽略 `--sheet-id`。 - -### 上传附件到表格 -``` -Usage: - dws sheet media-upload [flags] -Example: - dws sheet media-upload --node --file ./report.pdf - dws sheet media-upload --node --file ./data.bin --name "数据文件.dat" --mime-type application/octet-stream -Flags: - --node string 目标表格文档的标识,支持传入 URL 或 ID (必填) - --file string 本地文件路径 (必填) - --name string 附件显示名称 (默认使用文件名) - --mime-type string 文件 MIME 类型 (默认根据扩展名推断) -``` - -`--format json` 输出规整 JSON:`{success, resourceId, resourceUrl, fileName, fileSize}`。`resourceUrl` 可用于 `create-float-image` 的 `--src`。 - -### 上传图片并写入表格单元格 -``` -Usage: - dws sheet write-image [flags] -Example: - dws sheet write-image --node --sheet-id --range A1:A1 --file ./chart.png - dws sheet write-image --node --sheet-id --range B2:B2 --file ./logo.png --width 200 --height 100 -Flags: - --node string 目标表格文档的标识,支持传入 URL 或 ID (必填) - --sheet-id string 工作表 ID 或名称 (必填) - --range string 目标单元格区域地址,如 A1:A1 (必填) - --file string 本地图片文件路径 (必填) - --name string 图片显示名称 (默认使用文件名) - --mime-type string 文件 MIME 类型 (默认根据扩展名推断) - --width int 图片显示宽度 (可选) - --height int 图片显示高度 (可选) -``` - -### 创建浮动图片 -``` -Usage: - dws sheet create-float-image [flags] -Example: - # 先上传图片获取 resourceUrl - dws sheet media-upload --node --file ./chart.png - # 输出: resourceUrl: /core/api/resources/img/xxxx... - - # 再创建浮动图片 - dws sheet create-float-image --node --sheet-id \ - --src "/core/api/resources/img/xxxx..." --range A1 --width 400 --height 300 - - # 带偏移量 - dws sheet create-float-image --node --sheet-id \ - --src "/core/api/resources/img/xxxx..." --range B2 --width 200 --height 150 --offset-x 10 --offset-y 20 -Flags: - --node string 表格文档 ID 或 URL (必填) - --sheet-id string 工作表 ID 或名称 (必填) - --src string 图片资源路径,通过 media-upload 获取的 resourceUrl (必填) - --range string 锚点单元格,A1 表示法,如 A1、B3 (必填) - --width int 图片宽度,像素,正整数 (必填) - --height int 图片高度,像素,正整数 (必填) - --offset-x int 水平偏移量,像素 (默认 0) - --offset-y int 垂直偏移量,像素 (默认 0) -``` - -浮动图片悬浮于单元格之上,不占用单元格内容,可自由定位和调整大小。 -- `--src` 必须是 `media-upload` 返回的 `resourceUrl`(格式为 `/core/api/resources/img/...`),不能直接传外部 URL -- `--range` 使用 A1 表示法指定锚点单元格(如 `A1`、`B3`),支持带工作表前缀(如 `Sheet1!A1`) -- `--width` / `--height` 为必填,单位像素,必须为正整数 -- `--offset-x` / `--offset-y` 表示相对锚点单元格左上角的偏移量(像素),默认 0,不能为负数 - -### 获取浮动图片详情 -``` -Usage: - dws sheet get-float-image [flags] -Example: - dws sheet get-float-image --node --sheet-id --float-image-id -Flags: - --node string 表格文档 ID 或 URL (必填) - --sheet-id string 工作表 ID 或名称 (必填) - --float-image-id string 浮动图片 ID (必填) -``` - -获取单个浮动图片的详细信息,包括 ID、图片资源路径、锚点位置、尺寸和偏移量。 -`--float-image-id` 可通过 `list-float-images` 获取。 - -### 列出工作表所有浮动图片 -``` -Usage: - dws sheet list-float-images [flags] -Example: - dws sheet list-float-images --node --sheet-id -Flags: - --node string 表格文档 ID 或 URL (必填) - --sheet-id string 工作表 ID 或名称 (必填) -``` - -列出指定工作表中所有浮动图片,返回 `floatImages` 数组和 `totalCount`。 - -### 更新浮动图片属性 -``` -Usage: - dws sheet update-float-image [flags] -Example: - # 移动浮动图片到新位置 - dws sheet update-float-image --node --sheet-id --float-image-id --range C5 - - # 调整尺寸 - dws sheet update-float-image --node --sheet-id --float-image-id --width 600 --height 400 - - # 替换图片(需先 media-upload 新图片获取 resourceUrl) - dws sheet update-float-image --node --sheet-id --float-image-id \ - --src "/core/api/resources/img/xxxx..." -Flags: - --node string 表格文档 ID 或 URL (必填) - --sheet-id string 工作表 ID 或名称 (必填) - --float-image-id string 浮动图片 ID (必填) - --src string 新的图片资源路径,通过 media-upload 获取的 resourceUrl - --range string 新的锚点单元格,A1 表示法 - --width int 新的图片宽度,像素 - --height int 新的图片高度,像素 - --offset-x int 新的水平偏移量,像素 - --offset-y int 新的垂直偏移量,像素 -``` - -更新浮动图片的属性,`--src` / `--range` / `--width` / `--height` / `--offset-x` / `--offset-y` 至少传入一个。 -`--float-image-id` 可通过 `list-float-images` 获取。 - -### 删除浮动图片 -``` -Usage: - dws sheet delete-float-image [flags] -Example: - dws sheet delete-float-image --node --sheet-id --float-image-id -Flags: - --node string 表格文档 ID 或 URL (必填) - --sheet-id string 工作表 ID 或名称 (必填) - --float-image-id string 浮动图片 ID (必填) -``` - -删除指定的浮动图片,操作不可恢复。`--float-image-id` 可通过 `list-float-images` 获取。 - -### 全局查找替换 -``` -Usage: - dws sheet replace [flags] -Example: - dws sheet replace --node --sheet-id --find "旧文本" --replacement "新文本" - dws sheet replace --node --sheet-id --find "待处理" --replacement "已完成" --match-entire-cell - dws sheet replace --node --sheet-id --find "\\d{4}" --replacement "****" --use-regexp - dws sheet replace --node --sheet-id --find "旧" --replacement "新" --range "A1:D100" - dws sheet replace --node --sheet-id --find "临时" --replacement "" -Flags: - --node string 表格文档 ID 或 URL (必填) - --sheet-id string 工作表 ID 或名称 (必填) - --find string 查找文本 (必填) - --replacement string 替换文本 (必填,可为空字符串表示删除) - --range string 替换范围,A1 表示法 (如 A1:D100) - --match-case 区分大小写 (默认 false) - --match-entire-cell 完整单元格匹配 - --use-regexp 启用正则表达式匹配 - --include-hidden 包含隐藏行/列 -``` - -返回被替换的单元格数量。`--replacement` 可以为空字符串,表示删除匹配内容。 - -### 移动行或列 -``` -Usage: - dws sheet move-dimension [flags] -Example: - # 将第 2 行移动到第 5 行的位置(索引从 0 开始) - dws sheet move-dimension --node --sheet-id \ - --dimension ROWS --start-index 1 --end-index 1 --destination-index 4 - - # 将第 2~4 行移动到第 1 行之前 - dws sheet move-dimension --node --sheet-id \ - --dimension ROWS --start-index 1 --end-index 3 --destination-index 0 - - # 将 B~C 列移动到 E 列的位置 - dws sheet move-dimension --node --sheet-id \ - --dimension COLUMNS --start-index 1 --end-index 2 --destination-index 4 -Flags: - --node string 表格文档 ID 或 URL (必填) - --sheet-id string 工作表 ID 或名称 (必填) - --dimension string 维度类型: ROWS 或 COLUMNS (必填) - --start-index int 源起始索引,0-based (必填) - --end-index int 源结束索引,0-based,包含 (必填) - --destination-index int 目标位置索引,0-based (必填) -``` - -索引均为 0-based(第 1 行/列的索引为 0)。destination-index 不能在 [start-index, end-index] 范围内。 - -**destination-index 计算规则:** -destination-index 是目标位置的 0-based 索引,即移动到第 n 行/列则传 n-1: -- 通用公式:`destination-index = 目标行号(1-based) - 1` -- 例如:将第 2 行移到第 5 行位置 → `destination-index = 5 - 1 = 4`,即 `start-index=1, end-index=1, destination-index=4` -- 例如:将第 4 行移到第 1 行(最前面)→ `destination-index = 1 - 1 = 0`,即 `start-index=3, end-index=3, destination-index=0` - -### 追加空行或空列 -``` -Usage: - dws sheet add-dimension [flags] -Example: - dws sheet add-dimension --node --sheet-id --dimension ROWS --length 5 - dws sheet add-dimension --node --sheet-id --dimension COLUMNS --length 3 -Flags: - --node string 表格文档 ID 或 URL (必填) - --sheet-id string 工作表 ID 或名称 (必填) - --dimension string 维度类型: ROWS 或 COLUMNS (必填) - --length int 追加数量,正整数,最多 5000 (必填) -``` - -在工作表末尾追加指定数量的空行或空列。 - -### 创建行或列分组 -``` -Usage: - dws sheet group-dimension [flags] -Example: - dws sheet group-dimension --node --sheet-id --range "3:7" - dws sheet group-dimension --node --sheet-id --range "C:F" --group-state fold - dws sheet group-dimension --node --sheet-id --range "Sheet1!3:7" -Flags: - --node string 表格文档 ID 或 URL (必填) - --sheet-id string 工作表 ID 或名称 (必填) - --range string 整行/整列范围,如 "3:7" 或 "C:F" (必填) - --group-state string 创建后的分组状态: expand 或 fold,默认 expand -``` - -在工作表中为连续行或连续列创建分组。`--range` 必须是整行或整列范围,不支持普通单元格矩形范围。 - -### 取消行或列分组 -``` -Usage: - dws sheet ungroup-dimension [flags] -Example: - dws sheet ungroup-dimension --node --sheet-id --range "3:7" - dws sheet ungroup-dimension --node --sheet-id --range "C:F" -Flags: - --node string 表格文档 ID 或 URL (必填) - --sheet-id string 工作表 ID 或名称 (必填) - --range string 整行/整列范围,如 "3:7" 或 "C:F" (必填) -``` - -取消指定连续行或连续列的分组。 - -### 取消合并单元格 -``` -Usage: - dws sheet unmerge-cells [flags] -Example: - dws sheet unmerge-cells --node --sheet-id --range "A1:D5" -Flags: - --node string 表格文档 ID 或 URL (必填) - --sheet-id string 工作表 ID 或名称 (必填) - --range string 取消合并的范围,A1 表示法 (必填) -``` - -取消指定范围内所有合并的单元格,恢复为独立单元格。 - -### 设置下拉列表 -``` -Usage: - dws sheet set-dropdown [flags] -Example: - # 设置单选下拉列表 - dws sheet set-dropdown --node --sheet-id --range "A2:A100" \ - --options '[{"value":"选项1"},{"value":"选项2"},{"value":"选项3"}]' - - # 设置带颜色的多选下拉列表 - dws sheet set-dropdown --node --sheet-id --range "B2:B50" \ - --options '[{"value":"高","color":"#ff0000"},{"value":"中","color":"#ffaa00"},{"value":"低","color":"#00ff00"}]' \ - --multi-select -Flags: - --node string 表格文档 ID 或 URL (必填) - --sheet-id string 工作表 ID 或名称 (必填) - --range string 目标单元格范围,A1 表示法,如 A2:A100 (必填) - --options string 下拉选项 JSON 数组 (必填),如 '[{"value":"选项1","color":"#ff0000"}]' - --multi-select 是否允许多选(默认单选) -``` - -在指定单元格范围内设置下拉列表。设置后用户可从预定义选项中选择值。 -- **用途**:为单元格配置下拉列表,支持自定义选项颜色和多选。 -- **场景**:规范数据输入,如状态选择(完成/进行中/待处理)、优先级(高/中/低)等。 -- **注意**:选项值不能包含英文逗号;如果目标范围已存在下拉列表,会被新配置覆盖。 - -### 获取下拉列表配置 -``` -Usage: - dws sheet get-dropdown [flags] -Example: - dws sheet get-dropdown --node --sheet-id --range "A2:A100" - dws sheet get-dropdown --node --sheet-id --range "A1" -Flags: - --node string 表格文档 ID 或 URL (必填) - --sheet-id string 工作表 ID 或名称 (必填) - --range string 查询范围,A1 表示法,如 A1:A100 (必填) -``` - -查询指定范围内的下拉列表配置信息,包括选项值、颜色和是否多选。 -- **用途**:查看单元格已设置的下拉列表选项和配置。 -- **场景**:在修改下拉列表前先查询现有配置;确认下拉列表是否设置成功。 -- **返回**:`dataValidations` 数组,相同选项的单元格聚合为一组,每组包含 `conditionValues`(选项值)、`ranges`(覆盖范围)、`options`(含 `multipleValues` 和 `colorValueMap`)。**判空以 `hasDropdown` 为准**:无下拉列表时 `hasDropdown` 为 false,但 `dataValidations` 里仍会带 1 个全 null 的幽灵条目,不要用数组长度判空。 -- **已知限制**:`options.multipleValues` 服务端恒返回 `null`(即使该下拉是用 `--multi-select` 建的),且不返回 `enableMultiSelect` 字段——**`get-dropdown` 读回无法判断下拉是否多选**。要判断是否多选,改用 `range read`:其 `cells[].dataValidation.enableMultiSelect` 字段准确(实测有效)。 - -### 删除下拉列表 -``` -Usage: - dws sheet delete-dropdown [flags] -Example: - dws sheet delete-dropdown --node --sheet-id --range "A2:A100" - dws sheet delete-dropdown --node --sheet-id --range "B1:D10" -Flags: - --node string 表格文档 ID 或 URL (必填) - --sheet-id string 工作表 ID 或名称 (必填) - --range string 要删除下拉列表的范围,A1 表示法 (必填) -``` - -删除指定范围内的下拉列表配置,单元格恢复为普通文本格式。 -- **用途**:移除不再需要的下拉列表约束。 -- **注意**:已填写的单元格值不会被清除;目标范围不存在下拉列表时操作仍返回成功。 - -### 获取筛选信息 -``` -Usage: - dws sheet filter get [flags] -Example: - dws sheet filter get --node --sheet-id - dws sheet filter get --node "https://alidocs.dingtalk.com/i/nodes/" --sheet-id "Sheet1" -Flags: - --node string 表格文档 ID 或 URL (必填) - --sheet-id string 工作表 ID 或名称 (必填) -``` - -获取指定工作表的全局筛选信息,返回筛选范围和各列的筛选条件详情。 -- **用途**:查看当前工作表上是否存在全局筛选及其配置。 -- **场景**:在修改或删除筛选前,先读取当前筛选配置;创建筛选前先确认是否已存在(每个工作表只能有一个筛选)。 -- **区分**:全局筛选(filter)影响所有协作者看到的数据展示;筛选视图(filter-view)是个人化的。 -- **返回**:`range`(筛选范围,A1 表示法)、`id`(筛选 ID)和 `criteria`(各列条件对象,key 为列偏移量;无条件时为 `{}`)。如果未设置筛选,返回筛选信息为空。 - -### 创建筛选 -``` -Usage: - dws sheet filter create [flags] -Example: - # 创建筛选框架(不设条件) - dws sheet filter create --node --sheet-id --range "A1:E100" - - # 创建筛选并同时设置条件(按值筛选) - dws sheet filter create --node --sheet-id --range "A1:E100" --criteria '[{"column":1,"filterType":"values","visibleValues":["北京","上海"]}]' - - # 创建筛选并设置条件筛选 - dws sheet filter create --node --sheet-id --range "A1:E100" --criteria '[{"column":2,"filterType":"condition","conditions":[{"operator":"greater","value":"100"}]}]' -Flags: - --node string 表格文档 ID 或 URL (必填) - --sheet-id string 工作表 ID 或名称 (必填) - --range string 筛选范围,A1 表示法,须包含表头行 (必填) - --criteria string 筛选条件 JSON 数组 (可选) -``` - -在工作表中创建全局筛选。 -- **用途**:为工作表建立筛选器,使数据可按条件过滤展示。 -- **约束**:每个工作表只能有一个全局筛选,已存在时会报错。应先 `filter get` 确认不存在后再创建。 -- **range 规范**:必须包含表头行(如 `A1:E100`),不能只包含数据行。 -- **criteria 格式**:JSON 数组,每个元素含 `column`(列偏移量,从 0 开始)和筛选条件字段。不传则仅创建空筛选框架,后续可通过 `filter update` 设置条件。 - -### 删除筛选 - -> **CAUTION:** 不可逆操作 — 执行前必须向用户确认。 - -``` -Usage: - dws sheet filter delete [flags] -Example: - dws sheet filter delete --node --sheet-id --yes -Flags: - --node string 表格文档 ID 或 URL (必填) - --sheet-id string 工作表 ID 或名称 (必填) -``` - -删除工作表的全局筛选。 -- **用途**:移除筛选器,所有被隐藏的行将重新显示。 -- **不可逆**:删除后所有筛选条件丢失,需重新创建。 -- **前置**:工作表没有筛选时调用会报错,应先 `filter get` 确认存在。 - -### 批量更新筛选条件 -``` -Usage: - dws sheet filter update [flags] -Example: - # 同时设置多列的筛选条件 - dws sheet filter update --node --sheet-id --criteria '[{"column":0,"filterType":"values","visibleValues":["已完成","进行中"]},{"column":2,"filterType":"condition","conditions":[{"operator":"greater","value":"50"}]}]' - - # 按颜色筛选 - dws sheet filter update --node --sheet-id --criteria '[{"column":1,"filterType":"color","backgroundColor":"#FF0000"}]' -Flags: - --node string 表格文档 ID 或 URL (必填) - --sheet-id string 工作表 ID 或名称 (必填) - --criteria string 筛选条件 JSON 数组 (必填) -``` - -批量更新筛选条件,可同时设置多列的筛选条件。 -- **用途**:一次性设置或替换多列的筛选条件。 -- **前置**:工作表必须已创建筛选(通过 `filter create`)。 -- **覆盖式**:指定列的条件会被替换,未指定的列保持不变。如只想修改某一列,建议先 `filter get` 读取现有配置。 -- **criteria 格式**:JSON 数组,支持三种 `filterType`: - - `values`:按值筛选,指定 `visibleValues` 数组 - - `condition`:按条件筛选,指定 `conditions` 数组(最多 2 个)和可选的 `conditionOperator`(`and`/`or`) - - `color`:按颜色筛选,指定 `backgroundColor` 或 `fontColor`(二选一) - -### 清除单列筛选条件 -``` -Usage: - dws sheet filter clear-criteria [flags] -Example: - # 清除第 2 列(B 列)的筛选条件 - dws sheet filter clear-criteria --node --sheet-id --column 1 - - # 清除第 1 列(A 列)的筛选条件 - dws sheet filter clear-criteria --node --sheet-id --column 0 -Flags: - --node string 表格文档 ID 或 URL (必填) - --sheet-id string 工作表 ID 或名称 (必填) - --column number 列偏移量,从 0 开始 (必填) -``` - -清除筛选中某一列的筛选条件。 -- **用途**:移除某列的筛选条件,该列不再参与筛选计算。 -- **区分**:仅清除指定列的条件,不删除整个筛选。如需删除整个筛选,使用 `filter delete`。 -- **幂等**:指定列没有设置筛选条件时调用不会报错。 - -### 筛选排序 -``` -Usage: - dws sheet filter sort [flags] -Example: - # 按第 1 列(A 列)升序排序 - dws sheet filter sort --node --sheet-id --column 0 --ascending - - # 按第 3 列(C 列)降序排序 - dws sheet filter sort --node --sheet-id --column 2 --ascending=false -Flags: - --node string 表格文档 ID 或 URL (必填) - --sheet-id string 工作表 ID 或名称 (必填) - --column number 排序列偏移量,从 0 开始 (必填) - --ascending 是否升序,默认 true (可选) -``` - -对筛选范围内的数据按指定列排序。 -- **用途**:对数据行按某一列的值进行升序或降序排列。 -- **前置**:工作表必须已创建筛选(通过 `filter create`)。 -- **注意**:排序会实际改变工作表中数据行的物理顺序,不可撤销。 -- **column**:列偏移量从 0 开始,相对于筛选范围首列。 - -### 获取所有筛选视图 -``` -Usage: - dws sheet filter-view list [flags] -Example: - dws sheet filter-view list --node --sheet-id - dws sheet filter-view list --node "https://alidocs.dingtalk.com/i/nodes/" --sheet-id "Sheet1" -Flags: - --node string 表格文档 ID 或 URL (必填) - --sheet-id string 工作表 ID 或名称 (必填) -``` - -获取指定工作表的所有筛选视图列表,返回每个筛选视图的 ID、名称和范围信息。 -- **用途**:查看当前工作表上已创建的所有筛选视图,获取视图 ID、名称和范围。 -- **场景**:在对筛选视图进行 update / delete / update-criteria 等操作前,先用 list 获取可用的 filterViewId。 -- **区分**:筛选视图(filter-view)是个人化的数据过滤方式,与全局筛选不同。每个用户可以创建自己的筛选视图,互不影响原始数据。如果没有筛选视图,返回空列表。 - -### 创建筛选视图 -``` -Usage: - dws sheet filter-view create [flags] -Example: - # 创建不带筛选条件的筛选视图 - dws sheet filter-view create --node --sheet-id --name "我的视图" --range "A1:E10" - - # 创建带按值筛选条件的筛选视图 - dws sheet filter-view create --node --sheet-id --name "销售筛选" --range "A1:E10" \ - --criteria '[{"column":0,"filterType":"values","visibleValues":["销售部"]}]' - - # 创建带按条件筛选的筛选视图(大于等于 200000) - dws sheet filter-view create --node --sheet-id --name "高预算" --range "A1:C10" \ - --criteria '[{"column":1,"filterType":"condition","conditions":[{"operator":"greater-equal","value":"200000"}]}]' -Flags: - --node string 表格文档 ID 或 URL (必填) - --sheet-id string 工作表 ID 或名称 (必填) - --name string 筛选视图名称 (必填) - --range string 筛选视图范围,A1 表示法,如 A1:E10 (必填) - --criteria string 筛选条件,JSON 数组 (可选) -``` - -在指定工作表中创建一个筛选视图。 -- **用途**:为指定数据区域创建一个可命名的个人化筛选视图,可选同时设置筛选条件。 -- **场景**:用户需要针对某个数据区域建立固定的筛选视角(如"高绩效员工""研发部数据"),方便反复查看。 -- **区分**:与全局筛选不同,筛选视图是个人化的,不影响其他用户看到的数据。如果只需创建视图不设条件,后续可通过 `update-criteria` 单独设置;如果要一步到位,可通过 `--criteria` 在创建时直接设置。 -`--criteria` 为 JSON 数组,每个元素包含 `column`(列偏移量,从 0 开始)和筛选条件字段。支持三种筛选类型: -- `values`:按值筛选,通过 `visibleValues` 指定允许显示的值列表 -- `condition`:按条件筛选,通过 `conditions` 指定条件列表(最多 2 个),每个条件包含 `operator` 和 `value`。支持的操作符(kebab-case):`equal`、`not-equal`、`contains`、`not-contains`、`starts-with`、`not-starts-with`、`ends-with`、`not-ends-with`、`greater`、`greater-equal`、`less`、`less-equal`。多条件之间通过 `conditionOperator` 指定逻辑关系:`and`(且,默认)或 `or`(或) -- `color`:按颜色筛选,通过 `backgroundColor` 或 `fontColor` 指定颜色值(十六进制,如 `#FF0000`),二选一 - -### 更新筛选视图属性 -``` -Usage: - dws sheet filter-view update [flags] -Example: - # 更新筛选视图名称 - dws sheet filter-view update --node --sheet-id --filter-view-id --name "新名称" - - # 更新筛选视图范围 - dws sheet filter-view update --node --sheet-id --filter-view-id --range "A1:F20" - - # 更新筛选条件 - dws sheet filter-view update --node --sheet-id --filter-view-id \ - --criteria '[{"column":1,"filterType":"condition","conditions":[{"operator":"greater","value":"100"}]}]' -Flags: - --node string 表格文档 ID 或 URL (必填) - --sheet-id string 工作表 ID 或名称 (必填) - --filter-view-id string 筛选视图 ID (必填) - --name string 筛选视图新名称 - --range string 筛选视图新范围,A1 表示法 - --criteria string 筛选条件,JSON 数组 -``` - -更新筛选视图的名称、范围和/或筛选条件,`--name`、`--range`、`--criteria` 至少传入一个。 -- **用途**:修改已有筛选视图的名称、数据范围或筛选条件。 -- **场景**:数据区域扩展后需要扩大筛选视图范围,或重命名视图,或通过 `--criteria` 一次性批量更新多列筛选条件。 -- **区分**:`update` 可同时修改名称、范围和条件,适合批量更新;`update-criteria` 只能设置单列条件,适合精确控制某一列的筛选逻辑。`--criteria` 指定列的条件会被替换,未指定的列保持不变。 - -`--criteria` 为 JSON 数组,格式与 `filter-view create` 的 `--criteria` 相同,支持的筛选类型和操作符参见「创建筛选视图」说明。 - -### 删除筛选视图 - -> **CAUTION:** 不可逆操作 — 执行前必须向用户确认。 - -``` -Usage: - dws sheet filter-view delete [flags] -Example: - dws sheet filter-view delete --node --sheet-id --filter-view-id -Flags: - --node string 表格文档 ID 或 URL (必填) - --sheet-id string 工作表 ID 或名称 (必填) - --filter-view-id string 筛选视图 ID (必填) -``` - -删除指定的筛选视图。 -- **用途**:永久删除一个不再需要的筛选视图及其所有筛选条件。 -- **场景**:筛选视图已过时或不再需要时,清理无用的视图。 -- **区分**:`delete` 删除整个筛选视图(包括所有列的条件),操作不可恢复;`delete-criteria` 只删除某一列的筛选条件,视图本身保留。此操作不影响全局筛选或其他筛选视图,也不影响原始数据。 - -### 更新筛选视图列条件 -``` -Usage: - dws sheet filter-view update-criteria [flags] -Example: - # 按值筛选:只显示"销售部"和"市场部" - dws sheet filter-view update-criteria --node --sheet-id --filter-view-id \ - --column 0 --filter-criteria '{"filterType":"values","visibleValues":["销售部","市场部"]}' - - # 按条件筛选:大于 100 - dws sheet filter-view update-criteria --node --sheet-id --filter-view-id \ - --column 2 --filter-criteria '{"filterType":"condition","conditions":[{"operator":"greater","value":"100"}]}' - - # 按条件筛选:大于等于 200000 - dws sheet filter-view update-criteria --node --sheet-id --filter-view-id \ - --column 1 --filter-criteria '{"filterType":"condition","conditions":[{"operator":"greater-equal","value":"200000"}]}' - - # 按条件筛选:小于 100 - dws sheet filter-view update-criteria --node --sheet-id --filter-view-id \ - --column 1 --filter-criteria '{"filterType":"condition","conditions":[{"operator":"less","value":"100"}]}' - - # 多条件筛选:大于等于 60 且 小于等于 90 - dws sheet filter-view update-criteria --node --sheet-id --filter-view-id \ - --column 2 --filter-criteria '{"filterType":"condition","conditionOperator":"and","conditions":[{"operator":"greater-equal","value":"60"},{"operator":"less-equal","value":"90"}]}' - - # 按颜色筛选:背景色为红色 - dws sheet filter-view update-criteria --node --sheet-id --filter-view-id \ - --column 1 --filter-criteria '{"filterType":"color","backgroundColor":"#FF0000"}' -Flags: - --node string 表格文档 ID 或 URL (必填) - --sheet-id string 工作表 ID 或名称 (必填) - --filter-view-id string 筛选视图 ID (必填) - --column int 列偏移量,从 0 开始 (必填) - --filter-criteria string 筛选条件,JSON 对象 (必填) -``` - -更新筛选视图中某一列的筛选条件。 -- **用途**:为筛选视图的指定列创建或更新筛选条件,控制该列哪些数据行可见。 -- **场景**:只显示某些特定值的行(如"只看研发部")→ `filterType: values`;按数值条件筛选(如"绩效 ≥ 85")→ `filterType: condition` + `operator: greater-equal`;按文本条件筛选(如"名称包含关键字")→ `filterType: condition` + `operator: contains`。 -- **区分**:`update-criteria` 精确控制单列条件,适合逐列设置不同的筛选逻辑;`filter-view update --criteria` 可以批量更新多列条件;`delete-criteria` 是 `update-criteria` 的逆操作,删除指定列的条件。 - -`--column` 为列偏移量(从 0 开始),相对于筛选视图范围首列。 -例如筛选视图范围为 `B1:E10`,则 `--column 0` 代表 B 列,`--column 1` 代表 C 列。 - -`--filter-criteria` 为 JSON 对象,支持三种筛选类型: -- `values`:按值筛选,通过 `visibleValues` 指定允许显示的值列表 -- `condition`:按条件筛选,通过 `conditions` 指定条件列表(最多 2 个),每个条件包含 `operator` 和 `value`。支持的操作符:`equal`、`not-equal`、`contains`、`not-contains`、`starts-with`、`not-starts-with`、`ends-with`、`not-ends-with`、`greater`、`greater-equal`、`less`、`less-equal`。多条件之间通过 `conditionOperator` 指定逻辑关系:`and`(且,默认)或 `or`(或) -- `color`:按颜色筛选,通过 `backgroundColor` 或 `fontColor` 指定颜色值(十六进制,如 `#FF0000`),二选一 - -### 删除筛选视图列条件 -``` -Usage: - dws sheet filter-view delete-criteria [flags] -Example: - # 删除第 1 列(A 列)的筛选条件 - dws sheet filter-view delete-criteria --node --sheet-id --filter-view-id --column 0 - - # 删除第 3 列(C 列)的筛选条件 - dws sheet filter-view delete-criteria --node --sheet-id --filter-view-id --column 2 -Flags: - --node string 表格文档 ID 或 URL (必填) - --sheet-id string 工作表 ID 或名称 (必填) - --filter-view-id string 筛选视图 ID (必填) - --column int 列偏移量,从 0 开始 (必填) -``` - -清除筛选视图中指定列的筛选条件。 -- **用途**:移除筛选视图中指定列的筛选条件,使该列不再参与过滤。 -- **场景**:之前通过 `update-criteria` 设置了某列的筛选条件,现在需要取消该列的筛选以显示全部数据。 -- **区分**:`delete-criteria` 只清除指定列的条件,筛选视图本身和其他列的条件保持不变;`delete` 会删除整个筛选视图。如果指定列没有设置筛选条件,调用此命令不会报错(幂等操作)。 - -### 获取单个筛选视图详情 -``` -Usage: - dws sheet filter-view info [flags] -Example: - # 查看指定筛选视图的详情 - dws sheet filter-view info --node --sheet-id --filter-view-id -Flags: - --node string 表格文档 ID 或 URL (必填) - --sheet-id string 工作表 ID 或名称 (必填) - --filter-view-id string 筛选视图 ID (必填) -``` - -获取指定筛选视图的完整信息,包括 ID、名称、范围和筛选条件。 -- **用途**:查看某个筛选视图的当前配置,包括已设置的所有筛选条件详情。 -- **场景**:在修改或删除筛选视图前,先确认其当前状态;或在 `update-criteria` 后验证条件是否生效。 -- **区分**:`info` 返回单个视图的完整信息(含 criteria);`list` 返回所有视图的列表概要。`info` 需要指定 `--filter-view-id`,ID 可通过 `list` 获取。 -- **实现**:内部调用 `get_filter_views` 获取全部列表后按 ID 过滤。 - -### 列出筛选视图所有列条件 -``` -Usage: - dws sheet filter-view list-criteria [flags] -Example: - # 列出筛选视图的所有条件 - dws sheet filter-view list-criteria --node --sheet-id --filter-view-id -Flags: - --node string 表格文档 ID 或 URL (必填) - --sheet-id string 工作表 ID 或名称 (必填) - --filter-view-id string 筛选视图 ID (必填) -``` - -列出指定筛选视图中已设置的所有列筛选条件。 -- **用途**:查看某个筛选视图当前设置了哪些列的筛选条件,包括每列的条件类型和具体规则。 -- **场景**:在管理筛选条件(修改/删除特定列条件)前,先了解当前视图有哪些条件;或排查筛选结果不符合预期时检查条件配置。 -- **区分**:`list-criteria` 返回所有列的条件(按列偏移量为 key 的对象);`get-criteria` 只返回指定列的条件。如果没有设置任何条件,返回空对象 `{}`。 -- **实现**:内部调用 `get_filter_views` 获取视图详情后提取 `criteria` 字段。 - -### 获取单列筛选条件 -``` -Usage: - dws sheet filter-view get-criteria [flags] -Example: - # 查看第 1 列(偏移量 0)的筛选条件 - dws sheet filter-view get-criteria --node --sheet-id --filter-view-id --column 0 - - # 查看第 3 列(偏移量 2)的筛选条件 - dws sheet filter-view get-criteria --node --sheet-id --filter-view-id --column 2 -Flags: - --node string 表格文档 ID 或 URL (必填) - --sheet-id string 工作表 ID 或名称 (必填) - --filter-view-id string 筛选视图 ID (必填) - --column int 列偏移量,从 0 开始 (必填) -``` - -获取指定筛选视图中某一列的筛选条件详情。 -- **用途**:查看某个筛选视图中指定列当前设置的筛选条件,包括条件类型、运算符和比较值。 -- **场景**:在修改某列条件前,先查看其当前配置;或验证 `update-criteria` 后该列条件是否正确。 -- **区分**:`get-criteria` 只返回指定列的条件;`list-criteria` 返回所有列的条件。`--column` 为列偏移量(从 0 开始),相对于筛选视图范围首列。 -- **实现**:内部调用 `get_filter_views` 获取视图详情后按列偏移量过滤 `criteria` 中的对应条件。 - -### 导出表格为 xlsx(异步任务一站式) -``` -Usage: - dws sheet export [flags] # 一站式:提交 → 轮询 → 可选下载 -Example: - # 仅导出,返回 downloadUrl(链接有时效性,请尽快下载) - dws sheet export --node - dws sheet export --node "https://alidocs.dingtalk.com/i/nodes/" - - # 导出并自动下载为本地文件 - dws sheet export --node --output ./report.xlsx - - # --output 为目录时,自动按下载链接中的文件名保存 - dws sheet export --node --output ./ - -Flags: - --node string 表格文档 ID 或 URL (必填) - --output string 本地保存路径(可选,支持文件路径或目录) -``` - -将钉钉在线电子表格导出为 Office xlsx 格式。**单命令一站式**:命令内部自动完成「提交任务 → 渐进式退避轮询 → (可选)下载文件」全流程,AI Agent 无需自行拆分步骤或实现轮询。 - -**内部流程**: -1. 调 `submit_export_job` 获取 `jobId` -2. 按渐进式退避策略轮询 `query_export_job` 直至任务终态或超时 -3. 任务成功后取得 `downloadUrl`;若指定了 `--output`,自动 HTTP GET 下载 xlsx 到本地文件 - -**内置轮询策略(CLI 内实现,无需关心)**: -- 第 1~5 次:每次间隔 2 秒 -- 第 6~10 次:每次间隔 5 秒 -- 第 11~20 次:每次间隔 10 秒 -- 第 21~30 次:每次间隔 15 秒 -- **硬上限:最多轮询 30 次(约 5 分钟)**,超时后命令返回错误 - -**命令返回**(`--format json`,默认):输出规整 JSON `{success, jobId, downloadUrl[, outputPath]}`。轮询进度以 `[INFO] [N/30] 状态: ...` 打到 stderr,不污染 stdout 的 JSON。 -- `--output` 未指定:JSON 含 `jobId` + `downloadUrl`(链接有时效性,请尽快下载) -- `--output` 指定为文件路径:下载到该路径,JSON 额外含 `outputPath` -- `--output` 指定为已存在目录:自动从 `downloadUrl` 推断文件名并保存到该目录,JSON 含 `outputPath` - -**失败处理(命令内部已处理,Agent 仅需转述)**: -- MCP 返回 `FAILED`:命令立即返回错误并附带失败原因,**禁止自动重试 `dws sheet export`**,告知用户稍后再试 -- 轮询 30 次仍 `PROCESSING`:命令返回超时错误,告知用户稍后再试 - -**限制**:仅支持钉钉在线电子表格(axls)→ xlsx。导出钉钉文字文档请使用 `doc` 产品对应的导出工具。 - -### 表格模板(list / search / apply) -``` -Usage: - dws sheet template list [flags] # 列出可用模板(--cursor / --limit 分页) - dws sheet template search [flags] # 按关键词搜索模板 - dws sheet template apply [flags] # 应用模板创建新表格文档 - -template search Flags: - --query string 搜索关键词 (必填) - --source string 模板来源: MY(我的模版,默认) / PUBLIC(公开模版) - --cursor string 分页游标 - --limit int 返回数量上限 - -template apply Flags: - --template-id string 模板 ID (必填,来自 list/search) - --name string 新表格文档名称 (可选) - --folder string 目标文件夹 ID (可选,UUID 或 URL) - --workspace string 知识库 ID (可选) -``` - -- 用户说"用模板建表 / 有哪些模板 / 找个 XX 模板"时走 template 系列 -- 典型链路:`template search --query "..."` 或 `template list` 拿到 `templateId` → `template apply --template-id --name "..."` 创建新表格,返回新文档的 `nodeId` - -## 核心工作流 - -```bash -# ── 工作流 1: 创建表格并写入数据 ── - -# 1. 创建表格文档 — 提取 nodeId -dws sheet create --name "销售数据" --format json - -# 2. 查看工作表列表 — 提取 sheetId -dws sheet list --node --format json - -# 3. 写入表头和数据(每个单元格必须是 object;数字也写成字符串) -dws sheet range update --node --sheet-id --range "A1:C1" \ - --values '[[{"type":"text","text":"姓名"},{"type":"text","text":"部门"},{"type":"text","text":"销售额"}]]' --format json - -dws sheet range update --node --sheet-id --range "A2:C4" \ - --values '[[{"type":"text","text":"张三"},{"type":"text","text":"销售部"},{"type":"text","text":"50000"}],[{"type":"text","text":"李四"},{"type":"text","text":"市场部"},{"type":"text","text":"38000"}],[{"type":"text","text":"王五"},{"type":"text","text":"销售部"},{"type":"text","text":"62000"}]]' --format json - -# ── 工作流 2: 读取已有表格数据 ── - -# 1. 获取工作表列表 -dws sheet list --node --format json - -# 2. 查看工作表详情(行列数、最后非空位置等) -dws sheet info --node --sheet-id --format json - -# 3. 读取全部数据 -dws sheet range read --node --sheet-id --format json - -# 4. 读取指定区域 -dws sheet range read --node --sheet-id --range "A1:D10" --format json - -# ── 工作流 3: 多工作表管理 ── - -# 1. 新建工作表 -dws sheet new --node --name "汇总" --format json - -# 2. 在新工作表中写入汇总公式(公式写在 text,以 = 开头) -dws sheet range update --node --sheet-id --range "A1:B1" \ - --values '[[{"type":"text","text":"指标"},{"type":"text","text":"数值"}]]' --format json - -dws sheet range update --node --sheet-id --range "A2:B2" \ - --values '[[{"type":"text","text":"总销售额"},{"type":"text","text":"=SUM(Sheet1!C2:C100)"}]]' --format json - -# ── 工作流 4: 写入数据并设置样式 ── - -# 1. 写入数据 -dws sheet range update --node --sheet-id --range "A1:C3" \ - --values '[[{"type":"text","text":"商品"},{"type":"text","text":"单价"},{"type":"text","text":"数量"}],[{"type":"text","text":"苹果"},{"type":"text","text":"5.5"},{"type":"text","text":"100"}],[{"type":"text","text":"香蕉"},{"type":"text","text":"3.2"},{"type":"text","text":"200"}]]' --format json - -# 2. 设置数字格式(人民币)——批量刷整列走 set-style -dws sheet range set-style --node --sheet-id --range "B2:B3" \ - --number-format "¥#,##0.00" --format json - -# 3. 写入超链接(写在 cell 的 hyperlink 字段) -dws sheet range update --node --sheet-id --range "D1" \ - --values '[[{"type":"text","text":"详情","hyperlink":{"type":"path","link":"https://dingtalk.com"}}]]' --format json - -# ── 工作流 5: 追加数据 ── - -# 1. 获取工作表列表 -dws sheet list --node --format json - -# 2. 查看工作表详情(确认列结构) -dws sheet info --node --sheet-id --format json - -# 3. 追加单行数据 -dws sheet append --node --sheet-id \ - --values '[["张三","销售部",50000]]' --format json - -# 4. 追加多行数据 -dws sheet append --node --sheet-id \ - --values '[["李四","市场部",38000],["王五","销售部",62000]]' --format json -``` - -```bash -# ── 工作流 6: 插入行或列 ── - -# 1. 获取工作表列表 -dws sheet list --node --format json - -# 2. 在第 3 行之前插入 2 行 -dws sheet insert-dimension --node --sheet-id \ - --dimension ROWS --position "3" --length 2 --format json - -# 3. 在 A 列之前插入 1 列 -dws sheet insert-dimension --node --sheet-id \ - --dimension COLUMNS --position "A" --length 1 --format json - -# 4. 使用工作表前缀指定位置 -dws sheet insert-dimension --node --sheet-id \ - --dimension ROWS --position "Sheet1!5" --length 3 --format json -``` - -```bash -# ── 工作流 6b: 删除行或列 ── - -# 1. 获取工作表列表 -dws sheet list --node --format json - -# 2. 从第 3 行开始删除 2 行 -dws sheet delete-dimension --node --sheet-id \ - --dimension ROWS --position "3" --length 2 --format json - -# 3. 从 A 列开始删除 1 列 -dws sheet delete-dimension --node --sheet-id \ - --dimension COLUMNS --position "A" --length 1 --format json - -# 4. 使用工作表前缀指定位置 -dws sheet delete-dimension --node --sheet-id \ - --dimension ROWS --position "Sheet1!5" --length 3 --format json -``` - -```bash -# ── 工作流 6c: 更新行/列属性(显隐、行高/列宽) ── - -# 1. 获取工作表列表 -dws sheet list --node --format json - -# 2. 隐藏第 3~4 行 -dws sheet update-dimension --node --sheet-id \ - --dimension ROWS --start-index "3" --length 2 --hidden --format json - -# 3. 显示 A~B 列 -dws sheet update-dimension --node --sheet-id \ - --dimension COLUMNS --start-index "A" --length 2 --hidden=false --format json - -# 4. 设置第 1~5 行行高为 40px -dws sheet update-dimension --node --sheet-id \ - --dimension ROWS --start-index "1" --length 5 --pixel-size 40 --format json - -# 5. 设置 C 列列宽为 200px 并隐藏 -dws sheet update-dimension --node --sheet-id \ - --dimension COLUMNS --start-index "C" --length 1 --pixel-size 200 --hidden --format json -``` - -```bash -# ── 工作流 7: 搜索表格数据 ── - -# 1. 获取工作表列表 -dws sheet list --node --format json - -# 2. 基本搜索 — 在指定工作表中查找文本 -dws sheet find --node --sheet-id --find "销售额" --format json - -# 3. 在指定范围内搜索 -dws sheet find --node --sheet-id --find "合计" --range "A1:D100" --format json - -# 4. 正则搜索(不区分大小写) -dws sheet find --node --sheet-id --find "^total" --use-regexp --match-case=false --format json - -# 5. 精确匹配整个单元格 -dws sheet find --node --sheet-id --find "完成" --match-entire-cell --format json - -# 6. 搜索公式文本 -dws sheet find --node --sheet-id --find "SUM" --match-formula --format json -``` - -```bash -# ── 工作流 8: 合并单元格 ── - -# 1. 获取工作表列表 -dws sheet list --node --format json - -# 2. 合并所有单元格(默认 mergeAll) -dws sheet merge-cells --node --sheet-id --range "A1:B3" --format json - -# 3. 按行合并 -dws sheet merge-cells --node --sheet-id --range "A1:C3" --merge-type mergeRows --format json - -# 4. 按列合并 -dws sheet merge-cells --node --sheet-id --range "A1:C3" --merge-type mergeColumns --format json -``` - -```bash -# ── 工作流 9: 上传附件到表格 ── - -# 1. 基本用法: 上传本地文件到表格 -dws sheet media-upload --node --file ./report.pdf -f json - -# 2. 自定义附件显示名称 (--name 指定上传后在表格中显示的名称) -dws sheet media-upload --node --file ./data.csv --name "销售数据.csv" -f json - -# 3. 指定 MIME 类型 (文件扩展名无法推断时) -dws sheet media-upload --node --file ./data.bin --name "导出数据.dat" --mime-type application/octet-stream -f json - -# 4. 完整流程: 创建表格 → 上传附件 -dws sheet create --name "项目资料" -f json -# 提取 nodeId 后: -dws sheet media-upload --node --file ./design.pdf -f json -dws sheet media-upload --node --file ./timeline.xlsx --name "项目时间线.xlsx" -f json - -# ── 工作流 10: 写入图片到表格单元格 ── - -# 1. 基本用法: 写入图片到指定单元格 -dws sheet write-image --node --sheet-id --range A1:A1 --file ./chart.png -f json - -# 2. 指定显示尺寸 -dws sheet write-image --node --sheet-id --range B2:B2 --file ./logo.png --width 200 --height 100 -f json - -# 3. 自定义图片名称 -dws sheet write-image --node --sheet-id --range C3:C3 --file ./photo.jpg --name "产品图.jpg" -f json - -# 4. 完整流程: 创建表格 → 写表头 → 写入图片 -dws sheet create --name "产品目录" -f json -# 提取 nodeId 后: -dws sheet range update --node --sheet-id Sheet1 --range "A1:B1" --values '[[{"type":"text","text":"产品名称"},{"type":"text","text":"产品图片"}]]' -f json -dws sheet range update --node --sheet-id Sheet1 --range "A2:A2" --values '[[{"type":"text","text":"MacBook Pro"}]]' -f json -dws sheet write-image --node --sheet-id Sheet1 --range B2:B2 --file ./macbook.png --width 150 --height 100 -f json -``` - -```bash -# ── 工作流 11: 筛选视图管理 ── - -# 1. 获取工作表列表 -dws sheet list --node -f json - -# 2. 查看已有筛选视图 -dws sheet filter-view list --node --sheet-id -f json - -# 3. 创建筛选视图(不带条件) -dws sheet filter-view create --node --sheet-id \ - --name "我的筛选" --range "A1:E100" -f json - -# 4. 为筛选视图设置列条件(按值筛选) -dws sheet filter-view update-criteria --node --sheet-id --filter-view-id \ - --column 0 --filter-criteria '{"filterType":"values","visibleValues":["销售部","市场部"]}' -f json - -# 5. 为筛选视图设置列条件(按条件筛选) -dws sheet filter-view update-criteria --node --sheet-id --filter-view-id \ - --column 2 --filter-criteria '{"filterType":"condition","conditions":[{"operator":"greater","value":"100"}]}' -f json - -# 6. 更新筛选视图名称和范围 -dws sheet filter-view update --node --sheet-id --filter-view-id \ - --name "销售数据筛选" --range "A1:F200" -f json - -# 7. 清除某列的筛选条件 -dws sheet filter-view delete-criteria --node --sheet-id --filter-view-id \ - --column 0 -f json - -# 8. 删除筛选视图 -dws sheet filter-view delete --node --sheet-id --filter-view-id -f json -``` - -```bash -# ── 工作流 11b: 创建带条件的筛选视图(一步完成) ── - -# 创建筛选视图时直接指定筛选条件 -dws sheet filter-view create --node --sheet-id \ - --name "高销售额视图" --range "A1:E100" \ - --criteria '[{"column":0,"filterType":"values","visibleValues":["销售部"]},{"column":2,"filterType":"condition","conditions":[{"operator":"greater","value":"50000"}]}]' \ - -f json -``` - -```bash -# ── 工作流 12: 导出表格为 xlsx(单命令一站式)── - -# 场景 A:仅获取下载链接(命令内部自动完成提交+轮询,最终返回 downloadUrl) -dws sheet export --node --format json -# 传入 URL 也可: -# dws sheet export --node "https://alidocs.dingtalk.com/i/nodes/" --format json - -# 场景 B:导出并自动下载为本地文件 -dws sheet export --node --output ./report.xlsx - -# 场景 C:下载到目录,自动按链接推断文件名 -dws sheet export --node --output ./ - -# 禁止在 Agent 侧实现任何轮询或重试,CLI 内部已按 2s/5s/10s/15s 渐进式退避自动完成(最多 30 次)。 -# 若命令返回失败或超时,直接告知用户稍后再试,不要自动重调 dws sheet export。 -``` - -## 上下文传递表 - -| 操作 | 从返回中提取 | 用于 | -|------|-------------|------| -| `create` | `nodeId` | list / info / new / range read / range update / find 的 --node | -| `list` | 工作表的 `sheetId` | info / range read / range update / find 的 --sheet-id | -| `new` | 新工作表的 `sheetId` | range read / range update / find 的 --sheet-id | -| `info` | `rowCount` / `nonEmptyRange.range` / `nonEmptyRange.lastRow` / `nonEmptyRange.lastColumn` / `mergedRanges` | 确定数据范围、追加写入起始行、判断合并结构(空表时 nonEmptyRange.* 为 null) | -| `find` | `matchedCells` 中的 `a1Notation` | 定位目标单元格,用于 range read / range update | -| `append` | `a1Notation` 追加数据所在范围 | 确认追加位置 | -| `csv-put` | `a1Notation` 实际写入的单元格范围 | 确认写入位置和范围 | -| `insert-dimension` | `a1Notation` 新插入区域范围 | 确认插入位置和范围 | -| `delete-dimension` | `a1Notation` 被删除区域范围 | 确认删除位置和范围 | -| `update-dimension` | `a1Notation` 被更新区域范围、`hidden` 生效的显隐状态、`pixelSize` 生效的尺寸 | 确认更新结果 | -| `merge-cells` | `a1Notation` 实际被合并的范围、`mergeType` 生效的合并方式 | 确认合并结果 | -| `media-upload` | `resourceId`、`resourceUrl` | 附件已上传到表格;`resourceUrl` 可用于 `create-float-image` 的 `--src` | -| `write-image` | `resourceId` | 图片已写入指定单元格 | -| `create-float-image` | `floatImage`(含 `id`、`src`、`range`、`width`、`height`、`offsetX`、`offsetY`) | `id` 用于后续 get / update / delete 的 `--float-image-id` | -| `get-float-image` | `floatImage`(完整信息) | 查看单个浮动图片详情 | -| `list-float-images` | `floatImages` 数组、`totalCount` | 获取所有浮动图片的 `id`,用于后续操作 | -| `update-float-image` | `floatImage`(更新后的完整信息) | 确认更新结果 | -| `delete-float-image` | `message` | 确认删除完成 | -| `replace` | `replaceCount` 被替换的单元格数量 | 确认替换结果 | -| `move-dimension` | `sheetId` 工作表 ID | 确认操作完成 | -| `add-dimension` | `sheetId` 工作表 ID | 确认操作完成 | -| `group-dimension` | `range` / `level` / `groupState` | 确认行列分组层级和展开状态 | -| `ungroup-dimension` | `range` / `level` | 确认行列分组已取消 | -| `unmerge-cells` | `sheetId` 工作表 ID | 确认操作完成 | -| `set-dropdown` | `range` 实际设置范围、`optionCount` 选项数量、`enableMultiSelect` 是否多选 | 确认下拉列表设置成功 | -| `get-dropdown` | `hasDropdown` 是否存在下拉、`dataValidations` 下拉配置列表(含 `conditionValues`、`ranges`、`options`) | 查看已有下拉配置 | -| `delete-dropdown` | `range` 实际删除范围 | 确认下拉列表删除完成 | -| `filter-view list` | `filterViews` 筛选视图列表(含 `id`、`name`、`range`) | 获取 filterViewId 用于 info / update / delete / update-criteria / delete-criteria / list-criteria / get-criteria | -| `filter-view info` | `id`、`name`、`range`、`criteria` | 查看单个视图完整配置,确认条件是否生效 | -| `filter-view create` | `id` 筛选视图 ID、`name`、`range` | 用于后续 update / delete / update-criteria / delete-criteria 的 --filter-view-id | -| `filter-view update` | `id`、`name`、`range`、`criteria` | 确认更新结果 | -| `filter-view delete` | `id` 被删除的筛选视图 ID | 确认删除完成 | -| `filter-view update-criteria` | `id` 筛选视图 ID | 确认条件设置完成 | -| `filter-view delete-criteria` | `id` 筛选视图 ID | 确认条件清除完成 | -| `filter-view list-criteria` | 所有列条件(按列偏移量为 key 的对象) | 了解当前视图已设置哪些列的条件 | -| `filter-view get-criteria` | 指定列的条件详情(`filterType`、`conditions` 等) | 查看某列的具体筛选规则 | -| `export` | `downloadUrl`(未指定 --output)/ `outputPath`(指定 --output) | 直接下发给用户或告知文件已保存到本地。命令内部已完成轮询,不要再调用其他 export 相关命令 | - -## nodeId 多格式说明 - -所有 `--node` 参数同时支持文档 ID、文档 URL、表格分享链接,系统自动识别。详细格式和提取规则请参见前文「URL 识别与 NODE_ID 提取」章节。 - -> ** 禁止使用 `dentryId`**:`drive list` 返回结果中同时包含 `dentryId`(纯数字,如 `218595998810`)和 `fileId`(UUID 格式,如 `ZgpG2NdyVXYOR2D5UGDok65MJMwvDqPk`)两个字段。sheet 的 `--node` 和 `--folder` 参数**只能使用 `fileId`(UUID 格式)**,不能使用纯数字的 `dentryId`,传入纯数字会导致命令失败。 - -## values 参数格式说明 - -`--values` 为二维 JSON 数组,第一维为行,第二维为列。**每个单元格必须是 object**,不支持裸值 `"文本"` / `100` / `null`: -- 文本: `{"type":"text","text":"文本"}` -- 数字/布尔: 写成字符串 `{"type":"text","text":"100"}`,服务端按内容自动识别为数字/布尔 -- 公式: `{"type":"text","text":"=SUM(B2:B4)"}`(`text` 以 `=` 开头识别为公式) -- 清空单个单元格: `{"type":"text","text":""}`;清空整片区域用 `range clear` -- 跳过某格保留原值: `{}` 空对象 -- 整格样式: `{"type":"text","text":"重要","cellStyles":{"fontWeight":"bold","fontColor":"#FF0000","numberFormat":"@"}}` -- 富文本: `{"type":"richText","texts":[...]}`(子项 text/link/attachment/image,片段样式写在子项 `style`) - -维度必须与 `--range` 范围一致,例如 `--range "A1:B3"` 需要 3 行 2 列的 object 数组。 - -## 超链接写法说明 - -**没有 `--hyperlinks` flag**(旧写法已废弃,传入报 unknown flag)。单元格级超链接写在 `--values` 里 cell object 的 `hyperlink` 字段: -- `{"type":"text","text":"钉钉","hyperlink":{"type":"path","link":"https://dingtalk.com"}}`:写外部链接 -- `hyperlink.type` 可为 `path`(外部链接)/ `sheet`(工作表跳转,link 为工作表名)/ `range`(区域跳转,link 为 `Sheet2!A1`) -- `{"hyperlink":{"type":"none"}}`:清除整格超链接(保留原值) -- 富文本片段内的链接写在 `richText` 子项的 `link` 字段,与整格 `hyperlink` 不同 - -## number-format 常用值 - -适用范围:`number-format` 在 `range set-style` / `range batch-set-style` 中作为 `--number-format` 参数;`range update` 没有 `--number-format` flag,但可在每个 cell object 的 `cellStyles.numberFormat` 里写同样的格式代码。批量刷整片区域优先用 `set-style`。 - -| 格式代码 | 说明 | 示例 | -|----------|------|------| -| `General` | 常规 | 1234.5 | -| `@` | 文本 | 001234 | -| `#,##0` | 整数千分位 | 1,235 | -| `#,##0.00` | 两位小数 | 1,234.50 | -| `0%` | 百分比 | 85% | -| `yyyy/m/d` | 日期 | 2026/3/15 | -| `hh:mm:ss` | 时间 | 14:30:00 | -| `¥#,##0` | 人民币 | ¥1,235 | - -## 注意事项(完整版) - -> 标 ★ 的条目已在前文「关键注意事项」中列出,此处为完整说明。 - -- ★ `--sheet-id` 获取规范(强制):所有涉及 `--sheet-id` 参数的命令(`info` / `new` / `range read` / `range update` / `find` / `append` / `insert-dimension` / `delete-dimension` / `update-dimension` / `move-dimension` / `add-dimension` / `merge-cells` / `unmerge-cells` / `replace` / `write-image` / `set-dropdown` / `get-dropdown` / `delete-dropdown` / `filter-view *` 等),除非用户主动提供了工作表 ID 或工作表名称,否则在 `sheetId` 未知时必须先通过 `dws sheet list --node --format json` 查询真实的 `sheetId` / 工作表名称后再调用,禁止凭空编造(如臆测为 `Sheet1`、`sheet1`、`0`、`default` 等);用户仅给出工作表名称时,也应通过 `list` 校验该名称是否存在,避免名称大小写或拼写不一致导致失败 -- ★ `range update` 单元格对象协议(强制):`--values` 每个单元格必须是 object(`{"type":"text","text":...}` / `{"type":"richText",...}` / `{}` 等),不支持裸值 `"张三"` / `90` / `null`,裸值报「不支持原始值……每个单元格必须是 object」。数字/布尔写成字符串 object,服务端自动识别类型。超链接写在 cell object 的 `hyperlink` 字段,没有 `--hyperlinks` flag -- ★ `range update` 维度校验(强制):`--values` 二维数组的行数与列数必须与 `--range` 完全一致: - - 例如 `--range "A1:C3"` 表示 3 行 × 3 列,`--values` 必须是 3×3 的 object 数组 - - `--range "A1"` 表示 1 行 × 1 列,`--values` 必须是 `[[{...}]]` - - 不改的格用 `{}` 空对象占位补齐;禁止各行列数不一致或与 `--range` 不匹配,否则报错 -- ★ `range update` 清空单元格规范(强制):清空单个单元格用 `{"type":"text","text":""}`(不是裸 `""`,也不是 `null`);清空整片区域用 `range clear`;跳过某格保留原值用 `{}` 空对象 -- `create` 不传 `--folder` 和 `--workspace` 时,默认创建在"我的文档"根目录 -- `list` 返回所有工作表的 ID 和名称,是后续操作的必要前置步骤 -- `info` 不传 `--sheet-id` 时默认返回第一个工作表的详情 -- `range read` 不传 `--range` 时默认读取整个工作表的全部非空数据 -- `range read` 的 `--range` 支持 `Sheet1!A1:D10` 格式直接指定工作表(此时忽略 `--sheet-id`) -- `range read` 遇到超时或响应过慢时,应缩小 `--range` 查询范围,**单次读取的单元格数量建议控制在 5000 个以内**;数据量较大时通过 `info` 获取边界后分批读取,避免不传 `--range` 直接读取整个大工作表 -- `range update` 只有 `--values` 一个内容参数(必填),超链接/样式/数据验证都写进 cell object 内部字段 -- `range update` 职责边界:可写值、超链接、per-cell 样式(`cellStyles`)与数据验证(`dataValidation`)。批量刷整片区域的统一样式或数字格式优先用 `dws sheet range set-style --number-format <格式代码>`;批量多区域走 `dws sheet range batch-set-style --batch `(配置文件用 `numberFormat` 字段) -- ★ `range update` / `range set-style` / `range batch-set-style` 单次调用上限(强制):行数 ≤ 1000,单元格总数(行×列)建议≤ 5000(底层硬限 30000);超限请拆分多次调用。CLI 会在调用前做本地预校验,底层超 30000 会直接报错 -- `range set-style` / `range batch-set-style` 的样式枚举按驼峰书写:`wordWrap` 取 `overflow`/`clip`/`autoWrap`,`fontWeight` 取 `bold`/`normal`,`hAlign` 取 `left`/`center`/`right`/`general`,`vAlign` 取 `top`/`middle`/`bottom`;背景色/字体颜色统一使用 `#RRGGBB` 格式 -- `new` 创建工作表时,如名称与已有工作表重复,系统会自动重命名 -- `find` 返回匹配单元格的地址(A1 表示法)和值,无匹配时返回空数组 -- `find` 的 `--match-entire-cell` 用于精确匹配:只返回单元格内容完全等于搜索文本的结果,不会匹配包含该文本的单元格(例如搜索"苹果"时,只匹配"苹果",不匹配"苹果手机""苹果汁"等)。用户说"精确搜索/完全匹配/只搜等于XX的"时必须使用此参数 -- `find` 的 `--match-case` 默认为 true(区分大小写),设为 false 可忽略大小写 -- `find` 的 `--use-regexp` 启用后,`--find` 参数作为正则表达式处理 -- ★ 当用户要求搜索/查找表格数据时,使用 `find` 命令,不要用 `range read` 读取全量数据后自行过滤——`find` 支持服务端搜索,效率更高、语义更准确 -- `append` 自动定位到最后一行有数据的位置下方插入,无需手动计算行号 -- `append` 的 `--values` 二维数组中每行的列数必须一致,否则会报错。如果用户提供的数据中各行长度不同,必须先将短行用空字符串 `""` 补齐到与最长行相同的列数后再调用。追加的数据列数也应与工作表已有数据列数保持一致 -- `append` vs `range update`:追加新行用 `append`,修改已有单元格用 `range update` -- `insert-dimension` 在指定位置之前插入空行或空列,不写入数据;如需在末尾追加行/列,使用 `append` -- `insert-dimension` 的 `--dimension` 只接受 `ROWS` 或 `COLUMNS` -- `insert-dimension` 的 `--position` 支持工作表前缀(如 `Sheet1!3`),此时忽略 `--sheet-id` -- `insert-dimension` 的 `--length` 最大为 5000 -- `delete-dimension` 从指定位置起删除若干连续的行或列,删除后后续行/列向前移动填补空位 -- `delete-dimension` 的 `--dimension` 只接受 `ROWS` 或 `COLUMNS` -- `delete-dimension` 的 `--position` 支持工作表前缀(如 `Sheet1!3`),此时忽略 `--sheet-id` -- `delete-dimension` 的 `--length` 最大为 5000 -- `delete-dimension` 若需仅清空内容但保留行/列占位,请使用 `range clear`(整片区域清除,比逐格写空更简洁) -- `update-dimension` 批量更新连续行/列的显隐状态与行高/列宽 -- `update-dimension` 的 `--dimension` 只接受 `ROWS` 或 `COLUMNS` -- `update-dimension` 的 `--start-index` 支持工作表前缀(如 `Sheet1!3`),此时忽略 `--sheet-id` -- `update-dimension` 的 `--length` 最大为 5000 -- `update-dimension` 的 `--hidden` 与 `--pixel-size` 至少必须提供一个 -- `update-dimension` 的 `--pixel-size` 单位为像素,`dimension=ROWS` 时表示行高、`dimension=COLUMNS` 时表示列宽 -- `update-dimension` 当同时提供 `--hidden` 与 `--pixel-size` 时,将先应用尺寸再应用显隐,任一失败整体失败 -- `merge-cells` 合并时只保留左上角单元格的值,其他单元格的值会被丢弃 -- `merge-cells` 的 `--merge-type` 不传时默认为 `mergeAll`(合并所有单元格) -- `merge-cells` 的 `--range` 支持带工作表前缀的写法(如 `Sheet1!A1:B3`),此时忽略 `--sheet-id` -- `merge-cells` 如果目标区域与其他合并单元格、锁定区域或表格区域存在交集,合并将失败 -- `media-upload` 是两步自动完成的流程 (获取附件上传凭证 → OSS 上传),无需手动分步操作 -- `write-image` 是三步自动完成的流程 (获取附件上传凭证 → OSS 上传 → 写入图片到单元格),无需手动分步操作 -- ★ 向表格单元格中写入图片必须使用 `write-image`,禁止使用 `range update`。`range update` 底层调用的 `update_range` MCP 工具不支持图片类型参数,调用会失败 -- `write-image` 与 `media-upload` 的区别:`media-upload` 仅上传附件到表格获取 resourceId;`write-image` 在上传后还会将图片写入指定单元格 -- `create-float-image` 创建浮动图片前必须先通过 `media-upload` 上传图片获取 `resourceUrl`,再将其作为 `--src` 传入。`--src` 的格式为 `/core/api/resources/img/...`,不能直接传外部 URL -- `create-float-image` 的 `--range` 使用 A1 表示法指定锚点单元格(如 `A1`、`B3`),支持带工作表前缀(如 `Sheet1!A1`) -- `create-float-image` 的 `--width` / `--height` 为必填,单位像素,必须为正整数;`--offset-x` / `--offset-y` 可选,默认 0,不能为负数 -- `write-image`(单元格内嵌图片)vs `create-float-image`(浮动图片):`write-image` 将图片写入单元格内部,占据单元格内容;`create-float-image` 创建悬浮于单元格之上的浮动图片,不占用单元格内容,可自由调整位置和大小 -- `update-float-image` 的 `--src` / `--range` / `--width` / `--height` / `--offset-x` / `--offset-y` 至少必须提供一个 -- `list-float-images` 返回 `floatImages` 数组和 `totalCount`,每个元素包含 `id`(用于后续 get / update / delete) -- `delete-float-image` 操作不可恢复,删除后图片将从工作表中移除 -- `replace` 的 `--find` 不能为空字符串,`--replace` 可以为空字符串(表示删除匹配内容) -- `replace` 的 `--match-case` 默认为 false(不区分大小写),与 `find` 的默认行为不同 -- ★ `replace` vs `range update`:需要批量替换文本时,必须使用 `replace` 命令,禁止用 `range update` 手动重写单元格来实现替换效果。`replace` 支持服务端全局替换,效率更高且会返回替换计数 -- ★ `move-dimension` vs `range update`:需要移动行或列时,必须使用 `move-dimension` 命令,禁止用 `range update` 读取数据后手动重写来模拟移动效果。`move-dimension` 是原子操作,能保留单元格的格式、合并状态等属性 -- `move-dimension` 的索引均为 0-based(第 1 行/列的索引为 0),`endIndex` 包含在移动范围内 -- `move-dimension` 的 `--destination-index` 不能在 [start-index, end-index] 范围内 -- `move-dimension` 的移动跨度(end-index - start-index + 1)不超过 5000 -- `move-dimension` 的 `--destination-index` 是目标位置的 0-based 索引,即移动到第 n 行/列则传 `n - 1`(通用公式:`destination-index = 目标行号(1-based) - 1`) -- `add-dimension` vs `range update`:需要在末尾追加空行/空列时,必须使用 `add-dimension` 命令,禁止用 `range update` 写空数据来模拟追加效果 -- `add-dimension` 追加的是空行/空列,与 `append`(追加带数据的行)不同 -- `add-dimension` 的 `--length` 必须为正整数(>= 1),行列均不超过 5000 -- `group-dimension` / `ungroup-dimension` 的 `--range` 只接受整行或整列范围,如 `3:7`、`C:F`、`Sheet1!3:7`,不支持 `A1:C5` -- `group-dimension` 的 `--group-state` 只接受 `expand` 或 `fold`,默认 `expand` -- `batch-update` 支持 `group-dimension` / `ungroup-dimension`;需要创建后立即折叠时,优先使用独立 `group-dimension --group-state fold` -- `unmerge-cells` 取消指定范围内所有合并单元格,使用 A1 表示法指定范围 -- `set-dropdown` 在指定范围内设置下拉列表,`--options` 为 JSON 数组,每个元素包含 `value`(必填)和 `color`(可选,`#RRGGBB` 格式)。选项值不能包含英文逗号。`--multi-select` 启用多选模式。如果目标范围已存在下拉列表,会被新配置覆盖 -- `get-dropdown` 查询指定范围内的下拉列表配置,返回 `dataValidations` 数组,相同选项的单元格聚合为一组。无下拉列表时 `hasDropdown` 为 false -- `delete-dropdown` 删除指定范围内的下拉列表配置,单元格恢复为普通文本格式。已填写的值不会被清除。目标范围不存在下拉列表时操作仍返回成功 -- ★ **全局筛选(filter)与筛选视图(filter-view)的区别**:全局筛选影响所有协作者看到的数据展示,每个工作表最多一个;筛选视图是个人化的,互不影响。用户只说"筛选"时默认走 `filter` 系列 -- `filter get` 获取工作表的全局筛选信息,返回 `range`(筛选范围)、`id` 和 `criteria`(各列条件对象,无条件时为 `{}`)。无筛选时返回空 -- `filter create` 创建全局筛选时 `--range` 必须包含表头行(如 `A1:E100`),不能只包含数据行。每个工作表只能有一个筛选,已存在时报错 -- `filter create` 的 `--criteria` 可选,不传则仅创建空筛选框架,后续通过 `filter update` 设置条件 -- `filter delete` 删除后所有筛选条件丢失且所有被隐藏行重新显示,不可恢复 -- `filter delete` 工作表没有筛选时调用会报错,应先 `filter get` 确认存在 -- `filter update` 是覆盖式:指定列的条件会被替换,未指定的列保持不变。如只想修改某一列,建议先 `filter get` 读取现有配置再 patch -- `filter update` 前置:工作表必须已创建筛选 -- `filter clear-criteria` 仅清除指定列的条件,不删除整个筛选。指定列无条件时不报错(幂等) -- `filter sort` 会实际改变数据行的物理顺序,不可撤销。前置:工作表必须已创建筛选 -- ★ **筛选操作规范**(参照飞书 core-operations): - - 当用户要求"筛选/只看/仅保留 X"时,**必须**通过 `filter create` / `filter update` 创建真实的筛选器。**禁止**用"删除不符合条件的行"或"新建工作表只放符合条件的行"来代替 - - 创建/更新筛选后**必须** `filter get` 回读验证配置正确 - - 更新已有筛选前先 `filter get` 读取当前配置,确认目标存在且了解现有条件后再操作 - - 筛选条件的列索引(`column`)必须与实际数据列精确对应,不要凭猜测填写 - - 筛选不支持正则表达式,传入正则会当成普通文本处理 -- `filter-view list` 获取指定工作表的所有筛选视图列表,返回的 `id` 可用于后续 info / update / delete / update-criteria / delete-criteria / list-criteria / get-criteria 的 `--filter-view-id` -- `filter-view info` 获取单个筛选视图的完整信息(含 criteria),内部复用 `get_filter_views` MCP 按 ID 过滤 -- `filter-view list-criteria` 列出指定筛选视图已设置的所有列条件,返回按列偏移量为 key 的对象;无条件时返回空对象 `{}` -- `filter-view get-criteria` 获取指定列的条件详情,`--column` 为列偏移量(从 0 开始);该列无条件时返回错误提示 -- `filter-view create` 创建筛选视图时 `--range` 应包含表头行。`--criteria` 可选,不传则创建后无筛选条件,后续可通过 `filter-view update-criteria` 设置 -- `filter-view update` 的 `--name`、`--range`、`--criteria` 至少需要传入一个,未指定的字段保持不变 -- `filter-view update` 的 `--criteria` 中指定列的条件会被替换,未指定的列保持不变 -- `filter-view delete` 删除后该视图及其所有筛选条件将被永久移除,不可恢复 -- `filter-view delete` 不影响全局筛选或其他筛选视图 -- `filter-view update-criteria` 的 `--column` 为列偏移量(从 0 开始),相对于筛选视图范围首列。例如筛选视图范围为 `B1:E10`,则 `--column 0` 代表 B 列 -- `filter-view update-criteria` 设置条件后立即在该筛选视图中生效,仅影响当前视图,不影响全局筛选或其他筛选视图 -- `filter-view update-criteria` 的 `--filter-criteria` 中 `conditions` 最多 2 个条件,多条件之间通过 `conditionOperator` 指定逻辑关系(`and` 或 `or`) -- `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` 仅支持钉钉在线电子表格(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`**;直接告知用户导出失败并建议稍后再试 -- `export` 未指定 `--output` 时,返回的 `downloadUrl` 具有时效性,获取后请尽快下载;若用户需要本地文件,优先直接传 `--output` 让 CLI 代为下载 -- `export` 的 `--output` 可为文件路径或已存在目录;为目录时自动从 `downloadUrl` 推断文件名,为文件路径时直接按该路径保存 -- 用户要求"导出表格/下载 xlsx"时,必须使用 `export` 单命令,禁止用 `range read` 读全量数据后自行拼 xlsx 模拟导出(服务端导出会保留格式/合并/公式等完整属性) -- `update` 的 `--title`、`--index`、`--hidden`、`--frozen-row-count`、`--frozen-column-count` 至少必须提供一个 -- `update` 的 `--title` 最长 100 字符,不能包含 `/ \ ? * [ ] :` 等特殊字符 -- `update` 的 `--index` 为 0-based 非负整数,0 表示移动到最前面 -- `update` 的 `--hidden` 设为 true 时,至少需要保留一个可见的工作表,不能将所有工作表都隐藏 -- `update` 的 `--frozen-row-count` / `--frozen-column-count` 为非负整数,不能超过工作表的总行数/列数,设为 0 表示取消冻结 -- `update` 当同时提供多个属性时,所有属性将在同一次请求中更新 -- `copy` 复制操作会将源工作表的所有内容(包括数据、格式、公式等)完整复制到新工作表 -- `copy` 的 `--title` 可选,不传时系统自动生成名称(通常为"源名称 副本"或类似格式) -- `copy` 的 `--title` 最长 100 字符,不能包含 `/ \ ? * [ ] :` 等特殊字符 -- `copy` 当指定名称与已有工作表重复时,系统会自动重命名为合法值 -- `copy` 的 `--index` 可选,不传时副本将放置在源工作表之后的默认位置 -- ★ 关键区分: sheet(电子表格/单元格读写) vs aitable(AI多维表/结构化记录/字段定义) vs doc(文档编辑/阅读) -- sheet 产品线仅支持 `axls`(在线电子表格,`contentType=ALIDOC`),不支持 `xlsx` / `xls` / `xlsm` / `csv` 等本地表格文件 -- 遇到未知 `alidocs` URL 时,必须先 probe(`dws doc info --node --format json`)确认 `contentType` 和 `extension`,才能决定是否走 sheet -- 当节点 `extension=xlsx` / `xls` / `xlsm` / `csv`(`contentType≠ALIDOC`)时,必须用 `dws doc download --node --output <路径>` 先下载到本地再处理,禁止调用任何 sheet 子命令(sheet 底层 MCP 工具只识别 axls,调用 xlsx 节点必失败) -- 要把在线表格导出为 xlsx 文件——走 `dws sheet export`(axls → xlsx 的格式转换);要读已有的 xlsx 文件——走 `dws doc download` 后在本地解析,两者方向相反 +创建工作簿、管理工作表见 [sheet-workbook.md](./sheet/sheet-workbook.md)。 diff --git a/skills/multi/dingtalk-sheet/references/sheet/sheet-batch-operations.md b/skills/multi/dingtalk-sheet/references/sheet/sheet-batch-operations.md index 819a654d..e0c9ef55 100644 --- a/skills/multi/dingtalk-sheet/references/sheet/sheet-batch-operations.md +++ b/skills/multi/dingtalk-sheet/references/sheet/sheet-batch-operations.md @@ -5,7 +5,7 @@ `batch-update` 把多次写入打包成单次请求,但每个子操作仍受编辑类任务硬性默认规则约束: 1. **目标 range 必须落在用户授权范围内**:除用户明示要修改的区域外,子操作禁止扩张到无关单元格 / 列 / 工作表。规划 range 时先确认每个子操作的边界。 -2. **批次完成后必须回读校验**:整个 `batch-update` 执行成功后,用 `range read` 或 `csv-get` 抽样回读受影响区域,至少校验 3-5 个代表性单元格(首 / 中 / 末),与本地预先计算的预期值对照。 +2. **批次完成后必须回读校验**:整个 `batch-update` 执行成功后,单元格值用 `range read` 或 `csv-get` 抽样回读受影响区域;合并、行列分组等结构元数据用 `sheet info` 回读校验。 3. **预期条数前置断言**:涉及"批量填充 N 行"或"对 M 个区域分别写入"时,先把 N、M 硬编码进代码,回读后断言实际等于预期;不一致就再发一轮 `batch-update` 补齐,禁止交付半成品。 4. **统一确认语义**:`range batch-clear` 和 `batch-update` 都可删除内容或结构;先用 `--dry-run` 预览,真实执行必须获得用户确认后追加 `--yes`。 @@ -23,17 +23,23 @@ - 原子事务:任一操作失败则整批回滚 - 传 `--continue-on-error` 可切换为宽松模式(失败继续) +用户说"批量创建行列分组/批量取消行列分组/同时调整尺寸并分组": +- 连续行/列分组 → `group-dimension` +- 取消连续行/列分组 → `ungroup-dimension` +- 分组回读用 `sheet info` +- `group-dimension` 在 batch 中只适合默认展开分组;需要创建后立即折叠时,用独立 `dws sheet group-dimension --group-state fold` + **何时推荐使用 `batch-update`**: - 需要对**多个**不同区域执行 `merge-cells` / `unmerge-cells` 时(如按分组合并多列相同内容) - 需要对**多个**不同区域执行 `update-dimension` 时(如统一调整多列列宽或多行行高,含 hidden + pixelSize) +- 需要对**多个**连续行/列范围执行 `group-dimension` / `ungroup-dimension` 时(默认展开分组) - 需要先插入行列再写入数据时(`add-dimension` + `range update` / `csv-put`) -- 需要批量创建或取消行列分组时(`group-dimension` / `ungroup-dimension`) - 需要对多个区域执行不同写入操作时(多次 `range update` + `range clear` 等组合) 当同一工具需要对多个区域重复调用时,**推荐**改用 `batch-update` 合并为单次请求——`batch-update` 是原子提交(要么全成功要么整批回滚);逐个调用非原子,中途失败会留下半成品。 **不可放进 `--operations` 的操作**(强行写入会被校验拒或行为未定义): -- `range read` / `csv-get`(只读操作,不属于写入) +- `range read` / `csv-get`(读取操作,不在 batch dispatch 表中) - `range sort` / `range move-to`(尚未支持,后续版本补充) - `write-image` / `media-upload` / `create-float-image` / `update-float-image`(需本地上传或依赖前置上传句柄) - `set-style` / `range batch-set-style`(自身已是批量入口,不可再嵌套) @@ -72,7 +78,8 @@ Example: {"toolName":"range clear","input":{"sheet-id":"Sheet1","range":"A1:B3","type":"content"}}, {"toolName":"range update","input":{"sheet-id":"Sheet1","range":"A1","values":[[{"type":"text","text":"hello"}]]}}, {"toolName":"merge-cells","input":{"sheet-id":"Sheet1","range":"A1:B1","merge-type":"mergeAll"}}, - {"toolName":"update-dimension","input":{"sheet-id":"Sheet1","dimension":"ROWS","start-index":"1","length":1,"pixel-size":40}} + {"toolName":"update-dimension","input":{"sheet-id":"Sheet1","dimension":"ROWS","start-index":"1","length":1,"pixel-size":40}}, + {"toolName":"group-dimension","input":{"sheet-id":"Sheet1","range":"3:7","group-state":"expand"}} ]' --yes dws sheet batch-update --node --continue-on-error --operations '[...]' --yes Flags: @@ -91,6 +98,7 @@ Notes: - 当需要对多个区域执行相同清除时,优先使用 `range batch-clear`(更简洁) - 典型场景:先插入行列再写入数据、先清除再写入、批量合并+调整行高列宽 - 真实执行必须获得用户确认后加 --yes;--dry-run 不需要 --yes + - `group-dimension` 在 batch 中只适合默认展开分组;需要 `--group-state fold` 时请使用独立 `dws sheet group-dimension` ``` ### 子操作定位规则 @@ -106,6 +114,7 @@ Notes: ] ``` + ## 校验与预览 ### Validate 阶段 @@ -123,24 +132,6 @@ CLI 在发送请求前执行以下本地校验(不消耗网络请求): > `input` 内的字段(如 `sheet-id`、`range`、`values` 等)由服务端校验,CLI 不提前拦截——传入空值或缺失必填字段时,请求会到达服务端再返回错误。 -### DryRun 阶段 - -加 `--dry-run` 预览翻译后的完整 MCP 请求,不实际执行任何操作: - -```bash -dws sheet batch-update --node NODE_ID --dry-run --operations '[...]' -``` - -DryRun 输出内容: -- `Tool: batch_update`(最终调用的 MCP 工具名) -- `Arguments:` 完整 JSON(含翻译后的 `operations` 数组,展示每个子操作的 MCP toolName + 参数名 + 参数值) -- 若传了 `--continue-on-error`,输出 JSON 中会包含 `"continueOnError": true` - -DryRun 适用场景: -- 确认 CLI 命令名 → MCP toolName 的翻译是否正确 -- 确认参数名转换(如 `--range` → `rangeAddress`)是否符合预期 -- 检查 `csv-put` 的 `@filepath` 是否被正确解析为文件内容 - ### --continue-on-error 行为 | 模式 | flag | 子操作失败时 | 返回格式 | @@ -148,7 +139,7 @@ DryRun 适用场景: | 严格事务(默认) | 不传 | 整批回滚到初始状态,后续子操作不执行 | 顶层 `error` 对象 | | 宽松模式 | `--continue-on-error` | 已执行的子操作不回滚,继续执行后续子操作 | `results` 数组 | -**严格事务失败的错误信息**由服务端(flex-table-app)生成,包含以下内容: +**严格事务失败的错误信息**由服务端生成,包含以下内容: - 失败操作索引:`operations[N]`(0-based,即 `--operations` JSON 数组中的下标) - 回滚告知:"All previously executed operations have been rolled back." - 失败原因:原始错误码和错误消息 @@ -176,13 +167,13 @@ Cause: The requested resource was not found by the identifier 'NonExistentSheet' | `merge-cells` | [sheet-style-format](./sheet-style-format.md) | | `unmerge-cells` | [sheet-style-format](./sheet-style-format.md) | | `update-dimension` | [sheet-dimension-operations](./sheet-dimension-operations.md) | +| `group-dimension` | [sheet-dimension-operations](./sheet-dimension-operations.md) | +| `ungroup-dimension` | [sheet-dimension-operations](./sheet-dimension-operations.md) | | `range fill` | [sheet-range-operations](./sheet-range-operations.md) | | `range copy-to` | [sheet-range-operations](./sheet-range-operations.md) | | `add-dimension` | [sheet-dimension-operations](./sheet-dimension-operations.md) | | `delete-dimension` | [sheet-dimension-operations](./sheet-dimension-operations.md) | | `move-dimension` | [sheet-dimension-operations](./sheet-dimension-operations.md) | -| `group-dimension` | [sheet-dimension-operations](./sheet-dimension-operations.md) | -| `ungroup-dimension` | [sheet-dimension-operations](./sheet-dimension-operations.md) | | `set-dropdown` | [sheet-dropdown](./sheet-dropdown.md) | | `delete-dropdown` | [sheet-dropdown](./sheet-dropdown.md) | | `csv-put` | [sheet-write-data](./sheet-write-data.md) | @@ -236,32 +227,43 @@ Cause: The requested resource was not found by the identifier 'NonExistentSheet' ] ``` -### 批量创建行列分组 +### 批量创建/取消行列分组 + +分组属于工作表结构元数据,批次完成后用 `sheet info` 回读 `rowGroups` / `columnGroups`,不要用 `range read` 校验。回读项中的 `level` 是 1-based,`collapsed` 表示当前折叠状态。 ```jsonc [ {"toolName":"group-dimension", "input":{"sheet-id":"...","range":"3:7","group-state":"expand"}}, {"toolName":"group-dimension", - "input":{"sheet-id":"...","range":"C:F"}}, - {"toolName":"ungroup-dimension", - "input":{"sheet-id":"...","range":"10:12"}} + "input":{"sheet-id":"...","range":"C:F","group-state":"expand"}} ] ``` +```jsonc +[ + {"toolName":"ungroup-dimension", + "input":{"sheet-id":"...","range":"3:7"}}, + {"toolName":"ungroup-dimension", + "input":{"sheet-id":"...","range":"C:F"}} +] +``` + +注意:batch 中不要用 `group-state:"fold"` 来创建折叠分组;需要折叠初始状态时,用独立 `dws sheet group-dimension --group-state fold`。 + ## 上下文传递 | 操作 | 从返回中提取 | 用于 | |------|-------------|------| | `list` | 工作表的 `sheetId` | batch-clear 的 --ranges sheet 前缀 / batch-update 子操作 input 的 sheet-id | -| `batch-update` | 各子操作的执行结果 | 回读校验(range read / csv-get) | +| `batch-update` | 各子操作的执行结果 | 回读校验(值用 range read / csv-get;合并、分组等结构用 sheet info) | ## 注意事项 - ★ **`--sheet-id` 获取规范(强制)**:`sheetId` 未知时必须先通过 `dws sheet list --node --format json` 查询真实的 `sheetId` / 工作表名称后再调用,禁止凭空编造(如臆测为 `Sheet1`、`sheet1`、`0`、`default` 等) - ★ **需要对多个区域执行相同清除操作时,用 `range batch-clear`**:一次原子请求清除多个区域(可跨工作表),失败时整批回滚 - ★ **需要组合多个不同写操作(清除+写入等)时,用 `batch-update`**:原子事务,任一操作失败则整批回滚,避免留下半成品 -- ★ **批次完成后必须回读校验**:用 `range read` 或 `csv-get` 抽样回读受影响区域,至少校验 3-5 个代表性单元格(首 / 中 / 末),与预期值对照 +- ★ **批次完成后必须回读校验**:值写入用 `range read` 或 `csv-get` 抽样回读受影响区域;结构变更用 `sheet info` 回读 - ★ **`batch-update` 不支持嵌套**:`--operations` 中的 `toolName` 必须是原子操作,不可再嵌套 `batch-update` -- `batch-update` 支持 `group-dimension` / `ungroup-dimension`;`input.range` 只接受整行或整列范围,如 `3:7`、`C:F` -- 需要创建后立即折叠分组时,优先使用独立 `dws sheet group-dimension --group-state fold` +- `batch-update` 支持 `group-dimension` / `ungroup-dimension`;分组结果用 `sheet info` 回读 +- `batch-update` 中 `group-dimension` 只适合默认展开分组;需要 `fold` 初始状态时使用独立 `group-dimension` diff --git a/skills/multi/dingtalk-sheet/references/sheet/sheet-capabilities.md b/skills/multi/dingtalk-sheet/references/sheet/sheet-capabilities.md new file mode 100644 index 00000000..ec9b2a71 --- /dev/null +++ b/skills/multi/dingtalk-sheet/references/sheet/sheet-capabilities.md @@ -0,0 +1,7 @@ +# 当前能力边界 + +## 当前能力边界 + +- **公式**:支持通过 `range update` 写入、通过 `range read --value-render-option formula` 回读公式文本、通过 `raw_value` / `formatted_value` 回读计算结果;当前没有聚合式 `formula-verify`,不能只凭写入成功声称公式零错误。 +- **视觉规范**:当前不维护独立的表格视觉方案文档;样式、条件格式、图表分别按对应子文档执行。 +- **未暴露或未确认能力**:迷你图、历史 changeset、评论/批注等能力未在本入口承诺;只有出现稳定 DWS 命令和可回读语义后再补充。 diff --git a/skills/multi/dingtalk-sheet/references/sheet/sheet-chart.md b/skills/multi/dingtalk-sheet/references/sheet/sheet-chart.md index 0295faae..df84da69 100644 --- a/skills/multi/dingtalk-sheet/references/sheet/sheet-chart.md +++ b/skills/multi/dingtalk-sheet/references/sheet/sheet-chart.md @@ -34,6 +34,7 @@ | "百分比面积" | 百分比堆积面积图(areaPercentStacked) | 占比式面积 | | "散点图"、"相关性" | 散点图(scatter) | 两变量相关性 | | "雷达图"、"多维度" | 雷达图(radar) | 多维度对比 | +| "柱+线"、"组合图"、"双轴" | 组合图(combo) | 多种图表叠加,支持副轴 | **多图表需求**:当用户同时提到多种分析(如"统计占比 + 对比数量"),必须创建多个图表,每个对应一种类型,不要只做一个。 @@ -55,8 +56,6 @@ Flags: --chart-id string 浮动图表 ID (可选,不传则返回全部) ``` -- **判空看 `totalCount` / `message`,不要看数组长度**:无图表时 `floatCharts` 数组里仍会带 1 个全 null 的幽灵元素(`chart.category`/`chart.series` 全 null,无 id),但 `totalCount` 为 0、`message` 为「Successfully retrieved 0 float chart(s).」。判断有没有图表以 `totalCount` 为准,或过滤掉没有 `id` 的元素。 - ### 创建浮动图表 ``` Usage: @@ -130,7 +129,7 @@ Flags: | 字段 | 类型 | 必填 | 说明 | |------|------|------|------| | `row` | number | 是 | 锚点行索引(0-based),如 0 表示第一行 | -| `col` | string | 是 | 锚点列。使用字母表示法(`"A"`=第1列, `"B"`=第2列, `"Z"`=第26列, `"AA"`=第27列),不支持数字形式 | +| `col` | string/number | 是 | 锚点列。支持字母表示法(`"A"`=第1列, `"B"`=第2列, `"Z"`=第26列, `"AA"`=第27列)或 0-based 数字(`0`=第1列) | **offset 字段**(可选,不传时默认 0): @@ -150,9 +149,10 @@ Flags: | 字段 | 类型 | 必填 | 说明 | |------|------|------|------| -| `type` | string | 是 | 图表类型,枚举值:`column` / `bar` / `columnStacked` / `barStacked` / `line` / `lineStacked` / `area` / `areaStacked` / `areaPercentStacked` / `pie` / `doughnut` / `scatter` / `radar` | -| `series` | object[] | 是 | 数据系列数组,至少一项,详见下方 series 字段表 | -| `category` | string[] | 否 | 分类轴(X轴)数据区域,A1 表示法字符串数组(如 `["A2:A10"]`) | +| `type` | string | 是 | 图表类型,枚举值:`column` / `bar` / `columnStacked` / `barStacked` / `line` / `lineStacked` / `area` / `areaStacked` / `areaPercentStacked` / `pie` / `doughnut` / `scatter` / `radar` / `combo` | +| `series` | object[] | 非 combo 必填 | 数据系列数组,至少一项,详见下方 series 字段表。combo 类型不使用此字段 | +| `subCharts` | object[] | combo 必填 | 组合图子图数组,至少一项,详见下方 subCharts 字段表。仅 combo 类型使用 | +| `category` | string[] | 否 | 分类轴(X轴)数据区域,A1 表示法字符串数组(如 `["A2:A10"]`)。combo 类型在 subCharts 内声明 | | `title` | object | 否 | 图表标题配置,详见下方 title 字段表 | | `legend` | object | 否 | 图例配置,详见下方 legend 字段表 | | `catAx` | object | 否 | 分类轴(X轴)样式配置,详见下方轴配置表 | @@ -165,6 +165,15 @@ Flags: | `name` | string | 否 | 系列名称的单元格引用,A1 表示法(如 `"B1"`)。不传时图例显示为"系列1"等默认名 | | `value` | string[] | 是 | 系列数据区域,A1 表示法字符串数组(如 `["B2:B10"]`)。每个元素为一段连续区域 | +**subCharts 数组项**(仅 combo 类型): + +| 字段 | 类型 | 必填 | 说明 | +|------|------|------|------| +| `type` | string | 是 | 子图类型,仅允许笛卡尔坐标系类型:`column` / `bar` / `columnStacked` / `barStacked` / `line` / `lineStacked` / `area` / `areaStacked` / `areaPercentStacked`。不允许 pie/doughnut/scatter/radar/combo | +| `series` | object[] | 是 | 子图数据系列,格式同上方 series 数组项 | +| `category` | string[] | 是 | 子图分类轴数据区域(笛卡尔坐标系必须提供) | +| `useSecondaryAxis` | boolean | 否 | 是否使用副轴(右侧 Y 轴),默认 false | + **title 字段**: | 字段 | 类型 | 必填 | 说明 | @@ -199,7 +208,7 @@ Flags: - 格式:`"A2:B10"`(默认使用 `--sheet-id` 对应的工作表) - 跨 sheet 引用:`"SHEET_ID!A2:B10"`(前缀为工作表 ID) - 单元格引用:`"B1"`(等同于 `"B1:B1"`) -- MCP 内部自动将 A1 引用转换为 ISheetRange 结构,CLI 直接透传即可 +- 直接传 A1 表示法字符串即可,命令会处理成图表所需的区域引用 ## 图表位置选择(创建前必做) @@ -277,32 +286,19 @@ dws sheet chart create --node --sheet-id --properties '{ } }' -# ── 工作流 4: 多图表组合分析 ── -# 当用户需要"柱+线"等多种图表组合分析时,分别创建对应类型的图表: +# ── 工作流 4: 创建组合图(combo)── +# 当用户需要"柱+线"叠加、双轴对比时,使用 combo 类型: -# 创建柱形图 dws sheet chart create --node --sheet-id --properties '{ "position": {"row": 40, "col": "A"}, - "dimensions": {"width": 700, "height": 450}, + "dimensions": {"width": 800, "height": 500}, "chart": { - "type": "column", - "series": [{"name": "B1", "value": ["B2:B10"]}], - "category": ["A2:A10"], - "title": {"show": true, "text": "销售额"}, - "legend": {"show": true, "pos": "t"} - } -}' - -# 创建折线图 -dws sheet chart create --node --sheet-id --properties '{ - "position": {"row": 40, "col": "J"}, - "dimensions": {"width": 700, "height": 450}, - "chart": { - "type": "line", - "series": [{"name": "C1", "value": ["C2:C10"]}], - "category": ["A2:A10"], - "title": {"show": true, "text": "增长率"}, - "legend": {"show": true, "pos": "t"} + "type": "combo", + "subCharts": [ + {"type": "column", "series": [{"name": "B1", "value": ["B2:B10"]}], "category": ["A2:A10"]}, + {"type": "line", "series": [{"name": "C1", "value": ["C2:C10"]}], "category": ["A2:A10"], "useSecondaryAxis": true} + ], + "title": {"show": true, "text": "销售额与增长率"} } }' ``` @@ -344,10 +340,10 @@ dws sheet chart update --node --sheet-id --chart-id --format json` 查询,禁止凭空编造(如臆测为 `Sheet1`、`sheet1`、`0`、`default` 等) - [强制] **创建后必须验证**:图表创建后必须调用 `chart list` 验证配置是否正确 -- ⚠️ **`chart list` 判空看 `totalCount` 不看数组长度**:无图表时 `floatCharts` 仍含 1 个全 null 幽灵元素(无 id),`totalCount` 为 0、`message` 说明为空。判断有没有图表以 `totalCount` 为准或过滤无 `id` 的元素 - [强制] **chart-id 禁止臆测**:必须通过 `chart list` 获取真实的图表 ID,不可编造 -- **图表类型映射**:用户说"柱形图"用 `column`,"条形图"(横向)用 `bar`,"散点图"用 `scatter`,"雷达图"用 `radar`,"环形图"用 `doughnut` +- **图表类型映射**:用户说"柱形图"用 `column`,"条形图"(横向)用 `bar`,"散点图"用 `scatter`,"雷达图"用 `radar`,"环形图"用 `doughnut`,"柱+线/组合/双轴"用 `combo` - **堆积类型后缀**:堆叠版本的图表在基本类型后加 `Stacked`(如 `columnStacked`、`lineStacked`、`areaStacked`) +- **combo 组合图**:不用顶层 `series`/`category`,改用 `chart.subCharts` 数组,每个子图独立声明 `type`/`series`/`category`,可选 `useSecondaryAxis: true` 指定副轴。子图 type 仅允许笛卡尔坐标系类型(column/bar/line/area 及其 Stacked 变体),不允许 pie/doughnut/scatter/radar/combo - **位置不得压在数据区上**:图表浮层会遮挡原始数据,必须放在空白区域 - **多图表需求**:多次调用 `chart create`,不要试图用一个图表包含所有分析 - **跨 sheet 引用数据**:在 A1 区域前加 `SHEET_ID!` 前缀,如 `"sheetXXX!A2:B10"` diff --git a/skills/multi/dingtalk-sheet/references/sheet/sheet-conditional-format.md b/skills/multi/dingtalk-sheet/references/sheet/sheet-conditional-format.md index 70bc77b7..ccee67c4 100644 --- a/skills/multi/dingtalk-sheet/references/sheet/sheet-conditional-format.md +++ b/skills/multi/dingtalk-sheet/references/sheet/sheet-conditional-format.md @@ -64,14 +64,13 @@ **正确做法(两步走)**: ```bash -# Step 1: 用 range update 在新列写判断公式(形成"是/否"辅助列) -# 每个单元格必须是 object,公式写在 text(以 = 开头);不支持裸字符串 -dws sheet range update --node NODE_ID --sheet-id SHEET_ID --range "H2:H100" \ - --values '[[{"type":"text","text":"=IF(A2>B2,\"是\",\"否\")"}],...]' +# Step 1: 用 range update 在新列写判断公式(形成"是/否"辅助列,以 H2:H4 为例) +dws sheet range update --node NODE_ID --sheet-id SHEET_ID --range "H2:H4" \ + --values '[[{"type":"text","text":"=IF(A2>B2,\"是\",\"否\")"}],[{"type":"text","text":"=IF(A3>B3,\"是\",\"否\")"}],[{"type":"text","text":"=IF(A4>B4,\"是\",\"否\")"}]]' # Step 2: 基于辅助列值做条件格式(用 formulaCondition 引用辅助列) dws sheet cond-format create --node NODE_ID --sheet-id SHEET_ID \ - --ranges '["A2:H100"]' \ + --ranges '["A2:H4"]' \ --condition '{"formulaCondition":{"formula":"=$H2=\"是\""}}' \ --cell-style '{"backgroundColor":"#FFECEC"}' ``` @@ -143,8 +142,7 @@ Flags: - **用途**:查看指定工作表中已有的条件格式规则,或获取单个规则的详情。 - **场景**:创建/更新/删除条件格式前后验证规则状态;获取 ruleId 供后续 update/delete 使用。 -- **返回**:`rules` 数组,每条规则包含 id、type、ranges、条件参数、cellStyle/dataBarStyle 等。 -- **判空要看 `message`,不要看数组长度**:无规则时 `rules` 数组里仍会带 1 个全 null 的幽灵元素(`colorScaleCondition`/`dataBarStyle`/`iconSetCondition` 全 null,无 id),`message` 会是「No conditional formatting rules found.」。该命令**没有 `totalCount` 字段**,判空以 message 为准,或过滤掉没有 `id` 的元素。 +- **返回**:rules 数组,每条规则包含 id、type、ranges、条件参数、cellStyle/dataBarStyle 等。 ### 创建条件格式规则 ``` @@ -271,4 +269,3 @@ Flags: - **辅助列+条件格式两步走**:用户明确要求"辅助列"时,必须按两步走(先建辅助列 → 再基于辅助列做条件格式),禁止直接用 `formulaCondition` 一步绕过 - **大数据量优势**:当数据量 > 1000 行时,条件格式是首选——它由服务端自身渲染,不需要逐行调用 `range set-style`,性能远优于静态样式写入 - **判断标准**:交付后 `cond-format list` 必须能返回该规则;否则视为违规 -- ⚠️ **`cond-format list` 判空看 `message` 不看数组长度**:无规则时 `rules` 仍含 1 个全 null 幽灵元素(无 id),`message` 为「No conditional formatting rules found.」。该命令无 `totalCount`,判空以 message 为准或过滤无 `id` 的元素 diff --git a/skills/multi/dingtalk-sheet/references/sheet/sheet-dimension-operations.md b/skills/multi/dingtalk-sheet/references/sheet/sheet-dimension-operations.md index afc2f5ac..745e6793 100644 --- a/skills/multi/dingtalk-sheet/references/sheet/sheet-dimension-operations.md +++ b/skills/multi/dingtalk-sheet/references/sheet/sheet-dimension-operations.md @@ -26,11 +26,12 @@ - 注意与 `append`(追加数据行)区分:`add-dimension` 追加的是空行/空列,`append` 追加的是带数据的行 - 请勿用 `range update` 写空数据来模拟追加,`add-dimension` 直接扩展表格维度 -用户说"创建行分组/创建列分组/折叠几行/折叠几列/展开行列分组/取消行列分组": -- 创建分组 → `group-dimension --range <整行或整列范围>` -- 创建并折叠 → `group-dimension --group-state fold` -- 取消分组 → `ungroup-dimension --range <整行或整列范围>` -- `--range` 只能传整行或整列范围,如 `3:7`、`C:F`、`Sheet1!3:7` +用户说"创建行分组/创建列分组/新建分组并折叠/新建分组并展开/取消分组": +- 创建连续行/列分组 → `group-dimension` +- 取消连续行/列分组 → `ungroup-dimension` +- 分组创建后用 `sheet info` 回读 `rowGroups` / `columnGroups` +- 仅创建分组时可用 `--group-state expand|fold` 指定初始展开/折叠;当前没有单独调整已有分组折叠状态的命令 +- 用户要求"折叠/展开已有分组"时,当前不支持直接修改已有分组的 `collapsed` 状态,不能承诺可用 `group-dimension` 完成 **结构预检**:插入、删除、移动行列前,必须先执行 `dws sheet info --node --sheet-id --format json` 查看 `mergedRanges`。合并区域跨过操作位置时,行列变更可能导致表头/分组标题断裂、空白或错位;需要先向用户说明影响,必要时取消合并后操作,再按原模式重新 `merge-cells`。 @@ -183,32 +184,51 @@ Flags: Usage: dws sheet group-dimension [flags] Example: + # 创建第 3~7 行分组,默认展开 dws sheet group-dimension --node --sheet-id --range "3:7" + + # 创建 C~F 列分组,并在创建后折叠 dws sheet group-dimension --node --sheet-id --range "C:F" --group-state fold + + # 使用工作表前缀(忽略 --sheet-id) dws sheet group-dimension --node --sheet-id --range "Sheet1!3:7" Flags: --node string 表格文档 ID 或 URL (必填) --sheet-id string 工作表 ID 或名称 (必填) - --range string 整行/整列范围,如 "3:7" 或 "C:F" (必填) - --group-state string 创建后的分组状态: expand 或 fold,默认 expand + --range string 整行或整列范围,A1 表示法 (必填)。行如 "3:7",列如 "C:F" + --group-state string 创建后的分组状态: expand 或 fold (默认 expand) ``` -为连续行或连续列创建分组。`--range` 必须是整行或整列范围,不支持 `A1:C5` 这类普通单元格矩形范围。 +创建连续整行或整列分组。`--range` 只能是整行范围或整列范围,不支持普通单元格矩形(如 `A1:C5`)。支持单行/单列(如 `"3:3"` / `"C:C"`)以及带工作表前缀(如 `Sheet1!3:7`),带前缀时忽略 `--sheet-id`。 + +返回中使用 `level` 表示分组展示层级,`level` 为 1-based;不要查找或依赖 `depth`。`--group-state fold` 只用于创建后的初始折叠状态,当前不提供"仅修改已有分组 collapsed 状态"的独立命令。 + +创建后用以下命令回读: +```bash +dws sheet info --node --sheet-id --format json +``` + +回读字段为 `rowGroups` / `columnGroups`,单项包含 `range`、起止行列、`count`、`level`、`collapsed`。`collapsed=true` 表示当前分组折叠。 ### 取消行或列分组 ``` Usage: dws sheet ungroup-dimension [flags] Example: + # 取消第 3~7 行分组 dws sheet ungroup-dimension --node --sheet-id --range "3:7" + + # 取消 C~F 列分组 dws sheet ungroup-dimension --node --sheet-id --range "C:F" Flags: --node string 表格文档 ID 或 URL (必填) --sheet-id string 工作表 ID 或名称 (必填) - --range string 整行/整列范围,如 "3:7" 或 "C:F" (必填) + --range string 整行或整列范围,A1 表示法 (必填)。行如 "3:7",列如 "C:F" ``` -取消指定连续行或连续列的分组。 +取消指定连续整行或整列范围上的分组。取消后同样用 `sheet info` 回读确认目标 `range` 已从 `rowGroups` / `columnGroups` 中移除。 + +`group-dimension` / `ungroup-dimension` 可以放进 `batch-update` 做原子组合;但 batch 中的 `group-dimension` 只适合默认展开分组。需要创建后立即折叠时,请使用独立 `dws sheet group-dimension --group-state fold`。 ## 核心工作流 @@ -282,15 +302,15 @@ dws sheet update-dimension --node --sheet-id \ | `update-dimension` | `a1Notation` 被更新区域范围、`hidden` 生效的显隐状态、`pixelSize` 生效的尺寸 | 确认更新结果 | | `move-dimension` | `sheetId` 工作表 ID | 确认操作完成 | | `add-dimension` | `sheetId` 工作表 ID | 确认操作完成 | -| `group-dimension` | `range` / `level` / `groupState` | 确认行列分组层级和展开状态 | -| `ungroup-dimension` | `range` / `level` | 确认行列分组已取消 | +| `group-dimension` | `range` / `level` / `groupState` | 确认创建的分组范围、层级和初始状态 | +| `ungroup-dimension` | `range` / `level` | 确认取消分组的范围和层级 | | `list` | 工作表的 `sheetId` | info / range read / range update / find 的 --sheet-id | ## 注意事项 - ★ **`--sheet-id` 获取规范(强制)**:`sheetId` 未知时必须先通过 `dws sheet list --node --format json` 查询,禁止凭空编造(如臆测为 `Sheet1`、`sheet1`、`0`、`default` 等) - `sheet info` 的 `mergedRanges` 是行列结构操作的重要预检信息。插入列时尤其要检查多行表头合并区,原有合并区域通常不会自动扩展到新列,必要时需重新设置合并区域 -- `insert-dimension` 在指定位置之前插入空行或空列,不写入数据;如需在末尾追加行/列,使用 `append` +- `insert-dimension` 在指定位置之前插入空行或空列,不写入数据;如需在末尾追加行/列,使用 `append`;如需"先插入行列再写入数据"的组合操作,使用 [batch-update](./sheet-batch-operations.md) 打包为原子事务 - `insert-dimension` 的 `--dimension` 只接受 `ROWS` 或 `COLUMNS` - `insert-dimension` 的 `--position` 支持工作表前缀(如 `Sheet1!3`),此时忽略 `--sheet-id` - `insert-dimension` 的 `--length` 最大为 5000 @@ -313,7 +333,8 @@ dws sheet update-dimension --node --sheet-id \ - `add-dimension` vs `range update`:需要在末尾追加空行/空列时,必须使用 `add-dimension` 命令,禁止用 `range update` 写空数据来模拟追加效果 - `add-dimension` 追加的是空行/空列,与 `append`(追加带数据的行)不同 - `add-dimension` 的 `--length` 必须为正整数(>= 1),行列均不超过 5000 -- `group-dimension` / `ungroup-dimension` 的 `--range` 只接受整行或整列范围,如 `3:7`、`C:F`、`Sheet1!3:7`,不支持 `A1:C5` -- `group-dimension` 的 `--group-state` 只接受 `expand` 或 `fold`,默认 `expand` -- 分组操作返回 `level` 表示当前分组层级;不要把它误认为固定深度参数 -- `batch-update` 支持 `group-dimension` / `ungroup-dimension`;需要创建后立即折叠时,优先使用独立 `group-dimension --group-state fold` +- `group-dimension` / `ungroup-dimension` 的 `--range` 只接受整行或整列范围,不接受普通单元格矩形 +- `group-dimension` 输出和 `sheet info` 回读均使用 `level`,且为 1-based;不要使用旧的 `depth` 字段 +- `sheet info` 是分组回读入口;`range read` / `csv-get` 不返回行列分组 +- 当前不能直接调整已有分组的 `collapsed` 状态;`--group-state fold` 只在创建分组时生效 +- `batch-update` 支持 `group-dimension` / `ungroup-dimension`,但不适合创建后立即折叠;需要 `fold` 时使用独立 `group-dimension` diff --git a/skills/multi/dingtalk-sheet/references/sheet/sheet-dropdown.md b/skills/multi/dingtalk-sheet/references/sheet/sheet-dropdown.md index 173594e2..0b135aa4 100644 --- a/skills/multi/dingtalk-sheet/references/sheet/sheet-dropdown.md +++ b/skills/multi/dingtalk-sheet/references/sheet/sheet-dropdown.md @@ -55,11 +55,10 @@ Flags: --range string 查询范围,A1 表示法,如 A1:A100 (必填) ``` -查询指定范围内的下拉列表配置信息,包括选项值和颜色。 +查询指定范围内的下拉列表配置信息,包括选项值、颜色和是否多选。 - **用途**:查看单元格已设置的下拉列表选项和配置。 - **场景**:在修改下拉列表前先查询现有配置;确认下拉列表是否设置成功。 -- **返回**:`dataValidations` 数组,相同选项的单元格聚合为一组,每组包含 `conditionValues`(选项值)、`ranges`(覆盖范围)、`options`(含 `multipleValues` 和 `colorValueMap`)。**判空以 `hasDropdown` 为准**:范围内无下拉列表时 `hasDropdown` 为 false,但 `dataValidations` 里仍会带 1 个全 null 的幽灵条目,不要用数组长度判空。 -- ⚠️ **已知限制**:`options.multipleValues` 服务端恒返回 `null`(即使该下拉是用 `--multi-select` 建的),也不返回 `enableMultiSelect` 字段——**`get-dropdown` 读回无法判断该下拉是否多选**。要判断是否多选,改用 `range read`:它返回的 `cells[].dataValidation.enableMultiSelect` 字段是准确的(实测有效)。 +- **返回**:`dataValidations` 数组,相同选项的单元格聚合为一组,每组包含 `conditionValues`(选项值)、`ranges`(覆盖范围)、`options`(含 `enableMultiSelect` 和 `colorValueMap`)。范围内无下拉列表时 `hasDropdown` 为 false。 ### 删除下拉列表 ``` @@ -91,6 +90,5 @@ Flags: - ★ **`--sheet-id` 获取规范(强制)**:`sheetId` 未知时必须先通过 `dws sheet list --node --format json` 查询,禁止凭空编造(如臆测为 `Sheet1`、`sheet1`、`0`、`default` 等) - `set-dropdown` 在指定范围内设置下拉列表,`--options` 为 JSON 数组,每个元素包含 `value`(必填)和 `color`(可选,`#RRGGBB` 格式)。选项值不能包含英文逗号。`--multi-select` 启用多选模式。如果目标范围已存在下拉列表,会被新配置覆盖 -- `get-dropdown` 查询指定范围内的下拉列表配置,返回 `dataValidations` 数组,相同选项的单元格聚合为一组。判空以 `hasDropdown` 为准(无下拉时为 false,但 `dataValidations` 仍含 1 个全 null 幽灵条目) -- ⚠️ `get-dropdown` 的 `options.multipleValues` 恒为 `null`、无 `enableMultiSelect` 字段,读回无法判断是否多选;要判断是否多选改用 `range read`(其 `dataValidation.enableMultiSelect` 准确) +- `get-dropdown` 查询指定范围内的下拉列表配置,返回 `dataValidations` 数组,相同选项的单元格聚合为一组。无下拉列表时 `hasDropdown` 为 false - `delete-dropdown` 删除指定范围内的下拉列表配置,单元格恢复为普通文本格式。已填写的值不会被清除。目标范围不存在下拉列表时操作仍返回成功 diff --git a/skills/multi/dingtalk-sheet/references/sheet/sheet-edit-rules.md b/skills/multi/dingtalk-sheet/references/sheet/sheet-edit-rules.md new file mode 100644 index 00000000..6a87cd1e --- /dev/null +++ b/skills/multi/dingtalk-sheet/references/sheet/sheet-edit-rules.md @@ -0,0 +1,12 @@ +# Agent 编辑总则 + +## Agent 编辑总则 + +1. **先定位再编辑**:未知 `sheet-id` 时必须先 `dws sheet list --node --format json`,禁止猜 `Sheet1` / `sheet1` / `0` / `default`。用户只给工作表名称时,也先用 `list` 确认真实名称或 ID。 +2. **先读结构再动结构**:合并、冻结、行列分组、最后非空边界都属于工作表结构信息,先读 `dws sheet info --node --sheet-id --format json`。 +3. **按目的选择读取方式**:快速看值和大表分批用 `csv-get`;需要公式、样式、数据验证、超链接、富文本等 per-cell 元数据用 `range read`。 +4. **区分写入成功与内容验收**:`success` / `updatedRows` / `updatedCells` 可确认写请求成功;用户要求内容准确性验收,或涉及复杂公式、格式、结构变更时,再用 `csv-get` / `range read` / `sheet info` 或对应 `list` / `get` 回读。用户明确禁止额外读取时不强制回读。 +5. **大批量纯值不要拼大 JSON**:超过 5 行或 20 个单元格的纯值写入优先 `csv-put`。 +6. **公式是有限回读,不是零错误工具**:写公式前读 [sheet-formula](./sheet-formula.md)。当前没有聚合式 `formula-verify`,只能回读公式文本和计算结果,并检查明显错误值。 +7. **专用操作用专用命令**:搜索用 `find`、替换用 `replace`、清空用 `range clear`、排序用 `range sort`、复制/移动区域用 `range copy-to` / `range move-to`,不要用 `range read` + `range update` 客户端模拟。 +8. **大整数按文本写**:超过 `9007199254740991` 的整数、长数字 ID、订单号、手机号等需要逐位精确的值,不要按 JSON number 或 `int64` / `uint64` 写入;用字符串值 + `object` dtype + 文本格式 `@`。 diff --git a/skills/multi/dingtalk-sheet/references/sheet/sheet-export-index.md b/skills/multi/dingtalk-sheet/references/sheet/sheet-export-index.md new file mode 100644 index 00000000..06f502d0 --- /dev/null +++ b/skills/multi/dingtalk-sheet/references/sheet/sheet-export-index.md @@ -0,0 +1,7 @@ +# 导出与批量 + +| 命令 | 详见 | +|---|---| +| `sheet export` | [`sheet-export.md`](./sheet-export.md) | +| `sheet range batch-*` | [`sheet-batch-operations.md`](./sheet-batch-operations.md) | +| `sheet dimension` | [`sheet-dimension-operations.md`](./sheet-dimension-operations.md) | diff --git a/skills/multi/dingtalk-sheet/references/sheet/sheet-export.md b/skills/multi/dingtalk-sheet/references/sheet/sheet-export.md index 891da573..731ab64a 100644 --- a/skills/multi/dingtalk-sheet/references/sheet/sheet-export.md +++ b/skills/multi/dingtalk-sheet/references/sheet/sheet-export.md @@ -47,16 +47,16 @@ Flags: - 第 21~30 次:每次间隔 15 秒 - **硬上限:最多轮询 30 次(约 5 分钟)**,超时后命令返回错误 -**命令返回**(`--format json`,默认):输出规整 JSON `{success, jobId, downloadUrl[, outputPath]}`。轮询进度以 `[INFO] [N/30] 状态: ...` 打到 stderr,不污染 stdout 的 JSON(可直接 `python3 -m json.tool` 解析 stdout)。 -- `--output` 未指定:JSON 含 `jobId` + `downloadUrl`(链接有时效性,请尽快下载) -- `--output` 指定为文件路径:下载到该路径,JSON 额外含 `outputPath` -- `--output` 指定为已存在目录:自动从 `downloadUrl` 推断文件名保存,JSON 含 `outputPath` +**命令返回**: +- `--output` 未指定:进度日志 + 末尾输出 `jobId` 和 `downloadUrl`(链接有时效性,请尽快下载) +- `--output` 指定为文件路径:下载到该路径并输出 `导出完成: ` +- `--output` 指定为已存在目录:自动从 `downloadUrl` 推断文件名并保存到该目录下 **失败处理(命令内部已处理,Agent 仅需转述)**: -- MCP 返回 `FAILED`:命令立即返回错误并附带失败原因,**禁止自动重试 `dws sheet export`**,告知用户稍后再试 +- 导出任务返回 `FAILED`:命令立即返回错误并附带失败原因,**禁止自动重试 `dws sheet export`**,告知用户稍后再试 - 轮询 30 次仍 `PROCESSING`:命令返回超时错误,告知用户稍后再试 -**限制**:仅支持钉钉在线电子表格(axls)→ xlsx。导出钉钉文字文档请使用 `doc` 产品对应的导出工具。 +**限制**:仅支持钉钉在线电子表格(alxs)→ xlsx。导出钉钉文字文档请使用 `doc` 产品对应的导出工具。 ## 核心工作流 @@ -82,11 +82,11 @@ dws sheet export --node --output ./ | 操作 | 从返回中提取 | 用于 | |------|-------------|------| -| `export` | `downloadUrl`(未指定 --output)/ `outputPath`(指定 --output) | 直接下发给用户或告知文件已保存到本地。命令内部已完成轮询,不要再调用其他 export 相关命令 | +| `export` | `downloadUrl`(未指定 --output)/ `导出完成: `(指定 --output) | 直接下发给用户或告知文件已保存到本地。命令内部已完成轮询,不要再调用其他 export 相关命令 | ## 注意事项 -- ★ `export` 仅支持钉钉在线电子表格(axls)→ xlsx;传入钉钉文字文档会报 `invalidRequest.document.typeIllegal` +- ★ `export` 仅支持钉钉在线电子表格(alxs)→ 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-filter-view.md b/skills/multi/dingtalk-sheet/references/sheet/sheet-filter-view.md index 631a6c6f..89b1e0d3 100644 --- a/skills/multi/dingtalk-sheet/references/sheet/sheet-filter-view.md +++ b/skills/multi/dingtalk-sheet/references/sheet/sheet-filter-view.md @@ -219,7 +219,6 @@ Flags: - **用途**:查看某个筛选视图的当前配置,包括已设置的所有筛选条件详情。 - **场景**:在修改或删除筛选视图前,先确认其当前状态;或在 `update-criteria` 后验证条件是否生效。 - **区分**:`info` 返回单个视图的完整信息(含 criteria);`list` 返回所有视图的列表概要。`info` 需要指定 `--filter-view-id`,ID 可通过 `list` 获取。 -- **实现**:内部调用 `get_filter_views` 获取全部列表后按 ID 过滤。 ### 列出筛选视图所有列条件 ``` @@ -238,7 +237,6 @@ Flags: - **用途**:查看某个筛选视图当前设置了哪些列的筛选条件,包括每列的条件类型和具体规则。 - **场景**:在管理筛选条件(修改/删除特定列条件)前,先了解当前视图有哪些条件;或排查筛选结果不符合预期时检查条件配置。 - **区分**:`list-criteria` 返回所有列的条件(按列偏移量为 key 的对象);`get-criteria` 只返回指定列的条件。如果没有设置任何条件,返回空对象 `{}`。 -- **实现**:内部调用 `get_filter_views` 获取视图详情后提取 `criteria` 字段。 ### 获取单列筛选条件 ``` @@ -261,7 +259,6 @@ Flags: - **用途**:查看某个筛选视图中指定列当前设置的筛选条件,包括条件类型、运算符和比较值。 - **场景**:在修改某列条件前,先查看其当前配置;或验证 `update-criteria` 后该列条件是否正确。 - **区分**:`get-criteria` 只返回指定列的条件;`list-criteria` 返回所有列的条件。`--column` 为列偏移量(从 0 开始),相对于筛选视图范围首列。 -- **实现**:内部调用 `get_filter_views` 获取视图详情后按列偏移量过滤 `criteria` 中的对应条件。 ## 核心工作流 @@ -328,7 +325,7 @@ dws sheet filter-view create --node --sheet-id \ - ★ **`--sheet-id` 获取规范(强制)**:`sheetId` 未知时必须先通过 `dws sheet list --node --format json` 查询,禁止凭空编造(如臆测为 `Sheet1`、`sheet1`、`0`、`default` 等) - ★ **全局筛选(filter)与筛选视图(filter-view)的区别**:全局筛选影响所有协作者看到的数据展示,每个工作表最多一个;筛选视图是个人化的,互不影响。用户只说"筛选"时默认走 `filter` 系列 - `filter-view list` 获取指定工作表的所有筛选视图列表,返回的 `id` 可用于后续 info / update / delete / update-criteria / delete-criteria / list-criteria / get-criteria 的 `--filter-view-id` -- `filter-view info` 获取单个筛选视图的完整信息(含 criteria),内部复用 `get_filter_views` MCP 按 ID 过滤 +- `filter-view info` 获取单个筛选视图的完整信息(含 criteria),按 `--filter-view-id` 返回对应视图 - `filter-view list-criteria` 列出指定筛选视图已设置的所有列条件,返回按列偏移量为 key 的对象;无条件时返回空对象 `{}` - `filter-view get-criteria` 获取指定列的条件详情,`--column` 为列偏移量(从 0 开始);该列无条件时返回错误提示 - `filter-view create` 创建筛选视图时 `--range` 应包含表头行。`--criteria` 可选,不传则创建后无筛选条件,后续可通过 `filter-view update-criteria` 设置 diff --git a/skills/multi/dingtalk-sheet/references/sheet/sheet-filter.md b/skills/multi/dingtalk-sheet/references/sheet/sheet-filter.md index 36d69dc4..0e65607b 100644 --- a/skills/multi/dingtalk-sheet/references/sheet/sheet-filter.md +++ b/skills/multi/dingtalk-sheet/references/sheet/sheet-filter.md @@ -32,7 +32,7 @@ Flags: - **用途**:查看当前工作表上是否存在全局筛选及其配置。 - **场景**:在修改或删除筛选前,先读取当前筛选配置;创建筛选前先确认是否已存在(每个工作表只能有一个筛选)。 - **区分**:全局筛选(filter)影响所有协作者看到的数据展示;筛选视图(filter-view)是个人化的。 -- **返回**:`range`(筛选范围,A1 表示法)、`id`(筛选 ID)和 `criteria`(各列条件对象,key 为列偏移量;无条件时为 `{}`)。如果未设置筛选,返回筛选信息为空。 +- **返回**:`range`(筛选范围,A1 表示法)和 `columnFilterCriteria`(各列条件,key 为列偏移量)。如果未设置筛选,返回筛选信息为空。 ### 创建筛选 ``` @@ -152,7 +152,7 @@ Flags: | 操作 | 从返回中提取 | 用于 | |------|-------------|------| -| `filter get` | `range`(筛选范围)、`id`、`criteria`(各列条件对象,无条件为 `{}`) | 查看当前筛选配置,确认筛选是否存在 | +| `filter get` | `range`(筛选范围)、`columnFilterCriteria`(各列条件) | 查看当前筛选配置,确认筛选是否存在 | | `filter create` | 筛选创建成功的确认 | 确认筛选已建立,后续可通过 `filter update` 设置条件 | | `filter delete` | 删除成功的确认 | 确认筛选已删除 | | `filter update` | 更新成功的确认 | 确认条件已设置 | @@ -164,7 +164,7 @@ Flags: - ★ **`--sheet-id` 获取规范(强制)**:`sheetId` 未知时必须先通过 `dws sheet list --node --format json` 查询,禁止凭空编造(如臆测为 `Sheet1`、`sheet1`、`0`、`default` 等) - ★ **全局筛选(filter)与筛选视图(filter-view)的区别**:全局筛选影响所有协作者看到的数据展示,每个工作表最多一个;筛选视图是个人化的,互不影响。用户只说"筛选"时默认走 `filter` 系列 -- `filter get` 获取工作表的全局筛选信息,返回 `range`(筛选范围)、`id` 和 `criteria`(各列条件对象,无条件时为 `{}`)。无筛选时返回空 +- `filter get` 获取工作表的全局筛选信息,返回 `range`(筛选范围)和 `columnFilterCriteria`(各列条件)。无筛选时返回空 - `filter create` 创建全局筛选时 `--range` 必须包含表头行(如 `A1:E100`),不能只包含数据行。每个工作表只能有一个筛选,已存在时报错 - `filter create` 的 `--criteria` 可选,不传则仅创建空筛选框架,后续通过 `filter update` 设置条件 - `filter delete` 删除后所有筛选条件丢失且所有被隐藏行重新显示,不可恢复 diff --git a/skills/multi/dingtalk-sheet/references/sheet/sheet-format-index.md b/skills/multi/dingtalk-sheet/references/sheet/sheet-format-index.md new file mode 100644 index 00000000..52860b09 --- /dev/null +++ b/skills/multi/dingtalk-sheet/references/sheet/sheet-format-index.md @@ -0,0 +1,7 @@ +# 格式与样式 + +| 命令 | 详见 | +|---|---| +| `sheet range set-style` | [`sheet-style-format.md`](./sheet-style-format.md) | +| `sheet dropdown` | [`sheet-dropdown.md`](./sheet-dropdown.md) | +| `sheet cond-format` | [`sheet-conditional-format.md`](./sheet-conditional-format.md) | diff --git a/skills/multi/dingtalk-sheet/references/sheet/sheet-formula.md b/skills/multi/dingtalk-sheet/references/sheet/sheet-formula.md new file mode 100644 index 00000000..97b5831d --- /dev/null +++ b/skills/multi/dingtalk-sheet/references/sheet/sheet-formula.md @@ -0,0 +1,117 @@ +# 公式写入与有限回读校验 + +## 使用场景 + +用户说"写公式/计算列/辅助列/总计/占比/增长率/查找计算/自动计算"时使用本页。 + +- 能由表内其他单元格推导的派生值,优先写公式,不要写一次性的静态结果。 +- 写公式前先读表头和 3-5 行样本,确认列含义、数据类型、真实行号和目标范围。 +- 用户明确要求"辅助列"时,需要真实写入辅助列公式;不要只用条件格式或本地计算绕过。 + +## 当前能力边界 + +- 写公式:使用 `dws sheet range update`。 +- 公式载体:公式写在 cell object 的 `text` 字段中,例如 `{"type":"text","text":"=SUM(B2:B10)"}`。 +- 读取公式文本:使用 `dws sheet range read --value-render-option formula`。 +- 读取计算结果:使用 `dws sheet range read --value-render-option raw_value` 或默认 `formatted_value`。 +- 当前没有聚合式 `formula-verify` 工具,不能宣称已经完成全表零错误校验。 +- `csv-put` / `append` 不作为公式写入协议;`=` 开头内容会按普通值处理。需要公式时用 `range update`。 + +## 命令选择 + +| 目的 | 命令 | 说明 | +|------|------|------| +| 写入少量或中等范围公式 | `range update` | `--values` 必须是二维 cell object,维度与 `--range` 完全一致 | +| 查看已写入的公式文本 | `range read --value-render-option formula` | 确认公式本身是否落表、范围和引用是否正确 | +| 查看公式计算结果 | `range read --value-render-option raw_value` | 用于数值对账、错误值检查 | +| 查看格式化展示结果 | `range read` 或 `csv-get` 默认模式 | 用于用户肉眼看到的展示值检查 | + +## 推荐流程 + +1. 用 `dws sheet list --node --format json` 获取真实 `sheetId`。 +2. 用 `range read` 或 `csv-get` 读取表头和样本数据,确认目标列与行号。 +3. 明确相对引用和绝对引用:向下填充时检查固定汇率、税率、查找表、标题行是否需要 `$` 锁定。 +4. 用 `range update` 写入公式矩阵;矩阵行列数必须与 `--range` 完全一致。 +5. 用 `range read --value-render-option formula` 回读公式文本。 +6. 用 `range read --value-render-option raw_value` 回读计算结果,并检查明显错误值。 +7. 若发现错误值,先定位依赖单元格、空值、除数为 0、引用范围越界或函数名错误,再重写公式并重新回读。 + +## 写入示例 + +### 单格公式 + +```bash +dws sheet range update --node --sheet-id --range "D2" \ + --values '[[{"type":"text","text":"=B2*C2"}]]' --format json +``` + +### 整列公式 + +```bash +dws sheet range update --node --sheet-id --range "D2:D5" \ + --values '[ + [{"type":"text","text":"=B2*C2"}], + [{"type":"text","text":"=B3*C3"}], + [{"type":"text","text":"=B4*C4"}], + [{"type":"text","text":"=B5*C5"}] + ]' --format json +``` + +### 含绝对引用 + +税率在 `G1` 时,向下填充应锁定税率单元格: + +```bash +dws sheet range update --node --sheet-id --range "E2:E5" \ + --values '[ + [{"type":"text","text":"=D2*$G$1"}], + [{"type":"text","text":"=D3*$G$1"}], + [{"type":"text","text":"=D4*$G$1"}], + [{"type":"text","text":"=D5*$G$1"}] + ]' --format json +``` + +## 回读校验 + +### 1. 回读公式文本 + +```bash +dws sheet range read --node --sheet-id --range "D2:D5" \ + --value-render-option formula --format json +``` + +检查点: +- `value` 应返回以 `=` 开头的公式文本。 +- 行号、列号、相对引用、绝对引用应与写入计划一致。 +- 无公式的单元格在 `formula` 模式下可能回退为原始值,不能把这种回退误判为公式已写入。 + +### 2. 回读计算结果 + +```bash +dws sheet range read --node --sheet-id --range "D2:D5" \ + --value-render-option raw_value --format json +``` + +检查点: +- 数值结果应与样本手算或本地复算一致。 +- 检查结果中是否出现 `#REF!` / `#DIV/0!` / `#VALUE!` / `#NAME?` / `#NULL!` / `#NUM!` / `#N/A`。 +- 对大范围公式,至少抽样检查首行、末行、边界行和异常数据行;用户要求全量处理时,应分批回读并断言处理数量。 + +### 3. 当前不等价于公式零错误校验 + +有限回读只能发现公式文本错误、明显运行时错误值和样本不一致。它不能替代未来可能的聚合式公式校验工具,也不能证明整本表所有公式都没有隐藏错误。 + +## 常见错误 + +- 用 `csv-put` 写 `=SUM(...)`,导致公式没有按公式协议落表。 +- 用原始二维数组 `--values '[["=B2*C2"]]'`,而不是 cell object。 +- 写整列公式时只写第一行,忘记把 `--range` 和 `--values` 扩成同样行数。 +- 复制公式时没有锁定固定引用,例如税率、汇率、查找表范围。 +- 没有回读 `formula` 模式,只看写入返回 `success`。 +- 只回读展示值,不检查 `raw_value` 中的错误值。 + +## 关联文档 + +- [sheet-write-data](./sheet-write-data.md):`range update` 的 `--values` cell object 结构、维度校验、富格式能力。 +- [sheet-read-data](./sheet-read-data.md):`value-render-option` 的 `formatted_value` / `raw_value` / `formula` 读取模式。 +- [sheet-conditional-format](./sheet-conditional-format.md):条件格式中的 `formulaCondition` 与辅助列公式的职责边界。 diff --git a/skills/multi/dingtalk-sheet/references/sheet/sheet-hard-constraints.md b/skills/multi/dingtalk-sheet/references/sheet/sheet-hard-constraints.md new file mode 100644 index 00000000..1426ebb9 --- /dev/null +++ b/skills/multi/dingtalk-sheet/references/sheet/sheet-hard-constraints.md @@ -0,0 +1,21 @@ +# 全局硬约束 + +## 全局硬约束 + +1. **`--sheet-id` 禁止臆测**:未知时必须 `dws sheet list --node --format json` 查询,禁止编造 `Sheet1`/`sheet1`/`0`/`default` +2. **合并单元格是结构信息**:`dws sheet info --node --sheet-id --format json` 返回 `mergedRanges`(如 `["C7:D11"]`);不要在 `range read` / `csv-get` 里寻找合并信息 +3. **冻结行列是工作表元数据**:`sheet info` 顶层返回 `frozenRowCount` / `frozenColumnCount`,分别表示从顶部第 1 行、左侧第 A 列开始冻结的数量;`0` 表示未冻结。不要在 `range read` / `csv-get` 里寻找冻结信息 +4. **行列分组必须回读**:创建/取消分组后,用 `dws sheet info --node --sheet-id --format json` 回读 `rowGroups` / `columnGroups`。分组项返回 `range`、起止行列、`count`、`level`、`collapsed`;`level` 是 1-based 展示层级,不使用 `depth` +5. **最后非空坐标只用 A1 语义**:`sheet info` 通过 `nonEmptyRange` 返回 A1/UI 边界;优先使用 `nonEmptyRange.range`,需要追加行/列时使用 `nonEmptyRange.lastRow` / `nonEmptyRange.lastColumn`。不要使用旧的 0-based 字段 `lastNonEmptyRow` / `lastNonEmptyColumn` +6. **`range update` 维度校验**:`--values` 行列数必须与 `--range` 完全一致;只接 `--values` 一个数据参数,cell `type` 仅支持 `text` / `richText`;整格超链接通过 cell-level `hyperlink` 表达,富文本片段链接才使用 `richText.texts[].type="link"` +7. **dataValidation 三语义**:不传 `dataValidation` 字段=保留原 DV;`dataValidation:{type:"none"}`=显式清除;`dataValidation:{type:"dropdown"/"checkbox",...}`=覆盖。`{}` 跳过亦保留原 DV +8. **hyperlink 三语义**:不传 `hyperlink` 字段=保留原整格超链接;`hyperlink:{type:"none"}`=显式清除;`hyperlink:{type:"path"/"sheet"/"range",link,...}`=覆盖。Agent 调用不要用 `hyperlink:null`,避免网关/Schema 过滤 null 字段 +9. **样式写法**:cell-level 样式用 `cellStyles` 或 `range set-style`;richText 片段级样式才用子项 `style`。不要在 `type:"text"` 顶层使用旧 `style` 字段 +10. **用专用命令不用组合模拟**:搜索→`find`、替换→`replace`、清空→`range clear`、排序→`range sort`、填充→`range fill`、复制区域→`range copy-to`、移动区域→`range move-to`、移动行列→`move-dimension` +11. **大批量纯值用 `csv-put`**(>5 行或 >20 单元格),不用 `range update` +12. **单元格图片用 `write-image`**(`range update` 不支持图片参数) +13. **`export` 禁止自行轮询**(CLI 内部已完成渐进式退避,最多 30 次约 5 分钟) +14. **单次调用上限**:`range update` / `set-style` 行数 ≤ 1000,单元格总数建议 ≤ 5000(硬限 30000) +15. **大整数精度保护**:超过 `9007199254740991` 的整数和长数字标识符必须按字符串写入;需要固定文本格式时使用 `cellStyles.numberFormat: "@"` +16. **批量写操作推荐用 `batch-update`**:对多个区域重复调用同一写入工具时,推荐合并为单次原子请求(详见 [sheet-batch-operations](./sheet-batch-operations.md));逐个调用非原子,中途失败会留下半成品 +17. **关键区分**:sheet(电子表格/单元格读写)vs aitable(AI多维表/结构化记录)vs doc(文档) diff --git a/skills/multi/dingtalk-sheet/references/sheet/sheet-import.md b/skills/multi/dingtalk-sheet/references/sheet/sheet-import.md new file mode 100644 index 00000000..d2e2687c --- /dev/null +++ b/skills/multi/dingtalk-sheet/references/sheet/sheet-import.md @@ -0,0 +1,18 @@ +# 导入本地表格 + +## 导入本地表格 + +用户说"导入Excel/把xlsx转为在线表格/上传表格并在线编辑"时: +```bash +# 上传并转换为在线电子表格(转换后返回 nodeId,可用 sheet 命令操作) +dws drive upload --file ./data.xlsx --convert + +# 指定上传到某个文件夹 +dws drive upload --file ./data.xlsx --folder --convert + +# 指定上传到知识库 +dws drive upload --file ./data.xlsx --workspace --convert +``` +- `--convert` 是关键参数,不加则仅上传为附件,不会转换为在线电子表格 +- 转换后的文档为 `axls` 格式,可用 `sheet` 全部命令操作 +- 支持 `.xlsx` / `.xls` / `.csv` 等格式 diff --git a/skills/multi/dingtalk-sheet/references/sheet/sheet-media-image.md b/skills/multi/dingtalk-sheet/references/sheet/sheet-media-image.md index 060c55c9..06aba4b7 100644 --- a/skills/multi/dingtalk-sheet/references/sheet/sheet-media-image.md +++ b/skills/multi/dingtalk-sheet/references/sheet/sheet-media-image.md @@ -11,7 +11,7 @@ 用户说"写入图片/插入图片/加图片/放图片到单元格/嵌入图片到表格": - 写入图片 → `write-image`(需表格 ID + 工作表 ID + 单元格范围 + 本地图片路径) -- 禁止使用 `range update` 写入图片,因为 `update_range` 的 MCP 工具不支持图片类型参数,调用必定失败。必须使用 `write-image` 命令 +- 禁止使用 `range update` 写入图片;图片对象必须使用 `write-image` 命令 - 用户指定了图片尺寸 → `write-image --width N --height M` ### 浮动图片 @@ -48,8 +48,6 @@ Flags: --mime-type string 文件 MIME 类型 (默认根据扩展名推断) ``` -`--format json` 输出规整 JSON:`{success, resourceId, resourceUrl, fileName, fileSize}`。`resourceUrl` 可用于 `create-float-image` 的 `--src`。 - ### 上传图片并写入表格单元格 ``` Usage: @@ -205,10 +203,13 @@ dws sheet write-image --node --sheet-id --range C3:C3 --fil # 4. 完整流程: 创建表格 → 写表头 → 写入图片 dws sheet create --name "产品目录" -f json -# 提取 nodeId 后: -dws sheet range update --node --sheet-id Sheet1 --range "A1:B1" --values '[[{"type":"text","text":"产品名称"},{"type":"text","text":"产品图片"}]]' -f json -dws sheet range update --node --sheet-id Sheet1 --range "A2:A2" --values '[[{"type":"text","text":"MacBook Pro"}]]' -f json -dws sheet write-image --node --sheet-id Sheet1 --range B2:B2 --file ./macbook.png --width 150 --height 100 -f json +# 提取 nodeId 后,先用 list 获取真实 sheetId: +dws sheet list --node -f json +dws sheet range update --node --sheet-id --range "A1:B1" \ + --values '[[{"type":"text","text":"产品名称"},{"type":"text","text":"产品图片"}]]' -f json +dws sheet range update --node --sheet-id --range "A2:A2" \ + --values '[[{"type":"text","text":"MacBook Pro"}]]' -f json +dws sheet write-image --node --sheet-id --range B2:B2 --file ./macbook.png --width 150 --height 100 -f json ``` ## 上下文传递 @@ -229,7 +230,7 @@ dws sheet write-image --node --sheet-id Sheet1 --range B2:B2 --file ./ - ★ **`--sheet-id` 获取规范(强制)**:`sheetId` 未知时必须先通过 `dws sheet list --node --format json` 查询,禁止凭空编造(如臆测为 `Sheet1`、`sheet1`、`0`、`default` 等) - `media-upload` 是两步自动完成的流程 (获取附件上传凭证 → OSS 上传),无需手动分步操作 - `write-image` 是三步自动完成的流程 (获取附件上传凭证 → OSS 上传 → 写入图片到单元格),无需手动分步操作 -- ★ 向表格单元格中写入图片必须使用 `write-image`,禁止使用 `range update`。`range update` 底层调用的 `update_range` MCP 工具不支持图片类型参数,调用会失败 +- ★ 向表格单元格中写入图片必须使用 `write-image`,禁止使用 `range update`。`range update` 不支持图片对象 - `write-image` 与 `media-upload` 的区别:`media-upload` 仅上传附件到表格获取 resourceId;`write-image` 在上传后还会将图片写入指定单元格 - `create-float-image` 创建浮动图片前必须先通过 `media-upload` 上传图片获取 `resourceUrl`,再将其作为 `--src` 传入。`--src` 的格式为 `/core/api/resources/img/...`,不能直接传外部 URL - `create-float-image` 的 `--range` 使用 A1 表示法指定锚点单元格(如 `A1`、`B3`),支持带工作表前缀(如 `Sheet1!A1`) diff --git a/skills/multi/dingtalk-sheet/references/sheet/sheet-nodeid.md b/skills/multi/dingtalk-sheet/references/sheet/sheet-nodeid.md new file mode 100644 index 00000000..7919f336 --- /dev/null +++ b/skills/multi/dingtalk-sheet/references/sheet/sheet-nodeid.md @@ -0,0 +1,5 @@ +# nodeId 说明 + +## nodeId 说明 + +`--node` 同时支持文档 ID、URL、分享链接。`drive list` 返回中必须用 `fileId`(UUID 格式),禁止用 `dentryId`(纯数字)。 diff --git a/skills/multi/dingtalk-sheet/references/sheet/sheet-pivot-table.md b/skills/multi/dingtalk-sheet/references/sheet/sheet-pivot-table.md index 88547ca0..cc23a0af 100644 --- a/skills/multi/dingtalk-sheet/references/sheet/sheet-pivot-table.md +++ b/skills/multi/dingtalk-sheet/references/sheet/sheet-pivot-table.md @@ -1,67 +1,292 @@ -# 透视表管理 +# 透视表 (pivot-table) + +## 真对象硬约束 + +当用户要求"透视表 / 分组汇总 / 交叉分析 / 按 X 统计 Y"时,**必须**通过 `pivot-table create` 创建真实的透视表对象。**禁止**用 `SUMIFS` / `COUNTIFS` 等普通公式 + `csv-put` 在原表中拼一张"看起来像透视表的汇总表"来代替——静态公式无法随源数据动态更新,且失去交互能力。判断标准:交付后 `pivot-table list` 必须能返回该对象。 ## 使用场景 -- 用户要按部门、月份等维度汇总数据 → `pivot-table create` -- 用户要查看现有透视表或取得 `pivotTableId` → `pivot-table list` -- 用户要调整行列、聚合方式或显示选项 → `pivot-table update` -- 用户明确要求删除透视表 → `pivot-table delete`(不可逆,必须先确认) +读写透视表对象。本 reference 覆盖 4 个命令: -透视表是原生对象,会随源数据更新;不要用一组普通汇总公式冒充透视表。 +| 操作需求 | 使用命令 | 说明 | +|---------|---------|------| +| 查看已有透视表 | `pivot-table list` | 获取透视表的结构、数据源和配置 | +| 创建透视表 | `pivot-table create` | 创建透视表对象 | +| 更新透视表 | `pivot-table update` | 更新透视表配置(行/列/值/筛选字段) | +| 删除透视表 | `pivot-table delete` | 删除透视表 | -## 列出与查看 +典型工作流:先读取现有透视表了解配置 -> 执行创建/更新/删除 -> 再次读取验证结果。 -```bash -dws sheet pivot-table list --node --sheet-id -dws sheet pivot-table list --node --sheet-id --pivot-table-id +## 行/值字段映射(创建前必做) + +创建透视表前先识别用户需求中的分组维度和聚合指标,**不要搞反**: + +- **rows(行字段)** = 分组维度,即"按什么分组"。例:部门、地区、医生、产品类别 +- **values(值字段)** = 聚合指标,即"统计什么数值"。例:销售额(`summarize_by: "sum"`)、订单数(`summarize_by: "count"`) +- **columns(列字段)** = 交叉维度(可选),即"再按什么横向展开"。例:月份、性别 + +| 用户说 | rows | values | columns | +|--------|------|--------|---------| +| "按部门统计人数" | 部门 | 姓名(`"count"`) | -- | +| "按医生统计费用和结余" | 主管医生 | 费用(`"sum"`)、结余(`"sum"`) | -- | +| "各部门男女人数" | 部门 | 姓名(`"count"`) | 性别 | + +**常见配置错误(必须注意)**: +- **数据源范围必须精确**:透视表的数据源范围必须包含表头行,且精确覆盖全部数据行列。范围过大(包含空行/空列)或过小(遗漏数据列)都会导致透视表结果错误 +- **行列字段选择要匹配用户意图**:用户说"按商品统计金额" -> 行字段=商品,值字段=金额(`summarize_by: "sum"`)。不要把行列字段搞反 +- **聚合类型要匹配**:用户说"统计数量" -> `"count"`;"统计总额" -> `"sum"`;"统计平均" -> `"average"` +- **创建后必须验证**:调用 `pivot-table list` 确认透视表结构正确 + +## 命令详细参考 + +### 获取透视表 +``` +Usage: + dws sheet pivot-table list [flags] +Example: + # 列出所有透视表 + dws sheet pivot-table list --node NODE_ID --sheet-id SHEET_ID + + # 获取单个透视表详情 + dws sheet pivot-table list --node NODE_ID --sheet-id SHEET_ID --pivot-table-id PT_ID +Flags: + --node string 表格文档 ID 或 URL (必填) + --sheet-id string 工作表 ID 或名称 (必填) + --pivot-table-id string 透视表 ID (可选,不传则返回全部) ``` -不传 `--pivot-table-id` 时列出工作表中的全部透视表;传入时返回单个透视表详情。 +### 创建透视表 +``` +Usage: + dws sheet pivot-table create [flags] +Example: + # 按部门统计销售额(默认自动新建工作表存放) + dws sheet pivot-table create --node NODE_ID \ + --source "'Sheet1'!A1:D100" \ + --properties '{ + "rows": [{"field": "部门"}], + "values": [{"field": "销售额", "summarize_by": "sum"}], + "show_row_grand_total": true + }' -## 创建 + # 指定放置到已有工作表的特定位置 + dws sheet pivot-table create --node NODE_ID \ + --source "'Sheet1'!A1:E200" \ + --target-sheet-id TARGET_SHEET_ID --target-position "A1" \ + --properties '{ + "rows": [{"field": "部门"}], + "values": [{"field": "销售额", "summarize_by": "sum"}] + }' + + # 通过文件传入配置 + dws sheet pivot-table create --node NODE_ID \ + --source "'Sheet1'!A1:D50" --properties @pivot.json +Flags: + --node string 表格文档 ID 或 URL (必填) + --source string 数据源区域,A1 表示法含 sheet 前缀 (必填,如 "'Sheet1'!A1:D100") + --properties string 透视表配置 JSON (必填,含 rows/columns/values/filters) + --target-sheet-id string 目标工作表 ID 或名称 (可选,不传则自动新建工作表) + --target-position string 透视表放置位置 (可选,A1 格式单个 cell,如 "B5",不传默认 A1) +``` + +### 更新透视表 +``` +Usage: + dws sheet pivot-table update [flags] +Example: + # 先获取现有配置 + dws sheet pivot-table list --node NODE_ID --sheet-id SHEET_ID --pivot-table-id PT_ID + + # 修改后回写 + dws sheet pivot-table update --node NODE_ID --sheet-id SHEET_ID --pivot-table-id PT_ID \ + --properties '{ + "rows": [{"field": "部门"}], + "values": [ + {"field": "销售额", "summarize_by": "sum"}, + {"field": "订单号", "summarize_by": "count", "display_name": "订单数"} + ], + "show_row_grand_total": true + }' +Flags: + --node string 表格文档 ID 或 URL (必填) + --sheet-id string 工作表 ID 或名称 (必填) + --pivot-table-id string 透视表 ID (必填,可通过 pivot-table list 获取) + --properties string 透视表配置 JSON (必填) +``` + +### 删除透视表 +``` +Usage: + dws sheet pivot-table delete [flags] +Example: + dws sheet pivot-table delete --node NODE_ID --sheet-id SHEET_ID --pivot-table-id PT_ID --yes +Flags: + --node string 表格文档 ID 或 URL (必填) + --sheet-id string 工作表 ID 或名称 (必填) + --pivot-table-id string 透视表 ID (必填,可通过 pivot-table list 获取) +``` + +> [强制] 危险操作:删除不可恢复。必须先向用户展示操作摘要并获得明确同意,用户同意后才加 `--yes` 执行。 + +## `--properties` JSON Schema 速查 + +**顶层字段**: + +| 字段 | 类型 | 必填 | 说明 | +|------|------|------|------| +| `rows` | object[] | 否 | 行字段数组(分组维度),详见下方「rows/columns 字段项」表 | +| `columns` | object[] | 否 | 列字段数组(交叉维度),结构同 rows | +| `values` | object[] | 是 | 值字段数组(聚合指标,至少一项),详见下方「values 字段项」表 | +| `filters` | object[] | 否 | 筛选字段数组,详见下方「filters 字段项」表 | +| `show_row_grand_total` | boolean | 否 | 是否显示行总计,默认 true | +| `show_col_grand_total` | boolean | 否 | 是否显示列总计,默认 true | +| `show_subtotals` | boolean | 否 | 是否显示分类小计,默认 true | +| `repeat_row_labels` | boolean | 否 | 是否显示重复项标签,默认 false | +| `collapse` | object | 否 | 行字段折叠状态:字段名 -> 要折叠的项目列表,如 `{"部门": ["A组", "B组"]}` | + +**rows/columns 字段项**: + +| 字段 | 类型 | 必填 | 说明 | +|------|------|------|------| +| `field` | string | 是 | 列名(表头文本),必须与数据源首行的列名完全匹配 | +| `display_name` | string | 否 | 显示名称(不传时使用 field 值) | + +**filters 字段项**: + +| 字段 | 类型 | 必填 | 说明 | +|------|------|------|------| +| `field` | string | 是 | 列名(表头文本),必须与数据源首行的列名完全匹配 | +| `display_name` | string | 否 | 显示名称(不传时使用 field 值) | + +**values 字段项**: + +| 字段 | 类型 | 必填 | 说明 | +|------|------|------|------| +| `field` | string | 是 | 列名(表头文本),必须与数据源首行的列名完全匹配 | +| `summarize_by` | string | 否 | 聚合方式,默认 sum,详见下方枚举表 | +| `display_name` | string | 否 | 显示名称(不传时自动生成,如"求和 - 销售额") | +| `show_data_as` | string | 否 | 值显示方式:`normal`/`percent_of_row_total`/`percent_of_col_total`/`percent_of_grand_total` | + +**summarize_by 枚举值**: + +| 值 | 说明 | +|----|------| +| `sum` | 求和(默认) | +| `count` | 计数 | +| `average` | 平均值 | +| `max` | 最大值 | +| `min` | 最小值 | +| `product` | 乘积 | +| `count_numbers` | 数值计数 | +| `std_dev` | 标准偏差 | +| `std_dev_p` | 总体标准偏差 | +| `var` | 方差 | +| `var_p` | 总体方差 | +| `distinct` | 去重计数 | +| `median` | 中位数 | + +## `--source` 数据源格式 + +- 格式:`'SheetName'!StartCell:EndCell` +- 示例:`'Sheet1'!A1:D100`、`'销售数据'!A1:F500` +- 必须包含表头行(通常从第 1 行开始) +- sheet 名称用单引号包裹(含空格或特殊字符时必须) +- MCP 内部自动将 source 解析为内部 sheetId + ISheetRange,CLI 直接透传即可 + +## 高级功能示例 ```bash +# 含折叠 + show_data_as dws sheet pivot-table create --node \ - --source "'Data'!A1:D100" \ + --source "'Sheet1'!A1:E200" \ --properties '{ - "rows":[{"field":"部门"}], - "columns":[{"field":"月份"}], - "values":[{"field":"销售额","summarize_by":"sum"}], - "show_row_grand_total":true + "rows": [{"field": "部门"}], + "values": [ + {"field": "销售额", "summarize_by": "sum"}, + {"field": "订单数", "summarize_by": "count", "show_data_as": "percent_of_col_total"} + ], + "collapse": {"部门": ["A组"]}, + "show_row_grand_total": true }' ``` -可选参数: +> collapse/show_data_as 为可选透传字段,CLI 和 MCP 层不做额外校验,直接传递给底层引擎处理。 -- `--target-sheet-id`:将透视表放到指定工作表;不传时由服务端创建目标工作表。 -- `--target-position`:目标起始单元格,例如 `B2`。 -- `--properties @pivot.json`:从文件读取配置。 - -`--source` 必须包含工作表前缀并覆盖表头和数据,例如 `"'Data'!A1:D100"`。创建时 `properties.values` 必须是非空数组。 - -## properties 结构 - -- `rows` / `columns` / `filters`:字段数组,每项至少包含非空 `field`。 -- `values`:聚合字段数组,每项至少包含 `field`;创建时至少一项。 -- `summarize_by`:`sum`、`count`、`average`、`max`、`min`、`product`、`count_numbers`、`std_dev`、`std_dev_p`、`var`、`var_p`、`distinct`、`median`。 -- `collapse`:字段折叠配置,可为对象或数组。 -- 显示选项:`show_row_grand_total`、`show_col_grand_total`、`show_subtotals`、`repeat_row_labels`。 - -## 更新 +## 核心工作流 ```bash -dws sheet pivot-table update --node --sheet-id \ - --pivot-table-id \ - --properties '{"show_subtotals":false}' +# -- 工作流 1: 创建简单分组汇总 -- + +# 1. 先查 sheetId +dws sheet list --node -f json + +# 2. 查看数据范围确认列名和边界 +dws sheet csv-get --node --sheet-id --range "A1:F5" + +# 3. 创建透视表(按部门统计销售额) +dws sheet pivot-table create --node \ + --source "'Sheet1'!A1:D100" \ + --properties '{ + "rows": [{"field": "部门"}], + "values": [{"field": "销售额", "summarize_by": "sum"}], + "show_row_grand_total": true + }' + +# 4. 验证创建结果(用 create 返回的 targetSheetId 查询) +dws sheet pivot-table list --node --sheet-id + +# -- 工作流 2: 多维度交叉分析 -- + +dws sheet pivot-table create --node \ + --source "'Sheet1'!A1:E200" \ + --properties '{ + "rows": [{"field": "部门"}, {"field": "产品"}], + "columns": [{"field": "季度"}], + "values": [ + {"field": "销售额", "summarize_by": "sum"}, + {"field": "订单号", "summarize_by": "count", "display_name": "订单数"} + ], + "show_row_grand_total": true, + "show_col_grand_total": true, + "show_subtotals": true + }' + +# -- 工作流 3: 更新透视表配置 -- + +# 先获取现有配置 +dws sheet pivot-table list --node --sheet-id --pivot-table-id + +# 修改后回写(增加一个值字段) +dws sheet pivot-table update --node --sheet-id --pivot-table-id \ + --properties '{ + "rows": [{"field": "部门"}], + "values": [ + {"field": "销售额", "summarize_by": "sum"}, + {"field": "利润", "summarize_by": "average", "display_name": "平均利润"} + ], + "show_row_grand_total": true + }' ``` -Open 接口支持部分更新,因此更新显示选项时不需要重复传入 `values`。更新行列或聚合字段时,应先用 `list --pivot-table-id` 读取当前配置,再传入需要变更的字段。 +## 上下文传递 -## 删除 +| 操作 | 从返回中提取 | 用于 | +|------|-------------|------| +| `pivot-table list` | `pivotTables[].id` | 后续 update / delete 的 `--pivot-table-id` | +| `pivot-table list --pivot-table-id` | 完整配置(rows/columns/values/filters/collapse/options) | update 时作为基础配置修改后回写 | +| `pivot-table create` | `pivotTable.id` | 后续 update / delete 的 `--pivot-table-id` | +| `pivot-table create` | `pivotTable.targetSheetId` | 后续 list / update / delete 的 `--sheet-id`(透视表所在工作表) | +| `pivot-table delete` | `message` | 确认删除完成 | +| `sheet list` | 工作表的 `sheetId` | 所有 pivot-table 命令的 `--sheet-id` | -```bash -dws sheet pivot-table delete --node --sheet-id \ - --pivot-table-id --yes -``` +## 注意事项 -删除不可恢复。Agent 必须先展示目标文档、工作表和 `pivotTableId`,获得用户明确同意后才能附加 `--yes`。 +- [强制] **`--sheet-id` 获取规范**:`sheetId` 未知时必须先通过 `dws sheet list --node --format json` 查询,禁止凭空编造(如臆测为 `Sheet1`、`sheet1`、`0`、`default` 等) +- [强制] **创建后必须验证**:透视表创建后必须调用 `pivot-table list` 验证配置是否正确 +- [强制] **pivot-table-id 禁止臆测**:必须通过 `pivot-table list` 获取真实的透视表 ID,不可编造 +- [强制] **source 必须精确**:数据源范围必须从表头行开始,精确覆盖数据区域,先用 `csv-get` 确认数据边界 +- **field 名称必须准确**:rows/columns/values/filters 中的 field 值必须与源数据表头完全一致(区分大小写) +- **透视表自动新建子表**:创建的透视表默认放置在自动新建的子表中,不会覆盖源数据。可通过 `--target-sheet-id` 和 `--target-position` 指定放置到已有工作表 +- **不支持修改数据源**:update 仅可修改字段配置和显示选项,不可修改 source +- **折叠状态**:collapse 字段用于控制行字段的展开/折叠,格式为 {字段名: [要折叠的项]} +- **大 JSON 用 @file**:`--properties` 支持 `@文件路径` 读取本地 JSON 文件 diff --git a/skills/multi/dingtalk-sheet/references/sheet/sheet-query-index.md b/skills/multi/dingtalk-sheet/references/sheet/sheet-query-index.md new file mode 100644 index 00000000..3b7a5872 --- /dev/null +++ b/skills/multi/dingtalk-sheet/references/sheet/sheet-query-index.md @@ -0,0 +1,6 @@ +# 筛选与查询 + +| 命令 | 详见 | +|---|---| +| `sheet filter` | [`sheet-filter.md`](./sheet-filter.md) | +| `sheet filter-view` | [`sheet-filter-view.md`](./sheet-filter-view.md) | diff --git a/skills/multi/dingtalk-sheet/references/sheet/sheet-quick-ref.md b/skills/multi/dingtalk-sheet/references/sheet/sheet-quick-ref.md new file mode 100644 index 00000000..3b077760 --- /dev/null +++ b/skills/multi/dingtalk-sheet/references/sheet/sheet-quick-ref.md @@ -0,0 +1,16 @@ +# 场景速查 + +## 场景速查 + +| 用户要做 | 正确命令 | 动手前读 | 禁止写法 | +|---------|---------|----------|----------| +| 快速查看数据 / 大表分批读取 | `csv-get` | [sheet-read-data](./sheet-read-data.md) | 不传 `--range` 全量 `range read` 大表 | +| 少量精确写入、公式、超链接、富文本、数据验证 | `range update` | [sheet-write-data](./sheet-write-data.md)、[sheet-formula](./sheet-formula.md) | 用 `csv-put` / `append` 写公式或富格式 | +| 批量纯值写入 / CSV 粘贴 | `csv-put` | [sheet-write-data](./sheet-write-data.md) | 为大块纯值手写巨大 `--values` JSON | +| 追加记录到末尾 | `append` | [sheet-write-data](./sheet-write-data.md) | 手算最后一行后 `range update` | +| 查找 / 替换 | `find` / `replace` | [sheet-search-replace](./sheet-search-replace.md) | 读全表后本地过滤或手写替换 | +| 清空 / 排序 / 填充 / 复制移动区域 | `range clear` / `range sort` / `range fill` / `range copy-to` / `range move-to` | [sheet-range-operations](./sheet-range-operations.md) | 用读写组合模拟服务端原子操作 | +| 合并、冻结、分组、行高列宽 | `sheet info` + 结构命令 | [sheet-workbook](./sheet-workbook.md)、[sheet-dimension-operations](./sheet-dimension-operations.md) | 从 `range read` / CSV 空值推断结构 | +| 多个原子写操作组合 | `batch-update` | [sheet-batch-operations](./sheet-batch-operations.md) | 多次独立调用导致半成品 | +| 图片写入单元格 / 浮动图片 | `write-image` / `media-upload` + `create-float-image` | [sheet-media-image](./sheet-media-image.md) | 用 `range update` 写图片 | +| 条件高亮 / 标红 / 数据条 / 色阶 | `cond-format` | [sheet-conditional-format](./sheet-conditional-format.md) | 用静态 `set-style` 冒充条件格式 | diff --git a/skills/multi/dingtalk-sheet/references/sheet/sheet-range-operations.md b/skills/multi/dingtalk-sheet/references/sheet/sheet-range-operations.md index af6c076d..54bc630e 100644 --- a/skills/multi/dingtalk-sheet/references/sheet/sheet-range-operations.md +++ b/skills/multi/dingtalk-sheet/references/sheet/sheet-range-operations.md @@ -22,6 +22,11 @@ - 自动填充 → `range fill` - 请勿用 `range read` 读取源数据后手动计算规律再 `range update` 写入,`range fill` 支持服务端智能填充 +用户说"批量清除/批量操作/一次执行多个写操作/原子批量/先清除再写入": +- 批量清除多个区域 → `range batch-clear` +- 组合多个不同写操作 → `batch-update` +- 详见 [sheet-batch-operations](./sheet-batch-operations.md) + 用户说"复制区域/把这块数据复制到/复制到另一个工作表": - 复制区域 → `range copy-to` - 跨工作表 → `range copy-to --target-sheet-id Sheet2` 或 `--target-range "Sheet2!A1"` @@ -82,11 +87,9 @@ Flags: --sheet-id string 工作表 ID 或名称 (必填) --source-range string 源数据范围,A1 表示法 (必填) --target-range string 目标填充范围,A1 表示法 (必填) - --fill-type string 填充类型: 不传则自动检测 / copy(复制) / onlystyle(仅格式) / withoutstyle(仅值) + --fill-type string 填充类型: series(序列,默认) / copy(复制) / onlystyle(仅格式) / withoutstyle(仅值) ``` -`--fill-type` **不传时自动检测**(根据源数据智能判断:数值序列递增、日期递增、文本复制等),这是填序列的默认行为,无需显式传值。`--help` 枚举的可选值只有 `copy` / `onlystyle` / `withoutstyle`;服务端另外也接受 `series`(强制按序列填充),但它不在 `--help` 列表里,一般用不到——要序列递增直接不传 `--fill-type` 即可。 - 目标范围须与源范围在行或列维度对齐(不支持对角填充)。 ### 复制区域 @@ -144,6 +147,8 @@ Flags: |------|-------------|------| | `list` | 工作表的 `sheetId` | range clear / range sort / range fill / range copy-to / range move-to 的 --sheet-id | +> **批量操作**(`range batch-clear` / `batch-update`)已拆分至 [sheet-batch-operations](./sheet-batch-operations.md),含写入边界 + 回读校验规范、典型组合场景示例等。 + ## 注意事项 - ★ **`--sheet-id` 获取规范(强制)**:`sheetId` 未知时必须先通过 `dws sheet list --node --format json` 查询真实的 `sheetId` / 工作表名称后再调用,禁止凭空编造(如臆测为 `Sheet1`、`sheet1`、`0`、`default` 等);用户仅给出工作表名称时,也应通过 `list` 校验该名称是否存在,避免名称大小写或拼写不一致导致失败 @@ -153,3 +158,4 @@ Flags: - ★ **排序用 `range sort` 不用 `range read` + 客户端排序 + `range update`**:服务端原子操作,支持多级排序 - ★ **排序前必须 `range read` 前几行判断表头**:读取排序范围前 3-5 行,对比首行与后续行的数据模式(类型、语义)来判断是否有表头。禁止不读就排,表头被排入数据不可撤销 - ★ **填充用 `range fill` 不用 `range read` + 手动计算 + `range update`**:服务端智能填充,支持序列递增、公式扩展等 +- ★ **批量操作详见 [sheet-batch-operations](./sheet-batch-operations.md)**:`range batch-clear` 多区域清除、`batch-update` 组合写操作,均原子事务 diff --git a/skills/multi/dingtalk-sheet/references/sheet/sheet-read-data.md b/skills/multi/dingtalk-sheet/references/sheet/sheet-read-data.md index 03d52d67..9da424f9 100644 --- a/skills/multi/dingtalk-sheet/references/sheet/sheet-read-data.md +++ b/skills/multi/dingtalk-sheet/references/sheet/sheet-read-data.md @@ -6,6 +6,7 @@ - 需要列名、二维数据、dtype 和 number format,或准备与 `table-put` 配套处理 → `table-get` - 快速查看纯值数据、批量处理、大表分批读 → `csv-get`(token 消耗低,防爆保护) - 需要结构化信息(值+样式+数据验证+富文本+单元格级超链接)、查看公式或原始值 → `range read` +- 需要校验公式写入结果 → 先 `range read --value-render-option formula` 回读公式文本,再 `range read --value-render-option raw_value` 回读计算结果;完整流程见 [sheet-formula](./sheet-formula.md) - 需要查看合并单元格 / 表头合并结构 → `sheet info`,读取返回的 `mergedRanges`;不要在 `csv-get` 或 `range read` 里找合并信息 ## 命令选择 @@ -18,9 +19,15 @@ | 查看单元格样式(背景色/字体/对齐等) | `range read` | 返回 per-cell 结构,含 cellStyles(仅显式设置的样式) | | 查看单元格级超链接 | `range read` | 返回 per-cell 结构,含 hyperlink;富文本片段链接仍在 richText 内 | | 查看公式文本 | `range read --value-render-option formula` | value 返回公式 | +| 公式写后结果校验 | `range read --value-render-option formula` + `raw_value` | 先确认公式文本,再检查计算结果和错误值 | | 获取原始值(数字/布尔而非格式化字符串) | `range read --value-render-option raw_value` | value 返回原始类型 | | 查看合并单元格范围 | `sheet info` | 返回 `mergedRanges`,这是工作表结构信息,不属于单元格值读取 | +## 与现有读取能力的区别 + +- `csv-get` 面向快速浏览和大表纯值读取:返回 CSV 文本、真实行号/列号映射和截断标记,token 低;不表达列类型、number format 或 per-cell 元数据 +- `range read` 面向精确单元格读取:返回二维 per-cell 对象,可看公式、原始值、dataValidation、hyperlink、richText、cellStyles;适合少量或中等范围的细节检查,但大范围读取 token 成本更高 + ## 命令详细参考 ### 读取结构化 table 数据 @@ -107,7 +114,7 @@ Flags: | `dataValidation` | object | 仅有数据验证时出现,无则省略 | 数据验证配置,见下表 | | `hyperlink` | object | 仅有单元格级超链接时出现,无则省略 | 整格超链接,结构为 `{type, link, text?}`,见下表 | | `richText` | object | 仅富文本单元格出现 | 富文本结构(含超链接、附件、图片、样式片段等),普通纯文本不含此字段 | -| `cellStyles` | object | 仅有显式设置的样式时出现;MCP 序列化层也可能返回全 null 空壳 | cell-level 样式,见下表。读取时只看非 null 字段;全 null 等同不存在 | +| `cellStyles` | object | 仅有显式设置的样式时出现;部分返回链路也可能返回全 null 空壳 | cell-level 样式,见下表。读取时只看非 null 字段;全 null 等同不存在 | `range read` / `range get` 不返回合并单元格结构。要看合并单元格,请先或另行调用 `dws sheet info --node --sheet-id --format json`,使用其中的 `mergedRanges`。 @@ -174,7 +181,7 @@ Flags: } ``` -说明:第一行表头有 `cellStyles`(加粗 + 背景色),第二行第二格有单元格级 `hyperlink`。注意:MCP 平台序列化会将未设置的字段填充为 null(如 `"fontStyle": null`),读取时应忽略值为 null 的字段,仅关注非 null 的属性;如果 `cellStyles` 全字段都是 null,视同不存在。`richText` 字段同理——无富文本的普通单元格可能返回 `{"type": null, "texts": null}`,视同不存在。 +说明:第一行表头有 `cellStyles`(加粗 + 背景色),第二行第二格有单元格级 `hyperlink`。注意:部分返回链路会将未设置的字段填充为 null(如 `"fontStyle": null`),读取时应忽略值为 null 的字段,仅关注非 null 的属性;如果 `cellStyles` 全字段都是 null,视同不存在。`richText` 字段同理——无富文本的普通单元格可能返回 `{"type": null, "texts": null}`,视同不存在。 **取值模式说明**: | 模式 | value 返回内容 | 适用场景 | @@ -183,6 +190,8 @@ Flags: | `raw_value` | 原始值(如 1000、45808) | 数据处理、计算 | | `formula` | 公式文本(如 =SUM(A1:A10)),无公式时回退原始值 | 查看/复制公式 | +**公式回读建议**:写公式后不要只看写入返回结果。先用 `formula` 模式确认公式文本已落表,再用 `raw_value` 模式检查计算结果和明显错误值;详见 [sheet-formula](./sheet-formula.md)。 + **超时处理建议**:读取大范围数据时若出现超时或响应过慢,请主动缩小 `--range` 查询范围,**建议单次读取的单元格数量控制在 5000 个以内**(例如 50 行 × 100 列、100 行 × 50 列)。对于大表可采用分页读取策略: - 先通过 `info` 获取 `nonEmptyRange.range`,或用 `nonEmptyRange.lastRow` / `nonEmptyRange.lastColumn` 确定 A1 边界 - 按行分批读取,如 `A1:J500`、`A501:J1000`、`A1001:J1500` …… @@ -219,5 +228,6 @@ dws sheet range read --node --sheet-id --range "A1:D10" --f - `range read` 不传 `--range` 时默认读取整个工作表的全部非空数据 - `range read` 的 `--range` 支持 `Sheet1!A1:D10` 格式直接指定工作表(此时忽略 `--sheet-id`) - ★ `csv-get` / `range read` / `range get` 不返回合并单元格结构;查看合并范围必须用 `sheet info` 的 `mergedRanges` -- `range read` 遇到超时或响应过慢时,应缩小 `--range` 查询范围,**单次读取的单元格数量建议控制在 5000 个以内**;数据量较大时通过 `info` 获取边界后分批读取,避免不传 `--range` 直接读取整个大工作表 +- ★ 大整数和长数字标识符回读校验:精确 ID 应按字符串保存;超过 `9007199254740991` 时不要把数值型回读结果视为逐位精确 +- `range read` 遇到超时或响应过慢时,应缩小 `--range` 查询范围,**单次读取的单元格数量建议控制在 5000 个以内**;数据量较大时通过 `info` 的 `nonEmptyRange.range` 获取 A1 边界后分批读取,避免不传 `--range` 直接读取整个大工作表 - ★ 当用户要求搜索/查找表格数据时,使用 `find` 命令,不要用 `range read` 读取全量数据后自行过滤——`find` 支持服务端搜索,效率更高、语义更准确 diff --git a/skills/multi/dingtalk-sheet/references/sheet/sheet-read-index.md b/skills/multi/dingtalk-sheet/references/sheet/sheet-read-index.md new file mode 100644 index 00000000..4384045e --- /dev/null +++ b/skills/multi/dingtalk-sheet/references/sheet/sheet-read-index.md @@ -0,0 +1,6 @@ +# 读取 + +| 命令 | 详见 | +|---|---| +| `sheet range read` | [`sheet-read-data.md`](./sheet-read-data.md) | +| `sheet find` / `sheet replace` | [`sheet-search-replace.md`](./sheet-search-replace.md) | diff --git a/skills/multi/dingtalk-sheet/references/sheet/sheet-ref-index.md b/skills/multi/dingtalk-sheet/references/sheet/sheet-ref-index.md new file mode 100644 index 00000000..f97bdb5b --- /dev/null +++ b/skills/multi/dingtalk-sheet/references/sheet/sheet-ref-index.md @@ -0,0 +1,24 @@ +# Reference 索引 + +## Reference 索引 + +| Reference | 描述 | +|-----------|------| +| [sheet-workbook](./sheet-workbook.md) | 管理表格文档与工作表。当用户说"创建表格"、"有哪些工作表"、"新建/重命名/隐藏/冻结/复制/删除工作表"、"显示/隐藏网格线"时使用;`info` 可回读工作表结构。命令:`create`/`list`/`info`/`new`/`update`/`copy`/`delete-sheet`/`show-gridline`/`hide-gridline` | +| [sheet-read-data](./sheet-read-data.md) | 读取工作表数据。当用户说"读数据"、"看表格内容"、"结构化/DataFrame 读取"时使用。推荐 `csv-get`(CSV 格式、token 低);需 columns / data / dtypes / formats 时用 `table-get`;需 per-cell 元数据时用 `range read`。大范围数据建议分页读取(单次 ≤5000 单元格)。命令:`table-get`/`csv-get`/`range read` | +| [sheet-write-data](./sheet-write-data.md) | 写入数据到工作表。当用户说"结构化/DataFrame 写入"、"写数据"、"填表"、"写公式"、"超链接"、"追加数据"、"导入CSV"时使用。多工作表结构化写入用 `table-put`;大批量纯值(>5行或>20单元格)必须用 `csv-put` 而非 `range update`。命令:`table-put`/`range update`/`append`/`csv-put` | +| [sheet-formula](./sheet-formula.md) | 公式写入与有限回读校验。当用户说"写公式"、"计算列"、"辅助列"、"总计/占比/增长率/查找计算"时使用。当前无聚合式公式校验工具,写后必须分别回读公式文本和计算结果。命令:`range update` + `range read --value-render-option formula/raw_value` | +| [sheet-search-replace](./sheet-search-replace.md) | 搜索和替换文本。当用户说"搜索"、"查找"、"替换"、"把A改成B"时使用。禁止用 `range read` 全量读取后客户端过滤代替 `find`,禁止用 `range update` 模拟 `replace`。命令:`find`/`replace` | +| [sheet-range-operations](./sheet-range-operations.md) | 区域结构性操作。当用户说"清空"、"排序"、"自动填充"、"复制区域到"、"移动数据到"时使用。均为服务端原子操作,禁止 `range read`+`range update` 组合模拟。排序前必须先读前几行判断表头。命令:`range clear`/`range sort`/`range fill`/`range copy-to`/`range move-to` | +| [sheet-batch-operations](./sheet-batch-operations.md) | 批量操作。当用户说"批量清除多个区域"、"组合多个写操作"、"先清除再写入"、"插行列再写数据"、"批量创建/取消分组"时使用。`batch-update` 只支持已列明的原子写操作。命令:`range batch-clear`/`batch-update` | +| [sheet-dimension-operations](./sheet-dimension-operations.md) | 行列增删移动、属性设置与分组。当用户说"插入行/列"、"删除行/列"、"隐藏/显示行列"、"设行高/列宽"、"移动行/列"、"追加空行/空列"、"创建/取消行列分组"、"新建分组并设为折叠/展开"时使用。命令:`insert-dimension`/`delete-dimension`/`update-dimension`/`move-dimension`/`add-dimension`/`group-dimension`/`ungroup-dimension` | +| [sheet-style-format](./sheet-style-format.md) | 单元格样式与合并。当用户说"设样式"、"改颜色/字体/对齐"、"数字格式(百分比/货币/日期)"、"合并/取消合并"时使用。纯样式/批量样式走 `set-style`;写值同时设置少量 cell 样式可用 `range update` 的 `cellStyles`。命令:`range set-style`/`range batch-set-style`/`merge-cells`/`unmerge-cells` | +| [sheet-dropdown](./sheet-dropdown.md) | 下拉列表管理。当用户说"设置下拉"、"下拉选项"、"删除下拉"时使用。命令:`set-dropdown`/`get-dropdown`/`delete-dropdown` | +| [sheet-media-image](./sheet-media-image.md) | 附件上传与图片。当用户说"上传附件"、"写入图片到单元格"、"浮动图片"时使用。单元格图片用 `write-image`(禁止 `range update`);浮动图片需先 `media-upload` 再 `create-float-image`。命令:`media-upload`/`write-image`/`create-float-image`/`get-float-image`/`list-float-images`/`update-float-image`/`delete-float-image` | +| [sheet-filter](./sheet-filter.md) | 全局筛选。当用户说"筛选"、"过滤"、"只看某些行"(未说"筛选视图")时使用。禁止用"删除不符合条件的行"代替筛选。命令:`filter get`/`create`/`delete`/`update`/`clear-criteria`/`sort` | +| [sheet-filter-view](./sheet-filter-view.md) | 筛选视图(个人化,不影响协作者)。当用户明确说"筛选视图"时使用,与全局筛选相互独立。命令:`filter-view list`/`create`/`update`/`delete`/`info`/`update-criteria`/`delete-criteria`/`list-criteria`/`get-criteria` | +| [sheet-conditional-format](./sheet-conditional-format.md) | 条件格式规则。触发词:标红/标黄/高亮/突出/标记/数据条/色阶/颜色随数据变 → **强制**走条件格式,禁止 `range set-style` 静态样式替代。命令:`cond-format list`/`create`/`update`/`delete` | +| [sheet-export](./sheet-export.md) | 导出表格为 xlsx。当用户说“导出”、“下载xlsx”、“存为Excel”时使用。单命令一站式,CLI 内部自动轮询,禁止 Agent 侧重试。命令:`export` | +| [sheet-chart](./sheet-chart.md) | 浮动图表管理。当用户说“画图/数据可视化/趋势图/对比图/占比图/柱形图/折线图/饼图”时使用。禁止用本地脚本生成静态图片替代。命令:`chart list`/`create`/`update`/`delete` | +| [sheet-pivot-table](./sheet-pivot-table.md) | 透视表管理。当用户说“透视表/分组汇总/交叉分析/按X统计Y”时使用。禁止用公式拼汇总表替代。命令:`pivot-table list`/`create`/`update`/`delete` | +| sheet-template | 表格模板管理。当用户说“用模板创建表格”、“搜索模板”、“模板列表”时使用。命令:`template list`/`template search`/`template apply` | diff --git a/skills/multi/dingtalk-sheet/references/sheet/sheet-scope.md b/skills/multi/dingtalk-sheet/references/sheet/sheet-scope.md new file mode 100644 index 00000000..2280f71f --- /dev/null +++ b/skills/multi/dingtalk-sheet/references/sheet/sheet-scope.md @@ -0,0 +1,16 @@ +# 适用范围 + +## 适用范围 + +`sheet` 仅支持钉钉在线电子表格(`contentType=ALIDOC`、`extension=axls`)。 + +| 文件类型 | 处理方式 | +|---------|---------| +| 在线电子表格(`axls`) | 走 `sheet` 全部命令 | +| `xlsx` / `xls` / `xlsm` / `csv` | `dws drive download --node --output <路径>` 下载到本地处理,禁止调用任何 `sheet` 子命令 | +| 本地 xlsx 导入为在线表格 | `dws drive upload --file <路径> --convert`(上传并转换为在线电子表格,转换后可用 `sheet` 命令操作) | +| 在线表格导出为 xlsx | `dws sheet export`(axls → xlsx 格式转换) | + +用户贴原始 `alidocs` URL 时必须先 probe:`dws doc info --node --format json`,按 [链接规范](../url-patterns.md#alidocs-url-类型探测流程) 校验: +- `contentType=ALIDOC` + `extension=axls` → 继续走 `sheet` +- `extension=xlsx` / `xls` / `xlsm` / `csv` → 转 `dws drive download`,告知用户"这是本地表格文件,已为你下载到本地处理" diff --git a/skills/multi/dingtalk-sheet/references/sheet/sheet-style-format.md b/skills/multi/dingtalk-sheet/references/sheet/sheet-style-format.md index bfa4bc1f..da8e6aaa 100644 --- a/skills/multi/dingtalk-sheet/references/sheet/sheet-style-format.md +++ b/skills/multi/dingtalk-sheet/references/sheet/sheet-style-format.md @@ -26,7 +26,7 @@ 用户说"设置样式/改颜色/设背景色/加粗/居中/换行/字体颜色/字号": - 仅设样式不改值 → `range set-style` -- 批量设置不同 range 的样式 → `range batch-set-style --batch ./styles.json`(内部顺序循环调 `update_range`) +- 批量设置不同 range 的样式 → `range batch-set-style --batch ./styles.json`(按配置顺序执行多条样式更新) - 写值同时附带样式 → `range update --values` 中使用 `cellStyles` 字段(参见 sheet-write-data.md) - 请勿用 `range update --values` 写空/重写来模拟纯样式变更 @@ -121,7 +121,7 @@ Flags: ``` **特性说明**: -- CLI 侧顺序循环逐条调用 `update_range`(非服务端批量),运行时输出 `[N/M]` 进度 +- CLI 按配置顺序逐条执行样式更新(非原子事务),运行时输出 `[N/M]` 进度 - 每条记录执行与 `set-style` 一致的校验:至少一项样式字段 + rows ≤ 1000 + rows×cols ≤ 30000 + 枚举合法 - 默认遇错即停(返回非 0),`--continue-on-error` 时所有条目跑完再返回首个错误 @@ -219,7 +219,7 @@ Flags: ```bash # ── 工作流 4: 写入数据并设置样式 ── -# 1. 写入数据(每个单元格必须是 object;数字也写成字符串) +# 1. 写入数据 dws sheet range update --node --sheet-id --range "A1:C3" \ --values '[[{"type":"text","text":"商品"},{"type":"text","text":"单价"},{"type":"text","text":"数量"}],[{"type":"text","text":"苹果"},{"type":"text","text":"5.5"},{"type":"text","text":"100"}],[{"type":"text","text":"香蕉"},{"type":"text","text":"3.2"},{"type":"text","text":"200"}]]' --format json diff --git a/skills/multi/dingtalk-sheet/references/sheet/sheet-templates.md b/skills/multi/dingtalk-sheet/references/sheet/sheet-templates.md new file mode 100644 index 00000000..1ba8dc2e --- /dev/null +++ b/skills/multi/dingtalk-sheet/references/sheet/sheet-templates.md @@ -0,0 +1,52 @@ +# 模板管理 + +## 模板管理 + +当用户说“用模板创建表格”、“搜索表格模板”、“有哪些表格模板”时使用。 + +### 获取表格模板列表 + +``` +Usage: + dws sheet template list [flags] +Example: + dws sheet template list + dws sheet template list --source MY + dws sheet template list --source PUBLIC + dws sheet template list --limit 20 +Flags: + --source string 模板来源: MY(我的模版)/PUBLIC(公开模版),不传默认 MY + --limit int 返回数量上限 (可选) + --cursor string 分页游标 (可选) +``` + +### 搜索表格模板 + +``` +Usage: + dws sheet template search [flags] +Example: + dws sheet template search --query "预算" + dws sheet template search --query "排班表" --limit 10 + dws sheet template search --query "财务" --source PUBLIC +Flags: + --query string 搜索关键词 (必填) + --source string 模板来源: MY(我的模版)/PUBLIC(公开模版),不传默认 MY + --limit int 返回数量上限 (可选) + --cursor string 分页游标 (可选) +``` + +### 应用表格模板 + +``` +Usage: + dws sheet template apply [flags] +Example: + dws sheet template apply --template-id TPL_ID --name "月度预算表" + dws sheet template apply --template-id TPL_ID --name "排班表" --folder FOLDER_ID +Flags: + --template-id string 模板 ID (必填,从 template list/search 获取) + --name string 新表格文档名称 (可选) + --folder string 目标文件夹 ID (可选) + --workspace string 知识库 ID (可选) +``` diff --git a/skills/multi/dingtalk-sheet/references/sheet/sheet-triggers.md b/skills/multi/dingtalk-sheet/references/sheet/sheet-triggers.md new file mode 100644 index 00000000..b3596f4f --- /dev/null +++ b/skills/multi/dingtalk-sheet/references/sheet/sheet-triggers.md @@ -0,0 +1,10 @@ +# 触发必读 + +## 触发必读 + +- 涉及公式、计算列、辅助列、占比、增长率、查找计算:先读 [sheet-formula](./sheet-formula.md)。 +- 涉及 `range update --values`、超链接、富文本、数据验证、少量样式随值写入:先读 [sheet-write-data](./sheet-write-data.md)。 +- 涉及公式文本 / 原始值 / 格式化值回读、per-cell 元数据、大表分页:先读 [sheet-read-data](./sheet-read-data.md)。 +- 涉及合并、冻结、工作表增删改、最后非空边界:先读 [sheet-workbook](./sheet-workbook.md)。 +- 涉及行列插删、隐藏、尺寸、移动、分组:先读 [sheet-dimension-operations](./sheet-dimension-operations.md)。 +- 涉及多个写操作一次提交:先读 [sheet-batch-operations](./sheet-batch-operations.md)。 diff --git a/skills/multi/dingtalk-sheet/references/sheet/sheet-url-paste.md b/skills/multi/dingtalk-sheet/references/sheet/sheet-url-paste.md new file mode 100644 index 00000000..057a2e08 --- /dev/null +++ b/skills/multi/dingtalk-sheet/references/sheet/sheet-url-paste.md @@ -0,0 +1,12 @@ +# URL 粘贴场景 + +## URL 粘贴场景 + +用户直接粘贴表格 URL(无其他指令): +- 先 probe:`dws doc info --node --format json` +- `extension=axls` → `list` + `range read`(读取第一个工作表数据) +- `extension=xlsx`/`xls`/`xlsm`/`csv` → 转 `dws drive download --node --output ./` + +用户粘贴 URL + 附加指令: +- probe 为 `axls` → 按 Reference 索引路由到对应命令 +- probe 为 xlsx/csv → 先 `dws drive download` 下载到本地,严禁调用 sheet 命令 diff --git a/skills/multi/dingtalk-sheet/references/sheet/sheet-url-to-node-id.md b/skills/multi/dingtalk-sheet/references/sheet/sheet-url-to-node-id.md new file mode 100644 index 00000000..784e0aaa --- /dev/null +++ b/skills/multi/dingtalk-sheet/references/sheet/sheet-url-to-node-id.md @@ -0,0 +1,10 @@ +# URL → NODE_ID + +## URL → NODE_ID + +| URL 格式 | 提取方式 | +|----------|---------| +| `.../i/nodes/{id}` 或 `.../i/nodes/{id}?query` | 取路径末段作 NODE_ID(忽略 query) | +| `.../spreadsheetv2/{key}/...` | **完整 URL 原样传 `--node`**,禁止提取 path segment | + +参数不确定时先 `dws sheet <命令> --help`。 diff --git a/skills/multi/dingtalk-sheet/references/sheet/sheet-visual-index.md b/skills/multi/dingtalk-sheet/references/sheet/sheet-visual-index.md new file mode 100644 index 00000000..6ed87051 --- /dev/null +++ b/skills/multi/dingtalk-sheet/references/sheet/sheet-visual-index.md @@ -0,0 +1,7 @@ +# 图表与可视化 + +| 命令 | 详见 | +|---|---| +| `sheet chart` | [`sheet-chart.md`](./sheet-chart.md) | +| `sheet media-image` | [`sheet-media-image.md`](./sheet-media-image.md) | +| `sheet pivot-table` | [`sheet-pivot-table.md`](./sheet-pivot-table.md) | diff --git a/skills/multi/dingtalk-sheet/references/sheet/sheet-workbook.md b/skills/multi/dingtalk-sheet/references/sheet/sheet-workbook.md index 1d1006b1..b8f0c4d6 100644 --- a/skills/multi/dingtalk-sheet/references/sheet/sheet-workbook.md +++ b/skills/multi/dingtalk-sheet/references/sheet/sheet-workbook.md @@ -26,6 +26,12 @@ - 复制并指定名称 → `copy --name "副本名称"` - 复制并指定位置 → `copy --index N` +用户说"显示网格线/显示网格/恢复网格线": +- 显示网格线 → `show-gridline`(连续多次调用幂等,不报错) + +用户说"隐藏网格线/去掉网格/关闭网格线/展示模式/看板模式": +- 隐藏网格线 → `hide-gridline`(连续多次调用幂等,不报错) + 用户说"删除工作表/移除工作表/删掉这个Sheet": - 删除工作表 → `delete-sheet`(不可逆操作,执行前必须向用户确认) @@ -70,13 +76,25 @@ Example: dws sheet info --node dws sheet info --node --sheet-id dws sheet info --node --sheet-id "Sheet1" + dws sheet info --node --sheet-id Flags: --node string 表格文档 ID 或 URL (必填) --sheet-id string 工作表 ID 或名称 (不传则返回第一个工作表) + --include strings 可选扩展信息,逗号分隔;当前支持 groups ``` 返回字段中 `mergedRanges` 是当前工作表的合并单元格范围列表(A1 表示法,如 `["C7:D11"]`)。它属于工作表结构/布局元数据:读写单元格内容前,如需判断表头、分组标题、续写位置或避开合并冲突,应先看 `sheet info`,不要在 `range read` / `csv-get` 的单元格值里寻找合并信息。 +返回字段中 `frozenRowCount` / `frozenColumnCount` 是冻结行列数量:冻结总是从工作表顶部第 1 行、左侧第 A 列开始连续计算;`0` 表示未冻结。它们是工作表级元数据,默认随 `sheet info` 返回,不需要额外 `include`。 + +最后非空数据边界通过 `nonEmptyRange` 返回,字段均为 A1/UI 语义:`range` 是从 `A1` 到最后非空单元格的范围,`lastCell` 是最后非空单元格地址,`lastRow` 是 1-based 行号,`lastColumn` 是列字母。空表时 `nonEmptyRange` 为 `null`。不要使用旧的 0-based 字段 `lastNonEmptyRow` / `lastNonEmptyColumn`。 + +需要读取行列分组时,检查 `sheet info` 返回中的以下字段: +- `rowGroups`:行分组列表,单项包含 `range`、`startRow`、`endRow`、`count`、`level`、`collapsed` +- `columnGroups`:列分组列表,单项包含 `range`、`startColumn`、`endColumn`、`count`、`level`、`collapsed` + +其中 `range` 使用 A1 行/列范围(如 `"3:7"` / `"C:F"`),起止行号为 1-based,列使用字母;`level` 是 1-based 展示层级,不返回 `depth`;`collapsed` 表示当前折叠状态。`range read` / `csv-get` 不返回 `mergedRanges`、冻结或分组等结构元数据。 + ### 新建工作表 ``` Usage: @@ -112,16 +130,14 @@ Flags: --node string 表格文档 ID 或 URL (必填) --sheet-id string 工作表 ID 或名称 (必填) --name string 新名称,最长 100 字符,不能包含 / \ ? * [ ] : - --title string --name 的别名(兼容) --index int 新位置(从 0 开始) --hidden --hidden=true 隐藏,--hidden=false 取消隐藏 - --tab-color string 工作表标签颜色,Hex 如 #FF0000;传空字符串清除颜色 --frozen-row-count int 冻结行数,0 表示取消冻结 --frozen-column-count int 冻结列数,0 表示取消冻结 ``` -更新工作表名称、位置、隐藏状态、标签颜色、冻结行列。 -`--name`(别名 `--title`)/ `--index` / `--hidden` / `--tab-color` / `--frozen-row-count` / `--frozen-column-count` 至少提供一个;多个属性可同时传入,将在同一次请求中更新。 +更新工作表名称、位置、隐藏状态、冻结行列。 +`--name` / `--index` / `--hidden` / `--frozen-row-count` / `--frozen-column-count` 至少提供一个;多个属性可同时传入,将在同一次请求中更新。 注意: - 至少需要保留一个可见的工作表,不能将所有工作表都隐藏 @@ -169,17 +185,33 @@ Flags: - 不能删除隐藏的工作表(需先通过 `sheet update --hidden false` 取消隐藏再删除) - 不能删除最后一个可见工作表(至少保留一个可见工作表) -### 显示或隐藏网格线 +### 显示工作表网格线 ``` Usage: + dws sheet show-gridline [flags] +Example: dws sheet show-gridline --node --sheet-id - dws sheet hide-gridline --node --sheet-id + dws sheet show-gridline --node --sheet-id "Sheet1" Flags: --node string 表格文档 ID 或 URL (必填) --sheet-id string 工作表 ID 或名称 (必填) ``` -这两个命令只修改指定工作表的网格线显示状态,不修改单元格内容或边框样式。执行后可用同一命令反向恢复。 +### 隐藏工作表网格线 +``` +Usage: + dws sheet hide-gridline [flags] +Example: + dws sheet hide-gridline --node --sheet-id + dws sheet hide-gridline --node --sheet-id "Sheet1" +Flags: + --node string 表格文档 ID 或 URL (必填) + --sheet-id string 工作表 ID 或名称 (必填) +``` + +切换子表网格线显隐;二态语义在命令名里,无需额外参数(同 `update --hidden` / `update --hidden=false` 的隐藏/显示工作表模式)。 +网格线默认显示;隐藏后工作表背景为纯白色,适合截图、演示、仪表盘/看板场景(不影响打印和数据)。 +连续多次 show 或多次 hide 均为幂等操作,不会报错。 ## 核心工作流 @@ -192,7 +224,7 @@ dws sheet create --name "销售数据" --format json # 2. 查看工作表列表 — 提取 sheetId dws sheet list --node --format json -# 3. 写入表头和数据(每个单元格必须是 object;数字也写成字符串) +# 3. 写入表头和数据 dws sheet range update --node --sheet-id --range "A1:C1" \ --values '[[{"type":"text","text":"姓名"},{"type":"text","text":"部门"},{"type":"text","text":"销售额"}]]' --format json @@ -218,7 +250,7 @@ dws sheet range read --node --sheet-id --range "A1:D10" --f # 1. 新建工作表 dws sheet new --node --name "汇总" --format json -# 2. 在新工作表中写入汇总公式(公式写在 text,以 = 开头) +# 2. 在新工作表中写入汇总公式 dws sheet range update --node --sheet-id --range "A1:B1" \ --values '[[{"type":"text","text":"指标"},{"type":"text","text":"数值"}]]' --format json @@ -233,7 +265,7 @@ dws sheet range update --node --sheet-id --range "A2:B2 | `create` | `nodeId` | list / info / new / range read / range update / find 的 --node | | `list` | 工作表的 `sheetId` | info / range read / range update / find 的 --sheet-id | | `new` | 新工作表的 `sheetId` | range read / range update / find 的 --sheet-id | -| `info` | `rowCount` / `nonEmptyRange.range` / `nonEmptyRange.lastRow` / `nonEmptyRange.lastColumn` / `mergedRanges` | 确定数据范围、追加写入起始行、判断合并单元格结构 | +| `info` | `rowCount` / `nonEmptyRange.range` / `nonEmptyRange.lastRow` / `nonEmptyRange.lastColumn` / `mergedRanges` / `frozenRowCount` / `frozenColumnCount` / `rowGroups` / `columnGroups` | 确定数据范围、追加写入起始行、判断合并单元格结构、回读冻结与行列分组 | ## 注意事项 @@ -257,4 +289,6 @@ dws sheet range update --node --sheet-id --range "A2:B2 - `delete-sheet` 为不可逆操作,执行前必须向用户确认 - `delete-sheet` 不能删除隐藏的工作表,需先通过 `update --hidden=false` 取消隐藏再删除 - `delete-sheet` 不能删除最后一个可见工作表,至少保留一个可见工作表 +- `show-gridline` / `hide-gridline` 为幂等写操作:连续调用同一命令不会报错,适合 Agent 不确定当前状态时直接调用 +- `show-gridline` / `hide-gridline` 仅控制网格线视觉显示,不影响数据、打印、冻结行列等任何其他属性 - ★ 关键区分: sheet(电子表格/单元格读写) vs aitable(AI多维表/结构化记录/字段定义) vs doc(文档编辑/阅读) diff --git a/skills/multi/dingtalk-sheet/references/sheet/sheet-write-data.md b/skills/multi/dingtalk-sheet/references/sheet/sheet-write-data.md index c56c7921..ffce565e 100644 --- a/skills/multi/dingtalk-sheet/references/sheet/sheet-write-data.md +++ b/skills/multi/dingtalk-sheet/references/sheet/sheet-write-data.md @@ -5,6 +5,7 @@ 用户说"写数据/填表/更新单元格/写入公式": - 从 DataFrame/结构化对象一次写入一个或多个工作表 → `table-put` - 更新数据 → `range update` +- 写入公式前先读 [sheet-formula](./sheet-formula.md);写完必须回读公式文本和计算结果,不能只看写入返回成功 - 【强制】`--sheet-id` 必填:即使是单工作表也不能省略,不要参照 `range read` 的默认行为;未知时先执行 `dws sheet list --node --format json` 获取 `sheetId`,禁止凭空臆测为 `Sheet1`、`sheet1`、`0`、`default` 等 - 注意:如果用户的目的是替换文本、移动行列、追加空行空列、清空区域、排序、填充、复制区域或移动区域,请勿使用 `range update`,必须使用对应的专用命令(`replace`/`move-dimension`/`add-dimension`/`range clear`/`range sort`/`range fill`/`range copy-to`/`range move-to`) - **批量纯值写入优先用 `csv-put`**:当写入场景同时满足以下条件时,必须优先使用 `csv-put` 而非 `range update`:(1) 写入的是纯值(不含公式、超链接、dataValidation、cellStyles、richText);(2) 数据量较大(超过 5 行或超过 20 个单元格);(3) 数据来源为表格/CSV 文本/结构化文本。`csv-put` 无需手动构造二维 JSON 数组,直接传 CSV 文本即可,更简洁高效且支持自动扩容 @@ -30,6 +31,8 @@ | 自动定位末尾 | mode=append | 不支持 | 支持 | 不支持 | | 自动扩容行列 | 支持 | 不支持 | 支持 | 支持 | +`range update` 用于公式、超链接、dataValidation、richText 和少量 cellStyles 等精确单元格写入;`append` 只做末尾追加;`csv-put` 面向大批量纯值;`table-put` 面向多工作表结构化数据。公式写入流程见 [sheet-formula](./sheet-formula.md);写入后至少回读 `formula` 模式确认公式文本,再回读 `raw_value` 或展示值检查计算结果。 + ## 命令详细参考 ### 写入结构化 table 数据 @@ -80,7 +83,7 @@ Flags: --values string 单元格内容,二维 JSON 数组 (必填);每个元素必须是 object:{type:text,text:...}、{type:richText,texts:[...]}、{dataValidation:...}、{cellStyles:...}、{hyperlink:...} 或 {}(详见下文 values 参数格式说明) ``` -**合并单元格注意(`range update`)**:这里说的是 `range update` 写入单元格对象这一路径,不是所有写入命令的统一行为。目标范围与已有合并区域冲突时,MCP 服务端会拦截并返回 `MERGED_CELLS_CONFLICT` 错误,错误消息中通常会列出具体冲突的合并区域地址。收到此错误时按以下流程处理: +**合并单元格注意(`range update`)**:这里说的是 `range update` 写入单元格对象这一路径,不是所有写入命令的统一行为。目标范围与已有合并区域冲突时,服务端会拦截并返回 `MERGED_CELLS_CONFLICT` 错误,错误消息中通常会列出具体冲突的合并区域地址。收到此错误时按以下流程处理: 1. 从错误消息中获取冲突的合并区域地址(如 `A1:B2, C3:D4`),或通过 `dws sheet info --node --sheet-id --format json` 查询完整的合并区域列表(`mergedRanges` 数组) 2. 用 `dws sheet unmerge-cells --range <冲突区域>` 取消这些合并 3. 执行 `range update` 写入数据 @@ -94,6 +97,8 @@ Flags: **范围职责**:`range update` 负责写入单元格内容(原始值/公式/富文本对象),并支持通过 `cellStyles` 附带 per-cell 样式。如需批量设置整片区域的样式(不写值),请使用 `dws sheet range set-style`。 +**公式回读要求**:公式用 `{"type":"text","text":"=..."}` 写入。写完后先执行 `range read --value-render-option formula` 确认公式文本,再执行 `range read --value-render-option raw_value` 检查计算结果和错误值。详见 [sheet-formula](./sheet-formula.md)。 + ### 在工作表末尾追加数据 ``` Usage: @@ -116,11 +121,9 @@ Flags: Usage: dws sheet csv-put [flags] Example: - # 内联多行 CSV:必须用 $'...' 让 \n 变成真换行;普通单引号 '...\n...' 里的 \n 是字面量,会写成一行含字面 \n 的错误数据 dws sheet csv-put --node --sheet-id --start-cell A1 \ - --csv $'name,score\nAlice,95\nBob,87' + --csv 'name,score\nAlice,95\nBob,87' - # 多行数据推荐用 @文件,最稳妥 dws sheet csv-put --node --sheet-id --start-cell B2 \ --csv @data.csv --allow-overwrite @@ -140,7 +143,7 @@ Flags: 将 RFC 4180 格式的 CSV 文本写入指定工作表的指定单元格位置。 - **分隔符必须是英文逗号 `,`**(ASCII 0x2C),禁止使用中文逗号 `,`(U+FF0C)。中文逗号不会被识别为分隔符,会导致整行被写入同一个单元格。生成 CSV 内容时务必检查分隔符 - 只写纯值,不支持公式/样式/批注。`=` 开头的内容当文本处理,不会被解析为公式 -- 数字/日期/百分数由表格引擎自动识别类型(如 `95` 存为数字,`2025-03-01` 存为日期) +- 数字/日期/百分数由表格系统自动识别类型(如 `95` 存为数字,`2025-03-01` 存为日期) - 自动扩容行列:CSV 数据超出当前工作表维度时自动追加行/列 - 与 `range update` 不同,目标区域如含合并单元格,`csv-put` 会打散合并并写入纯值 - 若需要保留原有合并结构,写入前先用 `sheet info` 记录 `mergedRanges`,写入后用 `merge-cells` 恢复对应区域 @@ -184,7 +187,7 @@ Flags: `hyperlink` 作用于整个单元格,适合“这个单元格整体可点击跳转”的场景。它和 richText 的片段级 `link` 不同。 > **hyperlink 三种语义**: -> - **不传 `hyperlink` 字段** → 保留原超链接(引擎自动 readback 回写) +> - **不传 `hyperlink` 字段** → 保留原超链接(系统自动保留) > - **`hyperlink: {"type":"none"}`** → 显式清除单元格超链接 > - **`hyperlink: {"type":"path"/"sheet"/"range", link, text?}`** → 写新超链接(覆盖) > @@ -205,7 +208,7 @@ Flags: 注意: - 不传 `hyperlink` 字段同于 “保留原超链接”,无需先 read 再回传 -- Agent 调用统一使用 `hyperlink: {type:"none"}` 清除超链接;底层 REST 兼容 `hyperlink:null`,但 MCP schema / 网关可能过滤 null 字段,不要把 null 当默认写法 +- Agent 调用统一使用 `hyperlink: {type:"none"}` 清除超链接;不要把 `hyperlink:null` 当默认写法,避免空字段在调用链路中被过滤后语义不清 - `hyperlink` 可以不带 `type/text` cell 单独出现,用于只设置或清理链接并保留原值 - 不要把 `hyperlink` 和 `type:"richText"` 混用;整格链接用 `hyperlink`,片段链接用 richText 子项 `type:"link"` @@ -338,7 +341,7 @@ dws sheet range update --node NODE_ID --sheet-id SHEET_ID --range "A1:C1" \ ### 重要约束 -- 不再支持 `{type:"number"}` / `{type:"boolean"}` / `{type:"null"}` —— MCP `complexValues` 仅接受 `text` / `richText` 两种 type,或 `{}` 跳过。数字 / 布尔走 `{type:"text","text":"<字符串形式>"}` +- 不再支持 `{type:"number"}` / `{type:"boolean"}` / `{type:"null"}` —— 当前单元格对象协议仅接受 `text` / `richText` 两种 type,或 `{}` 跳过。数字 / 布尔走 `{type:"text","text":"<字符串形式>"}` - 不支持直接传入原始值(字符串、数字、布尔、null、空字符串);`null` 不等同于 `{}`,`null` 会报错 - 维度必须与 `--range` 范围完全一致,例如 `--range "A1:B3"` 需要 3 行 2 列的数组 - 清理整格超链接使用 `{"hyperlink":{"type":"none"}}`;不要使用 `{"hyperlink":null}` 作为 agent 默认调用形态 @@ -420,14 +423,16 @@ dws sheet append --node --sheet-id \ - 维度不足请按行 / 列补齐为同等大小;不需要修改的位置用 `{}` 跳过(保留原值),需要清空的位置用 `{"type":"text","text":""}`;禁止出现各行列数不一致或与 `--range` 不匹配的情况,否则调用会直接报错 - 如需写整格超链接,把 `{"type":"text","text":"...","hyperlink":{"type":"path","link":"..."}}` 放进 `--values` 二维数组对应的单元格里;富文本片段链接才使用 richText 子项 `type:"link"` - ★ **清空区域优先用 `range clear`(强制)**:需要清空整片区域时必须使用 `range clear`,禁止用 `range update` 模拟。仅在 `range update` 写入混合数据时个别 cell 需要清空,才在 `--values` 中用 `{"type":"text","text":""}` -- ★ **不再支持 `{type:"number"}` / `{type:"boolean"}` / `{type:"null"}`(强制)**:MCP `complexValues` 仅接受 `type:"text"` 与 `type:"richText"` 两种,CLI 会在本地直接拦截非法 type 并报错。写数字 / 布尔请用 `{"type":"text","text":"<字符串形式>"}`(服务端按内容自动识别),不要再用旧的 `value` 字段 -- **dataValidation 三语义**:不传字段=保留;`{type:"none"}`=清除;`{type:"dropdown"/"checkbox",...}`=覆盖。无需先 read 再回传,引擎自动保留原 DV +- ★ **不再支持 `{type:"number"}` / `{type:"boolean"}` / `{type:"null"}`(强制)**:当前单元格对象协议仅接受 `type:"text"` 与 `type:"richText"` 两种,CLI 会在本地直接拦截非法 type 并报错。写数字 / 布尔请用 `{"type":"text","text":"<字符串形式>"}`(服务端按内容自动识别),不要再用旧的 `value` 字段 +- **dataValidation 三语义**:不传字段=保留;`{type:"none"}`=清除;`{type:"dropdown"/"checkbox",...}`=覆盖。无需先 read 再回传,系统会保留原 DV - **hyperlink 三语义**:不传字段=保留;`{type:"none"}`=清除;`{type:"path"/"sheet"/"range",...}`=覆盖。Agent 调用不要使用 `hyperlink:null` - ★ **单次调用上限(强制)**:`range update` / `set-style` 行数 ≤ 1000,单元格总数建议 ≤ 5000(硬限 30000) - ★ **大批量纯值写入用 `csv-put` 不用 `range update`**:当写入纯值(无公式、无超链接、无富文本对象)且数据量较大时(>5 行或 >20 单元格),必须使用 `csv-put`。`csv-put` 接受 CSV 文本直接写入,无需构造二维 JSON 数组,支持自动扩容,更简洁高效。仅在需要写入公式、单元格级超链接、富文本对象,或仅更新少量单元格时才使用 `range update` +- ★ **公式写完必须回读校验**:先用 `range read --value-render-option formula` 确认公式文本,再用 `range read --value-render-option raw_value` 检查计算结果和明显错误值;当前没有聚合式公式校验工具 +- ★ **大整数/长数字 ID 必须按文本写(强制)**:超过 `9007199254740991` 的整数、订单号、手机号、SKU 等需要逐位精确的值,使用字符串写入;需要固定文本格式时用 `range update` 的 `cellStyles.numberFormat: "@"`,并以 `range read --value-render-option raw_value` 回读确认 - `range update` 必填 `--values`;单元格级超链接通过 cell 的 `hyperlink` 字段表达,附件 / 图片 / 带样式片段通过 `--values` 内的 richText 富格式表达,CLI 不再有 `--hyperlinks` 参数 - `range update` 职责边界:`range update` 写入单元格内容(文本 / 公式 / 富文本对象),支持通过 `cellStyles` 附带 per-cell 样式(背景色 / 字号 / 对齐等)。但批量刷整片区域的统一样式时,应使用 `dws sheet range set-style`(如 "给表头加粗居中")或 `dws sheet range batch-set-style --batch `。两种方式各有适用场景:少量 cell 写值 + 样式一步到位用 `cellStyles`;大面积统一样式用 `set-style` - `append` 自动定位到最后一行有数据的位置下方插入,无需手动计算行号 - `append` 的 `--values` 二维数组中每行的列数必须一致,否则会报错。如果用户提供的数据中各行长度不同,必须先将短行用空字符串 `""` 补齐到与最长行相同的列数后再调用。追加的数据列数也应与工作表已有数据列数保持一致 - `append` vs `range update`:追加新行用 `append`,修改已有单元格用 `range update` -- ★ **`append` / `csv-put` 不支持 `{}` skip、`dataValidation`、富文本、公式**:这些能力仅限 `range update`。`append` 和 `csv-put` 只接受原始值(字符串/数字/布尔),走的是不同的 MCP tool(`append_rows` / `set_range_from_csv`)。需要写入公式、超链接、下拉列表或跳过部分单元格时,必须使用 `range update` +- ★ **`append` / `csv-put` 不支持 `{}` skip、dataValidation、富文本、超链接**:这些能力仅限 `range update`;需要公式、超链接、下拉列表或跳过部分单元格时,使用 `range update` diff --git a/skills/multi/dingtalk-sheet/references/sheet/sheet-write-index.md b/skills/multi/dingtalk-sheet/references/sheet/sheet-write-index.md new file mode 100644 index 00000000..73f32ed1 --- /dev/null +++ b/skills/multi/dingtalk-sheet/references/sheet/sheet-write-index.md @@ -0,0 +1,7 @@ +# 写入 + +| 命令 | 详见 | +|---|---| +| `sheet range update` | [`sheet-write-data.md`](./sheet-write-data.md) | +| `sheet range batch-set-style` | [`sheet-batch-operations.md`](./sheet-batch-operations.md) | +| `sheet range copy-to` / `move-to` / `clear` | [`sheet-range-operations.md`](./sheet-range-operations.md) | diff --git a/skills/multi/dingtalk-sheet/references/url-patterns.md b/skills/multi/dingtalk-sheet/references/url-patterns.md index 908e07ef..9498faa0 100644 --- a/skills/multi/dingtalk-sheet/references/url-patterns.md +++ b/skills/multi/dingtalk-sheet/references/url-patterns.md @@ -19,6 +19,8 @@ | 产品 | 用途 | URL 格式 | ID 来源 | |------|------|----------|---------| | `aitable` | AI表格 Base 链接 | `https://alidocs.dingtalk.com/i/nodes/{baseId}` | `base list/search/create/get` 返回的 `baseId` | +| `aitable` | AI表格指定数据表链接 | `https://alidocs.dingtalk.com/i/nodes/{baseId}?iframeQuery=sheetId%3D{tableId}` | `baseId` + `table create/get` 或 `base get` 返回的 `tableId` | +| `aitable` | AI表格指定数据表+视图链接 | `https://alidocs.dingtalk.com/i/nodes/{baseId}?iframeQuery=sheetId%3D{tableId}%26viewId%3D{viewId}` | `baseId` + `tableId` + `view create/get` 返回的 `viewId` | | `aitable` | AI表格模板预览 | `https://docs.dingtalk.com/table/template/{templateId}` | `template search` 返回的 `templateId` | | `doc` | 文档链接 | `https://alidocs.dingtalk.com/i/nodes/{dentryUuid}` | `doc` 命令返回的 `dentryUuid` | | `sheet` | 电子表格链接 | `https://alidocs.dingtalk.com/i/nodes/{dentryUuid}` | `sheet create` 返回的 `dentryUuid` | @@ -67,7 +69,7 @@ dws doc read --node "https://alidocs.dingtalk.com/i/p/Y7kmbokZp3pgGLq2/docs/AY39 > > 你可以: > 1. 在钉钉客户端中打开该文档,将正文内容复制粘贴给我 -> 2. 如果文档已保存在你的文档空间中,可以告诉我文档名称,我通过 `dws doc search` 搜索后再读取 +> 2. 如果文档已保存在你的文档空间中,可以告诉我文档名称,我通过 `dws drive search` 搜索后再读取 --- @@ -87,18 +89,18 @@ Step 3 → 按下方路由规则映射到对应产品 | 条件 | 路由到产品 | 后续操作 | |------|-----------|---------| -| `contentType=ALIDOC`, `extension=adoc` | `doc` | 切到 `dingtalk-doc` skill,按其中 `references/doc.md` 操作 | -| `contentType=ALIDOC`, `extension=axls` | `sheet` | 按 [sheet.md](./sheet.md) 操作(仅 `axls` 在线电子表格) | -| `contentType=ALIDOC`, `extension=able` | `aitable` | 将 nodeId 作为 baseId,切到 `dingtalk-aitable` skill 操作 | -| `contentType=DOCUMENT`, `extension=xlsx` / `xls` / `xlsm` / `csv` | `doc` | 必须用 `dws doc download` 下载到本地处理,禁止走 `sheet`(非在线表格,sheet 命令无法操作) | -| `contentType≠ALIDOC`, `nodeType=file` | `doc` | 调用 `dws doc download` 下载,返回文件下载链接 | -| `nodeType=folder` | `doc` | 调用 `dws doc list --folder ` 列出指定文件夹直接子节点列表 | +| `contentType=ALIDOC`, `extension=adoc` | `doc` | 加载 `dingtalk-doc` 操作内容 | +| `contentType=ALIDOC`, `extension=axls` | `sheet` | 加载 `dingtalk-sheet` 操作(仅 `axls` 在线电子表格) | +| `contentType=ALIDOC`, `extension=able` | `aitable` | 将 nodeId 作为 baseId,加载 `dingtalk-aitable` 操作 | +| `contentType=DOCUMENT`, `extension=xlsx` / `xls` / `xlsm` / `csv` | `drive` | 必须用 `dws drive download` 下载到本地处理,禁止走 `sheet`(非在线表格,sheet 命令无法操作) | +| `contentType≠ALIDOC`, `nodeType=file` | `drive` | 调用 `dws drive download` 下载,返回文件下载链接 | +| `nodeType=folder` | `drive` / `wiki` | 调用 `dws drive list --workspace ` 或 `dws wiki node list` 列出子节点 | | 以上均不匹配 | — | 告知用户当前暂不支持该类型 | > axls vs xlsx 关键区分: > - `axls`(钉钉在线电子表格,`contentType=ALIDOC`)→ 走 `sheet` 产品线(读/写/筛选/导出等服务端原子操作) -> - `xlsx` / `xls` / `xlsm` / `csv`(上传到文档空间的本地表格文件,`contentType=DOCUMENT`)→ 必须走 `dws doc download` 下载到本地后再解析处理,严禁错误路由到 `sheet` 产品线(sheet 命令只支持在线表格,调用 xlsx 节点会直接报错) -> - 用户想把在线表格导出为 xlsx 文件 → 开源 dws CLI 暂未暴露在线表格导出能力(旧动态 schema 曾包含 `submit_export_job` / `query_export_job`,但当前 cobra 未注册),需要在钉钉客户端手动导出 xlsx +> - `xlsx` / `xls` / `xlsm` / `csv`(上传到文档空间的本地表格文件,`contentType=DOCUMENT`)→ 必须走 `dws drive download` 下载到本地后再解析处理,严禁错误路由到 `sheet` 产品线(sheet 命令只支持在线表格,调用 xlsx 节点会直接报错) +> - 用户想把在线表格导出为 xlsx 文件 → 用 `dws sheet export`(输入是 `axls`,输出是 xlsx,这是 axls → xlsx 的格式转换,不属于 xlsx 读取场景) ### 示例 @@ -110,13 +112,13 @@ 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 +dws drive list --workspace --format json ``` ### 何时可跳过探测 diff --git a/skills/multi/dingtalk-skill/SKILL.md b/skills/multi/dingtalk-skill/SKILL.md index 31af3941..2f5b4531 100644 --- a/skills/multi/dingtalk-skill/SKILL.md +++ b/skills/multi/dingtalk-skill/SKILL.md @@ -1,6 +1,6 @@ --- name: dingtalk-skill -description: 悟空技能管理(搜索 / 安装技能)。Use when 用户说 搜索技能/找技能/安装技能/技能市场/企业技能库。注意:这是元 skill —— 它管理的是 dws 平台上的其他 skill。命令前缀:dws skill。 +description: DWS 技能市场搜索、下载、安装与内置技能部署。Use when 用户说搜索技能/找技能/安装技能/技能市场/企业技能库/安装 DWS multi 或 mono skill。注意:这是元 skill,只管理 dws 平台上的技能资源。Distinct from dws-shared(钉钉产品路由入口)、其他 dingtalk-* 产品 skill(执行具体业务能力)、本地 Codex skill 开发。命令前缀:dws skill。 cli_version: ">=0.2.14" metadata: category: product @@ -10,33 +10,36 @@ metadata: - dws --- -# 悟空技能管理 Skill +# DWS 技能管理 Skill > 🧪 **EXPERIMENTAL · 试验版 / Preview** — multi 模式当前未达 stable 标准。全部 dingtalk-* skill 已通过 dispatch verifier,但接口、命名、跨 skill 引用后续可能调整;生产 / 共享环境请优先使用 mono 模式(`dws skill setup --mode mono`)。问题请提 issue 反馈。 -> **PREREQUISITE:** Read the `dws-shared` skill first for auth, global flags, product routing, URL preflight, error codes, and safety rules. The `dws` binary must be on PATH. +## 前置条件 + +> **`use_skill(dws-shared)`** — 认证、全局参数、错误码与安全规则。执行网络请求前先读;本地 `skill setup` 可直接使用本 skill。 -> ⚠️ **命令可用性以当前 dws 二进制为准**。服务发现已下线,本文档随内置 skill 发布;如果 `dws --help` 不存在,说明当前版本未暴露该命令。若命令存在但调用失败,请按错误中的 endpoint 或 tool 提示确认静态端点目录和后端工具注册。实际调用前可用 `dws --help` 或 `--dry-run` 验证。 - - -> 命令参考:[skill.md](references/skill.md)。 +> 详细命令:[skill.md](references/skill.md)。 ## 意图表 | 用户说 | 命令 | -|--------|------| -| "搜索技能 / 找技能" | `dws skill search --query "<关键词>" [--source "DingtalkMarket OrgInternal"]` | -| "下载技能包到本地临时目录" | `dws skill get --skill-id ` | -| "安装技能到 Agent 目录" | `dws skill install `(target: claude / cursor / codex / opencode / qoder / .) | +|---|---| +| "搜索技能 / 找技能" | `dws skill search --query "<关键词>" [--source DingtalkMarket\|OrgInternal]` | +| "下载技能包" | `dws skill get --skill-id ` | +| "安装市场技能" | `dws skill install ` | +| "安装 DWS mono/multi skills" | `dws skill setup --mode --target --yes` | -## 环境 +## 约束 -- `DWS_SKILL_API_HOST` 覆盖技能 API 地址(默认 `https://mcp.dingtalk.com`) +- `skillId` 必须来自 `skill search` 返回,不能用名称代替。 +- `skill install` 的 `skillId` 与 `target` 是位置参数,不是 `--skill-id` flag。 +- `skill setup --mode multi` 可用 `--skill/-s` 只装指定产品,或用 `--exclude/-x` 排除产品,两者不能同时使用。 +- 搜索结果中的 `securityStatus` 需要如实展示;状态异常时不要把安装描述为已通过安全检测。 +- 开源 CLI 不提供技能发布/上传命令;发布需求应转到对应的技能市场发布流程。 ## 兼容提示 -- `dws skill find` → 用 `dws skill search --query <关键词>` -- `--scopes` 已废弃(会打印 `Flag --scopes has been deprecated, 请使用 --source 替代` 警告)→ 用 `--source` -- 前置:`skill search` 依赖企业开通技能市场;未开通时报「当前企业暂未开放此功能」,属企业级开关,非命令写法问题 +- `dws skill find` → `dws skill search --query <关键词>` +- `dws skill add` → `dws skill install ` diff --git a/skills/multi/dingtalk-skill/references/skill.md b/skills/multi/dingtalk-skill/references/skill.md index 68fdcea7..9da13a04 100644 --- a/skills/multi/dingtalk-skill/references/skill.md +++ b/skills/multi/dingtalk-skill/references/skill.md @@ -1,113 +1,70 @@ -# 单命令产品合集 +# 技能管理命令参考 -以下产品命令较少,合并参考。 - ---- - -## devdoc — 开放平台文档 - -### 搜索开放平台文档 -``` -Usage: - dws devdoc article search [flags] -Example: - dws devdoc article search --query "OAuth2 接入" --page 1 --size 10 --format json -Flags: - --query string 搜索关键词 (必填) - --page string 页码 (默认 1) - --size string 每页数量 (默认 10) -``` - -## live — 直播 - -### 查看我的直播列表 -``` -Usage: - dws live stream list [flags] -Example: - dws live stream list --format json -``` - ---- - -## skill — 技能管理 - -悟空技能市场与企业技能库:搜索技能、安装到本地 Agent 目录、从本地目录或 zip 发布到企业技能库。 - -### 搜索技能 +## 搜索技能 ``` Usage: dws skill search [flags] Example: dws skill search --query "周报" - dws skill search --query "日报" --source "OrgInternal" - dws skill search --query "日报" --source "DingtalkMarket OrgInternal" + dws skill search --query "日报" --source OrgInternal Flags: - --query string 搜索关键词 (必填) - --source string 查询范围,空格分隔。备选值:DingtalkMarket(钉钉市场)、OrgInternal(企业内部)。为空默认查市场技能 + --query string 搜索关键词 (必填) + --source string 查询范围:DingtalkMarket / OrgInternal;空格分隔 ``` -> `--scopes` 已废弃(会打印 `Flag --scopes has been deprecated, 请使用 --source 替代` 警告),一律用 `--source`。 -> 前置:`skill search` 依赖企业开通技能市场;未开通时报「当前企业暂未开放此功能」,这是企业级开关,非命令写法问题。 +从返回中提取真实 `skillId`、名称、版本、来源与 `securityStatus`。兼容入口 `skill find` 只会提示改用 `search`。 -返回字段: -- `skillId` — 技能 ID(后续 `install` 需要) -- `name` — 技能唯一标识(SKILL.md 的 name) -- `displayName` — 人类可读名称 -- `displayDescription` — 人类可读描述 -- `version` — 最新版本号 -- `relevanceScore` — 向量相关性分数 -- `source` — 来源:`DingtalkMarket`(钉钉市场)/ `OrgInternal`(企业内部) -- `securityStatus` — 安全检测状态:`passed`(通过)/ `failed`(未通过)/ `checking`(检测中) - -安全提示: 安全检测未通过的技能会标注 ⚠️ 警告,不建议安装。 - -前置: 已登录钉钉(未登录会由系统自动触发授权;可用 `dws auth status` 确认)(调用技能市场接口需 access token)。 - -兼容提示: `dws skill find` 会提示改用 `dws skill search --query <关键词>`。 - -### 安装技能 +## 下载技能包 ``` Usage: - dws skill install [flags] -Example: - dws skill install skill-123 claude # 安装到 ~/.claude/skills/ - dws skill install skill-123 cursor # 安装到 ~/.cursor/skills/ - dws skill install skill-123 codex # 安装到 ~/.codex/skills/ - dws skill install skill-123 . # 安装到当前目录 -Args: - 技能 ID(必填,从 search 结果获取) - 目标 Agent:claude / cursor / codex / qoder / opencode 或 . 表示当前目录 + dws skill get --skill-id +Flags: + --skill-id string 技能 ID (必填) ``` -流程: 下载技能包 → 解压 → 调用 real-cli 注册到悟空 SkillStore。 +成功后返回本地临时目录路径,供检查或后续安装使用。 -安全拦截: 安全检测未通过的技能默认拒绝安装,使用 `--force` 可强制安装。 +## 安装市场技能 -前置: 已登录钉钉(未登录会由系统自动触发授权;可用 `dws auth status` 确认);悟空 App 已安装。 +``` +Usage: + dws skill install +Example: + dws skill install skill-123 claude + dws skill install skill-123 qoder + dws skill install skill-123 . +``` -兼容提示: `dws skill add` 会提示改用 `dws skill install `(位置参数)。 +`skillId` 来自搜索结果;`target` 使用 `skill install --help` 列出的 Agent 名称,或用 `.` 安装到当前目录。两个值均为位置参数。 -环境: 技能 API 默认 `https://mcp.dingtalk.com`;可通过 `DWS_SKILL_API_HOST` 覆盖。 +## 部署 DWS 内置技能 -## 意图判断 +``` +Usage: + dws skill setup [flags] +Example: + dws skill setup --mode mono --yes + dws skill setup --mode multi --target qoder --yes + dws skill setup --mode multi -s aitable -s calendar --target qoder --yes + dws skill setup --mode multi -x live -x devdoc --target qoder --yes +Flags: + --mode string mono | multi + --target string 目标 Agent,默认 all + --source string 显式 skill 源目录 + -s, --skill strings multi 模式只安装指定子 skill + -x, --exclude strings multi 模式排除指定子 skill + --yes 跳过确认 +``` -- 用户说"开发文档/API 文档/接口文档/调用报错" → `devdoc article search` -- 用户说"直播/我的直播" → `live stream list` -- 用户说"搜索技能/找技能/安装技能/技能市场" → `skill search` / `skill install`(按步骤衔接) +`--skill` 与 `--exclude` 互斥。未指定 `--source` 时使用当前二进制内置的 skill 版本。 -## 上下文传递表 +## 上下文传递 | 操作 | 从返回中提取 | 用于 | -|------|-------------|------| -| `devdoc article search` | 文档链接 | 直接展示给用户 | -| `skill search` | `skillId`、名称、描述 | 用户选型后传给 `skill install ` | -| `skill install` | 安装成功/失败信息 | 确认目标 Agent 目录已注册 | -| `skill publish` | 发布结果(成功或错误信息) | 确认企业技能库已更新 | - -## 相关产品 - -- `dingtalk-calendar` (`references/calendar.md`) — 日历日程管理(含参与者/会议室) -- 视频会议(conference)— 当前 CLI 不提供对应 skill;发起/预约/邀请入会/会中控制请在钉钉客户端操作 +|---|---|---| +| `skill search` | `skillId`、版本、来源、安全状态 | 下载或安装 | +| `skill get` | 临时目录 | 本地检查 | +| `skill install` | 安装目标与结果 | 确认指定 Agent 已安装 | +| `skill setup` | 已安装/保留/跳过的 skill 列表 | 验证 mono/multi 部署 | diff --git a/skills/multi/dingtalk-todo/SKILL.md b/skills/multi/dingtalk-todo/SKILL.md index 868a3e30..8191e80b 100644 --- a/skills/multi/dingtalk-todo/SKILL.md +++ b/skills/multi/dingtalk-todo/SKILL.md @@ -1,6 +1,6 @@ --- name: dingtalk-todo -description: 钉钉待办 / TODO。Use when 用户说 创建待办/TODO/任务提醒/指派任务/标记完成/查待办/紧急待办/循环待办/批量建待办/逾期待办。Distinct from dingtalk-report(日报周报)、dingtalk-oa(审批)、dingtalk-calendar(日程)。命令前缀:dws todo。 +description: 钉钉待办 / TODO。Use when 用户说 创建待办/TODO/任务提醒/指派任务/标记完成/查待办/紧急待办/循环待办/批量建待办/逾期待办。不做日报周报(走 dingtalk-report)、审批(走 dingtalk-oa)、日程(走 dingtalk-calendar)。命令前缀:dws todo。 cli_version: ">=0.2.14" metadata: category: product @@ -14,13 +14,12 @@ metadata: > 🧪 **EXPERIMENTAL · 试验版 / Preview** — multi 模式当前未达 stable 标准。全部 dingtalk-* skill 已通过 dispatch verifier,但接口、命名、跨 skill 引用后续可能调整;生产 / 共享环境请优先使用 mono 模式(`dws skill setup --mode mono`)。问题请提 issue 反馈。 -> **PREREQUISITE:** Read the `dws-shared` skill first for auth, global flags, product routing, URL preflight, error codes, and safety rules. The `dws` binary must be on PATH. +## 前置条件 — 执行操作前必读 + +> **`use_skill(dws-shared)`** — 认证、全局参数(`--format json` / `--yes`)、错误码、URL 模板、跨产品消歧、安全规则与 capability 边界。**执行任何 `dws` 命令前先读;** 单产品的清晰命令可直接用本 skill。 -> ⚠️ **命令可用性以当前 dws 二进制为准**。服务发现已下线,本文档随内置 skill 发布;如果 `dws --help` 不存在,说明当前版本未暴露该命令。若命令存在但调用失败,请按错误中的 endpoint 或 tool 提示确认静态端点目录和后端工具注册。实际调用前可用 `dws --help` 或 `--dry-run` 验证。 - - > 命令参考:[todo.md](references/todo.md);剧本:[02-task.md](references/02-task.md)。 ## 意图表 @@ -38,6 +37,47 @@ metadata: | "修改标题/截止时间/优先级" | `dws todo task update --task-id ...` | | "删除待办" | `dws todo task delete --task-id `(需用户确认) | +## 标准 SOP(必遵流程) + +> 命中以下意图**必须**按对应 SOP 顺序执行;**禁止**跳步、替换命令、编造 taskId。每条命令必须带 `--format json`。创建、完成或删除后按对应 SOP 回读验证。 + +### SOP-1 建待办(create-todo) + +**触发**:建待办/任务提醒/指派任务/TODO。 + +1. **解析执行者(必须)**:指定姓名 → `dws aisearch person --keyword "<姓名>" --dimension name --format json` 取 `userId`;未指定 → `dws contact user get-self --format json` 取当前用户 `userId`;多人逐个搜索后英文逗号拼接。 +2. **执行(必须)**:`dws todo task create --title "<标题>" --executors [,...] --priority <10/20/30/40> --format json`;有截止时间加 `--due ""`;循环待办加 `--due "<首次截止ISO>" --recurrence "DTSTART:\nRRULE:FREQ=DAILY;INTERVAL=1"`。 +3. **验证(必须)**:从返回取 `taskId`/`todoTaskId`,立即 `dws todo task get --task-id --format json` 回读。 + +**禁止**:跳过执行者解析直接传姓名、用 `task detail` 取详情(正确是 `task get`)、创建后不回读。 + +### SOP-2 查询待办(query-todo) + +**触发**:查待办/今天本周待办/未完成/已完成。 + +1. **执行(必须)**:`dws todo task list --status false|true --format json`(`false`=未完成、`true`=已完成、不传=全部);`hasMore=true` 必须翻页。 +2. **摘要脚本(必须)**:今天/本周未完成 → `python scripts/todo_daily_summary.py today|tomorrow|week`;逾期 → `python scripts/todo_overdue_check.py`。 +3. **详情(必须)**:`dws todo task get --task-id --format json`;按主题筛选先 `task list` 再按标题过滤,**禁止**编造主题查询 flag。 + +**禁止**:写 `--done true`(用 `--status true`)、编造主题筛选参数。 + +### SOP-3 完成 / 重开 / 改 / 删(mutate-todo) + +**触发**:标记完成/重开/改标题截止优先级/删待办。 + +1. **执行(必须)**:完成/重开 `dws todo task done --task-id --status true|false --format json`;修改 `dws todo task update --task-id ...`;删除 `dws todo task delete --task-id `(**必须**先与用户确认)。 +2. **验证(必须)**:`task done`/`update` 后用 `task get` 或对应 `task list --status ...` 回读确认;`delete` 后用 `task get` 确认已不存在或列表已移除。 + +**禁止**:未确认就删除、用 `update --done`(首选 `task done --status`)、改动后不回读。 + +### SOP-4 批量建待办(batch-create) + +**触发**:批量建待办/一次建多条。 + +1. **执行(必须)**:`python scripts/todo_batch_create.py todos.json`(JSON 文件,每条含 title/executors/priority/due);执行者姓名需先批量解析成 userId 再写入 JSON。 + +**禁止**:在 JSON 里写姓名不写 userId、跳过脚本逐条手敲。 + ## 参数硬约束 - 任务详情只用 `dws todo task get --task-id `;不要写 `task detail`。 @@ -46,8 +86,7 @@ metadata: - `--id` / `--ids` 是隐藏兼容别名,文档和生成命令统一写 `--task-id`,减少模型漂移。 - 优先级映射:低=10,普通=20,较高/高/重要=30,紧急/最高/P0/马上处理=40;不要把"较高"写成 40。 - 截止时间必须是 ISO-8601。相对日期按当前日期计算;例如周五说"下周二"就是紧接下一个自然周的周二,不要再加一周。 -- 附件命令 `task add-attachment` / `list-attachment` / `remove-attachment` 均可用:`add-attachment --task-id --file-path <本地文件>`(真实上传,先确认待办存在,返回 `result.attachmentIds`);`list-attachment --task-id `(返回顶层 `attachments[].attachmentId`);`remove-attachment --task-id --attachment-id --yes`(不可逆,先确认)。 -- 创建、标记完成、重开、删除后必须 `task get` 或对应 `task list --status ...` 验证,不要只凭创建返回或口头计划结束。 +- 创建、标记完成、重开或删除后,使用 `task get` 或对应 `task list --status ...` 验证最终状态。 - 所有 dws 命令带 `--format json`。 ## 跨产品协作 @@ -55,3 +94,6 @@ metadata: - 执行人是人名 → 先用 `dingtalk-aisearch` 拿 `userId` - 会后从听记自动建待办 → 切到 `dingtalk-minutes` - 项目进度汇总写文档 → 切到 `dingtalk-doc` +## 局部意图与 Recipe + +- [局部意图消歧](references/intent-guide.md);[Lite Recipe](references/lite-recipes.md)。 diff --git a/skills/multi/dingtalk-todo/references/02-task.md b/skills/multi/dingtalk-todo/references/02-task.md index 7daaf1fc..7d5d9b2a 100644 --- a/skills/multi/dingtalk-todo/references/02-task.md +++ b/skills/multi/dingtalk-todo/references/02-task.md @@ -1,19 +1,19 @@ # 任务管理 -> **SKILL.md** 中 #2 仅内联 **lite**:`create-todo`、`list-todo`、`get-todo-detail`、`update-todo-status`、`query-todo-by-topic`。其中 `list-todo` 统一覆盖 open/completed/all(`--status false|true` 或不传),`update-todo-status` 统一覆盖 complete/reopen(`--status true|false`)。下列 recipe 已迁出速查表,命中时读本文件对应行。重型 **full** 见下表「行动指南」。命令细节见 [todo.md](./todo.md)。 +> **SKILL.md** 中 #2 仅内联 **lite**:`create-todo`、`todo-query-ops`。其中 `todo-query-ops` 统一覆盖 list/get/complete/reopen/topic-filter。下列 recipe 已迁出速查表,命中时读本文件对应行。重型 **full** 见下表「行动指南」。命令细节见 [todo.md](./todo.md)。 ## Recipe 速查(非 SKILL lite) -| Recipe | 步骤(命令均须 `--format json`,下略) | -|--------|----------------------------------------| -| `create-priority-todo` | 1. 确定执行者(同 [SKILL.md](../SKILL.md) 中 `create-todo` 步骤 1)
2. `todo task create --title "<标题>" --executors [,...] --priority <10/20/30/40>`(可选 `--due "<截止ISO>"`;10低/20普通/30较高/40紧急)→ 取 `taskId` | -| `create-recurring-todo` | 1. 确定执行者(同 `create-todo` 步骤 1)
2. `todo task create --title "<标题>" --executors --due "<首次截止ISO>" --recurrence "DTSTART:\nRRULE:FREQ=DAILY;INTERVAL=1"`(`--due` 必填;仅支持按天循环,见 [todo.md](./todo.md))→ 取 `taskId` | -| `reschedule-todo` | 1. `todo task list --status false` → 取 `taskId`
2. `todo task update --task-id --due "<新截止时间>"` | +| Recipe | 步骤(命令均须 `--format json`,下略) | +|--------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| `create-priority-todo` | 1. 确定执行者(同 [SKILL.md](../SKILL.md) 中 `create-todo` 步骤 1)
2. `todo task create --title "<标题>" --executors [,...] --priority <10/20/30/40>`(可选 `--due "<截止ISO>"`;10低/20普通/30较高/40紧急)→ 取 `todoTaskId` | +| `create-recurring-todo` | 1. 确定执行者(同 `create-todo` 步骤 1)
2. `todo task create --title "<标题>" --executors --due "<首次截止ISO> --priority <10/20/30/40>" --recurrence "DTSTART:\nRRULE:FREQ=DAILY;INTERVAL=1"`(`--due` 必填;仅支持按天循环,见 [todo.md](./todo.md))→ 取 `todoTaskId` | +| `reschedule-todo` | 1. `todo task list --status false` → 取 `todoTaskId`
2. `todo task update --task-id --due "<新截止时间>"` | ## Full / 组合(固定路线) -| Recipe | 行动指南(固定路线) | -|--------|---------------------| -| generate-progress-report | 1. 按「多源并行采集」(见 `dws-shared/references/best_practices/_common/conventions.md`)执行
2. 交叉比对各源数据
3. `doc create --name "<报告名>" --content "<报告内容>"` | -| batch-create-todo | 1. 按「多源并行采集」(见 `dws-shared/references/best_practices/_common/conventions.md`)执行 → 从结果提取任务条目
2. 每条:`aisearch person --keyword "<姓名>" --dimension name` → 取 `userId`
3. **优先**:将待办写入 `todos.json`(格式见 [todo_batch_create.py](../scripts/todo_batch_create.py)),执行 `python scripts/todo_batch_create.py todos.json`
备选:逐条 `todo task create --title "<标题>" --executors ` → 汇总回显
**单批超 30 条须用户确认** | -| assign-and-notify | 1. `aisearch person --keyword "<姓名>" --dimension name` → 取 `userId`
2. `todo task create --title "<标题>" --executors ` → 取 `taskId`
3. `chat search --query "<群名>"` → 取 `openConversationId` → `chat message send --group --text "<通知内容>"` 通知 | +| Recipe | 行动指南(固定路线) | +|--------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| generate-progress-report | 1. 按[「多源并行采集」](_common/conventions.md#多源并行采集公共模式)执行
2. 交叉比对各源数据
3. `doc create --name "<报告名>" --content "<报告内容>"` | +| batch-create-todo | 1. 按[「多源并行采集」](_common/conventions.md#多源并行采集公共模式)执行 → 从结果提取任务条目
2. 每条:`aisearch person --keyword "<姓名>" --dimension name` → 取 `userId`
3. **优先**:将待办写入 `todos.json`(格式见 [todo_batch_create.py](../scripts/todo_batch_create.py)),执行 `python scripts/todo_batch_create.py todos.json`
备选:逐条 `todo task create --title "<标题>" --executors --priority <10/20/30/40>` → 汇总回显
**单批超 30 条须用户确认** | +| assign-and-notify | 1. `aisearch person --keyword "<姓名>" --dimension name` → 取 `userId`
2. `todo task create --title "<标题>" --executors --priority <10/20/30/40>` → 取 `todoTaskId`
3. `chat search --query "<群名>"` → 取 `openConversationId` → `chat message send --group --text "<通知内容>"` 通知 | diff --git a/skills/multi/dingtalk-todo/references/_common/conventions.md b/skills/multi/dingtalk-todo/references/_common/conventions.md new file mode 100644 index 00000000..75491aab --- /dev/null +++ b/skills/multi/dingtalk-todo/references/_common/conventions.md @@ -0,0 +1,44 @@ +# 业务域通用规范 + +> 仅服务本 skill 已迁入的行动指南。安全门控、危险操作确认、`--format json` 等已在本 skill 的 `SKILL.md` 中定义,此处不重复。 + +## 批量查询规范 + +| # | 规范 | +|---|------| +| 1 | **并行查详情**:拿到多个 ID 后,用 `&` 合并到同一条 Shell 命令并行执行 + `wait`,**严禁逐条串行** | +| 2 | **翻页**:分页接口须拉全直至无更多 | +| 3 | **优先批量 API**:有批量接口则用批量;无则按 #1 并行 | +| 4 | **群消息**:必须先 `chat search --query` 得 `openConversationId`,再 `chat message list --group`;多群同条命令并行 | +| 5 | **列表少轮次**:带条件搜索/列表 → 一次采全详情;**禁止**无新参数时重复同一 `list` / `search` | + +## 多源并行采集(公共模式) + +> recipe 引用方式:`按「多源并行采集」执行(关键词=,时间=至)`。 + +- 同条 Shell:`&` 并行 + `wait`;分页须采全。 +- 只保留与主题相关的数据,无关丢弃。 +- 有批量详情接口优先;否则并行拉详情(见上表 #1)。 +- 具体采哪些产品列表由对应 **行动指南 recipe** 与当前产品参考决定;不要引入本文档未覆盖的产品路线。 + +## 字段术语与 ID 传递 + +> list 返回 JSON 后,必须提取下表字段传给后续命令。**禁止用其他字段替代。** + +| 字段 | 来源 | 传递给 | +|------|------|--------| +| `taskUuid` | `minutes list` | `minutes get summary/info/batch --id(s)` | +| `userId` | `aisearch person` / `contact user search` / `contact dept list-members` | `contact user get --ids`、`todo --executors`、`calendar --users` | +| `deptId` | `contact dept search` | `contact dept list-members --depts`;多子部门时对每个子部门分别 `dept search` 取 id | +| `nodeId` | `drive search` / `wiki node search` | `doc read/update --node`、`drive copy/move/rename/delete --node` | +| `nodeId` | `wiki node list` 中的 folder 类型节点 / `wiki node create --type folder` | `wiki node list --folder`、`wiki node create --folder`、`drive upload --folder`、`drive copy/move --folder` | +| `eventId` | `calendar event list` | `calendar event get/update --id` | +| `processInstanceId` | `oa approval list-*` | `oa approval detail/approve --instance-id` | +| `openConversationId` | `chat search` | `chat message list/send --group` | +| `todoTaskId` | `todo task list` | `todo task update/done --task-id` | +| `reportId` | `report inbox list` / `report outbox list` | `report entry get/stats --report-id` | +| `baseId` / `tableId` | `aitable base search` | `aitable record query --base-id --table-id` | +| `dentryUuid` | `drive list` / `drive mkdir` | `drive info/download --node`、`drive list/mkdir/upload --folder` | +| `dentryId` | `drive info` 的数字字段 | 仅用于 `chat message send --dentry-id` | + +**ID 边界硬约束**:`dentryId` 通常是纯数字,只表示聊天文件消息需要的钉盘条目数字 ID;它不是父目录 ID。遇到 `drive --node/--folder`、`doc --node/--folder`、`wiki node --folder` 时,只能使用 `dentryUuid` / `nodeId` / 文档 URL。若当前上下文只有数字型 `dentryId`,必须先重新 `drive list` / `drive search` / `wiki node list` 获取正确 ID,不能把该数字直接代入后续命令。 diff --git a/skills/multi/dingtalk-todo/references/intent-guide.md b/skills/multi/dingtalk-todo/references/intent-guide.md new file mode 100644 index 00000000..532c65ce --- /dev/null +++ b/skills/multi/dingtalk-todo/references/intent-guide.md @@ -0,0 +1,11 @@ +# todo 局部意图消歧 + +本文件从单 Skill `intent-guide.md` 拆分而来,仅保留与本产品相关的跨产品消歧规则。 + +| 用户说... | 真实意图 | 应该用 | 不要用 | 理由 | +|---|---|---|---|---| +| "帮我记一下明天要做的事" | 创建个人待办 | `todo` | `doc` | 个人待办提醒,非文档内容 | +| "给自己留一个明天下午的时间块/建个个人日程" | 创建个人日程 | `calendar event create` | `todo` | 个人 schedule 仍属于日历事件,不是待办 | +| "明早 9 点提醒我提交周报" | 创建个人待办,但需先声明 reminder 边界 | `todo` | `calendar` | todo 当前只支持 dueTime 截止时间,不支持独立精确 reminder | +| "帮我创建一个待办提醒" | 个人待办 | `todo` | `report` | 个人任务提醒,不是日志汇报 | +| "把最近几次关于XX的会议汇总成报告" | 按主题汇总多次听记 | #5 generate-topic-report | #7 meeting-followup | #7 是单次会议听记跟进;多次会议按主题汇总属于工作汇报 | diff --git a/skills/multi/dingtalk-todo/references/lite-recipes.md b/skills/multi/dingtalk-todo/references/lite-recipes.md new file mode 100644 index 00000000..0aa480f2 --- /dev/null +++ b/skills/multi/dingtalk-todo/references/lite-recipes.md @@ -0,0 +1,23 @@ +# todo Lite Recipe + +## create-todo + +1. 确定执行者:指定姓名 → `aisearch person --keyword "<姓名>" --dimension name` → userId;未指定 → `contact user get-self` → userId。 +2. 多人时逐个解析 userId,以英文逗号拼接。 +3. 创建:`todo task create --title "<标题>" --executors [,...] --priority <10|20|30|40> [--due "<截止ISO>"]`。 +4. 从返回中保存 `todoTaskId`,用于详情、完成或重开。 + +## todo-query-ops + +- 查询:`todo task list [--status false|true]` +- 详情:`todo task get --task-id ` +- 完成/重开:`todo task done --task-id --status ` + +## minutes-to-todo + +1. 切到 `dingtalk-minutes`,定位真实 taskUuid。 +2. 执行 `minutes get todos --id --format json`。 +3. 对每条行动项解析执行人 userId;原听记未给执行人或截止时间时先请用户确认。 +4. 使用 `todo task create` 创建任务;批量任务按返回逐条核对成功与失败项。 + +不要在 todo Skill 中复制听记的命令参考;听记查询、scope、分页与对象匹配规则以 `dingtalk-minutes` 为准。 diff --git a/skills/multi/dingtalk-todo/references/todo-index.md b/skills/multi/dingtalk-todo/references/todo-index.md new file mode 100644 index 00000000..587ba447 --- /dev/null +++ b/skills/multi/dingtalk-todo/references/todo-index.md @@ -0,0 +1,6 @@ +# 待办能力索引 + +| 主题 | 详见 | +|---|---| +| 待办创建/查询/更新/删除 | [`todo.md`](./todo.md) | +| 任务最佳实践 | [`02-task.md`](./02-task.md) | diff --git a/skills/multi/dingtalk-todo/references/todo.md b/skills/multi/dingtalk-todo/references/todo.md index 759c05be..4dc88724 100644 --- a/skills/multi/dingtalk-todo/references/todo.md +++ b/skills/multi/dingtalk-todo/references/todo.md @@ -38,13 +38,28 @@ Flags: Usage: dws todo task list [flags] Example: + dws todo task list --page 1 --size 20 dws todo task list --page 1 --size 20 --status false + dws todo task list --page 1 --size 20 --status false --priority 40,30,10 --role-types creator,executor,participant --plan-finish-date-start "2026-03-01T00:00:00+08:00" --plan-finish-date-end "2026-03-10T18:00:00+08:00" Flags: - --page string 页码 (默认 1) - --size string 每页数量 (默认 20) - --status string true=已完成, false=未完成 + --page string 页码 (默认 1) + --plan-finish-date-end string 截止时间范围查询结束 ISO-8601 (如 2026-03-10T18:00:00+08:00) + --plan-finish-date-start string 截止时间范围查询开始 ISO-8601 (如 2026-03-01T00:00:00+08:00) + --priority string 优先级: 10低/20普通/30较高/40紧急,支持逗号分隔多个值 (如 40,30,10) + --role-types string 角色类型: creator/executor/participant,可同时传入多个值用逗号分隔(如 creator,executor),默认 executor + --size string 每页数量 (默认 20) + --status string true=已完成, false=未完成 ``` +参数说明: + +| 参数 | 类型 | 说明 | +|------|------|------| +| `--priority` | string | 优先级筛选,支持逗号分隔多个值,如 `40,30,10` 表示同时筛选紧急、较高、低优先级 | +| `--role-types` | string | 按角色筛选,支持 `creator`(创建者)、`executor`(执行者)、`participant`(参与人);可同时传入多个角色用逗号分隔(如 `creator,executor`),一次查询即可覆盖多个角色,无需分多次调用;默认为 `executor` | +| `--plan-finish-date-start` | string | 截止时间范围查询开始,ISO-8601 格式,需与 `--plan-finish-date-end` 成对使用 | +| `--plan-finish-date-end` | string | 截止时间范围查询结束,ISO-8601 格式,需与 `--plan-finish-date-start` 成对使用 | + ### 修改待办任务 ``` Usage: @@ -188,11 +203,9 @@ Flags: --task-id string 待办任务 ID (必填) ``` -返回结构:子待办在**顶层** `subTasks[]` 数组里(不在 `result` 下),每个元素含 `taskId`(子待办 ID)、`subject`、`priority` 等。取子待办的 `taskId` 用于后续操作。`task get` 的 `result.todoDetailModel.subTodos[]` 没有 taskId 字段,取子待办 ID 必须用本命令。 - ### 上传待办附件 -> ⚠️ 重要:该接口会真实上传文件到附件,不可用于测试或试探性调用。调用前必须确认待办存在。 +> ⚠️ 重要:该接口会上传文件到附件,不可用于测试或试探性调用。调用前必须确认待办存在。 ``` Usage: @@ -204,10 +217,7 @@ Flags: --task-id string 待办任务 ID (必填) ``` -返回 `result.attachmentIds`(数组,如 `["6a4cffb79e2b520ed3600960"]`)。 - ### 查询待办附件列表 - ``` Usage: dws todo task list-attachment [flags] @@ -217,8 +227,6 @@ Flags: --task-id string 待办任务 ID (必填) ``` -返回顶层 `attachments[]`,每个元素含 `attachmentId`、`fileName`、`fileSize`;无附件时为空数组。 - ### 删除待办附件 > **CAUTION:** 不可逆操作 — 执行前必须向用户确认。 @@ -233,7 +241,7 @@ Flags: --attachment-id string 待办附件 ID (必填) --task-id string 待办任务 ID (必填) ``` -附件 attachmentId 使用 `dws todo task list-attachment` 命令获取(顶层 `attachments[].attachmentId`)。删除后可再 `list-attachment` 复查,返回空数组即成功。 +附件 attachmentId 使用 `dws todo task list-attachment` 命令获取。 ### 添加待办提醒 ``` @@ -307,13 +315,17 @@ JSON 数组,每个元素为一条提醒规则,支持两种 `baseTime` 模式 用户说"移除参与人/删除参与者" → `task remove-participant` 用户说"给待办加个提醒/设置提醒" → `task add-reminder` 用户说"重置提醒/清除提醒/修改提醒规则" → `task reset-reminder` +用户说"查看子待办/子任务列表" → `task list-sub` +用户说"给待办加个附件/上传附件" → `task add-attachment` +用户说"查看待办附件/附件列表" → `task list-attachment` +用户说"删除附件/移除附件" → `task remove-attachment` 关键区分: todo(个人待办) ## 核心工作流 ```bash -# 1. 创建待办 — 从返回 result.taskId 提取任务 ID +# 1. 创建待办 — 提取 todoTaskId dws todo task create --title "修复线上Bug" --executors userId1,userId2 \ --priority 40 --due "2026-03-10T18:00:00+08:00" --format json @@ -368,26 +380,27 @@ dws todo task reset-reminder --task-id --format json # 17. 重置待办提醒(指定新规则) dws todo task reset-reminder --task-id --reminder-rules '' --format json -# 18. 上传附件(真实上传,先确认待办存在)— 从返回 result.attachmentIds 取 attachmentId -dws todo task add-attachment --task-id --file-path /path/to/file.pdf --format json -# 19. 查询附件列表 — 从顶层 attachments[].attachmentId 取 ID -dws todo task list-attachment --task-id --format json -# 20. 删除附件(用户确认后加 --yes;删完可 list-attachment 复查为空) -dws todo task remove-attachment --task-id --attachment-id --yes --format json -# 21. 查询子待办 — 从顶层 subTasks[].taskId 取子待办 ID +# 18. 查询子待办列表 dws todo task list-sub --task-id --format json +# 19. 上传待办附件 +dws todo task add-attachment --task-id --file-path /path/to/file.pdf --format json +# 20. 查询待办附件列表 +dws todo task list-attachment --task-id --format json +# 21. 删除待办附件 +dws todo task remove-attachment --task-id --attachment-id --yes --format json ``` ## 上下文传递表 | 操作 | 从返回中提取 | 用于 | |------|-------------|---------------------------------------------| -| `task create` / `task create-sub` | `result.taskId` | update/done/get/delete/comment 的 --task-id | -| `task list` | `result.todoCards[].taskId` | update/done/get/delete/comment/add-executor/remove-executor/add-participant/remove-participant 的 --task-id | -| `task list-sub` | `subTasks[].taskId`(顶层数组,不在 `result` 下) | 子待办的后续操作 --task-id | -| `add-attachment` | `result.attachmentIds[]` | 新上传附件的 attachmentId | -| `list-attachment` | `attachments[].attachmentId`(顶层数组) | `remove-attachment` 的 --attachment-id | -| `comment list` | `result.comments[].id` | `comment delete` 的 --comment-id | +| `task create` | `todoTaskId` | update/done/get/delete 的 --task-id | +| `task list` | `result[].id` | update/done/get/delete 的 --task-id | +| `task create` | `todoTaskId` | update/done/get/delete/comment 的 --task-id | +| `task list` | `result[].id` | update/done/get/delete/comment/add-executor/remove-executor/add-participant/remove-participant 的 --task-id | +| `task get` | `result.todoDetailModel.subTodos[]` | 获取子待办列表,提取子待办的 `taskId` 用于后续操作 | +| `comment list` | `result[].commentId` | `comment delete` 的 --comment-id | +| `task list-attachment` | `result[].attachmentId` | `task remove-attachment` 的 --attachment-id | ## 注意事项 @@ -397,6 +410,9 @@ dws todo task list-sub --task-id --format json - `--recurrence`:仅在与 `--due` 同时设置时有效;当前仅支持按天循环。字符串内需含换行,示例:`DTSTART:20260320T020000Z\nRRULE:FREQ=DAILY;INTERVAL=1`(DTSTART 表示首次截止时间,需与业务约定一致) - 若用户的真实诉求是“到点提醒我”,需要先说明能力边界;当前 CLI 只能表达 deadline / recurrence,不能表达独立 reminder schedule - `task list` 的 `--status` 对应 MCP `get_user_todos_in_current_org` 的 `todoStatus` 参数 +- `task list` 的 `--priority` 支持逗号分隔多个优先级值(如 `40,30,10`),用于同时筛选多个优先级 +- `task list` 的 `--role-types` 支持 `creator`/`executor`/`participant`,可在一次调用中同时传入多个角色用逗号分隔(如 `--role-types creator,executor`),无需分多次查询;不传时默认按 `executor` 查询 +- `task list` 的 `--plan-finish-date-start` 与 `--plan-finish-date-end` 用于按截止时间范围筛选,需成对使用,格式为 ISO-8601 - todo 是个人待办管理产品 - `task update` 可同时修改标题/优先级/截止时间/完成状态 - `task done` 专用于修改执行者的完成状态,与 `task update --done` 作用不同 @@ -407,9 +423,10 @@ dws todo task list-sub --task-id --format json - 执行人 (executor) 与参与人 (participant) 的区别:执行人负责完成待办,参与人仅关注待办进度 - `task add-reminder` 用于为待办添加提醒,`--base-time` 支持 `dueTime`(基于截止时间偏移,待办必须有截止时间)和 `customTime`(自定义时间戳)两种模式 - `task reset-reminder` 用于重置待办提醒规则,不传 `--reminder-rules` 则清除所有提醒 -- `task add-attachment` / `list-attachment` / `remove-attachment` 三条附件命令均可用;`add-attachment` 会真实上传文件,勿用于试探性调用,先确认待办存在 -- 附件 ID 的取法:`add-attachment` 从 `result.attachmentIds[]` 取,`list-attachment` 从顶层 `attachments[].attachmentId` 取;`remove-attachment` 用 `--attachment-id` + `--yes` -- 子待办 ID 只能从 `task list-sub` 的顶层 `subTasks[].taskId` 取;`task get` 的 `result.todoDetailModel.subTodos[]` 没有 taskId 字段 +- `task list-sub` 用于查询指定待办的子待办列表,需通过 `task list` 或 `task create` 获取父待办 ID +- `task add-attachment` 用于上传本地文件作为待办附件,`--file-path` 为本地文件绝对路径;该操作不可逆,调用前必须确认待办存在 +- `task list-attachment` 用于查询指定待办的附件列表 +- `task remove-attachment` 用于删除待办附件,为不可逆操作,执行前需用户确认;`--attachment-id` 可通过 `task list-attachment` 获取 ## 自动化脚本 diff --git a/skills/multi/dingtalk-wiki/SKILL.md b/skills/multi/dingtalk-wiki/SKILL.md index d32fd99c..22b3d36f 100644 --- a/skills/multi/dingtalk-wiki/SKILL.md +++ b/skills/multi/dingtalk-wiki/SKILL.md @@ -1,6 +1,6 @@ --- name: dingtalk-wiki -description: 钉钉知识库(Wiki 空间)。Use when 用户说 知识库/wiki/创建知识库/搜索知识库空间/我的文档/知识库归档。Distinct from dingtalk-doc(单文档编辑)、dingtalk-drive(钉盘文件)。命令前缀:dws wiki。 +description: 钉钉知识库与空间管理。Use when 用户说 知识库/wiki/创建知识库/搜索知识库空间/我的文档/团队空间/空间成员/空间内节点创建/列出/搜索/删除。复制/移动统一走 dingtalk-drive;空间内单文档内容读写先用本 skill 定位再切到 dingtalk-doc。命令前缀:dws wiki。 cli_version: ">=0.2.14" metadata: category: product @@ -14,48 +14,80 @@ metadata: > 🧪 **EXPERIMENTAL · 试验版 / Preview** — multi 模式当前未达 stable 标准。全部 dingtalk-* skill 已通过 dispatch verifier,但接口、命名、跨 skill 引用后续可能调整;生产 / 共享环境请优先使用 mono 模式(`dws skill setup --mode mono`)。问题请提 issue 反馈。 -> **PREREQUISITE:** Read the `dws-shared` skill first for auth, global flags, product routing, URL preflight, error codes, and safety rules. The `dws` binary must be on PATH. +## 前置条件 — 执行操作前必读 + +> **`use_skill(dws-shared)`** — 认证、全局参数(`--format json` / `--yes`)、错误码、URL 模板、跨产品消歧、安全规则与 capability 边界。**执行任何 `dws` 命令前先读;** 单产品的清晰命令可直接用本 skill。 -> ⚠️ **命令可用性以当前 dws 二进制为准**。服务发现已下线,本文档随内置 skill 发布;如果 `dws --help` 不存在,说明当前版本未暴露该命令。若命令存在但调用失败,请按错误中的 endpoint 或 tool 提示确认静态端点目录和后端工具注册。实际调用前可用 `dws --help` 或 `--dry-run` 验证。 - - > 命令参考:[wiki.md](references/wiki.md)。 ## 意图表 -`dws wiki` 三个命令族:`space`(知识库容器)、`node`(库内节点:文档/文件夹/表格等)、`member`(容器级成员)。 - | 用户说 | 命令 | |--------|------| | "创建知识库" | `dws wiki space create --name "<名称>" [--desc "<描述>"]` | -| "查看知识库详情" | `dws wiki space get --workspace ` | -| "搜索知识库空间" | `dws wiki space search --query "<关键词>" [--limit <1-20>]` | -| "我的文档 / 个人知识库" | `dws wiki space list --type myWikiSpace` | -| "列出组织知识库" | `dws wiki space list [--type orgWikiSpace] [--limit <1-50>]` | -| "删除知识库" | `dws wiki space delete --workspace `(不可逆,先确认)| -| "知识库里有哪些文档/浏览内容" | `dws wiki node list --workspace [--folder ]` | -| "在知识库里创建文档/文件夹" | `dws wiki node create --workspace --name "<名>" [--type folder\|axls\|...]` | -| "空间内搜文档" | `dws wiki node search --workspace --query "<关键词>"` | -| "复制 / 移动库内节点" | `dws wiki node copy` / `dws wiki node move --workspace --node [--folder ]` | -| "删除库内节点" | `dws wiki node delete --workspace --node `(不可逆,先确认)| -| "把知识库分享给某人 / 加成员" | `dws wiki member add --workspace --users --role EDITOR` | -| "改成员角色" | `dws wiki member update --workspace --users --role ` | -| "移除知识库成员" | `dws wiki member remove --workspace --users ` | -| "查看知识库成员" | `dws wiki member list --workspace ` | +| "搜索组织知识库空间" | `dws wiki space search --type orgWikiSpace --query "<关键词>"` | +| "我的文档 / 个人知识库" | `dws wiki space search --type myWikiSpace` | +| "列出知识库" | `dws wiki space list` | +| "列出我的文档空间" | `dws wiki space list --type myWikiSpace` | +| "列出知识库里的文件/节点" | `dws wiki node list --workspace ` | +| "在知识库里搜" | `dws wiki node search --workspace --keyword "<关键词>"` | +| "在知识库里创建文档节点" | `dws wiki node create --workspace --type adoc --name "<名称>"` | -## 评测高频硬约束 +## 标准 SOP(必遵流程) -- `space search` / `node search` 关键词 flag 是 `--query`(`--keyword` 是遗留别名,仍可跑通,但优先用 `--query`);`space create --desc`、`space list --cursor` 同理(`--description` / `--page-token` 为旧别名)。 -- 按类型列出空间优先走 `space list --type myWikiSpace/orgWikiSpace`;用户说"我的文档/个人空间"时用 `dws wiki space list --type myWikiSpace --format json`。 -- 用户给空关键词时,不要构造空 `--query ""`;若语义是我的文档则走 `space list --type myWikiSpace`,否则请用户补关键词。 -- `node create --type` 服务端支持 `adoc / axls / able / appt / adraw / amind / folder`;**`asheet` 不支持**,创建在线表格用 `axls`。 -- `member` 的 `--users` 是复数、逗号分隔;`--role` 大小写不敏感(`editor` 与 `EDITOR` 都行)。`member list` 只返回 `name/role/type`,**不含 userId**,拿不到 userId 去串联 update/remove,需用 `dws contact user search --query "<姓名>"` 反查。 -- 知识库内节点的**内容读写**(读取/编辑文档正文)切到 `dingtalk-doc`(先 `node list`/`node search` 拿 nodeId,再 `dws doc read --node `);不要在 `wiki` 下编造 doc 子命令。 +> 命中以下意图**必须**按对应 SOP 顺序执行;**禁止**跳步、替换命令、编造 workspaceId/nodeId。每条命令必须带 `--format json`。`workspaceId`/`nodeId` 一律先查后用。 + +### SOP-1 找知识库(find-space) + +**触发**:找知识库/列表空间/某知识库在哪。 + +1. **执行(必须)**:`dws wiki space list --format json`(列所有知识库/钉盘空间);按名称找 `dws wiki space search --keyword "<名称>" --format json`。 +2. **解析(必须)**:取真实 `workspaceId`;多候选让用户确认,**禁止**默认取第一个。`hasMore=true` 用 `nextPageToken` 翻页。 + +**禁止**:编造 workspaceId、把空间名当 ID。 + +### SOP-2 浏览 / 查节点(list-nodes) + +**触发**:知识库里有哪些文档/列节点/看某节点。 + +1. **前置(必须)**:先按 SOP-1 拿 `workspaceId`。 +2. **执行(必须)**:`dws wiki node list --workspace --format json`(按需 `--parent-id ` 看子节点);按内容找 `dws wiki node search --workspace --keyword "<关键词>" --format json`。 +3. **解析(必须)**:取真实 `nodeId` + `nodeType`/`type`;锁定目标后按需切 `dingtalk-doc` 读写节点内容。 + +**禁止**:跳过 SOP-1 直接猜 workspaceId、用 `node list` 替代 `node search` 做关键词查找。 + +### SOP-3 建节点(create-node) + +**触发**:在知识库建文档/页面。 + +1. **执行(必须)**:`dws wiki node create --workspace --type adoc --name "<名称>" --format json`(按需 `--parent-id <父节点>`);返回取 `nodeId`。 +2. **写内容(必须)**:节点内容编辑切 `dingtalk-doc`,用 `dws doc update --node --mode overwrite|append --content-file --yes`;写后 `doc read` 回读。 +3. **验证(必须)**:`dws wiki node list --workspace --format json` 复核节点已建。 + +**禁止**:在 wiki 内直接拼内容(应切 doc 写)、建后不回读。 + +### SOP-4 移动 / 复制 / 删节点(mutate-node) + +**触发**:移动节点/复制节点/删节点。 + +1. **执行(必须)**:移动 `dws wiki node move --workspace --node --folder <目标文件夹nodeId>`;复制 `node copy`;删除 `node delete --node `(**必须**先与用户确认)。 + +**禁止**:未确认就删除/移动、编造 nodeId/targetParentId。 + +## 高频硬约束 + +- `space search` 用 `--query`,不要用 `--keyword`;组织知识库显式加 `--type orgWikiSpace`。 +- 用户说"我的文档/个人空间/my workspace"时必须用 `dws wiki space search --type myWikiSpace --format json`;该模式不需要 keyword。 +- 用户给空关键词时,不要构造空 `--query ""`;若语义是我的文档则用 `--type myWikiSpace`,否则请用户补关键词。 +- 搜到空间后复用返回的 `workspaceId/id`;空间内节点创建/列表/搜索用 `wiki node`,具体文档内容读写切到 `dingtalk-doc`,复制/移动切到 `dingtalk-drive`。 - 所有 `dws wiki` 命令加 `--format json`。 ## 跨产品协作 -- 知识库内文档内容读写 → 切到 `dingtalk-doc` -- 文件存储 / 全局搜索 → 切到 `dingtalk-drive` +- 知识库内具体文档读写 → 切到 `dingtalk-doc` +- 文件存储 → 切到 `dingtalk-drive` +## 局部意图与 Recipe + +- [局部意图消歧](references/intent-guide.md);[Lite Recipe](references/lite-recipes.md)。 diff --git a/skills/multi/dingtalk-wiki/references/intent-guide.md b/skills/multi/dingtalk-wiki/references/intent-guide.md new file mode 100644 index 00000000..62e83b7a --- /dev/null +++ b/skills/multi/dingtalk-wiki/references/intent-guide.md @@ -0,0 +1,12 @@ +# wiki 局部意图消歧 + +本文件从单 Skill `intent-guide.md` 拆分而来,仅保留与本产品相关的跨产品消歧规则。 + +| 用户说... | 真实意图 | 应该用 | 不要用 | 理由 | +|---|---|---|---|---| +| "帮我看看知识库里的文件" | 知识库节点列表 | `wiki node list --workspace` | `drive list` | 明确"知识库"上下文 → wiki node list | +| "列出钉盘团队空间" | 列出钉盘空间 | `wiki space list --type orgSpace` | `drive list-spaces` | 空间管理归 wiki,drive list-spaces 已 deprecated | +| "在知识库里搜方案" | 空间内搜索 | `wiki node search --workspace` | `drive search` | 指定了空间上下文 → wiki node search | +| "搜一下有没有叫XX的文件" | 全局搜索 | `drive search` | `wiki node search` | 未指定空间 → drive search 全局聚合搜索 | +| "在知识库里创建一个文档" | 创建空文件实体 | `wiki node create --type adoc` | `doc create` | 空间内创建节点归 wiki;doc create 是向已有文档写入内容,不是创建文件节点 | +| "整理一下XX项目的所有讨论" | 跨源主题归档 | #5 generate-topic-report | #4 write-doc | #4 侧重单篇文档创作;按主题跨听记/群消息汇总属于工作汇报 | diff --git a/skills/multi/dingtalk-wiki/references/lite-recipes.md b/skills/multi/dingtalk-wiki/references/lite-recipes.md new file mode 100644 index 00000000..ce42cba8 --- /dev/null +++ b/skills/multi/dingtalk-wiki/references/lite-recipes.md @@ -0,0 +1,44 @@ +# wiki Lite Recipe + +本文件从单 Skill `lite-recipes.md` 拆分而来,仅保留与本产品相关的轻量流程。 + +## #4 文档知识 + +### query-doc + +1. 全局搜索:`drive search --query "<关键词>"` → `nodeId`(聚合钉盘+文档空间) +2. 空间内搜索:`wiki node search --workspace --keyword "<关键词>"` → `nodeId` +3. `doc read --node `(按需;大文档只抽章节) + +### list-folder-docs + +`drive list --workspace ` 或 `wiki node list --workspace ` + +### import-file + +将本地文件导入为钉钉在线文档。**一条命令完成上传+格式转换+创建**,无需先读取文件内容。 + +```bash +dws doc import --file ./report.docx --format json +``` + +1. 确认文件路径(用户提供的本地文件路径) +2. 执行:`dws doc import --file <文件路径> --format json`(可选 `--folder <文件夹ID>` / `--workspace <知识库ID>` / `--name "文档名"`) +3. 从返回中提取 `documentUrl`,告知用户导入完成并提供链接 +4. 超时或中断时 CLI 返回 `taskId`,用 `dws doc import get --task-id --format json` 手动查询 + +**`--folder` 参数传值规则**: +- 首选路径:用户提供 alidocs URL 时,直接将完整 URL 传入 `--folder`,无需先调 `doc info` +- 预检路径:若需确认 URL 指向的是文件夹,可先调 `dws doc info --node `: + - `nodeType == "folder"` → 使用 `nodeId` 或原始 URL 作为 `--folder` 值 + - `nodeType` 不是 folder → 提示用户:该链接指向的不是文件夹 +- 禁止:不得使用 `doc info` 返回的 `folderId` 字段作为 `--folder` 的值(`folderId` 是父文件夹 ID,非当前节点 ID) + +格式与文档类型映射: +- `.docx` / `.doc` → 文字文档(DOC) +- `.xlsx` / `.xls` → 电子表格(SHEET) +- `.xmind` / `.mark` → 脑图(MIND) +- `.md` / `.txt` → 文字文档(DOC) + +> **禁止先 Read 文件再 `doc create` + `doc update`**。`doc import` 是服务端格式转换,客户端无需解析文件内容。 +> 详见 [./doc/doc-import.md](../../dingtalk-doc/references/doc/doc-import.md)。 diff --git a/skills/multi/dingtalk-wiki/references/wiki-index.md b/skills/multi/dingtalk-wiki/references/wiki-index.md new file mode 100644 index 00000000..7057489b --- /dev/null +++ b/skills/multi/dingtalk-wiki/references/wiki-index.md @@ -0,0 +1,5 @@ +# 知识库能力索引 + +| 主题 | 详见 | +|---|---| +| 空间/节点/成员管理 | [`wiki.md`](./wiki.md) | diff --git a/skills/multi/dingtalk-wiki/references/wiki.md b/skills/multi/dingtalk-wiki/references/wiki.md index eacfb661..3da1556e 100644 --- a/skills/multi/dingtalk-wiki/references/wiki.md +++ b/skills/multi/dingtalk-wiki/references/wiki.md @@ -14,7 +14,6 @@ dws wiki member add --help # 查看子命令组下的所有命令 dws wiki space --help -dws wiki node --help dws wiki member --help ``` @@ -23,8 +22,6 @@ dws wiki member --help - 报错 "unknown flag" 时 → `--help` 确认正确的 flag 名称 - 不确定某个功能是否存在时 → `dws wiki --help` 查看命令列表 -`dws wiki` 有三个命令族:`space`(知识库容器)、`node`(知识库内节点:文档/文件夹/表格等)、`member`(容器级成员)。 - ## 命令总览 ### 创建知识库 @@ -35,11 +32,31 @@ Example: dws wiki space create --name "产品文档库" --format json dws wiki space create --name "技术方案" --desc "团队技术方案归档" --format json Flags: - --name string 知识库名称 (必填,不超过 100 字符) + --name string 知识库名称 (必填,不超过 100 字符) --desc string 知识库描述 (选填,不超过 500 字符) - --icon string 知识库图标标识 (选填) + --icon string 知识库图标标识 (选填) ``` +### 删除知识库 + +> **CAUTION:** 不可逆操作 — 执行前必须向用户确认。 + +``` +Usage: + dws wiki space delete [flags] +Example: + dws wiki space delete --workspace + dws wiki space delete --workspace "https://alidocs.dingtalk.com/i/spaces/xxx/overview" +Flags: + --workspace string 知识库 ID 或 URL (必填) +``` + +将指定知识库移入回收站。删除后知识库会进入回收站,可在回收站中恢复。 + +> **重要约束**: +> - 操作者必须具备知识库的 OWNER 角色。 +> - 删除操作不可逆(从回收站恢复除外),请确认后再执行。 + ### 查看知识库详情 ``` Usage: @@ -63,9 +80,9 @@ Example: dws wiki space list --type myWikiSpace --format json dws wiki space list --type orgWikiSpace --limit 50 --format json Flags: - --type string 空间类型: orgWikiSpace(默认) / myWikiSpace / orgSpace(钉盘企业空间) / mySpace(钉盘我的文件) - --limit string 每页数量 1-50 (默认 20) - --cursor string 分页游标 (首页留空) + --type string 知识库类型: myWikiSpace / orgWikiSpace (默认 orgWikiSpace) + --limit string 每页数量 1-50 (默认 20) + --cursor string 分页游标 (首页留空) ``` - `myWikiSpace`:返回当前用户的「我的文档」个人空间(固定 1 条,不支持分页) @@ -78,37 +95,92 @@ Usage: Example: dws wiki space search --query "产品文档" --format json dws wiki space search --query "技术方案" --limit 20 --format json - dws wiki space list --type myWikiSpace --format json + dws wiki space search --type myWikiSpace --format json Flags: - --query string 搜索关键词 (搜索组织知识库时必填) - --type string 知识库类型: myWikiSpace 时直接返回「我的文档」,省略则搜索组织知识库 - --limit string 返回数量 1-20 (默认 10) + --query string 搜索关键词 (--type myWikiSpace 时可省略) + --type string 知识库类型: myWikiSpace 时直接返回「我的文档」,省略则搜索组织知识库 + --limit string 返回数量 1-20 (默认 10) ``` 当 `--type myWikiSpace` 时,忽略 `--query`,直接返回「我的文档」个人空间。 -### 删除知识库 - -> **CAUTION:** 不可逆操作 — 执行前必须向用户确认。 - +### 添加知识库成员(容器级授权) ``` Usage: - dws wiki space delete [flags] + dws wiki member add [flags] Example: - dws wiki space delete --workspace + dws wiki member add --workspace --users uid1 --role READER + dws wiki member add --workspace --users uid1,uid2 --role EDITOR + dws wiki member add --workspace "https://alidocs.dingtalk.com/i/spaces//overview" --users uid1 --role MANAGER Flags: - --workspace string 知识库 ID 或 URL (必填) + --workspace string 目标知识库 ID 或 URL (必填) + --users strings 被加入的用户 userId 列表,逗号分隔 (必填,单次最多 30 个) + --role string 授予的角色 (必填,大小写敏感,必须全大写): MANAGER (管理者) / EDITOR (可编辑) / DOWNLOADER (可下载) / READER (可阅读) ``` -将指定知识库移入回收站。操作者必须具备知识库的 OWNER 角色。 +> **重要约束**: +> - 仅支持 USER 类型。 +> - 角色枚举严格大写:MANAGER / EDITOR / DOWNLOADER / READER(OWNER 不可通过此接口添加,知识库创建者默认为所有者)。 +> - 操作者需具备知识库的 OWNER 或 MANAGER 权限。 +> - 「我的文档」(myWikiSpace) 是个人空间,**不支持容器级成员管理**;后端会直接拒绝。如果你的目标只是把某篇文档分享给别人,请改用 `dws drive permission add` 在节点级别授权。 + +### 移除知识库成员 +``` +Usage: + dws wiki member remove [flags] +Example: + dws wiki member remove --workspace --users uid1 + dws wiki member remove --workspace --users uid1,uid2 +Flags: + --workspace string 目标知识库 ID 或 URL (必填) + --users strings 被移除的用户 userId 列表,逗号分隔 (必填,单次最多 30 个) +``` + +> **重要约束**: +> - OWNER 角色不可通过此接口移除。 +> - 操作者需具备知识库的 OWNER 或 MANAGER 权限。 +> - 移除后相关用户将无法访问该知识库下的内容(除非通过节点级权限另行授权)。 +> - 「我的文档」(myWikiSpace) 是个人空间,**不支持容器级成员管理**。 + +### 修改知识库成员角色 +``` +Usage: + dws wiki member update [flags] +Example: + dws wiki member update --workspace --users uid1 --role EDITOR + dws wiki member update --workspace --users uid1,uid2 --role READER +Flags: + --workspace string 目标知识库 ID 或 URL (必填) + --users strings 目标用户 userId 列表,逗号分隔 (必填,单次最多 30 个) + --role string 新角色 (必填,大小写敏感,必须全大写): MANAGER / EDITOR / DOWNLOADER / READER +``` + +### 列出知识库成员 +``` +Usage: + dws wiki member list [flags] +Example: + dws wiki member list --workspace + dws wiki member list --workspace --limit 100 + dws wiki member list --workspace --filter-role EDITOR +Flags: + --workspace string 目标知识库 ID 或 URL (必填) + --limit int 返回数量上限,最大 200 (默认 50) + --filter-role string 按角色过滤: MANAGER / EDITOR / DOWNLOADER / READER (选填) +``` + +> 接口不支持游标分页,使用 `--limit` 一次性拉取。 ### 列出知识库节点 ``` Usage: dws wiki node list [flags] +Aliases: + list, ls Example: dws wiki node list --workspace --format json dws wiki node list --workspace --folder --format json + dws wiki node list --workspace --limit 20 --cursor --format json Flags: --workspace string 知识库 ID (必填) --folder string 父节点 nodeId (选填,不传则列出根目录) @@ -123,16 +195,14 @@ Usage: Example: dws wiki node create --workspace --name "新文档" --format json dws wiki node create --workspace --name "方案目录" --type folder --format json - dws wiki node create --workspace --name "数据表" --type axls --folder --format json + dws wiki node create --workspace --name "数据表" --type asheet --folder --format json Flags: --workspace string 知识库 ID (必填) --name string 节点名称 (必填) - --type string 节点类型 (默认 adoc),服务端支持: adoc(在线文档) / axls(在线表格) / able(多维表) / appt(在线演示) / adraw(白板) / amind(脑图) / folder(文件夹) + --type string 节点类型: adoc / asheet / folder / axls (默认 adoc) --folder string 父节点 nodeId (选填,不传则在根目录创建) ``` -> ⚠️ **type 枚举**:服务端支持 `adoc` / `axls` / `able` / `appt` / `adraw` / `amind` / `folder` 七种;**`asheet` 不支持**,传了会被后端拒绝(报「不支持的文件类型 asheet」)。创建在线表格请用 `axls`。 - ### 复制知识库节点 ``` Usage: @@ -152,6 +222,7 @@ Usage: dws wiki node move [flags] Example: dws wiki node move --workspace --node --folder --format json + dws wiki node move --workspace --node --format json Flags: --workspace string 知识库 ID (必填) --node string 源节点 ID (必填) @@ -167,12 +238,13 @@ Usage: dws wiki node delete [flags] Example: dws wiki node delete --workspace --node + dws wiki node delete --workspace --node --yes Flags: --workspace string 知识库 ID (必填,用于权限校验) --node string 节点 ID (必填) ``` -将知识库中的节点移入回收站。权限要求: 对节点有「管理」权限。 +将知识库中的节点移入回收站。权限要求: 对节点有"管理"权限。 ### 在知识库中搜索节点 ``` @@ -180,104 +252,73 @@ Usage: dws wiki node search [flags] Example: dws wiki node search --workspace --query "方案" --format json - dws wiki node search --workspace --query "设计" --extensions adoc,axls --format json + dws wiki node search --workspace --query "周报" --limit 10 --format json + dws wiki node search --workspace --query "设计" --extensions adoc,asheet --format json Flags: - --workspace string 知识库 ID (必填) - --query string 搜索关键词 (必填) - --extensions strings 按文件扩展名过滤,逗号分隔 (如 adoc,axls,pdf) (选填) - --limit int 每页数量 (默认 10,最大 30) (选填) - --cursor string 分页游标 (选填) + --workspace string 知识库 ID (必填) + --query string 搜索关键词 (必填) + --extensions string 按文件类型过滤,逗号分隔: adoc,asheet 等 (选填) + --limit int 每页数量 (选填) + --cursor string 分页游标 (选填) ``` -在指定知识库空间内搜索节点(需 `--workspace`)。全局聚合搜索走 `dingtalk-drive` 的 `drive search`。 +在指定知识库空间内搜索节点。与 `drive search` 的区别: +- `wiki node search` — 限定在某个知识库空间内搜索(需要 `--workspace`) +- `drive search` — 全局搜索,聚合钉盘 + 文档空间结果 + +### 列出空间(支持钉盘空间类型) + +`wiki space list` 除了支持知识库类型(`orgWikiSpace` / `myWikiSpace`),还支持钉盘空间类型: -### 添加知识库成员(容器级授权) ``` Usage: - dws wiki member add [flags] -Example: - dws wiki member add --workspace --users uid1 --role READER - dws wiki member add --workspace --users uid1,uid2 --role EDITOR + dws wiki space list --type orgSpace --format json # 钉盘企业空间 + dws wiki space list --type mySpace --format json # 钉盘「我的文件」 + dws wiki space list --type orgWikiSpace --format json # 知识库(默认) + dws wiki space list --type myWikiSpace --format json # 我的文档 Flags: - --workspace string 目标知识库 ID 或 URL (必填) - --users string 被加入的用户 userId 列表,逗号分隔 (必填,单次最多 30 个) - --role string 授予的角色 (必填,大小写不敏感): MANAGER / EDITOR / DOWNLOADER / READER + --type string 空间类型: + orgWikiSpace (默认) — 组织知识库 + myWikiSpace — 我的文档个人空间 + orgSpace — 钉盘企业空间 + mySpace — 钉盘「我的文件」 + --limit string 每页数量 1-50 (默认 20) + --cursor string 分页游标 (首页留空) ``` -> **❗ 重要约束**: -> - 仅支持 USER 类型;角色枚举 MANAGER / EDITOR / DOWNLOADER / READER(OWNER 不可通过此接口添加,知识库创建者默认为所有者)。 -> - `--role` **大小写不敏感**,`editor` 与 `EDITOR` 都能通过校验(CLI 会归一化成大写)。 -> - 操作者需具备知识库的 OWNER 或 MANAGER 权限。 -> - 「我的文档」(myWikiSpace) 是个人空间,**不支持容器级成员管理**;后端会直接拒绝。节点级权限授权在开源 dws 暂不支持,请在钉钉客户端文档中通过「分享」设置。 - -### 修改知识库成员角色 -``` -Usage: - dws wiki member update [flags] -Example: - dws wiki member update --workspace --users uid1 --role EDITOR -Flags: - --workspace string 目标知识库 ID 或 URL (必填) - --users string 目标用户 userId 列表,逗号分隔 (必填,单次最多 30 个) - --role string 新角色 (必填,大小写不敏感): MANAGER / EDITOR / DOWNLOADER / READER -``` - -### 移除知识库成员 -``` -Usage: - dws wiki member remove [flags] -Example: - dws wiki member remove --workspace --users uid1 -Flags: - --workspace string 目标知识库 ID 或 URL (必填) - --users string 被移除的用户 userId 列表,逗号分隔 (必填,单次最多 30 个) -``` - -> OWNER 角色不可通过此接口移除;操作者需具备 OWNER 或 MANAGER 权限。 - -### 列出知识库成员 -``` -Usage: - dws wiki member list [flags] -Example: - dws wiki member list --workspace - dws wiki member list --workspace --filter-role EDITOR -Flags: - --workspace string 目标知识库 ID 或 URL (必填) - --limit int 返回成员数上限,默认 30,最大 200 - --filter-role string 按角色过滤,逗号分隔: OWNER / MANAGER / EDITOR / DOWNLOADER / READER (选填) -``` - -> 接口不支持游标分页,使用 `--limit` 一次性拉取。 - -> ⚠️ **返回字段限制**:`member list` 每条只返回 `name` / `role` / `type` 三个字段,**不含 userId**(服务端不返回)。因此**无法**从 `member list` 拿到 userId 再去串联 `member update` / `member remove`。要对某人改角色 / 移除,需另行拿到其 userId(例如用 `dws contact user search --query "<姓名>"` 按姓名反查)。 +> 钉盘空间类型(`orgSpace` / `mySpace`)会自动路由到钉盘 MCP 服务,等同于原 `drive list-spaces`(已 deprecated)。 ## 意图判断 - 用户说"创建知识库/新建知识库" → `space create` - 用户说"查看知识库/知识库详情" → `space get` - 用户说"我的知识库/知识库列表/有哪些知识库" → `space list` +- 用户说"列出钉盘空间/钉盘团队空间" → `space list --type orgSpace` - 用户说"搜索知识库/找知识库" → `space search` - 用户说"我的文档/个人空间" → `space list --type myWikiSpace` -- 用户说"删除知识库/移除知识库" → `space delete`(需 `--workspace`) - 用户说"知识库下的文件/知识库里有哪些文档/浏览知识库内容" → `node list`(需 `--workspace`) - 用户说"在知识库里搜文档/空间内搜索" → `node search`(需 `--workspace` + `--query`) - 用户说"在知识库里创建文档/新建文件夹" → `node create`(需 `--workspace` + `--name`) -- 用户说"复制/移动知识库里的文档" → `node copy` / `node move`(需 `--workspace` + `--node`) +- 用户说"复制知识库里的文档" → `node copy`(需 `--workspace` + `--node`) +- 用户说"移动知识库里的文档" → `node move`(需 `--workspace` + `--node`) - 用户说"删除知识库里的文档/节点" → `node delete`(需 `--workspace` + `--node`) -- 用户说"把知识库分享给某人/邀请进知识库" → `member add`(需 `--workspace` + `--users` + `--role`) +- 用户说"把知识库分享给某人/给某人加入知识库/邀请进知识库" → `member add`(需 `--workspace` + `--user` + `--role`) - 用户说"修改某人在知识库的权限/调整成员角色" → `member update` -- 用户说"移除知识库成员/把某人从知识库移除" → `member remove`(需 `--workspace` + `--users`) +- 用户说"移除知识库成员/把某人从知识库移除/删除知识库成员" → `member remove`(需 `--workspace` + `--users`) - 用户说"知识库有哪些成员/查看知识库成员" → `member list` +- 用户说"删除知识库/移除知识库/把知识库删了" → `space delete`(需 `--workspace`) -> **跨产品路由**:`dws wiki node` 负责知识库内节点的**列出/创建/复制/移动/删除/搜索**(空间管理层);节点的**内容读写**(读取/编辑/块级操作)切到 `dingtalk-doc`: -> - 用户说"读某个知识库里的某篇文档" → 先用 `node list` / `node search` 拿到 `nodeId`,再切到 `dingtalk-doc` 用 `dws doc read --node ` -> - 用户说"搜文件"(不指定空间) → 切到 `dingtalk-drive` 用 `dws drive search`(全局聚合搜索) +> **跨产品路由说明**:知识库节点的**内容操作**(读取/编辑/块级操作)仍由 `dws doc` 承担: +>- 用户说"读某个知识库里的某篇文档" → 先 `node list` 拿到 nodeId,再走 **`dws doc read --node `** +>- 用户说"搜文件"(不指定空间) → 走 **`dws drive search`**(全局聚合搜索) -关键区分: -- **wiki node**(空间管理层:节点列出/创建/复制/移动/删除/搜索)vs **doc**(内容层:读写/编辑/块级/评论/导出)vs **drive**(存储层:上传/下载/全局搜索/权限) -- **wiki node search**(空间内搜索,需 `--workspace`)vs **drive search**(全局聚合搜索) -- **wiki member**(容器级,授权整个知识库);「我的文档」不支持容器级成员管理 +关键区分(两层模型): +- **wiki node**(空间管理层:节点的列出/创建/复制/移动/删除/搜索)vs **doc**(内容层:读写/编辑/块级/评论/导出)vs **drive**(存储层:文件上传/下载/搜索/权限,不关心格式) +- **wiki node search**(空间内搜索,需 `--workspace`)vs **drive search**(全局搜索,聚合钉盘+文档空间) +- **wiki node create**(在空间中创建空文件实体)vs **doc create**(创建文档并写入内容) +- **wiki member**(容器级,授权整个知识库)vs **doc permission / drive permission**(节点级,授权单篇文档) + - 「我的文档」**只能用** `doc permission` / `drive permission`,不能用 `wiki member` +- **wiki space list --type orgSpace/mySpace**(列出钉盘空间)vs **wiki space list**(默认列出知识库) ## 核心工作流 @@ -285,34 +326,98 @@ Flags: # 列出我有权访问的组织知识库 dws wiki space list --format json +# 获取「我的文档」个人空间 +dws wiki space list --type myWikiSpace --format json + # 搜索知识库 dws wiki space search --query "产品" --format json # 创建知识库 dws wiki space create --name "新项目文档" --desc "项目相关文档归档" --format json -# ── 工作流: 浏览知识库内容 ── -dws wiki space list --format json # 1. 取 workspaceId -dws wiki node list --workspace --format json # 2. 列根目录节点 -dws wiki node list --workspace --folder --format json # 3. 进子目录 -# 4. 读取文档内容 → 切到 dingtalk-doc: dws doc read --node --format json +# 查看知识库详情 +dws wiki space get --workspace --format json -# ── 工作流: 在知识库中创建文档/文件夹 ── +# ── 工作流: 浏览知识库内容 ── + +# 1. 获取知识库 ID +dws wiki space list --format json + +# 2. 列出根目录节点 +dws wiki node list --workspace --format json + +# 3. 进入子目录 +dws wiki node list --workspace --folder --format json + +# 4. 读取文档内容(跨到 doc) +dws doc read --node --format json + +# ── 工作流: 在知识库中创建文档 ── + +# 1. 创建文档节点 dws wiki node create --workspace --name "新方案" --format json + +# 2. 创建文件夹 dws wiki node create --workspace --name "方案归档" --type folder --format json +# 3. 在指定文件夹下创建 +dws wiki node create --workspace --name "子文档" --folder --format json + # ── 工作流: 在知识库中搜索 ── + +# 在指定知识库内搜索 dws wiki node search --workspace --query "方案" --format json +# 按文件类型过滤 +dws wiki node search --workspace --query "周报" --extensions adoc --format json + +# ── 工作流: 列出钉盘空间 ── + +# 列出钉盘企业空间 +dws wiki space list --type orgSpace --format json + +# 获取钉盘「我的文件」 +dws wiki space list --type mySpace --format json + +# ── 工作流: 复制/移动节点 ── + +# 复制节点到另一个文件夹 +dws wiki node copy --workspace --node --folder --format json + +# 移动节点到另一个文件夹 +dws wiki node move --workspace --node --folder --format json + +# ── 工作流: 删除知识库节点 ── + +# 删除节点(会要求确认) +dws wiki node delete --workspace --node + # ── 工作流: 给知识库加成员 ── -dws wiki space list --format json # 1. 确认 workspaceId(不要 --type myWikiSpace) -dws wiki member add --workspace --users --role EDITOR --format json # 2. 加成员 -dws wiki member list --workspace --format json # 3. 查看当前成员 + +# 1. 先确认知识库 ID(避免授权到「我的文档」) +dws wiki space list --format json # 注意:不要 --type myWikiSpace + +# 2. 添加成员 +dws wiki member add --workspace --users --role EDITOR --format json + +# 3. 查看当前成员 +dws wiki member list --workspace --format json # ── 工作流: 移除知识库成员 ── -dws wiki member list --workspace --format json # 1. 查看成员(只返回 name/role/type,无 userId) -dws contact user search --query "<姓名>" --format json # 2. 按姓名反查 userId -dws wiki member remove --workspace --users --format json # 3. 移除 + +# 1. 查看当前成员 +dws wiki member list --workspace --format json + +# 2. 移除成员 +dws wiki member remove --workspace --users --format json + +# ── 工作流: 删除知识库 ── + +# 1. 确认知识库信息 +dws wiki space get --workspace --format json + +# 2. 删除知识库 +dws wiki space delete --workspace --format json ``` ## 上下文传递表 @@ -326,9 +431,9 @@ dws wiki member remove --workspace --users --format json # 3. 移 | `node list` | `nodeId` | node copy/move/delete 的 --node / `dws doc read` 的 --node | | `node search` | `nodeId` | node copy/move/delete 的 --node / `dws doc read` 的 --node | | `node create` | `nodeId` | node copy/move/delete 的 --node / `dws doc read` 的 --node | -| `member list` | `name` / `role` / `type`(**不含 userId**)| 仅用于查看成员名单;**无法**从这里取 userId 去串联 member update/remove,需另行按姓名反查 userId(如 `dws contact user search --query "<姓名>"`)| +| `member list` | `userId` | member update 的 --users / member remove 的 --users | ## 相关产品 -- [doc](../../dingtalk-doc/references/doc.md) — 内容层:文档读写/编辑/块级操作/评论/导出 -- [drive](../../dingtalk-drive/references/drive.md) — 存储层:文件上传/下载/全局搜索/权限 +- [doc](../../dingtalk-doc/references/doc.md) — 内容层:文档读写/编辑/块级操作/评论/导出(仅对自研文档有意义) +- [drive](../../dingtalk-drive/references/drive.md) — 存储层:文件列出/搜索/上传/下载/复制/移动/重命名/删除/权限(不关心文件格式) diff --git a/skills/multi/dws-shared/SKILL.md b/skills/multi/dws-shared/SKILL.md index 0a26b752..48a92862 100644 --- a/skills/multi/dws-shared/SKILL.md +++ b/skills/multi/dws-shared/SKILL.md @@ -59,3 +59,14 @@ dws 可同时登录多个钉钉组织,一个 profile = 一个已登录组织 - `unknown command` / `unknown flag`:先跑 `dws --help` 查证再修正一次,别把自然语言当命令 / flag - 服务端 token 过期:提示用户 `dws auth login` 重新登录;本地密钥不匹配按上面的 macOS 迁移流程处理,不要混为 token 过期 - 业务错误码 / 接口语义:用 `dws devdoc article search --query "<关键词>" --format json` 查官方文档,不编造原因 + +## 详细参考 + +- [全局参数与认证](references/global-reference.md) +- [URL 路由与 alidocs 识别](references/url-patterns.md) +- [跨产品意图消歧](references/intent-guide.md) +- [能力边界](references/capability-limits.md) +- [错误码与排查](references/error-codes.md) +- [字段规则](references/field-rules.md) +- [通用规范](references/best_practices/_common/conventions.md) +- [Recipe 约定](references/best_practices/_common/recipe-conventions.md) diff --git a/skills/multi/dws-shared/references/best_practices/_common/conventions.md b/skills/multi/dws-shared/references/best_practices/_common/conventions.md new file mode 100644 index 00000000..b02448de --- /dev/null +++ b/skills/multi/dws-shared/references/best_practices/_common/conventions.md @@ -0,0 +1,45 @@ +# 通用规范 + +> full recipe 执行时的共享规范。安全门控、危险操作确认、`--format json` 等已在根 [SKILL.md](../../../SKILL.md) 中定义,此处不重复。 +> Recipe 元规范(YAML frontmatter、命名、三层架构)见 [recipe-conventions.md](recipe-conventions.md)。 + +## 批量查询规范 + +| # | 规范 | +|---|------| +| 1 | **并行查详情**:拿到多个 ID 后,用 `&` 合并到同一条 Shell 命令并行执行 + `wait`,**严禁逐条串行** | +| 2 | **翻页**:分页接口须拉全直至无更多 | +| 3 | **优先批量 API**:有批量接口则用批量;无则按 #1 并行 | +| 4 | **群消息**:必须先 `chat search --query` 得 `openConversationId`,再 `chat message list --group`;多群同条命令并行 | +| 5 | **列表少轮次**:带条件搜索/列表 → 一次采全详情;**禁止**无新参数时重复同一 `list` / `search` | + +## 多源并行采集(公共模式) + +> recipe 引用方式:`按「多源并行采集」执行(关键词=,时间=至)`。 + +- 同条 Shell:`&` 并行 + `wait`;分页须采全。 +- 只保留与主题相关的数据,无关丢弃。 +- 有批量详情接口优先;否则并行拉详情(见上表 #1)。 +- 具体采哪些产品列表由对应 **行动指南 recipe** 与 [SKILL 产品参考](../../../SKILL.md) 决定。 + +## 字段术语与 ID 传递 + +> list 返回 JSON 后,必须提取下表字段传给后续命令。**禁止用其他字段替代。** + +| 字段 | 来源 | 传递给 | +|------|------|--------| +| `taskUuid` | `minutes list` | `minutes get summary/info/batch --id(s)` | +| `userId` | `aisearch person` / `contact user search` / `contact dept list-members` | `contact user get --ids`、`todo --executors`、`calendar --users` | +| `deptId` | `contact dept search` | `contact dept list-members --depts`;多子部门时对每个子部门分别 `dept search` 取 id | +| `nodeId` | `drive search` / `wiki node search` | `doc read/update --node`、`drive copy/move/rename/delete --node` | +| `nodeId` | `wiki node list` 中的 folder 类型节点 / `wiki node create --type folder` | `wiki node list --folder`、`wiki node create --folder`、`drive upload --folder`、`drive copy/move --folder` | +| `eventId` | `calendar event list` | `calendar event get/update --id` | +| `processInstanceId` | `oa approval list-*` | `oa approval detail/approve --instance-id` | +| `openConversationId` | `chat search` | `chat message list/send --group` | +| `todoTaskId` | `todo task list` | `todo task update/done --task-id` | +| `reportId` | `report inbox list` / `report outbox list` | `report entry get/stats --report-id` | +| `baseId` / `tableId` | `aitable base search` | `aitable record query --base-id --table-id` | +| `dentryUuid` | `drive list` / `drive mkdir` | `drive info/download --node`、`drive list/mkdir/upload --folder` | +| `dentryId` | `drive info` 的数字字段 | 仅用于 `chat message send --dentry-id` | + +**ID 边界硬约束**:`dentryId` 通常是纯数字,只表示聊天文件消息需要的钉盘条目数字 ID;它不是父目录 ID。遇到 `drive --node/--folder`、`doc --node/--folder`、`wiki node --folder` 时,只能使用 `dentryUuid` / `nodeId` / 文档 URL。若当前上下文只有数字型 `dentryId`,必须先重新 `drive list` / `drive search` / `wiki node list` 获取正确 ID,不能把该数字直接代入后续命令。 diff --git a/skills/multi/dws-shared/references/best_practices/_common/recipe-conventions.md b/skills/multi/dws-shared/references/best_practices/_common/recipe-conventions.md new file mode 100644 index 00000000..3b0db88b --- /dev/null +++ b/skills/multi/dws-shared/references/best_practices/_common/recipe-conventions.md @@ -0,0 +1,67 @@ +# Recipe 规范 + +> 每个 recipe 是一个 **SKILL.md**,格式与产品 skill 同等身份。 + +## 架构三层 + +``` +Layer 1: _common/conventions.md → 全局共享(认证、安全规则、字段术语) + 相当于 gws-shared/SKILL.md + +Layer 2: products/*.md → 产品能力(命令用法、flags、示例) + 相当于 gws-gmail/SKILL.md, gws-docs/SKILL.md + +Layer 3: recipe-xxx/SKILL.md → 组合配方(固定步骤 + 声明依赖) + 相当于 recipe-send-team-announcement/SKILL.md +``` + +## Recipe SKILL.md 格式 + +```yaml +--- +name: recipe-- +description: "一句话描述" +metadata: + category: "recipe" + domain: "<领域编号>" + requires: + bins: + - dws + skills: + - + - +--- +``` + +**正文**只包含: +1. 标题 + 一句话描述 +2. `> **PREREQUISITE:** ...` 提示加载哪些 skill +3. `## Steps` — 编号步骤,每步一条 `dws` 命令 + +**不包含**(这些留给 conventions.md 和 best_practice.md): +- 安全门控/决策分支/踩坑提醒 +- 失败处理策略 +- 数据佐证 + +## 命名规则 + +``` +recipe--[-] +``` + +| 动词 | 含义 | 示例 | +|------|------|------| +| send | 发送 | recipe-send-message | +| create | 创建 | recipe-create-todo | +| query | 查询 | recipe-query-doc | +| write | 写入 | recipe-write-doc | +| resolve | 解析 | recipe-resolve-contact | +| generate | 生成 | recipe-generate-daily-report | +| share | 分享 | recipe-share-doc-and-notify | + +## 路由规则 + +Agent 识别意图后: +1. 匹配到 recipe → 加载 recipe 声明的 skills → 按 Steps 执行 +2. 无匹配 recipe → 走领域 best_practice.md 策略指南 +3. 只需单个命令 → 直接查 products/*.md diff --git a/skills/multi/dws-shared/references/capability-limits.md b/skills/multi/dws-shared/references/capability-limits.md new file mode 100644 index 00000000..d1830bf8 --- /dev/null +++ b/skills/multi/dws-shared/references/capability-limits.md @@ -0,0 +1,19 @@ +# 已知能力限制 + +遇到以下操作时,**不要重试或变通**,直接告知用户当前不支持并建议在钉钉客户端操作。 + +## aitable + +| 不支持的操作 | 说明 | +|------------|------| +| 创建公式/查找引用等高级字段类型 | 部分高级字段类型暂不支持 API 创建 | + +## minutes + +| 不支持的操作 | 说明 | +|------------|------| +| 跨听记的全局热词修正 | `replace-text` 仅修正**当前这一篇听记**的文字,不会影响后续新听记的语音识别结果。要让某个词长期不再被识别错,必须额外引导用户使用 `hot-word add` 添加个人热词 | + +--- + +> 此文件随产品能力迭代更新。新增限制时按产品分类追加即可。 diff --git a/skills/multi/dws-shared/references/error-codes.md b/skills/multi/dws-shared/references/error-codes.md new file mode 100644 index 00000000..f32311e5 --- /dev/null +++ b/skills/multi/dws-shared/references/error-codes.md @@ -0,0 +1,111 @@ +# 错误码说明 +全产品错误参考 + 调试流程。Agent 遇到错误时查阅此文档。 + +## 错误返回格式 + +```json +{"success": false, "code": "InvalidParameter", "message": "baseId is required"} +{"success": false, "code": "AUTH_TOKEN_EXPIRED", "message": "Token验证失败"} +{"success": false, "code": "PermissionDenied", "message": "无权限访问该资源"} +``` + +## 错误分类与 Agent 行为 + +### 可自行修复 +- 参数缺失 / 格式错误 / ID 无效 → 检查参数后修正重试 + +### 需用户介入 +- 权限不足 / 资源不存在 / 配额超限 → 报告完整错误信息给用户,不要自行尝试替代方案 + +## 通用错误 +- 请求超时 — 网络慢或服务端响应慢 → `--timeout 60` 重试 +- 网络连接失败 — 无法连接 MCP Server → 用最简命令验证: `dws contact user get-self --format json` + +--- + +## aitable 高频错误 + +> 参数体系: `baseId / tableId / fieldId / recordId`。CLI flag 用 kebab-case(`--base-id`),JSON 内用 camelCase(`baseId`)。 + +- 参数缺失 / 无效请求 — 还在用旧参数 `dentryUuid` / `--doc` / `--sheet` → 改用 `--base-id` / `--table-id` / `--field-id` / `--record-ids` +- 参数传了但服务端没收到 — flag 用了 camelCase(如 `--baseId`)→ flag 用 kebab-case: `--base-id ` +- `record query --filters` 无结果 — 单选/多选过滤用了 option name 而非 id → 先 `field get` 读取 options,用 option id 过滤 +- record create/update 失败 — `cells` key 用了字段名(应为 fieldId);特殊字段格式错误 → 先 `table get` 拿字段目录;url 传 `{"text":"..","link":".."}` +- 更新选项后历史数据异常 — 更新 options 没传完整列表 / 没保留原 id → 先 `field get` 取完整配置,保留已有 option 的 id +- `cannot delete the last table` — 该表是 Base 最后一张表 → 先新建表再删旧表,或用 `base delete` +- `formula` 类型 `not supported yet` — 部分字段类型暂不支持 API 创建 → 复杂字段拆开单独创建,先建基础结构 + +**排查链路**: `base list` → `base get`(→tableId) → `table get`(→fieldId) → `record query`(→recordId)。别跳步,别猜 ID。 + +**批量上限**: record 100 条 / field 15 个 / table·field 详情 10 个。 + +--- + +## doc 高频错误 + +- 文档不存在 / nodeId 无效 — nodeId 或 URL 不正确、文档已删除 → `doc search` 或 `doc list` 重新获取正确 nodeId +- 无下载权限 — 文档分享设置不允许 → 报告用户,建议联系文档所有者 +- `update --mode overwrite` 意外清空 — overwrite 会清空原内容后重写 → 默认用 `--mode append`,overwrite 前必须跟用户确认 +- 块编辑 blockId 无效 — blockId 过期或文档结构已变 → 先 `block list` 刷新获取最新 blockId +- `CONTENT_TRUNCATED` — 分片写入持续超时,分片大小已减半至最小阈值(5000 字符)仍无法成功 → 后端服务可能过载或网络异常。已写入部分内容可通过 `doc read --node ` 查看,待后端恢复后从断点处用 `doc update --mode append` 继续追加 + +--- + +## calendar 高频错误 + +- **误用顶层 `dws calendar` / 臆造 `calendar list`** — 只输入 `dws calendar` 或尝试不存在的 `calendar list` 会打印大段 Usage,易导致上下文暴涨与响应变慢 → **改用** `dws calendar event list --start "" --end "" --format json`,或 `python scripts/calendar_today_agenda.py ...`;详见 [calendar.md](../../dingtalk-calendar/references/calendar.md)「CLI 命令树与黄金路径」「反模式(禁止)」 +- 时间格式错误 — 未使用 ISO-8601 格式 → 标准格式: `2026-03-10T14:00:00+08:00` +- 会议室搜索报错 / 返空 — 企业会议室超 100 条未分组查询 → 先 `room list-groups` → 按 `--group-id` 逐组搜索 +- 参与者 / 会议室添加失败 — eventId 不正确 → 先 `event list` 或 `event create` 获取正确 eventId +- `roomId invalid` / 订房失败 — 把会议室**展示名**或用户口语当成了 `roomId` → **只能**使用 `room search` 返回 JSON 中的 `rooms[].roomId` 填入 `room add --rooms`;不得以中文名、楼层编号文案充当 ID +- `unknown flag: --query`(会议室)— `room search` **不支持**按名称搜索 → 先 `room list-groups` 再按分组 `room search`,在返回列表中匹配名称后取 `roomId`(见 calendar.md) + +--- + +## chat 高频错误 + +- 参数互斥报错 — `--group` 与 `--user` / `--users` 同时传入 → 群聊用 `--group`,单聊用 `--user`/`--users`,二者互斥 +- 群不存在 — openconversation_id 不正确 → `chat search --query "群名"` 获取正确 ID +- 机器人无法添加到群 — 当前用户非群管理员 → 报告给用户,需群管理员操作 +- `send` 消息 text 参数缺失 — text 是位置参数,不是 flag → text 直接跟在 flags 后: `send --group "内容"` + +--- + +## oa 高频错误 + +- approve/reject 缺少 taskId — 未先获取审批任务 → 先 `approval tasks --instance-id ` 获取 taskId +- list-initiated 缺少 processCode — 未查询审批表单 → 先 `approval list-forms` 或 `detail` 获取 processCode +- 撤销审批失败 — 非本人发起的审批 → `revoke` 只能撤销自己发起的审批 + +--- + +## report 高频错误 + +> 参数体系:`templateId / reportId`。CLI flag 用 kebab-case(`--template-id` / `--contents-file`)。`contents` 数组每项含 `key/sort/content/contentType/type` 五个字段,`key/sort/type` 必须严格对齐 `template get` 返回的 `field_name/field_sort/field_type`。 + +- `INPUT_INVALID_JSON` — `--contents` 或 `--contents-file` 内容非合法 JSON → 检查数组结构 `[{key, sort, content, contentType, type}]` +- `INPUT_FILE_NOT_FOUND` — `--contents-file` 路径错 / sandbox OS 风格不匹配 → 先确认 sandbox OS(Windows: `C:\...` / macOS|Linux: `/...`)后改写 +- `INPUT_MISSING_PARAM` — `--template-id` 或 contents 必填缺失 → 先跑 `dws report template list --format json` 取 templateId +- `MCP_TOOL_ERROR` + `server_error_code: PARAM_ERROR` — 服务端业务校验失败(templateId 错 / `key` 不在模版定义 / 类型不匹配 / 必填空 等多种形态都返回这一个码,且服务端不区分子原因)→ 不要靠错误信息排查具体字段,按提交链路重新走 `template list → template get → entry submit`;连续 ≥ 2 次失败必须停止重试,降级 final_reply +- `MCP_TOOL_ERROR` + 其他 server_error_code — 查看 `technical_detail`;如出现不可读错误(仅含 `root.success当前值`),降级 final_reply 引导用户手动操作 + +**排查链路**:`template list` → `template get`(取 `result.report_template_fields[]`,每项含 `field_name/field_sort/field_type`)→ 拼 `--contents`:`field_name → key`、`field_sort → sort`、`field_type → type`,再填 `content` 与 `contentType` → `entry submit`。**别跳步、别猜字段名、别自己改写 key 名。** + +--- + +## contact / drive / mail 高频错误 + +- contact: `dept list-children` 报错 — `--id` 传了非整数值 → deptId 必须为整数,从 `dept search` 获取 +- drive: 文件不存在 — dentryUuid 不正确 → `drive list` 逐级浏览获取正确 ID +- drive: 上传失败 / uploadId 无效 — 跳过了 `upload-info` 步骤 → 必须先 `upload-info` 获取上传凭证,再 `commit` +- drive: 文件名报错 — `--file-name` 缺少扩展名 → 必须包含扩展名: `report.pdf` +- mail: 发件地址不正确 — 未先查询可用邮箱 → 先 `mailbox list` 获取邮箱地址 +- mail: KQL 搜索无结果 — 查询语法错误 → 字段值含空格用双引号: `subject:\"周报\"` + +--- + +## 通用排查三步法 + +1. **确认 ID** — 从最顶层资源逐级获取,不猜 ID、不跳步 +2. **确认参数** — flag 用 kebab-case,JSON 用 camelCase;特殊字段查产品参考文档确认格式 +3. **确认限制** — 检查批量上限和已知约束(各产品注意事项见对应产品参考文档) diff --git a/skills/multi/dws-shared/references/field-rules.md b/skills/multi/dws-shared/references/field-rules.md new file mode 100644 index 00000000..08d983a0 --- /dev/null +++ b/skills/multi/dws-shared/references/field-rules.md @@ -0,0 +1,104 @@ +# 易混淆操作与字段规则 + +## 易混淆操作 (高风险场景必读) + +| 用户说的 | 正确命令 | 不是这个 | +|---------|----------|---------| +| "创建一个新表格 (Base)" | `base create` | 不是 `table create` | +| "在表格里加一个数据表" | `table create` | 不是 `base create` | +| "看看表格里有哪些表" | `base get` | 不是 `field get` | +| "看看表里有哪些列" | `field get` / `table get` | 不是 `base get` | +| "搜索表格" (找 Base) | `base search` | 不是 `record query` | +| "搜索记录" (查表内数据) | `record query` | 不是 `base search` | +| "删掉这个数据表" | `table delete` | 不是 `record delete` | +| "删掉这条数据" | `record delete` | 不是 `table delete` | +| "删掉这个列" | `field delete` | 不是 `record delete` | +| "改字段类型" | 先 `field delete` 再 `field create` | `field update` **不能改类型** | +| "移动字段/调整字段顺序" | `view update --config '{"visibleFieldIds":[...]}'`(视图层重排,首列主字段必须保留在第一位) | 没有 `field reorder`/`field move` 命令;不能改字段在元数据里的"原始定义顺序" | + +## field 子命令总览 + +> ⚠️ field 有且仅有以下 **4 个** 子命令,没有 `list`、`reorder`、`move`: + +| 子命令 | 用途 | +|-------|------| +| `field get` | 获取字段详情(含完整 config/options)。**不是 `field list`** | +| `field create` | **创建字段(支持通过 config.options 设置选项)** | +| `field update` | 更新字段名称或配置(**不能改类型**,**不能改顺序**) | +| `field delete` | 删除字段(不可逆) | + +> **想"调整字段顺序"?请使用 `view update`**(视图层操作,不属于 `field` 子命令): +> - 通过 `--config '{"visibleFieldIds":["fld1","fld2",...]}'` 传入 fieldId 数组,数组顺序即视图中的字段显示顺序 +> - 仅影响**该视图**的列排列,同一 table 的其他视图与字段元数据原始顺序不变 +> - **首列字段(主字段)必须保留在数组第一位**,不能移动到非首位 +> - **漏传的字段不会被隐藏**,而是会被 API 自动追加到列表末尾 +> - **读写命名不一致**:写入键名 `visibleFieldIds`,但读出(`view get`)时该字段在 view 里叫 `columns`——校验顺序时请看 `views[].columns` + +## 字段创建时设置 config(重要) + +创建 singleSelect/multipleSelect 字段时,**必须设置选项 (options)**: + +```bash +# 创建带选项的单选字段 (推荐新语法: --name/--type/--config) +dws aitable field create --base-id --table-id \ + --name "优先级" --type "singleSelect" \ + --config '{"options":[{"name":"高"},{"name":"中"},{"name":"低"}]}' \ + --format json + +# 建表时也可以直接通过 --fields 批量带选项字段 +dws aitable table create --base-id --name "任务表" \ + --fields '[{"fieldName":"任务","type":"text"},{"fieldName":"状态","type":"singleSelect","config":{"options":[{"name":"待办"},{"name":"进行中"},{"name":"已完成"}]}}]' \ + --format json +``` + +> ⚠️ **不要混淆**: +> - **字段创建**(`field create` 的 `--config` 或 `table create` 的 `--fields`):创建时就指定 `options` +> - **记录写入**(`record create` / `record update` 的 `--records`):只能写入已存在的选项名称 + +## 主字段约束(table create 必读) + +> ⚠️ `table create` 的 `--fields` 中,**第一个字段自动成为主字段**。 +> 主字段只能是 **text** 类型,不能是 attachment、checkbox、formula 等。 + +**实际影响**:当用户要求创建的字段不适合做主字段时(如附件、复选框),必须: +1. 先放一个 text 字段作为第一个字段(主字段) +2. 再放用户要求的字段 +3. **告知用户**为何多了一个字段 + +```bash +# 例: 用户要求只创建附件字段 → 附件不能做主字段,必须先加 text 主字段 +dws aitable table create --base-id --name "产品图片" \ + --fields '[{"fieldName":"名称","type":"text"},{"fieldName":"产品图片","type":"attachment"}]' \ + --format json +``` + +## 只读字段 (不可写入) + +以下类型的字段不可写入, 执行 `field get` / `table get` 后识别并跳过: +- 创建时间 / 修改时间 (系统自动) +- 创建人 / 修改人 (系统自动) +- 自动编号 +- 公式字段 +- 引用字段 + +## 记录写入格式(record create / record update) + +> 各字段类型的完整写入/读取格式规范请参考:[aitable-cell-value.md](../../dingtalk-aitable/references/aitable/aitable-cell-value.md) +> +> 该文件是 cellValue 格式的 **source of truth**,包含所有字段类型的详细示例和注意事项。 + +## ⚠️ 附件上传完整流程(必读!) + +> **不要**使用钉盘 (drive) 上传来替代此流程!钉盘 fileId **无法**写入 attachment 字段。 + +附件字段写入使用 `upload_attachment.py` 脚本,**2 步**完成: + +```bash +# 步骤 1: 一键上传文件(脚本内部自动完成 prepare + PUT to OSS) +python3 scripts/upload_attachment.py /path/to/photo.png +# 输出: { "fileToken": "ft_xxx", "fileName": "photo.png", "size": 1024 } + +# 步骤 2: 在 record create/update 中使用 fileToken +dws aitable record create --base-id --table-id \ + --records '[{"cells":{"fldAttachId":[{"fileToken":"ft_xxx"}]}}]' --format json +``` diff --git a/skills/multi/dws-shared/references/global-reference.md b/skills/multi/dws-shared/references/global-reference.md new file mode 100644 index 00000000..4ad3b94e --- /dev/null +++ b/skills/multi/dws-shared/references/global-reference.md @@ -0,0 +1,89 @@ +# 全局参考 + +## 认证 + +认证由系统自动管理,无需手动登录。 + +```bash +# 查看当前认证状态 +dws auth status + +# 退出登录 +dws auth logout +``` + +系统自动管理 token 刷新,日常使用无需干预。 + +| Token | 有效期 | 说明 | +|-------|--------|------| +| Access Token | 2 小时 | 调用 API 的凭证,过期自动刷新 | +| Refresh Token | 30 天 | 换新 Access Token,使用后轮转 | + +30 天内使用一次即自动续期。 + +### 认证失败处理 +- 命令返回 `AUTH_TOKEN_EXPIRED` / `USER_TOKEN_ILLEGAL` / "Token验证失败" → 请重新执行上一条命令(最多重试两次) + +### Headless 环境 (CI/CD) + +```bash +# 桌面: 导出凭证 +dws auth export > credentials.json + +# 服务器: 设置环境变量 +export DINGTALK_CREDENTIALS_FILE=~/.config/dingtalk/credentials.json +``` +refresh_token 单设备独占,远程刷新后源设备凭证失效。 + + +## 全局标志 + +| 标志 | 短名 | 说明 | 默认 | +|------|:---:|------|------| +| `--format` | `-f` | 输出格式: json / table / raw | table | +| `--verbose` | `-v` | 详细日志 | false | +| `--debug` | | 调试日志 | false | +| `--yes` | `-y` | 跳过确认提示 | false | +| `--dry-run` | | 预览操作不执行 | false | +| `--timeout` | | HTTP 超时 (秒) | 30 | +| `--token` | | API Token (覆盖配置) | 无 | +| `--mock` | | Mock 数据 (开发用) | false | + +## 输出格式 + +### --format json (机器可读, Agent 必须使用) + +```json +{"success": true, "body": {...}} +``` + +### --format table (人类可读, 默认) + +``` +已创建 AI 表格 "项目管理" (UUID: abc123) + +下一步: + dws aitable base get --base-id abc123 +``` + +## 环境变量 + +| 变量 | 说明 | +|------|------| +| `DINGTALK_TOKEN` | API Token (最高优先级) | +| `DINGTALK_CREDENTIALS_FILE` | 导出的凭证文件路径 | +| `DINGTALK_MCP_URL` | MCP Server URL (覆盖内置) | + +凭证优先级: `--token` > `DINGTALK_TOKEN` > `DINGTALK_CREDENTIALS_FILE` > OAuth token.json + +## 命令自省 + +不确定某个命令的参数时,用 `--help` 查看: + +```bash +dws calendar event create --help +dws chat message send --help +dws aitable record query --help +``` + +`--help` 会显示所有 flag、类型、默认值和必填标记,比凭记忆猜参数更可靠。 diff --git a/skills/multi/dws-shared/references/intent-guide.md b/skills/multi/dws-shared/references/intent-guide.md new file mode 100644 index 00000000..3f7e76c7 --- /dev/null +++ b/skills/multi/dws-shared/references/intent-guide.md @@ -0,0 +1,530 @@ +# 意图路由指南 + +当用户请求难以判断归属哪个产品时,参考本指南。 + +## 易混淆场景快速对照表 + +| 用户说... | 真实意图 | 应该用 | 不要用 | 理由 | +|-----------|----------|--------|--------|------| +| "搜一下 OAuth2 接入文档" | 搜索开发文档 | `devdoc` | `doc search` | 搜索开放平台技术文档,不是钉钉内部内容 | +| "帮我建一个项目跟踪表" | 创建数据表格 | `aitable` | `doc` / `sheet` | 涉及结构化数据/行列操作,不是富文本文档或电子表格 | +| "帮我写个项目周报" | 创建钉钉文档 | `doc` | `aitable` | 富文本内容创作,不是数据表 | +| "参照这个生成同样的 / 按模板生成 / 复刻 X / 同样的模板 X 月份的" + 已有 alidocs URL | 模板保形生成同形态变体 | `drive copy + drive rename + doc block update` → 见 [模板保形生成](../../dingtalk-doc/references/04-document.md#模板保形生成) | `doc read + doc create`(重写链) | adoc → markdown 是有损投影,read+create 会丢行高/单元格背景色/字号;copy 在 adoc 层保形复制后只在副本上局部修改 | +| "创建一个电子表格" | 创建表格文档 | `sheet` | `aitable` | Excel 式表格/单元格操作,不是多维表记录 | +| "帮我读一下表格 A1:D10 的数据" | 读取单元格数据 | `sheet` | `aitable` | 按单元格区域读写,不是按记录查询 | +| "这个 alidocs 表格链接帮我看下"(粘贴原始 URL) | 先 probe 节点类型 | `dws doc info --node` → 按 `extension` 路由 | 直接调 `sheet` | `alidocs/i/nodes/{id}` 可能是文档/axls/able/xlsx 等,禁止凭 URL 猜类型 | +| "读一下这个 xlsx 的数据" / xlsx 节点链接 | 下载本地表格文件 | `dws drive download --node` | `sheet range read` | xlsx / xls / xlsm / csv 是上传的本地文件(`contentType=DOCUMENT`),sheet 命令只支持在线表格,必须下载后本地解析 | +| "把这个在线表格导出为 xlsx 文件" | 在线表格格式转换 | `dws sheet export` | `dws drive download` | `export` 是 axls → xlsx 的导出转换;`download` 只能下载已有的 xlsx 节点 | +| "帮我记一下明天要做的事" | 创建个人待办 | `todo` | `doc` | 个人待办提醒,非文档内容 | +| "给自己留一个明天下午的时间块/建个个人日程" | 创建个人日程 | `calendar event create` | `todo` | 个人 schedule 仍属于日历事件,不是待办 | +| "帮我把这个文件传到网盘" | 钉盘上传 | `drive upload` | — | 文件上传是存储层操作,归 drive | +| "上传文件到钉盘/我的文件" | 钉盘上传 | `drive upload` | — | 提到"钉盘/网盘/我的文件"→ drive | +| "上传文件"(未指定目标) | 默认钉盘 | `drive upload` | — | 未明确目标时默认上传到钉盘 | +| "帮我看看知识库里的文件" | 知识库节点列表 | `wiki node list --workspace` | `drive list` | 明确"知识库"上下文 → wiki node list | +| "列出钉盘团队空间" | 列出钉盘空间 | `wiki space list --type orgSpace` | `drive list-spaces` | 空间管理归 wiki,drive list-spaces 已 deprecated | +| "在知识库里搜方案" | 空间内搜索 | `wiki node search --workspace` | `drive search` | 指定了空间上下文 → wiki node search | +| "搜一下有没有叫XX的文件" | 全局搜索 | `drive search` | `wiki node search` | 未指定空间 → drive search 全局聚合搜索 | +| "在知识库里创建一个文档" | 创建空文件实体 | `wiki node create --type adoc` | `doc create` | 空间内创建节点归 wiki;doc create 是向已有文档写入内容,不是创建文件节点 | +| "帮我预约一个视频会议" / "开个会" / "发起会议" | 视频会议 | 钉钉客户端(当前 CLI 不支持) | `calendar` | 不要把实时视频会议诉求改写成日历日程 | +| "帮我建一个明天下午的日程" | 日历日程 | `calendar` | — | 日历日程管理(可含参与者/会议室);视频会议当前 CLI 不支持 | +| "明早 9 点提醒我提交周报" | 创建个人待办,但需先声明 reminder 边界 | `todo` | `calendar` | todo 当前只支持 dueTime 截止时间,不支持独立精确 reminder | +| "通知群里的人都来开会" | 个人身份群发 | `chat message send` | `chat message send-by-bot` | 以个人身份向群发消息 | +| "让机器人每天推送日报" | 机器人定时推送 | `chat message send-by-bot` | `chat message send` | 需要机器人身份定期发送 | +| "CPU 超过 90% 自动告警" | Webhook 告警 | `chat message send-by-webhook` | `chat message send-by-bot` | 系统告警场景,需自定义 Webhook | +| "帮我看看收到的日报" | 收到的日志 | `report` | `doc` | 钉钉日志系统(日报/周报),不是文档 | +| "帮我创建一个待办提醒" | 个人待办 | `todo` | `report` | 个人任务提醒,不是日志汇报 | +| "拉取一下上周项目群的聊天记录" | 拉取会话消息 | `chat message list` | — | 拉取指定群聊的消息列表 | +| "看看张三发给我的消息" | 按发送者查询消息 | `chat message list-by-sender` | `chat message list --user` | 用户未明确说"单聊"时优先用 list-by-sender(跨单聊/群聊) | +| "拉取和张三的单聊记录" | 拉取单聊消息 | `chat message list --user` | `chat message list-by-sender` | 用户明确说"单聊"时用 list --user | +| "谁@了我/查看提及我的消息" | 查询@我的消息 | `chat message list-mentions` | `chat message list-all` | 都是跨会话时间范围查询,但 list-mentions 只返回@我的消息 | +| "查看我今天的所有消息" | 全量会话消息 | `chat message list-all` | `chat message list` | 用户未指定具体会话时用 list-all(跨所有会话),指定了具体群或人时用 list | +| "搜一下消息里的changefree链接" | 消息搜索 | `chat message search-advanced`(首选) | `chat search` | 推荐首选 search-advanced,它是 search 的严格超集(keyword 可选、支持多群、可叠加发送者/at 维度) | +| "按发送者搜索/指定多个群搜索/多维度搜消息" | 多维度搜索消息 | `chat message search-advanced`(首选) | `chat message search` | 推荐首选,支持关键词、发送者、@我、多个会话等维度组合 | +| "消息发没发成功/查询消息发送状态" | 查询消息发送状态 | `chat message query-send-status` | — | 需要 send 返回的 openTaskId | +| "撤回我发的消息/撤回消息/群主撤回消息/管理员撤回他人消息" | 撤回消息 | `chat message recall` | `chat message recall-by-bot` | recall 撤回个人消息或群主/管理员撤回他人消息,recall-by-bot 撤回机器人消息 | +| "未读消息会话/未读会话列表/我的未读会话" | 未读会话列表 | `chat message list-unread-conversations` | `chat message read-status` | list-unread-conversations 查哪些会话有未读;read-status 查具体消息的已读状态 | +| "谁看了这条消息/消息已读未读/查读状态" | 查询消息已读状态 | `chat message read-status` | `chat message list-unread-conversations` | read-status 查具体消息的已读人员;list-unread-conversations 查未读会话列表 | +| "查看消息的表情回复/拉取消息回复/消息的文字回复" | 批量拉取消息表情回复和文字回复 | `chat message list-emotion-replies` | — | 根据消息 ID 批量查询表情回复和文字回复 | +| "我和风雷的共同群/我们都在哪些群" | 搜索共同群 | `chat search-common` | `chat search` | search-common 按人员搜共同群,chat search 按群名搜索 | +| "查看会话分组/自定义分组" | 获取会话分组 | `chat category list` | — | 获取用户自定义的会话分组列表 | +| "某个分组下有哪些会话" | 分组下会话列表 | `chat category list-conversations` | — | 需先通过 category list 获取分组 ID | +| "创建会话分组/新建分组" | 创建自定义分组 | `chat category create` | — | 需指定 --title 分组名称 | +| "删除会话分组/移除分组" | 删除自定义分组 | `chat category delete` | — | 需指定 --category-id | +| "重命名分组/修改分组名称" | 更新分组名称 | `chat category rename` | — | 需指定 --category-id 和 --title | +| "把会话加到分组/将群移入分组" | 会话加入分组 | `chat category add-conv` | `chat category remove-conv` | add-conv 加入;remove-conv 移出 | +| "把会话从分组移出/从分组中删除" | 会话移出分组 | `chat category remove-conv` | `chat category add-conv` | remove-conv 移出;add-conv 加入 | +| "创建智能分组/新建智能会话分组/按关键词分组" | 创建智能会话分组 | `chat category create-smart` | `chat category create` | create-smart 按关键词/成员智能匹配;create 手动创建普通分组 | +| "分享群链接/把群分享给某人/群链接发到某群" | 分享群聊链接到会话 | `chat group share-invite` | `chat group invite-url` | share-invite 直接分享到目标会话/用户;invite-url 只获取链接不发送 | +| "根据群号查群信息/群号转openConversationId" | 群号查群聊信息 | `chat group get-by-group-id` | `chat search` | 已知数字群号时直接查;用户发消息只给了群号时,先用此工具将群号转为 openConversationId | +| "我创建的群/我管理的群/我是群主的群/我当管理员的群" | 拉取我创建/管理的群 | `chat group list-my-groups` | `chat search` | list-my-groups 按角色(群主/管理员)过滤当前用户的群;chat search 按关键词搜全部群 | +| "引用消息回复/回复那条消息" | 引用回复消息 | `chat message reply` | `chat message send` | reply 引用指定消息回复;send 普通发消息不引用 | +| "转发消息/把消息转到另一个群" | 转发单条消息 | `chat message forward` | `chat message send` | forward 转发已有消息到目标会话;send 是发新消息 | +| "置顶会话/取消置顶" | 设置/取消会话置顶 | `chat set-top` | `chat list-top-conversations` | set-top 设置或取消置顶;list-top-conversations 查看置顶列表 | +| "全员禁言/群禁言" | 全员禁言或解除 | `chat group-mute` | `chat group-mute-member` | group-mute 全员禁言或解除;group-mute-member 指定成员禁言 | +| "禁言某人/指定成员禁言" | 指定成员禁言 | `chat group-mute-member` | `chat group-mute` | group-mute-member 指定成员禁言/解禁;group-mute 全员禁言 | +| "设管理员/取消管理员" | 设置群管理员 | `chat group set-admin` | `chat group invite` | set-admin 设置或取消管理员角色;invite 是邀请入群 | +| "退群/退出群聊/离开群" | 退出群聊 | `chat group quit` | `chat group members remove` | quit 是当前用户自己退群;members remove 是管理员踢别人 | +| "改群头像/更新群头像" | 更新群头像 | `chat group update-icon` | `chat group rename` | update-icon 改群头像;rename 改群名称 | +| "改群设置/群设置开关/群权限/入群许可/禁止私聊" | 更新群设置 | `chat group update-settings` | `chat group set-admin` | update-settings 改群功能开关;set-admin 是管理员角色操作 | +| "改群昵称/设置群昵称/我在群里的名字" | 设置个人群昵称 | `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 是整个会话免打扰 | +| "解散群/解散群聊" | 解散群聊 | `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 列普通群成员 | +| "从群里移除机器人/踢机器人" | 移除群内机器人 | `chat group members remove-bot` | `chat group members add-bot` | remove-bot 通过 openBotId 移除;add-bot 通过 robotCode 添加 | +| "批量查群成员详情/按ID查群成员/查看指定成员信息" | 批量查询群成员详情 | `chat group members list-by-ids` | `chat group members` | list-by-ids 根据 openDingTalkId 列表查询指定成员详情;members 分页查询全部成员列表 | +| "标记未读/设为未读/会话标未读" | 标记会话为未读 | `chat mark-unread` | `chat clear-red-point` | mark-unread 标记为未读;clear-red-point 清除红点(相反操作) | +| "清除红点/已读/取消未读" | 清除会话红点 | `chat clear-red-point` | `chat mark-unread` | clear-red-point 清除单个会话红点;mark-unread 标记未读(相反操作) | +| "全部已读/一键清除所有未读/红点清零" | 清除所有会话红点 | `chat clear-all-red-point` | `chat clear-red-point` | clear-all-red-point 清除所有会话红点;clear-red-point 只清单个会话 | +| "所有会话/全部会话列表/我的会话" | 分页获取全部会话 | `chat list-all-conversations` | `chat list-top-conversations` | list-all-conversations 返回所有会话;list-top-conversations 仅返回置顶会话 | +| "清空聊天记录/删除会话消息/清空消息" | 清空会话聊天记录 | `chat clear-messages` | `chat clear-red-point` | clear-messages 清空聊天记录;clear-red-point 仅清除未读红点 | +| "标记已读/消息已读/读了" | 标记消息已读 | `chat mark-read` | `chat clear-red-point` | mark-read 标记指定消息及之前的消息为已读;clear-red-point 仅清除红点不标记消息 | +| "我加入的所有群/拉取全部群列表/我的群" | 分页拉取所有群 | `chat group list-all` | `chat group list-my-groups` | list-all 返回用户加入的所有群;list-my-groups 仅返回用户作为群主/管理员的群 | +| "入群申请记录/谁申请入群/群申请列表/查看入群审批" | 拉取入群验证记录 | `chat group list-join-validations` | `chat group list-all` | list-join-validations 拉取入群验证/审批记录;list-all 拉取群列表 | +| "通过入群申请/拒绝入群/审批入群/同意加群" | 审批入群验证 | `chat group audit-join-validation` | `chat group list-join-validations` | audit-join-validation 执行审批操作;list-join-validations 仅查询记录 | +| "发群公告/在群里发公告/群公告置顶" | 发布群公告 | `chat group notice create` | `blackboard create` | notice create 是群维度公告(发到指定群聊);blackboard 是企业公告(面向全员,不可撤回) | +| "改群公告/修改群公告/更新群公告" | 修改群公告 | `chat group notice edit` | `chat group notice create` | edit 整体替换已有公告内容(需 dataId);create 发布新公告 | +| "查群公告/看群公告/群公告列表/群里有什么公告" | 查看群公告列表 | `chat group notice list` | `blackboard list` | notice list 查指定群的公告;blackboard list 查企业公告 | +| "群公告详情/公告已读人数/谁读了公告" | 查看群公告详情 | `chat group notice get` | `chat group notice list` | get 返回单条公告详情(含已读人数、点赞/评论数);list 分页拉公告列表 | +| "搜索机器人/找机器人/查机器人/帮我找XXX机器人" | 搜索全部可用机器人 | `chat bot find` | `chat bot search` | find 返回全部可用机器人(含他人/官方),额外返回 openDingTalkId(可用于给机器人发单聊消息);search 仅返回我创建的 | +| "给机器人发单聊/给机器人发消息/跟机器人聊天" | 给机器人发单聊消息 | `chat bot find` → `chat message send --open-dingtalk-id` | `chat bot search` | 必须先用 find 拿 openDingTalkId(search 没有此字段),再用 send --open-dingtalk-id 发单聊 | +| "我创建的机器人/我的机器人/我自己的机器人/查看我的机器人" | 搜索我创建的机器人 | `chat bot search` | `chat bot find` | search 仅返回当前用户自己创建的机器人(返回 robotCode + robotName,无 openDingTalkId);find 返回全部可用机器人 | +| "合并转发/批量转发/合并转发多条消息" | 合并转发多条消息 | `chat message combine-forward` | `chat message forward` | combine-forward 合并多条为一条转发;forward 转发单条消息 | +| "转发话题/转发话题消息/话题转发到另一个群" | 转发话题消息 | `chat message forward-topic` | `chat message forward` | forward-topic 专用于转发话题消息(需要话题ID);forward 转发普通单条消息 | +| "发卡片消息/推送流式卡片" | 创建并推送流式卡片 | `chat message send-card` | `chat message send` | send-card 发流式卡片;send 发普通文本/Markdown 消息 | +| "更新卡片/流式更新卡片" | 流式更新卡片内容 | `chat message update-card` | `chat message send-card` | update-card 更新已有卡片;send-card 创建新卡片 | +| "钉住消息/Pin消息/置顶消息到会话" | 钉住消息 | `chat message set-pin-msg` | `chat set-top` | set-pin-msg 钉住单条消息(Pin);set-top 置顶整个会话 | +| "取消钉住/Unpin消息" | 取消钉住消息 | `chat message unset-pin-msg` | `chat message set-pin-msg` | unset-pin-msg 取消钉住;set-pin-msg 设置钉住 | +| "查看钉住的消息/Pin列表/钉住消息列表" | 拉取钉住消息列表 | `chat message list-pin-msg` | `chat message list` | list-pin-msg 只返回被钉住的消息;list 拉取全部消息 | +| "置顶某条消息/把这条消息置顶" | 置顶消息 | `chat message set-top-msg` | `chat set-top` | set-top-msg 置顶会话内某条消息;set-top 置顶整个会话在列表中 | +| "取消置顶消息/取消消息置顶" | 取消置顶消息 | `chat message unset-top-msg` | `chat message set-top-msg` | unset-top-msg 取消置顶;set-top-msg 设置置顶 | +| "DING消息/查DING/DING历史" | 查询 DING 消息列表 | `ding message list` | `chat message list` | ding 是独立顶层命令;ding message list 查 DING 消息;chat message list 查普通聊天消息 | +| "DING接收状态/谁收到了DING" | DING 接收状态 | `ding message receiver-status` | `chat message read-status` | ding 是独立顶层命令;receiver-status 查 DING 接收;chat message read-status 查普通消息已读 | +| "发DING/DING通知" | 发送 DING 消息 | `ding message send` | `chat message send` | DING 是钉钉的强提醒(应用内/短信/电话),独立顶层命令;普通群消息用 chat | +| "撤回DING" | 撤回 DING 消息 | `ding message recall` | `chat message recall` | DING 撤回独立命令;chat recall 是撤回普通聊天消息 | +| "以我的名义发DING/个人发DING/用户身份DING" | 以用户身份发 DING | `ding message send-personal` | `ding message send` | send-personal 以用户身份发送,无需 robot-code;send 以机器人身份发送 | +| "以我的名义撤回DING/个人撤回DING" | 以用户身份撤回 DING | `ding message recall-personal` | `ding message recall` | recall-personal 以用户身份撤回;recall 以机器人身份撤回 | +| "消息转DING/把这条消息DING给某人/转发为DING" | 消息转 DING | `ding message send-by-message` | `ding message send-personal` | send-by-message 是将已有消息转为 DING,需指定原消息;send-personal 是直接发新 DING | +| "把最近几次关于XX的会议汇总成报告" | 按主题汇总多次听记 | #5 generate-topic-report | #7 meeting-followup | #7 是单次会议听记跟进;多次会议按主题汇总属于工作汇报 | +| "整理一下XX项目的所有讨论" | 跨源主题归档 | #5 generate-topic-report | #4 write-doc | #4 侧重单篇文档创作;按主题跨听记/群消息汇总属于工作汇报 | +| "张三在哪个部门/查一下同事工号" | 通讯录精确查询 | #8 `contact` | #5 汇报 / #4 文档 | 需要 userId、手机号、部门 ID 等精确信息时用 contact | +| "研发部的详细信息/部门信息" | 查部门详情 | `contact dept get-info` | `contact dept list-members` | 查部门属性(ID、名称、人数)用 get-info;查成员列表用 list-members | +| "研发部有多少人" | 查部门人数 | `contact dept get-info` | `contact dept list-members` | 问人数用 get-info(返回 memberCount);问有哪些人用 list-members | +| "找一下张三/搜同事/找人" | AI搜人(首选) | `aisearch person` | `contact user search` | 搜人首选 aisearch,支持姓名/部门/职责/上下级维度;精确查 userId/手机号用 contact | +| "五道的上级是谁/谁负责XX/XX的下属有谁" | AI语义搜人 | `aisearch person` | `contact` | 涉及上下级、职责、负责人等语义维度搜索,用 aisearch | +| "222020这个工号是谁/查工号" | 按工号搜人 | `aisearch person --dimension jobNumber` | `contact` | 工号查人走 aisearch,dimension=jobNumber | +| "13800138000是谁/查手机号" | 按手机号搜人 | `aisearch person --dimension phone` | `contact` | 手机号查人走 aisearch,dimension=phone | +| "搜一下智能化方案/最近 OKR 相关邮件/最近发版相关消息" | 搜企业知识内容 | `aisearch enterprise` | `doc search` / `mail search` / `chat message search` | 跨文档、消息、日程、听记、邮件等企业内容语义检索走 enterprise;具体 `queries/types/time-range` 抽槽见 `aisearch.md` | +| "我发给某人的消息/邮件/文档/今天我干了什么" | 搜行为记录 | `aisearch behavior` | `chat` / `mail` / `doc` / `report` | 关注“谁对什么做过什么”,走 behavior;具体 `behavior-type/direction/chat-scope` 抽槽见 `aisearch.md` | +| "查/提交 请假/加班/外出/出差/补卡 审批单" | 考勤业务审批单 | `attendance approve`(查询走 `attendance approve list`;提交走 `attendance approve templates --type leave\|overtime\|repair-check\|travel\|out`) | `oa approval list-pending` / `oa approval records` | 请假/加班/外出/出差/补卡 这 5 类属于考勤业务审批单,按业务类型查询;`oa approval` 是通用 OA 审批中心,覆盖范围不同 | +| "把这段文字翻译成英文/translate this" | 通用文本翻译 | `chat text translate` | `doc` / `aisearch` | 纯文本翻译,不是文档编辑或语义搜索 | +| "帮我把这个文档翻译成日文" | 文档内容翻译 | 先 `doc get` 再 `chat text translate` | `chat text translate` 直接传文件 | translate 仅支持纯文本,需先提取文档内容 | + +--- + +## 典型场景详解 + +### 1. aitable vs doc vs sheet — 数据表格 vs 文档内容 vs 电子表格 + +**用 `aitable` 的场景**: +- "创建一个表格记录团队成员信息" — 结构化数据,有行列 +- "在表格里加一列'状态'字段" — 字段/列操作 +- "查一下表格里所有优先级为高的记录" — 数据筛选和查询 +- "用项目管理模板建一个表" — 模板创建 +- 用户提到"多维表"、"Base"、"数据表"、"记录" + +**用 `doc` 的场景**: +- "帮我写个会议纪要" — 富文本内容创作 +- "看一下这个文档链接的内容" — 阅读文档 +- "在知识库创建一个文件夹" — 文档空间管理 +- 用户提到"文档"、"知识库"、"写文档" + +**用 `sheet` 的场景**: +- "创建一个电子表格" — 创建 Excel 式在线表格 +- "帮我读一下这个表格 A1 到 D10 的数据" — 按单元格区域读取 +- "在 B2 写入一个 SUM 公式" — 写入公式/值到单元格 +- "帮我看看这个表格有哪些工作表" — 工作表管理 +- 用户提到"电子表格"、"Excel"、"工作表"、"Sheet"、"单元格"、"公式" + +**三者判断关键**: +- 有字段定义/记录增删改查/数据筛选 → `aitable` +- 纯文本/Markdown/富文本编辑 → `doc` +- 单元格区域读写/公式/多工作表 → `sheet` + +**易误判场景**: +- "在知识库中新建一个表格" — 指在钉钉文档空间创建表格类型节点 → `doc`(不是 `aitable`) +- "帮我建个表记录项目进度" — 指创建结构化数据表 → `aitable` + +--- + +### 1.1 xlsx vs axls — 本地表格文件 vs 在线电子表格 + +alidocs 链接表面长得一样(`https://alidocs.dingtalk.com/i/nodes/{id}`),但节点类型完全不同。sheet 产品线只服务 axls(在线电子表格),xlsx / xls / xlsm / csv 等本地表格文件必须走 `dws drive download`,严禁错路由。 + +用 `sheet` 的场景(axls,钉钉在线电子表格): +- `dws doc info --node ` 返回 `contentType=ALIDOC` + `extension=axls` +- 用户在钉钉文档空间直接"新建电子表格"得到的节点 +- 所有 sheet 子命令(`list` / `range read` / `range write` / `export` 等)仅服务这类节点 + +用 `dws drive download` 的场景(xlsx / xls / xlsm / csv 本地表格文件): +- `dws doc info --node ` 返回 `contentType=DOCUMENT` + `extension=xlsx` / `xls` / `xlsm` / `csv` +- 用户把本地 Excel 文件上传到文档空间得到的节点,本质是"文件 + 预览",非在线表格 +- sheet 命令直接调用会报错,必须先 `dws drive download --node ` 下载到本地再解析处理 + +判断关键: +- 未知 alidocs URL → 必须先 `dws doc info --node --format json` 探测 `contentType` 与 `extension` +- `contentType=ALIDOC` + `extension=axls` → `sheet` +- `contentType=DOCUMENT` + `extension=xlsx` / `xls` / `xlsm` / `csv` → `dws drive download` +- 用户说"把在线表格导出为 xlsx 文件" → `dws sheet export`(axls → xlsx 的格式转换,不是读取 xlsx) + +易误判场景: +- 用户粘贴一个 alidocs 链接说"读一下这个表格" — 不能直接调 `sheet range read`,必须先 probe 再按 `extension` 路由 +- 用户说"读一下这个 xlsx 文件里的数据" — 走 `dws drive download` 下载后本地解析,不要走 `sheet` +- 用户说"把这个在线表格导出为 xlsx" — 走 `dws sheet export`,不要走 `dws drive download`(后者只能下载已有的 xlsx 节点,无法从 axls 生成) + +详见 [url-patterns.md](./url-patterns.md) 和 [sheet.md 适用范围](../../dingtalk-sheet/references/sheet.md)。 + +--- + +### 2. devdoc vs drive search / wiki node search — 两种搜索 + +**用 `devdoc` 的场景**: +- "API 调用报错 403 怎么解决" — 开发调试问题 +- "搜一下 OAuth2 接入文档" — 开放平台技术文档 +- "CLI 命令出错了怎么办" — CLI 使用错误 +- 用户提到"开发"、"API"、"调用错误" + +**用 `drive search` / `wiki node search` 的场景**: +- "在我的文档里搜一下'项目方案'" — 全局搜索用 `drive search` +- 用户明确说"某个知识库里搜" — 空间内搜索用 `wiki node search --workspace` + +**判断关键**:搜开发文档→ `devdoc`;搜用户自己的文档→ `drive search`(全局)或 `wiki node search`(空间内) + +--- + +### 3. drive vs doc vs wiki — 存储层 vs 内容层 vs 空间管理层 + +> **两层模型判定口诀**:如果操作换一种文件类型还能成立,就是存储层(→ drive);操作只对特定格式有意义,就是内容层(→ doc/sheet);操作是对空间/节点的组织管理,就是空间管理层(→ wiki)。 + +**用 `drive`(存储层)的场景**: +- "把这个 PDF 传到钉盘" — 上传文件(不关心格式) +- "下载那个 Excel 附件" — 下载文件(不关心格式) +- "看一下钉盘根目录有什么文件" — 浏览文件列表 +- "搜一下有没有叫季度汇报的文件" — 全局搜索文件实体 (`drive search`) +- "把这个文档复制一份" — 复制文件实体 +- "把文件移到另一个文件夹" — 移动文件实体 +- "改一下文件名" — 重命名文件实体 +- "给张三加个编辑权限" — 权限管理 +- 用户提到"钉盘"、"网盘"、"上传"、"下载"、"搜文件"、"找文件"、"复制"、"移动"、"重命名"、"权限" + +**用 `doc`(内容层)的场景**: +- "读一下这个文档的内容" — 读取文档 Markdown(仅 adoc 有意义) +- "帮我写入一段话到文档里" — 编辑文档内容(仅 adoc 有意义) +- "在第三段后面插入一个表格" — 块级编辑(仅 adoc 有意义) +- "给这段内容加个评论" — 文档评论(仅 adoc 有意义) +- "把这个文档导出为 docx" — 文档导出(当前仅 adoc 支持) +- 用户提到"读文档内容"、"写文档"、"编辑文档"、"块级编辑"、"文档评论"、"导出文档" + +**用 `wiki`(空间管理层)的场景**: +- "列出所有知识库" — 空间列表 (`wiki space list`) +- "列出钉盘团队空间" — 钉盘空间列表 (`wiki space list --type orgSpace`) +- "在产品知识库里搜一下方案" — 空间内搜索 (`wiki node search --workspace`) +- "在知识库里创建一个空白文档" — 创建节点 (`wiki node create --type adoc`) +- "把这个文件移到另一个知识库" — 跨知识库移动 (`drive move`) +- "给知识库加个成员" — 成员管理 (`wiki member add`) +- 用户提到"知识库"、"团队空间"、"空间成员"、"空间内搜索"、"列出空间" + +**判断关键(两层模型)**: +- 操作不关心文件格式 → `drive`(存储层) +- 操作仅对特定文档格式有意义 → `doc` / `sheet`(内容层) +- 操作是对空间/节点的组织管理 → `wiki`(空间管理层) + +**搜索场景路由**: +- "搜文件" / "找文件"(不指定空间) → `drive search`(全局聚合搜索) +- "在某个知识库里搜" → `wiki node search --workspace `(空间内搜索) + +**创建场景路由**: +- "在知识库里创建一个文档" → `wiki node create --type adoc`(创建空文件实体) +- "帮我写一篇项目周报" → `doc create`(创建并写入内容) +- "创建一篇文档并写入内容" → 先 `wiki node create` 再 `doc update`(先创建实体,再写内容) + +**列表场景路由**: +- "列出钉盘文件" → `drive list` +- "列出知识库里的文件" → `drive list --workspace ` 或 `wiki node list --workspace ` +- "列出所有空间" → `wiki space list` + +--- + +### 4. 视频会议 vs 日历日程 — 视频会议不在当前 CLI + +当前 CLI **不提供**视频会议命令。“预约视频会议/开个会/发起会议/会中控制(静音/共享屏幕/录制)/呼叫入会”等诉求,直接告知用户请在钉钉客户端完成,不要试探 `dws conference` 或用其他命令变通。 + +**用 `calendar` 的场景**(这些都不是视频会议): +- "明天下午安排个会" — 日程管理(可含会议室) +- "给自己留两个小时写方案/建个个人日程" — 个人日历事件,仍用 `calendar event create` +- "帮我约几个人开会" — 创建日程 + 添加参与者 +- "看看下午有没有空闲会议室" — 会议室管理 +- "帮我查一下同事有空吗" — 闲忙查询 +- 用户提到"日程"、"会议室"、"约会" + +**判断关键**:视频会议相关(发起/预约/控制/邀请)→ 当前 CLI 不支持,请在钉钉客户端操作;日程/参与者/会议室管理 → `calendar` + +--- + +### 5. chat 内部 — 消息发送与撤回 + +**用 `chat message send` 的场景**: +- "帮我在群里发个消息提醒大家" — **个人身份**发群消息 +- "发个单聊消息给某人" — 个人身份发单聊: + - 已有 userId 时直接使用 `--user`;已有 openDingTalkId 时使用 `--open-dingtalk-id` + - 纯文本/Markdown 单聊传 `--user` 时直接走 userId 发送能力,不需要先手动查询 openDingTalkId + - 富媒体消息(image/file)单聊优先使用 `--open-dingtalk-id`;传 `--user` 时 CLI 会尝试解析为 openDingTalkId 后发送 +- "发张图片/截图/语音/视频/文件到群里" / "发张图给某某" — **统一一条命令**:`dws chat message send ... --msg-type file --file-path <本地路径>`,CLI 内部自动上传并发送,**任意扩展名(png/jpg/pdf/mp4/zip…)都走这条** +- "发图片+文字说明" — 不要硬塞进一条命令;先发文件消息再补一条 `--text "..."` 即可 + +```bash +dws chat message send --group --msg-type file --file-path ./screenshot.png --format json +dws chat message send --open-dingtalk-id --msg-type file --file-path ./report.pdf --format json +``` + +> ❌ 反模式:调 `dt_media_upload` / `extract_media_id.py` / `drive upload` / `drive download` 等前置工具再 `--msg-type image --media-id`。这是**旧链路**,仅当上游已持有 mediaId 才用;新场景一律 `--file-path` 直发,避免长链路与“空白图”现象。 +> 富媒体消息单聊优先使用 `--open-dingtalk-id`;传 `--user` 时 CLI 会尝试解析为 openDingTalkId 后发送。 + +- "发送位置/坐标/地址到群里" / "发个位置给某某" — `dws chat message send ... --msg-type location --latitude <纬度> --longitude <经度> --location-name <地址名称> --map-thumbnail-url @mediaId`;地图缩略图需先通过 `dt_media_upload` 上传获取 mediaId + +```bash +dws chat message send --group --msg-type location --latitude <纬度> --longitude <经度> --location-name <地址名称> --format json +``` + +- "分享联系人/名片到群里" / "把某某的名片发给某某" — `dws chat message send ... --msg-type profile --contact-id ` + +```bash +dws chat message send --group --msg-type profile --contact-id --format json +``` + +**用 `chat message send-by-bot` 的场景**: +- "让机器人在群里发一条通知" — **机器人身份**发消息 +- "给张三发一条机器人单聊消息" — 机器人单聊 + +**用 `chat message send-by-webhook` 的场景**: +- "通过 Webhook 发告警到群里" — 自定义机器人 Webhook +- 用户有 Webhook Token + +**用 `chat message recall-by-bot` 的场景**: +- "撤回刚才机器人发的消息" — 需要 robot-code + processQueryKey + +用 `chat message recall` 的场景: +- "撤回我刚发的消息" — 撤回以个人身份发送的消息,需要 openConversationId + openMessageId + +用 `chat message query-send-status` 的场景: +- "消息发没发成功/查询消息发送状态" — 查询个人发送消息的状态,需要 send 返回的 openTaskId + +用 `chat message search-advanced` 的场景(推荐首选): +- "按发送者搜索消息/指定多个群搜索/@我的消息多维度搜" — 支持关键词、发送者、@我、@指定人、多个会话等维度组合搜索 +- 替代关系:完全替代 `chat message search`(严格超集:keyword 可选 vs 必填,支持多群 vs 单群);大部分替代 `chat message list-by-sender`(--user/--users 覆盖按 userId 搜索发送者,--sender-ids 覆盖按 openDingTalkId 搜索)和 `chat message list-mentions`(--at-me 覆盖核心功能) +- 不能替代:`chat message list-focused`(「特别关注人」是独立维度) +- 默认使用 search-advanced,仅在上述不适用场景才降级到具体命令 + +**不支持的场景**: +- "撤回我刚发的消息"(但不知道消息 ID) — 需先通过消息拉取或搜索接口(如 `chat message list`、`chat message search-advanced` 等)获取 openMessageId,再调用 `chat message recall` + +判断关键:个人发→ `send`;机器人发→ `send-by-bot`;有 Webhook Token→ `send-by-webhook`;个人撤回→ `recall`;机器人撤回→ `recall-by-bot`;查发送状态→ `query-send-status`;消息搜索类意图优先路由到 `search-advanced`(推荐首选),仅在不适用时降级到具体命令 + +--- + +### 6. chat vs ding — 普通会话 vs 强提醒 + +**用 `chat` 的场景**: +- "在群里发条通知" — 钉钉会话/群消息 +- "拉取某个群/某个人单聊的聊天记录" — `chat message list` +- "某人发给我的消息" — `chat message list-by-sender` +- "@我的消息/提及我的" — `chat message list-mentions` +- "查看我最近的所有消息" — `chat message list-all` +- "特别关注人的消息/关注的人的消息/我特别关注的人最近发了什么消息/关注的人最近聊了啥" — `chat message list-focused` +- 注:判断顺序——**先**看 query 是否含动词【发/说/聊/讲】或名词【消息/聊天/动态】,含则路由到 `chat message list-focused`;**仅**当 query 终点是"人员列表"(如"我关注了谁/我特别关注的人有哪些",无任何消息域动词)时,才路由到 `contact relation list-my-followings`。 +- "搜索消息里的XX/查找包含XX的消息" — `chat message search` +- "我和XX的共同群" — `chat search-common` +- "置顶会话/我的置顶/查看置顶" — `chat list-top-conversations` +- "置顶消息" — 先 `chat list-top-conversations` 拉置顶会话列表,再用 `chat message list --group ` 分别拉各会话的消息 +- "置顶某条消息/把这条消息置顶" — `chat message set-top-msg` +- "取消置顶消息/取消消息置顶" — `chat message unset-top-msg` +- "设置/取消会话置顶" — `chat set-top`(--off 取消置顶) +- "引用回复消息" — `chat message reply` +- "转发消息到另一个群" — `chat message forward` +- "全员禁言/解除禁言" — `chat group-mute`(--off 解除禁言) +- "禁言某人/指定成员禁言" — `chat group-mute-member` +- "设管理员/取消管理员" — `chat group set-admin`(--off 取消) +- "发群公告/定时发群公告" — `chat group notice create`(--run-at 定时发布;企业级全员公告用 `blackboard create`) +- "改群公告" — `chat group notice edit`(需先用 notice list 拿 dataId) +- "查群公告/群公告列表/定时公告" — `chat group notice list`(--scheduled 查定时公告) +- "群公告详情/公告已读人数" — `chat group notice get` +- "发DING/DING通知" — `ding message send`(机器人身份,需 --robot-code) +- "以我的名义发DING/个人发DING/用户身份DING" — `ding message send-personal`(用户身份,无需 robot-code) +- "撤回DING" — `ding message recall`(机器人身份) +- "以我的名义撤回DING/个人撤回" — `ding message recall-personal`(用户身份) +- "DING消息/查DING历史" — `ding message list` +- "DING接收状态/谁收到了DING" — `ding message receiver-status` +- 用户明确说"群"、"会话"、"机器人发群消息"、"Webhook" + +**判断关键**:普通单聊、群聊、机器人或 Webhook 消息走 `chat`;需要应用内强提醒、短信或电话升级时走 `ding`。 + +--- + +### 7. report vs doc vs todo — 日志 vs 文档 vs 待办 + +**用 `report` 的场景**: +- "帮我看看收到的日报" — 收件箱列表 (`report inbox list`) +- "帮我写/提交今天的日报(钉钉日志模版)" — 先 `report template list` / `template get`,再 `report entry submit` +- "有什么日志模版" — 查看模版 (`report template list`) +- "看看这个日志的已读统计" — 阅读状态 (`report entry stats`) +- "我发过的日志有哪些" — 发件箱列表 (`report outbox list`) +- 用户提到"日报"、"周报"、"日志" + +**用 `doc` 的场景**: +- "帮我写个项目总结文档" — 长文本创作(钉钉在线文档,非日志模版) + +**用 `todo` 的场景**: +- "记一下这周要做的事" — 个人任务管理 +- "创建一个待办提醒" — 仍归 `todo`,但要先说明当前只有 dueTime 截止时间,没有独立 reminder schedule + +**判断关键**:钉钉日志系统(日报/周报模版,含按模版创建汇报)→ `report`;文档/知识库长文→ `doc`;任务清单→ `todo` + +--- + +### 7.1 attendance approve vs oa approval — 考勤业务审批 vs 通用 OA 审批 + +**用 `attendance approve` 的场景**(考勤业务审批单): +- "上周谁请假了 / 某人近期的加班记录 / 外出出差单 / 补卡审批单" — `attendance approve list --types overtime,leave,trip,patch`(`trip` 在查询接口 bizType=2 同时覆盖出差与外出,两者合并为同一类,查询不再细分;travel / business_trip / 出差 / 外出 亦映射到 2) +- "查看考勤审批模板 / 帮我提交考勤审批" — `attendance approve templates --type leave|overtime|repair-check|travel|out`(外出=travel/TRAVEL,出差=out/trip/OUT) +- 用户提到“请假单 / 加班单 / 出差单 / 补卡单 / 考勤审批”等考勤上下文 + +**用 `oa approval` 的场景**(通用 OA 审批中心): +- "我的待审批 / 待办审批 / 已审批记录" — `oa approval list-pending` +- "某审批单详情 / 同意或驳回审批 / 撤销我发起的审批" — `oa approval detail/approve/reject/revoke` +- "查业务审批记录 / 审批转交 / 添加评论与抄送" — `oa approval records/transfer/comment/cc` +- 用户提到“报销 / 采购 / 用印 / 合同 等非考勤类审批” + +**判断关键**: +- 审批主题明确是【请假 / 加班 / 外出 / 出差 / 补卡】这 5 类 → `attendance approve` +- 不限于考勤业务、面向“我上下游的审批任务”表述 → `oa approval` + +**提交审批单的边界**: +- 提交考勤审批单走 `attendance approve templates --type leave|overtime|repair-check|travel|out`(外出=travel,出差=out/trip),命令会返回审批表单的 submitUrl 跳转链接,由用户点击链接跳转到钉钉客户端的提交页面完成填写与提交。**展示链接时必须用 Markdown 可点击格式 `[表单名称](submitUrl)`,不要裸露 URL**。 +- 提交诉求的辅助查询:可用假期余额走 `attendance vacation balance`、历史已提交记录走 `attendance approve list`。 +- 任何场景下都**不要误用 `oa approval` 代替** —— 该命令组只能查/审/撤已存在的审批单,考勤业务审批单走考勤自己的逻辑便于区分。 + +--- + +## 跨产品工作流路由 + +以下场景需要多个产品配合完成,注意上下文传递顺序。多步骤操作有现成脚本时优先使用脚本。 + +### 发邮件给同事(aisearch → contact → mail) + +用户说“给张三发封邮件”,但只知道名字不知道邮箱地址: + +> 有脚本: `python scripts/mail_send_with_cc.py` + +```bash +# 1. 搜人获取 userId(多人同名须 contact user get 消歧,禁止默认选第一个,详见 08-directory.md「多命中」) +dws aisearch person --keyword "张三" --dimension name --format json + +# 2. 用 userId 查详情获取 email +dws contact user get --ids --format json + +# 3. 用搜索到的邮箱地址作为收件人发送邮件 +dws mail mailbox list --format json # 获取发件人邮箱 +dws mail message send --from my@company.com --to zhangsan@company.com \ + --subject "周报" --body "内容" --format json +``` + +### 创建日程并邀请同事(aisearch → calendar) + +用户说“约张三明天下午开会”: + +> 有脚本: `python scripts/calendar_schedule_meeting.py --title "会议" --start "..." --end "..." --users userId1 --book-room` + +```bash +# 手动流程(脚本不可用时): +# 1. 搜人获取 userId(多人同名须 contact user get 消歧,禁止默认选第一个,详见 08-directory.md「多命中」) +dws aisearch person --keyword "张三" --dimension name --format json + +# 2. 创建日程 +dws calendar event create --title "会议" \ + --start "2026-03-15T14:00:00+08:00" --end "2026-03-15T15:00:00+08:00" --format json + +# 3. 添加参与者 +dws calendar participant add --event --users --format json +``` + +### 创建待办并指派(aisearch → todo) + +用户说“给张三建个待办”: + +```bash +# 1. 搜人获取 userId(多人同名须 contact user get 消歧,禁止默认选第一个,详见 08-directory.md「多命中」) +dws aisearch person --keyword "张三" --dimension name --format json + +# 2. 创建待办 +dws todo task create --title "任务内容" --executors --format json +``` + +--- + +### 8. 纯通讯录查询 vs 跨产品(#8) + +**仅查人/部门/成员/归属/组织关系**(没有「发消息、写文档、建待办」等第二动作)→ 匹配 [SKILL.md](../SKILL.md) **#8 通讯录**(行动指南 [08-directory.md](../../dingtalk-contact/references/08-directory.md)),不要用 #5 汇报或 #4 文档。 + +**多轮对话**:用户先说「搜 userId」再说「要详细资料」→ 仍属 #8;第二步 **必须** 执行 `contact user get --ids`,禁止只用第一次 `user search` 的浅表字段交差。 + +**与 #1 消息区分**:终点是「把消息发给某人」→ #1;终点是「某人 userId/部门是什么」→ #8。可先 #8 解析 ID 再 #1。 + +**与发邮件、待办、日程混排**:先后顺序与口径见 [08-directory.md](../../dingtalk-contact/references/08-directory.md#与其他场景消歧)。 + +**特别关注列表查询**:用户说"我关注了谁/我的特别关注列表/我的星标联系人/特别关注的人有哪些" → `dws contact relation list-my-followings`(无入参)。 + +- 与 `chat message list-focused` 区分:前者拉"人员列表",后者拉"这些人发的消息"。 +- **可执行判断口径(按顺序扫描)**: + 1. 扫描 query 是否含动词【发/说/聊/讲】或名词【消息/聊天/动态/最新内容】 → 含则**强制**路由到 `chat message list-focused`,**忽略**主语中的"关注/特别关注/星标"。 + 2. 仅含"关注/特别关注/星标"+"人/列表/谁/有哪些/多少" → 路由到 `dws contact relation list-my-followings`。 +- **反例 query(绝不路由到 list-my-followings)**: + - "我特别关注的人最近发了什么消息" → `chat message list-focused` + - "关注的人最近都说了啥" → `chat message list-focused` + - "星标联系人发的群消息" → `chat message list-focused` +- **正例 query(路由到 list-my-followings)**: + - "我特别关注的人有哪些"、"我关注了谁"、"我的星标联系人" + + +### 发送图片 / 本地文件(统一一条命令) + +用户说"发张图/把这张图发给某某/发个 PDF/发个语音/发个视频/发个文件"等任何场景,**统一一条命令**: + +```bash +# 群聊 +dws chat message send --group --msg-type file --file-path <本地路径> --format json + +# 单聊(推荐 --open-dingtalk-id;--user 也支持) +dws chat message send --open-dingtalk-id --msg-type file --file-path <本地路径> --format json +``` + +支持任意扩展名(`.png/.jpg/.gif/.bmp/.webp/.pdf/.doc/.xls/.zip/.mp3/.wav/.mp4/.avi` …),CLI 自动识别并处理。**无需** `dt_media_upload` / `extract_media_id.py` / `drive upload` / `drive download` / `chat conversation-info` / `chat file upload` 等任何前置工具调用。 + +### 图片/文件 + 文字说明 + +不要把文字塞进 `--msg-type file` 命令(该命令不读 `--text`)。先发文件再补一条文本消息即可: + +```bash +dws chat message send --open-dingtalk-id --msg-type file --file-path ./screenshot.png --format json +dws chat message send --open-dingtalk-id --text "这是本周数据汇总" --format json +``` + +### 旧链路(mediaId)— 仅兼容场景 + +仅当上游已经通过 `dt_media_upload` 拿到 `@lQL...` 形式的 mediaId 时使用: + +```bash +dws chat message send --group --msg-type image --media-id "@lQLPD4JNnliqBq3NBQDNA8Cw" --format json +``` diff --git a/skills/multi/dws-shared/references/shared-index.md b/skills/multi/dws-shared/references/shared-index.md new file mode 100644 index 00000000..20718eb8 --- /dev/null +++ b/skills/multi/dws-shared/references/shared-index.md @@ -0,0 +1,12 @@ +# 共享能力索引 + +| 主题 | 详见 | +|---|---| +| 认证/全局参数/多组织 | [`global-reference.md`](./global-reference.md) | +| URL 路由与类型探测 | [`url-patterns.md`](./url-patterns.md) | +| 跨产品意图消歧 | [`intent-guide.md`](./intent-guide.md) | +| 能力边界 | [`capability-limits.md`](./capability-limits.md) | +| 错误码与排查 | [`error-codes.md`](./error-codes.md) | +| 字段规则 | [`field-rules.md`](./field-rules.md) | +| 通用规范 | [`best_practices/_common/conventions.md`](./best_practices/_common/conventions.md) | +| Recipe 约定 | [`best_practices/_common/recipe-conventions.md`](./best_practices/_common/recipe-conventions.md) | diff --git a/skills/multi/dws-shared/references/url-patterns.md b/skills/multi/dws-shared/references/url-patterns.md new file mode 100644 index 00000000..b359e7e7 --- /dev/null +++ b/skills/multi/dws-shared/references/url-patterns.md @@ -0,0 +1,129 @@ +# URL 格式与处理规范 + +## alidocs URL 分流决策(必须首先执行) + +收到 `alidocs.dingtalk.com` URL 时,**必须按以下顺序判断,禁止跳过**: + +1. URL 路径含 `/i/p/` → **分享短链**,禁止调用 `dws doc` 任何子命令 → 按下方 [分享短链处理](#分享短链处理) 执行 +2. URL 路径含 `/i/nodes/` → **节点链接**,需探测类型 → 按下方 [alidocs URL 类型探测流程](#alidocs-url-类型探测流程) 执行 +3. URL 路径含 `/spreadsheetv2/` → **电子表格直链**,直接路由到 `sheet`,将完整 URL 原样传给 `--node` 参数 +4. URL 路径含 `/document/edit` 或 `/document/preview` 且 query 参数包含 `dentryKey` → **文档链接**,直接路由到 `doc`,将完整 URL 原样传给 `--node` 参数(URL 中不一定有 `type=d`,只需匹配路径和 `dentryKey` 参数即可) +5. 其他 alidocs URL 格式 → 告知用户当前暂不支持该链接格式 + +--- + +## 已知 URL 格式 + +需要自行拼接链接时,只能使用以下模板: + +| 产品 | 用途 | URL 格式 | ID 来源 | +|------|------|----------|---------| +| `aitable` | AI表格 Base 链接 | `https://alidocs.dingtalk.com/i/nodes/{baseId}` | `base list/search/create/get` 返回的 `baseId` | +| `aitable` | AI表格指定数据表链接 | `https://alidocs.dingtalk.com/i/nodes/{baseId}?iframeQuery=sheetId%3D{tableId}` | `baseId` + `table create/get` 或 `base get` 返回的 `tableId` | +| `aitable` | AI表格指定数据表+视图链接 | `https://alidocs.dingtalk.com/i/nodes/{baseId}?iframeQuery=sheetId%3D{tableId}%26viewId%3D{viewId}` | `baseId` + `tableId` + `view create/get` 返回的 `viewId` | +| `aitable` | AI表格模板预览 | `https://docs.dingtalk.com/table/template/{templateId}` | `template search` 返回的 `templateId` | +| `doc` | 文档链接 | `https://alidocs.dingtalk.com/i/nodes/{dentryUuid}` | `doc` 命令返回的 `dentryUuid` | +| `sheet` | 电子表格链接 | `https://alidocs.dingtalk.com/i/nodes/{dentryUuid}` | `sheet create` 返回的 `dentryUuid` | +| `sheet` | 电子表格直链 | `https://alidocs.dingtalk.com/spreadsheetv2/{key}/...?dentryKey={key}&type=s` | 用户提供的完整 URL,直接传给 `--node` | +| `doc` | 文档链接(edit/preview) | `https://alidocs.dingtalk.com/document/{edit\|preview}?...&dentryKey={key}` | 用户提供的完整 URL,直接传给 `--node` | +| `minutes` | 听记链接 | `https://shanji.dingtalk.com/app/transcribes/{taskUuid}` | `list mine/shared` 返回的 `taskUuid` | + +不在此表中的产品,禁止自行拼接 URL。命令返回中包含完整链接时直接使用,否则告知用户无法提供。 + +## 分享短链处理 + +`alidocs.dingtalk.com/i/p/{shortKey}` 是钉钉文档的**对外分享短链**,`dws doc` 命令无法解析此格式。 + +### 识别规则 + +URL 路径中包含 `/i/p/` 即为分享短链(无论后面是否还有子路径),例如: +- `https://alidocs.dingtalk.com/i/p/Y7kmbokZp3pgGLq2` +- `https://alidocs.dingtalk.com/i/p/Y7kmbokZp3pgGLq2/docs/AY39rGpMPmeVNpXZevZm8OZkXKnaoNQ7` +- `https://alidocs.dingtalk.com/i/p/AbCdEfGh1234` +- `https://alidocs.dingtalk.com/i/p/AbCdEfGh1234/sheets/XYZ789` + +> **关键**:只要 URL 中出现 `/i/p/`,无论后面跟什么子路径(`/docs/...`、`/sheets/...` 等),都属于分享短链,一律禁止调用 `dws doc`。 + +### 处理方式 + +**不要调用 `dws doc` 任何子命令**(包括 `doc info`、`doc read` 等),`dws` 无法解析此格式。 + +- **需要获取文档内容时**:使用 `read_url` 工具直接读取该链接 +- **其他操作(如移动、复制、权限管理等)**:告知用户此链接为分享短链,无法直接执行复制、移动、权限管理等操作。如需保存该文档内容,建议用户在钉钉客户端中打开该页面,手动复制文本内容,然后可通过 `dws doc create` 创建一篇新文档并将内容写入 + +``` +# 需要读取文档内容时(无论 /i/p/ 后面有没有子路径,都用 read_url) +read_url("https://alidocs.dingtalk.com/i/p/Y7kmbokZp3pgGLq2") +read_url("https://alidocs.dingtalk.com/i/p/Y7kmbokZp3pgGLq2/docs/AY39rGpMPmeVNpXZevZm8OZkXKnaoNQ7") + +# 禁止(以下全部会失败,dws 无法解析任何含 /i/p/ 的 URL) +dws doc info --node "https://alidocs.dingtalk.com/i/p/Y7kmbokZp3pgGLq2" --format json +dws doc read --node "https://alidocs.dingtalk.com/i/p/Y7kmbokZp3pgGLq2/docs/AY39rGpMPmeVNpXZevZm8OZkXKnaoNQ7" --format json +``` + +### 当 `read_url` 返回内容不完整时 + +钉钉文档分享页是动态渲染的,`read_url` 可能只能获取到页面标题等有限信息,无法获取文档正文。此时**禁止猜测原因**(如"权限不足""文档为空""文档已删除"等),**禁止建议用户"提供 `/i/nodes/` 格式链接"**(分享短链和节点链接是不同体系,普通用户无法自行转换)。应直接告知用户: + +> 这个链接是钉钉文档的分享短链,由于页面是动态渲染的,我无法通过该链接直接获取文档的完整正文内容。 +> +> 你可以: +> 1. 在钉钉客户端中打开该文档,将正文内容复制粘贴给我 +> 2. 如果文档已保存在你的文档空间中,可以告诉我文档名称,我通过 `dws drive search` 搜索后再读取 + +--- + +## alidocs URL 类型探测流程 + +`alidocs.dingtalk.com/i/nodes/{id}` 是钉钉文档空间的统一 URL,可能指向**文档、电子表格、多维表、文件、文件夹**等不同类型。**禁止仅凭 URL 就假定为文档**,必须先探测类型再路由到正确的产品。 + +### 探测步骤 + +``` +Step 1 → dws doc info --node "" --format json +Step 2 → 从返回中提取 contentType、extension、nodeType 字段 +Step 3 → 按下方路由规则映射到对应产品 +``` + +### 路由映射表 + +| 条件 | 路由到产品 | 后续操作 | +|------|-----------|---------| +| `contentType=ALIDOC`, `extension=adoc` | `doc` | 按 [doc.md](../../dingtalk-doc/references/doc.md) 操作 | +| `contentType=ALIDOC`, `extension=axls` | `sheet` | 按 [sheet.md](../../dingtalk-sheet/references/sheet.md) 操作(仅 `axls` 在线电子表格) | +| `contentType=ALIDOC`, `extension=able` | `aitable` | 将 nodeId 作为 baseId,按 [aitable.md](../../dingtalk-aitable/references/aitable.md) 操作 | +| `contentType=DOCUMENT`, `extension=xlsx` / `xls` / `xlsm` / `csv` | `drive` | 必须用 `dws drive download` 下载到本地处理,禁止走 `sheet`(非在线表格,sheet 命令无法操作) | +| `contentType≠ALIDOC`, `nodeType=file` | `drive` | 调用 `dws drive download` 下载,返回文件下载链接 | +| `nodeType=folder` | `drive` | 调用 `dws drive list --workspace ` 或 `dws wiki node list` 列出子节点 | +| 以上均不匹配 | — | 告知用户当前暂不支持该类型 | + +> axls vs xlsx 关键区分: +> - `axls`(钉钉在线电子表格,`contentType=ALIDOC`)→ 走 `sheet` 产品线(读/写/筛选/导出等服务端原子操作) +> - `xlsx` / `xls` / `xlsm` / `csv`(上传到文档空间的本地表格文件,`contentType=DOCUMENT`)→ 必须走 `dws drive download` 下载到本地后再解析处理,严禁错误路由到 `sheet` 产品线(sheet 命令只支持在线表格,调用 xlsx 节点会直接报错) +> - 用户想把在线表格导出为 xlsx 文件 → 用 `dws sheet export`(输入是 `axls`,输出是 xlsx,这是 axls → xlsx 的格式转换,不属于 xlsx 读取场景) + +### 示例 + +```bash +# 用户传入: https://alidocs.dingtalk.com/i/nodes/abc123 +dws doc info --node "https://alidocs.dingtalk.com/i/nodes/abc123" --format json + +# 返回 contentType=ALIDOC, extension=axls → 在线电子表格,路由到 sheet +dws sheet list --node "https://alidocs.dingtalk.com/i/nodes/abc123" --format json + +# 返回 contentType≠ALIDOC, extension=xlsx/xls/csv → 本地表格文件,必须下载处理(禁止走 sheet) +dws drive download --node "https://alidocs.dingtalk.com/i/nodes/xlsx456" + +# 返回 contentType≠ALIDOC, nodeType=file → 普通文件,下载 +dws drive download --node "https://alidocs.dingtalk.com/i/nodes/def456" + +# 返回 nodeType=folder → 文件夹,列出子节点 +dws drive list --workspace --format json +``` + +### 何时可跳过探测 + +当用户指令中已明确指定产品(如"帮我读这个文档"、"看下这个表格的数据"),可结合用户意图**跳过探测**直接路由。仅在以下情况**必须执行探测**: +- 用户只粘贴 URL,无其他上下文 +- 用户指令与 URL 实际类型可能不一致(如说"文档"但实际是表格) +- 用户直接粘贴的是原始 `alidocs` URL,且没有上游命令返回来确认类型 diff --git a/test/skill_e2e/skill_e2e_test.go b/test/skill_e2e/skill_e2e_test.go index 9300bbbb..f139e785 100644 --- a/test/skill_e2e/skill_e2e_test.go +++ b/test/skill_e2e/skill_e2e_test.go @@ -2,31 +2,35 @@ // +build skill_verify // Package skill_e2e_test runs every backtick `dws ...` command from skill -// docs through the real cobra binary in --mock mode, then asserts the -// returned envelope is well-formed and success=true. +// docs through a real Cobra binary in --mock mode and rejects unknown-command +// and unknown-flag dispatch failures. Mock JSON envelopes and help-like +// non-JSON output are both accepted because some documented entries +// intentionally name a command group rather than a leaf operation. // -// Build tag rationale: this test depends on a real `dws` binary on PATH -// and invokes ~220 subprocesses (slow). Default `go test ./...` skips it. -// Run explicitly: `go test -tags skill_verify ./test/skill_e2e/...` +// Build tag rationale: this test depends on a real `dws` binary built from the +// current checkout and invokes hundreds of subprocesses. Default `go test +// ./...` skips it. Build with `go build -o dws ./cmd`, then run explicitly +// with: `go test -tags skill_verify ./test/skill_e2e/...` // // This is Layer B of the skill verification matrix — pure open-source, -// no real tenant required. Layer A (test/skill_static) checks help-text -// dispatch; this layer actually invokes each command end-to-end and -// validates the envelope/JSON response. +// no real tenant required. Layer A (test/skill_static) checks the current +// source command tree and multi-skill flags; this layer actually invokes each +// supported command end-to-end. // // Why --mock instead of real calls: open-source CI has no real DingTalk // tenant, and depending on one would either (a) leak credentials or // (b) make the test flaky on tenant drift. --mock returns a deterministic // shape (`{"_mock": true, "_tool": "", "result": [], "success": true}`) -// for every cobra command that resolves cleanly. Any real dispatch -// failure (unknown flag, unknown command, broken envelope) surfaces -// here exactly as it would on a real tenant. +// for Cobra commands that resolve through the MCP path. Unknown command/flag +// failures surface here exactly as they would on a real tenant. package skill_e2e_test import ( "bufio" "encoding/json" "fmt" + "io/fs" + "os" "os/exec" "path/filepath" "regexp" @@ -63,13 +67,6 @@ var ( "参数名是", "参数错", "应该用", "废弃", "已下线", } - // Per-file line ranges considered intentional anti-pattern blocks. - antiPatternRanges = map[string][][2]int{ - "skills/multi/dingtalk-minutes/references/minutes.md": { - {40, 130}, {1280, 1320}, {1700, 1900}, - }, - } - // Verbs that imply writes; auto-append --dry-run. writeVerbs = map[string]bool{ "create": true, "update": true, "delete": true, "send": true, @@ -81,6 +78,16 @@ var ( "replace": true, "mute": true, "cancel": true, "install": true, "publish": true, "forward": true, "reply": true, } + + // Utility commands do not participate in the MCP --mock envelope path; + // Layer A still validates their Cobra registration. Executing them here can + // trigger local setup, authentication, upgrades, or long-running consumers. + mockUnsupportedRoots = map[string]bool{ + "api": true, "auth": true, "cache": true, "catalog": true, + "completion": true, "config": true, "doctor": true, "event": true, + "mcp": true, "plugin": true, "profile": true, "recovery": true, + "schema": true, "skill": true, "upgrade": true, "version": true, + } ) func repoRoot(t *testing.T) string { @@ -89,13 +96,49 @@ func repoRoot(t *testing.T) string { return filepath.Clean(filepath.Join(filepath.Dir(here), "..", "..")) } -func dwsBinary(t *testing.T) string { +func dwsBinary(t *testing.T, root string) string { t.Helper() - p, err := exec.LookPath("dws") - if err != nil { - t.Skipf("dws binary not on PATH; build it (go build -o /tmp/dws ./cmd && PATH=/tmp:$PATH) to enable this layer-B test") + if configured := os.Getenv("DWS_TEST_BINARY"); configured != "" { + p, err := filepath.Abs(configured) + if err != nil { + t.Fatalf("resolve DWS_TEST_BINARY: %v", err) + } + if info, err := os.Stat(p); err != nil || info.IsDir() { + t.Fatalf("DWS_TEST_BINARY must name an executable file: %s", p) + } + return p } - return p + name := "dws" + if runtime.GOOS == "windows" { + name += ".exe" + } + p := filepath.Join(root, name) + if info, err := os.Stat(p); err == nil && !info.IsDir() { + return p + } + t.Skipf("current-checkout dws binary not found; run `go build -o %s ./cmd` or set DWS_TEST_BINARY", name) + return "" +} + +func appendMultiReferenceMarkdown(files []string, multiRoot string) ([]string, error) { + err := filepath.WalkDir(multiRoot, func(path string, d fs.DirEntry, walkErr error) error { + if walkErr != nil { + return walkErr + } + if d.IsDir() || !strings.EqualFold(filepath.Ext(path), ".md") { + return nil + } + rel, err := filepath.Rel(multiRoot, path) + if err != nil { + return err + } + parts := strings.Split(filepath.ToSlash(rel), "/") + if len(parts) >= 3 && parts[1] == "references" { + files = append(files, path) + } + return nil + }) + return files, err } type cmdRef struct { @@ -111,7 +154,6 @@ func extract(root string) ([]cmdRef, error) { dir + "/mono/SKILL.md", dir + "/mono/references/products/*.md", dir + "/multi/*/SKILL.md", - dir + "/multi/*/references/*.md", } { m, err := filepath.Glob(pat) if err != nil { @@ -119,10 +161,15 @@ func extract(root string) ([]cmdRef, error) { } files = append(files, m...) } + var err error + files, err = appendMultiReferenceMarkdown(files, filepath.Join(dir, "multi")) + if err != nil { + return nil, err + } var refs []cmdRef for _, f := range files { - out, err := exec.Command("cat", f).Output() + out, err := os.ReadFile(f) if err != nil { return nil, err } @@ -133,7 +180,7 @@ func extract(root string) ([]cmdRef, error) { for scanner.Scan() { lineNo++ line := scanner.Text() - if isAntiLine(line) || inAntiRange(root, f, lineNo) { + if isAntiLine(line) { continue } for _, m := range backtickRe.FindAllStringSubmatch(line, -1) { @@ -157,21 +204,17 @@ func isAntiLine(line string) bool { return false } -func inAntiRange(root, file string, ln int) bool { - rel, _ := filepath.Rel(root, file) - ranges, ok := antiPatternRanges[rel] - if !ok { - return false - } - for _, r := range ranges { - if ln >= r[0] && ln <= r[1] { - return true - } - } - return false -} - func shouldSkip(cmd string) bool { + tokens := strings.Fields(cmd) + if len(tokens) >= 2 && mockUnsupportedRoots[tokens[1]] { + return true + } + if cmd == "dws sheet export" || strings.HasPrefix(cmd, "dws sheet export ") { + // The export helper performs its asynchronous polling loop even under + // --mock; Layer A validates its command path without making this audit + // wait for the production export timeout. + return true + } if strings.HasPrefix(cmd, "dws dev ") || cmd == "dws dev" { // dev (product id: devapp) is a target MCP-discovered command tree. Until the MCP product // is published into the open-source registry, validate its Agent routing @@ -278,7 +321,11 @@ func substitute(cmd string) string { func TestSkillCommandsMockDispatch(t *testing.T) { root := repoRoot(t) - dws := dwsBinary(t) + dws := dwsBinary(t, root) + t.Setenv("HOME", t.TempDir()) + t.Setenv("DWS_CONFIG_DIR", t.TempDir()) + t.Setenv("DWS_DISABLE_KEYCHAIN", "1") + t.Setenv("DWS_KEYCHAIN_DIR", t.TempDir()) refs, err := extract(root) if err != nil { @@ -325,12 +372,10 @@ func TestSkillCommandsMockDispatch(t *testing.T) { if isWrite(subPath(r.Cmd)) && !strings.Contains(actual, " --dry-run") { actual += " --dry-run" } - argv := strings.Fields(actual) - // shlex-light: rejoin quoted segments - argv = rejoinQuoted(actual) + argv := rejoinQuoted(actual) out, _ := exec.Command(dws, argv[1:]...).CombinedOutput() s := string(out) - if strings.Contains(s, "unknown flag") || strings.Contains(s, "unknown command") { + if strings.Contains(s, "unknown flag") || strings.Contains(s, "unknown command") || strings.Contains(s, "unknown subcommand") { mu.Lock() fails = append(fails, failure{ref: r, actual: actual, reason: firstLine(s, 200)}) mu.Unlock() @@ -352,7 +397,7 @@ func TestSkillCommandsMockDispatch(t *testing.T) { } if errBlock, ok := j["error"].(map[string]any); ok { msg, _ := errBlock["message"].(string) - if strings.Contains(msg, "unknown flag") || strings.Contains(msg, "unknown command") { + if strings.Contains(msg, "unknown flag") || strings.Contains(msg, "unknown command") || strings.Contains(msg, "unknown subcommand") { mu.Lock() fails = append(fails, failure{ref: r, actual: actual, reason: msg}) mu.Unlock() diff --git a/test/skill_static/skill_static_test.go b/test/skill_static/skill_static_test.go index 9423a7b3..c2d24433 100644 --- a/test/skill_static/skill_static_test.go +++ b/test/skill_static/skill_static_test.go @@ -3,17 +3,20 @@ // Package skill_static_test verifies that every `dws ...` command example // referenced in skill documentation can actually be dispatched by the -// open-source dws CLI binary (cobra Available Commands check). +// open-source dws CLI command tree. // -// Build tag rationale: this test depends on a real `dws` binary being -// reachable via PATH, which the default `go test ./...` doesn't guarantee. +// Build tag rationale: this is a repository-wide documentation audit and is +// intentionally opt-in rather than part of every package's unit-test loop. // Run explicitly with: `go test -tags skill_verify ./test/skill_static/...` // // Layer A of the skill verification matrix: // 1. Every backtick `dws ...` command in skills/**/*.md is parsed. // 2. For each, the sub-command path (tokens before first --flag) is -// checked against `dws --help`'s "Available Commands" list. -// 3. Every flag used must appear in the leaf command's help text. +// checked against the Cobra tree constructed from the current source. +// 3. Registered aliases are accepted; unknown descendants are rejected. +// 4. Flags in recursively discovered multi-skill docs are checked against +// the registered leaf command. Mono docs retain historical bad-case +// examples and are path-checked only. // // The test deliberately tolerates a small whitelist of intentional // anti-pattern references (e.g. "dws minutes detail" appearing in the @@ -24,13 +27,16 @@ package skill_static_test import ( "bufio" "fmt" - "os/exec" + "io/fs" + "os" "path/filepath" "regexp" "runtime" "strings" - "sync" "testing" + + "github.com/DingTalk-Real-AI/dingtalk-workspace-cli/internal/app" + "github.com/spf13/cobra" ) var ( @@ -61,13 +67,25 @@ func repoRoot(t *testing.T) string { return filepath.Clean(filepath.Join(filepath.Dir(here), "..", "..")) } -func dwsBinary(t *testing.T) string { - t.Helper() - p, err := exec.LookPath("dws") - if err != nil { - t.Skipf("dws binary not on PATH; install dws (or run `go build -o /tmp/dws ./cmd` and PATH=/tmp:$PATH) to enable this test") - } - return p +func appendMultiReferenceMarkdown(files []string, multiRoot string) ([]string, error) { + err := filepath.WalkDir(multiRoot, func(path string, d fs.DirEntry, walkErr error) error { + if walkErr != nil { + return walkErr + } + if d.IsDir() || !strings.EqualFold(filepath.Ext(path), ".md") { + return nil + } + rel, err := filepath.Rel(multiRoot, path) + if err != nil { + return err + } + parts := strings.Split(filepath.ToSlash(rel), "/") + if len(parts) >= 3 && parts[1] == "references" { + files = append(files, path) + } + return nil + }) + return files, err } type cmdRef struct { @@ -94,8 +112,8 @@ func extractCommands(root string) ([]cmdRef, error) { if more, err = filepath.Glob(skillsDir + "/multi/*/SKILL.md"); err == nil { files = append(files, more...) } - if more, err = filepath.Glob(skillsDir + "/multi/*/references/*.md"); err == nil { - files = append(files, more...) + if files, err = appendMultiReferenceMarkdown(files, filepath.Join(skillsDir, "multi")); err != nil { + return nil, err } for _, f := range files { data, err := readFile(f) @@ -121,7 +139,7 @@ func extractCommands(root string) ([]cmdRef, error) { } func readFile(p string) (string, error) { - b, err := exec.Command("cat", p).Output() + b, err := os.ReadFile(p) if err != nil { return "", err } @@ -226,69 +244,83 @@ func parseSubPath(cmd string) (subPath, leaf string, flags []string) { return subPath, leaf, flags } -// helpCache memoizes `dws --help` outputs. -type helpCache struct { - mu sync.Mutex - dws string - m map[string]string -} - -func newHelpCache(dws string) *helpCache { - return &helpCache{dws: dws, m: map[string]string{}} -} - -func (c *helpCache) get(sub string) string { - c.mu.Lock() - defer c.mu.Unlock() - if v, ok := c.m[sub]; ok { - return v - } - args := append(strings.Fields(sub)[1:], "--help") - out, _ := exec.Command(c.dws, args...).CombinedOutput() - c.m[sub] = string(out) - return string(out) -} - -var availableRe = regexp.MustCompile(`(?s)Available Commands:\n((?:\s+\S.*\n)+)`) - -// usagePathRe extracts the command path from cobra Usage line: -// -// "Usage:\n dws report inbox list [flags]" → "report inbox list" -// -// Used to detect top-level alias dispatch (envelope-declared cli.Aliases / -// cli.Prefixes[1:]): if the actual Usage path has the same token count as -// the documented sub but a different first token, sub is a registered alias. -var usagePathRe = regexp.MustCompile(`(?m)^Usage:\s*\n\s+dws\s+(.+?)(?:\s+\[flags\]|\s+\[command\]|\s*$)`) - -func extractUsagePath(helpText string) string { - m := usagePathRe.FindStringSubmatch(helpText) - if len(m) < 2 { - return "" - } - return strings.TrimSpace(m[1]) -} - -func parseAvailable(helpText string) []string { - m := availableRe.FindStringSubmatch(helpText) - if m == nil { - return nil - } - var cmds []string - for _, line := range strings.Split(m[1], "\n") { - fields := strings.Fields(line) - if len(fields) > 0 && !strings.HasPrefix(fields[0], "-") { - cmds = append(cmds, fields[0]) +// exactCommand walks the current source tree's Cobra command graph without +// executing a subprocess. Aliases are accepted because they dispatch to the +// same registered command at runtime. +func exactCommand(root *cobra.Command, sub string) *cobra.Command { + tokens := strings.Fields(strings.TrimPrefix(sub, "dws ")) + current := root + for i, token := range tokens { + var next *cobra.Command + for _, child := range current.Commands() { + if child.Name() == token || child.HasAlias(token) { + next = child + break + } } + if next == nil { + // Once a runnable command is reached, remaining non-flag tokens are + // positional arguments rather than descendants. Validate them with + // the command's Cobra Args contract when one is present. + if current.Runnable() { + args := tokens[i:] + if current.Args == nil || current.Args(current, args) == nil { + return current + } + } + return nil + } + current = next } - return cmds + return current +} + +func commandHasFlag(command *cobra.Command, flag string) bool { + flag = strings.TrimLeft(flag, "-") + if flag == "" { + return false + } + command.InitDefaultHelpFlag() + if command.Flags().Lookup(flag) != nil || command.PersistentFlags().Lookup(flag) != nil || command.InheritedFlags().Lookup(flag) != nil { + return true + } + if len(flag) == 1 { + return command.Flags().ShorthandLookup(flag) != nil || command.PersistentFlags().ShorthandLookup(flag) != nil || command.InheritedFlags().ShorthandLookup(flag) != nil + } + return false +} + +func availableCommands(root *cobra.Command, sub string) []string { + tokens := strings.Fields(strings.TrimPrefix(sub, "dws ")) + if len(tokens) > 0 { + tokens = tokens[:len(tokens)-1] + } + parent := root + for _, token := range tokens { + next := exactCommand(parent, "dws "+token) + if next == nil { + return nil + } + parent = next + } + var names []string + for _, child := range parent.Commands() { + names = append(names, child.Name()) + } + return names } func TestSkillCommandsDispatch(t *testing.T) { - root := repoRoot(t) - dws := dwsBinary(t) - cache := newHelpCache(dws) + repo := repoRoot(t) + // Keep command-tree construction isolated from the developer's plugins and + // macOS Keychain while validating the open-source surface. + t.Setenv("HOME", t.TempDir()) + t.Setenv("DWS_CONFIG_DIR", t.TempDir()) + t.Setenv("DWS_DISABLE_KEYCHAIN", "1") + t.Setenv("DWS_KEYCHAIN_DIR", t.TempDir()) + commandRoot := app.NewRootCommand() - refs, err := extractCommands(root) + refs, err := extractCommands(repo) if err != nil { t.Fatalf("extract: %v", err) } @@ -299,73 +331,43 @@ func TestSkillCommandsDispatch(t *testing.T) { reason string } var fails []failure - seen := map[string]bool{} // dedupe identical sub-paths + seen := map[string]bool{} // dedupe identical sub-path and flag sets for _, r := range refs { - sub, _, _ := parseSubPath(r.Cmd) + sub, _, flags := parseSubPath(r.Cmd) if sub == "" { continue } if antiPatternAllowlist[sub] { continue } - if seen[sub+"|"+r.File] { + key := sub + "|" + strings.Join(flags, ",") + "|" + r.File + if seen[key] { + continue + } + seen[key] = true + if command := exactCommand(commandRoot, sub); command != nil { + var unknownFlags []string + if strings.Contains(filepath.ToSlash(r.File), "/skills/multi/") { + for _, flag := range flags { + if !commandHasFlag(command, flag) { + unknownFlags = append(unknownFlags, flag) + } + } + } + if len(unknownFlags) > 0 { + fails = append(fails, failure{ref: r, reason: fmt.Sprintf("unknown flags for `%s`: %v", sub, unknownFlags)}) + } continue } - seen[sub+"|"+r.File] = true - - // Compute parent + leaf tokens := strings.Fields(sub) if len(tokens) < 2 { continue } parent := strings.Join(tokens[:len(tokens)-1], " ") leaf := tokens[len(tokens)-1] - - // Strategy: directly check sub's own help; if its Usage line starts - // with the sub-path, the sub-command is registered. Otherwise cobra - // fell back to a parent and the sub doesn't exist. - subHelp := cache.get(sub) - expectedUsage := "Usage:\n " + sub - if strings.Contains(subHelp, expectedUsage) { - continue // sub-path exists, all good - } - - // Sub may be a leaf with subcommands list — also valid (e.g. dws sheet) - // In that case Usage line is "Usage:\n [flags]\n [command]" - if strings.Contains(subHelp, "Usage:\n "+sub+" [flags]\n "+sub+" [command]") { - continue - } - - // Top-level alias rewrite: envelope-declared aliases (e.g. report.aliases=["log"], - // im.prefixes=["chat","im"]) make `dws log inbox list` dispatch to `dws report - // inbox list`. Cobra prints the canonical path in Usage. If the Usage path has - // the same token count as sub (minus the "dws" prefix) but a different first - // token, sub is a registered alias path — treat as valid. - if actualPath := extractUsagePath(subHelp); actualPath != "" { - actualTokens := strings.Fields(actualPath) - subTokens := strings.Fields(sub) - if len(subTokens) > 0 && subTokens[0] == "dws" { - subTokens = subTokens[1:] - } - if len(actualTokens) == len(subTokens) && len(actualTokens) > 0 && actualTokens[0] != subTokens[0] { - continue // alias dispatched cleanly to canonical product - } - } - - // Otherwise verify by looking at parent's Available Commands - parentHelp := cache.get(parent) - available := parseAvailable(parentHelp) - found := false - for _, c := range available { - if c == leaf { - found = true - break - } - } - if !found { - fails = append(fails, failure{ref: r, reason: fmt.Sprintf("`%s` not in Available Commands of `%s`: %v", leaf, parent, available)}) - } + available := availableCommands(commandRoot, sub) + fails = append(fails, failure{ref: r, reason: fmt.Sprintf("`%s` not in Available Commands of `%s`: %v", leaf, parent, available)}) } if len(fails) == 0 { @@ -375,9 +377,9 @@ func TestSkillCommandsDispatch(t *testing.T) { // Format failure report var sb strings.Builder - sb.WriteString(fmt.Sprintf("Found %d skill-doc commands that don't dispatch in open-source dws v1.0.30:\n\n", len(fails))) + sb.WriteString(fmt.Sprintf("Found %d skill-doc commands that don't dispatch in the current open-source dws command tree:\n\n", len(fails))) for _, f := range fails { - rel, _ := filepath.Rel(root, f.ref.File) + rel, _ := filepath.Rel(repo, f.ref.File) sb.WriteString(fmt.Sprintf(" ❌ %s:%d\n cmd: %s\n reason: %s\n\n", rel, f.ref.Line, f.ref.Cmd, f.reason)) } t.Fatal(sb.String())