From dc20ddecf68c25840ca3c2dc3fa5961ee0d7ed7a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=8E=89=E6=BE=9C?= Date: Tue, 28 Jul 2026 20:20:12 +0800 Subject: [PATCH] feat(skills): sync wukong 13-sub-skill multi layout with open-source cleanup Replace skills/multi with wukong's consolidated structure (long-tail products folded into dingtalk-misc), keeping GitHub-only skills (dingtalk-dev/event/pat/profile/skill). Prune MCP-only product refs and align all documented commands/flags with the open-source Cobra tree: remove markdown/*, drive task get, drive version flags, doc read --scope, --async modes, retired conference/chat-file-upload mentions. --- skills/multi/dingtalk-agoal/SKILL.md | 41 - skills/multi/dingtalk-aisearch/SKILL.md | 29 +- .../dingtalk-aisearch/references/aisearch.md | 11 +- .../references/intent-guide.md | 7 +- .../references/lite-recipes.md | 5 +- skills/multi/dingtalk-aitable/SKILL.md | 26 +- .../references/_common/conventions.md | 8 +- .../references/aitable-record-ops.md | 10 +- .../dingtalk-aitable/references/aitable.md | 499 +++- .../aitable/aitable-advperm-index.md | 6 - .../aitable/aitable-attachment-index.md | 5 - .../references/aitable/aitable-base-index.md | 10 - .../aitable/aitable-best-practices.md | 4 +- .../references/aitable/aitable-cell-value.md | 2 +- .../aitable/aitable-command-index.md | 275 -- .../references/aitable/aitable-complex-ops.md | 40 - .../references/aitable/aitable-context.md | 13 - .../aitable/aitable-core-workflow.md | 21 - .../aitable/aitable-dashboard-index.md | 6 - .../aitable/aitable-error-recovery.md | 6 +- .../aitable/aitable-export-import-index.md | 6 - .../aitable/aitable-export-import.md | 14 +- .../references/aitable/aitable-field-index.md | 9 - .../references/aitable/aitable-field.md | 30 +- .../references/aitable/aitable-filter-sort.md | 2 +- .../references/aitable/aitable-form-index.md | 8 - .../references/aitable/aitable-intent.md | 95 - .../references/aitable/aitable-notes.md | 8 - .../references/aitable/aitable-primary-doc.md | 6 +- .../aitable/aitable-record-create.md | 10 +- .../aitable/aitable-record-index.md | 13 - .../aitable/aitable-record-update.md | 36 +- .../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 | 29 +- .../references/aitable/aitable-view-index.md | 6 - .../aitable/aitable-workflow-index.md | 6 - .../references/aitable/aitable-workflow.md | 84 +- .../references/field-rules.md | 4 +- .../references/url-patterns.md | 47 +- .../scripts/aitable_export_via_task.py | 6 +- .../scripts/bulk_add_fields.py | 2 +- .../scripts/import_records.py | 2 +- skills/multi/dingtalk-attendance/SKILL.md | 81 - .../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-routing.md | 55 - .../references/attendance-scripts.md | 21 - .../references/attendance-workflow.md | 148 - .../references/attendance.md | 11 - skills/multi/dingtalk-calendar/SKILL.md | 27 +- .../references/03-meeting.md | 39 +- .../references/calendar-index.md | 7 - .../dingtalk-calendar/references/calendar.md | 40 +- .../references/intent-guide.md | 3 +- .../references/lite-recipes.md | 6 - .../scripts/calendar_free_slot_finder.py | 60 +- .../scripts/calendar_schedule_meeting.py | 1 + skills/multi/dingtalk-chat/SKILL.md | 54 +- .../dingtalk-chat/references/01-messaging.md | 5 +- .../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 | 483 ---- .../dingtalk-chat/references/chat-index.md | 13 - .../dingtalk-chat/references/chat-intent.md | 163 -- .../references/chat-message-file-commands.md | 610 ---- .../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 | 348 ++- .../dingtalk-chat/references/chat/chat-bot.md | 139 + .../references/chat/chat-conversation.md | 140 + .../references/chat/chat-group.md | 225 ++ .../references/chat/chat-message.md | 269 ++ .../references/chat/chat-workflows.md | 179 ++ .../dingtalk-chat/references/intent-guide.md | 7 +- .../dingtalk-chat/references/lite-recipes.md | 2 +- .../scripts/chat_export_messages.py | 44 +- .../scripts/chat_history_with_user.py | 44 +- skills/multi/dingtalk-contact/SKILL.md | 52 +- .../references/08-directory.md | 34 +- .../references/_common/conventions.md | 8 +- .../references/contact-index.md | 6 - .../dingtalk-contact/references/contact.md | 178 +- .../references/intent-guide.md | 7 +- .../scripts/contact_dept_members.py | 34 +- skills/multi/dingtalk-devdoc/SKILL.md | 38 - skills/multi/dingtalk-ding/SKILL.md | 56 - skills/multi/dingtalk-doc/SKILL.md | 82 +- .../dingtalk-doc/references/04-document.md | 29 +- .../references/_common/conventions.md | 8 +- skills/multi/dingtalk-doc/references/doc.md | 498 +++- .../dingtalk-doc/references/doc/doc-block.md | 9 +- .../references/doc/doc-command-index.md | 167 -- .../references/doc/doc-comment.md | 77 +- .../references/doc/doc-context.md | 25 - .../dingtalk-doc/references/doc/doc-create.md | 12 +- .../dingtalk-doc/references/doc/doc-export.md | 24 +- .../references/doc/doc-extra-index.md | 7 - .../references/doc/doc-file-ops.md | 225 -- .../references/doc/doc-help-query.md | 25 - .../dingtalk-doc/references/doc/doc-import.md | 37 +- .../references/doc/doc-info-index.md | 7 - .../dingtalk-doc/references/doc/doc-info.md | 34 +- .../dingtalk-doc/references/doc/doc-intent.md | 136 - .../dingtalk-doc/references/doc/doc-list.md | 24 - .../references/doc/doc-media-index.md | 7 - .../dingtalk-doc/references/doc/doc-media.md | 4 + .../references/doc/doc-permission.md | 113 - .../references/doc/doc-prerequisites.md | 15 - .../dingtalk-doc/references/doc/doc-read.md | 9 +- .../references/doc/doc-related.md | 8 - .../references/doc/doc-scenarios.md | 22 - .../dingtalk-doc/references/doc/doc-search.md | 24 - .../references/doc/doc-template.md | 54 - .../dingtalk-doc/references/doc/doc-update.md | 7 +- .../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/format/doc-jsonml-schema.md | 4 +- .../doc/style/doc-create-workflow.md | 2 +- .../references/format/doc-jsonml-index.md | 9 - .../dingtalk-doc/references/intent-guide.md | 4 +- .../dingtalk-doc/references/lite-recipes.md | 13 +- .../dingtalk-doc/references/url-patterns.md | 83 +- skills/multi/dingtalk-drive/SKILL.md | 61 +- .../dingtalk-drive/references/drive-index.md | 5 - .../multi/dingtalk-drive/references/drive.md | 394 ++- .../dingtalk-drive/references/intent-guide.md | 2 +- .../dingtalk-drive/references/lite-recipes.md | 7 +- .../dingtalk-drive/scripts/drive_tree_list.py | 30 +- skills/multi/dingtalk-live/SKILL.md | 29 - skills/multi/dingtalk-mail/SKILL.md | 29 +- .../multi/dingtalk-mail/references/09-mail.md | 10 +- .../references/_common/conventions.md | 8 +- .../dingtalk-mail/references/lite-recipes.md | 6 +- .../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 | 1893 ++++++++++++- .../scripts/mail_send_with_cc.py | 25 +- .../scripts/mail_unread_summary.py | 26 +- skills/multi/dingtalk-minutes/SKILL.md | 27 +- .../dingtalk-minutes/references/07-minutes.md | 180 +- .../references/10-minutes-speaker-match.md | 4 +- .../references/11-minutes-speaker-correct.md | 8 +- .../references/_common/conventions.md | 8 +- .../references/lite-recipes.md | 164 +- .../references/minutes-commands.md | 877 ------ .../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 | 2522 ++++++++++++++++- .../scripts/minutes_extract_todos.py | 62 +- .../scripts/minutes_list_parse.py | 6 +- .../scripts/minutes_recent_summary.py | 13 +- skills/multi/dingtalk-misc/SKILL.md | 38 + .../references/05-reporting.md | 12 +- .../references/agoal.md | 22 + .../references/attendance-intent-guide.md} | 0 .../references/attendance-report.md | 8 +- .../references/attendance-schedule.md | 95 +- .../references/attendance-vacation.md | 4 +- .../references/attendance.md} | 519 +++- .../multi/dingtalk-misc/references/devapp.md | 29 + .../references/devdoc-intent-guide.md} | 0 .../references/devdoc.md | 19 + .../references/ding-intent-guide.md} | 0 .../references/ding-lite-recipes.md} | 2 +- .../references/ding.md | 52 +- .../references/live.md | 10 + .../references/oa.md | 54 +- .../references/report-conventions.md} | 8 +- .../references/report-intent-guide.md} | 0 .../references/report-lite-recipes.md} | 0 .../references/report.md | 62 +- .../references/sheet-conventions.md | 44 + .../references/sheet-intent-guide.md} | 2 +- .../multi/dingtalk-misc/references/sheet.md | 263 ++ .../sheet/sheet-batch-operations.md | 23 +- .../references/sheet/sheet-chart.md | 0 .../references/sheet/sheet-comment.md | 166 ++ .../sheet/sheet-conditional-format.md | 0 .../sheet/sheet-dimension-operations.md | 2 +- .../references/sheet/sheet-dropdown.md | 0 .../references/sheet/sheet-export.md | 9 + .../references/sheet/sheet-filter-view.md | 0 .../references/sheet/sheet-filter.md | 0 .../references/sheet/sheet-formula.md | 189 ++ .../references/sheet/sheet-import.md | 115 + .../references/sheet/sheet-media-image.md | 0 .../references/sheet/sheet-pivot-table.md | 2 +- .../sheet/sheet-range-operations.md | 0 .../references/sheet/sheet-read-data.md | 71 +- .../references/sheet/sheet-search-replace.md | 20 +- .../references/sheet/sheet-style-format.md | 0 .../references/sheet/sheet-version.md | 69 + .../references/sheet/sheet-workbook.md | 24 +- .../references/sheet/sheet-write-data.md | 193 +- .../references/url-patterns.md | 47 +- .../scripts/aiapp_create_and_poll.py | 138 + .../scripts/attendance_my_record.py | 17 +- .../scripts/attendance_report_checkin.py | 0 .../scripts/attendance_report_common.py | 21 +- .../scripts/attendance_report_daily.py | 0 .../scripts/attendance_report_detail.py | 10 +- .../scripts/attendance_report_monthly.py | 0 .../scripts/attendance_report_record.py | 0 .../scripts/attendance_schedule_export.py | 2 +- .../scripts/attendance_schedule_import.py | 6 +- .../scripts/attendance_team_shift.py | 0 .../scripts/attendance_vacation_balance.py | 0 .../scripts/finance_daily_cashflow.py | 70 + .../scripts/finance_expense_flow.py | 138 + .../scripts/oa_batch_approve.py | 0 .../scripts/oa_pending_review.py | 0 .../scripts/report_received_today.py | 148 + .../scripts/yida_custom_page_update.py | 401 +++ .../scripts/yida_form_builder.py | 526 ++++ .../dingtalk-misc/scripts/yida_form_fields.py | 315 ++ .../scripts/yida_form_inspector.py | 311 ++ .../dingtalk-misc/scripts/yida_form_update.py | 185 ++ .../scripts/yida_jsx_pipeline.py | 1588 +++++++++++ .../scripts/yida_page_compiler.py | 1441 ++++++++++ .../scripts/yida_page_generate.py | 514 ++++ .../dingtalk-misc/scripts/yida_page_schema.py | 519 ++++ .../scripts/yida_page_self_check.py | 398 +++ .../scripts/yida_process_flow.py | 1478 ++++++++++ .../scripts/yida_process_update.py | 589 ++++ .../scripts/yida_report_builder.py | 494 ++++ .../scripts/yida_report_charts.py | 1434 ++++++++++ .../scripts/yida_report_update.py | 244 ++ .../scripts/yida_schema_common.py | 340 +++ skills/multi/dingtalk-oa/SKILL.md | 59 - .../multi/dingtalk-oa/references/oa-index.md | 5 - skills/multi/dingtalk-report/SKILL.md | 79 - .../references/report-index.md | 6 - .../scripts/report_received_today.py | 167 -- skills/multi/dingtalk-sheet/SKILL.md | 82 - .../multi/dingtalk-sheet/references/sheet.md | 19 - .../references/sheet/sheet-capabilities.md | 7 - .../references/sheet/sheet-edit-rules.md | 12 - .../references/sheet/sheet-export-index.md | 8 - .../references/sheet/sheet-format-index.md | 7 - .../references/sheet/sheet-formula.md | 117 - .../sheet/sheet-hard-constraints.md | 21 - .../references/sheet/sheet-import.md | 76 - .../references/sheet/sheet-nodeid.md | 5 - .../references/sheet/sheet-query-index.md | 6 - .../references/sheet/sheet-quick-ref.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-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-write-index.md | 7 - skills/multi/dingtalk-todo/SKILL.md | 29 +- .../multi/dingtalk-todo/references/02-task.md | 8 +- .../references/_common/conventions.md | 8 +- .../dingtalk-todo/references/lite-recipes.md | 154 +- .../dingtalk-todo/references/todo-index.md | 6 - skills/multi/dingtalk-todo/references/todo.md | 76 + skills/multi/dingtalk-wiki/SKILL.md | 43 +- .../dingtalk-wiki/references/lite-recipes.md | 6 +- .../dingtalk-wiki/references/wiki-index.md | 5 - skills/multi/dws-shared/SKILL.md | 128 +- .../best_practices/_common/conventions.md | 8 +- .../best_practices/_common/lite-recipes.md | 275 ++ .../references/capability-limits.md | 12 + .../dws-shared/references/error-codes.md | 8 +- .../dws-shared/references/field-rules.md | 104 - .../dws-shared/references/intent-guide.md | 87 +- skills/multi/dws-shared/references/routing.md | 47 + .../dws-shared/references/shared-index.md | 12 - .../dws-shared/references/url-patterns.md | 49 +- .../dws-shared/references/workflow-routing.md | 45 + 303 files changed, 22316 insertions(+), 10413 deletions(-) delete mode 100644 skills/multi/dingtalk-agoal/SKILL.md delete mode 100644 skills/multi/dingtalk-aitable/references/aitable/aitable-advperm-index.md delete mode 100644 skills/multi/dingtalk-aitable/references/aitable/aitable-attachment-index.md delete mode 100644 skills/multi/dingtalk-aitable/references/aitable/aitable-base-index.md delete mode 100644 skills/multi/dingtalk-aitable/references/aitable/aitable-command-index.md delete mode 100644 skills/multi/dingtalk-aitable/references/aitable/aitable-complex-ops.md delete mode 100644 skills/multi/dingtalk-aitable/references/aitable/aitable-context.md delete mode 100644 skills/multi/dingtalk-aitable/references/aitable/aitable-core-workflow.md delete mode 100644 skills/multi/dingtalk-aitable/references/aitable/aitable-dashboard-index.md delete mode 100644 skills/multi/dingtalk-aitable/references/aitable/aitable-export-import-index.md delete mode 100644 skills/multi/dingtalk-aitable/references/aitable/aitable-field-index.md delete mode 100644 skills/multi/dingtalk-aitable/references/aitable/aitable-form-index.md delete mode 100644 skills/multi/dingtalk-aitable/references/aitable/aitable-intent.md delete mode 100644 skills/multi/dingtalk-aitable/references/aitable/aitable-notes.md delete mode 100644 skills/multi/dingtalk-aitable/references/aitable/aitable-record-index.md delete mode 100644 skills/multi/dingtalk-aitable/references/aitable/aitable-related.md delete mode 100644 skills/multi/dingtalk-aitable/references/aitable/aitable-scripts.md delete mode 100644 skills/multi/dingtalk-aitable/references/aitable/aitable-table-index.md delete mode 100644 skills/multi/dingtalk-aitable/references/aitable/aitable-uri.md delete mode 100644 skills/multi/dingtalk-aitable/references/aitable/aitable-url-to-baseid.md delete mode 100644 skills/multi/dingtalk-aitable/references/aitable/aitable-view-index.md delete mode 100644 skills/multi/dingtalk-aitable/references/aitable/aitable-workflow-index.md delete mode 100644 skills/multi/dingtalk-attendance/SKILL.md delete mode 100644 skills/multi/dingtalk-attendance/references/attendance-constraints.md delete mode 100644 skills/multi/dingtalk-attendance/references/attendance-context.md delete mode 100644 skills/multi/dingtalk-attendance/references/attendance-index.md delete mode 100644 skills/multi/dingtalk-attendance/references/attendance-intent.md delete mode 100644 skills/multi/dingtalk-attendance/references/attendance-notes.md delete mode 100644 skills/multi/dingtalk-attendance/references/attendance-routing.md delete mode 100644 skills/multi/dingtalk-attendance/references/attendance-scripts.md delete mode 100644 skills/multi/dingtalk-attendance/references/attendance-workflow.md delete mode 100644 skills/multi/dingtalk-attendance/references/attendance.md delete mode 100644 skills/multi/dingtalk-calendar/references/calendar-index.md delete mode 100644 skills/multi/dingtalk-chat/references/chat-admin-commands.md delete mode 100644 skills/multi/dingtalk-chat/references/chat-commands.md delete mode 100644 skills/multi/dingtalk-chat/references/chat-composite.md delete mode 100644 skills/multi/dingtalk-chat/references/chat-context.md delete mode 100644 skills/multi/dingtalk-chat/references/chat-conversation-commands.md delete mode 100644 skills/multi/dingtalk-chat/references/chat-group-commands.md delete mode 100644 skills/multi/dingtalk-chat/references/chat-index.md delete mode 100644 skills/multi/dingtalk-chat/references/chat-intent.md delete mode 100644 skills/multi/dingtalk-chat/references/chat-message-file-commands.md delete mode 100644 skills/multi/dingtalk-chat/references/chat-notes.md delete mode 100644 skills/multi/dingtalk-chat/references/chat-related.md delete mode 100644 skills/multi/dingtalk-chat/references/chat-scripts.md delete mode 100644 skills/multi/dingtalk-chat/references/chat-workflow.md create mode 100644 skills/multi/dingtalk-chat/references/chat/chat-bot.md create mode 100644 skills/multi/dingtalk-chat/references/chat/chat-conversation.md create mode 100644 skills/multi/dingtalk-chat/references/chat/chat-group.md create mode 100644 skills/multi/dingtalk-chat/references/chat/chat-message.md create mode 100644 skills/multi/dingtalk-chat/references/chat/chat-workflows.md delete mode 100644 skills/multi/dingtalk-contact/references/contact-index.md delete mode 100644 skills/multi/dingtalk-devdoc/SKILL.md delete mode 100644 skills/multi/dingtalk-ding/SKILL.md delete mode 100644 skills/multi/dingtalk-doc/references/doc/doc-command-index.md delete mode 100644 skills/multi/dingtalk-doc/references/doc/doc-context.md delete mode 100644 skills/multi/dingtalk-doc/references/doc/doc-extra-index.md delete mode 100644 skills/multi/dingtalk-doc/references/doc/doc-file-ops.md delete mode 100644 skills/multi/dingtalk-doc/references/doc/doc-help-query.md delete mode 100644 skills/multi/dingtalk-doc/references/doc/doc-info-index.md delete mode 100644 skills/multi/dingtalk-doc/references/doc/doc-intent.md delete mode 100644 skills/multi/dingtalk-doc/references/doc/doc-list.md delete mode 100644 skills/multi/dingtalk-doc/references/doc/doc-media-index.md delete mode 100644 skills/multi/dingtalk-doc/references/doc/doc-permission.md delete mode 100644 skills/multi/dingtalk-doc/references/doc/doc-prerequisites.md delete mode 100644 skills/multi/dingtalk-doc/references/doc/doc-related.md delete mode 100644 skills/multi/dingtalk-doc/references/doc/doc-scenarios.md delete mode 100644 skills/multi/dingtalk-doc/references/doc/doc-search.md delete mode 100644 skills/multi/dingtalk-doc/references/doc/doc-template.md delete mode 100644 skills/multi/dingtalk-doc/references/doc/doc-uri.md delete mode 100644 skills/multi/dingtalk-doc/references/doc/doc-version.md delete mode 100644 skills/multi/dingtalk-doc/references/doc/doc-workflow.md delete mode 100644 skills/multi/dingtalk-doc/references/doc/doc-write-index.md delete mode 100644 skills/multi/dingtalk-doc/references/format/doc-jsonml-index.md delete mode 100644 skills/multi/dingtalk-drive/references/drive-index.md delete mode 100644 skills/multi/dingtalk-live/SKILL.md delete mode 100644 skills/multi/dingtalk-mail/references/mail-command-index.md delete mode 100644 skills/multi/dingtalk-mail/references/mail-common-errors.md delete mode 100644 skills/multi/dingtalk-mail/references/mail-context.md delete mode 100644 skills/multi/dingtalk-mail/references/mail-default-mailbox-rules.md delete mode 100644 skills/multi/dingtalk-mail/references/mail-draft-directory-commands.md delete mode 100644 skills/multi/dingtalk-mail/references/mail-folder-attachment-tag-commands.md delete mode 100644 skills/multi/dingtalk-mail/references/mail-index.md delete mode 100644 skills/multi/dingtalk-mail/references/mail-intent.md delete mode 100644 skills/multi/dingtalk-mail/references/mail-message-commands.md delete mode 100644 skills/multi/dingtalk-mail/references/mail-never-do.md delete mode 100644 skills/multi/dingtalk-mail/references/mail-notes.md delete mode 100644 skills/multi/dingtalk-mail/references/mail-policy-commands.md delete mode 100644 skills/multi/dingtalk-mail/references/mail-thread-actions.md delete mode 100644 skills/multi/dingtalk-mail/references/mail-workflow.md rename skills/multi/{dingtalk-sheet => dingtalk-minutes}/references/_common/conventions.md (81%) delete mode 100644 skills/multi/dingtalk-minutes/references/minutes-commands.md delete mode 100644 skills/multi/dingtalk-minutes/references/minutes-error.md delete mode 100644 skills/multi/dingtalk-minutes/references/minutes-index.md delete mode 100644 skills/multi/dingtalk-minutes/references/minutes-intent.md delete mode 100644 skills/multi/dingtalk-minutes/references/minutes-overview.md delete mode 100644 skills/multi/dingtalk-minutes/references/minutes-workflow.md create mode 100644 skills/multi/dingtalk-misc/SKILL.md rename skills/multi/{dingtalk-report => dingtalk-misc}/references/05-reporting.md (57%) rename skills/multi/{dingtalk-agoal => dingtalk-misc}/references/agoal.md (92%) rename skills/multi/{dingtalk-attendance/references/intent-guide.md => dingtalk-misc/references/attendance-intent-guide.md} (100%) rename skills/multi/{dingtalk-attendance => dingtalk-misc}/references/attendance-report.md (98%) rename skills/multi/{dingtalk-attendance => dingtalk-misc}/references/attendance-schedule.md (85%) rename skills/multi/{dingtalk-attendance => dingtalk-misc}/references/attendance-vacation.md (98%) rename skills/multi/{dingtalk-attendance/references/attendance-commands.md => dingtalk-misc/references/attendance.md} (60%) create mode 100644 skills/multi/dingtalk-misc/references/devapp.md rename skills/multi/{dingtalk-devdoc/references/intent-guide.md => dingtalk-misc/references/devdoc-intent-guide.md} (100%) rename skills/multi/{dingtalk-devdoc => dingtalk-misc}/references/devdoc.md (53%) rename skills/multi/{dingtalk-ding/references/intent-guide.md => dingtalk-misc/references/ding-intent-guide.md} (100%) rename skills/multi/{dingtalk-ding/references/lite-recipes.md => dingtalk-misc/references/ding-lite-recipes.md} (88%) rename skills/multi/{dingtalk-ding => dingtalk-misc}/references/ding.md (74%) rename skills/multi/{dingtalk-live => dingtalk-misc}/references/live.md (54%) rename skills/multi/{dingtalk-oa => dingtalk-misc}/references/oa.md (90%) rename skills/multi/{dingtalk-report/references/_common/conventions.md => dingtalk-misc/references/report-conventions.md} (81%) rename skills/multi/{dingtalk-report/references/intent-guide.md => dingtalk-misc/references/report-intent-guide.md} (100%) rename skills/multi/{dingtalk-report/references/lite-recipes.md => dingtalk-misc/references/report-lite-recipes.md} (100%) rename skills/multi/{dingtalk-report => dingtalk-misc}/references/report.md (86%) create mode 100644 skills/multi/dingtalk-misc/references/sheet-conventions.md rename skills/multi/{dingtalk-sheet/references/intent-guide.md => dingtalk-misc/references/sheet-intent-guide.md} (86%) create mode 100644 skills/multi/dingtalk-misc/references/sheet.md rename skills/multi/{dingtalk-sheet => dingtalk-misc}/references/sheet/sheet-batch-operations.md (91%) rename skills/multi/{dingtalk-sheet => dingtalk-misc}/references/sheet/sheet-chart.md (100%) create mode 100644 skills/multi/dingtalk-misc/references/sheet/sheet-comment.md rename skills/multi/{dingtalk-sheet => dingtalk-misc}/references/sheet/sheet-conditional-format.md (100%) rename skills/multi/{dingtalk-sheet => dingtalk-misc}/references/sheet/sheet-dimension-operations.md (99%) rename skills/multi/{dingtalk-sheet => dingtalk-misc}/references/sheet/sheet-dropdown.md (100%) rename skills/multi/{dingtalk-sheet => dingtalk-misc}/references/sheet/sheet-export.md (93%) rename skills/multi/{dingtalk-sheet => dingtalk-misc}/references/sheet/sheet-filter-view.md (100%) rename skills/multi/{dingtalk-sheet => dingtalk-misc}/references/sheet/sheet-filter.md (100%) create mode 100644 skills/multi/dingtalk-misc/references/sheet/sheet-formula.md create mode 100644 skills/multi/dingtalk-misc/references/sheet/sheet-import.md rename skills/multi/{dingtalk-sheet => dingtalk-misc}/references/sheet/sheet-media-image.md (100%) rename skills/multi/{dingtalk-sheet => dingtalk-misc}/references/sheet/sheet-pivot-table.md (99%) rename skills/multi/{dingtalk-sheet => dingtalk-misc}/references/sheet/sheet-range-operations.md (100%) rename skills/multi/{dingtalk-sheet => dingtalk-misc}/references/sheet/sheet-read-data.md (75%) rename skills/multi/{dingtalk-sheet => dingtalk-misc}/references/sheet/sheet-search-replace.md (84%) rename skills/multi/{dingtalk-sheet => dingtalk-misc}/references/sheet/sheet-style-format.md (100%) create mode 100644 skills/multi/dingtalk-misc/references/sheet/sheet-version.md rename skills/multi/{dingtalk-sheet => dingtalk-misc}/references/sheet/sheet-workbook.md (88%) rename skills/multi/{dingtalk-sheet => dingtalk-misc}/references/sheet/sheet-write-data.md (73%) rename skills/multi/{dingtalk-sheet => dingtalk-misc}/references/url-patterns.md (78%) create mode 100644 skills/multi/dingtalk-misc/scripts/aiapp_create_and_poll.py rename skills/multi/{dingtalk-attendance => dingtalk-misc}/scripts/attendance_my_record.py (81%) rename skills/multi/{dingtalk-attendance => dingtalk-misc}/scripts/attendance_report_checkin.py (100%) rename skills/multi/{dingtalk-attendance => dingtalk-misc}/scripts/attendance_report_common.py (98%) rename skills/multi/{dingtalk-attendance => dingtalk-misc}/scripts/attendance_report_daily.py (100%) rename skills/multi/{dingtalk-attendance => dingtalk-misc}/scripts/attendance_report_detail.py (99%) rename skills/multi/{dingtalk-attendance => dingtalk-misc}/scripts/attendance_report_monthly.py (100%) rename skills/multi/{dingtalk-attendance => dingtalk-misc}/scripts/attendance_report_record.py (100%) rename skills/multi/{dingtalk-attendance => dingtalk-misc}/scripts/attendance_schedule_export.py (99%) rename skills/multi/{dingtalk-attendance => dingtalk-misc}/scripts/attendance_schedule_import.py (99%) rename skills/multi/{dingtalk-attendance => dingtalk-misc}/scripts/attendance_team_shift.py (100%) rename skills/multi/{dingtalk-attendance => dingtalk-misc}/scripts/attendance_vacation_balance.py (100%) create mode 100644 skills/multi/dingtalk-misc/scripts/finance_daily_cashflow.py create mode 100644 skills/multi/dingtalk-misc/scripts/finance_expense_flow.py rename skills/multi/{dingtalk-oa => dingtalk-misc}/scripts/oa_batch_approve.py (100%) rename skills/multi/{dingtalk-oa => dingtalk-misc}/scripts/oa_pending_review.py (100%) create mode 100644 skills/multi/dingtalk-misc/scripts/report_received_today.py create mode 100644 skills/multi/dingtalk-misc/scripts/yida_custom_page_update.py create mode 100644 skills/multi/dingtalk-misc/scripts/yida_form_builder.py create mode 100644 skills/multi/dingtalk-misc/scripts/yida_form_fields.py create mode 100644 skills/multi/dingtalk-misc/scripts/yida_form_inspector.py create mode 100644 skills/multi/dingtalk-misc/scripts/yida_form_update.py create mode 100644 skills/multi/dingtalk-misc/scripts/yida_jsx_pipeline.py create mode 100644 skills/multi/dingtalk-misc/scripts/yida_page_compiler.py create mode 100644 skills/multi/dingtalk-misc/scripts/yida_page_generate.py create mode 100644 skills/multi/dingtalk-misc/scripts/yida_page_schema.py create mode 100644 skills/multi/dingtalk-misc/scripts/yida_page_self_check.py create mode 100644 skills/multi/dingtalk-misc/scripts/yida_process_flow.py create mode 100644 skills/multi/dingtalk-misc/scripts/yida_process_update.py create mode 100644 skills/multi/dingtalk-misc/scripts/yida_report_builder.py create mode 100644 skills/multi/dingtalk-misc/scripts/yida_report_charts.py create mode 100644 skills/multi/dingtalk-misc/scripts/yida_report_update.py create mode 100644 skills/multi/dingtalk-misc/scripts/yida_schema_common.py delete mode 100644 skills/multi/dingtalk-oa/SKILL.md delete mode 100644 skills/multi/dingtalk-oa/references/oa-index.md delete mode 100644 skills/multi/dingtalk-report/SKILL.md delete mode 100644 skills/multi/dingtalk-report/references/report-index.md delete mode 100644 skills/multi/dingtalk-report/scripts/report_received_today.py delete mode 100644 skills/multi/dingtalk-sheet/SKILL.md delete mode 100644 skills/multi/dingtalk-sheet/references/sheet.md delete mode 100644 skills/multi/dingtalk-sheet/references/sheet/sheet-capabilities.md delete mode 100644 skills/multi/dingtalk-sheet/references/sheet/sheet-edit-rules.md delete mode 100644 skills/multi/dingtalk-sheet/references/sheet/sheet-export-index.md delete mode 100644 skills/multi/dingtalk-sheet/references/sheet/sheet-format-index.md delete mode 100644 skills/multi/dingtalk-sheet/references/sheet/sheet-formula.md delete mode 100644 skills/multi/dingtalk-sheet/references/sheet/sheet-hard-constraints.md delete mode 100644 skills/multi/dingtalk-sheet/references/sheet/sheet-import.md delete mode 100644 skills/multi/dingtalk-sheet/references/sheet/sheet-nodeid.md delete mode 100644 skills/multi/dingtalk-sheet/references/sheet/sheet-query-index.md delete mode 100644 skills/multi/dingtalk-sheet/references/sheet/sheet-quick-ref.md delete mode 100644 skills/multi/dingtalk-sheet/references/sheet/sheet-read-index.md delete mode 100644 skills/multi/dingtalk-sheet/references/sheet/sheet-ref-index.md delete mode 100644 skills/multi/dingtalk-sheet/references/sheet/sheet-scope.md delete mode 100644 skills/multi/dingtalk-sheet/references/sheet/sheet-templates.md delete mode 100644 skills/multi/dingtalk-sheet/references/sheet/sheet-triggers.md delete mode 100644 skills/multi/dingtalk-sheet/references/sheet/sheet-url-paste.md delete mode 100644 skills/multi/dingtalk-sheet/references/sheet/sheet-url-to-node-id.md delete mode 100644 skills/multi/dingtalk-sheet/references/sheet/sheet-visual-index.md delete mode 100644 skills/multi/dingtalk-sheet/references/sheet/sheet-write-index.md delete mode 100644 skills/multi/dingtalk-todo/references/todo-index.md delete mode 100644 skills/multi/dingtalk-wiki/references/wiki-index.md create mode 100644 skills/multi/dws-shared/references/best_practices/_common/lite-recipes.md delete mode 100644 skills/multi/dws-shared/references/field-rules.md create mode 100644 skills/multi/dws-shared/references/routing.md delete mode 100644 skills/multi/dws-shared/references/shared-index.md create mode 100644 skills/multi/dws-shared/references/workflow-routing.md diff --git a/skills/multi/dingtalk-agoal/SKILL.md b/skills/multi/dingtalk-agoal/SKILL.md deleted file mode 100644 index 5a180a7f..00000000 --- a/skills/multi/dingtalk-agoal/SKILL.md +++ /dev/null @@ -1,41 +0,0 @@ ---- -name: dingtalk-agoal -description: 钉钉 Agoal 目标管理与经营目标跟进。Use when 用户说目标管理/战略解码/经营合约/计分卡/用户目标/OKR拆解/更新目标/查看经营合约/目标进展/目标负责人/周月报统计/周报提交情况/跟催/迟交/未提交。Distinct from dingtalk-todo(个人任务待办)、dingtalk-report(日报周报内容)、dingtalk-oa(审批流)。命令前缀:dws agoal。 -cli_version: ">=0.2.14" -metadata: - category: product - stability: experimental - requires: - bins: - - dws ---- - -# 钉钉目标管理 Skill - -> 🧪 **EXPERIMENTAL · 试验版 / Preview** — multi 模式当前未达 stable 标准。全部 dingtalk-* skill 已通过 dispatch verifier,但接口、命名、跨 skill 引用后续可能调整;生产 / 共享环境请优先使用 mono 模式(`dws skill setup --mode mono`)。问题请提 issue 反馈。 - -## 前置条件 — 执行操作前必读 - -> **`use_skill(dws-shared)`** — 认证、全局参数(`--format json` / `--yes`)、错误码、URL 模板、跨产品消歧、安全规则与 capability 边界。**执行任何 `dws` 命令前先读;** 单产品的清晰命令可直接用本 skill。 - - - -> 命令参考:[agoal.md](references/agoal.md)。 - -## 意图表 - -| 用户说 | 命令 | -|--------|------| -| "查战略解码 / 战略列表" | `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` 都是覆盖式更新,必须先查询详情,在返回数据基础上修改后再提交。 -- 所有命令带 `--format json`;涉及写操作时回查确认。 diff --git a/skills/multi/dingtalk-aisearch/SKILL.md b/skills/multi/dingtalk-aisearch/SKILL.md index 00681136..9378458a 100644 --- a/skills/multi/dingtalk-aisearch/SKILL.md +++ b/skills/multi/dingtalk-aisearch/SKILL.md @@ -1,10 +1,9 @@ --- name: dingtalk-aisearch -description: AI 搜问——跨源语义检索/发现(只做找到/定位,不做读写;具体读写各归 doc/report/mail/contact)。Use when 找同事/谁负责/查上下级/查工号手机号、跨文档消息邮件听记的内容检索、"我发过/收到过"行为回溯。模糊找人拿 userId 后精确查走 dingtalk-contact。命令前缀:dws aisearch。 -cli_version: ">=0.2.14" +description: AI搜问:人员语义搜索与跨源定位。Use when 按姓名/工号/部门/职责/上下级或手机号线索找人,跨文档/消息/邮件/听记检索,或回溯“我发过/收到过”。完整手机号反查走 dingtalk-contact;找到 userId 后由 contact 补详情。命令前缀:dws aisearch。 metadata: + cli_version: ">=0.2.14" category: product - stability: experimental requires: bins: - dws @@ -12,13 +11,9 @@ metadata: # 钉钉 AI 搜问 Skill -> 🧪 **EXPERIMENTAL · 试验版 / Preview** — multi 模式当前未达 stable 标准。全部 dingtalk-* skill 已通过 dispatch verifier,但接口、命名、跨 skill 引用后续可能调整;生产 / 共享环境请优先使用 mono 模式(`dws skill setup --mode mono`)。问题请提 issue 反馈。 - ## 前置条件 — 执行操作前必读 -> **`use_skill(dws-shared)`** — 认证、全局参数(`--format json` / `--yes`)、错误码、URL 模板、跨产品消歧、安全规则与 capability 边界。**执行任何 `dws` 命令前先读;** 单产品的清晰命令可直接用本 skill。 - - +> **CRITICAL — 执行任何 `dws` 操作前,MUST 先用 Read 工具完整读取 [`dws-shared`](../dws-shared/SKILL.md)。**该轻量文件包含全局执行契约、安全底线及 shared references 的按需加载导航;不要预加载其全部 references。 > 命令参考:[aisearch.md](references/aisearch.md)。 @@ -30,7 +25,9 @@ metadata: | "谁负责 XX / XX 负责人是谁" | `dws aisearch person --keyword "" --dimension duty` | | "张三的上级 / 下级" | `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` | +| "工号 12345 是谁" | `dws aisearch person --keyword "<工号>" --dimension jobNumber` | +| "按手机号线索语义搜人" | `dws aisearch person --keyword "<手机号线索>" --dimension phone` | +| "完整手机号精确反查" | `dws contact user search-mobile --mobile "<完整手机号>"` | | "最近 OKR 相关邮件 / 项目相关文档" | `dws aisearch enterprise --queries "<主题>" --types mail/document --time-range "<时间>"` | | "我发过/创建过/分享过/收到过什么" | `dws aisearch behavior --queries "<主题>" --behavior-type <动作> --direction <方向>` | @@ -40,12 +37,12 @@ metadata: ### SOP-1 搜人 → 拿 userId(search-person) -**触发**:找人/谁负责/查上下级/部门成员/工号或手机号反查。 +**触发**:姓名模糊找人/谁负责/查上下级/部门成员/工号反查/手机号线索语义搜人。 -1. **定维度(必须)**:姓名→`name`、"谁负责 XX"→`duty`、部门成员→`department`、上级/下级→`supervisor`/`subordinate`、工号→`jobNumber`、手机号→`phone`;不确定→`all`。`--keyword` 必须按用户原文**完整保真**,切勿截断、改昵称、扩同音字。 +1. **定维度(必须)**:姓名→`name`、"谁负责 XX"→`duty`、部门成员→`department`、上级/下级→`supervisor`/`subordinate`、工号→`jobNumber`、手机号语义线索→`phone`;完整手机号精确反查切到 `dingtalk-contact` 的 `user search-mobile`;不确定→`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`。 +4. **衔接(必须)**:要邮箱/部门/职位/主管等详情 → 切 `dingtalk-contact` 执行 `dws contact user get --ids --format json`;发消息 → `dingtalk-chat`;发 DING → `dingtalk-misc`(`references/ding.md`)。 5. **失败(必须)**:未命中最多换 1 个维度重试一次(如 `name`→`department`/`jobNumber`/`phone`),仍保留完整目标值;仍无果**必须如实告知**。 **禁止**:用半截姓名扩大搜索、跳过 `--format json`、取首个候选、凭空补全人员信息。 @@ -55,7 +52,7 @@ metadata: **触发**:跨文档/邮件/消息按主题找内容。 1. **执行(必须)**:`dws aisearch enterprise --queries "<主题>" --types --time-range "<时间>" --format json`;多主题逗号分隔。 -2. **衔接(必须)**:按命中来源切下游读写——文档→`dingtalk-doc`、邮件→`dingtalk-mail`、消息→`dingtalk-chat`。**aisearch 只负责"找到",不做读写。** +2. **衔接(必须)**:按命中来源切到对应产品 skill 读写。**aisearch 只负责"找到",不做读写。** **禁止**:把 aisearch 当作读写入口、跳过下游 skill 直接改数据。 @@ -80,7 +77,7 @@ metadata: - 拿到 userId 后查详情 / 部门 → 切到 `dingtalk-contact` - 拿到 userId 发消息 → 切到 `dingtalk-chat` -- 拿到 userId 发 DING → 切到 `dingtalk-ding` -## 局部意图与 Recipe +- 拿到 userId 发 DING → 切到 `dingtalk-misc`(`references/ding.md`) +## 局部意图与短流程 -- [局部意图消歧](references/intent-guide.md);[Lite Recipe](references/lite-recipes.md)。 +- [局部意图消歧](references/intent-guide.md);[短流程](references/lite-recipes.md)。 diff --git a/skills/multi/dingtalk-aisearch/references/aisearch.md b/skills/multi/dingtalk-aisearch/references/aisearch.md index 8a21aa13..d211c93f 100644 --- a/skills/multi/dingtalk-aisearch/references/aisearch.md +++ b/skills/multi/dingtalk-aisearch/references/aisearch.md @@ -19,7 +19,7 @@ Example: dws aisearch person --keyword "五道" --dimension supervisor --format json dws aisearch person --keyword "AI搜问" --dimension duty --format json dws aisearch person --keyword "李四" --dimension name,department --format json - dws aisearch person --keyword "13800138000" --dimension phone --format json + dws aisearch person --keyword "<手机号线索>" --dimension phone --format json dws aisearch person --keyword "W12345" --dimension jobNumber --format json Flags: --keyword string 搜索关键词 (必填,如人名、技能关键词等) @@ -37,7 +37,7 @@ Flags: | `duty` | 职责/技能 | "负责什么"、"职责"、"技能"、"负责人" | | `supervisor` | 上级 | "上级"、"领导"、"主管" | | `subordinate` | 下级 | "下级"、"下属"、"团队成员" | -| `phone` | 手机号 | "手机号是多少"、"电话"、"联系方式" | +| `phone` | 手机号语义线索 | "电话线索"、"联系方式相关";完整手机号精确反查走 `contact user search-mobile` | | `jobNumber` | 工号 | "工号"、"工号是多少"、"员工编号" | ### keyword 提取规则 @@ -51,7 +51,7 @@ Flags: | "AI搜问的负责人是谁" | AI搜问 | duty | | "产品部有谁" | 产品部 | department | | "李四是哪个部门的" | 李四 | department | -| "13800138000是谁" | 13800138000 | phone | +| "按手机号线索找人" | 用户提供的手机号线索 | phone | | "工号W12345是谁" | W12345 | jobNumber | --- @@ -59,12 +59,13 @@ Flags: ## 意图判断 - 用户说"搜人/找人/谁负责/上级是谁/哪个部门的人" → `aisearch person` +- 用户提供完整手机号精确反查 → `contact user search-mobile` - 用户说"搜资料/找方案/查文档/搜企业知识/项目相关内容/工作总结/周报总结" → `aisearch enterprise` - 用户说"最近/本周/今天 + XX相关消息/文档/邮件有哪些" → `aisearch enterprise`,时间词进 `--time-range`,类型词进 `--types` - 用户说"我发过/谁发给我/创建过/分享过/收到过/今天我干了什么" → `aisearch behavior` -- 用户说"搜同事/查部门/查通讯录" → `contact`(通讯录) +- 用户说"搜同事" → `aisearch person`;查部门详情/部门成员/通讯录精确信息 → `contact` -**关键区分**:`aisearch person`(AI 语义搜人,支持职责/手机号/上级/下级等维度)vs `aisearch enterprise`(按内容找企业内部知识)vs `aisearch behavior`(按动作找发送/创建/分享/编辑/接收记录)vs `contact`(通讯录精确查询:userId/部门成员列表) +**关键区分**:`aisearch person`(姓名模糊、工号、职责、手机号线索、上下级等搜索)vs `aisearch enterprise`(按内容找企业内部知识)vs `aisearch behavior`(按动作找发送/创建/分享/编辑/接收记录)vs `contact`(完整手机号反查、已知 userId 详情、部门成员列表) ### 高优先级抽取规则 diff --git a/skills/multi/dingtalk-aisearch/references/intent-guide.md b/skills/multi/dingtalk-aisearch/references/intent-guide.md index c38b26a0..7add78f0 100644 --- a/skills/multi/dingtalk-aisearch/references/intent-guide.md +++ b/skills/multi/dingtalk-aisearch/references/intent-guide.md @@ -4,11 +4,12 @@ | 用户说... | 真实意图 | 应该用 | 不要用 | 理由 | |---|---|---|---|---| -| "张三在哪个部门/查一下同事工号" | 通讯录精确查询 | #8 `contact` | #5 汇报 / #4 文档 | 需要 userId、手机号、部门 ID 等精确信息时用 contact | -| "找一下张三/搜同事/找人" | AI搜人(首选) | `aisearch person` | `contact user search` | 搜人首选 aisearch,支持姓名/部门/职责/上下级维度;精确查 userId/手机号用 contact | +| "张三在哪个部门/张三的工号是多少" | 搜人后查通讯录详情 | `aisearch person` → `contact user get` | 直接 `contact user search` | 姓名或工号先由 aisearch 获取 userId,再由 contact 补部门、工号等详情 | +| "找一下张三/搜同事/找人" | 人员语义搜索 | `aisearch person` | `contact user search` | 姓名模糊搜索、工号、部门、职责和上下级走 aisearch;contact 在拿到 userId 后补详情 | | "五道的上级是谁/谁负责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 | +| "13800138000是谁/完整手机号反查" | 精确手机号反查 | `contact user search-mobile` | `contact user search` | 完整手机号精确匹配使用 search-mobile | +| "按手机号线索找人" | 手机号语义搜人 | `aisearch person --dimension phone` | `contact user search` | 非精确手机号匹配走 aisearch 的 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 index b0594321..00f85340 100644 --- a/skills/multi/dingtalk-aisearch/references/lite-recipes.md +++ b/skills/multi/dingtalk-aisearch/references/lite-recipes.md @@ -24,7 +24,8 @@ - 需要获取 userId 给其他产品使用(发消息/建待办/约日程) - 已有 userId 需查完整详情(`contact user get --ids`) +- 完整手机号精确反查(`contact user search-mobile --mobile`) -1. `aisearch person --keyword "<姓名>" --dimension name` → `userId`;**多命中须列出候选请用户确认**。 +1. 完整手机号精确反查:`contact user search-mobile --mobile "<手机号>"`;其他搜人:`aisearch person --keyword "<关键词>" --dimension <维度>`。 2. **重名消歧**:多人同名时禁止默认选第一个,须追加 `contact user get --ids` 获取部门/职位后请用户确认,详见 [08-directory.md](../../dingtalk-contact/references/08-directory.md)「多命中」。 -3. 需详情时:`contact user get --ids `(多人可 `--ids id1,id2,...`) +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 eae159b2..4725814a 100644 --- a/skills/multi/dingtalk-aitable/SKILL.md +++ b/skills/multi/dingtalk-aitable/SKILL.md @@ -1,10 +1,9 @@ --- name: dingtalk-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" +description: 钉钉 AI 表格(多维表)。Use when 用户说 AI表格/多维表/数据表/base/table/建表/查记录/写数据/字段/记录增删改查/筛选/排序/公式/模板搜索/批量导入CSV或JSON/导出/仪表盘/图表/上传附件到表格/按字段类型建表。不做电子表格单元格读写(走 dingtalk-misc)、文档编辑(走 dingtalk-doc);听记待办入表先用 dingtalk-minutes 提取,再由本 skill 写入。命令前缀:dws aitable。 metadata: + cli_version: ">=0.2.14" category: product - stability: experimental requires: bins: - dws @@ -12,21 +11,16 @@ metadata: # 钉钉 AI 表格 Skill -> 🧪 **EXPERIMENTAL · 试验版 / Preview** — multi 模式当前未达 stable 标准。全部 dingtalk-* skill 已通过 dispatch verifier,但接口、命名、跨 skill 引用后续可能调整;生产 / 共享环境请优先使用 mono 模式(`dws skill setup --mode mono`)。问题请提 issue 反馈。 - ## 前置条件 — 执行操作前必读 -> **`use_skill(dws-shared)`** — 认证、全局参数(`--format json` / `--yes`)、错误码、URL 模板、跨产品消歧、安全规则与 capability 边界。**执行任何 `dws` 命令前先读;** 单产品的清晰命令可直接用本 skill。 - - +> **CRITICAL — 执行任何 `dws` 操作前,MUST 先用 Read 工具完整读取 [`dws-shared`](../dws-shared/SKILL.md)。**该轻量文件包含全局执行契约、安全底线及 shared references 的按需加载导航;不要预加载其全部 references。 > 命令参考:[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)。 ## Shortcuts(无专用脚本/recipe 时优先) -以下 shortcut 来自独立于 Runtime Schema 的公开 catalog。先按本 skill 的意图表、脚本和 recipe 路由:存在精确覆盖该场景的专用脚本/recipe 时按其执行;否则用户意图命中时,shortcut 优先于手写原子命令。用 `dws shortcut list --service aitable --format json` 读取参数、约束、风险和示例,并以 `dws aitable --help` 核对当前 Cobra flags;不要对 `+` 路径调用 `dws schema`。 +以下命令来自独立于 Runtime Schema 的公开 catalog。先运行 `dws shortcut list --service aitable --format json` 读取完整契约,再用 `dws aitable --help` 核对 flags;不要对 `+` 路径调用 `dws schema`。 | Shortcut | 风险 | 适用场景 | |---|---|---| @@ -89,7 +83,7 @@ metadata: 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` 一并记下。 +4. **取 tableId(必须)**:`dws aitable table get --base-id --format json` → 从 `data.tables[].tableId` 取目标表 ID,并记录 `views[]`。枚举模式不返回 `fields[]`;需要字段目录时必须继续执行 SOP-2 的 `field get`。若只核对某张表,可显式加 `--table-ids ` 控制返回体。 5. **失败(必须)**:`base list` 为空或不命中 → 换 `base search --query` 关键词重试一次;仍无果**必须如实告知**,禁止臆造 baseId/tableId。 **禁止**:跳过 `table get` 直接用字段名写记录、用模糊名匹配当 baseId、用旧会话里的 ID 不再校验。 @@ -113,7 +107,7 @@ metadata: 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**,禁止继续按原名称猜。 +4. **解析与验证(必须)**:从返回 `data.newRecordIds[]` 取全部新记录 ID;不要读取不存在的标量 `recordId`。立即执行 `dws aitable record query --base-id --table-id --record-ids --format json` 回读写入值。 5. **失败(必须)**:类型/格式错误按返回报错修正后重试,**禁止**降级丢弃字段;不确定格式先 `field get` 复核 config。 **禁止**:编造 fieldId/recordId、跳过 `field get` 凭中文名写、把 URL 字符串直接塞给 url 字段。 @@ -136,7 +130,7 @@ metadata: 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 与受影响字段。 +3. **解析与验证(必须)**:写入格式同 SOP-3;从返回 `data.recordIds[]` 取实际更新的记录 ID。更新响应不返回“受影响字段”,必须立即用 `record query --record-ids --format json` 回读目标字段确认。 4. **失败(必须)**:recordId 不存在或类型不符 → 回 SOP-4 重新定位,**禁止**编造 ID 强写。 **禁止**:省略 `recordId`、用字段中文名当 cells key、凭空猜测 recordId 直接 update。 @@ -147,7 +141,7 @@ metadata: ## 高频硬约束 -- 创建 Base、改字段或写记录时,固定按“获取真实 ID → 执行写命令 → 回读验证”顺序完成,禁止跳过任一步。 +- 创建/改字段/写记录是多轮连续任务时,不能在"让我执行/先获取 ID"后停下;必须实际调用对应 `dws aitable` 命令并验证结果。 - 字段重命名使用 `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`,不要继续按原名称猜。 @@ -160,7 +154,7 @@ metadata: ## 跨产品协作 -- 单元格 / 工作表 / 公式 → 切到 `dingtalk-sheet`(命令前缀:`dws sheet`) -## 局部意图与 Recipe +- 单元格 / 工作表 / 公式 → 切到 `dingtalk-misc`(`references/sheet.md`,命令前缀:`dws sheet`) +## 局部意图 - [局部意图消歧](references/intent-guide.md)。 diff --git a/skills/multi/dingtalk-aitable/references/_common/conventions.md b/skills/multi/dingtalk-aitable/references/_common/conventions.md index 75491aab..803212b8 100644 --- a/skills/multi/dingtalk-aitable/references/_common/conventions.md +++ b/skills/multi/dingtalk-aitable/references/_common/conventions.md @@ -9,7 +9,7 @@ | 1 | **并行查详情**:拿到多个 ID 后,用 `&` 合并到同一条 Shell 命令并行执行 + `wait`,**严禁逐条串行** | | 2 | **翻页**:分页接口须拉全直至无更多 | | 3 | **优先批量 API**:有批量接口则用批量;无则按 #1 并行 | -| 4 | **群消息**:必须先 `chat search --query` 得 `openConversationId`,再 `chat message list --group`;多群同条命令并行 | +| 4 | **群消息**:必须先 `chat search --query` 得 `openConversationId`,再 `chat message list --group --time "" --direction older`;多群同条命令并行 | | 5 | **列表少轮次**:带条件搜索/列表 → 一次采全详情;**禁止**无新参数时重复同一 `list` / `search` | ## 多源并行采集(公共模式) @@ -29,7 +29,7 @@ |------|------|--------| | `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 | +| `deptId` | `contact dept search` | `contact dept list-members --ids `;多子部门时对每个子部门分别 `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` | @@ -38,7 +38,7 @@ | `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` | +| `dentryUuid` | `drive list` / `drive mkdir` | `drive info/download/copy/move/rename/delete --node`、`drive list/mkdir/upload/copy/move --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,不能把该数字直接代入后续命令。 +**ID 边界硬约束**:`dentryId` 通常是纯数字,只表示聊天文件消息需要的钉盘条目数字 ID;它不是父目录 ID。遇到 `drive --node/--folder`、`doc --node`、`wiki node --folder` 时,只能使用 `dentryUuid` / `nodeId` / 文档 URL。若当前上下文只有数字型 `dentryId`,必须先重新 `drive list` / `drive search` / `wiki node list` 获取正确 ID,不能把该数字直接代入后续命令。 diff --git a/skills/multi/dingtalk-aitable/references/aitable-record-ops.md b/skills/multi/dingtalk-aitable/references/aitable-record-ops.md index f7f67401..48bfab14 100644 --- a/skills/multi/dingtalk-aitable/references/aitable-record-ops.md +++ b/skills/multi/dingtalk-aitable/references/aitable-record-ops.md @@ -27,7 +27,7 @@ dws aitable record query --base-id --table-id \ --format json # 关键词搜索 -dws aitable record query --base-id --table-id --query "设计" --format json +dws aitable record query --base-id --table-id --keyword "设计" --format json # 按 ID 查询 dws aitable record query --base-id --table-id --record-ids rec001,rec002 --format json @@ -38,7 +38,7 @@ dws aitable record query --base-id --table-id --limit 50 -- ## 添加记录 -**必须先执行 `table get` 获取 fieldId,再写入。cells 的 key 必须是 fieldId(如 fldXXX),不是字段名。** +**必须先执行 `field get` 获取 fieldId,再写入。cells 的 key 必须是 fieldId(如 fldXXX),不是字段名。** ```bash # 单条 @@ -59,6 +59,8 @@ dws aitable record create --base-id --table-id \ {"data": {"newRecordIds": ["rec-new-001", "rec-new-002"]}} ``` +从 `data.newRecordIds[]` 提取新记录 ID,并立即执行 `record query --record-ids ` 回读写入结果;不要只看命令退出码。 + ### --records 格式常见错误 ```bash @@ -85,7 +87,7 @@ dws aitable record update --base-id --table-id \ --format json ``` -只需传入需修改的字段,未传入的保持原值。 +更新响应中的成功记录 ID 位于 `data.recordIds[]`。只需传入需修改的字段,未传入的保持原值;响应不返回“受影响字段”,必须再执行 `record query --record-ids ` 回读确认。 ## 删除记录 @@ -132,4 +134,4 @@ dws aitable record create --base-id --table-id \ - 公式字段、引用字段 - 自动编号字段 -执行 `table get` 后识别字段类型,跳过只读字段。 +执行 `field get` 后识别字段类型,跳过只读字段。 diff --git a/skills/multi/dingtalk-aitable/references/aitable.md b/skills/multi/dingtalk-aitable/references/aitable.md index 42d3eff3..18c13b21 100644 --- a/skills/multi/dingtalk-aitable/references/aitable.md +++ b/skills/multi/dingtalk-aitable/references/aitable.md @@ -1,21 +1,488 @@ -# AI 表格 (aitable) 命令参考 +# AI表格 (aitable) 命令参考 -> 渐进式入口:先读命令索引,再只加载命中的模块参考。 +> **渐进式文档**:本文件为路由层(索引 + 意图判断),各命令的详细参数、示例和踩坑说明在 [aitable/](./aitable/) 目录下按需加载。 -## 按需加载 +## 文档地址 (URI) -| 任务 | 参考 | -|---|---| -| 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-core-workflow.md) · [上下文传递](./aitable/aitable-context.md) | -| 注意事项、脚本、产品边界 | [注意事项](./aitable/aitable-notes.md) · [自动化脚本](./aitable/aitable-scripts.md) · [相关产品](./aitable/aitable-related.md) | +| 资源 | 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}` | -## 创建 AI 表格 +> **操作后请返回文档 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 表格处理。 -创建 Base 先读 [Base 索引](./aitable/aitable-base-index.md);创建数据表再读 [Table 索引](./aitable/aitable-table-index.md)。 +## 命令索引表 + +### base (Base 管理) + +| 命令 | 用途 | 必填参数 | 路由提醒 | +|------|------|----------|----------| +| `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 (数据表管理) + +| 命令 | 用途 | 必填参数 | 路由提醒 | +|------|------|----------|----------| +| `table get` | 获取数据表/视图目录 | `--base-id` | 不传 `--table-ids` 枚举全部表,但不返回字段;字段目录使用 `field get` | +| `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.md](./aitable/aitable-field.md)、[field-properties](./aitable/aitable-field-properties.md) + +| 命令 | 用途 | 必填参数 | 路由提醒 | +|------|------|----------|----------| +| `field get` | 获取字段完整配置 | `--base-id` `--table-id` | 按需展开少量字段 | +| `field create` | 创建字段 | `--base-id` `--table-id` + (`--name --type` 或 `--fields`) | 单字段/批量两种模式严格互斥;单字段配置传 `--config`,批量配置写入 `--fields` 每个元素的 `config` | +| `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 (记录管理) + +| 命令 | 用途 | 必读 reference | 路由提醒 | +|------|------|----------------|----------| +| `record query` | 查询/搜索记录 | [aitable-record-query.md](./aitable/aitable-record-query.md) | 先 `field get` 拿 fieldId;`--all` 自动翻页;filters 结构见 reference | +| `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 create` | 新增记录 | [aitable-record-create.md](./aitable/aitable-record-create.md) | cells key 必须是 fieldId 不是字段名;单次最多 100 条 | +| `record update` | 更新记录(每条独立 cells) | [aitable-record-update.md](./aitable/aitable-record-update.md) | 需先 query 拿 recordId;`cells` key 支持 fieldId 或当前表内唯一字段名,推荐 fieldId;`--records` 是 `[{recordId,cells},...]` 数组 | +| `record batch-update` | 批量更新(同一 cells 应用到多条 recordId) | [aitable-record-update.md](./aitable/aitable-record-update.md)、[aitable-cell-value.md](./aitable/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/aitable-record-delete.md) | 不可逆,需先 query 确认 | +| `record history-list` | 查询单条记录的变更历史 | [aitable-record-history.md](./aitable/aitable-record-history.md) | 必填 `--record-id`;分页 `--offset --limit`,limit 范围 [1,50] 默认 20 | +| `record query-empty` | 查询完全没填用户字段的空行 | [aitable-record-query.md](./aitable/aitable-record-query.md) | 一页扫描 `--limit` [1,100] 默认 100;扫完前需用 `--cursor` 翻页(nextCursor 为空才表扫完) | +| `record share-url` | 批量获取记录分享链接 | [aitable-record-share.md](./aitable/aitable-record-share.md) | 必填 `--record-ids`(CSV,单次最多 20 条);可选 `--view-id` 带视图上下文 | +| `record upsert` | 批量创建或更新(按 recordId 是否存在自动拆分) | [aitable-record-upsert.md](./aitable/aitable-record-upsert.md) | --records 同 record update 格式;带 recordId 走 update,不带走 create;单次最多 100 | +| `record primary-doc-get` | 查询记录的主键文档 nodeId | [aitable-primary-doc.md](./aitable/aitable-primary-doc.md) | 返回的 nodeId 可直接用于 `dws doc read/update --node` | +| `record primary-doc-create` | 为记录创建主键文档(幂等) | [aitable-primary-doc.md](./aitable/aitable-primary-doc.md) | fieldId 必须是 primaryDoc 类型;已存在则返回已有 nodeId | + +### view (视图管理) + +| 命令 | 用途 | 必填参数 | 路由提醒 | +|------|------|----------|----------| +| `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/aitable-view-config.md))+ lock/frozen-cols/row-height/fill-color-rule(详见 [aitable-view-extras.md](./aitable/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;可用 `--visible-field-ids`,或用 `--config` 传 visibleFieldIds/filter/sort/group;**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/aitable-view-extras.md) | +| `view duplicate` | 复制视图 | `--base-id` `--table-id` `--view-id` | 可选 `--new-name`;保留源视图全部配置。详见 [aitable-view-extras.md](./aitable/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/aitable-view-config.md) +> - lock / frozen-cols / row-height / fill-color-rule / duplicate → [aitable-view-extras.md](./aitable/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.md](./aitable/aitable-form.md) + +| 命令 | 用途 | 必填参数 | 路由提醒 | +|------|------|----------|----------| +| `form list` | 列出表单视图 | `--base-id` `--table-id` | 详情见 [aitable-form.md](./aitable/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.md](./aitable/aitable-workflow.md) + +| 命令 | 用途 | 必填参数 | 路由提醒 | +|------|------|----------|----------| +| `workflow create` | 创建并发布自动化工作流 | `--base-id` `--dsl` | 按子文档 Demo 组装 DSL;必须检查返回的 `data.valid` / `issues`;create 不自动重试 | +| `workflow update` | 更新并发布已有自动化工作流 | `--base-id` `--workflow-id` `--dsl` | 先 get 留底;提交完整目标 DSL;必须检查 `data.valid` / `issues` | +| `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 | + +> 创建/更新的 `--dsl` 使用钉钉 AI 表格 `workflow-dsl/v1`;完整格式和最小 Demo 见 [aitable-workflow.md](./aitable/aitable-workflow.md)。删除工作流暂未开放。 + +### dashboard & chart → 详见 [aitable-dashboard-chart.md](./aitable/aitable-dashboard-chart.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.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 | + +> **角色 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` 重试。 + +## 复杂操作 + +### 仪表盘 / 图表(建议顺序) + +```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` + +## 意图判断 + +用户说"表格/多维表/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/aitable-record-name-key.md) +- 删除 → `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)) +- 找空行 / 没填东西的行 → `record query-empty`(读 [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)) +- 修改/更新(每条独立 cells) → `record update`(读 [aitable-record-update.md](./aitable/aitable-record-update.md)) +- **批量更新同一字段值**(统一标记/统一改值) → `record batch-update --record-ids ... --cells '{...}'` +- 删除 → `record delete` +- **查记录的字段变更历史 / 操作审计** → `record history-list`(读 [aitable-record-history.md](./aitable/aitable-record-history.md)) +- **取记录分享链接 / 把这行发给同事** → `record share-url`(读 [aitable-record-share.md](./aitable/aitable-record-share.md)) +- **不知道有没有 → 有就改、没有就建** → `record upsert`(读 [aitable-record-upsert.md](./aitable/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/aitable-view-config.md)) +- 锁定 / 冻结列 / 行高 / 数据高亮规则 / 复制视图 → 读 [aitable-view-extras.md](./aitable/aitable-view-extras.md) +- 删除 → `view delete` + +用户说"锁定视图/解锁视图/lock view" → `view lock` / `view lock --off`,详见 [aitable-view-extras.md](./aitable/aitable-view-extras.md) + +用户说"冻结列/冻结首列/frozen columns" → `view update frozen-cols --count N`,详见 [aitable-view-extras.md](./aitable/aitable-view-extras.md) + +用户说"行高/单元格高度/紧凑模式/cell height" → `view update row-height --cell-height N`(合法档位 32/56/88/128),详见 [aitable-view-extras.md](./aitable/aitable-view-extras.md) + +用户说"数据高亮/条件格式/单元格上色/fill color rule" → `view update fill-color-rule --json '[...]'`,详见 [aitable-view-extras.md](./aitable/aitable-view-extras.md) + +用户说"复制视图/duplicate view" → `view duplicate --view-id ... [--new-name ...]`,详见 [aitable-view-extras.md](./aitable/aitable-view-extras.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 跨表引用) + +用户说"表单/form/收集表/问卷/催办填写" → 读 [aitable-form.md](./aitable/aitable-form.md) + +用户说"自动化/工作流/流程/触发/automation/workflow" → 读 [aitable-workflow.md](./aitable/aitable-workflow.md) +- 新建自动化 → 按子文档的最小 Demo 组装完整 DSL,再 `workflow create --dsl @file` +- 修改自动化 → `workflow get` 留底,按最新 DSL 文档生成完整目标 DSL,再 `workflow update --dsl @file` +- 看 Base 里有哪些流程 / 哪些在跑 → `workflow list`(看 `recordCount` / `runningCount`) +- 看某个流程具体配置(触发条件、动作步骤) → `workflow get` +- 启用流程 → `workflow enable` +- 临时停掉流程(调试 / 数据迁移)→ `workflow disable --yes` +- 删除流程:当前不支持,引导用户到 AI 表格 Web 端 → 数据表 → 自动化 面板手动完成 + +用户说"仪表盘/图表/chart" → 读 [aitable-dashboard-chart.md](./aitable/aitable-dashboard-chart.md) + +用户说"仪表盘排版乱了/图表对不齐/重新排布/自动布局/美化仪表盘" → `dashboard arrange`(读 [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-advperm.md](./aitable/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/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 field 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,拼接指定数据表 URI | +| `table create` | `tableId` | 后续命令 + 拼接指定数据表 URI | +| `table get` | `tables[].tableId`、视图目录 | 定位数据表和视图;字段需继续调用 `field get` | +| `field get` | `fields[].fieldId` | record 操作的 cells key, field 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](../../dingtalk-doc/references/doc.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 deleted file mode 100644 index 61d89780..00000000 --- a/skills/multi/dingtalk-aitable/references/aitable/aitable-advperm-index.md +++ /dev/null @@ -1,6 +0,0 @@ -# 高级权限与自定义角色 - -| 命令 | 用途 | 详见 | -|---|---|---| -| `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 deleted file mode 100644 index 0c63516e..00000000 --- a/skills/multi/dingtalk-aitable/references/aitable/aitable-attachment-index.md +++ /dev/null @@ -1,5 +0,0 @@ -# 附件 - -| 命令 | 用途 | 详见 | -|---|---|---| -| `attachment upload` | 准备附件上传凭证 | [`aitable-attachment.md`](./aitable-attachment.md) | diff --git a/skills/multi/dingtalk-aitable/references/aitable/aitable-base-index.md b/skills/multi/dingtalk-aitable/references/aitable/aitable-base-index.md deleted file mode 100644 index 504be3e3..00000000 --- a/skills/multi/dingtalk-aitable/references/aitable/aitable-base-index.md +++ /dev/null @@ -1,10 +0,0 @@ -# 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 db63f634..2becd358 100644 --- a/skills/multi/dingtalk-aitable/references/aitable/aitable-best-practices.md +++ b/skills/multi/dingtalk-aitable/references/aitable/aitable-best-practices.md @@ -15,7 +15,7 @@ 1. **不要拉全量后在 context 里手动统计** — 优先用 `--filters` 在服务端过滤 2. **has_more=true 时不能做全局结论** — 数据可能不完整 3. **优先用 `--filters` 在服务端过滤** — 不要拉全量后在本地 jq/grep -4. **字段名必须来自 `table get` 真实返回** — 不要猜测 fieldId +4. **fieldId 必须来自 `field get` 真实返回** — 不要猜测 fieldId 5. **减少响应体积** — 用 `--field-ids` 仅返回需要的字段 ## 3. 任务选路 @@ -36,7 +36,7 @@ | 写操作 | 建议回读命令 | 确认内容 | |--------|-------------|----------| | `table create` | `table get --table-ids <新tableId>` | 表名、字段列表是否符合预期 | -| `field create` | `table get --table-ids ` | 新字段是否出现在字段列表中 | +| `field create` | `field get --table-id ` | 新字段是否出现在字段列表中 | | `record create/update` | `record query --record-ids <新recordId>` | 写入值是否正确 | ## 5. AI 字段注意事项 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 bc3a8125..4f6da4e1 100644 --- a/skills/multi/dingtalk-aitable/references/aitable/aitable-cell-value.md +++ b/skills/multi/dingtalk-aitable/references/aitable/aitable-cell-value.md @@ -7,7 +7,7 @@ ## 顶层规则 - cells 的 key **必须是 fieldId**(如 `fldXXX`),不是字段名称 -- fieldId 必须从 `table get` 返回中获取 +- fieldId 必须从 `field get` 返回中获取 - 不同字段类型的 value 格式不同,混用会报错 - 系统只读字段(creator/lastModifier/createdTime/lastModifiedTime/formula)不可写入 diff --git a/skills/multi/dingtalk-aitable/references/aitable/aitable-command-index.md b/skills/multi/dingtalk-aitable/references/aitable/aitable-command-index.md deleted file mode 100644 index 78d5e2a9..00000000 --- a/skills/multi/dingtalk-aitable/references/aitable/aitable-command-index.md +++ /dev/null @@ -1,275 +0,0 @@ -# 命令索引表 - -## 命令索引表 - -### 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 create` | 创建并发布工作流 | `--base-id` `--dsl` | 非幂等,不自动重试;检查 `data.valid/issues` | -| `workflow update` | 全量更新并发布工作流 | `--base-id` `--workflow-id` `--dsl` | 先 get 留底;检查 `data.valid/issues` | -| `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 | - -> 当前支持创建、更新、查询和启停;删除、运行历史及手动触发仍未开放。 - -### 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 deleted file mode 100644 index 58f0eca1..00000000 --- a/skills/multi/dingtalk-aitable/references/aitable/aitable-complex-ops.md +++ /dev/null @@ -1,40 +0,0 @@ -# 复杂操作 - -## 复杂操作 - -### 仪表盘 / 图表(建议顺序) - -```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 -# 第一步:创建任务(业务格式与输出格式分开) -dws aitable export data --base-id --scope table --table-id --export-format excel --timeout-ms 1000 --format json - -# 第二步:拿 taskId 继续轮询,直到返回 downloadUrl -dws aitable export data --base-id --task-id --timeout-ms 3000 --format json -``` - -参数约束 - -- 创建任务统一需要 `base-id + scope + export-format` -- `scope=all`:无额外 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 deleted file mode 100644 index bbcf5e2d..00000000 --- a/skills/multi/dingtalk-aitable/references/aitable/aitable-context.md +++ /dev/null @@ -1,13 +0,0 @@ -# 上下文传递表 - -## 上下文传递表 - -| 操作 | 从返回中提取 | 用于 | -|------|-------------|------| -| `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-core-workflow.md b/skills/multi/dingtalk-aitable/references/aitable/aitable-core-workflow.md deleted file mode 100644 index 87bdc777..00000000 --- a/skills/multi/dingtalk-aitable/references/aitable/aitable-core-workflow.md +++ /dev/null @@ -1,21 +0,0 @@ -# 核心工作流 - -## 核心工作流 - -```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-ids --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 -``` diff --git a/skills/multi/dingtalk-aitable/references/aitable/aitable-dashboard-index.md b/skills/multi/dingtalk-aitable/references/aitable/aitable-dashboard-index.md deleted file mode 100644 index 473bd567..00000000 --- a/skills/multi/dingtalk-aitable/references/aitable/aitable-dashboard-index.md +++ /dev/null @@ -1,6 +0,0 @@ -# 仪表盘与图表 - -| 命令 | 用途 | 详见 | -|---|---|---| -| `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-error-recovery.md b/skills/multi/dingtalk-aitable/references/aitable/aitable-error-recovery.md index e62fb0a4..88c3aa9c 100644 --- a/skills/multi/dingtalk-aitable/references/aitable/aitable-error-recovery.md +++ b/skills/multi/dingtalk-aitable/references/aitable/aitable-error-recovery.md @@ -32,7 +32,7 @@ | 错误现象 / summary | 原因 | 恢复动作 | |-------------------|------|---------| | `Failed to create field` | config 格式错误或必填项缺失 | 检查 [field-properties](./aitable-field-properties.md) 中该类型的必填 config | -| `field not found` | field-id 不存在 | 用 `table get` 获取最新字段列表 | +| `field not found` | field-id 不存在 | 用 `field get` 获取最新字段列表 | | formula 创建失败 | 公式语法错误或引用字段名不匹配 | 先 `field get` 确认字段精确名称,再检查公式语法(见 [formula-guide](./aitable-formula-guide.md)) | | 删除主字段失败 | 主字段(第一列)不可删除 | 改为更新字段名或类型,不能删除 | @@ -56,7 +56,7 @@ | 错误现象 / summary | 原因 | 恢复动作 | |-------------------|------|---------| | filters 无效被忽略 | 根节点不是 and/or,或 operands 格式错误 | 确保 filters 根节点是 `{"operator":"and"/"or", "operands":[...]}` 结构 | -| sort 无效 | fieldId 不存在 | 先 `table get` 确认字段 ID | +| sort 无效 | fieldId 不存在 | 先 `field get` 确认字段 ID | | 筛选结果为空 | 条件过严或字段值不匹配 | 放宽条件验证;注意 singleSelect 筛选值用 option name 或 id | ### 2.6 导入导出错误 @@ -120,7 +120,7 @@ dws aitable record create \ ## 5. 错误预防最佳实践 -1. **写记录前先读字段结构** — `field get` 或 `table get` 确认字段类型和 ID +1. **写记录前先读字段结构** — `field get` 确认字段类型和 ID 2. **写字段前先读 field-properties** — 确认 config 的必填项和格式 3. **formula 字段先确认引用字段名** — `[字段名]` 必须精确匹配 4. **options 更新传完整列表** — 更新 singleSelect/multipleSelect 的 options 是全量覆盖 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 deleted file mode 100644 index ca6b192e..00000000 --- a/skills/multi/dingtalk-aitable/references/aitable/aitable-export-import-index.md +++ /dev/null @@ -1,6 +0,0 @@ -# 导入导出 - -| 命令 | 用途 | 详见 | -|---|---|---| -| `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 126e0425..fc755451 100644 --- a/skills/multi/dingtalk-aitable/references/aitable/aitable-export-import.md +++ b/skills/multi/dingtalk-aitable/references/aitable/aitable-export-import.md @@ -4,23 +4,21 @@ `export data` 为异步任务:首次调用可能只返回 `taskId`,需要继续轮询。 -> ⚠️ **两种 format 严格分工**:业务导出格式只用 `--export-format`(excel/attachment 等);全局 `--format` 只控制 CLI 输出格式,推荐保持 `--format json`。旧写法 `--format excel` 仅用于兼容历史脚本,新调用不要使用。 +> ⚠️ **`--format` 冲突警告**:`export data` 的 `--format` 是**导出格式**(excel/attachment 等),不是全局输出格式。**此命令禁止追加全局 `--format json`**,否则会覆盖导出格式导致 `INVALID_EXPORT_FORMAT` 错误。输出默认就是 JSON,无需额外指定。 ```bash -# 第一步:创建任务;--export-format 是业务导出格式,--format json 是结构化输出格式 -dws aitable export data --base-id --scope table --table-id --export-format excel --timeout-ms 1000 --format json +# 第一步:创建任务(按 scope 传必要参数)——注意:不要加 --format json! +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 --format json +dws aitable export data --base-id --task-id --timeout-ms 3000 ``` ### 参数约束 -创建导出任务时统一必传 `--base-id`、`--scope` 和 `--export-format`;下表是不同 scope 的额外必传参数。使用 `--task-id` 轮询时不再传 scope/导出格式。 - -| scope | 额外必传参数 | +| scope | 必传参数 | |-------|----------| -| `all` | 无 | +| `all` | 只需 `--base-id` | | `table` | 必须 `--table-id` | | `view` | 必须 `--table-id` + `--view-id` | diff --git a/skills/multi/dingtalk-aitable/references/aitable/aitable-field-index.md b/skills/multi/dingtalk-aitable/references/aitable/aitable-field-index.md deleted file mode 100644 index c4f9469d..00000000 --- a/skills/multi/dingtalk-aitable/references/aitable/aitable-field-index.md +++ /dev/null @@ -1,9 +0,0 @@ -# 字段管理 - -| 命令 | 用途 | 详见 | -|---|---|---| -| `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-field.md b/skills/multi/dingtalk-aitable/references/aitable/aitable-field.md index 122b6006..303cf474 100644 --- a/skills/multi/dingtalk-aitable/references/aitable/aitable-field.md +++ b/skills/multi/dingtalk-aitable/references/aitable/aitable-field.md @@ -14,7 +14,7 @@ Flags: --table-id string Table ID (必填) ``` -返回字段的完整配置(含 options 等)。在 table get 拿到字段目录后,按需展开少量字段的完整配置。 +返回字段的完整配置(含 options 等)。不要假设未指定 `--table-ids` 的 `table get` 枚举结果含字段;字段目录和配置以 `field get` 返回为准。 ## field create — 创建字段 @@ -31,13 +31,33 @@ Example: Flags: --base-id string Base ID (必填) --name string 要创建的单字段名称(与 --type 配合使用,替代 --fields) - --type string 要创建的单字段类型(参考 table create 字段类型) - --config string 单字段配置,如 options(可选) - --ai-config string 单字段 AI 配置 JSON(可选,用于创建 AI 字段) - --fields string 批量新增字段 JSON 数组,单次最多 15 个 (与 --name/--type 二选一) + --type string 要创建的单字段类型(需要配合 --name,参考 table create 的内置类型) + --config string 单字段配置 JSON(需要配合 --name/--type,结构参考 table create) + --ai-config string 单字段 AI 配置 JSON(需要配合 --name/--type) + --fields string 批量新增字段 JSON 数组,单次最多 15 个;每个字段的配置写在其 config/aiConfig 内 --table-id string Table ID (必填) ``` +`field create` 有且只有两种输入模式: + +- 单字段模式:必须同时传 `--name` 和 `--type`;`--config`、`--ai-config` 只作为该字段的附加配置。 +- 批量模式:只传 `--fields`;字段配置写在数组内各对象的 `config` / `aiConfig` 中。 + +两种模式严格互斥。`--fields` 不能与 `--name`、`--type`、`--config` 或 `--ai-config` 混用;单独传 `--config` 也会报错,不会被静默忽略。 + +例如创建单选字段时,单字段模式的 `--config` 是一个配置对象;批量模式则把同一对象放入对应字段元素的 `config`: + +```bash +# 单字段模式 +dws aitable field create --base-id --table-id \ + --name "部门" --type singleSelect \ + --config '{"options":[{"name":"技术部"},{"name":"产品部"}]}' + +# 批量模式 +dws aitable field create --base-id --table-id \ + --fields '[{"fieldName":"部门","type":"singleSelect","config":{"options":[{"name":"技术部"},{"name":"产品部"}]}}]' +``` + 允许部分成功,返回结果逐项标明成功/失败状态。 ### AI 字段创建示例 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 8fabf8b4..5766f9f0 100644 --- a/skills/multi/dingtalk-aitable/references/aitable/aitable-filter-sort.md +++ b/skills/multi/dingtalk-aitable/references/aitable/aitable-filter-sort.md @@ -9,7 +9,7 @@ 1. **根节点必须是逻辑操作符**:`"operator"` 必须是 `"and"` 或 `"or"`,不能是 `"eq"` 等比较操作符 2. 比较操作必须放在根节点的 `"operands"` 数组内的对象中 3. `singleSelect` 和 `multipleSelect` 字段,推荐使用 **选项的 exact String 名称 (name)** 作为比较值 -4. fieldId 必须通过 `table get` 或 `field get` 获取,不能直接用字段名称 +4. fieldId 必须通过 `field get` 获取,不能直接用字段名称 ### 精简防呆模板 diff --git a/skills/multi/dingtalk-aitable/references/aitable/aitable-form-index.md b/skills/multi/dingtalk-aitable/references/aitable/aitable-form-index.md deleted file mode 100644 index 78aaf4e7..00000000 --- a/skills/multi/dingtalk-aitable/references/aitable/aitable-form-index.md +++ /dev/null @@ -1,8 +0,0 @@ -# 表单管理 - -| 命令 | 用途 | 详见 | -|---|---|---| -| `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-intent.md b/skills/multi/dingtalk-aitable/references/aitable/aitable-intent.md deleted file mode 100644 index 69b7269a..00000000 --- a/skills/multi/dingtalk-aitable/references/aitable/aitable-intent.md +++ /dev/null @@ -1,95 +0,0 @@ -# 意图判断 - -## 意图判断 - -用户说"表格/多维表/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) -- 新建并发布流程 → `workflow create --base-id --dsl @workflow.json` -- 修改并发布已有流程 → 先 `workflow get` 留底,再 `workflow update --base-id --workflow-id --dsl @workflow.json` -- 看 Base 里有哪些流程 / 哪些在跑 → `workflow list`(看 `recordCount` / `runningCount`) -- 看某个流程具体配置(触发条件、动作步骤) → `workflow get` -- 启用流程 → `workflow enable` -- 临时停掉流程(调试 / 数据迁移)→ `workflow disable --yes` -- **删除流程**:当前 CLI 暂不支持,引导用户到 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 deleted file mode 100644 index 5b38658d..00000000 --- a/skills/multi/dingtalk-aitable/references/aitable/aitable-notes.md +++ /dev/null @@ -1,8 +0,0 @@ -# 注意事项 - -## 注意事项 - -- 所有操作使用 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 eefda277..ff9bd738 100644 --- a/skills/multi/dingtalk-aitable/references/aitable/aitable-primary-doc.md +++ b/skills/multi/dingtalk-aitable/references/aitable/aitable-primary-doc.md @@ -28,7 +28,7 @@ dws aitable record primary-doc-create --base-id BASE_ID --table-id TABLE_ID --fi **参数:** - `--base-id`(必填):Base ID - `--table-id`(必填):Table ID -- `--field-id`(必填):主键字段 ID,必须是 primaryDoc 类型(通过 `dws aitable table get` 查看字段类型) +- `--field-id`(必填):主键字段 ID,必须是 primaryDoc 类型(通过 `dws aitable field get` 查看字段类型) - `--record-id`(必填):Record ID **返回:** `data.nodeId` — 创建或已存在的主键文档 nodeId。 @@ -44,8 +44,8 @@ dws aitable record primary-doc-create --base-id BASE_ID --table-id TABLE_ID --fi ## 典型工作流 ```bash -# 1. 查询表结构,拿到 primaryDoc 字段的 fieldId -dws aitable table get --base-id BASE_ID --table-ids TABLE_ID +# 1. 查询字段目录,拿到 primaryDoc 字段的 fieldId +dws aitable field get --base-id BASE_ID --table-id TABLE_ID # 2. 为某条记录创建主键文档 dws aitable record primary-doc-create --base-id BASE_ID --table-id TABLE_ID --field-id FIELD_ID --record-id RECORD_ID 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 fc9628e8..ec597412 100644 --- a/skills/multi/dingtalk-aitable/references/aitable/aitable-record-create.md +++ b/skills/multi/dingtalk-aitable/references/aitable/aitable-record-create.md @@ -25,7 +25,7 @@ Flags: |------|------| | 参数名用 `--data` | ❌ 参数名是 `--records`,不是 `--data` | | cells key 用字段名 | ❌ cells key 必须是 fieldId(如 `fldXXX`),不是字段名称(如 `"课程名称"`) | -| 不先获取 fieldId | ❌ 必须先 `table get` 获取 fieldId,再写入记录 | +| 不先获取 fieldId | ❌ 必须先 `field get` 获取 fieldId,再写入记录 | | 单次超 100 条 | ❌ 单次最多 100 条,超过需分批 | | 附件/图片字段直传 URL | ❌ 严禁 `{"url":"https://..."}` — 会触发 TIMEOUT_ERROR。必须先 `attachment upload` 获取 `fileToken`,再用 `{"fileToken":"ft_xxx"}` 写入。详见 [aitable-attachment.md](./aitable-attachment.md) | @@ -33,14 +33,20 @@ Flags: ```bash # 先获取 fieldId -dws aitable table get --base-id --table-ids --format json +dws aitable field get --base-id --table-id --format json # 从返回中提取 fieldId(如 fldABC123) # 再用 fieldId 写入记录 dws aitable record create --base-id --table-id \ --records '[{"cells":{"fldABC123":"Python入门"}}]' --format json + +# 从创建响应的 data.newRecordIds[] 提取新 ID,并回读确认真实写入值 +dws aitable record query --base-id --table-id \ + --record-ids --format json ``` +创建成功以 `data.newRecordIds[]` 为 ID 来源;不要把整个 `data` 当作单个 recordId,也不要只以退出码作为写入成功证据。 + ## cells 写入格式 各字段类型的写入格式见 [aitable-cell-value.md](./aitable-cell-value.md)。 diff --git a/skills/multi/dingtalk-aitable/references/aitable/aitable-record-index.md b/skills/multi/dingtalk-aitable/references/aitable/aitable-record-index.md deleted file mode 100644 index 8b68f123..00000000 --- a/skills/multi/dingtalk-aitable/references/aitable/aitable-record-index.md +++ /dev/null @@ -1,13 +0,0 @@ -# 记录管理 - -| 命令 | 用途 | 详见 | -|---|---|---| -| `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-update.md b/skills/multi/dingtalk-aitable/references/aitable/aitable-record-update.md index 2e67abf4..eb47db53 100644 --- a/skills/multi/dingtalk-aitable/references/aitable/aitable-record-update.md +++ b/skills/multi/dingtalk-aitable/references/aitable/aitable-record-update.md @@ -10,21 +10,39 @@ Example: --records '[{"recordId":"recXXX","cells":{"fldStatusId":"已完成"}}]' Flags: --base-id string Base ID (必填) - --records string 待更新记录 JSON 数组,单次最多 100 条 (必填,与 --records-file 二选一) + --records string 待更新记录 JSON 数组,单次最多 100 条;cells key 支持 fieldId 或当前表内唯一字段名,推荐 fieldId (必填,与 --records-file 二选一) --records-file string 从文件读取 records JSON(替代 --records,适合超长数据或 Windows 环境) --table-id string Table ID (必填) ``` 只需传入需修改的字段,未传入的保持原值。每条记录必须含 recordId 和 cells。 -## 高频错误 flag(LLM 极易踩坑,必读) +## cells key:优先使用 fieldId,也支持唯一字段名 -CLI **没有** `--record-id` 和 `--cells` 两个独立 flag,**只接受 `--records` 一个参数**,格式为 JSON 数组。 -即使只改一条记录,也必须包在数组里。 +`cells` 的 key 有两种写法: -| 错误(LLM 直觉) | 正确 | +- fieldId(推荐):不受字段重命名或重名影响,通过 `field get` 获取。 +- 当前表内唯一的字段名:按名称精确匹配;如果存在同名字段,必须改用 fieldId。 + +同一字段同时通过 fieldId 和字段名传入时,fieldId 对应的值优先。 + +```bash +# 推荐:fieldId +dws aitable record update --base-id --table-id \ + --records '[{"recordId":"recXXX","cells":{"fldStatusId":"已完成"}}]' --format json + +# 便捷写法:当前表内唯一字段名 +dws aitable record update --base-id --table-id \ + --records '[{"recordId":"recXXX","cells":{"状态":"已完成"}}]' --format json +``` + +## 推荐参数形式 + +公开、稳定的批量入口是 `--records`(或 `--records-file`),格式为 JSON 数组;即使只改一条记录,推荐也包在数组里。CLI 仍保留隐藏的 `--record-id` + `--cells` 兼容入口,但它不会出现在常规帮助中,自动化脚本应优先使用 `--records`。 + +| 不推荐或无效写法 | 推荐写法 | |---|---| -| `--record-id recXXX --cells '{"fldX":"值"}'` | `--records '[{"recordId":"recXXX","cells":{"fldX":"值"}}]'` | +| `--record-id recXXX --cells '{"fldX":"值"}'`(隐藏兼容入口) | `--records '[{"recordId":"recXXX","cells":{"fldX":"值"}}]'` | | `--id recXXX --data '{"fldX":"值"}'` | 同上 | | `--record-id recXXX --field fldX --value "新值"` | 同上 | @@ -33,8 +51,14 @@ CLI **没有** `--record-id` 和 `--cells` 两个独立 flag,**只接受 `--re ```bash dws aitable record update --base-id --table-id \ --records '[{"recordId":"","cells":{"":"新值"}}]' --format json + +# 从更新响应的 data.recordIds[] 提取成功记录 ID,并回读确认真实值 +dws aitable record query --base-id --table-id \ + --record-ids --format json ``` +更新响应不返回“受影响字段”;以 `data.recordIds[]` 确定成功记录,再用查询回读验证。 + ## 引号转义提示 - Linux/macOS:外层用单引号 `'[...]'`,内部 JSON 用双引号即可 diff --git a/skills/multi/dingtalk-aitable/references/aitable/aitable-related.md b/skills/multi/dingtalk-aitable/references/aitable/aitable-related.md deleted file mode 100644 index dd2282d5..00000000 --- a/skills/multi/dingtalk-aitable/references/aitable/aitable-related.md +++ /dev/null @@ -1,5 +0,0 @@ -# 相关产品 - -## 相关产品 - -- [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 deleted file mode 100644 index 18e5718c..00000000 --- a/skills/multi/dingtalk-aitable/references/aitable/aitable-scripts.md +++ /dev/null @@ -1,10 +0,0 @@ -# 自动化脚本 - -## 自动化脚本 - -| 脚本 | 场景 | -|------|------| -| [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 deleted file mode 100644 index 39fc31c1..00000000 --- a/skills/multi/dingtalk-aitable/references/aitable/aitable-table-index.md +++ /dev/null @@ -1,8 +0,0 @@ -# 数据表管理 - -| 命令 | 用途 | 必读参数 | -|---|---|---| -| `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 deleted file mode 100644 index e208b572..00000000 --- a/skills/multi/dingtalk-aitable/references/aitable/aitable-uri.md +++ /dev/null @@ -1,17 +0,0 @@ -# 文档地址 (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 deleted file mode 100644 index 2ed51eb1..00000000 --- a/skills/multi/dingtalk-aitable/references/aitable/aitable-url-to-baseid.md +++ /dev/null @@ -1,11 +0,0 @@ -# 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 6b22ec03..6c9b5feb 100644 --- a/skills/multi/dingtalk-aitable/references/aitable/aitable-view-config.md +++ b/skills/multi/dingtalk-aitable/references/aitable/aitable-view-config.md @@ -16,7 +16,34 @@ > card 在 Kanban 走 `kanbanCard`,在 Gallery 走 `galleryCard`,CLI 自动按 viewType dispatch(preflight 1 次 `get_views`)。timebar 仅 Gantt 支持;Calendar 服务端未暴露任何 timebar 配置。 -> **Gantt 视图必须两步创建**:`view create --view-type Gantt` 只创建空壳(`ganttTimebar: {}`),**必须**紧跟 `view update timebar --start-field <日期字段ID>` 绑定时间轴字段,否则视图打开是空白。`create_view` 的 `--config` 中传入 `ganttTimebar` 会被服务端忽略。 +> **Gantt 视图必须两步创建**:`view create --view-type Gantt` 只创建空壳(`ganttTimebar: {}`),**必须**紧跟 `view update timebar --start-field <日期字段ID>` 绑定时间轴字段,否则视图打开是空白。`view create --config` 不接受 `ganttTimebar`,请在创建后使用专属子命令。 + +## 创建:view create + +`--view-type` 支持 `Grid`、`Kanban`、`Gantt`、`Calendar`、`Gallery`、`FormDesigner`。创建时有两种方式设置可见字段: + +```bash +# 推荐:CSV typed flag;主字段必须排第一 +dws aitable view create --base-id BASE_ID --table-id TABLE_ID \ + --view-type Grid --name "任务视图" \ + --visible-field-ids fldPrimary,fldStatus,fldOwner + +# JSON 方式:可同时配置可见字段、筛选、排序和分组 +dws aitable view create --base-id BASE_ID --table-id TABLE_ID \ + --view-type Grid --name "任务视图" \ + --config '{"visibleFieldIds":["fldPrimary","fldStatus","fldOwner"],"sort":[{"fieldId":"fldStatus","direction":"asc"}]}' +``` + +创建阶段的 `--config` 是 JSON 对象,并且只接受以下 4 个 key: + +| key | 类型 | 说明 | +|---|---|---| +| `visibleFieldIds` | `string[]` | fieldId 数组,不接受字段名;至少一个,主字段必须排第一 | +| `filter` | `object[]` | 筛选规则数组;兼容单个 object,CLI 会自动包装为数组 | +| `sort` | `object[]` | 排序规则数组;兼容单个 object,CLI 会自动包装为数组 | +| `group` | `object[]` | 分组规则数组;兼容单个 object,CLI 会自动包装为数组 | + +`--visible-field-ids` 与 `config.visibleFieldIds` 互斥。描述使用独立的 `--desc '{"content":[]}'`;`description`、`fieldWidths`、`aggregate`、`kanbanCard`、`ganttTimebar`、`galleryCard` 等其他 key 会在调用服务端前被拒绝,并提示对应的 `view update` 子命令。 ## 读取:view get diff --git a/skills/multi/dingtalk-aitable/references/aitable/aitable-view-index.md b/skills/multi/dingtalk-aitable/references/aitable/aitable-view-index.md deleted file mode 100644 index b962437e..00000000 --- a/skills/multi/dingtalk-aitable/references/aitable/aitable-view-index.md +++ /dev/null @@ -1,6 +0,0 @@ -# 视图管理 - -| 命令 | 用途 | 详见 | -|---|---|---| -| `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 deleted file mode 100644 index 7e0b1cdd..00000000 --- a/skills/multi/dingtalk-aitable/references/aitable/aitable-workflow-index.md +++ /dev/null @@ -1,6 +0,0 @@ -# 自动化工作流 - -| 命令 | 用途 | 详见 | -|---|---|---| -| `workflow create/update` | 创建或全量更新并发布工作流 | [`aitable-workflow.md`](./aitable-workflow.md) | -| `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 1db8249b..6df46e2b 100644 --- a/skills/multi/dingtalk-aitable/references/aitable/aitable-workflow.md +++ b/skills/multi/dingtalk-aitable/references/aitable/aitable-workflow.md @@ -18,9 +18,15 @@ ## DSL 入参格式与最小 Demo -`workflow create/update` 的 `--dsl` 接收完整的 `workflow-dsl/v1` JSON object,不是局部 patch。支持内联 JSON、`@文件路径` 或 `-` 从 stdin 读取。 +`workflow create/update` 的 `--dsl` 接收钉钉 AI 表格 `workflow-dsl/v1` JSON object。当前同步范围只包含 create/update,没有新增 DSL 文档子命令;其他 Agent 可以直接使用下面的最小 Demo 理解调用格式。 -复杂工作流应先用 `table get` / `field get` / `view list` 确认真实 `sheetId`、`fieldId`、`viewId`,并检查所有 `next`、`loopEntry`、branch `to` 和 ref。下面是一个不依赖数据表字段的最小定时消息工作流: +复杂工作流还应注意: + +1. 如果 Agent 运行环境直接提供 AI 表格 MCP 的 `get_workflow_dsl_docs`,可用它获取最新 DSL Guide、Schema 和示例。 +2. 涉及数据表、字段或视图的节点,先用 `table get` / `field get` / `view list` 确认真实 `sheetId`、`fieldId`、`viewId`。 +3. create 和 update 都提交完整的 workflow-dsl/v1 JSON object,并检查所有 `next`、`loopEntry`、branch `to` 和 ref。 + +以下 Demo 表示“每天 09:00 触发,并向 Base 所有者发送消息”,不依赖数据表、字段或视图 ID: ```json { @@ -42,18 +48,16 @@ "type": "SendMessage", "data": { "title": "定时任务已触发", - "to": {"users": [{"ref": "$.system_node.ownerUserId"}]} + "to": { + "users": [{"ref": "$.system_node.ownerUserId"}] + } } } } } ``` -create 和 update 都必须同时满足 `status=success`、`data.valid=true`、`data.issues=[]` 才表示发布成功。 - -## 命令详情 - -### workflow create — 创建并发布工作流 +将上述 JSON 保存为 `workflow.json` 后创建工作流: ```bash dws aitable workflow create \ @@ -61,22 +65,69 @@ dws aitable workflow create \ --dsl @workflow.json \ --locale zh-CN \ --format json +``` +保存创建结果中的 `data.flowId`。更新时修改 `workflow.json` 中的完整目标定义,例如修改 `name`、`description` 或消息 `title`,然后调用: + +```bash +dws aitable workflow update \ + --base-id BASE_ID \ + --workflow-id FLOW_ID \ + --dsl @workflow.json \ + --locale zh-CN \ + --format json +``` + +create 和 update 都必须同时满足 `status=success`、`data.valid=true`、`data.issues=[]` 才表示发布成功;update 返回的 `data.flowId` 应与传入的 `FLOW_ID` 一致。以上仅为最小 Demo,复杂节点的 `type` 和 `data` 结构以钉钉 AI 表格 MCP 最新 DSL 文档为准。 + +## 命令详情 + +### workflow create — 创建并发布工作流 + +```bash +# 大 DSL 推荐从文件读取 +dws aitable workflow create \ + --base-id BASE_ID \ + --dsl @workflow.json \ + --locale zh-CN \ + --format json + +# 也支持 stdin cat workflow.json | dws aitable workflow create --base-id BASE_ID --dsl - --format json ``` | flag | 必填 | 说明 | |------|------|------| | `--base-id` | 是 | 所属 Base ID | -| `--dsl` | 是 | workflow-dsl/v1 JSON object;支持内联、`@filepath`、`-` stdin | +| `--dsl` | 是 | workflow-dsl/v1 JSON object;支持内联 JSON、`@filepath`、`-` stdin | | `--locale` | 否 | 请求语言,如 `zh-CN` / `zh_CN` | -`create` 非幂等,CLI 不自动重试。若网络中断导致结果不确定,先 `workflow list` 按名称确认是否已经创建,再决定是否重试。`status=success` 只说明服务正常返回;`data.valid=false` 仍是发布失败,必须读取 `issues`。 +创建成功返回发布结果: + +```json +{ + "status": "success", + "data": { + "valid": true, + "flowId": "G-FLOW-XXXXXX", + "flowSchema": {}, + "stepNodeIds": {}, + "referenceMap": {}, + "issues": [] + } +} +``` + +关键语义: + +- `create` 非幂等,CLI 不自动重试。若网络中断导致结果不确定,先 `workflow list` 按名称确认是否已创建,再决定是否重试。 +- `status=success` 只说明 workflow-edit 正常返回;如果 `data.valid=false`,仍表示 DSL 未通过校验或发布,必须读取 `issues` 修正。 +- 创建并发布后,用 `workflow list` 确认 `status`;需要运行但状态为 `STOP` 时再调用 `workflow enable`。 ### workflow update — 更新并发布工作流 ```bash -# 先留底,再提交完整目标 DSL +# 先留底现有详情,再提交完整目标 DSL dws aitable workflow get --base-id BASE_ID --workflow-id WORKFLOW_ID --format json > /tmp/workflow-backup.json dws aitable workflow update \ --base-id BASE_ID \ @@ -93,7 +144,7 @@ dws aitable workflow update \ | `--dsl` | 是 | 完整目标 workflow-dsl/v1 JSON object;支持内联、`@filepath`、`-` stdin | | `--locale` | 否 | 请求语言,如 `zh-CN` / `zh_CN` | -update 返回结构与 create 相同,并会对瞬态错误重试。成功时 `data.flowId` 应与传入的工作流 ID 一致;随后用 `workflow get/list` 验证发布结果和运行状态。 +返回结构与 create 相同,成功时 `flowId` 应为目标工作流。update 使用 AI 表格瞬态错误重试;最终仍必须检查 `data.valid` 和 `issues`,并用 `workflow get/list` 验证发布结果与运行状态。 ### workflow list — 列出工作流 @@ -226,10 +277,14 @@ dws aitable workflow disable --base-id BASE_ID --workflow-id WORKFLOW_ID --yes - ### 创建并确认一个工作流 ```bash +# 1. 按本文 DSL Demo 生成 /tmp/workflow.json dws aitable workflow create --base-id BASE_ID --dsl @/tmp/workflow.json --locale zh-CN --format json \ | tee /tmp/workflow-result.json +# 2. valid 必须为 true;保存 flowId jq '{valid: .data.valid, flowId: .data.flowId, issues: .data.issues}' /tmp/workflow-result.json + +# 3. 确认运行状态,需要时显式启用 FLOW_ID=$(jq -r '.data.flowId' /tmp/workflow-result.json) dws aitable workflow list --base-id BASE_ID --format json \ | jq --arg id "$FLOW_ID" '.data.list[] | select(.flowId == $id) | {flowId, name, status}' @@ -268,9 +323,10 @@ done ## 注意事项 - `--workflow-id` 接受的就是 `list` 返回里的 `flowId`(同值,CLI 屏蔽了服务端字段名差异)。 -- create / update 的 `--dsl` 必须是 JSON object,不能传数组、二次字符串化 JSON 或 `flowSchema`。 +- create / update 的 `--dsl` 必须是 JSON object,不能传数组、字符串化的二次 JSON 或 FlowSchema。 +- 本文 Demo 可直接用于最小定时消息工作流;复杂节点应以钉钉 AI 表格 MCP 最新 DSL 文档为准。 - `status=success` 且 `data.valid=false` 仍是 DSL 校验失败;`issues` 才是下一步修复依据。 -- create 不自动重试;update 仅对网络、5xx、`retryable:true` 等瞬态错误自动重试。 +- create 不自动重试;update 仅对网络/5xx/`retryable:true` 瞬态错误自动重试。 - enable / disable 出参里的 `enabled` / `disabled` 是 **动作确认 flag**,不是当前状态字段。要确认真生效请走 `workflow list` 查 `status`。 - `workflow get` 的 `flowSchema` 结构随触发器/动作类型变化,不要假设固定字段。 - 删除、运行历史和手动触发当前仍未开放。 diff --git a/skills/multi/dingtalk-aitable/references/field-rules.md b/skills/multi/dingtalk-aitable/references/field-rules.md index 7f1fd6ec..87725163 100644 --- a/skills/multi/dingtalk-aitable/references/field-rules.md +++ b/skills/multi/dingtalk-aitable/references/field-rules.md @@ -7,7 +7,7 @@ | "创建一个新表格 (Base)" | `base create` | 不是 `table create` | | "在表格里加一个数据表" | `table create` | 不是 `base create` | | "看看表格里有哪些表" | `base get` | 不是 `field get` | -| "看看表里有哪些列" | `field get` / `table get` | 不是 `base get` | +| "看看表里有哪些列" | `field get` | 不是 `base get` | | "搜索表格" (找 Base) | `base search` | 不是 `record query` | | "搜索记录" (查表内数据) | `record query` | 不是 `base search` | | "删掉这个数据表" | `table delete` | 不是 `record delete` | @@ -74,7 +74,7 @@ dws aitable table create --base-id --name "产品图片" \ ## 只读字段 (不可写入) -以下类型的字段不可写入, 执行 `field get` / `table get` 后识别并跳过: +以下类型的字段不可写入, 执行 `field get` 后识别并跳过: - 创建时间 / 修改时间 (系统自动) - 创建人 / 修改人 (系统自动) - 自动编号 diff --git a/skills/multi/dingtalk-aitable/references/url-patterns.md b/skills/multi/dingtalk-aitable/references/url-patterns.md index 9498faa0..f25880ef 100644 --- a/skills/multi/dingtalk-aitable/references/url-patterns.md +++ b/skills/multi/dingtalk-aitable/references/url-patterns.md @@ -1,8 +1,21 @@ # URL 格式与处理规范 -## alidocs URL 分流决策(必须首先执行) +## 路由第 0 步:意图直达(优先级高于 URL 探测) -收到 `alidocs.dingtalk.com` URL 时,**必须按以下顺序判断,禁止跳过**: +用户已经明确表达某产品的内容意图时,直接进入对应产品场域,不要先做 URL 类型 +探测。尤其: + +- 明确提到 Markdown / `.md` 文件的读取或修改,按普通文件走 `drive` 场域: + 先 `dws drive download` 下载到本地处理,再用 `dws drive upload` 回传。 +- 明确“读这篇文档 / 编辑文档正文”进入 `doc`;明确“看这个在线表格数据”进入 + `sheet`。 + +仅当用户只粘贴 URL、没有明确产品意图,或意图与链接类型可能冲突时,才执行下方 +类型探测。 + +## alidocs URL 分流决策(意图不明确时执行) + +收到 `alidocs.dingtalk.com` URL 且无法从指令判断产品时,必须按以下顺序判断: 1. URL 路径含 `/i/p/` → **分享短链**,禁止调用 `dws doc` 任何子命令 → 按下方 [分享短链处理](#分享短链处理) 执行 2. URL 路径含 `/i/nodes/` → **节点链接**,需探测类型 → 按下方 [alidocs URL 类型探测流程](#alidocs-url-类型探测流程) 执行 @@ -80,20 +93,23 @@ dws doc read --node "https://alidocs.dingtalk.com/i/p/Y7kmbokZp3pgGLq2/docs/AY39 ### 探测步骤 ``` -Step 1 → dws doc info --node "" --format json -Step 2 → 从返回中提取 contentType、extension、nodeType 字段 +Step 1 → dws drive info --node "" --format json +Step 2 → 从返回中提取 extension、nodeType 字段 Step 3 → 按下方路由规则映射到对应产品 ``` +> 路由依据是 `extension`,不是 `contentType`。`drive info` 检测到 +> `adoc` / `axls` / `able` 时会自动补充在线文档信息。 + ### 路由映射表 | 条件 | 路由到产品 | 后续操作 | |------|-----------|---------| -| `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` 下载,返回文件下载链接 | +| `extension=adoc` | `doc` | 加载 `dingtalk-doc` 操作内容 | +| `extension=axls` | `sheet` | 加载 `dingtalk-misc` 的 `references/sheet.md` 操作(仅 `axls` 在线电子表格) | +| `extension=able` | `aitable` | 将 nodeId 作为 baseId,加载 `dingtalk-aitable` 操作 | +| `extension=xlsx` / `xls` / `xlsm` / `csv` | `drive` | 必须用 `dws drive download` 下载到本地处理,禁止走 `sheet` | +| `nodeType=file`(非在线文档扩展名,含 `md`) | `drive` | 下载用 `dws drive download --node --output --format json`;上传/覆盖用 `dws drive upload` | | `nodeType=folder` | `drive` / `wiki` | 调用 `dws drive list --workspace ` 或 `dws wiki node list` 列出子节点 | | 以上均不匹配 | — | 告知用户当前暂不支持该类型 | @@ -106,16 +122,16 @@ Step 3 → 按下方路由规则映射到对应产品 ```bash # 用户传入: https://alidocs.dingtalk.com/i/nodes/abc123 -dws doc info --node "https://alidocs.dingtalk.com/i/nodes/abc123" --format json +dws drive info --node "https://alidocs.dingtalk.com/i/nodes/abc123" --format json -# 返回 contentType=ALIDOC, extension=axls → 在线电子表格,路由到 sheet +# 返回 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" +# 返回 extension=xlsx/xls/csv → 本地表格文件,必须下载处理(禁止走 sheet) +dws drive download --node "https://alidocs.dingtalk.com/i/nodes/xlsx456" --output --format json -# 返回 contentType≠ALIDOC, nodeType=file → 普通文件,下载 -dws drive download --node "https://alidocs.dingtalk.com/i/nodes/def456" +# 返回 nodeType=file → 普通文件,下载 +dws drive download --node "https://alidocs.dingtalk.com/i/nodes/def456" --output --format json # 返回 nodeType=folder → 文件夹,列出子节点 dws drive list --workspace --format json @@ -126,4 +142,3 @@ 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 08f60340..caa66f80 100644 --- a/skills/multi/dingtalk-aitable/scripts/aitable_export_via_task.py +++ b/skills/multi/dingtalk-aitable/scripts/aitable_export_via_task.py @@ -88,12 +88,10 @@ def build_start_args(args: argparse.Namespace) -> list[str]: args.base_id, "--scope", args.scope, - "--export-format", + "--format", args.export_format, "--timeout-ms", str(args.timeout_ms), - "--format", - "json", ] if args.table_id: cmd.extend(["--table-id", args.table_id]) @@ -157,8 +155,6 @@ def main() -> None: task_id, "--timeout-ms", str(args.poll_timeout_ms), - "--format", - "json", ], timeout_sec=max(120, int(args.poll_timeout_ms / 1000) + 60), ) diff --git a/skills/multi/dingtalk-aitable/scripts/bulk_add_fields.py b/skills/multi/dingtalk-aitable/scripts/bulk_add_fields.py index 5783d9b4..3ba88313 100644 --- a/skills/multi/dingtalk-aitable/scripts/bulk_add_fields.py +++ b/skills/multi/dingtalk-aitable/scripts/bulk_add_fields.py @@ -29,7 +29,7 @@ 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', diff --git a/skills/multi/dingtalk-aitable/scripts/import_records.py b/skills/multi/dingtalk-aitable/scripts/import_records.py index 7dd68dbb..a275806e 100644 --- a/skills/multi/dingtalk-aitable/scripts/import_records.py +++ b/skills/multi/dingtalk-aitable/scripts/import_records.py @@ -35,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 diff --git a/skills/multi/dingtalk-attendance/SKILL.md b/skills/multi/dingtalk-attendance/SKILL.md deleted file mode 100644 index 6e28e9e3..00000000 --- a/skills/multi/dingtalk-attendance/SKILL.md +++ /dev/null @@ -1,81 +0,0 @@ ---- -name: dingtalk-attendance -description: 钉钉考勤、打卡、排班与考勤报表。Use when 用户说考勤/打卡记录/查打卡/排班/班次/考勤报表/导出考勤/出勤汇总/考勤明细/迟到早退统计/全员考勤数据/某月考勤统计/考勤表格/考勤Excel。不做日程会议(走 dingtalk-calendar)、日报周报(走 dingtalk-report)、审批请假流程(走 dingtalk-oa)。命令前缀:dws attendance。 -cli_version: ">=0.2.14" -metadata: - category: product - stability: experimental - requires: - bins: - - dws ---- - -# 钉钉考勤 Skill - -> 🧪 **EXPERIMENTAL · 试验版 / Preview** — multi 模式当前未达 stable 标准。全部 dingtalk-* skill 已通过 dispatch verifier,但接口、命名、跨 skill 引用后续可能调整;生产 / 共享环境请优先使用 mono 模式(`dws skill setup --mode mono`)。问题请提 issue 反馈。 - -## 前置条件 — 执行操作前必读 - -> **`use_skill(dws-shared)`** — 认证、全局参数(`--format json` / `--yes`)、错误码、URL 模板、跨产品消歧、安全规则与 capability 边界。**执行任何 `dws` 命令前先读;** 单产品的清晰命令可直接用本 skill。 - - - -> 渐进式参考:[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)。 - - -## Shortcuts(无专用脚本/recipe 时优先) - -以下 shortcut 来自独立于 Runtime Schema 的公开 catalog。先按本 skill 的意图表、脚本和 recipe 路由:存在精确覆盖该场景的专用脚本/recipe 时按其执行;否则用户意图命中时,shortcut 优先于手写原子命令。用 `dws shortcut list --service attendance --format json` 读取参数、约束、风险和示例,并以 `dws attendance --help` 核对当前 Cobra flags;不要对 `+` 路径调用 `dws schema`。 - -| Shortcut | 风险 | 适用场景 | -|---|---|---| -| `dws attendance +check-record` | read | 查询用户打卡流水(打卡时间/地点/定位方式) | -| `dws attendance +check-result` | read | 查询用户打卡结果(迟到/早退/缺卡等) | -| `dws attendance +get-adjustment-rule` | read | 根据补卡规则主键 ID 查询补卡规则详情 | -| `dws attendance +get-approve-template` | read | 查询补卡/请假/加班/外出/出差审批提交链接 | -| `dws attendance +get-checkin-record` | read | 查询指定员工一段时间内的签到记录 | -| `dws attendance +get-leave-records` | read | 查询指定员工的假期余额变更记录 | -| `dws attendance +get-overtime-rule` | read | 根据加班规则主键 ID 查询加班规则详情 | -| `dws attendance +get-schedule` | read | 获取指定用户一段时间内的排班记录 | -| `dws attendance +get-self-setting` | read | 查询个人规则设置(打卡提醒/极速打卡/缺卡提醒等) | -| `dws attendance +get-summary` | read | 查询某个人的考勤统计摘要(周/月) | -| `dws attendance +list-approve` | read | 查询用户考勤审批单(补卡/加班/请假/出差外出) | -| `dws attendance +list-leave-types` | read | 查询当前用户可用的假期规则列表 | -| `dws attendance +my-attendance` | read | 查我今天的考勤打卡记录(打卡流水,自动解析当前用户) | -| `dws attendance +query-report-data` | read | 根据字段查询考勤报表数据(仅管理员) | -| `dws attendance +search-adjustment-rule` | read | 查询当前用户可管理的补卡规则列表 | -| `dws attendance +search-class` | read | 查询当前用户可管理的班次详情列表 | -| `dws attendance +search-group` | read | 查询当前用户可管理的考勤组列表 | -| `dws attendance +search-overtime-rule` | read | 查询当前用户可管理的加班规则列表 | -| `dws attendance +this-month` | read | 查我本月的考勤打卡记录(打卡流水,自动解析当前用户) | - - -## 意图表 - -| 用户说 | 命令 | -|--------|------| -| "查我自己的打卡 / 某天考勤" | `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 ...`(需确认) | - -## 高频硬约束 - -- 不要在读完 [attendance.md](references/attendance-commands.md) 前判断"CLI 不支持"。`class create`、`schedule import`、`group update-members`、`group update` 都是已支持写操作,但必须先展示摘要并等用户确认。 -- 查询迟到/缺勤名单时,空打卡结果不等于"没人迟到"。必须结合排班、`NotSigned`、`Absenteeism`、无记录人员分别说明;数据缺失要标为"无记录/无法判断",不要归为正常。 -- 做部门 Top N 排名时,用户要求前 N 名就必须输出 N 个部门;无打卡记录或无可计算数据的部门按 0 或"无数据"保留在排名中,不能只输出有数据的少数部门。 -- 处理请假/补卡/加班审批时,先用考勤审批模板或 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-constraints.md b/skills/multi/dingtalk-attendance/references/attendance-constraints.md deleted file mode 100644 index 7397b708..00000000 --- a/skills/multi/dingtalk-attendance/references/attendance-constraints.md +++ /dev/null @@ -1,10 +0,0 @@ -# 严格约束 - -## 严格约束 -- 不要凭历史记忆复用 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 deleted file mode 100644 index 2901b02e..00000000 --- a/skills/multi/dingtalk-attendance/references/attendance-context.md +++ /dev/null @@ -1,13 +0,0 @@ -# 上下文传递表 - -## 上下文传递表 -| 操作 | 提取 | 用于 | -|------|------|------| -| `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 deleted file mode 100644 index 01c5b06c..00000000 --- a/skills/multi/dingtalk-attendance/references/attendance-index.md +++ /dev/null @@ -1,11 +0,0 @@ -# 考勤能力索引 - -| 主题 | 详见 | -|---|---| -| 日期范围计算、签到与打卡分流 | [`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 deleted file mode 100644 index e0cbb0b4..00000000 --- a/skills/multi/dingtalk-attendance/references/attendance-intent.md +++ /dev/null @@ -1,62 +0,0 @@ -# 意图判断 - -## 意图判断 - -用户说"签到记录/签到数据/签到明细/外勤签到/导出签到" → `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 列表,则批量调用 `dws contact user get --ids `(或等价通讯录查询),在最终输出中展示员工姓名而非裸 userId -用户说"考勤组成员/打卡地址/打卡wifi/打卡蓝牙" → `group filtered-get`(按需查询,节省成本),若返回结果中含成员 userId 列表,则批量调用 `dws contact user get --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 deleted file mode 100644 index 86a109af..00000000 --- a/skills/multi/dingtalk-attendance/references/attendance-notes.md +++ /dev/null @@ -1,107 +0,0 @@ -# 注意事项 - -## 注意事项 -**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 --users USER_ID --start DATE --end 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 --users 03642229451220076 --start 2026-05-13 --end 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-routing.md b/skills/multi/dingtalk-attendance/references/attendance-routing.md deleted file mode 100644 index 1a27c629..00000000 --- a/skills/multi/dingtalk-attendance/references/attendance-routing.md +++ /dev/null @@ -1,55 +0,0 @@ -# 日期范围与签到优先路由 - -> **【必读】日期范围严格计算规则 — 所有含 --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-scripts.md b/skills/multi/dingtalk-attendance/references/attendance-scripts.md deleted file mode 100644 index 660531e3..00000000 --- a/skills/multi/dingtalk-attendance/references/attendance-scripts.md +++ /dev/null @@ -1,21 +0,0 @@ -# 自动化脚本 - -## 自动化脚本 - -| 脚本 | 场景 | 用法 | -|------|------|------| -| [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-workflow.md b/skills/multi/dingtalk-attendance/references/attendance-workflow.md deleted file mode 100644 index 8997ac6f..00000000 --- a/skills/multi/dingtalk-attendance/references/attendance-workflow.md +++ /dev/null @@ -1,148 +0,0 @@ -# 核心工作流 - -## 核心工作流 - -```bash -# 导入排班记录 -dws attendance schedule import --groupId 123456 \ - --scheduleVOS '[{"userId":"user001","classId":123,"workDate":"2026-04-22","checkBeginTime":"09:00","checkEndTime":"18:00"}]' \ - --user-say-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 deleted file mode 100644 index 3015c566..00000000 --- a/skills/multi/dingtalk-attendance/references/attendance.md +++ /dev/null @@ -1,11 +0,0 @@ -# 考勤 (attendance) 命令参考 - -> 渐进式兼容入口:新任务优先从 [考勤能力索引](./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-calendar/SKILL.md b/skills/multi/dingtalk-calendar/SKILL.md index 66ededd4..4d6554cc 100644 --- a/skills/multi/dingtalk-calendar/SKILL.md +++ b/skills/multi/dingtalk-calendar/SKILL.md @@ -1,10 +1,9 @@ --- name: dingtalk-calendar -description: 钉钉日历与会议室。Use when 用户说 约会议/查日程/订会议室/查闲忙/加参会人/改期/取消会议/今天的日程/本周日程/共同空闲。视频会议发起/邀请入会/会中控制当前 CLI 不支持,应提示在钉钉客户端操作;AI 听记走 dingtalk-minutes,待办任务走 dingtalk-todo。命令前缀:dws calendar。 -cli_version: ">=0.2.14" +description: 钉钉日历与会议室。Use when 用户说 约会议/查日程/订会议室/查闲忙/加参会人/改期/取消会议/今天的日程/本周日程/共同空闲。不做视频会议发起/邀请入会/会中控制(走 dingtalk-misc)、AI 听记(走 dingtalk-minutes)、待办任务(走 dingtalk-todo)。命令前缀:dws calendar。 metadata: + cli_version: ">=0.2.14" category: product - stability: experimental requires: bins: - dws @@ -12,20 +11,16 @@ metadata: # 钉钉日历 Skill -> 🧪 **EXPERIMENTAL · 试验版 / Preview** — multi 模式当前未达 stable 标准。全部 dingtalk-* skill 已通过 dispatch verifier,但接口、命名、跨 skill 引用后续可能调整;生产 / 共享环境请优先使用 mono 模式(`dws skill setup --mode mono`)。问题请提 issue 反馈。 - ## 前置条件 — 执行操作前必读 -> **`use_skill(dws-shared)`** — 认证、全局参数(`--format json` / `--yes`)、错误码、URL 模板、跨产品消歧、安全规则与 capability 边界。**执行任何 `dws` 命令前先读;** 单产品的清晰命令可直接用本 skill。 - - +> **CRITICAL — 执行任何 `dws` 操作前,MUST 先用 Read 工具完整读取 [`dws-shared`](../dws-shared/SKILL.md)。**该轻量文件包含全局执行契约、安全底线及 shared references 的按需加载导航;不要预加载其全部 references。 > 命令参考:[calendar.md](references/calendar.md);剧本:[03-meeting.md](references/03-meeting.md)。 ## Shortcuts(无专用脚本/recipe 时优先) -以下 shortcut 来自独立于 Runtime Schema 的公开 catalog。先按本 skill 的意图表、脚本和 recipe 路由:存在精确覆盖该场景的专用脚本/recipe 时按其执行;否则用户意图命中时,shortcut 优先于手写原子命令。用 `dws shortcut list --service calendar --format json` 读取参数、约束、风险和示例,并以 `dws calendar --help` 核对当前 Cobra flags;不要对 `+` 路径调用 `dws schema`。 +以下命令来自独立于 Runtime Schema 的公开 catalog。先运行 `dws shortcut list --service calendar --format json` 读取完整契约,再用 `dws calendar --help` 核对 flags;不要对 `+` 路径调用 `dws schema`。 | Shortcut | 风险 | 适用场景 | |---|---|---| @@ -58,7 +53,7 @@ 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 event list --start "" --end ""` | +| "查闲忙" | `dws calendar busy search --users --start "" --end ""` | | "加参会人" / "订房" / "取消" | `dws calendar attendee add` / `room add` / `event delete` | ## 标准 SOP(必遵流程) @@ -81,7 +76,7 @@ metadata: 1. **解析与会人(必须)**:对每个姓名 `dws aisearch person --keyword "<姓名>" --dimension name --format json` 取 `userId`,多人逗号拼接。 2. **执行(必须)**:`dws calendar event create --title "<主题>" --start "" --end "" --attendees --format json`(按需加 `--location`/`--desc`/`--rooms`)。 -3. **验证(必须)**:从返回取 `eventId`,可 `dws calendar event list --start "" --end "" --format json` 复核。 +3. **验证(必须)**:从返回 `result.id` 取日程 ID(下游参数语义称 `eventId`),再执行 `dws calendar event list --start "" --end "" --format json` 复核标题、描述和时段。 **禁止**:跳过与会人 userId 解析直接传姓名、编造会议室 roomId。 @@ -99,22 +94,22 @@ metadata: ## 执行硬约束 - 多轮日程任务必须保留 `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`。 ## 跨产品协作 -- 视频会议发起 / 入会链接 / 邀请入会 / 会中控制 → 当前 CLI 不支持,请在钉钉客户端操作;预约日程仍走 `calendar` +- 视频会议发起 / 入会链接 / 邀请入会 / 会中控制 → 切到 `dingtalk-misc`(`references/conference.md`) - 会后摘要 / 待办 → 切到 `dingtalk-minutes` - 参会人按人名 → 先用 `dingtalk-aisearch` 解析 ## 注意 `schedule-meeting` 必须读 [03-meeting.md](references/03-meeting.md) 中的「两准则」「搜房失败硬门禁」,禁止假设 `roomId`。 -## 局部意图与 Recipe +## 局部意图与短流程 -- [局部意图消歧](references/intent-guide.md);[Lite Recipe](references/lite-recipes.md)。 +- [局部意图消歧](references/intent-guide.md);[短流程](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 26de916f..ffc6c708 100644 --- a/skills/multi/dingtalk-calendar/references/03-meeting.md +++ b/skills/multi/dingtalk-calendar/references/03-meeting.md @@ -1,47 +1,56 @@ # 会议管理(日程与会议室) -> lite recipe(`list-today-meetings`、`check-users-busy`)见 [lite-recipes.md](./lite-recipes.md);视频会议发起、邀请入会与会中控制当前 CLI 不支持,请在钉钉客户端操作。列表类操作须遵循 [calendar.md](./calendar.md) **「CLI 命令树与黄金路径」**,禁止无子命令的 `dws calendar` 或臆造 `calendar list`(见该文 **「反模式(禁止)」**)。预约会议须遵循本文件的时段、会议室与失败收束规则。 +> lite recipe(`list-today-meetings`、`check-users-busy`)见 [lite-recipes.md](./lite-recipes.md);`start-conference`、`invite-participant`、`share-screen` 见 `dingtalk-misc`(`references/conference.md`)。列表类操作须遵循 [calendar.md](./calendar.md) **「CLI 命令树与黄金路径」**,禁止无子命令的 `dws calendar` 或臆造 `calendar list`(见该文 **「反模式(禁止)」**)。**`schedule-meeting` 不做内联**:须读本文件 **「两准则」「搜房失败硬门禁」** 及下表 **schedule-meeting** 行全文。 > **听记、会后待办、摘要分享** 见 `dingtalk-minutes/references/07-minutes.md`。 ## 日程与会议室两准则(强制) 1. **时段**:用户已明确会议起止时间 → **禁止**自动改期、禁止用闲忙结果或「推荐时段」覆盖用户给定时段;只能在此时段内建日程、订会议室;该时段内无可用或指定资源不可用 → **立刻如实告知**,不得偷偷换时间段再试。 -2. **会议室**:用户点名具体会议室 → **禁止**换其他会议室;在用户给定时段内查无该房 → **立刻告知**。**用户未给出时段时,必须先显式向用户追问具体开始/结束时间;禁止默认用「当前时刻至当日 23:59:59」之类窗口代查。** **`calendar_schedule_meeting.py`**:仅需 `--title`、`--start`、`--end`;先创建日程,再邀请参会人,最后搜房/订房。未给会议室范围时,`--book-room` 为 **单次**无 `--group-id`、带 `--start` / `--end` 的 `room search`,取返回的**第一个**会议室并 `room add`;无结果则告警、不删日程。**若用户明确限定楼层/楼宇/园区/分组,应先用 `room list-groups` 解析允许的 `group-id`,再把这些 `group-id` 传给脚本 `--room-group-id`(或手工 `room search --group-id ...`);脚本只会在这些 group 内查找,若无空房则直接返回。对于同一地点(同园区/楼栋/楼层)的会议室,必须优先锁定最相关、最贴近该地点的承载 group;该 group 查无 roomId/空房,即可判定该地点当前时段无可订会议室,**不得**再去别的无关 group 继续碰运气,因为同一地点的会议室只会挂在其所属 group 下。** **在组织内、按早停规则已把应查的分组(或未限范围时的根目录一次查询)全部查完仍无可用会议室时,必须立即向用户说明「当前时段没有可预订的会议室」或「范围内未检索到可用会议室/资源」并收束,禁止继续扩区、换参重试或虚构有房。** 手工 `room search` **禁止**为试出空闲擅自改日或拉长时间窗。 +2. **会议室**:用户点名具体会议室 → **禁止**换其他会议室;在用户给定时段内查无该房 → **立刻告知**。**用户未给出时段时,必须先显式向用户追问具体开始/结束时间;禁止默认用「当前时刻至当日 23:59:59」之类窗口代查。** **`calendar_schedule_meeting.py`**:仅需 `--title`、`--start`、`--end`;先创建日程,再邀请参会人,最后搜房/订房。未给会议室范围时,`--book-room` 为 **单次**无 `--group-id` 的 `room search --available`,取返回的**第一个**会议室并 `room add`;无结果则告警、不删日程。**若用户明确限定楼层/楼宇/园区/分组,应先用 `room list-groups` 解析允许的 `group-id`,再把这些 `group-id` 传给脚本 `--room-group-id`(或手工 `room search --group-id ...`);脚本只会在这些 group 内查找,若无空房则直接返回。对于同一地点(同园区/楼栋/楼层)的会议室,必须优先锁定最相关、最贴近该地点的承载 group;该 group 查无 roomId/空房,即可判定该地点当前时段无可订会议室,**不得**再去别的无关 group 继续碰运气,因为同一地点的会议室只会挂在其所属 group 下。** **在组织内、按早停规则已把应查的分组(或未限范围时的根目录一次查询)全部查完仍无可用会议室时,必须立即向用户说明「当前时段没有可预订的会议室」或「范围内未检索到可用会议室/资源」并收束,禁止继续扩区、换参重试或虚构有房。** 手工 `room search` **禁止**为试出空闲擅自改日或拉长时间窗。 ### 会议室搜索早停 > 专用于 `calendar room list-groups` / `room search` / `room add`;与通用规范「无新参数不重复 search」一致。 -**`room search` 时间段模式**(传入 `--start` / `--end`):返回的是在**该整段时段内**可被预订的空闲会议室(不是「有一段空就算」);脚本与用户手工选房均应沿用同一时间窗,避免误以为分段凑满即等价于整段可用。 +**`room search --available`**(与传入的 `--start` / `--end` 配对):返回的是在**该整段时段内**可被预订的空闲会议室(不是「有一段空就算」);脚本与用户手工选房均应沿用同一时间窗,避免误以为分段凑满即等价于整段可用。 -**`dws calendar room search` 合法参数**(与 [calendar.md](./calendar.md) 一致):按需使用 `--start`、`--end`、`--group-id`、`--room-name`、`--limit`、`--page` 和全局 `--format json`;**禁止使用 `--query` 或 `--available`**,否则会报 unknown flag。传 `--start` / `--end` 即进入可用会议室搜索模式。 +**`dws calendar room search` 合法参数**(与 [calendar.md](./calendar.md) 一致):仅 `--start`、`--end`、`--group-id`(可选)、`--available`(可选)、`--format json` 等;**禁止使用 `--query`**,否则会报 `unknown flag: --query`。 **地点归组早停**:若用户给的是同一地点范围(如“西溪园区 C6 楼 3-5 层”或具体楼层/楼栋),先用 `room list-groups` 找到**最相关的承载 group**(通常是该楼层;若楼层下无会议室则为直接挂会议室的上一级)。在这个最相关 group 下查不到有效 `rooms[].roomId` 或空房时,**不得**再跳去别的同级/异地 group 继续搜;同一地点的会议室不会散落在别的 group 里。只有用户明确放宽到别的楼层、楼栋或园区,才能重新解析新的 group 并继续。 -**用户点名具体会议室(如「C6-4-06-N / 贡嘎山」)**:**不要**尝试 `room search --query "<名称>"`;**禁止**把用户原文(含「C6-4-06-N 贡嘎山」整句)或展示名当作 `room add --rooms` 的 `roomId`。用户输入**几乎从不会是**有效 `roomId`。须先 `dws calendar room list-groups` 定位所在楼层/分组的 `group-id`,再 `dws calendar room search --start "" --end "" --group-id --format json`(带时间范围即查询可用会议室),在返回 `rooms[]` 中对 `roomName`、`name` 等与用户表述匹配,**仅**取 JSON 里的 `roomId`(典型为小写十六进制串,长度以返回为准),最后 `dws calendar room add --event --rooms `。该时段无匹配或房间忙 → 如实告知;**禁止**为通过校验而编造、拼接或猜测 `roomId`。 +**用户点名具体会议室(如「C6-4-06-N / 贡嘎山」)**:**不要**尝试 `room search --query "<名称>"`;**禁止**把用户原文(含「C6-4-06-N 贡嘎山」整句)或展示名当作 `room add --rooms` 的 `roomId`。用户输入**几乎从不会是**有效 `roomId`。须先 `dws calendar room list-groups` 定位所在楼层/分组的 `group-id`,再 `dws calendar room search --start "" --end "" --group-id [--available] --format json`,在返回 `rooms[]` 中对 `roomName`、`name` 等与用户表述匹配,**仅**取 JSON 里的 `roomId`(典型为小写十六进制串,长度以返回为准),最后 `dws calendar room add --event --rooms `。该时段无匹配或房间忙 → 如实告知;**禁止**为通过校验而编造、拼接或猜测 `roomId`。 ### 搜房失败硬门禁(园区/范围搜尽仍无 roomId) -在用户限定的园区、楼宇、楼层或固定分组内,已按早停规则**逐组 `room search` 查完**仍得不到任何有效 `rooms[].roomId`(或无任何空闲房)→ **立即停止**,向用户**明确报错/失败结论**(例如:该时段在指定范围内未检索到可预订会议室或无法获得 roomId),当前预订链路结束。 +在用户限定的园区、楼宇、楼层或固定分组内,已按早停规则**逐组 `room search` 查完**仍得不到任何有效 `rooms[].roomId`(或无任何空闲房)→ **立即停止**,向用户**明确报错/失败结论**(例如:该时段在指定范围内未检索到可预订会议室或无法获得 roomId),**本回合订房流程结束**。 -**失败处理**:明确说明“指定范围/指定会议室在该时段未找到可预订会议室,因此当前无法完成预订”。只有用户同意放宽地点范围、改时间或接受不订会议室后,才可按新条件继续。 +**用户汇报硬门禁**:一旦触发上面的失败条件,**下一条对外输出必须直接面向用户汇报结果**,不得继续在会话里自言自语式地延长推理。允许的后续只有两类: +1. **失败汇报**:明确说明“指定范围/指定会议室在该时段未找到可预订会议室,因此当前无法完成预订” +2. **确认放宽条件**:仅在需要继续推进时,明确问用户是否放宽地点范围、改时间或接受不订会议室 -空结果只表示当前授权范围内没有返回合法 `roomId`;不要推测隐藏映射、环境差异或未经用户授权的新搜索范围。 +以下表述/行为视为**违例**:继续写“让我再试一次”“也许是 Mock/测试环境”“可能存在预设 roomId 映射”“我去别的 group 看看”“我换个时间验证一下”“我先看看脚本/示例还能不能推断出 roomId”。 以下行为**一律禁止**(与是否「想多试一次」无关):编造/假设 `roomId` 格式做「预订测试」;在**没有**合法 `roomId` 时调用 `room add` 试探错误详情;拉 `event get` / 日程详情等试图**绕开** `room search` 推断 roomId;换无关园区、扩大关键词、换工具名做未经用户授权的新搜索。 -出现空结果、`roomId invalid` 或授权范围查尽时,停止当前预订链路;不得沿原假设重复搜索。后续只能报告失败,或在用户确认新范围/新时间后重新搜索。 +**失败后强制回读**:若出现以下任一信号,下一步**必须重新读取本文件本节与 `schedule-meeting` recipe**,不得沿着当前假设继续试: +1. 连续 **2 次** `room search` 空结果/无 `roomId` +2. 任意一次 `roomId invalid` +3. 已开始尝试「换园区 / 换楼栋 / 看 event 详情 / 猜 roomId」 + +回读后只允许二选一: +1. **报错收束**:已搜尽允许范围/整园仍无 `roomId` 或无空房 +2. **用户确认**:明确询问是否放宽范围、换时间,或接受不订会议室 | # | 规范 | |---|------| -| 1 | **一键脚本**:`calendar_schedule_meeting.py` 做「建日程 → 加人 → 可选搜房/订房」;未限范围时可直接 `--book-room`,脚本按根目录单次带 `--start` / `--end` 的 `room search` 订第一家。**若搜房失败,脚本应输出明确失败原因并返回非零退出码,促使上层立即向用户汇报,而不是继续试探。** | -| 2 | **要限范围/具名**:先 `list-groups` 解析允许的 `group-id`。若用户说的是同一地点(同园区/楼栋/楼层),应优先锁定**最相关的承载 group** 并只查它;该 group 无结果即可按该地点无房收束,不再试别的无关 group。仅当用户明确给出多个允许地点时,才分别对这些 group 各 **1 次**带 `--start` / `--end` 的 `room search` 再 `room add` | -| 3 | **禁止**:无新信息时反复切换参数、父组子组试探、在最相关 group 无结果后改搜别的同级/异地 group、超 100 条后仍根分组或未授权区域全量搜;**禁止**对 `room search` 使用不存在的 `--query` 或 `--available` | +| 1 | **一键脚本**:`calendar_schedule_meeting.py` 做「建日程 → 加人 → 可选搜房/订房」;未限范围时可直接 `--book-room`,脚本按根目录单次 `room search --available` 订第一家。**若搜房失败,脚本应输出明确失败原因并返回非零退出码,促使上层立即向用户汇报,而不是继续试探。** | +| 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 | **授权范围无结果即收束**:在用户允许的搜索范围内仍无可用会议室或有效 `roomId`,说明当前范围不可预订;不得假设 ID、用 `room add` 试探或绕路推断 | -| 6 | **无效 ID 恢复**:遇到 `roomId invalid` 时停止预订,回到 `room search` 的真实返回;若范围已查尽则报告无可用房,否则先确认是否放宽地点或时间 | +| 5 | **全量无结果即收束**:在用户允许的搜索范围内(含**整园/全 campus** 若用户要求已逐组查尽)仍无任何可用会议室或有效 `roomId` → **直接报错/告知失败并结束订房**,且**下一条消息必须汇报给用户**;**不得**假设 ID、不得用 `room add` 试探、不得绕路查日程、不得继续自说自话分析 Mock/测试环境 | +| 6 | **失败触发回读**:连续 2 次空结果、任意一次 `roomId invalid`、或开始换园区/绕路时 → **必须回读本节**;回读后只允许「报错收束」或「向用户确认是否放宽条件」 | | Recipe | 行动指南(固定路线) | | ------------------ | ------------------- | -| 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`。授权范围内无空房则报告不可预订;需要扩大范围或改时间时先取得用户同意。 | +| schedule-meeting | **见上文「两准则」**、**「搜房失败硬门禁」**。**未给时段且仅说「发起/开个会」**→ 不走本 recipe,走 lite recipe `start-conference`。**未给时段但有预约意图**("安排""约""定"等词):追问具体开始/结束时间。**已有时段后**,按固定顺序执行:1. `dws calendar event create` 建日程;2. 有参会人则 `dws calendar participant 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/测试环境。细则见「会议室搜索早停」。 | | 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 deleted file mode 100644 index 3cef2c2b..00000000 --- a/skills/multi/dingtalk-calendar/references/calendar-index.md +++ /dev/null @@ -1,7 +0,0 @@ -# 日历能力索引 - -| 主题 | 详见 | -|---|---| -| 日程/会议/会议室/闲忙 | [`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 132cb034..d68eb2ef 100644 --- a/skills/multi/dingtalk-calendar/references/calendar.md +++ b/skills/multi/dingtalk-calendar/references/calendar.md @@ -25,7 +25,7 @@ 日程实例(event instance):日程的具体时间实例,可以通过event list指令查询时间段内的所有实例。1个普通日程和对应1个Instance,而1个重复性日程(SeriesMaster)对应N个Instance(同属一个日程序列)。 - 同一个日程序列具有相同的iCalUid,并且重复性日程,其eventId和iCalUid的值相同。因此可以通过重复性日程实例的iCalUid得到重复性日程(SeriesMaster)的eventId 重复规则(recurrence rule):定义重复性日程的重复规则。 -参会人(attendee):日程的参与者。常用通讯录工具查询userId,dws contact user search --query "姓名"。 +参会人(attendee):日程的参与者。按姓名统一使用 `dws aisearch person --keyword "姓名" --dimension name --format json` 查询 userId。 响应状态(response):参会人对日程的回应,包括:未响应、接受、待定、拒绝。 忙闲时间(busy):查询用户在指定时间段的忙闲状态,查询会议室在指定时间段的预定状态,用于会议时间协调。 会议室(room):room是 会议室 ,room可视为日程的资源类参会人,需要加入日程完成预订。注意和location区分,location只是地点,和room不同。 @@ -41,6 +41,8 @@ dws calendar event [create|update|get|delete|respond] [flags] # 按时间范围批量查询 dws calendar event list [flags] +# 查询循环日程的实例列表(按时间范围展开重复日程) +dws calendar event instances [flags] # 对于非明确时间或一段时间范围的约会场景,可基于所有参会人的忙闲状态,推荐多个可用的时间块方案 dws calendar event suggest [flags] ``` @@ -122,7 +124,6 @@ Flags: **权限**:查询共享日历下的日程时,至少要有reader权限。 **分页**:单次最多返回 `--limit` 指定的条数(默认/最大 100);当结果超过 limit 时,返回体包含 `nextCursor` 字段。首次查询无需传 `--cursor`,仅在翻页时将上一次返回的 `nextCursor` 作为 `--cursor` 传入。 -当前用户的个人日程使用 `dws calendar event list` 查询;参数包含 `--limit` 和 `--cursor` 分页。 ### 获取日程详情 ``` @@ -136,6 +137,28 @@ Flags: --calendar-id string 日历 ID (默认 primary 主日历) ``` +### 查询循环日程实例 +``` +Usage: + dws calendar event instances [flags] +Example: + dws calendar event instances --id + dws calendar event instances --id --start "2026-03-10T00:00:00+08:00" --end "2026-03-31T23:59:59+08:00" + dws calendar event instances --id --limit 50 + dws calendar event instances --id --cursor "" +Flags: + --id string 日程 ID (必填,重复性日程 SeriesMaster 的 eventId) + --calendar-id string 日历 ID (默认 primary 主日历,仅在查询其他日历本时填写;通过 `book list` 获取) + --start string 开始时间 ISO-8601 (例如 2026-03-10T00:00:00+08:00,不传则默认今天 00:00:00) + --end string 结束时间 ISO-8601 (例如 2026-03-31T23:59:59+08:00,不传则默认今天 23:59:59) + --limit int 每页返回条数 (默认 100,最大 100) + --cursor string 分页游标 (从上一次返回的 nextCursor 获取,首次查询无需传入) +``` + +> **说明**:用于按时间范围展开重复日程(SeriesMaster)的每一个实例。**此接口只能查询重复性日程;若传入普通非循环日程,将查不到任何实例信息。**`--id` 必须是重复性日程的 eventId,可通过 `event list` 获取。 +> **默认行为**:不传 `--start` / `--end` 时,默认返回今天的实例(00:00:00 ~ 23:59:59)。 +> **分页**:单次最多返回 `--limit` 指定的条数(默认/最大 100);当结果超过 limit 时,返回体包含 `nextCursor` 字段。 + ### 创建日程 ``` Usage: @@ -560,6 +583,7 @@ Flags: - 取消/删除 → `event delete` - 推荐时间/什么时候有空/协调时间 → `event suggest` - 接受/拒绝/暂定日程 → `event respond` +- 查询循环日程/重复日程的每次实例/展开循环日程 → `event instances` 用户说"参会人/与会者": - 查看 → `attendee list` @@ -629,11 +653,11 @@ dws calendar event create --title "Q1 复盘会" \ **方式二:先创建日程,再单独添加参会人 / 会议室** ```bash -# Step 1: 创建日程 — 提取 eventId +# Step 1: 创建日程 — 从 result.id 提取日程 ID dws calendar event create --title "Q1 复盘会" \ --start "2026-03-10T14:00:00+08:00" --end "2026-03-10T15:00:00+08:00" --format json -# Step 2: 添加参会人(必须用 Step 1 返回的 eventId) +# Step 2: 添加参会人(必须用 Step 1 返回的 result.id) dws calendar attendee add --event --attendees userId1,userId2 --format json # Step 3: 搜索空闲会议室 @@ -661,10 +685,11 @@ 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`, `nextCursor` | event get/update/delete/respond 的 --id;下一页 --cursor | +| `event create` | `result.id` | attendee/room/attachment 操作的 --event | +| `event list` | `result.events[].id`, `nextCursor` | event get/update/delete/respond 的 --id;下一页 --cursor | | `event suggest` | 推荐的时间段 | event create 的 --start/--end | | `event respond` | 响应结果 | — | +| `event instances` | `result.events[].id`, `nextCursor` | event get/update/delete/respond 的 --id;下一页 --cursor | | `room search` | `rooms[].roomId` | room add 的 --rooms 或 event create 的 --rooms | | `room list-groups` | `groups[].groupId` | room search 的 --group-id | | `book list` | `id`(如 `primary`) | event list/get 的 --calendar-id, book get/update 的 --id | @@ -690,7 +715,8 @@ dws calendar event list --start "2026-03-10T14:00:00+08:00" --end "2026-03-10T15 - **自动化校验**:凡涉及 `room add` / `event create --rooms` 的流程,`--rooms` 只能填上游 `room search`(或等价接口)返回 JSON 中的 **`rooms[].roomId`**;不得以会议室展示名、楼层文案或用户口语当作 `roomId` - **附件**:`attachment add` 仅负责挂载,**不上传**文件;fileId 必须先通过钉盘流程取得;`--files` 多附件用 `:` 元素逗号分隔 - **日历本**:`book list` 返回的 `id` 才是合法 `calendarId`;如无明确说明,`event list` / `event get` 都不要带 `--calendar-id`,让接口默认走 primary 主日历 -- **分页查询**:`event list` / `list-mine` 支持 `--limit`(控制每页条数,默认/最大 100)和 `--cursor`(翻页游标);**首次查询无需传 `--cursor`**,仅当返回体中包含 `nextCursor` 时,将其作为 `--cursor` 传入可获取下一页 +- **分页查询**:`event list` / `event instances` 均支持 `--limit`(控制每页条数,默认/最大 100)和 `--cursor`(翻页游标);**首次查询无需传 `--cursor`**,仅当返回体中包含 `nextCursor` 时,将其作为 `--cursor` 传入可获取下一页 +- **循环日程实例**:`event instances` 用于按时间范围展开重复日程(SeriesMaster)的每一个实例;**普通非循环日程调用该命令将查不到任何实例信息** - **日程提醒**:`event create` 支持 `--remind-minutes` 设置开始前提醒,逗号分隔多个分钟数(如 `--remind-minutes 5,10,15`),不传则默认15分钟提醒 - **会议室分页**:`room search` 支持 `--limit`(每页条数,默认100,最大100)和 `--page`(分页起始位置,默认0),与 `room list-groups` 分页风格一致 diff --git a/skills/multi/dingtalk-calendar/references/intent-guide.md b/skills/multi/dingtalk-calendar/references/intent-guide.md index c5236af7..c56c7ac5 100644 --- a/skills/multi/dingtalk-calendar/references/intent-guide.md +++ b/skills/multi/dingtalk-calendar/references/intent-guide.md @@ -5,6 +5,5 @@ | 用户说... | 真实意图 | 应该用 | 不要用 | 理由 | |---|---|---|---|---| | "给自己留一个明天下午的时间块/建个个人日程" | 创建个人日程 | `calendar event create` | `todo` | 个人 schedule 仍属于日历事件,不是待办 | -| "帮我预约一个视频会议" / "开个会" / "发起会议" | 视频会议 | 钉钉客户端(当前 CLI 不支持) | `calendar` | 不要把实时视频会议诉求改写成日历日程 | -| "帮我建一个明天下午的日程" | 日历日程 | `calendar` | — | 日历日程管理(可含参与者/会议室);视频会议当前 CLI 不支持 | +| "帮我建一个明天下午的日程" | 日历日程 | `calendar` | `conference` | 日历日程管理(可含参与者/会议室) | | "明早 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 index 4d50f237..6a1faa32 100644 --- a/skills/multi/dingtalk-calendar/references/lite-recipes.md +++ b/skills/multi/dingtalk-calendar/references/lite-recipes.md @@ -19,9 +19,3 @@ 详见 [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 0968a5fc..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,48 +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 - 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): - 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 - 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 ( @@ -190,15 +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: - error = data.get('errorMsg') or data.get('errorCode') or data - print(f"错误:忙闲查询失败: {error}", file=sys.stderr) - sys.exit(2) - busy = parse_busy_intervals(data) free = find_free_slots(day_start, day_end, busy, args.duration) @@ -208,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-calendar/scripts/calendar_schedule_meeting.py b/skills/multi/dingtalk-calendar/scripts/calendar_schedule_meeting.py index d069418f..f5ce05b0 100644 --- a/skills/multi/dingtalk-calendar/scripts/calendar_schedule_meeting.py +++ b/skills/multi/dingtalk-calendar/scripts/calendar_schedule_meeting.py @@ -154,6 +154,7 @@ def main(): 'calendar', 'room', 'search', '--start', start_iso, '--end', end_iso, + '--available', '--format', 'json', ] if group_id: diff --git a/skills/multi/dingtalk-chat/SKILL.md b/skills/multi/dingtalk-chat/SKILL.md index 8bef791f..216eaed8 100644 --- a/skills/multi/dingtalk-chat/SKILL.md +++ b/skills/multi/dingtalk-chat/SKILL.md @@ -1,10 +1,9 @@ --- name: dingtalk-chat -description: 钉钉群聊与消息。Use when 用户提到 发消息/单聊/群聊/建群/拉人进群/改群名/搜索群/群成员管理/@消息/收藏消息/撤回消息/机器人群发/Webhook通知/发图片或文件到群/标记未读/清除红点/置顶消息/全部群列表。不做紧急 DING/短信/电话(走 dingtalk-ding)、邮件(走 dingtalk-mail)、班级群(走 dingtalk-edu-group)。命令前缀:dws chat。 -cli_version: ">=0.2.14" +description: 钉钉群聊与消息。Use when 用户提到 发消息/单聊/群聊/建群/拉人进群/改群名/搜索群/群成员管理/@消息/撤回消息/机器人群发/Webhook通知/发图片或文件到群/标记未读/清除红点/置顶消息/全部群列表。不做紧急 DING/短信/电话(走 dingtalk-misc)、邮件(走 dingtalk-mail)、班级群(走 dingtalk-misc)。命令前缀:dws chat。 metadata: + cli_version: ">=0.2.14" category: product - stability: experimental requires: bins: - dws @@ -12,22 +11,16 @@ metadata: # 钉钉群聊 / 消息 Skill -> 🧪 **EXPERIMENTAL · 试验版 / Preview** — multi 模式当前未达 stable 标准。全部 dingtalk-* skill 已通过 dispatch verifier,但接口、命名、跨 skill 引用后续可能调整;生产 / 共享环境请优先使用 mono 模式(`dws skill setup --mode mono`)。问题请提 issue 反馈。 - ## 前置条件 — 执行操作前必读 -> **`use_skill(dws-shared)`** — 认证、全局参数(`--format json` / `--yes`)、错误码、URL 模板、跨产品消歧、安全规则与 capability 边界。**执行任何 `dws` 命令前先读;** 单产品的清晰命令可直接用本 skill。 +> **CRITICAL — 执行任何 `dws` 操作前,MUST 先用 Read 工具完整读取 [`dws-shared`](../dws-shared/SKILL.md)。**该轻量文件包含全局执行契约、安全底线及 shared references 的按需加载导航;不要预加载其全部 references。 - - -> 渐进式参考:[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.md](references/chat.md);表情:[chat-emoji-list.md](references/chat-emoji-list.md);剧本:[01-messaging.md](references/01-messaging.md)。 ## Shortcuts(无专用脚本/recipe 时优先) -以下 shortcut 来自独立于 Runtime Schema 的公开 catalog。先按本 skill 的意图表、脚本和 recipe 路由:存在精确覆盖该场景的专用脚本/recipe 时按其执行;否则用户意图命中时,shortcut 优先于手写原子命令。用 `dws shortcut list --service chat --format json` 读取参数、约束、风险和示例,并以 `dws chat --help` 核对当前 Cobra flags;不要对 `+` 路径调用 `dws schema`。 +以下命令来自独立于 Runtime Schema 的公开 catalog。先运行 `dws shortcut list --service chat --format json` 读取完整契约,再用 `dws chat --help` 核对 flags;不要对 `+` 路径调用 `dws schema`。 | Shortcut | 风险 | 适用场景 | |---|---|---| @@ -36,7 +29,7 @@ metadata: | `dws chat +bot-search` | read | 搜索当前用户自己创建的机器人 | | `dws chat +broadcast` | write | 按姓名逐一给多个人群发同一条单聊消息(自动解析 userId、逐个发送) | | `dws chat +category-create` | write | 创建用户自定义会话分组 | -| `dws chat +category-delete` | high-risk-write | 删除用户自定义会话分组 | +| `dws chat +category-delete` | write | 删除用户自定义会话分组 | | `dws chat +category-list` | read | 获取用户自定义会话分组 | | `dws chat +category-rename` | write | 更新用户自定义会话分组的名称 | | `dws chat +chat-bots` | read | 查看群内所有机器人 | @@ -56,7 +49,7 @@ metadata: | `dws chat +chat-set-history` | write | 设置新成员入群可查看历史消息范围 | | `dws chat +chat-update-alias` | write | 设置群备注(仅自己可见) | | `dws chat +chat-update-nick` | write | 设置当前用户在群内的群昵称 | -| `dws chat +conversation-clear-all-red-point` | write | 清除所有会话红点(全部已读) | +| `dws chat +conversation-clear-all-red-point` | high-risk-write | 清除所有会话红点(全部已读) | | `dws chat +conversation-info` | read | 获取会话信息(群聊传 --group,单聊传 --open-dingtalk-id) | | `dws chat +conversation-list` | read | 分页获取当前用户的全部会话列表(单聊+群聊) | | `dws chat +conversation-list-top` | read | 拉取置顶会话列表 | @@ -82,22 +75,19 @@ metadata: | "发消息给张三" | `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 group rename` / `dws chat group members remove` | | "@我消息" | `dws chat message list-mentions` | -| "查群聊记录" | `dws chat message list` | -| "收藏/取消收藏这条消息" | `dws chat message add-favorite` / `dws chat message remove-favorite`(均需 `openMessageId` 和 `openConversationId`)| -| "查看我收藏的消息" | `dws chat message list-favorites`(默认 `--cursor 0 --size 20`)| -| "用机器人发消息" | `dws chat message send-by-bot --robot-code --group --title "<标题>" --text "<内容>"` | -| "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 search --query "<群名>"` → `dws chat message list --group --time "" --direction older` | +| "用机器人发消息" | `dws chat message send-by-bot --robot-code --group ` | +| "Webhook 推一条" | `dws chat message send-by-webhook --token ` | +| "撤回消息" | `dws chat message recall --conversation-id --msg-id ` | | "标记未读 / 清除红点 / 全部已读" | `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` | ## 标准 SOP(必遵流程) -> 命中以下意图**必须**按对应 SOP 顺序执行;**禁止**跳步、替换命令、编造 ID。每条命令必须带 `--format json`,执行后必须按"解析"步取真实字段(`openDingTalkId` / `openConversationId` / `clientMsgId`)。 +> 命中以下意图**必须**按对应 SOP 顺序执行;**禁止**跳步、替换命令、编造 ID。每条命令必须带 `--format json`,执行后必须按"解析"步取真实字段(`openDingTalkId` / `openConversationId` / `openMessageId`)。 ### SOP-1 发消息(send-message) @@ -105,7 +95,7 @@ metadata: 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` 必须如实报错,不要谎报已发。 +3. **验证(必须)**:发送接口成功时可能只返回 `result.openTaskId`,它可用于确认提交成功,但**不是撤回所需消息 ID**。需要撤回时必须按 SOP-7 用带 `--time` 的 `message list` 回查,取得真实 `openMessageId`;返回非 `success` 必须如实报错,不要谎报已发。 **禁止**:把人名/群名直接当 ID 传入、跳过 `aisearch person`/`chat search` 解析、跳过 `--format json`、未发送成功就答复"已发送"。 @@ -155,12 +145,22 @@ metadata: **禁止**:用普通 `message list` 冒充 focused、把人员列表需求硬塞进 chat。 +### SOP-7 拉取 / 撤回消息(list-or-recall-message) + +**触发**:查某个群或单聊的聊天记录、撤回某条消息。 + +1. **定位会话(必须)**:群名先 `dws chat search --query "<群名>" --format json` 取 `openConversationId`;单聊对象先解析真实 `userId` 或 `openDingTalkId`。 +2. **拉取消息(必须)**:`dws chat message list --group --time "" --direction older --format json`;单聊将 `--group` 换成 `--user` 或 `--open-dingtalk-id`。`--time` 是必填参数,必须来自用户时间范围或明确收敛后的边界。 +3. **撤回(必须)**:仅在用户明确要求撤回时,从消息列表 `result.messages[].openMessageId` 取真实 ID,执行 `dws chat message recall --conversation-id --msg-id --format json`。 + +**禁止**:省略 `--time`、用发送返回的 `clientMsgId` 代替 `openMessageId`、只传 `--client-msg-id`、编造会话或消息 ID。 + ## 跨产品协作 - 收件人是人名 → 先用 `dingtalk-contact` 或 `dingtalk-aisearch` 拿 `openDingTalkId` / `userId` - 要发图片/文件 → 先 `dt_media_upload` 上传 → `python scripts/extract_media_id.py ""` 提取 mediaId → 再用 `--media-id` -- 紧急升级(应用内/短信/电话)→ 切到 `dingtalk-ding` +- 紧急升级(应用内/短信/电话)→ 切到 `dingtalk-misc`(`references/ding.md`) - 发邮件 → 切到 `dingtalk-mail` -## 局部意图与 Recipe +## 局部意图与短流程 -- [局部意图消歧](references/intent-guide.md);[Lite Recipe](references/lite-recipes.md)。 +- [局部意图消歧](references/intent-guide.md);[短流程](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 b9d1c70f..1f043cd6 100644 --- a/skills/multi/dingtalk-chat/references/01-messaging.md +++ b/skills/multi/dingtalk-chat/references/01-messaging.md @@ -6,9 +6,8 @@ |--------|-------------------| | 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` | +| escalate-ding | 三级升级:
1. `ding message send --robot-code --type app --users --content "<内容>"`(必填项见 `dingtalk-misc` 的 `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` | | 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 `(备选) | +| forward-message | 1. `chat search --query "<群名>"` → 取 `openConversationId` → `chat message list --group --time "<起始时间>"` 拉源消息
2. `aisearch person --keyword "<姓名>" --dimension name` → 取 `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` 易混淆硬规则)
翻页:`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 deleted file mode 100644 index 53c84811..00000000 --- a/skills/multi/dingtalk-chat/references/chat-admin-commands.md +++ /dev/null @@ -1,417 +0,0 @@ -# 机器人、分组与会话状态命令 - -### 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 deleted file mode 100644 index e9449718..00000000 --- a/skills/multi/dingtalk-chat/references/chat-commands.md +++ /dev/null @@ -1,10 +0,0 @@ -# 聊天命令路由 - -> `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 deleted file mode 100644 index 89f09830..00000000 --- a/skills/multi/dingtalk-chat/references/chat-composite.md +++ /dev/null @@ -1,139 +0,0 @@ -# 复合工作流 - -## 复合工作流 - -### 机器人发消息后撤回(完整流程) - -`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 --id --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 --query "姓名" --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 deleted file mode 100644 index 9add1c19..00000000 --- a/skills/multi/dingtalk-chat/references/chat-context.md +++ /dev/null @@ -1,27 +0,0 @@ -# 上下文传递表 - -## 上下文传递表 - -| 操作 | 从返回中提取 | 用于 | -|------|-------------|------| -| `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 deleted file mode 100644 index d248fb9a..00000000 --- a/skills/multi/dingtalk-chat/references/chat-conversation-commands.md +++ /dev/null @@ -1,206 +0,0 @@ -# 会话查询与媒体命令 - -### 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 deleted file mode 100644 index ad043820..00000000 --- a/skills/multi/dingtalk-chat/references/chat-group-commands.md +++ /dev/null @@ -1,483 +0,0 @@ -# 群组、群身份与群搜索命令 - -## 命令总览 - -### 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 -Flags: - --name string 群名称 (必填) - --users string 成员 userId 或 openDingTalkId(可混传),用户本身会自动加入无需包含,逗号分隔,不超过20个 (必填) - --type string 群类型: INTERNAL(内部群,默认)/EXTERNAL(外部群)/NORMAL(普通群) - --thread 开启话题模式,将创建话题圈 -``` - -创建时当前登录用户自动成为群主。需要指定他人为群主时,先创建群并从返回中取 `openConversationId`,再执行 `dws chat group transfer-owner --group --new-owner `。 - -#### 查看群成员列表 — 分页查询指定群聊的成员 -``` -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 --query "姓名" --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 --query "姓名" --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 deleted file mode 100644 index c8e0e91d..00000000 --- a/skills/multi/dingtalk-chat/references/chat-index.md +++ /dev/null @@ -1,13 +0,0 @@ -# 聊天能力索引 - -| 主题 | 详见 | -|---|---| -| 群创建/设置/成员/角色/搜索 | [`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 deleted file mode 100644 index 742254c0..00000000 --- a/skills/multi/dingtalk-chat/references/chat-intent.md +++ /dev/null @@ -1,163 +0,0 @@ -# 意图判断 - -## 意图判断 - -用户说"我特别关注的人最近发了什么消息/关注的人最近聊了啥/星标联系人最近的动态" → `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`,从返回取 `openConversationId`,再 `chat group transfer-owner --group --new-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 deleted file mode 100644 index 8f50e63d..00000000 --- a/skills/multi/dingtalk-chat/references/chat-message-file-commands.md +++ /dev/null @@ -1,610 +0,0 @@ -# 消息与会话文件命令 - -### 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 -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/audio/video(推荐统一用 `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 内不会重复发送(可选) - -注意: - - --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 内不会重复投递消息(可选,群聊和单聊均支持) - - 当前命令不支持位置消息或联系人名片;不得生成 `location` / `profile` 类型或相应的臆造参数 -``` - -#### 查询消息发送状态 — 查询以当前用户身份发送的消息的发送状态 - -查询以当前用户身份发送的消息的发送状态。需要传入发送消息时返回的 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" - # 查询群 ID: dws chat search --query "群名" - # 查询单聊会话 ID: dws chat conversation-info --user - # 查询人员: dws contact user search --query "姓名" --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 列表,逗号分隔(可选,群聊或单聊均可,不传则搜索所有会话) - --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 列表,逗号分隔 (必填) - -注意: - - 支持批量查询多条消息的表情和文字回复 - - 消息 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 deleted file mode 100644 index ae4240ce..00000000 --- a/skills/multi/dingtalk-chat/references/chat-notes.md +++ /dev/null @@ -1,67 +0,0 @@ -# 注意事项 - -## 注意事项 - -- **发消息前参数审查(必须执行)**: - - 发消息(`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 --id --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 --query "姓名" --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 deleted file mode 100644 index 894e845b..00000000 --- a/skills/multi/dingtalk-chat/references/chat-related.md +++ /dev/null @@ -1,32 +0,0 @@ -# 相关产品 - -## 相关产品 - -- [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 deleted file mode 100644 index 0ab1f5a9..00000000 --- a/skills/multi/dingtalk-chat/references/chat-scripts.md +++ /dev/null @@ -1,9 +0,0 @@ -# 自动化脚本 - -## 自动化脚本 - -| 脚本 | 场景 | 用法 | -|------|------|------| -| [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 deleted file mode 100644 index c7100b6d..00000000 --- a/skills/multi/dingtalk-chat/references/chat-workflow.md +++ /dev/null @@ -1,35 +0,0 @@ -# 核心工作流 - -## 核心工作流 - -```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 3025c514..5cf96fe3 100644 --- a/skills/multi/dingtalk-chat/references/chat.md +++ b/skills/multi/dingtalk-chat/references/chat.md @@ -1,10 +1,344 @@ # 会话与群聊 (chat) 命令参考 -> 渐进式兼容入口:新任务优先从 [聊天能力索引](./chat-index.md) 选择专题。 +> **渐进式文档**:本文件为路由层(索引 + 意图判断 + 全局约束),详细参数、示例和踩坑说明在 [chat/](./chat/) 目录下按场景加载。 -| 任务 | 参考 | -|---|---| -| 命令路由 | [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) | +> 命令别名:`dws im` 等价于 `dws chat`。 + +## 适用范围与安全硬约束 + +`chat` 覆盖钉钉会话、群聊、群成员、会话消息、机器人消息、Webhook、会话状态和群身份管理。 + +**发消息前参数审查(必须执行)**: + +- 发消息类命令包括 `chat message send`、`send-by-bot`、`send-by-webhook`、`send-card`、`reply`、`forward`。 +- 执行前必须逐项核对接收对象、群/人、消息内容、@ 对象、消息类型、附件路径,确认全部来自用户原始需求。 +- 用户没说发给谁、群名/人名可能匹配多个对象、消息文本由 Agent 组织、是否 @ 某人不明确时,必须先确认,严禁自行补全。 +- 重试发送建议复用同一个 `--uuid`,避免重复投递;不传 `--uuid` 时每次调用都视为新消息。 + +**文件、图片、音频、视频发送硬规则**: + +- 新场景发送本地文件、音频、视频统一用 `dws chat message send --msg-type file|audio|video --file-path <本地路径>`,CLI 内部完成上传和发送;`audio` / `video` 底层按 `file` 链路处理。 +- 不要先走 `dt_media_upload`、`extract_media_id.py`、`drive upload`;`--msg-type image --media-id` 只保留给已有 mediaId 的旧链路。 + +**Markdown 换行硬规则**: + +- `--text` 必须使用真实换行符 `U+000A`,不能传字面量 `\n`。 +- Markdown 单个换行通常不换行,需要空行 `\n\n`、行尾两个空格 + 换行,或 `
`。 +- 机器人 `message send-by-bot` 的 Markdown 文本优先用空行分隔段落;如果在模型内部用转义形式组织文本,必须写 `\n\n`,不要只写 `\n`。 + +## 查询命令帮助 + +参数名、必填项或命令是否存在不确定时,先查 `--help`,不要猜。 + +```bash +dws chat --help +dws chat message --help +dws chat message send --help +dws chat group --help +dws chat bot --help +``` + +## Reference 索引 + +| Reference | 何时读取 | 主要命令 | +|-----------|----------|----------| +| [chat-message](./chat/chat-message.md) | 发消息、查消息、撤回、搜索、已读、话题、Pin/Top/Favorite、表情、卡片、文件、音频、视频、位置、名片、文本翻译 | `message *`、`file upload`、`download-media`、`text translate` | +| [chat-group](./chat/chat-group.md) | 建群、搜群、成员、机器人进群、群设置、群主、入群审批、群身份、群公告、群邀请分享 | `group *`、`group-role *`、`search`、`search-common` | +| [chat-bot](./chat/chat-bot.md) | 查机器人、机器人发消息/撤回、Webhook、机器人单聊、机器人进群 | `bot *`、`message send-by-bot`、`recall-by-bot`、`send-by-webhook` | +| [chat-conversation](./chat/chat-conversation.md) | 会话列表、置顶、免打扰、隐藏、红点、已读未读、清空、会话分组、智能分组 | `conversation-info`、`category *`、`mute`、`set-top`、`mark-read` | +| [chat-workflows](./chat/chat-workflows.md) | 高频复合流程、上下文传递表、openDingTalkId 获取方式、自动化脚本 | 工作流与字段传递 | + +## ID 获取速查 + +| 需要的 ID | 首选获取方式 | 说明 | +|-----------|--------------|------| +| 群聊 `openConversationId` | `dws chat search --query "群名" --format json` | 群聊发消息、拉消息、成员管理、会话状态都用它 | +| 数字群号对应的 `openConversationId` | `dws chat group get-by-group-id --group-id <数字群号>` | 用户只给数字群号时先转换 | +| 单聊 `openConversationId` | `dws chat conversation-info --user ` 或 `--open-dingtalk-id ` | 单聊转发、会话置顶、清红点等状态命令需要 | +| `userId` | `dws aisearch person --keyword "姓名" --dimension name --format json` | 内部人员、机器人单聊 users、按发送者 userId 搜索 | +| `openDingTalkId` | `dws aisearch person --keyword "姓名" --dimension name --format json` | 三方/跨组织、openDingTalkId 单聊、@ openDingTalkId | +| `robotCode` | `dws chat bot search --name "机器人名" --format json` | 我创建的机器人,发机器人消息和撤回使用 | +| 机器人 `openDingTalkId` | `dws chat bot find --query "机器人名" --format json` | 给机器人发单聊必须用 find,search 不返回该字段 | +| 群成员昵称 | `dws chat group members --id --format json` | 返回中包含成员在群里的昵称/展示名;不要只查 contact 个人名 | +| 消息 `openMessageId` / `openMsgId` | `dws chat message list ... --format json` | 撤回、已读状态、回复、转发、表情回应 | +| 话题 `openConvThreadId` | `dws chat message list ... --format json` | 拉话题回复或往话题内回复;禁止自行拼接 | +| 卡片 `bizId` | `dws chat message send-card ... --format json` | `update-card --biz-id` | + +## 易混淆路由 + +| 易混淆说法 | 正确路由 | 不要这样做 | +|------------|----------|------------| +| “置顶会话” | `chat set-top` 或 `chat list-top-conversations` | 不要用 `message set-top-msg` | +| “置顶某条消息” | `chat message set-top-msg` | 不要用 `chat set-top` | +| “机器人发消息” | `chat message send-by-bot` | 不要用当前用户身份 `message send` 代发 | +| “给机器人发单聊” | `chat bot find` 取 openDingTalkId → `message send --open-dingtalk-id` | 不要用 `bot search`,它没有 openDingTalkId | +| “发图片/文件/音频/视频” | `message send --msg-type file|audio|video --file-path`;已有图片 mediaId 才用 `image` | 不要先 `drive upload` 或 `dt_media_upload` | +| “特别关注的人最近发了什么” | `message list-focused` | 不要先查 `contact relation list-my-followings` | +| “某人发给我的消息” | `message list-by-sender` | 不要只查单聊,结果应覆盖群聊+单聊 | +| “搜索消息” | 首选 `message search-advanced` | 不要在可组合条件下退回简单 `message search` | +| “消息转待办” | 先用 `chat message list/search-advanced` 取消息内容,再调用 `dws todo task create` | 不要在 chat 内寻找“转待办”命令 | +| “消息转日程” | 先用 `chat message list/search-advanced` 取消息内容,再调用 `dws calendar event create` | 不要在 chat 内寻找“转日程”命令 | +| “共同群” | `chat search-common --nicks` | 不要分别搜索群再手工求交 | +| “拉取群文件 / 群文件列表” | `chat conversation-info --group ` 取 `spaceId` → `dws drive list --space-id ` | 不要用 `chat file upload`,它只负责上传 | +| “清空会话聊天记录” | `chat clear-messages` | 不要理解为删除群消息;它只影响当前用户视角 | + +## 发送前检查清单 + +执行任何发送类命令前,至少检查: + +1. 接收对象明确:群聊必须有 `openConversationId`,单聊必须有 `userId` 或 `openDingTalkId`。 +2. 身份明确:用户身份发送用 `message send`,机器人身份发送用 `send-by-bot`,Webhook 用 `send-by-webhook`。 +3. 内容明确:消息正文、标题、附件、卡片内容都能从用户原始需求中找到依据。 +4. @ 对象明确:`--at-all`、`--at-user-ids`、`--at-open-dingtalk-ids` 不可自行追加。 +5. 文件路径明确:本地文件、音频、视频必须传 `--msg-type file|audio|video --file-path`,并确认路径是用户提供或当前任务生成的目标文件。 +6. 重试策略明确:失败重试时复用同一个 `--uuid`,避免重复投递。 + +## 高频一跳命令 + +这些命令覆盖多数日常问题;复杂参数再跳到对应子文档。 + +| 场景 | 一跳命令 | +|------|----------| +| 找群 | `dws chat search --query "群名" --format json` | +| 建群 | `dws chat group create --name "群名" --users userId1,userId2 --format json` | +| 看群成员 | `dws chat group members --id --format json` | +| 看群内昵称 | `dws chat group members --id --format json` | +| 发群文字 | `dws chat message send --group --text "内容" --uuid --format json` | +| 发单聊文字 | `dws chat message send --user --text "内容" --uuid --format json` | +| 发本地文件 | `dws chat message send --group --msg-type file --file-path ./file.pdf --format json` | +| 发音频/视频 | `dws chat message send --group --msg-type audio --file-path ./voice.mp3 --format json` / `--msg-type video --file-path ./demo.mp4` | +| 发位置/名片 | `dws chat message send --group --msg-type location ...` / `--msg-type profile --contact-id ` | +| 拉群消息 | `dws chat message list --group --time "2026-03-10 00:00:00" --direction older --format json` | +| 搜消息 | `dws chat message search-advanced --query "关键词" --start --end --format json` | +| 查 @ 我的消息 | `dws chat message search-advanced --at-me --start --end --format json` | +| 查未读会话 | `dws chat message list-unread-conversations --count 20 --format json` | +| 撤回自己消息 | `dws chat message recall --conversation-id --msg-id --format json` | +| 查我的机器人 | `dws chat bot search --name "机器人名" --format json` | +| 机器人发群消息 | `dws chat message send-by-bot --robot-code --group --title "标题" --text "## 标题\n\n正文" --format json` | +| Webhook 告警 | `dws chat message send-by-webhook --token --title "告警" --text "内容" --format json` | +| 查单聊会话 ID | `dws chat conversation-info --user --format json` | +| 查群钉盘空间 | `dws chat conversation-info --group --format json` | +| 发群公告 | `dws chat group notice create --group --content "公告内容" --format json` | +| 分享群邀请 | `dws chat group share-invite --source --target --format json` | +| 置顶会话 | `dws chat set-top --conversation-id --format json` | +| 清红点 | `dws chat clear-red-point --conversation-id --format json` | +| 会话分组 | `dws chat category list --format json` | +| 智能会话分组 | `dws chat category create-smart --name "重点群" --keywords "项目,重点" --format json` | +| 文本翻译 | `dws chat text translate --query "你好世界" --to en_US --format json` | + +## 命令索引表 + +### 消息与文件 + +| 命令 | 用途 | 必填参数 | 详见 | +|------|------|----------|------| +| `message send` | 当前用户发群聊/单聊文本、Markdown、图片、文件、音频、视频、位置、名片 | `--group` / `--user` / `--open-dingtalk-id` 三选一;文本用 `--text`,本地文件/音视频用 `--msg-type file|audio|video --file-path` | [chat-message](chat/chat-message.md#发送消息) | +| `message list` | 拉取群聊或单聊消息 | `--time` + `--group` / `--user` / `--open-dingtalk-id` 三选一 | [chat-message](chat/chat-message.md#拉取消息) | +| `message list-all` | 指定时间范围内拉取当前用户全部会话消息 | `--start` `--end` `--limit` `--cursor` | [chat-message](chat/chat-message.md#拉取消息) | +| `message list-by-sender` | 跨单聊和群聊查指定发送者消息 | `--sender-user-id` / `--sender-open-dingtalk-id` 二选一 | [chat-message](chat/chat-message.md#拉取消息) | +| `message list-mentions` | 查 @ 我的消息 | 可选 `--group`、`--start`、`--end` | [chat-message](chat/chat-message.md#拉取消息) | +| `message list-focused` | 查特别关注人消息 | 无 | [chat-message](chat/chat-message.md#拉取消息) | +| `message list-unread-conversations` | 获取未读会话列表 | 可选 `--count` | [chat-conversation](chat/chat-conversation.md#会话列表与红点) | +| `message search-advanced` | 多维度搜索消息,首选;支持消息类型、会话类型、机器人消息过滤 | 至少一个搜索条件 | [chat-message](chat/chat-message.md#搜索消息) | +| `message search` | 简单关键词搜索消息 | `--query` | [chat-message](chat/chat-message.md#搜索消息) | +| `message read-status` | 查询消息已读/未读状态 | `--group` `--message-id` | [chat-message](chat/chat-message.md#消息状态与撤回) | +| `message query-send-status` | 查询发送任务状态 | `--open-task-id` | [chat-message](chat/chat-message.md#消息状态与撤回) | +| `message recall` | 撤回当前用户消息;群主/管理员可撤回群内他人消息 | `--conversation-id` `--msg-id` | [chat-message](chat/chat-message.md#消息状态与撤回) | +| `message edit` | 编辑已发送消息内容 | `--conversation-id` `--msg-id`,`--text` / `--content` 二选一 | [chat-message](chat/chat-message.md#消息状态与撤回) | +| `message reply` | 引用回复消息 | `--conversation-id` `--ref-msg-id` `--ref-sender` `--text` | [chat-message](chat/chat-message.md#回复与转发) | +| `message forward` | 转发单条消息 | `--src-conversation-id` `--msg-id` `--dest-conversation-id` | [chat-message](chat/chat-message.md#回复与转发) | +| `message combine-forward` | 合并转发多条消息 | `--src-conversation-id` `--msg-ids` `--dest-conversation-id` | [chat-message](chat/chat-message.md#回复与转发) | +| `message list-topic-replies` | 拉取话题回复 | `--group` `--topic-id` | [chat-message](chat/chat-message.md#话题与卡片) | +| `message forward-topic` | 转发话题消息 | 源消息、源会话、话题 ID、目标会话 | [chat-message](chat/chat-message.md#话题与卡片) | +| `message send-card` / `update-card` | 创建并流式更新卡片 | `send-card` 目标二选一;`update-card` 需 `--biz-id` | [chat-message](chat/chat-message.md#话题与卡片) | +| `message add-emoji` / `remove-emoji` | 添加/移除默认表情回应 | `--conversation-id` `--msg-id` `--emoji` | [chat-message](chat/chat-message.md#表情回应) | +| `message add-text-emotion` / `update-text-emotion` / `remove-text-emotion` | 添加/更新/移除文字表情 | update 需 `--old-emotion-id` `--emotion-id`,其余用 `--emotion-*` | [chat-message](chat/chat-message.md#表情回应) | +| `message create-text-emotion` | 创建文字表情模板 | `--emotion-name` `--text` | [chat-message](chat/chat-message.md#表情回应) | +| `message list-emotion-replies` | 批量拉取消息表情回复和文字回复 | `--msg-ids` | [chat-message](chat/chat-message.md#表情回应) | +| `file upload` | 上传本地或 URL 文件到会话文件空间 | 目标三选一 + `--file` / `--url` 二选一 | [chat-message](chat/chat-message.md#文件与媒体) | +| `message download-media` | 下载消息中的图片/视频/语音等资源 | `--type` `--resource-id` `--message-id` `--open-conversation-id` `--output` | [chat-message](chat/chat-message.md#文件与媒体) | +| `text translate` | 翻译文本内容 | `--query` `--to` | [chat-message](chat/chat-message.md#文本工具) | + +### 群聊与群身份 + +| 命令 | 用途 | 必填参数 | 详见 | +|------|------|----------|------| +| `search` | 按关键词搜索群聊 | `--query` | [chat-group](chat/chat-group.md#搜索与基础信息) | +| `search-common` | 查询指定人共同群 | `--nicks` | [chat-group](chat/chat-group.md#搜索与基础信息) | +| `group create` | 创建内部群/外部群/普通群/话题圈,可指定群主 | `--name` `--users`,可选 `--owner` | [chat-group](chat/chat-group.md#群创建与基础操作) | +| `group members` | 查看群成员 | `--id` | [chat-group](chat/chat-group.md#成员与机器人) | +| `group members add/remove` | 添加/移除群成员 | `--id` `--users` | [chat-group](chat/chat-group.md#成员与机器人) | +| `group members add-bot/remove-bot` | 添加/移除群机器人 | `--id` + 机器人标识 | [chat-group](chat/chat-group.md#成员与机器人) | +| `group members list-by-ids` | 批量查群成员详情 | `--id` `--users` | [chat-group](chat/chat-group.md#成员与机器人) | +| `group rename` | 修改群名称 | `--id` `--name` | [chat-group](chat/chat-group.md#群创建与基础操作) | +| `group get-by-group-id` | 数字群号转 openConversationId | `--group-id` | [chat-group](chat/chat-group.md#搜索与基础信息) | +| `group transfer-owner` | 转让群主 | `--group` + `--user` / `--new-owner` | [chat-group](chat/chat-group.md#群设置与权限) | +| `group upgrade-to-external` | 仅将 NORMAL_GROUP 普通群升级为外部群 | `--group` `--yes`,可选 `--extension` | [chat-group](chat/chat-group.md#群设置与权限) | +| `group invite-url` | 获取群邀请链接 | `--group` | [chat-group](chat/chat-group.md#群设置与权限) | +| `group share-invite` | 将群邀请链接分享到另一个会话或单聊用户 | `--source` + `--target` / `--receiver` 二选一 | [chat-group](chat/chat-group.md#群设置与权限) | +| `group notice *` | 群公告创建、修改、详情、列表 | `--group`,视子命令补充公告参数 | [chat-group](chat/chat-group.md#群公告) | +| `group quit` / `dismiss` | 退出/解散群聊 | `--group` | [chat-group](chat/chat-group.md#群创建与基础操作) | +| `group update-icon` | 更新群头像 | `--group` `--icon-media-id` | [chat-group](chat/chat-group.md#群设置与权限) | +| `group update-settings` | 更新管理员级别的群功能开关 | `--group` `--setting-key` `--status` | [chat-group](chat/chat-group.md#群设置与权限) | +| `group user-settings query/set` | 批量查询/更新当前用户自己的群会话设置 | `query --groups` / `set --items` | [chat-group](chat/chat-group.md#群设置与权限) | +| `group update-nick` / `update-alias` | 设置或清除群昵称 / 设置群备注 | `update-nick` 需 `--group`,`--nick` 可选;`update-alias` 需备注 | [chat-group](chat/chat-group.md#群设置与权限) | +| `group set-history` | 设置新成员可见历史消息范围 | `--group` `--option` | [chat-group](chat/chat-group.md#群设置与权限) | +| `group get-mute-config` | 查询群用户禁言配置 | `--group` | [chat-group](chat/chat-group.md#群设置与权限) | +| `group list-my-groups` / `list-all` | 拉取我管理的群 / 我加入的所有群 | 可选分页参数 | [chat-group](chat/chat-group.md#群列表与入群审批) | +| `group list-join-validations` | 拉取入群验证记录 | 可选分页参数 | [chat-group](chat/chat-group.md#群列表与入群审批) | +| `group audit-join-validation` | 审批入群验证 | `--group` `--record-id` `--applicant` `--inviter` `--status` | [chat-group](chat/chat-group.md#群列表与入群审批) | +| `group-mute` / `group-mute-member` | 全员禁言 / 指定成员禁言 | `--group`,成员禁言还需用户与时长 | [chat-group](chat/chat-group.md#群设置与权限) | +| `group set-admin` | 设置/取消群管理员 | `--group` `--user` / `--users` | [chat-group](chat/chat-group.md#群设置与权限) | +| `group-role *` | 群身份列表、增删改、设置成员身份 | `--group` + role/user 参数 | [chat-group](chat/chat-group.md#群身份) | + +### 机器人与 Webhook + +| 命令 | 用途 | 必填参数 | 详见 | +|------|------|----------|------| +| `bot search` | 搜索我创建的机器人 | 可选 `--name` | [chat-bot](./chat/chat-bot.md#机器人搜索) | +| `bot find` | 搜索全部可用机器人,返回 openDingTalkId | `--query` | [chat-bot](./chat/chat-bot.md#机器人搜索) | +| `message send-by-bot` | 机器人发群聊或单聊消息 | `--robot-code` `--title` `--text` + 目标二选一 | [chat-bot](./chat/chat-bot.md#机器人发送与撤回) | +| `message recall-by-bot` | 撤回机器人消息 | `--robot-code` `--keys` | [chat-bot](./chat/chat-bot.md#机器人发送与撤回) | +| `message send-by-webhook` | 自定义机器人 Webhook 发群消息 | `--token` `--title` `--text` | [chat-bot](./chat/chat-bot.md#webhook) | +| `group members add-bot/remove-bot` | 机器人加入/移出群 | `--id` + 机器人标识 | [chat-bot](./chat/chat-bot.md#机器人进群) | +| `group bots` | 查看群内所有机器人 | `--group` | [chat-bot](./chat/chat-bot.md#机器人进群) | + +### 会话状态与分组 + +| 命令 | 用途 | 必填参数 | 详见 | +|------|------|----------|------| +| `conversation-info` | 获取群聊或单聊会话基础信息 | `--group` / `--user` / `--open-dingtalk-id` 三选一 | [chat-conversation](./chat/chat-conversation.md#会话基础信息) | +| `list-all-conversations` | 分页获取全部会话 | 可选分页参数 | [chat-conversation](./chat/chat-conversation.md#会话列表与红点) | +| `list-top-conversations` | 拉取置顶会话列表 | 可选分页参数 | [chat-conversation](./chat/chat-conversation.md#会话列表与红点) | +| `set-top` | 设置/取消会话置顶 | `--conversation-id` | [chat-conversation](./chat/chat-conversation.md#会话置顶与通知) | +| `mute` | 开启/关闭会话免打扰 | `--conversation-id` | [chat-conversation](./chat/chat-conversation.md#会话置顶与通知) | +| `hide` | 隐藏会话 | `--conversation-id` | [chat-conversation](./chat/chat-conversation.md#会话置顶与通知) | +| `mute-at-all` / `mute-red-envelope` | 关闭/恢复 @所有人或红包通知 | `--conversation-id` | [chat-conversation](./chat/chat-conversation.md#会话置顶与通知) | +| `mark-unread` / `mark-read` | 标记会话未读 / 标记消息已读 | 会话 ID;`mark-read` 还需消息 ID | [chat-conversation](./chat/chat-conversation.md#已读未读与清理) | +| `clear-red-point` / `clear-all-red-point` | 清除单会话/全部红点 | 单会话需 `--conversation-id` | [chat-conversation](./chat/chat-conversation.md#会话列表与红点) | +| `clear-messages` | 清空当前用户视角会话消息 | `--conversation-id` | [chat-conversation](./chat/chat-conversation.md#已读未读与清理) | +| `category list-by-conv` | 拉取指定会话所属的用户自定义会话分组 | `--group` | [chat-conversation](./chat/chat-conversation.md#会话分组) | +| `category batch-info` | 批量拉取用户自定义会话分组信息 | `--category-ids` | [chat-conversation](./chat/chat-conversation.md#会话分组) | +| `category *` | 自定义会话分组和智能分组管理 | 视子命令而定 | [chat-conversation](./chat/chat-conversation.md#会话分组) | + +## 意图判断 + +### 消息 + +| 用户说 | 路由 | +|--------|------| +| “发群消息 / 发到某群” | `chat message send --group`,先用 `chat search` 找 `openConversationId` | +| “发单聊 / 发给某人” | `chat message send --user` 或 `--open-dingtalk-id`,人员信息见 `dingtalk-contact` | +| “发图片 / 发文件 / 发截图 / 发音频 / 发视频” | `chat message send --msg-type file|audio|video --file-path <本地路径>`;`audio/video` 底层按 `file` 发送 | +| “发位置 / 分享地址” | `chat message send --msg-type location`,需经用户确认经纬度和地址名 | +| “发联系人名片 / 分享联系人” | `chat message send --msg-type profile --contact-id ` | +| “机器人发消息 / 机器人群发” | `chat message send-by-bot`,不要用用户身份代发 | +| “撤回我发的消息” | `chat message recall` | +| “撤回机器人发的消息” | `chat message recall-by-bot` | +| “查某群聊天记录” | `chat message list --group` | +| “查和某人的单聊记录” | `chat message list --user` 或 `--open-dingtalk-id` | +| “某人发给我的消息 / 指定发送者消息” | `chat message list-by-sender`,跨单聊和群聊 | +| “我今天/最近所有消息” | `chat message list-all --start --end ` | +| “@我的消息” | `chat message list-mentions` 或 `search-advanced --at-me` | +| “特别关注的人最近发了什么” | `chat message list-focused`,不要先查关注人员列表 | +| “搜索消息里的关键词 / 多维度搜索” | 首选 `chat message search-advanced` | +| “只搜文件消息 / 只搜群聊 / 只看机器人消息” | `chat message search-advanced --message-type file --search-conv-type group_chat --only-robot-messages` 按需组合 | +| “翻译这段文字” | `chat text translate --query <文本> --to <语言代码>` | +| “把这条消息转待办 / 消息里提到的事项建待办” | 先取消息内容和相关人员,再按 `dingtalk-todo` 调 `dws todo task create`;标题、执行人、截止时间必须来自消息或用户确认 | +| “把这条消息转日程 / 消息里约的会建日程” | 先取消息内容、时间、地点、参会人,再按 `dingtalk-calendar` 调 `dws calendar event create`;时间不明确必须先确认 | +| “话题回复 / 往话题里回复” | 先 `message list` 获取 `openConvThreadId`,再 `list-topic-replies` 或 `message send --group ` | +| “消息已读未读 / 谁看了消息” | `chat message read-status` | +| “置顶某条消息 / 取消消息置顶” | `chat message set-top-msg` / `unset-top-msg` | + +### 群与成员 + +| 用户说 | 路由 | +|--------|------| +| “建群 / 创建群聊” | `chat group create` | +| “建群并指定群主 / 让某某当群主” | `chat group create --owner `,群主必须来自用户指定或确认 | +| “建外部群 / 建普通群 / 建话题圈” | `chat group create --type EXTERNAL` / `--type NORMAL` / `--thread` | +| “找群 / 搜索群” | `chat search --query` | +| “群成员 / 群里有谁” | `chat group members` | +| “群成员在群里的昵称 / 群昵称” | `chat group members --id `,返回中包含群内昵称/展示名 | +| “拉人进群 / 踢人” | `chat group members add` / `remove` | +| “批量查群成员详情” | `chat group members list-by-ids` | +| “改群名 / 群头像 / 群备注 / 群昵称” | `group rename` / `update-icon` / `update-alias` / `update-nick` | +| “转让群主 / 设置管理员 / 禁言” | `group transfer-owner` / `group set-admin` / `group-mute*` | +| “分享群链接 / 群邀请发给某人或某群” | `chat group share-invite` | +| “发群公告 / 改群公告 / 查群公告” | `chat group notice create/edit/get/list` | +| “入群审批 / 入群验证记录” | `group list-join-validations` → `group audit-join-validation` | +| “群身份 / 群标签 / 给成员设置身份” | `group-role *` | +| “我和某人的共同群” | `chat search-common --nicks` | + +### 机器人 + +| 用户说 | 路由 | +|--------|------| +| “查看我的机器人 / 我创建的机器人” | `chat bot search` | +| “找机器人 / 搜索全部可用机器人 / 给机器人发单聊” | `chat bot find`,需要 `openDingTalkId` | +| “加机器人到群 / 从群移除机器人 / 群里有哪些机器人” | `group members add-bot` / `remove-bot` / `group bots` | +| “Webhook 发告警” | `chat message send-by-webhook` | + +### 会话状态 + +| 用户说 | 路由 | +|--------|------| +| “未读会话列表” | `chat message list-unread-conversations` | +| “置顶会话 / 查看置顶会话” | `chat list-top-conversations` | +| “把会话置顶 / 取消会话置顶” | `chat set-top` / `chat set-top --off` | +| “免打扰 / 关闭免打扰” | `chat mute` / `chat mute --off` | +| “隐藏会话” | `chat hide` | +| “清红点 / 全部已读” | `chat clear-red-point` / `chat clear-all-red-point` | +| “标记未读 / 标记已读” | `chat mark-unread` / `chat mark-read` | +| “清空聊天记录” | `chat clear-messages` | +| “会话分组” | `chat category *` | +| “智能分组 / 按关键词或成员自动分组” | `chat category create-smart` | +| “群文件 / 拉取群文件列表” | `chat conversation-info --group ` 取 `spaceId`,再按 `dingtalk-drive` 调 `dws drive list --space-id ` | + +## 核心工作流索引 + +| 场景 | 入口 | +|------|------| +| 搜索群并拉消息 | [chat-workflows §群聊消息](./chat/chat-workflows.md#群聊消息) | +| 个人身份发送群聊/单聊/文件 | [chat-workflows §发送消息](./chat/chat-workflows.md#发送消息) | +| 机器人发消息后撤回 | [chat-workflows §机器人消息](./chat/chat-workflows.md#机器人消息) | +| 机器人不在群内时先邀请再发送 | [chat-workflows §机器人消息](./chat/chat-workflows.md#机器人消息) | +| 给机器人发单聊 | [chat-workflows §机器人消息](./chat/chat-workflows.md#机器人消息) | +| 发送图片 / 文件 / 音频 / 视频 | [chat-workflows §发送消息](./chat/chat-workflows.md#发送消息) | +| 流式卡片创建与更新 | [chat-message §话题与卡片](./chat/chat-message.md#话题与卡片) | +| 群公告发布与修改 | [chat-group §群公告](./chat/chat-group.md#群公告) | +| 消息转待办 | 先用 [chat-message](./chat/chat-message.md#拉取消息) 取消息内容,再读取 `dingtalk-todo` 创建待办 | +| 消息转日程 | 先用 [chat-message](./chat/chat-message.md#拉取消息) 取消息内容,再读取 `dingtalk-calendar` 创建日程 | +| 拉取群文件 | `conversation-info` 取 `spaceId`,再读取 `dingtalk-drive` 查询钉盘文件 | + +## 上下文传递摘要 + +| 操作 | 从返回中提取 | 用于 | +|------|-------------|------| +| `chat search` / `chat group create` / `chat group get-by-group-id` | `openConversationId` | `--group`、`--conversation-id`、群成员、消息发送/拉取 | +| `chat conversation-info` | 单聊 `openConversationId` | 单聊消息搜索、转发、会话状态操作 | +| `chat conversation-info --group` | `spaceId` | `drive list/info/download --space-id`,用于拉取群文件 | +| `chat group members` | 群成员昵称/展示名、`userId`、`openDingTalkId` | 识别群内发言人、@ 人、待办执行人、日程参会人 | +| `aisearch person` | `userId`、`openDingTalkId` | 单聊、@ 人、机器人单聊、按发送者搜索 | +| `chat bot search` | `robotCode` | `send-by-bot`、`recall-by-bot` | +| `chat bot find` | `openDingTalkId` | 给机器人发单聊 | +| `chat message send-by-bot` | `processQueryKey` | `recall-by-bot --keys` | +| `chat message send` | `openTaskId` | `query-send-status` | +| `chat message list` | `openMessageId`、`openMsgId`、`openConvThreadId` | 撤回、已读状态、表情、回复、转发、话题回复 | +| `chat message list/search-advanced` | 消息正文、发送人、时间 | 创建 `todo task` 或 `calendar event` 前的输入材料 | +| `chat message search(-advanced)` / `list-all` | `nextCursor` | 下一页 `--cursor` | +| `chat group-role list` | `openRoleId` | `group-role update/remove/set-user/remove-user` | +| `chat message send-card` | `bizId` | `message update-card --biz-id` | + +完整字段传递表见 [chat-workflows](./chat/chat-workflows.md#上下文传递表)。 + +## 相关产品 + +- [contact](../../dingtalk-contact/references/contact.md) — 搜索人员,获取 `userId` / `openDingTalkId`。 +- [todo](../../dingtalk-todo/references/todo.md) — 消息转待办时使用;从 chat 消息内容提取标题、执行人、截止时间后调用 `dws todo task create`。 +- [calendar](../../dingtalk-calendar/references/calendar.md) — 消息转日程时使用;从 chat 消息内容提取标题、开始/结束时间、地点、参会人后调用 `dws calendar event create`。 +- [drive](../../dingtalk-drive/references/drive.md) — 云盘/群文件管理;拉群文件先用 `chat conversation-info` 取 `spaceId`,再用 `dws drive list --space-id `;chat 纯文件/音视频发送优先用 `chat message send --msg-type file|audio|video --file-path`。 +- [aisearch](../../dingtalk-aisearch/references/aisearch.md) — 可用于人员、行为、历史信息搜索,但消息发送与会话管理仍走 `chat`。 +- [ding](../../dingtalk-misc/references/ding.md) — DING 通知与升级提醒,不等价于群聊消息。 + +文本翻译(`chat text translate`)详见 [chat-message](./chat/chat-message.md#文本工具)。 diff --git a/skills/multi/dingtalk-chat/references/chat/chat-bot.md b/skills/multi/dingtalk-chat/references/chat/chat-bot.md new file mode 100644 index 00000000..5ddeb2f1 --- /dev/null +++ b/skills/multi/dingtalk-chat/references/chat/chat-bot.md @@ -0,0 +1,139 @@ +# chat-bot:机器人与 Webhook + +> 返回入口:[chat.md](../chat.md) + +## 适用场景 + +用于搜索机器人、机器人发送/撤回消息、Webhook 告警、机器人加入/移出群,以及给机器人发单聊消息。 + +## 必读约束 + +- 用户明确要求“用机器人/机器人身份/robot”发送时,必须用 `chat message send-by-bot`,严禁改用 `chat message send`。 +- `chat bot search` 只返回我创建的机器人,没有 `openDingTalkId`;给机器人发单聊必须用 `chat bot find`。 +- 机器人发群消息前需确认机器人已在群中;报“机器人不存在”时先 `group members add-bot`。 +- `send-by-bot --text` 支持 Markdown;需要稳定换行时用空行分隔段落。若以转义形式组织文本,写 `\n\n`,不要只写 `\n`。 +- `recall-by-bot` 使用 `processQueryKey`,不是 `openMessageId`。 + +## 命令明细 + +### 机器人搜索 + +| 命令 | 范围 | 返回 openDingTalkId | 典型触发词 | +|------|------|---------------------|------------| +| `chat bot search` | 仅当前用户自己创建的机器人 | 否 | “我的机器人”“我创建的机器人” | +| `chat bot find` | 当前用户可用的全部机器人(含他人/官方) | 是 | “找机器人”“搜索机器人”“给机器人发单聊” | + +```bash +dws chat bot search --page 1 --size 10 --name "日报" +dws chat bot find --query "日报" --limit 20 +dws chat bot find --query "日报" --limit 20 --cursor +``` + +`bot find` 翻页时 `cursor` 必须使用上次返回的 `nextCursor` 字符串原值,不要传 `"0"` 或数字字面量。 + +### 机器人发送与撤回 + +#### `dws chat message send-by-bot` + +```bash +# 群聊 +dws chat message send-by-bot --robot-code --group --title "日报" --text "## 今日完成\n\n- 事项 A\n\n- 事项 B" + +# 单聊 userId +dws chat message send-by-bot --robot-code --users userId1,userId2 --title "提醒" --text "请提交周报" + +# 单聊 openDingTalkId +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: + +| Flag | 说明 | +|------|------| +| `--robot-code` | 机器人 Code,必填 | +| `--group` | 群聊 openConversationId | +| `--users` | 单聊 userId 列表,逗号分隔,最多 20 个 | +| `--open-dingtalk-ids` | 单聊 openDingTalkId 列表 | +| `--title` / `--text` | 消息标题与 Markdown 内容,必填;Markdown 换行用空行,转义表示为 `\n\n` | +| `--at-user-ids` / `--at-open-dingtalk-ids` | 群聊 @ 指定成员,正文需含对应 `@id` 文本 | +| `--at-all` | 群聊 @所有人 | + +#### `dws chat message recall-by-bot` + +```bash +dws chat message recall-by-bot --robot-code --group --keys +dws chat message recall-by-bot --robot-code --keys key1,key2 +``` + +群聊撤回传 `--group`;单聊撤回不传 `--group`。`--keys` 来自 `send-by-bot` 返回的 `processQueryKey`。 + +### Webhook + +```bash +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 +``` + +关键规则: + +- `--token`、`--title`、`--text` 必填。 +- `--at-all` 时 `--text` 中需包含 `@10`。 +- `--at-users` 或 `--at-mobiles` 时,`--text` 中需包含对应 `@userId` 或 `@手机号`,否则 @ 不生效。 + +### 机器人进群 + +| 命令 | 用途 | 必填参数 | +|------|------|----------| +| `group members add-bot` | 将自定义机器人加入群 | `--id` `--robot-code` | +| `group members remove-bot` | 从群移除机器人 | `--id` `--bot-id` | +| `group bots` | 查看群内机器人列表 | `--group` | + +```bash +dws chat group members add-bot --id --robot-code +dws chat group bots --group +dws chat group members remove-bot --id --bot-id +``` + +## 常见工作流 + +### 机器人发消息后撤回 + +```bash +dws chat bot search --name "日报" --format json +dws chat message send-by-bot --robot-code --group --title "日报" --text "## 今日完成\n\n- 事项 A\n\n- 事项 B" --format json +dws chat message recall-by-bot --robot-code --group --keys --format json +``` + +### 机器人不在群内时先邀请再发送 + +```bash +dws chat bot search --name "日报" --format json +dws chat group members add-bot --id --robot-code --format json +dws chat message send-by-bot --robot-code --group --title "通知" --text "内容" --format json +``` + +### 给机器人发单聊 + +```bash +dws chat bot find --query "玉澜" --format json +dws chat message send --open-dingtalk-id --text "你好" --format json +``` + +### 机器人 @ 指定人 + +```bash +dws aisearch person --keyword "张三" --dimension name --format json +dws chat message send-by-bot --robot-code --group --at-user-ids userId1 --title "提醒" --text "@userId1 请查收" --format json +``` + +## 常见错误与回退 + +- 机器人单聊没有 openDingTalkId:改用 `chat bot find`,不要用 `bot search`。 +- 机器人发群消息报“机器人不存在”:先 `group members add-bot`。 +- 撤回失败:确认使用 `processQueryKey`,不是 `openMessageId`。 +- @ 不生效:检查正文是否包含 `@userId` / `@openDingTalkId` / `@10`。 diff --git a/skills/multi/dingtalk-chat/references/chat/chat-conversation.md b/skills/multi/dingtalk-chat/references/chat/chat-conversation.md new file mode 100644 index 00000000..7803ed59 --- /dev/null +++ b/skills/multi/dingtalk-chat/references/chat/chat-conversation.md @@ -0,0 +1,140 @@ +# chat-conversation:会话状态、红点、置顶与分组 + +> 返回入口:[chat.md](../chat.md) + +## 适用场景 + +用于获取会话基础信息、全部会话/置顶会话列表、会话置顶、免打扰、隐藏、红点、已读未读、清空聊天记录、自定义会话分组和智能会话分组。 + +## 必读约束 + +- 会话状态类命令通常需要 `openConversationId`。群聊可由 `chat search` 获取,单聊可由 `chat conversation-info --user/--open-dingtalk-id` 获取。 +- `set-top` 是会话置顶;`message set-top-msg` 是会话内消息置顶,二者不能混用。 +- `clear-messages` 只清空当前用户视角的消息,不影响其他成员。 +- 智能分组规则中的成员使用 openDingTalkId;如果用户只给姓名,先用 `aisearch person --dimension name` 获取。 + +## 命令明细 + +### 会话基础信息 + +```bash +dws chat conversation-info --group --format json +dws chat conversation-info --user --format json +dws chat conversation-info --open-dingtalk-id --format json +``` + +`--group`、`--user`、`--open-dingtalk-id` 互斥且必须指定一个。文件/音视频发送不再依赖调用方读取 spaceId;直接用 `message send --msg-type file|audio|video --file-path`。 + +### 会话列表与红点 + +| 命令 | 用途 | 参数 | +|------|------|------| +| `list-all-conversations` | 分页获取当前用户全部会话 | 可选 `--limit` `--cursor` `--exclude-muted` | +| `list-top-conversations` | 获取置顶会话列表 | 可选 `--limit` `--cursor` `--exclude-muted` | +| `message list-unread-conversations` | 获取未读会话列表 | 可选 `--count` `--exclude-muted` | +| `clear-red-point` | 清除指定会话红点 | `--conversation-id`,别名 `--id` / `--chat` | +| `clear-all-red-point` | 清除所有会话红点,一键全部已读 | 无参数 | + +翻页时,`hasMore=true` 用返回的 `nextCursor` 作为下次 `--cursor`。 + +### 会话置顶与通知 + +| 命令 | 用途 | 必填参数 | +|------|------|----------| +| `set-top` | 设置/取消会话置顶 | `--conversation-id`;默认置顶,`--off` 取消 | +| `mute` | 开启/关闭会话免打扰 | `--conversation-id`;默认开启,`--off` 关闭 | +| `hide` | 隐藏会话 | `--conversation-id` | +| `mute-at-all` | 关闭/恢复 @所有人通知 | `--conversation-id`;默认关闭,`--off` 恢复 | +| `mute-red-envelope` | 关闭/恢复红包通知 | `--conversation-id`;默认关闭,`--off` 恢复 | + +```bash +dws chat set-top --conversation-id +dws chat set-top --conversation-id --off +dws chat mute --conversation-id +dws chat mute --conversation-id --off +dws chat hide --conversation-id +``` + +### 已读未读与清理 + +| 命令 | 用途 | 必填参数 | +|------|------|----------| +| `mark-unread` | 标记指定会话为未读 | `--conversation-id` | +| `mark-read` | 将指定消息及之前消息标记为已读 | `--conversation-id` `--message-id` | +| `clear-messages` | 清空当前用户指定会话的消息 | `--conversation-id` | + +```bash +dws chat mark-unread --conversation-id +dws chat mark-read --conversation-id --message-id +dws chat clear-messages --conversation-id +``` + +### 会话分组 + +| 命令 | 用途 | 必填参数 | +|------|------|----------| +| `category list` | 获取用户自定义会话分组 | 无 | +| `category list-conversations` | 拉取指定分组下会话 | `--category-id`,可选 `--exclude-muted` | +| `category list-by-conv` | 拉取指定会话所属的用户自定义会话分组 | `--group` | +| `category batch-info` | 批量拉取用户自定义会话分组信息 | `--category-ids` | +| `category create` | 创建会话分组 | `--title` | +| `category create-smart` | 创建智能会话分组,可按群名称关键词和群内成员匹配 | `--name`,可选 `--keywords` `--members` | +| `category delete` | 删除会话分组 | `--category-id` | +| `category rename` | 修改分组名称 | `--category-id` `--title` | +| `category add-conv` | 将会话加入分组 | `--group` `--category-ids` | +| `category remove-conv` | 将会话移出分组 | `--group` `--category-ids` | + +```bash +dws chat category list +dws chat category list-by-conv --group +dws chat category batch-info --category-ids 123,456 +dws chat category create --title "工作群" +dws chat category create-smart --name "重点群" --keywords "重点,项目" --members openDingTalkId1,openDingTalkId2 +dws chat category add-conv --group --category-ids 123,456 +``` + +`create-smart` 中 `--keywords` 是群名称关键词列表,`--members` 是群内成员 openDingTalkId 列表;两者可单独使用,也可组合使用。 + +## 常见工作流 + +### 获取单聊会话 ID 后置顶 + +```bash +dws aisearch person --keyword "张三" --dimension name --format json +dws chat conversation-info --user --format json +dws chat set-top --conversation-id --format json +``` + +### 查看置顶会话并拉消息 + +```bash +dws chat list-top-conversations --limit 100 --format json +dws chat message list --group --time "2026-03-10 00:00:00" --direction older --format json +``` + +### 会话分组 + +```bash +dws chat category create --title "重点项目" --format json +dws chat category add-conv --group --category-ids --format json +dws chat category list-by-conv --group --format json +dws chat category batch-info --category-ids --format json +dws chat category list-conversations --category-id --format json +``` + +### 智能会话分组 + +```bash +dws aisearch person --keyword "张三" --dimension name --format json +dws chat category create-smart --name "项目组" --keywords "项目,开发" --format json +dws chat category create-smart --name "团队群" --members openDingTalkId1,openDingTalkId2 --format json +dws chat category create-smart --name "重点群" --keywords "重点" --members openDingTalkId1 --format json +``` + +## 常见错误与回退 + +- 用户说“置顶消息”:用 `message set-top-msg`,不是 `chat set-top`。 +- 用户说“置顶会话”:用 `chat set-top` 或 `list-top-conversations`。 +- 单聊没有会话 ID:先 `conversation-info --user` 或 `--open-dingtalk-id`。 +- 清空聊天记录前必须确认目标会话;该操作只影响当前用户视角。 +- 智能分组没有匹配条件:至少确认分组名称;关键词和成员规则不明确时先向用户确认,不要自行猜成员。 diff --git a/skills/multi/dingtalk-chat/references/chat/chat-group.md b/skills/multi/dingtalk-chat/references/chat/chat-group.md new file mode 100644 index 00000000..1aeb0aa3 --- /dev/null +++ b/skills/multi/dingtalk-chat/references/chat/chat-group.md @@ -0,0 +1,225 @@ +# chat-group:群聊、成员、设置与群身份 + +> 返回入口:[chat.md](../chat.md) + +## 适用场景 + +用于群搜索、建群、成员增删、机器人进群、群设置、群主转让、群邀请分享、群公告、入群审批、群身份和群禁言。 + +## 必读约束 + +- 群聊目标统一使用 `openConversationId`。只有数字群号时,先用 `chat group get-by-group-id` 转换。 +- `chat search` 在 `chat` 根下,不在 `chat group` 下。 +- 群成员操作中,`--users` 常为逗号分隔列表;具体要求以命令 `--help` 为准。 +- 解散群、踢人、转让群主、禁言、管理员设置都是高影响操作,执行前必须确认目标群和用户。 +- 创建群并指定群主时,`--owner` 必须是用户明确指定或确认过的 `openDingTalkId`。 +- 发布或修改群公告会触达群成员;公告正文是 Markdown,定时公告 `--run-at` 建议带时区。 + +## 命令明细 + +### 搜索与基础信息 + +| 命令 | 用途 | 示例与要点 | +|------|------|------------| +| `chat search` | 按关键词搜索群聊 | `dws chat search --query "项目冲刺" --limit 20 --cursor 0`;可选 `--exclude-muted` | +| `chat search-common` | 搜索共同群 | `dws chat search-common --nicks "风雷,山乔" --match-mode AND --limit 20 --cursor 0` | +| `chat group get-by-group-id` | 数字群号转 openConversationId | `dws chat group get-by-group-id --group-id 12345678` | +| `chat group bots` | 查看群内所有机器人 | `dws chat group bots --group ` | + +`search-common` 中 `--match-mode AND` 表示所有人都在群里,`OR` 表示任一人在群里。 + +### 群创建与基础操作 + +#### `dws chat group create` + +```bash +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: + +| Flag | 说明 | +|------|------| +| `--name` | 群名称,必填 | +| `--users` | 成员 userId 或 openDingTalkId,逗号分隔,必填 | +| `--owner` | 指定群主 openDingTalkId,可选;仅在用户明确指定群主时使用 | +| `--type` | `INTERNAL` / `EXTERNAL` / `NORMAL`,默认 `INTERNAL` | +| `--thread` | 开启话题模式,创建话题圈 | + +创建成功后提取 `openConversationId`,用于发消息、成员管理、群设置。 + +| 命令 | 用途 | 必填参数 | +|------|------|----------| +| `group rename` | 更新群名称 | `--id` `--name` | +| `group quit` | 当前用户退出群聊 | `--group` | +| `group dismiss` | 解散群聊,不可逆 | `--group` | + +### 成员与机器人 + +| 命令 | 用途 | 示例 | +|------|------|------| +| `group members` | 分页查看群成员 | `dws chat group members --id --cursor 0` | +| `group members add` | 添加群成员 | `dws chat group members add --id --users userId1,userId2` | +| `group members remove` | 移除群成员 | `dws chat group members remove --id --users userId1,userId2` | +| `group members list-by-ids` | 按 openDingTalkId 批量查成员详情 | `dws chat group members list-by-ids --id --users openDingTalkId1,openDingTalkId2` | +| `group members add-bot` | 将自定义机器人加入群 | `dws chat group members add-bot --id --robot-code ` | +| `group members remove-bot` | 从群内移除机器人 | `dws chat group members remove-bot --id --bot-id ` | + +机器人发群消息如果报“机器人不存在”,先 `group members add-bot` 再重发。 + +### 群设置与权限 + +| 命令 | 用途 | 必填参数 | +|------|------|----------| +| `group transfer-owner` | 转让群主 | `--group` + `--user`(userId) 或 `--new-owner`(openDingTalkId) | +| `group upgrade-to-external` | 将普通群升级为外部群(不可逆,需先确认) | `--group` `--yes`;可选 `--extension`(拓展字段) | +| `group invite-url` | 获取群邀请链接 | `--group`,可选 `--expires-seconds`,0 表示永久 | +| `group share-invite` | 将指定群的邀请链接分享到另一个会话或单聊用户 | `--source` + `--target` / `--receiver` 二选一 | +| `group update-icon` | 更新群头像 | `--group` `--icon-media-id` | +| `group update-settings` | 更新管理员级别的群功能开关 | `--group` `--setting-key` `--status` | +| `group user-settings query` | 批量查询当前用户自己的群会话设置 | `--groups` | +| `group user-settings set` | 批量更新当前用户自己的群会话设置 | `--items` | +| `group update-nick` | 设置或清除当前用户群昵称 | `--group`,可选 `--nick`;不传则清除群昵称 | +| `group update-alias` | 设置当前用户群备注 | `--group` `--alias-title` | +| `group set-history` | 设置新成员可查看历史消息范围 | `--group` `--option` | +| `group get-mute-config` | 查询群用户禁言配置 | `--group` | +| `group-mute` | 全员禁言/解除全员禁言 | `--group`,默认禁言,`--off` 解除 | +| `group-mute-member` | 指定成员禁言/解除禁言 | `--group` `--user`/`--users`;禁言需 `--mute-time` | +| `group set-admin` | 设置/取消管理员 | `--group` `--user`/`--users`;`--off` 取消 | + +`update-settings` 是管理员级别的群功能开关操作,常用 settingKey:`authority`、`joinValidation`、`onlyAdminCanAtAll`、`searchable`、`addFriendForbidden`、`onlyAdminCanDING`、`onlyAdminCanPinMsg`、`onlyAdminCanSendFile`、`groupEmailDisabled`、`groupLiveAuthority`、`groupBillAuthority`。 + +`group user-settings` 是当前登录用户自己的群会话设置批量入口(置顶、免打扰、群昵称、群备注等),不是管理员级别的群功能开关;单个群昵称/群备注仍优先使用 `group update-nick` / `group update-alias`,管理员级别的群功能开关继续使用 `group update-settings`。 + +`group user-settings set --items` 传 JSON 数组,每个元素表示一个群会话的当前用户设置。字段含义: + +| 字段 | 含义 | 值说明 | +|------|------|--------| +| `openConversationId` | 群会话 ID | 必填,来自 `chat search` / `group list-all` 等真实返回 | +| `top` | 当前用户是否置顶该群会话 | `true`=置顶,`false`=取消置顶 | +| `mute` | 当前用户是否开启该群会话免打扰 | `true`=开启免打扰,`false`=关闭免打扰 | +| `groupNick` | 当前用户在该群里的群昵称 | 字符串;空字符串表示清空昵称 | +| `groupAlias` | 当前用户给该群设置的备注 | 字符串;空字符串表示清空备注 | + +只传本次要改的字段;不要补用户没要求修改的字段。批量设置多个群时,`items` 放多个对象。 + +`group-mute-member --mute-time` 单位毫秒,常用值:`300000`、`3600000`、`86400000`、`604800000`、`2592000000`。 + +`group share-invite` 的 `--source` 是被分享群的 `openConversationId`;`--target` 是接收分享消息的会话,`--receiver` 是接收分享消息的单聊用户 `openDingTalkId`,二者必须二选一。 + +```bash +dws chat group share-invite --source --target +dws chat group share-invite --source --receiver +dws chat group share-invite --source --target --expires-seconds 86400 --uuid +dws chat group user-settings query --groups , +dws chat group user-settings set --items '[{"openConversationId":"cid1","top":true,"mute":false}]' +``` + +### 群公告 + +群公告正文使用 Markdown。支持标题、加粗、斜体、删除线、行内代码、链接、代码块、列表、表格、引用、分割线、图片、段落和换行;下划线、字体色、背景色、字号属于编辑器专属能力,无法通过 Markdown 表达。 + +| 命令 | 用途 | 必填参数 | +|------|------|----------| +| `group notice create` | 发布群公告,支持置顶、DING 和定时发布 | `--group` `--content` | +| `group notice edit` | 整体替换指定群公告内容 | `--group` `--notice-id` `--content` | +| `group notice get` | 查询单条群公告详情 | `--group` `--notice-id` | +| `group notice list` | 分页拉取群公告列表 | `--group` | + +```bash +dws chat group notice create --group --content "今晚 22 点系统维护,请提前保存工作内容" +dws chat group notice create --group --content "# 重要通知\n\n请大家查收" --sticky --send-ding +dws chat group notice create --group --content "明早九点例会" --run-at "2026-07-03T09:00:00+08:00" +dws chat group notice list --group --limit 20 --cursor +dws chat group notice get --group --notice-id +dws chat group notice edit --group --notice-id --content "更新后的公告内容" +``` + +注意事项: + +- `notice edit` 会整体替换原公告正文,必须传完整的新内容。 +- `notice list --scheduled` 查询尚未到发布时间的定时公告;默认查询已发布公告。 +- `hasMore=true` 时,用返回的 `nextPageCursor` 继续翻页。 +- `notice get` 返回正文摘要、置顶状态、发布者、已读人数/应收人数、点赞/评论数、是否可编辑、是否已读、是否定时公告等信息。 + +### 群列表与入群审批 + +| 命令 | 用途 | 示例与要点 | +|------|------|------------| +| `group list-my-groups` | 拉取我创建/管理的群 | 可选 `--role OWNER/ADMIN`、`--limit`、`--exclude-muted` | +| `group list-all` | 分页拉取我加入的所有群 | `--limit` 默认 100,最大 200;翻页用 `nextCursor` | +| `group list-join-validations` | 拉取入群验证记录 | 包括自己被拒绝的记录以及作为审批者的记录 | +| `group audit-join-validation` | 审批入群验证 | `--group` `--record-id` `--applicant` `--inviter` `--status` | + +审批动作 `--status`:`AuditApprove`、`AuditDelete`、`AuditIgnore`、`AuditRefuse`、`AuditBlock`。 + +```bash +dws chat group list-join-validations --limit 20 +dws chat group audit-join-validation --group --record-id 123456 --applicant --inviter --status AuditApprove +``` + +### 群身份 + +`chat group-role` 管理群内自定义身份标签。 + +| 命令 | 用途 | 必填参数 | +|------|------|----------| +| `group-role list` | 查看群身份列表 | `--group` | +| `group-role add` | 新增群身份 | `--group` `--name` | +| `group-role update` | 修改群身份名称 | `--group` `--role-id` `--name` | +| `group-role remove` | 删除群身份 | `--group` `--role-id` | +| `group-role set-user` | 覆盖用户全部群身份,空 `--role-ids` 表示清除 | `--group` `--user` `--role-ids` | +| `group-role remove-user` | 移除用户指定群身份 | `--group` `--user` `--role-ids` | +| `group-role query-user` | 查询用户当前群身份 | `--group` `--user` | + +`openRoleId` 来自 `group-role list` 返回。 + +## 常见工作流 + +### 搜索群并发消息 + +```bash +dws chat search --query "项目冲刺" --format json +dws chat message send --group --text "请大家看一下最新进展" --format json +``` + +### 建群并拉人 + +```bash +dws aisearch person --keyword "张三" --dimension name --format json +dws chat group create --name "Q1 项目冲刺群" --users userId1,userId2 --format json +dws chat group members add --id --users userId3,userId4 --format json +``` + +### 建群并指定群主 + +```bash +dws aisearch person --keyword "群主姓名" --dimension name --format json +dws chat group create --name "项目群" --users userId1,userId2 --owner --format json +``` + +### 分享群邀请并发布公告 + +```bash +dws chat group share-invite --source --target --format json +dws chat group notice create --group --content "# 项目公告\n\n请大家关注最新安排" --send-ding --format json +``` + +### 设置管理员并禁言成员 + +```bash +dws chat group set-admin --group --users userId1,userId2 --format json +dws chat group-mute-member --group --users userId3 --mute-time 3600000 --format json +``` + +## 常见错误与回退 + +- 只有数字群号:先 `group get-by-group-id`,不要直接当 `--group`。 +- 找不到群:先扩大 `chat search --query`,不要臆测 openConversationId。 +- 入群审批缺参数:从 `group list-join-validations` 提取 `record-id`、`applicant`、`inviter`。 +- 机器人进群失败:确认当前用户有群管理权限。 +- 分享群邀请目标不明确:`--target` 和 `--receiver` 只能二选一,先确认是发到群/会话还是发给个人。 +- 修改公告前没有完整新正文:先向用户确认完整公告内容,不要只传增量片段。 diff --git a/skills/multi/dingtalk-chat/references/chat/chat-message.md b/skills/multi/dingtalk-chat/references/chat/chat-message.md new file mode 100644 index 00000000..7e412ef6 --- /dev/null +++ b/skills/multi/dingtalk-chat/references/chat/chat-message.md @@ -0,0 +1,269 @@ +# chat-message:消息、文件、搜索与卡片 + +> 返回入口:[chat.md](../chat.md) + +## 适用场景 + +用于当前用户发消息、拉消息、搜索消息、撤回、已读状态、文件/图片/音频/视频/位置/名片发送、话题回复、转发、Pin/Top、表情回应、文本翻译和流式卡片。 + +## 必读约束 + +- 发消息前必须核对接收对象、消息内容、@ 对象、附件路径和消息类型;不明确时先问用户。 +- `--group`、`--user`、`--open-dingtalk-id` 通常互斥,群聊用 `--group`,单聊用 `--user` 或 `--open-dingtalk-id`。 +- 发送本地文件、音频、视频统一用 `chat message send --msg-type file|audio|video --file-path `;`audio` / `video` 底层按 `file` 链路发送;旧图片链路 `--msg-type image --media-id` 仅在已有 mediaId 时使用。 +- 发送位置消息前必须确认纬度、经度、地址名称;地图缩略图需先通过旧媒体上传链路拿到 mediaId。 +- 分享联系人名片前必须确认联系人 `openDingTalkId`,不要把 userId 直接当 `--contact-id`。 +- 消息内容按 Markdown 渲染,换行必须是真实换行符;需要换行效果时用空行、行尾两个空格或 `
`。 +- 建议发送时带 `--uuid`,失败重试复用同一个值。 + +## 命令明细 + +### 发送消息 + +#### `dws chat message send` + +以当前用户身份发送群聊或单聊消息。 + +```bash +# 文本/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 "请大家本周五前提交周报" --uuid + +# @ 群成员 +dws chat message send --group --at-all "<@all> 请大家注意" +dws chat message send --group --at-open-dingtalk-ids odt1,odt2 "<@odt1> <@odt2> 请查收" + +# 图片/文件/音频/视频,一条命令直发 +dws chat message send --group --msg-type file --file-path ./screenshot.png +dws chat message send --open-dingtalk-id --msg-type file --file-path ./report.pdf +dws chat message send --group --msg-type audio --file-path ./voice.mp3 +dws chat message send --group --msg-type video --file-path ./demo.mp4 + +# 位置/联系人名片 +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: + +| Flag | 说明 | +|------|------| +| `--group` | 群聊 openConversationId;别名 `--id` / `--chat` / `--conversation-id` | +| `--user` | 单聊接收人 userId | +| `--open-dingtalk-id` | 单聊接收人 openDingTalkId | +| `--text` | 消息内容,推荐使用;也支持位置参数 | +| `--title` | 消息标题,未传时使用安全标题 | +| `--at-all` | 群聊 @所有人,正文需含 `<@all>` | +| `--at-open-dingtalk-ids` | 群聊 @指定 openDingTalkId,正文需含 `<@id>` | +| `--msg-type` | `file` / `audio` / `video` / `image` / `location` / `profile`;本地音视频用 `audio` / `video`,底层按 `file` 发送 | +| `--file-path` | 本地文件路径,`msg-type=file/audio/video` 时自动上传并发送 | +| `--media-id` | 旧图片链路 mediaId | +| `--latitude` / `--longitude` / `--location-name` | 位置消息参数 | +| `--map-thumbnail-url` | 位置消息缩略图 mediaId,形如 `@mediaId` | +| `--contact-id` | 联系人名片 openDingTalkId | +| `--uuid` | 幂等 UUID,24h 内相同值不重复投递 | + +话题回复:`chat message list` 返回的 `openConvThreadId` 可直接作为 `--group` 值发送到话题内,禁止自行拼接。 + +### 拉取消息 + +| 命令 | 用途 | 示例与要点 | +|------|------|------------| +| `message list` | 拉取指定群聊或单聊消息 | `dws chat message list --group --time "2025-03-01 00:00:00" --direction older`;目标三选一,`--direction newer/older` 优先于旧 `--forward` | +| `message list-all` | 时间范围内全部会话消息 | `dws chat message list-all --start --end --limit 100 --cursor 0`;四个参数每次请求都传,翻页用 `nextCursor` | +| `message list-by-sender` | 查指定发送者消息 | `--sender-user-id` 与 `--sender-open-dingtalk-id` 二选一,跨单聊+群聊 | +| `message list-mentions` | 查 @ 我的消息 | 可传 `--group` 限定群,不传查全部 | +| `message list-focused` | 查特别关注人消息 | 零参数可用,可加 `--limit` / `--cursor` | +| `message list-unread-conversations` | 未读会话列表 | 可选 `--count` | +| `message list-topic-replies` | 拉取话题回复 | `--group --topic-id ` | +| `message list-by-ids` | 按消息 ID 批量查询 | `--msg-ids msgId1,msgId2`,最多 50 条 | + +`message list` 注意事项: + +- `--group`、`--user`、`--open-dingtalk-id` 互斥且必须指定一个。 +- `--time` 格式为 `yyyy-MM-dd HH:mm:ss`。 +- `hasMore=true` 时,用结果中的边界 `createTime` 作为下次 `--time`。 +- 返回 `openConvThreadId` 表示话题消息,完整内容需再拉 `list-topic-replies`。 + +### 搜索消息 + +优先使用 `message search-advanced`,它是 `message search` 的严格超集。 + +```bash +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 --end +dws chat message search-advanced --at-me --start --end +dws chat message search-advanced --conversation-ids , --query "合同" --limit 50 --cursor 0 +dws chat message search-advanced --message-type file --search-conv-type group_chat --query "附件" +dws chat message search-advanced --only-robot-messages --query "通知" +``` + +| 参数 | 说明 | +|------|------| +| `--query` | 搜索关键词,可选 | +| `--user` / `--users` | 发送者 userId | +| `--sender-ids` | 发送者 openDingTalkId | +| `--at-me` / `--at-ids` | @ 我 / @ 指定 openDingTalkId | +| `--conversation-ids` | 多个群聊或单聊 openConversationId;别名 `--groups` | +| `--message-type` | 按消息类型过滤,例如 `file` | +| `--search-conv-type` | 按会话类型过滤,例如 `group_chat` | +| `--only-robot-messages` | 只搜索机器人消息 | +| `--start` / `--end` | ISO-8601 时间范围 | +| `--cursor` / `--limit` | 分页,翻页用 `nextCursor` | + +仅简单关键词搜索时可用: + +```bash +dws chat message search --query "changefree" --start --end --limit 50 --cursor 0 +dws chat message search --query "codereview" --group --start --end +``` + +### 消息状态与撤回 + +| 命令 | 用途 | 必填参数 | +|------|------|----------| +| `message query-send-status` | 查询当前用户发消息任务状态 | `--open-task-id`,来自 `message send` 返回 | +| `message recall` | 撤回当前用户消息;群主/管理员可撤回群内他人消息 | `--conversation-id` `--msg-id` | +| `message edit` | 编辑已发送消息内容 | `--conversation-id` `--msg-id`,并在 `--text` / `--content` 中二选一;可选 `--title` `--at-all` `--at-open-dingtalk-ids` | +| `message read-status` | 查消息已读/未读状态 | `--group` `--message-id`;可选目标用户 | + +`recall` 与 `recall-by-bot` 不同:前者通过 IM 接口撤回用户消息,需要 `openConversationId + openMessageId`,支持撤回自己发出的消息,也支持群主/管理员撤回群内他人消息;后者撤回机器人消息,需要 `robot-code + processQueryKey`。 + +编辑消息使用 `message edit`。推荐传 `--text`,CLI 会生成 markdown content JSON:`{"title":"标题","text":"正文"}`;可选 `--title`,不传时会从正文自动生成标题。高级场景可直接传 `--content`,此时必须是完整 markdown content JSON,且不能同时传 `--text`。 + +@ 规则:`--at-all` 会传 `atAll=true`,正文应包含 `<@all>`,未包含时 CLI 会自动补到开头;`--at-open-dingtalk-ids` 会传 `atOpenDingTalkIds`,正文需包含对应 `<@openDingTalkId>` 占位符,裸 `@openDingTalkId` 会自动补成尖括号格式。 + +```bash +dws chat message edit --conversation-id --msg-id --text "更新后的内容" +dws chat message edit --group --msg-id --title "标题" --text "更新后的内容" +dws chat message edit --group --msg-id --text "<@all> 请查看" --at-all +dws chat message edit --group --msg-id --text "<@openDingTalkId1> 请查看" --at-open-dingtalk-ids +dws chat message edit --group --msg-id --content '{"title":"标题","text":"更新后的内容"}' +``` + +### 回复与转发 + +| 命令 | 用途 | 必填参数 | +|------|------|----------| +| `message reply` | 引用回复,单聊/群聊均可 | `--conversation-id` `--ref-msg-id` `--ref-sender` `--text` | +| `message forward` | 转发单条消息,源/目标均支持单聊/群聊 | `--src-conversation-id` `--msg-id` `--dest-conversation-id` | +| `message combine-forward` | 多条消息合并为一条转发 | `--src-conversation-id` `--msg-ids` `--dest-conversation-id`,可选 `--uuid` | +| `message forward-topic` | 转发话题消息 | `--src-msg-id` `--src-conversation-id` `--src-thread-id` `--dest-conversation-id` | + +### 话题与卡片 + +话题完整读取流程: + +1. `dws chat message list --group --time ...` 获取话题主消息。 +2. 如果返回 `openConvThreadId`,执行 `dws chat message list-topic-replies --group --topic-id `。 + +流式卡片必须 `send-card` 与 `update-card` 搭配: + +```bash +dws chat message send-card --group +dws chat message send-card --receiver +dws chat message update-card --biz-id --content "更新的卡片内容" --flow-status 2 +dws chat message update-card --biz-id --content "最终内容" --flow-status 3 +``` + +`flow-status`:1=处理中,2=输入中,3=完成,4=执行中,5=错误。最后一次必须设为 3,否则卡片会停留在“生成中”。 + +### Pin / Top / Favorite + +| 命令 | 用途 | 必填参数 | +|------|------|----------| +| `message set-pin-msg` / `unset-pin-msg` | 钉住/取消钉住消息 | `--open-conversation-id` `--msg-id` | +| `message list-pin-msg` | 拉取钉住消息列表 | `--open-conversation-id`,可选 `--cursor` `--size` | +| `message set-top-msg` / `unset-top-msg` | 置顶/取消置顶会话内某条消息 | `--open-conversation-id` `--msg-id` | +| `message add-favorite` | 收藏消息 | `--open-message-id` `--open-conversation-id` | +| `message remove-favorite` | 取消收藏消息 | `--open-message-id` `--open-conversation-id` | +| `message list-favorites` | 查询收藏消息列表 | 可选 `--cursor` `--size` | + +消息置顶 `set-top-msg` 与会话置顶 `chat set-top` 不同:前者置顶会话内消息,后者置顶整个会话。 + +### 表情回应 + +优先查 [chat-emoji-list.md](../chat-emoji-list.md) 中的默认表情名称。 + +| 命令 | 场景 | 必填参数 | +|------|------|----------| +| `message add-emoji` / `remove-emoji` | 默认表情命中时使用 | `--conversation-id` `--msg-id` `--emoji` | +| `message create-text-emotion` | 默认表情没有合适项时先创建 | `--emotion-name` `--text`,可选 `--background-id` | +| `message add-text-emotion` / `remove-text-emotion` | 添加/移除文字表情 | `--conversation-id` `--msg-id` `--emotion-id` `--emotion-name` `--text` `--background-id` | +| `message update-text-emotion` | 用新的文字表情替换消息上的原回应 | `--conversation-id` `--msg-id` `--old-emotion-id` `--emotion-id` `--emotion-name` `--text` `--background-id` | +| `message list-emotion-replies` | 批量查询消息的表情回复和文字回复 | `--msg-ids` | + +```bash +dws chat message list-emotion-replies --msg-ids msgId1,msgId2,msgId3 +``` + +消息 ID 可通过 `dws chat message list` 获取;该命令用于一次性查看多条消息上的 emoji 回应和文字表情回应。 + +### 文本工具 + +#### `dws chat text translate` + +将指定文本翻译成目标语言。用户只说“翻译这段文字”时使用;不要误走 `message send`。 + +```bash +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: + +| Flag | 说明 | +|------|------| +| `--query` | 待翻译文本,必填 | +| `--to` | 目标语言代码,必填,默认 `en_US` | + +### 文件与媒体 + +#### `dws chat message download-media` + +```bash +dws chat message download-media --type mediaId --resource-id --message-id --open-conversation-id --output ./downloads/ +``` + +`resource-id` 来自消息内容中的 mediaId,`message-id` 来自 `openMessageId`,会话 ID 来自 `chat search` 或 `conversation-info`。 + +## 常见工作流 + +### 群聊发文字与文件 + +```bash +dws chat search --query "项目冲刺" --format json +dws chat message send --group --text "请大家本周五前提交周报" --uuid --format json +dws chat message send --group --msg-type file --file-path ./report.pdf --format json +dws chat message send --group --msg-type audio --file-path ./voice.mp3 --format json +dws chat message send --group --msg-type video --file-path ./demo.mp4 --format json +dws chat message send --group --msg-type location --latitude <纬度> --longitude <经度> --location-name <地址名称> --map-thumbnail-url "@mediaId" --format json +dws chat message send --group --msg-type profile --contact-id --format json +``` + +### 查消息并撤回 + +```bash +dws chat message list --group --time "2026-03-10 00:00:00" --direction older --format json +dws chat message recall --conversation-id --msg-id --format json +``` + +### 多维度搜索 + +```bash +dws chat message search-advanced --query "合同" --conversation-ids , --start "2026-04-01T00:00:00+08:00" --end "2026-04-15T00:00:00+08:00" --limit 50 --cursor 0 --format json +dws chat message search-advanced --message-type file --search-conv-type group_chat --query "附件" --format json +``` + +## 常见错误与回退 + +- 发送目标不唯一:先确认群/人;群用 `chat search`,单聊用 `aisearch person` + `conversation-info`。 +- `unknown flag`:立即执行对应命令 `--help`,不要猜参数。 +- 文件/音视频发送失败:确认本地路径可读;新链路使用 `--msg-type file|audio|video --file-path`。 +- 位置消息参数不完整:先确认经纬度、地址名称和缩略图 mediaId。 +- 名片发送失败:确认 `--contact-id` 是 openDingTalkId,不是 userId。 +- 话题回复缺失:检查是否只拉了主消息,需继续用 `list-topic-replies`。 +- `search-advanced` 无条件:至少提供 query、sender、@、conversation、时间等任一有效过滤条件。 diff --git a/skills/multi/dingtalk-chat/references/chat/chat-workflows.md b/skills/multi/dingtalk-chat/references/chat/chat-workflows.md new file mode 100644 index 00000000..889c5e13 --- /dev/null +++ b/skills/multi/dingtalk-chat/references/chat/chat-workflows.md @@ -0,0 +1,179 @@ +# chat-workflows:高频流程与上下文传递 + +> 返回入口:[chat.md](../chat.md) + +## 适用场景 + +用于复合任务编排、字段传递、openDingTalkId 获取、自动化脚本和跨产品衔接。 + +## 必读约束 + +- 已知上游字段时直接使用,不要重复搜索。 +- 群聊 `openConversationId` 通常来自 `chat search`;单聊 `openConversationId` 来自 `conversation-info`。 +- `userId` 与 `openDingTalkId` 不是同一种标识,按命令要求传递。 + +## openDingTalkId 获取方式 + +| 场景 | 命令 | +|------|------| +| 搜人员 openDingTalkId | `dws aisearch person --keyword "姓名" --dimension name --format json` | +| 给机器人发单聊 | `dws chat bot find --query "机器人名" --format json`,不要用 `bot search` | +| 已有 userId 需要单聊会话 ID | `dws chat conversation-info --user --format json` | +| 已有 openDingTalkId 需要单聊会话 ID | `dws chat conversation-info --open-dingtalk-id --format json` | + +## 高频工作流 + +### 群聊消息 + +```bash +# 1. 搜索群,提取 openConversationId +dws chat search --query "项目冲刺" --format json + +# 2. 拉取群消息 +dws chat message list --group --time "2025-03-01 00:00:00" --direction older --format json + +# 3. 拉未读会话 +dws chat message list-unread-conversations --count 20 --format json +``` + +### 发送消息 + +```bash +# 个人身份发群消息 +dws chat message send --group --title "周报提醒" --text "请大家本周五前提交周报" --uuid --format json + +# 个人身份发单聊 +dws chat message send --user --text "你好" --uuid --format json +dws chat message send --open-dingtalk-id --text "你好" --uuid --format json + +# 发送图片/文件/音频/视频 +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 +dws chat message send --group --msg-type audio --file-path ./voice.mp3 --format json +dws chat message send --group --msg-type video --file-path ./demo.mp4 --format json + +# 文件后补文字说明 +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 +``` + +### 机器人消息 + +```bash +# 查我的机器人,提取 robotCode +dws chat bot search --name "日报" --format json + +# 机器人发群消息,提取 processQueryKey;Markdown 稳定换行用 \n\n,不要只用 \n +dws chat message send-by-bot --robot-code --group --title "日报" --text "## 今日完成\n\n- 事项 A\n\n- 事项 B" --format json + +# 撤回机器人消息 +dws chat message recall-by-bot --robot-code --group --keys --format json + +# 机器人不在群内时先邀请 +dws chat group members add-bot --id --robot-code --format json +dws chat message send-by-bot --robot-code --group --title "通知" --text "内容" --format json + +# 给机器人发单聊,必须用 find 拿 openDingTalkId +dws chat bot find --query "玉澜" --format json +dws chat message send --open-dingtalk-id --text "你好" --format json +``` + +### 机器人 @ 指定人 + +```bash +dws aisearch person --keyword "张三" --dimension name --format json + +dws chat message send-by-bot --robot-code --group \ + --at-user-ids userId1,userId2 \ + --title "提醒" --text "@userId1 @userId2 请查收本周报告" --format json + +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 +``` + +### Webhook 告警 + +```bash +dws chat message send-by-webhook --token --title "告警" --text "CPU 超 90% @10" --at-all --format json +``` + +### 话题完整读取与回复 + +```bash +dws chat message list --group --time "2026-03-10 00:00:00" --direction older --format json +dws chat message list-topic-replies --group --topic-id --format json +dws chat message send --group --text "回复话题内容" --format json +``` + +### 群公告与邀请分享 + +```bash +# 分享 A 群邀请链接到 B 群 +dws chat group share-invite --source --target --format json + +# 发布群公告,Markdown 段落换行用空行 +dws chat group notice create --group --content "# 重要通知\n\n请大家查收" --send-ding --format json + +# 修改公告前先列表查询 notice-id/dataId +dws chat group notice list --group --format json +dws chat group notice edit --group --notice-id --content "完整的新公告内容" --format json +``` + +### 智能分组与文本翻译 + +```bash +dws aisearch person --keyword "张三" --dimension name --format json +dws chat category create-smart --name "重点群" --keywords "重点,项目" --members openDingTalkId1 --format json +dws chat text translate --query "你好世界" --to en_US --format json +``` + +## 上下文传递表 + +| 操作 | 从返回中提取 | 用于 | +|------|-------------|------| +| `chat search` | `openConversationId` | `message send/list`、`group members`、`set-top`、`group-mute` | +| `chat group create` | `openConversationId` | 新群后续发消息、成员管理、群设置 | +| `chat group get-by-group-id` | `openConversationId` | 将数字群号转为可用会话 ID | +| `chat conversation-info` | `openConversationId` | 单聊消息搜索、转发、会话状态操作 | +| `aisearch person` | `userId` | `message send --user`、`send-by-bot --users`、`--at-user-ids`、`list-by-sender --sender-user-id` | +| `aisearch person` | `openDingTalkId` | `message send --open-dingtalk-id`、`--at-open-dingtalk-ids`、`send-by-bot --open-dingtalk-ids` | +| `aisearch person` | `userId` | 可替代人员搜索结果,继续用于 userId 参数 | +| `chat bot search` | `robotCode` | `send-by-bot`、`recall-by-bot` | +| `chat bot find` | `openDingTalkId` | 给机器人发单聊 | +| `chat group bots` | `openBotId` | `group members remove-bot --bot-id` | +| `chat message send-by-bot` | `processQueryKey` | `recall-by-bot --keys` | +| `chat message send` | `openTaskId` | `query-send-status --open-task-id` | +| `chat message list` | `openMessageId` | `message recall --msg-id`、`reply --ref-msg-id`、`forward --msg-id` | +| `chat message list` | `openMsgId` | `read-status --message-id`、表情回应命令 | +| `chat message list` | `openConvThreadId` | `list-topic-replies --topic-id`、话题回复 `message send --group` | +| `chat message search` | `nextCursor` | 下次 `message search --cursor` | +| `chat message search-advanced` | `nextCursor` | 下次 `search-advanced --cursor` | +| `chat message list-all` | `nextCursor` | 下次 `list-all --cursor` | +| `chat search-common` | `openConversationId` | 后续消息发送/拉取 | +| `chat group notice list/get/create` | `dataId` / `notice-id` | `group notice get/edit --notice-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 message list/search-advanced` | `openMessageId` / `openMsgId` | `list-emotion-replies --msg-ids` | +| `chat category list` | `categoryId` | `category list-conversations/add-conv/remove-conv --category-id(s)` | +| `chat category create-smart` | 智能分组 ID/规则结果 | 后续查看或调整智能分组 | +| `chat message send-card` | `bizId` | `update-card --biz-id` | + +## 自动化脚本 + +| 脚本 | 场景 | 用法 | +|------|------|------| +| [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 | 新场景不要用,文件/音视频直接 `--msg-type file|audio|video --file-path` | + +## 常见错误与回退 + +- 字段已由上游返回:直接传递,不要再次搜索。 +- `chat bot search` 没有 openDingTalkId:改用 `chat bot find`。 +- 群号是纯数字:先 `chat group get-by-group-id`。 +- 翻页不要自造 cursor:使用响应中的 `nextCursor` 原值。 +- 文件/音视频发送不要走旧上传链路:优先 `message send --msg-type file|audio|video --file-path`;`audio/video` 底层按 `file` 发送。 diff --git a/skills/multi/dingtalk-chat/references/intent-guide.md b/skills/multi/dingtalk-chat/references/intent-guide.md index 10278125..7fa79ebf 100644 --- a/skills/multi/dingtalk-chat/references/intent-guide.md +++ b/skills/multi/dingtalk-chat/references/intent-guide.md @@ -36,17 +36,20 @@ | "置顶会话/取消置顶" | 设置/取消会话置顶 | `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 get-mute-config` | `chat group-mute-member` | get-mute-config 是只读查询,返回黑白名单及时间配置;group-mute-member 会修改禁言状态 | | "设管理员/取消管理员" | 设置群管理员 | `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-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 group user-settings set` | `chat group update-settings` | user-settings set 是个人会话置顶;update-settings 是管理员级别的群功能开关 | +| "我的群免打扰/关闭我的群免打扰/这个群别提醒我" | 设置当前用户群免打扰 | `chat group user-settings set` | `chat group update-settings` | user-settings set 是个人通知偏好;update-settings 是管理员级别的群功能开关 | | "隐藏会话/隐藏群聊/隐藏对话" | 隐藏会话 | `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 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 分页查询全部成员列表 | diff --git a/skills/multi/dingtalk-chat/references/lite-recipes.md b/skills/multi/dingtalk-chat/references/lite-recipes.md index 082f4b56..09f1c2ae 100644 --- a/skills/multi/dingtalk-chat/references/lite-recipes.md +++ b/skills/multi/dingtalk-chat/references/lite-recipes.md @@ -4,4 +4,4 @@ ## #1 消息沟通 -所有消息沟通相关的命令详情、参数说明、意图路由和复合工作流,请查阅 [chat.md](./chat-commands.md)。 +所有消息沟通相关的命令详情、参数说明、意图路由和复合工作流,请查阅 [chat.md](./chat.md)。 diff --git a/skills/multi/dingtalk-chat/scripts/chat_export_messages.py b/skills/multi/dingtalk-chat/scripts/chat_export_messages.py index 782432fb..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]: @@ -130,7 +139,7 @@ def main(): '--format', 'json', ] if args.no_forward: - cmd_args.extend(['--direction', 'older']) + cmd_args.append('--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)]) @@ -150,20 +159,25 @@ def main(): if isinstance(data, list): page_msgs = data has_more = False + next_cursor = None elif isinstance(data, dict): inner = data.get('result', data) if isinstance(inner, dict): page_msgs = inner.get('messages', []) has_more = inner.get('hasMore', False) + next_cursor = inner.get('nextCursor') elif isinstance(inner, list): page_msgs = inner has_more = False + next_cursor = None else: page_msgs = [] has_more = False + next_cursor = None else: page_msgs = [] has_more = False + next_cursor = None if not page_msgs: break @@ -175,16 +189,19 @@ def main(): if not has_more: break - # message list 没有 cursor flag;按 CLI 契约用边界 createTime 翻页。 - 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: @@ -197,8 +214,9 @@ def main(): print(f" ✓ 已导出 {len(all_messages)} 条消息到 {args.output}") else: for m in all_messages: - sender = (m.get('sender') or m.get('senderNick') - or m.get('senderOpenDingTalkId') or '未知') + 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') 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 0b36c7c3..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.extend(['--direction', 'older']) + 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)]) @@ -142,20 +151,25 @@ def main(): if isinstance(data, list): page_msgs = data has_more = False + next_cursor = None elif isinstance(data, dict): inner = data.get('result', data) if isinstance(inner, dict): page_msgs = inner.get('messages', []) has_more = inner.get('hasMore', False) + next_cursor = inner.get('nextCursor') elif isinstance(inner, list): page_msgs = inner has_more = False + next_cursor = None else: page_msgs = [] has_more = False + next_cursor = None else: page_msgs = [] has_more = False + next_cursor = None if not page_msgs: break @@ -167,16 +181,19 @@ def main(): if not has_more: break - # message list 没有 cursor flag;按 CLI 契约用边界 createTime 翻页。 - 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: @@ -190,8 +207,9 @@ def main(): print(f" ✓ 已导出 {len(all_messages)} 条消息到 {args.output}") else: for m in all_messages: - sender = (m.get('sender') or m.get('senderNick') - or m.get('senderOpenDingTalkId') or '未知') + 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') diff --git a/skills/multi/dingtalk-contact/SKILL.md b/skills/multi/dingtalk-contact/SKILL.md index 53d30c7d..c9c549c5 100644 --- a/skills/multi/dingtalk-contact/SKILL.md +++ b/skills/multi/dingtalk-contact/SKILL.md @@ -1,10 +1,9 @@ --- name: dingtalk-contact -description: 钉钉通讯录精确查询与组织管理(用户、部门、角色、花名册、离职员工、创建企业、企业账号、邀请员工)。Use when 用户说 查部门/部门成员/我的信息/按 userId 查/角色成员/离职员工/花名册/创建企业/企业账号/邀请员工。模糊找人(找同事/查上下级/谁负责)先走 dingtalk-aisearch,拿到 userId 再用本 skill 查详情。命令前缀:dws contact。 -cli_version: ">=0.2.14" +description: 钉钉通讯录精确查询。Use when 已有 userId 后查详情、部门、职位或邮箱,按完整手机号反查用户,或查询自己、部门成员及角色。姓名模糊搜索、工号、职责、上下级走 dingtalk-aisearch,拿到 userId 后用本 skill 补详情。命令前缀:dws contact。 metadata: + cli_version: ">=0.2.14" category: product - stability: experimental requires: bins: - dws @@ -12,20 +11,16 @@ metadata: # 钉钉通讯录 Skill -> 🧪 **EXPERIMENTAL · 试验版 / Preview** — multi 模式当前未达 stable 标准。全部 dingtalk-* skill 已通过 dispatch verifier,但接口、命名、跨 skill 引用后续可能调整;生产 / 共享环境请优先使用 mono 模式(`dws skill setup --mode mono`)。问题请提 issue 反馈。 - ## 前置条件 — 执行操作前必读 -> **`use_skill(dws-shared)`** — 认证、全局参数(`--format json` / `--yes`)、错误码、URL 模板、跨产品消歧、安全规则与 capability 边界。**执行任何 `dws` 命令前先读;** 单产品的清晰命令可直接用本 skill。 - - +> **CRITICAL — 执行任何 `dws` 操作前,MUST 先用 Read 工具完整读取 [`dws-shared`](../dws-shared/SKILL.md)。**该轻量文件包含全局执行契约、安全底线及 shared references 的按需加载导航;不要预加载其全部 references。 > 命令参考:[contact.md](references/contact.md);剧本:[08-directory.md](references/08-directory.md)。 ## Shortcuts(无专用脚本/recipe 时优先) -以下 shortcut 来自独立于 Runtime Schema 的公开 catalog。先按本 skill 的意图表、脚本和 recipe 路由:存在精确覆盖该场景的专用脚本/recipe 时按其执行;否则用户意图命中时,shortcut 优先于手写原子命令。用 `dws shortcut list --service contact --format json` 读取参数、约束、风险和示例,并以 `dws contact --help` 核对当前 Cobra flags;不要对 `+` 路径调用 `dws schema`。 +以下命令来自独立于 Runtime Schema 的公开 catalog。先运行 `dws shortcut list --service contact --format json` 读取完整契约,再用 `dws contact --help` 核对 flags;不要对 `+` 路径调用 `dws schema`。 | Shortcut | 风险 | 适用场景 | |---|---|---| @@ -51,31 +46,36 @@ metadata: |--------|------| | "查我自己的信息" | `dws contact user get-self` | | "按 userId 查详情" | `dws contact user get --ids ,,...`(多个并行) | +| "完整手机号反查用户" | `dws contact user search-mobile --mobile <手机号>` | | "按部门名拉成员" | `python scripts/contact_dept_members.py --query "<部门名>"` | | "搜部门" | `dws contact dept search --query "<关键词>"` | -| "部门成员列表" | `dws contact dept list-members --depts ` | +| "部门成员列表" | `dws contact dept list-members --ids ` | | "列出企业角色 / 有哪些角色" | `dws contact label list` | | "按角色名查角色ID" | `dws contact label get --names "<角色名>"` | | "查某角色下有哪些成员" | `dws contact label list-members --id ` | -| "离职员工/离职名单/已离职" | `dws contact user dismission search`(可加 `--name` / `--start` + `--end` / `--depts`) | -| "花名册/员工档案/学历/银行卡/合同" | `dws contact user profile get --staff-id `(先 `profile fields` 查字段) | -| "创建企业/新建企业/初始化企业" | `dws contact org create --org-name "<企业名>" --creator-username "<创建者名称>"` | -| "创建企业账号/专属账号/企业登录账号" | `dws contact account create --org-user-name "<姓名>" --login-id "<登录号>"` | -| "邀请员工/添加员工/新员工入职" | `dws contact user invite --org-user-name "<姓名>" --org-user-mobile "<手机号>"` | ## 标准 SOP(必遵流程) -> 命中以下意图**必须**按对应 SOP 顺序执行;**禁止**跳步、替换命令、编造 userId。每条命令必须带 `--format json`。模糊找人**首选** `dingtalk-aisearch`;contact 负责 userId→详情补全与精确查询。 +> 命中以下意图**必须**按对应 SOP 顺序执行;**禁止**跳步、替换命令、编造 userId。每条命令必须带 `--format json`。姓名模糊搜索、工号、职责与上下级走 `dingtalk-aisearch`;完整手机号精确反查走 contact;拿到 userId 后由 contact 补详情。 ### SOP-1 搜人(search-person) -**触发**:找人/搜同事/谁负责/上级/下级/团队成员/按部门找人。 +**触发**:按姓名/工号/部门/职责/上下级找人,或用手机号线索做语义搜索。 -1. **切 aisearch(必须)**:模糊找人优先走 `dingtalk-aisearch`:`dws aisearch person --keyword "<关键词>" --dimension <维度> --format json`(姓名→`name`、负责人→`duty`、部门→`department`、上下级→`supervisor`/`subordinate`)。 +1. **切 aisearch(必须)**:`dws aisearch person --keyword "<关键词>" --dimension <维度> --format json`(姓名→`name`、工号→`jobNumber`、手机号语义线索→`phone`、负责人→`duty`、部门→`department`、上下级→`supervisor`/`subordinate`)。 2. **解析(必须)**:从结果取 `userId`、`title`;**多人同名禁止默认选第一个**,必须批量 `dws contact user get --ids --format json` 拿部门/职位后让用户确认。 3. **补详情(必须)**:要完整部门/职位/邮箱/主管时 `dws contact user get --ids --format json`。 -**禁止**:在 contact 里做模糊搜人(应切 aisearch)、默认取首个候选、编造人员字段。 +**禁止**:用 `contact user search` 做姓名或工号搜索、默认取首个候选、编造人员字段。完整手机号精确反查是 `search-mobile` 的唯一搜索例外。 + +### SOP-1A 完整手机号精确反查(search-person-by-mobile) + +**触发**:用户提供完整手机号并要求确认是谁或取得 userId。 + +1. **执行(必须)**:`dws contact user search-mobile --mobile "<完整手机号>" --format json`。 +2. **补详情(按需)**:从结果取 `userId`,需要部门、职位或邮箱时继续 `dws contact user get --ids --format json`。 + +**禁止**:把完整手机号精确反查改走姓名搜索,或在未返回 userId 时猜测人员。 ### SOP-2 精确查人/补详情(search-user) @@ -98,7 +98,7 @@ metadata: **触发**:部门列表/部门成员/角色/角色成员。 -1. **执行(必须)**:搜部门 `dws contact dept search --query "<部门名>" --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 `。 +1. **执行(必须)**:搜部门 `dws contact dept search --query "<部门名>" --format json`;某部门下子部门 `dws contact dept list-children --dept <父部门ID> --format json`;部门成员 `dws contact dept list-members --ids <部门ID>[,<部门ID2>...] --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 `。搜索企业根部门时服务端可能返回 `deptId=-1` 哨兵,后续 `list-children` / `list-members` / `get-info` 必须规范化为真实根部门 `deptId=1`。 2. **补详情(必须)**:拿到 userId 后用 `contact user get --ids` 补部门/职位;上下级关系优先经 `dingtalk-aisearch` 的 `supervisor`/`subordinate` 维度。 **禁止**:使用不存在的 `contact dept list`(已废弃/歧义)、编造 deptId/labelId、跳过 aisearch 维度直接猜上下级。 @@ -107,17 +107,17 @@ metadata: - 通讯录问题必须调用 `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`。 +- 姓名模糊搜索、工号反查、职责或上下级搜索走 `dws aisearch person`;完整手机号精确反查走 `dws contact user search-mobile --mobile "<手机号>" --format json`。拿到 `userId` 后按需 `dws contact user get --ids --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` 筛字段替代。 -- "创建企业账号"必须优先匹配长模式 `account create`,不要误路由为 `org create`;三条组织管理命令都是写操作,执行前确认当前企业与目标信息。 ## 跨产品协作 -- 模糊找人(姓名 / 上下级 / 谁负责 / 工号 / 手机号)→ 切到 `dingtalk-aisearch` +- 姓名模糊搜索、上下级、谁负责、工号反查、手机号语义搜索 → `dingtalk-aisearch` +- 完整手机号精确反查 → `dws contact user search-mobile` - 拿到 email 发邮件 → 切到 `dingtalk-mail` - 拿到 userId 发消息 → 切到 `dingtalk-chat` -## 局部意图与 Recipe +## 局部意图与短流程 -- [局部意图消歧](references/intent-guide.md);[Lite Recipe](references/lite-recipes.md)。 +- [局部意图消歧](references/intent-guide.md);[短流程](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 a55e9641..2ba71c68 100644 --- a/skills/multi/dingtalk-contact/references/08-directory.md +++ b/skills/multi/dingtalk-contact/references/08-directory.md @@ -6,28 +6,24 @@ ## 专用规则(#8 非 lite 步骤必守) - **角色类查人优先 label**:用户说"角色为XX的员工/XX角色的员工/XX角色的人员""所有主管/主管理员/财务/HR/总经理"等角色类型人员时,**优先** `contact label list` 获取全部角色 → 匹配目标角色 → `contact label list-members --id `;若用户明确指定了角色名称(如"角色为总经理"),则先用 `contact label get --names ` 精确匹配,**若精确匹配无结果,降级 `label list` 模糊匹配**(如用户说"管理员"可匹配到"主管理员"和"子管理员")。 -- **脚本优先**:按部门拉成员**优先** `python scripts/contact_dept_members.py --query "<部门名>"`(`--dry-run` / `--format json`);失败再 `dept search` → `dept list-members --depts`。 -- **详情链路**:用户要子部门、职位、联系方式、汇报关系等,在 `user search` 之后**必须**再 `contact user get --ids `;禁止仅用 search 的浅表字段交差。 +- **脚本优先**:按部门拉成员**优先** `python scripts/contact_dept_members.py --query "<部门名>"`(`--dry-run` / `--format json`);失败再 `dept search` → `dept list-members --ids`。 +- **详情链路**:用户要子部门、职位、联系方式、汇报关系等,在 `aisearch person` 找到 `userId` 后**必须**再 `contact user get --ids `;禁止仅用搜索结果的浅表字段交差。 - **`user get` 后部门仍空**:不得过早结束或只建议用户去 App;须在 CLI 能力内尝试 **用户点名的部门** `dept search` + `dept list-members` 等与 `userId` 交叉核对,再结构化汇总「返回中有哪些字段 / 哪些为空及可能原因」。 -- **多命中**:`user search` 或 `dept search` 多条时须列候选(姓名、title、部门线索)请用户确认,禁止默认猜一人。具体消歧流程: +- **多命中**:`aisearch person` 或 `dept search` 返回多条时须列候选(姓名、title、部门线索)请用户确认,禁止默认猜一人。具体消歧流程: 1. 从搜索结果中提取所有同名/多命中用户的 `userId` 2. 调用 `contact user get --ids userId1,userId2,...` 获取每人详情(含 `depts` 部门列表、职位等) 3. 将「姓名 + 部门 + 职位」列表展示给用户,请用户确认选择哪一位 4. 使用用户确认的 `userId` 继续后续操作 - > **根因**:`user search` 和 `aisearch person` 均不返回部门信息,无法仅凭搜索结果区分同名用户。**必须**追加 `contact user get` 获取部门信息才能消歧。 + > **根因**:`aisearch person` 不返回完整部门信息,无法仅凭搜索结果区分同名用户。**必须**追加 `contact user get` 获取部门信息才能消歧。 - **批量**:多个 `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`。 ## 与其他场景消歧 -> **CRITICAL — "创建企业" vs "创建企业账号"必须优先匹配长模式**:先检查 query 是否含"账号"关键词;含则必须路由 `account create`,**禁止**路由 `org create`。 - -- **创建企业账号 / 新建企业账号 / 开通企业账号 / 专属账号**(含"账号")→ `contact account create --org-user-name "<姓名>" --login-id "<登录号>" --format json`(手机号可选) -- **创建企业 / 新建企业 / 开通企业 / 初始化企业**(不含"账号")→ `contact org create --org-name "<企业名>" --creator-username "<创建者名称>" --format json` -- **邀请员工 / 添加员工 / 新员工入职 / 拉人进企业** → `contact user invite --org-user-name "<姓名>" --org-user-mobile "<手机号>" --depts '[{"deptId":...}]' --format json` - **按角色/职位类型查人(主管/管理员/财务等)** → 优先 `contact label list` + `label list-members`;label 精确命中角色维度,返回完整名单;aisearch 是语义模糊搜索不保证完整性。 -- **搜人/找人/找同事/查工号/查手机号** → 首选 **`aisearch person`**(AI 语义搜索,支持姓名/部门/职责/上下级/手机号/工号维度),见 `dingtalk-aisearch`。 -- **需要 userId 做后续操作 / 按手机号查 / 按 userId 查详情** → `contact`(精确查询)。 +- **搜人/找人/找同事/查工号/手机号语义搜索** → **`aisearch person`**(支持姓名/部门/职责/上下级/手机号线索/工号维度),见 `dingtalk-aisearch`。 +- **完整手机号精确反查** → `contact user search-mobile --mobile "<手机号>"`。 +- **已有 userId 后查详情 / 查自己 / 查部门与角色** → `contact`(精确查询)。 - **纯查部门与子部门成员 / 验证归属 / 组织关系** → `contact`。 - **终点是发消息、待办、日程** → 先用 `search-person` 或 `search-user` 取 `userId`,再进入 #1 / #2 / #3。 - **联系客户 + 发邮件** → 先用 `contact` 取 `orgAuthEmail`,再走 `dingtalk-mail`。 @@ -37,19 +33,19 @@ | Recipe | 步骤 | |--------|------| | `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 ` | +| `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 --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` 核对部门字段 | +| `list-dept-members` | 1. **优先** `python scripts/contact_dept_members.py --query "<部门名>"`
2. 备选:`lookup-dept-id` → `contact dept list-members --ids `
3. 若要每人档案字段:对 `userId` 批量 `contact user get --ids …` | +| `list-multi-dept-members` | 1. 对每个部门名 `contact dept search --query "<名>"` → 各 `deptId`
2. `contact dept list-members --ids ,,...`(多部门并行/批量见 conventions)
3. 需要档案再 `contact user get --ids …` | +| `verify-user-dept` | 1. `contact dept search --query "<部门名>"` → `deptId`
2. `contact dept list-members --ids ` 中匹配姓名;或先 `search-user` lite 再 `user get` 核对部门字段 | ## Full / 多步组合 | Recipe | 行动指南(固定路线) | |--------|---------------------| -| 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 条/批,超出分批 + 用户确认) | +| 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 --ids ,,...`(多部门按 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 --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. 结果过多时汇总或请用户收窄 | +| 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 --ids ` → 按需 `user get` | +| user-detail-organization | 1. `aisearch person --keyword "<关键词>" --dimension <维度>` → `userId`(多结果先消歧)
2. **必须** `contact user get --ids `
3. 若用户同时给出部门语境:叠加 `verify-user-dept` | +| batch-users-by-keyword | 1. `aisearch person --keyword "<职位或技能关键词>" --dimension position,duty` → 多条
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 index 75491aab..803212b8 100644 --- a/skills/multi/dingtalk-contact/references/_common/conventions.md +++ b/skills/multi/dingtalk-contact/references/_common/conventions.md @@ -9,7 +9,7 @@ | 1 | **并行查详情**:拿到多个 ID 后,用 `&` 合并到同一条 Shell 命令并行执行 + `wait`,**严禁逐条串行** | | 2 | **翻页**:分页接口须拉全直至无更多 | | 3 | **优先批量 API**:有批量接口则用批量;无则按 #1 并行 | -| 4 | **群消息**:必须先 `chat search --query` 得 `openConversationId`,再 `chat message list --group`;多群同条命令并行 | +| 4 | **群消息**:必须先 `chat search --query` 得 `openConversationId`,再 `chat message list --group --time "" --direction older`;多群同条命令并行 | | 5 | **列表少轮次**:带条件搜索/列表 → 一次采全详情;**禁止**无新参数时重复同一 `list` / `search` | ## 多源并行采集(公共模式) @@ -29,7 +29,7 @@ |------|------|--------| | `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 | +| `deptId` | `contact dept search` | `contact dept list-members --ids `;多子部门时对每个子部门分别 `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` | @@ -38,7 +38,7 @@ | `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` | +| `dentryUuid` | `drive list` / `drive mkdir` | `drive info/download/copy/move/rename/delete --node`、`drive list/mkdir/upload/copy/move --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,不能把该数字直接代入后续命令。 +**ID 边界硬约束**:`dentryId` 通常是纯数字,只表示聊天文件消息需要的钉盘条目数字 ID;它不是父目录 ID。遇到 `drive --node/--folder`、`doc --node`、`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 deleted file mode 100644 index b7220362..00000000 --- a/skills/multi/dingtalk-contact/references/contact-index.md +++ /dev/null @@ -1,6 +0,0 @@ -# 通讯录能力索引 - -| 主题 | 详见 | -|---|---| -| 用户/部门/组织关系 | [`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 9b2f8632..7bf26e5f 100644 --- a/skills/multi/dingtalk-contact/references/contact.md +++ b/skills/multi/dingtalk-contact/references/contact.md @@ -9,7 +9,7 @@ > - 用户说"创建企业 / 新建企业 / 开通企业 / 初始化企业"(**不含"账号"二字**)→ **`org create`**(创建企业组织本身) > - **判断口径**:先检查 query 是否含"账号"关键词;含则必须路由 `account create`,**禁止**路由 `org create`。 > -> **CRITICAL — 根部门**:钉钉根部门 `deptId=1`。`dept` 系列命令查根部门统一传 `--dept 1` 或 `--depts 1`,不要传 `self / me / root / 0`。 +> **CRITICAL — 根部门**:钉钉根部门 `deptId=1`。单部门命令查根部门通常传 `--dept 1`;`dept list-members` 传 `--ids 1`。`dept search` 精确命中企业根部门时可能返回 `deptId=-1` 哨兵,后续部门命令必须规范化为 `1`。不要传 `self / me / root / 0`。 ## 命令总览 @@ -80,14 +80,58 @@ Usage: Example: dws contact user invite --org-user-name "张三" --org-user-mobile "13800138000" --depts '[{"deptId":1}]' Flags: - --org-user-name string 员工在企业内的名称 (必填) - --org-user-mobile string 员工手机号 (必填) - --depts string 员工所属部门列表 JSON 数组(可选),格式: [{"deptId":1}] + --org-user-name string 员工在企业内的名称 (必填) + --org-user-mobile string 员工手机号 (必填) + --depts string 员工所属部门列表 JSON 数组(可选),格式: [{"deptId":1}] Notes: - 通过手机号邀请单个员工加入当前企业 - 认证信息(corpId、optUserId)由系统自动注入,无需手动传入 ``` +#### 修改员工信息 +``` +Usage: + dws contact user update [flags] +Aliases: + update, modify, edit +Example: + dws contact user update --user-id user001 --org-user-name "张三三" + dws contact user update --user-id user001 --depts '[{"deptId":1}]' + dws contact user update --user-id user001 --master-user-id manager001 --yes +Flags: + --user-id string 要修改的员工 userId (必填) + --org-user-name string 员工在企业内的名称(可选) + --depts string 员工所属部门列表 JSON 数组(可选),格式: [{"deptId":1}] + --master-user-id string 直属主管 userId(可选) + --yes 跳过二次确认(可选) +Notes: + - 至少需要一个修改项(--org-user-name、--depts 或 --master-user-id) + - 未加 --yes 时会交互式提示确认;脚本/自动化场景请显式带上 --yes + - 认证信息(corpId、optUserId)由系统自动注入,无需手动传入 +``` + +#### 更新自己的 profile 信息 +``` +Usage: + dws contact user update-self [flags] +Aliases: + update-self, update-me, update-self-profile, edit-self, modify-self +Example: + dws contact user update-self --nick "新昵称" + dws contact user update-self --avatar-file-id "xxxxxx" --yes + dws contact user update-self --nick "新昵称" --avatar-file-id "xxxxxx" --yes +Flags: + --nick string 新昵称(可选) + --avatar-file-id string 新头像在钉盘的 fileId(可选) + --yes 跳过二次确认(可选) +Notes: + - 更新当前登录用户自己的个人 profile 信息(昵称 / 头像),不是修改员工组织信息 + - 至少需要一个修改项(--nick 或 --avatar-file-id) + - 头像 fileId 需要先上传头像到钉盘获取 + - 未加 --yes 时会交互式提示确认;脚本/自动化场景请显式带上 --yes + - 认证信息(corpId、optUserId)由系统自动注入,无需手动传入 +``` + ### profile (用户档案 / 花名册) #### 查询花名册有权限的字段列表 @@ -144,7 +188,7 @@ Flags: 查询离职员工列表,支持按员工姓名、离职日期范围、部门进行过滤。认证信息(corpId、optUserId)由系统自动注入,无需手动传入。 `--start` 和 `--end` 必须同时设置或同时不设置,不允许只传其中一个。 -### dept (部门查询) +### dept (部门查询与管理) #### 搜索部门 ``` @@ -194,17 +238,55 @@ Notes: Usage: dws contact dept list-members [flags] Example: - dws contact dept list-members --depts 12345,67890 - dws contact dept list-members --depts 1 # 根部门 + dws contact dept list-members --ids 12345,67890 + dws contact dept list-members --ids 1 # 根部门 Flags: - --depts string 部门 ID 列表,逗号分隔 (必填) + --ids string 部门 ID 列表,逗号分隔 (必填) Notes: - - **钉钉根部门 `deptId=1`**;查根部门直属成员用 `--depts 1` + - **钉钉根部门 `deptId=1`**;查根部门直属成员用 `--ids 1` - 仅返回**本部门**直接成员,**不含下级部门**成员;需含下级请先 `dept list-children` 枚举子部门,再对子 deptId 分别/合并调用 `list-members` - - 受组织架构可见性控制;`--depts` 支持逗号分隔批量查询多个部门 + - 受组织架构可见性控制;`--ids` 支持逗号分隔批量查询多个部门 - 跨层级成员展开见 [08-directory.md](08-directory.md) 的 `cross-level-dept-members` recipe ``` +#### 创建部门 +``` +Usage: + dws contact dept create [flags] +Example: + dws contact dept create --name "新产品部" --create-dept-group true + dws contact dept create --name "研发一组" --parent 12345 --create-dept-group false --yes +Flags: + --name string 部门名称 (必填) + --parent string 父部门 ID(可选),不传默认根部门 + --create-dept-group bool 是否创建部门群(必填) + --yes 跳过二次确认(可选) +Notes: + - 父部门不传时默认钉钉根部门 deptId=1 + - --create-dept-group 必须显式指定 true 或 false + - 未加 --yes 时会交互式提示确认;脚本/自动化场景请显式带上 --yes + - 认证信息(corpId、optUserId)由系统自动注入,无需手动传入 +``` + +#### 更新部门 +``` +Usage: + dws contact dept update [flags] +Example: + dws contact dept update --dept 12345 --name "新部门名" + dws contact dept update --dept 12345 --name "新名称" --parent 67890 --yes +Flags: + --dept string 部门 ID (必填) + --name string 新部门名称(必填) + --parent string 新父部门 ID(可选) + --yes 跳过二次确认(可选) +Notes: + - --dept 为要更新的部门 ID,可通过 dept search 或 dept list-children 获取 + - --name 必填;--parent 可选,未指定时只更新部门名称 + - 未加 --yes 时会交互式提示确认;脚本/自动化场景请显式带上 --yes + - 认证信息(corpId、optUserId)由系统自动注入,无需手动传入 +``` + ### label (角色查询) > **角色ID = labelId**:用户提到"角色ID"时,均指通讯录 label 系统中的角色ID,**不是部门ID也不是userId**。查角色成员用 `label list-members --id <角色ID>`,不要传给 `dept get-info` 或 `user get`。 @@ -288,23 +370,55 @@ Notes: - 认证信息(corpId、optUserId)由系统自动注入,无需手动传入 ``` +#### 更新企业账号用户信息 +``` +Usage: + dws contact account update [flags] +Aliases: + update, modify, edit +Example: + dws contact account update --user-id user001 --org-user-name "张三" + dws contact account update --user-id user001 --depts '[{"deptId":1}]' + dws contact account update --user-id user001 --nick "新昵称" --avatar-file-id "xxxxxx" --yes +Flags: + --user-id string 被修改企业账号的 userId (必填) + --org-user-name string 企业账号在企业内的员工姓名(可选) + --depts string 部门列表 JSON 数组(可选),格式: [{"deptId":1}] + --master-user-id string 直属主管 userId(可选) + --nick string 企业账号自身昵称,用于 profile 展示(可选) + --avatar-file-id string 企业账号头像在钉盘的 fileId(可选) + --yes 跳过二次确认(可选) +Notes: + - 更新指定企业账号用户的信息,不是修改普通员工信息 + - 至少需要一个修改项(--org-user-name / --depts / --master-user-id / --nick / --avatar-file-id) + - 头像 fileId 需要先上传头像到钉盘获取 + - 未加 --yes 时会交互式提示确认;脚本/自动化场景请显式带上 --yes + - 认证信息(corpId、optUserId)由系统自动注入,无需手动传入 +``` + ## 意图判断 -> **搜人首选 `aisearch person`**:凡是“找人/搜人/找同事/谁负责/上级/下级”均优先用 [aisearch person](../../dingtalk-aisearch/references/aisearch.md),以下场景才用 contact。 +> **按搜索性质分流**:姓名模糊搜索、工号、部门、职责和上下级使用 [aisearch person](../../dingtalk-aisearch/references/aisearch.md);完整手机号精确反查使用 `user search-mobile`;拿到 userId 后,以下详情、部门和角色场景再用 contact。 用户说"我是谁/我的信息/我的 userId/当前用户/本人/self/me/whoami" → `user get-self`(无需参数;禁止用 `user get --ids me/self` 代替) -用户需要 userId 给其他产品使用(发消息/建待办/约日程)→ `user search`(按名字)或 `user search-mobile`(按手机号) +用户需要 userId 给其他产品使用(发消息/建待办/约日程)→ `aisearch person` 按对应维度搜索 +用户提供完整手机号并要求反查用户 → `user search-mobile --mobile "<完整手机号>"` 用户说"查用户详情/部门/主管/管理员" → `user get`(需 userId,返回组织管理信息) -用户说"邀请员工/添加员工/加人/新员工入职/拉人进企业" → `user invite`(需手机号 + 企业内名称,部门可选) +用户说"修改员工/更新员工/改员工姓名/改员工部门/换部门/改直属主管/换主管/调整员工信息" → `user update`(需 userId;姓名 / 部门 / 主管至少改一项) +用户说"改昵称/改头像/更新我的资料/更新我的profile/更新我的个人信息/修改我的昵称" → `user update-self`(昵称 / 头像至少改一项;头像 fileId 需先上传钉盘) +用户说"邀请员工/添加员工/批量邀请/加人/新员工入职/拉人进企业" → `user invite`(需手机号 + 企业内名称 + 部门) 用户说"花名册字段/有哪些字段/字段列表" → `user profile fields` 用户说"花名册/员工档案/学历/家庭/银行卡/紧急联系人/合同" → `user profile get`(需 staffId,返回个人档案信息) 用户说"离职员工/离职名单/离职人员/已离职" → `user dismission search` 用户说"创建企业账号/新建企业账号/开通企业账号/专属账号/企业登录账号"(含"账号")→ `account create`(需员工名称 + 登录号;手机号可选) +用户说"更新企业账号/修改企业账号/改企业账号信息/改企业账号姓名/改企业账号部门/改企业账号主管/改企业账号昵称/改企业账号头像"(含"账号"且含"改/更新/修改")→ `account update`(需 userId;至少改一项) 用户说"创建企业/新建企业/开通企业/初始化企业"(不含"账号")→ `org create`(需企业名称 + 创建者名称) 用户说"找部门/哪个部门" → `dept search` 用户说"部门详情/部门信息/部门多少人" → `dept get-info`(返回部门ID、部门名称、部门人数;需 deptId,若只有部门名称需先 `dept search`) 用户说"子部门/下设部门/部门有哪些下级部门/枚举二级部门" → `dept list-children`(需父 deptId;只有部门名先 `dept search`) 用户说"部门有谁/部门成员/人员名单" → `dept list-members`(需 deptId;**仅本部门不含下级**,含下级先 `dept list-children` 再合并查) +用户说"创建部门/新建部门/添加部门/成立部门/建部门" → `dept create`(需部门名;父部门可选,默认根部门) +用户说"更新部门/修改部门/改部门名/改部门名称/换部门名/改父部门/换上级部门" → `dept update`(需 deptId + name;parent 可选) 用户查询涵盖"角色"(主管/管理员/财务/HR/总经理等任意角色名)→ 统一走 `contact label` 链路,按下方决策树选命令: - 不知道角色名 / 枚举所有角色 → `label list` - 已知角色名,查ID或成员 → 先 `label get --names <名>` 拿ID,查成员再调 `label list-members --id `;**精确匹配无结果时降级 `label list` 模糊匹配** @@ -336,7 +450,7 @@ Notes: > [!IMPORTANT] > **易混淆硬规则**:`relation list-my-followings` **只**返回"我特别关注的人员列表"(一组 openDingTalkId),**不**返回任何消息内容。 > -> **禁止路由到本命令的场景**(query 中同时包含『关注/特别关注/星标』和以下任一消息域动词/名词时,必须路由到 [`chat message list-focused`](../../dingtalk-chat/references/chat-commands.md)): +> **禁止路由到本命令的场景**(query 中同时包含『关注/特别关注/星标』和以下任一消息域动词/名词时,必须路由到 [`chat message list-focused`](../../dingtalk-chat/references/chat.md)): > - 动词类:**发**了什么、**说**了什么/啥、**聊**了什么、**讲**了什么 > - 名词类:**消息**、**聊天**、**动态**、**最新内容** > @@ -355,8 +469,8 @@ Notes: # 1. 查看自己的信息 — 提取 userId dws contact user get-self --format json -# 2. 按名字搜索同事或好友 — 可提取 同事的userId,或好友的openDingTalkId -dws contact user search --query "张三" --format json +# 2. 按名字搜索人员 — 统一从 AI 搜问取得 userId/openDingTalkId +dws aisearch person --keyword "张三" --dimension name --format json # 3. 查看部门结构 — 提取 deptId dws contact dept search --query "技术部" --format json @@ -368,7 +482,7 @@ dws contact dept get-info --dept --format json dws contact dept list-children --dept <父deptId> --format json # 6. 查看部门成员 -dws contact dept list-members --depts --format json +dws contact dept list-members --ids --format json # 7. 获取企业所有角色列表 — 不知道角色名时先浏览 dws contact label list --format json @@ -400,10 +514,25 @@ dws contact user dismission search --depts 123456,789012 --hide-retirement=false dws contact org create --org-name "我的企业" --creator-username "张三" --format json # 16. 创建企业专属账号 -dws contact account create --org-user-name "张三" --login-id "zhangsan001" --format json +dws contact account create --org-user-name "张三" --login-id "zhangsan001" --org-user-mobile "13800138000" --email "zhangsan@example.com" --dept-ids "1,2,3" --send-pwd-via-sms --format json -# 17. 邀请员工加入企业 +# 17. 更新企业账号用户信息 +dws contact account update --user-id user001 --org-user-name "张三三" --depts '[{"deptId":1}]' --master-user-id manager001 --nick "新昵称" --avatar-file-id "xxxxxx" --yes --format json + +# 18. 邀请员工加入企业 dws contact user invite --org-user-name "张三" --org-user-mobile "13800138000" --depts '[{"deptId":1}]' --format json + +# 19. 修改员工信息 +dws contact user update --user-id user001 --org-user-name "张三三" --depts '[{"deptId":1}]' --master-user-id manager001 --yes --format json + +# 20. 更新自己的 profile 信息 +dws contact user update-self --nick "新昵称" --avatar-file-id "xxxxxx" --yes --format json + +# 21. 创建部门 +dws contact dept create --name "新产品部" --parent 12345 --create-dept-group true --yes --format json + +# 22. 更新部门 +dws contact dept update --dept 12345 --name "新部门名" --parent 67890 --yes --format json ``` ## 上下文传递表 @@ -416,16 +545,16 @@ dws contact user invite --org-user-name "张三" --org-user-mobile "13800138000" | `user profile fields` | `fieldCode` | profile get 的 --fields | | `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` | dept get-info/list-children/update 的 --dept;dept list-members 的 --ids | | `dept search/list-children` | `deptId` | dismission search 的 --depts | -| `dept search/list-children` | `deptId` | user invite 的 --depts 或 account create 的 --dept-ids | +| `dept create` | `deptId` | dept get-info/list-children/update 的 --dept;dept list-members 的 --ids | ## 注意事项 - `user get-self` 是获取 userId 的最快方式,其他产品的 --users/--executor 都需要 userId -- `user get --ids` 和 `dept list-members --depts` 都支持批量查询,逗号分隔 +- `user get --ids` 和 `dept list-members --ids` 都支持批量查询,逗号分隔 - `user get` 返回组织管理信息(部门、主管、管理员权限),`user profile get` 返回个人档案信息(学历、家庭、银行卡等),注意区分 -- `user profile get` 的 `--staff-id` 可通过 `user get-self`、`user search` 或 `aisearch person` 获取 +- `user profile get` 的 `--staff-id` 可通过 `user get-self` 或 `aisearch person` 获取 - `user profile get` 的 `--fields` 可通过 `user profile fields` 获取可用字段 code 列表;不填则查询所有可见字段 - 建议先执行 `user profile fields` 获取可用字段列表,再根据需要的字段 code 执行 `user profile get` - `user dismission search` 的 `--start`/`--end` 必须同时设置或同时不设置,不允许只传其中一个 @@ -435,7 +564,8 @@ dws contact user invite --org-user-name "张三" --org-user-mobile "13800138000" - `label get` 是精确匹配角色名称,不支持模糊搜索;支持逗号分隔同时查询多个角色名称 - **`label get` 精确匹配无结果时的降级策略**:若 `label get --names "XX"` 返回空结果,必须降级调用 `label list` 获取全部角色列表,从中模糊匹配包含XX关键词的角色(如用户说"管理员"可匹配到"主管理员"和"子管理员"),再对匹配到的角色调用 `label list-members` - `label list-members` 需要先通过 `label list` 或 `label get` 获取 labelId,再用 --id 查询角色下的成员 -- `org create`、`account create`、`user invite` 都是写操作;执行前确认当前 profile 对应的企业以及名称、手机号、登录号和部门信息 +- `user update-self` 用于更新当前用户自己的昵称/头像;头像 fileId 需先上传头像到钉盘获取 +- `account update` 用于更新企业账号用户信息;`--depts` 为 JSON 数组格式,头像 fileId 需先上传钉盘获取 ## 自动化脚本 diff --git a/skills/multi/dingtalk-contact/references/intent-guide.md b/skills/multi/dingtalk-contact/references/intent-guide.md index 17836d4b..9f036d67 100644 --- a/skills/multi/dingtalk-contact/references/intent-guide.md +++ b/skills/multi/dingtalk-contact/references/intent-guide.md @@ -4,10 +4,11 @@ | 用户说... | 真实意图 | 应该用 | 不要用 | 理由 | |---|---|---|---|---| -| "张三在哪个部门/查一下同事工号" | 通讯录精确查询 | #8 `contact` | #5 汇报 / #4 文档 | 需要 userId、手机号、部门 ID 等精确信息时用 contact | +| "张三在哪个部门/张三的工号是多少" | 搜人后查通讯录详情 | `aisearch person` → `contact user get` | 直接 `contact user search` | 姓名或工号先由 aisearch 获取 userId,再由 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 | +| "找一下张三/搜同事/找人" | 人员语义搜索 | `aisearch person` | `contact user search` | 姓名模糊搜索、工号、部门、职责和上下级走 aisearch;contact 在拿到 userId 后补详情 | | "五道的上级是谁/谁负责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 | +| "13800138000是谁/完整手机号反查" | 精确手机号反查 | `contact user search-mobile` | `contact user search` | 完整手机号精确匹配使用 search-mobile | +| "按手机号线索找人" | 手机号语义搜人 | `aisearch person --dimension phone` | `contact user search` | 非精确手机号匹配走 aisearch 的 phone 维度 | diff --git a/skills/multi/dingtalk-contact/scripts/contact_dept_members.py b/skills/multi/dingtalk-contact/scripts/contact_dept_members.py index df660a6c..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]: @@ -62,7 +54,7 @@ def main(): if args.dry_run: run_dws([ 'contact', 'dept', 'list-members', - '--depts', '', '--format', 'json', + '--ids', '', '--format', 'json', ], dry_run=True) return @@ -75,10 +67,7 @@ def main(): elif isinstance(dept_data, dict): inner = dept_data.get('result', dept_data) if isinstance(inner, dict): - 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: @@ -91,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 @@ -102,7 +89,7 @@ def main(): members_data = run_dws([ 'contact', 'dept', 'list-members', - '--depts', str(dept_id), '--format', 'json', + '--ids', str(dept_id), '--format', 'json', ]) if not members_data: print(' 无法获取成员列表') @@ -113,10 +100,8 @@ def main(): elif isinstance(members_data, dict): inner = members_data.get('result', members_data) if isinstance(inner, dict): - 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: @@ -128,10 +113,9 @@ def main(): continue for m in members: - 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-devdoc/SKILL.md b/skills/multi/dingtalk-devdoc/SKILL.md deleted file mode 100644 index 47156997..00000000 --- a/skills/multi/dingtalk-devdoc/SKILL.md +++ /dev/null @@ -1,38 +0,0 @@ ---- -name: dingtalk-devdoc -description: 钉钉开放平台开发文档搜索。Use when 用户说 开放平台文档/API 文档/接口文档/调用报错/开放接口怎么调。Distinct from dingtalk-doc(钉钉云文档)。命令前缀:dws devdoc。 -cli_version: ">=0.2.14" -metadata: - category: product - stability: experimental - requires: - bins: - - dws ---- - -# 钉钉开放平台文档 Skill - -> 🧪 **EXPERIMENTAL · 试验版 / Preview** — multi 模式当前未达 stable 标准。全部 dingtalk-* skill 已通过 dispatch verifier,但接口、命名、跨 skill 引用后续可能调整;生产 / 共享环境请优先使用 mono 模式(`dws skill setup --mode mono`)。问题请提 issue 反馈。 - -## 前置条件 — 执行操作前必读 - -> **`use_skill(dws-shared)`** — 认证、全局参数(`--format json` / `--yes`)、错误码、URL 模板、跨产品消歧、安全规则与 capability 边界。**执行任何 `dws` 命令前先读;** 单产品的清晰命令可直接用本 skill。 - - - -> 命令参考:[devdoc.md](references/devdoc.md)。 - -## 意图表 - -| 用户说 | 命令 | -|--------|------| -| "查 OAuth2 接入文档" | `dws devdoc article search --query "OAuth2 接入"` | -| "API 调用报错怎么办" | `dws devdoc article search --query "<报错关键词>"` | -| "开放接口文档" | `dws devdoc article search --query "<接口名或场景>"` | - -## 跨产品协作 - -- 钉钉云文档(个人 / 企业内文档)→ 切到 `dingtalk-doc` -## 局部意图与 Recipe - -- [局部意图消歧](references/intent-guide.md)。 diff --git a/skills/multi/dingtalk-ding/SKILL.md b/skills/multi/dingtalk-ding/SKILL.md deleted file mode 100644 index e57b2f18..00000000 --- a/skills/multi/dingtalk-ding/SKILL.md +++ /dev/null @@ -1,56 +0,0 @@ ---- -name: dingtalk-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 - stability: experimental - requires: - bins: - - dws ---- - -# 钉钉 DING 紧急消息 Skill - -> 🧪 **EXPERIMENTAL · 试验版 / Preview** — multi 模式当前未达 stable 标准。全部 dingtalk-* skill 已通过 dispatch verifier,但接口、命名、跨 skill 引用后续可能调整;生产 / 共享环境请优先使用 mono 模式(`dws skill setup --mode mono`)。问题请提 issue 反馈。 - -## 前置条件 — 执行操作前必读 - -> **`use_skill(dws-shared)`** — 认证、全局参数(`--format json` / `--yes`)、错误码、URL 模板、跨产品消歧、安全规则与 capability 边界。**执行任何 `dws` 命令前先读;** 单产品的清晰命令可直接用本 skill。 - - - -> 命令参考:[ding.md](references/ding.md)。 - - -## Shortcuts(无专用脚本/recipe 时优先) - -以下 shortcut 来自独立于 Runtime Schema 的公开 catalog。先按本 skill 的意图表、脚本和 recipe 路由:存在精确覆盖该场景的专用脚本/recipe 时按其执行;否则用户意图命中时,shortcut 优先于手写原子命令。用 `dws shortcut list --service ding --format json` 读取参数、约束、风险和示例,并以 `dws ding --help` 核对当前 Cobra flags;不要对 `+` 路径调用 `dws schema`。 - -| Shortcut | 风险 | 适用场景 | -|---|---|---| -| `dws ding +list` | read | 查询 DING 消息列表 | -| `dws ding +recall-personal` | high-risk-write | 撤回本人发起的 DING | -| `dws ding +receiver-status` | read | 查询 DING 消息接收人已读状态 | -| `dws ding +send-personal` | write | 以本人身份发送 DING 给指定人 | - - -## 意图表 - -| 用户说 | 命令 | -|--------|------| -| "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 --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-doc/SKILL.md b/skills/multi/dingtalk-doc/SKILL.md index 3aad6a7d..6cac5572 100644 --- a/skills/multi/dingtalk-doc/SKILL.md +++ b/skills/multi/dingtalk-doc/SKILL.md @@ -1,10 +1,9 @@ --- name: dingtalk-doc -description: 钉钉文档内容操作(内容层)。Use when 用户说 写文档/读文档/创建文档内容/编辑文档内容/文档块/分块编辑/Markdown 写入/评论/媒体附件/导出文档/导出 PDF/导出 Markdown/版本历史/保存版本/回滚版本。搜文件/列文件/上传下载/复制移动/重命名/权限管理走 dingtalk-drive;知识库/空间/节点创建列表搜索走 dingtalk-wiki;文档里嵌入的电子表格/多维表格先用本 skill 提取 token 再切到 dingtalk-sheet/dingtalk-aitable。命令前缀:dws doc。 -cli_version: ">=0.2.14" +description: 钉钉文档(adoc):创建、读取、编辑、块、评论、附件、导出、版本及Markdown/JSONML写入。原生 .md→dingtalk-misc;文件→dingtalk-drive;知识库→dingtalk-wiki;axls→dingtalk-misc,able→dingtalk-aitable。 metadata: + cli_version: ">=0.2.14" category: product - stability: experimental requires: bins: - dws @@ -12,16 +11,11 @@ metadata: # 钉钉文档 Skill -> 🧪 **EXPERIMENTAL · 试验版 / Preview** — multi 模式当前未达 stable 标准。全部 dingtalk-* skill 已通过 dispatch verifier,但接口、命名、跨 skill 引用后续可能调整;生产 / 共享环境请优先使用 mono 模式(`dws skill setup --mode mono`)。问题请提 issue 反馈。 - ## 前置条件 — 执行操作前必读 -> **`use_skill(dws-shared)`** — 认证、全局参数(`--format json` / `--yes`)、错误码、URL 模板、跨产品消歧、安全规则与 capability 边界。**执行任何 `dws` 命令前先读;** 单产品的清晰命令可直接用本 skill。 - - +> **CRITICAL — 执行任何 `dws` 操作前,MUST 先用 Read 工具完整读取 [`dws-shared`](../dws-shared/SKILL.md)。**该轻量文件包含全局执行契约、安全底线及 shared references 的按需加载导航;不要预加载其全部 references。 > 命令参考:[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)。 ## 参数硬约束 @@ -35,7 +29,7 @@ metadata: ## Shortcuts(无专用脚本/recipe 时优先) -以下 shortcut 来自独立于 Runtime Schema 的公开 catalog。先按本 skill 的意图表、脚本和 recipe 路由:存在精确覆盖该场景的专用脚本/recipe 时按其执行;否则用户意图命中时,shortcut 优先于手写原子命令。用 `dws shortcut list --service doc --format json` 读取参数、约束、风险和示例,并以 `dws doc --help` 核对当前 Cobra flags;不要对 `+` 路径调用 `dws schema`。 +以下命令来自独立于 Runtime Schema 的公开 catalog。先运行 `dws shortcut list --service doc --format json` 读取完整契约,再用 `dws doc --help` 核对 flags;不要对 `+` 路径调用 `dws schema`。 | Shortcut | 风险 | 适用场景 | |---|---|---| @@ -64,44 +58,48 @@ metadata: |--------|------| | "创建文档(短内容)" | `dws doc create --name "<标题>" --content "<内容>"` | | "创建+写入(长内容自动分块)" | `python scripts/doc_create_and_write.py --name "<标题>" --content "<内容>" [--mode append\|overwrite]` | -| "搜文档 / 找文档" | `dws drive search --query "<关键词>"` → 取 `nodeId` 后 `dws doc read --node ` | -| "读文档内容" | `dws doc read --node ` | +| "搜在线文字文档 / 找在线文字文档" | `dws drive search --query "<关键词>" --format json` → `dws drive info --node --format json` → 仅 `extension=adoc` 使用 `dws doc read --node --format json` | +| "读在线文字文档(adoc)内容" | `dws doc read --node --format json` | | "更新文档内容 / 分块追加" | `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` | +| "查模板 / 套用模板创建文档" | `dws doc template list|search|apply`(详见 `references/doc.md` 模板管理) | +| "保存 / 查看 / 回滚在线文字文档(adoc)版本" | `dws doc version save/list/revert` | ## 标准 SOP(必遵流程) -> 命中以下意图**必须**按对应 SOP 顺序执行;**禁止**跳步、替换命令、编造 nodeId/blockId。每条命令必须带 `--format json`,执行后必须按"验证"步回读真实字段。文件类操作(上传/下载/复制/移动)切 `dingtalk-drive`;知识库节点管理切 `dingtalk-wiki`。 +> 命中以下意图**必须**按对应 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`;大文档**必须**只抽取用户需要的章节,**禁止**无差别全文展开。 +1. **定位(必须)**:用户已提供 URL / `nodeId` 时直接使用原值;未提供目标时才执行 `dws drive search --query "<关键词>" --format json`,再取候选结果的真实 `nodeId`。 +2. **探测(必须)**:对选中的候选执行 `dws drive info --node --format json`,从真实返回读取 `extension`;不得因为搜索结果标题像“文档”就跳过探测。 +3. **按类型读取(必须)**: + - `extension=adoc`:`dws doc read --node --format json`;大文档只抽取用户需要的章节。 + - `extension=md`:按普通文件处理,切到 `dingtalk-drive` 用 `dws drive download --node --output --format json` 下载后本地读取。 + - `extension=axls`:切到 `dingtalk-misc`,读取 `references/sheet.md` 后按电子表格意图执行。 + - `extension=able`:切到 `dingtalk-aitable`。 + - `extension=xlsx` / `xls` / `xlsm` / `csv` 或其他普通文件:切到 `dingtalk-drive`;不得执行 `dws doc read`。 -**禁止**:跳过 `drive search` 直接猜 nodeId、把整篇文档原样贴给用户。 +**禁止**:用户未提供目标时跳过搜索并猜 nodeId、未探测类型就执行 `doc read`、把整篇文档原样贴给用户。 ### 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`。 +1. **执行(必须)**:`dws doc create --name "<标题>" --content-file [--folder | --workspace ] --format json`(长/多行内容用 `--content-file`,不要用 `--content` 拼长串;用户未指定位置时省略两个位置参数,创建到“我的文档”根目录)。 +2. **验证(必须)**:从返回取 `nodeId`,立即 `dws doc info --node --format json` 回读确认。 -**完成条件**:创建结果返回真实 `nodeId`,且正文回读成功;`--folder` 只能使用文档文件夹 nodeId,不能传空间 ID。 +**禁止**:创建后不回读就答复"已创建"、把 `--folder` 当成空间 ID 传入。 ### SOP-3 覆盖/追加内容(write-content) **触发**:覆盖写/追加内容/改文档正文。 -1. **执行(必须)**:覆盖 `dws doc update --node --mode overwrite --content-file --yes --format json`;追加 `--mode append`。破坏性覆盖**必须**带 `--yes`,且建议先 `--dry-run` 预览。 +1. **执行(必须)**:覆盖先执行 `dws doc update --node --mode overwrite --content-file --dry-run --format json` 预览,用户确认后改用 `--yes` 实际覆盖;追加执行 `dws doc update --node --mode append --content-file --format json`。 2. **验证(必须)**:写后 `dws doc read --node --format json` 抽取受影响段落核对。 **禁止**:不加 `--yes` 反复重试覆盖、跳过 `--dry-run` 直接覆盖未确认的长文档。 @@ -110,19 +108,23 @@ metadata: **触发**:导出文档/下载文档/转 PDF·Markdown。 -1. **判类型(必须)**:先 `dws doc info --node --format json`;在线文档 → `dws doc export --node --export-format --output --format json`;普通文件 → 切 `dingtalk-drive` 用 `dws drive download --node --output --format json`。 +1. **判类型(必须)**:先 `dws drive info --node --format json`;`extension=adoc` → `dws doc export --node --export-format --output --format json`;普通文件 → 切 `dingtalk-drive` 用 `dws drive download --node --output --format json`。 -**禁止**:不分类型一律走 `doc export`(普通文件会失败)、跳过 `doc info` 判断。 +**禁止**:不分类型一律走 `doc export`(普通文件会失败)、跳过 `drive 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 步回读验证。 +1. **先列块(必须)**:`dws doc block list --node --format json`,当前响应的可操作块 ID 位于 `blocks[].element.id`(部分版本可能回显为 `blockId`);必须从目标内容对应项读取,不得编造。空文档的占位空段落可能不能作为 `--ref-block`。 +2. **按动作执行(必须)**: + - 插入:默认追加用 `dws doc block insert --node --text "<内容>" --format json`;只有明确要求相对位置时才加 `--ref-block <非空参照块ID> --where before|after`,容器内插入使用 `--parent-block <父块ID> --index <位置>`。插入命令**不接受** `--block-id`。 + - 更新:`dws doc block update --node --block-id <目标blockId> --text "<新内容>" --format json`。 + - 删除:用户确认后执行 `dws doc block delete --node --block-id <目标blockId> --yes --format json`。 +3. **验证(必须)**:再次执行 `dws doc block list --node --format json` 核对插入、更新或删除结果。 +4. **复杂块(必须)**:插入引用/代码/表格/分栏/附件/图片前,**必须**先读 [doc.md](references/doc.md) 对应小节,**禁止**只停在"准备查看 help"——说"我将插入..."后必须立即执行命令。 -**禁止**:编造 blockId、未确认就删除。命令帮助只用于确认参数,操作结果必须来自实际命令与回读。 +**禁止**:编造 blockId、未确认就删除、把完整 `--help` 输出当成最终结果答复用户。 ### SOP-6 导入本地文件为在线文档(import-file) **触发**:导入 Word / Excel / Markdown / 本地文件为在线文档。 @@ -133,26 +135,28 @@ metadata: **禁止**:把上传文件到钉盘误当成 doc import;不知道目标文件夹 nodeId 时先切 `dingtalk-drive`/`dingtalk-wiki` 查询。 - ## 多步文档短路径 - 在目标文件夹创建文字文档:`dws doc create --name "<标题>" --folder --content-file --format json`。拿到 `nodeId` 后立即回读。 -- 块级编辑固定顺序:`doc block list --node ` → 选 `blockId` → `doc block insert/update/delete` → `doc block list` 验证。删除块必须已有用户明确删除意图或二次确认。 -- 插入引用块、代码块、表格、分栏、附件、图片时,按 [写入索引](references/doc/doc-write-index.md) 选择命令与结构,并在写后回读。 -- 多个子文档、附件或块操作按依赖顺序执行;每一步复用上一条命令返回的真实 ID,最后统一回读受影响内容。 -- 用户说"读取并下载/导出"时,先 `doc info --node ... --format json` 判断类型:在线文档用 `doc export`,普通文件切到 `dingtalk-drive` 用 `drive download`。 -- 所有 dws 命令带 `--format json`;仅参数不确定时查 `--help`,并以实际命令结果完成验证。 +- 块级编辑固定顺序:`doc block list --node ` → 插入用 `--ref-block`/`--parent-block`,更新或删除用 `--block-id` → `doc block list` 验证。删除块必须已有用户明确删除意图或二次确认。 +- 插入引用块、代码块、表格、分栏、附件、图片时,优先读 [doc.md](references/doc.md) 对应小节,不要只停在"准备查看 help"。说出"我将插入..."后必须立即执行对应 terminal 调用。 +- 用户要求多个子文档/附件/块操作时,按 checklist 串行完成;最后一条 assistant 消息不能停在"接下来我要...",必须有实际工具调用或明确失败原因。 +- 用户说“读取并下载/导出”时,先 `drive info --node ... --format json` 按 + `extension` 判断类型:`adoc` 用 `doc export`,普通文件切到 + `dingtalk-drive` 用 `drive download`。 +- 所有结构化 dws 命令带 `--format json`。仅参数不确定时查 `--help`,不要把完整 help 当成最终结果。 ## 危险操作 -`block delete` 和 `comment delete` 不可逆,必须确认再加 `--yes`。 +`block delete` 不可逆,必须确认再加 `--yes`。 ## 跨产品协作 - 文件存储 / 上传下载 → 切到 `dingtalk-drive` - 知识库空间管理 → 切到 `dingtalk-wiki` - 数据表 → 切到 `dingtalk-aitable` +- 原生 `.md` 文件按普通文件处理 → 切到 `dingtalk-drive` 用 `drive download` / `drive upload` - 长篇报告生成(多源采集 + 写文档)→ 此 skill 提供 `doc_create_and_write.py` 脚本 -## 局部意图与 Recipe +## 局部意图与短流程 -- [局部意图消歧](references/intent-guide.md);[Lite Recipe](references/lite-recipes.md)。 +- [局部意图消歧](references/intent-guide.md);[短流程](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 7a7ed50a..706567a9 100644 --- a/skills/multi/dingtalk-doc/references/04-document.md +++ b/skills/multi/dingtalk-doc/references/04-document.md @@ -1,31 +1,32 @@ # 文档知识 -## 模板保形生成 - -已有 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 | 行动指南(固定路线) | |--------|-------------------| | 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 篇) | +| search-docs-and-share | 1. `dws drive search --query "<关键词>" --format json` → 取候选 `nodeId` + 标题建索引(不读全文)
2. 对追问选中的候选执行 `dws drive info --node --format json`
3. 仅 `extension=adoc` 使用 `dws doc read --node --format json`(最多 2 篇);`md` / `axls` / `able` / 普通文件分别切到 markdown / sheet / aitable / drive,禁止固定执行 `doc read` | | 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. `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) | +| update-doc-section | 1. `dws drive search --query "<关键词>" --format json` → 取 `nodeId`
2. `dws drive info --node --format json`,仅 `extension=adoc` 继续;其他类型切到对应 skill/reference
3. **形态选择(按 [doc-update-workflow.md §1.3](./doc/style/doc-update-workflow.md) 优先级)**:目标段落含 callout / 分栏 / 颜色 / @人 / 附件 / 嵌套结构 → 走 `jsonml-node-edit`;纯文本替换且确认无富结构 → 继续本 recipe
4. `dws doc read --node --format json` 定位目标章节
5. `dws doc update --node --content "<替换内容>" --mode overwrite --yes --format json`
6. **回读校验**:`dws doc read --node --format json` 确认 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 `(备选) | +| doc-to-message | 1. `doc read --node ` → 取正文(大文档只摘要+链接)
2. `aisearch person --keyword "<姓名>" --dimension name` → 取 `openDingTalkId`(推荐);或 `chat search --query "<群名>"` → 取 `openConversationId`
3. `chat message send --open-dingtalk-id --text "<内容>"`(推荐)或 `--group --text "<内容>"` 发送。仅当无法获取 openDingTalkId 时才用 `--user `(备选) | | 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 调用) | +## template-based-generation + +当用户提供已有 alidocs 文档并要求“按模板生成、复刻、生成同形态的新版本”时,必须走保形复制链路: + +1. `dws drive info --node <源文档ID或URL> --format json`,确认源节点存在并记录类型。 +2. `dws drive copy --node <源文档ID或URL> [--folder <目标文件夹>] [--workspace <目标知识库>] --format json`,从返回中提取副本 `nodeId`。 +3. 如需改名,只对副本执行 `dws drive rename --node <副本nodeId> --name "<新名称>" --format json`。 +4. `dws doc block list --node <副本nodeId> --content-format jsonml` 定位需要替换的块;仅对副本执行 `dws doc block update`。涉及多个块或富结构时,按 [doc-update-workflow.md](./doc/style/doc-update-workflow.md) 的 JSONML 路径操作。 +5. 再次读取副本的 `drive info` 和目标块,确认名称、内容与结构;禁止修改源文档。 + +**禁止**用 `doc read` → `doc create` 重建模板。Markdown 是有损投影,会丢失行高、单元格背景色、字号和部分富结构。 + --- ## 分块 append 截断风险提示 diff --git a/skills/multi/dingtalk-doc/references/_common/conventions.md b/skills/multi/dingtalk-doc/references/_common/conventions.md index 75491aab..803212b8 100644 --- a/skills/multi/dingtalk-doc/references/_common/conventions.md +++ b/skills/multi/dingtalk-doc/references/_common/conventions.md @@ -9,7 +9,7 @@ | 1 | **并行查详情**:拿到多个 ID 后,用 `&` 合并到同一条 Shell 命令并行执行 + `wait`,**严禁逐条串行** | | 2 | **翻页**:分页接口须拉全直至无更多 | | 3 | **优先批量 API**:有批量接口则用批量;无则按 #1 并行 | -| 4 | **群消息**:必须先 `chat search --query` 得 `openConversationId`,再 `chat message list --group`;多群同条命令并行 | +| 4 | **群消息**:必须先 `chat search --query` 得 `openConversationId`,再 `chat message list --group --time "" --direction older`;多群同条命令并行 | | 5 | **列表少轮次**:带条件搜索/列表 → 一次采全详情;**禁止**无新参数时重复同一 `list` / `search` | ## 多源并行采集(公共模式) @@ -29,7 +29,7 @@ |------|------|--------| | `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 | +| `deptId` | `contact dept search` | `contact dept list-members --ids `;多子部门时对每个子部门分别 `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` | @@ -38,7 +38,7 @@ | `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` | +| `dentryUuid` | `drive list` / `drive mkdir` | `drive info/download/copy/move/rename/delete --node`、`drive list/mkdir/upload/copy/move --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,不能把该数字直接代入后续命令。 +**ID 边界硬约束**:`dentryId` 通常是纯数字,只表示聊天文件消息需要的钉盘条目数字 ID;它不是父目录 ID。遇到 `drive --node/--folder`、`doc --node`、`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 8305de3d..805b6315 100644 --- a/skills/multi/dingtalk-doc/references/doc.md +++ b/skills/multi/dingtalk-doc/references/doc.md @@ -1,26 +1,490 @@ # 文档 (doc) 命令参考 -> 渐进式入口:先按任务选择下表中的参考,不要一次性加载全部文档。 +> **渐进式文档**:本文件为路由层(命令索引 + 场景索引 + 意图判断 + 工作流),各命令的详细参数、示例和踩坑说明在 [doc/](./doc/) 目录下按需加载。 -## 按需加载 +## 文档地址 (URI) -| 任务 | 参考 | -|---|---| -| 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) | +| 资源 | 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 ` 补查。 + +## 前置条件 — 执行操作前必读 + +**CRITICAL — 执行对应操作前,MUST 先用 Read 工具读取以下子文件:** + +1. **解析 URL / 定位文档**(几乎所有命令都需要先拿 nodeId) + → 必读 [`doc/doc-info.md`](doc/doc-info.md)(URL/dentryKey 提取规则、ID 边界、extension 路由、**获取 nodeId 三种方式 A/B/C**) + +2. **创建或编辑文档内容**(`doc create` / `doc update` / `doc block insert|update`) + → 必读 [`doc/style/doc-update-workflow.md`](./doc/style/doc-update-workflow.md)(**形态优先级硬规则:JSONML > element JSON > markdown**;markdown overwrite 会丢富结构) + - 从零创建时加读 [`doc/style/doc-create-workflow.md`](./doc/style/doc-create-workflow.md) + - **任何 `doc create` 都必须先读 [`doc/style/doc-style-guideline.md`](./doc/style/doc-style-guideline.md) §2.0 类型决策表 + §1 硬规则**(决定骨架 + 全局约束,不读就不知道用哪种骨架) + - 涉及 callout / 分栏 / 富 block 精修时再加读 style-guideline §4-§7 + [`doc/format/doc-jsonml-cookbook.md`](./doc/format/doc-jsonml-cookbook.md) + +**未读以上文件就改写已有文档会导致富结构丢失、参数错误或样式不达标。其他命令(阅读 / 评论 / 权限 / 附件 / 下载导出 / 文件操作)按需查下方 §命令索引表跳转对应子文件加载,不必提前加载。** + +## 查询命令帮助 + +当你不确定某个命令的具体参数、格式或可选项时,**优先执行 `--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` 查看命令列表 + +## 命令索引表 + +> 命令名 → 单文件,按需加载子文档。复杂任务请优先看下方 §场景索引。 + +### 阅读 / 元信息 + +| 命令 | 用途 | 必填参数 | 详见 | +|------|------|----------|------| +| `doc info` | 文档元信息(含 contentType / extension) | `--node` | [`doc/doc-info.md`](./doc/doc-info.md) | +| `doc read` | 读取正文(markdown 或 jsonml) | `--node` | [`doc/doc-read.md`](./doc/doc-read.md) | + +### 创建 / 写入 / 块编辑 + +| 命令 | 用途 | 必填参数 | 详见 | +|------|------|----------|------| +| `doc create` | 创建文字文档(adoc) | `--name` | [`doc/doc-create.md`](./doc/doc-create.md) | +| `doc update` | 整篇 / 段落级更新(markdown / jsonml) | `--node` `--mode` | [`doc/doc-update.md`](./doc/doc-update.md) | +| `doc block list/insert/update/delete` | 块级精细编辑(含 JSONML 节点操作) | `--node` (+ `--block-id`) | [`doc/doc-block.md`](./doc/doc-block.md) | + +### 附件 / 评论 / 导出 + +| 命令 | 用途 | 必填参数 | 详见 | +|------|------|----------|------| +| `doc media insert/download` | 附件 / 图片插入与下载 | `--node` `--file` 或 `--resource-id` | [`doc/doc-media.md`](./doc/doc-media.md) | +| `doc comment list/create/reply/update/delete/create-inline` | 文档评论与划词评论 | `--node` (+ ...) | [`doc/doc-comment.md`](./doc/doc-comment.md) | +| `doc export` / `doc export get` | 在线文档导出 docx/markdown/pdf;`export get` 输出已对齐统一 TaskResult 格式 | `--node` `--output` | [`doc/doc-export.md`](./doc/doc-export.md) | + +### 导入 + +| 命令 | 用途 | 必填参数 | 详见 | +|------|------|----------|------| +| `doc import` / `doc import get` | 本地文件导入为在线文档(docx→文档 / xlsx→表格 / xmind→脑图 / md→文档);`import get` 输出已对齐统一 TaskResult 格式 | `--file` | [`doc/doc-import.md`](./doc/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) + +> **迁移提示**:文件管理命令已按产品领域架构重新归属。**Agent 必须使用右侧推荐命令,禁止使用左侧旧命令**(旧命令仅供人类用户过渡参考,可能已不可用)。 + +| 旧命令 | 推荐命令 | 详见 | +|--------|----------|------| +| `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`](./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/format/doc-jsonml-cookbook.md`](./doc/format/doc-jsonml-cookbook.md) | +| JSONML 节点结构 | 字段定义 + JSON Schema | [`doc/format/doc-jsonml-schema.md`](./doc/format/doc-jsonml-schema.md) | + +## 场景索引 + +> 任务驱动的入口:知道任务但不确定要读哪些命令文件时,按本表**一次性 Read 文件组**,不要逐个跳。 + +| 任务场景 | 一次性读取 | 主命令 | +|---------|-----------|--------| +| 定位 nodeId / URL 解析 | [`doc-info.md`](./doc/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/doc-info.md) + [`doc-read.md`](./doc/doc-read.md) | read | +| 创建新文档 | [`doc-create.md`](./doc/doc-create.md) + [`doc-update.md`](./doc/doc-update.md)(写入管道)+ [`style/doc-create-workflow.md`](./doc/style/doc-create-workflow.md) + [`style/doc-style-guideline.md`](./doc/style/doc-style-guideline.md) | create | +| 创建文档且包含图片/截图/图文并茂 | [`doc-create.md`](./doc/doc-create.md) + [`doc-media.md`](./doc/doc-media.md) + [`style/doc-create-workflow.md`](./doc/style/doc-create-workflow.md) + [`style/doc-style-guideline.md`](./doc/style/doc-style-guideline.md) | create → media insert | +| 局部改写 / 段落替换(保真) | [`doc-read.md`](./doc/doc-read.md) + [`doc-update.md`](./doc/doc-update.md) + [`doc-block.md`](./doc/doc-block.md) + [`format/doc-jsonml-cookbook.md`](./doc/format/doc-jsonml-cookbook.md) + [`style/doc-update-workflow.md`](./doc/style/doc-update-workflow.md) | block update | +| 整篇 overwrite(可选并发检查) | [`doc-read.md`](./doc/doc-read.md) + [`doc-update.md`](./doc/doc-update.md) + [`format/doc-jsonml-schema.md`](./doc/format/doc-jsonml-schema.md) + [`style/doc-update-workflow.md`](./doc/style/doc-update-workflow.md) | update | +| 插入富 block(callout / 分栏 / 表格) | [`doc-block.md`](./doc/doc-block.md) + [`format/doc-jsonml-cookbook.md`](./doc/format/doc-jsonml-cookbook.md) + [`style/doc-style-guideline.md`](./doc/style/doc-style-guideline.md) | block insert | +| 上传图片 / 附件 | [`doc-media.md`](./doc/doc-media.md) | media insert | +| 评论 / 划词评论(含 @人 / 更新 / 删除) | [`doc-comment.md`](./doc/doc-comment.md)(+ `dws aisearch person` 取 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/doc-info.md) + [`doc-export.md`](./doc/doc-export.md) | export | +| 导入本地文件为在线文档 | [`doc-info.md`](./doc/doc-info.md) + [`doc-import.md`](./doc/doc-import.md) | import | +| 文件下载 / 上传 / 移动 / 重命名 / 复制 | [`drive.md`](../../dingtalk-drive/references/drive.md)(已迁移:`dws drive upload/download/copy/move/rename/delete`) | `drive *` | +| 版本管理(保存/列出/回滚) | 本文件 §版本管理(**仅 adoc**) | `dws doc version save/list/revert`(adoc);其他文件类型当前 CLI 不支持版本管理 | ## 意图判断 -按 [doc-intent.md](./doc/doc-intent.md) 判断 `doc`、`drive`、`wiki`、`sheet` 与 `aitable` 的边界。 +用户说"找文档/搜文档/最近文档": -## 模板管理 +- 全局搜索 → `dws drive search --query "<关键词>"`(聚合钉盘+文档空间) +- 空间内搜索 → `dws wiki node search --workspace --query "<关键词>"` +- 遍历文件夹 → `dws drive list --workspace ` 或 `dws wiki node list --workspace ` +- 最近访问/最近编辑 → `dws drive recent`(见 [`drive.md`](../../dingtalk-drive/references/drive.md)) -模板列表、搜索与套用见 [doc-template.md](./doc/doc-template.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-misc/references/sheet/sheet-workbook.md#创建钉钉表格文档)(axls 在线电子表格) +> - 用户说"创建多维表格/新建 AI 表格/建 base/数据库表" → 走 [`dws aitable base create`](../../dingtalk-aitable/references/aitable.md#base-base-管理)(able 多维表格) + +用户说"建文件夹/新建目录": + +- 创建 → `dws drive folder create` + +用户说"上传文件/传文件/上传到文档/上传到知识库": + +- 上传 → `dws drive upload`(需本地文件路径) + +用户说"下载/导出/下载到本地/导出文档/导出为Word/导出为docx/把文档导出来": + +- **必须先判断目标文件类型**,再决定走 `export` 还是 `download`: + - 在线文档 (alidocs/adoc) → **`doc export`**(导出是内容层操作,仅对 adoc 有意义) + - 已有文件(PDF、图片、附件、视频等非在线文档) → **`dws drive download`** +- **任务查询**:`doc export` 内部自动轮询完成导出;仅在超时或中断后,用 `dws doc export get --job-id ` 手动查询任务状态 +- 判断方法: + 1. 如果用户明确说了"导出文档"、"导出为Word/docx" → 直接走 `doc export` + 2. 如果用户明确说了"下载PDF/图片/附件" → 直接走 `drive download` + 3. 不确定时,先用 `drive info --node ` 查询节点信息,根据返回的 `extension` 字段判断: + - `extension` 为 `adoc` → 走 `doc export` + - `extension` 为其他(txt/pdf/docx/md/xlsx 等)→ 走 `drive download` + +> **严禁将"导出文档"直接路由到 `download`**。`download` 只能下载已有文件(原样下载),`export` 是将在线文档格式转换后导出为 docx 或 markdown 或 pdf,两者完全不同。 +> +> **导出任务查询**:`doc export` 超时后可用 `dws doc export get --job-id ` 查询任务状态。 + +用户说"导入文件/导入docx/导入Word/导入Excel/导入脑图/导入xmind/导入思维导图/把文件转成文档/文件转在线文档/导入到知识库/打开本地文件": + +- **直接调用 `dws doc import --file <文件路径> --workspace `**(或 `--folder `),一条命令自动完成上传+转换+创建在线文档,**无需先读取文件内容再手动创建**。**注意:`--folder` 和 `--workspace` 至少传一个,不传会报错。导入到"我的文档"需先执行 `dws wiki space list --type myWikiSpace` 获取 workspaceId。** +- 支持格式与映射:docx/doc→文档, xlsx/xls→表格, xmind/mark→脑图, md/txt→文档 +- 文件大小限制:20MB +- 导入后会自动转换为钉钉在线文档,返回在线文档 URL +- **任务查询**:`doc import` 内部自动轮询完成转换;仅在超时或中断后,用 `dws doc import get --task-id ` 手动查询任务状态 + +> **严禁先 Read 文件再 `doc create` + `doc update`**。`doc import` 是一体化命令,服务端负责格式转换,客户端无需解析文件内容。 +> +> **导入任务查询**:`doc import` 超时后可用 `dws doc import get --task-id ` 查询任务状态。 + +用户说"复制文档/拷贝文件/复制到": + +- 复制 → `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` + +用户说"保存版本/创建版本快照/手动保存一下版本": + +> **前置守卫**:以下 `doc version` 命令仅适用于钉钉在线文档(adoc)。其他文件类型(txt/pdf/docx/xlsx/md 等)当前 CLI 不支持版本管理。 + +- 保存版本 → `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,先按 [链接规范](../../dws-shared/references/url-patterns.md#alidocs-url-类型探测流程) 用 `dws drive info` probe;只有 probe 确认 `extension=adoc` 后,才继续按下列意图执行。其他类型(txt/pdf/docx/md/xlsx/file/folder 等)走 [`drive.md`](../../dingtalk-drive/references/drive.md) 或对应产品。 + +**用户直接粘贴文档 URL(无其他指令)**: + +- 默认 → `read`(读取文档内容) +- 如 URL 明显是文件夹 → `list`(列出文件夹内容) + +**用户粘贴 URL + 附加指令**: + +- "帮我看看这个文档" → `read` +- "这个文档的信息" → `info` +- "往这个文档追加内容" → `update --mode append` +- "把这个文档标题改成 X" / "这个文档改名为 X" → `dws drive rename` +- "把正文里的一级标题/章节标题改成 X" → `block update` + +关键区分: doc(文档编辑/阅读) vs aitable(数据表格操作) vs drive(文件管理) + +## 核心工作流 + +> 步骤性指引。"读哪些文件"组合参见上方 §场景索引;命令详细参数参见对应子文件。 + +```bash +# ── 工作流 1: 定位并阅读文档 ── +dws drive search --query "<关键词>" --format json # 1. 搜索定位 nodeId(或 wiki node search) +dws drive info --node --format json # 2. 必须确认 extension=adoc +dws doc read --node --format json # 3. 仅 adoc 读正文;其他 extension 切对应 skill + +# ── 工作流 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 --workspace --format json # 一体化导入(推荐) +dws doc import --file ./notes.md --folder --format json # 指定目标文件夹 +dws doc import --file ./data.xlsx --workspace --format json # 指定目标知识库 + +# ── 工作流 7: 下载 vs 导出(先 info 判 extension)── +dws drive info --node --format json # 必须先查 extension +dws doc export --node --output ~/downloads/ # extension=adoc 走 export(内容层) +dws drive download --node --output ~/downloads/ # extension≠adoc 走 drive download(存储层) + +# ── 工作流 8: 评论 + 划词(@人 需 userId)── +dws aisearch person --keyword "张三" --dimension name --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 +dws doc comment update --node --comment-key --content "已按最新数据修正" --format json +dws doc comment delete --node --comment-key --yes --format json # 删除不可逆,必须先确认 + +# ── 工作流 9: 权限授予(节点级,已迁移到 drive)── +dws aisearch person --keyword "张三" --dimension name --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) +``` + +## 上下文传递表 + +| 操作 | 从返回中提取 | 用于 | +|------|-------------|------| +| `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/update/delete 的 --comment-key | +| `comment create` | `commentKey` | comment reply/update/delete 的 --comment-key | +| `comment create-inline` | `commentKey` | comment reply/update/delete 的 --comment-key | +| `block list` | `blocks[].element.id` | comment create-inline 的 --block-id | +| `block list` | `blocks[].element.paragraph.text` | 计算 create-inline 的 --start / --end 偏移量 | +| `aisearch person` | `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`(查询后取结果获取文档地址) | + +## 相关产品 + +- [wiki](../../dingtalk-wiki/references/wiki.md) — 知识库空间级管理(创建/查询/列出/搜索知识库),doc 中的文档存储在 wiki 知识库中 +- [aitable](../../dingtalk-aitable/references/aitable.md) — 结构化数据表格(行列/字段/记录),不是富文本文档 +- [drive](../../dingtalk-drive/references/drive.md) — 钉盘文件存储/上传/下载,不是文档内容编辑 +- [report](../../dingtalk-misc/references/report.md) — 钉钉日志系统(日报/周报模版),不是在线文档 diff --git a/skills/multi/dingtalk-doc/references/doc/doc-block.md b/skills/multi/dingtalk-doc/references/doc/doc-block.md index 277e9829..456650a5 100644 --- a/skills/multi/dingtalk-doc/references/doc/doc-block.md +++ b/skills/multi/dingtalk-doc/references/doc/doc-block.md @@ -1,6 +1,10 @@ # doc block(块级精细编辑:list / insert / update / delete) -> **按需参考**:复杂或保真改写见 [更新工作流](./style/doc-update-workflow.md);涉及 callout、分栏、复杂表格等 JSONML 节点时再读取 [范例](./format/doc-jsonml-cookbook.md) 与 [结构定义](./format/doc-jsonml-schema.md)。 +> **前置条件(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 节点结构字段定义 > > **同任务常配合**:[`doc-update.md`](./doc-update.md)(整篇 overwrite / 末尾追加纯文本)/ [`./format/doc-jsonml-cookbook.md`](./format/doc-jsonml-cookbook.md)(JSONML 复制范例) @@ -180,7 +184,7 @@ dws doc block delete --node DOC_ID --block-id UUID | 从返回中提取 | 用于 | |-------------|------| -| `blocks[].blockId` | `block insert` 的 `--ref-block`、`block update/delete` 的 `--block-id` | +| element 模式的 `blocks[].element.id`;JSONML 模式的 `blocks[].blockId` | `block insert` 的 `--ref-block`、`block update/delete` 的 `--block-id`;空文档占位空段落不要作为 `--ref-block` | | `blocks[].element.id` | [`./doc-comment.md`](./doc-comment.md) `comment create-inline` 的 `--block-id` | | `blocks[].element.paragraph.text` | 计算 [`./doc-comment.md`](./doc-comment.md) `comment create-inline` 的 `--start` / `--end` 偏移量 | | attachment 块的 `resourceId` | [`./doc-media.md`](./doc-media.md) `media download` 的 `--resource-id` | @@ -256,6 +260,7 @@ 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 2c9407f4..6bdc7eab 100644 --- a/skills/multi/dingtalk-doc/references/doc/doc-comment.md +++ b/skills/multi/dingtalk-doc/references/doc/doc-comment.md @@ -1,6 +1,9 @@ # doc comment(文档评论:list / create / reply / update / delete / create-inline) -> **同任务常配合**:`dws contact user search`(查 `--mention` 用 userId)/ [`doc-block.md`](./doc-block.md)(划词评论必须先取 blockId 与 paragraph 文本) +> **前置条件(MUST READ):** 执行本命令前,必须先用 Read 工具读取以下文件: +> 1. [`../doc.md`](../doc.md) — 命令路由 + 场景索引 + 意图判断 + 工作流 +> +> **同任务常配合**:`dws aisearch person`(查 `--mention` 用 userId)/ `dws chat search`(查群用 openConversationId)/ [`doc-block.md`](./doc-block.md)(划词评论必须先取 blockId 与 paragraph 文本) --- @@ -31,10 +34,12 @@ Usage: Example: dws doc comment create --node --content "这里需要修改" dws doc comment create --node --content "请review" --mention uid1,uid2 + dws doc comment create --node --content "请群内关注" --mentioned-open-conversation-id Flags: --node string 目标文档的标识,支持传入 URL 或 ID (必填) --content string 评论的文字内容,纯文本 (必填) --mention string 被 @ 的用户 uid 列表,逗号分隔 + --mentioned-open-conversation-id strings 被 @ 的群 openConversationId,可重复指定或逗号分隔 ``` --- @@ -48,51 +53,57 @@ 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 + dws doc comment reply --node --comment-key --content "请群内确认" --mentioned-open-conversation-id Flags: --node string 目标文档的标识,支持传入 URL 或 ID (必填) --content string 回复的文字内容,表情回复时填写表情名称 (必填) --comment-key string 被回复评论的 commentKey,格式: {13位毫秒时间戳}{32位UUID},可从 list/create 结果获取 (必填) --emoji 设为 true 时作为表情贴图回复 (默认 false) --mention string 被 @ 的用户 uid 列表,逗号分隔 + --mentioned-open-conversation-id strings 被 @ 的群 openConversationId,可重复指定或逗号分隔;不得与 --emoji 同时使用 ``` --- ## doc comment update(更新评论) -```text +``` Usage: dws doc comment update [flags] Example: dws doc comment update --node --comment-key --content "已按最新数据修正" - dws doc comment update --node --comment-key --content "请确认" --mention uid1,uid2 + dws doc comment update --node --comment-key --content "更新后的评论内容" + dws doc comment update --node --comment-key --content "请群内关注" --mentioned-open-conversation-id Flags: - --node string 目标文档 ID 或 URL (必填) - --comment-key string 待更新评论的 commentKey (必填) - --content string 更新后的评论内容 (必填) + --node string 目标文档的标识,支持传入 URL 或 ID (必填) + --comment-key string 待更新评论的 commentKey,格式: {13位毫秒时间戳}{32位UUID},可从 list/create/create-inline 结果获取 (必填) + --content string 更新后的评论文字内容,纯文本 (必填) --mention string 被 @ 的用户 uid 列表,逗号分隔 + --mentioned-open-conversation-id strings 被 @ 的群 openConversationId,可重复指定或逗号分隔 ``` --- ## doc comment delete(删除评论) -```text +> **CAUTION:** 不可逆操作。必须先向用户确认。用户明确同意后,命令加 `--yes` 执行;不加 `--yes` 时 CLI 会在终端中提示输入 `yes/no`。 + +``` Usage: dws doc comment delete [flags] Example: dws doc comment delete --node --comment-key --yes Flags: - --node string 目标文档 ID 或 URL (必填) - --comment-key string 待删除评论的 commentKey (必填) + --node string 目标文档的标识,支持传入 URL 或 ID (必填) + --comment-key string 待删除评论的 commentKey,格式: {13位毫秒时间戳}{32位UUID},可从 list/create/create-inline 结果获取 (必填) ``` -删除不可恢复,必须先获得用户明确确认,再传全局 `--yes`。 - --- ## doc comment create-inline(创建划词评论) +> **能力边界:** `create_inline_comment` 后端当前未提供 `mentionedOpenConversationIds`,因此 `create-inline` 不支持 @群。不得只在正文写 `@群名` 冒充真实群 mention;如果用户要求划词评论 @群,应明确提示暂不支持。 + ``` Usage: dws doc comment create-inline [flags] @@ -112,10 +123,18 @@ Flags: ## 关键说明 -- `--mention` 接受 `userId` 列表(逗号分隔),需要先用 `dws contact user search --query "<姓名>"` 拿到 userId。 -- `--comment-key` 是评论唯一标识,从 `list` / `create` / `create-inline` 返回中提取,可用于 `reply` / `update` / `delete`。 +- `--mention` 接受 `userId` 列表(逗号分隔),需要先用 `dws aisearch person --keyword "<姓名>" --dimension name` 拿到 userId。 +- `--mentioned-open-conversation-id` 接受群 `openConversationId`,可重复传入或逗号分隔;CLI 稳定去重并拒绝空白值,不会转换成内部 `cid`。 +- 群 mention 仅支持全文评论 `create`、`update`、`reply`;`create-inline` 后端当前未提供该字段,不支持 @群。 +- 真正的用户 mention 必须传 `mentionedUserIds`,真正的群 mention 必须传 `mentionedOpenConversationIds`。只在 `content` 写 `@名称` 是普通文本,不会创建 mention 节点或通知。 +- 用户直接给 openConversationId 时直接使用。用户给群名时,使用当前 DWS profile 执行 `dws chat search --query "<群名>" --format json`。只在唯一匹配时取 `openConversationId`;多个同名候选必须展示群名称、组织等区分信息让用户选择;无匹配或查询失败时不得调用评论接口。 +- 不按所属组织在客户端过滤或提前拒绝 mention,最终权限以评论接口为准。服务端失败时保留或准确概括 `errorCode`、`errorMessage`、`logId`,不得删除 mention 参数静默重试;若要降级为普通文本必须先征得用户同意。 +- `content` 只保留正文。仅移除已成功解析成真实 mention 的明确 `@群名` token,保持原换行;普通 `@` 文本或未解析名称不得误删。 +- `--comment-key` 是 13 位毫秒时间戳 + 32 位 UUID 的拼接字符串,从 `list` / `create` / `create-inline` 返回中提取,用于 `reply` / `update` / `delete`。 - 划词评论的 `--start` / `--end` 是块内文本字符偏移量,从 0 开始;通过 [`./doc-block.md`](./doc-block.md) `block list` 取 `paragraph.text` 后人工或脚本计算。 - `reply` 加 `--emoji` 时 `--content` 填表情名称(如 `比心`、`赞`),不是文字内容。 +- `reply --emoji` 与群 mention 冲突;CLI 会在调用服务端前报错,不会静默忽略。 +- `delete` 是不可逆操作;AI Agent 必须先让用户确认,再追加 `--yes`,避免 CLI 进入交互等待。 ## 上下文传递 @@ -126,7 +145,8 @@ Flags: | `comment create-inline` `commentKey` | `comment reply/update/delete` 的 `--comment-key` | | [`./doc-block.md`](./doc-block.md) `block list` 的 `blocks[].element.id` | `comment create-inline` 的 `--block-id` | | [`./doc-block.md`](./doc-block.md) `block list` 的 `blocks[].element.paragraph.text` | 计算 `create-inline` 的 `--start` / `--end` 偏移量 | -| `dws contact user search` 的 `userId` | `comment create/reply/create-inline` 的 `--mention` | +| `dws aisearch person` 的 `userId` | `comment create/reply/create-inline` 的 `--mention` | +| `dws chat search` 的 `openConversationId` | `comment create/update/reply` 的 `--mentioned-open-conversation-id` | ## 常用模板 @@ -140,23 +160,33 @@ dws doc comment list --node --type inline --resolve-status unresolved - # 创建全文评论 dws doc comment create --node --content "这里需要补充数据来源" --format json -# 创建评论 + @人(先 contact user search 拿 userId) -dws contact user search --query "张三" --format json +# 创建评论 + @人(先 aisearch person 拿 userId) +dws aisearch person --keyword "张三" --dimension name --format json # 提取 userId 后: dws doc comment create --node --content "请确认这部分" --mention , --format json +# 创建评论 + @群(搜索结果唯一后取 openConversationId) +dws chat search --query "项目群" --format json +dws doc comment create --node --content "请群内关注" --mentioned-open-conversation-id --format json + # 文字回复 dws doc comment reply --node --comment-key --content "已修改" --format json +# 回复 + @群 +dws doc comment reply --node --comment-key --content "请群内确认" --mentioned-open-conversation-id --format json + +# 更新评论 +dws doc comment update --node --comment-key --content "已按最新数据修正" --format json + +# 更新 + 同时 @用户和 @群 +dws doc comment update --node --comment-key --content "请大家确认" --mention --mentioned-open-conversation-id --format json + +# 删除评论(不可逆;必须用户确认后再加 --yes) +dws doc comment delete --node --comment-key --yes --format json + # 表情回复(--content 填表情名称) dws doc comment reply --node --comment-key --content "比心" --emoji --format json -# 更新评论(可选 --mention) -dws doc comment update --node --comment-key --content "已修正" --format json - -# 删除评论(危险操作,需用户先明确确认) -dws doc comment delete --node --comment-key --yes --format json - # 划词评论(先 block list 取 blockId + paragraph.text,计算 start/end) dws doc block list --node --format json # 计算偏移后: @@ -168,5 +198,6 @@ 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,跨产品命令) +- `dws aisearch person`(取 mention 用的 userId,跨产品命令) diff --git a/skills/multi/dingtalk-doc/references/doc/doc-context.md b/skills/multi/dingtalk-doc/references/doc/doc-context.md deleted file mode 100644 index a5aa3972..00000000 --- a/skills/multi/dingtalk-doc/references/doc/doc-context.md +++ /dev/null @@ -1,25 +0,0 @@ -# 上下文传递表 - -## 上下文传递表 - -| 操作 | 从返回中提取 | 用于 | -|------|-------------|------| -| `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 89e00f50..bf11a677 100644 --- a/skills/multi/dingtalk-doc/references/doc/doc-create.md +++ b/skills/multi/dingtalk-doc/references/doc/doc-create.md @@ -1,13 +1,18 @@ # doc create(创建文档) -> **按需参考**:长内容、目标文件夹或回读验收见 [创建工作流](./style/doc-create-workflow.md) 与 [内容写入管道](./doc-update.md);用户要求正式排版时读取 [样式规范](./style/doc-style-guideline.md);仅在使用 `--content-format jsonml` 时读取 [JSONML 范例](./format/doc-jsonml-cookbook.md)。 +> **前置条件(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) — 长内容自动分片、`--content-file` vs `--content` 选择 +> 5. [`./format/doc-jsonml-cookbook.md`](./format/doc-jsonml-cookbook.md) — 仅当使用 `--content-format jsonml` 时必读 ## 创建路由前置判断(必看) > `dws doc create` 只能创建在线文字文档(adoc),**不要**用它承接所有「新建 xxx」请求。收到「创建/新建」类需求时,必须先按文件类型分流: > -> - 用户说「创建表格 / 新建表格 / 建个电子表格 / 在线表格 / 销售数据表」等 → 走 [`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 sheet create`](../../../dingtalk-misc/references/sheet/sheet-workbook.md#创建钉钉表格文档)(钉钉在线电子表格 `axls`),**不要**走 `doc create` +> - 用户说「创建多维表格 / 新建 AI 表格 / 建个 base / 数据库表」等 → 走 [`dws aitable base create`](../../../dingtalk-aitable/references/aitable.md#base-base-管理)(多维表格 `able`),**不要**走 `doc create` > - 用户说「创建文档 / 新建文档 / 写篇文档 / 会议纪要 / 周报 / 方案」等文字型内容 → 才走 `dws doc create` > > 一句话口诀:表格 → sheet/aitable;文档 → doc。 @@ -84,6 +89,7 @@ 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 d7b3c723..39b84e47 100644 --- a/skills/multi/dingtalk-doc/references/doc/doc-export.md +++ b/skills/multi/dingtalk-doc/references/doc/doc-export.md @@ -1,8 +1,11 @@ # 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`](../../../dingtalk-drive/references/drive.md)) +> **前置条件(MUST READ):** 执行本命令前,必须先用 Read 工具读取以下文件: +> 1. [`../doc.md`](../doc.md) — 命令路由 + 场景索引 + 意图判断 + 工作流 + +> **路由前置判断**:用户说「下载/导出」时**必须**先用 `dws drive info --node --format json` 查 `extension`: +> - `extension` 为 `adoc`(在线文档)→ **必须用 `export`**,禁止用 `download` +> - `extension` 为其他(txt/pdf/docx/md/xlsx 等)→ 用 `dws drive download`(详见 [`../drive.md`](../../../dingtalk-drive/references/drive.md)) > > `drive download` 只能下载**已有文件**(原样下载),`export` 是将**在线文档格式转换**后导出为 docx、markdown 或 pdf,两者完全不同。 @@ -17,6 +20,7 @@ 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 + # 异步模式:只提交不等待,立即返回 jobId Flags: --node string 要导出的文档标识,支持文档 URL 或 dentryUuid (必填) --output string 本地保存路径,文件路径或目录 (必填) @@ -26,10 +30,14 @@ Flags: CLI 内部自动完成:提交导出任务 → 渐进式退避轮询(最多约 5 分钟)→ 成功后自动下载文件。 **只需一条命令,无需手动轮询。** +**任务查询**:`doc export` 内部自动轮询直到完成,通常不需要手动查询;仅在超时或中断后用 `dws doc export get --job-id ` 兜底查询。 + --- ## doc export get(手动兜底查询任务) +> 任务状态:PROCESSING(处理中)/ SUCCESS(导出成功,返回 downloadUrl)/ FAILED(导出失败)。 + ``` Usage: dws doc export get [flags] @@ -48,13 +56,14 @@ Flags: - `export` 支持钉钉在线文档(alidocs,`contentType=ALIDOC`)导出为 `docx`、`markdown` 或 `pdf`,**在线表格导出请使用其他命令**。 - `--export-format` 支持 `docx`(默认)、`markdown`(或 `md`)和 `pdf` 三种格式。 - `--output` 既可以是文件完整路径,也可以是目录(CLI 自动按文档名生成 `.docx`、`.md` 或 `.pdf`)。 +- `doc export get` 主参数为 `--job-id`。 ## 上下文传递 | 从返回中提取 | 用于 | |-------------|------| -| `localPath` | 用户可访问的本地文件路径 | -| 中断时返回的 `jobId` | `export get` 的 `--job-id` | +| `localPath` | 用户可访问的本地文件路径(同步模式) | +| 超时/中断时返回的 `jobId` | `export get` 的 `--job-id`(查询后取 downloadUrl 获取下载地址) | ## 常用模板 @@ -81,7 +90,7 @@ dws doc export --node --export-format markdown --output ~/downloads/ dws doc export --node "https://alidocs.dingtalk.com/i/nodes/" --output ./exported.docx # 兜底:超时或中断后手动查任务 -dws doc export get --job-id --format json +dws doc export get --task-id --format json ``` ## 输出规范 @@ -89,12 +98,13 @@ dws doc export get --job-id --format json 导出完成后的输出必须遵循以下规则: - **仅返回本地文件路径**:导出成功后,直接告知用户文件已保存到本地的具体路径 -- **禁止上传 CDN**:不要在导出完成后调用已下线的 `chat file upload` 或其他上传命令将文件上传到钉钉 CDN +- **禁止上传 CDN**:导出完成后不要调用任何上传命令将文件上传到钉钉 CDN - **禁止输出下载链接**:不得生成或展示 `down.dingtalk.com` 等 CDN 下载链接 - 正确示例:`文档已导出到: ./文档名.docx` - 错误示例:上传文件并展示 CDN 下载链接 ## 参考 +- [`../doc.md` §意图判断](../doc.md#意图判断)(如何路由到本命令) - [`./doc-info.md`](./doc-info.md)(前置:判断 contentType=ALIDOC 才走 export) - [`../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 deleted file mode 100644 index 9bd6c2c4..00000000 --- a/skills/multi/dingtalk-doc/references/doc/doc-extra-index.md +++ /dev/null @@ -1,7 +0,0 @@ -# 导入 / 版本 / 模板 - -| 命令 | 用途 | 详见 | -|---|---|---| -| `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 deleted file mode 100644 index bd4f2923..00000000 --- a/skills/multi/dingtalk-doc/references/doc/doc-file-ops.md +++ /dev/null @@ -1,225 +0,0 @@ -# doc 文件操作(upload / download / copy / move / rename / delete + folder create) - -> **弃用提示(文件管理命令正在迁移到 drive / wiki)**:本文所列 `doc` 文件管理命令虽仍能跑,但执行时会打印弃用警告,请优先改用 `drive` / `wiki` 对应命令: -> - `doc download` → **`dws drive download`**(下载已有文件;在线文档导出 docx 仍走 `doc export`) -> - `doc folder create` → **`dws drive mkdir`**(个人空间/钉盘)或 **`dws wiki node create --workspace --type folder`**(知识库内) -> - `doc upload` / `doc copy` / `doc move` / `doc rename` / `doc delete` 也在同一迁移方向(→ `drive`),命令用法以 `drive` 为准。 - ---- - -## doc upload(上传文件到钉钉文档/知识库) - -``` -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 目标文档文件夹 nodeId 或 alidocs 文件夹 URL;不要传 drive dentryId/parent-id 这类纯数字 ID - --workspace string 目标知识库 ID - --convert 是否转换为钉钉在线文档 -``` - -### 关键说明 - -- `upload` 是三步自动完成的流程(获取凭证 → OSS 上传 → 提交入库),无需手动分步操作。 -- 支持上传任意类型文件(PDF、Office、图片等)到钉钉文档空间或知识库。 -- `--convert` 可将 Office 文件转换为钉钉在线文档。 -- **`doc upload` vs `drive upload`**:用户提到「知识库 / 文档空间 / workspace」→ `doc upload`;提到「钉盘 / 网盘 / 我的文件」→ `drive upload`;未明确目标时默认 `drive upload`。 -- 与 [`./doc-media.md`](./doc-media.md) `media insert` 的区别:`upload` 上传到文档空间作为**独立文件**;`media insert` 作为**附件块插入到文档正文中**。 - ---- - -## doc download(下载文件到本地) - -> **路由前置判断**:用户说「下载 / 导出」时**必须**先用 [`./doc-info.md`](./doc-info.md) `info --node --format json` 查 `contentType`: -> -> - `contentType` 为 `ALIDOC`(在线文档)→ 走 [`./doc-export.md`](./doc-export.md) `export` -> - `contentType` 为 `DOCUMENT`/`IMAGE`/`VIDEO` 等(已有文件)→ 走本命令 `download` - -``` -Usage: - dws doc download [flags] -Example: - dws doc download --node - dws doc download --node --output ./report.pdf - dws doc download --node "https://alidocs.dingtalk.com/i/nodes/" --output ~/downloads/ -Flags: - --node string 文件节点 ID 或 URL (必填) - --output string 本地保存路径 (文件路径或目录,必填) -``` - -### 关键说明 - -- `download` 是两步自动完成的流程(获取下载链接 → HTTP GET 下载),支持自动推断文件名;`--output` 可指定文件路径或目录。 -- **严禁将「导出文档」直接路由到 `download`**——`download` 只能下载已有文件(原样下载),`export` 是将在线文档格式转换后导出为 docx。 - ---- - -## doc copy(复制文档/文件) - -``` -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 目标文档文件夹 nodeId 或 alidocs 文件夹 URL;不要传 drive dentryId/parent-id 这类纯数字 ID - --workspace string 目标知识库 ID 或 URL (不传 --folder 时复制到该知识库根目录) -``` - -### 关键说明 - -- 需要对源文档有「阅读」权限,且对目标文件夹有「编辑」权限。 -- `copy` 是异步任务,若任务未完成则不会返回新文档 ID;如需获取可稍后通过 [`./doc-list.md`](./doc-list.md) 查询目标文件夹。 - ---- - -## doc move(移动文档/文件) - -``` -Usage: - dws doc move [flags] -Example: - dws doc move --node --folder - dws doc move --node --workspace - dws doc move --node "https://alidocs.dingtalk.com/i/nodes/" --folder -Flags: - --node string 文档/文件 ID 或 URL (必填) - --folder string 目标文档文件夹 nodeId 或 alidocs 文件夹 URL;不要传 drive dentryId/parent-id 这类纯数字 ID - --workspace string 目标知识库 ID 或 URL (不传 --folder 时移动到该知识库根目录) -``` - -### 关键说明 - -- 需要对源文档有「管理」权限,且对目标文件夹有「编辑」权限;移动后原位置的文档将不再存在。 - ---- - -## doc rename(重命名文档/文件) - -``` -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 新名称 (必填) -``` - -### 关键说明 - -- 需要对文档有「编辑」权限。 -- 只要意图是修改文档在列表和链接中展示的名称,统一路由到本命令;**不要**走 `drive`、`doc update` 或重新 `doc create`。 -- 只有用户明确说「正文里的标题/章节标题/段落标题/H1 标题」时,才走 [`./doc-block.md`](./doc-block.md) `block update`。 - ---- - -## doc delete(删除到回收站) - -> **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 (必填) -``` - -权限要求: 对文档有「管理」权限。 - -正确流程:1.向用户展示「即将删除「文档名」到回收站」 → 2.等用户确认 → 3.执行 `dws doc delete --node --yes`。 - ---- - -## doc folder create(创建文件夹) - -``` -Usage: - dws doc folder create [flags] -Example: - dws doc folder create --name "项目资料" - dws doc folder create --name "子文件夹" --folder -Flags: - --name string 文件夹名称 (必填) - --folder string 父文档文件夹 nodeId 或 alidocs 文件夹 URL;不要传 drive dentryId/parent-id 这类纯数字 ID - --workspace string 目标知识库 ID -``` - -## 上下文传递 - -| 操作 | 从返回中提取 | 用于 | -|------|-------------|------| -| `upload` | `nodeId` / URL | 上传后文件的访问链接;后续 `--node` 入参 | -| `download` | 本地文件路径 | 下载后的文件保存位置 | -| `copy` | `nodeId` / URL(异步,不保证返回) | 异步任务,未完成时不返回新文档 ID;可用 [`./doc-list.md`](./doc-list.md) 在目标文件夹查询 | -| `folder create` | `nodeId` | [`./doc-create.md`](./doc-create.md) / `list` / `upload` / `copy` / `move` 的 `--folder` | - -## 常用模板 - -```bash -# ── upload ── -# 上传到「我的文档」根目录 -dws doc upload --file ./report.pdf - -# 上传到指定文件夹 -dws doc upload --file ./slides.pptx --name "Q1汇报.pptx" --folder - -# 上传到知识库并转换为在线文档 -dws doc upload --file ./data.xlsx --workspace --convert - -# ── download(仅限非 ALIDOC 文件)── -# 自动推断文件名,下到目录 -dws doc download --node --output ~/downloads/ - -# 指定文件路径 -dws doc download --node --output ./report.pdf - -# ── copy ── -# 复制到指定文件夹(异步) -dws doc copy --node --folder --format json - -# 复制到目标知识库根目录 -dws doc copy --node --workspace --format json - -# ── move ── -# 移动到指定文件夹 -dws doc move --node --folder --format json - -# 移动到知识库根目录 -dws doc move --node --workspace --format json - -# ── rename ── -dws doc rename --node --name "新名称" --format json - -# ── delete(用户确认后才加 --yes)── -dws doc delete --node --yes --format json - -# ── folder create ── -# 在「我的文档」根目录创建 -dws doc folder create --name "项目资料" --format json - -# 在指定父文件夹下创建 -dws doc folder create --name "子文件夹" --folder --format json - -# 在知识库下创建 -dws doc folder create --name "项目资料" --workspace --format json -``` - -## 参考 - -- [`./doc-info.md`](./doc-info.md)(前置:判断 contentType 决定 download / export) -- [`./doc-export.md`](./doc-export.md)(在线文档格式转换导出 docx) -- [`./doc-media.md`](./doc-media.md)(文件作为附件块插入文档正文,与 upload 区分) -- [`./doc-search.md`](./doc-search.md) / [`./doc-list.md`](./doc-list.md)(拿 nodeId 的入口) diff --git a/skills/multi/dingtalk-doc/references/doc/doc-help-query.md b/skills/multi/dingtalk-doc/references/doc/doc-help-query.md deleted file mode 100644 index 31aec05d..00000000 --- a/skills/multi/dingtalk-doc/references/doc/doc-help-query.md +++ /dev/null @@ -1,25 +0,0 @@ -# 查询命令帮助 - -## 查询命令帮助 - -当你不确定某个命令的具体参数、格式或可选项时,**优先执行 `--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 ca9f4d19..7387fe48 100644 --- a/skills/multi/dingtalk-doc/references/doc/doc-import.md +++ b/skills/multi/dingtalk-doc/references/doc/doc-import.md @@ -1,5 +1,8 @@ # doc import(本地文件导入为在线文档) +> **前置条件(MUST READ):** 执行本命令前,必须先用 Read 工具读取以下文件: +> 1. [`../doc.md`](../doc.md) — 命令路由 + 场景索引 + 意图判断 + 工作流 + > **支持的文件格式**:docx, doc, xlsx, xls, md, txt, xmind, mark > **文件大小限制**:20MB @@ -11,14 +14,14 @@ Usage: dws doc import [flags] Example: - dws doc import --file ./report.docx + dws doc import --file ./report.docx --workspace dws doc import --file ./notes.md --folder dws doc import --file ./data.xlsx --workspace - dws doc import --file ./draft.md --name "项目周报" + dws doc import --file ./draft.md --workspace --name "项目周报" Flags: --file string 本地文件路径 (必填) - --folder string 目标文件夹 ID 或 URL (可选,与 --workspace 至少传一个时优先) - --workspace string 目标知识库 ID 或 URL (可选,不传时默认导入"知识库—我的文档"根目录) + --folder string 目标文件夹 ID 或 URL (与 --workspace 必填其一) + --workspace string 目标知识库 ID 或 URL (与 --folder 必填其一) --name string 导入后文档名称 (可选,默认取文件名不含扩展名) ``` @@ -34,6 +37,8 @@ CLI 内部自动完成:创建导入会话 → 上传文件到 OSS → 确认 ## doc import get(手动兜底查询任务) +> 任务状态:processing(转换中)/ completed(导入成功,返回 documentUrl)/ failed(导入失败)。 + ``` Usage: dws doc import get [flags] @@ -45,13 +50,23 @@ Flags: 仅在 `dws doc import` 超时或中断后,用于手动查询任务状态。通常不需要调用。 +**输出示例**(统一 TaskResult 格式): +```json +{"id":"task-abc123","type":"import","status":"SUCCESS","resultUrl":"https://alidocs.dingtalk.com/i/nodes/xxx","message":"","createTime":"2024-01-15T10:30:00Z"} +``` + +**处理中示例**: +```json +{"id":"task-abc123","type":"import","status":"PROCESSING","resultUrl":"","message":"任务处理中,请稍后查询","createTime":"2024-01-15T10:30:00Z"} +``` + ## 关键说明 - `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` 至少传一个,不传会报错。如需导入到"我的文档"根目录,请先获取 workspaceId:`dws wiki space list --type myWikiSpace --format json`,然后传 `--workspace `。 - `--folder` 优先级高于 `--workspace`:两者都传时以 `--folder` 为准。 ## 上下文传递 @@ -61,7 +76,7 @@ Flags: | `documentUrl` | 导入完成后的在线文档地址(可直接分享给用户) | | `documentName` | 导入后的文档名称 | | `documentType` | 文档类型(DOC / SHEET / MIND) | -| 中断时返回的 `taskId` | `import get` 的 `--task-id` | +| 超时/中断时返回的 `taskId` | `import get` 的 `--task-id`(查询后取 documentUrl 获取文档地址) | ## 输出规范 @@ -85,10 +100,10 @@ Flags: ```bash # 导入 Word 文档(最常用) -dws doc import --file ./report.docx --format json +dws doc import --file ./report.docx --workspace --format json # 导入 Markdown 文件 -dws doc import --file ./notes.md --format json +dws doc import --file ./notes.md --workspace --format json # 导入到指定文件夹 dws doc import --file ./data.xlsx --folder --format json @@ -97,16 +112,18 @@ 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 +dws doc import --file ./draft.md --workspace --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.md` §意图判断](../doc.md#意图判断)(如何路由到本命令) - [`./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 deleted file mode 100644 index 766948e2..00000000 --- a/skills/multi/dingtalk-doc/references/doc/doc-info-index.md +++ /dev/null @@ -1,7 +0,0 @@ -# 阅读 / 元信息 - -| 命令 | 用途 | 详见 | -|---|---|---| -| `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 706928ad..c80bd791 100644 --- a/skills/multi/dingtalk-doc/references/doc/doc-info.md +++ b/skills/multi/dingtalk-doc/references/doc/doc-info.md @@ -1,8 +1,12 @@ # doc info(获取文档元信息 + URL 解析) -> 用户给出原始 `alidocs` URL 且需要 probe 时,按需读取 [URL 规则](../url-patterns.md)。 +> **前置条件(MUST READ):** 执行本命令前,必须先用 Read 工具读取以下文件: +> 1. [`../doc.md`](../doc.md) — 命令路由 + 场景索引 + 意图判断 + 工作流 +> 2. [`url-patterns.md`](../../../dws-shared/references/url-patterns.md) — 仅当用户原始 `alidocs` URL 需要 probe 时 > -> **同任务常配合**:`dws drive search` / `dws wiki node search`(先定位 nodeId)/ [`doc-read.md`](./doc-read.md)(确认是 ALIDOC 后读正文) +> **探测入口变更**:alidocs URL 的类型探测现在统一走 `dws drive info`(详见 [链接规范](../../../dws-shared/references/url-patterns.md#alidocs-url-类型探测流程))。`drive info` 检测到 `extension=adoc/axls/able` 时会自动调用 `doc info` 返回更详细的文档信息。**仅在 `drive info` 已确认是 ALIDOC 类型后**,才需要直接使用 `doc info`。 +> +> **同任务常配合**:`dws drive search` / `dws wiki node search`(先定位 nodeId)/ [`doc-read.md`](doc-read.md)(确认是 ALIDOC 后读正文) ## 命令格式 @@ -18,24 +22,11 @@ Flags: --node string 文档 ID 或 URL (必填) ``` -## 文件内容获取路由规则 - -> 当用户请求"分析/查看/读取某个文件内容"时,**必须先调用 `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 --sheet-id ` | -| ALIDOC | able | 在线查询多维表格记录 | `dws aitable table list --base-id ` → `dws aitable record query --base-id --table-id ` | -| 非 ALIDOC | — | **不支持在线分析** | 告知用户需下载到本地后查看 | - -**关键规则**:非 ALIDOC 类型文件(PDF/Word/图片/视频等)不支持在线分析,用户可以选择下载后本地查看。 - ## URL 识别与 DOC_ID 提取 当用户输入包含钉钉文档 URL 时,**必须先识别并提取 DOC_ID**,再判断意图。 -补充:如果这是用户直接提供的原始 `alidocs` URL,必须先按 [链接规范](../url-patterns.md#alidocs-url-类型探测流程) probe 一次确认真实类型,再判断是否继续走 `doc`。 +补充:如果这是用户直接提供的原始 `alidocs` URL,必须先按 [链接规范](../../../dws-shared/references/url-patterns.md#alidocs-url-类型探测流程) 用 `dws drive info` 探测真实类型。只有 `extension=adoc` 时才继续走 `doc`;其他类型(pdf/docx/txt/md/xlsx 等)走 `drive`。 ### 支持的 URL 格式 @@ -49,7 +40,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,先按 [链接规范](../../../dws-shared/references/url-patterns.md#alidocs-url-类型探测流程) 用 `dws drive info` 探测;只有探测确认 `extension=adoc` 时,才继续走 `doc` ## ID 边界与参数映射 @@ -65,7 +56,7 @@ Flags: 用户输入含 alidocs.dingtalk.com URL → 若是用户直接提供的原始 URL,先按链接规范做 probe → 提取 DOC_ID(URL 路径最后一段) - → 结合用户意图选择命令(doc 默认 read,folder 默认 list,file 默认 download) + → 结合用户意图选择命令(adoc 默认 `doc read`,folder 默认 `drive list`,file 默认 `drive download`) → 将 DOC_ID 传给 --node 参数 ``` @@ -108,7 +99,7 @@ dws doc read --node "https://alidocs.dingtalk.com/document/preview?cid=749936706 | 从返回中提取 | 用于 | |-------------|------| -| `contentType` + `extension` | 选择 [`./doc-read.md`](./doc-read.md) / `dws sheet ...` / `dws aitable ...` / `dws drive download`(非 ALIDOC 走存储层下载) | +| `extension` | 选择 [`./doc-read.md`](doc-read.md)(adoc)/ `dws sheet ...`(axls)/ `dws aitable ...`(able)/ `dws drive download`(其他) | | `nodeId` / `docUrl` | 后续所有 `--node` 入参 | ## 常用模板 @@ -142,6 +133,7 @@ dws doc info --node # 错误 ## 参考 +- [`../doc.md` §意图判断](../doc.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 流程) +- [`./doc-read.md`](doc-read.md)(extension=adoc 的后续命令) +- [`url-patterns.md`](../../../dws-shared/references/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 deleted file mode 100644 index eaa1986c..00000000 --- a/skills/multi/dingtalk-doc/references/doc/doc-intent.md +++ /dev/null @@ -1,136 +0,0 @@ -# 意图判断 - -## 意图判断 - -用户说"找文档/搜文档/最近文档": - -- 全局搜索 → `dws drive search --query "<关键词>"`(聚合钉盘+文档空间) -- 空间内搜索 → `dws wiki node search --workspace --query "<关键词>"` -- 遍历文件夹 → `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-list.md b/skills/multi/dingtalk-doc/references/doc/doc-list.md deleted file mode 100644 index 20a45a9f..00000000 --- a/skills/multi/dingtalk-doc/references/doc/doc-list.md +++ /dev/null @@ -1,24 +0,0 @@ -# doc list(已弃用 → drive list / wiki node list) - -> **弃用提示**:`dws doc list` 已迁移,不再是 `doc` 子命令。它虽仍能跑,但每次都会打印弃用警告: -> `'dws doc list' is deprecated, use 'dws drive list --workspace ' or 'dws wiki node list --workspace '`。 -> 遍历文件夹/知识库请切到 `dingtalk-drive` skill,用下面的命令。 - -## 改用什么 - -| 旧命令(弃用) | 改用 | 场景 | -|--------------|------|------| -| `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 ` | 遍历知识库 | - -`drive list` 的完整参数(`--folder` / `--workspace` / `--limit` / `--cursor` / `--order-by` 等)见 `dingtalk-drive` skill。 - -## 上下文传递 - -从返回里取 `nodes[].nodeId`(folder 类型的 `nodeId` 可继续作 `--folder`),传给 `doc read` / `doc info` / `doc update` 等内容级命令的 `--node`。 - -## 参考 - -- `dingtalk-drive` skill(列表 / 搜索 / 上传下载等文件管理命令的归属) -- [`./doc-info.md`](./doc-info.md)(拿到 nodeId 后查元信息) diff --git a/skills/multi/dingtalk-doc/references/doc/doc-media-index.md b/skills/multi/dingtalk-doc/references/doc/doc-media-index.md deleted file mode 100644 index 2c5c0aa7..00000000 --- a/skills/multi/dingtalk-doc/references/doc/doc-media-index.md +++ /dev/null @@ -1,7 +0,0 @@ -# 附件 / 评论 / 导出 - -| 命令 | 用途 | 详见 | -|---|---|---| -| `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 0771ec2b..079a4f48 100644 --- a/skills/multi/dingtalk-doc/references/doc/doc-media.md +++ b/skills/multi/dingtalk-doc/references/doc/doc-media.md @@ -1,5 +1,8 @@ # 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 `![](...)` 图片语法——这些是页面链接,不是可渲染的图片资源 @@ -89,6 +92,7 @@ dws doc media download --node --resource-id ## 参考 +- [`../doc.md` §意图判断](../doc.md#意图判断)(如何路由到本命令族) - [`./doc-block.md`](./doc-block.md)(block list 取 attachment 的 resourceId) - [`../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 deleted file mode 100644 index a1265932..00000000 --- a/skills/multi/dingtalk-doc/references/doc/doc-permission.md +++ /dev/null @@ -1,113 +0,0 @@ -# doc permission(文档权限:add / update / list) - -> **关键区分**: -> - "把**某篇文档**授权给某人" → `doc permission add`(节点级,包括「我的文档」下的文档都支持) -> - "把**某个知识库**整体授权给某人" → `wiki member add`(容器级,但**「我的文档」个人空间不支持**) - ---- - -## doc permission add(节点级授权) - -``` -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 string 被授权的用户 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`(那个是知识库容器级授权)。 - ---- - -## doc permission update(修改权限) - -``` -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 string 目标用户 userId 列表,逗号分隔 (必填,单次最多 30 个) - --role string 新角色 (必填,大小写不敏感): MANAGER / EDITOR / DOWNLOADER / READER - --workspace string 所属知识库 ID (选填) -``` - ---- - -## doc permission list(列出权限) - -``` -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 (默认 30) - --filter-role string 按角色过滤: MANAGER / EDITOR / DOWNLOADER / READER (选填) -``` - -> 接口不支持游标分页,使用 `--limit` 控制单次返回数量。 - -## 关键说明 - -- `--role` 大小写不敏感;文档统一使用大写枚举:`MANAGER` / `EDITOR` / `DOWNLOADER` / `READER`。`--filter-role` 也统一传大写枚举。 -- `--users` 单次最多 30 个 userId,逗号分隔。 -- 需要 userId 时,使用 `dws contact user search --query "<姓名>"`(跨产品命令)取得。 - -## 上下文传递 - -| 从返回中提取 | 用于 | -|-------------|------| -| `dws contact user search` 的 `userId` | `permission add/update` 的 `--users` | -| [`./doc-search.md`](./doc-search.md) / [`./doc-list.md`](./doc-list.md) 的 `nodeId` | 三命令的 `--node` | - -## 常用模板 - -```bash -# 查看谁有权限 -dws doc permission list --node --format json - -# 按角色过滤查看 -dws doc permission list --node --filter-role EDITOR --format json - -# 拉取更多结果(默认 30,最大 200) -dws doc permission list --node --limit 200 --format json - -# 给单人开 READER 权限 -dws doc permission add --node --users --role READER --format json - -# 给多人开 EDITOR 权限(最多 30 个 uid) -dws doc permission add --node --users ,, --role EDITOR --format json - -# 设为管理者 -dws doc permission add --node --users --role MANAGER --format json - -# 修改某人角色(READER → EDITOR) -dws doc permission update --node --users --role EDITOR --format json - -# 通过 alidocs URL 直接授权 -dws doc permission add --node "https://alidocs.dingtalk.com/i/nodes/" --users --role READER --format json -``` - -## 参考 - -- `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 deleted file mode 100644 index f1dc5358..00000000 --- a/skills/multi/dingtalk-doc/references/doc/doc-prerequisites.md +++ /dev/null @@ -1,15 +0,0 @@ -# 文档参考选择 - -只读取当前操作需要的参考,不要从命令 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 c3a3a72b..97e1441f 100644 --- a/skills/multi/dingtalk-doc/references/doc/doc-read.md +++ b/skills/multi/dingtalk-doc/references/doc/doc-read.md @@ -1,8 +1,10 @@ # doc read(读取文档内容) -> 仅当需要解释或编辑 JSONML 结构时,按需读取 [JSONML 范例](./format/doc-jsonml-cookbook.md)。 +> **前置条件(MUST READ):** 执行本命令前,必须先用 Read 工具读取以下文件: +> 1. [`../doc.md`](../doc.md) — 命令路由 + 场景索引 + 意图判断 + 工作流 +> 2. [`./format/doc-jsonml-cookbook.md`](./format/doc-jsonml-cookbook.md) — 仅当使用 `--content-format jsonml` 时必读 > -> **同任务常配合**:[`doc-info.md`](./doc-info.md)(先解析 URL,确认 contentType=ALIDOC、extension=adoc)/ [`doc-update.md`](./doc-update.md)(读后改写)/ [`doc-block.md`](./doc-block.md)(块级精修前先读结构) +> **同任务常配合**:[`doc-info.md`](doc-info.md)(先解析 URL,确认 extension=adoc)/ [`doc-update.md`](doc-update.md)(读后改写)/ [`doc-block.md`](doc-block.md)(块级精修前先读结构) ## 命令格式 @@ -43,7 +45,7 @@ Flags: | 从返回中提取 | 用于 | |-------------|------| | Markdown 正文 | 用户可读输出 / 二次处理 | -| JSONML `jsonml` 数组 | [`doc-update.md`](./doc-update.md) 的 `--content-file` + `--content-format jsonml` | +| JSONML `jsonml`(完整 body) | [`doc-update.md`](./doc-update.md) 的 `--content-file` + `--content-format jsonml`(可整体写回) | | JSONML `revision` | [`doc-update.md`](./doc-update.md) 的 `--revision`(可选;担心被并发覆盖时使用) | | 附件链接中的 `resourceId` | [`doc-media.md`](./doc-media.md) 的 `--resource-id`(链接过期后续期) | @@ -75,6 +77,7 @@ 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 deleted file mode 100644 index 63ac1f27..00000000 --- a/skills/multi/dingtalk-doc/references/doc/doc-related.md +++ /dev/null @@ -1,8 +0,0 @@ -# 相关产品 - -## 相关产品 - -- [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 deleted file mode 100644 index b82bd13d..00000000 --- a/skills/multi/dingtalk-doc/references/doc/doc-scenarios.md +++ /dev/null @@ -1,22 +0,0 @@ -# 场景索引 - -## 场景索引 - -> 任务驱动的入口:知道任务但不确定要读哪些命令文件时,按本表**一次性 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-search.md b/skills/multi/dingtalk-doc/references/doc/doc-search.md deleted file mode 100644 index f7cd6c40..00000000 --- a/skills/multi/dingtalk-doc/references/doc/doc-search.md +++ /dev/null @@ -1,24 +0,0 @@ -# doc search(已弃用 → drive search / wiki node search) - -> **弃用提示**:`dws doc search` 已迁移,不再是 `doc` 子命令。它虽仍能跑,但每次都会打印弃用警告: -> `'dws doc search' is deprecated, use 'dws drive search' or 'dws wiki node search --workspace '`。 -> 搜索文档/文件请切到 `dingtalk-drive` skill,用下面的命令。 - -## 改用什么 - -| 旧命令(弃用) | 改用 | 场景 | -|--------------|------|------| -| `dws doc search --query "<关键词>"` | `dws drive search --query "<关键词>"` | 全局搜文档/文件(跨钉盘+文档空间聚合) | -| `dws doc search --extensions pdf,docx` | `dws drive search --query "..." --extensions pdf,docx` | 按扩展名过滤 | -| `dws doc search --workspace-ids ` | `dws wiki node search --workspace --query "<关键词>"` | 指定知识库内搜索 | - -`drive search` 的完整参数(`--query` / `--extensions` / `--created-from` / `--creator-uids` / `--limit` / `--cursor` 等)见 `dingtalk-drive` skill。 - -## 上下文传递 - -拿到返回里的文档 `nodeId` / URL 后,`doc read` / `doc info` / `doc update` / `doc block` 等内容级命令照常用。 - -## 参考 - -- `dingtalk-drive` skill(搜索 / 列表 / 上传下载等文件管理命令的归属) -- [`./doc-info.md`](./doc-info.md)(URL → nodeId 提取) diff --git a/skills/multi/dingtalk-doc/references/doc/doc-template.md b/skills/multi/dingtalk-doc/references/doc/doc-template.md deleted file mode 100644 index 840a0f83..00000000 --- a/skills/multi/dingtalk-doc/references/doc/doc-template.md +++ /dev/null @@ -1,54 +0,0 @@ -# 模板管理 - -| 命令 | 用途 | 必填参数 | -|---|---|---| -| `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 cd105b4f..026b33c4 100644 --- a/skills/multi/dingtalk-doc/references/doc/doc-update.md +++ b/skills/multi/dingtalk-doc/references/doc/doc-update.md @@ -1,6 +1,10 @@ # doc update(更新文档内容) -> **按需参考**:复杂改写、分片或保真编辑见 [更新工作流](./style/doc-update-workflow.md);用户要求统一排版时见 [样式规范](./style/doc-style-guideline.md);仅在使用 `--content-format jsonml` 时读取 [JSONML 范例](./format/doc-jsonml-cookbook.md)。 +> **前置条件(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` 时必读 > > **同任务常配合**:[`doc-read.md`](./doc-read.md)(改写前必读,jsonml 模式拿当前结构;担心被并发覆盖时再取 revision)/ [`doc-block.md`](./doc-block.md)(单 block 改写优先;本命令更适合追加 / 整篇 overwrite) @@ -224,6 +228,7 @@ 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 deleted file mode 100644 index 2305b7b9..00000000 --- a/skills/multi/dingtalk-doc/references/doc/doc-uri.md +++ /dev/null @@ -1,10 +0,0 @@ -# 文档地址 (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 deleted file mode 100644 index 22d5f36b..00000000 --- a/skills/multi/dingtalk-doc/references/doc/doc-version.md +++ /dev/null @@ -1,49 +0,0 @@ -# 版本管理 - -| 命令 | 用途 | 必填参数 | -|---|---|---| -| `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 deleted file mode 100644 index de1fbf4e..00000000 --- a/skills/multi/dingtalk-doc/references/doc/doc-workflow.md +++ /dev/null @@ -1,77 +0,0 @@ -# 核心工作流 - -## 核心工作流 - -> 步骤性指引。"读哪些文件"组合参见上方 §场景索引;命令详细参数参见对应子文件。 - -```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 --users , --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 deleted file mode 100644 index d5320d5e..00000000 --- a/skills/multi/dingtalk-doc/references/doc/doc-write-index.md +++ /dev/null @@ -1,7 +0,0 @@ -# 创建 / 写入 / 块编辑 - -| 命令 | 用途 | 必读参考 | -|---|---|---| -| `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/format/doc-jsonml-schema.md b/skills/multi/dingtalk-doc/references/doc/format/doc-jsonml-schema.md index 69375599..4978c91d 100644 --- a/skills/multi/dingtalk-doc/references/doc/format/doc-jsonml-schema.md +++ b/skills/multi/dingtalk-doc/references/doc/format/doc-jsonml-schema.md @@ -460,7 +460,9 @@ block tag 也可出现在 inline 上下文(如 `img` 既是 block 又是 inlin ["img", {"uuid": "img1", "src": "https://example.com/photo.png", "width": 400, "height": 300}] ``` -- **注意**: 不存在 `layout` 属性,布局由 UI 层控制 +- **注意**: + - 不存在 `layout` 属性,布局由 UI 层控制 + - `img` 是 inline 元素,必须包裹在 `p` 段落中 ### mention(@提及) 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 4856887f..e055629e 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 重组结构”。 -> 使用 JSONML 创建复杂结构前,按需读取 [doc-jsonml-cookbook.md](../format/doc-jsonml-cookbook.md);其中 §决策型文档骨架范例 有可复用模板。 +> **MUST READ**:动手写 JSONML 前,必须先用 Read 工具读取 [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/format/doc-jsonml-index.md b/skills/multi/dingtalk-doc/references/format/doc-jsonml-index.md deleted file mode 100644 index 85b64374..00000000 --- a/skills/multi/dingtalk-doc/references/format/doc-jsonml-index.md +++ /dev/null @@ -1,9 +0,0 @@ -# 排版规范 / 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 index 6d6d8cd9..95684503 100644 --- a/skills/multi/dingtalk-doc/references/intent-guide.md +++ b/skills/multi/dingtalk-doc/references/intent-guide.md @@ -7,8 +7,8 @@ | "搜一下 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 猜类型 | +| "参照这个生成同样的 / 按模板生成 / 复刻 X / 同样的模板 X 月份的" + 已有 alidocs URL | 模板保形生成同形态变体 | `drive copy + drive rename + doc block update` → 见 [best_practices/04-document.md `template-based-generation`](../../dingtalk-doc/references/04-document.md#template-based-generation) | `doc read + doc create`(重写链) | adoc → markdown 是有损投影,read+create 会丢行高/单元格背景色/字号;copy 在 adoc 层保形复制后只在副本上局部修改 | +| "这个 alidocs 表格链接帮我看下"(粘贴原始 URL) | 先 probe 节点类型 | `dws drive 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` | 钉钉日志系统(日报/周报),不是文档 | diff --git a/skills/multi/dingtalk-doc/references/lite-recipes.md b/skills/multi/dingtalk-doc/references/lite-recipes.md index e1050d22..7d98e101 100644 --- a/skills/multi/dingtalk-doc/references/lite-recipes.md +++ b/skills/multi/dingtalk-doc/references/lite-recipes.md @@ -6,9 +6,9 @@ ### query-doc -1. 全局搜索:`drive search --query "<关键词>"` → `nodeId`(聚合钉盘+文档空间) -2. 空间内搜索:`wiki node search --workspace --query "<关键词>"` → `nodeId` -3. `doc read --node `(按需;大文档只抽章节) +1. 用户已提供 URL / `nodeId` 时直接使用原值;否则全局搜索 `dws drive search --query "<关键词>" --format json` → `nodeId`(聚合钉盘+文档空间),或在指定空间执行 `dws wiki node search --workspace --query "<关键词>" --format json` → `nodeId`。 +2. 对选中的候选执行 `dws drive info --node --format json`,读取真实 `extension`。 +3. 按类型分流:`adoc` → `dws doc read --node --format json`(大文档只抽章节);`md` → 按普通文件切 `dingtalk-drive` 用 `dws drive download` 下载后本地读取;`axls` → 切 `dingtalk-misc` 的 `references/sheet.md`;`able` → 切 `dingtalk-aitable`;xlsx/xls/xlsm/csv 和其他普通文件 → 切 `dingtalk-drive`。非 `adoc` 禁止执行 `doc read`。 ### list-folder-docs @@ -28,11 +28,11 @@ dws doc import --file ./report.docx --format json 4. 超时或中断时 CLI 返回 `taskId`,用 `dws doc import get --task-id --format json` 手动查询 **`--folder` 参数传值规则**: -- 首选路径:用户提供 alidocs URL 时,直接将完整 URL 传入 `--folder`,无需先调 `doc info` -- 预检路径:若需确认 URL 指向的是文件夹,可先调 `dws doc info --node `: +- 首选路径:用户提供 alidocs URL 时,直接将完整 URL 传入 `--folder`,无需先调 `drive info` +- 预检路径:若需确认 URL 指向的是文件夹,可先调 `dws drive info --node `: - `nodeType == "folder"` → 使用 `nodeId` 或原始 URL 作为 `--folder` 值 - `nodeType` 不是 folder → 提示用户:该链接指向的不是文件夹 -- 禁止:不得使用 `doc info` 返回的 `folderId` 字段作为 `--folder` 的值(`folderId` 是父文件夹 ID,非当前节点 ID) +- 禁止:不得使用 `drive info` 返回的 `folderId` 字段作为 `--folder` 的值(`folderId` 是父文件夹 ID,非当前节点 ID) 格式与文档类型映射: - `.docx` / `.doc` → 文字文档(DOC) @@ -42,3 +42,4 @@ dws doc import --file ./report.docx --format json > **禁止先 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 be491017..f25880ef 100644 --- a/skills/multi/dingtalk-doc/references/url-patterns.md +++ b/skills/multi/dingtalk-doc/references/url-patterns.md @@ -1,8 +1,21 @@ # URL 格式与处理规范 -## alidocs URL 分流决策(必须首先执行) +## 路由第 0 步:意图直达(优先级高于 URL 探测) -收到 `alidocs.dingtalk.com` URL 时,**必须按以下顺序判断,禁止跳过**: +用户已经明确表达某产品的内容意图时,直接进入对应产品场域,不要先做 URL 类型 +探测。尤其: + +- 明确提到 Markdown / `.md` 文件的读取或修改,按普通文件走 `drive` 场域: + 先 `dws drive download` 下载到本地处理,再用 `dws drive upload` 回传。 +- 明确“读这篇文档 / 编辑文档正文”进入 `doc`;明确“看这个在线表格数据”进入 + `sheet`。 + +仅当用户只粘贴 URL、没有明确产品意图,或意图与链接类型可能冲突时,才执行下方 +类型探测。 + +## alidocs URL 分流决策(意图不明确时执行) + +收到 `alidocs.dingtalk.com` URL 且无法从指令判断产品时,必须按以下顺序判断: 1. URL 路径含 `/i/p/` → **分享短链**,禁止调用 `dws doc` 任何子命令 → 按下方 [分享短链处理](#分享短链处理) 执行 2. URL 路径含 `/i/nodes/` → **节点链接**,需探测类型 → 按下方 [alidocs URL 类型探测流程](#alidocs-url-类型探测流程) 执行 @@ -80,20 +93,23 @@ dws doc read --node "https://alidocs.dingtalk.com/i/p/Y7kmbokZp3pgGLq2/docs/AY39 ### 探测步骤 ``` -Step 1 → dws doc info --node "" --format json -Step 2 → 从返回中提取 contentType、extension、nodeType 字段 +Step 1 → dws drive info --node "" --format json +Step 2 → 从返回中提取 extension、nodeType 字段 Step 3 → 按下方路由规则映射到对应产品 ``` +> 路由依据是 `extension`,不是 `contentType`。`drive info` 检测到 +> `adoc` / `axls` / `able` 时会自动补充在线文档信息。 + ### 路由映射表 | 条件 | 路由到产品 | 后续操作 | |------|-----------|---------| -| `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` 下载,返回文件下载链接 | +| `extension=adoc` | `doc` | 加载 `dingtalk-doc` 操作内容 | +| `extension=axls` | `sheet` | 加载 `dingtalk-misc` 的 `references/sheet.md` 操作(仅 `axls` 在线电子表格) | +| `extension=able` | `aitable` | 将 nodeId 作为 baseId,加载 `dingtalk-aitable` 操作 | +| `extension=xlsx` / `xls` / `xlsm` / `csv` | `drive` | 必须用 `dws drive download` 下载到本地处理,禁止走 `sheet` | +| `nodeType=file`(非在线文档扩展名,含 `md`) | `drive` | 下载用 `dws drive download --node --output --format json`;上传/覆盖用 `dws drive upload` | | `nodeType=folder` | `drive` / `wiki` | 调用 `dws drive list --workspace ` 或 `dws wiki node list` 列出子节点 | | 以上均不匹配 | — | 告知用户当前暂不支持该类型 | @@ -106,16 +122,16 @@ Step 3 → 按下方路由规则映射到对应产品 ```bash # 用户传入: https://alidocs.dingtalk.com/i/nodes/abc123 -dws doc info --node "https://alidocs.dingtalk.com/i/nodes/abc123" --format json +dws drive info --node "https://alidocs.dingtalk.com/i/nodes/abc123" --format json -# 返回 contentType=ALIDOC, extension=axls → 在线电子表格,路由到 sheet +# 返回 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" +# 返回 extension=xlsx/xls/csv → 本地表格文件,必须下载处理(禁止走 sheet) +dws drive download --node "https://alidocs.dingtalk.com/i/nodes/xlsx456" --output --format json -# 返回 contentType≠ALIDOC, nodeType=file → 普通文件,下载 -dws drive download --node "https://alidocs.dingtalk.com/i/nodes/def456" +# 返回 nodeType=file → 普通文件,下载 +dws drive download --node "https://alidocs.dingtalk.com/i/nodes/def456" --output --format json # 返回 nodeType=folder → 文件夹,列出子节点 dws drive list --workspace --format json @@ -126,40 +142,3 @@ dws drive list --workspace --format json 当用户指令中已明确指定产品(如"帮我读这个文档"、"看下这个表格的数据"),可结合用户意图**跳过探测**直接路由。仅在以下情况**必须执行探测**: - 用户只粘贴 URL,无其他上下文 - 用户指令与 URL 实际类型可能不一致(如说"文档"但实际是表格) -- 用户直接粘贴的是原始 `alidocs` URL,且没有上游命令返回来确认类型 - ---- - -## alidocs URL probe 后能力矩阵 - -> 给 Agent 在用户问"那能不能 XXX"时使用——一眼看出该节点类型支持哪些操作。 -> 标 ⚠️ 的项是当前 dws-opensource 用 transitional helper 实现(feat/align-yuyuan 分支),mse 端 toolOverride 落地后转为动态生成。 - -| extension / contentType | 读取 | 写入 | 删除 | 导出 | 权限 | 媒体 | -|-------------------------|------|------|------|------|------|------| -| **adoc**(在线文档) | `doc read` | `doc update` / `doc block update` | ⚠️ `doc delete` | ⚠️ `doc export` (→ docx) | ⚠️ `doc permission *` | ⚠️ `doc media download/insert` | -| **axls**(在线电子表格) | `sheet range read` / `sheet list` | `sheet range update` / `sheet append` | ⚠️ `doc delete`(节点删除) | `sheet export`(单命令一站式:提交→轮询→下载,可选 `--output` 落盘) | ⚠️ `doc permission *`(节点级,跨产品) | 不适用 | -| **able**(在线多维表) | `aitable base get` / `aitable record query` | `aitable record create/update` | ⚠️ `doc delete`(节点删除)或 `aitable base delete --yes` | `aitable export data --scope all --export-format excel --format json`(取 downloadUrl,`--output` 不落盘) | ⚠️ `doc permission *`(节点级) | `aitable attachment upload` | -| **xlsx / xls / xlsm / csv**(本地表格文件) | `drive download` → 本地用 xlsx skill 解析 | 不支持服务端写(先下载改本地再上传) | ⚠️ `doc delete`(节点删除) | 不需要(本身就是 xlsx) | ⚠️ `doc permission *` | 不适用 | -| **普通文件** (nodeType=file) | `drive download` | 不支持服务端写 | ⚠️ `doc delete` | 不需要 | ⚠️ `doc permission *` | 不适用 | -| **文件夹** (nodeType=folder) | `doc list --folder ` | `doc create --folder ...` | ⚠️ `doc delete` | 不适用 | ⚠️ `doc permission *` | 不适用 | -| **分享短链** `/i/p/` | `read_url` 兜底(外部工具) | 不适用 | 不适用 | 不适用 | 不适用 | 不适用 | - -### 使用方式 - -``` -Agent 流程: - 1. 用户给 URL → dws doc info --node (路由起点) - 2. 拿到 extension / contentType / nodeType - 3. 在本矩阵查"能做什么 / 不能做什么" - 4. 不能做的直接告知用户(参考 capability-limits.md),不要重试 -``` - -### 跨产品授权的关键判断 - -| 用户说 | 路由 | 不要混淆 | -|--------|------|---------| -| "把这个文档/表格/多维表分享给张三" | **节点级**:`doc permission add --node --users --role EDITOR` | 不是 `wiki member add` | -| "把张三加到这个知识库" | **容器级**:`wiki member add --workspace --user --role ` | 不是 `doc permission add` | - -> 区分依据:**doc permission 作用于单个 node(document / file / folder);wiki member 作用于整个 workspace 容器**。同一用户在 workspace 是 EDITOR、在某个 node 上仍可被单独提升为 MANAGER(节点级覆盖容器级)。 diff --git a/skills/multi/dingtalk-drive/SKILL.md b/skills/multi/dingtalk-drive/SKILL.md index a14f1579..d23c99c6 100644 --- a/skills/multi/dingtalk-drive/SKILL.md +++ b/skills/multi/dingtalk-drive/SKILL.md @@ -1,10 +1,9 @@ --- name: dingtalk-drive description: 钉钉文件管理(存储层)。Use when 用户说 钉盘/上传文件/下载文件/文件夹/查文件/找文件/全局搜索文件/复制/移动/重命名/删除/回收站/还原删除文件/权限管理/普通文件下载。任何文件类型都适用;文档内容编辑走 dingtalk-doc,知识库空间和空间内节点管理走 dingtalk-wiki。命令前缀:dws drive。 -cli_version: ">=0.2.14" metadata: + cli_version: ">=0.2.14" category: product - stability: experimental requires: bins: - dws @@ -12,20 +11,16 @@ metadata: # 钉盘 Skill -> 🧪 **EXPERIMENTAL · 试验版 / Preview** — multi 模式当前未达 stable 标准。全部 dingtalk-* skill 已通过 dispatch verifier,但接口、命名、跨 skill 引用后续可能调整;生产 / 共享环境请优先使用 mono 模式(`dws skill setup --mode mono`)。问题请提 issue 反馈。 - ## 前置条件 — 执行操作前必读 -> **`use_skill(dws-shared)`** — 认证、全局参数(`--format json` / `--yes`)、错误码、URL 模板、跨产品消歧、安全规则与 capability 边界。**执行任何 `dws` 命令前先读;** 单产品的清晰命令可直接用本 skill。 - - +> **CRITICAL — 执行任何 `dws` 操作前,MUST 先用 Read 工具完整读取 [`dws-shared`](../dws-shared/SKILL.md)。**该轻量文件包含全局执行契约、安全底线及 shared references 的按需加载导航;不要预加载其全部 references。 > 命令参考:[drive.md](references/drive.md)。 ## Shortcuts(无专用脚本/recipe 时优先) -以下 shortcut 来自独立于 Runtime Schema 的公开 catalog。先按本 skill 的意图表、脚本和 recipe 路由:存在精确覆盖该场景的专用脚本/recipe 时按其执行;否则用户意图命中时,shortcut 优先于手写原子命令。用 `dws shortcut list --service drive --format json` 读取参数、约束、风险和示例,并以 `dws drive --help` 核对当前 Cobra flags;不要对 `+` 路径调用 `dws schema`。 +以下命令来自独立于 Runtime Schema 的公开 catalog。先运行 `dws shortcut list --service drive --format json` 读取完整契约,再用 `dws drive --help` 核对 flags;不要对 `+` 路径调用 `dws schema`。 | Shortcut | 风险 | 适用场景 | |---|---|---| @@ -44,17 +39,13 @@ metadata: |--------|------| | "看钉盘文件 / 文件夹列表" | `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 info --node ` | | "搜文件 / 找文件" | `dws drive search --query "<关键词>"` | -| "下载文件" | `dws drive download --node --output ` | -| "上传本地文件" | `dws drive upload --file ./report.pdf [--folder ]` | -| "建文件夹" | `dws drive mkdir --name "<名称>" [--folder ]` | -| "复制 / 移动 / 重命名" | `dws drive copy` / `move` / `rename --node --name "<主名>"` | -| "删除文件 / 移到回收站(需确认)" | `dws drive delete --node --yes` | -| "回收站 / 还原" | `dws drive recycle list` / `recycle restore --id ` | -| "公开 / 取消公开 / 查公开状态" | `dws drive publish set` / `unset` / `get --node ` | +| "下载文件" | `dws drive download --node --output ` | +| "上传文件" | `dws drive upload --file [--folder ]` | +| "建钉盘文件夹" | `dws drive mkdir --name "<名称>" [--folder ]` | +| "复制/移动/重命名/删除/权限管理" | `dws drive copy/move/rename/delete/permission ...` | +| "回收站 / 还原删除的文件" | `dws drive recycle list` / `dws drive recycle restore --id ` | ## 标准 SOP(必遵流程) @@ -65,27 +56,28 @@ metadata: **触发**:找文件/搜文件/我的文件/最近文件/某文档在哪。 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 普通文件)。 +2. **解析(必须)**:取真实 `dentryUuid`(= `id`/`nodeId`);多候选让用户确认,**禁止**默认取第一个。 +3. **下钻(必须)**:根目录没命中时,进入最相关文件夹继续 `drive list --folder`,必要时 `python scripts/drive_tree_list.py --depth 2` 递归,**禁止**只看根目录就放弃。 +4. **回读元数据(必须)**:命中后 `dws drive info --node --format json`,按 `extension` 确认类型。 **禁止**:编造 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 <本地路径>`。 +1. **上传(必须)**:`dws drive upload --file <本地路径> [--folder ] --format json`;返回取 `dentryUuid`,用 `drive info --node` 回读确认。 +2. **覆盖(必须)**:当前 CLI 不支持原地覆盖已有文件。需要更新内容时,先 `dws drive info --node --format json` 确认类型:普通文件(含 `.md`)在用户确认后先 `dws drive delete --node --yes --format json` 删除旧文件(进回收站),再 `dws drive upload --file <本地路径> --folder <父目录dentryUuid> --file-name "<原name>" --format json` 上传新文件;`adoc` / `axls` / `able` 切对应内容 skill/reference,不按普通文件覆盖。 +3. **下载(必须)**:先 `dws drive info --node --format json` 判断类型——`extension=adoc` 切 `dingtalk-doc` 用 `doc export`;普通文件执行 `dws drive download --node --output <本地路径> --format json`。 -**禁止**:对在线文档用 `drive download`(会失败)、上传后不回读。 +**禁止**:对在线文档用 `drive download`(会失败)、普通文件覆盖时省略 `--file-name` 导致隐式重命名、上传或覆盖后不回读。 ### 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>` 回读。 +1. **执行(必须)**:建钉盘文件夹 `dws drive mkdir --name "<名称>" [--folder ]`;复制 `drive copy --node --folder <目标>`;移动 `drive move --node --folder <目标>`;重命名 `drive rename --node --name "<新名>"`。全部加 `--format json`。 +2. **验证(必须)**:操作后 `drive info --node <新dentryUuid>` 或 `drive list --folder <目标>` 回读。 **禁止**:未确认就移动/覆盖他人文件、跳过回读。 @@ -93,7 +85,7 @@ metadata: **触发**:删文件/回收站/还原。 -1. **删除(必须)**:`dws drive delete --node --yes --format json`(**必须**先与用户确认)。 +1. **删除(必须)**:`dws drive delete --node --format json`(**必须**先与用户确认)。 2. **还原(必须)**:`dws drive recycle list --format json` 取 `recycleItemId` → `dws drive recycle restore --id --format json`。 **禁止**:未确认就删除、把 `dentryUuid` 当 `recycleItemId` 传给 restore。 @@ -102,19 +94,16 @@ metadata: **触发**:互联网公开/取消公开/查公开状态。 -1. **执行(必须)**:查状态 `dws drive publish get --node --format json`;开启公开 `dws drive publish set --node --yes`(**[危险]** 必须用户确认);关闭公开 `dws drive publish unset --node --yes`。 +1. **执行(必须)**:查状态 `dws drive publish get --node --format json`;开启公开 `dws drive publish set --node --yes`(**[危险]** 必须用户确认);关闭公开 `dws drive publish unset --node --yes`。 2. **边界(必须)**:对外公开前**必须**与用户确认边界与后果。 **禁止**:未确认就 `publish set`、跳过 `--yes`。 ## 高频硬约束 -- **ID 字段选择**:`drive list` 返回同时有 `dentryId`(纯数字)和 `fileId`(UUID 格式)。所有 `--node` 和 `--folder` 参数**必须用 `fileId`**,纯数字 `dentryId` 会被拒绝。 -- 查找文件不要只看根目录后放弃;根目录没命中时,进入最相关的目标文件夹继续 `drive list --folder `,必要时用目录树脚本递归到合理深度。 +- 查找文件不要只看根目录后放弃;根目录没命中时,进入最相关的目标文件夹继续 `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` 是只读操作。 +- 全局找文件优先 `drive search --query`;指定目录浏览用 `drive list`,命中后必须 `drive info --node --format json` 回读元数据。 - 删除、覆盖、移动等破坏性操作必须确认;上传、创建文件夹、下载后要读回或列目录验证。 - 所有 `dws drive` 命令加 `--format json`。 @@ -122,6 +111,6 @@ metadata: - 文件内容编辑(钉钉文档)→ 切到 `dingtalk-doc` - 知识库空间 → 切到 `dingtalk-wiki` -## 局部意图与 Recipe +## 局部意图与短流程 -- [局部意图消歧](references/intent-guide.md);[Lite Recipe](references/lite-recipes.md)。 +- [局部意图消歧](references/intent-guide.md);[短流程](references/lite-recipes.md)。 diff --git a/skills/multi/dingtalk-drive/references/drive-index.md b/skills/multi/dingtalk-drive/references/drive-index.md deleted file mode 100644 index 758d24ef..00000000 --- a/skills/multi/dingtalk-drive/references/drive-index.md +++ /dev/null @@ -1,5 +0,0 @@ -# 钉盘能力索引 - -| 主题 | 详见 | -|---|---| -| 文件上传/下载/复制/移动/删除/权限 | [`drive.md`](./drive.md) | diff --git a/skills/multi/dingtalk-drive/references/drive.md b/skills/multi/dingtalk-drive/references/drive.md index 634d5bca..49194b5f 100644 --- a/skills/multi/dingtalk-drive/references/drive.md +++ b/skills/multi/dingtalk-drive/references/drive.md @@ -13,8 +13,6 @@ dws drive list --help dws drive search --help dws drive upload --help dws drive download --help -dws drive stats --help -dws drive shortcut --help ``` 规则: @@ -68,9 +66,9 @@ spaceType 筛选规则: - `spaceType` — 空间类型(如 `orgSpace`) - `nextToken` — 若不为空,表示还有更多空间可查询(仅企业空间) -### 搜索钉盘文件/文件夹/空间 +### 搜索文件/文件夹/空间 -按关键词在钉盘中搜索文件、文件夹或团队空间。不同于 `list`(需要明确的 spaceId/parentId 逐层遍历),`search` 用于不知道具体位置、只记得名称/关键词的场景。 +按关键词搜索文件、文件夹或团队空间。不同于 `list`(需要明确的 spaceId/parentId 逐层遍历),`search` 用于不知道具体位置、只记得名称/关键词的场景。 ``` Usage: @@ -149,38 +147,17 @@ Flags: --space-id string 节点所属空间 ID (可选) ``` -### 获取节点统计信息 - -```text -Usage: - dws drive stats --node -``` - -返回节点可用的阅读、编辑、评论、点赞、预览或下载等统计维度;不同文件类型返回字段可能不同。本命令只读。 - -### 创建节点快捷方式 - -```text -Usage: - dws drive shortcut --node [--folder ] [--workspace ] -Example: - dws drive shortcut --node - dws drive shortcut --node --folder - dws drive shortcut --node --workspace -``` - -`--folder` 和 `--workspace` 均可省略,此时由服务端选择默认位置。创建后应通过 `drive list` 回读目标位置。 - ### 文件内容获取路由规则 -> 当用户请求"分析/查看/读取某个钉盘文件内容"时,**必须先调用 `dws drive info` 获取文件元数据**,再根据返回的 `extension` 字段选择对应链路。 +> 当用户请求"分析/查看/读取某个文件内容"时,**必须先调用 `dws drive info` 获取文件元数据**,再根据返回的 `extension` 字段选择对应链路。 > 注意:若检测到钉钉文档类型(adoc/axls/amind/adraw),会自动跟进调用 `doc info` 返回更准确的文档信息。 | extension | 文件类型 | 操作 | 命令 | |-----------|---------|------|------| | 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 ` | +| axls | 在线表格 | 在线读取表格数据 | `dws sheet list --node ` → `dws sheet range read --node --sheet-id --range ` | +| able | 多维表格 | 在线查询记录 | `dws aitable table list --base-id ` → `dws aitable record query --base-id --table-id ` | +| md | Markdown 文件 | 按普通文件下载后本地读取 | `dws drive download` | | 其他(pdf/docx/txt/png 等) | 普通文件 | **不支持在线分析**,需用户主动下载后本地查看 | `dws drive download` | ### 下载文件到本地 @@ -195,11 +172,11 @@ Example: dws drive download --node --output ~/downloads/ Flags: --node string 文件 ID (dentryUuid) (必填) - --output string 本地保存路径 (必填),可以是文件路径或目录;如果指定目录,文件名从下载 URL 中自动推断 + --output string 本地保存路径 (文件路径或目录,不传则保存到当前目录);如果指定目录,文件名从下载 URL 中自动推断 (可选) --space-id string 文件所属空间 ID (可选) ``` -> **注意**:`--output` 是必填参数,不传会报错。 +> **提示**:`--output` 为可选参数,不传则保存到当前目录,文件名从下载 URL 中自动推断。 ### 创建文件夹 @@ -226,16 +203,23 @@ Example: dws drive upload --file ./report.pdf dws drive upload --file ./slides.pptx --file-name "Q1汇报.pptx" dws drive upload --file ./data.xlsx --folder + dws drive upload --file ./updated.pdf --node --file-name "<原文件名.pdf>" Flags: --file string 本地文件路径 (必填) --file-name string 文件显示名称 (默认使用文件名) --space-id string 目标空间 ID,不传则使用「我的文件」 (可选) --mime-type string 文件 MIME 类型,不传则自动推断 (可选) - --folder string 父节点 ID (dentryUuid),不传则上传到空间根目录 (可选) + --folder string 父节点 ID (dentryUuid),不传则上传到空间根目录 (可选,与 --node 互斥) + --node string 覆盖目标文件 ID,传入即覆盖已有文件(透明模式:钉盘路径映射 overwriteFileId,知识库路径映射 overwriteNodeId)(可选,与 --folder 互斥) ``` `upload` 命令内部自动完成三步流程(获取凭证 → OSS PUT → 提交入库),无需手动分步操作。 +> **覆盖保名规则(实测)**:使用 `--node` 覆盖普通文件前,先执行 +> `dws drive info --node --format json` 记录原 `name`,再把该值原样传给 +> `--file-name`。省略 `--file-name` 会采用本地文件名,并同时重命名远端目标。 +> `adoc` / `axls` / `able` 切对应内容产品,不按普通文件覆盖。 + ### 删除文件/文件夹到回收站 > **CAUTION:** 不可逆操作 — 执行前必须向用户确认。 @@ -278,6 +262,62 @@ Flags: > **注意**:还原操作可能是异步的(返回 `async=true` 和 `taskId`)。 +### 收藏文档 + +``` +Usage: + dws drive star add [flags] +Example: + dws drive star add --node +Flags: + --node string 文档 ID 或 URL (必填) +``` + +### 取消收藏文档 + +``` +Usage: + dws drive star remove [flags] +Example: + dws drive star remove --node +Flags: + --node string 文档 ID 或 URL (必填) +``` + +### 获取收藏列表 + +``` +Usage: + dws drive star list [flags] +Example: + dws drive star list + dws drive star list --content-types doc,sheet + dws drive star list --resource-types DENTRY --limit 10 + dws drive star list --order-by createTime --sort desc --cursor +Flags: + --limit int 每页条数 (默认 20,最大 20) + --cursor string 分页游标,从上次返回的 nextCursor 获取 + --order-by string 排序字段: createTime (可选) + --sort string 排序方向: asc|desc (可选,默认 desc) + --resource-types strings 资源大类筛选,逗号分隔: DENTRY, TEAM, WORKSPACE + --content-types strings 内容类型筛选,逗号分隔: doc,sheet,ppt,whiteboard,mind,notable,pdf,other,folder +``` + +返回字段说明: +- `starList[]` — 收藏项数组,每项包含: + - `resourceType` — 资源类型: DENTRY / TEAM / WORKSPACE + - `nodeId` — 文档节点 ID(仅 DENTRY) + - `name` — 资源名称 + - `url` — 资源访问链接 + - `contentType` — 内容类型: ALIDOC / DOCUMENT / IMAGE / VIDEO / AUDIO / ARCHIVE / OTHER(仅 DENTRY) + - `extension` — 文件扩展名(仅 DENTRY) + - `dentryType` — 节点类型: file / folder(仅 DENTRY) + - `starTime` — 收藏时间(RFC 3339 格式) + - `id` — 资源唯一标识(仅 TEAM / WORKSPACE) + - `spaceType` — 知识库类型: 1=知识库, 2=我的文档(仅 WORKSPACE) +- `hasMore` — 是否有更多数据 +- `nextCursor` — 下次分页游标 + ## 意图判断 用户说"我的文件/钉盘/网盘/云盘" → `list` @@ -285,32 +325,45 @@ Flags: 用户说"钉盘空间/团队文件/有哪些空间/空间列表/团队文件列表" → `list-spaces` 用户说"搜索钉盘文件/钉盘里找个文件/查找某个钉盘文件/钉盘中搜索" → `search` 用户说"文件详情/文件信息" → `info` -用户说"文件阅读量/编辑量/评论数/下载数/节点统计" → `stats` -用户说"给文件创建快捷方式/放一个链接到目标文件夹" → `shortcut` -用户说"下载文件" → `download` 指定 `--output` 保存到本地 -用户说"新建文件夹/创建目录" → `mkdir`(钉盘空间)/ `wiki node create --type folder`(文档空间) -用户说"上传文件/传文件到钉盘" → `upload`(首选此命令,自动完成三步流程) +用户说"下载文件" → `download` +用户说"新建文件夹/创建目录" → `mkdir`(钉盘空间)/ `folder create`(文档空间) +用户说"上传文件/传文件到钉盘" → `upload`(必须使用此命令,自动完成三步流程) 用户说"复制文件/移动文件/搬到/移到" → `copy` / `move` +用户说"创建快捷方式/创建链接/放个快捷方式/引用到其他位置" → `shortcut` +用户说"阅读量/查看次数/多少人看过/点赞数/评论数/下载次数/文档统计/数据统计" → `stats` +用户说"封面/封面图/缩略图/预览图/节点封面" → `cover` +用户说"历史版本/版本列表/文件版本/有哪些版本/版本记录" → `list --versions --node ` +用户说"下载历史版本/下载旧版本/恢复历史版本到本地" → `download --version ` +用户说"回滚版本/恢复到某个版本/还原到旧版本/版本回退" → `revert`(危险操作,需确认) 用户说"重命名/改名" → `rename` 用户说"删除文件/删除文件夹/移到回收站" → `delete`(危险操作,需确认) 用户说"回收站/查看回收站/回收站列表/回收站里有什么" → `recycle list` 用户说"恢复文件/还原删除的文件/从回收站恢复/还原回收站文件" → `recycle restore` +用户说"收藏文档/收藏这个文件/加个收藏/标星" → `star add` +用户说"取消收藏/去掉收藏/不收藏了" → `star remove` +用户说"我的收藏/收藏列表/收藏了哪些文档/看看收藏" → `star list` 用户说"给文档授权/分享权限" → `permission add` +用户说"打不开/没权限/申请权限/我要编辑权限/找谁审批/申请访问权限" → 先 `permission apply-info` 查可申请角色与审批人 → 再 `permission apply` 发起申请 用户说"公开文件/互联网公开/设置公开/让互联网所有人可访问" → `publish set` 用户说"关闭公开/取消公开/取消互联网访问" → `publish unset` 用户说"查看公开状态/是否公开/发布状态" → `publish get` +用户说"查任务状态/导出好了没/任务进度/导入状态" → 文档导出用 `dws doc export get --job-id `,导入用 `dws doc import get --task-id ` 关键区分: drive(文件管理) vs doc(文档内容读写) vs wiki(空间管理) **drive search vs wiki node search**: 用户提到"钉盘/网盘/我的文件里搜" → `drive search`;提到"知识库/文档空间/workspace 里搜" → `wiki node search`;未明确目标时优先问明。 -**drive upload**: 文件上传统一走 `drive upload`。上传到知识库/文档空间时加 `--workspace` 参数。 +**drive upload**: 新文件上传统一走 `drive upload`。覆盖普通文件时先 `drive info` 取原 `name`,再用 `drive upload --node --file --file-name "<原name>" --format json`;省略 `--file-name` 会隐式重命名。上传到知识库/文档空间时加 `--workspace` 参数。 **drive permission vs wiki member**: "给某篇文档/文件授权" → `drive permission add`(节点级);"给某个知识库整体加成员" → `wiki member add`(空间级) **创建在线文档/表格/脑图**: drive 不支持创建文件,需走 `wiki node create --type `(创建空节点)或 `doc create`(创建并写入内容)。 -**导出文档/导出为Word**: 导出是内容层操作,走 `doc export`,不属于 drive。 +**导出文档/导出为Word**: 导出是内容层操作,走 `doc export`,不属于 drive。超时后的状态查询用 `dws doc export get --job-id `。 + +**版本管理**: 当前 CLI 仅支持钉钉在线文档(adoc)的版本管理(`dws doc version list/save/revert`);普通文件的历史版本列出/下载/回滚暂不支持。 + +**.md 文件的内容操作路由**: 当 `drive info` 返回 `extension=md` 时,按普通文件处理——用 `dws drive download --node --output --format json` 下载后本地读取;修改后用 `dws drive upload` 回传。 ## 核心工作流 @@ -341,8 +394,37 @@ dws drive delete --node --yes --format json # 8. 查看回收站并还原文件 dws drive recycle list --format json dws drive recycle restore --id --format json + +# 9. 收藏 / 取消收藏 / 查看收藏列表 +dws drive star add --node --format json +dws drive star remove --node --format json +dws drive star list --format json +dws drive star list --content-types doc,sheet --limit 10 --format json ``` +## 无权限引导申请工作流 + +当 Agent 执行文档操作(如 `doc read` / `drive info` / `drive download`)遇到权限拒绝错误时,按以下流程引导用户申请权限: + +> **CAUTION(红线):`apply` 会真实通知审批人,属于有副作用操作。Agent 严禁自行提交申请。必须先向用户逐项回显确认「资源 / 角色 / 审批人 / 理由」,得到用户明确同意后,才能执行 `apply`。用户未明确同意前,只能停在确认环节,不得调用 `apply`。** + +``` +1. 识别错误 → 询问用户「该文档你暂无权限,是否申请?」 +2. 用户确认申请 → 查可申请角色与审批人: + dws drive permission apply-info --node <节点ID或URL> --format json +3. 向用户展示 availableRoles / approvers,询问「申请哪个角色?发给哪位审批人?」 +4. 用户选择 role + 审批人(可选填 reason) +5. Agent 回显确认「资源 / 角色 / 审批人 / 理由」→ 等待用户明确同意(这是提交前的强制关卡,不可跳过) +6. 仅在用户明确同意后,才发起申请: + dws drive permission apply --node <节点ID或URL> --role --users <审批人userId> [--reason "..."] +7. 展示 applyResult / applyResultDesc +``` + +要点: +- `apply-info` 的 `availableRoles[].roleId` → `apply --role`;`approvers[].userId` → `apply --users` +- **`apply` 严禁自动/静默调用**:必须由用户在看到确认信息后明确同意(如回复"确认/同意/提交")才可执行;Agent 不得替用户决定或跳过确认 +- `applyResult` 含义:NORMAL(已提交)/HAS_PERMISSION_ALREADY(已拥有)/APPLYING(审批中)/APPLYING_BUT_REAPPLY(已重新提交) + ## 文档空间管理命令 > 以下命令操作的是**文档空间**(知识库 / 我的文档),底层路由到 doc MCP server。 @@ -364,6 +446,124 @@ Flags: > **字段选择**:`drive list` 返回中有 `dentryId`(数字格式)和 `fileId`(UUID 格式),**必须使用 `fileId`(UUID 格式)**作为 `--node` 和 `--folder` 参数值。 +### 获取节点统计信息 + +获取指定节点的统计数据(阅读人数、阅读次数、编辑次数、评论数、点赞数、预览次数、下载次数等)。 + +``` +Usage: + dws drive stats [flags] +Example: + dws drive stats --node + dws drive stats --node https://alidocs.dingtalk.com/i/nodes/ +Flags: + --node string 节点 ID (dentryUuid) 或文档 URL (必填) +``` + +> **统计维度因文件类型而异**: +> - 钉钉在线文档(adoc):阅读人数、阅读次数、编辑次数、评论数、点赞数、预览次数等 +> - 普通文件:仅阅读次数、下载次数 +> - 点赞数(likeCount)仅在线文档(adoc)有效 + +### 获取节点封面地址 + +获取指定节点的封面图片地址。 + +``` +Usage: + dws drive cover [flags] +Example: + dws drive cover --node + dws drive cover --node https://alidocs.dingtalk.com/i/nodes/ +Flags: + --node string 节点 ID (dentryUuid) 或文档 URL (必填) +``` + +> **封面因文件类型而异**: +> - 钉钉在线文档(adoc):文档首图/预览封面 +> - 图片文件:图片缩略图地址 +> - 其他文件:文件类型图标地址(如有) + +### 文件历史版本管理 + +获取文件历史版本列表、下载历史版本。 + +> **适用范围**:仅适用于普通文件(如 pdf、docx、xlsx、png 等)。 +> - 钉钉在线文档(adoc)请使用 `dws doc version list` +> - 钉钉在线表格(axls)请使用 `dws sheet version list` +> - 命令会自动检测文件类型,若为在线文档会提示使用对应服务命令 + +#### 获取文件历史版本列表 + +``` +Usage: + dws drive list --versions [flags] +Example: + dws drive list --node --versions --format json + dws drive list --node --versions --limit 20 --format json +Flags: + --node string 文件 ID (dentryUuid) 或 URL (必填) + --limit int 每页最大数量 (默认 20,最大 50) + --cursor string 分页游标 (从上次返回结果获取,首次不传) +``` + +#### 下载文件历史版本 + +``` +Usage: + dws drive download [flags] +Example: + dws drive download --node --version 3 --output ./report_v1.pdf --format json + dws drive download --node --version 3 --output --format json +Flags: + --node string 文件 ID (dentryUuid) 或 URL (必填) + --version int 历史版本号 (必填,正整数,从 list --versions 获取) + --output string 本地保存路径 (文件路径或目录,不传则保存到当前目录) (可选) +``` + +> **两步下载流程**:先调用 MCP 工具获取下载 URL 和签名头,再 HTTP GET 下载文件内容到本地。 +> 不传 `--output` 或指定目录时,优先从文件信息中获取原始文件名,获取不到时从下载 URL 推断;均不可用时使用默认名 `download.<扩展名>`。 + +#### 回滚文件到指定历史版本 + +> **CAUTION:** 危险操作 — 执行前必须向用户确认。 + +``` +Usage: + dws drive revert [flags] +Example: + dws drive revert --node --version 3 --yes --format json +Flags: + --node string 文件 ID (dentryUuid) 或 URL (必填) + --version int 要回滚到的历史版本号 (必填,正整数) + --yes 跳过确认提示 +``` + +回滚成功后,系统会基于目标版本生成一份新的最新版本(内容与目标版本一致),原有历史版本不会丢失。 +仅支持普通文件(Word、Excel、PDF、图片等)的历史版本回滚。在线文档的版本回滚请用 `dws doc version revert`,在线表格请用 `dws sheet version revert`。 +需要当前用户对该文件具备编辑权限。 + +### 创建快捷方式 + +为指定的源节点创建快捷方式(链接),放置到目标位置。 + +``` +Usage: + dws drive shortcut [flags] +Example: + dws drive shortcut --node + dws drive shortcut --node --folder + dws drive shortcut --node --workspace +Flags: + --node string 源节点 ID (dentryUuid) 或文档 URL (必填) + --folder string 目标文件夹 nodeId,不传则放到知识库/我的文档根目录 (可选) + --workspace string 目标知识库 ID (可选) +``` + +> **目标位置规则**:`--folder` 和 `--workspace` 都不传时默认放置到「我的文档」根目录;同时传入时以 `--folder` 为准,`--workspace` 仅用于一致性校验。 +> **权限要求**:对源节点有“可查看”权限,对目标文件夹有“编辑”权限。 +> **目标位置限制**:若目标位置不支持在其下创建子节点(如快捷方式节点、非文件夹),会返回错误,请改指定一个文件夹作为目标位置。 + ### 创建文件夹(文档空间) ``` @@ -377,22 +577,91 @@ Flags: ### 权限管理(文档节点级) -> 仅适用于文档空间节点,不适用于钉盘文件。 - ``` Usage: dws drive permission add --node --users uid1,uid2 --role READER dws drive permission update --node --users uid1 --role EDITOR dws drive permission list --node dws drive permission remove --node --users uid1 + dws drive permission transfer-owner --node --new-owner + dws drive permission transfer-owner --workspace --new-owner + dws drive permission transfer-owner --node --new-owner --reserve-role EDITOR --recursive=false --yes Flags: --node string 目标节点 ID 或 URL (必填) --users string 用户 userId 列表,逗号分隔 --role string 角色: MANAGER / EDITOR / DOWNLOADER / READER --limit int 返回成员数上限 (仅 list,默认 30,最大 200) --filter-role string 按角色过滤 (仅 list) + --workspace string 目标知识库 ID 或 URL (仅 transfer-owner,与 --node 二选一) + --new-owner string 新所有者的用户 userId (仅 transfer-owner,必填) + --reserve-role string 转交后原所有者保留角色: MANAGER / EDITOR / DOWNLOADER / READER / NONE(移除权限) (仅 transfer-owner) + --recursive bool 是否递归变更所有子节点的所有者 (仅 transfer-owner) ``` +> **`transfer-owner` 为 [危险] 操作,执行前需要确认。** +> +> - 交互模式下会依次提示选择原所有者保留角色、是否递归变更子节点,然后显示操作摘要等待确认 +> - 使用 `--yes` 跳过确认时,`--reserve-role` 和 `--recursive` 必须显式指定(禁止静默默认值) +> - `--reserve-role NONE` 表示移除原所有者的所有权限 +> - 当前用户需为该节点/知识库的所有者才能执行转交 + +> ** Agent 行为约束(必须遵守)** +> +> `--reserve-role` 和 `--recursive` 属于高风险操作的核心决策参数,**必须由用户明确指定**,Agent 不得自行选择默认值(如 `NONE` / `false`)代替用户决策。 +> +> 执行 `transfer-owner` 前,Agent **必须**先通过对话向用户询问以下两个问题,只有在用户明确回答后才构造命令执行: +> +> 1. **"转交后原所有者保留什么角色?"** 选项: +> - `MANAGER`(管理者) +> - `EDITOR`(编辑者) +> - `DOWNLOADER`(下载者) +> - `READER`(只读者) +> - `NONE`(移除原所有者权限) +> 2. **"是否递归变更所有子节点的所有者?"** 选项: +> - `true`(是) +> - `false`(否) +> +> 正确流程:1.向用户询问上述两个问题 → 2.根据用户选择构造 `--reserve-role <用户选择> --recursive=<用户选择> --yes` → 3.执行命令 +> +> **禁止**在未获得用户明确回答的情况下直接执行命令。 + +### 权限申请(无权限时发起申请) + +> 当你对文档/知识库暂无访问权限时,可查询可申请的角色与审批人,并向审批人发起权限申请。 + +``` +Usage: + dws drive permission apply-info --node + dws drive permission apply --node --role --users <审批人userId...> +Example: + dws drive permission apply-info --node + dws drive permission apply --node --role READER --users uid1 + dws drive permission apply --node --role EDITOR --users uid1,uid2 --reason "需要编辑该文档" +Flags: + --node string 目标节点 ID 或 URL (必填) + --role string 申请的角色: EDITOR / DOWNLOADER / READER (仅 apply 必填) + --users string 审批人 userId 列表,逗号分隔 (仅 apply 必填) + --notify-mode string 通知方式: DEFAULT / MSG_ACCOUNT / SINGLE_CHAT (仅 apply 可选) + --reason string 申请理由,最长 200 字符 (仅 apply 可选) +``` + +子命令说明: +- `apply-info` — 查询节点可申请的角色列表 (availableRoles) 与审批人列表 (approvers) +- `apply` — 向审批人发起权限申请 + +返回字段说明: +- `apply-info`: + - `availableRoles[].roleId` — 可申请角色 (EDITOR/DOWNLOADER/READER),用作 `apply --role` + - `availableRoles[].name` / `desc` — 角色名称/描述 + - `approvers[].userId` — 审批人 userId,用作 `apply --users` + - `approvers[].userName` — 审批人名称 + - `approvers[].isResourceCreator` — 是否为资源创建者 +- `apply`: + - `applyResult` — 申请结果:NORMAL(已提交申请)/HAS_PERMISSION_ALREADY(已拥有该权限)/APPLYING(已有待审批申请)/APPLYING_BUT_REAPPLY(已重新提交申请) + - `applyResultDesc` — 结果的人类可读描述 + +> **重要(Agent 编排)**:`apply` 会向审批人发送通知,属副作用操作。Agent 必须在调用 `apply` 前,向用户回显「资源 / 申请角色 / 审批人 / 理由」并取得用户确认后再执行。 + ### 文件互联网公开发布 管理文件的互联网公开发布状态。公开后任何人通过链接即可访问,无需登录钉钉。操作者需要是文件的管理员或拥有者。 @@ -458,26 +727,27 @@ dws drive copy --node <源文件dentryUuid> --folder <目标文件夹fileId> --f ## 上下文传递表 -| 操作 | 从返回中提取 | 用于 | -|------|-------------|------| -| `list` | **`fileId`**(UUID 格式,注意:不是 `dentryId`) | info / stats / shortcut / download / delete 的 --node;list / mkdir 的 --folder;`drive copy/move/shortcut` 的 --node 或 --folder | -| `list` | `spaceId` | info / download / mkdir / upload 的 --space-id | -| `list` | `nextCursor` | 下次 list 的 --cursor | -| `list-spaces` / `wiki space list` | `rootFolderId` | `drive copy/move` 的 --folder(复制/移动到钉盘 space 根目录时) | -| `list-spaces` / `wiki space list` | `spaceId` | list / info / download / mkdir / upload 的 --space-id | -| `search` | **`fileId`**(文件/文件夹结果) | info / download / delete 的 --node;list 的 --folder | -| `search` | `spaceId` / `rootFolderId`(空间结果) | list 的 --space-id;`drive copy/move` 的 --folder | -| `search` | `nextCursor` | search 的 --cursor(翻页) | -| `mkdir` | `fileId`(UUID 格式) | list / upload 的 --folder | -| `upload` | `dentryUuid` | download / info 的 --node | -| `recycle list` | `id`(回收项 ID) | recycle restore 的 --id | -| `recycle list` | `name`(原始文件名) | 供用户确认还原目标 | -| `recent` | `recentItems[].nodeId` / `docUrl` | doc read / info / update / block 操作的 --node | -| `recent` | `nextCursor` | recent 的 --cursor(翻页) | +| 操作 | 从返回中提取 | 用于 | +| ------------- | ---------------------------- | -------------------------------------------------------- | +| `list` | **`fileId`**(UUID 格式,注意:不是 `dentryId`) | info / download / mkdir / delete / stats / shortcut / cover 的 --node 或 --folder;`drive copy/move` 的 --node 或 --folder | +| `list` | `spaceId` | info / download / mkdir / commit 的 --space-id | +| `list-spaces` | `rootFolderId` | `drive copy/move` 的 --folder(复制/移动到钉盘 space 根目录时) | +| `list-spaces` | `spaceId` | list / info / download / mkdir / upload 的 --space-id | +| `search` | **`fileId`**(文件/文件夹结果) | info / download / delete 的 --node;list 的 --folder | +| `search` | `spaceId` / `rootFolderId`(空间结果) | list 的 --space-id;`drive copy/move` 的 --folder | +| `search` | `nextCursor` | search 的 --cursor(翻页) | +| `mkdir` | `fileId`(UUID 格式) | list 的 --folder | +| `recycle list` | `id`(回收项 ID) | recycle restore 的 --id | +| `recycle list` | `name`(原始文件名) | 供用户确认还原目标 | +| `recent` | `recentItems[].nodeId` / `docUrl` | doc read / info / update / block 操作的 --node | +| `recent` | `nextCursor` | recent 的 --cursor(翻页) | +| `permission apply-info` | `availableRoles[].roleId` | `permission apply` 的 --role | +| `permission apply-info` | `approvers[].userId` | `permission apply` 的 --users | +| `star list` | `starList[].nodeId` | star remove 的 --node;info / download 的 --node | +| `star list` | `nextCursor` | star list 的 --cursor(翻页) | > **重要**:`drive list` 返回结果中同时包含 `dentryId` 和 `fileId` 两个字段。所有需要传 `--node` 的命令(info / download / delete)必须使用 `fileId`(即 dentryUuid),**不要使用** `dentryId`。 - ## 注意事项 - 不传 `--space-id` 时默认使用「我的文件」空间 @@ -487,19 +757,15 @@ dws drive copy --node <源文件dentryUuid> --folder <目标文件夹fileId> --f - `--order-by` 支持: `createTime`、`modifyTime`、`name` - **上传文件必须使用 `dws drive upload` 命令**,禁止使用 `upload-info` + `curl` + `commit` 三步手动流程 - `--file-name` 必须包含扩展名(如 `report.pdf`) -- `download` 需要指定 `--output`,CLI 会把文件保存到本地路径或目录 -- 文件名规则:头尾不能有空格;不能含 `*`、`"`、`<`、`>`、`|`、制表符;不能以 `.` 结尾 -- `shortcut` 会创建新节点,执行后必须通过 `drive list` 回读确认目标位置;`stats` 为只读命令 ## 自动化脚本 - | 脚本 | 场景 | 用法 | | ------------------------------------------------------ | ----------- | ------------------------------------- | | [drive_tree_list.py](../scripts/drive_tree_list.py) | 递归列出钉盘目录树结构 | `python drive_tree_list.py --depth 2` | - ## 相关产品 - [doc](../../dingtalk-doc/references/doc.md) — 文档内容读写/知识库空间,不是文件存储 -- [chat](../../dingtalk-chat/references/chat-commands.md) — 上传文件到 drive 后可通过 Markdown 语法发送图片/文件消息 +- [markdown](../../dingtalk-misc/references/markdown.md) — `.md` 文件的内容读取(fetch),非文件管理 +- [chat](../../dingtalk-chat/references/chat.md) — 上传文件到 drive 后可通过 Markdown 语法发送图片/文件消息 diff --git a/skills/multi/dingtalk-drive/references/intent-guide.md b/skills/multi/dingtalk-drive/references/intent-guide.md index e6911e5d..14ff825c 100644 --- a/skills/multi/dingtalk-drive/references/intent-guide.md +++ b/skills/multi/dingtalk-drive/references/intent-guide.md @@ -4,7 +4,7 @@ | 用户说... | 真实意图 | 应该用 | 不要用 | 理由 | |---|---|---|---|---| -| "参照这个生成同样的 / 按模板生成 / 复刻 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 层保形复制后只在副本上局部修改 | +| "参照这个生成同样的 / 按模板生成 / 复刻 X / 同样的模板 X 月份的" + 已有 alidocs URL | 模板保形生成同形态变体 | `drive copy + drive rename + doc block update` → 见 [best_practices/04-document.md `template-based-generation`](../../dingtalk-doc/references/04-document.md#template-based-generation) | `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 | diff --git a/skills/multi/dingtalk-drive/references/lite-recipes.md b/skills/multi/dingtalk-drive/references/lite-recipes.md index 24b91e67..206c781d 100644 --- a/skills/multi/dingtalk-drive/references/lite-recipes.md +++ b/skills/multi/dingtalk-drive/references/lite-recipes.md @@ -28,11 +28,11 @@ dws doc import --file ./report.docx --format json 4. 超时或中断时 CLI 返回 `taskId`,用 `dws doc import get --task-id --format json` 手动查询 **`--folder` 参数传值规则**: -- 首选路径:用户提供 alidocs URL 时,直接将完整 URL 传入 `--folder`,无需先调 `doc info` -- 预检路径:若需确认 URL 指向的是文件夹,可先调 `dws doc info --node `: +- 首选路径:用户提供 alidocs URL 时,直接将完整 URL 传入 `--folder`,无需先调 `drive info` +- 预检路径:若需确认 URL 指向的是文件夹,可先调 `dws drive info --node `: - `nodeType == "folder"` → 使用 `nodeId` 或原始 URL 作为 `--folder` 值 - `nodeType` 不是 folder → 提示用户:该链接指向的不是文件夹 -- 禁止:不得使用 `doc info` 返回的 `folderId` 字段作为 `--folder` 的值(`folderId` 是父文件夹 ID,非当前节点 ID) +- 禁止:不得使用 `drive info` 返回的 `folderId` 字段作为 `--folder` 的值(`folderId` 是父文件夹 ID,非当前节点 ID) 格式与文档类型映射: - `.docx` / `.doc` → 文字文档(DOC) @@ -42,3 +42,4 @@ dws doc import --file ./report.docx --format json > **禁止先 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 2fd349b5..a7e3439b 100644 --- a/skills/multi/dingtalk-drive/scripts/drive_tree_list.py +++ b/skills/multi/dingtalk-drive/scripts/drive_tree_list.py @@ -5,14 +5,8 @@ 用法: python drive_tree_list.py # 列出根目录 python drive_tree_list.py --depth 2 # 递归 2 层 - python drive_tree_list.py --folder # 指定目录(使用 fileId) + 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 [] @@ -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,7 +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, @@ -118,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 deleted file mode 100644 index c0cdd9cd..00000000 --- a/skills/multi/dingtalk-live/SKILL.md +++ /dev/null @@ -1,29 +0,0 @@ ---- -name: dingtalk-live -description: 钉钉直播列表与直播记录查询。Use when 用户说直播/我的直播/直播列表/查直播/直播回放/直播记录。视频会议/会议控制当前 CLI 不支持,应提示在钉钉客户端操作;AI 听记/转写摘要走 dingtalk-minutes,群消息走 dingtalk-chat。命令前缀:dws live。 -cli_version: ">=0.2.14" -metadata: - category: product - stability: experimental - requires: - bins: - - dws ---- - -# 钉钉直播 Skill - -> 🧪 **EXPERIMENTAL · 试验版 / Preview** — multi 模式当前未达 stable 标准。全部 dingtalk-* skill 已通过 dispatch verifier,但接口、命名、跨 skill 引用后续可能调整;生产 / 共享环境请优先使用 mono 模式(`dws skill setup --mode mono`)。问题请提 issue 反馈。 - -## 前置条件 — 执行操作前必读 - -> **`use_skill(dws-shared)`** — 认证、全局参数(`--format json` / `--yes`)、错误码、URL 模板、跨产品消歧、安全规则与 capability 边界。**执行任何 `dws` 命令前先读;** 单产品的清晰命令可直接用本 skill。 - - - -> 命令参考:[live.md](references/live.md)。 - -## 意图表 - -| 用户说 | 命令 | -|--------|------| -| "我的直播 / 直播列表" | `dws live stream list` | diff --git a/skills/multi/dingtalk-mail/SKILL.md b/skills/multi/dingtalk-mail/SKILL.md index a17f77f3..cc6cde34 100644 --- a/skills/multi/dingtalk-mail/SKILL.md +++ b/skills/multi/dingtalk-mail/SKILL.md @@ -1,10 +1,9 @@ --- name: dingtalk-mail -description: 钉钉邮箱读写、搜索、回复与转发。Use when 用户说发邮件/查邮件/回邮件/转发邮件/未读邮件/邮件搜索/邮箱附件。一句话解析联系人并确认发送邮件走配方 dingtalk-one-click-email;不做钉钉消息(走 dingtalk-chat)、紧急通知(走 dingtalk-ding)。命令前缀:dws mail。 -cli_version: ">=0.2.14" +description: 钉钉邮箱读写、搜索、回复与转发。Use when 用户说发邮件/查邮件/回邮件/转发邮件/未读邮件/邮件搜索/邮箱附件。一句话发邮件时先用 dingtalk-contact 解析并确认收件人,再由本 skill 发送;不做钉钉消息(走 dingtalk-chat)、紧急通知(走 dingtalk-misc)。命令前缀:dws mail。 metadata: + cli_version: ">=0.2.14" category: product - stability: experimental requires: bins: - dws @@ -12,22 +11,16 @@ metadata: # 钉钉邮箱 Skill -> 🧪 **EXPERIMENTAL · 试验版 / Preview** — multi 模式当前未达 stable 标准。全部 dingtalk-* skill 已通过 dispatch verifier,但接口、命名、跨 skill 引用后续可能调整;生产 / 共享环境请优先使用 mono 模式(`dws skill setup --mode mono`)。问题请提 issue 反馈。 - ## 前置条件 — 执行操作前必读 -> **`use_skill(dws-shared)`** — 认证、全局参数(`--format json` / `--yes`)、错误码、URL 模板、跨产品消歧、安全规则与 capability 边界。**执行任何 `dws` 命令前先读;** 单产品的清晰命令可直接用本 skill。 +> **CRITICAL — 执行任何 `dws` 操作前,MUST 先用 Read 工具完整读取 [`dws-shared`](../dws-shared/SKILL.md)。**该轻量文件包含全局执行契约、安全底线及 shared references 的按需加载导航;不要预加载其全部 references。 - - -> 渐进式参考:[mail-index.md](references/mail-index.md)。复杂搜索、附件、批量处理、草稿等多步邮件场景参考:[09-mail.md](references/09-mail.md)。先按索引选择专题。 - -> 旧路径兼容入口:[mail.md](references/mail.md)。 +> 命令参考:[mail.md](references/mail.md)。复杂搜索、附件、批量处理、草稿等多步邮件场景参考:[09-mail.md](references/09-mail.md)。 ## Shortcuts(无专用脚本/recipe 时优先) -以下 shortcut 来自独立于 Runtime Schema 的公开 catalog。先按本 skill 的意图表、脚本和 recipe 路由:存在精确覆盖该场景的专用脚本/recipe 时按其执行;否则用户意图命中时,shortcut 优先于手写原子命令。用 `dws shortcut list --service mail --format json` 读取参数、约束、风险和示例,并以 `dws mail --help` 核对当前 Cobra flags;不要对 `+` 路径调用 `dws schema`。 +以下命令来自独立于 Runtime Schema 的公开 catalog。先运行 `dws shortcut list --service mail --format json` 读取完整契约,再用 `dws mail --help` 核对 flags;不要对 `+` 路径调用 `dws schema`。 | Shortcut | 风险 | 适用场景 | |---|---|---| @@ -79,11 +72,11 @@ metadata: **触发**:发邮件/写邮件/群发。 1. **发件邮箱(必须)**:`dws mail mailbox list` 取自己邮箱。 -2. **收件邮箱(必须)**:地址直接用;姓名按 [mail-message-commands.md](references/mail-message-commands.md) “查找他人邮箱地址”流程(`mail user search` 等)获取,**禁止**猜测。 +2. **收件邮箱(必须)**:地址直接用;姓名按 [mail.md](references/mail.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` 查发送状态。 +4. **验证(必须)**:从发送返回取真实 `internetMessageId`,执行 `dws mail message verify --email <发件邮箱> --internet-message-id --format json` 查发送状态;不要把普通 `messageId` 传给 verify。 -**完成条件**:收件邮箱来自真实解析结果,且发送返回或 `message verify` 显示成功。 +**禁止**:猜测收件邮箱、发送后不确认状态就答复"已发送"。 ### SOP-4 回复 / 转发(reply-forward) @@ -96,7 +89,7 @@ metadata: ## 高频硬约束 -- 用户要“完整内容 / 看看这封邮件 / 正文”时,`message search` 只负责定位 messageId,随后用 `dws mail message get --email <邮箱> --id --format json` 获取正文。 +- 用户要"完整内容/看看这封邮件/正文"时,`message search` 命中后必须继续调用 `dws mail message get --email <邮箱> --id --format json`;不要只列候选后停下。 - 搜到多封邮件时,若用户给了明确主题、附件名、发件人或时间线索,先选最匹配的一封执行 `message get`;只有同等候选无法判断时才询问用户。 - 附件链路固定三步:`message search` → `attachment list --email <邮箱> --id ` → `attachment download --email <邮箱> --message-id --attachment-id --name <文件名>`;不存在批量下载命令。 - 写入类操作(发送、回复、转发、删除、批量移动)按安全策略确认;只读查看、搜索、附件列表、下载不需要确认。 @@ -106,6 +99,6 @@ metadata: - 收件人是人名 → 先用 `dingtalk-contact` 取 `orgAuthEmail` - 钉钉内消息 → 切到 `dingtalk-chat` -## 局部意图与 Recipe +## 局部意图与短流程 -- [局部意图消歧](references/intent-guide.md);[Lite Recipe](references/lite-recipes.md)。 +- [局部意图消歧](references/intent-guide.md);[短流程](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 d0b64cc9..dcf365ee 100644 --- a/skills/multi/dingtalk-mail/references/09-mail.md +++ b/skills/multi/dingtalk-mail/references/09-mail.md @@ -1,13 +1,13 @@ # 邮件 -> 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)。 +> 邮件的 4 条 **lite** recipe:`mail-list-mailbox`、`mail-search`、`mail-send`、`mail-reply-forward`,见 [lite-recipes.md](./lite-recipes.md)。下列专用规则与消歧请在命中邮件场景且**超出**上述 lite 时阅读本文。 +> 产品命令见 [mail.md](./mail.md)。通用批量/并行见 [_common/conventions.md](_common/conventions.md)。 ## 专用规则(#9 非 lite 步骤必守) -- **KQL 语法强制**:邮件搜索的查询条件**只能**通过 `--query` 参数以 KQL 语法传入(如 `subject:周报`),**禁止臆造** `--subject`、`--sender`、`--from-address` 等不存在的 flag。详见 [mail-message-commands.md](./mail-message-commands.md) 中 KQL 查询字段说明。 +- **KQL 语法强制**:邮件搜索的查询条件**只能**通过 `--query` 参数以 KQL 语法传入(如 `subject:周报`),**禁止臆造** `--subject`、`--sender`、`--from-address` 等不存在的 flag。详见 [mail.md](./mail.md) 中 KQL 查询字段说明。 - **邮箱地址前置**:大部分邮件命令需要 `--email` 或 `--from` 参数,执行前**必须**先通过 `mail mailbox list` 获取当前用户邮箱,禁止猜测邮箱地址。 -- **查找他人邮箱**:需要获取某人邮箱地址时,**不要用 `mailbox list`**(只返回自己的),必须走三路并发查询流程(见 [mail-message-commands.md](./mail-message-commands.md) 中「查找他人邮箱地址」章节)。 +- **查找他人邮箱**:需要获取某人邮箱地址时,**不要用 `mailbox list`**(只返回自己的),必须走三路并发查询流程(见 [mail.md](./mail.md) 中「查找他人邮箱地址」章节)。 - **附件下载三步走**:先 `message search` 搜索邮件获取 messageId,再 `attachment list` 获取附件 ID 和文件名,最后逐个 `attachment download` 下载。**不存在 `download_batch` / `download_all` 等批量下载命令,禁止编造**。 - **危险操作确认**:`batch-delete` 执行前必须向用户确认,同意后加 `--yes`。 @@ -42,4 +42,4 @@ | 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` → 取邮箱
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 "<内容>"` | +| send-to-person-by-name | 1. `mail mailbox list` → 取发件邮箱
2. 走「查找他人邮箱地址」三路并发查询获取收件人邮箱(见 [mail.md](./mail.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 index 75491aab..803212b8 100644 --- a/skills/multi/dingtalk-mail/references/_common/conventions.md +++ b/skills/multi/dingtalk-mail/references/_common/conventions.md @@ -9,7 +9,7 @@ | 1 | **并行查详情**:拿到多个 ID 后,用 `&` 合并到同一条 Shell 命令并行执行 + `wait`,**严禁逐条串行** | | 2 | **翻页**:分页接口须拉全直至无更多 | | 3 | **优先批量 API**:有批量接口则用批量;无则按 #1 并行 | -| 4 | **群消息**:必须先 `chat search --query` 得 `openConversationId`,再 `chat message list --group`;多群同条命令并行 | +| 4 | **群消息**:必须先 `chat search --query` 得 `openConversationId`,再 `chat message list --group --time "" --direction older`;多群同条命令并行 | | 5 | **列表少轮次**:带条件搜索/列表 → 一次采全详情;**禁止**无新参数时重复同一 `list` / `search` | ## 多源并行采集(公共模式) @@ -29,7 +29,7 @@ |------|------|--------| | `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 | +| `deptId` | `contact dept search` | `contact dept list-members --ids `;多子部门时对每个子部门分别 `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` | @@ -38,7 +38,7 @@ | `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` | +| `dentryUuid` | `drive list` / `drive mkdir` | `drive info/download/copy/move/rename/delete --node`、`drive list/mkdir/upload/copy/move --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,不能把该数字直接代入后续命令。 +**ID 边界硬约束**:`dentryId` 通常是纯数字,只表示聊天文件消息需要的钉盘条目数字 ID;它不是父目录 ID。遇到 `drive --node/--folder`、`doc --node`、`wiki node --folder` 时,只能使用 `dentryUuid` / `nodeId` / 文档 URL。若当前上下文只有数字型 `dentryId`,必须先重新 `drive list` / `drive search` / `wiki node list` 获取正确 ID,不能把该数字直接代入后续命令。 diff --git a/skills/multi/dingtalk-mail/references/lite-recipes.md b/skills/multi/dingtalk-mail/references/lite-recipes.md index eb23dc5d..a3065e88 100644 --- a/skills/multi/dingtalk-mail/references/lite-recipes.md +++ b/skills/multi/dingtalk-mail/references/lite-recipes.md @@ -6,7 +6,7 @@ ### mail-list-mailbox -查询当前用户自己的可用邮箱地址列表。**仅返回自己的邮箱**,不能查他人邮箱(查他人邮箱请走 [mail-message-commands.md](./mail-message-commands.md) 中「查找他人邮箱地址」三路并发查询流程)。 +查询当前用户自己的可用邮箱地址列表。**仅返回自己的邮箱**,不能查他人邮箱(查他人邮箱请走 [mail.md](./mail.md) 中「查找他人邮箱地址」三路并发查询流程)。 `mail mailbox list` @@ -15,7 +15,7 @@ 搜索邮件。**必须使用 KQL 语法通过 `--query` 传递查询条件**,禁止臆造 `--subject`、`--from` 等不存在的 flag。 1. 获取邮箱地址:`mail mailbox list` → 取用户邮箱地址。若用户已提供邮箱可跳过。 -2. 构造 KQL 查询:根据用户意图将搜索条件转为 KQL 表达式(详见 [mail-message-commands.md](./mail-message-commands.md) 中 KQL 查询字段说明)。 +2. 构造 KQL 查询:根据用户意图将搜索条件转为 KQL 表达式(详见 [mail.md](./mail.md) 中 KQL 查询字段说明)。 - 按主题:`subject:周报`、`subject:"项目 进展"`(含空格须加双引号) - 按发件人:`from:alice@company.com` 或 `from:"张三"` - 按日期:`date>2025-06-01T00:00:00Z`(ISO8601 格式,必须含时间部分) @@ -30,7 +30,7 @@ 发送邮件。 1. 获取邮箱地址:`mail mailbox list` → 取用户邮箱作为 `--from`。 -2. 确定收件人:用户直接提供邮箱地址 → 直接使用;用户提供姓名或工号 → 走「查找他人邮箱地址」三路并发流程(见 [mail-message-commands.md](./mail-message-commands.md))。 +2. 确定收件人:用户直接提供邮箱地址 → 直接使用;用户提供姓名或工号 → 走「查找他人邮箱地址」三路并发流程(见 [mail.md](./mail.md))。 3. 发送:`mail message send --from <发件邮箱> --to <收件邮箱> --subject "<主题>" --content "<正文>"`(可选 `--cc`、`--attachment`、`--inline-attachment`)。 ### mail-reply-forward diff --git a/skills/multi/dingtalk-mail/references/mail-command-index.md b/skills/multi/dingtalk-mail/references/mail-command-index.md deleted file mode 100644 index 9c638211..00000000 --- a/skills/multi/dingtalk-mail/references/mail-command-index.md +++ /dev/null @@ -1,78 +0,0 @@ -# 命令速查目录 - -## 按需加载详细参数 - -| 命令族 | 详见 | -|---|---| -| 邮箱选择、邮件列表/搜索/读取/发送 | [`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 deleted file mode 100644 index 7f010fae..00000000 --- a/skills/multi/dingtalk-mail/references/mail-common-errors.md +++ /dev/null @@ -1,9 +0,0 @@ -# 通用错误说明 - -## 通用错误说明 - -以下错误适用于所有 mail 命令。 - -| 错误标识 | 含义 | 处理建议 | -|----------|------|----------| -| `domain.notFound` | 该用户的邮箱不是由钉钉邮箱托管,无法完成操作 | 确认邮箱是否已开通钉钉企业邮箱服务 | diff --git a/skills/multi/dingtalk-mail/references/mail-context.md b/skills/multi/dingtalk-mail/references/mail-context.md deleted file mode 100644 index 5c8c6b83..00000000 --- a/skills/multi/dingtalk-mail/references/mail-context.md +++ /dev/null @@ -1,22 +0,0 @@ -# 上下文传递表 - -## 上下文传递表 - -| 操作 | 从返回中提取 | 用于 | -|------|-------------|------| -| `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 deleted file mode 100644 index 3ade2717..00000000 --- a/skills/multi/dingtalk-mail/references/mail-default-mailbox-rules.md +++ /dev/null @@ -1,21 +0,0 @@ -# 默认邮箱选择规则(重要) - -## 默认邮箱选择规则(重要) - -所有 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 deleted file mode 100644 index 1da0423d..00000000 --- a/skills/multi/dingtalk-mail/references/mail-draft-directory-commands.md +++ /dev/null @@ -1,259 +0,0 @@ -# 草稿、用户、模板与联系人命令 - -### 创建草稿 -``` -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 deleted file mode 100644 index be18c6bb..00000000 --- a/skills/multi/dingtalk-mail/references/mail-folder-attachment-tag-commands.md +++ /dev/null @@ -1,291 +0,0 @@ -# 邮件文件夹、附件与标签命令 - -### 列举邮件文件夹 -``` -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 deleted file mode 100644 index 2e21b985..00000000 --- a/skills/multi/dingtalk-mail/references/mail-index.md +++ /dev/null @@ -1,11 +0,0 @@ -# 邮件能力索引 - -| 主题 | 详见 | -|---|---| -| 命令速查与详细命令族 | [`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 deleted file mode 100644 index 31203cd0..00000000 --- a/skills/multi/dingtalk-mail/references/mail-intent.md +++ /dev/null @@ -1,41 +0,0 @@ -# 意图判断 - -## 意图判断 - -用户说"我的邮箱/邮箱地址" → `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 --query <姓名>`,提取用户邮箱字段 - **第二步**:用获得的目标邮箱拼入 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 deleted file mode 100644 index fe227a42..00000000 --- a/skills/multi/dingtalk-mail/references/mail-message-commands.md +++ /dev/null @@ -1,240 +0,0 @@ -# 邮箱与邮件命令 - -## 命令总览 - -### 查询可用邮箱地址 -> **注意:** 仅返回当前登录用户**自己的**邮箱列表,不能用于查找他人邮箱。查找他人邮箱请使用三路并发流程(见"查找他人邮箱地址"章节)。 -``` -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 --query "姓名" --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 deleted file mode 100644 index 5881a597..00000000 --- a/skills/multi/dingtalk-mail/references/mail-never-do.md +++ /dev/null @@ -1,15 +0,0 @@ -# 严格禁止 (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 deleted file mode 100644 index 515c1e1e..00000000 --- a/skills/multi/dingtalk-mail/references/mail-notes.md +++ /dev/null @@ -1,19 +0,0 @@ -# 注意事项 - -## 注意事项 - -- `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 --query "名字"`,提取用户邮箱字段 - 若三路均无有效邮箱,请用户手动提供收件人邮箱,严禁臆测和假设 -- `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 deleted file mode 100644 index 2196ef9b..00000000 --- a/skills/multi/dingtalk-mail/references/mail-policy-commands.md +++ /dev/null @@ -1,388 +0,0 @@ -# 自动回复、名单与收信规则命令 - -### 获取自动回复配置 - -获取当前用户的邮件自动回复配置,包括是否启用、生效时间、回复范围和回复内容。 - -``` -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 deleted file mode 100644 index 29077927..00000000 --- a/skills/multi/dingtalk-mail/references/mail-thread-actions.md +++ /dev/null @@ -1,453 +0,0 @@ -# 邮件会话与消息动作命令 - -### 列出邮件会话 - -``` -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 deleted file mode 100644 index 5e01d7af..00000000 --- a/skills/multi/dingtalk-mail/references/mail-workflow.md +++ /dev/null @@ -1,85 +0,0 @@ -# 核心工作流 - -## 核心工作流 - -```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 5fb9330a..cbcfabd4 100644 --- a/skills/multi/dingtalk-mail/references/mail.md +++ b/skills/multi/dingtalk-mail/references/mail.md @@ -1,10 +1,1889 @@ # 邮箱 (mail) 命令参考 -> 渐进式兼容入口:新任务优先从 [邮件能力索引](./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) | +| 命令 | 功能简述 | +|------|----------| +| `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. 从返回的 `mailboxes` 中**优先选择企业邮箱**(账号类型为企业邮箱、域名非 `@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 +``` + +**返回字段:** + +| 字段 | 类型 | 说明 | +|------|------|------| +| `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 +# 主路径:aisearch + contact user get +dws aisearch person --keyword "姓名" --dimension name --format json +# → 取 userId,再执行: +dws contact user get --ids --format json +# → 提取 orgAuthEmail 字段 + +# orgAuthEmail 为空时的邮箱侧补查: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 +``` + +若两步均无有效邮箱,必须 `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=已删除),默认为收件箱,别名: --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` + +### 列举邮件文件夹 +``` +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": {} +} +``` + +### 列出邮件会话 + +``` +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` | 失败 | + +### 创建草稿 +``` +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 列表,逗号分隔 (必填) +``` + +### 获取自动回复配置 + +获取当前用户的邮件自动回复配置,包括是否启用、生效时间、回复范围和回复内容。 + +``` +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 | 调整结果 | + +## 通用错误说明 + +以下错误适用于所有 mail 命令。 + +| 错误标识 | 含义 | 处理建议 | +|----------|------|----------| +| `domain.notFound` | 该用户的邮箱不是由钉钉邮箱托管,无法完成操作 | 确认邮箱是否已开通钉钉企业邮箱服务 | + +## 意图判断 + +用户说"我的邮箱/邮箱地址" → `mailbox list`(**仅限查询自己的邮箱,不能查他人**) +用户说"获取/查找/得到 某人的邮箱地址" → **不是 `mailbox list`**,走三路并发查询流程(见「查找他人邮箱地址」章节) +用户说"找邮件/搜邮件/查邮件" → `message search` +用户说"看邮件/打开邮件/邮件内容" → 先 `message search` 获取 messageId,再 `message get` +用户说"发邮件/写邮件" → 先 `mailbox list` 获取发件地址,再 `message send` +用户说“给(某人名字)发邮件” / “查询某人发给我的邮件” / “查询发给某人的邮件” / 任何涉及按人名查找邮箱的场景 → + **第一步**:`aisearch person --keyword <姓名> --dimension name` → `contact user get --ids `,提取 `orgAuthEmail`;为空时再用 `mail user search --email <当前邮箱> --keyword <姓名>` 补查。仍无有效邮箱则 ask_human 请用户提供,禁止臆测。 + **第二步**:用获得的目标邮箱拼入 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表达式`,返回邮件列表 + + +## 严格禁止 (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 --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 +``` + +## 上下文传递表 + +| 操作 | 从返回中提取 | 用于 | +|------|-------------|------| +| `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`;必要时 `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 | + +## 注意事项 + +- `mailbox list` 返回用户所有邮箱(含个人和企业),每条记录包含邮箱地址、账号类型、所属企业。**默认一律选择企业邮箱**(除非用户明确指定使用个人邮箱);若有多个企业邮箱可选,优先匹配用户当前所在企业的那一个;仍无法判断时向用户确认后再操作。详见文档顶部「默认邮箱选择规则」章节 +- `message search` 返回邮件 ID 和元信息(不含正文),需 `message get` 获取完整内容 +- KQL 查询支持 AND/OR/NOT 组合,字段值含空格时需用双引号 +- `--cc` 抄送人支持多人,逗号分隔 +- 收件人邮箱获取:用户只知道同事名字时,先 `dws aisearch person --keyword "名字" --dimension name` 取得 userId,再 `dws contact user get --ids ` 提取 `orgAuthEmail`。该字段为空时,可用 `dws mail user search --email <发件人邮箱> --keyword "名字"` 补查(仅企业邮箱账号可调用;若已知工号,可改用 `--employee-no <工号>`)。仍无有效邮箱时必须 ask_human,严禁臆测和假设 +- `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/scripts/mail_send_with_cc.py b/skills/multi/dingtalk-mail/scripts/mail_send_with_cc.py index 9dc72fec..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,17 +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'] - for account in accounts: - if (isinstance(account, dict) - and account.get('type') == 'ORG' - and account.get('email')): - return account['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') @@ -122,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 80b09411..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,17 +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'] - for account in accounts: - if (isinstance(account, dict) - and account.get('type') == 'ORG' - and account.get('email')): - return account['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') @@ -103,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) @@ -113,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 090ed63e..688ed9b1 100644 --- a/skills/multi/dingtalk-minutes/SKILL.md +++ b/skills/multi/dingtalk-minutes/SKILL.md @@ -1,10 +1,9 @@ --- name: dingtalk-minutes -description: 钉钉 AI 听记查看、摘要、转写、待办提取与分享。Use when 用户说听记/会议录音/会议纪要/AI摘要/转写/关键字/听记标题/会后待办提取/分享听记。本地音视频转纪要/逐字稿也优先用本 skill,不要用 ffmpeg/whisper 本地转写。不做听记生成报告文档(走配方 minutes-report-to-doc)、听记补充到已有文档(走配方 minutes-to-doc)、日程(走 dingtalk-calendar)。命令前缀:dws minutes。 -cli_version: ">=0.2.14" +description: 钉钉 AI 听记。Use when 查询听记摘要、转写、关键词、待办或分享。写文档走 dingtalk-doc;日程走 dingtalk-calendar。 metadata: + cli_version: ">=0.2.14" category: product - stability: experimental requires: bins: - dws @@ -12,22 +11,16 @@ metadata: # 钉钉 AI 听记 Skill -> 🧪 **EXPERIMENTAL · 试验版 / Preview** — multi 模式当前未达 stable 标准。全部 dingtalk-* skill 已通过 dispatch verifier,但接口、命名、跨 skill 引用后续可能调整;生产 / 共享环境请优先使用 mono 模式(`dws skill setup --mode mono`)。问题请提 issue 反馈。 - ## 前置条件 — 执行操作前必读 -> **`use_skill(dws-shared)`** — 认证、全局参数(`--format json` / `--yes`)、错误码、URL 模板、跨产品消歧、安全规则与 capability 边界。**执行任何 `dws` 命令前先读;** 单产品的清晰命令可直接用本 skill。 +> **CRITICAL — 执行任何 `dws` 操作前,MUST 先用 Read 工具完整读取 [`dws-shared`](../dws-shared/SKILL.md)。**该轻量文件包含全局执行契约、安全底线及 shared references 的按需加载导航;不要预加载其全部 references。 - - -> 渐进式参考:[minutes-index.md](references/minutes-index.md);剧本:[07-minutes.md](references/07-minutes.md)。先按索引选择专题,不要一次性加载全部听记参考。 - -> 旧路径兼容入口:[minutes.md](references/minutes.md)。 +> 命令参考:[minutes.md](references/minutes.md);剧本:[07-minutes.md](references/07-minutes.md)。 ## Shortcuts(无专用脚本/recipe 时优先) -以下 shortcut 来自独立于 Runtime Schema 的公开 catalog。先按本 skill 的意图表、脚本和 recipe 路由:存在精确覆盖该场景的专用脚本/recipe 时按其执行;否则用户意图命中时,shortcut 优先于手写原子命令。用 `dws shortcut list --service minutes --format json` 读取参数、约束、风险和示例,并以 `dws minutes --help` 核对当前 Cobra flags;不要对 `+` 路径调用 `dws schema`。 +以下命令来自独立于 Runtime Schema 的公开 catalog。先运行 `dws shortcut list --service minutes --format json` 读取完整契约,再用 `dws minutes --help` 核对 flags;不要对 `+` 路径调用 `dws schema`。 | Shortcut | 风险 | 适用场景 | |---|---|---| @@ -61,7 +54,7 @@ metadata: **触发**:查听记/会议记录/某会议的纪要/按关键词或时间找听记。 1. **选 scope(必须·铁律)**:`mine`=我创建/发起的;`shared`=他人共享给我的;`all`=我可访问的全部(含 mine+shared)。用户说"我能访问/可见/所有/我的听记"等覆盖语义**一律 `all`**;只有明确"我创建的/我发起的/我录的"才用 `mine`。 -2. **执行(必须)**:`dws minutes list all|mine|shared --format json`;按关键词加 `--query "<关键词>"`,按时间加 `--start "" --end ""`,限制条数用 canonical 参数 `--limit `,翻页用 `--cursor `。 +2. **执行(必须)**:`dws minutes list all|mine|shared --format json`;按关键词加 `--query "<关键词>"`,按时间加 `--start "" --end ""`,限制条数 `--limit `(`--max` 兼容别名),翻页用 `--cursor `(`--next-token` 兼容别名)。 3. **解析(必须)**:从 `itemList[]` 取真实 `taskUuid` + `title` + 时间;多候选必须让用户确认,**禁止**默认取第一条。 **禁止**:把 `mine` 当全量、凭标题相似度锁定、跳过 `--format json`。 @@ -80,7 +73,7 @@ metadata: **触发**:把会议纪要转成文档/把听记待办建到待办系统。 -1. **转文档(必须)**:取 `minutes get summary`/`transcription` 内容 → 切 `dingtalk-doc` 用 `dws doc create --content-file ` 落盘(或用 `dingtalk-products-skills/minutes-to-doc` recipe skill)。 +1. **转文档(必须)**:取 `minutes get summary`/`transcription` 内容 → 切 `dingtalk-doc` 用 `dws doc create --content-file ` 落盘。 2. **待办回写(必须)**:取 `minutes get todos` 的待办项 → 切 `dingtalk-todo` 按 SOP-1 解析执行者 userId 后 `todo task create`。 **禁止**:在 minutes 内直接写文档/建待办(应切对应 skill)。 @@ -98,9 +91,9 @@ metadata: ## 跨产品协作 -- 提取的待办批量建任务 → 切到 `dingtalk-todo` 的 batch-create-todo recipe;批量脚本仅在 `dingtalk-todo` sub-skill 内可用,未切换前不要在当前 skill 运行。 +- 提取的待办批量建任务 → 切到 `dingtalk-todo`,按其批量创建 SOP 执行;批量脚本仅在 `dingtalk-todo` sub-skill 内可用,未切换前不要在当前 skill 运行。 - 摘要发给同事 → 切到 `dingtalk-chat` - 日程 / 会议室 → 切到 `dingtalk-calendar` -## 局部意图与 Recipe +## 局部意图与短流程 -- [局部意图消歧](references/intent-guide.md);[Lite Recipe](references/lite-recipes.md)。 +- [局部意图消歧](references/intent-guide.md);[短流程](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 78bf3ab7..54534645 100644 --- a/skills/multi/dingtalk-minutes/references/07-minutes.md +++ b/skills/multi/dingtalk-minutes/references/07-minutes.md @@ -1,78 +1,142 @@ -# 听记与会后工作流 +# 听记与会后 -> 产品命令路由见 [minutes-index.md](./minutes-index.md),scope、taskUuid 与对象选择规则见 [minutes-overview.md](./minutes-overview.md)。 +> lite(`minutes-query`)见 [lite-recipes.md](./lite-recipes.md)。full recipe 见下表。 +> 日程、订会议室、`schedule-meeting` 见 `dingtalk-calendar/references/03-meeting.md`。产品命令见 [minutes.md](./minutes.md)。 -## 通用前置 +### 听记列表参数速查 -1. 用户未提供 taskUuid 或 URL 时,先用 `minutes list all` 定位。 -2. 同时核对标题、组织、时间与 taskUuid;多候选时让用户确认。 -3. 有关键词或时间条件时使用服务端 `--query`、`--start`、`--end` 过滤。 -4. 后续调用复用已确认的 taskUuid;不要因某字段为空切换到其他听记。 -5. 所有命令带 `--format json`,并从真实返回中提取下一步参数。 +`list mine` / `list shared` / `list all` 均支持以下筛选参数(**有时间/关键词条件时优先使用服务端过滤;无筛选条件时可全量拉取**): -## 工作流索引 - -| 场景 | 固定路线 | -|---|---| -| 查询与浏览 | `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` → 回读验证 | - -## 查询与详情 +| 参数 | 说明 | 示例 | +|------|------|------| +| `--query "<关键词>"` | 服务端关键词搜索 | `--query "周会"` | +| `--start ""` | 开始时间(含时区) | `--start "2026-05-01T00:00:00+08:00"` | +| `--end ""` | 结束时间(含时区) | `--end "2026-05-25T23:59:59+08:00"` | +| `--limit ` | 每页返回条数(默认 10) | `--limit 20`(`--max` 为兼容别名) | +| `--cursor ""` | 分页 token(首页留空) | `--cursor "abc123"`(`--next-token` 为兼容别名) | +**组合筛选示例**: ```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 +# 按时间范围 +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 mine --query "需求评审" --start "2026-05-25T00:00:00+08:00" --end "2026-05-25T23:59:59+08:00" --format json +# 限制条数 +dws minutes list mine --limit 5 --format json ``` -只需列表时停在 `list`;用户要正文或结论时继续 `get summary`;用户要逐字稿或原话时继续 `get transcription` 并翻页至结束。 +| Recipe | 行动指南(固定路线) | +| ---------------- |---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| meeting-followup | 1. **提取待办优先**:`python scripts/minutes_extract_todos.py [--id ] [--limit N]` → 获取行动项列表 无脚本时按[「多源并行采集」](_common/conventions.md#多源并行采集公共模式)执行 2. 提取行动项 3. `aisearch person --keyword "<姓名>" --dimension name` → 取 `userId` → `todo task create --title "<行动项>" --executors --priority <10/20/30/40>`(每条行动项;批量时切到 `dingtalk-todo` 的 batch-create-todo recipe,批量脚本只在该 sub-skill 内可用) 4. `chat message send --group --text "<通知内容>"` 通知已创建 | +| share-minutes | **拉摘要优先**:`python scripts/minutes_recent_summary.py [--limit N] [--output summary.md]` → 获取近期听记摘要 备选手动:1. `minutes list mine` → 取 `taskUuid` 2. 用户选定 3. `minutes get summary --id ` → 取摘要 4. 单聊:`aisearch person --keyword "<姓名>" --dimension name` → 取 `openDingTalkId` → `chat message send --open-dingtalk-id --text "<摘要内容>"`(推荐);群聊:`--group --text "<摘要内容>"` 发送。仅当无法获取 openDingTalkId 时才用 `--user `(备选) | +| browse-minutes | 1. `minutes list all --query "<关键词>" --limit ` → 取全量 `taskUuid`(翻页直至无更多;无主题筛选用 `minutes list mine`;**有时间范围时加 `--start`/`--end` 服务端过滤**,如 `--start "2026-04-01T00:00:00+08:00" --end "2026-04-30T23:59:59+08:00"`) 2. **详情/元数据优先** `minutes get batch --ids `(仅 API 上限时拆多批);**摘要专项**且无 batch 字段时再用并行 `get summary` 或 `**python scripts/minutes_recent_summary.py --limit `** 3. 汇总展示;**同一用户诉求下避免**「半截 list → 再 list 剩余」「拆 4 条一批 summary 连跑多轮」等无必要拆分 | +| minutes-raw-export | 适用"下载/导出最近一周听记原文/逐字稿/转写内容/完整记录":1. 按用户时间词计算 `--start` / `--end`(最近一周示例:7 天前 00:00:00+08:00 到今天 23:59:59+08:00) 2. `minutes list all --start "" --end "" --format json` 获取范围内所有可见听记;为空时直接说明无相关数据 3. 对列表中每个 `taskUuid` 逐一 `minutes get transcription --id --format json`,如返回 `cursor` / `nextToken` / `nextCursor`,继续用 `--cursor ` 拉完后续页 4. 交付文件或最终回复必须包含各听记的真实转写段落,可附标题/时间/组织,禁止只交 summary 或让用户再选某一条 | +| minutes-detail | 1. 确定 `taskUuid`:用户已提供 → 直接用;未提供 → `minutes list mine --limit 5` 让用户选(**有时间线索时加 `--start`/`--end`**,如「上周五的会」→ `--start "2026-05-23T00:00:00+08:00" --end "2026-05-23T23:59:59+08:00"`) 2. 并行拉取四维信息:`minutes get info --id ` `&` `minutes get summary --id ` `&` `minutes get keywords --id ` `&` `minutes get todos --id ` `& wait` 3. 整合输出:基础信息(标题、时间、参与人)→ AI 摘要 → 关键字 → 行动项/待办 4. **展示发言人列表**:从转写/info 提取所有发言人(含已标注姓名和匿名编号),列出每位发言人的发言次数和时长占比,引导用户:「是否需要查看某位发言人的详细内容总结?请输入姓名或编号」→ 用户选定 → 进入 `minutes-speaker-summarize` recipe 5.(可选)用户要求看原文 → `minutes get transcription --id ` | +| minutes-speaker-summarize | 1. 读取转写 → `minutes get transcription --id ` 2. 声纹标注检查:已标注 → 跳 Step 6;匿名编号 → 继续 3. 转写原文推断(称呼/自我介绍/上下文指代)→ 高置信度跳 Step 6 4. 并发身份推断:`calendar event list` + `participant list` 取日程参与人(最高优先) & `aisearch person` & `chat message list` & `drive search` `& wait`;未找到同时段日程 → 引导用户提供日程链接或参会人名单;两路以上一致才下结论 5. 置信度判断:>70% 直接输出;≤70% 展示 TOP3 候选让用户选(最多一次) 6. 结构化总结输出(核心观点 + 问题 + Action Item + 立场)→ 追问是否替换发言人标注。详见 [10-minutes-speaker-match.md](./10-minutes-speaker-match.md) | +| browse-by-tag | 1. `minutes tag list` → 获取用户标签/分组列表(含 tagId 和名称) 2. 按用户指定的标签名称匹配 tagId(若用户直接提供 tagId 则跳过 Step 1) 3. `minutes tag query --tag-id ` → 返回该标签下的听记列表(支持 `--limit`/`--cursor` 分页) 4. 展示结果,按需调用 `get summary`/`get info` 获取详情 | +| speaker-correct | 1. **查同时段日程**:从听记元数据获取录音开始时间 → `calendar event list`(前后 30 分钟内)→ 若匹配到日程则提取参会人名单并展示 2. **展示发言人状态表格**:列出所有发言人及标注状态(已识别 / 未标注 / 未标注,发言较少) 3. **让用户选择识别方式**:听音识人(裁剪代表性音频片段让用户辨认)/ 手动设置(用户直接告知对应关系)/ 智能匹配(结合参会人名单和发言内容推断) 4. **执行替换**:确认对应关系后统一搜人获取 dingUid → `dws aisearch person --keyword "<姓名>" --dimension name --format json` → 取 userId(长整型)→ `dws minutes speaker replace --id --from "发言人X" --to "<姓名>" --target-uid --format json`;多个匹配时列出候选让用户选;无匹配时执行不带 `--target-uid` 的替换 5. 替换成功后追问「还有其他发言人需要帮你识别和替换吗?」详见 [11-minutes-speaker-correct.md](./11-minutes-speaker-correct.md) | -## 批量详情 +--- -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. 范围内无听记时直接说明无数据,不生成模板内容代替。 +#### 0. 选对象铁律(命令对了但选错听记 = 整任务失败,0605 P2 EDD badcase 提炼) -## 会议待办 +> 这是历史任务中**最致命**的一类失败:命令写得完全正确,但 list/搜索拿到结果后**选错了听记对象**,导致后续所有 `get summary` / `get keywords` / `update` 全部作用在错误条目上。详见 [minutes.md](./minutes.md)「选对象铁律 S1~S6」,速记如下: -1. `minutes get todos --id ` 获取真实行动项。 -2. 对责任人使用 `aisearch person` 或 `contact user search` 获取 userId。 -3. 切到 `dingtalk-todo`,逐条或批量创建任务。 -4. 原数据未给责任人或截止时间时,先请用户确认,不自行补造。 +| 铁律 | 场景 | 正确做法 | 真实 badcase | +|------|------|----------|------------| +| **S1 跨组织汇总** | 「帮我看看我所有听记」涉及多个组织 | 以 list 返回的 `taskUuid + title + organizationName` 三元组**逐条照抄**,组织与听记不可张冠李戴,禁止按标题相似度乱配 | 0028:把「12月文档切片讨论」误识别为「晨会427」 | +| **S2 最近一次某类会议** | 「最近一次周会」 | 先 `--query "周会"` 过滤出**该类会议**,再在候选里按时间取最新;**主题匹配优先级高于时间** | 0026:选成普通的「2026-06-02 录音」而非周会纪要 | +| **S3 比时长最长** | 「最长的那条听记」 | 必须读 `durationMicros`(微秒)字段做数值比较取最大;**口头结论与操作的 taskUuid 必须自洽** | 0030:声称 A 最长却操作了 B | +| **S4 内容为空** | get summary/keywords 返回空 | 锁定 taskUuid 后所有 get/update **复用同一 id**;某字段为空就**如实告知**,禁止偷偷切换到另一条听记 | 0020:第一条空就擅自换另一条展示 | +| **S5 模糊日期匹配不到** | 「4月9日周会纪要」实际创建于 4.14 | 日期可能是"会议主题日期"而非"创建日期",按标题关键词搜,精确日期没命中就放宽 ±7 天/同主题候选请用户确认,**禁止直接报"找不到"** | 0030:按 4-09 精确匹配导致漏掉 | +| **S6 给标题没给 id** | 「把'X'这条听记标题改成'Y'」 | **必须先** `list all --query "<标题关键词>"` 定位出唯一 taskUuid 再 update/get,禁止凭记忆直接填 `--id` 跳过定位 | 0012:跳过 list 直接用已知 id 改标题 | -## 发言人处理 +> **批量场景补充(0030 P2 badcase 提炼)**:用户一次性要查多条听记详情时,定位出各 taskUuid 后应优先用 `minutes get batch --ids ` 批量查询(见 browse-minutes recipe),而非逐个 `get`。**批量接口容错铁律**: +> - 部分条目权限失败时,**展示成功条目的完整信息 + 列出失败条目的 id 与原因**,不要整批放弃改为逐个查询 +> - 若 `get batch` 整体报错(如权限/参数问题),**降级为逐个 `get info`**,但必须在最终输出中说明"批量接口不可用,已逐个查询" +> - 逐个查询时仍需保证**所有条目都尝试过**,不能因某一条失败就放弃后续条目 +> +> **批量定位 + 模糊匹配组合策略(0030 实战链路)**: +> ``` +> 用户: "帮我批量查一下这几条听记的详情:4月9日周会纪要、4月项目自动化周报、【内部会议】测试报告与多模型适配进展" +> +> Step 1: 逐个关键词搜索定位 +> dws minutes list all --query "周会纪要" --format json → 若命中则取 taskUuid +> dws minutes list all --query "项目自动化周报" --format json → 取 taskUuid +> dws minutes list all --query "测试报告与多模型适配" --format json → 取 taskUuid +> +> Step 2: 某条搜不到时的回退(S5 铁律) +> "4月9日周会纪要" 按 --query "周会纪要" 未命中 → 放宽搜索: +> dws minutes list all --query "周会" --start "2026-04-02T00:00:00+08:00" --end "2026-04-16T23:59:59+08:00" --format json +> → 列出候选让用户确认,禁止直接报"找不到" +> +> Step 3: 全部定位后批量查询 +> dws minutes get batch --ids --format json +> ``` -- 发言人匹配见 [10-minutes-speaker-match.md](./10-minutes-speaker-match.md)。 -- 发言人纠偏见 [11-minutes-speaker-correct.md](./11-minutes-speaker-correct.md)。 -- 未确认身份前只展示候选和依据;确认后再调用 `speaker replace` 写回。 -- 判断某人是否参会时,结合参会人列表、通讯录和转写线索,不用文本中是否出现姓名作为唯一依据。 +#### 1. 空数据场景处理(最高频失败原因) -## 空数据与错误 +`minutes list` 返回空列表时的正确行为: -- `list` 为空时,保留同一时间范围,先去掉过窄关键词再查一次;仍为空则明确说明。 -- 权限失败时说明目标听记未共享给当前用户,不换 ID 绕过。 -- 多步流程某一步失败时停止依赖该结果的后续写操作,并说明具体失败点。 -- 多数据源任务分别声明各来源的取数状态;缺失来源不得用推测补齐。 +``` +正确做法: +1. 调用 dws minutes list(mine/shared/all 均尝试) +2. 若全部返回空 → 明确告知用户「当前没有找到听记记录」 +3. 给出替代建议(如:检查是否有进行中的会议、确认听记权限等) -## 忠实性 +错误做法: +- 沉默放弃,不给用户任何反馈 +- 反问用户「你有听记吗?」而不先主动查询 +- 在无数据时仍尝试调用 get summary / get transcription(会因缺少 taskUuid 报错) +``` -- 标题、组织、时间、时长、责任人和统计数字均以工具返回为准。 -- 源数据没有行动项、责任人或数字时,不生成对应事实。 -- 汇总前确认每条结论都能追溯到已读取的 summary、transcription 或 todos。 +#### 2. 钉钉文档链接读取(严禁误用工具) + +当用户提供 `https://alidocs.dingtalk.com/...` 链接时: + +``` +正确做法: +dws doc read --url "https://alidocs.dingtalk.com/i/nodes/xxx" --format json + +严禁使用: +- read_file 打开钉钉 URL(会返回登录页 HTML) +- read_ali_doc(这是 Agent 自身的文档读取工具,非 dws 命令) +- web_fetch / browser_use(无法访问需登录的内部文档) +``` + +#### 3. 核心链路执行原则 + +| 场景 | 完整链路 | 无数据时的最低要求 | +|------|---------|------------------| +| 录制转写 | `minutes list` → `get transcription` | 调用了 `minutes list` + 明确告知无数据 | +| 内容读取 | `minutes list` → `get summary` / `get transcription` | 调用了 `minutes list` + 明确告知无数据 | +| 内容总结 | `minutes list` → `get summary`(多条)→ 提炼 | 调用了 `minutes list` + 明确告知无数据 | +| 待办提取 | `minutes list` → `get todos` / `get summary` | 调用了 `minutes list` + 明确告知无数据 | +| 原文文件导出 | `minutes list all --start --end` → 对每条听记 `get transcription` 翻页拉全 → 交付真实原文 | 调用了 `minutes list` + 明确告知无数据;禁止用 summary 替代 | +| 链接解析 | `dws doc read --url ` | 调用了 `dws doc` 命令 + 告知错误原因 | +| 聚焦原话/沟通细节 | `minutes list all` → `get transcription`(翻页拉全) | 禁止仅凭 summary 出稿 | +| 聊天消息中听记链接 | 解析 `minutesId` → `minutes get summary/transcription` | 禁止把听记链接降级为关键词 | +| 按标签筛选听记 | `minutes tag list` → 匹配 tagId → `minutes tag query --tag-id ` | 调用了 `tag list` + 告知无标签或无结果 | + +#### 3.5 听记取数深度约束(0609 点踩 case 提炼) + +> 以下约束覆盖周报/日报/汇报等需要从听记提取内容的全部场景。详细说明见 [minutes.md](./minutes.md)。 + +| 约束 | 说明 | 真实 badcase | +|------|------|------------| +| **转写原文硬约束** | 用户诉求含「聚焦原话/逐字/沟通细节」等词时,**必须先调 `get transcription` 翻页拉全**,禁止仅凭 summary 出稿 | 模型以"通话短摘要够用"跳过转写,输出泛化套话(457c3f0) | +| **数据源下钻** | 听记维度**必须 `get summary`(或 `get transcription`)读正文**,严禁只取标题列表;scope 用 `all`;空时换窗重试或标注 | 听记只取标题不调 get summary,周报严重不完整(76f1d42) | +| **听记链接解析** | 聊天消息中遇到听记链接(`flash_minutes_detail`/`SHANJI`)→ 解析 `minutesId` → 调 `minutes get summary/transcription` | 群里日会是听记链接却不读正文,日报缺失(7b8e002) | +| **忠实性约束** | 源数据无某要素(行动项/责任人/数字)时禁止生成;统计字段基于实际取数计数 | 取数成功但篡改成带责任人的 ActionItems、虚报文档数(14e3365) | +| **多源全覆盖** | 用户枚举多数据源时每个来源都必须调对应工具;瞬时错误重试;如实声明缺失来源禁编无来源数字 | 4 类数据源点名,chat/minutes 完全没调,编精确数字(f3197dd) | + +#### 4. 通用规范 + +- **所有 dws 命令必须带 `--format json`**,确保输出可解析 +- **参数错误重试不超过 3 次**:同一命令因参数错误失败后,最多调整参数重试 3 次,超过则告知用户并给出替代方案 +- **不要要求用户上传音频文件**:听记场景应通过 `minutes list` 获取已有记录,而非要求用户提供文件 +- **有时间线索时优先使用 `--start` / `--end` 服务端过滤**:如「昨天的听记」应计算昨日时间范围并传参,而非全量拉取后客户端过滤 diff --git a/skills/multi/dingtalk-minutes/references/10-minutes-speaker-match.md b/skills/multi/dingtalk-minutes/references/10-minutes-speaker-match.md index 729a46ec..671041c6 100644 --- a/skills/multi/dingtalk-minutes/references/10-minutes-speaker-match.md +++ b/skills/multi/dingtalk-minutes/references/10-minutes-speaker-match.md @@ -1,7 +1,7 @@ # 听记发言人智能匹配 > 从钉钉听记中定向提取并总结指定人物的发言内容。 -> 听记产品命令见 [minutes.md](./minutes-commands.md);跨产品检索/消息/文档能力按需加载 `dingtalk-aisearch`、`dingtalk-chat`、`dingtalk-doc`。 +> 听记产品命令见 [minutes.md](./minutes.md);跨产品检索/消息/文档能力按需加载 `dingtalk-aisearch`、`dingtalk-chat`、`dingtalk-doc`。 | Recipe | 行动指南(固定路线) | | ------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | @@ -47,7 +47,7 @@ | 优先级 | 路径 | 做什么 | | ------ | ------------------ | ---------------------------------------------------------------------------------------------------- | | ① 最高 | 日程参与人 | 从听记 info 提取会议时间 → `dws calendar event list --start <会议开始-30min> --end <会议结束+30min>` → 匹配同时段日程 → `dws calendar participant list --event ` → 获取参与人姓名列表,与转写中匿名发言人做数量/顺序对照 | -| ② 高 | 通讯录 + 组织架构 | `dws contact user search --query <姓名> --format json` → 部门/职级/上级/汇报关系 | +| ② 高 | 人员 + 组织架构 | `dws aisearch person --keyword <姓名> --dimension name --format json` → userId,再 `dws contact user get --ids ` 补详情 | | ③ 高 | 聊天记录 | `dws chat message list` 获取与目标人的近期 IM 消息 → 工作内容/语言风格/职责线索 | | ④ 兜底 | 本人文档 | `dws drive search --query <姓名>` 取前 3 篇标题+摘要 → 角色精确信号 | diff --git a/skills/multi/dingtalk-minutes/references/11-minutes-speaker-correct.md b/skills/multi/dingtalk-minutes/references/11-minutes-speaker-correct.md index 3843a954..0d21e734 100644 --- a/skills/multi/dingtalk-minutes/references/11-minutes-speaker-correct.md +++ b/skills/multi/dingtalk-minutes/references/11-minutes-speaker-correct.md @@ -83,7 +83,7 @@ ffmpeg -y -ss 465 -i audio.mp3 -t 10 -c copy tmp/发言人3_片段.mp3 -loglevel | 优先级 | 路径 | 做什么 | |--------|------|--------| -| ① 最高 | 通讯录 + 组织架构 | `dws aisearch person --keyword <姓名> --dimension name`(或 `dws contact user search --query <姓名> --format json`)→ 部门/职级/上级/汇报关系 | +| ① 最高 | 人员 + 组织架构 | `dws aisearch person --keyword <姓名> --dimension name` → userId,再 `dws contact user get --ids ` 补部门/职级等详情 | | ② 高 | 聊天记录 | `dws chat message list` 获取与目标人的近期 IM 消息 → 工作内容/语言风格/职责线索 | | ③ 兜底 | 本人文档 | `dws drive search --query <姓名>` 取前 3 篇标题+摘要 → 角色精确信号 | @@ -112,7 +112,7 @@ ffmpeg -y -ss 465 -i audio.mp3 -t 10 -c copy tmp/发言人3_片段.mp3 -loglevel 确认对应关系后,**必须先查询通讯录获取 dingUid**: -1. 调用 `dws contact user search --query "<姓名>" --format json` +1. 调用 `dws aisearch person --keyword "<姓名>" --dimension name --format json` 2. 从返回结果提取 `userId`(长整型数字,即 dingUid) 3. 根据匹配结果执行替换: @@ -131,12 +131,12 @@ ffmpeg -y -ss 465 -i audio.mp3 -t 10 -c copy tmp/发言人3_片段.mp3 -loglevel ## 注意事项 -- **保护内部处理细节**:不要向用户暴露本地文件路径、ffmpeg 命令、内部评分或时间戳选择逻辑。代表性音频发送完成后,请用户回复发言人与姓名的对应关系。 +- **静默执行,绝不透出中间过程**:从用户选择「听音识人」到钉钉发送完毕,所有操作均静默完成。严禁向用户输出任何中间状态,包括:下载进度("音频下载完成")、裁剪阶段说明("开始裁剪"、"裁剪完成,现在发送")、工具执行统计("已运行 X 条命令"、"编辑 X 个文件")、ffmpeg 命令/评分逻辑/时间戳/选取依据,以及一切"正在......"/"现在......"/"接下来......"的过程描述。全部音频发送完毕后,只说一句话请用户回复对应关系 - **不输出表格和链接**:不向用户展示音频文件列表表格、不输出任何下载链接,音频一律通过钉钉单聊文件消息直接发送 - **不做身份画像分析**:不提供语言风格、决策权等文字画像 - **串行处理**:逐条执行裁剪命令,不使用 shell `&` 并行 - **全已识别情况**:若所有发言人均已识别,直接告知「所有发言人已识别,无需修改」 -- **通讯录查询必跑**:替换前必须调用 `dws contact user search` 获取 userId,严禁跳过 +- **人员搜索必跑**:替换前必须调用 `dws aisearch person` 获取 userId,严禁跳过 - **依赖工具**:需要 `ffmpeg` 可用,以及能够访问听记原始音频的权限(通过 `dws` 技能获取) ## 异常处理 diff --git a/skills/multi/dingtalk-sheet/references/_common/conventions.md b/skills/multi/dingtalk-minutes/references/_common/conventions.md similarity index 81% rename from skills/multi/dingtalk-sheet/references/_common/conventions.md rename to skills/multi/dingtalk-minutes/references/_common/conventions.md index 75491aab..803212b8 100644 --- a/skills/multi/dingtalk-sheet/references/_common/conventions.md +++ b/skills/multi/dingtalk-minutes/references/_common/conventions.md @@ -9,7 +9,7 @@ | 1 | **并行查详情**:拿到多个 ID 后,用 `&` 合并到同一条 Shell 命令并行执行 + `wait`,**严禁逐条串行** | | 2 | **翻页**:分页接口须拉全直至无更多 | | 3 | **优先批量 API**:有批量接口则用批量;无则按 #1 并行 | -| 4 | **群消息**:必须先 `chat search --query` 得 `openConversationId`,再 `chat message list --group`;多群同条命令并行 | +| 4 | **群消息**:必须先 `chat search --query` 得 `openConversationId`,再 `chat message list --group --time "" --direction older`;多群同条命令并行 | | 5 | **列表少轮次**:带条件搜索/列表 → 一次采全详情;**禁止**无新参数时重复同一 `list` / `search` | ## 多源并行采集(公共模式) @@ -29,7 +29,7 @@ |------|------|--------| | `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 | +| `deptId` | `contact dept search` | `contact dept list-members --ids `;多子部门时对每个子部门分别 `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` | @@ -38,7 +38,7 @@ | `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` | +| `dentryUuid` | `drive list` / `drive mkdir` | `drive info/download/copy/move/rename/delete --node`、`drive list/mkdir/upload/copy/move --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,不能把该数字直接代入后续命令。 +**ID 边界硬约束**:`dentryId` 通常是纯数字,只表示聊天文件消息需要的钉盘条目数字 ID;它不是父目录 ID。遇到 `drive --node/--folder`、`doc --node`、`wiki node --folder` 时,只能使用 `dentryUuid` / `nodeId` / 文档 URL。若当前上下文只有数字型 `dentryId`,必须先重新 `drive list` / `drive search` / `wiki node list` 获取正确 ID,不能把该数字直接代入后续命令。 diff --git a/skills/multi/dingtalk-minutes/references/lite-recipes.md b/skills/multi/dingtalk-minutes/references/lite-recipes.md index 359ff5e8..c5ffd820 100644 --- a/skills/multi/dingtalk-minutes/references/lite-recipes.md +++ b/skills/multi/dingtalk-minutes/references/lite-recipes.md @@ -1,56 +1,136 @@ # minutes Lite Recipe -> 完整命令路由见 [minutes-index.md](./minutes-index.md),高级会后流程见 [07-minutes.md](./07-minutes.md)。 +本文件从单 Skill `lite-recipes.md` 拆分而来,仅保留与本产品相关的轻量流程。 -## minutes-query +## #7 听记与会后 -- 默认范围:`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 非空时继续 +> 产品命令完整参考见 [minutes.md](./minutes.md)。full recipe 见 [07-minutes.md](../../dingtalk-minutes/references/07-minutes.md)。 -对象选择必须使用本次 `list` 返回的 taskUuid,并同时核对标题、组织和时间。多候选时让用户确认。 +### minutes-query(查询与获取) -## minutes-edit +> **scope 选择铁律(P2 真实 badcase)**:`list` 后的 scope 决定查询范围,最高频误判是把"我能访问的所有听记"错选成 `mine`: +> - `mine` = **仅我自己创建/发起**的听记(范围最窄) +> - `shared` = **仅他人共享给我**的听记 +> - `all` = **我可访问的全部**(= mine ∪ shared,范围最广) +> - **判定口诀**:query 含"访问/权限/可见/能看到/所有/我的"等覆盖范围语义 → 一律走 `all`;**仅当**明确说"我创建的/我发起的/我录的" → 才走 `mine`。**不要因为句子里有"我"字就退化成 `mine`。** +> - 错误:`我能访问的所有听记` → `list mine`(漏掉共享给我的,判定不通过) +> - 正确:`我能访问的所有听记` → `dws minutes list all --format json` -- 修改标题:`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` +> **选对象铁律(0605 P2 EDD badcase 提炼,命令对了但选错听记 = 整任务失败)**:list/搜索拿到结果后,必须按语义精准锁定目标听记,详见 [minutes.md](./minutes.md)「选对象铁律 S1~S6」。速记: +> - **S1 跨组织汇总**:以 list 返回的 `taskUuid + title + organizationName` 三元组为准逐条照抄,组织与听记不可张冠李戴。 +> - **S2 "最近一次某类会议"**:先 `--query "<主题词>"`(如周会)过滤出该类,再在候选里取时间最新;主题匹配优先级高于时间。 +> - **S3 比时长最长**:必须读 `durationMicros` 字段做数值比较,禁止凭印象/标题猜,口头结论与操作的 taskUuid 须自洽。 +> - **S4 内容为空**:锁定 taskUuid 后所有 get/update 复用同一 id;某字段为空就如实说,**禁止偷偷切换到另一条听记**。 +> - **S5 模糊日期匹配不到**:日期可能是"会议主题日期"而非"创建日期",按标题关键词搜,精确日期没命中就放宽 ±7 天/同主题候选请用户确认,**禁止直接报"找不到"**。搜索回退策略:① 先 `--query "<主题关键词>"` 不带日期搜 → ② 若结果过多则加 `--start/--end` 扩大到 ±7 天 → ③ 列出候选让用户确认。 +> - **S6 给标题没给 id**:必须先 `list all --query "<标题关键词>"` 定位 taskUuid 再 update/get,禁止凭记忆直接填 `--id` 跳过定位。 -所有写操作先定位唯一 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 +**列表查询**(`list` 后**必须**跟 scope:`mine`/`shared`/`all`,默认补 `all`): ```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 +# 我可访问的所有听记(默认) +dws minutes list all --format json +# 按关键词服务端搜索(严禁全量拉取后本地 grep) +dws minutes list all --query "周会" --format json +# 按时间范围筛选(ISO-8601 格式) +dws minutes list mine --start "2026-05-01T00:00:00+08:00" --end "2026-05-25T23:59:59+08:00" --format json +# 关键词 + 时间组合 +dws minutes list all --query "需求评审" --start "2026-05-25T00:00:00+08:00" --end "2026-05-25T23:59:59+08:00" --format json +# 限制条数 +dws minutes list mine --limit 5 --format json +# 共享给我的听记 +dws minutes list shared --query "ROI" --format json ``` -使用 `create` 返回的 sessionId 和上传地址;文件大小单位是字节。 +| 参数 | 说明 | +|------|------| +| `--query "<关键词>"` | 服务端关键词搜索 | +| `--start ""` | 开始时间 | +| `--end ""` | 结束时间 | +| `--limit ` | 每页条数,默认 10(`--max` 为兼容别名) | +| `--cursor ""` | 分页 token,首页留空(`--next-token` 为兼容别名) | -## 数据深度 +**获取详情**: -- 只要列表时不要额外拉取转写。 -- 要摘要时读取 `get summary`。 -- 要原话、逐字稿或沟通细节时读取 `get transcription` 并翻页至结束。 -- 要待办时读取 `get todos`;缺少责任人或期限时先确认。 -- 多数据源任务分别读取各来源并如实标注缺失项。 +- 批量基础信息:`minutes get batch --ids ` +- 单篇摘要:`minutes get summary --id ` +- 转写原文(自动翻页):`minutes get transcription --id `(返回 `nextToken` 时用 `--next-token ` 继续) +- 关键词:`minutes get keywords --id ` +- 待办事项:`minutes get todos --id ` +- 基础信息:`minutes get info --id ` +- 音频地址:`minutes get audio --id ` + +> `--id`/`--uuid`/`--task-uuid` 三者等价。推荐 `--id`。 + +### minutes-edit(编辑与替换) + +- **替换转写文字**:`minutes replace-text --id --search "旧文字" --replace "新文字"` + - 执行前检查特殊字符(引号/书名号/括号等),若包含先提示用户确认去除 + - 替换成功后追问是否加热词:`minutes hot-word add --words "新文字"` +- **替换发言人**:先统一搜人取得 dingUid → `minutes speaker replace --id --from "发言人X" --to "姓名" --target-uid ` + - 查询 dingUid:`aisearch person --keyword "姓名" --dimension name --format json` → 取 `userId` + - 多个匹配 → 列出候选让用户选;无匹配 → 不带 `--target-uid` 执行 +- **修改标题**:`minutes update title --id --title "新标题"` +- **修改摘要**:`minutes update summary --id --content "新内容"` +- **热词管理**:`minutes hot-word add --words "词1,词2"` / `minutes hot-word list` +- **思维导图**:`minutes mind-graph create --id ` → `mind-graph status --id ` 轮询至完成 + +### minutes-tag(标签/分组查询) + +- 查询标签列表:`minutes tag list` → 返回用户在听记页面创建的所有标签/分组(含 tagId 和名称) +- 按标签查听记:`minutes tag query --tag-id [--limit 20] [--cursor ]` + - tagId 来自 `tag list` 返回值,不可编造 + - 支持分页,`--cursor` 传入上一次返回的 nextToken + +**典型链路**:用户说"帮我看看'周会'标签下的听记" → +1. `dws minutes tag list --format json` → 按名称匹配找到 tagId +2. `dws minutes tag query --tag-id --format json` + +### minutes-permission(权限管理) + +- 添加成员:`minutes permission add --ids --member-uids --policy 4` + - 需先通过 `aisearch person --keyword "<姓名>" --dimension name` 获取目标 userId + - policy:0=不可见 / 1=仅查看 / 2=查看+下载 / 3=查看+下载+编辑 / 4=全部权限 +- 移除成员:`minutes permission remove --ids --member-uids ` + +### minutes-upload(音频上传) + +```bash +# 创建上传会话 +dws minutes upload create --file-name "meeting.mp3" --file-size 61565431 --format json +# 上传完成后确认 +dws minutes upload complete --session-id --format json +# 取消上传 +dws minutes upload cancel --session-id --format json +``` + +### 最佳实践案例速查(详见 [minutes.md](./minutes.md)) + +| 案例 | 场景 | 正确链路 | +|------|------|----------| +| 案例 1 | 听记 URL + 创建思维导图 | 提取 taskUuid → `mind-graph create` → `mind-graph status` 轮询;**禁止**走 app-development 或前端库 | +| 案例 2 | 替换文字后未引导热词 | 检查特殊字符 → `replace-text` → 追问加热词 `hot-word add` | +| 案例 3 | 查听记拉了不必要的转写 | 用户只要列表 → `list` 即可,**不要**自动拉 `get transcription` | +| 案例 4 | 拉完转写只输出时间线原文 | 拉完后追问按发言人聚类 → 引导匹配 → 调用 `speaker replace` 写回 | +| 案例 5 | 查某人说了什么不引导替换 | 推断发言人 → **用户确认** → 结构化总结 → 引导 `speaker replace` | +| 案例 6 | 通讯录+部门+转写三路印证 | Step 3 画像 + Step 4 `aisearch person` 并发 → 置信度 ≥70% → 确认 → 替换 | +| 案例 7 | grep 花名误判未参会 | **禁止**在转写文本里 grep 人名判参会;**必须**调 `aisearch person` | +| 案例 8 | 听记类 query 不走 dws | **禁止**用 session_search/browser_use/activity:search 替代 dws;模糊请求先 `list mine` | +| 案例 9 | 按标签筛选听记 | `tag list` → 按名称匹配 tagId → `tag query --tag-id `;**禁止**编造 tagId | + +### 听记取数深度约束(0609 点踩 case 提炼) + +> 详细说明见 [minutes.md](./minutes.md)。 + +- **转写原文硬约束**:用户诉求含「聚焦原话/逐字/沟通细节/具体讨论了什么」等词时,**必须先调 `get transcription` 翻页拉全**,禁止仅凭 summary 出稿 +- **数据源下钻**:听记维度**必须 `get summary`(或 `get transcription`)读正文**,严禁只取标题列表;scope 用 `all`;空时换窗重试或标注 +- **听记链接解析**:聊天消息中遇到听记链接(`flash_minutes_detail`/`SHANJI`)→ 解析 `minutesId` → 调 `minutes get summary/transcription`,禁止把链接降级为关键词 +- **忠实性约束**:源数据无某要素(行动项/责任人/数字)时禁止生成;统计字段基于实际取数计数,不得编造 +- **多源全覆盖**:用户枚举多数据源时每个来源都必须调对应工具;瞬时错误重试;如实声明缺失来源,禁编无来源数字 + +### 间接意图识别铁律 + +query 未提"听记"但任务产出依赖会议讨论内容时(报告/总结/日报/复盘/商业分析/市场感知),听记采集是**必跑前置步骤**: + +1. **铁律 A**:任务含"会议/讨论/沟通"信息需求 → `dws minutes list` 必跑 +2. **铁律 B**:用户说"文档啥也没有" → 听记优先级更高(唯一结构化数据源) +3. **铁律 C**:多源聚合场景 → 每个被提及的数据源都必须有采集动作,听记侧 0 调用 = 严重失败 diff --git a/skills/multi/dingtalk-minutes/references/minutes-commands.md b/skills/multi/dingtalk-minutes/references/minutes-commands.md deleted file mode 100644 index d029b156..00000000 --- a/skills/multi/dingtalk-minutes/references/minutes-commands.md +++ /dev/null @@ -1,877 +0,0 @@ -# 听记命令参考 - -## 命令总览 - -### 查询我创建的听记列表 -``` -Usage: - dws minutes list mine [flags] -Example: - dws minutes list mine - dws minutes list mine --limit 10 - dws minutes list mine --limit 10 --cursor - dws minutes list mine --query "周会" -Flags: - --limit float 每页数据条数 (默认 10) - --cursor string 分页 token (首页留空,后续填写前次返回的 nextToken) - --query string 关键字筛选 (可选) - --start string 开始时间 ISO-8601 (可选) - --end string 结束时间 ISO-8601 (可选) -``` - -查询我创建的听记列表,支持 `--limit` 和 `--cursor` 分页,支持按关键字和时间范围筛选。 - -### 查询他人共享给我的听记列表 -``` -Usage: - dws minutes list shared [flags] -Example: - dws minutes list shared - dws minutes list shared --limit 20 - dws minutes list shared --limit 5 --cursor -Flags: - --limit float 每页数据条数 (默认 10) - --cursor string 分页 token (首页留空,后续填写前次返回的 nextToken) - --query string 关键字筛选 (可选) - --start string 开始时间 ISO-8601 (可选) - --end string 结束时间 ISO-8601 (可选) -``` - -查询他人共享给我的听记列表,支持 `--limit` 和 `--cursor` 分页,支持按关键字和时间范围筛选。 - -### 查询我有权限访问的所有听记列表 -``` -Usage: - dws minutes list all [flags] -Example: - dws minutes list all - dws minutes list all --limit 20 - dws minutes list all --query "周会" --limit 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 --limit 10 --cursor -Flags: - --end string 结束时间 ISO-8601 (可选) - --query string 关键字筛选 (可选) - --limit float 每页数据条数 (默认 10) - --cursor string 分页 token (首页留空,后续填写前次返回的 nextToken) - --start string 开始时间 ISO-8601 (可选) -``` - -查询我有权限访问的所有听记列表(包括我创建的、他人共享给我的等所有有权限的听记)。支持按关键字和时间范围筛选。时间范围和关键字为可选参数,不传则返回所有有权限的听记。支持使用 `--limit` 和 `--cursor` 进行分页查询。 - -### 获取听记基础信息 -``` -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 --cursor --format json -Flags: - --direction string 排序方向: 0=正序, 1=倒序 (默认 0) - --id string 听记 taskUuid (必填),取值逻辑参考 ## 注意事项 - --cursor string 分页 token,首次查询留空,后续填写前次返回的 nextToken -``` - -每条记录包含: 发言人信息、转写文本、对应时间戳 - -**关键:转写接口每次最多返回 50 段,必须自动翻页才能拿到完整原文!** - -> **这是最高频的 silent failure:** `get transcription` 单次调用**最多只返回 50 段**。一篇 30 分钟会议的转写通常有 150-400 段,如果不翻页就只能看到前 1/3 甚至 1/8 的内容。 -> -> **分页机制(必须掌握):** -> - 首次调用:`dws minutes get transcription --id --format json`(不传 `--cursor`) -> - 检查返回 JSON 中是否包含 `nextToken` 字段(非空字符串) -> - 如果有 `nextToken`:继续调用并传入 `--cursor <上次返回的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 --cursor --format json -> all_paragraphs.append(result.paragraphs) -> next_token = result.nextToken # 可能为空或不存在 -> if next_token 为空或不存在: -> break # 全部拉取完毕 -> # 此时 all_paragraphs 才是完整转写原文 -> ``` -> -> **典型错误(导致“总结整篇听记”只覆盖前 50 段):** -> - [错误] 只调用一次 `get transcription`,看到返回了内容就以为拿全了 → 实际只有前 50 段 -> - [错误] 看到返回 JSON 里有 `nextToken` 字段但不知道这是什么、不知道要传 `--cursor` → 默默丢弃了后续页 -> - [错误] 用 `--help` 查参数但在 Windows 下输出被截断,没看到 `--cursor` 参数 → 以为不支持分页 -> - [正确] **正确做法:无条件按上述循环逻辑自动翻页,直到 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 --query "目标人名"` → 部门/职级/上级/真名 | 职能大类(技术/产品/设计/管理)+ 是否存在该人 | -| ② 本人创建的文档 | `dws doc search --query "目标人名/真名"` 至少获取 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 -Flags: - --ids string 听记 taskUuid 列表,逗号分隔 (必填) -``` - -触发创建发言人的段落总结任务,将听记中每位发言人的所有发言内容汇总总结。触发后需调用 `speaker summary get` 查询总结结果。 - -统一使用 canonical 参数 `--ids`。 - -### 查询发言人段落总结结果 -``` -Usage: - dws minutes speaker summary get [flags] -Example: - dws minutes speaker summary get --ids -Flags: - --ids string 听记 taskUuid 列表,逗号分隔 (必填) -``` - -查询发言人段落总结任务的结果,返回每位发言人的发言汇总。需先调用 `speaker summary create` 触发任务。 - -统一使用 canonical 参数 `--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" -Flags: - --ids string 听记 taskUuid 列表,逗号分隔 (必填) - --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 -Flags: - --ids string 听记 taskUuid 列表,逗号分隔 (必填) - --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 deleted file mode 100644 index 7a093c00..00000000 --- a/skills/multi/dingtalk-minutes/references/minutes-error.md +++ /dev/null @@ -1,91 +0,0 @@ -# 听记错误诊断与恢复 - -> 命令层级与对象选择见 [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 deleted file mode 100644 index 13638126..00000000 --- a/skills/multi/dingtalk-minutes/references/minutes-index.md +++ /dev/null @@ -1,13 +0,0 @@ -# 听记能力索引 - -| 主题 | 详见 | -|---|---| -| 命令层级、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 deleted file mode 100644 index 2797b49b..00000000 --- a/skills/multi/dingtalk-minutes/references/minutes-intent.md +++ /dev/null @@ -1,414 +0,0 @@ -# 听记意图判断 - -## 意图判断 - -### 发起听记 -用户说"开始听记/开始录音/发起一个听记/启动听记/开始听记录这次会议/我要开始听记了/录音开始/我要听记" → `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 deleted file mode 100644 index f1dc7ea7..00000000 --- a/skills/multi/dingtalk-minutes/references/minutes-overview.md +++ /dev/null @@ -1,121 +0,0 @@ -# 听记命令层级与路由 - -> 本文只保留命令导航和跨命令不变量。具体参数见 [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 deleted file mode 100644 index f2abe153..00000000 --- a/skills/multi/dingtalk-minutes/references/minutes-workflow.md +++ /dev/null @@ -1,89 +0,0 @@ -# 听记核心工作流与上下文 - -## 核心工作流 - -```bash -# 0. 发起听记(开始录音) -dws minutes record start --format json - -# 1. 查看我的听记列表 — 提取 taskUuid -dws minutes list mine --format json -dws minutes list mine --limit 10 --cursor --format json -dws minutes list mine --query "周会" --format json - -# 1b. 查看共享给我的听记 -dws minutes list shared --limit 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;翻页时 --cursor | -| `list shared` | `taskUuid`、`nextToken` | get/update 的 --id;翻页时 --cursor | -| `list all` | `taskUuid`、`nextToken` | get/update 的 --id;翻页时 --cursor | -| `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 3eed6075..7a8c9b19 100644 --- a/skills/multi/dingtalk-minutes/references/minutes.md +++ b/skills/multi/dingtalk-minutes/references/minutes.md @@ -1,11 +1,2515 @@ -# AI 听记 (minutes) 命令参考 +# AI听记 (minutes) 命令参考 -> 渐进式兼容入口:新任务优先从 [听记能力索引](./minutes-index.md) 选择专题。 +## 命令层级结构(防混淆,必须先读) -| 任务 | 参考 | -|---|---| -| 命令层级与指令树 | [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) | +minutes 模块的命令是**两级或三级结构**,不同层级之间不能混用、跳级或遗漏: + +> **默认 scope 规则**:`dws minutes list` 后**必须**跟 scope 子命令。若用户未明确指定查询范围,**一律默认补 `all`**(查询我可访问的所有听记 = 我创建的 + 他人共享给我的,覆盖面最广)。 +> +> **`list` 不带 scope 的陷阱(0519 P2 Golden Case 提炼)**: +> - 裸 `dws minutes list`(不跟 mine/shared/all)虽然不会报错,但返回结果**不完整**(仅返回最近少量条目,约 913 字节) +> - `dws minutes list all` 才能返回完整列表(约 3362 字节) +> - **AI 严禁使用裸 `dws minutes list`**,必须始终带 scope 子命令 +> - 如果 LLM 不确定用哪个 scope,**一律用 `all`** +> +> **scope 语义辨析(高频误判场景)**: +> - `mine` = **仅**我自己创建/发起的听记(范围最窄) +> - `shared` = **仅**他人共享给我的听记(不含我自己创建的) +> - `all` = 我可访问的**所有**听记(mine + shared 的并集,范围最广) +> +> **关键区分**:用户说"我可访问的"/"我能看到的"/"我有权限的"/"所有听记" → 选 `all`(不是 `mine`!);用户说"我自己创建的"/"我发起的"/"我录的" → 才选 `mine`。 +> +> **P2 真实 badcase(必读,最高频误判)**:用户 query「**我能访问的所有听记列表**」/「**我有权限的听记**」/「**帮我看一下我能看到的所有听记**」时,LLM 把"我能访问的""我"误推理成 `list mine`,导致只返回我自己创建的,遗漏他人共享给我的听记,**判定不通过**。 +> - 牢记口诀:**`mine` = 我创建的(窄);`all` = 我可见的全部(宽,= mine ∪ shared)**。 +> - 只要 query 里出现"**访问/权限/可见/能看到/所有**"等覆盖范围语义词,**无条件选 `all`**,绝不能因为句子里有"我"就退化成 `mine`。 +> - 反例对照: +> - 错误:`我能访问的所有听记` → `dws minutes list mine`(只查我创建的,漏掉共享的) +> - 正确:`我能访问的所有听记` → `dws minutes list all --format json` +> - 错误:`我有权限的听记帮我看一下` → `dws minutes list mine` +> - 正确:`我有权限的听记帮我看一下` → `dws minutes list all --format json` + +``` +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 # 获取听记基础信息(标题、时长、参与人、状态等) +│ ├── 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 # 移除成员权限 +└── tag # 听记标签/分组管理(标签在听记页面手动创建) + ├── list # 查询我的听记标签/分组列表(返回 tagId 和标签名称) + └── query --tag-id [--limit] [--cursor] # 根据标签ID查询该标签下的听记列表 +``` + +**高频错误命令 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 ` | +| `minutes get --uuid ` | `get` 后缺少子命令(info/summary/transcription 等) | `dws minutes get summary --id ` | +| `minutes get --task-uuid ` | 同上,`get` 后缺子命令。注意:`--task-uuid` 在子命令层是合法别名,但 `get` 层级不认识它 | `dws minutes get info --id ` | +| `dws minutes detail --id ` | `detail` 不是合法子命令——**不存在此命令**。LLM 高频幻觉 | `dws minutes get info --id ` | +| `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"` | +| `minutes list --page-size 10` | `--page-size` 不存在,分页用 `--max`;且 `list` 后缺 scope | `dws minutes list mine --max 10` | +| `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` 尚未注册(跨产品规约 Primary 为 `--limit`,但 minutes CLI 当前只接受 `--max`)。**待 alias 注册后两者等价** | `dws minutes list mine --max 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` | +| `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` 未注册 | 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`,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 all`(默认 scope)获取列表,从返回结果中按标题/参会人/时间匹配选出正确条目,再用其 taskUuid 调用后续命令。**绝不允许凭空编造 hex 字符串作为 --id 的值。** + +> **【0529 P2 Golden Case 提炼】按标题/关键词定位某篇听记时,scope 必须用 `all`,严禁用 `mine`:** +> - 真实 badcase:用户「帮我分别看看『4 月评测自动化周报』和『评测系统重构分工』的 AI 摘要」,LLM 用 `dws minutes list mine --query "..."` 搜索 → 因这两篇是**他人共享给我的听记**(不在 `mine` 范围)→ 返回为空 → 误判"找不到该听记"。而用户手动在客户端能搜到(客户端搜的是全部可见听记)→ 表现为"手动找得到、任务执行找不到"。 +> - **根因**:`mine` 仅含我自己创建的听记,`all` = 我可访问的全部(mine ∪ shared)。按标题定位时**无法预知**目标听记是我创建的还是别人共享的,因此**只能用 `all`** 才不会漏。 +> - **铁律**:凡是"按标题/关键词/参会人/时间去定位某篇(或某几篇)听记"的场景,**一律 `dws minutes list all --query "<关键词>"`**,绝不能因为铁律里出现"我的听记"就退化成 `list mine`。 + +典型错误链路: +``` +用户: "帮我看一下上次和 Tony 开会的听记摘要" +错误1: LLM 直接编造: dws minutes get summary --id "7632756964..." -- 这个 id 是捏造的 +错误2: LLM 用 dws minutes list mine 搜索 -- 若该听记是他人共享的则搜不到,误报"找不到" +正确: 先 dws minutes list all --query "Tony" → 从结果按标题/参会人匹配 → 再 get summary --id <真实id> +``` + +**「分别看 N 篇」多篇定位场景(0529 P2 提炼):** + +> 用户一次性要求查看多篇听记(如"分别看看 A 周报和 B 分工的摘要")时: +> 1. **优先一次 `dws minutes list all` 拉全列表**(必要时按 `--query` 各自的关键词或不带 query 拉全后在内存中匹配),分别匹配出 A、B 两篇的 taskUuid +> 2. 对每个 taskUuid **逐一串行**调用 `get summary`(严禁 shell `&` 并行 / `|` 管道) +> 3. 若某一篇匹配不到,先用**更宽松的关键词**或**不带 query 的 `list all`** 再确认一次,仍找不到才告知用户该篇未定位到,并列出已找到的近似标题供用户确认——**严禁一篇搜不到就直接判定"听记不存在"** + +**选对象铁律:list/搜索拿到结果后,必须按语义精准锁定目标听记(0605 P2 EDD badcase 提炼)** + +> 这是历史任务中**最高频、最致命**的一类失败:命令写得完全正确,但**选错了听记对象**,导致后续所有 get summary / keywords / update 操作全部作用在错误条目上,整任务判定不通过。即使 LLM 评分给到 30~55,只要选错对象就是"核心目标未达成"。 +> +> **铁律 S1 —— 跨组织/多组织汇总时,组织与听记的对应关系绝不能张冠李戴:** +> - 真实 badcase(用例 20260513_dws_minutes_0028「跨组织听记汇总」):用户「帮我看看我所有听记」,list 返回多个组织下的听记,模型把「龙赓一家人」组织下的「12月文档切片讨论」误识别为「晨会427」,把「云购电商测试」下的「评测系统重构分工」误识别为「DWS功能对齐与评测优化」,6 个 CRITICAL 检查点错 4 个。 +> - **根因**:list 返回的每条听记都带有 `organizationName` / `orgName`(或所属组织/空间)字段,模型未逐条核对组织归属,凭标题相似度乱配。 +> - **做法**:跨组织汇总时,**必须**以 list 返回里每条记录的 `taskUuid` + `title` + `organizationName` 三元组为准,逐条原样引用,**严禁**对标题做"近似改写""脑补归并"。展示时按组织分组,组织名和听记标题都**逐字照抄** list 返回值,不得自创或张冠李戴。 +> +> **铁律 S2 —— "最近一次/最新/上一次某类会议"必须用结构化字段判断,不能凭标题猜:** +> - 真实 badcase(用例 0026「周会回顾整理」):用户「帮我找一下最近一次周会的听记」,应命中「4月9日周会纪要」(2026-04-09),模型却选了「2026-06-02 17:33 录音」这条普通录音。 +> - **根因**:模型把"最近"简单理解成"列表里时间最新的那条",忽略了"周会"这个**主题限定词**——普通录音不是周会。 +> - **做法**:解析"最近一次 + <主题词>"时,先用 `--query "<主题词>"`(如 `周会`)做服务端过滤缩小候选,再在候选里按 `createTime` / 开始时间字段取最新的一条。**主题限定优先级高于时间**:先满足"是这类会议",再在其中比"最近"。绝不能在全量列表里只挑时间最新而忽略主题是否匹配。 +> +> **铁律 S3 —— 比较"最长/时长最长"听记时,必须读 `durationMicros` 实际字段,不能凭自己声称的时长:** +> - 真实 badcase(用例 0030「批量听记信息整理」):模型口头声称最长的是「【内部会议】测试报告与多模型适配进展」(约12分钟),实际操作的 taskUuid 却对应「4月评测自动化周报」(约4分5秒),而真正 `durationMicros=737927000`(约12分钟)的「【内部会议】评测流程与报告优化」未被识别。 +> - **做法**:凡涉及"最长/最短/时长排序",**必须**从 list 或 `get info` 返回的 `durationMicros`(微秒)字段做数值比较,取最大值对应的 `taskUuid`,**严禁**凭自然语言印象或标题臆断时长。口头结论与实际操作的 taskUuid 必须自洽——说哪条最长,就操作哪条。 +> +> **铁律 S4 —— 目标听记内容为空/获取失败时,绝不允许擅自切换到另一条听记:** +> - 真实 badcase(用例 0020「搜索+摘要+关键词」):用户「帮我找一下关于'模型'的听记」,模型搜到第一条后,get summary/keywords 返回内容为空,便**自作主张换成另一条听记**展示关键词,违反「三次操作目标听记一致」的 CRITICAL 检查点。 +> - **做法**:一旦锁定目标 taskUuid,后续 get summary / keywords / transcription / update **必须始终复用同一个 taskUuid**。若某项内容为空或接口失败,应**如实告知用户"该听记的摘要/关键词暂为空"**,并询问是否查看其它字段或换一条,**绝不能在用户不知情的情况下偷偷切换对象**。另外,`--query "模型"` 搜索后要核对返回标题确实与"模型"主题相关,无关结果应说明而非强行展示。 +> +> **铁律 S5 —— 模糊日期("X月X日XX")匹配不到时,要放宽到邻近日期再确认,不能直接报"找不到":** +> - 真实 badcase(用例 0030):用户提到「4月9日周会纪要」,实际该听记创建于 4.14,模型严格按 4-09 精确匹配导致找不到。人工判定理由:未找到周会纪要,可能是因为4月9日周会纪要实际创建日期是4.14日。 +> - **根因**:用户给的日期常是"会议主题里的日期"而非"听记实际创建日期",二者可能错位数天甚至一周以上。 +> - **做法**:按标题关键词(如"周会纪要")`--query` 搜索为主,日期仅作辅助排序。**三级回退搜索策略**: +> 1. **首选**:`dws minutes list all --query "<主题关键词>" --format json`(不带日期,纯关键词搜)→ 在结果中按时间匹配最接近用户提到的日期的条目 +> 2. **次选**(结果过多时缩小范围):加 `--start`/`--end` 但**放宽到 ±7 天**,如用户说"4月9日" → `--start "2026-04-02T00:00:00+08:00" --end "2026-04-16T23:59:59+08:00"` +> 3. **兜底**(仍无结果):去掉日期相关词,只用核心主题词搜索(如"周会"),列出近期候选请用户确认 +> - **严禁**:① 按精确日期匹配不到就直接报"找不到"终止任务;② 只搜一次就放弃;③ 不列候选就判定"不存在" +> +> **铁律 S6 —— 用户给了标题但没给 taskUuid 时,必须先 list/搜索定位,禁止直接拿"记忆里的 id"操作:** +> - 真实 badcase(用例 0012「修改听记标题」):用户「把'1月22日废弃片段'这条听记标题改成'1月22日短会片段'」,模型跳过 list/搜索,直接用一个已知 ID 执行 `update title`,不符合流程规范(即使最终改对了也判不通过)。 +> - **做法**:只要用户是用**标题/关键词**指代听记(而非直接粘贴 taskUuid 或 URL),就**必须**先 `dws minutes list all --query "<标题关键词>"` 定位出唯一 taskUuid,再执行 update/get。**严禁**凭上下文记忆或猜测直接填 `--id` 跳过定位步骤。这与"禁止编造 taskUuid"铁律一脉相承:没有经过本轮 list/搜索确认的 id 一律不可信。 + +**意图→命令决策表(0512 P0 Golden Case 提炼):** + +| 用户意图关键词 | 对应命令 | 说明 | +|--------------|---------|------| +| 总结、摘要、重点、概述、主要内容、按模板整理 | `get summary` | 返回结构化摘要 | +| 逐字稿、全文、原文、完整记录、转写、录音文字 | `get transcription` | 返回完整转写原文 | +| "帮我看/总结某次会议"(未给 id) | 先 `list all`(默认) | 按标题/时间匹配后再 get | +| "我能访问的所有听记列表"/"我可访问的听记"/"我能看到的"/"我有权限的"/"所有听记" | `list all` | **不是 `list mine`!P2 真实 badcase**:query 里有"我能访问的""我有权限的"就被误判成 `mine`。只要出现"访问/权限/可见/能看到/所有"等覆盖范围词,无条件走 `all`,不能因为有"我"字退化成 `mine`。`all` = 我可访问的全部(mine + shared),`mine` 仅限我自己创建的 | +| "我的听记"(模糊表述) | `list all` | "我的"通常指用户可见的全部听记,默认走 `all`;仅当明确说"我创建/发起的"时才 `mine` | +| "我自己创建的听记"/"我发起的听记"/"我录的听记" | `list mine` | 明确限定创建者是自己时才走 `mine` | +| "上上周/两周前的会议" | `list all --start --end` | 需正确计算时间范围 | +| 添加参会者、批量添加参会者、添加成员、批量添加成员、加参会人 | `permission add` | 听记无独立"添加成员"接口,统一走权限接口 | +| "聚焦原话/逐字/根据听记内容/沟通细节/具体讨论了什么" | 先 `list all` → 再 `get transcription`(翻页拉全) | **禁止仅凭 summary 出稿**,必须拉转写原文。见下方「转写原文硬约束」 | + +**转写原文硬约束(0609 P0 点踩 case 提炼,trace: 457c3f0):** + +> 用户诉求涉及「聚焦原话 / 逐字 / 根据听记内容 / 沟通细节 / 具体讨论了什么 / 原始发言」等**需要还原真实发言**的关键词时: +> 1. **必须先调 `dws minutes get transcription --id --format json`** 翻页拉全完整转写原文,基于原文提炼输出 +> 2. **严禁**仅凭 `get summary` 的 AI 二手摘要换皮成泛化套话——summary 是结构化摘要,不含原始发言细节 +> 3. 通话类听记(时长较短)也不能跳过转写——"通话短、摘要够用"不是省略转写的理由 +> +> **真实 badcase**:用户要聚焦沟通内容,模型以「通话短摘要够用」为由跳过 `get transcription`,只拿 AI 二手摘要换皮成泛化套话 → 点踩。 + +**数据源下钻硬约束(0609 P0 点踩 case 提炼,trace: 76f1d42):** + +> 周报 / 汇报 / 多源汇总等需要从听记提取内容的场景: +> 1. 听记维度**必须** `get summary`(或 `get transcription`)读纪要正文,**严禁只取标题列表**当作"已读取内容" +> 2. 听记 scope **一律用 `all`**(周报场景可能涉及他人共享的会议听记) +> 3. 某维度数据为空时,**换时间窗重试**(如扩大 ±3 天)或**如实标注缺失**,不得静默跳过 +> 4. 交付前做**完整性自检**:每个用户要求的数据源维度(听记/日历/聊天/文档)是否都已实际调用并获取到内容 +> +> **真实 badcase**:把「列出数据源清单」当成「读取内容」,听记只取标题不调 `get summary`,文档不 `doc read`,群聊空不兜底,周报严重不完整 → 点踩。 + +**聊天消息中听记链接解析规则(0609 P1 点踩 case 提炼,trace: 7b8e002):** + +> 从聊天消息(`dws chat message list`)中遇到听记链接时: +> 1. **识别听记链接特征**:URL 中包含 `flash_minutes_detail` 或 `SHANJI` 路径、或包含 `minutesId` / `taskUuid` 参数 +> 2. **自动解析 `minutesId`**(即 `taskUuid`)→ 调 `dws minutes get summary --id ` 或 `get transcription` 读纪要正文 +> 3. **严禁**把听记链接降级成普通关键词——链接里有完整的 `taskUuid`,应直接用它获取内容 +> 4. 日报 / 周报场景,若聊天中有日会听记链接但未解析读取,属于**取数没跟到底**,交付物缺失 +> +> **真实 badcase**:群里日会是听记链接(带 minutesId),却从不调 `dws minutes get` 读纪要正文,把日会降级成关键词,日报缺失也不用 `report` 兜底 → 点踩。 + +**忠实性硬约束(0609 P1 点踩 case 提炼,trace: 14e3365):** + +> 基于听记数据生成长文(周报/汇报/纪要整理)时: +> 1. **源数据无某要素(行动项/责任人/数字)时,禁止凭空生成对应内容**——听记 AI 建议 ≠ 带责任人的 ActionItems +> 2. 统计字段(文档数 / 会议数 / ActionItem 数)**必须基于实际取数结果计数**,不得编造 +> 3. 交付前做**「证据-断言对齐」校验**:稿中每条核心断言须有源数据支撑,与实际取数对账 +> +> **真实 badcase**:取数全成功,但写周报时把听记 AI 建议篡改成带责任人的 ActionItems、虚报文档数(7 vs 10),无忠实性护栏 → 点踩。 + +**多数据源全覆盖约束(0609 P1 点踩 case 提炼,trace: f3197dd):** + +> 用户枚举多个数据源(如「数据源包含:日历、聊天、听记、文档」)时: +> 1. **每个来源都必须生成对应工具调用**:`calendar event list` + `chat message list` + `minutes list all` → `get summary/transcription` + `doc search/read` +> 2. **瞬时错误(超时/网络抖动)自动重试**(最多 2 次),不得一次失败即放弃该数据源 +> 3. 最终交付物**如实声明各来源取数情况**:成功获取了哪些、哪些因超时/权限失败被跳过 +> 4. **严禁**用训练记忆 / 常识填充失败数据源的空缺——编造无来源的精确数字是最严重的幻觉 +> +> **真实 badcase**:用户点名 4 类数据源,chat/minutes 命令现成却完全没调,日历超时即放弃,残缺日志当全量交付还编精确数字 → 点踩。 + +**多步流程错误传播规则(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` + `uploadUrl` +> 2. 将文件通过 `uploadUrl` 直传(由客户端侧完成,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 +└── tag + ├── list + └── query --tag-id [--limit] [--cursor] +``` + +## 命令总览 + +### 查询我创建的听记列表 +``` +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` 和 `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](10-minutes-speaker-match.md))。**智能匹配核心:同时发起三路查询、不串行等待**:① 人员+组织架构 `dws aisearch person --keyword <姓名> --dimension name` → userId,再用 `dws contact user get --ids ` 补部门/职级;② 聊天记录 `dws chat message list` → 工作内容/语言风格/职责线索;③ 本人文档 `dws doc search --keyword <姓名>` 前 3 篇 → 角色精确信号。**判定**:通讯录+聊天两路一致即可确认角色(通常无需查文档),仅一路或矛盾时补查文档兜底、两路以上一致才下结论;部门名 ≠ 角色。**文档角色对照**:PRD/需求/商业化方案→产品经理,视觉规范/原型/交互评审→产品设计师,技术方案/架构→研发,分析报告/底表→数据分析师。**置信度分支**:> 70% 输出匹配结果并执行替换,< 70% 不输出、请用户补充信息或改用手动设置 + - 选 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 摘要参与人只有故愚 → 灵麦没参会" 是典型的错误推理链 +> - 如果一定要列出参会人,应使用 `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 aisearch person` 也搜不到该人 → 才允许告知用户"该人不在参会列表" + +##### 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 aisearch person --keyword "目标人名" --dimension name` → userId,再用 `dws contact user get --ids ` 补详情 | 职能大类(技术/产品/设计/管理)+ 是否存在该人 | +| ② 本人创建的文档 | `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 aisearch person --keyword "[人名]" --dimension name --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 列表,逗号分隔 (必填) +``` + +返回字段: 听记标题、时长、参与人列表、创建时间、taskUuid、听记状态 + +**批量查询容错铁律(0030 P2 badcase 提炼):** +> - `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 创建标签 + +## 意图判断 + +### 发起听记 +用户说"开始听记/开始录音/发起一个听记/启动听记/开始听记录这次会议/我要开始听记了/录音开始/我要听记" → `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 aisearch person --keyword "张三" --dimension name --format json + ``` +2. 从返回结果中提取匹配的人员列表,每条包含 `userId`(长整型数字,即 dingUid)和姓名: + - **唯一匹配** → 直接向用户确认:"通讯录中找到『张三(userId: 123456789)』,确认将发言人替换为此人并关联通讯录身份?" + - **多个匹配** → 列出候选让用户选择: + > 通讯录中找到多个匹配,请选择目标人员: + > + > | # | 姓名 | 部门 | userId | + > |---|------|------|--------| + > | 1 | 张三 | 产品部 | 123456789 | + > | 2 | 张三丰 | 技术部 | 987654321 | + > + > 请输入序号选择。 + - **无匹配** → 提示用户:"通讯录中未找到『张三』,可以直接替换发言人名称(不关联通讯录身份),或提供更精确的姓名重新查询。直接替换?[是] [重新查询]" +3. 获取到 userId 后,在 `speaker replace` 中附加 `--target-uid `,实现发言人关联通讯录身份 + +> **注意**:`dws aisearch person` 返回的 `userId` 是**长整型数字**(如 `123456789`),这就是 `--target-uid` 需要的值。不要与 `staffId`、`unionId` 等其他 ID 混淆。 + +**发言人映射模式识别(高频场景):** + +用户经常使用如下格式批量告知发言人对应关系: +- `发言人1:张三` +- `发言人1:张三`(中文冒号) +- `发言人1->张三` +- `发言人1 -> 张三` +- `发言人1 = 张三` +- 多条换行或逗号分隔:`发言人1:张三, 发言人2:李四, 发言人3:王五` + +**AI 识别到上述模式时,必须执行以下流程:** + +1. **解析映射关系**:提取所有 `源发言人 → 目标姓名` 的配对 +2. **批量人员查询**:对每个目标姓名调用 `dws aisearch person --keyword "<姓名>" --dimension name --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 aisearch person --keyword "姓名" --dimension name` 查询) + +**典型执行链路:** +1. 用户说"把张三加到这个听记中" → AI 需获取张三的 UID +2. 调用 `dws aisearch person --keyword "张三" --dimension name --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 操作 + +## 核心工作流 + +```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`;② `minutes get --id` → `get` 后缺子命令,应为 `get info/summary/transcription` 等;③ `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. 用户确认继续 → 接着翻页;用户拒绝 → 停止翻页,基于已拉取内容进行分析展示 + +> **【铁律·转写覆盖校验:用户指定了时间区间/时间点时,必须拉到该区间再分析,没拉到要准确提示】(202606 P0 真实 badcase 实锤)** +> +> **真实事故**:用户对一篇 1 小时+ 的长听记说「总结里面 57:52 → 01:05:44 的内容,形成关键三件事」。`get transcription` 每页只返回 50 段,必须靠 `nextToken` 顺序翻页。模型只翻了 4 页、转写**只拉到 31:09 就停了**(撞上 12000 字符上限 / react 步数预算),**根本没拉到 57:52 那段**,却拿着"前 31 分钟"的内容硬凑用户要的"57 分钟那段",总结完全驴唇不对马嘴。事后换更强模型继续翻页到 63:53、覆盖了目标区间才答对——**但这不是模型能力问题,是取数没拉到目标区间**。 +> +> **根因**:`get transcription` 是**从头顺序分页**的,转写段落带 `startTime`/`endTime`(**毫秒**)时间戳。用户要的区间靠后时,必须一直翻页到该区间才能拿到对应原文。顺序翻页中途因字符上限/步数预算停止 = **目标区间的转写根本没进上下文**。 +> +> **铁律(用户的请求里含具体时间区间/时间点,如 "57:52~01:05:44"、"最后 10 分钟"、"开头那段"、"第 N 分钟讲了啥"时,必须执行):** +> +> 1. **解析目标区间**:把用户的 `mm:ss` / `hh:mm:ss` 换算成毫秒(如 `57:52` = (57×60+52)×1000 = 3472000ms,`01:05:44` = 3944000ms)。 +> 2. **翻页时按段落 `endTime` 判断是否已覆盖目标区间**:每翻一页,看本页段落的 `endTime` 最大值。**只有当已拉取段落的 `endTime` ≥ 用户区间上界(或 `hasNext=false` 即已到末尾)时,才算"覆盖到了",才能停止翻页进入分析。** +> 3. **目标区间靠后时优先用倒序/定向减少翻页**:若用户只要靠后的一段(如"最后 10 分钟"),优先用 `--direction 1`(倒序)从尾部拉,减少翻页轮数;不要从 0:00 一页页顺序翻到 50 分钟。 +> 4. **撞到字符上限但还没覆盖目标区间时,提示词必须"准确"——明确告知"已拉到哪、目标区间还没拉到、二选一怎么办",严禁拿没覆盖的内容硬总结**: +> > "这篇听记较长,我目前已拉取到约 **31:09** 处的转写(约 X 字符,已达单次处理上限),但你要的 **57:52 ~ 01:05:44** 这一段**还在后面、尚未拉取到**。请选择:① 我继续翻页直到拉到该区间再总结;② 缩小一下你要的时间范围。在拉到目标区间之前,我不会用前面的内容硬凑总结。" +> 5. **翻页耗尽(`hasNext=false`)仍未到达用户区间**(说明用户给的时间点超过了听记总时长)→ **准确提示**: +> > "这篇听记总时长约 **X 分钟**(转写到 X 处结束),但你要的 **57:52** 已经超出了听记的实际长度,没有对应内容。是不是时间点记错了?或者你要的是听记里的哪一段?" +> +> **绝对禁止(本条是 P0 红线):** +> - **[禁止]** 用户指定了时间区间,却只拉了前几页(没覆盖目标区间)就开始总结——这是本次事故的直接原因 +> - **[禁止]** 拿"已拉到的前半段内容"冒充"用户要的后半段区间"硬凑答案,且**不告诉用户实际只拉到了哪里** +> - **[禁止]** 因字符上限/步数预算停止翻页时,只说"已达上限是否继续"却**不说明"目标区间还没拉到"**——用户无法判断继续与否,提示等于无效 +> - **[禁止]** 因为"换个更强的模型就答对了"而把问题归因为模型能力——真正要修的是**取数完整性(是否覆盖用户请求的时间区间)+ 覆盖校验 + 准确提示**,取数修对后普通模型也能答对 + +### 案例 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 aisearch person --keyword "张三" --dimension name --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 aisearch person --keyword "木兰" --dimension name` → `dws contact user get --ids ` | 木兰 = **王佳明**,X 事业群-X 事业部-X-X-**产品设计部**,上级临渊(王临一)| +| ② 文档产出 | `dws drive search --query "王佳明"`(按需)| 多为设计稿/原型,进一步印证设计师角色 | + +**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 aisearch person --keyword "木兰" --dimension name` | 铁律 3 | 人员搜索是 Step 4 的必跑项,拿到 userId 后再由 contact 补详情 | +| 一旦字面搜不到就放弃身份推断,把任务甩给用户 | 铁律 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 aisearch person --keyword "木兰" --dimension name` → userId,再 `dws contact user get --ids ` 补部门等详情 +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` 字段。 + +3. **`dws aisearch person` 是 Step 4 ① 的必跑项**:先找到人员并取得 userId,再用 `dws contact user get` 补部门等详情;拿到"木兰=王佳明,产品设计部"后,Step 5 的角色匹配才有可靠锚点。 + +4. **想说"找不到 X"前的四个自检问题**(任何一个回答"没"都禁止给"找不到"结论): + - 人员搜了吗?(`dws aisearch person --keyword "X" --dimension name`) + - 文档查了吗?(`dws drive search --query "X"`) + - 聊天记录查了吗?(`dws chat message list`) + - 基于角色在转写里做模式匹配了吗?(设计师 vs 研发 vs 管理者的发言特征) + +5. **`get summary` 不能替代发言人识别**:摘要是"会议讲了什么"的总览,**不是**"谁讲了什么"的精细切分。混淆这两个能力会导致 Step 4 直接跳过。 + +**绝对禁止:** +- **[禁止]** Step 2 看到匿名编号后,直接在转写文本里 grep 目标人名 → 没找到就退出 +- **[禁止]** 用 `get summary` 摘要里写到的"参与人"判断某人是否参会 +- **[禁止]** 全流程不调用 `dws aisearch person` 就给出"找不到 X"的结论 +- **[禁止]** 把身份推断的责任甩回给用户:"请你告诉我哪位是木兰" / "请去客户端看参会人"——发言人识别功能的存在意义就是 AI 来做这件事 + + +### 案例 9:长听记按时间区间总结,转写没翻页到目标区间就硬总结 → 答非所问(202606 P0 实锤复盘) + +> 这是一次**有 SLS 日志实锤根因**的真实事故:同一篇 1 小时+ 的长听记、同一个"总结某时间区间"的诉求,弱档位答错、强档位答对,但根因**不是模型能力,而是转写没拉到用户要的时间区间**。本案例用于固化"转写覆盖校验"铁律(详见案例 3 末尾的铁律)。 + +**用户输入:** +``` +总结里面 57:52 开始到 01:05:44 的内容给我,形成关键的三件事及拆解 +https://shanji.dingtalk.com/app/transcribes/ (一篇时长 1 小时+ 的会议听记) +``` + +**[错误] 真实失败链路(SLS 日志实锤):** + +``` +Step 1 [完成] 从 URL 提取 taskUuid → get transcription 拉第 1 页(50 段,到 8:59) + ↓ +Step 2 [完成] 续翻第 2 页(到 19:23)、第 3 页(到 26:34)、第 4 页(到 31:09) + ↓ +Step 3 [禁止] 翻到 31:09 时撞上字符上限 / 步数预算 → 停止翻页 + ↓ +错误动作:手里只有 0:02 ~ 31:09 的转写,但用户要的是 57:52 ~ 01:05:44 + ↓ +错误结论:在"前 31 分钟"的内容里硬找"57 分钟那段",拼出一个"关键三件事" + ↓ +后果:总结的内容和用户要的那段完全对不上,用户评"总结的一般",被迫换更强模型重做 +``` + +> **强档位为什么"答对了"**:换档位重做时,翻页继续推进到了 **63:53**,**覆盖了 57:52~01:05:44 整段**,所以答对了——**对的是"把转写翻到了目标区间",不是"模型更聪明"**。SLS 日志清晰记录了两轮的翻页进度:弱档位止于 31:09(漏掉目标区间),强档位推进到 63:53(覆盖目标区间)。 + +**这条链路的核心错误:** + +| 反模式 | 后果 | +|--------|------| +| 用户给了明确时间区间(57:52~01:05:44),却没翻页到该区间就停 | 目标区间的转写**根本没进上下文**,总结无源可依 | +| 拿"前 31 分钟"的内容冒充"用户要的 57 分钟那段" | 答非所问,且用户不知道 AI 其实只拉到了 31:09 | +| 撞字符上限时只想着"是否继续",没意识到"目标区间还没拉到" | 即使提示了"是否继续"也没说清"为什么必须继续",提示无效 | +| 答错后归因为"模型不行、换强模型" | 真正要修的是取数覆盖校验,归因错了优化方向就错了 | + +**[正确] 应该这样执行:** + +1. **解析目标区间**:`57:52` → 3472000ms,`01:05:44` → 3944000ms。 +2. **从 URL 提取 taskUuid** → `dws minutes get transcription --id --format json` 拉第一页,拿 `nextToken` 和本页段落 `endTime` 最大值。 +3. **按 `endTime` 校验覆盖**:每翻一页,检查"已拉取段落的 `endTime` 最大值"是否 ≥ 3944000ms(目标区间上界)。 + - 未达到 且 `hasNext=true` → **继续翻页**(即使中途超过 12000 字符,也要明确告知用户"目标区间还在后面、需继续翻页",而不是停下硬总结)。 + - 已达到 / `hasNext=false` → 停止翻页,进入分析。 +4. **截取目标区间**:从拉取到的全部段落里,筛出 `startTime`/`endTime` 落在 [3472000, 3944000] 的段落,**只对这一段**做"关键三件事 + 拆解"。 +5. **若翻页耗尽仍没到 57:52**(听记总时长 < 57:52)→ 准确提示:"这篇听记总时长约 X 分钟,你要的 57:52 超出了实际长度,是不是时间点记错了?" +6. **若因字符上限暂停且尚未覆盖目标区间** → 准确提示(见案例 3 铁律第 4 条的话术),让用户在"继续翻页 / 缩小范围"间选择,**绝不拿没覆盖的内容硬总结**。 + +**本案例固化的关键经验(强制吸收):** + +1. **用户给了时间区间 = 必须翻页到该区间才能总结**:`get transcription` 是从头顺序分页的,区间靠后就得一直翻到那里。中途停 = 目标区间没进上下文。 +2. **"撞字符上限"不是停止翻页的理由,"已覆盖目标区间"才是**:用户明确指定区间时,覆盖校验优先于字符上限——没覆盖就得继续翻(或准确提示用户),不能闷头停。 +3. **答错后先查"取数是否拉全",再怀疑模型**:本案例换强模型答对是假象,真因是取数覆盖。遇到"总结不准"的长听记 case,第一反应应是"目标区间的转写拉到了吗"。 +4. **提示必须准确到"已拉到哪 / 目标区间在不在"**:模糊的"是否继续拉取"对用户无意义;必须告诉用户"已拉到 31:09,你要的 57:52 还没拉到"。 + +**绝对禁止:** +- **[禁止]** 用户指定了时间区间,却没翻页到该区间(按 `endTime` 校验)就开始总结 +- **[禁止]** 拿"已拉到的前半段"冒充"用户要的后半段区间"硬凑答案,且不告知用户实际只拉到了哪里 +- **[禁止]** 因字符上限/步数预算停止翻页时,只说"已达上限是否继续"而不说明"目标区间尚未拉到" +- **[禁止]** 把"总结不准"归因为模型能力、直接建议换模型——先核对取数是否覆盖了用户请求的时间区间 diff --git a/skills/multi/dingtalk-minutes/scripts/minutes_extract_todos.py b/skills/multi/dingtalk-minutes/scripts/minutes_extract_todos.py index a447f78e..7ca3d651 100644 --- a/skills/multi/dingtalk-minutes/scripts/minutes_extract_todos.py +++ b/skills/multi/dingtalk-minutes/scripts/minutes_extract_todos.py @@ -44,50 +44,6 @@ def run_dws( return None -def todos_from_payload(payload: Any) -> List[dict]: - """解析 result.dingtalkTodoList;为空时回退解析 actions。""" - inner = payload.get('result', payload) if isinstance(payload, dict) else payload - out: List[dict] = [] - if isinstance(inner, dict): - ding_list = inner.get('dingtalkTodoList') - if isinstance(ding_list, list): - for item in ding_list: - if not isinstance(item, dict): - continue - content = item.get('title') or item.get('content') or '' - if content: - out.append({'content': str(content), '_raw': item}) - if out: - return out - actions = inner.get('actions') - if isinstance(actions, list): - for action in actions: - content = '' - if isinstance(action, str): - text = action.strip() - if text.startswith('{'): - try: - content = json.loads(text).get('value') or '' - except json.JSONDecodeError: - content = text - else: - content = text - elif isinstance(action, dict): - content = (action.get('value') or action.get('content') - or action.get('title') or '') - if content: - out.append({'content': str(content)}) - elif isinstance(inner, list): - for item in inner: - if not isinstance(item, dict): - continue - content = (item.get('content') or item.get('text') - or item.get('title') or item.get('value') or '') - if content: - out.append({'content': str(content), '_raw': item}) - return out - - def main(): parser = argparse.ArgumentParser( description='从听记中提取待办事项' @@ -104,7 +60,7 @@ def main(): print('🎙️ 获取听记列表...') data = run_dws([ 'minutes', 'list', 'mine', - '--limit', str(args.max), + '--max', str(args.max), '--format', 'json', ], dry_run=args.dry_run) if args.dry_run: @@ -126,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 c256cc64..24e8ed44 100644 --- a/skills/multi/dingtalk-minutes/scripts/minutes_recent_summary.py +++ b/skills/multi/dingtalk-minutes/scripts/minutes_recent_summary.py @@ -60,7 +60,7 @@ def main(): print('🎙️ 获取听记列表...') list_data = run_dws([ 'minutes', 'list', 'mine', - '--limit', str(args.max), + '--max', str(args.max), '--format', 'json', ], dry_run=args.dry_run) @@ -93,14 +93,9 @@ def main(): if isinstance(summary_data, str): summary_text = summary_data elif isinstance(summary_data, dict): - 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-misc/SKILL.md b/skills/multi/dingtalk-misc/SKILL.md new file mode 100644 index 00000000..5bacb5ba --- /dev/null +++ b/skills/multi/dingtalk-misc/SKILL.md @@ -0,0 +1,38 @@ +--- +name: dingtalk-misc +description: 长尾产品集合技能,覆盖低频钉钉产品:OA审批/考勤/直播/DING紧急消息/开放平台应用管理/Agoal目标管理/日志日报周报/电子表格/开放平台文档搜索。Use when 用户提到上述任一产品,或审批/打卡/排班/OKR/日报周报/单元格读写等相关操作。命中后由本 skill 的「产品索引表」定位具体子产品和命令前缀,再按对应子产品说明执行。 +metadata: + cli_version: ">=0.2.14" + category: product + requires: + bins: + - dws +--- + +# 长尾产品集合 Skill(dingtalk-misc) + +## 前置条件 — 执行操作前必读 + +> **CRITICAL — 执行任何 `dws` 操作前,MUST 先用 Read 工具完整读取 [`dws-shared`](../dws-shared/SKILL.md)。**该轻量文件包含全局执行契约、安全底线及 shared references 的按需加载导航;不要预加载其全部 references。 + +本 skill 打包低频长尾产品,命令前缀各不相同。**不要**把这份 SKILL.md 当作命令细节来源——它只负责路由;命中某个产品后,务必读取该产品的 `references/.md` 获取 Usage / Example / Flags / 危险操作确认 / 跨产品协作等完整信息。 + +## 产品索引表 + +| 触发关键词 | 一句话范围 | 命令前缀 | 详细参考 | +|---|---|---|---| +| OA / 审批 / 待处理审批 / 同意 / 拒绝 / 撤销 / 已发起审批 | OA 审批:待处理/详情/同意/拒绝/撤销/已发起/批量审批 | `dws oa` | [oa.md](references/oa.md) | +| 考勤 / 打卡记录 / 排班 / 班次 / 考勤报表 / 考勤组 | 考勤记录、打卡查询、排班、考勤组、报表导出 | `dws attendance` | [attendance.md](references/attendance.md) | +| 直播 / 我的直播 / 直播列表 | 直播列表与直播记录查询 | `dws live` | [live.md](references/live.md) | +| DING / 紧急通知 / 电话DING / 短信DING / 必达消息 | DING 紧急消息(应用内/短信/电话),个人DING | `dws ding` | [ding.md](references/ding.md) | +| 开放平台应用 / 企业内部应用 / 应用成员 / 应用权限 / 应用版本 | 开放平台企业内部应用的查询、创建、修改、成员权限和版本管理 | `dws devapp` | [devapp.md](references/devapp.md) | +| 目标管理 / 战略解码 / 经营合约 / 计分卡 / OKR / 周月报统计 | Agoal 目标管理与经营目标跟进 | `dws agoal` | [agoal.md](references/agoal.md) | +| 日报 / 周报 / 月报 / 写日志 / 收件箱日志 / 发件箱日志 | 日志(日报/周报/月报)查询与按模版提交 | `dws report`(别名 `dws log`) | [report.md](references/report.md) | +| 电子表格 / 工作表 / 单元格读写 / 公式 / 超链接 / 浮动图片 | 电子表格创建/读写/公式/超链接/浮动图片/导出 | `dws sheet` | [sheet.md](references/sheet.md) | +| 开放平台文档 / API文档 / 接口文档 / 接口报错 | 开放平台开发文档搜索 | `dws devdoc` | [devdoc.md](references/devdoc.md) | + +## 说明 + +- 每个产品的意图表、危险操作确认、硬约束、跨产品协作和短流程均在其 `references/.md` 内,命中产品后必须读取,不要只凭本表推测命令。 +- 产品自己的局部意图消歧文档命名为 `references/-intent-guide.md`,不是共享的 `references/intent-guide.md`。 +- 各产品之间跨产品协作若指向本包内的其它产品,已在对应 `references/.md` 里写成"见本包 references/X.md",无需切换 skill;若指向 top10 独立产品(如 `chat`/`aisearch`/`doc`),仍按 `dingtalk-` 切换 skill。 diff --git a/skills/multi/dingtalk-report/references/05-reporting.md b/skills/multi/dingtalk-misc/references/05-reporting.md similarity index 57% rename from skills/multi/dingtalk-report/references/05-reporting.md rename to skills/multi/dingtalk-misc/references/05-reporting.md index af075ade..503d4b52 100644 --- a/skills/multi/dingtalk-report/references/05-reporting.md +++ b/skills/multi/dingtalk-misc/references/05-reporting.md @@ -1,6 +1,6 @@ # 工作汇报 -> lite recipe(`view-report-inbox`、`check-report-read-status`)见 [SKILL.md](../SKILL.md#lite-recipes)。 +> lite recipe(`view-report-inbox`、`check-report-read-status`)见 [report-lite-recipes.md](./report-lite-recipes.md)。 ## 路径分歧(先判定后选 recipe) @@ -22,8 +22,8 @@ | Recipe | 行动指南(固定路线) | |--------|-------------------| | 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`(兜底同上) | +| generate-daily-report | 1. 按[「多源并行采集」](./report-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. 按[「多源并行采集」](./report-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. 按[「多源并行采集」](./report-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. 按[「多源并行采集」](./report-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. 按[「多源并行采集」](./report-conventions.md#多源并行采集公共模式)执行
3. 按时间线排列,交叉归纳核心结论/决策/行动项/未解决问题/演进脉络,并把内容写入临时文件 `.md`
4. **创建文档**:`doc create --name "<报告名>" --content-file .md`(兜底同上) | diff --git a/skills/multi/dingtalk-agoal/references/agoal.md b/skills/multi/dingtalk-misc/references/agoal.md similarity index 92% rename from skills/multi/dingtalk-agoal/references/agoal.md rename to skills/multi/dingtalk-misc/references/agoal.md index 029166c2..f76d6bee 100644 --- a/skills/multi/dingtalk-agoal/references/agoal.md +++ b/skills/multi/dingtalk-misc/references/agoal.md @@ -271,3 +271,25 @@ dws agoal obj-template create-or-update --template-id TPL_ID --title "业绩模 - `--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,不传时由服务端使用默认值 + +--- + +## SKILL 摘要(原 dingtalk-agoal/SKILL.md 正文) + +## 意图表 + +| 用户说 | 命令 | +|--------|------| +| "查战略解码 / 战略列表" | `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](./agoal.md) 覆盖更新 | +| "查周月报统计/提交情况/跟催" | `dws agoal report list-statistics` / `report submit-detail` | + +## 硬约束 + +- `strategy update`、`contract update`、`scorecard update` 都是覆盖式更新,必须先查询详情,在返回数据基础上修改后再提交。 +- 所有命令带 `--format json`;涉及写操作时回查确认。 diff --git a/skills/multi/dingtalk-attendance/references/intent-guide.md b/skills/multi/dingtalk-misc/references/attendance-intent-guide.md similarity index 100% rename from skills/multi/dingtalk-attendance/references/intent-guide.md rename to skills/multi/dingtalk-misc/references/attendance-intent-guide.md diff --git a/skills/multi/dingtalk-attendance/references/attendance-report.md b/skills/multi/dingtalk-misc/references/attendance-report.md similarity index 98% rename from skills/multi/dingtalk-attendance/references/attendance-report.md rename to skills/multi/dingtalk-misc/references/attendance-report.md index 990e76f8..bbfc5cbf 100644 --- a/skills/multi/dingtalk-attendance/references/attendance-report.md +++ b/skills/multi/dingtalk-misc/references/attendance-report.md @@ -102,10 +102,10 @@ Agent 解析用户意图(报表类型、人员范围、时间范围、关注 | 用户说 | 应路由到 | |--------|---------| | "今天打卡了吗" | `dws attendance record get`(单次查询,非报表) | -| "帮我排班" | `dws attendance schedule import`(排班写入,非报表;执行前确认) | +| "帮我排班" | `dws attendance schedule import`(导入排班,非报表;先确认考勤组、人员、日期、班次和休息日) | | "我的假期还剩多少" | `dws attendance vacation balance`(单次查询) | | "帮我请假" | `dws oa`(审批流程,非报表) | -| "我这个月的考勤怎么样" | `dws attendance summary --user --date --stats-type month`(个人统计,非报表) | +| "我这个月的考勤怎么样" | `dws attendance summary`(个人统计,非报表) | ## 工作流 @@ -132,7 +132,7 @@ dws aisearch person --keyword "<员工姓名>" --dimension name --format json **场景 B — 按部门查询**: ```bash dws contact dept search --query "<部门名>" --format json -dws contact dept list-members --depts --format json +dws contact dept list-members --ids --format json ``` **场景 C — 多个部门**: 对每个部门分别执行 B,汇总去重。 @@ -502,7 +502,7 @@ python scripts/attendance_report_checkin.py \ ```bash # 1. 获取部门成员 dws contact dept search --query "研发组" --format json -dws contact dept list-members --depts --format json +dws contact dept list-members --ids --format json # 2. 调用脚本(默认月度汇总,不传 --column-keywords) python scripts/attendance_report_monthly.py \ diff --git a/skills/multi/dingtalk-attendance/references/attendance-schedule.md b/skills/multi/dingtalk-misc/references/attendance-schedule.md similarity index 85% rename from skills/multi/dingtalk-attendance/references/attendance-schedule.md rename to skills/multi/dingtalk-misc/references/attendance-schedule.md index fe339480..9175261a 100644 --- a/skills/multi/dingtalk-attendance/references/attendance-schedule.md +++ b/skills/multi/dingtalk-misc/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 — 查询考勤组详情和关联班次(必须在收集班次选择前完成)**: +**步骤 0b — 查询考勤组详情和关联班次(必须在 ask_question 之前完成)**: ```bash # 获取考勤组详情(含绑定的班次列表) @@ -132,7 +132,7 @@ dws attendance group get --group-id --format json - 考勤组类型:`groupVO.type`(必须为 TURN) - 关联班次列表:`groupVO.shiftVOList[].shiftSetting.{shiftId, shiftName}` -**步骤 0c — 收集缺失参数**: +**步骤 0c — 收集缺失参数(ask_question 卡片交互)**: 拿到考勤组和关联班次后,再向用户收集缺失的参数。排班所需的四个参数: - **考勤组**:已在 0a 自动获取,无需询问 @@ -140,12 +140,59 @@ dws attendance group get --group-id --format json - **员工范围**(必填):指定员工姓名 / 部门 / 考勤组全员 / "给我排班" - **日期范围**(必填):具体日期 / 日期范围(如"下周"、"5月19日到5月23日") -**只收集真正缺失的参数**,用户已经提供的不要重复询问;多个缺失参数可集中询问。日期选项必须展示实际计算后的日期范围,班次选项必须在运行时从 `shiftVOList` 动态生成,并以真实 `shiftId` 作为值、真实 `shiftName` 作为展示名称;可额外提供“排休”。只有一个关联班次时直接使用,无需再次选择。 +**只收集真正缺失的参数**,用户已经提供的不要重复询问。将缺失参数**合并到一次 `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`,**严禁编造任何班次名称**(如"正常班"、"早班"、"晚班"、"全部排XX班"等都是编造) - 每个班次选项的 `id` 必须是 `shiftVOList` 中的真实 `shiftId`,`label` 必须是真实的 `shiftName` - - 如果 `shiftVOList` 为空,降级从 `groupVO.classIds` + `class get` 按 ID 精确查询(不是全局搜索) + - 如果 `shiftVOList` 为空,降级从 `groupVO.classIds` + `class search` 按 ID 精确查询(不是全局搜索) - **只收集缺失的参数**:用户已经提供的参数不要重复询问 - **如果考勤组只有一个班次,直接使用该班次,不需要询问用户选择** - **禁止用 `class search` 全局搜索来给用户展示班次选项**——全局班次列表包含不属于该考勤组的班次,用户选了也会被校验拒绝 @@ -183,7 +230,7 @@ dws aisearch person --keyword "<员工姓名>" --dimension name --format json **场景 B — 按部门查询**: ```bash dws contact dept search --query "<部门名>" --format json -dws contact dept list-members --depts --format json +dws contact dept list-members --ids --format json ``` **场景 C — 考勤组全员**: @@ -204,21 +251,21 @@ dws attendance group filtered-get --group-id --member --format json **禁止调用 `dws attendance class search` 全局搜索班次**——全局搜索会返回不属于该考勤组的班次,即使用户指定了班次名称,也必须在 `shiftVOList` 中匹配,而不是全局搜索。 **校验规则**: -1. 用户选择的班次 `shiftId` 必须存在于 `shiftVOList` 中;所有候选都必须由 `shiftVOList` 动态生成 +1. 用户在 `ask_question` 卡片中选择的班次,其 `shiftId` 必须存在于 `shiftVOList` 中(卡片选项本身就是从 `shiftVOList` 构建的,所以天然满足) 2. 如果用户通过自然语言指定了班次名称(如"排早班"),必须在 `shiftVOList` 中**按名称模糊匹配**,找到对应的 `shiftId` 3. 如果用户指定的班次不在 `shiftVOList` 中,**必须拒绝**,并列出该考勤组关联的全部班次让用户重新选择 4. 仅当 `shiftVOList` 为空时,才降级从 `groupVO.classIds` + `dws attendance class get` 按 ID 精确查询(仍然不是全局搜索) **提取信息**:班次 ID(`classId` / `shiftId`)、班次名称(`shiftName`) -### 阶段 4: 回显排班内容并获得明确确认 +### 阶段 4: 回显排班内容并使用 ask_question 卡片确认 -> **[硬性门禁]** 必须先向用户展示**完整的排班明细表格**(包含每个人、每天、具体班次名称),再获得用户明确确认。 -> **禁止在用户还不知道"谁、哪天、上什么班"的情况下请求确认**。 +> **[硬性门禁]** 必须先用普通文本向用户展示**完整的排班明细表格**(包含每个人、每天、具体班次名称),用户看到排班明细后,才能弹出 `ask_question` 确认卡片。 +> **禁止在用户还不知道"谁、哪天、上什么班"的情况下就弹确认卡片**——这等于让用户盲签,体验极差。 -**步骤 4a — 展示排班明细(必须在请求确认之前)**: +**步骤 4a — 展示排班明细(必须在确认卡片之前)**: -请求确认前,**必须先**向用户展示排班内容。表格中**必须包含班次名称**(如"早班 09:00-18:00"),不能只展示 classId 数字: +在调用 `ask_question` 之前,**必须先**用普通文本向用户展示排班内容。表格中**必须包含班次名称**(如"早班 09:00-18:00"),不能只展示 classId 数字: ``` 排班预览 @@ -244,9 +291,27 @@ dws attendance group filtered-get --group-id --member --format json - [ ] 排休的记录标注了"排休" - [ ] 表格涵盖了所有待排班的员工和日期 -**步骤 4b — 获得明确确认(必须在展示表格之后)**: +**步骤 4b — 使用 `ask_question` 卡片确认(必须在展示表格之后)**: -说明排班将覆盖所选日期的现有记录,并等待用户明确确认。确认后进入阶段 5;用户取消或未明确同意时不执行导入。 +``` +ask_question({ + title: "排班执行确认", + questions: [ + { + id: "confirm_execute", + prompt: "以上排班将覆盖所选日期的现有排班记录,确认执行吗?", + options: [ + { id: "yes", label: "确认执行" }, + { id: "no", label: "取消" } + ] + } + ] +}) +``` + +**处理用户选择**: +- 用户选择 **"确认执行"** → 进入阶段 5 +- 用户选择 **"取消"** → 终止流程,提示"已取消排班操作" ### 阶段 5: 调用脚本执行排班 @@ -430,7 +495,7 @@ dws attendance group get --group-id --format json # 2. 获取部门成员 dws contact dept search --query "研发部" --format json -dws contact dept list-members --depts --format json +dws contact dept list-members --ids --format json # 3. 回显确认 → 用户确认 → 执行 python scripts/attendance_schedule_import.py \ diff --git a/skills/multi/dingtalk-attendance/references/attendance-vacation.md b/skills/multi/dingtalk-misc/references/attendance-vacation.md similarity index 98% rename from skills/multi/dingtalk-attendance/references/attendance-vacation.md rename to skills/multi/dingtalk-misc/references/attendance-vacation.md index 217be447..afeee1eb 100644 --- a/skills/multi/dingtalk-attendance/references/attendance-vacation.md +++ b/skills/multi/dingtalk-misc/references/attendance-vacation.md @@ -64,7 +64,7 @@ Agent 只负责解析人员范围并获取 userId 列表;假期规则查询、 1. 识别人员范围: - 指定员工姓名:用 `dws aisearch person --keyword "<姓名>" --dimension name --format json` 获取 userId - - 指定部门:用 `dws contact dept search --query "<部门名>" --format json`,再用 `dws contact dept list-members --depts --format json` 获取成员 + - 指定部门:用 `dws contact dept search --query "<部门名>" --format json`,再用 `dws contact dept list-members --ids --format json` 获取成员 - 已提供 userId:直接使用 2. 识别假期列范围: - 未指定假期类型:导出所有假期规则余额,不传 `--leave-keywords` @@ -80,7 +80,7 @@ dws aisearch person --keyword "<员工姓名>" --dimension name --format json **场景 B — 按部门查询**: ```bash dws contact dept search --query "<部门名>" --format json -dws contact dept list-members --depts --format json +dws contact dept list-members --ids --format json ``` **场景 C — 多个部门**: 对每个部门分别执行 B,汇总去重。 diff --git a/skills/multi/dingtalk-attendance/references/attendance-commands.md b/skills/multi/dingtalk-misc/references/attendance.md similarity index 60% rename from skills/multi/dingtalk-attendance/references/attendance-commands.md rename to skills/multi/dingtalk-misc/references/attendance.md index 96a6d1af..856ae37d 100644 --- a/skills/multi/dingtalk-attendance/references/attendance-commands.md +++ b/skills/multi/dingtalk-misc/references/attendance.md @@ -1,4 +1,58 @@ -# 命令总览 +# 考勤 (attendance) 命令参考 + +> **【必读】日期范围严格计算规则 — 所有含 --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 ## 命令总览 @@ -86,16 +140,16 @@ Flags: Usage: dws attendance schedule import [flags] Example: - dws attendance schedule import --groupId 123456 \ - --scheduleVOS '[{"userId":"user001","classId":123,"workDate":"2026-04-22","checkBeginTime":"09:00","checkEndTime":"18:00"}]' \ - --user-say-yes + dws attendance schedule import --group-id 123456 \ + --schedules '[{"userId":"user001","classId":123,"workDate":"2026-04-22","checkBeginTime":"09:00","checkEndTime":"18:00"}]' \ + --yes Flags: - --groupId string 考勤组(必填,传入考勤组ID) - --scheduleVOS string 排班记录 JSON 数组(必填) - --user-say-yes 用户已确认,跳过交互式确认提示 + --group-id string 考勤组(必填,传入考勤组ID) + --schedules string 排班记录 JSON 数组(必填) + --yes 跳过确认提示 ``` -为排班制考勤组导入排班记录。`--scheduleVOS` 为 JSON 数组,每条记录包含: +为排班制考勤组导入排班记录。`--schedules` 为 JSON 数组,每条记录包含: - `userId`: 员工ID - `classId`: 班次ID - `workDate`: 工作日期(YYYY-MM-DD),如 2026-04-22 @@ -108,7 +162,7 @@ Flags: `schedule import` 是写操作,会为考勤组导入或变更员工排班。AI 调用时必须按以下流程执行,不得在未确认的情况下直接导入: 1. **识别写操作**:用户表达“导入排班 / 设置排班 / 安排排班 / 给员工排班 / 批量排班”等意图时,命中 `schedule import`。 -2. **收集必要参数**:必须明确 `--groupId` 和 `--scheduleVOS`,并确认排班记录中的 `userId`、`classId`、`workDate`、`checkBeginTime`、`checkEndTime`、`isRest` 等字段。 +2. **收集必要参数**:必须明确 `--group-id` 和 `--schedules`,并确认排班记录中的 `userId`、`classId`、`workDate`、`checkBeginTime`、`checkEndTime`、`isRest` 等字段。 3. **展示导入摘要并反问确认**:向用户展示考勤组 ID、导入员工数量、涉及日期范围、班次 ID 列表,以及排班记录明细摘要,并询问是否确认执行导入。 4. **用户确认后再执行导入**:只有用户明确确认后,才可以执行 `dws attendance schedule import ... --format json`。 @@ -167,7 +221,7 @@ Flags: ### 创建班次 (写场景接口,必须走二次确认流程) **强制执行流程**:此命令为写操作,Agent 调用时必须遵守以下流程: 1. 先向用户展示待执行操作的完整参数摘要,包括班次名称、上下班时间、休息时段等 -2. 展示变更摘要并等待用户明确确认 +2. 使用 `ask_human` 或返回待确认状态,等待用户明确确认 3. 用户确认后,再传全局 `--yes` 执行命令 **禁止未经用户确认直接执行或自动添加 `--yes`。** @@ -196,7 +250,7 @@ Flags: ### 更新班次 (写场景接口,必须走二次确认流程) **强制执行流程**:此命令为写操作,Agent 调用时必须遵守以下流程: 1. 先向用户展示待执行操作的完整参数摘要,包括班次 ID、要修改的字段含义及新值 -2. 展示变更摘要并等待用户明确确认 +2. 使用 `ask_human` 或返回待确认状态,等待用户明确确认 3. 用户确认后,再传全局 `--yes` 执行命令 **禁止未经用户确认直接执行或自动添加 `--yes`。** @@ -327,7 +381,7 @@ Flags: ### 更新考勤组成员 (写场景接口,必须走二次确认流程) **强制执行流程**:此命令为写操作,Agent 调用时必须遵守以下流程: 1. 先向用户展示待执行操作的完整参数摘要,包括考勤组 ID、要添加/移除的成员列表 -2. 展示变更摘要并等待用户明确确认 +2. 使用 `ask_human` 或返回待确认状态,等待用户明确确认 3. 用户确认后,再传全局 `--yes` 执行命令 **禁止未经用户确认直接执行或自动添加 `--yes`。** @@ -354,7 +408,7 @@ Flags: ### 更新考勤组配置 (写场景接口,必须走二次确认流程) **强制执行流程**:此命令为写操作,Agent 调用时必须遵守以下流程: 1. 先向用户展示待执行操作的完整参数摘要,包括考勤组 ID、要修改的字段含义及新值 -2. 展示变更摘要并等待用户明确确认 +2. 使用 `ask_human` 或返回待确认状态,等待用户明确确认 3. 用户确认后,再传全局 `--yes` 执行命令 **禁止未经用户确认直接执行或自动添加 `--yes`。** @@ -394,7 +448,7 @@ Flags: ### 创建考勤组 (写场景接口,必须走二次确认流程) **强制执行流程**:此命令为写操作,Agent 调用时必须遵守以下流程: 1. 先向用户展示待执行操作的完整参数摘要,包括考勤组名称、类型、班次列表等 -2. 展示变更摘要并等待用户明确确认 +2. 使用 `ask_human` 或返回待确认状态,等待用户明确确认 3. 用户确认后,再传全局 `--yes` 执行命令 **禁止未经用户确认直接执行或自动添加 `--yes`。** @@ -526,11 +580,10 @@ Flags: 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" Flags: - --date string 查询日期,格式 YYYY-MM-DD 或 YYYY-MM-DD HH:mm:ss(必填) - --stats-type string 统计类型:week(周统计)/ month(月统计)(必填) - --user string 钉钉用户 ID(必填) + --date string 工作日期, 格式 yyyy-MM-dd HH:mm:ss (必填) + --user string 钉钉用户 ID (必填) ``` ### 查询考勤组与考勤规则 @@ -581,7 +634,7 @@ Flags: ### 更新保存个人规则设置 (写场景接口,必须走二次确认流程) **强制执行流程**:此命令为写操作,Agent 调用时必须遵守以下流程: 1. 先向用户展示待执行操作的完整参数摘要,包括目标用户、设置场景、当前值、新值和最终命令参数 -2. 展示变更摘要并等待用户明确确认 +2. 使用 `ask_human` 或返回待确认状态,等待用户明确确认 3. 用户确认后,再传全局 `--yes` 执行命令 **禁止未经用户确认直接执行或自动添加 `--yes`。** @@ -590,11 +643,11 @@ Flags: Usage: dws attendance selfsetting save [flags] Example: - # 开启打卡结果通知(执行前必须展示变更摘要并获得用户明确确认,确认后再追加 --yes) + # 开启打卡结果通知(Agent 调用时,必须先完成 ask_human 二次确认,确认后再追加 --yes 执行) dws attendance selfsetting save --setting-scene checkResultNotify --user --check-result-msg 1 --yes --format json - # 更新极速打卡设置(执行前必须展示变更摘要并获得用户明确确认,确认后再追加 --yes) + # 更新极速打卡设置(Agent 调用时,必须先完成 ask_human 二次确认,确认后再追加 --yes 执行) dws attendance selfsetting save --setting-scene fastCheck --user --onduty-check-type 3 --voice-remind-switch=true --yes --format json - # 更新打卡提醒设置(执行前必须展示变更摘要并获得用户明确确认,确认后再追加 --yes) + # 更新打卡提醒设置(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: @@ -625,7 +678,7 @@ Flags: --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` 由当前登录上下文自动注入,无需手动传入。 @@ -647,7 +700,7 @@ Flags: 1. **识别写操作**:用户表达“更新个人规则设置 / 保存打卡提醒 / 修改极速打卡 / 关闭缺卡提醒 / 开启打卡结果通知 / 设置个人考勤统计通知 / 设置团队考勤统计通知”等意图时,命中 `selfsetting save`。 2. **收集必要参数**:必须明确 `--user`、`--setting-scene`,以及对应场景下将要修改的字段和值。 3. **前置查询当前设置**:执行 `dws attendance selfsetting get --setting-scene --user --format json`,获取当前配置,用于确认目标用户和当前值。 -4. **展示待写入数据并等待确认**:向用户展示目标用户、设置场景、要更新的字段、当前值、新值和最终命令参数摘要,并等待用户明确确认。 +4. **展示待写入数据并等待确认**:向用户展示目标用户、设置场景、要更新的字段、当前值、新值和最终命令参数摘要。必须调用 `ask_human` 或返回待确认状态,并等待用户明确确认。 5. **用户确认后再执行保存**:**只有用户明确确认后**,才可以追加全局 `--yes` 执行 `dws attendance selfsetting save ... --yes --format json`。 确认话术示例: @@ -662,7 +715,7 @@ Flags: 是否确认执行更新? ``` -禁止在未获得用户明确确认前执行保存;禁止为了推进流程自动添加全局 `--yes`。即使用户在最初需求中表达“直接改/不用问”,也必须先展示完整参数摘要并获得明确确认,才允许追加 `--yes` 执行。 +禁止在未获得用户明确确认前执行保存;禁止为了推进流程自动添加全局 `--yes`。即使用户在最初需求中表达“直接改/不用问”,Agent 也必须先通过 `ask_human` 或待确认状态展示完整参数摘要并获得明确确认后,才允许追加 `--yes` 执行。 ### 查询全局规则设置(仅管理员) ``` @@ -820,7 +873,7 @@ Flags: **强制执行流程**:此命令为写操作,Agent 调用时必须遵守以下流程: 1. 先向用户展示待执行操作的完整参数摘要 -2. 展示变更摘要并等待用户明确确认 +2. 使用 `ask_human` 或返回待确认状态,等待用户明确确认 3. 用户确认后,再传 `--user-say-yes=true` 执行命令 **禁止未经用户确认直接执行或自动添加 `--user-say-yes=true`。** @@ -996,3 +1049,417 @@ Flags: - `mobileId`(string):设备 ID 顶层还包含 `success`(boolean)和 `arguments`(object[])字段。 + +## 意图判断 + +用户说"签到记录/签到数据/签到明细/外勤签到/导出签到" → `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` + +## 核心工作流 + +```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 15:00:00" --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 +``` + +## 上下文传递表 +| 操作 | 提取 | 用于 | +|------|------|------| +| `contact user get-self` | `userId` | summary 的 --user | +| `rules` | `groupId` | schedule import 的 --group-id | +| `schedule import` | `classId` | schedule import 的 schedules 中的 classId | +| `aisearch person` | `userId` | schedule import/get 的 userId | + +| `contact user get-self` / `aisearch person` | `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`;`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` 的 `--date` 格式: yyyy-MM-dd HH:mm:ss(如 `2026-03-12 15:00:00`) +- `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` 选填,可通过 `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. 使用 `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_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 + +## 严格约束 +- 不要凭历史记忆复用 userId / classId / leaveCode / groupId / instanceId 等任何 ID,每次必须从当次命令返回值中提取 +- 不要猜测命令,先查询明确命令,不要使用记忆中未经证实(即不存在于本.md文件中的指令)或执行失败(指令传参有误或flag不正确)的的命令 +- 制定 plan 并自我审查,严格按 plan 执行 +- 涉及超过 3 条记录的聚合(求和、分组、计数、排序、跨字段计算)时必须落 Python 脚本处理,禁止用大模型口算或目测。脚本里如果用到 mcp,先提前看下 mcp 返回的结构,避免执行异常 +- 遇到时长字段时,注意区分单位是秒、分钟还是小时 +- 遇到意图不清晰的场景不要猜测,主动询问用户明确意图 +- 如果查询结果很多时,不要自作主张省略,必须明确告知用户或者用表格或展示所有。 + +--- + +## SKILL 摘要(原 dingtalk-attendance/SKILL.md 正文) + + +## Shortcuts(无专用脚本/recipe 时优先) + +以下命令来自独立于 Runtime Schema 的公开 catalog。先运行 `dws shortcut list --service attendance --format json` 读取完整契约,再用 `dws attendance --help` 核对 flags;不要对 `+` 路径调用 `dws schema`。 + +| Shortcut | 风险 | 适用场景 | +|---|---|---| +| `dws attendance +check-record` | read | 查询用户打卡流水(打卡时间/地点/定位方式) | +| `dws attendance +check-result` | read | 查询用户打卡结果(迟到/早退/缺卡等) | +| `dws attendance +get-adjustment-rule` | read | 根据补卡规则主键 ID 查询补卡规则详情 | +| `dws attendance +get-approve-template` | read | 查询补卡/请假/加班/外出/出差审批提交链接 | +| `dws attendance +get-checkin-record` | read | 查询指定员工一段时间内的签到记录 | +| `dws attendance +get-leave-records` | read | 查询指定员工的假期余额变更记录 | +| `dws attendance +get-overtime-rule` | read | 根据加班规则主键 ID 查询加班规则详情 | +| `dws attendance +get-schedule` | read | 获取指定用户一段时间内的排班记录 | +| `dws attendance +get-self-setting` | read | 查询个人规则设置(打卡提醒/极速打卡/缺卡提醒等) | +| `dws attendance +get-summary` | read | 查询某个人的考勤统计摘要(周/月) | +| `dws attendance +list-approve` | read | 查询用户考勤审批单(补卡/加班/请假/出差外出) | +| `dws attendance +list-leave-types` | read | 查询当前用户可用的假期规则列表 | +| `dws attendance +my-attendance` | read | 查我今天的考勤打卡记录(打卡流水,自动解析当前用户) | +| `dws attendance +query-report-data` | read | 根据字段查询考勤报表数据(仅管理员) | +| `dws attendance +search-adjustment-rule` | read | 查询当前用户可管理的补卡规则列表 | +| `dws attendance +search-class` | read | 查询当前用户可管理的班次详情列表 | +| `dws attendance +search-group` | read | 查询当前用户可管理的考勤组列表 | +| `dws attendance +search-overtime-rule` | read | 查询当前用户可管理的加班规则列表 | +| `dws attendance +this-month` | read | 查我本月的考勤打卡记录(打卡流水,自动解析当前用户) | + + +## 意图表 + +| 用户说 | 命令 | +|--------|------| +| "查我自己的打卡 / 某天考勤" | `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](./attendance-report.md)** 强制门禁后选择 `attendance_report_{detail,monthly,daily}.py` | +| "创建班次 / 设置班次" | 先读 [attendance.md](./attendance.md) 的 `class create`,确认后执行 | +| "导入排班 / 安排排班" | 先读 [attendance.md](./attendance.md) 的 `schedule import`,确认后执行 | +| "加入/移出考勤组 / 更新考勤组成员" | `dws attendance group update-members ...`(需确认) | + +## 高频硬约束 + +- 不要在读完 [attendance.md](./attendance.md) 前判断"CLI 不支持"。`class create`、`schedule import`、`group update-members`、`group update` 都是已支持写操作,但必须先展示摘要并等用户确认。 +- 查询迟到/缺勤名单时,空打卡结果不等于"没人迟到"。必须结合排班、`NotSigned`、`Absenteeism`、无记录人员分别说明;数据缺失要标为"无记录/无法判断",不要归为正常。 +- 做部门 Top N 排名时,用户要求前 N 名就必须输出 N 个部门;无打卡记录或无可计算数据的部门按 0 或"无数据"保留在排名中,不能只输出有数据的少数部门。 +- 处理请假/补卡/加班审批时,先用考勤审批模板或 OA 查询确认可操作范围;没有直接提交接口时返回可点击提交链接并说明无法代填提交,不要假装已提交。 +- 更新考勤组成员时必须实际调用 `group update-members`:先 `aisearch/contact` 解析 userId、`group search` 解析 groupId,确认后执行,再 `group filtered-get --member` 回查。 +- 所有 dws 命令带 `--format json`,时间/日期按命令要求分别使用 `YYYY-MM-DD` 或 reference 指定格式。 + +## 跨产品协作 + +- 拿到 userId 前先用 `dingtalk-aisearch` 解析人名 +- 报表导出涉及多人 / 多月 → 脚本内部自动分批 + 切片,输出 xlsx +## 局部意图与 Recipe + +- [局部意图消歧](attendance-intent-guide.md)。 diff --git a/skills/multi/dingtalk-misc/references/devapp.md b/skills/multi/dingtalk-misc/references/devapp.md new file mode 100644 index 00000000..2e9db62a --- /dev/null +++ b/skills/multi/dingtalk-misc/references/devapp.md @@ -0,0 +1,29 @@ +# devapp Shortcut 参考 + + +## Shortcuts(无专用脚本/recipe 时优先) + +以下命令来自独立于 Runtime Schema 的公开 catalog。先运行 `dws shortcut list --service devapp --format json` 读取完整契约,再用 `dws devapp --help` 核对 flags;不要对 `+` 路径调用 `dws schema`。 + +| Shortcut | 风险 | 适用场景 | +|---|---|---| +| `dws devapp +create` | write | 创建开放平台企业内部应用 | +| `dws devapp +delete` | high-risk-write | 删除开放平台企业内部应用(不可逆) | +| `dws devapp +disable` | write | 停用开放平台企业内部应用 | +| `dws devapp +enable` | write | 启用开放平台企业内部应用 | +| `dws devapp +event-list` | read | 查询应用已订阅的事件列表 | +| `dws devapp +get` | read | 查询开放平台企业内部应用详情 | +| `dws devapp +list` | read | 查询开放平台企业内部应用列表 | +| `dws devapp +member-add` | write | 添加开放平台应用成员 | +| `dws devapp +member-list` | read | 查询开放平台应用成员 | +| `dws devapp +member-remove` | write | 移除开放平台应用成员 | +| `dws devapp +permission-list` | read | 查询开放平台应用权限列表 | +| `dws devapp +robot-get` | read | 查询现有应用的机器人配置 | +| `dws devapp +update` | write | 修改开放平台企业内部应用基础信息 | +| `dws devapp +version-check-approval` | read | 预检版本发布是否需要审批(不实际发布) | +| `dws devapp +version-get` | read | 查询指定版本详情 | +| `dws devapp +version-list` | read | 分页查询应用版本列表 | +| `dws devapp +version-status` | read | 查询版本发布/审批状态 | +| `dws devapp +webapp-config` | write | 配置网页应用能力 | +| `dws devapp +webapp-get` | read | 查询网页应用配置 | + diff --git a/skills/multi/dingtalk-devdoc/references/intent-guide.md b/skills/multi/dingtalk-misc/references/devdoc-intent-guide.md similarity index 100% rename from skills/multi/dingtalk-devdoc/references/intent-guide.md rename to skills/multi/dingtalk-misc/references/devdoc-intent-guide.md diff --git a/skills/multi/dingtalk-devdoc/references/devdoc.md b/skills/multi/dingtalk-misc/references/devdoc.md similarity index 53% rename from skills/multi/dingtalk-devdoc/references/devdoc.md rename to skills/multi/dingtalk-misc/references/devdoc.md index a542b62c..a47c872b 100644 --- a/skills/multi/dingtalk-devdoc/references/devdoc.md +++ b/skills/multi/dingtalk-misc/references/devdoc.md @@ -21,3 +21,22 @@ Flags: | 操作 | 从返回中提取 | 用于 | |------|-------------|------| | `devdoc article search` | 文档链接 | 直接展示给用户 | + +--- + +## SKILL 摘要(原 dingtalk-devdoc/SKILL.md 正文) + +## 意图表 + +| 用户说 | 命令 | +|--------|------| +| "查 OAuth2 接入文档" | `dws devdoc article search --query "OAuth2 接入"` | +| "API 调用报错怎么办" | `dws devdoc article search --query "<报错关键词>"` | +| "开放接口文档" | `dws devdoc article search --query "<接口名或场景>"` | + +## 跨产品协作 + +- 钉钉云文档(个人 / 企业内文档)→ 切到 `dingtalk-doc` +## 局部意图与 Recipe + +- [局部意图消歧](devdoc-intent-guide.md)。 diff --git a/skills/multi/dingtalk-ding/references/intent-guide.md b/skills/multi/dingtalk-misc/references/ding-intent-guide.md similarity index 100% rename from skills/multi/dingtalk-ding/references/intent-guide.md rename to skills/multi/dingtalk-misc/references/ding-intent-guide.md diff --git a/skills/multi/dingtalk-ding/references/lite-recipes.md b/skills/multi/dingtalk-misc/references/ding-lite-recipes.md similarity index 88% rename from skills/multi/dingtalk-ding/references/lite-recipes.md rename to skills/multi/dingtalk-misc/references/ding-lite-recipes.md index 2e4b7ef0..9f71da04 100644 --- a/skills/multi/dingtalk-ding/references/lite-recipes.md +++ b/skills/multi/dingtalk-misc/references/ding-lite-recipes.md @@ -4,4 +4,4 @@ ## #1 消息沟通 -所有消息沟通相关的命令详情、参数说明、意图路由和复合工作流,请查阅 [chat.md](../../dingtalk-chat/references/chat-commands.md)。 +所有消息沟通相关的命令详情、参数说明、意图路由和复合工作流,请查阅 [chat.md](../../dingtalk-chat/references/chat.md)。 diff --git a/skills/multi/dingtalk-ding/references/ding.md b/skills/multi/dingtalk-misc/references/ding.md similarity index 74% rename from skills/multi/dingtalk-ding/references/ding.md rename to skills/multi/dingtalk-misc/references/ding.md index 278ee050..e38de3a3 100644 --- a/skills/multi/dingtalk-ding/references/ding.md +++ b/skills/multi/dingtalk-misc/references/ding.md @@ -27,9 +27,6 @@ Flags: ``` ### 查询 DING 消息历史 - -列表项会同时返回 DING 的 `content`、`openDingId` 与状态,可直接读取钉内容,无需再调用详情接口。 - ``` Usage: dws ding message list [flags] @@ -60,7 +57,7 @@ Usage: 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 "姓名" + # 查询 openDingTalkId: dws aisearch person --keyword "姓名" --dimension name Flags: --users string 接收者 openDingTalkId 列表,逗号分隔 (必填) --content string DING 内容 (必填) @@ -69,7 +66,7 @@ Flags: 注意: - 与 `ding message send`(机器人身份)不同:send-personal 以当前用户身份发送,无需 --robot-code - - 接收者使用 openDingTalkId(非 userId),可通过 `dws contact user search --query "姓名"` 获取 + - 接收者使用 openDingTalkId(非 userId),可通过 `dws aisearch person --keyword "姓名" --dimension name` 获取 - sms/call 类型有通信费用,使用前需和用户确认 ``` @@ -95,8 +92,8 @@ Usage: 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 --query "群名" + # 查询 openDingTalkId: dws aisearch person --keyword "姓名" --dimension name + # 查询 openConversationId: dws chat search --keyword "群名" Flags: --group string 原消息所在会话 openConversationId (必填) --message-id string 原消息 openMessageId (必填) @@ -106,7 +103,7 @@ Flags: 注意: - 与 `send-personal` 不同: send-by-message 是将已有聊天消息转发为 DING,需要指定原消息的会话和消息 ID - - 接收者使用 openDingTalkId,可通过 `dws contact user search --query "姓名"` 获取 + - 接收者使用 openDingTalkId,可通过 `dws aisearch person --keyword "姓名" --dimension name` 获取 - sms/call 类型有通信费用,使用前需和用户确认 ``` @@ -160,7 +157,44 @@ dws ding message recall-personal --id --format json ## 注意事项 - `--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` 接收者使用 userId;`send-personal` / `send-by-message` 接收者使用 openDingTalkId(可通过 `dws aisearch person --keyword "姓名" --dimension name` 获取) - `send-by-message` 是将已有聊天消息转发为 DING,需指定 --group 和 --message-id - sms/call 类型有通信费用,使用前需和用户确认 - 默认 `--type app` 为应用内 DING(免费) + +--- + +## SKILL 摘要(原 dingtalk-ding/SKILL.md 正文) + + +## Shortcuts(无专用脚本/recipe 时优先) + +以下命令来自独立于 Runtime Schema 的公开 catalog。先运行 `dws shortcut list --service ding --format json` 读取完整契约,再用 `dws ding --help` 核对 flags;不要对 `+` 路径调用 `dws schema`。 + +| Shortcut | 风险 | 适用场景 | +|---|---|---| +| `dws ding +list` | read | 查询 DING 消息列表 | +| `dws ding +recall-personal` | high-risk-write | 撤回本人发起的 DING | +| `dws ding +receiver-status` | read | 查询 DING 消息接收人已读状态 | +| `dws ding +send-personal` | write | 以本人身份发送 DING 给指定人 | + + +## 意图表 + +| 用户说 | 命令 | +|--------|------| +| "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 --robot-code ` | +| "以我的名义发 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 + +- [局部意图消歧](ding-intent-guide.md);[Lite Recipe](ding-lite-recipes.md)。 diff --git a/skills/multi/dingtalk-live/references/live.md b/skills/multi/dingtalk-misc/references/live.md similarity index 54% rename from skills/multi/dingtalk-live/references/live.md rename to skills/multi/dingtalk-misc/references/live.md index 829d4b83..91e91c6b 100644 --- a/skills/multi/dingtalk-live/references/live.md +++ b/skills/multi/dingtalk-misc/references/live.md @@ -11,3 +11,13 @@ Example: ## 意图判断 - 用户说"直播 / 我的直播" → `live stream list` + +--- + +## SKILL 摘要(原 dingtalk-live/SKILL.md 正文) + +## 意图表 + +| 用户说 | 命令 | +|--------|------| +| "我的直播 / 直播列表" | `dws live stream list` | diff --git a/skills/multi/dingtalk-oa/references/oa.md b/skills/multi/dingtalk-misc/references/oa.md similarity index 90% rename from skills/multi/dingtalk-oa/references/oa.md rename to skills/multi/dingtalk-misc/references/oa.md index b5e05344..0b95cbc0 100644 --- a/skills/multi/dingtalk-oa/references/oa.md +++ b/skills/multi/dingtalk-misc/references/oa.md @@ -341,7 +341,7 @@ Flags: 用户说"加签/前加签/后加签/并加签/增加审批人/追加审批人" → `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 + - `--appender-user-ids` 可通过 `dws aisearch person` 获取目标用户 userId 用户说"退回审批/退回发起人/退回到XX节点/打回重交/重新审批/回退/退回到" → `approval revert-task`(需 --instance-id, --task-id, --target-activity-id, --action) - **前置步骤:** 必须先调用 `approval revert-activities --task-id ` 获取可回退节点列表,提取 `activityId` 和 `revertAction` - **无节点处理:** 若 `revert-activities` 返回空 (`instRevertActivities` 为空),必须明确告知用户"当前任务无可回退节点",不得继续执行退回操作 @@ -390,7 +390,7 @@ dws oa approval list-submitted --limit --page --query # 11. 抄送我的审批单 dws oa approval list-cc --limit --page --query 关键词 --format json -# 12. 转交审批任务(taskId 来自 tasks,toActionerId 来自 contact user search) +# 12. 转交审批任务(taskId 来自 tasks,toActionerId 来自 aisearch person) dws oa approval redirect-task --task-id --to-actioner-id --format json dws oa approval redirect-task --task-id --to-actioner-id --remark "请帮忙处理" --format json @@ -398,8 +398,8 @@ 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) @@ -409,7 +409,7 @@ 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) +# 16. 对审批任务进行加签(instanceId 来自 list-pending/list-submitted/list-executed/detail,taskId 来自list-pending/list-submitted/list-executed/detail中 ,appenderUserIds 来自 aisearch person) 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 @@ -440,8 +440,8 @@ dws oa approval revert-task --instance-id --task-id - `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 但不能是自己 +- `redirect-task` 的 `--to-actioner-id` 可通过 `dws aisearch person` 获取目标用户 userId +- `append-task` 的 `--appender-user-ids` 可通过 `dws aisearch person` 获取目标用户 userId 但不能是自己 - `append-task` 的 `--type` 值:before(前加签)、after(后加签)、Parallel(并加签) - `append-task` 的 `--activate-type` 值:ALL(或签)、ONE_BY_ONE(依次审批) - `append-task` 的 `--agree-all` 值:true(需要全部同意)、false(不需要全部同意) @@ -465,3 +465,43 @@ dws oa approval revert-task --instance-id --task-id |------|------|------| | [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` | + +--- + +## SKILL 摘要(原 dingtalk-oa/SKILL.md 正文) + + +## Shortcuts(无专用脚本/recipe 时优先) + +以下命令来自独立于 Runtime Schema 的公开 catalog。先运行 `dws shortcut list --service oa --format json` 读取完整契约,再用 `dws oa --help` 核对 flags;不要对 `+` 路径调用 `dws schema`。 + +| Shortcut | 风险 | 适用场景 | +|---|---|---| +| `dws oa +list-cc` | read | 获取抄送当前用户的审批单列表 | +| `dws oa +list-executed` | read | 获取当前用户已经处理过的审批单列表 | +| `dws oa +list-forms` | read | 获取当前用户可见的审批表单列表 | +| `dws oa +list-pending` | read | 查询待我处理的审批(时间范围为 epoch 毫秒) | +| `dws oa +list-submitted` | read | 获取当前用户已发起的审批单列表 | +| `dws oa +my-initiated` | read | 列出我发起(提交)的审批单据 | +| `dws oa +search-forms` | read | 按关键字模糊搜索当前用户可见的审批表单 | + + +## 意图表 + +| 用户说 | 命令 | +|--------|------| +| "待我处理的审批 / 7 天内待审" | `python scripts/oa_pending_review.py --days 7` | +| "查审批详情" | `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 --format json` | +| "我已发起的审批" | `dws oa approval list-submitted --format json` | + +## 危险操作 + +`approval approve / reject` 不可撤回,必须先向用户展示摘要并获得明确同意,再执行审批命令。 + +## 跨产品协作 + +- 催别人审批 → 在群里 @对方(`dingtalk-chat`),不要走 #1 消息剧本里的 escalate-ding +- 审批通过后建待办 → 切到 `dingtalk-todo` diff --git a/skills/multi/dingtalk-report/references/_common/conventions.md b/skills/multi/dingtalk-misc/references/report-conventions.md similarity index 81% rename from skills/multi/dingtalk-report/references/_common/conventions.md rename to skills/multi/dingtalk-misc/references/report-conventions.md index 75491aab..803212b8 100644 --- a/skills/multi/dingtalk-report/references/_common/conventions.md +++ b/skills/multi/dingtalk-misc/references/report-conventions.md @@ -9,7 +9,7 @@ | 1 | **并行查详情**:拿到多个 ID 后,用 `&` 合并到同一条 Shell 命令并行执行 + `wait`,**严禁逐条串行** | | 2 | **翻页**:分页接口须拉全直至无更多 | | 3 | **优先批量 API**:有批量接口则用批量;无则按 #1 并行 | -| 4 | **群消息**:必须先 `chat search --query` 得 `openConversationId`,再 `chat message list --group`;多群同条命令并行 | +| 4 | **群消息**:必须先 `chat search --query` 得 `openConversationId`,再 `chat message list --group --time "" --direction older`;多群同条命令并行 | | 5 | **列表少轮次**:带条件搜索/列表 → 一次采全详情;**禁止**无新参数时重复同一 `list` / `search` | ## 多源并行采集(公共模式) @@ -29,7 +29,7 @@ |------|------|--------| | `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 | +| `deptId` | `contact dept search` | `contact dept list-members --ids `;多子部门时对每个子部门分别 `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` | @@ -38,7 +38,7 @@ | `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` | +| `dentryUuid` | `drive list` / `drive mkdir` | `drive info/download/copy/move/rename/delete --node`、`drive list/mkdir/upload/copy/move --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,不能把该数字直接代入后续命令。 +**ID 边界硬约束**:`dentryId` 通常是纯数字,只表示聊天文件消息需要的钉盘条目数字 ID;它不是父目录 ID。遇到 `drive --node/--folder`、`doc --node`、`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-misc/references/report-intent-guide.md similarity index 100% rename from skills/multi/dingtalk-report/references/intent-guide.md rename to skills/multi/dingtalk-misc/references/report-intent-guide.md diff --git a/skills/multi/dingtalk-report/references/lite-recipes.md b/skills/multi/dingtalk-misc/references/report-lite-recipes.md similarity index 100% rename from skills/multi/dingtalk-report/references/lite-recipes.md rename to skills/multi/dingtalk-misc/references/report-lite-recipes.md diff --git a/skills/multi/dingtalk-report/references/report.md b/skills/multi/dingtalk-misc/references/report.md similarity index 86% rename from skills/multi/dingtalk-report/references/report.md rename to skills/multi/dingtalk-misc/references/report.md index f97e6bd3..ed853d68 100644 --- a/skills/multi/dingtalk-report/references/report.md +++ b/skills/multi/dingtalk-misc/references/report.md @@ -133,7 +133,7 @@ CLI 列表命令只返回 JSON-first 数据,不把 Markdown 表作为裸文本 | 日期 | 标题 | 发送人 | 状态 | 钉钉链接 | |------|------|--------|------|----------| -| 2026-05-09 23:08 | 张成强的周报 | 张成强 | 未读 | [在钉钉中查看日志](...) | +| 2026-05-09 23:08 | 张成强的周报 | 张成强 | 未读 | 在钉钉中查看日志(链接取自 `result[].钉钉链接`) | 操作列规则: @@ -437,3 +437,63 @@ dws report outbox list --cursor 0 --size 20 --format json - [doc](../../dingtalk-doc/references/doc.md) — 长文本文档创作(钉钉在线文档),不是日志模版 - [todo](../../dingtalk-todo/references/todo.md) — 个人任务管理,不是日志汇报 + +--- + +## SKILL 摘要(原 dingtalk-report/SKILL.md 正文) + + +## Shortcuts(无专用脚本/recipe 时优先) + +以下命令来自独立于 Runtime Schema 的公开 catalog。先运行 `dws shortcut list --service report --format json` 读取完整契约,再用 `dws report --help` 核对 flags;不要对 `+` 路径调用 `dws schema`。 + +| Shortcut | 风险 | 适用场景 | +|---|---|---| +| `dws report +inbox-list` | read | 列出我收到的日报(按时间范围分页) | +| `dws report +outbox-list` | read | 列出我发出的日报(可选时间/模版名过滤) | + + +## 意图表 + +| 用户说 | 命令 | +|--------|------| +| "今天 / 最近 / 最近一周收到的日志" | `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](./05-reporting.md) recipe | + +## 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`;已发/我创建/我发过 → `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 + +- [局部意图消歧](report-intent-guide.md);[Lite Recipe](report-lite-recipes.md)。 diff --git a/skills/multi/dingtalk-misc/references/sheet-conventions.md b/skills/multi/dingtalk-misc/references/sheet-conventions.md new file mode 100644 index 00000000..803212b8 --- /dev/null +++ b/skills/multi/dingtalk-misc/references/sheet-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 --time "" --direction older`;多群同条命令并行 | +| 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 --ids `;多子部门时对每个子部门分别 `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/copy/move/rename/delete --node`、`drive list/mkdir/upload/copy/move --folder` | +| `dentryId` | `drive info` 的数字字段 | 仅用于 `chat message send --dentry-id` | + +**ID 边界硬约束**:`dentryId` 通常是纯数字,只表示聊天文件消息需要的钉盘条目数字 ID;它不是父目录 ID。遇到 `drive --node/--folder`、`doc --node`、`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-misc/references/sheet-intent-guide.md similarity index 86% rename from skills/multi/dingtalk-sheet/references/intent-guide.md rename to skills/multi/dingtalk-misc/references/sheet-intent-guide.md index 78ee32b2..3f157c5f 100644 --- a/skills/multi/dingtalk-sheet/references/intent-guide.md +++ b/skills/multi/dingtalk-misc/references/sheet-intent-guide.md @@ -7,6 +7,6 @@ | "帮我建一个项目跟踪表" | 创建数据表格 | `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 猜类型 | +| "这个 alidocs 表格链接帮我看下"(粘贴原始 URL) | 先 probe 节点类型 | `dws drive 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-misc/references/sheet.md b/skills/multi/dingtalk-misc/references/sheet.md new file mode 100644 index 00000000..3d828387 --- /dev/null +++ b/skills/multi/dingtalk-misc/references/sheet.md @@ -0,0 +1,263 @@ +# 电子表格 (sheet) 命令参考 + +> **渐进式文档**:本文件为路由层(索引 + 意图判断 + 全局约束),各命令的详细参数、示例和注意事项在 [sheet/](./sheet/) 目录下按需加载。 + +## 适用范围 + +`sheet` 仅支持钉钉在线电子表格(`contentType=ALIDOC`、`extension=axls`)。 + +| 文件类型 | 处理方式 | +|---------|---------| +| 在线电子表格(`axls`) | 走 `sheet` 全部命令 | +| `xlsx` / `xls` / `xlsm` / `csv` | `dws drive download --node --output <路径>` 下载到本地处理,禁止调用任何 `sheet` 子命令 | +| 本地 xlsx/xls 导入为在线表格 | `dws sheet import --file <路径>`(单命令一站式:创建会话→上传→确认→轮询,产出在线电子表格) | +| 在线表格导出为 xlsx | `dws sheet export`(axls → xlsx 格式转换) | + +用户贴原始 `alidocs` URL 时必须先 probe:`dws drive info --node --format json`,按 [链接规范](../../dws-shared/references/url-patterns.md#alidocs-url-类型探测流程) 校验: +- `extension=axls` → 继续走 `sheet` +- `extension=xlsx` / `xls` / `xlsm` / `csv` → 转 `dws drive download`,告知用户"这是本地表格文件,已为你下载到本地处理" + +## URL → NODE_ID + +| URL 格式 | 提取方式 | +|----------|---------| +| `.../i/nodes/{id}` 或 `.../i/nodes/{id}?query` | 取路径末段作 NODE_ID(忽略 query) | +| `.../spreadsheetv2/{key}/...` | **完整 URL 原样传 `--node`**,禁止提取 path segment | + +参数不确定时先 `dws sheet <命令> --help`。 + +## 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`;需要 `columns` / `data` / `dtypes` / `formats` 用 `table-get`;需要公式、样式、数据验证、超链接、富文本等 per-cell 元数据用 `range read`。 +4. **写返回不等于完成**:任何写操作完成后都要用独立读命令回读确认。值写入用 `csv-get` / `range read` / `table-get`,结构变更用 `sheet info`,对象类操作用对应 `list` / `get`。 +5. **大批量纯值不要拼大 JSON**:超过 5 行或 20 个单元格的纯值写入优先 `csv-put`;需要 table/dataframe 语义时用 `table-put`。 +6. **公式写入后分层校验**:写公式前读 [sheet-formula](./sheet/sheet-formula.md)。写后先用 `range read --value-render-option formula` 确认公式文本,再用 `formula-verify` 聚合扫描计算错误;需要确认业务数值时再用 `raw_value` 抽样对账。 +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 + 文本格式 `@`。 + +## 场景速查 + +| 用户要做 | 正确命令 | 动手前读 | 禁止写法 | +|---------|---------|----------|----------| +| 快速查看数据 / 大表分批读取 | `csv-get` | [sheet-read-data](sheet/sheet-read-data.md) | 不传 `--range` 全量 `range read` 大表 | +| 按表格结构读写列名、类型、格式 | `table-get` / `table-put` | [sheet-read-data](sheet/sheet-read-data.md)、[sheet-write-data](sheet/sheet-write-data.md) | 把 table spec 塞进 `batch-update` | +| 少量精确写入、公式、超链接、富文本、数据验证 | `range update` | [sheet-write-data](sheet/sheet-write-data.md)、[sheet-formula](sheet/sheet-formula.md) | 用 `csv-put` / `append` 写公式或富格式 | +| 校验公式错误 / 全表公式检查 | `formula-verify` | [sheet-formula](sheet/sheet-formula.md) | 全表 `range read` 后由 Agent 自行枚举错误值 | +| 批量纯值写入 / CSV 粘贴 | `csv-put` | [sheet-write-data](sheet/sheet-write-data.md) | 为大块纯值手写巨大 `--values` JSON | +| 追加记录到末尾 | `append` | [sheet-write-data](sheet/sheet-write-data.md) | 手算最后一行后 `range update` | +| 查找 / 替换 | `find` / `replace` | [sheet-search-replace](sheet/sheet-search-replace.md) | 读全表后本地过滤或手写替换 | +| 清空 / 排序 / 填充 / 复制移动区域 | `range clear` / `range sort` / `range fill` / `range copy-to` / `range move-to` | [sheet-range-operations](sheet/sheet-range-operations.md) | 用读写组合模拟服务端原子操作 | +| 合并、冻结、分组、行高列宽 | `sheet info` + 结构命令 | [sheet-workbook](sheet/sheet-workbook.md)、[sheet-dimension-operations](sheet/sheet-dimension-operations.md) | 从 `range read` / CSV 空值推断结构 | +| 多个原子写操作组合 | `batch-update` | [sheet-batch-operations](sheet/sheet-batch-operations.md) | 多次独立调用导致半成品 | +| 图片写入单元格 / 浮动图片 | `write-image` / `media-upload` + `create-float-image` | [sheet-media-image](sheet/sheet-media-image.md) | 用 `range update` 写图片 | +| 条件高亮 / 标红 / 数据条 / 色阶 | `cond-format` | [sheet-conditional-format](sheet/sheet-conditional-format.md) | 用静态 `set-style` 冒充条件格式 | +| 单元格评论 / 批注 / @人讨论 | `comment list/create/reply/update/delete` | [sheet-comment](sheet/sheet-comment.md) | 把评论内容写进单元格值 | + +## 触发必读 + +- 涉及公式、计算列、辅助列、占比、增长率、查找计算、公式校验、公式错误扫描:先读 [sheet-formula](./sheet/sheet-formula.md)。 +- 涉及 `range update --values`、超链接、富文本、数据验证、少量样式随值写入:先读 [sheet-write-data](./sheet/sheet-write-data.md)。 +- 涉及公式文本 / 原始值 / 格式化值回读、per-cell 元数据、大表分页:先读 [sheet-read-data](./sheet/sheet-read-data.md)。 +- 涉及合并、冻结、工作表增删改、最后非空边界:先读 [sheet-workbook](./sheet/sheet-workbook.md)。 +- 涉及行列插删、隐藏、尺寸、移动、分组:先读 [sheet-dimension-operations](./sheet/sheet-dimension-operations.md)。 +- 涉及多个写操作一次提交:先读 [sheet-batch-operations](./sheet/sheet-batch-operations.md)。 + +## Reference 索引 + +| Reference | 描述 | +|-----------|------| +| [sheet-workbook](sheet/sheet-workbook.md) | 管理表格文档与工作表。当用户说"创建表格"、"有哪些工作表"、"新建/重命名/隐藏/冻结/复制/删除工作表"、"显示/隐藏网格线"时使用;`info` 可回读冻结行列等工作表结构信息。命令:`create`/`list`/`info`/`new`/`update`/`copy`/`delete-sheet`/`show-gridline`/`hide-gridline` | +| [sheet-read-data](sheet/sheet-read-data.md) | 读取工作表数据。当用户说"读数据"、"看表格内容"、"查看数据"时使用。推荐 `csv-get`(CSV 格式、token 低、防爆保护);需按 table/dataframe 结构读取列名、data、dtypes、formats 时用 `table-get`;需 value + dataValidation / hyperlink / richText / cellStyles 等 per-cell 元数据时用 `range read`。大范围数据建议分页读取(单次 ≤5000 单元格)。命令:`csv-get`/`table-get`/`range read` | +| [sheet-write-data](sheet/sheet-write-data.md) | 写入数据到工作表。当用户说"写数据"、"填表"、"更新单元格"、"写公式"、"超链接"、"写值同时设样式/数据验证"、"追加数据"、"导入CSV"、"写入结构化 table"时使用。大批量纯值(>5行或>20单元格)必须用 `csv-put` 而非 `range update`;结构化 table/dataframe 输入用 `table-put`。命令:`range update`/`append`/`csv-put`/`table-put` | +| [sheet-formula](sheet/sheet-formula.md) | 公式写入、文本回读、错误聚合与结果抽样。当用户说"写公式"、"计算列"、"辅助列"、"总计/占比/增长率/查找计算"、"校验公式"、"检查公式错误"时使用。命令:`range update` / `range read --value-render-option formula/raw_value` / `formula-verify` | +| [sheet-search-replace](sheet/sheet-search-replace.md) | 搜索和替换文本。当用户说"搜索"、"查找"、"替换"、"把A改成B"时使用。禁止用 `range read` 全量读取后客户端过滤代替 `find`,禁止用 `range update` 模拟 `replace`。命令:`find`/`replace` | +| [sheet-range-operations](sheet/sheet-range-operations.md) | 区域结构性操作。当用户说"清空"、"排序"、"自动填充"、"复制区域到"、"移动数据到"时使用。均为服务端原子操作,禁止 `range read`+`range update` 组合模拟。排序前必须先读前几行判断表头。命令:`range clear`/`range sort`/`range fill`/`range copy-to`/`range move-to` | +| [sheet-batch-operations](sheet/sheet-batch-operations.md) | 批量操作。当用户说"批量清除多个区域"、"组合多个写操作"、"先清除再写入"、"插行列再写数据"、"批量创建/取消分组"时使用。`batch-update` 只支持已列明的原子写操作;`table-put` / `table-get` 不放进 batch,结构化 table 请用独立 `table-put`。命令:`range batch-clear`/`batch-update` | +| [sheet-dimension-operations](sheet/sheet-dimension-operations.md) | 行列增删移动、属性设置与分组。当用户说"插入行/列"、"删除行/列"、"隐藏/显示行列"、"设行高/列宽"、"移动行/列"、"追加空行/空列"、"创建/取消行列分组"、"新建分组并设为折叠/展开"时使用。命令:`insert-dimension`/`delete-dimension`/`update-dimension`/`move-dimension`/`add-dimension`/`group-dimension`/`ungroup-dimension` | +| [sheet-style-format](sheet/sheet-style-format.md) | 单元格样式与合并。当用户说"设样式"、"改颜色/字体/对齐"、"数字格式(百分比/货币/日期)"、"合并/取消合并"时使用。纯样式/批量样式走 `set-style`;写值同时设置少量 cell 样式可用 `range update` 的 `cellStyles`。命令:`range set-style`/`range batch-set-style`/`merge-cells`/`unmerge-cells` | +| [sheet-dropdown](sheet/sheet-dropdown.md) | 下拉列表管理。当用户说"设置下拉"、"下拉选项"、"删除下拉"时使用。命令:`set-dropdown`/`get-dropdown`/`delete-dropdown` | +| [sheet-media-image](sheet/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/sheet-filter.md) | 全局筛选。当用户说"筛选"、"过滤"、"只看某些行"(未说"筛选视图")时使用。禁止用"删除不符合条件的行"代替筛选。命令:`filter get`/`create`/`delete`/`update`/`clear-criteria`/`sort` | +| [sheet-filter-view](sheet/sheet-filter-view.md) | 筛选视图(个人化,不影响协作者)。当用户明确说"筛选视图"时使用,与全局筛选相互独立。命令:`filter-view list`/`create`/`update`/`delete`/`info`/`update-criteria`/`delete-criteria`/`list-criteria`/`get-criteria` | +| [sheet-conditional-format](sheet/sheet-conditional-format.md) | 条件格式规则。触发词:标红/标黄/高亮/突出/标记/数据条/色阶/颜色随数据变 → **强制**走条件格式,禁止 `range set-style` 静态样式替代。命令:`cond-format list`/`create`/`update`/`delete` | +| [sheet-export](sheet/sheet-export.md) | 导出表格为 xlsx。当用户说“导出”、“下载xlsx”、“存为Excel”时使用。单命令一站式,CLI 内部自动轮询,禁止 Agent 侧重试。命令:`export` / `export get` | +| [sheet-import](sheet/sheet-import.md) | 导入本地表格文件为在线电子表格。当用户说“导入表格”、“把xlsx传成在线表格”、“上传Excel变在线表格”时使用。单命令一站式,CLI 内部自动轮询,禁止 Agent 侧重试,禁止用 `drive upload` 代替。命令:`import`/`import get` | +| [sheet-chart](sheet/sheet-chart.md) | 浮动图表管理。当用户说“画图/数据可视化/趋势图/对比图/占比图/柱形图/折线图/饼图”时使用。禁止用本地脚本生成静态图片替代。命令:`chart list`/`create`/`update`/`delete` | +| [sheet-pivot-table](sheet/sheet-pivot-table.md) | 透视表管理。当用户说“透视表/分组汇总/交叉分析/按X统计Y”时使用。禁止用公式拼汇总表替代。命令:`pivot-table list`/`create`/`update`/`delete` | +| [sheet-comment](sheet/sheet-comment.md) | 单元格评论管理。当用户说“给单元格加评论/批注”、“看某格的评论”、“回复/更新/删除评论”、“@某人讨论这个数据”时使用。评论锚定单元格(`--sheet-id`+`--range`),禁止把评论写进单元格值。命令:`comment list`/`create`/`reply`/`update`/`delete` | +| sheet-template | 表格模板管理。当用户说“用模板创建表格”、“搜索模板”、“模板列表”时使用。命令:`template list`/`template search`/`template apply` | +| [sheet-version](./sheet/sheet-version.md) | 表格历史版本管理。当用户说“保存版本/存个快照/看历史版本/版本列表/回滚到某个版本/恢复到之前的表格”时使用。回滚为危险操作,默认二次确认;版本号从 `version list` 获取。命令:`version save`/`version list`/`version revert` | + +## 全局硬约束 + +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` 回读工作表结构确认。 +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`;需要 dataframe/table 语义(列名、dtypes、formats、跨 sheet specs)时用 `table-get` / `table-put` +12. **单元格图片用 `write-image`**(`range update` 不支持图片参数) +13. **`export` / `import` 禁止自行轮询**(CLI 内部已完成渐进式退避,最多 30 次约 5 分钟);`export get` / `import get`为单次查询,非终态时可手动再次调用 +14. **单次调用上限**:`range update` / `set-style` 行数 ≤ 1000,单元格总数建议 ≤ 5000(硬限 30000) +15. **大整数精度保护**:超过 `9007199254740991` 的整数和长数字标识符必须按文本写入;`table-put` 中不要使用 JSON number 或 `int64` / `uint64` dtype,改用字符串值 + `object` dtype + `formats`/`cellStyles` 的 `@` +16. **批量写操作推荐用 `batch-update`**:对多个区域重复调用同一写入工具时,推荐合并为单次原子请求(详见 [sheet-batch-operations](./sheet/sheet-batch-operations.md));逐个调用非原子,中途失败会留下半成品。结构化 table 例外,`table-put` 不进 `batch-update` +17. **关键区分**:sheet(电子表格/单元格读写)vs aitable(AI多维表/结构化记录)vs doc(文档) + +## 当前能力边界 + +- **公式**:支持通过 `range update` 写入、通过 `range read --value-render-option formula` 回读公式文本、通过 `formula-verify` 按错误类型聚合扫描、通过 `raw_value` / `formatted_value` 回读计算结果。只有 `formula-verify` 返回 `status=success`、`hasMore=false`、`totalErrors=0` 时,才能声称本次目标范围未发现公式错误;这仍不等价于业务数值一定正确。 +- **视觉规范**:当前不维护独立的表格视觉方案文档;样式、条件格式、图表分别按对应子文档执行。 +- **结构化 table**:`table-get` / `table-put` 是 table/dataframe 语义入口,不嵌入 `batch-update`;需要原子组合时只组合已支持的单元格/结构写操作。 +- **历史版本**:支持通过 `version save`/`list`/`revert` 手动保存快照、查看版本列表、回滚到指定版本(底层复用 doc 域版本能力);回滚为危险操作,需二次确认。 +- **单元格评论**:支持通过 `comment create`/`list`/`reply`/`update`/`delete` 管理单元格评论(锚定 `--sheet-id`+`--range`,内容复用 `doc-comment` 服务、与前端评论同桶,单元格位置由 flex-table-app 承载并以 `customCategory`(cmt-*) 关联),详见 [sheet-comment](sheet/sheet-comment.md)。 +- **未暴露或未确认能力**:迷你图、历史 changeset 等能力未在本入口承诺;只有出现稳定 DWS 命令和可回读语义后再补充。 + +## URL 粘贴场景 + +用户直接粘贴表格 URL(无其他指令): +- 先 probe:`dws drive 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 命令 + +## 导入本地表格 + +用户说"导入Excel/把xlsx转为在线表格/上传表格并在线编辑"时,用 `dws sheet import`(单命令一站式,详见 [sheet-import](./sheet/sheet-import.md)): +```bash +# 导入本地表格文件为在线电子表格(默认表格名取文件名) +dws sheet import --file ./data.xlsx + +# 指定目标文件夹与导入后表格名 +dws sheet import --file ./report.xls --folder-token --name "月度报表" + +# 导入到指定知识库 +dws sheet import --file ./data.xls --workspace +``` +- 支持 `.xlsx` / `.xls`,固定产出**在线电子表格**;只新建、不覆盖已有表格 +- CLI 内部自动完成「创建会话→上传→确认→渐进式退避轮询」(最多 30 次约 5 分钟),**禁止 Agent 侧再实现轮询或重试** +- 导入结果返回 `documentUrl` / `nodeId`,`nodeId` 可直接作为后续 `sheet` 命令的 `--node` +- **禁止用 `dws drive upload --convert` 代替**:drive upload 只上传文件、不在表格导入闭环内,产出不保证为可编辑在线电子表格 + +## nodeId 说明 + +`--node` 同时支持文档 ID、URL、分享链接。`drive list` 返回中必须用 `fileId`(UUID 格式),禁止用 `dentryId`(纯数字)。 + +## 模板管理 + +当用户说“用模板创建表格”、“搜索表格模板”、“有哪些表格模板”时使用。 + +### 获取表格模板列表 + +``` +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 (可选) +``` + +--- + +## SKILL 摘要(原 dingtalk-sheet/SKILL.md 正文) + + +## Shortcuts(无专用脚本/recipe 时优先) + +以下命令来自独立于 Runtime Schema 的公开 catalog。先运行 `dws shortcut list --service sheet --format json` 读取完整契约,再用 `dws sheet --help` 核对 flags;不要对 `+` 路径调用 `dws schema`。 + +| Shortcut | 风险 | 适用场景 | +|---|---|---| +| `dws sheet +list-sheets` | read | 获取表格文档中全部工作表列表 | +| `dws sheet +read` | read | 读取工作表指定范围的结构化单元格数据 | + + +## 意图表 + +| 用户说 | 命令 | +|--------|------| +| "创建电子表格" | `dws sheet create --name "<标题>"` | +| "新建工作表" | `dws sheet new --node --name ""` | +| "读取单元格" | `dws sheet range read --node --sheet-id --range A1:B2` | +| "写入单元格" | `dws sheet range update --node --sheet-id --range A1:B2 --values '[[..]]'` | +| "写入超链接" | `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 --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 前置 + +- 用户直接粘贴 `alidocs.dingtalk.com` URL 且意图不明确时,先执行 + `dws drive info --node "" --format json` 探测;只有 `extension=axls` + 才继续走 `sheet`。 +- `spreadsheetv2` 链接不要截取短 path segment,必须把完整 URL 原样传给 `--node`。 +- 写入类命令必须先用 `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 update` 成功后默认用独立 `range read` / `csv-get` 回读受影响区域,不能只凭 `success` / `updatedRows` / `updatedCells` / `message` 宣称完成。仅当用户明确要求“不要再读/信任写入返回”时,才跳过回读并说明验证边界。 +- `range update` 的 `--values` 必须是二维 JSON,行列数量与 `--range` 完全一致;批量样式用 `range batch-set-style --batch `。 +- 整格超链接只能用 `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`。 +- 同一参数错误最多纠正 2 次;若持续 `nodeId 格式不合法`,回到 + `drive info --node ` 或 `sheet create` 返回重新取 ID,不要无意义重试。 + +## 跨产品协作 + +- 多维表 / 字段类型 → 切到 `dingtalk-aitable` +- 把表数据写进文档 → 切到 `dingtalk-doc` +## 局部意图与 Recipe + +- [局部意图消歧](sheet-intent-guide.md)。 diff --git a/skills/multi/dingtalk-sheet/references/sheet/sheet-batch-operations.md b/skills/multi/dingtalk-misc/references/sheet/sheet-batch-operations.md similarity index 91% rename from skills/multi/dingtalk-sheet/references/sheet/sheet-batch-operations.md rename to skills/multi/dingtalk-misc/references/sheet/sheet-batch-operations.md index e0c9ef55..8975870d 100644 --- a/skills/multi/dingtalk-sheet/references/sheet/sheet-batch-operations.md +++ b/skills/multi/dingtalk-misc/references/sheet/sheet-batch-operations.md @@ -7,7 +7,6 @@ 1. **目标 range 必须落在用户授权范围内**:除用户明示要修改的区域外,子操作禁止扩张到无关单元格 / 列 / 工作表。规划 range 时先确认每个子操作的边界。 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,6 +22,10 @@ - 原子事务:任一操作失败则整批回滚 - 传 `--continue-on-error` 可切换为宽松模式(失败继续) +用户说"批量写入多个结构化 table/跨 sheet 写入 dataframe/table": +- 不使用 `batch-update`;直接调用 `dws sheet table-put --node --sheets '{"sheets":[...]}'` +- `table-put` 自身支持一次写入多个 sheet spec,写入后用 `table-get` 回读 dtype / format / 数据行数 + 用户说"批量创建行列分组/批量取消行列分组/同时调整尺寸并分组": - 连续行/列分组 → `group-dimension` - 取消连续行/列分组 → `ungroup-dimension` @@ -39,7 +42,8 @@ 当同一工具需要对多个区域重复调用时,**推荐**改用 `batch-update` 合并为单次请求——`batch-update` 是原子提交(要么全成功要么整批回滚);逐个调用非原子,中途失败会留下半成品。 **不可放进 `--operations` 的操作**(强行写入会被校验拒或行为未定义): -- `range read` / `csv-get`(读取操作,不在 batch dispatch 表中) +- `range read` / `csv-get` / `table-get`(读取操作,不在 batch dispatch 表中;结构化 table 回读需在 batch 外单独调用) +- `table-put`(当前 `batch-update` 不支持结构化 table 写入;请直接调用独立 `dws sheet table-put`) - `range sort` / `range move-to`(尚未支持,后续版本补充) - `write-image` / `media-upload` / `create-float-image` / `update-float-image`(需本地上传或依赖前置上传句柄) - `set-style` / `range batch-set-style`(自身已是批量入口,不可再嵌套) @@ -56,9 +60,9 @@ Usage: dws sheet range batch-clear [flags] Example: - dws sheet range batch-clear --node --ranges '["Sheet1!A1:B3","Sheet2!C1:D5"]' --yes - dws sheet range batch-clear --node --ranges '["Sheet1!A1:Z1000"]' --type all --yes - dws sheet range batch-clear --node --ranges '["Sheet1!A2:A100","Sheet1!C2:C100"]' --type format --yes + dws sheet range batch-clear --node --ranges '["Sheet1!A1:B3","Sheet2!C1:D5"]' + dws sheet range batch-clear --node --ranges '["Sheet1!A1:Z1000"]' --type all + dws sheet range batch-clear --node --ranges '["Sheet1!A2:A100","Sheet1!C2:C100"]' --type format Flags: --node string 表格文档 ID 或 URL (必填) --ranges string 目标区域 JSON 数组,每项带 sheet 前缀 (必填) @@ -80,8 +84,8 @@ Example: {"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":"group-dimension","input":{"sheet-id":"Sheet1","range":"3:7","group-state":"expand"}} - ]' --yes - dws sheet batch-update --node --continue-on-error --operations '[...]' --yes + ]' + dws sheet batch-update --node --continue-on-error --operations '[...]' Flags: --node string 表格文档 ID 或 URL (必填) --operations string 操作数组 JSON (必填,每项 {toolName, input}) @@ -97,8 +101,8 @@ Notes: - operations 最多 20 条 - 当需要对多个区域执行相同清除时,优先使用 `range batch-clear`(更简洁) - 典型场景:先插入行列再写入数据、先清除再写入、批量合并+调整行高列宽 - - 真实执行必须获得用户确认后加 --yes;--dry-run 不需要 --yes - `group-dimension` 在 batch 中只适合默认展开分组;需要 `--group-state fold` 时请使用独立 `dws sheet group-dimension` + - `table-put` 不支持放进 batch-update;结构化 table 请用独立 `dws sheet table-put` ``` ### 子操作定位规则 @@ -114,6 +118,7 @@ Notes: ] ``` +`table-put` / `table-get` 不支持放进 `batch-update`。结构化 table 写入请直接调用 `dws sheet table-put`,写入完成后在 batch 外单独调用 `table-get` 回读校验。 ## 校验与预览 @@ -129,6 +134,7 @@ CLI 在发送请求前执行以下本地校验(不消耗网络请求): | 每项必须是 object | `operations[N] 不是 object` | `--operations '["string"]'` | | `toolName` 必须是支持的 CLI 命令名 | `unsupported toolName "xxx"` | 传 `"batch-update"`(禁止嵌套)或拼写错误 | | 禁止嵌套 `batch-update` | 同上(`batch-update` 不在 dispatch 表中,自动拦截) | `toolName: "batch-update"` | +| 禁止 `table-put` / `table-get` | `unsupported toolName "table-put"` | 结构化 table 应使用独立 `table-put` | > `input` 内的字段(如 `sheet-id`、`range`、`values` 等)由服务端校验,CLI 不提前拦截——传入空值或缺失必填字段时,请求会到达服务端再返回错误。 @@ -263,6 +269,7 @@ Cause: The requested resource was not found by the identifier 'NonExistentSheet' - ★ **`--sheet-id` 获取规范(强制)**:`sheetId` 未知时必须先通过 `dws sheet list --node --format json` 查询真实的 `sheetId` / 工作表名称后再调用,禁止凭空编造(如臆测为 `Sheet1`、`sheet1`、`0`、`default` 等) - ★ **需要对多个区域执行相同清除操作时,用 `range batch-clear`**:一次原子请求清除多个区域(可跨工作表),失败时整批回滚 - ★ **需要组合多个不同写操作(清除+写入等)时,用 `batch-update`**:原子事务,任一操作失败则整批回滚,避免留下半成品 +- ★ **table 写入不进 batch-update**:当前 `batch-update` 不支持结构化 table 写入;结构化 table 请直接调用 `dws sheet table-put` - ★ **批次完成后必须回读校验**:值写入用 `range read` 或 `csv-get` 抽样回读受影响区域;结构变更用 `sheet info` 回读 - ★ **`batch-update` 不支持嵌套**:`--operations` 中的 `toolName` 必须是原子操作,不可再嵌套 `batch-update` - `batch-update` 支持 `group-dimension` / `ungroup-dimension`;分组结果用 `sheet info` 回读 diff --git a/skills/multi/dingtalk-sheet/references/sheet/sheet-chart.md b/skills/multi/dingtalk-misc/references/sheet/sheet-chart.md similarity index 100% rename from skills/multi/dingtalk-sheet/references/sheet/sheet-chart.md rename to skills/multi/dingtalk-misc/references/sheet/sheet-chart.md diff --git a/skills/multi/dingtalk-misc/references/sheet/sheet-comment.md b/skills/multi/dingtalk-misc/references/sheet/sheet-comment.md new file mode 100644 index 00000000..c28f538e --- /dev/null +++ b/skills/multi/dingtalk-misc/references/sheet/sheet-comment.md @@ -0,0 +1,166 @@ +# sheet comment(表格单元格评论:list / create / reply / update / delete) + +> **前置条件(MUST READ):** 执行本命令前,必须先用 Read 工具读取以下文件: +> 1. [`../sheet.md`](../sheet.md) — 命令路由 + 场景索引 + 意图判断 + 全局约束 +> +> **同任务常配合**:`dws aisearch person`(查 `--mention` 用 userId)/ [`sheet-workbook.md`](sheet-workbook.md)(未知工作表名称时先 `list` 确认真实名称) + +--- + +## 适用范围 + +- 仅支持钉钉在线电子表格(`extension=axls`)。`xlsx` / `xls` / `csv` 等本地表格不支持评论。 +- 评论锚定在**单元格位置**:`create` / `list` 通过 `--sheet-id`(工作表 ID 或名称)+ `--range`(单元格坐标)定位;`reply` / `update` / `delete` 通过 `--comment-key` 操作,不依赖单元格位置。 +- `create` / `list` 为表格专属工具(`doc-comment` server 的 `create_sheet_comment` / `list_sheet_comments`);`reply` / `update` / `delete` 位置无关,复用 `doc-comment` server 的通用评论工具(`reply_comment` / `update_comment` / `delete_comment`)。五个命令统一走 `doc-comment` server。 +- 底层评论内容与文档评论同源存储(bizCode=DOCS),单元格位置由表格引擎(flex-table-app CP)承载,二者以 `cmt-{uuid}` 关联。 + +--- + +## sheet comment list(查询表格评论列表) + +``` +Usage: + dws sheet comment list [flags] +Example: + dws sheet comment list --node + dws sheet comment list --node --sheet-id Sheet1 --range A2 + dws sheet comment list --node --resolve-status unresolved + dws sheet comment list --node --cursor +Flags: + --node string 目标表格的标识,支持传入 URL 或 ID (必填) + --limit int 每页返回的评论数量,默认 50,最大 50 + --cursor string 分页游标,从上一次请求的返回结果中获取 (首次请求不传) + --resolve-status string 按解决状态过滤: resolved (已解决) / unresolved (未解决) + --sheet-id string 工作表 ID 或名称,如 Sheet1(与 --range 一起指定时按单元格过滤) + --range string 单元格位置,A1 表示法,如 A2、B5:C10(与 --sheet-id 一起指定时按单元格过滤) +``` + +- 同时传入 `--sheet-id` 和 `--range` 时,仅返回该单元格的评论(服务端按单元格精确过滤);不传则返回表格全部单元格评论。 + +--- + +## sheet comment create(创建单元格评论) + +``` +Usage: + dws sheet comment create [flags] +Example: + dws sheet comment create --node --sheet-id Sheet1 --range A2 --content "这个数字有问题" + dws sheet comment create --node --sheet-id Sheet1 --range A2 --content "请核实" --mention uid1,uid2 +Flags: + --node string 目标表格的标识,支持传入 URL 或 ID (必填) + --sheet-id string 工作表 ID 或名称,如 Sheet1 (必填) + --range string 单元格位置,A1 表示法,仅支持单个单元格,如 A2 (必填) + --content string 评论的文字内容,纯文本 (必填) + --mention string 被 @ 的用户 uid 列表,逗号分隔 +``` + +- 未知工作表名称时,先 `dws sheet list --node --format json` 确认真实名称,禁止臆测 `Sheet1` / `0` / `default`。 + +--- + +## sheet comment reply(回复评论) + +``` +Usage: + dws sheet comment reply [flags] +Example: + dws sheet comment reply --node --comment-key --content "已核实" + dws sheet comment reply --node --comment-key --content "比心" --emoji + dws sheet 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 列表,逗号分隔 +``` + +- 回复自动归属到被回复评论所在的单元格线程,无需再传 `--sheet-id` / `--range`。 + +--- + +## sheet comment update(更新评论) + +``` +Usage: + dws sheet comment update [flags] +Example: + dws sheet comment update --node --comment-key --content "已按最新数据修正" +Flags: + --node string 目标表格的标识,支持传入 URL 或 ID (必填) + --comment-key string 待更新评论的 commentKey,格式: {13位毫秒时间戳}{32位UUID},可从 list/create 结果获取 (必填) + --content string 更新后的评论文字内容,纯文本 (必填) +``` + +--- + +## sheet comment delete(删除评论) + +> [强制] 危险操作:删除不可恢复。必须先向用户展示操作摘要并获得明确同意,用户同意后才加 `--yes` 执行。 + +``` +Usage: + dws sheet comment delete [flags] +Example: + dws sheet comment delete --node --comment-key --yes +Flags: + --node string 目标表格的标识,支持传入 URL 或 ID (必填) + --comment-key string 待删除评论的 commentKey,格式: {13位毫秒时间戳}{32位UUID},可从 list/create 结果获取 (必填) +``` + +## 关键说明 + +- `--mention` 接受 `userId` 列表(逗号分隔),需要先用 `dws aisearch person --keyword "<姓名>" --dimension name` 拿到 userId。 +- `--comment-key` 是 13 位毫秒时间戳 + 32 位 UUID 的拼接字符串,从 `list` / `create` 返回中提取,用于 `reply` / `update` / `delete`。 +- `create` / `list`(按单元格过滤)的 `--sheet-id` 是工作表 ID 或名称;未知时先 `dws sheet list` 确认,禁止臆测。 +- `reply` 加 `--emoji` 时 `--content` 填表情名称(如 `比心`、`赞`),不是文字内容。 +- `delete` 是不可逆操作;AI Agent 必须先向用户展示操作摘要并获得明确同意,同意后才追加 `--yes`。 + +## 上下文传递 + +| 从返回中提取 | 用于 | +|-------------|------| +| `commentList[].commentKey` | `comment reply/update/delete` 的 `--comment-key` | +| `comment create` 的 `commentKey` | `comment reply/update/delete` 的 `--comment-key` | +| [`sheet-workbook.md`](sheet-workbook.md) `sheet list` 的工作表名称 | `comment create/list` 的 `--sheet-id` | +| `dws aisearch person` 的 `userId` | `comment create/reply` 的 `--mention` | + +## 常用模板 + +```bash +# 查看表格全部单元格评论 +dws sheet comment list --node --format json + +# 仅看某个单元格的评论 +dws sheet comment list --node --sheet-id Sheet1 --range A2 --format json + +# 仅看未解决的评论 +dws sheet comment list --node --resolve-status unresolved --format json + +# 在单元格上创建评论(未知工作表名先 sheet list 确认) +dws sheet list --node --format json +dws sheet comment create --node --sheet-id Sheet1 --range A2 --content "这个数字有问题" --format json + +# 创建评论 + @人(先 aisearch person 拿 userId) +dws aisearch person --keyword "张三" --dimension name --format json +dws sheet comment create --node --sheet-id Sheet1 --range A2 --content "请确认" --mention , --format json + +# 文字回复 +dws sheet comment reply --node --comment-key --content "已核实" --format json + +# 表情回复(--content 填表情名称) +dws sheet comment reply --node --comment-key --content "比心" --emoji --format json + +# 更新评论 +dws sheet comment update --node --comment-key --content "已按最新数据修正" --format json + +# 删除评论(不可逆;必须用户确认后再加 --yes) +dws sheet comment delete --node --comment-key --yes --format json +``` + +## 参考 + +- [`../sheet.md`](../sheet.md)(如何路由到本命令族) +- [`./sheet-workbook.md`](sheet-workbook.md)(取工作表名称) +- `dws aisearch person`(取 mention 用的 userId,跨产品命令) diff --git a/skills/multi/dingtalk-sheet/references/sheet/sheet-conditional-format.md b/skills/multi/dingtalk-misc/references/sheet/sheet-conditional-format.md similarity index 100% rename from skills/multi/dingtalk-sheet/references/sheet/sheet-conditional-format.md rename to skills/multi/dingtalk-misc/references/sheet/sheet-conditional-format.md diff --git a/skills/multi/dingtalk-sheet/references/sheet/sheet-dimension-operations.md b/skills/multi/dingtalk-misc/references/sheet/sheet-dimension-operations.md similarity index 99% rename from skills/multi/dingtalk-sheet/references/sheet/sheet-dimension-operations.md rename to skills/multi/dingtalk-misc/references/sheet/sheet-dimension-operations.md index 745e6793..751feec0 100644 --- a/skills/multi/dingtalk-sheet/references/sheet/sheet-dimension-operations.md +++ b/skills/multi/dingtalk-misc/references/sheet/sheet-dimension-operations.md @@ -334,7 +334,7 @@ dws sheet update-dimension --node --sheet-id \ - `add-dimension` 追加的是空行/空列,与 `append`(追加带数据的行)不同 - `add-dimension` 的 `--length` 必须为正整数(>= 1),行列均不超过 5000 - `group-dimension` / `ungroup-dimension` 的 `--range` 只接受整行或整列范围,不接受普通单元格矩形 -- `group-dimension` 输出和 `sheet info` 回读均使用 `level`,且为 1-based;不要使用旧的 `depth` 字段 +- `group-dimension` 输出使用 `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-misc/references/sheet/sheet-dropdown.md similarity index 100% rename from skills/multi/dingtalk-sheet/references/sheet/sheet-dropdown.md rename to skills/multi/dingtalk-misc/references/sheet/sheet-dropdown.md diff --git a/skills/multi/dingtalk-sheet/references/sheet/sheet-export.md b/skills/multi/dingtalk-misc/references/sheet/sheet-export.md similarity index 93% rename from skills/multi/dingtalk-sheet/references/sheet/sheet-export.md rename to skills/multi/dingtalk-misc/references/sheet/sheet-export.md index 731ab64a..99b38f57 100644 --- a/skills/multi/dingtalk-sheet/references/sheet/sheet-export.md +++ b/skills/multi/dingtalk-misc/references/sheet/sheet-export.md @@ -78,11 +78,20 @@ dws sheet export --node --output ./ # 若命令返回失败或超时,直接告知用户稍后再试,不要自动重调 dws sheet export。 ``` +```bash +# ── 工作流 13: 导出超时后查询任务状态(手动兜底)── + +# 1. 执行导出(一体化命令,内部自动轮询约 5 分钟) +dws sheet export --node --format json +# 若超时,命令返回错误;等待一段时间后重新执行导出即可 +``` + ## 上下文传递 | 操作 | 从返回中提取 | 用于 | |------|-------------|------| | `export` | `downloadUrl`(未指定 --output)/ `导出完成: `(指定 --output) | 直接下发给用户或告知文件已保存到本地。命令内部已完成轮询,不要再调用其他 export 相关命令 | +| `export` 超时中断 | 错误信息 | 告知用户稍后重试 | ## 注意事项 diff --git a/skills/multi/dingtalk-sheet/references/sheet/sheet-filter-view.md b/skills/multi/dingtalk-misc/references/sheet/sheet-filter-view.md similarity index 100% rename from skills/multi/dingtalk-sheet/references/sheet/sheet-filter-view.md rename to skills/multi/dingtalk-misc/references/sheet/sheet-filter-view.md diff --git a/skills/multi/dingtalk-sheet/references/sheet/sheet-filter.md b/skills/multi/dingtalk-misc/references/sheet/sheet-filter.md similarity index 100% rename from skills/multi/dingtalk-sheet/references/sheet/sheet-filter.md rename to skills/multi/dingtalk-misc/references/sheet/sheet-filter.md diff --git a/skills/multi/dingtalk-misc/references/sheet/sheet-formula.md b/skills/multi/dingtalk-misc/references/sheet/sheet-formula.md new file mode 100644 index 00000000..9ed8adad --- /dev/null +++ b/skills/multi/dingtalk-misc/references/sheet/sheet-formula.md @@ -0,0 +1,189 @@ +# 公式写入、回读与错误校验 + +## 使用场景 + +用户说"写公式/计算列/辅助列/总计/占比/增长率/查找计算/自动计算/校验公式/检查公式错误"时使用本页。 + +- 能由表内其他单元格推导的派生值,优先写公式,不要写一次性的静态结果。 +- 写公式前先读表头和 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`。 +- 聚合错误校验:使用 `dws sheet formula-verify`,支持整本表格、单个目标和多个目标。 +- `formula-verify` 扫描已经落表的公式计算结果,按 `#ERROR!` / `#NAME?` / `#DIV/0!` 等错误类型汇总;它不判断一个正常数值是否符合业务预期。 +- `csv-put` / `append` / `table-put` 不作为公式写入协议;`=` 开头内容可能按普通值或 table 数据处理。需要公式时用 `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` 默认模式 | 用于用户肉眼看到的展示值检查 | +| 扫描整本表格公式错误 | `formula-verify --node ` | 不传目标时扫描全部工作表的非空范围 | +| 扫描单个工作表或范围 | `formula-verify --sheet-id ... [--range ...]` | `--range` 只传 A1 范围,不带工作表前缀 | +| 扫描多个目标 | `formula-verify --targets ...` | 每项为 `{"sheetId":"...","range":"..."}`,`range` 可省略 | + +## 推荐流程 + +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. 对本次写入目标运行 `formula-verify`;若返回 `partial` / `hasMore=true`,缩小目标或提高 `--max-cells` 后继续扫描,直到结果完整。 +7. 用 `range read --value-render-option raw_value` 抽样对账业务数值;正常数值不会被 `formula-verify` 判定为业务计算错误。 +8. 若发现错误,先定位依赖单元格、空值、除数为 0、引用范围越界或函数名错误,再重写公式并重新执行文本回读、错误扫描和数值抽样。 + +## 聚合式公式校验 + +### 整本表格 + +不指定 `--sheet-id`、`--range` 或 `--targets` 时,扫描整本表格的全部工作表: + +```bash +dws sheet formula-verify --node --format json +``` + +### 单个工作表或范围 + +`--sheet-id` 支持工作表 ID 或名称;省略 `--range` 时扫描该工作表的非空范围: + +```bash +dws sheet formula-verify --node --sheet-id --format json + +dws sheet formula-verify --node --sheet-id \ + --range "D2:D100" --format json +``` + +`--range` 必须和 `--sheet-id` 一起使用,且只传 `D2:D100` 这类 A1 范围,不能传 `Sheet1!D2:D100`。 + +### 多个目标 + +```bash +dws sheet formula-verify --node \ + --targets '[{"sheetId":"Sheet1","range":"D2:D100"},{"sheetId":"Summary"}]' \ + --format json +``` + +`--targets` 也支持 `@targets.json` 和 `-`(stdin)。每项只允许 `sheetId` 和可选的 `range`;使用 `--targets` 时不能再传 `--sheet-id` 或 `--range`。 + +### 扫描限制与自动化 + +```bash +dws sheet formula-verify --node \ + --max-locations-per-error 20 --max-cells 30000 --format json + +dws sheet formula-verify --node --exit-on-error --format json +``` + +- `--max-locations-per-error` 只限制每类错误返回的 `locations` 和 `samples` 数量,`count` 与 `totalErrors` 仍保留实际扫描到的总数。 +- `--max-cells` 是本次调用跨全部 targets 共享的扫描预算;预算不足时返回 `status=partial`、`hasMore=true`。 +- `--exit-on-error` 适合 CI/自动化:发现公式错误时打印 JSON 结果并返回非 0;`partial` 结果中已经发现错误时同样返回非 0。 + +### 结果判定 + +| 字段 | 含义 | +|------|------| +| `status` | `success` / `errors_found` / `partial` | +| `totalErrors` | 实际扫描到的错误公式单元格总数 | +| `totalFormulas` | 实际扫描到的公式单元格总数 | +| `scannedCells` | 实际扫描的单元格数 | +| `hasMore` | `true` 表示结果不完整,不能据此声称目标范围零错误 | +| `errorSummary` | 按错误类型聚合的 `count`、`locations` 和 `samples` | +| `warningMessage` | `partial` 等情况下的扫描限制提示 | + +判定规则: + +- `status=success`、`hasMore=false`、`totalErrors=0`:本次目标范围未发现公式错误。 +- `status=errors_found`:按 `errorSummary` 修复后重新校验。 +- `status=partial` 或 `hasMore=true`:当前结果不完整;缩小 targets/range 或提高 `--max-cells` 后继续校验。 + +## 写入示例 + +### 单格公式 + +```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. 数值正确性边界 + +`formula-verify` 负责聚合已经落表的公式错误值;`range read` 负责确认实际公式文本和具体计算结果。即使 `formula-verify` 返回 `success`,也仍需对金额、比例、汇率、边界行等关键业务结果做 `raw_value` 抽样对账,因为一个公式可能计算出合法数值但业务逻辑仍然写错。 + +## 常见错误 + +- 用 `csv-put` 写 `=SUM(...)`,导致公式没有按公式协议落表。 +- 用原始二维数组 `--values '[["=B2*C2"]]'`,而不是 cell object。 +- 写整列公式时只写第一行,忘记把 `--range` 和 `--values` 扩成同样行数。 +- 复制公式时没有锁定固定引用,例如税率、汇率、查找表范围。 +- 没有回读 `formula` 模式,只看写入返回 `success`。 +- 只回读展示值,不运行 `formula-verify` 聚合扫描错误。 +- 把 `max-locations-per-error` 误解为错误计数上限;它只截断位置和样本。 +- 看到 `status=partial` 或 `hasMore=true` 仍声称整本表公式零错误。 +- 在 `--range` 中传 `Sheet1!A1:D10`,或把 `--targets` 与 `--sheet-id` 混用。 + +## 关联文档 + +- [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-misc/references/sheet/sheet-import.md b/skills/multi/dingtalk-misc/references/sheet/sheet-import.md new file mode 100644 index 00000000..a03379c5 --- /dev/null +++ b/skills/multi/dingtalk-misc/references/sheet/sheet-import.md @@ -0,0 +1,115 @@ +# 导入 (import) + +## 使用场景 + +### 导入 + +用户说"导入/上传本地表格/把 xlsx 变成在线表格/把 Excel 传成在线电子表格/供应商发来的报价单传成在线表格": +- 导入本地表格文件 → `import`(单命令一站式,内部自动完成创建会话、上传、确认、轮询) +- 需传 `--file` 与目标位置(`--folder-token` 或 `--workspace` 至少一个),`--name` 自定义表格名 +- 固定产出**在线电子表格**;只**新建**文档,不写入/覆盖已有表格 +- 支持格式:`xlsx` / `xls`(按扩展名识别);文件大小上限 20MB +- 禁止用 `drive upload` 代替:`drive upload` 只上传文件,不转换为可编辑的在线电子表格,语义不同且不在表格任务闭环内 +- 禁止在 AI Agent 侧实现轮询或重试,CLI 内部已按渐进式退避策略完成(最多 30 次约 5 分钟) +- 导入后如需 @同事,由 Agent 拿到 `documentUrl` 后再走 `chat`/`doc` 相关命令编排,本命令不负责通知 + +## 命令详细参考 + +### 导入本地表格文件为在线电子表格(异步任务一站式) +``` +Usage: + dws sheet import [flags] # 一站式:创建会话 → 上传 → 确认 → 轮询 + dws sheet import get [flags] # 兜底:按 taskId 手动查询导入结果 +Example: + # 导入 xlsx 为在线电子表格(默认表格名取文件名) + dws sheet import --file ./quote.xlsx + + # 指定目标文件夹与导入后表格名称 + dws sheet import --file ./report.xls --folder-token --name "月度报表" + + # 导入到指定知识库 + dws sheet import --file ./data.xls --workspace + +Flags: + --file string 本地表格文件路径 (必填,支持 xlsx/xls) + --folder-token string 目标文件夹 ID 或 URL (与 --workspace 至少传一个) + --workspace string 目标知识库 ID 或 URL (与 --folder-token 至少传一个) + -n, --name string 导入后表格名称 (可选,默认取文件名去扩展名) +``` + +将本地表格文件(xlsx/xls)导入为一个新的钉钉在线电子表格,与 `dws sheet export`(导出)对称,构成表格"进出闭环"。**单命令一站式**:命令内部自动完成「创建导入会话 → 上传文件到 OSS → 确认导入 → 渐进式退避轮询」全流程,AI Agent 无需自行拆分步骤或实现轮询。 + +**内部流程**: +1. 调 `create_import_session` 获取 `sessionId` 与 OSS `uploadUrl`(入参 `fileName`/`suffix`/`fileSize`,可选 `targetFolderId`/`workspaceId`) +2. HTTP PUT 上传本地文件到 `uploadUrl` +3. 调 `confirm_import` 触发格式转换,获取 `taskId` +4. 按渐进式退避策略轮询 `query_import_task` 直至任务终态或超时 + +**内置轮询策略(CLI 内实现,无需关心)**: +- 第 1~5 次:每次间隔 2 秒 +- 第 6~10 次:每次间隔 5 秒 +- 第 11~20 次:每次间隔 10 秒 +- 第 21~30 次:每次间隔 15 秒 +- **硬上限:最多轮询 30 次(约 5 分钟)**,超时后命令输出结构化 `timed_out:true` + `next_command` 并以成功退出(exit 0) + +**命令返回**: +- 成功:进度日志(`[1/4]`~`[4/4]`)+ 末尾 JSON,含 `success` / `taskId` / `documentUrl` / `documentName` / `documentType` / `nodeId` +- 超时:输出结构化 JSON,含 `success:false` / `timed_out:true` / `taskId` / `status:"processing"` / `next_command`(可直接复制执行的续查命令 `dws sheet import get --task-id `);命令以成功退出(exit 0),业务态由 `success:false` 表达,便于 Agent 编排续查 + +**失败处理(命令内部已处理,Agent 仅需转述)**: +- 转换返回失败状态:命令立即返回错误并附带原因,**禁止自动重试**,告知用户检查文件内容后再试 +- 轮询 30 次仍处理中:命令输出 `timed_out:true` + `next_command` 结构化字段(exit 0),Agent 检测到后按 `next_command` 用 `import get --task-id` 续查 + +**限制**:仅支持 xlsx/xls → 在线电子表格。导入为钉钉在线文档(md/docx 等 → 文字文档)请使用 `doc` 产品的 `import` 命令。 + +### 查询导入任务结果(兜底) +``` +Usage: + dws sheet import get [flags] +Example: + dws sheet import get --task-id + +Flags: + --task-id string 导入任务 ID (必填) +``` + +按 `taskId` 查询导入任务状态。通常不需要手动调用,`import` 会自动轮询到终态;仅在导入命令超时或中断后用于兜底查询。任务状态:`processing`(转换中)/ `completed`(成功,返回 documentUrl)/ `failed`(失败)。 + +## 核心工作流 + +```bash +# ── 工作流 13: 导入本地表格文件为在线电子表格(单命令一站式)── + +# 场景 A:导入 xlsx,默认表格名取文件名(命令内部自动完成上传+转换+轮询) +dws sheet import --file ./quote.xlsx --format json + +# 场景 B:导入 xls 到指定文件夹并自定义表格名 +dws sheet import --file ./report.xls --folder-token --name "月度报表" + +# 场景 C:导入到指定知识库 +dws sheet import --file ./data.xls --workspace + +# 场景 D:导入超时后兜底查询(用命令输出的 taskId) +dws sheet import get --task-id + +# 禁止在 Agent 侧实现任何轮询或重试,CLI 内部已按 2s/5s/10s/15s 渐进式退避自动完成(最多 30 次)。 +# 若命令返回失败或超时,按提示用 import get 查询,不要自动重调 import。 +``` + +## 上下文传递 + +| 操作 | 从返回中提取 | 用于 | +|------|-------------|------| +| `import` | `documentUrl` / `nodeId`(导入成功后返回) | 下发给用户,或作为后续 `sheet` 操作的 `--node`;如需 @同事再走 `chat` 命令编排 | +| `import`(超时) | `timed_out` / `taskId` / `next_command`(结构化输出) | 检测到 `timed_out:true` 后执行 `next_command`(即 `import get --task-id`)续查 | +| `import get` | `status` / `documentUrl` | 判断任务是否完成并取回文档链接 | + +## 注意事项 + +- ★ `import` 仅支持 xlsx/xls → 在线电子表格;导入为文字文档(md/docx 等)请用 `doc import` +- ★ `import` 只**新建**在线电子表格,不写入或覆盖已有表格;要往已有表格追加数据请用 `range update` / `range append` / `csv put` +- ★ `import` 为单命令一站式,CLI 内部已自动完成「创建会话 → 上传 → 确认 → 渐进式退避轮询」,**Agent 不得在外部实现轮询或重试** +- ★ 命令失败或超时时,**禁止自动重调 `import`**;超时会输出 `timed_out:true`,按 `next_command` 用 `import get --task-id` 续查,失败则告知用户检查文件后再试 +- 文件大小上限 20MB;空文件、目录、非白名单格式均会在上传前被 CLI 拦截报错 +- `--name` 未指定时默认取文件名去掉扩展名;`--folder-token` 与 `--workspace` 用于指定目标位置,至少提供一个,均不传会被 CLI 层拦截报错 +- 用户要求"导入表格/上传 xlsx 变在线表格"时,必须使用 `import`,禁止用 `drive upload`(只上传不转换,产出不可编辑,且不在表格闭环内) diff --git a/skills/multi/dingtalk-sheet/references/sheet/sheet-media-image.md b/skills/multi/dingtalk-misc/references/sheet/sheet-media-image.md similarity index 100% rename from skills/multi/dingtalk-sheet/references/sheet/sheet-media-image.md rename to skills/multi/dingtalk-misc/references/sheet/sheet-media-image.md diff --git a/skills/multi/dingtalk-sheet/references/sheet/sheet-pivot-table.md b/skills/multi/dingtalk-misc/references/sheet/sheet-pivot-table.md similarity index 99% rename from skills/multi/dingtalk-sheet/references/sheet/sheet-pivot-table.md rename to skills/multi/dingtalk-misc/references/sheet/sheet-pivot-table.md index cc23a0af..2073a4e5 100644 --- a/skills/multi/dingtalk-sheet/references/sheet/sheet-pivot-table.md +++ b/skills/multi/dingtalk-misc/references/sheet/sheet-pivot-table.md @@ -119,7 +119,7 @@ Flags: 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 + dws sheet pivot-table delete --node NODE_ID --sheet-id SHEET_ID --pivot-table-id PT_ID Flags: --node string 表格文档 ID 或 URL (必填) --sheet-id string 工作表 ID 或名称 (必填) diff --git a/skills/multi/dingtalk-sheet/references/sheet/sheet-range-operations.md b/skills/multi/dingtalk-misc/references/sheet/sheet-range-operations.md similarity index 100% rename from skills/multi/dingtalk-sheet/references/sheet/sheet-range-operations.md rename to skills/multi/dingtalk-misc/references/sheet/sheet-range-operations.md diff --git a/skills/multi/dingtalk-sheet/references/sheet/sheet-read-data.md b/skills/multi/dingtalk-misc/references/sheet/sheet-read-data.md similarity index 75% rename from skills/multi/dingtalk-sheet/references/sheet/sheet-read-data.md rename to skills/multi/dingtalk-misc/references/sheet/sheet-read-data.md index 9da424f9..0e2ae054 100644 --- a/skills/multi/dingtalk-sheet/references/sheet/sheet-read-data.md +++ b/skills/multi/dingtalk-misc/references/sheet/sheet-read-data.md @@ -3,50 +3,34 @@ ## 使用场景 用户说"读数据/看表格内容": -- 需要列名、二维数据、dtype 和 number format,或准备与 `table-put` 配套处理 → `table-get` - 快速查看纯值数据、批量处理、大表分批读 → `csv-get`(token 消耗低,防爆保护) +- 需要 dataframe/table 结构(列名、二维 data、pandas-style dtypes、number formats)→ `table-get` - 需要结构化信息(值+样式+数据验证+富文本+单元格级超链接)、查看公式或原始值 → `range read` -- 需要校验公式写入结果 → 先 `range read --value-render-option formula` 回读公式文本,再 `range read --value-render-option raw_value` 回读计算结果;完整流程见 [sheet-formula](./sheet-formula.md) +- 需要校验公式写入结果 → 先 `range read --value-render-option formula` 回读公式文本,再用 `formula-verify` 聚合扫描错误;需要确认业务数值时用 `raw_value` 抽样对账。完整流程见 [sheet-formula](./sheet-formula.md) - 需要查看合并单元格 / 表头合并结构 → `sheet info`,读取返回的 `mergedRanges`;不要在 `csv-get` 或 `range read` 里找合并信息 ## 命令选择 | 读取目的 | 推荐命令 | 说明 | |---------|---------|------| -| DataFrame 风格结构化读取 | `table-get` | 返回 columns / data / dtypes / formats,可与 table-put 配套 | | 快速查看纯值、数据分析、大表分批读取 | `csv-get` | CSV 格式,token 消耗约为 JSON 的 1/3,内置 maxChars 防爆 | +| 按 table/dataframe 协议读取 | `table-get` | 返回 `columns` / `data` / `dtypes` / `formats`;默认首行为表头 | | 查看数据验证配置(下拉/复选框) | `range read` | 返回 per-cell 结构,含 dataValidation | | 查看单元格样式(背景色/字体/对齐等) | `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 formula` + `formula-verify` + `raw_value` | 先确认公式文本,再聚合扫描错误,最后按需抽样对账业务数值 | | 获取原始值(数字/布尔而非格式化字符串) | `range read --value-render-option raw_value` | value 返回原始类型 | | 查看合并单元格范围 | `sheet info` | 返回 `mergedRanges`,这是工作表结构信息,不属于单元格值读取 | ## 与现有读取能力的区别 - `csv-get` 面向快速浏览和大表纯值读取:返回 CSV 文本、真实行号/列号映射和截断标记,token 低;不表达列类型、number format 或 per-cell 元数据 +- `table-get` 面向 dataframe/table 数据交换:返回 `columns` / `data` / `dtypes` / `formats`,适合后续结构化分析、类型保持和 `table-put` 回写;不返回合并单元格、dataValidation、hyperlink、richText、per-cell `cellStyles` - `range read` 面向精确单元格读取:返回二维 per-cell 对象,可看公式、原始值、dataValidation、hyperlink、richText、cellStyles;适合少量或中等范围的细节检查,但大范围读取 token 成本更高 ## 命令详细参考 -### 读取结构化 table 数据 -``` -Usage: - dws sheet table-get [flags] # 别名: dws sheet table-read -Example: - dws sheet table-get --node - dws sheet table-get --node --sheet-id --range "A1:D20" - dws sheet table-get --node --sheet-id --no-header -Flags: - --node string 表格文档 ID 或 URL (必填) - --sheet-id string 工作表 ID 或名称 - --range string 读取范围,A1 表示法;可带 sheet 前缀 - --no-header 首行不作为表头,自动生成 col1/col2/... -``` - -`table-get` 面向结构化数据处理,返回 `columns`、二维 `data`、`dtypes` 和可用时的 `formats`。未传 `--sheet-id` / `--range` 时由服务端读取目标文档中的 table 数据;大表仍应通过 `--range` 限定范围。需要单元格样式、公式、数据验证或富文本细节时,继续使用 `range read`。 - ### 以 CSV 格式读取工作表数据(推荐) ``` Usage: @@ -84,6 +68,46 @@ Flags: - 分批读取:`--range "A1:J500"`、`--range "A501:J1000"` …… - 单次建议 ≤5000 单元格 +### 以 table/dataframe 协议读取结构化数据 +``` +Usage: + dws sheet table-get [flags] +Example: + dws sheet table-get --node + dws sheet table-get --node --sheet-id --range "A1:D20" + dws sheet table-get --node --sheet-id "Sheet1" --no-header + dws sheet table-get --node --range "Sheet1!A1:D20" +Flags: + --node string 表格文档 ID 或 URL (必填) + --sheet-id string 工作表 ID 或名称;默认不指定 + --range string 读取范围,A1 表示法;可带 sheet 前缀,如 Sheet1!A1:D10;默认读取非空范围 + --no-header 首行不作为表头,自动生成 col1/col2/...;默认 false +``` + +**默认行为**: +- 不传 `--sheet-id` / `--range` 时,读取文档内所有工作表 +- `--range` 带 sheet 前缀时,以 `range` 中的 sheet 为读取目标 +- 不传 `--range` 时,每个目标工作表读取实际非空范围,并裁掉前导全空行/列;显式传 `--range` 时按用户范围原样读取 +- 默认首行作为表头;传 `--no-header` 时不消费表头行,并生成 `col1` / `col2` / ... +- 大表或多 sheet 文档建议显式传 `--sheet-id` 和 `--range`,避免一次返回所有工作表造成超时或 token 过大 + +**返回字段说明**: +- `sheets` — 工作表数组;每项表示一个 table 结果 +- `name` / `sheetId` — 工作表名称与 ID +- `range` — 实际读取范围,A1 表示法 +- `columns` — 列名数组;默认来自第一行,传 `--no-header` 时生成 `col1` / `col2` / ... +- `data` — 二维数组;默认不含表头行。空单元格 / 缺失值在 table 语义上是 `null`;部分返回链路中也可能表现为空对象 `{}`,此时 `{}` 只表示空位,不是 JSON object 单元格值 +- `dtypes` — 按列返回 pandas-style dtype,例如 `object` / `float64` / `bool` / `datetime64[ns]`。普通文本列返回 `object`,当前不返回 `string` +- `formats` — 按列返回 number format code,例如 `@` / `#,##0.00` / `yyyy-mm-dd` + +**适用边界**: +- 不传 `--range` 时读取实际非空范围;如需控制数据量,请显式传 `--range` 分批读取 +- 首行作为表头时,表头必须非空且不重复;没有稳定表头时传 `--no-header` +- `data[][]` 中的 `{}` 表示空位 / 空单元格,按 `null` 处理;不要把它理解成可写入的对象值 +- `table-get` 返回的是值级 table 结构,不返回合并单元格、dataValidation、hyperlink、richText 或 per-cell `cellStyles`。这些信息请使用 `sheet info` 或 `range read` +- `dtypes` / `formats` 是列级推断结果;单列混合类型通常按 `object` 处理。即使 `table-put` 写入时声明 `string`,文本列回读也通常是 `object` +- 大整数/长数字 ID 如果在 `table-get` 中回读为 `float64`,不要把它当作逐位精确值使用;超过 `9007199254740991` 的整数必须在写入时按文本保存,回读应表现为字符串 / `object`,必要时用 `range read --value-render-option raw_value` 复核 + ### 读取工作表数据(per-cell 结构化信息) ``` Usage: @@ -190,7 +214,7 @@ Flags: | `raw_value` | 原始值(如 1000、45808) | 数据处理、计算 | | `formula` | 公式文本(如 =SUM(A1:A10)),无公式时回退原始值 | 查看/复制公式 | -**公式回读建议**:写公式后不要只看写入返回结果。先用 `formula` 模式确认公式文本已落表,再用 `raw_value` 模式检查计算结果和明显错误值;详见 [sheet-formula](./sheet-formula.md)。 +**公式校验建议**:写公式后不要只看写入返回结果。先用 `formula` 模式确认公式文本已落表,再运行 `formula-verify` 聚合扫描错误;关键业务数值继续用 `raw_value` 抽样对账。详见 [sheet-formula](./sheet-formula.md)。 **超时处理建议**:读取大范围数据时若出现超时或响应过慢,请主动缩小 `--range` 查询范围,**建议单次读取的单元格数量控制在 5000 个以内**(例如 50 行 × 100 列、100 行 × 50 列)。对于大表可采用分页读取策略: - 先通过 `info` 获取 `nonEmptyRange.range`,或用 `nonEmptyRange.lastRow` / `nonEmptyRange.lastColumn` 确定 A1 边界 @@ -228,6 +252,7 @@ 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` -- ★ 大整数和长数字标识符回读校验:精确 ID 应按字符串保存;超过 `9007199254740991` 时不要把数值型回读结果视为逐位精确 +- `table-get` 返回 table/dataframe 结构,适合需要 `columns` / `data` / `dtypes` / `formats` 的场景;它不返回 per-cell 元数据,也不替代 `range read` +- ★ 大整数和长数字标识符回读校验:精确 ID 应是字符串 / `object`;若看到 `float64`,说明它已经按数值路径处理,超过 `9007199254740991` 时可能已丢精度 - `range read` 遇到超时或响应过慢时,应缩小 `--range` 查询范围,**单次读取的单元格数量建议控制在 5000 个以内**;数据量较大时通过 `info` 的 `nonEmptyRange.range` 获取 A1 边界后分批读取,避免不传 `--range` 直接读取整个大工作表 - ★ 当用户要求搜索/查找表格数据时,使用 `find` 命令,不要用 `range read` 读取全量数据后自行过滤——`find` 支持服务端搜索,效率更高、语义更准确 diff --git a/skills/multi/dingtalk-sheet/references/sheet/sheet-search-replace.md b/skills/multi/dingtalk-misc/references/sheet/sheet-search-replace.md similarity index 84% rename from skills/multi/dingtalk-sheet/references/sheet/sheet-search-replace.md rename to skills/multi/dingtalk-misc/references/sheet/sheet-search-replace.md index 49293d37..61a3db16 100644 --- a/skills/multi/dingtalk-sheet/references/sheet/sheet-search-replace.md +++ b/skills/multi/dingtalk-misc/references/sheet/sheet-search-replace.md @@ -24,19 +24,19 @@ Usage: dws sheet find [flags] Example: # 基本搜索 - dws sheet find --node --sheet-id --query "销售额" + dws sheet find --node --sheet-id --find "销售额" # 在指定范围内搜索 - dws sheet find --node --sheet-id --query "合计" --range "A1:D100" + dws sheet find --node --sheet-id --find "合计" --range "A1:D100" # 正则表达式搜索(不区分大小写) - dws sheet find --node --sheet-id --query "^total" --use-regexp --match-case=false + dws sheet find --node --sheet-id --find "^total" --use-regexp --match-case=false # 精确匹配整个单元格内容 - dws sheet find --node --sheet-id --query "完成" --match-entire-cell + dws sheet find --node --sheet-id --find "完成" --match-entire-cell # 搜索公式文本 - dws sheet find --node --sheet-id --query "SUM" --match-formula + dws sheet find --node --sheet-id --find "SUM" --match-formula Flags: --node string 表格文档 ID 或 URL (必填) --sheet-id string 工作表 ID 或名称 (必填) @@ -82,19 +82,19 @@ Flags: dws sheet list --node --format json # 2. 基本搜索 — 在指定工作表中查找文本 -dws sheet find --node --sheet-id --query "销售额" --format json +dws sheet find --node --sheet-id --find "销售额" --format json # 3. 在指定范围内搜索 -dws sheet find --node --sheet-id --query "合计" --range "A1:D100" --format json +dws sheet find --node --sheet-id --find "合计" --range "A1:D100" --format json # 4. 正则搜索(不区分大小写) -dws sheet find --node --sheet-id --query "^total" --use-regexp --match-case=false --format json +dws sheet find --node --sheet-id --find "^total" --use-regexp --match-case=false --format json # 5. 精确匹配整个单元格 -dws sheet find --node --sheet-id --query "完成" --match-entire-cell --format json +dws sheet find --node --sheet-id --find "完成" --match-entire-cell --format json # 6. 搜索公式文本 -dws sheet find --node --sheet-id --query "SUM" --match-formula --format json +dws sheet find --node --sheet-id --find "SUM" --match-formula --format json ``` ## 上下文传递 diff --git a/skills/multi/dingtalk-sheet/references/sheet/sheet-style-format.md b/skills/multi/dingtalk-misc/references/sheet/sheet-style-format.md similarity index 100% rename from skills/multi/dingtalk-sheet/references/sheet/sheet-style-format.md rename to skills/multi/dingtalk-misc/references/sheet/sheet-style-format.md diff --git a/skills/multi/dingtalk-misc/references/sheet/sheet-version.md b/skills/multi/dingtalk-misc/references/sheet/sheet-version.md new file mode 100644 index 00000000..3a119745 --- /dev/null +++ b/skills/multi/dingtalk-misc/references/sheet/sheet-version.md @@ -0,0 +1,69 @@ +# 历史版本 (version) + +## 使用场景 + +管理钉钉在线电子表格的历史版本快照。当用户说"保存版本/存个快照/看历史版本/版本列表/回滚到某个版本/恢复到之前的表格"时使用。 + +- 手动保存当前表格为一个版本快照 → `version save` +- 查看表格的历史版本列表(含版本号、名称、创建人、创建时间) → `version list`(别名 `ls`) +- 把表格回滚到指定历史版本 → `version revert`(危险操作,默认需二次确认) + +三个命令统一用 `--node` 指定表格文档(ID 或 URL)。回滚用的**版本号 `--version` 从 `version list` 的返回中获取**,禁止臆测。 + +## 命令详细参考 + +### 保存表格版本快照 +``` +Usage: + dws sheet version save [flags] +Example: + dws sheet version save --node SHEET_ID + +Flags: + --node string 表格文档 ID 或 URL (必填) +``` +手动为当前在线电子表格生成一个历史版本快照,便于后续查看或回滚。 + +### 查看表格历史版本列表 +``` +Usage: + dws sheet version list [flags] + dws sheet version ls [flags] +Example: + dws sheet version list --node SHEET_ID + dws sheet version list --node SHEET_ID --limit 10 + +Flags: + --node string 表格文档 ID 或 URL (必填) + --limit int 返回版本数量上限 (可选) + --cursor string 分页游标 (可选,游标分页) +``` +返回表格的历史版本列表;回滚前先用它拿到目标 `version`(版本号)。 + +### 回滚表格到指定版本 +``` +Usage: + dws sheet version revert [flags] +Example: + dws sheet version revert --node SHEET_ID --version 3 --yes + +Flags: + --node string 表格文档 ID 或 URL (必填) + --version int 目标版本号 (必填,从 version list 获取) +``` +把表格回滚到指定历史版本。**危险操作**:会覆盖当前内容,默认弹出二次确认;AI Agent 或脚本执行时可加全局 `--yes` 跳过确认。 + +## 上下文传递 + +| 操作 | 从返回中提取 | 用于 | +|------|-------------|------| +| `version list` | `version`(版本号) | 作为 `version revert --version` 的入参 | +| `version save` | 版本快照结果 | 确认已生成快照 | +| `version revert` | 回滚结果 | 确认回滚完成,回读表格内容验证 | + +## 注意事项 + +- ★ 回滚 `version revert` 会**覆盖当前表格内容**,属危险操作,默认二次确认;确认无误再执行,脚本/Agent 场景加 `--yes` +- ★ `--version` 必须来自 `version list` 的真实返回,禁止臆测版本号 +- 底层复用 doc 域的历史版本能力(`save_doc_version` / `list_doc_versions` / `revert_doc_version`),`--node` 传在线电子表格文档即可 +- 回滚后应用独立读命令(`csv-get` / `range read`)回读确认,避免"写返回不等于完成" diff --git a/skills/multi/dingtalk-sheet/references/sheet/sheet-workbook.md b/skills/multi/dingtalk-misc/references/sheet/sheet-workbook.md similarity index 88% rename from skills/multi/dingtalk-sheet/references/sheet/sheet-workbook.md rename to skills/multi/dingtalk-misc/references/sheet/sheet-workbook.md index b8f0c4d6..eae6ed42 100644 --- a/skills/multi/dingtalk-sheet/references/sheet/sheet-workbook.md +++ b/skills/multi/dingtalk-misc/references/sheet/sheet-workbook.md @@ -35,10 +35,6 @@ 用户说"删除工作表/移除工作表/删掉这个Sheet": - 删除工作表 → `delete-sheet`(不可逆操作,执行前必须向用户确认) -用户说"显示网格线/隐藏网格线/去掉单元格网格": -- 显示网格线 → `show-gridline` -- 隐藏网格线 → `hide-gridline` - ## 命令详细参考 ### 创建钉钉表格文档 @@ -76,11 +72,12 @@ Example: dws sheet info --node dws sheet info --node --sheet-id dws sheet info --node --sheet-id "Sheet1" - dws sheet info --node --sheet-id + dws sheet info --node --sheet-id --include groups + dws sheet info --node --sheet-id --include row_heights,col_widths,hidden_rows,hidden_cols Flags: --node string 表格文档 ID 或 URL (必填) --sheet-id string 工作表 ID 或名称 (不传则返回第一个工作表) - --include strings 可选扩展信息,逗号分隔;当前支持 groups + --include strings 可选扩展信息,逗号分隔;支持 groups / row_heights / col_widths / hidden_rows / hidden_cols / frozen ``` 返回字段中 `mergedRanges` 是当前工作表的合并单元格范围列表(A1 表示法,如 `["C7:D11"]`)。它属于工作表结构/布局元数据:读写单元格内容前,如需判断表头、分组标题、续写位置或避开合并冲突,应先看 `sheet info`,不要在 `range read` / `csv-get` 的单元格值里寻找合并信息。 @@ -89,12 +86,20 @@ Flags: 最后非空数据边界通过 `nonEmptyRange` 返回,字段均为 A1/UI 语义:`range` 是从 `A1` 到最后非空单元格的范围,`lastCell` 是最后非空单元格地址,`lastRow` 是 1-based 行号,`lastColumn` 是列字母。空表时 `nonEmptyRange` 为 `null`。不要使用旧的 0-based 字段 `lastNonEmptyRow` / `lastNonEmptyColumn`。 -需要读取行列分组时,检查 `sheet info` 返回中的以下字段: +需要读取行列分组时,加 `--include groups`。返回字段: - `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`、冻结或分组等结构元数据。 +需要读取行高、列宽、隐藏行列时,按需加对应 `--include` 值(可组合,逗号分隔)。返回字段: +- `--include row_heights` → `rowHeights`(逐条行高,单项 `{row, pixelSize}`,`row` 为 1-based)+ `defaultRowHeight`(默认行高像素) +- `--include col_widths` → `colWidths`(逐条列宽,单项 `{column, pixelSize}`,`column` 为列字母)+ `defaultColumnWidth`(默认列宽像素) +- `--include hidden_rows` → `hiddenRows`(隐藏行区间列表,单项 `{startRow, endRow}`,1-based,连续隐藏行合并为一个区间) +- `--include hidden_cols` → `hiddenCols`(隐藏列区间列表,单项 `{startColumn, endColumn}`,列字母,连续隐藏列合并为一个区间) + +注意:`rowHeights` / `colWidths` 只返回**显式设置过**尺寸的行列(未自定义的行列不出现,用 `defaultRowHeight` / `defaultColumnWidth` 兜底);隐藏行列即使未改尺寸也会出现在 `hiddenRows` / `hiddenCols`。`frozen` 也在 include 白名单中,但冻结字段本就默认返回,传 `frozen` 与否结果一致。 + ### 新建工作表 ``` Usage: @@ -231,6 +236,9 @@ dws sheet range update --node --sheet-id --range "A1:C1" \ 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 +# 4. 独立回读实际写入范围 +dws sheet range read --node --sheet-id --range "A1:C4" --format json + # ── 工作流 2: 读取已有表格数据 ── # 1. 获取工作表列表 @@ -265,7 +273,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` / `frozenRowCount` / `frozenColumnCount` / `rowGroups` / `columnGroups` | 确定数据范围、追加写入起始行、判断合并单元格结构、回读冻结与行列分组 | +| `info` | `rowCount` / `nonEmptyRange.range` / `nonEmptyRange.lastRow` / `nonEmptyRange.lastColumn` / `mergedRanges` / `frozenRowCount` / `frozenColumnCount` / `rowGroups` / `columnGroups` / `rowHeights` / `colWidths` / `hiddenRows` / `hiddenCols` | 确定数据范围、追加写入起始行、判断合并单元格结构、回读冻结与行列分组、回读行高列宽与隐藏行列 | ## 注意事项 diff --git a/skills/multi/dingtalk-sheet/references/sheet/sheet-write-data.md b/skills/multi/dingtalk-misc/references/sheet/sheet-write-data.md similarity index 73% rename from skills/multi/dingtalk-sheet/references/sheet/sheet-write-data.md rename to skills/multi/dingtalk-misc/references/sheet/sheet-write-data.md index ffce565e..655dd701 100644 --- a/skills/multi/dingtalk-sheet/references/sheet/sheet-write-data.md +++ b/skills/multi/dingtalk-misc/references/sheet/sheet-write-data.md @@ -3,13 +3,18 @@ ## 使用场景 用户说"写数据/填表/更新单元格/写入公式": -- 从 DataFrame/结构化对象一次写入一个或多个工作表 → `table-put` - 更新数据 → `range update` -- 写入公式前先读 [sheet-formula](./sheet-formula.md);写完必须回读公式文本和计算结果,不能只看写入返回成功 +- 写入公式前先读 [sheet-formula](./sheet-formula.md);写完必须回读公式文本并运行 `formula-verify`,关键业务数值还要抽样对账,不能只看写入返回成功 - 【强制】`--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 文本即可,更简洁高效且支持自动扩容 +用户说"写入结构化 table/dataframe/带列类型和格式的数据/跨 sheet 写入表格": +- 写入结构化 table → `table-put` +- `table-put` 的 `--sheets` 是 table spec JSON,支持一次写入一个或多个工作表;适合有明确 `columns` / `data` / `dtypes` / `formats` 的数据 +- table spec 较长或来自文件时,优先用 `--sheets @table.json`,避免 shell 转义长 JSON 出错 +- 如果只是一段 CSV 文本且没有 dtype/format 需求,用 `csv-put` 更简单;如果需要公式、超链接、dataValidation、richText,用 `range update` + 用户说"追加数据/添加行/在末尾加数据/新增记录": - 追加数据 → `append` @@ -18,40 +23,34 @@ - 与 `range update` 的区别:`csv-put` 接受 CSV 文本直接写入,无需手动构造二维 JSON 数组;适合大批量纯值写入 - 与 `append` 的区别:`csv-put` 写入指定位置(--start-cell),`append` 在末尾追加 +**与现有写入能力的区别**: + +- `range update` 面向精确单元格写入:适合公式、超链接、dataValidation、richText、少量 cellStyles 和 `{}` 跳过;必须自己提供 `--sheet-id`、`--range` 和维度完全匹配的二维 cell 对象 +- `append` 面向简单追加行:自动定位到末尾,只写原始值,不支持样式、公式、超链接或数据验证 +- `csv-put` 面向大批量纯值导入:输入 CSV 文本,写入指定起点,适合数据来源本来就是 CSV/表格文本;不保留 dtype/format 协议,也不支持富格式 +- `table-put` 面向 dataframe/table 数据交换:输入 `columns` / `data` / `dtypes` / `formats`,可一次写多个 sheet,适合和 `table-get` 往返;不支持 dataValidation、hyperlink、richText、附件或图片 + **四种写入命令能力对比**: -| 能力 | `table-put` | `range update` | `append` | `csv-put` | -|------|-------------|----------------|----------|-----------| -| 多工作表结构化写入 | 支持 | 不支持 | 不支持 | 不支持 | -| columns / dtypes / formats | 支持 | 不支持 | 不支持 | 不支持 | -| 公式(`=` 开头) | 按 dtype/底层能力 | 支持 | 不支持 | 不支持(当文本) | -| 单元格级超链接(`hyperlink`) | 不使用此命令 | 支持 | 不支持 | 不支持 | -| 富文本(片段链接/附件/图片) | 不使用此命令 | 支持 | 不支持 | 不支持 | -| 原始值(纯数字/字符串) | 支持 | 支持 | 支持 | 支持 | -| 自动定位末尾 | mode=append | 不支持 | 支持 | 不支持 | -| 自动扩容行列 | 支持 | 不支持 | 支持 | 支持 | +| 能力 | `range update` | `append` | `csv-put` | `table-put` | +|------|---------------|----------|-----------|-------------| +| 公式(`=` 开头) | 支持 | 不支持 | 不支持(当文本) | 不作为公式协议;公式请用 `range update` | +| 单元格级超链接(`hyperlink`) | 支持 | 不支持 | 不支持 | 不支持 | +| 富文本(片段链接/附件/图片) | 支持 | 不支持 | 不支持 | 不支持 | +| richText 片段样式(bold/color) | 支持 | 不支持 | 不支持 | 不支持 | +| `cellStyles`(背景色/字号/对齐等 cell-level 样式) | 支持 | 不支持 | 不支持 | 支持基础 cell-level 样式 | +| `{}` 跳过(保留原值) | 支持 | 不适用 | 不适用 | 不支持,按 table 矩阵写入 | +| `dataValidation`(下拉/复选框) | 支持 | 不支持 | 不支持 | 不支持 | +| 原始值(纯数字/字符串/布尔/null) | 支持 | 支持 | 支持 | 支持 | +| 列级 dtype / number format | 手工写 cellStyles | 不支持 | 自动识别为主 | 支持 `dtypes` / `formats` | +| 自动定位末尾 | 不支持 | 支持 | 不支持 | `mode:"append"` 支持 | +| 自动扩容行列 | 不支持 | 支持 | 支持 | 支持 | +| 跨 sheet 一次写入 | 不支持 | 不支持 | 不支持 | 支持 `sheets[]` | -`range update` 用于公式、超链接、dataValidation、richText 和少量 cellStyles 等精确单元格写入;`append` 只做末尾追加;`csv-put` 面向大批量纯值;`table-put` 面向多工作表结构化数据。公式写入流程见 [sheet-formula](./sheet-formula.md);写入后至少回读 `formula` 模式确认公式文本,再回读 `raw_value` 或展示值检查计算结果。 +公式写入与校验的完整流程见 [sheet-formula](./sheet-formula.md)。写公式后先回读 `formula` 模式确认公式文本,再运行 `formula-verify` 聚合扫描错误;需要确认业务数值时继续回读 `raw_value` 抽样对账。 ## 命令详细参考 -### 写入结构化 table 数据 -``` -Usage: - dws sheet table-put [flags] # 别名: dws sheet table-write -Example: - dws sheet table-put --node \ - --sheets '[{"name":"Data","columns":["name","score"],"data":[["Alice",95]],"dtypes":{"score":"float64"}}]' - - dws sheet table-put --node --sheets @table.json - cat table.json | dws sheet table-put --node --sheets - -Flags: - --node string 表格文档 ID 或 URL (必填) - --sheets string sheet table JSON、@文件路径 或 - 表示 stdin (必填) -``` - -`--sheets` 接受 JSON 数组、`{"sheets":[...]}` 包装对象或单个 sheet 对象,CLI 会统一转换为非空数组。每个 sheet 至少需要 `columns`,并提供 `sheetId` 或 `name`;`name` 必须非空且少于 31 个字符。写入后必须用 `table-get` 回读验证。`table-get/table-put` 不支持放入 `sheet batch-update`。 - ### 更新工作表指定区域内容 ``` Usage: @@ -97,7 +96,7 @@ 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)。 +**公式校验要求**:公式用 `{"type":"text","text":"=..."}` 写入。写完后先执行 `range read --value-render-option formula` 确认公式文本,再执行 `formula-verify` 聚合扫描错误;关键业务结果继续用 `range read --value-render-option raw_value` 抽样对账。详见 [sheet-formula](./sheet-formula.md)。 ### 在工作表末尾追加数据 ``` @@ -152,6 +151,121 @@ Flags: - CSV 文本上限 2M 字符,单元格总数上限 30000 - 特殊字符处理:CLI 会自动过滤 `\r`(Windows 换行符)和 BOM(UTF-8 文件头标记),Excel/Windows 导出的 CSV 可直接使用;如 CSV 数据中含零宽字符(U+200B 等)或 Bidi 控制符,CLI 会拒绝并报错 +### 写入结构化 table 数据 +``` +Usage: + dws sheet table-put [flags] +Example: + dws sheet table-put --node \ + --sheets '[{"name":"Sheet1","columns":["name","score"],"data":[["Alice",95]],"dtypes":{"score":"float64"},"formats":{"score":"0"}}]' + + dws sheet table-put --node --sheets @table.json + cat table.json | dws sheet table-put --node --sheets - +Flags: + --node string 表格文档 ID 或 URL (必填) + --sheets string sheet table JSON、@文件路径 或 - 表示 stdin (必填) +``` + +`--sheets` 支持三种形态: +```json +[ + {"name":"Sheet1","columns":["name"],"data":[["Alice"]]} +] +``` +```json +{ + "sheets": [ + {"name":"Sheet1","columns":["name"],"data":[["Alice"]]} + ] +} +``` +```json +{"name":"Sheet1","columns":["name"],"data":[["Alice"]]} +``` + +**单个 sheet spec 字段**: + +| 字段 | 必填 | 说明 | +|------|------|------| +| `name` | 与 `sheetId` 二选一 | 工作表名称。名称已存在时写入已有 sheet;不存在时创建同名 sheet | +| `sheetId` | 与 `name` 二选一 | 工作表 ID;传入时优先于 `name`,不会按 `name` 创建新 sheet | +| `startCell` | 否 | overwrite 起始单元格,或 append 起始列;默认 `A1` | +| `mode` | 否 | `overwrite`(默认)或 `append` | +| `header` | 否 | 是否写入表头。overwrite 默认 true;append 默认 false;append 到空表且未显式指定时写表头 | +| `allowOverwrite` | 否 | 是否允许覆盖已有值。默认 true;显式传 false 时,目标区域已有值会报错 | +| `columns` | 是 | 有序列名数组;不可为空、不可重复、不可包含空列名 | +| `data` | 否 | 行优先二维数组,默认 `[]`;每行宽度必须等于 `columns.length`;值支持 string / number / boolean / null。写入空位建议传 `null`;若复用 `table-get` 结果,`data[][]` 中的 `{}` 表示空位 / 空单元格,按 `null` 对待 | +| `dtypes` | 否 | 按列名指定 pandas-style dtype;默认 `{}`,未指定列按 object/string 写入。普通文本建议声明 `object`;`string` 会按文本写入,但 `table-get` 回读通常仍是 `object` | +| `formats` | 否 | 按列名指定 number format code;默认 `{}`,系统按 dtype 补日期/文本默认格式 | +| `cellStyles` | 否 | 基础 cell-level 样式,默认不设置样式;支持二维样式矩阵,或按列名指定样式对象。字段范围与下文 `cellStyles 子结构` 一致 | + +**完整示例**: +```json +{ + "sheets": [ + { + "name": "销售", + "startCell": "A1", + "mode": "overwrite", + "allowOverwrite": true, + "columns": ["日期", "金额", "渠道"], + "data": [ + ["2026-07-06", 1500.5, "门店"], + ["2026-07-07", 2300.75, "线上"] + ], + "dtypes": { + "日期": "datetime64[ns]", + "金额": "float64", + "渠道": "object" + }, + "formats": { + "日期": "yyyy-mm-dd", + "金额": "¥#,##0.00" + }, + "cellStyles": { + "金额": {"numberFormat": "¥#,##0.00"} + } + } + ] +} +``` + +**dtype 与 format 规则**: +- `datetime64[ns]` / `date` 按日期写入;日期值建议使用 ISO 字符串(如 `2026-07-06`),未传 format 时默认 `yyyy-mm-dd` +- `bool` / `boolean` 按布尔写入 +- `int*` / `uint*` / `float*` / `complex*` 按数值写入;读回时数值列通常统一表现为 `float64` +- `object` 是普通文本列的推荐 dtype;`string` 等其他 dtype 按字符串列处理,未传 format 时默认文本格式 `@`。读回时文本列通常表现为 `object`,不要期待返回 `string` +- `formats` 使用表格 number format code,含义与 `range set-style --number-format` 一致 +- 大整数精度保护:超过 `9007199254740991` 的整数、长数字 ID、订单号、手机号、SKU 等需要逐位精确的值,必须按文本写入。`table-put` 中即使把值写成字符串,只要 dtype 声明为 `int64` / `uint64` / `float64`,链路仍会按 number 处理并可能丢精度;应使用字符串值 + `object` dtype + 文本格式 `@` + +**大整数/长数字 ID 示例**: +```json +{ + "name": "订单", + "columns": ["order_id", "amount"], + "data": [["9007199254740993", 128.5]], + "dtypes": {"order_id": "object", "amount": "float64"}, + "formats": {"order_id": "@", "amount": "#,##0.00"} +} +``` + +**返回字段说明**: +- `sheets` — 写入结果数组,每项对应一个 sheet spec +- `name` / `sheetId` — 实际写入的工作表名称与 ID +- `range` — 实际写入范围 +- `dataRows` — 写入的数据行数,不含表头 +- `columns` — 写入列数 +- `writes` — 实际写入次数 +- `mode` — 实际写入模式,`overwrite` 或 `append` + +**限制与边界**: +- `table-put` 支持跨 sheet:一个 `sheets[]` 可包含多个 sheet spec +- 单次 table 写入单元格数上限 30000,计算包含表头行;超过上限需拆分 +- 大整数和长数字标识符不应声明为 `int64` / `uint64`。例如 `9007199254740993` 按数值写入会回读为 `9007199254740992`;要保精度必须写成 `"9007199254740993"`,并把该列声明为 `object` 且设置 `formats: {"列名":"@"}` +- `table-put` 不支持 dataValidation、hyperlink、richText、附件、单元格图片;这些能力用 `range update` / `write-image` +- 样式能力以 `cellStyles` 基础 cell-level 样式为准;复杂结构样式、合并单元格、行高列宽请另用 `range set-style` / `merge-cells` / `update-dimension` +- 写入后需要用 `table-get` 回读 `columns` / `data` / `dtypes` / `formats` 校验;`table-get` 不返回 `cellStyles`,需要校验 table 写入样式时用 `range read`;需要校验合并或结构元数据时用 `sheet info` + ## values 参数格式说明 `range update` 只接受 `--values` 一个数据参数,为二维 JSON 数组,第一维为行,第二维为列。每个 cell 是以下之一: @@ -366,6 +480,9 @@ dws sheet range update --node --sheet-id --range "A1:C1" \ 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 +# 4. 独立回读实际写入范围 +dws sheet range read --node --sheet-id --range "A1:C4" --format json + # ── 工作流 4: 写入数据并设置样式 ── # 1. 写入数据 @@ -407,12 +524,13 @@ dws sheet append --node --sheet-id \ | 操作 | 从返回中提取 | 用于 | |------|-------------|------| -| `create` | `nodeId` | list / info / new / range update / append / csv-put 的 --node | -| `list` | 工作表的 `sheetId` | range update / append / csv-put 的 --sheet-id | -| `new` | 新工作表的 `sheetId` | range update / append / csv-put 的 --sheet-id | +| `create` | `nodeId` | list / info / new / range update / append / csv-put / table-put 的 --node | +| `list` | 工作表的 `sheetId` | range update / append / csv-put 的 --sheet-id;table-put 的 sheetId | +| `new` | 新工作表的 `sheetId` | range update / append / csv-put 的 --sheet-id;table-put 的 sheetId | | `info` | `rowCount` / `nonEmptyRange.range` / `nonEmptyRange.lastRow` / `nonEmptyRange.lastColumn` / `mergedRanges` | 确定数据范围、追加写入起始行、识别合并单元格结构 | | `append` | `a1Notation` 追加数据所在范围 | 确认追加位置 | | `csv-put` | `a1Notation` 实际写入的单元格范围 | 确认写入位置和范围 | +| `table-put` | `sheets[].sheetId` / `sheets[].range` / `sheets[].dataRows` | 确认实际写入的 sheet、范围和行数;再用 `table-get` 回读类型与格式 | ## 注意事项 @@ -428,11 +546,12 @@ dws sheet append --node --sheet-id \ - **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 read --value-render-option formula` 确认公式文本,再用 `formula-verify` 聚合扫描错误;若返回 `partial` / `hasMore=true` 必须继续校验,关键业务数值还要用 `raw_value` 抽样对账 +- ★ **结构化 table 写入用 `table-put`**:需要列名、dtype、number format、跨 sheet specs 或 `table-get` 回写形态时使用 `table-put`;单次写入单元格数硬限 30000,包含表头 +- ★ **大整数/长数字 ID 必须按文本写(强制)**:超过 `9007199254740991` 的整数、订单号、手机号、SKU 等需要逐位精确的值,不要用 JSON number,也不要声明 `int64` / `uint64`。用字符串值 + `object` dtype + 文本格式 `@`;写后用 `table-get` 或 `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`;需要公式、超链接、下拉列表或跳过部分单元格时,使用 `range update` +- ★ **`append` / `csv-put` / `table-put` 不支持 `{}` skip、dataValidation、富文本、超链接**:这些能力仅限 `range update`。`append` 和 `csv-put` 只接受原始值;`table-put` 接受 table 矩阵、dtypes、formats 和基础 `cellStyles`。需要写入公式、超链接、下拉列表或跳过部分单元格时,必须使用 `range update` diff --git a/skills/multi/dingtalk-sheet/references/url-patterns.md b/skills/multi/dingtalk-misc/references/url-patterns.md similarity index 78% rename from skills/multi/dingtalk-sheet/references/url-patterns.md rename to skills/multi/dingtalk-misc/references/url-patterns.md index 9498faa0..6a2b702e 100644 --- a/skills/multi/dingtalk-sheet/references/url-patterns.md +++ b/skills/multi/dingtalk-misc/references/url-patterns.md @@ -1,8 +1,21 @@ # URL 格式与处理规范 -## alidocs URL 分流决策(必须首先执行) +## 路由第 0 步:意图直达(优先级高于 URL 探测) -收到 `alidocs.dingtalk.com` URL 时,**必须按以下顺序判断,禁止跳过**: +用户已经明确表达某产品的内容意图时,直接进入对应产品场域,不要先做 URL 类型 +探测。尤其: + +- 明确提到 Markdown / `.md` 文件的读取或修改,按普通文件走 `drive` 场域: + 先 `dws drive download` 下载到本地处理,再用 `dws drive upload` 回传。 +- 明确“读这篇文档 / 编辑文档正文”进入 `doc`;明确“看这个在线表格数据”进入 + `sheet`。 + +仅当用户只粘贴 URL、没有明确产品意图,或意图与链接类型可能冲突时,才执行下方 +类型探测。 + +## alidocs URL 分流决策(意图不明确时执行) + +收到 `alidocs.dingtalk.com` URL 且无法从指令判断产品时,必须按以下顺序判断: 1. URL 路径含 `/i/p/` → **分享短链**,禁止调用 `dws doc` 任何子命令 → 按下方 [分享短链处理](#分享短链处理) 执行 2. URL 路径含 `/i/nodes/` → **节点链接**,需探测类型 → 按下方 [alidocs URL 类型探测流程](#alidocs-url-类型探测流程) 执行 @@ -80,20 +93,23 @@ dws doc read --node "https://alidocs.dingtalk.com/i/p/Y7kmbokZp3pgGLq2/docs/AY39 ### 探测步骤 ``` -Step 1 → dws doc info --node "" --format json -Step 2 → 从返回中提取 contentType、extension、nodeType 字段 +Step 1 → dws drive info --node "" --format json +Step 2 → 从返回中提取 extension、nodeType 字段 Step 3 → 按下方路由规则映射到对应产品 ``` +> 路由依据是 `extension`,不是 `contentType`。`drive info` 检测到 +> `adoc` / `axls` / `able` 时会自动补充在线文档信息。 + ### 路由映射表 | 条件 | 路由到产品 | 后续操作 | |------|-----------|---------| -| `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` 下载,返回文件下载链接 | +| `extension=adoc` | `doc` | 加载 `dingtalk-doc` 操作内容 | +| `extension=axls` | `sheet` | 加载本包 `references/sheet.md` 操作(仅 `axls` 在线电子表格) | +| `extension=able` | `aitable` | 将 nodeId 作为 baseId,加载 `dingtalk-aitable` 操作 | +| `extension=xlsx` / `xls` / `xlsm` / `csv` | `drive` | 必须用 `dws drive download` 下载到本地处理,禁止走 `sheet` | +| `nodeType=file`(非在线文档扩展名,含 `md`) | `drive` | 下载用 `dws drive download --node --output --format json`;上传/覆盖用 `dws drive upload` | | `nodeType=folder` | `drive` / `wiki` | 调用 `dws drive list --workspace ` 或 `dws wiki node list` 列出子节点 | | 以上均不匹配 | — | 告知用户当前暂不支持该类型 | @@ -106,16 +122,16 @@ Step 3 → 按下方路由规则映射到对应产品 ```bash # 用户传入: https://alidocs.dingtalk.com/i/nodes/abc123 -dws doc info --node "https://alidocs.dingtalk.com/i/nodes/abc123" --format json +dws drive info --node "https://alidocs.dingtalk.com/i/nodes/abc123" --format json -# 返回 contentType=ALIDOC, extension=axls → 在线电子表格,路由到 sheet +# 返回 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" +# 返回 extension=xlsx/xls/csv → 本地表格文件,必须下载处理(禁止走 sheet) +dws drive download --node "https://alidocs.dingtalk.com/i/nodes/xlsx456" --output --format json -# 返回 contentType≠ALIDOC, nodeType=file → 普通文件,下载 -dws drive download --node "https://alidocs.dingtalk.com/i/nodes/def456" +# 返回 nodeType=file → 普通文件,下载 +dws drive download --node "https://alidocs.dingtalk.com/i/nodes/def456" --output --format json # 返回 nodeType=folder → 文件夹,列出子节点 dws drive list --workspace --format json @@ -126,4 +142,3 @@ dws drive list --workspace --format json 当用户指令中已明确指定产品(如"帮我读这个文档"、"看下这个表格的数据"),可结合用户意图**跳过探测**直接路由。仅在以下情况**必须执行探测**: - 用户只粘贴 URL,无其他上下文 - 用户指令与 URL 实际类型可能不一致(如说"文档"但实际是表格) -- 用户直接粘贴的是原始 `alidocs` URL,且没有上游命令返回来确认类型 diff --git a/skills/multi/dingtalk-misc/scripts/aiapp_create_and_poll.py b/skills/multi/dingtalk-misc/scripts/aiapp_create_and_poll.py new file mode 100644 index 00000000..663184a6 --- /dev/null +++ b/skills/multi/dingtalk-misc/scripts/aiapp_create_and_poll.py @@ -0,0 +1,138 @@ +#!/usr/bin/env python3 +""" +创建 AI 应用并自动轮询等待完成 + +用法: + python aiapp_create_and_poll.py \ + --prompt "创建一个仓库管理应用" + + python aiapp_create_and_poll.py \ + --prompt "生成客户管理 CRM" \ + --skills skill1,skill2 \ + --interval 30 \ + --timeout 600 + + python aiapp_create_and_poll.py --dry-run --prompt "test" +""" + +import sys +import json +import subprocess +import argparse +import time +from typing import List, Any, Optional + + +def run_dws( + args: List[str], dry_run: bool = False, +) -> Optional[Any]: + cmd = ['dws'] + args + if dry_run: + print(f"[dry-run] {' '.join(cmd)}") + return {'dry_run': True} + try: + result = subprocess.run( + cmd, capture_output=True, text=True, timeout=120 + ) + if result.returncode != 0: + print(f" ✗ 错误:{result.stderr.strip()}") + return None + return json.loads(result.stdout) + except (subprocess.TimeoutExpired, json.JSONDecodeError, + FileNotFoundError) as e: + print(f" ✗ 错误:{e}") + return None + + +def main(): + parser = argparse.ArgumentParser( + description='创建 AI 应用并轮询等待完成' + ) + parser.add_argument( + '--prompt', required=True, help='应用描述' + ) + parser.add_argument( + '--skills', default='', help='技能 ID 列表' + ) + parser.add_argument( + '--interval', type=int, default=30, + help='轮询间隔秒 (默认 30)', + ) + parser.add_argument( + '--timeout', type=int, default=600, + help='最大等待秒 (默认 600)', + ) + parser.add_argument('--dry-run', action='store_true') + args = parser.parse_args() + + print(f'🚀 创建 AI 应用...') + print(f' Prompt: {args.prompt}') + cmd_args = [ + 'aiapp', 'create', + '--prompt', args.prompt, + '--format', 'json', + ] + if args.skills: + cmd_args.extend(['--skills', args.skills]) + + create_data = run_dws(cmd_args, dry_run=args.dry_run) + if args.dry_run: + run_dws([ + 'aiapp', 'query', + '--task-id', '', + '--format', 'json', + ], dry_run=True) + return + + if not create_data: + sys.exit(1) + + task_id = create_data.get('taskId') or create_data.get('id', '') + thread_id = create_data.get('threadId', '') + print(f" ✓ 任务已创建") + print(f" taskId: {task_id}") + print(f" threadId: {thread_id}") + + print(f'\n⏳ 轮询等待 (间隔 {args.interval}s, ' + f'超时 {args.timeout}s)...') + elapsed = 0 + while elapsed < args.timeout: + time.sleep(args.interval) + elapsed += args.interval + + query_data = run_dws([ + 'aiapp', 'query', + '--task-id', task_id, + '--format', 'json', + ]) + if not query_data: + print(f" [{elapsed}s] ⚠ 查询失败,继续等待...") + continue + + status = (query_data.get('status') + or query_data.get('state', 'unknown')) + progress = query_data.get('progress', {}) + step = '' + if isinstance(progress, dict): + step = progress.get('currentStep', '') + + if status == 'succeeded': + print(f" [{elapsed}s] ✅ 应用创建成功!") + if thread_id: + print(f" threadId: {thread_id}") + return + elif status == 'failed': + print(f" [{elapsed}s] ❌ 创建失败") + sys.exit(1) + else: + info = f" [{elapsed}s] ⏳ {status}" + if step: + info += f" ({step})" + print(info) + + print(f"\n⏰ 超时 ({args.timeout}s),任务可能仍在运行") + print(f" 可手动查询: dws aiapp query --task-id {task_id}") + + +if __name__ == '__main__': + main() diff --git a/skills/multi/dingtalk-attendance/scripts/attendance_my_record.py b/skills/multi/dingtalk-misc/scripts/attendance_my_record.py similarity index 81% rename from skills/multi/dingtalk-attendance/scripts/attendance_my_record.py rename to skills/multi/dingtalk-misc/scripts/attendance_my_record.py index 90b12912..89898507 100644 --- a/skills/multi/dingtalk-attendance/scripts/attendance_my_record.py +++ b/skills/multi/dingtalk-misc/scripts/attendance_my_record.py @@ -48,22 +48,7 @@ def get_my_user_id(dry_run: bool = False) -> Optional[str]: return '' if not data or not isinstance(data, dict): return None - 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 - employee = item.get('orgEmployeeModel') - if isinstance(employee, dict) and employee.get('userId'): - return employee['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-misc/scripts/attendance_report_checkin.py similarity index 100% rename from skills/multi/dingtalk-attendance/scripts/attendance_report_checkin.py rename to skills/multi/dingtalk-misc/scripts/attendance_report_checkin.py diff --git a/skills/multi/dingtalk-attendance/scripts/attendance_report_common.py b/skills/multi/dingtalk-misc/scripts/attendance_report_common.py similarity index 98% rename from skills/multi/dingtalk-attendance/scripts/attendance_report_common.py rename to skills/multi/dingtalk-misc/scripts/attendance_report_common.py index aea12ee0..da292ad4 100644 --- a/skills/multi/dingtalk-attendance/scripts/attendance_report_common.py +++ b/skills/multi/dingtalk-misc/scripts/attendance_report_common.py @@ -192,13 +192,6 @@ def extract_records(payload: Any) -> list[dict]: for key in ("data", "records", "list", "items", "result"): if key in payload and isinstance(payload[key], list): return [item for item in payload[key] if isinstance(item, dict)] - # CLI/接口常见双层包装:{"result": {"deptUserList": [...]}}。 - # 先递归进入业务容器,再由下方的唯一 list 兜底识别具体记录键。 - for key in ("result", "data"): - if key in payload and isinstance(payload[key], dict): - nested = extract_records(payload[key]) - if nested: - return nested # 兜底:dict 中只有一个 list 值 list_values = [v for v in payload.values() if isinstance(v, list)] if len(list_values) == 1: @@ -573,16 +566,13 @@ def resolve_users_from_input(raw_ids: list[str]) -> list[str]: try: result = run_dws([ "contact", "dept", "list-members", - "--depts", ",".join(raw_ids), + "--ids", ",".join(raw_ids), ]) members = extract_records(result) if members: # 成功获取到成员 → 输入是部门ID for member in members: - info = member.get("userInfo", member) - if not isinstance(info, dict): - continue - uid = _first_nonempty(info, ("userId", "userid", "id")) + uid = _first_nonempty(member, ("userId", "userid", "id")) if uid and str(uid) not in seen: resolved.append(str(uid)) seen.add(str(uid)) @@ -599,15 +589,12 @@ def resolve_users_from_input(raw_ids: list[str]) -> list[str]: try: result = run_dws([ "contact", "dept", "list-members", - "--depts", raw_id, + "--ids", raw_id, ]) members = extract_records(result) if members: for member in members: - info = member.get("userInfo", member) - if not isinstance(info, dict): - continue - uid = _first_nonempty(info, ("userId", "userid", "id")) + uid = _first_nonempty(member, ("userId", "userid", "id")) if uid and str(uid) not in seen: resolved.append(str(uid)) seen.add(str(uid)) diff --git a/skills/multi/dingtalk-attendance/scripts/attendance_report_daily.py b/skills/multi/dingtalk-misc/scripts/attendance_report_daily.py similarity index 100% rename from skills/multi/dingtalk-attendance/scripts/attendance_report_daily.py rename to skills/multi/dingtalk-misc/scripts/attendance_report_daily.py diff --git a/skills/multi/dingtalk-attendance/scripts/attendance_report_detail.py b/skills/multi/dingtalk-misc/scripts/attendance_report_detail.py similarity index 99% rename from skills/multi/dingtalk-attendance/scripts/attendance_report_detail.py rename to skills/multi/dingtalk-misc/scripts/attendance_report_detail.py index efdf07d9..e6a94cfb 100644 --- a/skills/multi/dingtalk-attendance/scripts/attendance_report_detail.py +++ b/skills/multi/dingtalk-misc/scripts/attendance_report_detail.py @@ -161,8 +161,8 @@ def query_check_results( payload = cmn.run_dws([ "attendance", "check", "result", "--users", ",".join(user_batch), - "--start", from_date, - "--end", to_date, + "--from", from_date, + "--to", to_date, "--offset", str(offset), "--limit", str(CHECK_RESULT_PAGE_SIZE), ]) @@ -218,8 +218,8 @@ def query_check_records( payload = cmn.run_dws([ "attendance", "check", "record", "--users", ",".join(user_batch), - "--start", from_date, - "--end", to_date, + "--from", from_date, + "--to", to_date, ]) stats.total_dws_calls += 1 except cmn.DwsCallError as exc: @@ -806,4 +806,4 @@ def main() -> int: if __name__ == "__main__": - sys.exit(main()) + sys.exit(main()) \ No newline at end of file diff --git a/skills/multi/dingtalk-attendance/scripts/attendance_report_monthly.py b/skills/multi/dingtalk-misc/scripts/attendance_report_monthly.py similarity index 100% rename from skills/multi/dingtalk-attendance/scripts/attendance_report_monthly.py rename to skills/multi/dingtalk-misc/scripts/attendance_report_monthly.py diff --git a/skills/multi/dingtalk-attendance/scripts/attendance_report_record.py b/skills/multi/dingtalk-misc/scripts/attendance_report_record.py similarity index 100% rename from skills/multi/dingtalk-attendance/scripts/attendance_report_record.py rename to skills/multi/dingtalk-misc/scripts/attendance_report_record.py diff --git a/skills/multi/dingtalk-attendance/scripts/attendance_schedule_export.py b/skills/multi/dingtalk-misc/scripts/attendance_schedule_export.py similarity index 99% rename from skills/multi/dingtalk-attendance/scripts/attendance_schedule_export.py rename to skills/multi/dingtalk-misc/scripts/attendance_schedule_export.py index 098bc92b..db2cac02 100644 --- a/skills/multi/dingtalk-attendance/scripts/attendance_schedule_export.py +++ b/skills/multi/dingtalk-misc/scripts/attendance_schedule_export.py @@ -123,7 +123,7 @@ def build_class_name_map(records: list[dict]) -> dict[int, str]: if missing_ids: log(f"🔍 {len(missing_ids)} 个班次缺名称,从 class search 补全 ...") try: - result = run_dws(["attendance", "class", "search", "--limit", "200"]) + result = run_dws(["attendance", "class", "search", "--page-size", "200"]) for cls in (extract_records(result) if result else []): cid_raw = cls.get("id") or cls.get("classId") cname = cls.get("name") or cls.get("className") diff --git a/skills/multi/dingtalk-attendance/scripts/attendance_schedule_import.py b/skills/multi/dingtalk-misc/scripts/attendance_schedule_import.py similarity index 99% rename from skills/multi/dingtalk-attendance/scripts/attendance_schedule_import.py rename to skills/multi/dingtalk-misc/scripts/attendance_schedule_import.py index 4eafbdc9..2ca45322 100644 --- a/skills/multi/dingtalk-attendance/scripts/attendance_schedule_import.py +++ b/skills/multi/dingtalk-misc/scripts/attendance_schedule_import.py @@ -214,8 +214,8 @@ def fetch_all_classes() -> dict[int, str]: try: result = run_dws([ "attendance", "class", "search", - "--page", str(page_index), - "--limit", str(page_size), + "--page-index", str(page_index), + "--page-size", str(page_size), ]) except DwsCallError as exc: error(f"查询班次列表失败: {exc}") @@ -367,7 +367,7 @@ def execute_schedule_import(group_id: int, schedules: list[dict]) -> None: "attendance", "schedule", "import", "--groupId", str(group_id), "--scheduleVOS", schedules_json, - "--user-say-yes", + "--yes", ]) except DwsCallError as exc: error(f"排班导入失败: {exc}") diff --git a/skills/multi/dingtalk-attendance/scripts/attendance_team_shift.py b/skills/multi/dingtalk-misc/scripts/attendance_team_shift.py similarity index 100% rename from skills/multi/dingtalk-attendance/scripts/attendance_team_shift.py rename to skills/multi/dingtalk-misc/scripts/attendance_team_shift.py diff --git a/skills/multi/dingtalk-attendance/scripts/attendance_vacation_balance.py b/skills/multi/dingtalk-misc/scripts/attendance_vacation_balance.py similarity index 100% rename from skills/multi/dingtalk-attendance/scripts/attendance_vacation_balance.py rename to skills/multi/dingtalk-misc/scripts/attendance_vacation_balance.py diff --git a/skills/multi/dingtalk-misc/scripts/finance_daily_cashflow.py b/skills/multi/dingtalk-misc/scripts/finance_daily_cashflow.py new file mode 100644 index 00000000..1628982b --- /dev/null +++ b/skills/multi/dingtalk-misc/scripts/finance_daily_cashflow.py @@ -0,0 +1,70 @@ +#!/usr/bin/env python3 +""" +查看指定日期现金日报 + +用法: + python finance_daily_cashflow.py # 今天 + python finance_daily_cashflow.py --date 2026-03-10 + python finance_daily_cashflow.py --dry-run +""" + +import sys +import json +import subprocess +import argparse +from datetime import datetime +from typing import List, Any, Optional + + +def run_dws( + args: List[str], dry_run: bool = False, +) -> Optional[Any]: + cmd = ['dws'] + args + if dry_run: + print(f"[dry-run] {' '.join(cmd)}") + return None + try: + result = subprocess.run( + cmd, capture_output=True, text=True, timeout=60 + ) + if result.returncode != 0: + print(f"错误:{result.stderr.strip()}", file=sys.stderr) + return None + return json.loads(result.stdout) + except (subprocess.TimeoutExpired, json.JSONDecodeError, + FileNotFoundError) as e: + print(f"错误:{e}", file=sys.stderr) + return None + + +def main(): + parser = argparse.ArgumentParser( + description='查看现金日报' + ) + parser.add_argument( + '--date', default='', help='日期 YYYY-MM-DD (默认今天)' + ) + parser.add_argument('--dry-run', action='store_true') + args = parser.parse_args() + + date_str = args.date or datetime.now().strftime('%Y-%m-%d') + + print(f'💰 现金日报 ({date_str})\n') + data = run_dws([ + 'finance', 'journal', 'daily', + '--date', date_str, + '--format', 'json', + ], dry_run=args.dry_run) + + if args.dry_run: + return + if not data: + print('未查到现金日报') + return + + print('=' * 50) + print(json.dumps(data, ensure_ascii=False, indent=2)) + + +if __name__ == '__main__': + main() diff --git a/skills/multi/dingtalk-misc/scripts/finance_expense_flow.py b/skills/multi/dingtalk-misc/scripts/finance_expense_flow.py new file mode 100644 index 00000000..eff505f3 --- /dev/null +++ b/skills/multi/dingtalk-misc/scripts/finance_expense_flow.py @@ -0,0 +1,138 @@ +#!/usr/bin/env python3 +""" +完整报销流程:搜索供应商 → 搜索类别 → 创建付款单 + +用法: + python finance_expense_flow.py \ + --amount 5000 \ + --supplier "华为" \ + --category "差旅" \ + --category-type expense + + python finance_expense_flow.py --dry-run --amount 1000 +""" + +import sys +import json +import subprocess +import argparse +from typing import List, Any, Optional + + +def run_dws( + args: List[str], dry_run: bool = False, +) -> Optional[Any]: + cmd = ['dws'] + args + if dry_run: + print(f"[dry-run] {' '.join(cmd)}") + return {'dry_run': True} + try: + result = subprocess.run( + cmd, capture_output=True, text=True, timeout=60 + ) + if result.returncode != 0: + print(f" ✗ 错误:{result.stderr.strip()}") + return None + return json.loads(result.stdout) + except (subprocess.TimeoutExpired, json.JSONDecodeError, + FileNotFoundError) as e: + print(f" ✗ 错误:{e}") + return None + + +def main(): + parser = argparse.ArgumentParser( + description='完整报销流程' + ) + parser.add_argument( + '--amount', required=True, help='报销金额' + ) + parser.add_argument( + '--supplier', default='', help='供应商名称关键词' + ) + parser.add_argument( + '--category', default='', help='费用类别关键词' + ) + parser.add_argument( + '--category-type', default='expense', + choices=['income', 'expense'], + ) + parser.add_argument('--tax', default='', help='税额') + parser.add_argument('--dry-run', action='store_true') + args = parser.parse_args() + + supplier_code = '' + category_code = '' + + if args.supplier: + print(f'🔍 搜索供应商: {args.supplier}') + data = run_dws([ + 'finance', 'supplier', 'search', + '--query', args.supplier, + '--format', 'json', + ], dry_run=args.dry_run) + if not args.dry_run and data: + if isinstance(data, list): + items = data + elif isinstance(data, dict): + inner = data.get('result', data) + items = inner if isinstance(inner, list) else [] + else: + items = [] + if items: + supplier_code = (items[0].get('code') + or items[0].get('supplierCode', '')) + name = items[0].get('name', '') + print(f" ✓ 找到: {name} ({supplier_code})") + else: + print(f" ⚠ 未找到供应商: {args.supplier}") + + if args.category: + print(f'🔍 搜索费用类别: {args.category}') + data = run_dws([ + 'finance', 'category', 'search', + '--type', args.category_type, + '--query', args.category, + '--format', 'json', + ], dry_run=args.dry_run) + if not args.dry_run and data: + if isinstance(data, list): + items = data + elif isinstance(data, dict): + inner = data.get('result', data) + items = inner if isinstance(inner, list) else [] + else: + items = [] + if items: + category_code = (items[0].get('code') + or items[0].get('categoryCode', '')) + name = items[0].get('name', '') + print(f" ✓ 找到: {name} ({category_code})") + else: + print(f" ⚠ 未找到类别: {args.category}") + + print(f'\n💰 创建付款单 (金额: {args.amount})') + cmd_args = [ + 'finance', 'receipt', 'create', + '--amount', args.amount, + '--format', 'json', + ] + if supplier_code: + cmd_args.extend(['--supplier-code', supplier_code]) + if category_code: + cmd_args.extend(['--category-code', category_code]) + if args.tax: + cmd_args.extend(['--tax', args.tax]) + + result = run_dws(cmd_args, dry_run=args.dry_run) + if result: + print(f" ✓ 付款单已创建") + else: + print(f" ✗ 创建失败") + sys.exit(1) + + print('\n✅ 报销流程完成!') + + +if __name__ == '__main__': + main() diff --git a/skills/multi/dingtalk-oa/scripts/oa_batch_approve.py b/skills/multi/dingtalk-misc/scripts/oa_batch_approve.py similarity index 100% rename from skills/multi/dingtalk-oa/scripts/oa_batch_approve.py rename to skills/multi/dingtalk-misc/scripts/oa_batch_approve.py diff --git a/skills/multi/dingtalk-oa/scripts/oa_pending_review.py b/skills/multi/dingtalk-misc/scripts/oa_pending_review.py similarity index 100% rename from skills/multi/dingtalk-oa/scripts/oa_pending_review.py rename to skills/multi/dingtalk-misc/scripts/oa_pending_review.py diff --git a/skills/multi/dingtalk-misc/scripts/report_received_today.py b/skills/multi/dingtalk-misc/scripts/report_received_today.py new file mode 100644 index 00000000..e7e66211 --- /dev/null +++ b/skills/multi/dingtalk-misc/scripts/report_received_today.py @@ -0,0 +1,148 @@ +#!/usr/bin/env python3 +""" +查看今天收到的日志列表及详情 + +用法: + python report_received_today.py + python report_received_today.py --days 3 # 最近 3 天 + python report_received_today.py --dry-run +""" + +import sys +import json +import subprocess +import argparse +from datetime import datetime, timedelta +from typing import List, Any, Optional + + +def run_dws( + args: List[str], dry_run: bool = False, +) -> Optional[Any]: + cmd = ['dws'] + args + if dry_run: + print(f"[dry-run] {' '.join(cmd)}") + return None + try: + result = subprocess.run( + cmd, capture_output=True, text=True, timeout=60 + ) + if result.returncode != 0: + print(f"错误:{result.stderr.strip()}", file=sys.stderr) + return None + return json.loads(result.stdout) + except (subprocess.TimeoutExpired, json.JSONDecodeError, + FileNotFoundError) as e: + print(f"错误:{e}", file=sys.stderr) + return None + + +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.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 = 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') + + 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) + + if args.dry_run: + return + if not data: + print('未查到日志') + return + + 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 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 📝 {template or '日志'} - {creator}") + print(f" 时间: {create_time}") + print(f" ID: {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__': + main() diff --git a/skills/multi/dingtalk-misc/scripts/yida_custom_page_update.py b/skills/multi/dingtalk-misc/scripts/yida_custom_page_update.py new file mode 100644 index 00000000..1113a080 --- /dev/null +++ b/skills/multi/dingtalk-misc/scripts/yida_custom_page_update.py @@ -0,0 +1,401 @@ +#!/usr/bin/env python3 +""" +宜搭自定义页面 schema 生成/修改(编排:get-schema → 编译 + 构建 → update-schema) + +━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +【调用前必读】references/yida-custom-page-codegen.md + (JSX 入口签名 / Hooks 限制 / 行内样式 / 跨表单联动 5 种模式 / SEARCH/REPLACE + 增量改写 / 常见坑速查表)。本脚本 --help 仅给出基本用法,**不要**只看 + --help 就直接拼 JSX,几乎必踩坑。 +━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +用法: + python yida_custom_page_update.py --app APP_X --form FORM-XXX --code-file page.jsx --yes + python yida_custom_page_update.py --app APP_X --form FORM-XXX --code 'import ...' --yes + python yida_custom_page_update.py --app APP_X --form FORM-XXX --show-current + +━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +--show-current 模式: + 只拉取现有 schema 并输出当前代码,不做修改。 + +━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +写入模式: + 全量替换代码。脚本调用纯 Python 编译管线(JSX→createElement 转换 + + Hooks 兼容层 _customState/didMount),构建标准 Jsx 组件 schema,并保留 + page_id 和已有 dataSource。**零第三方依赖**(仅需 Python 3.7+ 标准库, + 无需 pip install、无需 Node.js)。空页面和已有代码的页面均可使用。 + + 跨表单联动场景:JSX 内可通过 Yida.api.form.* 直接读写同应用内任意表单, + 搭配 `yida_form_inspector.py --action fields-snippet` 取目标表的字段常量片段。 + 详见 references/yida-custom-page-codegen.md §9。 + +━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +""" +from __future__ import annotations + +import argparse +import json +import os +import subprocess +import sys +from pathlib import Path +from typing import Any + +_SCRIPT_DIR = Path(__file__).resolve().parent +if str(_SCRIPT_DIR) not in sys.path: + sys.path.insert(0, str(_SCRIPT_DIR)) + +from yida_page_compiler import compile_jsx_to_schema # noqa: E402 +from yida_page_schema import extract_source_code # noqa: E402 +from yida_jsx_pipeline import field_check, lint_check # noqa: E402 + +MAX_CODE_FILE_SIZE = 1 * 1024 * 1024 +MAX_INLINE_CODE = 200 * 1024 + + +def _gather_allowed_roots() -> list[Path]: + """收集所有允许的路径根目录。任一命中即放行,详见 _resolve_safe_path。 + + 优先级(靠前的优先,仅影响报错提示顺序): + 1. OPENYIDA_ALLOWED_ROOTS:显式多根,以 os.pathsep / ':' / ',' 分隔。 + 2. OPENCLAW_WORKSPACE:老环境变量,向后兼容。 + 3. 当前工作目录 cwd:兼容原行为。 + 4. 临时目录 tempdir:供脚本中转使用。 + """ + roots: list[Path] = [] + extra = os.environ.get("OPENYIDA_ALLOWED_ROOTS", "") + if extra: + seps = [os.pathsep, ":", ","] + parts: list[str] = [extra] + for sep in seps: + parts = [seg for chunk in parts for seg in chunk.split(sep)] + for part in parts: + part = part.strip() + if part: + roots.append(Path(part).expanduser().resolve()) + legacy = os.environ.get("OPENCLAW_WORKSPACE") + if legacy: + roots.append(Path(legacy).expanduser().resolve()) + roots.append(Path.cwd().resolve()) + import tempfile as _tempfile + roots.append(Path(_tempfile.gettempdir()).resolve()) + roots.append(Path("/tmp").resolve()) + roots.append(Path("/private/tmp").resolve()) + # 去重保序 + seen: set[str] = set() + uniq: list[Path] = [] + for r in roots: + s = str(r) + if s not in seen: + seen.add(s) + uniq.append(r) + return uniq + + +def _resolve_safe_path(path_str: str) -> Path: + target = Path(path_str).expanduser() + target = target.resolve() if target.is_absolute() else (Path.cwd() / target).resolve() + roots = _gather_allowed_roots() + for root in roots: + try: + target.relative_to(root) + return target + except ValueError: + continue + listing = "\n - ".join(str(r) for r in roots) + raise ValueError( + f"路径超出允许范围:{path_str}\n" + f"已尝试的允许根目录:\n - {listing}\n" + f"提示:设置 OPENYIDA_ALLOWED_ROOTS(允许多根,冒号/逗号分隔)或 OPENCLAW_WORKSPACE 扩展允许范围。" + ) + + +def _run_dws(args: list[str], dry_run: bool = False) -> Any | None: + cmd = ["dws"] + args + if dry_run: + print(f" [dry-run] {' '.join(cmd)}") + return {"dry_run": True} + try: + result = subprocess.run(cmd, capture_output=True, text=True, timeout=120) + except FileNotFoundError: + print(" [FAIL] 找不到 'dws' 命令", file=sys.stderr) + return None + except subprocess.TimeoutExpired: + print(" [FAIL] dws 超时", file=sys.stderr) + return None + if result.returncode != 0: + err = result.stderr.strip() or result.stdout.strip() + print(f" [FAIL] dws 失败 (exit {result.returncode}): {err}", file=sys.stderr) + return None + try: + return json.loads(result.stdout) + except json.JSONDecodeError as e: + print(f" [FAIL] 非 JSON: {e}\n 输出: {result.stdout[:300]}", file=sys.stderr) + return None + + +def _unwrap_content(data: Any) -> Any: + """兼容 dws JSON 输出的常见包裹层。""" + current = data + for _ in range(4): + if not isinstance(current, dict): + return current + if "content" in current: + current = current["content"] + continue + if "data" in current and isinstance(current["data"], dict): + current = current["data"] + continue + return current + return current + + +def _extract_form_type(info: Any) -> str: + """从 get-info 的不同返回形态中提取 formType/type。""" + candidates: list[Any] = [] + current = info + for _ in range(4): + if not isinstance(current, dict): + break + candidates.append(current) + next_obj = None + for key in ("content", "data", "result"): + value = current.get(key) + if isinstance(value, dict): + next_obj = value + break + if next_obj is None: + break + current = next_obj + + for item in candidates: + value = item.get("formType") or item.get("type") or item.get("pageType") + if isinstance(value, str) and value.strip(): + return value.strip().lower() + return "" + + +def _check_display_target(app: str, form: str, force: bool = False) -> bool: + """发布前确认目标是自定义展示页,避免覆盖普通表单/流程表单。""" + print("Step 0: 校验发布目标") + info = _run_dws(["yida", "design", "form", "get-info", "--app", app, + "--form", form, "--format", "json"]) + form_type = _extract_form_type(info) + if form_type == "display": + print(" [OK] 目标类型 display") + return True + + if force: + reason = form_type or "unknown" + print(f" [WARN] 目标类型为 {reason},已按 --force 跳过保护") + return True + + if not info: + print(" [FAIL] 无法获取目标页面类型,已拒绝写入", file=sys.stderr) + elif form_type: + print(f" [FAIL] 目标 formType={form_type},不是 display 自定义页面,已拒绝写入", + file=sys.stderr) + else: + print(" [FAIL] get-info 返回中未找到 formType,已拒绝写入", file=sys.stderr) + print(" [HINT] 请确认 --form 是 display 页面;确认无误时可加 --force 显式绕过", + file=sys.stderr) + return False + + +def _load_code(args: argparse.Namespace) -> str: + if args.code_file: + safe = _resolve_safe_path(args.code_file) + if not safe.exists(): + raise ValueError(f"文件不存在: {safe}") + if safe.stat().st_size > MAX_CODE_FILE_SIZE: + raise ValueError(f"文件过大 (限制 {MAX_CODE_FILE_SIZE:,} 字节)") + return safe.read_text(encoding="utf-8") + elif args.code: + if len(args.code.encode("utf-8")) > MAX_INLINE_CODE: + raise ValueError(f"--code 过长 (限制 {MAX_INLINE_CODE:,} 字节)") + return args.code + else: + raise ValueError("必须提供 --code-file 或 --code") + + +def _extract_existing_data_source(schema: dict) -> dict | None: + """从已有 schema 中提取 Page 组件的 dataSource,用于 merge 保留用户自定义数据源。""" + try: + return schema["pages"][0]["componentsTree"][0].get("dataSource") + except (KeyError, IndexError, TypeError): + return None + + +def main() -> int: + ap = argparse.ArgumentParser( + description=( + "宜搭自定义页面 schema 生成/修改 " + "【调用前必读】references/yida-custom-page-codegen.md" + "(JSX 写法 / Hooks 限制 / 跨表单联动 / 常见坑),不要只看 --help 就拼 JSX" + ), + formatter_class=argparse.RawDescriptionHelpFormatter, epilog=__doc__) + ap.add_argument("--app", required=True, help="应用编码 appType") + ap.add_argument("--form", required=True, help="页面 formUuid") + ap.add_argument("--code-file", help="新代码文件路径") + ap.add_argument("--code", help="新代码内联字符串") + ap.add_argument("--show-current", action="store_true", help="只输出当前代码不修改") + ap.add_argument("--yes", action="store_true", help="确认写入") + ap.add_argument("--dry-run", action="store_true", help="只编译不写入") + ap.add_argument("--skip-field-check", action="store_true", + help="跳过字段 ID 对账预检(不推荐)") + ap.add_argument("--skip-lint", action="store_true", + help="跳过 JSX 静态检查(30 条宜搭专属陷阱,不推荐)") + ap.add_argument("--force", action="store_true", + help="跳过发布目标 formType=display 保护(仅确认目标无误时使用)") + args = ap.parse_args() + + if not args.show_current and not args.code_file and not args.code: + print("错误: 必须提供 --code-file / --code 或 --show-current", file=sys.stderr) + return 1 + + if not args.show_current and not args.dry_run: + if not _check_display_target(args.app, args.form, force=args.force): + return 1 + + # Step 1: 拉取现有 schema + print("Step 1: 获取现有 schema") + resp = _run_dws(["yida", "design", "form", "get-schema", "--app", args.app, + "--form", args.form, "--format", "json"], dry_run=args.dry_run) + if args.dry_run and not args.show_current: + try: + new_code = _load_code(args) + except ValueError as e: + print(f"错误: {e}", file=sys.stderr) + return 1 + result = compile_jsx_to_schema(new_code, form_uuid=args.form) + if not result.get("ok"): + errors = result.get("errors", []) + err_msgs = "; ".join(e.get("message", "") for e in errors) + print(f" [FAIL] 编译失败: {err_msgs}", file=sys.stderr) + lint = result.get("lint", {}) + if lint.get("warnings"): + for w in lint["warnings"]: + print(f" [WARN] {w.get('message', w)}", file=sys.stderr) + return 1 + schema_json = result["schema"] + print(json.dumps({"ok": True, "dry_run": True, "formUuid": args.form, + "codeSize": len(new_code), + "schemaSize": len(schema_json)}, ensure_ascii=False, indent=2)) + return 0 + + if not resp: + return 1 + schema = resp + print(" [OK] 拿到 schema") + + # --show-current 模式 + if args.show_current: + try: + current_code = extract_source_code(schema) + except (ValueError, TypeError): + current_code = None + if current_code is None: + print(" [WARN] schema 中没有可提取的自定义页面代码") + print(json.dumps({"ok": False, "error": "not_a_custom_page"}, ensure_ascii=False)) + return 1 + print(json.dumps({"ok": True, "formUuid": args.form, + "codeSize": len(current_code), + "currentCode": current_code}, ensure_ascii=False, indent=2)) + return 0 + + # 获取 page_id + page_id = args.form + pages = schema.get("pages", []) + if pages: + page_id = pages[0].get("id", args.form) or args.form + + # 提取已有 dataSource(用于 merge) + existing_ds = _extract_existing_data_source(schema) + + # Step 2: 加载新代码、编译并构建 schema + try: + new_code = _load_code(args) + except ValueError as e: + print(f"错误: {e}", file=sys.stderr) + return 1 + + if not new_code.strip(): + print("错误: 代码不能为空", file=sys.stderr) + return 1 + + try: + current_code = extract_source_code(schema) + except (ValueError, TypeError): + current_code = None + previous_size = len(current_code) if current_code else 0 + + print(f"Step 2: 编译 + 构建 schema (新代码 {len(new_code):,} 字节)") + + result = compile_jsx_to_schema(new_code, form_uuid=page_id, existing_data_source=existing_ds) + if not result.get("ok"): + errors = result.get("errors", []) + err_msgs = "; ".join(e.get("message", "") for e in errors) + print(f" [FAIL] 编译失败: {err_msgs}", file=sys.stderr) + lint = result.get("lint", {}) + if lint.get("warnings"): + for w in lint["warnings"]: + print(f" [WARN] {w.get('message', w)}", file=sys.stderr) + return 1 + + schema_json = result["schema"] + lint = result.get("lint", {}) + if lint.get("warnings"): + for w in lint["warnings"]: + print(f" [WARN] lint: {w.get('message', w)}") + + print(f" [OK] 编译成功, schema 大小: {len(schema_json):,} 字节") + + # Step 2.5: 字段 ID 对账预检(避免发布后运行时才报 fieldId 不存在) + if not args.skip_field_check: + print("Step 2.5: 字段 ID 对账") + chk = field_check(new_code, args.app) + for w in chk.get("warnings", []): + print(f" [WARN] {w.get('message', w)}") + if not chk.get("ok"): + print(" [FAIL] 字段对账未通过,为避免发布后页面报错,拒绝写入:", file=sys.stderr) + for e in chk.get("errors", []): + print(f" - {e.get('message', e)}", file=sys.stderr) + print(" [HINT] 修复后重试;确认需要忽略可加 --skip-field-check(不推荐)", file=sys.stderr) + return 1 + info = chk.get("info", {}) + if info.get("skipped"): + print(f" [OK] 跳过({info['skipped']})") + else: + print(f" [OK] 已校验 {info.get('referencedFieldCount', 0)} 个字段引用," + f"覆盖 {len(info.get('checkedForms', []))} 张表单") + + # Step 2.7: JSX 静态检查(避免发布后运行时才报错) + if not args.skip_lint: + print("Step 2.7: JSX 静态检查") + lr = lint_check(new_code, filename=args.code_file or "page.jsx") + for w in lr.get("warnings", []): + print(f" [WARN] L{w['line']} [{w['rule']}] {w['message']}") + if not lr.get("ok"): + print(" [FAIL] JSX 静态检查未通过,为避免发布后页面报错,拒绝写入:", file=sys.stderr) + for e in lr.get("errors", []): + print(f" L{e['line']} [{e['rule']}] {e['message']}", file=sys.stderr) + print(" [HINT] 修复后重试;确认需要忽略可加 --skip-lint(不推荐)", file=sys.stderr) + print(" [HINT] 或在 JSX 中加 // dws-lint-disable-line [rule] 关闭单行检查", file=sys.stderr) + return 1 + info = lr.get("info", {}) + print(f" [OK] 检查通过(错误 {info.get('errorCount', 0)} / 警告 {info.get('warningCount', 0)})") + + # Step 3: 写回 + print("Step 3: 写入 schema") + resp = _run_dws(["yida", "design", "form", "update-schema", "--app", args.app, + "--form", args.form, "--form-type", "display", + "--content", schema_json, "--yes", "--format", "json"]) + if not resp: + return 1 + print(" [OK] 写入成功") + print(json.dumps({"ok": True, "formUuid": args.form, "codeSize": len(new_code), + "previousCodeSize": previous_size}, ensure_ascii=False)) + return 0 + + +if __name__ == "__main__": + sys.exit(main()) diff --git a/skills/multi/dingtalk-misc/scripts/yida_form_builder.py b/skills/multi/dingtalk-misc/scripts/yida_form_builder.py new file mode 100644 index 00000000..4641f599 --- /dev/null +++ b/skills/multi/dingtalk-misc/scripts/yida_form_builder.py @@ -0,0 +1,526 @@ +""" +宜搭表单 schema 构造 — 字段组件装配进 Page > FormContainer 骨架。 + +主入口: + build_form_schema(form_title, fields, form_uuid, corp_id, app_type) + apply_changes_to_schema(schema, changes) — 增量操作 +""" +from __future__ import annotations + +import copy +import json +import re +from typing import Any, Optional + +from yida_schema_common import ( + build_components_map, + collect_component_names, + DATA_SOURCE_FIT_COMPILED, + DATA_SOURCE_FIT_SOURCE, + generate_field_id, + i18n, + next_node_id, + normalize_field_type, + UTILS_LEGAO_BUILTIN, + UTILS_YIDA_PLUGIN, +) +from yida_form_fields import build_field_component + + +# --------------------------------------------------------------------------- +# 骨架常量 +# --------------------------------------------------------------------------- + +_FORM_ACTIONS = { + "module": { + "source": ( + '/**\n* 尊敬的用户,你好:页面 JS 面板是高阶用法。\n*/\n\n' + 'export function didMount() {\n' + ' console.log(`「页面 JS」:当前页面地址 ${location.href}`);\n' + '}' + ), + "compiled": ( + '"use strict";\n\nexports.__esModule = true;\nexports.didMount = didMount;\n' + 'function didMount() {\n' + ' console.log("\\u300C\\u9875\\u9762 JS\\u300D\\uFF1A\\u5F53\\u524D\\u9875\\u9762\\u5730\\u5740 " + location.href);\n' + '}\n' + ), + }, + "type": "FUNCTION", + "list": [{"id": "didMount", "title": "didMount"}], +} + + +_CONSTRUCTOR_SOURCE = ( + "function constructor() {\n" + "var module = { exports: {} };\n" + "var _this = this;\n" + "this.__initMethods__(module.exports, module);\n" + "Object.keys(module.exports).forEach(function(item) {\n" + " if(typeof module.exports[item] === 'function'){\n" + " _this[item] = module.exports[item];\n" + " }\n" + "});\n\n" + "}" +) + + +# --------------------------------------------------------------------------- +# 主入口 +# --------------------------------------------------------------------------- + + +def build_form_schema( + form_title: str, + fields: list[dict[str, Any]], + form_uuid: str = "", + corp_id: str = "", + app_type: str = "", + *, + label_align: str = "top", +) -> dict[str, Any]: + """ + 构造完整的表单 schema。 + + Args: + form_title: 表单标题 + fields: 字段定义数组(每项含 type + label + ...) + form_uuid: 表单 UUID + corp_id: 企业 ID(流水号需要) + app_type: 应用编码 + label_align: 标签对齐方式 top/left + """ + # 1. 构造所有字段节点 + field_nodes: list[dict[str, Any]] = [] + used_colors: set[str] = set() + for field in fields: + node, used_colors = build_field_component( + field, + used_colors=used_colors, + app_type=app_type, + form_uuid=form_uuid, + corp_id=corp_id, + ) + field_nodes.append(node) + + # 2. 后处理:解析 @label: 引用 + _resolve_field_id_references(field_nodes) + + # 3. 后处理:补全流水号 formula(此时 corp_id/app_type/form_uuid 确定) + _fill_serial_number_formulas(field_nodes, corp_id, app_type, form_uuid) + + # 4. 收集 componentsMap + all_component_names = ["Page", "RootHeader", "RootContent", "RootFooter", "FormContainer"] + all_component_names.extend(collect_component_names(field_nodes)) + components_map = build_components_map(all_component_names) + + # 5. 拼装骨架 + schema: dict[str, Any] = { + "schemaType": "superform", + "schemaVersion": "5.0", + "pages": [ + { + "utils": [UTILS_LEGAO_BUILTIN, UTILS_YIDA_PLUGIN], + "componentsMap": components_map, + "componentsTree": [ + { + "componentName": "Page", + "id": next_node_id(), + "props": { + "templateVersion": "1.0.0", + "pageStyle": {"backgroundColor": "#f2f3f5"}, + "titleName": i18n(form_title), + "titleDesc": i18n(""), + "titleColor": "light", + "titleBg": "https://img.alicdn.com/imgextra/i2/O1CN0143ATPP1wIa9TrVvzN_!!6000000006285-2-tps-3360-400.png_.webp", + "backgroundColorCustom": "#f1f2f3", + "sizePc": "medium", + "labelAlignPc": label_align, + "labelWidthPc": "130px", + "labelWeightPc": "normal", + "contentMargin": "12", + "contentPadding": "20", + "contentBgColor": "white", + "showTitle": True, + "labelAlignMobile": "left", + "labelWidthMobile": "100px", + "labelWeightMobile": "bold", + "contentMarginMobile": "12", + "contentPaddingMobile": "0", + "contentBgColorMobile": "white", + "className": "page_m8o991i5", + }, + "dataSource": { + "offline": [], + "globalConfig": { + "fit": { + "compiled": DATA_SOURCE_FIT_COMPILED, + "source": DATA_SOURCE_FIT_SOURCE, + "type": "js", + "error": {}, + }, + }, + "online": [], + "list": [], + "sync": True, + }, + "methods": { + "__initMethods__": { + "type": "js", + "source": "function (exports, module) { /*set actions code here*/ }", + "compiled": "function (exports, module) { /*set actions code here*/ }", + }, + }, + "lifeCycles": { + "componentDidMount": { + "id": "didMount", + "name": "didMount", + "params": {}, + "type": "actionRef", + }, + "componentWillUnmount": "", + "constructor": { + "type": "js", + "compiled": _CONSTRUCTOR_SOURCE, + "source": _CONSTRUCTOR_SOURCE, + }, + }, + "hidden": False, + "title": "", + "isLocked": False, + "condition": True, + "conditionGroup": "", + "children": [ + { + "componentName": "RootHeader", + "id": next_node_id(), + "props": {}, + "hidden": False, + "title": "", + "isLocked": False, + "condition": True, + "conditionGroup": "", + }, + { + "componentName": "RootContent", + "id": next_node_id(), + "props": {}, + "hidden": False, + "title": "", + "isLocked": False, + "condition": True, + "conditionGroup": "", + "children": [ + { + "componentName": "FormContainer", + "id": next_node_id(), + "props": { + "columns": 1, + "labelAlign": label_align, + "submitText": i18n("提交", "Submit"), + "fieldId": generate_field_id("formContainer"), + "aiFormConfig": { + "systemPrompt": "", + "model": "qwen", + }, + "beforeSubmit": False, + "afterSubmit": False, + }, + "hidden": False, + "title": "", + "isLocked": False, + "condition": True, + "conditionGroup": "", + "children": field_nodes, + }, + ], + }, + { + "componentName": "RootFooter", + "id": next_node_id(), + "props": {}, + "hidden": False, + "title": "", + "isLocked": False, + "condition": True, + "conditionGroup": "", + }, + ], + "css": "body{background-color:#f2f3f5}", + }, + ], + "componentAlias": {"items": []}, + "id": form_uuid or "xxxx", + "connectComponent": [], + }, + ], + "actions": copy.deepcopy(_FORM_ACTIONS), + "config": {"connectComponent": []}, + } + + return schema + + +# --------------------------------------------------------------------------- +# 增量修改 +# --------------------------------------------------------------------------- + + +def apply_changes_to_schema( + schema: dict[str, Any], + changes: list[dict[str, Any]], + *, + corp_id: str = "", + app_type: str = "", + form_uuid: str = "", +) -> dict[str, Any]: + """ + 在已有 schema 上执行增量 changes(add/update/delete)。 + + 返回修改后的 schema(原地修改)。 + """ + form_container = _find_form_container(schema) + if form_container is None: + raise ValueError("schema 中找不到 FormContainer 节点") + + children: list[dict[str, Any]] = form_container.get("children", []) + used_colors = _collect_existing_colors(children) + + for change in changes: + action = change.get("action", "") + if action == "add": + field_def = change.get("field", {}) + node, used_colors = build_field_component( + field_def, + used_colors=used_colors, + app_type=app_type, + form_uuid=form_uuid, + corp_id=corp_id, + ) + after_label = change.get("after") + before_label = change.get("before") + insert_idx = len(children) + + if after_label: + idx = _find_field_index_by_label(children, after_label) + if idx is not None: + insert_idx = idx + 1 + elif before_label: + idx = _find_field_index_by_label(children, before_label) + if idx is not None: + insert_idx = idx + + children.insert(insert_idx, node) + + elif action == "update": + label = change.get("label", "") + table_label = change.get("tableLabel") + patches = change.get("changes", {}) + + target_list = children + if table_label: + table_node = _find_field_by_label(children, table_label) + if table_node and table_node.get("componentName") == "TableField": + target_list = table_node.get("children", []) + + target = _find_field_by_label(target_list, label) + if target: + _apply_field_patches(target, patches, used_colors) + + elif action == "delete": + label = change.get("label", "") + table_label = change.get("tableLabel") + + target_list = children + if table_label: + table_node = _find_field_by_label(children, table_label) + if table_node and table_node.get("componentName") == "TableField": + target_list = table_node.get("children", []) + + idx = _find_field_index_by_label(target_list, label) + if idx is not None: + target_list.pop(idx) + + # 后处理 + all_fields = form_container.get("children", []) + _resolve_field_id_references(all_fields) + _fill_serial_number_formulas(all_fields, corp_id, app_type, form_uuid) + + # 更新 componentsMap + all_names = ["Page", "RootHeader", "RootContent", "RootFooter", "FormContainer"] + all_names.extend(collect_component_names(all_fields)) + schema["pages"][0]["componentsMap"] = build_components_map(all_names) + + return schema + + +# --------------------------------------------------------------------------- +# 后处理 +# --------------------------------------------------------------------------- + + +def _resolve_field_id_references(field_nodes: list[dict[str, Any]]) -> None: + """解析 @label:字段名 引用为真实 fieldId。""" + label_to_field_id: dict[str, str] = {} + + def _collect(nodes: list[dict[str, Any]]) -> None: + for node in nodes: + props = node.get("props", {}) + label_obj = props.get("label", {}) + label_text = label_obj.get("zh_CN", "") if isinstance(label_obj, dict) else str(label_obj) + field_id = props.get("fieldId", "") + if label_text and field_id: + label_to_field_id[label_text] = field_id + for child in node.get("children", []): + _collect([child]) + + _collect(field_nodes) + + def _resolve(nodes: list[dict[str, Any]]) -> None: + for node in nodes: + props = node.get("props", {}) + filling_rules = props.get("dataFillingRules", {}) + if isinstance(filling_rules, dict): + main_rules = filling_rules.get("mainRules", []) + for rule in main_rules: + for key in ("source", "sourceFieldId", "target", "targetFieldId"): + val = rule.get(key, "") + if isinstance(val, str) and val.startswith("@label:"): + name = val[7:] + if name in label_to_field_id: + rule[key] = label_to_field_id[name] + for child in node.get("children", []): + _resolve([child]) + + _resolve(field_nodes) + + +def _fill_serial_number_formulas( + field_nodes: list[dict[str, Any]], + corp_id: str, + app_type: str, + form_uuid: str, +) -> None: + """确保 SerialNumberField 的 formula 包含正确的 corp_id/app_type/form_uuid。""" + if not corp_id or not app_type or not form_uuid: + return + + def _walk(nodes: list[dict[str, Any]]) -> None: + for node in nodes: + if node.get("componentName") == "SerialNumberField": + props = node.get("props", {}) + field_id = props.get("fieldId", "") + serial_rule = props.get("serialNumberRule", []) + if serial_rule and field_id: + rule_json = json.dumps({"type": "custom", "value": serial_rule}).replace('"', '\\"') + props["formula"] = { + "expression": f'SERIALNUMBER("{corp_id}", "{app_type}", "{form_uuid}", "{field_id}", "{rule_json}")' + } + for child in node.get("children", []): + _walk([child]) + + _walk(field_nodes) + + +# --------------------------------------------------------------------------- +# 辅助函数 +# --------------------------------------------------------------------------- + + +def _find_form_container(schema: dict[str, Any]) -> Optional[dict[str, Any]]: + pages = schema.get("pages", []) + if not pages: + return None + tree = pages[0].get("componentsTree", []) + if not tree: + return None + + def _search(node: dict[str, Any]) -> Optional[dict[str, Any]]: + if node.get("componentName") == "FormContainer": + return node + for child in node.get("children", []): + found = _search(child) + if found: + return found + return None + + return _search(tree[0]) + + +def _find_field_by_label(nodes: list[dict[str, Any]], label: str) -> Optional[dict[str, Any]]: + for node in nodes: + props = node.get("props", {}) + label_obj = props.get("label", {}) + label_text = label_obj.get("zh_CN", "") if isinstance(label_obj, dict) else str(label_obj) + if label_text == label: + return node + return None + + +def _find_field_index_by_label(nodes: list[dict[str, Any]], label: str) -> Optional[int]: + for idx, node in enumerate(nodes): + props = node.get("props", {}) + label_obj = props.get("label", {}) + label_text = label_obj.get("zh_CN", "") if isinstance(label_obj, dict) else str(label_obj) + if label_text == label: + return idx + return None + + +def _collect_existing_colors(nodes: list[dict[str, Any]]) -> set[str]: + colors: set[str] = set() + for node in nodes: + props = node.get("props", {}) + for item in props.get("dataSource", []): + c = item.get("color") + if c: + colors.add(c) + for child in node.get("children", []): + colors.update(_collect_existing_colors([child])) + return colors + + +def _apply_field_patches(node: dict[str, Any], patches: dict[str, Any], used_colors: set[str]) -> None: + props = node.get("props", {}) + + if "label" in patches: + props["label"] = i18n(patches["label"]) + if "required" in patches: + validation = props.get("validation", []) + has_req = any(v.get("type") == "required" for v in validation) + if patches["required"] and not has_req: + validation.append({"type": "required"}) + props["validation"] = validation + elif not patches["required"] and has_req: + props["validation"] = [v for v in validation if v.get("type") != "required"] + if "behavior" in patches: + props["behavior"] = patches["behavior"] + if "options" in patches: + from yida_schema_common import build_option_data_source + component_name = node.get("componentName", "") + is_checkbox = component_name in ("CheckboxField", "MultiSelectField") + ds, _ = build_option_data_source(patches["options"], is_checkbox=is_checkbox, used_colors=used_colors) + props["dataSource"] = ds + props["isUseDataSourceColor"] = True + if "placeholder" in patches: + props["placeholder"] = i18n(patches["placeholder"]) + if "suffix" in patches: + props["innerAfter"] = i18n(patches["suffix"]) + if "prefix" in patches: + props["innerBefore"] = i18n(patches["prefix"]) + if "format" in patches: + props["format"] = patches["format"] + if "multiple" in patches or "multi" in patches: + val = patches.get("multiple") or patches.get("multi") + props["multiple"] = val + if val: + props["mode"] = "multiple" + + +def is_empty_skeleton(schema: dict[str, Any]) -> bool: + """判断 schema 是否是空骨架(无字段)。""" + fc = _find_form_container(schema) + if fc is None: + return True + children = fc.get("children", []) + return len(children) == 0 diff --git a/skills/multi/dingtalk-misc/scripts/yida_form_fields.py b/skills/multi/dingtalk-misc/scripts/yida_form_fields.py new file mode 100644 index 00000000..bffa3063 --- /dev/null +++ b/skills/multi/dingtalk-misc/scripts/yida_form_fields.py @@ -0,0 +1,315 @@ +""" +宜搭表单字段构造 — 按 type 分发,每种字段类型产出一个 component dict。 + +支持 19 种字段类型 + Divider。被 yida_form_builder.py 调用。 +""" +from __future__ import annotations + +import json +from typing import Any, Optional + +from yida_schema_common import ( + generate_field_id, + i18n, + next_node_id, + normalize_field_type, + build_option_data_source, + OPTION_FIELD_TYPES, + SUPPORTED_FIELD_TYPES, +) + + +def build_field_component( + field: dict[str, Any], + *, + used_colors: Optional[set[str]] = None, + app_type: str = "", + form_uuid: str = "", + corp_id: str = "", +) -> tuple[dict[str, Any], set[str]]: + """ + 根据字段定义 dict 构造一个组件节点。 + + 返回 (component_node, updated_used_colors)。 + """ + if used_colors is None: + used_colors = set() + + raw_type = field.get("type", "") + component_name = normalize_field_type(raw_type) + label = field.get("label", "") + required = field.get("required", False) + field_id = generate_field_id(component_name) + + node: dict[str, Any] = { + "componentName": component_name, + "id": next_node_id(), + "props": { + "label": i18n(label), + "fieldId": field_id, + "__category__": "form", + "behavior": field.get("behavior", "NORMAL"), + "visibility": ["PC", "MOBILE"], + "submittable": "DEFAULT", + }, + } + + props = node["props"] + + # required + if required: + props["validation"] = [{"type": "required"}] + + # placeholder + if field.get("placeholder"): + props["placeholder"] = i18n(field["placeholder"]) + + # defaultValue + if field.get("defaultValue") is not None: + props["defaultValue"] = field["defaultValue"] + + # --- 按类型分发 --- + if component_name == "TextareaField": + _apply_textarea_props(props, field) + elif component_name == "NumberField": + _apply_number_props(props, field) + elif component_name == "RateField": + _apply_rate_props(props, field) + elif component_name in ("DateField", "CascadeDateField"): + _apply_date_props(props, field, component_name) + elif component_name in OPTION_FIELD_TYPES: + used_colors = _apply_option_props(props, field, component_name, used_colors) + elif component_name in ("EmployeeField", "DepartmentSelectField"): + _apply_people_props(props, field) + elif component_name == "CountrySelectField": + _apply_people_props(props, field) + elif component_name == "AddressField": + _apply_address_props(props, field) + elif component_name == "AttachmentField": + _apply_attachment_props(props, field) + elif component_name == "ImageField": + _apply_image_props(props, field) + elif component_name == "SerialNumberField": + _apply_serial_number_props(props, field, app_type, form_uuid, field_id, corp_id) + elif component_name == "TableField": + used_colors = _apply_table_props(node, field, used_colors, app_type, form_uuid, corp_id) + elif component_name == "AssociationFormField": + _apply_association_props(props, field, app_type) + elif component_name == "Divider": + _apply_divider_props(props, field, label) + + return node, used_colors + + +# --------------------------------------------------------------------------- +# 各类型 props 应用 +# --------------------------------------------------------------------------- + + +def _apply_textarea_props(props: dict, field: dict) -> None: + props["rows"] = field.get("rows", 4) + props["htmlType"] = "textarea" + + +def _apply_number_props(props: dict, field: dict) -> None: + fmt = field.get("format", "INT") + if fmt == "FLOAT": + props["precision"] = field.get("precision", 2) + props["format"] = "money_w4" + elif fmt == "PERCENT": + props["precision"] = field.get("precision", 2) + props["format"] = "percent" + else: + props["precision"] = 0 + props["format"] = "integer" + + if field.get("suffix"): + props["innerAfter"] = i18n(field["suffix"]) + if field.get("prefix"): + props["innerBefore"] = i18n(field["prefix"]) + if field.get("min") is not None: + props["min"] = field["min"] + if field.get("max") is not None: + props["max"] = field["max"] + + +def _apply_rate_props(props: dict, field: dict) -> None: + props["count"] = field.get("total", 5) + if field.get("allowHalf"): + props["allowHalf"] = True + + +def _apply_date_props(props: dict, field: dict, component_name: str) -> None: + fmt = field.get("format", "yyyy-MM-dd") + props["format"] = fmt + if "HH" in fmt: + props["showTime"] = True + + +def _apply_option_props( + props: dict, + field: dict, + component_name: str, + used_colors: set[str], +) -> set[str]: + options = field.get("options", []) + if not options: + return used_colors + + is_checkbox = component_name in ("CheckboxField", "MultiSelectField") + props["isUseDataSourceColor"] = True + data_source, used_colors = build_option_data_source( + options, is_checkbox=is_checkbox, used_colors=used_colors + ) + props["dataSource"] = data_source + if not is_checkbox and data_source: + props["value"] = data_source[0]["value"] + return used_colors + + +def _apply_people_props(props: dict, field: dict) -> None: + multi = field.get("multi") or field.get("multiple") + if multi: + props["multiple"] = True + props["mode"] = "multiple" + + +def _apply_address_props(props: dict, field: dict) -> None: + level = field.get("level", "ADDRESS") + props["addressType"] = level + + +def _apply_attachment_props(props: dict, field: dict) -> None: + props["autoUpload"] = True + props["maxFileSize"] = field.get("maxFileSize", 100) + if field.get("maxFiles"): + props["maxItems"] = field["maxFiles"] + if field.get("fileTypes"): + props["accept"] = field["fileTypes"] + + +def _apply_image_props(props: dict, field: dict) -> None: + props["autoUpload"] = True + if field.get("maxFiles"): + props["maxItems"] = field["maxFiles"] + + +def _apply_serial_number_props( + props: dict, + field: dict, + app_type: str, + form_uuid: str, + field_id: str, + corp_id: str, +) -> None: + # 流水号不允许 required + if "validation" in props: + props["validation"] = [v for v in props["validation"] if v.get("type") != "required"] + + serial_rule = field.get("serialNumberRule") or [ + { + "__hide_delete__": False, + "ruleType": "date", + "content": "", + "formField": "", + "dateFormat": "yyyyMMdd", + "timeZone": "+8", + "digitCount": 4, + "isFixed": True, + "isFixedTips": "", + "resetPeriod": "noClean", + "resetPeriodTips": "", + "initialValue": 1, + }, + { + "__hide_delete__": True, + "ruleType": "autoCount", + "content": "", + "formField": "", + "dateFormat": "yyyyMMdd", + "timeZone": "+8", + "digitCount": "4", + "isFixed": True, + "isFixedTips": "", + "resetPeriod": "noClean", + "resetPeriodTips": "", + "initialValue": 1, + }, + ] + props["serialNumberRule"] = serial_rule + + serial_rule_json = json.dumps({"type": "custom", "value": serial_rule}).replace('"', '\\"') + props["formula"] = { + "expression": f'SERIALNUMBER("{corp_id}", "{app_type}", "{form_uuid}", "{field_id}", "{serial_rule_json}")' + } + + +def _apply_table_props( + node: dict, + field: dict, + used_colors: set[str], + app_type: str, + form_uuid: str, + corp_id: str, +) -> set[str]: + props = node["props"] + props["layout"] = "TABLE" + props["mobileLayout"] = "TILED" + props["maxItems"] = field.get("maxItems", 50) + + children_defs = field.get("children", []) + children_nodes: list[dict[str, Any]] = [] + for child_field in children_defs: + child_node, used_colors = build_field_component( + child_field, + used_colors=used_colors, + app_type=app_type, + form_uuid=form_uuid, + corp_id=corp_id, + ) + children_nodes.append(child_node) + + node["children"] = children_nodes + return used_colors + + +def _apply_association_props(props: dict, field: dict, app_type: str) -> None: + source_app = field.get("sourceApp", app_type) + source_form = field.get("sourceForm", "") + display_field_code = field.get("displayFieldCode", "") + + if source_form: + props["associationForm"] = { + "appType": source_app, + "formUuid": source_form, + "formType": "receipt", + "formTitle": "", + "mainFieldId": display_field_code, + "mainComponentName": "TextField", + "mainFieldLabel": "", + } + props["dataFilterRules"] = {"instanceFieldId": None} + + filling_rules = field.get("dataFillingRules", []) + if filling_rules: + normalized: list[dict[str, Any]] = [] + for rule in filling_rules: + src = rule.get("source", "") + tgt = rule.get("target", "") + normalized.append({ + "source": src, + "sourceFieldId": src, + "sourceType": rule.get("sourceType", "form"), + "target": tgt, + "targetFieldId": tgt, + "targetType": rule.get("targetType", "form"), + }) + props["dataFillingRules"] = {"mainRules": normalized} + + +def _apply_divider_props(props: dict, field: dict, label: str) -> None: + props["title"] = i18n(label) + props["type"] = field.get("dividerType", "multi-parallelograms-end") + props.pop("validation", None) + props["behavior"] = "NORMAL" + props.pop("__category__", None) diff --git a/skills/multi/dingtalk-misc/scripts/yida_form_inspector.py b/skills/multi/dingtalk-misc/scripts/yida_form_inspector.py new file mode 100644 index 00000000..4ab65be4 --- /dev/null +++ b/skills/multi/dingtalk-misc/scripts/yida_form_inspector.py @@ -0,0 +1,311 @@ +#!/usr/bin/env python3 +""" +yida_form_inspector.py — 跨表单元数据巡查工具 + +为「自定义页面联动其他表单」场景提供元数据采集能力,让 AI 在生成 JSX 前就能 +拿到目标应用下所有表单的 formUuid + 字段 ID,避免硬编码错误。 + +【调用前必读】references/yida-custom-page-codegen.md §9 + 跨表单联动 5 种模式(只读聚合 / 提交其他表 / Master-Detail / 多表 Dashboard / + 跨表搬运)。本脚本只负责取字段元数据,JSX 写法以 codegen.md 为准——不要 + 看到 --help 就直接拼 JSX。 + +用法: + # 列出应用下全部表单(含 formUuid / formType / title) + python yida_form_inspector.py --action list-forms --app APP_X + + # 查看单张表单的字段(fieldId / dataType / label) + python yida_form_inspector.py --action fields --app APP_X --form FORM-XXX + + # 一次性导出多张表单的字段汇总(推荐:跨表整合页面用) + python yida_form_inspector.py --action bundle --app APP_X --forms FORM-A,FORM-B,FORM-C --output ./forms.json + + # 直接生成可粘贴到 JSX 的 FIELDS 常量代码 + python yida_form_inspector.py --action fields-snippet --app APP_X --form FORM-XXX +""" +from __future__ import annotations + +import argparse +import json +import re +import subprocess +import sys +from pathlib import Path +from typing import Any + + +def _run_dws(args): + cmd = ["dws"] + args + try: + result = subprocess.run(cmd, capture_output=True, text=True, timeout=120) + except FileNotFoundError: + print("[FAIL] 找不到 'dws' 命令,请确认已安装并在 PATH 中", file=sys.stderr) + return None + except subprocess.TimeoutExpired: + print("[FAIL] dws 调用超时", file=sys.stderr) + return None + if result.returncode != 0: + err = result.stderr.strip() or result.stdout.strip() + print(f"[FAIL] dws 执行失败 (exit {result.returncode}): {err}", file=sys.stderr) + return None + try: + return json.loads(result.stdout) + except json.JSONDecodeError as exc: + print(f"[FAIL] 输出非 JSON: {exc}\n{result.stdout[:300]}", file=sys.stderr) + return None + + +def _list_forms(app, form_types=None): + args = ["yida", "app", "list-forms", "--app", app, "--format", "json"] + if form_types: + args.extend(["--form-types", form_types]) + data = _run_dws(args) + if data is None: + return None + if isinstance(data, dict): + for key in ("forms", "data", "items", "list"): + if isinstance(data.get(key), list): + return data[key] + return [data] + if isinstance(data, list): + return data + return [] + + +_FIELD_ID_VALUE_RE = re.compile(r'\b[A-Za-z]+Field_[A-Za-z0-9]+\b') +_COMPONENT_LIST_KEYS = ("components", "fields", "data", "items", "result", "children", "list") + + +def _walk_values(obj): + if isinstance(obj, dict): + yield obj + for value in obj.values(): + yield from _walk_values(value) + elif isinstance(obj, list): + for item in obj: + yield from _walk_values(item) + + +def _find_nested_value(obj, keys): + if isinstance(obj, dict): + for key in keys: + value = obj.get(key) + if value not in (None, ""): + return value + for value in obj.values(): + found = _find_nested_value(value, keys) + if found not in (None, ""): + return found + elif isinstance(obj, list): + for item in obj: + found = _find_nested_value(item, keys) + if found not in (None, ""): + return found + return None + + +def _normalize_i18n_label(label): + if isinstance(label, str): + raw = label.strip() + if raw.startswith("{") and raw.endswith("}"): + try: + parsed = json.loads(raw) + except json.JSONDecodeError: + return label + if isinstance(parsed, dict): + return (parsed.get("zh_CN") or parsed.get("zh-CN") + or parsed.get("text") or parsed.get("pureEn_US") + or parsed.get("en_US") or label) + return label + if isinstance(label, dict): + return (label.get("zh_CN") or label.get("zh-CN") or label.get("text") + or label.get("pureEn_US") or label.get("en_US") or "") + return label or "" + + +def _find_field_id(obj): + value = _find_nested_value(obj, ("fieldId", "fieldCode", "field_id", "fieldKey")) + if isinstance(value, str): + match = _FIELD_ID_VALUE_RE.search(value) + return match.group(0) if match else value + key_value = _find_nested_value(obj, ("key", "name", "id")) + if isinstance(key_value, str): + match = _FIELD_ID_VALUE_RE.search(key_value) + if match: + return match.group(0) + text = json.dumps(obj, ensure_ascii=False) if isinstance(obj, (dict, list)) else str(obj) + match = _FIELD_ID_VALUE_RE.search(text) + return match.group(0) if match else None + + +def _extract_component_items(data): + if isinstance(data, dict): + for key in _COMPONENT_LIST_KEYS: + value = data.get(key) + if isinstance(value, list): + return value + if isinstance(value, dict): + nested = _extract_component_items(value) + if nested: + return nested + if isinstance(data, list): + return data + + found = [] + seen = set() + for item in _walk_values(data): + if not isinstance(item, dict): + continue + field_id = _find_field_id(item) + if field_id and field_id not in seen: + seen.add(field_id) + found.append(item) + return found + + +def _components(app, form): + data = _run_dws(["yida", "form", "components", "--app", app, + "--form", form, "--format", "json"]) + if data is None: + return None + return _extract_component_items(data) + + +def _normalize_field(comp): + field_id = _find_field_id(comp) + label = (_find_nested_value(comp, ("label", "title", "text", "displayName", "nameCn")) + or "") + label = _normalize_i18n_label(label) + return { + "fieldId": field_id, + "label": label, + "dataType": _find_nested_value(comp, ("dataType", "valueType")) or "", + "componentName": _find_nested_value(comp, ("componentName", "type", "component")) or "", + } + + +def _camel_safe(text, fallback): + if not text: + text = fallback + cleaned = re.sub(r"[^A-Za-z0-9]+", " ", text).strip() + if not cleaned: + return fallback + parts = cleaned.split() + return parts[0].lower() + "".join(p.capitalize() for p in parts[1:]) + + +def _action_list_forms(args): + forms = _list_forms(args.app, args.form_types) + if forms is None: + return 1 + summary = [] + for f in forms: + summary.append({ + "formUuid": f.get("formUuid") or f.get("formId") or f.get("uuid"), + "title": f.get("title") or f.get("name"), + "formType": f.get("formType") or f.get("type"), + "appType": f.get("appType") or args.app, + }) + print(json.dumps({"ok": True, "app": args.app, "count": len(summary), + "forms": summary}, ensure_ascii=False, indent=2)) + return 0 + + +def _action_fields(args): + if not args.form: + print("错误: --action fields 需要 --form", file=sys.stderr) + return 1 + comps = _components(args.app, args.form) + if comps is None: + return 1 + fields = [_normalize_field(c) for c in comps] + print(json.dumps({"ok": True, "app": args.app, "form": args.form, + "count": len(fields), "fields": fields}, + ensure_ascii=False, indent=2)) + return 0 + + +def _action_fields_snippet(args): + if not args.form: + print("错误: --action fields-snippet 需要 --form", file=sys.stderr) + return 1 + comps = _components(args.app, args.form) + if comps is None: + return 1 + lines = ["// 由 yida_form_inspector.py 自动生成", + f"// 表单: {args.form}", + "var FIELDS = {"] + used = set() + for idx, comp in enumerate(comps): + f = _normalize_field(comp) + if not f["fieldId"]: + continue + var_name = _camel_safe(f["label"], f"field{idx}") + base = var_name + n = 2 + while var_name in used: + var_name = f"{base}{n}" + n += 1 + used.add(var_name) + comment = f" // {f['label']} ({f['dataType']})" if f["label"] else "" + lines.append(f" {var_name}: '{f['fieldId']}',{comment}") + lines.append("};") + print("\n".join(lines)) + return 0 + + +def _action_bundle(args): + if not args.forms: + print("错误: --action bundle 需要 --forms FORM-A,FORM-B,...", file=sys.stderr) + return 1 + form_ids = [s.strip() for s in args.forms.split(",") if s.strip()] + bundle = {"ok": True, "app": args.app, "forms": {}} + for fid in form_ids: + comps = _components(args.app, fid) + if comps is None: + bundle["forms"][fid] = {"ok": False, "error": "fetch_failed"} + continue + bundle["forms"][fid] = { + "ok": True, + "fields": [_normalize_field(c) for c in comps], + } + out = json.dumps(bundle, ensure_ascii=False, indent=2) + if args.output: + Path(args.output).write_text(out, encoding="utf-8") + print(f"[OK] 已写入 {args.output}({len(form_ids)} 张表单)") + else: + print(out) + return 0 + + +def main(): + ap = argparse.ArgumentParser( + description=( + "跨表单元数据巡查 / FIELDS 常量生成 " + "【调用前必读】references/yida-custom-page-codegen.md §9" + "(跨表单联动 5 种模式),不要只看 --help 就拼 JSX" + ), + formatter_class=argparse.RawDescriptionHelpFormatter, + epilog=__doc__, + ) + ap.add_argument("--action", required=True, + choices=["list-forms", "fields", "fields-snippet", "bundle"]) + ap.add_argument("--app", required=True, help="应用编码 appType") + ap.add_argument("--form", help="单表 formUuid(fields / fields-snippet 用)") + ap.add_argument("--forms", help="多表 formUuid 逗号分隔(bundle 用)") + ap.add_argument("--form-types", + help="list-forms 过滤:receipt / process / report / display 等") + ap.add_argument("--output", help="bundle 写入文件") + args = ap.parse_args() + + handlers = { + "list-forms": _action_list_forms, + "fields": _action_fields, + "fields-snippet": _action_fields_snippet, + "bundle": _action_bundle, + } + return handlers[args.action](args) + + +if __name__ == "__main__": + sys.exit(main()) diff --git a/skills/multi/dingtalk-misc/scripts/yida_form_update.py b/skills/multi/dingtalk-misc/scripts/yida_form_update.py new file mode 100644 index 00000000..879c0ce8 --- /dev/null +++ b/skills/multi/dingtalk-misc/scripts/yida_form_update.py @@ -0,0 +1,185 @@ +#!/usr/bin/env python3 +""" +宜搭表单 schema 生成/修改(编排:get-schema → apply changes → update-schema) + +━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +用法: + python yida_form_update.py --app APP_X --form FORM-XXX --changes-file fields.json --yes + python yida_form_update.py --app APP_X --form FORM-XXX --changes-json '[...]' --yes + +━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +changes.json 格式(非空数组,≤ 30 条): + + [ + {"action": "add", "field": {"type": "TextField", "label": "备注"}, "after": "请假事由"}, + {"action": "update", "label": "天数", "changes": {"required": true, "suffix": "天"}}, + {"action": "delete", "label": "废弃字段"} + ] + +action 支持: add / update / delete +━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +新建场景:CLI 先 `dws yida design form create` 拿到 formUuid, +再调本脚本传全 add 的 changes → 自动走 build_form_schema 全量构建。 + +更新场景:传含 add/update/delete 的 changes → 增量修改既有 schema。 +━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +""" +from __future__ import annotations + +import argparse +import json +import os +import subprocess +import sys +from pathlib import Path +from typing import Any + +_SCRIPT_DIR = Path(__file__).resolve().parent +if str(_SCRIPT_DIR) not in sys.path: + sys.path.insert(0, str(_SCRIPT_DIR)) + +from yida_form_builder import apply_changes_to_schema, build_form_schema, is_empty_skeleton # noqa: E402 + +MAX_CHANGES = 30 +MAX_CHANGES_FILE_SIZE = 512 * 1024 +MAX_INLINE_JSON = 64 * 1024 + + +def _resolve_safe_path(path_str: str) -> Path: + allowed_root = os.environ.get("OPENCLAW_WORKSPACE", os.getcwd()) + allowed_root_p = Path(allowed_root).resolve() + target = Path(path_str).resolve() if Path(path_str).is_absolute() else (Path.cwd() / path_str).resolve() + try: + target.relative_to(allowed_root_p) + except ValueError: + raise ValueError(f"路径超出允许范围:{path_str}\n允许根目录:{allowed_root_p}") + return target + + +def _run_dws(args: list[str], dry_run: bool = False) -> Any | None: + cmd = ["dws"] + args + if dry_run: + print(f" [dry-run] {' '.join(cmd)}") + return {"dry_run": True} + try: + result = subprocess.run(cmd, capture_output=True, text=True, timeout=120) + except FileNotFoundError: + print(" ✗ 找不到 'dws' 命令", file=sys.stderr) + return None + except subprocess.TimeoutExpired: + print(" ✗ dws 超时", file=sys.stderr) + return None + if result.returncode != 0: + err = result.stderr.strip() or result.stdout.strip() + print(f" ✗ dws 失败 (exit {result.returncode}): {err}", file=sys.stderr) + return None + try: + return json.loads(result.stdout) + except json.JSONDecodeError as e: + print(f" ✗ 非 JSON: {e}\n 输出: {result.stdout[:300]}", file=sys.stderr) + return None + + +def _extract_schema(resp: Any) -> dict[str, Any] | None: + if isinstance(resp, dict) and isinstance(resp.get("content"), dict): + return resp["content"] + if isinstance(resp, dict): + return resp + return None + + +def _load_changes(args: argparse.Namespace) -> list[dict]: + if args.changes_file: + safe = _resolve_safe_path(args.changes_file) + if not safe.exists(): + raise ValueError(f"文件不存在: {safe}") + if safe.stat().st_size > MAX_CHANGES_FILE_SIZE: + raise ValueError(f"文件过大 (限制 {MAX_CHANGES_FILE_SIZE:,} 字节)") + with safe.open("r", encoding="utf-8") as f: + changes = json.load(f) + elif args.changes_json: + if len(args.changes_json.encode("utf-8")) > MAX_INLINE_JSON: + raise ValueError(f"--changes-json 过长 (限制 {MAX_INLINE_JSON:,} 字节)") + changes = json.loads(args.changes_json) + else: + raise ValueError("必须提供 --changes-file 或 --changes-json") + + if not isinstance(changes, list) or not changes: + raise ValueError("changes 必须是非空数组") + if len(changes) > MAX_CHANGES: + raise ValueError(f"changes 过多 ({len(changes)} > {MAX_CHANGES})") + for i, c in enumerate(changes): + if not isinstance(c, dict): + raise ValueError(f"change #{i+1} 必须是对象") + if c.get("action") not in ("add", "update", "delete"): + raise ValueError(f"change #{i+1} action 无效: {c.get('action')}") + return changes + + +def main() -> int: + ap = argparse.ArgumentParser(description="宜搭表单 schema 生成/修改", + formatter_class=argparse.RawDescriptionHelpFormatter, epilog=__doc__) + ap.add_argument("--app", required=True, help="应用编码 appType") + ap.add_argument("--form", required=True, help="表单 formUuid") + ap.add_argument("--changes-file", help="变更定义 JSON 文件路径") + ap.add_argument("--changes-json", help="变更定义 JSON 内联") + ap.add_argument("--corp-id", default="", help="企业 ID") + ap.add_argument("--yes", action="store_true", help="确认写入") + ap.add_argument("--dry-run", action="store_true", help="只生成不写入") + args = ap.parse_args() + + try: + changes = _load_changes(args) + except ValueError as e: + print(f"错误: {e}", file=sys.stderr) + return 1 + + # Step 1: 拉取现有 schema + print("Step 1/3: 获取现有 schema") + resp = _run_dws(["yida", "design", "form", "get-schema", "--app", args.app, + "--form", args.form, "--format", "json"], dry_run=args.dry_run) + if args.dry_run: + print(" [dry-run] 跳过 get-schema") + print(json.dumps({"ok": True, "dry_run": True, "changeCount": len(changes)}, ensure_ascii=False)) + return 0 + if not resp: + return 1 + schema = _extract_schema(resp) + if not schema: + print("错误: get-schema 返回结构异常,未找到 schema", file=sys.stderr) + return 1 + print(" ✓ 拿到 schema") + + # Step 2: 空骨架自愈 + all_add = all(c.get("action") == "add" for c in changes) + if is_empty_skeleton(schema): + if not all_add: + print("错误: 表单是空骨架,但 changes 含 update/delete;空表单只能用全 add", file=sys.stderr) + return 1 + print(" ⚠ 空骨架 + 全 add → 全量构建") + info = _run_dws(["yida", "design", "form", "get-info", "--app", args.app, + "--form", args.form, "--format", "json"]) + title = (info.get("title", "") or "未命名") if info else "未命名" + fields = [c["field"] for c in changes] + schema = build_form_schema(form_title=title, fields=fields, form_uuid=args.form, + corp_id=args.corp_id, app_type=args.app) + else: + print(f"Step 2/3: 应用 {len(changes)} 条变更") + schema = apply_changes_to_schema(schema, changes, corp_id=args.corp_id, + app_type=args.app, form_uuid=args.form) + print(" ✓ 本地变更完成") + + # Step 3: 写回 + schema_json = json.dumps(schema, ensure_ascii=False, separators=(",", ":")) + print(f"Step 3/3: 写入 schema ({len(schema_json):,} 字节)") + resp = _run_dws(["yida", "design", "form", "update-schema", "--app", args.app, + "--form", args.form, "--content", schema_json, "--yes", "--format", "json"]) + if not resp: + return 1 + print(" ✓ 写入成功") + print(json.dumps({"ok": True, "formUuid": args.form, "changeCount": len(changes)}, ensure_ascii=False)) + return 0 + + +if __name__ == "__main__": + sys.exit(main()) diff --git a/skills/multi/dingtalk-misc/scripts/yida_jsx_pipeline.py b/skills/multi/dingtalk-misc/scripts/yida_jsx_pipeline.py new file mode 100644 index 00000000..da620a8d --- /dev/null +++ b/skills/multi/dingtalk-misc/scripts/yida_jsx_pipeline.py @@ -0,0 +1,1588 @@ +#!/usr/bin/env python3 +"""yida_jsx_pipeline.py — JSX 全链路处理流水线(transform + field_check + lint)。 + +三块能力一站式聚合(按 region 分段维护): + 1) [region: transform] JSX 源码 → React.createElement 调用(纯 Python,零依赖)。 + 2) [region: field_check] 发布前字段 ID 对账(拉表单 schema 比对 JSX 引用)。 + 3) [region: lint] 宜搭专属运行时静态检查(30 条规则)。 + +公开 API(被 yida_custom_page_update.py 直接 import): + - transform_jsx(source) -> str + - field_check(code, app, *, schema_fetcher=None) -> dict + - lint_yida_source(source, filename=None) -> dict + - lint_check(source, filename=None) -> dict + - extract_form_uuids / extract_field_ids / fetch_form_fields (field_check 辅助) + - EVENT_NAME_ALIASES / THEN_CALLBACK_LINE_LIMIT / CALLBACK_SCAN_LINE_LIMIT (lint 常量) + +CLI: + python yida_jsx_pipeline.py transform + python yida_jsx_pipeline.py field-check --app APP_X --code-file page.jsx + python yida_jsx_pipeline.py lint page.jsx [--json] [--stdin] +""" +from __future__ import annotations + +import argparse +import json +import re +import subprocess +import sys +from pathlib import Path +from typing import Callable, Iterable + +# =========================================================================== +# region: transform —— JSX → React.createElement +# =========================================================================== + +_VOID_ELEMENTS = frozenset([ + 'area', 'base', 'br', 'col', 'embed', 'hr', 'img', 'input', + 'link', 'meta', 'param', 'source', 'track', 'wbr', +]) + +_JSX_TAG_START = re.compile(r'<([A-Za-z_][A-Za-z0-9_.]*|>)') + + +def transform_jsx(source): + """将 JSX 源码中的 JSX 表达式转换为 React.createElement 调用。 + + Args: + source: 包含 JSX 的 JavaScript 源码字符串 + + Returns: + 转换后不含 JSX 的 JavaScript 源码 + """ + transformer = _JsxTransformer(source) + return transformer.transform() + + +class _JsxTransformer: + """递归下降 JSX 转换器""" + + def __init__(self, source): + self.source = source + self.pos = 0 + self.output = [] + + def transform(self): + while self.pos < len(self.source): + if self._at_jsx_start(): + jsx_code = self._parse_jsx_element() + self.output.append(jsx_code) + else: + self.output.append(self.source[self.pos]) + self.pos += 1 + return ''.join(self.output) + + def _peek(self, offset=0): + idx = self.pos + offset + if idx < len(self.source): + return self.source[idx] + return '' + + def _remaining(self): + return self.source[self.pos:] + + def _at_jsx_start(self): + """判断当前 < 是否是 JSX 标签开始(而非比较运算符)""" + if self._peek() != '<': + return False + next_char = self._peek(1) + if next_char == '>' or next_char == '/': + return self._is_jsx_context() + if next_char.isalpha() or next_char == '_': + return self._is_jsx_context() + return False + + def _is_jsx_context(self): + """通过前文判断当前 < 是否处于 JSX 可出现的上下文""" + preceding = ''.join(self.output).rstrip() + if not preceding: + # 在递归调用中(如表达式容器 {
}),output 为空但来源是合法 JSX + # 检查 source 前面是否刚从表达式容器开始 + if self.pos == 0: + return True + return False + last_char = preceding[-1] + # JSX 可出现在 return/( 之后、赋值/逗号/冒号/三元之后、逻辑运算符之后 + if last_char in ('(', ',', ':', '?', '=', '&', '|', '!', ';', '{', '[', '\n'): + return True + # return '): + return True + if last_char == '>': + return False + return False + + def _skip_whitespace(self): + while self.pos < len(self.source) and self.source[self.pos] in ' \t\n\r': + self.pos += 1 + + def _parse_jsx_element(self): + """解析一个完整的 JSX 元素,返回 createElement 调用字符串""" + assert self.source[self.pos] == '<' + self.pos += 1 # skip < + + # Fragment: <>... + if self._peek() == '>': + self.pos += 1 # skip > + children = self._parse_jsx_children('') + # expect + if self.source[self.pos:self.pos + 3] == '': + self.pos += 3 + return self._emit_create_element('React.Fragment', 'null', children) + + # 解析标签名 + tag_name = self._parse_tag_name() + + # 解析属性 + attrs = self._parse_attributes() + + # 自闭合 /> 或 > + self._skip_whitespace() + if self.source[self.pos:self.pos + 2] == '/>': + self.pos += 2 + props_str = self._attrs_to_props(attrs) + return self._emit_create_element(self._tag_ref(tag_name), props_str, []) + + assert self.source[self.pos] == '>', f"Expected > at pos {self.pos}, got: {self.source[self.pos:self.pos+20]}" + self.pos += 1 # skip > + + # 解析子元素 + children = self._parse_jsx_children(tag_name) + + # 解析闭合标签 + self._parse_closing_tag(tag_name) + + props_str = self._attrs_to_props(attrs) + return self._emit_create_element(self._tag_ref(tag_name), props_str, children) + + def _parse_tag_name(self): + start = self.pos + while self.pos < len(self.source) and (self.source[self.pos].isalnum() or self.source[self.pos] in '_.'): + self.pos += 1 + return self.source[start:self.pos] + + def _parse_attributes(self): + """解析 JSX 属性列表,返回 [(name, value_str), ...]""" + attrs = [] + while self.pos < len(self.source): + self._skip_whitespace() + ch = self._peek() + if ch == '/' or ch == '>': + break + attr_name = self._parse_attribute_name() + if not attr_name: + break + + self._skip_whitespace() + if self._peek() == '=': + self.pos += 1 # skip = + self._skip_whitespace() + value = self._parse_attribute_value() + attrs.append((attr_name, value)) + else: + # boolean attribute + attrs.append((attr_name, 'true')) + return attrs + + def _parse_attribute_name(self): + start = self.pos + while self.pos < len(self.source): + ch = self.source[self.pos] + if ch.isalnum() or ch in '_-': + self.pos += 1 + else: + break + return self.source[start:self.pos] + + def _parse_attribute_value(self): + """解析属性值,返回 JS 表达式字符串""" + ch = self._peek() + if ch == '"': + return self._parse_string_literal('"') + if ch == "'": + return self._parse_string_literal("'") + if ch == '{': + return self._parse_jsx_expression() + return 'true' + + def _parse_string_literal(self, quote): + self.pos += 1 # skip opening quote + result = [quote] + while self.pos < len(self.source): + ch = self.source[self.pos] + if ch == '\\': + result.append(ch) + self.pos += 1 + if self.pos < len(self.source): + result.append(self.source[self.pos]) + self.pos += 1 + elif ch == quote: + result.append(ch) + self.pos += 1 + break + else: + result.append(ch) + self.pos += 1 + return ''.join(result) + + def _parse_jsx_expression(self): + """解析 { ... } 表达式容器,返回内部表达式字符串(可能含嵌套 JSX)""" + assert self.source[self.pos] == '{' + self.pos += 1 # skip { + content = self._read_balanced_braces() + # 递归转换内部 JSX + inner_transformed = transform_jsx(content) + return inner_transformed + + def _read_balanced_braces(self): + """从当前位置读取到匹配 } 为止的内容(处理嵌套 {} 和字符串)""" + depth = 1 + result = [] + while self.pos < len(self.source) and depth > 0: + ch = self.source[self.pos] + if ch in ('"', "'", '`'): + string_content = self._consume_string_in_expr(ch) + result.append(string_content) + elif ch == '{': + depth += 1 + result.append(ch) + self.pos += 1 + elif ch == '}': + depth -= 1 + if depth > 0: + result.append(ch) + self.pos += 1 + elif ch == '/' and self.pos + 1 < len(self.source): + next_ch = self.source[self.pos + 1] + if next_ch == '/': + comment = self._consume_line_comment() + result.append(comment) + elif next_ch == '*': + comment = self._consume_block_comment() + result.append(comment) + else: + result.append(ch) + self.pos += 1 + else: + result.append(ch) + self.pos += 1 + return ''.join(result) + + def _consume_string_in_expr(self, quote): + """消费字符串字面量(含转义),返回完整字符串""" + result = [quote] + self.pos += 1 + if quote == '`': + return self._consume_template_literal(result) + while self.pos < len(self.source): + ch = self.source[self.pos] + result.append(ch) + self.pos += 1 + if ch == '\\' and self.pos < len(self.source): + result.append(self.source[self.pos]) + self.pos += 1 + elif ch == quote: + break + return ''.join(result) + + def _consume_template_literal(self, result): + """消费模板字面量 `...${expr}...`""" + while self.pos < len(self.source): + ch = self.source[self.pos] + result.append(ch) + self.pos += 1 + if ch == '\\' and self.pos < len(self.source): + result.append(self.source[self.pos]) + self.pos += 1 + elif ch == '`': + break + elif ch == '$' and self.pos < len(self.source) and self.source[self.pos] == '{': + result.append('{') + self.pos += 1 + # 读到匹配的 } + depth = 1 + while self.pos < len(self.source) and depth > 0: + c = self.source[self.pos] + result.append(c) + self.pos += 1 + if c == '{': + depth += 1 + elif c == '}': + depth -= 1 + return ''.join(result) + + def _consume_line_comment(self): + start = self.pos + while self.pos < len(self.source) and self.source[self.pos] != '\n': + self.pos += 1 + return self.source[start:self.pos] + + def _consume_block_comment(self): + start = self.pos + self.pos += 2 # skip /* + while self.pos < len(self.source) - 1: + if self.source[self.pos] == '*' and self.source[self.pos + 1] == '/': + self.pos += 2 + break + self.pos += 1 + else: + self.pos = len(self.source) + return self.source[start:self.pos] + + def _parse_jsx_children(self, parent_tag): + """解析子元素直到遇到 或 """ + children = [] + text_buf = [] + + while self.pos < len(self.source): + # 检查闭合标签 + if parent_tag and self.source[self.pos:self.pos + 2] == '': + break + + ch = self.source[self.pos] + + if ch == '{': + # 先 flush text + if text_buf: + text = ''.join(text_buf).strip() + if text: + children.append(repr(text)) + text_buf = [] + # 表达式子元素 + self.pos += 1 + expr_content = self._read_balanced_braces() + expr_transformed = transform_jsx(expr_content) + stripped = expr_transformed.strip() + # 跳过纯 JSX 注释表达式 {/* ... */} 或 {// ...} + # 否则会在 React.createElement 子节点列表里产生空位导致连续逗号 ,, + comment_stripped = re.sub(r'/\*[\s\S]*?\*/', '', stripped) + comment_stripped = re.sub(r'//[^\n]*', '', comment_stripped).strip() + if not comment_stripped: + continue + children.append(stripped) + + elif ch == '<': + # 先 flush text + if text_buf: + text = ''.join(text_buf).strip() + if text: + children.append(repr(text)) + text_buf = [] + # 嵌套子元素 + child_element = self._parse_jsx_element() + children.append(child_element) + else: + text_buf.append(ch) + self.pos += 1 + + # flush remaining text + if text_buf: + text = ''.join(text_buf).strip() + if text: + children.append(repr(text)) + + return children + + def _parse_closing_tag(self, expected_tag): + """解析 """ + if self.source[self.pos:self.pos + 2] != '': + self.pos += 1 + + def _tag_ref(self, tag_name): + """标签名转为 createElement 第一参数""" + if tag_name[0].isupper(): + return tag_name # 组件引用 + return repr(tag_name) # HTML 元素用字符串 + + def _attrs_to_props(self, attrs): + """将属性列表转为 props 对象字符串""" + if not attrs: + return 'null' + pairs = [] + for name, value in attrs: + prop_name = self._normalize_attr_name(name) + pairs.append(f'{prop_name}: {value}') + return '{' + ', '.join(pairs) + '}' + + def _normalize_attr_name(self, name): + """HTML 属性名 → JS 属性名""" + mapping = { + 'class': 'className', + 'for': 'htmlFor', + 'tabindex': 'tabIndex', + 'readonly': 'readOnly', + 'maxlength': 'maxLength', + 'colspan': 'colSpan', + 'rowspan': 'rowSpan', + 'enctype': 'encType', + 'contenteditable': 'contentEditable', + 'crossorigin': 'crossOrigin', + 'accesskey': 'accessKey', + 'autocomplete': 'autoComplete', + 'autofocus': 'autoFocus', + 'autoplay': 'autoPlay', + } + normalized = mapping.get(name.lower(), name) + # 带 - 的属性名需要引号 + if '-' in normalized and not normalized.startswith('data-') and not normalized.startswith('aria-'): + return repr(normalized) + if '-' in normalized: + return repr(normalized) + return normalized + + def _emit_create_element(self, tag, props, children): + """生成 React.createElement 调用""" + args = [tag, props] + args.extend(children) + return 'React.createElement(' + ', '.join(args) + ')' + + +# =========================================================================== +# region: field_check —— 发布前字段 ID 对账 +# =========================================================================== + +# JSX 里的表单引用:'FORM-XXX' 字面量 +_FORM_UUID_RE = re.compile(r"['\"](FORM-[A-Z0-9]+)['\"]") + +# 字段 ID 命名规范:以 Field/SelectField/... 结尾再接下划线 + 大小写数字 +# 用宽松前缀(任何字母)+ 强约束的 fieldId 命名格式(见 yida-custom-page-codegen.md) +_FIELD_ID_LITERAL_RE = re.compile(r"['\"]([a-zA-Z][a-zA-Z0-9]*Field_[A-Za-z0-9]+)['\"]") + + +def extract_form_uuids(code: str) -> set[str]: + """提取 JSX 中所有 'FORM-XXX' 字面量。""" + return set(_FORM_UUID_RE.findall(code)) + + +def extract_field_ids(code: str) -> set[str]: + """提取 JSX 中所有疑似字段 ID 的字符串字面量(xxxField_yyyy 形式)。""" + return set(_FIELD_ID_LITERAL_RE.findall(code)) + + +def _run_dws(args: list[str]) -> tuple[object | None, str | None]: + try: + result = subprocess.run(["dws"] + args, capture_output=True, text=True, timeout=120) + except FileNotFoundError: + return None, "找不到 'dws' 命令(请确认已安装并在 PATH)" + except subprocess.TimeoutExpired: + return None, "dws 调用超时" + if result.returncode != 0: + err = result.stderr.strip() or result.stdout.strip() + return None, f"dws 失败 (exit {result.returncode}): {err[:200]}" + try: + return json.loads(result.stdout), None + except json.JSONDecodeError as e: + return None, f"输出非 JSON: {e}" + + +_FIELD_ID_VALUE_RE = re.compile(r'\b[A-Za-z]+Field_[A-Za-z0-9]+\b') +_COMPONENT_LIST_KEYS = ("components", "fields", "data", "items", "result", "children", "list") + + +def _walk_values(obj): + if isinstance(obj, dict): + yield obj + for value in obj.values(): + yield from _walk_values(value) + elif isinstance(obj, list): + for item in obj: + yield from _walk_values(item) + + +def _find_nested_value(obj, keys): + if isinstance(obj, dict): + for key in keys: + value = obj.get(key) + if value not in (None, ""): + return value + for value in obj.values(): + found = _find_nested_value(value, keys) + if found not in (None, ""): + return found + elif isinstance(obj, list): + for item in obj: + found = _find_nested_value(item, keys) + if found not in (None, ""): + return found + return None + + +def _normalize_i18n_label(label): + if isinstance(label, str): + raw = label.strip() + if raw.startswith("{") and raw.endswith("}"): + try: + parsed = json.loads(raw) + except json.JSONDecodeError: + return label + if isinstance(parsed, dict): + return (parsed.get("zh_CN") or parsed.get("zh-CN") + or parsed.get("text") or parsed.get("pureEn_US") + or parsed.get("en_US") or label) + return label + if isinstance(label, dict): + return (label.get("zh_CN") or label.get("zh-CN") or label.get("text") + or label.get("pureEn_US") or label.get("en_US") or "") + return label or "" + + +def _find_field_id(obj): + value = _find_nested_value(obj, ("fieldId", "fieldCode", "field_id", "fieldKey")) + if isinstance(value, str): + m = _FIELD_ID_VALUE_RE.search(value) + if m: + return m.group(0) + return value + + key_value = _find_nested_value(obj, ("key", "name", "id")) + if isinstance(key_value, str): + m = _FIELD_ID_VALUE_RE.search(key_value) + if m: + return m.group(0) + + text = json.dumps(obj, ensure_ascii=False) if isinstance(obj, (dict, list)) else str(obj) + m = _FIELD_ID_VALUE_RE.search(text) + return m.group(0) if m else None + + +def _extract_component_items(data): + if isinstance(data, dict): + for key in _COMPONENT_LIST_KEYS: + value = data.get(key) + if isinstance(value, list): + return value + if isinstance(value, dict): + nested = _extract_component_items(value) + if nested: + return nested + if isinstance(data, list): + return data + + found = [] + seen = set() + for item in _walk_values(data): + if not isinstance(item, dict): + continue + fid = _find_field_id(item) + if fid and fid not in seen: + seen.add(fid) + found.append(item) + return found + + +def fetch_form_fields(app: str, form_uuid: str) -> tuple[list[dict] | None, str | None]: + """调用 dws yida form components 拿表单字段。""" + data, err = _run_dws(["yida", "form", "components", "--app", app, + "--form", form_uuid, "--format", "json"]) + if err: + return None, err + return _extract_component_items(data), None + + +def _normalize_field(comp: dict) -> dict: + field_id = _find_field_id(comp) + label = (_find_nested_value(comp, ("label", "title", "text", "displayName", "nameCn")) + or "") + label = _normalize_i18n_label(label) + return { + "fieldId": field_id, + "label": label, + "componentName": (_find_nested_value(comp, ("componentName", "type", "component")) + or ""), + } + + +def field_check(code: str, app: str, *, + schema_fetcher: Callable[[str], tuple[list[dict] | None, str | None]] | None = None) -> dict: + """字段 ID 对账。 + + Args: + code: JSX 源码 + app: 应用 appType + schema_fetcher: 可注入的字段拉取函数(测试用),签名 (form_uuid)->(fields, err) + + Returns: + { + ok: bool, + errors: [{type, fieldId?, message}], + warnings: [{type, message}], + info: {checkedForms, referencedFieldCount, knownFieldCount, missing} + } + """ + fetcher = schema_fetcher or (lambda fu: fetch_form_fields(app, fu)) + + form_uuids = extract_form_uuids(code) + referenced = extract_field_ids(code) + + if not form_uuids: + return { + "ok": True, "errors": [], "warnings": [], + "info": {"skipped": "no_form_uuid_in_jsx", + "referencedFieldCount": len(referenced)}, + } + + if not referenced: + return { + "ok": True, "errors": [], "warnings": [], + "info": {"skipped": "no_field_id_referenced", + "checkedForms": sorted(form_uuids)}, + } + + all_known: dict[str, dict] = {} + fetch_errors: list[dict] = [] + for fu in sorted(form_uuids): + fields, err = fetcher(fu) + if err or fields is None: + fetch_errors.append({"form": fu, "error": err or "unknown"}) + continue + for c in fields: + f = _normalize_field(c) + if f["fieldId"]: + all_known[f["fieldId"]] = {**f, "form": fu} + + # 所有表单都拉不到 → 无法对账,作为 warning 放行(避免登录态过期把发布卡死) + if fetch_errors and not all_known: + return { + "ok": True, + "errors": [], + "warnings": [{ + "type": "schema_fetch_all_failed", + "message": ("无法拉取任何表单 schema,跳过字段对账。" + "可能原因:登录态过期 / 网络异常 / form_uuid 不属于当前应用。" + f"详情:{fetch_errors}"), + }], + "info": {"checkedForms": sorted(form_uuids), + "referencedFieldCount": len(referenced), + "knownFieldCount": 0}, + } + + missing = sorted(referenced - all_known.keys()) + errors: list[dict] = [] + if missing: + forms_hint = ", ".join(sorted(form_uuids)) + for fid in missing: + errors.append({ + "type": "field_id_not_found", + "fieldId": fid, + "message": ( + f"字段 ID `{fid}` 在表单 schema 中不存在。\n" + f" 已检查的表单:{forms_hint}\n" + f" 修复建议:\n" + f" 1) 用 `python yida_form_inspector.py --action fields-snippet " + f"--app {app} --form ` 重新生成 FIELDS 常量\n" + f" 2) 或核对 var FORM_UUID 是否填错" + ), + }) + + warnings: list[dict] = [] + for fe in fetch_errors: + warnings.append({ + "type": "schema_fetch_partial", + "message": (f"表单 {fe['form']} schema 拉取失败(部分对账已跳过):{fe['error']}"), + }) + + return { + "ok": len(errors) == 0, + "errors": errors, + "warnings": warnings, + "info": { + "checkedForms": sorted(form_uuids), + "referencedFieldCount": len(referenced), + "knownFieldCount": len(all_known), + "missing": missing, + }, + } + + +# =========================================================================== +# region: lint —— 宜搭专属运行时静态检查(30 条规则) +# =========================================================================== + +THEN_CALLBACK_LINE_LIMIT = 50 +CALLBACK_SCAN_LINE_LIMIT = 80 + +EVENT_NAME_ALIASES = { + 'onclick': 'onClick', 'onchange': 'onChange', 'oninput': 'onChange', + 'onsubmit': 'onSubmit', 'onkeydown': 'onKeyDown', 'onkeyup': 'onKeyUp', + 'onkeypress': 'onKeyPress', 'onfocus': 'onFocus', 'onblur': 'onBlur', + 'onmouseenter': 'onMouseEnter', 'onmouseleave': 'onMouseLeave', + 'onmousedown': 'onMouseDown', 'onmouseup': 'onMouseUp', + 'onmousemove': 'onMouseMove', 'oncompositionstart': 'onCompositionStart', + 'oncompositionend': 'onCompositionEnd', +} + + +# ---- helpers ---- + +def _is_in_comment_or_string(line: str, match_index: int) -> bool: + """判断行内某偏移是否在 // 注释或字符串中(简化版)。""" + if match_index is None or match_index < 0: + return False + before = line[:match_index] + if '//' in before: + return True + quotes = sum(1 for ch in before if ch in ("'", '"')) + return quotes % 2 != 0 + + +def _strip_comments_and_strings(source: str) -> str: + """剥离 /**/ // 块/行注释 和 ' " ` 字符串,便于纯结构扫描。""" + out = re.sub(r'/\*[\s\S]*?\*/', '', source) + out = re.sub(r'//.*$', '', out, flags=re.M) + out = re.sub(r"'(?:\\.|[^'\\])*'", "''", out) + out = re.sub(r'"(?:\\.|[^"\\])*"', '""', out) + out = re.sub(r'`(?:\\.|[^`\\])*`', '``', out) + return out + + +def _extract_function_body(lines: list[str], start_line_index: int, function_index: int) -> str: + """从 lines[start_line_index] 起,找到第一个 `{` 并按 brace 深度抽出函数体(最多扫 80 行)。""" + end_line_index = min(len(lines), start_line_index + CALLBACK_SCAN_LINE_LIMIT) + source = '\n'.join(lines[start_line_index:end_line_index]) + open_brace_index = source.find('{', function_index) + if open_brace_index < 0: + return '' + + brace_depth = 0 + quote: str | None = None + in_line_comment = False + in_block_comment = False + + i = open_brace_index + n = len(source) + while i < n: + ch = source[i] + nxt = source[i + 1] if i + 1 < n else '' + + if in_line_comment: + if ch == '\n': + in_line_comment = False + i += 1 + continue + if in_block_comment: + if ch == '*' and nxt == '/': + in_block_comment = False + i += 2 + continue + i += 1 + continue + if quote: + if ch == '\\': + i += 2 + continue + if ch == quote: + quote = None + i += 1 + continue + if ch == '/' and nxt == '/': + in_line_comment = True + i += 2 + continue + if ch == '/' and nxt == '*': + in_block_comment = True + i += 2 + continue + if ch in ("'", '"', '`'): + quote = ch + i += 1 + continue + if ch == '{': + brace_depth += 1 + elif ch == '}': + brace_depth -= 1 + if brace_depth == 0: + return source[open_brace_index + 1:i] + i += 1 + return source[open_brace_index + 1:] + + +def _function_callback_uses_this(lines: list[str], line_index: int, match_index: int) -> bool: + line = lines[line_index] + fn_idx = line.find('function', match_index) + if fn_idx < 0: + return False + body = _extract_function_body(lines, line_index, fn_idx) + return bool(re.search(r'\bthis\b', _strip_comments_and_strings(body))) + + +def _extract_named_export_function_body(source: str, name: str) -> tuple[int, str] | None: + """Extract body of `export function name(...) { ... }` from full source.""" + m = re.search(r'\bexport\s+function\s+' + re.escape(name) + r'\s*\(', source) + if not m: + return None + open_brace = source.find('{', m.end()) + if open_brace < 0: + return None + + brace_depth = 0 + quote: str | None = None + in_line_comment = False + in_block_comment = False + i = open_brace + n = len(source) + while i < n: + ch = source[i] + nxt = source[i + 1] if i + 1 < n else '' + if in_line_comment: + if ch == '\n': + in_line_comment = False + i += 1 + continue + if in_block_comment: + if ch == '*' and nxt == '/': + in_block_comment = False + i += 2 + continue + i += 1 + continue + if quote: + if ch == '\\': + i += 2 + continue + if ch == quote: + quote = None + i += 1 + continue + if ch == '/' and nxt == '/': + in_line_comment = True + i += 2 + continue + if ch == '/' and nxt == '*': + in_block_comment = True + i += 2 + continue + if ch in ("'", '"', '`'): + quote = ch + i += 1 + continue + if ch == '{': + brace_depth += 1 + elif ch == '}': + brace_depth -= 1 + if brace_depth == 0: + start_line = source.count('\n', 0, open_brace) + 1 + return start_line, source[open_brace + 1:i] + i += 1 + start_line = source.count('\n', 0, open_brace) + 1 + return start_line, source[open_brace + 1:] + + +# ---- disable map ---- + +_DISABLE_LINE_RE = re.compile( + r'(?:dws|openyida)-lint-disable-line(?:\s+([a-z0-9_,\-\s]+))?', re.I) +_DISABLE_NEXT_RE = re.compile( + r'(?:dws|openyida)-lint-disable-next-line(?:\s+([a-z0-9_,\-\s]+))?', re.I) + + +def _parse_disable_rules(raw: str | None) -> list[str]: + if not raw or not raw.strip(): + return ['*'] + return [r.strip() for r in re.split(r'[,\s]+', raw) if r.strip()] + + +def _build_disable_map(lines: list[str]) -> dict[int, set[str]]: + dmap: dict[int, set[str]] = {} + for idx, line in enumerate(lines): + line_no = idx + 1 + m = _DISABLE_LINE_RE.search(line) + if m: + dmap.setdefault(line_no, set()).update(_parse_disable_rules(m.group(1))) + m2 = _DISABLE_NEXT_RE.search(line) + if m2: + dmap.setdefault(line_no + 1, set()).update(_parse_disable_rules(m2.group(1))) + return dmap + + +def _is_rule_disabled(dmap: dict[int, set[str]], line: int, rule: str) -> bool: + rules = dmap.get(line) + if not rules: + return False + return '*' in rules or rule in rules + + +def _push_issue(lst: list[dict], line: int, rule: str, message: str, + dmap: dict[int, set[str]]) -> None: + if _is_rule_disabled(dmap, line, rule): + return + for it in lst: + if it['line'] == line and it['rule'] == rule and it['message'] == message: + return + lst.append({'line': line, 'rule': rule, 'message': message}) + + +# ---- aux detectors ---- + +_THEN_START_RE = re.compile( + r'\.then\s*\(\s*(?:function\s*\(|(?:\([^)]*\)|[a-zA-Z_$]\w*)\s*=>)') + + +def _detect_large_then_callbacks(lines: list[str]) -> list[dict]: + results: list[dict] = [] + in_then = False + brace_depth = 0 + then_start_line = 0 + then_body_start_line = 0 + + for i, line in enumerate(lines): + trimmed = line.strip() + if trimmed.startswith('//') or trimmed.startswith('*') or trimmed.startswith('/*'): + continue + if not in_then: + m = _THEN_START_RE.search(line) + if m and not _is_in_comment_or_string(line, m.start()): + in_then = True + then_start_line = i + 1 + brace_depth = 0 + after = line[m.start():] + for ch in after: + if ch == '{': + brace_depth += 1 + elif ch == '}': + brace_depth -= 1 + then_body_start_line = i + 1 + else: + for ch in line: + if ch == '{': + brace_depth += 1 + elif ch == '}': + brace_depth -= 1 + if brace_depth <= 0: + callback_line_count = (i + 1) - then_body_start_line + if callback_line_count > THEN_CALLBACK_LINE_LIMIT: + results.append({'line': then_start_line, 'lineCount': callback_line_count}) + in_then = False + return results + + +_YIDA_CALL_RE = re.compile(r'this\.utils\.yida\.[A-Za-z_$][\w$]*\s*\(') + + +def _detect_yida_calls_without_catch(source: str, warnings: list[dict], + dmap: dict[int, set[str]]) -> None: + for m in _YIDA_CALL_RE.finditer(source): + line = source.count('\n', 0, m.start()) + 1 + statement = source[m.start():m.start() + 600] + if '.catch(' not in statement: + _push_issue(warnings, line, 'yida-api-catch', + '调用 this.utils.yida.* 应跟 .catch() 防止 Promise 异常吞没', dmap) + + +_LABEL_FORMATTER_RE = re.compile( + r'\blabel\s*:\s*\{[\s\S]{0,1200}?\bformatter\s*:\s*function\b') + + +def _detect_echarts_rich_label_formatter(source: str, warnings: list[dict], + dmap: dict[int, set[str]]) -> None: + for m in _LABEL_FORMATTER_RE.finditer(source): + line = source.count('\n', 0, m.start()) + 1 + block = source[m.start():m.start() + 1600] + uses_rich = bool(re.search(r'\brich\s*:', block) or + re.search(r"return\s+['\"`][\s\S]{0,300}?\{[A-Za-z0-9_]+\|", block)) + if uses_rich: + _push_issue(warnings, line, 'echarts-rich-label-formatter', + 'echarts label.formatter 使用 rich 模板时建议改用对象写法或 string formatter', dmap) + + +# ---- lifecycle scan (replaces AST ExportNamedDeclaration / ClassMethod) ---- + +_EXPORT_FN_RE = re.compile(r'\bexport\s+function\s+([A-Za-z][\w]*)\s*\(') +_LIFECYCLE_DECL_RE = re.compile( + r'(?:^|[\s;{])(componentDidMount|componentWillUnmount)\s*\(') + + +def _scan_lifecycle(source: str, errors: list[dict], dmap: dict[int, set[str]]) -> None: + for m in _EXPORT_FN_RE.finditer(source): + name = m.group(1) + line = source.count('\n', 0, m.start()) + 1 + if name.lower() == 'didmount' and name != 'didMount': + _push_issue(errors, line, 'lifecycle-case', + f'生命周期函数大小写错误:{name} 应为 didMount', dmap) + elif name.lower() == 'didunmount' and name != 'didUnmount': + _push_issue(errors, line, 'lifecycle-case', + f'生命周期函数大小写错误:{name} 应为 didUnmount', dmap) + if name in ('componentDidMount', 'componentWillUnmount'): + expected = 'didMount' if name == 'componentDidMount' else 'didUnmount' + _push_issue(errors, line, 'react-lifecycle-method', + f'宜搭页面不使用 React 生命周期:{name} 应为 {expected}', dmap) + # 类方法 / 对象方法形式(避免重复行) + for m in _LIFECYCLE_DECL_RE.finditer(source): + name = m.group(1) + line = source.count('\n', 0, m.start()) + 1 + expected = 'didMount' if name == 'componentDidMount' else 'didUnmount' + _push_issue(errors, line, 'react-lifecycle-method', + f'宜搭页面不使用 React 生命周期:{name} 应为 {expected}', dmap) + + +# ---- jsx element scan (replaces AST JSXOpeningElement) ---- + +# JSX 起始标签匹配:必须自己处理 brace/quote 平衡,避免 `=>` 中的 `>` 被误当作 +# 标签结束符(这是 [^<>] 简化正则的固有缺陷)。 +_JSX_TAG_NAME_RE = re.compile(r'<([a-zA-Z][\w.\-]*)') +# 在属性串内找事件属性 onXxx= +_EVENT_ATTR_RE = re.compile(r'\b(on[A-Za-z]\w*)\s*=\s*(\{[^{}]*(?:\{[^{}]*\}[^{}]*)*\}|"[^"]*"|\'[^\']*\')') +_LOWER_EVENT_RE = re.compile(r'\bon[a-z]\w+\b') + + +def _iter_jsx_open_tags(source: str): + """逐个 yield JSX 起始标签 `(start_pos, tag_name, attr_str)`。 + + 自己跟踪 `{}` 深度和字符串边界,避免 `() => x` 中的 `>` 被误当作标签结束。 + """ + n = len(source) + i = 0 + while i < n: + m = _JSX_TAG_NAME_RE.match(source, i) + if not m: + i += 1 + continue + tag_name = m.group(1) + j = m.end() + brace = 0 + quote: str | None = None + end_pos = -1 + while j < n: + ch = source[j] + nxt = source[j + 1] if j + 1 < n else '' + if quote: + if ch == '\\': + j += 2 + continue + if ch == quote: + quote = None + j += 1 + continue + if ch in ("'", '"', '`'): + quote = ch + j += 1 + continue + if ch == '{': + brace += 1 + elif ch == '}': + brace -= 1 + elif ch == '<' and brace == 0: + # 嵌套标签,提前结束当前扫描 + break + elif ch == '>' and brace == 0: + end_pos = j + break + j += 1 + if end_pos < 0: + i = m.end() + continue + attr_str = source[m.end():end_pos] + # 兼容自闭合: 的最后 / 落在 attr_str 末尾,去掉 + if attr_str.endswith('/'): + attr_str = attr_str[:-1] + yield (m.start(), tag_name, attr_str) + i = end_pos + 1 + + +def _attrs_split(attr_str: str) -> dict[str, str]: + """粗略拆解属性串,返回 {name: rawValue}。仅用于 button 缺 handler 检测。""" + if not attr_str: + return {} + result: dict[str, str] = {} + # 匹配 name="..." name='...' name={...} name (布尔属性) + pattern = re.compile( + r'([a-zA-Z][\w-]*)(?:\s*=\s*("[^"]*"|\'[^\']*\'|\{[^{}]*(?:\{[^{}]*\}[^{}]*)*\}))?') + for m in pattern.finditer(attr_str): + name = m.group(1) + value = m.group(2) or '' + result[name] = value + return result + + +def _is_interactive_button(attrs: dict[str, str]) -> bool: + if 'disabled' in attrs or 'aria-disabled' in attrs: + return True + btn_type = attrs.get('type', '').strip('"\'').lower() + if btn_type == 'submit': + return True + for k in ('onClick', 'onMouseDown', 'onKeyDown'): + if k in attrs: + return True + return False + + +def _canonical_event_name(name: str) -> str: + if name in EVENT_NAME_ALIASES: + return EVENT_NAME_ALIASES[name] + if not name or len(name) <= 2: + return name + return 'on' + name[2].upper() + name[3:] + + +_BARE_HANDLER_IDENT_RE = re.compile(r'^(?:handle|on)[A-Z]\w*$') +_THIS_OR_SELF_MEMBER_RE = re.compile(r'^(?:this|self)\.[A-Za-z_$][\w$]*$') +_BIND_THIS_RE = re.compile(r'\.bind\s*\(\s*this\s*\)\s*$') + + +def _expr_is_bare_handler_ref(expr: str) -> bool: + expr = expr.strip() + if _THIS_OR_SELF_MEMBER_RE.match(expr): + return True + if _BARE_HANDLER_IDENT_RE.match(expr): + return True + return False + + +def _arrow_returns_bare_handler(value: str) -> bool: + """识别 {() => this.foo} / {(a) => handleX} / {() => { this.foo; }} 这类无效箭头。""" + inner = value.strip() + if inner.startswith('{') and inner.endswith('}'): + inner = inner[1:-1].strip() + # 匹配箭头函数:(...) => body 或 ident => body + m = re.match(r'(?:\([^)]*\)|[A-Za-z_$][\w$]*)\s*=>\s*(.+)$', inner, re.S) + if not m: + return False + body = m.group(1).strip() + if body.startswith('{') and body.endswith('}'): + body = body[1:-1].strip().rstrip(';').strip() + # 多语句:取最后一个表达式判断 + parts = [p.strip() for p in re.split(r';|\n', body) if p.strip()] + return any(_expr_is_bare_handler_ref(p) for p in parts) + return _expr_is_bare_handler_ref(body) + + +# 复用行扫描的 message 模板,确保和 _DIRECT_METHOD_RE/_BIND_INLINE_RE 触发的告警 +# 内容完全一致 → _push_issue 的 (line, rule, message) 去重生效。 +_MSG_DIRECT_METHOD = 'onXxx={this.foo} 会导致 this 上下文丢失,请改用箭头函数' +_MSG_BIND_THIS = 'onXxx={fn.bind(this)} 在宜搭运行时不稳定,请改用箭头函数' + + +def _scan_jsx_elements(source: str, errors: list[dict], warnings: list[dict], + dmap: dict[int, set[str]]) -> None: + for start_pos, tag, attr_str in _iter_jsx_open_tags(source): + line = source.count('\n', 0, start_pos) + 1 + is_lower_tag = tag[0].islower() + attrs = _attrs_split(attr_str) + + if is_lower_tag and tag == 'button' and not _is_interactive_button(attrs): + _push_issue(errors, line, 'button-missing-handler', + '