diff --git a/CHANGELOG.md b/CHANGELOG.md index 8f51e827..8f38585d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,10 +6,27 @@ The format is inspired by [Keep a Changelog](https://keepachangelog.com/) and th ## [Unreleased] +## [1.0.55-beta.6] - 2026-07-29 + +This beta packages PRs #621, #676, #757, #815, and #816, validating the Wukong +capability and multi-Skill synchronization, declarative command and Schema +delivery, hardened Chat shortcuts, external contact resolution, and Agent +product identity on top of the `v1.0.55-beta.5` baseline. + +### Added + +- **Wukong capability and multi-Skill synchronization** (#621) — ports roughly 30 reviewed leaf commands into the open-source CLI across Drive, Doc, Sheet, and Chat, including in-place text-emotion updates, Drive version and permission operations, document styling, and Sheet comment/version/formula verification. The bundled multi-Skill framework is reorganized into progressive product references and routing guidance while retaining current open-source command, response, safety, and Runtime Schema contracts. +- **Declarative leaf commands and unified metadata delivery** (#676) — adds the reusable `LeafSpec` command framework and migrates 27 DevApp commands without changing their paths or flags. Runtime consumers now resolve identity, safety, and selection through one embedded Catalog-backed API, and guarded Help output publishes the command's safety/confirmation annotation. +- **Agent product identity** (#816) — adds the optional `DWS_AGENT_PRODUCT` override for the existing HTTP `claw-type` header while preserving each edition's default when unset. Product and runtime labels are caller-declared signals, not authentication credentials; services must validate supported values and must not grant access solely from them. The override does not change the separate IM message-display `clawType` parameter controlled by the edition and `--ai-tag`. + +### Changed + +- **Reviewed Chat shortcut delivery** (#815) — publishes 88 currently available Chat shortcuts after real-business validation, keeps three confirmed lower-service failures unavailable, strengthens semantic availability and dry-run contracts, and adds safe message-resource download plus group-member listing. Conversation filtering, IM routing/reporting, and member mute resolution are aligned with the validated backend identities. +- **Agent identity label hardening** (#816) — limits `DWS_AGENT_PRODUCT` and `DWS_AGENT_HOST` to 64 ASCII bytes, trims only surrounding ASCII spaces and tabs, and rejects other control or Unicode whitespace. QwenWork integrations should report the two dimensions separately as `DWS_AGENT_PRODUCT=qwenwork` plus `DWS_AGENT_HOST=cloud` or `desktop`; previously used combined Host labels such as `qwenwork_cloud` remain syntactically valid for compatibility. + ### Fixed -- **Name→ID resolution kept external contacts** — the shared contact resolver (`chat +dm`, `+broadcast`, …) no longer drops `search_contact_by_key_word` rows that carry only an `openDingTalkId` (external / cross-org contacts have an empty `userId`), so those people are found instead of reported missing or collapsed into a wrong single match; the display name also falls back through `nick`/`showName`/`flowerName`/`staffName`/`userName`. -- **`chat +messages-resource-url` flag aliases** — the media download-URL shortcut now accepts `--msg-id` / `--open-message-id` as aliases for `--message-id` (matching the `openMessageId`/`msgId` output field), so agents chaining from a message list no longer hit "unknown flag". +- **External-contact and message-resource chaining** (#757) — the shared name-to-ID resolver keeps external or cross-organization contacts that expose only `openDingTalkId`, applies reviewed display-name fallbacks, and preserves organization-only filtering for commands that require `userId`. `chat +messages-resource-url` now accepts `--msg-id` and `--open-message-id` as aliases for `--message-id`, matching message-list response fields. ## [1.0.55-beta.5] - 2026-07-28 diff --git a/docs/reference.md b/docs/reference.md index 2c05ccdb..1cf4555e 100644 --- a/docs/reference.md +++ b/docs/reference.md @@ -5,7 +5,8 @@ | Variable | Purpose / 用途 | |---------|---------| | `DWS_CONFIG_DIR` | Override default config directory / 覆盖默认配置目录 | -| `DWS_AGENT_HOST` | Optional Agent host observation label sent as `x-dws-agent-host` (for example `qwenwork_cloud`). Values are trimmed and must match `^[a-z0-9][a-z0-9_-]*$`; unset values are omitted. Used only for logs and BI, never for authentication or routing. / 可选 Agent 宿主观测标识,经裁剪并校验后作为 `x-dws-agent-host` 发送;仅用于日志与 BI,不参与鉴权或路由 | +| `DWS_AGENT_PRODUCT` | Optional, caller-declared Agent product sent through the existing HTTP `claw-type` header (for example `qwenwork`). Surrounding ASCII spaces/tabs are trimmed; the remaining value must be at most 64 bytes and match `^[A-Za-z0-9][A-Za-z0-9_-]*$`. Unset or empty values preserve the edition default (`openClaw` in the open-source build). / 可选、由调用方声明的 Agent 产品标识,经校验后覆盖 HTTP `claw-type` 请求头;未设置或为空时保持当前发行版默认值 | +| `DWS_AGENT_HOST` | Optional, caller-declared Agent runtime form sent as `x-dws-agent-host` (for example `cloud` or `desktop`). Surrounding ASCII spaces/tabs are trimmed; the remaining value must be at most 64 bytes and match `^[a-z0-9][a-z0-9_-]*$`; unset values are omitted. / 可选、由调用方声明的 Agent 运行形态,经校验后作为 `x-dws-agent-host` 发送;未设置时省略 | | `DWS__MCP_URL` | Override a product MCP endpoint for local development / 本地开发时覆盖指定产品 MCP endpoint | | `DWS_CLIENT_ID` | OAuth client ID (DingTalk AppKey) | | `DWS_CLIENT_SECRET` | OAuth client secret (DingTalk AppSecret) | @@ -13,6 +14,42 @@ | `DWS_ALLOW_HTTP_ENDPOINTS` | Set `1` to allow HTTP for loopback during dev / 设为 `1` 允许回环地址 HTTP,仅用于开发调试 | | `DWS_DISABLE_KEYCHAIN` | macOS only. Set `1` to skip system Keychain for the encryption key and use file-based storage (same scheme as Linux). For sandboxed runtimes (e.g. Codex App) that block Keychain APIs. Weakens at-rest protection — DEK and ciphertext live in the same directory. / 仅 macOS。设为 `1` 时跳过系统 Keychain,密钥以文件形式存储(与 Linux 一致)。用于 Keychain API 被拦截的沙盒环境(如 Codex App)。代价是 DEK 与密文同目录,保护强度低于默认方案 | +### Agent Product and Host trust model / Agent 产品与运行形态的信任模型 + +`DWS_AGENT_PRODUCT` and `DWS_AGENT_HOST` are caller-declared selection and +observation signals. They are not credentials, attestations, or proof of the +calling host's identity. DingTalk services may record them for logs/BI and may +combine supported values with separately authenticated context for PAT +compatibility, PAT identity/source derivation, or Discovery eligibility. A +service must allowlist supported values and must never grant access, bypass +authentication, or skip authorization solely because either Header claims a +particular product or runtime form. They are not used to select ordinary MCP +tool endpoints. + +`DWS_AGENT_PRODUCT` controls only the HTTP `claw-type` Header. The similarly +named `clawType` tool argument on IM send operations is an independent +message-display axis used for the “Send from AI” label. It remains controlled +by the active edition's `ClawTypeValue` and `--ai-tag`; changing +`DWS_AGENT_PRODUCT` does not change that message label. + +For QwenWork, report the dimensions separately: + +```bash +DWS_AGENT_PRODUCT=qwenwork +DWS_AGENT_HOST=cloud # or desktop +``` + +Do not set arbitrary product values that the target service has not explicitly +enabled. Older combined Host labels such as `qwenwork_cloud` still satisfy the +generic syntax for compatibility, but new integrations should use the +two-dimensional convention above. + +`DWS_AGENT_PRODUCT` 和 `DWS_AGENT_HOST` 均由调用方声明,不是认证凭据,也不能证明 +真实宿主身份。服务端可以在独立认证上下文中将受支持值用于日志/BI、PAT 兼容策略、 +PAT 身份/来源派生或 Discovery 准入,但不得仅凭这两个 Header 放权、绕过认证或跳过 +授权。HTTP `claw-type` 与 IM 消息发送参数 `clawType` 是两个独立维度;后者仅控制 +“Send from AI”展示,仍由发行版 `ClawTypeValue` 和 `--ai-tag` 决定。 + ## Exit Codes / 退出码 | Code | Category | Description / 描述 | diff --git a/internal/app/agent_host.go b/internal/app/agent_host.go index 89d7822f..37413cc3 100644 --- a/internal/app/agent_host.go +++ b/internal/app/agent_host.go @@ -24,6 +24,7 @@ import ( const ( envDWSAgentHost = "DWS_AGENT_HOST" headerDWSAgentHost = "x-dws-agent-host" + maxAgentHostBytes = 64 ) var agentHostPattern = regexp.MustCompile(`^[a-z0-9][a-z0-9_-]*$`) @@ -32,23 +33,27 @@ func init() { configmeta.Register(configmeta.ConfigItem{ Name: envDWSAgentHost, Category: configmeta.CategoryExternal, - Description: "调用 DWS 的 Agent 宿主标识;仅用于日志和 BI 观测", - Example: "qwenwork_cloud", + Description: "调用 DWS 的 Agent 运行形态标识;服务端可结合产品用于观测和 PAT 兼容策略", + Example: "cloud", }) } -// parseAgentHost normalizes and validates the caller-provided observation -// label. CR/LF is rejected before trimming so it can never be hidden at the -// edge of a value. An unset or whitespace-only value means "do not emit". +// parseAgentHost normalizes and validates the caller-declared runtime-form +// signal. Only surrounding ASCII spaces and tabs are trimmed; other control +// or Unicode whitespace remains visible to validation and is rejected. An +// unset or ASCII-whitespace-only value means "do not emit". func parseAgentHost(raw string) (string, error) { if strings.ContainsAny(raw, "\r\n") { return "", invalidAgentHostError() } - value := strings.TrimSpace(raw) + value := strings.Trim(raw, " \t") if value == "" { return "", nil } + if len(value) > maxAgentHostBytes { + return "", invalidAgentHostError() + } if !agentHostPattern.MatchString(value) { return "", invalidAgentHostError() } @@ -59,7 +64,7 @@ func invalidAgentHostError() error { // Do not include the raw environment value in the error: it is an // untrusted caller-controlled string and may contain sensitive data. return apperrors.NewValidation( - "DWS_AGENT_HOST must match ^[a-z0-9][a-z0-9_-]*$", + "DWS_AGENT_HOST must be at most 64 bytes and match ^[a-z0-9][a-z0-9_-]*$", apperrors.WithReason("invalid_agent_host"), ) } diff --git a/internal/app/agent_host_test.go b/internal/app/agent_host_test.go index 4477e966..38c5e6a0 100644 --- a/internal/app/agent_host_test.go +++ b/internal/app/agent_host_test.go @@ -21,6 +21,7 @@ import ( authpkg "github.com/DingTalk-Real-AI/dingtalk-workspace-cli/internal/auth" apperrors "github.com/DingTalk-Real-AI/dingtalk-workspace-cli/internal/errors" + "github.com/DingTalk-Real-AI/dingtalk-workspace-cli/pkg/agentproduct" "github.com/DingTalk-Real-AI/dingtalk-workspace-cli/pkg/edition" "github.com/spf13/cobra" ) @@ -32,12 +33,14 @@ func TestParseAgentHost(t *testing.T) { want string }{ {name: "unset", raw: "", want: ""}, - {name: "whitespace only", raw: " \t\u3000", want: ""}, - {name: "cloud", raw: "qwenwork_cloud", want: "qwenwork_cloud"}, - {name: "desktop", raw: "qwenwork_desktop", want: "qwenwork_desktop"}, - {name: "trim", raw: " \tqwenwork_cloud\t ", want: "qwenwork_cloud"}, + {name: "ASCII whitespace only", raw: " \t ", want: ""}, + {name: "cloud", raw: "cloud", want: "cloud"}, + {name: "desktop", raw: "desktop", want: "desktop"}, + {name: "legacy combined label remains valid", raw: "qwenwork_cloud", want: "qwenwork_cloud"}, + {name: "trim", raw: " \tcloud\t ", want: "cloud"}, {name: "generic", raw: "host-2_alpha", want: "host-2_alpha"}, {name: "leading digit", raw: "2nd_host", want: "2nd_host"}, + {name: "maximum length", raw: strings.Repeat("a", maxAgentHostBytes), want: strings.Repeat("a", maxAgentHostBytes)}, } for _, tc := range valid { t.Run(tc.name, func(t *testing.T) { @@ -64,6 +67,12 @@ func TestParseAgentHost(t *testing.T) { {name: "leading dash", raw: "-qwenwork"}, {name: "leading underscore", raw: "_qwenwork"}, {name: "control character", raw: "qwenwork\x00cloud"}, + {name: "vertical tab", raw: "\vcloud"}, + {name: "form feed", raw: "cloud\f"}, + {name: "next line", raw: "cloud\u0085"}, + {name: "non-breaking space", raw: "\u00a0cloud"}, + {name: "ideographic space", raw: "cloud\u3000"}, + {name: "too long", raw: strings.Repeat("a", maxAgentHostBytes+1)}, } for _, tc := range invalid { t.Run(tc.name, func(t *testing.T) { @@ -91,6 +100,7 @@ func TestParseAgentHost(t *testing.T) { func TestResolveIdentityHeadersAddsAgentHostBeforeEditionMerge(t *testing.T) { t.Setenv("DWS_CONFIG_DIR", t.TempDir()) t.Setenv(envDWSAgentHost, " qwenwork_desktop ") + t.Setenv(agentproduct.EnvName, "") t.Setenv(envDWSChannel, "channel-test") t.Setenv(envDingtalkAgent, "agent-test") t.Setenv(authpkg.AgentCodeEnv, "agent-code-test") diff --git a/internal/app/agent_product.go b/internal/app/agent_product.go new file mode 100644 index 00000000..78a0b91f --- /dev/null +++ b/internal/app/agent_product.go @@ -0,0 +1,74 @@ +// Copyright 2026 Alibaba Group +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package app + +import ( + apperrors "github.com/DingTalk-Real-AI/dingtalk-workspace-cli/internal/errors" + "github.com/DingTalk-Real-AI/dingtalk-workspace-cli/pkg/agentproduct" + "github.com/DingTalk-Real-AI/dingtalk-workspace-cli/pkg/configmeta" + "github.com/DingTalk-Real-AI/dingtalk-workspace-cli/pkg/edition" +) + +func init() { + configmeta.Register(configmeta.ConfigItem{ + Name: agentproduct.EnvName, + Category: configmeta.CategoryExternal, + Description: "调用方声明的 Agent 产品标识;覆盖 HTTP claw-type,但不是认证凭据", + DefaultValue: "由当前发行版决定", + Example: "qwenwork", + }) +} + +// parseAgentProduct converts the reusable package error into the CLI's stable +// structured validation error without exposing the untrusted raw value. +func parseAgentProduct(raw string) (string, error) { + value, err := agentproduct.Parse(raw) + if err != nil { + return "", invalidAgentProductError() + } + return value, nil +} + +func invalidAgentProductError() error { + return apperrors.NewValidation( + "DWS_AGENT_PRODUCT must be at most 64 bytes and match ^[A-Za-z0-9][A-Za-z0-9_-]*$", + apperrors.WithReason("invalid_agent_product"), + ) +} + +// resolveEffectiveAgentProduct resolves the request-header identity with one +// shared precedence rule: a valid non-empty runtime override wins, otherwise +// the edition's MergeHeaders value wins, otherwise the OSS default is used. +// Invalid runtime input falls back here for library callers that bypass root +// validation; normal CLI execution rejects it before network access. +func resolveEffectiveAgentProduct(headers map[string]string) string { + fallback := edition.DefaultOSSClawType + if value := headers[agentproduct.HeaderName]; value != "" { + fallback = value + } + value, err := agentproduct.ResolveFromEnv(fallback) + if err != nil { + return fallback + } + return value +} + +func applyAgentProductOverride(headers map[string]string) map[string]string { + value := resolveEffectiveAgentProduct(headers) + if headers == nil { + headers = make(map[string]string) + } + headers[agentproduct.HeaderName] = value + return headers +} diff --git a/internal/app/agent_product_test.go b/internal/app/agent_product_test.go new file mode 100644 index 00000000..939553d5 --- /dev/null +++ b/internal/app/agent_product_test.go @@ -0,0 +1,267 @@ +// Copyright 2026 Alibaba Group +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package app + +import ( + "errors" + "io" + "strings" + "testing" + + authpkg "github.com/DingTalk-Real-AI/dingtalk-workspace-cli/internal/auth" + apperrors "github.com/DingTalk-Real-AI/dingtalk-workspace-cli/internal/errors" + "github.com/DingTalk-Real-AI/dingtalk-workspace-cli/pkg/agentproduct" + "github.com/DingTalk-Real-AI/dingtalk-workspace-cli/pkg/edition" + "github.com/spf13/cobra" +) + +func TestUnsetAgentProductKeepsOpenSourceDefault(t *testing.T) { + t.Setenv("DWS_CONFIG_DIR", t.TempDir()) + t.Setenv(agentproduct.EnvName, "") + + headers := resolveIdentityHeaders() + if got := headers[agentproduct.HeaderName]; got != edition.DefaultOSSClawType { + t.Fatalf("%s = %q, want %q", agentproduct.HeaderName, got, edition.DefaultOSSClawType) + } +} + +func TestParseAgentProductReturnsStableValidationError(t *testing.T) { + const invalidValue = "DO_NOT ECHO" + + got, err := parseAgentProduct(invalidValue) + if got != "" { + t.Fatalf("parseAgentProduct() = %q, want empty", got) + } + + var appErr *apperrors.Error + if !errors.As(err, &appErr) { + t.Fatalf("parseAgentProduct() error type = %T, want *errors.Error", err) + } + if appErr.Category != apperrors.CategoryValidation { + t.Fatalf("category = %q, want validation", appErr.Category) + } + if appErr.Reason != "invalid_agent_product" { + t.Fatalf("reason = %q, want invalid_agent_product", appErr.Reason) + } + if strings.Contains(err.Error(), invalidValue) { + t.Fatalf("error must not echo invalid value: %v", err) + } +} + +func TestResolveIdentityHeadersAgentProductPrecedence(t *testing.T) { + t.Setenv("DWS_CONFIG_DIR", t.TempDir()) + + oldEdition := edition.Get() + t.Cleanup(func() { edition.Override(oldEdition) }) + edition.Override(&edition.Hooks{ + MergeHeaders: func(headers map[string]string) map[string]string { + headers[agentproduct.HeaderName] = "wukong" + headers["x-edition-header"] = "preserved" + return headers + }, + EnterpriseCredentialHeaders: func(headers map[string]string) map[string]string { + headers[agentproduct.HeaderName] = "enterprise-default" + headers["x-enterprise-header"] = "preserved" + return headers + }, + }) + + t.Run("unset keeps edition default", func(t *testing.T) { + t.Setenv(agentproduct.EnvName, "") + headers := resolveIdentityHeaders() + if got := headers[agentproduct.HeaderName]; got != "wukong" { + t.Fatalf("%s = %q, want wukong", agentproduct.HeaderName, got) + } + }) + + t.Run("valid override is final", func(t *testing.T) { + t.Setenv(agentproduct.EnvName, " qwenwork ") + headers := resolveIdentityHeaders() + if got := headers[agentproduct.HeaderName]; got != "qwenwork" { + t.Fatalf("%s = %q, want qwenwork", agentproduct.HeaderName, got) + } + if got := headers["x-edition-header"]; got != "preserved" { + t.Fatalf("edition header = %q, want preserved", got) + } + if got := headers["x-enterprise-header"]; got != "preserved" { + t.Fatalf("enterprise header = %q, want preserved", got) + } + if got := headers["x-dingtalk-source"]; got != "github" { + t.Fatalf("x-dingtalk-source = %q, want github", got) + } + }) + + t.Run("invalid library input falls back to edition", func(t *testing.T) { + t.Setenv(agentproduct.EnvName, "qwen work") + headers := resolveIdentityHeaders() + if got := headers[agentproduct.HeaderName]; got != "wukong" { + t.Fatalf("%s = %q, want wukong", agentproduct.HeaderName, got) + } + }) +} + +func TestApplyAgentProductOverrideAllocatesHeaders(t *testing.T) { + t.Setenv(agentproduct.EnvName, "qwenwork") + + headers := applyAgentProductOverride(nil) + if got := headers[agentproduct.HeaderName]; got != "qwenwork" { + t.Fatalf("%s = %q, want qwenwork", agentproduct.HeaderName, got) + } +} + +func TestRootRejectsInvalidAgentProductBeforeEditionHook(t *testing.T) { + t.Setenv("DWS_CONFIG_DIR", t.TempDir()) + const invalidValue = "DO_NOT ECHO" + t.Setenv(agentproduct.EnvName, invalidValue) + + oldEdition := edition.Get() + t.Cleanup(func() { edition.Override(oldEdition) }) + + hookCalled := false + edition.Override(&edition.Hooks{ + AfterPersistentPreRun: func(_ *cobra.Command, _ []string) error { + hookCalled = true + return nil + }, + }) + + root := NewRootCommand() + root.SetOut(io.Discard) + root.SetErr(io.Discard) + root.SetArgs([]string{"version"}) + err := root.Execute() + if err == nil { + t.Fatal("root command accepted invalid DWS_AGENT_PRODUCT") + } + if hookCalled { + t.Fatal("edition AfterPersistentPreRun ran before DWS_AGENT_PRODUCT validation") + } + + var appErr *apperrors.Error + if !errors.As(err, &appErr) { + t.Fatalf("root error type = %T, want *errors.Error", err) + } + if appErr.Category != apperrors.CategoryValidation || appErr.Reason != "invalid_agent_product" { + t.Fatalf("root error = category %q reason %q", appErr.Category, appErr.Reason) + } + if strings.Contains(err.Error(), invalidValue) { + t.Fatalf("root error must not echo invalid value: %v", err) + } +} + +func TestEffectiveClawTypeDoesNotInvokeEnterpriseCredentialHeaders(t *testing.T) { + oldEdition := edition.Get() + t.Cleanup(func() { edition.Override(oldEdition) }) + + hookCalled := false + edition.Override(&edition.Hooks{ + MergeHeaders: func(headers map[string]string) map[string]string { + headers[agentproduct.HeaderName] = "wukong" + return headers + }, + EnterpriseCredentialHeaders: func(headers map[string]string) map[string]string { + hookCalled = true + headers[agentproduct.HeaderName] = "enterprise-default" + return headers + }, + }) + + t.Setenv(agentproduct.EnvName, "") + if got := effectiveClawType(); got != "wukong" { + t.Fatalf("effectiveClawType() = %q, want wukong", got) + } + if hookCalled { + t.Fatal("EnterpriseCredentialHeaders hook ran during PAT error serialization") + } +} + +func TestAgentProductHeaderIsSeparateFromMessageClawType(t *testing.T) { + t.Setenv("DWS_CONFIG_DIR", t.TempDir()) + t.Setenv(agentproduct.EnvName, "qwenwork") + + oldEdition := edition.Get() + t.Cleanup(func() { edition.Override(oldEdition) }) + edition.Override(&edition.Hooks{ + ClawTypeValue: "message-brand", + MergeHeaders: func(headers map[string]string) map[string]string { + headers[agentproduct.HeaderName] = "wukong" + return headers + }, + }) + + if got := resolveIdentityHeaders()[agentproduct.HeaderName]; got != "qwenwork" { + t.Fatalf("HTTP %s = %q, want qwenwork", agentproduct.HeaderName, got) + } + if got := edition.ClawType(); got != "message-brand" { + t.Fatalf("message clawType = %q, want message-brand", got) + } +} + +func TestResolveIdentityHeadersRestoresAgentProductAfterNilCredentialHeaders(t *testing.T) { + t.Setenv("DWS_CONFIG_DIR", t.TempDir()) + t.Setenv(agentproduct.EnvName, "qwenwork") + + oldEdition := edition.Get() + t.Cleanup(func() { edition.Override(oldEdition) }) + + credentialHookCalled := false + edition.Override(&edition.Hooks{ + MergeHeaders: func(headers map[string]string) map[string]string { + headers[agentproduct.HeaderName] = "wukong" + return headers + }, + EnterpriseCredentialHeaders: func(map[string]string) map[string]string { + credentialHookCalled = true + return nil + }, + }) + + headers := resolveIdentityHeaders() + if !credentialHookCalled { + t.Fatal("EnterpriseCredentialHeaders hook was not called") + } + if got := headers[agentproduct.HeaderName]; got != "qwenwork" { + t.Fatalf("%s = %q, want qwenwork", agentproduct.HeaderName, got) + } +} + +func TestEffectiveClawTypeUsesAgentProductOverride(t *testing.T) { + oldEdition := edition.Get() + t.Cleanup(func() { edition.Override(oldEdition) }) + edition.Override(&edition.Hooks{ + MergeHeaders: func(headers map[string]string) map[string]string { + headers[agentproduct.HeaderName] = "wukong" + return headers + }, + }) + + t.Setenv(agentproduct.EnvName, "qwenwork") + if got := effectiveClawType(); got != "qwenwork" { + t.Fatalf("effectiveClawType() = %q, want qwenwork", got) + } + t.Setenv(authpkg.AgentCodeEnv, "agent-code") + if got := apperrors.HostControlBlock()["clawType"]; got != "qwenwork" { + t.Fatalf("hostControl.clawType = %q, want qwenwork", got) + } + + t.Setenv(agentproduct.EnvName, "") + if got := effectiveClawType(); got != "wukong" { + t.Fatalf("effectiveClawType() = %q, want wukong", got) + } + + t.Setenv(agentproduct.EnvName, "invalid product") + if got := effectiveClawType(); got != "wukong" { + t.Fatalf("effectiveClawType() with invalid env = %q, want wukong", got) + } +} diff --git a/internal/app/app_small_coverage_test.go b/internal/app/app_small_coverage_test.go index a81497b7..2da69c02 100644 --- a/internal/app/app_small_coverage_test.go +++ b/internal/app/app_small_coverage_test.go @@ -17,6 +17,7 @@ import ( "github.com/DingTalk-Real-AI/dingtalk-workspace-cli/internal/executor" "github.com/DingTalk-Real-AI/dingtalk-workspace-cli/internal/plugin" "github.com/DingTalk-Real-AI/dingtalk-workspace-cli/internal/transport" + "github.com/DingTalk-Real-AI/dingtalk-workspace-cli/pkg/agentproduct" "github.com/DingTalk-Real-AI/dingtalk-workspace-cli/pkg/edition" "github.com/spf13/cobra" ) @@ -346,6 +347,7 @@ func TestCrossPlatformCoverageOverlayRecoveryHostAndHelperRemainingCoverage(t *t t.Fatal("host control enabled without agent code") } t.Setenv(authpkg.AgentCodeEnv, "agent") + t.Setenv(agentproduct.EnvName, "") edition.Override(&edition.Hooks{MergeHeaders: func(headers map[string]string) map[string]string { return headers }}) if got := hostControlProviderFromEnv(); got != edition.DefaultOSSClawType { t.Fatalf("default claw type = %q", got) diff --git a/internal/app/pat_auth_retry.go b/internal/app/pat_auth_retry.go index 93c86344..14e34661 100644 --- a/internal/app/pat_auth_retry.go +++ b/internal/app/pat_auth_retry.go @@ -570,7 +570,8 @@ func handlePatAuthCheck( // or when flowId is absent, the CLI returns machine-readable JSON to // stderr and leaves UI/polling/retry to the host. `claw-type` is NOT // used for this decision — it is only forwarded on the wire via - // edition.MergeHeaders and surfaced in hostControl for traceability. + // the edition default / DWS_AGENT_PRODUCT override and surfaced in + // hostControl for traceability. if hostOwnedPAT || patData.Data.FlowID == "" { if hostOwnedPAT { return executor.Result{}, &apperrors.PATError{RawJSON: enrichPATErrorForHostControl(patErr.RawJSON)} diff --git a/internal/app/pat_auth_retry_test.go b/internal/app/pat_auth_retry_test.go index 5886a5e6..07696f81 100644 --- a/internal/app/pat_auth_retry_test.go +++ b/internal/app/pat_auth_retry_test.go @@ -30,6 +30,7 @@ import ( apperrors "github.com/DingTalk-Real-AI/dingtalk-workspace-cli/internal/errors" "github.com/DingTalk-Real-AI/dingtalk-workspace-cli/internal/executor" "github.com/DingTalk-Real-AI/dingtalk-workspace-cli/internal/pat" + "github.com/DingTalk-Real-AI/dingtalk-workspace-cli/pkg/agentproduct" ) func TestIsPatScopeError_MissingScope(t *testing.T) { @@ -1006,10 +1007,11 @@ func TestHandlePatAuthCheck_HostControlledFlowIDPassthrough(t *testing.T) { t.Setenv("DWS_CONFIG_DIR", tmpDir) // Host-owned decision: driven ONLY by DINGTALK_DWS_AGENTCODE. // DINGTALK_AGENT is set to demonstrate it does NOT leak into - // hostControl.clawType — the open-source build pins that to the - // literal edition.DefaultOSSClawType value ("openClaw"). + // hostControl.clawType. With no DWS_AGENT_PRODUCT override the + // open-source edition default remains "openClaw". t.Setenv(authpkg.AgentCodeEnv, "agt-sales") t.Setenv("DINGTALK_AGENT", "sales-copilot") + t.Setenv(agentproduct.EnvName, "") mock := &mockRunner{ runFunc: func(ctx context.Context, inv executor.Invocation) (executor.Result, error) { @@ -1053,7 +1055,7 @@ func TestHandlePatAuthCheck_HostControlledFlowIDPassthrough(t *testing.T) { } hostControl, _ := data["hostControl"].(map[string]any) if got, _ := hostControl["clawType"].(string); got != "openClaw" { - t.Fatalf("hostControl.clawType = %q, want openClaw (hard-wired by open-source edition)", got) + t.Fatalf("hostControl.clawType = %q, want openClaw (open-source edition default)", got) } if got, _ := hostControl["callbackOwner"].(string); got != "host" { t.Fatalf("hostControl.callbackOwner = %q, want host", got) diff --git a/internal/app/pat_hostcontrol_wire.go b/internal/app/pat_hostcontrol_wire.go index 0c8f7872..8b21a472 100644 --- a/internal/app/pat_hostcontrol_wire.go +++ b/internal/app/pat_hostcontrol_wire.go @@ -27,11 +27,11 @@ import ( // // Decision rule: // - Host-owned is triggered iff DINGTALK_DWS_AGENTCODE is non-empty. -// - When triggered, `clawType` in the emitted hostControl block MUST -// be the exact value the CLI actually injects on the wire into the -// `claw-type` HTTP header. The open-source build pins that to -// edition.DefaultOSSClawType ("openClaw") unconditionally — there -// is no per-spawn env override. +// - When triggered, `clawType` in the emitted hostControl block MUST be the +// exact value the CLI actually injects on the wire. Each edition supplies +// its existing default and an optional valid DWS_AGENT_PRODUCT overrides +// it. Invalid input falls back here for library compatibility; root command +// execution rejects it before network access. // - When DINGTALK_DWS_AGENTCODE is empty the provider returns "" so // HostControlBlock yields nil and no hostControl block is emitted. func init() { @@ -48,15 +48,16 @@ func hostControlProviderFromEnv() string { return effectiveClawType() } -// effectiveClawType returns the literal value that MergeHeaders will -// inject into outbound `claw-type` headers. Going through the edition -// hook (instead of a hard-coded constant) keeps this site correct for -// downstream editions that override MergeHeaders. +// effectiveClawType resolves the literal value injected into outbound +// `claw-type` headers without invoking credential hooks from PAT error +// serialization. MergeHeaders implementations that set claw-type must satisfy +// the edition contract that this value is independent of the base map. func effectiveClawType() string { - if h := edition.Get(); h != nil && h.MergeHeaders != nil { - if v, ok := h.MergeHeaders(map[string]string{})["claw-type"]; ok && v != "" { - return v + headers := make(map[string]string) + if h := edition.Get(); h != nil { + if h.MergeHeaders != nil { + headers = h.MergeHeaders(headers) } } - return edition.DefaultOSSClawType + return resolveEffectiveAgentProduct(headers) } diff --git a/internal/app/root.go b/internal/app/root.go index a929a0db..8f407aae 100644 --- a/internal/app/root.go +++ b/internal/app/root.go @@ -41,6 +41,7 @@ import ( "github.com/DingTalk-Real-AI/dingtalk-workspace-cli/internal/plugin" "github.com/DingTalk-Real-AI/dingtalk-workspace-cli/internal/recovery" "github.com/DingTalk-Real-AI/dingtalk-workspace-cli/internal/shortcut/usage" + "github.com/DingTalk-Real-AI/dingtalk-workspace-cli/pkg/agentproduct" "github.com/DingTalk-Real-AI/dingtalk-workspace-cli/pkg/cmdutil" "github.com/DingTalk-Real-AI/dingtalk-workspace-cli/pkg/edition" "github.com/DingTalk-Real-AI/dingtalk-workspace-cli/pkg/mcptypes" @@ -353,12 +354,15 @@ func newRootCommandWithEngine(rootCtx context.Context, engine *pipeline.Engine, return cmd.Help() }, PersistentPreRunE: func(cmd *cobra.Command, args []string) error { - // Validate the optional observation label before any edition hook + // Validate caller-provided identity labels before any edition hook // or command network activity can run. Header-only library callers // use the best-effort path in resolveIdentityHeaders instead. if _, err := parseAgentHost(os.Getenv(envDWSAgentHost)); err != nil { return err } + if _, err := parseAgentProduct(os.Getenv(agentproduct.EnvName)); err != nil { + return err + } authpkg.SetRuntimeProfile(flags.Profile) // Apply OAuth credential overrides from CLI flags (highest priority). diff --git a/internal/app/runner.go b/internal/app/runner.go index ae9a7f95..dee0b5fc 100644 --- a/internal/app/runner.go +++ b/internal/app/runner.go @@ -36,6 +36,7 @@ import ( "github.com/DingTalk-Real-AI/dingtalk-workspace-cli/internal/logging" "github.com/DingTalk-Real-AI/dingtalk-workspace-cli/internal/safety" "github.com/DingTalk-Real-AI/dingtalk-workspace-cli/internal/transport" + "github.com/DingTalk-Real-AI/dingtalk-workspace-cli/pkg/agentproduct" "github.com/DingTalk-Real-AI/dingtalk-workspace-cli/pkg/configmeta" "github.com/DingTalk-Real-AI/dingtalk-workspace-cli/pkg/edition" ) @@ -1004,10 +1005,10 @@ func resolveIdentityHeaders() map[string]string { // Inject environment variable based headers for MCP gateway tracking. // DINGTALK_AGENT, if set by the caller, is forwarded verbatim as the - // x-dingtalk-agent header. It does NOT influence claw-type (which the - // open-source edition pins to edition.DefaultOSSClawType via the - // MergeHeaders hook below) and it does NOT influence the host-owned - // PAT decision (driven solely by DINGTALK_DWS_AGENTCODE). + // x-dingtalk-agent header. It does NOT influence claw-type (which comes + // from the edition default plus the explicit DWS_AGENT_PRODUCT override) + // and it does NOT influence the host-owned PAT decision (driven solely by + // DINGTALK_DWS_AGENTCODE). sessionID := os.Getenv(envDingtalkSessionID) if sessionID == "" { sessionID = os.Getenv(envDWSSessionID) @@ -1056,7 +1057,7 @@ func resolveIdentityHeaders() map[string]string { headers["x-dws-channel"] = v } - // DWS_AGENT_HOST is a caller-provided observation label only. Root command + // DWS_AGENT_HOST is a caller-declared runtime-form signal. Root command // execution validates it strictly in PersistentPreRunE. Library callers // that bypass the root command keep this best-effort API contract: invalid // values are omitted rather than changing the public function signature. @@ -1067,9 +1068,22 @@ func resolveIdentityHeaders() map[string]string { if fn := edition.Get().MergeHeaders; fn != nil { headers = fn(headers) } + // Resolve the Agent Product before credential injection. The credential + // hook has a separate contract and must not be able to replace the + // request identity used by PAT hostControl serialization. + headers = applyAgentProductOverride(headers) + agentProduct := headers[agentproduct.HeaderName] if fn := edition.Get().EnterpriseCredentialHeaders; fn != nil { headers = fn(headers) } + if headers == nil { + headers = make(map[string]string) + } + // DWS_AGENT_PRODUCT is the explicit caller override for the existing + // claw-type wire header. Reassert the resolved product after credential + // injection so that hook cannot alter identity. Invalid values are ignored + // on this best-effort library path; root execution rejects them earlier. + headers[agentproduct.HeaderName] = agentProduct return headers } diff --git a/internal/app/schema_shortcut_contract_test.go b/internal/app/schema_shortcut_contract_test.go index 3baf87a5..4e09f8fb 100644 --- a/internal/app/schema_shortcut_contract_test.go +++ b/internal/app/schema_shortcut_contract_test.go @@ -106,7 +106,7 @@ func TestEmbeddedShortcutProgressiveQueriesReturnCompleteContracts(t *testing.T) product := executeShortcutSchemaQuery(t, "chat") productPayload, _ := product["product"].(map[string]any) - if got, want := int(product["count"].(float64)), 125; got != want { + if got, want := int(product["count"].(float64)), 129; got != want { t.Fatalf("schema chat count = %d, want %d", got, want) } summaries := schemaContractObjectSlice(productPayload["tools"]) diff --git a/internal/cli/schema_agent_metadata/chat.json b/internal/cli/schema_agent_metadata/chat.json index ac67619d..ad3f8136 100644 --- a/internal/cli/schema_agent_metadata/chat.json +++ b/internal/cli/schema_agent_metadata/chat.json @@ -15356,6 +15356,264 @@ "用户只提供数字群号且需要 openConversationId 时" ] }, + "chat group get-mute-config": { + "agent_summary": "查询群用户禁言配置(禁言黑名单/全员禁言白名单)", + "agent_summary_source": "dws-agent-selection/chat", + "availability": "available", + "avoid_when": [ + "设置全员禁言用 chat group-mute;禁言个人用 chat group-mute-member" + ], + "confirmation": "not_required", + "effect": "read", + "effect_source": "agent-hint", + "examples": [ + "dws chat group get-mute-config --group \u003copenConversationId\u003e --format json" + ], + "field_provenance": { + "agent_summary": { + "value": "查询群用户禁言配置(禁言黑名单/全员禁言白名单)", + "source": "internal/cli/schema_hints/selection/chat.json", + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "人工审阅:对照悟空 develop 同名命令实现、Cobra Long/Example 与 Runtime 确认门禁移植;不改变命令身份、参数契约或接口绑定。", + "candidates": [ + { + "value": "查询群用户禁言配置(禁言黑名单/全员禁言白名单)", + "source": "internal/cli/schema_hints/selection/chat.json", + "precedence": "reviewed_explicit", + "selected": true, + "review_reason": "人工审阅:对照悟空 develop 同名命令实现、Cobra Long/Example 与 Runtime 确认门禁移植;不改变命令身份、参数契约或接口绑定。" + } + ] + }, + "availability": { + "value": "available", + "source": "internal/cli/schema_hints/metadata/chat.json", + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "dws-tool-metadata/chat marks this tool as reviewed", + "candidates": [ + { + "value": "available", + "source": "internal/cli/schema_hints/metadata/chat.json", + "precedence": "reviewed_explicit", + "selected": true, + "review_reason": "dws-tool-metadata/chat marks this tool as reviewed" + } + ] + }, + "avoid_when": { + "value": [ + "设置全员禁言用 chat group-mute;禁言个人用 chat group-mute-member" + ], + "source": "internal/cli/schema_hints/selection/chat.json", + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "人工审阅:对照悟空 develop 同名命令实现、Cobra Long/Example 与 Runtime 确认门禁移植;不改变命令身份、参数契约或接口绑定。", + "candidates": [ + { + "value": [ + "设置全员禁言用 chat group-mute;禁言个人用 chat group-mute-member" + ], + "source": "internal/cli/schema_hints/selection/chat.json", + "precedence": "reviewed_explicit", + "selected": true, + "review_reason": "人工审阅:对照悟空 develop 同名命令实现、Cobra Long/Example 与 Runtime 确认门禁移植;不改变命令身份、参数契约或接口绑定。" + } + ] + }, + "confirmation": { + "value": "not_required", + "source": "internal/cli/schema_hints/metadata/chat.json", + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "dws-tool-metadata/chat marks this tool as reviewed", + "candidates": [ + { + "value": "not_required", + "source": "internal/cli/schema_hints/metadata/chat.json", + "precedence": "reviewed_explicit", + "selected": true, + "review_reason": "dws-tool-metadata/chat marks this tool as reviewed" + } + ] + }, + "effect": { + "value": "read", + "source": "internal/cli/schema_hints/metadata/chat.json", + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "dws-tool-metadata/chat marks this tool as reviewed", + "candidates": [ + { + "value": "read", + "source": "internal/cli/schema_hints/metadata/chat.json", + "precedence": "reviewed_explicit", + "selected": true, + "review_reason": "dws-tool-metadata/chat marks this tool as reviewed" + } + ] + }, + "examples": { + "value": [ + "dws chat group get-mute-config --group \u003copenConversationId\u003e --format json" + ], + "source": "internal/cli/schema_hints/selection/chat.json", + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "人工审阅:对照悟空 develop 同名命令实现、Cobra Long/Example 与 Runtime 确认门禁移植;不改变命令身份、参数契约或接口绑定。", + "candidates": [ + { + "value": [ + "dws chat group get-mute-config --group \u003copenConversationId\u003e --format json" + ], + "source": "internal/cli/schema_hints/selection/chat.json", + "precedence": "reviewed_explicit", + "selected": true, + "review_reason": "人工审阅:对照悟空 develop 同名命令实现、Cobra Long/Example 与 Runtime 确认门禁移植;不改变命令身份、参数契约或接口绑定。" + } + ] + }, + "idempotency": { + "value": "idempotent", + "source": "internal/cli/schema_hints/metadata/chat.json", + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "dws-tool-metadata/chat marks this tool as reviewed", + "candidates": [ + { + "value": "idempotent", + "source": "internal/cli/schema_hints/metadata/chat.json", + "precedence": "reviewed_explicit", + "selected": true, + "review_reason": "dws-tool-metadata/chat marks this tool as reviewed" + } + ] + }, + "interface_mode": { + "value": "composite", + "source": "internal/cli/schema_hints/metadata/chat.json", + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "dws-tool-metadata/chat marks this tool as reviewed", + "candidates": [ + { + "value": "composite", + "source": "internal/cli/schema_hints/metadata/chat.json", + "precedence": "reviewed_explicit", + "selected": true, + "review_reason": "dws-tool-metadata/chat marks this tool as reviewed" + } + ] + }, + "interface_reason": { + "value": "Reviewed unpinned remote adapter: this executable CLI wrapper calls a remote helper that is absent from the pinned MCP metadata snapshot; no single pinned semantically equivalent interface_ref can represent the command.", + "source": "internal/cli/schema_hints/metadata/chat.json", + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "dws-tool-metadata/chat marks this tool as reviewed", + "candidates": [ + { + "value": "Reviewed unpinned remote adapter: this executable CLI wrapper calls a remote helper that is absent from the pinned MCP metadata snapshot; no single pinned semantically equivalent interface_ref can represent the command.", + "source": "internal/cli/schema_hints/metadata/chat.json", + "precedence": "reviewed_explicit", + "selected": true, + "review_reason": "dws-tool-metadata/chat marks this tool as reviewed" + } + ] + }, + "interface_ref": { + "value": null, + "source": "internal/cli/schema_hints/metadata/chat.json", + "precedence": "reviewed_explicit", + "resolution": "interface_disposition_matrix", + "review_reason": "final interface mode composite forbids a direct MCP interface_ref", + "candidates": [ + { + "value": null, + "source": "internal/cli/schema_hints/metadata/chat.json", + "precedence": "reviewed_explicit", + "selected": true, + "review_reason": "final interface mode composite forbids a direct MCP interface_ref" + } + ] + }, + "reviewed": { + "value": true, + "source": "internal/cli/schema_hints/metadata/chat.json", + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "dws-tool-metadata/chat marks this tool as reviewed", + "candidates": [ + { + "value": true, + "source": "internal/cli/schema_hints/metadata/chat.json", + "precedence": "reviewed_explicit", + "selected": true, + "review_reason": "dws-tool-metadata/chat marks this tool as reviewed" + }, + { + "value": true, + "source": "internal/cli/schema_hints/selection/chat.json", + "precedence": "reviewed_explicit", + "selected": false, + "review_reason": "人工审阅:对照悟空 develop 同名命令实现、Cobra Long/Example 与 Runtime 确认门禁移植;不改变命令身份、参数契约或接口绑定。" + } + ] + }, + "risk": { + "value": "low", + "source": "internal/cli/schema_hints/metadata/chat.json", + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "dws-tool-metadata/chat marks this tool as reviewed", + "candidates": [ + { + "value": "low", + "source": "internal/cli/schema_hints/metadata/chat.json", + "precedence": "reviewed_explicit", + "selected": true, + "review_reason": "dws-tool-metadata/chat marks this tool as reviewed" + } + ] + }, + "use_when": { + "value": [ + "用户说 看下群里谁被禁言/禁言配置" + ], + "source": "internal/cli/schema_hints/selection/chat.json", + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "人工审阅:对照悟空 develop 同名命令实现、Cobra Long/Example 与 Runtime 确认门禁移植;不改变命令身份、参数契约或接口绑定。", + "candidates": [ + { + "value": [ + "用户说 看下群里谁被禁言/禁言配置" + ], + "source": "internal/cli/schema_hints/selection/chat.json", + "precedence": "reviewed_explicit", + "selected": true, + "review_reason": "人工审阅:对照悟空 develop 同名命令实现、Cobra Long/Example 与 Runtime 确认门禁移植;不改变命令身份、参数契约或接口绑定。" + } + ] + } + }, + "idempotency": "idempotent", + "interface_mode": "composite", + "interface_reason": "Reviewed unpinned remote adapter: this executable CLI wrapper calls a remote helper that is absent from the pinned MCP metadata snapshot; no single pinned semantically equivalent interface_ref can represent the command.", + "reviewed": true, + "risk": "low", + "source_refs": [ + "CommandRegistry:canonical_path=chat.get_group_mute_config", + "cobra-help:dws chat group get-mute-config", + "internal/cli/schema_hints/metadata/chat.json", + "internal/cli/schema_hints/selection/chat.json", + "wukong-develop:wukong/products" + ], + "use_when": [ + "用户说 看下群里谁被禁言/禁言配置" + ] + }, "chat group invite-url": { "agent_summary": "获取指定群聊的邀请链接", "agent_summary_source": "dws-agent-selection/chat", @@ -19279,6 +19537,522 @@ "群主明确要求保留现有会话并升级为可跨组织协作的外部群,且已确认不可逆影响" ] }, + "chat group user-settings query": { + "agent_summary": "批量查询当前用户自己的群会话设置(置顶/免打扰/群昵称/群备注)", + "agent_summary_source": "dws-agent-selection/chat", + "availability": "available", + "avoid_when": [ + "管理员级群功能开关用 chat group update-settings" + ], + "confirmation": "not_required", + "effect": "read", + "effect_source": "agent-hint", + "examples": [ + "dws chat group user-settings query --groups cid1,cid2 --format json" + ], + "field_provenance": { + "agent_summary": { + "value": "批量查询当前用户自己的群会话设置(置顶/免打扰/群昵称/群备注)", + "source": "internal/cli/schema_hints/selection/chat.json", + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "人工审阅:对照悟空 develop 同名命令实现、Cobra Long/Example 与 Runtime 确认门禁移植;不改变命令身份、参数契约或接口绑定。", + "candidates": [ + { + "value": "批量查询当前用户自己的群会话设置(置顶/免打扰/群昵称/群备注)", + "source": "internal/cli/schema_hints/selection/chat.json", + "precedence": "reviewed_explicit", + "selected": true, + "review_reason": "人工审阅:对照悟空 develop 同名命令实现、Cobra Long/Example 与 Runtime 确认门禁移植;不改变命令身份、参数契约或接口绑定。" + } + ] + }, + "availability": { + "value": "available", + "source": "internal/cli/schema_hints/metadata/chat.json", + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "dws-tool-metadata/chat marks this tool as reviewed", + "candidates": [ + { + "value": "available", + "source": "internal/cli/schema_hints/metadata/chat.json", + "precedence": "reviewed_explicit", + "selected": true, + "review_reason": "dws-tool-metadata/chat marks this tool as reviewed" + } + ] + }, + "avoid_when": { + "value": [ + "管理员级群功能开关用 chat group update-settings" + ], + "source": "internal/cli/schema_hints/selection/chat.json", + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "人工审阅:对照悟空 develop 同名命令实现、Cobra Long/Example 与 Runtime 确认门禁移植;不改变命令身份、参数契约或接口绑定。", + "candidates": [ + { + "value": [ + "管理员级群功能开关用 chat group update-settings" + ], + "source": "internal/cli/schema_hints/selection/chat.json", + "precedence": "reviewed_explicit", + "selected": true, + "review_reason": "人工审阅:对照悟空 develop 同名命令实现、Cobra Long/Example 与 Runtime 确认门禁移植;不改变命令身份、参数契约或接口绑定。" + } + ] + }, + "confirmation": { + "value": "not_required", + "source": "internal/cli/schema_hints/metadata/chat.json", + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "dws-tool-metadata/chat marks this tool as reviewed", + "candidates": [ + { + "value": "not_required", + "source": "internal/cli/schema_hints/metadata/chat.json", + "precedence": "reviewed_explicit", + "selected": true, + "review_reason": "dws-tool-metadata/chat marks this tool as reviewed" + } + ] + }, + "effect": { + "value": "read", + "source": "internal/cli/schema_hints/metadata/chat.json", + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "dws-tool-metadata/chat marks this tool as reviewed", + "candidates": [ + { + "value": "read", + "source": "internal/cli/schema_hints/metadata/chat.json", + "precedence": "reviewed_explicit", + "selected": true, + "review_reason": "dws-tool-metadata/chat marks this tool as reviewed" + } + ] + }, + "examples": { + "value": [ + "dws chat group user-settings query --groups cid1,cid2 --format json" + ], + "source": "internal/cli/schema_hints/selection/chat.json", + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "人工审阅:对照悟空 develop 同名命令实现、Cobra Long/Example 与 Runtime 确认门禁移植;不改变命令身份、参数契约或接口绑定。", + "candidates": [ + { + "value": [ + "dws chat group user-settings query --groups cid1,cid2 --format json" + ], + "source": "internal/cli/schema_hints/selection/chat.json", + "precedence": "reviewed_explicit", + "selected": true, + "review_reason": "人工审阅:对照悟空 develop 同名命令实现、Cobra Long/Example 与 Runtime 确认门禁移植;不改变命令身份、参数契约或接口绑定。" + } + ] + }, + "idempotency": { + "value": "idempotent", + "source": "internal/cli/schema_hints/metadata/chat.json", + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "dws-tool-metadata/chat marks this tool as reviewed", + "candidates": [ + { + "value": "idempotent", + "source": "internal/cli/schema_hints/metadata/chat.json", + "precedence": "reviewed_explicit", + "selected": true, + "review_reason": "dws-tool-metadata/chat marks this tool as reviewed" + } + ] + }, + "interface_mode": { + "value": "composite", + "source": "internal/cli/schema_hints/metadata/chat.json", + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "dws-tool-metadata/chat marks this tool as reviewed", + "candidates": [ + { + "value": "composite", + "source": "internal/cli/schema_hints/metadata/chat.json", + "precedence": "reviewed_explicit", + "selected": true, + "review_reason": "dws-tool-metadata/chat marks this tool as reviewed" + } + ] + }, + "interface_reason": { + "value": "Reviewed unpinned remote adapter: this executable CLI wrapper calls a remote helper that is absent from the pinned MCP metadata snapshot; no single pinned semantically equivalent interface_ref can represent the command.", + "source": "internal/cli/schema_hints/metadata/chat.json", + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "dws-tool-metadata/chat marks this tool as reviewed", + "candidates": [ + { + "value": "Reviewed unpinned remote adapter: this executable CLI wrapper calls a remote helper that is absent from the pinned MCP metadata snapshot; no single pinned semantically equivalent interface_ref can represent the command.", + "source": "internal/cli/schema_hints/metadata/chat.json", + "precedence": "reviewed_explicit", + "selected": true, + "review_reason": "dws-tool-metadata/chat marks this tool as reviewed" + } + ] + }, + "interface_ref": { + "value": null, + "source": "internal/cli/schema_hints/metadata/chat.json", + "precedence": "reviewed_explicit", + "resolution": "interface_disposition_matrix", + "review_reason": "final interface mode composite forbids a direct MCP interface_ref", + "candidates": [ + { + "value": null, + "source": "internal/cli/schema_hints/metadata/chat.json", + "precedence": "reviewed_explicit", + "selected": true, + "review_reason": "final interface mode composite forbids a direct MCP interface_ref" + } + ] + }, + "reviewed": { + "value": true, + "source": "internal/cli/schema_hints/metadata/chat.json", + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "dws-tool-metadata/chat marks this tool as reviewed", + "candidates": [ + { + "value": true, + "source": "internal/cli/schema_hints/metadata/chat.json", + "precedence": "reviewed_explicit", + "selected": true, + "review_reason": "dws-tool-metadata/chat marks this tool as reviewed" + }, + { + "value": true, + "source": "internal/cli/schema_hints/selection/chat.json", + "precedence": "reviewed_explicit", + "selected": false, + "review_reason": "人工审阅:对照悟空 develop 同名命令实现、Cobra Long/Example 与 Runtime 确认门禁移植;不改变命令身份、参数契约或接口绑定。" + } + ] + }, + "risk": { + "value": "low", + "source": "internal/cli/schema_hints/metadata/chat.json", + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "dws-tool-metadata/chat marks this tool as reviewed", + "candidates": [ + { + "value": "low", + "source": "internal/cli/schema_hints/metadata/chat.json", + "precedence": "reviewed_explicit", + "selected": true, + "review_reason": "dws-tool-metadata/chat marks this tool as reviewed" + } + ] + }, + "use_when": { + "value": [ + "用户说 看下这些群我的置顶和免打扰设置" + ], + "source": "internal/cli/schema_hints/selection/chat.json", + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "人工审阅:对照悟空 develop 同名命令实现、Cobra Long/Example 与 Runtime 确认门禁移植;不改变命令身份、参数契约或接口绑定。", + "candidates": [ + { + "value": [ + "用户说 看下这些群我的置顶和免打扰设置" + ], + "source": "internal/cli/schema_hints/selection/chat.json", + "precedence": "reviewed_explicit", + "selected": true, + "review_reason": "人工审阅:对照悟空 develop 同名命令实现、Cobra Long/Example 与 Runtime 确认门禁移植;不改变命令身份、参数契约或接口绑定。" + } + ] + } + }, + "idempotency": "idempotent", + "interface_mode": "composite", + "interface_reason": "Reviewed unpinned remote adapter: this executable CLI wrapper calls a remote helper that is absent from the pinned MCP metadata snapshot; no single pinned semantically equivalent interface_ref can represent the command.", + "reviewed": true, + "risk": "low", + "source_refs": [ + "CommandRegistry:canonical_path=chat.batch_query_group_chat_settings", + "cobra-help:dws chat group user-settings query", + "internal/cli/schema_hints/metadata/chat.json", + "internal/cli/schema_hints/selection/chat.json", + "wukong-develop:wukong/products" + ], + "use_when": [ + "用户说 看下这些群我的置顶和免打扰设置" + ] + }, + "chat group user-settings set": { + "agent_summary": "批量更新当前用户自己的群会话设置(置顶/免打扰/群昵称/群备注)", + "agent_summary_source": "dws-agent-selection/chat", + "availability": "available", + "avoid_when": [ + "单个群昵称优先 chat group update-nick" + ], + "confirmation": "not_required", + "effect": "write", + "effect_source": "agent-hint", + "examples": [ + "dws chat group user-settings set --items '[{\"openConversationId\":\"cid1\",\"top\":true,\"mute\":false}]' --format json" + ], + "field_provenance": { + "agent_summary": { + "value": "批量更新当前用户自己的群会话设置(置顶/免打扰/群昵称/群备注)", + "source": "internal/cli/schema_hints/selection/chat.json", + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "人工审阅:对照悟空 develop 同名命令实现、Cobra Long/Example 与 Runtime 确认门禁移植;不改变命令身份、参数契约或接口绑定。", + "candidates": [ + { + "value": "批量更新当前用户自己的群会话设置(置顶/免打扰/群昵称/群备注)", + "source": "internal/cli/schema_hints/selection/chat.json", + "precedence": "reviewed_explicit", + "selected": true, + "review_reason": "人工审阅:对照悟空 develop 同名命令实现、Cobra Long/Example 与 Runtime 确认门禁移植;不改变命令身份、参数契约或接口绑定。" + } + ] + }, + "availability": { + "value": "available", + "source": "internal/cli/schema_hints/metadata/chat.json", + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "dws-tool-metadata/chat marks this tool as reviewed", + "candidates": [ + { + "value": "available", + "source": "internal/cli/schema_hints/metadata/chat.json", + "precedence": "reviewed_explicit", + "selected": true, + "review_reason": "dws-tool-metadata/chat marks this tool as reviewed" + } + ] + }, + "avoid_when": { + "value": [ + "单个群昵称优先 chat group update-nick" + ], + "source": "internal/cli/schema_hints/selection/chat.json", + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "人工审阅:对照悟空 develop 同名命令实现、Cobra Long/Example 与 Runtime 确认门禁移植;不改变命令身份、参数契约或接口绑定。", + "candidates": [ + { + "value": [ + "单个群昵称优先 chat group update-nick" + ], + "source": "internal/cli/schema_hints/selection/chat.json", + "precedence": "reviewed_explicit", + "selected": true, + "review_reason": "人工审阅:对照悟空 develop 同名命令实现、Cobra Long/Example 与 Runtime 确认门禁移植;不改变命令身份、参数契约或接口绑定。" + } + ] + }, + "confirmation": { + "value": "not_required", + "source": "internal/cli/schema_hints/metadata/chat.json", + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "dws-tool-metadata/chat marks this tool as reviewed", + "candidates": [ + { + "value": "not_required", + "source": "internal/cli/schema_hints/metadata/chat.json", + "precedence": "reviewed_explicit", + "selected": true, + "review_reason": "dws-tool-metadata/chat marks this tool as reviewed" + } + ] + }, + "effect": { + "value": "write", + "source": "internal/cli/schema_hints/metadata/chat.json", + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "dws-tool-metadata/chat marks this tool as reviewed", + "candidates": [ + { + "value": "write", + "source": "internal/cli/schema_hints/metadata/chat.json", + "precedence": "reviewed_explicit", + "selected": true, + "review_reason": "dws-tool-metadata/chat marks this tool as reviewed" + } + ] + }, + "examples": { + "value": [ + "dws chat group user-settings set --items '[{\"openConversationId\":\"cid1\",\"top\":true,\"mute\":false}]' --format json" + ], + "source": "internal/cli/schema_hints/selection/chat.json", + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "人工审阅:对照悟空 develop 同名命令实现、Cobra Long/Example 与 Runtime 确认门禁移植;不改变命令身份、参数契约或接口绑定。", + "candidates": [ + { + "value": [ + "dws chat group user-settings set --items '[{\"openConversationId\":\"cid1\",\"top\":true,\"mute\":false}]' --format json" + ], + "source": "internal/cli/schema_hints/selection/chat.json", + "precedence": "reviewed_explicit", + "selected": true, + "review_reason": "人工审阅:对照悟空 develop 同名命令实现、Cobra Long/Example 与 Runtime 确认门禁移植;不改变命令身份、参数契约或接口绑定。" + } + ] + }, + "idempotency": { + "value": "idempotent", + "source": "internal/cli/schema_hints/metadata/chat.json", + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "dws-tool-metadata/chat marks this tool as reviewed", + "candidates": [ + { + "value": "idempotent", + "source": "internal/cli/schema_hints/metadata/chat.json", + "precedence": "reviewed_explicit", + "selected": true, + "review_reason": "dws-tool-metadata/chat marks this tool as reviewed" + } + ] + }, + "interface_mode": { + "value": "composite", + "source": "internal/cli/schema_hints/metadata/chat.json", + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "dws-tool-metadata/chat marks this tool as reviewed", + "candidates": [ + { + "value": "composite", + "source": "internal/cli/schema_hints/metadata/chat.json", + "precedence": "reviewed_explicit", + "selected": true, + "review_reason": "dws-tool-metadata/chat marks this tool as reviewed" + } + ] + }, + "interface_reason": { + "value": "Reviewed unpinned remote adapter: this executable CLI wrapper calls a remote helper that is absent from the pinned MCP metadata snapshot; no single pinned semantically equivalent interface_ref can represent the command.", + "source": "internal/cli/schema_hints/metadata/chat.json", + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "dws-tool-metadata/chat marks this tool as reviewed", + "candidates": [ + { + "value": "Reviewed unpinned remote adapter: this executable CLI wrapper calls a remote helper that is absent from the pinned MCP metadata snapshot; no single pinned semantically equivalent interface_ref can represent the command.", + "source": "internal/cli/schema_hints/metadata/chat.json", + "precedence": "reviewed_explicit", + "selected": true, + "review_reason": "dws-tool-metadata/chat marks this tool as reviewed" + } + ] + }, + "interface_ref": { + "value": null, + "source": "internal/cli/schema_hints/metadata/chat.json", + "precedence": "reviewed_explicit", + "resolution": "interface_disposition_matrix", + "review_reason": "final interface mode composite forbids a direct MCP interface_ref", + "candidates": [ + { + "value": null, + "source": "internal/cli/schema_hints/metadata/chat.json", + "precedence": "reviewed_explicit", + "selected": true, + "review_reason": "final interface mode composite forbids a direct MCP interface_ref" + } + ] + }, + "reviewed": { + "value": true, + "source": "internal/cli/schema_hints/metadata/chat.json", + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "dws-tool-metadata/chat marks this tool as reviewed", + "candidates": [ + { + "value": true, + "source": "internal/cli/schema_hints/metadata/chat.json", + "precedence": "reviewed_explicit", + "selected": true, + "review_reason": "dws-tool-metadata/chat marks this tool as reviewed" + }, + { + "value": true, + "source": "internal/cli/schema_hints/selection/chat.json", + "precedence": "reviewed_explicit", + "selected": false, + "review_reason": "人工审阅:对照悟空 develop 同名命令实现、Cobra Long/Example 与 Runtime 确认门禁移植;不改变命令身份、参数契约或接口绑定。" + } + ] + }, + "risk": { + "value": "medium", + "source": "internal/cli/schema_hints/metadata/chat.json", + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "dws-tool-metadata/chat marks this tool as reviewed", + "candidates": [ + { + "value": "medium", + "source": "internal/cli/schema_hints/metadata/chat.json", + "precedence": "reviewed_explicit", + "selected": true, + "review_reason": "dws-tool-metadata/chat marks this tool as reviewed" + } + ] + }, + "use_when": { + "value": [ + "用户说 把这些群都设为免打扰/置顶" + ], + "source": "internal/cli/schema_hints/selection/chat.json", + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "人工审阅:对照悟空 develop 同名命令实现、Cobra Long/Example 与 Runtime 确认门禁移植;不改变命令身份、参数契约或接口绑定。", + "candidates": [ + { + "value": [ + "用户说 把这些群都设为免打扰/置顶" + ], + "source": "internal/cli/schema_hints/selection/chat.json", + "precedence": "reviewed_explicit", + "selected": true, + "review_reason": "人工审阅:对照悟空 develop 同名命令实现、Cobra Long/Example 与 Runtime 确认门禁移植;不改变命令身份、参数契约或接口绑定。" + } + ] + } + }, + "idempotency": "idempotent", + "interface_mode": "composite", + "interface_reason": "Reviewed unpinned remote adapter: this executable CLI wrapper calls a remote helper that is absent from the pinned MCP metadata snapshot; no single pinned semantically equivalent interface_ref can represent the command.", + "reviewed": true, + "risk": "medium", + "source_refs": [ + "CommandRegistry:canonical_path=chat.batch_update_group_chat_settings", + "cobra-help:dws chat group user-settings set", + "internal/cli/schema_hints/metadata/chat.json", + "internal/cli/schema_hints/selection/chat.json", + "wukong-develop:wukong/products" + ], + "use_when": [ + "用户说 把这些群都设为免打扰/置顶" + ] + }, "chat group-mute": { "agent_summary": "开启或关闭群聊全员禁言", "agent_summary_source": "dws-agent-selection/chat", @@ -31348,6 +32122,296 @@ "已有 bizId 并需要追加内容或结束流式输出时" ] }, + "chat message update-text-emotion": { + "agent_summary": "把消息上已有的文字表情原地替换为新的文字表情", + "agent_summary_source": "dws-agent-selection/chat", + "availability": "available", + "avoid_when": [ + "消息上还没有文字表情时使用 chat message add-text-emotion", + "只需清除文字表情时使用 chat message remove-text-emotion" + ], + "confirmation": "not_required", + "effect": "write", + "effect_source": "agent-hint", + "examples": [ + "dws chat message update-text-emotion --conversation-id \u003copenConversationId\u003e --msg-id \u003copenMessageId\u003e --old-emotion-id \u003coldEmotionId\u003e --emotion-id \u003cemotionId\u003e --emotion-name \"处理中\" --text \"处理中 2 分钟\" --background-id im_bg_5" + ], + "field_provenance": { + "agent_summary": { + "value": "把消息上已有的文字表情原地替换为新的文字表情", + "source": "internal/cli/schema_hints/selection/chat.json", + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "依据 issue #85 的原地状态更新场景、Wukong 30f13f02 的真实 Cobra/MCP 映射和开源实现复核;选择语义不改变执行、安全或参数事实。", + "candidates": [ + { + "value": "把消息上已有的文字表情原地替换为新的文字表情", + "source": "internal/cli/schema_hints/selection/chat.json", + "precedence": "reviewed_explicit", + "selected": true, + "review_reason": "依据 issue #85 的原地状态更新场景、Wukong 30f13f02 的真实 Cobra/MCP 映射和开源实现复核;选择语义不改变执行、安全或参数事实。" + } + ] + }, + "availability": { + "value": "available", + "source": "internal/cli/schema_hints/metadata/chat.json", + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "Ported from dws-wukong commits d6831318 and bcf56ccf, merged at 30f13f02. The four conversation locators alias one property; the other six business parameters are required by Cobra before the single im/update_text_emotion call.", + "candidates": [ + { + "value": "available", + "source": "internal/cli/schema_hints/metadata/chat.json", + "precedence": "reviewed_explicit", + "selected": true, + "review_reason": "Ported from dws-wukong commits d6831318 and bcf56ccf, merged at 30f13f02. The four conversation locators alias one property; the other six business parameters are required by Cobra before the single im/update_text_emotion call." + } + ] + }, + "avoid_when": { + "value": [ + "消息上还没有文字表情时使用 chat message add-text-emotion", + "只需清除文字表情时使用 chat message remove-text-emotion" + ], + "source": "internal/cli/schema_hints/selection/chat.json", + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "依据 issue #85 的原地状态更新场景、Wukong 30f13f02 的真实 Cobra/MCP 映射和开源实现复核;选择语义不改变执行、安全或参数事实。", + "candidates": [ + { + "value": [ + "消息上还没有文字表情时使用 chat message add-text-emotion", + "只需清除文字表情时使用 chat message remove-text-emotion" + ], + "source": "internal/cli/schema_hints/selection/chat.json", + "precedence": "reviewed_explicit", + "selected": true, + "review_reason": "依据 issue #85 的原地状态更新场景、Wukong 30f13f02 的真实 Cobra/MCP 映射和开源实现复核;选择语义不改变执行、安全或参数事实。" + } + ] + }, + "confirmation": { + "value": "not_required", + "source": "internal/cli/schema_hints/metadata/chat.json", + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "Ported from dws-wukong commits d6831318 and bcf56ccf, merged at 30f13f02. The four conversation locators alias one property; the other six business parameters are required by Cobra before the single im/update_text_emotion call.", + "candidates": [ + { + "value": "not_required", + "source": "internal/cli/schema_hints/metadata/chat.json", + "precedence": "reviewed_explicit", + "selected": true, + "review_reason": "Ported from dws-wukong commits d6831318 and bcf56ccf, merged at 30f13f02. The four conversation locators alias one property; the other six business parameters are required by Cobra before the single im/update_text_emotion call." + }, + { + "value": "not_required", + "source": "risk-default", + "precedence": "inference_or_default", + "selected": false + } + ] + }, + "effect": { + "value": "write", + "source": "internal/cli/schema_hints/metadata/chat.json", + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "Ported from dws-wukong commits d6831318 and bcf56ccf, merged at 30f13f02. The four conversation locators alias one property; the other six business parameters are required by Cobra before the single im/update_text_emotion call.", + "candidates": [ + { + "value": "write", + "source": "internal/cli/schema_hints/metadata/chat.json", + "precedence": "reviewed_explicit", + "selected": true, + "review_reason": "Ported from dws-wukong commits d6831318 and bcf56ccf, merged at 30f13f02. The four conversation locators alias one property; the other six business parameters are required by Cobra before the single im/update_text_emotion call." + }, + { + "value": "write", + "source": "command-verb", + "precedence": "inference_or_default", + "selected": false + } + ] + }, + "examples": { + "value": [ + "dws chat message update-text-emotion --conversation-id \u003copenConversationId\u003e --msg-id \u003copenMessageId\u003e --old-emotion-id \u003coldEmotionId\u003e --emotion-id \u003cemotionId\u003e --emotion-name \"处理中\" --text \"处理中 2 分钟\" --background-id im_bg_5" + ], + "source": "internal/cli/schema_hints/selection/chat.json", + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "依据 issue #85 的原地状态更新场景、Wukong 30f13f02 的真实 Cobra/MCP 映射和开源实现复核;选择语义不改变执行、安全或参数事实。", + "candidates": [ + { + "value": [ + "dws chat message update-text-emotion --conversation-id \u003copenConversationId\u003e --msg-id \u003copenMessageId\u003e --old-emotion-id \u003coldEmotionId\u003e --emotion-id \u003cemotionId\u003e --emotion-name \"处理中\" --text \"处理中 2 分钟\" --background-id im_bg_5" + ], + "source": "internal/cli/schema_hints/selection/chat.json", + "precedence": "reviewed_explicit", + "selected": true, + "review_reason": "依据 issue #85 的原地状态更新场景、Wukong 30f13f02 的真实 Cobra/MCP 映射和开源实现复核;选择语义不改变执行、安全或参数事实。" + } + ] + }, + "idempotency": { + "value": "idempotent", + "source": "internal/cli/schema_hints/metadata/chat.json", + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "Ported from dws-wukong commits d6831318 and bcf56ccf, merged at 30f13f02. The four conversation locators alias one property; the other six business parameters are required by Cobra before the single im/update_text_emotion call.", + "candidates": [ + { + "value": "idempotent", + "source": "internal/cli/schema_hints/metadata/chat.json", + "precedence": "reviewed_explicit", + "selected": true, + "review_reason": "Ported from dws-wukong commits d6831318 and bcf56ccf, merged at 30f13f02. The four conversation locators alias one property; the other six business parameters are required by Cobra before the single im/update_text_emotion call." + }, + { + "value": "unknown", + "source": "effect-default", + "precedence": "inference_or_default", + "selected": false + } + ] + }, + "interface_mode": { + "value": "composite", + "source": "internal/cli/schema_hints/metadata/chat.json", + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "Ported from dws-wukong commits d6831318 and bcf56ccf, merged at 30f13f02. The four conversation locators alias one property; the other six business parameters are required by Cobra before the single im/update_text_emotion call.", + "candidates": [ + { + "value": "composite", + "source": "internal/cli/schema_hints/metadata/chat.json", + "precedence": "reviewed_explicit", + "selected": true, + "review_reason": "Ported from dws-wukong commits d6831318 and bcf56ccf, merged at 30f13f02. The four conversation locators alias one property; the other six business parameters are required by Cobra before the single im/update_text_emotion call." + } + ] + }, + "interface_reason": { + "value": "Reviewed unpinned remote adapter: the executable CLI calls im/update_text_emotion, which is proven by dws-wukong develop@30f13f02 but absent from the pinned MCP metadata snapshot; no pinned interface_ref can represent the command yet.", + "source": "internal/cli/schema_hints/metadata/chat.json", + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "Ported from dws-wukong commits d6831318 and bcf56ccf, merged at 30f13f02. The four conversation locators alias one property; the other six business parameters are required by Cobra before the single im/update_text_emotion call.", + "candidates": [ + { + "value": "Reviewed unpinned remote adapter: the executable CLI calls im/update_text_emotion, which is proven by dws-wukong develop@30f13f02 but absent from the pinned MCP metadata snapshot; no pinned interface_ref can represent the command yet.", + "source": "internal/cli/schema_hints/metadata/chat.json", + "precedence": "reviewed_explicit", + "selected": true, + "review_reason": "Ported from dws-wukong commits d6831318 and bcf56ccf, merged at 30f13f02. The four conversation locators alias one property; the other six business parameters are required by Cobra before the single im/update_text_emotion call." + } + ] + }, + "interface_ref": { + "value": null, + "source": "internal/cli/schema_hints/metadata/chat.json", + "precedence": "reviewed_explicit", + "resolution": "interface_disposition_matrix", + "review_reason": "final interface mode composite forbids a direct MCP interface_ref", + "candidates": [ + { + "value": null, + "source": "internal/cli/schema_hints/metadata/chat.json", + "precedence": "reviewed_explicit", + "selected": true, + "review_reason": "final interface mode composite forbids a direct MCP interface_ref" + } + ] + }, + "reviewed": { + "value": true, + "source": "internal/cli/schema_hints/metadata/chat.json", + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "Ported from dws-wukong commits d6831318 and bcf56ccf, merged at 30f13f02. The four conversation locators alias one property; the other six business parameters are required by Cobra before the single im/update_text_emotion call.", + "candidates": [ + { + "value": true, + "source": "internal/cli/schema_hints/metadata/chat.json", + "precedence": "reviewed_explicit", + "selected": true, + "review_reason": "Ported from dws-wukong commits d6831318 and bcf56ccf, merged at 30f13f02. The four conversation locators alias one property; the other six business parameters are required by Cobra before the single im/update_text_emotion call." + }, + { + "value": true, + "source": "internal/cli/schema_hints/selection/chat.json", + "precedence": "reviewed_explicit", + "selected": false, + "review_reason": "依据 issue #85 的原地状态更新场景、Wukong 30f13f02 的真实 Cobra/MCP 映射和开源实现复核;选择语义不改变执行、安全或参数事实。" + } + ] + }, + "risk": { + "value": "low", + "source": "internal/cli/schema_hints/metadata/chat.json", + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "Ported from dws-wukong commits d6831318 and bcf56ccf, merged at 30f13f02. The four conversation locators alias one property; the other six business parameters are required by Cobra before the single im/update_text_emotion call.", + "candidates": [ + { + "value": "low", + "source": "internal/cli/schema_hints/metadata/chat.json", + "precedence": "reviewed_explicit", + "selected": true, + "review_reason": "Ported from dws-wukong commits d6831318 and bcf56ccf, merged at 30f13f02. The four conversation locators alias one property; the other six business parameters are required by Cobra before the single im/update_text_emotion call." + }, + { + "value": "medium", + "source": "effect-default", + "precedence": "inference_or_default", + "selected": false + } + ] + }, + "use_when": { + "value": [ + "需要更新消息的状态文字或表情,并避免先移除再添加造成闪烁和两次网络调用时" + ], + "source": "internal/cli/schema_hints/selection/chat.json", + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "依据 issue #85 的原地状态更新场景、Wukong 30f13f02 的真实 Cobra/MCP 映射和开源实现复核;选择语义不改变执行、安全或参数事实。", + "candidates": [ + { + "value": [ + "需要更新消息的状态文字或表情,并避免先移除再添加造成闪烁和两次网络调用时" + ], + "source": "internal/cli/schema_hints/selection/chat.json", + "precedence": "reviewed_explicit", + "selected": true, + "review_reason": "依据 issue #85 的原地状态更新场景、Wukong 30f13f02 的真实 Cobra/MCP 映射和开源实现复核;选择语义不改变执行、安全或参数事实。" + } + ] + } + }, + "idempotency": "idempotent", + "interface_mode": "composite", + "interface_reason": "Reviewed unpinned remote adapter: the executable CLI calls im/update_text_emotion, which is proven by dws-wukong develop@30f13f02 but absent from the pinned MCP metadata snapshot; no pinned interface_ref can represent the command yet.", + "reviewed": true, + "risk": "low", + "source_refs": [ + "cobra-help:dws chat message update-text-emotion", + "dws-wukong:30f13f02", + "dws-wukong:bcf56ccf", + "dws-wukong:d6831318", + "github:hugozhu/dingtalk-opencode-tag#85", + "internal/cli/schema_command_registry/products/chat.json#chat.update_text_emotion", + "internal/cli/schema_hints/metadata/chat.json", + "internal/cli/schema_hints/selection/chat.json", + "live-dws-schema:im.update_text_emotion#FAILED_NOT_AUTHENTICATED", + "skills/mono/references/products/chat.md" + ], + "use_when": [ + "需要更新消息的状态文字或表情,并避免先移除再添加造成闪烁和两次网络调用时" + ] + }, "chat mute": { "agent_summary": "开启或关闭指定会话的免打扰", "agent_summary_source": "dws-agent-selection/chat", diff --git a/internal/cli/schema_agent_metadata/doc.json b/internal/cli/schema_agent_metadata/doc.json index 7b4d2a1f..27387e12 100644 --- a/internal/cli/schema_agent_metadata/doc.json +++ b/internal/cli/schema_agent_metadata/doc.json @@ -12585,6 +12585,1296 @@ "兼容入口:按关键词搜文档时(已弃用,日常改用 drive search / wiki node search)" ] }, + "doc style background clear": { + "agent_summary": "清除钉钉文档背景", + "agent_summary_source": "dws-agent-selection/doc", + "availability": "available", + "avoid_when": [ + "设置背景用 background set" + ], + "confirmation": "not_required", + "effect": "write", + "effect_source": "agent-hint", + "examples": [ + "dws doc style background clear --node \u003cDOC_ID\u003e --format json" + ], + "field_provenance": { + "agent_summary": { + "value": "清除钉钉文档背景", + "source": "internal/cli/schema_hints/selection/doc.json", + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "人工审阅:对照悟空 develop 同名命令实现、Cobra Long/Example 与 Runtime 确认门禁移植;不改变命令身份、参数契约或接口绑定。", + "candidates": [ + { + "value": "清除钉钉文档背景", + "source": "internal/cli/schema_hints/selection/doc.json", + "precedence": "reviewed_explicit", + "selected": true, + "review_reason": "人工审阅:对照悟空 develop 同名命令实现、Cobra Long/Example 与 Runtime 确认门禁移植;不改变命令身份、参数契约或接口绑定。" + } + ] + }, + "availability": { + "value": "available", + "source": "internal/cli/schema_hints/metadata/doc.json", + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "dws-tool-metadata/doc marks this tool as reviewed", + "candidates": [ + { + "value": "available", + "source": "internal/cli/schema_hints/metadata/doc.json", + "precedence": "reviewed_explicit", + "selected": true, + "review_reason": "dws-tool-metadata/doc marks this tool as reviewed" + } + ] + }, + "avoid_when": { + "value": [ + "设置背景用 background set" + ], + "source": "internal/cli/schema_hints/selection/doc.json", + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "人工审阅:对照悟空 develop 同名命令实现、Cobra Long/Example 与 Runtime 确认门禁移植;不改变命令身份、参数契约或接口绑定。", + "candidates": [ + { + "value": [ + "设置背景用 background set" + ], + "source": "internal/cli/schema_hints/selection/doc.json", + "precedence": "reviewed_explicit", + "selected": true, + "review_reason": "人工审阅:对照悟空 develop 同名命令实现、Cobra Long/Example 与 Runtime 确认门禁移植;不改变命令身份、参数契约或接口绑定。" + } + ] + }, + "confirmation": { + "value": "not_required", + "source": "internal/cli/schema_hints/metadata/doc.json", + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "dws-tool-metadata/doc marks this tool as reviewed", + "candidates": [ + { + "value": "not_required", + "source": "internal/cli/schema_hints/metadata/doc.json", + "precedence": "reviewed_explicit", + "selected": true, + "review_reason": "dws-tool-metadata/doc marks this tool as reviewed" + } + ] + }, + "effect": { + "value": "write", + "source": "internal/cli/schema_hints/metadata/doc.json", + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "dws-tool-metadata/doc marks this tool as reviewed", + "candidates": [ + { + "value": "write", + "source": "internal/cli/schema_hints/metadata/doc.json", + "precedence": "reviewed_explicit", + "selected": true, + "review_reason": "dws-tool-metadata/doc marks this tool as reviewed" + } + ] + }, + "examples": { + "value": [ + "dws doc style background clear --node \u003cDOC_ID\u003e --format json" + ], + "source": "internal/cli/schema_hints/selection/doc.json", + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "人工审阅:对照悟空 develop 同名命令实现、Cobra Long/Example 与 Runtime 确认门禁移植;不改变命令身份、参数契约或接口绑定。", + "candidates": [ + { + "value": [ + "dws doc style background clear --node \u003cDOC_ID\u003e --format json" + ], + "source": "internal/cli/schema_hints/selection/doc.json", + "precedence": "reviewed_explicit", + "selected": true, + "review_reason": "人工审阅:对照悟空 develop 同名命令实现、Cobra Long/Example 与 Runtime 确认门禁移植;不改变命令身份、参数契约或接口绑定。" + } + ] + }, + "idempotency": { + "value": "idempotent", + "source": "internal/cli/schema_hints/metadata/doc.json", + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "dws-tool-metadata/doc marks this tool as reviewed", + "candidates": [ + { + "value": "idempotent", + "source": "internal/cli/schema_hints/metadata/doc.json", + "precedence": "reviewed_explicit", + "selected": true, + "review_reason": "dws-tool-metadata/doc marks this tool as reviewed" + } + ] + }, + "interface_mode": { + "value": "composite", + "source": "internal/cli/schema_hints/metadata/doc.json", + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "dws-tool-metadata/doc marks this tool as reviewed", + "candidates": [ + { + "value": "composite", + "source": "internal/cli/schema_hints/metadata/doc.json", + "precedence": "reviewed_explicit", + "selected": true, + "review_reason": "dws-tool-metadata/doc marks this tool as reviewed" + } + ] + }, + "interface_reason": { + "value": "Reviewed unpinned remote adapter: this executable CLI wrapper calls a remote helper that is absent from the pinned MCP metadata snapshot; no single pinned semantically equivalent interface_ref can represent the command.", + "source": "internal/cli/schema_hints/metadata/doc.json", + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "dws-tool-metadata/doc marks this tool as reviewed", + "candidates": [ + { + "value": "Reviewed unpinned remote adapter: this executable CLI wrapper calls a remote helper that is absent from the pinned MCP metadata snapshot; no single pinned semantically equivalent interface_ref can represent the command.", + "source": "internal/cli/schema_hints/metadata/doc.json", + "precedence": "reviewed_explicit", + "selected": true, + "review_reason": "dws-tool-metadata/doc marks this tool as reviewed" + } + ] + }, + "interface_ref": { + "value": null, + "source": "internal/cli/schema_hints/metadata/doc.json", + "precedence": "reviewed_explicit", + "resolution": "interface_disposition_matrix", + "review_reason": "final interface mode composite forbids a direct MCP interface_ref", + "candidates": [ + { + "value": null, + "source": "internal/cli/schema_hints/metadata/doc.json", + "precedence": "reviewed_explicit", + "selected": true, + "review_reason": "final interface mode composite forbids a direct MCP interface_ref" + } + ] + }, + "reviewed": { + "value": true, + "source": "internal/cli/schema_hints/metadata/doc.json", + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "dws-tool-metadata/doc marks this tool as reviewed", + "candidates": [ + { + "value": true, + "source": "internal/cli/schema_hints/metadata/doc.json", + "precedence": "reviewed_explicit", + "selected": true, + "review_reason": "dws-tool-metadata/doc marks this tool as reviewed" + }, + { + "value": true, + "source": "internal/cli/schema_hints/selection/doc.json", + "precedence": "reviewed_explicit", + "selected": false, + "review_reason": "人工审阅:对照悟空 develop 同名命令实现、Cobra Long/Example 与 Runtime 确认门禁移植;不改变命令身份、参数契约或接口绑定。" + } + ] + }, + "risk": { + "value": "low", + "source": "internal/cli/schema_hints/metadata/doc.json", + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "dws-tool-metadata/doc marks this tool as reviewed", + "candidates": [ + { + "value": "low", + "source": "internal/cli/schema_hints/metadata/doc.json", + "precedence": "reviewed_explicit", + "selected": true, + "review_reason": "dws-tool-metadata/doc marks this tool as reviewed" + } + ] + }, + "use_when": { + "value": [ + "用户说 去掉文档背景色" + ], + "source": "internal/cli/schema_hints/selection/doc.json", + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "人工审阅:对照悟空 develop 同名命令实现、Cobra Long/Example 与 Runtime 确认门禁移植;不改变命令身份、参数契约或接口绑定。", + "candidates": [ + { + "value": [ + "用户说 去掉文档背景色" + ], + "source": "internal/cli/schema_hints/selection/doc.json", + "precedence": "reviewed_explicit", + "selected": true, + "review_reason": "人工审阅:对照悟空 develop 同名命令实现、Cobra Long/Example 与 Runtime 确认门禁移植;不改变命令身份、参数契约或接口绑定。" + } + ] + } + }, + "idempotency": "idempotent", + "interface_mode": "composite", + "interface_reason": "Reviewed unpinned remote adapter: this executable CLI wrapper calls a remote helper that is absent from the pinned MCP metadata snapshot; no single pinned semantically equivalent interface_ref can represent the command.", + "reviewed": true, + "risk": "low", + "source_refs": [ + "CommandRegistry:canonical_path=doc.style_background_clear", + "cobra-help:dws doc style background clear", + "internal/cli/schema_hints/metadata/doc.json", + "internal/cli/schema_hints/selection/doc.json", + "wukong-develop:wukong/products" + ], + "use_when": [ + "用户说 去掉文档背景色" + ] + }, + "doc style background set": { + "agent_summary": "设置钉钉文档背景纯色(#RRGGBB)", + "agent_summary_source": "dws-agent-selection/doc", + "availability": "available", + "avoid_when": [ + "背景不支持图片;封面用 cover set" + ], + "confirmation": "not_required", + "effect": "write", + "effect_source": "agent-hint", + "examples": [ + "dws doc style background set --node \u003cDOC_ID\u003e --color \"#E8F2FE\" --format json" + ], + "field_provenance": { + "agent_summary": { + "value": "设置钉钉文档背景纯色(#RRGGBB)", + "source": "internal/cli/schema_hints/selection/doc.json", + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "人工审阅:对照悟空 develop 同名命令实现、Cobra Long/Example 与 Runtime 确认门禁移植;不改变命令身份、参数契约或接口绑定。", + "candidates": [ + { + "value": "设置钉钉文档背景纯色(#RRGGBB)", + "source": "internal/cli/schema_hints/selection/doc.json", + "precedence": "reviewed_explicit", + "selected": true, + "review_reason": "人工审阅:对照悟空 develop 同名命令实现、Cobra Long/Example 与 Runtime 确认门禁移植;不改变命令身份、参数契约或接口绑定。" + } + ] + }, + "availability": { + "value": "available", + "source": "internal/cli/schema_hints/metadata/doc.json", + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "dws-tool-metadata/doc marks this tool as reviewed", + "candidates": [ + { + "value": "available", + "source": "internal/cli/schema_hints/metadata/doc.json", + "precedence": "reviewed_explicit", + "selected": true, + "review_reason": "dws-tool-metadata/doc marks this tool as reviewed" + } + ] + }, + "avoid_when": { + "value": [ + "背景不支持图片;封面用 cover set" + ], + "source": "internal/cli/schema_hints/selection/doc.json", + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "人工审阅:对照悟空 develop 同名命令实现、Cobra Long/Example 与 Runtime 确认门禁移植;不改变命令身份、参数契约或接口绑定。", + "candidates": [ + { + "value": [ + "背景不支持图片;封面用 cover set" + ], + "source": "internal/cli/schema_hints/selection/doc.json", + "precedence": "reviewed_explicit", + "selected": true, + "review_reason": "人工审阅:对照悟空 develop 同名命令实现、Cobra Long/Example 与 Runtime 确认门禁移植;不改变命令身份、参数契约或接口绑定。" + } + ] + }, + "confirmation": { + "value": "not_required", + "source": "internal/cli/schema_hints/metadata/doc.json", + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "dws-tool-metadata/doc marks this tool as reviewed", + "candidates": [ + { + "value": "not_required", + "source": "internal/cli/schema_hints/metadata/doc.json", + "precedence": "reviewed_explicit", + "selected": true, + "review_reason": "dws-tool-metadata/doc marks this tool as reviewed" + } + ] + }, + "effect": { + "value": "write", + "source": "internal/cli/schema_hints/metadata/doc.json", + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "dws-tool-metadata/doc marks this tool as reviewed", + "candidates": [ + { + "value": "write", + "source": "internal/cli/schema_hints/metadata/doc.json", + "precedence": "reviewed_explicit", + "selected": true, + "review_reason": "dws-tool-metadata/doc marks this tool as reviewed" + } + ] + }, + "examples": { + "value": [ + "dws doc style background set --node \u003cDOC_ID\u003e --color \"#E8F2FE\" --format json" + ], + "source": "internal/cli/schema_hints/selection/doc.json", + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "人工审阅:对照悟空 develop 同名命令实现、Cobra Long/Example 与 Runtime 确认门禁移植;不改变命令身份、参数契约或接口绑定。", + "candidates": [ + { + "value": [ + "dws doc style background set --node \u003cDOC_ID\u003e --color \"#E8F2FE\" --format json" + ], + "source": "internal/cli/schema_hints/selection/doc.json", + "precedence": "reviewed_explicit", + "selected": true, + "review_reason": "人工审阅:对照悟空 develop 同名命令实现、Cobra Long/Example 与 Runtime 确认门禁移植;不改变命令身份、参数契约或接口绑定。" + } + ] + }, + "idempotency": { + "value": "idempotent", + "source": "internal/cli/schema_hints/metadata/doc.json", + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "dws-tool-metadata/doc marks this tool as reviewed", + "candidates": [ + { + "value": "idempotent", + "source": "internal/cli/schema_hints/metadata/doc.json", + "precedence": "reviewed_explicit", + "selected": true, + "review_reason": "dws-tool-metadata/doc marks this tool as reviewed" + } + ] + }, + "interface_mode": { + "value": "composite", + "source": "internal/cli/schema_hints/metadata/doc.json", + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "dws-tool-metadata/doc marks this tool as reviewed", + "candidates": [ + { + "value": "composite", + "source": "internal/cli/schema_hints/metadata/doc.json", + "precedence": "reviewed_explicit", + "selected": true, + "review_reason": "dws-tool-metadata/doc marks this tool as reviewed" + } + ] + }, + "interface_reason": { + "value": "Reviewed unpinned remote adapter: this executable CLI wrapper calls a remote helper that is absent from the pinned MCP metadata snapshot; no single pinned semantically equivalent interface_ref can represent the command.", + "source": "internal/cli/schema_hints/metadata/doc.json", + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "dws-tool-metadata/doc marks this tool as reviewed", + "candidates": [ + { + "value": "Reviewed unpinned remote adapter: this executable CLI wrapper calls a remote helper that is absent from the pinned MCP metadata snapshot; no single pinned semantically equivalent interface_ref can represent the command.", + "source": "internal/cli/schema_hints/metadata/doc.json", + "precedence": "reviewed_explicit", + "selected": true, + "review_reason": "dws-tool-metadata/doc marks this tool as reviewed" + } + ] + }, + "interface_ref": { + "value": null, + "source": "internal/cli/schema_hints/metadata/doc.json", + "precedence": "reviewed_explicit", + "resolution": "interface_disposition_matrix", + "review_reason": "final interface mode composite forbids a direct MCP interface_ref", + "candidates": [ + { + "value": null, + "source": "internal/cli/schema_hints/metadata/doc.json", + "precedence": "reviewed_explicit", + "selected": true, + "review_reason": "final interface mode composite forbids a direct MCP interface_ref" + } + ] + }, + "reviewed": { + "value": true, + "source": "internal/cli/schema_hints/metadata/doc.json", + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "dws-tool-metadata/doc marks this tool as reviewed", + "candidates": [ + { + "value": true, + "source": "internal/cli/schema_hints/metadata/doc.json", + "precedence": "reviewed_explicit", + "selected": true, + "review_reason": "dws-tool-metadata/doc marks this tool as reviewed" + }, + { + "value": true, + "source": "internal/cli/schema_hints/selection/doc.json", + "precedence": "reviewed_explicit", + "selected": false, + "review_reason": "人工审阅:对照悟空 develop 同名命令实现、Cobra Long/Example 与 Runtime 确认门禁移植;不改变命令身份、参数契约或接口绑定。" + } + ] + }, + "risk": { + "value": "low", + "source": "internal/cli/schema_hints/metadata/doc.json", + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "dws-tool-metadata/doc marks this tool as reviewed", + "candidates": [ + { + "value": "low", + "source": "internal/cli/schema_hints/metadata/doc.json", + "precedence": "reviewed_explicit", + "selected": true, + "review_reason": "dws-tool-metadata/doc marks this tool as reviewed" + } + ] + }, + "use_when": { + "value": [ + "用户说 给文档设置背景色" + ], + "source": "internal/cli/schema_hints/selection/doc.json", + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "人工审阅:对照悟空 develop 同名命令实现、Cobra Long/Example 与 Runtime 确认门禁移植;不改变命令身份、参数契约或接口绑定。", + "candidates": [ + { + "value": [ + "用户说 给文档设置背景色" + ], + "source": "internal/cli/schema_hints/selection/doc.json", + "precedence": "reviewed_explicit", + "selected": true, + "review_reason": "人工审阅:对照悟空 develop 同名命令实现、Cobra Long/Example 与 Runtime 确认门禁移植;不改变命令身份、参数契约或接口绑定。" + } + ] + } + }, + "idempotency": "idempotent", + "interface_mode": "composite", + "interface_reason": "Reviewed unpinned remote adapter: this executable CLI wrapper calls a remote helper that is absent from the pinned MCP metadata snapshot; no single pinned semantically equivalent interface_ref can represent the command.", + "reviewed": true, + "risk": "low", + "source_refs": [ + "CommandRegistry:canonical_path=doc.style_background_set", + "cobra-help:dws doc style background set", + "internal/cli/schema_hints/metadata/doc.json", + "internal/cli/schema_hints/selection/doc.json", + "wukong-develop:wukong/products" + ], + "use_when": [ + "用户说 给文档设置背景色" + ] + }, + "doc style cover clear": { + "agent_summary": "移除钉钉文档封面", + "agent_summary_source": "dws-agent-selection/doc", + "availability": "available", + "avoid_when": [ + "设置封面用 cover set" + ], + "confirmation": "not_required", + "effect": "write", + "effect_source": "agent-hint", + "examples": [ + "dws doc style cover clear --node \u003cDOC_ID\u003e --format json" + ], + "field_provenance": { + "agent_summary": { + "value": "移除钉钉文档封面", + "source": "internal/cli/schema_hints/selection/doc.json", + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "人工审阅:对照悟空 develop 同名命令实现、Cobra Long/Example 与 Runtime 确认门禁移植;不改变命令身份、参数契约或接口绑定。", + "candidates": [ + { + "value": "移除钉钉文档封面", + "source": "internal/cli/schema_hints/selection/doc.json", + "precedence": "reviewed_explicit", + "selected": true, + "review_reason": "人工审阅:对照悟空 develop 同名命令实现、Cobra Long/Example 与 Runtime 确认门禁移植;不改变命令身份、参数契约或接口绑定。" + } + ] + }, + "availability": { + "value": "available", + "source": "internal/cli/schema_hints/metadata/doc.json", + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "dws-tool-metadata/doc marks this tool as reviewed", + "candidates": [ + { + "value": "available", + "source": "internal/cli/schema_hints/metadata/doc.json", + "precedence": "reviewed_explicit", + "selected": true, + "review_reason": "dws-tool-metadata/doc marks this tool as reviewed" + } + ] + }, + "avoid_when": { + "value": [ + "设置封面用 cover set" + ], + "source": "internal/cli/schema_hints/selection/doc.json", + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "人工审阅:对照悟空 develop 同名命令实现、Cobra Long/Example 与 Runtime 确认门禁移植;不改变命令身份、参数契约或接口绑定。", + "candidates": [ + { + "value": [ + "设置封面用 cover set" + ], + "source": "internal/cli/schema_hints/selection/doc.json", + "precedence": "reviewed_explicit", + "selected": true, + "review_reason": "人工审阅:对照悟空 develop 同名命令实现、Cobra Long/Example 与 Runtime 确认门禁移植;不改变命令身份、参数契约或接口绑定。" + } + ] + }, + "confirmation": { + "value": "not_required", + "source": "internal/cli/schema_hints/metadata/doc.json", + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "dws-tool-metadata/doc marks this tool as reviewed", + "candidates": [ + { + "value": "not_required", + "source": "internal/cli/schema_hints/metadata/doc.json", + "precedence": "reviewed_explicit", + "selected": true, + "review_reason": "dws-tool-metadata/doc marks this tool as reviewed" + } + ] + }, + "effect": { + "value": "write", + "source": "internal/cli/schema_hints/metadata/doc.json", + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "dws-tool-metadata/doc marks this tool as reviewed", + "candidates": [ + { + "value": "write", + "source": "internal/cli/schema_hints/metadata/doc.json", + "precedence": "reviewed_explicit", + "selected": true, + "review_reason": "dws-tool-metadata/doc marks this tool as reviewed" + } + ] + }, + "examples": { + "value": [ + "dws doc style cover clear --node \u003cDOC_ID\u003e --format json" + ], + "source": "internal/cli/schema_hints/selection/doc.json", + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "人工审阅:对照悟空 develop 同名命令实现、Cobra Long/Example 与 Runtime 确认门禁移植;不改变命令身份、参数契约或接口绑定。", + "candidates": [ + { + "value": [ + "dws doc style cover clear --node \u003cDOC_ID\u003e --format json" + ], + "source": "internal/cli/schema_hints/selection/doc.json", + "precedence": "reviewed_explicit", + "selected": true, + "review_reason": "人工审阅:对照悟空 develop 同名命令实现、Cobra Long/Example 与 Runtime 确认门禁移植;不改变命令身份、参数契约或接口绑定。" + } + ] + }, + "idempotency": { + "value": "idempotent", + "source": "internal/cli/schema_hints/metadata/doc.json", + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "dws-tool-metadata/doc marks this tool as reviewed", + "candidates": [ + { + "value": "idempotent", + "source": "internal/cli/schema_hints/metadata/doc.json", + "precedence": "reviewed_explicit", + "selected": true, + "review_reason": "dws-tool-metadata/doc marks this tool as reviewed" + } + ] + }, + "interface_mode": { + "value": "composite", + "source": "internal/cli/schema_hints/metadata/doc.json", + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "dws-tool-metadata/doc marks this tool as reviewed", + "candidates": [ + { + "value": "composite", + "source": "internal/cli/schema_hints/metadata/doc.json", + "precedence": "reviewed_explicit", + "selected": true, + "review_reason": "dws-tool-metadata/doc marks this tool as reviewed" + } + ] + }, + "interface_reason": { + "value": "Reviewed unpinned remote adapter: this executable CLI wrapper calls a remote helper that is absent from the pinned MCP metadata snapshot; no single pinned semantically equivalent interface_ref can represent the command.", + "source": "internal/cli/schema_hints/metadata/doc.json", + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "dws-tool-metadata/doc marks this tool as reviewed", + "candidates": [ + { + "value": "Reviewed unpinned remote adapter: this executable CLI wrapper calls a remote helper that is absent from the pinned MCP metadata snapshot; no single pinned semantically equivalent interface_ref can represent the command.", + "source": "internal/cli/schema_hints/metadata/doc.json", + "precedence": "reviewed_explicit", + "selected": true, + "review_reason": "dws-tool-metadata/doc marks this tool as reviewed" + } + ] + }, + "interface_ref": { + "value": null, + "source": "internal/cli/schema_hints/metadata/doc.json", + "precedence": "reviewed_explicit", + "resolution": "interface_disposition_matrix", + "review_reason": "final interface mode composite forbids a direct MCP interface_ref", + "candidates": [ + { + "value": null, + "source": "internal/cli/schema_hints/metadata/doc.json", + "precedence": "reviewed_explicit", + "selected": true, + "review_reason": "final interface mode composite forbids a direct MCP interface_ref" + } + ] + }, + "reviewed": { + "value": true, + "source": "internal/cli/schema_hints/metadata/doc.json", + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "dws-tool-metadata/doc marks this tool as reviewed", + "candidates": [ + { + "value": true, + "source": "internal/cli/schema_hints/metadata/doc.json", + "precedence": "reviewed_explicit", + "selected": true, + "review_reason": "dws-tool-metadata/doc marks this tool as reviewed" + }, + { + "value": true, + "source": "internal/cli/schema_hints/selection/doc.json", + "precedence": "reviewed_explicit", + "selected": false, + "review_reason": "人工审阅:对照悟空 develop 同名命令实现、Cobra Long/Example 与 Runtime 确认门禁移植;不改变命令身份、参数契约或接口绑定。" + } + ] + }, + "risk": { + "value": "low", + "source": "internal/cli/schema_hints/metadata/doc.json", + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "dws-tool-metadata/doc marks this tool as reviewed", + "candidates": [ + { + "value": "low", + "source": "internal/cli/schema_hints/metadata/doc.json", + "precedence": "reviewed_explicit", + "selected": true, + "review_reason": "dws-tool-metadata/doc marks this tool as reviewed" + } + ] + }, + "use_when": { + "value": [ + "用户说 去掉文档封面" + ], + "source": "internal/cli/schema_hints/selection/doc.json", + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "人工审阅:对照悟空 develop 同名命令实现、Cobra Long/Example 与 Runtime 确认门禁移植;不改变命令身份、参数契约或接口绑定。", + "candidates": [ + { + "value": [ + "用户说 去掉文档封面" + ], + "source": "internal/cli/schema_hints/selection/doc.json", + "precedence": "reviewed_explicit", + "selected": true, + "review_reason": "人工审阅:对照悟空 develop 同名命令实现、Cobra Long/Example 与 Runtime 确认门禁移植;不改变命令身份、参数契约或接口绑定。" + } + ] + } + }, + "idempotency": "idempotent", + "interface_mode": "composite", + "interface_reason": "Reviewed unpinned remote adapter: this executable CLI wrapper calls a remote helper that is absent from the pinned MCP metadata snapshot; no single pinned semantically equivalent interface_ref can represent the command.", + "reviewed": true, + "risk": "low", + "source_refs": [ + "CommandRegistry:canonical_path=doc.style_cover_clear", + "cobra-help:dws doc style cover clear", + "internal/cli/schema_hints/metadata/doc.json", + "internal/cli/schema_hints/selection/doc.json", + "wukong-develop:wukong/products" + ], + "use_when": [ + "用户说 去掉文档封面" + ] + }, + "doc style cover set": { + "agent_summary": "设置钉钉文档顶部封面图(外链或本地图片上传)", + "agent_summary_source": "dws-agent-selection/doc", + "availability": "available", + "avoid_when": [ + "读取当前封面用 doc style get;移除封面用 cover clear" + ], + "confirmation": "not_required", + "effect": "write", + "effect_source": "agent-hint", + "examples": [ + "dws doc style cover set --node \u003cDOC_ID\u003e --image https://img.example.com/cover.png --format json" + ], + "field_provenance": { + "agent_summary": { + "value": "设置钉钉文档顶部封面图(外链或本地图片上传)", + "source": "internal/cli/schema_hints/selection/doc.json", + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "人工审阅:对照悟空 develop 同名命令实现、Cobra Long/Example 与 Runtime 确认门禁移植;不改变命令身份、参数契约或接口绑定。", + "candidates": [ + { + "value": "设置钉钉文档顶部封面图(外链或本地图片上传)", + "source": "internal/cli/schema_hints/selection/doc.json", + "precedence": "reviewed_explicit", + "selected": true, + "review_reason": "人工审阅:对照悟空 develop 同名命令实现、Cobra Long/Example 与 Runtime 确认门禁移植;不改变命令身份、参数契约或接口绑定。" + } + ] + }, + "availability": { + "value": "available", + "source": "internal/cli/schema_hints/metadata/doc.json", + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "dws-tool-metadata/doc marks this tool as reviewed", + "candidates": [ + { + "value": "available", + "source": "internal/cli/schema_hints/metadata/doc.json", + "precedence": "reviewed_explicit", + "selected": true, + "review_reason": "dws-tool-metadata/doc marks this tool as reviewed" + } + ] + }, + "avoid_when": { + "value": [ + "读取当前封面用 doc style get;移除封面用 cover clear" + ], + "source": "internal/cli/schema_hints/selection/doc.json", + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "人工审阅:对照悟空 develop 同名命令实现、Cobra Long/Example 与 Runtime 确认门禁移植;不改变命令身份、参数契约或接口绑定。", + "candidates": [ + { + "value": [ + "读取当前封面用 doc style get;移除封面用 cover clear" + ], + "source": "internal/cli/schema_hints/selection/doc.json", + "precedence": "reviewed_explicit", + "selected": true, + "review_reason": "人工审阅:对照悟空 develop 同名命令实现、Cobra Long/Example 与 Runtime 确认门禁移植;不改变命令身份、参数契约或接口绑定。" + } + ] + }, + "confirmation": { + "value": "not_required", + "source": "internal/cli/schema_hints/metadata/doc.json", + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "dws-tool-metadata/doc marks this tool as reviewed", + "candidates": [ + { + "value": "not_required", + "source": "internal/cli/schema_hints/metadata/doc.json", + "precedence": "reviewed_explicit", + "selected": true, + "review_reason": "dws-tool-metadata/doc marks this tool as reviewed" + } + ] + }, + "effect": { + "value": "write", + "source": "internal/cli/schema_hints/metadata/doc.json", + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "dws-tool-metadata/doc marks this tool as reviewed", + "candidates": [ + { + "value": "write", + "source": "internal/cli/schema_hints/metadata/doc.json", + "precedence": "reviewed_explicit", + "selected": true, + "review_reason": "dws-tool-metadata/doc marks this tool as reviewed" + } + ] + }, + "examples": { + "value": [ + "dws doc style cover set --node \u003cDOC_ID\u003e --image https://img.example.com/cover.png --format json" + ], + "source": "internal/cli/schema_hints/selection/doc.json", + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "人工审阅:对照悟空 develop 同名命令实现、Cobra Long/Example 与 Runtime 确认门禁移植;不改变命令身份、参数契约或接口绑定。", + "candidates": [ + { + "value": [ + "dws doc style cover set --node \u003cDOC_ID\u003e --image https://img.example.com/cover.png --format json" + ], + "source": "internal/cli/schema_hints/selection/doc.json", + "precedence": "reviewed_explicit", + "selected": true, + "review_reason": "人工审阅:对照悟空 develop 同名命令实现、Cobra Long/Example 与 Runtime 确认门禁移植;不改变命令身份、参数契约或接口绑定。" + } + ] + }, + "idempotency": { + "value": "idempotent", + "source": "internal/cli/schema_hints/metadata/doc.json", + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "dws-tool-metadata/doc marks this tool as reviewed", + "candidates": [ + { + "value": "idempotent", + "source": "internal/cli/schema_hints/metadata/doc.json", + "precedence": "reviewed_explicit", + "selected": true, + "review_reason": "dws-tool-metadata/doc marks this tool as reviewed" + } + ] + }, + "interface_mode": { + "value": "composite", + "source": "internal/cli/schema_hints/metadata/doc.json", + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "dws-tool-metadata/doc marks this tool as reviewed", + "candidates": [ + { + "value": "composite", + "source": "internal/cli/schema_hints/metadata/doc.json", + "precedence": "reviewed_explicit", + "selected": true, + "review_reason": "dws-tool-metadata/doc marks this tool as reviewed" + } + ] + }, + "interface_reason": { + "value": "Reviewed unpinned remote adapter: this executable CLI wrapper calls a remote helper that is absent from the pinned MCP metadata snapshot; no single pinned semantically equivalent interface_ref can represent the command.", + "source": "internal/cli/schema_hints/metadata/doc.json", + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "dws-tool-metadata/doc marks this tool as reviewed", + "candidates": [ + { + "value": "Reviewed unpinned remote adapter: this executable CLI wrapper calls a remote helper that is absent from the pinned MCP metadata snapshot; no single pinned semantically equivalent interface_ref can represent the command.", + "source": "internal/cli/schema_hints/metadata/doc.json", + "precedence": "reviewed_explicit", + "selected": true, + "review_reason": "dws-tool-metadata/doc marks this tool as reviewed" + } + ] + }, + "interface_ref": { + "value": null, + "source": "internal/cli/schema_hints/metadata/doc.json", + "precedence": "reviewed_explicit", + "resolution": "interface_disposition_matrix", + "review_reason": "final interface mode composite forbids a direct MCP interface_ref", + "candidates": [ + { + "value": null, + "source": "internal/cli/schema_hints/metadata/doc.json", + "precedence": "reviewed_explicit", + "selected": true, + "review_reason": "final interface mode composite forbids a direct MCP interface_ref" + } + ] + }, + "reviewed": { + "value": true, + "source": "internal/cli/schema_hints/metadata/doc.json", + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "dws-tool-metadata/doc marks this tool as reviewed", + "candidates": [ + { + "value": true, + "source": "internal/cli/schema_hints/metadata/doc.json", + "precedence": "reviewed_explicit", + "selected": true, + "review_reason": "dws-tool-metadata/doc marks this tool as reviewed" + }, + { + "value": true, + "source": "internal/cli/schema_hints/selection/doc.json", + "precedence": "reviewed_explicit", + "selected": false, + "review_reason": "人工审阅:对照悟空 develop 同名命令实现、Cobra Long/Example 与 Runtime 确认门禁移植;不改变命令身份、参数契约或接口绑定。" + } + ] + }, + "risk": { + "value": "low", + "source": "internal/cli/schema_hints/metadata/doc.json", + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "dws-tool-metadata/doc marks this tool as reviewed", + "candidates": [ + { + "value": "low", + "source": "internal/cli/schema_hints/metadata/doc.json", + "precedence": "reviewed_explicit", + "selected": true, + "review_reason": "dws-tool-metadata/doc marks this tool as reviewed" + } + ] + }, + "use_when": { + "value": [ + "用户说 给文档设置封面/换个封面图" + ], + "source": "internal/cli/schema_hints/selection/doc.json", + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "人工审阅:对照悟空 develop 同名命令实现、Cobra Long/Example 与 Runtime 确认门禁移植;不改变命令身份、参数契约或接口绑定。", + "candidates": [ + { + "value": [ + "用户说 给文档设置封面/换个封面图" + ], + "source": "internal/cli/schema_hints/selection/doc.json", + "precedence": "reviewed_explicit", + "selected": true, + "review_reason": "人工审阅:对照悟空 develop 同名命令实现、Cobra Long/Example 与 Runtime 确认门禁移植;不改变命令身份、参数契约或接口绑定。" + } + ] + } + }, + "idempotency": "idempotent", + "interface_mode": "composite", + "interface_reason": "Reviewed unpinned remote adapter: this executable CLI wrapper calls a remote helper that is absent from the pinned MCP metadata snapshot; no single pinned semantically equivalent interface_ref can represent the command.", + "reviewed": true, + "risk": "low", + "source_refs": [ + "CommandRegistry:canonical_path=doc.style_cover_set", + "cobra-help:dws doc style cover set", + "internal/cli/schema_hints/metadata/doc.json", + "internal/cli/schema_hints/selection/doc.json", + "wukong-develop:wukong/products" + ], + "use_when": [ + "用户说 给文档设置封面/换个封面图" + ] + }, + "doc style get": { + "agent_summary": "读取文档当前封面与背景配置(只读)", + "agent_summary_source": "dws-agent-selection/doc", + "availability": "available", + "avoid_when": [ + "修改用 cover set / background set" + ], + "confirmation": "not_required", + "effect": "read", + "effect_source": "agent-hint", + "examples": [ + "dws doc style get --node \u003cDOC_ID\u003e --format json" + ], + "field_provenance": { + "agent_summary": { + "value": "读取文档当前封面与背景配置(只读)", + "source": "internal/cli/schema_hints/selection/doc.json", + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "人工审阅:对照悟空 develop 同名命令实现、Cobra Long/Example 与 Runtime 确认门禁移植;不改变命令身份、参数契约或接口绑定。", + "candidates": [ + { + "value": "读取文档当前封面与背景配置(只读)", + "source": "internal/cli/schema_hints/selection/doc.json", + "precedence": "reviewed_explicit", + "selected": true, + "review_reason": "人工审阅:对照悟空 develop 同名命令实现、Cobra Long/Example 与 Runtime 确认门禁移植;不改变命令身份、参数契约或接口绑定。" + } + ] + }, + "availability": { + "value": "available", + "source": "internal/cli/schema_hints/metadata/doc.json", + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "dws-tool-metadata/doc marks this tool as reviewed", + "candidates": [ + { + "value": "available", + "source": "internal/cli/schema_hints/metadata/doc.json", + "precedence": "reviewed_explicit", + "selected": true, + "review_reason": "dws-tool-metadata/doc marks this tool as reviewed" + } + ] + }, + "avoid_when": { + "value": [ + "修改用 cover set / background set" + ], + "source": "internal/cli/schema_hints/selection/doc.json", + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "人工审阅:对照悟空 develop 同名命令实现、Cobra Long/Example 与 Runtime 确认门禁移植;不改变命令身份、参数契约或接口绑定。", + "candidates": [ + { + "value": [ + "修改用 cover set / background set" + ], + "source": "internal/cli/schema_hints/selection/doc.json", + "precedence": "reviewed_explicit", + "selected": true, + "review_reason": "人工审阅:对照悟空 develop 同名命令实现、Cobra Long/Example 与 Runtime 确认门禁移植;不改变命令身份、参数契约或接口绑定。" + } + ] + }, + "confirmation": { + "value": "not_required", + "source": "internal/cli/schema_hints/metadata/doc.json", + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "dws-tool-metadata/doc marks this tool as reviewed", + "candidates": [ + { + "value": "not_required", + "source": "internal/cli/schema_hints/metadata/doc.json", + "precedence": "reviewed_explicit", + "selected": true, + "review_reason": "dws-tool-metadata/doc marks this tool as reviewed" + } + ] + }, + "effect": { + "value": "read", + "source": "internal/cli/schema_hints/metadata/doc.json", + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "dws-tool-metadata/doc marks this tool as reviewed", + "candidates": [ + { + "value": "read", + "source": "internal/cli/schema_hints/metadata/doc.json", + "precedence": "reviewed_explicit", + "selected": true, + "review_reason": "dws-tool-metadata/doc marks this tool as reviewed" + } + ] + }, + "examples": { + "value": [ + "dws doc style get --node \u003cDOC_ID\u003e --format json" + ], + "source": "internal/cli/schema_hints/selection/doc.json", + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "人工审阅:对照悟空 develop 同名命令实现、Cobra Long/Example 与 Runtime 确认门禁移植;不改变命令身份、参数契约或接口绑定。", + "candidates": [ + { + "value": [ + "dws doc style get --node \u003cDOC_ID\u003e --format json" + ], + "source": "internal/cli/schema_hints/selection/doc.json", + "precedence": "reviewed_explicit", + "selected": true, + "review_reason": "人工审阅:对照悟空 develop 同名命令实现、Cobra Long/Example 与 Runtime 确认门禁移植;不改变命令身份、参数契约或接口绑定。" + } + ] + }, + "idempotency": { + "value": "idempotent", + "source": "internal/cli/schema_hints/metadata/doc.json", + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "dws-tool-metadata/doc marks this tool as reviewed", + "candidates": [ + { + "value": "idempotent", + "source": "internal/cli/schema_hints/metadata/doc.json", + "precedence": "reviewed_explicit", + "selected": true, + "review_reason": "dws-tool-metadata/doc marks this tool as reviewed" + } + ] + }, + "interface_mode": { + "value": "composite", + "source": "internal/cli/schema_hints/metadata/doc.json", + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "dws-tool-metadata/doc marks this tool as reviewed", + "candidates": [ + { + "value": "composite", + "source": "internal/cli/schema_hints/metadata/doc.json", + "precedence": "reviewed_explicit", + "selected": true, + "review_reason": "dws-tool-metadata/doc marks this tool as reviewed" + } + ] + }, + "interface_reason": { + "value": "Reviewed unpinned remote adapter: this executable CLI wrapper calls a remote helper that is absent from the pinned MCP metadata snapshot; no single pinned semantically equivalent interface_ref can represent the command.", + "source": "internal/cli/schema_hints/metadata/doc.json", + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "dws-tool-metadata/doc marks this tool as reviewed", + "candidates": [ + { + "value": "Reviewed unpinned remote adapter: this executable CLI wrapper calls a remote helper that is absent from the pinned MCP metadata snapshot; no single pinned semantically equivalent interface_ref can represent the command.", + "source": "internal/cli/schema_hints/metadata/doc.json", + "precedence": "reviewed_explicit", + "selected": true, + "review_reason": "dws-tool-metadata/doc marks this tool as reviewed" + } + ] + }, + "interface_ref": { + "value": null, + "source": "internal/cli/schema_hints/metadata/doc.json", + "precedence": "reviewed_explicit", + "resolution": "interface_disposition_matrix", + "review_reason": "final interface mode composite forbids a direct MCP interface_ref", + "candidates": [ + { + "value": null, + "source": "internal/cli/schema_hints/metadata/doc.json", + "precedence": "reviewed_explicit", + "selected": true, + "review_reason": "final interface mode composite forbids a direct MCP interface_ref" + } + ] + }, + "reviewed": { + "value": true, + "source": "internal/cli/schema_hints/metadata/doc.json", + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "dws-tool-metadata/doc marks this tool as reviewed", + "candidates": [ + { + "value": true, + "source": "internal/cli/schema_hints/metadata/doc.json", + "precedence": "reviewed_explicit", + "selected": true, + "review_reason": "dws-tool-metadata/doc marks this tool as reviewed" + }, + { + "value": true, + "source": "internal/cli/schema_hints/selection/doc.json", + "precedence": "reviewed_explicit", + "selected": false, + "review_reason": "人工审阅:对照悟空 develop 同名命令实现、Cobra Long/Example 与 Runtime 确认门禁移植;不改变命令身份、参数契约或接口绑定。" + } + ] + }, + "risk": { + "value": "low", + "source": "internal/cli/schema_hints/metadata/doc.json", + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "dws-tool-metadata/doc marks this tool as reviewed", + "candidates": [ + { + "value": "low", + "source": "internal/cli/schema_hints/metadata/doc.json", + "precedence": "reviewed_explicit", + "selected": true, + "review_reason": "dws-tool-metadata/doc marks this tool as reviewed" + } + ] + }, + "use_when": { + "value": [ + "用户说 看下文档现在的封面/背景" + ], + "source": "internal/cli/schema_hints/selection/doc.json", + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "人工审阅:对照悟空 develop 同名命令实现、Cobra Long/Example 与 Runtime 确认门禁移植;不改变命令身份、参数契约或接口绑定。", + "candidates": [ + { + "value": [ + "用户说 看下文档现在的封面/背景" + ], + "source": "internal/cli/schema_hints/selection/doc.json", + "precedence": "reviewed_explicit", + "selected": true, + "review_reason": "人工审阅:对照悟空 develop 同名命令实现、Cobra Long/Example 与 Runtime 确认门禁移植;不改变命令身份、参数契约或接口绑定。" + } + ] + } + }, + "idempotency": "idempotent", + "interface_mode": "composite", + "interface_reason": "Reviewed unpinned remote adapter: this executable CLI wrapper calls a remote helper that is absent from the pinned MCP metadata snapshot; no single pinned semantically equivalent interface_ref can represent the command.", + "reviewed": true, + "risk": "low", + "source_refs": [ + "CommandRegistry:canonical_path=doc.get_document_style", + "cobra-help:dws doc style get", + "internal/cli/schema_hints/metadata/doc.json", + "internal/cli/schema_hints/selection/doc.json", + "wukong-develop:wukong/products" + ], + "use_when": [ + "用户说 看下文档现在的封面/背景" + ] + }, "doc template apply": { "agent_summary": "使用指定模板创建新文档", "agent_summary_source": "dws-agent-selection/doc", diff --git a/internal/cli/schema_agent_metadata/drive.json b/internal/cli/schema_agent_metadata/drive.json index 22fd9b37..a46029fa 100644 --- a/internal/cli/schema_agent_metadata/drive.json +++ b/internal/cli/schema_agent_metadata/drive.json @@ -2364,6 +2364,264 @@ "跨钉盘 space 复制到子文件夹:先 list 取目标文件夹 fileId,再 --folder 传入" ] }, + "drive cover": { + "agent_summary": "获取节点封面图片地址(文档首图/图片缩略图/类型图标)", + "agent_summary_source": "dws-agent-selection/drive", + "availability": "available", + "avoid_when": [ + "设置文档封面用 doc style cover set" + ], + "confirmation": "not_required", + "effect": "read", + "effect_source": "agent-hint", + "examples": [ + "dws drive cover --node \u003cdentryUuid\u003e --format json" + ], + "field_provenance": { + "agent_summary": { + "value": "获取节点封面图片地址(文档首图/图片缩略图/类型图标)", + "source": "internal/cli/schema_hints/selection/drive.json", + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "人工审阅:对照悟空 develop 同名命令实现、Cobra Long/Example 与 Runtime 确认门禁移植;不改变命令身份、参数契约或接口绑定。", + "candidates": [ + { + "value": "获取节点封面图片地址(文档首图/图片缩略图/类型图标)", + "source": "internal/cli/schema_hints/selection/drive.json", + "precedence": "reviewed_explicit", + "selected": true, + "review_reason": "人工审阅:对照悟空 develop 同名命令实现、Cobra Long/Example 与 Runtime 确认门禁移植;不改变命令身份、参数契约或接口绑定。" + } + ] + }, + "availability": { + "value": "available", + "source": "internal/cli/schema_hints/metadata/drive.json", + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "dws-tool-metadata/drive marks this tool as reviewed", + "candidates": [ + { + "value": "available", + "source": "internal/cli/schema_hints/metadata/drive.json", + "precedence": "reviewed_explicit", + "selected": true, + "review_reason": "dws-tool-metadata/drive marks this tool as reviewed" + } + ] + }, + "avoid_when": { + "value": [ + "设置文档封面用 doc style cover set" + ], + "source": "internal/cli/schema_hints/selection/drive.json", + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "人工审阅:对照悟空 develop 同名命令实现、Cobra Long/Example 与 Runtime 确认门禁移植;不改变命令身份、参数契约或接口绑定。", + "candidates": [ + { + "value": [ + "设置文档封面用 doc style cover set" + ], + "source": "internal/cli/schema_hints/selection/drive.json", + "precedence": "reviewed_explicit", + "selected": true, + "review_reason": "人工审阅:对照悟空 develop 同名命令实现、Cobra Long/Example 与 Runtime 确认门禁移植;不改变命令身份、参数契约或接口绑定。" + } + ] + }, + "confirmation": { + "value": "not_required", + "source": "internal/cli/schema_hints/metadata/drive.json", + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "dws-tool-metadata/drive marks this tool as reviewed", + "candidates": [ + { + "value": "not_required", + "source": "internal/cli/schema_hints/metadata/drive.json", + "precedence": "reviewed_explicit", + "selected": true, + "review_reason": "dws-tool-metadata/drive marks this tool as reviewed" + } + ] + }, + "effect": { + "value": "read", + "source": "internal/cli/schema_hints/metadata/drive.json", + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "dws-tool-metadata/drive marks this tool as reviewed", + "candidates": [ + { + "value": "read", + "source": "internal/cli/schema_hints/metadata/drive.json", + "precedence": "reviewed_explicit", + "selected": true, + "review_reason": "dws-tool-metadata/drive marks this tool as reviewed" + } + ] + }, + "examples": { + "value": [ + "dws drive cover --node \u003cdentryUuid\u003e --format json" + ], + "source": "internal/cli/schema_hints/selection/drive.json", + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "人工审阅:对照悟空 develop 同名命令实现、Cobra Long/Example 与 Runtime 确认门禁移植;不改变命令身份、参数契约或接口绑定。", + "candidates": [ + { + "value": [ + "dws drive cover --node \u003cdentryUuid\u003e --format json" + ], + "source": "internal/cli/schema_hints/selection/drive.json", + "precedence": "reviewed_explicit", + "selected": true, + "review_reason": "人工审阅:对照悟空 develop 同名命令实现、Cobra Long/Example 与 Runtime 确认门禁移植;不改变命令身份、参数契约或接口绑定。" + } + ] + }, + "idempotency": { + "value": "idempotent", + "source": "internal/cli/schema_hints/metadata/drive.json", + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "dws-tool-metadata/drive marks this tool as reviewed", + "candidates": [ + { + "value": "idempotent", + "source": "internal/cli/schema_hints/metadata/drive.json", + "precedence": "reviewed_explicit", + "selected": true, + "review_reason": "dws-tool-metadata/drive marks this tool as reviewed" + } + ] + }, + "interface_mode": { + "value": "composite", + "source": "internal/cli/schema_hints/metadata/drive.json", + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "dws-tool-metadata/drive marks this tool as reviewed", + "candidates": [ + { + "value": "composite", + "source": "internal/cli/schema_hints/metadata/drive.json", + "precedence": "reviewed_explicit", + "selected": true, + "review_reason": "dws-tool-metadata/drive marks this tool as reviewed" + } + ] + }, + "interface_reason": { + "value": "Reviewed unpinned remote adapter: this executable CLI wrapper calls a remote helper that is absent from the pinned MCP metadata snapshot; no single pinned semantically equivalent interface_ref can represent the command.", + "source": "internal/cli/schema_hints/metadata/drive.json", + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "dws-tool-metadata/drive marks this tool as reviewed", + "candidates": [ + { + "value": "Reviewed unpinned remote adapter: this executable CLI wrapper calls a remote helper that is absent from the pinned MCP metadata snapshot; no single pinned semantically equivalent interface_ref can represent the command.", + "source": "internal/cli/schema_hints/metadata/drive.json", + "precedence": "reviewed_explicit", + "selected": true, + "review_reason": "dws-tool-metadata/drive marks this tool as reviewed" + } + ] + }, + "interface_ref": { + "value": null, + "source": "internal/cli/schema_hints/metadata/drive.json", + "precedence": "reviewed_explicit", + "resolution": "interface_disposition_matrix", + "review_reason": "final interface mode composite forbids a direct MCP interface_ref", + "candidates": [ + { + "value": null, + "source": "internal/cli/schema_hints/metadata/drive.json", + "precedence": "reviewed_explicit", + "selected": true, + "review_reason": "final interface mode composite forbids a direct MCP interface_ref" + } + ] + }, + "reviewed": { + "value": true, + "source": "internal/cli/schema_hints/metadata/drive.json", + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "dws-tool-metadata/drive marks this tool as reviewed", + "candidates": [ + { + "value": true, + "source": "internal/cli/schema_hints/metadata/drive.json", + "precedence": "reviewed_explicit", + "selected": true, + "review_reason": "dws-tool-metadata/drive marks this tool as reviewed" + }, + { + "value": true, + "source": "internal/cli/schema_hints/selection/drive.json", + "precedence": "reviewed_explicit", + "selected": false, + "review_reason": "人工审阅:对照悟空 develop 同名命令实现、Cobra Long/Example 与 Runtime 确认门禁移植;不改变命令身份、参数契约或接口绑定。" + } + ] + }, + "risk": { + "value": "low", + "source": "internal/cli/schema_hints/metadata/drive.json", + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "dws-tool-metadata/drive marks this tool as reviewed", + "candidates": [ + { + "value": "low", + "source": "internal/cli/schema_hints/metadata/drive.json", + "precedence": "reviewed_explicit", + "selected": true, + "review_reason": "dws-tool-metadata/drive marks this tool as reviewed" + } + ] + }, + "use_when": { + "value": [ + "用户说 封面/封面图/缩略图/预览图" + ], + "source": "internal/cli/schema_hints/selection/drive.json", + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "人工审阅:对照悟空 develop 同名命令实现、Cobra Long/Example 与 Runtime 确认门禁移植;不改变命令身份、参数契约或接口绑定。", + "candidates": [ + { + "value": [ + "用户说 封面/封面图/缩略图/预览图" + ], + "source": "internal/cli/schema_hints/selection/drive.json", + "precedence": "reviewed_explicit", + "selected": true, + "review_reason": "人工审阅:对照悟空 develop 同名命令实现、Cobra Long/Example 与 Runtime 确认门禁移植;不改变命令身份、参数契约或接口绑定。" + } + ] + } + }, + "idempotency": "idempotent", + "interface_mode": "composite", + "interface_reason": "Reviewed unpinned remote adapter: this executable CLI wrapper calls a remote helper that is absent from the pinned MCP metadata snapshot; no single pinned semantically equivalent interface_ref can represent the command.", + "reviewed": true, + "risk": "low", + "source_refs": [ + "CommandRegistry:canonical_path=drive.get_cover", + "cobra-help:dws drive cover", + "internal/cli/schema_hints/metadata/drive.json", + "internal/cli/schema_hints/selection/drive.json", + "wukong-develop:wukong/products" + ], + "use_when": [ + "用户说 封面/封面图/缩略图/预览图" + ] + }, "drive delete": { "agent_summary": "将钉盘中的文件或文件夹移入回收站", "agent_summary_source": "dws-agent-selection/drive", @@ -2929,6 +3187,273 @@ "已确认 contentType 非 ALIDOC,需要落盘本地查看时" ] }, + "drive download-version": { + "agent_summary": "下载钉盘普通文件的指定历史版本到本地(两步下载:取签名 URL 后 HTTP GET)", + "agent_summary_source": "dws-agent-selection/drive", + "availability": "available", + "avoid_when": [ + "下载最新版本用 drive download", + "在线文档(adoc)历史版本用 doc version 系列命令", + "在线表格(axls)历史版本用 sheet version 系列命令" + ], + "confirmation": "not_required", + "effect": "read", + "effect_source": "agent-hint", + "examples": [ + "dws drive download-version --node \u003cdentryUuid\u003e --version 3 --output ./report_v3.pdf" + ], + "field_provenance": { + "agent_summary": { + "value": "下载钉盘普通文件的指定历史版本到本地(两步下载:取签名 URL 后 HTTP GET)", + "source": "internal/cli/schema_hints/selection/drive.json", + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "人工审阅:对照悟空 develop drive download --version 分支实现移植为独立命令;单一 download_file_version 远端调用,参数契约与帮助文档一致。", + "candidates": [ + { + "value": "下载钉盘普通文件的指定历史版本到本地(两步下载:取签名 URL 后 HTTP GET)", + "source": "internal/cli/schema_hints/selection/drive.json", + "precedence": "reviewed_explicit", + "selected": true, + "review_reason": "人工审阅:对照悟空 develop drive download --version 分支实现移植为独立命令;单一 download_file_version 远端调用,参数契约与帮助文档一致。" + } + ] + }, + "availability": { + "value": "available", + "source": "internal/cli/schema_hints/metadata/drive.json", + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "dws-tool-metadata/drive marks this tool as reviewed", + "candidates": [ + { + "value": "available", + "source": "internal/cli/schema_hints/metadata/drive.json", + "precedence": "reviewed_explicit", + "selected": true, + "review_reason": "dws-tool-metadata/drive marks this tool as reviewed" + } + ] + }, + "avoid_when": { + "value": [ + "下载最新版本用 drive download", + "在线文档(adoc)历史版本用 doc version 系列命令", + "在线表格(axls)历史版本用 sheet version 系列命令" + ], + "source": "internal/cli/schema_hints/selection/drive.json", + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "人工审阅:对照悟空 develop drive download --version 分支实现移植为独立命令;单一 download_file_version 远端调用,参数契约与帮助文档一致。", + "candidates": [ + { + "value": [ + "下载最新版本用 drive download", + "在线文档(adoc)历史版本用 doc version 系列命令", + "在线表格(axls)历史版本用 sheet version 系列命令" + ], + "source": "internal/cli/schema_hints/selection/drive.json", + "precedence": "reviewed_explicit", + "selected": true, + "review_reason": "人工审阅:对照悟空 develop drive download --version 分支实现移植为独立命令;单一 download_file_version 远端调用,参数契约与帮助文档一致。" + } + ] + }, + "confirmation": { + "value": "not_required", + "source": "internal/cli/schema_hints/metadata/drive.json", + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "dws-tool-metadata/drive marks this tool as reviewed", + "candidates": [ + { + "value": "not_required", + "source": "internal/cli/schema_hints/metadata/drive.json", + "precedence": "reviewed_explicit", + "selected": true, + "review_reason": "dws-tool-metadata/drive marks this tool as reviewed" + } + ] + }, + "effect": { + "value": "read", + "source": "internal/cli/schema_hints/metadata/drive.json", + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "dws-tool-metadata/drive marks this tool as reviewed", + "candidates": [ + { + "value": "read", + "source": "internal/cli/schema_hints/metadata/drive.json", + "precedence": "reviewed_explicit", + "selected": true, + "review_reason": "dws-tool-metadata/drive marks this tool as reviewed" + } + ] + }, + "examples": { + "value": [ + "dws drive download-version --node \u003cdentryUuid\u003e --version 3 --output ./report_v3.pdf" + ], + "source": "internal/cli/schema_hints/selection/drive.json", + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "人工审阅:对照悟空 develop drive download --version 分支实现移植为独立命令;单一 download_file_version 远端调用,参数契约与帮助文档一致。", + "candidates": [ + { + "value": [ + "dws drive download-version --node \u003cdentryUuid\u003e --version 3 --output ./report_v3.pdf" + ], + "source": "internal/cli/schema_hints/selection/drive.json", + "precedence": "reviewed_explicit", + "selected": true, + "review_reason": "人工审阅:对照悟空 develop drive download --version 分支实现移植为独立命令;单一 download_file_version 远端调用,参数契约与帮助文档一致。" + } + ] + }, + "idempotency": { + "value": "idempotent", + "source": "internal/cli/schema_hints/metadata/drive.json", + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "dws-tool-metadata/drive marks this tool as reviewed", + "candidates": [ + { + "value": "idempotent", + "source": "internal/cli/schema_hints/metadata/drive.json", + "precedence": "reviewed_explicit", + "selected": true, + "review_reason": "dws-tool-metadata/drive marks this tool as reviewed" + } + ] + }, + "interface_mode": { + "value": "composite", + "source": "internal/cli/schema_hints/metadata/drive.json", + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "dws-tool-metadata/drive marks this tool as reviewed", + "candidates": [ + { + "value": "composite", + "source": "internal/cli/schema_hints/metadata/drive.json", + "precedence": "reviewed_explicit", + "selected": true, + "review_reason": "dws-tool-metadata/drive marks this tool as reviewed" + } + ] + }, + "interface_reason": { + "value": "Reviewed unpinned remote adapter: this executable CLI wrapper calls a remote helper that is absent from the pinned MCP metadata snapshot; no single pinned semantically equivalent interface_ref can represent the command.", + "source": "internal/cli/schema_hints/metadata/drive.json", + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "dws-tool-metadata/drive marks this tool as reviewed", + "candidates": [ + { + "value": "Reviewed unpinned remote adapter: this executable CLI wrapper calls a remote helper that is absent from the pinned MCP metadata snapshot; no single pinned semantically equivalent interface_ref can represent the command.", + "source": "internal/cli/schema_hints/metadata/drive.json", + "precedence": "reviewed_explicit", + "selected": true, + "review_reason": "dws-tool-metadata/drive marks this tool as reviewed" + } + ] + }, + "interface_ref": { + "value": null, + "source": "internal/cli/schema_hints/metadata/drive.json", + "precedence": "reviewed_explicit", + "resolution": "interface_disposition_matrix", + "review_reason": "final interface mode composite forbids a direct MCP interface_ref", + "candidates": [ + { + "value": null, + "source": "internal/cli/schema_hints/metadata/drive.json", + "precedence": "reviewed_explicit", + "selected": true, + "review_reason": "final interface mode composite forbids a direct MCP interface_ref" + } + ] + }, + "reviewed": { + "value": true, + "source": "internal/cli/schema_hints/metadata/drive.json", + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "dws-tool-metadata/drive marks this tool as reviewed", + "candidates": [ + { + "value": true, + "source": "internal/cli/schema_hints/metadata/drive.json", + "precedence": "reviewed_explicit", + "selected": true, + "review_reason": "dws-tool-metadata/drive marks this tool as reviewed" + }, + { + "value": true, + "source": "internal/cli/schema_hints/selection/drive.json", + "precedence": "reviewed_explicit", + "selected": false, + "review_reason": "人工审阅:对照悟空 develop drive download --version 分支实现移植为独立命令;单一 download_file_version 远端调用,参数契约与帮助文档一致。" + } + ] + }, + "risk": { + "value": "low", + "source": "internal/cli/schema_hints/metadata/drive.json", + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "dws-tool-metadata/drive marks this tool as reviewed", + "candidates": [ + { + "value": "low", + "source": "internal/cli/schema_hints/metadata/drive.json", + "precedence": "reviewed_explicit", + "selected": true, + "review_reason": "dws-tool-metadata/drive marks this tool as reviewed" + } + ] + }, + "use_when": { + "value": [ + "用户要下载文件的历史版本/旧版本", + "版本号已通过 drive list --versions 获取" + ], + "source": "internal/cli/schema_hints/selection/drive.json", + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "人工审阅:对照悟空 develop drive download --version 分支实现移植为独立命令;单一 download_file_version 远端调用,参数契约与帮助文档一致。", + "candidates": [ + { + "value": [ + "用户要下载文件的历史版本/旧版本", + "版本号已通过 drive list --versions 获取" + ], + "source": "internal/cli/schema_hints/selection/drive.json", + "precedence": "reviewed_explicit", + "selected": true, + "review_reason": "人工审阅:对照悟空 develop drive download --version 分支实现移植为独立命令;单一 download_file_version 远端调用,参数契约与帮助文档一致。" + } + ] + } + }, + "idempotency": "idempotent", + "interface_mode": "composite", + "interface_reason": "Reviewed unpinned remote adapter: this executable CLI wrapper calls a remote helper that is absent from the pinned MCP metadata snapshot; no single pinned semantically equivalent interface_ref can represent the command.", + "reviewed": true, + "risk": "low", + "source_refs": [ + "CommandRegistry:canonical_path=drive.download_file_version", + "cobra-help:dws drive download-version", + "internal/cli/schema_hints/metadata/drive.json", + "internal/cli/schema_hints/selection/drive.json", + "wukong-develop:wukong/products/drive.go" + ], + "use_when": [ + "用户要下载文件的历史版本/旧版本", + "版本号已通过 drive list --versions 获取" + ] + }, "drive info": { "agent_summary": "获取文件元数据信息", "agent_summary_source": "dws-agent-selection/drive", @@ -4574,6 +5099,522 @@ "用户说把某篇文档分享给某人看/可编辑时(含「我的文档」下的节点)" ] }, + "drive permission apply": { + "agent_summary": "向审批人发起文档权限申请(会真实通知审批人)", + "agent_summary_source": "dws-agent-selection/drive", + "availability": "available", + "avoid_when": [ + "先用 apply-info 查可申请角色与审批人;未经用户确认不得自行提交" + ], + "confirmation": "user_required", + "effect": "write", + "effect_source": "agent-hint", + "examples": [ + "dws drive permission apply --node \u003cDOC_ID\u003e --role READER --users \u003c审批人userId\u003e --format json" + ], + "field_provenance": { + "agent_summary": { + "value": "向审批人发起文档权限申请(会真实通知审批人)", + "source": "internal/cli/schema_hints/selection/drive.json", + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "人工审阅:对照悟空 develop 同名命令实现、Cobra Long/Example 与 Runtime 确认门禁移植;不改变命令身份、参数契约或接口绑定。", + "candidates": [ + { + "value": "向审批人发起文档权限申请(会真实通知审批人)", + "source": "internal/cli/schema_hints/selection/drive.json", + "precedence": "reviewed_explicit", + "selected": true, + "review_reason": "人工审阅:对照悟空 develop 同名命令实现、Cobra Long/Example 与 Runtime 确认门禁移植;不改变命令身份、参数契约或接口绑定。" + } + ] + }, + "availability": { + "value": "available", + "source": "internal/cli/schema_hints/metadata/drive.json", + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "dws-tool-metadata/drive marks this tool as reviewed", + "candidates": [ + { + "value": "available", + "source": "internal/cli/schema_hints/metadata/drive.json", + "precedence": "reviewed_explicit", + "selected": true, + "review_reason": "dws-tool-metadata/drive marks this tool as reviewed" + } + ] + }, + "avoid_when": { + "value": [ + "先用 apply-info 查可申请角色与审批人;未经用户确认不得自行提交" + ], + "source": "internal/cli/schema_hints/selection/drive.json", + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "人工审阅:对照悟空 develop 同名命令实现、Cobra Long/Example 与 Runtime 确认门禁移植;不改变命令身份、参数契约或接口绑定。", + "candidates": [ + { + "value": [ + "先用 apply-info 查可申请角色与审批人;未经用户确认不得自行提交" + ], + "source": "internal/cli/schema_hints/selection/drive.json", + "precedence": "reviewed_explicit", + "selected": true, + "review_reason": "人工审阅:对照悟空 develop 同名命令实现、Cobra Long/Example 与 Runtime 确认门禁移植;不改变命令身份、参数契约或接口绑定。" + } + ] + }, + "confirmation": { + "value": "user_required", + "source": "internal/cli/schema_hints/metadata/drive.json", + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "dws-tool-metadata/drive marks this tool as reviewed", + "candidates": [ + { + "value": "user_required", + "source": "internal/cli/schema_hints/metadata/drive.json", + "precedence": "reviewed_explicit", + "selected": true, + "review_reason": "dws-tool-metadata/drive marks this tool as reviewed" + } + ] + }, + "effect": { + "value": "write", + "source": "internal/cli/schema_hints/metadata/drive.json", + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "dws-tool-metadata/drive marks this tool as reviewed", + "candidates": [ + { + "value": "write", + "source": "internal/cli/schema_hints/metadata/drive.json", + "precedence": "reviewed_explicit", + "selected": true, + "review_reason": "dws-tool-metadata/drive marks this tool as reviewed" + } + ] + }, + "examples": { + "value": [ + "dws drive permission apply --node \u003cDOC_ID\u003e --role READER --users \u003c审批人userId\u003e --format json" + ], + "source": "internal/cli/schema_hints/selection/drive.json", + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "人工审阅:对照悟空 develop 同名命令实现、Cobra Long/Example 与 Runtime 确认门禁移植;不改变命令身份、参数契约或接口绑定。", + "candidates": [ + { + "value": [ + "dws drive permission apply --node \u003cDOC_ID\u003e --role READER --users \u003c审批人userId\u003e --format json" + ], + "source": "internal/cli/schema_hints/selection/drive.json", + "precedence": "reviewed_explicit", + "selected": true, + "review_reason": "人工审阅:对照悟空 develop 同名命令实现、Cobra Long/Example 与 Runtime 确认门禁移植;不改变命令身份、参数契约或接口绑定。" + } + ] + }, + "idempotency": { + "value": "non_idempotent", + "source": "internal/cli/schema_hints/metadata/drive.json", + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "dws-tool-metadata/drive marks this tool as reviewed", + "candidates": [ + { + "value": "non_idempotent", + "source": "internal/cli/schema_hints/metadata/drive.json", + "precedence": "reviewed_explicit", + "selected": true, + "review_reason": "dws-tool-metadata/drive marks this tool as reviewed" + } + ] + }, + "interface_mode": { + "value": "composite", + "source": "internal/cli/schema_hints/metadata/drive.json", + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "dws-tool-metadata/drive marks this tool as reviewed", + "candidates": [ + { + "value": "composite", + "source": "internal/cli/schema_hints/metadata/drive.json", + "precedence": "reviewed_explicit", + "selected": true, + "review_reason": "dws-tool-metadata/drive marks this tool as reviewed" + } + ] + }, + "interface_reason": { + "value": "Reviewed unpinned remote adapter: this executable CLI wrapper calls a remote helper that is absent from the pinned MCP metadata snapshot; no single pinned semantically equivalent interface_ref can represent the command.", + "source": "internal/cli/schema_hints/metadata/drive.json", + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "dws-tool-metadata/drive marks this tool as reviewed", + "candidates": [ + { + "value": "Reviewed unpinned remote adapter: this executable CLI wrapper calls a remote helper that is absent from the pinned MCP metadata snapshot; no single pinned semantically equivalent interface_ref can represent the command.", + "source": "internal/cli/schema_hints/metadata/drive.json", + "precedence": "reviewed_explicit", + "selected": true, + "review_reason": "dws-tool-metadata/drive marks this tool as reviewed" + } + ] + }, + "interface_ref": { + "value": null, + "source": "internal/cli/schema_hints/metadata/drive.json", + "precedence": "reviewed_explicit", + "resolution": "interface_disposition_matrix", + "review_reason": "final interface mode composite forbids a direct MCP interface_ref", + "candidates": [ + { + "value": null, + "source": "internal/cli/schema_hints/metadata/drive.json", + "precedence": "reviewed_explicit", + "selected": true, + "review_reason": "final interface mode composite forbids a direct MCP interface_ref" + } + ] + }, + "reviewed": { + "value": true, + "source": "internal/cli/schema_hints/metadata/drive.json", + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "dws-tool-metadata/drive marks this tool as reviewed", + "candidates": [ + { + "value": true, + "source": "internal/cli/schema_hints/metadata/drive.json", + "precedence": "reviewed_explicit", + "selected": true, + "review_reason": "dws-tool-metadata/drive marks this tool as reviewed" + }, + { + "value": true, + "source": "internal/cli/schema_hints/selection/drive.json", + "precedence": "reviewed_explicit", + "selected": false, + "review_reason": "人工审阅:对照悟空 develop 同名命令实现、Cobra Long/Example 与 Runtime 确认门禁移植;不改变命令身份、参数契约或接口绑定。" + } + ] + }, + "risk": { + "value": "medium", + "source": "internal/cli/schema_hints/metadata/drive.json", + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "dws-tool-metadata/drive marks this tool as reviewed", + "candidates": [ + { + "value": "medium", + "source": "internal/cli/schema_hints/metadata/drive.json", + "precedence": "reviewed_explicit", + "selected": true, + "review_reason": "dws-tool-metadata/drive marks this tool as reviewed" + } + ] + }, + "use_when": { + "value": [ + "用户确认要为无权限文档发起权限申请" + ], + "source": "internal/cli/schema_hints/selection/drive.json", + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "人工审阅:对照悟空 develop 同名命令实现、Cobra Long/Example 与 Runtime 确认门禁移植;不改变命令身份、参数契约或接口绑定。", + "candidates": [ + { + "value": [ + "用户确认要为无权限文档发起权限申请" + ], + "source": "internal/cli/schema_hints/selection/drive.json", + "precedence": "reviewed_explicit", + "selected": true, + "review_reason": "人工审阅:对照悟空 develop 同名命令实现、Cobra Long/Example 与 Runtime 确认门禁移植;不改变命令身份、参数契约或接口绑定。" + } + ] + } + }, + "idempotency": "non_idempotent", + "interface_mode": "composite", + "interface_reason": "Reviewed unpinned remote adapter: this executable CLI wrapper calls a remote helper that is absent from the pinned MCP metadata snapshot; no single pinned semantically equivalent interface_ref can represent the command.", + "reviewed": true, + "risk": "medium", + "source_refs": [ + "CommandRegistry:canonical_path=drive.apply_permission", + "cobra-help:dws drive permission apply", + "internal/cli/schema_hints/metadata/drive.json", + "internal/cli/schema_hints/selection/drive.json", + "wukong-develop:wukong/products" + ], + "use_when": [ + "用户确认要为无权限文档发起权限申请" + ] + }, + "drive permission apply-info": { + "agent_summary": "查询节点可申请的权限角色列表与审批人列表", + "agent_summary_source": "dws-agent-selection/drive", + "availability": "available", + "avoid_when": [ + "实际发起申请用 apply_permission" + ], + "confirmation": "not_required", + "effect": "read", + "effect_source": "agent-hint", + "examples": [ + "dws drive permission apply-info --node \u003cDOC_ID\u003e --format json" + ], + "field_provenance": { + "agent_summary": { + "value": "查询节点可申请的权限角色列表与审批人列表", + "source": "internal/cli/schema_hints/selection/drive.json", + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "人工审阅:对照悟空 develop 同名命令实现、Cobra Long/Example 与 Runtime 确认门禁移植;不改变命令身份、参数契约或接口绑定。", + "candidates": [ + { + "value": "查询节点可申请的权限角色列表与审批人列表", + "source": "internal/cli/schema_hints/selection/drive.json", + "precedence": "reviewed_explicit", + "selected": true, + "review_reason": "人工审阅:对照悟空 develop 同名命令实现、Cobra Long/Example 与 Runtime 确认门禁移植;不改变命令身份、参数契约或接口绑定。" + } + ] + }, + "availability": { + "value": "available", + "source": "internal/cli/schema_hints/metadata/drive.json", + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "dws-tool-metadata/drive marks this tool as reviewed", + "candidates": [ + { + "value": "available", + "source": "internal/cli/schema_hints/metadata/drive.json", + "precedence": "reviewed_explicit", + "selected": true, + "review_reason": "dws-tool-metadata/drive marks this tool as reviewed" + } + ] + }, + "avoid_when": { + "value": [ + "实际发起申请用 apply_permission" + ], + "source": "internal/cli/schema_hints/selection/drive.json", + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "人工审阅:对照悟空 develop 同名命令实现、Cobra Long/Example 与 Runtime 确认门禁移植;不改变命令身份、参数契约或接口绑定。", + "candidates": [ + { + "value": [ + "实际发起申请用 apply_permission" + ], + "source": "internal/cli/schema_hints/selection/drive.json", + "precedence": "reviewed_explicit", + "selected": true, + "review_reason": "人工审阅:对照悟空 develop 同名命令实现、Cobra Long/Example 与 Runtime 确认门禁移植;不改变命令身份、参数契约或接口绑定。" + } + ] + }, + "confirmation": { + "value": "not_required", + "source": "internal/cli/schema_hints/metadata/drive.json", + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "dws-tool-metadata/drive marks this tool as reviewed", + "candidates": [ + { + "value": "not_required", + "source": "internal/cli/schema_hints/metadata/drive.json", + "precedence": "reviewed_explicit", + "selected": true, + "review_reason": "dws-tool-metadata/drive marks this tool as reviewed" + } + ] + }, + "effect": { + "value": "read", + "source": "internal/cli/schema_hints/metadata/drive.json", + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "dws-tool-metadata/drive marks this tool as reviewed", + "candidates": [ + { + "value": "read", + "source": "internal/cli/schema_hints/metadata/drive.json", + "precedence": "reviewed_explicit", + "selected": true, + "review_reason": "dws-tool-metadata/drive marks this tool as reviewed" + } + ] + }, + "examples": { + "value": [ + "dws drive permission apply-info --node \u003cDOC_ID\u003e --format json" + ], + "source": "internal/cli/schema_hints/selection/drive.json", + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "人工审阅:对照悟空 develop 同名命令实现、Cobra Long/Example 与 Runtime 确认门禁移植;不改变命令身份、参数契约或接口绑定。", + "candidates": [ + { + "value": [ + "dws drive permission apply-info --node \u003cDOC_ID\u003e --format json" + ], + "source": "internal/cli/schema_hints/selection/drive.json", + "precedence": "reviewed_explicit", + "selected": true, + "review_reason": "人工审阅:对照悟空 develop 同名命令实现、Cobra Long/Example 与 Runtime 确认门禁移植;不改变命令身份、参数契约或接口绑定。" + } + ] + }, + "idempotency": { + "value": "idempotent", + "source": "internal/cli/schema_hints/metadata/drive.json", + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "dws-tool-metadata/drive marks this tool as reviewed", + "candidates": [ + { + "value": "idempotent", + "source": "internal/cli/schema_hints/metadata/drive.json", + "precedence": "reviewed_explicit", + "selected": true, + "review_reason": "dws-tool-metadata/drive marks this tool as reviewed" + } + ] + }, + "interface_mode": { + "value": "composite", + "source": "internal/cli/schema_hints/metadata/drive.json", + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "dws-tool-metadata/drive marks this tool as reviewed", + "candidates": [ + { + "value": "composite", + "source": "internal/cli/schema_hints/metadata/drive.json", + "precedence": "reviewed_explicit", + "selected": true, + "review_reason": "dws-tool-metadata/drive marks this tool as reviewed" + } + ] + }, + "interface_reason": { + "value": "Reviewed unpinned remote adapter: this executable CLI wrapper calls a remote helper that is absent from the pinned MCP metadata snapshot; no single pinned semantically equivalent interface_ref can represent the command.", + "source": "internal/cli/schema_hints/metadata/drive.json", + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "dws-tool-metadata/drive marks this tool as reviewed", + "candidates": [ + { + "value": "Reviewed unpinned remote adapter: this executable CLI wrapper calls a remote helper that is absent from the pinned MCP metadata snapshot; no single pinned semantically equivalent interface_ref can represent the command.", + "source": "internal/cli/schema_hints/metadata/drive.json", + "precedence": "reviewed_explicit", + "selected": true, + "review_reason": "dws-tool-metadata/drive marks this tool as reviewed" + } + ] + }, + "interface_ref": { + "value": null, + "source": "internal/cli/schema_hints/metadata/drive.json", + "precedence": "reviewed_explicit", + "resolution": "interface_disposition_matrix", + "review_reason": "final interface mode composite forbids a direct MCP interface_ref", + "candidates": [ + { + "value": null, + "source": "internal/cli/schema_hints/metadata/drive.json", + "precedence": "reviewed_explicit", + "selected": true, + "review_reason": "final interface mode composite forbids a direct MCP interface_ref" + } + ] + }, + "reviewed": { + "value": true, + "source": "internal/cli/schema_hints/metadata/drive.json", + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "dws-tool-metadata/drive marks this tool as reviewed", + "candidates": [ + { + "value": true, + "source": "internal/cli/schema_hints/metadata/drive.json", + "precedence": "reviewed_explicit", + "selected": true, + "review_reason": "dws-tool-metadata/drive marks this tool as reviewed" + }, + { + "value": true, + "source": "internal/cli/schema_hints/selection/drive.json", + "precedence": "reviewed_explicit", + "selected": false, + "review_reason": "人工审阅:对照悟空 develop 同名命令实现、Cobra Long/Example 与 Runtime 确认门禁移植;不改变命令身份、参数契约或接口绑定。" + } + ] + }, + "risk": { + "value": "low", + "source": "internal/cli/schema_hints/metadata/drive.json", + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "dws-tool-metadata/drive marks this tool as reviewed", + "candidates": [ + { + "value": "low", + "source": "internal/cli/schema_hints/metadata/drive.json", + "precedence": "reviewed_explicit", + "selected": true, + "review_reason": "dws-tool-metadata/drive marks this tool as reviewed" + } + ] + }, + "use_when": { + "value": [ + "无权限访问文档时,先查可申请角色与审批人" + ], + "source": "internal/cli/schema_hints/selection/drive.json", + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "人工审阅:对照悟空 develop 同名命令实现、Cobra Long/Example 与 Runtime 确认门禁移植;不改变命令身份、参数契约或接口绑定。", + "candidates": [ + { + "value": [ + "无权限访问文档时,先查可申请角色与审批人" + ], + "source": "internal/cli/schema_hints/selection/drive.json", + "precedence": "reviewed_explicit", + "selected": true, + "review_reason": "人工审阅:对照悟空 develop 同名命令实现、Cobra Long/Example 与 Runtime 确认门禁移植;不改变命令身份、参数契约或接口绑定。" + } + ] + } + }, + "idempotency": "idempotent", + "interface_mode": "composite", + "interface_reason": "Reviewed unpinned remote adapter: this executable CLI wrapper calls a remote helper that is absent from the pinned MCP metadata snapshot; no single pinned semantically equivalent interface_ref can represent the command.", + "reviewed": true, + "risk": "low", + "source_refs": [ + "CommandRegistry:canonical_path=drive.query_permission_apply_info", + "cobra-help:dws drive permission apply-info", + "internal/cli/schema_hints/metadata/drive.json", + "internal/cli/schema_hints/selection/drive.json", + "wukong-develop:wukong/products" + ], + "use_when": [ + "无权限访问文档时,先查可申请角色与审批人" + ] + }, "drive permission list": { "agent_summary": "查询文档空间节点的协作者列表", "agent_summary_source": "dws-agent-selection/drive", @@ -5124,6 +6165,264 @@ "从节点移除指定用户的直接授权时" ] }, + "drive permission transfer-owner": { + "agent_summary": "转交文档或知识库所有者给指定用户(不可逆)", + "agent_summary_source": "dws-agent-selection/drive", + "availability": "available", + "avoid_when": [ + "普通协作权限变更用 permission add/update/remove" + ], + "confirmation": "user_required", + "effect": "write", + "effect_source": "agent-hint", + "examples": [ + "dws drive permission transfer-owner --node \u003cDOC_ID\u003e --new-owner \u003cuserId\u003e --reserve-role EDITOR --recursive=false --format json" + ], + "field_provenance": { + "agent_summary": { + "value": "转交文档或知识库所有者给指定用户(不可逆)", + "source": "internal/cli/schema_hints/selection/drive.json", + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "人工审阅:对照悟空 develop 同名命令实现、Cobra Long/Example 与 Runtime 确认门禁移植;不改变命令身份、参数契约或接口绑定。", + "candidates": [ + { + "value": "转交文档或知识库所有者给指定用户(不可逆)", + "source": "internal/cli/schema_hints/selection/drive.json", + "precedence": "reviewed_explicit", + "selected": true, + "review_reason": "人工审阅:对照悟空 develop 同名命令实现、Cobra Long/Example 与 Runtime 确认门禁移植;不改变命令身份、参数契约或接口绑定。" + } + ] + }, + "availability": { + "value": "available", + "source": "internal/cli/schema_hints/metadata/drive.json", + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "dws-tool-metadata/drive marks this tool as reviewed", + "candidates": [ + { + "value": "available", + "source": "internal/cli/schema_hints/metadata/drive.json", + "precedence": "reviewed_explicit", + "selected": true, + "review_reason": "dws-tool-metadata/drive marks this tool as reviewed" + } + ] + }, + "avoid_when": { + "value": [ + "普通协作权限变更用 permission add/update/remove" + ], + "source": "internal/cli/schema_hints/selection/drive.json", + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "人工审阅:对照悟空 develop 同名命令实现、Cobra Long/Example 与 Runtime 确认门禁移植;不改变命令身份、参数契约或接口绑定。", + "candidates": [ + { + "value": [ + "普通协作权限变更用 permission add/update/remove" + ], + "source": "internal/cli/schema_hints/selection/drive.json", + "precedence": "reviewed_explicit", + "selected": true, + "review_reason": "人工审阅:对照悟空 develop 同名命令实现、Cobra Long/Example 与 Runtime 确认门禁移植;不改变命令身份、参数契约或接口绑定。" + } + ] + }, + "confirmation": { + "value": "user_required", + "source": "internal/cli/schema_hints/metadata/drive.json", + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "dws-tool-metadata/drive marks this tool as reviewed", + "candidates": [ + { + "value": "user_required", + "source": "internal/cli/schema_hints/metadata/drive.json", + "precedence": "reviewed_explicit", + "selected": true, + "review_reason": "dws-tool-metadata/drive marks this tool as reviewed" + } + ] + }, + "effect": { + "value": "write", + "source": "internal/cli/schema_hints/metadata/drive.json", + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "dws-tool-metadata/drive marks this tool as reviewed", + "candidates": [ + { + "value": "write", + "source": "internal/cli/schema_hints/metadata/drive.json", + "precedence": "reviewed_explicit", + "selected": true, + "review_reason": "dws-tool-metadata/drive marks this tool as reviewed" + } + ] + }, + "examples": { + "value": [ + "dws drive permission transfer-owner --node \u003cDOC_ID\u003e --new-owner \u003cuserId\u003e --reserve-role EDITOR --recursive=false --format json" + ], + "source": "internal/cli/schema_hints/selection/drive.json", + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "人工审阅:对照悟空 develop 同名命令实现、Cobra Long/Example 与 Runtime 确认门禁移植;不改变命令身份、参数契约或接口绑定。", + "candidates": [ + { + "value": [ + "dws drive permission transfer-owner --node \u003cDOC_ID\u003e --new-owner \u003cuserId\u003e --reserve-role EDITOR --recursive=false --format json" + ], + "source": "internal/cli/schema_hints/selection/drive.json", + "precedence": "reviewed_explicit", + "selected": true, + "review_reason": "人工审阅:对照悟空 develop 同名命令实现、Cobra Long/Example 与 Runtime 确认门禁移植;不改变命令身份、参数契约或接口绑定。" + } + ] + }, + "idempotency": { + "value": "non_idempotent", + "source": "internal/cli/schema_hints/metadata/drive.json", + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "dws-tool-metadata/drive marks this tool as reviewed", + "candidates": [ + { + "value": "non_idempotent", + "source": "internal/cli/schema_hints/metadata/drive.json", + "precedence": "reviewed_explicit", + "selected": true, + "review_reason": "dws-tool-metadata/drive marks this tool as reviewed" + } + ] + }, + "interface_mode": { + "value": "composite", + "source": "internal/cli/schema_hints/metadata/drive.json", + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "dws-tool-metadata/drive marks this tool as reviewed", + "candidates": [ + { + "value": "composite", + "source": "internal/cli/schema_hints/metadata/drive.json", + "precedence": "reviewed_explicit", + "selected": true, + "review_reason": "dws-tool-metadata/drive marks this tool as reviewed" + } + ] + }, + "interface_reason": { + "value": "Reviewed unpinned remote adapter: this executable CLI wrapper calls a remote helper that is absent from the pinned MCP metadata snapshot; no single pinned semantically equivalent interface_ref can represent the command.", + "source": "internal/cli/schema_hints/metadata/drive.json", + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "dws-tool-metadata/drive marks this tool as reviewed", + "candidates": [ + { + "value": "Reviewed unpinned remote adapter: this executable CLI wrapper calls a remote helper that is absent from the pinned MCP metadata snapshot; no single pinned semantically equivalent interface_ref can represent the command.", + "source": "internal/cli/schema_hints/metadata/drive.json", + "precedence": "reviewed_explicit", + "selected": true, + "review_reason": "dws-tool-metadata/drive marks this tool as reviewed" + } + ] + }, + "interface_ref": { + "value": null, + "source": "internal/cli/schema_hints/metadata/drive.json", + "precedence": "reviewed_explicit", + "resolution": "interface_disposition_matrix", + "review_reason": "final interface mode composite forbids a direct MCP interface_ref", + "candidates": [ + { + "value": null, + "source": "internal/cli/schema_hints/metadata/drive.json", + "precedence": "reviewed_explicit", + "selected": true, + "review_reason": "final interface mode composite forbids a direct MCP interface_ref" + } + ] + }, + "reviewed": { + "value": true, + "source": "internal/cli/schema_hints/metadata/drive.json", + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "dws-tool-metadata/drive marks this tool as reviewed", + "candidates": [ + { + "value": true, + "source": "internal/cli/schema_hints/metadata/drive.json", + "precedence": "reviewed_explicit", + "selected": true, + "review_reason": "dws-tool-metadata/drive marks this tool as reviewed" + }, + { + "value": true, + "source": "internal/cli/schema_hints/selection/drive.json", + "precedence": "reviewed_explicit", + "selected": false, + "review_reason": "人工审阅:对照悟空 develop 同名命令实现、Cobra Long/Example 与 Runtime 确认门禁移植;不改变命令身份、参数契约或接口绑定。" + } + ] + }, + "risk": { + "value": "high", + "source": "internal/cli/schema_hints/metadata/drive.json", + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "dws-tool-metadata/drive marks this tool as reviewed", + "candidates": [ + { + "value": "high", + "source": "internal/cli/schema_hints/metadata/drive.json", + "precedence": "reviewed_explicit", + "selected": true, + "review_reason": "dws-tool-metadata/drive marks this tool as reviewed" + } + ] + }, + "use_when": { + "value": [ + "用户明确要求转交文档/知识库所有权" + ], + "source": "internal/cli/schema_hints/selection/drive.json", + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "人工审阅:对照悟空 develop 同名命令实现、Cobra Long/Example 与 Runtime 确认门禁移植;不改变命令身份、参数契约或接口绑定。", + "candidates": [ + { + "value": [ + "用户明确要求转交文档/知识库所有权" + ], + "source": "internal/cli/schema_hints/selection/drive.json", + "precedence": "reviewed_explicit", + "selected": true, + "review_reason": "人工审阅:对照悟空 develop 同名命令实现、Cobra Long/Example 与 Runtime 确认门禁移植;不改变命令身份、参数契约或接口绑定。" + } + ] + } + }, + "idempotency": "non_idempotent", + "interface_mode": "composite", + "interface_reason": "Reviewed unpinned remote adapter: this executable CLI wrapper calls a remote helper that is absent from the pinned MCP metadata snapshot; no single pinned semantically equivalent interface_ref can represent the command.", + "reviewed": true, + "risk": "high", + "source_refs": [ + "CommandRegistry:canonical_path=drive.transfer_owner", + "cobra-help:dws drive permission transfer-owner", + "internal/cli/schema_hints/metadata/drive.json", + "internal/cli/schema_hints/selection/drive.json", + "wukong-develop:wukong/products" + ], + "use_when": [ + "用户明确要求转交文档/知识库所有权" + ] + }, "drive permission update": { "agent_summary": "更新文档空间节点已有协作者的权限角色", "agent_summary_source": "dws-agent-selection/drive", @@ -7348,6 +8647,264 @@ "用户要重命名钉盘/文档空间中的文件、文档或文件夹时;实际执行会读取节点类型和当前扩展名,仅去掉完全匹配的一层后缀" ] }, + "drive revert": { + "agent_summary": "回滚普通文件到指定历史版本(生成新最新版本,历史不丢失)", + "agent_summary_source": "dws-agent-selection/drive", + "availability": "available", + "avoid_when": [ + "在线文档回滚用 doc version revert;在线表格用 sheet version revert" + ], + "confirmation": "user_required", + "effect": "write", + "effect_source": "agent-hint", + "examples": [ + "dws drive revert --node \u003cdentryUuid\u003e --version 3 --format json" + ], + "field_provenance": { + "agent_summary": { + "value": "回滚普通文件到指定历史版本(生成新最新版本,历史不丢失)", + "source": "internal/cli/schema_hints/selection/drive.json", + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "人工审阅:对照悟空 develop 同名命令实现、Cobra Long/Example 与 Runtime 确认门禁移植;不改变命令身份、参数契约或接口绑定。", + "candidates": [ + { + "value": "回滚普通文件到指定历史版本(生成新最新版本,历史不丢失)", + "source": "internal/cli/schema_hints/selection/drive.json", + "precedence": "reviewed_explicit", + "selected": true, + "review_reason": "人工审阅:对照悟空 develop 同名命令实现、Cobra Long/Example 与 Runtime 确认门禁移植;不改变命令身份、参数契约或接口绑定。" + } + ] + }, + "availability": { + "value": "available", + "source": "internal/cli/schema_hints/metadata/drive.json", + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "dws-tool-metadata/drive marks this tool as reviewed", + "candidates": [ + { + "value": "available", + "source": "internal/cli/schema_hints/metadata/drive.json", + "precedence": "reviewed_explicit", + "selected": true, + "review_reason": "dws-tool-metadata/drive marks this tool as reviewed" + } + ] + }, + "avoid_when": { + "value": [ + "在线文档回滚用 doc version revert;在线表格用 sheet version revert" + ], + "source": "internal/cli/schema_hints/selection/drive.json", + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "人工审阅:对照悟空 develop 同名命令实现、Cobra Long/Example 与 Runtime 确认门禁移植;不改变命令身份、参数契约或接口绑定。", + "candidates": [ + { + "value": [ + "在线文档回滚用 doc version revert;在线表格用 sheet version revert" + ], + "source": "internal/cli/schema_hints/selection/drive.json", + "precedence": "reviewed_explicit", + "selected": true, + "review_reason": "人工审阅:对照悟空 develop 同名命令实现、Cobra Long/Example 与 Runtime 确认门禁移植;不改变命令身份、参数契约或接口绑定。" + } + ] + }, + "confirmation": { + "value": "user_required", + "source": "internal/cli/schema_hints/metadata/drive.json", + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "dws-tool-metadata/drive marks this tool as reviewed", + "candidates": [ + { + "value": "user_required", + "source": "internal/cli/schema_hints/metadata/drive.json", + "precedence": "reviewed_explicit", + "selected": true, + "review_reason": "dws-tool-metadata/drive marks this tool as reviewed" + } + ] + }, + "effect": { + "value": "write", + "source": "internal/cli/schema_hints/metadata/drive.json", + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "dws-tool-metadata/drive marks this tool as reviewed", + "candidates": [ + { + "value": "write", + "source": "internal/cli/schema_hints/metadata/drive.json", + "precedence": "reviewed_explicit", + "selected": true, + "review_reason": "dws-tool-metadata/drive marks this tool as reviewed" + } + ] + }, + "examples": { + "value": [ + "dws drive revert --node \u003cdentryUuid\u003e --version 3 --format json" + ], + "source": "internal/cli/schema_hints/selection/drive.json", + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "人工审阅:对照悟空 develop 同名命令实现、Cobra Long/Example 与 Runtime 确认门禁移植;不改变命令身份、参数契约或接口绑定。", + "candidates": [ + { + "value": [ + "dws drive revert --node \u003cdentryUuid\u003e --version 3 --format json" + ], + "source": "internal/cli/schema_hints/selection/drive.json", + "precedence": "reviewed_explicit", + "selected": true, + "review_reason": "人工审阅:对照悟空 develop 同名命令实现、Cobra Long/Example 与 Runtime 确认门禁移植;不改变命令身份、参数契约或接口绑定。" + } + ] + }, + "idempotency": { + "value": "non_idempotent", + "source": "internal/cli/schema_hints/metadata/drive.json", + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "dws-tool-metadata/drive marks this tool as reviewed", + "candidates": [ + { + "value": "non_idempotent", + "source": "internal/cli/schema_hints/metadata/drive.json", + "precedence": "reviewed_explicit", + "selected": true, + "review_reason": "dws-tool-metadata/drive marks this tool as reviewed" + } + ] + }, + "interface_mode": { + "value": "composite", + "source": "internal/cli/schema_hints/metadata/drive.json", + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "dws-tool-metadata/drive marks this tool as reviewed", + "candidates": [ + { + "value": "composite", + "source": "internal/cli/schema_hints/metadata/drive.json", + "precedence": "reviewed_explicit", + "selected": true, + "review_reason": "dws-tool-metadata/drive marks this tool as reviewed" + } + ] + }, + "interface_reason": { + "value": "Reviewed unpinned remote adapter: this executable CLI wrapper calls a remote helper that is absent from the pinned MCP metadata snapshot; no single pinned semantically equivalent interface_ref can represent the command.", + "source": "internal/cli/schema_hints/metadata/drive.json", + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "dws-tool-metadata/drive marks this tool as reviewed", + "candidates": [ + { + "value": "Reviewed unpinned remote adapter: this executable CLI wrapper calls a remote helper that is absent from the pinned MCP metadata snapshot; no single pinned semantically equivalent interface_ref can represent the command.", + "source": "internal/cli/schema_hints/metadata/drive.json", + "precedence": "reviewed_explicit", + "selected": true, + "review_reason": "dws-tool-metadata/drive marks this tool as reviewed" + } + ] + }, + "interface_ref": { + "value": null, + "source": "internal/cli/schema_hints/metadata/drive.json", + "precedence": "reviewed_explicit", + "resolution": "interface_disposition_matrix", + "review_reason": "final interface mode composite forbids a direct MCP interface_ref", + "candidates": [ + { + "value": null, + "source": "internal/cli/schema_hints/metadata/drive.json", + "precedence": "reviewed_explicit", + "selected": true, + "review_reason": "final interface mode composite forbids a direct MCP interface_ref" + } + ] + }, + "reviewed": { + "value": true, + "source": "internal/cli/schema_hints/metadata/drive.json", + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "dws-tool-metadata/drive marks this tool as reviewed", + "candidates": [ + { + "value": true, + "source": "internal/cli/schema_hints/metadata/drive.json", + "precedence": "reviewed_explicit", + "selected": true, + "review_reason": "dws-tool-metadata/drive marks this tool as reviewed" + }, + { + "value": true, + "source": "internal/cli/schema_hints/selection/drive.json", + "precedence": "reviewed_explicit", + "selected": false, + "review_reason": "人工审阅:对照悟空 develop 同名命令实现、Cobra Long/Example 与 Runtime 确认门禁移植;不改变命令身份、参数契约或接口绑定。" + } + ] + }, + "risk": { + "value": "medium", + "source": "internal/cli/schema_hints/metadata/drive.json", + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "dws-tool-metadata/drive marks this tool as reviewed", + "candidates": [ + { + "value": "medium", + "source": "internal/cli/schema_hints/metadata/drive.json", + "precedence": "reviewed_explicit", + "selected": true, + "review_reason": "dws-tool-metadata/drive marks this tool as reviewed" + } + ] + }, + "use_when": { + "value": [ + "用户说 回滚版本/恢复到某个版本/版本回退,且目标是普通文件" + ], + "source": "internal/cli/schema_hints/selection/drive.json", + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "人工审阅:对照悟空 develop 同名命令实现、Cobra Long/Example 与 Runtime 确认门禁移植;不改变命令身份、参数契约或接口绑定。", + "candidates": [ + { + "value": [ + "用户说 回滚版本/恢复到某个版本/版本回退,且目标是普通文件" + ], + "source": "internal/cli/schema_hints/selection/drive.json", + "precedence": "reviewed_explicit", + "selected": true, + "review_reason": "人工审阅:对照悟空 develop 同名命令实现、Cobra Long/Example 与 Runtime 确认门禁移植;不改变命令身份、参数契约或接口绑定。" + } + ] + } + }, + "idempotency": "non_idempotent", + "interface_mode": "composite", + "interface_reason": "Reviewed unpinned remote adapter: this executable CLI wrapper calls a remote helper that is absent from the pinned MCP metadata snapshot; no single pinned semantically equivalent interface_ref can represent the command.", + "reviewed": true, + "risk": "medium", + "source_refs": [ + "CommandRegistry:canonical_path=drive.revert_file_version", + "cobra-help:dws drive revert", + "internal/cli/schema_hints/metadata/drive.json", + "internal/cli/schema_hints/selection/drive.json", + "wukong-develop:wukong/products" + ], + "use_when": [ + "用户说 回滚版本/恢复到某个版本/版本回退,且目标是普通文件" + ] + }, "drive search": { "agent_summary": "全局搜索文件,默认同时搜索钉盘和文档空间,合并返回结果", "agent_summary_source": "dws-agent-selection/drive", @@ -7884,6 +9441,783 @@ "用户要给已有节点建快捷方式/软链接到目标文件夹或知识库时" ] }, + "drive star add": { + "agent_summary": "收藏文档/文件到当前用户收藏列表", + "agent_summary_source": "dws-agent-selection/drive", + "availability": "available", + "avoid_when": [ + "取消收藏用 unmark_star;查看收藏列表用 get_star_list" + ], + "confirmation": "not_required", + "effect": "write", + "effect_source": "agent-hint", + "examples": [ + "dws drive star add --node \u003cnodeId\u003e --format json" + ], + "field_provenance": { + "agent_summary": { + "value": "收藏文档/文件到当前用户收藏列表", + "source": "internal/cli/schema_hints/selection/drive.json", + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "人工审阅:对照悟空 develop 同名命令实现、Cobra Long/Example 与 Runtime 确认门禁移植;不改变命令身份、参数契约或接口绑定。", + "candidates": [ + { + "value": "收藏文档/文件到当前用户收藏列表", + "source": "internal/cli/schema_hints/selection/drive.json", + "precedence": "reviewed_explicit", + "selected": true, + "review_reason": "人工审阅:对照悟空 develop 同名命令实现、Cobra Long/Example 与 Runtime 确认门禁移植;不改变命令身份、参数契约或接口绑定。" + } + ] + }, + "availability": { + "value": "available", + "source": "internal/cli/schema_hints/metadata/drive.json", + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "dws-tool-metadata/drive marks this tool as reviewed", + "candidates": [ + { + "value": "available", + "source": "internal/cli/schema_hints/metadata/drive.json", + "precedence": "reviewed_explicit", + "selected": true, + "review_reason": "dws-tool-metadata/drive marks this tool as reviewed" + } + ] + }, + "avoid_when": { + "value": [ + "取消收藏用 unmark_star;查看收藏列表用 get_star_list" + ], + "source": "internal/cli/schema_hints/selection/drive.json", + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "人工审阅:对照悟空 develop 同名命令实现、Cobra Long/Example 与 Runtime 确认门禁移植;不改变命令身份、参数契约或接口绑定。", + "candidates": [ + { + "value": [ + "取消收藏用 unmark_star;查看收藏列表用 get_star_list" + ], + "source": "internal/cli/schema_hints/selection/drive.json", + "precedence": "reviewed_explicit", + "selected": true, + "review_reason": "人工审阅:对照悟空 develop 同名命令实现、Cobra Long/Example 与 Runtime 确认门禁移植;不改变命令身份、参数契约或接口绑定。" + } + ] + }, + "confirmation": { + "value": "not_required", + "source": "internal/cli/schema_hints/metadata/drive.json", + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "dws-tool-metadata/drive marks this tool as reviewed", + "candidates": [ + { + "value": "not_required", + "source": "internal/cli/schema_hints/metadata/drive.json", + "precedence": "reviewed_explicit", + "selected": true, + "review_reason": "dws-tool-metadata/drive marks this tool as reviewed" + } + ] + }, + "effect": { + "value": "write", + "source": "internal/cli/schema_hints/metadata/drive.json", + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "dws-tool-metadata/drive marks this tool as reviewed", + "candidates": [ + { + "value": "write", + "source": "internal/cli/schema_hints/metadata/drive.json", + "precedence": "reviewed_explicit", + "selected": true, + "review_reason": "dws-tool-metadata/drive marks this tool as reviewed" + } + ] + }, + "examples": { + "value": [ + "dws drive star add --node \u003cnodeId\u003e --format json" + ], + "source": "internal/cli/schema_hints/selection/drive.json", + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "人工审阅:对照悟空 develop 同名命令实现、Cobra Long/Example 与 Runtime 确认门禁移植;不改变命令身份、参数契约或接口绑定。", + "candidates": [ + { + "value": [ + "dws drive star add --node \u003cnodeId\u003e --format json" + ], + "source": "internal/cli/schema_hints/selection/drive.json", + "precedence": "reviewed_explicit", + "selected": true, + "review_reason": "人工审阅:对照悟空 develop 同名命令实现、Cobra Long/Example 与 Runtime 确认门禁移植;不改变命令身份、参数契约或接口绑定。" + } + ] + }, + "idempotency": { + "value": "idempotent", + "source": "internal/cli/schema_hints/metadata/drive.json", + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "dws-tool-metadata/drive marks this tool as reviewed", + "candidates": [ + { + "value": "idempotent", + "source": "internal/cli/schema_hints/metadata/drive.json", + "precedence": "reviewed_explicit", + "selected": true, + "review_reason": "dws-tool-metadata/drive marks this tool as reviewed" + } + ] + }, + "interface_mode": { + "value": "composite", + "source": "internal/cli/schema_hints/metadata/drive.json", + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "dws-tool-metadata/drive marks this tool as reviewed", + "candidates": [ + { + "value": "composite", + "source": "internal/cli/schema_hints/metadata/drive.json", + "precedence": "reviewed_explicit", + "selected": true, + "review_reason": "dws-tool-metadata/drive marks this tool as reviewed" + } + ] + }, + "interface_reason": { + "value": "Reviewed unpinned remote adapter: this executable CLI wrapper calls a remote helper that is absent from the pinned MCP metadata snapshot; no single pinned semantically equivalent interface_ref can represent the command.", + "source": "internal/cli/schema_hints/metadata/drive.json", + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "dws-tool-metadata/drive marks this tool as reviewed", + "candidates": [ + { + "value": "Reviewed unpinned remote adapter: this executable CLI wrapper calls a remote helper that is absent from the pinned MCP metadata snapshot; no single pinned semantically equivalent interface_ref can represent the command.", + "source": "internal/cli/schema_hints/metadata/drive.json", + "precedence": "reviewed_explicit", + "selected": true, + "review_reason": "dws-tool-metadata/drive marks this tool as reviewed" + } + ] + }, + "interface_ref": { + "value": null, + "source": "internal/cli/schema_hints/metadata/drive.json", + "precedence": "reviewed_explicit", + "resolution": "interface_disposition_matrix", + "review_reason": "final interface mode composite forbids a direct MCP interface_ref", + "candidates": [ + { + "value": null, + "source": "internal/cli/schema_hints/metadata/drive.json", + "precedence": "reviewed_explicit", + "selected": true, + "review_reason": "final interface mode composite forbids a direct MCP interface_ref" + } + ] + }, + "reviewed": { + "value": true, + "source": "internal/cli/schema_hints/metadata/drive.json", + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "dws-tool-metadata/drive marks this tool as reviewed", + "candidates": [ + { + "value": true, + "source": "internal/cli/schema_hints/metadata/drive.json", + "precedence": "reviewed_explicit", + "selected": true, + "review_reason": "dws-tool-metadata/drive marks this tool as reviewed" + }, + { + "value": true, + "source": "internal/cli/schema_hints/selection/drive.json", + "precedence": "reviewed_explicit", + "selected": false, + "review_reason": "人工审阅:对照悟空 develop 同名命令实现、Cobra Long/Example 与 Runtime 确认门禁移植;不改变命令身份、参数契约或接口绑定。" + } + ] + }, + "risk": { + "value": "low", + "source": "internal/cli/schema_hints/metadata/drive.json", + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "dws-tool-metadata/drive marks this tool as reviewed", + "candidates": [ + { + "value": "low", + "source": "internal/cli/schema_hints/metadata/drive.json", + "precedence": "reviewed_explicit", + "selected": true, + "review_reason": "dws-tool-metadata/drive marks this tool as reviewed" + } + ] + }, + "use_when": { + "value": [ + "用户说 收藏这个文档/加个收藏/标星" + ], + "source": "internal/cli/schema_hints/selection/drive.json", + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "人工审阅:对照悟空 develop 同名命令实现、Cobra Long/Example 与 Runtime 确认门禁移植;不改变命令身份、参数契约或接口绑定。", + "candidates": [ + { + "value": [ + "用户说 收藏这个文档/加个收藏/标星" + ], + "source": "internal/cli/schema_hints/selection/drive.json", + "precedence": "reviewed_explicit", + "selected": true, + "review_reason": "人工审阅:对照悟空 develop 同名命令实现、Cobra Long/Example 与 Runtime 确认门禁移植;不改变命令身份、参数契约或接口绑定。" + } + ] + } + }, + "idempotency": "idempotent", + "interface_mode": "composite", + "interface_reason": "Reviewed unpinned remote adapter: this executable CLI wrapper calls a remote helper that is absent from the pinned MCP metadata snapshot; no single pinned semantically equivalent interface_ref can represent the command.", + "reviewed": true, + "risk": "low", + "source_refs": [ + "CommandRegistry:canonical_path=drive.mark_star", + "cobra-help:dws drive star add", + "internal/cli/schema_hints/metadata/drive.json", + "internal/cli/schema_hints/selection/drive.json", + "wukong-develop:wukong/products" + ], + "use_when": [ + "用户说 收藏这个文档/加个收藏/标星" + ] + }, + "drive star list": { + "agent_summary": "获取当前用户的收藏列表,支持分页与按内容类型筛选", + "agent_summary_source": "dws-agent-selection/drive", + "availability": "available", + "avoid_when": [ + "操作单个收藏用 mark_star/unmark_star" + ], + "confirmation": "not_required", + "effect": "read", + "effect_source": "agent-hint", + "examples": [ + "dws drive star list --format json", + "dws drive star list --content-types doc,sheet --limit 10 --format json" + ], + "field_provenance": { + "agent_summary": { + "value": "获取当前用户的收藏列表,支持分页与按内容类型筛选", + "source": "internal/cli/schema_hints/selection/drive.json", + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "人工审阅:对照悟空 develop 同名命令实现、Cobra Long/Example 与 Runtime 确认门禁移植;不改变命令身份、参数契约或接口绑定。", + "candidates": [ + { + "value": "获取当前用户的收藏列表,支持分页与按内容类型筛选", + "source": "internal/cli/schema_hints/selection/drive.json", + "precedence": "reviewed_explicit", + "selected": true, + "review_reason": "人工审阅:对照悟空 develop 同名命令实现、Cobra Long/Example 与 Runtime 确认门禁移植;不改变命令身份、参数契约或接口绑定。" + } + ] + }, + "availability": { + "value": "available", + "source": "internal/cli/schema_hints/metadata/drive.json", + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "dws-tool-metadata/drive marks this tool as reviewed", + "candidates": [ + { + "value": "available", + "source": "internal/cli/schema_hints/metadata/drive.json", + "precedence": "reviewed_explicit", + "selected": true, + "review_reason": "dws-tool-metadata/drive marks this tool as reviewed" + } + ] + }, + "avoid_when": { + "value": [ + "操作单个收藏用 mark_star/unmark_star" + ], + "source": "internal/cli/schema_hints/selection/drive.json", + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "人工审阅:对照悟空 develop 同名命令实现、Cobra Long/Example 与 Runtime 确认门禁移植;不改变命令身份、参数契约或接口绑定。", + "candidates": [ + { + "value": [ + "操作单个收藏用 mark_star/unmark_star" + ], + "source": "internal/cli/schema_hints/selection/drive.json", + "precedence": "reviewed_explicit", + "selected": true, + "review_reason": "人工审阅:对照悟空 develop 同名命令实现、Cobra Long/Example 与 Runtime 确认门禁移植;不改变命令身份、参数契约或接口绑定。" + } + ] + }, + "confirmation": { + "value": "not_required", + "source": "internal/cli/schema_hints/metadata/drive.json", + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "dws-tool-metadata/drive marks this tool as reviewed", + "candidates": [ + { + "value": "not_required", + "source": "internal/cli/schema_hints/metadata/drive.json", + "precedence": "reviewed_explicit", + "selected": true, + "review_reason": "dws-tool-metadata/drive marks this tool as reviewed" + } + ] + }, + "effect": { + "value": "read", + "source": "internal/cli/schema_hints/metadata/drive.json", + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "dws-tool-metadata/drive marks this tool as reviewed", + "candidates": [ + { + "value": "read", + "source": "internal/cli/schema_hints/metadata/drive.json", + "precedence": "reviewed_explicit", + "selected": true, + "review_reason": "dws-tool-metadata/drive marks this tool as reviewed" + } + ] + }, + "examples": { + "value": [ + "dws drive star list --format json", + "dws drive star list --content-types doc,sheet --limit 10 --format json" + ], + "source": "internal/cli/schema_hints/selection/drive.json", + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "人工审阅:对照悟空 develop 同名命令实现、Cobra Long/Example 与 Runtime 确认门禁移植;不改变命令身份、参数契约或接口绑定。", + "candidates": [ + { + "value": [ + "dws drive star list --format json", + "dws drive star list --content-types doc,sheet --limit 10 --format json" + ], + "source": "internal/cli/schema_hints/selection/drive.json", + "precedence": "reviewed_explicit", + "selected": true, + "review_reason": "人工审阅:对照悟空 develop 同名命令实现、Cobra Long/Example 与 Runtime 确认门禁移植;不改变命令身份、参数契约或接口绑定。" + } + ] + }, + "idempotency": { + "value": "idempotent", + "source": "internal/cli/schema_hints/metadata/drive.json", + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "dws-tool-metadata/drive marks this tool as reviewed", + "candidates": [ + { + "value": "idempotent", + "source": "internal/cli/schema_hints/metadata/drive.json", + "precedence": "reviewed_explicit", + "selected": true, + "review_reason": "dws-tool-metadata/drive marks this tool as reviewed" + } + ] + }, + "interface_mode": { + "value": "composite", + "source": "internal/cli/schema_hints/metadata/drive.json", + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "dws-tool-metadata/drive marks this tool as reviewed", + "candidates": [ + { + "value": "composite", + "source": "internal/cli/schema_hints/metadata/drive.json", + "precedence": "reviewed_explicit", + "selected": true, + "review_reason": "dws-tool-metadata/drive marks this tool as reviewed" + } + ] + }, + "interface_reason": { + "value": "Reviewed unpinned remote adapter: this executable CLI wrapper calls a remote helper that is absent from the pinned MCP metadata snapshot; no single pinned semantically equivalent interface_ref can represent the command.", + "source": "internal/cli/schema_hints/metadata/drive.json", + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "dws-tool-metadata/drive marks this tool as reviewed", + "candidates": [ + { + "value": "Reviewed unpinned remote adapter: this executable CLI wrapper calls a remote helper that is absent from the pinned MCP metadata snapshot; no single pinned semantically equivalent interface_ref can represent the command.", + "source": "internal/cli/schema_hints/metadata/drive.json", + "precedence": "reviewed_explicit", + "selected": true, + "review_reason": "dws-tool-metadata/drive marks this tool as reviewed" + } + ] + }, + "interface_ref": { + "value": null, + "source": "internal/cli/schema_hints/metadata/drive.json", + "precedence": "reviewed_explicit", + "resolution": "interface_disposition_matrix", + "review_reason": "final interface mode composite forbids a direct MCP interface_ref", + "candidates": [ + { + "value": null, + "source": "internal/cli/schema_hints/metadata/drive.json", + "precedence": "reviewed_explicit", + "selected": true, + "review_reason": "final interface mode composite forbids a direct MCP interface_ref" + } + ] + }, + "reviewed": { + "value": true, + "source": "internal/cli/schema_hints/metadata/drive.json", + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "dws-tool-metadata/drive marks this tool as reviewed", + "candidates": [ + { + "value": true, + "source": "internal/cli/schema_hints/metadata/drive.json", + "precedence": "reviewed_explicit", + "selected": true, + "review_reason": "dws-tool-metadata/drive marks this tool as reviewed" + }, + { + "value": true, + "source": "internal/cli/schema_hints/selection/drive.json", + "precedence": "reviewed_explicit", + "selected": false, + "review_reason": "人工审阅:对照悟空 develop 同名命令实现、Cobra Long/Example 与 Runtime 确认门禁移植;不改变命令身份、参数契约或接口绑定。" + } + ] + }, + "risk": { + "value": "low", + "source": "internal/cli/schema_hints/metadata/drive.json", + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "dws-tool-metadata/drive marks this tool as reviewed", + "candidates": [ + { + "value": "low", + "source": "internal/cli/schema_hints/metadata/drive.json", + "precedence": "reviewed_explicit", + "selected": true, + "review_reason": "dws-tool-metadata/drive marks this tool as reviewed" + } + ] + }, + "use_when": { + "value": [ + "用户说 我的收藏/收藏列表/收藏了哪些文档" + ], + "source": "internal/cli/schema_hints/selection/drive.json", + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "人工审阅:对照悟空 develop 同名命令实现、Cobra Long/Example 与 Runtime 确认门禁移植;不改变命令身份、参数契约或接口绑定。", + "candidates": [ + { + "value": [ + "用户说 我的收藏/收藏列表/收藏了哪些文档" + ], + "source": "internal/cli/schema_hints/selection/drive.json", + "precedence": "reviewed_explicit", + "selected": true, + "review_reason": "人工审阅:对照悟空 develop 同名命令实现、Cobra Long/Example 与 Runtime 确认门禁移植;不改变命令身份、参数契约或接口绑定。" + } + ] + } + }, + "idempotency": "idempotent", + "interface_mode": "composite", + "interface_reason": "Reviewed unpinned remote adapter: this executable CLI wrapper calls a remote helper that is absent from the pinned MCP metadata snapshot; no single pinned semantically equivalent interface_ref can represent the command.", + "reviewed": true, + "risk": "low", + "source_refs": [ + "CommandRegistry:canonical_path=drive.get_star_list", + "cobra-help:dws drive star list", + "internal/cli/schema_hints/metadata/drive.json", + "internal/cli/schema_hints/selection/drive.json", + "wukong-develop:wukong/products" + ], + "use_when": [ + "用户说 我的收藏/收藏列表/收藏了哪些文档" + ] + }, + "drive star remove": { + "agent_summary": "将文档/文件从当前用户收藏列表移除", + "agent_summary_source": "dws-agent-selection/drive", + "availability": "available", + "avoid_when": [ + "添加收藏用 mark_star" + ], + "confirmation": "not_required", + "effect": "write", + "effect_source": "agent-hint", + "examples": [ + "dws drive star remove --node \u003cnodeId\u003e --format json" + ], + "field_provenance": { + "agent_summary": { + "value": "将文档/文件从当前用户收藏列表移除", + "source": "internal/cli/schema_hints/selection/drive.json", + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "人工审阅:对照悟空 develop 同名命令实现、Cobra Long/Example 与 Runtime 确认门禁移植;不改变命令身份、参数契约或接口绑定。", + "candidates": [ + { + "value": "将文档/文件从当前用户收藏列表移除", + "source": "internal/cli/schema_hints/selection/drive.json", + "precedence": "reviewed_explicit", + "selected": true, + "review_reason": "人工审阅:对照悟空 develop 同名命令实现、Cobra Long/Example 与 Runtime 确认门禁移植;不改变命令身份、参数契约或接口绑定。" + } + ] + }, + "availability": { + "value": "available", + "source": "internal/cli/schema_hints/metadata/drive.json", + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "dws-tool-metadata/drive marks this tool as reviewed", + "candidates": [ + { + "value": "available", + "source": "internal/cli/schema_hints/metadata/drive.json", + "precedence": "reviewed_explicit", + "selected": true, + "review_reason": "dws-tool-metadata/drive marks this tool as reviewed" + } + ] + }, + "avoid_when": { + "value": [ + "添加收藏用 mark_star" + ], + "source": "internal/cli/schema_hints/selection/drive.json", + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "人工审阅:对照悟空 develop 同名命令实现、Cobra Long/Example 与 Runtime 确认门禁移植;不改变命令身份、参数契约或接口绑定。", + "candidates": [ + { + "value": [ + "添加收藏用 mark_star" + ], + "source": "internal/cli/schema_hints/selection/drive.json", + "precedence": "reviewed_explicit", + "selected": true, + "review_reason": "人工审阅:对照悟空 develop 同名命令实现、Cobra Long/Example 与 Runtime 确认门禁移植;不改变命令身份、参数契约或接口绑定。" + } + ] + }, + "confirmation": { + "value": "not_required", + "source": "internal/cli/schema_hints/metadata/drive.json", + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "dws-tool-metadata/drive marks this tool as reviewed", + "candidates": [ + { + "value": "not_required", + "source": "internal/cli/schema_hints/metadata/drive.json", + "precedence": "reviewed_explicit", + "selected": true, + "review_reason": "dws-tool-metadata/drive marks this tool as reviewed" + } + ] + }, + "effect": { + "value": "write", + "source": "internal/cli/schema_hints/metadata/drive.json", + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "dws-tool-metadata/drive marks this tool as reviewed", + "candidates": [ + { + "value": "write", + "source": "internal/cli/schema_hints/metadata/drive.json", + "precedence": "reviewed_explicit", + "selected": true, + "review_reason": "dws-tool-metadata/drive marks this tool as reviewed" + } + ] + }, + "examples": { + "value": [ + "dws drive star remove --node \u003cnodeId\u003e --format json" + ], + "source": "internal/cli/schema_hints/selection/drive.json", + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "人工审阅:对照悟空 develop 同名命令实现、Cobra Long/Example 与 Runtime 确认门禁移植;不改变命令身份、参数契约或接口绑定。", + "candidates": [ + { + "value": [ + "dws drive star remove --node \u003cnodeId\u003e --format json" + ], + "source": "internal/cli/schema_hints/selection/drive.json", + "precedence": "reviewed_explicit", + "selected": true, + "review_reason": "人工审阅:对照悟空 develop 同名命令实现、Cobra Long/Example 与 Runtime 确认门禁移植;不改变命令身份、参数契约或接口绑定。" + } + ] + }, + "idempotency": { + "value": "idempotent", + "source": "internal/cli/schema_hints/metadata/drive.json", + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "dws-tool-metadata/drive marks this tool as reviewed", + "candidates": [ + { + "value": "idempotent", + "source": "internal/cli/schema_hints/metadata/drive.json", + "precedence": "reviewed_explicit", + "selected": true, + "review_reason": "dws-tool-metadata/drive marks this tool as reviewed" + } + ] + }, + "interface_mode": { + "value": "composite", + "source": "internal/cli/schema_hints/metadata/drive.json", + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "dws-tool-metadata/drive marks this tool as reviewed", + "candidates": [ + { + "value": "composite", + "source": "internal/cli/schema_hints/metadata/drive.json", + "precedence": "reviewed_explicit", + "selected": true, + "review_reason": "dws-tool-metadata/drive marks this tool as reviewed" + } + ] + }, + "interface_reason": { + "value": "Reviewed unpinned remote adapter: this executable CLI wrapper calls a remote helper that is absent from the pinned MCP metadata snapshot; no single pinned semantically equivalent interface_ref can represent the command.", + "source": "internal/cli/schema_hints/metadata/drive.json", + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "dws-tool-metadata/drive marks this tool as reviewed", + "candidates": [ + { + "value": "Reviewed unpinned remote adapter: this executable CLI wrapper calls a remote helper that is absent from the pinned MCP metadata snapshot; no single pinned semantically equivalent interface_ref can represent the command.", + "source": "internal/cli/schema_hints/metadata/drive.json", + "precedence": "reviewed_explicit", + "selected": true, + "review_reason": "dws-tool-metadata/drive marks this tool as reviewed" + } + ] + }, + "interface_ref": { + "value": null, + "source": "internal/cli/schema_hints/metadata/drive.json", + "precedence": "reviewed_explicit", + "resolution": "interface_disposition_matrix", + "review_reason": "final interface mode composite forbids a direct MCP interface_ref", + "candidates": [ + { + "value": null, + "source": "internal/cli/schema_hints/metadata/drive.json", + "precedence": "reviewed_explicit", + "selected": true, + "review_reason": "final interface mode composite forbids a direct MCP interface_ref" + } + ] + }, + "reviewed": { + "value": true, + "source": "internal/cli/schema_hints/metadata/drive.json", + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "dws-tool-metadata/drive marks this tool as reviewed", + "candidates": [ + { + "value": true, + "source": "internal/cli/schema_hints/metadata/drive.json", + "precedence": "reviewed_explicit", + "selected": true, + "review_reason": "dws-tool-metadata/drive marks this tool as reviewed" + }, + { + "value": true, + "source": "internal/cli/schema_hints/selection/drive.json", + "precedence": "reviewed_explicit", + "selected": false, + "review_reason": "人工审阅:对照悟空 develop 同名命令实现、Cobra Long/Example 与 Runtime 确认门禁移植;不改变命令身份、参数契约或接口绑定。" + } + ] + }, + "risk": { + "value": "low", + "source": "internal/cli/schema_hints/metadata/drive.json", + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "dws-tool-metadata/drive marks this tool as reviewed", + "candidates": [ + { + "value": "low", + "source": "internal/cli/schema_hints/metadata/drive.json", + "precedence": "reviewed_explicit", + "selected": true, + "review_reason": "dws-tool-metadata/drive marks this tool as reviewed" + } + ] + }, + "use_when": { + "value": [ + "用户说 取消收藏/去掉收藏/不收藏了" + ], + "source": "internal/cli/schema_hints/selection/drive.json", + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "人工审阅:对照悟空 develop 同名命令实现、Cobra Long/Example 与 Runtime 确认门禁移植;不改变命令身份、参数契约或接口绑定。", + "candidates": [ + { + "value": [ + "用户说 取消收藏/去掉收藏/不收藏了" + ], + "source": "internal/cli/schema_hints/selection/drive.json", + "precedence": "reviewed_explicit", + "selected": true, + "review_reason": "人工审阅:对照悟空 develop 同名命令实现、Cobra Long/Example 与 Runtime 确认门禁移植;不改变命令身份、参数契约或接口绑定。" + } + ] + } + }, + "idempotency": "idempotent", + "interface_mode": "composite", + "interface_reason": "Reviewed unpinned remote adapter: this executable CLI wrapper calls a remote helper that is absent from the pinned MCP metadata snapshot; no single pinned semantically equivalent interface_ref can represent the command.", + "reviewed": true, + "risk": "low", + "source_refs": [ + "CommandRegistry:canonical_path=drive.unmark_star", + "cobra-help:dws drive star remove", + "internal/cli/schema_hints/metadata/drive.json", + "internal/cli/schema_hints/selection/drive.json", + "wukong-develop:wukong/products" + ], + "use_when": [ + "用户说 取消收藏/去掉收藏/不收藏了" + ] + }, "drive stats": { "agent_summary": "读取指定钉盘或文档空间节点的阅读、编辑、评论、点赞、预览与下载等统计。", "agent_summary_source": "dws-agent-selection/drive", diff --git a/internal/cli/schema_agent_metadata/index.json b/internal/cli/schema_agent_metadata/index.json index 1d650d88..36562b23 100644 --- a/internal/cli/schema_agent_metadata/index.json +++ b/internal/cli/schema_agent_metadata/index.json @@ -1,17 +1,17 @@ { "version": 1, - "source_hash": "sha256:ab583120b83cad7e98084072198a961a7f7cf6fe6e79140ec41e21ffaac84f82", - "surface_hash": "sha256:698ccdc961c0b745e6524e33ae13d87ef4fcac012a2e833c7a727a79d35004db", + "source_hash": "sha256:22e3a7c7fcd96293d9f78e274fccc777f896c9b2edb2ed184b46a6a73937cace", + "surface_hash": "sha256:60eee8e2f37d6d9d60689efce85082798eb9ad38b7ba7c0b471c3de676a85a16", "coverage": { "surface_products": 26, "products_with_metadata": 26, - "surface_tools": 818, - "tools_with_metadata": 818, - "tools_with_agent_summary": 818, - "tools_with_use_when": 818, - "tools_with_avoid_when": 818, - "tools_with_examples": 818, - "tools_with_interface_mode": 818, + "surface_tools": 845, + "tools_with_metadata": 845, + "tools_with_agent_summary": 845, + "tools_with_use_when": 845, + "tools_with_avoid_when": 845, + "tools_with_examples": 845, + "tools_with_interface_mode": 845, "unmatched_skill_tools": 122, "unreviewed_skill_tools": 11 }, diff --git a/internal/cli/schema_agent_metadata/sheet.json b/internal/cli/schema_agent_metadata/sheet.json index 15abf382..30e7a6f7 100644 --- a/internal/cli/schema_agent_metadata/sheet.json +++ b/internal/cli/schema_agent_metadata/sheet.json @@ -2438,6 +2438,1296 @@ "已知 chart-id,需要调整图表配置时" ] }, + "sheet comment create": { + "agent_summary": "在指定单元格上创建评论,可 @ 用户", + "agent_summary_source": "dws-agent-selection/sheet", + "availability": "available", + "avoid_when": [ + "回复已有评论用 comment reply;不要把评论写进单元格值" + ], + "confirmation": "not_required", + "effect": "write", + "effect_source": "agent-hint", + "examples": [ + "dws sheet comment create --node \u003cSHEET_ID\u003e --sheet-id Sheet1 --range A2 --content \"这个数字有问题\" --format json" + ], + "field_provenance": { + "agent_summary": { + "value": "在指定单元格上创建评论,可 @ 用户", + "source": "internal/cli/schema_hints/selection/sheet.json", + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "人工审阅:对照悟空 develop 同名命令实现、Cobra Long/Example 与 Runtime 确认门禁移植;不改变命令身份、参数契约或接口绑定。", + "candidates": [ + { + "value": "在指定单元格上创建评论,可 @ 用户", + "source": "internal/cli/schema_hints/selection/sheet.json", + "precedence": "reviewed_explicit", + "selected": true, + "review_reason": "人工审阅:对照悟空 develop 同名命令实现、Cobra Long/Example 与 Runtime 确认门禁移植;不改变命令身份、参数契约或接口绑定。" + } + ] + }, + "availability": { + "value": "available", + "source": "internal/cli/schema_hints/metadata/sheet.json", + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "dws-tool-metadata/sheet marks this tool as reviewed", + "candidates": [ + { + "value": "available", + "source": "internal/cli/schema_hints/metadata/sheet.json", + "precedence": "reviewed_explicit", + "selected": true, + "review_reason": "dws-tool-metadata/sheet marks this tool as reviewed" + } + ] + }, + "avoid_when": { + "value": [ + "回复已有评论用 comment reply;不要把评论写进单元格值" + ], + "source": "internal/cli/schema_hints/selection/sheet.json", + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "人工审阅:对照悟空 develop 同名命令实现、Cobra Long/Example 与 Runtime 确认门禁移植;不改变命令身份、参数契约或接口绑定。", + "candidates": [ + { + "value": [ + "回复已有评论用 comment reply;不要把评论写进单元格值" + ], + "source": "internal/cli/schema_hints/selection/sheet.json", + "precedence": "reviewed_explicit", + "selected": true, + "review_reason": "人工审阅:对照悟空 develop 同名命令实现、Cobra Long/Example 与 Runtime 确认门禁移植;不改变命令身份、参数契约或接口绑定。" + } + ] + }, + "confirmation": { + "value": "not_required", + "source": "internal/cli/schema_hints/metadata/sheet.json", + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "dws-tool-metadata/sheet marks this tool as reviewed", + "candidates": [ + { + "value": "not_required", + "source": "internal/cli/schema_hints/metadata/sheet.json", + "precedence": "reviewed_explicit", + "selected": true, + "review_reason": "dws-tool-metadata/sheet marks this tool as reviewed" + } + ] + }, + "effect": { + "value": "write", + "source": "internal/cli/schema_hints/metadata/sheet.json", + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "dws-tool-metadata/sheet marks this tool as reviewed", + "candidates": [ + { + "value": "write", + "source": "internal/cli/schema_hints/metadata/sheet.json", + "precedence": "reviewed_explicit", + "selected": true, + "review_reason": "dws-tool-metadata/sheet marks this tool as reviewed" + } + ] + }, + "examples": { + "value": [ + "dws sheet comment create --node \u003cSHEET_ID\u003e --sheet-id Sheet1 --range A2 --content \"这个数字有问题\" --format json" + ], + "source": "internal/cli/schema_hints/selection/sheet.json", + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "人工审阅:对照悟空 develop 同名命令实现、Cobra Long/Example 与 Runtime 确认门禁移植;不改变命令身份、参数契约或接口绑定。", + "candidates": [ + { + "value": [ + "dws sheet comment create --node \u003cSHEET_ID\u003e --sheet-id Sheet1 --range A2 --content \"这个数字有问题\" --format json" + ], + "source": "internal/cli/schema_hints/selection/sheet.json", + "precedence": "reviewed_explicit", + "selected": true, + "review_reason": "人工审阅:对照悟空 develop 同名命令实现、Cobra Long/Example 与 Runtime 确认门禁移植;不改变命令身份、参数契约或接口绑定。" + } + ] + }, + "idempotency": { + "value": "non_idempotent", + "source": "internal/cli/schema_hints/metadata/sheet.json", + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "dws-tool-metadata/sheet marks this tool as reviewed", + "candidates": [ + { + "value": "non_idempotent", + "source": "internal/cli/schema_hints/metadata/sheet.json", + "precedence": "reviewed_explicit", + "selected": true, + "review_reason": "dws-tool-metadata/sheet marks this tool as reviewed" + } + ] + }, + "interface_mode": { + "value": "composite", + "source": "internal/cli/schema_hints/metadata/sheet.json", + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "dws-tool-metadata/sheet marks this tool as reviewed", + "candidates": [ + { + "value": "composite", + "source": "internal/cli/schema_hints/metadata/sheet.json", + "precedence": "reviewed_explicit", + "selected": true, + "review_reason": "dws-tool-metadata/sheet marks this tool as reviewed" + } + ] + }, + "interface_reason": { + "value": "Reviewed unpinned remote adapter: this executable CLI wrapper calls a remote helper that is absent from the pinned MCP metadata snapshot; no single pinned semantically equivalent interface_ref can represent the command.", + "source": "internal/cli/schema_hints/metadata/sheet.json", + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "dws-tool-metadata/sheet marks this tool as reviewed", + "candidates": [ + { + "value": "Reviewed unpinned remote adapter: this executable CLI wrapper calls a remote helper that is absent from the pinned MCP metadata snapshot; no single pinned semantically equivalent interface_ref can represent the command.", + "source": "internal/cli/schema_hints/metadata/sheet.json", + "precedence": "reviewed_explicit", + "selected": true, + "review_reason": "dws-tool-metadata/sheet marks this tool as reviewed" + } + ] + }, + "interface_ref": { + "value": null, + "source": "internal/cli/schema_hints/metadata/sheet.json", + "precedence": "reviewed_explicit", + "resolution": "interface_disposition_matrix", + "review_reason": "final interface mode composite forbids a direct MCP interface_ref", + "candidates": [ + { + "value": null, + "source": "internal/cli/schema_hints/metadata/sheet.json", + "precedence": "reviewed_explicit", + "selected": true, + "review_reason": "final interface mode composite forbids a direct MCP interface_ref" + } + ] + }, + "reviewed": { + "value": true, + "source": "internal/cli/schema_hints/metadata/sheet.json", + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "dws-tool-metadata/sheet marks this tool as reviewed", + "candidates": [ + { + "value": true, + "source": "internal/cli/schema_hints/metadata/sheet.json", + "precedence": "reviewed_explicit", + "selected": true, + "review_reason": "dws-tool-metadata/sheet marks this tool as reviewed" + }, + { + "value": true, + "source": "internal/cli/schema_hints/selection/sheet.json", + "precedence": "reviewed_explicit", + "selected": false, + "review_reason": "人工审阅:对照悟空 develop 同名命令实现、Cobra Long/Example 与 Runtime 确认门禁移植;不改变命令身份、参数契约或接口绑定。" + } + ] + }, + "risk": { + "value": "low", + "source": "internal/cli/schema_hints/metadata/sheet.json", + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "dws-tool-metadata/sheet marks this tool as reviewed", + "candidates": [ + { + "value": "low", + "source": "internal/cli/schema_hints/metadata/sheet.json", + "precedence": "reviewed_explicit", + "selected": true, + "review_reason": "dws-tool-metadata/sheet marks this tool as reviewed" + } + ] + }, + "use_when": { + "value": [ + "用户说 给单元格加评论/批注/@某人讨论这个数据" + ], + "source": "internal/cli/schema_hints/selection/sheet.json", + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "人工审阅:对照悟空 develop 同名命令实现、Cobra Long/Example 与 Runtime 确认门禁移植;不改变命令身份、参数契约或接口绑定。", + "candidates": [ + { + "value": [ + "用户说 给单元格加评论/批注/@某人讨论这个数据" + ], + "source": "internal/cli/schema_hints/selection/sheet.json", + "precedence": "reviewed_explicit", + "selected": true, + "review_reason": "人工审阅:对照悟空 develop 同名命令实现、Cobra Long/Example 与 Runtime 确认门禁移植;不改变命令身份、参数契约或接口绑定。" + } + ] + } + }, + "idempotency": "non_idempotent", + "interface_mode": "composite", + "interface_reason": "Reviewed unpinned remote adapter: this executable CLI wrapper calls a remote helper that is absent from the pinned MCP metadata snapshot; no single pinned semantically equivalent interface_ref can represent the command.", + "reviewed": true, + "risk": "low", + "source_refs": [ + "CommandRegistry:canonical_path=sheet.create_sheet_comment", + "cobra-help:dws sheet comment create", + "internal/cli/schema_hints/metadata/sheet.json", + "internal/cli/schema_hints/selection/sheet.json", + "wukong-develop:wukong/products" + ], + "use_when": [ + "用户说 给单元格加评论/批注/@某人讨论这个数据" + ] + }, + "sheet comment delete": { + "agent_summary": "删除单元格评论(不可恢复)", + "agent_summary_source": "dws-agent-selection/sheet", + "availability": "available", + "avoid_when": [ + "标记解决不等于删除" + ], + "confirmation": "user_required", + "effect": "write", + "effect_source": "agent-hint", + "examples": [ + "dws sheet comment delete --node \u003cSHEET_ID\u003e --comment-key \u003cCOMMENT_KEY\u003e --format json" + ], + "field_provenance": { + "agent_summary": { + "value": "删除单元格评论(不可恢复)", + "source": "internal/cli/schema_hints/selection/sheet.json", + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "人工审阅:对照悟空 develop 同名命令实现、Cobra Long/Example 与 Runtime 确认门禁移植;不改变命令身份、参数契约或接口绑定。", + "candidates": [ + { + "value": "删除单元格评论(不可恢复)", + "source": "internal/cli/schema_hints/selection/sheet.json", + "precedence": "reviewed_explicit", + "selected": true, + "review_reason": "人工审阅:对照悟空 develop 同名命令实现、Cobra Long/Example 与 Runtime 确认门禁移植;不改变命令身份、参数契约或接口绑定。" + } + ] + }, + "availability": { + "value": "available", + "source": "internal/cli/schema_hints/metadata/sheet.json", + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "dws-tool-metadata/sheet marks this tool as reviewed", + "candidates": [ + { + "value": "available", + "source": "internal/cli/schema_hints/metadata/sheet.json", + "precedence": "reviewed_explicit", + "selected": true, + "review_reason": "dws-tool-metadata/sheet marks this tool as reviewed" + } + ] + }, + "avoid_when": { + "value": [ + "标记解决不等于删除" + ], + "source": "internal/cli/schema_hints/selection/sheet.json", + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "人工审阅:对照悟空 develop 同名命令实现、Cobra Long/Example 与 Runtime 确认门禁移植;不改变命令身份、参数契约或接口绑定。", + "candidates": [ + { + "value": [ + "标记解决不等于删除" + ], + "source": "internal/cli/schema_hints/selection/sheet.json", + "precedence": "reviewed_explicit", + "selected": true, + "review_reason": "人工审阅:对照悟空 develop 同名命令实现、Cobra Long/Example 与 Runtime 确认门禁移植;不改变命令身份、参数契约或接口绑定。" + } + ] + }, + "confirmation": { + "value": "user_required", + "source": "internal/cli/schema_hints/metadata/sheet.json", + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "dws-tool-metadata/sheet marks this tool as reviewed", + "candidates": [ + { + "value": "user_required", + "source": "internal/cli/schema_hints/metadata/sheet.json", + "precedence": "reviewed_explicit", + "selected": true, + "review_reason": "dws-tool-metadata/sheet marks this tool as reviewed" + } + ] + }, + "effect": { + "value": "write", + "source": "internal/cli/schema_hints/metadata/sheet.json", + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "dws-tool-metadata/sheet marks this tool as reviewed", + "candidates": [ + { + "value": "write", + "source": "internal/cli/schema_hints/metadata/sheet.json", + "precedence": "reviewed_explicit", + "selected": true, + "review_reason": "dws-tool-metadata/sheet marks this tool as reviewed" + } + ] + }, + "examples": { + "value": [ + "dws sheet comment delete --node \u003cSHEET_ID\u003e --comment-key \u003cCOMMENT_KEY\u003e --format json" + ], + "source": "internal/cli/schema_hints/selection/sheet.json", + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "人工审阅:对照悟空 develop 同名命令实现、Cobra Long/Example 与 Runtime 确认门禁移植;不改变命令身份、参数契约或接口绑定。", + "candidates": [ + { + "value": [ + "dws sheet comment delete --node \u003cSHEET_ID\u003e --comment-key \u003cCOMMENT_KEY\u003e --format json" + ], + "source": "internal/cli/schema_hints/selection/sheet.json", + "precedence": "reviewed_explicit", + "selected": true, + "review_reason": "人工审阅:对照悟空 develop 同名命令实现、Cobra Long/Example 与 Runtime 确认门禁移植;不改变命令身份、参数契约或接口绑定。" + } + ] + }, + "idempotency": { + "value": "non_idempotent", + "source": "internal/cli/schema_hints/metadata/sheet.json", + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "dws-tool-metadata/sheet marks this tool as reviewed", + "candidates": [ + { + "value": "non_idempotent", + "source": "internal/cli/schema_hints/metadata/sheet.json", + "precedence": "reviewed_explicit", + "selected": true, + "review_reason": "dws-tool-metadata/sheet marks this tool as reviewed" + } + ] + }, + "interface_mode": { + "value": "composite", + "source": "internal/cli/schema_hints/metadata/sheet.json", + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "dws-tool-metadata/sheet marks this tool as reviewed", + "candidates": [ + { + "value": "composite", + "source": "internal/cli/schema_hints/metadata/sheet.json", + "precedence": "reviewed_explicit", + "selected": true, + "review_reason": "dws-tool-metadata/sheet marks this tool as reviewed" + } + ] + }, + "interface_reason": { + "value": "Reviewed unpinned remote adapter: this executable CLI wrapper calls a remote helper that is absent from the pinned MCP metadata snapshot; no single pinned semantically equivalent interface_ref can represent the command.", + "source": "internal/cli/schema_hints/metadata/sheet.json", + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "dws-tool-metadata/sheet marks this tool as reviewed", + "candidates": [ + { + "value": "Reviewed unpinned remote adapter: this executable CLI wrapper calls a remote helper that is absent from the pinned MCP metadata snapshot; no single pinned semantically equivalent interface_ref can represent the command.", + "source": "internal/cli/schema_hints/metadata/sheet.json", + "precedence": "reviewed_explicit", + "selected": true, + "review_reason": "dws-tool-metadata/sheet marks this tool as reviewed" + } + ] + }, + "interface_ref": { + "value": null, + "source": "internal/cli/schema_hints/metadata/sheet.json", + "precedence": "reviewed_explicit", + "resolution": "interface_disposition_matrix", + "review_reason": "final interface mode composite forbids a direct MCP interface_ref", + "candidates": [ + { + "value": null, + "source": "internal/cli/schema_hints/metadata/sheet.json", + "precedence": "reviewed_explicit", + "selected": true, + "review_reason": "final interface mode composite forbids a direct MCP interface_ref" + } + ] + }, + "reviewed": { + "value": true, + "source": "internal/cli/schema_hints/metadata/sheet.json", + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "dws-tool-metadata/sheet marks this tool as reviewed", + "candidates": [ + { + "value": true, + "source": "internal/cli/schema_hints/metadata/sheet.json", + "precedence": "reviewed_explicit", + "selected": true, + "review_reason": "dws-tool-metadata/sheet marks this tool as reviewed" + }, + { + "value": true, + "source": "internal/cli/schema_hints/selection/sheet.json", + "precedence": "reviewed_explicit", + "selected": false, + "review_reason": "人工审阅:对照悟空 develop 同名命令实现、Cobra Long/Example 与 Runtime 确认门禁移植;不改变命令身份、参数契约或接口绑定。" + } + ] + }, + "risk": { + "value": "medium", + "source": "internal/cli/schema_hints/metadata/sheet.json", + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "dws-tool-metadata/sheet marks this tool as reviewed", + "candidates": [ + { + "value": "medium", + "source": "internal/cli/schema_hints/metadata/sheet.json", + "precedence": "reviewed_explicit", + "selected": true, + "review_reason": "dws-tool-metadata/sheet marks this tool as reviewed" + } + ] + }, + "use_when": { + "value": [ + "用户明确要求删除某条评论" + ], + "source": "internal/cli/schema_hints/selection/sheet.json", + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "人工审阅:对照悟空 develop 同名命令实现、Cobra Long/Example 与 Runtime 确认门禁移植;不改变命令身份、参数契约或接口绑定。", + "candidates": [ + { + "value": [ + "用户明确要求删除某条评论" + ], + "source": "internal/cli/schema_hints/selection/sheet.json", + "precedence": "reviewed_explicit", + "selected": true, + "review_reason": "人工审阅:对照悟空 develop 同名命令实现、Cobra Long/Example 与 Runtime 确认门禁移植;不改变命令身份、参数契约或接口绑定。" + } + ] + } + }, + "idempotency": "non_idempotent", + "interface_mode": "composite", + "interface_reason": "Reviewed unpinned remote adapter: this executable CLI wrapper calls a remote helper that is absent from the pinned MCP metadata snapshot; no single pinned semantically equivalent interface_ref can represent the command.", + "reviewed": true, + "risk": "medium", + "source_refs": [ + "CommandRegistry:canonical_path=sheet.delete_sheet_comment", + "cobra-help:dws sheet comment delete", + "internal/cli/schema_hints/metadata/sheet.json", + "internal/cli/schema_hints/selection/sheet.json", + "wukong-develop:wukong/products" + ], + "use_when": [ + "用户明确要求删除某条评论" + ] + }, + "sheet comment list": { + "agent_summary": "查询表格单元格评论列表,支持分页与按解决状态过滤", + "agent_summary_source": "dws-agent-selection/sheet", + "availability": "available", + "avoid_when": [ + "新建评论用 comment create" + ], + "confirmation": "not_required", + "effect": "read", + "effect_source": "agent-hint", + "examples": [ + "dws sheet comment list --node \u003cSHEET_ID\u003e --format json" + ], + "field_provenance": { + "agent_summary": { + "value": "查询表格单元格评论列表,支持分页与按解决状态过滤", + "source": "internal/cli/schema_hints/selection/sheet.json", + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "人工审阅:对照悟空 develop 同名命令实现、Cobra Long/Example 与 Runtime 确认门禁移植;不改变命令身份、参数契约或接口绑定。", + "candidates": [ + { + "value": "查询表格单元格评论列表,支持分页与按解决状态过滤", + "source": "internal/cli/schema_hints/selection/sheet.json", + "precedence": "reviewed_explicit", + "selected": true, + "review_reason": "人工审阅:对照悟空 develop 同名命令实现、Cobra Long/Example 与 Runtime 确认门禁移植;不改变命令身份、参数契约或接口绑定。" + } + ] + }, + "availability": { + "value": "available", + "source": "internal/cli/schema_hints/metadata/sheet.json", + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "dws-tool-metadata/sheet marks this tool as reviewed", + "candidates": [ + { + "value": "available", + "source": "internal/cli/schema_hints/metadata/sheet.json", + "precedence": "reviewed_explicit", + "selected": true, + "review_reason": "dws-tool-metadata/sheet marks this tool as reviewed" + } + ] + }, + "avoid_when": { + "value": [ + "新建评论用 comment create" + ], + "source": "internal/cli/schema_hints/selection/sheet.json", + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "人工审阅:对照悟空 develop 同名命令实现、Cobra Long/Example 与 Runtime 确认门禁移植;不改变命令身份、参数契约或接口绑定。", + "candidates": [ + { + "value": [ + "新建评论用 comment create" + ], + "source": "internal/cli/schema_hints/selection/sheet.json", + "precedence": "reviewed_explicit", + "selected": true, + "review_reason": "人工审阅:对照悟空 develop 同名命令实现、Cobra Long/Example 与 Runtime 确认门禁移植;不改变命令身份、参数契约或接口绑定。" + } + ] + }, + "confirmation": { + "value": "not_required", + "source": "internal/cli/schema_hints/metadata/sheet.json", + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "dws-tool-metadata/sheet marks this tool as reviewed", + "candidates": [ + { + "value": "not_required", + "source": "internal/cli/schema_hints/metadata/sheet.json", + "precedence": "reviewed_explicit", + "selected": true, + "review_reason": "dws-tool-metadata/sheet marks this tool as reviewed" + } + ] + }, + "effect": { + "value": "read", + "source": "internal/cli/schema_hints/metadata/sheet.json", + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "dws-tool-metadata/sheet marks this tool as reviewed", + "candidates": [ + { + "value": "read", + "source": "internal/cli/schema_hints/metadata/sheet.json", + "precedence": "reviewed_explicit", + "selected": true, + "review_reason": "dws-tool-metadata/sheet marks this tool as reviewed" + } + ] + }, + "examples": { + "value": [ + "dws sheet comment list --node \u003cSHEET_ID\u003e --format json" + ], + "source": "internal/cli/schema_hints/selection/sheet.json", + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "人工审阅:对照悟空 develop 同名命令实现、Cobra Long/Example 与 Runtime 确认门禁移植;不改变命令身份、参数契约或接口绑定。", + "candidates": [ + { + "value": [ + "dws sheet comment list --node \u003cSHEET_ID\u003e --format json" + ], + "source": "internal/cli/schema_hints/selection/sheet.json", + "precedence": "reviewed_explicit", + "selected": true, + "review_reason": "人工审阅:对照悟空 develop 同名命令实现、Cobra Long/Example 与 Runtime 确认门禁移植;不改变命令身份、参数契约或接口绑定。" + } + ] + }, + "idempotency": { + "value": "idempotent", + "source": "internal/cli/schema_hints/metadata/sheet.json", + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "dws-tool-metadata/sheet marks this tool as reviewed", + "candidates": [ + { + "value": "idempotent", + "source": "internal/cli/schema_hints/metadata/sheet.json", + "precedence": "reviewed_explicit", + "selected": true, + "review_reason": "dws-tool-metadata/sheet marks this tool as reviewed" + } + ] + }, + "interface_mode": { + "value": "composite", + "source": "internal/cli/schema_hints/metadata/sheet.json", + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "dws-tool-metadata/sheet marks this tool as reviewed", + "candidates": [ + { + "value": "composite", + "source": "internal/cli/schema_hints/metadata/sheet.json", + "precedence": "reviewed_explicit", + "selected": true, + "review_reason": "dws-tool-metadata/sheet marks this tool as reviewed" + } + ] + }, + "interface_reason": { + "value": "Reviewed unpinned remote adapter: this executable CLI wrapper calls a remote helper that is absent from the pinned MCP metadata snapshot; no single pinned semantically equivalent interface_ref can represent the command.", + "source": "internal/cli/schema_hints/metadata/sheet.json", + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "dws-tool-metadata/sheet marks this tool as reviewed", + "candidates": [ + { + "value": "Reviewed unpinned remote adapter: this executable CLI wrapper calls a remote helper that is absent from the pinned MCP metadata snapshot; no single pinned semantically equivalent interface_ref can represent the command.", + "source": "internal/cli/schema_hints/metadata/sheet.json", + "precedence": "reviewed_explicit", + "selected": true, + "review_reason": "dws-tool-metadata/sheet marks this tool as reviewed" + } + ] + }, + "interface_ref": { + "value": null, + "source": "internal/cli/schema_hints/metadata/sheet.json", + "precedence": "reviewed_explicit", + "resolution": "interface_disposition_matrix", + "review_reason": "final interface mode composite forbids a direct MCP interface_ref", + "candidates": [ + { + "value": null, + "source": "internal/cli/schema_hints/metadata/sheet.json", + "precedence": "reviewed_explicit", + "selected": true, + "review_reason": "final interface mode composite forbids a direct MCP interface_ref" + } + ] + }, + "reviewed": { + "value": true, + "source": "internal/cli/schema_hints/metadata/sheet.json", + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "dws-tool-metadata/sheet marks this tool as reviewed", + "candidates": [ + { + "value": true, + "source": "internal/cli/schema_hints/metadata/sheet.json", + "precedence": "reviewed_explicit", + "selected": true, + "review_reason": "dws-tool-metadata/sheet marks this tool as reviewed" + }, + { + "value": true, + "source": "internal/cli/schema_hints/selection/sheet.json", + "precedence": "reviewed_explicit", + "selected": false, + "review_reason": "人工审阅:对照悟空 develop 同名命令实现、Cobra Long/Example 与 Runtime 确认门禁移植;不改变命令身份、参数契约或接口绑定。" + } + ] + }, + "risk": { + "value": "low", + "source": "internal/cli/schema_hints/metadata/sheet.json", + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "dws-tool-metadata/sheet marks this tool as reviewed", + "candidates": [ + { + "value": "low", + "source": "internal/cli/schema_hints/metadata/sheet.json", + "precedence": "reviewed_explicit", + "selected": true, + "review_reason": "dws-tool-metadata/sheet marks this tool as reviewed" + } + ] + }, + "use_when": { + "value": [ + "用户说 看某格的评论/表格里有哪些批注" + ], + "source": "internal/cli/schema_hints/selection/sheet.json", + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "人工审阅:对照悟空 develop 同名命令实现、Cobra Long/Example 与 Runtime 确认门禁移植;不改变命令身份、参数契约或接口绑定。", + "candidates": [ + { + "value": [ + "用户说 看某格的评论/表格里有哪些批注" + ], + "source": "internal/cli/schema_hints/selection/sheet.json", + "precedence": "reviewed_explicit", + "selected": true, + "review_reason": "人工审阅:对照悟空 develop 同名命令实现、Cobra Long/Example 与 Runtime 确认门禁移植;不改变命令身份、参数契约或接口绑定。" + } + ] + } + }, + "idempotency": "idempotent", + "interface_mode": "composite", + "interface_reason": "Reviewed unpinned remote adapter: this executable CLI wrapper calls a remote helper that is absent from the pinned MCP metadata snapshot; no single pinned semantically equivalent interface_ref can represent the command.", + "reviewed": true, + "risk": "low", + "source_refs": [ + "CommandRegistry:canonical_path=sheet.list_sheet_comments", + "cobra-help:dws sheet comment list", + "internal/cli/schema_hints/metadata/sheet.json", + "internal/cli/schema_hints/selection/sheet.json", + "wukong-develop:wukong/products" + ], + "use_when": [ + "用户说 看某格的评论/表格里有哪些批注" + ] + }, + "sheet comment reply": { + "agent_summary": "回复指定单元格评论,支持表情贴图回复", + "agent_summary_source": "dws-agent-selection/sheet", + "availability": "available", + "avoid_when": [ + "修改评论内容用 comment update" + ], + "confirmation": "not_required", + "effect": "write", + "effect_source": "agent-hint", + "examples": [ + "dws sheet comment reply --node \u003cSHEET_ID\u003e --comment-key \u003cCOMMENT_KEY\u003e --content \"已核实\" --format json" + ], + "field_provenance": { + "agent_summary": { + "value": "回复指定单元格评论,支持表情贴图回复", + "source": "internal/cli/schema_hints/selection/sheet.json", + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "人工审阅:对照悟空 develop 同名命令实现、Cobra Long/Example 与 Runtime 确认门禁移植;不改变命令身份、参数契约或接口绑定。", + "candidates": [ + { + "value": "回复指定单元格评论,支持表情贴图回复", + "source": "internal/cli/schema_hints/selection/sheet.json", + "precedence": "reviewed_explicit", + "selected": true, + "review_reason": "人工审阅:对照悟空 develop 同名命令实现、Cobra Long/Example 与 Runtime 确认门禁移植;不改变命令身份、参数契约或接口绑定。" + } + ] + }, + "availability": { + "value": "available", + "source": "internal/cli/schema_hints/metadata/sheet.json", + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "dws-tool-metadata/sheet marks this tool as reviewed", + "candidates": [ + { + "value": "available", + "source": "internal/cli/schema_hints/metadata/sheet.json", + "precedence": "reviewed_explicit", + "selected": true, + "review_reason": "dws-tool-metadata/sheet marks this tool as reviewed" + } + ] + }, + "avoid_when": { + "value": [ + "修改评论内容用 comment update" + ], + "source": "internal/cli/schema_hints/selection/sheet.json", + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "人工审阅:对照悟空 develop 同名命令实现、Cobra Long/Example 与 Runtime 确认门禁移植;不改变命令身份、参数契约或接口绑定。", + "candidates": [ + { + "value": [ + "修改评论内容用 comment update" + ], + "source": "internal/cli/schema_hints/selection/sheet.json", + "precedence": "reviewed_explicit", + "selected": true, + "review_reason": "人工审阅:对照悟空 develop 同名命令实现、Cobra Long/Example 与 Runtime 确认门禁移植;不改变命令身份、参数契约或接口绑定。" + } + ] + }, + "confirmation": { + "value": "not_required", + "source": "internal/cli/schema_hints/metadata/sheet.json", + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "dws-tool-metadata/sheet marks this tool as reviewed", + "candidates": [ + { + "value": "not_required", + "source": "internal/cli/schema_hints/metadata/sheet.json", + "precedence": "reviewed_explicit", + "selected": true, + "review_reason": "dws-tool-metadata/sheet marks this tool as reviewed" + } + ] + }, + "effect": { + "value": "write", + "source": "internal/cli/schema_hints/metadata/sheet.json", + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "dws-tool-metadata/sheet marks this tool as reviewed", + "candidates": [ + { + "value": "write", + "source": "internal/cli/schema_hints/metadata/sheet.json", + "precedence": "reviewed_explicit", + "selected": true, + "review_reason": "dws-tool-metadata/sheet marks this tool as reviewed" + } + ] + }, + "examples": { + "value": [ + "dws sheet comment reply --node \u003cSHEET_ID\u003e --comment-key \u003cCOMMENT_KEY\u003e --content \"已核实\" --format json" + ], + "source": "internal/cli/schema_hints/selection/sheet.json", + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "人工审阅:对照悟空 develop 同名命令实现、Cobra Long/Example 与 Runtime 确认门禁移植;不改变命令身份、参数契约或接口绑定。", + "candidates": [ + { + "value": [ + "dws sheet comment reply --node \u003cSHEET_ID\u003e --comment-key \u003cCOMMENT_KEY\u003e --content \"已核实\" --format json" + ], + "source": "internal/cli/schema_hints/selection/sheet.json", + "precedence": "reviewed_explicit", + "selected": true, + "review_reason": "人工审阅:对照悟空 develop 同名命令实现、Cobra Long/Example 与 Runtime 确认门禁移植;不改变命令身份、参数契约或接口绑定。" + } + ] + }, + "idempotency": { + "value": "non_idempotent", + "source": "internal/cli/schema_hints/metadata/sheet.json", + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "dws-tool-metadata/sheet marks this tool as reviewed", + "candidates": [ + { + "value": "non_idempotent", + "source": "internal/cli/schema_hints/metadata/sheet.json", + "precedence": "reviewed_explicit", + "selected": true, + "review_reason": "dws-tool-metadata/sheet marks this tool as reviewed" + } + ] + }, + "interface_mode": { + "value": "composite", + "source": "internal/cli/schema_hints/metadata/sheet.json", + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "dws-tool-metadata/sheet marks this tool as reviewed", + "candidates": [ + { + "value": "composite", + "source": "internal/cli/schema_hints/metadata/sheet.json", + "precedence": "reviewed_explicit", + "selected": true, + "review_reason": "dws-tool-metadata/sheet marks this tool as reviewed" + } + ] + }, + "interface_reason": { + "value": "Reviewed unpinned remote adapter: this executable CLI wrapper calls a remote helper that is absent from the pinned MCP metadata snapshot; no single pinned semantically equivalent interface_ref can represent the command.", + "source": "internal/cli/schema_hints/metadata/sheet.json", + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "dws-tool-metadata/sheet marks this tool as reviewed", + "candidates": [ + { + "value": "Reviewed unpinned remote adapter: this executable CLI wrapper calls a remote helper that is absent from the pinned MCP metadata snapshot; no single pinned semantically equivalent interface_ref can represent the command.", + "source": "internal/cli/schema_hints/metadata/sheet.json", + "precedence": "reviewed_explicit", + "selected": true, + "review_reason": "dws-tool-metadata/sheet marks this tool as reviewed" + } + ] + }, + "interface_ref": { + "value": null, + "source": "internal/cli/schema_hints/metadata/sheet.json", + "precedence": "reviewed_explicit", + "resolution": "interface_disposition_matrix", + "review_reason": "final interface mode composite forbids a direct MCP interface_ref", + "candidates": [ + { + "value": null, + "source": "internal/cli/schema_hints/metadata/sheet.json", + "precedence": "reviewed_explicit", + "selected": true, + "review_reason": "final interface mode composite forbids a direct MCP interface_ref" + } + ] + }, + "reviewed": { + "value": true, + "source": "internal/cli/schema_hints/metadata/sheet.json", + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "dws-tool-metadata/sheet marks this tool as reviewed", + "candidates": [ + { + "value": true, + "source": "internal/cli/schema_hints/metadata/sheet.json", + "precedence": "reviewed_explicit", + "selected": true, + "review_reason": "dws-tool-metadata/sheet marks this tool as reviewed" + }, + { + "value": true, + "source": "internal/cli/schema_hints/selection/sheet.json", + "precedence": "reviewed_explicit", + "selected": false, + "review_reason": "人工审阅:对照悟空 develop 同名命令实现、Cobra Long/Example 与 Runtime 确认门禁移植;不改变命令身份、参数契约或接口绑定。" + } + ] + }, + "risk": { + "value": "low", + "source": "internal/cli/schema_hints/metadata/sheet.json", + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "dws-tool-metadata/sheet marks this tool as reviewed", + "candidates": [ + { + "value": "low", + "source": "internal/cli/schema_hints/metadata/sheet.json", + "precedence": "reviewed_explicit", + "selected": true, + "review_reason": "dws-tool-metadata/sheet marks this tool as reviewed" + } + ] + }, + "use_when": { + "value": [ + "用户说 回复这条评论" + ], + "source": "internal/cli/schema_hints/selection/sheet.json", + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "人工审阅:对照悟空 develop 同名命令实现、Cobra Long/Example 与 Runtime 确认门禁移植;不改变命令身份、参数契约或接口绑定。", + "candidates": [ + { + "value": [ + "用户说 回复这条评论" + ], + "source": "internal/cli/schema_hints/selection/sheet.json", + "precedence": "reviewed_explicit", + "selected": true, + "review_reason": "人工审阅:对照悟空 develop 同名命令实现、Cobra Long/Example 与 Runtime 确认门禁移植;不改变命令身份、参数契约或接口绑定。" + } + ] + } + }, + "idempotency": "non_idempotent", + "interface_mode": "composite", + "interface_reason": "Reviewed unpinned remote adapter: this executable CLI wrapper calls a remote helper that is absent from the pinned MCP metadata snapshot; no single pinned semantically equivalent interface_ref can represent the command.", + "reviewed": true, + "risk": "low", + "source_refs": [ + "CommandRegistry:canonical_path=sheet.reply_sheet_comment", + "cobra-help:dws sheet comment reply", + "internal/cli/schema_hints/metadata/sheet.json", + "internal/cli/schema_hints/selection/sheet.json", + "wukong-develop:wukong/products" + ], + "use_when": [ + "用户说 回复这条评论" + ] + }, + "sheet comment update": { + "agent_summary": "更新单元格评论内容", + "agent_summary_source": "dws-agent-selection/sheet", + "availability": "available", + "avoid_when": [ + "回复评论用 comment reply" + ], + "confirmation": "not_required", + "effect": "write", + "effect_source": "agent-hint", + "examples": [ + "dws sheet comment update --node \u003cSHEET_ID\u003e --comment-key \u003cCOMMENT_KEY\u003e --content \"已修正\" --format json" + ], + "field_provenance": { + "agent_summary": { + "value": "更新单元格评论内容", + "source": "internal/cli/schema_hints/selection/sheet.json", + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "人工审阅:对照悟空 develop 同名命令实现、Cobra Long/Example 与 Runtime 确认门禁移植;不改变命令身份、参数契约或接口绑定。", + "candidates": [ + { + "value": "更新单元格评论内容", + "source": "internal/cli/schema_hints/selection/sheet.json", + "precedence": "reviewed_explicit", + "selected": true, + "review_reason": "人工审阅:对照悟空 develop 同名命令实现、Cobra Long/Example 与 Runtime 确认门禁移植;不改变命令身份、参数契约或接口绑定。" + } + ] + }, + "availability": { + "value": "available", + "source": "internal/cli/schema_hints/metadata/sheet.json", + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "dws-tool-metadata/sheet marks this tool as reviewed", + "candidates": [ + { + "value": "available", + "source": "internal/cli/schema_hints/metadata/sheet.json", + "precedence": "reviewed_explicit", + "selected": true, + "review_reason": "dws-tool-metadata/sheet marks this tool as reviewed" + } + ] + }, + "avoid_when": { + "value": [ + "回复评论用 comment reply" + ], + "source": "internal/cli/schema_hints/selection/sheet.json", + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "人工审阅:对照悟空 develop 同名命令实现、Cobra Long/Example 与 Runtime 确认门禁移植;不改变命令身份、参数契约或接口绑定。", + "candidates": [ + { + "value": [ + "回复评论用 comment reply" + ], + "source": "internal/cli/schema_hints/selection/sheet.json", + "precedence": "reviewed_explicit", + "selected": true, + "review_reason": "人工审阅:对照悟空 develop 同名命令实现、Cobra Long/Example 与 Runtime 确认门禁移植;不改变命令身份、参数契约或接口绑定。" + } + ] + }, + "confirmation": { + "value": "not_required", + "source": "internal/cli/schema_hints/metadata/sheet.json", + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "dws-tool-metadata/sheet marks this tool as reviewed", + "candidates": [ + { + "value": "not_required", + "source": "internal/cli/schema_hints/metadata/sheet.json", + "precedence": "reviewed_explicit", + "selected": true, + "review_reason": "dws-tool-metadata/sheet marks this tool as reviewed" + } + ] + }, + "effect": { + "value": "write", + "source": "internal/cli/schema_hints/metadata/sheet.json", + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "dws-tool-metadata/sheet marks this tool as reviewed", + "candidates": [ + { + "value": "write", + "source": "internal/cli/schema_hints/metadata/sheet.json", + "precedence": "reviewed_explicit", + "selected": true, + "review_reason": "dws-tool-metadata/sheet marks this tool as reviewed" + } + ] + }, + "examples": { + "value": [ + "dws sheet comment update --node \u003cSHEET_ID\u003e --comment-key \u003cCOMMENT_KEY\u003e --content \"已修正\" --format json" + ], + "source": "internal/cli/schema_hints/selection/sheet.json", + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "人工审阅:对照悟空 develop 同名命令实现、Cobra Long/Example 与 Runtime 确认门禁移植;不改变命令身份、参数契约或接口绑定。", + "candidates": [ + { + "value": [ + "dws sheet comment update --node \u003cSHEET_ID\u003e --comment-key \u003cCOMMENT_KEY\u003e --content \"已修正\" --format json" + ], + "source": "internal/cli/schema_hints/selection/sheet.json", + "precedence": "reviewed_explicit", + "selected": true, + "review_reason": "人工审阅:对照悟空 develop 同名命令实现、Cobra Long/Example 与 Runtime 确认门禁移植;不改变命令身份、参数契约或接口绑定。" + } + ] + }, + "idempotency": { + "value": "idempotent", + "source": "internal/cli/schema_hints/metadata/sheet.json", + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "dws-tool-metadata/sheet marks this tool as reviewed", + "candidates": [ + { + "value": "idempotent", + "source": "internal/cli/schema_hints/metadata/sheet.json", + "precedence": "reviewed_explicit", + "selected": true, + "review_reason": "dws-tool-metadata/sheet marks this tool as reviewed" + } + ] + }, + "interface_mode": { + "value": "composite", + "source": "internal/cli/schema_hints/metadata/sheet.json", + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "dws-tool-metadata/sheet marks this tool as reviewed", + "candidates": [ + { + "value": "composite", + "source": "internal/cli/schema_hints/metadata/sheet.json", + "precedence": "reviewed_explicit", + "selected": true, + "review_reason": "dws-tool-metadata/sheet marks this tool as reviewed" + } + ] + }, + "interface_reason": { + "value": "Reviewed unpinned remote adapter: this executable CLI wrapper calls a remote helper that is absent from the pinned MCP metadata snapshot; no single pinned semantically equivalent interface_ref can represent the command.", + "source": "internal/cli/schema_hints/metadata/sheet.json", + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "dws-tool-metadata/sheet marks this tool as reviewed", + "candidates": [ + { + "value": "Reviewed unpinned remote adapter: this executable CLI wrapper calls a remote helper that is absent from the pinned MCP metadata snapshot; no single pinned semantically equivalent interface_ref can represent the command.", + "source": "internal/cli/schema_hints/metadata/sheet.json", + "precedence": "reviewed_explicit", + "selected": true, + "review_reason": "dws-tool-metadata/sheet marks this tool as reviewed" + } + ] + }, + "interface_ref": { + "value": null, + "source": "internal/cli/schema_hints/metadata/sheet.json", + "precedence": "reviewed_explicit", + "resolution": "interface_disposition_matrix", + "review_reason": "final interface mode composite forbids a direct MCP interface_ref", + "candidates": [ + { + "value": null, + "source": "internal/cli/schema_hints/metadata/sheet.json", + "precedence": "reviewed_explicit", + "selected": true, + "review_reason": "final interface mode composite forbids a direct MCP interface_ref" + } + ] + }, + "reviewed": { + "value": true, + "source": "internal/cli/schema_hints/metadata/sheet.json", + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "dws-tool-metadata/sheet marks this tool as reviewed", + "candidates": [ + { + "value": true, + "source": "internal/cli/schema_hints/metadata/sheet.json", + "precedence": "reviewed_explicit", + "selected": true, + "review_reason": "dws-tool-metadata/sheet marks this tool as reviewed" + }, + { + "value": true, + "source": "internal/cli/schema_hints/selection/sheet.json", + "precedence": "reviewed_explicit", + "selected": false, + "review_reason": "人工审阅:对照悟空 develop 同名命令实现、Cobra Long/Example 与 Runtime 确认门禁移植;不改变命令身份、参数契约或接口绑定。" + } + ] + }, + "risk": { + "value": "low", + "source": "internal/cli/schema_hints/metadata/sheet.json", + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "dws-tool-metadata/sheet marks this tool as reviewed", + "candidates": [ + { + "value": "low", + "source": "internal/cli/schema_hints/metadata/sheet.json", + "precedence": "reviewed_explicit", + "selected": true, + "review_reason": "dws-tool-metadata/sheet marks this tool as reviewed" + } + ] + }, + "use_when": { + "value": [ + "用户说 改一下这条评论" + ], + "source": "internal/cli/schema_hints/selection/sheet.json", + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "人工审阅:对照悟空 develop 同名命令实现、Cobra Long/Example 与 Runtime 确认门禁移植;不改变命令身份、参数契约或接口绑定。", + "candidates": [ + { + "value": [ + "用户说 改一下这条评论" + ], + "source": "internal/cli/schema_hints/selection/sheet.json", + "precedence": "reviewed_explicit", + "selected": true, + "review_reason": "人工审阅:对照悟空 develop 同名命令实现、Cobra Long/Example 与 Runtime 确认门禁移植;不改变命令身份、参数契约或接口绑定。" + } + ] + } + }, + "idempotency": "idempotent", + "interface_mode": "composite", + "interface_reason": "Reviewed unpinned remote adapter: this executable CLI wrapper calls a remote helper that is absent from the pinned MCP metadata snapshot; no single pinned semantically equivalent interface_ref can represent the command.", + "reviewed": true, + "risk": "low", + "source_refs": [ + "CommandRegistry:canonical_path=sheet.update_sheet_comment", + "cobra-help:dws sheet comment update", + "internal/cli/schema_hints/metadata/sheet.json", + "internal/cli/schema_hints/selection/sheet.json", + "wukong-develop:wukong/products" + ], + "use_when": [ + "用户说 改一下这条评论" + ] + }, "sheet cond-format create": { "agent_summary": "创建条件格式规则。", "agent_summary_source": "dws-agent-selection/sheet", @@ -10715,6 +12005,264 @@ "要查找包含某文本或公式的单元格位置时,必须用服务端 find" ] }, + "sheet formula-verify": { + "agent_summary": "扫描表格公式单元格并按错误类型聚合返回错误数量与位置", + "agent_summary_source": "dws-agent-selection/sheet", + "availability": "available", + "avoid_when": [ + "读取公式文本用 range read --value-render-option formula" + ], + "confirmation": "not_required", + "effect": "read", + "effect_source": "agent-hint", + "examples": [ + "dws sheet formula-verify --node \u003cNODE_ID\u003e --format json" + ], + "field_provenance": { + "agent_summary": { + "value": "扫描表格公式单元格并按错误类型聚合返回错误数量与位置", + "source": "internal/cli/schema_hints/selection/sheet.json", + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "人工审阅:对照悟空 develop 同名命令实现、Cobra Long/Example 与 Runtime 确认门禁移植;不改变命令身份、参数契约或接口绑定。", + "candidates": [ + { + "value": "扫描表格公式单元格并按错误类型聚合返回错误数量与位置", + "source": "internal/cli/schema_hints/selection/sheet.json", + "precedence": "reviewed_explicit", + "selected": true, + "review_reason": "人工审阅:对照悟空 develop 同名命令实现、Cobra Long/Example 与 Runtime 确认门禁移植;不改变命令身份、参数契约或接口绑定。" + } + ] + }, + "availability": { + "value": "available", + "source": "internal/cli/schema_hints/metadata/sheet.json", + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "dws-tool-metadata/sheet marks this tool as reviewed", + "candidates": [ + { + "value": "available", + "source": "internal/cli/schema_hints/metadata/sheet.json", + "precedence": "reviewed_explicit", + "selected": true, + "review_reason": "dws-tool-metadata/sheet marks this tool as reviewed" + } + ] + }, + "avoid_when": { + "value": [ + "读取公式文本用 range read --value-render-option formula" + ], + "source": "internal/cli/schema_hints/selection/sheet.json", + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "人工审阅:对照悟空 develop 同名命令实现、Cobra Long/Example 与 Runtime 确认门禁移植;不改变命令身份、参数契约或接口绑定。", + "candidates": [ + { + "value": [ + "读取公式文本用 range read --value-render-option formula" + ], + "source": "internal/cli/schema_hints/selection/sheet.json", + "precedence": "reviewed_explicit", + "selected": true, + "review_reason": "人工审阅:对照悟空 develop 同名命令实现、Cobra Long/Example 与 Runtime 确认门禁移植;不改变命令身份、参数契约或接口绑定。" + } + ] + }, + "confirmation": { + "value": "not_required", + "source": "internal/cli/schema_hints/metadata/sheet.json", + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "dws-tool-metadata/sheet marks this tool as reviewed", + "candidates": [ + { + "value": "not_required", + "source": "internal/cli/schema_hints/metadata/sheet.json", + "precedence": "reviewed_explicit", + "selected": true, + "review_reason": "dws-tool-metadata/sheet marks this tool as reviewed" + } + ] + }, + "effect": { + "value": "read", + "source": "internal/cli/schema_hints/metadata/sheet.json", + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "dws-tool-metadata/sheet marks this tool as reviewed", + "candidates": [ + { + "value": "read", + "source": "internal/cli/schema_hints/metadata/sheet.json", + "precedence": "reviewed_explicit", + "selected": true, + "review_reason": "dws-tool-metadata/sheet marks this tool as reviewed" + } + ] + }, + "examples": { + "value": [ + "dws sheet formula-verify --node \u003cNODE_ID\u003e --format json" + ], + "source": "internal/cli/schema_hints/selection/sheet.json", + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "人工审阅:对照悟空 develop 同名命令实现、Cobra Long/Example 与 Runtime 确认门禁移植;不改变命令身份、参数契约或接口绑定。", + "candidates": [ + { + "value": [ + "dws sheet formula-verify --node \u003cNODE_ID\u003e --format json" + ], + "source": "internal/cli/schema_hints/selection/sheet.json", + "precedence": "reviewed_explicit", + "selected": true, + "review_reason": "人工审阅:对照悟空 develop 同名命令实现、Cobra Long/Example 与 Runtime 确认门禁移植;不改变命令身份、参数契约或接口绑定。" + } + ] + }, + "idempotency": { + "value": "idempotent", + "source": "internal/cli/schema_hints/metadata/sheet.json", + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "dws-tool-metadata/sheet marks this tool as reviewed", + "candidates": [ + { + "value": "idempotent", + "source": "internal/cli/schema_hints/metadata/sheet.json", + "precedence": "reviewed_explicit", + "selected": true, + "review_reason": "dws-tool-metadata/sheet marks this tool as reviewed" + } + ] + }, + "interface_mode": { + "value": "composite", + "source": "internal/cli/schema_hints/metadata/sheet.json", + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "dws-tool-metadata/sheet marks this tool as reviewed", + "candidates": [ + { + "value": "composite", + "source": "internal/cli/schema_hints/metadata/sheet.json", + "precedence": "reviewed_explicit", + "selected": true, + "review_reason": "dws-tool-metadata/sheet marks this tool as reviewed" + } + ] + }, + "interface_reason": { + "value": "Reviewed unpinned remote adapter: this executable CLI wrapper calls a remote helper that is absent from the pinned MCP metadata snapshot; no single pinned semantically equivalent interface_ref can represent the command.", + "source": "internal/cli/schema_hints/metadata/sheet.json", + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "dws-tool-metadata/sheet marks this tool as reviewed", + "candidates": [ + { + "value": "Reviewed unpinned remote adapter: this executable CLI wrapper calls a remote helper that is absent from the pinned MCP metadata snapshot; no single pinned semantically equivalent interface_ref can represent the command.", + "source": "internal/cli/schema_hints/metadata/sheet.json", + "precedence": "reviewed_explicit", + "selected": true, + "review_reason": "dws-tool-metadata/sheet marks this tool as reviewed" + } + ] + }, + "interface_ref": { + "value": null, + "source": "internal/cli/schema_hints/metadata/sheet.json", + "precedence": "reviewed_explicit", + "resolution": "interface_disposition_matrix", + "review_reason": "final interface mode composite forbids a direct MCP interface_ref", + "candidates": [ + { + "value": null, + "source": "internal/cli/schema_hints/metadata/sheet.json", + "precedence": "reviewed_explicit", + "selected": true, + "review_reason": "final interface mode composite forbids a direct MCP interface_ref" + } + ] + }, + "reviewed": { + "value": true, + "source": "internal/cli/schema_hints/metadata/sheet.json", + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "dws-tool-metadata/sheet marks this tool as reviewed", + "candidates": [ + { + "value": true, + "source": "internal/cli/schema_hints/metadata/sheet.json", + "precedence": "reviewed_explicit", + "selected": true, + "review_reason": "dws-tool-metadata/sheet marks this tool as reviewed" + }, + { + "value": true, + "source": "internal/cli/schema_hints/selection/sheet.json", + "precedence": "reviewed_explicit", + "selected": false, + "review_reason": "人工审阅:对照悟空 develop 同名命令实现、Cobra Long/Example 与 Runtime 确认门禁移植;不改变命令身份、参数契约或接口绑定。" + } + ] + }, + "risk": { + "value": "low", + "source": "internal/cli/schema_hints/metadata/sheet.json", + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "dws-tool-metadata/sheet marks this tool as reviewed", + "candidates": [ + { + "value": "low", + "source": "internal/cli/schema_hints/metadata/sheet.json", + "precedence": "reviewed_explicit", + "selected": true, + "review_reason": "dws-tool-metadata/sheet marks this tool as reviewed" + } + ] + }, + "use_when": { + "value": [ + "用户说 校验公式/检查公式错误/公式错误扫描" + ], + "source": "internal/cli/schema_hints/selection/sheet.json", + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "人工审阅:对照悟空 develop 同名命令实现、Cobra Long/Example 与 Runtime 确认门禁移植;不改变命令身份、参数契约或接口绑定。", + "candidates": [ + { + "value": [ + "用户说 校验公式/检查公式错误/公式错误扫描" + ], + "source": "internal/cli/schema_hints/selection/sheet.json", + "precedence": "reviewed_explicit", + "selected": true, + "review_reason": "人工审阅:对照悟空 develop 同名命令实现、Cobra Long/Example 与 Runtime 确认门禁移植;不改变命令身份、参数契约或接口绑定。" + } + ] + } + }, + "idempotency": "idempotent", + "interface_mode": "composite", + "interface_reason": "Reviewed unpinned remote adapter: this executable CLI wrapper calls a remote helper that is absent from the pinned MCP metadata snapshot; no single pinned semantically equivalent interface_ref can represent the command.", + "reviewed": true, + "risk": "low", + "source_refs": [ + "CommandRegistry:canonical_path=sheet.formula_verify", + "cobra-help:dws sheet formula-verify", + "internal/cli/schema_hints/metadata/sheet.json", + "internal/cli/schema_hints/selection/sheet.json", + "wukong-develop:wukong/products" + ], + "use_when": [ + "用户说 校验公式/检查公式错误/公式错误扫描" + ] + }, "sheet get-dropdown": { "agent_summary": "查询指定范围的下拉列表配置。", "agent_summary_source": "dws-agent-selection/sheet", @@ -21650,6 +23198,780 @@ "需要移动/缩放/替换已有浮动图片时" ] }, + "sheet version list": { + "agent_summary": "查看表格历史版本列表", + "agent_summary_source": "dws-agent-selection/sheet", + "availability": "available", + "avoid_when": [ + "回滚用 version revert" + ], + "confirmation": "not_required", + "effect": "read", + "effect_source": "agent-hint", + "examples": [ + "dws sheet version list --node \u003cSHEET_ID\u003e --limit 10 --format json" + ], + "field_provenance": { + "agent_summary": { + "value": "查看表格历史版本列表", + "source": "internal/cli/schema_hints/selection/sheet.json", + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "人工审阅:对照悟空 develop 同名命令实现、Cobra Long/Example 与 Runtime 确认门禁移植;不改变命令身份、参数契约或接口绑定。", + "candidates": [ + { + "value": "查看表格历史版本列表", + "source": "internal/cli/schema_hints/selection/sheet.json", + "precedence": "reviewed_explicit", + "selected": true, + "review_reason": "人工审阅:对照悟空 develop 同名命令实现、Cobra Long/Example 与 Runtime 确认门禁移植;不改变命令身份、参数契约或接口绑定。" + } + ] + }, + "availability": { + "value": "available", + "source": "internal/cli/schema_hints/metadata/sheet.json", + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "dws-tool-metadata/sheet marks this tool as reviewed", + "candidates": [ + { + "value": "available", + "source": "internal/cli/schema_hints/metadata/sheet.json", + "precedence": "reviewed_explicit", + "selected": true, + "review_reason": "dws-tool-metadata/sheet marks this tool as reviewed" + } + ] + }, + "avoid_when": { + "value": [ + "回滚用 version revert" + ], + "source": "internal/cli/schema_hints/selection/sheet.json", + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "人工审阅:对照悟空 develop 同名命令实现、Cobra Long/Example 与 Runtime 确认门禁移植;不改变命令身份、参数契约或接口绑定。", + "candidates": [ + { + "value": [ + "回滚用 version revert" + ], + "source": "internal/cli/schema_hints/selection/sheet.json", + "precedence": "reviewed_explicit", + "selected": true, + "review_reason": "人工审阅:对照悟空 develop 同名命令实现、Cobra Long/Example 与 Runtime 确认门禁移植;不改变命令身份、参数契约或接口绑定。" + } + ] + }, + "confirmation": { + "value": "not_required", + "source": "internal/cli/schema_hints/metadata/sheet.json", + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "dws-tool-metadata/sheet marks this tool as reviewed", + "candidates": [ + { + "value": "not_required", + "source": "internal/cli/schema_hints/metadata/sheet.json", + "precedence": "reviewed_explicit", + "selected": true, + "review_reason": "dws-tool-metadata/sheet marks this tool as reviewed" + } + ] + }, + "effect": { + "value": "read", + "source": "internal/cli/schema_hints/metadata/sheet.json", + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "dws-tool-metadata/sheet marks this tool as reviewed", + "candidates": [ + { + "value": "read", + "source": "internal/cli/schema_hints/metadata/sheet.json", + "precedence": "reviewed_explicit", + "selected": true, + "review_reason": "dws-tool-metadata/sheet marks this tool as reviewed" + } + ] + }, + "examples": { + "value": [ + "dws sheet version list --node \u003cSHEET_ID\u003e --limit 10 --format json" + ], + "source": "internal/cli/schema_hints/selection/sheet.json", + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "人工审阅:对照悟空 develop 同名命令实现、Cobra Long/Example 与 Runtime 确认门禁移植;不改变命令身份、参数契约或接口绑定。", + "candidates": [ + { + "value": [ + "dws sheet version list --node \u003cSHEET_ID\u003e --limit 10 --format json" + ], + "source": "internal/cli/schema_hints/selection/sheet.json", + "precedence": "reviewed_explicit", + "selected": true, + "review_reason": "人工审阅:对照悟空 develop 同名命令实现、Cobra Long/Example 与 Runtime 确认门禁移植;不改变命令身份、参数契约或接口绑定。" + } + ] + }, + "idempotency": { + "value": "idempotent", + "source": "internal/cli/schema_hints/metadata/sheet.json", + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "dws-tool-metadata/sheet marks this tool as reviewed", + "candidates": [ + { + "value": "idempotent", + "source": "internal/cli/schema_hints/metadata/sheet.json", + "precedence": "reviewed_explicit", + "selected": true, + "review_reason": "dws-tool-metadata/sheet marks this tool as reviewed" + } + ] + }, + "interface_mode": { + "value": "composite", + "source": "internal/cli/schema_hints/metadata/sheet.json", + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "dws-tool-metadata/sheet marks this tool as reviewed", + "candidates": [ + { + "value": "composite", + "source": "internal/cli/schema_hints/metadata/sheet.json", + "precedence": "reviewed_explicit", + "selected": true, + "review_reason": "dws-tool-metadata/sheet marks this tool as reviewed" + } + ] + }, + "interface_reason": { + "value": "Reviewed unpinned remote adapter: this executable CLI wrapper calls a remote helper that is absent from the pinned MCP metadata snapshot; no single pinned semantically equivalent interface_ref can represent the command.", + "source": "internal/cli/schema_hints/metadata/sheet.json", + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "dws-tool-metadata/sheet marks this tool as reviewed", + "candidates": [ + { + "value": "Reviewed unpinned remote adapter: this executable CLI wrapper calls a remote helper that is absent from the pinned MCP metadata snapshot; no single pinned semantically equivalent interface_ref can represent the command.", + "source": "internal/cli/schema_hints/metadata/sheet.json", + "precedence": "reviewed_explicit", + "selected": true, + "review_reason": "dws-tool-metadata/sheet marks this tool as reviewed" + } + ] + }, + "interface_ref": { + "value": null, + "source": "internal/cli/schema_hints/metadata/sheet.json", + "precedence": "reviewed_explicit", + "resolution": "interface_disposition_matrix", + "review_reason": "final interface mode composite forbids a direct MCP interface_ref", + "candidates": [ + { + "value": null, + "source": "internal/cli/schema_hints/metadata/sheet.json", + "precedence": "reviewed_explicit", + "selected": true, + "review_reason": "final interface mode composite forbids a direct MCP interface_ref" + } + ] + }, + "reviewed": { + "value": true, + "source": "internal/cli/schema_hints/metadata/sheet.json", + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "dws-tool-metadata/sheet marks this tool as reviewed", + "candidates": [ + { + "value": true, + "source": "internal/cli/schema_hints/metadata/sheet.json", + "precedence": "reviewed_explicit", + "selected": true, + "review_reason": "dws-tool-metadata/sheet marks this tool as reviewed" + }, + { + "value": true, + "source": "internal/cli/schema_hints/selection/sheet.json", + "precedence": "reviewed_explicit", + "selected": false, + "review_reason": "人工审阅:对照悟空 develop 同名命令实现、Cobra Long/Example 与 Runtime 确认门禁移植;不改变命令身份、参数契约或接口绑定。" + } + ] + }, + "risk": { + "value": "low", + "source": "internal/cli/schema_hints/metadata/sheet.json", + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "dws-tool-metadata/sheet marks this tool as reviewed", + "candidates": [ + { + "value": "low", + "source": "internal/cli/schema_hints/metadata/sheet.json", + "precedence": "reviewed_explicit", + "selected": true, + "review_reason": "dws-tool-metadata/sheet marks this tool as reviewed" + } + ] + }, + "use_when": { + "value": [ + "用户说 看历史版本/版本列表,目标是在线表格" + ], + "source": "internal/cli/schema_hints/selection/sheet.json", + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "人工审阅:对照悟空 develop 同名命令实现、Cobra Long/Example 与 Runtime 确认门禁移植;不改变命令身份、参数契约或接口绑定。", + "candidates": [ + { + "value": [ + "用户说 看历史版本/版本列表,目标是在线表格" + ], + "source": "internal/cli/schema_hints/selection/sheet.json", + "precedence": "reviewed_explicit", + "selected": true, + "review_reason": "人工审阅:对照悟空 develop 同名命令实现、Cobra Long/Example 与 Runtime 确认门禁移植;不改变命令身份、参数契约或接口绑定。" + } + ] + } + }, + "idempotency": "idempotent", + "interface_mode": "composite", + "interface_reason": "Reviewed unpinned remote adapter: this executable CLI wrapper calls a remote helper that is absent from the pinned MCP metadata snapshot; no single pinned semantically equivalent interface_ref can represent the command.", + "reviewed": true, + "risk": "low", + "source_refs": [ + "CommandRegistry:canonical_path=sheet.version_list", + "cobra-help:dws sheet version list", + "internal/cli/schema_hints/metadata/sheet.json", + "internal/cli/schema_hints/selection/sheet.json", + "wukong-develop:wukong/products" + ], + "use_when": [ + "用户说 看历史版本/版本列表,目标是在线表格" + ] + }, + "sheet version revert": { + "agent_summary": "回滚表格到指定历史版本", + "agent_summary_source": "dws-agent-selection/sheet", + "availability": "available", + "avoid_when": [ + "普通文件回滚用 drive revert;在线文档用 doc version revert" + ], + "confirmation": "user_required", + "effect": "write", + "effect_source": "agent-hint", + "examples": [ + "dws sheet version revert --node \u003cSHEET_ID\u003e --version 3 --format json" + ], + "field_provenance": { + "agent_summary": { + "value": "回滚表格到指定历史版本", + "source": "internal/cli/schema_hints/selection/sheet.json", + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "人工审阅:对照悟空 develop 同名命令实现、Cobra Long/Example 与 Runtime 确认门禁移植;不改变命令身份、参数契约或接口绑定。", + "candidates": [ + { + "value": "回滚表格到指定历史版本", + "source": "internal/cli/schema_hints/selection/sheet.json", + "precedence": "reviewed_explicit", + "selected": true, + "review_reason": "人工审阅:对照悟空 develop 同名命令实现、Cobra Long/Example 与 Runtime 确认门禁移植;不改变命令身份、参数契约或接口绑定。" + } + ] + }, + "availability": { + "value": "available", + "source": "internal/cli/schema_hints/metadata/sheet.json", + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "dws-tool-metadata/sheet marks this tool as reviewed", + "candidates": [ + { + "value": "available", + "source": "internal/cli/schema_hints/metadata/sheet.json", + "precedence": "reviewed_explicit", + "selected": true, + "review_reason": "dws-tool-metadata/sheet marks this tool as reviewed" + } + ] + }, + "avoid_when": { + "value": [ + "普通文件回滚用 drive revert;在线文档用 doc version revert" + ], + "source": "internal/cli/schema_hints/selection/sheet.json", + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "人工审阅:对照悟空 develop 同名命令实现、Cobra Long/Example 与 Runtime 确认门禁移植;不改变命令身份、参数契约或接口绑定。", + "candidates": [ + { + "value": [ + "普通文件回滚用 drive revert;在线文档用 doc version revert" + ], + "source": "internal/cli/schema_hints/selection/sheet.json", + "precedence": "reviewed_explicit", + "selected": true, + "review_reason": "人工审阅:对照悟空 develop 同名命令实现、Cobra Long/Example 与 Runtime 确认门禁移植;不改变命令身份、参数契约或接口绑定。" + } + ] + }, + "confirmation": { + "value": "user_required", + "source": "internal/cli/schema_hints/metadata/sheet.json", + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "dws-tool-metadata/sheet marks this tool as reviewed", + "candidates": [ + { + "value": "user_required", + "source": "internal/cli/schema_hints/metadata/sheet.json", + "precedence": "reviewed_explicit", + "selected": true, + "review_reason": "dws-tool-metadata/sheet marks this tool as reviewed" + } + ] + }, + "effect": { + "value": "write", + "source": "internal/cli/schema_hints/metadata/sheet.json", + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "dws-tool-metadata/sheet marks this tool as reviewed", + "candidates": [ + { + "value": "write", + "source": "internal/cli/schema_hints/metadata/sheet.json", + "precedence": "reviewed_explicit", + "selected": true, + "review_reason": "dws-tool-metadata/sheet marks this tool as reviewed" + } + ] + }, + "examples": { + "value": [ + "dws sheet version revert --node \u003cSHEET_ID\u003e --version 3 --format json" + ], + "source": "internal/cli/schema_hints/selection/sheet.json", + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "人工审阅:对照悟空 develop 同名命令实现、Cobra Long/Example 与 Runtime 确认门禁移植;不改变命令身份、参数契约或接口绑定。", + "candidates": [ + { + "value": [ + "dws sheet version revert --node \u003cSHEET_ID\u003e --version 3 --format json" + ], + "source": "internal/cli/schema_hints/selection/sheet.json", + "precedence": "reviewed_explicit", + "selected": true, + "review_reason": "人工审阅:对照悟空 develop 同名命令实现、Cobra Long/Example 与 Runtime 确认门禁移植;不改变命令身份、参数契约或接口绑定。" + } + ] + }, + "idempotency": { + "value": "non_idempotent", + "source": "internal/cli/schema_hints/metadata/sheet.json", + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "dws-tool-metadata/sheet marks this tool as reviewed", + "candidates": [ + { + "value": "non_idempotent", + "source": "internal/cli/schema_hints/metadata/sheet.json", + "precedence": "reviewed_explicit", + "selected": true, + "review_reason": "dws-tool-metadata/sheet marks this tool as reviewed" + } + ] + }, + "interface_mode": { + "value": "composite", + "source": "internal/cli/schema_hints/metadata/sheet.json", + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "dws-tool-metadata/sheet marks this tool as reviewed", + "candidates": [ + { + "value": "composite", + "source": "internal/cli/schema_hints/metadata/sheet.json", + "precedence": "reviewed_explicit", + "selected": true, + "review_reason": "dws-tool-metadata/sheet marks this tool as reviewed" + } + ] + }, + "interface_reason": { + "value": "Reviewed unpinned remote adapter: this executable CLI wrapper calls a remote helper that is absent from the pinned MCP metadata snapshot; no single pinned semantically equivalent interface_ref can represent the command.", + "source": "internal/cli/schema_hints/metadata/sheet.json", + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "dws-tool-metadata/sheet marks this tool as reviewed", + "candidates": [ + { + "value": "Reviewed unpinned remote adapter: this executable CLI wrapper calls a remote helper that is absent from the pinned MCP metadata snapshot; no single pinned semantically equivalent interface_ref can represent the command.", + "source": "internal/cli/schema_hints/metadata/sheet.json", + "precedence": "reviewed_explicit", + "selected": true, + "review_reason": "dws-tool-metadata/sheet marks this tool as reviewed" + } + ] + }, + "interface_ref": { + "value": null, + "source": "internal/cli/schema_hints/metadata/sheet.json", + "precedence": "reviewed_explicit", + "resolution": "interface_disposition_matrix", + "review_reason": "final interface mode composite forbids a direct MCP interface_ref", + "candidates": [ + { + "value": null, + "source": "internal/cli/schema_hints/metadata/sheet.json", + "precedence": "reviewed_explicit", + "selected": true, + "review_reason": "final interface mode composite forbids a direct MCP interface_ref" + } + ] + }, + "reviewed": { + "value": true, + "source": "internal/cli/schema_hints/metadata/sheet.json", + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "dws-tool-metadata/sheet marks this tool as reviewed", + "candidates": [ + { + "value": true, + "source": "internal/cli/schema_hints/metadata/sheet.json", + "precedence": "reviewed_explicit", + "selected": true, + "review_reason": "dws-tool-metadata/sheet marks this tool as reviewed" + }, + { + "value": true, + "source": "internal/cli/schema_hints/selection/sheet.json", + "precedence": "reviewed_explicit", + "selected": false, + "review_reason": "人工审阅:对照悟空 develop 同名命令实现、Cobra Long/Example 与 Runtime 确认门禁移植;不改变命令身份、参数契约或接口绑定。" + } + ] + }, + "risk": { + "value": "medium", + "source": "internal/cli/schema_hints/metadata/sheet.json", + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "dws-tool-metadata/sheet marks this tool as reviewed", + "candidates": [ + { + "value": "medium", + "source": "internal/cli/schema_hints/metadata/sheet.json", + "precedence": "reviewed_explicit", + "selected": true, + "review_reason": "dws-tool-metadata/sheet marks this tool as reviewed" + } + ] + }, + "use_when": { + "value": [ + "用户说 回滚到某个版本/恢复到之前的表格" + ], + "source": "internal/cli/schema_hints/selection/sheet.json", + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "人工审阅:对照悟空 develop 同名命令实现、Cobra Long/Example 与 Runtime 确认门禁移植;不改变命令身份、参数契约或接口绑定。", + "candidates": [ + { + "value": [ + "用户说 回滚到某个版本/恢复到之前的表格" + ], + "source": "internal/cli/schema_hints/selection/sheet.json", + "precedence": "reviewed_explicit", + "selected": true, + "review_reason": "人工审阅:对照悟空 develop 同名命令实现、Cobra Long/Example 与 Runtime 确认门禁移植;不改变命令身份、参数契约或接口绑定。" + } + ] + } + }, + "idempotency": "non_idempotent", + "interface_mode": "composite", + "interface_reason": "Reviewed unpinned remote adapter: this executable CLI wrapper calls a remote helper that is absent from the pinned MCP metadata snapshot; no single pinned semantically equivalent interface_ref can represent the command.", + "reviewed": true, + "risk": "medium", + "source_refs": [ + "CommandRegistry:canonical_path=sheet.version_revert", + "cobra-help:dws sheet version revert", + "internal/cli/schema_hints/metadata/sheet.json", + "internal/cli/schema_hints/selection/sheet.json", + "wukong-develop:wukong/products" + ], + "use_when": [ + "用户说 回滚到某个版本/恢复到之前的表格" + ] + }, + "sheet version save": { + "agent_summary": "手动保存表格版本快照", + "agent_summary_source": "dws-agent-selection/sheet", + "availability": "available", + "avoid_when": [ + "回滚用 version revert;查看历史用 version list" + ], + "confirmation": "not_required", + "effect": "write", + "effect_source": "agent-hint", + "examples": [ + "dws sheet version save --node \u003cSHEET_ID\u003e --format json" + ], + "field_provenance": { + "agent_summary": { + "value": "手动保存表格版本快照", + "source": "internal/cli/schema_hints/selection/sheet.json", + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "人工审阅:对照悟空 develop 同名命令实现、Cobra Long/Example 与 Runtime 确认门禁移植;不改变命令身份、参数契约或接口绑定。", + "candidates": [ + { + "value": "手动保存表格版本快照", + "source": "internal/cli/schema_hints/selection/sheet.json", + "precedence": "reviewed_explicit", + "selected": true, + "review_reason": "人工审阅:对照悟空 develop 同名命令实现、Cobra Long/Example 与 Runtime 确认门禁移植;不改变命令身份、参数契约或接口绑定。" + } + ] + }, + "availability": { + "value": "available", + "source": "internal/cli/schema_hints/metadata/sheet.json", + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "dws-tool-metadata/sheet marks this tool as reviewed", + "candidates": [ + { + "value": "available", + "source": "internal/cli/schema_hints/metadata/sheet.json", + "precedence": "reviewed_explicit", + "selected": true, + "review_reason": "dws-tool-metadata/sheet marks this tool as reviewed" + } + ] + }, + "avoid_when": { + "value": [ + "回滚用 version revert;查看历史用 version list" + ], + "source": "internal/cli/schema_hints/selection/sheet.json", + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "人工审阅:对照悟空 develop 同名命令实现、Cobra Long/Example 与 Runtime 确认门禁移植;不改变命令身份、参数契约或接口绑定。", + "candidates": [ + { + "value": [ + "回滚用 version revert;查看历史用 version list" + ], + "source": "internal/cli/schema_hints/selection/sheet.json", + "precedence": "reviewed_explicit", + "selected": true, + "review_reason": "人工审阅:对照悟空 develop 同名命令实现、Cobra Long/Example 与 Runtime 确认门禁移植;不改变命令身份、参数契约或接口绑定。" + } + ] + }, + "confirmation": { + "value": "not_required", + "source": "internal/cli/schema_hints/metadata/sheet.json", + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "dws-tool-metadata/sheet marks this tool as reviewed", + "candidates": [ + { + "value": "not_required", + "source": "internal/cli/schema_hints/metadata/sheet.json", + "precedence": "reviewed_explicit", + "selected": true, + "review_reason": "dws-tool-metadata/sheet marks this tool as reviewed" + } + ] + }, + "effect": { + "value": "write", + "source": "internal/cli/schema_hints/metadata/sheet.json", + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "dws-tool-metadata/sheet marks this tool as reviewed", + "candidates": [ + { + "value": "write", + "source": "internal/cli/schema_hints/metadata/sheet.json", + "precedence": "reviewed_explicit", + "selected": true, + "review_reason": "dws-tool-metadata/sheet marks this tool as reviewed" + } + ] + }, + "examples": { + "value": [ + "dws sheet version save --node \u003cSHEET_ID\u003e --format json" + ], + "source": "internal/cli/schema_hints/selection/sheet.json", + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "人工审阅:对照悟空 develop 同名命令实现、Cobra Long/Example 与 Runtime 确认门禁移植;不改变命令身份、参数契约或接口绑定。", + "candidates": [ + { + "value": [ + "dws sheet version save --node \u003cSHEET_ID\u003e --format json" + ], + "source": "internal/cli/schema_hints/selection/sheet.json", + "precedence": "reviewed_explicit", + "selected": true, + "review_reason": "人工审阅:对照悟空 develop 同名命令实现、Cobra Long/Example 与 Runtime 确认门禁移植;不改变命令身份、参数契约或接口绑定。" + } + ] + }, + "idempotency": { + "value": "non_idempotent", + "source": "internal/cli/schema_hints/metadata/sheet.json", + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "dws-tool-metadata/sheet marks this tool as reviewed", + "candidates": [ + { + "value": "non_idempotent", + "source": "internal/cli/schema_hints/metadata/sheet.json", + "precedence": "reviewed_explicit", + "selected": true, + "review_reason": "dws-tool-metadata/sheet marks this tool as reviewed" + } + ] + }, + "interface_mode": { + "value": "composite", + "source": "internal/cli/schema_hints/metadata/sheet.json", + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "dws-tool-metadata/sheet marks this tool as reviewed", + "candidates": [ + { + "value": "composite", + "source": "internal/cli/schema_hints/metadata/sheet.json", + "precedence": "reviewed_explicit", + "selected": true, + "review_reason": "dws-tool-metadata/sheet marks this tool as reviewed" + } + ] + }, + "interface_reason": { + "value": "Reviewed unpinned remote adapter: this executable CLI wrapper calls a remote helper that is absent from the pinned MCP metadata snapshot; no single pinned semantically equivalent interface_ref can represent the command.", + "source": "internal/cli/schema_hints/metadata/sheet.json", + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "dws-tool-metadata/sheet marks this tool as reviewed", + "candidates": [ + { + "value": "Reviewed unpinned remote adapter: this executable CLI wrapper calls a remote helper that is absent from the pinned MCP metadata snapshot; no single pinned semantically equivalent interface_ref can represent the command.", + "source": "internal/cli/schema_hints/metadata/sheet.json", + "precedence": "reviewed_explicit", + "selected": true, + "review_reason": "dws-tool-metadata/sheet marks this tool as reviewed" + } + ] + }, + "interface_ref": { + "value": null, + "source": "internal/cli/schema_hints/metadata/sheet.json", + "precedence": "reviewed_explicit", + "resolution": "interface_disposition_matrix", + "review_reason": "final interface mode composite forbids a direct MCP interface_ref", + "candidates": [ + { + "value": null, + "source": "internal/cli/schema_hints/metadata/sheet.json", + "precedence": "reviewed_explicit", + "selected": true, + "review_reason": "final interface mode composite forbids a direct MCP interface_ref" + } + ] + }, + "reviewed": { + "value": true, + "source": "internal/cli/schema_hints/metadata/sheet.json", + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "dws-tool-metadata/sheet marks this tool as reviewed", + "candidates": [ + { + "value": true, + "source": "internal/cli/schema_hints/metadata/sheet.json", + "precedence": "reviewed_explicit", + "selected": true, + "review_reason": "dws-tool-metadata/sheet marks this tool as reviewed" + }, + { + "value": true, + "source": "internal/cli/schema_hints/selection/sheet.json", + "precedence": "reviewed_explicit", + "selected": false, + "review_reason": "人工审阅:对照悟空 develop 同名命令实现、Cobra Long/Example 与 Runtime 确认门禁移植;不改变命令身份、参数契约或接口绑定。" + } + ] + }, + "risk": { + "value": "low", + "source": "internal/cli/schema_hints/metadata/sheet.json", + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "dws-tool-metadata/sheet marks this tool as reviewed", + "candidates": [ + { + "value": "low", + "source": "internal/cli/schema_hints/metadata/sheet.json", + "precedence": "reviewed_explicit", + "selected": true, + "review_reason": "dws-tool-metadata/sheet marks this tool as reviewed" + } + ] + }, + "use_when": { + "value": [ + "用户说 保存版本/存个快照,目标是在线表格" + ], + "source": "internal/cli/schema_hints/selection/sheet.json", + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "人工审阅:对照悟空 develop 同名命令实现、Cobra Long/Example 与 Runtime 确认门禁移植;不改变命令身份、参数契约或接口绑定。", + "candidates": [ + { + "value": [ + "用户说 保存版本/存个快照,目标是在线表格" + ], + "source": "internal/cli/schema_hints/selection/sheet.json", + "precedence": "reviewed_explicit", + "selected": true, + "review_reason": "人工审阅:对照悟空 develop 同名命令实现、Cobra Long/Example 与 Runtime 确认门禁移植;不改变命令身份、参数契约或接口绑定。" + } + ] + } + }, + "idempotency": "non_idempotent", + "interface_mode": "composite", + "interface_reason": "Reviewed unpinned remote adapter: this executable CLI wrapper calls a remote helper that is absent from the pinned MCP metadata snapshot; no single pinned semantically equivalent interface_ref can represent the command.", + "reviewed": true, + "risk": "low", + "source_refs": [ + "CommandRegistry:canonical_path=sheet.version_save", + "cobra-help:dws sheet version save", + "internal/cli/schema_hints/metadata/sheet.json", + "internal/cli/schema_hints/selection/sheet.json", + "wukong-develop:wukong/products" + ], + "use_when": [ + "用户说 保存版本/存个快照,目标是在线表格" + ] + }, "sheet write-image": { "agent_summary": "上传图片并写入指定单元格(占单元格内容)。", "agent_summary_source": "dws-agent-selection/sheet", diff --git a/internal/cli/schema_agent_metadata_audit.json b/internal/cli/schema_agent_metadata_audit.json index 2c7758d6..79ce2b79 100644 --- a/internal/cli/schema_agent_metadata_audit.json +++ b/internal/cli/schema_agent_metadata_audit.json @@ -1,11 +1,11 @@ { "version": 1, - "source_hash": "sha256:ab583120b83cad7e98084072198a961a7f7cf6fe6e79140ec41e21ffaac84f82", - "surface_hash": "sha256:698ccdc961c0b745e6524e33ae13d87ef4fcac012a2e833c7a727a79d35004db", + "source_hash": "sha256:22e3a7c7fcd96293d9f78e274fccc777f896c9b2edb2ed184b46a6a73937cace", + "surface_hash": "sha256:60eee8e2f37d6d9d60689efce85082798eb9ad38b7ba7c0b471c3de676a85a16", "source_files": 160, "hint_files": 54, "hint_products": 43, - "hint_tools": 1788, + "hint_tools": 1842, "interface_metadata": { "source": "mcp-tools-list+cli-registry", "revision": "4574f7022c32cf4c033e9b7b4156e2fec815fed8", @@ -29,13 +29,13 @@ "coverage": { "surface_products": 26, "products_with_metadata": 26, - "surface_tools": 818, - "tools_with_metadata": 818, - "tools_with_agent_summary": 818, - "tools_with_use_when": 818, - "tools_with_avoid_when": 818, - "tools_with_examples": 818, - "tools_with_interface_mode": 818, + "surface_tools": 845, + "tools_with_metadata": 845, + "tools_with_agent_summary": 845, + "tools_with_use_when": 845, + "tools_with_avoid_when": 845, + "tools_with_examples": 845, + "tools_with_interface_mode": 845, "unmatched_skill_tools": 122, "unreviewed_skill_tools": 11 }, @@ -1494,7 +1494,7 @@ { "tool_path": "chat message list-emotion-replies", "source": "skills/mono/references/products/chat.md", - "line": 1124, + "line": 1140, "candidates": [ "chat message add-emoji", "chat message add-favorite", @@ -1508,7 +1508,7 @@ { "tool_path": "chat category create", "source": "skills/mono/references/products/chat.md", - "line": 1501, + "line": 1517, "candidates": [ "chat group create", "chat category batch-info", @@ -1522,7 +1522,7 @@ { "tool_path": "chat category delete", "source": "skills/mono/references/products/chat.md", - "line": 1526, + "line": 1542, "candidates": [ "chat category batch-info", "chat category create-smart", @@ -1536,7 +1536,7 @@ { "tool_path": "chat category rename", "source": "skills/mono/references/products/chat.md", - "line": 1537, + "line": 1553, "candidates": [ "chat group rename", "chat category batch-info", @@ -1550,7 +1550,7 @@ { "tool_path": "chat category add-conv", "source": "skills/mono/references/products/chat.md", - "line": 1549, + "line": 1565, "candidates": [ "chat category batch-info", "chat category create-smart", @@ -1564,7 +1564,7 @@ { "tool_path": "chat category remove-conv", "source": "skills/mono/references/products/chat.md", - "line": 1562, + "line": 1578, "candidates": [ "chat category batch-info", "chat category create-smart", @@ -1578,7 +1578,7 @@ { "tool_path": "chat text translate", "source": "skills/mono/references/products/chat.md", - "line": 1577, + "line": 1593, "candidates": [ "chat +at-me", "chat +bot-find", @@ -1592,7 +1592,7 @@ { "tool_path": "chat text translate", "source": "skills/mono/references/products/chat.md", - "line": 1578, + "line": 1594, "candidates": [ "chat +at-me", "chat +bot-find", @@ -1606,7 +1606,7 @@ { "tool_path": "chat text translate", "source": "skills/mono/references/products/chat.md", - "line": 1579, + "line": 1595, "candidates": [ "chat +at-me", "chat +bot-find", @@ -1620,7 +1620,7 @@ { "tool_path": "chat hide", "source": "skills/mono/references/products/chat.md", - "line": 1616, + "line": 1632, "candidates": [ "chat +at-me", "chat +bot-find", @@ -1634,7 +1634,7 @@ { "tool_path": "chat hide", "source": "skills/mono/references/products/chat.md", - "line": 1617, + "line": 1633, "candidates": [ "chat +at-me", "chat +bot-find", @@ -1648,7 +1648,7 @@ { "tool_path": "chat mute-at-all", "source": "skills/mono/references/products/chat.md", - "line": 1639, + "line": 1655, "candidates": [ "chat +at-me", "chat +bot-find", @@ -1662,7 +1662,7 @@ { "tool_path": "chat mute-at-all", "source": "skills/mono/references/products/chat.md", - "line": 1640, + "line": 1656, "candidates": [ "chat +at-me", "chat +bot-find", @@ -1676,7 +1676,7 @@ { "tool_path": "chat mute-red-envelope", "source": "skills/mono/references/products/chat.md", - "line": 1662, + "line": 1678, "candidates": [ "chat +at-me", "chat +bot-find", @@ -1690,7 +1690,7 @@ { "tool_path": "chat mute-red-envelope", "source": "skills/mono/references/products/chat.md", - "line": 1663, + "line": 1679, "candidates": [ "chat +at-me", "chat +bot-find", @@ -1704,7 +1704,7 @@ { "tool_path": "chat mark-unread", "source": "skills/mono/references/products/chat.md", - "line": 1683, + "line": 1699, "candidates": [ "chat +at-me", "chat +bot-find", @@ -1718,7 +1718,7 @@ { "tool_path": "chat mark-unread", "source": "skills/mono/references/products/chat.md", - "line": 1684, + "line": 1700, "candidates": [ "chat +at-me", "chat +bot-find", @@ -1732,7 +1732,7 @@ { "tool_path": "chat clear-red-point", "source": "skills/mono/references/products/chat.md", - "line": 1702, + "line": 1718, "candidates": [ "chat +at-me", "chat +bot-find", @@ -1746,20 +1746,6 @@ { "tool_path": "chat clear-red-point", "source": "skills/mono/references/products/chat.md", - "line": 1703, - "candidates": [ - "chat +at-me", - "chat +bot-find", - "chat +bot-search" - ], - "review": { - "status": "stale", - "reason": "旧版 Skill 或说明性引用,当前公开命令面无等价 leaf,禁止词法模糊映射" - } - }, - { - "tool_path": "chat clear-all-red-point", - "source": "skills/mono/references/products/chat.md", "line": 1719, "candidates": [ "chat +at-me", @@ -1774,20 +1760,6 @@ { "tool_path": "chat clear-all-red-point", "source": "skills/mono/references/products/chat.md", - "line": 1721, - "candidates": [ - "chat +at-me", - "chat +bot-find", - "chat +bot-search" - ], - "review": { - "status": "stale", - "reason": "旧版 Skill 或说明性引用,当前公开命令面无等价 leaf,禁止词法模糊映射" - } - }, - { - "tool_path": "chat list-all-conversations", - "source": "skills/mono/references/products/chat.md", "line": 1735, "candidates": [ "chat +at-me", @@ -1800,21 +1772,7 @@ } }, { - "tool_path": "chat list-all-conversations", - "source": "skills/mono/references/products/chat.md", - "line": 1736, - "candidates": [ - "chat +at-me", - "chat +bot-find", - "chat +bot-search" - ], - "review": { - "status": "stale", - "reason": "旧版 Skill 或说明性引用,当前公开命令面无等价 leaf,禁止词法模糊映射" - } - }, - { - "tool_path": "chat list-all-conversations", + "tool_path": "chat clear-all-red-point", "source": "skills/mono/references/products/chat.md", "line": 1737, "candidates": [ @@ -1830,413 +1788,7 @@ { "tool_path": "chat list-all-conversations", "source": "skills/mono/references/products/chat.md", - "line": 1738, - "candidates": [ - "chat +at-me", - "chat +bot-find", - "chat +bot-search" - ], - "review": { - "status": "stale", - "reason": "旧版 Skill 或说明性引用,当前公开命令面无等价 leaf,禁止词法模糊映射" - } - }, - { - "tool_path": "chat clear-messages", - "source": "skills/mono/references/products/chat.md", - "line": 1758, - "candidates": [ - "chat +at-me", - "chat +bot-find", - "chat +bot-search" - ], - "review": { - "status": "stale", - "reason": "旧版 Skill 或说明性引用,当前公开命令面无等价 leaf,禁止词法模糊映射" - } - }, - { - "tool_path": "chat clear-messages", - "source": "skills/mono/references/products/chat.md", - "line": 1759, - "candidates": [ - "chat +at-me", - "chat +bot-find", - "chat +bot-search" - ], - "review": { - "status": "stale", - "reason": "旧版 Skill 或说明性引用,当前公开命令面无等价 leaf,禁止词法模糊映射" - } - }, - { - "tool_path": "chat mark-read", - "source": "skills/mono/references/products/chat.md", - "line": 1777, - "candidates": [ - "chat +at-me", - "chat +bot-find", - "chat +bot-search" - ], - "review": { - "status": "stale", - "reason": "旧版 Skill 或说明性引用,当前公开命令面无等价 leaf,禁止词法模糊映射" - } - }, - { - "tool_path": "chat mark-read", - "source": "skills/mono/references/products/chat.md", - "line": 1778, - "candidates": [ - "chat +at-me", - "chat +bot-find", - "chat +bot-search" - ], - "review": { - "status": "stale", - "reason": "旧版 Skill 或说明性引用,当前公开命令面无等价 leaf,禁止词法模糊映射" - } - }, - { - "tool_path": "chat group list-join-validations", - "source": "skills/mono/references/products/chat.md", - "line": 1821, - "candidates": [ - "chat group bots", - "chat group create", - "chat group dismiss" - ], - "review": { - "status": "stale", - "reason": "旧版 Skill 或说明性引用,当前公开命令面无等价 leaf,禁止词法模糊映射" - } - }, - { - "tool_path": "chat group list-join-validations", - "source": "skills/mono/references/products/chat.md", - "line": 1822, - "candidates": [ - "chat group bots", - "chat group create", - "chat group dismiss" - ], - "review": { - "status": "stale", - "reason": "旧版 Skill 或说明性引用,当前公开命令面无等价 leaf,禁止词法模糊映射" - } - }, - { - "tool_path": "chat group list-join-validations", - "source": "skills/mono/references/products/chat.md", - "line": 1823, - "candidates": [ - "chat group bots", - "chat group create", - "chat group dismiss" - ], - "review": { - "status": "stale", - "reason": "旧版 Skill 或说明性引用,当前公开命令面无等价 leaf,禁止词法模糊映射" - } - }, - { - "tool_path": "chat group audit-join-validation", - "source": "skills/mono/references/products/chat.md", - "line": 1843, - "candidates": [ - "chat group bots", - "chat group create", - "chat group dismiss" - ], - "review": { - "status": "stale", - "reason": "旧版 Skill 或说明性引用,当前公开命令面无等价 leaf,禁止词法模糊映射" - } - }, - { - "tool_path": "chat group audit-join-validation", - "source": "skills/mono/references/products/chat.md", - "line": 1844, - "candidates": [ - "chat group bots", - "chat group create", - "chat group dismiss" - ], - "review": { - "status": "stale", - "reason": "旧版 Skill 或说明性引用,当前公开命令面无等价 leaf,禁止词法模糊映射" - } - }, - { - "tool_path": "chat group members", - "source": "skills/mono/references/products/chat.md", - "line": 1876, - "candidates": [ - "chat group members add", - "chat group members add-bot", - "chat group members remove" - ], - "review": { - "status": "group", - "reason": "Skill 中的命令分组引用,不是可执行 leaf,禁止模糊绑定到任一子命令" - } - }, - { - "tool_path": "chat group update-alias", - "source": "skills/mono/references/products/chat.md", - "line": 1881, - "candidates": [ - "chat group bots", - "chat group create", - "chat group dismiss" - ], - "review": { - "status": "stale", - "reason": "旧版 Skill 或说明性引用,当前公开命令面无等价 leaf,禁止词法模糊映射" - } - }, - { - "tool_path": "chat group members list-by-ids", - "source": "skills/mono/references/products/chat.md", - "line": 1885, - "candidates": [ - "chat message list-by-ids", - "chat group members add", - "chat group members add-bot" - ], - "review": { - "status": "stale", - "reason": "旧版 Skill 或说明性引用,当前公开命令面无等价 leaf,禁止词法模糊映射" - } - }, - { - "tool_path": "chat category create", - "source": "skills/mono/references/products/chat.md", - "line": 1912, - "candidates": [ - "chat group create", - "chat category batch-info", - "chat category create-smart" - ], - "review": { - "status": "stale", - "reason": "旧版 Skill 或说明性引用,当前公开命令面无等价 leaf,禁止词法模糊映射" - } - }, - { - "tool_path": "chat category delete", - "source": "skills/mono/references/products/chat.md", - "line": 1914, - "candidates": [ - "chat category batch-info", - "chat category create-smart", - "chat category list" - ], - "review": { - "status": "stale", - "reason": "旧版 Skill 或说明性引用,当前公开命令面无等价 leaf,禁止词法模糊映射" - } - }, - { - "tool_path": "chat category rename", - "source": "skills/mono/references/products/chat.md", - "line": 1915, - "candidates": [ - "chat group rename", - "chat category batch-info", - "chat category create-smart" - ], - "review": { - "status": "stale", - "reason": "旧版 Skill 或说明性引用,当前公开命令面无等价 leaf,禁止词法模糊映射" - } - }, - { - "tool_path": "chat category add-conv", - "source": "skills/mono/references/products/chat.md", - "line": 1916, - "candidates": [ - "chat category batch-info", - "chat category create-smart", - "chat category list" - ], - "review": { - "status": "stale", - "reason": "旧版 Skill 或说明性引用,当前公开命令面无等价 leaf,禁止词法模糊映射" - } - }, - { - "tool_path": "chat category remove-conv", - "source": "skills/mono/references/products/chat.md", - "line": 1917, - "candidates": [ - "chat category batch-info", - "chat category create-smart", - "chat category list" - ], - "review": { - "status": "stale", - "reason": "旧版 Skill 或说明性引用,当前公开命令面无等价 leaf,禁止词法模糊映射" - } - }, - { - "tool_path": "chat group share-invite", - "source": "skills/mono/references/products/chat.md", - "line": 1928, - "candidates": [ - "chat group bots", - "chat group create", - "chat group dismiss" - ], - "review": { - "status": "stale", - "reason": "旧版 Skill 或说明性引用,当前公开命令面无等价 leaf,禁止词法模糊映射" - } - }, - { - "tool_path": "chat group notice create/edit/get/list", - "source": "skills/mono/references/products/chat.md", - "line": 1929, - "candidates": [ - "chat group bots", - "chat group create", - "chat group dismiss" - ], - "review": { - "status": "stale", - "reason": "旧版 Skill 或说明性引用,当前公开命令面无等价 leaf,禁止词法模糊映射" - } - }, - { - "tool_path": "chat message list-emotion-replies", - "source": "skills/mono/references/products/chat.md", - "line": 1931, - "candidates": [ - "chat message add-emoji", - "chat message add-favorite", - "chat message add-text-emotion" - ], - "review": { - "status": "stale", - "reason": "旧版 Skill 或说明性引用,当前公开命令面无等价 leaf,禁止词法模糊映射" - } - }, - { - "tool_path": "chat hide", - "source": "skills/mono/references/products/chat.md", - "line": 1938, - "candidates": [ - "chat +at-me", - "chat +bot-find", - "chat +bot-search" - ], - "review": { - "status": "stale", - "reason": "旧版 Skill 或说明性引用,当前公开命令面无等价 leaf,禁止词法模糊映射" - } - }, - { - "tool_path": "chat mute-at-all", - "source": "skills/mono/references/products/chat.md", - "line": 1939, - "candidates": [ - "chat +at-me", - "chat +bot-find", - "chat +bot-search" - ], - "review": { - "status": "stale", - "reason": "旧版 Skill 或说明性引用,当前公开命令面无等价 leaf,禁止词法模糊映射" - } - }, - { - "tool_path": "chat mute-at-all", - "source": "skills/mono/references/products/chat.md", - "line": 1940, - "candidates": [ - "chat +at-me", - "chat +bot-find", - "chat +bot-search" - ], - "review": { - "status": "stale", - "reason": "旧版 Skill 或说明性引用,当前公开命令面无等价 leaf,禁止词法模糊映射" - } - }, - { - "tool_path": "chat mute-red-envelope", - "source": "skills/mono/references/products/chat.md", - "line": 1941, - "candidates": [ - "chat +at-me", - "chat +bot-find", - "chat +bot-search" - ], - "review": { - "status": "stale", - "reason": "旧版 Skill 或说明性引用,当前公开命令面无等价 leaf,禁止词法模糊映射" - } - }, - { - "tool_path": "chat mute-red-envelope", - "source": "skills/mono/references/products/chat.md", - "line": 1942, - "candidates": [ - "chat +at-me", - "chat +bot-find", - "chat +bot-search" - ], - "review": { - "status": "stale", - "reason": "旧版 Skill 或说明性引用,当前公开命令面无等价 leaf,禁止词法模糊映射" - } - }, - { - "tool_path": "chat mark-unread", - "source": "skills/mono/references/products/chat.md", - "line": 1943, - "candidates": [ - "chat +at-me", - "chat +bot-find", - "chat +bot-search" - ], - "review": { - "status": "stale", - "reason": "旧版 Skill 或说明性引用,当前公开命令面无等价 leaf,禁止词法模糊映射" - } - }, - { - "tool_path": "chat mark-read", - "source": "skills/mono/references/products/chat.md", - "line": 1944, - "candidates": [ - "chat +at-me", - "chat +bot-find", - "chat +bot-search" - ], - "review": { - "status": "stale", - "reason": "旧版 Skill 或说明性引用,当前公开命令面无等价 leaf,禁止词法模糊映射" - } - }, - { - "tool_path": "chat clear-red-point", - "source": "skills/mono/references/products/chat.md", - "line": 1945, - "candidates": [ - "chat +at-me", - "chat +bot-find", - "chat +bot-search" - ], - "review": { - "status": "stale", - "reason": "旧版 Skill 或说明性引用,当前公开命令面无等价 leaf,禁止词法模糊映射" - } - }, - { - "tool_path": "chat clear-all-red-point", - "source": "skills/mono/references/products/chat.md", - "line": 1946, + "line": 1751, "candidates": [ "chat +at-me", "chat +bot-find", @@ -2250,7 +1802,35 @@ { "tool_path": "chat list-all-conversations", "source": "skills/mono/references/products/chat.md", - "line": 1947, + "line": 1752, + "candidates": [ + "chat +at-me", + "chat +bot-find", + "chat +bot-search" + ], + "review": { + "status": "stale", + "reason": "旧版 Skill 或说明性引用,当前公开命令面无等价 leaf,禁止词法模糊映射" + } + }, + { + "tool_path": "chat list-all-conversations", + "source": "skills/mono/references/products/chat.md", + "line": 1753, + "candidates": [ + "chat +at-me", + "chat +bot-find", + "chat +bot-search" + ], + "review": { + "status": "stale", + "reason": "旧版 Skill 或说明性引用,当前公开命令面无等价 leaf,禁止词法模糊映射" + } + }, + { + "tool_path": "chat list-all-conversations", + "source": "skills/mono/references/products/chat.md", + "line": 1754, "candidates": [ "chat +at-me", "chat +bot-find", @@ -2264,7 +1844,49 @@ { "tool_path": "chat clear-messages", "source": "skills/mono/references/products/chat.md", - "line": 1948, + "line": 1774, + "candidates": [ + "chat +at-me", + "chat +bot-find", + "chat +bot-search" + ], + "review": { + "status": "stale", + "reason": "旧版 Skill 或说明性引用,当前公开命令面无等价 leaf,禁止词法模糊映射" + } + }, + { + "tool_path": "chat clear-messages", + "source": "skills/mono/references/products/chat.md", + "line": 1775, + "candidates": [ + "chat +at-me", + "chat +bot-find", + "chat +bot-search" + ], + "review": { + "status": "stale", + "reason": "旧版 Skill 或说明性引用,当前公开命令面无等价 leaf,禁止词法模糊映射" + } + }, + { + "tool_path": "chat mark-read", + "source": "skills/mono/references/products/chat.md", + "line": 1793, + "candidates": [ + "chat +at-me", + "chat +bot-find", + "chat +bot-search" + ], + "review": { + "status": "stale", + "reason": "旧版 Skill 或说明性引用,当前公开命令面无等价 leaf,禁止词法模糊映射" + } + }, + { + "tool_path": "chat mark-read", + "source": "skills/mono/references/products/chat.md", + "line": 1794, "candidates": [ "chat +at-me", "chat +bot-find", @@ -2278,7 +1900,35 @@ { "tool_path": "chat group list-join-validations", "source": "skills/mono/references/products/chat.md", - "line": 1950, + "line": 1837, + "candidates": [ + "chat group bots", + "chat group create", + "chat group dismiss" + ], + "review": { + "status": "stale", + "reason": "旧版 Skill 或说明性引用,当前公开命令面无等价 leaf,禁止词法模糊映射" + } + }, + { + "tool_path": "chat group list-join-validations", + "source": "skills/mono/references/products/chat.md", + "line": 1838, + "candidates": [ + "chat group bots", + "chat group create", + "chat group dismiss" + ], + "review": { + "status": "stale", + "reason": "旧版 Skill 或说明性引用,当前公开命令面无等价 leaf,禁止词法模糊映射" + } + }, + { + "tool_path": "chat group list-join-validations", + "source": "skills/mono/references/products/chat.md", + "line": 1839, "candidates": [ "chat group bots", "chat group create", @@ -2292,7 +1942,357 @@ { "tool_path": "chat group audit-join-validation", "source": "skills/mono/references/products/chat.md", - "line": 1951, + "line": 1859, + "candidates": [ + "chat group bots", + "chat group create", + "chat group dismiss" + ], + "review": { + "status": "stale", + "reason": "旧版 Skill 或说明性引用,当前公开命令面无等价 leaf,禁止词法模糊映射" + } + }, + { + "tool_path": "chat group audit-join-validation", + "source": "skills/mono/references/products/chat.md", + "line": 1860, + "candidates": [ + "chat group bots", + "chat group create", + "chat group dismiss" + ], + "review": { + "status": "stale", + "reason": "旧版 Skill 或说明性引用,当前公开命令面无等价 leaf,禁止词法模糊映射" + } + }, + { + "tool_path": "chat group members", + "source": "skills/mono/references/products/chat.md", + "line": 1892, + "candidates": [ + "chat group members add", + "chat group members add-bot", + "chat group members remove" + ], + "review": { + "status": "group", + "reason": "Skill 中的命令分组引用,不是可执行 leaf,禁止模糊绑定到任一子命令" + } + }, + { + "tool_path": "chat group update-alias", + "source": "skills/mono/references/products/chat.md", + "line": 1897, + "candidates": [ + "chat group bots", + "chat group create", + "chat group dismiss" + ], + "review": { + "status": "stale", + "reason": "旧版 Skill 或说明性引用,当前公开命令面无等价 leaf,禁止词法模糊映射" + } + }, + { + "tool_path": "chat group members list-by-ids", + "source": "skills/mono/references/products/chat.md", + "line": 1901, + "candidates": [ + "chat message list-by-ids", + "chat group members add", + "chat group members add-bot" + ], + "review": { + "status": "stale", + "reason": "旧版 Skill 或说明性引用,当前公开命令面无等价 leaf,禁止词法模糊映射" + } + }, + { + "tool_path": "chat category create", + "source": "skills/mono/references/products/chat.md", + "line": 1928, + "candidates": [ + "chat group create", + "chat category batch-info", + "chat category create-smart" + ], + "review": { + "status": "stale", + "reason": "旧版 Skill 或说明性引用,当前公开命令面无等价 leaf,禁止词法模糊映射" + } + }, + { + "tool_path": "chat category delete", + "source": "skills/mono/references/products/chat.md", + "line": 1930, + "candidates": [ + "chat category batch-info", + "chat category create-smart", + "chat category list" + ], + "review": { + "status": "stale", + "reason": "旧版 Skill 或说明性引用,当前公开命令面无等价 leaf,禁止词法模糊映射" + } + }, + { + "tool_path": "chat category rename", + "source": "skills/mono/references/products/chat.md", + "line": 1931, + "candidates": [ + "chat group rename", + "chat category batch-info", + "chat category create-smart" + ], + "review": { + "status": "stale", + "reason": "旧版 Skill 或说明性引用,当前公开命令面无等价 leaf,禁止词法模糊映射" + } + }, + { + "tool_path": "chat category add-conv", + "source": "skills/mono/references/products/chat.md", + "line": 1932, + "candidates": [ + "chat category batch-info", + "chat category create-smart", + "chat category list" + ], + "review": { + "status": "stale", + "reason": "旧版 Skill 或说明性引用,当前公开命令面无等价 leaf,禁止词法模糊映射" + } + }, + { + "tool_path": "chat category remove-conv", + "source": "skills/mono/references/products/chat.md", + "line": 1933, + "candidates": [ + "chat category batch-info", + "chat category create-smart", + "chat category list" + ], + "review": { + "status": "stale", + "reason": "旧版 Skill 或说明性引用,当前公开命令面无等价 leaf,禁止词法模糊映射" + } + }, + { + "tool_path": "chat group share-invite", + "source": "skills/mono/references/products/chat.md", + "line": 1944, + "candidates": [ + "chat group bots", + "chat group create", + "chat group dismiss" + ], + "review": { + "status": "stale", + "reason": "旧版 Skill 或说明性引用,当前公开命令面无等价 leaf,禁止词法模糊映射" + } + }, + { + "tool_path": "chat group notice create/edit/get/list", + "source": "skills/mono/references/products/chat.md", + "line": 1945, + "candidates": [ + "chat group bots", + "chat group create", + "chat group dismiss" + ], + "review": { + "status": "stale", + "reason": "旧版 Skill 或说明性引用,当前公开命令面无等价 leaf,禁止词法模糊映射" + } + }, + { + "tool_path": "chat message list-emotion-replies", + "source": "skills/mono/references/products/chat.md", + "line": 1947, + "candidates": [ + "chat message add-emoji", + "chat message add-favorite", + "chat message add-text-emotion" + ], + "review": { + "status": "stale", + "reason": "旧版 Skill 或说明性引用,当前公开命令面无等价 leaf,禁止词法模糊映射" + } + }, + { + "tool_path": "chat hide", + "source": "skills/mono/references/products/chat.md", + "line": 1955, + "candidates": [ + "chat +at-me", + "chat +bot-find", + "chat +bot-search" + ], + "review": { + "status": "stale", + "reason": "旧版 Skill 或说明性引用,当前公开命令面无等价 leaf,禁止词法模糊映射" + } + }, + { + "tool_path": "chat mute-at-all", + "source": "skills/mono/references/products/chat.md", + "line": 1956, + "candidates": [ + "chat +at-me", + "chat +bot-find", + "chat +bot-search" + ], + "review": { + "status": "stale", + "reason": "旧版 Skill 或说明性引用,当前公开命令面无等价 leaf,禁止词法模糊映射" + } + }, + { + "tool_path": "chat mute-at-all", + "source": "skills/mono/references/products/chat.md", + "line": 1957, + "candidates": [ + "chat +at-me", + "chat +bot-find", + "chat +bot-search" + ], + "review": { + "status": "stale", + "reason": "旧版 Skill 或说明性引用,当前公开命令面无等价 leaf,禁止词法模糊映射" + } + }, + { + "tool_path": "chat mute-red-envelope", + "source": "skills/mono/references/products/chat.md", + "line": 1958, + "candidates": [ + "chat +at-me", + "chat +bot-find", + "chat +bot-search" + ], + "review": { + "status": "stale", + "reason": "旧版 Skill 或说明性引用,当前公开命令面无等价 leaf,禁止词法模糊映射" + } + }, + { + "tool_path": "chat mute-red-envelope", + "source": "skills/mono/references/products/chat.md", + "line": 1959, + "candidates": [ + "chat +at-me", + "chat +bot-find", + "chat +bot-search" + ], + "review": { + "status": "stale", + "reason": "旧版 Skill 或说明性引用,当前公开命令面无等价 leaf,禁止词法模糊映射" + } + }, + { + "tool_path": "chat mark-unread", + "source": "skills/mono/references/products/chat.md", + "line": 1960, + "candidates": [ + "chat +at-me", + "chat +bot-find", + "chat +bot-search" + ], + "review": { + "status": "stale", + "reason": "旧版 Skill 或说明性引用,当前公开命令面无等价 leaf,禁止词法模糊映射" + } + }, + { + "tool_path": "chat mark-read", + "source": "skills/mono/references/products/chat.md", + "line": 1961, + "candidates": [ + "chat +at-me", + "chat +bot-find", + "chat +bot-search" + ], + "review": { + "status": "stale", + "reason": "旧版 Skill 或说明性引用,当前公开命令面无等价 leaf,禁止词法模糊映射" + } + }, + { + "tool_path": "chat clear-red-point", + "source": "skills/mono/references/products/chat.md", + "line": 1962, + "candidates": [ + "chat +at-me", + "chat +bot-find", + "chat +bot-search" + ], + "review": { + "status": "stale", + "reason": "旧版 Skill 或说明性引用,当前公开命令面无等价 leaf,禁止词法模糊映射" + } + }, + { + "tool_path": "chat clear-all-red-point", + "source": "skills/mono/references/products/chat.md", + "line": 1963, + "candidates": [ + "chat +at-me", + "chat +bot-find", + "chat +bot-search" + ], + "review": { + "status": "stale", + "reason": "旧版 Skill 或说明性引用,当前公开命令面无等价 leaf,禁止词法模糊映射" + } + }, + { + "tool_path": "chat list-all-conversations", + "source": "skills/mono/references/products/chat.md", + "line": 1964, + "candidates": [ + "chat +at-me", + "chat +bot-find", + "chat +bot-search" + ], + "review": { + "status": "stale", + "reason": "旧版 Skill 或说明性引用,当前公开命令面无等价 leaf,禁止词法模糊映射" + } + }, + { + "tool_path": "chat clear-messages", + "source": "skills/mono/references/products/chat.md", + "line": 1965, + "candidates": [ + "chat +at-me", + "chat +bot-find", + "chat +bot-search" + ], + "review": { + "status": "stale", + "reason": "旧版 Skill 或说明性引用,当前公开命令面无等价 leaf,禁止词法模糊映射" + } + }, + { + "tool_path": "chat group list-join-validations", + "source": "skills/mono/references/products/chat.md", + "line": 1967, + "candidates": [ + "chat group bots", + "chat group create", + "chat group dismiss" + ], + "review": { + "status": "stale", + "reason": "旧版 Skill 或说明性引用,当前公开命令面无等价 leaf,禁止词法模糊映射" + } + }, + { + "tool_path": "chat group audit-join-validation", + "source": "skills/mono/references/products/chat.md", + "line": 1968, "candidates": [ "chat group bots", "chat group create", @@ -2306,7 +2306,7 @@ { "tool_path": "chat text translate", "source": "skills/mono/references/products/chat.md", - "line": 1971, + "line": 1988, "candidates": [ "chat +at-me", "chat +bot-find", diff --git a/internal/cli/schema_catalog/catalog.json b/internal/cli/schema_catalog/catalog.json index f8c408e5..3a4e74d7 100644 --- a/internal/cli/schema_catalog/catalog.json +++ b/internal/cli/schema_catalog/catalog.json @@ -1,17 +1,17 @@ { "version": 1, - "surface_hash": "sha256:698ccdc961c0b745e6524e33ae13d87ef4fcac012a2e833c7a727a79d35004db", - "source_hash": "sha256:fad73828190f801f871fedbbc45bce19401104aa32907fd15191b3f44ce3369e", + "surface_hash": "sha256:60eee8e2f37d6d9d60689efce85082798eb9ad38b7ba7c0b471c3de676a85a16", + "source_hash": "sha256:56356e64f95943e7e3546be446b30b19e44fd8e30d1e80a44be89527b288ead4", "catalog": { "agent_metadata": { "products_with_metadata": 26, "source": "embedded-skill-metadata", - "source_hash": "sha256:ab583120b83cad7e98084072198a961a7f7cf6fe6e79140ec41e21ffaac84f82", - "surface_hash": "sha256:698ccdc961c0b745e6524e33ae13d87ef4fcac012a2e833c7a727a79d35004db", + "source_hash": "sha256:22e3a7c7fcd96293d9f78e274fccc777f896c9b2edb2ed184b46a6a73937cace", + "surface_hash": "sha256:60eee8e2f37d6d9d60689efce85082798eb9ad38b7ba7c0b471c3de676a85a16", "surface_products": 26, - "surface_tools": 818, - "tools_with_agent_summary": 818, - "tools_with_metadata": 818, + "surface_tools": 845, + "tools_with_agent_summary": 845, + "tools_with_metadata": 845, "unmatched_skill_tools": 122, "version": 1 }, @@ -7722,7 +7722,7 @@ "id": "chat", "name": "群聊 / 消息 / 机器人", "runtime": true, - "tool_count": 125, + "tool_count": 129, "tools": [ { "agent_metadata_source": "embedded-skill-metadata", @@ -7907,6 +7907,60 @@ "已有文字表情定义并要附加到消息时" ] }, + { + "agent_metadata_source": "embedded-skill-metadata", + "agent_summary": "批量查询当前用户自己的群会话设置(置顶/免打扰/群昵称/群备注)", + "agent_summary_source": "dws-agent-selection/chat", + "availability": "available", + "avoid_when": [ + "管理员级群功能开关用 chat group update-settings" + ], + "canonical_path": "chat.batch_query_group_chat_settings", + "cli_name": "query", + "cli_path": "chat group user-settings query", + "confirmation": "not_required", + "description": "批量查询当前用户的群会话设置", + "effect": "read", + "group": "group.user-settings", + "idempotency": "idempotent", + "interface_mode": "composite", + "interface_reason": "Reviewed unpinned remote adapter: this executable CLI wrapper calls a remote helper that is absent from the pinned MCP metadata snapshot; no single pinned semantically equivalent interface_ref can represent the command.", + "name": "batch_query_group_chat_settings", + "primary_cli_path": "chat group user-settings query", + "reviewed": true, + "risk": "low", + "title": "批量查询当前用户的群会话设置", + "use_when": [ + "用户说 看下这些群我的置顶和免打扰设置" + ] + }, + { + "agent_metadata_source": "embedded-skill-metadata", + "agent_summary": "批量更新当前用户自己的群会话设置(置顶/免打扰/群昵称/群备注)", + "agent_summary_source": "dws-agent-selection/chat", + "availability": "available", + "avoid_when": [ + "单个群昵称优先 chat group update-nick" + ], + "canonical_path": "chat.batch_update_group_chat_settings", + "cli_name": "set", + "cli_path": "chat group user-settings set", + "confirmation": "not_required", + "description": "批量更新当前用户的群会话设置", + "effect": "write", + "group": "group.user-settings", + "idempotency": "idempotent", + "interface_mode": "composite", + "interface_reason": "Reviewed unpinned remote adapter: this executable CLI wrapper calls a remote helper that is absent from the pinned MCP metadata snapshot; no single pinned semantically equivalent interface_ref can represent the command.", + "name": "batch_update_group_chat_settings", + "primary_cli_path": "chat group user-settings set", + "reviewed": true, + "risk": "medium", + "title": "批量更新当前用户的群会话设置", + "use_when": [ + "用户说 把这些群都设为免打扰/置顶" + ] + }, { "agent_metadata_source": "embedded-skill-metadata", "agent_summary": "把多条消息合并转发到目标会话", @@ -8332,6 +8386,33 @@ "需要生成群邀请链接或设置有效期时" ] }, + { + "agent_metadata_source": "embedded-skill-metadata", + "agent_summary": "查询群用户禁言配置(禁言黑名单/全员禁言白名单)", + "agent_summary_source": "dws-agent-selection/chat", + "availability": "available", + "avoid_when": [ + "设置全员禁言用 chat group-mute;禁言个人用 chat group-mute-member" + ], + "canonical_path": "chat.get_group_mute_config", + "cli_name": "get-mute-config", + "cli_path": "chat group get-mute-config", + "confirmation": "not_required", + "description": "查询指定群的用户禁言配置,包括单独禁言黑名单、全员禁言白名单及相关操作时间。\n返回的是原始配置记录,不等同于当前被禁言成员列表;全员禁言开关也不在本命令的返回范围内。", + "effect": "read", + "group": "group", + "idempotency": "idempotent", + "interface_mode": "composite", + "interface_reason": "Reviewed unpinned remote adapter: this executable CLI wrapper calls a remote helper that is absent from the pinned MCP metadata snapshot; no single pinned semantically equivalent interface_ref can represent the command.", + "name": "get_group_mute_config", + "primary_cli_path": "chat group get-mute-config", + "reviewed": true, + "risk": "low", + "title": "查询群用户禁言配置", + "use_when": [ + "用户说 看下群里谁被禁言/禁言配置" + ] + }, { "agent_metadata_source": "embedded-skill-metadata", "agent_summary": "查询指定会话所属的自定义会话分组", @@ -11296,6 +11377,34 @@ "已有 bizId 并需要追加内容或结束流式输出时" ] }, + { + "agent_metadata_source": "embedded-skill-metadata", + "agent_summary": "把消息上已有的文字表情原地替换为新的文字表情", + "agent_summary_source": "dws-agent-selection/chat", + "availability": "available", + "avoid_when": [ + "消息上还没有文字表情时使用 chat message add-text-emotion", + "只需清除文字表情时使用 chat message remove-text-emotion" + ], + "canonical_path": "chat.update_text_emotion", + "cli_name": "update-text-emotion", + "cli_path": "chat message update-text-emotion", + "confirmation": "not_required", + "description": "更新消息的文字表情回应", + "effect": "write", + "group": "message", + "idempotency": "idempotent", + "interface_mode": "composite", + "interface_reason": "Reviewed unpinned remote adapter: the executable CLI calls im/update_text_emotion, which is proven by dws-wukong develop@30f13f02 but absent from the pinned MCP metadata snapshot; no pinned interface_ref can represent the command yet.", + "name": "update_text_emotion", + "primary_cli_path": "chat message update-text-emotion", + "reviewed": true, + "risk": "low", + "title": "更新消息的文字表情回应", + "use_when": [ + "需要更新消息的状态文字或表情,并避免先移除再添加造成闪烁和两次网络调用时" + ] + }, { "agent_metadata_source": "embedded-skill-metadata", "agent_summary": "不可逆地把已有普通群升级为外部群", @@ -14368,7 +14477,7 @@ "id": "doc", "name": "钉钉文档管理", "runtime": true, - "tool_count": 55, + "tool_count": 60, "tools": [ { "agent_metadata_source": "embedded-skill-metadata", @@ -14817,6 +14926,33 @@ "准备读内容前必须先看 contentType/extension 以路由到 read/sheet/aitable/download 时" ] }, + { + "agent_metadata_source": "embedded-skill-metadata", + "agent_summary": "读取文档当前封面与背景配置(只读)", + "agent_summary_source": "dws-agent-selection/doc", + "availability": "available", + "avoid_when": [ + "修改用 cover set / background set" + ], + "canonical_path": "doc.get_document_style", + "cli_name": "get", + "cli_path": "doc style get", + "confirmation": "not_required", + "description": "读取钉钉文档当前的封面与背景配置(只读,单接口收口 get_document_style)。", + "effect": "read", + "group": "style", + "idempotency": "idempotent", + "interface_mode": "composite", + "interface_reason": "Reviewed unpinned remote adapter: this executable CLI wrapper calls a remote helper that is absent from the pinned MCP metadata snapshot; no single pinned semantically equivalent interface_ref can represent the command.", + "name": "get_document_style", + "primary_cli_path": "doc style get", + "reviewed": true, + "risk": "low", + "title": "读取文档封面/背景 (只读)", + "use_when": [ + "用户说 看下文档现在的封面/背景" + ] + }, { "agent_metadata_source": "embedded-skill-metadata", "agent_summary": "根据 taskId 查询文档导入任务的执行结果", @@ -15666,6 +15802,114 @@ "当你在做重大改动前后、想手动打一个可回滚的版本存档点时使用;输入 node,会实际为该文档保存一个当前内容的历史版本快照。" ] }, + { + "agent_metadata_source": "embedded-skill-metadata", + "agent_summary": "清除钉钉文档背景", + "agent_summary_source": "dws-agent-selection/doc", + "availability": "available", + "avoid_when": [ + "设置背景用 background set" + ], + "canonical_path": "doc.style_background_clear", + "cli_name": "clear", + "cli_path": "doc style background clear", + "confirmation": "not_required", + "description": "清除文档背景", + "effect": "write", + "group": "style.background", + "idempotency": "idempotent", + "interface_mode": "composite", + "interface_reason": "Reviewed unpinned remote adapter: this executable CLI wrapper calls a remote helper that is absent from the pinned MCP metadata snapshot; no single pinned semantically equivalent interface_ref can represent the command.", + "name": "style_background_clear", + "primary_cli_path": "doc style background clear", + "reviewed": true, + "risk": "low", + "title": "清除文档背景", + "use_when": [ + "用户说 去掉文档背景色" + ] + }, + { + "agent_metadata_source": "embedded-skill-metadata", + "agent_summary": "设置钉钉文档背景纯色(#RRGGBB)", + "agent_summary_source": "dws-agent-selection/doc", + "availability": "available", + "avoid_when": [ + "背景不支持图片;封面用 cover set" + ], + "canonical_path": "doc.style_background_set", + "cli_name": "set", + "cli_path": "doc style background set", + "confirmation": "not_required", + "description": "为钉钉文档设置背景纯色(--color)。背景仅支持纯色,不支持背景图片上传(对齐前端能力)。", + "effect": "write", + "group": "style.background", + "idempotency": "idempotent", + "interface_mode": "composite", + "interface_reason": "Reviewed unpinned remote adapter: this executable CLI wrapper calls a remote helper that is absent from the pinned MCP metadata snapshot; no single pinned semantically equivalent interface_ref can represent the command.", + "name": "style_background_set", + "primary_cli_path": "doc style background set", + "reviewed": true, + "risk": "low", + "title": "设置文档背景纯色", + "use_when": [ + "用户说 给文档设置背景色" + ] + }, + { + "agent_metadata_source": "embedded-skill-metadata", + "agent_summary": "移除钉钉文档封面", + "agent_summary_source": "dws-agent-selection/doc", + "availability": "available", + "avoid_when": [ + "设置封面用 cover set" + ], + "canonical_path": "doc.style_cover_clear", + "cli_name": "clear", + "cli_path": "doc style cover clear", + "confirmation": "not_required", + "description": "移除文档封面", + "effect": "write", + "group": "style.cover", + "idempotency": "idempotent", + "interface_mode": "composite", + "interface_reason": "Reviewed unpinned remote adapter: this executable CLI wrapper calls a remote helper that is absent from the pinned MCP metadata snapshot; no single pinned semantically equivalent interface_ref can represent the command.", + "name": "style_cover_clear", + "primary_cli_path": "doc style cover clear", + "reviewed": true, + "risk": "low", + "title": "移除文档封面", + "use_when": [ + "用户说 去掉文档封面" + ] + }, + { + "agent_metadata_source": "embedded-skill-metadata", + "agent_summary": "设置钉钉文档顶部封面图(外链或本地图片上传)", + "agent_summary_source": "dws-agent-selection/doc", + "availability": "available", + "avoid_when": [ + "读取当前封面用 doc style get;移除封面用 cover clear" + ], + "canonical_path": "doc.style_cover_set", + "cli_name": "set", + "cli_path": "doc style cover set", + "confirmation": "not_required", + "description": "为钉钉文档设置顶部封面图,可指定竖直显示位置。图片来源二选一:--image 外链或 --file 本地文件。", + "effect": "write", + "group": "style.cover", + "idempotency": "idempotent", + "interface_mode": "composite", + "interface_reason": "Reviewed unpinned remote adapter: this executable CLI wrapper calls a remote helper that is absent from the pinned MCP metadata snapshot; no single pinned semantically equivalent interface_ref can represent the command.", + "name": "style_cover_set", + "primary_cli_path": "doc style cover set", + "reviewed": true, + "risk": "low", + "title": "设置文档封面", + "use_when": [ + "用户说 给文档设置封面/换个封面图" + ] + }, { "agent_metadata_source": "embedded-skill-metadata", "agent_summary": "使用指定模板创建新文档", @@ -16059,7 +16303,7 @@ "id": "drive", "name": "钉盘文件管理", "runtime": true, - "tool_count": 32, + "tool_count": 41, "tools": [ { "agent_metadata_source": "embedded-skill-metadata", @@ -16095,6 +16339,33 @@ "用户说把某篇文档分享给某人看/可编辑时(含「我的文档」下的节点)" ] }, + { + "agent_metadata_source": "embedded-skill-metadata", + "agent_summary": "向审批人发起文档权限申请(会真实通知审批人)", + "agent_summary_source": "dws-agent-selection/drive", + "availability": "available", + "avoid_when": [ + "先用 apply-info 查可申请角色与审批人;未经用户确认不得自行提交" + ], + "canonical_path": "drive.apply_permission", + "cli_name": "apply", + "cli_path": "drive permission apply", + "confirmation": "user_required", + "description": "向指定节点的审批人发起权限申请。建议先用 apply-info 获取可申请角色与审批人。\n\n注意: 本命令会真实通知审批人,Agent 必须先获得用户明确同意后再执行。", + "effect": "write", + "group": "permission", + "idempotency": "non_idempotent", + "interface_mode": "composite", + "interface_reason": "Reviewed unpinned remote adapter: this executable CLI wrapper calls a remote helper that is absent from the pinned MCP metadata snapshot; no single pinned semantically equivalent interface_ref can represent the command.", + "name": "apply_permission", + "primary_cli_path": "drive permission apply", + "reviewed": true, + "risk": "medium", + "title": "发起权限申请", + "use_when": [ + "用户确认要为无权限文档发起权限申请" + ] + }, { "agent_metadata_source": "embedded-skill-metadata", "agent_summary": "提交文件上传", @@ -16290,6 +16561,61 @@ "已确认 contentType 非 ALIDOC,需要落盘本地查看时" ] }, + { + "agent_metadata_source": "embedded-skill-metadata", + "agent_summary": "下载钉盘普通文件的指定历史版本到本地(两步下载:取签名 URL 后 HTTP GET)", + "agent_summary_source": "dws-agent-selection/drive", + "availability": "available", + "avoid_when": [ + "下载最新版本用 drive download", + "在线文档(adoc)历史版本用 doc version 系列命令", + "在线表格(axls)历史版本用 sheet version 系列命令" + ], + "canonical_path": "drive.download_file_version", + "cli_name": "download-version", + "cli_path": "drive download-version", + "confirmation": "not_required", + "description": "下载钉盘文件的指定历史版本到本地(两步下载流程)。\n\n仅适用于普通文件(如 pdf、docx、xlsx、png 等):\n 钉钉在线文档(adoc)请使用 dws doc version 系列命令\n 钉钉在线表格(axls)请使用 dws sheet version 系列命令\n\n流程:\n 1. 获取历史版本下载 URL 和签名请求头 (download_file_version)\n 2. HTTP GET 下载文件二进制内容到本地\n\n版本号从 dws drive list --node \u003cdentryUuid\u003e --versions 获取。", + "effect": "read", + "idempotency": "idempotent", + "interface_mode": "composite", + "interface_reason": "Reviewed unpinned remote adapter: this executable CLI wrapper calls a remote helper that is absent from the pinned MCP metadata snapshot; no single pinned semantically equivalent interface_ref can represent the command.", + "name": "download_file_version", + "primary_cli_path": "drive download-version", + "reviewed": true, + "risk": "low", + "title": "下载文件历史版本到本地", + "use_when": [ + "用户要下载文件的历史版本/旧版本", + "版本号已通过 drive list --versions 获取" + ] + }, + { + "agent_metadata_source": "embedded-skill-metadata", + "agent_summary": "获取节点封面图片地址(文档首图/图片缩略图/类型图标)", + "agent_summary_source": "dws-agent-selection/drive", + "availability": "available", + "avoid_when": [ + "设置文档封面用 doc style cover set" + ], + "canonical_path": "drive.get_cover", + "cli_name": "cover", + "cli_path": "drive cover", + "confirmation": "not_required", + "description": "获取节点封面地址", + "effect": "read", + "idempotency": "idempotent", + "interface_mode": "composite", + "interface_reason": "Reviewed unpinned remote adapter: this executable CLI wrapper calls a remote helper that is absent from the pinned MCP metadata snapshot; no single pinned semantically equivalent interface_ref can represent the command.", + "name": "get_cover", + "primary_cli_path": "drive cover", + "reviewed": true, + "risk": "low", + "title": "获取节点封面地址", + "use_when": [ + "用户说 封面/封面图/缩略图/预览图" + ] + }, { "agent_metadata_source": "embedded-skill-metadata", "agent_summary": "获取文件元数据信息", @@ -16350,6 +16676,33 @@ "用户要查看节点阅读/编辑/评论/点赞/预览/下载等统计维度时" ] }, + { + "agent_metadata_source": "embedded-skill-metadata", + "agent_summary": "获取当前用户的收藏列表,支持分页与按内容类型筛选", + "agent_summary_source": "dws-agent-selection/drive", + "availability": "available", + "avoid_when": [ + "操作单个收藏用 mark_star/unmark_star" + ], + "canonical_path": "drive.get_star_list", + "cli_name": "list", + "cli_path": "drive star list", + "confirmation": "not_required", + "description": "获取收藏列表", + "effect": "read", + "group": "star", + "idempotency": "idempotent", + "interface_mode": "composite", + "interface_reason": "Reviewed unpinned remote adapter: this executable CLI wrapper calls a remote helper that is absent from the pinned MCP metadata snapshot; no single pinned semantically equivalent interface_ref can represent the command.", + "name": "get_star_list", + "primary_cli_path": "drive star list", + "reviewed": true, + "risk": "low", + "title": "获取收藏列表", + "use_when": [ + "用户说 我的收藏/收藏列表/收藏了哪些文档" + ] + }, { "agent_metadata_source": "embedded-skill-metadata", "agent_summary": "获取文件上传信息", @@ -16475,6 +16828,33 @@ "兼容入口:枚举钉盘企业空间或「我的文件」空间以拿 spaceId/rootFolderId 时" ] }, + { + "agent_metadata_source": "embedded-skill-metadata", + "agent_summary": "收藏文档/文件到当前用户收藏列表", + "agent_summary_source": "dws-agent-selection/drive", + "availability": "available", + "avoid_when": [ + "取消收藏用 unmark_star;查看收藏列表用 get_star_list" + ], + "canonical_path": "drive.mark_star", + "cli_name": "add", + "cli_path": "drive star add", + "confirmation": "not_required", + "description": "收藏文档", + "effect": "write", + "group": "star", + "idempotency": "idempotent", + "interface_mode": "composite", + "interface_reason": "Reviewed unpinned remote adapter: this executable CLI wrapper calls a remote helper that is absent from the pinned MCP metadata snapshot; no single pinned semantically equivalent interface_ref can represent the command.", + "name": "mark_star", + "primary_cli_path": "drive star add", + "reviewed": true, + "risk": "low", + "title": "收藏文档", + "use_when": [ + "用户说 收藏这个文档/加个收藏/标星" + ] + }, { "agent_metadata_source": "embedded-skill-metadata", "agent_summary": "将文件或文档移动到目标文件夹或知识库(原位置不再保留)", @@ -16658,6 +17038,33 @@ "用户明确要求关闭文件的互联网公开发布,使外部链接失效时" ] }, + { + "agent_metadata_source": "embedded-skill-metadata", + "agent_summary": "查询节点可申请的权限角色列表与审批人列表", + "agent_summary_source": "dws-agent-selection/drive", + "availability": "available", + "avoid_when": [ + "实际发起申请用 apply_permission" + ], + "canonical_path": "drive.query_permission_apply_info", + "cli_name": "apply-info", + "cli_path": "drive permission apply-info", + "confirmation": "not_required", + "description": "查询节点可申请的角色与审批人", + "effect": "read", + "group": "permission", + "idempotency": "idempotent", + "interface_mode": "composite", + "interface_reason": "Reviewed unpinned remote adapter: this executable CLI wrapper calls a remote helper that is absent from the pinned MCP metadata snapshot; no single pinned semantically equivalent interface_ref can represent the command.", + "name": "query_permission_apply_info", + "primary_cli_path": "drive permission apply-info", + "reviewed": true, + "risk": "low", + "title": "查询节点可申请的角色与审批人", + "use_when": [ + "无权限访问文档时,先查可申请角色与审批人" + ] + }, { "agent_metadata_source": "embedded-skill-metadata", "agent_summary": "获取当前用户最近访问或编辑过的文档列表", @@ -16774,6 +17181,32 @@ "用户要重命名钉盘/文档空间中的文件、文档或文件夹时;实际执行会读取节点类型和当前扩展名,仅去掉完全匹配的一层后缀" ] }, + { + "agent_metadata_source": "embedded-skill-metadata", + "agent_summary": "回滚普通文件到指定历史版本(生成新最新版本,历史不丢失)", + "agent_summary_source": "dws-agent-selection/drive", + "availability": "available", + "avoid_when": [ + "在线文档回滚用 doc version revert;在线表格用 sheet version revert" + ], + "canonical_path": "drive.revert_file_version", + "cli_name": "revert", + "cli_path": "drive revert", + "confirmation": "user_required", + "description": "将指定文件回滚到某个历史版本。仅支持普通文件(Word、Excel、PDF、图片等)。\n在线文档请用 dws doc version revert,在线表格请用 dws sheet version revert。", + "effect": "write", + "idempotency": "non_idempotent", + "interface_mode": "composite", + "interface_reason": "Reviewed unpinned remote adapter: this executable CLI wrapper calls a remote helper that is absent from the pinned MCP metadata snapshot; no single pinned semantically equivalent interface_ref can represent the command.", + "name": "revert_file_version", + "primary_cli_path": "drive revert", + "reviewed": true, + "risk": "medium", + "title": "[危险] 回滚文件到指定历史版本", + "use_when": [ + "用户说 回滚版本/恢复到某个版本/版本回退,且目标是普通文件" + ] + }, { "agent_metadata_source": "embedded-skill-metadata", "agent_summary": "全局搜索文件,默认同时搜索钉盘和文档空间,合并返回结果", @@ -16990,6 +17423,60 @@ "当你只记得文档标题或关键词、想在文档空间/知识库中检索在线文档(区别于 +search 检索钉盘文件)时使用;输入 query 关键词,返回匹配的文档及其节点信息。" ] }, + { + "agent_metadata_source": "embedded-skill-metadata", + "agent_summary": "转交文档或知识库所有者给指定用户(不可逆)", + "agent_summary_source": "dws-agent-selection/drive", + "availability": "available", + "avoid_when": [ + "普通协作权限变更用 permission add/update/remove" + ], + "canonical_path": "drive.transfer_owner", + "cli_name": "transfer-owner", + "cli_path": "drive permission transfer-owner", + "confirmation": "user_required", + "description": "转交文档或知识库的所有者给指定用户。此操作不可逆,执行前需要确认。\n\n--node 和 --workspace 二选一。转交后原所有者保留角色由 --reserve-role 指定。\n使用 --yes 跳过确认时,--reserve-role 和 --recursive 必须显式指定。", + "effect": "write", + "group": "permission", + "idempotency": "non_idempotent", + "interface_mode": "composite", + "interface_reason": "Reviewed unpinned remote adapter: this executable CLI wrapper calls a remote helper that is absent from the pinned MCP metadata snapshot; no single pinned semantically equivalent interface_ref can represent the command.", + "name": "transfer_owner", + "primary_cli_path": "drive permission transfer-owner", + "reviewed": true, + "risk": "high", + "title": "[危险] 转交所有者", + "use_when": [ + "用户明确要求转交文档/知识库所有权" + ] + }, + { + "agent_metadata_source": "embedded-skill-metadata", + "agent_summary": "将文档/文件从当前用户收藏列表移除", + "agent_summary_source": "dws-agent-selection/drive", + "availability": "available", + "avoid_when": [ + "添加收藏用 mark_star" + ], + "canonical_path": "drive.unmark_star", + "cli_name": "remove", + "cli_path": "drive star remove", + "confirmation": "not_required", + "description": "取消收藏文档", + "effect": "write", + "group": "star", + "idempotency": "idempotent", + "interface_mode": "composite", + "interface_reason": "Reviewed unpinned remote adapter: this executable CLI wrapper calls a remote helper that is absent from the pinned MCP metadata snapshot; no single pinned semantically equivalent interface_ref can represent the command.", + "name": "unmark_star", + "primary_cli_path": "drive star remove", + "reviewed": true, + "risk": "low", + "title": "取消收藏文档", + "use_when": [ + "用户说 取消收藏/去掉收藏/不收藏了" + ] + }, { "agent_metadata_source": "embedded-skill-metadata", "agent_summary": "上传本地文件到钉盘或文档空间,或按节点 ID 确认覆盖已有文件", @@ -21858,7 +22345,7 @@ "id": "sheet", "name": "钉钉表格管理", "runtime": true, - "tool_count": 81, + "tool_count": 90, "tools": [ { "agent_metadata_source": "embedded-skill-metadata", @@ -22295,6 +22782,33 @@ "已有 axls 文档,需要新增一个工作表页签时" ] }, + { + "agent_metadata_source": "embedded-skill-metadata", + "agent_summary": "在指定单元格上创建评论,可 @ 用户", + "agent_summary_source": "dws-agent-selection/sheet", + "availability": "available", + "avoid_when": [ + "回复已有评论用 comment reply;不要把评论写进单元格值" + ], + "canonical_path": "sheet.create_sheet_comment", + "cli_name": "create", + "cli_path": "sheet comment create", + "confirmation": "not_required", + "description": "创建单元格评论", + "effect": "write", + "group": "comment", + "idempotency": "non_idempotent", + "interface_mode": "composite", + "interface_reason": "Reviewed unpinned remote adapter: this executable CLI wrapper calls a remote helper that is absent from the pinned MCP metadata snapshot; no single pinned semantically equivalent interface_ref can represent the command.", + "name": "create_sheet_comment", + "primary_cli_path": "sheet comment create", + "reviewed": true, + "risk": "low", + "title": "创建单元格评论", + "use_when": [ + "用户说 给单元格加评论/批注/@某人讨论这个数据" + ] + }, { "agent_metadata_source": "embedded-skill-metadata", "agent_summary": "创建钉钉在线电子表格文档(axls),返回 nodeId。", @@ -22565,6 +23079,33 @@ "用户明确要求永久删除某个工作表页签,且已确认目标 sheetId 时" ] }, + { + "agent_metadata_source": "embedded-skill-metadata", + "agent_summary": "删除单元格评论(不可恢复)", + "agent_summary_source": "dws-agent-selection/sheet", + "availability": "available", + "avoid_when": [ + "标记解决不等于删除" + ], + "canonical_path": "sheet.delete_sheet_comment", + "cli_name": "delete", + "cli_path": "sheet comment delete", + "confirmation": "user_required", + "description": "删除单元格评论", + "effect": "write", + "group": "comment", + "idempotency": "non_idempotent", + "interface_mode": "composite", + "interface_reason": "Reviewed unpinned remote adapter: this executable CLI wrapper calls a remote helper that is absent from the pinned MCP metadata snapshot; no single pinned semantically equivalent interface_ref can represent the command.", + "name": "delete_sheet_comment", + "primary_cli_path": "sheet comment delete", + "reviewed": true, + "risk": "medium", + "title": "删除单元格评论", + "use_when": [ + "用户明确要求删除某条评论" + ] + }, { "agent_metadata_source": "embedded-skill-metadata", "agent_summary": "按源区域对目标区域做自动填充(复制或序列)。", @@ -22817,6 +23358,32 @@ "要查找包含某文本或公式的单元格位置时,必须用服务端 find" ] }, + { + "agent_metadata_source": "embedded-skill-metadata", + "agent_summary": "扫描表格公式单元格并按错误类型聚合返回错误数量与位置", + "agent_summary_source": "dws-agent-selection/sheet", + "availability": "available", + "avoid_when": [ + "读取公式文本用 range read --value-render-option formula" + ], + "canonical_path": "sheet.formula_verify", + "cli_name": "formula-verify", + "cli_path": "sheet formula-verify", + "confirmation": "not_required", + "description": "扫描钉钉电子表格中已落表的公式单元格,按计算结果错误类型聚合返回错误数量、位置和样本。\n\n不指定 --sheet-id / --range / --targets 时默认扫描整本表格的全部工作表。", + "effect": "read", + "idempotency": "idempotent", + "interface_mode": "composite", + "interface_reason": "Reviewed unpinned remote adapter: this executable CLI wrapper calls a remote helper that is absent from the pinned MCP metadata snapshot; no single pinned semantically equivalent interface_ref can represent the command.", + "name": "formula_verify", + "primary_cli_path": "sheet formula-verify", + "reviewed": true, + "risk": "low", + "title": "校验表格公式错误", + "use_when": [ + "用户说 校验公式/检查公式错误/公式错误扫描" + ] + }, { "agent_metadata_source": "embedded-skill-metadata", "agent_summary": "列出电子表格文档内全部工作表的 ID 与名称。", @@ -23255,6 +23822,33 @@ "需要查看工作表上有哪些透视表或某个透视表详情时" ] }, + { + "agent_metadata_source": "embedded-skill-metadata", + "agent_summary": "查询表格单元格评论列表,支持分页与按解决状态过滤", + "agent_summary_source": "dws-agent-selection/sheet", + "availability": "available", + "avoid_when": [ + "新建评论用 comment create" + ], + "canonical_path": "sheet.list_sheet_comments", + "cli_name": "list", + "cli_path": "sheet comment list", + "confirmation": "not_required", + "description": "查询表格评论列表", + "effect": "read", + "group": "comment", + "idempotency": "idempotent", + "interface_mode": "composite", + "interface_reason": "Reviewed unpinned remote adapter: this executable CLI wrapper calls a remote helper that is absent from the pinned MCP metadata snapshot; no single pinned semantically equivalent interface_ref can represent the command.", + "name": "list_sheet_comments", + "primary_cli_path": "sheet comment list", + "reviewed": true, + "risk": "low", + "title": "查询表格评论列表", + "use_when": [ + "用户说 看某格的评论/表格里有哪些批注" + ] + }, { "agent_metadata_source": "embedded-skill-metadata", "agent_summary": "上传附件到表格并返回 resourceUrl(浮动图片前置步骤)。", @@ -23581,6 +24175,33 @@ "要把工作表中匹配文本批量替换为另一文本时" ] }, + { + "agent_metadata_source": "embedded-skill-metadata", + "agent_summary": "回复指定单元格评论,支持表情贴图回复", + "agent_summary_source": "dws-agent-selection/sheet", + "availability": "available", + "avoid_when": [ + "修改评论内容用 comment update" + ], + "canonical_path": "sheet.reply_sheet_comment", + "cli_name": "reply", + "cli_path": "sheet comment reply", + "confirmation": "not_required", + "description": "回复单元格评论", + "effect": "write", + "group": "comment", + "idempotency": "non_idempotent", + "interface_mode": "composite", + "interface_reason": "Reviewed unpinned remote adapter: this executable CLI wrapper calls a remote helper that is absent from the pinned MCP metadata snapshot; no single pinned semantically equivalent interface_ref can represent the command.", + "name": "reply_sheet_comment", + "primary_cli_path": "sheet comment reply", + "reviewed": true, + "risk": "low", + "title": "回复单元格评论", + "use_when": [ + "用户说 回复这条评论" + ] + }, { "agent_metadata_source": "embedded-skill-metadata", "agent_summary": "为指定范围设置下拉列表(可多选、可带颜色)。", @@ -24219,6 +24840,114 @@ "要重命名工作表、调整页签顺序、隐藏/显示、冻结行列或改标签颜色时" ] }, + { + "agent_metadata_source": "embedded-skill-metadata", + "agent_summary": "更新单元格评论内容", + "agent_summary_source": "dws-agent-selection/sheet", + "availability": "available", + "avoid_when": [ + "回复评论用 comment reply" + ], + "canonical_path": "sheet.update_sheet_comment", + "cli_name": "update", + "cli_path": "sheet comment update", + "confirmation": "not_required", + "description": "更新单元格评论", + "effect": "write", + "group": "comment", + "idempotency": "idempotent", + "interface_mode": "composite", + "interface_reason": "Reviewed unpinned remote adapter: this executable CLI wrapper calls a remote helper that is absent from the pinned MCP metadata snapshot; no single pinned semantically equivalent interface_ref can represent the command.", + "name": "update_sheet_comment", + "primary_cli_path": "sheet comment update", + "reviewed": true, + "risk": "low", + "title": "更新单元格评论", + "use_when": [ + "用户说 改一下这条评论" + ] + }, + { + "agent_metadata_source": "embedded-skill-metadata", + "agent_summary": "查看表格历史版本列表", + "agent_summary_source": "dws-agent-selection/sheet", + "availability": "available", + "avoid_when": [ + "回滚用 version revert" + ], + "canonical_path": "sheet.version_list", + "cli_name": "list", + "cli_path": "sheet version list", + "confirmation": "not_required", + "description": "查看表格历史版本列表", + "effect": "read", + "group": "version", + "idempotency": "idempotent", + "interface_mode": "composite", + "interface_reason": "Reviewed unpinned remote adapter: this executable CLI wrapper calls a remote helper that is absent from the pinned MCP metadata snapshot; no single pinned semantically equivalent interface_ref can represent the command.", + "name": "version_list", + "primary_cli_path": "sheet version list", + "reviewed": true, + "risk": "low", + "title": "查看表格历史版本列表", + "use_when": [ + "用户说 看历史版本/版本列表,目标是在线表格" + ] + }, + { + "agent_metadata_source": "embedded-skill-metadata", + "agent_summary": "回滚表格到指定历史版本", + "agent_summary_source": "dws-agent-selection/sheet", + "availability": "available", + "avoid_when": [ + "普通文件回滚用 drive revert;在线文档用 doc version revert" + ], + "canonical_path": "sheet.version_revert", + "cli_name": "revert", + "cli_path": "sheet version revert", + "confirmation": "user_required", + "description": "[危险] 回滚表格到指定版本", + "effect": "write", + "group": "version", + "idempotency": "non_idempotent", + "interface_mode": "composite", + "interface_reason": "Reviewed unpinned remote adapter: this executable CLI wrapper calls a remote helper that is absent from the pinned MCP metadata snapshot; no single pinned semantically equivalent interface_ref can represent the command.", + "name": "version_revert", + "primary_cli_path": "sheet version revert", + "reviewed": true, + "risk": "medium", + "title": "[危险] 回滚表格到指定版本", + "use_when": [ + "用户说 回滚到某个版本/恢复到之前的表格" + ] + }, + { + "agent_metadata_source": "embedded-skill-metadata", + "agent_summary": "手动保存表格版本快照", + "agent_summary_source": "dws-agent-selection/sheet", + "availability": "available", + "avoid_when": [ + "回滚用 version revert;查看历史用 version list" + ], + "canonical_path": "sheet.version_save", + "cli_name": "save", + "cli_path": "sheet version save", + "confirmation": "not_required", + "description": "手动保存表格版本快照", + "effect": "write", + "group": "version", + "idempotency": "non_idempotent", + "interface_mode": "composite", + "interface_reason": "Reviewed unpinned remote adapter: this executable CLI wrapper calls a remote helper that is absent from the pinned MCP metadata snapshot; no single pinned semantically equivalent interface_ref can represent the command.", + "name": "version_save", + "primary_cli_path": "sheet version save", + "reviewed": true, + "risk": "low", + "title": "手动保存表格版本快照", + "use_when": [ + "用户说 保存版本/存个快照,目标是在线表格" + ] + }, { "agent_metadata_source": "embedded-skill-metadata", "agent_summary": "上传图片并写入指定单元格(占单元格内容)。", @@ -25915,6 +26644,6 @@ } ], "source": "embedded-command-catalog", - "tool_count": 818 + "tool_count": 845 } } diff --git a/internal/cli/schema_catalog/tools/chat.json b/internal/cli/schema_catalog/tools/chat.json index 2558eaf3..bb736b71 100644 --- a/internal/cli/schema_catalog/tools/chat.json +++ b/internal/cli/schema_catalog/tools/chat.json @@ -4849,6 +4849,840 @@ "已有文字表情定义并要附加到消息时" ] }, + "chat.batch_query_group_chat_settings": { + "agent_metadata_source": "embedded-skill-metadata", + "agent_source_refs": [ + "CommandRegistry:canonical_path=chat.batch_query_group_chat_settings", + "cobra-help:dws chat group user-settings query", + "internal/cli/schema_hints/metadata/chat.json", + "internal/cli/schema_hints/selection/chat.json", + "wukong-develop:wukong/products" + ], + "agent_summary": "批量查询当前用户自己的群会话设置(置顶/免打扰/群昵称/群备注)", + "agent_summary_source": "dws-agent-selection/chat", + "availability": "available", + "avoid_when": [ + "管理员级群功能开关用 chat group update-settings" + ], + "canonical_path": "chat.batch_query_group_chat_settings", + "cli_name": "query", + "cli_path": "chat group user-settings query", + "confirmation": "not_required", + "description": "批量查询当前用户的群会话设置", + "display": "群聊 / 消息 / 机器人", + "effect": "read", + "effect_source": "agent-hint", + "examples": [ + "dws chat group user-settings query --groups cid1,cid2 --format json" + ], + "field_provenance": { + "agent_summary": { + "candidates": [ + { + "precedence": "reviewed_explicit", + "review_reason": "人工审阅:对照悟空 develop 同名命令实现、Cobra Long/Example 与 Runtime 确认门禁移植;不改变命令身份、参数契约或接口绑定。", + "selected": true, + "source": "internal/cli/schema_hints/selection/chat.json", + "value": "批量查询当前用户自己的群会话设置(置顶/免打扰/群昵称/群备注)" + } + ], + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "人工审阅:对照悟空 develop 同名命令实现、Cobra Long/Example 与 Runtime 确认门禁移植;不改变命令身份、参数契约或接口绑定。", + "source": "internal/cli/schema_hints/selection/chat.json", + "value": "批量查询当前用户自己的群会话设置(置顶/免打扰/群昵称/群备注)" + }, + "availability": { + "candidates": [ + { + "precedence": "reviewed_explicit", + "review_reason": "dws-tool-metadata/chat marks this tool as reviewed", + "selected": true, + "source": "internal/cli/schema_hints/metadata/chat.json", + "value": "available" + } + ], + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "dws-tool-metadata/chat marks this tool as reviewed", + "source": "internal/cli/schema_hints/metadata/chat.json", + "value": "available" + }, + "avoid_when": { + "candidates": [ + { + "precedence": "reviewed_explicit", + "review_reason": "人工审阅:对照悟空 develop 同名命令实现、Cobra Long/Example 与 Runtime 确认门禁移植;不改变命令身份、参数契约或接口绑定。", + "selected": true, + "source": "internal/cli/schema_hints/selection/chat.json", + "value": [ + "管理员级群功能开关用 chat group update-settings" + ] + } + ], + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "人工审阅:对照悟空 develop 同名命令实现、Cobra Long/Example 与 Runtime 确认门禁移植;不改变命令身份、参数契约或接口绑定。", + "source": "internal/cli/schema_hints/selection/chat.json", + "value": [ + "管理员级群功能开关用 chat group update-settings" + ] + }, + "canonical_path": { + "candidates": [ + { + "precedence": "command_registry", + "selected": true, + "source": "reviewed_command_registry", + "source_ref": "chat group user-settings query", + "value": "chat.batch_query_group_chat_settings" + } + ], + "precedence": "command_registry", + "resolution": "registry_identity", + "source": "reviewed_command_registry", + "source_ref": "chat group user-settings query", + "value": "chat.batch_query_group_chat_settings" + }, + "confirmation": { + "candidates": [ + { + "precedence": "reviewed_explicit", + "review_reason": "dws-tool-metadata/chat marks this tool as reviewed", + "selected": true, + "source": "internal/cli/schema_hints/metadata/chat.json", + "value": "not_required" + } + ], + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "dws-tool-metadata/chat marks this tool as reviewed", + "source": "internal/cli/schema_hints/metadata/chat.json", + "value": "not_required" + }, + "description": { + "candidates": [ + { + "precedence": "cobra_help", + "selected": true, + "source": "cobra_help", + "value": "批量查询当前用户的群会话设置" + } + ], + "precedence": "cobra_help", + "resolution": "highest_precedence", + "source": "cobra_help", + "value": "批量查询当前用户的群会话设置" + }, + "effect": { + "candidates": [ + { + "precedence": "reviewed_explicit", + "review_reason": "dws-tool-metadata/chat marks this tool as reviewed", + "selected": true, + "source": "internal/cli/schema_hints/metadata/chat.json", + "value": "read" + } + ], + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "dws-tool-metadata/chat marks this tool as reviewed", + "source": "internal/cli/schema_hints/metadata/chat.json", + "value": "read" + }, + "examples": { + "candidates": [ + { + "precedence": "reviewed_explicit", + "review_reason": "人工审阅:对照悟空 develop 同名命令实现、Cobra Long/Example 与 Runtime 确认门禁移植;不改变命令身份、参数契约或接口绑定。", + "selected": true, + "source": "internal/cli/schema_hints/selection/chat.json", + "value": [ + "dws chat group user-settings query --groups cid1,cid2 --format json" + ] + } + ], + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "人工审阅:对照悟空 develop 同名命令实现、Cobra Long/Example 与 Runtime 确认门禁移植;不改变命令身份、参数契约或接口绑定。", + "source": "internal/cli/schema_hints/selection/chat.json", + "value": [ + "dws chat group user-settings query --groups cid1,cid2 --format json" + ] + }, + "idempotency": { + "candidates": [ + { + "precedence": "reviewed_explicit", + "review_reason": "dws-tool-metadata/chat marks this tool as reviewed", + "selected": true, + "source": "internal/cli/schema_hints/metadata/chat.json", + "value": "idempotent" + } + ], + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "dws-tool-metadata/chat marks this tool as reviewed", + "source": "internal/cli/schema_hints/metadata/chat.json", + "value": "idempotent" + }, + "interface_mode": { + "candidates": [ + { + "precedence": "reviewed_explicit", + "review_reason": "dws-tool-metadata/chat marks this tool as reviewed", + "selected": true, + "source": "internal/cli/schema_hints/metadata/chat.json", + "value": "composite" + } + ], + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "dws-tool-metadata/chat marks this tool as reviewed", + "source": "internal/cli/schema_hints/metadata/chat.json", + "value": "composite" + }, + "interface_reason": { + "candidates": [ + { + "precedence": "reviewed_explicit", + "review_reason": "dws-tool-metadata/chat marks this tool as reviewed", + "selected": true, + "source": "internal/cli/schema_hints/metadata/chat.json", + "value": "Reviewed unpinned remote adapter: this executable CLI wrapper calls a remote helper that is absent from the pinned MCP metadata snapshot; no single pinned semantically equivalent interface_ref can represent the command." + } + ], + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "dws-tool-metadata/chat marks this tool as reviewed", + "source": "internal/cli/schema_hints/metadata/chat.json", + "value": "Reviewed unpinned remote adapter: this executable CLI wrapper calls a remote helper that is absent from the pinned MCP metadata snapshot; no single pinned semantically equivalent interface_ref can represent the command." + }, + "interface_ref": { + "candidates": [ + { + "precedence": "reviewed_explicit", + "review_reason": "final interface mode composite forbids a direct MCP interface_ref", + "selected": true, + "source": "internal/cli/schema_hints/metadata/chat.json", + "value": null + } + ], + "precedence": "reviewed_explicit", + "resolution": "interface_disposition_matrix", + "review_reason": "final interface mode composite forbids a direct MCP interface_ref", + "source": "internal/cli/schema_hints/metadata/chat.json", + "value": null + }, + "reviewed": { + "candidates": [ + { + "precedence": "reviewed_explicit", + "review_reason": "dws-tool-metadata/chat marks this tool as reviewed", + "selected": true, + "source": "internal/cli/schema_hints/metadata/chat.json", + "value": true + }, + { + "precedence": "reviewed_explicit", + "review_reason": "人工审阅:对照悟空 develop 同名命令实现、Cobra Long/Example 与 Runtime 确认门禁移植;不改变命令身份、参数契约或接口绑定。", + "selected": false, + "source": "internal/cli/schema_hints/selection/chat.json", + "value": true + } + ], + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "dws-tool-metadata/chat marks this tool as reviewed", + "source": "internal/cli/schema_hints/metadata/chat.json", + "value": true + }, + "risk": { + "candidates": [ + { + "precedence": "reviewed_explicit", + "review_reason": "dws-tool-metadata/chat marks this tool as reviewed", + "selected": true, + "source": "internal/cli/schema_hints/metadata/chat.json", + "value": "low" + } + ], + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "dws-tool-metadata/chat marks this tool as reviewed", + "source": "internal/cli/schema_hints/metadata/chat.json", + "value": "low" + }, + "title": { + "candidates": [ + { + "precedence": "cobra_help", + "selected": true, + "source": "cobra_help", + "value": "批量查询当前用户的群会话设置" + } + ], + "precedence": "cobra_help", + "resolution": "highest_precedence", + "source": "cobra_help", + "value": "批量查询当前用户的群会话设置" + }, + "use_when": { + "candidates": [ + { + "precedence": "reviewed_explicit", + "review_reason": "人工审阅:对照悟空 develop 同名命令实现、Cobra Long/Example 与 Runtime 确认门禁移植;不改变命令身份、参数契约或接口绑定。", + "selected": true, + "source": "internal/cli/schema_hints/selection/chat.json", + "value": [ + "用户说 看下这些群我的置顶和免打扰设置" + ] + } + ], + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "人工审阅:对照悟空 develop 同名命令实现、Cobra Long/Example 与 Runtime 确认门禁移植;不改变命令身份、参数契约或接口绑定。", + "source": "internal/cli/schema_hints/selection/chat.json", + "value": [ + "用户说 看下这些群我的置顶和免打扰设置" + ] + } + }, + "group": "group.user-settings", + "has_parameters": true, + "idempotency": "idempotent", + "interface_mode": "composite", + "interface_reason": "Reviewed unpinned remote adapter: this executable CLI wrapper calls a remote helper that is absent from the pinned MCP metadata snapshot; no single pinned semantically equivalent interface_ref can represent the command.", + "is_alias": false, + "name": "batch_query_group_chat_settings", + "parameter_count": 1, + "parameters": { + "groups": { + "description": "群会话 openConversationId 列表,逗号分隔,最多 100 个 (必填)", + "field_provenance": { + "description": { + "candidates": [ + { + "precedence": "cobra_contract", + "selected": true, + "source": "cobra_usage", + "value": "群会话 openConversationId 列表,逗号分隔,最多 100 个 (必填)" + }, + { + "precedence": "default", + "selected": false, + "source": "default", + "value": "" + } + ], + "precedence": "cobra_contract", + "resolution": "highest_precedence", + "source": "cobra_usage", + "value": "群会话 openConversationId 列表,逗号分隔,最多 100 个 (必填)" + }, + "property": { + "candidates": [ + { + "precedence": "reviewed_mapping_exclusion", + "review_reason": "Reviewed unpinned adapter: chat.batch_query_group_chat_settings has no singular pinned interface_ref; --groups is a CLI wrapper input and does not publish a direct interface property.", + "selected": true, + "source": "reviewed_mapping_exclusion", + "value": "" + }, + { + "precedence": "inference", + "selected": false, + "source": "flag_name_inference", + "value": "groups" + } + ], + "precedence": "reviewed_mapping_exclusion", + "resolution": "highest_precedence", + "review_reason": "Reviewed unpinned adapter: chat.batch_query_group_chat_settings has no singular pinned interface_ref; --groups is a CLI wrapper input and does not publish a direct interface property.", + "source": "reviewed_mapping_exclusion", + "value": "" + }, + "required": { + "candidates": [ + { + "precedence": "inference", + "selected": true, + "source": "usage_required_inference", + "value": true + }, + { + "precedence": "default", + "selected": false, + "source": "default", + "value": false + } + ], + "precedence": "inference", + "resolution": "highest_precedence", + "source": "usage_required_inference", + "value": true + }, + "required_when": { + "candidates": [ + { + "precedence": "default", + "selected": true, + "source": "default", + "value": "" + } + ], + "precedence": "default", + "resolution": "highest_precedence", + "source": "default", + "value": "" + }, + "type": { + "candidates": [ + { + "precedence": "cobra_contract", + "selected": true, + "source": "cobra_flag_type", + "value": "string" + } + ], + "precedence": "cobra_contract", + "resolution": "highest_precedence", + "source": "cobra_flag_type", + "value": "string" + } + }, + "required": true, + "type": "string" + } + }, + "path": "chat.batch_query_group_chat_settings", + "primary_cli_path": "chat group user-settings query", + "product_id": "chat", + "reviewed": true, + "risk": "low", + "source": "reviewed_command_registry", + "title": "批量查询当前用户的群会话设置", + "use_when": [ + "用户说 看下这些群我的置顶和免打扰设置" + ] + }, + "chat.batch_update_group_chat_settings": { + "agent_metadata_source": "embedded-skill-metadata", + "agent_source_refs": [ + "CommandRegistry:canonical_path=chat.batch_update_group_chat_settings", + "cobra-help:dws chat group user-settings set", + "internal/cli/schema_hints/metadata/chat.json", + "internal/cli/schema_hints/selection/chat.json", + "wukong-develop:wukong/products" + ], + "agent_summary": "批量更新当前用户自己的群会话设置(置顶/免打扰/群昵称/群备注)", + "agent_summary_source": "dws-agent-selection/chat", + "availability": "available", + "avoid_when": [ + "单个群昵称优先 chat group update-nick" + ], + "canonical_path": "chat.batch_update_group_chat_settings", + "cli_name": "set", + "cli_path": "chat group user-settings set", + "confirmation": "not_required", + "description": "批量更新当前用户的群会话设置", + "display": "群聊 / 消息 / 机器人", + "effect": "write", + "effect_source": "agent-hint", + "examples": [ + "dws chat group user-settings set --items '[{\"openConversationId\":\"cid1\",\"top\":true,\"mute\":false}]' --format json" + ], + "field_provenance": { + "agent_summary": { + "candidates": [ + { + "precedence": "reviewed_explicit", + "review_reason": "人工审阅:对照悟空 develop 同名命令实现、Cobra Long/Example 与 Runtime 确认门禁移植;不改变命令身份、参数契约或接口绑定。", + "selected": true, + "source": "internal/cli/schema_hints/selection/chat.json", + "value": "批量更新当前用户自己的群会话设置(置顶/免打扰/群昵称/群备注)" + } + ], + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "人工审阅:对照悟空 develop 同名命令实现、Cobra Long/Example 与 Runtime 确认门禁移植;不改变命令身份、参数契约或接口绑定。", + "source": "internal/cli/schema_hints/selection/chat.json", + "value": "批量更新当前用户自己的群会话设置(置顶/免打扰/群昵称/群备注)" + }, + "availability": { + "candidates": [ + { + "precedence": "reviewed_explicit", + "review_reason": "dws-tool-metadata/chat marks this tool as reviewed", + "selected": true, + "source": "internal/cli/schema_hints/metadata/chat.json", + "value": "available" + } + ], + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "dws-tool-metadata/chat marks this tool as reviewed", + "source": "internal/cli/schema_hints/metadata/chat.json", + "value": "available" + }, + "avoid_when": { + "candidates": [ + { + "precedence": "reviewed_explicit", + "review_reason": "人工审阅:对照悟空 develop 同名命令实现、Cobra Long/Example 与 Runtime 确认门禁移植;不改变命令身份、参数契约或接口绑定。", + "selected": true, + "source": "internal/cli/schema_hints/selection/chat.json", + "value": [ + "单个群昵称优先 chat group update-nick" + ] + } + ], + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "人工审阅:对照悟空 develop 同名命令实现、Cobra Long/Example 与 Runtime 确认门禁移植;不改变命令身份、参数契约或接口绑定。", + "source": "internal/cli/schema_hints/selection/chat.json", + "value": [ + "单个群昵称优先 chat group update-nick" + ] + }, + "canonical_path": { + "candidates": [ + { + "precedence": "command_registry", + "selected": true, + "source": "reviewed_command_registry", + "source_ref": "chat group user-settings set", + "value": "chat.batch_update_group_chat_settings" + } + ], + "precedence": "command_registry", + "resolution": "registry_identity", + "source": "reviewed_command_registry", + "source_ref": "chat group user-settings set", + "value": "chat.batch_update_group_chat_settings" + }, + "confirmation": { + "candidates": [ + { + "precedence": "reviewed_explicit", + "review_reason": "dws-tool-metadata/chat marks this tool as reviewed", + "selected": true, + "source": "internal/cli/schema_hints/metadata/chat.json", + "value": "not_required" + } + ], + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "dws-tool-metadata/chat marks this tool as reviewed", + "source": "internal/cli/schema_hints/metadata/chat.json", + "value": "not_required" + }, + "description": { + "candidates": [ + { + "precedence": "cobra_help", + "selected": true, + "source": "cobra_help", + "value": "批量更新当前用户的群会话设置" + } + ], + "precedence": "cobra_help", + "resolution": "highest_precedence", + "source": "cobra_help", + "value": "批量更新当前用户的群会话设置" + }, + "effect": { + "candidates": [ + { + "precedence": "reviewed_explicit", + "review_reason": "dws-tool-metadata/chat marks this tool as reviewed", + "selected": true, + "source": "internal/cli/schema_hints/metadata/chat.json", + "value": "write" + } + ], + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "dws-tool-metadata/chat marks this tool as reviewed", + "source": "internal/cli/schema_hints/metadata/chat.json", + "value": "write" + }, + "examples": { + "candidates": [ + { + "precedence": "reviewed_explicit", + "review_reason": "人工审阅:对照悟空 develop 同名命令实现、Cobra Long/Example 与 Runtime 确认门禁移植;不改变命令身份、参数契约或接口绑定。", + "selected": true, + "source": "internal/cli/schema_hints/selection/chat.json", + "value": [ + "dws chat group user-settings set --items '[{\"openConversationId\":\"cid1\",\"top\":true,\"mute\":false}]' --format json" + ] + } + ], + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "人工审阅:对照悟空 develop 同名命令实现、Cobra Long/Example 与 Runtime 确认门禁移植;不改变命令身份、参数契约或接口绑定。", + "source": "internal/cli/schema_hints/selection/chat.json", + "value": [ + "dws chat group user-settings set --items '[{\"openConversationId\":\"cid1\",\"top\":true,\"mute\":false}]' --format json" + ] + }, + "idempotency": { + "candidates": [ + { + "precedence": "reviewed_explicit", + "review_reason": "dws-tool-metadata/chat marks this tool as reviewed", + "selected": true, + "source": "internal/cli/schema_hints/metadata/chat.json", + "value": "idempotent" + } + ], + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "dws-tool-metadata/chat marks this tool as reviewed", + "source": "internal/cli/schema_hints/metadata/chat.json", + "value": "idempotent" + }, + "interface_mode": { + "candidates": [ + { + "precedence": "reviewed_explicit", + "review_reason": "dws-tool-metadata/chat marks this tool as reviewed", + "selected": true, + "source": "internal/cli/schema_hints/metadata/chat.json", + "value": "composite" + } + ], + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "dws-tool-metadata/chat marks this tool as reviewed", + "source": "internal/cli/schema_hints/metadata/chat.json", + "value": "composite" + }, + "interface_reason": { + "candidates": [ + { + "precedence": "reviewed_explicit", + "review_reason": "dws-tool-metadata/chat marks this tool as reviewed", + "selected": true, + "source": "internal/cli/schema_hints/metadata/chat.json", + "value": "Reviewed unpinned remote adapter: this executable CLI wrapper calls a remote helper that is absent from the pinned MCP metadata snapshot; no single pinned semantically equivalent interface_ref can represent the command." + } + ], + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "dws-tool-metadata/chat marks this tool as reviewed", + "source": "internal/cli/schema_hints/metadata/chat.json", + "value": "Reviewed unpinned remote adapter: this executable CLI wrapper calls a remote helper that is absent from the pinned MCP metadata snapshot; no single pinned semantically equivalent interface_ref can represent the command." + }, + "interface_ref": { + "candidates": [ + { + "precedence": "reviewed_explicit", + "review_reason": "final interface mode composite forbids a direct MCP interface_ref", + "selected": true, + "source": "internal/cli/schema_hints/metadata/chat.json", + "value": null + } + ], + "precedence": "reviewed_explicit", + "resolution": "interface_disposition_matrix", + "review_reason": "final interface mode composite forbids a direct MCP interface_ref", + "source": "internal/cli/schema_hints/metadata/chat.json", + "value": null + }, + "reviewed": { + "candidates": [ + { + "precedence": "reviewed_explicit", + "review_reason": "dws-tool-metadata/chat marks this tool as reviewed", + "selected": true, + "source": "internal/cli/schema_hints/metadata/chat.json", + "value": true + }, + { + "precedence": "reviewed_explicit", + "review_reason": "人工审阅:对照悟空 develop 同名命令实现、Cobra Long/Example 与 Runtime 确认门禁移植;不改变命令身份、参数契约或接口绑定。", + "selected": false, + "source": "internal/cli/schema_hints/selection/chat.json", + "value": true + } + ], + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "dws-tool-metadata/chat marks this tool as reviewed", + "source": "internal/cli/schema_hints/metadata/chat.json", + "value": true + }, + "risk": { + "candidates": [ + { + "precedence": "reviewed_explicit", + "review_reason": "dws-tool-metadata/chat marks this tool as reviewed", + "selected": true, + "source": "internal/cli/schema_hints/metadata/chat.json", + "value": "medium" + } + ], + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "dws-tool-metadata/chat marks this tool as reviewed", + "source": "internal/cli/schema_hints/metadata/chat.json", + "value": "medium" + }, + "title": { + "candidates": [ + { + "precedence": "cobra_help", + "selected": true, + "source": "cobra_help", + "value": "批量更新当前用户的群会话设置" + } + ], + "precedence": "cobra_help", + "resolution": "highest_precedence", + "source": "cobra_help", + "value": "批量更新当前用户的群会话设置" + }, + "use_when": { + "candidates": [ + { + "precedence": "reviewed_explicit", + "review_reason": "人工审阅:对照悟空 develop 同名命令实现、Cobra Long/Example 与 Runtime 确认门禁移植;不改变命令身份、参数契约或接口绑定。", + "selected": true, + "source": "internal/cli/schema_hints/selection/chat.json", + "value": [ + "用户说 把这些群都设为免打扰/置顶" + ] + } + ], + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "人工审阅:对照悟空 develop 同名命令实现、Cobra Long/Example 与 Runtime 确认门禁移植;不改变命令身份、参数契约或接口绑定。", + "source": "internal/cli/schema_hints/selection/chat.json", + "value": [ + "用户说 把这些群都设为免打扰/置顶" + ] + } + }, + "group": "group.user-settings", + "has_parameters": true, + "idempotency": "idempotent", + "interface_mode": "composite", + "interface_reason": "Reviewed unpinned remote adapter: this executable CLI wrapper calls a remote helper that is absent from the pinned MCP metadata snapshot; no single pinned semantically equivalent interface_ref can represent the command.", + "is_alias": false, + "name": "batch_update_group_chat_settings", + "parameter_count": 1, + "parameters": { + "items": { + "description": "JSON 数组,每项 {\"openConversationId\":\"cid\",\"top\":bool,\"mute\":bool,\"groupNick\":\"...\",\"groupAlias\":\"...\"} (必填)", + "field_provenance": { + "description": { + "candidates": [ + { + "precedence": "cobra_contract", + "selected": true, + "source": "cobra_usage", + "value": "JSON 数组,每项 {\"openConversationId\":\"cid\",\"top\":bool,\"mute\":bool,\"groupNick\":\"...\",\"groupAlias\":\"...\"} (必填)" + }, + { + "precedence": "default", + "selected": false, + "source": "default", + "value": "" + } + ], + "precedence": "cobra_contract", + "resolution": "highest_precedence", + "source": "cobra_usage", + "value": "JSON 数组,每项 {\"openConversationId\":\"cid\",\"top\":bool,\"mute\":bool,\"groupNick\":\"...\",\"groupAlias\":\"...\"} (必填)" + }, + "property": { + "candidates": [ + { + "precedence": "reviewed_mapping_exclusion", + "review_reason": "Reviewed unpinned adapter: chat.batch_update_group_chat_settings has no singular pinned interface_ref; --items is a CLI wrapper input and does not publish a direct interface property.", + "selected": true, + "source": "reviewed_mapping_exclusion", + "value": "" + }, + { + "precedence": "inference", + "selected": false, + "source": "flag_name_inference", + "value": "items" + } + ], + "precedence": "reviewed_mapping_exclusion", + "resolution": "highest_precedence", + "review_reason": "Reviewed unpinned adapter: chat.batch_update_group_chat_settings has no singular pinned interface_ref; --items is a CLI wrapper input and does not publish a direct interface property.", + "source": "reviewed_mapping_exclusion", + "value": "" + }, + "required": { + "candidates": [ + { + "precedence": "inference", + "selected": true, + "source": "usage_required_inference", + "value": true + }, + { + "precedence": "default", + "selected": false, + "source": "default", + "value": false + } + ], + "precedence": "inference", + "resolution": "highest_precedence", + "source": "usage_required_inference", + "value": true + }, + "required_when": { + "candidates": [ + { + "precedence": "default", + "selected": true, + "source": "default", + "value": "" + } + ], + "precedence": "default", + "resolution": "highest_precedence", + "source": "default", + "value": "" + }, + "type": { + "candidates": [ + { + "precedence": "cobra_contract", + "selected": true, + "source": "cobra_flag_type", + "value": "string" + } + ], + "precedence": "cobra_contract", + "resolution": "highest_precedence", + "source": "cobra_flag_type", + "value": "string" + } + }, + "required": true, + "type": "string" + } + }, + "path": "chat.batch_update_group_chat_settings", + "primary_cli_path": "chat group user-settings set", + "product_id": "chat", + "reviewed": true, + "risk": "medium", + "source": "reviewed_command_registry", + "title": "批量更新当前用户的群会话设置", + "use_when": [ + "用户说 把这些群都设为免打扰/置顶" + ] + }, "chat.combine_forward_messages": { "agent_metadata_source": "embedded-skill-metadata", "agent_source_refs": [ @@ -15037,6 +15871,423 @@ "需要生成群邀请链接或设置有效期时" ] }, + "chat.get_group_mute_config": { + "agent_metadata_source": "embedded-skill-metadata", + "agent_source_refs": [ + "CommandRegistry:canonical_path=chat.get_group_mute_config", + "cobra-help:dws chat group get-mute-config", + "internal/cli/schema_hints/metadata/chat.json", + "internal/cli/schema_hints/selection/chat.json", + "wukong-develop:wukong/products" + ], + "agent_summary": "查询群用户禁言配置(禁言黑名单/全员禁言白名单)", + "agent_summary_source": "dws-agent-selection/chat", + "availability": "available", + "avoid_when": [ + "设置全员禁言用 chat group-mute;禁言个人用 chat group-mute-member" + ], + "canonical_path": "chat.get_group_mute_config", + "cli_name": "get-mute-config", + "cli_path": "chat group get-mute-config", + "confirmation": "not_required", + "description": "查询指定群的用户禁言配置,包括单独禁言黑名单、全员禁言白名单及相关操作时间。\n返回的是原始配置记录,不等同于当前被禁言成员列表;全员禁言开关也不在本命令的返回范围内。", + "display": "群聊 / 消息 / 机器人", + "effect": "read", + "effect_source": "agent-hint", + "examples": [ + "dws chat group get-mute-config --group \u003copenConversationId\u003e --format json" + ], + "field_provenance": { + "agent_summary": { + "candidates": [ + { + "precedence": "reviewed_explicit", + "review_reason": "人工审阅:对照悟空 develop 同名命令实现、Cobra Long/Example 与 Runtime 确认门禁移植;不改变命令身份、参数契约或接口绑定。", + "selected": true, + "source": "internal/cli/schema_hints/selection/chat.json", + "value": "查询群用户禁言配置(禁言黑名单/全员禁言白名单)" + } + ], + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "人工审阅:对照悟空 develop 同名命令实现、Cobra Long/Example 与 Runtime 确认门禁移植;不改变命令身份、参数契约或接口绑定。", + "source": "internal/cli/schema_hints/selection/chat.json", + "value": "查询群用户禁言配置(禁言黑名单/全员禁言白名单)" + }, + "availability": { + "candidates": [ + { + "precedence": "reviewed_explicit", + "review_reason": "dws-tool-metadata/chat marks this tool as reviewed", + "selected": true, + "source": "internal/cli/schema_hints/metadata/chat.json", + "value": "available" + } + ], + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "dws-tool-metadata/chat marks this tool as reviewed", + "source": "internal/cli/schema_hints/metadata/chat.json", + "value": "available" + }, + "avoid_when": { + "candidates": [ + { + "precedence": "reviewed_explicit", + "review_reason": "人工审阅:对照悟空 develop 同名命令实现、Cobra Long/Example 与 Runtime 确认门禁移植;不改变命令身份、参数契约或接口绑定。", + "selected": true, + "source": "internal/cli/schema_hints/selection/chat.json", + "value": [ + "设置全员禁言用 chat group-mute;禁言个人用 chat group-mute-member" + ] + } + ], + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "人工审阅:对照悟空 develop 同名命令实现、Cobra Long/Example 与 Runtime 确认门禁移植;不改变命令身份、参数契约或接口绑定。", + "source": "internal/cli/schema_hints/selection/chat.json", + "value": [ + "设置全员禁言用 chat group-mute;禁言个人用 chat group-mute-member" + ] + }, + "canonical_path": { + "candidates": [ + { + "precedence": "command_registry", + "selected": true, + "source": "reviewed_command_registry", + "source_ref": "chat group get-mute-config", + "value": "chat.get_group_mute_config" + } + ], + "precedence": "command_registry", + "resolution": "registry_identity", + "source": "reviewed_command_registry", + "source_ref": "chat group get-mute-config", + "value": "chat.get_group_mute_config" + }, + "confirmation": { + "candidates": [ + { + "precedence": "reviewed_explicit", + "review_reason": "dws-tool-metadata/chat marks this tool as reviewed", + "selected": true, + "source": "internal/cli/schema_hints/metadata/chat.json", + "value": "not_required" + } + ], + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "dws-tool-metadata/chat marks this tool as reviewed", + "source": "internal/cli/schema_hints/metadata/chat.json", + "value": "not_required" + }, + "description": { + "candidates": [ + { + "precedence": "cobra_help", + "selected": true, + "source": "cobra_help", + "value": "查询指定群的用户禁言配置,包括单独禁言黑名单、全员禁言白名单及相关操作时间。\n返回的是原始配置记录,不等同于当前被禁言成员列表;全员禁言开关也不在本命令的返回范围内。" + } + ], + "precedence": "cobra_help", + "resolution": "highest_precedence", + "source": "cobra_help", + "value": "查询指定群的用户禁言配置,包括单独禁言黑名单、全员禁言白名单及相关操作时间。\n返回的是原始配置记录,不等同于当前被禁言成员列表;全员禁言开关也不在本命令的返回范围内。" + }, + "effect": { + "candidates": [ + { + "precedence": "reviewed_explicit", + "review_reason": "dws-tool-metadata/chat marks this tool as reviewed", + "selected": true, + "source": "internal/cli/schema_hints/metadata/chat.json", + "value": "read" + } + ], + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "dws-tool-metadata/chat marks this tool as reviewed", + "source": "internal/cli/schema_hints/metadata/chat.json", + "value": "read" + }, + "examples": { + "candidates": [ + { + "precedence": "reviewed_explicit", + "review_reason": "人工审阅:对照悟空 develop 同名命令实现、Cobra Long/Example 与 Runtime 确认门禁移植;不改变命令身份、参数契约或接口绑定。", + "selected": true, + "source": "internal/cli/schema_hints/selection/chat.json", + "value": [ + "dws chat group get-mute-config --group \u003copenConversationId\u003e --format json" + ] + } + ], + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "人工审阅:对照悟空 develop 同名命令实现、Cobra Long/Example 与 Runtime 确认门禁移植;不改变命令身份、参数契约或接口绑定。", + "source": "internal/cli/schema_hints/selection/chat.json", + "value": [ + "dws chat group get-mute-config --group \u003copenConversationId\u003e --format json" + ] + }, + "idempotency": { + "candidates": [ + { + "precedence": "reviewed_explicit", + "review_reason": "dws-tool-metadata/chat marks this tool as reviewed", + "selected": true, + "source": "internal/cli/schema_hints/metadata/chat.json", + "value": "idempotent" + } + ], + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "dws-tool-metadata/chat marks this tool as reviewed", + "source": "internal/cli/schema_hints/metadata/chat.json", + "value": "idempotent" + }, + "interface_mode": { + "candidates": [ + { + "precedence": "reviewed_explicit", + "review_reason": "dws-tool-metadata/chat marks this tool as reviewed", + "selected": true, + "source": "internal/cli/schema_hints/metadata/chat.json", + "value": "composite" + } + ], + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "dws-tool-metadata/chat marks this tool as reviewed", + "source": "internal/cli/schema_hints/metadata/chat.json", + "value": "composite" + }, + "interface_reason": { + "candidates": [ + { + "precedence": "reviewed_explicit", + "review_reason": "dws-tool-metadata/chat marks this tool as reviewed", + "selected": true, + "source": "internal/cli/schema_hints/metadata/chat.json", + "value": "Reviewed unpinned remote adapter: this executable CLI wrapper calls a remote helper that is absent from the pinned MCP metadata snapshot; no single pinned semantically equivalent interface_ref can represent the command." + } + ], + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "dws-tool-metadata/chat marks this tool as reviewed", + "source": "internal/cli/schema_hints/metadata/chat.json", + "value": "Reviewed unpinned remote adapter: this executable CLI wrapper calls a remote helper that is absent from the pinned MCP metadata snapshot; no single pinned semantically equivalent interface_ref can represent the command." + }, + "interface_ref": { + "candidates": [ + { + "precedence": "reviewed_explicit", + "review_reason": "final interface mode composite forbids a direct MCP interface_ref", + "selected": true, + "source": "internal/cli/schema_hints/metadata/chat.json", + "value": null + } + ], + "precedence": "reviewed_explicit", + "resolution": "interface_disposition_matrix", + "review_reason": "final interface mode composite forbids a direct MCP interface_ref", + "source": "internal/cli/schema_hints/metadata/chat.json", + "value": null + }, + "reviewed": { + "candidates": [ + { + "precedence": "reviewed_explicit", + "review_reason": "dws-tool-metadata/chat marks this tool as reviewed", + "selected": true, + "source": "internal/cli/schema_hints/metadata/chat.json", + "value": true + }, + { + "precedence": "reviewed_explicit", + "review_reason": "人工审阅:对照悟空 develop 同名命令实现、Cobra Long/Example 与 Runtime 确认门禁移植;不改变命令身份、参数契约或接口绑定。", + "selected": false, + "source": "internal/cli/schema_hints/selection/chat.json", + "value": true + } + ], + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "dws-tool-metadata/chat marks this tool as reviewed", + "source": "internal/cli/schema_hints/metadata/chat.json", + "value": true + }, + "risk": { + "candidates": [ + { + "precedence": "reviewed_explicit", + "review_reason": "dws-tool-metadata/chat marks this tool as reviewed", + "selected": true, + "source": "internal/cli/schema_hints/metadata/chat.json", + "value": "low" + } + ], + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "dws-tool-metadata/chat marks this tool as reviewed", + "source": "internal/cli/schema_hints/metadata/chat.json", + "value": "low" + }, + "title": { + "candidates": [ + { + "precedence": "cobra_help", + "selected": true, + "source": "cobra_help", + "value": "查询群用户禁言配置" + } + ], + "precedence": "cobra_help", + "resolution": "highest_precedence", + "source": "cobra_help", + "value": "查询群用户禁言配置" + }, + "use_when": { + "candidates": [ + { + "precedence": "reviewed_explicit", + "review_reason": "人工审阅:对照悟空 develop 同名命令实现、Cobra Long/Example 与 Runtime 确认门禁移植;不改变命令身份、参数契约或接口绑定。", + "selected": true, + "source": "internal/cli/schema_hints/selection/chat.json", + "value": [ + "用户说 看下群里谁被禁言/禁言配置" + ] + } + ], + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "人工审阅:对照悟空 develop 同名命令实现、Cobra Long/Example 与 Runtime 确认门禁移植;不改变命令身份、参数契约或接口绑定。", + "source": "internal/cli/schema_hints/selection/chat.json", + "value": [ + "用户说 看下群里谁被禁言/禁言配置" + ] + } + }, + "group": "group", + "has_parameters": true, + "idempotency": "idempotent", + "interface_mode": "composite", + "interface_reason": "Reviewed unpinned remote adapter: this executable CLI wrapper calls a remote helper that is absent from the pinned MCP metadata snapshot; no single pinned semantically equivalent interface_ref can represent the command.", + "is_alias": false, + "name": "get_group_mute_config", + "parameter_count": 1, + "parameters": { + "group": { + "description": "群聊 openConversationId (必填)", + "field_provenance": { + "description": { + "candidates": [ + { + "precedence": "cobra_contract", + "selected": true, + "source": "cobra_usage", + "value": "群聊 openConversationId (必填)" + }, + { + "precedence": "default", + "selected": false, + "source": "default", + "value": "" + } + ], + "precedence": "cobra_contract", + "resolution": "highest_precedence", + "source": "cobra_usage", + "value": "群聊 openConversationId (必填)" + }, + "property": { + "candidates": [ + { + "precedence": "reviewed_mapping_exclusion", + "review_reason": "Reviewed unpinned adapter: chat.get_group_mute_config has no singular pinned interface_ref; --group is a CLI wrapper input and does not publish a direct interface property.", + "selected": true, + "source": "reviewed_mapping_exclusion", + "value": "" + }, + { + "precedence": "inference", + "selected": false, + "source": "flag_name_inference", + "value": "group" + } + ], + "precedence": "reviewed_mapping_exclusion", + "resolution": "highest_precedence", + "review_reason": "Reviewed unpinned adapter: chat.get_group_mute_config has no singular pinned interface_ref; --group is a CLI wrapper input and does not publish a direct interface property.", + "source": "reviewed_mapping_exclusion", + "value": "" + }, + "required": { + "candidates": [ + { + "precedence": "inference", + "selected": true, + "source": "usage_required_inference", + "value": true + }, + { + "precedence": "default", + "selected": false, + "source": "default", + "value": false + } + ], + "precedence": "inference", + "resolution": "highest_precedence", + "source": "usage_required_inference", + "value": true + }, + "required_when": { + "candidates": [ + { + "precedence": "default", + "selected": true, + "source": "default", + "value": "" + } + ], + "precedence": "default", + "resolution": "highest_precedence", + "source": "default", + "value": "" + }, + "type": { + "candidates": [ + { + "precedence": "cobra_contract", + "selected": true, + "source": "cobra_flag_type", + "value": "string" + } + ], + "precedence": "cobra_contract", + "resolution": "highest_precedence", + "source": "cobra_flag_type", + "value": "string" + } + }, + "required": true, + "type": "string" + } + }, + "path": "chat.get_group_mute_config", + "primary_cli_path": "chat group get-mute-config", + "product_id": "chat", + "reviewed": true, + "risk": "low", + "source": "reviewed_command_registry", + "title": "查询群用户禁言配置", + "use_when": [ + "用户说 看下群里谁被禁言/禁言配置" + ] + }, "chat.list_conv_categories_by_conv": { "agent_metadata_source": "embedded-skill-metadata", "agent_source_refs": [ @@ -44906,7 +46157,7 @@ "is_alias": false, "metadata_source": "embedded-mcp-metadata", "name": "send_personal_message", - "parameter_count": 12, + "parameter_count": 17, "parameters": { "ai-tag": { "default": "true", @@ -45242,6 +46493,97 @@ "required": false, "type": "string" }, + "contact-id": { + "description": "联系人名片 openDingTalkId(msgType=profile 时必填)", + "field_provenance": { + "description": { + "candidates": [ + { + "precedence": "cobra_contract", + "selected": true, + "source": "cobra_usage", + "value": "联系人名片 openDingTalkId(msgType=profile 时必填)" + }, + { + "precedence": "default", + "selected": false, + "source": "default", + "value": "" + } + ], + "precedence": "cobra_contract", + "resolution": "highest_precedence", + "source": "cobra_usage", + "value": "联系人名片 openDingTalkId(msgType=profile 时必填)" + }, + "property": { + "candidates": [ + { + "precedence": "reviewed_mapping_exclusion", + "review_reason": "serialized into the aggregate content payload", + "selected": true, + "source": "reviewed_mapping_exclusion", + "value": "" + }, + { + "precedence": "inference", + "selected": false, + "source": "flag_name_inference", + "value": "contactId" + } + ], + "precedence": "reviewed_mapping_exclusion", + "resolution": "highest_precedence", + "review_reason": "serialized into the aggregate content payload", + "source": "reviewed_mapping_exclusion", + "value": "" + }, + "required": { + "candidates": [ + { + "precedence": "default", + "selected": true, + "source": "default", + "value": false + } + ], + "precedence": "default", + "resolution": "fallback", + "source": "default", + "value": false + }, + "required_when": { + "candidates": [ + { + "precedence": "default", + "selected": true, + "source": "default", + "value": "" + } + ], + "precedence": "default", + "resolution": "highest_precedence", + "source": "default", + "value": "" + }, + "type": { + "candidates": [ + { + "precedence": "cobra_contract", + "selected": true, + "source": "cobra_flag_type", + "value": "string" + } + ], + "precedence": "cobra_contract", + "resolution": "highest_precedence", + "source": "cobra_flag_type", + "value": "string" + } + }, + "required": false, + "type": "string" + }, "file-path": { "description": "本地文件路径(msgType=file/audio/video 时直接上传并按 file 消息发送)", "field_provenance": { @@ -45464,6 +46806,370 @@ "required": false, "type": "string" }, + "latitude": { + "description": "位置消息纬度(msgType=location 时必填)", + "field_provenance": { + "description": { + "candidates": [ + { + "precedence": "cobra_contract", + "selected": true, + "source": "cobra_usage", + "value": "位置消息纬度(msgType=location 时必填)" + }, + { + "precedence": "default", + "selected": false, + "source": "default", + "value": "" + } + ], + "precedence": "cobra_contract", + "resolution": "highest_precedence", + "source": "cobra_usage", + "value": "位置消息纬度(msgType=location 时必填)" + }, + "property": { + "candidates": [ + { + "precedence": "reviewed_mapping_exclusion", + "review_reason": "serialized into the aggregate content payload", + "selected": true, + "source": "reviewed_mapping_exclusion", + "value": "" + }, + { + "precedence": "inference", + "selected": false, + "source": "flag_name_inference", + "value": "latitude" + } + ], + "precedence": "reviewed_mapping_exclusion", + "resolution": "highest_precedence", + "review_reason": "serialized into the aggregate content payload", + "source": "reviewed_mapping_exclusion", + "value": "" + }, + "required": { + "candidates": [ + { + "precedence": "default", + "selected": true, + "source": "default", + "value": false + } + ], + "precedence": "default", + "resolution": "fallback", + "source": "default", + "value": false + }, + "required_when": { + "candidates": [ + { + "precedence": "default", + "selected": true, + "source": "default", + "value": "" + } + ], + "precedence": "default", + "resolution": "highest_precedence", + "source": "default", + "value": "" + }, + "type": { + "candidates": [ + { + "precedence": "cobra_contract", + "selected": true, + "source": "cobra_flag_type", + "value": "string" + } + ], + "precedence": "cobra_contract", + "resolution": "highest_precedence", + "source": "cobra_flag_type", + "value": "string" + } + }, + "required": false, + "type": "string" + }, + "location-name": { + "description": "位置消息地址名称(msgType=location 时必填)", + "field_provenance": { + "description": { + "candidates": [ + { + "precedence": "cobra_contract", + "selected": true, + "source": "cobra_usage", + "value": "位置消息地址名称(msgType=location 时必填)" + }, + { + "precedence": "default", + "selected": false, + "source": "default", + "value": "" + } + ], + "precedence": "cobra_contract", + "resolution": "highest_precedence", + "source": "cobra_usage", + "value": "位置消息地址名称(msgType=location 时必填)" + }, + "property": { + "candidates": [ + { + "precedence": "reviewed_mapping_exclusion", + "review_reason": "serialized into the aggregate content payload", + "selected": true, + "source": "reviewed_mapping_exclusion", + "value": "" + }, + { + "precedence": "inference", + "selected": false, + "source": "flag_name_inference", + "value": "locationName" + } + ], + "precedence": "reviewed_mapping_exclusion", + "resolution": "highest_precedence", + "review_reason": "serialized into the aggregate content payload", + "source": "reviewed_mapping_exclusion", + "value": "" + }, + "required": { + "candidates": [ + { + "precedence": "default", + "selected": true, + "source": "default", + "value": false + } + ], + "precedence": "default", + "resolution": "fallback", + "source": "default", + "value": false + }, + "required_when": { + "candidates": [ + { + "precedence": "default", + "selected": true, + "source": "default", + "value": "" + } + ], + "precedence": "default", + "resolution": "highest_precedence", + "source": "default", + "value": "" + }, + "type": { + "candidates": [ + { + "precedence": "cobra_contract", + "selected": true, + "source": "cobra_flag_type", + "value": "string" + } + ], + "precedence": "cobra_contract", + "resolution": "highest_precedence", + "source": "cobra_flag_type", + "value": "string" + } + }, + "required": false, + "type": "string" + }, + "longitude": { + "description": "位置消息经度(msgType=location 时必填)", + "field_provenance": { + "description": { + "candidates": [ + { + "precedence": "cobra_contract", + "selected": true, + "source": "cobra_usage", + "value": "位置消息经度(msgType=location 时必填)" + }, + { + "precedence": "default", + "selected": false, + "source": "default", + "value": "" + } + ], + "precedence": "cobra_contract", + "resolution": "highest_precedence", + "source": "cobra_usage", + "value": "位置消息经度(msgType=location 时必填)" + }, + "property": { + "candidates": [ + { + "precedence": "reviewed_mapping_exclusion", + "review_reason": "serialized into the aggregate content payload", + "selected": true, + "source": "reviewed_mapping_exclusion", + "value": "" + }, + { + "precedence": "inference", + "selected": false, + "source": "flag_name_inference", + "value": "longitude" + } + ], + "precedence": "reviewed_mapping_exclusion", + "resolution": "highest_precedence", + "review_reason": "serialized into the aggregate content payload", + "source": "reviewed_mapping_exclusion", + "value": "" + }, + "required": { + "candidates": [ + { + "precedence": "default", + "selected": true, + "source": "default", + "value": false + } + ], + "precedence": "default", + "resolution": "fallback", + "source": "default", + "value": false + }, + "required_when": { + "candidates": [ + { + "precedence": "default", + "selected": true, + "source": "default", + "value": "" + } + ], + "precedence": "default", + "resolution": "highest_precedence", + "source": "default", + "value": "" + }, + "type": { + "candidates": [ + { + "precedence": "cobra_contract", + "selected": true, + "source": "cobra_flag_type", + "value": "string" + } + ], + "precedence": "cobra_contract", + "resolution": "highest_precedence", + "source": "cobra_flag_type", + "value": "string" + } + }, + "required": false, + "type": "string" + }, + "map-thumbnail-url": { + "description": "位置消息地图缩略图 mediaId,形如 @mediaId(msgType=location 时必填)", + "field_provenance": { + "description": { + "candidates": [ + { + "precedence": "cobra_contract", + "selected": true, + "source": "cobra_usage", + "value": "位置消息地图缩略图 mediaId,形如 @mediaId(msgType=location 时必填)" + }, + { + "precedence": "default", + "selected": false, + "source": "default", + "value": "" + } + ], + "precedence": "cobra_contract", + "resolution": "highest_precedence", + "source": "cobra_usage", + "value": "位置消息地图缩略图 mediaId,形如 @mediaId(msgType=location 时必填)" + }, + "property": { + "candidates": [ + { + "precedence": "reviewed_mapping_exclusion", + "review_reason": "serialized into the aggregate content payload", + "selected": true, + "source": "reviewed_mapping_exclusion", + "value": "" + }, + { + "precedence": "inference", + "selected": false, + "source": "flag_name_inference", + "value": "mapThumbnailUrl" + } + ], + "precedence": "reviewed_mapping_exclusion", + "resolution": "highest_precedence", + "review_reason": "serialized into the aggregate content payload", + "source": "reviewed_mapping_exclusion", + "value": "" + }, + "required": { + "candidates": [ + { + "precedence": "default", + "selected": true, + "source": "default", + "value": false + } + ], + "precedence": "default", + "resolution": "fallback", + "source": "default", + "value": false + }, + "required_when": { + "candidates": [ + { + "precedence": "default", + "selected": true, + "source": "default", + "value": "" + } + ], + "precedence": "default", + "resolution": "highest_precedence", + "source": "default", + "value": "" + }, + "type": { + "candidates": [ + { + "precedence": "cobra_contract", + "selected": true, + "source": "cobra_flag_type", + "value": "string" + } + ], + "precedence": "cobra_contract", + "resolution": "highest_precedence", + "source": "cobra_flag_type", + "value": "string" + } + }, + "required": false, + "type": "string" + }, "media-id": { "description": "上游已提供的图片 mediaId(仅旧版 msgType=image;CLI 不提供本地上传到 mediaId)", "example": "@lADP_schema_smoke", @@ -45584,7 +47290,7 @@ "type": "string" }, "msg-type": { - "description": "富媒体消息类型: image/file/audio/video(本地图片/文件推荐 file --file-path;image 仅接受已有 mediaId)", + "description": "富媒体消息类型: image/file/audio/video/location/profile(本地图片/文件推荐 file --file-path;image 仅接受已有 mediaId)", "enum": [ "image", "file", @@ -45598,7 +47304,7 @@ "precedence": "cobra_contract", "selected": true, "source": "cobra_usage", - "value": "富媒体消息类型: image/file/audio/video(本地图片/文件推荐 file --file-path;image 仅接受已有 mediaId)" + "value": "富媒体消息类型: image/file/audio/video/location/profile(本地图片/文件推荐 file --file-path;image 仅接受已有 mediaId)" }, { "precedence": "mcp_metadata", @@ -45616,7 +47322,7 @@ "precedence": "cobra_contract", "resolution": "highest_precedence", "source": "cobra_usage", - "value": "富媒体消息类型: image/file/audio/video(本地图片/文件推荐 file --file-path;image 仅接受已有 mediaId)" + "value": "富媒体消息类型: image/file/audio/video/location/profile(本地图片/文件推荐 file --file-path;image 仅接受已有 mediaId)" }, "enum": { "candidates": [ @@ -90728,6 +92434,1624 @@ "已有 bizId 并需要追加内容或结束流式输出时" ] }, + "chat.update_text_emotion": { + "agent_metadata_source": "embedded-skill-metadata", + "agent_source_refs": [ + "cobra-help:dws chat message update-text-emotion", + "dws-wukong:30f13f02", + "dws-wukong:bcf56ccf", + "dws-wukong:d6831318", + "github:hugozhu/dingtalk-opencode-tag#85", + "internal/cli/schema_command_registry/products/chat.json#chat.update_text_emotion", + "internal/cli/schema_hints/metadata/chat.json", + "internal/cli/schema_hints/selection/chat.json", + "live-dws-schema:im.update_text_emotion#FAILED_NOT_AUTHENTICATED", + "skills/mono/references/products/chat.md" + ], + "agent_summary": "把消息上已有的文字表情原地替换为新的文字表情", + "agent_summary_source": "dws-agent-selection/chat", + "availability": "available", + "avoid_when": [ + "消息上还没有文字表情时使用 chat message add-text-emotion", + "只需清除文字表情时使用 chat message remove-text-emotion" + ], + "canonical_path": "chat.update_text_emotion", + "cli_name": "update-text-emotion", + "cli_path": "chat message update-text-emotion", + "confirmation": "not_required", + "constraints": { + "require_one_of": [ + [ + "conversation-id", + "group", + "id", + "chat" + ] + ] + }, + "description": "更新消息的文字表情回应", + "display": "群聊 / 消息 / 机器人", + "effect": "write", + "effect_source": "agent-hint", + "examples": [ + "dws chat message update-text-emotion --conversation-id \u003copenConversationId\u003e --msg-id \u003copenMessageId\u003e --old-emotion-id \u003coldEmotionId\u003e --emotion-id \u003cemotionId\u003e --emotion-name \"处理中\" --text \"处理中 2 分钟\" --background-id im_bg_5" + ], + "field_provenance": { + "agent_summary": { + "candidates": [ + { + "precedence": "reviewed_explicit", + "review_reason": "依据 issue #85 的原地状态更新场景、Wukong 30f13f02 的真实 Cobra/MCP 映射和开源实现复核;选择语义不改变执行、安全或参数事实。", + "selected": true, + "source": "internal/cli/schema_hints/selection/chat.json", + "value": "把消息上已有的文字表情原地替换为新的文字表情" + } + ], + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "依据 issue #85 的原地状态更新场景、Wukong 30f13f02 的真实 Cobra/MCP 映射和开源实现复核;选择语义不改变执行、安全或参数事实。", + "source": "internal/cli/schema_hints/selection/chat.json", + "value": "把消息上已有的文字表情原地替换为新的文字表情" + }, + "availability": { + "candidates": [ + { + "precedence": "reviewed_explicit", + "review_reason": "Ported from dws-wukong commits d6831318 and bcf56ccf, merged at 30f13f02. The four conversation locators alias one property; the other six business parameters are required by Cobra before the single im/update_text_emotion call.", + "selected": true, + "source": "internal/cli/schema_hints/metadata/chat.json", + "value": "available" + } + ], + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "Ported from dws-wukong commits d6831318 and bcf56ccf, merged at 30f13f02. The four conversation locators alias one property; the other six business parameters are required by Cobra before the single im/update_text_emotion call.", + "source": "internal/cli/schema_hints/metadata/chat.json", + "value": "available" + }, + "avoid_when": { + "candidates": [ + { + "precedence": "reviewed_explicit", + "review_reason": "依据 issue #85 的原地状态更新场景、Wukong 30f13f02 的真实 Cobra/MCP 映射和开源实现复核;选择语义不改变执行、安全或参数事实。", + "selected": true, + "source": "internal/cli/schema_hints/selection/chat.json", + "value": [ + "消息上还没有文字表情时使用 chat message add-text-emotion", + "只需清除文字表情时使用 chat message remove-text-emotion" + ] + } + ], + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "依据 issue #85 的原地状态更新场景、Wukong 30f13f02 的真实 Cobra/MCP 映射和开源实现复核;选择语义不改变执行、安全或参数事实。", + "source": "internal/cli/schema_hints/selection/chat.json", + "value": [ + "消息上还没有文字表情时使用 chat message add-text-emotion", + "只需清除文字表情时使用 chat message remove-text-emotion" + ] + }, + "canonical_path": { + "candidates": [ + { + "precedence": "command_registry", + "selected": true, + "source": "reviewed_command_registry", + "source_ref": "chat message update-text-emotion", + "value": "chat.update_text_emotion" + }, + { + "precedence": "reviewed_manual", + "review_reason": "Ported from dws-wukong commits d6831318 and bcf56ccf, merged at 30f13f02. The four conversation locators alias one property; the other six business parameters are required by Cobra before the single im/update_text_emotion call.", + "selected": false, + "source": "reviewed_manual_hint", + "source_ref": "chat message update-text-emotion", + "value": "chat.update_text_emotion" + } + ], + "precedence": "command_registry", + "resolution": "registry_identity", + "source": "reviewed_command_registry", + "source_ref": "chat message update-text-emotion", + "value": "chat.update_text_emotion" + }, + "confirmation": { + "candidates": [ + { + "precedence": "reviewed_explicit", + "review_reason": "Ported from dws-wukong commits d6831318 and bcf56ccf, merged at 30f13f02. The four conversation locators alias one property; the other six business parameters are required by Cobra before the single im/update_text_emotion call.", + "selected": true, + "source": "internal/cli/schema_hints/metadata/chat.json", + "value": "not_required" + }, + { + "precedence": "inference_or_default", + "selected": false, + "source": "risk-default", + "value": "not_required" + } + ], + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "Ported from dws-wukong commits d6831318 and bcf56ccf, merged at 30f13f02. The four conversation locators alias one property; the other six business parameters are required by Cobra before the single im/update_text_emotion call.", + "source": "internal/cli/schema_hints/metadata/chat.json", + "value": "not_required" + }, + "description": { + "candidates": [ + { + "precedence": "cobra_help", + "selected": true, + "source": "cobra_help", + "value": "更新消息的文字表情回应" + } + ], + "precedence": "cobra_help", + "resolution": "highest_precedence", + "source": "cobra_help", + "value": "更新消息的文字表情回应" + }, + "effect": { + "candidates": [ + { + "precedence": "reviewed_explicit", + "review_reason": "Ported from dws-wukong commits d6831318 and bcf56ccf, merged at 30f13f02. The four conversation locators alias one property; the other six business parameters are required by Cobra before the single im/update_text_emotion call.", + "selected": true, + "source": "internal/cli/schema_hints/metadata/chat.json", + "value": "write" + }, + { + "precedence": "inference_or_default", + "selected": false, + "source": "command-verb", + "value": "write" + } + ], + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "Ported from dws-wukong commits d6831318 and bcf56ccf, merged at 30f13f02. The four conversation locators alias one property; the other six business parameters are required by Cobra before the single im/update_text_emotion call.", + "source": "internal/cli/schema_hints/metadata/chat.json", + "value": "write" + }, + "examples": { + "candidates": [ + { + "precedence": "reviewed_explicit", + "review_reason": "依据 issue #85 的原地状态更新场景、Wukong 30f13f02 的真实 Cobra/MCP 映射和开源实现复核;选择语义不改变执行、安全或参数事实。", + "selected": true, + "source": "internal/cli/schema_hints/selection/chat.json", + "value": [ + "dws chat message update-text-emotion --conversation-id \u003copenConversationId\u003e --msg-id \u003copenMessageId\u003e --old-emotion-id \u003coldEmotionId\u003e --emotion-id \u003cemotionId\u003e --emotion-name \"处理中\" --text \"处理中 2 分钟\" --background-id im_bg_5" + ] + } + ], + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "依据 issue #85 的原地状态更新场景、Wukong 30f13f02 的真实 Cobra/MCP 映射和开源实现复核;选择语义不改变执行、安全或参数事实。", + "source": "internal/cli/schema_hints/selection/chat.json", + "value": [ + "dws chat message update-text-emotion --conversation-id \u003copenConversationId\u003e --msg-id \u003copenMessageId\u003e --old-emotion-id \u003coldEmotionId\u003e --emotion-id \u003cemotionId\u003e --emotion-name \"处理中\" --text \"处理中 2 分钟\" --background-id im_bg_5" + ] + }, + "idempotency": { + "candidates": [ + { + "precedence": "reviewed_explicit", + "review_reason": "Ported from dws-wukong commits d6831318 and bcf56ccf, merged at 30f13f02. The four conversation locators alias one property; the other six business parameters are required by Cobra before the single im/update_text_emotion call.", + "selected": true, + "source": "internal/cli/schema_hints/metadata/chat.json", + "value": "idempotent" + }, + { + "precedence": "inference_or_default", + "selected": false, + "source": "effect-default", + "value": "unknown" + } + ], + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "Ported from dws-wukong commits d6831318 and bcf56ccf, merged at 30f13f02. The four conversation locators alias one property; the other six business parameters are required by Cobra before the single im/update_text_emotion call.", + "source": "internal/cli/schema_hints/metadata/chat.json", + "value": "idempotent" + }, + "interface_mode": { + "candidates": [ + { + "precedence": "reviewed_explicit", + "review_reason": "Ported from dws-wukong commits d6831318 and bcf56ccf, merged at 30f13f02. The four conversation locators alias one property; the other six business parameters are required by Cobra before the single im/update_text_emotion call.", + "selected": true, + "source": "internal/cli/schema_hints/metadata/chat.json", + "value": "composite" + } + ], + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "Ported from dws-wukong commits d6831318 and bcf56ccf, merged at 30f13f02. The four conversation locators alias one property; the other six business parameters are required by Cobra before the single im/update_text_emotion call.", + "source": "internal/cli/schema_hints/metadata/chat.json", + "value": "composite" + }, + "interface_reason": { + "candidates": [ + { + "precedence": "reviewed_explicit", + "review_reason": "Ported from dws-wukong commits d6831318 and bcf56ccf, merged at 30f13f02. The four conversation locators alias one property; the other six business parameters are required by Cobra before the single im/update_text_emotion call.", + "selected": true, + "source": "internal/cli/schema_hints/metadata/chat.json", + "value": "Reviewed unpinned remote adapter: the executable CLI calls im/update_text_emotion, which is proven by dws-wukong develop@30f13f02 but absent from the pinned MCP metadata snapshot; no pinned interface_ref can represent the command yet." + } + ], + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "Ported from dws-wukong commits d6831318 and bcf56ccf, merged at 30f13f02. The four conversation locators alias one property; the other six business parameters are required by Cobra before the single im/update_text_emotion call.", + "source": "internal/cli/schema_hints/metadata/chat.json", + "value": "Reviewed unpinned remote adapter: the executable CLI calls im/update_text_emotion, which is proven by dws-wukong develop@30f13f02 but absent from the pinned MCP metadata snapshot; no pinned interface_ref can represent the command yet." + }, + "interface_ref": { + "candidates": [ + { + "precedence": "reviewed_explicit", + "review_reason": "final interface mode composite forbids a direct MCP interface_ref", + "selected": true, + "source": "internal/cli/schema_hints/metadata/chat.json", + "value": null + } + ], + "precedence": "reviewed_explicit", + "resolution": "interface_disposition_matrix", + "review_reason": "final interface mode composite forbids a direct MCP interface_ref", + "source": "internal/cli/schema_hints/metadata/chat.json", + "value": null + }, + "reviewed": { + "candidates": [ + { + "precedence": "reviewed_explicit", + "review_reason": "Ported from dws-wukong commits d6831318 and bcf56ccf, merged at 30f13f02. The four conversation locators alias one property; the other six business parameters are required by Cobra before the single im/update_text_emotion call.", + "selected": true, + "source": "internal/cli/schema_hints/metadata/chat.json", + "value": true + }, + { + "precedence": "reviewed_explicit", + "review_reason": "依据 issue #85 的原地状态更新场景、Wukong 30f13f02 的真实 Cobra/MCP 映射和开源实现复核;选择语义不改变执行、安全或参数事实。", + "selected": false, + "source": "internal/cli/schema_hints/selection/chat.json", + "value": true + } + ], + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "Ported from dws-wukong commits d6831318 and bcf56ccf, merged at 30f13f02. The four conversation locators alias one property; the other six business parameters are required by Cobra before the single im/update_text_emotion call.", + "source": "internal/cli/schema_hints/metadata/chat.json", + "value": true + }, + "risk": { + "candidates": [ + { + "precedence": "reviewed_explicit", + "review_reason": "Ported from dws-wukong commits d6831318 and bcf56ccf, merged at 30f13f02. The four conversation locators alias one property; the other six business parameters are required by Cobra before the single im/update_text_emotion call.", + "selected": true, + "source": "internal/cli/schema_hints/metadata/chat.json", + "value": "low" + }, + { + "precedence": "inference_or_default", + "selected": false, + "source": "effect-default", + "value": "medium" + } + ], + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "Ported from dws-wukong commits d6831318 and bcf56ccf, merged at 30f13f02. The four conversation locators alias one property; the other six business parameters are required by Cobra before the single im/update_text_emotion call.", + "source": "internal/cli/schema_hints/metadata/chat.json", + "value": "low" + }, + "title": { + "candidates": [ + { + "precedence": "cobra_help", + "selected": true, + "source": "cobra_help", + "value": "更新消息的文字表情回应" + } + ], + "precedence": "cobra_help", + "resolution": "highest_precedence", + "source": "cobra_help", + "value": "更新消息的文字表情回应" + }, + "use_when": { + "candidates": [ + { + "precedence": "reviewed_explicit", + "review_reason": "依据 issue #85 的原地状态更新场景、Wukong 30f13f02 的真实 Cobra/MCP 映射和开源实现复核;选择语义不改变执行、安全或参数事实。", + "selected": true, + "source": "internal/cli/schema_hints/selection/chat.json", + "value": [ + "需要更新消息的状态文字或表情,并避免先移除再添加造成闪烁和两次网络调用时" + ] + } + ], + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "依据 issue #85 的原地状态更新场景、Wukong 30f13f02 的真实 Cobra/MCP 映射和开源实现复核;选择语义不改变执行、安全或参数事实。", + "source": "internal/cli/schema_hints/selection/chat.json", + "value": [ + "需要更新消息的状态文字或表情,并避免先移除再添加造成闪烁和两次网络调用时" + ] + } + }, + "group": "message", + "has_parameters": true, + "idempotency": "idempotent", + "interface_mode": "composite", + "interface_reason": "Reviewed unpinned remote adapter: the executable CLI calls im/update_text_emotion, which is proven by dws-wukong develop@30f13f02 but absent from the pinned MCP metadata snapshot; no pinned interface_ref can represent the command yet.", + "is_alias": false, + "name": "update_text_emotion", + "parameter_count": 10, + "parameters": { + "background-id": { + "cli_required": true, + "description": "新文字表情的背景 ID", + "field_provenance": { + "cli_required": { + "candidates": [ + { + "precedence": "cobra_contract", + "selected": true, + "source": "cobra_hard_required", + "value": true + } + ], + "precedence": "cobra_contract", + "resolution": "highest_precedence", + "source": "cobra_hard_required", + "value": true + }, + "description": { + "candidates": [ + { + "precedence": "reviewed_manual", + "review_reason": "Ported from dws-wukong commits d6831318 and bcf56ccf, merged at 30f13f02. The four conversation locators alias one property; the other six business parameters are required by Cobra before the single im/update_text_emotion call.", + "selected": true, + "source": "reviewed_manual_hint", + "value": "新文字表情的背景 ID" + }, + { + "precedence": "cobra_contract", + "selected": false, + "source": "cobra_usage", + "value": "新背景 ID (必填)" + }, + { + "precedence": "default", + "selected": false, + "source": "default", + "value": "" + } + ], + "precedence": "reviewed_manual", + "resolution": "highest_precedence", + "review_reason": "Ported from dws-wukong commits d6831318 and bcf56ccf, merged at 30f13f02. The four conversation locators alias one property; the other six business parameters are required by Cobra before the single im/update_text_emotion call.", + "source": "reviewed_manual_hint", + "value": "新文字表情的背景 ID" + }, + "property": { + "candidates": [ + { + "precedence": "reviewed_manual", + "review_reason": "Ported from dws-wukong commits d6831318 and bcf56ccf, merged at 30f13f02. The four conversation locators alias one property; the other six business parameters are required by Cobra before the single im/update_text_emotion call.", + "selected": true, + "source": "reviewed_manual_hint", + "value": "backgroundId" + }, + { + "precedence": "inference", + "selected": false, + "source": "flag_name_inference", + "value": "backgroundId" + } + ], + "precedence": "reviewed_manual", + "resolution": "highest_precedence", + "review_reason": "Ported from dws-wukong commits d6831318 and bcf56ccf, merged at 30f13f02. The four conversation locators alias one property; the other six business parameters are required by Cobra before the single im/update_text_emotion call.", + "source": "reviewed_manual_hint", + "value": "backgroundId" + }, + "required": { + "candidates": [ + { + "precedence": "reviewed_manual", + "review_reason": "Ported from dws-wukong commits d6831318 and bcf56ccf, merged at 30f13f02. The four conversation locators alias one property; the other six business parameters are required by Cobra before the single im/update_text_emotion call.", + "selected": true, + "source": "reviewed_manual_hint", + "value": true + }, + { + "precedence": "cobra_contract", + "selected": false, + "source": "cobra_hard_required", + "value": true + }, + { + "precedence": "inference", + "selected": false, + "source": "usage_required_inference", + "value": true + }, + { + "precedence": "default", + "selected": false, + "source": "default", + "value": false + } + ], + "precedence": "reviewed_manual", + "resolution": "highest_precedence", + "review_reason": "Ported from dws-wukong commits d6831318 and bcf56ccf, merged at 30f13f02. The four conversation locators alias one property; the other six business parameters are required by Cobra before the single im/update_text_emotion call.", + "source": "reviewed_manual_hint", + "value": true + }, + "required_when": { + "candidates": [ + { + "precedence": "default", + "selected": true, + "source": "default", + "value": "" + } + ], + "precedence": "default", + "resolution": "highest_precedence", + "source": "default", + "value": "" + }, + "type": { + "candidates": [ + { + "precedence": "cobra_contract", + "selected": true, + "source": "cobra_flag_type", + "value": "string" + } + ], + "precedence": "cobra_contract", + "resolution": "highest_precedence", + "source": "cobra_flag_type", + "value": "string" + } + }, + "property": "backgroundId", + "required": true, + "type": "string" + }, + "chat": { + "description": "--conversation-id 的别名", + "field_provenance": { + "description": { + "candidates": [ + { + "precedence": "cobra_contract", + "selected": true, + "source": "cobra_usage", + "value": "--conversation-id 的别名" + }, + { + "precedence": "default", + "selected": false, + "source": "default", + "value": "" + } + ], + "precedence": "cobra_contract", + "resolution": "highest_precedence", + "source": "cobra_usage", + "value": "--conversation-id 的别名" + }, + "property": { + "candidates": [ + { + "precedence": "reviewed_manual", + "review_reason": "Ported from dws-wukong commits d6831318 and bcf56ccf, merged at 30f13f02. The four conversation locators alias one property; the other six business parameters are required by Cobra before the single im/update_text_emotion call.", + "selected": true, + "source": "reviewed_manual_hint", + "value": "openConversationId" + }, + { + "precedence": "inference", + "selected": false, + "source": "flag_name_inference", + "value": "chat" + } + ], + "precedence": "reviewed_manual", + "resolution": "highest_precedence", + "review_reason": "Ported from dws-wukong commits d6831318 and bcf56ccf, merged at 30f13f02. The four conversation locators alias one property; the other six business parameters are required by Cobra before the single im/update_text_emotion call.", + "source": "reviewed_manual_hint", + "value": "openConversationId" + }, + "required": { + "candidates": [ + { + "precedence": "reviewed_manual", + "review_reason": "Ported from dws-wukong commits d6831318 and bcf56ccf, merged at 30f13f02. The four conversation locators alias one property; the other six business parameters are required by Cobra before the single im/update_text_emotion call.", + "selected": true, + "source": "reviewed_manual_hint", + "value": false + }, + { + "precedence": "command_constraint", + "selected": false, + "source": "require_one_of_constraint", + "value": false + }, + { + "precedence": "default", + "selected": false, + "source": "default", + "value": false + } + ], + "precedence": "reviewed_manual", + "resolution": "highest_precedence", + "review_reason": "Ported from dws-wukong commits d6831318 and bcf56ccf, merged at 30f13f02. The four conversation locators alias one property; the other six business parameters are required by Cobra before the single im/update_text_emotion call.", + "source": "reviewed_manual_hint", + "value": false + }, + "required_when": { + "candidates": [ + { + "precedence": "default", + "selected": true, + "source": "default", + "value": "" + } + ], + "precedence": "default", + "resolution": "highest_precedence", + "source": "default", + "value": "" + }, + "type": { + "candidates": [ + { + "precedence": "cobra_contract", + "selected": true, + "source": "cobra_flag_type", + "value": "string" + } + ], + "precedence": "cobra_contract", + "resolution": "highest_precedence", + "source": "cobra_flag_type", + "value": "string" + } + }, + "property": "openConversationId", + "required": false, + "type": "string" + }, + "conversation-id": { + "description": "会话 openConversationId;与 --group、--id、--chat 四选一", + "field_provenance": { + "description": { + "candidates": [ + { + "precedence": "reviewed_manual", + "review_reason": "Ported from dws-wukong commits d6831318 and bcf56ccf, merged at 30f13f02. The four conversation locators alias one property; the other six business parameters are required by Cobra before the single im/update_text_emotion call.", + "selected": true, + "source": "reviewed_manual_hint", + "value": "会话 openConversationId;与 --group、--id、--chat 四选一" + }, + { + "precedence": "cobra_contract", + "selected": false, + "source": "cobra_usage", + "value": "会话 openConversationId (必填,支持单聊/群聊)" + }, + { + "precedence": "default", + "selected": false, + "source": "default", + "value": "" + } + ], + "precedence": "reviewed_manual", + "resolution": "highest_precedence", + "review_reason": "Ported from dws-wukong commits d6831318 and bcf56ccf, merged at 30f13f02. The four conversation locators alias one property; the other six business parameters are required by Cobra before the single im/update_text_emotion call.", + "source": "reviewed_manual_hint", + "value": "会话 openConversationId;与 --group、--id、--chat 四选一" + }, + "property": { + "candidates": [ + { + "precedence": "reviewed_manual", + "review_reason": "Ported from dws-wukong commits d6831318 and bcf56ccf, merged at 30f13f02. The four conversation locators alias one property; the other six business parameters are required by Cobra before the single im/update_text_emotion call.", + "selected": true, + "source": "reviewed_manual_hint", + "value": "openConversationId" + }, + { + "precedence": "inference", + "selected": false, + "source": "flag_name_inference", + "value": "conversationId" + } + ], + "precedence": "reviewed_manual", + "resolution": "highest_precedence", + "review_reason": "Ported from dws-wukong commits d6831318 and bcf56ccf, merged at 30f13f02. The four conversation locators alias one property; the other six business parameters are required by Cobra before the single im/update_text_emotion call.", + "source": "reviewed_manual_hint", + "value": "openConversationId" + }, + "required": { + "candidates": [ + { + "precedence": "reviewed_manual", + "review_reason": "Ported from dws-wukong commits d6831318 and bcf56ccf, merged at 30f13f02. The four conversation locators alias one property; the other six business parameters are required by Cobra before the single im/update_text_emotion call.", + "selected": true, + "source": "reviewed_manual_hint", + "value": false + }, + { + "precedence": "command_constraint", + "selected": false, + "source": "require_one_of_constraint", + "value": false + }, + { + "precedence": "inference", + "selected": false, + "source": "usage_required_inference", + "value": true + }, + { + "precedence": "default", + "selected": false, + "source": "default", + "value": false + } + ], + "precedence": "reviewed_manual", + "resolution": "highest_precedence", + "review_reason": "Ported from dws-wukong commits d6831318 and bcf56ccf, merged at 30f13f02. The four conversation locators alias one property; the other six business parameters are required by Cobra before the single im/update_text_emotion call.", + "source": "reviewed_manual_hint", + "value": false + }, + "required_when": { + "candidates": [ + { + "precedence": "default", + "selected": true, + "source": "default", + "value": "" + } + ], + "precedence": "default", + "resolution": "highest_precedence", + "source": "default", + "value": "" + }, + "type": { + "candidates": [ + { + "precedence": "cobra_contract", + "selected": true, + "source": "cobra_flag_type", + "value": "string" + } + ], + "precedence": "cobra_contract", + "resolution": "highest_precedence", + "source": "cobra_flag_type", + "value": "string" + } + }, + "property": "openConversationId", + "required": false, + "type": "string" + }, + "emotion-id": { + "cli_required": true, + "description": "新的文字表情 ID,可通过 create-text-emotion 获取", + "field_provenance": { + "cli_required": { + "candidates": [ + { + "precedence": "cobra_contract", + "selected": true, + "source": "cobra_hard_required", + "value": true + } + ], + "precedence": "cobra_contract", + "resolution": "highest_precedence", + "source": "cobra_hard_required", + "value": true + }, + "description": { + "candidates": [ + { + "precedence": "reviewed_manual", + "review_reason": "Ported from dws-wukong commits d6831318 and bcf56ccf, merged at 30f13f02. The four conversation locators alias one property; the other six business parameters are required by Cobra before the single im/update_text_emotion call.", + "selected": true, + "source": "reviewed_manual_hint", + "value": "新的文字表情 ID,可通过 create-text-emotion 获取" + }, + { + "precedence": "cobra_contract", + "selected": false, + "source": "cobra_usage", + "value": "新表情 ID (必填)" + }, + { + "precedence": "default", + "selected": false, + "source": "default", + "value": "" + } + ], + "precedence": "reviewed_manual", + "resolution": "highest_precedence", + "review_reason": "Ported from dws-wukong commits d6831318 and bcf56ccf, merged at 30f13f02. The four conversation locators alias one property; the other six business parameters are required by Cobra before the single im/update_text_emotion call.", + "source": "reviewed_manual_hint", + "value": "新的文字表情 ID,可通过 create-text-emotion 获取" + }, + "property": { + "candidates": [ + { + "precedence": "reviewed_manual", + "review_reason": "Ported from dws-wukong commits d6831318 and bcf56ccf, merged at 30f13f02. The four conversation locators alias one property; the other six business parameters are required by Cobra before the single im/update_text_emotion call.", + "selected": true, + "source": "reviewed_manual_hint", + "value": "emotionId" + }, + { + "precedence": "inference", + "selected": false, + "source": "flag_name_inference", + "value": "emotionId" + } + ], + "precedence": "reviewed_manual", + "resolution": "highest_precedence", + "review_reason": "Ported from dws-wukong commits d6831318 and bcf56ccf, merged at 30f13f02. The four conversation locators alias one property; the other six business parameters are required by Cobra before the single im/update_text_emotion call.", + "source": "reviewed_manual_hint", + "value": "emotionId" + }, + "required": { + "candidates": [ + { + "precedence": "reviewed_manual", + "review_reason": "Ported from dws-wukong commits d6831318 and bcf56ccf, merged at 30f13f02. The four conversation locators alias one property; the other six business parameters are required by Cobra before the single im/update_text_emotion call.", + "selected": true, + "source": "reviewed_manual_hint", + "value": true + }, + { + "precedence": "cobra_contract", + "selected": false, + "source": "cobra_hard_required", + "value": true + }, + { + "precedence": "inference", + "selected": false, + "source": "usage_required_inference", + "value": true + }, + { + "precedence": "default", + "selected": false, + "source": "default", + "value": false + } + ], + "precedence": "reviewed_manual", + "resolution": "highest_precedence", + "review_reason": "Ported from dws-wukong commits d6831318 and bcf56ccf, merged at 30f13f02. The four conversation locators alias one property; the other six business parameters are required by Cobra before the single im/update_text_emotion call.", + "source": "reviewed_manual_hint", + "value": true + }, + "required_when": { + "candidates": [ + { + "precedence": "default", + "selected": true, + "source": "default", + "value": "" + } + ], + "precedence": "default", + "resolution": "highest_precedence", + "source": "default", + "value": "" + }, + "type": { + "candidates": [ + { + "precedence": "cobra_contract", + "selected": true, + "source": "cobra_flag_type", + "value": "string" + } + ], + "precedence": "cobra_contract", + "resolution": "highest_precedence", + "source": "cobra_flag_type", + "value": "string" + } + }, + "property": "emotionId", + "required": true, + "type": "string" + }, + "emotion-name": { + "cli_required": true, + "description": "新的文字表情名称", + "field_provenance": { + "cli_required": { + "candidates": [ + { + "precedence": "cobra_contract", + "selected": true, + "source": "cobra_hard_required", + "value": true + } + ], + "precedence": "cobra_contract", + "resolution": "highest_precedence", + "source": "cobra_hard_required", + "value": true + }, + "description": { + "candidates": [ + { + "precedence": "reviewed_manual", + "review_reason": "Ported from dws-wukong commits d6831318 and bcf56ccf, merged at 30f13f02. The four conversation locators alias one property; the other six business parameters are required by Cobra before the single im/update_text_emotion call.", + "selected": true, + "source": "reviewed_manual_hint", + "value": "新的文字表情名称" + }, + { + "precedence": "cobra_contract", + "selected": false, + "source": "cobra_usage", + "value": "新表情名称 (必填)" + }, + { + "precedence": "default", + "selected": false, + "source": "default", + "value": "" + } + ], + "precedence": "reviewed_manual", + "resolution": "highest_precedence", + "review_reason": "Ported from dws-wukong commits d6831318 and bcf56ccf, merged at 30f13f02. The four conversation locators alias one property; the other six business parameters are required by Cobra before the single im/update_text_emotion call.", + "source": "reviewed_manual_hint", + "value": "新的文字表情名称" + }, + "property": { + "candidates": [ + { + "precedence": "reviewed_manual", + "review_reason": "Ported from dws-wukong commits d6831318 and bcf56ccf, merged at 30f13f02. The four conversation locators alias one property; the other six business parameters are required by Cobra before the single im/update_text_emotion call.", + "selected": true, + "source": "reviewed_manual_hint", + "value": "emotionName" + }, + { + "precedence": "inference", + "selected": false, + "source": "flag_name_inference", + "value": "emotionName" + } + ], + "precedence": "reviewed_manual", + "resolution": "highest_precedence", + "review_reason": "Ported from dws-wukong commits d6831318 and bcf56ccf, merged at 30f13f02. The four conversation locators alias one property; the other six business parameters are required by Cobra before the single im/update_text_emotion call.", + "source": "reviewed_manual_hint", + "value": "emotionName" + }, + "required": { + "candidates": [ + { + "precedence": "reviewed_manual", + "review_reason": "Ported from dws-wukong commits d6831318 and bcf56ccf, merged at 30f13f02. The four conversation locators alias one property; the other six business parameters are required by Cobra before the single im/update_text_emotion call.", + "selected": true, + "source": "reviewed_manual_hint", + "value": true + }, + { + "precedence": "cobra_contract", + "selected": false, + "source": "cobra_hard_required", + "value": true + }, + { + "precedence": "inference", + "selected": false, + "source": "usage_required_inference", + "value": true + }, + { + "precedence": "default", + "selected": false, + "source": "default", + "value": false + } + ], + "precedence": "reviewed_manual", + "resolution": "highest_precedence", + "review_reason": "Ported from dws-wukong commits d6831318 and bcf56ccf, merged at 30f13f02. The four conversation locators alias one property; the other six business parameters are required by Cobra before the single im/update_text_emotion call.", + "source": "reviewed_manual_hint", + "value": true + }, + "required_when": { + "candidates": [ + { + "precedence": "default", + "selected": true, + "source": "default", + "value": "" + } + ], + "precedence": "default", + "resolution": "highest_precedence", + "source": "default", + "value": "" + }, + "type": { + "candidates": [ + { + "precedence": "cobra_contract", + "selected": true, + "source": "cobra_flag_type", + "value": "string" + } + ], + "precedence": "cobra_contract", + "resolution": "highest_precedence", + "source": "cobra_flag_type", + "value": "string" + } + }, + "property": "emotionName", + "required": true, + "type": "string" + }, + "group": { + "description": "--conversation-id 的别名", + "field_provenance": { + "description": { + "candidates": [ + { + "precedence": "cobra_contract", + "selected": true, + "source": "cobra_usage", + "value": "--conversation-id 的别名" + }, + { + "precedence": "default", + "selected": false, + "source": "default", + "value": "" + } + ], + "precedence": "cobra_contract", + "resolution": "highest_precedence", + "source": "cobra_usage", + "value": "--conversation-id 的别名" + }, + "property": { + "candidates": [ + { + "precedence": "reviewed_manual", + "review_reason": "Ported from dws-wukong commits d6831318 and bcf56ccf, merged at 30f13f02. The four conversation locators alias one property; the other six business parameters are required by Cobra before the single im/update_text_emotion call.", + "selected": true, + "source": "reviewed_manual_hint", + "value": "openConversationId" + }, + { + "precedence": "inference", + "selected": false, + "source": "flag_name_inference", + "value": "group" + } + ], + "precedence": "reviewed_manual", + "resolution": "highest_precedence", + "review_reason": "Ported from dws-wukong commits d6831318 and bcf56ccf, merged at 30f13f02. The four conversation locators alias one property; the other six business parameters are required by Cobra before the single im/update_text_emotion call.", + "source": "reviewed_manual_hint", + "value": "openConversationId" + }, + "required": { + "candidates": [ + { + "precedence": "reviewed_manual", + "review_reason": "Ported from dws-wukong commits d6831318 and bcf56ccf, merged at 30f13f02. The four conversation locators alias one property; the other six business parameters are required by Cobra before the single im/update_text_emotion call.", + "selected": true, + "source": "reviewed_manual_hint", + "value": false + }, + { + "precedence": "command_constraint", + "selected": false, + "source": "require_one_of_constraint", + "value": false + }, + { + "precedence": "default", + "selected": false, + "source": "default", + "value": false + } + ], + "precedence": "reviewed_manual", + "resolution": "highest_precedence", + "review_reason": "Ported from dws-wukong commits d6831318 and bcf56ccf, merged at 30f13f02. The four conversation locators alias one property; the other six business parameters are required by Cobra before the single im/update_text_emotion call.", + "source": "reviewed_manual_hint", + "value": false + }, + "required_when": { + "candidates": [ + { + "precedence": "default", + "selected": true, + "source": "default", + "value": "" + } + ], + "precedence": "default", + "resolution": "highest_precedence", + "source": "default", + "value": "" + }, + "type": { + "candidates": [ + { + "precedence": "cobra_contract", + "selected": true, + "source": "cobra_flag_type", + "value": "string" + } + ], + "precedence": "cobra_contract", + "resolution": "highest_precedence", + "source": "cobra_flag_type", + "value": "string" + } + }, + "property": "openConversationId", + "required": false, + "type": "string" + }, + "id": { + "description": "--conversation-id 的别名", + "field_provenance": { + "description": { + "candidates": [ + { + "precedence": "cobra_contract", + "selected": true, + "source": "cobra_usage", + "value": "--conversation-id 的别名" + }, + { + "precedence": "default", + "selected": false, + "source": "default", + "value": "" + } + ], + "precedence": "cobra_contract", + "resolution": "highest_precedence", + "source": "cobra_usage", + "value": "--conversation-id 的别名" + }, + "property": { + "candidates": [ + { + "precedence": "reviewed_manual", + "review_reason": "Ported from dws-wukong commits d6831318 and bcf56ccf, merged at 30f13f02. The four conversation locators alias one property; the other six business parameters are required by Cobra before the single im/update_text_emotion call.", + "selected": true, + "source": "reviewed_manual_hint", + "value": "openConversationId" + }, + { + "precedence": "inference", + "selected": false, + "source": "flag_name_inference", + "value": "id" + } + ], + "precedence": "reviewed_manual", + "resolution": "highest_precedence", + "review_reason": "Ported from dws-wukong commits d6831318 and bcf56ccf, merged at 30f13f02. The four conversation locators alias one property; the other six business parameters are required by Cobra before the single im/update_text_emotion call.", + "source": "reviewed_manual_hint", + "value": "openConversationId" + }, + "required": { + "candidates": [ + { + "precedence": "reviewed_manual", + "review_reason": "Ported from dws-wukong commits d6831318 and bcf56ccf, merged at 30f13f02. The four conversation locators alias one property; the other six business parameters are required by Cobra before the single im/update_text_emotion call.", + "selected": true, + "source": "reviewed_manual_hint", + "value": false + }, + { + "precedence": "command_constraint", + "selected": false, + "source": "require_one_of_constraint", + "value": false + }, + { + "precedence": "default", + "selected": false, + "source": "default", + "value": false + } + ], + "precedence": "reviewed_manual", + "resolution": "highest_precedence", + "review_reason": "Ported from dws-wukong commits d6831318 and bcf56ccf, merged at 30f13f02. The four conversation locators alias one property; the other six business parameters are required by Cobra before the single im/update_text_emotion call.", + "source": "reviewed_manual_hint", + "value": false + }, + "required_when": { + "candidates": [ + { + "precedence": "default", + "selected": true, + "source": "default", + "value": "" + } + ], + "precedence": "default", + "resolution": "highest_precedence", + "source": "default", + "value": "" + }, + "type": { + "candidates": [ + { + "precedence": "cobra_contract", + "selected": true, + "source": "cobra_flag_type", + "value": "string" + } + ], + "precedence": "cobra_contract", + "resolution": "highest_precedence", + "source": "cobra_flag_type", + "value": "string" + } + }, + "property": "openConversationId", + "required": false, + "type": "string" + }, + "msg-id": { + "cli_required": true, + "description": "需要原地更新文字表情的消息 openMsgId", + "field_provenance": { + "cli_required": { + "candidates": [ + { + "precedence": "cobra_contract", + "selected": true, + "source": "cobra_hard_required", + "value": true + } + ], + "precedence": "cobra_contract", + "resolution": "highest_precedence", + "source": "cobra_hard_required", + "value": true + }, + "description": { + "candidates": [ + { + "precedence": "reviewed_manual", + "review_reason": "Ported from dws-wukong commits d6831318 and bcf56ccf, merged at 30f13f02. The four conversation locators alias one property; the other six business parameters are required by Cobra before the single im/update_text_emotion call.", + "selected": true, + "source": "reviewed_manual_hint", + "value": "需要原地更新文字表情的消息 openMsgId" + }, + { + "precedence": "cobra_contract", + "selected": false, + "source": "cobra_usage", + "value": "消息 openMsgId (必填)" + }, + { + "precedence": "default", + "selected": false, + "source": "default", + "value": "" + } + ], + "precedence": "reviewed_manual", + "resolution": "highest_precedence", + "review_reason": "Ported from dws-wukong commits d6831318 and bcf56ccf, merged at 30f13f02. The four conversation locators alias one property; the other six business parameters are required by Cobra before the single im/update_text_emotion call.", + "source": "reviewed_manual_hint", + "value": "需要原地更新文字表情的消息 openMsgId" + }, + "property": { + "candidates": [ + { + "precedence": "reviewed_manual", + "review_reason": "Ported from dws-wukong commits d6831318 and bcf56ccf, merged at 30f13f02. The four conversation locators alias one property; the other six business parameters are required by Cobra before the single im/update_text_emotion call.", + "selected": true, + "source": "reviewed_manual_hint", + "value": "openMsgId" + }, + { + "precedence": "inference", + "selected": false, + "source": "flag_name_inference", + "value": "msgId" + } + ], + "precedence": "reviewed_manual", + "resolution": "highest_precedence", + "review_reason": "Ported from dws-wukong commits d6831318 and bcf56ccf, merged at 30f13f02. The four conversation locators alias one property; the other six business parameters are required by Cobra before the single im/update_text_emotion call.", + "source": "reviewed_manual_hint", + "value": "openMsgId" + }, + "required": { + "candidates": [ + { + "precedence": "reviewed_manual", + "review_reason": "Ported from dws-wukong commits d6831318 and bcf56ccf, merged at 30f13f02. The four conversation locators alias one property; the other six business parameters are required by Cobra before the single im/update_text_emotion call.", + "selected": true, + "source": "reviewed_manual_hint", + "value": true + }, + { + "precedence": "cobra_contract", + "selected": false, + "source": "cobra_hard_required", + "value": true + }, + { + "precedence": "inference", + "selected": false, + "source": "usage_required_inference", + "value": true + }, + { + "precedence": "default", + "selected": false, + "source": "default", + "value": false + } + ], + "precedence": "reviewed_manual", + "resolution": "highest_precedence", + "review_reason": "Ported from dws-wukong commits d6831318 and bcf56ccf, merged at 30f13f02. The four conversation locators alias one property; the other six business parameters are required by Cobra before the single im/update_text_emotion call.", + "source": "reviewed_manual_hint", + "value": true + }, + "required_when": { + "candidates": [ + { + "precedence": "default", + "selected": true, + "source": "default", + "value": "" + } + ], + "precedence": "default", + "resolution": "highest_precedence", + "source": "default", + "value": "" + }, + "type": { + "candidates": [ + { + "precedence": "cobra_contract", + "selected": true, + "source": "cobra_flag_type", + "value": "string" + } + ], + "precedence": "cobra_contract", + "resolution": "highest_precedence", + "source": "cobra_flag_type", + "value": "string" + } + }, + "property": "openMsgId", + "required": true, + "type": "string" + }, + "old-emotion-id": { + "cli_required": true, + "description": "消息上当前文字表情的 emotionId", + "field_provenance": { + "cli_required": { + "candidates": [ + { + "precedence": "cobra_contract", + "selected": true, + "source": "cobra_hard_required", + "value": true + } + ], + "precedence": "cobra_contract", + "resolution": "highest_precedence", + "source": "cobra_hard_required", + "value": true + }, + "description": { + "candidates": [ + { + "precedence": "reviewed_manual", + "review_reason": "Ported from dws-wukong commits d6831318 and bcf56ccf, merged at 30f13f02. The four conversation locators alias one property; the other six business parameters are required by Cobra before the single im/update_text_emotion call.", + "selected": true, + "source": "reviewed_manual_hint", + "value": "消息上当前文字表情的 emotionId" + }, + { + "precedence": "cobra_contract", + "selected": false, + "source": "cobra_usage", + "value": "待更新的原表情 ID (必填)" + }, + { + "precedence": "default", + "selected": false, + "source": "default", + "value": "" + } + ], + "precedence": "reviewed_manual", + "resolution": "highest_precedence", + "review_reason": "Ported from dws-wukong commits d6831318 and bcf56ccf, merged at 30f13f02. The four conversation locators alias one property; the other six business parameters are required by Cobra before the single im/update_text_emotion call.", + "source": "reviewed_manual_hint", + "value": "消息上当前文字表情的 emotionId" + }, + "property": { + "candidates": [ + { + "precedence": "reviewed_manual", + "review_reason": "Ported from dws-wukong commits d6831318 and bcf56ccf, merged at 30f13f02. The four conversation locators alias one property; the other six business parameters are required by Cobra before the single im/update_text_emotion call.", + "selected": true, + "source": "reviewed_manual_hint", + "value": "oldEmotionId" + }, + { + "precedence": "inference", + "selected": false, + "source": "flag_name_inference", + "value": "oldEmotionId" + } + ], + "precedence": "reviewed_manual", + "resolution": "highest_precedence", + "review_reason": "Ported from dws-wukong commits d6831318 and bcf56ccf, merged at 30f13f02. The four conversation locators alias one property; the other six business parameters are required by Cobra before the single im/update_text_emotion call.", + "source": "reviewed_manual_hint", + "value": "oldEmotionId" + }, + "required": { + "candidates": [ + { + "precedence": "reviewed_manual", + "review_reason": "Ported from dws-wukong commits d6831318 and bcf56ccf, merged at 30f13f02. The four conversation locators alias one property; the other six business parameters are required by Cobra before the single im/update_text_emotion call.", + "selected": true, + "source": "reviewed_manual_hint", + "value": true + }, + { + "precedence": "cobra_contract", + "selected": false, + "source": "cobra_hard_required", + "value": true + }, + { + "precedence": "inference", + "selected": false, + "source": "usage_required_inference", + "value": true + }, + { + "precedence": "default", + "selected": false, + "source": "default", + "value": false + } + ], + "precedence": "reviewed_manual", + "resolution": "highest_precedence", + "review_reason": "Ported from dws-wukong commits d6831318 and bcf56ccf, merged at 30f13f02. The four conversation locators alias one property; the other six business parameters are required by Cobra before the single im/update_text_emotion call.", + "source": "reviewed_manual_hint", + "value": true + }, + "required_when": { + "candidates": [ + { + "precedence": "default", + "selected": true, + "source": "default", + "value": "" + } + ], + "precedence": "default", + "resolution": "highest_precedence", + "source": "default", + "value": "" + }, + "type": { + "candidates": [ + { + "precedence": "cobra_contract", + "selected": true, + "source": "cobra_flag_type", + "value": "string" + } + ], + "precedence": "cobra_contract", + "resolution": "highest_precedence", + "source": "cobra_flag_type", + "value": "string" + } + }, + "property": "oldEmotionId", + "required": true, + "type": "string" + }, + "text": { + "cli_required": true, + "description": "新的文字表情内容", + "field_provenance": { + "cli_required": { + "candidates": [ + { + "precedence": "cobra_contract", + "selected": true, + "source": "cobra_hard_required", + "value": true + } + ], + "precedence": "cobra_contract", + "resolution": "highest_precedence", + "source": "cobra_hard_required", + "value": true + }, + "description": { + "candidates": [ + { + "precedence": "reviewed_manual", + "review_reason": "Ported from dws-wukong commits d6831318 and bcf56ccf, merged at 30f13f02. The four conversation locators alias one property; the other six business parameters are required by Cobra before the single im/update_text_emotion call.", + "selected": true, + "source": "reviewed_manual_hint", + "value": "新的文字表情内容" + }, + { + "precedence": "cobra_contract", + "selected": false, + "source": "cobra_usage", + "value": "新文字内容 (必填)" + }, + { + "precedence": "default", + "selected": false, + "source": "default", + "value": "" + } + ], + "precedence": "reviewed_manual", + "resolution": "highest_precedence", + "review_reason": "Ported from dws-wukong commits d6831318 and bcf56ccf, merged at 30f13f02. The four conversation locators alias one property; the other six business parameters are required by Cobra before the single im/update_text_emotion call.", + "source": "reviewed_manual_hint", + "value": "新的文字表情内容" + }, + "property": { + "candidates": [ + { + "precedence": "reviewed_manual", + "review_reason": "Ported from dws-wukong commits d6831318 and bcf56ccf, merged at 30f13f02. The four conversation locators alias one property; the other six business parameters are required by Cobra before the single im/update_text_emotion call.", + "selected": true, + "source": "reviewed_manual_hint", + "value": "text" + }, + { + "precedence": "inference", + "selected": false, + "source": "flag_name_inference", + "value": "text" + } + ], + "precedence": "reviewed_manual", + "resolution": "highest_precedence", + "review_reason": "Ported from dws-wukong commits d6831318 and bcf56ccf, merged at 30f13f02. The four conversation locators alias one property; the other six business parameters are required by Cobra before the single im/update_text_emotion call.", + "source": "reviewed_manual_hint", + "value": "text" + }, + "required": { + "candidates": [ + { + "precedence": "reviewed_manual", + "review_reason": "Ported from dws-wukong commits d6831318 and bcf56ccf, merged at 30f13f02. The four conversation locators alias one property; the other six business parameters are required by Cobra before the single im/update_text_emotion call.", + "selected": true, + "source": "reviewed_manual_hint", + "value": true + }, + { + "precedence": "cobra_contract", + "selected": false, + "source": "cobra_hard_required", + "value": true + }, + { + "precedence": "inference", + "selected": false, + "source": "usage_required_inference", + "value": true + }, + { + "precedence": "default", + "selected": false, + "source": "default", + "value": false + } + ], + "precedence": "reviewed_manual", + "resolution": "highest_precedence", + "review_reason": "Ported from dws-wukong commits d6831318 and bcf56ccf, merged at 30f13f02. The four conversation locators alias one property; the other six business parameters are required by Cobra before the single im/update_text_emotion call.", + "source": "reviewed_manual_hint", + "value": true + }, + "required_when": { + "candidates": [ + { + "precedence": "default", + "selected": true, + "source": "default", + "value": "" + } + ], + "precedence": "default", + "resolution": "highest_precedence", + "source": "default", + "value": "" + }, + "type": { + "candidates": [ + { + "precedence": "cobra_contract", + "selected": true, + "source": "cobra_flag_type", + "value": "string" + } + ], + "precedence": "cobra_contract", + "resolution": "highest_precedence", + "source": "cobra_flag_type", + "value": "string" + } + }, + "property": "text", + "required": true, + "type": "string" + } + }, + "path": "chat.update_text_emotion", + "primary_cli_path": "chat message update-text-emotion", + "product_id": "chat", + "reviewed": true, + "risk": "low", + "source": "reviewed_command_registry", + "title": "更新消息的文字表情回应", + "use_when": [ + "需要更新消息的状态文字或表情,并避免先移除再添加造成闪烁和两次网络调用时" + ] + }, "chat.upgrade_group_to_external": { "agent_metadata_source": "embedded-skill-metadata", "agent_source_refs": [ diff --git a/internal/cli/schema_catalog/tools/doc.json b/internal/cli/schema_catalog/tools/doc.json index 6e331b04..9397637f 100644 --- a/internal/cli/schema_catalog/tools/doc.json +++ b/internal/cli/schema_catalog/tools/doc.json @@ -10505,6 +10505,423 @@ "准备读内容前必须先看 contentType/extension 以路由到 read/sheet/aitable/download 时" ] }, + "doc.get_document_style": { + "agent_metadata_source": "embedded-skill-metadata", + "agent_source_refs": [ + "CommandRegistry:canonical_path=doc.get_document_style", + "cobra-help:dws doc style get", + "internal/cli/schema_hints/metadata/doc.json", + "internal/cli/schema_hints/selection/doc.json", + "wukong-develop:wukong/products" + ], + "agent_summary": "读取文档当前封面与背景配置(只读)", + "agent_summary_source": "dws-agent-selection/doc", + "availability": "available", + "avoid_when": [ + "修改用 cover set / background set" + ], + "canonical_path": "doc.get_document_style", + "cli_name": "get", + "cli_path": "doc style get", + "confirmation": "not_required", + "description": "读取钉钉文档当前的封面与背景配置(只读,单接口收口 get_document_style)。", + "display": "钉钉文档管理", + "effect": "read", + "effect_source": "agent-hint", + "examples": [ + "dws doc style get --node \u003cDOC_ID\u003e --format json" + ], + "field_provenance": { + "agent_summary": { + "candidates": [ + { + "precedence": "reviewed_explicit", + "review_reason": "人工审阅:对照悟空 develop 同名命令实现、Cobra Long/Example 与 Runtime 确认门禁移植;不改变命令身份、参数契约或接口绑定。", + "selected": true, + "source": "internal/cli/schema_hints/selection/doc.json", + "value": "读取文档当前封面与背景配置(只读)" + } + ], + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "人工审阅:对照悟空 develop 同名命令实现、Cobra Long/Example 与 Runtime 确认门禁移植;不改变命令身份、参数契约或接口绑定。", + "source": "internal/cli/schema_hints/selection/doc.json", + "value": "读取文档当前封面与背景配置(只读)" + }, + "availability": { + "candidates": [ + { + "precedence": "reviewed_explicit", + "review_reason": "dws-tool-metadata/doc marks this tool as reviewed", + "selected": true, + "source": "internal/cli/schema_hints/metadata/doc.json", + "value": "available" + } + ], + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "dws-tool-metadata/doc marks this tool as reviewed", + "source": "internal/cli/schema_hints/metadata/doc.json", + "value": "available" + }, + "avoid_when": { + "candidates": [ + { + "precedence": "reviewed_explicit", + "review_reason": "人工审阅:对照悟空 develop 同名命令实现、Cobra Long/Example 与 Runtime 确认门禁移植;不改变命令身份、参数契约或接口绑定。", + "selected": true, + "source": "internal/cli/schema_hints/selection/doc.json", + "value": [ + "修改用 cover set / background set" + ] + } + ], + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "人工审阅:对照悟空 develop 同名命令实现、Cobra Long/Example 与 Runtime 确认门禁移植;不改变命令身份、参数契约或接口绑定。", + "source": "internal/cli/schema_hints/selection/doc.json", + "value": [ + "修改用 cover set / background set" + ] + }, + "canonical_path": { + "candidates": [ + { + "precedence": "command_registry", + "selected": true, + "source": "reviewed_command_registry", + "source_ref": "doc style get", + "value": "doc.get_document_style" + } + ], + "precedence": "command_registry", + "resolution": "registry_identity", + "source": "reviewed_command_registry", + "source_ref": "doc style get", + "value": "doc.get_document_style" + }, + "confirmation": { + "candidates": [ + { + "precedence": "reviewed_explicit", + "review_reason": "dws-tool-metadata/doc marks this tool as reviewed", + "selected": true, + "source": "internal/cli/schema_hints/metadata/doc.json", + "value": "not_required" + } + ], + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "dws-tool-metadata/doc marks this tool as reviewed", + "source": "internal/cli/schema_hints/metadata/doc.json", + "value": "not_required" + }, + "description": { + "candidates": [ + { + "precedence": "cobra_help", + "selected": true, + "source": "cobra_help", + "value": "读取钉钉文档当前的封面与背景配置(只读,单接口收口 get_document_style)。" + } + ], + "precedence": "cobra_help", + "resolution": "highest_precedence", + "source": "cobra_help", + "value": "读取钉钉文档当前的封面与背景配置(只读,单接口收口 get_document_style)。" + }, + "effect": { + "candidates": [ + { + "precedence": "reviewed_explicit", + "review_reason": "dws-tool-metadata/doc marks this tool as reviewed", + "selected": true, + "source": "internal/cli/schema_hints/metadata/doc.json", + "value": "read" + } + ], + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "dws-tool-metadata/doc marks this tool as reviewed", + "source": "internal/cli/schema_hints/metadata/doc.json", + "value": "read" + }, + "examples": { + "candidates": [ + { + "precedence": "reviewed_explicit", + "review_reason": "人工审阅:对照悟空 develop 同名命令实现、Cobra Long/Example 与 Runtime 确认门禁移植;不改变命令身份、参数契约或接口绑定。", + "selected": true, + "source": "internal/cli/schema_hints/selection/doc.json", + "value": [ + "dws doc style get --node \u003cDOC_ID\u003e --format json" + ] + } + ], + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "人工审阅:对照悟空 develop 同名命令实现、Cobra Long/Example 与 Runtime 确认门禁移植;不改变命令身份、参数契约或接口绑定。", + "source": "internal/cli/schema_hints/selection/doc.json", + "value": [ + "dws doc style get --node \u003cDOC_ID\u003e --format json" + ] + }, + "idempotency": { + "candidates": [ + { + "precedence": "reviewed_explicit", + "review_reason": "dws-tool-metadata/doc marks this tool as reviewed", + "selected": true, + "source": "internal/cli/schema_hints/metadata/doc.json", + "value": "idempotent" + } + ], + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "dws-tool-metadata/doc marks this tool as reviewed", + "source": "internal/cli/schema_hints/metadata/doc.json", + "value": "idempotent" + }, + "interface_mode": { + "candidates": [ + { + "precedence": "reviewed_explicit", + "review_reason": "dws-tool-metadata/doc marks this tool as reviewed", + "selected": true, + "source": "internal/cli/schema_hints/metadata/doc.json", + "value": "composite" + } + ], + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "dws-tool-metadata/doc marks this tool as reviewed", + "source": "internal/cli/schema_hints/metadata/doc.json", + "value": "composite" + }, + "interface_reason": { + "candidates": [ + { + "precedence": "reviewed_explicit", + "review_reason": "dws-tool-metadata/doc marks this tool as reviewed", + "selected": true, + "source": "internal/cli/schema_hints/metadata/doc.json", + "value": "Reviewed unpinned remote adapter: this executable CLI wrapper calls a remote helper that is absent from the pinned MCP metadata snapshot; no single pinned semantically equivalent interface_ref can represent the command." + } + ], + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "dws-tool-metadata/doc marks this tool as reviewed", + "source": "internal/cli/schema_hints/metadata/doc.json", + "value": "Reviewed unpinned remote adapter: this executable CLI wrapper calls a remote helper that is absent from the pinned MCP metadata snapshot; no single pinned semantically equivalent interface_ref can represent the command." + }, + "interface_ref": { + "candidates": [ + { + "precedence": "reviewed_explicit", + "review_reason": "final interface mode composite forbids a direct MCP interface_ref", + "selected": true, + "source": "internal/cli/schema_hints/metadata/doc.json", + "value": null + } + ], + "precedence": "reviewed_explicit", + "resolution": "interface_disposition_matrix", + "review_reason": "final interface mode composite forbids a direct MCP interface_ref", + "source": "internal/cli/schema_hints/metadata/doc.json", + "value": null + }, + "reviewed": { + "candidates": [ + { + "precedence": "reviewed_explicit", + "review_reason": "dws-tool-metadata/doc marks this tool as reviewed", + "selected": true, + "source": "internal/cli/schema_hints/metadata/doc.json", + "value": true + }, + { + "precedence": "reviewed_explicit", + "review_reason": "人工审阅:对照悟空 develop 同名命令实现、Cobra Long/Example 与 Runtime 确认门禁移植;不改变命令身份、参数契约或接口绑定。", + "selected": false, + "source": "internal/cli/schema_hints/selection/doc.json", + "value": true + } + ], + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "dws-tool-metadata/doc marks this tool as reviewed", + "source": "internal/cli/schema_hints/metadata/doc.json", + "value": true + }, + "risk": { + "candidates": [ + { + "precedence": "reviewed_explicit", + "review_reason": "dws-tool-metadata/doc marks this tool as reviewed", + "selected": true, + "source": "internal/cli/schema_hints/metadata/doc.json", + "value": "low" + } + ], + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "dws-tool-metadata/doc marks this tool as reviewed", + "source": "internal/cli/schema_hints/metadata/doc.json", + "value": "low" + }, + "title": { + "candidates": [ + { + "precedence": "cobra_help", + "selected": true, + "source": "cobra_help", + "value": "读取文档封面/背景 (只读)" + } + ], + "precedence": "cobra_help", + "resolution": "highest_precedence", + "source": "cobra_help", + "value": "读取文档封面/背景 (只读)" + }, + "use_when": { + "candidates": [ + { + "precedence": "reviewed_explicit", + "review_reason": "人工审阅:对照悟空 develop 同名命令实现、Cobra Long/Example 与 Runtime 确认门禁移植;不改变命令身份、参数契约或接口绑定。", + "selected": true, + "source": "internal/cli/schema_hints/selection/doc.json", + "value": [ + "用户说 看下文档现在的封面/背景" + ] + } + ], + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "人工审阅:对照悟空 develop 同名命令实现、Cobra Long/Example 与 Runtime 确认门禁移植;不改变命令身份、参数契约或接口绑定。", + "source": "internal/cli/schema_hints/selection/doc.json", + "value": [ + "用户说 看下文档现在的封面/背景" + ] + } + }, + "group": "style", + "has_parameters": true, + "idempotency": "idempotent", + "interface_mode": "composite", + "interface_reason": "Reviewed unpinned remote adapter: this executable CLI wrapper calls a remote helper that is absent from the pinned MCP metadata snapshot; no single pinned semantically equivalent interface_ref can represent the command.", + "is_alias": false, + "name": "get_document_style", + "parameter_count": 1, + "parameters": { + "node": { + "description": "目标文档标识,支持 URL 或 ID (必填)", + "field_provenance": { + "description": { + "candidates": [ + { + "precedence": "cobra_contract", + "selected": true, + "source": "cobra_usage", + "value": "目标文档标识,支持 URL 或 ID (必填)" + }, + { + "precedence": "default", + "selected": false, + "source": "default", + "value": "" + } + ], + "precedence": "cobra_contract", + "resolution": "highest_precedence", + "source": "cobra_usage", + "value": "目标文档标识,支持 URL 或 ID (必填)" + }, + "property": { + "candidates": [ + { + "precedence": "reviewed_mapping_exclusion", + "review_reason": "Reviewed unpinned adapter: doc.get_document_style has no singular pinned interface_ref; --node is a CLI wrapper input and does not publish a direct interface property.", + "selected": true, + "source": "reviewed_mapping_exclusion", + "value": "" + }, + { + "precedence": "inference", + "selected": false, + "source": "flag_name_inference", + "value": "node" + } + ], + "precedence": "reviewed_mapping_exclusion", + "resolution": "highest_precedence", + "review_reason": "Reviewed unpinned adapter: doc.get_document_style has no singular pinned interface_ref; --node is a CLI wrapper input and does not publish a direct interface property.", + "source": "reviewed_mapping_exclusion", + "value": "" + }, + "required": { + "candidates": [ + { + "precedence": "inference", + "selected": true, + "source": "usage_required_inference", + "value": true + }, + { + "precedence": "default", + "selected": false, + "source": "default", + "value": false + } + ], + "precedence": "inference", + "resolution": "highest_precedence", + "source": "usage_required_inference", + "value": true + }, + "required_when": { + "candidates": [ + { + "precedence": "default", + "selected": true, + "source": "default", + "value": "" + } + ], + "precedence": "default", + "resolution": "highest_precedence", + "source": "default", + "value": "" + }, + "type": { + "candidates": [ + { + "precedence": "cobra_contract", + "selected": true, + "source": "cobra_flag_type", + "value": "string" + } + ], + "precedence": "cobra_contract", + "resolution": "highest_precedence", + "source": "cobra_flag_type", + "value": "string" + } + }, + "required": true, + "type": "string" + } + }, + "path": "doc.get_document_style", + "primary_cli_path": "doc style get", + "product_id": "doc", + "reviewed": true, + "risk": "low", + "source": "reviewed_command_registry", + "title": "读取文档封面/背景 (只读)", + "use_when": [ + "用户说 看下文档现在的封面/背景" + ] + }, "doc.import_get": { "agent_metadata_source": "embedded-skill-metadata", "agent_source_refs": [ @@ -32755,6 +33172,2051 @@ "当你在做重大改动前后、想手动打一个可回滚的版本存档点时使用;输入 node,会实际为该文档保存一个当前内容的历史版本快照。" ] }, + "doc.style_background_clear": { + "agent_metadata_source": "embedded-skill-metadata", + "agent_source_refs": [ + "CommandRegistry:canonical_path=doc.style_background_clear", + "cobra-help:dws doc style background clear", + "internal/cli/schema_hints/metadata/doc.json", + "internal/cli/schema_hints/selection/doc.json", + "wukong-develop:wukong/products" + ], + "agent_summary": "清除钉钉文档背景", + "agent_summary_source": "dws-agent-selection/doc", + "availability": "available", + "avoid_when": [ + "设置背景用 background set" + ], + "canonical_path": "doc.style_background_clear", + "cli_name": "clear", + "cli_path": "doc style background clear", + "confirmation": "not_required", + "description": "清除文档背景", + "display": "钉钉文档管理", + "effect": "write", + "effect_source": "agent-hint", + "examples": [ + "dws doc style background clear --node \u003cDOC_ID\u003e --format json" + ], + "field_provenance": { + "agent_summary": { + "candidates": [ + { + "precedence": "reviewed_explicit", + "review_reason": "人工审阅:对照悟空 develop 同名命令实现、Cobra Long/Example 与 Runtime 确认门禁移植;不改变命令身份、参数契约或接口绑定。", + "selected": true, + "source": "internal/cli/schema_hints/selection/doc.json", + "value": "清除钉钉文档背景" + } + ], + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "人工审阅:对照悟空 develop 同名命令实现、Cobra Long/Example 与 Runtime 确认门禁移植;不改变命令身份、参数契约或接口绑定。", + "source": "internal/cli/schema_hints/selection/doc.json", + "value": "清除钉钉文档背景" + }, + "availability": { + "candidates": [ + { + "precedence": "reviewed_explicit", + "review_reason": "dws-tool-metadata/doc marks this tool as reviewed", + "selected": true, + "source": "internal/cli/schema_hints/metadata/doc.json", + "value": "available" + } + ], + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "dws-tool-metadata/doc marks this tool as reviewed", + "source": "internal/cli/schema_hints/metadata/doc.json", + "value": "available" + }, + "avoid_when": { + "candidates": [ + { + "precedence": "reviewed_explicit", + "review_reason": "人工审阅:对照悟空 develop 同名命令实现、Cobra Long/Example 与 Runtime 确认门禁移植;不改变命令身份、参数契约或接口绑定。", + "selected": true, + "source": "internal/cli/schema_hints/selection/doc.json", + "value": [ + "设置背景用 background set" + ] + } + ], + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "人工审阅:对照悟空 develop 同名命令实现、Cobra Long/Example 与 Runtime 确认门禁移植;不改变命令身份、参数契约或接口绑定。", + "source": "internal/cli/schema_hints/selection/doc.json", + "value": [ + "设置背景用 background set" + ] + }, + "canonical_path": { + "candidates": [ + { + "precedence": "command_registry", + "selected": true, + "source": "reviewed_command_registry", + "source_ref": "doc style background clear", + "value": "doc.style_background_clear" + } + ], + "precedence": "command_registry", + "resolution": "registry_identity", + "source": "reviewed_command_registry", + "source_ref": "doc style background clear", + "value": "doc.style_background_clear" + }, + "confirmation": { + "candidates": [ + { + "precedence": "reviewed_explicit", + "review_reason": "dws-tool-metadata/doc marks this tool as reviewed", + "selected": true, + "source": "internal/cli/schema_hints/metadata/doc.json", + "value": "not_required" + } + ], + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "dws-tool-metadata/doc marks this tool as reviewed", + "source": "internal/cli/schema_hints/metadata/doc.json", + "value": "not_required" + }, + "description": { + "candidates": [ + { + "precedence": "cobra_help", + "selected": true, + "source": "cobra_help", + "value": "清除文档背景" + } + ], + "precedence": "cobra_help", + "resolution": "highest_precedence", + "source": "cobra_help", + "value": "清除文档背景" + }, + "effect": { + "candidates": [ + { + "precedence": "reviewed_explicit", + "review_reason": "dws-tool-metadata/doc marks this tool as reviewed", + "selected": true, + "source": "internal/cli/schema_hints/metadata/doc.json", + "value": "write" + } + ], + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "dws-tool-metadata/doc marks this tool as reviewed", + "source": "internal/cli/schema_hints/metadata/doc.json", + "value": "write" + }, + "examples": { + "candidates": [ + { + "precedence": "reviewed_explicit", + "review_reason": "人工审阅:对照悟空 develop 同名命令实现、Cobra Long/Example 与 Runtime 确认门禁移植;不改变命令身份、参数契约或接口绑定。", + "selected": true, + "source": "internal/cli/schema_hints/selection/doc.json", + "value": [ + "dws doc style background clear --node \u003cDOC_ID\u003e --format json" + ] + } + ], + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "人工审阅:对照悟空 develop 同名命令实现、Cobra Long/Example 与 Runtime 确认门禁移植;不改变命令身份、参数契约或接口绑定。", + "source": "internal/cli/schema_hints/selection/doc.json", + "value": [ + "dws doc style background clear --node \u003cDOC_ID\u003e --format json" + ] + }, + "idempotency": { + "candidates": [ + { + "precedence": "reviewed_explicit", + "review_reason": "dws-tool-metadata/doc marks this tool as reviewed", + "selected": true, + "source": "internal/cli/schema_hints/metadata/doc.json", + "value": "idempotent" + } + ], + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "dws-tool-metadata/doc marks this tool as reviewed", + "source": "internal/cli/schema_hints/metadata/doc.json", + "value": "idempotent" + }, + "interface_mode": { + "candidates": [ + { + "precedence": "reviewed_explicit", + "review_reason": "dws-tool-metadata/doc marks this tool as reviewed", + "selected": true, + "source": "internal/cli/schema_hints/metadata/doc.json", + "value": "composite" + } + ], + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "dws-tool-metadata/doc marks this tool as reviewed", + "source": "internal/cli/schema_hints/metadata/doc.json", + "value": "composite" + }, + "interface_reason": { + "candidates": [ + { + "precedence": "reviewed_explicit", + "review_reason": "dws-tool-metadata/doc marks this tool as reviewed", + "selected": true, + "source": "internal/cli/schema_hints/metadata/doc.json", + "value": "Reviewed unpinned remote adapter: this executable CLI wrapper calls a remote helper that is absent from the pinned MCP metadata snapshot; no single pinned semantically equivalent interface_ref can represent the command." + } + ], + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "dws-tool-metadata/doc marks this tool as reviewed", + "source": "internal/cli/schema_hints/metadata/doc.json", + "value": "Reviewed unpinned remote adapter: this executable CLI wrapper calls a remote helper that is absent from the pinned MCP metadata snapshot; no single pinned semantically equivalent interface_ref can represent the command." + }, + "interface_ref": { + "candidates": [ + { + "precedence": "reviewed_explicit", + "review_reason": "final interface mode composite forbids a direct MCP interface_ref", + "selected": true, + "source": "internal/cli/schema_hints/metadata/doc.json", + "value": null + } + ], + "precedence": "reviewed_explicit", + "resolution": "interface_disposition_matrix", + "review_reason": "final interface mode composite forbids a direct MCP interface_ref", + "source": "internal/cli/schema_hints/metadata/doc.json", + "value": null + }, + "reviewed": { + "candidates": [ + { + "precedence": "reviewed_explicit", + "review_reason": "dws-tool-metadata/doc marks this tool as reviewed", + "selected": true, + "source": "internal/cli/schema_hints/metadata/doc.json", + "value": true + }, + { + "precedence": "reviewed_explicit", + "review_reason": "人工审阅:对照悟空 develop 同名命令实现、Cobra Long/Example 与 Runtime 确认门禁移植;不改变命令身份、参数契约或接口绑定。", + "selected": false, + "source": "internal/cli/schema_hints/selection/doc.json", + "value": true + } + ], + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "dws-tool-metadata/doc marks this tool as reviewed", + "source": "internal/cli/schema_hints/metadata/doc.json", + "value": true + }, + "risk": { + "candidates": [ + { + "precedence": "reviewed_explicit", + "review_reason": "dws-tool-metadata/doc marks this tool as reviewed", + "selected": true, + "source": "internal/cli/schema_hints/metadata/doc.json", + "value": "low" + } + ], + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "dws-tool-metadata/doc marks this tool as reviewed", + "source": "internal/cli/schema_hints/metadata/doc.json", + "value": "low" + }, + "title": { + "candidates": [ + { + "precedence": "cobra_help", + "selected": true, + "source": "cobra_help", + "value": "清除文档背景" + } + ], + "precedence": "cobra_help", + "resolution": "highest_precedence", + "source": "cobra_help", + "value": "清除文档背景" + }, + "use_when": { + "candidates": [ + { + "precedence": "reviewed_explicit", + "review_reason": "人工审阅:对照悟空 develop 同名命令实现、Cobra Long/Example 与 Runtime 确认门禁移植;不改变命令身份、参数契约或接口绑定。", + "selected": true, + "source": "internal/cli/schema_hints/selection/doc.json", + "value": [ + "用户说 去掉文档背景色" + ] + } + ], + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "人工审阅:对照悟空 develop 同名命令实现、Cobra Long/Example 与 Runtime 确认门禁移植;不改变命令身份、参数契约或接口绑定。", + "source": "internal/cli/schema_hints/selection/doc.json", + "value": [ + "用户说 去掉文档背景色" + ] + } + }, + "group": "style.background", + "has_parameters": true, + "idempotency": "idempotent", + "interface_mode": "composite", + "interface_reason": "Reviewed unpinned remote adapter: this executable CLI wrapper calls a remote helper that is absent from the pinned MCP metadata snapshot; no single pinned semantically equivalent interface_ref can represent the command.", + "is_alias": false, + "name": "style_background_clear", + "parameter_count": 1, + "parameters": { + "node": { + "description": "目标文档标识,支持 URL 或 ID (必填)", + "field_provenance": { + "description": { + "candidates": [ + { + "precedence": "cobra_contract", + "selected": true, + "source": "cobra_usage", + "value": "目标文档标识,支持 URL 或 ID (必填)" + }, + { + "precedence": "default", + "selected": false, + "source": "default", + "value": "" + } + ], + "precedence": "cobra_contract", + "resolution": "highest_precedence", + "source": "cobra_usage", + "value": "目标文档标识,支持 URL 或 ID (必填)" + }, + "property": { + "candidates": [ + { + "precedence": "reviewed_mapping_exclusion", + "review_reason": "Reviewed unpinned adapter: doc.style_background_clear has no singular pinned interface_ref; --node is a CLI wrapper input and does not publish a direct interface property.", + "selected": true, + "source": "reviewed_mapping_exclusion", + "value": "" + }, + { + "precedence": "inference", + "selected": false, + "source": "flag_name_inference", + "value": "node" + } + ], + "precedence": "reviewed_mapping_exclusion", + "resolution": "highest_precedence", + "review_reason": "Reviewed unpinned adapter: doc.style_background_clear has no singular pinned interface_ref; --node is a CLI wrapper input and does not publish a direct interface property.", + "source": "reviewed_mapping_exclusion", + "value": "" + }, + "required": { + "candidates": [ + { + "precedence": "inference", + "selected": true, + "source": "usage_required_inference", + "value": true + }, + { + "precedence": "default", + "selected": false, + "source": "default", + "value": false + } + ], + "precedence": "inference", + "resolution": "highest_precedence", + "source": "usage_required_inference", + "value": true + }, + "required_when": { + "candidates": [ + { + "precedence": "default", + "selected": true, + "source": "default", + "value": "" + } + ], + "precedence": "default", + "resolution": "highest_precedence", + "source": "default", + "value": "" + }, + "type": { + "candidates": [ + { + "precedence": "cobra_contract", + "selected": true, + "source": "cobra_flag_type", + "value": "string" + } + ], + "precedence": "cobra_contract", + "resolution": "highest_precedence", + "source": "cobra_flag_type", + "value": "string" + } + }, + "required": true, + "type": "string" + } + }, + "path": "doc.style_background_clear", + "primary_cli_path": "doc style background clear", + "product_id": "doc", + "reviewed": true, + "risk": "low", + "source": "reviewed_command_registry", + "title": "清除文档背景", + "use_when": [ + "用户说 去掉文档背景色" + ] + }, + "doc.style_background_set": { + "agent_metadata_source": "embedded-skill-metadata", + "agent_source_refs": [ + "CommandRegistry:canonical_path=doc.style_background_set", + "cobra-help:dws doc style background set", + "internal/cli/schema_hints/metadata/doc.json", + "internal/cli/schema_hints/selection/doc.json", + "wukong-develop:wukong/products" + ], + "agent_summary": "设置钉钉文档背景纯色(#RRGGBB)", + "agent_summary_source": "dws-agent-selection/doc", + "availability": "available", + "avoid_when": [ + "背景不支持图片;封面用 cover set" + ], + "canonical_path": "doc.style_background_set", + "cli_name": "set", + "cli_path": "doc style background set", + "confirmation": "not_required", + "description": "为钉钉文档设置背景纯色(--color)。背景仅支持纯色,不支持背景图片上传(对齐前端能力)。", + "display": "钉钉文档管理", + "effect": "write", + "effect_source": "agent-hint", + "examples": [ + "dws doc style background set --node \u003cDOC_ID\u003e --color \"#E8F2FE\" --format json" + ], + "field_provenance": { + "agent_summary": { + "candidates": [ + { + "precedence": "reviewed_explicit", + "review_reason": "人工审阅:对照悟空 develop 同名命令实现、Cobra Long/Example 与 Runtime 确认门禁移植;不改变命令身份、参数契约或接口绑定。", + "selected": true, + "source": "internal/cli/schema_hints/selection/doc.json", + "value": "设置钉钉文档背景纯色(#RRGGBB)" + } + ], + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "人工审阅:对照悟空 develop 同名命令实现、Cobra Long/Example 与 Runtime 确认门禁移植;不改变命令身份、参数契约或接口绑定。", + "source": "internal/cli/schema_hints/selection/doc.json", + "value": "设置钉钉文档背景纯色(#RRGGBB)" + }, + "availability": { + "candidates": [ + { + "precedence": "reviewed_explicit", + "review_reason": "dws-tool-metadata/doc marks this tool as reviewed", + "selected": true, + "source": "internal/cli/schema_hints/metadata/doc.json", + "value": "available" + } + ], + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "dws-tool-metadata/doc marks this tool as reviewed", + "source": "internal/cli/schema_hints/metadata/doc.json", + "value": "available" + }, + "avoid_when": { + "candidates": [ + { + "precedence": "reviewed_explicit", + "review_reason": "人工审阅:对照悟空 develop 同名命令实现、Cobra Long/Example 与 Runtime 确认门禁移植;不改变命令身份、参数契约或接口绑定。", + "selected": true, + "source": "internal/cli/schema_hints/selection/doc.json", + "value": [ + "背景不支持图片;封面用 cover set" + ] + } + ], + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "人工审阅:对照悟空 develop 同名命令实现、Cobra Long/Example 与 Runtime 确认门禁移植;不改变命令身份、参数契约或接口绑定。", + "source": "internal/cli/schema_hints/selection/doc.json", + "value": [ + "背景不支持图片;封面用 cover set" + ] + }, + "canonical_path": { + "candidates": [ + { + "precedence": "command_registry", + "selected": true, + "source": "reviewed_command_registry", + "source_ref": "doc style background set", + "value": "doc.style_background_set" + } + ], + "precedence": "command_registry", + "resolution": "registry_identity", + "source": "reviewed_command_registry", + "source_ref": "doc style background set", + "value": "doc.style_background_set" + }, + "confirmation": { + "candidates": [ + { + "precedence": "reviewed_explicit", + "review_reason": "dws-tool-metadata/doc marks this tool as reviewed", + "selected": true, + "source": "internal/cli/schema_hints/metadata/doc.json", + "value": "not_required" + } + ], + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "dws-tool-metadata/doc marks this tool as reviewed", + "source": "internal/cli/schema_hints/metadata/doc.json", + "value": "not_required" + }, + "description": { + "candidates": [ + { + "precedence": "cobra_help", + "selected": true, + "source": "cobra_help", + "value": "为钉钉文档设置背景纯色(--color)。背景仅支持纯色,不支持背景图片上传(对齐前端能力)。" + } + ], + "precedence": "cobra_help", + "resolution": "highest_precedence", + "source": "cobra_help", + "value": "为钉钉文档设置背景纯色(--color)。背景仅支持纯色,不支持背景图片上传(对齐前端能力)。" + }, + "effect": { + "candidates": [ + { + "precedence": "reviewed_explicit", + "review_reason": "dws-tool-metadata/doc marks this tool as reviewed", + "selected": true, + "source": "internal/cli/schema_hints/metadata/doc.json", + "value": "write" + } + ], + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "dws-tool-metadata/doc marks this tool as reviewed", + "source": "internal/cli/schema_hints/metadata/doc.json", + "value": "write" + }, + "examples": { + "candidates": [ + { + "precedence": "reviewed_explicit", + "review_reason": "人工审阅:对照悟空 develop 同名命令实现、Cobra Long/Example 与 Runtime 确认门禁移植;不改变命令身份、参数契约或接口绑定。", + "selected": true, + "source": "internal/cli/schema_hints/selection/doc.json", + "value": [ + "dws doc style background set --node \u003cDOC_ID\u003e --color \"#E8F2FE\" --format json" + ] + } + ], + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "人工审阅:对照悟空 develop 同名命令实现、Cobra Long/Example 与 Runtime 确认门禁移植;不改变命令身份、参数契约或接口绑定。", + "source": "internal/cli/schema_hints/selection/doc.json", + "value": [ + "dws doc style background set --node \u003cDOC_ID\u003e --color \"#E8F2FE\" --format json" + ] + }, + "idempotency": { + "candidates": [ + { + "precedence": "reviewed_explicit", + "review_reason": "dws-tool-metadata/doc marks this tool as reviewed", + "selected": true, + "source": "internal/cli/schema_hints/metadata/doc.json", + "value": "idempotent" + } + ], + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "dws-tool-metadata/doc marks this tool as reviewed", + "source": "internal/cli/schema_hints/metadata/doc.json", + "value": "idempotent" + }, + "interface_mode": { + "candidates": [ + { + "precedence": "reviewed_explicit", + "review_reason": "dws-tool-metadata/doc marks this tool as reviewed", + "selected": true, + "source": "internal/cli/schema_hints/metadata/doc.json", + "value": "composite" + } + ], + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "dws-tool-metadata/doc marks this tool as reviewed", + "source": "internal/cli/schema_hints/metadata/doc.json", + "value": "composite" + }, + "interface_reason": { + "candidates": [ + { + "precedence": "reviewed_explicit", + "review_reason": "dws-tool-metadata/doc marks this tool as reviewed", + "selected": true, + "source": "internal/cli/schema_hints/metadata/doc.json", + "value": "Reviewed unpinned remote adapter: this executable CLI wrapper calls a remote helper that is absent from the pinned MCP metadata snapshot; no single pinned semantically equivalent interface_ref can represent the command." + } + ], + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "dws-tool-metadata/doc marks this tool as reviewed", + "source": "internal/cli/schema_hints/metadata/doc.json", + "value": "Reviewed unpinned remote adapter: this executable CLI wrapper calls a remote helper that is absent from the pinned MCP metadata snapshot; no single pinned semantically equivalent interface_ref can represent the command." + }, + "interface_ref": { + "candidates": [ + { + "precedence": "reviewed_explicit", + "review_reason": "final interface mode composite forbids a direct MCP interface_ref", + "selected": true, + "source": "internal/cli/schema_hints/metadata/doc.json", + "value": null + } + ], + "precedence": "reviewed_explicit", + "resolution": "interface_disposition_matrix", + "review_reason": "final interface mode composite forbids a direct MCP interface_ref", + "source": "internal/cli/schema_hints/metadata/doc.json", + "value": null + }, + "reviewed": { + "candidates": [ + { + "precedence": "reviewed_explicit", + "review_reason": "dws-tool-metadata/doc marks this tool as reviewed", + "selected": true, + "source": "internal/cli/schema_hints/metadata/doc.json", + "value": true + }, + { + "precedence": "reviewed_explicit", + "review_reason": "人工审阅:对照悟空 develop 同名命令实现、Cobra Long/Example 与 Runtime 确认门禁移植;不改变命令身份、参数契约或接口绑定。", + "selected": false, + "source": "internal/cli/schema_hints/selection/doc.json", + "value": true + } + ], + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "dws-tool-metadata/doc marks this tool as reviewed", + "source": "internal/cli/schema_hints/metadata/doc.json", + "value": true + }, + "risk": { + "candidates": [ + { + "precedence": "reviewed_explicit", + "review_reason": "dws-tool-metadata/doc marks this tool as reviewed", + "selected": true, + "source": "internal/cli/schema_hints/metadata/doc.json", + "value": "low" + } + ], + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "dws-tool-metadata/doc marks this tool as reviewed", + "source": "internal/cli/schema_hints/metadata/doc.json", + "value": "low" + }, + "title": { + "candidates": [ + { + "precedence": "cobra_help", + "selected": true, + "source": "cobra_help", + "value": "设置文档背景纯色" + } + ], + "precedence": "cobra_help", + "resolution": "highest_precedence", + "source": "cobra_help", + "value": "设置文档背景纯色" + }, + "use_when": { + "candidates": [ + { + "precedence": "reviewed_explicit", + "review_reason": "人工审阅:对照悟空 develop 同名命令实现、Cobra Long/Example 与 Runtime 确认门禁移植;不改变命令身份、参数契约或接口绑定。", + "selected": true, + "source": "internal/cli/schema_hints/selection/doc.json", + "value": [ + "用户说 给文档设置背景色" + ] + } + ], + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "人工审阅:对照悟空 develop 同名命令实现、Cobra Long/Example 与 Runtime 确认门禁移植;不改变命令身份、参数契约或接口绑定。", + "source": "internal/cli/schema_hints/selection/doc.json", + "value": [ + "用户说 给文档设置背景色" + ] + } + }, + "group": "style.background", + "has_parameters": true, + "idempotency": "idempotent", + "interface_mode": "composite", + "interface_reason": "Reviewed unpinned remote adapter: this executable CLI wrapper calls a remote helper that is absent from the pinned MCP metadata snapshot; no single pinned semantically equivalent interface_ref can represent the command.", + "is_alias": false, + "name": "style_background_set", + "parameter_count": 2, + "parameters": { + "color": { + "description": "背景纯色,如 #E8F2FE (必填)", + "field_provenance": { + "description": { + "candidates": [ + { + "precedence": "cobra_contract", + "selected": true, + "source": "cobra_usage", + "value": "背景纯色,如 #E8F2FE (必填)" + }, + { + "precedence": "default", + "selected": false, + "source": "default", + "value": "" + } + ], + "precedence": "cobra_contract", + "resolution": "highest_precedence", + "source": "cobra_usage", + "value": "背景纯色,如 #E8F2FE (必填)" + }, + "property": { + "candidates": [ + { + "precedence": "reviewed_mapping_exclusion", + "review_reason": "Reviewed unpinned adapter: doc.style_background_set has no singular pinned interface_ref; --color is a CLI wrapper input and does not publish a direct interface property.", + "selected": true, + "source": "reviewed_mapping_exclusion", + "value": "" + }, + { + "precedence": "inference", + "selected": false, + "source": "flag_name_inference", + "value": "color" + } + ], + "precedence": "reviewed_mapping_exclusion", + "resolution": "highest_precedence", + "review_reason": "Reviewed unpinned adapter: doc.style_background_set has no singular pinned interface_ref; --color is a CLI wrapper input and does not publish a direct interface property.", + "source": "reviewed_mapping_exclusion", + "value": "" + }, + "required": { + "candidates": [ + { + "precedence": "inference", + "selected": true, + "source": "usage_required_inference", + "value": true + }, + { + "precedence": "default", + "selected": false, + "source": "default", + "value": false + } + ], + "precedence": "inference", + "resolution": "highest_precedence", + "source": "usage_required_inference", + "value": true + }, + "required_when": { + "candidates": [ + { + "precedence": "default", + "selected": true, + "source": "default", + "value": "" + } + ], + "precedence": "default", + "resolution": "highest_precedence", + "source": "default", + "value": "" + }, + "type": { + "candidates": [ + { + "precedence": "cobra_contract", + "selected": true, + "source": "cobra_flag_type", + "value": "string" + } + ], + "precedence": "cobra_contract", + "resolution": "highest_precedence", + "source": "cobra_flag_type", + "value": "string" + } + }, + "required": true, + "type": "string" + }, + "node": { + "description": "目标文档标识,支持 URL 或 ID (必填)", + "field_provenance": { + "description": { + "candidates": [ + { + "precedence": "cobra_contract", + "selected": true, + "source": "cobra_usage", + "value": "目标文档标识,支持 URL 或 ID (必填)" + }, + { + "precedence": "default", + "selected": false, + "source": "default", + "value": "" + } + ], + "precedence": "cobra_contract", + "resolution": "highest_precedence", + "source": "cobra_usage", + "value": "目标文档标识,支持 URL 或 ID (必填)" + }, + "property": { + "candidates": [ + { + "precedence": "reviewed_mapping_exclusion", + "review_reason": "Reviewed unpinned adapter: doc.style_background_set has no singular pinned interface_ref; --node is a CLI wrapper input and does not publish a direct interface property.", + "selected": true, + "source": "reviewed_mapping_exclusion", + "value": "" + }, + { + "precedence": "inference", + "selected": false, + "source": "flag_name_inference", + "value": "node" + } + ], + "precedence": "reviewed_mapping_exclusion", + "resolution": "highest_precedence", + "review_reason": "Reviewed unpinned adapter: doc.style_background_set has no singular pinned interface_ref; --node is a CLI wrapper input and does not publish a direct interface property.", + "source": "reviewed_mapping_exclusion", + "value": "" + }, + "required": { + "candidates": [ + { + "precedence": "inference", + "selected": true, + "source": "usage_required_inference", + "value": true + }, + { + "precedence": "default", + "selected": false, + "source": "default", + "value": false + } + ], + "precedence": "inference", + "resolution": "highest_precedence", + "source": "usage_required_inference", + "value": true + }, + "required_when": { + "candidates": [ + { + "precedence": "default", + "selected": true, + "source": "default", + "value": "" + } + ], + "precedence": "default", + "resolution": "highest_precedence", + "source": "default", + "value": "" + }, + "type": { + "candidates": [ + { + "precedence": "cobra_contract", + "selected": true, + "source": "cobra_flag_type", + "value": "string" + } + ], + "precedence": "cobra_contract", + "resolution": "highest_precedence", + "source": "cobra_flag_type", + "value": "string" + } + }, + "required": true, + "type": "string" + } + }, + "path": "doc.style_background_set", + "primary_cli_path": "doc style background set", + "product_id": "doc", + "reviewed": true, + "risk": "low", + "source": "reviewed_command_registry", + "title": "设置文档背景纯色", + "use_when": [ + "用户说 给文档设置背景色" + ] + }, + "doc.style_cover_clear": { + "agent_metadata_source": "embedded-skill-metadata", + "agent_source_refs": [ + "CommandRegistry:canonical_path=doc.style_cover_clear", + "cobra-help:dws doc style cover clear", + "internal/cli/schema_hints/metadata/doc.json", + "internal/cli/schema_hints/selection/doc.json", + "wukong-develop:wukong/products" + ], + "agent_summary": "移除钉钉文档封面", + "agent_summary_source": "dws-agent-selection/doc", + "availability": "available", + "avoid_when": [ + "设置封面用 cover set" + ], + "canonical_path": "doc.style_cover_clear", + "cli_name": "clear", + "cli_path": "doc style cover clear", + "confirmation": "not_required", + "description": "移除文档封面", + "display": "钉钉文档管理", + "effect": "write", + "effect_source": "agent-hint", + "examples": [ + "dws doc style cover clear --node \u003cDOC_ID\u003e --format json" + ], + "field_provenance": { + "agent_summary": { + "candidates": [ + { + "precedence": "reviewed_explicit", + "review_reason": "人工审阅:对照悟空 develop 同名命令实现、Cobra Long/Example 与 Runtime 确认门禁移植;不改变命令身份、参数契约或接口绑定。", + "selected": true, + "source": "internal/cli/schema_hints/selection/doc.json", + "value": "移除钉钉文档封面" + } + ], + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "人工审阅:对照悟空 develop 同名命令实现、Cobra Long/Example 与 Runtime 确认门禁移植;不改变命令身份、参数契约或接口绑定。", + "source": "internal/cli/schema_hints/selection/doc.json", + "value": "移除钉钉文档封面" + }, + "availability": { + "candidates": [ + { + "precedence": "reviewed_explicit", + "review_reason": "dws-tool-metadata/doc marks this tool as reviewed", + "selected": true, + "source": "internal/cli/schema_hints/metadata/doc.json", + "value": "available" + } + ], + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "dws-tool-metadata/doc marks this tool as reviewed", + "source": "internal/cli/schema_hints/metadata/doc.json", + "value": "available" + }, + "avoid_when": { + "candidates": [ + { + "precedence": "reviewed_explicit", + "review_reason": "人工审阅:对照悟空 develop 同名命令实现、Cobra Long/Example 与 Runtime 确认门禁移植;不改变命令身份、参数契约或接口绑定。", + "selected": true, + "source": "internal/cli/schema_hints/selection/doc.json", + "value": [ + "设置封面用 cover set" + ] + } + ], + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "人工审阅:对照悟空 develop 同名命令实现、Cobra Long/Example 与 Runtime 确认门禁移植;不改变命令身份、参数契约或接口绑定。", + "source": "internal/cli/schema_hints/selection/doc.json", + "value": [ + "设置封面用 cover set" + ] + }, + "canonical_path": { + "candidates": [ + { + "precedence": "command_registry", + "selected": true, + "source": "reviewed_command_registry", + "source_ref": "doc style cover clear", + "value": "doc.style_cover_clear" + } + ], + "precedence": "command_registry", + "resolution": "registry_identity", + "source": "reviewed_command_registry", + "source_ref": "doc style cover clear", + "value": "doc.style_cover_clear" + }, + "confirmation": { + "candidates": [ + { + "precedence": "reviewed_explicit", + "review_reason": "dws-tool-metadata/doc marks this tool as reviewed", + "selected": true, + "source": "internal/cli/schema_hints/metadata/doc.json", + "value": "not_required" + } + ], + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "dws-tool-metadata/doc marks this tool as reviewed", + "source": "internal/cli/schema_hints/metadata/doc.json", + "value": "not_required" + }, + "description": { + "candidates": [ + { + "precedence": "cobra_help", + "selected": true, + "source": "cobra_help", + "value": "移除文档封面" + } + ], + "precedence": "cobra_help", + "resolution": "highest_precedence", + "source": "cobra_help", + "value": "移除文档封面" + }, + "effect": { + "candidates": [ + { + "precedence": "reviewed_explicit", + "review_reason": "dws-tool-metadata/doc marks this tool as reviewed", + "selected": true, + "source": "internal/cli/schema_hints/metadata/doc.json", + "value": "write" + } + ], + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "dws-tool-metadata/doc marks this tool as reviewed", + "source": "internal/cli/schema_hints/metadata/doc.json", + "value": "write" + }, + "examples": { + "candidates": [ + { + "precedence": "reviewed_explicit", + "review_reason": "人工审阅:对照悟空 develop 同名命令实现、Cobra Long/Example 与 Runtime 确认门禁移植;不改变命令身份、参数契约或接口绑定。", + "selected": true, + "source": "internal/cli/schema_hints/selection/doc.json", + "value": [ + "dws doc style cover clear --node \u003cDOC_ID\u003e --format json" + ] + } + ], + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "人工审阅:对照悟空 develop 同名命令实现、Cobra Long/Example 与 Runtime 确认门禁移植;不改变命令身份、参数契约或接口绑定。", + "source": "internal/cli/schema_hints/selection/doc.json", + "value": [ + "dws doc style cover clear --node \u003cDOC_ID\u003e --format json" + ] + }, + "idempotency": { + "candidates": [ + { + "precedence": "reviewed_explicit", + "review_reason": "dws-tool-metadata/doc marks this tool as reviewed", + "selected": true, + "source": "internal/cli/schema_hints/metadata/doc.json", + "value": "idempotent" + } + ], + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "dws-tool-metadata/doc marks this tool as reviewed", + "source": "internal/cli/schema_hints/metadata/doc.json", + "value": "idempotent" + }, + "interface_mode": { + "candidates": [ + { + "precedence": "reviewed_explicit", + "review_reason": "dws-tool-metadata/doc marks this tool as reviewed", + "selected": true, + "source": "internal/cli/schema_hints/metadata/doc.json", + "value": "composite" + } + ], + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "dws-tool-metadata/doc marks this tool as reviewed", + "source": "internal/cli/schema_hints/metadata/doc.json", + "value": "composite" + }, + "interface_reason": { + "candidates": [ + { + "precedence": "reviewed_explicit", + "review_reason": "dws-tool-metadata/doc marks this tool as reviewed", + "selected": true, + "source": "internal/cli/schema_hints/metadata/doc.json", + "value": "Reviewed unpinned remote adapter: this executable CLI wrapper calls a remote helper that is absent from the pinned MCP metadata snapshot; no single pinned semantically equivalent interface_ref can represent the command." + } + ], + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "dws-tool-metadata/doc marks this tool as reviewed", + "source": "internal/cli/schema_hints/metadata/doc.json", + "value": "Reviewed unpinned remote adapter: this executable CLI wrapper calls a remote helper that is absent from the pinned MCP metadata snapshot; no single pinned semantically equivalent interface_ref can represent the command." + }, + "interface_ref": { + "candidates": [ + { + "precedence": "reviewed_explicit", + "review_reason": "final interface mode composite forbids a direct MCP interface_ref", + "selected": true, + "source": "internal/cli/schema_hints/metadata/doc.json", + "value": null + } + ], + "precedence": "reviewed_explicit", + "resolution": "interface_disposition_matrix", + "review_reason": "final interface mode composite forbids a direct MCP interface_ref", + "source": "internal/cli/schema_hints/metadata/doc.json", + "value": null + }, + "reviewed": { + "candidates": [ + { + "precedence": "reviewed_explicit", + "review_reason": "dws-tool-metadata/doc marks this tool as reviewed", + "selected": true, + "source": "internal/cli/schema_hints/metadata/doc.json", + "value": true + }, + { + "precedence": "reviewed_explicit", + "review_reason": "人工审阅:对照悟空 develop 同名命令实现、Cobra Long/Example 与 Runtime 确认门禁移植;不改变命令身份、参数契约或接口绑定。", + "selected": false, + "source": "internal/cli/schema_hints/selection/doc.json", + "value": true + } + ], + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "dws-tool-metadata/doc marks this tool as reviewed", + "source": "internal/cli/schema_hints/metadata/doc.json", + "value": true + }, + "risk": { + "candidates": [ + { + "precedence": "reviewed_explicit", + "review_reason": "dws-tool-metadata/doc marks this tool as reviewed", + "selected": true, + "source": "internal/cli/schema_hints/metadata/doc.json", + "value": "low" + } + ], + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "dws-tool-metadata/doc marks this tool as reviewed", + "source": "internal/cli/schema_hints/metadata/doc.json", + "value": "low" + }, + "title": { + "candidates": [ + { + "precedence": "cobra_help", + "selected": true, + "source": "cobra_help", + "value": "移除文档封面" + } + ], + "precedence": "cobra_help", + "resolution": "highest_precedence", + "source": "cobra_help", + "value": "移除文档封面" + }, + "use_when": { + "candidates": [ + { + "precedence": "reviewed_explicit", + "review_reason": "人工审阅:对照悟空 develop 同名命令实现、Cobra Long/Example 与 Runtime 确认门禁移植;不改变命令身份、参数契约或接口绑定。", + "selected": true, + "source": "internal/cli/schema_hints/selection/doc.json", + "value": [ + "用户说 去掉文档封面" + ] + } + ], + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "人工审阅:对照悟空 develop 同名命令实现、Cobra Long/Example 与 Runtime 确认门禁移植;不改变命令身份、参数契约或接口绑定。", + "source": "internal/cli/schema_hints/selection/doc.json", + "value": [ + "用户说 去掉文档封面" + ] + } + }, + "group": "style.cover", + "has_parameters": true, + "idempotency": "idempotent", + "interface_mode": "composite", + "interface_reason": "Reviewed unpinned remote adapter: this executable CLI wrapper calls a remote helper that is absent from the pinned MCP metadata snapshot; no single pinned semantically equivalent interface_ref can represent the command.", + "is_alias": false, + "name": "style_cover_clear", + "parameter_count": 1, + "parameters": { + "node": { + "description": "目标文档标识,支持 URL 或 ID (必填)", + "field_provenance": { + "description": { + "candidates": [ + { + "precedence": "cobra_contract", + "selected": true, + "source": "cobra_usage", + "value": "目标文档标识,支持 URL 或 ID (必填)" + }, + { + "precedence": "default", + "selected": false, + "source": "default", + "value": "" + } + ], + "precedence": "cobra_contract", + "resolution": "highest_precedence", + "source": "cobra_usage", + "value": "目标文档标识,支持 URL 或 ID (必填)" + }, + "property": { + "candidates": [ + { + "precedence": "reviewed_mapping_exclusion", + "review_reason": "Reviewed unpinned adapter: doc.style_cover_clear has no singular pinned interface_ref; --node is a CLI wrapper input and does not publish a direct interface property.", + "selected": true, + "source": "reviewed_mapping_exclusion", + "value": "" + }, + { + "precedence": "inference", + "selected": false, + "source": "flag_name_inference", + "value": "node" + } + ], + "precedence": "reviewed_mapping_exclusion", + "resolution": "highest_precedence", + "review_reason": "Reviewed unpinned adapter: doc.style_cover_clear has no singular pinned interface_ref; --node is a CLI wrapper input and does not publish a direct interface property.", + "source": "reviewed_mapping_exclusion", + "value": "" + }, + "required": { + "candidates": [ + { + "precedence": "inference", + "selected": true, + "source": "usage_required_inference", + "value": true + }, + { + "precedence": "default", + "selected": false, + "source": "default", + "value": false + } + ], + "precedence": "inference", + "resolution": "highest_precedence", + "source": "usage_required_inference", + "value": true + }, + "required_when": { + "candidates": [ + { + "precedence": "default", + "selected": true, + "source": "default", + "value": "" + } + ], + "precedence": "default", + "resolution": "highest_precedence", + "source": "default", + "value": "" + }, + "type": { + "candidates": [ + { + "precedence": "cobra_contract", + "selected": true, + "source": "cobra_flag_type", + "value": "string" + } + ], + "precedence": "cobra_contract", + "resolution": "highest_precedence", + "source": "cobra_flag_type", + "value": "string" + } + }, + "required": true, + "type": "string" + } + }, + "path": "doc.style_cover_clear", + "primary_cli_path": "doc style cover clear", + "product_id": "doc", + "reviewed": true, + "risk": "low", + "source": "reviewed_command_registry", + "title": "移除文档封面", + "use_when": [ + "用户说 去掉文档封面" + ] + }, + "doc.style_cover_set": { + "agent_metadata_source": "embedded-skill-metadata", + "agent_source_refs": [ + "CommandRegistry:canonical_path=doc.style_cover_set", + "cobra-help:dws doc style cover set", + "internal/cli/schema_hints/metadata/doc.json", + "internal/cli/schema_hints/selection/doc.json", + "wukong-develop:wukong/products" + ], + "agent_summary": "设置钉钉文档顶部封面图(外链或本地图片上传)", + "agent_summary_source": "dws-agent-selection/doc", + "availability": "available", + "avoid_when": [ + "读取当前封面用 doc style get;移除封面用 cover clear" + ], + "canonical_path": "doc.style_cover_set", + "cli_name": "set", + "cli_path": "doc style cover set", + "confirmation": "not_required", + "description": "为钉钉文档设置顶部封面图,可指定竖直显示位置。图片来源二选一:--image 外链或 --file 本地文件。", + "display": "钉钉文档管理", + "effect": "write", + "effect_source": "agent-hint", + "examples": [ + "dws doc style cover set --node \u003cDOC_ID\u003e --image https://img.example.com/cover.png --format json" + ], + "field_provenance": { + "agent_summary": { + "candidates": [ + { + "precedence": "reviewed_explicit", + "review_reason": "人工审阅:对照悟空 develop 同名命令实现、Cobra Long/Example 与 Runtime 确认门禁移植;不改变命令身份、参数契约或接口绑定。", + "selected": true, + "source": "internal/cli/schema_hints/selection/doc.json", + "value": "设置钉钉文档顶部封面图(外链或本地图片上传)" + } + ], + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "人工审阅:对照悟空 develop 同名命令实现、Cobra Long/Example 与 Runtime 确认门禁移植;不改变命令身份、参数契约或接口绑定。", + "source": "internal/cli/schema_hints/selection/doc.json", + "value": "设置钉钉文档顶部封面图(外链或本地图片上传)" + }, + "availability": { + "candidates": [ + { + "precedence": "reviewed_explicit", + "review_reason": "dws-tool-metadata/doc marks this tool as reviewed", + "selected": true, + "source": "internal/cli/schema_hints/metadata/doc.json", + "value": "available" + } + ], + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "dws-tool-metadata/doc marks this tool as reviewed", + "source": "internal/cli/schema_hints/metadata/doc.json", + "value": "available" + }, + "avoid_when": { + "candidates": [ + { + "precedence": "reviewed_explicit", + "review_reason": "人工审阅:对照悟空 develop 同名命令实现、Cobra Long/Example 与 Runtime 确认门禁移植;不改变命令身份、参数契约或接口绑定。", + "selected": true, + "source": "internal/cli/schema_hints/selection/doc.json", + "value": [ + "读取当前封面用 doc style get;移除封面用 cover clear" + ] + } + ], + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "人工审阅:对照悟空 develop 同名命令实现、Cobra Long/Example 与 Runtime 确认门禁移植;不改变命令身份、参数契约或接口绑定。", + "source": "internal/cli/schema_hints/selection/doc.json", + "value": [ + "读取当前封面用 doc style get;移除封面用 cover clear" + ] + }, + "canonical_path": { + "candidates": [ + { + "precedence": "command_registry", + "selected": true, + "source": "reviewed_command_registry", + "source_ref": "doc style cover set", + "value": "doc.style_cover_set" + } + ], + "precedence": "command_registry", + "resolution": "registry_identity", + "source": "reviewed_command_registry", + "source_ref": "doc style cover set", + "value": "doc.style_cover_set" + }, + "confirmation": { + "candidates": [ + { + "precedence": "reviewed_explicit", + "review_reason": "dws-tool-metadata/doc marks this tool as reviewed", + "selected": true, + "source": "internal/cli/schema_hints/metadata/doc.json", + "value": "not_required" + } + ], + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "dws-tool-metadata/doc marks this tool as reviewed", + "source": "internal/cli/schema_hints/metadata/doc.json", + "value": "not_required" + }, + "description": { + "candidates": [ + { + "precedence": "cobra_help", + "selected": true, + "source": "cobra_help", + "value": "为钉钉文档设置顶部封面图,可指定竖直显示位置。图片来源二选一:--image 外链或 --file 本地文件。" + } + ], + "precedence": "cobra_help", + "resolution": "highest_precedence", + "source": "cobra_help", + "value": "为钉钉文档设置顶部封面图,可指定竖直显示位置。图片来源二选一:--image 外链或 --file 本地文件。" + }, + "effect": { + "candidates": [ + { + "precedence": "reviewed_explicit", + "review_reason": "dws-tool-metadata/doc marks this tool as reviewed", + "selected": true, + "source": "internal/cli/schema_hints/metadata/doc.json", + "value": "write" + } + ], + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "dws-tool-metadata/doc marks this tool as reviewed", + "source": "internal/cli/schema_hints/metadata/doc.json", + "value": "write" + }, + "examples": { + "candidates": [ + { + "precedence": "reviewed_explicit", + "review_reason": "人工审阅:对照悟空 develop 同名命令实现、Cobra Long/Example 与 Runtime 确认门禁移植;不改变命令身份、参数契约或接口绑定。", + "selected": true, + "source": "internal/cli/schema_hints/selection/doc.json", + "value": [ + "dws doc style cover set --node \u003cDOC_ID\u003e --image https://img.example.com/cover.png --format json" + ] + } + ], + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "人工审阅:对照悟空 develop 同名命令实现、Cobra Long/Example 与 Runtime 确认门禁移植;不改变命令身份、参数契约或接口绑定。", + "source": "internal/cli/schema_hints/selection/doc.json", + "value": [ + "dws doc style cover set --node \u003cDOC_ID\u003e --image https://img.example.com/cover.png --format json" + ] + }, + "idempotency": { + "candidates": [ + { + "precedence": "reviewed_explicit", + "review_reason": "dws-tool-metadata/doc marks this tool as reviewed", + "selected": true, + "source": "internal/cli/schema_hints/metadata/doc.json", + "value": "idempotent" + } + ], + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "dws-tool-metadata/doc marks this tool as reviewed", + "source": "internal/cli/schema_hints/metadata/doc.json", + "value": "idempotent" + }, + "interface_mode": { + "candidates": [ + { + "precedence": "reviewed_explicit", + "review_reason": "dws-tool-metadata/doc marks this tool as reviewed", + "selected": true, + "source": "internal/cli/schema_hints/metadata/doc.json", + "value": "composite" + } + ], + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "dws-tool-metadata/doc marks this tool as reviewed", + "source": "internal/cli/schema_hints/metadata/doc.json", + "value": "composite" + }, + "interface_reason": { + "candidates": [ + { + "precedence": "reviewed_explicit", + "review_reason": "dws-tool-metadata/doc marks this tool as reviewed", + "selected": true, + "source": "internal/cli/schema_hints/metadata/doc.json", + "value": "Reviewed unpinned remote adapter: this executable CLI wrapper calls a remote helper that is absent from the pinned MCP metadata snapshot; no single pinned semantically equivalent interface_ref can represent the command." + } + ], + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "dws-tool-metadata/doc marks this tool as reviewed", + "source": "internal/cli/schema_hints/metadata/doc.json", + "value": "Reviewed unpinned remote adapter: this executable CLI wrapper calls a remote helper that is absent from the pinned MCP metadata snapshot; no single pinned semantically equivalent interface_ref can represent the command." + }, + "interface_ref": { + "candidates": [ + { + "precedence": "reviewed_explicit", + "review_reason": "final interface mode composite forbids a direct MCP interface_ref", + "selected": true, + "source": "internal/cli/schema_hints/metadata/doc.json", + "value": null + } + ], + "precedence": "reviewed_explicit", + "resolution": "interface_disposition_matrix", + "review_reason": "final interface mode composite forbids a direct MCP interface_ref", + "source": "internal/cli/schema_hints/metadata/doc.json", + "value": null + }, + "reviewed": { + "candidates": [ + { + "precedence": "reviewed_explicit", + "review_reason": "dws-tool-metadata/doc marks this tool as reviewed", + "selected": true, + "source": "internal/cli/schema_hints/metadata/doc.json", + "value": true + }, + { + "precedence": "reviewed_explicit", + "review_reason": "人工审阅:对照悟空 develop 同名命令实现、Cobra Long/Example 与 Runtime 确认门禁移植;不改变命令身份、参数契约或接口绑定。", + "selected": false, + "source": "internal/cli/schema_hints/selection/doc.json", + "value": true + } + ], + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "dws-tool-metadata/doc marks this tool as reviewed", + "source": "internal/cli/schema_hints/metadata/doc.json", + "value": true + }, + "risk": { + "candidates": [ + { + "precedence": "reviewed_explicit", + "review_reason": "dws-tool-metadata/doc marks this tool as reviewed", + "selected": true, + "source": "internal/cli/schema_hints/metadata/doc.json", + "value": "low" + } + ], + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "dws-tool-metadata/doc marks this tool as reviewed", + "source": "internal/cli/schema_hints/metadata/doc.json", + "value": "low" + }, + "title": { + "candidates": [ + { + "precedence": "cobra_help", + "selected": true, + "source": "cobra_help", + "value": "设置文档封面" + } + ], + "precedence": "cobra_help", + "resolution": "highest_precedence", + "source": "cobra_help", + "value": "设置文档封面" + }, + "use_when": { + "candidates": [ + { + "precedence": "reviewed_explicit", + "review_reason": "人工审阅:对照悟空 develop 同名命令实现、Cobra Long/Example 与 Runtime 确认门禁移植;不改变命令身份、参数契约或接口绑定。", + "selected": true, + "source": "internal/cli/schema_hints/selection/doc.json", + "value": [ + "用户说 给文档设置封面/换个封面图" + ] + } + ], + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "人工审阅:对照悟空 develop 同名命令实现、Cobra Long/Example 与 Runtime 确认门禁移植;不改变命令身份、参数契约或接口绑定。", + "source": "internal/cli/schema_hints/selection/doc.json", + "value": [ + "用户说 给文档设置封面/换个封面图" + ] + } + }, + "group": "style.cover", + "has_parameters": true, + "idempotency": "idempotent", + "interface_mode": "composite", + "interface_reason": "Reviewed unpinned remote adapter: this executable CLI wrapper calls a remote helper that is absent from the pinned MCP metadata snapshot; no single pinned semantically equivalent interface_ref can represent the command.", + "is_alias": false, + "name": "style_cover_set", + "parameter_count": 4, + "parameters": { + "file": { + "description": "本地图片文件路径 (与 --image 互斥)", + "field_provenance": { + "description": { + "candidates": [ + { + "precedence": "cobra_contract", + "selected": true, + "source": "cobra_usage", + "value": "本地图片文件路径 (与 --image 互斥)" + }, + { + "precedence": "default", + "selected": false, + "source": "default", + "value": "" + } + ], + "precedence": "cobra_contract", + "resolution": "highest_precedence", + "source": "cobra_usage", + "value": "本地图片文件路径 (与 --image 互斥)" + }, + "property": { + "candidates": [ + { + "precedence": "reviewed_mapping_exclusion", + "review_reason": "Reviewed unpinned adapter: doc.style_cover_set has no singular pinned interface_ref; --file is a CLI wrapper input and does not publish a direct interface property.", + "selected": true, + "source": "reviewed_mapping_exclusion", + "value": "" + }, + { + "precedence": "inference", + "selected": false, + "source": "flag_name_inference", + "value": "file" + } + ], + "precedence": "reviewed_mapping_exclusion", + "resolution": "highest_precedence", + "review_reason": "Reviewed unpinned adapter: doc.style_cover_set has no singular pinned interface_ref; --file is a CLI wrapper input and does not publish a direct interface property.", + "source": "reviewed_mapping_exclusion", + "value": "" + }, + "required": { + "candidates": [ + { + "precedence": "default", + "selected": true, + "source": "default", + "value": false + } + ], + "precedence": "default", + "resolution": "fallback", + "source": "default", + "value": false + }, + "required_when": { + "candidates": [ + { + "precedence": "default", + "selected": true, + "source": "default", + "value": "" + } + ], + "precedence": "default", + "resolution": "highest_precedence", + "source": "default", + "value": "" + }, + "type": { + "candidates": [ + { + "precedence": "cobra_contract", + "selected": true, + "source": "cobra_flag_type", + "value": "string" + } + ], + "precedence": "cobra_contract", + "resolution": "highest_precedence", + "source": "cobra_flag_type", + "value": "string" + } + }, + "required": false, + "type": "string" + }, + "image": { + "description": "封面图片 URL (外链会自动转存为内部地址)", + "field_provenance": { + "description": { + "candidates": [ + { + "precedence": "cobra_contract", + "selected": true, + "source": "cobra_usage", + "value": "封面图片 URL (外链会自动转存为内部地址)" + }, + { + "precedence": "default", + "selected": false, + "source": "default", + "value": "" + } + ], + "precedence": "cobra_contract", + "resolution": "highest_precedence", + "source": "cobra_usage", + "value": "封面图片 URL (外链会自动转存为内部地址)" + }, + "property": { + "candidates": [ + { + "precedence": "reviewed_mapping_exclusion", + "review_reason": "Reviewed unpinned adapter: doc.style_cover_set has no singular pinned interface_ref; --image is a CLI wrapper input and does not publish a direct interface property.", + "selected": true, + "source": "reviewed_mapping_exclusion", + "value": "" + }, + { + "precedence": "inference", + "selected": false, + "source": "flag_name_inference", + "value": "image" + } + ], + "precedence": "reviewed_mapping_exclusion", + "resolution": "highest_precedence", + "review_reason": "Reviewed unpinned adapter: doc.style_cover_set has no singular pinned interface_ref; --image is a CLI wrapper input and does not publish a direct interface property.", + "source": "reviewed_mapping_exclusion", + "value": "" + }, + "required": { + "candidates": [ + { + "precedence": "default", + "selected": true, + "source": "default", + "value": false + } + ], + "precedence": "default", + "resolution": "fallback", + "source": "default", + "value": false + }, + "required_when": { + "candidates": [ + { + "precedence": "default", + "selected": true, + "source": "default", + "value": "" + } + ], + "precedence": "default", + "resolution": "highest_precedence", + "source": "default", + "value": "" + }, + "type": { + "candidates": [ + { + "precedence": "cobra_contract", + "selected": true, + "source": "cobra_flag_type", + "value": "string" + } + ], + "precedence": "cobra_contract", + "resolution": "highest_precedence", + "source": "cobra_flag_type", + "value": "string" + } + }, + "required": false, + "type": "string" + }, + "node": { + "description": "目标文档标识,支持 URL 或 ID (必填)", + "field_provenance": { + "description": { + "candidates": [ + { + "precedence": "cobra_contract", + "selected": true, + "source": "cobra_usage", + "value": "目标文档标识,支持 URL 或 ID (必填)" + }, + { + "precedence": "default", + "selected": false, + "source": "default", + "value": "" + } + ], + "precedence": "cobra_contract", + "resolution": "highest_precedence", + "source": "cobra_usage", + "value": "目标文档标识,支持 URL 或 ID (必填)" + }, + "property": { + "candidates": [ + { + "precedence": "reviewed_mapping_exclusion", + "review_reason": "Reviewed unpinned adapter: doc.style_cover_set has no singular pinned interface_ref; --node is a CLI wrapper input and does not publish a direct interface property.", + "selected": true, + "source": "reviewed_mapping_exclusion", + "value": "" + }, + { + "precedence": "inference", + "selected": false, + "source": "flag_name_inference", + "value": "node" + } + ], + "precedence": "reviewed_mapping_exclusion", + "resolution": "highest_precedence", + "review_reason": "Reviewed unpinned adapter: doc.style_cover_set has no singular pinned interface_ref; --node is a CLI wrapper input and does not publish a direct interface property.", + "source": "reviewed_mapping_exclusion", + "value": "" + }, + "required": { + "candidates": [ + { + "precedence": "inference", + "selected": true, + "source": "usage_required_inference", + "value": true + }, + { + "precedence": "default", + "selected": false, + "source": "default", + "value": false + } + ], + "precedence": "inference", + "resolution": "highest_precedence", + "source": "usage_required_inference", + "value": true + }, + "required_when": { + "candidates": [ + { + "precedence": "default", + "selected": true, + "source": "default", + "value": "" + } + ], + "precedence": "default", + "resolution": "highest_precedence", + "source": "default", + "value": "" + }, + "type": { + "candidates": [ + { + "precedence": "cobra_contract", + "selected": true, + "source": "cobra_flag_type", + "value": "string" + } + ], + "precedence": "cobra_contract", + "resolution": "highest_precedence", + "source": "cobra_flag_type", + "value": "string" + } + }, + "required": true, + "type": "string" + }, + "position": { + "default": "0.5", + "description": "封面竖直位置 [0,1],默认 0.5", + "field_provenance": { + "description": { + "candidates": [ + { + "precedence": "cobra_contract", + "selected": true, + "source": "cobra_usage", + "value": "封面竖直位置 [0,1],默认 0.5" + }, + { + "precedence": "default", + "selected": false, + "source": "default", + "value": "" + } + ], + "precedence": "cobra_contract", + "resolution": "highest_precedence", + "source": "cobra_usage", + "value": "封面竖直位置 [0,1],默认 0.5" + }, + "property": { + "candidates": [ + { + "precedence": "reviewed_mapping_exclusion", + "review_reason": "Reviewed unpinned adapter: doc.style_cover_set has no singular pinned interface_ref; --position is a CLI wrapper input and does not publish a direct interface property.", + "selected": true, + "source": "reviewed_mapping_exclusion", + "value": "" + }, + { + "precedence": "inference", + "selected": false, + "source": "flag_name_inference", + "value": "position" + } + ], + "precedence": "reviewed_mapping_exclusion", + "resolution": "highest_precedence", + "review_reason": "Reviewed unpinned adapter: doc.style_cover_set has no singular pinned interface_ref; --position is a CLI wrapper input and does not publish a direct interface property.", + "source": "reviewed_mapping_exclusion", + "value": "" + }, + "required": { + "candidates": [ + { + "precedence": "cobra_contract", + "selected": true, + "source": "cobra_nonzero_default", + "value": false + }, + { + "precedence": "default", + "selected": false, + "source": "default", + "value": false + } + ], + "precedence": "cobra_contract", + "resolution": "highest_precedence", + "source": "cobra_nonzero_default", + "value": false + }, + "required_when": { + "candidates": [ + { + "precedence": "default", + "selected": true, + "source": "default", + "value": "" + } + ], + "precedence": "default", + "resolution": "highest_precedence", + "source": "default", + "value": "" + }, + "type": { + "candidates": [ + { + "precedence": "cobra_contract", + "selected": true, + "source": "cobra_flag_type", + "value": "number" + } + ], + "precedence": "cobra_contract", + "resolution": "highest_precedence", + "source": "cobra_flag_type", + "value": "number" + } + }, + "required": false, + "type": "number" + } + }, + "path": "doc.style_cover_set", + "primary_cli_path": "doc style cover set", + "product_id": "doc", + "reviewed": true, + "risk": "low", + "source": "reviewed_command_registry", + "title": "设置文档封面", + "use_when": [ + "用户说 给文档设置封面/换个封面图" + ] + }, "doc.template_apply": { "agent_metadata_source": "embedded-skill-metadata", "agent_source_refs": [ diff --git a/internal/cli/schema_catalog/tools/drive.json b/internal/cli/schema_catalog/tools/drive.json index 1b6a4014..0581d77a 100644 --- a/internal/cli/schema_catalog/tools/drive.json +++ b/internal/cli/schema_catalog/tools/drive.json @@ -810,6 +810,805 @@ "用户说把某篇文档分享给某人看/可编辑时(含「我的文档」下的节点)" ] }, + "drive.apply_permission": { + "agent_metadata_source": "embedded-skill-metadata", + "agent_source_refs": [ + "CommandRegistry:canonical_path=drive.apply_permission", + "cobra-help:dws drive permission apply", + "internal/cli/schema_hints/metadata/drive.json", + "internal/cli/schema_hints/selection/drive.json", + "wukong-develop:wukong/products" + ], + "agent_summary": "向审批人发起文档权限申请(会真实通知审批人)", + "agent_summary_source": "dws-agent-selection/drive", + "availability": "available", + "avoid_when": [ + "先用 apply-info 查可申请角色与审批人;未经用户确认不得自行提交" + ], + "canonical_path": "drive.apply_permission", + "cli_name": "apply", + "cli_path": "drive permission apply", + "confirmation": "user_required", + "description": "向指定节点的审批人发起权限申请。建议先用 apply-info 获取可申请角色与审批人。\n\n注意: 本命令会真实通知审批人,Agent 必须先获得用户明确同意后再执行。", + "display": "钉盘文件管理", + "effect": "write", + "effect_source": "agent-hint", + "examples": [ + "dws drive permission apply --node \u003cDOC_ID\u003e --role READER --users \u003c审批人userId\u003e --format json" + ], + "field_provenance": { + "agent_summary": { + "candidates": [ + { + "precedence": "reviewed_explicit", + "review_reason": "人工审阅:对照悟空 develop 同名命令实现、Cobra Long/Example 与 Runtime 确认门禁移植;不改变命令身份、参数契约或接口绑定。", + "selected": true, + "source": "internal/cli/schema_hints/selection/drive.json", + "value": "向审批人发起文档权限申请(会真实通知审批人)" + } + ], + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "人工审阅:对照悟空 develop 同名命令实现、Cobra Long/Example 与 Runtime 确认门禁移植;不改变命令身份、参数契约或接口绑定。", + "source": "internal/cli/schema_hints/selection/drive.json", + "value": "向审批人发起文档权限申请(会真实通知审批人)" + }, + "availability": { + "candidates": [ + { + "precedence": "reviewed_explicit", + "review_reason": "dws-tool-metadata/drive marks this tool as reviewed", + "selected": true, + "source": "internal/cli/schema_hints/metadata/drive.json", + "value": "available" + } + ], + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "dws-tool-metadata/drive marks this tool as reviewed", + "source": "internal/cli/schema_hints/metadata/drive.json", + "value": "available" + }, + "avoid_when": { + "candidates": [ + { + "precedence": "reviewed_explicit", + "review_reason": "人工审阅:对照悟空 develop 同名命令实现、Cobra Long/Example 与 Runtime 确认门禁移植;不改变命令身份、参数契约或接口绑定。", + "selected": true, + "source": "internal/cli/schema_hints/selection/drive.json", + "value": [ + "先用 apply-info 查可申请角色与审批人;未经用户确认不得自行提交" + ] + } + ], + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "人工审阅:对照悟空 develop 同名命令实现、Cobra Long/Example 与 Runtime 确认门禁移植;不改变命令身份、参数契约或接口绑定。", + "source": "internal/cli/schema_hints/selection/drive.json", + "value": [ + "先用 apply-info 查可申请角色与审批人;未经用户确认不得自行提交" + ] + }, + "canonical_path": { + "candidates": [ + { + "precedence": "command_registry", + "selected": true, + "source": "reviewed_command_registry", + "source_ref": "drive permission apply", + "value": "drive.apply_permission" + } + ], + "precedence": "command_registry", + "resolution": "registry_identity", + "source": "reviewed_command_registry", + "source_ref": "drive permission apply", + "value": "drive.apply_permission" + }, + "confirmation": { + "candidates": [ + { + "precedence": "reviewed_explicit", + "review_reason": "dws-tool-metadata/drive marks this tool as reviewed", + "selected": true, + "source": "internal/cli/schema_hints/metadata/drive.json", + "value": "user_required" + } + ], + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "dws-tool-metadata/drive marks this tool as reviewed", + "source": "internal/cli/schema_hints/metadata/drive.json", + "value": "user_required" + }, + "description": { + "candidates": [ + { + "precedence": "cobra_help", + "selected": true, + "source": "cobra_help", + "value": "向指定节点的审批人发起权限申请。建议先用 apply-info 获取可申请角色与审批人。\n\n注意: 本命令会真实通知审批人,Agent 必须先获得用户明确同意后再执行。" + } + ], + "precedence": "cobra_help", + "resolution": "highest_precedence", + "source": "cobra_help", + "value": "向指定节点的审批人发起权限申请。建议先用 apply-info 获取可申请角色与审批人。\n\n注意: 本命令会真实通知审批人,Agent 必须先获得用户明确同意后再执行。" + }, + "effect": { + "candidates": [ + { + "precedence": "reviewed_explicit", + "review_reason": "dws-tool-metadata/drive marks this tool as reviewed", + "selected": true, + "source": "internal/cli/schema_hints/metadata/drive.json", + "value": "write" + } + ], + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "dws-tool-metadata/drive marks this tool as reviewed", + "source": "internal/cli/schema_hints/metadata/drive.json", + "value": "write" + }, + "examples": { + "candidates": [ + { + "precedence": "reviewed_explicit", + "review_reason": "人工审阅:对照悟空 develop 同名命令实现、Cobra Long/Example 与 Runtime 确认门禁移植;不改变命令身份、参数契约或接口绑定。", + "selected": true, + "source": "internal/cli/schema_hints/selection/drive.json", + "value": [ + "dws drive permission apply --node \u003cDOC_ID\u003e --role READER --users \u003c审批人userId\u003e --format json" + ] + } + ], + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "人工审阅:对照悟空 develop 同名命令实现、Cobra Long/Example 与 Runtime 确认门禁移植;不改变命令身份、参数契约或接口绑定。", + "source": "internal/cli/schema_hints/selection/drive.json", + "value": [ + "dws drive permission apply --node \u003cDOC_ID\u003e --role READER --users \u003c审批人userId\u003e --format json" + ] + }, + "idempotency": { + "candidates": [ + { + "precedence": "reviewed_explicit", + "review_reason": "dws-tool-metadata/drive marks this tool as reviewed", + "selected": true, + "source": "internal/cli/schema_hints/metadata/drive.json", + "value": "non_idempotent" + } + ], + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "dws-tool-metadata/drive marks this tool as reviewed", + "source": "internal/cli/schema_hints/metadata/drive.json", + "value": "non_idempotent" + }, + "interface_mode": { + "candidates": [ + { + "precedence": "reviewed_explicit", + "review_reason": "dws-tool-metadata/drive marks this tool as reviewed", + "selected": true, + "source": "internal/cli/schema_hints/metadata/drive.json", + "value": "composite" + } + ], + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "dws-tool-metadata/drive marks this tool as reviewed", + "source": "internal/cli/schema_hints/metadata/drive.json", + "value": "composite" + }, + "interface_reason": { + "candidates": [ + { + "precedence": "reviewed_explicit", + "review_reason": "dws-tool-metadata/drive marks this tool as reviewed", + "selected": true, + "source": "internal/cli/schema_hints/metadata/drive.json", + "value": "Reviewed unpinned remote adapter: this executable CLI wrapper calls a remote helper that is absent from the pinned MCP metadata snapshot; no single pinned semantically equivalent interface_ref can represent the command." + } + ], + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "dws-tool-metadata/drive marks this tool as reviewed", + "source": "internal/cli/schema_hints/metadata/drive.json", + "value": "Reviewed unpinned remote adapter: this executable CLI wrapper calls a remote helper that is absent from the pinned MCP metadata snapshot; no single pinned semantically equivalent interface_ref can represent the command." + }, + "interface_ref": { + "candidates": [ + { + "precedence": "reviewed_explicit", + "review_reason": "final interface mode composite forbids a direct MCP interface_ref", + "selected": true, + "source": "internal/cli/schema_hints/metadata/drive.json", + "value": null + } + ], + "precedence": "reviewed_explicit", + "resolution": "interface_disposition_matrix", + "review_reason": "final interface mode composite forbids a direct MCP interface_ref", + "source": "internal/cli/schema_hints/metadata/drive.json", + "value": null + }, + "reviewed": { + "candidates": [ + { + "precedence": "reviewed_explicit", + "review_reason": "dws-tool-metadata/drive marks this tool as reviewed", + "selected": true, + "source": "internal/cli/schema_hints/metadata/drive.json", + "value": true + }, + { + "precedence": "reviewed_explicit", + "review_reason": "人工审阅:对照悟空 develop 同名命令实现、Cobra Long/Example 与 Runtime 确认门禁移植;不改变命令身份、参数契约或接口绑定。", + "selected": false, + "source": "internal/cli/schema_hints/selection/drive.json", + "value": true + } + ], + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "dws-tool-metadata/drive marks this tool as reviewed", + "source": "internal/cli/schema_hints/metadata/drive.json", + "value": true + }, + "risk": { + "candidates": [ + { + "precedence": "reviewed_explicit", + "review_reason": "dws-tool-metadata/drive marks this tool as reviewed", + "selected": true, + "source": "internal/cli/schema_hints/metadata/drive.json", + "value": "medium" + } + ], + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "dws-tool-metadata/drive marks this tool as reviewed", + "source": "internal/cli/schema_hints/metadata/drive.json", + "value": "medium" + }, + "title": { + "candidates": [ + { + "precedence": "cobra_help", + "selected": true, + "source": "cobra_help", + "value": "发起权限申请" + } + ], + "precedence": "cobra_help", + "resolution": "highest_precedence", + "source": "cobra_help", + "value": "发起权限申请" + }, + "use_when": { + "candidates": [ + { + "precedence": "reviewed_explicit", + "review_reason": "人工审阅:对照悟空 develop 同名命令实现、Cobra Long/Example 与 Runtime 确认门禁移植;不改变命令身份、参数契约或接口绑定。", + "selected": true, + "source": "internal/cli/schema_hints/selection/drive.json", + "value": [ + "用户确认要为无权限文档发起权限申请" + ] + } + ], + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "人工审阅:对照悟空 develop 同名命令实现、Cobra Long/Example 与 Runtime 确认门禁移植;不改变命令身份、参数契约或接口绑定。", + "source": "internal/cli/schema_hints/selection/drive.json", + "value": [ + "用户确认要为无权限文档发起权限申请" + ] + } + }, + "group": "permission", + "has_parameters": true, + "idempotency": "non_idempotent", + "interface_mode": "composite", + "interface_reason": "Reviewed unpinned remote adapter: this executable CLI wrapper calls a remote helper that is absent from the pinned MCP metadata snapshot; no single pinned semantically equivalent interface_ref can represent the command.", + "is_alias": false, + "name": "apply_permission", + "parameter_count": 5, + "parameters": { + "node": { + "description": "目标节点 ID 或 URL (必填)", + "field_provenance": { + "description": { + "candidates": [ + { + "precedence": "cobra_contract", + "selected": true, + "source": "cobra_usage", + "value": "目标节点 ID 或 URL (必填)" + }, + { + "precedence": "default", + "selected": false, + "source": "default", + "value": "" + } + ], + "precedence": "cobra_contract", + "resolution": "highest_precedence", + "source": "cobra_usage", + "value": "目标节点 ID 或 URL (必填)" + }, + "property": { + "candidates": [ + { + "precedence": "reviewed_mapping_exclusion", + "review_reason": "Reviewed unpinned adapter: drive.apply_permission has no singular pinned interface_ref; --node is a CLI wrapper input and does not publish a direct interface property.", + "selected": true, + "source": "reviewed_mapping_exclusion", + "value": "" + }, + { + "precedence": "inference", + "selected": false, + "source": "flag_name_inference", + "value": "node" + } + ], + "precedence": "reviewed_mapping_exclusion", + "resolution": "highest_precedence", + "review_reason": "Reviewed unpinned adapter: drive.apply_permission has no singular pinned interface_ref; --node is a CLI wrapper input and does not publish a direct interface property.", + "source": "reviewed_mapping_exclusion", + "value": "" + }, + "required": { + "candidates": [ + { + "precedence": "inference", + "selected": true, + "source": "usage_required_inference", + "value": true + }, + { + "precedence": "default", + "selected": false, + "source": "default", + "value": false + } + ], + "precedence": "inference", + "resolution": "highest_precedence", + "source": "usage_required_inference", + "value": true + }, + "required_when": { + "candidates": [ + { + "precedence": "default", + "selected": true, + "source": "default", + "value": "" + } + ], + "precedence": "default", + "resolution": "highest_precedence", + "source": "default", + "value": "" + }, + "type": { + "candidates": [ + { + "precedence": "cobra_contract", + "selected": true, + "source": "cobra_flag_type", + "value": "string" + } + ], + "precedence": "cobra_contract", + "resolution": "highest_precedence", + "source": "cobra_flag_type", + "value": "string" + } + }, + "required": true, + "type": "string" + }, + "notify-mode": { + "description": "通知方式: DEFAULT / MSG_ACCOUNT / SINGLE_CHAT", + "field_provenance": { + "description": { + "candidates": [ + { + "precedence": "cobra_contract", + "selected": true, + "source": "cobra_usage", + "value": "通知方式: DEFAULT / MSG_ACCOUNT / SINGLE_CHAT" + }, + { + "precedence": "default", + "selected": false, + "source": "default", + "value": "" + } + ], + "precedence": "cobra_contract", + "resolution": "highest_precedence", + "source": "cobra_usage", + "value": "通知方式: DEFAULT / MSG_ACCOUNT / SINGLE_CHAT" + }, + "property": { + "candidates": [ + { + "precedence": "reviewed_mapping_exclusion", + "review_reason": "Reviewed unpinned adapter: drive.apply_permission has no singular pinned interface_ref; --notify-mode is a CLI wrapper input and does not publish a direct interface property.", + "selected": true, + "source": "reviewed_mapping_exclusion", + "value": "" + }, + { + "precedence": "inference", + "selected": false, + "source": "flag_name_inference", + "value": "notifyMode" + } + ], + "precedence": "reviewed_mapping_exclusion", + "resolution": "highest_precedence", + "review_reason": "Reviewed unpinned adapter: drive.apply_permission has no singular pinned interface_ref; --notify-mode is a CLI wrapper input and does not publish a direct interface property.", + "source": "reviewed_mapping_exclusion", + "value": "" + }, + "required": { + "candidates": [ + { + "precedence": "cobra_contract", + "selected": true, + "source": "cobra_nonzero_default", + "value": false + }, + { + "precedence": "default", + "selected": false, + "source": "default", + "value": false + } + ], + "precedence": "cobra_contract", + "resolution": "highest_precedence", + "source": "cobra_nonzero_default", + "value": false + }, + "required_when": { + "candidates": [ + { + "precedence": "default", + "selected": true, + "source": "default", + "value": "" + } + ], + "precedence": "default", + "resolution": "highest_precedence", + "source": "default", + "value": "" + }, + "type": { + "candidates": [ + { + "precedence": "cobra_contract", + "selected": true, + "source": "cobra_flag_type", + "value": "string" + } + ], + "precedence": "cobra_contract", + "resolution": "highest_precedence", + "source": "cobra_flag_type", + "value": "string" + } + }, + "required": false, + "type": "string" + }, + "reason": { + "description": "申请理由,最长 200 字符", + "field_provenance": { + "description": { + "candidates": [ + { + "precedence": "cobra_contract", + "selected": true, + "source": "cobra_usage", + "value": "申请理由,最长 200 字符" + }, + { + "precedence": "default", + "selected": false, + "source": "default", + "value": "" + } + ], + "precedence": "cobra_contract", + "resolution": "highest_precedence", + "source": "cobra_usage", + "value": "申请理由,最长 200 字符" + }, + "property": { + "candidates": [ + { + "precedence": "reviewed_mapping_exclusion", + "review_reason": "Reviewed unpinned adapter: drive.apply_permission has no singular pinned interface_ref; --reason is a CLI wrapper input and does not publish a direct interface property.", + "selected": true, + "source": "reviewed_mapping_exclusion", + "value": "" + }, + { + "precedence": "inference", + "selected": false, + "source": "flag_name_inference", + "value": "reason" + } + ], + "precedence": "reviewed_mapping_exclusion", + "resolution": "highest_precedence", + "review_reason": "Reviewed unpinned adapter: drive.apply_permission has no singular pinned interface_ref; --reason is a CLI wrapper input and does not publish a direct interface property.", + "source": "reviewed_mapping_exclusion", + "value": "" + }, + "required": { + "candidates": [ + { + "precedence": "default", + "selected": true, + "source": "default", + "value": false + } + ], + "precedence": "default", + "resolution": "fallback", + "source": "default", + "value": false + }, + "required_when": { + "candidates": [ + { + "precedence": "default", + "selected": true, + "source": "default", + "value": "" + } + ], + "precedence": "default", + "resolution": "highest_precedence", + "source": "default", + "value": "" + }, + "type": { + "candidates": [ + { + "precedence": "cobra_contract", + "selected": true, + "source": "cobra_flag_type", + "value": "string" + } + ], + "precedence": "cobra_contract", + "resolution": "highest_precedence", + "source": "cobra_flag_type", + "value": "string" + } + }, + "required": false, + "type": "string" + }, + "role": { + "description": "申请的角色: EDITOR / DOWNLOADER / READER (必填)", + "field_provenance": { + "description": { + "candidates": [ + { + "precedence": "cobra_contract", + "selected": true, + "source": "cobra_usage", + "value": "申请的角色: EDITOR / DOWNLOADER / READER (必填)" + }, + { + "precedence": "default", + "selected": false, + "source": "default", + "value": "" + } + ], + "precedence": "cobra_contract", + "resolution": "highest_precedence", + "source": "cobra_usage", + "value": "申请的角色: EDITOR / DOWNLOADER / READER (必填)" + }, + "property": { + "candidates": [ + { + "precedence": "reviewed_mapping_exclusion", + "review_reason": "Reviewed unpinned adapter: drive.apply_permission has no singular pinned interface_ref; --role is a CLI wrapper input and does not publish a direct interface property.", + "selected": true, + "source": "reviewed_mapping_exclusion", + "value": "" + }, + { + "precedence": "inference", + "selected": false, + "source": "flag_name_inference", + "value": "role" + } + ], + "precedence": "reviewed_mapping_exclusion", + "resolution": "highest_precedence", + "review_reason": "Reviewed unpinned adapter: drive.apply_permission has no singular pinned interface_ref; --role is a CLI wrapper input and does not publish a direct interface property.", + "source": "reviewed_mapping_exclusion", + "value": "" + }, + "required": { + "candidates": [ + { + "precedence": "inference", + "selected": true, + "source": "usage_required_inference", + "value": true + }, + { + "precedence": "default", + "selected": false, + "source": "default", + "value": false + } + ], + "precedence": "inference", + "resolution": "highest_precedence", + "source": "usage_required_inference", + "value": true + }, + "required_when": { + "candidates": [ + { + "precedence": "default", + "selected": true, + "source": "default", + "value": "" + } + ], + "precedence": "default", + "resolution": "highest_precedence", + "source": "default", + "value": "" + }, + "type": { + "candidates": [ + { + "precedence": "cobra_contract", + "selected": true, + "source": "cobra_flag_type", + "value": "string" + } + ], + "precedence": "cobra_contract", + "resolution": "highest_precedence", + "source": "cobra_flag_type", + "value": "string" + } + }, + "required": true, + "type": "string" + }, + "users": { + "description": "审批人 userId 列表,逗号分隔 (必填)", + "field_provenance": { + "description": { + "candidates": [ + { + "precedence": "cobra_contract", + "selected": true, + "source": "cobra_usage", + "value": "审批人 userId 列表,逗号分隔 (必填)" + }, + { + "precedence": "default", + "selected": false, + "source": "default", + "value": "" + } + ], + "precedence": "cobra_contract", + "resolution": "highest_precedence", + "source": "cobra_usage", + "value": "审批人 userId 列表,逗号分隔 (必填)" + }, + "property": { + "candidates": [ + { + "precedence": "reviewed_mapping_exclusion", + "review_reason": "Reviewed unpinned adapter: drive.apply_permission has no singular pinned interface_ref; --users is a CLI wrapper input and does not publish a direct interface property.", + "selected": true, + "source": "reviewed_mapping_exclusion", + "value": "" + }, + { + "precedence": "inference", + "selected": false, + "source": "flag_name_inference", + "value": "users" + } + ], + "precedence": "reviewed_mapping_exclusion", + "resolution": "highest_precedence", + "review_reason": "Reviewed unpinned adapter: drive.apply_permission has no singular pinned interface_ref; --users is a CLI wrapper input and does not publish a direct interface property.", + "source": "reviewed_mapping_exclusion", + "value": "" + }, + "required": { + "candidates": [ + { + "precedence": "inference", + "selected": true, + "source": "usage_required_inference", + "value": true + }, + { + "precedence": "default", + "selected": false, + "source": "default", + "value": false + } + ], + "precedence": "inference", + "resolution": "highest_precedence", + "source": "usage_required_inference", + "value": true + }, + "required_when": { + "candidates": [ + { + "precedence": "default", + "selected": true, + "source": "default", + "value": "" + } + ], + "precedence": "default", + "resolution": "highest_precedence", + "source": "default", + "value": "" + }, + "type": { + "candidates": [ + { + "precedence": "cobra_contract", + "selected": true, + "source": "cobra_flag_type", + "value": "string" + } + ], + "precedence": "cobra_contract", + "resolution": "highest_precedence", + "source": "cobra_flag_type", + "value": "string" + } + }, + "required": true, + "type": "string" + } + }, + "path": "drive.apply_permission", + "primary_cli_path": "drive permission apply", + "product_id": "drive", + "reviewed": true, + "risk": "medium", + "source": "reviewed_command_registry", + "title": "发起权限申请", + "use_when": [ + "用户确认要为无权限文档发起权限申请" + ] + }, "drive.commit_upload": { "agent_metadata_source": "embedded-skill-metadata", "agent_source_refs": [ @@ -4825,6 +5624,1041 @@ "已确认 contentType 非 ALIDOC,需要落盘本地查看时" ] }, + "drive.download_file_version": { + "agent_metadata_source": "embedded-skill-metadata", + "agent_source_refs": [ + "CommandRegistry:canonical_path=drive.download_file_version", + "cobra-help:dws drive download-version", + "internal/cli/schema_hints/metadata/drive.json", + "internal/cli/schema_hints/selection/drive.json", + "wukong-develop:wukong/products/drive.go" + ], + "agent_summary": "下载钉盘普通文件的指定历史版本到本地(两步下载:取签名 URL 后 HTTP GET)", + "agent_summary_source": "dws-agent-selection/drive", + "availability": "available", + "avoid_when": [ + "下载最新版本用 drive download", + "在线文档(adoc)历史版本用 doc version 系列命令", + "在线表格(axls)历史版本用 sheet version 系列命令" + ], + "canonical_path": "drive.download_file_version", + "cli_name": "download-version", + "cli_path": "drive download-version", + "confirmation": "not_required", + "description": "下载钉盘文件的指定历史版本到本地(两步下载流程)。\n\n仅适用于普通文件(如 pdf、docx、xlsx、png 等):\n 钉钉在线文档(adoc)请使用 dws doc version 系列命令\n 钉钉在线表格(axls)请使用 dws sheet version 系列命令\n\n流程:\n 1. 获取历史版本下载 URL 和签名请求头 (download_file_version)\n 2. HTTP GET 下载文件二进制内容到本地\n\n版本号从 dws drive list --node \u003cdentryUuid\u003e --versions 获取。", + "display": "钉盘文件管理", + "effect": "read", + "effect_source": "agent-hint", + "examples": [ + "dws drive download-version --node \u003cdentryUuid\u003e --version 3 --output ./report_v3.pdf" + ], + "field_provenance": { + "agent_summary": { + "candidates": [ + { + "precedence": "reviewed_explicit", + "review_reason": "人工审阅:对照悟空 develop drive download --version 分支实现移植为独立命令;单一 download_file_version 远端调用,参数契约与帮助文档一致。", + "selected": true, + "source": "internal/cli/schema_hints/selection/drive.json", + "value": "下载钉盘普通文件的指定历史版本到本地(两步下载:取签名 URL 后 HTTP GET)" + } + ], + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "人工审阅:对照悟空 develop drive download --version 分支实现移植为独立命令;单一 download_file_version 远端调用,参数契约与帮助文档一致。", + "source": "internal/cli/schema_hints/selection/drive.json", + "value": "下载钉盘普通文件的指定历史版本到本地(两步下载:取签名 URL 后 HTTP GET)" + }, + "availability": { + "candidates": [ + { + "precedence": "reviewed_explicit", + "review_reason": "dws-tool-metadata/drive marks this tool as reviewed", + "selected": true, + "source": "internal/cli/schema_hints/metadata/drive.json", + "value": "available" + } + ], + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "dws-tool-metadata/drive marks this tool as reviewed", + "source": "internal/cli/schema_hints/metadata/drive.json", + "value": "available" + }, + "avoid_when": { + "candidates": [ + { + "precedence": "reviewed_explicit", + "review_reason": "人工审阅:对照悟空 develop drive download --version 分支实现移植为独立命令;单一 download_file_version 远端调用,参数契约与帮助文档一致。", + "selected": true, + "source": "internal/cli/schema_hints/selection/drive.json", + "value": [ + "下载最新版本用 drive download", + "在线文档(adoc)历史版本用 doc version 系列命令", + "在线表格(axls)历史版本用 sheet version 系列命令" + ] + } + ], + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "人工审阅:对照悟空 develop drive download --version 分支实现移植为独立命令;单一 download_file_version 远端调用,参数契约与帮助文档一致。", + "source": "internal/cli/schema_hints/selection/drive.json", + "value": [ + "下载最新版本用 drive download", + "在线文档(adoc)历史版本用 doc version 系列命令", + "在线表格(axls)历史版本用 sheet version 系列命令" + ] + }, + "canonical_path": { + "candidates": [ + { + "precedence": "command_registry", + "selected": true, + "source": "reviewed_command_registry", + "source_ref": "drive download-version", + "value": "drive.download_file_version" + } + ], + "precedence": "command_registry", + "resolution": "registry_identity", + "source": "reviewed_command_registry", + "source_ref": "drive download-version", + "value": "drive.download_file_version" + }, + "confirmation": { + "candidates": [ + { + "precedence": "reviewed_explicit", + "review_reason": "dws-tool-metadata/drive marks this tool as reviewed", + "selected": true, + "source": "internal/cli/schema_hints/metadata/drive.json", + "value": "not_required" + } + ], + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "dws-tool-metadata/drive marks this tool as reviewed", + "source": "internal/cli/schema_hints/metadata/drive.json", + "value": "not_required" + }, + "description": { + "candidates": [ + { + "precedence": "cobra_help", + "selected": true, + "source": "cobra_help", + "value": "下载钉盘文件的指定历史版本到本地(两步下载流程)。\n\n仅适用于普通文件(如 pdf、docx、xlsx、png 等):\n 钉钉在线文档(adoc)请使用 dws doc version 系列命令\n 钉钉在线表格(axls)请使用 dws sheet version 系列命令\n\n流程:\n 1. 获取历史版本下载 URL 和签名请求头 (download_file_version)\n 2. HTTP GET 下载文件二进制内容到本地\n\n版本号从 dws drive list --node \u003cdentryUuid\u003e --versions 获取。" + } + ], + "precedence": "cobra_help", + "resolution": "highest_precedence", + "source": "cobra_help", + "value": "下载钉盘文件的指定历史版本到本地(两步下载流程)。\n\n仅适用于普通文件(如 pdf、docx、xlsx、png 等):\n 钉钉在线文档(adoc)请使用 dws doc version 系列命令\n 钉钉在线表格(axls)请使用 dws sheet version 系列命令\n\n流程:\n 1. 获取历史版本下载 URL 和签名请求头 (download_file_version)\n 2. HTTP GET 下载文件二进制内容到本地\n\n版本号从 dws drive list --node \u003cdentryUuid\u003e --versions 获取。" + }, + "effect": { + "candidates": [ + { + "precedence": "reviewed_explicit", + "review_reason": "dws-tool-metadata/drive marks this tool as reviewed", + "selected": true, + "source": "internal/cli/schema_hints/metadata/drive.json", + "value": "read" + } + ], + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "dws-tool-metadata/drive marks this tool as reviewed", + "source": "internal/cli/schema_hints/metadata/drive.json", + "value": "read" + }, + "examples": { + "candidates": [ + { + "precedence": "reviewed_explicit", + "review_reason": "人工审阅:对照悟空 develop drive download --version 分支实现移植为独立命令;单一 download_file_version 远端调用,参数契约与帮助文档一致。", + "selected": true, + "source": "internal/cli/schema_hints/selection/drive.json", + "value": [ + "dws drive download-version --node \u003cdentryUuid\u003e --version 3 --output ./report_v3.pdf" + ] + } + ], + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "人工审阅:对照悟空 develop drive download --version 分支实现移植为独立命令;单一 download_file_version 远端调用,参数契约与帮助文档一致。", + "source": "internal/cli/schema_hints/selection/drive.json", + "value": [ + "dws drive download-version --node \u003cdentryUuid\u003e --version 3 --output ./report_v3.pdf" + ] + }, + "idempotency": { + "candidates": [ + { + "precedence": "reviewed_explicit", + "review_reason": "dws-tool-metadata/drive marks this tool as reviewed", + "selected": true, + "source": "internal/cli/schema_hints/metadata/drive.json", + "value": "idempotent" + } + ], + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "dws-tool-metadata/drive marks this tool as reviewed", + "source": "internal/cli/schema_hints/metadata/drive.json", + "value": "idempotent" + }, + "interface_mode": { + "candidates": [ + { + "precedence": "reviewed_explicit", + "review_reason": "dws-tool-metadata/drive marks this tool as reviewed", + "selected": true, + "source": "internal/cli/schema_hints/metadata/drive.json", + "value": "composite" + } + ], + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "dws-tool-metadata/drive marks this tool as reviewed", + "source": "internal/cli/schema_hints/metadata/drive.json", + "value": "composite" + }, + "interface_reason": { + "candidates": [ + { + "precedence": "reviewed_explicit", + "review_reason": "dws-tool-metadata/drive marks this tool as reviewed", + "selected": true, + "source": "internal/cli/schema_hints/metadata/drive.json", + "value": "Reviewed unpinned remote adapter: this executable CLI wrapper calls a remote helper that is absent from the pinned MCP metadata snapshot; no single pinned semantically equivalent interface_ref can represent the command." + } + ], + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "dws-tool-metadata/drive marks this tool as reviewed", + "source": "internal/cli/schema_hints/metadata/drive.json", + "value": "Reviewed unpinned remote adapter: this executable CLI wrapper calls a remote helper that is absent from the pinned MCP metadata snapshot; no single pinned semantically equivalent interface_ref can represent the command." + }, + "interface_ref": { + "candidates": [ + { + "precedence": "reviewed_explicit", + "review_reason": "final interface mode composite forbids a direct MCP interface_ref", + "selected": true, + "source": "internal/cli/schema_hints/metadata/drive.json", + "value": null + } + ], + "precedence": "reviewed_explicit", + "resolution": "interface_disposition_matrix", + "review_reason": "final interface mode composite forbids a direct MCP interface_ref", + "source": "internal/cli/schema_hints/metadata/drive.json", + "value": null + }, + "reviewed": { + "candidates": [ + { + "precedence": "reviewed_explicit", + "review_reason": "dws-tool-metadata/drive marks this tool as reviewed", + "selected": true, + "source": "internal/cli/schema_hints/metadata/drive.json", + "value": true + }, + { + "precedence": "reviewed_explicit", + "review_reason": "人工审阅:对照悟空 develop drive download --version 分支实现移植为独立命令;单一 download_file_version 远端调用,参数契约与帮助文档一致。", + "selected": false, + "source": "internal/cli/schema_hints/selection/drive.json", + "value": true + } + ], + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "dws-tool-metadata/drive marks this tool as reviewed", + "source": "internal/cli/schema_hints/metadata/drive.json", + "value": true + }, + "risk": { + "candidates": [ + { + "precedence": "reviewed_explicit", + "review_reason": "dws-tool-metadata/drive marks this tool as reviewed", + "selected": true, + "source": "internal/cli/schema_hints/metadata/drive.json", + "value": "low" + } + ], + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "dws-tool-metadata/drive marks this tool as reviewed", + "source": "internal/cli/schema_hints/metadata/drive.json", + "value": "low" + }, + "title": { + "candidates": [ + { + "precedence": "cobra_help", + "selected": true, + "source": "cobra_help", + "value": "下载文件历史版本到本地" + } + ], + "precedence": "cobra_help", + "resolution": "highest_precedence", + "source": "cobra_help", + "value": "下载文件历史版本到本地" + }, + "use_when": { + "candidates": [ + { + "precedence": "reviewed_explicit", + "review_reason": "人工审阅:对照悟空 develop drive download --version 分支实现移植为独立命令;单一 download_file_version 远端调用,参数契约与帮助文档一致。", + "selected": true, + "source": "internal/cli/schema_hints/selection/drive.json", + "value": [ + "用户要下载文件的历史版本/旧版本", + "版本号已通过 drive list --versions 获取" + ] + } + ], + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "人工审阅:对照悟空 develop drive download --version 分支实现移植为独立命令;单一 download_file_version 远端调用,参数契约与帮助文档一致。", + "source": "internal/cli/schema_hints/selection/drive.json", + "value": [ + "用户要下载文件的历史版本/旧版本", + "版本号已通过 drive list --versions 获取" + ] + } + }, + "has_parameters": true, + "idempotency": "idempotent", + "interface_mode": "composite", + "interface_reason": "Reviewed unpinned remote adapter: this executable CLI wrapper calls a remote helper that is absent from the pinned MCP metadata snapshot; no single pinned semantically equivalent interface_ref can represent the command.", + "is_alias": false, + "name": "download_file_version", + "parameter_count": 3, + "parameters": { + "node": { + "description": "文件 ID (dentryUuid) 或 URL (必填)", + "field_provenance": { + "description": { + "candidates": [ + { + "precedence": "cobra_contract", + "selected": true, + "source": "cobra_usage", + "value": "文件 ID (dentryUuid) 或 URL (必填)" + }, + { + "precedence": "default", + "selected": false, + "source": "default", + "value": "" + } + ], + "precedence": "cobra_contract", + "resolution": "highest_precedence", + "source": "cobra_usage", + "value": "文件 ID (dentryUuid) 或 URL (必填)" + }, + "property": { + "candidates": [ + { + "precedence": "reviewed_mapping_exclusion", + "review_reason": "Reviewed unpinned adapter: drive.download_file_version has no singular pinned interface_ref; --node is a CLI wrapper input and does not publish a direct interface property.", + "selected": true, + "source": "reviewed_mapping_exclusion", + "value": "" + }, + { + "precedence": "inference", + "selected": false, + "source": "flag_name_inference", + "value": "node" + } + ], + "precedence": "reviewed_mapping_exclusion", + "resolution": "highest_precedence", + "review_reason": "Reviewed unpinned adapter: drive.download_file_version has no singular pinned interface_ref; --node is a CLI wrapper input and does not publish a direct interface property.", + "source": "reviewed_mapping_exclusion", + "value": "" + }, + "required": { + "candidates": [ + { + "precedence": "inference", + "selected": true, + "source": "usage_required_inference", + "value": true + }, + { + "precedence": "default", + "selected": false, + "source": "default", + "value": false + } + ], + "precedence": "inference", + "resolution": "highest_precedence", + "source": "usage_required_inference", + "value": true + }, + "required_when": { + "candidates": [ + { + "precedence": "default", + "selected": true, + "source": "default", + "value": "" + } + ], + "precedence": "default", + "resolution": "highest_precedence", + "source": "default", + "value": "" + }, + "type": { + "candidates": [ + { + "precedence": "cobra_contract", + "selected": true, + "source": "cobra_flag_type", + "value": "string" + } + ], + "precedence": "cobra_contract", + "resolution": "highest_precedence", + "source": "cobra_flag_type", + "value": "string" + } + }, + "required": true, + "type": "string" + }, + "output": { + "description": "本地保存路径 (文件路径或目录,必填)", + "field_provenance": { + "description": { + "candidates": [ + { + "precedence": "cobra_contract", + "selected": true, + "source": "cobra_usage", + "value": "本地保存路径 (文件路径或目录,必填)" + }, + { + "precedence": "default", + "selected": false, + "source": "default", + "value": "" + } + ], + "precedence": "cobra_contract", + "resolution": "highest_precedence", + "source": "cobra_usage", + "value": "本地保存路径 (文件路径或目录,必填)" + }, + "property": { + "candidates": [ + { + "precedence": "reviewed_mapping_exclusion", + "review_reason": "Reviewed unpinned adapter: drive.download_file_version has no singular pinned interface_ref; --output is a CLI wrapper input and does not publish a direct interface property.", + "selected": true, + "source": "reviewed_mapping_exclusion", + "value": "" + }, + { + "precedence": "inference", + "selected": false, + "source": "flag_name_inference", + "value": "output" + } + ], + "precedence": "reviewed_mapping_exclusion", + "resolution": "highest_precedence", + "review_reason": "Reviewed unpinned adapter: drive.download_file_version has no singular pinned interface_ref; --output is a CLI wrapper input and does not publish a direct interface property.", + "source": "reviewed_mapping_exclusion", + "value": "" + }, + "required": { + "candidates": [ + { + "precedence": "inference", + "selected": true, + "source": "usage_required_inference", + "value": true + }, + { + "precedence": "default", + "selected": false, + "source": "default", + "value": false + } + ], + "precedence": "inference", + "resolution": "highest_precedence", + "source": "usage_required_inference", + "value": true + }, + "required_when": { + "candidates": [ + { + "precedence": "default", + "selected": true, + "source": "default", + "value": "" + } + ], + "precedence": "default", + "resolution": "highest_precedence", + "source": "default", + "value": "" + }, + "type": { + "candidates": [ + { + "precedence": "cobra_contract", + "selected": true, + "source": "cobra_flag_type", + "value": "string" + } + ], + "precedence": "cobra_contract", + "resolution": "highest_precedence", + "source": "cobra_flag_type", + "value": "string" + } + }, + "required": true, + "type": "string" + }, + "version": { + "description": "历史版本号 (必填,正整数,从 drive list --versions 获取)", + "field_provenance": { + "description": { + "candidates": [ + { + "precedence": "cobra_contract", + "selected": true, + "source": "cobra_usage", + "value": "历史版本号 (必填,正整数,从 drive list --versions 获取)" + }, + { + "precedence": "default", + "selected": false, + "source": "default", + "value": "" + } + ], + "precedence": "cobra_contract", + "resolution": "highest_precedence", + "source": "cobra_usage", + "value": "历史版本号 (必填,正整数,从 drive list --versions 获取)" + }, + "property": { + "candidates": [ + { + "precedence": "reviewed_mapping_exclusion", + "review_reason": "Reviewed unpinned adapter: drive.download_file_version has no singular pinned interface_ref; --version is a CLI wrapper input and does not publish a direct interface property.", + "selected": true, + "source": "reviewed_mapping_exclusion", + "value": "" + }, + { + "precedence": "inference", + "selected": false, + "source": "flag_name_inference", + "value": "version" + } + ], + "precedence": "reviewed_mapping_exclusion", + "resolution": "highest_precedence", + "review_reason": "Reviewed unpinned adapter: drive.download_file_version has no singular pinned interface_ref; --version is a CLI wrapper input and does not publish a direct interface property.", + "source": "reviewed_mapping_exclusion", + "value": "" + }, + "required": { + "candidates": [ + { + "precedence": "inference", + "selected": true, + "source": "usage_required_inference", + "value": true + }, + { + "precedence": "default", + "selected": false, + "source": "default", + "value": false + } + ], + "precedence": "inference", + "resolution": "highest_precedence", + "source": "usage_required_inference", + "value": true + }, + "required_when": { + "candidates": [ + { + "precedence": "default", + "selected": true, + "source": "default", + "value": "" + } + ], + "precedence": "default", + "resolution": "highest_precedence", + "source": "default", + "value": "" + }, + "type": { + "candidates": [ + { + "precedence": "cobra_contract", + "selected": true, + "source": "cobra_flag_type", + "value": "integer" + } + ], + "precedence": "cobra_contract", + "resolution": "highest_precedence", + "source": "cobra_flag_type", + "value": "integer" + } + }, + "required": true, + "type": "integer" + } + }, + "path": "drive.download_file_version", + "primary_cli_path": "drive download-version", + "product_id": "drive", + "reviewed": true, + "risk": "low", + "source": "reviewed_command_registry", + "title": "下载文件历史版本到本地", + "use_when": [ + "用户要下载文件的历史版本/旧版本", + "版本号已通过 drive list --versions 获取" + ] + }, + "drive.get_cover": { + "agent_metadata_source": "embedded-skill-metadata", + "agent_source_refs": [ + "CommandRegistry:canonical_path=drive.get_cover", + "cobra-help:dws drive cover", + "internal/cli/schema_hints/metadata/drive.json", + "internal/cli/schema_hints/selection/drive.json", + "wukong-develop:wukong/products" + ], + "agent_summary": "获取节点封面图片地址(文档首图/图片缩略图/类型图标)", + "agent_summary_source": "dws-agent-selection/drive", + "availability": "available", + "avoid_when": [ + "设置文档封面用 doc style cover set" + ], + "canonical_path": "drive.get_cover", + "cli_name": "cover", + "cli_path": "drive cover", + "confirmation": "not_required", + "description": "获取节点封面地址", + "display": "钉盘文件管理", + "effect": "read", + "effect_source": "agent-hint", + "examples": [ + "dws drive cover --node \u003cdentryUuid\u003e --format json" + ], + "field_provenance": { + "agent_summary": { + "candidates": [ + { + "precedence": "reviewed_explicit", + "review_reason": "人工审阅:对照悟空 develop 同名命令实现、Cobra Long/Example 与 Runtime 确认门禁移植;不改变命令身份、参数契约或接口绑定。", + "selected": true, + "source": "internal/cli/schema_hints/selection/drive.json", + "value": "获取节点封面图片地址(文档首图/图片缩略图/类型图标)" + } + ], + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "人工审阅:对照悟空 develop 同名命令实现、Cobra Long/Example 与 Runtime 确认门禁移植;不改变命令身份、参数契约或接口绑定。", + "source": "internal/cli/schema_hints/selection/drive.json", + "value": "获取节点封面图片地址(文档首图/图片缩略图/类型图标)" + }, + "availability": { + "candidates": [ + { + "precedence": "reviewed_explicit", + "review_reason": "dws-tool-metadata/drive marks this tool as reviewed", + "selected": true, + "source": "internal/cli/schema_hints/metadata/drive.json", + "value": "available" + } + ], + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "dws-tool-metadata/drive marks this tool as reviewed", + "source": "internal/cli/schema_hints/metadata/drive.json", + "value": "available" + }, + "avoid_when": { + "candidates": [ + { + "precedence": "reviewed_explicit", + "review_reason": "人工审阅:对照悟空 develop 同名命令实现、Cobra Long/Example 与 Runtime 确认门禁移植;不改变命令身份、参数契约或接口绑定。", + "selected": true, + "source": "internal/cli/schema_hints/selection/drive.json", + "value": [ + "设置文档封面用 doc style cover set" + ] + } + ], + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "人工审阅:对照悟空 develop 同名命令实现、Cobra Long/Example 与 Runtime 确认门禁移植;不改变命令身份、参数契约或接口绑定。", + "source": "internal/cli/schema_hints/selection/drive.json", + "value": [ + "设置文档封面用 doc style cover set" + ] + }, + "canonical_path": { + "candidates": [ + { + "precedence": "command_registry", + "selected": true, + "source": "reviewed_command_registry", + "source_ref": "drive cover", + "value": "drive.get_cover" + } + ], + "precedence": "command_registry", + "resolution": "registry_identity", + "source": "reviewed_command_registry", + "source_ref": "drive cover", + "value": "drive.get_cover" + }, + "confirmation": { + "candidates": [ + { + "precedence": "reviewed_explicit", + "review_reason": "dws-tool-metadata/drive marks this tool as reviewed", + "selected": true, + "source": "internal/cli/schema_hints/metadata/drive.json", + "value": "not_required" + } + ], + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "dws-tool-metadata/drive marks this tool as reviewed", + "source": "internal/cli/schema_hints/metadata/drive.json", + "value": "not_required" + }, + "description": { + "candidates": [ + { + "precedence": "cobra_help", + "selected": true, + "source": "cobra_help", + "value": "获取节点封面地址" + } + ], + "precedence": "cobra_help", + "resolution": "highest_precedence", + "source": "cobra_help", + "value": "获取节点封面地址" + }, + "effect": { + "candidates": [ + { + "precedence": "reviewed_explicit", + "review_reason": "dws-tool-metadata/drive marks this tool as reviewed", + "selected": true, + "source": "internal/cli/schema_hints/metadata/drive.json", + "value": "read" + } + ], + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "dws-tool-metadata/drive marks this tool as reviewed", + "source": "internal/cli/schema_hints/metadata/drive.json", + "value": "read" + }, + "examples": { + "candidates": [ + { + "precedence": "reviewed_explicit", + "review_reason": "人工审阅:对照悟空 develop 同名命令实现、Cobra Long/Example 与 Runtime 确认门禁移植;不改变命令身份、参数契约或接口绑定。", + "selected": true, + "source": "internal/cli/schema_hints/selection/drive.json", + "value": [ + "dws drive cover --node \u003cdentryUuid\u003e --format json" + ] + } + ], + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "人工审阅:对照悟空 develop 同名命令实现、Cobra Long/Example 与 Runtime 确认门禁移植;不改变命令身份、参数契约或接口绑定。", + "source": "internal/cli/schema_hints/selection/drive.json", + "value": [ + "dws drive cover --node \u003cdentryUuid\u003e --format json" + ] + }, + "idempotency": { + "candidates": [ + { + "precedence": "reviewed_explicit", + "review_reason": "dws-tool-metadata/drive marks this tool as reviewed", + "selected": true, + "source": "internal/cli/schema_hints/metadata/drive.json", + "value": "idempotent" + } + ], + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "dws-tool-metadata/drive marks this tool as reviewed", + "source": "internal/cli/schema_hints/metadata/drive.json", + "value": "idempotent" + }, + "interface_mode": { + "candidates": [ + { + "precedence": "reviewed_explicit", + "review_reason": "dws-tool-metadata/drive marks this tool as reviewed", + "selected": true, + "source": "internal/cli/schema_hints/metadata/drive.json", + "value": "composite" + } + ], + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "dws-tool-metadata/drive marks this tool as reviewed", + "source": "internal/cli/schema_hints/metadata/drive.json", + "value": "composite" + }, + "interface_reason": { + "candidates": [ + { + "precedence": "reviewed_explicit", + "review_reason": "dws-tool-metadata/drive marks this tool as reviewed", + "selected": true, + "source": "internal/cli/schema_hints/metadata/drive.json", + "value": "Reviewed unpinned remote adapter: this executable CLI wrapper calls a remote helper that is absent from the pinned MCP metadata snapshot; no single pinned semantically equivalent interface_ref can represent the command." + } + ], + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "dws-tool-metadata/drive marks this tool as reviewed", + "source": "internal/cli/schema_hints/metadata/drive.json", + "value": "Reviewed unpinned remote adapter: this executable CLI wrapper calls a remote helper that is absent from the pinned MCP metadata snapshot; no single pinned semantically equivalent interface_ref can represent the command." + }, + "interface_ref": { + "candidates": [ + { + "precedence": "reviewed_explicit", + "review_reason": "final interface mode composite forbids a direct MCP interface_ref", + "selected": true, + "source": "internal/cli/schema_hints/metadata/drive.json", + "value": null + } + ], + "precedence": "reviewed_explicit", + "resolution": "interface_disposition_matrix", + "review_reason": "final interface mode composite forbids a direct MCP interface_ref", + "source": "internal/cli/schema_hints/metadata/drive.json", + "value": null + }, + "reviewed": { + "candidates": [ + { + "precedence": "reviewed_explicit", + "review_reason": "dws-tool-metadata/drive marks this tool as reviewed", + "selected": true, + "source": "internal/cli/schema_hints/metadata/drive.json", + "value": true + }, + { + "precedence": "reviewed_explicit", + "review_reason": "人工审阅:对照悟空 develop 同名命令实现、Cobra Long/Example 与 Runtime 确认门禁移植;不改变命令身份、参数契约或接口绑定。", + "selected": false, + "source": "internal/cli/schema_hints/selection/drive.json", + "value": true + } + ], + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "dws-tool-metadata/drive marks this tool as reviewed", + "source": "internal/cli/schema_hints/metadata/drive.json", + "value": true + }, + "risk": { + "candidates": [ + { + "precedence": "reviewed_explicit", + "review_reason": "dws-tool-metadata/drive marks this tool as reviewed", + "selected": true, + "source": "internal/cli/schema_hints/metadata/drive.json", + "value": "low" + } + ], + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "dws-tool-metadata/drive marks this tool as reviewed", + "source": "internal/cli/schema_hints/metadata/drive.json", + "value": "low" + }, + "title": { + "candidates": [ + { + "precedence": "cobra_help", + "selected": true, + "source": "cobra_help", + "value": "获取节点封面地址" + } + ], + "precedence": "cobra_help", + "resolution": "highest_precedence", + "source": "cobra_help", + "value": "获取节点封面地址" + }, + "use_when": { + "candidates": [ + { + "precedence": "reviewed_explicit", + "review_reason": "人工审阅:对照悟空 develop 同名命令实现、Cobra Long/Example 与 Runtime 确认门禁移植;不改变命令身份、参数契约或接口绑定。", + "selected": true, + "source": "internal/cli/schema_hints/selection/drive.json", + "value": [ + "用户说 封面/封面图/缩略图/预览图" + ] + } + ], + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "人工审阅:对照悟空 develop 同名命令实现、Cobra Long/Example 与 Runtime 确认门禁移植;不改变命令身份、参数契约或接口绑定。", + "source": "internal/cli/schema_hints/selection/drive.json", + "value": [ + "用户说 封面/封面图/缩略图/预览图" + ] + } + }, + "has_parameters": true, + "idempotency": "idempotent", + "interface_mode": "composite", + "interface_reason": "Reviewed unpinned remote adapter: this executable CLI wrapper calls a remote helper that is absent from the pinned MCP metadata snapshot; no single pinned semantically equivalent interface_ref can represent the command.", + "is_alias": false, + "name": "get_cover", + "parameter_count": 1, + "parameters": { + "node": { + "description": "节点 ID (dentryUuid) 或文档 URL (必填)", + "field_provenance": { + "description": { + "candidates": [ + { + "precedence": "cobra_contract", + "selected": true, + "source": "cobra_usage", + "value": "节点 ID (dentryUuid) 或文档 URL (必填)" + }, + { + "precedence": "default", + "selected": false, + "source": "default", + "value": "" + } + ], + "precedence": "cobra_contract", + "resolution": "highest_precedence", + "source": "cobra_usage", + "value": "节点 ID (dentryUuid) 或文档 URL (必填)" + }, + "property": { + "candidates": [ + { + "precedence": "reviewed_mapping_exclusion", + "review_reason": "Reviewed unpinned adapter: drive.get_cover has no singular pinned interface_ref; --node is a CLI wrapper input and does not publish a direct interface property.", + "selected": true, + "source": "reviewed_mapping_exclusion", + "value": "" + }, + { + "precedence": "inference", + "selected": false, + "source": "flag_name_inference", + "value": "node" + } + ], + "precedence": "reviewed_mapping_exclusion", + "resolution": "highest_precedence", + "review_reason": "Reviewed unpinned adapter: drive.get_cover has no singular pinned interface_ref; --node is a CLI wrapper input and does not publish a direct interface property.", + "source": "reviewed_mapping_exclusion", + "value": "" + }, + "required": { + "candidates": [ + { + "precedence": "inference", + "selected": true, + "source": "usage_required_inference", + "value": true + }, + { + "precedence": "default", + "selected": false, + "source": "default", + "value": false + } + ], + "precedence": "inference", + "resolution": "highest_precedence", + "source": "usage_required_inference", + "value": true + }, + "required_when": { + "candidates": [ + { + "precedence": "default", + "selected": true, + "source": "default", + "value": "" + } + ], + "precedence": "default", + "resolution": "highest_precedence", + "source": "default", + "value": "" + }, + "type": { + "candidates": [ + { + "precedence": "cobra_contract", + "selected": true, + "source": "cobra_flag_type", + "value": "string" + } + ], + "precedence": "cobra_contract", + "resolution": "highest_precedence", + "source": "cobra_flag_type", + "value": "string" + } + }, + "required": true, + "type": "string" + } + }, + "path": "drive.get_cover", + "primary_cli_path": "drive cover", + "product_id": "drive", + "reviewed": true, + "risk": "low", + "source": "reviewed_command_registry", + "title": "获取节点封面地址", + "use_when": [ + "用户说 封面/封面图/缩略图/预览图" + ] + }, "drive.get_file_info": { "agent_metadata_source": "embedded-skill-metadata", "agent_source_refs": [ @@ -5801,6 +7635,881 @@ "用户要查看节点阅读/编辑/评论/点赞/预览/下载等统计维度时" ] }, + "drive.get_star_list": { + "agent_metadata_source": "embedded-skill-metadata", + "agent_source_refs": [ + "CommandRegistry:canonical_path=drive.get_star_list", + "cobra-help:dws drive star list", + "internal/cli/schema_hints/metadata/drive.json", + "internal/cli/schema_hints/selection/drive.json", + "wukong-develop:wukong/products" + ], + "agent_summary": "获取当前用户的收藏列表,支持分页与按内容类型筛选", + "agent_summary_source": "dws-agent-selection/drive", + "availability": "available", + "avoid_when": [ + "操作单个收藏用 mark_star/unmark_star" + ], + "canonical_path": "drive.get_star_list", + "cli_name": "list", + "cli_path": "drive star list", + "confirmation": "not_required", + "description": "获取收藏列表", + "display": "钉盘文件管理", + "effect": "read", + "effect_source": "agent-hint", + "examples": [ + "dws drive star list --format json", + "dws drive star list --content-types doc,sheet --limit 10 --format json" + ], + "field_provenance": { + "agent_summary": { + "candidates": [ + { + "precedence": "reviewed_explicit", + "review_reason": "人工审阅:对照悟空 develop 同名命令实现、Cobra Long/Example 与 Runtime 确认门禁移植;不改变命令身份、参数契约或接口绑定。", + "selected": true, + "source": "internal/cli/schema_hints/selection/drive.json", + "value": "获取当前用户的收藏列表,支持分页与按内容类型筛选" + } + ], + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "人工审阅:对照悟空 develop 同名命令实现、Cobra Long/Example 与 Runtime 确认门禁移植;不改变命令身份、参数契约或接口绑定。", + "source": "internal/cli/schema_hints/selection/drive.json", + "value": "获取当前用户的收藏列表,支持分页与按内容类型筛选" + }, + "availability": { + "candidates": [ + { + "precedence": "reviewed_explicit", + "review_reason": "dws-tool-metadata/drive marks this tool as reviewed", + "selected": true, + "source": "internal/cli/schema_hints/metadata/drive.json", + "value": "available" + } + ], + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "dws-tool-metadata/drive marks this tool as reviewed", + "source": "internal/cli/schema_hints/metadata/drive.json", + "value": "available" + }, + "avoid_when": { + "candidates": [ + { + "precedence": "reviewed_explicit", + "review_reason": "人工审阅:对照悟空 develop 同名命令实现、Cobra Long/Example 与 Runtime 确认门禁移植;不改变命令身份、参数契约或接口绑定。", + "selected": true, + "source": "internal/cli/schema_hints/selection/drive.json", + "value": [ + "操作单个收藏用 mark_star/unmark_star" + ] + } + ], + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "人工审阅:对照悟空 develop 同名命令实现、Cobra Long/Example 与 Runtime 确认门禁移植;不改变命令身份、参数契约或接口绑定。", + "source": "internal/cli/schema_hints/selection/drive.json", + "value": [ + "操作单个收藏用 mark_star/unmark_star" + ] + }, + "canonical_path": { + "candidates": [ + { + "precedence": "command_registry", + "selected": true, + "source": "reviewed_command_registry", + "source_ref": "drive star list", + "value": "drive.get_star_list" + } + ], + "precedence": "command_registry", + "resolution": "registry_identity", + "source": "reviewed_command_registry", + "source_ref": "drive star list", + "value": "drive.get_star_list" + }, + "confirmation": { + "candidates": [ + { + "precedence": "reviewed_explicit", + "review_reason": "dws-tool-metadata/drive marks this tool as reviewed", + "selected": true, + "source": "internal/cli/schema_hints/metadata/drive.json", + "value": "not_required" + } + ], + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "dws-tool-metadata/drive marks this tool as reviewed", + "source": "internal/cli/schema_hints/metadata/drive.json", + "value": "not_required" + }, + "description": { + "candidates": [ + { + "precedence": "cobra_help", + "selected": true, + "source": "cobra_help", + "value": "获取收藏列表" + } + ], + "precedence": "cobra_help", + "resolution": "highest_precedence", + "source": "cobra_help", + "value": "获取收藏列表" + }, + "effect": { + "candidates": [ + { + "precedence": "reviewed_explicit", + "review_reason": "dws-tool-metadata/drive marks this tool as reviewed", + "selected": true, + "source": "internal/cli/schema_hints/metadata/drive.json", + "value": "read" + } + ], + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "dws-tool-metadata/drive marks this tool as reviewed", + "source": "internal/cli/schema_hints/metadata/drive.json", + "value": "read" + }, + "examples": { + "candidates": [ + { + "precedence": "reviewed_explicit", + "review_reason": "人工审阅:对照悟空 develop 同名命令实现、Cobra Long/Example 与 Runtime 确认门禁移植;不改变命令身份、参数契约或接口绑定。", + "selected": true, + "source": "internal/cli/schema_hints/selection/drive.json", + "value": [ + "dws drive star list --format json", + "dws drive star list --content-types doc,sheet --limit 10 --format json" + ] + } + ], + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "人工审阅:对照悟空 develop 同名命令实现、Cobra Long/Example 与 Runtime 确认门禁移植;不改变命令身份、参数契约或接口绑定。", + "source": "internal/cli/schema_hints/selection/drive.json", + "value": [ + "dws drive star list --format json", + "dws drive star list --content-types doc,sheet --limit 10 --format json" + ] + }, + "idempotency": { + "candidates": [ + { + "precedence": "reviewed_explicit", + "review_reason": "dws-tool-metadata/drive marks this tool as reviewed", + "selected": true, + "source": "internal/cli/schema_hints/metadata/drive.json", + "value": "idempotent" + } + ], + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "dws-tool-metadata/drive marks this tool as reviewed", + "source": "internal/cli/schema_hints/metadata/drive.json", + "value": "idempotent" + }, + "interface_mode": { + "candidates": [ + { + "precedence": "reviewed_explicit", + "review_reason": "dws-tool-metadata/drive marks this tool as reviewed", + "selected": true, + "source": "internal/cli/schema_hints/metadata/drive.json", + "value": "composite" + } + ], + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "dws-tool-metadata/drive marks this tool as reviewed", + "source": "internal/cli/schema_hints/metadata/drive.json", + "value": "composite" + }, + "interface_reason": { + "candidates": [ + { + "precedence": "reviewed_explicit", + "review_reason": "dws-tool-metadata/drive marks this tool as reviewed", + "selected": true, + "source": "internal/cli/schema_hints/metadata/drive.json", + "value": "Reviewed unpinned remote adapter: this executable CLI wrapper calls a remote helper that is absent from the pinned MCP metadata snapshot; no single pinned semantically equivalent interface_ref can represent the command." + } + ], + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "dws-tool-metadata/drive marks this tool as reviewed", + "source": "internal/cli/schema_hints/metadata/drive.json", + "value": "Reviewed unpinned remote adapter: this executable CLI wrapper calls a remote helper that is absent from the pinned MCP metadata snapshot; no single pinned semantically equivalent interface_ref can represent the command." + }, + "interface_ref": { + "candidates": [ + { + "precedence": "reviewed_explicit", + "review_reason": "final interface mode composite forbids a direct MCP interface_ref", + "selected": true, + "source": "internal/cli/schema_hints/metadata/drive.json", + "value": null + } + ], + "precedence": "reviewed_explicit", + "resolution": "interface_disposition_matrix", + "review_reason": "final interface mode composite forbids a direct MCP interface_ref", + "source": "internal/cli/schema_hints/metadata/drive.json", + "value": null + }, + "reviewed": { + "candidates": [ + { + "precedence": "reviewed_explicit", + "review_reason": "dws-tool-metadata/drive marks this tool as reviewed", + "selected": true, + "source": "internal/cli/schema_hints/metadata/drive.json", + "value": true + }, + { + "precedence": "reviewed_explicit", + "review_reason": "人工审阅:对照悟空 develop 同名命令实现、Cobra Long/Example 与 Runtime 确认门禁移植;不改变命令身份、参数契约或接口绑定。", + "selected": false, + "source": "internal/cli/schema_hints/selection/drive.json", + "value": true + } + ], + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "dws-tool-metadata/drive marks this tool as reviewed", + "source": "internal/cli/schema_hints/metadata/drive.json", + "value": true + }, + "risk": { + "candidates": [ + { + "precedence": "reviewed_explicit", + "review_reason": "dws-tool-metadata/drive marks this tool as reviewed", + "selected": true, + "source": "internal/cli/schema_hints/metadata/drive.json", + "value": "low" + } + ], + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "dws-tool-metadata/drive marks this tool as reviewed", + "source": "internal/cli/schema_hints/metadata/drive.json", + "value": "low" + }, + "title": { + "candidates": [ + { + "precedence": "cobra_help", + "selected": true, + "source": "cobra_help", + "value": "获取收藏列表" + } + ], + "precedence": "cobra_help", + "resolution": "highest_precedence", + "source": "cobra_help", + "value": "获取收藏列表" + }, + "use_when": { + "candidates": [ + { + "precedence": "reviewed_explicit", + "review_reason": "人工审阅:对照悟空 develop 同名命令实现、Cobra Long/Example 与 Runtime 确认门禁移植;不改变命令身份、参数契约或接口绑定。", + "selected": true, + "source": "internal/cli/schema_hints/selection/drive.json", + "value": [ + "用户说 我的收藏/收藏列表/收藏了哪些文档" + ] + } + ], + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "人工审阅:对照悟空 develop 同名命令实现、Cobra Long/Example 与 Runtime 确认门禁移植;不改变命令身份、参数契约或接口绑定。", + "source": "internal/cli/schema_hints/selection/drive.json", + "value": [ + "用户说 我的收藏/收藏列表/收藏了哪些文档" + ] + } + }, + "group": "star", + "has_parameters": true, + "idempotency": "idempotent", + "interface_mode": "composite", + "interface_reason": "Reviewed unpinned remote adapter: this executable CLI wrapper calls a remote helper that is absent from the pinned MCP metadata snapshot; no single pinned semantically equivalent interface_ref can represent the command.", + "is_alias": false, + "name": "get_star_list", + "parameter_count": 6, + "parameters": { + "content-types": { + "description": "内容类型: doc,sheet,ppt,whiteboard,mind,notable,pdf,other,folder", + "field_provenance": { + "description": { + "candidates": [ + { + "precedence": "cobra_contract", + "selected": true, + "source": "cobra_usage", + "value": "内容类型: doc,sheet,ppt,whiteboard,mind,notable,pdf,other,folder" + }, + { + "precedence": "default", + "selected": false, + "source": "default", + "value": "" + } + ], + "precedence": "cobra_contract", + "resolution": "highest_precedence", + "source": "cobra_usage", + "value": "内容类型: doc,sheet,ppt,whiteboard,mind,notable,pdf,other,folder" + }, + "property": { + "candidates": [ + { + "precedence": "reviewed_mapping_exclusion", + "review_reason": "Reviewed unpinned adapter: drive.get_star_list has no singular pinned interface_ref; --content-types is a CLI wrapper input and does not publish a direct interface property.", + "selected": true, + "source": "reviewed_mapping_exclusion", + "value": "" + }, + { + "precedence": "inference", + "selected": false, + "source": "flag_name_inference", + "value": "contentTypes" + } + ], + "precedence": "reviewed_mapping_exclusion", + "resolution": "highest_precedence", + "review_reason": "Reviewed unpinned adapter: drive.get_star_list has no singular pinned interface_ref; --content-types is a CLI wrapper input and does not publish a direct interface property.", + "source": "reviewed_mapping_exclusion", + "value": "" + }, + "required": { + "candidates": [ + { + "precedence": "default", + "selected": true, + "source": "default", + "value": false + } + ], + "precedence": "default", + "resolution": "fallback", + "source": "default", + "value": false + }, + "required_when": { + "candidates": [ + { + "precedence": "default", + "selected": true, + "source": "default", + "value": "" + } + ], + "precedence": "default", + "resolution": "highest_precedence", + "source": "default", + "value": "" + }, + "type": { + "candidates": [ + { + "precedence": "cobra_contract", + "selected": true, + "source": "cobra_flag_type", + "value": "array" + } + ], + "precedence": "cobra_contract", + "resolution": "highest_precedence", + "source": "cobra_flag_type", + "value": "array" + } + }, + "required": false, + "type": "array" + }, + "cursor": { + "description": "分页游标", + "field_provenance": { + "description": { + "candidates": [ + { + "precedence": "cobra_contract", + "selected": true, + "source": "cobra_usage", + "value": "分页游标" + }, + { + "precedence": "default", + "selected": false, + "source": "default", + "value": "" + } + ], + "precedence": "cobra_contract", + "resolution": "highest_precedence", + "source": "cobra_usage", + "value": "分页游标" + }, + "property": { + "candidates": [ + { + "precedence": "reviewed_mapping_exclusion", + "review_reason": "Reviewed unpinned adapter: drive.get_star_list has no singular pinned interface_ref; --cursor is a CLI wrapper input and does not publish a direct interface property.", + "selected": true, + "source": "reviewed_mapping_exclusion", + "value": "" + }, + { + "precedence": "inference", + "selected": false, + "source": "flag_name_inference", + "value": "cursor" + } + ], + "precedence": "reviewed_mapping_exclusion", + "resolution": "highest_precedence", + "review_reason": "Reviewed unpinned adapter: drive.get_star_list has no singular pinned interface_ref; --cursor is a CLI wrapper input and does not publish a direct interface property.", + "source": "reviewed_mapping_exclusion", + "value": "" + }, + "required": { + "candidates": [ + { + "precedence": "default", + "selected": true, + "source": "default", + "value": false + } + ], + "precedence": "default", + "resolution": "fallback", + "source": "default", + "value": false + }, + "required_when": { + "candidates": [ + { + "precedence": "default", + "selected": true, + "source": "default", + "value": "" + } + ], + "precedence": "default", + "resolution": "highest_precedence", + "source": "default", + "value": "" + }, + "type": { + "candidates": [ + { + "precedence": "cobra_contract", + "selected": true, + "source": "cobra_flag_type", + "value": "string" + } + ], + "precedence": "cobra_contract", + "resolution": "highest_precedence", + "source": "cobra_flag_type", + "value": "string" + } + }, + "required": false, + "type": "string" + }, + "limit": { + "description": "每页条数 (默认 20,最大 20)", + "field_provenance": { + "description": { + "candidates": [ + { + "precedence": "cobra_contract", + "selected": true, + "source": "cobra_usage", + "value": "每页条数 (默认 20,最大 20)" + }, + { + "precedence": "default", + "selected": false, + "source": "default", + "value": "" + } + ], + "precedence": "cobra_contract", + "resolution": "highest_precedence", + "source": "cobra_usage", + "value": "每页条数 (默认 20,最大 20)" + }, + "property": { + "candidates": [ + { + "precedence": "reviewed_mapping_exclusion", + "review_reason": "Reviewed unpinned adapter: drive.get_star_list has no singular pinned interface_ref; --limit is a CLI wrapper input and does not publish a direct interface property.", + "selected": true, + "source": "reviewed_mapping_exclusion", + "value": "" + }, + { + "precedence": "inference", + "selected": false, + "source": "flag_name_inference", + "value": "limit" + } + ], + "precedence": "reviewed_mapping_exclusion", + "resolution": "highest_precedence", + "review_reason": "Reviewed unpinned adapter: drive.get_star_list has no singular pinned interface_ref; --limit is a CLI wrapper input and does not publish a direct interface property.", + "source": "reviewed_mapping_exclusion", + "value": "" + }, + "required": { + "candidates": [ + { + "precedence": "cobra_contract", + "selected": true, + "source": "cobra_nonzero_default", + "value": false + }, + { + "precedence": "default", + "selected": false, + "source": "default", + "value": false + } + ], + "precedence": "cobra_contract", + "resolution": "highest_precedence", + "source": "cobra_nonzero_default", + "value": false + }, + "required_when": { + "candidates": [ + { + "precedence": "default", + "selected": true, + "source": "default", + "value": "" + } + ], + "precedence": "default", + "resolution": "highest_precedence", + "source": "default", + "value": "" + }, + "type": { + "candidates": [ + { + "precedence": "cobra_contract", + "selected": true, + "source": "cobra_flag_type", + "value": "integer" + } + ], + "precedence": "cobra_contract", + "resolution": "highest_precedence", + "source": "cobra_flag_type", + "value": "integer" + } + }, + "required": false, + "type": "integer" + }, + "order-by": { + "description": "排序字段: createTime", + "field_provenance": { + "description": { + "candidates": [ + { + "precedence": "cobra_contract", + "selected": true, + "source": "cobra_usage", + "value": "排序字段: createTime" + }, + { + "precedence": "default", + "selected": false, + "source": "default", + "value": "" + } + ], + "precedence": "cobra_contract", + "resolution": "highest_precedence", + "source": "cobra_usage", + "value": "排序字段: createTime" + }, + "property": { + "candidates": [ + { + "precedence": "reviewed_mapping_exclusion", + "review_reason": "Reviewed unpinned adapter: drive.get_star_list has no singular pinned interface_ref; --order-by is a CLI wrapper input and does not publish a direct interface property.", + "selected": true, + "source": "reviewed_mapping_exclusion", + "value": "" + }, + { + "precedence": "inference", + "selected": false, + "source": "flag_name_inference", + "value": "orderBy" + } + ], + "precedence": "reviewed_mapping_exclusion", + "resolution": "highest_precedence", + "review_reason": "Reviewed unpinned adapter: drive.get_star_list has no singular pinned interface_ref; --order-by is a CLI wrapper input and does not publish a direct interface property.", + "source": "reviewed_mapping_exclusion", + "value": "" + }, + "required": { + "candidates": [ + { + "precedence": "default", + "selected": true, + "source": "default", + "value": false + } + ], + "precedence": "default", + "resolution": "fallback", + "source": "default", + "value": false + }, + "required_when": { + "candidates": [ + { + "precedence": "default", + "selected": true, + "source": "default", + "value": "" + } + ], + "precedence": "default", + "resolution": "highest_precedence", + "source": "default", + "value": "" + }, + "type": { + "candidates": [ + { + "precedence": "cobra_contract", + "selected": true, + "source": "cobra_flag_type", + "value": "string" + } + ], + "precedence": "cobra_contract", + "resolution": "highest_precedence", + "source": "cobra_flag_type", + "value": "string" + } + }, + "required": false, + "type": "string" + }, + "resource-types": { + "description": "资源大类: DENTRY, TEAM, WORKSPACE", + "field_provenance": { + "description": { + "candidates": [ + { + "precedence": "cobra_contract", + "selected": true, + "source": "cobra_usage", + "value": "资源大类: DENTRY, TEAM, WORKSPACE" + }, + { + "precedence": "default", + "selected": false, + "source": "default", + "value": "" + } + ], + "precedence": "cobra_contract", + "resolution": "highest_precedence", + "source": "cobra_usage", + "value": "资源大类: DENTRY, TEAM, WORKSPACE" + }, + "property": { + "candidates": [ + { + "precedence": "reviewed_mapping_exclusion", + "review_reason": "Reviewed unpinned adapter: drive.get_star_list has no singular pinned interface_ref; --resource-types is a CLI wrapper input and does not publish a direct interface property.", + "selected": true, + "source": "reviewed_mapping_exclusion", + "value": "" + }, + { + "precedence": "inference", + "selected": false, + "source": "flag_name_inference", + "value": "resourceTypes" + } + ], + "precedence": "reviewed_mapping_exclusion", + "resolution": "highest_precedence", + "review_reason": "Reviewed unpinned adapter: drive.get_star_list has no singular pinned interface_ref; --resource-types is a CLI wrapper input and does not publish a direct interface property.", + "source": "reviewed_mapping_exclusion", + "value": "" + }, + "required": { + "candidates": [ + { + "precedence": "default", + "selected": true, + "source": "default", + "value": false + } + ], + "precedence": "default", + "resolution": "fallback", + "source": "default", + "value": false + }, + "required_when": { + "candidates": [ + { + "precedence": "default", + "selected": true, + "source": "default", + "value": "" + } + ], + "precedence": "default", + "resolution": "highest_precedence", + "source": "default", + "value": "" + }, + "type": { + "candidates": [ + { + "precedence": "cobra_contract", + "selected": true, + "source": "cobra_flag_type", + "value": "array" + } + ], + "precedence": "cobra_contract", + "resolution": "highest_precedence", + "source": "cobra_flag_type", + "value": "array" + } + }, + "required": false, + "type": "array" + }, + "sort": { + "description": "排序方向: asc|desc", + "field_provenance": { + "description": { + "candidates": [ + { + "precedence": "cobra_contract", + "selected": true, + "source": "cobra_usage", + "value": "排序方向: asc|desc" + }, + { + "precedence": "default", + "selected": false, + "source": "default", + "value": "" + } + ], + "precedence": "cobra_contract", + "resolution": "highest_precedence", + "source": "cobra_usage", + "value": "排序方向: asc|desc" + }, + "property": { + "candidates": [ + { + "precedence": "reviewed_mapping_exclusion", + "review_reason": "Reviewed unpinned adapter: drive.get_star_list has no singular pinned interface_ref; --sort is a CLI wrapper input and does not publish a direct interface property.", + "selected": true, + "source": "reviewed_mapping_exclusion", + "value": "" + }, + { + "precedence": "inference", + "selected": false, + "source": "flag_name_inference", + "value": "sort" + } + ], + "precedence": "reviewed_mapping_exclusion", + "resolution": "highest_precedence", + "review_reason": "Reviewed unpinned adapter: drive.get_star_list has no singular pinned interface_ref; --sort is a CLI wrapper input and does not publish a direct interface property.", + "source": "reviewed_mapping_exclusion", + "value": "" + }, + "required": { + "candidates": [ + { + "precedence": "default", + "selected": true, + "source": "default", + "value": false + } + ], + "precedence": "default", + "resolution": "fallback", + "source": "default", + "value": false + }, + "required_when": { + "candidates": [ + { + "precedence": "default", + "selected": true, + "source": "default", + "value": "" + } + ], + "precedence": "default", + "resolution": "highest_precedence", + "source": "default", + "value": "" + }, + "type": { + "candidates": [ + { + "precedence": "cobra_contract", + "selected": true, + "source": "cobra_flag_type", + "value": "string" + } + ], + "precedence": "cobra_contract", + "resolution": "highest_precedence", + "source": "cobra_flag_type", + "value": "string" + } + }, + "required": false, + "type": "string" + } + }, + "path": "drive.get_star_list", + "primary_cli_path": "drive star list", + "product_id": "drive", + "reviewed": true, + "risk": "low", + "source": "reviewed_command_registry", + "title": "获取收藏列表", + "use_when": [ + "用户说 我的收藏/收藏列表/收藏了哪些文档" + ] + }, "drive.get_upload_info": { "agent_metadata_source": "embedded-skill-metadata", "agent_source_refs": [ @@ -7059,7 +9768,7 @@ "is_alias": false, "metadata_source": "embedded-mcp-metadata", "name": "list_files", - "parameter_count": 8, + "parameter_count": 13, "parameters": { "cursor": { "description": "分页游标,首次不传 (可选)", @@ -7152,6 +9861,97 @@ "required": false, "type": "string" }, + "depth": { + "default": "1", + "description": "递归列出子目录层级,默认 1(仅当前层),最大 5;与 --cursor/--limit 互斥;与 --workspace 组合时走知识库递归 (可选)", + "field_provenance": { + "description": { + "candidates": [ + { + "precedence": "cobra_contract", + "selected": true, + "source": "cobra_usage", + "value": "递归列出子目录层级,默认 1(仅当前层),最大 5;与 --cursor/--limit 互斥;与 --workspace 组合时走知识库递归 (可选)" + }, + { + "precedence": "default", + "selected": false, + "source": "default", + "value": "" + } + ], + "precedence": "cobra_contract", + "resolution": "highest_precedence", + "source": "cobra_usage", + "value": "递归列出子目录层级,默认 1(仅当前层),最大 5;与 --cursor/--limit 互斥;与 --workspace 组合时走知识库递归 (可选)" + }, + "property": { + "candidates": [ + { + "precedence": "inference", + "selected": true, + "source": "flag_name_inference", + "value": "depth" + } + ], + "precedence": "inference", + "resolution": "highest_precedence", + "source": "flag_name_inference", + "value": "depth" + }, + "required": { + "candidates": [ + { + "precedence": "cobra_contract", + "selected": true, + "source": "cobra_nonzero_default", + "value": false + }, + { + "precedence": "default", + "selected": false, + "source": "default", + "value": false + } + ], + "precedence": "cobra_contract", + "resolution": "highest_precedence", + "source": "cobra_nonzero_default", + "value": false + }, + "required_when": { + "candidates": [ + { + "precedence": "default", + "selected": true, + "source": "default", + "value": "" + } + ], + "precedence": "default", + "resolution": "highest_precedence", + "source": "default", + "value": "" + }, + "type": { + "candidates": [ + { + "precedence": "cobra_contract", + "selected": true, + "source": "cobra_flag_type", + "value": "integer" + } + ], + "precedence": "cobra_contract", + "resolution": "highest_precedence", + "source": "cobra_flag_type", + "value": "integer" + } + }, + "property": "depth", + "required": false, + "type": "integer" + }, "folder": { "description": "父节点 ID (dentryUuid),不传则列出空间根目录 (可选)", "field_provenance": { @@ -7341,6 +10141,90 @@ "required": false, "type": "integer" }, + "node": { + "description": "文件 ID (dentryUuid) 或 URL (--versions 模式下必填)", + "field_provenance": { + "description": { + "candidates": [ + { + "precedence": "cobra_contract", + "selected": true, + "source": "cobra_usage", + "value": "文件 ID (dentryUuid) 或 URL (--versions 模式下必填)" + }, + { + "precedence": "default", + "selected": false, + "source": "default", + "value": "" + } + ], + "precedence": "cobra_contract", + "resolution": "highest_precedence", + "source": "cobra_usage", + "value": "文件 ID (dentryUuid) 或 URL (--versions 模式下必填)" + }, + "property": { + "candidates": [ + { + "precedence": "inference", + "selected": true, + "source": "flag_name_inference", + "value": "node" + } + ], + "precedence": "inference", + "resolution": "highest_precedence", + "source": "flag_name_inference", + "value": "node" + }, + "required": { + "candidates": [ + { + "precedence": "default", + "selected": true, + "source": "default", + "value": false + } + ], + "precedence": "default", + "resolution": "fallback", + "source": "default", + "value": false + }, + "required_when": { + "candidates": [ + { + "precedence": "default", + "selected": true, + "source": "default", + "value": "" + } + ], + "precedence": "default", + "resolution": "highest_precedence", + "source": "default", + "value": "" + }, + "type": { + "candidates": [ + { + "precedence": "cobra_contract", + "selected": true, + "source": "cobra_flag_type", + "value": "string" + } + ], + "precedence": "cobra_contract", + "resolution": "highest_precedence", + "source": "cobra_flag_type", + "value": "string" + } + }, + "property": "node", + "required": false, + "type": "string" + }, "order": { "description": "排序方向: asc|desc,默认 desc (可选,仅钉盘)", "field_provenance": { @@ -7529,6 +10413,174 @@ "required": false, "type": "string" }, + "pattern": { + "description": "按名称通配过滤结果,如 \"*日报*\" (客户端过滤) (可选)", + "field_provenance": { + "description": { + "candidates": [ + { + "precedence": "cobra_contract", + "selected": true, + "source": "cobra_usage", + "value": "按名称通配过滤结果,如 \"*日报*\" (客户端过滤) (可选)" + }, + { + "precedence": "default", + "selected": false, + "source": "default", + "value": "" + } + ], + "precedence": "cobra_contract", + "resolution": "highest_precedence", + "source": "cobra_usage", + "value": "按名称通配过滤结果,如 \"*日报*\" (客户端过滤) (可选)" + }, + "property": { + "candidates": [ + { + "precedence": "inference", + "selected": true, + "source": "flag_name_inference", + "value": "pattern" + } + ], + "precedence": "inference", + "resolution": "highest_precedence", + "source": "flag_name_inference", + "value": "pattern" + }, + "required": { + "candidates": [ + { + "precedence": "default", + "selected": true, + "source": "default", + "value": false + } + ], + "precedence": "default", + "resolution": "fallback", + "source": "default", + "value": false + }, + "required_when": { + "candidates": [ + { + "precedence": "default", + "selected": true, + "source": "default", + "value": "" + } + ], + "precedence": "default", + "resolution": "highest_precedence", + "source": "default", + "value": "" + }, + "type": { + "candidates": [ + { + "precedence": "cobra_contract", + "selected": true, + "source": "cobra_flag_type", + "value": "string" + } + ], + "precedence": "cobra_contract", + "resolution": "highest_precedence", + "source": "cobra_flag_type", + "value": "string" + } + }, + "property": "pattern", + "required": false, + "type": "string" + }, + "quiet": { + "description": "关闭递归进度输出(stderr),不影响 stdout JSON (仅 --depth\u003e1 时有效) (可选)", + "field_provenance": { + "description": { + "candidates": [ + { + "precedence": "cobra_contract", + "selected": true, + "source": "cobra_usage", + "value": "关闭递归进度输出(stderr),不影响 stdout JSON (仅 --depth\u003e1 时有效) (可选)" + }, + { + "precedence": "default", + "selected": false, + "source": "default", + "value": "" + } + ], + "precedence": "cobra_contract", + "resolution": "highest_precedence", + "source": "cobra_usage", + "value": "关闭递归进度输出(stderr),不影响 stdout JSON (仅 --depth\u003e1 时有效) (可选)" + }, + "property": { + "candidates": [ + { + "precedence": "inference", + "selected": true, + "source": "flag_name_inference", + "value": "quiet" + } + ], + "precedence": "inference", + "resolution": "highest_precedence", + "source": "flag_name_inference", + "value": "quiet" + }, + "required": { + "candidates": [ + { + "precedence": "default", + "selected": true, + "source": "default", + "value": false + } + ], + "precedence": "default", + "resolution": "fallback", + "source": "default", + "value": false + }, + "required_when": { + "candidates": [ + { + "precedence": "default", + "selected": true, + "source": "default", + "value": "" + } + ], + "precedence": "default", + "resolution": "highest_precedence", + "source": "default", + "value": "" + }, + "type": { + "candidates": [ + { + "precedence": "cobra_contract", + "selected": true, + "source": "cobra_flag_type", + "value": "boolean" + } + ], + "precedence": "cobra_contract", + "resolution": "highest_precedence", + "source": "cobra_flag_type", + "value": "boolean" + } + }, + "property": "quiet", + "required": false, + "type": "boolean" + }, "space-id": { "description": "钉盘空间 ID (纯数字),不传则使用「我的文件」(可选)", "field_provenance": { @@ -7711,6 +10763,90 @@ "required": false, "type": "boolean" }, + "versions": { + "description": "列出文件历史版本而非文件列表 (需配合 --node)", + "field_provenance": { + "description": { + "candidates": [ + { + "precedence": "cobra_contract", + "selected": true, + "source": "cobra_usage", + "value": "列出文件历史版本而非文件列表 (需配合 --node)" + }, + { + "precedence": "default", + "selected": false, + "source": "default", + "value": "" + } + ], + "precedence": "cobra_contract", + "resolution": "highest_precedence", + "source": "cobra_usage", + "value": "列出文件历史版本而非文件列表 (需配合 --node)" + }, + "property": { + "candidates": [ + { + "precedence": "inference", + "selected": true, + "source": "flag_name_inference", + "value": "versions" + } + ], + "precedence": "inference", + "resolution": "highest_precedence", + "source": "flag_name_inference", + "value": "versions" + }, + "required": { + "candidates": [ + { + "precedence": "default", + "selected": true, + "source": "default", + "value": false + } + ], + "precedence": "default", + "resolution": "fallback", + "source": "default", + "value": false + }, + "required_when": { + "candidates": [ + { + "precedence": "default", + "selected": true, + "source": "default", + "value": "" + } + ], + "precedence": "default", + "resolution": "highest_precedence", + "source": "default", + "value": "" + }, + "type": { + "candidates": [ + { + "precedence": "cobra_contract", + "selected": true, + "source": "cobra_flag_type", + "value": "boolean" + } + ], + "precedence": "cobra_contract", + "resolution": "highest_precedence", + "source": "cobra_flag_type", + "value": "boolean" + } + }, + "property": "versions", + "required": false, + "type": "boolean" + }, "workspace": { "description": "文档空间/知识库 ID (加密 string 或 URL),传入则路由到文档空间 (可选)", "field_provenance": { @@ -9274,6 +12410,423 @@ "兼容入口:枚举钉盘企业空间或「我的文件」空间以拿 spaceId/rootFolderId 时" ] }, + "drive.mark_star": { + "agent_metadata_source": "embedded-skill-metadata", + "agent_source_refs": [ + "CommandRegistry:canonical_path=drive.mark_star", + "cobra-help:dws drive star add", + "internal/cli/schema_hints/metadata/drive.json", + "internal/cli/schema_hints/selection/drive.json", + "wukong-develop:wukong/products" + ], + "agent_summary": "收藏文档/文件到当前用户收藏列表", + "agent_summary_source": "dws-agent-selection/drive", + "availability": "available", + "avoid_when": [ + "取消收藏用 unmark_star;查看收藏列表用 get_star_list" + ], + "canonical_path": "drive.mark_star", + "cli_name": "add", + "cli_path": "drive star add", + "confirmation": "not_required", + "description": "收藏文档", + "display": "钉盘文件管理", + "effect": "write", + "effect_source": "agent-hint", + "examples": [ + "dws drive star add --node \u003cnodeId\u003e --format json" + ], + "field_provenance": { + "agent_summary": { + "candidates": [ + { + "precedence": "reviewed_explicit", + "review_reason": "人工审阅:对照悟空 develop 同名命令实现、Cobra Long/Example 与 Runtime 确认门禁移植;不改变命令身份、参数契约或接口绑定。", + "selected": true, + "source": "internal/cli/schema_hints/selection/drive.json", + "value": "收藏文档/文件到当前用户收藏列表" + } + ], + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "人工审阅:对照悟空 develop 同名命令实现、Cobra Long/Example 与 Runtime 确认门禁移植;不改变命令身份、参数契约或接口绑定。", + "source": "internal/cli/schema_hints/selection/drive.json", + "value": "收藏文档/文件到当前用户收藏列表" + }, + "availability": { + "candidates": [ + { + "precedence": "reviewed_explicit", + "review_reason": "dws-tool-metadata/drive marks this tool as reviewed", + "selected": true, + "source": "internal/cli/schema_hints/metadata/drive.json", + "value": "available" + } + ], + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "dws-tool-metadata/drive marks this tool as reviewed", + "source": "internal/cli/schema_hints/metadata/drive.json", + "value": "available" + }, + "avoid_when": { + "candidates": [ + { + "precedence": "reviewed_explicit", + "review_reason": "人工审阅:对照悟空 develop 同名命令实现、Cobra Long/Example 与 Runtime 确认门禁移植;不改变命令身份、参数契约或接口绑定。", + "selected": true, + "source": "internal/cli/schema_hints/selection/drive.json", + "value": [ + "取消收藏用 unmark_star;查看收藏列表用 get_star_list" + ] + } + ], + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "人工审阅:对照悟空 develop 同名命令实现、Cobra Long/Example 与 Runtime 确认门禁移植;不改变命令身份、参数契约或接口绑定。", + "source": "internal/cli/schema_hints/selection/drive.json", + "value": [ + "取消收藏用 unmark_star;查看收藏列表用 get_star_list" + ] + }, + "canonical_path": { + "candidates": [ + { + "precedence": "command_registry", + "selected": true, + "source": "reviewed_command_registry", + "source_ref": "drive star add", + "value": "drive.mark_star" + } + ], + "precedence": "command_registry", + "resolution": "registry_identity", + "source": "reviewed_command_registry", + "source_ref": "drive star add", + "value": "drive.mark_star" + }, + "confirmation": { + "candidates": [ + { + "precedence": "reviewed_explicit", + "review_reason": "dws-tool-metadata/drive marks this tool as reviewed", + "selected": true, + "source": "internal/cli/schema_hints/metadata/drive.json", + "value": "not_required" + } + ], + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "dws-tool-metadata/drive marks this tool as reviewed", + "source": "internal/cli/schema_hints/metadata/drive.json", + "value": "not_required" + }, + "description": { + "candidates": [ + { + "precedence": "cobra_help", + "selected": true, + "source": "cobra_help", + "value": "收藏文档" + } + ], + "precedence": "cobra_help", + "resolution": "highest_precedence", + "source": "cobra_help", + "value": "收藏文档" + }, + "effect": { + "candidates": [ + { + "precedence": "reviewed_explicit", + "review_reason": "dws-tool-metadata/drive marks this tool as reviewed", + "selected": true, + "source": "internal/cli/schema_hints/metadata/drive.json", + "value": "write" + } + ], + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "dws-tool-metadata/drive marks this tool as reviewed", + "source": "internal/cli/schema_hints/metadata/drive.json", + "value": "write" + }, + "examples": { + "candidates": [ + { + "precedence": "reviewed_explicit", + "review_reason": "人工审阅:对照悟空 develop 同名命令实现、Cobra Long/Example 与 Runtime 确认门禁移植;不改变命令身份、参数契约或接口绑定。", + "selected": true, + "source": "internal/cli/schema_hints/selection/drive.json", + "value": [ + "dws drive star add --node \u003cnodeId\u003e --format json" + ] + } + ], + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "人工审阅:对照悟空 develop 同名命令实现、Cobra Long/Example 与 Runtime 确认门禁移植;不改变命令身份、参数契约或接口绑定。", + "source": "internal/cli/schema_hints/selection/drive.json", + "value": [ + "dws drive star add --node \u003cnodeId\u003e --format json" + ] + }, + "idempotency": { + "candidates": [ + { + "precedence": "reviewed_explicit", + "review_reason": "dws-tool-metadata/drive marks this tool as reviewed", + "selected": true, + "source": "internal/cli/schema_hints/metadata/drive.json", + "value": "idempotent" + } + ], + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "dws-tool-metadata/drive marks this tool as reviewed", + "source": "internal/cli/schema_hints/metadata/drive.json", + "value": "idempotent" + }, + "interface_mode": { + "candidates": [ + { + "precedence": "reviewed_explicit", + "review_reason": "dws-tool-metadata/drive marks this tool as reviewed", + "selected": true, + "source": "internal/cli/schema_hints/metadata/drive.json", + "value": "composite" + } + ], + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "dws-tool-metadata/drive marks this tool as reviewed", + "source": "internal/cli/schema_hints/metadata/drive.json", + "value": "composite" + }, + "interface_reason": { + "candidates": [ + { + "precedence": "reviewed_explicit", + "review_reason": "dws-tool-metadata/drive marks this tool as reviewed", + "selected": true, + "source": "internal/cli/schema_hints/metadata/drive.json", + "value": "Reviewed unpinned remote adapter: this executable CLI wrapper calls a remote helper that is absent from the pinned MCP metadata snapshot; no single pinned semantically equivalent interface_ref can represent the command." + } + ], + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "dws-tool-metadata/drive marks this tool as reviewed", + "source": "internal/cli/schema_hints/metadata/drive.json", + "value": "Reviewed unpinned remote adapter: this executable CLI wrapper calls a remote helper that is absent from the pinned MCP metadata snapshot; no single pinned semantically equivalent interface_ref can represent the command." + }, + "interface_ref": { + "candidates": [ + { + "precedence": "reviewed_explicit", + "review_reason": "final interface mode composite forbids a direct MCP interface_ref", + "selected": true, + "source": "internal/cli/schema_hints/metadata/drive.json", + "value": null + } + ], + "precedence": "reviewed_explicit", + "resolution": "interface_disposition_matrix", + "review_reason": "final interface mode composite forbids a direct MCP interface_ref", + "source": "internal/cli/schema_hints/metadata/drive.json", + "value": null + }, + "reviewed": { + "candidates": [ + { + "precedence": "reviewed_explicit", + "review_reason": "dws-tool-metadata/drive marks this tool as reviewed", + "selected": true, + "source": "internal/cli/schema_hints/metadata/drive.json", + "value": true + }, + { + "precedence": "reviewed_explicit", + "review_reason": "人工审阅:对照悟空 develop 同名命令实现、Cobra Long/Example 与 Runtime 确认门禁移植;不改变命令身份、参数契约或接口绑定。", + "selected": false, + "source": "internal/cli/schema_hints/selection/drive.json", + "value": true + } + ], + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "dws-tool-metadata/drive marks this tool as reviewed", + "source": "internal/cli/schema_hints/metadata/drive.json", + "value": true + }, + "risk": { + "candidates": [ + { + "precedence": "reviewed_explicit", + "review_reason": "dws-tool-metadata/drive marks this tool as reviewed", + "selected": true, + "source": "internal/cli/schema_hints/metadata/drive.json", + "value": "low" + } + ], + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "dws-tool-metadata/drive marks this tool as reviewed", + "source": "internal/cli/schema_hints/metadata/drive.json", + "value": "low" + }, + "title": { + "candidates": [ + { + "precedence": "cobra_help", + "selected": true, + "source": "cobra_help", + "value": "收藏文档" + } + ], + "precedence": "cobra_help", + "resolution": "highest_precedence", + "source": "cobra_help", + "value": "收藏文档" + }, + "use_when": { + "candidates": [ + { + "precedence": "reviewed_explicit", + "review_reason": "人工审阅:对照悟空 develop 同名命令实现、Cobra Long/Example 与 Runtime 确认门禁移植;不改变命令身份、参数契约或接口绑定。", + "selected": true, + "source": "internal/cli/schema_hints/selection/drive.json", + "value": [ + "用户说 收藏这个文档/加个收藏/标星" + ] + } + ], + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "人工审阅:对照悟空 develop 同名命令实现、Cobra Long/Example 与 Runtime 确认门禁移植;不改变命令身份、参数契约或接口绑定。", + "source": "internal/cli/schema_hints/selection/drive.json", + "value": [ + "用户说 收藏这个文档/加个收藏/标星" + ] + } + }, + "group": "star", + "has_parameters": true, + "idempotency": "idempotent", + "interface_mode": "composite", + "interface_reason": "Reviewed unpinned remote adapter: this executable CLI wrapper calls a remote helper that is absent from the pinned MCP metadata snapshot; no single pinned semantically equivalent interface_ref can represent the command.", + "is_alias": false, + "name": "mark_star", + "parameter_count": 1, + "parameters": { + "node": { + "description": "文档 ID 或 URL (必填)", + "field_provenance": { + "description": { + "candidates": [ + { + "precedence": "cobra_contract", + "selected": true, + "source": "cobra_usage", + "value": "文档 ID 或 URL (必填)" + }, + { + "precedence": "default", + "selected": false, + "source": "default", + "value": "" + } + ], + "precedence": "cobra_contract", + "resolution": "highest_precedence", + "source": "cobra_usage", + "value": "文档 ID 或 URL (必填)" + }, + "property": { + "candidates": [ + { + "precedence": "reviewed_mapping_exclusion", + "review_reason": "Reviewed unpinned adapter: drive.mark_star has no singular pinned interface_ref; --node is a CLI wrapper input and does not publish a direct interface property.", + "selected": true, + "source": "reviewed_mapping_exclusion", + "value": "" + }, + { + "precedence": "inference", + "selected": false, + "source": "flag_name_inference", + "value": "node" + } + ], + "precedence": "reviewed_mapping_exclusion", + "resolution": "highest_precedence", + "review_reason": "Reviewed unpinned adapter: drive.mark_star has no singular pinned interface_ref; --node is a CLI wrapper input and does not publish a direct interface property.", + "source": "reviewed_mapping_exclusion", + "value": "" + }, + "required": { + "candidates": [ + { + "precedence": "inference", + "selected": true, + "source": "usage_required_inference", + "value": true + }, + { + "precedence": "default", + "selected": false, + "source": "default", + "value": false + } + ], + "precedence": "inference", + "resolution": "highest_precedence", + "source": "usage_required_inference", + "value": true + }, + "required_when": { + "candidates": [ + { + "precedence": "default", + "selected": true, + "source": "default", + "value": "" + } + ], + "precedence": "default", + "resolution": "highest_precedence", + "source": "default", + "value": "" + }, + "type": { + "candidates": [ + { + "precedence": "cobra_contract", + "selected": true, + "source": "cobra_flag_type", + "value": "string" + } + ], + "precedence": "cobra_contract", + "resolution": "highest_precedence", + "source": "cobra_flag_type", + "value": "string" + } + }, + "required": true, + "type": "string" + } + }, + "path": "drive.mark_star", + "primary_cli_path": "drive star add", + "product_id": "drive", + "reviewed": true, + "risk": "low", + "source": "reviewed_command_registry", + "title": "收藏文档", + "use_when": [ + "用户说 收藏这个文档/加个收藏/标星" + ] + }, "drive.move_document": { "agent_metadata_source": "embedded-skill-metadata", "agent_source_refs": [ @@ -12911,6 +16464,423 @@ "用户明确要求关闭文件的互联网公开发布,使外部链接失效时" ] }, + "drive.query_permission_apply_info": { + "agent_metadata_source": "embedded-skill-metadata", + "agent_source_refs": [ + "CommandRegistry:canonical_path=drive.query_permission_apply_info", + "cobra-help:dws drive permission apply-info", + "internal/cli/schema_hints/metadata/drive.json", + "internal/cli/schema_hints/selection/drive.json", + "wukong-develop:wukong/products" + ], + "agent_summary": "查询节点可申请的权限角色列表与审批人列表", + "agent_summary_source": "dws-agent-selection/drive", + "availability": "available", + "avoid_when": [ + "实际发起申请用 apply_permission" + ], + "canonical_path": "drive.query_permission_apply_info", + "cli_name": "apply-info", + "cli_path": "drive permission apply-info", + "confirmation": "not_required", + "description": "查询节点可申请的角色与审批人", + "display": "钉盘文件管理", + "effect": "read", + "effect_source": "agent-hint", + "examples": [ + "dws drive permission apply-info --node \u003cDOC_ID\u003e --format json" + ], + "field_provenance": { + "agent_summary": { + "candidates": [ + { + "precedence": "reviewed_explicit", + "review_reason": "人工审阅:对照悟空 develop 同名命令实现、Cobra Long/Example 与 Runtime 确认门禁移植;不改变命令身份、参数契约或接口绑定。", + "selected": true, + "source": "internal/cli/schema_hints/selection/drive.json", + "value": "查询节点可申请的权限角色列表与审批人列表" + } + ], + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "人工审阅:对照悟空 develop 同名命令实现、Cobra Long/Example 与 Runtime 确认门禁移植;不改变命令身份、参数契约或接口绑定。", + "source": "internal/cli/schema_hints/selection/drive.json", + "value": "查询节点可申请的权限角色列表与审批人列表" + }, + "availability": { + "candidates": [ + { + "precedence": "reviewed_explicit", + "review_reason": "dws-tool-metadata/drive marks this tool as reviewed", + "selected": true, + "source": "internal/cli/schema_hints/metadata/drive.json", + "value": "available" + } + ], + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "dws-tool-metadata/drive marks this tool as reviewed", + "source": "internal/cli/schema_hints/metadata/drive.json", + "value": "available" + }, + "avoid_when": { + "candidates": [ + { + "precedence": "reviewed_explicit", + "review_reason": "人工审阅:对照悟空 develop 同名命令实现、Cobra Long/Example 与 Runtime 确认门禁移植;不改变命令身份、参数契约或接口绑定。", + "selected": true, + "source": "internal/cli/schema_hints/selection/drive.json", + "value": [ + "实际发起申请用 apply_permission" + ] + } + ], + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "人工审阅:对照悟空 develop 同名命令实现、Cobra Long/Example 与 Runtime 确认门禁移植;不改变命令身份、参数契约或接口绑定。", + "source": "internal/cli/schema_hints/selection/drive.json", + "value": [ + "实际发起申请用 apply_permission" + ] + }, + "canonical_path": { + "candidates": [ + { + "precedence": "command_registry", + "selected": true, + "source": "reviewed_command_registry", + "source_ref": "drive permission apply-info", + "value": "drive.query_permission_apply_info" + } + ], + "precedence": "command_registry", + "resolution": "registry_identity", + "source": "reviewed_command_registry", + "source_ref": "drive permission apply-info", + "value": "drive.query_permission_apply_info" + }, + "confirmation": { + "candidates": [ + { + "precedence": "reviewed_explicit", + "review_reason": "dws-tool-metadata/drive marks this tool as reviewed", + "selected": true, + "source": "internal/cli/schema_hints/metadata/drive.json", + "value": "not_required" + } + ], + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "dws-tool-metadata/drive marks this tool as reviewed", + "source": "internal/cli/schema_hints/metadata/drive.json", + "value": "not_required" + }, + "description": { + "candidates": [ + { + "precedence": "cobra_help", + "selected": true, + "source": "cobra_help", + "value": "查询节点可申请的角色与审批人" + } + ], + "precedence": "cobra_help", + "resolution": "highest_precedence", + "source": "cobra_help", + "value": "查询节点可申请的角色与审批人" + }, + "effect": { + "candidates": [ + { + "precedence": "reviewed_explicit", + "review_reason": "dws-tool-metadata/drive marks this tool as reviewed", + "selected": true, + "source": "internal/cli/schema_hints/metadata/drive.json", + "value": "read" + } + ], + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "dws-tool-metadata/drive marks this tool as reviewed", + "source": "internal/cli/schema_hints/metadata/drive.json", + "value": "read" + }, + "examples": { + "candidates": [ + { + "precedence": "reviewed_explicit", + "review_reason": "人工审阅:对照悟空 develop 同名命令实现、Cobra Long/Example 与 Runtime 确认门禁移植;不改变命令身份、参数契约或接口绑定。", + "selected": true, + "source": "internal/cli/schema_hints/selection/drive.json", + "value": [ + "dws drive permission apply-info --node \u003cDOC_ID\u003e --format json" + ] + } + ], + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "人工审阅:对照悟空 develop 同名命令实现、Cobra Long/Example 与 Runtime 确认门禁移植;不改变命令身份、参数契约或接口绑定。", + "source": "internal/cli/schema_hints/selection/drive.json", + "value": [ + "dws drive permission apply-info --node \u003cDOC_ID\u003e --format json" + ] + }, + "idempotency": { + "candidates": [ + { + "precedence": "reviewed_explicit", + "review_reason": "dws-tool-metadata/drive marks this tool as reviewed", + "selected": true, + "source": "internal/cli/schema_hints/metadata/drive.json", + "value": "idempotent" + } + ], + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "dws-tool-metadata/drive marks this tool as reviewed", + "source": "internal/cli/schema_hints/metadata/drive.json", + "value": "idempotent" + }, + "interface_mode": { + "candidates": [ + { + "precedence": "reviewed_explicit", + "review_reason": "dws-tool-metadata/drive marks this tool as reviewed", + "selected": true, + "source": "internal/cli/schema_hints/metadata/drive.json", + "value": "composite" + } + ], + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "dws-tool-metadata/drive marks this tool as reviewed", + "source": "internal/cli/schema_hints/metadata/drive.json", + "value": "composite" + }, + "interface_reason": { + "candidates": [ + { + "precedence": "reviewed_explicit", + "review_reason": "dws-tool-metadata/drive marks this tool as reviewed", + "selected": true, + "source": "internal/cli/schema_hints/metadata/drive.json", + "value": "Reviewed unpinned remote adapter: this executable CLI wrapper calls a remote helper that is absent from the pinned MCP metadata snapshot; no single pinned semantically equivalent interface_ref can represent the command." + } + ], + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "dws-tool-metadata/drive marks this tool as reviewed", + "source": "internal/cli/schema_hints/metadata/drive.json", + "value": "Reviewed unpinned remote adapter: this executable CLI wrapper calls a remote helper that is absent from the pinned MCP metadata snapshot; no single pinned semantically equivalent interface_ref can represent the command." + }, + "interface_ref": { + "candidates": [ + { + "precedence": "reviewed_explicit", + "review_reason": "final interface mode composite forbids a direct MCP interface_ref", + "selected": true, + "source": "internal/cli/schema_hints/metadata/drive.json", + "value": null + } + ], + "precedence": "reviewed_explicit", + "resolution": "interface_disposition_matrix", + "review_reason": "final interface mode composite forbids a direct MCP interface_ref", + "source": "internal/cli/schema_hints/metadata/drive.json", + "value": null + }, + "reviewed": { + "candidates": [ + { + "precedence": "reviewed_explicit", + "review_reason": "dws-tool-metadata/drive marks this tool as reviewed", + "selected": true, + "source": "internal/cli/schema_hints/metadata/drive.json", + "value": true + }, + { + "precedence": "reviewed_explicit", + "review_reason": "人工审阅:对照悟空 develop 同名命令实现、Cobra Long/Example 与 Runtime 确认门禁移植;不改变命令身份、参数契约或接口绑定。", + "selected": false, + "source": "internal/cli/schema_hints/selection/drive.json", + "value": true + } + ], + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "dws-tool-metadata/drive marks this tool as reviewed", + "source": "internal/cli/schema_hints/metadata/drive.json", + "value": true + }, + "risk": { + "candidates": [ + { + "precedence": "reviewed_explicit", + "review_reason": "dws-tool-metadata/drive marks this tool as reviewed", + "selected": true, + "source": "internal/cli/schema_hints/metadata/drive.json", + "value": "low" + } + ], + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "dws-tool-metadata/drive marks this tool as reviewed", + "source": "internal/cli/schema_hints/metadata/drive.json", + "value": "low" + }, + "title": { + "candidates": [ + { + "precedence": "cobra_help", + "selected": true, + "source": "cobra_help", + "value": "查询节点可申请的角色与审批人" + } + ], + "precedence": "cobra_help", + "resolution": "highest_precedence", + "source": "cobra_help", + "value": "查询节点可申请的角色与审批人" + }, + "use_when": { + "candidates": [ + { + "precedence": "reviewed_explicit", + "review_reason": "人工审阅:对照悟空 develop 同名命令实现、Cobra Long/Example 与 Runtime 确认门禁移植;不改变命令身份、参数契约或接口绑定。", + "selected": true, + "source": "internal/cli/schema_hints/selection/drive.json", + "value": [ + "无权限访问文档时,先查可申请角色与审批人" + ] + } + ], + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "人工审阅:对照悟空 develop 同名命令实现、Cobra Long/Example 与 Runtime 确认门禁移植;不改变命令身份、参数契约或接口绑定。", + "source": "internal/cli/schema_hints/selection/drive.json", + "value": [ + "无权限访问文档时,先查可申请角色与审批人" + ] + } + }, + "group": "permission", + "has_parameters": true, + "idempotency": "idempotent", + "interface_mode": "composite", + "interface_reason": "Reviewed unpinned remote adapter: this executable CLI wrapper calls a remote helper that is absent from the pinned MCP metadata snapshot; no single pinned semantically equivalent interface_ref can represent the command.", + "is_alias": false, + "name": "query_permission_apply_info", + "parameter_count": 1, + "parameters": { + "node": { + "description": "目标节点 ID 或 URL (必填)", + "field_provenance": { + "description": { + "candidates": [ + { + "precedence": "cobra_contract", + "selected": true, + "source": "cobra_usage", + "value": "目标节点 ID 或 URL (必填)" + }, + { + "precedence": "default", + "selected": false, + "source": "default", + "value": "" + } + ], + "precedence": "cobra_contract", + "resolution": "highest_precedence", + "source": "cobra_usage", + "value": "目标节点 ID 或 URL (必填)" + }, + "property": { + "candidates": [ + { + "precedence": "reviewed_mapping_exclusion", + "review_reason": "Reviewed unpinned adapter: drive.query_permission_apply_info has no singular pinned interface_ref; --node is a CLI wrapper input and does not publish a direct interface property.", + "selected": true, + "source": "reviewed_mapping_exclusion", + "value": "" + }, + { + "precedence": "inference", + "selected": false, + "source": "flag_name_inference", + "value": "node" + } + ], + "precedence": "reviewed_mapping_exclusion", + "resolution": "highest_precedence", + "review_reason": "Reviewed unpinned adapter: drive.query_permission_apply_info has no singular pinned interface_ref; --node is a CLI wrapper input and does not publish a direct interface property.", + "source": "reviewed_mapping_exclusion", + "value": "" + }, + "required": { + "candidates": [ + { + "precedence": "inference", + "selected": true, + "source": "usage_required_inference", + "value": true + }, + { + "precedence": "default", + "selected": false, + "source": "default", + "value": false + } + ], + "precedence": "inference", + "resolution": "highest_precedence", + "source": "usage_required_inference", + "value": true + }, + "required_when": { + "candidates": [ + { + "precedence": "default", + "selected": true, + "source": "default", + "value": "" + } + ], + "precedence": "default", + "resolution": "highest_precedence", + "source": "default", + "value": "" + }, + "type": { + "candidates": [ + { + "precedence": "cobra_contract", + "selected": true, + "source": "cobra_flag_type", + "value": "string" + } + ], + "precedence": "cobra_contract", + "resolution": "highest_precedence", + "source": "cobra_flag_type", + "value": "string" + } + }, + "required": true, + "type": "string" + } + }, + "path": "drive.query_permission_apply_info", + "primary_cli_path": "drive permission apply-info", + "product_id": "drive", + "reviewed": true, + "risk": "low", + "source": "reviewed_command_registry", + "title": "查询节点可申请的角色与审批人", + "use_when": [ + "无权限访问文档时,先查可申请角色与审批人" + ] + }, "drive.recent": { "agent_metadata_source": "embedded-skill-metadata", "agent_source_refs": [ @@ -15449,6 +19419,519 @@ "用户要重命名钉盘/文档空间中的文件、文档或文件夹时;实际执行会读取节点类型和当前扩展名,仅去掉完全匹配的一层后缀" ] }, + "drive.revert_file_version": { + "agent_metadata_source": "embedded-skill-metadata", + "agent_source_refs": [ + "CommandRegistry:canonical_path=drive.revert_file_version", + "cobra-help:dws drive revert", + "internal/cli/schema_hints/metadata/drive.json", + "internal/cli/schema_hints/selection/drive.json", + "wukong-develop:wukong/products" + ], + "agent_summary": "回滚普通文件到指定历史版本(生成新最新版本,历史不丢失)", + "agent_summary_source": "dws-agent-selection/drive", + "availability": "available", + "avoid_when": [ + "在线文档回滚用 doc version revert;在线表格用 sheet version revert" + ], + "canonical_path": "drive.revert_file_version", + "cli_name": "revert", + "cli_path": "drive revert", + "confirmation": "user_required", + "description": "将指定文件回滚到某个历史版本。仅支持普通文件(Word、Excel、PDF、图片等)。\n在线文档请用 dws doc version revert,在线表格请用 dws sheet version revert。", + "display": "钉盘文件管理", + "effect": "write", + "effect_source": "agent-hint", + "examples": [ + "dws drive revert --node \u003cdentryUuid\u003e --version 3 --format json" + ], + "field_provenance": { + "agent_summary": { + "candidates": [ + { + "precedence": "reviewed_explicit", + "review_reason": "人工审阅:对照悟空 develop 同名命令实现、Cobra Long/Example 与 Runtime 确认门禁移植;不改变命令身份、参数契约或接口绑定。", + "selected": true, + "source": "internal/cli/schema_hints/selection/drive.json", + "value": "回滚普通文件到指定历史版本(生成新最新版本,历史不丢失)" + } + ], + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "人工审阅:对照悟空 develop 同名命令实现、Cobra Long/Example 与 Runtime 确认门禁移植;不改变命令身份、参数契约或接口绑定。", + "source": "internal/cli/schema_hints/selection/drive.json", + "value": "回滚普通文件到指定历史版本(生成新最新版本,历史不丢失)" + }, + "availability": { + "candidates": [ + { + "precedence": "reviewed_explicit", + "review_reason": "dws-tool-metadata/drive marks this tool as reviewed", + "selected": true, + "source": "internal/cli/schema_hints/metadata/drive.json", + "value": "available" + } + ], + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "dws-tool-metadata/drive marks this tool as reviewed", + "source": "internal/cli/schema_hints/metadata/drive.json", + "value": "available" + }, + "avoid_when": { + "candidates": [ + { + "precedence": "reviewed_explicit", + "review_reason": "人工审阅:对照悟空 develop 同名命令实现、Cobra Long/Example 与 Runtime 确认门禁移植;不改变命令身份、参数契约或接口绑定。", + "selected": true, + "source": "internal/cli/schema_hints/selection/drive.json", + "value": [ + "在线文档回滚用 doc version revert;在线表格用 sheet version revert" + ] + } + ], + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "人工审阅:对照悟空 develop 同名命令实现、Cobra Long/Example 与 Runtime 确认门禁移植;不改变命令身份、参数契约或接口绑定。", + "source": "internal/cli/schema_hints/selection/drive.json", + "value": [ + "在线文档回滚用 doc version revert;在线表格用 sheet version revert" + ] + }, + "canonical_path": { + "candidates": [ + { + "precedence": "command_registry", + "selected": true, + "source": "reviewed_command_registry", + "source_ref": "drive revert", + "value": "drive.revert_file_version" + } + ], + "precedence": "command_registry", + "resolution": "registry_identity", + "source": "reviewed_command_registry", + "source_ref": "drive revert", + "value": "drive.revert_file_version" + }, + "confirmation": { + "candidates": [ + { + "precedence": "reviewed_explicit", + "review_reason": "dws-tool-metadata/drive marks this tool as reviewed", + "selected": true, + "source": "internal/cli/schema_hints/metadata/drive.json", + "value": "user_required" + } + ], + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "dws-tool-metadata/drive marks this tool as reviewed", + "source": "internal/cli/schema_hints/metadata/drive.json", + "value": "user_required" + }, + "description": { + "candidates": [ + { + "precedence": "cobra_help", + "selected": true, + "source": "cobra_help", + "value": "将指定文件回滚到某个历史版本。仅支持普通文件(Word、Excel、PDF、图片等)。\n在线文档请用 dws doc version revert,在线表格请用 dws sheet version revert。" + } + ], + "precedence": "cobra_help", + "resolution": "highest_precedence", + "source": "cobra_help", + "value": "将指定文件回滚到某个历史版本。仅支持普通文件(Word、Excel、PDF、图片等)。\n在线文档请用 dws doc version revert,在线表格请用 dws sheet version revert。" + }, + "effect": { + "candidates": [ + { + "precedence": "reviewed_explicit", + "review_reason": "dws-tool-metadata/drive marks this tool as reviewed", + "selected": true, + "source": "internal/cli/schema_hints/metadata/drive.json", + "value": "write" + } + ], + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "dws-tool-metadata/drive marks this tool as reviewed", + "source": "internal/cli/schema_hints/metadata/drive.json", + "value": "write" + }, + "examples": { + "candidates": [ + { + "precedence": "reviewed_explicit", + "review_reason": "人工审阅:对照悟空 develop 同名命令实现、Cobra Long/Example 与 Runtime 确认门禁移植;不改变命令身份、参数契约或接口绑定。", + "selected": true, + "source": "internal/cli/schema_hints/selection/drive.json", + "value": [ + "dws drive revert --node \u003cdentryUuid\u003e --version 3 --format json" + ] + } + ], + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "人工审阅:对照悟空 develop 同名命令实现、Cobra Long/Example 与 Runtime 确认门禁移植;不改变命令身份、参数契约或接口绑定。", + "source": "internal/cli/schema_hints/selection/drive.json", + "value": [ + "dws drive revert --node \u003cdentryUuid\u003e --version 3 --format json" + ] + }, + "idempotency": { + "candidates": [ + { + "precedence": "reviewed_explicit", + "review_reason": "dws-tool-metadata/drive marks this tool as reviewed", + "selected": true, + "source": "internal/cli/schema_hints/metadata/drive.json", + "value": "non_idempotent" + } + ], + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "dws-tool-metadata/drive marks this tool as reviewed", + "source": "internal/cli/schema_hints/metadata/drive.json", + "value": "non_idempotent" + }, + "interface_mode": { + "candidates": [ + { + "precedence": "reviewed_explicit", + "review_reason": "dws-tool-metadata/drive marks this tool as reviewed", + "selected": true, + "source": "internal/cli/schema_hints/metadata/drive.json", + "value": "composite" + } + ], + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "dws-tool-metadata/drive marks this tool as reviewed", + "source": "internal/cli/schema_hints/metadata/drive.json", + "value": "composite" + }, + "interface_reason": { + "candidates": [ + { + "precedence": "reviewed_explicit", + "review_reason": "dws-tool-metadata/drive marks this tool as reviewed", + "selected": true, + "source": "internal/cli/schema_hints/metadata/drive.json", + "value": "Reviewed unpinned remote adapter: this executable CLI wrapper calls a remote helper that is absent from the pinned MCP metadata snapshot; no single pinned semantically equivalent interface_ref can represent the command." + } + ], + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "dws-tool-metadata/drive marks this tool as reviewed", + "source": "internal/cli/schema_hints/metadata/drive.json", + "value": "Reviewed unpinned remote adapter: this executable CLI wrapper calls a remote helper that is absent from the pinned MCP metadata snapshot; no single pinned semantically equivalent interface_ref can represent the command." + }, + "interface_ref": { + "candidates": [ + { + "precedence": "reviewed_explicit", + "review_reason": "final interface mode composite forbids a direct MCP interface_ref", + "selected": true, + "source": "internal/cli/schema_hints/metadata/drive.json", + "value": null + } + ], + "precedence": "reviewed_explicit", + "resolution": "interface_disposition_matrix", + "review_reason": "final interface mode composite forbids a direct MCP interface_ref", + "source": "internal/cli/schema_hints/metadata/drive.json", + "value": null + }, + "reviewed": { + "candidates": [ + { + "precedence": "reviewed_explicit", + "review_reason": "dws-tool-metadata/drive marks this tool as reviewed", + "selected": true, + "source": "internal/cli/schema_hints/metadata/drive.json", + "value": true + }, + { + "precedence": "reviewed_explicit", + "review_reason": "人工审阅:对照悟空 develop 同名命令实现、Cobra Long/Example 与 Runtime 确认门禁移植;不改变命令身份、参数契约或接口绑定。", + "selected": false, + "source": "internal/cli/schema_hints/selection/drive.json", + "value": true + } + ], + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "dws-tool-metadata/drive marks this tool as reviewed", + "source": "internal/cli/schema_hints/metadata/drive.json", + "value": true + }, + "risk": { + "candidates": [ + { + "precedence": "reviewed_explicit", + "review_reason": "dws-tool-metadata/drive marks this tool as reviewed", + "selected": true, + "source": "internal/cli/schema_hints/metadata/drive.json", + "value": "medium" + } + ], + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "dws-tool-metadata/drive marks this tool as reviewed", + "source": "internal/cli/schema_hints/metadata/drive.json", + "value": "medium" + }, + "title": { + "candidates": [ + { + "precedence": "cobra_help", + "selected": true, + "source": "cobra_help", + "value": "[危险] 回滚文件到指定历史版本" + } + ], + "precedence": "cobra_help", + "resolution": "highest_precedence", + "source": "cobra_help", + "value": "[危险] 回滚文件到指定历史版本" + }, + "use_when": { + "candidates": [ + { + "precedence": "reviewed_explicit", + "review_reason": "人工审阅:对照悟空 develop 同名命令实现、Cobra Long/Example 与 Runtime 确认门禁移植;不改变命令身份、参数契约或接口绑定。", + "selected": true, + "source": "internal/cli/schema_hints/selection/drive.json", + "value": [ + "用户说 回滚版本/恢复到某个版本/版本回退,且目标是普通文件" + ] + } + ], + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "人工审阅:对照悟空 develop 同名命令实现、Cobra Long/Example 与 Runtime 确认门禁移植;不改变命令身份、参数契约或接口绑定。", + "source": "internal/cli/schema_hints/selection/drive.json", + "value": [ + "用户说 回滚版本/恢复到某个版本/版本回退,且目标是普通文件" + ] + } + }, + "has_parameters": true, + "idempotency": "non_idempotent", + "interface_mode": "composite", + "interface_reason": "Reviewed unpinned remote adapter: this executable CLI wrapper calls a remote helper that is absent from the pinned MCP metadata snapshot; no single pinned semantically equivalent interface_ref can represent the command.", + "is_alias": false, + "name": "revert_file_version", + "parameter_count": 2, + "parameters": { + "node": { + "description": "文件 ID (dentryUuid) 或 URL (必填)", + "field_provenance": { + "description": { + "candidates": [ + { + "precedence": "cobra_contract", + "selected": true, + "source": "cobra_usage", + "value": "文件 ID (dentryUuid) 或 URL (必填)" + }, + { + "precedence": "default", + "selected": false, + "source": "default", + "value": "" + } + ], + "precedence": "cobra_contract", + "resolution": "highest_precedence", + "source": "cobra_usage", + "value": "文件 ID (dentryUuid) 或 URL (必填)" + }, + "property": { + "candidates": [ + { + "precedence": "reviewed_mapping_exclusion", + "review_reason": "Reviewed unpinned adapter: drive.revert_file_version has no singular pinned interface_ref; --node is a CLI wrapper input and does not publish a direct interface property.", + "selected": true, + "source": "reviewed_mapping_exclusion", + "value": "" + }, + { + "precedence": "inference", + "selected": false, + "source": "flag_name_inference", + "value": "node" + } + ], + "precedence": "reviewed_mapping_exclusion", + "resolution": "highest_precedence", + "review_reason": "Reviewed unpinned adapter: drive.revert_file_version has no singular pinned interface_ref; --node is a CLI wrapper input and does not publish a direct interface property.", + "source": "reviewed_mapping_exclusion", + "value": "" + }, + "required": { + "candidates": [ + { + "precedence": "inference", + "selected": true, + "source": "usage_required_inference", + "value": true + }, + { + "precedence": "default", + "selected": false, + "source": "default", + "value": false + } + ], + "precedence": "inference", + "resolution": "highest_precedence", + "source": "usage_required_inference", + "value": true + }, + "required_when": { + "candidates": [ + { + "precedence": "default", + "selected": true, + "source": "default", + "value": "" + } + ], + "precedence": "default", + "resolution": "highest_precedence", + "source": "default", + "value": "" + }, + "type": { + "candidates": [ + { + "precedence": "cobra_contract", + "selected": true, + "source": "cobra_flag_type", + "value": "string" + } + ], + "precedence": "cobra_contract", + "resolution": "highest_precedence", + "source": "cobra_flag_type", + "value": "string" + } + }, + "required": true, + "type": "string" + }, + "version": { + "description": "要回滚到的历史版本号 (必填,正整数)", + "field_provenance": { + "description": { + "candidates": [ + { + "precedence": "cobra_contract", + "selected": true, + "source": "cobra_usage", + "value": "要回滚到的历史版本号 (必填,正整数)" + }, + { + "precedence": "default", + "selected": false, + "source": "default", + "value": "" + } + ], + "precedence": "cobra_contract", + "resolution": "highest_precedence", + "source": "cobra_usage", + "value": "要回滚到的历史版本号 (必填,正整数)" + }, + "property": { + "candidates": [ + { + "precedence": "reviewed_mapping_exclusion", + "review_reason": "Reviewed unpinned adapter: drive.revert_file_version has no singular pinned interface_ref; --version is a CLI wrapper input and does not publish a direct interface property.", + "selected": true, + "source": "reviewed_mapping_exclusion", + "value": "" + }, + { + "precedence": "inference", + "selected": false, + "source": "flag_name_inference", + "value": "version" + } + ], + "precedence": "reviewed_mapping_exclusion", + "resolution": "highest_precedence", + "review_reason": "Reviewed unpinned adapter: drive.revert_file_version has no singular pinned interface_ref; --version is a CLI wrapper input and does not publish a direct interface property.", + "source": "reviewed_mapping_exclusion", + "value": "" + }, + "required": { + "candidates": [ + { + "precedence": "inference", + "selected": true, + "source": "usage_required_inference", + "value": true + }, + { + "precedence": "default", + "selected": false, + "source": "default", + "value": false + } + ], + "precedence": "inference", + "resolution": "highest_precedence", + "source": "usage_required_inference", + "value": true + }, + "required_when": { + "candidates": [ + { + "precedence": "default", + "selected": true, + "source": "default", + "value": "" + } + ], + "precedence": "default", + "resolution": "highest_precedence", + "source": "default", + "value": "" + }, + "type": { + "candidates": [ + { + "precedence": "cobra_contract", + "selected": true, + "source": "cobra_flag_type", + "value": "integer" + } + ], + "precedence": "cobra_contract", + "resolution": "highest_precedence", + "source": "cobra_flag_type", + "value": "integer" + } + }, + "required": true, + "type": "integer" + } + }, + "path": "drive.revert_file_version", + "primary_cli_path": "drive revert", + "product_id": "drive", + "reviewed": true, + "risk": "medium", + "source": "reviewed_command_registry", + "title": "[危险] 回滚文件到指定历史版本", + "use_when": [ + "用户说 回滚版本/恢复到某个版本/版本回退,且目标是普通文件" + ] + }, "drive.search_files": { "agent_metadata_source": "embedded-skill-metadata", "agent_source_refs": [ @@ -21543,6 +26026,1204 @@ "当你只记得文档标题或关键词、想在文档空间/知识库中检索在线文档(区别于 +search 检索钉盘文件)时使用;输入 query 关键词,返回匹配的文档及其节点信息。" ] }, + "drive.transfer_owner": { + "agent_metadata_source": "embedded-skill-metadata", + "agent_source_refs": [ + "CommandRegistry:canonical_path=drive.transfer_owner", + "cobra-help:dws drive permission transfer-owner", + "internal/cli/schema_hints/metadata/drive.json", + "internal/cli/schema_hints/selection/drive.json", + "wukong-develop:wukong/products" + ], + "agent_summary": "转交文档或知识库所有者给指定用户(不可逆)", + "agent_summary_source": "dws-agent-selection/drive", + "availability": "available", + "avoid_when": [ + "普通协作权限变更用 permission add/update/remove" + ], + "canonical_path": "drive.transfer_owner", + "cli_name": "transfer-owner", + "cli_path": "drive permission transfer-owner", + "confirmation": "user_required", + "description": "转交文档或知识库的所有者给指定用户。此操作不可逆,执行前需要确认。\n\n--node 和 --workspace 二选一。转交后原所有者保留角色由 --reserve-role 指定。\n使用 --yes 跳过确认时,--reserve-role 和 --recursive 必须显式指定。", + "display": "钉盘文件管理", + "effect": "write", + "effect_source": "agent-hint", + "examples": [ + "dws drive permission transfer-owner --node \u003cDOC_ID\u003e --new-owner \u003cuserId\u003e --reserve-role EDITOR --recursive=false --format json" + ], + "field_provenance": { + "agent_summary": { + "candidates": [ + { + "precedence": "reviewed_explicit", + "review_reason": "人工审阅:对照悟空 develop 同名命令实现、Cobra Long/Example 与 Runtime 确认门禁移植;不改变命令身份、参数契约或接口绑定。", + "selected": true, + "source": "internal/cli/schema_hints/selection/drive.json", + "value": "转交文档或知识库所有者给指定用户(不可逆)" + } + ], + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "人工审阅:对照悟空 develop 同名命令实现、Cobra Long/Example 与 Runtime 确认门禁移植;不改变命令身份、参数契约或接口绑定。", + "source": "internal/cli/schema_hints/selection/drive.json", + "value": "转交文档或知识库所有者给指定用户(不可逆)" + }, + "availability": { + "candidates": [ + { + "precedence": "reviewed_explicit", + "review_reason": "dws-tool-metadata/drive marks this tool as reviewed", + "selected": true, + "source": "internal/cli/schema_hints/metadata/drive.json", + "value": "available" + } + ], + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "dws-tool-metadata/drive marks this tool as reviewed", + "source": "internal/cli/schema_hints/metadata/drive.json", + "value": "available" + }, + "avoid_when": { + "candidates": [ + { + "precedence": "reviewed_explicit", + "review_reason": "人工审阅:对照悟空 develop 同名命令实现、Cobra Long/Example 与 Runtime 确认门禁移植;不改变命令身份、参数契约或接口绑定。", + "selected": true, + "source": "internal/cli/schema_hints/selection/drive.json", + "value": [ + "普通协作权限变更用 permission add/update/remove" + ] + } + ], + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "人工审阅:对照悟空 develop 同名命令实现、Cobra Long/Example 与 Runtime 确认门禁移植;不改变命令身份、参数契约或接口绑定。", + "source": "internal/cli/schema_hints/selection/drive.json", + "value": [ + "普通协作权限变更用 permission add/update/remove" + ] + }, + "canonical_path": { + "candidates": [ + { + "precedence": "command_registry", + "selected": true, + "source": "reviewed_command_registry", + "source_ref": "drive permission transfer-owner", + "value": "drive.transfer_owner" + } + ], + "precedence": "command_registry", + "resolution": "registry_identity", + "source": "reviewed_command_registry", + "source_ref": "drive permission transfer-owner", + "value": "drive.transfer_owner" + }, + "confirmation": { + "candidates": [ + { + "precedence": "reviewed_explicit", + "review_reason": "dws-tool-metadata/drive marks this tool as reviewed", + "selected": true, + "source": "internal/cli/schema_hints/metadata/drive.json", + "value": "user_required" + } + ], + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "dws-tool-metadata/drive marks this tool as reviewed", + "source": "internal/cli/schema_hints/metadata/drive.json", + "value": "user_required" + }, + "description": { + "candidates": [ + { + "precedence": "cobra_help", + "selected": true, + "source": "cobra_help", + "value": "转交文档或知识库的所有者给指定用户。此操作不可逆,执行前需要确认。\n\n--node 和 --workspace 二选一。转交后原所有者保留角色由 --reserve-role 指定。\n使用 --yes 跳过确认时,--reserve-role 和 --recursive 必须显式指定。" + } + ], + "precedence": "cobra_help", + "resolution": "highest_precedence", + "source": "cobra_help", + "value": "转交文档或知识库的所有者给指定用户。此操作不可逆,执行前需要确认。\n\n--node 和 --workspace 二选一。转交后原所有者保留角色由 --reserve-role 指定。\n使用 --yes 跳过确认时,--reserve-role 和 --recursive 必须显式指定。" + }, + "effect": { + "candidates": [ + { + "precedence": "reviewed_explicit", + "review_reason": "dws-tool-metadata/drive marks this tool as reviewed", + "selected": true, + "source": "internal/cli/schema_hints/metadata/drive.json", + "value": "write" + } + ], + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "dws-tool-metadata/drive marks this tool as reviewed", + "source": "internal/cli/schema_hints/metadata/drive.json", + "value": "write" + }, + "examples": { + "candidates": [ + { + "precedence": "reviewed_explicit", + "review_reason": "人工审阅:对照悟空 develop 同名命令实现、Cobra Long/Example 与 Runtime 确认门禁移植;不改变命令身份、参数契约或接口绑定。", + "selected": true, + "source": "internal/cli/schema_hints/selection/drive.json", + "value": [ + "dws drive permission transfer-owner --node \u003cDOC_ID\u003e --new-owner \u003cuserId\u003e --reserve-role EDITOR --recursive=false --format json" + ] + } + ], + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "人工审阅:对照悟空 develop 同名命令实现、Cobra Long/Example 与 Runtime 确认门禁移植;不改变命令身份、参数契约或接口绑定。", + "source": "internal/cli/schema_hints/selection/drive.json", + "value": [ + "dws drive permission transfer-owner --node \u003cDOC_ID\u003e --new-owner \u003cuserId\u003e --reserve-role EDITOR --recursive=false --format json" + ] + }, + "idempotency": { + "candidates": [ + { + "precedence": "reviewed_explicit", + "review_reason": "dws-tool-metadata/drive marks this tool as reviewed", + "selected": true, + "source": "internal/cli/schema_hints/metadata/drive.json", + "value": "non_idempotent" + } + ], + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "dws-tool-metadata/drive marks this tool as reviewed", + "source": "internal/cli/schema_hints/metadata/drive.json", + "value": "non_idempotent" + }, + "interface_mode": { + "candidates": [ + { + "precedence": "reviewed_explicit", + "review_reason": "dws-tool-metadata/drive marks this tool as reviewed", + "selected": true, + "source": "internal/cli/schema_hints/metadata/drive.json", + "value": "composite" + } + ], + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "dws-tool-metadata/drive marks this tool as reviewed", + "source": "internal/cli/schema_hints/metadata/drive.json", + "value": "composite" + }, + "interface_reason": { + "candidates": [ + { + "precedence": "reviewed_explicit", + "review_reason": "dws-tool-metadata/drive marks this tool as reviewed", + "selected": true, + "source": "internal/cli/schema_hints/metadata/drive.json", + "value": "Reviewed unpinned remote adapter: this executable CLI wrapper calls a remote helper that is absent from the pinned MCP metadata snapshot; no single pinned semantically equivalent interface_ref can represent the command." + } + ], + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "dws-tool-metadata/drive marks this tool as reviewed", + "source": "internal/cli/schema_hints/metadata/drive.json", + "value": "Reviewed unpinned remote adapter: this executable CLI wrapper calls a remote helper that is absent from the pinned MCP metadata snapshot; no single pinned semantically equivalent interface_ref can represent the command." + }, + "interface_ref": { + "candidates": [ + { + "precedence": "reviewed_explicit", + "review_reason": "final interface mode composite forbids a direct MCP interface_ref", + "selected": true, + "source": "internal/cli/schema_hints/metadata/drive.json", + "value": null + } + ], + "precedence": "reviewed_explicit", + "resolution": "interface_disposition_matrix", + "review_reason": "final interface mode composite forbids a direct MCP interface_ref", + "source": "internal/cli/schema_hints/metadata/drive.json", + "value": null + }, + "reviewed": { + "candidates": [ + { + "precedence": "reviewed_explicit", + "review_reason": "dws-tool-metadata/drive marks this tool as reviewed", + "selected": true, + "source": "internal/cli/schema_hints/metadata/drive.json", + "value": true + }, + { + "precedence": "reviewed_explicit", + "review_reason": "人工审阅:对照悟空 develop 同名命令实现、Cobra Long/Example 与 Runtime 确认门禁移植;不改变命令身份、参数契约或接口绑定。", + "selected": false, + "source": "internal/cli/schema_hints/selection/drive.json", + "value": true + } + ], + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "dws-tool-metadata/drive marks this tool as reviewed", + "source": "internal/cli/schema_hints/metadata/drive.json", + "value": true + }, + "risk": { + "candidates": [ + { + "precedence": "reviewed_explicit", + "review_reason": "dws-tool-metadata/drive marks this tool as reviewed", + "selected": true, + "source": "internal/cli/schema_hints/metadata/drive.json", + "value": "high" + } + ], + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "dws-tool-metadata/drive marks this tool as reviewed", + "source": "internal/cli/schema_hints/metadata/drive.json", + "value": "high" + }, + "title": { + "candidates": [ + { + "precedence": "cobra_help", + "selected": true, + "source": "cobra_help", + "value": "[危险] 转交所有者" + } + ], + "precedence": "cobra_help", + "resolution": "highest_precedence", + "source": "cobra_help", + "value": "[危险] 转交所有者" + }, + "use_when": { + "candidates": [ + { + "precedence": "reviewed_explicit", + "review_reason": "人工审阅:对照悟空 develop 同名命令实现、Cobra Long/Example 与 Runtime 确认门禁移植;不改变命令身份、参数契约或接口绑定。", + "selected": true, + "source": "internal/cli/schema_hints/selection/drive.json", + "value": [ + "用户明确要求转交文档/知识库所有权" + ] + } + ], + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "人工审阅:对照悟空 develop 同名命令实现、Cobra Long/Example 与 Runtime 确认门禁移植;不改变命令身份、参数契约或接口绑定。", + "source": "internal/cli/schema_hints/selection/drive.json", + "value": [ + "用户明确要求转交文档/知识库所有权" + ] + } + }, + "group": "permission", + "has_parameters": true, + "idempotency": "non_idempotent", + "interface_mode": "composite", + "interface_reason": "Reviewed unpinned remote adapter: this executable CLI wrapper calls a remote helper that is absent from the pinned MCP metadata snapshot; no single pinned semantically equivalent interface_ref can represent the command.", + "is_alias": false, + "name": "transfer_owner", + "parameter_count": 5, + "parameters": { + "new-owner": { + "description": "新所有者的用户 userId (必填)", + "field_provenance": { + "description": { + "candidates": [ + { + "precedence": "cobra_contract", + "selected": true, + "source": "cobra_usage", + "value": "新所有者的用户 userId (必填)" + }, + { + "precedence": "default", + "selected": false, + "source": "default", + "value": "" + } + ], + "precedence": "cobra_contract", + "resolution": "highest_precedence", + "source": "cobra_usage", + "value": "新所有者的用户 userId (必填)" + }, + "property": { + "candidates": [ + { + "precedence": "reviewed_mapping_exclusion", + "review_reason": "Reviewed unpinned adapter: drive.transfer_owner has no singular pinned interface_ref; --new-owner is a CLI wrapper input and does not publish a direct interface property.", + "selected": true, + "source": "reviewed_mapping_exclusion", + "value": "" + }, + { + "precedence": "inference", + "selected": false, + "source": "flag_name_inference", + "value": "newOwner" + } + ], + "precedence": "reviewed_mapping_exclusion", + "resolution": "highest_precedence", + "review_reason": "Reviewed unpinned adapter: drive.transfer_owner has no singular pinned interface_ref; --new-owner is a CLI wrapper input and does not publish a direct interface property.", + "source": "reviewed_mapping_exclusion", + "value": "" + }, + "required": { + "candidates": [ + { + "precedence": "inference", + "selected": true, + "source": "usage_required_inference", + "value": true + }, + { + "precedence": "default", + "selected": false, + "source": "default", + "value": false + } + ], + "precedence": "inference", + "resolution": "highest_precedence", + "source": "usage_required_inference", + "value": true + }, + "required_when": { + "candidates": [ + { + "precedence": "default", + "selected": true, + "source": "default", + "value": "" + } + ], + "precedence": "default", + "resolution": "highest_precedence", + "source": "default", + "value": "" + }, + "type": { + "candidates": [ + { + "precedence": "cobra_contract", + "selected": true, + "source": "cobra_flag_type", + "value": "string" + } + ], + "precedence": "cobra_contract", + "resolution": "highest_precedence", + "source": "cobra_flag_type", + "value": "string" + } + }, + "required": true, + "type": "string" + }, + "node": { + "description": "目标节点 ID 或 URL(与 --workspace 二选一)", + "field_provenance": { + "description": { + "candidates": [ + { + "precedence": "cobra_contract", + "selected": true, + "source": "cobra_usage", + "value": "目标节点 ID 或 URL(与 --workspace 二选一)" + }, + { + "precedence": "default", + "selected": false, + "source": "default", + "value": "" + } + ], + "precedence": "cobra_contract", + "resolution": "highest_precedence", + "source": "cobra_usage", + "value": "目标节点 ID 或 URL(与 --workspace 二选一)" + }, + "property": { + "candidates": [ + { + "precedence": "reviewed_mapping_exclusion", + "review_reason": "Reviewed unpinned adapter: drive.transfer_owner has no singular pinned interface_ref; --node is a CLI wrapper input and does not publish a direct interface property.", + "selected": true, + "source": "reviewed_mapping_exclusion", + "value": "" + }, + { + "precedence": "inference", + "selected": false, + "source": "flag_name_inference", + "value": "node" + } + ], + "precedence": "reviewed_mapping_exclusion", + "resolution": "highest_precedence", + "review_reason": "Reviewed unpinned adapter: drive.transfer_owner has no singular pinned interface_ref; --node is a CLI wrapper input and does not publish a direct interface property.", + "source": "reviewed_mapping_exclusion", + "value": "" + }, + "required": { + "candidates": [ + { + "precedence": "default", + "selected": true, + "source": "default", + "value": false + } + ], + "precedence": "default", + "resolution": "fallback", + "source": "default", + "value": false + }, + "required_when": { + "candidates": [ + { + "precedence": "default", + "selected": true, + "source": "default", + "value": "" + } + ], + "precedence": "default", + "resolution": "highest_precedence", + "source": "default", + "value": "" + }, + "type": { + "candidates": [ + { + "precedence": "cobra_contract", + "selected": true, + "source": "cobra_flag_type", + "value": "string" + } + ], + "precedence": "cobra_contract", + "resolution": "highest_precedence", + "source": "cobra_flag_type", + "value": "string" + } + }, + "required": false, + "type": "string" + }, + "recursive": { + "description": "是否递归变更所有子节点的所有者", + "field_provenance": { + "description": { + "candidates": [ + { + "precedence": "cobra_contract", + "selected": true, + "source": "cobra_usage", + "value": "是否递归变更所有子节点的所有者" + }, + { + "precedence": "default", + "selected": false, + "source": "default", + "value": "" + } + ], + "precedence": "cobra_contract", + "resolution": "highest_precedence", + "source": "cobra_usage", + "value": "是否递归变更所有子节点的所有者" + }, + "property": { + "candidates": [ + { + "precedence": "reviewed_mapping_exclusion", + "review_reason": "Reviewed unpinned adapter: drive.transfer_owner has no singular pinned interface_ref; --recursive is a CLI wrapper input and does not publish a direct interface property.", + "selected": true, + "source": "reviewed_mapping_exclusion", + "value": "" + }, + { + "precedence": "inference", + "selected": false, + "source": "flag_name_inference", + "value": "recursive" + } + ], + "precedence": "reviewed_mapping_exclusion", + "resolution": "highest_precedence", + "review_reason": "Reviewed unpinned adapter: drive.transfer_owner has no singular pinned interface_ref; --recursive is a CLI wrapper input and does not publish a direct interface property.", + "source": "reviewed_mapping_exclusion", + "value": "" + }, + "required": { + "candidates": [ + { + "precedence": "default", + "selected": true, + "source": "default", + "value": false + } + ], + "precedence": "default", + "resolution": "fallback", + "source": "default", + "value": false + }, + "required_when": { + "candidates": [ + { + "precedence": "default", + "selected": true, + "source": "default", + "value": "" + } + ], + "precedence": "default", + "resolution": "highest_precedence", + "source": "default", + "value": "" + }, + "type": { + "candidates": [ + { + "precedence": "cobra_contract", + "selected": true, + "source": "cobra_flag_type", + "value": "boolean" + } + ], + "precedence": "cobra_contract", + "resolution": "highest_precedence", + "source": "cobra_flag_type", + "value": "boolean" + } + }, + "required": false, + "type": "boolean" + }, + "reserve-role": { + "description": "转交后原所有者保留角色: MANAGER / EDITOR / DOWNLOADER / READER / NONE", + "field_provenance": { + "description": { + "candidates": [ + { + "precedence": "cobra_contract", + "selected": true, + "source": "cobra_usage", + "value": "转交后原所有者保留角色: MANAGER / EDITOR / DOWNLOADER / READER / NONE" + }, + { + "precedence": "default", + "selected": false, + "source": "default", + "value": "" + } + ], + "precedence": "cobra_contract", + "resolution": "highest_precedence", + "source": "cobra_usage", + "value": "转交后原所有者保留角色: MANAGER / EDITOR / DOWNLOADER / READER / NONE" + }, + "property": { + "candidates": [ + { + "precedence": "reviewed_mapping_exclusion", + "review_reason": "Reviewed unpinned adapter: drive.transfer_owner has no singular pinned interface_ref; --reserve-role is a CLI wrapper input and does not publish a direct interface property.", + "selected": true, + "source": "reviewed_mapping_exclusion", + "value": "" + }, + { + "precedence": "inference", + "selected": false, + "source": "flag_name_inference", + "value": "reserveRole" + } + ], + "precedence": "reviewed_mapping_exclusion", + "resolution": "highest_precedence", + "review_reason": "Reviewed unpinned adapter: drive.transfer_owner has no singular pinned interface_ref; --reserve-role is a CLI wrapper input and does not publish a direct interface property.", + "source": "reviewed_mapping_exclusion", + "value": "" + }, + "required": { + "candidates": [ + { + "precedence": "default", + "selected": true, + "source": "default", + "value": false + } + ], + "precedence": "default", + "resolution": "fallback", + "source": "default", + "value": false + }, + "required_when": { + "candidates": [ + { + "precedence": "default", + "selected": true, + "source": "default", + "value": "" + } + ], + "precedence": "default", + "resolution": "highest_precedence", + "source": "default", + "value": "" + }, + "type": { + "candidates": [ + { + "precedence": "cobra_contract", + "selected": true, + "source": "cobra_flag_type", + "value": "string" + } + ], + "precedence": "cobra_contract", + "resolution": "highest_precedence", + "source": "cobra_flag_type", + "value": "string" + } + }, + "required": false, + "type": "string" + }, + "workspace": { + "description": "目标知识库 ID 或 URL(与 --node 二选一)", + "field_provenance": { + "description": { + "candidates": [ + { + "precedence": "cobra_contract", + "selected": true, + "source": "cobra_usage", + "value": "目标知识库 ID 或 URL(与 --node 二选一)" + }, + { + "precedence": "default", + "selected": false, + "source": "default", + "value": "" + } + ], + "precedence": "cobra_contract", + "resolution": "highest_precedence", + "source": "cobra_usage", + "value": "目标知识库 ID 或 URL(与 --node 二选一)" + }, + "property": { + "candidates": [ + { + "precedence": "reviewed_mapping_exclusion", + "review_reason": "Reviewed unpinned adapter: drive.transfer_owner has no singular pinned interface_ref; --workspace is a CLI wrapper input and does not publish a direct interface property.", + "selected": true, + "source": "reviewed_mapping_exclusion", + "value": "" + }, + { + "precedence": "inference", + "selected": false, + "source": "flag_name_inference", + "value": "workspace" + } + ], + "precedence": "reviewed_mapping_exclusion", + "resolution": "highest_precedence", + "review_reason": "Reviewed unpinned adapter: drive.transfer_owner has no singular pinned interface_ref; --workspace is a CLI wrapper input and does not publish a direct interface property.", + "source": "reviewed_mapping_exclusion", + "value": "" + }, + "required": { + "candidates": [ + { + "precedence": "default", + "selected": true, + "source": "default", + "value": false + } + ], + "precedence": "default", + "resolution": "fallback", + "source": "default", + "value": false + }, + "required_when": { + "candidates": [ + { + "precedence": "default", + "selected": true, + "source": "default", + "value": "" + } + ], + "precedence": "default", + "resolution": "highest_precedence", + "source": "default", + "value": "" + }, + "type": { + "candidates": [ + { + "precedence": "cobra_contract", + "selected": true, + "source": "cobra_flag_type", + "value": "string" + } + ], + "precedence": "cobra_contract", + "resolution": "highest_precedence", + "source": "cobra_flag_type", + "value": "string" + } + }, + "required": false, + "type": "string" + } + }, + "path": "drive.transfer_owner", + "primary_cli_path": "drive permission transfer-owner", + "product_id": "drive", + "reviewed": true, + "risk": "high", + "source": "reviewed_command_registry", + "title": "[危险] 转交所有者", + "use_when": [ + "用户明确要求转交文档/知识库所有权" + ] + }, + "drive.unmark_star": { + "agent_metadata_source": "embedded-skill-metadata", + "agent_source_refs": [ + "CommandRegistry:canonical_path=drive.unmark_star", + "cobra-help:dws drive star remove", + "internal/cli/schema_hints/metadata/drive.json", + "internal/cli/schema_hints/selection/drive.json", + "wukong-develop:wukong/products" + ], + "agent_summary": "将文档/文件从当前用户收藏列表移除", + "agent_summary_source": "dws-agent-selection/drive", + "availability": "available", + "avoid_when": [ + "添加收藏用 mark_star" + ], + "canonical_path": "drive.unmark_star", + "cli_name": "remove", + "cli_path": "drive star remove", + "confirmation": "not_required", + "description": "取消收藏文档", + "display": "钉盘文件管理", + "effect": "write", + "effect_source": "agent-hint", + "examples": [ + "dws drive star remove --node \u003cnodeId\u003e --format json" + ], + "field_provenance": { + "agent_summary": { + "candidates": [ + { + "precedence": "reviewed_explicit", + "review_reason": "人工审阅:对照悟空 develop 同名命令实现、Cobra Long/Example 与 Runtime 确认门禁移植;不改变命令身份、参数契约或接口绑定。", + "selected": true, + "source": "internal/cli/schema_hints/selection/drive.json", + "value": "将文档/文件从当前用户收藏列表移除" + } + ], + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "人工审阅:对照悟空 develop 同名命令实现、Cobra Long/Example 与 Runtime 确认门禁移植;不改变命令身份、参数契约或接口绑定。", + "source": "internal/cli/schema_hints/selection/drive.json", + "value": "将文档/文件从当前用户收藏列表移除" + }, + "availability": { + "candidates": [ + { + "precedence": "reviewed_explicit", + "review_reason": "dws-tool-metadata/drive marks this tool as reviewed", + "selected": true, + "source": "internal/cli/schema_hints/metadata/drive.json", + "value": "available" + } + ], + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "dws-tool-metadata/drive marks this tool as reviewed", + "source": "internal/cli/schema_hints/metadata/drive.json", + "value": "available" + }, + "avoid_when": { + "candidates": [ + { + "precedence": "reviewed_explicit", + "review_reason": "人工审阅:对照悟空 develop 同名命令实现、Cobra Long/Example 与 Runtime 确认门禁移植;不改变命令身份、参数契约或接口绑定。", + "selected": true, + "source": "internal/cli/schema_hints/selection/drive.json", + "value": [ + "添加收藏用 mark_star" + ] + } + ], + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "人工审阅:对照悟空 develop 同名命令实现、Cobra Long/Example 与 Runtime 确认门禁移植;不改变命令身份、参数契约或接口绑定。", + "source": "internal/cli/schema_hints/selection/drive.json", + "value": [ + "添加收藏用 mark_star" + ] + }, + "canonical_path": { + "candidates": [ + { + "precedence": "command_registry", + "selected": true, + "source": "reviewed_command_registry", + "source_ref": "drive star remove", + "value": "drive.unmark_star" + } + ], + "precedence": "command_registry", + "resolution": "registry_identity", + "source": "reviewed_command_registry", + "source_ref": "drive star remove", + "value": "drive.unmark_star" + }, + "confirmation": { + "candidates": [ + { + "precedence": "reviewed_explicit", + "review_reason": "dws-tool-metadata/drive marks this tool as reviewed", + "selected": true, + "source": "internal/cli/schema_hints/metadata/drive.json", + "value": "not_required" + } + ], + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "dws-tool-metadata/drive marks this tool as reviewed", + "source": "internal/cli/schema_hints/metadata/drive.json", + "value": "not_required" + }, + "description": { + "candidates": [ + { + "precedence": "cobra_help", + "selected": true, + "source": "cobra_help", + "value": "取消收藏文档" + } + ], + "precedence": "cobra_help", + "resolution": "highest_precedence", + "source": "cobra_help", + "value": "取消收藏文档" + }, + "effect": { + "candidates": [ + { + "precedence": "reviewed_explicit", + "review_reason": "dws-tool-metadata/drive marks this tool as reviewed", + "selected": true, + "source": "internal/cli/schema_hints/metadata/drive.json", + "value": "write" + } + ], + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "dws-tool-metadata/drive marks this tool as reviewed", + "source": "internal/cli/schema_hints/metadata/drive.json", + "value": "write" + }, + "examples": { + "candidates": [ + { + "precedence": "reviewed_explicit", + "review_reason": "人工审阅:对照悟空 develop 同名命令实现、Cobra Long/Example 与 Runtime 确认门禁移植;不改变命令身份、参数契约或接口绑定。", + "selected": true, + "source": "internal/cli/schema_hints/selection/drive.json", + "value": [ + "dws drive star remove --node \u003cnodeId\u003e --format json" + ] + } + ], + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "人工审阅:对照悟空 develop 同名命令实现、Cobra Long/Example 与 Runtime 确认门禁移植;不改变命令身份、参数契约或接口绑定。", + "source": "internal/cli/schema_hints/selection/drive.json", + "value": [ + "dws drive star remove --node \u003cnodeId\u003e --format json" + ] + }, + "idempotency": { + "candidates": [ + { + "precedence": "reviewed_explicit", + "review_reason": "dws-tool-metadata/drive marks this tool as reviewed", + "selected": true, + "source": "internal/cli/schema_hints/metadata/drive.json", + "value": "idempotent" + } + ], + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "dws-tool-metadata/drive marks this tool as reviewed", + "source": "internal/cli/schema_hints/metadata/drive.json", + "value": "idempotent" + }, + "interface_mode": { + "candidates": [ + { + "precedence": "reviewed_explicit", + "review_reason": "dws-tool-metadata/drive marks this tool as reviewed", + "selected": true, + "source": "internal/cli/schema_hints/metadata/drive.json", + "value": "composite" + } + ], + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "dws-tool-metadata/drive marks this tool as reviewed", + "source": "internal/cli/schema_hints/metadata/drive.json", + "value": "composite" + }, + "interface_reason": { + "candidates": [ + { + "precedence": "reviewed_explicit", + "review_reason": "dws-tool-metadata/drive marks this tool as reviewed", + "selected": true, + "source": "internal/cli/schema_hints/metadata/drive.json", + "value": "Reviewed unpinned remote adapter: this executable CLI wrapper calls a remote helper that is absent from the pinned MCP metadata snapshot; no single pinned semantically equivalent interface_ref can represent the command." + } + ], + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "dws-tool-metadata/drive marks this tool as reviewed", + "source": "internal/cli/schema_hints/metadata/drive.json", + "value": "Reviewed unpinned remote adapter: this executable CLI wrapper calls a remote helper that is absent from the pinned MCP metadata snapshot; no single pinned semantically equivalent interface_ref can represent the command." + }, + "interface_ref": { + "candidates": [ + { + "precedence": "reviewed_explicit", + "review_reason": "final interface mode composite forbids a direct MCP interface_ref", + "selected": true, + "source": "internal/cli/schema_hints/metadata/drive.json", + "value": null + } + ], + "precedence": "reviewed_explicit", + "resolution": "interface_disposition_matrix", + "review_reason": "final interface mode composite forbids a direct MCP interface_ref", + "source": "internal/cli/schema_hints/metadata/drive.json", + "value": null + }, + "reviewed": { + "candidates": [ + { + "precedence": "reviewed_explicit", + "review_reason": "dws-tool-metadata/drive marks this tool as reviewed", + "selected": true, + "source": "internal/cli/schema_hints/metadata/drive.json", + "value": true + }, + { + "precedence": "reviewed_explicit", + "review_reason": "人工审阅:对照悟空 develop 同名命令实现、Cobra Long/Example 与 Runtime 确认门禁移植;不改变命令身份、参数契约或接口绑定。", + "selected": false, + "source": "internal/cli/schema_hints/selection/drive.json", + "value": true + } + ], + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "dws-tool-metadata/drive marks this tool as reviewed", + "source": "internal/cli/schema_hints/metadata/drive.json", + "value": true + }, + "risk": { + "candidates": [ + { + "precedence": "reviewed_explicit", + "review_reason": "dws-tool-metadata/drive marks this tool as reviewed", + "selected": true, + "source": "internal/cli/schema_hints/metadata/drive.json", + "value": "low" + } + ], + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "dws-tool-metadata/drive marks this tool as reviewed", + "source": "internal/cli/schema_hints/metadata/drive.json", + "value": "low" + }, + "title": { + "candidates": [ + { + "precedence": "cobra_help", + "selected": true, + "source": "cobra_help", + "value": "取消收藏文档" + } + ], + "precedence": "cobra_help", + "resolution": "highest_precedence", + "source": "cobra_help", + "value": "取消收藏文档" + }, + "use_when": { + "candidates": [ + { + "precedence": "reviewed_explicit", + "review_reason": "人工审阅:对照悟空 develop 同名命令实现、Cobra Long/Example 与 Runtime 确认门禁移植;不改变命令身份、参数契约或接口绑定。", + "selected": true, + "source": "internal/cli/schema_hints/selection/drive.json", + "value": [ + "用户说 取消收藏/去掉收藏/不收藏了" + ] + } + ], + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "人工审阅:对照悟空 develop 同名命令实现、Cobra Long/Example 与 Runtime 确认门禁移植;不改变命令身份、参数契约或接口绑定。", + "source": "internal/cli/schema_hints/selection/drive.json", + "value": [ + "用户说 取消收藏/去掉收藏/不收藏了" + ] + } + }, + "group": "star", + "has_parameters": true, + "idempotency": "idempotent", + "interface_mode": "composite", + "interface_reason": "Reviewed unpinned remote adapter: this executable CLI wrapper calls a remote helper that is absent from the pinned MCP metadata snapshot; no single pinned semantically equivalent interface_ref can represent the command.", + "is_alias": false, + "name": "unmark_star", + "parameter_count": 1, + "parameters": { + "node": { + "description": "文档 ID 或 URL (必填)", + "field_provenance": { + "description": { + "candidates": [ + { + "precedence": "cobra_contract", + "selected": true, + "source": "cobra_usage", + "value": "文档 ID 或 URL (必填)" + }, + { + "precedence": "default", + "selected": false, + "source": "default", + "value": "" + } + ], + "precedence": "cobra_contract", + "resolution": "highest_precedence", + "source": "cobra_usage", + "value": "文档 ID 或 URL (必填)" + }, + "property": { + "candidates": [ + { + "precedence": "reviewed_mapping_exclusion", + "review_reason": "Reviewed unpinned adapter: drive.unmark_star has no singular pinned interface_ref; --node is a CLI wrapper input and does not publish a direct interface property.", + "selected": true, + "source": "reviewed_mapping_exclusion", + "value": "" + }, + { + "precedence": "inference", + "selected": false, + "source": "flag_name_inference", + "value": "node" + } + ], + "precedence": "reviewed_mapping_exclusion", + "resolution": "highest_precedence", + "review_reason": "Reviewed unpinned adapter: drive.unmark_star has no singular pinned interface_ref; --node is a CLI wrapper input and does not publish a direct interface property.", + "source": "reviewed_mapping_exclusion", + "value": "" + }, + "required": { + "candidates": [ + { + "precedence": "inference", + "selected": true, + "source": "usage_required_inference", + "value": true + }, + { + "precedence": "default", + "selected": false, + "source": "default", + "value": false + } + ], + "precedence": "inference", + "resolution": "highest_precedence", + "source": "usage_required_inference", + "value": true + }, + "required_when": { + "candidates": [ + { + "precedence": "default", + "selected": true, + "source": "default", + "value": "" + } + ], + "precedence": "default", + "resolution": "highest_precedence", + "source": "default", + "value": "" + }, + "type": { + "candidates": [ + { + "precedence": "cobra_contract", + "selected": true, + "source": "cobra_flag_type", + "value": "string" + } + ], + "precedence": "cobra_contract", + "resolution": "highest_precedence", + "source": "cobra_flag_type", + "value": "string" + } + }, + "required": true, + "type": "string" + } + }, + "path": "drive.unmark_star", + "primary_cli_path": "drive star remove", + "product_id": "drive", + "reviewed": true, + "risk": "low", + "source": "reviewed_command_registry", + "title": "取消收藏文档", + "use_when": [ + "用户说 取消收藏/去掉收藏/不收藏了" + ] + }, "drive.upload": { "agent_metadata_source": "embedded-skill-metadata", "agent_source_refs": [ diff --git a/internal/cli/schema_catalog/tools/sheet.json b/internal/cli/schema_catalog/tools/sheet.json index 15c04121..b4504f30 100644 --- a/internal/cli/schema_catalog/tools/sheet.json +++ b/internal/cli/schema_catalog/tools/sheet.json @@ -12279,6 +12279,826 @@ "已有 axls 文档,需要新增一个工作表页签时" ] }, + "sheet.create_sheet_comment": { + "agent_metadata_source": "embedded-skill-metadata", + "agent_source_refs": [ + "CommandRegistry:canonical_path=sheet.create_sheet_comment", + "cobra-help:dws sheet comment create", + "internal/cli/schema_hints/metadata/sheet.json", + "internal/cli/schema_hints/selection/sheet.json", + "wukong-develop:wukong/products" + ], + "agent_summary": "在指定单元格上创建评论,可 @ 用户", + "agent_summary_source": "dws-agent-selection/sheet", + "availability": "available", + "avoid_when": [ + "回复已有评论用 comment reply;不要把评论写进单元格值" + ], + "canonical_path": "sheet.create_sheet_comment", + "cli_name": "create", + "cli_path": "sheet comment create", + "confirmation": "not_required", + "description": "创建单元格评论", + "display": "钉钉表格管理", + "effect": "write", + "effect_source": "agent-hint", + "examples": [ + "dws sheet comment create --node \u003cSHEET_ID\u003e --sheet-id Sheet1 --range A2 --content \"这个数字有问题\" --format json" + ], + "field_provenance": { + "agent_summary": { + "candidates": [ + { + "precedence": "reviewed_explicit", + "review_reason": "人工审阅:对照悟空 develop 同名命令实现、Cobra Long/Example 与 Runtime 确认门禁移植;不改变命令身份、参数契约或接口绑定。", + "selected": true, + "source": "internal/cli/schema_hints/selection/sheet.json", + "value": "在指定单元格上创建评论,可 @ 用户" + } + ], + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "人工审阅:对照悟空 develop 同名命令实现、Cobra Long/Example 与 Runtime 确认门禁移植;不改变命令身份、参数契约或接口绑定。", + "source": "internal/cli/schema_hints/selection/sheet.json", + "value": "在指定单元格上创建评论,可 @ 用户" + }, + "availability": { + "candidates": [ + { + "precedence": "reviewed_explicit", + "review_reason": "dws-tool-metadata/sheet marks this tool as reviewed", + "selected": true, + "source": "internal/cli/schema_hints/metadata/sheet.json", + "value": "available" + } + ], + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "dws-tool-metadata/sheet marks this tool as reviewed", + "source": "internal/cli/schema_hints/metadata/sheet.json", + "value": "available" + }, + "avoid_when": { + "candidates": [ + { + "precedence": "reviewed_explicit", + "review_reason": "人工审阅:对照悟空 develop 同名命令实现、Cobra Long/Example 与 Runtime 确认门禁移植;不改变命令身份、参数契约或接口绑定。", + "selected": true, + "source": "internal/cli/schema_hints/selection/sheet.json", + "value": [ + "回复已有评论用 comment reply;不要把评论写进单元格值" + ] + } + ], + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "人工审阅:对照悟空 develop 同名命令实现、Cobra Long/Example 与 Runtime 确认门禁移植;不改变命令身份、参数契约或接口绑定。", + "source": "internal/cli/schema_hints/selection/sheet.json", + "value": [ + "回复已有评论用 comment reply;不要把评论写进单元格值" + ] + }, + "canonical_path": { + "candidates": [ + { + "precedence": "command_registry", + "selected": true, + "source": "reviewed_command_registry", + "source_ref": "sheet comment create", + "value": "sheet.create_sheet_comment" + } + ], + "precedence": "command_registry", + "resolution": "registry_identity", + "source": "reviewed_command_registry", + "source_ref": "sheet comment create", + "value": "sheet.create_sheet_comment" + }, + "confirmation": { + "candidates": [ + { + "precedence": "reviewed_explicit", + "review_reason": "dws-tool-metadata/sheet marks this tool as reviewed", + "selected": true, + "source": "internal/cli/schema_hints/metadata/sheet.json", + "value": "not_required" + } + ], + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "dws-tool-metadata/sheet marks this tool as reviewed", + "source": "internal/cli/schema_hints/metadata/sheet.json", + "value": "not_required" + }, + "description": { + "candidates": [ + { + "precedence": "cobra_help", + "selected": true, + "source": "cobra_help", + "value": "创建单元格评论" + } + ], + "precedence": "cobra_help", + "resolution": "highest_precedence", + "source": "cobra_help", + "value": "创建单元格评论" + }, + "effect": { + "candidates": [ + { + "precedence": "reviewed_explicit", + "review_reason": "dws-tool-metadata/sheet marks this tool as reviewed", + "selected": true, + "source": "internal/cli/schema_hints/metadata/sheet.json", + "value": "write" + } + ], + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "dws-tool-metadata/sheet marks this tool as reviewed", + "source": "internal/cli/schema_hints/metadata/sheet.json", + "value": "write" + }, + "examples": { + "candidates": [ + { + "precedence": "reviewed_explicit", + "review_reason": "人工审阅:对照悟空 develop 同名命令实现、Cobra Long/Example 与 Runtime 确认门禁移植;不改变命令身份、参数契约或接口绑定。", + "selected": true, + "source": "internal/cli/schema_hints/selection/sheet.json", + "value": [ + "dws sheet comment create --node \u003cSHEET_ID\u003e --sheet-id Sheet1 --range A2 --content \"这个数字有问题\" --format json" + ] + } + ], + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "人工审阅:对照悟空 develop 同名命令实现、Cobra Long/Example 与 Runtime 确认门禁移植;不改变命令身份、参数契约或接口绑定。", + "source": "internal/cli/schema_hints/selection/sheet.json", + "value": [ + "dws sheet comment create --node \u003cSHEET_ID\u003e --sheet-id Sheet1 --range A2 --content \"这个数字有问题\" --format json" + ] + }, + "idempotency": { + "candidates": [ + { + "precedence": "reviewed_explicit", + "review_reason": "dws-tool-metadata/sheet marks this tool as reviewed", + "selected": true, + "source": "internal/cli/schema_hints/metadata/sheet.json", + "value": "non_idempotent" + } + ], + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "dws-tool-metadata/sheet marks this tool as reviewed", + "source": "internal/cli/schema_hints/metadata/sheet.json", + "value": "non_idempotent" + }, + "interface_mode": { + "candidates": [ + { + "precedence": "reviewed_explicit", + "review_reason": "dws-tool-metadata/sheet marks this tool as reviewed", + "selected": true, + "source": "internal/cli/schema_hints/metadata/sheet.json", + "value": "composite" + } + ], + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "dws-tool-metadata/sheet marks this tool as reviewed", + "source": "internal/cli/schema_hints/metadata/sheet.json", + "value": "composite" + }, + "interface_reason": { + "candidates": [ + { + "precedence": "reviewed_explicit", + "review_reason": "dws-tool-metadata/sheet marks this tool as reviewed", + "selected": true, + "source": "internal/cli/schema_hints/metadata/sheet.json", + "value": "Reviewed unpinned remote adapter: this executable CLI wrapper calls a remote helper that is absent from the pinned MCP metadata snapshot; no single pinned semantically equivalent interface_ref can represent the command." + } + ], + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "dws-tool-metadata/sheet marks this tool as reviewed", + "source": "internal/cli/schema_hints/metadata/sheet.json", + "value": "Reviewed unpinned remote adapter: this executable CLI wrapper calls a remote helper that is absent from the pinned MCP metadata snapshot; no single pinned semantically equivalent interface_ref can represent the command." + }, + "interface_ref": { + "candidates": [ + { + "precedence": "reviewed_explicit", + "review_reason": "final interface mode composite forbids a direct MCP interface_ref", + "selected": true, + "source": "internal/cli/schema_hints/metadata/sheet.json", + "value": null + } + ], + "precedence": "reviewed_explicit", + "resolution": "interface_disposition_matrix", + "review_reason": "final interface mode composite forbids a direct MCP interface_ref", + "source": "internal/cli/schema_hints/metadata/sheet.json", + "value": null + }, + "reviewed": { + "candidates": [ + { + "precedence": "reviewed_explicit", + "review_reason": "dws-tool-metadata/sheet marks this tool as reviewed", + "selected": true, + "source": "internal/cli/schema_hints/metadata/sheet.json", + "value": true + }, + { + "precedence": "reviewed_explicit", + "review_reason": "人工审阅:对照悟空 develop 同名命令实现、Cobra Long/Example 与 Runtime 确认门禁移植;不改变命令身份、参数契约或接口绑定。", + "selected": false, + "source": "internal/cli/schema_hints/selection/sheet.json", + "value": true + } + ], + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "dws-tool-metadata/sheet marks this tool as reviewed", + "source": "internal/cli/schema_hints/metadata/sheet.json", + "value": true + }, + "risk": { + "candidates": [ + { + "precedence": "reviewed_explicit", + "review_reason": "dws-tool-metadata/sheet marks this tool as reviewed", + "selected": true, + "source": "internal/cli/schema_hints/metadata/sheet.json", + "value": "low" + } + ], + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "dws-tool-metadata/sheet marks this tool as reviewed", + "source": "internal/cli/schema_hints/metadata/sheet.json", + "value": "low" + }, + "title": { + "candidates": [ + { + "precedence": "cobra_help", + "selected": true, + "source": "cobra_help", + "value": "创建单元格评论" + } + ], + "precedence": "cobra_help", + "resolution": "highest_precedence", + "source": "cobra_help", + "value": "创建单元格评论" + }, + "use_when": { + "candidates": [ + { + "precedence": "reviewed_explicit", + "review_reason": "人工审阅:对照悟空 develop 同名命令实现、Cobra Long/Example 与 Runtime 确认门禁移植;不改变命令身份、参数契约或接口绑定。", + "selected": true, + "source": "internal/cli/schema_hints/selection/sheet.json", + "value": [ + "用户说 给单元格加评论/批注/@某人讨论这个数据" + ] + } + ], + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "人工审阅:对照悟空 develop 同名命令实现、Cobra Long/Example 与 Runtime 确认门禁移植;不改变命令身份、参数契约或接口绑定。", + "source": "internal/cli/schema_hints/selection/sheet.json", + "value": [ + "用户说 给单元格加评论/批注/@某人讨论这个数据" + ] + } + }, + "group": "comment", + "has_parameters": true, + "idempotency": "non_idempotent", + "interface_mode": "composite", + "interface_reason": "Reviewed unpinned remote adapter: this executable CLI wrapper calls a remote helper that is absent from the pinned MCP metadata snapshot; no single pinned semantically equivalent interface_ref can represent the command.", + "is_alias": false, + "name": "create_sheet_comment", + "parameter_count": 5, + "parameters": { + "content": { + "description": "评论内容 (必填)", + "field_provenance": { + "description": { + "candidates": [ + { + "precedence": "cobra_contract", + "selected": true, + "source": "cobra_usage", + "value": "评论内容 (必填)" + }, + { + "precedence": "default", + "selected": false, + "source": "default", + "value": "" + } + ], + "precedence": "cobra_contract", + "resolution": "highest_precedence", + "source": "cobra_usage", + "value": "评论内容 (必填)" + }, + "property": { + "candidates": [ + { + "precedence": "reviewed_mapping_exclusion", + "review_reason": "Reviewed unpinned adapter: sheet.create_sheet_comment has no singular pinned interface_ref; --content is a CLI wrapper input and does not publish a direct interface property.", + "selected": true, + "source": "reviewed_mapping_exclusion", + "value": "" + }, + { + "precedence": "inference", + "selected": false, + "source": "flag_name_inference", + "value": "content" + } + ], + "precedence": "reviewed_mapping_exclusion", + "resolution": "highest_precedence", + "review_reason": "Reviewed unpinned adapter: sheet.create_sheet_comment has no singular pinned interface_ref; --content is a CLI wrapper input and does not publish a direct interface property.", + "source": "reviewed_mapping_exclusion", + "value": "" + }, + "required": { + "candidates": [ + { + "precedence": "inference", + "selected": true, + "source": "usage_required_inference", + "value": true + }, + { + "precedence": "default", + "selected": false, + "source": "default", + "value": false + } + ], + "precedence": "inference", + "resolution": "highest_precedence", + "source": "usage_required_inference", + "value": true + }, + "required_when": { + "candidates": [ + { + "precedence": "default", + "selected": true, + "source": "default", + "value": "" + } + ], + "precedence": "default", + "resolution": "highest_precedence", + "source": "default", + "value": "" + }, + "type": { + "candidates": [ + { + "precedence": "cobra_contract", + "selected": true, + "source": "cobra_flag_type", + "value": "string" + } + ], + "precedence": "cobra_contract", + "resolution": "highest_precedence", + "source": "cobra_flag_type", + "value": "string" + } + }, + "required": true, + "type": "string" + }, + "mention": { + "description": "被 @ 的用户 uid 列表,逗号分隔", + "field_provenance": { + "description": { + "candidates": [ + { + "precedence": "cobra_contract", + "selected": true, + "source": "cobra_usage", + "value": "被 @ 的用户 uid 列表,逗号分隔" + }, + { + "precedence": "default", + "selected": false, + "source": "default", + "value": "" + } + ], + "precedence": "cobra_contract", + "resolution": "highest_precedence", + "source": "cobra_usage", + "value": "被 @ 的用户 uid 列表,逗号分隔" + }, + "property": { + "candidates": [ + { + "precedence": "reviewed_mapping_exclusion", + "review_reason": "Reviewed unpinned adapter: sheet.create_sheet_comment has no singular pinned interface_ref; --mention is a CLI wrapper input and does not publish a direct interface property.", + "selected": true, + "source": "reviewed_mapping_exclusion", + "value": "" + }, + { + "precedence": "inference", + "selected": false, + "source": "flag_name_inference", + "value": "mention" + } + ], + "precedence": "reviewed_mapping_exclusion", + "resolution": "highest_precedence", + "review_reason": "Reviewed unpinned adapter: sheet.create_sheet_comment has no singular pinned interface_ref; --mention is a CLI wrapper input and does not publish a direct interface property.", + "source": "reviewed_mapping_exclusion", + "value": "" + }, + "required": { + "candidates": [ + { + "precedence": "default", + "selected": true, + "source": "default", + "value": false + } + ], + "precedence": "default", + "resolution": "fallback", + "source": "default", + "value": false + }, + "required_when": { + "candidates": [ + { + "precedence": "default", + "selected": true, + "source": "default", + "value": "" + } + ], + "precedence": "default", + "resolution": "highest_precedence", + "source": "default", + "value": "" + }, + "type": { + "candidates": [ + { + "precedence": "cobra_contract", + "selected": true, + "source": "cobra_flag_type", + "value": "string" + } + ], + "precedence": "cobra_contract", + "resolution": "highest_precedence", + "source": "cobra_flag_type", + "value": "string" + } + }, + "required": false, + "type": "string" + }, + "node": { + "description": "表格文档 ID 或 URL (必填)", + "field_provenance": { + "description": { + "candidates": [ + { + "precedence": "cobra_contract", + "selected": true, + "source": "cobra_usage", + "value": "表格文档 ID 或 URL (必填)" + }, + { + "precedence": "default", + "selected": false, + "source": "default", + "value": "" + } + ], + "precedence": "cobra_contract", + "resolution": "highest_precedence", + "source": "cobra_usage", + "value": "表格文档 ID 或 URL (必填)" + }, + "property": { + "candidates": [ + { + "precedence": "reviewed_mapping_exclusion", + "review_reason": "Reviewed unpinned adapter: sheet.create_sheet_comment has no singular pinned interface_ref; --node is a CLI wrapper input and does not publish a direct interface property.", + "selected": true, + "source": "reviewed_mapping_exclusion", + "value": "" + }, + { + "precedence": "inference", + "selected": false, + "source": "flag_name_inference", + "value": "node" + } + ], + "precedence": "reviewed_mapping_exclusion", + "resolution": "highest_precedence", + "review_reason": "Reviewed unpinned adapter: sheet.create_sheet_comment has no singular pinned interface_ref; --node is a CLI wrapper input and does not publish a direct interface property.", + "source": "reviewed_mapping_exclusion", + "value": "" + }, + "required": { + "candidates": [ + { + "precedence": "inference", + "selected": true, + "source": "usage_required_inference", + "value": true + }, + { + "precedence": "default", + "selected": false, + "source": "default", + "value": false + } + ], + "precedence": "inference", + "resolution": "highest_precedence", + "source": "usage_required_inference", + "value": true + }, + "required_when": { + "candidates": [ + { + "precedence": "default", + "selected": true, + "source": "default", + "value": "" + } + ], + "precedence": "default", + "resolution": "highest_precedence", + "source": "default", + "value": "" + }, + "type": { + "candidates": [ + { + "precedence": "cobra_contract", + "selected": true, + "source": "cobra_flag_type", + "value": "string" + } + ], + "precedence": "cobra_contract", + "resolution": "highest_precedence", + "source": "cobra_flag_type", + "value": "string" + } + }, + "required": true, + "type": "string" + }, + "range": { + "description": "单元格位置 A1 表示法 (必填)", + "field_provenance": { + "description": { + "candidates": [ + { + "precedence": "cobra_contract", + "selected": true, + "source": "cobra_usage", + "value": "单元格位置 A1 表示法 (必填)" + }, + { + "precedence": "default", + "selected": false, + "source": "default", + "value": "" + } + ], + "precedence": "cobra_contract", + "resolution": "highest_precedence", + "source": "cobra_usage", + "value": "单元格位置 A1 表示法 (必填)" + }, + "format": { + "candidates": [ + { + "precedence": "inference", + "selected": true, + "source": "usage_format_inference", + "value": "a1-range" + }, + { + "precedence": "default", + "selected": false, + "source": "default", + "value": "" + } + ], + "precedence": "inference", + "resolution": "highest_precedence", + "source": "usage_format_inference", + "value": "a1-range" + }, + "property": { + "candidates": [ + { + "precedence": "reviewed_mapping_exclusion", + "review_reason": "Reviewed unpinned adapter: sheet.create_sheet_comment has no singular pinned interface_ref; --range is a CLI wrapper input and does not publish a direct interface property.", + "selected": true, + "source": "reviewed_mapping_exclusion", + "value": "" + }, + { + "precedence": "inference", + "selected": false, + "source": "flag_name_inference", + "value": "range" + } + ], + "precedence": "reviewed_mapping_exclusion", + "resolution": "highest_precedence", + "review_reason": "Reviewed unpinned adapter: sheet.create_sheet_comment has no singular pinned interface_ref; --range is a CLI wrapper input and does not publish a direct interface property.", + "source": "reviewed_mapping_exclusion", + "value": "" + }, + "required": { + "candidates": [ + { + "precedence": "inference", + "selected": true, + "source": "usage_required_inference", + "value": true + }, + { + "precedence": "default", + "selected": false, + "source": "default", + "value": false + } + ], + "precedence": "inference", + "resolution": "highest_precedence", + "source": "usage_required_inference", + "value": true + }, + "required_when": { + "candidates": [ + { + "precedence": "default", + "selected": true, + "source": "default", + "value": "" + } + ], + "precedence": "default", + "resolution": "highest_precedence", + "source": "default", + "value": "" + }, + "type": { + "candidates": [ + { + "precedence": "cobra_contract", + "selected": true, + "source": "cobra_flag_type", + "value": "string" + } + ], + "precedence": "cobra_contract", + "resolution": "highest_precedence", + "source": "cobra_flag_type", + "value": "string" + } + }, + "format": "a1-range", + "required": true, + "type": "string" + }, + "sheet-id": { + "description": "工作表 ID 或名称 (必填)", + "field_provenance": { + "description": { + "candidates": [ + { + "precedence": "cobra_contract", + "selected": true, + "source": "cobra_usage", + "value": "工作表 ID 或名称 (必填)" + }, + { + "precedence": "default", + "selected": false, + "source": "default", + "value": "" + } + ], + "precedence": "cobra_contract", + "resolution": "highest_precedence", + "source": "cobra_usage", + "value": "工作表 ID 或名称 (必填)" + }, + "property": { + "candidates": [ + { + "precedence": "reviewed_mapping_exclusion", + "review_reason": "Reviewed unpinned adapter: sheet.create_sheet_comment has no singular pinned interface_ref; --sheet-id is a CLI wrapper input and does not publish a direct interface property.", + "selected": true, + "source": "reviewed_mapping_exclusion", + "value": "" + }, + { + "precedence": "inference", + "selected": false, + "source": "flag_name_inference", + "value": "sheetId" + } + ], + "precedence": "reviewed_mapping_exclusion", + "resolution": "highest_precedence", + "review_reason": "Reviewed unpinned adapter: sheet.create_sheet_comment has no singular pinned interface_ref; --sheet-id is a CLI wrapper input and does not publish a direct interface property.", + "source": "reviewed_mapping_exclusion", + "value": "" + }, + "required": { + "candidates": [ + { + "precedence": "inference", + "selected": true, + "source": "usage_required_inference", + "value": true + }, + { + "precedence": "default", + "selected": false, + "source": "default", + "value": false + } + ], + "precedence": "inference", + "resolution": "highest_precedence", + "source": "usage_required_inference", + "value": true + }, + "required_when": { + "candidates": [ + { + "precedence": "default", + "selected": true, + "source": "default", + "value": "" + } + ], + "precedence": "default", + "resolution": "highest_precedence", + "source": "default", + "value": "" + }, + "type": { + "candidates": [ + { + "precedence": "cobra_contract", + "selected": true, + "source": "cobra_flag_type", + "value": "string" + } + ], + "precedence": "cobra_contract", + "resolution": "highest_precedence", + "source": "cobra_flag_type", + "value": "string" + } + }, + "required": true, + "type": "string" + } + }, + "path": "sheet.create_sheet_comment", + "primary_cli_path": "sheet comment create", + "product_id": "sheet", + "reviewed": true, + "risk": "low", + "source": "reviewed_command_registry", + "title": "创建单元格评论", + "use_when": [ + "用户说 给单元格加评论/批注/@某人讨论这个数据" + ] + }, "sheet.create_workspace_sheet": { "agent_metadata_source": "embedded-skill-metadata", "agent_source_refs": [ @@ -18618,6 +19438,520 @@ "用户明确要求永久删除某个工作表页签,且已确认目标 sheetId 时" ] }, + "sheet.delete_sheet_comment": { + "agent_metadata_source": "embedded-skill-metadata", + "agent_source_refs": [ + "CommandRegistry:canonical_path=sheet.delete_sheet_comment", + "cobra-help:dws sheet comment delete", + "internal/cli/schema_hints/metadata/sheet.json", + "internal/cli/schema_hints/selection/sheet.json", + "wukong-develop:wukong/products" + ], + "agent_summary": "删除单元格评论(不可恢复)", + "agent_summary_source": "dws-agent-selection/sheet", + "availability": "available", + "avoid_when": [ + "标记解决不等于删除" + ], + "canonical_path": "sheet.delete_sheet_comment", + "cli_name": "delete", + "cli_path": "sheet comment delete", + "confirmation": "user_required", + "description": "删除单元格评论", + "display": "钉钉表格管理", + "effect": "write", + "effect_source": "agent-hint", + "examples": [ + "dws sheet comment delete --node \u003cSHEET_ID\u003e --comment-key \u003cCOMMENT_KEY\u003e --format json" + ], + "field_provenance": { + "agent_summary": { + "candidates": [ + { + "precedence": "reviewed_explicit", + "review_reason": "人工审阅:对照悟空 develop 同名命令实现、Cobra Long/Example 与 Runtime 确认门禁移植;不改变命令身份、参数契约或接口绑定。", + "selected": true, + "source": "internal/cli/schema_hints/selection/sheet.json", + "value": "删除单元格评论(不可恢复)" + } + ], + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "人工审阅:对照悟空 develop 同名命令实现、Cobra Long/Example 与 Runtime 确认门禁移植;不改变命令身份、参数契约或接口绑定。", + "source": "internal/cli/schema_hints/selection/sheet.json", + "value": "删除单元格评论(不可恢复)" + }, + "availability": { + "candidates": [ + { + "precedence": "reviewed_explicit", + "review_reason": "dws-tool-metadata/sheet marks this tool as reviewed", + "selected": true, + "source": "internal/cli/schema_hints/metadata/sheet.json", + "value": "available" + } + ], + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "dws-tool-metadata/sheet marks this tool as reviewed", + "source": "internal/cli/schema_hints/metadata/sheet.json", + "value": "available" + }, + "avoid_when": { + "candidates": [ + { + "precedence": "reviewed_explicit", + "review_reason": "人工审阅:对照悟空 develop 同名命令实现、Cobra Long/Example 与 Runtime 确认门禁移植;不改变命令身份、参数契约或接口绑定。", + "selected": true, + "source": "internal/cli/schema_hints/selection/sheet.json", + "value": [ + "标记解决不等于删除" + ] + } + ], + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "人工审阅:对照悟空 develop 同名命令实现、Cobra Long/Example 与 Runtime 确认门禁移植;不改变命令身份、参数契约或接口绑定。", + "source": "internal/cli/schema_hints/selection/sheet.json", + "value": [ + "标记解决不等于删除" + ] + }, + "canonical_path": { + "candidates": [ + { + "precedence": "command_registry", + "selected": true, + "source": "reviewed_command_registry", + "source_ref": "sheet comment delete", + "value": "sheet.delete_sheet_comment" + } + ], + "precedence": "command_registry", + "resolution": "registry_identity", + "source": "reviewed_command_registry", + "source_ref": "sheet comment delete", + "value": "sheet.delete_sheet_comment" + }, + "confirmation": { + "candidates": [ + { + "precedence": "reviewed_explicit", + "review_reason": "dws-tool-metadata/sheet marks this tool as reviewed", + "selected": true, + "source": "internal/cli/schema_hints/metadata/sheet.json", + "value": "user_required" + } + ], + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "dws-tool-metadata/sheet marks this tool as reviewed", + "source": "internal/cli/schema_hints/metadata/sheet.json", + "value": "user_required" + }, + "description": { + "candidates": [ + { + "precedence": "cobra_help", + "selected": true, + "source": "cobra_help", + "value": "删除单元格评论" + } + ], + "precedence": "cobra_help", + "resolution": "highest_precedence", + "source": "cobra_help", + "value": "删除单元格评论" + }, + "effect": { + "candidates": [ + { + "precedence": "reviewed_explicit", + "review_reason": "dws-tool-metadata/sheet marks this tool as reviewed", + "selected": true, + "source": "internal/cli/schema_hints/metadata/sheet.json", + "value": "write" + } + ], + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "dws-tool-metadata/sheet marks this tool as reviewed", + "source": "internal/cli/schema_hints/metadata/sheet.json", + "value": "write" + }, + "examples": { + "candidates": [ + { + "precedence": "reviewed_explicit", + "review_reason": "人工审阅:对照悟空 develop 同名命令实现、Cobra Long/Example 与 Runtime 确认门禁移植;不改变命令身份、参数契约或接口绑定。", + "selected": true, + "source": "internal/cli/schema_hints/selection/sheet.json", + "value": [ + "dws sheet comment delete --node \u003cSHEET_ID\u003e --comment-key \u003cCOMMENT_KEY\u003e --format json" + ] + } + ], + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "人工审阅:对照悟空 develop 同名命令实现、Cobra Long/Example 与 Runtime 确认门禁移植;不改变命令身份、参数契约或接口绑定。", + "source": "internal/cli/schema_hints/selection/sheet.json", + "value": [ + "dws sheet comment delete --node \u003cSHEET_ID\u003e --comment-key \u003cCOMMENT_KEY\u003e --format json" + ] + }, + "idempotency": { + "candidates": [ + { + "precedence": "reviewed_explicit", + "review_reason": "dws-tool-metadata/sheet marks this tool as reviewed", + "selected": true, + "source": "internal/cli/schema_hints/metadata/sheet.json", + "value": "non_idempotent" + } + ], + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "dws-tool-metadata/sheet marks this tool as reviewed", + "source": "internal/cli/schema_hints/metadata/sheet.json", + "value": "non_idempotent" + }, + "interface_mode": { + "candidates": [ + { + "precedence": "reviewed_explicit", + "review_reason": "dws-tool-metadata/sheet marks this tool as reviewed", + "selected": true, + "source": "internal/cli/schema_hints/metadata/sheet.json", + "value": "composite" + } + ], + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "dws-tool-metadata/sheet marks this tool as reviewed", + "source": "internal/cli/schema_hints/metadata/sheet.json", + "value": "composite" + }, + "interface_reason": { + "candidates": [ + { + "precedence": "reviewed_explicit", + "review_reason": "dws-tool-metadata/sheet marks this tool as reviewed", + "selected": true, + "source": "internal/cli/schema_hints/metadata/sheet.json", + "value": "Reviewed unpinned remote adapter: this executable CLI wrapper calls a remote helper that is absent from the pinned MCP metadata snapshot; no single pinned semantically equivalent interface_ref can represent the command." + } + ], + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "dws-tool-metadata/sheet marks this tool as reviewed", + "source": "internal/cli/schema_hints/metadata/sheet.json", + "value": "Reviewed unpinned remote adapter: this executable CLI wrapper calls a remote helper that is absent from the pinned MCP metadata snapshot; no single pinned semantically equivalent interface_ref can represent the command." + }, + "interface_ref": { + "candidates": [ + { + "precedence": "reviewed_explicit", + "review_reason": "final interface mode composite forbids a direct MCP interface_ref", + "selected": true, + "source": "internal/cli/schema_hints/metadata/sheet.json", + "value": null + } + ], + "precedence": "reviewed_explicit", + "resolution": "interface_disposition_matrix", + "review_reason": "final interface mode composite forbids a direct MCP interface_ref", + "source": "internal/cli/schema_hints/metadata/sheet.json", + "value": null + }, + "reviewed": { + "candidates": [ + { + "precedence": "reviewed_explicit", + "review_reason": "dws-tool-metadata/sheet marks this tool as reviewed", + "selected": true, + "source": "internal/cli/schema_hints/metadata/sheet.json", + "value": true + }, + { + "precedence": "reviewed_explicit", + "review_reason": "人工审阅:对照悟空 develop 同名命令实现、Cobra Long/Example 与 Runtime 确认门禁移植;不改变命令身份、参数契约或接口绑定。", + "selected": false, + "source": "internal/cli/schema_hints/selection/sheet.json", + "value": true + } + ], + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "dws-tool-metadata/sheet marks this tool as reviewed", + "source": "internal/cli/schema_hints/metadata/sheet.json", + "value": true + }, + "risk": { + "candidates": [ + { + "precedence": "reviewed_explicit", + "review_reason": "dws-tool-metadata/sheet marks this tool as reviewed", + "selected": true, + "source": "internal/cli/schema_hints/metadata/sheet.json", + "value": "medium" + } + ], + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "dws-tool-metadata/sheet marks this tool as reviewed", + "source": "internal/cli/schema_hints/metadata/sheet.json", + "value": "medium" + }, + "title": { + "candidates": [ + { + "precedence": "cobra_help", + "selected": true, + "source": "cobra_help", + "value": "删除单元格评论" + } + ], + "precedence": "cobra_help", + "resolution": "highest_precedence", + "source": "cobra_help", + "value": "删除单元格评论" + }, + "use_when": { + "candidates": [ + { + "precedence": "reviewed_explicit", + "review_reason": "人工审阅:对照悟空 develop 同名命令实现、Cobra Long/Example 与 Runtime 确认门禁移植;不改变命令身份、参数契约或接口绑定。", + "selected": true, + "source": "internal/cli/schema_hints/selection/sheet.json", + "value": [ + "用户明确要求删除某条评论" + ] + } + ], + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "人工审阅:对照悟空 develop 同名命令实现、Cobra Long/Example 与 Runtime 确认门禁移植;不改变命令身份、参数契约或接口绑定。", + "source": "internal/cli/schema_hints/selection/sheet.json", + "value": [ + "用户明确要求删除某条评论" + ] + } + }, + "group": "comment", + "has_parameters": true, + "idempotency": "non_idempotent", + "interface_mode": "composite", + "interface_reason": "Reviewed unpinned remote adapter: this executable CLI wrapper calls a remote helper that is absent from the pinned MCP metadata snapshot; no single pinned semantically equivalent interface_ref can represent the command.", + "is_alias": false, + "name": "delete_sheet_comment", + "parameter_count": 2, + "parameters": { + "comment-key": { + "description": "待删除评论的 commentKey (必填)", + "field_provenance": { + "description": { + "candidates": [ + { + "precedence": "cobra_contract", + "selected": true, + "source": "cobra_usage", + "value": "待删除评论的 commentKey (必填)" + }, + { + "precedence": "default", + "selected": false, + "source": "default", + "value": "" + } + ], + "precedence": "cobra_contract", + "resolution": "highest_precedence", + "source": "cobra_usage", + "value": "待删除评论的 commentKey (必填)" + }, + "property": { + "candidates": [ + { + "precedence": "reviewed_mapping_exclusion", + "review_reason": "Reviewed unpinned adapter: sheet.delete_sheet_comment has no singular pinned interface_ref; --comment-key is a CLI wrapper input and does not publish a direct interface property.", + "selected": true, + "source": "reviewed_mapping_exclusion", + "value": "" + }, + { + "precedence": "inference", + "selected": false, + "source": "flag_name_inference", + "value": "commentKey" + } + ], + "precedence": "reviewed_mapping_exclusion", + "resolution": "highest_precedence", + "review_reason": "Reviewed unpinned adapter: sheet.delete_sheet_comment has no singular pinned interface_ref; --comment-key is a CLI wrapper input and does not publish a direct interface property.", + "source": "reviewed_mapping_exclusion", + "value": "" + }, + "required": { + "candidates": [ + { + "precedence": "inference", + "selected": true, + "source": "usage_required_inference", + "value": true + }, + { + "precedence": "default", + "selected": false, + "source": "default", + "value": false + } + ], + "precedence": "inference", + "resolution": "highest_precedence", + "source": "usage_required_inference", + "value": true + }, + "required_when": { + "candidates": [ + { + "precedence": "default", + "selected": true, + "source": "default", + "value": "" + } + ], + "precedence": "default", + "resolution": "highest_precedence", + "source": "default", + "value": "" + }, + "type": { + "candidates": [ + { + "precedence": "cobra_contract", + "selected": true, + "source": "cobra_flag_type", + "value": "string" + } + ], + "precedence": "cobra_contract", + "resolution": "highest_precedence", + "source": "cobra_flag_type", + "value": "string" + } + }, + "required": true, + "type": "string" + }, + "node": { + "description": "表格文档 ID 或 URL (必填)", + "field_provenance": { + "description": { + "candidates": [ + { + "precedence": "cobra_contract", + "selected": true, + "source": "cobra_usage", + "value": "表格文档 ID 或 URL (必填)" + }, + { + "precedence": "default", + "selected": false, + "source": "default", + "value": "" + } + ], + "precedence": "cobra_contract", + "resolution": "highest_precedence", + "source": "cobra_usage", + "value": "表格文档 ID 或 URL (必填)" + }, + "property": { + "candidates": [ + { + "precedence": "reviewed_mapping_exclusion", + "review_reason": "Reviewed unpinned adapter: sheet.delete_sheet_comment has no singular pinned interface_ref; --node is a CLI wrapper input and does not publish a direct interface property.", + "selected": true, + "source": "reviewed_mapping_exclusion", + "value": "" + }, + { + "precedence": "inference", + "selected": false, + "source": "flag_name_inference", + "value": "node" + } + ], + "precedence": "reviewed_mapping_exclusion", + "resolution": "highest_precedence", + "review_reason": "Reviewed unpinned adapter: sheet.delete_sheet_comment has no singular pinned interface_ref; --node is a CLI wrapper input and does not publish a direct interface property.", + "source": "reviewed_mapping_exclusion", + "value": "" + }, + "required": { + "candidates": [ + { + "precedence": "inference", + "selected": true, + "source": "usage_required_inference", + "value": true + }, + { + "precedence": "default", + "selected": false, + "source": "default", + "value": false + } + ], + "precedence": "inference", + "resolution": "highest_precedence", + "source": "usage_required_inference", + "value": true + }, + "required_when": { + "candidates": [ + { + "precedence": "default", + "selected": true, + "source": "default", + "value": "" + } + ], + "precedence": "default", + "resolution": "highest_precedence", + "source": "default", + "value": "" + }, + "type": { + "candidates": [ + { + "precedence": "cobra_contract", + "selected": true, + "source": "cobra_flag_type", + "value": "string" + } + ], + "precedence": "cobra_contract", + "resolution": "highest_precedence", + "source": "cobra_flag_type", + "value": "string" + } + }, + "required": true, + "type": "string" + } + }, + "path": "sheet.delete_sheet_comment", + "primary_cli_path": "sheet comment delete", + "product_id": "sheet", + "reviewed": true, + "risk": "medium", + "source": "reviewed_command_registry", + "title": "删除单元格评论", + "use_when": [ + "用户明确要求删除某条评论" + ] + }, "sheet.fill_range": { "agent_metadata_source": "embedded-skill-metadata", "agent_source_refs": [ @@ -25344,6 +26678,1010 @@ "要查找包含某文本或公式的单元格位置时,必须用服务端 find" ] }, + "sheet.formula_verify": { + "agent_metadata_source": "embedded-skill-metadata", + "agent_source_refs": [ + "CommandRegistry:canonical_path=sheet.formula_verify", + "cobra-help:dws sheet formula-verify", + "internal/cli/schema_hints/metadata/sheet.json", + "internal/cli/schema_hints/selection/sheet.json", + "wukong-develop:wukong/products" + ], + "agent_summary": "扫描表格公式单元格并按错误类型聚合返回错误数量与位置", + "agent_summary_source": "dws-agent-selection/sheet", + "availability": "available", + "avoid_when": [ + "读取公式文本用 range read --value-render-option formula" + ], + "canonical_path": "sheet.formula_verify", + "cli_name": "formula-verify", + "cli_path": "sheet formula-verify", + "confirmation": "not_required", + "description": "扫描钉钉电子表格中已落表的公式单元格,按计算结果错误类型聚合返回错误数量、位置和样本。\n\n不指定 --sheet-id / --range / --targets 时默认扫描整本表格的全部工作表。", + "display": "钉钉表格管理", + "effect": "read", + "effect_source": "agent-hint", + "examples": [ + "dws sheet formula-verify --node \u003cNODE_ID\u003e --format json" + ], + "field_provenance": { + "agent_summary": { + "candidates": [ + { + "precedence": "reviewed_explicit", + "review_reason": "人工审阅:对照悟空 develop 同名命令实现、Cobra Long/Example 与 Runtime 确认门禁移植;不改变命令身份、参数契约或接口绑定。", + "selected": true, + "source": "internal/cli/schema_hints/selection/sheet.json", + "value": "扫描表格公式单元格并按错误类型聚合返回错误数量与位置" + } + ], + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "人工审阅:对照悟空 develop 同名命令实现、Cobra Long/Example 与 Runtime 确认门禁移植;不改变命令身份、参数契约或接口绑定。", + "source": "internal/cli/schema_hints/selection/sheet.json", + "value": "扫描表格公式单元格并按错误类型聚合返回错误数量与位置" + }, + "availability": { + "candidates": [ + { + "precedence": "reviewed_explicit", + "review_reason": "dws-tool-metadata/sheet marks this tool as reviewed", + "selected": true, + "source": "internal/cli/schema_hints/metadata/sheet.json", + "value": "available" + } + ], + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "dws-tool-metadata/sheet marks this tool as reviewed", + "source": "internal/cli/schema_hints/metadata/sheet.json", + "value": "available" + }, + "avoid_when": { + "candidates": [ + { + "precedence": "reviewed_explicit", + "review_reason": "人工审阅:对照悟空 develop 同名命令实现、Cobra Long/Example 与 Runtime 确认门禁移植;不改变命令身份、参数契约或接口绑定。", + "selected": true, + "source": "internal/cli/schema_hints/selection/sheet.json", + "value": [ + "读取公式文本用 range read --value-render-option formula" + ] + } + ], + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "人工审阅:对照悟空 develop 同名命令实现、Cobra Long/Example 与 Runtime 确认门禁移植;不改变命令身份、参数契约或接口绑定。", + "source": "internal/cli/schema_hints/selection/sheet.json", + "value": [ + "读取公式文本用 range read --value-render-option formula" + ] + }, + "canonical_path": { + "candidates": [ + { + "precedence": "command_registry", + "selected": true, + "source": "reviewed_command_registry", + "source_ref": "sheet formula-verify", + "value": "sheet.formula_verify" + } + ], + "precedence": "command_registry", + "resolution": "registry_identity", + "source": "reviewed_command_registry", + "source_ref": "sheet formula-verify", + "value": "sheet.formula_verify" + }, + "confirmation": { + "candidates": [ + { + "precedence": "reviewed_explicit", + "review_reason": "dws-tool-metadata/sheet marks this tool as reviewed", + "selected": true, + "source": "internal/cli/schema_hints/metadata/sheet.json", + "value": "not_required" + } + ], + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "dws-tool-metadata/sheet marks this tool as reviewed", + "source": "internal/cli/schema_hints/metadata/sheet.json", + "value": "not_required" + }, + "description": { + "candidates": [ + { + "precedence": "cobra_help", + "selected": true, + "source": "cobra_help", + "value": "扫描钉钉电子表格中已落表的公式单元格,按计算结果错误类型聚合返回错误数量、位置和样本。\n\n不指定 --sheet-id / --range / --targets 时默认扫描整本表格的全部工作表。" + } + ], + "precedence": "cobra_help", + "resolution": "highest_precedence", + "source": "cobra_help", + "value": "扫描钉钉电子表格中已落表的公式单元格,按计算结果错误类型聚合返回错误数量、位置和样本。\n\n不指定 --sheet-id / --range / --targets 时默认扫描整本表格的全部工作表。" + }, + "effect": { + "candidates": [ + { + "precedence": "reviewed_explicit", + "review_reason": "dws-tool-metadata/sheet marks this tool as reviewed", + "selected": true, + "source": "internal/cli/schema_hints/metadata/sheet.json", + "value": "read" + } + ], + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "dws-tool-metadata/sheet marks this tool as reviewed", + "source": "internal/cli/schema_hints/metadata/sheet.json", + "value": "read" + }, + "examples": { + "candidates": [ + { + "precedence": "reviewed_explicit", + "review_reason": "人工审阅:对照悟空 develop 同名命令实现、Cobra Long/Example 与 Runtime 确认门禁移植;不改变命令身份、参数契约或接口绑定。", + "selected": true, + "source": "internal/cli/schema_hints/selection/sheet.json", + "value": [ + "dws sheet formula-verify --node \u003cNODE_ID\u003e --format json" + ] + } + ], + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "人工审阅:对照悟空 develop 同名命令实现、Cobra Long/Example 与 Runtime 确认门禁移植;不改变命令身份、参数契约或接口绑定。", + "source": "internal/cli/schema_hints/selection/sheet.json", + "value": [ + "dws sheet formula-verify --node \u003cNODE_ID\u003e --format json" + ] + }, + "idempotency": { + "candidates": [ + { + "precedence": "reviewed_explicit", + "review_reason": "dws-tool-metadata/sheet marks this tool as reviewed", + "selected": true, + "source": "internal/cli/schema_hints/metadata/sheet.json", + "value": "idempotent" + } + ], + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "dws-tool-metadata/sheet marks this tool as reviewed", + "source": "internal/cli/schema_hints/metadata/sheet.json", + "value": "idempotent" + }, + "interface_mode": { + "candidates": [ + { + "precedence": "reviewed_explicit", + "review_reason": "dws-tool-metadata/sheet marks this tool as reviewed", + "selected": true, + "source": "internal/cli/schema_hints/metadata/sheet.json", + "value": "composite" + } + ], + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "dws-tool-metadata/sheet marks this tool as reviewed", + "source": "internal/cli/schema_hints/metadata/sheet.json", + "value": "composite" + }, + "interface_reason": { + "candidates": [ + { + "precedence": "reviewed_explicit", + "review_reason": "dws-tool-metadata/sheet marks this tool as reviewed", + "selected": true, + "source": "internal/cli/schema_hints/metadata/sheet.json", + "value": "Reviewed unpinned remote adapter: this executable CLI wrapper calls a remote helper that is absent from the pinned MCP metadata snapshot; no single pinned semantically equivalent interface_ref can represent the command." + } + ], + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "dws-tool-metadata/sheet marks this tool as reviewed", + "source": "internal/cli/schema_hints/metadata/sheet.json", + "value": "Reviewed unpinned remote adapter: this executable CLI wrapper calls a remote helper that is absent from the pinned MCP metadata snapshot; no single pinned semantically equivalent interface_ref can represent the command." + }, + "interface_ref": { + "candidates": [ + { + "precedence": "reviewed_explicit", + "review_reason": "final interface mode composite forbids a direct MCP interface_ref", + "selected": true, + "source": "internal/cli/schema_hints/metadata/sheet.json", + "value": null + } + ], + "precedence": "reviewed_explicit", + "resolution": "interface_disposition_matrix", + "review_reason": "final interface mode composite forbids a direct MCP interface_ref", + "source": "internal/cli/schema_hints/metadata/sheet.json", + "value": null + }, + "reviewed": { + "candidates": [ + { + "precedence": "reviewed_explicit", + "review_reason": "dws-tool-metadata/sheet marks this tool as reviewed", + "selected": true, + "source": "internal/cli/schema_hints/metadata/sheet.json", + "value": true + }, + { + "precedence": "reviewed_explicit", + "review_reason": "人工审阅:对照悟空 develop 同名命令实现、Cobra Long/Example 与 Runtime 确认门禁移植;不改变命令身份、参数契约或接口绑定。", + "selected": false, + "source": "internal/cli/schema_hints/selection/sheet.json", + "value": true + } + ], + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "dws-tool-metadata/sheet marks this tool as reviewed", + "source": "internal/cli/schema_hints/metadata/sheet.json", + "value": true + }, + "risk": { + "candidates": [ + { + "precedence": "reviewed_explicit", + "review_reason": "dws-tool-metadata/sheet marks this tool as reviewed", + "selected": true, + "source": "internal/cli/schema_hints/metadata/sheet.json", + "value": "low" + } + ], + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "dws-tool-metadata/sheet marks this tool as reviewed", + "source": "internal/cli/schema_hints/metadata/sheet.json", + "value": "low" + }, + "title": { + "candidates": [ + { + "precedence": "cobra_help", + "selected": true, + "source": "cobra_help", + "value": "校验表格公式错误" + } + ], + "precedence": "cobra_help", + "resolution": "highest_precedence", + "source": "cobra_help", + "value": "校验表格公式错误" + }, + "use_when": { + "candidates": [ + { + "precedence": "reviewed_explicit", + "review_reason": "人工审阅:对照悟空 develop 同名命令实现、Cobra Long/Example 与 Runtime 确认门禁移植;不改变命令身份、参数契约或接口绑定。", + "selected": true, + "source": "internal/cli/schema_hints/selection/sheet.json", + "value": [ + "用户说 校验公式/检查公式错误/公式错误扫描" + ] + } + ], + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "人工审阅:对照悟空 develop 同名命令实现、Cobra Long/Example 与 Runtime 确认门禁移植;不改变命令身份、参数契约或接口绑定。", + "source": "internal/cli/schema_hints/selection/sheet.json", + "value": [ + "用户说 校验公式/检查公式错误/公式错误扫描" + ] + } + }, + "has_parameters": true, + "idempotency": "idempotent", + "interface_mode": "composite", + "interface_reason": "Reviewed unpinned remote adapter: this executable CLI wrapper calls a remote helper that is absent from the pinned MCP metadata snapshot; no single pinned semantically equivalent interface_ref can represent the command.", + "is_alias": false, + "name": "formula_verify", + "parameter_count": 7, + "parameters": { + "exit-on-error": { + "description": "发现公式错误时返回非 0 退出码,便于 CI/自动化使用", + "field_provenance": { + "description": { + "candidates": [ + { + "precedence": "cobra_contract", + "selected": true, + "source": "cobra_usage", + "value": "发现公式错误时返回非 0 退出码,便于 CI/自动化使用" + }, + { + "precedence": "default", + "selected": false, + "source": "default", + "value": "" + } + ], + "precedence": "cobra_contract", + "resolution": "highest_precedence", + "source": "cobra_usage", + "value": "发现公式错误时返回非 0 退出码,便于 CI/自动化使用" + }, + "property": { + "candidates": [ + { + "precedence": "reviewed_mapping_exclusion", + "review_reason": "Reviewed unpinned adapter: sheet.formula_verify has no singular pinned interface_ref; --exit-on-error is a CLI wrapper input and does not publish a direct interface property.", + "selected": true, + "source": "reviewed_mapping_exclusion", + "value": "" + }, + { + "precedence": "inference", + "selected": false, + "source": "flag_name_inference", + "value": "exitOnError" + } + ], + "precedence": "reviewed_mapping_exclusion", + "resolution": "highest_precedence", + "review_reason": "Reviewed unpinned adapter: sheet.formula_verify has no singular pinned interface_ref; --exit-on-error is a CLI wrapper input and does not publish a direct interface property.", + "source": "reviewed_mapping_exclusion", + "value": "" + }, + "required": { + "candidates": [ + { + "precedence": "default", + "selected": true, + "source": "default", + "value": false + } + ], + "precedence": "default", + "resolution": "fallback", + "source": "default", + "value": false + }, + "required_when": { + "candidates": [ + { + "precedence": "default", + "selected": true, + "source": "default", + "value": "" + } + ], + "precedence": "default", + "resolution": "highest_precedence", + "source": "default", + "value": "" + }, + "type": { + "candidates": [ + { + "precedence": "cobra_contract", + "selected": true, + "source": "cobra_flag_type", + "value": "boolean" + } + ], + "precedence": "cobra_contract", + "resolution": "highest_precedence", + "source": "cobra_flag_type", + "value": "boolean" + } + }, + "required": false, + "type": "boolean" + }, + "max-cells": { + "description": "最多扫描的单元格数", + "field_provenance": { + "description": { + "candidates": [ + { + "precedence": "cobra_contract", + "selected": true, + "source": "cobra_usage", + "value": "最多扫描的单元格数" + }, + { + "precedence": "default", + "selected": false, + "source": "default", + "value": "" + } + ], + "precedence": "cobra_contract", + "resolution": "highest_precedence", + "source": "cobra_usage", + "value": "最多扫描的单元格数" + }, + "property": { + "candidates": [ + { + "precedence": "reviewed_mapping_exclusion", + "review_reason": "Reviewed unpinned adapter: sheet.formula_verify has no singular pinned interface_ref; --max-cells is a CLI wrapper input and does not publish a direct interface property.", + "selected": true, + "source": "reviewed_mapping_exclusion", + "value": "" + }, + { + "precedence": "inference", + "selected": false, + "source": "flag_name_inference", + "value": "maxCells" + } + ], + "precedence": "reviewed_mapping_exclusion", + "resolution": "highest_precedence", + "review_reason": "Reviewed unpinned adapter: sheet.formula_verify has no singular pinned interface_ref; --max-cells is a CLI wrapper input and does not publish a direct interface property.", + "source": "reviewed_mapping_exclusion", + "value": "" + }, + "required": { + "candidates": [ + { + "precedence": "default", + "selected": true, + "source": "default", + "value": false + } + ], + "precedence": "default", + "resolution": "fallback", + "source": "default", + "value": false + }, + "required_when": { + "candidates": [ + { + "precedence": "default", + "selected": true, + "source": "default", + "value": "" + } + ], + "precedence": "default", + "resolution": "highest_precedence", + "source": "default", + "value": "" + }, + "type": { + "candidates": [ + { + "precedence": "cobra_contract", + "selected": true, + "source": "cobra_flag_type", + "value": "integer" + } + ], + "precedence": "cobra_contract", + "resolution": "highest_precedence", + "source": "cobra_flag_type", + "value": "integer" + } + }, + "required": false, + "type": "integer" + }, + "max-locations-per-error": { + "description": "每种错误类型最多返回的位置数", + "field_provenance": { + "description": { + "candidates": [ + { + "precedence": "cobra_contract", + "selected": true, + "source": "cobra_usage", + "value": "每种错误类型最多返回的位置数" + }, + { + "precedence": "default", + "selected": false, + "source": "default", + "value": "" + } + ], + "precedence": "cobra_contract", + "resolution": "highest_precedence", + "source": "cobra_usage", + "value": "每种错误类型最多返回的位置数" + }, + "property": { + "candidates": [ + { + "precedence": "reviewed_mapping_exclusion", + "review_reason": "Reviewed unpinned adapter: sheet.formula_verify has no singular pinned interface_ref; --max-locations-per-error is a CLI wrapper input and does not publish a direct interface property.", + "selected": true, + "source": "reviewed_mapping_exclusion", + "value": "" + }, + { + "precedence": "inference", + "selected": false, + "source": "flag_name_inference", + "value": "maxLocationsPerError" + } + ], + "precedence": "reviewed_mapping_exclusion", + "resolution": "highest_precedence", + "review_reason": "Reviewed unpinned adapter: sheet.formula_verify has no singular pinned interface_ref; --max-locations-per-error is a CLI wrapper input and does not publish a direct interface property.", + "source": "reviewed_mapping_exclusion", + "value": "" + }, + "required": { + "candidates": [ + { + "precedence": "default", + "selected": true, + "source": "default", + "value": false + } + ], + "precedence": "default", + "resolution": "fallback", + "source": "default", + "value": false + }, + "required_when": { + "candidates": [ + { + "precedence": "default", + "selected": true, + "source": "default", + "value": "" + } + ], + "precedence": "default", + "resolution": "highest_precedence", + "source": "default", + "value": "" + }, + "type": { + "candidates": [ + { + "precedence": "cobra_contract", + "selected": true, + "source": "cobra_flag_type", + "value": "integer" + } + ], + "precedence": "cobra_contract", + "resolution": "highest_precedence", + "source": "cobra_flag_type", + "value": "integer" + } + }, + "required": false, + "type": "integer" + }, + "node": { + "description": "表格文档 ID 或 URL (必填)", + "field_provenance": { + "description": { + "candidates": [ + { + "precedence": "cobra_contract", + "selected": true, + "source": "cobra_usage", + "value": "表格文档 ID 或 URL (必填)" + }, + { + "precedence": "default", + "selected": false, + "source": "default", + "value": "" + } + ], + "precedence": "cobra_contract", + "resolution": "highest_precedence", + "source": "cobra_usage", + "value": "表格文档 ID 或 URL (必填)" + }, + "property": { + "candidates": [ + { + "precedence": "reviewed_mapping_exclusion", + "review_reason": "Reviewed unpinned adapter: sheet.formula_verify has no singular pinned interface_ref; --node is a CLI wrapper input and does not publish a direct interface property.", + "selected": true, + "source": "reviewed_mapping_exclusion", + "value": "" + }, + { + "precedence": "inference", + "selected": false, + "source": "flag_name_inference", + "value": "node" + } + ], + "precedence": "reviewed_mapping_exclusion", + "resolution": "highest_precedence", + "review_reason": "Reviewed unpinned adapter: sheet.formula_verify has no singular pinned interface_ref; --node is a CLI wrapper input and does not publish a direct interface property.", + "source": "reviewed_mapping_exclusion", + "value": "" + }, + "required": { + "candidates": [ + { + "precedence": "inference", + "selected": true, + "source": "usage_required_inference", + "value": true + }, + { + "precedence": "default", + "selected": false, + "source": "default", + "value": false + } + ], + "precedence": "inference", + "resolution": "highest_precedence", + "source": "usage_required_inference", + "value": true + }, + "required_when": { + "candidates": [ + { + "precedence": "default", + "selected": true, + "source": "default", + "value": "" + } + ], + "precedence": "default", + "resolution": "highest_precedence", + "source": "default", + "value": "" + }, + "type": { + "candidates": [ + { + "precedence": "cobra_contract", + "selected": true, + "source": "cobra_flag_type", + "value": "string" + } + ], + "precedence": "cobra_contract", + "resolution": "highest_precedence", + "source": "cobra_flag_type", + "value": "string" + } + }, + "required": true, + "type": "string" + }, + "range": { + "description": "A1 范围;需与 --sheet-id 配合使用", + "field_provenance": { + "description": { + "candidates": [ + { + "precedence": "cobra_contract", + "selected": true, + "source": "cobra_usage", + "value": "A1 范围;需与 --sheet-id 配合使用" + }, + { + "precedence": "default", + "selected": false, + "source": "default", + "value": "" + } + ], + "precedence": "cobra_contract", + "resolution": "highest_precedence", + "source": "cobra_usage", + "value": "A1 范围;需与 --sheet-id 配合使用" + }, + "format": { + "candidates": [ + { + "precedence": "inference", + "selected": true, + "source": "usage_format_inference", + "value": "a1-range" + }, + { + "precedence": "default", + "selected": false, + "source": "default", + "value": "" + } + ], + "precedence": "inference", + "resolution": "highest_precedence", + "source": "usage_format_inference", + "value": "a1-range" + }, + "property": { + "candidates": [ + { + "precedence": "reviewed_mapping_exclusion", + "review_reason": "Reviewed unpinned adapter: sheet.formula_verify has no singular pinned interface_ref; --range is a CLI wrapper input and does not publish a direct interface property.", + "selected": true, + "source": "reviewed_mapping_exclusion", + "value": "" + }, + { + "precedence": "inference", + "selected": false, + "source": "flag_name_inference", + "value": "range" + } + ], + "precedence": "reviewed_mapping_exclusion", + "resolution": "highest_precedence", + "review_reason": "Reviewed unpinned adapter: sheet.formula_verify has no singular pinned interface_ref; --range is a CLI wrapper input and does not publish a direct interface property.", + "source": "reviewed_mapping_exclusion", + "value": "" + }, + "required": { + "candidates": [ + { + "precedence": "default", + "selected": true, + "source": "default", + "value": false + } + ], + "precedence": "default", + "resolution": "fallback", + "source": "default", + "value": false + }, + "required_when": { + "candidates": [ + { + "precedence": "default", + "selected": true, + "source": "default", + "value": "" + } + ], + "precedence": "default", + "resolution": "highest_precedence", + "source": "default", + "value": "" + }, + "type": { + "candidates": [ + { + "precedence": "cobra_contract", + "selected": true, + "source": "cobra_flag_type", + "value": "string" + } + ], + "precedence": "cobra_contract", + "resolution": "highest_precedence", + "source": "cobra_flag_type", + "value": "string" + } + }, + "format": "a1-range", + "required": false, + "type": "string" + }, + "sheet-id": { + "description": "工作表 ID 或名称;与 --range 组成单个扫描目标", + "field_provenance": { + "description": { + "candidates": [ + { + "precedence": "cobra_contract", + "selected": true, + "source": "cobra_usage", + "value": "工作表 ID 或名称;与 --range 组成单个扫描目标" + }, + { + "precedence": "default", + "selected": false, + "source": "default", + "value": "" + } + ], + "precedence": "cobra_contract", + "resolution": "highest_precedence", + "source": "cobra_usage", + "value": "工作表 ID 或名称;与 --range 组成单个扫描目标" + }, + "property": { + "candidates": [ + { + "precedence": "reviewed_mapping_exclusion", + "review_reason": "Reviewed unpinned adapter: sheet.formula_verify has no singular pinned interface_ref; --sheet-id is a CLI wrapper input and does not publish a direct interface property.", + "selected": true, + "source": "reviewed_mapping_exclusion", + "value": "" + }, + { + "precedence": "inference", + "selected": false, + "source": "flag_name_inference", + "value": "sheetId" + } + ], + "precedence": "reviewed_mapping_exclusion", + "resolution": "highest_precedence", + "review_reason": "Reviewed unpinned adapter: sheet.formula_verify has no singular pinned interface_ref; --sheet-id is a CLI wrapper input and does not publish a direct interface property.", + "source": "reviewed_mapping_exclusion", + "value": "" + }, + "required": { + "candidates": [ + { + "precedence": "default", + "selected": true, + "source": "default", + "value": false + } + ], + "precedence": "default", + "resolution": "fallback", + "source": "default", + "value": false + }, + "required_when": { + "candidates": [ + { + "precedence": "default", + "selected": true, + "source": "default", + "value": "" + } + ], + "precedence": "default", + "resolution": "highest_precedence", + "source": "default", + "value": "" + }, + "type": { + "candidates": [ + { + "precedence": "cobra_contract", + "selected": true, + "source": "cobra_flag_type", + "value": "string" + } + ], + "precedence": "cobra_contract", + "resolution": "highest_precedence", + "source": "cobra_flag_type", + "value": "string" + } + }, + "required": false, + "type": "string" + }, + "targets": { + "description": "扫描目标 JSON 数组、@文件路径 或 - 表示 stdin;每项 {\"sheetId\":\"Sheet1\",\"range\":\"A1:D100\"}", + "field_provenance": { + "description": { + "candidates": [ + { + "precedence": "cobra_contract", + "selected": true, + "source": "cobra_usage", + "value": "扫描目标 JSON 数组、@文件路径 或 - 表示 stdin;每项 {\"sheetId\":\"Sheet1\",\"range\":\"A1:D100\"}" + }, + { + "precedence": "default", + "selected": false, + "source": "default", + "value": "" + } + ], + "precedence": "cobra_contract", + "resolution": "highest_precedence", + "source": "cobra_usage", + "value": "扫描目标 JSON 数组、@文件路径 或 - 表示 stdin;每项 {\"sheetId\":\"Sheet1\",\"range\":\"A1:D100\"}" + }, + "format": { + "candidates": [ + { + "precedence": "inference", + "selected": true, + "source": "usage_format_inference", + "value": "a1-range" + }, + { + "precedence": "default", + "selected": false, + "source": "default", + "value": "" + } + ], + "precedence": "inference", + "resolution": "highest_precedence", + "source": "usage_format_inference", + "value": "a1-range" + }, + "property": { + "candidates": [ + { + "precedence": "reviewed_mapping_exclusion", + "review_reason": "Reviewed unpinned adapter: sheet.formula_verify has no singular pinned interface_ref; --targets is a CLI wrapper input and does not publish a direct interface property.", + "selected": true, + "source": "reviewed_mapping_exclusion", + "value": "" + }, + { + "precedence": "inference", + "selected": false, + "source": "flag_name_inference", + "value": "targets" + } + ], + "precedence": "reviewed_mapping_exclusion", + "resolution": "highest_precedence", + "review_reason": "Reviewed unpinned adapter: sheet.formula_verify has no singular pinned interface_ref; --targets is a CLI wrapper input and does not publish a direct interface property.", + "source": "reviewed_mapping_exclusion", + "value": "" + }, + "required": { + "candidates": [ + { + "precedence": "default", + "selected": true, + "source": "default", + "value": false + } + ], + "precedence": "default", + "resolution": "fallback", + "source": "default", + "value": false + }, + "required_when": { + "candidates": [ + { + "precedence": "default", + "selected": true, + "source": "default", + "value": "" + } + ], + "precedence": "default", + "resolution": "highest_precedence", + "source": "default", + "value": "" + }, + "type": { + "candidates": [ + { + "precedence": "cobra_contract", + "selected": true, + "source": "cobra_flag_type", + "value": "string" + } + ], + "precedence": "cobra_contract", + "resolution": "highest_precedence", + "source": "cobra_flag_type", + "value": "string" + } + }, + "format": "a1-range", + "required": false, + "type": "string" + } + }, + "path": "sheet.formula_verify", + "primary_cli_path": "sheet formula-verify", + "product_id": "sheet", + "reviewed": true, + "risk": "low", + "source": "reviewed_command_registry", + "title": "校验表格公式错误", + "use_when": [ + "用户说 校验公式/检查公式错误/公式错误扫描" + ] + }, "sheet.get_all_sheets": { "agent_metadata_source": "embedded-skill-metadata", "agent_source_refs": [ @@ -32666,8 +35004,105 @@ "is_alias": false, "metadata_source": "embedded-mcp-metadata", "name": "info", - "parameter_count": 2, + "parameter_count": 3, "parameters": { + "include": { + "description": "可选扩展信息,逗号分隔;支持 groups / row_heights / col_widths / hidden_rows / hidden_cols / frozen", + "field_provenance": { + "description": { + "candidates": [ + { + "precedence": "cobra_contract", + "selected": true, + "source": "cobra_usage", + "value": "可选扩展信息,逗号分隔;支持 groups / row_heights / col_widths / hidden_rows / hidden_cols / frozen" + }, + { + "precedence": "mcp_metadata", + "selected": false, + "source": "mcp_metadata", + "value": "可选扩展信息列表。当前支持 groups,用于返回 rowGroups / columnGroups 分组信息。未指定时只返回基础工作表信息、冻结信息和 mergedRanges。" + }, + { + "precedence": "default", + "selected": false, + "source": "default", + "value": "" + } + ], + "precedence": "cobra_contract", + "resolution": "highest_precedence", + "source": "cobra_usage", + "value": "可选扩展信息,逗号分隔;支持 groups / row_heights / col_widths / hidden_rows / hidden_cols / frozen" + }, + "property": { + "candidates": [ + { + "precedence": "versioned_binding", + "selected": true, + "source": "versioned_parameter_binding", + "value": "include" + }, + { + "precedence": "inference", + "selected": false, + "source": "flag_name_inference", + "value": "include" + } + ], + "precedence": "versioned_binding", + "resolution": "highest_precedence", + "source": "versioned_parameter_binding", + "value": "include" + }, + "required": { + "candidates": [ + { + "precedence": "default", + "selected": true, + "source": "default", + "value": false + } + ], + "precedence": "default", + "resolution": "fallback", + "source": "default", + "value": false + }, + "required_when": { + "candidates": [ + { + "precedence": "default", + "selected": true, + "source": "default", + "value": "" + } + ], + "precedence": "default", + "resolution": "highest_precedence", + "source": "default", + "value": "" + }, + "type": { + "candidates": [ + { + "precedence": "cobra_contract", + "selected": true, + "source": "cobra_flag_type", + "value": "array" + } + ], + "precedence": "cobra_contract", + "resolution": "highest_precedence", + "source": "cobra_flag_type", + "value": "array" + } + }, + "interface_description": "可选扩展信息列表。当前支持 groups,用于返回 rowGroups / columnGroups 分组信息。未指定时只返回基础工作表信息、冻结信息和 mergedRanges。", + "property": "include", + "required": false, + "type": "array" + }, "node": { "description": "表格文档 ID 或 URL (必填)", "field_provenance": { @@ -35013,6 +37448,906 @@ "需要查看工作表上有哪些透视表或某个透视表详情时" ] }, + "sheet.list_sheet_comments": { + "agent_metadata_source": "embedded-skill-metadata", + "agent_source_refs": [ + "CommandRegistry:canonical_path=sheet.list_sheet_comments", + "cobra-help:dws sheet comment list", + "internal/cli/schema_hints/metadata/sheet.json", + "internal/cli/schema_hints/selection/sheet.json", + "wukong-develop:wukong/products" + ], + "agent_summary": "查询表格单元格评论列表,支持分页与按解决状态过滤", + "agent_summary_source": "dws-agent-selection/sheet", + "availability": "available", + "avoid_when": [ + "新建评论用 comment create" + ], + "canonical_path": "sheet.list_sheet_comments", + "cli_name": "list", + "cli_path": "sheet comment list", + "confirmation": "not_required", + "description": "查询表格评论列表", + "display": "钉钉表格管理", + "effect": "read", + "effect_source": "agent-hint", + "examples": [ + "dws sheet comment list --node \u003cSHEET_ID\u003e --format json" + ], + "field_provenance": { + "agent_summary": { + "candidates": [ + { + "precedence": "reviewed_explicit", + "review_reason": "人工审阅:对照悟空 develop 同名命令实现、Cobra Long/Example 与 Runtime 确认门禁移植;不改变命令身份、参数契约或接口绑定。", + "selected": true, + "source": "internal/cli/schema_hints/selection/sheet.json", + "value": "查询表格单元格评论列表,支持分页与按解决状态过滤" + } + ], + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "人工审阅:对照悟空 develop 同名命令实现、Cobra Long/Example 与 Runtime 确认门禁移植;不改变命令身份、参数契约或接口绑定。", + "source": "internal/cli/schema_hints/selection/sheet.json", + "value": "查询表格单元格评论列表,支持分页与按解决状态过滤" + }, + "availability": { + "candidates": [ + { + "precedence": "reviewed_explicit", + "review_reason": "dws-tool-metadata/sheet marks this tool as reviewed", + "selected": true, + "source": "internal/cli/schema_hints/metadata/sheet.json", + "value": "available" + } + ], + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "dws-tool-metadata/sheet marks this tool as reviewed", + "source": "internal/cli/schema_hints/metadata/sheet.json", + "value": "available" + }, + "avoid_when": { + "candidates": [ + { + "precedence": "reviewed_explicit", + "review_reason": "人工审阅:对照悟空 develop 同名命令实现、Cobra Long/Example 与 Runtime 确认门禁移植;不改变命令身份、参数契约或接口绑定。", + "selected": true, + "source": "internal/cli/schema_hints/selection/sheet.json", + "value": [ + "新建评论用 comment create" + ] + } + ], + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "人工审阅:对照悟空 develop 同名命令实现、Cobra Long/Example 与 Runtime 确认门禁移植;不改变命令身份、参数契约或接口绑定。", + "source": "internal/cli/schema_hints/selection/sheet.json", + "value": [ + "新建评论用 comment create" + ] + }, + "canonical_path": { + "candidates": [ + { + "precedence": "command_registry", + "selected": true, + "source": "reviewed_command_registry", + "source_ref": "sheet comment list", + "value": "sheet.list_sheet_comments" + } + ], + "precedence": "command_registry", + "resolution": "registry_identity", + "source": "reviewed_command_registry", + "source_ref": "sheet comment list", + "value": "sheet.list_sheet_comments" + }, + "confirmation": { + "candidates": [ + { + "precedence": "reviewed_explicit", + "review_reason": "dws-tool-metadata/sheet marks this tool as reviewed", + "selected": true, + "source": "internal/cli/schema_hints/metadata/sheet.json", + "value": "not_required" + } + ], + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "dws-tool-metadata/sheet marks this tool as reviewed", + "source": "internal/cli/schema_hints/metadata/sheet.json", + "value": "not_required" + }, + "description": { + "candidates": [ + { + "precedence": "cobra_help", + "selected": true, + "source": "cobra_help", + "value": "查询表格评论列表" + } + ], + "precedence": "cobra_help", + "resolution": "highest_precedence", + "source": "cobra_help", + "value": "查询表格评论列表" + }, + "effect": { + "candidates": [ + { + "precedence": "reviewed_explicit", + "review_reason": "dws-tool-metadata/sheet marks this tool as reviewed", + "selected": true, + "source": "internal/cli/schema_hints/metadata/sheet.json", + "value": "read" + } + ], + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "dws-tool-metadata/sheet marks this tool as reviewed", + "source": "internal/cli/schema_hints/metadata/sheet.json", + "value": "read" + }, + "examples": { + "candidates": [ + { + "precedence": "reviewed_explicit", + "review_reason": "人工审阅:对照悟空 develop 同名命令实现、Cobra Long/Example 与 Runtime 确认门禁移植;不改变命令身份、参数契约或接口绑定。", + "selected": true, + "source": "internal/cli/schema_hints/selection/sheet.json", + "value": [ + "dws sheet comment list --node \u003cSHEET_ID\u003e --format json" + ] + } + ], + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "人工审阅:对照悟空 develop 同名命令实现、Cobra Long/Example 与 Runtime 确认门禁移植;不改变命令身份、参数契约或接口绑定。", + "source": "internal/cli/schema_hints/selection/sheet.json", + "value": [ + "dws sheet comment list --node \u003cSHEET_ID\u003e --format json" + ] + }, + "idempotency": { + "candidates": [ + { + "precedence": "reviewed_explicit", + "review_reason": "dws-tool-metadata/sheet marks this tool as reviewed", + "selected": true, + "source": "internal/cli/schema_hints/metadata/sheet.json", + "value": "idempotent" + } + ], + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "dws-tool-metadata/sheet marks this tool as reviewed", + "source": "internal/cli/schema_hints/metadata/sheet.json", + "value": "idempotent" + }, + "interface_mode": { + "candidates": [ + { + "precedence": "reviewed_explicit", + "review_reason": "dws-tool-metadata/sheet marks this tool as reviewed", + "selected": true, + "source": "internal/cli/schema_hints/metadata/sheet.json", + "value": "composite" + } + ], + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "dws-tool-metadata/sheet marks this tool as reviewed", + "source": "internal/cli/schema_hints/metadata/sheet.json", + "value": "composite" + }, + "interface_reason": { + "candidates": [ + { + "precedence": "reviewed_explicit", + "review_reason": "dws-tool-metadata/sheet marks this tool as reviewed", + "selected": true, + "source": "internal/cli/schema_hints/metadata/sheet.json", + "value": "Reviewed unpinned remote adapter: this executable CLI wrapper calls a remote helper that is absent from the pinned MCP metadata snapshot; no single pinned semantically equivalent interface_ref can represent the command." + } + ], + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "dws-tool-metadata/sheet marks this tool as reviewed", + "source": "internal/cli/schema_hints/metadata/sheet.json", + "value": "Reviewed unpinned remote adapter: this executable CLI wrapper calls a remote helper that is absent from the pinned MCP metadata snapshot; no single pinned semantically equivalent interface_ref can represent the command." + }, + "interface_ref": { + "candidates": [ + { + "precedence": "reviewed_explicit", + "review_reason": "final interface mode composite forbids a direct MCP interface_ref", + "selected": true, + "source": "internal/cli/schema_hints/metadata/sheet.json", + "value": null + } + ], + "precedence": "reviewed_explicit", + "resolution": "interface_disposition_matrix", + "review_reason": "final interface mode composite forbids a direct MCP interface_ref", + "source": "internal/cli/schema_hints/metadata/sheet.json", + "value": null + }, + "reviewed": { + "candidates": [ + { + "precedence": "reviewed_explicit", + "review_reason": "dws-tool-metadata/sheet marks this tool as reviewed", + "selected": true, + "source": "internal/cli/schema_hints/metadata/sheet.json", + "value": true + }, + { + "precedence": "reviewed_explicit", + "review_reason": "人工审阅:对照悟空 develop 同名命令实现、Cobra Long/Example 与 Runtime 确认门禁移植;不改变命令身份、参数契约或接口绑定。", + "selected": false, + "source": "internal/cli/schema_hints/selection/sheet.json", + "value": true + } + ], + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "dws-tool-metadata/sheet marks this tool as reviewed", + "source": "internal/cli/schema_hints/metadata/sheet.json", + "value": true + }, + "risk": { + "candidates": [ + { + "precedence": "reviewed_explicit", + "review_reason": "dws-tool-metadata/sheet marks this tool as reviewed", + "selected": true, + "source": "internal/cli/schema_hints/metadata/sheet.json", + "value": "low" + } + ], + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "dws-tool-metadata/sheet marks this tool as reviewed", + "source": "internal/cli/schema_hints/metadata/sheet.json", + "value": "low" + }, + "title": { + "candidates": [ + { + "precedence": "cobra_help", + "selected": true, + "source": "cobra_help", + "value": "查询表格评论列表" + } + ], + "precedence": "cobra_help", + "resolution": "highest_precedence", + "source": "cobra_help", + "value": "查询表格评论列表" + }, + "use_when": { + "candidates": [ + { + "precedence": "reviewed_explicit", + "review_reason": "人工审阅:对照悟空 develop 同名命令实现、Cobra Long/Example 与 Runtime 确认门禁移植;不改变命令身份、参数契约或接口绑定。", + "selected": true, + "source": "internal/cli/schema_hints/selection/sheet.json", + "value": [ + "用户说 看某格的评论/表格里有哪些批注" + ] + } + ], + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "人工审阅:对照悟空 develop 同名命令实现、Cobra Long/Example 与 Runtime 确认门禁移植;不改变命令身份、参数契约或接口绑定。", + "source": "internal/cli/schema_hints/selection/sheet.json", + "value": [ + "用户说 看某格的评论/表格里有哪些批注" + ] + } + }, + "group": "comment", + "has_parameters": true, + "idempotency": "idempotent", + "interface_mode": "composite", + "interface_reason": "Reviewed unpinned remote adapter: this executable CLI wrapper calls a remote helper that is absent from the pinned MCP metadata snapshot; no single pinned semantically equivalent interface_ref can represent the command.", + "is_alias": false, + "name": "list_sheet_comments", + "parameter_count": 6, + "parameters": { + "cursor": { + "description": "分页游标", + "field_provenance": { + "description": { + "candidates": [ + { + "precedence": "cobra_contract", + "selected": true, + "source": "cobra_usage", + "value": "分页游标" + }, + { + "precedence": "default", + "selected": false, + "source": "default", + "value": "" + } + ], + "precedence": "cobra_contract", + "resolution": "highest_precedence", + "source": "cobra_usage", + "value": "分页游标" + }, + "property": { + "candidates": [ + { + "precedence": "reviewed_mapping_exclusion", + "review_reason": "Reviewed unpinned adapter: sheet.list_sheet_comments has no singular pinned interface_ref; --cursor is a CLI wrapper input and does not publish a direct interface property.", + "selected": true, + "source": "reviewed_mapping_exclusion", + "value": "" + }, + { + "precedence": "inference", + "selected": false, + "source": "flag_name_inference", + "value": "cursor" + } + ], + "precedence": "reviewed_mapping_exclusion", + "resolution": "highest_precedence", + "review_reason": "Reviewed unpinned adapter: sheet.list_sheet_comments has no singular pinned interface_ref; --cursor is a CLI wrapper input and does not publish a direct interface property.", + "source": "reviewed_mapping_exclusion", + "value": "" + }, + "required": { + "candidates": [ + { + "precedence": "default", + "selected": true, + "source": "default", + "value": false + } + ], + "precedence": "default", + "resolution": "fallback", + "source": "default", + "value": false + }, + "required_when": { + "candidates": [ + { + "precedence": "default", + "selected": true, + "source": "default", + "value": "" + } + ], + "precedence": "default", + "resolution": "highest_precedence", + "source": "default", + "value": "" + }, + "type": { + "candidates": [ + { + "precedence": "cobra_contract", + "selected": true, + "source": "cobra_flag_type", + "value": "string" + } + ], + "precedence": "cobra_contract", + "resolution": "highest_precedence", + "source": "cobra_flag_type", + "value": "string" + } + }, + "required": false, + "type": "string" + }, + "limit": { + "default": "50", + "description": "每页返回的评论数量,默认 50,最大 50", + "field_provenance": { + "description": { + "candidates": [ + { + "precedence": "cobra_contract", + "selected": true, + "source": "cobra_usage", + "value": "每页返回的评论数量,默认 50,最大 50" + }, + { + "precedence": "default", + "selected": false, + "source": "default", + "value": "" + } + ], + "precedence": "cobra_contract", + "resolution": "highest_precedence", + "source": "cobra_usage", + "value": "每页返回的评论数量,默认 50,最大 50" + }, + "property": { + "candidates": [ + { + "precedence": "reviewed_mapping_exclusion", + "review_reason": "Reviewed unpinned adapter: sheet.list_sheet_comments has no singular pinned interface_ref; --limit is a CLI wrapper input and does not publish a direct interface property.", + "selected": true, + "source": "reviewed_mapping_exclusion", + "value": "" + }, + { + "precedence": "inference", + "selected": false, + "source": "flag_name_inference", + "value": "limit" + } + ], + "precedence": "reviewed_mapping_exclusion", + "resolution": "highest_precedence", + "review_reason": "Reviewed unpinned adapter: sheet.list_sheet_comments has no singular pinned interface_ref; --limit is a CLI wrapper input and does not publish a direct interface property.", + "source": "reviewed_mapping_exclusion", + "value": "" + }, + "required": { + "candidates": [ + { + "precedence": "cobra_contract", + "selected": true, + "source": "cobra_nonzero_default", + "value": false + }, + { + "precedence": "default", + "selected": false, + "source": "default", + "value": false + } + ], + "precedence": "cobra_contract", + "resolution": "highest_precedence", + "source": "cobra_nonzero_default", + "value": false + }, + "required_when": { + "candidates": [ + { + "precedence": "default", + "selected": true, + "source": "default", + "value": "" + } + ], + "precedence": "default", + "resolution": "highest_precedence", + "source": "default", + "value": "" + }, + "type": { + "candidates": [ + { + "precedence": "cobra_contract", + "selected": true, + "source": "cobra_flag_type", + "value": "integer" + } + ], + "precedence": "cobra_contract", + "resolution": "highest_precedence", + "source": "cobra_flag_type", + "value": "integer" + } + }, + "required": false, + "type": "integer" + }, + "node": { + "description": "表格文档 ID 或 URL (必填)", + "field_provenance": { + "description": { + "candidates": [ + { + "precedence": "cobra_contract", + "selected": true, + "source": "cobra_usage", + "value": "表格文档 ID 或 URL (必填)" + }, + { + "precedence": "default", + "selected": false, + "source": "default", + "value": "" + } + ], + "precedence": "cobra_contract", + "resolution": "highest_precedence", + "source": "cobra_usage", + "value": "表格文档 ID 或 URL (必填)" + }, + "property": { + "candidates": [ + { + "precedence": "reviewed_mapping_exclusion", + "review_reason": "Reviewed unpinned adapter: sheet.list_sheet_comments has no singular pinned interface_ref; --node is a CLI wrapper input and does not publish a direct interface property.", + "selected": true, + "source": "reviewed_mapping_exclusion", + "value": "" + }, + { + "precedence": "inference", + "selected": false, + "source": "flag_name_inference", + "value": "node" + } + ], + "precedence": "reviewed_mapping_exclusion", + "resolution": "highest_precedence", + "review_reason": "Reviewed unpinned adapter: sheet.list_sheet_comments has no singular pinned interface_ref; --node is a CLI wrapper input and does not publish a direct interface property.", + "source": "reviewed_mapping_exclusion", + "value": "" + }, + "required": { + "candidates": [ + { + "precedence": "inference", + "selected": true, + "source": "usage_required_inference", + "value": true + }, + { + "precedence": "default", + "selected": false, + "source": "default", + "value": false + } + ], + "precedence": "inference", + "resolution": "highest_precedence", + "source": "usage_required_inference", + "value": true + }, + "required_when": { + "candidates": [ + { + "precedence": "default", + "selected": true, + "source": "default", + "value": "" + } + ], + "precedence": "default", + "resolution": "highest_precedence", + "source": "default", + "value": "" + }, + "type": { + "candidates": [ + { + "precedence": "cobra_contract", + "selected": true, + "source": "cobra_flag_type", + "value": "string" + } + ], + "precedence": "cobra_contract", + "resolution": "highest_precedence", + "source": "cobra_flag_type", + "value": "string" + } + }, + "required": true, + "type": "string" + }, + "range": { + "description": "单元格位置 A1 表示法(与 --sheet-id 一起按单元格过滤)", + "field_provenance": { + "description": { + "candidates": [ + { + "precedence": "cobra_contract", + "selected": true, + "source": "cobra_usage", + "value": "单元格位置 A1 表示法(与 --sheet-id 一起按单元格过滤)" + }, + { + "precedence": "default", + "selected": false, + "source": "default", + "value": "" + } + ], + "precedence": "cobra_contract", + "resolution": "highest_precedence", + "source": "cobra_usage", + "value": "单元格位置 A1 表示法(与 --sheet-id 一起按单元格过滤)" + }, + "format": { + "candidates": [ + { + "precedence": "inference", + "selected": true, + "source": "usage_format_inference", + "value": "a1-range" + }, + { + "precedence": "default", + "selected": false, + "source": "default", + "value": "" + } + ], + "precedence": "inference", + "resolution": "highest_precedence", + "source": "usage_format_inference", + "value": "a1-range" + }, + "property": { + "candidates": [ + { + "precedence": "reviewed_mapping_exclusion", + "review_reason": "Reviewed unpinned adapter: sheet.list_sheet_comments has no singular pinned interface_ref; --range is a CLI wrapper input and does not publish a direct interface property.", + "selected": true, + "source": "reviewed_mapping_exclusion", + "value": "" + }, + { + "precedence": "inference", + "selected": false, + "source": "flag_name_inference", + "value": "range" + } + ], + "precedence": "reviewed_mapping_exclusion", + "resolution": "highest_precedence", + "review_reason": "Reviewed unpinned adapter: sheet.list_sheet_comments has no singular pinned interface_ref; --range is a CLI wrapper input and does not publish a direct interface property.", + "source": "reviewed_mapping_exclusion", + "value": "" + }, + "required": { + "candidates": [ + { + "precedence": "default", + "selected": true, + "source": "default", + "value": false + } + ], + "precedence": "default", + "resolution": "fallback", + "source": "default", + "value": false + }, + "required_when": { + "candidates": [ + { + "precedence": "default", + "selected": true, + "source": "default", + "value": "" + } + ], + "precedence": "default", + "resolution": "highest_precedence", + "source": "default", + "value": "" + }, + "type": { + "candidates": [ + { + "precedence": "cobra_contract", + "selected": true, + "source": "cobra_flag_type", + "value": "string" + } + ], + "precedence": "cobra_contract", + "resolution": "highest_precedence", + "source": "cobra_flag_type", + "value": "string" + } + }, + "format": "a1-range", + "required": false, + "type": "string" + }, + "resolve-status": { + "description": "按解决状态过滤: resolved / unresolved", + "field_provenance": { + "description": { + "candidates": [ + { + "precedence": "cobra_contract", + "selected": true, + "source": "cobra_usage", + "value": "按解决状态过滤: resolved / unresolved" + }, + { + "precedence": "default", + "selected": false, + "source": "default", + "value": "" + } + ], + "precedence": "cobra_contract", + "resolution": "highest_precedence", + "source": "cobra_usage", + "value": "按解决状态过滤: resolved / unresolved" + }, + "property": { + "candidates": [ + { + "precedence": "reviewed_mapping_exclusion", + "review_reason": "Reviewed unpinned adapter: sheet.list_sheet_comments has no singular pinned interface_ref; --resolve-status is a CLI wrapper input and does not publish a direct interface property.", + "selected": true, + "source": "reviewed_mapping_exclusion", + "value": "" + }, + { + "precedence": "inference", + "selected": false, + "source": "flag_name_inference", + "value": "resolveStatus" + } + ], + "precedence": "reviewed_mapping_exclusion", + "resolution": "highest_precedence", + "review_reason": "Reviewed unpinned adapter: sheet.list_sheet_comments has no singular pinned interface_ref; --resolve-status is a CLI wrapper input and does not publish a direct interface property.", + "source": "reviewed_mapping_exclusion", + "value": "" + }, + "required": { + "candidates": [ + { + "precedence": "default", + "selected": true, + "source": "default", + "value": false + } + ], + "precedence": "default", + "resolution": "fallback", + "source": "default", + "value": false + }, + "required_when": { + "candidates": [ + { + "precedence": "default", + "selected": true, + "source": "default", + "value": "" + } + ], + "precedence": "default", + "resolution": "highest_precedence", + "source": "default", + "value": "" + }, + "type": { + "candidates": [ + { + "precedence": "cobra_contract", + "selected": true, + "source": "cobra_flag_type", + "value": "string" + } + ], + "precedence": "cobra_contract", + "resolution": "highest_precedence", + "source": "cobra_flag_type", + "value": "string" + } + }, + "required": false, + "type": "string" + }, + "sheet-id": { + "description": "工作表 ID 或名称(与 --range 一起按单元格过滤)", + "field_provenance": { + "description": { + "candidates": [ + { + "precedence": "cobra_contract", + "selected": true, + "source": "cobra_usage", + "value": "工作表 ID 或名称(与 --range 一起按单元格过滤)" + }, + { + "precedence": "default", + "selected": false, + "source": "default", + "value": "" + } + ], + "precedence": "cobra_contract", + "resolution": "highest_precedence", + "source": "cobra_usage", + "value": "工作表 ID 或名称(与 --range 一起按单元格过滤)" + }, + "property": { + "candidates": [ + { + "precedence": "reviewed_mapping_exclusion", + "review_reason": "Reviewed unpinned adapter: sheet.list_sheet_comments has no singular pinned interface_ref; --sheet-id is a CLI wrapper input and does not publish a direct interface property.", + "selected": true, + "source": "reviewed_mapping_exclusion", + "value": "" + }, + { + "precedence": "inference", + "selected": false, + "source": "flag_name_inference", + "value": "sheetId" + } + ], + "precedence": "reviewed_mapping_exclusion", + "resolution": "highest_precedence", + "review_reason": "Reviewed unpinned adapter: sheet.list_sheet_comments has no singular pinned interface_ref; --sheet-id is a CLI wrapper input and does not publish a direct interface property.", + "source": "reviewed_mapping_exclusion", + "value": "" + }, + "required": { + "candidates": [ + { + "precedence": "default", + "selected": true, + "source": "default", + "value": false + } + ], + "precedence": "default", + "resolution": "fallback", + "source": "default", + "value": false + }, + "required_when": { + "candidates": [ + { + "precedence": "default", + "selected": true, + "source": "default", + "value": "" + } + ], + "precedence": "default", + "resolution": "highest_precedence", + "source": "default", + "value": "" + }, + "type": { + "candidates": [ + { + "precedence": "cobra_contract", + "selected": true, + "source": "cobra_flag_type", + "value": "string" + } + ], + "precedence": "cobra_contract", + "resolution": "highest_precedence", + "source": "cobra_flag_type", + "value": "string" + } + }, + "required": false, + "type": "string" + } + }, + "path": "sheet.list_sheet_comments", + "primary_cli_path": "sheet comment list", + "product_id": "sheet", + "reviewed": true, + "risk": "low", + "source": "reviewed_command_registry", + "title": "查询表格评论列表", + "use_when": [ + "用户说 看某格的评论/表格里有哪些批注" + ] + }, "sheet.media_upload": { "agent_metadata_source": "embedded-skill-metadata", "agent_source_refs": [ @@ -46687,6 +50022,799 @@ "要把工作表中匹配文本批量替换为另一文本时" ] }, + "sheet.reply_sheet_comment": { + "agent_metadata_source": "embedded-skill-metadata", + "agent_source_refs": [ + "CommandRegistry:canonical_path=sheet.reply_sheet_comment", + "cobra-help:dws sheet comment reply", + "internal/cli/schema_hints/metadata/sheet.json", + "internal/cli/schema_hints/selection/sheet.json", + "wukong-develop:wukong/products" + ], + "agent_summary": "回复指定单元格评论,支持表情贴图回复", + "agent_summary_source": "dws-agent-selection/sheet", + "availability": "available", + "avoid_when": [ + "修改评论内容用 comment update" + ], + "canonical_path": "sheet.reply_sheet_comment", + "cli_name": "reply", + "cli_path": "sheet comment reply", + "confirmation": "not_required", + "description": "回复单元格评论", + "display": "钉钉表格管理", + "effect": "write", + "effect_source": "agent-hint", + "examples": [ + "dws sheet comment reply --node \u003cSHEET_ID\u003e --comment-key \u003cCOMMENT_KEY\u003e --content \"已核实\" --format json" + ], + "field_provenance": { + "agent_summary": { + "candidates": [ + { + "precedence": "reviewed_explicit", + "review_reason": "人工审阅:对照悟空 develop 同名命令实现、Cobra Long/Example 与 Runtime 确认门禁移植;不改变命令身份、参数契约或接口绑定。", + "selected": true, + "source": "internal/cli/schema_hints/selection/sheet.json", + "value": "回复指定单元格评论,支持表情贴图回复" + } + ], + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "人工审阅:对照悟空 develop 同名命令实现、Cobra Long/Example 与 Runtime 确认门禁移植;不改变命令身份、参数契约或接口绑定。", + "source": "internal/cli/schema_hints/selection/sheet.json", + "value": "回复指定单元格评论,支持表情贴图回复" + }, + "availability": { + "candidates": [ + { + "precedence": "reviewed_explicit", + "review_reason": "dws-tool-metadata/sheet marks this tool as reviewed", + "selected": true, + "source": "internal/cli/schema_hints/metadata/sheet.json", + "value": "available" + } + ], + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "dws-tool-metadata/sheet marks this tool as reviewed", + "source": "internal/cli/schema_hints/metadata/sheet.json", + "value": "available" + }, + "avoid_when": { + "candidates": [ + { + "precedence": "reviewed_explicit", + "review_reason": "人工审阅:对照悟空 develop 同名命令实现、Cobra Long/Example 与 Runtime 确认门禁移植;不改变命令身份、参数契约或接口绑定。", + "selected": true, + "source": "internal/cli/schema_hints/selection/sheet.json", + "value": [ + "修改评论内容用 comment update" + ] + } + ], + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "人工审阅:对照悟空 develop 同名命令实现、Cobra Long/Example 与 Runtime 确认门禁移植;不改变命令身份、参数契约或接口绑定。", + "source": "internal/cli/schema_hints/selection/sheet.json", + "value": [ + "修改评论内容用 comment update" + ] + }, + "canonical_path": { + "candidates": [ + { + "precedence": "command_registry", + "selected": true, + "source": "reviewed_command_registry", + "source_ref": "sheet comment reply", + "value": "sheet.reply_sheet_comment" + } + ], + "precedence": "command_registry", + "resolution": "registry_identity", + "source": "reviewed_command_registry", + "source_ref": "sheet comment reply", + "value": "sheet.reply_sheet_comment" + }, + "confirmation": { + "candidates": [ + { + "precedence": "reviewed_explicit", + "review_reason": "dws-tool-metadata/sheet marks this tool as reviewed", + "selected": true, + "source": "internal/cli/schema_hints/metadata/sheet.json", + "value": "not_required" + } + ], + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "dws-tool-metadata/sheet marks this tool as reviewed", + "source": "internal/cli/schema_hints/metadata/sheet.json", + "value": "not_required" + }, + "description": { + "candidates": [ + { + "precedence": "cobra_help", + "selected": true, + "source": "cobra_help", + "value": "回复单元格评论" + } + ], + "precedence": "cobra_help", + "resolution": "highest_precedence", + "source": "cobra_help", + "value": "回复单元格评论" + }, + "effect": { + "candidates": [ + { + "precedence": "reviewed_explicit", + "review_reason": "dws-tool-metadata/sheet marks this tool as reviewed", + "selected": true, + "source": "internal/cli/schema_hints/metadata/sheet.json", + "value": "write" + } + ], + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "dws-tool-metadata/sheet marks this tool as reviewed", + "source": "internal/cli/schema_hints/metadata/sheet.json", + "value": "write" + }, + "examples": { + "candidates": [ + { + "precedence": "reviewed_explicit", + "review_reason": "人工审阅:对照悟空 develop 同名命令实现、Cobra Long/Example 与 Runtime 确认门禁移植;不改变命令身份、参数契约或接口绑定。", + "selected": true, + "source": "internal/cli/schema_hints/selection/sheet.json", + "value": [ + "dws sheet comment reply --node \u003cSHEET_ID\u003e --comment-key \u003cCOMMENT_KEY\u003e --content \"已核实\" --format json" + ] + } + ], + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "人工审阅:对照悟空 develop 同名命令实现、Cobra Long/Example 与 Runtime 确认门禁移植;不改变命令身份、参数契约或接口绑定。", + "source": "internal/cli/schema_hints/selection/sheet.json", + "value": [ + "dws sheet comment reply --node \u003cSHEET_ID\u003e --comment-key \u003cCOMMENT_KEY\u003e --content \"已核实\" --format json" + ] + }, + "idempotency": { + "candidates": [ + { + "precedence": "reviewed_explicit", + "review_reason": "dws-tool-metadata/sheet marks this tool as reviewed", + "selected": true, + "source": "internal/cli/schema_hints/metadata/sheet.json", + "value": "non_idempotent" + } + ], + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "dws-tool-metadata/sheet marks this tool as reviewed", + "source": "internal/cli/schema_hints/metadata/sheet.json", + "value": "non_idempotent" + }, + "interface_mode": { + "candidates": [ + { + "precedence": "reviewed_explicit", + "review_reason": "dws-tool-metadata/sheet marks this tool as reviewed", + "selected": true, + "source": "internal/cli/schema_hints/metadata/sheet.json", + "value": "composite" + } + ], + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "dws-tool-metadata/sheet marks this tool as reviewed", + "source": "internal/cli/schema_hints/metadata/sheet.json", + "value": "composite" + }, + "interface_reason": { + "candidates": [ + { + "precedence": "reviewed_explicit", + "review_reason": "dws-tool-metadata/sheet marks this tool as reviewed", + "selected": true, + "source": "internal/cli/schema_hints/metadata/sheet.json", + "value": "Reviewed unpinned remote adapter: this executable CLI wrapper calls a remote helper that is absent from the pinned MCP metadata snapshot; no single pinned semantically equivalent interface_ref can represent the command." + } + ], + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "dws-tool-metadata/sheet marks this tool as reviewed", + "source": "internal/cli/schema_hints/metadata/sheet.json", + "value": "Reviewed unpinned remote adapter: this executable CLI wrapper calls a remote helper that is absent from the pinned MCP metadata snapshot; no single pinned semantically equivalent interface_ref can represent the command." + }, + "interface_ref": { + "candidates": [ + { + "precedence": "reviewed_explicit", + "review_reason": "final interface mode composite forbids a direct MCP interface_ref", + "selected": true, + "source": "internal/cli/schema_hints/metadata/sheet.json", + "value": null + } + ], + "precedence": "reviewed_explicit", + "resolution": "interface_disposition_matrix", + "review_reason": "final interface mode composite forbids a direct MCP interface_ref", + "source": "internal/cli/schema_hints/metadata/sheet.json", + "value": null + }, + "reviewed": { + "candidates": [ + { + "precedence": "reviewed_explicit", + "review_reason": "dws-tool-metadata/sheet marks this tool as reviewed", + "selected": true, + "source": "internal/cli/schema_hints/metadata/sheet.json", + "value": true + }, + { + "precedence": "reviewed_explicit", + "review_reason": "人工审阅:对照悟空 develop 同名命令实现、Cobra Long/Example 与 Runtime 确认门禁移植;不改变命令身份、参数契约或接口绑定。", + "selected": false, + "source": "internal/cli/schema_hints/selection/sheet.json", + "value": true + } + ], + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "dws-tool-metadata/sheet marks this tool as reviewed", + "source": "internal/cli/schema_hints/metadata/sheet.json", + "value": true + }, + "risk": { + "candidates": [ + { + "precedence": "reviewed_explicit", + "review_reason": "dws-tool-metadata/sheet marks this tool as reviewed", + "selected": true, + "source": "internal/cli/schema_hints/metadata/sheet.json", + "value": "low" + } + ], + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "dws-tool-metadata/sheet marks this tool as reviewed", + "source": "internal/cli/schema_hints/metadata/sheet.json", + "value": "low" + }, + "title": { + "candidates": [ + { + "precedence": "cobra_help", + "selected": true, + "source": "cobra_help", + "value": "回复单元格评论" + } + ], + "precedence": "cobra_help", + "resolution": "highest_precedence", + "source": "cobra_help", + "value": "回复单元格评论" + }, + "use_when": { + "candidates": [ + { + "precedence": "reviewed_explicit", + "review_reason": "人工审阅:对照悟空 develop 同名命令实现、Cobra Long/Example 与 Runtime 确认门禁移植;不改变命令身份、参数契约或接口绑定。", + "selected": true, + "source": "internal/cli/schema_hints/selection/sheet.json", + "value": [ + "用户说 回复这条评论" + ] + } + ], + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "人工审阅:对照悟空 develop 同名命令实现、Cobra Long/Example 与 Runtime 确认门禁移植;不改变命令身份、参数契约或接口绑定。", + "source": "internal/cli/schema_hints/selection/sheet.json", + "value": [ + "用户说 回复这条评论" + ] + } + }, + "group": "comment", + "has_parameters": true, + "idempotency": "non_idempotent", + "interface_mode": "composite", + "interface_reason": "Reviewed unpinned remote adapter: this executable CLI wrapper calls a remote helper that is absent from the pinned MCP metadata snapshot; no single pinned semantically equivalent interface_ref can represent the command.", + "is_alias": false, + "name": "reply_sheet_comment", + "parameter_count": 5, + "parameters": { + "comment-key": { + "description": "被回复评论的 commentKey (必填)", + "field_provenance": { + "description": { + "candidates": [ + { + "precedence": "cobra_contract", + "selected": true, + "source": "cobra_usage", + "value": "被回复评论的 commentKey (必填)" + }, + { + "precedence": "default", + "selected": false, + "source": "default", + "value": "" + } + ], + "precedence": "cobra_contract", + "resolution": "highest_precedence", + "source": "cobra_usage", + "value": "被回复评论的 commentKey (必填)" + }, + "property": { + "candidates": [ + { + "precedence": "reviewed_mapping_exclusion", + "review_reason": "Reviewed unpinned adapter: sheet.reply_sheet_comment has no singular pinned interface_ref; --comment-key is a CLI wrapper input and does not publish a direct interface property.", + "selected": true, + "source": "reviewed_mapping_exclusion", + "value": "" + }, + { + "precedence": "inference", + "selected": false, + "source": "flag_name_inference", + "value": "commentKey" + } + ], + "precedence": "reviewed_mapping_exclusion", + "resolution": "highest_precedence", + "review_reason": "Reviewed unpinned adapter: sheet.reply_sheet_comment has no singular pinned interface_ref; --comment-key is a CLI wrapper input and does not publish a direct interface property.", + "source": "reviewed_mapping_exclusion", + "value": "" + }, + "required": { + "candidates": [ + { + "precedence": "inference", + "selected": true, + "source": "usage_required_inference", + "value": true + }, + { + "precedence": "default", + "selected": false, + "source": "default", + "value": false + } + ], + "precedence": "inference", + "resolution": "highest_precedence", + "source": "usage_required_inference", + "value": true + }, + "required_when": { + "candidates": [ + { + "precedence": "default", + "selected": true, + "source": "default", + "value": "" + } + ], + "precedence": "default", + "resolution": "highest_precedence", + "source": "default", + "value": "" + }, + "type": { + "candidates": [ + { + "precedence": "cobra_contract", + "selected": true, + "source": "cobra_flag_type", + "value": "string" + } + ], + "precedence": "cobra_contract", + "resolution": "highest_precedence", + "source": "cobra_flag_type", + "value": "string" + } + }, + "required": true, + "type": "string" + }, + "content": { + "description": "回复内容 (必填)", + "field_provenance": { + "description": { + "candidates": [ + { + "precedence": "cobra_contract", + "selected": true, + "source": "cobra_usage", + "value": "回复内容 (必填)" + }, + { + "precedence": "default", + "selected": false, + "source": "default", + "value": "" + } + ], + "precedence": "cobra_contract", + "resolution": "highest_precedence", + "source": "cobra_usage", + "value": "回复内容 (必填)" + }, + "property": { + "candidates": [ + { + "precedence": "reviewed_mapping_exclusion", + "review_reason": "Reviewed unpinned adapter: sheet.reply_sheet_comment has no singular pinned interface_ref; --content is a CLI wrapper input and does not publish a direct interface property.", + "selected": true, + "source": "reviewed_mapping_exclusion", + "value": "" + }, + { + "precedence": "inference", + "selected": false, + "source": "flag_name_inference", + "value": "content" + } + ], + "precedence": "reviewed_mapping_exclusion", + "resolution": "highest_precedence", + "review_reason": "Reviewed unpinned adapter: sheet.reply_sheet_comment has no singular pinned interface_ref; --content is a CLI wrapper input and does not publish a direct interface property.", + "source": "reviewed_mapping_exclusion", + "value": "" + }, + "required": { + "candidates": [ + { + "precedence": "inference", + "selected": true, + "source": "usage_required_inference", + "value": true + }, + { + "precedence": "default", + "selected": false, + "source": "default", + "value": false + } + ], + "precedence": "inference", + "resolution": "highest_precedence", + "source": "usage_required_inference", + "value": true + }, + "required_when": { + "candidates": [ + { + "precedence": "default", + "selected": true, + "source": "default", + "value": "" + } + ], + "precedence": "default", + "resolution": "highest_precedence", + "source": "default", + "value": "" + }, + "type": { + "candidates": [ + { + "precedence": "cobra_contract", + "selected": true, + "source": "cobra_flag_type", + "value": "string" + } + ], + "precedence": "cobra_contract", + "resolution": "highest_precedence", + "source": "cobra_flag_type", + "value": "string" + } + }, + "required": true, + "type": "string" + }, + "emoji": { + "description": "作为表情贴图回复", + "field_provenance": { + "description": { + "candidates": [ + { + "precedence": "cobra_contract", + "selected": true, + "source": "cobra_usage", + "value": "作为表情贴图回复" + }, + { + "precedence": "default", + "selected": false, + "source": "default", + "value": "" + } + ], + "precedence": "cobra_contract", + "resolution": "highest_precedence", + "source": "cobra_usage", + "value": "作为表情贴图回复" + }, + "property": { + "candidates": [ + { + "precedence": "reviewed_mapping_exclusion", + "review_reason": "Reviewed unpinned adapter: sheet.reply_sheet_comment has no singular pinned interface_ref; --emoji is a CLI wrapper input and does not publish a direct interface property.", + "selected": true, + "source": "reviewed_mapping_exclusion", + "value": "" + }, + { + "precedence": "inference", + "selected": false, + "source": "flag_name_inference", + "value": "emoji" + } + ], + "precedence": "reviewed_mapping_exclusion", + "resolution": "highest_precedence", + "review_reason": "Reviewed unpinned adapter: sheet.reply_sheet_comment has no singular pinned interface_ref; --emoji is a CLI wrapper input and does not publish a direct interface property.", + "source": "reviewed_mapping_exclusion", + "value": "" + }, + "required": { + "candidates": [ + { + "precedence": "default", + "selected": true, + "source": "default", + "value": false + } + ], + "precedence": "default", + "resolution": "fallback", + "source": "default", + "value": false + }, + "required_when": { + "candidates": [ + { + "precedence": "default", + "selected": true, + "source": "default", + "value": "" + } + ], + "precedence": "default", + "resolution": "highest_precedence", + "source": "default", + "value": "" + }, + "type": { + "candidates": [ + { + "precedence": "cobra_contract", + "selected": true, + "source": "cobra_flag_type", + "value": "boolean" + } + ], + "precedence": "cobra_contract", + "resolution": "highest_precedence", + "source": "cobra_flag_type", + "value": "boolean" + } + }, + "required": false, + "type": "boolean" + }, + "mention": { + "description": "被 @ 的用户 uid 列表,逗号分隔", + "field_provenance": { + "description": { + "candidates": [ + { + "precedence": "cobra_contract", + "selected": true, + "source": "cobra_usage", + "value": "被 @ 的用户 uid 列表,逗号分隔" + }, + { + "precedence": "default", + "selected": false, + "source": "default", + "value": "" + } + ], + "precedence": "cobra_contract", + "resolution": "highest_precedence", + "source": "cobra_usage", + "value": "被 @ 的用户 uid 列表,逗号分隔" + }, + "property": { + "candidates": [ + { + "precedence": "reviewed_mapping_exclusion", + "review_reason": "Reviewed unpinned adapter: sheet.reply_sheet_comment has no singular pinned interface_ref; --mention is a CLI wrapper input and does not publish a direct interface property.", + "selected": true, + "source": "reviewed_mapping_exclusion", + "value": "" + }, + { + "precedence": "inference", + "selected": false, + "source": "flag_name_inference", + "value": "mention" + } + ], + "precedence": "reviewed_mapping_exclusion", + "resolution": "highest_precedence", + "review_reason": "Reviewed unpinned adapter: sheet.reply_sheet_comment has no singular pinned interface_ref; --mention is a CLI wrapper input and does not publish a direct interface property.", + "source": "reviewed_mapping_exclusion", + "value": "" + }, + "required": { + "candidates": [ + { + "precedence": "default", + "selected": true, + "source": "default", + "value": false + } + ], + "precedence": "default", + "resolution": "fallback", + "source": "default", + "value": false + }, + "required_when": { + "candidates": [ + { + "precedence": "default", + "selected": true, + "source": "default", + "value": "" + } + ], + "precedence": "default", + "resolution": "highest_precedence", + "source": "default", + "value": "" + }, + "type": { + "candidates": [ + { + "precedence": "cobra_contract", + "selected": true, + "source": "cobra_flag_type", + "value": "string" + } + ], + "precedence": "cobra_contract", + "resolution": "highest_precedence", + "source": "cobra_flag_type", + "value": "string" + } + }, + "required": false, + "type": "string" + }, + "node": { + "description": "表格文档 ID 或 URL (必填)", + "field_provenance": { + "description": { + "candidates": [ + { + "precedence": "cobra_contract", + "selected": true, + "source": "cobra_usage", + "value": "表格文档 ID 或 URL (必填)" + }, + { + "precedence": "default", + "selected": false, + "source": "default", + "value": "" + } + ], + "precedence": "cobra_contract", + "resolution": "highest_precedence", + "source": "cobra_usage", + "value": "表格文档 ID 或 URL (必填)" + }, + "property": { + "candidates": [ + { + "precedence": "reviewed_mapping_exclusion", + "review_reason": "Reviewed unpinned adapter: sheet.reply_sheet_comment has no singular pinned interface_ref; --node is a CLI wrapper input and does not publish a direct interface property.", + "selected": true, + "source": "reviewed_mapping_exclusion", + "value": "" + }, + { + "precedence": "inference", + "selected": false, + "source": "flag_name_inference", + "value": "node" + } + ], + "precedence": "reviewed_mapping_exclusion", + "resolution": "highest_precedence", + "review_reason": "Reviewed unpinned adapter: sheet.reply_sheet_comment has no singular pinned interface_ref; --node is a CLI wrapper input and does not publish a direct interface property.", + "source": "reviewed_mapping_exclusion", + "value": "" + }, + "required": { + "candidates": [ + { + "precedence": "inference", + "selected": true, + "source": "usage_required_inference", + "value": true + }, + { + "precedence": "default", + "selected": false, + "source": "default", + "value": false + } + ], + "precedence": "inference", + "resolution": "highest_precedence", + "source": "usage_required_inference", + "value": true + }, + "required_when": { + "candidates": [ + { + "precedence": "default", + "selected": true, + "source": "default", + "value": "" + } + ], + "precedence": "default", + "resolution": "highest_precedence", + "source": "default", + "value": "" + }, + "type": { + "candidates": [ + { + "precedence": "cobra_contract", + "selected": true, + "source": "cobra_flag_type", + "value": "string" + } + ], + "precedence": "cobra_contract", + "resolution": "highest_precedence", + "source": "cobra_flag_type", + "value": "string" + } + }, + "required": true, + "type": "string" + } + }, + "path": "sheet.reply_sheet_comment", + "primary_cli_path": "sheet comment reply", + "product_id": "sheet", + "reviewed": true, + "risk": "low", + "source": "reviewed_command_registry", + "title": "回复单元格评论", + "use_when": [ + "用户说 回复这条评论" + ] + }, "sheet.set_dropdown_lists": { "agent_metadata_source": "embedded-skill-metadata", "agent_source_refs": [ @@ -64731,6 +68859,2147 @@ "要重命名工作表、调整页签顺序、隐藏/显示、冻结行列或改标签颜色时" ] }, + "sheet.update_sheet_comment": { + "agent_metadata_source": "embedded-skill-metadata", + "agent_source_refs": [ + "CommandRegistry:canonical_path=sheet.update_sheet_comment", + "cobra-help:dws sheet comment update", + "internal/cli/schema_hints/metadata/sheet.json", + "internal/cli/schema_hints/selection/sheet.json", + "wukong-develop:wukong/products" + ], + "agent_summary": "更新单元格评论内容", + "agent_summary_source": "dws-agent-selection/sheet", + "availability": "available", + "avoid_when": [ + "回复评论用 comment reply" + ], + "canonical_path": "sheet.update_sheet_comment", + "cli_name": "update", + "cli_path": "sheet comment update", + "confirmation": "not_required", + "description": "更新单元格评论", + "display": "钉钉表格管理", + "effect": "write", + "effect_source": "agent-hint", + "examples": [ + "dws sheet comment update --node \u003cSHEET_ID\u003e --comment-key \u003cCOMMENT_KEY\u003e --content \"已修正\" --format json" + ], + "field_provenance": { + "agent_summary": { + "candidates": [ + { + "precedence": "reviewed_explicit", + "review_reason": "人工审阅:对照悟空 develop 同名命令实现、Cobra Long/Example 与 Runtime 确认门禁移植;不改变命令身份、参数契约或接口绑定。", + "selected": true, + "source": "internal/cli/schema_hints/selection/sheet.json", + "value": "更新单元格评论内容" + } + ], + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "人工审阅:对照悟空 develop 同名命令实现、Cobra Long/Example 与 Runtime 确认门禁移植;不改变命令身份、参数契约或接口绑定。", + "source": "internal/cli/schema_hints/selection/sheet.json", + "value": "更新单元格评论内容" + }, + "availability": { + "candidates": [ + { + "precedence": "reviewed_explicit", + "review_reason": "dws-tool-metadata/sheet marks this tool as reviewed", + "selected": true, + "source": "internal/cli/schema_hints/metadata/sheet.json", + "value": "available" + } + ], + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "dws-tool-metadata/sheet marks this tool as reviewed", + "source": "internal/cli/schema_hints/metadata/sheet.json", + "value": "available" + }, + "avoid_when": { + "candidates": [ + { + "precedence": "reviewed_explicit", + "review_reason": "人工审阅:对照悟空 develop 同名命令实现、Cobra Long/Example 与 Runtime 确认门禁移植;不改变命令身份、参数契约或接口绑定。", + "selected": true, + "source": "internal/cli/schema_hints/selection/sheet.json", + "value": [ + "回复评论用 comment reply" + ] + } + ], + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "人工审阅:对照悟空 develop 同名命令实现、Cobra Long/Example 与 Runtime 确认门禁移植;不改变命令身份、参数契约或接口绑定。", + "source": "internal/cli/schema_hints/selection/sheet.json", + "value": [ + "回复评论用 comment reply" + ] + }, + "canonical_path": { + "candidates": [ + { + "precedence": "command_registry", + "selected": true, + "source": "reviewed_command_registry", + "source_ref": "sheet comment update", + "value": "sheet.update_sheet_comment" + } + ], + "precedence": "command_registry", + "resolution": "registry_identity", + "source": "reviewed_command_registry", + "source_ref": "sheet comment update", + "value": "sheet.update_sheet_comment" + }, + "confirmation": { + "candidates": [ + { + "precedence": "reviewed_explicit", + "review_reason": "dws-tool-metadata/sheet marks this tool as reviewed", + "selected": true, + "source": "internal/cli/schema_hints/metadata/sheet.json", + "value": "not_required" + } + ], + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "dws-tool-metadata/sheet marks this tool as reviewed", + "source": "internal/cli/schema_hints/metadata/sheet.json", + "value": "not_required" + }, + "description": { + "candidates": [ + { + "precedence": "cobra_help", + "selected": true, + "source": "cobra_help", + "value": "更新单元格评论" + } + ], + "precedence": "cobra_help", + "resolution": "highest_precedence", + "source": "cobra_help", + "value": "更新单元格评论" + }, + "effect": { + "candidates": [ + { + "precedence": "reviewed_explicit", + "review_reason": "dws-tool-metadata/sheet marks this tool as reviewed", + "selected": true, + "source": "internal/cli/schema_hints/metadata/sheet.json", + "value": "write" + } + ], + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "dws-tool-metadata/sheet marks this tool as reviewed", + "source": "internal/cli/schema_hints/metadata/sheet.json", + "value": "write" + }, + "examples": { + "candidates": [ + { + "precedence": "reviewed_explicit", + "review_reason": "人工审阅:对照悟空 develop 同名命令实现、Cobra Long/Example 与 Runtime 确认门禁移植;不改变命令身份、参数契约或接口绑定。", + "selected": true, + "source": "internal/cli/schema_hints/selection/sheet.json", + "value": [ + "dws sheet comment update --node \u003cSHEET_ID\u003e --comment-key \u003cCOMMENT_KEY\u003e --content \"已修正\" --format json" + ] + } + ], + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "人工审阅:对照悟空 develop 同名命令实现、Cobra Long/Example 与 Runtime 确认门禁移植;不改变命令身份、参数契约或接口绑定。", + "source": "internal/cli/schema_hints/selection/sheet.json", + "value": [ + "dws sheet comment update --node \u003cSHEET_ID\u003e --comment-key \u003cCOMMENT_KEY\u003e --content \"已修正\" --format json" + ] + }, + "idempotency": { + "candidates": [ + { + "precedence": "reviewed_explicit", + "review_reason": "dws-tool-metadata/sheet marks this tool as reviewed", + "selected": true, + "source": "internal/cli/schema_hints/metadata/sheet.json", + "value": "idempotent" + } + ], + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "dws-tool-metadata/sheet marks this tool as reviewed", + "source": "internal/cli/schema_hints/metadata/sheet.json", + "value": "idempotent" + }, + "interface_mode": { + "candidates": [ + { + "precedence": "reviewed_explicit", + "review_reason": "dws-tool-metadata/sheet marks this tool as reviewed", + "selected": true, + "source": "internal/cli/schema_hints/metadata/sheet.json", + "value": "composite" + } + ], + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "dws-tool-metadata/sheet marks this tool as reviewed", + "source": "internal/cli/schema_hints/metadata/sheet.json", + "value": "composite" + }, + "interface_reason": { + "candidates": [ + { + "precedence": "reviewed_explicit", + "review_reason": "dws-tool-metadata/sheet marks this tool as reviewed", + "selected": true, + "source": "internal/cli/schema_hints/metadata/sheet.json", + "value": "Reviewed unpinned remote adapter: this executable CLI wrapper calls a remote helper that is absent from the pinned MCP metadata snapshot; no single pinned semantically equivalent interface_ref can represent the command." + } + ], + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "dws-tool-metadata/sheet marks this tool as reviewed", + "source": "internal/cli/schema_hints/metadata/sheet.json", + "value": "Reviewed unpinned remote adapter: this executable CLI wrapper calls a remote helper that is absent from the pinned MCP metadata snapshot; no single pinned semantically equivalent interface_ref can represent the command." + }, + "interface_ref": { + "candidates": [ + { + "precedence": "reviewed_explicit", + "review_reason": "final interface mode composite forbids a direct MCP interface_ref", + "selected": true, + "source": "internal/cli/schema_hints/metadata/sheet.json", + "value": null + } + ], + "precedence": "reviewed_explicit", + "resolution": "interface_disposition_matrix", + "review_reason": "final interface mode composite forbids a direct MCP interface_ref", + "source": "internal/cli/schema_hints/metadata/sheet.json", + "value": null + }, + "reviewed": { + "candidates": [ + { + "precedence": "reviewed_explicit", + "review_reason": "dws-tool-metadata/sheet marks this tool as reviewed", + "selected": true, + "source": "internal/cli/schema_hints/metadata/sheet.json", + "value": true + }, + { + "precedence": "reviewed_explicit", + "review_reason": "人工审阅:对照悟空 develop 同名命令实现、Cobra Long/Example 与 Runtime 确认门禁移植;不改变命令身份、参数契约或接口绑定。", + "selected": false, + "source": "internal/cli/schema_hints/selection/sheet.json", + "value": true + } + ], + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "dws-tool-metadata/sheet marks this tool as reviewed", + "source": "internal/cli/schema_hints/metadata/sheet.json", + "value": true + }, + "risk": { + "candidates": [ + { + "precedence": "reviewed_explicit", + "review_reason": "dws-tool-metadata/sheet marks this tool as reviewed", + "selected": true, + "source": "internal/cli/schema_hints/metadata/sheet.json", + "value": "low" + } + ], + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "dws-tool-metadata/sheet marks this tool as reviewed", + "source": "internal/cli/schema_hints/metadata/sheet.json", + "value": "low" + }, + "title": { + "candidates": [ + { + "precedence": "cobra_help", + "selected": true, + "source": "cobra_help", + "value": "更新单元格评论" + } + ], + "precedence": "cobra_help", + "resolution": "highest_precedence", + "source": "cobra_help", + "value": "更新单元格评论" + }, + "use_when": { + "candidates": [ + { + "precedence": "reviewed_explicit", + "review_reason": "人工审阅:对照悟空 develop 同名命令实现、Cobra Long/Example 与 Runtime 确认门禁移植;不改变命令身份、参数契约或接口绑定。", + "selected": true, + "source": "internal/cli/schema_hints/selection/sheet.json", + "value": [ + "用户说 改一下这条评论" + ] + } + ], + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "人工审阅:对照悟空 develop 同名命令实现、Cobra Long/Example 与 Runtime 确认门禁移植;不改变命令身份、参数契约或接口绑定。", + "source": "internal/cli/schema_hints/selection/sheet.json", + "value": [ + "用户说 改一下这条评论" + ] + } + }, + "group": "comment", + "has_parameters": true, + "idempotency": "idempotent", + "interface_mode": "composite", + "interface_reason": "Reviewed unpinned remote adapter: this executable CLI wrapper calls a remote helper that is absent from the pinned MCP metadata snapshot; no single pinned semantically equivalent interface_ref can represent the command.", + "is_alias": false, + "name": "update_sheet_comment", + "parameter_count": 3, + "parameters": { + "comment-key": { + "description": "待更新评论的 commentKey (必填)", + "field_provenance": { + "description": { + "candidates": [ + { + "precedence": "cobra_contract", + "selected": true, + "source": "cobra_usage", + "value": "待更新评论的 commentKey (必填)" + }, + { + "precedence": "default", + "selected": false, + "source": "default", + "value": "" + } + ], + "precedence": "cobra_contract", + "resolution": "highest_precedence", + "source": "cobra_usage", + "value": "待更新评论的 commentKey (必填)" + }, + "property": { + "candidates": [ + { + "precedence": "reviewed_mapping_exclusion", + "review_reason": "Reviewed unpinned adapter: sheet.update_sheet_comment has no singular pinned interface_ref; --comment-key is a CLI wrapper input and does not publish a direct interface property.", + "selected": true, + "source": "reviewed_mapping_exclusion", + "value": "" + }, + { + "precedence": "inference", + "selected": false, + "source": "flag_name_inference", + "value": "commentKey" + } + ], + "precedence": "reviewed_mapping_exclusion", + "resolution": "highest_precedence", + "review_reason": "Reviewed unpinned adapter: sheet.update_sheet_comment has no singular pinned interface_ref; --comment-key is a CLI wrapper input and does not publish a direct interface property.", + "source": "reviewed_mapping_exclusion", + "value": "" + }, + "required": { + "candidates": [ + { + "precedence": "inference", + "selected": true, + "source": "usage_required_inference", + "value": true + }, + { + "precedence": "default", + "selected": false, + "source": "default", + "value": false + } + ], + "precedence": "inference", + "resolution": "highest_precedence", + "source": "usage_required_inference", + "value": true + }, + "required_when": { + "candidates": [ + { + "precedence": "default", + "selected": true, + "source": "default", + "value": "" + } + ], + "precedence": "default", + "resolution": "highest_precedence", + "source": "default", + "value": "" + }, + "type": { + "candidates": [ + { + "precedence": "cobra_contract", + "selected": true, + "source": "cobra_flag_type", + "value": "string" + } + ], + "precedence": "cobra_contract", + "resolution": "highest_precedence", + "source": "cobra_flag_type", + "value": "string" + } + }, + "required": true, + "type": "string" + }, + "content": { + "description": "更新后的评论内容 (必填)", + "field_provenance": { + "description": { + "candidates": [ + { + "precedence": "cobra_contract", + "selected": true, + "source": "cobra_usage", + "value": "更新后的评论内容 (必填)" + }, + { + "precedence": "default", + "selected": false, + "source": "default", + "value": "" + } + ], + "precedence": "cobra_contract", + "resolution": "highest_precedence", + "source": "cobra_usage", + "value": "更新后的评论内容 (必填)" + }, + "property": { + "candidates": [ + { + "precedence": "reviewed_mapping_exclusion", + "review_reason": "Reviewed unpinned adapter: sheet.update_sheet_comment has no singular pinned interface_ref; --content is a CLI wrapper input and does not publish a direct interface property.", + "selected": true, + "source": "reviewed_mapping_exclusion", + "value": "" + }, + { + "precedence": "inference", + "selected": false, + "source": "flag_name_inference", + "value": "content" + } + ], + "precedence": "reviewed_mapping_exclusion", + "resolution": "highest_precedence", + "review_reason": "Reviewed unpinned adapter: sheet.update_sheet_comment has no singular pinned interface_ref; --content is a CLI wrapper input and does not publish a direct interface property.", + "source": "reviewed_mapping_exclusion", + "value": "" + }, + "required": { + "candidates": [ + { + "precedence": "inference", + "selected": true, + "source": "usage_required_inference", + "value": true + }, + { + "precedence": "default", + "selected": false, + "source": "default", + "value": false + } + ], + "precedence": "inference", + "resolution": "highest_precedence", + "source": "usage_required_inference", + "value": true + }, + "required_when": { + "candidates": [ + { + "precedence": "default", + "selected": true, + "source": "default", + "value": "" + } + ], + "precedence": "default", + "resolution": "highest_precedence", + "source": "default", + "value": "" + }, + "type": { + "candidates": [ + { + "precedence": "cobra_contract", + "selected": true, + "source": "cobra_flag_type", + "value": "string" + } + ], + "precedence": "cobra_contract", + "resolution": "highest_precedence", + "source": "cobra_flag_type", + "value": "string" + } + }, + "required": true, + "type": "string" + }, + "node": { + "description": "表格文档 ID 或 URL (必填)", + "field_provenance": { + "description": { + "candidates": [ + { + "precedence": "cobra_contract", + "selected": true, + "source": "cobra_usage", + "value": "表格文档 ID 或 URL (必填)" + }, + { + "precedence": "default", + "selected": false, + "source": "default", + "value": "" + } + ], + "precedence": "cobra_contract", + "resolution": "highest_precedence", + "source": "cobra_usage", + "value": "表格文档 ID 或 URL (必填)" + }, + "property": { + "candidates": [ + { + "precedence": "reviewed_mapping_exclusion", + "review_reason": "Reviewed unpinned adapter: sheet.update_sheet_comment has no singular pinned interface_ref; --node is a CLI wrapper input and does not publish a direct interface property.", + "selected": true, + "source": "reviewed_mapping_exclusion", + "value": "" + }, + { + "precedence": "inference", + "selected": false, + "source": "flag_name_inference", + "value": "node" + } + ], + "precedence": "reviewed_mapping_exclusion", + "resolution": "highest_precedence", + "review_reason": "Reviewed unpinned adapter: sheet.update_sheet_comment has no singular pinned interface_ref; --node is a CLI wrapper input and does not publish a direct interface property.", + "source": "reviewed_mapping_exclusion", + "value": "" + }, + "required": { + "candidates": [ + { + "precedence": "inference", + "selected": true, + "source": "usage_required_inference", + "value": true + }, + { + "precedence": "default", + "selected": false, + "source": "default", + "value": false + } + ], + "precedence": "inference", + "resolution": "highest_precedence", + "source": "usage_required_inference", + "value": true + }, + "required_when": { + "candidates": [ + { + "precedence": "default", + "selected": true, + "source": "default", + "value": "" + } + ], + "precedence": "default", + "resolution": "highest_precedence", + "source": "default", + "value": "" + }, + "type": { + "candidates": [ + { + "precedence": "cobra_contract", + "selected": true, + "source": "cobra_flag_type", + "value": "string" + } + ], + "precedence": "cobra_contract", + "resolution": "highest_precedence", + "source": "cobra_flag_type", + "value": "string" + } + }, + "required": true, + "type": "string" + } + }, + "path": "sheet.update_sheet_comment", + "primary_cli_path": "sheet comment update", + "product_id": "sheet", + "reviewed": true, + "risk": "low", + "source": "reviewed_command_registry", + "title": "更新单元格评论", + "use_when": [ + "用户说 改一下这条评论" + ] + }, + "sheet.version_list": { + "agent_metadata_source": "embedded-skill-metadata", + "agent_source_refs": [ + "CommandRegistry:canonical_path=sheet.version_list", + "cobra-help:dws sheet version list", + "internal/cli/schema_hints/metadata/sheet.json", + "internal/cli/schema_hints/selection/sheet.json", + "wukong-develop:wukong/products" + ], + "agent_summary": "查看表格历史版本列表", + "agent_summary_source": "dws-agent-selection/sheet", + "availability": "available", + "avoid_when": [ + "回滚用 version revert" + ], + "canonical_path": "sheet.version_list", + "cli_name": "list", + "cli_path": "sheet version list", + "confirmation": "not_required", + "description": "查看表格历史版本列表", + "display": "钉钉表格管理", + "effect": "read", + "effect_source": "agent-hint", + "examples": [ + "dws sheet version list --node \u003cSHEET_ID\u003e --limit 10 --format json" + ], + "field_provenance": { + "agent_summary": { + "candidates": [ + { + "precedence": "reviewed_explicit", + "review_reason": "人工审阅:对照悟空 develop 同名命令实现、Cobra Long/Example 与 Runtime 确认门禁移植;不改变命令身份、参数契约或接口绑定。", + "selected": true, + "source": "internal/cli/schema_hints/selection/sheet.json", + "value": "查看表格历史版本列表" + } + ], + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "人工审阅:对照悟空 develop 同名命令实现、Cobra Long/Example 与 Runtime 确认门禁移植;不改变命令身份、参数契约或接口绑定。", + "source": "internal/cli/schema_hints/selection/sheet.json", + "value": "查看表格历史版本列表" + }, + "availability": { + "candidates": [ + { + "precedence": "reviewed_explicit", + "review_reason": "dws-tool-metadata/sheet marks this tool as reviewed", + "selected": true, + "source": "internal/cli/schema_hints/metadata/sheet.json", + "value": "available" + } + ], + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "dws-tool-metadata/sheet marks this tool as reviewed", + "source": "internal/cli/schema_hints/metadata/sheet.json", + "value": "available" + }, + "avoid_when": { + "candidates": [ + { + "precedence": "reviewed_explicit", + "review_reason": "人工审阅:对照悟空 develop 同名命令实现、Cobra Long/Example 与 Runtime 确认门禁移植;不改变命令身份、参数契约或接口绑定。", + "selected": true, + "source": "internal/cli/schema_hints/selection/sheet.json", + "value": [ + "回滚用 version revert" + ] + } + ], + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "人工审阅:对照悟空 develop 同名命令实现、Cobra Long/Example 与 Runtime 确认门禁移植;不改变命令身份、参数契约或接口绑定。", + "source": "internal/cli/schema_hints/selection/sheet.json", + "value": [ + "回滚用 version revert" + ] + }, + "canonical_path": { + "candidates": [ + { + "precedence": "command_registry", + "selected": true, + "source": "reviewed_command_registry", + "source_ref": "sheet version list", + "value": "sheet.version_list" + } + ], + "precedence": "command_registry", + "resolution": "registry_identity", + "source": "reviewed_command_registry", + "source_ref": "sheet version list", + "value": "sheet.version_list" + }, + "confirmation": { + "candidates": [ + { + "precedence": "reviewed_explicit", + "review_reason": "dws-tool-metadata/sheet marks this tool as reviewed", + "selected": true, + "source": "internal/cli/schema_hints/metadata/sheet.json", + "value": "not_required" + } + ], + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "dws-tool-metadata/sheet marks this tool as reviewed", + "source": "internal/cli/schema_hints/metadata/sheet.json", + "value": "not_required" + }, + "description": { + "candidates": [ + { + "precedence": "cobra_help", + "selected": true, + "source": "cobra_help", + "value": "查看表格历史版本列表" + } + ], + "precedence": "cobra_help", + "resolution": "highest_precedence", + "source": "cobra_help", + "value": "查看表格历史版本列表" + }, + "effect": { + "candidates": [ + { + "precedence": "reviewed_explicit", + "review_reason": "dws-tool-metadata/sheet marks this tool as reviewed", + "selected": true, + "source": "internal/cli/schema_hints/metadata/sheet.json", + "value": "read" + } + ], + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "dws-tool-metadata/sheet marks this tool as reviewed", + "source": "internal/cli/schema_hints/metadata/sheet.json", + "value": "read" + }, + "examples": { + "candidates": [ + { + "precedence": "reviewed_explicit", + "review_reason": "人工审阅:对照悟空 develop 同名命令实现、Cobra Long/Example 与 Runtime 确认门禁移植;不改变命令身份、参数契约或接口绑定。", + "selected": true, + "source": "internal/cli/schema_hints/selection/sheet.json", + "value": [ + "dws sheet version list --node \u003cSHEET_ID\u003e --limit 10 --format json" + ] + } + ], + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "人工审阅:对照悟空 develop 同名命令实现、Cobra Long/Example 与 Runtime 确认门禁移植;不改变命令身份、参数契约或接口绑定。", + "source": "internal/cli/schema_hints/selection/sheet.json", + "value": [ + "dws sheet version list --node \u003cSHEET_ID\u003e --limit 10 --format json" + ] + }, + "idempotency": { + "candidates": [ + { + "precedence": "reviewed_explicit", + "review_reason": "dws-tool-metadata/sheet marks this tool as reviewed", + "selected": true, + "source": "internal/cli/schema_hints/metadata/sheet.json", + "value": "idempotent" + } + ], + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "dws-tool-metadata/sheet marks this tool as reviewed", + "source": "internal/cli/schema_hints/metadata/sheet.json", + "value": "idempotent" + }, + "interface_mode": { + "candidates": [ + { + "precedence": "reviewed_explicit", + "review_reason": "dws-tool-metadata/sheet marks this tool as reviewed", + "selected": true, + "source": "internal/cli/schema_hints/metadata/sheet.json", + "value": "composite" + } + ], + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "dws-tool-metadata/sheet marks this tool as reviewed", + "source": "internal/cli/schema_hints/metadata/sheet.json", + "value": "composite" + }, + "interface_reason": { + "candidates": [ + { + "precedence": "reviewed_explicit", + "review_reason": "dws-tool-metadata/sheet marks this tool as reviewed", + "selected": true, + "source": "internal/cli/schema_hints/metadata/sheet.json", + "value": "Reviewed unpinned remote adapter: this executable CLI wrapper calls a remote helper that is absent from the pinned MCP metadata snapshot; no single pinned semantically equivalent interface_ref can represent the command." + } + ], + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "dws-tool-metadata/sheet marks this tool as reviewed", + "source": "internal/cli/schema_hints/metadata/sheet.json", + "value": "Reviewed unpinned remote adapter: this executable CLI wrapper calls a remote helper that is absent from the pinned MCP metadata snapshot; no single pinned semantically equivalent interface_ref can represent the command." + }, + "interface_ref": { + "candidates": [ + { + "precedence": "reviewed_explicit", + "review_reason": "final interface mode composite forbids a direct MCP interface_ref", + "selected": true, + "source": "internal/cli/schema_hints/metadata/sheet.json", + "value": null + } + ], + "precedence": "reviewed_explicit", + "resolution": "interface_disposition_matrix", + "review_reason": "final interface mode composite forbids a direct MCP interface_ref", + "source": "internal/cli/schema_hints/metadata/sheet.json", + "value": null + }, + "reviewed": { + "candidates": [ + { + "precedence": "reviewed_explicit", + "review_reason": "dws-tool-metadata/sheet marks this tool as reviewed", + "selected": true, + "source": "internal/cli/schema_hints/metadata/sheet.json", + "value": true + }, + { + "precedence": "reviewed_explicit", + "review_reason": "人工审阅:对照悟空 develop 同名命令实现、Cobra Long/Example 与 Runtime 确认门禁移植;不改变命令身份、参数契约或接口绑定。", + "selected": false, + "source": "internal/cli/schema_hints/selection/sheet.json", + "value": true + } + ], + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "dws-tool-metadata/sheet marks this tool as reviewed", + "source": "internal/cli/schema_hints/metadata/sheet.json", + "value": true + }, + "risk": { + "candidates": [ + { + "precedence": "reviewed_explicit", + "review_reason": "dws-tool-metadata/sheet marks this tool as reviewed", + "selected": true, + "source": "internal/cli/schema_hints/metadata/sheet.json", + "value": "low" + } + ], + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "dws-tool-metadata/sheet marks this tool as reviewed", + "source": "internal/cli/schema_hints/metadata/sheet.json", + "value": "low" + }, + "title": { + "candidates": [ + { + "precedence": "cobra_help", + "selected": true, + "source": "cobra_help", + "value": "查看表格历史版本列表" + } + ], + "precedence": "cobra_help", + "resolution": "highest_precedence", + "source": "cobra_help", + "value": "查看表格历史版本列表" + }, + "use_when": { + "candidates": [ + { + "precedence": "reviewed_explicit", + "review_reason": "人工审阅:对照悟空 develop 同名命令实现、Cobra Long/Example 与 Runtime 确认门禁移植;不改变命令身份、参数契约或接口绑定。", + "selected": true, + "source": "internal/cli/schema_hints/selection/sheet.json", + "value": [ + "用户说 看历史版本/版本列表,目标是在线表格" + ] + } + ], + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "人工审阅:对照悟空 develop 同名命令实现、Cobra Long/Example 与 Runtime 确认门禁移植;不改变命令身份、参数契约或接口绑定。", + "source": "internal/cli/schema_hints/selection/sheet.json", + "value": [ + "用户说 看历史版本/版本列表,目标是在线表格" + ] + } + }, + "group": "version", + "has_parameters": true, + "idempotency": "idempotent", + "interface_mode": "composite", + "interface_reason": "Reviewed unpinned remote adapter: this executable CLI wrapper calls a remote helper that is absent from the pinned MCP metadata snapshot; no single pinned semantically equivalent interface_ref can represent the command.", + "is_alias": false, + "name": "version_list", + "parameter_count": 3, + "parameters": { + "cursor": { + "description": "分页游标", + "field_provenance": { + "description": { + "candidates": [ + { + "precedence": "cobra_contract", + "selected": true, + "source": "cobra_usage", + "value": "分页游标" + }, + { + "precedence": "default", + "selected": false, + "source": "default", + "value": "" + } + ], + "precedence": "cobra_contract", + "resolution": "highest_precedence", + "source": "cobra_usage", + "value": "分页游标" + }, + "property": { + "candidates": [ + { + "precedence": "reviewed_mapping_exclusion", + "review_reason": "Reviewed unpinned adapter: sheet.version_list has no singular pinned interface_ref; --cursor is a CLI wrapper input and does not publish a direct interface property.", + "selected": true, + "source": "reviewed_mapping_exclusion", + "value": "" + }, + { + "precedence": "inference", + "selected": false, + "source": "flag_name_inference", + "value": "cursor" + } + ], + "precedence": "reviewed_mapping_exclusion", + "resolution": "highest_precedence", + "review_reason": "Reviewed unpinned adapter: sheet.version_list has no singular pinned interface_ref; --cursor is a CLI wrapper input and does not publish a direct interface property.", + "source": "reviewed_mapping_exclusion", + "value": "" + }, + "required": { + "candidates": [ + { + "precedence": "default", + "selected": true, + "source": "default", + "value": false + } + ], + "precedence": "default", + "resolution": "fallback", + "source": "default", + "value": false + }, + "required_when": { + "candidates": [ + { + "precedence": "default", + "selected": true, + "source": "default", + "value": "" + } + ], + "precedence": "default", + "resolution": "highest_precedence", + "source": "default", + "value": "" + }, + "type": { + "candidates": [ + { + "precedence": "cobra_contract", + "selected": true, + "source": "cobra_flag_type", + "value": "string" + } + ], + "precedence": "cobra_contract", + "resolution": "highest_precedence", + "source": "cobra_flag_type", + "value": "string" + } + }, + "required": false, + "type": "string" + }, + "limit": { + "description": "返回版本数量上限", + "field_provenance": { + "description": { + "candidates": [ + { + "precedence": "cobra_contract", + "selected": true, + "source": "cobra_usage", + "value": "返回版本数量上限" + }, + { + "precedence": "default", + "selected": false, + "source": "default", + "value": "" + } + ], + "precedence": "cobra_contract", + "resolution": "highest_precedence", + "source": "cobra_usage", + "value": "返回版本数量上限" + }, + "property": { + "candidates": [ + { + "precedence": "reviewed_mapping_exclusion", + "review_reason": "Reviewed unpinned adapter: sheet.version_list has no singular pinned interface_ref; --limit is a CLI wrapper input and does not publish a direct interface property.", + "selected": true, + "source": "reviewed_mapping_exclusion", + "value": "" + }, + { + "precedence": "inference", + "selected": false, + "source": "flag_name_inference", + "value": "limit" + } + ], + "precedence": "reviewed_mapping_exclusion", + "resolution": "highest_precedence", + "review_reason": "Reviewed unpinned adapter: sheet.version_list has no singular pinned interface_ref; --limit is a CLI wrapper input and does not publish a direct interface property.", + "source": "reviewed_mapping_exclusion", + "value": "" + }, + "required": { + "candidates": [ + { + "precedence": "default", + "selected": true, + "source": "default", + "value": false + } + ], + "precedence": "default", + "resolution": "fallback", + "source": "default", + "value": false + }, + "required_when": { + "candidates": [ + { + "precedence": "default", + "selected": true, + "source": "default", + "value": "" + } + ], + "precedence": "default", + "resolution": "highest_precedence", + "source": "default", + "value": "" + }, + "type": { + "candidates": [ + { + "precedence": "cobra_contract", + "selected": true, + "source": "cobra_flag_type", + "value": "integer" + } + ], + "precedence": "cobra_contract", + "resolution": "highest_precedence", + "source": "cobra_flag_type", + "value": "integer" + } + }, + "required": false, + "type": "integer" + }, + "node": { + "description": "表格文档 ID 或 URL (必填)", + "field_provenance": { + "description": { + "candidates": [ + { + "precedence": "cobra_contract", + "selected": true, + "source": "cobra_usage", + "value": "表格文档 ID 或 URL (必填)" + }, + { + "precedence": "default", + "selected": false, + "source": "default", + "value": "" + } + ], + "precedence": "cobra_contract", + "resolution": "highest_precedence", + "source": "cobra_usage", + "value": "表格文档 ID 或 URL (必填)" + }, + "property": { + "candidates": [ + { + "precedence": "reviewed_mapping_exclusion", + "review_reason": "Reviewed unpinned adapter: sheet.version_list has no singular pinned interface_ref; --node is a CLI wrapper input and does not publish a direct interface property.", + "selected": true, + "source": "reviewed_mapping_exclusion", + "value": "" + }, + { + "precedence": "inference", + "selected": false, + "source": "flag_name_inference", + "value": "node" + } + ], + "precedence": "reviewed_mapping_exclusion", + "resolution": "highest_precedence", + "review_reason": "Reviewed unpinned adapter: sheet.version_list has no singular pinned interface_ref; --node is a CLI wrapper input and does not publish a direct interface property.", + "source": "reviewed_mapping_exclusion", + "value": "" + }, + "required": { + "candidates": [ + { + "precedence": "inference", + "selected": true, + "source": "usage_required_inference", + "value": true + }, + { + "precedence": "default", + "selected": false, + "source": "default", + "value": false + } + ], + "precedence": "inference", + "resolution": "highest_precedence", + "source": "usage_required_inference", + "value": true + }, + "required_when": { + "candidates": [ + { + "precedence": "default", + "selected": true, + "source": "default", + "value": "" + } + ], + "precedence": "default", + "resolution": "highest_precedence", + "source": "default", + "value": "" + }, + "type": { + "candidates": [ + { + "precedence": "cobra_contract", + "selected": true, + "source": "cobra_flag_type", + "value": "string" + } + ], + "precedence": "cobra_contract", + "resolution": "highest_precedence", + "source": "cobra_flag_type", + "value": "string" + } + }, + "required": true, + "type": "string" + } + }, + "path": "sheet.version_list", + "primary_cli_path": "sheet version list", + "product_id": "sheet", + "reviewed": true, + "risk": "low", + "source": "reviewed_command_registry", + "title": "查看表格历史版本列表", + "use_when": [ + "用户说 看历史版本/版本列表,目标是在线表格" + ] + }, + "sheet.version_revert": { + "agent_metadata_source": "embedded-skill-metadata", + "agent_source_refs": [ + "CommandRegistry:canonical_path=sheet.version_revert", + "cobra-help:dws sheet version revert", + "internal/cli/schema_hints/metadata/sheet.json", + "internal/cli/schema_hints/selection/sheet.json", + "wukong-develop:wukong/products" + ], + "agent_summary": "回滚表格到指定历史版本", + "agent_summary_source": "dws-agent-selection/sheet", + "availability": "available", + "avoid_when": [ + "普通文件回滚用 drive revert;在线文档用 doc version revert" + ], + "canonical_path": "sheet.version_revert", + "cli_name": "revert", + "cli_path": "sheet version revert", + "confirmation": "user_required", + "description": "[危险] 回滚表格到指定版本", + "display": "钉钉表格管理", + "effect": "write", + "effect_source": "agent-hint", + "examples": [ + "dws sheet version revert --node \u003cSHEET_ID\u003e --version 3 --format json" + ], + "field_provenance": { + "agent_summary": { + "candidates": [ + { + "precedence": "reviewed_explicit", + "review_reason": "人工审阅:对照悟空 develop 同名命令实现、Cobra Long/Example 与 Runtime 确认门禁移植;不改变命令身份、参数契约或接口绑定。", + "selected": true, + "source": "internal/cli/schema_hints/selection/sheet.json", + "value": "回滚表格到指定历史版本" + } + ], + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "人工审阅:对照悟空 develop 同名命令实现、Cobra Long/Example 与 Runtime 确认门禁移植;不改变命令身份、参数契约或接口绑定。", + "source": "internal/cli/schema_hints/selection/sheet.json", + "value": "回滚表格到指定历史版本" + }, + "availability": { + "candidates": [ + { + "precedence": "reviewed_explicit", + "review_reason": "dws-tool-metadata/sheet marks this tool as reviewed", + "selected": true, + "source": "internal/cli/schema_hints/metadata/sheet.json", + "value": "available" + } + ], + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "dws-tool-metadata/sheet marks this tool as reviewed", + "source": "internal/cli/schema_hints/metadata/sheet.json", + "value": "available" + }, + "avoid_when": { + "candidates": [ + { + "precedence": "reviewed_explicit", + "review_reason": "人工审阅:对照悟空 develop 同名命令实现、Cobra Long/Example 与 Runtime 确认门禁移植;不改变命令身份、参数契约或接口绑定。", + "selected": true, + "source": "internal/cli/schema_hints/selection/sheet.json", + "value": [ + "普通文件回滚用 drive revert;在线文档用 doc version revert" + ] + } + ], + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "人工审阅:对照悟空 develop 同名命令实现、Cobra Long/Example 与 Runtime 确认门禁移植;不改变命令身份、参数契约或接口绑定。", + "source": "internal/cli/schema_hints/selection/sheet.json", + "value": [ + "普通文件回滚用 drive revert;在线文档用 doc version revert" + ] + }, + "canonical_path": { + "candidates": [ + { + "precedence": "command_registry", + "selected": true, + "source": "reviewed_command_registry", + "source_ref": "sheet version revert", + "value": "sheet.version_revert" + } + ], + "precedence": "command_registry", + "resolution": "registry_identity", + "source": "reviewed_command_registry", + "source_ref": "sheet version revert", + "value": "sheet.version_revert" + }, + "confirmation": { + "candidates": [ + { + "precedence": "reviewed_explicit", + "review_reason": "dws-tool-metadata/sheet marks this tool as reviewed", + "selected": true, + "source": "internal/cli/schema_hints/metadata/sheet.json", + "value": "user_required" + } + ], + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "dws-tool-metadata/sheet marks this tool as reviewed", + "source": "internal/cli/schema_hints/metadata/sheet.json", + "value": "user_required" + }, + "description": { + "candidates": [ + { + "precedence": "cobra_help", + "selected": true, + "source": "cobra_help", + "value": "[危险] 回滚表格到指定版本" + } + ], + "precedence": "cobra_help", + "resolution": "highest_precedence", + "source": "cobra_help", + "value": "[危险] 回滚表格到指定版本" + }, + "effect": { + "candidates": [ + { + "precedence": "reviewed_explicit", + "review_reason": "dws-tool-metadata/sheet marks this tool as reviewed", + "selected": true, + "source": "internal/cli/schema_hints/metadata/sheet.json", + "value": "write" + } + ], + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "dws-tool-metadata/sheet marks this tool as reviewed", + "source": "internal/cli/schema_hints/metadata/sheet.json", + "value": "write" + }, + "examples": { + "candidates": [ + { + "precedence": "reviewed_explicit", + "review_reason": "人工审阅:对照悟空 develop 同名命令实现、Cobra Long/Example 与 Runtime 确认门禁移植;不改变命令身份、参数契约或接口绑定。", + "selected": true, + "source": "internal/cli/schema_hints/selection/sheet.json", + "value": [ + "dws sheet version revert --node \u003cSHEET_ID\u003e --version 3 --format json" + ] + } + ], + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "人工审阅:对照悟空 develop 同名命令实现、Cobra Long/Example 与 Runtime 确认门禁移植;不改变命令身份、参数契约或接口绑定。", + "source": "internal/cli/schema_hints/selection/sheet.json", + "value": [ + "dws sheet version revert --node \u003cSHEET_ID\u003e --version 3 --format json" + ] + }, + "idempotency": { + "candidates": [ + { + "precedence": "reviewed_explicit", + "review_reason": "dws-tool-metadata/sheet marks this tool as reviewed", + "selected": true, + "source": "internal/cli/schema_hints/metadata/sheet.json", + "value": "non_idempotent" + } + ], + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "dws-tool-metadata/sheet marks this tool as reviewed", + "source": "internal/cli/schema_hints/metadata/sheet.json", + "value": "non_idempotent" + }, + "interface_mode": { + "candidates": [ + { + "precedence": "reviewed_explicit", + "review_reason": "dws-tool-metadata/sheet marks this tool as reviewed", + "selected": true, + "source": "internal/cli/schema_hints/metadata/sheet.json", + "value": "composite" + } + ], + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "dws-tool-metadata/sheet marks this tool as reviewed", + "source": "internal/cli/schema_hints/metadata/sheet.json", + "value": "composite" + }, + "interface_reason": { + "candidates": [ + { + "precedence": "reviewed_explicit", + "review_reason": "dws-tool-metadata/sheet marks this tool as reviewed", + "selected": true, + "source": "internal/cli/schema_hints/metadata/sheet.json", + "value": "Reviewed unpinned remote adapter: this executable CLI wrapper calls a remote helper that is absent from the pinned MCP metadata snapshot; no single pinned semantically equivalent interface_ref can represent the command." + } + ], + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "dws-tool-metadata/sheet marks this tool as reviewed", + "source": "internal/cli/schema_hints/metadata/sheet.json", + "value": "Reviewed unpinned remote adapter: this executable CLI wrapper calls a remote helper that is absent from the pinned MCP metadata snapshot; no single pinned semantically equivalent interface_ref can represent the command." + }, + "interface_ref": { + "candidates": [ + { + "precedence": "reviewed_explicit", + "review_reason": "final interface mode composite forbids a direct MCP interface_ref", + "selected": true, + "source": "internal/cli/schema_hints/metadata/sheet.json", + "value": null + } + ], + "precedence": "reviewed_explicit", + "resolution": "interface_disposition_matrix", + "review_reason": "final interface mode composite forbids a direct MCP interface_ref", + "source": "internal/cli/schema_hints/metadata/sheet.json", + "value": null + }, + "reviewed": { + "candidates": [ + { + "precedence": "reviewed_explicit", + "review_reason": "dws-tool-metadata/sheet marks this tool as reviewed", + "selected": true, + "source": "internal/cli/schema_hints/metadata/sheet.json", + "value": true + }, + { + "precedence": "reviewed_explicit", + "review_reason": "人工审阅:对照悟空 develop 同名命令实现、Cobra Long/Example 与 Runtime 确认门禁移植;不改变命令身份、参数契约或接口绑定。", + "selected": false, + "source": "internal/cli/schema_hints/selection/sheet.json", + "value": true + } + ], + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "dws-tool-metadata/sheet marks this tool as reviewed", + "source": "internal/cli/schema_hints/metadata/sheet.json", + "value": true + }, + "risk": { + "candidates": [ + { + "precedence": "reviewed_explicit", + "review_reason": "dws-tool-metadata/sheet marks this tool as reviewed", + "selected": true, + "source": "internal/cli/schema_hints/metadata/sheet.json", + "value": "medium" + } + ], + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "dws-tool-metadata/sheet marks this tool as reviewed", + "source": "internal/cli/schema_hints/metadata/sheet.json", + "value": "medium" + }, + "title": { + "candidates": [ + { + "precedence": "cobra_help", + "selected": true, + "source": "cobra_help", + "value": "[危险] 回滚表格到指定版本" + } + ], + "precedence": "cobra_help", + "resolution": "highest_precedence", + "source": "cobra_help", + "value": "[危险] 回滚表格到指定版本" + }, + "use_when": { + "candidates": [ + { + "precedence": "reviewed_explicit", + "review_reason": "人工审阅:对照悟空 develop 同名命令实现、Cobra Long/Example 与 Runtime 确认门禁移植;不改变命令身份、参数契约或接口绑定。", + "selected": true, + "source": "internal/cli/schema_hints/selection/sheet.json", + "value": [ + "用户说 回滚到某个版本/恢复到之前的表格" + ] + } + ], + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "人工审阅:对照悟空 develop 同名命令实现、Cobra Long/Example 与 Runtime 确认门禁移植;不改变命令身份、参数契约或接口绑定。", + "source": "internal/cli/schema_hints/selection/sheet.json", + "value": [ + "用户说 回滚到某个版本/恢复到之前的表格" + ] + } + }, + "group": "version", + "has_parameters": true, + "idempotency": "non_idempotent", + "interface_mode": "composite", + "interface_reason": "Reviewed unpinned remote adapter: this executable CLI wrapper calls a remote helper that is absent from the pinned MCP metadata snapshot; no single pinned semantically equivalent interface_ref can represent the command.", + "is_alias": false, + "name": "version_revert", + "parameter_count": 2, + "parameters": { + "node": { + "description": "表格文档 ID 或 URL (必填)", + "field_provenance": { + "description": { + "candidates": [ + { + "precedence": "cobra_contract", + "selected": true, + "source": "cobra_usage", + "value": "表格文档 ID 或 URL (必填)" + }, + { + "precedence": "default", + "selected": false, + "source": "default", + "value": "" + } + ], + "precedence": "cobra_contract", + "resolution": "highest_precedence", + "source": "cobra_usage", + "value": "表格文档 ID 或 URL (必填)" + }, + "property": { + "candidates": [ + { + "precedence": "reviewed_mapping_exclusion", + "review_reason": "Reviewed unpinned adapter: sheet.version_revert has no singular pinned interface_ref; --node is a CLI wrapper input and does not publish a direct interface property.", + "selected": true, + "source": "reviewed_mapping_exclusion", + "value": "" + }, + { + "precedence": "inference", + "selected": false, + "source": "flag_name_inference", + "value": "node" + } + ], + "precedence": "reviewed_mapping_exclusion", + "resolution": "highest_precedence", + "review_reason": "Reviewed unpinned adapter: sheet.version_revert has no singular pinned interface_ref; --node is a CLI wrapper input and does not publish a direct interface property.", + "source": "reviewed_mapping_exclusion", + "value": "" + }, + "required": { + "candidates": [ + { + "precedence": "inference", + "selected": true, + "source": "usage_required_inference", + "value": true + }, + { + "precedence": "default", + "selected": false, + "source": "default", + "value": false + } + ], + "precedence": "inference", + "resolution": "highest_precedence", + "source": "usage_required_inference", + "value": true + }, + "required_when": { + "candidates": [ + { + "precedence": "default", + "selected": true, + "source": "default", + "value": "" + } + ], + "precedence": "default", + "resolution": "highest_precedence", + "source": "default", + "value": "" + }, + "type": { + "candidates": [ + { + "precedence": "cobra_contract", + "selected": true, + "source": "cobra_flag_type", + "value": "string" + } + ], + "precedence": "cobra_contract", + "resolution": "highest_precedence", + "source": "cobra_flag_type", + "value": "string" + } + }, + "required": true, + "type": "string" + }, + "version": { + "description": "目标版本号 (必填,从 list 获取)", + "field_provenance": { + "description": { + "candidates": [ + { + "precedence": "cobra_contract", + "selected": true, + "source": "cobra_usage", + "value": "目标版本号 (必填,从 list 获取)" + }, + { + "precedence": "default", + "selected": false, + "source": "default", + "value": "" + } + ], + "precedence": "cobra_contract", + "resolution": "highest_precedence", + "source": "cobra_usage", + "value": "目标版本号 (必填,从 list 获取)" + }, + "property": { + "candidates": [ + { + "precedence": "reviewed_mapping_exclusion", + "review_reason": "Reviewed unpinned adapter: sheet.version_revert has no singular pinned interface_ref; --version is a CLI wrapper input and does not publish a direct interface property.", + "selected": true, + "source": "reviewed_mapping_exclusion", + "value": "" + }, + { + "precedence": "inference", + "selected": false, + "source": "flag_name_inference", + "value": "version" + } + ], + "precedence": "reviewed_mapping_exclusion", + "resolution": "highest_precedence", + "review_reason": "Reviewed unpinned adapter: sheet.version_revert has no singular pinned interface_ref; --version is a CLI wrapper input and does not publish a direct interface property.", + "source": "reviewed_mapping_exclusion", + "value": "" + }, + "required": { + "candidates": [ + { + "precedence": "inference", + "selected": true, + "source": "usage_required_inference", + "value": true + }, + { + "precedence": "default", + "selected": false, + "source": "default", + "value": false + } + ], + "precedence": "inference", + "resolution": "highest_precedence", + "source": "usage_required_inference", + "value": true + }, + "required_when": { + "candidates": [ + { + "precedence": "default", + "selected": true, + "source": "default", + "value": "" + } + ], + "precedence": "default", + "resolution": "highest_precedence", + "source": "default", + "value": "" + }, + "type": { + "candidates": [ + { + "precedence": "cobra_contract", + "selected": true, + "source": "cobra_flag_type", + "value": "integer" + } + ], + "precedence": "cobra_contract", + "resolution": "highest_precedence", + "source": "cobra_flag_type", + "value": "integer" + } + }, + "required": true, + "type": "integer" + } + }, + "path": "sheet.version_revert", + "primary_cli_path": "sheet version revert", + "product_id": "sheet", + "reviewed": true, + "risk": "medium", + "source": "reviewed_command_registry", + "title": "[危险] 回滚表格到指定版本", + "use_when": [ + "用户说 回滚到某个版本/恢复到之前的表格" + ] + }, + "sheet.version_save": { + "agent_metadata_source": "embedded-skill-metadata", + "agent_source_refs": [ + "CommandRegistry:canonical_path=sheet.version_save", + "cobra-help:dws sheet version save", + "internal/cli/schema_hints/metadata/sheet.json", + "internal/cli/schema_hints/selection/sheet.json", + "wukong-develop:wukong/products" + ], + "agent_summary": "手动保存表格版本快照", + "agent_summary_source": "dws-agent-selection/sheet", + "availability": "available", + "avoid_when": [ + "回滚用 version revert;查看历史用 version list" + ], + "canonical_path": "sheet.version_save", + "cli_name": "save", + "cli_path": "sheet version save", + "confirmation": "not_required", + "description": "手动保存表格版本快照", + "display": "钉钉表格管理", + "effect": "write", + "effect_source": "agent-hint", + "examples": [ + "dws sheet version save --node \u003cSHEET_ID\u003e --format json" + ], + "field_provenance": { + "agent_summary": { + "candidates": [ + { + "precedence": "reviewed_explicit", + "review_reason": "人工审阅:对照悟空 develop 同名命令实现、Cobra Long/Example 与 Runtime 确认门禁移植;不改变命令身份、参数契约或接口绑定。", + "selected": true, + "source": "internal/cli/schema_hints/selection/sheet.json", + "value": "手动保存表格版本快照" + } + ], + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "人工审阅:对照悟空 develop 同名命令实现、Cobra Long/Example 与 Runtime 确认门禁移植;不改变命令身份、参数契约或接口绑定。", + "source": "internal/cli/schema_hints/selection/sheet.json", + "value": "手动保存表格版本快照" + }, + "availability": { + "candidates": [ + { + "precedence": "reviewed_explicit", + "review_reason": "dws-tool-metadata/sheet marks this tool as reviewed", + "selected": true, + "source": "internal/cli/schema_hints/metadata/sheet.json", + "value": "available" + } + ], + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "dws-tool-metadata/sheet marks this tool as reviewed", + "source": "internal/cli/schema_hints/metadata/sheet.json", + "value": "available" + }, + "avoid_when": { + "candidates": [ + { + "precedence": "reviewed_explicit", + "review_reason": "人工审阅:对照悟空 develop 同名命令实现、Cobra Long/Example 与 Runtime 确认门禁移植;不改变命令身份、参数契约或接口绑定。", + "selected": true, + "source": "internal/cli/schema_hints/selection/sheet.json", + "value": [ + "回滚用 version revert;查看历史用 version list" + ] + } + ], + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "人工审阅:对照悟空 develop 同名命令实现、Cobra Long/Example 与 Runtime 确认门禁移植;不改变命令身份、参数契约或接口绑定。", + "source": "internal/cli/schema_hints/selection/sheet.json", + "value": [ + "回滚用 version revert;查看历史用 version list" + ] + }, + "canonical_path": { + "candidates": [ + { + "precedence": "command_registry", + "selected": true, + "source": "reviewed_command_registry", + "source_ref": "sheet version save", + "value": "sheet.version_save" + } + ], + "precedence": "command_registry", + "resolution": "registry_identity", + "source": "reviewed_command_registry", + "source_ref": "sheet version save", + "value": "sheet.version_save" + }, + "confirmation": { + "candidates": [ + { + "precedence": "reviewed_explicit", + "review_reason": "dws-tool-metadata/sheet marks this tool as reviewed", + "selected": true, + "source": "internal/cli/schema_hints/metadata/sheet.json", + "value": "not_required" + } + ], + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "dws-tool-metadata/sheet marks this tool as reviewed", + "source": "internal/cli/schema_hints/metadata/sheet.json", + "value": "not_required" + }, + "description": { + "candidates": [ + { + "precedence": "cobra_help", + "selected": true, + "source": "cobra_help", + "value": "手动保存表格版本快照" + } + ], + "precedence": "cobra_help", + "resolution": "highest_precedence", + "source": "cobra_help", + "value": "手动保存表格版本快照" + }, + "effect": { + "candidates": [ + { + "precedence": "reviewed_explicit", + "review_reason": "dws-tool-metadata/sheet marks this tool as reviewed", + "selected": true, + "source": "internal/cli/schema_hints/metadata/sheet.json", + "value": "write" + } + ], + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "dws-tool-metadata/sheet marks this tool as reviewed", + "source": "internal/cli/schema_hints/metadata/sheet.json", + "value": "write" + }, + "examples": { + "candidates": [ + { + "precedence": "reviewed_explicit", + "review_reason": "人工审阅:对照悟空 develop 同名命令实现、Cobra Long/Example 与 Runtime 确认门禁移植;不改变命令身份、参数契约或接口绑定。", + "selected": true, + "source": "internal/cli/schema_hints/selection/sheet.json", + "value": [ + "dws sheet version save --node \u003cSHEET_ID\u003e --format json" + ] + } + ], + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "人工审阅:对照悟空 develop 同名命令实现、Cobra Long/Example 与 Runtime 确认门禁移植;不改变命令身份、参数契约或接口绑定。", + "source": "internal/cli/schema_hints/selection/sheet.json", + "value": [ + "dws sheet version save --node \u003cSHEET_ID\u003e --format json" + ] + }, + "idempotency": { + "candidates": [ + { + "precedence": "reviewed_explicit", + "review_reason": "dws-tool-metadata/sheet marks this tool as reviewed", + "selected": true, + "source": "internal/cli/schema_hints/metadata/sheet.json", + "value": "non_idempotent" + } + ], + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "dws-tool-metadata/sheet marks this tool as reviewed", + "source": "internal/cli/schema_hints/metadata/sheet.json", + "value": "non_idempotent" + }, + "interface_mode": { + "candidates": [ + { + "precedence": "reviewed_explicit", + "review_reason": "dws-tool-metadata/sheet marks this tool as reviewed", + "selected": true, + "source": "internal/cli/schema_hints/metadata/sheet.json", + "value": "composite" + } + ], + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "dws-tool-metadata/sheet marks this tool as reviewed", + "source": "internal/cli/schema_hints/metadata/sheet.json", + "value": "composite" + }, + "interface_reason": { + "candidates": [ + { + "precedence": "reviewed_explicit", + "review_reason": "dws-tool-metadata/sheet marks this tool as reviewed", + "selected": true, + "source": "internal/cli/schema_hints/metadata/sheet.json", + "value": "Reviewed unpinned remote adapter: this executable CLI wrapper calls a remote helper that is absent from the pinned MCP metadata snapshot; no single pinned semantically equivalent interface_ref can represent the command." + } + ], + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "dws-tool-metadata/sheet marks this tool as reviewed", + "source": "internal/cli/schema_hints/metadata/sheet.json", + "value": "Reviewed unpinned remote adapter: this executable CLI wrapper calls a remote helper that is absent from the pinned MCP metadata snapshot; no single pinned semantically equivalent interface_ref can represent the command." + }, + "interface_ref": { + "candidates": [ + { + "precedence": "reviewed_explicit", + "review_reason": "final interface mode composite forbids a direct MCP interface_ref", + "selected": true, + "source": "internal/cli/schema_hints/metadata/sheet.json", + "value": null + } + ], + "precedence": "reviewed_explicit", + "resolution": "interface_disposition_matrix", + "review_reason": "final interface mode composite forbids a direct MCP interface_ref", + "source": "internal/cli/schema_hints/metadata/sheet.json", + "value": null + }, + "reviewed": { + "candidates": [ + { + "precedence": "reviewed_explicit", + "review_reason": "dws-tool-metadata/sheet marks this tool as reviewed", + "selected": true, + "source": "internal/cli/schema_hints/metadata/sheet.json", + "value": true + }, + { + "precedence": "reviewed_explicit", + "review_reason": "人工审阅:对照悟空 develop 同名命令实现、Cobra Long/Example 与 Runtime 确认门禁移植;不改变命令身份、参数契约或接口绑定。", + "selected": false, + "source": "internal/cli/schema_hints/selection/sheet.json", + "value": true + } + ], + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "dws-tool-metadata/sheet marks this tool as reviewed", + "source": "internal/cli/schema_hints/metadata/sheet.json", + "value": true + }, + "risk": { + "candidates": [ + { + "precedence": "reviewed_explicit", + "review_reason": "dws-tool-metadata/sheet marks this tool as reviewed", + "selected": true, + "source": "internal/cli/schema_hints/metadata/sheet.json", + "value": "low" + } + ], + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "dws-tool-metadata/sheet marks this tool as reviewed", + "source": "internal/cli/schema_hints/metadata/sheet.json", + "value": "low" + }, + "title": { + "candidates": [ + { + "precedence": "cobra_help", + "selected": true, + "source": "cobra_help", + "value": "手动保存表格版本快照" + } + ], + "precedence": "cobra_help", + "resolution": "highest_precedence", + "source": "cobra_help", + "value": "手动保存表格版本快照" + }, + "use_when": { + "candidates": [ + { + "precedence": "reviewed_explicit", + "review_reason": "人工审阅:对照悟空 develop 同名命令实现、Cobra Long/Example 与 Runtime 确认门禁移植;不改变命令身份、参数契约或接口绑定。", + "selected": true, + "source": "internal/cli/schema_hints/selection/sheet.json", + "value": [ + "用户说 保存版本/存个快照,目标是在线表格" + ] + } + ], + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "人工审阅:对照悟空 develop 同名命令实现、Cobra Long/Example 与 Runtime 确认门禁移植;不改变命令身份、参数契约或接口绑定。", + "source": "internal/cli/schema_hints/selection/sheet.json", + "value": [ + "用户说 保存版本/存个快照,目标是在线表格" + ] + } + }, + "group": "version", + "has_parameters": true, + "idempotency": "non_idempotent", + "interface_mode": "composite", + "interface_reason": "Reviewed unpinned remote adapter: this executable CLI wrapper calls a remote helper that is absent from the pinned MCP metadata snapshot; no single pinned semantically equivalent interface_ref can represent the command.", + "is_alias": false, + "name": "version_save", + "parameter_count": 1, + "parameters": { + "node": { + "description": "表格文档 ID 或 URL (必填)", + "field_provenance": { + "description": { + "candidates": [ + { + "precedence": "cobra_contract", + "selected": true, + "source": "cobra_usage", + "value": "表格文档 ID 或 URL (必填)" + }, + { + "precedence": "default", + "selected": false, + "source": "default", + "value": "" + } + ], + "precedence": "cobra_contract", + "resolution": "highest_precedence", + "source": "cobra_usage", + "value": "表格文档 ID 或 URL (必填)" + }, + "property": { + "candidates": [ + { + "precedence": "reviewed_mapping_exclusion", + "review_reason": "Reviewed unpinned adapter: sheet.version_save has no singular pinned interface_ref; --node is a CLI wrapper input and does not publish a direct interface property.", + "selected": true, + "source": "reviewed_mapping_exclusion", + "value": "" + }, + { + "precedence": "inference", + "selected": false, + "source": "flag_name_inference", + "value": "node" + } + ], + "precedence": "reviewed_mapping_exclusion", + "resolution": "highest_precedence", + "review_reason": "Reviewed unpinned adapter: sheet.version_save has no singular pinned interface_ref; --node is a CLI wrapper input and does not publish a direct interface property.", + "source": "reviewed_mapping_exclusion", + "value": "" + }, + "required": { + "candidates": [ + { + "precedence": "inference", + "selected": true, + "source": "usage_required_inference", + "value": true + }, + { + "precedence": "default", + "selected": false, + "source": "default", + "value": false + } + ], + "precedence": "inference", + "resolution": "highest_precedence", + "source": "usage_required_inference", + "value": true + }, + "required_when": { + "candidates": [ + { + "precedence": "default", + "selected": true, + "source": "default", + "value": "" + } + ], + "precedence": "default", + "resolution": "highest_precedence", + "source": "default", + "value": "" + }, + "type": { + "candidates": [ + { + "precedence": "cobra_contract", + "selected": true, + "source": "cobra_flag_type", + "value": "string" + } + ], + "precedence": "cobra_contract", + "resolution": "highest_precedence", + "source": "cobra_flag_type", + "value": "string" + } + }, + "required": true, + "type": "string" + } + }, + "path": "sheet.version_save", + "primary_cli_path": "sheet version save", + "product_id": "sheet", + "reviewed": true, + "risk": "low", + "source": "reviewed_command_registry", + "title": "手动保存表格版本快照", + "use_when": [ + "用户说 保存版本/存个快照,目标是在线表格" + ] + }, "sheet.write_image": { "agent_metadata_source": "embedded-skill-metadata", "agent_source_refs": [ diff --git a/internal/cli/schema_command_registry/products/chat.json b/internal/cli/schema_command_registry/products/chat.json index 18cbbec5..91559ada 100644 --- a/internal/cli/schema_command_registry/products/chat.json +++ b/internal/cli/schema_command_registry/products/chat.json @@ -1,6 +1,174 @@ { "id": "chat", "tools": [ + { + "canonical_path": "chat.shortcut_at_me", + "cli_path": "chat +at-me" + }, + { + "canonical_path": "chat.shortcut_bot_find", + "cli_path": "chat +bot-find" + }, + { + "canonical_path": "chat.shortcut_bot_search", + "cli_path": "chat +bot-search" + }, + { + "canonical_path": "chat.shortcut_broadcast", + "cli_path": "chat +broadcast" + }, + { + "canonical_path": "chat.shortcut_category_create", + "cli_path": "chat +category-create" + }, + { + "canonical_path": "chat.shortcut_category_delete", + "cli_path": "chat +category-delete" + }, + { + "canonical_path": "chat.shortcut_category_list", + "cli_path": "chat +category-list" + }, + { + "canonical_path": "chat.shortcut_category_rename", + "cli_path": "chat +category-rename" + }, + { + "canonical_path": "chat.shortcut_chat_bots", + "cli_path": "chat +chat-bots" + }, + { + "canonical_path": "chat.shortcut_chat_dismiss", + "cli_path": "chat +chat-dismiss" + }, + { + "canonical_path": "chat.shortcut_chat_invite_url", + "cli_path": "chat +chat-invite-url" + }, + { + "canonical_path": "chat.shortcut_chat_list_all", + "cli_path": "chat +chat-list-all" + }, + { + "canonical_path": "chat.shortcut_chat_list_join_requests", + "cli_path": "chat +chat-list-join-requests" + }, + { + "canonical_path": "chat.shortcut_chat_list_mine", + "cli_path": "chat +chat-list-mine" + }, + { + "canonical_path": "chat.shortcut_chat_mute", + "cli_path": "chat +chat-mute" + }, + { + "canonical_path": "chat.shortcut_chat_role_add", + "cli_path": "chat +chat-role-add" + }, + { + "canonical_path": "chat.shortcut_chat_role_list", + "cli_path": "chat +chat-role-list" + }, + { + "canonical_path": "chat.shortcut_chat_role_query_user", + "cli_path": "chat +chat-role-query-user" + }, + { + "canonical_path": "chat.shortcut_chat_role_set_user", + "cli_path": "chat +chat-role-set-user" + }, + { + "canonical_path": "chat.shortcut_chat_role_update", + "cli_path": "chat +chat-role-update" + }, + { + "canonical_path": "chat.shortcut_chat_search", + "cli_path": "chat +chat-search" + }, + { + "canonical_path": "chat.shortcut_chat_set_admin", + "cli_path": "chat +chat-set-admin" + }, + { + "canonical_path": "chat.shortcut_chat_set_history", + "cli_path": "chat +chat-set-history" + }, + { + "canonical_path": "chat.shortcut_chat_update_alias", + "cli_path": "chat +chat-update-alias" + }, + { + "canonical_path": "chat.shortcut_chat_update_nick", + "cli_path": "chat +chat-update-nick" + }, + { + "canonical_path": "chat.shortcut_conversation_clear_all_red_point", + "cli_path": "chat +conversation-clear-all-red-point" + }, + { + "canonical_path": "chat.shortcut_conversation_info", + "cli_path": "chat +conversation-info" + }, + { + "canonical_path": "chat.shortcut_conversation_list", + "cli_path": "chat +conversation-list" + }, + { + "canonical_path": "chat.shortcut_conversation_list_top", + "cli_path": "chat +conversation-list-top" + }, + { + "canonical_path": "chat.shortcut_dm", + "cli_path": "chat +dm" + }, + { + "canonical_path": "chat.shortcut_group_members", + "cli_path": "chat +group-members" + }, + { + "canonical_path": "chat.shortcut_messages_list_direct", + "cli_path": "chat +messages-list-direct" + }, + { + "canonical_path": "chat.shortcut_messages_list_pin", + "cli_path": "chat +messages-list-pin" + }, + { + "canonical_path": "chat.shortcut_messages_list_unread_conversations", + "cli_path": "chat +messages-list-unread-conversations" + }, + { + "canonical_path": "chat.shortcut_messages_mget", + "cli_path": "chat +messages-mget" + }, + { + "canonical_path": "chat.shortcut_messages_query_send_status", + "cli_path": "chat +messages-query-send-status" + }, + { + "canonical_path": "chat.shortcut_messages_read_status", + "cli_path": "chat +messages-read-status" + }, + { + "canonical_path": "chat.shortcut_messages_send_by_webhook", + "cli_path": "chat +messages-send-by-webhook" + }, + { + "canonical_path": "chat.shortcut_messages_update_card", + "cli_path": "chat +messages-update-card" + }, + { + "canonical_path": "chat.shortcut_my_groups", + "cli_path": "chat +my-groups" + }, + { + "canonical_path": "chat.shortcut_send_to_group", + "cli_path": "chat +send-to-group" + }, + { + "canonical_path": "chat.shortcut_unread_chats", + "cli_path": "chat +unread-chats" + }, { "canonical_path": "chat.search_bots", "cli_path": "chat bot find" @@ -9,6 +177,10 @@ "canonical_path": "chat.search_my_robots", "cli_path": "chat bot search" }, + { + "canonical_path": "chat.get_conv_categories_info", + "cli_path": "chat category batch-info" + }, { "canonical_path": "chat.create_smart_conv_category", "cli_path": "chat category create-smart" @@ -17,6 +189,10 @@ "canonical_path": "chat.list_user_define_conv_categories", "cli_path": "chat category list" }, + { + "canonical_path": "chat.list_conv_categories_by_conv", + "cli_path": "chat category list-by-conv" + }, { "canonical_path": "chat.list_conversations_by_category", "cli_path": "chat category list-conversations" @@ -41,6 +217,10 @@ "canonical_path": "chat.get_conv_info_by_group_id", "cli_path": "chat group get-by-group-id" }, + { + "canonical_path": "chat.get_group_mute_config", + "cli_path": "chat group get-mute-config" + }, { "canonical_path": "chat.get_group_invite_url", "cli_path": "chat group invite-url" @@ -89,10 +269,26 @@ "canonical_path": "chat.update_group_icon", "cli_path": "chat group update-icon" }, + { + "canonical_path": "chat.update_group_nick", + "cli_path": "chat group update-nick" + }, { "canonical_path": "chat.update_group_settings", "cli_path": "chat group update-settings" }, + { + "canonical_path": "chat.upgrade_group_to_external", + "cli_path": "chat group upgrade-to-external" + }, + { + "canonical_path": "chat.batch_query_group_chat_settings", + "cli_path": "chat group user-settings query" + }, + { + "canonical_path": "chat.batch_update_group_chat_settings", + "cli_path": "chat group user-settings set" + }, { "canonical_path": "chat.set_group_mute", "cli_path": "chat group-mute" @@ -157,6 +353,10 @@ "canonical_path": "chat.download_media", "cli_path": "chat message download-media" }, + { + "canonical_path": "chat.edit_message", + "cli_path": "chat message edit" + }, { "canonical_path": "chat.forward_message", "cli_path": "chat message forward" @@ -277,6 +477,10 @@ "canonical_path": "chat.update_streaming_card", "cli_path": "chat message update-card" }, + { + "canonical_path": "chat.update_text_emotion", + "cli_path": "chat message update-text-emotion" + }, { "canonical_path": "chat.update_notification_off", "cli_path": "chat mute" @@ -293,134 +497,6 @@ "canonical_path": "chat.set_top_conversation", "cli_path": "chat set-top" }, - { - "canonical_path": "chat.edit_message", - "cli_path": "chat message edit" - }, - { - "canonical_path": "chat.get_conv_categories_info", - "cli_path": "chat category batch-info" - }, - { - "canonical_path": "chat.list_conv_categories_by_conv", - "cli_path": "chat category list-by-conv" - }, - { - "canonical_path": "chat.update_group_nick", - "cli_path": "chat group update-nick" - }, - { - "canonical_path": "chat.upgrade_group_to_external", - "cli_path": "chat group upgrade-to-external" - }, - { - "canonical_path": "chat.shortcut_bot_search", - "cli_path": "chat +bot-search" - }, - { - "canonical_path": "chat.shortcut_bot_find", - "cli_path": "chat +bot-find" - }, - { - "canonical_path": "chat.shortcut_conversation_info", - "cli_path": "chat +conversation-info" - }, - { - "canonical_path": "chat.shortcut_conversation_clear_all_red_point", - "cli_path": "chat +conversation-clear-all-red-point" - }, - { - "canonical_path": "chat.shortcut_conversation_list", - "cli_path": "chat +conversation-list" - }, - { - "canonical_path": "chat.shortcut_conversation_list_top", - "cli_path": "chat +conversation-list-top" - }, - { - "canonical_path": "chat.shortcut_category_list", - "cli_path": "chat +category-list" - }, - { - "canonical_path": "chat.shortcut_category_create", - "cli_path": "chat +category-create" - }, - { - "canonical_path": "chat.shortcut_category_delete", - "cli_path": "chat +category-delete" - }, - { - "canonical_path": "chat.shortcut_category_rename", - "cli_path": "chat +category-rename" - }, - { - "canonical_path": "chat.shortcut_chat_search", - "cli_path": "chat +chat-search" - }, - { - "canonical_path": "chat.shortcut_chat_invite_url", - "cli_path": "chat +chat-invite-url" - }, - { - "canonical_path": "chat.shortcut_chat_dismiss", - "cli_path": "chat +chat-dismiss" - }, - { - "canonical_path": "chat.shortcut_chat_set_history", - "cli_path": "chat +chat-set-history" - }, - { - "canonical_path": "chat.shortcut_chat_update_nick", - "cli_path": "chat +chat-update-nick" - }, - { - "canonical_path": "chat.shortcut_chat_update_alias", - "cli_path": "chat +chat-update-alias" - }, - { - "canonical_path": "chat.shortcut_chat_list_mine", - "cli_path": "chat +chat-list-mine" - }, - { - "canonical_path": "chat.shortcut_chat_list_all", - "cli_path": "chat +chat-list-all" - }, - { - "canonical_path": "chat.shortcut_chat_list_join_requests", - "cli_path": "chat +chat-list-join-requests" - }, - { - "canonical_path": "chat.shortcut_chat_bots", - "cli_path": "chat +chat-bots" - }, - { - "canonical_path": "chat.shortcut_chat_set_admin", - "cli_path": "chat +chat-set-admin" - }, - { - "canonical_path": "chat.shortcut_chat_mute", - "cli_path": "chat +chat-mute" - }, - { - "canonical_path": "chat.shortcut_chat_role_list", - "cli_path": "chat +chat-role-list" - }, - { - "canonical_path": "chat.shortcut_chat_role_add", - "cli_path": "chat +chat-role-add" - }, - { - "canonical_path": "chat.shortcut_chat_role_update", - "cli_path": "chat +chat-role-update" - }, - { - "canonical_path": "chat.shortcut_chat_role_set_user", - "cli_path": "chat +chat-role-set-user" - }, - { - "canonical_path": "chat.shortcut_chat_role_query_user", - "cli_path": "chat +chat-role-query-user" - }, { "canonical_path": "chat.shortcut_chat_messages", "cli_path": "chat +chat-messages" @@ -429,77 +505,17 @@ "canonical_path": "chat.shortcut_messages_send", "cli_path": "chat +messages-send" }, - { - "canonical_path": "chat.shortcut_messages_send_by_webhook", - "cli_path": "chat +messages-send-by-webhook" - }, { "canonical_path": "chat.shortcut_messages_send_card", "cli_path": "chat +messages-send-card" }, - { - "canonical_path": "chat.shortcut_messages_list_direct", - "cli_path": "chat +messages-list-direct" - }, - { - "canonical_path": "chat.shortcut_messages_list_unread_conversations", - "cli_path": "chat +messages-list-unread-conversations" - }, - { - "canonical_path": "chat.shortcut_messages_mget", - "cli_path": "chat +messages-mget" - }, - { - "canonical_path": "chat.shortcut_messages_query_send_status", - "cli_path": "chat +messages-query-send-status" - }, - { - "canonical_path": "chat.shortcut_messages_read_status", - "cli_path": "chat +messages-read-status" - }, - { - "canonical_path": "chat.shortcut_messages_update_card", - "cli_path": "chat +messages-update-card" - }, - { - "canonical_path": "chat.shortcut_messages_list_pin", - "cli_path": "chat +messages-list-pin" - }, - { - "canonical_path": "chat.shortcut_at_me", - "cli_path": "chat +at-me" - }, - { - "canonical_path": "chat.shortcut_broadcast", - "cli_path": "chat +broadcast" - }, - { - "canonical_path": "chat.shortcut_dm", - "cli_path": "chat +dm" - }, - { - "canonical_path": "chat.shortcut_group_members", - "cli_path": "chat +group-members" - }, - { - "canonical_path": "chat.shortcut_my_groups", - "cli_path": "chat +my-groups" - }, { "canonical_path": "chat.shortcut_search_msg", "cli_path": "chat +search-msg" }, - { - "canonical_path": "chat.shortcut_send_to_group", - "cli_path": "chat +send-to-group" - }, { "canonical_path": "chat.shortcut_thread_replies", "cli_path": "chat +thread-replies" - }, - { - "canonical_path": "chat.shortcut_unread_chats", - "cli_path": "chat +unread-chats" } ] } diff --git a/internal/cli/schema_command_registry/products/doc.json b/internal/cli/schema_command_registry/products/doc.json index f7826f80..26956a84 100644 --- a/internal/cli/schema_command_registry/products/doc.json +++ b/internal/cli/schema_command_registry/products/doc.json @@ -1,6 +1,74 @@ { "id": "doc", "tools": [ + { + "canonical_path": "doc.shortcut_comment_create", + "cli_path": "doc +comment-create" + }, + { + "canonical_path": "doc.shortcut_comment_list", + "cli_path": "doc +comment-list" + }, + { + "canonical_path": "doc.shortcut_comment_reply", + "cli_path": "doc +comment-reply" + }, + { + "canonical_path": "doc.shortcut_copy", + "cli_path": "doc +copy" + }, + { + "canonical_path": "doc.shortcut_doc_append", + "cli_path": "doc +doc-append" + }, + { + "canonical_path": "doc.shortcut_export_get", + "cli_path": "doc +export-get" + }, + { + "canonical_path": "doc.shortcut_export_submit", + "cli_path": "doc +export-submit" + }, + { + "canonical_path": "doc.shortcut_find_doc", + "cli_path": "doc +find-doc" + }, + { + "canonical_path": "doc.shortcut_list", + "cli_path": "doc +list" + }, + { + "canonical_path": "doc.shortcut_move", + "cli_path": "doc +move" + }, + { + "canonical_path": "doc.shortcut_search", + "cli_path": "doc +search" + }, + { + "canonical_path": "doc.shortcut_share_doc", + "cli_path": "doc +share-doc" + }, + { + "canonical_path": "doc.shortcut_template_list", + "cli_path": "doc +template-list" + }, + { + "canonical_path": "doc.shortcut_template_search", + "cli_path": "doc +template-search" + }, + { + "canonical_path": "doc.shortcut_version_list", + "cli_path": "doc +version-list" + }, + { + "canonical_path": "doc.shortcut_version_revert", + "cli_path": "doc +version-revert" + }, + { + "canonical_path": "doc.shortcut_version_save", + "cli_path": "doc +version-save" + }, { "canonical_path": "doc.delete_document_block", "cli_path": "doc block delete" @@ -121,6 +189,26 @@ "canonical_path": "doc.search_documents", "cli_path": "doc search" }, + { + "canonical_path": "doc.style_background_clear", + "cli_path": "doc style background clear" + }, + { + "canonical_path": "doc.style_background_set", + "cli_path": "doc style background set" + }, + { + "canonical_path": "doc.style_cover_clear", + "cli_path": "doc style cover clear" + }, + { + "canonical_path": "doc.style_cover_set", + "cli_path": "doc style cover set" + }, + { + "canonical_path": "doc.get_document_style", + "cli_path": "doc style get" + }, { "canonical_path": "doc.template_apply", "cli_path": "doc template apply" @@ -152,74 +240,6 @@ { "canonical_path": "doc.version_save", "cli_path": "doc version save" - }, - { - "canonical_path": "doc.shortcut_search", - "cli_path": "doc +search" - }, - { - "canonical_path": "doc.shortcut_list", - "cli_path": "doc +list" - }, - { - "canonical_path": "doc.shortcut_copy", - "cli_path": "doc +copy" - }, - { - "canonical_path": "doc.shortcut_move", - "cli_path": "doc +move" - }, - { - "canonical_path": "doc.shortcut_comment_list", - "cli_path": "doc +comment-list" - }, - { - "canonical_path": "doc.shortcut_comment_create", - "cli_path": "doc +comment-create" - }, - { - "canonical_path": "doc.shortcut_comment_reply", - "cli_path": "doc +comment-reply" - }, - { - "canonical_path": "doc.shortcut_export_submit", - "cli_path": "doc +export-submit" - }, - { - "canonical_path": "doc.shortcut_export_get", - "cli_path": "doc +export-get" - }, - { - "canonical_path": "doc.shortcut_version_save", - "cli_path": "doc +version-save" - }, - { - "canonical_path": "doc.shortcut_version_list", - "cli_path": "doc +version-list" - }, - { - "canonical_path": "doc.shortcut_version_revert", - "cli_path": "doc +version-revert" - }, - { - "canonical_path": "doc.shortcut_template_list", - "cli_path": "doc +template-list" - }, - { - "canonical_path": "doc.shortcut_template_search", - "cli_path": "doc +template-search" - }, - { - "canonical_path": "doc.shortcut_doc_append", - "cli_path": "doc +doc-append" - }, - { - "canonical_path": "doc.shortcut_find_doc", - "cli_path": "doc +find-doc" - }, - { - "canonical_path": "doc.shortcut_share_doc", - "cli_path": "doc +share-doc" } ] } diff --git a/internal/cli/schema_command_registry/products/drive.json b/internal/cli/schema_command_registry/products/drive.json index b3484517..771e1a18 100644 --- a/internal/cli/schema_command_registry/products/drive.json +++ b/internal/cli/schema_command_registry/products/drive.json @@ -1,6 +1,34 @@ { "id": "drive", "tools": [ + { + "canonical_path": "drive.shortcut_copy", + "cli_path": "drive +copy" + }, + { + "canonical_path": "drive.shortcut_find_file", + "cli_path": "drive +find-file" + }, + { + "canonical_path": "drive.shortcut_info", + "cli_path": "drive +info" + }, + { + "canonical_path": "drive.shortcut_move", + "cli_path": "drive +move" + }, + { + "canonical_path": "drive.shortcut_recent", + "cli_path": "drive +recent" + }, + { + "canonical_path": "drive.shortcut_search", + "cli_path": "drive +search" + }, + { + "canonical_path": "drive.shortcut_search_docs", + "cli_path": "drive +search-docs" + }, { "canonical_path": "drive.commit_upload", "cli_path": "drive commit" @@ -9,6 +37,10 @@ "canonical_path": "drive.copy_document", "cli_path": "drive copy" }, + { + "canonical_path": "drive.get_cover", + "cli_path": "drive cover" + }, { "canonical_path": "drive.delete_document", "cli_path": "drive delete" @@ -17,6 +49,10 @@ "canonical_path": "drive.download_file", "cli_path": "drive download" }, + { + "canonical_path": "drive.download_file_version", + "cli_path": "drive download-version" + }, { "canonical_path": "drive.get_file_info", "cli_path": "drive info" @@ -41,6 +77,14 @@ "canonical_path": "drive.add_permission", "cli_path": "drive permission add" }, + { + "canonical_path": "drive.apply_permission", + "cli_path": "drive permission apply" + }, + { + "canonical_path": "drive.query_permission_apply_info", + "cli_path": "drive permission apply-info" + }, { "canonical_path": "drive.list_permission", "cli_path": "drive permission list" @@ -49,6 +93,10 @@ "canonical_path": "drive.permission_remove", "cli_path": "drive permission remove" }, + { + "canonical_path": "drive.transfer_owner", + "cli_path": "drive permission transfer-owner" + }, { "canonical_path": "drive.permission_update", "cli_path": "drive permission update" @@ -81,6 +129,10 @@ "canonical_path": "drive.rename_document", "cli_path": "drive rename" }, + { + "canonical_path": "drive.revert_file_version", + "cli_path": "drive revert" + }, { "canonical_path": "drive.search_files", "cli_path": "drive search" @@ -89,6 +141,18 @@ "canonical_path": "drive.create_shortcut", "cli_path": "drive shortcut" }, + { + "canonical_path": "drive.mark_star", + "cli_path": "drive star add" + }, + { + "canonical_path": "drive.get_star_list", + "cli_path": "drive star list" + }, + { + "canonical_path": "drive.unmark_star", + "cli_path": "drive star remove" + }, { "canonical_path": "drive.get_node_stats", "cli_path": "drive stats" @@ -100,34 +164,6 @@ { "canonical_path": "drive.get_upload_info", "cli_path": "drive upload-info" - }, - { - "canonical_path": "drive.shortcut_info", - "cli_path": "drive +info" - }, - { - "canonical_path": "drive.shortcut_search", - "cli_path": "drive +search" - }, - { - "canonical_path": "drive.shortcut_search_docs", - "cli_path": "drive +search-docs" - }, - { - "canonical_path": "drive.shortcut_copy", - "cli_path": "drive +copy" - }, - { - "canonical_path": "drive.shortcut_move", - "cli_path": "drive +move" - }, - { - "canonical_path": "drive.shortcut_recent", - "cli_path": "drive +recent" - }, - { - "canonical_path": "drive.shortcut_find_file", - "cli_path": "drive +find-file" } ] } diff --git a/internal/cli/schema_command_registry/products/sheet.json b/internal/cli/schema_command_registry/products/sheet.json index da72585b..f3da73bf 100644 --- a/internal/cli/schema_command_registry/products/sheet.json +++ b/internal/cli/schema_command_registry/products/sheet.json @@ -1,6 +1,14 @@ { "id": "sheet", "tools": [ + { + "canonical_path": "sheet.shortcut_list_sheets", + "cli_path": "sheet +list-sheets" + }, + { + "canonical_path": "sheet.shortcut_read", + "cli_path": "sheet +read" + }, { "canonical_path": "sheet.add_dimension", "cli_path": "sheet add-dimension" @@ -29,6 +37,26 @@ "canonical_path": "sheet.chart_update", "cli_path": "sheet chart update" }, + { + "canonical_path": "sheet.create_sheet_comment", + "cli_path": "sheet comment create" + }, + { + "canonical_path": "sheet.delete_sheet_comment", + "cli_path": "sheet comment delete" + }, + { + "canonical_path": "sheet.list_sheet_comments", + "cli_path": "sheet comment list" + }, + { + "canonical_path": "sheet.reply_sheet_comment", + "cli_path": "sheet comment reply" + }, + { + "canonical_path": "sheet.update_sheet_comment", + "cli_path": "sheet comment update" + }, { "canonical_path": "sheet.create_cond_format", "cli_path": "sheet cond-format create" @@ -149,6 +177,10 @@ "canonical_path": "sheet.find_cells", "cli_path": "sheet find" }, + { + "canonical_path": "sheet.formula_verify", + "cli_path": "sheet formula-verify" + }, { "canonical_path": "sheet.get_dropdown_lists", "cli_path": "sheet get-dropdown" @@ -165,10 +197,6 @@ "canonical_path": "sheet.hide_gridline", "cli_path": "sheet hide-gridline" }, - { - "canonical_path": "sheet.info", - "cli_path": "sheet info" - }, { "canonical_path": "sheet.import", "cli_path": "sheet import create" @@ -177,6 +205,10 @@ "canonical_path": "sheet.import_get", "cli_path": "sheet import get" }, + { + "canonical_path": "sheet.info", + "cli_path": "sheet info" + }, { "canonical_path": "sheet.insert_dimension", "cli_path": "sheet insert-dimension" @@ -319,17 +351,21 @@ "canonical_path": "sheet.update_float_image", "cli_path": "sheet update-float-image" }, + { + "canonical_path": "sheet.version_list", + "cli_path": "sheet version list" + }, + { + "canonical_path": "sheet.version_revert", + "cli_path": "sheet version revert" + }, + { + "canonical_path": "sheet.version_save", + "cli_path": "sheet version save" + }, { "canonical_path": "sheet.write_image", "cli_path": "sheet write-image" - }, - { - "canonical_path": "sheet.shortcut_list_sheets", - "cli_path": "sheet +list-sheets" - }, - { - "canonical_path": "sheet.shortcut_read", - "cli_path": "sheet +read" } ] } diff --git a/internal/cli/schema_constraints_runtime.go b/internal/cli/schema_constraints_runtime.go index 2351f60f..686e7b76 100644 --- a/internal/cli/schema_constraints_runtime.go +++ b/internal/cli/schema_constraints_runtime.go @@ -40,6 +40,7 @@ func init() { registerExclusiveOneOf("chat.create_and_send_card", "group", "receiver") registerRequireOneOf("chat.add_emoji_reaction", "conversation-id", "group", "id", "chat") registerRequireOneOf("chat.add_text_emotion", "conversation-id", "group", "id", "chat") + registerRequireOneOf("chat.update_text_emotion", "conversation-id", "group", "id", "chat") registerExclusiveOneOf("chat.get_conversation_info", "group", "user", "open-dingtalk-id") registerExclusiveOneOf("chat.list_conversation_message_v2", "group", "user", "open-dingtalk-id") registerExclusiveOneOf("chat.list_individual_chat_message", "user", "open-dingtalk-id") diff --git a/internal/cli/schema_hints/metadata/chat.json b/internal/cli/schema_hints/metadata/chat.json index 3fd57e62..66da8a4a 100644 --- a/internal/cli/schema_hints/metadata/chat.json +++ b/internal/cli/schema_hints/metadata/chat.json @@ -1411,6 +1411,101 @@ "review_reason": "Reviewed against the built-in Shortcut registry and mounted Cobra command: publishes the executable risk gate and composite interface without inventing a direct MCP identity.", "cli_path": "chat +unread-chats", "runtime_gate": "none" + }, + "chat.update_text_emotion": { + "effect": "write", + "risk": "low", + "confirmation": "not_required", + "idempotency": "idempotent", + "interface_mode": "composite", + "availability": "available", + "interface_reason": "Reviewed unpinned remote adapter: the executable CLI calls im/update_text_emotion, which is proven by dws-wukong develop@30f13f02 but absent from the pinned MCP metadata snapshot; no pinned interface_ref can represent the command yet.", + "reviewed": true, + "parameters": { + "background-id": { + "property": "backgroundId", + "description": "新文字表情的背景 ID", + "required": true + }, + "chat": { + "property": "openConversationId", + "required": false + }, + "conversation-id": { + "property": "openConversationId", + "description": "会话 openConversationId;与 --group、--id、--chat 四选一", + "required": false + }, + "emotion-id": { + "property": "emotionId", + "description": "新的文字表情 ID,可通过 create-text-emotion 获取", + "required": true + }, + "emotion-name": { + "property": "emotionName", + "description": "新的文字表情名称", + "required": true + }, + "group": { + "property": "openConversationId", + "required": false + }, + "id": { + "property": "openConversationId", + "required": false + }, + "msg-id": { + "property": "openMsgId", + "description": "需要原地更新文字表情的消息 openMsgId", + "required": true + }, + "old-emotion-id": { + "property": "oldEmotionId", + "description": "消息上当前文字表情的 emotionId", + "required": true + }, + "text": { + "property": "text", + "description": "新的文字表情内容", + "required": true + } + }, + "review_reason": "Ported from dws-wukong commits d6831318 and bcf56ccf, merged at 30f13f02. The four conversation locators alias one property; the other six business parameters are required by Cobra before the single im/update_text_emotion call.", + "cli_path": "chat message update-text-emotion", + "runtime_gate": "none" + }, + "chat.get_group_mute_config": { + "effect": "read", + "risk": "low", + "confirmation": "not_required", + "idempotency": "idempotent", + "interface_mode": "composite", + "availability": "available", + "interface_reason": "Reviewed unpinned remote adapter: this executable CLI wrapper calls a remote helper that is absent from the pinned MCP metadata snapshot; no single pinned semantically equivalent interface_ref can represent the command.", + "reviewed": true, + "runtime_gate": "none" + }, + "chat.batch_query_group_chat_settings": { + "effect": "read", + "risk": "low", + "confirmation": "not_required", + "idempotency": "idempotent", + "interface_mode": "composite", + "availability": "available", + "interface_reason": "Reviewed unpinned remote adapter: this executable CLI wrapper calls a remote helper that is absent from the pinned MCP metadata snapshot; no single pinned semantically equivalent interface_ref can represent the command.", + "reviewed": true, + "runtime_gate": "none" + }, + "chat.batch_update_group_chat_settings": { + "effect": "write", + "risk": "medium", + "confirmation": "not_required", + "idempotency": "idempotent", + "interface_mode": "composite", + "availability": "available", + "interface_reason": "Reviewed unpinned remote adapter: this executable CLI wrapper calls a remote helper that is absent from the pinned MCP metadata snapshot; no single pinned semantically equivalent interface_ref can represent the command.", + "reviewed": true, + "runtime_gate": "none" } } } diff --git a/internal/cli/schema_hints/metadata/doc.json b/internal/cli/schema_hints/metadata/doc.json index e9212b3f..bdd70a2a 100644 --- a/internal/cli/schema_hints/metadata/doc.json +++ b/internal/cli/schema_hints/metadata/doc.json @@ -587,6 +587,61 @@ "review_reason": "Reviewed against the built-in Shortcut registry and mounted Cobra command: publishes the executable risk gate and composite interface without inventing a direct MCP identity.", "cli_path": "doc +share-doc", "runtime_gate": "typed_yes" + }, + "doc.style_cover_set": { + "effect": "write", + "risk": "low", + "confirmation": "not_required", + "idempotency": "idempotent", + "interface_mode": "composite", + "availability": "available", + "interface_reason": "Reviewed unpinned remote adapter: this executable CLI wrapper calls a remote helper that is absent from the pinned MCP metadata snapshot; no single pinned semantically equivalent interface_ref can represent the command.", + "reviewed": true, + "runtime_gate": "none" + }, + "doc.style_cover_clear": { + "effect": "write", + "risk": "low", + "confirmation": "not_required", + "idempotency": "idempotent", + "interface_mode": "composite", + "availability": "available", + "interface_reason": "Reviewed unpinned remote adapter: this executable CLI wrapper calls a remote helper that is absent from the pinned MCP metadata snapshot; no single pinned semantically equivalent interface_ref can represent the command.", + "reviewed": true, + "runtime_gate": "none" + }, + "doc.style_background_set": { + "effect": "write", + "risk": "low", + "confirmation": "not_required", + "idempotency": "idempotent", + "interface_mode": "composite", + "availability": "available", + "interface_reason": "Reviewed unpinned remote adapter: this executable CLI wrapper calls a remote helper that is absent from the pinned MCP metadata snapshot; no single pinned semantically equivalent interface_ref can represent the command.", + "reviewed": true, + "runtime_gate": "none" + }, + "doc.style_background_clear": { + "effect": "write", + "risk": "low", + "confirmation": "not_required", + "idempotency": "idempotent", + "interface_mode": "composite", + "availability": "available", + "interface_reason": "Reviewed unpinned remote adapter: this executable CLI wrapper calls a remote helper that is absent from the pinned MCP metadata snapshot; no single pinned semantically equivalent interface_ref can represent the command.", + "reviewed": true, + "runtime_gate": "none" + }, + "doc.get_document_style": { + "effect": "read", + "risk": "low", + "confirmation": "not_required", + "idempotency": "idempotent", + "interface_mode": "composite", + "availability": "available", + "interface_reason": "Reviewed unpinned remote adapter: this executable CLI wrapper calls a remote helper that is absent from the pinned MCP metadata snapshot; no single pinned semantically equivalent interface_ref can represent the command.", + "reviewed": true, + "runtime_gate": "none" } } } diff --git a/internal/cli/schema_hints/metadata/drive.json b/internal/cli/schema_hints/metadata/drive.json index 6873a74e..3cb923e0 100644 --- a/internal/cli/schema_hints/metadata/drive.json +++ b/internal/cli/schema_hints/metadata/drive.json @@ -17,6 +17,17 @@ "availability": "available", "reviewed": true }, + "drive.apply_permission": { + "effect": "write", + "risk": "medium", + "confirmation": "user_required", + "idempotency": "non_idempotent", + "interface_mode": "composite", + "availability": "available", + "interface_reason": "Reviewed unpinned remote adapter: this executable CLI wrapper calls a remote helper that is absent from the pinned MCP metadata snapshot; no single pinned semantically equivalent interface_ref can represent the command.", + "reviewed": true, + "runtime_gate": "confirm_delete" + }, "drive.commit_upload": { "interface_mode": "mcp", "availability": "available", @@ -64,6 +75,28 @@ "availability": "available", "reviewed": true }, + "drive.download_file_version": { + "effect": "read", + "risk": "low", + "confirmation": "not_required", + "idempotency": "idempotent", + "interface_mode": "composite", + "availability": "available", + "interface_reason": "Reviewed unpinned remote adapter: this executable CLI wrapper calls a remote helper that is absent from the pinned MCP metadata snapshot; no single pinned semantically equivalent interface_ref can represent the command.", + "reviewed": true, + "runtime_gate": "none" + }, + "drive.get_cover": { + "effect": "read", + "risk": "low", + "confirmation": "not_required", + "idempotency": "idempotent", + "interface_mode": "composite", + "availability": "available", + "interface_reason": "Reviewed unpinned remote adapter: this executable CLI wrapper calls a remote helper that is absent from the pinned MCP metadata snapshot; no single pinned semantically equivalent interface_ref can represent the command.", + "reviewed": true, + "runtime_gate": "none" + }, "drive.get_file_info": { "interface_mode": "mcp", "availability": "available", @@ -75,6 +108,17 @@ "interface_reason": "Reviewed unpinned remote adapter: this executable CLI wrapper calls a remote helper that is absent from the pinned MCP metadata snapshot; no single pinned semantically equivalent interface_ref can represent the command.", "reviewed": true }, + "drive.get_star_list": { + "effect": "read", + "risk": "low", + "confirmation": "not_required", + "idempotency": "idempotent", + "interface_mode": "composite", + "availability": "available", + "interface_reason": "Reviewed unpinned remote adapter: this executable CLI wrapper calls a remote helper that is absent from the pinned MCP metadata snapshot; no single pinned semantically equivalent interface_ref can represent the command.", + "reviewed": true, + "runtime_gate": "none" + }, "drive.get_upload_info": { "interface_mode": "mcp", "availability": "available", @@ -100,6 +144,17 @@ "availability": "available", "reviewed": true }, + "drive.mark_star": { + "effect": "write", + "risk": "low", + "confirmation": "not_required", + "idempotency": "idempotent", + "interface_mode": "composite", + "availability": "available", + "interface_reason": "Reviewed unpinned remote adapter: this executable CLI wrapper calls a remote helper that is absent from the pinned MCP metadata snapshot; no single pinned semantically equivalent interface_ref can represent the command.", + "reviewed": true, + "runtime_gate": "none" + }, "drive.move_document": { "interface_ref": { "product_id": "doc", @@ -164,6 +219,17 @@ "reviewed": true, "runtime_gate": "confirm_dangerous" }, + "drive.query_permission_apply_info": { + "effect": "read", + "risk": "low", + "confirmation": "not_required", + "idempotency": "idempotent", + "interface_mode": "composite", + "availability": "available", + "interface_reason": "Reviewed unpinned remote adapter: this executable CLI wrapper calls a remote helper that is absent from the pinned MCP metadata snapshot; no single pinned semantically equivalent interface_ref can represent the command.", + "reviewed": true, + "runtime_gate": "none" + }, "drive.recent": { "effect": "read", "risk": "low", @@ -212,28 +278,46 @@ "review_reason": "The reviewed CLI reads node metadata before mutation and strips only the exact current extension for non-folder nodes, avoiding both duplicate extensions and dotted-folder corruption without a suffix whitelist.", "cli_path": "drive rename" }, + "drive.revert_file_version": { + "effect": "write", + "risk": "medium", + "confirmation": "user_required", + "idempotency": "non_idempotent", + "interface_mode": "composite", + "availability": "available", + "interface_reason": "Reviewed unpinned remote adapter: this executable CLI wrapper calls a remote helper that is absent from the pinned MCP metadata snapshot; no single pinned semantically equivalent interface_ref can represent the command.", + "reviewed": true, + "runtime_gate": "confirm_delete" + }, "drive.search_files": { "interface_mode": "mcp", "availability": "available", "reviewed": true }, - "drive.upload": { + "drive.shortcut_copy": { "effect": "write", "risk": "medium", - "confirmation": "not_required", + "confirmation": "user_required", "idempotency": "unknown", "interface_mode": "composite", "availability": "available", - "interface_reason": "命令包含多个 RPC、条件分派或本地 HTTP/文件步骤,不能绑定为单一 interface_ref", + "interface_reason": "Reviewed built-in shortcut adapter: the executable CLI owns validation, optional multi-step orchestration, output projection, and confirmation; the complete command contract is not represented by one pinned MCP interface_ref.", "reviewed": true, - "parameters": { - "node": { - "property": "nodeId", - "required": false - } - }, - "review_reason": "The reviewed helper preserves the stable command-level Schema contract for ordinary uploads; --node switches to overwrite mode and the runtime still requires explicit confirmation unless --yes is supplied.", - "cli_path": "drive upload", + "review_reason": "Reviewed against the built-in Shortcut registry and mounted Cobra command: publishes the executable risk gate and composite interface without inventing a direct MCP identity.", + "cli_path": "drive +copy", + "runtime_gate": "typed_yes" + }, + "drive.shortcut_find_file": { + "effect": "read", + "risk": "low", + "confirmation": "not_required", + "idempotency": "idempotent", + "interface_mode": "composite", + "availability": "available", + "interface_reason": "Reviewed built-in shortcut adapter: the executable CLI owns validation, optional multi-step orchestration, output projection, and confirmation; the complete command contract is not represented by one pinned MCP interface_ref.", + "reviewed": true, + "review_reason": "Reviewed against the built-in Shortcut registry and mounted Cobra command: publishes the executable risk gate and composite interface without inventing a direct MCP identity.", + "cli_path": "drive +find-file", "runtime_gate": "none" }, "drive.shortcut_info": { @@ -249,6 +333,32 @@ "cli_path": "drive +info", "runtime_gate": "none" }, + "drive.shortcut_move": { + "effect": "write", + "risk": "medium", + "confirmation": "user_required", + "idempotency": "unknown", + "interface_mode": "composite", + "availability": "available", + "interface_reason": "Reviewed built-in shortcut adapter: the executable CLI owns validation, optional multi-step orchestration, output projection, and confirmation; the complete command contract is not represented by one pinned MCP interface_ref.", + "reviewed": true, + "review_reason": "Reviewed against the built-in Shortcut registry and mounted Cobra command: publishes the executable risk gate and composite interface without inventing a direct MCP identity.", + "cli_path": "drive +move", + "runtime_gate": "typed_yes" + }, + "drive.shortcut_recent": { + "effect": "read", + "risk": "low", + "confirmation": "not_required", + "idempotency": "idempotent", + "interface_mode": "composite", + "availability": "available", + "interface_reason": "Reviewed built-in shortcut adapter: the executable CLI owns validation, optional multi-step orchestration, output projection, and confirmation; the complete command contract is not represented by one pinned MCP interface_ref.", + "reviewed": true, + "review_reason": "Reviewed against the built-in Shortcut registry and mounted Cobra command: publishes the executable risk gate and composite interface without inventing a direct MCP identity.", + "cli_path": "drive +recent", + "runtime_gate": "none" + }, "drive.shortcut_search": { "effect": "read", "risk": "low", @@ -275,56 +385,45 @@ "cli_path": "drive +search-docs", "runtime_gate": "none" }, - "drive.shortcut_copy": { + "drive.transfer_owner": { "effect": "write", - "risk": "medium", + "risk": "high", "confirmation": "user_required", - "idempotency": "unknown", + "idempotency": "non_idempotent", "interface_mode": "composite", "availability": "available", - "interface_reason": "Reviewed built-in shortcut adapter: the executable CLI owns validation, optional multi-step orchestration, output projection, and confirmation; the complete command contract is not represented by one pinned MCP interface_ref.", + "interface_reason": "Reviewed unpinned remote adapter: this executable CLI wrapper calls a remote helper that is absent from the pinned MCP metadata snapshot; no single pinned semantically equivalent interface_ref can represent the command.", "reviewed": true, - "review_reason": "Reviewed against the built-in Shortcut registry and mounted Cobra command: publishes the executable risk gate and composite interface without inventing a direct MCP identity.", - "cli_path": "drive +copy", - "runtime_gate": "typed_yes" + "runtime_gate": "confirm_delete" }, - "drive.shortcut_move": { + "drive.unmark_star": { "effect": "write", - "risk": "medium", - "confirmation": "user_required", - "idempotency": "unknown", - "interface_mode": "composite", - "availability": "available", - "interface_reason": "Reviewed built-in shortcut adapter: the executable CLI owns validation, optional multi-step orchestration, output projection, and confirmation; the complete command contract is not represented by one pinned MCP interface_ref.", - "reviewed": true, - "review_reason": "Reviewed against the built-in Shortcut registry and mounted Cobra command: publishes the executable risk gate and composite interface without inventing a direct MCP identity.", - "cli_path": "drive +move", - "runtime_gate": "typed_yes" - }, - "drive.shortcut_recent": { - "effect": "read", "risk": "low", "confirmation": "not_required", "idempotency": "idempotent", "interface_mode": "composite", "availability": "available", - "interface_reason": "Reviewed built-in shortcut adapter: the executable CLI owns validation, optional multi-step orchestration, output projection, and confirmation; the complete command contract is not represented by one pinned MCP interface_ref.", + "interface_reason": "Reviewed unpinned remote adapter: this executable CLI wrapper calls a remote helper that is absent from the pinned MCP metadata snapshot; no single pinned semantically equivalent interface_ref can represent the command.", "reviewed": true, - "review_reason": "Reviewed against the built-in Shortcut registry and mounted Cobra command: publishes the executable risk gate and composite interface without inventing a direct MCP identity.", - "cli_path": "drive +recent", "runtime_gate": "none" }, - "drive.shortcut_find_file": { - "effect": "read", - "risk": "low", + "drive.upload": { + "effect": "write", + "risk": "medium", "confirmation": "not_required", - "idempotency": "idempotent", + "idempotency": "unknown", "interface_mode": "composite", "availability": "available", - "interface_reason": "Reviewed built-in shortcut adapter: the executable CLI owns validation, optional multi-step orchestration, output projection, and confirmation; the complete command contract is not represented by one pinned MCP interface_ref.", + "interface_reason": "命令包含多个 RPC、条件分派或本地 HTTP/文件步骤,不能绑定为单一 interface_ref", "reviewed": true, - "review_reason": "Reviewed against the built-in Shortcut registry and mounted Cobra command: publishes the executable risk gate and composite interface without inventing a direct MCP identity.", - "cli_path": "drive +find-file", + "parameters": { + "node": { + "property": "nodeId", + "required": false + } + }, + "review_reason": "The reviewed helper preserves the stable command-level Schema contract for ordinary uploads; --node switches to overwrite mode and the runtime still requires explicit confirmation unless --yes is supplied.", + "cli_path": "drive upload", "runtime_gate": "none" } } diff --git a/internal/cli/schema_hints/metadata/sheet.json b/internal/cli/schema_hints/metadata/sheet.json index 13ded011..2204b144 100644 --- a/internal/cli/schema_hints/metadata/sheet.json +++ b/internal/cli/schema_hints/metadata/sheet.json @@ -734,6 +734,105 @@ "review_reason": "Reviewed against the built-in Shortcut registry and mounted Cobra command: publishes the executable risk gate and composite interface without inventing a direct MCP identity.", "cli_path": "sheet +read", "runtime_gate": "none" + }, + "sheet.version_save": { + "effect": "write", + "risk": "low", + "confirmation": "not_required", + "idempotency": "non_idempotent", + "interface_mode": "composite", + "availability": "available", + "interface_reason": "Reviewed unpinned remote adapter: this executable CLI wrapper calls a remote helper that is absent from the pinned MCP metadata snapshot; no single pinned semantically equivalent interface_ref can represent the command.", + "reviewed": true, + "runtime_gate": "none" + }, + "sheet.version_list": { + "effect": "read", + "risk": "low", + "confirmation": "not_required", + "idempotency": "idempotent", + "interface_mode": "composite", + "availability": "available", + "interface_reason": "Reviewed unpinned remote adapter: this executable CLI wrapper calls a remote helper that is absent from the pinned MCP metadata snapshot; no single pinned semantically equivalent interface_ref can represent the command.", + "reviewed": true, + "runtime_gate": "none" + }, + "sheet.version_revert": { + "effect": "write", + "risk": "medium", + "confirmation": "user_required", + "idempotency": "non_idempotent", + "interface_mode": "composite", + "availability": "available", + "interface_reason": "Reviewed unpinned remote adapter: this executable CLI wrapper calls a remote helper that is absent from the pinned MCP metadata snapshot; no single pinned semantically equivalent interface_ref can represent the command.", + "reviewed": true, + "runtime_gate": "confirm_delete" + }, + "sheet.formula_verify": { + "effect": "read", + "risk": "low", + "confirmation": "not_required", + "idempotency": "idempotent", + "interface_mode": "composite", + "availability": "available", + "interface_reason": "Reviewed unpinned remote adapter: this executable CLI wrapper calls a remote helper that is absent from the pinned MCP metadata snapshot; no single pinned semantically equivalent interface_ref can represent the command.", + "reviewed": true, + "runtime_gate": "none" + }, + "sheet.list_sheet_comments": { + "effect": "read", + "risk": "low", + "confirmation": "not_required", + "idempotency": "idempotent", + "interface_mode": "composite", + "availability": "available", + "interface_reason": "Reviewed unpinned remote adapter: this executable CLI wrapper calls a remote helper that is absent from the pinned MCP metadata snapshot; no single pinned semantically equivalent interface_ref can represent the command.", + "reviewed": true, + "runtime_gate": "none" + }, + "sheet.create_sheet_comment": { + "effect": "write", + "risk": "low", + "confirmation": "not_required", + "idempotency": "non_idempotent", + "interface_mode": "composite", + "availability": "available", + "interface_reason": "Reviewed unpinned remote adapter: this executable CLI wrapper calls a remote helper that is absent from the pinned MCP metadata snapshot; no single pinned semantically equivalent interface_ref can represent the command.", + "reviewed": true, + "runtime_gate": "none" + }, + "sheet.reply_sheet_comment": { + "effect": "write", + "risk": "low", + "confirmation": "not_required", + "idempotency": "non_idempotent", + "interface_mode": "composite", + "availability": "available", + "interface_reason": "Reviewed unpinned remote adapter: this executable CLI wrapper calls a remote helper that is absent from the pinned MCP metadata snapshot; no single pinned semantically equivalent interface_ref can represent the command.", + "reviewed": true, + "runtime_gate": "none" + }, + "sheet.update_sheet_comment": { + "effect": "write", + "risk": "low", + "confirmation": "not_required", + "idempotency": "idempotent", + "interface_mode": "composite", + "availability": "available", + "interface_reason": "Reviewed unpinned remote adapter: this executable CLI wrapper calls a remote helper that is absent from the pinned MCP metadata snapshot; no single pinned semantically equivalent interface_ref can represent the command.", + "reviewed": true, + "runtime_gate": "none" + }, + "sheet.delete_sheet_comment": { + "effect": "write", + "risk": "medium", + "confirmation": "user_required", + "idempotency": "non_idempotent", + "interface_mode": "composite", + "availability": "available", + "interface_reason": "Reviewed unpinned remote adapter: this executable CLI wrapper calls a remote helper that is absent from the pinned MCP metadata snapshot; no single pinned semantically equivalent interface_ref can represent the command.", + "reviewed": true, + "runtime_gate": "confirm_delete" } } } diff --git a/internal/cli/schema_hints/runtime-surface-completeness.json b/internal/cli/schema_hints/runtime-surface-completeness.json index fc13201c..e80c4705 100644 --- a/internal/cli/schema_hints/runtime-surface-completeness.json +++ b/internal/cli/schema_hints/runtime-surface-completeness.json @@ -7,7 +7,7 @@ "channel": "open-source" }, "coverage": { - "source_tools": 818, + "source_tools": 845, "matched_tools": 71 }, "tools": { diff --git a/internal/cli/schema_hints/selection/chat.json b/internal/cli/schema_hints/selection/chat.json index d84a884e..d6e2eaa1 100644 --- a/internal/cli/schema_hints/selection/chat.json +++ b/internal/cli/schema_hints/selection/chat.json @@ -2497,6 +2497,87 @@ "cobra-help:dws chat +unread-chats", "ShortcutRegistry:chat +unread-chats" ] + }, + "chat.update_text_emotion": { + "agent_summary": "把消息上已有的文字表情原地替换为新的文字表情", + "use_when": [ + "需要更新消息的状态文字或表情,并避免先移除再添加造成闪烁和两次网络调用时" + ], + "avoid_when": [ + "消息上还没有文字表情时使用 chat message add-text-emotion", + "只需清除文字表情时使用 chat message remove-text-emotion" + ], + "examples": [ + "dws chat message update-text-emotion --conversation-id --msg-id --old-emotion-id --emotion-id --emotion-name \"处理中\" --text \"处理中 2 分钟\" --background-id im_bg_5" + ], + "reviewed": true, + "review_reason": "依据 issue #85 的原地状态更新场景、Wukong 30f13f02 的真实 Cobra/MCP 映射和开源实现复核;选择语义不改变执行、安全或参数事实。", + "source_refs": [ + "internal/cli/schema_command_registry/products/chat.json#chat.update_text_emotion", + "cobra-help:dws chat message update-text-emotion", + "dws-wukong:d6831318", + "dws-wukong:bcf56ccf", + "dws-wukong:30f13f02", + "github:hugozhu/dingtalk-opencode-tag#85", + "live-dws-schema:im.update_text_emotion#FAILED_NOT_AUTHENTICATED" + ] + }, + "chat.get_group_mute_config": { + "agent_summary": "查询群用户禁言配置(禁言黑名单/全员禁言白名单)", + "use_when": [ + "用户说 看下群里谁被禁言/禁言配置" + ], + "avoid_when": [ + "设置全员禁言用 chat group-mute;禁言个人用 chat group-mute-member" + ], + "examples": [ + "dws chat group get-mute-config --group --format json" + ], + "reviewed": true, + "review_reason": "人工审阅:对照悟空 develop 同名命令实现、Cobra Long/Example 与 Runtime 确认门禁移植;不改变命令身份、参数契约或接口绑定。", + "source_refs": [ + "CommandRegistry:canonical_path=chat.get_group_mute_config", + "cobra-help:dws chat group get-mute-config", + "wukong-develop:wukong/products" + ] + }, + "chat.batch_query_group_chat_settings": { + "agent_summary": "批量查询当前用户自己的群会话设置(置顶/免打扰/群昵称/群备注)", + "use_when": [ + "用户说 看下这些群我的置顶和免打扰设置" + ], + "avoid_when": [ + "管理员级群功能开关用 chat group update-settings" + ], + "examples": [ + "dws chat group user-settings query --groups cid1,cid2 --format json" + ], + "reviewed": true, + "review_reason": "人工审阅:对照悟空 develop 同名命令实现、Cobra Long/Example 与 Runtime 确认门禁移植;不改变命令身份、参数契约或接口绑定。", + "source_refs": [ + "CommandRegistry:canonical_path=chat.batch_query_group_chat_settings", + "cobra-help:dws chat group user-settings query", + "wukong-develop:wukong/products" + ] + }, + "chat.batch_update_group_chat_settings": { + "agent_summary": "批量更新当前用户自己的群会话设置(置顶/免打扰/群昵称/群备注)", + "use_when": [ + "用户说 把这些群都设为免打扰/置顶" + ], + "avoid_when": [ + "单个群昵称优先 chat group update-nick" + ], + "examples": [ + "dws chat group user-settings set --items '[{\"openConversationId\":\"cid1\",\"top\":true,\"mute\":false}]' --format json" + ], + "reviewed": true, + "review_reason": "人工审阅:对照悟空 develop 同名命令实现、Cobra Long/Example 与 Runtime 确认门禁移植;不改变命令身份、参数契约或接口绑定。", + "source_refs": [ + "CommandRegistry:canonical_path=chat.batch_update_group_chat_settings", + "cobra-help:dws chat group user-settings set", + "wukong-develop:wukong/products" + ] } }, "products": { diff --git a/internal/cli/schema_hints/selection/doc.json b/internal/cli/schema_hints/selection/doc.json index 2114cfc5..de160fe6 100644 --- a/internal/cli/schema_hints/selection/doc.json +++ b/internal/cli/schema_hints/selection/doc.json @@ -1226,6 +1226,101 @@ "cobra-help:dws doc +share-doc", "ShortcutRegistry:doc +share-doc" ] + }, + "doc.style_cover_set": { + "agent_summary": "设置钉钉文档顶部封面图(外链或本地图片上传)", + "use_when": [ + "用户说 给文档设置封面/换个封面图" + ], + "avoid_when": [ + "读取当前封面用 doc style get;移除封面用 cover clear" + ], + "examples": [ + "dws doc style cover set --node --image https://img.example.com/cover.png --format json" + ], + "reviewed": true, + "review_reason": "人工审阅:对照悟空 develop 同名命令实现、Cobra Long/Example 与 Runtime 确认门禁移植;不改变命令身份、参数契约或接口绑定。", + "source_refs": [ + "CommandRegistry:canonical_path=doc.style_cover_set", + "cobra-help:dws doc style cover set", + "wukong-develop:wukong/products" + ] + }, + "doc.style_cover_clear": { + "agent_summary": "移除钉钉文档封面", + "use_when": [ + "用户说 去掉文档封面" + ], + "avoid_when": [ + "设置封面用 cover set" + ], + "examples": [ + "dws doc style cover clear --node --format json" + ], + "reviewed": true, + "review_reason": "人工审阅:对照悟空 develop 同名命令实现、Cobra Long/Example 与 Runtime 确认门禁移植;不改变命令身份、参数契约或接口绑定。", + "source_refs": [ + "CommandRegistry:canonical_path=doc.style_cover_clear", + "cobra-help:dws doc style cover clear", + "wukong-develop:wukong/products" + ] + }, + "doc.style_background_set": { + "agent_summary": "设置钉钉文档背景纯色(#RRGGBB)", + "use_when": [ + "用户说 给文档设置背景色" + ], + "avoid_when": [ + "背景不支持图片;封面用 cover set" + ], + "examples": [ + "dws doc style background set --node --color \"#E8F2FE\" --format json" + ], + "reviewed": true, + "review_reason": "人工审阅:对照悟空 develop 同名命令实现、Cobra Long/Example 与 Runtime 确认门禁移植;不改变命令身份、参数契约或接口绑定。", + "source_refs": [ + "CommandRegistry:canonical_path=doc.style_background_set", + "cobra-help:dws doc style background set", + "wukong-develop:wukong/products" + ] + }, + "doc.style_background_clear": { + "agent_summary": "清除钉钉文档背景", + "use_when": [ + "用户说 去掉文档背景色" + ], + "avoid_when": [ + "设置背景用 background set" + ], + "examples": [ + "dws doc style background clear --node --format json" + ], + "reviewed": true, + "review_reason": "人工审阅:对照悟空 develop 同名命令实现、Cobra Long/Example 与 Runtime 确认门禁移植;不改变命令身份、参数契约或接口绑定。", + "source_refs": [ + "CommandRegistry:canonical_path=doc.style_background_clear", + "cobra-help:dws doc style background clear", + "wukong-develop:wukong/products" + ] + }, + "doc.get_document_style": { + "agent_summary": "读取文档当前封面与背景配置(只读)", + "use_when": [ + "用户说 看下文档现在的封面/背景" + ], + "avoid_when": [ + "修改用 cover set / background set" + ], + "examples": [ + "dws doc style get --node --format json" + ], + "reviewed": true, + "review_reason": "人工审阅:对照悟空 develop 同名命令实现、Cobra Long/Example 与 Runtime 确认门禁移植;不改变命令身份、参数契约或接口绑定。", + "source_refs": [ + "CommandRegistry:canonical_path=doc.get_document_style", + "cobra-help:dws doc style get", + "wukong-develop:wukong/products" + ] } }, "products": { diff --git a/internal/cli/schema_hints/selection/drive.json b/internal/cli/schema_hints/selection/drive.json index 333eadbb..f34254e6 100644 --- a/internal/cli/schema_hints/selection/drive.json +++ b/internal/cli/schema_hints/selection/drive.json @@ -33,6 +33,25 @@ "live-mcp:dws schema doc.add_permission" ] }, + "drive.apply_permission": { + "agent_summary": "向审批人发起文档权限申请(会真实通知审批人)", + "use_when": [ + "用户确认要为无权限文档发起权限申请" + ], + "avoid_when": [ + "先用 apply-info 查可申请角色与审批人;未经用户确认不得自行提交" + ], + "examples": [ + "dws drive permission apply --node --role READER --users <审批人userId> --format json" + ], + "reviewed": true, + "review_reason": "人工审阅:对照悟空 develop 同名命令实现、Cobra Long/Example 与 Runtime 确认门禁移植;不改变命令身份、参数契约或接口绑定。", + "source_refs": [ + "CommandRegistry:canonical_path=drive.apply_permission", + "cobra-help:dws drive permission apply", + "wukong-develop:wukong/products" + ] + }, "drive.commit_upload": { "agent_summary": "提交文件上传", "use_when": [ @@ -187,6 +206,47 @@ "live-mcp:dws schema drive.download_file" ] }, + "drive.download_file_version": { + "agent_summary": "下载钉盘普通文件的指定历史版本到本地(两步下载:取签名 URL 后 HTTP GET)", + "use_when": [ + "用户要下载文件的历史版本/旧版本", + "版本号已通过 drive list --versions 获取" + ], + "avoid_when": [ + "下载最新版本用 drive download", + "在线文档(adoc)历史版本用 doc version 系列命令", + "在线表格(axls)历史版本用 sheet version 系列命令" + ], + "examples": [ + "dws drive download-version --node --version 3 --output ./report_v3.pdf" + ], + "reviewed": true, + "review_reason": "人工审阅:对照悟空 develop drive download --version 分支实现移植为独立命令;单一 download_file_version 远端调用,参数契约与帮助文档一致。", + "source_refs": [ + "CommandRegistry:canonical_path=drive.download_file_version", + "cobra-help:dws drive download-version", + "wukong-develop:wukong/products/drive.go" + ] + }, + "drive.get_cover": { + "agent_summary": "获取节点封面图片地址(文档首图/图片缩略图/类型图标)", + "use_when": [ + "用户说 封面/封面图/缩略图/预览图" + ], + "avoid_when": [ + "设置文档封面用 doc style cover set" + ], + "examples": [ + "dws drive cover --node --format json" + ], + "reviewed": true, + "review_reason": "人工审阅:对照悟空 develop 同名命令实现、Cobra Long/Example 与 Runtime 确认门禁移植;不改变命令身份、参数契约或接口绑定。", + "source_refs": [ + "CommandRegistry:canonical_path=drive.get_cover", + "cobra-help:dws drive cover", + "wukong-develop:wukong/products" + ] + }, "drive.get_file_info": { "agent_summary": "获取文件元数据信息", "use_when": [ @@ -235,6 +295,26 @@ "live-mcp:none (Cobra+Skill; composite/unpinned)" ] }, + "drive.get_star_list": { + "agent_summary": "获取当前用户的收藏列表,支持分页与按内容类型筛选", + "use_when": [ + "用户说 我的收藏/收藏列表/收藏了哪些文档" + ], + "avoid_when": [ + "操作单个收藏用 mark_star/unmark_star" + ], + "examples": [ + "dws drive star list --format json", + "dws drive star list --content-types doc,sheet --limit 10 --format json" + ], + "reviewed": true, + "review_reason": "人工审阅:对照悟空 develop 同名命令实现、Cobra Long/Example 与 Runtime 确认门禁移植;不改变命令身份、参数契约或接口绑定。", + "source_refs": [ + "CommandRegistry:canonical_path=drive.get_star_list", + "cobra-help:dws drive star list", + "wukong-develop:wukong/products" + ] + }, "drive.get_upload_info": { "agent_summary": "获取文件上传信息", "use_when": [ @@ -334,6 +414,25 @@ "live-mcp:dws schema drive.list_spaces" ] }, + "drive.mark_star": { + "agent_summary": "收藏文档/文件到当前用户收藏列表", + "use_when": [ + "用户说 收藏这个文档/加个收藏/标星" + ], + "avoid_when": [ + "取消收藏用 unmark_star;查看收藏列表用 get_star_list" + ], + "examples": [ + "dws drive star add --node --format json" + ], + "reviewed": true, + "review_reason": "人工审阅:对照悟空 develop 同名命令实现、Cobra Long/Example 与 Runtime 确认门禁移植;不改变命令身份、参数契约或接口绑定。", + "source_refs": [ + "CommandRegistry:canonical_path=drive.mark_star", + "cobra-help:dws drive star add", + "wukong-develop:wukong/products" + ] + }, "drive.move_document": { "agent_summary": "将文件或文档移动到目标文件夹或知识库(原位置不再保留)", "use_when": [ @@ -475,6 +574,25 @@ "live-mcp:none (Cobra+Skill; composite/unpinned)" ] }, + "drive.query_permission_apply_info": { + "agent_summary": "查询节点可申请的权限角色列表与审批人列表", + "use_when": [ + "无权限访问文档时,先查可申请角色与审批人" + ], + "avoid_when": [ + "实际发起申请用 apply_permission" + ], + "examples": [ + "dws drive permission apply-info --node --format json" + ], + "reviewed": true, + "review_reason": "人工审阅:对照悟空 develop 同名命令实现、Cobra Long/Example 与 Runtime 确认门禁移植;不改变命令身份、参数契约或接口绑定。", + "source_refs": [ + "CommandRegistry:canonical_path=drive.query_permission_apply_info", + "cobra-help:dws drive permission apply-info", + "wukong-develop:wukong/products" + ] + }, "drive.recent": { "agent_summary": "获取当前用户最近访问或编辑过的文档列表", "use_when": [ @@ -568,6 +686,25 @@ "live-mcp:dws schema doc.rename_document" ] }, + "drive.revert_file_version": { + "agent_summary": "回滚普通文件到指定历史版本(生成新最新版本,历史不丢失)", + "use_when": [ + "用户说 回滚版本/恢复到某个版本/版本回退,且目标是普通文件" + ], + "avoid_when": [ + "在线文档回滚用 doc version revert;在线表格用 sheet version revert" + ], + "examples": [ + "dws drive revert --node --version 3 --format json" + ], + "reviewed": true, + "review_reason": "人工审阅:对照悟空 develop 同名命令实现、Cobra Long/Example 与 Runtime 确认门禁移植;不改变命令身份、参数契约或接口绑定。", + "source_refs": [ + "CommandRegistry:canonical_path=drive.revert_file_version", + "cobra-help:dws drive revert", + "wukong-develop:wukong/products" + ] + }, "drive.search_files": { "agent_summary": "全局搜索文件,默认同时搜索钉盘和文档空间,合并返回结果", "use_when": [ @@ -595,32 +732,43 @@ "live-mcp:dws schema drive.search_files" ] }, - "drive.upload": { - "agent_summary": "上传本地文件到钉盘或文档空间,或按节点 ID 确认覆盖已有文件", + "drive.shortcut_copy": { + "agent_summary": "复制文件/文档到指定位置", "use_when": [ - "用户要把本地文件上传到钉盘/我的文件(首选一条命令自动完成凭证+PUT+提交)时", - "上传到知识库/文档空间时加 --workspace;需要转在线文档时加 --convert", - "用户明确要求用本地文件替换已有钉盘/文档空间文件时传 --node;该模式会覆盖远端内容并要求确认" + "当你想保留原件、把某个文件/文档拷贝一份到指定文件夹或知识库时使用;输入源节点 node 及目标 folder/workspace,会实际生成一个副本,原文件位置不变。" ], "avoid_when": [ - "常规场景不要拆成 upload-info + 手动 PUT + commit;仅自定义流式上传才用三步", - "要把文件作为文档正文附件插入改用 dws doc media insert", - "用户明确说文档空间且走 doc 兼容入口时可用 dws doc upload,默认仍推荐本命令", - "用户没有明确同意替换目标文件时不要使用 --node;新建上传应使用 --folder 或目标根目录" + "需要该 Shortcut 未公开的底层参数、原始响应或不同执行语义时,改用对应原子命令" ], "examples": [ - "dws drive upload --file ./report.pdf --format json", - "dws drive upload --file ./README.md --node --format json" + "dws drive +copy --node --folder " ], "reviewed": true, - "review_reason": "人工审阅:结合本机 dws schema 实时 MCP description/parameters(经 interface_ref)、产品 Skill、Cobra Long 与 Runtime 确认门禁,按飞书风格重写选型文案;不改变命令身份、参数契约或接口绑定。", + "review_reason": "Agent-authored and reviewed from the built-in Shortcut intent, executable Cobra path, and declared outcome; it affects selection only and does not alter execution or safety facts.", "source_refs": [ - "CommandRegistry:canonical_path=drive.upload", - "cobra-help:dws drive upload", - "Skill:skills/mono/references/products/drive.md", - "structured-hint:internal/cli/schema_hints/selection-review.json#drive.upload", - "structured-hint:internal/cli/schema_hints/products/drive.json", - "live-mcp:none (Cobra+Skill; composite/unpinned)" + "internal/cli/schema_command_registry.json#drive.shortcut_copy", + "cobra-help:dws drive +copy", + "ShortcutRegistry:drive +copy" + ] + }, + "drive.shortcut_find_file": { + "agent_summary": "按名称关键词搜索钉盘文件并投影关键字段(只读)", + "use_when": [ + "当你只记得钉盘文件的名字(或其中一部分),想快速按文件名关键词找到它、拿到它的 dentryId 以便后续下载/查看,却不想手动翻目录或写复杂过滤条件时使用;内部调用钉盘的 search_files 工具,把 --query 作为文件名关键词(keyword) 并限定搜索范围为钉盘文件(searchTarget=file),再在本地把每条命中结果精简为「文件名、类型、dentryId、大小」四个字段后打印。这是纯只读操作,只做搜索与本地投影,不会创建、移动或删除任何文件;未命中时返回空列表。" + ], + "avoid_when": [ + "需要该 Shortcut 未公开的底层参数、原始响应或不同执行语义时,改用对应原子命令" + ], + "examples": [ + "dws drive +find-file --query 季度汇报", + "dws drive +find-file --query 合同" + ], + "reviewed": true, + "review_reason": "Agent-authored and reviewed from the built-in Shortcut intent, executable Cobra path, and declared outcome; it affects selection only and does not alter execution or safety facts.", + "source_refs": [ + "internal/cli/schema_command_registry.json#drive.shortcut_find_file", + "cobra-help:dws drive +find-file", + "ShortcutRegistry:drive +find-file" ] }, "drive.shortcut_info": { @@ -642,64 +790,6 @@ "ShortcutRegistry:drive +info" ] }, - "drive.shortcut_search": { - "agent_summary": "搜索钉盘文件", - "use_when": [ - "当你只记得文件名或内容关键词、不知道它在哪个目录时用它全局检索钉盘文件;输入 query,可按文件类型、扩展名、创建者、创建/修改时间范围过滤,返回匹配文件及其 ID,便于再做下载或整理。" - ], - "avoid_when": [ - "需要该 Shortcut 未公开的底层参数、原始响应或不同执行语义时,改用对应原子命令" - ], - "examples": [ - "dws drive +search --query \"季度汇报\"", - "dws drive +search --query \"合同\" --target file --extensions pdf,docx" - ], - "reviewed": true, - "review_reason": "Agent-authored and reviewed from the built-in Shortcut intent, executable Cobra path, and declared outcome; it affects selection only and does not alter execution or safety facts.", - "source_refs": [ - "internal/cli/schema_command_registry.json#drive.shortcut_search", - "cobra-help:dws drive +search", - "ShortcutRegistry:drive +search" - ] - }, - "drive.shortcut_search_docs": { - "agent_summary": "搜索文档空间文档", - "use_when": [ - "当你只记得文档标题或关键词、想在文档空间/知识库中检索在线文档(区别于 +search 检索钉盘文件)时使用;输入 query 关键词,返回匹配的文档及其节点信息。" - ], - "avoid_when": [ - "需要该 Shortcut 未公开的底层参数、原始响应或不同执行语义时,改用对应原子命令" - ], - "examples": [ - "dws drive +search-docs --query \"季度汇报\"" - ], - "reviewed": true, - "review_reason": "Agent-authored and reviewed from the built-in Shortcut intent, executable Cobra path, and declared outcome; it affects selection only and does not alter execution or safety facts.", - "source_refs": [ - "internal/cli/schema_command_registry.json#drive.shortcut_search_docs", - "cobra-help:dws drive +search-docs", - "ShortcutRegistry:drive +search-docs" - ] - }, - "drive.shortcut_copy": { - "agent_summary": "复制文件/文档到指定位置", - "use_when": [ - "当你想保留原件、把某个文件/文档拷贝一份到指定文件夹或知识库时使用;输入源节点 node 及目标 folder/workspace,会实际生成一个副本,原文件位置不变。" - ], - "avoid_when": [ - "需要该 Shortcut 未公开的底层参数、原始响应或不同执行语义时,改用对应原子命令" - ], - "examples": [ - "dws drive +copy --node --folder " - ], - "reviewed": true, - "review_reason": "Agent-authored and reviewed from the built-in Shortcut intent, executable Cobra path, and declared outcome; it affects selection only and does not alter execution or safety facts.", - "source_refs": [ - "internal/cli/schema_command_registry.json#drive.shortcut_copy", - "cobra-help:dws drive +copy", - "ShortcutRegistry:drive +copy" - ] - }, "drive.shortcut_move": { "agent_summary": "移动文件/文档到指定位置", "use_when": [ @@ -739,24 +829,109 @@ "ShortcutRegistry:drive +recent" ] }, - "drive.shortcut_find_file": { - "agent_summary": "按名称关键词搜索钉盘文件并投影关键字段(只读)", + "drive.shortcut_search": { + "agent_summary": "搜索钉盘文件", "use_when": [ - "当你只记得钉盘文件的名字(或其中一部分),想快速按文件名关键词找到它、拿到它的 dentryId 以便后续下载/查看,却不想手动翻目录或写复杂过滤条件时使用;内部调用钉盘的 search_files 工具,把 --query 作为文件名关键词(keyword) 并限定搜索范围为钉盘文件(searchTarget=file),再在本地把每条命中结果精简为「文件名、类型、dentryId、大小」四个字段后打印。这是纯只读操作,只做搜索与本地投影,不会创建、移动或删除任何文件;未命中时返回空列表。" + "当你只记得文件名或内容关键词、不知道它在哪个目录时用它全局检索钉盘文件;输入 query,可按文件类型、扩展名、创建者、创建/修改时间范围过滤,返回匹配文件及其 ID,便于再做下载或整理。" ], "avoid_when": [ "需要该 Shortcut 未公开的底层参数、原始响应或不同执行语义时,改用对应原子命令" ], "examples": [ - "dws drive +find-file --query 季度汇报", - "dws drive +find-file --query 合同" + "dws drive +search --query \"季度汇报\"", + "dws drive +search --query \"合同\" --target file --extensions pdf,docx" ], "reviewed": true, "review_reason": "Agent-authored and reviewed from the built-in Shortcut intent, executable Cobra path, and declared outcome; it affects selection only and does not alter execution or safety facts.", "source_refs": [ - "internal/cli/schema_command_registry.json#drive.shortcut_find_file", - "cobra-help:dws drive +find-file", - "ShortcutRegistry:drive +find-file" + "internal/cli/schema_command_registry.json#drive.shortcut_search", + "cobra-help:dws drive +search", + "ShortcutRegistry:drive +search" + ] + }, + "drive.shortcut_search_docs": { + "agent_summary": "搜索文档空间文档", + "use_when": [ + "当你只记得文档标题或关键词、想在文档空间/知识库中检索在线文档(区别于 +search 检索钉盘文件)时使用;输入 query 关键词,返回匹配的文档及其节点信息。" + ], + "avoid_when": [ + "需要该 Shortcut 未公开的底层参数、原始响应或不同执行语义时,改用对应原子命令" + ], + "examples": [ + "dws drive +search-docs --query \"季度汇报\"" + ], + "reviewed": true, + "review_reason": "Agent-authored and reviewed from the built-in Shortcut intent, executable Cobra path, and declared outcome; it affects selection only and does not alter execution or safety facts.", + "source_refs": [ + "internal/cli/schema_command_registry.json#drive.shortcut_search_docs", + "cobra-help:dws drive +search-docs", + "ShortcutRegistry:drive +search-docs" + ] + }, + "drive.transfer_owner": { + "agent_summary": "转交文档或知识库所有者给指定用户(不可逆)", + "use_when": [ + "用户明确要求转交文档/知识库所有权" + ], + "avoid_when": [ + "普通协作权限变更用 permission add/update/remove" + ], + "examples": [ + "dws drive permission transfer-owner --node --new-owner --reserve-role EDITOR --recursive=false --format json" + ], + "reviewed": true, + "review_reason": "人工审阅:对照悟空 develop 同名命令实现、Cobra Long/Example 与 Runtime 确认门禁移植;不改变命令身份、参数契约或接口绑定。", + "source_refs": [ + "CommandRegistry:canonical_path=drive.transfer_owner", + "cobra-help:dws drive permission transfer-owner", + "wukong-develop:wukong/products" + ] + }, + "drive.unmark_star": { + "agent_summary": "将文档/文件从当前用户收藏列表移除", + "use_when": [ + "用户说 取消收藏/去掉收藏/不收藏了" + ], + "avoid_when": [ + "添加收藏用 mark_star" + ], + "examples": [ + "dws drive star remove --node --format json" + ], + "reviewed": true, + "review_reason": "人工审阅:对照悟空 develop 同名命令实现、Cobra Long/Example 与 Runtime 确认门禁移植;不改变命令身份、参数契约或接口绑定。", + "source_refs": [ + "CommandRegistry:canonical_path=drive.unmark_star", + "cobra-help:dws drive star remove", + "wukong-develop:wukong/products" + ] + }, + "drive.upload": { + "agent_summary": "上传本地文件到钉盘或文档空间,或按节点 ID 确认覆盖已有文件", + "use_when": [ + "用户要把本地文件上传到钉盘/我的文件(首选一条命令自动完成凭证+PUT+提交)时", + "上传到知识库/文档空间时加 --workspace;需要转在线文档时加 --convert", + "用户明确要求用本地文件替换已有钉盘/文档空间文件时传 --node;该模式会覆盖远端内容并要求确认" + ], + "avoid_when": [ + "常规场景不要拆成 upload-info + 手动 PUT + commit;仅自定义流式上传才用三步", + "要把文件作为文档正文附件插入改用 dws doc media insert", + "用户明确说文档空间且走 doc 兼容入口时可用 dws doc upload,默认仍推荐本命令", + "用户没有明确同意替换目标文件时不要使用 --node;新建上传应使用 --folder 或目标根目录" + ], + "examples": [ + "dws drive upload --file ./report.pdf --format json", + "dws drive upload --file ./README.md --node --format json" + ], + "reviewed": true, + "review_reason": "人工审阅:结合本机 dws schema 实时 MCP description/parameters(经 interface_ref)、产品 Skill、Cobra Long 与 Runtime 确认门禁,按飞书风格重写选型文案;不改变命令身份、参数契约或接口绑定。", + "source_refs": [ + "CommandRegistry:canonical_path=drive.upload", + "cobra-help:dws drive upload", + "Skill:skills/mono/references/products/drive.md", + "structured-hint:internal/cli/schema_hints/selection-review.json#drive.upload", + "structured-hint:internal/cli/schema_hints/products/drive.json", + "live-mcp:none (Cobra+Skill; composite/unpinned)" ] } }, diff --git a/internal/cli/schema_hints/selection/sheet.json b/internal/cli/schema_hints/selection/sheet.json index 1c063a6e..4db775f7 100644 --- a/internal/cli/schema_hints/selection/sheet.json +++ b/internal/cli/schema_hints/selection/sheet.json @@ -1629,6 +1629,177 @@ "cobra-help:dws sheet +read", "ShortcutRegistry:sheet +read" ] + }, + "sheet.version_save": { + "agent_summary": "手动保存表格版本快照", + "use_when": [ + "用户说 保存版本/存个快照,目标是在线表格" + ], + "avoid_when": [ + "回滚用 version revert;查看历史用 version list" + ], + "examples": [ + "dws sheet version save --node --format json" + ], + "reviewed": true, + "review_reason": "人工审阅:对照悟空 develop 同名命令实现、Cobra Long/Example 与 Runtime 确认门禁移植;不改变命令身份、参数契约或接口绑定。", + "source_refs": [ + "CommandRegistry:canonical_path=sheet.version_save", + "cobra-help:dws sheet version save", + "wukong-develop:wukong/products" + ] + }, + "sheet.version_list": { + "agent_summary": "查看表格历史版本列表", + "use_when": [ + "用户说 看历史版本/版本列表,目标是在线表格" + ], + "avoid_when": [ + "回滚用 version revert" + ], + "examples": [ + "dws sheet version list --node --limit 10 --format json" + ], + "reviewed": true, + "review_reason": "人工审阅:对照悟空 develop 同名命令实现、Cobra Long/Example 与 Runtime 确认门禁移植;不改变命令身份、参数契约或接口绑定。", + "source_refs": [ + "CommandRegistry:canonical_path=sheet.version_list", + "cobra-help:dws sheet version list", + "wukong-develop:wukong/products" + ] + }, + "sheet.version_revert": { + "agent_summary": "回滚表格到指定历史版本", + "use_when": [ + "用户说 回滚到某个版本/恢复到之前的表格" + ], + "avoid_when": [ + "普通文件回滚用 drive revert;在线文档用 doc version revert" + ], + "examples": [ + "dws sheet version revert --node --version 3 --format json" + ], + "reviewed": true, + "review_reason": "人工审阅:对照悟空 develop 同名命令实现、Cobra Long/Example 与 Runtime 确认门禁移植;不改变命令身份、参数契约或接口绑定。", + "source_refs": [ + "CommandRegistry:canonical_path=sheet.version_revert", + "cobra-help:dws sheet version revert", + "wukong-develop:wukong/products" + ] + }, + "sheet.formula_verify": { + "agent_summary": "扫描表格公式单元格并按错误类型聚合返回错误数量与位置", + "use_when": [ + "用户说 校验公式/检查公式错误/公式错误扫描" + ], + "avoid_when": [ + "读取公式文本用 range read --value-render-option formula" + ], + "examples": [ + "dws sheet formula-verify --node --format json" + ], + "reviewed": true, + "review_reason": "人工审阅:对照悟空 develop 同名命令实现、Cobra Long/Example 与 Runtime 确认门禁移植;不改变命令身份、参数契约或接口绑定。", + "source_refs": [ + "CommandRegistry:canonical_path=sheet.formula_verify", + "cobra-help:dws sheet formula-verify", + "wukong-develop:wukong/products" + ] + }, + "sheet.list_sheet_comments": { + "agent_summary": "查询表格单元格评论列表,支持分页与按解决状态过滤", + "use_when": [ + "用户说 看某格的评论/表格里有哪些批注" + ], + "avoid_when": [ + "新建评论用 comment create" + ], + "examples": [ + "dws sheet comment list --node --format json" + ], + "reviewed": true, + "review_reason": "人工审阅:对照悟空 develop 同名命令实现、Cobra Long/Example 与 Runtime 确认门禁移植;不改变命令身份、参数契约或接口绑定。", + "source_refs": [ + "CommandRegistry:canonical_path=sheet.list_sheet_comments", + "cobra-help:dws sheet comment list", + "wukong-develop:wukong/products" + ] + }, + "sheet.create_sheet_comment": { + "agent_summary": "在指定单元格上创建评论,可 @ 用户", + "use_when": [ + "用户说 给单元格加评论/批注/@某人讨论这个数据" + ], + "avoid_when": [ + "回复已有评论用 comment reply;不要把评论写进单元格值" + ], + "examples": [ + "dws sheet comment create --node --sheet-id Sheet1 --range A2 --content \"这个数字有问题\" --format json" + ], + "reviewed": true, + "review_reason": "人工审阅:对照悟空 develop 同名命令实现、Cobra Long/Example 与 Runtime 确认门禁移植;不改变命令身份、参数契约或接口绑定。", + "source_refs": [ + "CommandRegistry:canonical_path=sheet.create_sheet_comment", + "cobra-help:dws sheet comment create", + "wukong-develop:wukong/products" + ] + }, + "sheet.reply_sheet_comment": { + "agent_summary": "回复指定单元格评论,支持表情贴图回复", + "use_when": [ + "用户说 回复这条评论" + ], + "avoid_when": [ + "修改评论内容用 comment update" + ], + "examples": [ + "dws sheet comment reply --node --comment-key --content \"已核实\" --format json" + ], + "reviewed": true, + "review_reason": "人工审阅:对照悟空 develop 同名命令实现、Cobra Long/Example 与 Runtime 确认门禁移植;不改变命令身份、参数契约或接口绑定。", + "source_refs": [ + "CommandRegistry:canonical_path=sheet.reply_sheet_comment", + "cobra-help:dws sheet comment reply", + "wukong-develop:wukong/products" + ] + }, + "sheet.update_sheet_comment": { + "agent_summary": "更新单元格评论内容", + "use_when": [ + "用户说 改一下这条评论" + ], + "avoid_when": [ + "回复评论用 comment reply" + ], + "examples": [ + "dws sheet comment update --node --comment-key --content \"已修正\" --format json" + ], + "reviewed": true, + "review_reason": "人工审阅:对照悟空 develop 同名命令实现、Cobra Long/Example 与 Runtime 确认门禁移植;不改变命令身份、参数契约或接口绑定。", + "source_refs": [ + "CommandRegistry:canonical_path=sheet.update_sheet_comment", + "cobra-help:dws sheet comment update", + "wukong-develop:wukong/products" + ] + }, + "sheet.delete_sheet_comment": { + "agent_summary": "删除单元格评论(不可恢复)", + "use_when": [ + "用户明确要求删除某条评论" + ], + "avoid_when": [ + "标记解决不等于删除" + ], + "examples": [ + "dws sheet comment delete --node --comment-key --format json" + ], + "reviewed": true, + "review_reason": "人工审阅:对照悟空 develop 同名命令实现、Cobra Long/Example 与 Runtime 确认门禁移植;不改变命令身份、参数契约或接口绑定。", + "source_refs": [ + "CommandRegistry:canonical_path=sheet.delete_sheet_comment", + "cobra-help:dws sheet comment delete", + "wukong-develop:wukong/products" + ] } }, "products": { diff --git a/internal/cli/schema_parameter_bindings.json b/internal/cli/schema_parameter_bindings.json index da4c740a..2b0be894 100644 --- a/internal/cli/schema_parameter_bindings.json +++ b/internal/cli/schema_parameter_bindings.json @@ -2,8 +2,8 @@ "version": 3, "baseline": { "manifest": "schema-parameter-bindings-v3", - "sha256": "sha256:f48d7b55b6ca9719c466475ee32d678eab0deaee1d79ac4e4790c7ee441ebe7b", - "reason": "Reviewed v3 baseline after exposing the pinned chat.search_messages messageType, onlyRobotMessages, and searchConvType properties through exact Cobra flags.", + "sha256": "sha256:ff0f76145dd27da5429434a348344c9cdf74c44eaf6f4ea5411d29d904a9066f", + "reason": "Reviewed v3 baseline after binding sheet.info --include to the include property while porting the wukong sheet info expansion flag.", "reviewed": true }, "removals": { @@ -43,6 +43,10 @@ "reason": "The public pagination flag was normalized from --size to --limit.", "replaced_by": "oa.list_user_visible_process --limit", "reviewed": true + }, + "drive.download_file --version": { + "reason": "Polymorphic dispatch: --version switches the MCP tool call from download_file to download_file_version; the version property belongs to download_file_version metadata, not download_file.", + "reviewed": true } }, "bindings": { @@ -1108,7 +1112,8 @@ "node": "nodeId" }, "sheet.info": { - "node": "nodeId" + "node": "nodeId", + "include": "include" }, "sheet.insert_dimension": { "node": "nodeId" @@ -1590,7 +1595,10 @@ "attendance.vacation_update_type --when-can-leave": "Reviewed unpinned adapter: attendance.vacation_update_type has no singular pinned interface_ref; --when-can-leave is a CLI wrapper input and does not publish a direct interface property.", "chat.add_message_favorite --open-conversation-id": "Reviewed unpinned adapter: chat.add_message_favorite has no singular pinned interface_ref; --open-conversation-id is a CLI wrapper input and does not publish a direct interface property.", "chat.add_message_favorite --open-message-id": "Reviewed unpinned adapter: chat.add_message_favorite has no singular pinned interface_ref; --open-message-id is a CLI wrapper input and does not publish a direct interface property.", + "chat.batch_query_group_chat_settings --groups": "Reviewed unpinned adapter: chat.batch_query_group_chat_settings has no singular pinned interface_ref; --groups is a CLI wrapper input and does not publish a direct interface property.", + "chat.batch_update_group_chat_settings --items": "Reviewed unpinned adapter: chat.batch_update_group_chat_settings has no singular pinned interface_ref; --items is a CLI wrapper input and does not publish a direct interface property.", "chat.create_text_emotion --background-id": "Runtime extension: the executable helper forwards backgroundId to im/create_text_emotion, but the pinned source-revision metadata does not declare that optional property; preserve the compatibility flag without advertising it as a pinned RPC field.", + "chat.get_group_mute_config --group": "Reviewed unpinned adapter: chat.get_group_mute_config has no singular pinned interface_ref; --group is a CLI wrapper input and does not publish a direct interface property.", "chat.list_conversation_message_v2 --open-dingtalk-id": "selects the alternate list_individual_chat_message branch", "chat.list_conversation_message_v2 --user": "selects the alternate list_individual_chat_message branch", "chat.list_message_favorites --cursor": "Reviewed unpinned adapter: chat.list_message_favorites has no singular pinned interface_ref; --cursor is a CLI wrapper input and does not publish a direct interface property.", @@ -1607,7 +1615,12 @@ "chat.search_groups --limit": "Reviewed unpinned adapter: chat.search_groups has no singular pinned interface_ref; --limit is a CLI wrapper input and does not publish a direct interface property.", "chat.search_messages --user": "conditional wrapper: parseCSVValues + appendChatIDArgs routes each supplied identifier to senderUserIds or senderOpenDingTakIds according to its runtime ID shape; there is no single RPC property for this flag", "chat.search_messages --users": "conditional wrapper/alias of --user: parseCSVValues + appendChatIDArgs routes each supplied identifier to senderUserIds or senderOpenDingTakIds according to its runtime ID shape; there is no single RPC property for this flag", + "chat.send_personal_message --contact-id": "serialized into the aggregate content payload", "chat.send_personal_message --file-path": "local upload/preprocessing input", + "chat.send_personal_message --latitude": "serialized into the aggregate content payload", + "chat.send_personal_message --location-name": "serialized into the aggregate content payload", + "chat.send_personal_message --longitude": "serialized into the aggregate content payload", + "chat.send_personal_message --map-thumbnail-url": "serialized into the aggregate content payload", "chat.send_personal_message --media-id": "serialized into the aggregate content payload", "chat.send_personal_message --text": "serialized into the aggregate content payload", "chat.send_personal_message --title": "serialized into the aggregate content payload", @@ -1632,8 +1645,8 @@ "dev.disable_dev_app_robot --unified-app-id": "Reviewed unpinned adapter: dev.disable_dev_app_robot has no singular pinned interface_ref; --unified-app-id is a CLI wrapper input and does not publish a direct interface property.", "dev.enable_dev_app --unified-app-id": "Reviewed unpinned adapter: dev.enable_dev_app has no singular pinned interface_ref; --unified-app-id is a CLI wrapper input and does not publish a direct interface property.", "dev.enable_dev_app_robot --unified-app-id": "Reviewed unpinned adapter: dev.enable_dev_app_robot has no singular pinned interface_ref; --unified-app-id is a CLI wrapper input and does not publish a direct interface property.", - "dev.get_dev_app --unified-app-id": "Reviewed unpinned adapter: dev.get_dev_app has no singular pinned interface_ref; --unified-app-id is a CLI wrapper input and does not publish a direct interface property.", "dev.get_dev_app --app-key": "Reviewed unpinned adapter: dev.get_dev_app has no singular pinned interface_ref; --app-key is a CLI wrapper input and does not publish a direct interface property.", + "dev.get_dev_app --unified-app-id": "Reviewed unpinned adapter: dev.get_dev_app has no singular pinned interface_ref; --unified-app-id is a CLI wrapper input and does not publish a direct interface property.", "dev.get_dev_app_credentials --unified-app-id": "Reviewed unpinned adapter: dev.get_dev_app_credentials has no singular pinned interface_ref; --unified-app-id is a CLI wrapper input and does not publish a direct interface property.", "dev.get_dev_app_version_detail --unified-app-id": "Reviewed unpinned adapter: dev.get_dev_app_version_detail has no singular pinned interface_ref; --unified-app-id is a CLI wrapper input and does not publish a direct interface property.", "dev.get_dev_app_version_detail --version-id": "Reviewed unpinned adapter: dev.get_dev_app_version_detail has no singular pinned interface_ref; --version-id is a CLI wrapper input and does not publish a direct interface property.", @@ -1728,6 +1741,7 @@ "doc.get_document_content --scope": "Runtime extension sends scope for scoped JSONML reads, which is absent from the immutable pinned get_document_content metadata at its declared source revision.", "doc.get_document_content --start-block-id": "Runtime extension sends startBlockId for scoped JSONML reads, which is absent from the immutable pinned get_document_content metadata at its declared source revision.", "doc.get_document_content --tags": "Runtime extension sends tags for scoped JSONML reads, which is absent from the immutable pinned get_document_content metadata at its declared source revision.", + "doc.get_document_style --node": "Reviewed unpinned adapter: doc.get_document_style has no singular pinned interface_ref; --node is a CLI wrapper input and does not publish a direct interface property.", "doc.import_get --task-id": "Reviewed unpinned adapter: doc.import_get has no singular pinned interface_ref; --task-id is a CLI wrapper input and does not publish a direct interface property.", "doc.insert_document_block --fix-jsonml": "local JSONML normalization control", "doc.insert_document_block --heading": "aggregate convenience input used to build element", @@ -1735,6 +1749,14 @@ "doc.insert_document_block --text": "aggregate convenience input used to build element", "doc.list_document_blocks --block-id": "runtime extension sends blockId, which is absent from the pinned list_document_blocks metadata", "doc.reply_comment --mentioned-open-conversation-id": "Runtime extension sends mentionedOpenConversationIds, which is absent from the immutable pinned reply_comment metadata at its declared source revision.", + "doc.style_background_clear --node": "Reviewed unpinned adapter: doc.style_background_clear has no singular pinned interface_ref; --node is a CLI wrapper input and does not publish a direct interface property.", + "doc.style_background_set --color": "Reviewed unpinned adapter: doc.style_background_set has no singular pinned interface_ref; --color is a CLI wrapper input and does not publish a direct interface property.", + "doc.style_background_set --node": "Reviewed unpinned adapter: doc.style_background_set has no singular pinned interface_ref; --node is a CLI wrapper input and does not publish a direct interface property.", + "doc.style_cover_clear --node": "Reviewed unpinned adapter: doc.style_cover_clear has no singular pinned interface_ref; --node is a CLI wrapper input and does not publish a direct interface property.", + "doc.style_cover_set --file": "Reviewed unpinned adapter: doc.style_cover_set has no singular pinned interface_ref; --file is a CLI wrapper input and does not publish a direct interface property.", + "doc.style_cover_set --image": "Reviewed unpinned adapter: doc.style_cover_set has no singular pinned interface_ref; --image is a CLI wrapper input and does not publish a direct interface property.", + "doc.style_cover_set --node": "Reviewed unpinned adapter: doc.style_cover_set has no singular pinned interface_ref; --node is a CLI wrapper input and does not publish a direct interface property.", + "doc.style_cover_set --position": "Reviewed unpinned adapter: doc.style_cover_set has no singular pinned interface_ref; --position is a CLI wrapper input and does not publish a direct interface property.", "doc.template_apply --folder": "Reviewed unpinned adapter: doc.template_apply has no singular pinned interface_ref; --folder is a CLI wrapper input and does not publish a direct interface property.", "doc.template_apply --name": "Reviewed unpinned adapter: doc.template_apply has no singular pinned interface_ref; --name is a CLI wrapper input and does not publish a direct interface property.", "doc.template_apply --template-id": "Reviewed unpinned adapter: doc.template_apply has no singular pinned interface_ref; --template-id is a CLI wrapper input and does not publish a direct interface property.", @@ -1763,7 +1785,22 @@ "doc.version_revert --node": "Reviewed unpinned adapter: doc.version_revert has no singular pinned interface_ref; --node is a CLI wrapper input and does not publish a direct interface property.", "doc.version_revert --version": "Reviewed unpinned adapter: doc.version_revert has no singular pinned interface_ref; --version is a CLI wrapper input and does not publish a direct interface property.", "doc.version_save --node": "Reviewed unpinned adapter: doc.version_save has no singular pinned interface_ref; --node is a CLI wrapper input and does not publish a direct interface property.", + "drive.apply_permission --node": "Reviewed unpinned adapter: drive.apply_permission has no singular pinned interface_ref; --node is a CLI wrapper input and does not publish a direct interface property.", + "drive.apply_permission --notify-mode": "Reviewed unpinned adapter: drive.apply_permission has no singular pinned interface_ref; --notify-mode is a CLI wrapper input and does not publish a direct interface property.", + "drive.apply_permission --reason": "Reviewed unpinned adapter: drive.apply_permission has no singular pinned interface_ref; --reason is a CLI wrapper input and does not publish a direct interface property.", + "drive.apply_permission --role": "Reviewed unpinned adapter: drive.apply_permission has no singular pinned interface_ref; --role is a CLI wrapper input and does not publish a direct interface property.", + "drive.apply_permission --users": "Reviewed unpinned adapter: drive.apply_permission has no singular pinned interface_ref; --users is a CLI wrapper input and does not publish a direct interface property.", "drive.download_file --output": "local output path", + "drive.download_file_version --node": "Reviewed unpinned adapter: drive.download_file_version has no singular pinned interface_ref; --node is a CLI wrapper input and does not publish a direct interface property.", + "drive.download_file_version --output": "Reviewed unpinned adapter: drive.download_file_version has no singular pinned interface_ref; --output is a CLI wrapper input and does not publish a direct interface property.", + "drive.download_file_version --version": "Reviewed unpinned adapter: drive.download_file_version has no singular pinned interface_ref; --version is a CLI wrapper input and does not publish a direct interface property.", + "drive.get_cover --node": "Reviewed unpinned adapter: drive.get_cover has no singular pinned interface_ref; --node is a CLI wrapper input and does not publish a direct interface property.", + "drive.get_star_list --content-types": "Reviewed unpinned adapter: drive.get_star_list has no singular pinned interface_ref; --content-types is a CLI wrapper input and does not publish a direct interface property.", + "drive.get_star_list --cursor": "Reviewed unpinned adapter: drive.get_star_list has no singular pinned interface_ref; --cursor is a CLI wrapper input and does not publish a direct interface property.", + "drive.get_star_list --limit": "Reviewed unpinned adapter: drive.get_star_list has no singular pinned interface_ref; --limit is a CLI wrapper input and does not publish a direct interface property.", + "drive.get_star_list --order-by": "Reviewed unpinned adapter: drive.get_star_list has no singular pinned interface_ref; --order-by is a CLI wrapper input and does not publish a direct interface property.", + "drive.get_star_list --resource-types": "Reviewed unpinned adapter: drive.get_star_list has no singular pinned interface_ref; --resource-types is a CLI wrapper input and does not publish a direct interface property.", + "drive.get_star_list --sort": "Reviewed unpinned adapter: drive.get_star_list has no singular pinned interface_ref; --sort is a CLI wrapper input and does not publish a direct interface property.", "drive.list_files --cursor": "composite route maps to nextToken for drive.list_files or pageToken for doc.list_nodes", "drive.list_files --folder": "composite route maps to parentId for drive.list_files or folderId for doc.list_nodes", "drive.list_files --limit": "composite route maps to maxResults for drive.list_files or pageSize for doc.list_nodes", @@ -1772,10 +1809,12 @@ "drive.list_files --space-id": "drive-branch-only route input on a composite drive/doc command; no singular interface property is advertised", "drive.list_files --thumbnail": "drive-branch-only option on a composite drive/doc command; no singular interface property is advertised", "drive.list_files --workspace": "selects the doc.list_nodes branch of the composite drive/doc command", + "drive.mark_star --node": "Reviewed unpinned adapter: drive.mark_star has no singular pinned interface_ref; --node is a CLI wrapper input and does not publish a direct interface property.", "drive.publish_get --node": "Reviewed unpinned adapter: drive.publish_get has no singular pinned interface_ref; --node is a CLI wrapper input and does not publish a direct interface property.", "drive.publish_set --node": "Reviewed unpinned adapter: drive.publish_set has no singular pinned interface_ref; --node is a CLI wrapper input and does not publish a direct interface property.", "drive.publish_set --permission": "Reviewed unpinned adapter: drive.publish_set has no singular pinned interface_ref; --permission is a CLI wrapper input and does not publish a direct interface property.", "drive.publish_unset --node": "Reviewed unpinned adapter: drive.publish_unset has no singular pinned interface_ref; --node is a CLI wrapper input and does not publish a direct interface property.", + "drive.query_permission_apply_info --node": "Reviewed unpinned adapter: drive.query_permission_apply_info has no singular pinned interface_ref; --node is a CLI wrapper input and does not publish a direct interface property.", "drive.recent --creator-type": "Reviewed unpinned adapter: drive.recent has no singular pinned interface_ref; --creator-type is a CLI wrapper input and does not publish a direct interface property.", "drive.recent --cursor": "Reviewed unpinned adapter: drive.recent has no singular pinned interface_ref; --cursor is a CLI wrapper input and does not publish a direct interface property.", "drive.recent --file-types": "Reviewed unpinned adapter: drive.recent has no singular pinned interface_ref; --file-types is a CLI wrapper input and does not publish a direct interface property.", @@ -1786,6 +1825,14 @@ "drive.recycle_list --limit": "Reviewed unpinned adapter: drive.recycle_list has no singular pinned interface_ref; --limit is a CLI wrapper input and does not publish a direct interface property.", "drive.recycle_list --space-id": "Reviewed unpinned adapter: drive.recycle_list has no singular pinned interface_ref; --space-id is a CLI wrapper input and does not publish a direct interface property.", "drive.recycle_restore --id": "Reviewed unpinned adapter: drive.recycle_restore has no singular pinned interface_ref; --id is a CLI wrapper input and does not publish a direct interface property.", + "drive.revert_file_version --node": "Reviewed unpinned adapter: drive.revert_file_version has no singular pinned interface_ref; --node is a CLI wrapper input and does not publish a direct interface property.", + "drive.revert_file_version --version": "Reviewed unpinned adapter: drive.revert_file_version has no singular pinned interface_ref; --version is a CLI wrapper input and does not publish a direct interface property.", + "drive.transfer_owner --new-owner": "Reviewed unpinned adapter: drive.transfer_owner has no singular pinned interface_ref; --new-owner is a CLI wrapper input and does not publish a direct interface property.", + "drive.transfer_owner --node": "Reviewed unpinned adapter: drive.transfer_owner has no singular pinned interface_ref; --node is a CLI wrapper input and does not publish a direct interface property.", + "drive.transfer_owner --recursive": "Reviewed unpinned adapter: drive.transfer_owner has no singular pinned interface_ref; --recursive is a CLI wrapper input and does not publish a direct interface property.", + "drive.transfer_owner --reserve-role": "Reviewed unpinned adapter: drive.transfer_owner has no singular pinned interface_ref; --reserve-role is a CLI wrapper input and does not publish a direct interface property.", + "drive.transfer_owner --workspace": "Reviewed unpinned adapter: drive.transfer_owner has no singular pinned interface_ref; --workspace is a CLI wrapper input and does not publish a direct interface property.", + "drive.unmark_star --node": "Reviewed unpinned adapter: drive.unmark_star has no singular pinned interface_ref; --node is a CLI wrapper input and does not publish a direct interface property.", "mail.create_download_session --name": "local download filename", "mail.create_download_session --output": "local output path", "mail.create_draft --attachment": "reviewed local attachment preprocessing input", @@ -1831,9 +1878,16 @@ "sheet.create_pivot_table --source": "Reviewed unpinned adapter: sheet.create_pivot_table has no singular pinned interface_ref; --source is a CLI wrapper input and does not publish a direct interface property.", "sheet.create_pivot_table --target-position": "Reviewed unpinned adapter: sheet.create_pivot_table has no singular pinned interface_ref; --target-position is a CLI wrapper input and does not publish a direct interface property.", "sheet.create_pivot_table --target-sheet-id": "Reviewed unpinned adapter: sheet.create_pivot_table has no singular pinned interface_ref; --target-sheet-id is a CLI wrapper input and does not publish a direct interface property.", + "sheet.create_sheet_comment --content": "Reviewed unpinned adapter: sheet.create_sheet_comment has no singular pinned interface_ref; --content is a CLI wrapper input and does not publish a direct interface property.", + "sheet.create_sheet_comment --mention": "Reviewed unpinned adapter: sheet.create_sheet_comment has no singular pinned interface_ref; --mention is a CLI wrapper input and does not publish a direct interface property.", + "sheet.create_sheet_comment --node": "Reviewed unpinned adapter: sheet.create_sheet_comment has no singular pinned interface_ref; --node is a CLI wrapper input and does not publish a direct interface property.", + "sheet.create_sheet_comment --range": "Reviewed unpinned adapter: sheet.create_sheet_comment has no singular pinned interface_ref; --range is a CLI wrapper input and does not publish a direct interface property.", + "sheet.create_sheet_comment --sheet-id": "Reviewed unpinned adapter: sheet.create_sheet_comment has no singular pinned interface_ref; --sheet-id is a CLI wrapper input and does not publish a direct interface property.", "sheet.delete_cond_format --yes": "local confirmation control", "sheet.delete_pivot_table --pivot-table-id": "Reviewed unpinned adapter: sheet.delete_pivot_table has no singular pinned interface_ref; --pivot-table-id is a CLI wrapper input and does not publish a direct interface property.", "sheet.delete_pivot_table --sheet-id": "Reviewed unpinned adapter: sheet.delete_pivot_table has no singular pinned interface_ref; --sheet-id is a CLI wrapper input and does not publish a direct interface property.", + "sheet.delete_sheet_comment --comment-key": "Reviewed unpinned adapter: sheet.delete_sheet_comment has no singular pinned interface_ref; --comment-key is a CLI wrapper input and does not publish a direct interface property.", + "sheet.delete_sheet_comment --node": "Reviewed unpinned adapter: sheet.delete_sheet_comment has no singular pinned interface_ref; --node is a CLI wrapper input and does not publish a direct interface property.", "sheet.filter_view_get_criteria --column": "local post-filter over get_filter_views response", "sheet.filter_view_get_criteria --filter-view-id": "local post-filter over get_filter_views response", "sheet.filter_view_info --filter-view-id": "local post-filter over get_filter_views response", @@ -1843,6 +1897,13 @@ "sheet.filter_view_update_criteria --filter-view-id": "Reviewed unpinned adapter: sheet.filter_view_update_criteria has no singular pinned interface_ref; --filter-view-id is a CLI wrapper input and does not publish a direct interface property.", "sheet.filter_view_update_criteria --node": "Reviewed unpinned adapter: sheet.filter_view_update_criteria has no singular pinned interface_ref; --node is a CLI wrapper input and does not publish a direct interface property.", "sheet.filter_view_update_criteria --sheet-id": "Reviewed unpinned adapter: sheet.filter_view_update_criteria has no singular pinned interface_ref; --sheet-id is a CLI wrapper input and does not publish a direct interface property.", + "sheet.formula_verify --exit-on-error": "Reviewed unpinned adapter: sheet.formula_verify has no singular pinned interface_ref; --exit-on-error is a CLI wrapper input and does not publish a direct interface property.", + "sheet.formula_verify --max-cells": "Reviewed unpinned adapter: sheet.formula_verify has no singular pinned interface_ref; --max-cells is a CLI wrapper input and does not publish a direct interface property.", + "sheet.formula_verify --max-locations-per-error": "Reviewed unpinned adapter: sheet.formula_verify has no singular pinned interface_ref; --max-locations-per-error is a CLI wrapper input and does not publish a direct interface property.", + "sheet.formula_verify --node": "Reviewed unpinned adapter: sheet.formula_verify has no singular pinned interface_ref; --node is a CLI wrapper input and does not publish a direct interface property.", + "sheet.formula_verify --range": "Reviewed unpinned adapter: sheet.formula_verify has no singular pinned interface_ref; --range is a CLI wrapper input and does not publish a direct interface property.", + "sheet.formula_verify --sheet-id": "Reviewed unpinned adapter: sheet.formula_verify has no singular pinned interface_ref; --sheet-id is a CLI wrapper input and does not publish a direct interface property.", + "sheet.formula_verify --targets": "Reviewed unpinned adapter: sheet.formula_verify has no singular pinned interface_ref; --targets is a CLI wrapper input and does not publish a direct interface property.", "sheet.group_dimension --group-state": "Reviewed unpinned adapter: sheet.group_dimension has no singular pinned interface_ref; --group-state is a CLI wrapper input and does not publish a direct interface property.", "sheet.group_dimension --node": "Reviewed unpinned adapter: sheet.group_dimension has no singular pinned interface_ref; --node is a CLI wrapper input and does not publish a direct interface property.", "sheet.group_dimension --range": "Reviewed unpinned adapter: sheet.group_dimension has no singular pinned interface_ref; --range is a CLI wrapper input and does not publish a direct interface property.", @@ -1850,6 +1911,12 @@ "sheet.hide_gridline --sheet-id": "Reviewed unpinned adapter: sheet.hide_gridline has no singular pinned interface_ref; --sheet-id is a CLI wrapper input and does not publish a direct interface property.", "sheet.list_pivot_tables --pivot-table-id": "Reviewed unpinned adapter: sheet.list_pivot_tables has no singular pinned interface_ref; --pivot-table-id is a CLI wrapper input and does not publish a direct interface property.", "sheet.list_pivot_tables --sheet-id": "Reviewed unpinned adapter: sheet.list_pivot_tables has no singular pinned interface_ref; --sheet-id is a CLI wrapper input and does not publish a direct interface property.", + "sheet.list_sheet_comments --cursor": "Reviewed unpinned adapter: sheet.list_sheet_comments has no singular pinned interface_ref; --cursor is a CLI wrapper input and does not publish a direct interface property.", + "sheet.list_sheet_comments --limit": "Reviewed unpinned adapter: sheet.list_sheet_comments has no singular pinned interface_ref; --limit is a CLI wrapper input and does not publish a direct interface property.", + "sheet.list_sheet_comments --node": "Reviewed unpinned adapter: sheet.list_sheet_comments has no singular pinned interface_ref; --node is a CLI wrapper input and does not publish a direct interface property.", + "sheet.list_sheet_comments --range": "Reviewed unpinned adapter: sheet.list_sheet_comments has no singular pinned interface_ref; --range is a CLI wrapper input and does not publish a direct interface property.", + "sheet.list_sheet_comments --resolve-status": "Reviewed unpinned adapter: sheet.list_sheet_comments has no singular pinned interface_ref; --resolve-status is a CLI wrapper input and does not publish a direct interface property.", + "sheet.list_sheet_comments --sheet-id": "Reviewed unpinned adapter: sheet.list_sheet_comments has no singular pinned interface_ref; --sheet-id is a CLI wrapper input and does not publish a direct interface property.", "sheet.range_batch_clear --node": "Reviewed unpinned adapter: sheet.range_batch_clear has no singular pinned interface_ref; --node is a CLI wrapper input and does not publish a direct interface property.", "sheet.range_batch_clear --ranges": "Reviewed unpinned adapter: sheet.range_batch_clear has no singular pinned interface_ref; --ranges is a CLI wrapper input and does not publish a direct interface property.", "sheet.range_batch_clear --type": "Reviewed unpinned adapter: sheet.range_batch_clear has no singular pinned interface_ref; --type is a CLI wrapper input and does not publish a direct interface property.", @@ -1857,6 +1924,11 @@ "sheet.range_batch_set_style --continue-on-error": "Composite wrapper consumes this flag in its local multi-call error loop and never sends it to update_range.", "sheet.range_read --range": "Reviewed unpinned adapter: sheet.range_read has no singular pinned interface_ref; --range is a CLI wrapper input and does not publish a direct interface property.", "sheet.range_read --sheet-id": "Reviewed unpinned adapter: sheet.range_read has no singular pinned interface_ref; --sheet-id is a CLI wrapper input and does not publish a direct interface property.", + "sheet.reply_sheet_comment --comment-key": "Reviewed unpinned adapter: sheet.reply_sheet_comment has no singular pinned interface_ref; --comment-key is a CLI wrapper input and does not publish a direct interface property.", + "sheet.reply_sheet_comment --content": "Reviewed unpinned adapter: sheet.reply_sheet_comment has no singular pinned interface_ref; --content is a CLI wrapper input and does not publish a direct interface property.", + "sheet.reply_sheet_comment --emoji": "Reviewed unpinned adapter: sheet.reply_sheet_comment has no singular pinned interface_ref; --emoji is a CLI wrapper input and does not publish a direct interface property.", + "sheet.reply_sheet_comment --mention": "Reviewed unpinned adapter: sheet.reply_sheet_comment has no singular pinned interface_ref; --mention is a CLI wrapper input and does not publish a direct interface property.", + "sheet.reply_sheet_comment --node": "Reviewed unpinned adapter: sheet.reply_sheet_comment has no singular pinned interface_ref; --node is a CLI wrapper input and does not publish a direct interface property.", "sheet.show_gridline --sheet-id": "Reviewed unpinned adapter: sheet.show_gridline has no singular pinned interface_ref; --sheet-id is a CLI wrapper input and does not publish a direct interface property.", "sheet.submit_export_job --output": "local destination consumed after export job completion", "sheet.table_get --no-header": "Reviewed unpinned adapter: sheet.table_get has no singular pinned interface_ref; --no-header is a CLI wrapper input and does not publish a direct interface property.", @@ -1881,6 +1953,15 @@ "sheet.update_pivot_table --pivot-table-id": "Reviewed unpinned adapter: sheet.update_pivot_table has no singular pinned interface_ref; --pivot-table-id is a CLI wrapper input and does not publish a direct interface property.", "sheet.update_pivot_table --properties": "Reviewed unpinned adapter: sheet.update_pivot_table has no singular pinned interface_ref; --properties is a CLI wrapper input and does not publish a direct interface property.", "sheet.update_pivot_table --sheet-id": "Reviewed unpinned adapter: sheet.update_pivot_table has no singular pinned interface_ref; --sheet-id is a CLI wrapper input and does not publish a direct interface property.", + "sheet.update_sheet_comment --comment-key": "Reviewed unpinned adapter: sheet.update_sheet_comment has no singular pinned interface_ref; --comment-key is a CLI wrapper input and does not publish a direct interface property.", + "sheet.update_sheet_comment --content": "Reviewed unpinned adapter: sheet.update_sheet_comment has no singular pinned interface_ref; --content is a CLI wrapper input and does not publish a direct interface property.", + "sheet.update_sheet_comment --node": "Reviewed unpinned adapter: sheet.update_sheet_comment has no singular pinned interface_ref; --node is a CLI wrapper input and does not publish a direct interface property.", + "sheet.version_list --cursor": "Reviewed unpinned adapter: sheet.version_list has no singular pinned interface_ref; --cursor is a CLI wrapper input and does not publish a direct interface property.", + "sheet.version_list --limit": "Reviewed unpinned adapter: sheet.version_list has no singular pinned interface_ref; --limit is a CLI wrapper input and does not publish a direct interface property.", + "sheet.version_list --node": "Reviewed unpinned adapter: sheet.version_list has no singular pinned interface_ref; --node is a CLI wrapper input and does not publish a direct interface property.", + "sheet.version_revert --node": "Reviewed unpinned adapter: sheet.version_revert has no singular pinned interface_ref; --node is a CLI wrapper input and does not publish a direct interface property.", + "sheet.version_revert --version": "Reviewed unpinned adapter: sheet.version_revert has no singular pinned interface_ref; --version is a CLI wrapper input and does not publish a direct interface property.", + "sheet.version_save --node": "Reviewed unpinned adapter: sheet.version_save has no singular pinned interface_ref; --node is a CLI wrapper input and does not publish a direct interface property.", "sheet.write_image --file": "local upload input used to obtain resourceId/resourceUrl", "sheet.write_image --mime-type": "local upload metadata", "sheet.write_image --name": "local upload metadata", diff --git a/internal/helpers/chat.go b/internal/helpers/chat.go index ef6ea2a9..f4861bc5 100644 --- a/internal/helpers/chat.go +++ b/internal/helpers/chat.go @@ -1624,8 +1624,23 @@ func newChatCommand() *cobra.Command { contentJSON = fmt.Sprintf(`{"dentryId":%d,"spaceId":%d,"fileName":"%s","fileType":"%s","filePath":"%s","fileSize":%d}`, dentryId, spaceId, fileName, fileType, filePath, fileSize) } + case "location": + latitude, _ := cmd.Flags().GetString("latitude") + longitude, _ := cmd.Flags().GetString("longitude") + locationName, _ := cmd.Flags().GetString("location-name") + mapThumbnailUrl, _ := cmd.Flags().GetString("map-thumbnail-url") + if latitude == "" || longitude == "" || locationName == "" || mapThumbnailUrl == "" { + return fmt.Errorf("--latitude, --longitude, --location-name, --map-thumbnail-url are all required for msgType=location") + } + contentJSON = fmt.Sprintf(`{"locationName":"%s","longitude":"%s","latitude":"%s","mapThumbnailUrl":"%s"}`, locationName, longitude, latitude, mapThumbnailUrl) + case "profile": + contactID, _ := cmd.Flags().GetString("contact-id") + if contactID == "" { + return fmt.Errorf("--contact-id is required for msgType=profile") + } + contentJSON = fmt.Sprintf(`{"openDingTalkId":"%s"}`, contactID) default: - return fmt.Errorf("unsupported --msg-type: %s (supported: image, file, audio, video)", msgType) + return fmt.Errorf("unsupported --msg-type: %s (supported: image, file, audio, video, location, profile)", msgType) } params := map[string]any{ @@ -2226,8 +2241,10 @@ func newChatCommand() *cobra.Command { } if cmd.Flags().Changed("only-robot") { toolArgs["onlyRobotMessages"], _ = cmd.Flags().GetBool("only-robot") + } else if cmd.Flags().Changed("only-robot-messages") { + toolArgs["onlyRobotMessages"], _ = cmd.Flags().GetBool("only-robot-messages") } - if v, _ := cmd.Flags().GetString("conversation-type"); v != "" { + if v := flagOrFallback(cmd, "conversation-type", "search-conv-type"); v != "" { toolArgs["searchConvType"] = v } @@ -2534,7 +2551,12 @@ func newChatCommand() *cobra.Command { chatMessageSendCmd.Flags().Bool("at-all", false, "@所有人(仅群聊时生效,可选),设置时,消息内容中一定要包含对应的占位符<@all>") chatMessageSendCmd.Flags().String("at-open-dingtalk-ids", "", "@指定成员的 openDingTalkId 列表,逗号分隔(仅群聊时生效,可选),设置--at-open-dingtalk-ids openDingTalkId1,openDingTalkId2时,消息内容中一定要包含对应格式的占位符<@openDingTalkId1> <@openDingTalkId2>") chatMessageSendCmd.Flags().String("media-id", "", "上游已提供的图片 mediaId(仅旧版 msgType=image;CLI 不提供本地上传到 mediaId)") - chatMessageSendCmd.Flags().String("msg-type", "", "富媒体消息类型: image/file/audio/video(本地图片/文件推荐 file --file-path;image 仅接受已有 mediaId)") + chatMessageSendCmd.Flags().String("msg-type", "", "富媒体消息类型: image/file/audio/video/location/profile(本地图片/文件推荐 file --file-path;image 仅接受已有 mediaId)") + chatMessageSendCmd.Flags().String("latitude", "", "位置消息纬度(msgType=location 时必填)") + chatMessageSendCmd.Flags().String("longitude", "", "位置消息经度(msgType=location 时必填)") + chatMessageSendCmd.Flags().String("location-name", "", "位置消息地址名称(msgType=location 时必填)") + chatMessageSendCmd.Flags().String("map-thumbnail-url", "", "位置消息地图缩略图 mediaId,形如 @mediaId(msgType=location 时必填)") + chatMessageSendCmd.Flags().String("contact-id", "", "联系人名片 openDingTalkId(msgType=profile 时必填)") chatMessageSendCmd.Flags().Int64("dentry-id", 0, "文件 dentryId(与 --space-id 成对传入时跳过自动上传)") chatMessageSendCmd.Flags().Int64("space-id", 0, "空间 ID(与 --dentry-id 成对传入时跳过自动上传)") chatMessageSendCmd.Flags().String("file-name", "", "文件名") @@ -2728,7 +2750,11 @@ func newChatCommand() *cobra.Command { _ = chatMessageSearchAdvancedCmd.Flags().MarkHidden("group") chatMessageSearchAdvancedCmd.Flags().String("message-type", "", "下层消息类型过滤值(可选,以当前 IM Schema 支持值为准)") chatMessageSearchAdvancedCmd.Flags().Bool("only-robot", false, "只搜索机器人消息(可选;显式传 false 时也会传给下层)") + chatMessageSearchAdvancedCmd.Flags().Bool("only-robot-messages", false, "--only-robot 的别名") + _ = chatMessageSearchAdvancedCmd.Flags().MarkHidden("only-robot-messages") chatMessageSearchAdvancedCmd.Flags().String("conversation-type", "", "下层会话类型过滤值(可选,以当前 IM Schema 支持值为准)") + chatMessageSearchAdvancedCmd.Flags().String("search-conv-type", "", "--conversation-type 的别名") + _ = chatMessageSearchAdvancedCmd.Flags().MarkHidden("search-conv-type") chatMessageSearchAdvancedCmd.Flags().String("start", "", "开始时间,ISO-8601 格式(可选)") chatMessageSearchAdvancedCmd.Flags().String("end", "", "结束时间,ISO-8601 格式(可选)") chatMessageSearchAdvancedCmd.Flags().String("cursor", "0", "分页游标(默认 \"0\")") @@ -3228,6 +3254,40 @@ func newChatCommand() *cobra.Command { chatMessageRemoveTextEmotionCmd.Flags().String("text", "", "文字内容 (必填)") chatMessageRemoveTextEmotionCmd.Flags().String("background-id", "", "背景 ID (必填)") + chatMessageUpdateTextEmotionCmd := &cobra.Command{ + Use: "update-text-emotion", + Short: "更新消息的文字表情回应", + Example: ` dws chat message update-text-emotion --conversation-id --msg-id --old-emotion-id --emotion-id --emotion-name "赞" --text "nice" --background-id im_bg_5`, + RunE: func(cmd *cobra.Command, args []string) error { + if err := validateRequiredFlagWithAliases(cmd, "conversation-id", "group", "id", "chat"); err != nil { + return err + } + return callMCPToolOnServer("im", "update_text_emotion", map[string]any{ + "openConversationId": flagOrFallback(cmd, "conversation-id", "group", "id", "chat"), + "openMsgId": mustGetFlag(cmd, "msg-id"), + "oldEmotionId": mustGetFlag(cmd, "old-emotion-id"), + "emotionId": mustGetFlag(cmd, "emotion-id"), + "emotionName": mustGetFlag(cmd, "emotion-name"), + "text": mustGetFlag(cmd, "text"), + "backgroundId": mustGetFlag(cmd, "background-id"), + }) + }, + } + chatMessageUpdateTextEmotionCmd.Flags().String("conversation-id", "", "会话 openConversationId (必填,支持单聊/群聊)") + chatMessageUpdateTextEmotionCmd.Flags().String("group", "", "--conversation-id 的别名") + chatMessageUpdateTextEmotionCmd.Flags().String("id", "", "--conversation-id 的别名") + chatMessageUpdateTextEmotionCmd.Flags().String("chat", "", "--conversation-id 的别名") + chatMessageUpdateTextEmotionCmd.Flags().String("msg-id", "", "消息 openMsgId (必填)") + chatMessageUpdateTextEmotionCmd.Flags().String("old-emotion-id", "", "待更新的原表情 ID (必填)") + chatMessageUpdateTextEmotionCmd.Flags().String("emotion-id", "", "新表情 ID (必填)") + chatMessageUpdateTextEmotionCmd.Flags().String("emotion-name", "", "新表情名称 (必填)") + chatMessageUpdateTextEmotionCmd.Flags().String("text", "", "新文字内容 (必填)") + chatMessageUpdateTextEmotionCmd.Flags().String("background-id", "", "新背景 ID (必填)") + chatMessageUpdateTextEmotionCmd.MarkFlagsOneRequired("conversation-id", "group", "id", "chat") + for _, name := range []string{"msg-id", "old-emotion-id", "emotion-id", "emotion-name", "text", "background-id"} { + _ = chatMessageUpdateTextEmotionCmd.MarkFlagRequired(name) + } + // ── 创建文字表情(获取 emotionId)────────────────────── chatMessageCreateTextEmotionCmd := &cobra.Command{ @@ -3457,7 +3517,7 @@ flow-status 取值:1=处理中(PROCESSING),2=输入中(INPUTTING),3=完成 chatMessageDownloadMediaCmd.Flags().String("output", "", "本地保存路径,文件或目录 (必填)") _ = chatMessageDownloadMediaCmd.MarkFlagRequired("output") - chatMessageCmd.AddCommand(chatMessageListCmd, chatMessageSendCmd, chatMessageSendByBotCmd, chatMessageRecallByBotCmd, chatMessageSendByWebhookCmd, chatMessageListTopicRepliesCmd, chatMessageListAllCmd, chatMessageListBySenderCmd, chatMessageListMentionsCmd, chatMessageListFocusedCmd, chatMessageListUnreadConversationsCmd, chatMessageSearchCmd, chatMessageListByIdsCmd, chatMessageAddEmojiCmd, chatMessageRemoveEmojiCmd, chatMessageAddTextEmotionCmd, chatMessageRemoveTextEmotionCmd, chatMessageCreateTextEmotionCmd, chatMessageSearchAdvancedCmd, chatMessageQuerySendStatusCmd, chatMessageRecallCmd, chatMessageEditCmd, chatMessageReadStatusCmd, chatMessageSendCardCmd, chatMessageUpdateCardCmd, chatMessageDownloadMediaCmd) + chatMessageCmd.AddCommand(chatMessageListCmd, chatMessageSendCmd, chatMessageSendByBotCmd, chatMessageRecallByBotCmd, chatMessageSendByWebhookCmd, chatMessageListTopicRepliesCmd, chatMessageListAllCmd, chatMessageListBySenderCmd, chatMessageListMentionsCmd, chatMessageListFocusedCmd, chatMessageListUnreadConversationsCmd, chatMessageSearchCmd, chatMessageListByIdsCmd, chatMessageAddEmojiCmd, chatMessageRemoveEmojiCmd, chatMessageAddTextEmotionCmd, chatMessageRemoveTextEmotionCmd, chatMessageUpdateTextEmotionCmd, chatMessageCreateTextEmotionCmd, chatMessageSearchAdvancedCmd, chatMessageQuerySendStatusCmd, chatMessageRecallCmd, chatMessageEditCmd, chatMessageReadStatusCmd, chatMessageSendCardCmd, chatMessageUpdateCardCmd, chatMessageDownloadMediaCmd) chatBotCmd.AddCommand(chatBotSearchCmd) chatCategoryCmd.AddCommand(chatCategoryListCmd, chatCategoryConvsCmd, chatCategoryCreateCmd, chatCategoryDeleteCmd, chatCategoryRenameCmd, chatCategoryAddConvCmd, chatCategoryRemoveConvCmd, chatCategoryListByConvCmd, chatCategoryBatchInfoCmd) chatGroupCmd.AddCommand(chatGroupInfoByIdCmd) @@ -3774,6 +3834,25 @@ flow-status 取值:1=处理中(PROCESSING),2=输入中(INPUTTING),3=完成 _ = chatSetTopCmd.MarkFlagRequired("conversation-id") chatSetTopCmd.Flags().Bool("off", false, "取消置顶(不传则设置置顶)") + // ── group get-mute-config: 查询群用户禁言配置 ─────────────── + chatGroupGetMuteConfigCmd := &cobra.Command{ + Use: "get-mute-config", + Short: "查询群用户禁言配置", + Long: `查询指定群的用户禁言配置,包括单独禁言黑名单、全员禁言白名单及相关操作时间。 +返回的是原始配置记录,不等同于当前被禁言成员列表;全员禁言开关也不在本命令的返回范围内。`, + Example: ` dws chat group get-mute-config --group `, + RunE: func(cmd *cobra.Command, args []string) error { + if err := validateRequiredFlags(cmd, "group"); err != nil { + return err + } + return callMCPToolOnServer("im", "get_group_mute_config", map[string]any{ + "openConversationId": mustGetFlag(cmd, "group"), + }) + }, + } + chatGroupGetMuteConfigCmd.Flags().String("group", "", "群聊 openConversationId (必填)") + chatGroupCmd.AddCommand(chatGroupGetMuteConfigCmd) + // ── group-mute: 全员禁言 ─────────────────────────────── chatGroupMuteCmd := &cobra.Command{ @@ -5352,6 +5431,68 @@ pl_PL, sv_SE, fi_FI, cs_CZ, ar_SA, tl_PH, he_IL, nl_NL, lo_LA, it_IT`, chatTextCmd.AddCommand(chatTextTranslateCmd) chatGroupCmd.AddCommand(chatGroupBotsCmd, chatGroupDismissCmd, chatGroupSetHistoryCmd, chatGroupListMyGroupsCmd, chatGroupUpdateNickCmd, chatGroupUpdateAliasCmd, chatGroupListAllCmd, chatGroupListJoinValidationsCmd, chatGroupAuditJoinValidationCmd, chatGroupNoticeCmd, chatGroupShareInviteCmd, chatGroupUpgradeToExternalCmd) + + // ── chat group user-settings ── + chatGroupUserSettingsCmd := &cobra.Command{ + Use: "user-settings", + Short: "批量查询或更新当前用户的群会话设置", + RunE: groupRunE, + } + chatGroupUserSettingsQueryCmd := &cobra.Command{ + Use: "query", + Short: "批量查询当前用户的群会话设置", + Example: ` dws chat group user-settings query --groups cid1,cid2`, + RunE: func(cmd *cobra.Command, args []string) error { + if err := validateRequiredFlags(cmd, "groups"); err != nil { + return err + } + convIds := parseCSVValues(mustGetFlag(cmd, "groups")) + if len(convIds) == 0 { + return fmt.Errorf("--groups must not be empty") + } + if len(convIds) > 100 { + return fmt.Errorf("--groups batch size %d exceeds limit 100", len(convIds)) + } + return callMCPToolOnServer("im", "batch_query_group_chat_settings", map[string]any{ + "openConversationIds": convIds, + }) + }, + } + chatGroupUserSettingsQueryCmd.Flags().String("groups", "", "群会话 openConversationId 列表,逗号分隔,最多 100 个 (必填)") + chatGroupUserSettingsSetCmd := &cobra.Command{ + Use: "set", + Short: "批量更新当前用户的群会话设置", + Example: ` dws chat group user-settings set --items '[{"openConversationId":"cid1","top":true,"mute":false}]'`, + RunE: func(cmd *cobra.Command, args []string) error { + if err := validateRequiredFlags(cmd, "items"); err != nil { + return err + } + itemsJSON := mustGetFlag(cmd, "items") + var items []map[string]any + if err := json.Unmarshal([]byte(itemsJSON), &items); err != nil { + return fmt.Errorf("--items JSON parse error: %w", err) + } + if len(items) == 0 { + return fmt.Errorf("--items must not be empty") + } + if len(items) > 100 { + return fmt.Errorf("--items batch size %d exceeds limit 100", len(items)) + } + // 每项必须携带非空 openConversationId,缺失时 fail-fast,避免下发无效批量更新。 + for i, item := range items { + cid, _ := item["openConversationId"].(string) + if strings.TrimSpace(cid) == "" { + return fmt.Errorf("--items[%d] 缺少非空 openConversationId", i) + } + } + return callMCPToolOnServer("im", "batch_update_group_chat_settings", map[string]any{ + "items": items, + }) + }, + } + chatGroupUserSettingsSetCmd.Flags().String("items", "", `JSON 数组,每项 {"openConversationId":"cid","top":bool,"mute":bool,"groupNick":"...","groupAlias":"..."} (必填)`) + chatGroupUserSettingsCmd.AddCommand(chatGroupUserSettingsQueryCmd, chatGroupUserSettingsSetCmd) + chatGroupCmd.AddCommand(chatGroupUserSettingsCmd) chatGroupMembersCmd.AddCommand(chatGroupMembersRemoveBotCmd, chatGroupMembersListByIdsCmd) chatBotCmd.AddCommand(chatBotFindCmd) chatCategoryCmd.AddCommand(chatCategoryCreateSmartCmd) diff --git a/internal/helpers/chat_ports_coverage_test.go b/internal/helpers/chat_ports_coverage_test.go new file mode 100644 index 00000000..4466a966 --- /dev/null +++ b/internal/helpers/chat_ports_coverage_test.go @@ -0,0 +1,209 @@ +package helpers + +import ( + "io" + "reflect" + "strings" + "testing" + + "github.com/DingTalk-Real-AI/dingtalk-workspace-cli/pkg/edition" +) + +func executeChatPortsCoverageCommand(t *testing.T, caller edition.ToolCaller, args ...string) error { + t.Helper() + previousDeps := deps + t.Cleanup(func() { deps = previousDeps }) + InitDeps(caller) + deps.Out.w = io.Discard + deps.Out.errW = io.Discard + root := newChatCommand() + root.SilenceErrors = true + root.SilenceUsage = true + root.SetArgs(args) + return root.Execute() +} + +func TestCrossPlatformCoverageChatUpdateTextEmotion(t *testing.T) { + tests := []struct { + name string + args []string + want guardedMutationCall + }{ + { + name: "conversation-id flag", + args: []string{ + "message", "update-text-emotion", + "--conversation-id", "conv-1", + "--msg-id", "msg-1", + "--old-emotion-id", "old-1", + "--emotion-id", "new-1", + "--emotion-name", "like", + "--text", "nice", + "--background-id", "im_bg_5", + }, + want: guardedMutationCall{ + productID: "im", + toolName: "update_text_emotion", + args: map[string]any{ + "openConversationId": "conv-1", + "openMsgId": "msg-1", + "oldEmotionId": "old-1", + "emotionId": "new-1", + "emotionName": "like", + "text": "nice", + "backgroundId": "im_bg_5", + }, + }, + }, + { + name: "group alias for conversation-id", + args: []string{ + "message", "update-text-emotion", + "--group", "conv-2", + "--msg-id", "msg-2", + "--old-emotion-id", "old-2", + "--emotion-id", "new-2", + "--emotion-name", "heart", + "--text", "great", + "--background-id", "im_bg_1", + }, + want: guardedMutationCall{ + productID: "im", + toolName: "update_text_emotion", + args: map[string]any{ + "openConversationId": "conv-2", + "openMsgId": "msg-2", + "oldEmotionId": "old-2", + "emotionId": "new-2", + "emotionName": "heart", + "text": "great", + "backgroundId": "im_bg_1", + }, + }, + }, + } + for _, test := range tests { + test := test + t.Run(test.name, func(t *testing.T) { + caller := &guardedMutationCaller{} + err := executeGuardedMutationCommand(t, caller, newChatCommand, test.args...) + if err != nil { + t.Fatalf("chat %s returned error: %v", strings.Join(test.args, " "), err) + } + if len(caller.calls) != 1 || !reflect.DeepEqual(caller.calls[0], test.want) { + t.Fatalf("tool calls = %#v, want %#v", caller.calls, test.want) + } + }) + } +} + +func TestCrossPlatformCoverageChatUpdateTextEmotionRequiredFlags(t *testing.T) { + fullArgs := []string{ + "message", "update-text-emotion", + "--conversation-id", "conv-1", + "--msg-id", "msg-1", + "--old-emotion-id", "old-1", + "--emotion-id", "new-1", + "--emotion-name", "like", + "--text", "nice", + "--background-id", "im_bg_5", + } + dropFlag := func(name string) []string { + out := make([]string, 0, len(fullArgs)) + for i := 0; i < len(fullArgs); i++ { + if fullArgs[i] == name { + i++ // skip the flag value too + continue + } + out = append(out, fullArgs[i]) + } + return out + } + tests := []struct { + name string + args []string + wantErr string + }{ + { + name: "missing conversation-id and aliases", + args: dropFlag("--conversation-id"), + wantErr: "at least one of the flags in the group [conversation-id group id chat] is required", + }, + { + name: "missing old-emotion-id", + args: dropFlag("--old-emotion-id"), + wantErr: `required flag(s) "old-emotion-id" not set`, + }, + { + name: "missing msg-id and background-id", + args: []string{ + "message", "update-text-emotion", + "--conversation-id", "conv-1", + "--old-emotion-id", "old-1", + "--emotion-id", "new-1", + "--emotion-name", "like", + "--text", "nice", + }, + wantErr: `required flag(s) "background-id", "msg-id" not set`, + }, + } + for _, test := range tests { + test := test + t.Run(test.name, func(t *testing.T) { + caller := &guardedMutationCaller{} + err := executeGuardedMutationCommand(t, caller, newChatCommand, test.args...) + if err == nil || !strings.Contains(err.Error(), test.wantErr) { + t.Fatalf("err = %v, want message containing %q", err, test.wantErr) + } + if len(caller.calls) != 0 { + t.Fatalf("tool calls = %#v, want none", caller.calls) + } + }) + } +} + +func TestCrossPlatformCoverageChatGroupGetMuteConfig(t *testing.T) { + caller := &guardedMutationCaller{} + err := executeGuardedMutationCommand(t, caller, newChatCommand, + "group", "get-mute-config", "--group", "conv-1") + if err != nil { + t.Fatalf("get-mute-config returned error: %v", err) + } + want := guardedMutationCall{ + productID: "im", + toolName: "get_group_mute_config", + args: map[string]any{"openConversationId": "conv-1"}, + } + if len(caller.calls) != 1 || !reflect.DeepEqual(caller.calls[0], want) { + t.Fatalf("tool calls = %#v, want %#v", caller.calls, want) + } +} + +func TestCrossPlatformCoverageChatGroupGetMuteConfigRecordsRawArgs(t *testing.T) { + caller := &depthArgsRecordingCaller{steps: []scriptedToolStep{ + {text: `{"muteBlackList":[],"muteWhiteList":[]}`}, + }} + err := executeChatPortsCoverageCommand(t, caller, + "group", "get-mute-config", "--group", "conv-9") + if err != nil { + t.Fatal(err) + } + if len(caller.calls) != 1 { + t.Fatalf("calls = %#v, want 1", caller.calls) + } + if caller.calls[0]["openConversationId"] != "conv-9" { + t.Fatalf("raw args = %#v", caller.calls[0]) + } +} + +func TestCrossPlatformCoverageChatGroupGetMuteConfigRequiresGroup(t *testing.T) { + caller := &guardedMutationCaller{} + err := executeGuardedMutationCommand(t, caller, newChatCommand, + "group", "get-mute-config") + if err == nil || !strings.Contains(err.Error(), "--group") { + t.Fatalf("err = %v, want message containing --group", err) + } + if len(caller.calls) != 0 { + t.Fatalf("tool calls = %#v, want none", caller.calls) + } +} diff --git a/internal/helpers/chat_text_emotion_update_test.go b/internal/helpers/chat_text_emotion_update_test.go new file mode 100644 index 00000000..d372b3a4 --- /dev/null +++ b/internal/helpers/chat_text_emotion_update_test.go @@ -0,0 +1,86 @@ +// Copyright 2026 Alibaba Group +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package helpers + +import ( + "strings" + "testing" +) + +func TestCrossPlatformCoverageChatMessageUpdateTextEmotionMapsReplacementTemplate(t *testing.T) { + for _, locator := range []string{"conversation-id", "group", "id", "chat"} { + t.Run(locator, func(t *testing.T) { + caller := &wukongWeeklySyncCaller{} + _, _, err := executeWukongWeeklySyncCommand(t, "chat", caller, newChatCommand, + "message", "update-text-emotion", + "--"+locator, "cid", + "--msg-id", "mid", + "--old-emotion-id", "old-emotion", + "--emotion-id", "new-emotion", + "--emotion-name", "赞", + "--text", "nice", + "--background-id", "im_bg_5", + ) + if err != nil { + t.Fatalf("update-text-emotion returned error: %v", err) + } + requireWukongWeeklySyncCall(t, caller, wukongWeeklySyncCall{ + server: "im", + tool: "update_text_emotion", + args: map[string]any{ + "openConversationId": "cid", + "openMsgId": "mid", + "oldEmotionId": "old-emotion", + "emotionId": "new-emotion", + "emotionName": "赞", + "text": "nice", + "backgroundId": "im_bg_5", + }, + }) + }) + } +} + +func TestCrossPlatformCoverageChatMessageUpdateTextEmotionRequiresEveryBusinessParameter(t *testing.T) { + required := []struct { + name string + args []string + }{ + {name: "conversation-id", args: []string{"--group", "cid"}}, + {name: "msg-id", args: []string{"--msg-id", "mid"}}, + {name: "old-emotion-id", args: []string{"--old-emotion-id", "old-emotion"}}, + {name: "emotion-id", args: []string{"--emotion-id", "new-emotion"}}, + {name: "emotion-name", args: []string{"--emotion-name", "赞"}}, + {name: "text", args: []string{"--text", "nice"}}, + {name: "background-id", args: []string{"--background-id", "im_bg_5"}}, + } + + for omitted := range required { + t.Run("missing-"+required[omitted].name, func(t *testing.T) { + args := []string{"message", "update-text-emotion"} + for i, parameter := range required { + if i != omitted { + args = append(args, parameter.args...) + } + } + + caller := &wukongWeeklySyncCaller{} + _, _, err := executeWukongWeeklySyncCommand(t, "chat", caller, newChatCommand, args...) + if err == nil || !strings.Contains(err.Error(), required[omitted].name) { + t.Fatalf("missing %s error = %v", required[omitted].name, err) + } + requireWukongWeeklySyncNoCalls(t, caller) + }) + } +} diff --git a/internal/helpers/chat_user_settings_test.go b/internal/helpers/chat_user_settings_test.go new file mode 100644 index 00000000..69107d78 --- /dev/null +++ b/internal/helpers/chat_user_settings_test.go @@ -0,0 +1,162 @@ +package helpers + +import ( + "strings" + "testing" +) + +func TestCrossPlatformCoverageChatGroupUserSettingsQueryValidation(t *testing.T) { + caller := &guardedMutationCaller{} + if err := executeGuardedMutationCommand(t, caller, newChatCommand, + "group", "user-settings", "query", "--groups", ","); err == nil { + t.Fatal("empty groups returned nil") + } + + tooMany := make([]string, 101) + for i := range tooMany { + tooMany[i] = "cid" + } + if err := executeGuardedMutationCommand(t, caller, newChatCommand, + "group", "user-settings", "query", "--groups", strings.Join(tooMany, ",")); err == nil { + t.Fatal("101 groups returned nil") + } +} + +func TestCrossPlatformCoverageChatGroupUserSettingsSetValidation(t *testing.T) { + caller := &guardedMutationCaller{} + if err := executeGuardedMutationCommand(t, caller, newChatCommand, + "group", "user-settings", "set", "--items", "[]"); err == nil { + t.Fatal("empty items returned nil") + } + + var sb strings.Builder + sb.WriteString("[") + for i := 0; i < 101; i++ { + if i > 0 { + sb.WriteString(",") + } + sb.WriteString(`{"openConversationId":"cid"}`) + } + sb.WriteString("]") + if err := executeGuardedMutationCommand(t, caller, newChatCommand, + "group", "user-settings", "set", "--items", sb.String()); err == nil { + t.Fatal("101 items returned nil") + } +} + +func TestCrossPlatformCoverageChatGroupUserSettingsSetItemValidation(t *testing.T) { + caller := &guardedMutationCaller{} + err := executeGuardedMutationCommand(t, caller, newChatCommand, + "group", "user-settings", "set", "--items", `[{"top":true}]`) + if err == nil || !strings.Contains(err.Error(), "openConversationId") || !strings.Contains(err.Error(), "--items[0]") { + t.Fatalf("err = %v, want missing openConversationId error", err) + } + if len(caller.calls) != 0 { + t.Fatalf("calls = %#v, want no MCP call on invalid item", caller.calls) + } + + // 非首项缺失时错误需带正确下标;空白字符串同样视为缺失。 + caller = &guardedMutationCaller{} + err = executeGuardedMutationCommand(t, caller, newChatCommand, + "group", "user-settings", "set", "--items", `[{"openConversationId":"cid1"},{"openConversationId":" "}]`) + if err == nil || !strings.Contains(err.Error(), "--items[1]") { + t.Fatalf("err = %v, want --items[1] error", err) + } + + // 合法条目仍然成功下发。 + caller = &guardedMutationCaller{} + if err := executeGuardedMutationCommand(t, caller, newChatCommand, + "group", "user-settings", "set", "--items", `[{"openConversationId":"cid1","top":true,"mute":false}]`); err != nil { + t.Fatal(err) + } + if len(caller.calls) != 1 || caller.calls[0].toolName != "batch_update_group_chat_settings" { + t.Fatalf("calls = %#v", caller.calls) + } +} + +func TestCrossPlatformCoverageChatGroupUserSettingsSetHappyPath(t *testing.T) { + caller := &guardedMutationCaller{} + err := executeGuardedMutationCommand(t, caller, newChatCommand, + "group", "user-settings", "set", "--items", `[{"openConversationId":"cid1","top":true}]`) + if err != nil { + t.Fatal(err) + } + if len(caller.calls) != 1 { + t.Fatalf("calls = %#v", caller.calls) + } + call := caller.calls[0] + if call.productID != "im" || call.toolName != "batch_update_group_chat_settings" { + t.Fatalf("call = %#v", call) + } +} + +func TestCrossPlatformCoverageChatMessageSendLocation(t *testing.T) { + caller := &guardedMutationCaller{} + err := executeGuardedMutationCommand(t, caller, newChatCommand, + "message", "send", "--group", "cid1", "--msg-type", "location", "--latitude", "39.9") + if err == nil || !strings.Contains(err.Error(), "are all required for msgType=location") { + t.Fatalf("err = %v, want missing location params", err) + } + + caller = &guardedMutationCaller{} + err = executeGuardedMutationCommand(t, caller, newChatCommand, + "message", "send", "--group", "cid1", "--msg-type", "location", + "--latitude", "39.9", "--longitude", "116.4", "--location-name", "国贸", "--map-thumbnail-url", "@media1") + if err != nil { + t.Fatal(err) + } + if len(caller.calls) != 1 { + t.Fatalf("calls = %#v", caller.calls) + } + call := caller.calls[0] + if call.args["msgType"] != "location" { + t.Fatalf("msgType = %#v", call.args["msgType"]) + } + content, _ := call.args["content"].(string) + if !strings.Contains(content, `"latitude":"39.9"`) || !strings.Contains(content, `"mapThumbnailUrl":"@media1"`) { + t.Fatalf("content = %s", content) + } +} + +func TestCrossPlatformCoverageChatMessageSendProfile(t *testing.T) { + caller := &guardedMutationCaller{} + err := executeGuardedMutationCommand(t, caller, newChatCommand, + "message", "send", "--group", "cid1", "--msg-type", "profile") + if err == nil || !strings.Contains(err.Error(), "--contact-id is required") { + t.Fatalf("err = %v, want contact-id required", err) + } + + caller = &guardedMutationCaller{} + err = executeGuardedMutationCommand(t, caller, newChatCommand, + "message", "send", "--group", "cid1", "--msg-type", "profile", "--contact-id", "od123") + if err != nil { + t.Fatal(err) + } + if len(caller.calls) != 1 { + t.Fatalf("calls = %#v", caller.calls) + } + content, _ := caller.calls[0].args["content"].(string) + if !strings.Contains(content, `"openDingTalkId":"od123"`) { + t.Fatalf("content = %s", content) + } +} + +func TestCrossPlatformCoverageChatSearchAdvancedWukongAliases(t *testing.T) { + caller := &guardedMutationCaller{} + err := executeGuardedMutationCommand(t, caller, newChatCommand, + "message", "search-advanced", "--query", "通知", + "--only-robot-messages", "--search-conv-type", "group_chat") + if err != nil { + t.Fatal(err) + } + if len(caller.calls) != 1 { + t.Fatalf("calls = %#v", caller.calls) + } + args := caller.calls[0].args + if args["onlyRobotMessages"] != true { + t.Fatalf("onlyRobotMessages = %#v", args["onlyRobotMessages"]) + } + if args["searchConvType"] != "group_chat" { + t.Fatalf("searchConvType = %#v", args["searchConvType"]) + } +} diff --git a/internal/helpers/destructive_confirmation_test.go b/internal/helpers/destructive_confirmation_test.go index a5aac072..a036d497 100644 --- a/internal/helpers/destructive_confirmation_test.go +++ b/internal/helpers/destructive_confirmation_test.go @@ -287,6 +287,14 @@ func TestSheetConfirmationGuardCoversEveryProtectedLeaf(t *testing.T) { path: "sheet range move-to", args: []string{"range", "move-to", "--node", "node-1", "--sheet-id", "sheet-1", "--source-range", "A1:B3", "--target-range", "D1"}, }, + { + path: "sheet comment delete", + args: []string{"comment", "delete", "--node", "node-1", "--comment-key", "ck-1"}, + }, + { + path: "sheet version revert", + args: []string{"version", "revert", "--node", "node-1", "--version", "2"}, + }, } // A newly protected command must add a runnable case here. This keeps the diff --git a/internal/helpers/doc.go b/internal/helpers/doc.go index adcc6764..ab2c0ee3 100644 --- a/internal/helpers/doc.go +++ b/internal/helpers/doc.go @@ -2901,7 +2901,7 @@ CLI 内部自动完成全部流程: folderCmd.Hidden = true permissionCmd.Hidden = true - root.AddCommand(searchCmd, listCmd, infoCmd, readCmd, createCmd, updateCmd, uploadCmd, downloadCmd, copyCmd, moveCmd, renameCmd, deleteCmd, fileCmd, folderCmd, blockCmd, commentCmd, mediaCmd, permissionCmd, exportCmd, importCmd, versionCmd, templateCmd) + root.AddCommand(searchCmd, listCmd, infoCmd, readCmd, createCmd, updateCmd, uploadCmd, downloadCmd, copyCmd, moveCmd, renameCmd, deleteCmd, fileCmd, folderCmd, blockCmd, commentCmd, mediaCmd, permissionCmd, exportCmd, importCmd, versionCmd, templateCmd, newDocStyleCommand()) return root } diff --git a/internal/helpers/doc_style.go b/internal/helpers/doc_style.go new file mode 100644 index 00000000..a807ee33 --- /dev/null +++ b/internal/helpers/doc_style.go @@ -0,0 +1,321 @@ +package helpers + +import ( + "context" + "fmt" + "os" + "path/filepath" + "strings" + + "github.com/spf13/cobra" +) + +// doc_style.go — `dws doc style`(文档封面/背景样式配置)。 +// 写入收口到 MCP 工具 update_document_style;读取用 get_document_style。 + +const docStyleToolName = "update_document_style" + +const docStyleGetToolName = "get_document_style" + +// newDocStyleCommand 构建 `dws doc style` 命令组:cover set|clear、background set|clear、get。 +func newDocStyleCommand() *cobra.Command { + styleCmd := &cobra.Command{ + Use: "style", + Short: "文档样式配置 (封面/背景)", + Long: `配置钉钉文档的封面与背景(单接口收口 update_document_style)。 + +命令结构: + dws doc style cover set 设置文档封面 + dws doc style cover clear 移除文档封面 + dws doc style background set 设置文档背景纯色 + dws doc style background clear 清除文档背景 + dws doc style get 读取文档封面/背景 (只读) + +封面图片支持 --image 外链 (自动转存) 或 --file 本地文件上传,均会转存为公开读地址;背景仅支持 --color 纯色。`, + RunE: groupRunE, + } + + coverCmd := &cobra.Command{ + Use: "cover", + Short: "文档封面设置/移除", + RunE: groupRunE, + } + coverSetCmd := &cobra.Command{ + Use: "set", + Short: "设置文档封面", + Long: `为钉钉文档设置顶部封面图,可指定竖直显示位置。图片来源二选一:--image 外链或 --file 本地文件。`, + Example: ` dws doc style cover set --node DOC_ID --image https://img.example.com/cover.png + dws doc style cover set --node DOC_ID --file ./cover.png --position 0.3`, + RunE: runDocStyleCoverSet, + } + coverClearCmd := &cobra.Command{ + Use: "clear", + Short: "移除文档封面", + Example: ` dws doc style cover clear --node DOC_ID`, + RunE: runDocStyleCoverClear, + } + + backgroundCmd := &cobra.Command{ + Use: "background", + Short: "文档背景设置/清除", + RunE: groupRunE, + } + backgroundSetCmd := &cobra.Command{ + Use: "set", + Short: "设置文档背景纯色", + Long: `为钉钉文档设置背景纯色(--color)。背景仅支持纯色,不支持背景图片上传(对齐前端能力)。`, + Example: ` dws doc style background set --node DOC_ID --color "#E8F2FE"`, + RunE: runDocStyleBackgroundSet, + } + backgroundClearCmd := &cobra.Command{ + Use: "clear", + Short: "清除文档背景", + Example: ` dws doc style background clear --node DOC_ID`, + RunE: runDocStyleBackgroundClear, + } + getCmd := &cobra.Command{ + Use: "get", + Short: "读取文档封面/背景 (只读)", + Long: `读取钉钉文档当前的封面与背景配置(只读,单接口收口 get_document_style)。`, + Example: ` dws doc style get --node DOC_ID`, + RunE: runDocStyleGet, + } + + // cover set flags + coverSetCmd.Flags().String("node", "", "目标文档标识,支持 URL 或 ID (必填)") + coverSetCmd.Flags().String("image", "", "封面图片 URL (外链会自动转存为内部地址)") + coverSetCmd.Flags().String("file", "", "本地图片文件路径 (与 --image 互斥)") + coverSetCmd.Flags().Float64("position", 0.5, "封面竖直位置 [0,1],默认 0.5") + coverSetCmd.MarkFlagsMutuallyExclusive("image", "file") + + // cover clear flags + coverClearCmd.Flags().String("node", "", "目标文档标识,支持 URL 或 ID (必填)") + + // background set flags(仅纯色,对齐前端;不支持背景图片上传) + backgroundSetCmd.Flags().String("node", "", "目标文档标识,支持 URL 或 ID (必填)") + backgroundSetCmd.Flags().String("color", "", "背景纯色,如 #E8F2FE (必填)") + + // background clear flags + backgroundClearCmd.Flags().String("node", "", "目标文档标识,支持 URL 或 ID (必填)") + + // get flags + getCmd.Flags().String("node", "", "目标文档标识,支持 URL 或 ID (必填)") + + // --node 隐藏别名 (--url/--id/--node-id/--doc-id/--file-id) + for _, c := range []*cobra.Command{coverSetCmd, coverClearCmd, backgroundSetCmd, backgroundClearCmd, getCmd} { + c.Flags().String("url", "", "--node 的别名") + c.Flags().String("id", "", "--node 的别名") + c.Flags().String("node-id", "", "--node 的别名") + c.Flags().String("doc-id", "", "--node 的别名") + c.Flags().String("file-id", "", "--node 的别名 (跨产品兼容 drive)") + _ = c.Flags().MarkHidden("url") + _ = c.Flags().MarkHidden("id") + _ = c.Flags().MarkHidden("node-id") + _ = c.Flags().MarkHidden("doc-id") + _ = c.Flags().MarkHidden("file-id") + } + + coverCmd.AddCommand(coverSetCmd, coverClearCmd) + backgroundCmd.AddCommand(backgroundSetCmd, backgroundClearCmd) + styleCmd.AddCommand(coverCmd, backgroundCmd, getCmd) + + // 递归注册跨产品别名,让 cover set 的 --file 获得统一别名 --file-path。 + registerCrossProductAliasesRecursive(styleCmd) + return styleCmd +} + +// registerCrossProductAliasesRecursive 递归为 cmd 及其所有子命令注册跨产品别名。 +// 可正确处理多层分组(如 style 的 cover/background 组下还有 set/clear 叶子)。 +func registerCrossProductAliasesRecursive(cmd *cobra.Command) { + RegisterCrossProductAliases(cmd) + for _, sub := range cmd.Commands() { + registerCrossProductAliasesRecursive(sub) + } +} + +// docStyleNodeID 解析目标文档标识,支持 --node 及隐藏别名。 +func docStyleNodeID(cmd *cobra.Command) (string, error) { + return mustFlagOrFallback(cmd, "node", "url", "id", "node-id", "doc-id", "file-id") +} + +func runDocStyleCoverSet(cmd *cobra.Command, _ []string) error { + nodeID, err := docStyleNodeID(cmd) + if err != nil { + return err + } + // 封面图片来源二选一:--image 外链(服务端转存公开)或 --file 本地上传(拿 resourceId 转存公开)。 + image, _ := cmd.Flags().GetString("image") + filePath := flagOrFallback(cmd, "file", "file-path") + // --image 与 --file/--file-path 互斥。cobra 的 MarkFlagsMutuallyExclusive 只认主名 + // image/file,拦不住别名 --file-path,故在此显式兜底。 + if image != "" && filePath != "" { + return fmt.Errorf("--image and --file/--file-path are mutually exclusive; specify only one") + } + if image == "" && filePath == "" { + return fmt.Errorf("flag --image or --file is required for `cover set`") + } + + cover := map[string]any{"action": "set"} + // position 客户端校验:帮助文档承诺 [0,1],越界直接报错,避免触发上传/请求。 + if cmd.Flags().Changed("position") { + pos, _ := cmd.Flags().GetFloat64("position") + if pos < 0 || pos > 1 { + return fmt.Errorf("--position must be within [0,1], got %v", pos) + } + cover["position"] = pos + } + + if image != "" { + cover["imageUrl"] = image + } else { + // --file:先做本地校验(存在 + 必须为图片);dry-run 时在上传前短路, + // 绝不读取/上传本地文件。 + mimeType, fileSize, err := validateCoverImageFile(filePath) + if err != nil { + return err + } + if deps.Caller.DryRun() { + cover["resourceId"] = fmt.Sprintf("(pending upload from --file %s)", filePath) + } else { + // 使用命令上下文,让 Ctrl-C 能够取消上传凭证获取与 OSS 上传两次网络请求。 + ctx := cmd.Context() + if ctx == nil { + ctx = context.Background() + } + resourceID, err := uploadDocStyleImage(ctx, nodeID, filePath, filepath.Base(filePath), mimeType, fileSize) + if err != nil { + return err + } + cover["resourceId"] = resourceID + } + } + + return callMCPToolOnServer("doc", docStyleToolName, map[string]any{ + "nodeId": nodeID, + "cover": cover, + }) +} + +func runDocStyleCoverClear(cmd *cobra.Command, _ []string) error { + nodeID, err := docStyleNodeID(cmd) + if err != nil { + return err + } + return callMCPToolOnServer("doc", docStyleToolName, map[string]any{ + "nodeId": nodeID, + "cover": map[string]any{"action": "clear"}, + }) +} + +func runDocStyleBackgroundSet(cmd *cobra.Command, _ []string) error { + nodeID, err := docStyleNodeID(cmd) + if err != nil { + return err + } + // 背景仅支持纯色(对齐前端,不支持背景图片上传)。 + color, _ := cmd.Flags().GetString("color") + if color == "" { + return fmt.Errorf("flag --color is required for `background set`") + } + // 帮助文档承诺 #RRGGBB 十六进制纯色,非法值直接报错,不下发请求。 + if !isHexColor(color) { + return fmt.Errorf("--color must be a hex color like #E8F2FE, got %q", color) + } + return callMCPToolOnServer("doc", docStyleToolName, map[string]any{ + "nodeId": nodeID, + "background": map[string]any{ + "action": "set", + "backgroundColor": color, + }, + }) +} + +func runDocStyleBackgroundClear(cmd *cobra.Command, _ []string) error { + nodeID, err := docStyleNodeID(cmd) + if err != nil { + return err + } + return callMCPToolOnServer("doc", docStyleToolName, map[string]any{ + "nodeId": nodeID, + "background": map[string]any{"action": "clear"}, + }) +} + +// runDocStyleGet 读取文档当前封面/背景(只读)。 +func runDocStyleGet(cmd *cobra.Command, _ []string) error { + nodeID, err := docStyleNodeID(cmd) + if err != nil { + return err + } + return callMCPToolOnServer("doc", docStyleGetToolName, map[string]any{ + "nodeId": nodeID, + }) +} + +// docStyleCoverMaxBytes 是 cover set --file 本地图片的大小上限(20 MiB)。 +// 悟空参考实现未设上限,此处补充客户端 fail-fast,避免超大文件被先行上传。 +const docStyleCoverMaxBytes = 20 << 20 + +// validateCoverImageFile 对 cover set --file 做本地校验(不上传/不读取文件内容): +// 文件须存在、非目录、不超过大小上限,且按扩展名推断的 MIME 必须为 image/*。 +// 返回 mimeType 与文件大小,供 dry-run 预览与真实上传前 fail-fast,避免非法文件被先行上传。 +func validateCoverImageFile(filePath string) (mimeType string, fileSize int64, err error) { + fileInfo, err := os.Stat(filePath) + if err != nil { + return "", 0, fmt.Errorf("cannot read file %s: %w", filePath, err) + } + if fileInfo.IsDir() { + return "", 0, fmt.Errorf("%s is a directory, not a file", filePath) + } + if fileInfo.Size() > docStyleCoverMaxBytes { + return "", 0, fmt.Errorf("封面图片文件过大:上限 %d 字节 (20 MiB),实际 %d 字节 (%s)", int64(docStyleCoverMaxBytes), fileInfo.Size(), filepath.Base(filePath)) + } + mimeType = inferMimeType(filepath.Base(filePath)) + if !strings.HasPrefix(mimeType, "image/") { + return "", 0, fmt.Errorf("cover --file must be an image (png/jpg/jpeg/gif/svg/webp), got %s", filepath.Base(filePath)) + } + return mimeType, fileInfo.Size(), nil +} + +// isHexColor 校验 #RRGGBB 形式的十六进制颜色(大小写均可)。 +func isHexColor(s string) bool { + if len(s) != 7 || s[0] != '#' { + return false + } + for _, c := range s[1:] { + switch { + case c >= '0' && c <= '9', c >= 'a' && c <= 'f', c >= 'A' && c <= 'F': + default: + return false + } + } + return true +} + +// uploadDocStyleImage 复用附件上传子流程(get_doc_attachment_upload_info + httpPutFile), +// 上传本地文件为鉴权资源,返回其 resourceId(供封面在服务端转存为公开读地址)。 +// 入参 mimeType/fileSize 由调用方经 validateCoverImageFile 预校验得到;进度写 stderr, +// 保证 --format json 时 stdout 只含合法 JSON。本函数一定发起网络请求,调用方须自行做 dry-run 短路。 +func uploadDocStyleImage(ctx context.Context, nodeID, filePath, fileName, mimeType string, fileSize int64) (string, error) { + deps.Out.PrintProgress(fmt.Sprintf("[1/2] 获取图片上传凭证 (%s, %d bytes)...", fileName, fileSize)) + credText, err := callMCPToolReturnTextOnServer(ctx, "doc", "get_doc_attachment_upload_info", map[string]any{ + "nodeId": nodeID, + "fileName": fileName, + "fileSize": float64(fileSize), + "mimeType": mimeType, + }) + if err != nil { + return "", err + } + uploadURL, resourceID, _, err := parseAttachmentUploadInfo(credText) + if err != nil { + return "", err + } + + deps.Out.PrintProgress("[2/2] 上传图片到 OSS...") + ossHeaders := map[string]string{"Content-Type": mimeType} + if err := httpPutFile(ctx, uploadURL, ossHeaders, filePath, fileSize); err != nil { + return "", err + } + return resourceID, nil +} diff --git a/internal/helpers/doc_style_test.go b/internal/helpers/doc_style_test.go new file mode 100644 index 00000000..dded4c3f --- /dev/null +++ b/internal/helpers/doc_style_test.go @@ -0,0 +1,230 @@ +package helpers + +import ( + "context" + "errors" + "os" + "path/filepath" + "strings" + "testing" +) + +func writeTempImage(t *testing.T, name string) string { + t.Helper() + path := filepath.Join(t.TempDir(), name) + if err := os.WriteFile(path, []byte("fake-image-bytes"), 0o600); err != nil { + t.Fatal(err) + } + return path +} + +func TestCrossPlatformCoverageValidateCoverImageFile(t *testing.T) { + if _, _, err := validateCoverImageFile(t.TempDir()); err == nil { + t.Fatal("directory returned nil") + } + path := writeTempImage(t, "cover.png") + mimeType, size, err := validateCoverImageFile(path) + if err != nil { + t.Fatal(err) + } + if mimeType != "image/png" || size != int64(len("fake-image-bytes")) { + t.Fatalf("mimeType=%q size=%d", mimeType, size) + } +} + +func TestCrossPlatformCoverageIsHexColorEdge(t *testing.T) { + for _, valid := range []string{"#E8F2FE", "#e8f2fe", "#000000", "#aB1234"} { + if !isHexColor(valid) { + t.Fatalf("%s should be valid", valid) + } + } + for _, invalid := range []string{"#GG0000", "E8F2FE", "#E8F2F", "#E8F2FEE", ""} { + if isHexColor(invalid) { + t.Fatalf("%s should be invalid", invalid) + } + } +} + +func executeDocStyleCommand(t *testing.T, caller *scriptedToolCaller, args ...string) error { + t.Helper() + installScriptedCaller(t, caller) + root := newDocStyleCommand() + root.SilenceErrors = true + root.SilenceUsage = true + root.SetArgs(args) + return root.Execute() +} + +func TestCrossPlatformCoverageDocStyleCoverSetMutualExclusion(t *testing.T) { + err := executeDocStyleCommand(t, &scriptedToolCaller{}, + "cover", "set", "--node", "n1", "--image", "https://img.test/a.png", "--file-path", "a.png") + if err == nil || !strings.Contains(err.Error(), "mutually exclusive") { + t.Fatalf("err = %v, want mutual exclusion", err) + } +} + +func TestCrossPlatformCoverageDocStyleCoverSetPositionValidation(t *testing.T) { + err := executeDocStyleCommand(t, &scriptedToolCaller{}, + "cover", "set", "--node", "n1", "--image", "https://img.test/a.png", "--position", "1.5") + if err == nil || !strings.Contains(err.Error(), "--position must be within [0,1]") { + t.Fatalf("err = %v, want position range error", err) + } + + caller := &scriptedToolCaller{} + if err := executeDocStyleCommand(t, caller, + "cover", "set", "--node", "n1", "--image", "https://img.test/a.png", "--position", "0.5"); err != nil { + t.Fatal(err) + } + if caller.calls != 1 { + t.Fatalf("calls = %d, want 1", caller.calls) + } +} + +func TestCrossPlatformCoverageDocStyleCoverSetFileDryRun(t *testing.T) { + path := writeTempImage(t, "cover.png") + caller := &scriptedToolCaller{dry: true} + if err := executeDocStyleCommand(t, caller, "cover", "set", "--node", "n1", "--file", path); err != nil { + t.Fatal(err) + } +} + +func stubHTTPPutFile(t *testing.T, err error) { + t.Helper() + old := httpPutFile + httpPutFile = func(context.Context, string, map[string]string, string, int64) error { return err } + t.Cleanup(func() { httpPutFile = old }) +} + +func TestCrossPlatformCoverageDocStyleCoverSetFileUpload(t *testing.T) { + path := writeTempImage(t, "cover.png") + stubHTTPPutFile(t, nil) + caller := &scriptedToolCaller{steps: []scriptedToolStep{ + {text: `{"uploadUrl":"https://oss.test/put","resourceId":"res-1"}`}, + {text: `{}`}, + }} + if err := executeDocStyleCommand(t, caller, "cover", "set", "--node", "n1", "--file", path); err != nil { + t.Fatal(err) + } + if caller.calls != 2 { + t.Fatalf("calls = %d, want 2 (upload info + style set)", caller.calls) + } +} + +func TestCrossPlatformCoverageDocStyleCoverSetFileUploadFailure(t *testing.T) { + path := writeTempImage(t, "cover.png") + stubHTTPPutFile(t, errors.New("oss down")) + caller := &scriptedToolCaller{steps: []scriptedToolStep{ + {text: `{"uploadUrl":"https://oss.test/put","resourceId":"res-1"}`}, + }} + err := executeDocStyleCommand(t, caller, "cover", "set", "--node", "n1", "--file", path) + if err == nil || !strings.Contains(err.Error(), "oss down") { + t.Fatalf("err = %v, want oss failure", err) + } +} + +func TestCrossPlatformCoverageUploadDocStyleImageFailures(t *testing.T) { + caller := &scriptedToolCaller{steps: []scriptedToolStep{{err: errors.New("cred failed")}}} + installScriptedCaller(t, caller) + if _, err := uploadDocStyleImage(context.Background(), "n1", "a.png", "a.png", "image/png", 10); err == nil { + t.Fatal("cred failure returned nil") + } + + caller = &scriptedToolCaller{steps: []scriptedToolStep{{text: `not json`}}} + installScriptedCaller(t, caller) + if _, err := uploadDocStyleImage(context.Background(), "n1", "a.png", "a.png", "image/png", 10); err == nil { + t.Fatal("invalid upload info returned nil") + } +} + +func TestCrossPlatformCoverageValidateCoverImageFileSizeLimit(t *testing.T) { + // 上限以内的正常图片通过校验。 + path := writeTempImage(t, "cover.png") + if _, _, err := validateCoverImageFile(path); err != nil { + t.Fatal(err) + } + + // os.Truncate 生成超过上限 1 字节的稀疏文件(瞬时创建,不占实际磁盘)。 + if err := os.Truncate(path, docStyleCoverMaxBytes+1); err != nil { + t.Fatal(err) + } + _, _, err := validateCoverImageFile(path) + if err == nil || !strings.Contains(err.Error(), "封面图片文件过大") || !strings.Contains(err.Error(), "20 MiB") { + t.Fatalf("err = %v, want size limit error", err) + } + + // 恰好等于上限时仍应通过。 + if err := os.Truncate(path, docStyleCoverMaxBytes); err != nil { + t.Fatal(err) + } + if _, _, err := validateCoverImageFile(path); err != nil { + t.Fatalf("size == limit should pass, got %v", err) + } +} + +func TestCrossPlatformCoverageDocStyleCoverSetSizeLimitViaCommand(t *testing.T) { + path := writeTempImage(t, "cover.png") + if err := os.Truncate(path, docStyleCoverMaxBytes+1); err != nil { + t.Fatal(err) + } + caller := &scriptedToolCaller{} + err := executeDocStyleCommand(t, caller, "cover", "set", "--node", "n1", "--file", path) + if err == nil || !strings.Contains(err.Error(), "封面图片文件过大") { + t.Fatalf("err = %v, want size limit error", err) + } + if caller.calls != 0 { + t.Fatalf("calls = %d, want 0 (fail before any network request)", caller.calls) + } +} + +func TestCrossPlatformCoverageDocStyleCoverSetNilContextFallback(t *testing.T) { + // 直接调用 RunE(不经 Execute),cmd.Context() 为 nil,覆盖 context.Background() 兜底分支。 + path := writeTempImage(t, "cover.png") + stubHTTPPutFile(t, nil) + caller := &scriptedToolCaller{steps: []scriptedToolStep{ + {text: `{"uploadUrl":"https://oss.test/put","resourceId":"res-1"}`}, + {text: `{}`}, + }} + installScriptedCaller(t, caller) + root := newDocStyleCommand() + coverSet, _, err := root.Find([]string{"cover", "set"}) + if err != nil { + t.Fatal(err) + } + if coverSet.Context() != nil { + t.Fatal("expected nil context on unexecuted command") + } + if err := coverSet.Flags().Set("node", "n1"); err != nil { + t.Fatal(err) + } + if err := coverSet.Flags().Set("file", path); err != nil { + t.Fatal(err) + } + if err := runDocStyleCoverSet(coverSet, nil); err != nil { + t.Fatal(err) + } + if caller.calls != 2 { + t.Fatalf("calls = %d, want 2 (upload info + style set)", caller.calls) + } +} + +func TestCrossPlatformCoverageDocStyleCoverSetUsesCommandContext(t *testing.T) { + // 经 ExecuteContext 执行时,上传流程应使用命令上下文;scriptedToolCaller 忽略 ctx, + // 故此处做行为性断言:正常上下文下上传路径成功走通(覆盖 ctx := cmd.Context() 分支)。 + path := writeTempImage(t, "cover.png") + stubHTTPPutFile(t, nil) + caller := &scriptedToolCaller{steps: []scriptedToolStep{ + {text: `{"uploadUrl":"https://oss.test/put","resourceId":"res-1"}`}, + {text: `{}`}, + }} + installScriptedCaller(t, caller) + root := newDocStyleCommand() + root.SilenceErrors = true + root.SilenceUsage = true + root.SetArgs([]string{"cover", "set", "--node", "n1", "--file", path}) + if err := root.ExecuteContext(context.Background()); err != nil { + t.Fatal(err) + } + if caller.calls != 2 { + t.Fatalf("calls = %d, want 2 (upload info + style set)", caller.calls) + } +} diff --git a/internal/helpers/drive.go b/internal/helpers/drive.go index 6b82fb0b..87211ed1 100644 --- a/internal/helpers/drive.go +++ b/internal/helpers/drive.go @@ -302,9 +302,68 @@ func newDriveCommand() *cobra.Command { dws drive list --workspace dws drive list --workspace --folder `, RunE: func(cmd *cobra.Command, args []string) error { + pattern, _ := cmd.Flags().GetString("pattern") + + depth, _ := cmd.Flags().GetInt("depth") + + // --versions 模式:列出文件历史版本(仅普通文件) + // 先于 --depth 校验执行:versions 模式合法使用 --limit, + // 不应被「--limit 与 --depth 不兼容」的误导性报错拦截。 + if cmd.Flags().Changed("versions") { + if cmd.Flags().Changed("depth") && depth > 1 { + return &CLIError{ + Code: CodeInvalidParam, + Message: "--versions 与 --depth 不能同时使用", + } + } + if pattern != "" { + return &CLIError{ + Code: CodeInvalidParam, + Message: "--versions 与 --pattern 不能同时使用", + } + } + nodeID, err := mustFlagOrFallback(cmd, "node", "url", "id", "node-id", "doc-id", "file-id") + if err != nil { + return err + } + toolArgs := map[string]any{"nodeId": nodeID} + if v, _ := cmd.Flags().GetInt("limit"); v > 0 { + toolArgs["maxResults"] = v + } + if v := flagOrFallback(cmd, "cursor", "next-token", "page-token"); v != "" { + toolArgs["nextCursor"] = v + } + return callMCPToolOnServer("drive", "list_file_versions", toolArgs) + } + + if cmd.Flags().Changed("depth") { + if err := validateDriveListDepth(cmd, depth); err != nil { + return err + } + } + // 如果指定了 --workspace,路由到文档空间(doc MCP server) workspaceID := flagOrFallback(cmd, "workspace", "workspace-id") if workspaceID != "" { + // depth>1 时 --pattern 放开(先递归后过滤);--order-by/--space-id/--thumbnail + // 知识库无对应参数,静默忽略。 + if depth > 1 { + quiet, _ := cmd.Flags().GetBool("quiet") + baseArgs := map[string]any{"workspaceId": workspaceID} + rootFolder := docFolderFlag(cmd, "node", "file-id") + if rootFolder != "" { + if err := validateDocFolderID(rootFolder); err != nil { + return err + } + } + return runDriveListDepth(cmd, newDocDepthRoute(), baseArgs, rootFolder, depth, pattern, quiet) + } + if pattern != "" { + return &CLIError{ + Code: CodeInvalidParam, + Message: "--pattern 仅适用于钉盘文件列表,不能与 --workspace 同时使用", + } + } toolArgs := map[string]any{"workspaceId": workspaceID} if folder := docFolderFlag(cmd, "node", "file-id"); folder != "" { if err := validateDocFolderID(folder); err != nil { @@ -321,6 +380,30 @@ func newDriveCommand() *cobra.Command { return callMCPToolOnServer("doc", "list_nodes", toolArgs) } + if depth > 1 { + quiet, _ := cmd.Flags().GetBool("quiet") + baseArgs := map[string]any{} + if v, _ := cmd.Flags().GetString("space-id"); v != "" { + baseArgs["spaceId"] = v + } + if v, _ := cmd.Flags().GetString("order-by"); v != "" { + baseArgs["orderBy"] = v + } + if v, _ := cmd.Flags().GetString("order"); v != "" { + baseArgs["order"] = v + } + if v, _ := cmd.Flags().GetBool("thumbnail"); v { + baseArgs["withThumbnail"] = true + } + rootFolder := flagOrFallback(cmd, "folder", "parent-id") + if rootFolder != "" { + if err := validateDriveParentID(rootFolder); err != nil { + return err + } + } + return runDriveListDepth(cmd, newDrivePanDepthRoute(), baseArgs, rootFolder, depth, pattern, quiet) + } + // 默认路由:钉盘文件列表 maxResults, _ := cmd.Flags().GetInt("limit") if !cmd.Flags().Changed("limit") { @@ -451,6 +534,73 @@ func newDriveCommand() *cobra.Command { }, } + driveDownloadVersionCmd := &cobra.Command{ + Use: "download-version", + Short: "下载文件历史版本到本地", + Long: `下载钉盘文件的指定历史版本到本地(两步下载流程)。 + +仅适用于普通文件(如 pdf、docx、xlsx、png 等): + 钉钉在线文档(adoc)请使用 dws doc version 系列命令 + 钉钉在线表格(axls)请使用 dws sheet version 系列命令 + +流程: + 1. 获取历史版本下载 URL 和签名请求头 (download_file_version) + 2. HTTP GET 下载文件二进制内容到本地 + +版本号从 dws drive list --node --versions 获取。`, + Example: ` dws drive download-version --node --version 3 --output ./report_v3.pdf + dws drive download-version --node --version 3 --output ~/downloads/`, + RunE: func(cmd *cobra.Command, args []string) error { + fileID, err := mustFlagOrFallback(cmd, "node", "url", "id", "node-id", "doc-id", "file-id") + if err != nil { + return err + } + versionNum, _ := cmd.Flags().GetInt("version") + if versionNum <= 0 { + return fmt.Errorf("--version 必须为正整数,当前值: %d(版本号从 drive list --versions 获取)", versionNum) + } + outputPath, _ := cmd.Flags().GetString("output") + if outputPath == "" { + return fmt.Errorf("flag --output is required") + } + + if deps.Caller.DryRun() { + deps.Out.PrintKeyValue("操作", "下载文件历史版本") + deps.Out.PrintKeyValue("节点ID", fileID) + deps.Out.PrintKeyValue("版本号", fmt.Sprintf("%d", versionNum)) + deps.Out.PrintKeyValue("输出", outputPath) + return nil + } + + ctx := context.Background() + deps.Out.PrintInfo("[1/2] 获取历史版本下载链接...") + text, err := callMCPToolReturnTextOnServer(ctx, "drive", "download_file_version", map[string]any{ + "nodeId": fileID, + "version": versionNum, + }) + if err != nil { + return err + } + resourceURL, dlHeaders, err := parseDownloadInfo(text) + if err != nil { + return err + } + if fi, statErr := os.Stat(outputPath); statErr == nil && fi.IsDir() { + filename := extractFileNameFromResponse(text) + if filename == "" { + filename = inferFilename(resourceURL) + } + outputPath = filepath.Join(outputPath, filename) + } + deps.Out.PrintInfo(fmt.Sprintf("[2/2] 下载文件到 %s ...", outputPath)) + if err := httpGetFile(ctx, resourceURL, dlHeaders, outputPath); err != nil { + return err + } + deps.Out.PrintInfo(fmt.Sprintf("下载完成: %s", outputPath)) + return nil + }, + } + driveMkdirCmd := &cobra.Command{ Use: "mkdir", Short: "创建文件夹", @@ -547,6 +697,11 @@ func newDriveCommand() *cobra.Command { driveListCmd.Flags().String("order-by", "", "排序字段: createTime|modifyTime|name (可选,仅钉盘)") driveListCmd.Flags().String("order", "", "排序方向: asc|desc,默认 desc (可选,仅钉盘)") driveListCmd.Flags().Bool("thumbnail", false, "是否返回缩略图信息 (可选,仅钉盘)") + driveListCmd.Flags().Bool("versions", false, "列出文件历史版本而非文件列表 (需配合 --node)") + driveListCmd.Flags().String("node", "", "文件 ID (dentryUuid) 或 URL (--versions 模式下必填)") + driveListCmd.Flags().String("pattern", "", "按名称通配过滤结果,如 \"*日报*\" (客户端过滤) (可选)") + driveListCmd.Flags().Int("depth", 1, "递归列出子目录层级,默认 1(仅当前层),最大 5;与 --cursor/--limit 互斥;与 --workspace 组合时走知识库递归 (可选)") + driveListCmd.Flags().Bool("quiet", false, "关闭递归进度输出(stderr),不影响 stdout JSON (仅 --depth>1 时有效) (可选)") driveInfoCmd.Flags().String("node", "", "节点 ID (dentryUuid) (必填)") driveInfoCmd.Flags().String("space-id", "", "节点所属空间 ID (可选)") @@ -555,6 +710,14 @@ func newDriveCommand() *cobra.Command { driveDownloadCmd.Flags().String("space-id", "", "文件所属空间 ID (可选)") driveDownloadCmd.Flags().String("output", "", "本地保存路径 (文件路径或目录,必填)") + driveDownloadVersionCmd.Flags().String("node", "", "文件 ID (dentryUuid) 或 URL (必填)") + driveDownloadVersionCmd.Flags().Int("version", 0, "历史版本号 (必填,正整数,从 drive list --versions 获取)") + driveDownloadVersionCmd.Flags().String("output", "", "本地保存路径 (文件路径或目录,必填)") + for _, alias := range []string{"url", "id", "node-id", "doc-id", "file-id"} { + driveDownloadVersionCmd.Flags().String(alias, "", "") + _ = driveDownloadVersionCmd.Flags().MarkHidden(alias) + } + driveMkdirCmd.Flags().String("name", "", "文件夹名称,最长 50 字符 (必填)") driveMkdirCmd.Flags().String("space-id", "", "目标空间 ID,不传则使用「我的文件」 (可选)") driveMkdirCmd.Flags().String("folder", "", "父节点 ID (dentryUuid),不传则在空间根目录下创建 (可选)") @@ -1124,7 +1287,151 @@ func newDriveCommand() *cobra.Command { _ = c.Flags().MarkHidden("file-id") } - drivePermissionCmd.AddCommand(drivePermAddCmd, drivePermUpdateCmd, drivePermListCmd, drivePermRemoveCmd) + drivePermTransferOwnerCmd := &cobra.Command{ + Use: "transfer-owner", + Short: "[危险] 转交所有者", + Long: `转交文档或知识库的所有者给指定用户。此操作不可逆,执行前需要确认。 + +--node 和 --workspace 二选一。转交后原所有者保留角色由 --reserve-role 指定。 +使用 --yes 跳过确认时,--reserve-role 和 --recursive 必须显式指定。`, + Example: ` dws drive permission transfer-owner --node DOC_ID --new-owner uid123 --reserve-role EDITOR --recursive=false --yes`, + RunE: func(cmd *cobra.Command, args []string) error { + nodeID, _ := mustFlagOrFallback(cmd, "node", "url", "id", "node-id", "doc-id", "file-id") + workspaceID := flagOrFallback(cmd, "workspace", "workspace-id") + if nodeID == "" && workspaceID == "" { + return fmt.Errorf("--node or --workspace is required") + } + // 不可逆高危操作:帮助文档承诺二选一,两者同传直接失败,绝不静默择一。 + if nodeID != "" && workspaceID != "" { + return fmt.Errorf("--node and --workspace are mutually exclusive; specify exactly one") + } + if err := validateRequiredFlags(cmd, "new-owner"); err != nil { + return err + } + newOwnerID := mustGetFlag(cmd, "new-owner") + + yesMode, _ := cmd.Flags().GetBool("yes") + if yesMode { + if !cmd.Flags().Changed("reserve-role") { + return fmt.Errorf("--reserve-role is required when using --yes") + } + if !cmd.Flags().Changed("recursive") { + return fmt.Errorf("--recursive is required when using --yes") + } + } + + if commandDryRun(cmd) { + if deps.Caller.Format() == "json" { + payload := map[string]any{ + "dry_run": true, + "executed": false, + "operation": "转交所有者", + "newOwnerId": newOwnerID, + } + if nodeID != "" { + payload["nodeId"] = nodeID + } else { + payload["workspaceId"] = workspaceID + } + return deps.Out.PrintJSON(payload) + } + deps.Out.PrintKeyValue("操作", "转交所有者") + deps.Out.PrintKeyValue("新所有者", newOwnerID) + return nil + } + + reserveRole := mustGetFlag(cmd, "reserve-role") + recursive, _ := cmd.Flags().GetBool("recursive") + + target := nodeID + if target == "" { + target = workspaceID + } + if !yesMode && !confirmDangerousAction(cmd, "转交所有者", target) { + return nil + } + + toolArgs := map[string]any{"newOwnerId": newOwnerID} + if nodeID != "" { + toolArgs["nodeId"] = nodeID + } else { + toolArgs["workspaceId"] = workspaceID + } + if reserveRole != "" { + toolArgs["reserveOldOwnerRole"] = reserveRole + } + if cmd.Flags().Changed("recursive") { + toolArgs["recursiveChange"] = recursive + } + return callMCPToolOnServer("doc", "transfer_owner", toolArgs) + }, + } + drivePermTransferOwnerCmd.Flags().String("node", "", "目标节点 ID 或 URL(与 --workspace 二选一)") + drivePermTransferOwnerCmd.Flags().String("workspace", "", "目标知识库 ID 或 URL(与 --node 二选一)") + drivePermTransferOwnerCmd.Flags().String("new-owner", "", "新所有者的用户 userId (必填)") + drivePermTransferOwnerCmd.Flags().String("reserve-role", "", "转交后原所有者保留角色: MANAGER / EDITOR / DOWNLOADER / READER / NONE") + drivePermTransferOwnerCmd.Flags().Bool("recursive", false, "是否递归变更所有子节点的所有者") + + drivePermApplyInfoCmd := &cobra.Command{ + Use: "apply-info", + Short: "查询节点可申请的角色与审批人", + Example: ` dws drive permission apply-info --node DOC_ID`, + RunE: func(cmd *cobra.Command, args []string) error { + nodeID, err := mustFlagOrFallback(cmd, "node", "url", "id", "node-id", "doc-id", "file-id") + if err != nil { + return err + } + return callMCPToolOnServer("drive", "query_permission_apply_info", map[string]any{"nodeId": nodeID}) + }, + } + drivePermApplyInfoCmd.Flags().String("node", "", "目标节点 ID 或 URL (必填)") + + drivePermApplyCmd := &cobra.Command{ + Use: "apply", + Short: "发起权限申请", + Long: `向指定节点的审批人发起权限申请。建议先用 apply-info 获取可申请角色与审批人。 + +注意: 本命令会真实通知审批人,Agent 必须先获得用户明确同意后再执行。`, + Example: ` dws drive permission apply --node DOC_ID --role READER --users uid1 + dws drive permission apply --node DOC_ID --role EDITOR --users uid1,uid2 --reason "需要编辑该文档"`, + RunE: func(cmd *cobra.Command, args []string) error { + nodeID, err := mustFlagOrFallback(cmd, "node", "url", "id", "node-id", "doc-id", "file-id") + if err != nil { + return err + } + if err := validateRequiredFlags(cmd, "role"); err != nil { + return err + } + userIds, err := collectUserIDs(cmd) + if err != nil { + return err + } + toolArgs := map[string]any{ + "nodeId": nodeID, + "roleId": normalizePermissionRole(mustGetFlag(cmd, "role")), + "receivers": userIds, + } + if v := mustGetFlag(cmd, "notify-mode"); v != "" { + toolArgs["notifyMode"] = v + } + if v := mustGetFlag(cmd, "reason"); v != "" { + toolArgs["reason"] = v + } + if !commandDryRun(cmd) && !confirmDangerousAction(cmd, "发起权限申请", fmt.Sprintf("节点 %s(将真实通知审批人 %s)", nodeID, strings.Join(userIds, ","))) { + return nil + } + return callMCPToolOnServer("drive", "apply_permission", toolArgs) + }, + } + drivePermApplyCmd.Flags().String("node", "", "目标节点 ID 或 URL (必填)") + drivePermApplyCmd.Flags().String("role", "", "申请的角色: EDITOR / DOWNLOADER / READER (必填)") + drivePermApplyCmd.Flags().String("users", "", "审批人 userId 列表,逗号分隔 (必填)") + drivePermApplyCmd.Flags().String("user", "", "") + _ = drivePermApplyCmd.Flags().MarkHidden("user") + drivePermApplyCmd.Flags().String("notify-mode", "", "通知方式: DEFAULT / MSG_ACCOUNT / SINGLE_CHAT") + drivePermApplyCmd.Flags().String("reason", "", "申请理由,最长 200 字符") + + drivePermissionCmd.AddCommand(drivePermAddCmd, drivePermUpdateCmd, drivePermListCmd, drivePermRemoveCmd, drivePermTransferOwnerCmd, drivePermApplyInfoCmd, drivePermApplyCmd) // --node 隐藏别名(保持与迁移前 doc 命令一致) driveNodeAliasCmds := []*cobra.Command{ @@ -1397,11 +1704,124 @@ func newDriveCommand() *cobra.Command { driveRecentCmd.Flags().String("page-token", "", "") _ = driveRecentCmd.Flags().MarkHidden("page-token") + // ── drive star (文档收藏管理) ── + driveStarCmd := &cobra.Command{ + Use: "star", + Short: "文档收藏管理", + RunE: groupRunE, + } + driveStarAddCmd := &cobra.Command{ + Use: "add", + Short: "收藏文档", + Example: ` dws drive star add --node `, + RunE: func(cmd *cobra.Command, args []string) error { + nodeID, err := mustFlagOrFallback(cmd, "node", "url", "id", "node-id", "doc-id", "file-id") + if err != nil { + return err + } + return callMCPToolOnServer("drive", "mark_star", map[string]any{"nodeId": nodeID}) + }, + } + driveStarAddCmd.Flags().String("node", "", "文档 ID 或 URL (必填)") + driveStarRemoveCmd := &cobra.Command{ + Use: "remove", + Aliases: []string{"rm"}, + Short: "取消收藏文档", + Example: ` dws drive star remove --node `, + RunE: func(cmd *cobra.Command, args []string) error { + nodeID, err := mustFlagOrFallback(cmd, "node", "url", "id", "node-id", "doc-id", "file-id") + if err != nil { + return err + } + return callMCPToolOnServer("drive", "unmark_star", map[string]any{"nodeId": nodeID}) + }, + } + driveStarRemoveCmd.Flags().String("node", "", "文档 ID 或 URL (必填)") + driveStarListCmd := &cobra.Command{ + Use: "list", + Short: "获取收藏列表", + Example: ` dws drive star list + dws drive star list --content-types doc,sheet --limit 10`, + RunE: func(cmd *cobra.Command, args []string) error { + toolArgs := map[string]any{} + if v, _ := cmd.Flags().GetInt("limit"); v > 0 { + toolArgs["limit"] = v + } + if v, _ := cmd.Flags().GetString("cursor"); v != "" { + toolArgs["cursor"] = v + } + if v, _ := cmd.Flags().GetString("order-by"); v != "" { + toolArgs["orderBy"] = v + } + if v, _ := cmd.Flags().GetString("sort"); v != "" { + toolArgs["sortType"] = v + } + if v, _ := cmd.Flags().GetStringSlice("resource-types"); len(v) > 0 { + toolArgs["supportResourceTypes"] = v + } + if v, _ := cmd.Flags().GetStringSlice("content-types"); len(v) > 0 { + toolArgs["contentTypes"] = v + } + return callMCPToolOnServer("drive", "get_star_list", toolArgs) + }, + } + driveStarListCmd.Flags().Int("limit", 0, "每页条数 (默认 20,最大 20)") + driveStarListCmd.Flags().String("cursor", "", "分页游标") + driveStarListCmd.Flags().String("order-by", "", "排序字段: createTime") + driveStarListCmd.Flags().String("sort", "", "排序方向: asc|desc") + driveStarListCmd.Flags().StringSlice("resource-types", nil, "资源大类: DENTRY, TEAM, WORKSPACE") + driveStarListCmd.Flags().StringSlice("content-types", nil, "内容类型: doc,sheet,ppt,whiteboard,mind,notable,pdf,other,folder") + driveStarCmd.AddCommand(driveStarAddCmd, driveStarRemoveCmd, driveStarListCmd) + + // ── drive cover (获取节点封面地址) ── + driveCoverCmd := &cobra.Command{ + Use: "cover", + Short: "获取节点封面地址", + Example: " dws drive cover --node ", + RunE: func(cmd *cobra.Command, args []string) error { + nodeID, err := mustFlagOrFallback(cmd, "node", "url", "id", "node-id", "doc-id", "file-id") + if err != nil { + return err + } + return callMCPToolOnServer("drive", "get_cover", map[string]any{"nodeId": nodeID}) + }, + } + driveCoverCmd.Flags().String("node", "", "节点 ID (dentryUuid) 或文档 URL (必填)") + + // ── drive revert (回滚文件到指定历史版本) ── + driveRevertCmd := &cobra.Command{ + Use: "revert", + Short: "[危险] 回滚文件到指定历史版本", + Long: `将指定文件回滚到某个历史版本。仅支持普通文件(Word、Excel、PDF、图片等)。 +在线文档请用 dws doc version revert,在线表格请用 dws sheet version revert。`, + Example: ` dws drive revert --node --version 3 --yes`, + RunE: func(cmd *cobra.Command, args []string) error { + nodeID, err := mustFlagOrFallback(cmd, "node", "url", "id", "node-id", "doc-id", "file-id") + if err != nil { + return err + } + versionNum, err := cmd.Flags().GetInt("version") + if err != nil || versionNum <= 0 { + return fmt.Errorf("flag --version is required and must be a positive integer") + } + if !commandDryRun(cmd) && !confirmDangerousAction(cmd, "回滚文件版本", fmt.Sprintf("节点 %s 回滚到版本 %d", nodeID, versionNum)) { + return nil + } + return callMCPToolOnServer("drive", "revert_file_version", map[string]any{ + "nodeId": nodeID, + "version": versionNum, + }) + }, + } + driveRevertCmd.Flags().String("node", "", "文件 ID (dentryUuid) 或 URL (必填)") + driveRevertCmd.Flags().Int("version", 0, "要回滚到的历史版本号 (必填,正整数)") + driveCmd.AddCommand( driveListCmd, driveListSpacesCmd, driveInfoCmd, driveDownloadCmd, + driveDownloadVersionCmd, driveMkdirCmd, driveUploadInfoCmd, driveCommitCmd, @@ -1418,6 +1838,9 @@ func newDriveCommand() *cobra.Command { drivePermissionCmd, drivePublishCmd, recycleCmd, + driveStarCmd, + driveCoverCmd, + driveRevertCmd, // deprecated 兼容命令(Phase 2)— 隐藏,保留向后兼容 driveFolderCmd, ) diff --git a/internal/helpers/drive_depth.go b/internal/helpers/drive_depth.go new file mode 100644 index 00000000..94dbceb6 --- /dev/null +++ b/internal/helpers/drive_depth.go @@ -0,0 +1,562 @@ +package helpers + +import ( + "context" + "encoding/json" + "errors" + "fmt" + "log/slog" + "os" + "os/signal" + "path/filepath" + "sort" + "strings" + "time" + + "github.com/fatih/color" + "github.com/spf13/cobra" +) + +// ────────────────────────────────────────────────────────── +// drive list --depth 递归编排(BFS + 限流补偿) +// +// 服务端无递归 API,CLI 侧 BFS 过渡方案;服务端递归 API 上线后本块整体退役。 +// 钉盘(list_files)与知识库(list_nodes)共用一份路由无关骨架,差异收敛在 driveDepthRoute。 +// ────────────────────────────────────────────────────────── + +const ( + // 50 来源于服务端 MAX_PAGE_SIZE 硬校验(超限抛错不 clamp),服务端放宽后仅需修改此常量。 + driveDepthPageSize = 50 + // 来源与 driveDepthPageSize 不同(list_nodes pageSize 上限),数值碰巧相同但不合并。 + docDepthPageSize = 50 + // API 调用数随目录宽度指数增长,超过报错不 clamp。 + driveDepthMax = 5 + driveDepthMaxItems = 2000 + // Sentinel 限流以 HTTP 200 + body errorCode 返回,transport 层不会自动重试,补偿只能在 BFS 层做。 + driveDepthRateLimitedCode = "invalidRequest.rateLimited" + driveDepthCancelledMsg = "cancelled by user, partial result emitted" +) + +type driveDepthFolder struct { + id string // 钉盘 dentryUuid / 知识库 nodeId + name string + depth int + relPath string + retried bool // 已因 rate_limited 重新入队过一次,不二次补偿 + // 限流重入队时保留失败页游标:已成功页的条目已进 collected, + // 从头重扫会产生重复条目并提前耗尽全局 2000 上限。 + pageToken string +} + +type driveDepthError struct { + Depth int `json:"depth"` + FolderID string `json:"folderId"` + FolderName string `json:"folderName"` + Reason string `json:"reason"` + Message string `json:"message"` +} + +// 取消不是错误:不记 errors[]、不复用 1~6 退出码,固定 130(128+SIGINT)。 +type driveDepthCancelledError struct{} + +func (e *driveDepthCancelledError) Error() string { return driveDepthCancelledMsg } +func (e *driveDepthCancelledError) RawStderr() string { return driveDepthCancelledMsg } +func (e *driveDepthCancelledError) ExitCode() int { return 130 } + +// depth==1 走原有单层路径,仅做范围校验,不触发互斥检查。 +func validateDriveListDepth(cmd *cobra.Command, depth int) error { + if depth < 1 { + return &CLIError{ + Code: CodeInvalidParam, + Message: fmt.Sprintf("--depth 必须为 1~%d 的整数,当前: %d", driveDepthMax, depth), + } + } + if depth > driveDepthMax { + // 不 clamp:静默 clamp 会让用户误以为拿到了完整树。 + return &CLIError{ + Code: CodeInvalidParam, + Message: fmt.Sprintf("DEPTH_EXCEED_MAX: --depth 最大为 %d,当前: %d(API 调用数随目录宽度指数增长,不做静默 clamp)", driveDepthMax, depth), + } + } + if depth == 1 { + return nil + } + // --workspace 不互斥:depth>1 时作为路由开关切到知识库 BFS。 + if v := flagOrFallback(cmd, "cursor", "next-token"); v != "" { + return driveDepthExclusiveError("cursor", "depth>1 时多文件夹合成一份清单,无连续游标") + } + if cmd.Flags().Changed("limit") || cmd.Flags().Changed("max") { + return driveDepthExclusiveError("limit", "递归模式数据量由全局上限 2000 与 --depth 层数控制") + } + return nil +} + +func driveDepthExclusiveError(flag, reason string) error { + return &CLIError{ + Code: CodeInvalidParam, + Message: fmt.Sprintf("--depth 不能与 --%s 同时使用:%s", flag, reason), + } +} + +// 路由绑定差异全部收敛在此,BFS 主循环保持路由无关。 +type driveDepthRoute struct { + serverID string // ""=钉盘自动路由 / "doc"=知识库 + toolName string + pageSize int + // 钉盘契约无 hasMore 字段,只信空 nextToken;知识库契约含 hasMore,显式 false 即停。 + // 钉盘的 hasMore 仅用于「hasMore=true 但 token 为空」异常检测。 + hasMoreAuthoritative bool + buildArgs func(base map[string]any, folderID, pageToken string) map[string]any + parsePage func(text string) (items []map[string]any, nextToken string, hasMore bool) + isFolder func(item map[string]any) bool + itemID func(item map[string]any) string +} + +func (r driveDepthRoute) fetchPage(ctx context.Context, args map[string]any) (string, error) { + if r.serverID == "" { + return callMCPToolReturnText(ctx, r.toolName, args) + } + return callMCPToolReturnTextOnServer(ctx, r.serverID, r.toolName, args) +} + +func newDrivePanDepthRoute() driveDepthRoute { + return driveDepthRoute{ + serverID: "", + toolName: "list_files", + pageSize: driveDepthPageSize, + hasMoreAuthoritative: false, + buildArgs: func(base map[string]any, folderID, pageToken string) map[string]any { + args := map[string]any{"maxResults": float64(driveDepthPageSize)} + for k, v := range base { + args[k] = v + } + if folderID != "" { + args["parentId"] = folderID + } + if pageToken != "" { + args["nextToken"] = pageToken + } + return args + }, + parsePage: parseDriveDepthPage, + isFolder: isDriveDepthFolder, + itemID: func(item map[string]any) string { + id, _ := item["fileId"].(string) + return id + }, + } +} + +// folderId 为空 = 知识库根。 +func newDocDepthRoute() driveDepthRoute { + return driveDepthRoute{ + serverID: "doc", + toolName: "list_nodes", + pageSize: docDepthPageSize, + // 字段缺失(契约违背)时按停处理——保守方向,宁可少翻页不空转。 + hasMoreAuthoritative: true, + buildArgs: func(base map[string]any, folderID, pageToken string) map[string]any { + args := map[string]any{"pageSize": float64(docDepthPageSize)} + for k, v := range base { + args[k] = v + } + if folderID != "" { + args["folderId"] = folderID + } + if pageToken != "" { + args["pageToken"] = pageToken + } + return args + }, + parsePage: parseDocDepthPage, + isFolder: isDocDepthFolder, + itemID: func(item map[string]any) string { + id, _ := item["nodeId"].(string) + return id + }, + } +} + +// SIGINT 检查两点(出队后发首页前 + 翻页循环发每页前),入队是纯内存操作不检查。 +func runDriveListDepth(cmd *cobra.Command, route driveDepthRoute, baseArgs map[string]any, rootFolderID string, maxDepth int, pattern string, quiet bool) error { + if deps.Caller.DryRun() { + return printDriveDepthDryRun(route, baseArgs, maxDepth) + } + + parent := cmd.Context() + if parent == nil { + parent = context.Background() + } + ctx, stop := signal.NotifyContext(parent, os.Interrupt) + defer stop() + + // 兜底服务端「空页 + 非空游标」类 bug:该形态下条目不增长、全局 2000 永不触发。 + // 不用「token 不推进即停」:兜不住每页 token 都不同的空页流。 + maxPagesPerFolder := driveDepthMaxItems/route.pageSize + 1 + + traceID := fmt.Sprintf("drive-depth-%d", time.Now().UnixNano()) + var ( + collected []map[string]any + errs = make([]driveDepthError, 0) + truncated bool + visited = map[string]bool{} + ) + queue := []driveDepthFolder{{id: rootFolderID, depth: 0}} + if rootFolderID != "" { + visited[rootFolderID] = true + } + +bfs: + for len(queue) > 0 { + if ctx.Err() != nil { + return emitDriveDepthCancelled(collected, errs, pattern) + } + folder := queue[0] + queue = queue[1:] + + start := time.Now() + var folderErr error + pageToken := folder.pageToken + pages := 0 + for { + if ctx.Err() != nil { + return emitDriveDepthCancelled(collected, errs, pattern) + } + pages++ + if pages > maxPagesPerFolder { + folderErr = &CLIError{ + Code: CodeMCPToolError, + Message: fmt.Sprintf("pagination anomaly: folder exceeded %d pages with non-empty nextToken, cursor loop suspected, directory may be truncated", maxPagesPerFolder), + } + break + } + args := route.buildArgs(baseArgs, folder.id, pageToken) + text, err := route.fetchPage(ctx, args) + if ctx.Err() != nil { + return emitDriveDepthCancelled(collected, errs, pattern) + } + if err != nil { + folderErr = err + break + } + items, next, hasMore := route.parsePage(text) + for _, item := range items { + name, _ := item["name"].(string) + rel := name + if folder.relPath != "" { + rel = folder.relPath + "/" + name + } + item["depth"] = folder.depth + 1 + item["parentId"] = folder.id // 根级为空串 + item["rel_path"] = rel // 不保证唯一,组树以 parentId 为准 + collected = append(collected, item) + if len(collected) >= driveDepthMaxItems { + // 未访问目录不记 errors[](没失败只是没扫),避免 errors 数组被淹没 + truncated = true + break + } + if route.isFolder(item) && folder.depth+1 < maxDepth { + if id := route.itemID(item); id != "" && !visited[id] { + // 重复条目静默丢弃 + visited[id] = true + queue = append(queue, driveDepthFolder{id: id, name: name, depth: folder.depth + 1, relPath: rel}) + } + } + } + if truncated { + break bfs + } + if hasMore && next == "" { + // 不静默截断——显式落 api_error,让消费方感知数据不完整。 + folderErr = &CLIError{ + Code: CodeMCPToolError, + Message: "pagination anomaly: hasMore=true but nextToken is empty, directory may be truncated", + } + break + } + if next == "" { + break + } + if route.hasMoreAuthoritative && !hasMore { + break + } + pageToken = next + } + + slog.Info("drive list depth folder done", + "traceId", traceID, "depth", folder.depth, "folderId", folder.id, + "latency", time.Since(start).Milliseconds(), "failed", folderErr != nil) + + if folderErr != nil { + if driveDepthUnrecoverable(folderErr) { + // partial 照吐 stdout,错误详情走 stderr,非零退出 + errs = append(errs, newDriveDepthError(folder, folderErr)) + if emitErr := emitDriveDepthResult(collected, errs, truncated, pattern); emitErr != nil { + return emitErr + } + return folderErr + } + if driveDepthErrorCode(folderErr) == driveDepthRateLimitedCode && !folder.retried { + // 限流命中在入口处、失败页本身无副作用;但此前成功页已进 collected, + // 必须从失败页游标续扫而非整目录重扫。不加 sleep/退避。 + folder.retried = true + folder.pageToken = pageToken + queue = append(queue, folder) + continue + } + if folder.depth == 0 && len(collected) == 0 { + return folderErr + } + // 403 / transport 重试耗尽 / 限流重入队仍失败:记 errors[] 跳过 + errs = append(errs, newDriveDepthError(folder, folderErr)) + continue + } + + if !quiet { + display := folder.name + if display == "" { + display = folder.id + } + if display == "" { + display = "" + } + fmt.Fprintf(os.Stderr, "[drive-list] depth=%d folder=%s done\n", folder.depth, display) + } + } + + return emitDriveDepthResult(collected, errs, truncated, pattern) +} + +func emitDriveDepthCancelled(items []map[string]any, errs []driveDepthError, pattern string) error { + if err := emitDriveDepthResult(items, errs, true, pattern); err != nil { + return err + } + return &driveDepthCancelledError{} +} + +// depth>1 不输出 nextToken。 +func emitDriveDepthResult(items []map[string]any, errs []driveDepthError, truncated bool, pattern string) error { + if pattern != "" { + // 先递归后过滤,过滤仅作用于输出项,不阻止文件夹下钻 + filtered := make([]map[string]any, 0, len(items)) + for _, item := range items { + name, _ := item["name"].(string) + if name == "" { + name, _ = item["fileName"].(string) + } + if matchDriveNamePattern(name, pattern) { + filtered = append(filtered, item) + } + } + items = filtered + } + // 排列为 rel_path 树序:BFS 只决定截断时哪些条目入选,树序决定呈现顺序。 + sort.SliceStable(items, func(i, j int) bool { + ri, _ := items[i]["rel_path"].(string) + rj, _ := items[j]["rel_path"].(string) + if ri != rj { + return ri < rj + } + return driveDepthItemID(items[i]) < driveDepthItemID(items[j]) + }) + maxDepth := 0 + for _, item := range items { + if d, ok := item["depth"].(int); ok && d > maxDepth { + maxDepth = d + } + } + if items == nil { + items = []map[string]any{} + } + return deps.Out.PrintJSON(map[string]any{ + "items": items, + "maxDepth": maxDepth, + "truncated": truncated, + "errors": errs, + }) +} + +// 不输出预估调用次数:零调用下平均子文件夹数不可知,硬编码上界会被误读为真实估算。 +func printDriveDepthDryRun(route driveDepthRoute, baseArgs map[string]any, maxDepth int) error { + if deps.Caller.Format() == "json" { + return deps.Out.PrintJSON(map[string]any{ + "dry_run": true, + "executed": false, + "tool": route.toolName, + "baseArgs": baseArgs, + "maxDepth": maxDepth, + "pageSize": route.pageSize, + "totalLimit": driveDepthMaxItems, + "warning": "调用数随目录宽度指数增长", + }) + } + bold := color.New(color.FgYellow, color.Bold) + bold.Println("[DRY-RUN] Preview only, not executed:") + deps.Out.PrintKeyValue("Tool", route.toolName) + argsJSON, _ := json.MarshalIndent(baseArgs, " ", " ") + deps.Out.PrintKeyValue("baseArgs", "\n "+string(argsJSON)) + deps.Out.PrintKeyValue("maxDepth", fmt.Sprintf("%d", maxDepth)) + deps.Out.PrintKeyValue("每页条数", fmt.Sprintf("%d", route.pageSize)) + deps.Out.PrintKeyValue("全局上限", fmt.Sprintf("%d", driveDepthMaxItems)) + deps.Out.PrintWarning("调用数随目录宽度指数增长") + return nil +} + +// 钉盘契约无 hasMore 字段,hasMoreExplicit 仅供「hasMore=true 但 token 为空」异常检测。 +func parseDriveDepthPage(text string) (items []map[string]any, nextToken string, hasMoreExplicit bool) { + var body map[string]any + if json.Unmarshal([]byte(text), &body) != nil { + return nil, "", false + } + target := body + if inner, ok := body["result"].(map[string]any); ok && driveDepthListItemsKey(inner) != "" { + target = inner + } + key := driveDepthListItemsKey(target) + if key == "" { + return nil, "", false + } + arr, _ := target[key].([]any) + for _, it := range arr { + if m, ok := it.(map[string]any); ok { + items = append(items, m) + } + } + nextToken, _ = target["nextToken"].(string) + hasMoreExplicit, _ = target["hasMore"].(bool) + return items, nextToken, hasMoreExplicit +} + +func driveDepthListItemsKey(m map[string]any) string { + for _, k := range []string{"items", "dentryList"} { + if arr, ok := m[k].([]any); ok && arr != nil { + return k + } + } + return "" +} + +// 非法通配符降级为子串匹配,避免 filepath.Match 报错导致过滤静默失效。 +func matchDriveNamePattern(name, pattern string) bool { + if pattern == "" { + return true + } + p := pattern + if !strings.ContainsAny(p, "*?[") { + p = "*" + p + "*" + } + ok, err := filepath.Match(p, name) + if err != nil { + return strings.Contains(name, pattern) + } + return ok +} + +func isDriveDepthFolder(item map[string]any) bool { + for _, key := range []string{"type", "dentryType"} { + if v, ok := item[key].(string); ok && strings.EqualFold(v, "FOLDER") { + return true + } + } + return false +} + +// list_nodes 游标字段叫 nextPageToken,与钉盘的 nextToken 不同名。 +func parseDocDepthPage(text string) (items []map[string]any, nextToken string, hasMore bool) { + var body map[string]any + if json.Unmarshal([]byte(text), &body) != nil { + return nil, "", false + } + arr, _ := body["nodes"].([]any) + for _, it := range arr { + if m, ok := it.(map[string]any); ok { + items = append(items, m) + } + } + nextToken, _ = body["nextPageToken"].(string) + hasMore, _ = body["hasMore"].(bool) + return items, nextToken, hasMore +} + +func isDocDepthFolder(item map[string]any) bool { + v, _ := item["nodeType"].(string) + return strings.EqualFold(v, "folder") +} + +func driveDepthItemID(item map[string]any) string { + for _, key := range []string{"fileId", "nodeId"} { + if s, ok := item[key].(string); ok && s != "" { + return s + } + } + return "" +} + +func driveDepthUnrecoverable(err error) bool { + var cliErr *CLIError + if errors.As(err, &cliErr) { + switch cliErr.Code { + case CodeAuthTokenExpired, CodeAuthNotConfigured, CodeNetworkTimeout, CodeNetworkUnreachable: + return true + } + } + return false +} + +// CLIError.Message 保留了原始响应 JSON。 +func driveDepthErrorCode(err error) string { + var cliErr *CLIError + if !errors.As(err, &cliErr) { + return "" + } + var body map[string]any + if json.Unmarshal([]byte(cliErr.Message), &body) != nil { + return "" + } + for _, key := range []string{"errorCode", "error_code", "code"} { + if s, ok := body[key].(string); ok && s != "" { + return s + } + } + return "" +} + +// 匹配顺序是硬约束:invalidRequest.rateLimited 自带 "invalidRequest." 前缀, +// 若先做 invalidRequest.* 前缀分类,限流会被吞进参数错误类。必须先精确匹配限流, +// 再 forbidden.* 前缀,最后兜底 api_error。未知 reason 按 api_error 兼容。 +func classifyDriveDepthReason(errorCode string) string { + switch { + case errorCode == driveDepthRateLimitedCode: + return "rate_limited" + case strings.HasPrefix(errorCode, "forbidden."): + return "permission_denied" + default: + return "api_error" + } +} + +func newDriveDepthError(folder driveDepthFolder, err error) driveDepthError { + return driveDepthError{ + Depth: folder.depth, + FolderID: folder.id, + FolderName: folder.name, + Reason: classifyDriveDepthReason(driveDepthErrorCode(err)), + Message: driveDepthErrorMessage(err), + } +} + +func driveDepthErrorMessage(err error) string { + var cliErr *CLIError + if errors.As(err, &cliErr) { + var body map[string]any + if json.Unmarshal([]byte(cliErr.Message), &body) == nil { + for _, key := range []string{"errorMsg", "message"} { + if s, ok := body[key].(string); ok && s != "" { + return s + } + } + } + return cliErr.Message + } + return err.Error() +} diff --git a/internal/helpers/drive_depth_test.go b/internal/helpers/drive_depth_test.go new file mode 100644 index 00000000..88a72995 --- /dev/null +++ b/internal/helpers/drive_depth_test.go @@ -0,0 +1,813 @@ +package helpers + +import ( + "bytes" + "context" + "encoding/json" + "errors" + "fmt" + "io" + "os" + "strings" + "testing" + + "github.com/DingTalk-Real-AI/dingtalk-workspace-cli/pkg/edition" + "github.com/spf13/cobra" +) + +func driveDepthTestCmd() *cobra.Command { + cmd := &cobra.Command{Use: "list"} + cmd.Flags().String("cursor", "", "") + cmd.Flags().String("next-token", "", "") + cmd.Flags().Int("limit", 0, "") + cmd.Flags().Int("max", 0, "") + return cmd +} + +func TestCrossPlatformCoverageDriveDepthCancelledError(t *testing.T) { + err := &driveDepthCancelledError{} + if err.Error() != driveDepthCancelledMsg || err.RawStderr() != driveDepthCancelledMsg { + t.Fatalf("messages = %q/%q", err.Error(), err.RawStderr()) + } + if err.ExitCode() != 130 { + t.Fatalf("ExitCode = %d, want 130", err.ExitCode()) + } +} + +func TestCrossPlatformCoverageValidateDriveListDepth(t *testing.T) { + newCmd := func() *cobra.Command { return driveDepthTestCmd() } + + if err := validateDriveListDepth(newCmd(), 0); err == nil { + t.Fatal("depth 0 returned nil") + } + if err := validateDriveListDepth(newCmd(), driveDepthMax+1); err == nil { + t.Fatal("depth above max returned nil") + } + + shallow := newCmd() + _ = shallow.Flags().Set("cursor", "c1") + _ = shallow.Flags().Set("limit", "5") + if err := validateDriveListDepth(shallow, 1); err != nil { + t.Fatalf("depth 1 with cursor/limit returned %v", err) + } + + withCursor := newCmd() + _ = withCursor.Flags().Set("cursor", "c1") + if err := validateDriveListDepth(withCursor, 2); err == nil { + t.Fatal("depth 2 with cursor returned nil") + } + + withNextToken := newCmd() + _ = withNextToken.Flags().Set("next-token", "t1") + if err := validateDriveListDepth(withNextToken, 2); err == nil { + t.Fatal("depth 2 with next-token returned nil") + } + + withLimit := newCmd() + _ = withLimit.Flags().Set("limit", "5") + if err := validateDriveListDepth(withLimit, 2); err == nil { + t.Fatal("depth 2 with limit returned nil") + } + + withMax := newCmd() + _ = withMax.Flags().Set("max", "5") + if err := validateDriveListDepth(withMax, 2); err == nil { + t.Fatal("depth 2 with max returned nil") + } + + if err := validateDriveListDepth(newCmd(), 2); err != nil { + t.Fatalf("depth 2 clean returned %v", err) + } + if err := validateDriveListDepth(newCmd(), driveDepthMax); err != nil { + t.Fatalf("depth max clean returned %v", err) + } +} + +func TestCrossPlatformCoverageDriveDepthRoutesBuildArgs(t *testing.T) { + pan := newDrivePanDepthRoute() + args := pan.buildArgs(map[string]any{"spaceId": "s1"}, "folder1", "tok") + if args["maxResults"] != float64(driveDepthPageSize) || args["spaceId"] != "s1" || + args["parentId"] != "folder1" || args["nextToken"] != "tok" { + t.Fatalf("pan args = %#v", args) + } + empty := pan.buildArgs(nil, "", "") + if _, ok := empty["parentId"]; ok { + t.Fatalf("pan empty folder args = %#v", empty) + } + if _, ok := empty["nextToken"]; ok { + t.Fatalf("pan empty token args = %#v", empty) + } + if id := pan.itemID(map[string]any{"fileId": "f1"}); id != "f1" { + t.Fatalf("pan itemID = %q", id) + } + if id := pan.itemID(map[string]any{}); id != "" { + t.Fatalf("pan missing itemID = %q", id) + } + + doc := newDocDepthRoute() + dargs := doc.buildArgs(map[string]any{"workspaceId": "w1"}, "node1", "pt") + if dargs["pageSize"] != float64(docDepthPageSize) || dargs["workspaceId"] != "w1" || + dargs["folderId"] != "node1" || dargs["pageToken"] != "pt" { + t.Fatalf("doc args = %#v", dargs) + } + dempty := doc.buildArgs(nil, "", "") + if _, ok := dempty["folderId"]; ok { + t.Fatalf("doc empty folder args = %#v", dempty) + } + if id := doc.itemID(map[string]any{"nodeId": "n1"}); id != "n1" { + t.Fatalf("doc itemID = %q", id) + } + if id := doc.itemID(map[string]any{}); id != "" { + t.Fatalf("doc missing itemID = %q", id) + } +} + +func TestCrossPlatformCoverageParseDriveDepthPage(t *testing.T) { + if items, next, hasMore := parseDriveDepthPage(`{invalid`); items != nil || next != "" || hasMore { + t.Fatalf("invalid JSON = %#v %q %v", items, next, hasMore) + } + if items, _, _ := parseDriveDepthPage(`{"foo":1}`); items != nil { + t.Fatalf("missing items key = %#v", items) + } + items, next, hasMore := parseDriveDepthPage(`{"items":[{"name":"a"},42],"nextToken":"t","hasMore":true}`) + if len(items) != 1 || items[0]["name"] != "a" || next != "t" || !hasMore { + t.Fatalf("top-level items = %#v %q %v", items, next, hasMore) + } + items, next, _ = parseDriveDepthPage(`{"result":{"dentryList":[{"name":"b"}],"nextToken":"t2"}}`) + if len(items) != 1 || items[0]["name"] != "b" || next != "t2" { + t.Fatalf("result dentryList = %#v %q", items, next) + } + if items, _, _ := parseDriveDepthPage(`{"result":{"other":1}}`); items != nil { + t.Fatalf("result without items = %#v", items) + } +} + +func TestCrossPlatformCoverageDriveDepthListItemsKey(t *testing.T) { + if k := driveDepthListItemsKey(map[string]any{"items": []any{}}); k != "items" { + t.Fatalf("items key = %q", k) + } + if k := driveDepthListItemsKey(map[string]any{"dentryList": []any{1}}); k != "dentryList" { + t.Fatalf("dentryList key = %q", k) + } + if k := driveDepthListItemsKey(map[string]any{"items": nil}); k != "" { + t.Fatalf("nil items key = %q", k) + } + if k := driveDepthListItemsKey(map[string]any{}); k != "" { + t.Fatalf("missing key = %q", k) + } +} + +func TestCrossPlatformCoverageParseDocDepthPage(t *testing.T) { + if items, next, hasMore := parseDocDepthPage(`{bad`); items != nil || next != "" || hasMore { + t.Fatalf("invalid JSON = %#v %q %v", items, next, hasMore) + } + items, next, hasMore := parseDocDepthPage(`{"nodes":[{"name":"a"},"x"],"nextPageToken":"nt","hasMore":true}`) + if len(items) != 1 || next != "nt" || !hasMore { + t.Fatalf("nodes = %#v %q %v", items, next, hasMore) + } +} + +func TestCrossPlatformCoverageMatchDriveNamePattern(t *testing.T) { + if !matchDriveNamePattern("anything", "") { + t.Fatal("empty pattern should match") + } + if !matchDriveNamePattern("report.xlsx", "*.xlsx") || matchDriveNamePattern("report.csv", "*.xlsx") { + t.Fatal("glob match failed") + } + if !matchDriveNamePattern("weekly-report.xlsx", "report") || matchDriveNamePattern("a.txt", "report") { + t.Fatal("substring wrap failed") + } + if !matchDriveNamePattern("a[b", "[") { + t.Fatal("invalid pattern should fall back to substring") + } +} + +func TestCrossPlatformCoverageDriveDepthItemClassifiers(t *testing.T) { + if !isDriveDepthFolder(map[string]any{"type": "FOLDER"}) { + t.Fatal("type FOLDER not detected") + } + if !isDriveDepthFolder(map[string]any{"dentryType": "folder"}) { + t.Fatal("dentryType folder not detected") + } + if isDriveDepthFolder(map[string]any{"type": "FILE"}) || isDriveDepthFolder(map[string]any{}) { + t.Fatal("non-folder detected as folder") + } + if !isDocDepthFolder(map[string]any{"nodeType": "Folder"}) { + t.Fatal("doc folder not detected") + } + if isDocDepthFolder(map[string]any{"nodeType": "doc"}) { + t.Fatal("doc detected as folder") + } + if id := driveDepthItemID(map[string]any{"fileId": "f"}); id != "f" { + t.Fatalf("fileId = %q", id) + } + if id := driveDepthItemID(map[string]any{"nodeId": "n"}); id != "n" { + t.Fatalf("nodeId = %q", id) + } + if id := driveDepthItemID(map[string]any{"fileId": ""}); id != "" { + t.Fatalf("empty = %q", id) + } +} + +func TestCrossPlatformCoverageDriveDepthUnrecoverable(t *testing.T) { + for _, code := range []string{CodeAuthTokenExpired, CodeAuthNotConfigured, CodeNetworkTimeout, CodeNetworkUnreachable} { + if !driveDepthUnrecoverable(&CLIError{Code: code}) { + t.Fatalf("%s not unrecoverable", code) + } + } + if driveDepthUnrecoverable(&CLIError{Code: CodeMCPToolError}) { + t.Fatal("tool error marked unrecoverable") + } + if driveDepthUnrecoverable(errors.New("plain")) { + t.Fatal("plain error marked unrecoverable") + } +} + +func TestCrossPlatformCoverageDriveDepthErrorCode(t *testing.T) { + if c := driveDepthErrorCode(errors.New("plain")); c != "" { + t.Fatalf("plain = %q", c) + } + if c := driveDepthErrorCode(&CLIError{Message: "not json"}); c != "" { + t.Fatalf("non-JSON = %q", c) + } + for _, tc := range []struct{ body, want string }{ + {`{"errorCode":"a.b"}`, "a.b"}, + {`{"error_code":"c.d"}`, "c.d"}, + {`{"code":"e.f"}`, "e.f"}, + {`{"errorCode":""}`, ""}, + } { + if c := driveDepthErrorCode(&CLIError{Message: tc.body}); c != tc.want { + t.Fatalf("%s = %q, want %q", tc.body, c, tc.want) + } + } +} + +func TestCrossPlatformCoverageClassifyDriveDepthReason(t *testing.T) { + if r := classifyDriveDepthReason(driveDepthRateLimitedCode); r != "rate_limited" { + t.Fatalf("rate limit = %q", r) + } + if r := classifyDriveDepthReason("forbidden.noPermission"); r != "permission_denied" { + t.Fatalf("forbidden = %q", r) + } + if r := classifyDriveDepthReason("other.x"); r != "api_error" { + t.Fatalf("other = %q", r) + } +} + +func TestCrossPlatformCoverageDriveDepthErrorMessage(t *testing.T) { + if m := driveDepthErrorMessage(&CLIError{Message: `{"errorMsg":"boom"}`}); m != "boom" { + t.Fatalf("errorMsg = %q", m) + } + if m := driveDepthErrorMessage(&CLIError{Message: `{"message":"m2"}`}); m != "m2" { + t.Fatalf("message = %q", m) + } + if m := driveDepthErrorMessage(&CLIError{Message: "raw text"}); m != "raw text" { + t.Fatalf("raw = %q", m) + } + if m := driveDepthErrorMessage(errors.New("plain boom")); m != "plain boom" { + t.Fatalf("plain = %q", m) + } +} + +func TestCrossPlatformCoverageNewDriveDepthError(t *testing.T) { + folder := driveDepthFolder{id: "f1", name: "folder one", depth: 2} + derr := newDriveDepthError(folder, &CLIError{Code: CodeMCPToolError, Message: `{"errorCode":"forbidden.x","errorMsg":"denied"}`}) + if derr.Depth != 2 || derr.FolderID != "f1" || derr.FolderName != "folder one" || + derr.Reason != "permission_denied" || derr.Message != "denied" { + t.Fatalf("driveDepthError = %#v", derr) + } +} + +type failingWriter struct{} + +func (failingWriter) Write([]byte) (int, error) { return 0, errors.New("write failed") } + +func installDepthCaller(t *testing.T, caller *scriptedToolCaller) *bytes.Buffer { + t.Helper() + installScriptedCaller(t, caller) + buf := &bytes.Buffer{} + deps.Out.w = buf + return buf +} + +func decodeDepthResult(t *testing.T, buf *bytes.Buffer) map[string]any { + t.Helper() + var out map[string]any + if err := json.Unmarshal(buf.Bytes(), &out); err != nil { + t.Fatalf("output is not JSON: %v\n%s", err, buf.String()) + } + return out +} + +func TestCrossPlatformCoverageEmitDriveDepthResult(t *testing.T) { + out := installDepthCaller(t, &scriptedToolCaller{}) + items := []map[string]any{ + {"fileName": "b-file.xlsx", "rel_path": "b", "depth": 1, "fileId": "f2"}, + {"name": "a-file.xlsx", "rel_path": "a", "depth": 2, "fileId": "f1"}, + {"name": "skip-me.csv", "rel_path": "c", "depth": 1, "fileId": "f3"}, + } + if err := emitDriveDepthResult(items, nil, false, "*.xlsx"); err != nil { + t.Fatal(err) + } + result := decodeDepthResult(t, out) + got := result["items"].([]any) + if len(got) != 2 { + t.Fatalf("filtered items = %#v", got) + } + if got[0].(map[string]any)["rel_path"] != "a" || got[1].(map[string]any)["rel_path"] != "b" { + t.Fatalf("sort order = %#v", got) + } + if result["maxDepth"] != float64(2) { + t.Fatalf("maxDepth = %#v", result["maxDepth"]) + } + if result["truncated"] != false { + t.Fatalf("truncated = %#v", result["truncated"]) + } + + out.Reset() + if err := emitDriveDepthResult(nil, nil, false, ""); err != nil { + t.Fatal(err) + } + result = decodeDepthResult(t, out) + if items, ok := result["items"].([]any); !ok || len(items) != 0 { + t.Fatalf("nil items = %#v", result["items"]) + } + + samePath := []map[string]any{ + {"name": "dup", "rel_path": "p/dup", "fileId": "z9"}, + {"name": "dup", "rel_path": "p/dup", "fileId": "a1"}, + } + out.Reset() + if err := emitDriveDepthResult(samePath, nil, false, ""); err != nil { + t.Fatal(err) + } + got = decodeDepthResult(t, out)["items"].([]any) + if got[0].(map[string]any)["fileId"] != "a1" { + t.Fatalf("tie-break order = %#v", got) + } + + deps.Out.w = failingWriter{} + if err := emitDriveDepthResult(nil, nil, false, ""); err == nil { + t.Fatal("failing writer returned nil") + } +} + +func TestCrossPlatformCoveragePrintDriveDepthDryRun(t *testing.T) { + jsonOut := installDepthCaller(t, &scriptedToolCaller{format: "json", dry: true}) + if err := printDriveDepthDryRun(newDrivePanDepthRoute(), map[string]any{"spaceId": "s1"}, 3); err != nil { + t.Fatal(err) + } + var payload map[string]any + if err := json.Unmarshal(jsonOut.Bytes(), &payload); err != nil { + t.Fatalf("json dry-run output: %v", err) + } + if payload["dry_run"] != true || payload["tool"] != "list_files" || payload["maxDepth"] != float64(3) { + t.Fatalf("json dry-run = %#v", payload) + } + + installDepthCaller(t, &scriptedToolCaller{format: "table", dry: true}) + if err := printDriveDepthDryRun(newDocDepthRoute(), map[string]any{"workspaceId": "w1"}, 2); err != nil { + t.Fatal(err) + } +} + +func useDriveDepthArgs(t *testing.T) { + t.Helper() + old := os.Args + os.Args = []string{"dws", "drive", "list", "--depth", "2"} + t.Cleanup(func() { os.Args = old }) +} + +func runDepthBFS(t *testing.T, caller *scriptedToolCaller, route driveDepthRoute, root string, maxDepth int, pattern string) (map[string]any, error) { + t.Helper() + out := installDepthCaller(t, caller) + cmd := &cobra.Command{Use: "list"} + err := runDriveListDepth(cmd, route, map[string]any{}, root, maxDepth, pattern, true) + return decodeDepthResult(t, out), err +} + +func TestCrossPlatformCoverageRunDriveListDepthDryRun(t *testing.T) { + caller := &scriptedToolCaller{format: "json", dry: true} + out := installDepthCaller(t, caller) + cmd := &cobra.Command{Use: "list"} + if err := runDriveListDepth(cmd, newDrivePanDepthRoute(), map[string]any{}, "", 2, "", true); err != nil { + t.Fatal(err) + } + if caller.calls != 0 { + t.Fatalf("dry-run calls = %d", caller.calls) + } + var payload map[string]any + if err := json.Unmarshal(out.Bytes(), &payload); err != nil { + t.Fatalf("dry-run output: %v", err) + } + if payload["dry_run"] != true { + t.Fatalf("payload = %#v", payload) + } +} + +func TestCrossPlatformCoverageRunDriveListDepthPanBFS(t *testing.T) { + useDriveDepthArgs(t) + caller := &scriptedToolCaller{steps: []scriptedToolStep{ + {text: `{"items":[{"fileId":"fA","name":"dirA","type":"FOLDER"},{"fileId":"fX","name":"x.txt","type":"FILE"}]}`}, + {text: `{"items":[{"fileId":"fY","name":"y.txt","type":"FILE"}]}`}, + }} + out := installDepthCaller(t, caller) + cmd := &cobra.Command{Use: "list"} + if err := runDriveListDepth(cmd, newDrivePanDepthRoute(), map[string]any{}, "", 3, "", false); err != nil { + t.Fatal(err) + } + if caller.calls != 2 { + t.Fatalf("calls = %d, want 2", caller.calls) + } + items := decodeDepthResult(t, out)["items"].([]any) + if len(items) != 3 { + t.Fatalf("items = %#v", items) + } + var dirA, nested map[string]any + for _, raw := range items { + item := raw.(map[string]any) + switch item["rel_path"] { + case "dirA": + dirA = item + case "dirA/y.txt": + nested = item + } + } + if dirA == nil || nested == nil { + t.Fatalf("rel_path tree = %#v", items) + } + if dirA["depth"] != float64(1) || dirA["parentId"] != "" { + t.Fatalf("root item = %#v", dirA) + } + if nested["depth"] != float64(2) || nested["parentId"] != "fA" { + t.Fatalf("nested item = %#v", nested) + } +} + +func TestCrossPlatformCoverageRunDriveListDepthMaxDepthSkipsEnqueue(t *testing.T) { + useDriveDepthArgs(t) + caller := &scriptedToolCaller{steps: []scriptedToolStep{ + {text: `{"items":[{"fileId":"fA","name":"dirA","type":"FOLDER"}]}`}, + }} + result, err := runDepthBFS(t, caller, newDrivePanDepthRoute(), "", 1, "") + if err != nil { + t.Fatal(err) + } + if caller.calls != 1 { + t.Fatalf("calls = %d, want 1", caller.calls) + } + if len(result["items"].([]any)) != 1 { + t.Fatalf("items = %#v", result["items"]) + } +} + +func TestCrossPlatformCoverageRunDriveListDepthDedup(t *testing.T) { + useDriveDepthArgs(t) + caller := &scriptedToolCaller{steps: []scriptedToolStep{ + {text: `{"items":[ + {"fileId":"fA","name":"dirA","type":"FOLDER"}, + {"fileId":"fA","name":"dirA-copy","type":"FOLDER"}, + {"fileId":"","name":"no-id","type":"FOLDER"}, + {"fileId":"fB","name":"dirB","type":"FOLDER"}]}`}, + {text: `{"items":[{"fileId":"f1","name":"a.txt","type":"FILE"}]}`}, + {text: `{"items":[{"fileId":"f2","name":"b.txt","type":"FILE"}]}`}, + }} + result, err := runDepthBFS(t, caller, newDrivePanDepthRoute(), "", 3, "") + if err != nil { + t.Fatal(err) + } + if caller.calls != 3 { + t.Fatalf("calls = %d, want 3 (dedup + empty id skipped)", caller.calls) + } + if len(result["items"].([]any)) != 6 { + t.Fatalf("items = %#v", result["items"]) + } +} + +func TestCrossPlatformCoverageRunDriveListDepthDocRouteAnomaly(t *testing.T) { + caller := &scriptedToolCaller{steps: []scriptedToolStep{ + {text: `{"nodes":[{"nodeId":"nB","name":"folderB","nodeType":"folder"},{"nodeId":"nD","name":"doc1","nodeType":"doc"}],"hasMore":false,"nextPageToken":"ignored"}`}, + {text: `{"nodes":[{"nodeId":"nE","name":"inner","nodeType":"doc"}],"hasMore":true}`}, + }} + result, err := runDepthBFS(t, caller, newDocDepthRoute(), "root1", 3, "") + if err != nil { + t.Fatal(err) + } + if caller.calls != 2 { + t.Fatalf("calls = %d, want 2 (hasMore=false stops authoritative route)", caller.calls) + } + errs := result["errors"].([]any) + if len(errs) != 1 { + t.Fatalf("errors = %#v", result["errors"]) + } + entry := errs[0].(map[string]any) + if entry["folderId"] != "nB" || entry["reason"] != "api_error" { + t.Fatalf("anomaly entry = %#v", entry) + } + if !strings.Contains(entry["message"].(string), "hasMore=true but nextToken is empty") { + t.Fatalf("anomaly message = %#v", entry) + } + if len(result["items"].([]any)) != 3 { + t.Fatalf("items = %#v", result["items"]) + } +} + +func TestCrossPlatformCoverageRunDriveListDepthRateLimitRetry(t *testing.T) { + useDriveDepthArgs(t) + caller := &scriptedToolCaller{steps: []scriptedToolStep{ + {text: `{"items":[{"fileId":"fA","name":"dirA","type":"FOLDER"}]}`}, + {text: `{"errorCode":"invalidRequest.rateLimited","errorMsg":"slow down"}`}, + {text: `{"items":[{"fileId":"f1","name":"a.txt","type":"FILE"}]}`}, + }} + result, err := runDepthBFS(t, caller, newDrivePanDepthRoute(), "", 3, "") + if err != nil { + t.Fatal(err) + } + if caller.calls != 3 { + t.Fatalf("calls = %d, want 3", caller.calls) + } + if errs := result["errors"].([]any); len(errs) != 0 { + t.Fatalf("errors = %#v", errs) + } + if len(result["items"].([]any)) != 2 { + t.Fatalf("items = %#v", result["items"]) + } +} + +func TestCrossPlatformCoverageRunDriveListDepthRateLimitResumesFromFailedPage(t *testing.T) { + useDriveDepthArgs(t) + caller := &depthArgsRecordingCaller{steps: []scriptedToolStep{ + {text: `{"items":[{"fileId":"f1","name":"page1.txt","type":"FILE"}],"nextToken":"p2"}`}, + {text: `{"errorCode":"invalidRequest.rateLimited","errorMsg":"slow down"}`}, + {text: `{"items":[{"fileId":"f2","name":"page2.txt","type":"FILE"}]}`}, + }} + previousDeps := deps + t.Cleanup(func() { deps = previousDeps }) + InitDeps(caller) + out := &bytes.Buffer{} + deps.Out.w = out + deps.Out.errW = io.Discard + cmd := &cobra.Command{Use: "list"} + if err := runDriveListDepth(cmd, newDrivePanDepthRoute(), map[string]any{}, "", 3, "", true); err != nil { + t.Fatal(err) + } + if len(caller.calls) != 3 { + t.Fatalf("calls = %d, want 3", len(caller.calls)) + } + if tok, _ := caller.calls[2]["nextToken"].(string); tok != "p2" { + t.Fatalf("retry call args = %#v, want resume with nextToken=p2", caller.calls[2]) + } + items := decodeDepthResult(t, out)["items"].([]any) + if len(items) != 2 { + t.Fatalf("items = %#v, want 2 without duplicates", items) + } +} + +func TestCrossPlatformCoverageRunDriveListDepthRateLimitExhausted(t *testing.T) { + useDriveDepthArgs(t) + caller := &scriptedToolCaller{steps: []scriptedToolStep{ + {text: `{"items":[{"fileId":"fA","name":"dirA","type":"FOLDER"}]}`}, + {text: `{"errorCode":"invalidRequest.rateLimited","errorMsg":"slow down"}`}, + }} + result, err := runDepthBFS(t, caller, newDrivePanDepthRoute(), "", 3, "") + if err != nil { + t.Fatal(err) + } + if caller.calls != 3 { + t.Fatalf("calls = %d, want 3 (one retry)", caller.calls) + } + errs := result["errors"].([]any) + if len(errs) != 1 { + t.Fatalf("errors = %#v", errs) + } + entry := errs[0].(map[string]any) + if entry["reason"] != "rate_limited" || entry["folderId"] != "fA" { + t.Fatalf("rate limit entry = %#v", entry) + } +} + +func TestCrossPlatformCoverageRunDriveListDepthRootFailure(t *testing.T) { + useDriveDepthArgs(t) + caller := &scriptedToolCaller{steps: []scriptedToolStep{ + {text: `{"errorCode":"invalidRequest.rateLimited","errorMsg":"slow down"}`}, + }} + installDepthCaller(t, caller) + cmd := &cobra.Command{Use: "list"} + err := runDriveListDepth(cmd, newDrivePanDepthRoute(), map[string]any{}, "", 3, "", true) + if err == nil { + t.Fatal("root failure returned nil") + } + if caller.calls != 2 { + t.Fatalf("calls = %d, want 2 (single retry then fail)", caller.calls) + } +} + +func TestCrossPlatformCoverageRunDriveListDepthUnrecoverable(t *testing.T) { + useDriveDepthArgs(t) + caller := &scriptedToolCaller{steps: []scriptedToolStep{ + {text: `{"items":[{"fileId":"fA","name":"dirA","type":"FOLDER"},{"fileId":"fX","name":"x.txt","type":"FILE"}]}`}, + {text: `{"errorCode":"DWS_SERVICE_UNAUTHORIZED"}`}, + }} + out := installDepthCaller(t, caller) + cmd := &cobra.Command{Use: "list"} + err := runDriveListDepth(cmd, newDrivePanDepthRoute(), map[string]any{}, "", 3, "", true) + if err == nil { + t.Fatal("unrecoverable returned nil") + } + var cliErr *CLIError + if !errors.As(err, &cliErr) || cliErr.Code != CodeAuthTokenExpired { + t.Fatalf("err = %T %v", err, err) + } + result := decodeDepthResult(t, out) + if len(result["items"].([]any)) != 2 { + t.Fatalf("partial items = %#v", result["items"]) + } + if len(result["errors"].([]any)) != 1 { + t.Fatalf("errors = %#v", result["errors"]) + } +} + +func TestCrossPlatformCoverageRunDriveListDepthUnrecoverableEmitFailure(t *testing.T) { + useDriveDepthArgs(t) + caller := &scriptedToolCaller{steps: []scriptedToolStep{ + {text: `{"errorCode":"DWS_SERVICE_UNAUTHORIZED"}`}, + }} + installDepthCaller(t, caller) + deps.Out.w = failingWriter{} + cmd := &cobra.Command{Use: "list"} + err := runDriveListDepth(cmd, newDrivePanDepthRoute(), map[string]any{}, "", 3, "", true) + if err == nil || !strings.Contains(err.Error(), "write failed") { + t.Fatalf("err = %v, want emit failure", err) + } +} + +func TestCrossPlatformCoverageRunDriveListDepthPaginationLoop(t *testing.T) { + useDriveDepthArgs(t) + caller := &scriptedToolCaller{steps: []scriptedToolStep{ + {text: `{"items":[],"nextToken":"t","hasMore":true}`}, + }} + installDepthCaller(t, caller) + cmd := &cobra.Command{Use: "list"} + err := runDriveListDepth(cmd, newDrivePanDepthRoute(), map[string]any{}, "", 3, "", true) + if err == nil || !strings.Contains(err.Error(), "cursor loop suspected") { + t.Fatalf("err = %v, want pagination anomaly", err) + } + maxPages := driveDepthMaxItems/driveDepthPageSize + 1 + if caller.calls != maxPages { + t.Fatalf("calls = %d, want %d", caller.calls, maxPages) + } +} + +func TestCrossPlatformCoverageRunDriveListDepthTruncation(t *testing.T) { + useDriveDepthArgs(t) + var sb strings.Builder + sb.WriteString(`{"items":[`) + for i := 0; i < driveDepthMaxItems; i++ { + if i > 0 { + sb.WriteString(",") + } + fmt.Fprintf(&sb, `{"fileId":"f%d","name":"file-%d.txt","type":"FILE"}`, i, i) + } + sb.WriteString(`]}`) + caller := &scriptedToolCaller{steps: []scriptedToolStep{{text: sb.String()}}} + result, err := runDepthBFS(t, caller, newDrivePanDepthRoute(), "", 3, "") + if err != nil { + t.Fatal(err) + } + if result["truncated"] != true { + t.Fatalf("truncated = %#v", result["truncated"]) + } + if len(result["items"].([]any)) != driveDepthMaxItems { + t.Fatalf("items len = %d", len(result["items"].([]any))) + } +} + +func TestCrossPlatformCoverageRunDriveListDepthCancelled(t *testing.T) { + useDriveDepthArgs(t) + caller := &scriptedToolCaller{} + out := installDepthCaller(t, caller) + ctx, cancel := context.WithCancel(context.Background()) + cancel() + cmd := &cobra.Command{Use: "list"} + cmd.SetContext(ctx) + err := runDriveListDepth(cmd, newDrivePanDepthRoute(), map[string]any{}, "", 3, "", true) + var cancelErr *driveDepthCancelledError + if !errors.As(err, &cancelErr) { + t.Fatalf("err = %T %v, want driveDepthCancelledError", err, err) + } + result := decodeDepthResult(t, out) + if result["truncated"] != true { + t.Fatalf("cancelled result = %#v", result) + } +} + +func TestCrossPlatformCoverageRunDriveListDepthCancelledEmitFailure(t *testing.T) { + useDriveDepthArgs(t) + caller := &scriptedToolCaller{} + installDepthCaller(t, caller) + deps.Out.w = failingWriter{} + ctx, cancel := context.WithCancel(context.Background()) + cancel() + cmd := &cobra.Command{Use: "list"} + cmd.SetContext(ctx) + err := runDriveListDepth(cmd, newDrivePanDepthRoute(), map[string]any{}, "", 3, "", true) + if err == nil || !strings.Contains(err.Error(), "write failed") { + t.Fatalf("err = %v, want emit failure", err) + } +} + +func TestCrossPlatformCoverageRunDriveListDepthFinalEmitFailure(t *testing.T) { + useDriveDepthArgs(t) + caller := &scriptedToolCaller{steps: []scriptedToolStep{ + {text: `{"items":[{"fileId":"fX","name":"x.txt","type":"FILE"}]}`}, + }} + installDepthCaller(t, caller) + deps.Out.w = failingWriter{} + cmd := &cobra.Command{Use: "list"} + err := runDriveListDepth(cmd, newDrivePanDepthRoute(), map[string]any{}, "", 3, "", true) + if err == nil || !strings.Contains(err.Error(), "write failed") { + t.Fatalf("err = %v, want emit failure", err) + } +} + +func TestCrossPlatformCoverageRunDriveListDepthPatternFilter(t *testing.T) { + useDriveDepthArgs(t) + caller := &scriptedToolCaller{steps: []scriptedToolStep{ + {text: `{"items":[{"fileId":"fA","name":"dirA","type":"FOLDER"},{"fileId":"fX","name":"x.txt","type":"FILE"}]}`}, + {text: `{"items":[{"fileId":"fY","name":"keep.xlsx","type":"FILE"}]}`}, + }} + result, err := runDepthBFS(t, caller, newDrivePanDepthRoute(), "", 3, "*.xlsx") + if err != nil { + t.Fatal(err) + } + items := result["items"].([]any) + if len(items) != 1 || items[0].(map[string]any)["name"] != "keep.xlsx" { + t.Fatalf("filtered = %#v", items) + } +} + +type cancelOnCallCaller struct { + scriptedToolCaller + cancel context.CancelFunc +} + +func (c *cancelOnCallCaller) CallTool(ctx context.Context, productID, toolName string, args map[string]any) (*edition.ToolResult, error) { + c.cancel() + return c.scriptedToolCaller.CallTool(ctx, productID, toolName, args) +} + +func TestCrossPlatformCoverageRunDriveListDepthCancelledInsidePagination(t *testing.T) { + useDriveDepthArgs(t) + ctx, cancel := context.WithCancel(context.Background()) + t.Cleanup(cancel) + route := newDrivePanDepthRoute() + baseParse := route.parsePage + route.parsePage = func(text string) ([]map[string]any, string, bool) { + cancel() + return baseParse(text) + } + caller := &scriptedToolCaller{steps: []scriptedToolStep{ + {text: `{"items":[{"fileId":"fX","name":"x.txt","type":"FILE"}],"nextToken":"more"}`}, + }} + out := installDepthCaller(t, caller) + cmd := &cobra.Command{Use: "list"} + cmd.SetContext(ctx) + err := runDriveListDepth(cmd, route, map[string]any{}, "", 3, "", true) + var cancelErr *driveDepthCancelledError + if !errors.As(err, &cancelErr) { + t.Fatalf("err = %T %v, want driveDepthCancelledError", err, err) + } + result := decodeDepthResult(t, out) + if len(result["items"].([]any)) != 1 || result["truncated"] != true { + t.Fatalf("cancelled partial = %#v", result) + } +} + +func TestCrossPlatformCoverageRunDriveListDepthCancelledAfterFetch(t *testing.T) { + useDriveDepthArgs(t) + ctx, cancel := context.WithCancel(context.Background()) + t.Cleanup(cancel) + caller := &cancelOnCallCaller{ + scriptedToolCaller: scriptedToolCaller{steps: []scriptedToolStep{ + {text: `{"items":[{"fileId":"fX","name":"x.txt","type":"FILE"}]}`}, + }}, + cancel: cancel, + } + previousDeps := deps + t.Cleanup(func() { deps = previousDeps }) + InitDeps(caller) + out := &bytes.Buffer{} + deps.Out.w = out + deps.Out.errW = io.Discard + cmd := &cobra.Command{Use: "list"} + cmd.SetContext(ctx) + err := runDriveListDepth(cmd, newDrivePanDepthRoute(), map[string]any{}, "", 3, "", true) + var cancelErr *driveDepthCancelledError + if !errors.As(err, &cancelErr) { + t.Fatalf("err = %T %v, want driveDepthCancelledError", err, err) + } + result := decodeDepthResult(t, out) + if result["truncated"] != true { + t.Fatalf("cancelled result = %#v", result) + } +} diff --git a/internal/helpers/drive_list_modes_test.go b/internal/helpers/drive_list_modes_test.go new file mode 100644 index 00000000..2bc4b1ac --- /dev/null +++ b/internal/helpers/drive_list_modes_test.go @@ -0,0 +1,227 @@ +package helpers + +import ( + "context" + "io" + "strings" + "testing" + + "github.com/DingTalk-Real-AI/dingtalk-workspace-cli/pkg/edition" + "github.com/spf13/cobra" +) + +type depthArgsRecordingCaller struct { + steps []scriptedToolStep + index int + calls []map[string]any +} + +func (c *depthArgsRecordingCaller) CallTool(_ context.Context, _, _ string, args map[string]any) (*edition.ToolResult, error) { + c.calls = append(c.calls, args) + index := c.index + if index >= len(c.steps) { + index = len(c.steps) - 1 + } + c.index++ + return textToolResult(c.steps[index].text), nil +} + +func (*depthArgsRecordingCaller) Format() string { return "json" } +func (*depthArgsRecordingCaller) DryRun() bool { return false } +func (*depthArgsRecordingCaller) Fields() string { return "" } +func (*depthArgsRecordingCaller) JQ() string { return "" } + +func executeDriveCommand(t *testing.T, caller edition.ToolCaller, args ...string) error { + t.Helper() + previousDeps := deps + t.Cleanup(func() { deps = previousDeps }) + InitDeps(caller) + deps.Out.w = io.Discard + deps.Out.errW = io.Discard + root := newDriveCommand() + root.SilenceErrors = true + root.SilenceUsage = true + root.SetArgs(args) + return root.Execute() +} + +func TestCrossPlatformCoverageDriveListVersionsMode(t *testing.T) { + caller := &guardedMutationCaller{} + err := executeGuardedMutationCommand(t, caller, newDriveCommand, + "list", "--versions", "--node", "node-1", "--limit", "5", "--cursor", "cur-1") + if err != nil { + t.Fatal(err) + } + if len(caller.calls) != 1 { + t.Fatalf("calls = %#v", caller.calls) + } + call := caller.calls[0] + if call.productID != "drive" || call.toolName != "list_file_versions" { + t.Fatalf("call = %#v", call) + } + if call.args["nodeId"] != "node-1" || call.args["maxResults"] != 5 || call.args["nextCursor"] != "cur-1" { + t.Fatalf("args = %#v", call.args) + } +} + +func TestCrossPlatformCoverageDriveListVersionsModeRequiresNode(t *testing.T) { + caller := &guardedMutationCaller{} + err := executeGuardedMutationCommand(t, caller, newDriveCommand, "list", "--versions") + if err == nil { + t.Fatal("versions without --node returned nil") + } + if len(caller.calls) != 0 { + t.Fatalf("calls = %#v", caller.calls) + } +} + +func TestCrossPlatformCoverageDriveListWorkspaceDepthRoutesDocBFS(t *testing.T) { + caller := &depthArgsRecordingCaller{steps: []scriptedToolStep{ + {text: `{"nodes":[{"nodeId":"n1","name":"doc1","nodeType":"doc"}],"hasMore":false}`}, + }} + err := executeDriveCommand(t, caller, + "list", "--workspace", "ws-1", "--depth", "2", "--node", "folder-1") + if err != nil { + t.Fatal(err) + } + if len(caller.calls) != 1 { + t.Fatalf("calls = %#v, want 1", caller.calls) + } + args := caller.calls[0] + if args["workspaceId"] != "ws-1" || args["folderId"] != "folder-1" || args["pageSize"] != float64(docDepthPageSize) { + t.Fatalf("doc BFS args = %#v", args) + } +} + +func TestCrossPlatformCoverageDriveListWorkspaceDepthRejectsNumericFolder(t *testing.T) { + caller := &guardedMutationCaller{} + err := executeGuardedMutationCommand(t, caller, newDriveCommand, + "list", "--workspace", "ws-1", "--depth", "2", "--node", "12345") + if err == nil { + t.Fatal("numeric doc folder returned nil") + } +} + +func TestCrossPlatformCoverageDriveListPanDepthBuildsBaseArgs(t *testing.T) { + useDriveDepthArgs(t) + caller := &depthArgsRecordingCaller{steps: []scriptedToolStep{ + {text: `{"items":[{"fileId":"f1","name":"a.txt","type":"FILE"}]}`}, + }} + err := executeDriveCommand(t, caller, + "list", "--depth", "2", "--space-id", "sp-1", "--order-by", "name", "--order", "asc", "--thumbnail", "--folder", "root-folder") + if err != nil { + t.Fatal(err) + } + if len(caller.calls) != 1 { + t.Fatalf("calls = %#v, want 1", caller.calls) + } + args := caller.calls[0] + if args["spaceId"] != "sp-1" || args["orderBy"] != "name" || args["order"] != "asc" || + args["withThumbnail"] != true || args["parentId"] != "root-folder" || args["maxResults"] != float64(driveDepthPageSize) { + t.Fatalf("pan BFS args = %#v", args) + } +} + +func TestCrossPlatformCoverageDriveListPanDepthRejectsNumericFolder(t *testing.T) { + caller := &guardedMutationCaller{} + err := executeGuardedMutationCommand(t, caller, newDriveCommand, + "list", "--depth", "2", "--folder", "12345") + if err == nil { + t.Fatal("numeric drive folder returned nil") + } +} + +func TestCrossPlatformCoverageDriveTransferOwnerDryRun(t *testing.T) { + caller := &guardedMutationCaller{dryRun: true} + err := executeGuardedMutationCommand(t, caller, newDriveCommand, + "permission", "transfer-owner", "--node", "node-1", "--new-owner", "user-1", "--dry-run") + if err != nil { + t.Fatal(err) + } + if len(caller.calls) != 0 { + t.Fatalf("dry-run calls = %#v", caller.calls) + } +} + +func TestCrossPlatformCoverageDriveTransferOwnerYesRequiresRecursive(t *testing.T) { + caller := &guardedMutationCaller{} + err := executeGuardedMutationCommand(t, caller, newDriveCommand, + "permission", "transfer-owner", "--node", "node-1", "--new-owner", "user-1", "--reserve-role", "EDITOR", "--yes") + if err == nil || !strings.Contains(err.Error(), "--recursive is required") { + t.Fatalf("err = %v, want --recursive required", err) + } +} + +func TestCrossPlatformCoverageDriveTransferOwnerWorkspaceTarget(t *testing.T) { + caller := &guardedMutationCaller{} + err := executeGuardedMutationCommand(t, caller, newDriveCommand, + "permission", "transfer-owner", "--workspace", "ws-1", "--new-owner", "user-1", "--reserve-role", "EDITOR", "--recursive", "--yes") + if err != nil { + t.Fatal(err) + } + if len(caller.calls) != 1 { + t.Fatalf("calls = %#v", caller.calls) + } + call := caller.calls[0] + if call.productID != "doc" || call.toolName != "transfer_owner" { + t.Fatalf("call = %#v", call) + } + if call.args["workspaceId"] != "ws-1" || call.args["newOwnerId"] != "user-1" || + call.args["reserveOldOwnerRole"] != "EDITOR" || call.args["recursiveChange"] != true { + t.Fatalf("args = %#v", call.args) + } + if _, ok := call.args["nodeId"]; ok { + t.Fatalf("unexpected nodeId in %#v", call.args) + } +} + +func TestCrossPlatformCoverageDriveTransferOwnerDeclined(t *testing.T) { + caller := &guardedMutationCaller{} + root := newDriveCommand() + root.SetIn(strings.NewReader("no\n")) + err := executeGuardedMutationCommand(t, caller, func() *cobra.Command { return root }, + "permission", "transfer-owner", "--node", "node-1", "--new-owner", "user-1") + if err != nil { + t.Fatal(err) + } + if len(caller.calls) != 0 { + t.Fatalf("declined calls = %#v", caller.calls) + } +} + +func TestCrossPlatformCoverageDriveCoverRequiresNode(t *testing.T) { + caller := &guardedMutationCaller{} + err := executeGuardedMutationCommand(t, caller, newDriveCommand, "cover") + if err == nil { + t.Fatal("cover without --node returned nil") + } + if len(caller.calls) != 0 { + t.Fatalf("calls = %#v", caller.calls) + } +} + +func TestCrossPlatformCoverageDriveRevertDeclined(t *testing.T) { + caller := &guardedMutationCaller{} + root := newDriveCommand() + root.SetIn(strings.NewReader("no\n")) + err := executeGuardedMutationCommand(t, caller, func() *cobra.Command { return root }, + "revert", "--node", "node-1", "--version", "3") + if err != nil { + t.Fatal(err) + } + if len(caller.calls) != 0 { + t.Fatalf("declined revert calls = %#v", caller.calls) + } +} + +func TestCrossPlatformCoverageDriveTransferOwnerRejectsBothTargets(t *testing.T) { + caller := &guardedMutationCaller{} + err := executeGuardedMutationCommand(t, caller, newDriveCommand, + "permission", "transfer-owner", "--node", "node-1", "--workspace", "ws-1", "--new-owner", "user-1") + if err == nil || !strings.Contains(err.Error(), "mutually exclusive") { + t.Fatalf("err = %v, want mutual exclusion", err) + } + if len(caller.calls) != 0 { + t.Fatalf("calls = %#v, want none", caller.calls) + } +} diff --git a/internal/helpers/drive_review_fixes_test.go b/internal/helpers/drive_review_fixes_test.go new file mode 100644 index 00000000..d3cc7648 --- /dev/null +++ b/internal/helpers/drive_review_fixes_test.go @@ -0,0 +1,263 @@ +// Copyright 2026 Alibaba Group +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. + +package helpers + +import ( + "bytes" + "context" + "encoding/json" + "io" + "reflect" + "strings" + "testing" + + "github.com/DingTalk-Real-AI/dingtalk-workspace-cli/pkg/edition" + "github.com/spf13/cobra" +) + +// executeDriveCommandCapture 执行 drive 命令并捕获 deps.Out 的 stdout 输出, +// 供需要断言 JSON 输出形态的测试使用(等价于 installDepthCaller + Execute)。 +func executeDriveCommandCapture(t *testing.T, caller edition.ToolCaller, args ...string) (*bytes.Buffer, error) { + t.Helper() + previousDeps := deps + t.Cleanup(func() { deps = previousDeps }) + InitDeps(caller) + buf := &bytes.Buffer{} + deps.Out.w = buf + deps.Out.errW = io.Discard + root := newDriveCommand() + if root.PersistentFlags().Lookup("yes") == nil { + root.PersistentFlags().Bool("yes", false, "confirm high-risk operation") + } + if root.PersistentFlags().Lookup("dry-run") == nil { + root.PersistentFlags().Bool("dry-run", false, "preview without executing") + } + root.SilenceErrors = true + root.SilenceUsage = true + root.SetArgs(args) + err := root.Execute() + return buf, err +} + +// ── drive permission apply:确认门禁与帮助文案契约一致 ── + +func TestCrossPlatformCoverageDrivePermissionApplyDeclined(t *testing.T) { + caller := &guardedMutationCaller{} + root := newDriveCommand() + root.SetIn(strings.NewReader("no\n")) + err := executeGuardedMutationCommand(t, caller, func() *cobra.Command { return root }, + "permission", "apply", "--node", "node-1", "--role", "READER", "--users", "u1") + if err != nil { + t.Fatal(err) + } + if len(caller.calls) != 0 { + t.Fatalf("declined apply calls = %#v, want none", caller.calls) + } +} + +func TestCrossPlatformCoverageDrivePermissionApplyYesProceeds(t *testing.T) { + caller := &guardedMutationCaller{} + err := executeGuardedMutationCommand(t, caller, newDriveCommand, + "permission", "apply", "--node", "node-1", "--role", "reader", "--users", "u1,u2", "--yes") + if err != nil { + t.Fatal(err) + } + if len(caller.calls) != 1 { + t.Fatalf("calls = %#v, want exactly one", caller.calls) + } + call := caller.calls[0] + if call.productID != "drive" || call.toolName != "apply_permission" { + t.Fatalf("call = %#v", call) + } + if call.args["nodeId"] != "node-1" || call.args["roleId"] != "READER" { + t.Fatalf("args = %#v", call.args) + } + if !reflect.DeepEqual(call.args["receivers"], []string{"u1", "u2"}) { + t.Fatalf("receivers = %#v", call.args["receivers"]) + } +} + +func TestCrossPlatformCoverageDrivePermissionApplyDryRunNoPromptNoCall(t *testing.T) { + caller := &guardedMutationCaller{dryRun: true} + root := newDriveCommand() + promptOut := &bytes.Buffer{} + root.SetErr(promptOut) + root.SetIn(strings.NewReader("no\n")) + err := executeGuardedMutationCommand(t, caller, func() *cobra.Command { return root }, + "permission", "apply", "--node", "node-1", "--role", "READER", "--users", "u1", "--dry-run") + if err != nil { + t.Fatal(err) + } + if len(caller.calls) != 0 { + t.Fatalf("dry-run apply calls = %#v, want none", caller.calls) + } + if strings.Contains(promptOut.String(), "Confirm action") { + t.Fatalf("dry-run prompted for confirmation: %q", promptOut.String()) + } +} + +// ── drive permission transfer-owner:dry-run JSON 输出且校验先于 dry-run ── + +func TestCrossPlatformCoverageDriveTransferOwnerDryRunJSONNode(t *testing.T) { + caller := &scriptedToolCaller{format: "json", dry: true} + out, err := executeDriveCommandCapture(t, caller, + "permission", "transfer-owner", "--node", "node-1", "--new-owner", "user-1", "--dry-run") + if err != nil { + t.Fatal(err) + } + if caller.calls != 0 { + t.Fatalf("dry-run tool calls = %d, want none", caller.calls) + } + var payload map[string]any + if err := json.Unmarshal(out.Bytes(), &payload); err != nil { + t.Fatalf("dry-run output is not JSON: %v\n%s", err, out.String()) + } + if payload["dry_run"] != true || payload["executed"] != false { + t.Fatalf("payload = %#v", payload) + } + if payload["operation"] != "转交所有者" || payload["newOwnerId"] != "user-1" || payload["nodeId"] != "node-1" { + t.Fatalf("payload = %#v", payload) + } + if _, ok := payload["workspaceId"]; ok { + t.Fatalf("unexpected workspaceId in %#v", payload) + } +} + +func TestCrossPlatformCoverageDriveTransferOwnerDryRunJSONWorkspace(t *testing.T) { + caller := &scriptedToolCaller{format: "json", dry: true} + out, err := executeDriveCommandCapture(t, caller, + "permission", "transfer-owner", "--workspace", "ws-1", "--new-owner", "user-1", "--dry-run") + if err != nil { + t.Fatal(err) + } + var payload map[string]any + if err := json.Unmarshal(out.Bytes(), &payload); err != nil { + t.Fatalf("dry-run output is not JSON: %v\n%s", err, out.String()) + } + if payload["workspaceId"] != "ws-1" || payload["newOwnerId"] != "user-1" { + t.Fatalf("payload = %#v", payload) + } + if _, ok := payload["nodeId"]; ok { + t.Fatalf("unexpected nodeId in %#v", payload) + } +} + +func TestCrossPlatformCoverageDriveTransferOwnerDryRunYesValidatesFirst(t *testing.T) { + caller := &guardedMutationCaller{dryRun: true} + err := executeGuardedMutationCommand(t, caller, newDriveCommand, + "permission", "transfer-owner", "--node", "node-1", "--new-owner", "user-1", "--yes", "--dry-run") + if err == nil || !strings.Contains(err.Error(), "--reserve-role is required") { + t.Fatalf("err = %v, want --reserve-role required even under --dry-run", err) + } + if len(caller.calls) != 0 { + t.Fatalf("calls = %#v, want none", caller.calls) + } +} + +func TestCrossPlatformCoverageDriveTransferOwnerDryRunNonJSON(t *testing.T) { + caller := &scriptedToolCaller{format: "table", dry: true} + _, err := executeDriveCommandCapture(t, caller, + "permission", "transfer-owner", "--node", "node-1", "--new-owner", "user-1", "--dry-run") + if err != nil { + t.Fatal(err) + } + if caller.calls != 0 { + t.Fatalf("dry-run tool calls = %d, want none", caller.calls) + } +} + +// ── drive list:--versions 与 --depth/--pattern 的交互 ── + +func TestCrossPlatformCoverageDriveListVersionsRejectsDepth(t *testing.T) { + caller := &guardedMutationCaller{} + err := executeGuardedMutationCommand(t, caller, newDriveCommand, + "list", "--versions", "--node", "node-1", "--depth", "2", "--limit", "10") + if err == nil || !strings.Contains(err.Error(), "--depth") { + t.Fatalf("err = %v, want explicit --versions/--depth conflict", err) + } + if len(caller.calls) != 0 { + t.Fatalf("calls = %#v, want none", caller.calls) + } +} + +func TestCrossPlatformCoverageDriveListVersionsRejectsPattern(t *testing.T) { + caller := &guardedMutationCaller{} + err := executeGuardedMutationCommand(t, caller, newDriveCommand, + "list", "--versions", "--node", "node-1", "--pattern", "x") + if err == nil || !strings.Contains(err.Error(), "--pattern") { + t.Fatalf("err = %v, want explicit --versions/--pattern conflict", err) + } + if len(caller.calls) != 0 { + t.Fatalf("calls = %#v, want none", caller.calls) + } +} + +func TestCrossPlatformCoverageDriveListVersionsAllowsDepthOne(t *testing.T) { + caller := &guardedMutationCaller{} + err := executeGuardedMutationCommand(t, caller, newDriveCommand, + "list", "--versions", "--node", "node-1", "--depth", "1") + if err != nil { + t.Fatal(err) + } + if len(caller.calls) != 1 || caller.calls[0].toolName != "list_file_versions" { + t.Fatalf("calls = %#v", caller.calls) + } +} + +func TestCrossPlatformCoverageDriveListVersionsWithLimit(t *testing.T) { + caller := &guardedMutationCaller{} + err := executeGuardedMutationCommand(t, caller, newDriveCommand, + "list", "--versions", "--node", "node-1", "--limit", "10") + if err != nil { + t.Fatal(err) + } + if len(caller.calls) != 1 { + t.Fatalf("calls = %#v, want exactly one", caller.calls) + } + call := caller.calls[0] + if call.productID != "drive" || call.toolName != "list_file_versions" { + t.Fatalf("call = %#v", call) + } + if call.args["nodeId"] != "node-1" || call.args["maxResults"] != 10 { + t.Fatalf("args = %#v", call.args) + } +} + +func TestCrossPlatformCoverageDriveDownloadVersionDryRun(t *testing.T) { + caller := &guardedMutationCaller{dryRun: true} + err := executeGuardedMutationCommand(t, caller, newDriveCommand, + "download-version", "--node", "node-1", "--version", "3", "--output", "./x.pdf", "--dry-run") + if err != nil { + t.Fatal(err) + } + if len(caller.calls) != 0 { + t.Fatalf("dry-run calls = %#v, want none", caller.calls) + } +} + +func TestCrossPlatformCoverageDriveDownloadVersionDirectoryOutput(t *testing.T) { + SetHTTPGetFile(func(context.Context, string, map[string]string, string) error { return nil }) + t.Cleanup(func() { SetHTTPGetFile(nil) }) + + dir := t.TempDir() + for _, resp := range []string{ + `{"downloadUrl":"https://oss.test/get/report_v3.pdf","fileName":"报告v3.pdf"}`, + `{"downloadUrl":"https://oss.test/get/inferred_v3.pdf"}`, + } { + caller := &scriptedToolCaller{steps: []scriptedToolStep{{text: resp}}} + installScriptedCaller(t, caller) + root := newDriveCommand() + root.PersistentFlags().Bool("dry-run", false, "") + root.SilenceErrors = true + root.SilenceUsage = true + root.SetArgs([]string{"download-version", "--node", "node-1", "--version", "3", "--output", dir}) + if err := root.Execute(); err != nil { + t.Fatal(err) + } + if caller.calls != 1 { + t.Fatalf("calls = %d, want 1", caller.calls) + } + } +} diff --git a/internal/helpers/drive_star_coverage_test.go b/internal/helpers/drive_star_coverage_test.go new file mode 100644 index 00000000..1264c1a4 --- /dev/null +++ b/internal/helpers/drive_star_coverage_test.go @@ -0,0 +1,168 @@ +package helpers + +import ( + "reflect" + "strings" + "testing" +) + +func TestCrossPlatformCoverageDriveStarCommands(t *testing.T) { + tests := []struct { + name string + args []string + want guardedMutationCall + }{ + { + name: "add", + args: []string{"star", "add", "--node", "node-1"}, + want: guardedMutationCall{ + productID: "drive", + toolName: "mark_star", + args: map[string]any{"nodeId": "node-1"}, + }, + }, + { + name: "add with url value passed through", + args: []string{"star", "add", "--node", "https://alidocs.dingtalk.com/i/nodes/node-2"}, + want: guardedMutationCall{ + productID: "drive", + toolName: "mark_star", + args: map[string]any{"nodeId": "https://alidocs.dingtalk.com/i/nodes/node-2"}, + }, + }, + { + name: "remove", + args: []string{"star", "remove", "--node", "node-3"}, + want: guardedMutationCall{ + productID: "drive", + toolName: "unmark_star", + args: map[string]any{"nodeId": "node-3"}, + }, + }, + { + name: "remove rm alias", + args: []string{"star", "rm", "--node", "node-4"}, + want: guardedMutationCall{ + productID: "drive", + toolName: "unmark_star", + args: map[string]any{"nodeId": "node-4"}, + }, + }, + { + name: "list without filters", + args: []string{"star", "list"}, + want: guardedMutationCall{ + productID: "drive", + toolName: "get_star_list", + args: map[string]any{}, + }, + }, + { + name: "list with all filters", + args: []string{ + "star", "list", + "--limit", "10", + "--cursor", "cur-1", + "--order-by", "createTime", + "--sort", "desc", + "--resource-types", "DENTRY,WORKSPACE", + "--content-types", "doc,sheet", + }, + want: guardedMutationCall{ + productID: "drive", + toolName: "get_star_list", + args: map[string]any{ + "limit": 10, + "cursor": "cur-1", + "orderBy": "createTime", + "sortType": "desc", + "supportResourceTypes": []string{"DENTRY", "WORKSPACE"}, + "contentTypes": []string{"doc", "sheet"}, + }, + }, + }, + } + for _, test := range tests { + test := test + t.Run(test.name, func(t *testing.T) { + caller := &guardedMutationCaller{} + err := executeGuardedMutationCommand(t, caller, newDriveCommand, test.args...) + if err != nil { + t.Fatalf("drive %s returned error: %v", strings.Join(test.args, " "), err) + } + if len(caller.calls) != 1 || !reflect.DeepEqual(caller.calls[0], test.want) { + t.Fatalf("tool calls = %#v, want %#v", caller.calls, test.want) + } + }) + } +} + +func TestCrossPlatformCoverageDriveStarRequiredFlags(t *testing.T) { + tests := []struct { + name string + args []string + wantErr string + }{ + { + name: "add without node", + args: []string{"star", "add"}, + wantErr: "--node", + }, + { + name: "remove without node", + args: []string{"star", "remove"}, + wantErr: "--node", + }, + } + for _, test := range tests { + test := test + t.Run(test.name, func(t *testing.T) { + caller := &guardedMutationCaller{} + err := executeGuardedMutationCommand(t, caller, newDriveCommand, test.args...) + if err == nil || !strings.Contains(err.Error(), test.wantErr) { + t.Fatalf("err = %v, want message containing %q", err, test.wantErr) + } + if len(caller.calls) != 0 { + t.Fatalf("tool calls = %#v, want none", caller.calls) + } + }) + } +} + +func TestCrossPlatformCoverageDriveStarListRecordsRawArgs(t *testing.T) { + caller := &depthArgsRecordingCaller{steps: []scriptedToolStep{ + {text: `{"items":[{"nodeId":"n1","name":"doc1"}],"hasMore":false}`}, + }} + err := executeDriveCommand(t, caller, + "star", "list", "--limit", "5", "--cursor", "cur-9", "--sort", "asc") + if err != nil { + t.Fatal(err) + } + if len(caller.calls) != 1 { + t.Fatalf("calls = %#v, want 1", caller.calls) + } + args := caller.calls[0] + if args["limit"] != 5 || args["cursor"] != "cur-9" || args["sortType"] != "asc" { + t.Fatalf("star list raw args = %#v", args) + } + if _, ok := args["orderBy"]; ok { + t.Fatalf("unexpected orderBy in %#v", args) + } +} + +func TestCrossPlatformCoverageDriveStarAddScriptedSuccess(t *testing.T) { + caller := &scriptedToolCaller{format: "json", steps: []scriptedToolStep{ + {text: `{"success":true}`}, + }} + installScriptedCaller(t, caller) + root := newDriveCommand() + root.SilenceErrors = true + root.SilenceUsage = true + root.SetArgs([]string{"star", "add", "--node", "node-1"}) + if err := root.Execute(); err != nil { + t.Fatalf("star add returned error: %v", err) + } + if caller.calls != 1 { + t.Fatalf("tool calls = %d, want 1", caller.calls) + } +} diff --git a/internal/helpers/output.go b/internal/helpers/output.go index 255be4fe..43e8f43e 100644 --- a/internal/helpers/output.go +++ b/internal/helpers/output.go @@ -144,11 +144,12 @@ func (f *Formatter) PrintTable(headers []string, rows [][]string) { fmt.Fprintf(f.w, "共 %d 条\n", len(rows)) } -func (f *Formatter) PrintSuccess(msg string) { fmt.Fprintf(f.w, "[OK] %s\n", msg) } -func (f *Formatter) PrintError(msg string) { fmt.Fprintf(f.w, "[ERROR] %s\n", msg) } -func (f *Formatter) PrintWarning(msg string) { fmt.Fprintf(f.errW, "[WARN] %s\n", msg) } -func (f *Formatter) PrintInfo(msg string) { fmt.Fprintf(f.w, "[INFO] %s\n", msg) } -func (f *Formatter) PrintDim(msg string) { fmt.Fprintf(f.w, " %s\n", msg) } +func (f *Formatter) PrintSuccess(msg string) { fmt.Fprintf(f.w, "[OK] %s\n", msg) } +func (f *Formatter) PrintError(msg string) { fmt.Fprintf(f.w, "[ERROR] %s\n", msg) } +func (f *Formatter) PrintWarning(msg string) { fmt.Fprintf(f.errW, "[WARN] %s\n", msg) } +func (f *Formatter) PrintInfo(msg string) { fmt.Fprintf(f.w, "[INFO] %s\n", msg) } +func (f *Formatter) PrintProgress(msg string) { fmt.Fprintf(f.errW, "%s\n", msg) } +func (f *Formatter) PrintDim(msg string) { fmt.Fprintf(f.w, " %s\n", msg) } func (f *Formatter) PrintKeyValue(key, value string) { fmt.Fprintf(f.w, "%-16s%s\n", key+":", value) diff --git a/internal/helpers/sheet.go b/internal/helpers/sheet.go index 277b3db8..f0ab41de 100644 --- a/internal/helpers/sheet.go +++ b/internal/helpers/sheet.go @@ -151,6 +151,7 @@ func newSheetCommand() *cobra.Command { // Add all to root root.AddCommand(standaloneCmds...) root.AddCommand(rangeCmd, filterCmd, filterViewCmd, condFormatCmd, chartCmd, templateCmd, pivotTableCmd) + root.AddCommand(newSheetVersionCmd(), newSheetCommentCmd(), newSheetFormulaVerifyCmd()) // This is the reviewed runtime counterpart of the final Sheet Schema // confirmation=user_required set. It is intentionally command-local: there @@ -175,6 +176,8 @@ func newSheetCommand() *cobra.Command { {path: "filter-view delete-criteria", operation: "删除筛选视图列条件", targetHint: "文档、工作表、筛选视图和列"}, {path: "range batch-clear", operation: "批量清除工作表区域", targetHint: "文档、工作表、清除范围和清除类型"}, {path: "range move-to", operation: "移动工作表区域", targetHint: "源工作表范围和目标位置"}, + {path: "version revert", operation: "回滚表格版本", targetHint: "文档和目标版本号"}, + {path: "comment delete", operation: "删除单元格评论", targetHint: "文档和评论 commentKey"}, } for _, guard := range confirmationGuards { attachSheetConfirmationGuard(root, guard.path, guard.operation, guard.targetHint) diff --git a/internal/helpers/sheet_comment.go b/internal/helpers/sheet_comment.go new file mode 100644 index 00000000..76156de9 --- /dev/null +++ b/internal/helpers/sheet_comment.go @@ -0,0 +1,172 @@ +package helpers + +import ( + "github.com/spf13/cobra" +) + +func newSheetCommentCmd() *cobra.Command { + commentCmd := &cobra.Command{ + Use: "comment", + Short: "表格评论 / 单元格评论管理", + Long: `管理钉钉表格的单元格评论:查询评论列表、创建评论、回复评论、更新评论、删除评论。`, + RunE: groupRunE, + } + + commentListCmd := &cobra.Command{ + Use: "list", + Short: "查询表格评论列表", + Example: ` dws sheet comment list --node + dws sheet comment list --node --sheet-id Sheet1 --range A2 + dws sheet comment list --node --resolve-status unresolved`, + RunE: func(cmd *cobra.Command, args []string) error { + nodeID, err := mustFlagOrFallback(cmd, "node", "url", "id", "node-id") + if err != nil { + return err + } + toolArgs := map[string]any{"nodeId": nodeID} + if v, _ := cmd.Flags().GetInt("limit"); cmd.Flags().Changed("limit") { + toolArgs["pageSize"] = v + } + if v := flagOrFallback(cmd, "cursor", "next-token"); v != "" { + toolArgs["nextToken"] = v + } + if v, _ := cmd.Flags().GetString("resolve-status"); v != "" { + toolArgs["resolveStatus"] = v + } + if v, _ := cmd.Flags().GetString("sheet-id"); v != "" { + toolArgs["sheetId"] = v + } + if v, _ := cmd.Flags().GetString("range"); v != "" { + toolArgs["range"] = v + } + return callMCPToolOnServer("doc-comment", "list_sheet_comments", toolArgs) + }, + } + commentListCmd.Flags().String("node", "", "表格文档 ID 或 URL (必填)") + commentListCmd.Flags().Int("limit", 50, "每页返回的评论数量,默认 50,最大 50") + commentListCmd.Flags().String("cursor", "", "分页游标") + commentListCmd.Flags().String("resolve-status", "", "按解决状态过滤: resolved / unresolved") + commentListCmd.Flags().String("sheet-id", "", "工作表 ID 或名称(与 --range 一起按单元格过滤)") + commentListCmd.Flags().String("range", "", "单元格位置 A1 表示法(与 --sheet-id 一起按单元格过滤)") + + commentCreateCmd := &cobra.Command{ + Use: "create", + Short: "创建单元格评论", + 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`, + RunE: func(cmd *cobra.Command, args []string) error { + nodeID, err := mustFlagOrFallback(cmd, "node", "url", "id", "node-id") + if err != nil { + return err + } + if err := validateRequiredFlags(cmd, "content", "sheet-id", "range"); err != nil { + return err + } + toolArgs := map[string]any{ + "nodeId": nodeID, + "content": mustGetFlag(cmd, "content"), + "sheetId": mustGetFlag(cmd, "sheet-id"), + "range": mustGetFlag(cmd, "range"), + } + if v, _ := cmd.Flags().GetString("mention"); v != "" { + toolArgs["mentionedUserIds"] = parseCommentMentionIds(v) + } + return callMCPToolOnServer("doc-comment", "create_sheet_comment", toolArgs) + }, + } + commentCreateCmd.Flags().String("node", "", "表格文档 ID 或 URL (必填)") + commentCreateCmd.Flags().String("content", "", "评论内容 (必填)") + commentCreateCmd.Flags().String("sheet-id", "", "工作表 ID 或名称 (必填)") + commentCreateCmd.Flags().String("range", "", "单元格位置 A1 表示法 (必填)") + commentCreateCmd.Flags().String("mention", "", "被 @ 的用户 uid 列表,逗号分隔") + + commentReplyCmd := &cobra.Command{ + Use: "reply", + Short: "回复单元格评论", + Example: ` dws sheet comment reply --node --comment-key --content "已核实" + dws sheet comment reply --node --comment-key --content "比心" --emoji`, + RunE: func(cmd *cobra.Command, args []string) error { + nodeID, err := mustFlagOrFallback(cmd, "node", "url", "id", "node-id") + if err != nil { + return err + } + if err := validateRequiredFlags(cmd, "content", "comment-key"); err != nil { + return err + } + toolArgs := map[string]any{ + "nodeId": nodeID, + "content": mustGetFlag(cmd, "content"), + "replyCommentKey": mustGetFlag(cmd, "comment-key"), + } + if v, _ := cmd.Flags().GetBool("emoji"); v { + toolArgs["emoji"] = true + } + if v, _ := cmd.Flags().GetString("mention"); v != "" { + toolArgs["mentionedUserIds"] = parseCommentMentionIds(v) + } + return callMCPToolOnServer("doc-comment", "reply_comment", toolArgs) + }, + } + commentReplyCmd.Flags().String("node", "", "表格文档 ID 或 URL (必填)") + commentReplyCmd.Flags().String("content", "", "回复内容 (必填)") + commentReplyCmd.Flags().String("comment-key", "", "被回复评论的 commentKey (必填)") + commentReplyCmd.Flags().Bool("emoji", false, "作为表情贴图回复") + commentReplyCmd.Flags().String("mention", "", "被 @ 的用户 uid 列表,逗号分隔") + + commentUpdateCmd := &cobra.Command{ + Use: "update", + Short: "更新单元格评论", + Example: ` dws sheet comment update --node --comment-key --content "已按最新数据修正"`, + RunE: func(cmd *cobra.Command, args []string) error { + nodeID, err := mustFlagOrFallback(cmd, "node", "url", "id", "node-id") + if err != nil { + return err + } + if err := validateRequiredFlags(cmd, "content", "comment-key"); err != nil { + return err + } + return callMCPToolOnServer("doc-comment", "update_comment", map[string]any{ + "nodeId": nodeID, + "commentKey": mustGetFlag(cmd, "comment-key"), + "content": mustGetFlag(cmd, "content"), + }) + }, + } + commentUpdateCmd.Flags().String("node", "", "表格文档 ID 或 URL (必填)") + commentUpdateCmd.Flags().String("comment-key", "", "待更新评论的 commentKey (必填)") + commentUpdateCmd.Flags().String("content", "", "更新后的评论内容 (必填)") + + commentDeleteCmd := &cobra.Command{ + Use: "delete", + Short: "删除单元格评论", + Example: ` dws sheet comment delete --node --comment-key --yes`, + RunE: func(cmd *cobra.Command, args []string) error { + nodeID, err := mustFlagOrFallback(cmd, "node", "url", "id", "node-id") + if err != nil { + return err + } + if err := validateRequiredFlags(cmd, "comment-key"); err != nil { + return err + } + commentKey := mustGetFlag(cmd, "comment-key") + return callMCPToolOnServer("doc-comment", "delete_comment", map[string]any{ + "nodeId": nodeID, + "commentKey": commentKey, + }) + }, + } + commentDeleteCmd.Flags().String("node", "", "表格文档 ID 或 URL (必填)") + commentDeleteCmd.Flags().String("comment-key", "", "待删除评论的 commentKey (必填)") + + for _, c := range []*cobra.Command{commentListCmd, commentCreateCmd, commentReplyCmd, commentUpdateCmd, commentDeleteCmd} { + c.Flags().String("url", "", "") + c.Flags().String("id", "", "") + c.Flags().String("node-id", "", "") + _ = c.Flags().MarkHidden("url") + _ = c.Flags().MarkHidden("id") + _ = c.Flags().MarkHidden("node-id") + } + + commentCmd.AddCommand(commentListCmd, commentCreateCmd, commentReplyCmd, commentUpdateCmd, commentDeleteCmd) + return commentCmd +} diff --git a/internal/helpers/sheet_formula_verify.go b/internal/helpers/sheet_formula_verify.go new file mode 100644 index 00000000..618608d2 --- /dev/null +++ b/internal/helpers/sheet_formula_verify.go @@ -0,0 +1,156 @@ +package helpers + +import ( + "context" + "encoding/json" + "fmt" + "io" + "os" + "strings" + + "github.com/spf13/cobra" +) + +func newSheetFormulaVerifyCmd() *cobra.Command { + cmd := &cobra.Command{ + Use: "formula-verify", + Short: "校验表格公式错误", + Long: `扫描钉钉电子表格中已落表的公式单元格,按计算结果错误类型聚合返回错误数量、位置和样本。 + +不指定 --sheet-id / --range / --targets 时默认扫描整本表格的全部工作表。`, + Example: ` dws sheet formula-verify --node NODE_ID + dws sheet formula-verify --node NODE_ID --sheet-id Sheet1 --range A1:D100 + dws sheet formula-verify --node NODE_ID --targets '[{"sheetId":"Sheet1","range":"A1:D100"}]'`, + RunE: func(cmd *cobra.Command, args []string) error { + if err := validateRequiredFlags(cmd, "node"); err != nil { + return err + } + toolArgs := map[string]any{ + "nodeId": mustGetFlag(cmd, "node"), + } + targets, err := formulaVerifyTargetsFromFlags(cmd) + if err != nil { + return err + } + if len(targets) > 0 { + toolArgs["targets"] = targets + } + if cmd.Flags().Changed("max-locations-per-error") { + v, _ := cmd.Flags().GetInt("max-locations-per-error") + if v <= 0 { + return fmt.Errorf("--max-locations-per-error 必须是正整数") + } + toolArgs["maxLocationsPerError"] = v + } + if cmd.Flags().Changed("max-cells") { + v, _ := cmd.Flags().GetInt("max-cells") + if v <= 0 { + return fmt.Errorf("--max-cells 必须是正整数") + } + toolArgs["maxCells"] = v + } + exitOnError, _ := cmd.Flags().GetBool("exit-on-error") + return callMCPToolFormulaVerify(toolArgs, exitOnError) + }, + } + cmd.Flags().String("node", "", "表格文档 ID 或 URL (必填)") + cmd.Flags().String("sheet-id", "", "工作表 ID 或名称;与 --range 组成单个扫描目标") + cmd.Flags().String("range", "", "A1 范围;需与 --sheet-id 配合使用") + cmd.Flags().String("targets", "", `扫描目标 JSON 数组、@文件路径 或 - 表示 stdin;每项 {"sheetId":"Sheet1","range":"A1:D100"}`) + cmd.Flags().Int("max-locations-per-error", 0, "每种错误类型最多返回的位置数") + cmd.Flags().Int("max-cells", 0, "最多扫描的单元格数") + cmd.Flags().Bool("exit-on-error", false, "发现公式错误时返回非 0 退出码,便于 CI/自动化使用") + return cmd +} + +// callMCPToolFormulaVerify 与常规打印路径的差异:--exit-on-error 需要读取 +// 返回 payload 判定是否存在公式错误,故走 ReturnText 再自行 PrintJSON。 +func callMCPToolFormulaVerify(toolArgs map[string]any, exitOnError bool) error { + if deps.Caller.DryRun() { + return callMCPToolOnServer("sheet", "formula_verify", toolArgs) + } + text, err := callMCPToolReturnTextOnServer(context.Background(), "sheet", "formula_verify", toolArgs) + if err != nil { + return err + } + if text == "" { + return nil + } + var parsed map[string]any + if err := json.Unmarshal([]byte(text), &parsed); err != nil { + deps.Out.PrintRaw(text) + return nil + } + if parsed == nil { + return fmt.Errorf("formula_verify returned empty result") + } + if err := deps.Out.PrintJSON(parsed); err != nil { + return err + } + if exitOnError && formulaVerifyHasErrors(parsed) { + return fmt.Errorf("formula errors found") + } + return nil +} + +func formulaVerifyHasErrors(parsed map[string]any) bool { + result := formulaVerifyResultObject(parsed) + status := strings.ToLower(strings.TrimSpace(fmt.Sprint(result["status"]))) + if status == "errors_found" { + return true + } + totalErrors, ok := nonNegativeJSONInt(result["totalErrors"]) + return ok && totalErrors > 0 +} + +func formulaVerifyResultObject(parsed map[string]any) map[string]any { + if result, ok := parsed["result"].(map[string]any); ok { + return result + } + return parsed +} + +func formulaVerifyTargetsFromFlags(cmd *cobra.Command) ([]map[string]any, error) { + sheetID, _ := cmd.Flags().GetString("sheet-id") + rangeStr, _ := cmd.Flags().GetString("range") + if v, _ := cmd.Flags().GetString("targets"); v != "" { + if strings.TrimSpace(sheetID) != "" || strings.TrimSpace(rangeStr) != "" { + return nil, fmt.Errorf("--targets 不能与 --sheet-id 或 --range 同时使用") + } + return parseFormulaVerifyTargets(cmd, v) + } + if sheetID == "" && rangeStr != "" { + return nil, fmt.Errorf("--range 必须与 --sheet-id 配合使用") + } + if sheetID != "" { + t := map[string]any{"sheetId": sheetID} + if rangeStr != "" { + t["range"] = rangeStr + } + return []map[string]any{t}, nil + } + return nil, nil +} + +func parseFormulaVerifyTargets(cmd *cobra.Command, raw string) ([]map[string]any, error) { + data := raw + if strings.HasPrefix(raw, "@") { + filePath := strings.TrimPrefix(raw, "@") + content, err := os.ReadFile(filePath) + if err != nil { + return nil, fmt.Errorf("读取 --targets 文件失败: %w", err) + } + data = string(content) + } else if raw == "-" { + content, err := io.ReadAll(cmd.InOrStdin()) + if err != nil { + return nil, fmt.Errorf("读取 stdin 失败: %w", err) + } + data = string(content) + } + var targets []map[string]any + if err := json.Unmarshal([]byte(data), &targets); err != nil { + return nil, fmt.Errorf("--targets JSON 解析失败: %w", err) + } + return targets, nil +} diff --git a/internal/helpers/sheet_formula_verify_test.go b/internal/helpers/sheet_formula_verify_test.go new file mode 100644 index 00000000..ab393642 --- /dev/null +++ b/internal/helpers/sheet_formula_verify_test.go @@ -0,0 +1,175 @@ +package helpers + +import ( + "os" + "path/filepath" + "strings" + "testing" +) + +func executeFormulaVerify(t *testing.T, caller *scriptedToolCaller, stdin *strings.Reader, args ...string) error { + t.Helper() + installScriptedCaller(t, caller) + cmd := newSheetFormulaVerifyCmd() + cmd.SilenceErrors = true + cmd.SilenceUsage = true + if stdin != nil { + cmd.SetIn(stdin) + } + cmd.SetArgs(args) + return cmd.Execute() +} + +func TestCrossPlatformCoverageSheetFormulaVerifyRejectsNonPositiveLimits(t *testing.T) { + if err := executeFormulaVerify(t, &scriptedToolCaller{}, nil, + "--node", "n1", "--max-locations-per-error", "0"); err == nil { + t.Fatal("max-locations-per-error 0 returned nil") + } + if err := executeFormulaVerify(t, &scriptedToolCaller{}, nil, + "--node", "n1", "--max-cells", "-1"); err == nil { + t.Fatal("max-cells -1 returned nil") + } +} + +func TestCrossPlatformCoverageSheetFormulaVerifyLimitsAndInlineTargets(t *testing.T) { + caller := &scriptedToolCaller{} + err := executeFormulaVerify(t, caller, nil, + "--node", "n1", "--max-locations-per-error", "3", "--max-cells", "100", + "--targets", `[{"sheetId":"Sheet1","range":"A1:D10"}]`) + if err != nil { + t.Fatal(err) + } + if caller.calls != 1 { + t.Fatalf("calls = %d, want 1", caller.calls) + } +} + +func TestCrossPlatformCoverageSheetFormulaVerifyTargetsFromFile(t *testing.T) { + path := filepath.Join(t.TempDir(), "targets.json") + if err := os.WriteFile(path, []byte(`[{"sheetId":"Sheet1"}]`), 0o600); err != nil { + t.Fatal(err) + } + caller := &scriptedToolCaller{} + if err := executeFormulaVerify(t, caller, nil, "--node", "n1", "--targets", "@"+path); err != nil { + t.Fatal(err) + } + if caller.calls != 1 { + t.Fatalf("calls = %d, want 1", caller.calls) + } +} + +func TestCrossPlatformCoverageSheetFormulaVerifyTargetsFileMissing(t *testing.T) { + err := executeFormulaVerify(t, &scriptedToolCaller{}, nil, + "--node", "n1", "--targets", "@/nonexistent/targets.json") + if err == nil || !strings.Contains(err.Error(), "读取 --targets 文件失败") { + t.Fatalf("err = %v, want file read failure", err) + } +} + +func TestCrossPlatformCoverageSheetFormulaVerifyTargetsFromStdin(t *testing.T) { + caller := &scriptedToolCaller{} + if err := executeFormulaVerify(t, caller, strings.NewReader(`[{"sheetId":"S1"}]`), + "--node", "n1", "--targets", "-"); err != nil { + t.Fatal(err) + } + if caller.calls != 1 { + t.Fatalf("calls = %d, want 1", caller.calls) + } +} + +func TestCrossPlatformCoverageSheetFormulaVerifyTargetsStdinFailure(t *testing.T) { + installScriptedCaller(t, &scriptedToolCaller{}) + cmd := newSheetFormulaVerifyCmd() + cmd.SilenceErrors = true + cmd.SilenceUsage = true + cmd.SetIn(failingReader{}) + cmd.SetArgs([]string{"--node", "n1", "--targets", "-"}) + err := cmd.Execute() + if err == nil || !strings.Contains(err.Error(), "读取 stdin 失败") { + t.Fatalf("err = %v, want stdin failure", err) + } +} + +func TestCrossPlatformCoverageSheetFormulaVerifyTargetsConflict(t *testing.T) { + for _, extra := range [][]string{ + {"--sheet-id", "Sheet1"}, + {"--range", "A1:B2"}, + } { + args := append([]string{"--node", "n1", "--targets", `[{"sheetId":"S1"}]`}, extra...) + err := executeFormulaVerify(t, &scriptedToolCaller{}, nil, args...) + if err == nil || !strings.Contains(err.Error(), "--targets 不能与 --sheet-id 或 --range 同时使用") { + t.Fatalf("args %v err = %v, want conflict error", extra, err) + } + } +} + +func TestCrossPlatformCoverageSheetFormulaVerifyExitOnError(t *testing.T) { + caller := &scriptedToolCaller{steps: []scriptedToolStep{ + {text: `{"status":"ERRORS_FOUND","totalErrors":2}`}, + }} + err := executeFormulaVerify(t, caller, nil, "--node", "n1", "--exit-on-error") + if err == nil || !strings.Contains(err.Error(), "formula errors found") { + t.Fatalf("err = %v, want formula errors found", err) + } + + caller = &scriptedToolCaller{steps: []scriptedToolStep{ + {text: `{"result":{"status":"partial","totalErrors":1}}`}, + }} + err = executeFormulaVerify(t, caller, nil, "--node", "n1", "--exit-on-error") + if err == nil || !strings.Contains(err.Error(), "formula errors found") { + t.Fatalf("nested totalErrors err = %v, want formula errors found", err) + } + + caller = &scriptedToolCaller{steps: []scriptedToolStep{ + {text: `{"status":"clean","totalErrors":0}`}, + }} + if err := executeFormulaVerify(t, caller, nil, "--node", "n1", "--exit-on-error"); err != nil { + t.Fatalf("clean result err = %v", err) + } + + caller = &scriptedToolCaller{steps: []scriptedToolStep{ + {text: `{"status":"errors_found"}`}, + }} + if err := executeFormulaVerify(t, caller, nil, "--node", "n1"); err != nil { + t.Fatalf("errors without --exit-on-error err = %v", err) + } +} + +func TestCrossPlatformCoverageSheetFormulaVerifyPayloadEdges(t *testing.T) { + caller := &scriptedToolCaller{steps: []scriptedToolStep{{text: `plain text result`}}} + if err := executeFormulaVerify(t, caller, nil, "--node", "n1", "--exit-on-error"); err != nil { + t.Fatalf("non-JSON payload err = %v", err) + } + + caller = &scriptedToolCaller{steps: []scriptedToolStep{{text: `null`}}} + err := executeFormulaVerify(t, caller, nil, "--node", "n1") + if err == nil || !strings.Contains(err.Error(), "empty result") { + t.Fatalf("null payload err = %v, want empty result", err) + } + + caller = &scriptedToolCaller{steps: []scriptedToolStep{{text: `{"errorCode":"forbidden.x","errorMsg":"denied"}`}}} + if err := executeFormulaVerify(t, caller, nil, "--node", "n1"); err == nil { + t.Fatal("business error returned nil") + } + + caller = &scriptedToolCaller{steps: []scriptedToolStep{{text: `{"status":"clean"}`}}} + installScriptedCaller(t, caller) + deps.Out.w = failingWriter{} + cmd := newSheetFormulaVerifyCmd() + cmd.SilenceErrors = true + cmd.SilenceUsage = true + cmd.SetArgs([]string{"--node", "n1"}) + if err := cmd.Execute(); err == nil || !strings.Contains(err.Error(), "write failed") { + t.Fatalf("print failure err = %v, want write failed", err) + } +} + +func TestCrossPlatformCoverageSheetFormulaVerifyDryRunShortCircuit(t *testing.T) { + caller := &scriptedToolCaller{dry: true} + if err := executeFormulaVerify(t, caller, nil, "--node", "n1", "--exit-on-error"); err != nil { + t.Fatal(err) + } + if caller.calls != 0 { + t.Fatalf("dry-run calls = %d, want 0 (preview only)", caller.calls) + } +} diff --git a/internal/helpers/sheet_info_include_test.go b/internal/helpers/sheet_info_include_test.go new file mode 100644 index 00000000..16d2b7c9 --- /dev/null +++ b/internal/helpers/sheet_info_include_test.go @@ -0,0 +1,40 @@ +package helpers + +import ( + "io" + "os" + "testing" +) + +func TestCrossPlatformCoverageSheetInfoIncludePassthrough(t *testing.T) { + oldArgs := os.Args + os.Args = []string{"dws", "sheet"} + t.Cleanup(func() { os.Args = oldArgs }) + + caller := &depthArgsRecordingCaller{steps: []scriptedToolStep{ + {text: `{"sheetId":"s1"}`}, + }} + previousDeps := deps + t.Cleanup(func() { deps = previousDeps }) + InitDeps(caller) + deps.Out.w = io.Discard + deps.Out.errW = io.Discard + + root := newSheetCommand() + if root.PersistentFlags().Lookup("dry-run") == nil { + root.PersistentFlags().Bool("dry-run", false, "") + } + root.SilenceErrors = true + root.SilenceUsage = true + root.SetArgs([]string{"info", "--node", "n1", "--sheet-id", "Sheet1", "--include", "row_heights,col_widths"}) + if err := root.Execute(); err != nil { + t.Fatal(err) + } + if len(caller.calls) != 1 { + t.Fatalf("calls = %#v", caller.calls) + } + include, _ := caller.calls[0]["include"].([]string) + if len(include) != 2 || include[0] != "row_heights" || include[1] != "col_widths" { + t.Fatalf("include = %#v", caller.calls[0]["include"]) + } +} diff --git a/internal/helpers/sheet_ports_coverage_test.go b/internal/helpers/sheet_ports_coverage_test.go new file mode 100644 index 00000000..c8aa0fc5 --- /dev/null +++ b/internal/helpers/sheet_ports_coverage_test.go @@ -0,0 +1,298 @@ +package helpers + +import ( + "reflect" + "strings" + "testing" +) + +func TestCrossPlatformCoverageSheetCommentCommands(t *testing.T) { + tests := []struct { + name string + args []string + want guardedMutationCall + }{ + { + name: "list minimal", + args: []string{"comment", "list", "--node", "node-1"}, + want: guardedMutationCall{ + productID: "doc-comment", + toolName: "list_sheet_comments", + args: map[string]any{"nodeId": "node-1"}, + }, + }, + { + name: "list with cell and status filters", + args: []string{ + "comment", "list", + "--node", "node-1", + "--sheet-id", "Sheet1", + "--range", "A2", + "--resolve-status", "unresolved", + "--limit", "10", + "--cursor", "cur-1", + }, + want: guardedMutationCall{ + productID: "doc-comment", + toolName: "list_sheet_comments", + args: map[string]any{ + "nodeId": "node-1", + "sheetId": "Sheet1", + "range": "A2", + "resolveStatus": "unresolved", + "pageSize": 10, + "nextToken": "cur-1", + }, + }, + }, + { + name: "create with mention parsing", + args: []string{ + "comment", "create", + "--node", "node-1", + "--sheet-id", "Sheet1", + "--range", "A2", + "--content", "check this", + "--mention", "uid1, uid2 ,,uid3", + }, + want: guardedMutationCall{ + productID: "doc-comment", + toolName: "create_sheet_comment", + args: map[string]any{ + "nodeId": "node-1", + "content": "check this", + "sheetId": "Sheet1", + "range": "A2", + "mentionedUserIds": []string{"uid1", "uid2", "uid3"}, + }, + }, + }, + { + name: "create without mention", + args: []string{ + "comment", "create", + "--node", "node-1", + "--sheet-id", "Sheet1", + "--range", "B3", + "--content", "plain", + }, + want: guardedMutationCall{ + productID: "doc-comment", + toolName: "create_sheet_comment", + args: map[string]any{ + "nodeId": "node-1", + "content": "plain", + "sheetId": "Sheet1", + "range": "B3", + }, + }, + }, + { + name: "reply with emoji and mention", + args: []string{ + "comment", "reply", + "--node", "node-1", + "--comment-key", "ck-1", + "--content", "heart", + "--emoji", + "--mention", "uid1", + }, + want: guardedMutationCall{ + productID: "doc-comment", + toolName: "reply_comment", + args: map[string]any{ + "nodeId": "node-1", + "content": "heart", + "replyCommentKey": "ck-1", + "emoji": true, + "mentionedUserIds": []string{"uid1"}, + }, + }, + }, + { + name: "reply plain text", + args: []string{ + "comment", "reply", + "--node", "node-1", + "--comment-key", "ck-2", + "--content", "confirmed", + }, + want: guardedMutationCall{ + productID: "doc-comment", + toolName: "reply_comment", + args: map[string]any{ + "nodeId": "node-1", + "content": "confirmed", + "replyCommentKey": "ck-2", + }, + }, + }, + { + name: "update", + args: []string{ + "comment", "update", + "--node", "node-1", + "--comment-key", "ck-3", + "--content", "revised", + }, + want: guardedMutationCall{ + productID: "doc-comment", + toolName: "update_comment", + args: map[string]any{ + "nodeId": "node-1", + "commentKey": "ck-3", + "content": "revised", + }, + }, + }, + } + for _, test := range tests { + test := test + t.Run(test.name, func(t *testing.T) { + caller := &guardedMutationCaller{} + err := executeGuardedMutationCommand(t, caller, newSheetCommand, test.args...) + if err != nil { + t.Fatalf("sheet %s returned error: %v", strings.Join(test.args, " "), err) + } + if len(caller.calls) != 1 || !reflect.DeepEqual(caller.calls[0], test.want) { + t.Fatalf("tool calls = %#v, want %#v", caller.calls, test.want) + } + }) + } +} + +func TestCrossPlatformCoverageSheetCommentRequiredFlags(t *testing.T) { + tests := []struct { + name string + args []string + wantErr string + }{ + { + name: "list without node", + args: []string{"comment", "list"}, + wantErr: "--node", + }, + { + name: "create without content sheet-id range", + args: []string{"comment", "create", "--node", "node-1"}, + wantErr: "--content, --sheet-id, --range", + }, + { + name: "reply without comment-key", + args: []string{"comment", "reply", "--node", "node-1", "--content", "text"}, + wantErr: "--comment-key", + }, + { + name: "update without content", + args: []string{"comment", "update", "--node", "node-1", "--comment-key", "ck-1"}, + wantErr: "--content", + }, + } + for _, test := range tests { + test := test + t.Run(test.name, func(t *testing.T) { + caller := &guardedMutationCaller{} + err := executeGuardedMutationCommand(t, caller, newSheetCommand, test.args...) + if err == nil || !strings.Contains(err.Error(), test.wantErr) { + t.Fatalf("err = %v, want message containing %q", err, test.wantErr) + } + if len(caller.calls) != 0 { + t.Fatalf("tool calls = %#v, want none", caller.calls) + } + }) + } +} + +func TestCrossPlatformCoverageSheetVersionCommands(t *testing.T) { + tests := []struct { + name string + args []string + want guardedMutationCall + }{ + { + name: "save", + args: []string{"version", "save", "--node", "node-1"}, + want: guardedMutationCall{ + productID: "doc", + toolName: "save_doc_version", + args: map[string]any{"nodeId": "node-1"}, + }, + }, + { + name: "list minimal", + args: []string{"version", "list", "--node", "node-1"}, + want: guardedMutationCall{ + productID: "doc", + toolName: "list_doc_versions", + args: map[string]any{"nodeId": "node-1"}, + }, + }, + { + name: "list with pagination pass-through", + args: []string{"version", "list", "--node", "node-1", "--limit", "10", "--cursor", "cur-1"}, + want: guardedMutationCall{ + productID: "doc", + toolName: "list_doc_versions", + args: map[string]any{ + "nodeId": "node-1", + "maxResults": 10, + "nextCursor": "cur-1", + }, + }, + }, + { + name: "list ls alias", + args: []string{"version", "ls", "--node", "node-2"}, + want: guardedMutationCall{ + productID: "doc", + toolName: "list_doc_versions", + args: map[string]any{"nodeId": "node-2"}, + }, + }, + } + for _, test := range tests { + test := test + t.Run(test.name, func(t *testing.T) { + caller := &guardedMutationCaller{} + err := executeGuardedMutationCommand(t, caller, newSheetCommand, test.args...) + if err != nil { + t.Fatalf("sheet %s returned error: %v", strings.Join(test.args, " "), err) + } + if len(caller.calls) != 1 || !reflect.DeepEqual(caller.calls[0], test.want) { + t.Fatalf("tool calls = %#v, want %#v", caller.calls, test.want) + } + }) + } +} + +func TestCrossPlatformCoverageSheetVersionRequiredFlags(t *testing.T) { + tests := []struct { + name string + args []string + wantErr string + }{ + { + name: "save without node", + args: []string{"version", "save"}, + wantErr: "--node", + }, + { + name: "list without node", + args: []string{"version", "list"}, + wantErr: "--node", + }, + } + for _, test := range tests { + test := test + t.Run(test.name, func(t *testing.T) { + caller := &guardedMutationCaller{} + err := executeGuardedMutationCommand(t, caller, newSheetCommand, test.args...) + if err == nil || !strings.Contains(err.Error(), test.wantErr) { + t.Fatalf("err = %v, want message containing %q", err, test.wantErr) + } + if len(caller.calls) != 0 { + t.Fatalf("tool calls = %#v, want none", caller.calls) + } + }) + } +} diff --git a/internal/helpers/sheet_version.go b/internal/helpers/sheet_version.go new file mode 100644 index 00000000..67df4404 --- /dev/null +++ b/internal/helpers/sheet_version.go @@ -0,0 +1,95 @@ +package helpers + +import ( + "fmt" + + "github.com/spf13/cobra" +) + +func newSheetVersionCmd() *cobra.Command { + versionCmd := &cobra.Command{ + Use: "version", + Short: "表格历史版本管理", + Long: `管理钉钉在线电子表格的历史版本:手动保存、查看版本列表、回滚到指定版本。`, + RunE: groupRunE, + } + + versionSaveCmd := &cobra.Command{ + Use: "save", + Short: "手动保存表格版本快照", + Example: ` dws sheet version save --node SHEET_ID`, + RunE: func(cmd *cobra.Command, args []string) error { + nodeID, err := mustFlagOrFallback(cmd, "node", "url", "id", "node-id", "doc-id", "file-id") + if err != nil { + return err + } + return callMCPToolOnServer("doc", "save_doc_version", map[string]any{ + "nodeId": nodeID, + }) + }, + } + versionSaveCmd.Flags().String("node", "", "表格文档 ID 或 URL (必填)") + + versionListCmd := &cobra.Command{ + Use: "list", + Aliases: []string{"ls"}, + Short: "查看表格历史版本列表", + Example: ` dws sheet version list --node SHEET_ID + dws sheet version list --node SHEET_ID --limit 10`, + RunE: func(cmd *cobra.Command, args []string) error { + nodeID, err := mustFlagOrFallback(cmd, "node", "url", "id", "node-id", "doc-id", "file-id") + if err != nil { + return err + } + toolArgs := map[string]any{"nodeId": nodeID} + if v, _ := cmd.Flags().GetInt("limit"); v > 0 { + toolArgs["maxResults"] = v + } + if v := flagOrFallback(cmd, "cursor", "page-token", "next-token"); v != "" { + toolArgs["nextCursor"] = v + } + return callMCPToolOnServer("doc", "list_doc_versions", toolArgs) + }, + } + versionListCmd.Flags().String("node", "", "表格文档 ID 或 URL (必填)") + versionListCmd.Flags().Int("limit", 0, "返回版本数量上限") + versionListCmd.Flags().String("cursor", "", "分页游标") + + versionRevertCmd := &cobra.Command{ + Use: "revert", + Short: "[危险] 回滚表格到指定版本", + Example: ` dws sheet version revert --node SHEET_ID --version 3 --yes`, + RunE: func(cmd *cobra.Command, args []string) error { + nodeID, err := mustFlagOrFallback(cmd, "node", "url", "id", "node-id", "doc-id", "file-id") + if err != nil { + return err + } + if !cmd.Flags().Changed("version") { + return fmt.Errorf("flag --version is required") + } + version, _ := cmd.Flags().GetInt("version") + return callMCPToolOnServer("doc", "revert_doc_version", map[string]any{ + "nodeId": nodeID, + "version": version, + }) + }, + } + versionRevertCmd.Flags().String("node", "", "表格文档 ID 或 URL (必填)") + versionRevertCmd.Flags().Int("version", 0, "目标版本号 (必填,从 list 获取)") + + for _, c := range []*cobra.Command{versionSaveCmd, versionListCmd, versionRevertCmd} { + c.Flags().String("url", "", "") + c.Flags().String("id", "", "") + c.Flags().String("node-id", "", "") + c.Flags().String("doc-id", "", "") + c.Flags().String("file-id", "", "") + _ = c.Flags().MarkHidden("url") + _ = c.Flags().MarkHidden("id") + _ = c.Flags().MarkHidden("node-id") + _ = c.Flags().MarkHidden("doc-id") + _ = c.Flags().MarkHidden("file-id") + } + + versionCmd.AddCommand(versionSaveCmd, versionListCmd, versionRevertCmd) + return versionCmd +} diff --git a/internal/helpers/sheet_workbook.go b/internal/helpers/sheet_workbook.go index b6f6d966..9f2a9925 100644 --- a/internal/helpers/sheet_workbook.go +++ b/internal/helpers/sheet_workbook.go @@ -69,7 +69,8 @@ sheetId 支持传入工作表 ID 或工作表名称,可通过 sheet list 获 不传 --sheet-id 时默认返回第一个工作表。`, Example: ` dws sheet info --node NODE_ID dws sheet info --node NODE_ID --sheet-id SHEET_ID - dws sheet info --node NODE_ID --sheet-id "Sheet1"`, + dws sheet info --node NODE_ID --sheet-id "Sheet1" + dws sheet info --node NODE_ID --sheet-id SHEET_ID --include row_heights,col_widths`, RunE: func(cmd *cobra.Command, args []string) error { toolArgs := map[string]any{ "nodeId": mustGetFlag(cmd, "node"), @@ -77,11 +78,15 @@ sheetId 支持传入工作表 ID 或工作表名称,可通过 sheet list 获 if v, _ := cmd.Flags().GetString("sheet-id"); v != "" { toolArgs["sheetId"] = v } + if v, _ := cmd.Flags().GetStringSlice("include"); len(v) > 0 { + toolArgs["include"] = v + } return callMCPToolSheetInfo(toolArgs) }, } infoCmd.Flags().String("node", "", "表格文档 ID 或 URL (必填)") infoCmd.Flags().String("sheet-id", "", "工作表 ID 或名称 (不传则返回第一个工作表)") + infoCmd.Flags().StringSlice("include", nil, "可选扩展信息,逗号分隔;支持 groups / row_heights / col_widths / hidden_rows / hidden_cols / frozen") newCmd := &cobra.Command{ Use: "new", diff --git a/internal/pat/pat.go b/internal/pat/pat.go index 5e535f00..c01217fc 100644 --- a/internal/pat/pat.go +++ b/internal/pat/pat.go @@ -55,10 +55,12 @@ Host-owned PAT 开关: 由宿主处理全部 UI / 交互 / 回调节奏 / 重试逻辑, CLI 侧不再拉起任何本地浏览器 / 轮询。 -服务端路由标签 claw-type(开源构建硬编码): - 开源构建在所有出站 MCP 请求上恒定注入 claw-type: openClaw, - 与 DINGTALK_AGENT / 宿主环境解耦,与历史 main 行为一致。 - hostControl.clawType 也会回填该值,便于宿主侧审计/路由。 +服务端 Agent 产品标签 claw-type: + 开源构建默认在出站 MCP 请求中注入 claw-type: openClaw。 + 如设置 DWS_AGENT_PRODUCT,则使用经校验的环境变量值覆盖该默认值。 + hostControl.clawType 会回填请求实际使用的值,避免 PAT 与请求标识漂移。 + 该值由调用方声明,不是认证凭据;服务端不得仅凭它放权或跳过授权。 + 它也不会修改 IM 消息展示使用的 clawType 参数或 --ai-tag 行为。 DINGTALK_AGENT(可选,仅供 x-dingtalk-agent 使用): 如设置,将原样注入 HTTP 请求头 x-dingtalk-agent, diff --git a/internal/shortcut/chat/gap_fill_test.go b/internal/shortcut/chat/gap_fill_test.go index c0705b02..8d50f4a6 100644 --- a/internal/shortcut/chat/gap_fill_test.go +++ b/internal/shortcut/chat/gap_fill_test.go @@ -455,6 +455,9 @@ func TestCrossPlatformCoverageFindCardBizIDResponseShapes(t *testing.T) { "metadata": map[string]any{"bizId": "stale"}, "result": map[string]any{"bizId": "current"}, }, "current"}, + {map[string]any{ + "extension": map[string]any{"bizId": "fallback"}, + }, "fallback"}, {`{"result":{"bizId":"json"}}`, "json"}, {`[{"bizId":"array-json"}]`, "array-json"}, {`{"bizId":`, ""}, diff --git a/internal/shortcut/shortcut_test.go b/internal/shortcut/shortcut_test.go index ca08f805..ac191b09 100644 --- a/internal/shortcut/shortcut_test.go +++ b/internal/shortcut/shortcut_test.go @@ -15,6 +15,7 @@ package shortcut import ( "bytes" + "os" "strings" "testing" @@ -63,14 +64,74 @@ func TestCrossPlatformCoverageEffectiveRiskUsesParsedFlagsAndFailsClosed(t *test t.Fatalf("download effectiveRisk() = %q, want write", got) } - s.Risk = RiskHighWrite + s.Risk = RiskRead s.RiskWhen = func(*RuntimeContext) Risk { return Risk("unknown") } if got := s.effectiveRisk(rt); got != RiskHighWrite { t.Fatalf("invalid callback effectiveRisk() = %q, want high-risk-write", got) } + s.Risk = RiskWrite + s.RiskWhen = func(*RuntimeContext) Risk { return RiskHighWrite } + if got := s.effectiveRisk(rt); got != RiskHighWrite { + t.Fatalf("write-to-high upgrade effectiveRisk() = %q, want high-risk-write", got) + } + s.RiskWhen = func(*RuntimeContext) Risk { return RiskRead } + if got := s.effectiveRisk(rt); got != RiskWrite { + t.Fatalf("write-to-read downgrade effectiveRisk() = %q, want write", got) + } + s.Risk = Risk("unknown") + if got := s.effectiveRisk(rt); got != RiskHighWrite { + t.Fatalf("invalid base effectiveRisk() = %q, want high-risk-write", got) + } + s.Risk = RiskHighWrite s.RiskWhen = func(*RuntimeContext) Risk { return RiskRead } if got := s.effectiveRisk(rt); got != RiskHighWrite { - t.Fatalf("downgrade effectiveRisk() = %q, want high-risk-write", got) + t.Fatalf("high-risk downgrade effectiveRisk() = %q, want high-risk-write", got) + } +} + +func TestCrossPlatformCoverageConfirmRiskUsesEffectiveRisk(t *testing.T) { + cmd := &cobra.Command{} + cmd.Flags().Bool("download-resources", false, "") + if err := cmd.Flags().Set("download-resources", "true"); err != nil { + t.Fatal(err) + } + var stderr bytes.Buffer + cmd.SetErr(&stderr) + rt := &RuntimeContext{cmd: cmd} + s := Shortcut{ + Service: "chat", + Command: "+messages-mget", + Risk: RiskRead, + RiskWhen: func(rt *RuntimeContext) Risk { + if rt.Bool("download-resources") { + return RiskWrite + } + return RiskRead + }, + } + + reader, writer, err := os.Pipe() + if err != nil { + t.Fatal(err) + } + previousStdin := os.Stdin + os.Stdin = reader + t.Cleanup(func() { + os.Stdin = previousStdin + _ = reader.Close() + _ = writer.Close() + }) + if _, err := writer.WriteString("yes\n"); err != nil { + t.Fatal(err) + } + if err := writer.Close(); err != nil { + t.Fatal(err) + } + if !confirmRisk(rt, s) { + t.Fatal("dynamic write risk was not confirmed") + } + if got := stderr.String(); !strings.Contains(got, "chat +messages-mget(write)") { + t.Fatalf("confirmation prompt = %q", got) } } diff --git a/internal/shortcut/types.go b/internal/shortcut/types.go index 4090a265..64dd1625 100644 --- a/internal/shortcut/types.go +++ b/internal/shortcut/types.go @@ -201,8 +201,9 @@ func (s Shortcut) risk() Risk { } // effectiveRisk returns the invocation-specific risk after flags are resolved. -// Invalid callback values and attempted downgrades fail closed to the -// declarative default instead of weakening its confirmation policy. +// Invalid callback/base values fail closed to high-risk-write, while attempted +// downgrades preserve the declarative default instead of weakening its +// confirmation policy. func (s Shortcut) effectiveRisk(rt *RuntimeContext) Risk { base := s.risk() if s.RiskWhen == nil { @@ -212,7 +213,7 @@ func (s Shortcut) effectiveRisk(rt *RuntimeContext) Risk { switch dynamic { case RiskRead, RiskWrite, RiskHighWrite: default: - return base + return RiskHighWrite } switch base { case RiskHighWrite: @@ -225,6 +226,6 @@ func (s Shortcut) effectiveRisk(rt *RuntimeContext) Risk { case RiskRead: return dynamic default: - return base + return RiskHighWrite } } diff --git a/pkg/agentproduct/agent_product.go b/pkg/agentproduct/agent_product.go new file mode 100644 index 00000000..db01f6c8 --- /dev/null +++ b/pkg/agentproduct/agent_product.go @@ -0,0 +1,76 @@ +// Copyright 2026 Alibaba Group +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Package agentproduct defines the caller-provided Agent product identity +// carried on outbound DWS requests. +package agentproduct + +import ( + "errors" + "os" + "regexp" + "strings" +) + +const ( + // EnvName is the runtime override for the Agent product identity. + EnvName = "DWS_AGENT_PRODUCT" + // HeaderName is the existing wire header used for the Agent product. + HeaderName = "claw-type" + // MaxValueBytes bounds the value because it is attached to every outbound + // MCP request. Supported values are ASCII, so bytes and characters match. + MaxValueBytes = 64 +) + +// ErrInvalid is returned when an Agent product value is unsafe or does not +// match the supported wire format. The error intentionally excludes the raw +// caller-controlled value. +var ErrInvalid = errors.New("invalid agent product") + +var valuePattern = regexp.MustCompile(`^[A-Za-z0-9][A-Za-z0-9_-]*$`) + +// Parse normalizes and validates a caller-provided Agent product. Only +// surrounding ASCII spaces and tabs are trimmed; other control or Unicode +// whitespace remains visible to validation and is rejected. An unset or +// ASCII-whitespace-only value means "use the edition default". +func Parse(raw string) (string, error) { + if strings.ContainsAny(raw, "\r\n") { + return "", ErrInvalid + } + + value := strings.Trim(raw, " \t") + if value == "" { + return "", nil + } + if len(value) > MaxValueBytes { + return "", ErrInvalid + } + if !valuePattern.MatchString(value) { + return "", ErrInvalid + } + return value, nil +} + +// ResolveFromEnv returns the validated DWS_AGENT_PRODUCT value, or fallback +// when the environment variable is unset or empty. Invalid caller input is +// returned as an error so command entrypoints can fail before network access. +func ResolveFromEnv(fallback string) (string, error) { + value, err := Parse(os.Getenv(EnvName)) + if err != nil { + return "", err + } + if value == "" { + return fallback, nil + } + return value, nil +} diff --git a/pkg/agentproduct/agent_product_test.go b/pkg/agentproduct/agent_product_test.go new file mode 100644 index 00000000..cacb440b --- /dev/null +++ b/pkg/agentproduct/agent_product_test.go @@ -0,0 +1,105 @@ +// Copyright 2026 Alibaba Group +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package agentproduct + +import ( + "errors" + "strings" + "testing" +) + +func TestParse(t *testing.T) { + valid := []struct { + name string + raw string + want string + }{ + {name: "unset", raw: "", want: ""}, + {name: "ASCII whitespace only", raw: " \t ", want: ""}, + {name: "qwenwork", raw: "qwenwork", want: "qwenwork"}, + {name: "legacy open claw", raw: "openClaw", want: "openClaw"}, + {name: "trim", raw: " \tqwenwork\t ", want: "qwenwork"}, + {name: "generic", raw: "agent-2_alpha", want: "agent-2_alpha"}, + {name: "leading digit", raw: "2nd_product", want: "2nd_product"}, + {name: "maximum length", raw: strings.Repeat("a", MaxValueBytes), want: strings.Repeat("a", MaxValueBytes)}, + } + for _, tc := range valid { + t.Run(tc.name, func(t *testing.T) { + got, err := Parse(tc.raw) + if err != nil { + t.Fatalf("Parse() error = %v", err) + } + if got != tc.want { + t.Fatalf("Parse() = %q, want %q", got, tc.want) + } + }) + } + + invalid := []struct { + name string + raw string + }{ + {name: "carriage return", raw: "qwenwork\r"}, + {name: "line feed", raw: "\nqwenwork"}, + {name: "internal space", raw: "qwen work"}, + {name: "internal tab", raw: "qwen\twork"}, + {name: "unicode", raw: "千问办公"}, + {name: "leading dash", raw: "-qwenwork"}, + {name: "leading underscore", raw: "_qwenwork"}, + {name: "control character", raw: "qwenwork\x00cloud"}, + {name: "vertical tab", raw: "\vqwenwork"}, + {name: "form feed", raw: "qwenwork\f"}, + {name: "next line", raw: "qwenwork\u0085"}, + {name: "non-breaking space", raw: "\u00a0qwenwork"}, + {name: "ideographic space", raw: "qwenwork\u3000"}, + {name: "too long", raw: strings.Repeat("a", MaxValueBytes+1)}, + } + for _, tc := range invalid { + t.Run(tc.name, func(t *testing.T) { + got, err := Parse(tc.raw) + if !errors.Is(err, ErrInvalid) { + t.Fatalf("Parse(%q) = %q, %v; want ErrInvalid", tc.raw, got, err) + } + if strings.Contains(err.Error(), tc.raw) { + t.Fatalf("error must not echo invalid value %q: %v", tc.raw, err) + } + }) + } +} + +func TestResolveFromEnv(t *testing.T) { + t.Run("unset uses fallback", func(t *testing.T) { + t.Setenv(EnvName, "") + got, err := ResolveFromEnv("openClaw") + if err != nil || got != "openClaw" { + t.Fatalf("ResolveFromEnv() = %q, %v; want openClaw, nil", got, err) + } + }) + + t.Run("valid environment wins", func(t *testing.T) { + t.Setenv(EnvName, " qwenwork ") + got, err := ResolveFromEnv("openClaw") + if err != nil || got != "qwenwork" { + t.Fatalf("ResolveFromEnv() = %q, %v; want qwenwork, nil", got, err) + } + }) + + t.Run("invalid environment does not return fallback", func(t *testing.T) { + t.Setenv(EnvName, "qwen work") + got, err := ResolveFromEnv("openClaw") + if got != "" || !errors.Is(err, ErrInvalid) { + t.Fatalf("ResolveFromEnv() = %q, %v; want empty, ErrInvalid", got, err) + } + }) +} diff --git a/pkg/edition/default.go b/pkg/edition/default.go index b3d79f62..680f69a9 100644 --- a/pkg/edition/default.go +++ b/pkg/edition/default.go @@ -15,20 +15,18 @@ package edition import "github.com/DingTalk-Real-AI/dingtalk-workspace-cli/internal/syncdata" -// DefaultOSSClawType is the wire value for request header claw-type in -// the open-source build. It is intentionally hard-wired — the open-source -// CLI does NOT derive claw-type from DINGTALK_AGENT or any other caller -// input, so third-party hosts get a predictable header regardless of -// their environment. +// DefaultOSSClawType is the default wire value for request header claw-type +// in the open-source build. DWS_AGENT_PRODUCT may explicitly override the +// request identity; unrelated caller inputs such as DINGTALK_AGENT do not. const DefaultOSSClawType = "openClaw" // defaultHooks returns the open-source edition defaults. // -// MergeHeaders is the only hook that ships with behaviour: it pins the -// `claw-type` request header to DefaultOSSClawType so every open-source -// MCP request carries the same stable routing tag. All other fields are -// nil — the internal code interprets nil as "use standard open-source -// behaviour". +// MergeHeaders is the only hook that ships with behaviour: it supplies +// DefaultOSSClawType so every open-source MCP request has a stable default. +// The core applies an optional DWS_AGENT_PRODUCT override after edition hooks. +// All other fields are nil — the internal code interprets nil as "use +// standard open-source behaviour". func defaultHooks() *Hooks { return &Hooks{ Name: "open", diff --git a/pkg/edition/edition.go b/pkg/edition/edition.go index d5e77e6c..f7ec156d 100644 --- a/pkg/edition/edition.go +++ b/pkg/edition/edition.go @@ -85,10 +85,12 @@ type Hooks struct { Name string // "open" (default) / overlay identifier ScenarioCode string // injected into x-dingtalk-scenario-code header - // ClawTypeValue is the claw identity carried in message-send tool + // ClawTypeValue is the display identity carried only in message-send tool // arguments (parameter clawType) so the IM server can render the - // "Send from AI" indicator on delivered messages. Empty → falls back - // to DefaultOSSClawType; overlays set their own value (e.g. "wukong"). + // "Send from AI" indicator on delivered messages. It is intentionally + // separate from the HTTP claw-type Agent Product header and is not + // overridden by DWS_AGENT_PRODUCT. Empty → DefaultOSSClawType; overlays + // set their own message-display value (e.g. "wukong"). ClawTypeValue string // PersonalEventSourceID identifies the personal-event source channel @@ -106,9 +108,15 @@ type Hooks struct { ConfigDir func() string // custom config directory; nil → ~/.dws // --- HTTP headers --- + // MergeHeaders must preserve base headers. If it sets claw-type, that value + // must be deterministic and independent of the supplied base map because + // PAT error serialization resolves it with an empty map. The hook must not + // perform network, keychain, credential-refresh, or other blocking work. MergeHeaders func(base map[string]string) map[string]string // --- EnterpriseCredential HTTP headers --- + // This hook is only for credential material. It must not set claw-type; + // the core reasserts that Header after the hook returns. EnterpriseCredentialHeaders func(base map[string]string) map[string]string // --- auth --- @@ -198,10 +206,11 @@ func Override(h *Hooks) { current = h } -// ClawType returns the claw identity for the active edition, falling back -// to DefaultOSSClawType when the overlay does not set one. Message-send -// helpers attach this value as the clawType tool argument so the IM server -// can label delivered messages as sent via AI. +// ClawType returns the message-display identity for the active edition, +// falling back to DefaultOSSClawType when the overlay does not set one. +// Message-send helpers attach this value as the clawType tool argument so the +// IM server can label delivered messages as sent via AI. It is a separate axis +// from the HTTP claw-type header and is not affected by DWS_AGENT_PRODUCT. func ClawType() string { if v := Get().ClawTypeValue; v != "" { return v diff --git a/scripts/gen_skill_shortcut_sections.py b/scripts/gen_skill_shortcut_sections.py index 79926c60..49c8eaaf 100644 --- a/scripts/gen_skill_shortcut_sections.py +++ b/scripts/gen_skill_shortcut_sections.py @@ -25,19 +25,19 @@ MONO_SKILL = ROOT / "skills" / "mono" / "SKILL.md" SERVICE_TO_SKILL = { "aitable": ROOT / "skills" / "multi" / "dingtalk-aitable" / "SKILL.md", - "attendance": ROOT / "skills" / "multi" / "dingtalk-attendance" / "SKILL.md", + "attendance": ROOT / "skills" / "multi" / "dingtalk-misc" / "references" / "attendance.md", "calendar": ROOT / "skills" / "multi" / "dingtalk-calendar" / "SKILL.md", "chat": ROOT / "skills" / "multi" / "dingtalk-chat" / "SKILL.md", "contact": ROOT / "skills" / "multi" / "dingtalk-contact" / "SKILL.md", "devapp": ROOT / "skills" / "multi" / "dingtalk-dev" / "SKILL.md", - "ding": ROOT / "skills" / "multi" / "dingtalk-ding" / "SKILL.md", + "ding": ROOT / "skills" / "multi" / "dingtalk-misc" / "references" / "ding.md", "doc": ROOT / "skills" / "multi" / "dingtalk-doc" / "SKILL.md", "drive": ROOT / "skills" / "multi" / "dingtalk-drive" / "SKILL.md", "mail": ROOT / "skills" / "multi" / "dingtalk-mail" / "SKILL.md", "minutes": ROOT / "skills" / "multi" / "dingtalk-minutes" / "SKILL.md", - "oa": ROOT / "skills" / "multi" / "dingtalk-oa" / "SKILL.md", - "report": ROOT / "skills" / "multi" / "dingtalk-report" / "SKILL.md", - "sheet": ROOT / "skills" / "multi" / "dingtalk-sheet" / "SKILL.md", + "oa": ROOT / "skills" / "multi" / "dingtalk-misc" / "references" / "oa.md", + "report": ROOT / "skills" / "multi" / "dingtalk-misc" / "references" / "report.md", + "sheet": ROOT / "skills" / "multi" / "dingtalk-misc" / "references" / "sheet.md", "todo": ROOT / "skills" / "multi" / "dingtalk-todo" / "SKILL.md", "wiki": ROOT / "skills" / "multi" / "dingtalk-wiki" / "SKILL.md", } @@ -88,7 +88,9 @@ def mono_overview(items: list[dict[str, Any]]) -> str: rows = [] for service, count in sorted(counts.items()): path = SERVICE_TO_SKILL.get(service) - skill = path.parent.name if path else "—" + skill = "—" + if path: + skill = next((part for part in path.parts if part.startswith("dingtalk-")), path.parent.name) rows.append(f"| `{md_escape(service)}` | {count} | `{md_escape(skill)}` | `dws shortcut list --service {md_escape(service)} --format json` |") body = "\n".join(rows) return f"""{MONO_START} diff --git a/skills/mono/SKILL.md b/skills/mono/SKILL.md index 9c6aaf76..92c326ab 100644 --- a/skills/mono/SKILL.md +++ b/skills/mono/SKILL.md @@ -43,22 +43,22 @@ cli_version: ">=1.0.15" | 服务 | shortcut 数 | multi skill | 发现命令 | |---|---:|---|---| -| `aitable` | 29 | `dingtalk-aitable` | `dws shortcut list --service aitable --format json` | -| `attendance` | 19 | `dingtalk-attendance` | `dws shortcut list --service attendance --format json` | -| `calendar` | 20 | `dingtalk-calendar` | `dws shortcut list --service calendar --format json` | -| `chat` | 97 | `dingtalk-chat` | `dws shortcut list --service chat --format json` | -| `contact` | 14 | `dingtalk-contact` | `dws shortcut list --service contact --format json` | -| `devapp` | 19 | `dingtalk-dev` | `dws shortcut list --service devapp --format json` | -| `ding` | 4 | `dingtalk-ding` | `dws shortcut list --service ding --format json` | -| `doc` | 17 | `dingtalk-doc` | `dws shortcut list --service doc --format json` | -| `drive` | 7 | `dingtalk-drive` | `dws shortcut list --service drive --format json` | -| `mail` | 10 | `dingtalk-mail` | `dws shortcut list --service mail --format json` | -| `minutes` | 6 | `dingtalk-minutes` | `dws shortcut list --service minutes --format json` | -| `oa` | 7 | `dingtalk-oa` | `dws shortcut list --service oa --format json` | -| `report` | 2 | `dingtalk-report` | `dws shortcut list --service report --format json` | -| `sheet` | 2 | `dingtalk-sheet` | `dws shortcut list --service sheet --format json` | -| `todo` | 11 | `dingtalk-todo` | `dws shortcut list --service todo --format json` | -| `wiki` | 1 | `dingtalk-wiki` | `dws shortcut list --service wiki --format json` | +| `aitable` | 29 | `dingtalk-workspace` | `dws shortcut list --service aitable --format json` | +| `attendance` | 19 | `dingtalk-workspace` | `dws shortcut list --service attendance --format json` | +| `calendar` | 20 | `dingtalk-workspace` | `dws shortcut list --service calendar --format json` | +| `chat` | 97 | `dingtalk-workspace` | `dws shortcut list --service chat --format json` | +| `contact` | 14 | `dingtalk-workspace` | `dws shortcut list --service contact --format json` | +| `devapp` | 19 | `dingtalk-workspace` | `dws shortcut list --service devapp --format json` | +| `ding` | 4 | `dingtalk-workspace` | `dws shortcut list --service ding --format json` | +| `doc` | 17 | `dingtalk-workspace` | `dws shortcut list --service doc --format json` | +| `drive` | 7 | `dingtalk-workspace` | `dws shortcut list --service drive --format json` | +| `mail` | 10 | `dingtalk-workspace` | `dws shortcut list --service mail --format json` | +| `minutes` | 6 | `dingtalk-workspace` | `dws shortcut list --service minutes --format json` | +| `oa` | 7 | `dingtalk-workspace` | `dws shortcut list --service oa --format json` | +| `report` | 2 | `dingtalk-workspace` | `dws shortcut list --service report --format json` | +| `sheet` | 2 | `dingtalk-workspace` | `dws shortcut list --service sheet --format json` | +| `todo` | 11 | `dingtalk-workspace` | `dws shortcut list --service todo --format json` | +| `wiki` | 1 | `dingtalk-workspace` | `dws shortcut list --service wiki --format json` | ## 多组织 / 多账号 diff --git a/skills/mono/references/intent-guide.md b/skills/mono/references/intent-guide.md index ff6966d6..7f613207 100644 --- a/skills/mono/references/intent-guide.md +++ b/skills/mono/references/intent-guide.md @@ -315,7 +315,7 @@ dws chat +messages-send --as user --open-dingtalk-id --msg-type 资源读取优先在 `+at-me`、`+chat-messages`、`+messages-mget`、`+search-msg`、`+thread-replies` 上加 `--download-resources`。引用、回复或合并转发里的资源使用结果 `resourceRefs` 自带的子消息 `messageId`;子消息没有会话 ID 时才继承父消息会话。单独下载一个资源时用 `+messages-resource-download`,输出必须是工作目录内的相对路径,默认拒绝覆盖。 -上述查询 Shortcut 因可选本地下载能力按命令级 Schema 发布为 `write/user_required`;执行前读取 leaf Schema,确认输出目录和覆盖策略,获得用户确认后才加 `--yes`。 +上述五个查询 Shortcut 的默认 leaf Schema 契约是 `read/not_required`;仅显式传 `--download-resources` 时,运行时风险升级为 `write` 并要求确认。确认输出目录和覆盖策略、获得用户确认后才加 `--yes`。 **不支持的场景**: - "撤回我刚发的消息"(但不知道消息 ID) — 需先通过消息拉取或搜索接口(如 `chat message list`、`chat message search-advanced` 等)获取 openMessageId,再调用 `chat message recall` diff --git a/skills/mono/references/products/chat.md b/skills/mono/references/products/chat.md index 69556541..4ab88de6 100644 --- a/skills/mono/references/products/chat.md +++ b/skills/mono/references/products/chat.md @@ -20,7 +20,7 @@ - `+messages-send` 会自动规范化并补齐 @ 占位符。user 使用 `<@id>` / `<@all>`;bot/webhook 使用 `@id` / `@手机号` / `@all`。声明 `--at-*` / `--at-all` 即可,不要为统一 Shortcut 手工拼 `@10`。 - `+search-msg --page-all` 连续翻页并默认按消息 ID 批量富化;任何续页或富化失败都会保留已取得结果并返回逐项失败 ledger。 - `+at-me`、`+chat-messages`、`+messages-mget`、`+search-msg`、`+thread-replies` 可用 `--download-resources` 下载资源。引用、回复、合并转发中的资源使用结果 `resourceRefs` 自带的子消息 `messageId`;仅当子消息缺会话 ID 时继承父消息 `openConversationId`。 -- 上述查询 Shortcut 因可选本地下载能力按命令级 Schema 发布为 `write/user_required`;`+messages-resource-download` 始终写本地。确认相对输出目录和覆盖策略后才加 `--yes`,默认拒绝覆盖已有文件。 +- 上述五个查询 Shortcut 的默认 Schema 契约是 `read/not_required`;仅显式传 `--download-resources` 时,运行时风险升级为 `write` 并要求确认。`+messages-resource-download` 始终写本地。确认相对输出目录和覆盖策略后才加 `--yes`,默认拒绝覆盖已有文件。 - 下载器仅接受经审查的钉钉与公网 OSS HTTPS 地址并逐跳校验重定向;跨主机时不会转发下层提供的请求头。新官方域名被拒绝时记录错误中的 host 供审查,不要放宽为任意 HTTPS。 ### group (群组管理) @@ -1081,6 +1081,22 @@ Flags: --background-id string 背景 ID (必填) ``` +#### 原地更新消息的文字表情回应 +``` +Usage: + dws chat message update-text-emotion [flags] +Example: + dws chat message update-text-emotion --conversation-id --msg-id --old-emotion-id --emotion-id --emotion-name "处理中" --text "处理中 2 分钟" --background-id im_bg_5 +Flags: + --conversation-id string 会话 openConversationId (必填,支持单聊/群聊,别名: --group / --id / --chat) + --msg-id string 消息 openMsgId (必填) + --old-emotion-id string 待替换的原表情 ID (必填) + --emotion-id string 新表情 ID (必填,通过 create-text-emotion 获取) + --emotion-name string 新表情名称 (必填) + --text string 新文字内容 (必填) + --background-id string 新背景 ID (必填) +``` + #### 移除消息的文字表情回应 ``` Usage: @@ -1932,6 +1948,7 @@ Flags: 用户说"emoji回应/表情回应/给消息加表情" → `chat message add-emoji` 用户说"取消emoji回应/移除表情回应" → `chat message remove-emoji` 用户说"文字表情回应/添加文字表情" → `chat message add-text-emotion` +用户说"修改文字表情回应/更新消息状态文字" → `chat message update-text-emotion` 用户说"取消文字表情回应/移除文字表情" → `chat message remove-text-emotion` 用户说"创建文字表情/新建文字表情" → `chat message create-text-emotion` 用户说"免打扰/消息免打扰/静音/开启免打扰/关闭免打扰" → `chat mute` @@ -1994,7 +2011,7 @@ Flags: - `chat message list-by-ids` — 根据消息 ID 批量查询消息(最多 50 条) - `chat message add-emoji` / `remove-emoji` — 对消息添加/移除 emoji 表情回应 - `chat message list-emotion-replies` — 批量拉取消息的表情回复和文字回复 -- `chat message add-text-emotion` / `remove-text-emotion` — 对消息添加/移除文字表情回应 +- `chat message add-text-emotion` / `update-text-emotion` / `remove-text-emotion` — 对消息添加、原地更新或移除文字表情回应 - `chat message create-text-emotion` — 创建文字表情模板,返回 emotionId 供 add-text-emotion 使用 - `chat category list` — 获取用户自定义会话分组列表 - `chat category list-conversations` — 拉取指定分组下的会话列表 diff --git a/skills/mono/scripts/bulk_add_fields.py b/skills/mono/scripts/bulk_add_fields.py index 78c5bdfc..5783d9b4 100644 --- a/skills/mono/scripts/bulk_add_fields.py +++ b/skills/mono/scripts/bulk_add_fields.py @@ -34,9 +34,9 @@ ALLOWED_FIELD_TYPES = { 'text', 'number', 'singleSelect', 'multipleSelect', 'date', 'currency', 'user', 'department', 'group', 'progress', 'rating', 'checkbox', 'attachment', 'url', 'richText', 'telephone', 'email', 'idCard', - 'barcode', 'geolocation', 'primaryDoc', 'formula', 'unidirectionalLink', - 'bidirectionalLink', 'creator', 'lastModifier', 'createdTime', - 'lastModifiedTime', + 'barcode', 'geolocation', 'address', 'primaryDoc', 'formula', + 'unidirectionalLink', 'bidirectionalLink', 'lookup', 'filterUp', + 'creator', 'lastModifier', 'createdTime', 'lastModifiedTime', } FIELD_TYPE_ALIASES = { 'phone': 'telephone', @@ -121,9 +121,32 @@ def validate_field_config(field: Dict[str, Any]) -> Tuple[bool, str]: ) if field_type in {'unidirectionalLink', 'bidirectionalLink'}: - linked_sheet_id = (config or {}).get('linkedSheetId') - if not linked_sheet_id or not validate_resource_id(linked_sheet_id): - return False, '关联字段必须提供合法的 config.linkedSheetId' + linked_table_id = (config or {}).get('linkedTableId') + if not linked_table_id or not validate_resource_id(linked_table_id): + return False, ( + '关联字段必须提供合法的 config.linkedTableId(目标 Table ID)' + ) + + if field_type == 'lookup': + cfg = config or {} + if not cfg.get('associateField'): + return False, 'lookup 必须提供 config.associateField(本表关联字段的 fieldId)' + if not cfg.get('valuesField'): + return False, 'lookup 必须提供 config.valuesField(关联目标表中要取值的字段 fieldId)' + if not cfg.get('aggregator'): + return False, 'lookup 必须提供 config.aggregator(SUM/AVERAGE/COUNT/MAX/MIN/CONCATENATE)' + + if field_type == 'filterUp': + cfg = config or {} + if not cfg.get('targetSheet'): + return False, 'filterUp 必须提供 config.targetSheet(目标 Table ID)' + filters = cfg.get('filters') + if not filters or not isinstance(filters, list): + return False, 'filterUp 必须提供 config.filters(至少一条筛选规则)' + if not cfg.get('valuesField'): + return False, 'filterUp 必须提供 config.valuesField(目标表中要取值的字段 fieldId)' + if not cfg.get('aggregator'): + return False, 'filterUp 必须提供 config.aggregator(SUM/AVERAGE/COUNT/MAX/MIN/CONCATENATE)' return True, '' diff --git a/skills/multi/dingtalk-agoal/SKILL.md b/skills/multi/dingtalk-agoal/SKILL.md deleted file mode 100644 index 6f501853..00000000 --- a/skills/multi/dingtalk-agoal/SKILL.md +++ /dev/null @@ -1,43 +0,0 @@ ---- -name: dingtalk-agoal -description: 钉钉 Agoal 目标管理。Use when 用户说 目标管理/Agoal/战略解码/经营合约/计分卡/OKR/目标模板/周月报提交统计/跟催。Distinct from dingtalk-todo(待办任务)、dingtalk-report(日志)。命令前缀:dws agoal。 -cli_version: ">=0.2.14" -metadata: - category: product - stability: experimental - requires: - bins: - - dws ---- - -# 钉钉 Agoal Skill - -> 🧪 **EXPERIMENTAL · 试验版 / Preview** — multi 模式当前未达 stable 标准。全部 dingtalk-* skill 已通过 dispatch verifier,但接口、命名、跨 skill 引用后续可能调整;生产 / 共享环境请优先使用 mono 模式(`dws skill setup --mode mono`)。问题请提 issue 反馈。 - -> **PREREQUISITE:** Read the `dws-shared` skill first for auth, global flags, product routing, URL preflight, error codes, and safety rules. The `dws` binary must be on PATH. - - - -> ⚠️ **命令可用性以当前 dws 二进制为准**。服务发现已下线,本文档随内置 skill 发布;如果 `dws --help` 不存在,说明当前版本未暴露该命令。若命令存在但调用失败,请按错误中的 endpoint 或 tool 提示确认静态端点目录和后端工具注册。实际调用前可用 `dws --help` 或 `--dry-run` 验证。 - -> 命令参考:[agoal.md](references/agoal.md)。 - -## 意图表 - -| 用户说 | 命令 | -|--------|------| -| "查战略解码 / OGSM" | `dws agoal strategy list --scope-type --scope-id ` | -| "看战略解码详情" | `dws agoal strategy detail --profile-id ` | -| "更新战略解码" | 先 detail,基于完整数据修改后 `dws agoal strategy update ...` | -| "查经营合约 / KPI合约" | `dws agoal contract list/detail/fields` | -| "更新经营合约" | 先 detail,基于完整 dimensions 修改后 `dws agoal contract update ...` | -| "查计分卡" | `dws agoal scorecard detail --selected-time --dept-id ` | -| "我的目标 / 个人目标" | `dws agoal user rules` → `dws agoal user objectives` | -| "周月报提交统计 / 跟催 / 迟交 / 未提交" | `dws agoal report list-statistics` → `dws agoal report submit-detail` | -| "目标模板" | `dws agoal obj-template list/create-or-update` | - -## 写操作硬约束 - -- `strategy update` / `contract update` / `scorecard update` / `obj-template create-or-update` 都是覆盖式写入。 -- 必须先读取现有详情或模板列表,在原数据基础上修改;禁止只传局部字段覆盖。 -- 执行前必须向用户展示目标对象、修改字段和最终命令参数摘要,并等待确认。 diff --git a/skills/multi/dingtalk-agoal/references/agoal.md b/skills/multi/dingtalk-agoal/references/agoal.md deleted file mode 100644 index 1f1ad332..00000000 --- a/skills/multi/dingtalk-agoal/references/agoal.md +++ /dev/null @@ -1,77 +0,0 @@ -# Agoal(目标管理) - -## 产品说明 - -Agoal 是钉钉目标管理工具,支持战略解码、经营合约、计分卡、用户目标、目标模板、周月报六大模块,帮助组织将战略目标从顶层分解到个人并持续跟踪。 - -**CLI 前缀**: `dws agoal` - -## 命令总览 - -### strategy (战略解码管理) - -| 命令 | 用途 | 必填参数 | 备注 | -|------|------|----------|------| -| `strategy list` | 获取战略解码列表 | `--scope-type` `--scope-id` | scopeType: DEPT/PERSONAL;scope-id 为 scope-type 对应的部门 id 或用户 id | -| `strategy detail` | 获取战略解码详情 | `--profile-id` | 根据战略解码 id 查询 | -| `strategy update` | 更新战略解码 | `--profile-id` `--content` | 覆盖逻辑,必须基于查询返回的老数据修改后再传入;`--content` 为 JSON 数组 | - -### contract (经营合约管理) - -| 命令 | 用途 | 必填参数 | 备注 | -|------|------|----------|------| -| `contract list` | 获取经营合约列表 | `--scope-type` `--scope-id` | scopeType: DEPT/PERSONAL;scope-id 为 scope-type 对应的部门 id 或用户 id | -| `contract fields` | 获取经营合约字段列表 | - | 获取组织下经营合约的字段配置 | -| `contract detail` | 获取经营合约详情 | `--contract-id` | 根据合约 id 查询 | -| `contract update` | 更新经营合约 | `--contract-id` `--dimensions` | 覆盖逻辑;可选 `--audit-config`、`--objective-template` | - -### scorecard (计分卡管理) - -| 命令 | 用途 | 必填参数 | 备注 | -|------|------|----------|------| -| `scorecard detail` | 获取计分卡详情 | `--selected-time` `--dept-id` | selectedTime 为 ISO-8601 字符串 | -| `scorecard entity-detail` | 获取计分卡实体详情 | `--sc-id` `--entity-id` | 根据计分卡 id 和实体 id 查询 | -| `scorecard update` | 更新计分卡 | `--dept-id` `--selected-time` `--id` `--tracking-period-type` `--content` | trackingPeriodType: MONTHLY/QUARTERLY | - -### user / report / obj-template - -| 命令 | 用途 | 必填参数 | -|------|------|----------| -| `user rules` | 获取用户规则周期列表 | - | -| `user objectives` | 查询用户目标列表 | `--user-id` `--rule-id` `--period-ids` | -| `report list-statistics` | 获取周月报数据跟催列表 | - | -| `report submit-detail` | 获取周月报规则提交详情 | `--template-id` `--submit-state` | -| `obj-template list` | 获取目标模板列表 | - | -| `obj-template create-or-update` | 新增或更新目标模板 | `--dimensions` | - -## 意图判断 - -- "战略解码/战略目标/OGSM" → `strategy list/detail/update` -- "经营合约/合约/KPI合约" → `contract list/fields/detail/update` -- "计分卡/scorecard/绩效看板" → `scorecard detail/entity-detail/update` -- "目标/OKR/我的目标/个人目标" → `user rules/objectives` -- "目标模板/模板管理" → `obj-template list/create-or-update` -- "周月报/周报统计/提交情况/跟催/迟交/未提交" → `report list-statistics/submit-detail` - -## 常用命令 - -```bash -dws agoal strategy list --scope-type DEPT --scope-id DEPT_ID --format json -dws agoal strategy detail --profile-id PROFILE_ID --format json -dws agoal contract list --scope-type PERSONAL --scope-id USER_ID --format json -dws agoal contract fields --format json -dws agoal contract detail --contract-id CONTRACT_ID --format json -dws agoal scorecard detail --selected-time "2026-01-01T00:00:00+08:00" --dept-id DEPT_ID --format json -dws agoal user rules --user-id USER_ID --format json -dws agoal user objectives --user-id USER_ID --rule-id RULE_ID --period-ids "period1,period2" --format json -dws agoal report list-statistics --format json -dws agoal report submit-detail --template-id TPL_ID --submit-state ON_TIME --format json -dws agoal obj-template list --keyword "业绩" --format json -``` - -## 安全规则 - -- 所有 update / create-or-update 命令都是覆盖逻辑:必须先用对应 detail/list 查询完整数据,在原数据基础上修改后再传入。 -- 写入类命令执行前必须展示变更摘要并等待用户确认。 -- `--selected-time` / `--query-date` 接受 ISO-8601 字符串。 -- `--period-ids` 为逗号分隔字符串。 diff --git a/skills/multi/dingtalk-aisearch/SKILL.md b/skills/multi/dingtalk-aisearch/SKILL.md index e22c3dd8..9378458a 100644 --- a/skills/multi/dingtalk-aisearch/SKILL.md +++ b/skills/multi/dingtalk-aisearch/SKILL.md @@ -1,25 +1,19 @@ --- name: dingtalk-aisearch -description: AI 搜问 - 搜人首选入口(按姓名/部门/职位/职责/上下级/手机号/工号维度)。Use when 用户说 找同事/找人/谁负责XX/XX的负责人是谁/查上级/查下级/团队成员/XX工号是谁/XX手机号。Distinct from dingtalk-contact(精确按 userId 查详情)。命令前缀: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 --- -# 钉钉 AI 搜问(搜人)Skill +# 钉钉 AI 搜问 Skill -> 🧪 **EXPERIMENTAL · 试验版 / Preview** — multi 模式当前未达 stable 标准。全部 dingtalk-* skill 已通过 dispatch verifier,但接口、命名、跨 skill 引用后续可能调整;生产 / 共享环境请优先使用 mono 模式(`dws skill setup --mode mono`)。问题请提 issue 反馈。 - -> **PREREQUISITE:** Read the `dws-shared` skill first for auth, global flags, product routing, URL preflight, error codes, and safety rules. The `dws` binary must be on PATH. - - - -> ⚠️ **命令可用性以当前 dws 二进制为准**。服务发现已下线,本文档随内置 skill 发布;如果 `dws --help` 不存在,说明当前版本未暴露该命令。若命令存在但调用失败,请按错误中的 endpoint 或 tool 提示确认静态端点目录和后端工具注册。实际调用前可用 `dws --help` 或 `--dry-run` 验证。 +## 前置条件 — 执行操作前必读 +> **CRITICAL — 执行任何 `dws` 操作前,MUST 先用 Read 工具完整读取 [`dws-shared`](../dws-shared/SKILL.md)。**该轻量文件包含全局执行契约、安全底线及 shared references 的按需加载导航;不要预加载其全部 references。 > 命令参考:[aisearch.md](references/aisearch.md)。 @@ -31,9 +25,47 @@ 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 <方向>` | -## 评测高频硬约束 +## 标准 SOP(必遵流程) + +> 命中以下意图**必须**按对应 SOP 顺序执行;**禁止**跳步、替换命令、编造 flag/ID。每条命令必须带 `--format json`,执行后必须按"解析"步取真实字段,不得凭返回结构猜测。 + +### SOP-1 搜人 → 拿 userId(search-person) + +**触发**:姓名模糊找人/谁负责/查上下级/部门成员/工号反查/手机号线索语义搜人。 + +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-misc`(`references/ding.md`)。 +5. **失败(必须)**:未命中最多换 1 个维度重试一次(如 `name`→`department`/`jobNumber`/`phone`),仍保留完整目标值;仍无果**必须如实告知**。 + +**禁止**:用半截姓名扩大搜索、跳过 `--format json`、取首个候选、凭空补全人员信息。 + +### SOP-2 跨源搜内容(search-content) + +**触发**:跨文档/邮件/消息按主题找内容。 + +1. **执行(必须)**:`dws aisearch enterprise --queries "<主题>" --types --time-range "<时间>" --format json`;多主题逗号分隔。 +2. **衔接(必须)**:按命中来源切到对应产品 skill 读写。**aisearch 只负责"找到",不做读写。** + +**禁止**:把 aisearch 当作读写入口、跳过下游 skill 直接改数据。 + +### SOP-3 行为回溯(search-behavior) + +**触发**:"我发过/收到过/创建过/分享过什么"。 + +1. **执行(必须)**:`dws aisearch behavior --queries "<主题>" --behavior-type <动作> --direction <方向> --format json`。 +2. **衔接(必须)**:按记录类型切对应 skill 操作;aisearch 不做读写。 + +**禁止**:编造行为结果、跳过 `--format json`。 + +## 高频硬约束 - 搜索目标必须完整保真:姓名、工号、手机号、部门名按用户原文完整传入 `--keyword`,严禁自行截断、拆字、改昵称或扩展同音字。 - 首次未命中时最多换维度重试一次(如 name → department/jobNumber/phone),仍必须保留完整目标值;不要用半截姓名扩大搜索。 @@ -45,4 +77,7 @@ metadata: - 拿到 userId 后查详情 / 部门 → 切到 `dingtalk-contact` - 拿到 userId 发消息 → 切到 `dingtalk-chat` -- 拿到 userId 发 DING → 切到 `dingtalk-ding` +- 拿到 userId 发 DING → 切到 `dingtalk-misc`(`references/ding.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 f7f33544..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 详情、部门成员列表) ### 高优先级抽取规则 @@ -98,7 +99,7 @@ Flags: | 操作 | 从返回中提取 | 用于 | |------|-------------|------| -| `aisearch person` | `userId`(用户ID,= `meta.staffId`/`meta.jobNumber`)、`author`(真实姓名,= `meta.name`);`title` 是花名/显示名,不一定等于姓名 | 展示搜索结果、后续操作(发消息/建待办等) | +| `aisearch person` | `userId`(用户ID)、`title`(姓名) | 展示搜索结果、后续操作(发消息/建待办等) | ## 重名消歧 diff --git a/skills/multi/dingtalk-aisearch/references/intent-guide.md b/skills/multi/dingtalk-aisearch/references/intent-guide.md new file mode 100644 index 00000000..7add78f0 --- /dev/null +++ b/skills/multi/dingtalk-aisearch/references/intent-guide.md @@ -0,0 +1,15 @@ +# aisearch 局部意图消歧 + +本文件从单 Skill `intent-guide.md` 拆分而来,仅保留与本产品相关的跨产品消歧规则。 + +| 用户说... | 真实意图 | 应该用 | 不要用 | 理由 | +|---|---|---|---|---| +| "张三在哪个部门/张三的工号是多少" | 搜人后查通讯录详情 | `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是谁/完整手机号反查" | 精确手机号反查 | `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 new file mode 100644 index 00000000..00f85340 --- /dev/null +++ b/skills/multi/dingtalk-aisearch/references/lite-recipes.md @@ -0,0 +1,31 @@ +# aisearch Lite Recipe + +本文件从单 Skill `lite-recipes.md` 拆分而来,仅保留与本产品相关的轻量流程。 + +## #8 通讯录 + +### get-contact-self + +`contact user get-self` → 当前用户 userId、部门、主管等 + +### search-person + +**搜人首选入口**。凡是“找人/搜人/找同事/谁负责/上级/下级/负责人/团队成员”均优先用 `aisearch person`: + +1. 从用户问题中提取 keyword(人名/业务关键词)和 dimension(维度),规则见 [aisearch.md](./aisearch.md)。 +2. `aisearch person --keyword "<关键词>" --dimension <维度>` +3. 结果中提取 `userId` 和 `title`(姓名)展示给用户。 +4. 若需要 userId 做后续操作(发消息/建待办),可直接使用结果中的 `userId`。 +5. **重名消歧**:多人同名时禁止默认选第一个,须追加 `contact user get --ids` 获取部门/职位后请用户确认,详见 [08-directory.md](../../dingtalk-contact/references/08-directory.md)「多命中」。 + +### search-user + +仅在以下**精确查询**场景使用,搜人请优先用 `search-person`: + +- 需要获取 userId 给其他产品使用(发消息/建待办/约日程) +- 已有 userId 需查完整详情(`contact user get --ids`) +- 完整手机号精确反查(`contact user search-mobile --mobile`) + +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,...`)。 diff --git a/skills/multi/dingtalk-aitable/SKILL.md b/skills/multi/dingtalk-aitable/SKILL.md index e5dd5122..8078fe62 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/导出/仪表盘/图表/上传附件到表格/按字段类型建表。Distinct from 主 dws skill 的 dws sheet(电子表格/单元格读写/公式)、dws doc(文档编辑)。命令前缀: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,14 +11,9 @@ metadata: # 钉钉 AI 表格 Skill -> 🧪 **EXPERIMENTAL · 试验版 / Preview** — multi 模式当前未达 stable 标准。全部 dingtalk-* skill 已通过 dispatch verifier,但接口、命名、跨 skill 引用后续可能调整;生产 / 共享环境请优先使用 mono 模式(`dws skill setup --mode mono`)。问题请提 issue 反馈。 - -> **PREREQUISITE:** Read the root `dws` skill first for auth, global flags, product routing, URL preflight, error codes, and safety rules. The `dws` binary must be on PATH. - - - -> ⚠️ **命令可用性以当前 dws 二进制为准**。服务发现已下线,本文档随内置 skill 发布;如果 `dws --help` 不存在,说明当前版本未暴露该命令。若命令存在但调用失败,请按错误中的 endpoint 或 tool 提示确认静态端点目录和后端工具注册。实际调用前可用 `dws --help` 或 `--dry-run` 验证。 +## 前置条件 — 执行操作前必读 +> **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)。 @@ -78,11 +72,74 @@ metadata: | "仪表盘 / 图表" | 先读 `references/aitable/aitable-dashboard-chart.md` | | "上传附件到记录" | 先读 `references/aitable/aitable-attachment.md`;可用 `python scripts/upload_attachment.py --base-id --file ` | +## 标准 SOP(必遵流程) + +> 命中以下意图**必须**按对应 SOP 顺序执行;**禁止**跳步、替换命令、编造 flag/ID。每条命令必须带 `--format json`,执行后必须按"解析"步取真实字段,不得凭返回结构猜测。`baseId`/`tableId`/`fieldId`/`recordId` 一律先查后用,**禁止默认/编造**。 + +### SOP-1 定位 Base 与 Table(list / search → table get) + +**触发**:找/打开某张 AI 表格、不知 baseId 或 tableId。 + +1. **选源(必须)**:有名称/关键词 → `dws aitable base search --query "<名称>"`;列最近访问 → `dws aitable base list`。`base list` 仅返回最近访问,不是全部,**禁止**当作全量清单。 +2. **执行(必须)**:`dws aitable base search --query "<完整名>" --format json`(或 `dws aitable base list --format json`)。 +3. **解析(必须)**:从 JSON 取真实 `baseId`;**多候选必须输出让用户选,禁止默认取第一个**。 +4. **取 tableId(必须)**:`dws aitable table get --base-id --format json` → 从 `data.tables[].tableId` 取目标表 ID,并记录 `views[]`。枚举模式不返回 `fields[]`;需要字段目录时必须继续执行 SOP-2 的 `field get`。若只核对某张表,可显式加 `--table-ids ` 控制返回体。 +5. **失败(必须)**:`base list` 为空或不命中 → 换 `base search --query` 关键词重试一次;仍无果**必须如实告知**,禁止臆造 baseId/tableId。 + +**禁止**:跳过 `table get` 直接用字段名写记录、用模糊名匹配当 baseId、用旧会话里的 ID 不再校验。 + +### SOP-2 拿字段定义(field get,写记录/改字段前置) + +**触发**:建/改/写记录、改字段名或 options、按字段类型拼写入参前。 + +1. **前置(必须)**:先按 SOP-1 拿到 `baseId` + `tableId`。 +2. **执行(必须)**:`dws aitable field get --base-id --table-id --format json`(仅展开需要的字段时加 `--field-ids fld1,fld2`,单次最多 10 个)。 +3. **解析(必须)**:取每个目标字段的 `fieldId`、`type`、`config`(如 singleSelect/multipleSelect 的 `options[].id|name`);写入 cells 的 key **必须用 `fieldId`**,不是字段中文名;select 字段过滤/写入传**选项名称字面量**,不传 option ID。 +4. **衔接(必须)**:拿到字段定义 → 进入 SOP-3 写记录、或 `dws aitable field update --field-id --name <新名>|--config --format json` 改字段。 +5. **失败(必须)**:字段不存在或类型不符 → 重新 `field get` 核对,**禁止**凭旧名称/旧类型继续写入。 + +**禁止**:用字段中文名当 cells key、跳过 `field get` 直接 `record create/update`、对 select 字段传 option ID 当写入值。 + +### SOP-3 写/批量写记录(record create) + +**触发**:新增记录、批量加数据、CSV/JSON 入表。 + +1. **前置(必须)**:SOP-1 取 `baseId`/`tableId` + SOP-2 取 `fieldId`/类型。 +2. **执行(必须)**:`dws aitable record create --base-id --table-id --records '[{"cells":{"":<值>}}]' --format json`;单次最多 100 条,超长用 `--records-file ./data.json`。 +3. **写入格式(必须)**:按 `record create --help` 类型表严格传值(text→字符串、number→数值、singleSelect→"选项名"、date→RFC3339、url→`{"text","link"}`、group→`{"cid"}` 等);`filterUp`/`lookup` 字段只读不可写。 +4. **解析与验证(必须)**:从返回 `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 字段。 + +### SOP-4 查/筛/排记录(record query) + +**触发**:查记录、按条件筛选、排序、取关联记录、定位待改/待删的 recordId。 + +1. **前置(必须)**:SOP-1 拿 `baseId`/`tableId`。 +2. **执行(必须)**:`dws aitable record query --base-id --table-id --format json`;已知 ID 直取加 `--record-ids rec1,rec2`(忽略 filters/sort,单次≤100)。 +3. **筛选/排序(必须)**:`--filters` 最外层必须 `{"operator":"and|or","operands":[...]}`,select 字段值传**选项名字面量**;日期只能用 `date_eq/before/after/not_before/not_after`,范围用 `not_before`+`not_after` 组合,**禁止** `eq`/区间/相对时间。`--sort` 用 `[{"fieldId":"..","direction":"asc|desc"}]`(**必须用 `direction`**)。公式/引用/关联字段默认不返回,需显式 `--field-ids` 指定。 +4. **解析(必须)**:取真实 `recordId` 与字段值;分页用 `--cursor`,全表用 `--all --page-limit N`。 +5. **衔接(必须)**:拿到 recordId → SOP-5 更新、`record delete --record-ids --yes` 删除(删前确认)。 + +**禁止**:用字段名做 filter/sort key、对日期用 `eq`、漏掉 `direction` 用旧 `order` 字段、用本地过滤替代服务端 filter。 + +### SOP-5 更新记录(record update) + +**触发**:改记录字段值、批量更新状态、单字段重命名需求之外的记录改动。 + +1. **前置(必须)**:SOP-1 拿 `baseId`/`tableId`;SOP-2 拿字段类型;SOP-4 拿目标 `recordId`。 +2. **执行(必须)**:`dws aitable record update --base-id --table-id --records '[{"recordId":"recXXX","cells":{"":<新值>}}]' --format json`(每条必含 `recordId`+`cells`,单次≤100;超长用 `--records-file`);只传需改字段,未传保持原值。 +3. **解析与验证(必须)**:写入格式同 SOP-3;从返回 `data.recordIds[]` 取实际更新的记录 ID。更新响应不返回“受影响字段”,必须立即用 `record query --record-ids --format json` 回读目标字段确认。 +4. **失败(必须)**:recordId 不存在或类型不符 → 回 SOP-4 重新定位,**禁止**编造 ID 强写。 + +**禁止**:省略 `recordId`、用字段中文名当 cells key、凭空猜测 recordId 直接 update。 + ## 危险操作 `base delete` / `table delete` / `field delete` / `record delete` 不可逆,必须先向用户确认再加 `--yes`。 -## 评测高频硬约束 +## 高频硬约束 - 创建/改字段/写记录是多轮连续任务时,不能在"让我执行/先获取 ID"后停下;必须实际调用对应 `dws aitable` 命令并验证结果。 - 字段重命名使用 `dws aitable field update --base-id --table-id --field-id --name "<新名称>" --format json`;先 `field get` 找真实 `fieldId`,不要猜字段名能直接更新。 @@ -97,4 +154,7 @@ metadata: ## 跨产品协作 -- 单元格 / 工作表 / 公式 → 走主 `dws` skill 的 `sheet` 产品路由(`dws sheet`) +- 单元格 / 工作表 / 公式 → 切到 `dingtalk-misc`(`references/sheet.md`,命令前缀:`dws sheet`) +## 局部意图 + +- [局部意图消歧](references/intent-guide.md)。 diff --git a/skills/multi/dingtalk-aitable/references/06-data-analytics.md b/skills/multi/dingtalk-aitable/references/06-data-analytics.md index 3fd9b0bc..41e8d5bf 100644 --- a/skills/multi/dingtalk-aitable/references/06-data-analytics.md +++ b/skills/multi/dingtalk-aitable/references/06-data-analytics.md @@ -4,10 +4,8 @@ | Recipe | 行动指南(固定路线) | |--------|-------------------| -| read-aitable | 1. `aitable base search --query "<表格名>"` → 取 `baseId`/`tableId`
2. `aitable field get --base-id --table-id ` → 取 `fieldId`
3. **取记录(按场景分流)**:
  • 数据统计/分析/全量汇总 → `aitable record query --base-id --table-id --all`(自动翻页,**禁止凭单页数据做统计**)
  • 大表保险 → 加 `--page-limit 100`(默认 50 页/5000 条,0 = 无限制)
  • 单纯预览前几条 → `aitable record query --base-id --table-id --limit 30`(不加 --all)
  • 筛选时 `--filters` 格式见 [aitable-filter-sort.md](../products/aitable/aitable-filter-sort.md)
4. **检查输出契约**:`hasMore=true` 时数据被截断,必须用 `--cursor ` 续拉;`partial=true` 时表示中途某页失败(保留已拉数据,可重试)
5. 总结数据 | -| generate-data-report | 1. 同 read-aitable 步骤 1-3(**必须用 --all 防漏数据**)
2. 按[「多源并行采集」](_common/conventions.md#多源并行采集公共模式)执行 → 补充背景
3. `doc create --name "<报告名>" --content "<分析报告>"` | -| create-aitable-record | **写入路径分流**(关键决策):
  • **追加到已有表**(场景:把这批数据加到「成员表」)→ `python scripts/import_records.py data.csv\|data.json [batch_size]`(走 `record create`,需要列名匹配字段名)
  • **新建表**(场景:把这个 Excel 变成多维表)→ `python scripts/aitable_import_via_task.py `(脚本内置 prepare→OSS PUT→import data 全流程和正确的头处理,**禁止自己写 PUT**)。注意 `aitable import upload` 没有 `--file` flag、也不代做 PUT,只准备导入;能一站式完成的是上面的脚本
  • **单条/少量手动**:1. `aitable base search` → `baseId`/`tableId`;2. `aitable field get` → `fieldId` 与类型;3. `aitable record create --records '[{"cells":{"":"值"}}]'` | -| update-aitable-record | 1. `aitable base search --query "<表格名>"` → 取 `baseId`/`tableId`
2. **取目标 record**:
  • 已知少量 recordId → `aitable record query --record-ids `
  • 按条件批量改 → `aitable record query --base-id --table-id --filters '' --all`(**用 --all 防止漏改**)
3. **先展示让用户确认要改的 record 列表**
4. `aitable record update --base-id --table-id --records '[{"recordId":"","cells":{...}}]'`(单次 ≤30 条) | +| read-aitable | 1. `aitable base search --query "<表格名>"` → 取 `baseId`/`tableId`
2. `aitable field get --base-id --table-id ` → 取 `fieldId`
3. `aitable record query --base-id --table-id ` → 取记录(分页)
  需要筛选时 `--filters` 格式见 [aitable-filter-sort.md](./aitable/aitable-filter-sort.md),根节点必须是 `{"operator":"and\|or","operands":[...]}`
4. 总结数据 | +| generate-data-report | 1. 同 read-aitable 步骤 1-3
2. 按[「多源并行采集」](_common/conventions.md#多源并行采集公共模式)执行 → 补充背景
3. `doc create --name "<报告名>" --content "<分析报告>"` | +| create-aitable-record | **批量导入优先**:`python scripts/import_records.py data.csv\|data.json [batch_size]`(自动分批创建)
单条/少量:1. `aitable base search --query "<表格名>"` → 取 `baseId`/`tableId`
2. `aitable field get --base-id --table-id ` → 取 `fieldId` 与类型
3. `aitable record create --base-id --table-id --records '[{"cells":{"":"值"}}]'` | +| update-aitable-record | 1. `aitable base search --query "<表格名>"` → 取 `baseId`/`tableId`
2. `aitable record query --base-id --table-id ` → 取 `recordId`,**先展示让用户确认**
3. `aitable record update --base-id --table-id --records '[{"recordId":"","cells":{...}}]'` | | search-aitable-template | 1. `aitable template search --query "<关键词>"` → 取 `templateId`
2. 用户选定
3. `aitable base create --name "<表格名>" --template-id ` → 取 `baseId` | -| export-aitable-to-xlsx | 1. `aitable base search --query "<表格名>"` → 取 `baseId`
2. **按场景选 scope**:
  • 全表+附件 → `aitable export data --base-id --scope all --export-format excel_and_attachment --output ./.xlsx`
  • 单表(仅 xlsx)→ `--scope table --table-id --export-format excel`
  • 单视图 → `--scope view --table-id --view-id `
3. CLI 内置渐进式退避轮询 + 自动落盘,**不要自己写 GET downloadUrl**
4. 大表超时(默认 5 分钟):加 `--timeout-sec 900` 或拿到 `taskId` 后 `aitable export data --task-id --output ./.xlsx` 续等
5. 与悟空脚本路径并存:复杂场景(多 base 批量 / 按视图组合)请用 `python scripts/aitable_export_via_task.py --scope all\|table\|view` | -| primary-doc-from-record | 当 AI 表格用文档作主键字段时,从 record 跳到其主文档:
1. `aitable record query` 取 `recordId`
2. `aitable base get-primary-doc-id --base-id --table-id --record-id ` → 取主键文档 `dentryUuid`
3. 凭 `dentryUuid` 调 `doc read --node ` 拿文档内容 | diff --git a/skills/multi/dingtalk-aitable/references/_common/conventions.md b/skills/multi/dingtalk-aitable/references/_common/conventions.md index 93e65f8c..803212b8 100644 --- a/skills/multi/dingtalk-aitable/references/_common/conventions.md +++ b/skills/multi/dingtalk-aitable/references/_common/conventions.md @@ -1,6 +1,6 @@ # 业务域通用规范 -> 仅服务本仓库已迁入的文档与 AI 表格行动指南。安全门控、危险操作确认、`--format json` 等已在根 skill 中定义,此处不重复。 +> 仅服务本 skill 已迁入的行动指南。安全门控、危险操作确认、`--format json` 等已在本 skill 的 `SKILL.md` 中定义,此处不重复。 ## 批量查询规范 @@ -9,7 +9,8 @@ | 1 | **并行查详情**:拿到多个 ID 后,用 `&` 合并到同一条 Shell 命令并行执行 + `wait`,**严禁逐条串行** | | 2 | **翻页**:分页接口须拉全直至无更多 | | 3 | **优先批量 API**:有批量接口则用批量;无则按 #1 并行 | -| 4 | **列表少轮次**:带条件搜索/列表 → 一次采全详情;**禁止**无新参数时重复同一 `list` / `search` | +| 4 | **群消息**:必须先 `chat search --query` 得 `openConversationId`,再 `chat message list --group --time "" --direction older`;多群同条命令并行 | +| 5 | **列表少轮次**:带条件搜索/列表 → 一次采全详情;**禁止**无新参数时重复同一 `list` / `search` | ## 多源并行采集(公共模式) @@ -26,10 +27,18 @@ | 字段 | 来源 | 传递给 | |------|------|--------| -| `nodeId` | `doc search` | `doc read/update/copy/move/rename --node` | -| `nodeId` | `doc list` 中的 folder 类型节点 / `doc folder create` | `doc list --folder`、`doc create --folder`、`doc upload --folder`、`doc copy/move --folder` | +| `taskUuid` | `minutes list` | `minutes get summary/info/batch --id(s)` | +| `userId` | `aisearch person` / `contact user search` / `contact dept list-members` | `contact user get --ids`、`todo --executors`、`calendar --users` | +| `deptId` | `contact dept search` | `contact dept list-members --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 --file-id`、`drive list/mkdir/upload --parent-id` | -| `workspaceId` | `wiki space search/list/create` | `doc list/search/create --workspace`、`wiki member * --workspace` | +| `dentryUuid` | `drive list` / `drive mkdir` | `drive info/download/copy/move/rename/delete --node`、`drive list/mkdir/upload/copy/move --folder` | +| `dentryId` | `drive info` 的数字字段 | 仅用于 `chat message send --dentry-id` | -**ID 边界硬约束**:遇到 `drive --parent-id`、`doc --folder`、`doc --node` 时,只能使用 `dentryUuid` / `nodeId` / 文档 URL。若当前上下文只有数字型 `dentryId`,必须先重新 `drive list` / `doc list` / `doc search` 获取正确 ID,不能把该数字直接代入后续命令。 +**ID 边界硬约束**:`dentryId` 通常是纯数字,只表示聊天文件消息需要的钉盘条目数字 ID;它不是父目录 ID。遇到 `drive --node/--folder`、`doc --node`、`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 41d428b9..48bfab14 100644 --- a/skills/multi/dingtalk-aitable/references/aitable-record-ops.md +++ b/skills/multi/dingtalk-aitable/references/aitable-record-ops.md @@ -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 c8929d70..890bea3e 100644 --- a/skills/multi/dingtalk-aitable/references/aitable.md +++ b/skills/multi/dingtalk-aitable/references/aitable.md @@ -7,10 +7,16 @@ | 资源 | 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**:每次执行 base list/search/create/get 操作后,从返回数据中提取 `baseId`,拼接为 `https://alidocs.dingtalk.com/i/nodes/{baseId}` 返回给用户。 -> 补充:如果 URL 不是来自 `aitable` 命令返回,而是用户直接贴的原始 `alidocs` URL,先按 [链接规范](../url-patterns.md#alidocs-url-类型探测流程) probe,确认是 `able` 后再按 AI 表格处理。 +> **操作后请返回文档 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 表格处理。 ## 命令索引表 @@ -19,11 +25,9 @@ | 命令 | 用途 | 必填参数 | 路由提醒 | |------|------|----------|----------| | `base list` | 列出最近访问的 Base | — | 仅返回最近访问过的,优先用 `base search` | -| `base search` | 按名称搜索 Base(别名 `aitable search`) | — | `--query` help 标必填但实际可省略:不传时返回最近访问的 Base 列表。`--keyword` 是 `--query` 的隐藏别名,同义 | +| `base search` | 按名称搜索 Base | `--query` | 关键词 ≥2 字符 | | `base get` | 获取 Base 信息(含 tables 列表) | `--base-id` | 用户给 URL 时提取末尾 ID | -| `base copy` | 复制整个 Base 到目标文件夹 | `--base-id` `--target-folder-id` | 默认全量复制;`--only-struct` 仅复制结构不含数据 | -| `base get-primary-doc-id` | 获取某记录的主键文档 ID | `--base-id` `--table-id` `--record-id` | 等价 `record primary-doc-get` 的取 ID 视角 | -| `base create` | 创建 Base | `--name` | 创建后直接用返回的 baseId | +| `base create` | 创建 Base | `--name` | 创建后直接用返回的 baseId;**默认新建的 base 自带一个空白「数据表」(含 3 行空记录)和一个空白仪表盘**,如需干净的空 base,传 `--template-id 1743` | | `base update` | 更新 Base 名称 | `--base-id` `--name` | — | | `base delete` | 删除 Base | `--base-id` | 不可逆 | @@ -31,10 +35,9 @@ | 命令 | 用途 | 必填参数 | 路由提醒 | |------|------|----------|----------| -| `table get` | 获取表结构(字段+视图目录) | `--base-id` | 不传 `--table-ids` 返回全部表 | -| `table list` | 获取数据表(`table get` 的别名) | `--base-id` | 与 `table get` 等价 | -| `table create` | 创建数据表 | `--base-id` `--name` | `--fields` 为 JSON 数组;**可传空数组 `[]`**(默认值即 `[]`),此时服务端自动补一个名为"标题"的 primaryDoc 首列;单次最多 15 个字段 | -| `table update` | 重命名表 | `--base-id` `--table-id` `--name` | — | +| `table 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) @@ -42,57 +45,83 @@ | 命令 | 用途 | 必填参数 | 路由提醒 | |------|------|----------|----------| | `field get` | 获取字段完整配置 | `--base-id` `--table-id` | 按需展开少量字段 | -| `field list` | 获取字段信息(`field get` 的别名) | `--base-id` `--table-id` | 与 `field get` 等价 | -| `field create` | 创建字段 | `--base-id` `--table-id` + (`--name --type` 或 `--fields`) | 支持单字段/批量模式 | +| `field 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` | 不可逆 | -| `field search-options` | 搜索单选/多选字段的选项 | `--base-id` `--table-id` `--field-id` | 仅 singleSelect/multipleSelect;`--keyword` 模糊过滤,不传返回全部 | + +#### 搜索字段选项 +``` +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) | 先 `table get` 拿 fieldId;`--all` 自动翻页;filters 结构见 reference;`--query`(隐藏别名 `--keyword`)全文搜索 | -| `record list` | 获取记录(`record query` 的别名) | [aitable-record-query.md](./aitable/aitable-record-query.md) | 与 `record query` 等价 | +| `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 query-empty` | 查询完全没填用户字段的空行 | — | `--base-id` `--table-id`;`--limit` 扫描预算 [1,100],`--cursor` 翻页 | | `record create` | 新增记录 | [aitable-record-create.md](./aitable/aitable-record-create.md) | cells key 必须是 fieldId 不是字段名;单次最多 100 条 | -| `record update` | 更新记录 | [aitable-record-update.md](./aitable/aitable-record-update.md) | 需先 query 拿 recordId;只传需改字段;**没有** `--record-id` `--cells` flag | -| `record batch-update` | 把同一份 cells 批量应用到多条记录 | [aitable-record-update.md](./aitable/aitable-record-update.md) | `--record-ids`(≤100)+ `--cells` 共享 patch | -| `record upsert` | 批量创建或更新(有 recordId 走更新,无则创建) | [aitable-record-upsert.md](./aitable/aitable-record-upsert.md) | `--records`/`--records-file`;单次最多 100 条 | +| `record 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 share-url` | 批量获取记录分享链接 | [aitable-record-share.md](./aitable/aitable-record-share.md) | `--record-ids`(逗号分隔,单次最多 20 条) | -| `record history-list` | 查询单条记录的变更历史 | [aitable-record-history.md](./aitable/aitable-record-history.md) | `--record-id` 单条;`--offset`/`--limit`(≤50) | -| `record primary-doc-get` | 查询记录的主键文档 nodeId | [aitable-primary-doc.md](./aitable/aitable-primary-doc.md) | 无文档时返回 `no record` 错误 | -| `record primary-doc-create` | 为记录创建主键文档 | [aitable-primary-doc.md](./aitable/aitable-primary-doc.md) | 幂等;`--field-id` 须 primaryDoc 类型 | +| `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 create` | 创建视图 | `--base-id` `--table-id` `--view-type` | 类型: Grid/Kanban/Gantt/Calendar/Gallery/FormDesigner | -| `view update` | 更新视图(**调整字段顺序的入口**) | `--base-id` `--table-id` `--view-id` | `visibleFieldIds` 重排字段顺序 | +| `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;用 `--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` | 不可删最后一个/锁定视图 | -> **"移动字段/调整字段顺序"** 在 AI 表格里没有 `field reorder` 命令,必须通过 `view update --config '{"visibleFieldIds":[...]}'` 完成。 +> **优先用 `view get ` / `view update ` 子命令**:每个属性独立命令,typed flag 友好,agent 不必拼 JSON。**`view update --config '{...}'` 仍可用**,适合一次性多属性更新或脚本场景。 -> **view update --config 支持的 key 白名单**(传入其他 key 会报错): -> - `visibleFieldIds` — 视图可见字段列表及顺序(首列字段必须保留在第一位) -> - `filter` — 筛选规则列表 -> - `sort` — 排序规则列表 -> - `group` — 分组规则列表 -> - `fieldWidths` — 列宽映射(仅 Grid 视图有效) -> -> 不支持 `formInfo`、`requiredFields`、`conditionalRules` 等 FormDesigner 高级配置,这些 key 会被服务端忽略。 +> **属性按 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` | 每条含 viewId/name;新建表单无 title 且 createdAt=0,改过后才有 | -| `form get` | 按 viewId 取单个表单详情 | `--base-id` `--table-id` `--view-id` | 客户端按 viewId 过滤,`data` 即该表单对象;viewId 不存在报 not found | -| `form create` | 创建表单视图 | `--base-id` `--table-id` `--name` | 等价 `view create --view-type FormDesigner` | +| `form 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` | 表单字段管理 | — | 详情见子文档 | @@ -105,14 +134,14 @@ | 命令 | 用途 | 必填参数 | 路由提醒 | |------|------|----------|----------| -| `workflow create` | 创建并发布工作流 | `--base-id` `--dsl` | `--dsl` 为完整 workflow-dsl/v1;非幂等,不自动重试 | -| `workflow update` | 更新并发布工作流 | `--base-id` `--workflow-id` `--dsl` | 全量替换,先 get 留底;检查 `data.valid/issues` | +| `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) @@ -120,6 +149,7 @@ |------|------| | `dashboard get/create/update/delete` | 仪表盘管理 | | `dashboard config-example` | 查看仪表盘配置模板 | +| `dashboard arrange` | 自动重排仪表盘图表布局(智能填满网格,避免空缺) | | `chart get/create/update/delete` | 图表管理 | | `chart widgets-example` | 查看图表 widgets 配置模板 | @@ -155,22 +185,150 @@ | `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 与父级关系。 +> 用于在 Base 的导航树中组织 table / dashboard / 表单视图 / 文档等节点(类似文件夹)。 +> 操作前建议先用 `section list-nodes` 拿到 nodeId / sectionId 与父级关系。 -| 命令 | 用途 | 必填参数 | 路由提醒 | -|------|------|----------|----------| -| `section list-nodes` | 列出 Base 下全部节点 | `--base-id` | 返回 `{nodeId, nodeType, parentSectionId, name?}`;是 move-node / reorder 的前置定位命令 | -| `section list-empty` | 列出空文件夹 | `--base-id` | 返回 `{sectionId, name, parentSectionId}`,用于清理导航树 | -| `section create` | 创建文件夹 | `--base-id` `--name` | `--parent-section-id` 不传/空串=根目录;`--index` 指定位置 | -| `section rename` | 重命名文件夹 | `--base-id` `--section-id` `--new-name` | — | -| `section delete` | 删除文件夹 | `--base-id` `--section-id` | 不可逆;建议先 list-empty 确认为空 | -| `section reorder` | 调整文件夹顺序 | `--base-id` `--section-id` `--target-index` | 仅当前父级下调序;跨父级用 move-node | -| `section move-node` | 移动节点(跨父级) | `--base-id` `--node-id` `--new-parent-section-id` | `--new-parent-section-id ""`=移到根;`--target-index` 调全局位置 | +#### 创建文件夹 +``` +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` ## 意图判断 @@ -184,7 +342,8 @@ 用户说"数据表/子表/table": - 查看 → `table get` - 创建 → `table create` -- 重命名 → `table update` +- 重命名 / 改备注 / 改行命名规则 → `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": @@ -195,12 +354,34 @@ 用户说"记录/行/数据/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)) -- 修改/更新 → `record update`(读 [aitable-record-update.md](./aitable/aitable-record-update.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)) -用户说"表单/问卷/form/收集信息" → 读 [aitable-form.md](./aitable/aitable-form.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) @@ -210,14 +391,35 @@ 用户说"查找引用/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=行 @@ -231,8 +433,8 @@ dws aitable base search --query "项目" --format json # 2. 获取 Base 信息 — 提取 tableId dws aitable base get --base-id --format json -# 3. 获取表结构 — 提取 fieldId -dws aitable table get --base-id --table-id --format json +# 3. 获取字段目录 — 提取 fieldId +dws aitable field get --base-id --table-id --format json # 4. 查询记录 dws aitable record query --base-id --table-id --format json @@ -248,8 +450,10 @@ dws aitable record create --base-id --table-id \ |------|-------------|------| | `base list/search` | `baseId` | 所有后续命令的 --base-id,拼接文档 URI | | `base create` | `baseId` | 后续命令 + 文档 URI | -| `base get` | `tables[].tableId` | --table-id | -| `table get` | `fields[].fieldId` | record 操作的 cells key, field get/update/delete | +| `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 | @@ -261,7 +465,7 @@ dws aitable record create --base-id --table-id \ 3. 传入 `--base-id` 参数 > 如果该 URL 来自 `dws aitable` 返回或已在当前链路 probe 过,可直接复用; -> 如果是用户直接提供的原始 `alidocs` URL,则先按 [链接规范](../url-patterns.md#alidocs-url-类型探测流程) probe,确认 `extension=able` 后再继续。 +> 如果是用户直接提供的原始 `alidocs` URL,则先按 [链接规范](url-patterns.md#alidocs-url-类型探测流程) probe,确认 `extension=able` 后再继续。 ## 注意事项 @@ -274,11 +478,11 @@ dws aitable record create --base-id --table-id \ | 脚本 | 场景 | |------|------| -| [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 表格记录 | +| [bulk_add_fields.py](../scripts/bulk_add_fields.py) | 批量添加字段 | +| [import_records.py](../scripts/import_records.py) | 从 JSON/CSV 批量导入记录 | +| [aitable_export_via_task.py](../scripts/aitable_export_via_task.py) | 文件导出(export_data 轮询 + 下载) | +| [upload_attachment.py](../scripts/upload_attachment.py) | 上传附件到 AI 表格记录 | ## 相关产品 -- [doc](./doc.md) — 富文本文档编辑,不是结构化数据表格 +- [doc](../../dingtalk-doc/references/doc.md) — 富文本文档编辑,不是结构化数据表格 diff --git a/skills/multi/dingtalk-aitable/references/aitable/aitable-attachment.md b/skills/multi/dingtalk-aitable/references/aitable/aitable-attachment.md index 48ae3306..45e9c2ae 100644 --- a/skills/multi/dingtalk-aitable/references/aitable/aitable-attachment.md +++ b/skills/multi/dingtalk-aitable/references/aitable/aitable-attachment.md @@ -1,6 +1,8 @@ # attachment — 附件上传 > **STOP — 不要使用钉盘 (drive) 上传!** 钉盘 fileId 无法写入 attachment 字段。必须使用以下流程。 +> +> **STOP — 严禁在 record create/update 的 cells 里直接传图片 URL!** 直传 `{"url":"https://..."}` 会导致服务端同步下载图片,批量写入时触发 TIMEOUT_ERROR。正确做法:先 `attachment upload` 获取 `fileToken`,再用 `{"fileToken":"ft_xxx"}` 写入。 ## 准备附件上传 @@ -38,10 +40,8 @@ dws aitable record create --base-id --table-id \ dws aitable attachment upload --base-id --file-name report.pdf --size 204800 --format json # → 返回 uploadUrl、fileToken -# 2. PUT 上传(Content-Type 必须与文件类型一致,不能留空) -# 留空或用 curl 默认的 application/x-www-form-urlencoded 都会被 OSS 拒为 403 +# 2. PUT 上传(Content-Type 必须是文件的具体 MIME type) curl -X PUT "" -H "Content-Type: application/pdf" --data-binary @report.pdf -# 例:.txt → text/plain,.png → image/png,.xlsx → application/vnd.openxmlformats-officedocument.spreadsheetml.sheet # 3. 写入记录 dws aitable record update --base-id --table-id \ diff --git a/skills/multi/dingtalk-aitable/references/aitable/aitable-best-practices.md b/skills/multi/dingtalk-aitable/references/aitable/aitable-best-practices.md index a851af83..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. 任务选路 @@ -26,7 +26,7 @@ | 全量拉取/统计 | `record query --all` | 不要手动循环 cursor | | 全量导出为文件 | `export data` | 不要 `--all` 拉全量再写文件 | | 批量写入 | `record create`(分批 100 条) | 不要一次传超过 100 条 | -| 附件上传 | `attachment upload` + `record update` | 不要在 cells 里伪造附件值 | +| 附件/图片上传 | `attachment upload` 获取 fileToken → `record create/update` 用 fileToken 写入 | **严禁直接传图片 URL 到附件字段**(服务端同步下载会超时) | | 文件级导入 | `import upload` + `import data` | 不要手动解析 xlsx 再逐条写入 | ## 4. 创建/修改后回读确认 @@ -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 9f747bf9..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)不可写入 @@ -86,11 +86,16 @@ {"fldDateId": "2026-03-15T09:00+08:00"} ``` -**读取**:RFC3339 字符串 +**读取**:RFC3339 字符串(带时区) ```json {"fldDateId": "2026-03-15T09:00:00+08:00"} ``` +**过滤**(`record query --filters`):日期字段**只能用日期专用操作符** `date_eq` / `before` / `after` / `not_before` / `not_after` / `exist` / `un_exist`,比较值用日期字符串(如 `"2026-03-15"`)。 +- ❌ 通用 `eq` / `ne` / `gt` / `gte` / `lt` / `lte` / `contain` 对日期字段无效,会静默返回 0 条; +- ❌ 不支持区间 `date_between` 与相对 `from_now`(CLI 会直接拒绝),范围查询用 `not_before` + `not_after` 组合。 +- 详见 [aitable-filter-sort.md](./aitable-filter-sort.md) §日期字段过滤。 + --- ### currency(货币) @@ -236,15 +241,14 @@ ### attachment(附件) -**写入**:对象数组,支持 `fileToken` 或 `url` 形式 +**写入**:对象数组,**必须使用 `fileToken`** ```json {"fldAttachId": [{"fileToken": "ft_xxx"}]} -{"fldAttachId": [{"url": "https://example.com/file.pdf"}]} ``` -> ⚠️ **必须先通过 [attachment upload 流程](./aitable-attachment.md) 获取 `fileToken`**。 -> URL 形式是 best-effort 异步转存,不保证立即可用。 +> ⚠️ **必须先通过 [attachment upload 流程](./aitable-attachment.md) 上传文件获取 `fileToken`,再将 `fileToken` 写入 cells。** +> ❌ **严禁直接传 `{"url": "https://..."}` 形式写入附件/图片字段** — 服务端会同步下载图片,10 条记录即触发 TIMEOUT_ERROR 超时。 > 写入会**整体覆盖**原附件列表,不是追加。 **读取**:对象数组(含下载链接、文件名、大小) @@ -335,7 +339,7 @@ |------|----------| | cells key 用字段名称 `"课程名称"` | 用 fieldId `"fldXXX"` | | progress 写入 `75` | 写入 `0.75`(范围 0~1) | -| attachment 直接传文件路径 | 必须先 upload 获取 fileToken | +| attachment 直接传文件路径或图片 URL | 必须先 `attachment upload` 获取 fileToken,再用 fileToken 写入(直传 URL 会超时) | | user 字段传用户名字符串 | 传对象数组 `[{"userId":"...", "corpId":"..."}]` | | group 字段用 `openConversationId` | 用 `cid` | | singleSelect 传 option id 字符串 | 传 name 字符串或 `{"id":"...", "name":"..."}` 对象 | diff --git a/skills/multi/dingtalk-aitable/references/aitable/aitable-dashboard-chart.md b/skills/multi/dingtalk-aitable/references/aitable/aitable-dashboard-chart.md index c6e4a1e7..94d576e2 100644 --- a/skills/multi/dingtalk-aitable/references/aitable/aitable-dashboard-chart.md +++ b/skills/multi/dingtalk-aitable/references/aitable/aitable-dashboard-chart.md @@ -15,10 +15,8 @@ dws aitable chart get --base-id --dashboard-id --chart- ## 要点 - `dashboard get` 返回的 `charts[].chartId` 可直接给 `chart get` 使用 -- `dashboard share get` 可能返回 `404`(`retryable:true`)——从未分享、甚至刚开启分享后立即查都可能 404;`data` 为 `{}`。按可重试错误处理,不要误判为参数拼错。它有时也会返回 `success + data`(如关闭分享之后),行为不稳定,别用它当"是否已分享"的唯一判据。 -- `chart share get` 稳定返回 `success + data`(含 `enabled` 等),可用于分享状态判断;从未分享时 `enabled=false`,不会 404。 -- ⚠️ **`dashboard share update` 开启 ORG 分享后返回的 `shareType` 是 `"[1]"`(未映射回 `ORG`,服务端已知问题)**;`chart share update --share-type ORG` 则正确返回 `shareType="ORG"`。判断 dashboard 是否 ORG 分享时对 `"[1]"` 做兼容。 -- `chart share update` / `dashboard share update` 的 `--enabled` 是字符串 flag:`--enabled false`(空格)和 `--enabled=false` 都能正确关闭分享。 +- `dashboard share get` 可能返回 `404`(资源不存在或未开通),需按可重试错误处理,不要误判为参数拼错 +- `chart share get` 可正常返回 `enabled/shareUrl`,用于分享状态判断 ## dashboard 子命令 @@ -27,25 +25,19 @@ dws aitable chart get --base-id --dashboard-id --chart- | `dashboard get` | 获取仪表盘详情(含 charts 列表) | `--base-id` `--dashboard-id` | — | | `dashboard create` | 创建仪表盘 | `--base-id` + (`--config` 或 `--name`) | `--name` 简化版创建空看板;`--config` 传完整 JSON | | `dashboard update` | 更新仪表盘 | `--base-id` `--dashboard-id` + (`--config` 或 `--name`) | `--name` 仅改名;`--config` 更新完整配置 | -| `dashboard arrange` | 自动重排仪表盘图表布局 | `--base-id` `--dashboard-id` | 让服务端重新排布 charts 位置 | | `dashboard delete` | 删除仪表盘 | `--base-id` `--dashboard-id` `--yes` | — | | `dashboard config-example` | 查看仪表盘配置模板 | 无 | 创建前先调此命令了解 config 结构 | -| `dashboard share get` | 获取仪表盘分享配置 | `--base-id` `--dashboard-id` | 可能 404,见上方要点 | -| `dashboard share update` | 更新仪表盘分享配置 | `--base-id` `--dashboard-id` `--enabled` | `--enabled true` 开启,`--share-type` 用 `ORG`(ORG 回显 `shareType="[1]"`);部分组织禁用了 `PUBLIC` 公开分享;若报 `Illegal argument`,改用 `ORG`(组织内分享)。`--enabled false` 关闭 | +| `dashboard arrange` | 自动重排图表布局 | `--base-id` `--dashboard-id` | 把图表按行铺满网格,避免某行只占半幅、留下大片空白;返回 `{totalColumns, layout, alignedChartCount}` | ## chart 子命令 | 命令 | 用途 | 必填参数 | |------|------|----------| | `chart get` | 获取图表详情 | `--base-id` `--dashboard-id` `--chart-id` | -| `chart create` | 创建图表 | `--base-id` `--dashboard-id` `--config` `--layout` | +| `chart create` | 创建图表 | `--base-id` `--dashboard-id` `--config` | | `chart update` | 更新图表配置 | `--base-id` `--dashboard-id` `--chart-id` `--config` | | `chart delete` | 删除图表 | `--base-id` `--dashboard-id` `--chart-id` `--yes` | | `chart widgets-example` | 查看图表 widgets 配置模板 | 无 | -| `chart share get` | 获取图表分享配置 | `--base-id` `--dashboard-id` `--chart-id` | -| `chart share update` | 更新图表分享配置(`--enabled true/false`,开启配 `--share-type`,用 `ORG`;部分组织禁用了 `PUBLIC` 公开分享;报 `Illegal argument` 时改用 `ORG`) | `--base-id` `--dashboard-id` `--chart-id` `--enabled` | - -> `chart create` 的 `--layout` 是**必填**(12 列网格布局,如 `{"x":0,"y":0,"w":6,"h":4}`);不传本地校验直接拒。`chart update` 的 `--config` 也**必填**——即便只想改 layout,也要带完整 config,否则服务端拒绝。 ## 配置获取流程 diff --git a/skills/multi/dingtalk-aitable/references/aitable/aitable-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.md b/skills/multi/dingtalk-aitable/references/aitable/aitable-export-import.md index 0d6e8317..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` | @@ -113,32 +111,3 @@ dws aitable import data --import-id --table-id --format js > **导入数据无法整体撤销**:文件一旦导入成功,数据即写入表中,没有"撤销导入"操作。如需清理导入的测试数据,只能手动通过 `record delete` 逐条或批量删除记录;如果是新建表模式导入的,可以直接 `table delete` 删除整张表。因此: > - 测试/验证场景建议导入到**独立的测试表或测试 Base**,用完后整体删除 > - 如果用户明确表示不想导入测试数据或要求先预览内容再决定,应先解析文件内容展示给用户确认,而非直接导入 - ---- - -## 导入数据:两条链路怎么选 - -用户给文件让导入 AI 表格时,路径选择决定成败: - -> ⚠️ **`import upload` 没有 `--file` flag**(传了会报 unknown flag)。它只申请上传凭证,必填 `--file-name` + `--file-size`,拿到 `uploadUrl` 后要**自己 curl PUT 上传文件**(Content-Type 留空,见上文三步流程),再 `import data`。想省事直接用 `aitable_import_via_task.py` 脚本,它把这三步包好了。 - -| 用户原话 | 链路 | 命令 / 脚本 | 行为 | -|---------|------|------------|------| -| "把这个 Excel 导入到 AI 表格"(无指定目标表) | **文件导入任务** | `python scripts/aitable_import_via_task.py `(推荐)或手动三步 `import upload --file-name x.xlsx --file-size <字节>` → curl PUT → `import data --import-id ` | 服务端解析文件,**新建数据表**,自动识别表头 | -| "把这个 Excel 导入新表 / 自动建表" | 同上 | 同上 | 同上 | -| "把这批记录追加到已有的『成员表』里" | **记录批量写入** | `python scripts/import_records.py ` | 走 `record create`,**写入已有 tableId**,需要字段名匹配 | -| "Excel 列名和表字段对不上但要追加" | 文件导入 + 追加 + 字段映射 | 三步导入后 `import data --import-id --table-id --field-mapping '{"目标":"源"}'` | 服务端按映射追加 | - -## 大表 / 长任务超时续等 - -单次等待窗口很短:`export data` 只有 `--timeout-ms`(默认且**上限 30000 = 30 秒**,没有 `--timeout-sec`,传了会报 unknown flag);`import data` 用 `--timeout`(秒,默认且推荐最大值 30)。窗口内没跑完,命令会返回 `taskId` / `importId`,用同命令带 ID 反复续等即可: - -```bash -# 续等导出:拿到 downloadUrl 后再 curl 下载(见下方警告) -dws aitable export data --base-id --task-id --timeout-ms 30000 - -# 续等导入 -dws aitable import data --import-id -``` - -> ⚠️ **`--output` 不会保存导出的 xlsx**:`--output` 是隐藏的全局 flag,作用是把命令的 **JSON 输出**写到文件,实测只生成一个 0 字节文件,不会下载导出内容。正确做法是从 `export data` 返回里取 `downloadUrl`,再 `curl -L "" -o out.xlsx` 下载。想省事直接用 `aitable_export_via_task.py` 脚本(它负责轮询 + 下载)。 diff --git a/skills/multi/dingtalk-aitable/references/aitable/aitable-field.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 8819a31f..5766f9f0 100644 --- a/skills/multi/dingtalk-aitable/references/aitable/aitable-filter-sort.md +++ b/skills/multi/dingtalk-aitable/references/aitable/aitable-filter-sort.md @@ -1,5 +1,7 @@ # filters & sort — 筛选排序语法参考 +> 视图(view)配置的 filter/sort/group **整体写入**请优先用 `view update filter` / `view update sort` / `view update group` 子命令,详见 [aitable-view-config.md](./aitable-view-config.md)。本文件聚焦于 `record query --filters` 与 view config filter 的语法和差异。 + ## filters 结构规范 ### 强制规则 @@ -7,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` 获取,不能直接用字段名称 ### 精简防呆模板 @@ -49,11 +51,34 @@ CLI 同时兼容两种子条件写法(推荐格式 A): | `exist` / `un_exist` | 有值 / 为空 | `["fieldId"]`(无需第二项) | | `any_of` / `none_of` / `all_of` | 包含任一 / 不包含任一 / 全包含(多选字段) | `["fieldId", "optionName"]` | | `date_eq` / `before` / `after` | 日期等于 / 早于 / 晚于 | `["fieldId", "dateStr"]` | -| `not_before` / `not_after` | 不早于 / 不晚于 | `["fieldId", "dateStr"]` | -| `from_now` | 从现在起 N 天内 | `["fieldId", "天数"]` | -| `date_between` | 日期区间 | `["fieldId", "[startTs, endTs]"]` | +| `not_before` / `not_after` | 不早于(≥) / 不晚于(≤) | `["fieldId", "2026-05-22"]` | > **操作符拼写必须严格匹配上表**,CLI 会在调用前校验,错误拼写会被拒绝。 +> +> **没有 `date_between`(区间)操作符**,也**不支持 `from_now`**——date 字段不支持区间/相对过滤,传了会被 CLI 拒绝。范围查询用 `not_before` + `not_after` 组合,见下方专节。 + +### 日期字段过滤(date / 创建时间 / 修改时间) + +日期类字段的过滤规则与其它字段**不同**,是线上反馈最高频的踩坑点。**经集成测试实测**确认的规则: + +1. **只能用日期专用操作符**:`date_eq` / `before` / `after` / `not_before` / `not_after` / `exist` / `un_exist`(与前端筛选 UI 的「等于 / 早于 / 晚于 / 早于或等于 / 晚于或等于 / 不为空 / 为空」一一对应)。 +2. **比较值用日期字符串**,如 `"2026-05-22"`(也接受 RFC3339 / 毫秒时间戳,内部统一转成毫秒比较)。读取返回的是带时区 RFC3339(如 `"2026-05-22T00:00:00+08:00"`)。 +3. **通用操作符 `eq` / `ne` / `gt` / `gte` / `lt` / `lte` / `contain` 对 date 字段无效**——无论传 ISO 字符串还是毫秒时间戳,都会**静默返回 0 条**。这是后端 date 字段的比较规则,不是 bug,CLI 也无法在本地拦截(不知道字段类型),务必用对操作符。 +4. **没有区间操作符 `date_between`**,也**不支持 `from_now`(相对天数)**——均会静默返回 0 条,CLI 已直接拒绝。范围查询用 `not_before`(≥起点)+ `not_after`(≤终点)两个条件 `and` 组合。 + +| 需求 | 操作符 | 示例 operands | +|------|--------|--------------| +| 等于某天 | `date_eq` | `["fldDate", "2026-05-22"]` | +| 早于 / 晚于(不含当天) | `before` / `after` | `["fldDate", "2026-05-22"]` | +| 不早于(≥) / 不晚于(≤) | `not_before` / `not_after` | `["fldDate", "2026-05-22"]` | +| 有值 / 为空 | `exist` / `un_exist` | `["fldDate"]` | + +**日期区间查询(替代 between)**——查 `2026-05-01 ~ 2026-05-31`(含端点): + +```bash +dws aitable record query --base-id X --table-id Y \ + --filters '{"operator":"and","operands":[{"operator":"not_before","operands":["fldDate","2026-05-01"]},{"operator":"not_after","operands":["fldDate","2026-05-31"]}]}' +``` ### 常见错误拼写(CLI 会自动提示纠正) @@ -104,3 +129,41 @@ dws aitable record query --base-id X --table-id Y \ ```bash --sort '[{"fieldId":"fldPriority","direction":"desc"},{"fieldId":"fldCreatedAt","direction":"asc"}]' ``` + +--- + +## view update --config 中的 filter / sort 格式 + +> **重要区分**:`record query --filters` 和 `view update --config` 中的 filter **格式不同**! + +| 场景 | filter 格式 | 说明 | +|------|-------------|------| +| `record query --filters` | **对象**:`{"operator":"and","operands":[...]}` | 直接传最外层逻辑对象 | +| `view update --config` 的 filter | **数组**:`[{"operator":"and","operands":[...]}]` | 外面多一层数组包裹 | +| `view update --config` 的 sort | **数组**:`[{"fieldId":"X","direction":"asc"}]` | 与 record query --sort 一致 | + +### 正确示例 + +```bash +# view update 设置筛选(filter 是数组) +dws aitable view update --base-id X --table-id Y --view-id Z \ + --config '{"filter":[{"operator":"and","operands":[{"operator":"eq","operands":["fldStatus","待处理"]}]}]}' + +# view update 设置排序(sort 是数组) +dws aitable view update --base-id X --table-id Y --view-id Z \ + --config '{"sort":[{"fieldId":"fldPriority","direction":"desc"}]}' + +# 同时设置 filter + sort + visibleFieldIds +dws aitable view update --base-id X --table-id Y --view-id Z \ + --config '{"filter":[{"operator":"and","operands":[{"operator":"eq","operands":["fldStatus","进行中"]}]}],"sort":[{"fieldId":"fldDate","direction":"asc"}],"visibleFieldIds":["fld1","fld2","fld3"]}' +``` + +### CLI 自动容错 + +CLI 会自动修正以下常见错误格式(不会报错,但建议直接使用正确格式): + +| 错误写法 | CLI 自动修正为 | +|----------|---------------| +| `"filter":{"operator":"and",...}` (对象) | `"filter":[{"operator":"and",...}]` (数组) | +| `"sort":{"fieldId":"X","direction":"asc"}` (对象) | `"sort":[{"fieldId":"X","direction":"asc"}]` (数组) | +| 子条件用 MCP 简写 `{"fieldId":"X","operator":"eq","value":"Y"}` | 自动转为 `{"operator":"eq","operands":["X","Y"]}` | diff --git a/skills/multi/dingtalk-aitable/references/aitable/aitable-form.md b/skills/multi/dingtalk-aitable/references/aitable/aitable-form.md index 2aaedba6..2b126d8f 100644 --- a/skills/multi/dingtalk-aitable/references/aitable/aitable-form.md +++ b/skills/multi/dingtalk-aitable/references/aitable/aitable-form.md @@ -50,8 +50,8 @@ dws aitable form share get --base-id BASE_ID --table-id TABLE_ID --view-id VIEW_ | 命令 | 用途 | 必填参数 | 说明 | |------|------|----------|------| -| `form list` | 列出表单视图 | `--base-id` `--table-id` | 每条返回 viewId/name;新建表单**无 title 且 createdAt=0**,改过(form update)后才出现 title 和真实 createdAt | -| `form get` | 按 viewId 取单个表单 | `--base-id` `--table-id` `--view-id` | 客户端按 viewId 过滤后 `data` **即该表单对象**(不是 formViews 数组);viewId 不存在返回 `form view ... not found` 错误 | +| `form list` | 列出表单视图 | `--base-id` `--table-id` | 返回 viewId/name/title/createdAt | +| `form get` | 按 viewId 取单个表单 | `--base-id` `--table-id` `--view-id` | 内部基于 list_form_views 过滤 | | `form create` | 创建表单视图 | `--base-id` `--table-id` `--name` | viewType=FormDesigner | | `form update` | 更新表单 | `--base-id` `--table-id` `--view-id` | `--title`/`--name`(等价)和 `--description` 至少传一项;同时传 title/name 时 title 优先 | | `form delete` | 删除表单 | `--base-id` `--table-id` `--view-id` `--yes` | 不可逆 | @@ -115,6 +115,6 @@ dws aitable form share update --base-id BASE_ID --table-id TABLE_ID --view-id VI ## 返回结构补充 -- `form list` 返回 `data.formViews[]`,**每条含** `viewId/name`(+ `createdAt`);**新建表单没有 `title` 字段且 `createdAt=0`**,只有在 `form update` 碰过之后才会出现 `title` 和真实 `createdAt`。`shareFormUuid` 不在此返回,请用 `form share get` 单独获取。 -- `form get` 的 `data` **就是命中的那一条表单对象**(如 `{viewId, name, createdAt, title?, shareFormUuid?}`),不是 `formViews` 数组。Agent 直接读 `data.viewId` / `data.name` 即可,**不要**再取 `data.formViews[0]`。服务端的 viewIds 过滤参数当前不生效,CLI 在客户端按 viewId 精确筛出单条;传了不存在的 viewId 会返回 `form view not found in table` 错误。 +- `form list` 返回 `data.formViews[]`,**每条仅含** `viewId/name/title/createdAt`;`shareFormUuid` 不在此返回,请用 `form share get` 单独获取。 +- `form get` 返回结构与 `form list` 完全一致(`data.formViews[]`),仅含一条记录(与请求 viewId 一致)。Agent 提取时仍走 `data.formViews[0]`。 - `form field list` 仅返回**未隐藏**的字段;`hidden=true` 的字段不在此返回,如需查看全部字段请用 `field get`。 diff --git a/skills/multi/dingtalk-aitable/references/aitable/aitable-primary-doc.md b/skills/multi/dingtalk-aitable/references/aitable/aitable-primary-doc.md index 8ed2198d..ff9bd738 100644 --- a/skills/multi/dingtalk-aitable/references/aitable/aitable-primary-doc.md +++ b/skills/multi/dingtalk-aitable/references/aitable/aitable-primary-doc.md @@ -17,9 +17,7 @@ dws aitable record primary-doc-get --base-id BASE_ID --table-id TABLE_ID --recor - `--table-id`(必填):Table ID - `--record-id`(必填):Record ID -**返回:** `data.nodeId` — 主键文档的 nodeId,可直接传给 `dws doc read/update` 的 `--node` 参数。 - -> **该记录尚未创建主键文档时**:不会返回 `nodeId: null`,而是 `status=error`、`data={}`,`error={code:"-1", message:"no record", type:"SYSTEM_ERROR", retryable:true}`。要判断"有没有主键文档",看是否命中这个 `no record` 错误,而不是判断 `nodeId` 是否为 null。需要文档时改用 `primary-doc-create`(幂等,已存在则直接返回)。 +**返回:** `data.nodeId` — 主键文档的 nodeId,可直接传给 `dws doc read/update` 的 `--node` 参数。若该记录尚未创建主键文档,`nodeId` 为 null。 ### 创建主键文档 @@ -30,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。 @@ -46,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 eb737228..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,21 +25,28 @@ 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) | ## 正确流程 ```bash # 先获取 fieldId -dws aitable table get --base-id --table-id --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-history.md b/skills/multi/dingtalk-aitable/references/aitable/aitable-record-history.md index 20154b98..f02d3d87 100644 --- a/skills/multi/dingtalk-aitable/references/aitable/aitable-record-history.md +++ b/skills/multi/dingtalk-aitable/references/aitable/aitable-record-history.md @@ -25,11 +25,12 @@ dws aitable record history-list \ "data": { "histories": [ { - "type": "row", // 变更类型,实测均为 "row"(行级变更) - "action": "updateRecords", // 操作动作:appendRow(新增行) / updateRecords(更新记录) - "newValue": "{\"fldX\":{\"dataType\":\"STRING\",\"value\":\"改后值\"}}", // 变更后的值(JSON 字符串,按 fieldId 组织) - "oldValue": "{\"fldX\":{\"dataType\":\"STRING\",\"value\":\"改前值\"}}", // 变更前的值(appendRow 新增行时无此字段) + "type": "field_change", // 变更类型 + "action": "update", // 操作动作: create / update / delete + "newValue": "{\"...\":\"...\"}", // 变更后的值(JSON 字符串) + "oldValue": "{\"...\":\"...\"}", // 变更前的值(JSON 字符串) "operateTime": 1733123456789, // 操作时间(毫秒级时间戳) + "typeChangedFields": "{...}", // 类型变更的字段信息(JSON 字符串) "version": 7 // 版本号(单调递增) } ] @@ -37,14 +38,14 @@ dws aitable record history-list \ } ``` -`newValue` / `oldValue` 是 JSON 字符串(不是 JSON 对象),需要二次 `JSON.parse` 才能拿到结构化值;解析后是 `{fieldId: {dataType, value}}` 结构。`appendRow`(新增行)事件没有 `oldValue`。实测返回里**没有** `typeChangedFields` 字段。 +`newValue` / `oldValue` / `typeChangedFields` 是 JSON 字符串(不是 JSON 对象),需要二次 `JSON.parse` 才能拿到结构化值。 ## 字段含义速查 | 字段 | 用途 | |------|------| -| `type` | 实测均为 `row`(行级变更),服务端未按 `record_create` / `field_change` 细分。 | -| `action` | 底层操作名:`appendRow`(新增行)/ `updateRecords`(更新记录)。按"动作"统计时以这两个值为准。 | +| `type` | 高层分类:`record_create` / `field_change` / `record_delete` 等。先按 type 过滤大类。 | +| `action` | 三态:`create` / `update` / `delete`。比 type 粗,但便于按"动作"统计。 | | `version` | 单调递增整数;同一 record 越新值越大。**用作"上一条 vs 这一条"的稳定排序键**。 | | `operateTime` | 毫秒时间戳;可格式化成可读时间。多条同 version 的极端场景用 operateTime 兜底排序。 | @@ -73,22 +74,20 @@ dws aitable record history-list --base-id BASE --table-id TBL --record-id REC -- ```bash dws aitable record history-list --base-id BASE --table-id TBL --record-id REC --limit 50 --format json \ - | jq '[.data.histories[] | select(.action == "updateRecords")][0].oldValue' + | jq '[.data.histories[] | select(.action == "update")][0].oldValue' ``` -### 4. 只看更新事件(排除新增行) +### 4. 找出删除事件(如果存在 delete history) ```bash dws aitable record history-list --base-id BASE --table-id TBL --record-id REC --format json \ - | jq '.data.histories[] | select(.action == "updateRecords") | {version, operateTime}' + | jq '.data.histories[] | select(.action == "delete") | {version, operateTime}' ``` -> 记录被 `record delete` 删除后,其历史不再返回(`histories` 为空数组),无法通过本命令回溯删除事件。 - ## 注意事项 - 一次只能查一条 record;如需批量审计多条记录请循环调用。 -- 仅返回**新增行**(appendRow)与**字段值变更**(updateRecords);记录被删除后其历史不再可查(返回空)。视图、字段定义、表结构变更不在此 history 里。 +- 仅返回**字段值变更**与**记录生命周期事件**;视图、字段定义、表结构变更不在此 history 里。 - 历史保留时长由 server 决定,过老的记录可能不再返回。 ## 与其他 record 命令的关系 diff --git a/skills/multi/dingtalk-aitable/references/aitable/aitable-record-query.md b/skills/multi/dingtalk-aitable/references/aitable/aitable-record-query.md index 8d04ab1b..20ed92da 100644 --- a/skills/multi/dingtalk-aitable/references/aitable/aitable-record-query.md +++ b/skills/multi/dingtalk-aitable/references/aitable/aitable-record-query.md @@ -72,3 +72,49 @@ dws aitable record query --base-id X --table-id Y --all --cursor "上次返回 - `--sort` 用 `"order":"desc"` → 必须用 `"direction":"desc"` - 不加 `--field-ids` 拉全字段 → 大表响应体积过大 - 全量拉取后在 context 里手动统计 → 应优先用 `--filters` 服务端过滤 + +## record query-empty — 找空行 + +`record query-empty` 是与 `record query` 平行的独立子命令,专门按表内顺序扫描出"完全没填用户字段"的空行。 + +```bash +dws aitable record query-empty --base-id BASE_ID --table-id TABLE_ID +``` + +| flag | 说明 | +|------|------| +| `--base-id` / `--base` | 必填 | +| `--table-id` | 必填 | +| `--limit` | 单次**扫描预算**(不是返回数);范围 [1, 100],默认 100 | +| `--cursor` | 分页游标。响应中 `nextCursor` 非空 → 用它翻页继续扫;nextCursor 为空(或不存在)→ 已扫完整表 | + +返回结构: + +```jsonc +{ "data": { "records": [...], "nextCursor": "..." } } +``` + +### 关键语义 + +1. **`--limit` 是扫描预算不是返回数**:可能扫了 100 条但全部非空,本页 `records: []`。 +2. **本页空 records ≠ 全表无空行**:必须看 `nextCursor`,nextCursor 还在就要继续翻。 +3. **空行定义**:除系统字段(recordId / 创建人 / 创建时间 / 修改人 / 修改时间)外,所有 cell 都是 null、空字符串、空集合或空 Map。一般是用户在 UI 上"插入空行"产生的。 + +### 典型用法 + +```bash +# 扫一页,看本页有没有空行 +dws aitable record query-empty --base-id BASE --table-id TBL + +# 翻页 +dws aitable record query-empty --base-id BASE --table-id TBL --cursor <上次的nextCursor> + +# 把整表扫完(手动循环 cursor) +NC="" +while : ; do + R=$(dws aitable record query-empty --base-id BASE --table-id TBL ${NC:+--cursor "$NC"} --format json) + echo "$R" | jq '.data.records[] | .recordId' + NC=$(echo "$R" | jq -r '.data.nextCursor // empty') + [ -z "$NC" ] && break +done +``` diff --git a/skills/multi/dingtalk-aitable/references/aitable/aitable-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-view-config.md b/skills/multi/dingtalk-aitable/references/aitable/aitable-view-config.md index d2423374..b133ac08 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,29 @@ > 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`。创建时通过 `--config` JSON 设置可见字段: + +```bash +# --config 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 会自动包装为数组 | + +描述使用独立的 `--desc '{"content":[]}'`;`description`、`fieldWidths`、`aggregate`、`kanbanCard`、`ganttTimebar`、`galleryCard` 等其他 key 会在调用服务端前被拒绝,并提示对应的 `view update` 子命令。 ## 读取:view get @@ -79,21 +101,21 @@ dws aitable view update timebar --view-id GANTT_ID --official-holiday=true ### view update aggregate(仅 Grid) -值是 `map[fieldId]→AggregateAction string`。**设置**聚合可用;**清除**聚合当前无效(见下方警告)。 +值是 `map[fieldId]→AggregateAction string`;传 null 清除某个字段聚合。 | flag | 类型 | 说明 | |------|------|------| | `--field-id` | string | 配合 `--action` 设置**单字段**聚合 | | `--action` | string | `SUM`/`AVG`/`MAX`/`MIN`/`MEDIAN`/`RANGE`/`TOTAL`/`DISTINCT`/`EXIST`/`UN_EXIST`/`CHECKED`/`EARLIEST_DATE` 等(按字段类型可用) | -| `--clear-field-id` | string (CSV) | 一/多个字段 ID,本意是清除其聚合,但**当前服务端不支持清除,静默无效**(见下方警告) | +| `--clear-field-id` | string (CSV) | 一/多个字段 ID,清除其聚合 | | `--json` | JSON | 完整 aggregate map | ```bash dws aitable view update aggregate --view-id GRID_ID --field-id fldX --action SUM +dws aitable view update aggregate --view-id GRID_ID --clear-field-id fldA,fldB +dws aitable view update aggregate --view-id GRID_ID --json '{"fldX":"AVG","fldY":null}' ``` -> ⚠️ **当前无法清除已设置的聚合(服务端限制)**:`--clear-field-id fldA,fldB` 与 `--json '{"fldX":null}'` 两种清除写法都返回 `success`,但用 `view get aggregate` 复核会发现聚合**原样不动**——是静默无效,不是真的清掉。这是服务端没有清除语义所致,直至服务端修复前不要依赖它。改聚合方式可行(重新 `--action` 覆盖成别的),只是无法回到"无聚合"。 - ### view update field-widths(仅 Grid) | flag | 类型 | @@ -108,11 +130,9 @@ dws aitable view update field-widths --view-id GRID_ID --json '{"fldA":120,"fldB ### view update visible-fields(通用) -整组替换可见字段列表与顺序,同时兼作**隐藏/显示**入口。首列字段(primaryDoc)必须保留在数组第一位。 +整组替换可见字段列表与顺序。首列字段(primaryDoc)必须保留在数组第一位。 -> **传入的列表既定顺序又定可见性**:传一个比当前 columns 短的列表,缺失的字段会被真正隐藏(该字段在 `view list` 的 `custom.hiddenFields` 里变 `true`);再传回全量列表即可解除隐藏(`hiddenFields` 变 `false`)。 -> -> ⚠️ **查隐藏状态别看这里**:`view get visible-fields` 返回的数组**包含已隐藏字段**(列的完整顺序),看不出谁被隐藏。要确认隐藏状态,读 `view get`(view list)里该视图的 `custom.hiddenFields`(`{fieldId: true|false}`)。 +> ⚠️ 注意:服务端**只接受 reorder,不接受真"隐藏字段"**——如果传入的列表比当前 columns 短,缺失的字段不会被隐藏。需要真正隐藏字段请到 AI 表格 Web UI。 | flag | 类型 | |------|------| @@ -120,9 +140,7 @@ dws aitable view update field-widths --view-id GRID_ID --json '{"fldA":120,"fldB | `--json` | string 数组 JSON(与 `--field-ids` 同传时 `--json` 优先) | ```bash -# 只保留首列和 fldA,其余字段被隐藏 -dws aitable view update visible-fields --view-id VIEW_ID --field-ids fldPrimary,fldA -# 传回全量列表解除隐藏 +dws aitable view update visible-fields --view-id VIEW_ID --field-ids fldPrimary,fldA,fldB dws aitable view update visible-fields --view-id VIEW_ID --json '["fldPrimary","fldA","fldB"]' ``` diff --git a/skills/multi/dingtalk-aitable/references/aitable/aitable-view-extras.md b/skills/multi/dingtalk-aitable/references/aitable/aitable-view-extras.md index c9174158..23db8059 100644 --- a/skills/multi/dingtalk-aitable/references/aitable/aitable-view-extras.md +++ b/skills/multi/dingtalk-aitable/references/aitable/aitable-view-extras.md @@ -54,7 +54,7 @@ dws aitable view update frozen-cols --view-id VIEW_ID --count 0 # 查询当前冻结列数 dws aitable view get frozen-cols --view-id VIEW_ID --format json -# → {"data": {..., "count": 1}} 未显式设置时整个 count 键缺失(不是返回 null) +# → {"data": {..., "count": 1}} count 为 null 表示视图未显式设置 ``` `--count` 必须 ≥ 0;负数会被拒绝。 @@ -69,7 +69,7 @@ dws aitable view update row-height --view-id VIEW_ID --cell-height 56 # 查询当前行高 dws aitable view get row-height --view-id VIEW_ID --format json -# → {"data": {..., "cellHeight": 56}} 未显式设置时整个 cellHeight 键缺失(不是返回 null;前端按 32 渲染) +# → {"data": {..., "cellHeight": 56}} cellHeight 为 null 表示视图未显式设置(前端按 32 渲染) ``` ## 数据高亮规则(条件填色,仅 Grid) diff --git a/skills/multi/dingtalk-aitable/references/aitable/aitable-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 4e393732..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` 后识别并跳过: - 创建时间 / 修改时间 (系统自动) - 创建人 / 修改人 (系统自动) - 自动编号 @@ -83,7 +83,7 @@ dws aitable table create --base-id --name "产品图片" \ ## 记录写入格式(record create / record update) -> 各字段类型的完整写入/读取格式规范请参考:[aitable-cell-value.md](./products/aitable/aitable-cell-value.md) +> 各字段类型的完整写入/读取格式规范请参考:[aitable-cell-value.md](./aitable/aitable-cell-value.md) > > 该文件是 cellValue 格式的 **source of truth**,包含所有字段类型的详细示例和注意事项。 diff --git a/skills/multi/dingtalk-aitable/references/intent-guide.md b/skills/multi/dingtalk-aitable/references/intent-guide.md new file mode 100644 index 00000000..a1428eaf --- /dev/null +++ b/skills/multi/dingtalk-aitable/references/intent-guide.md @@ -0,0 +1,10 @@ +# aitable 局部意图消歧 + +本文件从单 Skill `intent-guide.md` 拆分而来,仅保留与本产品相关的跨产品消歧规则。 + +| 用户说... | 真实意图 | 应该用 | 不要用 | 理由 | +|---|---|---|---|---| +| "帮我建一个项目跟踪表" | 创建数据表格 | `aitable` | `doc` / `sheet` | 涉及结构化数据/行列操作,不是富文本文档或电子表格 | +| "帮我写个项目周报" | 创建钉钉文档 | `doc` | `aitable` | 富文本内容创作,不是数据表 | +| "创建一个电子表格" | 创建表格文档 | `sheet` | `aitable` | Excel 式表格/单元格操作,不是多维表记录 | +| "帮我读一下表格 A1:D10 的数据" | 读取单元格数据 | `sheet` | `aitable` | 按单元格区域读写,不是按记录查询 | diff --git a/skills/multi/dingtalk-aitable/references/url-patterns.md b/skills/multi/dingtalk-aitable/references/url-patterns.md new file mode 100644 index 00000000..f25880ef --- /dev/null +++ b/skills/multi/dingtalk-aitable/references/url-patterns.md @@ -0,0 +1,144 @@ +# URL 格式与处理规范 + +## 路由第 0 步:意图直达(优先级高于 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-类型探测流程) 执行 +3. URL 路径含 `/spreadsheetv2/` → **电子表格直链**,直接路由到 `sheet`,将完整 URL 原样传给 `--node` 参数 +4. URL 路径含 `/document/edit` 或 `/document/preview` 且 query 参数包含 `dentryKey` → **文档链接**,直接路由到 `doc`,将完整 URL 原样传给 `--node` 参数(URL 中不一定有 `type=d`,只需匹配路径和 `dentryKey` 参数即可) +5. 其他 alidocs URL 格式 → 告知用户当前暂不支持该链接格式 + +--- + +## 已知 URL 格式 + +需要自行拼接链接时,只能使用以下模板: + +| 产品 | 用途 | URL 格式 | ID 来源 | +|------|------|----------|---------| +| `aitable` | AI表格 Base 链接 | `https://alidocs.dingtalk.com/i/nodes/{baseId}` | `base list/search/create/get` 返回的 `baseId` | +| `aitable` | AI表格指定数据表链接 | `https://alidocs.dingtalk.com/i/nodes/{baseId}?iframeQuery=sheetId%3D{tableId}` | `baseId` + `table create/get` 或 `base get` 返回的 `tableId` | +| `aitable` | AI表格指定数据表+视图链接 | `https://alidocs.dingtalk.com/i/nodes/{baseId}?iframeQuery=sheetId%3D{tableId}%26viewId%3D{viewId}` | `baseId` + `tableId` + `view create/get` 返回的 `viewId` | +| `aitable` | AI表格模板预览 | `https://docs.dingtalk.com/table/template/{templateId}` | `template search` 返回的 `templateId` | +| `doc` | 文档链接 | `https://alidocs.dingtalk.com/i/nodes/{dentryUuid}` | `doc` 命令返回的 `dentryUuid` | +| `sheet` | 电子表格链接 | `https://alidocs.dingtalk.com/i/nodes/{dentryUuid}` | `sheet create` 返回的 `dentryUuid` | +| `sheet` | 电子表格直链 | `https://alidocs.dingtalk.com/spreadsheetv2/{key}/...?dentryKey={key}&type=s` | 用户提供的完整 URL,直接传给 `--node` | +| `doc` | 文档链接(edit/preview) | `https://alidocs.dingtalk.com/document/{edit\|preview}?...&dentryKey={key}` | 用户提供的完整 URL,直接传给 `--node` | +| `minutes` | 听记链接 | `https://shanji.dingtalk.com/app/transcribes/{taskUuid}` | `list mine/shared` 返回的 `taskUuid` | + +不在此表中的产品,禁止自行拼接 URL。命令返回中包含完整链接时直接使用,否则告知用户无法提供。 + +## 分享短链处理 + +`alidocs.dingtalk.com/i/p/{shortKey}` 是钉钉文档的**对外分享短链**,`dws doc` 命令无法解析此格式。 + +### 识别规则 + +URL 路径中包含 `/i/p/` 即为分享短链(无论后面是否还有子路径),例如: +- `https://alidocs.dingtalk.com/i/p/Y7kmbokZp3pgGLq2` +- `https://alidocs.dingtalk.com/i/p/Y7kmbokZp3pgGLq2/docs/AY39rGpMPmeVNpXZevZm8OZkXKnaoNQ7` +- `https://alidocs.dingtalk.com/i/p/AbCdEfGh1234` +- `https://alidocs.dingtalk.com/i/p/AbCdEfGh1234/sheets/XYZ789` + +> **关键**:只要 URL 中出现 `/i/p/`,无论后面跟什么子路径(`/docs/...`、`/sheets/...` 等),都属于分享短链,一律禁止调用 `dws doc`。 + +### 处理方式 + +**不要调用 `dws doc` 任何子命令**(包括 `doc info`、`doc read` 等),`dws` 无法解析此格式。 + +- **需要获取文档内容时**:使用 `read_url` 工具直接读取该链接 +- **其他操作(如移动、复制、权限管理等)**:告知用户此链接为分享短链,无法直接执行复制、移动、权限管理等操作。如需保存该文档内容,建议用户在钉钉客户端中打开该页面,手动复制文本内容,然后可通过 `dws doc create` 创建一篇新文档并将内容写入 + +``` +# 需要读取文档内容时(无论 /i/p/ 后面有没有子路径,都用 read_url) +read_url("https://alidocs.dingtalk.com/i/p/Y7kmbokZp3pgGLq2") +read_url("https://alidocs.dingtalk.com/i/p/Y7kmbokZp3pgGLq2/docs/AY39rGpMPmeVNpXZevZm8OZkXKnaoNQ7") + +# 禁止(以下全部会失败,dws 无法解析任何含 /i/p/ 的 URL) +dws doc info --node "https://alidocs.dingtalk.com/i/p/Y7kmbokZp3pgGLq2" --format json +dws doc read --node "https://alidocs.dingtalk.com/i/p/Y7kmbokZp3pgGLq2/docs/AY39rGpMPmeVNpXZevZm8OZkXKnaoNQ7" --format json +``` + +### 当 `read_url` 返回内容不完整时 + +钉钉文档分享页是动态渲染的,`read_url` 可能只能获取到页面标题等有限信息,无法获取文档正文。此时**禁止猜测原因**(如"权限不足""文档为空""文档已删除"等),**禁止建议用户"提供 `/i/nodes/` 格式链接"**(分享短链和节点链接是不同体系,普通用户无法自行转换)。应直接告知用户: + +> 这个链接是钉钉文档的分享短链,由于页面是动态渲染的,我无法通过该链接直接获取文档的完整正文内容。 +> +> 你可以: +> 1. 在钉钉客户端中打开该文档,将正文内容复制粘贴给我 +> 2. 如果文档已保存在你的文档空间中,可以告诉我文档名称,我通过 `dws drive search` 搜索后再读取 + +--- + +## alidocs URL 类型探测流程 + +`alidocs.dingtalk.com/i/nodes/{id}` 是钉钉文档空间的统一 URL,可能指向**文档、电子表格、多维表、文件、文件夹**等不同类型。**禁止仅凭 URL 就假定为文档**,必须先探测类型再路由到正确的产品。 + +### 探测步骤 + +``` +Step 1 → dws drive info --node "" --format json +Step 2 → 从返回中提取 extension、nodeType 字段 +Step 3 → 按下方路由规则映射到对应产品 +``` + +> 路由依据是 `extension`,不是 `contentType`。`drive info` 检测到 +> `adoc` / `axls` / `able` 时会自动补充在线文档信息。 + +### 路由映射表 + +| 条件 | 路由到产品 | 后续操作 | +|------|-----------|---------| +| `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` 列出子节点 | +| 以上均不匹配 | — | 告知用户当前暂不支持该类型 | + +> axls vs xlsx 关键区分: +> - `axls`(钉钉在线电子表格,`contentType=ALIDOC`)→ 走 `sheet` 产品线(读/写/筛选/导出等服务端原子操作) +> - `xlsx` / `xls` / `xlsm` / `csv`(上传到文档空间的本地表格文件,`contentType=DOCUMENT`)→ 必须走 `dws drive download` 下载到本地后再解析处理,严禁错误路由到 `sheet` 产品线(sheet 命令只支持在线表格,调用 xlsx 节点会直接报错) +> - 用户想把在线表格导出为 xlsx 文件 → 用 `dws sheet export`(输入是 `axls`,输出是 xlsx,这是 axls → xlsx 的格式转换,不属于 xlsx 读取场景) + +### 示例 + +```bash +# 用户传入: https://alidocs.dingtalk.com/i/nodes/abc123 +dws drive info --node "https://alidocs.dingtalk.com/i/nodes/abc123" --format json + +# 返回 extension=axls → 在线电子表格,路由到 sheet +dws sheet list --node "https://alidocs.dingtalk.com/i/nodes/abc123" --format json + +# 返回 extension=xlsx/xls/csv → 本地表格文件,必须下载处理(禁止走 sheet) +dws drive download --node "https://alidocs.dingtalk.com/i/nodes/xlsx456" --output --format json + +# 返回 nodeType=file → 普通文件,下载 +dws drive download --node "https://alidocs.dingtalk.com/i/nodes/def456" --output --format json + +# 返回 nodeType=folder → 文件夹,列出子节点 +dws drive list --workspace --format json +``` + +### 何时可跳过探测 + +当用户指令中已明确指定产品(如"帮我读这个文档"、"看下这个表格的数据"),可结合用户意图**跳过探测**直接路由。仅在以下情况**必须执行探测**: +- 用户只粘贴 URL,无其他上下文 +- 用户指令与 URL 实际类型可能不一致(如说"文档"但实际是表格) diff --git a/skills/multi/dingtalk-aitable/scripts/aitable_export_via_task.py b/skills/multi/dingtalk-aitable/scripts/aitable_export_via_task.py index abee2023..caa66f80 100644 --- a/skills/multi/dingtalk-aitable/scripts/aitable_export_via_task.py +++ b/skills/multi/dingtalk-aitable/scripts/aitable_export_via_task.py @@ -90,10 +90,8 @@ def build_start_args(args: argparse.Namespace) -> list[str]: args.scope, "--format", args.export_format, - # CLI 的 --timeout-ms 是单次等待上限(毫秒,最大 30000);脚本自身的 - # --timeout-sec 用于整体轮询/子进程超时,二者语义不同,不能混用。 "--timeout-ms", - "30000", + str(args.timeout_ms), ] if args.table_id: cmd.extend(["--table-id", args.table_id]) @@ -109,8 +107,9 @@ def main() -> None: parser.add_argument("--table-id", help="scope=table/view 时必填") parser.add_argument("--view-id", help="scope=view 时必填") parser.add_argument("--export-format", default="excel", choices=sorted(ALLOWED_FORMATS), help="导出格式") - parser.add_argument("--timeout-sec", type=int, default=300, help="CLI 内置轮询整体超时(秒),默认 300(5 分钟)") - parser.add_argument("--max-polls", type=int, default=10, help="(兼容旧参数,开源 CLI 内置轮询不再使用)") + parser.add_argument("--timeout-ms", type=int, default=1000, help="单次等待毫秒数,默认 1000") + parser.add_argument("--poll-timeout-ms", type=int, default=3000, help="轮询等待毫秒数,默认 3000") + parser.add_argument("--max-polls", type=int, default=10, help="最大轮询次数,默认 10") parser.add_argument("--output", help="本地保存路径(不传则按 fileName 保存到当前目录)") parser.add_argument("--dws", default="dws", help="dws 可执行文件路径,默认 dws") parser.add_argument("--no-download", action="store_true", help="仅返回 downloadUrl,不下载文件") @@ -155,9 +154,9 @@ def main() -> None: "--task-id", task_id, "--timeout-ms", - "30000", + str(args.poll_timeout_ms), ], - timeout_sec=max(120, args.timeout_sec + 60), + timeout_sec=max(120, int(args.poll_timeout_ms / 1000) + 60), ) if rc2 != 0: fail(f"export_data 轮询失败: {err2 or out2}", rc2) diff --git a/skills/multi/dingtalk-aitable/scripts/aitable_import_via_task.py b/skills/multi/dingtalk-aitable/scripts/aitable_import_via_task.py index 566be970..fea1ad5e 100644 --- a/skills/multi/dingtalk-aitable/scripts/aitable_import_via_task.py +++ b/skills/multi/dingtalk-aitable/scripts/aitable_import_via_task.py @@ -77,7 +77,7 @@ def main() -> None: parser = argparse.ArgumentParser(description="通过文件导入任务导入 AI 表格") parser.add_argument("base_id", help="目标 AI 表格 baseId") parser.add_argument("file_path", help="待导入文件路径(.csv/.xlsx/.xls)") - parser.add_argument("--timeout-sec", type=int, default=300, help="CLI 内置轮询整体超时(秒),默认 300(5 分钟)") + parser.add_argument("--timeout", type=int, default=30, help="import_data 等待秒数,默认 30") parser.add_argument("--dws", default="dws", help="dws 可执行文件路径,默认 dws") args = parser.parse_args() @@ -140,14 +140,12 @@ def main() -> None: "data", "--import-id", import_id, - # import data 的 --timeout 单位是秒、最大 30;脚本的 --timeout-sec - # 是整体子进程预算,不能直接透传,这里用 CLI 允许的最大值。 "--timeout", - "30", + str(args.timeout), "--format", "json", ], - timeout_sec=max(120, args.timeout_sec + 30), + timeout_sec=max(120, args.timeout + 30), ) if rc2 != 0: fail(f"import_data 调用失败: {err2 or out2}", rc2) diff --git a/skills/multi/dingtalk-aitable/scripts/bulk_add_fields.py b/skills/multi/dingtalk-aitable/scripts/bulk_add_fields.py index 78c5bdfc..3ba88313 100644 --- a/skills/multi/dingtalk-aitable/scripts/bulk_add_fields.py +++ b/skills/multi/dingtalk-aitable/scripts/bulk_add_fields.py @@ -29,14 +29,14 @@ JsonData = Union[List[Any], Dict[str, Any]] MAX_FILE_SIZE = 10 * 1024 * 1024 ALLOWED_FILE_EXTENSIONS = ['.json'] -RESOURCE_ID_PATTERN = re.compile(r'^[A-Za-z0-9_-]{6,128}$') +RESOURCE_ID_PATTERN = re.compile(r'^[A-Za-z0-9_-]{8,128}$') ALLOWED_FIELD_TYPES = { 'text', 'number', 'singleSelect', 'multipleSelect', 'date', 'currency', 'user', 'department', 'group', 'progress', 'rating', 'checkbox', 'attachment', 'url', 'richText', 'telephone', 'email', 'idCard', - 'barcode', 'geolocation', 'primaryDoc', 'formula', 'unidirectionalLink', - 'bidirectionalLink', 'creator', 'lastModifier', 'createdTime', - 'lastModifiedTime', + 'barcode', 'geolocation', 'address', 'primaryDoc', 'formula', + 'unidirectionalLink', 'bidirectionalLink', 'lookup', 'filterUp', + 'creator', 'lastModifier', 'createdTime', 'lastModifiedTime', } FIELD_TYPE_ALIASES = { 'phone': 'telephone', @@ -121,9 +121,32 @@ def validate_field_config(field: Dict[str, Any]) -> Tuple[bool, str]: ) if field_type in {'unidirectionalLink', 'bidirectionalLink'}: - linked_sheet_id = (config or {}).get('linkedSheetId') - if not linked_sheet_id or not validate_resource_id(linked_sheet_id): - return False, '关联字段必须提供合法的 config.linkedSheetId' + linked_table_id = (config or {}).get('linkedTableId') + if not linked_table_id or not validate_resource_id(linked_table_id): + return False, ( + '关联字段必须提供合法的 config.linkedTableId(目标 Table ID)' + ) + + if field_type == 'lookup': + cfg = config or {} + if not cfg.get('associateField'): + return False, 'lookup 必须提供 config.associateField(本表关联字段的 fieldId)' + if not cfg.get('valuesField'): + return False, 'lookup 必须提供 config.valuesField(关联目标表中要取值的字段 fieldId)' + if not cfg.get('aggregator'): + return False, 'lookup 必须提供 config.aggregator(SUM/AVERAGE/COUNT/MAX/MIN/CONCATENATE)' + + if field_type == 'filterUp': + cfg = config or {} + if not cfg.get('targetSheet'): + return False, 'filterUp 必须提供 config.targetSheet(目标 Table ID)' + filters = cfg.get('filters') + if not filters or not isinstance(filters, list): + return False, 'filterUp 必须提供 config.filters(至少一条筛选规则)' + if not cfg.get('valuesField'): + return False, 'filterUp 必须提供 config.valuesField(目标表中要取值的字段 fieldId)' + if not cfg.get('aggregator'): + return False, 'filterUp 必须提供 config.aggregator(SUM/AVERAGE/COUNT/MAX/MIN/CONCATENATE)' return True, '' diff --git a/skills/multi/dingtalk-aitable/scripts/import_records.py b/skills/multi/dingtalk-aitable/scripts/import_records.py index a76f78d2..a275806e 100644 --- a/skills/multi/dingtalk-aitable/scripts/import_records.py +++ b/skills/multi/dingtalk-aitable/scripts/import_records.py @@ -11,6 +11,13 @@ - JSON 支持两种格式: 1. [{"cells": {"fldxxx": "value"}}, ...] 2. [{"fldxxx": "value"}, ...] # 会自动包装成 cells + +⚠️ CSV 自动类型转换风险: + CSV 读入的所有 cell 都是 string,本脚本会尝试自动识别 'true'/'false'/数字 + 并转成对应类型(避免 text 字段塞入纯文本数字)。但当 fieldId 对应的字段是 + text / telephone / idCard / barcode 这类"字符串形数字"字段时,自动转 int / float + 会让 server 拒绝(字段类型不匹配)。这种情况建议改用 JSON 格式(自己显式控制类型), + 或在 CSV 写入前给字段值前缀加引号 / 改为非纯数字。 """ import sys @@ -28,7 +35,7 @@ RecordDict = Dict[str, str] MAX_FILE_SIZE = 50 * 1024 * 1024 ALLOWED_CSV_EXTENSIONS = ['.csv'] ALLOWED_JSON_EXTENSIONS = ['.json'] -RESOURCE_ID_PATTERN = re.compile(r'^[A-Za-z0-9_-]{6,128}$') +RESOURCE_ID_PATTERN = re.compile(r'^[A-Za-z0-9_-]{8,128}$') MAX_RECORDS_PER_BATCH = 100 DEFAULT_BATCH_SIZE = 50 @@ -131,9 +138,7 @@ def normalize_record(record: Dict[str, Any]) -> Dict[str, Any]: return {'cells': normalized} -def validate_record( - record: Dict[str, Any], headers: List[str] -) -> Tuple[bool, str]: +def validate_record(record: Dict[str, Any]) -> Tuple[bool, str]: if not isinstance(record, dict): return False, '记录必须是对象' normalized = normalize_record(record) @@ -238,7 +243,7 @@ def import_from_json( return False for i, record in enumerate(records): - valid, error = validate_record(record, []) + valid, error = validate_record(record) if not valid: print(f"错误:记录 #{i+1} 格式无效:{error}") return False diff --git a/skills/multi/dingtalk-attendance/SKILL.md b/skills/multi/dingtalk-attendance/SKILL.md deleted file mode 100644 index 9004028e..00000000 --- a/skills/multi/dingtalk-attendance/SKILL.md +++ /dev/null @@ -1,79 +0,0 @@ ---- -name: dingtalk-attendance -description: 钉钉考勤。Use when 用户说 考勤/打卡记录/查打卡/查班次/考勤汇总/考勤规则/出勤情况/请假加班补卡/排班/考勤组/假期余额。命令前缀:dws attendance。支持查询与写操作(班次、排班、考勤组、个人规则设置、假期规则/余额等),写操作需二次确认后再加 --yes。 -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 反馈。 - -> **PREREQUISITE:** Read the `dws-shared` skill first for auth, global flags, product routing, URL preflight, error codes, and safety rules. The `dws` binary must be on PATH. - - - -> ⚠️ **命令可用性以当前 dws 二进制为准**。服务发现已下线,本文档随内置 skill 发布;如果 `dws --help` 不存在,说明当前版本未暴露该命令。若命令存在但调用失败,请按错误中的 endpoint 或 tool 提示确认静态端点目录和后端工具注册。实际调用前可用 `dws --help` 或 `--dry-run` 验证。 - - -> 命令参考:[attendance.md](references/attendance.md)。 - - -## Shortcuts(无专用脚本/recipe 时优先) - -以下 shortcut 同时进入公开 catalog 与 Runtime Schema。先按本 skill 的意图表、脚本和 recipe 路由:存在精确覆盖该场景的专用脚本/recipe 时按其执行;否则用户意图命中时,shortcut 优先于手写原子命令。用 leaf Schema(例如 `dws schema --cli-path "attendance +" --format json`)读取 Agent 选择、参数、约束、风险和确认语义;用 `dws shortcut list --service attendance --format json` 批量发现;最后以 `dws attendance --help` 核对当前 Cobra flags。 - -| 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 | 查我本月的考勤打卡记录(打卡流水,自动解析当前用户) | - - -## 意图表 - -| 用户说 | 命令 | -|--------|------| -| "查我 / 某人 某天的打卡" | `dws attendance record get --user --date ` | -| "查考勤组 / 考勤规则 / 打卡范围" | `dws attendance rules --date ` | -| "查班次 / 排班 / 谁今天上什么班" | `dws attendance shift list --users --start --end ` | -| "考勤统计 / 周月汇总 / 出勤天数" | `dws attendance summary --user --date --stats-type week\|month` | -| "请假 / 加班 / 补卡 / 出差 记录或提交链接" | `dws attendance approve list` / `dws attendance approve templates --type <类型>` | -| "班次 / 补卡规则 / 加班规则 / 考勤组 查询与修改" | `class` / `adjustment` / `overtime` / `group` 子命令 | -| "排班 / 假期规则 / 假期余额 / 签到 / 报表" | `schedule` / `vacation` / `checkin` / `report` 子命令 | - -> 完整命令集(含写操作与参数)见 [references/attendance.md](references/attendance.md)。 - -## 评测高频硬约束 - -- 当前 dws 已注册全部考勤子命令组(`record` / `check` / `approve` / `shift` / `schedule` / `class` / `adjustment` / `overtime` / `group` / `summary` / `rules` / `selfsetting` / `globalsetting` / `vacation` / `checkin` / `report` / `boss-check`),查询与写操作大多可直接调用后端。**不要再以"开源版只读/不支持写操作"为由拒答。** 创建班次、导入排班、加人入考勤组、保存个人规则设置、设置假期余额等写操作可执行,但必须先展示参数摘要并二次确认,再追加 `--yes`(或 `--user-say-yes`)执行;不要在未确认时直接写、也不要伪装成功。个别命令受权限/数据影响返回空或权限错误(如 `report` 系列仅管理员),如实说明即可。 -- 查询迟到/缺勤名单时,空打卡结果不等于"没人迟到"。必须结合排班、`NotSigned`、`Absenteeism`、无记录人员分别说明;数据缺失要标为"无记录/无法判断",不要归为正常。 -- 做部门 Top N 排名时,用户要求前 N 名就必须输出 N 个部门;无打卡记录或无可计算数据的部门按 0 或"无数据"保留在排名中,不能只输出有数据的少数部门。 -- `summary` 必须同时传 `--user`、`--date`、`--stats-type`(week/month),缺一返回 C0002。 -- `shift list` 时间跨度不超过 7 天,最多 50 人;`record get` 一次只查一个用户一天。 -- 所有 dws 命令带 `--format json`,时间/日期按命令要求分别使用 `YYYY-MM-DD` 或 `yyyy-MM-dd HH:mm:ss`。 - -## 跨产品协作 - -- 拿到 userId 前先用 `dingtalk-aisearch` 解析人名 diff --git a/skills/multi/dingtalk-calendar/SKILL.md b/skills/multi/dingtalk-calendar/SKILL.md index 70418714..eaaf74b4 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 不支持,应提示在钉钉客户端操作。Distinct from 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,14 +11,9 @@ metadata: # 钉钉日历 Skill -> 🧪 **EXPERIMENTAL · 试验版 / Preview** — multi 模式当前未达 stable 标准。全部 dingtalk-* skill 已通过 dispatch verifier,但接口、命名、跨 skill 引用后续可能调整;生产 / 共享环境请优先使用 mono 模式(`dws skill setup --mode mono`)。问题请提 issue 反馈。 - -> **PREREQUISITE:** Read the `dws-shared` skill first for auth, global flags, product routing, URL preflight, error codes, and safety rules. The `dws` binary must be on PATH. - - - -> ⚠️ **命令可用性以当前 dws 二进制为准**。服务发现已下线,本文档随内置 skill 发布;如果 `dws --help` 不存在,说明当前版本未暴露该命令。若命令存在但调用失败,请按错误中的 endpoint 或 tool 提示确认静态端点目录和后端工具注册。实际调用前可用 `dws --help` 或 `--dry-run` 验证。 +## 前置条件 — 执行操作前必读 +> **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)。 @@ -59,9 +53,44 @@ metadata: | "今天 / 明天 / 本周日程" | `python scripts/calendar_today_agenda.py [today\|tomorrow\|week]` | | "约会议(含参会人 + 会议室)" | `python scripts/calendar_schedule_meeting.py --title "<主题>" --start "<起>" --end "<止>" [--users ] [--book-room]` | | "多人共同空闲" | `python scripts/calendar_free_slot_finder.py --users --date ` | -| "查闲忙" | `dws calendar busy search --users --start "" --end ""` | +| "查闲忙" | `dws calendar busy search --users --start "" --end ""` | | "加参会人" / "订房" / "取消" | `dws calendar attendee add` / `room add` / `event delete` | +## 标准 SOP(必遵流程) + +> 命中以下意图**必须**按对应 SOP 顺序执行;**禁止**跳步、替换命令、编造 userId/eventId。每条命令必须带 `--format json`,时间参数**必须**是 ISO-8601(如 `2026-07-03T14:00:00+08:00`)。 + +### SOP-1 查日程(list-events) + +**触发**:今天/明天/本周日程/我有什么会/某时段日程。 + +1. **首选脚本(必须)**:`python scripts/calendar_today_agenda.py today|tomorrow|week`(聚合今日议程)。 +2. **降级 CLI(必须)**:脚本不可用时 `dws calendar event list --start "<起始ISO>" --end "<结束ISO>" --format json`;不传 `--start/--end` 默认查今天(00:00:00~23:59:59)。`hasMore=true` 用 `--limit`/翻页。 +3. **解析(必须)**:取真实 `eventId`、`attendees[]`、`start/end`;按需抽取,**禁止**把整段 JSON 原样贴出。 + +**禁止**:用 `event list` 替代闲忙查询(查闲忙走 SOP-3)、编造时间窗口、用非 ISO 时间格式。 + +### SOP-2 建日程(create-event) + +**触发**:建日程/约会议/加日程。 + +1. **解析与会人(必须)**:对每个姓名 `dws aisearch person --keyword "<姓名>" --dimension name --format json` 取 `userId`,多人逗号拼接。 +2. **执行(必须)**:`dws calendar event create --title "<主题>" --start "" --end "" --attendees --format json`(按需加 `--location`/`--desc`/`--rooms`)。 +3. **验证(必须)**:从返回 `result.id` 取日程 ID(下游参数语义称 `eventId`),再执行 `dws calendar event list --start "" --end "" --format json` 复核标题、描述和时段。 + +**禁止**:跳过与会人 userId 解析直接传姓名、编造会议室 roomId。 + +### SOP-3 查闲忙(check-busy) + +**触发**:某人/会议室是否有空/找空闲时段/避免冲突。 + +1. **解析对象(必须)**:姓名 → `dws aisearch person --keyword "<姓名>" --dimension name --format json` 取 `userId`;会议室用 `roomId`。 +2. **收敛时段(必须)**:`--start`/`--end` **必须**由用户给出或明确收敛;时段不明确**必须先追问**,**禁止**默认全天窗口。 +3. **执行(必须)**:`dws calendar busy search --users --start "" --end "" --format json`(查会议室换 `--rooms `,可同时传)。**禁止**用 `event list` 扫日程替代闲忙查询。 +4. **空闲时段(必须)**:找共同空闲用 `python scripts/calendar_free_slot_finder.py`。 + +**禁止**:用 `event list` 冒充 `busy search`、未确认时段就默认全天查询。 + ## 执行硬约束 - 多轮日程任务必须保留 `eventId`,后续加人、移人、订房、换房、改描述、删除都基于同一个 `eventId` 执行;不要重新创建重复日程。 @@ -74,10 +103,13 @@ metadata: ## 跨产品协作 -- 视频会议发起 / 入会链接 / 邀请入会 / 会中控制 → 当前 CLI 不支持,请在钉钉客户端操作;预约日程仍走 `calendar` +- 视频会议发起 / 入会链接 / 邀请入会 / 会中控制 → 切到 `dingtalk-misc`(`references/conference.md`) - 会后摘要 / 待办 → 切到 `dingtalk-minutes` - 参会人按人名 → 先用 `dingtalk-aisearch` 解析 ## 注意 `schedule-meeting` 必须读 [03-meeting.md](references/03-meeting.md) 中的「两准则」「搜房失败硬门禁」,禁止假设 `roomId`。 +## 局部意图与短流程 + +- [局部意图消歧](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 6479ed36..ffc6c708 100644 --- a/skills/multi/dingtalk-calendar/references/03-meeting.md +++ b/skills/multi/dingtalk-calendar/references/03-meeting.md @@ -1,7 +1,7 @@ # 会议管理(日程与会议室) -> **lite 速查表**含 `list-today-meetings`、`check-users-busy`;视频会议 `start-conference` 当前 CLI 不支持。列表类操作须遵循 [calendar.md](./calendar.md) **「CLI 命令树与黄金路径」**,禁止无子命令的 `dws calendar` 或臆造 `calendar list`(见该文 **「反模式(禁止)」**)。**`schedule-meeting` 不做内联**:须读本文件 **「两准则」「搜房失败硬门禁」** 及下表 **schedule-meeting** 行全文。 -> **听记、会后待办、摘要分享** 见 `dingtalk-minutes` sub-skill 的 `references/07-minutes.md`。 +> lite recipe(`list-today-meetings`、`check-users-busy`)见 [lite-recipes.md](./lite-recipes.md);`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`。 ## 日程与会议室两准则(强制) @@ -22,7 +22,7 @@ ### 搜房失败硬门禁(园区/范围搜尽仍无 roomId) -在用户限定的园区、楼宇、楼层或评测固定分组内,已按早停规则**逐组 `room search` 查完**仍得不到任何有效 `rooms[].roomId`(或无任何空闲房)→ **立即停止**,向用户**明确报错/失败结论**(例如:该时段在指定范围内未检索到可预订会议室或无法获得 roomId),**本回合订房流程结束**。 +在用户限定的园区、楼宇、楼层或固定分组内,已按早停规则**逐组 `room search` 查完**仍得不到任何有效 `rooms[].roomId`(或无任何空闲房)→ **立即停止**,向用户**明确报错/失败结论**(例如:该时段在指定范围内未检索到可预订会议室或无法获得 roomId),**本回合订房流程结束**。 **用户汇报硬门禁**:一旦触发上面的失败条件,**下一条对外输出必须直接面向用户汇报结果**,不得继续在会话里自言自语式地延长推理。允许的后续只有两类: 1. **失败汇报**:明确说明“指定范围/指定会议室在该时段未找到可预订会议室,因此当前无法完成预订” @@ -47,10 +47,10 @@ | 2 | **要限范围/具名**:先 `list-groups` 解析允许的 `group-id`。若用户说的是同一地点(同园区/楼栋/楼层),应优先锁定**最相关的承载 group** 并只查它;该 group 无结果即可按该地点无房收束,不再试别的无关 group。仅当用户明确给出多个允许地点时,才分别对这些 group 各 **1 次** `room search --available` 再 `room add` | | 3 | **禁止**:无新信息时反复 `--verbose`、反复切 `--available`、父组子组试探、在最相关 group 无结果后改搜别的同级/异地 group、超 100 条后仍根分组或未授权区域全量搜;**禁止**对 `room search` 使用不存在的 `--query` | | 4 | **`roomId` 门禁**:`room add --rooms` **只能**填 `room search` 返回 JSON 中的 `rooms[].roomId`;**禁止**将用户说的会议室名、编号文案、或「假 UUID / 试数字」当作 `roomId` | -| 5 | **全量无结果即收束**:在用户允许的搜索范围内(含**整园/全 campus** 若用户或评测要求已逐组查尽)仍无任何可用会议室或有效 `roomId` → **直接报错/告知失败并结束订房**,且**下一条消息必须汇报给用户**;**不得**假设 ID、不得用 `room add` 试探、不得绕路查日程、不得继续自说自话分析 Mock/测试环境 | +| 5 | **全量无结果即收束**:在用户允许的搜索范围内(含**整园/全 campus** 若用户要求已逐组查尽)仍无任何可用会议室或有效 `roomId` → **直接报错/告知失败并结束订房**,且**下一条消息必须汇报给用户**;**不得**假设 ID、不得用 `room add` 试探、不得绕路查日程、不得继续自说自话分析 Mock/测试环境 | | 6 | **失败触发回读**:连续 2 次空结果、任意一次 `roomId invalid`、或开始换园区/绕路时 → **必须回读本节**;回读后只允许「报错收束」或「向用户确认是否放宽条件」 | | Recipe | 行动指南(固定路线) | | ------------------ | ------------------- | -| schedule-meeting | **见上文「两准则」**、**「搜房失败硬门禁」**。**未给时段且仅说「发起/开个会」**→ 不走本 recipe;当前 CLI 不支持实时视频会议,告知用户请在钉钉客户端操作。**未给时段但有预约意图**("安排""约""定"等词):追问具体开始/结束时间。**已有时段后**,按固定顺序执行:1. `dws calendar event create` 建日程;2. 有参会人则 `dws calendar attendee add`;3. 再处理会议室。**无明确会议室范围**:可直接 `python scripts/calendar_schedule_meeting.py --title "<主题>" --start "<起始>" --end "<结束>" [--users ] [--book-room] [--dry-run]`。**有明确范围(某楼/层)**:先 `dws calendar room list-groups`,锁定该地点**最相关的承载 group**;若只有一个地点,`--room-group-id` 应只传这个最相关 group,**不要**把同楼内多个楼层 group 打包传入碰运气。只有用户明确给出多个允许地点时,才把这些 `group-id` 一并传给 `python scripts/calendar_schedule_meeting.py ... --book-room --room-group-id ""`。**用户点名具体会议室**:须手工 `dws calendar room search --start "" --end "" --group-id [--available] --format json`(**无** `--query`),在 JSON 中匹配名称取 **`rooms[].roomId` 唯一真值** → `dws calendar room add --event --rooms `;**不得**把用户输入的会议室名当 `roomId`。**一旦连续 2 次空结果 / 任意一次 `roomId invalid`**:**必须回读本节并立即收束判断**;若整园/限定范围内搜尽仍无 roomId 或无空房 → **下一条消息必须直接向用户汇报失败结论**;否则只能向用户确认是否放宽范围/改时间。**禁止**假设 roomId、禁止无 ID 调用 `room add`、禁止用日程详情绕路、禁止继续猜测 Mock/测试环境。细则见「会议室搜索早停」。 | +| schedule-meeting | **见上文「两准则」**、**「搜房失败硬门禁」**。**未给时段且仅说「发起/开个会」**→ 不走本 recipe,走 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.md b/skills/multi/dingtalk-calendar/references/calendar.md index d879a649..a4a03e6b 100644 --- a/skills/multi/dingtalk-calendar/references/calendar.md +++ b/skills/multi/dingtalk-calendar/references/calendar.md @@ -2,18 +2,17 @@ ## CLI 命令树与黄金路径 -- **二级子命令(必选其一)**:`event`(日程)、`attendee`(参会人)、`room`(会议室)、`busy`(闲忙)、`attachment`(日程附件)、`book`(用户日历列表)。`dws calendar` 后**必须**紧跟上述之一;**禁止**只执行 `dws calendar`(无子命令)。 +- **二级子命令(必选其一)**:`event`(日程)、`attendee`(参会人)、`room`(会议室)、`busy`(闲忙)、`attachment`(日程附件)、`book`(我能看哪些日历本)、`acl`(我的日历共享给了谁)。`dws calendar` 后**必须**紧跟上述之一;**禁止**只执行 `dws calendar`(无子命令)。 - **个人日程 / 给自己留时间块 / 专注时段**:统一走 **`dws calendar event create`**。当前**没有**单独的 `personal schedule create` / `calendar create` 命令。 - **查日程列表**:`dws calendar event list --start "" --end "" --format json`,或优先使用脚本 `python scripts/calendar_today_agenda.py [today|tomorrow|week]`(见文末「自动化脚本」)。 -- **查用户日历本列表**:`dws calendar book list`(返回字段为 `calendarId`,主日历 `calendarId == "primary"` 等;注意无 `id` 字段,按 `.id` 提取会取空)。**重要**可以查询他人共享给自己的日历本,根据日历本id可以进一步查询对方的日程信息。 +- **查用户日历本列表**:`dws calendar book list`(返回主日历 `id == "primary"` 等)。**重要**可以查询他人共享给自己的日历本,根据日历本id可以进一步查询对方的日程信息。 - **CLI 不存在**独立的 `dws calendar list`;若误跑无子命令的 `dws calendar`,会打印整段 Usage,**切勿**将该段 help 当作工具结果再次塞进对话(会急剧增加 token 与首字延迟)。 - **必须**遵循指令说明进行调用。**绝对禁止**使用虚构指令,使用虚构参数。 ## 反模式(禁止) -1. **禁止**执行 `dws calendar` 且不带二级子命令(会刷出大量帮助文本)。合法二级子命令:`event` / `attendee` / `room` / `busy` / `attachment` / `book`。 +1. **禁止**执行 `dws calendar` 且不带二级子命令(会刷出大量帮助文本)。合法二级子命令:`event` / `attendee` / `room` / `busy` / `attachment` / `book` / `acl`。 2. **禁止**使用不存在的子命令试探(如臆造 `dws calendar list`);需要日程列表时一律使用 **`dws calendar event list`**(带 `--start` / `--end`,见下文「查询日程列表」示例);需要日历本列表时使用 **`dws calendar book list`**。 3. **禁止**将完整 `--help`/Usage 输出作为「观察」重复提交给模型;若误触,应直接改用本节黄金路径中的合法命令并重试。 -4. **禁止**继续使用已弃用的 `--max-results` 参数 —— 它们仍可被解析但**会被丢弃**,不再透传到 MCP,模型生成命令时也不要再带。 5. **禁止**为已有日程重新创建日程来预订会议室。若日程已存在(同一会话中刚创建、或用户明确指向某日程),必须使用 `room add --event <已有EVENT_ID> --rooms ` 追加会议室,**绝不能**再调一次 `event create --rooms`(会创建重复日程)。 6. **禁止**用 `--location` 替代会议室预订。`--location` 是纯文本地点备注字段,填入会议室名称**不会**完成任何预订或占用。预订会议室必须通过 `room add --rooms ` 或 `event create --rooms `,roomId 来自 `room search` 返回;`--location` 与 `--rooms` 是两个独立字段,用途完全不同。 7. **禁止**只传 `--recurrence-*` 部分 flag **并不是彼此独立的参数**:只传其中一项(比如只改 `--recurrence-count`、只设 `--recurrence-type`)会让服务端收到不完整的 recurrence 结构,CLI 现已前置校验并直接拒绝这类调用。**修改已有周期日程的任何一个循环字段时,都必须重新提供完整的 pattern+range 字段集合**——必要时先 `event get` 读取现有 `recurrence`,再在命令中整体重传。 @@ -26,10 +25,13 @@ 日程实例(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 --keyword "姓名"。 +参会人(attendee):日程的参与者。按姓名统一使用 `dws aisearch person --keyword "姓名" --dimension name --format json` 查询 userId。 响应状态(response):参会人对日程的回应,包括:未响应、接受、待定、拒绝。 忙闲时间(busy):查询用户在指定时间段的忙闲状态,查询会议室在指定时间段的预定状态,用于会议时间协调。 会议室(room):room是 会议室 ,room可视为日程的资源类参会人,需要加入日程完成预订。注意和location区分,location只是地点,和room不同。 +访问控制 (acl):用户可通过设置acl将自己的日历访问权限授予给他人(即:共享日历),授予后,他人就可以查看日历下的日程信息。通过 `dws calendar acl list` 可查看当前要用户已经授权出去的权限。若 privilege >= reader,可查询此日历下的日程数据。若 privilege >= writer ,可操作(创建/修改/删除/响应等)此日历下的日程数据。 +共享日历:将自己日历的访问权限授予给他人,用于协作。通过 `dws calendar book list` 查询当前用户日历列表,其中type == `shared` 即他人共享给当前用户的日历。 +订阅日历:创建一个公共日历供他人订阅,他人订阅后即可查询日历下的日程数据。通过 `dws calendar book list` 查询当前用户日历列表,其中type == `subscribed` 即当前用户已订阅的日历。注意:订阅日历下的日程无参会人概念,无法执行 attendee 相关指令,也无法添加会议室。 ## 命令概览 @@ -61,7 +63,6 @@ dws calendar room add [flags] dws calendar room delete [flags] ``` > room是会议室,用于线下开会场景。 -> **组织限制**:部分企业使用自建会议室系统,未接入钉钉会议室能力。此时 `room search` / `room list-groups` 会返回业务错误 `400056`("所选组织不支持预定钉钉会议室")。这是组织级配置限制,不是命令用法问题;应直接告知用户该组织需在钉钉客户端手动预订会议室,不要重试或换参。 ### busy 相关三级子命令 ``` @@ -75,10 +76,26 @@ dws calendar busy search [flags] dws calendar attachment add [flags] ``` -### book 相关三级子命令 +### book 相关三级子命令(查"我能看哪些日历本") ``` -# 查询当前用户的所有日历,结果范围:用户自己的日历、已订阅的公共/团队日历、他人共享的日历。 +# 查询我拥有和可访问的所有日历本(含他人共享给我的) dws calendar book list [flags] +# 查询指定日历本信息 +dws calendar book get [flags] +# 按名称模糊搜索日历本 +dws calendar book search [flags] +# 更新日历本信息(需 owner 权限) +dws calendar book update [flags] +``` + +### acl 相关三级子命令(查"我的日历共享给了谁") +``` +# 查询我的日历共享给了哪些人、各自什么权限 +dws calendar acl list [flags] +# 把我的日历共享给某人 +dws calendar acl add [flags] +# 取消我的日历对某人的共享 +dws calendar acl delete [flags] ``` > **说明**: 可以通过 --help 进一步查看指令明细,也可以继续查看下一节 命令总览 @@ -90,17 +107,21 @@ Usage: dws calendar event list [flags] Example: dws calendar event list --start "2026-03-10T14:00:00+08:00" --end "2026-03-10T18:00:00+08:00" + dws calendar event list --start "2026-03-10T00:00:00+08:00" --end "2026-03-31T23:59:59+08:00" --limit 50 dws calendar event list --calendar-id primary + dws calendar event list --cursor "" Flags: --calendar-id string 日历 ID (默认 primary 主日历,仅在查询其他日历本时填写;通过 `book list` 获取) + --cursor string 分页游标 (从上一次返回的 nextCursor 获取,首次查询无需传入) --end string 结束时间 ISO-8601 (例如 2026-03-10T18:00:00+08:00) + --limit int 每页返回条数 (默认 100,最大 100) --start string 开始时间 ISO-8601 (例如 2026-03-10T14:00:00+08:00) ``` -> `--max-results` 已弃用:MCP 不再支持,CLI 仍接受但参数会被丢弃,结果固定最多返回 100 条。 - **默认行为**:不传 `--start` / `--end` 时,默认返回今天的日程(00:00:00 ~ 23:59:59)。 **权限**:查询共享日历下的日程时,至少要有reader权限。 +**分页**:单次最多返回 `--limit` 指定的条数(默认/最大 100);当结果超过 limit 时,返回体包含 `nextCursor` 字段。首次查询无需传 `--cursor`,仅在翻页时将上一次返回的 `nextCursor` 作为 `--cursor` 传入。 + ### 获取日程详情 ``` @@ -130,10 +151,17 @@ Example: dws calendar event create --title "每日站会" \ --start "2026-03-10T09:00:00+08:00" --end "2026-03-10T09:30:00+08:00" \ --recurrence-type daily --recurrence-interval 1 --recurrence-range-type numbered --recurrence-count 10 + dws calendar event create --title "团队周会" \ + --start "2026-03-10T14:00:00+08:00" --end "2026-03-10T15:00:00+08:00" \ + --calendar-id # 在指定日历本下创建日程 + dws calendar event create --title "重要会议" \ + --start "2026-03-10T14:00:00+08:00" --end "2026-03-10T15:00:00+08:00" \ + --remind-minutes 5,10 # 开始前5分钟和10分钟各提醒一次 Flags: --title string 日程标题 (必填,最大2048字符) --start string 开始时间 ISO-8601 (必填,例如 2026-03-10T14:00:00+08:00) --end string 结束时间 ISO-8601 (必填,例如 2026-03-10T15:00:00+08:00) + --calendar-id string 日历 ID (可选,默认 primary 主日历;仅在共享/订阅日历本下创建时填写,通过 `book list` 获取) --timezone string 时区 IANA 格式 (例如 Asia/Shanghai,默认 Asia/Shanghai) --desc string 日程描述 (最大5000字符) --attendees string 参会人 userId 列表,逗号分隔 (最多500人) 日程组织人自动放入参会人列表,无需传入userId @@ -153,6 +181,7 @@ Flags: --rich-text-desc string html格式的富文本类型日程描述,用于复杂内容的展示 --location string 地点信息(纯文本备注,如‘3号楼A区’;**不等于**预订会议室) --free-busy string 此日程的忙碌状态,默认值为busy。busy - 在忙闲视图中,此日程时间段为忙碌; free - 此日程不占用忙闲 + --remind-minutes string 日程开始前提醒,逗号分隔分钟数 (可选,例如 5,10,15 表示开始前5/10/15分钟提醒;不传则默认15分钟提醒) ``` > **说明**:个人日程也走 `event create`。如果只是给自己安排时间,不传 `--attendees` / `--open-dingtalk-ids` 即可。 @@ -166,8 +195,10 @@ Example: dws calendar event update --id --desc "新描述" --timezone Asia/Tokyo dws calendar event update --id --recurrence-type daily --recurrence-interval 1 \ --recurrence-range-type numbered --recurrence-count 5 + dws calendar event update --id --calendar-id --title "新标题" # 修改其他日历本下的日程 Flags: --id string 日程 ID (必填) + --calendar-id string 日历 ID (可选,默认 primary 主日历;指定其他日历本时填写,可通过 `book list` 获取) --title string 新标题 --start string 新开始时间 ISO-8601 --end string 新结束时间 ISO-8601 @@ -199,8 +230,10 @@ Usage: dws calendar event delete [flags] Example: dws calendar event delete --id --yes + dws calendar event delete --id --calendar-id --yes # 删除其他日历本下的日程 Flags: - --id string 日程 ID (必填) + --id string 日程 ID (必填) + --calendar-id string 日历 ID (可选,默认 primary 主日历;指定其他日历本时填写,可通过 `book list` 获取) ``` ### 查看参会人 @@ -209,8 +242,10 @@ Usage: dws calendar attendee list [flags] Example: dws calendar attendee list --event + dws calendar attendee list --event --calendar-id # 查看其他日历本下日程的参会人 Flags: - --event string 日程 ID (必填) + --event string 日程 ID (必填) + --calendar-id string 日历 ID (可选,默认 primary 主日历;指定其他日历本时填写,可通过 `book list` 获取, 注意:订阅日历下的日程无参会人,因此不可查看) ``` ### 添加参会人 @@ -220,10 +255,12 @@ Usage: Example: dws calendar attendee add --event --attendees , dws calendar attendee add --event --attendees --optional + dws calendar attendee add --event --attendees --calendar-id # 给其他日历本下的日程添加参会人 Flags: - --event string 日程 ID (必填) - --attendees string 参会人 userId 列表,逗号分隔 (必填,最多500人) - --optional 参会人可选 (默认必选参会人) + --event string 日程 ID (必填) + --attendees string 参会人 userId 列表,逗号分隔 (必填,最多500人) + --optional 参会人可选 (默认必选参会人) + --calendar-id string 日历 ID (可选,默认 primary 主日历;指定其他日历本时填写,可通过 `book list` 获取,注意:订阅日历下的日程无参会人,因此不可添加) ``` ### 移除参会人 @@ -235,36 +272,54 @@ Usage: dws calendar attendee delete [flags] Example: dws calendar attendee delete --event --attendees --yes + dws calendar attendee delete --event --attendees --calendar-id --yes # 移除其他日历本下日程的参会人 Flags: - --event string 日程 ID (必填) - --attendees string 参会人 userId 列表,逗号分隔 (必填) + --event string 日程 ID (必填) + --attendees string 参会人 userId 列表,逗号分隔 (必填) + --calendar-id string 日历 ID (可选,默认 primary 主日历;指定其他日历本时填写,可通过 `book list` 获取,注意:订阅日历下的日程无参会人,因此不可移除) ``` ### 搜索会议室 -> 此指令可用于搜索当前用户可用的会议室。**注意**,大部分会议室**仅在工作时间**可用,如果检索时间不在工作时间可能查不到任何结果。 +> 此指令支持两种模式:**按名称搜索**(不传 --start/--end)和**按时间段搜索可用会议室**(传 --start/--end 或不传任何参数)。 > 此指令搜索到的会议室结果中,有两个值需要注意: - - customApprovalProcess: true - 表示该会议室设置了自定义审批流程,只能通过客户端完成预订。 - - supportRecurring: true - 表示该会议室支持循环预定;false - 表示不支持循环预定,直接加入到循环日程会失败。 +> - customApprovalProcess: true - 表示该会议室设置了自定义审批流程,只能通过客户端完成预订。 +> - supportRecurring: true - 表示该会议室支持循环预定;false - 表示不支持循环预定,直接加入到循环日程会失败。 + +**模式路由规则**: +- **按名称搜索**:仅传 `--room-name`,不传 `--start`/`--end` → 返回所有匹配名称的会议室,**不检查可用性**。适用于「找到某个会议室」的场景。 +- **按时间段搜索可用会议室**:传 `--start`/`--end`,或不传任何参数 → 返回指定时间段内**可用**的会议室。不传时间时默认当前时间起 1 小时。 ``` Usage: dws calendar room search [flags] Example: + # 按名称搜索(不检查可用性,返回所有匹配的会议室) + dws calendar room search --room-name 永澄亭 # 注意:用户即使说「永澄亭会议室」,也应仅传「永澄亭」 + + # 按时间段搜索可用会议室 dws calendar room search --start "2026-03-10T14:00:00+08:00" --end "2026-03-10T15:00:00+08:00" dws calendar room search --start "2026-03-10T14:00:00+08:00" --end "2026-03-10T15:00:00+08:00" --group-id - dws calendar room search --room-name 永澄亭 # 注意:用户即使说「永澄亭会议室」,也应仅传「永澄亭」 dws calendar room search # 不传 --start/--end 时默认当前时间起 1 小时 + + # 名称 + 时间段:搜索指定名称的可用会议室 + dws calendar room search --room-name 永澄亭 --start "2026-03-10T14:00:00+08:00" --end "2026-03-10T15:00:00+08:00" + + # 分页(仅按时间段搜索时有效) + dws calendar room search --start "2026-03-10T14:00:00+08:00" --end "2026-03-10T15:00:00+08:00" --limit 20 --page 0 Flags: --start string 开始时间 ISO-8601 (可选,不传则默认当前时间+1分钟缓冲) --end string 结束时间 ISO-8601 (可选,不传则默认当前时间+1 小时) - --group-id string 会议室分组ID(可选,留空查根目录;超100条时需按分组查询) - --room-name string 按会议室名称过滤(可选,服务端模糊匹配;传入前必须由调用方精简,只保留核心专名) + --group-id string 会议室分组ID(可选,留空查根目录;超100条时需按分组查询;仅按时间段搜索时有效) + --room-name string 会议室名称(按名称搜索时必填;按时间段搜索时可选,用于过滤) + --limit string 页大小 (可选,不填默认 100,超过 100 按 100 处理;仅按时间段搜索时有效) + --page string 分页起始位置 (可选,不填默认 0;仅按时间段搜索时有效) ``` > **时间约束(API 限制)**:`start` 必须是未来的时间(服务端校验:start can not less current time)。 > - 若传入的 `--start` 早于当前时间,CLI 会自动修正为 `now + 1min`,调用方无需额外处理。 > - 若传入的 `--end` 早于当前时间,CLI 直接报错——无法检索已过去的时间段。 > - **最佳实践**:调用方在组装时间参数时应确保 start/end 都是未来时间;若不确定,可省略 `--start`/`--end` 让 CLI 使用默认值(当前时间起 1 小时)。 +> - **注意**:时间约束仅在「按时间段搜索」模式下生效。按名称搜索(仅传 `--room-name`)不受时间约束。 **名称过滤使用规范**:`--room-name` 适用于用户说「预定永澄亭」「约西湖厅」这类按名找会议室的场景。 - **服务端是模糊匹配,但匹配词越精简命中率越高**,关键疗法:**调用方必须在调用 CLI 前自行精简名称,CLI 不会再做任何删减**。 @@ -274,13 +329,16 @@ Flags: - 用户:「西湖厅有空吗」 → `--room-name 西湖厅`(本身就是专名,不删即可) - 用户:「预定贡嘎山大会议室」 → `--room-name 贡嘎山` -**优先路径**:当用户给了会议室中文名时,**优先用 `room search --room-name <核心专名> --start <开始时间> --end <结束时间>`**代替「先走 `list-groups`再遍历」的旧流程。`--room-name` 可与 `--group-id` 同时使用,表示「在指定分组内按名称过滤」。若返回空列表,再降级使用 `list-groups` 定位分组再查。 +**优先路径**: +- 当用户仅给出会议室名称、未指定时间段时,用 `room search --room-name <核心专名>` 快速找到会议室(不检查可用性)。 +- 当用户给出会议室名称且指定了时间段时,用 `room search --room-name <核心专名> --start <开始时间> --end <结束时间>` 查询该名称的可用会议室。 +- 若返回空列表,再降级使用 `list-groups` 定位分组再查。`--room-name` 可与 `--group-id` 同时使用(仅按时间段搜索时),表示「在指定分组内按名称过滤」。 **`roomId` 与用户说的话不是一回事**:用户说的「C6-4-06-N 贡嘎山」等是**展示名/编号文案**,**绝不能**直接填进 `room add --rooms`。`--rooms` 只接受上一步 `room search`(或同类接口)返回 JSON 里的 **`rooms[].roomId`**。形态上多为**小写十六进制串**(长度以接口为准,例如 `e6b7b65b8b30fb707afcf6c3b699f028003e6834fdd7fee7`)。含**中文、空格、连字符拼接的楼层编号**、或凭空调 UUID/纯数字「试格式」——一律视为非法,必须先搜房再取返回字段。 > 如果知道roomId,想查该会议室的预订记录,直接用dws calendar busy search 指令 ---- +--- ### 预定会议室 ``` @@ -288,9 +346,11 @@ Usage: dws calendar room add [flags] Example: dws calendar room add --event --rooms + dws calendar room add --event --rooms --calendar-id # 给其他日历本下的日程预定会议室 Flags: - --event string 日程 ID (必填) - --rooms string 会议室 ID 列表 (必填) + --event string 日程 ID (必填) + --rooms string 会议室 ID 列表 (必填) + --calendar-id string 日历 ID (可选,默认 primary 主日历;指定其他日历本时填写,可通过 `book list` 获取,注意:订阅日历下的日程不可添加会议室) ``` > room是会议室,用于线下开会场景。将room加入到日程完成预订 > 重复性日程,预订会议室时,必须设置 循环结束时间(recurrence-end-date),noEnd 或者 指定循环次数 都无法完成预定。 @@ -305,9 +365,11 @@ Usage: dws calendar room delete [flags] Example: dws calendar room delete --event --rooms --yes + dws calendar room delete --event --rooms --calendar-id --yes # 移除其他日历本下日程的会议室 Flags: - --event string 日程 ID (必填) - --rooms string 会议室 ID 列表 (必填) + --event string 日程 ID (必填) + --rooms string 会议室 ID 列表 (必填) + --calendar-id string 日历 ID (可选,默认 primary 主日历;指定其他日历本时填写,可通过 `book list` 获取。注意:订阅日历下的日程不可添加会议室) ``` ### 会议室分组列表 @@ -328,24 +390,104 @@ Usage: dws calendar attachment add [flags] Example: dws calendar attachment add --event --files :report.pdf,:slides.pptx + dws calendar attachment add --event --files :report.pdf --calendar-id # 给其他日历本下的日程添加附件 Flags: - --event string 日程 ID (必填) - --files string 附件列表,格式 :,多项逗号分隔 (必填) + --event string 日程 ID (必填) + --files string 附件列表,格式 :,多项逗号分隔 (必填) + --calendar-id string 日历 ID (可选,默认 primary 主日历;指定其他日历本时填写,可通过 `book list` 获取,注意:订阅日历下的日程不可添加附件) ``` > 上传文件得到 `fileId` 需配合钉盘相关流程;本命令只负责把已上传的文件挂载到日程上。 -### 查询用户日历列表 +### 查询我能看的所有日历本 ``` Usage: dws calendar book list [flags] Example: dws calendar book list ``` -> 通过此接口可查询当前用户的日历列表,包含 主日历本、他人共享的日历、订阅的公共/团队日历。 -> 共享日历本中有来自 xxx 的,且权限大于reader,那么通过 `event list --calendar-id `可查到xxx完整的日程安排 +> 查询"我能看哪些日历本",包含:我的主日历、他人共享**给我**的日历、订阅的公共/团队日历。注意区分:`acl list` 是查"我的日历共享**给了谁**",方向相反。 +> 共享日历本中有来自 xxx 的,且权限大于reader,那么通过 `event list --calendar-id `可查到xxx完整的日程安排。 > 主日历 `id` 固定为 `primary`,绝大多数日程操作都默认走主日历,只有当用户明确要求查/写其他日历本时才需要带 `--calendar-id`。 +### 查询指定日历本 +``` +Usage: + dws calendar book get [flags] +Example: + dws calendar book get --id primary + dws calendar book get --id CALENDAR_ID +Flags: + --id string 日历 ID (必填,主日历固定为 primary) +``` + +> **说明**:根据日历 id 查询指定日历的详细信息。用户主日历本 id 固定为 `primary`。 + +### 搜索日历本 +``` +Usage: + dws calendar book search [flags] +Example: + dws calendar book search --query "项目" + dws calendar book search --query "团队周报" +Flags: + --query string 按日历本名称模糊检索 (必填) +``` + +> **说明**:搜索当前用户拥有的日历本,支持按日历本名模糊搜索。获取全部日历请使用 `book list`。 + +### 更新日历本 +``` +Usage: + dws calendar book update [flags] +Example: + dws calendar book update --id CALENDAR_ID --summary "新日历名" + dws calendar book update --id CALENDAR_ID --desc "日历描述" +Flags: + --id string 日历 ID (必填) + --summary string 日历标题 + --desc string 日历描述 +``` + +> **说明**:更新日历信息,最低权限要求:privilege == "owner"。注意:用户主日历本 以及 他人共享的日历本 **不支持更新**。 + +### 查询我的日历共享给了谁 +``` +Usage: + dws calendar acl list [flags] +Example: + dws calendar acl list +``` + +> **说明**:查询"我的日历共享给了哪些人、各自什么权限"(即主日历的访问控制列表)。注意区分:`book list` 是查"我能看哪些日历本",方向相反。 + +### 把我的日历共享给某人 +``` +Usage: + dws calendar acl add [flags] +Example: + dws calendar acl add --user USER_ID --privilege reader + dws calendar acl add --user USER_ID --privilege writer --no-notification +Flags: + --user string 授予权限的目标用户 ID (必填) + --privilege string 授予的日历权限 (必填): free_busy_reader(查看忙闲)|title_reader(查看标题)|reader(查看详情)|writer(创建和编辑) + --no-notification 不向被授权用户发送提醒 (默认发送) +``` + +> **说明**:把我的日历共享给指定用户,授予对方相应权限。`--privilege` 可选值:`free_busy_reader`(查看忙闲)、`title_reader`(查看标题)、`reader`(查看详情)、`writer`(创建和编辑)。 + +### 取消我的日历对某人的共享 +``` +Usage: + dws calendar acl delete [flags] +Example: + dws calendar acl delete --acl-id ACL_ID +Flags: + --acl-id string 已授予权限的 ID (必填,可通过 acl list 查询) +``` + +> **说明**:取消我的日历对某人的共享(撤回已授予的访问权限)。aclId 可通过 `acl list` 获取。 + ### 查询用户 / 会议室闲忙状态 ``` Usage: @@ -398,9 +540,11 @@ Example: dws calendar event respond --id --status accepted dws calendar event respond --id --status declined dws calendar event respond --id --status tentative + dws calendar event respond --id --status accepted --calendar-id # 响应其他日历本下的日程 Flags: - --id string 日程 ID (必填) - --status string 响应状态: needsAction(未操作)|accepted(接受)|declined(拒绝)|tentative(暂定) (必填) + --id string 日程 ID (必填) + --status string 响应状态: needsAction(未操作)|accepted(接受)|declined(拒绝)|tentative(暂定) (必填) + --calendar-id string 日历 ID (可选,默认 primary 主日历;指定其他日历本时填写,可通过 `book list` 获取。注意:订阅日历下的日程无参会人,因此不可响应) ``` > **说明**:作为日程参会人,设置自己的响应状态(接受、拒绝、暂定)。`--status` 可选值:`needsAction`(未操作,默认值)、`accepted`(接受)、`declined`(拒绝)、`tentative`(暂定)。 @@ -410,7 +554,7 @@ Flags: 用户说"日程/会议/约会/日历": - 查看 → `event list` - 详情 → `event get` -- 创建/约/给自己留时间块/个人日程 → `event create`(带参会人时加 `--attendees`,循环日程加 `--recurrence-*`) +- 创建/约/给自己留时间块/个人日程 → `event create`(带参会人时加 `--attendees`,循环日程加 `--recurrence-*`,自定义提醒加 `--remind-minutes`) - 修改/改时间/改描述 → `event update`(支持修改标题、时间、描述、时区、循环规则) - 取消/删除 → `event delete` - 推荐时间/什么时候有空/协调时间 → `event suggest` @@ -422,13 +566,14 @@ Flags: - 移除 → `attendee delete --attendees ` 用户说"会议室/订会议室": -- 哪个空闲 → `room search` -- 按名找会议室(如「永澄亭」「永澄亭会议室」「约西湖厅」)→ 先在模型层精简名称(剔除「会议室」等通用后缀),再用 `room search --room-name <核心专名>` +- 哪个空闲 → `room search`(默认查当前时间起 1 小时内可用会议室) +- 按名找会议室(如「永澄亭」「永澄亭会议室」「约西湖厅」,未提时间段)→ 先在模型层精简名称(剔除「会议室」等通用后缀),再用 `room search --room-name <核心专名>`(按名称搜索,不检查可用性) +- 按名找可用会议室(如「永澄亭下午 2 点有空吗」)→ `room search --room-name <核心专名> --start <开始时间> --end <结束时间>`(按名称+时间段搜索可用会议室) - 预订 - 给已有日程订会议室 → `room add --event <已有EVENT_ID> --rooms ` - 创建新日程并订会议室 → `event create --rooms`(仅当日程尚不存在时) - 取消预定 → `room delete` -- 分组 → `room list-groups`,取 groupId 后 `room search --group-id`(可再叠加 `--room-name` 在分组内过滤) +- 分组 → `room list-groups`,取 groupId 后 `room search --group-id`(需配合 `--start`/`--end` 按时间段搜索;可再叠加 `--room-name` 在分组内过滤) 用户说"有空吗/忙不忙/闲忙": - 查询用户闲忙 → `busy search --users ` @@ -438,8 +583,18 @@ Flags: 用户说"日程附件/给会议加文件/上传日程材料": - 添加 → `attachment add`(先用钉盘上传得 fileId,再 `attachment add --files :`) -用户说"我有几个日历/查所有日历/共享日历本": -- 列表 → `book list`(主日历 id 固定为 `primary`) +用户说"我有几个日历/查所有日历/别人共享给我的日历/他人共享给我的日历本": +- 列表 → `book list`(返回用户拥有和订阅的所有日历本,包括他人共享给自己的;主日历 id 固定为 `primary`) +- 查指定日历本 → `book get --id ` +- 按名称搜索日历本 → `book search --query "关键词"` +- 修改日历本名称/描述 → `book update --id --summary "新名"` + +用户说"我的日历共享给了谁/谁能看我日历/日历权限/取消共享/把日历分享给xxx": +- 查看我共享出去的情况(即谁有权访问我的日历) → `acl list` +- 把我的日历共享给他人 → `acl add --user --privilege reader` +- 取消我的日历对某人的共享 → `acl delete --acl-id `(aclId 来自 `acl list`) + +> **易混淆辨析**:`book list` 查的是"我能看哪些日历本"(包含别人共享**给我**的);`acl list` 查的是"我的日历共享**给了谁**"(我的主日历的访问控制列表)。两者方向相反,不可混用。 用户说"查下xxx的日程安排": - 查询是否有共享关系 -> `book list` @@ -473,11 +628,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: 搜索空闲会议室 @@ -505,13 +660,17 @@ dws calendar event list --start "2026-03-10T14:00:00+08:00" --end "2026-03-10T15 | 操作 | 从返回中提取 | 用于 | |------|-------------|------| -| `event create` | `eventId` | attendee/room/attachment 操作的 --event | -| `event list` | `events[].eventId` | event get/update/delete/respond 的 --id | +| `event 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` | 响应结果 | — | | `room search` | `rooms[].roomId` | room add 的 --rooms 或 event create 的 --rooms | | `room list-groups` | `groups[].groupId` | room search 的 --group-id | -| `book list` | `calendarId`(如 `primary`) | event list/get 的 --calendar-id | +| `book list` | `id`(如 `primary`) | event list/get 的 --calendar-id, book get/update 的 --id | +| `book get` | 日历详细信息 | — | +| `book search` | 匹配的日历列表 | book get/update 的 --id | +| `acl list` | `aclId` | acl delete 的 --acl-id | +| `acl add` | 新增的权限记录 | — | | 钉盘上传 | 文件 `fileId` | attachment add 的 --files `:` | ## 注意事项 @@ -527,20 +686,22 @@ dws calendar event list --start "2026-03-10T14:00:00+08:00" --end "2026-03-10T15 - `room list-groups` 支持 `--limit` / `--page` 分页(schema 类型为字符串) - **`event create --rooms` / `room add --rooms` 的唯一合法来源**:最近一次(同一会话、同一时段窗口)`room search` 返回体中的 `roomId`;禁止把用户自然语言会议室名当 `roomId` 传入(否则会 `roomId invalid` 等错误) - **搜房无结果**:在符合早停/用户限定范围内,`room search`(含按分组逐组查)全部返回空或无空闲 → 应**直接向用户报错/说明失败**并结束订房;**禁止**假设 roomId、禁止无合法 `roomId` 时调用 `room add` / `event create --rooms` 试探、禁止用 `event get` 等绕路推断 roomId -- **评测 / 自动化断言**:凡涉及 `room add` / `event create --rooms` 的流程,`--rooms` 只能填上游 `room search`(或等价接口)返回 JSON 中的 **`rooms[].roomId`**;不得以会议室展示名、楼层文案或用户口语当作 `roomId` +- **自动化校验**:凡涉及 `room add` / `event create --rooms` 的流程,`--rooms` 只能填上游 `room search`(或等价接口)返回 JSON 中的 **`rooms[].roomId`**;不得以会议室展示名、楼层文案或用户口语当作 `roomId` - **附件**:`attachment add` 仅负责挂载,**不上传**文件;fileId 必须先通过钉盘流程取得;`--files` 多附件用 `:` 元素逗号分隔 -- **日历本**:`book list` 返回的 `calendarId` 字段才是合法日历 id(无 `id` 字段);如无明确说明,`event list` / `event get` 都不要带 `--calendar-id`,让接口默认走 primary 主日历 -- **已弃用入参**:`--max-results`(event list)在新 MCP schema 中被移除;CLI 仍接受但**不会**透传到 MCP;模型生成命令时**禁止**继续使用 +- **日历本**:`book list` 返回的 `id` 才是合法 `calendarId`;如无明确说明,`event list` / `event get` 都不要带 `--calendar-id`,让接口默认走 primary 主日历 +- **分页查询**:`event list` 支持 `--limit`(控制每页条数,默认/最大 100)和 `--cursor`(翻页游标);**首次查询无需传 `--cursor`**,仅当返回体中包含 `nextCursor` 时,将其作为 `--cursor` 传入可获取下一页 +- **日程提醒**:`event create` 支持 `--remind-minutes` 设置开始前提醒,逗号分隔多个分钟数(如 `--remind-minutes 5,10,15`),不传则默认15分钟提醒 +- **会议室分页**:`room search` 支持 `--limit`(每页条数,默认100,最大100)和 `--page`(分页起始位置,默认0),与 `room list-groups` 分页风格一致 ## 自动化脚本 | 脚本 | 场景 | 用法 | |------|------|------| -| [calendar_today_agenda.py](../../scripts/calendar_today_agenda.py) | 查看今天/明天/本周日程安排 | `python calendar_today_agenda.py today` | -| [calendar_schedule_meeting.py](../../scripts/calendar_schedule_meeting.py) | 一键创建日程+添加参会人+预定会议室;搜房失败时输出明确原因并返回非零退出码 | `python calendar_schedule_meeting.py --title "复盘会" --start "2026-03-15T14:00" --end "2026-03-15T15:00" --users userId1 --book-room` | -| [calendar_free_slot_finder.py](../../scripts/calendar_free_slot_finder.py) | 查询多人共同空闲时段 | `python calendar_free_slot_finder.py --users userId1,userId2 --date 2026-03-15` | +| [calendar_today_agenda.py](../scripts/calendar_today_agenda.py) | 查看今天/明天/本周日程安排 | `python calendar_today_agenda.py today` | +| [calendar_schedule_meeting.py](../scripts/calendar_schedule_meeting.py) | 一键创建日程+添加参会人+预定会议室;搜房失败时输出明确原因并返回非零退出码 | `python calendar_schedule_meeting.py --title "复盘会" --start "2026-03-15T14:00" --end "2026-03-15T15:00" --users userId1 --book-room` | +| [calendar_free_slot_finder.py](../scripts/calendar_free_slot_finder.py) | 查询多人共同空闲时段 | `python calendar_free_slot_finder.py --users userId1,userId2 --date 2026-03-15` | ## 相关产品 -- conference 视频会议 — 当前 CLI 不支持发起/预约/邀请入会/会中控制;请在钉钉客户端操作 -- [contact](./contact.md) — 搜索同事 userId,用于 attendee add --attendees +- conference(视频会议预约) — 仅视频会议预约(返回入会链接),不含参会人/会议室管理 +- [contact](../../dingtalk-contact/references/contact.md) — 搜索同事 userId,用于 attendee add --attendees diff --git a/skills/multi/dingtalk-calendar/references/intent-guide.md b/skills/multi/dingtalk-calendar/references/intent-guide.md new file mode 100644 index 00000000..c56c7ac5 --- /dev/null +++ b/skills/multi/dingtalk-calendar/references/intent-guide.md @@ -0,0 +1,9 @@ +# calendar 局部意图消歧 + +本文件从单 Skill `intent-guide.md` 拆分而来,仅保留与本产品相关的跨产品消歧规则。 + +| 用户说... | 真实意图 | 应该用 | 不要用 | 理由 | +|---|---|---|---|---| +| "给自己留一个明天下午的时间块/建个个人日程" | 创建个人日程 | `calendar event create` | `todo` | 个人 schedule 仍属于日历事件,不是待办 | +| "帮我建一个明天下午的日程" | 日历日程 | `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 new file mode 100644 index 00000000..6a1faa32 --- /dev/null +++ b/skills/multi/dingtalk-calendar/references/lite-recipes.md @@ -0,0 +1,21 @@ +# calendar Lite Recipe + +本文件从单 Skill `lite-recipes.md` 拆分而来,仅保留与本产品相关的轻量流程。 + +## #3 会议日程 + +### list-today-meetings + +**优先**:`python scripts/calendar_today_agenda.py [today|tomorrow|week]` +备选:`dws calendar event list --start "<今日起始ISO>" --end "<今日结束ISO>"`(须加 `--format json`) + +### check-users-busy + +查询多人在某时段内的闲忙(**busy**,不是用 `event list` 扫日程): + +1. 解析用户:对每个姓名执行 `aisearch person --keyword "<姓名>" --dimension name` → `userId`;多人将 `userId` 用英文逗号拼接(无空格或按 [calendar.md](./calendar.md) `busy search` 要求)。 +2. 确认时段:用户须给出或可收敛为明确的 `--start` / `--end`(ISO-8601);若未给出,**先追问**起止时间,禁止用任意默认全天窗口代替用户意图。 +3. 执行:`dws calendar busy search --users --start "" --end "" --format json` + +详见 [calendar.md](./calendar.md) 中「查询用户闲忙状态」。 + diff --git a/skills/multi/dingtalk-calendar/scripts/calendar_free_slot_finder.py b/skills/multi/dingtalk-calendar/scripts/calendar_free_slot_finder.py index b4bf20ff..01c9f637 100644 --- a/skills/multi/dingtalk-calendar/scripts/calendar_free_slot_finder.py +++ b/skills/multi/dingtalk-calendar/scripts/calendar_free_slot_finder.py @@ -51,51 +51,30 @@ def fmt_iso(dt: datetime) -> str: return dt.strftime('%Y-%m-%dT%H:%M:%S+08:00') -def _extract_time_str(value: Any) -> str: - """时间字段兼容字符串与 {dateTime, date} 对象两种形态""" - if isinstance(value, dict): - return value.get('dateTime') or value.get('date') or '' - return value or '' - - def parse_busy_intervals( data: Any, ) -> List[Tuple[datetime, datetime]]: intervals = [] if not data: return intervals - # 兼容 {result: [...]} 包裹结构 - if isinstance(data, dict) and 'result' in data: - data = data['result'] - rows = [] - if isinstance(data, list): - rows = data - elif isinstance(data, dict): - rows = list(data.values()) items = [] - for row in rows: - if isinstance(row, list): - items.extend(row) - elif isinstance(row, dict): - # 新结构: {userId, scheduleItems: [{start, end, status}]} - if isinstance(row.get('scheduleItems'), list): - items.extend(row['scheduleItems']) - elif isinstance(row.get('busyTimes'), list): - items.extend(row['busyTimes']) - elif row.get('startTime') or row.get('start'): - items.append(row) + if isinstance(data, list): + items = data + elif isinstance(data, dict): + inner = data.get('result', data) + if isinstance(inner, list): + items = inner + elif isinstance(inner, dict): + for user_data in inner.values(): + if isinstance(user_data, list): + items.extend(user_data) + elif isinstance(user_data, dict): + items.extend( + user_data.get('busyTimes', []) + ) for item in items: - if not isinstance(item, dict): - continue - # FREE 时段不算忙 - if str(item.get('status') or '').upper() == 'FREE': - continue - start_str = _extract_time_str( - item.get('startTime') or item.get('start') - ) - end_str = _extract_time_str( - item.get('endTime') or item.get('end') - ) + start_str = item.get('startTime') or item.get('start', '') + end_str = item.get('endTime') or item.get('end', '') if not start_str or not end_str: continue for fmt in ( @@ -193,16 +172,6 @@ def main(): if args.dry_run: return - # 查询失败必须中止, 否则会把"查不到"当成"全天空闲"给出危险结论 - if data is None: - print('错误:忙闲查询失败,无法给出空闲时段结论', file=sys.stderr) - sys.exit(2) - if isinstance(data, dict) and data.get('success') is False: - print(f"错误:忙闲查询失败: " - f"{data.get('errorMsg') or data.get('errorCode') or data}", - file=sys.stderr) - sys.exit(2) - busy = parse_busy_intervals(data) free = find_free_slots(day_start, day_end, busy, args.duration) @@ -212,7 +181,6 @@ def main(): print(f" 会议时长: {args.duration} 分钟") print(f" 工作时间: {args.start_hour}:00 ~ " f"{args.end_hour}:00") - print(f" 已识别忙时段: {len(busy)} 个") print('=' * 50) if not free: diff --git a/skills/multi/dingtalk-chat/SKILL.md b/skills/multi/dingtalk-chat/SKILL.md index 709cc7ee..74e3cd77 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通知/发送或下载消息图片与文件。Distinct from dingtalk-ding(紧急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,14 +11,9 @@ metadata: # 钉钉群聊 / 消息 Skill -> 🧪 **EXPERIMENTAL · 试验版 / Preview** — multi 模式当前未达 stable 标准。全部 dingtalk-* skill 已通过 dispatch verifier,但接口、命名、跨 skill 引用后续可能调整;生产 / 共享环境请优先使用 mono 模式(`dws skill setup --mode mono`)。问题请提 issue 反馈。 - -> **PREREQUISITE:** Read the `dws-shared` skill first for auth, global flags, product routing, URL preflight, error codes, and safety rules. The `dws` binary must be on PATH. - - - -> ⚠️ **命令可用性以当前 dws 二进制为准**。服务发现已下线,本文档随内置 skill 发布;如果 `dws --help` 不存在,说明当前版本未暴露该命令。若命令存在但调用失败,请按错误中的 endpoint 或 tool 提示确认静态端点目录和后端工具注册。实际调用前可用 `dws --help` 或 `--dry-run` 验证。 +## 前置条件 — 执行操作前必读 +> **CRITICAL — 执行任何 `dws` 操作前,MUST 先用 Read 工具完整读取 [`dws-shared`](../dws-shared/SKILL.md)。**该轻量文件包含全局执行契约、安全底线及 shared references 的按需加载导航;不要预加载其全部 references。 > 命令参考:[chat.md](references/chat.md);表情:[chat-emoji-list.md](references/chat-emoji-list.md);剧本:[01-messaging.md](references/01-messaging.md)。 @@ -30,7 +24,7 @@ metadata: | Shortcut | 风险 | 适用场景 | |---|---|---| -| `dws chat +at-me` | write | 查最近 @我 的消息(自动算时间窗,投影发送人/时间/内容/会话) | +| `dws chat +at-me` | read | 查最近 @我 的消息(自动算时间窗,投影发送人/时间/内容/会话) | | `dws chat +bot-find` | read | 搜索全部可用机器人(含他人/官方,返回 openDingTalkId 可发单聊) | | `dws chat +bot-search` | read | 搜索当前用户自己创建的机器人 | | `dws chat +broadcast` | write | 按姓名逐一给多个人群发同一条单聊消息(自动解析 userId、逐个发送) | @@ -53,7 +47,7 @@ metadata: | `dws chat +chat-list-mine` | read | 拉取我创建/管理的群 | | `dws chat +chat-members-get` | read | 根据成员 openDingTalkId 批量查询群成员详情 | | `dws chat +chat-members-list` | read | 列出群成员并把用户与机器人分桶(支持群名语义解析) | -| `dws chat +chat-messages` | write | 拉取某个会话(群聊或单聊)的消息列表并投影出发言人/文本/时间 | +| `dws chat +chat-messages` | read | 拉取某个会话(群聊或单聊)的消息列表并投影出发言人/文本/时间 | | `dws chat +chat-mute` | write | 全员禁言 / 取消全员禁言 | | `dws chat +chat-mute-member` | write | 指定群成员禁言 / 取消禁言 | | `dws chat +chat-quit` | write | 退出群聊 | @@ -103,7 +97,7 @@ metadata: | `dws chat +messages-list-direct` | read | 拉取单聊会话消息 | | `dws chat +messages-list-pin` | read | 拉取会话中钉住的消息列表 | | `dws chat +messages-list-unread-conversations` | read | 获取有未读消息的会话列表 | -| `dws chat +messages-mget` | write | 根据消息 ID 批量查询消息(最多 50 条) | +| `dws chat +messages-mget` | read | 根据消息 ID 批量查询消息(最多 50 条) | | `dws chat +messages-query-send-status` | read | 查询消息发送状态 | | `dws chat +messages-read-status` | read | 查询消息的已读/未读状态 | | `dws chat +messages-recall` | write | 撤回当前用户发送的消息 | @@ -123,9 +117,9 @@ metadata: | `dws chat +messages-unset-top` | write | 取消置顶消息 | | `dws chat +messages-update-card` | write | 流式更新卡片内容(最后一次 --flow-status 应为 3) | | `dws chat +my-groups` | read | 列出我加入的群,可按类型过滤并投影关键字段 | -| `dws chat +search-msg` | write | 多维搜索消息,可全量翻页并批量富化详情 | +| `dws chat +search-msg` | read | 多维搜索消息,可全量翻页并批量富化详情 | | `dws chat +send-to-group` | write | 按群名直接给群发消息(自动搜群解析 openConversationId) | -| `dws chat +thread-replies` | write | 拉取某条话题消息的全部回复并投影出发言人/文本/时间 | +| `dws chat +thread-replies` | read | 拉取某条话题消息的全部回复并投影出发言人/文本/时间 | | `dws chat +unread-chats` | read | 列出我有未读消息的会话(投影会话名/未读数/会话ID) | @@ -133,8 +127,8 @@ metadata: - 统一发送优先用 `chat +messages-send`:通过 `--as user|bot|webhook` 选择身份,并只传该身份真实支持的目标、内容和凭据。仅在需要原子命令的特定返回结构或兼容参数时,才降级到 `chat message send` / `send-by-bot` / `send-by-webhook`。 - 消息查询按意图选择:指定会话用 `+chat-messages`,跨维度过滤/全量翻页用 `+search-msg`,@我用 `+at-me`,已知消息 ID 批量富化用 `+messages-mget`,已知 thread/topic ID 读取回复用 `+thread-replies`。 -- 查询结果中的 `resourceRefs` 是可继续执行的资源上下文。引用、回复或合并转发中的子消息必须使用该子消息返回的 `messageId`;子消息缺会话 ID 时才继承父消息的 `openConversationId`,不要把所有资源重新绑定到父消息。 -- `+at-me`、`+chat-messages`、`+messages-mget`、`+search-msg`、`+thread-replies` 的 `--download-resources` 会写本地文件;`+messages-resource-download` 始终写本地文件。它们按 leaf Schema 发布为 `write/user_required`,先确认目标目录和覆盖策略,获得用户确认后才加 `--yes`。 +- 查询结果中的 `resourceRefs` 是可继续执行的资源上下文。引用、回复或合并转发中的子消息必须使用该子消息返回的 `messageId`;子消息缺会话 ID 时才继承父消息的 `openConversationId`。 +- 上述五个查询 Shortcut 默认按 `read/not_required` 执行;仅显式传 `--download-resources` 时,运行时风险升级为 `write` 并要求确认。`+messages-resource-download` 始终是写操作。确认目标目录和覆盖策略后才加 `--yes`。 - `+messages-send` 会按身份规范化 @ 占位符:user 使用 `<@id>` / `<@all>`,bot/webhook 使用 `@id` / `@手机号` / `@all`;只需声明 `--at-*` / `--at-all`,缺失占位符会自动补齐。 ## 意图表 @@ -143,34 +137,95 @@ metadata: |--------|------| | "发消息给张三" | `dws chat +messages-send --as user --open-dingtalk-id --text "<内容>"` | | "发到XX群" | `dws chat +chat-search --query "<群名>"` → `dws chat +messages-send --as user --chat-id --text "<内容>"` | -| "建群" / "拉人进群" | `dws chat +chat-create` / `dws chat group members add` | -| "改群名" / "踢人" | `dws chat group rename` / `dws chat group members remove --yes`(踢人不可逆,确认目标后加 --yes;踢群主会被 CLI 拦截,需先 `transfer-owner`)| +| "建群" / "拉人进群" | `dws chat group create` / `dws chat group members add` | +| "改群名" / "踢人" | `dws chat group rename` / `dws chat group members remove --yes`(踢人不可逆,先确认目标) | | "@我消息" | `dws chat +at-me` | | "查群聊记录" | `dws chat +chat-messages --group ` | | "收藏/取消收藏这条消息" | `dws chat +flag-create` / `dws chat +flag-cancel` | | "查看我收藏的消息" | `dws chat +flag-list` | | "用机器人发消息" | `dws chat +messages-send --as bot --robot-code --chat-id --text "<内容>"` | | "Webhook 推一条" | `dws chat +messages-send --as webhook --webhook-token --text "<内容>"` | -| "编辑 / 修改已发送消息" | `dws chat message edit --conversation-id --msg-id --text "<新内容>"` | -| "撤回我发的消息" | `dws chat message recall`(撤回当前用户发送的消息)| -| "撤回机器人消息" | `dws chat message recall-by-bot --robot-code --group --keys `(撤回机器人发的)| -| "把普通群升级为外部群" | `dws chat group upgrade-to-external --group --dry-run`,确认后加 `--yes` | -| "清除我的群昵称" | `dws chat group update-nick --group `(省略 `--nick`) | -| "这个会话属于哪些分组" | `dws chat category list-by-conv --group ` | -| "批量查询分组信息" | `dws chat category batch-info --category-ids ,` | +| "撤回消息" | `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` | -> **注**:`chat message send` 的 `--title` 可选(不传时用正文首行作标题);`send-by-bot` / `send-by-webhook` 的 `--title` 必填。 +## 标准 SOP(必遵流程) -## 评测高频硬约束 +> 命中以下意图**必须**按对应 SOP 顺序执行;**禁止**跳步、替换命令、编造 ID。每条命令必须带 `--format json`,执行后必须按"解析"步取真实字段(`openDingTalkId` / `openConversationId` / `openMessageId`)。 -- `chat message edit` 必须同时提供会话 ID 与消息 ID;`--text` 和 `--content` 二选一,`--title` 只与 `--text` 搭配。 -- `chat group upgrade-to-external` 只适用于 `NORMAL_GROUP`,不可逆且仅群主可执行。先 `--dry-run`,获得用户明确确认后再加 `--yes`。 -- `chat group update-nick` 不传 `--nick` 表示清除当前用户的群昵称,不是参数缺失。 -- 会话分组 ID 是数值 ID;按会话反查用 `category list-by-conv`,按多个分组 ID 查详情用 `category batch-info`。 +### SOP-1 发消息(send-message) + +**触发**:发消息/单聊/通知某人/发到群里。 + +1. **解析收件人(必须)**:人名 → 先 `dws aisearch person --keyword "<姓名>" --dimension name --format json` 取 `openDingTalkId`(优先)或 `userId`;群名 → 先 `dws chat search --query "<群名>" --format json` 取 `openConversationId`。 +2. **执行(必须)**:单聊 `dws chat message send --open-dingtalk-id --text "<内容>" --format json`(只有拿不到 `openDingTalkId` 时才用 `--user `);群聊 `dws chat message send --group --text "<内容>" --format json`。 +3. **验证(必须)**:发送接口成功时可能只返回 `result.openTaskId`,它可用于确认提交成功,但**不是撤回所需消息 ID**。需要撤回时必须按 SOP-7 用带 `--time` 的 `message list` 回查,取得真实 `openMessageId`;返回非 `success` 必须如实报错,不要谎报已发。 + +**禁止**:把人名/群名直接当 ID 传入、跳过 `aisearch person`/`chat search` 解析、跳过 `--format json`、未发送成功就答复"已发送"。 + +### SOP-2 建群(create-group) + +**触发**:建群/拉人进群/新建讨论组。 + +1. **解析成员(必须)**:对每个成员 `dws aisearch person --keyword "<姓名>" --dimension name --format json` 取 `userId`,多人英文逗号拼接。 +2. **执行(必须)**:`dws chat group create --name "<群名>" --users --format json`;外部群加 `--type EXTERNAL`,话题圈加 `--thread`。 +3. **验证(必须)**:从返回取 `openConversationId`,可用 `dws chat search --query "<群名>" --format json` 复核。 + +**禁止**:跳过成员 userId 解析直接传姓名、编造 `openConversationId`。 + +### SOP-3 Webhook 推送(send-by-webhook) + +**触发**:用机器人群 webhook 推一条消息。 + +1. **执行(必须)**:`dws chat message send-by-webhook --token --title "<标题>" --text "<内容>" --format json`。 +2. **@ 人(必须)**:需要 @ 时,`--text` 中**必须**先包含对应 `@userId` / `@手机号` / `@10`,再配合 `--at-users` / `--at-mobiles` / `--at-all`;否则 @ 不生效。 + +**禁止**:只传 `--at-users` 而 `--text` 里不含 `@<标识>`。 + +### SOP-4 共同群查询(search-common-group) + +**触发**:"我和 XX 的共同群"。 + +1. **取昵称(必须)**:先 `dws contact user get-self --format json` 取自己昵称;对方昵称从历史/上下文取,拿不到必须先问用户。 +2. **执行(必须)**:`dws chat search-common --nicks "<昵称1>,<昵称2>" --limit 20 --cursor 0 --format json`;`hasMore=true` 时**必须**用 `nextCursor` 翻页,不要停在第一页。 + +**禁止**:跳过昵称解析、忽略 `hasMore` 不翻页。 + +### SOP-5 红点 / 未读管理(manage-red-point) + +**触发**:标记未读/清除红点/全部已读。 + +1. **执行(必须)**:标记某会话未读 `dws chat mark-unread --conversation-id --format json`;清除某会话红点 `dws chat clear-red-point --conversation-id --format json`;全部已读 `dws chat clear-all-red-point --format json`。 +2. **取会话 ID(必须)**:`openConversationId` 拿不准时先 `dws chat group list-all --format json` 或 `dws chat search --query "<群名>" --format json`,**禁止**编造。 + +**禁止**:未确认会话就批量"全部已读"(破坏性,必须先与用户确认)。 + +### SOP-6 特别关注消息(focus-messages) + +**触发**:"特别关注的人最近发了什么/聊了什么"。 + +1. **执行(必须)**:`dws chat message list-focused --limit 50 --format json`,直接基于返回答复。 +2. **边界(必须)**:只有用户终点是"我关注了谁"这种**人员列表**时,才切 `dingtalk-contact` 关系查询。 + +**禁止**:用普通 `message list` 冒充 focused、把人员列表需求硬塞进 chat。 + +### 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` -- 要发本地图片/文件 → 直接用 `dws chat message send --msg-type file --file-path <本地路径>`;图片会作为可下载的文件附件发送,不会内联渲染。只有上游已提供有效 mediaId 时才用 `--msg-type image --media-id`,DWS CLI 不能把本地文件转换成 mediaId -- 紧急升级(应用内/短信/电话)→ 切到 `dingtalk-ding` +- 要发图片/文件 → 先 `dt_media_upload` 上传 → `python scripts/extract_media_id.py ""` 提取 mediaId → 再用 `--media-id` +- 紧急升级(应用内/短信/电话)→ 切到 `dingtalk-misc`(`references/ding.md`) - 发邮件 → 切到 `dingtalk-mail` +## 局部意图与短流程 + +- [局部意图消歧](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 bac5ddf7..1f043cd6 100644 --- a/skills/multi/dingtalk-chat/references/01-messaging.md +++ b/skills/multi/dingtalk-chat/references/01-messaging.md @@ -1,16 +1,13 @@ # 消息沟通 -> **安全红线**:收件人搜索返回多条/零条结果、或名称不完全匹配时,**必须列出候选项让用户确认**,严禁自动选择第一条。即使支持机器人消息撤回 `chat message recall-by-bot`,对方可能已读,发错代价高。禁止自行发送"测试消息"验证通道。 - -> lite recipe 见 `dws-shared/references/best_practices/_common/lite-recipes.md`。 +> lite recipe(`send-message`、`create-group`、`send-by-webhook`、`search-common-group`、`focus-messages`)见 [lite-recipes.md](./lite-recipes.md)。 | Recipe | 行动指南(固定路线) | |--------|-------------------| -| query-group-chat | **优先**:`python scripts/chat_export_messages.py --query "<群名>" --time "" [--no-forward] [--limit N] [--output messages.json]`(自动搜群+翻页+导出)
备选:1. `chat search --query "<群名>"` → 取 `openConversationId`
2. `chat message list --group --time ""` → 取消息列表
3. **翻页**:`hasMore=true` 时取本页最后 `createTime` 作为下次 `--time`,重复至 `hasMore=false`
4. `--direction older` 拉给定时间**之前**的消息(`newer` 拉之后)
5. 合并全部消息后总结 | -| query-private-chat | **优先**:`python scripts/chat_history_with_user.py --name "<姓名>" --time "" [--no-forward] [--limit N] [--output messages.json]`(自动搜人+翻页+导出)
备选:1. `aisearch person --keyword "<姓名>" --dimension name` → 取 `userId`
2. `chat message list-direct --user --time ""` → 取消息列表
3. **翻页**:同 query-group-chat
4. 合并全部消息后总结 | -| escalate-ding | 三级升级:
1. `ding message send --robot-code --type app --users --content "<内容>"`(必填项见 `dingtalk-ding/references/ding.md`)
2. `chat message send --group --text "<内容>"` 群里提醒(可选 `--title` / `@` 见 [chat.md](./chat.md))
3. `todo task create --title "<标题>" --executors --priority 40` 建紧急待办
前置:`aisearch person --keyword "<姓名>" --dimension name` → 取 `userId`;`chat search --query "<群名>"` → 取 `openConversationId` | +| query-group-chat | **优先**:`python scripts/chat_export_messages.py --query "<群名>" --time "" [--no-forward] [--limit N] [--output messages.json]`(自动搜群+翻页+导出)
备选:1. `chat search --query "<群名>"` → 取 `openConversationId`
2. `chat message list --group --time ""` → 取消息列表
3. **翻页**:`hasMore=true` 时取本页最后 `createTime` 作为下次 `--time`,重复至 `hasMore=false`
4. `--forward=false` 拉给定时间**之前**的消息
5. 合并全部消息后总结 | +| query-private-chat | **优先**:`python scripts/chat_history_with_user.py --name "<姓名>" --time "" [--no-forward] [--limit N] [--output messages.json]`(自动搜人+翻页+导出)
备选:1. `aisearch person --keyword "<姓名>" --dimension name` → 取 `userId`
2. `chat message list --user --time ""` → 取消息列表
3. **翻页**:同 query-group-chat
4. 合并全部消息后总结 | +| escalate-ding | 三级升级:
1. `ding message send --robot-code --type app --users --content "<内容>"`(必填项见 `dingtalk-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/references/contact.md` 意图判断)
翻页:`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` | +| 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 ` | diff --git a/skills/multi/dingtalk-chat/references/chat.md b/skills/multi/dingtalk-chat/references/chat.md index 19768008..9bd95676 100644 --- a/skills/multi/dingtalk-chat/references/chat.md +++ b/skills/multi/dingtalk-chat/references/chat.md @@ -1,10 +1,12 @@ # 会话与群聊 (chat) 命令参考 -## 命令总览 +> **渐进式文档**:本文件为路由层(索引 + 意图判断 + 全局约束),详细参数、示例和踩坑说明在 [chat/](./chat/) 目录下按场景加载。 + +> 命令别名:`dws im` 等价于 `dws chat`。 ## Shortcut 优先路由 -常见 Agent 意图优先使用公开 `+` Shortcut;下面的原子命令章节保留给需要特定原始返回结构、兼容参数或 Shortcut 未覆盖字段的场景。执行前用 `dws schema --cli-path "chat +" --format json` 读取最终参数、约束和确认语义。 +常见 Agent 意图优先使用公开 `+` Shortcut;原子命令保留给需要特定原始返回结构、兼容参数或 Shortcut 未覆盖字段的场景。执行前用 `dws schema --cli-path "chat +" --format json` 读取最终参数、约束和默认确认语义。 | 意图 | 首选 | |---|---| @@ -16,2227 +18,346 @@ | 读取已知 thread/topic 的全部回复 | `dws chat +thread-replies ...` | | 下载单个 mediaId/fileId | `dws chat +messages-resource-download ...` | -- `+messages-send` 只暴露下层真实支持的身份能力:user 支持文本/Markdown、已有 mediaId 图片、本地文件和幂等键;bot 支持群聊或批量单聊文本/Markdown;webhook 的目标由 token 所在群决定。 -- `+messages-send` 会自动规范化并补齐 @ 占位符。user 使用 `<@id>` / `<@all>`;bot/webhook 使用 `@id` / `@手机号` / `@all`。声明 `--at-*` / `--at-all` 即可,不要为统一 Shortcut 手工拼 `@10`。 -- `+search-msg --page-all` 连续翻页并默认按消息 ID 批量富化;任何续页或富化失败都会保留已取得结果并返回逐项失败 ledger。 -- `+at-me`、`+chat-messages`、`+messages-mget`、`+search-msg`、`+thread-replies` 可用 `--download-resources` 下载资源。引用、回复、合并转发中的资源使用结果 `resourceRefs` 自带的子消息 `messageId`;仅当子消息缺会话 ID 时继承父消息 `openConversationId`。 -- 上述查询 Shortcut 因可选本地下载能力按命令级 Schema 发布为 `write/user_required`;`+messages-resource-download` 始终写本地。确认相对输出目录和覆盖策略后才加 `--yes`,默认拒绝覆盖已有文件。 -- 下载器仅接受经审查的钉钉与公网 OSS HTTPS 地址并逐跳校验重定向;跨主机时不会转发下层提供的请求头。新官方域名被拒绝时记录错误中的 host 供审查,不要放宽为任意 HTTPS。 - -### group (群组管理) - -#### 创建群 — 当前登录用户自动成为群主 -``` -Usage: - dws chat group create [flags] -Example: - dws chat group create --name "Q1 项目冲刺群" --users userId1,userId2,userId3 -Flags: - --users string 成员 userId 列表,用户本身会自动加入,无需包含,逗号分隔,不超过20个 (必填) - --name string 群名称 (必填) -``` - -#### 查看群成员列表 — 分页查询指定群聊的成员 -``` -Usage: - dws chat group members [flags] -Example: - dws chat group members --id -Flags: - --cursor string 分页游标,首次从 0 开始 - --id string 群 ID / openconversation_id (必填) -``` - -#### 添加群成员 — 向指定群聊添加成员,需传入群 ID 与用户 ID 列表 -``` -Usage: - dws chat group members add [flags] -Example: - dws chat group members add --id --users userId1,userId2 -Flags: - --id string 群 ID / openconversation_id (必填) - --users string 要添加的用户 userId 列表,逗号分隔 (必填) -``` - -#### 移除群成员 — 从指定群聊中移除成员,需传入群 ID 与待移除的用户 ID 列表 -``` -Usage: - dws chat group members remove [flags] -Example: - dws chat group members remove --id --users userId1,userId2 -Flags: - --id string 群 ID / openconversation_id (必填) - --users string 要移除的用户 userId 列表,逗号分隔 (必填) -``` - -> 移除对象是群主时 CLI 会拦截并报错,提示先 `chat group transfer-owner` 转让群主(查不到群主时放行)。 - -#### 将机器人添加到群中 — 将自定义机器人添加到当前用户有管理权限的群聊中,如果没有权限则会报错 -``` -Usage: - dws chat group members add-bot [flags] -Example: - dws chat group members add-bot --robot-code --id -Flags: - --id string 群聊 openConversationId (必填) - --robot-code string 机器人 Code (必填) -``` - -#### 从群内移除机器人 — 将指定机器人从群聊中移除,需要群管理员或群主权限 -``` -Usage: - dws chat group members remove-bot [flags] -Example: - dws chat group members remove-bot --id --bot-id - # 查询群 ID: dws chat search --query "群名" - # 查询群内机器人: dws chat group bots --group -Flags: - --id string 群聊 openConversationId (必填) - --bot-id string 机器人 openBotId (必填) -``` - -#### 根据成员 ID 批量查询群成员详情 — 传入成员 openDingTalkId 列表批量查询 -``` -Usage: - dws chat group members list-by-ids [flags] -Example: - dws chat group members list-by-ids --id --users openDingTalkId1,openDingTalkId2 - # 查询群 ID: dws chat search --query "群名" - # 查询 openDingTalkId: dws contact user search --query "姓名" -Flags: - --id string 群 ID / openConversationId (必填) - --users string 成员 openDingTalkId 列表,逗号分隔 (必填) -``` - -#### 更新群名称 -``` -Usage: - dws chat group rename [flags] -Example: - dws chat group rename --id --name "新群名" -Flags: - --id string 群 ID / openconversation_id (必填) - --name string 修改后的群名称 (必填) -``` - -#### 根据群号获取群聊信息 — 当用户只提供了数字群号而非 openConversationId 时,用此命令转换 -``` -Usage: - dws chat group get-by-group-id [flags] -Example: - dws chat group get-by-group-id --group-id 12345678 - # 群号为数字类型的群ID -Flags: - --group-id int 群号 (必填,数字类型) -``` - -#### 转让群主 — 将群主身份转让给群内其他成员 -``` -Usage: - dws chat group transfer-owner [flags] -Example: - dws chat group transfer-owner --group --new-owner - dws chat group transfer-owner --group --user - # 查询群 ID: dws chat search --query "群名" - # 查询人员: dws aisearch person --keyword "姓名" --dimension name -Flags: - --group string 群聊 openConversationId (必填) - --new-owner string 新群主 openDingTalkId - --user string 新群主 userId -``` - -#### 将普通群升级为外部群 — 不可逆,仅群主可执行 -``` -Usage: - dws chat group upgrade-to-external [flags] -Example: - dws chat group upgrade-to-external --group --dry-run - dws chat group upgrade-to-external --group --yes - dws chat group upgrade-to-external --group --extension '{"source":"dws"}' --yes -Flags: - --group string 待升级普通群的 openConversationId (必填) - --extension string 预留扩展字段 JSON 对象;对象值必须是字符串 (可选) - --dry-run 预览操作,不实际升级 - --yes 确认执行不可逆升级 -``` - -仅 `NORMAL_GROUP` 普通群可升级;新建外部群使用 `chat group create --type EXTERNAL`。正式执行前必须确认目标群和影响,再传 `--yes`。 - -#### 获取群邀请链接 — 获取指定群聊的邀请加入链接 - -可选 --expires-seconds 指定链接有效期(秒),0 表示永久有效,不传则使用服务端默认值。 -``` -Usage: - dws chat group invite-url [flags] -Example: - dws chat group invite-url --group - dws chat group invite-url --group --expires-seconds 86400 - dws chat group invite-url --group --expires-seconds 0 - # 查询群 ID: dws chat search --query "群名" -Flags: - --group string 群聊 openConversationId (必填) - --expires-seconds int64 链接有效期(秒),0 表示永久有效,不传使用服务端默认值 -``` - -#### 分享群聊链接到会话 — 将指定群的邀请链接分享到另一个会话或单聊用户 - -`--target` 和 `--receiver` 二选一:`--target` 指定目标会话,`--receiver` 指定单聊用户。 - -``` -Usage: - dws chat group share-invite [flags] -Example: - dws chat group share-invite --source <被分享群openConversationId> --target <目标会话openConversationId> - dws chat group share-invite --source <被分享群openConversationId> --receiver <接收者openDingTalkId> - dws chat group share-invite --source --target --expires-seconds 86400 - # 查询群 ID: dws chat search --query "群名" -Flags: - --source string 被分享群的 openConversationId (必填) - --target string 接收分享消息的会话 openConversationId(与 --receiver 二选一) - --receiver string 接收分享消息的单聊用户 openDingTalkId(与 --target 二选一) - --expires-seconds int64 链接有效期(秒),0 表示永久有效,不传使用服务端默认值 - --uuid string 消息幂等键(可选) -``` - -#### 退出群聊 — 当前用户退出指定群聊 -``` -Usage: - dws chat group quit [flags] -Example: - dws chat group quit --group - # 查询群 ID: dws chat search --query "群名" -Flags: - --group string 群聊 openConversationId (必填) -``` -> ⚠️ 唯一群主 quit 会直接成功,产生无主群,之后对该群做 dismiss / members 等管理操作会报 listRoles null(11056) 无法管理。与 `chat group members remove` 移除群主有本地拦截不同,quit 无此防护。作为唯一群主想退群,应先 `chat group transfer-owner` 转让群主,或 `chat group dismiss` 解散群。 - -#### 更新群头像 — 更新指定群聊的群头像 -``` -Usage: - dws chat group update-icon [flags] -Example: - dws chat group update-icon --group --icon-media-id - # 查询群 ID: dws chat search --query "群名" -Flags: - --group string 群聊 openConversationId (必填) - --icon-media-id string 群头像 mediaId (必填) -``` - -> `--icon-media-id` 必须是 `@` 开头的、由可信上游提供的媒体 ID,非法格式会本地报错。 -> ⚠️ 本地格式校验只查前缀。格式合法但不真实存在的 mediaId 服务端仍会静默返回成功,头像并不会真正更新。务必使用真实上游媒体上传能力拿到有效 mediaId;DWS CLI 不提供本地文件到 mediaId 的上传命令。 - -#### 更新群设置 — 更新指定群聊的设置项 - ---setting-key 指定设置项,--status 指定值(0=关闭,1=开启)。 - -支持的 settingKey: - authority、joinValidation、onlyAdminCanAtAll、searchable、addFriendForbidden、 - toolbarStatus、pluginCustomizeVerify、onlyAdminCanDING、allMembersCanCreateMcsConf、 - onlyAdminCanSetMsgTop、onlyAdminCanPinMsg、onlyAdminCanSendFile、 - allMembersCanCreateCalendar、groupEmailDisabled、groupRedEnvelopeSwitch、 - groupLiveAuthority、groupBillAuthority -``` -Usage: - dws chat group update-settings [flags] -Example: - dws chat group update-settings --group --setting-key searchable --status 1 - dws chat group update-settings --group --setting-key onlyAdminCanAtAll --status 0 - # 查询群 ID: dws chat search --query "群名" -Flags: - --group string 群聊 openConversationId (必填) - --setting-key string 群设置项 key (必填) - --status int 设置值: 0=关闭, 1=开启 (必填) -``` - -#### 设置群备注 — 给群设置只有自己可见的备注标题 -``` -Usage: - dws chat group update-alias [flags] -Example: - dws chat group update-alias --group --alias-title "项目A群" - # 查询群 ID: dws chat search --query "群名" -Flags: - --group string 群聊 openConversationId (必填) - --alias-title string 群备注标题 (必填) -``` - -#### 设置或清除我在群内的群昵称 -``` -Usage: - dws chat group update-nick [flags] -Example: - dws chat group update-nick --group --nick "我的群昵称" - dws chat group update-nick --group - # 查询群 ID: dws chat search --query "群名" -Flags: - --group string 群聊 openConversationId (必填) - --nick string 个人群昵称;不传则清除当前群昵称 -``` - -#### 查看群内所有机器人 — 获取指定群聊中的所有机器人列表 -``` -Usage: - dws chat group bots [flags] -Example: - dws chat group bots --group - # 查询群 ID: dws chat search --query "群名" -Flags: - --group string 群聊 openConversationId (必填) -``` - -#### 解散群聊 — 解散指定群聊,操作不可逆,需要群主权限 -``` -Usage: - dws chat group dismiss [flags] -Example: - dws chat group dismiss --group - # 查询群 ID: dws chat search --query "群名" -Flags: - --group string 群聊 openConversationId (必填) -``` - -#### 设置新成员入群可查看历史消息选项 — 控制新加入成员可见的历史消息范围 -``` -Usage: - dws chat group set-history [flags] -Example: - dws chat group set-history --group --option RECENT_100 - dws chat group set-history --group --option FORBIDDEN - # 查询群 ID: dws chat search --query "群名" -Flags: - --group string 群聊 openConversationId (必填) - --option string 可见范围: FORBIDDEN | RECENT_100 | ALL (必填) - -注意: - - FORBIDDEN:禁止查看历史消息(默认安全策略) - - RECENT_100:可查看最近 100 条消息(最常用) - - ALL:可查看全部历史消息(开放性最高) -``` - -#### 拉取我创建/管理的群 — 查询当前用户作为群主或管理员的群列表 - -可通过 --role 过滤角色:OWNER 仅群主、ADMIN 仅管理员,不传则返回全部。可通过 --limit 限制返回数量,不传则返回所有符合条件的群。 -``` -Usage: - dws chat group list-my-groups [flags] -Example: - dws chat group list-my-groups - dws chat group list-my-groups --role OWNER - dws chat group list-my-groups --role ADMIN --limit 10 -Flags: - --role string 角色过滤: OWNER(仅群主) / ADMIN(仅管理员),不传返回全部 - --limit int 最多返回群数量,不传返回全部 - -注意: - - 底层先拉取最近 1000 条会话,剔除单聊和话题圈后筛选出群主/管理员的群 - - 内部群会校验 orgId 归属 - - 不传 --role 时返回群主 + 管理员的所有群 -``` - -### group notice (群公告管理) - -#### 发布群公告 — 在指定群聊中发布群公告,支持 Markdown、定时发布 - -``` -Usage: - dws chat group notice create [flags] -Example: - dws chat group notice create --group --content "今晚 22 点系统维护,请提前保存工作内容" - dws chat group notice create --group --content "# 重要通知\n请大家查收" --sticky --send-ding - dws chat group notice create --group --content "明早九点例会" --run-at "2026-07-03T09:00:00+08:00" -Flags: - --group string 群聊 openConversationId (必填) - --content string 公告正文,Markdown 格式 (必填) - --sticky 是否吊顶置顶(默认 false) - --send-ding 是否发 DING 提醒(默认 false) - --run-at string 定时发布时间 ISO-8601(传入则定时发布) -``` - -#### 修改群公告 — 整体替换指定群公告的内容 -``` -Usage: - dws chat group notice edit [flags] -Example: - dws chat group notice edit --group --notice-id --content "更新后的公告内容" -Flags: - --group string 群聊 openConversationId (必填) - --notice-id string 群公告 dataId (必填) - --content string 公告新正文,Markdown 格式 (必填) - --sticky 是否吊顶置顶 - --send-ding 是否发 DING 提醒 -``` - -#### 查看群公告详情 -``` -Usage: - dws chat group notice get [flags] -Example: - dws chat group notice get --group --notice-id -Flags: - --group string 群聊 openConversationId (必填) - --notice-id string 群公告 dataId (必填) -``` - -#### 查看群公告列表 -``` -Usage: - dws chat group notice list [flags] -Example: - dws chat group notice list --group - dws chat group notice list --group --limit 20 --cursor - dws chat group notice list --group --scheduled -Flags: - --group string 群聊 openConversationId (必填) - --limit int 每页返回数量(默认 10,最大 100) - --cursor string 分页游标 - --scheduled 是否查询定时公告列表 -``` - -### group-role (群身份管理) - -#### 查看群身份列表 — 拉取指定群聊的自定义群身份列表 -``` -Usage: - dws chat group-role list [flags] -Example: - dws chat group-role list --group -Flags: - --group string 群聊 openConversationId (必填) -``` - -#### 添加群身份 — 在指定群中创建一个新的自定义群身份 -``` -Usage: - dws chat group-role add [flags] -Example: - dws chat group-role add --group --name "管理员" -Flags: - --group string 群聊 openConversationId (必填) - --name string 群身份名称 (必填) -``` - -#### 更新群身份名称 — 修改指定群身份的名称 -``` -Usage: - dws chat group-role update [flags] -Example: - dws chat group-role update --group --role-id --name "新名称" -Flags: - --group string 群聊 openConversationId (必填) - --role-id string 群身份 openRoleId,由 group-role list 返回 (必填) - --name string 群身份新名称 (必填) -``` - -#### 删除群身份 — 删除指定群聊中的某个自定义群身份 -``` -Usage: - dws chat group-role remove [flags] -Example: - dws chat group-role remove --group --role-id -Flags: - --group string 群聊 openConversationId (必填) - --role-id string 群身份 openRoleId,由 group-role list 返回 (必填) -``` - -#### 设置用户群身份 — 覆盖指定用户在群中的全部群身份(传空则清除所有身份) -``` -Usage: - dws chat group-role set-user [flags] -Example: - dws chat group-role set-user --group --user --role-ids roleId1,roleId2 - # 查询人员: dws aisearch person --keyword "姓名" --dimension name - # 查询 role-id: dws chat group-role list --group -Flags: - --group string 群聊 openConversationId (必填) - --user string 用户 userId(必填) - --role-ids string 群身份 openRoleId 列表,逗号分隔 (必填),传空字符串则清除该用户所有群身份 -``` - -#### 移除用户的指定群身份 — 从用户身上移除指定的群身份(不影响其他群身份) - -> ⚠️ 当前不可用:该命令当前调用常失败(服务端返回 1002 系统繁忙),多为服务端侧限制。需要清除某人身份时,用 `chat group-role set-user --role-ids ""`(传空 role-ids 覆盖为无身份)兜底;但 set-user 会清掉该用户的**全部**身份,无法只移除其中一个。 -``` -Usage: - dws chat group-role remove-user [flags] -Example: - dws chat group-role remove-user --group --user --role-ids roleId1,roleId2 -Flags: - --group string 群聊 openConversationId (必填) - --user string 用户 userId(必填) - --role-ids string 要移除的群身份 openRoleId 列表,逗号分隔 (必填) -``` - -#### 查询群成员的群身份 — 查询指定群成员当前持有的所有群身份 -``` -Usage: - dws chat group-role query-user [flags] -Example: - dws chat group-role query-user --group --user -Flags: - --group string 群聊 openConversationId (必填) - --user string 用户 userId(必填) -``` - -### search (搜索群聊) - -#### 根据关键词搜索群聊 — 分页返回匹配群聊列表 - -hasMore=true 时用返回的 nextCursor 作为下次 --cursor 继续翻页。 - -**注意:** -1. query 不要拆分得太细,应使用群名称中连续的核心词作为关键词(如群名"项目冲刺群"应搜"项目冲刺"而非拆成"项目"+"冲刺"分别搜索)。 -2. 当搜索结果返回多个群聊时,应列出候选群让用户确认目标群聊,不要自行假定并直接进行后续操作。 - -``` -Usage: - dws chat search [flags] -Example: - dws chat search --query "项目冲刺" - dws chat search --query "项目冲刺" --limit 20 --cursor 0 -Flags: - --query string 搜索关键词 (必填) - --limit int 每页返回数量(默认 20) - --cursor string 分页游标(默认 "0",翻页传 nextCursor) - --exclude-muted 是否排除已设置免打扰的群聊(默认 false) -``` - -### data-auth (数据授权) - -#### 跨组织数据授权 — 授予 chat 跨组织消息拉取权限 - -用于跨组织消息拉取等数据访问场景,不用于发送、撤回、群管理等操作。该命令调用 `chat_permission_grant`,固定使用数据授权类别:`scope=chat.data:cross-org`、`grantCategory=data`。 - -``` -Usage: - dws chat data-auth cross-org [flags] - -Example: - dws chat data-auth cross-org --target-org-id 439446171 --format json - dws chat data-auth cross-org --target-org-id 439446171 --agentCode wukong --grant-type timed --ttl 24h --format json - dws chat data-auth cross-org --all --grant-type timed --ttl 24h --format json - -Flags: - --target-org-id string 目标组织 ID(与 --all 二选一) - --all 授权所有目标组织 - --agentCode string Agent 标识,默认 wukong - --grant-type string 授权策略: once|session|timed|permanent (default "timed") - --ttl string timed 授权有效期,如 1h/4h/24h/7d (default "24h") - --session-id string session 授权的会话标识 - -注意: - - --target-org-id 和 --all 必须且只能指定一个。 - - --grant-type=session 时必须传 --session-id。 - - --grant-type=timed 时必须传 --ttl。 -``` - -### message (会话消息管理) - -#### 拉取会话消息内容 — 拉取指定群聊或单聊的会话消息内容 - ---group 指定群聊,--user 指定单聊用户(通过 userId),--open-dingtalk-id 指定单聊用户(通过 openDingTalkId),三者互斥。用 --direction 控制时间方向:newer=从给定时间往现在拉,older=从给定时间往以前拉。hasMore=true 时用结果中的边界 createTime 作为下次 --time 翻页。引用回复消息会带 `quotedMessage`;被引用的原消息是合并转发或图片时,其类型与内容也会保留在引用上下文中。 -``` -Usage: - dws chat message list [flags] -Example: - dws chat message list --group --time "2025-03-01 00:00:00" - dws chat message list --user --time "2025-03-01 00:00:00" --limit 50 - dws chat message list --open-dingtalk-id --time "2025-03-01 00:00:00" --limit 50 - dws chat message list --group --time "2025-03-01 00:00:00" --direction older -Flags: - --direction string 时间方向: newer=从给定时间往现在拉,older=从给定时间往以前拉(推荐) - --group string 群聊 openconversation_id(群聊时必填) - --limit int 返回数量,不传则不限制 - --time string 开始时间,格式: yyyy-MM-dd HH:mm:ss (必填) - --user string 单聊用户 userId(单聊时与 --open-dingtalk-id 二选一) - --open-dingtalk-id string 单聊用户 openDingTalkId(单聊时与 --user 二选一,适用于三方应用等无法获取 userId 的场景) - -注意: - - --group、--user、--open-dingtalk-id 三者互斥,只需指定其一:群聊用 --group,单聊用 --user 或 --open-dingtalk-id - - --user 和 --open-dingtalk-id 都是发起单聊消息拉取,区别在于用不同格式的用户标识: - - --user 传 userId(企业内部应用常用) - - --open-dingtalk-id 传 openDingTalkId(三方应用或跨组织场景常用,无法获取 userId 时使用) - - --group 的别名: --id, --chat, --conversation-id (均可替代 --group) - - 翻页:hasMore=true 时,用结果中的边界 createTime 作为下次 --time - - 处理引用回复时读取 quotedMessage,不要只看回复正文;合并转发与图片引用的原消息内容也在该上下文中 - - 话题圈消息拉取流程:如果返回的会话消息中包含 openConvThreadId 字段,说明是话题类消息。要获取完整的话题内容,需要两步操作:(1) 先通过 dws chat message list 拉取话题主消息(即话题帖子本身);(2) 再调用 dws chat message list-topic-replies --group --topic-id 分页拉取该话题下的所有回复消息。只有话题主消息 + 回复列表合在一起,才是一条话题的完整内容。 -``` - -#### 以当前用户身份发送消息 — --group 群聊 / --user 或 --open-dingtalk-id 单聊 - -**重要:该接口会真实发送消息到目标会话,不可用于测试或试探性调用。调用前必须确认消息内容和接收对象无误。** - ---group 指定群聊 openConversationId 发群消息;--user 指定用户 userId 发单聊;--open-dingtalk-id 指定用户 openDingTalkId 发单聊。三者只能选其一,不能同时指定。纯文本/Markdown 单聊传 --user 时直接走 userId 发送能力,不需要先手动查询 openDingTalkId。推荐使用 --text flag 传递消息内容(也支持位置参数)。可选 --title 作为消息标题。 -若用户只提供了数字群号而非 openConversationId,需先调用 `chat group get-by-group-id` 将群号转为 openConversationId,再传入 --group。 ---群聊时可选 --at-all @所有人,或 --at-open-dingtalk-ids 指定成员(仅群聊时生效)。 ---本地图片、文件、音频或视频统一用 --msg-type file --file-path;图片会作为可下载的文件附件发送。--msg-type image --media-id 仅用于上游已经提供有效 mediaId 的场景。 -``` -Usage: - dws chat message send [flags] [] - -富媒体消息路由: - 场景 → msgType → 发送参数 - 本地图片/文件/音频/视频 → file → --file-path <本地路径> - 上游已提供有效 mediaId 的内联图片 → image → --media-id - - 注意:本地 .png/.jpg 也按 file 发送,接收方看到的是可下载附件;DWS CLI 不能把本地文件转换成 mediaId。 - -Example: - dws chat message send --group --text "hello" - dws chat message send --user --text "请查收" - dws chat message send --open-dingtalk-id --text "请查收" - dws chat message send --group "hello" - dws chat message send --group --title "周报提醒" --text "请大家本周五前提交周报" - # 幂等发送(24h 内相同 uuid 不重复投递) - dws chat message send --group --text "hello" --uuid "unique-id-123" - dws chat message send --group --at-all "<@all> 请大家注意" - dws chat message send --group --at-open-dingtalk-ids openDingTalkId1,openDingTalkId2 "<@openDingTalkId1> <@openDingTalkId2> 请查收" - # 本地图片/文件/音频/视频统一作为 file 附件发送 - dws chat message send --group --msg-type file --file-path ./screenshot.png - dws chat message send --group --msg-type file --file-path ./report.pdf - dws chat message send --group --msg-type file --file-path ./recording.mp3 - dws chat message send --group --msg-type file --file-path ./demo.mp4 - # 仅当上游已有有效 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(仅 msgType=image) - --msg-type string 消息类型: image/file/audio/video(本地文件统一使用 file;image 仅配合已有 mediaId) - --dentry-id int64 已有钉盘文件 dentryId(兼容参数,非本地文件发送的默认路径) - --space-id int64 已有钉盘文件空间 ID(元数据兼容模式) - --file-name string 已有钉盘文件名(元数据兼容模式) - --file-type string 已有钉盘文件类型/扩展名(元数据兼容模式) - --file-path string 本地文件路径(本地图片/文件/音视频配合 msgType=file 使用) - --file-size int64 已有钉盘文件大小,单位字节(元数据兼容模式) - --uuid string 幂等 UUID,相同 uuid 在 24h 内不会重复发送(可选) - --ai-tag 消息是否带 AI 发送角标(可选,默认 true) - -注意: - - --text 和位置参数二选一,--text 优先 - - --group、--user、--open-dingtalk-id 三者互斥,只需指定其一:群聊用 --group,单聊用 --user 或 --open-dingtalk-id - - 纯文本/Markdown 单聊发送时 `--user` 和 `--open-dingtalk-id` 都可用;传 `--user` 时直接走 userId 发送能力 - - --group 的别名: --id, --chat, --conversation-id (均可替代 --group) - - --at-all 和 --at-open-dingtalk-ids 仅在 --group 群聊时生效,单聊时无效;当设置--at-all时,消息内容中一定要包含对应的占位符<@all>;当设置--at-open-dingtalk-ids openDingTalkId1,openDingTalkId2时,消息内容中一定要包含对应格式的占位符<@openDingTalkId1> <@openDingTalkId2> - - **换行符**:消息内容按 Markdown 渲染,换行有两层要求,缺一不可: - 1. 必须使用**真实换行符**(Unicode `U+000A`),而非字面量字符串 `\n`(反斜杠 + 字母 n)。程序或大模型构造参数时,须确保已正确反转义;否则全部内容会渲染在同一行 - 2. Markdown 规范下**单个换行不产生换行效果**。需要换行时请使用:段落分隔(连续两个真实换行符 `\n\n`)、行尾两个空格 + 真实换行符(硬换行 `
`),或直接写 HTML 的 `
` 标签 - - 本地图片、文档、压缩包、音频和视频统一使用 `--msg-type file --file-path <本地路径>`;图片会成为可下载的 file 附件,不会内联渲染,也不会生成 mediaId - - `--msg-type image --media-id` 仅接受上游已经提供的有效 mediaId;DWS CLI 不提供本地文件到 mediaId 的上传或转换能力 - - audio/video 仍是兼容的 file 语义别名,但本地文件的推荐路径保持为 `--msg-type file --file-path` - - dentryId/spaceId 等参数仅用于调用方已经持有钉盘文件元数据的兼容场景,不是发送本地文件的前置步骤 - - --uuid 用于幂等发送,传入相同 uuid 在 24h 内不会重复投递消息(可选,群聊和单聊均支持) - - 富媒体消息的单聊优先使用 `--open-dingtalk-id`;传 `--user` 时 CLI 会尝试解析成 openDingTalkId 后发送 - - 发送文字 + 文件时,先发送 `--msg-type file --file-path` 文件消息,再补一条文本或 Markdown 说明;这是两条独立消息 -``` - -#### 查询消息发送状态 — 查询以当前用户身份发送的消息的发送状态 - -查询以当前用户身份发送的消息的发送状态。需要传入发送消息时返回的 openTaskId。 -``` -Usage: - dws chat message query-send-status [flags] -Example: - dws chat message query-send-status --open-task-id - # openTaskId 由 dws chat message send 返回 -Flags: - --open-task-id string 消息发送任务 ID (必填) - -注意: - - openTaskId 由 `dws chat message send` 发送消息成功后返回 - - 用于确认消息是否已成功发送或获取发送失败的原因 - - 返回结果中含发送成功消息的 openMessageId,可用于后续 recall(撤回)、read-status(查已读)等命令 -``` - -#### 撤回消息 — 撤回当前用户自己发出的消息 - -撤回当前用户以个人身份发送的消息。需要指定会话 ID(openConversationId)和消息 ID(openMessageId)。与 `recall-by-bot` 的区别:本命令通过 IM 接口撤回用户自己发出的消息,`recall-by-bot` 通过机器人接口撤回机器人发出的消息(需要 robot-code + processQueryKey)。 -``` -Usage: - dws chat message recall [flags] -Example: - dws chat message recall --conversation-id --msg-id - # 查询会话 ID: dws chat search --query "群名" - # 消息 ID 可通过 dws chat message list 获取 -Flags: - --conversation-id string 会话 openConversationId (必填,支持单聊/群聊,别名: --group / --id / --chat) - --msg-id string 消息 openMessageId (必填) - -注意: - - --conversation-id 的别名: --group, --id, --chat (均可替代 --conversation-id) - - 消息 ID 可通过 `dws chat message list` 命令获取 - - 仅支持撤回当前用户以个人身份发出的消息,不能撤回他人发送的消息,也不能撤回机器人发出的消息 - - 与 `recall-by-bot` 的区别:本命令通过 IM 接口撤回用户自己发出的消息(需要 openConversationId + openMessageId),`recall-by-bot` 通过机器人接口撤回机器人发出的消息(需要 robot-code + processQueryKey) -``` - -#### 编辑已发送消息 - -指定会话和消息后编辑 Markdown 消息内容。推荐使用 `--text`,CLI 会生成完整 content JSON;高级场景可直接传 `--content`,两者必须二选一且互斥。 - -``` -Usage: - dws chat message edit [flags] -Example: - 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 --content '{"title":"标题","text":"更新后的内容"}' -Flags: - --conversation-id string 会话 openConversationId (必填;别名 --group / --id / --chat) - --msg-id string 消息 openMessageId (必填) - --text string 编辑后的 Markdown 正文;与 --content 二选一 - --title string 消息标题;仅配合 --text,省略时从正文生成 - --content string 完整 Markdown content JSON;与 --text 二选一 - --at-all @所有人;正文缺少 <@all> 时自动补齐 - --at-open-dingtalk-ids string @成员的 openDingTalkId 列表,逗号分隔 -``` - -`--at-open-dingtalk-ids` 对应正文中的 `<@openDingTalkId>` 占位符;裸 `@openDingTalkId` 会规范化为尖括号格式。 - -#### 机器人发送消息(--group 群聊 / --users 单聊) - -**重要:该接口会真实发送消息到目标会话,不可用于测试或试探性调用。调用前必须确认消息内容和接收对象无误。** - -群聊:传 --group 指定群;单聊:传 --users 指定用户列表,二者只能选其一,不能同时指定。--text 支持 Markdown。群聊时可选 --at-user-ids @指定成员。 - -如果用户明确要求"用机器人/机器人身份/robot"发送,必须使用本命令,严禁改用 `chat message send` 以当前用户身份发送。 - -**重要**:机器人发群消息前,必须确认该机器人已在目标群中。若机器人不在群内会报错"机器人不存在",需先执行 `dws chat group members add-bot --id --robot-code ` 将机器人加入群聊后再发送。 -``` -Usage: - dws chat message send-by-bot [flags] -Example: - dws chat message send-by-bot --robot-code --group --title "日报" --text "## 今日完成..." - dws chat message send-by-bot --robot-code --users userId1,userId2 --title "提醒" --text "请提交周报" - dws chat message send-by-bot --robot-code --open-dingtalk-ids openDingtalkId1,openDingtalkId2 --title "提醒" --text "请提交周报" - dws chat message send-by-bot --robot-code --group --at-user-ids userId1,userId2 --title "提醒" --text "@userId1 @userId2 请查收本周报告" - dws chat message send-by-bot --robot-code --group --at-open-dingtalk-ids openDingtalkId1,openDingtalkId2 --title "提醒" --text "@openDingtalkId1 @openDingtalkId2 请查收本周报告" - dws chat message send-by-bot --robot-code --group --at-all --title "通知" --text "请所有人注意" -Flags: - --group string 群聊 openConversationId(群聊时必填) - --robot-code string 机器人 Code (必填) - --text string 消息内容 Markdown (必填) - --title string 消息标题 (必填) - --users string 用户 userId 列表,逗号分隔,最多20个(单聊时必填) - --open-dingtalk-ids string 用户 openDingtalkId 列表,逗号分隔(单聊时可替代 --users,可选) - --at-user-ids string @指定成员的 userId 列表,逗号分隔(仅群聊时生效,可选) - --at-open-dingtalk-ids string @指定成员的 openDingtalkId 列表,逗号分隔(仅群聊时生效,可选) - --at-all @所有人(可选),服务端接收字符串 true/false - -注意: - - 用户明确要求机器人发送时,必须使用 `chat message send-by-bot`;严禁使用 `chat message send` 以用户身份代发 - - --group 与 --users/--open-dingtalk-ids 互斥,必须且只能指定其一 - - --group 的别名: --id, --chat, --conversation-id (均可替代 --group) - - --at-user-ids 仅在 --group 群聊时生效,单聊时无效;设置时 --text 中需包含 @userId 对应文本 - - --at-open-dingtalk-ids 仅在 --group 群聊时生效,单聊时无效;设置时 --text 中需包含 @openDingtalkId 对应文本 - - --at-all @所有人,仅群聊时生效;只需带上 --at-all flag 即可,服务端会自动处理 - - userId 获取方式:`dws contact user search --query "姓名"` 搜人获取 userId - - **换行符**:--text 按 Markdown 渲染,换行规则同 `chat message send`: - 1. 必须使用**真实换行符**(`U+000A`),而非字面量 `\n`,否则全部内容会渲染在同一行 - 2. 单个换行不产生换行效果,需用空行(`\n\n`)做段落分隔,或行尾两空格 + 换行/`
` 做硬换行 -``` - -#### 机器人撤回消息(--group 群聊 / 不传为单聊) - -群聊:传 --group 与 --keys;单聊:仅传 --keys。--keys 为发送时返回的 processQueryKey 列表,逗号分隔。 -``` -Usage: - dws chat message recall-by-bot [flags] -Example: - dws chat message recall-by-bot --robot-code --group --keys - dws chat message recall-by-bot --robot-code --keys key1,key2 -Flags: - --group string 群聊 openConversationId(群聊撤回时必填) - --keys string 消息 processQueryKey 列表,逗号分隔 (必填) - --robot-code string 机器人 Code (必填) -``` - -#### 自定义机器人 Webhook 发送群消息 - -@ 人时需在 --text 中包含 @userId 或 @手机号,否则 @ 不生效;@所有人时需在 --text 中包含 @10 并带上 --at-all。 -``` -Usage: - dws chat message send-by-webhook [flags] -Example: - dws chat message send-by-webhook --token --title "告警" --text "CPU 超 90% @10" --at-all - dws chat message send-by-webhook --token --title "test" --text "hi @118785" --at-users 118785 -Flags: - --at-all @ 所有人(需在 --text 中包含 @10) - --at-mobiles string @ 指定手机号,逗号分隔 - --at-users string @ 指定用户,逗号分隔(需在 text 中包含 @userId) - --text string 消息内容 (必填) - --title string 消息标题 (必填) - --token string Webhook Token (必填) - -注意: - - **换行符**:--text 按 Markdown 渲染,换行规则同 `chat message send`: - 1. 必须使用**真实换行符**(`U+000A`),而非字面量 `\n`,否则全部内容会渲染在同一行 - 2. 单个换行不产生换行效果,需用空行(`\n\n`)做段落分隔,或行尾两空格 + 换行/`
` 做硬换行 -``` - -#### 拉取群话题回复消息列表 - -查询指定群聊中某条话题消息的全部回复。--group 指定群会话 ID,--topic-id 指定话题 ID(由 dws chat message list 返回)。 -``` -Usage: - dws chat message list-topic-replies [flags] -Example: - dws chat message list-topic-replies --group --topic-id - dws chat message list-topic-replies --group --topic-id --time "2025-03-01 00:00:00" --limit 20 -Flags: - --group string 群会话 openconversationId (必填) - --topic-id string 话题 ID,由 dws chat message list 返回 (必填) - --time string 开始时间,格式: yyyy-MM-dd HH:mm:ss(可选) - --limit int 返回数量(默认 50) - --direction string 时间方向: newer=从给定时间往现在拉,older=从给定时间往以前拉(推荐,默认 older) -``` - -#### 拉取指定时间范围内当前用户的所有会话消息 — 分页拉取当前登录用户在指定时间范围内的所有会话消息 - ---start 和 --end 限定时间范围,--limit 指定每页数量,--cursor 传分页游标(首页传 "0",后续从响应中的 nextCursor 获取)。服务端按 cursor 分页返回,hasMore=true 时用返回的 nextCursor 值作为下次 --cursor 继续翻页。若当前账号没有消息搜索权益,CLI 会透传服务端的友好提示与开通入口。 -``` -Usage: - dws chat message list-all [flags] -Example: - dws chat message list-all --start "2025-03-01 00:00:00" --end "2025-03-31 23:59:59" --limit 50 - dws chat message list-all --start "2025-03-01 00:00:00" --end "2025-03-31 23:59:59" --limit 50 --cursor "abc123token" -Flags: - --start string 起始时间,格式: yyyy-MM-dd HH:mm:ss (必填) - --end string 结束时间,格式: yyyy-MM-dd HH:mm:ss (必填) - --limit int 每页返回数量(默认 50) - --cursor string 分页游标(首页传 "0",后续从响应中的 nextCursor 获取) - -注意: - - 四个参数每次请求都会传递给服务端,cursor 首页传 "0" - - 与 chat message list 的区别:list 拉取指定单个会话(群聊或单聊)的消息,list-all 拉取当前用户所有会话的消息 - - 翻页:hasMore=true 时,用响应中的 nextCursor 值作为下次 --cursor 参数继续翻页 - - 时间格式统一为 yyyy-MM-dd HH:mm:ss - - 权限/权益错误不是空结果;应把返回的 friendly_hint 与 action_url 展示给用户,不要继续盲目翻页 -``` - -#### 拉取指定发送者的消息 — 搜索特定人发送给我的消息(包含单聊和群聊) - -> 推荐优先使用 `chat message search-advanced --user/--users`(userId)或 `--sender-ids`(openDingTalkId),它还能叠加关键词/群/at 等过滤条件。本命令保留给需要旧 list-by-sender 返回结构的场景。 - -搜索特定人发送给我的消息,返回结果包含单聊和群聊标识。--sender-user-id 指定发送者 userId,--sender-open-dingtalk-id 指定发送者 openDingTalkId,二者互斥。分页参数 --limit(默认 50)和 --cursor(默认 "0")始终传递;hasMore=true 时用返回的 nextCursor 作为下次 --cursor 继续翻页。 -``` -Usage: - dws chat message list-by-sender [flags] -Example: - dws chat message list-by-sender --sender-user-id --start "2026-03-10T00:00:00+08:00" --end "2026-03-11T00:00:00+08:00" --limit 50 --cursor 0 - dws chat message list-by-sender --sender-open-dingtalk-id --start "2026-03-10T00:00:00+08:00" --end "2026-03-11T00:00:00+08:00" --limit 50 --cursor 0 - dws chat message list-by-sender --sender-user-id --start "2026-03-10T00:00:00+08:00" --end "2026-03-10T23:59:59+08:00" --limit 20 --cursor 0 - dws chat message list-by-sender --sender-open-dingtalk-id --start "2026-03-10T00:00:00+08:00" --end "2026-03-11T00:00:00+08:00" --limit 50 --cursor -Flags: - --sender-user-id string 发送者 userId(与 --sender-open-dingtalk-id 二选一) - --sender-open-dingtalk-id string 发送者 openDingTalkId(与 --sender-user-id 二选一);该路径当前返回 result:{}、success:null(疑似服务端问题),优先用 --sender-user-id - --start string 开始时间,ISO-8601 格式 (必填) - --end string 结束时间,ISO-8601 格式 (必填) - --limit int 每页返回数量(默认 50) - --cursor string 分页游标(默认 "0",翻页传 nextCursor) - -注意: - - --sender-user-id 和 --sender-open-dingtalk-id 二者互斥,必须且只能指定其一: - - --sender-user-id 传 userId(企业内部应用常用,当前唯一可用路径) - - --sender-open-dingtalk-id 传 openDingTalkId(当前返回 result:{}、success:null(疑似服务端问题);即使只有 openDingTalkId,也建议先用 contact 换取 userId 后走 --sender-user-id) - - openDingTalkId 获取方式见下方「openDingTalkId 获取方式」小节 - - 不需要指定单聊/群聊,返回结果自带会话类型标识 - - 时间支持多种 ISO-8601 格式,如 "2026-03-10T00:00:00+08:00"、"2026-03-10 14:00:00"、"2026-03-10" 等 - - 翻页:hasMore=true 时,用返回的 nextCursor 作为下次 --cursor -``` - -#### 拉取 @我 的消息 — 搜索时间范围内 @我 的消息 - -> 推荐使用 `chat message search-advanced --at-me`,它还能叠加关键词/群/发送者等过滤条件。本命令适用于仅需拉取 @我 消息的简单场景。 - -搜索时间范围内 @我 的消息,可选指定群聊。返回结果包含单聊和群聊标识。分页参数 --limit(默认 50)和 --cursor(默认 "0")始终传递;hasMore=true 时用返回的 nextCursor 作为下次 --cursor 继续翻页。 -``` -Usage: - dws chat message list-mentions [flags] -Example: - dws chat message list-mentions --start "2026-03-10T00:00:00+08:00" --end "2026-03-11T00:00:00+08:00" --limit 50 --cursor 0 - dws chat message list-mentions --start "2026-04-01T00:00:00+08:00" --end "2026-04-14T00:00:00+08:00" --limit 20 --cursor 0 - dws chat message list-mentions --group --start "2026-03-10T00:00:00+08:00" --end "2026-03-11T00:00:00+08:00" --limit 50 --cursor 0 - dws chat message list-mentions --start "2026-03-10T00:00:00+08:00" --end "2026-03-11T00:00:00+08:00" --limit 50 --cursor -Flags: - --group string 群聊 openconversation_id(可选,不传则查全部) - --start string 开始时间,ISO-8601 格式 (必填) - --end string 结束时间,ISO-8601 格式 (必填) - --limit int 每页返回数量(默认 50) - --cursor string 分页游标(默认 "0",翻页传 nextCursor) - -注意: - - --group 可选,不传则查询所有会话中 @我 的消息;传入则只查指定群聊 - - --group 的别名: --id, --chat, --conversation-id (均可替代 --group) - - 时间支持多种 ISO-8601 格式,如 "2026-03-10T00:00:00+08:00"、"2026-03-10 14:00:00"、"2026-03-10" 等 - - 翻页:hasMore=true 时,用返回的 nextCursor 作为下次 --cursor -``` - -#### 拉取特别关注人的消息 - -拉取当前用户特别关注人的消息。分页参数 --limit 指定每页数量,--cursor 传分页游标(首次不传或传 0)。返回结果中 hasMore=true 时用 nextCursor 作为下次 --cursor 继续翻页。 -``` -Usage: - dws chat message list-focused [flags] -Example: - dws chat message list-focused --limit 50 - dws chat message list-focused --limit 20 --cursor -Flags: - --limit int 每页返回数量(默认 50) - --cursor int64 分页游标(首次不传或传 0,翻页传 nextCursor) - -注意: - - 首次调用不传 --cursor 或传 0,后续翻页传 nextCursor -``` - -#### 获取未读会话列表 - -获取当前用户有未读消息的会话信息。可选通过 `--count` 限制返回条数,不传则使用服务端默认值。 -``` -Usage: - dws chat message list-unread-conversations [flags] -Example: - dws chat message list-unread-conversations - dws chat message list-unread-conversations --count 20 - dws chat message list-unread-conversations --exclude-muted -Flags: - --count int 返回未读会话条数(可选) - --exclude-muted 是否排除已设置免打扰的会话(默认 false) -``` - -#### 查询消息的已读/未读状态 - -查询指定会话中消息的已读/未读状态(仅消息发送者可查询自己发出的消息)。--conversation-id 指定会话 openConversationId(群聊或单聊均可),--message-id 指定消息 ID(由 dws chat message list 返回的 openMessageId,必须是当前用户发送的消息)。目标用户 userId 使用 --user/--users;目标用户 openDingTalkId 使用 --target-open-dingtalk-ids;不传目标用户则返回所有接收者的状态。 -``` -Usage: - dws chat message read-status [flags] -Example: - dws chat message read-status --conversation-id --message-id - dws chat message read-status --conversation-id --message-id --user userId1,userId2 - dws chat message read-status --conversation-id --message-id --users userId1,userId2 - dws chat message read-status --conversation-id --message-id --target-open-dingtalk-ids openDingTalkId1,openDingTalkId2 -Flags: - --conversation-id string 会话 openConversationId (必填,群聊或单聊均可) - --message-id string 消息 openMessageId,由 chat message list 返回 (必填,必须是当前用户发送的消息) - --user string 目标用户 userId,支持逗号分隔(可选,不传则查所有接收者) - --users string 目标用户 userId 列表,逗号分隔(可选,不传则查所有接收者) - --target-open-dingtalk-ids string 目标用户 openDingTalkId 列表,逗号分隔(可选,不传则查所有接收者) - -注意: - - 仅消息发送者可查询自己发出的消息的已读/未读状态,查询他人发的消息会报错 - - --conversation-id 的别名: --group, --id, --chat (均可替代 --conversation-id) - - --message-id 从 dws chat message list 返回的消息列表中获取(字段名 openMessageId) - - --user / --users 传目标用户 userId - - --target-open-dingtalk-ids 不传时返回该消息所有接收者的已读状态;传入则只返回指定 openDingTalkId 用户的状态 -``` - -#### 按关键词搜索消息 — 在当前用户的会话中按关键词搜索消息 - -> 推荐优先使用 `chat message search-advanced`,它是本命令的严格超集:query 可选(非必填)、支持多个会话(非单个)、还能叠加发送者/at 等维度过滤。 - -按关键词搜索消息内容。--query 指定搜索关键词(必填)。可选 --group 限定搜索某个会话,不传则搜索所有会话。时间参数 --start/--end(ISO-8601)限定搜索时间范围。分页参数 --limit(默认 100)和 --cursor(默认 "0")始终传递;hasMore=true 时用返回的 nextCursor 作为下次 --cursor 继续翻页。 -``` -Usage: - dws chat message search [flags] -Example: - dws chat message search --query "changefree" --start "2026-04-01T00:00:00+08:00" --end "2026-04-15T00:00:00+08:00" --limit 50 --cursor 0 - dws chat message search --query "codereview" --group --start "2026-04-01T00:00:00+08:00" --end "2026-04-15T00:00:00+08:00" --limit 100 --cursor 0 - dws chat message search --query "链接" --start "2026-04-15T00:00:00+08:00" --end "2026-04-16T00:00:00+08:00" --limit 100 --cursor -Flags: - --query string 搜索关键词 (必填) - --group string 群聊 openconversation_id(可选,不传则搜索所有会话) - --start string 开始时间,ISO-8601 格式 (必填) - --end string 结束时间,ISO-8601 格式 (必填) - --limit int 每页返回数量(默认 100) - --cursor string 分页游标(默认 "0",翻页传 nextCursor) - -注意: - - --group 可选,不传则搜索所有会话中的消息;传入则只搜索指定会话 - - --group 的别名: --id, --chat, --conversation-id (均可替代 --group) - - 时间支持多种 ISO-8601 格式,如 "2026-03-10T00:00:00+08:00"、"2026-03-10 14:00:00"、"2026-03-10" 等 - - 翻页:hasMore=true 时,用返回的 nextCursor 作为下次 --cursor -``` - -#### 多维度搜索消息(推荐首选) — 支持按关键词、发送者、@我、@指定人、指定会话、时间范围等多维度搜索 - -> 推荐:这是消息搜索的首选接口。它可以完全替代 `chat message search`(query 可选 vs 必填,支持多个会话 vs 单个),大部分替代 `chat message list-by-sender`(通过 --user/--users 按 userId 搜索发送者,或通过 --sender-ids 按 openDingTalkId 搜索)和 `chat message list-mentions`(通过 --at-me 搜索@我的消息)。仅在拉取「特别关注人」消息时需要退回 `list-focused`。 - -支持按关键词、发送者、@我、@指定人、指定会话、时间范围等多维度搜索消息。发送者 userId 使用 --user/--users;发送者或 @ 人的 openDingTalkId 使用 --sender-ids/--at-ids。所有参数均为可选,至少指定一个搜索条件。 -``` -Usage: - dws chat message search-advanced [flags] -Example: - dws chat message search-advanced --query "周报" --start "2026-04-01T00:00:00+08:00" --end "2026-04-15T00:00:00+08:00" - dws chat message search-advanced --user --start "2026-04-01T00:00:00+08:00" --end "2026-04-15T00:00:00+08:00" - dws chat message search-advanced --users , --start "2026-04-01T00:00:00+08:00" --end "2026-04-15T00:00:00+08:00" - dws chat message search-advanced --sender-ids , --start "2026-04-01T00:00:00+08:00" --end "2026-04-15T00:00:00+08:00" - dws chat message search-advanced --at-me --start "2026-04-01T00:00:00+08:00" --end "2026-04-15T00:00:00+08:00" - dws chat message search-advanced --at-ids , --conversation-ids , --limit 50 --cursor 0 - dws chat message search-advanced --conversation-ids <单聊openConversationId> --query "合同" --start "2026-04-01T00:00:00+08:00" --end "2026-04-15T00:00:00+08:00" - # 查询群 ID: dws chat search --query "群名" - # 查询单聊会话 ID: dws chat conversation-info --user - # 查询人员: dws aisearch person --keyword "姓名" --dimension name -Flags: - --query string 搜索关键词(可选) - --user string 发送者 userId,支持逗号分隔(可选) - --users string 发送者 userId 列表,逗号分隔(可选) - --sender-ids string 发送者 openDingTalkId 列表,逗号分隔(可选) - --at-me 只搜索 @我 的消息(可选,默认 false) - --at-ids string @指定人的 openDingTalkId 列表,逗号分隔(可选) - --conversation-ids string 会话 openConversationId 列表,逗号分隔(可选,群聊或单聊均可,不传则搜索所有会话) - --start string 开始时间,ISO-8601 格式(可选) - --end string 结束时间,ISO-8601 格式(可选) - --cursor string 分页游标(默认 "0") - --limit int 每页返回数量(默认 100) - --conversation-ids 的别名: --groups - -注意: - - 所有参数均为可选,但至少需要指定一个搜索条件 - - --user / --users 传发送者 userId - - --sender-ids 和 --at-ids 传 openDingTalkId - - --conversation-ids 可指定多个会话 ID(群聊或单聊均可),逗号分隔,不传则搜索所有会话 - - 群聊 openConversationId 通过 `dws chat search --query "群名"` 获取 - - 单聊 openConversationId 通过 `dws chat conversation-info --user ` 或 `--open-dingtalk-id ` 获取 - - 时间支持多种 ISO-8601 格式,如 "2026-03-10T00:00:00+08:00"、"2026-03-10 14:00:00"、"2026-03-10" 等 - - 翻页:hasMore=true 时,用返回的 nextCursor 作为下次 --cursor - - 替代关系:完全替代 search(严格超集);大部分替代 list-by-sender(--user 覆盖按 userId 搜索发送者,--sender-ids 覆盖按 openDingTalkId 搜索)和 list-mentions(--at-me 覆盖核心功能);不能替代 list-focused(「特别关注」是独立维度) -``` - -#### 根据消息 ID 批量查询消息 -``` -Usage: - dws chat message list-by-ids [flags] -Example: - dws chat message list-by-ids --msg-ids msgId1,msgId2,msgId3 - # 最多传 50 条消息 ID -Flags: - --msg-ids string 消息 ID 列表,逗号分隔,最多 50 条 (必填) -``` - -#### 表情回应选择策略 - -> 贴表情时,优先查 [chat-emoji-list.md](chat-emoji-list.md) 中的默认表情名称(共 199 个,如「赞」「鼓掌」「感谢」等): -> - 命中 → 使用 `add-emoji --emoji `(直接贴 emoji) -> - 未命中 → 先 `create-text-emotion` 创建文字表情获取 emotionId,再 `add-text-emotion` 贴文字表情 - -#### 对消息添加 emoji 表情回应 -``` -Usage: - dws chat message add-emoji [flags] -Example: - dws chat message add-emoji --conversation-id --msg-id --emoji "赞" - dws chat message add-emoji --conversation-id --msg-id --emoji "鼓掌" - # --emoji 的值必须是 chat-emoji-list.md 中的 name(中文名),如:赞、鼓掌、感谢、微笑 等 - # 查询会话 ID: dws chat search --query "群名" -Flags: - --conversation-id string 会话 openConversationId (必填,支持单聊/群聊,别名: --group / --id / --chat) - --msg-id string 消息 openMsgId (必填) - --emoji string emoji 表情名称,必须是默认表情列表中的 name 值 (必填,参见 chat-emoji-list.md) -``` - -#### 移除消息的 emoji 表情回应 -``` -Usage: - dws chat message remove-emoji [flags] -Example: - dws chat message remove-emoji --conversation-id --msg-id --emoji "赞" - # 查询会话 ID: dws chat search --query "群名" -Flags: - --conversation-id string 会话 openConversationId (必填,支持单聊/群聊,别名: --group / --id / --chat) - --msg-id string 消息 openMsgId (必填) - --emoji string emoji 表情名称,必须是默认表情列表中的 name 值 (必填,参见 chat-emoji-list.md) -``` - -#### 对消息添加文字表情回应(当默认表情列表中没有所需表情时使用) -``` -Usage: - dws chat message add-text-emotion [flags] -Example: - dws chat message add-text-emotion --conversation-id --msg-id --emotion-id --emotion-name "赞" --text "nice" --background-id im_bg_5 -Flags: - --conversation-id string 会话 openConversationId (必填,支持单聊/群聊,别名: --group / --id / --chat) - --msg-id string 消息 openMsgId (必填) - --emotion-id string 表情 ID (必填,通过 create-text-emotion 或已知表情获取) - --emotion-name string 表情名称 (必填) - --text string 文字内容 (必填) - --background-id string 背景 ID (必填) -``` - -#### 移除消息的文字表情回应 -``` -Usage: - dws chat message remove-text-emotion [flags] -Example: - dws chat message remove-text-emotion --conversation-id --msg-id --emotion-id --emotion-name "赞" --text "nice" --background-id -Flags: - --conversation-id string 会话 openConversationId (必填,支持单聊/群聊,别名: --group / --id / --chat) - --msg-id string 消息 openMsgId (必填) - --emotion-id string 表情 ID (必填) - --emotion-name string 表情名称 (必填) - --text string 文字内容 (必填) - --background-id string 背景 ID (必填) -``` - -#### 创建文字表情(获取 emotionId)— 当 chat-emoji-list.md 中没有所需表情时,先创建再贴 -``` -Usage: - dws chat message create-text-emotion [flags] -Example: - dws chat message create-text-emotion --emotion-name "赞" --text "nice" - dws chat message create-text-emotion --emotion-name "感谢" --text "感谢" --background-id im_bg_5 -Flags: - --emotion-name string 表情名称 (必填) - --text string 文字内容 (必填) - --background-id string 背景 ID(可选,不传则由服务端默认分配) - -注意: - - 创建后返回 emotionId,可用于 add-text-emotion 命令 - - 如果已有合适的表情,无需创建新的 -``` - -#### 批量拉取消息的表情回复和文字回复 - -根据消息 ID 列表批量查询消息的表情回复和文字回复信息。 - -``` -Usage: - dws chat message list-emotion-replies [flags] -Example: - dws chat message list-emotion-replies --msg-ids msgId1,msgId2,msgId3 - # 消息 ID 可通过 dws chat message list 获取 -Flags: - --msg-ids string 消息 ID 列表,逗号分隔 (必填) -``` - -### list-top-conversations (置顶会话) - -#### 拉取置顶会话列表 - -拉取当前用户的置顶会话列表。分页参数 --limit 指定每页数量,--cursor 传分页游标(首次不传或传 0)。返回结果中 hasMore=true 时用 nextCursor 作为下次 --cursor 继续翻页。 -``` -Usage: - dws chat list-top-conversations [flags] -Example: - dws chat list-top-conversations --limit 1000 - dws chat list-top-conversations --limit 1000 --cursor -Flags: - --limit int 每页返回数量(默认 1000) - --cursor int64 分页游标(首次不传或传 0,翻页传 nextCursor) - --exclude-muted 是否排除已设置免打扰的会话(默认 false) - -注意: - - 用户询问"置顶会话"时,直接调用此命令返回置顶会话列表即可 - - 用户询问"置顶消息"时,需两步:先调用此命令拉取置顶会话列表获取各会话的 openConversationId,再用 `chat message list --group ` 分别拉取每个会话内的消息 - - 翻页:hasMore=true 时,用返回的 nextCursor 作为下次 --cursor -``` - -### download-media (下载消息资源) - -#### 下载消息中的资源(图片/视频/语音等)到本地 - -下载聊天消息中的图片、视频、语音等资源到本地文件。流程:先获取下载 URL,再 HTTP GET 下载。 -``` -Usage: - dws chat message download-media [flags] -Example: - dws chat message download-media --type mediaId --resource-id --message-id --open-conversation-id --output ./photo.jpg - dws chat message download-media --type mediaId --resource-id --message-id --open-conversation-id --output ./downloads/photo.jpg -Flags: - --type string 资源类型: mediaId (必填) - --resource-id string 资源 ID,mediaId 类型时为消息中的 mediaId 值 (必填) - --message-id string 消息 openMessageId (必填) - --open-conversation-id string 会话 openConversationId (必填) - --output string 本地保存路径 (必填),建议传完整文件路径(含文件名) - -注意: - - resource-id 从 `dws chat message list` 返回的消息内容中获取 mediaId - - message-id 从 `dws chat message list` 返回的 openMessageId - - open-conversation-id 从 `dws chat search` 获取 openConversationId - - --output 建议传完整文件路径(含扩展名,如 ./photo.jpg);只传目录(如 ./downloads/)在旧版本会下载失败,请显式带上文件名 -``` - -### search-common (搜索共同群) - -#### 搜索共同群 — 查询指定人共同所在的群聊 - -根据昵称列表搜索共同群聊。--nicks 指定要搜索的人员昵称(逗号分隔,必填)。--match-mode 控制匹配模式:AND 表示所有人都在群里,OR 表示任一人在群里(默认 AND)。分页参数 --limit(默认 20)和 --cursor(默认 "0")始终传递;hasMore=true 时用返回的 nextCursor 作为下次 --cursor 继续翻页。 -``` -Usage: - dws chat search-common [flags] -Example: - dws chat search-common --nicks "风雷,山乔" --limit 20 --cursor 0 - dws chat search-common --nicks "天鸡,乐函" --match-mode OR --limit 20 --cursor 0 - dws chat search-common --nicks "风雷,山乔,天鸡" --limit 10 --cursor -Flags: - --nicks string 要搜索的昵称列表,逗号分隔 (必填) - --match-mode string 匹配模式:AND=所有人都在群里,OR=任一人在群里(默认 AND) - --limit int 每页返回数量(默认 20) - --cursor string 分页游标(默认 "0",翻页传 nextCursor) - --exclude-muted 是否排除已设置免打扰的群聊(默认 false) - -注意: - - --nicks 传人员昵称(花名),逗号分隔,如 "风雷,山乔" - - --match-mode AND 表示群里必须包含所有指定的人;OR 表示包含任意一人即可 - - 翻页:hasMore=true 时,用返回的 nextCursor 作为下次 --cursor -``` - -### conversation-info (获取会话基础信息) - -#### 获取会话基础信息 — 含会话关联的钉盘共享空间 ID - -获取指定会话的基础信息,包含会话关联的钉盘共享空间 ID (newCSpaceIdIM)。该 ID 可用于独立的钉盘存储操作;发送本地图片或文件到聊天不需要先调用本命令,直接使用 `chat message send --msg-type file --file-path`。 -``` -Usage: - dws chat conversation-info [flags] -Example: - dws chat conversation-info --group --format json - dws chat conversation-info --user --format json - dws chat conversation-info --open-dingtalk-id --format json -Flags: - --group string 群聊 openConversationId(群聊时使用) - --user string 单聊对方 userId(单聊时使用) - --open-dingtalk-id string 单聊对方 openDingTalkId(单聊时使用) - -注意: - - --group、--user、--open-dingtalk-id 互斥,必须且只能指定其一 - - --group 的别名: --id, --chat, --conversation-id (均可替代 --group) - - 返回值中的 newCSpaceIdIM 为会话共享空间 ID,可用于调用方明确需要的钉盘存储流程 - - 该 ID 不是发送本地聊天附件的前置条件;本地附件直接走 `chat message send --msg-type file --file-path` -``` - -#### 引用回复消息 — 引用某条消息并回复文字(单聊/群聊均可) -``` -Usage: - dws chat message reply [flags] -Example: - dws chat message reply --conversation-id --ref-msg-id --ref-sender --text "收到,马上处理" - # 被引用消息的 openMessageId、发送者 openDingTalkId 通过 dws chat message list 获取 -Flags: - --conversation-id string 会话 openConversationId (必填,支持单聊/群聊) - --ref-msg-id string 被引用的消息 openMessageId (必填) - --ref-sender string 被引用消息的发送者 openDingTalkId (必填) - --text string 回复内容 (必填) - --ai-tag 消息是否带 AI 发送角标(可选,默认 true) - --uuid string 幂等键(可选) - -注意: - - 以当前用户身份引用回复,语义同 chat message send;目前回复类型仅支持 text -``` - -#### 转发单条消息 — 将一条消息从源会话转发到目标会话(源/目标均支持单聊/群聊) -``` -Usage: - dws chat message forward [flags] -Example: - dws chat message forward --src-conversation-id --msg-id --dest-conversation-id -Flags: - --src-conversation-id string 源会话 openConversationId (必填) - --msg-id string 源消息 openMessageId (必填) - --dest-conversation-id string 目标会话 openConversationId (必填) - --uuid string 幂等键(可选) - -注意: - - 与 combine-forward 区别: forward 转单条,combine-forward 合并多条为一条转发 -``` - -#### 转发话题消息 — 将一条话题消息转发到目标会话 -``` -Usage: - dws chat message forward-topic [flags] -Example: - dws chat message forward-topic --src-conversation-id --src-msg-id --src-thread-id --dest-conversation-id -Flags: - --src-conversation-id string 源会话 openConversationId (必填,消息所在的会话) - --src-msg-id string 源消息 openMessageId (必填,要转发的消息) - --src-thread-id string 话题 ID (必填,格式: convThread + 加密后的 convThreadId,即 message list 返回的 openConvThreadId) - --dest-conversation-id string 目标会话 openConversationId (必填,转发到的会话) -``` - -#### 置顶消息 — 将指定消息置顶到会话顶部 -``` -Usage: - dws chat message set-top-msg [flags] -Example: - dws chat message set-top-msg --open-conversation-id --msg-id -Flags: - --open-conversation-id string 会话 openConversationId (必填,支持群聊/单聊) - --msg-id string 消息 openMessageId (必填) -``` - -#### 取消置顶消息 — 取消会话顶部的置顶消息 -``` -Usage: - dws chat message unset-top-msg [flags] -Example: - dws chat message unset-top-msg --open-conversation-id --msg-id -Flags: - --open-conversation-id string 会话 openConversationId (必填,支持群聊/单聊) - --msg-id string 消息 openMessageId (必填) -``` - -#### 合并转发多条消息 — 将多条消息合并后转发到目标会话(源/目标会话均支持单聊/群聊) -``` -Usage: - dws chat message combine-forward [flags] -Example: - dws chat message combine-forward --src-conversation-id --msg-ids ,, --dest-conversation-id - dws chat message combine-forward --src-conversation-id --msg-ids , --dest-conversation-id --uuid -Flags: - --src-conversation-id string 源会话 openConversationId (必填) - --msg-ids string 源消息 openMessageId 列表,逗号分隔 (必填) - --dest-conversation-id string 目标会话 openConversationId (必填) - --uuid string 幂等键(可选) - -注意: - - 与 chat message forward 区别: forward 转单条,combine-forward 合并多条为一条转发 - - --msg-ids 多个消息 ID 用逗号分隔,无顺序要求 -``` - -#### 钉住某条消息(Pin) — 将指定消息设置为钉住状态 -``` -Usage: - dws chat message set-pin-msg [flags] -Example: - dws chat message set-pin-msg --open-conversation-id --msg-id -Flags: - --open-conversation-id string (必填)会话 openConversationId(支持群聊/单聊) - --msg-id string (必填)消息 openMessageId - -注意: - - 钉住消息后,会话成员均可在会话中看到被钉住的消息 -``` - -#### 取消钉住某条消息(Unpin) — 取消指定消息的钉住状态 -``` -Usage: - dws chat message unset-pin-msg [flags] -Example: - dws chat message unset-pin-msg --open-conversation-id --msg-id -Flags: - --open-conversation-id string (必填)会话 openConversationId(支持群聊/单聊) - --msg-id string (必填)消息 openMessageId - -注意: - - 取消钉住后消息仍保留在会话中,只是不再被标记为钉住状态 -``` - -#### 拉取某个会话中钉住的消息列表 — 拉取指定会话中被钉住的消息列表 -``` -Usage: - dws chat message list-pin-msg [flags] -Example: - dws chat message list-pin-msg --open-conversation-id - dws chat message list-pin-msg --open-conversation-id --size 50 - dws chat message list-pin-msg --open-conversation-id --cursor --size 20 -Flags: - --open-conversation-id string (必填)会话 openConversationId(支持群聊/单聊) - --cursor string (选填)分页游标,首次不传,翻页时传上次返回的 nextCursor - --size int (选填)一次拉取的消息数量(默认 20,最大 100) - -注意: - - 与 `chat message list` 区别: list-pin-msg 只返回被钉住的消息;list 拉取全部消息 - - 分页: hasMore=true 时,用返回的 nextCursor 作为下次 --cursor 继续翻页 -``` - -#### 收藏消息 — 为当前用户收藏指定会话中的一条消息 -``` -Usage: - dws chat message add-favorite [flags] -Example: - dws chat message add-favorite --open-message-id --open-conversation-id -Flags: - --open-message-id string 消息 openMessageId (必填) - --open-conversation-id string 消息所在会话 openConversationId (必填) -``` - -#### 取消收藏消息 — 移除当前用户的收藏标记,不删除原消息 -``` -Usage: - dws chat message remove-favorite [flags] -Example: - dws chat message remove-favorite --open-message-id --open-conversation-id -Flags: - --open-message-id string 消息 openMessageId (必填) - --open-conversation-id string 消息所在会话 openConversationId (必填) -``` - -#### 查询收藏消息 — 分页查询当前用户收藏的消息 -``` -Usage: - dws chat message list-favorites [flags] -Example: - dws chat message list-favorites - dws chat message list-favorites --size 50 - dws chat message list-favorites --cursor 20 --size 20 -Flags: - --cursor int 数字分页游标,默认 0;翻页时传上次返回的 nextCursor - --size int 一次拉取的收藏数量,默认 20,范围 1-100 - -注意: - - 首次请求可省略分页参数,CLI 会自动向 Open 服务传入 cursor=0、size="20" - - hasMore=true 时,将 nextCursor 作为下一次的 --cursor -``` - -### bot (机器人管理) - -#### 搜索【我创建的】机器人 — 仅返回当前用户自己创建的机器人 - -范围: 仅限当前登录用户自己创建的机器人(不含他人创建、官方机器人)。 -返回字段: 没有 openDingTalkId,如果需要给机器人发单聊消息请用 find。 -典型触发词: "我创建的机器人""我的机器人""我自己的机器人""我做的机器人""查看我的机器人"。 - -``` -Usage: - dws chat bot search [flags] -Example: - dws chat bot search --page 1 - dws chat bot search --page 1 --size 10 --name "日报" -Flags: - --name string 按名称搜索 - --page int 页码,从1开始 (默认 1) - --size int 每页条数 (默认 50),别名: --limit -``` - -#### 搜索【全部可用】机器人 — 含他人创建/官方机器人,额外返回机器人 openDingTalkId - -范围: 当前用户可用的全部机器人(含他人创建、官方机器人)。 -返回字段: 结果在 `result.bots[]` 中,每项含 `botOpenDingTalkId`(机器人的 openDingTalkId,用于给机器人发单聊消息)和 `name`。注意字段名是 `botOpenDingTalkId`,不是 `openDingTalkId`;search 没有此字段。 -典型触发词: "搜索机器人""找一个机器人""帮我找 XXX 机器人""所有可用机器人""查机器人"。 - -``` -Usage: - dws chat bot find [flags] -Example: - dws chat bot find --query "日报" - dws chat bot find --query "日报" --limit 20 - dws chat bot find --query "日报" --limit 20 --cursor <上次返回的 nextCursor> -Flags: - --query string 搜索关键词 (必填) - --limit int 每页返回数量(默认 20) - --cursor string 分页游标(首次调用不传,翻页时传上次返回的 nextCursor) - -注意: - - cursor 必须用上次返回的 nextCursor 字符串原值,不要传 "0" 或其他数字字面量 - (服务端 String 类型,但网关会把数字字符串 auto-coerce 回 Integer 导致 PARAM_ERROR) -``` - -search 与 find 选择指南: - -| 维度 | `chat bot search` | `chat bot find` | -|------|-------------------|-----------------| -| 范围 | 仅我创建的机器人 | 全部可用机器人(含他人/官方) | -| 额外返回机器人 openDingTalkId | 无 | 有,字段名 `botOpenDingTalkId`(可用于给机器人发单聊消息) | -| 触发词 | "我创建的""我的""我自己的" | "搜索机器人""找机器人""查机器人" | - -### category (会话分组管理) - -#### 获取用户自定义会话分组 -``` -Usage: - dws chat category list -Example: - dws chat category list - # 返回当前用户的所有自定义会话分组 -``` - -#### 查看指定会话所属分组 -``` -Usage: - dws chat category list-by-conv [flags] -Example: - dws chat category list-by-conv --group -Flags: - --group string 会话 openConversationId (必填;别名 --conversation-id / --id) -``` - -#### 批量查询会话分组信息 -``` -Usage: - dws chat category batch-info [flags] -Example: - dws chat category batch-info --category-ids 123,456 -Flags: - --category-ids string 分组 ID 列表,逗号分隔 (必填) -``` - -分组 ID 可通过 `chat category list` 或 `list-by-conv` 获取。 - -#### 拉取指定分组下的会话列表 -``` -Usage: - dws chat category list-conversations [flags] -Example: - dws chat category list-conversations --category-id <分组ID> - dws chat category list-conversations --category-id <分组ID> --exclude-muted - # 分组ID 可通过 dws chat category list 获取 -Flags: - --category-id int 会话分组 ID (必填) - --exclude-muted 是否排除已设置免打扰的会话(默认 false) -``` - -#### 创建会话分组 -``` -Usage: - dws chat category create [flags] -Example: - dws chat category create --title "工作群" -Flags: - --title string 分组名称 (必填) -``` - -#### 创建智能会话分组 — 可指定群名称关键词和群内成员作为匹配规则 -``` -Usage: - dws chat category create-smart [flags] -Example: - dws chat category create-smart --name "工作群" - dws chat category create-smart --name "项目组" --keywords "项目,开发" - dws chat category create-smart --name "团队群" --members openDingTalkId1,openDingTalkId2 - dws chat category create-smart --name "重点群" --keywords "重点" --members openDingTalkId1 -Flags: - --name string 分组名称 (必填) - --keywords string 群名称关键词列表,逗号分隔(可选) - --members string 群内成员 openDingTalkId 列表,逗号分隔(可选) -``` - -#### 删除会话分组 -``` -Usage: - dws chat category delete [flags] -Example: - dws chat category delete --category-id <分组ID> - # 分组ID 可通过 dws chat category list 获取 -Flags: - --category-id int 会话分组 ID (必填) -``` - -#### 重命名会话分组 -``` -Usage: - dws chat category rename [flags] -Example: - dws chat category rename --category-id <分组ID> --title "新名称" - # 分组ID 可通过 dws chat category list 获取 -Flags: - --category-id int 会话分组 ID (必填) - --title string 新的分组名称 (必填) -``` - -#### 将会话加入分组 -``` -Usage: - dws chat category add-conv [flags] -Example: - dws chat category add-conv --group --category-ids 123,456 - # 分组ID 可通过 dws chat category list 获取 - # 查询群 ID: dws chat search --query "群名" -Flags: - --group string 会话 openConversationId (必填) - --category-ids string 目标分组 ID 列表,逗号分隔 (必填) -``` - -#### 将会话移出分组 -``` -Usage: - dws chat category remove-conv [flags] -Example: - dws chat category remove-conv --group --category-ids 123,456 - # 分组ID 可通过 dws chat category list 获取 - # 查询群 ID: dws chat search --query "群名" -Flags: - --group string 会话 openConversationId (必填) - --category-ids string 目标分组 ID 列表,逗号分隔 (必填) -``` - -### text (文本内容处理) - -#### 翻译文本内容 -``` -Usage: - dws chat text translate [flags] -Example: - dws chat text translate --query "你好世界" --to en_US - dws chat text translate --query "Hello World" --to zh_CN - dws chat text translate --query "Bonjour" --to ja_JP -Flags: - --query string 待翻译的文本内容 (必填) - --to string 目标语言代码 (必填) -``` - -支持的目标语言代码:`en_US`, `zh_CN`, `zh_TW`, `zh_HK`, `ja_JP`, `ko_KR`, `vi_VN`, `th_TH`, `id_ID`, `ms_MY`, `es_419`, `fr_FR`, `pt_BR`, `tr_TR`, `ru_RU`, `de_DE`, `hi_IN`, `hu_HU`, `pl_PL`, `sv_SE`, `fi_FI`, `cs_CZ`, `ar_SA`, `tl_PH`, `he_IL`, `nl_NL`, `lo_LA`, `it_IT`。 - -### mute (会话免打扰) - -#### 会话消息免打扰 — 开启或关闭会话消息免打扰(支持单聊和群聊) -``` -Usage: - dws chat mute [flags] -Example: - dws chat mute --conversation-id - dws chat mute --conversation-id --off - # 查询群 ID: dws chat search --query "群名" - # 查询单聊会话 ID: dws chat conversation-info --user -Flags: - --conversation-id string 会话 openConversationId (必填,支持单聊/群聊) - --id string --conversation-id 的别名 - --chat string --conversation-id 的别名 - --off 关闭免打扰(不传则开启免打扰) - -注意: - - 默认行为是开启免打扰,传 --off 则关闭免打扰 - - 支持单聊和群聊,openConversationId 可通过 chat search(群聊)或 chat conversation-info(单聊)获取 -``` - -### hide (隐藏会话) - -#### 隐藏会话 — 在会话列表中隐藏指定会话(支持单聊/群聊),收到新消息时会重新出现 -``` -Usage: - dws chat hide [flags] -Example: - dws chat hide --conversation-id - dws chat hide --id -Flags: - --conversation-id string 会话 openConversationId (必填,支持单聊/群聊) - --id string --conversation-id 的别名 - --chat string --conversation-id 的别名 - -注意: - - 隐藏后会话不再显示在列表中,收到新消息时会重新出现 - - openConversationId 可通过 chat search(群聊)或 chat conversation-info(单聊)获取 -``` - -### mute-at-all (关闭@所有人通知) - -#### 关闭/开启 @所有人消息提醒 — 关闭或开启会话中 @所有人的消息通知 - -> ⚠️ 当前不可用:该命令当前调用常失败(服务端返回 1002 系统繁忙),多为服务端侧限制。命令本身参数合法,但调用不会生效。 -``` -Usage: - dws chat mute-at-all [flags] -Example: - dws chat mute-at-all --conversation-id - dws chat mute-at-all --conversation-id --off -Flags: - --conversation-id string 会话 openConversationId (必填,支持单聊/群聊) - --id string --conversation-id 的别名 - --chat string --conversation-id 的别名 - --off 恢复接收 @所有人通知(不传则关闭通知) -``` - -### mute-red-envelope (关闭红包通知) - -#### 关闭/开启红包消息提醒 — 关闭或开启会话中的红包消息通知 - -> ⚠️ 当前不可用:该命令当前调用常失败(服务端返回 1002 系统繁忙),多为服务端侧限制。命令本身参数合法,但调用不会生效。 -``` -Usage: - dws chat mute-red-envelope [flags] -Example: - dws chat mute-red-envelope --conversation-id - dws chat mute-red-envelope --conversation-id --off -Flags: - --conversation-id string 会话 openConversationId (必填,支持单聊/群聊) - --id string --conversation-id 的别名 - --chat string --conversation-id 的别名 - --off 恢复接收红包通知(不传则关闭通知) -``` - -### mark-unread (标记会话为未读) - -#### 标记会话为未读 — 将指定会话标记为未读状态 -``` -Usage: - dws chat mark-unread [flags] -Example: - dws chat mark-unread --conversation-id - dws chat mark-unread --id -Flags: - --conversation-id string 会话 openConversationId (必填,支持群聊/单聊) - --id string --conversation-id 的别名 - --chat string --conversation-id 的别名 - -注意: - - openConversationId 可通过 chat search(群聊)或 chat conversation-info(单聊)获取 - - API 返回成功,但该未读状态在 API 侧观察不到:list-all-conversations 返回的 unreadPoint 不会随之变化,只有钉钉客户端 UI 上能看到未读标记 -``` - -### clear-red-point (清除会话红点) - -#### 清除会话红点 — 清除指定会话的未读红点 -``` -Usage: - dws chat clear-red-point [flags] -Example: - dws chat clear-red-point --conversation-id - dws chat clear-red-point --id -Flags: - --conversation-id string 会话 openConversationId (必填,支持群聊/单聊) - --id string --conversation-id 的别名 - --chat string --conversation-id 的别名 - -注意: - - 清除红点后该会话不再显示未读标记 -``` - -### clear-all-red-point (红点清零) - -#### 清除所有会话红点 — 一键全部已读 -``` -Usage: - dws chat clear-all-red-point -Example: - dws chat clear-all-red-point - -注意: - - 无需任何参数,直接清除当前用户所有会话的未读红点,等效于"全部已读" -``` - -### list-all-conversations (全部会话列表) - -#### 分页获取全部会话列表 — 获取当前用户的所有会话 -``` -Usage: - dws chat list-all-conversations [flags] -Example: - dws chat list-all-conversations - dws chat list-all-conversations --limit 50 - dws chat list-all-conversations --exclude-muted -Flags: - --limit int 每页数量(1-100,默认 100);传 >100 会被明确拒绝 - --cursor int 分页游标(首次不传或传 0,翻页传 nextCursor) - --exclude-muted 是否排除已免打扰会话(默认 false) - -注意: - - 返回结果包含单聊和群聊,不区分会话类型 - - --limit 范围 1-100,默认 100,上限 100;传入 >100 会报错拒绝,不会静默截断 - - 分页当前不可用:当前 hasMore 恒为 false、nextCursor 恒为 null,本命令最多返回 100 条会话,无法用 --cursor 继续翻页取更多 - - 与 list-top-conversations 的区别: 本命令返回全部会话(单聊+群聊),list-top-conversations 仅返回置顶会话 -``` - -### clear-messages (清空会话聊天记录) - -#### 清空会话聊天记录 — 清空当前用户指定会话的消息 -``` -Usage: - dws chat clear-messages [flags] -Example: - dws chat clear-messages --conversation-id - dws chat clear-messages --id -Flags: - --conversation-id string 会话 openConversationId (必填,支持群聊/单聊) - --id string --conversation-id 的别名 - --chat string --conversation-id 的别名 - -注意: - - 仅清空当前用户视角的消息,不影响其他成员 -``` - -### mark-read (标记消息已读) - -#### 标记消息已读 — 将指定消息及之前的消息标记为已读 -``` -Usage: - dws chat mark-read [flags] -Example: - dws chat mark-read --conversation-id --message-id - dws chat mark-read --id --message-id -Flags: - --conversation-id string 会话 openConversationId (必填,支持群聊/单聊) - --id string --conversation-id 的别名 - --chat string --conversation-id 的别名 - --message-id string 消息 openMessageId (必填) - -注意: - - 标记该消息及之前的所有消息为已读 - - openMessageId 可通过 chat message list 获取 -``` - -### group list-all (分页拉取所有群) - -#### 分页拉取我所有群列表 — 获取当前用户加入的所有群聊 -``` -Usage: - dws chat group list-all [flags] -Example: - dws chat group list-all - dws chat group list-all --limit 50 - dws chat group list-all --limit 100 --cursor -Flags: - --limit int 每页返回数量(默认 100,最大 200) - --cursor string 分页游标(首次不传,翻页传返回的 nextCursor) - -注意: - - 与 chat group list-my-groups 区别: list-all 返回用户加入的所有群;list-my-groups 仅返回用户作为群主/管理员的群 - - 分页: hasMore=true 时用返回的 nextCursor 作为下次 --cursor - - ⚠️ 存在同步盲区:新建群后较长时间(实测 15 分钟后全量翻页仍查不到)内不会出现在 list-all 里;而 chat group list-my-groups / chat search 能立即查到。要确认刚建的群,用 list-my-groups 或 search,别依赖 list-all -``` - -### group list-join-validations (分页拉取入群验证记录) - -#### 分页拉取入群验证记录 — 获取当前用户的所有入群验证记录 - -包括自己被拒绝的记录以及作为审批者的记录。 -``` -Usage: - dws chat group list-join-validations [flags] -Example: - dws chat group list-join-validations - dws chat group list-join-validations --limit 30 - dws chat group list-join-validations --limit 20 --cursor -Flags: - --limit int 单页数量(默认 20,最大 50) - --cursor string 分页游标(首次不传,翻页传返回的 nextCursor) - -注意: - - 分页: hasMore=true 时用返回的 nextCursor 作为下次 --cursor - - cursor 首次拉取不传或传 null 时从当前时间开始拉 -``` - -### group audit-join-validation (审批入群验证) - -#### 审批入群验证 — 通过、删除单个审核 - -当前仅 AuditApprove(通过)和 AuditDelete(删除)两个动作可用。 -``` -Usage: - dws chat group audit-join-validation [flags] -Example: - dws chat group audit-join-validation --group --record-id 123456 --applicant --inviter --status AuditApprove - dws chat group audit-join-validation --group --record-id 123456 --applicant --inviter --status AuditDelete - # 查询入群验证记录: dws chat group list-join-validations -Flags: - --group string 群 openConversationId (必填) - --record-id string 申请记录 ID (必填) - --applicant string 申请人 openDingTalkId (必填) - --inviter string 邀请人 openDingTalkId (必填) - --status string 审批动作: AuditApprove(通过) / AuditDelete(删除) (必填) - --description string 审批说明(可选) - -注意: - - status 仅支持 AuditApprove(通过) 和 AuditDelete(删除);AuditIgnore(忽略)、AuditRefuse(拒绝)、AuditBlock(拒绝且拉黑) 会被服务端拒绝报 unsupported audit status,属服务端限制 - - record-id、applicant、inviter 可通过 dws chat group list-join-validations 查询获得 -``` +- `+messages-send` 只暴露下层真实支持的身份能力,并自动规范化、补齐对应身份的 @ 占位符。 +- `+search-msg --page-all` 连续翻页并默认按消息 ID 批量富化;续页或富化失败会保留已取得结果并返回逐项失败 ledger。 +- 五个查询 Shortcut 默认纯只读;显式传 `--download-resources` 时才在运行时升级为 `write` 并要求确认。引用、回复、合并转发中的资源使用 `resourceRefs` 自带的子消息 `messageId`;仅当子消息缺会话 ID 时继承父消息 `openConversationId`。 +- `+messages-resource-download` 始终写本地。确认相对输出目录和覆盖策略后才加 `--yes`,默认拒绝覆盖已有文件。 +- 下载器只接受经审查的钉钉与公网 OSS HTTPS 地址并逐跳校验重定向;跨主机时不会转发下层提供的请求头。 + +## 适用范围与安全硬约束 + +`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` | [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 list-focused`(零参数一行命令) -用户说"某人发给我的消息/指定发送者的消息/某人最近的消息" → `chat message list-by-sender --sender-user-id ` 或 `--sender-open-dingtalk-id `(跨单聊+群聊) -用户说"和某人的单聊聊天记录/拉某人单聊历史" → `chat message list --user ` 或 `--open-dingtalk-id ` -用户说"某个群的聊天记录" → `chat message list --group ` -用户说"我最近所有消息/我今天的消息" → `chat message list-all --start --end ` -用户说"@我的消息/提及我的" → `chat message list-mentions --start --end ` -用户说"搜索消息里的关键词/包含XX的消息" → `chat message search-advanced --query "<关键词>"`(首选,严格超集) -用户说"我和某人的共同群" → `chat search-common --nicks "<昵称1>,<昵称2>"` -用户说"未读会话列表" → `chat message list-unread-conversations` -用户说"群里某条话题的回复" → `chat message list-topic-replies --group --topic-id ` -用户说"置顶会话/置顶消息" → `chat list-top-conversations` 列会话 → 再 `chat message list --group ` 拉消息(两步) +### 消息 -用户说"建群/创建群聊" → `chat group create` -用户说"搜索群/找群" → `chat search` -用户说"我创建的群/我管理的群/我是群主的群/我当管理员的群" → `chat group list-my-groups` -用户说"群成员/看群里有谁" → `chat group members` -用户说"拉人进群/加群成员" → `chat group members add` -用户说"踢人/移除群成员" → `chat group members remove` -用户说"加机器人到群" → `chat group members add-bot` -用户说"改群名" → `chat group rename` -用户说"设置群备注/给群加备注" → `chat group update-alias` -用户说"改我在群里的昵称/设置群昵称" → `chat group update-nick --nick` -用户说"批量查群成员信息/按ID查群成员" → `chat group members list-by-ids` -用户说"聊天记录/会话消息/拉取会话" → `chat message list` -用户说"某人发给我的消息/指定发送者/某人的消息" → `chat message list-by-sender`(用户未明确说"单聊"时优先使用,跨单聊/群聊) -用户说"拉取和某人的单聊记录/单聊消息" → `chat message list --user`(用户明确说"单聊"时使用) -用户说"@我的消息/at我的/提及我的" → `chat message list-mentions` -用户说"未读消息会话/未读会话列表/我的未读会话" → `chat message list-unread-conversations` -用户说"发群消息(以个人身份)" → `chat message send --group` -用户说"发单聊消息(以个人身份)" → `chat message send --user`(有 userId 时)或 `chat message send --open-dingtalk-id`(有 openDingTalkId 时) -用户说"机器人发消息/机器人群发" → `chat message send-by-bot` -用户说"撤回我发的消息/撤回消息" → `chat message recall`(通过 IM 接口撤回当前用户自己发出的消息,需要 openConversationId + openMessageId) -用户说"编辑/修改已发送消息" → `chat message edit`(`--text` / `--content` 二选一) -用户说"撤回机器人发的消息/机器人撤回消息" → `chat message recall-by-bot`(通过机器人接口撤回机器人发出的消息,需要 robot-code + processQueryKey) -用户说"Webhook 发消息/告警消息" → `chat message send-by-webhook` -用户说"话题回复/群话题消息回复/拉取话题回复" → `chat message list-topic-replies` -用户说"所有消息/全部会话消息/拉取全部消息/时间范围内消息/我的消息/我今天的消息/查我的钉钉消息/最近的消息" → `chat message list-all` -用户说"特别关注人的消息/关注的人的消息/星标联系人的消息" → `chat message list-focused` -用户说"消息已读未读/谁看了消息/查读状态/消息读取状态" → `chat message read-status` -用户说"查看我的机器人" → `chat bot search` -用户说"搜索消息/查找关键词/搜一下消息里的XX" → 优先使用 `chat message search-advanced`(推荐首选,严格超集);仅在简单关键词搜索且无其他维度需求时可用 `chat message search` -用户说"多维度搜索/按发送者搜索/按人搜消息/指定多个群搜索/@我的消息搜索" → `chat message search-advanced`(推荐首选,支持多维度组合搜索) -用户说"查询消息发送状态/消息发没发成功/消息状态" → `chat message query-send-status` -用户说"我和XX的共同群/我们都在哪些群/查共同群" → `chat search-common` -用户说"置顶会话/置顶消息/我的置顶/查看置顶" → `chat list-top-conversations` -用户说"查看会话分组/自定义分组" → `chat category list` -用户说"这个会话属于哪些分组" → `chat category list-by-conv` -用户说"按多个分组 ID 批量查分组信息" → `chat category batch-info` -用户说"某个分组下的会话/分组会话列表" → `chat category list-conversations` -用户说"新建会话分组/创建分组" → `chat category create` -用户说"创建智能分组/新建智能会话分组" → `chat category create-smart` -用户说"删除会话分组" → `chat category delete` -用户说"重命名分组/改分组名" → `chat category rename` -用户说"把会话加入分组/会话归类到分组" → `chat category add-conv` -用户说"把会话移出分组/从分组移除会话" → `chat category remove-conv` -用户说"根据群号查群信息/群号查群/群号转openConversationId" → `chat group get-by-group-id`(当用户发消息时只提供了群号,用此工具将群号转为 openConversationId,再调用发消息接口) -用户说"查看群身份/群的自定义身份列表" → `chat group-role list` -用户说"创建/添加群身份" → `chat group-role add` -用户说"修改/更新群身份名称" → `chat group-role update` -用户说"删除群身份" → `chat group-role remove` -用户说"给某人设置群身份/设定用户的群身份" → `chat group-role set-user` -用户说"移除某人的群身份/撤销群身份" → `chat group-role remove-user` -用户说"查询某人的群身份/某人在群里有什么身份" → `chat group-role query-user` -用户说"转让群主/换群主/群主转让" → `chat group transfer-owner` -用户说"把普通群升级为外部群" → `chat group upgrade-to-external`(不可逆,仅群主,确认后 `--yes`) -用户说"清除/取消群昵称" → `chat group update-nick --group `(不传 `--nick`) -用户说"群邀请链接/入群链接/加群链接" → `chat group invite-url` -用户说"分享群链接/把群分享给某人/群链接发到某群" → `chat group share-invite` -用户说"群公告/发布公告/修改公告/查看公告/定时公告" → `chat group notice create/edit/get/list` -用户说"批量查消息/按ID查消息/根据消息ID查" → `chat message list-by-ids` -用户说"批量查消息回复/表情回复/文字回复/消息回应列表" → `chat message list-emotion-replies` -用户说"emoji回应/表情回应/给消息加表情" → `chat message add-emoji` -用户说"取消emoji回应/移除表情回应" → `chat message remove-emoji` -用户说"文字表情回应/添加文字表情" → `chat message add-text-emotion` -用户说"取消文字表情回应/移除文字表情" → `chat message remove-text-emotion` -用户说"创建文字表情/新建文字表情" → `chat message create-text-emotion` -用户说"免打扰/消息免打扰/静音/开启免打扰/关闭免打扰" → `chat mute` -用户说"隐藏会话/隐藏群聊/隐藏对话" → `chat hide` -用户说"关闭@所有人通知/屏蔽@所有人/不接收@all" → `chat mute-at-all`(当前服务端 1002 不可用) -用户说"开启@所有人通知/恢复@所有人提醒" → `chat mute-at-all --off`(当前服务端 1002 不可用) -用户说"关闭红包通知/屏蔽红包/不接收红包提醒" → `chat mute-red-envelope`(当前服务端 1002 不可用) -用户说"开启红包通知/恢复红包提醒" → `chat mute-red-envelope --off`(当前服务端 1002 不可用) -用户说"标记会话未读/标为未读" → `chat mark-unread` -用户说"标记已读/把消息标成已读" → `chat mark-read` -用户说"清除红点/去掉某个会话的未读红点" → `chat clear-red-point` -用户说"全部已读/一键清除红点/红点清零" → `chat clear-all-red-point` -用户说"我的所有会话/全部会话列表" → `chat list-all-conversations` -用户说"清空聊天记录/清空会话消息" → `chat clear-messages` -用户说"我加入的所有群/我的全部群列表" → `chat group list-all` -用户说"入群验证记录/谁申请进群" → `chat group list-join-validations` -用户说"审批入群/通过入群申请/删除入群申请" → `chat group audit-join-validation`(仅 AuditApprove/AuditDelete 可用) -用户说"引用回复/回复消息/引用消息回复" → `chat message reply` -用户说"转发消息/转发一条消息/把消息转发到另一个群" → `chat message forward` -用户说"合并转发/批量转发/合并转发多条消息" → `chat message combine-forward` -用户说"转发话题/转发话题消息" → `chat message forward-topic` -用户说"置顶消息/把消息置顶" → `chat message set-top-msg` -用户说"取消置顶消息/撤销消息置顶" → `chat message unset-top-msg` -用户说"发送/上传本地图片或媒体到聊天" → `chat message send --msg-type file --file-path <本地路径>` -用户明确只要 mediaId → DWS CLI 当前不提供本地上传入口;仅在上游已有有效 mediaId 时使用 `chat message send --msg-type image --media-id` -用户说"群机器人列表/群里有哪些机器人/查看群机器人" → `chat group bots` -用户说"从群里移除机器人/踢出机器人" → `chat group members remove-bot` -用户说"搜索机器人/找机器人/查机器人/帮我找XXX机器人" → `chat bot find`(全部可用机器人,额外返回 botOpenDingTalkId 可发单聊) -用户说"给机器人发单聊/给机器人发消息/跟机器人聊天" → 必须先 `chat bot find`(拿 openDingTalkId)→ 再 `chat message send --open-dingtalk-id`(search 没有 openDingTalkId,无法发单聊) -用户说"我创建的机器人/我的机器人/我自己的机器人/查看我的机器人" → `chat bot search`(仅我创建的机器人,无 openDingTalkId) -用户说"解散群/解散群聊" → `chat group dismiss` -用户说"设置历史消息/新成员看历史/新成员可见消息" → `chat group set-history` -用户说"置顶会话/取消置顶/会话置顶" → `chat set-top`(设置/取消置顶),`chat list-top-conversations`(查看置顶列表) -用户说"全员禁言/群禁言/解除禁言" → `chat group-mute` -用户说"禁言某人/指定成员禁言/解除某人禁言" → `chat group-mute-member` -用户说"设管理员/取消管理员/设置群管理员" → `chat group set-admin` -用户说"翻译文本/把这段话翻译成英文/翻译成中文" → `chat text translate` +| 用户说 | 路由 | +|--------|------| +| “发群消息 / 发到某群” | `chat 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 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 的场景,DWS CLI 不能从本地文件生成 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 list-emotion-replies` — 批量拉取消息的表情回复和文字回复 -- `chat message add-text-emotion` / `remove-text-emotion` — 对消息添加/移除文字表情回应 -- `chat message create-text-emotion` — 创建文字表情模板,返回 emotionId 供 add-text-emotion 使用 -- `chat category list` — 获取用户自定义会话分组列表 -- `chat category list-conversations` — 拉取指定分组下的会话列表 -- `chat category create-smart` — 创建智能会话分组(可指定群名称关键词和成员作为匹配规则) -- `chat category list-by-conv` / `chat category batch-info` — 查询会话所属分组 / 批量查询分组信息 -- `chat mute` — 开启/关闭会话消息免打扰(默认开启,--off 关闭) -- `chat group transfer-owner` — 转让群主 -- `chat group upgrade-to-external` — 将普通群不可逆升级为外部群 -- `chat group update-nick` — 设置群昵称;省略 `--nick` 时清除 -- `chat message edit` — 编辑已发送 Markdown 消息 -- `chat group invite-url` — 获取群邀请链接 -- `chat group share-invite` — 分享群聊链接到会话 -- `chat group notice create/edit/get/list` — 群公告发布、修改、详情、列表 -- `chat message reply` — 引用回复消息(在群聊中引用某条消息并回复文字) -- `chat message forward` — 转发单条消息(将一条消息从源会话转发到目标会话) -- `chat set-top` — 设置/取消会话置顶(默认置顶,--off 取消) -- `chat group-mute` — 全员禁言/取消全员禁言(默认禁言,--off 取消) -- `chat group-mute-member` — 指定群成员禁言/取消禁言(需指定 --users 和 --mute-time) -- `chat group set-admin` — 设置/取消群管理员(默认设为管理员,--off 取消) +### 群与成员 -## openDingTalkId 获取方式 +| 用户说 | 路由 | +|--------|------| +| “建群 / 创建群聊” | `chat group create` | +| “建外部群 / 建普通群 / 建话题圈” | `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` | -多个命令参数需要 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 +| 用户说 | 路由 | +|--------|------| +| “查看我的机器人 / 我创建的机器人” | `chat bot search` | +| “找机器人 / 搜索全部可用机器人 / 给机器人发单聊” | `chat bot find`,需要 `openDingTalkId` | +| “加机器人到群 / 从群移除机器人 / 群里有哪些机器人” | `group members add-bot` / `remove-bot` / `group bots` | +| “Webhook 发告警” | `chat message send-by-webhook` | -openDingTalkId 为当前用户视角下的目标用户唯一标识,不可跨用户共享。 +### 会话状态 -## 核心工作流 +| 用户说 | 路由 | +|--------|------| +| “未读会话列表” | `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 ` | -```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 +| 场景 | 入口 | +|------|------| +| 搜索群并拉消息 | [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` 查询钉盘文件 | -# 2b. 拉取未读会话列表 -dws chat message list-unread-conversations --count 20 --format json - -# 3. 以个人身份发送群消息 -dws chat message send --group --title "周报提醒" "请大家本周五前提交周报" --format json - -# 4. 以个人身份单聊(通过 userId) -dws chat message send --user "你好" --format json - -# 4b. 以个人身份单聊(通过 openDingTalkId,三方应用等无法获取 userId 时使用) -dws chat message send --open-dingtalk-id "你好" --format json - -# 5. 机器人发群消息(Markdown) -dws chat message send-by-bot --robot-code \ - --group --title "日报" --text "## 今日完成..." --format json - -# 6. 机器人单聊发消息 -dws chat message send-by-bot --robot-code \ - --users userId1,userId2 --title "提醒" --text "请提交周报" --format json - -# 7. Webhook 发告警 -dws chat message send-by-webhook --token \ - --title "告警" --text "CPU 超 90% @10" --at-all --format json -``` - -## 复合工作流 - -### 机器人发消息后撤回(完整流程) - -`recall-by-bot` 通过机器人接口撤回机器人发出的消息(需要 `--robot-code` + `--keys`)。`chat message recall` 通过 IM 接口撤回当前用户自己发出的消息(需要 `--conversation-id` + `--msg-id`)。 - -```bash -# Step 1: 查我的机器人 — 提取 robot-code -dws chat bot search --format json - -# Step 2: 用机器人发消息 — 提取返回中的 processQueryKey -dws chat message send-by-bot --robot-code --group \ - --title "通知" --text "内容" --format json - -# Step 3: 用同一个 robot-code + processQueryKey 撤回 -dws chat message recall-by-bot --robot-code --group \ - --keys --format json -``` - -### 机器人发群消息(含机器人不在群内的处理) - -机器人通过 `send-by-bot --group` 发群消息时,如果返回"机器人不存在"错误,说明该机器人尚未加入目标群,需先邀请进群再发送。 - -```bash -# Step 1: 查我的机器人 — 提取 robot-code -dws chat bot search --format json - -# Step 2: 尝试发送,若报"机器人不存在"则执行 Step 3 -dws chat message send-by-bot --robot-code --group \ - --title "通知" --text "内容" --format json - -# Step 3: 邀请机器人进群 -dws chat group members add-bot --group --robot-code - -# Step 4: 重新发送 -dws chat message send-by-bot --robot-code --group \ - --title "通知" --text "内容" --format json -``` - -### 给机器人发单聊消息(必须先用 find 拿 openDingTalkId) - -给机器人发单聊消息时,必须先用 `chat bot find` 搜索机器人拿到 `openDingTalkId`,再用 `chat message send --open-dingtalk-id` 发送。不能用 `chat bot search`,因为 search 不返回 `openDingTalkId`。 - -```bash -# Step 1: 搜索机器人 — 提取 openDingTalkId(必须用 find,search 没有此字段) -dws chat bot find --query "玉澜" --format json - -# Step 2: 用 openDingTalkId 发单聊消息 -dws chat message send --open-dingtalk-id --text "你好" --format json -``` - -### 机器人 @指定人发群消息 - -通过 `--at-user-ids` 传入 userId 列表或 `--at-open-dingtalk-ids` 传入 openDingtalkId 列表来 @指定成员,多个用逗号分隔。`--text` 中需包含 `@userId` 或 `@openDingtalkId` 文本(不要用尖括号,不要用姓名)。通过 `--at-all` @所有人。 - -```bash -# Step 1: 搜人获取 userId -dws contact user search --query "张三" --format json - -# Step 2: 用 userId 发送并 @(注意 text 中 @userId) -dws chat message send-by-bot --robot-code --group \ - --at-user-ids userId1,userId2 \ - --title "提醒" --text "@userId1 @userId2 请查收本周报告" --format json - -# 或者用 openDingtalkId 发送并 @ -dws chat message send-by-bot --robot-code --group \ - --at-open-dingtalk-ids openDingtalkId1,openDingtalkId2 \ - --title "提醒" --text "@openDingtalkId1 @openDingtalkId2 请查收本周报告" --format json - -# @所有人 -dws chat message send-by-bot --robot-code --group \ - --at-all --title "通知" --text "请所有人注意" --format json -``` - - -### 发送图片/文件 + 文字说明(两条消息) - -本地图片和文件先用 `--msg-type file --file-path` 发送,再补一条文本消息说明;这是两条独立消息,不需要媒体上传或钉盘前置步骤。图片会显示为可下载的文件附件,不会内联渲染。 - -```bash -dws chat message send --group --msg-type file --file-path ./screenshot.png --format json -dws chat message send --group --text "这是本周的数据汇总" --format json -``` - -如果调用方已经从上游取得有效 mediaId,可以先用 `--msg-type image --media-id` 发送内联图片,再补一条文本消息;DWS CLI 本身不能把本地图片转换成 mediaId。 - -#### 创建并推送流式卡片 — 向群聊或单聊发送流式卡片消息 - -群聊传 --group,单聊传 --receiver,二者互斥。 - -**注意:send-card 必须和 update-card 搭配使用。** 创建卡片时无需传入内容,后续通过 update-card 更新内容,最后一次更新必须将 --flow-status 设为 3(finish),否则卡片会一直处于"生成中"的加载状态。 -flow-status 取值:1=处理中(PROCESSING),2=输入中(INPUTTING),3=完成(FINISH),4=执行中(EXECUTING),5=错误(ERROR)。 -``` -Usage: - dws chat message send-card [flags] -Example: - dws chat message send-card --group - dws chat message send-card --receiver - # 查询群 ID: dws chat search --query "群名" - # 查询人员: dws aisearch person --keyword "姓名" --dimension name -Flags: - --group string 群聊 openConversationId(群聊时必填,与 --receiver 互斥) - --receiver string 单聊接收者 openDingTalkId(单聊时必填,与 --group 互斥) -``` - -#### 流式更新卡片内容 — 更新已发送的流式卡片内容 - ---biz-id 为 send-card 返回的业务 ID,--flow-status 控制流式状态。 -flow-status 取值:1=处理中(PROCESSING),2=输入中(INPUTTING),3=完成(FINISH),4=执行中(EXECUTING),5=错误(ERROR)。 - -**最后一次更新必须将 --flow-status 设为 3(finish),否则卡片会一直处于"生成中"的加载状态。** -``` -Usage: - dws chat message update-card [flags] -Example: - dws chat message update-card --biz-id --content "更新的卡片内容" --flow-status 2 - dws chat message update-card --biz-id --content "最终内容" --flow-status 3 -Flags: - --biz-id string 卡片业务 ID (必填) - --content string 卡片消息内容 (必填) - --flow-status int 流式状态 (必填) -``` - -## 上下文传递表 +## 上下文传递摘要 | 操作 | 从返回中提取 | 用于 | |------|-------------|------| -| `chat search` | `openConversationId` | message send/list、group members 等的 --group | -| `chat group create` | `openConversationId` | 同上 | -| `chat message list-all` | `nextCursor` | 下次 list-all 的 --cursor | -| `aisearch person` | `userId` | message send 的 --user、send-by-bot 的 --users、send-by-bot 的 --at-user-ids、list-by-sender 的 --sender-user-id | -| `aisearch person` → `contact user get` | `openDingTalkId` | message send 的 --at-open-dingtalk-ids、--open-dingtalk-id、send-by-bot 的 --open-dingtalk-ids、send-by-bot 的 --at-open-dingtalk-ids、list-by-sender 的 --sender-open-dingtalk-id、message list 的 --open-dingtalk-id | -| `chat bot search` | `robotCode` | send-by-bot / recall-by-bot 的 --robot-code(仅我创建的机器人,无 openDingTalkId) | -| `chat bot find` | `botOpenDingTalkId` | 给机器人发单聊消息(send --open-dingtalk-id;字段名是 botOpenDingTalkId,非 openDingTalkId) | -| `chat message send-by-bot` | `processQueryKey` | recall-by-bot 的 --keys | -| `chat message send` | `openTaskId` | query-send-status 的 --open-task-id | -| `chat message list` | `openMessageId` | recall 的 --msg-id | -| `chat message search` | `nextCursor` | 下次 message search 的 --cursor | -| `chat message search-advanced` | `nextCursor` | 下次 message search-advanced 的 --cursor | -| `chat search-common` | `openConversationId` | message send/list 等的 --group | -| `chat conversation-info` | `newCSpaceIdIM` | 独立钉盘存储流程的共享空间 ID;不是发送本地聊天附件的前置条件 | -| `chat message list` | `openMsgId` | message read-status 的 --message-id | -| `chat group-role list` | `openRoleId` | group-role update/remove/set-user/remove-user 的 --role-id | -| `chat message create-text-emotion` | `emotionId` | add-text-emotion 的 --emotion-id | -| `chat category list` | `categoryId` | category list-conversations 的 --category-id | -| `chat group get-by-group-id` | `openConversationId` | 同 chat search,将群号转为 openConversationId | -| `chat message send-card` | `bizId` | update-card 的 --biz-id | -| `chat message list` | `openMessageId` | message reply 的 --ref-msg-id、message forward 的 --msg-id | -| `chat search` | `openConversationId` | set-top 的 --conversation-id、group-mute / group-mute-member 的 --group | +| `chat 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 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`,其中图片是可下载附件;`--msg-type image --media-id` 仅用于上游已经提供有效 mediaId 的内联图片 -- `chat message list-all` 的四个参数(--start、--end、--limit、--cursor)每次请求都必须传递;翻页时用响应中的 nextCursor 值作为下次 --cursor -- `chat message list` 的 `--group`、`--user`、`--open-dingtalk-id` 三者互斥,必须且只能指定其一 -- `chat message list-by-sender` 不需要指定单聊/群聊,返回结果自带会话类型标识;`--sender-user-id`(userId)与 `--sender-open-dingtalk-id`(openDingTalkId)二选一;时间用 `--start`/`--end`(ISO-8601),分页用 `--limit`/`--cursor` -- `chat message list-mentions` 可选 `--group` 指定群聊,不传则查全部;时间用 `--start`/`--end`(ISO-8601),分页用 `--limit`/`--cursor` -- `chat message list-unread-conversations` 获取当前用户未读会话列表,可选 `--count` 指定返回条数 -- `chat message search` 按关键词搜索消息内容,`--query` 必填,可选 `--group` 限定搜索某个会话;时间用 `--start`/`--end`(ISO-8601),分页用 `--limit`(默认 100)/`--cursor` -- `chat message read-status` 查询指定消息的已读/未读状态,仅消息发送者可查询自己发出的消息;`--group`、`--message-id` 必填;目标用户 userId 用 `--user`/`--users`,openDingTalkId 用 `--target-open-dingtalk-ids`,不传则查所有接收者 -- `chat search-common` 搜索共同群,`--nicks` 传人员昵称(逗号分隔),`--match-mode` AND/OR 控制匹配逻辑,分页用 `--limit`(默认 20)/`--cursor` -- `chat list-top-conversations` 拉取置顶会话列表,分页用 `--limit`(默认 1000)/`--cursor`;用户询问"置顶会话"或"置顶消息"时均路由到此命令 -- `--user` 和 `--open-dingtalk-id` 本质上都是发起单聊操作,只是用户标识格式不同:userId 为企业内部应用常用标识,openDingTalkId 为三方应用或跨组织场景下的用户标识,服务端对两种 ID 的解析逻辑不同 -- `--time` 格式: `yyyy-MM-dd HH:mm:ss`,为拉取消息的起始时间点;`--direction` 控制方向(newer=从给定时间往现在拉,older=从给定时间往以前拉),`--limit` 控制数量 -- `chat search` 挂在 `chat` 下(非 `chat group` 下),路径为 `dws chat search` -- `send-by-bot` 群聊传 `--group`,单聊传 `--users` 或 `--open-dingtalk-ids`,与 `--group` 互斥且必选其一;群聊时可选 `--at-user-ids` @指定成员(传 userId 列表)或 `--at-open-dingtalk-ids` @指定成员(传 openDingtalkId 列表),content 中需包含对应 @标识;`--at-all` @所有人;群聊场景如果返回"机器人不存在"错误,需先通过 `chat group members add-bot --group --robot-code ` 将机器人邀请进群后再发送 -- `recall-by-bot` 群聊传 `--group` + `--keys`,单聊仅传 `--keys`(不传 `--group` 即为单聊撤回) -- `send-by-webhook` 支持 `--at-all`、`--at-mobiles`、`--at-users` 进行 @ 操作,但需在 `--text` 中包含 `@userId` 或 `@手机号` 才能生效;`--at-all` @所有人时需在 `--text` 中包含 `@10` -- `chat group-role` 系列命令用于管理群的自定义身份标签:`list` 查列表,`add` 创建,`update` 改名,`remove` 删除;`set-user` 覆盖某人全部身份(传空 --role-ids 则清除),`remove-user` 仅移除指定身份,`query-user` 查询某人当前身份;用户用 `--user ` -- 消息**换行符**(`send` / `send-by-bot` / `send-by-webhook` 的 `--text`)有两层要求:(1) 必须是**真实换行符** `U+000A`,不是字面量 `\n`;(2) Markdown 规范下单换行不生效,需用空行 `\n\n`(段落分隔)或行尾两空格 + 换行 / `
`(硬换行) -- `chat group transfer-owner` 转让群主,需传 --group(openConversationId);新群主 userId 用 `--user`,openDingTalkId 用 `--new-owner` -- `chat group invite-url` 获取群邀请链接,需传 --group(openConversationId),可选 --expires-seconds 指定有效期(秒,0=永久) -- `chat group quit` 退出群聊,需传 --group(openConversationId) -- `chat group update-icon` 更新群头像,需传 --group(openConversationId)和由可信上游提供的有效 --icon-media-id(mediaId);DWS CLI 不能从本地图片生成该 ID -- `chat group update-settings` 更新群设置,需传 --group(openConversationId)、--setting-key(设置项 key)、--status(0=关闭 1=开启) -- `chat message send-card` 创建并推送流式卡片,群聊传 --group,单聊传 --receiver,二者互斥;不传 content,后续通过 update-card 更新内容 -- `chat message update-card` 流式更新卡片内容,需传 --biz-id(创建卡片返回的业务 ID)、--content、--flow-status -- `chat message list-by-ids` 根据消息 ID 批量查询,--msg-ids 逗号分隔,最多 50 条 -- `chat message add-emoji` / `remove-emoji` 需传 --group(openConversationId)、--msg-id(openMsgId)、--emoji(表情名称) -- `chat message add-text-emotion` / `remove-text-emotion` 需传 --group、--msg-id、--emotion-id、--emotion-name、--text、--background-id,六个参数全部必填 -- `chat message create-text-emotion` 创建文字表情模板,返回 emotionId;--background-id 可选,不传由服务端默认分配 -- `chat category list` 无需参数;`category list-conversations` 需传 --category-id(通过 category list 获取) -- `chat mute` 默认开启免打扰,传 --off 关闭;--conversation-id / --id / --chat 三个别名均可用于传入会话 ID -- `chat message reply` 引用回复消息(**单聊/群聊均可**),需传 --conversation-id(openConversationId,单聊与群聊使用同一字段)、--ref-msg-id(被引用消息 openMessageId)、--ref-sender(被引用消息发送者 openDingTalkId)、--text(回复内容);目前回复类型仅支持 text -- `chat message forward` 转发单条消息(**源/目标会话均支持单聊/群聊**,常见组合:群→群、群→单、单→群、单→单),需传 --src-conversation-id(源会话 openConversationId)、--msg-id(源消息 openMessageId)、--dest-conversation-id(目标会话 openConversationId) -- `chat set-top` 设置/取消会话置顶(**单聊/群聊均可**),需传 --conversation-id(openConversationId,单聊与群聊使用同一字段),默认置顶,传 --off 取消 -- `chat message reply` 以当前用户身份引用回复,与 `chat message send` 的用户身份发送语义一致 -- **如何获取 openConversationId**(如果上层已有则直接使用,不必再查): - - 群聊:`dws chat search --query "群名"` - - 单聊:`dws chat conversation-info --user ` 或 `dws chat conversation-info --open-dingtalk-id `(人员信息可通过 `dws aisearch person --keyword "姓名" --dimension name` 获取) -- `chat group-mute` 全员禁言/取消全员禁言,需传 --group(openConversationId),默认禁言,传 --off 取消 -- `chat group-mute-member` 指定群成员禁言,需传 --group、--user/--users(userId,逗号分隔,CLI 自动解析为 openDingTalkId)、--mute-time(毫秒,仅禁言时必填,支持 300000/3600000/86400000/604800000/2592000000),传 --off 解除禁言;禁言群主会被服务端拒绝 -- `chat group set-admin` 设置/取消群管理员,需传 --group(openConversationId)、--user/--users(userId,逗号分隔),默认设为管理员,传 --off 取消 - -## 自动化脚本 - -| 脚本 | 场景 | 用法 | -|------|------|------| -| [chat_export_messages.py](../scripts/chat_export_messages.py) | 导出群聊消息到 JSON 文件 | `python chat_export_messages.py --query "项目冲刺" --time "2026-03-10 00:00:00"` | -| [chat_history_with_user.py](../scripts/chat_history_with_user.py) | 查询与某人的单聊聊天记录 | `python chat_history_with_user.py --name "张三" --time "2026-03-10 00:00:00"` | +完整字段传递表见 [chat-workflows](./chat/chat-workflows.md#上下文传递表)。 ## 相关产品 -- [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) — 钉盘文件存储与下载;不是发送本地聊天图片/文件的前置步骤 +- [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..d9d7ed55 --- /dev/null +++ b/skills/multi/dingtalk-chat/references/chat/chat-group.md @@ -0,0 +1,215 @@ +# chat-group:群聊、成员、设置与群身份 + +> 返回入口:[chat.md](../chat.md) + +## 适用场景 + +用于群搜索、建群、成员增删、机器人进群、群设置、群主转让、群邀请分享、群公告、入群审批、群身份和群禁言。 + +## 必读约束 + +- 群聊目标统一使用 `openConversationId`。只有数字群号时,先用 `chat group get-by-group-id` 转换。 +- `chat search` 在 `chat` 根下,不在 `chat group` 下。 +- 群成员操作中,`--users` 常为逗号分隔列表;具体要求以命令 `--help` 为准。 +- 解散群、踢人、转让群主、禁言、管理员设置都是高影响操作,执行前必须确认目标群和用户。 +- 发布或修改群公告会触达群成员;公告正文是 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 +``` + +关键 flags: + +| Flag | 说明 | +|------|------| +| `--name` | 群名称,必填 | +| `--users` | 成员 userId 或 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 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 new file mode 100644 index 00000000..7fa79ebf --- /dev/null +++ b/skills/multi/dingtalk-chat/references/intent-guide.md @@ -0,0 +1,88 @@ +# chat 局部意图消歧 + +本文件从单 Skill `intent-guide.md` 拆分而来,仅保留与本产品相关的跨产品消歧规则。 + +| 用户说... | 真实意图 | 应该用 | 不要用 | 理由 | +|---|---|---|---|---| +| "通知群里的人都来开会" | 个人身份群发 | `chat message send` | `chat message send-by-bot` | 以个人身份向群发消息 | +| "让机器人每天推送日报" | 机器人定时推送 | `chat message send-by-bot` | `chat message send` | 需要机器人身份定期发送 | +| "CPU 超过 90% 自动告警" | Webhook 告警 | `chat message send-by-webhook` | `chat message send-by-bot` | 系统告警场景,需自定义 Webhook | +| "拉取一下上周项目群的聊天记录" | 拉取会话消息 | `chat message list` | — | 拉取指定群聊的消息列表 | +| "看看张三发给我的消息" | 按发送者查询消息 | `chat message list-by-sender` | `chat message list --user` | 用户未明确说"单聊"时优先用 list-by-sender(跨单聊/群聊) | +| "拉取和张三的单聊记录" | 拉取单聊消息 | `chat message list --user` | `chat message list-by-sender` | 用户明确说"单聊"时用 list --user | +| "谁@了我/查看提及我的消息" | 查询@我的消息 | `chat message list-mentions` | `chat message list-all` | 都是跨会话时间范围查询,但 list-mentions 只返回@我的消息 | +| "查看我今天的所有消息" | 全量会话消息 | `chat message list-all` | `chat message list` | 用户未指定具体会话时用 list-all(跨所有会话),指定了具体群或人时用 list | +| "搜一下消息里的changefree链接" | 消息搜索 | `chat message search-advanced`(首选) | `chat search` | 推荐首选 search-advanced,它是 search 的严格超集(keyword 可选、支持多群、可叠加发送者/at 维度) | +| "按发送者搜索/指定多个群搜索/多维度搜消息" | 多维度搜索消息 | `chat message search-advanced`(首选) | `chat message search` | 推荐首选,支持关键词、发送者、@我、多个会话等维度组合 | +| "消息发没发成功/查询消息发送状态" | 查询消息发送状态 | `chat message query-send-status` | — | 需要 send 返回的 openTaskId | +| "撤回我发的消息/撤回消息/群主撤回消息/管理员撤回他人消息" | 撤回消息 | `chat message recall` | `chat message recall-by-bot` | recall 撤回个人消息或群主/管理员撤回他人消息,recall-by-bot 撤回机器人消息 | +| "未读消息会话/未读会话列表/我的未读会话" | 未读会话列表 | `chat message list-unread-conversations` | `chat message read-status` | list-unread-conversations 查哪些会话有未读;read-status 查具体消息的已读状态 | +| "谁看了这条消息/消息已读未读/查读状态" | 查询消息已读状态 | `chat message read-status` | `chat message list-unread-conversations` | read-status 查具体消息的已读人员;list-unread-conversations 查未读会话列表 | +| "查看消息的表情回复/拉取消息回复/消息的文字回复" | 批量拉取消息表情回复和文字回复 | `chat message list-emotion-replies` | — | 根据消息 ID 批量查询表情回复和文字回复 | +| "我和风雷的共同群/我们都在哪些群" | 搜索共同群 | `chat search-common` | `chat search` | search-common 按人员搜共同群,chat search 按群名搜索 | +| "查看会话分组/自定义分组" | 获取会话分组 | `chat category list` | — | 获取用户自定义的会话分组列表 | +| "某个分组下有哪些会话" | 分组下会话列表 | `chat category list-conversations` | — | 需先通过 category list 获取分组 ID | +| "创建会话分组/新建分组" | 创建自定义分组 | `chat category create` | — | 需指定 --title 分组名称 | +| "删除会话分组/移除分组" | 删除自定义分组 | `chat category delete` | — | 需指定 --category-id | +| "重命名分组/修改分组名称" | 更新分组名称 | `chat category rename` | — | 需指定 --category-id 和 --title | +| "把会话加到分组/将群移入分组" | 会话加入分组 | `chat category add-conv` | `chat category remove-conv` | add-conv 加入;remove-conv 移出 | +| "把会话从分组移出/从分组中删除" | 会话移出分组 | `chat category remove-conv` | `chat category add-conv` | remove-conv 移出;add-conv 加入 | +| "创建智能分组/新建智能会话分组/按关键词分组" | 创建智能会话分组 | `chat category create-smart` | `chat category create` | create-smart 按关键词/成员智能匹配;create 手动创建普通分组 | +| "分享群链接/把群分享给某人/群链接发到某群" | 分享群聊链接到会话 | `chat group share-invite` | `chat group invite-url` | share-invite 直接分享到目标会话/用户;invite-url 只获取链接不发送 | +| "根据群号查群信息/群号转openConversationId" | 群号查群聊信息 | `chat group get-by-group-id` | `chat search` | 已知数字群号时直接查;用户发消息只给了群号时,先用此工具将群号转为 openConversationId | +| "我创建的群/我管理的群/我是群主的群/我当管理员的群" | 拉取我创建/管理的群 | `chat group list-my-groups` | `chat search` | list-my-groups 按角色(群主/管理员)过滤当前用户的群;chat search 按关键词搜全部群 | +| "引用消息回复/回复那条消息" | 引用回复消息 | `chat message reply` | `chat message send` | reply 引用指定消息回复;send 普通发消息不引用 | +| "转发消息/把消息转到另一个群" | 转发单条消息 | `chat message forward` | `chat message send` | forward 转发已有消息到目标会话;send 是发新消息 | +| "置顶会话/取消置顶" | 设置/取消会话置顶 | `chat set-top` | `chat list-top-conversations` | set-top 设置或取消置顶;list-top-conversations 查看置顶列表 | +| "全员禁言/群禁言" | 全员禁言或解除 | `chat group-mute` | `chat group-mute-member` | group-mute 全员禁言或解除;group-mute-member 指定成员禁言 | +| "禁言某人/指定成员禁言" | 指定成员禁言 | `chat group-mute-member` | `chat group-mute` | group-mute-member 指定成员禁言/解禁;group-mute 全员禁言 | +| "查看群禁言配置/群里谁被禁言/禁言名单" | 查询群用户禁言配置 | `chat group 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-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 bots` | `chat group members` | bots 只列机器人;members 列普通群成员 | +| "从群里移除机器人/踢机器人" | 移除群内机器人 | `chat group members remove-bot` | `chat group members add-bot` | remove-bot 通过 openBotId 移除;add-bot 通过 robotCode 添加 | +| "批量查群成员详情/按ID查群成员/查看指定成员信息" | 批量查询群成员详情 | `chat group members list-by-ids` | `chat group members` | list-by-ids 根据 openDingTalkId 列表查询指定成员详情;members 分页查询全部成员列表 | +| "标记未读/设为未读/会话标未读" | 标记会话为未读 | `chat mark-unread` | `chat clear-red-point` | mark-unread 标记为未读;clear-red-point 清除红点(相反操作) | +| "清除红点/已读/取消未读" | 清除会话红点 | `chat clear-red-point` | `chat mark-unread` | clear-red-point 清除单个会话红点;mark-unread 标记未读(相反操作) | +| "全部已读/一键清除所有未读/红点清零" | 清除所有会话红点 | `chat clear-all-red-point` | `chat clear-red-point` | clear-all-red-point 清除所有会话红点;clear-red-point 只清单个会话 | +| "所有会话/全部会话列表/我的会话" | 分页获取全部会话 | `chat list-all-conversations` | `chat list-top-conversations` | list-all-conversations 返回所有会话;list-top-conversations 仅返回置顶会话 | +| "清空聊天记录/删除会话消息/清空消息" | 清空会话聊天记录 | `chat clear-messages` | `chat clear-red-point` | clear-messages 清空聊天记录;clear-red-point 仅清除未读红点 | +| "标记已读/消息已读/读了" | 标记消息已读 | `chat mark-read` | `chat clear-red-point` | mark-read 标记指定消息及之前的消息为已读;clear-red-point 仅清除红点不标记消息 | +| "我加入的所有群/拉取全部群列表/我的群" | 分页拉取所有群 | `chat group list-all` | `chat group list-my-groups` | list-all 返回用户加入的所有群;list-my-groups 仅返回用户作为群主/管理员的群 | +| "入群申请记录/谁申请入群/群申请列表/查看入群审批" | 拉取入群验证记录 | `chat group list-join-validations` | `chat group list-all` | list-join-validations 拉取入群验证/审批记录;list-all 拉取群列表 | +| "通过入群申请/拒绝入群/审批入群/同意加群" | 审批入群验证 | `chat group audit-join-validation` | `chat group list-join-validations` | audit-join-validation 执行审批操作;list-join-validations 仅查询记录 | +| "发群公告/在群里发公告/群公告置顶" | 发布群公告 | `chat group notice create` | `blackboard create` | notice create 是群维度公告(发到指定群聊);blackboard 是企业公告(面向全员,不可撤回) | +| "改群公告/修改群公告/更新群公告" | 修改群公告 | `chat group notice edit` | `chat group notice create` | edit 整体替换已有公告内容(需 dataId);create 发布新公告 | +| "查群公告/看群公告/群公告列表/群里有什么公告" | 查看群公告列表 | `chat group notice list` | `blackboard list` | notice list 查指定群的公告;blackboard list 查企业公告 | +| "群公告详情/公告已读人数/谁读了公告" | 查看群公告详情 | `chat group notice get` | `chat group notice list` | get 返回单条公告详情(含已读人数、点赞/评论数);list 分页拉公告列表 | +| "搜索机器人/找机器人/查机器人/帮我找XXX机器人" | 搜索全部可用机器人 | `chat bot find` | `chat bot search` | find 返回全部可用机器人(含他人/官方),额外返回 openDingTalkId(可用于给机器人发单聊消息);search 仅返回我创建的 | +| "给机器人发单聊/给机器人发消息/跟机器人聊天" | 给机器人发单聊消息 | `chat bot find` → `chat message send --open-dingtalk-id` | `chat bot search` | 必须先用 find 拿 openDingTalkId(search 没有此字段),再用 send --open-dingtalk-id 发单聊 | +| "我创建的机器人/我的机器人/我自己的机器人/查看我的机器人" | 搜索我创建的机器人 | `chat bot search` | `chat bot find` | search 仅返回当前用户自己创建的机器人(返回 robotCode + robotName,无 openDingTalkId);find 返回全部可用机器人 | +| "合并转发/批量转发/合并转发多条消息" | 合并转发多条消息 | `chat message combine-forward` | `chat message forward` | combine-forward 合并多条为一条转发;forward 转发单条消息 | +| "转发话题/转发话题消息/话题转发到另一个群" | 转发话题消息 | `chat message forward-topic` | `chat message forward` | forward-topic 专用于转发话题消息(需要话题ID);forward 转发普通单条消息 | +| "发卡片消息/推送流式卡片" | 创建并推送流式卡片 | `chat message send-card` | `chat message send` | send-card 发流式卡片;send 发普通文本/Markdown 消息 | +| "更新卡片/流式更新卡片" | 流式更新卡片内容 | `chat message update-card` | `chat message send-card` | update-card 更新已有卡片;send-card 创建新卡片 | +| "钉住消息/Pin消息/置顶消息到会话" | 钉住消息 | `chat message set-pin-msg` | `chat set-top` | set-pin-msg 钉住单条消息(Pin);set-top 置顶整个会话 | +| "取消钉住/Unpin消息" | 取消钉住消息 | `chat message unset-pin-msg` | `chat message set-pin-msg` | unset-pin-msg 取消钉住;set-pin-msg 设置钉住 | +| "查看钉住的消息/Pin列表/钉住消息列表" | 拉取钉住消息列表 | `chat message list-pin-msg` | `chat message list` | list-pin-msg 只返回被钉住的消息;list 拉取全部消息 | +| "置顶某条消息/把这条消息置顶" | 置顶消息 | `chat message set-top-msg` | `chat set-top` | set-top-msg 置顶会话内某条消息;set-top 置顶整个会话在列表中 | +| "取消置顶消息/取消消息置顶" | 取消置顶消息 | `chat message unset-top-msg` | `chat message set-top-msg` | unset-top-msg 取消置顶;set-top-msg 设置置顶 | +| "DING消息/查DING/DING历史" | 查询 DING 消息列表 | `ding message list` | `chat message list` | ding 是独立顶层命令;ding message list 查 DING 消息;chat message list 查普通聊天消息 | +| "DING接收状态/谁收到了DING" | DING 接收状态 | `ding message receiver-status` | `chat message read-status` | ding 是独立顶层命令;receiver-status 查 DING 接收;chat message read-status 查普通消息已读 | +| "发DING/DING通知" | 发送 DING 消息 | `ding message send` | `chat message send` | DING 是钉钉的强提醒(应用内/短信/电话),独立顶层命令;普通群消息用 chat | +| "撤回DING" | 撤回 DING 消息 | `ding message recall` | `chat message recall` | DING 撤回独立命令;chat recall 是撤回普通聊天消息 | +| "搜一下智能化方案/最近 OKR 相关邮件/最近发版相关消息" | 搜企业知识内容 | `aisearch enterprise` | `doc search` / `mail search` / `chat message search` | 跨文档、消息、日程、听记、邮件等企业内容语义检索走 enterprise;具体 `queries/types/time-range` 抽槽见 `aisearch.md` | +| "我发给某人的消息/邮件/文档/今天我干了什么" | 搜行为记录 | `aisearch behavior` | `chat` / `mail` / `doc` / `report` | 关注“谁对什么做过什么”,走 behavior;具体 `behavior-type/direction/chat-scope` 抽槽见 `aisearch.md` | +| "把这段文字翻译成英文/translate this" | 通用文本翻译 | `chat text translate` | `doc` / `aisearch` | 纯文本翻译,不是文档编辑或语义搜索 | +| "帮我把这个文档翻译成日文" | 文档内容翻译 | 先 `doc get` 再 `chat text translate` | `chat text translate` 直接传文件 | translate 仅支持纯文本,需先提取文档内容 | diff --git a/skills/multi/dingtalk-chat/references/lite-recipes.md b/skills/multi/dingtalk-chat/references/lite-recipes.md new file mode 100644 index 00000000..09f1c2ae --- /dev/null +++ b/skills/multi/dingtalk-chat/references/lite-recipes.md @@ -0,0 +1,7 @@ +# chat Lite Recipe + +本文件从单 Skill `lite-recipes.md` 拆分而来,仅保留与本产品相关的轻量流程。 + +## #1 消息沟通 + +所有消息沟通相关的命令详情、参数说明、意图路由和复合工作流,请查阅 [chat.md](./chat.md)。 diff --git a/skills/multi/dingtalk-chat/scripts/chat_export_messages.py b/skills/multi/dingtalk-chat/scripts/chat_export_messages.py index 2b77d85e..da7e50c4 100644 --- a/skills/multi/dingtalk-chat/scripts/chat_export_messages.py +++ b/skills/multi/dingtalk-chat/scripts/chat_export_messages.py @@ -16,11 +16,20 @@ import sys import json +import datetime import subprocess import argparse from typing import List, Any, Optional +def timestamp_to_datetime(ts_ms: int) -> str: + """将毫秒时间戳转换为 yyyy-MM-dd HH:mm:ss 格式""" + dt = datetime.datetime.fromtimestamp( + ts_ms / 1000, tz=datetime.timezone(datetime.timedelta(hours=8)) + ) + return dt.strftime("%Y-%m-%d %H:%M:%S") + + def run_dws( args: List[str], dry_run: bool = False, ) -> Optional[Any]: @@ -53,12 +62,20 @@ def search_group( return '' if not data: return None + if isinstance(data, dict) and data.get('success') is False: + print(f"搜索失败: {data.get('errorMsg', '未知错误')}", file=sys.stderr) + return None if isinstance(data, list): groups = data elif isinstance(data, dict): inner = data.get('result', data) if isinstance(inner, dict): - groups = inner.get('items', inner.get('groups', [])) + groups = ( + inner.get('value') + or inner.get('groups') + or inner.get('items') + or [] + ) elif isinstance(inner, list): groups = inner else: @@ -135,21 +152,32 @@ def main(): if not data: break - # 兼容两种结构: 顶层 messages / {result: {messages, hasMore}} + if isinstance(data, dict) and data.get('success') is False: + print(f"拉取失败: {data.get('errorMsg', '未知错误')}", file=sys.stderr) + break + if isinstance(data, list): page_msgs = data has_more = False - else: - container = data - inner = data.get('result') + next_cursor = None + elif isinstance(data, dict): + inner = data.get('result', data) if isinstance(inner, dict): - container = inner - page_msgs = container.get('messages') - if page_msgs is None and isinstance(inner, list): + page_msgs = inner.get('messages', []) + has_more = inner.get('hasMore', False) + next_cursor = inner.get('nextCursor') + elif isinstance(inner, list): page_msgs = inner - if not isinstance(page_msgs, list): + has_more = False + next_cursor = None + else: page_msgs = [] - has_more = bool(container.get('hasMore', False)) + has_more = False + next_cursor = None + else: + page_msgs = [] + has_more = False + next_cursor = None if not page_msgs: break @@ -161,13 +189,19 @@ def main(): if not has_more: break - last_msg = page_msgs[-1] - boundary_time = (last_msg.get('createTime') - or last_msg.get('createAt') - or last_msg.get('time', '')) - if not boundary_time or boundary_time == current_time: - break - current_time = boundary_time + # 翻页:优先用 nextCursor(毫秒精度,转为时间字符串),降级用最后消息的 createTime + if next_cursor: + current_time = timestamp_to_datetime(next_cursor) + else: + last_msg = page_msgs[-1] + boundary_time = ( + last_msg.get('createTime') + or last_msg.get('createAt') + or last_msg.get('time', '') + ) + if not boundary_time or boundary_time == current_time: + break + current_time = boundary_time print(f" 翻页 {page}: 已累计 {len(all_messages)} 条, 继续...") if not all_messages: @@ -180,12 +214,14 @@ def main(): print(f" ✓ 已导出 {len(all_messages)} 条消息到 {args.output}") else: for m in all_messages: - # 实际字段: sender/createTime/content (兼容旧字段名) - sender = (m.get('sender') or m.get('senderNick') - or m.get('senderOpenDingTalkId', '未知')) + sender = ( + m.get('sender') or m.get('senderNick') or '未知' + ) text = m.get('content') or m.get('text', '') - time_str = (m.get('createTime') or m.get('createAt') - or m.get('time', '')) + time_str = ( + m.get('createTime') or m.get('createAt') + or m.get('time', '') + ) print(f" [{time_str}] {sender}: {text[:80]}") print(f"\n合计: {len(all_messages)} 条消息 ({page} 页)") diff --git a/skills/multi/dingtalk-chat/scripts/chat_history_with_user.py b/skills/multi/dingtalk-chat/scripts/chat_history_with_user.py index b007c7be..8ef9c398 100644 --- a/skills/multi/dingtalk-chat/scripts/chat_history_with_user.py +++ b/skills/multi/dingtalk-chat/scripts/chat_history_with_user.py @@ -15,11 +15,20 @@ import sys import json +import datetime import subprocess import argparse from typing import List, Any, Optional +def timestamp_to_datetime(ts_ms: int) -> str: + """将毫秒时间戳转换为 yyyy-MM-dd HH:mm:ss 格式""" + dt = datetime.datetime.fromtimestamp( + ts_ms / 1000, tz=datetime.timezone(datetime.timedelta(hours=8)) + ) + return dt.strftime("%Y-%m-%d %H:%M:%S") + + def run_dws( args: List[str], dry_run: bool = False, ) -> Optional[Any]: @@ -122,7 +131,7 @@ def main(): '--format', 'json', ] if args.no_forward: - cmd_args.append('--forward=false') + cmd_args.extend(['--forward', 'false']) page_limit = min(int(remaining), 200) if args.limit > 0 else 0 if page_limit > 0: cmd_args.extend(['--limit', str(page_limit)]) @@ -135,21 +144,32 @@ def main(): if not data: break - # 兼容两种结构: 顶层 messages / {result: {messages, hasMore}} + if isinstance(data, dict) and data.get('success') is False: + print(f"拉取失败: {data.get('errorMsg', '未知错误')}", file=sys.stderr) + break + if isinstance(data, list): page_msgs = data has_more = False - else: - container = data - inner = data.get('result') + next_cursor = None + elif isinstance(data, dict): + inner = data.get('result', data) if isinstance(inner, dict): - container = inner - page_msgs = container.get('messages') - if page_msgs is None and isinstance(inner, list): + page_msgs = inner.get('messages', []) + has_more = inner.get('hasMore', False) + next_cursor = inner.get('nextCursor') + elif isinstance(inner, list): page_msgs = inner - if not isinstance(page_msgs, list): + has_more = False + next_cursor = None + else: page_msgs = [] - has_more = bool(container.get('hasMore', False)) + has_more = False + next_cursor = None + else: + page_msgs = [] + has_more = False + next_cursor = None if not page_msgs: break @@ -161,13 +181,19 @@ def main(): if not has_more: break - last_msg = page_msgs[-1] - boundary_time = (last_msg.get('createTime') - or last_msg.get('createAt') - or last_msg.get('time', '')) - if not boundary_time or boundary_time == current_time: - break - current_time = boundary_time + # 翻页:优先用 nextCursor(毫秒精度,转为时间字符串),降级用最后消息的 createTime + if next_cursor: + current_time = timestamp_to_datetime(next_cursor) + else: + last_msg = page_msgs[-1] + boundary_time = ( + last_msg.get('createTime') + or last_msg.get('createAt') + or last_msg.get('time', '') + ) + if not boundary_time or boundary_time == current_time: + break + current_time = boundary_time print(f" 翻页 {page}: 已累计 {len(all_messages)} 条, 继续...") if not all_messages: @@ -181,12 +207,14 @@ def main(): print(f" ✓ 已导出 {len(all_messages)} 条消息到 {args.output}") else: for m in all_messages: - # 实际字段: sender/createTime/content (兼容旧字段名) - sender = (m.get('sender') or m.get('senderNick') - or m.get('senderOpenDingTalkId', '未知')) + sender = ( + m.get('sender') or m.get('senderNick') or '未知' + ) text = m.get('content') or m.get('text', '') - time_str = (m.get('createTime') or m.get('createAt') - or m.get('time', '')) + time_str = ( + m.get('createTime') or m.get('createAt') + or m.get('time', '') + ) print(f" [{time_str}] {sender}: {text[:80]}") print(f"\n合计: {len(all_messages)} 条消息 ({page} 页)") diff --git a/skills/multi/dingtalk-contact/SKILL.md b/skills/multi/dingtalk-contact/SKILL.md index 7563892a..02a6d424 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 查/修改员工或自己的资料/创建或更新部门/创建或更新企业账号/邀请员工。Distinct from dingtalk-aisearch(模糊搜人首选:找同事/查上下级/谁负责)。命令前缀: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,14 +11,9 @@ metadata: # 钉钉通讯录 Skill -> 🧪 **EXPERIMENTAL · 试验版 / Preview** — multi 模式当前未达 stable 标准。全部 dingtalk-* skill 已通过 dispatch verifier,但接口、命名、跨 skill 引用后续可能调整;生产 / 共享环境请优先使用 mono 模式(`dws skill setup --mode mono`)。问题请提 issue 反馈。 - -> **PREREQUISITE:** Read the `dws-shared` skill first for auth, global flags, product routing, URL preflight, error codes, and safety rules. The `dws` binary must be on PATH. - - - -> ⚠️ **命令可用性以当前 dws 二进制为准**。服务发现已下线,本文档随内置 skill 发布;如果 `dws --help` 不存在,说明当前版本未暴露该命令。若命令存在但调用失败,请按错误中的 endpoint 或 tool 提示确认静态端点目录和后端工具注册。实际调用前可用 `dws --help` 或 `--dry-run` 验证。 +## 前置条件 — 执行操作前必读 +> **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)。 @@ -52,34 +46,78 @@ 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 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 "<手机号>"` | -| "修改员工姓名/部门/直属主管" | `dws contact user update --user-id ...` | -| "修改自己的昵称/头像" | `dws contact user update-self --nick "<昵称>"` / `--avatar-file-id ` | -| "创建部门" | `dws contact dept create --name "<部门名>" --create-dept-group=true\|false` | -| "修改部门名称/上级部门" | `dws contact dept update --dept --name "<新名称>" [--parent ]` | -| "更新企业账号资料" | `dws contact account update --user-id ...` | +| "部门成员列表" | `dws contact dept list-members --ids ` | +| "列出企业角色 / 有哪些角色" | `dws contact label list` | +| "按角色名查角色ID" | `dws contact label get --names "<角色名>"` | +| "查某角色下有哪些成员" | `dws contact label list-members --id ` | -## 评测高频硬约束 +## 标准 SOP(必遵流程) + +> 命中以下意图**必须**按对应 SOP 顺序执行;**禁止**跳步、替换命令、编造 userId。每条命令必须带 `--format json`。姓名模糊搜索、工号、职责与上下级走 `dingtalk-aisearch`;完整手机号精确反查走 contact;拿到 userId 后由 contact 补详情。 + +### SOP-1 搜人(search-person) + +**触发**:按姓名/工号/部门/职责/上下级找人,或用手机号线索做语义搜索。 + +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 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) + +**触发**:已有 userId 要查完整详情,或要拿 userId 给下游(发消息/建待办/约日程)。 + +1. **拿 userId(必须)**:`dws aisearch person --keyword "<姓名>" --dimension name --format json` → `userId`;多命中必须列候选请用户确认。 +2. **查详情(必须)**:`dws contact user get --ids --format json`,按返回字段(`orgEmployeeModel` 下部门/职位/邮箱)答复。 + +**禁止**:用模糊关键词直接调 `contact user search` 凑数、编造未返回字段。 + +### SOP-3 查自己(get-contact-self) + +**触发**:我的信息/我的 userId/我的部门。 + +1. **执行(必须)**:`dws contact user get-self --format json`,取 `orgEmployeeModel.userId` / `orgUserName` / `depts[].deptName` / 主管等。 + +**禁止**:把自己 userId 写死或猜测。 + +### SOP-4 查部门 / 角色(dept-and-relation) + +**触发**:部门列表/部门成员/角色/角色成员。 + +1. **执行(必须)**:搜部门 `dws contact dept search --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 维度直接猜上下级。 + +## 高频硬约束 - 通讯录问题必须调用 `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` 补部门/职位/邮箱。 +- 姓名模糊搜索、工号反查、职责或上下级搜索走 `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` 获取部门/职位后再消歧;不要默认取第一个。 -- "创建企业账号"必须优先匹配长模式 `account create`,不要误路由为 `org create`。 -- `user update` 至少修改姓名、部门、直属主管之一;`user update-self` 至少修改昵称、头像之一;`account update` 至少传一个修改项。 -- `dept create` 必须显式选择是否创建部门群;`dept update` 必须同时提供部门 ID 与新名称。 -- `org create`、`account create/update`、`user invite/update/update-self`、`dept create/update` 都是写操作;执行前确认当前企业、目标对象和字段,获得明确同意后才加 `--yes`。 +- 用户查询企业角色、角色ID、角色成员,或“管理员/财务/HR/主管”等角色类型人员时,走 `contact label list/get/list-members`;不要用 `dept list-members` 筛字段替代。 ## 跨产品协作 -- 模糊找人(姓名 / 上下级 / 谁负责 / 工号 / 手机号)→ 切到 `dingtalk-aisearch` +- 姓名模糊搜索、上下级、谁负责、工号反查、手机号语义搜索 → `dingtalk-aisearch` +- 完整手机号精确反查 → `dws contact user search-mobile` - 拿到 email 发邮件 → 切到 `dingtalk-mail` - 拿到 userId 发消息 → 切到 `dingtalk-chat` +## 局部意图与短流程 + +- [局部意图消歧](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 9f05e5ec..2ba71c68 100644 --- a/skills/multi/dingtalk-contact/references/08-directory.md +++ b/skills/multi/dingtalk-contact/references/08-directory.md @@ -1,55 +1,51 @@ # 通讯录(组织架构) -> **SKILL.md** 中 #8 仅内联 2 条 **lite**:`get-contact-self`、`search-user`。下列 recipe、专用规则与消歧请在命中 #8 且**超出**上述 lite 时阅读本文。 -> 产品命令见 [contact.md](./contact.md)。通用批量/并行见 `dws-shared/references/best_practices/_common/conventions.md`。 +> **SKILL.md** 中 #8 内联 3 条 **lite**:`get-contact-self`、`search-person`、`search-user`。下列 recipe、专用规则与消歧请在命中 #8 且**超出**上述 lite 时阅读本文。 +> 产品命令见 [contact.md](./contact.md)。通用批量/并行见 [_common/conventions.md](_common/conventions.md)。 ## 专用规则(#8 非 lite 步骤必守) - **角色类查人优先 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` 获取部门信息才能消歧。 -- **批量**:多个 `userId` 用 `contact user get --ids id1,id2,...`;多部门成员列表按需并行,遵守单次批量上限与 `dws-shared/references/best_practices/_common/conventions.md`。 -- **子部门枚举**:已知父部门 `deptId` 时**优先** `contact dept list-children --id <父deptId>` 直接拿到完整子部门列表;只知道部门名时先 `contact dept search --query "<父部门名>"` 取 `deptId` 再 `list-children`;用户明示的子部门名(无需枚举)可直接 `dept search` 命中。多子部门展开见 `explore-subdepts-and-members`。 + > **根因**:`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/references/aisearch.md`。 -- **需要 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/references/mail.md`。 +- **联系客户 + 发邮件** → 先用 `contact` 取 `orgAuthEmail`,再走 `dingtalk-mail`。 ## Recipe 速查(本表步骤,非 SKILL lite) | 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 --id <父deptId>` 直接取直属子部门列表
2. 只有部门名 → 先 `lookup-dept-id` 取 `deptId`,再 `list-children` | -| `list-dept-members` | 1. **优先** `python scripts/contact_dept_members.py --query "<部门名>"`
2. 备选:`lookup-dept-id` → `contact dept list-members --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-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 --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 --id <父deptId>` 拿到全部直属子 `deptId` 列表;若用户只点名了部分子部门,则改为对每个子部门名 `contact dept search --query "<子部门名>"`
3. 对子 `deptId`:`contact dept list-members --depts ,,...`(多部门按 conventions **并行/批量**)
4. 若还要成员详情:汇总 `userId` → `contact user get --ids …`(≤30 条/批,超出分批 + 用户确认) | +| explore-subdepts-and-members | 1. 取父部门 `deptId`:用户给了 ID 直接用;只给名字则 `contact dept search --query "<父部门名>"` → 父 `deptId`(多命中先消歧)
2. **优先** `contact dept list-children --dept <父deptId>` 拿到全部直属子 `deptId` 列表;若用户只点名了部分子部门,则改为对每个子部门名 `contact dept search --query "<子部门名>"`
3. 对子 `deptId`:`contact dept list-members --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 --id <父deptId>` 枚举全部直属子 `deptId`;用户已点名的子部门则用 `dept search` 精确命中
3. 需要逐层下钻时,对上一步拿到的子 `deptId` 继续 `dept list-children` 递归(注意控制深度,避免一次拉太多)
4. `contact dept list-members --depts ` → 按需 `user get` | -| user-detail-organization | 1. `contact user search --query "<关键词>"` → `userId`(多结果先消歧)
2. **必须** `contact user get --ids `
3. 若用户同时给出部门语境:叠加 `verify-user-dept` | -| batch-users-by-keyword | 1. `contact user search --query "<职位或技能关键词>"` → 多条
2. 提取 `userId`(≤30)→ `contact user get --ids …`
3. 结果过多时汇总或请用户收窄 | +| 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 new file mode 100644 index 00000000..803212b8 --- /dev/null +++ b/skills/multi/dingtalk-contact/references/_common/conventions.md @@ -0,0 +1,44 @@ +# 业务域通用规范 + +> 仅服务本 skill 已迁入的行动指南。安全门控、危险操作确认、`--format json` 等已在本 skill 的 `SKILL.md` 中定义,此处不重复。 + +## 批量查询规范 + +| # | 规范 | +|---|------| +| 1 | **并行查详情**:拿到多个 ID 后,用 `&` 合并到同一条 Shell 命令并行执行 + `wait`,**严禁逐条串行** | +| 2 | **翻页**:分页接口须拉全直至无更多 | +| 3 | **优先批量 API**:有批量接口则用批量;无则按 #1 并行 | +| 4 | **群消息**:必须先 `chat search --query` 得 `openConversationId`,再 `chat message list --group --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-contact/references/contact.md b/skills/multi/dingtalk-contact/references/contact.md index d9902869..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`。 ## 命令总览 @@ -41,16 +41,15 @@ Example: dws contact user search --query "张三" Flags: --query string 搜索关键词 (必填) -Returns: (顶层 `result` 为列表,每项包含以下字段) +Returns: (列表,每项包含以下字段) name string 成员姓名 nick string 成员昵称 - flowerName string 花名(无花名时为 null) userId string 成员 ID(仅同事关系时返回) title string 员工职位(仅同事关系时返回) openDingTalkId string 当前用户视角下的目标用户唯一标识,不可跨用户共享;可用于发消息等好友关系场景的操作 ``` -> **CAUTION:** 多人同名时禁止默认选第一个 — `user search` 不返回部门信息,须追加 `contact user get --ids userId1,userId2,...` 获取部门/职位后请用户确认。详见 [08-directory.md](./08-directory.md)「多命中」。 +> **CAUTION:** 多人同名时禁止默认选第一个 — `user search` 不返回部门信息,须追加 `contact user get --ids userId1,userId2,...` 获取部门/职位后请用户确认。详见 [08-directory.md](08-directory.md)「多命中」。 #### 按手机号搜索用户 ``` @@ -81,9 +80,9 @@ 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)由系统自动注入,无需手动传入 @@ -106,8 +105,9 @@ Flags: --master-user-id string 直属主管 userId(可选) --yes 跳过二次确认(可选) Notes: - - 姓名、部门、直属主管至少修改一项 - - 默认要求确认;自动化场景在已获得用户确认后显式传 --yes + - 至少需要一个修改项(--org-user-name、--depts 或 --master-user-id) + - 未加 --yes 时会交互式提示确认;脚本/自动化场景请显式带上 --yes + - 认证信息(corpId、optUserId)由系统自动注入,无需手动传入 ``` #### 更新自己的 profile 信息 @@ -118,15 +118,18 @@ 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 --yes + 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: - - 更新当前登录用户自己的昵称或头像,不修改员工组织信息 - - 昵称、头像至少修改一项;头像 fileId 需先上传到钉盘取得 - - 默认要求确认;自动化场景在已获得用户确认后显式传 --yes + - 更新当前登录用户自己的个人 profile 信息(昵称 / 头像),不是修改员工组织信息 + - 至少需要一个修改项(--nick 或 --avatar-file-id) + - 头像 fileId 需要先上传头像到钉盘获取 + - 未加 --yes 时会交互式提示确认;脚本/自动化场景请显式带上 --yes + - 认证信息(corpId、optUserId)由系统自动注入,无需手动传入 ``` ### profile (用户档案 / 花名册) @@ -143,8 +146,6 @@ Flags: 查询花名册有权限的字段列表,根据当前用户查询花名册有权限的字段列表。认证信息(corpId、optUserId)由系统自动注入,无需手动传入。 -> **前置条件:需操作人具备花名册管理权限。** 无权限时返回业务错误 `操作人无花名册管理权限`(非命令写法问题,不要改参数)。 - #### 查询员工花名册字段信息(个人档案) ``` Usage: @@ -161,8 +162,6 @@ Flags: 花名册字段包含:试用/转正信息、个人/家庭信息、学历信息、银行卡/合同信息、紧急联系人和其他企业自定义信息。 > **与 `contact user get` 的区别**:`user get` 返回组织管理信息(部门、主管、管理员权限),`user profile get` 返回个人档案信息(学历、家庭、银行卡等)。 -> -> **前置条件:需操作人具备花名册管理权限。** 无权限时返回业务错误 `操作人无花名册管理权限`(非命令写法问题,不要改参数)。 ### dismission (离职员工) @@ -189,9 +188,7 @@ Flags: 查询离职员工列表,支持按员工姓名、离职日期范围、部门进行过滤。认证信息(corpId、optUserId)由系统自动注入,无需手动传入。 `--start` 和 `--end` 必须同时设置或同时不设置,不允许只传其中一个。 -> **前置条件:需操作人具备已离职人员花名册管理权限。** 无权限时返回业务错误 `操作人无已离职人员花名册管理权限`(非命令写法问题,不要改参数)。 - -### dept (部门查询) +### dept (部门查询与管理) #### 搜索部门 ``` @@ -201,9 +198,6 @@ Example: dws contact dept search --query "技术部" Flags: --query string 搜索关键词 (必填) -Returns: (顶层 key 为 `deptList`,非 `result`,为列表,每项包含以下字段) - deptId int 部门 ID - deptName string 部门名称(命中关键词会用 … 高亮包裹,需自行去标签) ``` #### 获取部门详情 @@ -244,18 +238,15 @@ 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 列表,逗号分隔 (必填) -Returns: (顶层 key 为 `deptUserList`,非 `result`,为列表,每项形如 { "userInfo": { "name": ..., "userId": ... } }) - userInfo.name string 成员姓名 - userInfo.userId string 成员 ID + --ids string 部门 ID 列表,逗号分隔 (必填) Notes: - - **钉钉根部门 `deptId=1`**;查根部门直属成员用 `--depts 1` + - **钉钉根部门 `deptId=1`**;查根部门直属成员用 `--ids 1` - 仅返回**本部门**直接成员,**不含下级部门**成员;需含下级请先 `dept list-children` 枚举子部门,再对子 deptId 分别/合并调用 `list-members` - - 受组织架构可见性控制;`--depts` 支持逗号分隔批量查询多个部门 - - 跨层级成员展开见 [08-directory.md](./08-directory.md) 的 `cross-level-dept-members` recipe + - 受组织架构可见性控制;`--ids` 支持逗号分隔批量查询多个部门 + - 跨层级成员展开见 [08-directory.md](08-directory.md) 的 `cross-level-dept-members` recipe ``` #### 创建部门 @@ -263,16 +254,18 @@ Notes: 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 + 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 是否创建部门群(必填,必须显式传 true 或 false) + --parent string 父部门 ID(可选),不传默认根部门 + --create-dept-group bool 是否创建部门群(必填) --yes 跳过二次确认(可选) Notes: - - 不传 --parent 时默认根部门 deptId=1 - - 默认要求确认;自动化场景在已获得用户确认后显式传 --yes + - 父部门不传时默认钉钉根部门 deptId=1 + - --create-dept-group 必须显式指定 true 或 false + - 未加 --yes 时会交互式提示确认;脚本/自动化场景请显式带上 --yes + - 认证信息(corpId、optUserId)由系统自动注入,无需手动传入 ``` #### 更新部门 @@ -284,12 +277,14 @@ Example: dws contact dept update --dept 12345 --name "新名称" --parent 67890 --yes Flags: --dept string 部门 ID (必填) - --name string 新部门名称 (必填) + --name string 新部门名称(必填) --parent string 新父部门 ID(可选) --yes 跳过二次确认(可选) Notes: - - --dept 可由 dept search 或 dept list-children 获取 - - 默认要求确认;自动化场景在已获得用户确认后显式传 --yes + - --dept 为要更新的部门 ID,可通过 dept search 或 dept list-children 获取 + - --name 必填;--parent 可选,未指定时只更新部门名称 + - 未加 --yes 时会交互式提示确认;脚本/自动化场景请显式带上 --yes + - 认证信息(corpId、optUserId)由系统自动注入,无需手动传入 ``` ### label (角色查询) @@ -305,7 +300,7 @@ Example: Flags: 无 Notes: - - 无需参数,返回当前企业全部角色,按 `groupName` 分组,每组 `labels[]` 里每个角色是 `{labelId, name}`(字段名是 `name`,不是 `labelName`) + - 无需参数,返回当前企业全部角色列表(labelId、labelName等) - 用于不知道准确角色名称时先浏览全部角色 - 典型场景:用户说“企业所有主管/查所有管理员/财务人员有哪些”→ 先 label list 浏览全部角色,匹配目标角色后 label list-members 获取成员 ``` @@ -384,43 +379,46 @@ Aliases: 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 --yes + 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 企业账号自身昵称(可选) + --nick string 企业账号自身昵称,用于 profile 展示(可选) --avatar-file-id string 企业账号头像在钉盘的 fileId(可选) --yes 跳过二次确认(可选) Notes: - - 此命令修改企业账号,不用于普通员工;至少传一个修改项 - - 头像 fileId 需先上传到钉盘取得 - - 默认要求确认;自动化场景在已获得用户确认后显式传 --yes + - 更新指定企业账号用户的信息,不是修改普通员工信息 + - 至少需要一个修改项(--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 update`(需 userId;至少传一个修改项) -用户说"改自己的昵称/头像/profile" → `user update-self`(昵称、头像至少一项) -用户说"邀请员工/添加员工/加人/新员工入职/拉人进企业" → `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;至少传一个修改项) +用户说"更新企业账号/修改企业账号/改企业账号信息/改企业账号姓名/改企业账号部门/改企业账号主管/改企业账号昵称/改企业账号头像"(含"账号"且含"改/更新/修改")→ `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`(部门 ID 和新名称必填) +用户说"创建部门/新建部门/添加部门/成立部门/建部门" → `dept create`(需部门名;父部门可选,默认根部门) +用户说"更新部门/修改部门/改部门名/改部门名称/换部门名/改父部门/换上级部门" → `dept update`(需 deptId + name;parent 可选) 用户查询涵盖"角色"(主管/管理员/财务/HR/总经理等任意角色名)→ 统一走 `contact label` 链路,按下方决策树选命令: - 不知道角色名 / 枚举所有角色 → `label list` - 已知角色名,查ID或成员 → 先 `label get --names <名>` 拿ID,查成员再调 `label list-members --id `;**精确匹配无结果时降级 `label list` 模糊匹配** @@ -463,7 +461,7 @@ Notes: > - "我特别关注的人**最近发了什么消息**" → `chat message list-focused`(含"发""消息") > - "我关注的人**最近都说了啥**" → `chat message list-focused`(含"说") -组合场景(多子部门、跨层级成员、强消歧)见 [08-directory.md](./08-directory.md)。 +组合场景(多子部门、跨层级成员、强消歧)见 [08-directory.md](08-directory.md)。 ## 核心工作流 @@ -471,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 @@ -484,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 @@ -516,25 +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 -# 18. 修改员工组织信息 -dws contact user update --user-id user001 --org-user-name "张三三" --depts '[{"deptId":1}]' --yes --format json +# 19. 修改员工信息 +dws contact user update --user-id user001 --org-user-name "张三三" --depts '[{"deptId":1}]' --master-user-id manager001 --yes --format json -# 19. 更新自己的昵称或头像 -dws contact user update-self --nick "新昵称" --yes --format json +# 20. 更新自己的 profile 信息 +dws contact user update-self --nick "新昵称" --avatar-file-id "xxxxxx" --yes --format json -# 20. 创建部门 -dws contact dept create --name "新产品部" --parent 1 --create-dept-group=true --yes --format json +# 21. 创建部门 +dws contact dept create --name "新产品部" --parent 12345 --create-dept-group true --yes --format json -# 21. 更新部门 -dws contact dept update --dept 12345 --name "新部门名" --yes --format json - -# 22. 更新企业账号信息 -dws contact account update --user-id user001 --nick "新昵称" --yes --format json +# 22. 更新部门 +dws contact dept update --dept 12345 --name "新部门名" --parent 67890 --yes --format json ``` ## 上下文传递表 @@ -545,18 +543,18 @@ dws contact account update --user-id user001 --nick "新昵称" --yes --format j | `user get-self/search` | `orgAuthEmail` | mail message send 的 --to/--cc (跨产品) | | `user get-self/search` | `userId` | profile get 的 --staff-id | | `user profile fields` | `fieldCode` | profile get 的 --fields | -| `label list` | `labelId` / `name` | `label get --names` 或 `label list-members --id` | +| `label list` | `labelId` / `labelName` | `label get --names` 或 `label list-members --id` | | `label get` | `labelId` | `label list-members` 的 --id | -| `dept search/list-children/create` | `deptId` | dept get-info/list-children/list-members/update 的 --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` 必须同时设置或同时不设置,不允许只传其中一个 @@ -566,7 +564,8 @@ dws contact account update --user-id user001 --nick "新昵称" --yes --format j - `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/update`、`user invite/update/update-self`、`dept create/update` 都是写操作;执行前确认当前 profile、目标对象和修改字段。带确认保护的命令只在用户明确同意后追加 `--yes` +- `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 new file mode 100644 index 00000000..9f036d67 --- /dev/null +++ b/skills/multi/dingtalk-contact/references/intent-guide.md @@ -0,0 +1,14 @@ +# contact 局部意图消歧 + +本文件从单 Skill `intent-guide.md` 拆分而来,仅保留与本产品相关的跨产品消歧规则。 + +| 用户说... | 真实意图 | 应该用 | 不要用 | 理由 | +|---|---|---|---|---| +| "张三在哪个部门/张三的工号是多少" | 搜人后查通讯录详情 | `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 | +| "找一下张三/搜同事/找人" | 人员语义搜索 | `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是谁/完整手机号反查" | 精确手机号反查 | `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/references/lite-recipes.md b/skills/multi/dingtalk-contact/references/lite-recipes.md new file mode 100644 index 00000000..032ed75d --- /dev/null +++ b/skills/multi/dingtalk-contact/references/lite-recipes.md @@ -0,0 +1,30 @@ +# contact Lite Recipe + +本文件从单 Skill `lite-recipes.md` 拆分而来,仅保留与本产品相关的轻量流程。 + +## #8 通讯录 + +### get-contact-self + +`contact user get-self` → 当前用户 userId、部门、主管等 + +### search-person + +**搜人首选入口**。凡是“找人/搜人/找同事/谁负责/上级/下级/负责人/团队成员”均优先用 `aisearch person`: + +1. 从用户问题中提取 keyword(人名/业务关键词)和 dimension(维度),规则见 [aisearch.md](../../dingtalk-aisearch/references/aisearch.md)。 +2. `aisearch person --keyword "<关键词>" --dimension <维度>` +3. 结果中提取 `userId` 和 `title`(姓名)展示给用户。 +4. 若需要 userId 做后续操作(发消息/建待办),可直接使用结果中的 `userId`。 +5. **重名消歧**:多人同名时禁止默认选第一个,须追加 `contact user get --ids` 获取部门/职位后请用户确认,详见 [08-directory.md](../../dingtalk-contact/references/08-directory.md)「多命中」。 + +### search-user + +仅在以下**精确查询**场景使用,搜人请优先用 `search-person`: + +- 需要获取 userId 给其他产品使用(发消息/建待办/约日程) +- 已有 userId 需查完整详情(`contact user get --ids`) + +1. `aisearch person --keyword "<姓名>" --dimension name` → `userId`;**多命中须列出候选请用户确认**。 +2. **重名消歧**:多人同名时禁止默认选第一个,须追加 `contact user get --ids` 获取部门/职位后请用户确认,详见 [08-directory.md](../../dingtalk-contact/references/08-directory.md)「多命中」。 +3. 需详情时:`contact user get --ids `(多人可 `--ids id1,id2,...`) diff --git a/skills/multi/dingtalk-contact/scripts/contact_dept_members.py b/skills/multi/dingtalk-contact/scripts/contact_dept_members.py index 6280d849..519afacf 100644 --- a/skills/multi/dingtalk-contact/scripts/contact_dept_members.py +++ b/skills/multi/dingtalk-contact/scripts/contact_dept_members.py @@ -8,20 +8,12 @@ """ import sys -import re import json import subprocess import argparse from typing import List, Any, Optional -def strip_highlight(text: str) -> str: - """去除 dept search 返回名称中的 … 高亮标签。""" - if not isinstance(text, str): - return text - return re.sub(r'', '', text) - - def run_dws( args: List[str], dry_run: bool = False, ) -> Optional[Any]: @@ -75,11 +67,7 @@ def main(): elif isinstance(dept_data, dict): inner = dept_data.get('result', dept_data) if isinstance(inner, dict): - # dept search 返回顶层 deptList;兼容历史 items/depts 键。 - depts = (inner.get('deptList') - or inner.get('items') - or inner.get('depts') - or []) + depts = inner.get('items', inner.get('depts', [])) elif isinstance(inner, list): depts = inner else: @@ -92,9 +80,7 @@ def main(): for dept in depts: dept_id = dept.get('id') or dept.get('deptId') - dept_name = strip_highlight( - dept.get('name') or dept.get('deptName', '未知') - ) + dept_name = dept.get('name') or dept.get('deptName', '未知') if not dept_id: continue @@ -114,11 +100,8 @@ def main(): elif isinstance(members_data, dict): inner = members_data.get('result', members_data) if isinstance(inner, dict): - # list-members 返回 deptUserList;兼容历史 userlist/list 键。 - members = (inner.get('deptUserList') - or inner.get('userlist') - or inner.get('list') - or []) + members = inner.get('userlist', + inner.get('list', [])) elif isinstance(inner, list): members = inner else: @@ -130,12 +113,9 @@ def main(): continue for m in members: - # list-members 每项形如 {"userInfo": {"name":..., "userId":...}}, - # 成员字段嵌在 userInfo 下;兼容历史扁平结构。 - info = m.get('userInfo', m) - name = info.get('name') or info.get('userName', '未知') - title = info.get('title') or info.get('position', '') - uid = info.get('userId') or info.get('userid', '') + name = m.get('name') or m.get('userName', '未知') + title = m.get('title') or m.get('position', '') + uid = m.get('userId') or m.get('userid', '') line = f" 👤 {name}" if title: line += f" ({title})" diff --git a/skills/multi/dingtalk-dev/references/dev-index.md b/skills/multi/dingtalk-dev/references/dev-index.md new file mode 100644 index 00000000..af24e42c --- /dev/null +++ b/skills/multi/dingtalk-dev/references/dev-index.md @@ -0,0 +1,12 @@ +# 开发者能力索引 + +| 主题 | 详见 | +|---|---| +| 应用管理 | [`app.md`](./app.md) | +| 凭证/鉴权 | [`credentials.md`](./credentials.md) | +| 权限管理 | [`permission.md`](./permission.md) | +| 机器人 | [`robot.md`](./robot.md) | +| 长连接 | [`connect.md`](./connect.md) | +| 事件订阅 | [`event.md`](./event.md) | +| 成员管理 | [`member.md`](./member.md) | +| 最佳实践 | [`recipes.md`](./recipes.md) | diff --git a/skills/multi/dingtalk-dev/references/event.md b/skills/multi/dingtalk-dev/references/event.md index 83b459ae..a7313bd3 100644 --- a/skills/multi/dingtalk-dev/references/event.md +++ b/skills/multi/dingtalk-dev/references/event.md @@ -14,6 +14,7 @@ - 批量或全量订阅前,先把候选 `eventCode` 列给用户确认,再 `--dry-run` → `--yes`。 - 返回看 `events[].subscribed` 和 `pushType=STREAM`(事件走 Stream 长连推送;connect 与事件订阅的关系见下方「Stream 长连」)。 - `subscribe/unsubscribe` 的 `--event-codes` 必填,返回 `success/operation/unifiedAppId/eventCodes/needsPublish/versionRequiredAction`;失败时补 `errorCode/errorMsg/reason/retryable/action`。 +- `subscribe/unsubscribe` 返回 `needsPublish=true` 或非空 `versionRequiredAction` 时,按 [version.md](version.md) 继续 `version create → check-approval → publish → status`;进入 `RELEASE` 后重新 `event list`,确认目标 `events[].subscribed` 与本次操作一致。进入审核态则报告待审批;需要选择审批人时停下让用户选择。 - 如果订阅失败、返回提示长链接未在线(是泛化错误:`reason=business_error`、`message` 含「长链接未在线」、`server_error_code=-1`;没有 STREAM_NOT_CONNECTED 这类结构化错误码,也没有 action 字段),先执行 `dev connect` 建联,再重试订阅。 ## Stream 长连:connect 与事件订阅的关系 diff --git a/skills/multi/dingtalk-devapp/references/event.md b/skills/multi/dingtalk-devapp/references/event.md deleted file mode 100644 index 1ffd0267..00000000 --- a/skills/multi/dingtalk-devapp/references/event.md +++ /dev/null @@ -1,94 +0,0 @@ -# 事件订阅 - -管理开放平台企业内部应用的事件订阅:列出可订阅事件、按事件码列表批量订阅/退订。 - -> 所有命令通过 `--unified-app-id` 定位应用;`corpId` / `userId` 由 MCP 系统上下文注入,CLI 不传。订阅/退订是写操作,先 `--dry-run`,确认后再 `--yes`。 - -## 可订阅事件列表 - -**搜索优先**:定位事件码时优先用 `--keyword` 搜索;只有用户明确要「全部事件」时才不带 `--keyword` 翻全量。 - -```bash -# 首选:按关键词搜索定位事件码 -dws dev app event list --unified-app-id --keyword 通讯录 --page-size 20 --format json -# 兜底:用户明确要全部事件时才翻全量 -dws dev app event list --unified-app-id --page-size 20 --format json -``` - -MCP tool: `list_dev_app_events`。`--keyword` 按事件码或事件名称模糊匹配。返回 `pushType=STREAM`、`events[]`、`hasMore`、`nextCursor`、`pageSize`;`hasMore=true` 时下一页继续传 `--cursor `,逐页处理。`events[]` 每项含: - -| 字段 | 含义 | -|------|------| -| `eventCode` | 事件码,订阅/退订时传入,如 `user_add_org` | -| `eventName` | 事件名称,如「通讯录用户增加」 | -| `subscribed` | 当前是否已订阅 | - -常见事件码示例:`user_add_org`(通讯录用户增加) / `user_modify_org`(用户更改) / `user_leave_org`(用户离职) / `org_dept_create`(部门创建) / `org_dept_modify`(部门修改) / `org_dept_remove`(部门删除)。以 `event list` 实际返回为准。 - -## 订阅事件 - -```bash -dws dev app event subscribe --unified-app-id --event-codes user_add_org,org_dept_modify --dry-run --format json -dws dev app event subscribe --unified-app-id --event-codes user_add_org,org_dept_modify --yes --format json -``` - -MCP tool: `subscribe_dev_app_events`(入参 `unifiedAppId` + 必填数组 `eventCodes`,一次订阅多个事件码)。成功返回 `success/operation=SUBSCRIBE/unifiedAppId/eventCodes/needsPublish/versionRequiredAction`;失败时补 `errorCode/errorMsg/reason/retryable/action`。 - -## 退订事件 - -```bash -dws dev app event unsubscribe --unified-app-id --event-codes user_add_org,org_dept_modify --dry-run --format json -dws dev app event unsubscribe --unified-app-id --event-codes user_add_org,org_dept_modify --yes --format json -``` - -MCP tool: `unsubscribe_dev_app_events`(入参 `unifiedAppId` + 必填数组 `eventCodes`,一次退订多个事件码)。成功返回 `success/operation=UNSUBSCRIBE/unifiedAppId/eventCodes/needsPublish/versionRequiredAction`;失败时补 `errorCode/errorMsg/reason/retryable/action`。 - -## 字段映射 - -| CLI | MCP | 必填 | 说明 | -|-----|-----|------|------| -| `--unified-app-id` | `unifiedAppId` | 是 | 统一应用 ID | -| `--keyword` | `keyword` | list 可选 | 事件搜索关键词,匹配事件码或事件名称 | -| `--cursor` | `cursor` | list 可选 | 服务端返回的下一页游标 | -| `--page-size` | `pageSize` | list 可选 | 单页数量 | -| `--event-codes` | `eventCodes` | subscribe/unsubscribe 必填 | 事件码列表,逗号分隔,取自 `event list` | - -## 灰度应用需发布版本生效(重要) - -对**灰度统一应用**,`subscribe`/`unsubscribe` 只会把变更**暂存到版本元数据**,不会立即生效,需后续走版本发布链路: - -```text -event subscribe/unsubscribe 订阅变更写入版本元数据 - → version create 创建新版本 - → version check-approval 预检审批 - → version publish 发布后订阅变更才生效 - → version status 轮询发布/审批状态 -``` - -详见版本发布文档。 - -## 单步流程 - -```text -1. 定位事件码(搜索优先) - dws dev app event list --unified-app-id --keyword <关键词> --format json - → 从返回里挑 eventCode - → 用户明确要全部事件时,才不带 --keyword 翻全量(逐页) - -2. 订阅(批量/全量前先列候选 eventCode 给用户确认) - dws dev app event subscribe --unified-app-id --event-codes , --dry-run --format json - → 确认后加 --yes - -3. 验证 - dws dev app event list --unified-app-id --keyword <关键词> --format json - → 确认对应事件 subscribed=true(灰度应用需先发布版本) -``` - -## 错误处理 - -| 情况 | 处理 | -|------|------| -| `--event-codes` 缺失 | CLI 直接报错,先 `event list` 取事件码 | -| 订阅后 `subscribed` 仍为 false | 灰度应用需先 `version publish` 发布版本 | -| 返回提示「长链接未在线」(泛化错误 `reason=business_error`、`server_error_code=-1`,无 STREAM_NOT_CONNECTED 码) | 先执行 `dev connect` 建联,再重试订阅 | -| `ServiceResult.success=false` | 透传 `errorCode/errorMsg` | diff --git a/skills/multi/dingtalk-devdoc/SKILL.md b/skills/multi/dingtalk-devdoc/SKILL.md deleted file mode 100644 index a3e50ad7..00000000 --- a/skills/multi/dingtalk-devdoc/SKILL.md +++ /dev/null @@ -1,36 +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 反馈。 - -> **PREREQUISITE:** Read the `dws-shared` skill first for auth, global flags, product routing, URL preflight, error codes, and safety rules. The `dws` binary must be on PATH. - - - -> ⚠️ **命令可用性以当前 dws 二进制为准**。服务发现已下线,本文档随内置 skill 发布;如果 `dws --help` 不存在,说明当前版本未暴露该命令。若命令存在但调用失败,请按错误中的 endpoint 或 tool 提示确认静态端点目录和后端工具注册。实际调用前可用 `dws --help` 或 `--dry-run` 验证。 - - -> 命令参考:[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` diff --git a/skills/multi/dingtalk-devdoc/references/devdoc.md b/skills/multi/dingtalk-devdoc/references/devdoc.md deleted file mode 100644 index 1569a49b..00000000 --- a/skills/multi/dingtalk-devdoc/references/devdoc.md +++ /dev/null @@ -1,49 +0,0 @@ -# 开放平台文档 (devdoc) 命令参考 - -搜索钉钉**开放平台**开发文档,用于回答开发者关于 OpenAPI、字段、错误码、接入指南、配额等技术问题。 - -## 命令总览 - -### 搜索开发文档 -``` -Usage: - dws devdoc article search [keyword] [flags] -Example: - dws devdoc article search "MCP" # 关键词可作位置参数,等价于 --query - dws devdoc article search --query "OAuth2 接入" - dws devdoc article search --query "消息卡片" --page 2 --size 5 - dws devdoc article search --query "机器人" --size 10 -Flags: - --query string 搜索关键词 (必填;也可用位置参数 [keyword] 传) - --page string 分页页码 (从 1 开始,default "1") - --size string 分页大小 (default "10") -``` - -## 意图判断 - -用户问开放平台 API / 字段 / 错误码 / SDK / 鉴权 / 回调 / 配额相关的技术细节: -- 走 `devdoc article search`,把用户问的关键短语作为 `--query` - -关键区分: -- devdoc(钉钉**开放平台**开发者文档,面向研发) vs doc(钉钉在线文档,面向普通用户内容) -- devdoc 只做搜索,不做读取;命中条目返回标题、摘要、文档链接,由 Agent 引用链接或进一步浏览 - -## 核心工作流 - -```bash -# 开发者问"OAuth2 怎么接" -dws devdoc article search --query "OAuth2 接入" --format json - -# 命中结果多时翻页 -dws devdoc article search --query "消息卡片" --page 2 --size 5 --format json - -# 查错误码 / 字段含义 -dws devdoc article search --query "errcode 40078" --format json -``` - -## 注意事项 - -- `--query` 必填;建议传用户原话里的关键名词(API 名、错误码、能力名),不要过度改写 -- 返回按相关性排序,默认 `--size 10`;要拿更多结果时先翻页,再考虑换关键词 -- 命中结果里的链接是钉钉开放平台公开文档,可直接给用户做参考 -- 不要把 devdoc 用来查业务数据(那是 aitable / doc / report 的事);devdoc 只查**官方开发者文档** diff --git a/skills/multi/dingtalk-ding/SKILL.md b/skills/multi/dingtalk-ding/SKILL.md deleted file mode 100644 index 92de66c7..00000000 --- a/skills/multi/dingtalk-ding/SKILL.md +++ /dev/null @@ -1,51 +0,0 @@ ---- -name: dingtalk-ding -description: DING 紧急消息(应用内 / 短信 / 电话)。Use when 用户说 DING一下/紧急通知/电话DING/短信DING/必达消息/电话叫人。Distinct from 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 反馈。 - -> **PREREQUISITE:** Read the `dws-shared` skill first for auth, global flags, product routing, URL preflight, error codes, and safety rules. The `dws` binary must be on PATH. - - - -> ⚠️ **命令可用性以当前 dws 二进制为准**。服务发现已下线,本文档随内置 skill 发布;如果 `dws --help` 不存在,说明当前版本未暴露该命令。若命令存在但调用失败,请按错误中的 endpoint 或 tool 提示确认静态端点目录和后端工具注册。实际调用前可用 `dws --help` 或 `--dry-run` 验证。 - - -> 命令参考:[ding.md](references/ding.md)。 - - -## Shortcuts(无专用脚本/recipe 时优先) - -以下 shortcut 同时进入公开 catalog 与 Runtime Schema。先按本 skill 的意图表、脚本和 recipe 路由:存在精确覆盖该场景的专用脚本/recipe 时按其执行;否则用户意图命中时,shortcut 优先于手写原子命令。用 leaf Schema(例如 `dws schema --cli-path "ding +" --format json`)读取 Agent 选择、参数、约束、风险和确认语义;用 `dws shortcut list --service ding --format json` 批量发现;最后以 `dws ding --help` 核对当前 Cobra flags。 - -| 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 ` | - -## 跨产品协作 - -- 接收人是人名 → 先用 `dingtalk-aisearch` 拿 `userId` -- 普通通知(不需必达)→ 切到 `dingtalk-chat` diff --git a/skills/multi/dingtalk-ding/references/ding.md b/skills/multi/dingtalk-ding/references/ding.md deleted file mode 100644 index 6b45c656..00000000 --- a/skills/multi/dingtalk-ding/references/ding.md +++ /dev/null @@ -1,87 +0,0 @@ -# DING 消息 (ding) 命令参考 - -## 命令总览 - -### 发送 DING 消息 -``` -Usage: - dws ding message send [flags] -Example: - dws ding message send --robot-code --users , --content "请查看" -Flags: - --content string 消息内容 (必填) - --robot-code string 机器人 ID (必填, 可从 应用管理→机器人 获取, 或设 DINGTALK_DING_ROBOT_CODE) - --users string 接收人 userId 列表 (必填) - --type string 提醒类型: app/sms/call (默认 app) -``` - -### 撤回 DING 消息 -``` -Usage: - dws ding message recall [flags] -Example: - dws ding message recall --robot-code --id -Flags: - --id string DING 消息 ID (必填) - --robot-code string 机器人 ID (必填, 或设 DINGTALK_DING_ROBOT_CODE) -``` - -### 查询 DING 消息历史 - -列表项会同时返回 DING 的 `content`、`openDingId` 与状态,可直接读取钉内容,无需再调用详情接口。 - -``` -Usage: - dws ding message list [flags] -Example: - dws ding message list --type ALL - dws ding message list --type UNREAD - dws ding message list --type SEND --cursor 10 -Flags: - --cursor int 分页游标 (首次传 0, 翻页传返回的 nextCursor) - --type string 消息类型: ALL / UNREAD / SEND / NEW_COMMENT / DELETED (默认 ALL; 服务端不接受空值, CLI 不传时会自动按 ALL 查询, 故裸跑 `ding message list` 即可) -``` - -### 查看 DING 接收状态 -``` -Usage: - dws ding message receiver-status [flags] -Example: - dws ding message receiver-status --ding-id - # 查询 dingId: dws ding message list --type ALL -Flags: - --ding-id string DING 消息 openDingId (必填) -``` - -## 意图判断 - -用户说"DING 一下/紧急通知/电话提醒" → `message send` -用户说"撤回 DING" → `message recall` -用户说"DING 消息/查 DING/DING 历史/我的 DING" → `message list` -用户说"DING 接收状态/谁收到了 DING/DING 已读" → `message receiver-status` - -关键区分: -- ding(紧急提醒, 支持电话/短信) vs bot(常规群/单聊消息) -- sms/call 类型有通信费用 - -## 核心工作流 - -```bash -# 应用内 DING (免费) -dws ding message send --robot-code --type app --users userId1,userId2 --content "请查看" --format json - -# 电话 DING (紧急, 有成本!) -dws ding message send --robot-code --type call --users userId1 --content "紧急告警" --format json - -# 撤回 -dws ding message recall --robot-code --id --format json -``` -## 上下文传递表 -| 操作 | 提取 | 用于 | -|------|------|------| -| `message send` | `openDingId` | message recall 的 --id | -| `message list` | `openDingId` | message receiver-status 的 --ding-id | -## 注意事项 -- `--robot-code` 从钉钉开放平台 **应用管理 → 机器人** 中获取,也可设环境变量 `DINGTALK_DING_ROBOT_CODE` -- sms/call 类型有通信费用,使用前需和用户确认 -- 默认 `--type app`(应用内 DING,免费) diff --git a/skills/multi/dingtalk-doc/SKILL.md b/skills/multi/dingtalk-doc/SKILL.md index 723ebf5a..8e0f602e 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 用户说 写文档/读文档/按大纲、范围、章节或标签局部读取 JSONML/创建或编辑文档/文档块/文档评论及@群/Markdown 写入在线文档。Distinct from dingtalk-markdown(原生.md纯文本文件)、dingtalk-drive(通用文件存储)、dingtalk-aitable(数据表格)、dingtalk-wiki(知识库空间)。命令前缀: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,31 +11,19 @@ metadata: # 钉钉文档 Skill -> 🧪 **EXPERIMENTAL · 试验版 / Preview** — multi 模式当前未达 stable 标准。全部 dingtalk-* skill 已通过 dispatch verifier,但接口、命名、跨 skill 引用后续可能调整;生产 / 共享环境请优先使用 mono 模式(`dws skill setup --mode mono`)。问题请提 issue 反馈。 +## 前置条件 — 执行操作前必读 -> **PREREQUISITE:** Read the `dws-shared` skill first for auth, global flags, product routing, URL preflight, error codes, and safety rules. The `dws` binary must be on PATH. +> **CRITICAL — 执行任何 `dws` 操作前,MUST 先用 Read 工具完整读取 [`dws-shared`](../dws-shared/SKILL.md)。**该轻量文件包含全局执行契约、安全底线及 shared references 的按需加载导航;不要预加载其全部 references。 - - -> ⚠️ **命令可用性以当前 dws 二进制为准**。服务发现已下线,本文档随内置 skill 发布;如果 `dws --help` 不存在,说明当前版本未暴露该命令。若命令存在但调用失败,请按错误中的 endpoint 或 tool 提示确认静态端点目录和后端工具注册。实际调用前可用 `dws --help` 或 `--dry-run` 验证。 - - -> 命令参考:[doc.md](references/doc.md);剧本:[04-document.md](references/04-document.md);URL 识别与类型探测:[url-patterns.md](references/url-patterns.md)。 - -## URL 预检(含 alidocs URL 时必读) - -输入含 `alidocs.dingtalk.com` URL 时,该域名下存在多种路径格式:`/i/p/...`(分享短链)、`/i/nodes/...`(节点链接,类型需探测)、`/spreadsheetv2/...`(电子表格直链)、`/document/edit|preview?dentryKey=...`(文档链接)等,每种处理流程不同。**必须先读 [url-patterns.md](references/url-patterns.md) 中的「alidocs URL 分流决策」**,按规则识别 URL 类型后再选择对应命令;其中 `/document/edit|preview?dentryKey=...` 直接路由到 `doc`,将完整 URL 原样传给 `--node`,**不要**提取 `dentryKey` 当裸 nodeId。 +> 命令参考:[doc.md](references/doc.md);剧本:[04-document.md](references/04-document.md)。 ## 参数硬约束 - 创建文档只用 `--name`,不要写 `--title`。 - 目标文件夹只用 `--folder <文档文件夹nodeId或URL>`,不要写 `--parent` / `--parent-node` / `--parent-id`。 - 目标知识库只用 `--workspace `,不要写 `--space-id` / `--spaceId`。 -- 文档内容:`create` / `update` 都只接 `--content` / `--content-file`,不要写 `--markdown`。 +- 文档内容只用 `--content` / `--content-file`,不要写 `--markdown`。 - 复杂内容(换行、表格、代码块、长 Markdown)先写临时 `.md`,再用 `--content-file`,不要把大段 Markdown 塞进命令行。 -- JSONML 局部读取使用 `doc read --content-format jsonml --scope outline|range|section|tags`;`range` 传 `--start-block-id` / `--end-block-id`,`tags` 传 `--tags`,大纲深度用 `--max-depth`。 -- 局部读取返回的是 JSONML 片段,不应当作整篇文档缓存或直接全量覆盖回写。 -- `comment create` / `reply` / `update` 支持 `--mentioned-open-conversation-id` @群;`create-inline` 暂不支持 @群。 - 每次 `create` / `update` / `block insert` / `media insert` 后必须 `dws doc read` 或 `dws doc block list` 回读关键内容。 @@ -71,34 +58,105 @@ metadata: |--------|------| | "创建文档(短内容)" | `dws doc create --name "<标题>" --content "<内容>"` | | "创建+写入(长内容自动分块)" | `python scripts/doc_create_and_write.py --name "<标题>" --content "<内容>" [--mode append\|overwrite]` | -| "搜文档"(全局) | `dws drive search --query "<关键词>"`(`doc search` 已弃用;切到 `dingtalk-drive`) | -| "在某知识库里搜文档" | `dws wiki node search --workspace --query "<关键词>"` | -| "读文档内容" | `dws doc read --node ` | -| "只读大纲/块范围/章节/标签内容" | `dws doc read --node --content-format jsonml --scope outline\|range\|section\|tags ...` | +| "搜在线文字文档 / 找在线文字文档" | `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 create\|reply\|update ... --mentioned-open-conversation-id ` | -| "删除文档评论" | `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.md` 模板管理) | +| "保存 / 查看 / 回滚在线文字文档(adoc)版本" | `dws doc version save/list/revert` | -## 评测/多步文档短路径 +## 标准 SOP(必遵流程) + +> 命中以下意图**必须**按对应 SOP 顺序执行;**禁止**跳步、替换命令、编造 nodeId/blockId。结构化命令必须带 `--format json`,执行后必须按"验证"步回读真实字段。文件类操作(上传/下载/复制/移动)切 `dingtalk-drive`;知识库节点管理切 `dingtalk-wiki`。 + +### SOP-1 查找并读取文档(query-doc) + +**触发**:查文档/读文档/某文档在哪/搜文档内容。 + +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-markdown` 用 `dws markdown fetch --node --format json` 读取原文;仅需文件实体下载时切 `dingtalk-drive` 用 `drive download`。 + - `extension=axls`:切到 `dingtalk-misc`,读取 `references/sheet.md` 后按电子表格意图执行。 + - `extension=able`:切到 `dingtalk-aitable`。 + - `extension=xlsx` / `xls` / `xlsm` / `csv` 或其他普通文件:切到 `dingtalk-drive`;不得执行 `dws doc read`。 + +**禁止**:用户未提供目标时跳过搜索并猜 nodeId、未探测类型就执行 `doc read`、把整篇文档原样贴给用户。 + +### SOP-2 创建文档并写入(create-doc) + +**触发**:新建文档/写一篇/建文字文档。 + +1. **执行(必须)**:`dws doc create --name "<标题>" --content-file [--folder | --workspace ] --format json`(长/多行内容用 `--content-file`,不要用 `--content` 拼长串;用户未指定位置时省略两个位置参数,创建到“我的文档”根目录)。 +2. **验证(必须)**:从返回取 `nodeId`,立即 `dws doc info --node --format json` 回读确认。 + +**禁止**:创建后不回读就答复"已创建"、把 `--folder` 当成空间 ID 传入。 + +### SOP-3 覆盖/追加内容(write-content) + +**触发**:覆盖写/追加内容/改文档正文。 + +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` 直接覆盖未确认的长文档。 + +### SOP-4 导出 / 下载(export-doc) + +**触发**:导出文档/下载文档/转 PDF·Markdown。 + +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`(普通文件会失败)、跳过 `drive info` 判断。 + +### SOP-5 块级编辑(block-edit) + +**触发**:插引用块/代码块/表格/分栏/图片/附件,或删除某块。 + +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、未确认就删除、把完整 `--help` 输出当成最终结果答复用户。 +### SOP-6 导入本地文件为在线文档(import-file) + +**触发**:导入 Word / Excel / Markdown / 本地文件为在线文档。 + +1. **判类型(必须)**:确认用户意图是“导入为在线文档”,不是“上传到钉盘”。仅上传存储时切 `dingtalk-drive`。 +2. **执行(必须)**:`dws doc import --file --folder --name "<标题>" --format json`;复杂参数和限制见 [doc-import.md](references/doc/doc-import.md)。 +3. **验证(必须)**:拿到返回 `nodeId` 后执行 `dws doc info --node --format json`,必要时 `dws doc read --node --format json` 抽样核对内容。 + +**禁止**:把上传文件到钉盘误当成 doc import;不知道目标文件夹 nodeId 时先切 `dingtalk-drive`/`dingtalk-wiki` 查询。 + +## 多步文档短路径 -- 知识库「评测记录」下按日期文件夹执行:`dws wiki space search --keyword "评测记录" --format json` → `dws wiki node list --workspace --format json`(或 `dws drive list --workspace `;`doc list` 已弃用)→ 找 `评测-doc-YYYYMMDD`;不存在则 `dws wiki node create --workspace --name "评测-doc-YYYYMMDD" --type folder --format json`(`doc folder create` 已弃用)。 - 在目标文件夹创建文字文档:`dws doc create --name "<标题>" --folder --content-file --format json`。拿到 `nodeId` 后立即回读。 -- 块级编辑固定顺序:`doc block list --node ` → 选 `blockId` → `doc block insert/update/delete` → `doc block list` 验证。删除块必须已有用户明确删除意图或二次确认。 +- 块级编辑固定顺序:`doc block list --node ` → 插入用 `--ref-block`/`--parent-block`,更新或删除用 `--block-id` → `doc block list` 验证。删除块必须已有用户明确删除意图或二次确认。 - 插入引用块、代码块、表格、分栏、附件、图片时,优先读 [doc.md](references/doc.md) 对应小节,不要只停在"准备查看 help"。说出"我将插入..."后必须立即执行对应 terminal 调用。 - 用户要求多个子文档/附件/块操作时,按 checklist 串行完成;最后一条 assistant 消息不能停在"接下来我要...",必须有实际工具调用或明确失败原因。 -- 用户说"下载文件"(已有文件)时用 `dws drive download --node ... --output `(`doc download` 已弃用,切到 `dingtalk-drive`);用户说"导出在线文档为 docx"时用 `doc export --node ... --output `(内容级命令,未迁移)。 -- 所有 dws 命令带 `--format json`;仅参数不确定时查 `--help`,不要把完整 help 当成最终结果。 +- 用户说“读取并下载/导出”时,先 `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` -- 原生 `.md` 文件读取、创建、全量覆盖或局部替换 → 切到 `dingtalk-markdown` - 知识库空间管理 → 切到 `dingtalk-wiki` - 数据表 → 切到 `dingtalk-aitable` +- 原生 `.md` 文件读取、创建、全量覆盖或局部替换 → 切到 `dingtalk-markdown` - 长篇报告生成(多源采集 + 写文档)→ 此 skill 提供 `doc_create_and_write.py` 脚本 +## 局部意图与短流程 + +- [局部意图消歧](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 c884341e..706567a9 100644 --- a/skills/multi/dingtalk-doc/references/04-document.md +++ b/skills/multi/dingtalk-doc/references/04-document.md @@ -1,81 +1,31 @@ # 文档知识 -> 通用规范见 [_common/conventions.md](_common/conventions.md)。 +> lite recipe 见 [SKILL.md 速查表](../SKILL.md)。 | Recipe | 行动指南(固定路线) | |--------|-------------------| -| write-doc | 1. 按[「多源并行采集」](_common/conventions.md#多源并行采集公共模式)执行
2. **先把内容写入临时文件**(Linux/Mac `/tmp/.md`,Windows `%TEMP%\.md`)—— 含多行/表格/长文本必须走文件,不要把 markdown 直接作为命令行字符串
3. **单步创建**(< 200KB):`doc create --name "<文档名>" --content-file [--folder ] [--workspace ]`(`--folder` 只传文档文件夹 nodeId / alidocs 文件夹 URL,不传数字 dentryId)
4. **超长兜底**(> 200KB):**必须先向用户提示截断风险**(详见下方「分块 append 截断风险提示」),用户确认后再执行:`doc create --name "<文档名>" [--folder/--workspace]` → `nodeId` → 按段落切 ≤200KB 片段(不断表格) → 每片 `doc update --node --content-file --mode append`
5. **回读校验**(必须):所有写入完成后,执行 `doc read --node ` 回读文档,校验关键标题/段落是否完整写入(详见下方「doc update 回读校验规范」)
备选(仅短内容 <2KB 且无换行/表格):`doc create --name "..." --content "..."` | -| search-docs-and-share | 1. `doc search --query "<关键词>"` → 取 `nodeId` + 标题建索引(不读全文)
2. `doc read --node `(追问按需,最多 2 篇) | -| create-knowledge-base | 1. 创建知识库空间取 `WS_ID`
2. `doc create --name "<文档名>" --workspace ` → 取 `nodeId`
3. `doc list --workspace ` 确认 | +| import-file | 1. **直接执行** `dws doc import --file <本地文件路径> --format json`(一条命令完成上传+转换+创建)
2. 从返回中提取 `documentUrl` 并告知用户
3. **禁止先 Read 文件内容再 `doc create` + `doc update`**——`doc import` 是服务端格式转换,客户端无需解析文件内容
4. 可选参数:`--folder <文件夹ID>` 指定目标文件夹、`--workspace <知识库ID>` 指定目标知识库、`--name "文档名"` 自定义名称
5. 格式映射:docx/doc→文档, xlsx/xls→表格, xmind/mark→脑图, md/txt→文档
6. 超时或中断时 CLI 返回 `taskId`,用 `dws doc import get --task-id ` 手动查询
详见 [doc-import.md](./doc/doc-import.md) | +| write-doc | 0. 阅读 [doc-create-workflow.md](./doc/style/doc-create-workflow.md) 的 §前置必读 + §关键词速查表,锁定文档类型和起稿路径:**决策型/含对比的知识沉淀型/用户要求美观 → JSONML 起稿**(`.json`);执行型/说明型 → Markdown 起稿(`.md`)
1. 按选定路径执行 doc-create-workflow.md(JSONML 路径有骨架范例可直接复制修改)
2. `doc create --content-file /tmp/.json --content-format jsonml`(或 `.md` + `--content-format markdown`)
3. 大内容默认依赖 DWS 自动分片;只有 `CONTENT_TRUNCATED`、部分写入失败或回读发现缺失时,才按 workflow 的恢复流程手工补片
4. **回读校验(必须)**:所有写入完成后,执行 `doc read --node `,校验关键标题/段落/表格是否完整写入 | +| search-docs-and-share | 1. `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. `doc search --query "<关键词>"` → 取 `nodeId`
2. `doc block list --node --format json` 定位目标块取 `blockId`
3. `doc block update --node --block-id --text "<替换内容>" --format json` 局部修改(不破坏富格式)
4. **回读校验**:`doc read --node ` 确认目标章节已更新
**兜底**:仅当无法定位块且用户确认整篇替换时,先 `doc update --node --content-file .md --mode overwrite --dry-run` 预览,再加 `--yes` 执行;overwrite 会丢富格式(颜色/字号/合并单元格等),详见下方 template-based-generation §"为什么避免 overwrite" | -| template-based-generation | **触发**:用户给已有 alidocs URL + "参照/按模板/复刻/同样模板生成 X 月份的" → **保形复制,禁止重写**。固定走下方 [template-based-generation](#template-based-generation) 章节:`doc copy` 保形复制 → `doc rename` → `doc block list/update` 局部改副本。禁止 `doc read + doc create` 重写链。 | -| doc-to-message | 1. `doc read --node ` → 取正文(大文档只摘要+链接)
2. `contact user search --query "<姓名>"` → 取 `openDingTalkId`(推荐);或 `chat search --query "<群名>"` → 取 `openConversationId`
3. `chat message send --open-dingtalk-id --text "<内容>"`(推荐)或 `--group --text "<内容>"` 发送。仅当无法获取 openDingTalkId 时才用 `--user `(备选) | -| delete-old-doc | 1. `doc search --query "<关键词>"` 或 `doc list --folder ` 取 `nodeId`
2. **必须先向用户展示要删除的文档标题/路径**,等用户确认
3. `doc delete --node --yes`(注意:是 `doc delete`,不是 `doc block delete`;前者删整篇,后者删块)
4. 验证:`doc info --node ` 应返回 not-found | -| export-doc-as-docx | 1. `doc search --query "<关键词>"` → 取 `nodeId`
2. `doc export --node --output /tmp/.docx --timeout-sec 600`(CLI 内置渐进式退避轮询,**不要自己拼 GET downloadUrl**)
3. 落盘失败兜底:`doc export get --job-id --output /tmp/.docx` 续等
4. (可选)`doc-to-message` 把 docx 路径作为附件发给用户 | -| grant-doc-access | 1. `doc search --query "<关键词>"` → 取 `nodeId`
2. `contact user search --query "<姓名>"` → 取 `userId`(**注意**: doc permission 用 `userId`,不是 `openDingTalkId`)
3. **节点级**授权:`doc permission add --node --user --role EDITOR`
  role 取值: MANAGER / EDITOR / DOWNLOADER / READER(**不要传 OWNER**)
  单次最多 30 个 userId
4. 查权限:`doc permission list --node ` 确认
5. **不要混淆**: 给"知识库"加成员用 `wiki member add --workspace`(容器级);给"单个文档"加权限用 `doc permission add --node`(节点级)| -| insert-image-to-doc | 1. `doc search --query "<关键词>"` → 取 `nodeId`
2. `doc media insert --node --file ./image.png`(3 步一体化:获取凭证 → PUT OSS → 插入块;图片 ≤20MB 自动作为内联图片,其他作为附件块)
3. (可选)`doc read --node ` 回读确认
**禁止**: 自己拿 uploadUrl 写 PUT —— 90%+ 会因 Content-Type 未清空触发 SignatureDoesNotMatch | -| download-doc-attachment | 1. `doc block list --node ` → 找到 `blockType=attachment` 的块取 `resourceId`
2. `doc media download --node --resource-id ` → 返回 OSS 临时 URL(**注意 expirationSeconds**)
3. 调用方自行 GET 该 URL 落盘 | - ---- +| 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. `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 文档并要求“按模板生成、复刻、生成同形态的新版本”时,必须走保形复制链路: -用户给已有 alidocs URL,并要求: -- "参照这个生成同样的" -- "按模板生成" -- "复刻这个" -- "同样的模板,X 月份的" -- "仿照这个文档" +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` 和目标块,确认名称、内容与结构;禁止修改源文档。 -### 核心原则 - -**保形优于重写**。adoc 富格式(行高、单元格背景色、字号、渐变文字色、表格列宽、合并单元格)在 markdown 层无表达,`doc read → create_file → doc create` 会做两次 lossy projection(先 adoc→markdown,再 markdown→adoc),必然丢失这些属性。 - -正确做法:**在 adoc 层保形复制(`doc copy`)**,再局部改写需要替换的块(`doc block update`),保留所有富格式。 - -### 固定路线 - -```bash -# 1. probe 模板节点,确认是 ALIDOC(adoc 富文档) -dws doc info --node <模板NODE或URL> --format json - -# 2. 在 adoc 层保形复制(不经过 markdown 投影) -dws doc copy --node <模板NODE或URL> --folder <目标文件夹NODE或URL> --format json -# → 取返回的副本 nodeId - -# 3. 重命名副本 -dws doc rename --node <副本nodeId> --name "<新文档名>" --format json - -# 4. 列出所有块定位要改的内容 -dws doc block list --node <副本nodeId> --format json -# → 记录 blockId 和现有文本 - -# 5. 逐块局部改写(只改月份/日期/姓名/指标等局部变量,不动结构) -dws doc block update --node <副本nodeId> --block-id --text "<新内容>" --format json - -# 6. 回读确认 -dws doc read --node <副本nodeId> -``` - -### 为什么避免 overwrite - -| 场景 | overwrite 后果 | -|------|---------------| -| 模板带表格背景色 | 全部变白 | -| 标题字号 24pt 加粗渐变 | 退化为普通 H1 | -| 合并单元格的表格 | 拆成独立单元格 | -| 嵌入图片 | 链接保留但样式重置 | - -**用 copy+block update 全部可保留**。 - -### 禁止反模式 - -- ❌ doc read + doc create 重写模板文档(两次 lossy projection) -- ❌ 先 `dws doc copy`,再删除副本退回 create 重写链路 -- ❌ 用 `dws doc update --mode overwrite --yes` 把含富格式的 adoc 整篇覆盖成 markdown +**禁止**用 `doc read` → `doc create` 重建模板。Markdown 是有损投影,会丢失行高、单元格背景色、字号和部分富结构。 --- @@ -106,32 +56,15 @@ dws doc read --node <副本nodeId> ## doc update 回读校验规范 -### 为什么需要回读校验 +**所有 `doc update`(含 overwrite 和 append)执行后都必须回读校验**——返回 `success=true` 不等于内容真的写入完整。 -`doc update` 在以下场景可能产生**静默失败**,返回 `success=true` 但实际写入不完整: +完整规范(静默失败场景、校验流程、异常处理路径、"先清空再重建"命令样板)见 [doc-update-workflow.md §6「回读验收」](./doc/style/doc-update-workflow.md)。 -- **overwrite 降级为 append**:大文档 overwrite 模式被后端静默降级,导致旧内容未清除、新内容追加在末尾 -- **分块 append 内容截断**:超长文档分片写入时,部分片段丢失或顺序错乱 -- **编码/通道问题**:PowerShell 等特殊终端下 UTF-8 内容传输乱码 - -### 校验流程 - -在 **每次 `doc update` 完成后**(无论 overwrite 还是 append 模式),必须执行以下步骤: - -1. **回读文档**:`doc read --node ` -2. **关键内容校验**:检查回读结果是否包含预期的关键标题、段落首句、表格标记等 -3. **异常处理**: - - 若内容缺失或截断 → 向用户报告具体差异,建议重试或手动修复 - - 若 overwrite 后仍残留旧内容 → 提示用户 overwrite 可能被降级,建议先清空再 append - - **禁止**在未回读的情况下直接向用户报告"已完成" - -### 示例 +最小流程: +```bash +dws doc update --node --content-file /tmp/new-content.md --mode overwrite +dws doc read --node # 校验关键标题、段落首句、表格、@人、附件 ``` -# 更新文档 -doc update --node abc123 --content-file /tmp/new-content.md --mode overwrite -# 回读校验 -doc read --node abc123 -# → 检查输出是否包含预期内容的关键标题和段落 -``` +**禁止**在未回读的情况下向用户报告「已完成」。 diff --git a/skills/multi/dingtalk-doc/references/_common/conventions.md b/skills/multi/dingtalk-doc/references/_common/conventions.md index 93e65f8c..803212b8 100644 --- a/skills/multi/dingtalk-doc/references/_common/conventions.md +++ b/skills/multi/dingtalk-doc/references/_common/conventions.md @@ -1,6 +1,6 @@ # 业务域通用规范 -> 仅服务本仓库已迁入的文档与 AI 表格行动指南。安全门控、危险操作确认、`--format json` 等已在根 skill 中定义,此处不重复。 +> 仅服务本 skill 已迁入的行动指南。安全门控、危险操作确认、`--format json` 等已在本 skill 的 `SKILL.md` 中定义,此处不重复。 ## 批量查询规范 @@ -9,7 +9,8 @@ | 1 | **并行查详情**:拿到多个 ID 后,用 `&` 合并到同一条 Shell 命令并行执行 + `wait`,**严禁逐条串行** | | 2 | **翻页**:分页接口须拉全直至无更多 | | 3 | **优先批量 API**:有批量接口则用批量;无则按 #1 并行 | -| 4 | **列表少轮次**:带条件搜索/列表 → 一次采全详情;**禁止**无新参数时重复同一 `list` / `search` | +| 4 | **群消息**:必须先 `chat search --query` 得 `openConversationId`,再 `chat message list --group --time "" --direction older`;多群同条命令并行 | +| 5 | **列表少轮次**:带条件搜索/列表 → 一次采全详情;**禁止**无新参数时重复同一 `list` / `search` | ## 多源并行采集(公共模式) @@ -26,10 +27,18 @@ | 字段 | 来源 | 传递给 | |------|------|--------| -| `nodeId` | `doc search` | `doc read/update/copy/move/rename --node` | -| `nodeId` | `doc list` 中的 folder 类型节点 / `doc folder create` | `doc list --folder`、`doc create --folder`、`doc upload --folder`、`doc copy/move --folder` | +| `taskUuid` | `minutes list` | `minutes get summary/info/batch --id(s)` | +| `userId` | `aisearch person` / `contact user search` / `contact dept list-members` | `contact user get --ids`、`todo --executors`、`calendar --users` | +| `deptId` | `contact dept search` | `contact dept list-members --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 --file-id`、`drive list/mkdir/upload --parent-id` | -| `workspaceId` | `wiki space search/list/create` | `doc list/search/create --workspace`、`wiki member * --workspace` | +| `dentryUuid` | `drive list` / `drive mkdir` | `drive info/download/copy/move/rename/delete --node`、`drive list/mkdir/upload/copy/move --folder` | +| `dentryId` | `drive info` 的数字字段 | 仅用于 `chat message send --dentry-id` | -**ID 边界硬约束**:遇到 `drive --parent-id`、`doc --folder`、`doc --node` 时,只能使用 `dentryUuid` / `nodeId` / 文档 URL。若当前上下文只有数字型 `dentryId`,必须先重新 `drive list` / `doc list` / `doc search` 获取正确 ID,不能把该数字直接代入后续命令。 +**ID 边界硬约束**:`dentryId` 通常是纯数字,只表示聊天文件消息需要的钉盘条目数字 ID;它不是父目录 ID。遇到 `drive --node/--folder`、`doc --node`、`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 70d0efc1..805b6315 100644 --- a/skills/multi/dingtalk-doc/references/doc.md +++ b/skills/multi/dingtalk-doc/references/doc.md @@ -1,5 +1,31 @@ # 文档 (doc) 命令参考 +> **渐进式文档**:本文件为路由层(命令索引 + 场景索引 + 意图判断 + 工作流),各命令的详细参数、示例和踩坑说明在 [doc/](./doc/) 目录下按需加载。 + +## 文档地址 (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 ` 补查。 + +## 前置条件 — 执行操作前必读 + +**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` 查询**,不要猜测参数名或凭记忆编造。 @@ -15,1071 +41,450 @@ dws doc block insert --help # 查看子命令组下的所有命令 dws doc block --help -dws doc permission --help +dws doc media --help ``` 规则: + - 参数名不确定时 → 先 `--help`,再调用 - 报错 "unknown flag" 时 → `--help` 确认正确的 flag 名称 - 不确定某个功能是否存在时 → `dws doc --help` 查看命令列表 -## 命令总览 +## 命令索引表 -### 搜索 / 遍历文件(已迁移,不再是 doc 子命令) +> 命令名 → 单文件,按需加载子文档。复杂任务请优先看下方 §场景索引。 -> **弃用提示**:`dws doc search` 和 `dws doc list` 已迁移到 `drive` / `wiki`。这两条虽仍能跑,但每次都会打印弃用警告,请改用下面的命令,命令详情见 dingtalk-drive skill。 +### 阅读 / 元信息 -| 旧命令(弃用) | 改用 | 场景 | -|--------------|------|------| -| `dws doc search --query "<关键词>"` | `dws drive search --query "<关键词>"` | 全局搜文档/文件(跨钉盘+文档空间聚合) | -| `dws doc search --workspace-ids ` | `dws wiki node search --workspace --query "<关键词>"` | 指定知识库内搜索 | -| `dws doc list` | `dws drive list` | 遍历「我的文档」/钉盘根目录 | -| `dws doc list --folder ` | `dws drive list --folder ` | 遍历指定文件夹 | -| `dws doc list --workspace ` | `dws drive list --workspace ` 或 `dws wiki node list --workspace ` | 遍历知识库 | +| 命令 | 用途 | 必填参数 | 详见 | +|------|------|----------|------| +| `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) | -拿到 `nodeId` 后,`doc read` / `doc info` / `doc update` / `doc block` 等内容级命令照常用。 +### 创建 / 写入 / 块编辑 -### 获取文档元信息 -``` -Usage: - dws doc info [flags] -Example: - dws doc info --node - dws doc info --node "https://alidocs.dingtalk.com/i/nodes/" - dws doc info --node "https://alidocs.dingtalk.com/document/edit?dentryKey=" - dws doc info --node "https://alidocs.dingtalk.com/document/preview?dentryKey=" -Flags: - --node string 文档 ID 或 URL (必填) -``` +| 命令 | 用途 | 必填参数 | 详见 | +|------|------|----------|------| +| `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) | -### 读取文档内容 -``` -Usage: - dws doc read [flags] -Example: - dws doc read --node - dws doc read --node "https://alidocs.dingtalk.com/i/nodes/" - dws doc read --node "https://alidocs.dingtalk.com/document/edit?dentryKey=" - dws doc read --node "https://alidocs.dingtalk.com/document/preview?dentryKey=" - dws doc read --node --content-format jsonml --scope outline -Flags: - --node string 文档 ID 或 URL (必填) - --content-format string 输出格式: markdown / jsonml - --scope string JSONML 节点范围: outline / range / section / tags - --tags string scope=tags 时的节点 tag 列表 - --max-depth int 筛选遍历最大深度,0 表示不限 - --start-block-id string range / section 起始块 UUID - --end-block-id string range 结束块 UUID -``` +### 附件 / 评论 / 导出 -`--scope` 只适用于 JSONML 局部读取。结果是虚拟 fragment,剥壳后消费 children,不得把容器整体写回;详见 [`doc-read.md`](./doc/doc-read.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) | -### 创建文档 -``` -Usage: - dws doc create [flags] -Example: - dws doc create --name "项目周报" - dws doc create --name "Q1 总结" --content "# Q1 总结" --folder - dws doc create --name "知识库文档" --workspace -Flags: - --name string 文档名称 (必填) - --folder string 父文件夹 nodeId / URL - --workspace string Workspace ID - --content string 初始 Markdown 内容 - --content-file string 从文件读取初始 Markdown -``` +### 导入 -### 创建其他类型文件 (表格/脑图/白板/多维表/画板/文件夹) +| 命令 | 用途 | 必填参数 | 详见 | +|------|------|----------|------| +| `doc import` / `doc import get` | 本地文件导入为在线文档(docx→文档 / xlsx→表格 / xmind→脑图 / md→文档);`import get` 输出已对齐统一 TaskResult 格式 | `--file` | [`doc/doc-import.md`](./doc/doc-import.md) | -> **弃用提示**:`dws doc file create --type` 已弃用,执行时会打印 `deprecated, use 'dws wiki node create --type '`。创建各类型文件节点改用 `dws wiki node create`(与 intent-guide 一致);创建普通文件夹用 `dws drive mkdir`。 +### 版本管理 + +| 命令 | 用途 | 必填参数 | 详见 | +|------|------|----------|------| +| `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 wiki node create [flags] + dws doc version save [flags] Example: - dws wiki node create --workspace --name "数据统计" --type axls - dws wiki node create --workspace --name "思维导图" --type amind - dws wiki node create --workspace --name "方案目录" --type folder --folder + dws doc version save --node Flags: - --workspace string 目标知识库 ID (必填 — wiki node create 不支持不带 workspace 创建) - --name string 文件名称 (必填) - --type string 文件类型: adoc=文档, axls=表格, appt=演示, adraw=白板, amind=脑图, able=多维表, folder=文件夹 - --folder string 父节点 ID (选填) + --node string 文档 ID 或 URL (必填) ``` -> 说明:`wiki node create` **必须**带 `--workspace`(知识库上下文),不能像旧 `doc file create` 那样在「我的文档」个人空间直接建。要在个人空间/钉盘建普通文件夹,用 `dws drive mkdir --name "<名称>"`(见 dingtalk-drive skill)。 +> 仅支持 adoc 类型文档。保存后会创建一个 USER_SAVE 类型的版本记录。 -### 更新文档内容 -``` -Usage: - dws doc update [flags] -Example: - dws doc update --node --content "# 追加内容" --mode append - dws doc update --node --content "# 完整替换" --mode overwrite -Flags: - --node string 文档 nodeId / URL - --content string Markdown 内容 - --content-file string 从文件读取 Markdown - --mode string 更新模式: append / overwrite -``` - -### 上传文件到钉钉文档或钉钉知识库 -``` -Usage: - dws doc upload [flags] -Example: - dws doc upload --file ./report.pdf - dws doc upload --file ./slides.pptx --name "Q1汇报.pptx" --folder - dws doc upload --file ./data.xlsx --workspace --convert -Flags: - --file string 本地文件路径 (必填) - --name string 文件显示名称 (默认使用文件名) - --folder string 目标文件夹 ID 或 URL - --workspace string 目标知识库 ID - --convert 是否转换为钉钉在线文档 -``` - -### 导入本地文件为在线文档 - -详见 [doc/doc-import.md](./doc/doc-import.md)。 +#### 查看文档历史版本列表 ``` Usage: - dws doc import [flags] - dws doc import get [flags] + dws doc version list [flags] Example: - dws doc import --file ./report.docx - dws doc import --file ./notes.md --folder - dws doc import --file ./data.xlsx --workspace - dws doc import get --task-id + dws doc version list --node + dws doc version list --node --limit 10 Flags: - --file string 本地文件路径 (必填) - --folder string 目标文件夹 ID 或 URL - --workspace string 目标知识库 ID 或 URL - --name string 导入后文档名称 (默认取文件名) - --task-id string 导入任务 ID (import get 必填) + --node string 文档 ID 或 URL (必填) + --limit int 返回版本数量上限 (可选) + --cursor string 分页游标 (可选) ``` -### 下载文件到本地(已迁移到 drive) - -> **弃用提示**:`dws doc download` 已迁移到 `dws drive download`(执行 `doc download` 会打印弃用警告)。下载已有文件(PDF/图片/附件等非在线文档)改用: - -``` -dws drive download --node --output ~/downloads/ -dws drive download --node --output ./report.pdf -``` - -> 注意区分:**在线文档(ALIDOC)导出为 docx** 仍走 `doc export`(内容级命令,未迁移);`download` 只下载已有文件。命令详情见 dingtalk-drive skill。 - -### 创建文件夹(已迁移) - -> **弃用提示**:`dws doc folder create` 已弃用。创建文件夹改用: -> - 个人空间/钉盘 → `dws drive mkdir --name "<名称>" [--folder <父节点>]` -> - 知识库内 → `dws wiki node create --workspace --name "<名称>" --type folder` -> -> 命令详情见 dingtalk-drive skill。 - -### 复制文档/文件 -``` -Usage: - dws doc copy [flags] -Example: - dws doc copy --node --folder - dws doc copy --node --workspace - dws doc copy --node "https://alidocs.dingtalk.com/i/nodes/" --folder -Flags: - --node string 源文档/文件 ID 或 URL (必填) - --folder string 目标文件夹 ID 或 URL - --workspace string 目标知识库 ID 或 URL (不传 --folder 时复制到该知识库根目录) -``` - -### 移动文档/文件 -``` -Usage: - dws doc move [flags] -Example: - dws doc move --node --folder - dws doc move --node --workspace -Flags: - --node string 源文档/文件 ID 或 URL (必填) - --folder string 目标文件夹 ID 或 URL - --workspace string 目标知识库 ID 或 URL (不传 --folder 时移动到该知识库根目录) -``` - -### 重命名文档/文件 -``` -Usage: - dws doc rename [flags] -Example: - dws doc rename --node --name "新名称" - dws doc rename --node "https://alidocs.dingtalk.com/i/nodes/" --name "项目周报 v2" -Flags: - --node string 文档/文件 ID 或 URL (必填) - --name string 新名称 (必填) -``` - -### 查询块元素 -``` -Usage: - dws doc block list [flags] -Example: - dws doc block list --node - dws doc block list --node --start-index 0 --end-index 5 - dws doc block list --node --block-type heading -Flags: - --node string 文档 ID 或 URL (必填) - --start-index int 起始位置 (从 0 开始) - --end-index int 终止位置 (含) - --block-type string 按块类型过滤 -``` - -### 插入块元素 -``` -Usage: - dws doc block insert [flags] -Example: - dws doc block insert --node --text "这是一段文字" - dws doc block insert --node --heading "二级标题" --level 2 - dws doc block insert --node --element '{"blockType":"paragraph","paragraph":{"text":"内容"}}' - dws doc block insert --node --text "在此处之前插入" --ref-block --where before -Flags: - --node string 文档 ID 或 URL (必填) - --text string 快捷: 段落文本内容 - --heading string 快捷: 标题文本 - --level int 标题级别 1-6 (配合 --heading,默认 1) - --element string 块元素 JSON (高级) - --index int 参照位置索引 (从 0 开始) - --where string 插入方向: before / after (默认 after) - --ref-block string 参照块 ID (优先级高于 --index) -``` - -### 更新块元素 -``` -Usage: - dws doc block update [flags] -Example: - dws doc block update --node --block-id --text "新内容" - dws doc block update --node --block-id --element '{"blockType":"heading","heading":{"text":"新标题","level":1}}' -Flags: - --node string 文档 ID 或 URL (必填) - --block-id string 目标块 ID (必填) - --text string 快捷: 段落文本内容 - --heading string 快捷: 标题文本 - --level int 标题级别 1-6 (配合 --heading,默认 1) - --element string 块元素 JSON (高级) -``` - -### 删除块元素 +#### 回滚文档到指定版本 > **CAUTION:** 不可逆操作 — 执行前必须向用户确认。 ``` Usage: - dws doc block delete [flags] + dws doc version revert [flags] Example: - dws doc block delete --node --block-id --yes + dws doc version revert --node --version 3 --yes Flags: - --node string 文档 ID 或 URL (必填) - --block-id string 目标块 ID (必填) + --node string 文档 ID 或 URL (必填) + --version int 目标版本号 (必填,从 version list 获取) + --yes 跳过确认提示 (非交互终端必须传) ``` -### 查询文档评论列表 +#### 获取文档模板列表 + ``` Usage: - dws doc comment list [flags] + dws doc template list [flags] Example: - dws doc comment list --node - dws doc comment list --node --type inline --resolve-status unresolved - dws doc comment list --node --limit 20 --cursor + dws doc template list + dws doc template list --source MY + dws doc template list --source PUBLIC + dws doc template list --limit 20 Flags: - --node string 目标文档的标识,支持传入 URL 或 ID (必填) - --limit int 每页返回的评论数量,默认 50,最大 50 - --cursor string 分页游标,从上一次请求的返回结果中获取 (首次请求不传) - --type string 按评论类型过滤: global (全文评论) / inline (划词评论) - --resolve-status string 按解决状态过滤: resolved (已解决) / unresolved (未解决) + --source string 模板来源: MY(我的模版)/PUBLIC(公开模版),不传默认 MY + --limit int 返回数量上限 (可选) + --cursor string 分页游标 (可选) ``` -### 创建文档评论 +#### 搜索文档模板 + ``` Usage: - dws doc comment create [flags] + dws doc template search [flags] 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 + dws doc template search --query "周报" + dws doc template search --query "会议纪要" --limit 10 + dws doc template search --query "项目" --source PUBLIC Flags: - --node string 目标文档的标识,支持传入 URL 或 ID (必填) - --content string 评论的文字内容,纯文本 (必填) - --mention string 被 @ 的用户 uid 列表,逗号分隔 - --mentioned-open-conversation-id strings 被 @ 的群 openConversationId,可重复或逗号分隔 + --query string 搜索关键词 (必填) + --source string 模板来源: MY(我的模版)/PUBLIC(公开模版),不传默认 MY + --limit int 返回数量上限 (可选) + --cursor string 分页游标 (可选) ``` -### 创建划词评论 (内联评论) +#### 应用文档模板 + ``` Usage: - dws doc comment create-inline [flags] + dws doc template apply [flags] Example: - dws doc comment create-inline --node --block-id --start 0 --end 10 --content "这里需要修改" - dws doc comment create-inline --node --block-id --start 5 --end 20 --content "建议调整" --selected-text "被选中的原文" - dws doc comment create-inline --node --block-id --start 0 --end 10 --content "请review" --mention uid1,uid2 + dws doc template apply --template-id TPL_ID --name "我的周报" + dws doc template apply --template-id TPL_ID --name "项目方案" --folder FOLDER_ID Flags: - --node string 目标文档的标识,支持传入 URL 或 ID (必填) - --block-id string 评论锚定所在的块 ID (必填,可通过 dws doc block list 获取) - --start int 选中文本在块内的起始字符偏移量,从 0 开始 (必填) - --end int 选中文本在块内的结束字符偏移量,必须大于 start (必填) - --content string 评论的文字内容,纯文本 (必填) - --selected-text string 选中文本内容,填写后评论列表会展示「引用原文:xxx」 - --mention string 被 @ 的用户 uid 列表,逗号分隔 + --template-id string 模板 ID (必填,从 template list/search 获取) + --name string 新文档名称 (可选) + --folder string 目标文件夹 ID (可选) + --workspace string 知识库 ID (可选) ``` -### 回复文档评论 -``` -Usage: - dws doc comment reply [flags] -Example: - dws doc comment reply --node --comment-key --content "同意" - dws doc comment reply --node --comment-key --content "比心" --emoji - dws doc comment reply --node --comment-key --content "请确认" --mention uid1,uid2 - 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 同时使用 -``` +### 文件操作(已迁移,以下命令 deprecated) -### 更新文档评论 -```text -Usage: - dws doc comment update --node --comment-key --content [--mention uid1,uid2] [--mentioned-open-conversation-id ] -``` +> **迁移提示**:文件管理命令已按产品领域架构重新归属。**Agent 必须使用右侧推荐命令,禁止使用左侧旧命令**(旧命令仅供人类用户过渡参考,可能已不可用)。 -`commentKey` 从 `comment list/create/create-inline` 返回中获取。全文评论的 `create` / `reply` / `update` 支持真实 @群;`create-inline` 不支持 @群。 +| 旧命令 | 推荐命令 | 详见 | +|--------|----------|------| +| `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) | -### 删除文档评论 -```text -Usage: - dws doc comment delete --node --comment-key --yes -``` +### 排版规范 / JSONML 参考 -删除评论不可恢复,必须先获得用户明确确认,再添加全局 `--yes`。 +| 资源 | 用途 | 详见 | +|------|------|------| +| 创建工作流 | 标题、位置、骨架、回读校验 | [`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) | -### 文件内容获取路由规则 +## 场景索引 -> 当用户请求"分析/查看/读取某个文件内容"时,**必须先调用 `dws doc info` 获取文件元数据**,再根据返回的 `contentType` 和 `extension` 字段选择对应链路: +> 任务驱动的入口:知道任务但不确定要读哪些命令文件时,按本表**一次性 Read 文件组**,不要逐个跳。 -| contentType | extension | 操作 | 命令 | -|-------------|-----------|------|------| -| ALIDOC | adoc | 在线获取 Markdown 内容 | `dws doc read --node ` | -| ALIDOC | axls | 在线读取表格数据 | `dws sheet list --node ` → `dws sheet range read --node ` | -| ALIDOC | able | 在线查询多维表格记录 | `dws aitable table get --base-id ` → `dws aitable record query --base-id --table-id ` | -| 非 ALIDOC | — | **不支持在线分析** | 告知用户需下载到本地后查看 | - -**关键规则**:非 ALIDOC 类型文件(PDF/Word/图片/视频等)不支持在线分析,用户可以选择下载后本地查看。 - -### 格式保留度声明(adoc ↔ markdown lossy projection) - -`dws doc read --format json` 返回的 `markdown` 字段是钉钉 adoc 文档的**有损投影**。源 adoc 中以下富格式属性在 markdown 这一层**没有对应表达,会被丢弃**: - -- 行高(row height) -- 单元格背景色(cell background color) -- 字体大小(font size) -- 字体颜色的**渐变形态**(`radial-gradient` / `linear-gradient` — 文本会以 `` 字面保留,但 CSS `color` 不接受渐变,浏览器无法渲染,**字符串保留但语义已死**) -- 块级缩进 / 表格列宽 -- 合并单元格状态 -- checkbox 视觉样式(颜色、形状) -- 任何 SVG / 嵌入式画板的渲染细节 - -**硬指引(任何 contentType=ALIDOC 文档都适用)**: - -- 当用户要求"按模板生成同形态变体 / 参照这个生成 / 复刻 / 仿照这个文档"时,**禁止**走 doc read → create_file → doc create 链路——这条链路会做两次 lossy projection(adoc → markdown 丢一次,markdown → adoc 重建再丢一次),最终交付物只有文本内容,富格式全部失真。 -- 正确路径:`dws doc copy`(adoc 层面整文档保形复制)+ `dws doc rename` + `dws doc block list / dws doc block update`(在副本上做局部修改)。详见 [best_practices/04-document.md](../best_practices/04-document.md#template-based-generation) 的 `template-based-generation` recipe。 -- 当用户提到 `行高 / 颜色 / 字号 / 表格样式 / 周末高亮` 等富格式诉求时,agent 必须在动手前显式声明能力边界:"这些属性 markdown 无法表达,需走 copy + block update 路径",避免静默走破坏链。 - -### 内容写入管道(create / update 共用) - -> **关键原则**:CLI 内置自动分片。超长内容(>10000 字符)自动按 markdown 结构切分后逐片写入,对调用方透明。写入完成后由调用方自行决定是否回读确认。 - -#### 输入方式选择 - -| 场景 | 推荐方式 | 说明 | -|------|---------|------| -| 短文本(<2KB,无换行/表格/特殊字符) | `--content "..."` | 字面量传入,最简单 | -| 长文本(≥2KB)、含换行、含表格 | `--content-file ./file.md` | **必须**用文件路径,避免 shell escape 和截断 | -| 含特殊字符(`"`、`\`、`$`、`` ` ``) | `--content-file ./file.md` | 字面量传入会被 shell 转义破坏 | -| 管道/heredoc 输入 | `--content -` 或 `cat file \| dws doc ...` | 从 stdin 读取 | - -#### 自动分片行为 - -当内容超过 10000 字符时,CLI 自动执行: -1. **create**: 先创建空文档拿 `nodeId`,再按 markdown 标题边界切分后逐片 append -2. **update (overwrite)**: 第一片用 overwrite,后续片用 append -3. **update (append)**: 所有片段用 append - -分片策略按优先级:H1 标题 → H2 标题 → H3 标题 → 空行(段落边界)→ 硬切(保留表格/代码块完整性) - -如果某片写入超时,自动将分片大小减半重试(最小 5000 字符,低于此值报错)。 - -#### 输出格式 - -写入成功后输出 JSON(混合 `[INFO]` 进度行): - -```json -{"success": true, "nodeId": "xxx", "chunksWritten": 3} -``` - -| 字段 | 说明 | -|------|------| -| `nodeId` | 文档节点 ID,可用于后续读取或追加 | -| `chunksWritten` | 实际写入的分片数(1 = 单次写入) | - -#### 内容完整性验证(必读) - -CLI **不会**自动执行回读验证。**Agent 必须在文档写入完成后主动回读确认**——这是硬约束,不是建议: - -1. 使用 `dws doc read --node ` 读取写入后的文档内容 -2. 检查关键段落是否完整、顺序是否正确 -3. 如发现内容缺失或异常,使用 `dws doc update --mode append` 补写缺失部分 - -> **何时回读**:每次 create / update 操作完成后**必须**回读。 -> - 单次写入(≤10000 字符):写完立即回读一次 -> - 分片写入(>10000 字符):所有分片写完后回读一次全文,校验关键标题与段落完整性 -> - 破坏性 overwrite(`--mode overwrite --yes`):**必须**回读,确认 overwrite 未被后端静默降级为 append(详见 [best_practices/04-document.md "doc update 回读校验规范"](../best_practices/04-document.md#doc-update-回读校验规范)) -> - 连续多次编辑同一文档:可在全部编辑完成后统一回读一次 -> -> **禁止**在未回读的情况下直接向用户报告"已完成 / 更新成功"。Agent 自陈"完成"必须基于回读结果,而非工具调用的 `success=true` 字段。 - -#### 进度输出示例 - -``` -[INFO] 内容较长 (25000 字符),自动分片写入... -[INFO] 已创建空文档 (nodeId=abc123),开始分片写入... -[INFO] 写入分片 (1/3),10000 字符... -[INFO] 写入分片 (2/3),10000 字符... -[INFO] 写入分片 (3/3),5000 字符... -[INFO] 全部 3 个分片写入完成 -{"success": true, "nodeId": "abc123", "chunksWritten": 3} -``` - -#### CONTENT_TRUNCATED 错误 - -当分片写入持续超时且减半到最小阈值仍失败时,返回 `CONTENT_TRUNCATED` 错误码。应对策略: -1. 检查网络和后端服务状态 -2. 已写入的部分内容可通过 `dws doc read --node ` 查看 -3. 从断点处手动用 `dws doc update --mode append` 继续追加 - -### 删除文档/文件到回收站 - -> **CAUTION:** 不可逆操作 — 执行前必须向用户确认。 - -``` -Usage: - dws doc delete [flags] -Example: - dws doc delete --node --format json # 查询 nodeId: dws drive search --query "..." 或 dws drive list -Flags: - --node string 文档/文件 ID 或 URL (必填) -``` - -权限要求: 对文档有"管理"权限。 - -### 下载文档附件 -``` -Usage: - dws doc media download [flags] -Example: - dws doc media download --node --resource-id - dws doc media download --node "https://alidocs.dingtalk.com/i/nodes/xxx" --resource-id -Flags: - --node string 目标文档的标识,支持传入 URL 或 ID (必填) - --resource-id string 附件资源 ID,可通过 dws doc block list 获取 (必填) -``` - -### 上传附件并插入文档 -``` -Usage: - dws doc media insert [flags] -Example: - dws doc media insert --node --file ./report.pdf - dws doc media insert --node --file ./data.bin --name "数据文件.dat" --mime-type application/octet-stream - dws doc media insert --node --file ./image.png --ref-block --where before -Flags: - --node string 目标文档的标识,支持传入 URL 或 ID (必填) - --file string 本地文件路径 (必填) - --name string 附件显示名称 (默认使用文件名) - --mime-type string 文件 MIME 类型 (默认根据扩展名推断) - --index int 插入位置索引 - --where string 相对位置: before / after (配合 --ref-block) - --ref-block string 参考块 ID (配合 --where) -``` - -### 添加文档权限(节点级授权) -``` -Usage: - dws doc permission add [flags] -Example: - dws doc permission add --node --users uid1 --role READER - dws doc permission add --node --users uid1,uid2 --role EDITOR - dws doc permission add --node "https://alidocs.dingtalk.com/i/nodes/" --users uid1 --role MANAGER -Flags: - --node string 目标文档/文件夹的 ID 或 URL (必填) - --users strings 被授权的用户 userId 列表,逗号分隔 (必填,单次最多 30 个) - --role string 授予的角色 (必填,大小写敏感,必须全大写): MANAGER (管理者) / EDITOR (可编辑) / DOWNLOADER (可下载) / READER (可阅读) - --workspace string 所属知识库 ID (选填,仅用于辅助构造返回的 docUrl,业务实际依赖 nodeId) -``` - -> **重要约束**: -> - 仅支持 USER 类型授权。 -> - 角色枚举严格大写:MANAGER / EDITOR / DOWNLOADER / READER(OWNER 不可通过此接口添加)。 -> - 操作者需在该节点具备「可编辑(EDITOR)」及以上角色(OWNER / MANAGER / EDITOR)。 -> - 授权对象是文档节点本身,不需要也不应该用 `wiki member add`(那个是知识库容器级授权)。 - -### 修改文档权限(节点级) -``` -Usage: - dws doc permission update [flags] -Example: - dws doc permission update --node --users uid1 --role EDITOR - dws doc permission update --node --users uid1,uid2 --role READER -Flags: - --node string 目标文档/文件夹的 ID 或 URL (必填) - --users strings 目标用户 userId 列表,逗号分隔 (必填,单次最多 30 个) - --role string 新角色 (必填,大小写敏感,必须全大写): MANAGER / EDITOR / DOWNLOADER / READER - --workspace string 所属知识库 ID (选填) -``` - -### 列出文档权限(节点级) -``` -Usage: - dws doc permission list [flags] -Example: - dws doc permission list --node - dws doc permission list --node --limit 50 - dws doc permission list --node --filter-role EDITOR -Flags: - --node string 目标文档/文件夹的 ID 或 URL (必填) - --workspace string 所属知识库 ID (选填) - --limit int 返回数量上限,最大 200 (默认 50) - --filter-role string 按角色过滤: MANAGER / EDITOR / DOWNLOADER / READER (选填) -``` - -> 接口不支持游标分页,使用 `--limit` 一次性拉取。 - -### 导出在线文档为 docx(一体化命令) -``` -Usage: - dws doc export [flags] -Example: - dws doc export --node "https://alidocs.dingtalk.com/i/nodes/xxx" --output ./exported.docx - dws doc export --node --output ~/downloads/ -Flags: - --node string 要导出的文档标识,支持文档 URL 或 dentryUuid (必填) - --output string 本地保存路径,文件路径或目录 (必填) - --export-format string 导出格式,当前仅支持 docx (默认) -``` - -CLI 内部自动完成:提交导出任务 → 渐进式退避轮询(最多约 5 分钟)→ 成功后自动下载文件。 -**只需一条命令,无需手动轮询。** - -### 查询导出任务结果(手动兜底) -``` -Usage: - dws doc export get [flags] -Example: - dws doc export get --job-id -Flags: - --job-id string 导出任务 ID (必填) -``` - -仅在 `dws doc export` 超时或中断后,用于手动查询任务状态。通常不需要调用。 - - -## URL 识别与 DOC_ID 提取 - -当用户输入包含钉钉文档 URL 时,**必须先识别并提取 DOC_ID**,再判断意图。 - -补充:如果这是用户直接提供的原始 `alidocs` URL,必须先按 [链接规范](../url-patterns.md#alidocs-url-类型探测流程) probe 一次确认真实类型,再判断是否继续走 `doc`。 - -### 支持的 URL 格式 - -| 格式 | 示例 | DOC_ID 提取方式 | -|------|------|----------------| -| `alidocs.dingtalk.com/i/nodes/{id}` | `https://alidocs.dingtalk.com/i/nodes/9E05BDRVQePjzLkZt2p2vE7kV63zgkYA` | 取 URL 路径最后一段:`9E05BDRVQePjzLkZt2p2vE7kV63zgkYA` | -| `alidocs.dingtalk.com/i/nodes/{id}?queryParams` | `https://alidocs.dingtalk.com/i/nodes/abc123?doc_type=wiki_doc` | 忽略 query 参数,取路径最后一段:`abc123` | -| `alidocs.dingtalk.com/document/{edit\|preview}?...&dentryKey={key}` | `https://alidocs.dingtalk.com/document/edit?dentryKey=wo1g3x54FzVEJ5yE` | **不要提取 `dentryKey` 单独使用**,必须将完整 URL 原样传给 `--node` | - -### 提取规则 - -1. 匹配 URL 中 `alidocs.dingtalk.com` 域名 -2. 路径为 `/i/nodes/{id}` 时,取 URL path 的最后一段作为 DOC_ID(去掉 query string 和 fragment) -3. 路径为 `/document/edit` 或 `/document/preview` 且 query 含 `dentryKey` 时,**禁止**提取 `dentryKey` 当 DOC_ID;将整段 URL 原样传给 `--node`,CLI 会自动解析(追踪参数如 `utm_source`、`chInfo` 也不必清理) -4. 提取出的 DOC_ID 可直接用于所有 `--node` 参数,也可将完整 URL 传给 `--node`(CLI 会自动解析) - -### ID 边界与参数映射 - -- `nodeId` 是 `doc` 命令的统一节点标识;文档、文件夹、文件都通过 `nodeId` 或完整 `alidocs` URL 传给 `--node` / `--folder`。 -- `dentryUuid` 是 `alidocs` URL `/i/nodes/{dentryUuid}` 的最后一段,在 `doc` 场景中等价于可传入 CLI 的 `nodeId`;不要把它改写成数字 ID。 -- `dentryId` 通常是纯数字,**不是** `doc` 的 `nodeId`,也不是 `doc --folder` 的目标文件夹 ID;不要把数字 `dentryId` 当作 `--node`、`--folder`,也不要当作隐藏兼容 alias(如 `--parent-id` / `--parent-folder-id`)的取值。 -- 目标父文件夹的 canonical flag 是 `--folder `,目标知识库使用 `--workspace `。CLI 可能接受 `--parent-id` / `--parent-folder-id` 作为隐藏兼容 alias,但 skill 正文和示例应统一教 `--folder`,不要把 drive/chat 链路里的数字 parentId 搬到 doc 命令。 -- 如果上下文只有数字 `dentryId`,但用户要读、改、移动、复制、重命名文档,先通过 `drive search` / `drive list` / 用户提供的 `alidocs` URL 获取 `nodeId` / `dentryUuid`,不要用数字 `dentryId` 重试为父目录参数。 - -### 处理流程 - -``` -用户输入含 alidocs.dingtalk.com URL - → 若是用户直接提供的原始 URL,先按链接规范做 probe - → 提取 DOC_ID(URL 路径最后一段) - → 结合用户意图选择命令(doc 默认 read,folder 默认 drive list,file 默认 drive download) - → 将 DOC_ID 传给 --node 参数 -``` +| 任务场景 | 一次性读取 | 主命令 | +|---------|-----------|--------| +| 定位 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 不支持版本管理 | ## 意图判断 用户说"找文档/搜文档/最近文档": -- 搜索 → `dws drive search`(全局)或 `dws wiki node search --workspace `(指定知识库) -- 浏览 → `dws drive list`(`doc search` / `doc list` 已弃用,见本文顶部「搜索 / 遍历文件」) + +- 全局搜索 → `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) + +- 读取 → `read`(需文档 ID 或 URL) - 元信息 → `info` 用户说"写文档/创建文档": + - 新建文字文档(adoc)→ `doc create` - 追加内容 → `update --mode append` -- 整篇覆盖替换 → 先 `update --mode overwrite --dry-run` 预览,用户确认后再 `update --mode overwrite --yes` +- 覆盖替换 → `update --mode overwrite` - 指定父目录时,只有明确的文档文件夹 `nodeId` / `alidocs` 文件夹 URL 才能放入 `--folder`;上一步若只返回了纯数字 `dentryId`、`spaceId` 或 drive `parent-id`,不要把它传给 doc 的 `--folder` > 严禁把「创建表格」路由到 `doc create`: -> - 用户说"创建表格/新建表格/建个电子表格/在线表格" → 走 [`dws sheet create`](./sheet.md)(axls 在线电子表格) -> - 用户说"创建多维表格/新建 AI 表格/建 base/数据库表" → 走 [`dws aitable base create`](./aitable.md)(able 多维表格) - -用户说"新建脑图/白板/多维表/演示文稿": -- 用 `dws wiki node create --workspace --type ` 指定类型 (axls/amind/adraw/able/appt);`doc file create` 已弃用 -- 注意 `wiki node create` 必须带 `--workspace`(知识库上下文) +> +> - 用户说"创建表格/新建表格/建个电子表格/在线表格" → 走 [`dws 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 mkdir --name "<名称>"` -- 知识库内 → `dws wiki node create --workspace --type folder`(`doc folder create` / `doc file create --type folder` 已弃用) + +- 创建 → `dws drive folder create` 用户说"上传文件/传文件/上传到文档/上传到知识库": -- 上传 → `upload`(需本地文件路径) -- 上传并转换 → `upload --convert` -用户说"导入文件/导入为在线文档/导入 Word/导入 Excel/导入 xmind/导入 Markdown/把本地文件转在线文档": -- 导入并转换为在线文档 → `doc import --file <本地路径>` -- 支持 docx/doc/xlsx/xls/md/txt/xmind/mark,文件大小不超过 20MB -- 如果用户指定知识库或文件夹,补充 `--workspace` 或 `--folder` -- 不要把本地文件内容先读出来再 `doc create/update`;应直接使用 `doc import` +- 上传 → `dws drive upload`(需本地文件路径) 用户说"下载/导出/下载到本地/导出文档/导出为Word/导出为docx/把文档导出来": -- **必须先判断目标文件类型**,再决定走 `doc export` 还是 `drive download`: - - 在线文档(alidocs/adoc)→ **`doc export`**(内容级命令,格式转换后导出为 docx,未迁移) - - 已有文件(PDF、图片、附件、视频等非在线文档)→ **`drive download`**(`doc download` 已弃用) + +- **必须先判断目标文件类型**,再决定走 `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. 不确定时,先用 `doc info --node ` 查询节点信息,根据 `contentType` 判断: - - `contentType` = `ALIDOC` → 走 `doc export` - - `contentType` = `DOCUMENT`/`IMAGE`/`VIDEO` 等 → 走 `drive download` + 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` -> **严禁将"导出文档"直接路由到 `drive download`**。`drive download` 只能下载已有文件(原样下载),`doc export` 是将在线文档格式转换后导出为 docx,两者完全不同。 +> **严禁将"导出文档"直接路由到 `download`**。`download` 只能下载已有文件(原样下载),`export` 是将在线文档格式转换后导出为 docx 或 markdown 或 pdf,两者完全不同。 +> +> **导出任务查询**:`doc export` 超时后可用 `dws doc export get --job-id ` 查询任务状态。 -用户说"复制文档/拷贝一份/复制到": -- 复制 → `copy` (需源 --node 和目标 --folder/--workspace) +用户说"导入文件/导入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` 用户说"移动文档/搬到/移到/转移文件": -- 移动 → `move` (需源 --node 和目标 --folder/--workspace) + +- 移动 → `dws drive move` 用户说"重命名/rename/改名/改文档名/修改文档名称/修改文档标题/把这个文档叫做...": -- 重命名 → `doc rename`(需文档 ID 或 URL + 新名称) -- 只要意图是修改文档在列表和链接中展示的名称,统一路由到 `dws doc rename --node --name "新名称"`;不要走 `drive`、`doc update` 或重新 `doc create` -- 只有用户明确说"正文里的标题/章节标题/段落标题/H1 标题"时,才走 `block update` + +- 重命名 → `dws drive rename --node --name "新名称"` +- 只有用户明确说"正文里的标题/章节标题/段落标题/H1 标题"时,才走 `block update`。 用户说"删除文档/删掉这个文件/移到回收站/丢掉这篇文档": -- 删除节点 → `delete`(危险操作,需 `--yes` 确认;需文档 ID 或 URL) + +- 删除节点 → `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 看": -- 新增权限 → `permission add`(需 `--node` + `--user` + `--role`) -- 修改权限 → `permission update` -- 查看谁有权限 → `permission list` + +- 新增权限 → `dws drive permission add` +- 修改权限 → `dws drive permission update` +- 查看谁有权限 → `dws drive permission list` +- 移除权限 → `dws drive permission remove` > **关键区分**: -> - "把**某篇文档**授权给某人" → `doc permission add`(节点级,包括「我的文档」下的文档都支持) +> +> - "把**某篇文档**授权给某人" → `drive permission add`(节点级,包括「我的文档」下的文档都支持) > - "把**某个知识库**整体授权给某人" → `wiki member add`(容器级,但**「我的文档」个人空间不支持**) -> 补充:如果用户直接粘贴的是原始 `alidocs` URL,先按 [链接规范](../url-patterns.md#alidocs-url-类型探测流程) probe;只有 probe 确认是 `adoc` / `file` / `folder` 后,才继续按下列意图执行。 +> 补充:如果用户直接粘贴的是原始 `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 明显是文件夹 → `drive list`(列出文件夹内容;doc list 已弃用) +- 如 URL 明显是文件夹 → `list`(列出文件夹内容) **用户粘贴 URL + 附加指令**: + - "帮我看看这个文档" → `read` - "这个文档的信息" → `info` - "往这个文档追加内容" → `update --mode append` -- "把这个文档标题改成 X" / "这个文档改名为 X" → `rename` +- "把这个文档标题改成 X" / "这个文档改名为 X" → `dws drive rename` - "把正文里的一级标题/章节标题改成 X" → `block update` -关键区分: doc(文档编辑/阅读) vs aitable(数据表格操作) vs drive(钉盘文件管理) - -用户说"上传文件/传文件/上传到文档/上传到知识库": -- 上传 → `upload`(需本地文件路径) -- 上传并转换 → `upload --convert` - -用户说"下载文件/导出文件/下载到本地": -- 下载 → `download`(需文件节点 ID 或 URL) - -用户说"编辑块/改段落/插入标题/删除块": -- 查看结构 → `block list` -- 插入 → `block insert` -- 修改 → `block update` -- 删除 → `block delete` - -**用户直接粘贴文档 URL(无其他指令)**: -- 默认 → `read`(读取文档内容) -- 如 URL 明显是文件夹 → `drive list`(列出文件夹内容;doc list 已弃用) - -**用户粘贴 URL + 附加指令**: -- "帮我看看这个文档" → `read` -- "这个文档的信息" → `info` -- "往这个文档追加内容" → `update --mode append` -- "编辑这个文档的标题" → `block update` - -关键区分: doc(文档编辑/阅读) vs aitable(数据表格操作) vs drive(钉盘文件管理) +关键区分: doc(文档编辑/阅读) vs aitable(数据表格操作) vs drive(文件管理) ## 核心工作流 +> 步骤性指引。"读哪些文件"组合参见上方 §场景索引;命令详细参数参见对应子文件。 + ```bash -# ── 工作流 1: 浏览并阅读文档 ── +# ── 工作流 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 -# 1. 浏览我的文档/钉盘根目录(doc list 已弃用 → drive list) -dws drive list --format json +# ── 工作流 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. 回读校验(必须) -# 2. 浏览子文件夹 -dws drive list --folder --format json +# ── 工作流 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. 回读 -# 3. 获取文档元信息 (标题、类型、权限) -dws doc info --node --format json +# ── 工作流 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) -# 4. 读取文档内容 (Markdown 格式) -dws doc read --node --format json +# ── 工作流 5: 上传独立文件 vs 插入附件到文档正文 ── +dws drive upload --file ./report.pdf --folder # 上传作为独立文件(存储层) +dws doc media insert --node --file ./report.pdf # 上传并作为附件块插入正文(内容层) -# ── 工作流 2: 创建文档并写入内容 ── +# ── 工作流 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 # 指定目标知识库 -# 1. (可选) 创建文件夹 — 提取 nodeId(doc folder create 已弃用 → drive mkdir) -dws drive mkdir --name "项目资料" --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(存储层) -# 2. 创建文档 — 提取 nodeId -dws doc create --name "项目周报" --folder --format json - -# 3. 写入内容 (追加模式) -dws doc update --node --content "# 本周总结\n\n- 完成了 A\n- 推进了 B" --mode append --format json - -# ── 工作流 3: 一步创建带内容的文档 ── - -dws doc create --name "会议纪要" --content "# 会议纪要\n\n## 议题\n\n1. ..." --format json - -# ── 工作流 4: 上传本地文件到钉钉文档/知识库 ── - -# 1. 上传到"我的文档"根目录 -dws doc upload --file ./report.pdf - -# 2. 上传到指定文件夹 -dws doc upload --file ./slides.pptx --name "Q1汇报.pptx" --folder - -# 3. 上传到知识库并转换为在线文档 -dws doc upload --file ./data.xlsx --workspace --convert - -# ── 工作流 5: 下载/导出文件到本地 ── -# 必须先用 info 判断文件类型,再决定用 drive download 还是 doc export: -# - contentType 为 ALIDOC(在线文档)→ 用 doc export -# - contentType 为 DOCUMENT/IMAGE/VIDEO 等(已有文件)→ 用 drive download - -# 步骤 1: 查询文件类型 -dws doc info --node --format json -# 根据返回的 contentType 字段判断: - -# 如果是已有文件 (非 ALIDOC),用 drive download(doc download 已弃用): -dws drive download --node --output ~/downloads/ - -# 如果是在线文档 (ALIDOC),用 doc export: -dws doc export --node --output ~/downloads/ - -# ── 工作流 5a: 导入本地文件为在线文档 ── - -# 导入到默认位置 -dws doc import --file ./report.docx --format json - -# 导入到指定文件夹 -dws doc import --file ./notes.md --folder --format json - -# 导入到知识库 -dws doc import --file ./data.xlsx --workspace --format json - -# 如果导入命令超时或中断,可用 import get 手动查询任务状态: -# dws doc import get --task-id - -# ── 工作流 6: 上传附件并插入文档 ── - -# media insert 自动完成三步流程: -# 1. 获取附件上传凭证 (get_doc_attachment_upload_info) -# 2. HTTP PUT 上传文件到 OSS -# 3. 插入附件块到文档 (insert_document_block) - -# 1. 基本用法: 插入本地文件到文档 -dws doc media insert --node --file ./report.pdf - -# 2. 指定附件显示名称 -dws doc media insert --node --file ./data.xlsx --name "Q1数据报表.xlsx" - -# 3. 指定 MIME 类型 (文件扩展名无法推断时) -dws doc media insert --node --file ./data.bin --name "导出数据.dat" --mime-type application/octet-stream - -# 4. 在指定块之前插入附件 -dws doc media insert --node --file ./image.png --ref-block --where before - -# 5. 完整流程: 创建文档 → 写入内容 → 插入附件 -dws doc create --name "项目报告" --content "# 项目报告\n\n以下为相关附件:" --format json -# 提取 nodeId 后: -dws doc media insert --node --file ./design.pdf -dws doc media insert --node --file ./timeline.xlsx --name "项目时间线.xlsx" - -# ── 工作流 7: 块级精细编辑 ── - -# 1. 查看文档块结构 — 获取 blockId -dws doc block list --node --format json - -# 2. 在文档末尾插入段落 -dws doc block insert --node --text "新增内容" - -# 3. 在指定块之前插入标题 -dws doc block insert --node --heading "新章节" --level 2 --ref-block --where before - -# 4. 更新某个块的内容 -dws doc block update --node --block-id --text "修改后的内容" - -# 5. 删除块 -dws doc block delete --node --block-id --yes - -# ── 工作流 8: 复制/移动/重命名文档 ── - -# 获取 nodeId 的三种方式(按场景选择,无需全部执行): -# 方式 A: 用户直接提供文档 URL — 直接传给 --node,无需额外查询 -# 方式 B: 搜索文档 — 从返回中提取 nodeId(doc search 已弃用 → drive search) -dws drive search --query "项目周报" --format json -# 方式 C: 浏览文件夹 — 从返回中提取 nodeId(doc list 已弃用 → drive list) -dws drive list --folder --format json -# 注意: 这里的 是文件夹 nodeId/dentryUuid 或文件夹 URL,不是数字 dentryId;示例统一使用 canonical --folder。 - -# 复制文档到指定文件夹(--node 支持 ID 或 URL) -dws doc copy --node --folder --format json - -# 复制到目标知识库根目录(不传 --folder 时) -dws doc copy --node --workspace --format json - -# 移动文档到指定文件夹 -dws doc move --node --folder --format json - -# 移动到目标知识库根目录(不传 --folder 时) -dws doc move --node --workspace --format json - -# 重命名文档 -dws doc rename --node --name "新名称" --format json - -# 删除文档到回收站(危险操作:必须先向用户确认,用户同意后才加 --yes 执行) -# 正确流程:1.向用户展示"即将删除「文档名」到回收站" → 2.等用户确认 → 3.执行下面命令 -dws doc delete --node --yes --format json - -# ── 工作流 9: 文档评论管理 ── - -# 1. 查看文档的所有评论 -dws doc comment list --node --format json - -# 2. 在文档上创建评论 -dws doc comment create --node --content "这里需要补充数据来源" --format json - -# 3. 创建评论并 @ 相关人 -# 先搜索用户: dws contact user search --query "张三" --format json → 提取 userId -# 再将 userId 传入 --mention -dws doc comment create --node --content "请确认这部分内容" --mention , --format json - -# 3b. 创建评论并 @群(先唯一解析群 openConversationId) -dws chat search --query "项目群" --format json -dws doc comment create --node --content "请群内确认" --mentioned-open-conversation-id --format json - -# 4. 更新某条评论(可选 --mention) +# ── 工作流 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 # 删除不可逆,必须先确认 -# 5. 删除某条评论(不可恢复,必须先确认) -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 # 校验 -# 6. 回复某条评论(commentKey 从 list 或 create 返回中获取) -dws doc comment reply --node --comment-key --content "已修改" --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 +``` -# 7. 用表情回复评论 -dws doc comment reply --node --comment-key --content "比心" --emoji --format json - -# 8. 创建划词评论(针对文档中某段选中文本) -# 先获取块列表: dws doc block list --node --format json → 提取 blockId 和文本内容 -# 确定选中文本在块内的起始偏移量 (start) 和结束偏移量 (end) -dws doc comment create-inline --node --block-id --start 0 --end 10 --content "这里需要修改" --format json - -# 9. 创建划词评论并附带引用原文 + @ 相关人 -dws doc comment create-inline --node --block-id --start 5 --end 20 --content "请确认这部分" --selected-text "被选中的原文内容" --mention , --format json - -# ── 工作流 10: 导出在线文档为 docx ── - -# 一条命令自动完成(提交→轮询→下载),无需手动编排 -dws doc export --node --output ./exported.docx - -# 如果导出命令超时或中断,可用 export get 手动查询任务状态: -# dws doc export get --job-id +```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 list`(原 `doc list`,已弃用) | `nodes[].nodeId` / folder 类型 `nodeId` | read / info / update / block 的 --node;folder 用作 `--folder` | -| `drive search`(原 `doc search`,已弃用) | 文档 `nodeId` / URL / `createTime` / `creatorUid` | read / info / update 的 --node;创建时间与创建者信息 | +| `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 | -| `import` | `nodeId` / `documentUrl` / `documentName` / `documentType`;中断时提取 `taskId` | 后续 read / info / sheet 操作;中断后用 `doc import get --task-id` | -| `drive mkdir`(原 `doc folder create`,已弃用) | `nodeId` | create 的 --folder | | `block list` | `blockId` | block insert 的 --ref-block, block update/delete 的 --block-id | +| `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` / `comment create-inline` | `commentKey` | comment reply/update/delete 的 --comment-key | -| `block list` | `blockId` + 文本内容 | comment create-inline 的 --block-id 及 --start/--end 计算 | -| `contact user search` | `userId` | comment create / create-inline / reply / update 的 --mention | -| `chat search` | `openConversationId` | comment create / reply / update 的 --mentioned-open-conversation-id | -| `wiki node create`(原 `doc file create`,已弃用) | `nodeId` | 后续 read / update / block 操作的 --node(仅 adoc 支持 read/update,axls/amind 等类型用各自产品的命令) | -| `copy` / `move` | 新 `nodeId`(copy)或原 nodeId(move) | 后续 read / info 等的 --node | - -## nodeId 多格式说明 - -所有 `--node` 参数同时支持以下格式,系统自动识别: -- **文档 ID**: 字母数字字符串,如 `9E05BDRVQePjzLkZt2p2vE7kV63zgkYA` -- **文档 URL**: `https://alidocs.dingtalk.com/i/nodes/{dentryUuid}`,如 `https://alidocs.dingtalk.com/i/nodes/9E05BDRVQePjzLkZt2p2vE7kV63zgkYA` -- **文档链接(edit/preview)**: `https://alidocs.dingtalk.com/document/{edit|preview}?...&dentryKey={key}`(必须传入完整 URL,不要提取其中的 query 参数单独使用) - -以下命令效果相同: -```bash -dws doc read --node 9E05BDRVQePjzLkZt2p2vE7kV63zgkYA -dws doc read --node "https://alidocs.dingtalk.com/i/nodes/9E05BDRVQePjzLkZt2p2vE7kV63zgkYA" -dws doc read --node "https://alidocs.dingtalk.com/document/edit?dentryKey=wo1g3x54FzVEJ5yE" -dws doc read --node "https://alidocs.dingtalk.com/document/preview?cid=74993670680&type=d&docKey=Pd6l2Z7V8ZWydl7M&dentryKey=rBGBr2r1HmwanAGW" -``` - -> **注意**:`document/edit` 和 `document/preview` 格式 URL 中的 `dentryKey` 参数值不是合法的独立 nodeId,禁止提取后单独使用,必须传入完整 URL。URL 中可能包含 `utm_source`、`chInfo` 等追踪参数,无需手动去除,直接传入完整 URL 即可。 - -`--folder` 参数同样支持文件夹 URL 或 ID。 - -## 长 Markdown 写入 - -**核心规则**:含多行、表格、`\n` 或长度 >2KB 的 Markdown **必须**通过 `--content-file` 或 `--content -`(stdin)传入,禁止直接作为 `--content` 命令行字符串——shell escape 会破坏换行和表格,且命令行长度受限。 - -`dws doc create` 和 `dws doc update` 支持两种内容来源(`--content-file` 优先于 `--content`): - -| 形式 | 说明 | -|------|------| -| `--content "..."` | 字面量(仅推荐短文本 <2KB 且无换行/表格) | -| `--content -` | 从 stdin 读取(可配合 heredoc/pipe) | -| `--content-file path` | 从文件读取(UTF-8),推荐 | - -### 短/中等长度(< 200KB)— 单步创建 - -```bash -# 1. 把内容写入 UTF-8 文本文件: -# Linux/Mac: /tmp/.md;Windows: %TEMP%\.md -# 2. 一步创建+写入: -dws doc create --name "<文档名>" --content-file [--folder ] [--workspace ] -``` - -### 超长(> 200KB 兜底)— 创建空文档 + 分片追加 - -```bash -# 1. 创建空文档拿 nodeId -dws doc create --name "<文档名>" [--folder/--workspace] # → nodeId - -# 2. 按 markdown 标题或段落边界切成 ≤200KB 的片段(不要切断表格) -# 3. 逐个追加: -dws doc update --node --content-file --mode append -``` - -### stdin 变体 - -```bash -# pipe -cat report.md | dws doc update --node --content - --mode append - -# heredoc(真实换行,含表格) -dws doc update --node --mode append --content - <<'EOF' -## 追加段落 - -| 列1 | 列2 | -|---|---| -| a | b | -EOF -``` - -## 注意事项 - -- `export` 是一体化命令,一条命令自动完成提交→轮询→下载,**无需手动编排轮询**。CLI 内部使用渐进式退避轮询(最多约 5 分钟) -- `export` 超时或中断后,CLI 会输出 `jobId`,可用 `dws doc export get --job-id ` 手动查询任务状态 -- `export` 当前仅支持钉钉在线文档 (alidocs) 导出为 `docx`;当前开源 CLI 未暴露在线表格导出命令,在线表格数据读取走 `dws sheet range read --node ` -- `update --mode overwrite` 会**清空原内容后重写**,⚠️ 谨慎使用;必须先 `--dry-run` 预览并在用户确认后加 `--yes`;默认 `--mode append`(追加)更安全 -- `read` 返回 Markdown 格式的文档内容,仅限有"下载"权限的文档 -- `read` 返回的内容中,文档里的附件会以 OSS 临时下载链接形式给出(如 `https://alidocs2.oss-cn-zhangjiakou.aliyuncs.com/res/.../att/.ext?Expires=...`),该链接会过期。链接过期后,可从 URL 路径中提取 ``(即 `/att/` 后、扩展名前的 UUID 部分),然后使用 `media download --node --resource-id ` 重新获取下载链接 -- `create` 不传 `--folder` 和 `--workspace` 时,默认创建在"我的文档"根目录 -- `create` 只能建"文档"(adoc);要建表格/脑图/白板/多维表/演示,用 `dws wiki node create --workspace --type `(`doc file create` 已弃用);建普通文件夹用 `dws drive mkdir` -- `block list/insert/update/delete` 是块级精细编辑,适合结构化修改;简单内容追加建议用 `update --mode append` -- `block insert` 优先使用 `--text` 或 `--heading` 快捷方式;复杂块类型 (table, callout 等) 使用 `--element` JSON -- `--content` 参数中的换行必须使用**真实换行符**(即实际的换行字符,Unicode `U+000A`),而不是字面量字符串 `\n`(反斜杠加字母 n)。在通过程序或大模型构造此参数时,请确保字符串在发送前已正确反转义。如果传入的是两个字符的字面量 `\n`,所有内容将渲染在同一行,导致标题、段落和表格格式全部错乱。**含多行/表格/长文本时优先用 `--content-file path.md` 或 `--content -`(stdin),不经过 shell escape,换行和表格都保持原样**(详见下方「长 Markdown 写入」)。 -- 块类型包括: paragraph, heading, blockquote, callout, columns, orderedList, unorderedList, table, sheet, attachment, slot -- 关键区分: doc(文档内容级操作) vs wiki(知识库空间级管理) vs aitable(数据表格操作) vs drive(钉盘文件管理) -- wiki 是知识库容器,doc 是知识库中的文档内容;需要 `workspaceId` 时,先用 `dws wiki space list/search` 获取,再传给 doc 的 `--workspace` 参数 -- `doc upload vs drive upload`:用户提到"知识库/文档空间/workspace" → `doc upload`;提到"钉盘/网盘/我的文件" → `drive upload`;未明确目标时默认 `drive upload` -- `upload` 支持上传任意类型文件 (PDF、Office、图片等) 到钉钉文档空间或知识库;`--convert` 可将 Office 文件转换为钉钉在线文档 -- `upload` 是三步自动完成的流程 (获取凭证 → OSS 上传 → 提交入库),无需手动分步操作 -- `download` 是两步自动完成的流程 (获取下载链接 → HTTP GET 下载),支持自动推断文件名;`--output` 可指定文件路径或目录 -- `media insert` 是三步自动完成的流程 (获取附件上传凭证 → OSS 上传 → 插入附件块到文档),无需手动分步操作 -- `media insert` 的 `--mime-type` 可选,不指定时根据文件扩展名自动推断;支持常见文件类型 (PDF、Office、图片、视频、压缩包等) -- `media insert` 与 `upload` 的区别:`upload` 将文件上传到文档空间/知识库作为独立文件;`media insert` 将文件作为附件块插入到文档正文中 -- `media download` 用于获取文档正文中附件的临时下载链接,`--resource-id` 可通过 `block list` 返回的 attachment 块获取 -- `copy` 需要对源节点有"阅读"权限,且对目标文件夹有"编辑"权限 -- `move` 需要对源节点有"管理"权限,且对目标文件夹有"编辑"权限;移动后原位置的文档将不再存在 -- `rename` 需要对文档有"编辑"权限 -- `copy` / `move` 不传 `--folder` 时,`--workspace` 表示放到知识库根目录;两者都不传则回落到"我的文档" -- `comment create` 是全文评论;`comment create-inline` 是划词评论,必须先 `block list` 拿到 `blockId` 并确定 `--start` / `--end` 偏移(按块内纯文本字符算,从 0 开始) -- 全文评论 `create` / `reply` / `update` 支持 `--mentioned-open-conversation-id` @群;划词评论不支持 - -## 自动化脚本 - -| 脚本 | 场景 | 用法 | -|------|------|------| -| [doc_create_and_write.py](../../scripts/doc_create_and_write.py) | 创建文档并写入 Markdown 内容 | `python doc_create_and_write.py --name "周报" --content "# 本周总结"` | +| `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`(查询后取结果获取文档地址) | ## 相关产品 -- [aitable](./aitable.md) — 结构化数据表格(行列/字段/记录),不是富文本文档 -- [drive](./drive.md) — 钉盘文件存储/上传/下载,不是文档内容编辑 -- [markdown](../../dingtalk-markdown/references/markdown.md) — 原生 `.md` 文件的纯文本读写 -- [report](./report.md) — 钉钉日志系统(日报/周报模版),不是在线文档 +- [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 04e97ac2..456650a5 100644 --- a/skills/multi/dingtalk-doc/references/doc/doc-block.md +++ b/skills/multi/dingtalk-doc/references/doc/doc-block.md @@ -184,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` | diff --git a/skills/multi/dingtalk-doc/references/doc/doc-comment.md b/skills/multi/dingtalk-doc/references/doc/doc-comment.md index 475fa352..6bdc7eab 100644 --- a/skills/multi/dingtalk-doc/references/doc/doc-comment.md +++ b/skills/multi/dingtalk-doc/references/doc/doc-comment.md @@ -3,7 +3,7 @@ > **前置条件(MUST READ):** 执行本命令前,必须先用 Read 工具读取以下文件: > 1. [`../doc.md`](../doc.md) — 命令路由 + 场景索引 + 意图判断 + 工作流 > -> **同任务常配合**:`dws contact user search`(查 `--mention` 用 userId)/ `dws chat search`(查群用 openConversationId)/ [`doc-block.md`](./doc-block.md)(划词评论必须先取 blockId 与 paragraph 文本) +> **同任务常配合**:`dws aisearch person`(查 `--mention` 用 userId)/ `dws chat search`(查群用 openConversationId)/ [`doc-block.md`](./doc-block.md)(划词评论必须先取 blockId 与 paragraph 文本) --- @@ -67,17 +67,17 @@ Flags: ## 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,可重复指定或逗号分隔 ``` @@ -86,23 +86,23 @@ Flags: ## 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` 不支持 @群。不得只在正文中写 `@群名` 冒充真实群 mention。 +> **能力边界:** `create_inline_comment` 后端当前未提供 `mentionedOpenConversationIds`,因此 `create-inline` 不支持 @群。不得只在正文写 `@群名` 冒充真实群 mention;如果用户要求划词评论 @群,应明确提示暂不支持。 ``` Usage: @@ -123,13 +123,18 @@ Flags: ## 关键说明 -- `--mention` 接受 `userId` 列表(逗号分隔),需要先用 `dws contact user search --query "<姓名>"` 拿到 userId。 -- `--mentioned-open-conversation-id` 接受群 `openConversationId`,可重复传入或逗号分隔;仅全文评论 `create`、`reply`、`update` 支持,`create-inline` 不支持。 -- 群名先用 `dws chat search --query "<群名>" --format json` 唯一解析;只在正文写 `@群名` 不会创建真实 mention。 -- `--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` 不能同时 @群。 +- `reply --emoji` 与群 mention 冲突;CLI 会在调用服务端前报错,不会静默忽略。 +- `delete` 是不可逆操作;AI Agent 必须先让用户确认,再追加 `--yes`,避免 CLI 进入交互等待。 ## 上下文传递 @@ -140,8 +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 chat search` 的 `openConversationId` | `comment create/reply/update` 的 `--mentioned-open-conversation-id` | +| `dws aisearch person` 的 `userId` | `comment create/reply/create-inline` 的 `--mention` | +| `dws chat search` 的 `openConversationId` | `comment create/update/reply` 的 `--mentioned-open-conversation-id` | ## 常用模板 @@ -155,8 +160,8 @@ 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 @@ -167,15 +172,21 @@ dws doc comment create --node --content "请群内关注" --mentioned-o # 文字回复 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 # 计算偏移后: @@ -189,4 +200,4 @@ 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-create.md b/skills/multi/dingtalk-doc/references/doc/doc-create.md index f3f311f3..bf11a677 100644 --- a/skills/multi/dingtalk-doc/references/doc/doc-create.md +++ b/skills/multi/dingtalk-doc/references/doc/doc-create.md @@ -4,15 +4,15 @@ > 1. [`../doc.md`](../doc.md) — 命令路由 + 场景索引 + 意图判断 + 工作流 > 2. [`./style/doc-create-workflow.md`](./style/doc-create-workflow.md) — 创建工作流(标题、位置、骨架、回读校验) > 3. [`./style/doc-style-guideline.md`](./style/doc-style-guideline.md) — 排版规范(草稿元素清单、骨架样板) -> 4. [`./doc-update.md` §内容写入管道](./doc-update.md#内容写入管道createupdate-共用) — 长内容自动分片、`--content-file` vs `--content` 选择 +> 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`](../sheet.md#创建钉钉表格文档)(钉钉在线电子表格 `axls`),**不要**走 `doc create` -> - 用户说「创建多维表格 / 新建 AI 表格 / 建个 base / 数据库表」等 → 走 [`dws aitable base create`](../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。 @@ -43,7 +43,7 @@ Flags: - **`--name` 是 H1**:正文从 `##` 开始;正文内不要再写 `#` 一级标题(除非确需且已说明动机)。 - 不传 `--folder` 和 `--workspace` 时,默认创建在「我的文档」根目录。 - `--folder` 仅接受文档文件夹 `nodeId` / `dentryUuid` / alidocs 文件夹 URL;**禁止**传入 drive `dentryId`、`parentId`、`spaceId` 这类纯数字 ID。 -- 输入方式选择见 [`./doc-update.md` §内容写入管道](./doc-update.md#内容写入管道createupdate-共用)(与 update 共用)。短文本字面量可 `--content`,多行/表格/特殊字符必须 `--content-file` 或 `--content -`。 +- 输入方式选择见 [`./doc-update.md` §内容写入管道](./doc-update.md)(与 update 共用)。短文本字面量可 `--content`,多行/表格/特殊字符必须 `--content-file` 或 `--content -`。 - 长内容(>30000 字符)CLI 自动分片:先创建空文档拿 `nodeId`,再按 markdown 标题边界切分后逐片 append;调用方无需手动编排。 ## 上下文传递 diff --git a/skills/multi/dingtalk-doc/references/doc/doc-export.md b/skills/multi/dingtalk-doc/references/doc/doc-export.md index 0abbeedf..319b2aee 100644 --- a/skills/multi/dingtalk-doc/references/doc/doc-export.md +++ b/skills/multi/dingtalk-doc/references/doc/doc-export.md @@ -1,13 +1,13 @@ -# doc export(在线文档导出为 docx) +# doc export(在线文档导出为 docx/markdown/pdf) > **前置条件(MUST READ):** 执行本命令前,必须先用 Read 工具读取以下文件: > 1. [`../doc.md`](../doc.md) — 命令路由 + 场景索引 + 意图判断 + 工作流 -> **路由前置判断**:用户说「下载/导出」时**必须**先用 [`./doc-info.md`](./doc-info.md) `info --node --format json` 查 `contentType`: -> - `contentType` 为 `ALIDOC`(在线文档)→ **必须用 `export`**,禁止用 `download` -> - `contentType` 为 `DOCUMENT`/`IMAGE`/`VIDEO` 等(已有文件)→ 用 `dws drive download`(详见 [`../drive.md`](../drive.md)) +> **路由前置判断**:用户说「下载/导出」时**必须**先用 `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,两者完全不同。 +> `drive download` 只能下载**已有文件**(原样下载),`export` 是将**在线文档格式转换**后导出为 docx、markdown 或 pdf,两者完全不同。 --- @@ -19,19 +19,25 @@ Usage: Example: dws doc export --node "https://alidocs.dingtalk.com/i/nodes/xxx" --output ./exported.docx dws doc export --node --output ~/downloads/ + dws doc export --node --export-format markdown --output ./exported.md + # 异步模式:只提交不等待,立即返回 jobId Flags: - --node string 要导出的文档标识,支持文档 URL 或 dentryUuid (必填) - --output string 本地保存路径,文件路径或目录 (必填) - --export-format string 导出格式,当前仅支持 docx (默认) + --node string 要导出的文档标识,支持文档 URL 或 dentryUuid (必填) + --output string 本地保存路径,文件路径或目录 (必填) + --export-format string 导出格式,支持 docx(默认)、markdown(或 md)和 pdf ``` CLI 内部自动完成:提交导出任务 → 渐进式退避轮询(最多约 5 分钟)→ 成功后自动下载文件。 **只需一条命令,无需手动轮询。** +**任务查询**:`doc export` 内部自动轮询直到完成,通常不需要手动查询;仅在超时或中断后用 `dws doc export get --job-id ` 兜底查询。 + --- ## doc export get(手动兜底查询任务) +> 任务状态:PROCESSING(处理中)/ SUCCESS(导出成功,返回 downloadUrl)/ FAILED(导出失败)。 + ``` Usage: dws doc export get [flags] @@ -47,25 +53,39 @@ Flags: - `export` 是一体化命令,一条命令自动完成提交→轮询→下载,**无需手动编排轮询**。CLI 内部使用渐进式退避轮询(最多约 5 分钟)。 - `export` 超时或中断后,CLI 会输出 `jobId`,可用 `dws doc export get --job-id ` 手动查询任务状态。 -- `export` 当前仅支持钉钉在线文档(alidocs,`contentType=ALIDOC`)导出为 `docx`,**在线表格导出请使用其他命令**。 -- `--output` 既可以是文件完整路径,也可以是目录(CLI 自动按文档名生成 `.docx`)。 +- `export` 支持钉钉在线文档(alidocs,`contentType=ALIDOC`)导出为 `docx`、`markdown` 或 `pdf`,**在线表格导出请使用其他命令**。 +- `--export-format` 支持 `docx`(默认)、`markdown`(或 `md`)和 `pdf` 三种格式。 +- `--output` 既可以是文件完整路径,也可以是目录(CLI 自动按文档名生成 `.docx`、`.md` 或 `.pdf`)。 +- `doc export get` 主参数为 `--job-id`。 ## 上下文传递 | 从返回中提取 | 用于 | |-------------|------| -| `localPath` | 用户可访问的本地文件路径 | -| 中断时返回的 `jobId` | `export get` 的 `--job-id` | +| `localPath` | 用户可访问的本地文件路径(同步模式) | +| 超时/中断时返回的 `jobId` | `export get` 的 `--job-id`(查询后取 downloadUrl 获取下载地址) | ## 常用模板 ```bash -# 一体化导出(最常用) +# 一体化导出为 docx(最常用) dws doc export --node --output ./exported.docx -# 输出到目录(自动按文档名命名) +# 导出为 markdown +dws doc export --node --export-format markdown --output ./exported.md + +# 使用 md 缩写导出为 markdown +dws doc export --node --export-format md --output ./exported.md + +# 导出为 pdf +dws doc export --node --export-format pdf --output ./exported.pdf + +# 输出到目录(自动按文档名命名,docx 格式) dws doc export --node --output ~/downloads/ +# 输出到目录(markdown 格式,自动生成 .md 扩展名) +dws doc export --node --export-format markdown --output ~/downloads/ + # alidocs URL 直传 dws doc export --node "https://alidocs.dingtalk.com/i/nodes/" --output ./exported.docx @@ -73,8 +93,18 @@ dws doc export --node "https://alidocs.dingtalk.com/i/nodes/" --output dws doc export get --job-id --format json ``` +## 输出规范 + +导出完成后的输出必须遵循以下规则: + +- **仅返回本地文件路径**:导出成功后,直接告知用户文件已保存到本地的具体路径 +- **禁止上传 CDN**:导出完成后不要调用任何上传命令将文件上传到钉钉 CDN +- **禁止输出下载链接**:不得生成或展示 `down.dingtalk.com` 等 CDN 下载链接 +- 正确示例:`文档已导出到: ./文档名.docx` +- 错误示例:上传文件并展示 CDN 下载链接 + ## 参考 - [`../doc.md` §意图判断](../doc.md#意图判断)(如何路由到本命令) - [`./doc-info.md`](./doc-info.md)(前置:判断 contentType=ALIDOC 才走 export) -- [`../drive.md`](../drive.md)(非 ALIDOC 文件用 `dws drive download`) +- [`../drive.md`](../../../dingtalk-drive/references/drive.md)(非 ALIDOC 文件用 `dws drive download`) diff --git a/skills/multi/dingtalk-doc/references/doc/doc-file-ops.md b/skills/multi/dingtalk-doc/references/doc/doc-file-ops.md deleted file mode 100644 index 6ed912b2..00000000 --- a/skills/multi/dingtalk-doc/references/doc/doc-file-ops.md +++ /dev/null @@ -1,229 +0,0 @@ -# doc 文件操作(upload / download / copy / move / rename / delete + folder create) - -> **前置条件(MUST READ):** 执行本命令前,必须先用 Read 工具读取以下文件: -> 1. [`../doc.md`](../doc.md) — 命令路由 + 场景索引 + 意图判断 + 工作流 - -> **弃用提示(文件管理命令正在迁移到 drive / wiki)**:本文所列 `doc` 文件管理命令虽仍能跑,但执行时会打印弃用警告,请优先改用 `drive` / `wiki` 对应命令: -> - `doc download` → **`dws drive download`**(下载已有文件;在线文档导出 docx 仍走 `doc export`) -> - `doc folder create` → **`dws drive mkdir`**(个人空间/钉盘)或 **`dws wiki node create --workspace --type folder`**(知识库内) -> - `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.md` §意图判断](../doc.md#意图判断)(如何路由到本命令族) -- [`./doc-info.md`](./doc-info.md)(前置:判断 contentType 决定 download / export) -- [`./doc-export.md`](./doc-export.md)(在线文档格式转换导出 docx) -- [`./doc-media.md`](./doc-media.md)(文件作为附件块插入文档正文,与 upload 区分) -- [`./doc-search.md`](./doc-search.md) / [`./doc-list.md`](./doc-list.md)(拿 nodeId 的入口) diff --git a/skills/multi/dingtalk-doc/references/doc/doc-import.md b/skills/multi/dingtalk-doc/references/doc/doc-import.md index bd6291de..7387fe48 100644 --- a/skills/multi/dingtalk-doc/references/doc/doc-import.md +++ b/skills/multi/dingtalk-doc/references/doc/doc-import.md @@ -1,53 +1,129 @@ -# 本地文件导入为在线文档 (doc import) +# doc import(本地文件导入为在线文档) -## 使用场景 +> **前置条件(MUST READ):** 执行本命令前,必须先用 Read 工具读取以下文件: +> 1. [`../doc.md`](../doc.md) — 命令路由 + 场景索引 + 意图判断 + 工作流 -用户说"导入 Word/Excel/Markdown/xmind 到钉钉文档"、"把本地文件转成在线文档"、"导入到知识库/文件夹"时,使用 `dws doc import`。 +> **支持的文件格式**:docx, doc, xlsx, xls, md, txt, xmind, mark +> **文件大小限制**:20MB -不要先读取文件内容再调用 `doc create` 或 `doc update`。`doc import` 会按文件格式走导入任务,保留更完整的原始结构。 +--- -## 命令 +## doc import(一体化命令) -```bash -dws doc import --file ./report.docx --format json -dws doc import --file ./notes.md --folder --format json -dws doc import --file ./data.xlsx --workspace --format json -dws doc import --file ./draft.md --name "项目周报" --format json +``` +Usage: + dws doc import [flags] +Example: + dws doc import --file ./report.docx --workspace + dws doc import --file ./notes.md --folder + dws doc import --file ./data.xlsx --workspace + dws doc import --file ./draft.md --workspace --name "项目周报" +Flags: + --file string 本地文件路径 (必填) + --folder string 目标文件夹 ID 或 URL (与 --workspace 必填其一) + --workspace string 目标知识库 ID 或 URL (与 --folder 必填其一) + --name string 导入后文档名称 (可选,默认取文件名不含扩展名) ``` -```bash -dws doc import get --task-id --format json +> 注意:`--folder` 接受文件夹的 dentryUuid(nodeId)或 alidocs 文件夹 URL。 +> 如果通过 `dws doc info` 获取信息,应使用返回的 `nodeId` 字段, +> 而非 `folderId`(后者是父文件夹 ID)。 +> 当 URL 指向的节点 `nodeType` 不是 "folder" 时,应提示用户检查链接。 + +CLI 内部自动完成:创建导入会话 → 上传文件到 OSS → 确认导入触发转换 → 渐进式退避轮询(最多约 5 分钟)。 +**只需一条命令,无需手动轮询。** + +--- + +## doc import get(手动兜底查询任务) + +> 任务状态:processing(转换中)/ completed(导入成功,返回 documentUrl)/ failed(导入失败)。 + +``` +Usage: + dws doc import get [flags] +Example: + dws doc import get --task-id +Flags: + --task-id string 导入任务 ID (必填) ``` -## 参数 +仅在 `dws doc import` 超时或中断后,用于手动查询任务状态。通常不需要调用。 -| 参数 | 说明 | -|------|------| -| `--file` | 本地文件路径,必填 | -| `--folder` | 目标文件夹 ID 或 URL,可选 | -| `--workspace` | 目标知识库 ID 或 URL,可选 | -| `--name` / `-n` | 导入后的文档名称;不传时使用文件名 | -| `--task-id` | `import get` 查询导入任务时必填 | +**输出示例**(统一 TaskResult 格式): +```json +{"id":"task-abc123","type":"import","status":"SUCCESS","resultUrl":"https://alidocs.dingtalk.com/i/nodes/xxx","message":"","createTime":"2024-01-15T10:30:00Z"} +``` -支持格式:docx、doc、xlsx、xls、md、txt、xmind、mark。文件大小上限 20MB。 +**处理中示例**: +```json +{"id":"task-abc123","type":"import","status":"PROCESSING","resultUrl":"","message":"任务处理中,请稍后查询","createTime":"2024-01-15T10:30:00Z"} +``` -## 工作流 +## 关键说明 -1. 确认本地文件存在且格式受支持。 -2. 如果用户指定目标知识库或文件夹,传 `--workspace` 或 `--folder`;未指定时导入到默认位置。 -3. 执行 `dws doc import --file ... --format json`。 -4. 正常情况下 CLI 会自动提交、上传并轮询导入任务。 -5. 如果命令超时或中断,从输出中提取 `taskId`,再执行 `dws doc import get --task-id --format json`。 +- `import` 是一体化命令,一条命令自动完成创建会话→上传→确认→轮询,**无需手动编排**。CLI 内部使用渐进式退避轮询(最多约 5 分钟)。 +- `import` 超时或中断后,CLI 会输出 `taskId`,可用 `dws doc import get --task-id ` 手动查询任务状态。 +- 支持的文件格式:docx, doc, xlsx, xls, md, txt, xmind, mark(共 8 种)。 +- 文件大小限制:20MB。超过限制时 CLI 会直接报错,不会发起网络请求。 +- `--folder` 和 `--workspace` 至少传一个,不传会报错。如需导入到"我的文档"根目录,请先获取 workspaceId:`dws wiki space list --type myWikiSpace --format json`,然后传 `--workspace `。 +- `--folder` 优先级高于 `--workspace`:两者都传时以 `--folder` 为准。 ## 上下文传递 -| 操作 | 从返回中提取 | 用于 | -|------|-------------|------| -| `doc import` | `nodeId` / `documentUrl` / `documentName` / `documentType` | 后续 `doc read` / `doc info` / `sheet` 操作 | -| `doc import` 中断 | `taskId` | `doc import get --task-id` 查询任务状态 | +| 从返回中提取 | 用于 | +|-------------|------| +| `documentUrl` | 导入完成后的在线文档地址(可直接分享给用户) | +| `documentName` | 导入后的文档名称 | +| `documentType` | 文档类型(DOC / SHEET / MIND) | +| 超时/中断时返回的 `taskId` | `import get` 的 `--task-id`(查询后取 documentUrl 获取文档地址) | -## 注意事项 +## 输出规范 -- `--folder` 和 `--workspace` 都是目标位置参数;用户明确指定文件夹时优先使用 `--folder`。 -- 导入 Excel 后通常得到在线表格,后续数据读取和编辑走 `dws sheet ...`。 -- 导入 Markdown 或 Word 后通常得到在线文档,后续内容读取和编辑走 `dws doc ...`。 +导入完成后的输出必须遵循以下规则: + +- **返回在线文档 URL**:导入成功后,直接告知用户文档已导入并提供在线文档链接 +- **输出格式**:`文件已导入为在线文档: ` +- 正确示例:`文件已导入为在线文档: https://alidocs.dingtalk.com/i/nodes/xxx` + +## 格式与文档类型映射 + +| 文件扩展名 | 导入后文档类型 | 说明 | +|-----------|--------------|------| +| `.docx` / `.doc` | DOC(文字文档) | Word 文档导入为在线文档 | +| `.xlsx` / `.xls` | SHEET(电子表格) | Excel 文件导入为在线表格 | +| `.md` / `.txt` | DOC(文字文档) | Markdown/纯文本导入为在线文档 | +| `.xmind` | MIND(脑图) | XMind 文件导入为在线脑图 | +| `.mark` | MIND(脑图) | Mark 格式导入为在线脑图 | + +## 常用模板 + +```bash +# 导入 Word 文档(最常用) +dws doc import --file ./report.docx --workspace --format json + +# 导入 Markdown 文件 +dws doc import --file ./notes.md --workspace --format json + +# 导入到指定文件夹 +dws doc import --file ./data.xlsx --folder --format json + +# 导入到知识库根目录 +dws doc import --file ./plan.docx --workspace --format json + +# 自定义文档名称 +dws doc import --file ./draft.md --workspace --name "项目方案" --format json + +# 导入 XMind 脑图文件 +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.md b/skills/multi/dingtalk-doc/references/doc/doc-info.md index 0b98b804..c80bd791 100644 --- a/skills/multi/dingtalk-doc/references/doc/doc-info.md +++ b/skills/multi/dingtalk-doc/references/doc/doc-info.md @@ -2,9 +2,11 @@ > **前置条件(MUST READ):** 执行本命令前,必须先用 Read 工具读取以下文件: > 1. [`../doc.md`](../doc.md) — 命令路由 + 场景索引 + 意图判断 + 工作流 -> 2. [`../../url-patterns.md`](../../url-patterns.md) — 仅当用户原始 `alidocs` URL 需要 probe 时 +> 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 后读正文) ## 命令格式 @@ -20,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` → `dws sheet range read` | -| ALIDOC | able | 在线查询多维表格记录 | `dws aitable table list` → `dws aitable record query` | -| 非 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 格式 @@ -51,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 边界与参数映射 @@ -67,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 参数 ``` @@ -78,7 +67,7 @@ Flags: | 方式 | 触发条件 | 操作 | |------|----------|------| | **A** | 用户**直接提供文档 URL 或 nodeId** | **直接传给 `--node`**,无需额外查询;优先使用此方式 | -| **B** | 用户给出关键字 / 文档名 | `dws drive search --query "<关键字>" --format json` 或 `dws wiki node search --workspace --keyword "<关键字>"` 从返回中提取 nodeId | +| **B** | 用户给出关键字 / 文档名 | `dws drive search --query "<关键字>" --format json` 或 `dws wiki node search --workspace --query "<关键字>"` 从返回中提取 nodeId | | **C** | 用户指向某个文件夹下的文档 | `dws drive list --workspace --format json` 或 `dws wiki node list --workspace ` 从返回中提取 | > **关键节省**:方式 A 命中时,禁止再调 search/list "确认一下" —— 用户提供的 URL/nodeId 本身就是权威输入。同理,`--folder` 也支持 alidocs 文件夹 URL 直传,不要先 search 把 URL 解析成纯数字 ID 再传。 @@ -110,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` 入参 | ## 常用模板 @@ -145,6 +134,6 @@ dws doc info --node # 错误 ## 参考 - [`../doc.md` §意图判断](../doc.md#意图判断)(如何路由到本命令) -- `dws drive search` / `dws wiki node search`(前置:定位 nodeId 的搜索入口,详见 [`../drive.md`](../drive.md) / [`../wiki.md`](../wiki.md)) -- [`./doc-read.md`](./doc-read.md)(contentType=ALIDOC + extension=adoc 的后续命令) -- [`../../url-patterns.md`](../../url-patterns.md)(用户原始 alidocs URL 的 probe 流程) +- `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)(extension=adoc 的后续命令) +- [`url-patterns.md`](../../../dws-shared/references/url-patterns.md)(用户原始 alidocs URL 的 probe 流程) 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.md b/skills/multi/dingtalk-doc/references/doc/doc-media.md index 3be8600b..079a4f48 100644 --- a/skills/multi/dingtalk-doc/references/doc/doc-media.md +++ b/skills/multi/dingtalk-doc/references/doc/doc-media.md @@ -94,5 +94,5 @@ dws doc media download --node --resource-id - [`../doc.md` §意图判断](../doc.md#意图判断)(如何路由到本命令族) - [`./doc-block.md`](./doc-block.md)(block list 取 attachment 的 resourceId) -- [`../drive.md`](../drive.md)(独立文件上传:`dws drive upload`) +- [`../drive.md`](../../../dingtalk-drive/references/drive.md)(独立文件上传:`dws drive upload`) - [`./style/doc-style-guideline.md` §4.9 附件与图片](./style/doc-style-guideline.md)(图示与附件使用规范) diff --git a/skills/multi/dingtalk-doc/references/doc/doc-permission.md b/skills/multi/dingtalk-doc/references/doc/doc-permission.md deleted file mode 100644 index 647c5f24..00000000 --- a/skills/multi/dingtalk-doc/references/doc/doc-permission.md +++ /dev/null @@ -1,117 +0,0 @@ -# doc permission(文档权限:add / update / list) - -> **前置条件(MUST READ):** 执行本命令前,必须先用 Read 工具读取以下文件: -> 1. [`../doc.md`](../doc.md) — 命令路由 + 场景索引 + 意图判断 + 工作流 - -> **关键区分**: -> - "把**某篇文档**授权给某人" → `doc permission add`(节点级,包括「我的文档」下的文档都支持) -> - "把**某个知识库**整体授权给某人" → `wiki member add`(容器级,但**「我的文档」个人空间不支持**) - ---- - -## doc permission add(节点级授权) - -``` -Usage: - dws doc permission add [flags] -Example: - dws doc permission add --node --user uid1 --role READER - dws doc permission add --node --user uid1,uid2 --role EDITOR - dws doc permission add --node "https://alidocs.dingtalk.com/i/nodes/" --user uid1 --role MANAGER -Flags: - --node string 目标文档/文件夹的 ID 或 URL (必填) - --user strings 被授权的用户 userId 列表,逗号分隔 (必填,单次最多 30 个) - --role string 授予的角色 (必填,大小写敏感,必须全大写): MANAGER (管理者) / EDITOR (可编辑) / DOWNLOADER (可下载) / READER (可阅读) - --workspace string 所属知识库 ID (选填,仅用于辅助构造返回的 docUrl,业务实际依赖 nodeId) -``` - -> **重要约束**: -> -> - 仅支持 USER 类型授权。 -> - 角色枚举严格大写:`MANAGER` / `EDITOR` / `DOWNLOADER` / `READER`(`OWNER` 不可通过此接口添加)。 -> - 操作者需在该节点具备「可编辑(EDITOR)」及以上角色(OWNER / MANAGER / EDITOR)。 -> - 授权对象是文档节点本身,不需要也不应该用 `wiki member add`(那个是知识库容器级授权)。 - ---- - -## doc permission update(修改权限) - -``` -Usage: - dws doc permission update [flags] -Example: - dws doc permission update --node --user uid1 --role EDITOR - dws doc permission update --node --user uid1,uid2 --role READER -Flags: - --node string 目标文档/文件夹的 ID 或 URL (必填) - --user strings 目标用户 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 --max-results 50 - dws doc permission list --node --filter-role EDITOR -Flags: - --node string 目标文档/文件夹的 ID 或 URL (必填) - --workspace string 所属知识库 ID (选填) - --max-results int 返回数量上限,最大 200 (默认 50) - --filter-role string 按角色过滤: MANAGER / EDITOR / DOWNLOADER / READER (选填) -``` - -> 接口不支持游标分页,使用 `--max-results` 一次性拉取。 - -## 关键说明 - -- 三个命令的 `--role` / `--filter-role` 都是**大小写敏感**且**必须全大写**:`MANAGER` / `EDITOR` / `DOWNLOADER` / `READER`。 -- `--user` 单次最多 30 个 userId,逗号分隔。 -- 需要 userId 时,使用 `dws contact user search --query "<姓名>"`(跨产品命令)取得。 - -## 上下文传递 - -| 从返回中提取 | 用于 | -|-------------|------| -| `dws contact user search` 的 `userId` | `permission add/update` 的 `--user` | -| [`./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 - -# 拉取更多结果(默认 50,最大 200) -dws doc permission list --node --max-results 200 --format json - -# 给单人开 READER 权限 -dws doc permission add --node --user --role READER --format json - -# 给多人开 EDITOR 权限(最多 30 个 uid) -dws doc permission add --node --user ,, --role EDITOR --format json - -# 设为管理者 -dws doc permission add --node --user --role MANAGER --format json - -# 修改某人角色(READER → EDITOR) -dws doc permission update --node --user --role EDITOR --format json - -# 通过 alidocs URL 直接授权 -dws doc permission add --node "https://alidocs.dingtalk.com/i/nodes/" --user --role READER --format json -``` - -## 参考 - -- [`../doc.md` §意图判断](../doc.md#意图判断)(如何路由到本命令族) -- `dws contact user search`(取 userId 的入口,跨产品命令) -- [`wiki.md`](../../../dingtalk-wiki/references/wiki.md)(知识库容器级授权 `wiki member add`,与本节点级权限相区分) diff --git a/skills/multi/dingtalk-doc/references/doc/doc-read.md b/skills/multi/dingtalk-doc/references/doc/doc-read.md index 7b84e42a..5feebfa8 100644 --- a/skills/multi/dingtalk-doc/references/doc/doc-read.md +++ b/skills/multi/dingtalk-doc/references/doc/doc-read.md @@ -4,7 +4,7 @@ > 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)(块级精修前先读结构) ## 命令格式 @@ -20,11 +20,11 @@ Flags: --node string 文档 ID 或 URL (必填) --content-format string 输出格式: 默认为 markdown,可选 jsonml(返回完整 JSONML 结构) --output string 输出到本地文件路径(仅 --content-format jsonml 时生效) - --scope string JSONML 节点范围: outline / range / section / tags - --tags string 自定义 JSONML tag 列表,逗号分隔;仅 --scope tags 使用且必填 - --max-depth int 筛选遍历最大深度,0 表示不限 - --start-block-id string range / section 起始块 UUID - --end-block-id string range 结束块 UUID;空或 "-1" 表示到文档末尾 + --scope string 按 scope 筛选节点(需 --content-format jsonml): outline(全部 h1-h6 标题)/range(区间)/section(单块)/tags(配合 --tags 自定义); 详见下方 §scope/tags 节点筛选 + --tags string 自定义 JSONML tag 列表(逗号分隔, 如 h1,h2,table); 仅在 --scope tags 时使用且必填 + --max-depth int 筛选遍历最大深度, 0 表示不限(仅 --content-format jsonml + --scope/--tags 时生效) + --start-block-id string range/section 起始块 ID(节点 uuid); scope=range/section 时必填 + --end-block-id string range 结束块 ID(节点 uuid); "-1"或空=到文档末尾(仅 scope=range 生效) ``` ## 关键说明 @@ -48,28 +48,32 @@ Flags: ## scope/tags 节点筛选(返回 JSONML fragment) -只读取文档大纲、一个块或一段区间时,用 `--scope` 避免拉取整篇。筛选仅适用于 JSONML,必须同时传 `--content-format jsonml`。 +当只想读取文档的**部分节点**(如大纲、某区间、某个块)时,用 `--scope` 筛选,避免拉取整篇。**筛选是 JSONML 链路能力,MUST 配合 `--content-format jsonml`**(缺失会报错 `--scope/--tags requires --content-format jsonml`)。**`--scope` 必须显式指定为 `outline`|`range`|`section`|`tags` 之一**;`--tags` 仅在 `--scope tags` 时使用(其他组合会报错): -- `--scope outline`:返回全部 `h1` 到 `h6` 标题。 -- `--scope tags --tags h1,table`:返回指定 tag;`--tags` 只能与 `scope=tags` 一起使用。 -- `--scope range --start-block-id [--end-block-id ]`:返回顶层闭区间;结束 ID 为空或 `-1` 时读到文末。 -- `--scope section --start-block-id `:返回该块及其完整子树。 -- `--max-depth` 可限制筛选遍历深度,`0` 表示不限制。 +- **tag 类**(outline / tags):按节点类型筛选。`outline`=全部 h1-h6 标题预设;`--scope tags --tags h1,table` 按自定义 tag 子集筛选(tables/images/code 预设已下线,改用 `--scope tags --tags table`/`img`/`code`)。 +- **锚点类**(range/section):按位置截取。`range`=顶层 `[start,end]` 闭区间(`--end-block-id "-1"` 或留空=到文末);`section`=只取 `--start-block-id` 指向的单块(完整子树)。块 ID(节点 uuid)可从 [`doc-block.md`](./doc-block.md) 的 `list_document_blocks` 或 JSONML 节点的 `uuid` 属性获得;`scope=range/section` 时 `--start-block-id` 必填。 -块 UUID 可从 [`doc-block.md`](./doc-block.md) 的块列表或完整 JSONML 节点的 `uuid` 属性取得。 +### 返回:JSONML fragment 容器(MUST 剥壳消费) -筛选结果是查询用的虚拟 fragment 容器: +筛选结果是一个 **JSONML fragment 容器**(承载在 `jsonml` 字段): ```json -["fragment", {"source": "outline"}, ["h1", {"uuid": "h1a"}, "一级标题"]] +["fragment", {"source": "outline"}, ["h1", {"uuid": "h1a"}, "一级标题"], ["h2", {"uuid": "h2a"}, "二级标题"]] ``` -消费时必须剥掉 `"fragment"` 和属性对象,只取后面的 children。不得把 fragment 容器整体写回文档。 +- `source` 标识产生结果的操作(range/section/outline/tags)。 +- fragment 是**查询结果虚拟容器**,仅用于读出: + - **消费时 MUST 剥掉 fragment 外层**(跳过 index 0 的 `"fragment"` 与 index 1 的 attrs 对象),取其后的 children 使用。 + - **MUST NOT 把 fragment 整体写回文档**(如透传给 `doc update --content-format jsonml`)——服务端 JSONML 写校验会拦截。若要写回,只写剥壳后的 children。 ```bash +# 读文档大纲(目录) dws doc read --node --content-format jsonml --scope outline -dws doc read --node --content-format jsonml --scope range --start-block-id --end-block-id +# 读某区间(从块 A 到块 B)的 JSONML +dws doc read --node --scope range --start-block-id --end-block-id --content-format jsonml +# 读单个块及其子树 dws doc read --node --content-format jsonml --scope section --start-block-id +# 按自定义 tag 读取(如所有表格 / 图片) dws doc read --node --content-format jsonml --scope tags --tags table,img ``` @@ -78,8 +82,8 @@ dws doc read --node --content-format jsonml --scope tags --tags table,i | 从返回中提取 | 用于 | |-------------|------| | Markdown 正文 | 用户可读输出 / 二次处理 | -| JSONML `jsonml`(完整 body,无 scope) | [`doc-update.md`](./doc-update.md) 的 `--content-file` + `--content-format jsonml` | -| JSONML fragment(scope 筛选) | 剥掉 fragment 外层后消费 children;不得整体写回 | +| JSONML `jsonml`(完整 body,无 scope/tags 时) | [`doc-update.md`](./doc-update.md) 的 `--content-file` + `--content-format jsonml`(可整体写回) | +| JSONML `jsonml`(fragment,scope/tags 筛选时) | 剥掉 fragment 外层取 children 使用;**MUST NOT 整体写回** | | JSONML `revision` | [`doc-update.md`](./doc-update.md) 的 `--revision`(可选;担心被并发覆盖时使用) | | 附件链接中的 `resourceId` | [`doc-media.md`](./doc-media.md) 的 `--resource-id`(链接过期后续期) | 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-update.md b/skills/multi/dingtalk-doc/references/doc/doc-update.md index 34d1097b..026b33c4 100644 --- a/skills/multi/dingtalk-doc/references/doc/doc-update.md +++ b/skills/multi/dingtalk-doc/references/doc/doc-update.md @@ -170,7 +170,7 @@ dws doc update --node --content-file --mode overwrite --content-f dws doc update --node --content-file --mode append --content-format markdown ``` -> **注意**:分块 append 存在静默失败风险(部分片段返回 success 但实际未写入),执行前**必须**向用户发出截断风险提示并等待确认。完整规范见 [`../../best_practices/04-document.md` «分块 append 截断风险提示»](../../best_practices/04-document.md)。 +> **注意**:分块 append 存在静默失败风险(部分片段返回 success 但实际未写入),执行前**必须**向用户发出截断风险提示并等待确认。完整规范见 [`../../best_practices/04-document.md` «分块 append 截断风险提示»](../04-document.md)。 ### stdin 变体 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-update-workflow.md b/skills/multi/dingtalk-doc/references/doc/style/doc-update-workflow.md index 45d1cb88..88d8c68f 100644 --- a/skills/multi/dingtalk-doc/references/doc/style/doc-update-workflow.md +++ b/skills/multi/dingtalk-doc/references/doc/style/doc-update-workflow.md @@ -258,7 +258,7 @@ dws doc update --node --content-file /tmp/-full.md --mode overwri 当一次性追加内容 **超过 200KB** 时,必须拆分为多片 `--mode append`,并在执行第一片**之前**向用户发出截断风险提示等待确认。 -完整规范(提示话术模板、触发条件、失败处理)见 [04-document.md «分块 append 截断风险提示»](../../../best_practices/04-document.md)。 +完整规范(提示话术模板、触发条件、失败处理)见 [04-document.md «分块 append 截断风险提示»](../../04-document.md)。 update 场景下的额外约束: diff --git a/skills/multi/dingtalk-doc/references/intent-guide.md b/skills/multi/dingtalk-doc/references/intent-guide.md new file mode 100644 index 00000000..95684503 --- /dev/null +++ b/skills/multi/dingtalk-doc/references/intent-guide.md @@ -0,0 +1,19 @@ +# doc 局部意图消歧 + +本文件从单 Skill `intent-guide.md` 拆分而来,仅保留与本产品相关的跨产品消歧规则。 + +| 用户说... | 真实意图 | 应该用 | 不要用 | 理由 | +|---|---|---|---|---| +| "搜一下 OAuth2 接入文档" | 搜索开发文档 | `devdoc` | `doc search` | 搜索开放平台技术文档,不是钉钉内部内容 | +| "帮我建一个项目跟踪表" | 创建数据表格 | `aitable` | `doc` / `sheet` | 涉及结构化数据/行列操作,不是富文本文档或电子表格 | +| "帮我写个项目周报" | 创建钉钉文档 | `doc` | `aitable` | 富文本内容创作,不是数据表 | +| "参照这个生成同样的 / 按模板生成 / 复刻 X / 同样的模板 X 月份的" + 已有 alidocs URL | 模板保形生成同形态变体 | `drive copy + drive rename + doc block update` → 见 [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` | 钉钉日志系统(日报/周报),不是文档 | +| "整理一下XX项目的所有讨论" | 跨源主题归档 | #5 generate-topic-report | #4 write-doc | #4 侧重单篇文档创作;按主题跨听记/群消息汇总属于工作汇报 | +| "搜一下智能化方案/最近 OKR 相关邮件/最近发版相关消息" | 搜企业知识内容 | `aisearch enterprise` | `doc search` / `mail search` / `chat message search` | 跨文档、消息、日程、听记、邮件等企业内容语义检索走 enterprise;具体 `queries/types/time-range` 抽槽见 `aisearch.md` | +| "我发给某人的消息/邮件/文档/今天我干了什么" | 搜行为记录 | `aisearch behavior` | `chat` / `mail` / `doc` / `report` | 关注“谁对什么做过什么”,走 behavior;具体 `behavior-type/direction/chat-scope` 抽槽见 `aisearch.md` | +| "把这段文字翻译成英文/translate this" | 通用文本翻译 | `chat text translate` | `doc` / `aisearch` | 纯文本翻译,不是文档编辑或语义搜索 | +| "帮我把这个文档翻译成日文" | 文档内容翻译 | 先 `doc get` 再 `chat text translate` | `chat text translate` 直接传文件 | translate 仅支持纯文本,需先提取文档内容 | diff --git a/skills/multi/dingtalk-doc/references/lite-recipes.md b/skills/multi/dingtalk-doc/references/lite-recipes.md new file mode 100644 index 00000000..0b4fa0a2 --- /dev/null +++ b/skills/multi/dingtalk-doc/references/lite-recipes.md @@ -0,0 +1,45 @@ +# doc Lite Recipe + +本文件从单 Skill `lite-recipes.md` 拆分而来,仅保留与本产品相关的轻量流程。 + +## #4 文档知识 + +### query-doc + +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-markdown` 用 `dws markdown fetch` 读取原文;`axls` → 切 `dingtalk-misc` 的 `references/sheet.md`;`able` → 切 `dingtalk-aitable`;xlsx/xls/xlsm/csv 和其他普通文件 → 切 `dingtalk-drive`。非 `adoc` 禁止执行 `doc read`。 + +### list-folder-docs + +`drive list --workspace ` 或 `wiki node list --workspace ` + +### import-file + +将本地文件导入为钉钉在线文档。**一条命令完成上传+格式转换+创建**,无需先读取文件内容。 + +```bash +dws doc import --file ./report.docx --format json +``` + +1. 确认文件路径(用户提供的本地文件路径) +2. 执行:`dws doc import --file <文件路径> --format json`(可选 `--folder <文件夹ID>` / `--workspace <知识库ID>` / `--name "文档名"`) +3. 从返回中提取 `documentUrl`,告知用户导入完成并提供链接 +4. 超时或中断时 CLI 返回 `taskId`,用 `dws doc import get --task-id --format json` 手动查询 + +**`--folder` 参数传值规则**: +- 首选路径:用户提供 alidocs URL 时,直接将完整 URL 传入 `--folder`,无需先调 `drive info` +- 预检路径:若需确认 URL 指向的是文件夹,可先调 `dws drive info --node `: + - `nodeType == "folder"` → 使用 `nodeId` 或原始 URL 作为 `--folder` 值 + - `nodeType` 不是 folder → 提示用户:该链接指向的不是文件夹 +- 禁止:不得使用 `drive info` 返回的 `folderId` 字段作为 `--folder` 的值(`folderId` 是父文件夹 ID,非当前节点 ID) + +格式与文档类型映射: +- `.docx` / `.doc` → 文字文档(DOC) +- `.xlsx` / `.xls` → 电子表格(SHEET) +- `.xmind` / `.mark` → 脑图(MIND) +- `.md` / `.txt` → 文字文档(DOC) + +> **禁止先 Read 文件再 `doc create` + `doc update`**。`doc import` 是服务端格式转换,客户端无需解析文件内容。 +> 详见 [./doc/doc-import.md](./doc/doc-import.md)。 + diff --git a/skills/multi/dingtalk-doc/references/url-patterns.md b/skills/multi/dingtalk-doc/references/url-patterns.md index 8c5bc8f6..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-类型探测流程) 执行 @@ -19,6 +32,8 @@ | 产品 | 用途 | URL 格式 | ID 来源 | |------|------|----------|---------| | `aitable` | AI表格 Base 链接 | `https://alidocs.dingtalk.com/i/nodes/{baseId}` | `base list/search/create/get` 返回的 `baseId` | +| `aitable` | AI表格指定数据表链接 | `https://alidocs.dingtalk.com/i/nodes/{baseId}?iframeQuery=sheetId%3D{tableId}` | `baseId` + `table create/get` 或 `base get` 返回的 `tableId` | +| `aitable` | AI表格指定数据表+视图链接 | `https://alidocs.dingtalk.com/i/nodes/{baseId}?iframeQuery=sheetId%3D{tableId}%26viewId%3D{viewId}` | `baseId` + `tableId` + `view create/get` 返回的 `viewId` | | `aitable` | AI表格模板预览 | `https://docs.dingtalk.com/table/template/{templateId}` | `template search` 返回的 `templateId` | | `doc` | 文档链接 | `https://alidocs.dingtalk.com/i/nodes/{dentryUuid}` | `doc` 命令返回的 `dentryUuid` | | `sheet` | 电子表格链接 | `https://alidocs.dingtalk.com/i/nodes/{dentryUuid}` | `sheet create` 返回的 `dentryUuid` | @@ -67,7 +82,7 @@ dws doc read --node "https://alidocs.dingtalk.com/i/p/Y7kmbokZp3pgGLq2/docs/AY39 > > 你可以: > 1. 在钉钉客户端中打开该文档,将正文内容复制粘贴给我 -> 2. 如果文档已保存在你的文档空间中,可以告诉我文档名称,我通过 `dws doc search` 搜索后再读取 +> 2. 如果文档已保存在你的文档空间中,可以告诉我文档名称,我通过 `dws drive search` 搜索后再读取 --- @@ -78,45 +93,48 @@ 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` | 按 [doc.md](./products/doc.md) 操作 | -| `contentType=ALIDOC`, `extension=axls` | `sheet` | 按 [sheet.md](./products/sheet.md) 操作(仅 `axls` 在线电子表格) | -| `contentType=ALIDOC`, `extension=able` | `aitable` | 将 nodeId 作为 baseId,按 [aitable.md](./products/aitable.md) 操作 | -| `contentType=DOCUMENT`, `extension=xlsx` / `xls` / `xlsm` / `csv` | `doc` | 必须用 `dws drive download` 下载到本地处理,禁止走 `sheet`(非在线表格,sheet 命令无法操作) | -| `contentType≠ALIDOC`, `nodeType=file` | `doc` | 调用 `dws drive download` 下载,返回文件下载链接 | -| `nodeType=folder` | `doc` | 调用 `dws doc list --folder ` 列出指定文件夹直接子节点列表 | +| `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` 列出子节点 | | 以上均不匹配 | — | 告知用户当前暂不支持该类型 | > axls vs xlsx 关键区分: > - `axls`(钉钉在线电子表格,`contentType=ALIDOC`)→ 走 `sheet` 产品线(读/写/筛选/导出等服务端原子操作) > - `xlsx` / `xls` / `xlsm` / `csv`(上传到文档空间的本地表格文件,`contentType=DOCUMENT`)→ 必须走 `dws drive download` 下载到本地后再解析处理,严禁错误路由到 `sheet` 产品线(sheet 命令只支持在线表格,调用 xlsx 节点会直接报错) -> - 用户想把在线表格导出为 xlsx 文件 → 用 `dws sheet export --node <表格ID或URL>`(单命令一站式:提交导出任务→轮询→取 downloadUrl,加 `--output ./x.xlsx` 可直接落盘) +> - 用户想把在线表格导出为 xlsx 文件 → 用 `dws sheet export`(输入是 `axls`,输出是 xlsx,这是 axls → xlsx 的格式转换,不属于 xlsx 读取场景) ### 示例 ```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 doc list --folder "https://alidocs.dingtalk.com/i/nodes/ghi789" --format json +dws drive list --workspace --format json ``` ### 何时可跳过探测 @@ -124,40 +142,3 @@ dws doc list --folder "https://alidocs.dingtalk.com/i/nodes/ghi789" --format jso 当用户指令中已明确指定产品(如"帮我读这个文档"、"看下这个表格的数据"),可结合用户意图**跳过探测**直接路由。仅在以下情况**必须执行探测**: - 用户只粘贴 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 --user --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-doc/scripts/doc_create_and_write.py b/skills/multi/dingtalk-doc/scripts/doc_create_and_write.py index 367e4b93..fbf52f8d 100644 --- a/skills/multi/dingtalk-doc/scripts/doc_create_and_write.py +++ b/skills/multi/dingtalk-doc/scripts/doc_create_and_write.py @@ -96,7 +96,7 @@ def main(): if not content: print('错误:需要 --content 或 --content-file') sys.exit(1) - chunk_size = 30000 + chunk_size = 10000 create_args = ['doc', 'create', '--name', args.name, '--format', 'json'] if args.folder: diff --git a/skills/multi/dingtalk-drive/SKILL.md b/skills/multi/dingtalk-drive/SKILL.md index 95ff2ef6..79632e7b 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 用户说 钉盘/上传、覆盖或下载文件/文件夹/查文件/创建文件夹。Distinct from dingtalk-doc(在线文档内容编辑)、dingtalk-markdown(原生.md内容编辑)、dingtalk-wiki(知识库空间)。命令前缀:dws drive。 -cli_version: ">=0.2.14" +description: 钉钉文件管理(存储层)。Use when 用户说 钉盘/上传文件/下载文件/文件夹/查文件/找文件/全局搜索文件/复制/移动/重命名/删除/回收站/还原删除文件/权限管理/普通文件下载。任何文件类型都适用;文档内容编辑走 dingtalk-doc,知识库空间和空间内节点管理走 dingtalk-wiki。命令前缀:dws drive。 metadata: + cli_version: ">=0.2.14" category: product - stability: experimental requires: bins: - dws @@ -12,14 +11,9 @@ metadata: # 钉盘 Skill -> 🧪 **EXPERIMENTAL · 试验版 / Preview** — multi 模式当前未达 stable 标准。全部 dingtalk-* skill 已通过 dispatch verifier,但接口、命名、跨 skill 引用后续可能调整;生产 / 共享环境请优先使用 mono 模式(`dws skill setup --mode mono`)。问题请提 issue 反馈。 - -> **PREREQUISITE:** Read the `dws-shared` skill first for auth, global flags, product routing, URL preflight, error codes, and safety rules. The `dws` binary must be on PATH. - - - -> ⚠️ **命令可用性以当前 dws 二进制为准**。服务发现已下线,本文档随内置 skill 发布;如果 `dws --help` 不存在,说明当前版本未暴露该命令。若命令存在但调用失败,请按错误中的 endpoint 或 tool 提示确认静态端点目录和后端工具注册。实际调用前可用 `dws --help` 或 `--dry-run` 验证。 +## 前置条件 — 执行操作前必读 +> **CRITICAL — 执行任何 `dws` 操作前,MUST 先用 Read 工具完整读取 [`dws-shared`](../dws-shared/SKILL.md)。**该轻量文件包含全局执行契约、安全底线及 shared references 的按需加载导航;不要预加载其全部 references。 > 命令参考:[drive.md](references/drive.md)。 @@ -43,38 +37,80 @@ metadata: | 用户说 | 命令 | |--------|------| -| "看钉盘文件 / 文件夹列表" | `dws drive list --limit 20 [--folder ]` | -| "按名字搜文件(不知道在哪)" | `dws drive search --query "<关键词>"` | -| "看钉盘空间 / 团队文件 / 有哪些 space" | `dws wiki space list --type orgSpace`(`drive list-spaces` 已 deprecated) | -| "最近访问 / 最近编辑的文档" | `dws drive recent` | +| "看钉盘文件 / 文件夹列表" | `dws drive list [--folder ]` | | "钉盘目录树" | `python scripts/drive_tree_list.py --depth 2` | -| "查文件元数据" | `dws drive info --node ` | -| "查阅读/编辑/评论/下载等节点统计" | `dws drive stats --node ` | -| "创建文件快捷方式" | `dws drive shortcut --node [--folder ] [--workspace ]` | -| "下载文件" | `dws drive download --node --output ` | -| "上传本地文件" | `dws drive upload --file ./report.pdf [--folder ]` | -| "覆盖钉盘或知识库里的已有文件" | `dws drive upload --file ./updated.pdf --node --dry-run`,确认后加 `--yes` | -| "建文件夹" | `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 info --node ` | +| "搜文件 / 找文件" | `dws drive search --query "<关键词>"` | +| "下载文件" | `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(必遵流程) -- 找文件优先用 `drive search --query "<关键词>"`(不知道位置时全局搜);只有需要逐层浏览时才用 `drive list`。命中后必须 `drive info --node --format json` 回读元数据。 -- **ID 字段选择**:`drive list` 返回同时有 `dentryId`(纯数字)和 `fileId`(UUID 格式)。所有 `--node` 和 `--folder` 参数**必须用 `fileId`**,纯数字 `dentryId` 会被拒绝。 -- `drive list` 默认 `--limit 20`,最大 50;要更多用 `--cursor` 翻页,不要因参数边界报错反复重试。 -- `rename` 实际执行前读取节点类型和当前扩展名;仅对非文件夹且末尾后缀与当前扩展名完全匹配的名称去掉一层,避免双扩展名。带点文件夹名保持原样;`--dry-run` 不读取元数据,预览保留输入名称。 -- `drive download` 需要 `--output` 指定本地保存路径或目录;不要省略必填输出位置。 -- 删除、覆盖、移动、公开(publish set/unset)等破坏性操作必须先确认;上传、创建文件夹、下载后要读回或列目录验证。 -- `drive upload --node ` 覆盖已有文件,`--node` 与新建文件使用的 `--folder` 互斥;先 `--dry-run`,获得明确确认后再加 `--yes`。 -- 覆盖钉盘文件时 `--node` 是 fileId;带 `--workspace` 覆盖知识库文件时传目标 nodeId。 -- `shortcut` 会创建新节点,执行后必须用 `drive list` 回读目标位置;`stats` 是只读操作。 +> 命中以下意图**必须**按对应 SOP 顺序执行;**禁止**跳步、替换命令、编造 dentryUuid/nodeId。每条命令必须带 `--format json`。破坏性操作(删除/移动/覆盖/公开)**必须**先与用户确认。 + +### SOP-1 找文件(find-file) + +**触发**:找文件/搜文件/我的文件/最近文件/某文档在哪。 + +1. **选源(必须)**:最近访问 → `dws drive recent --limit --format json`(翻页用上次返回的 `nextCursor` 传 `--cursor`);按内容/名称全局搜 → `dws drive search --query "<关键词>" --format json`;浏览某目录 → `dws drive list --folder --format json`。 +2. **解析(必须)**:取真实 `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`;返回取 `dentryUuid`,用 `drive info --node` 回读确认。 +2. **覆盖(必须)**:先 `dws drive info --node --format json`,记录真实 `extension` 和原 `name`。`extension=md` 切 `dingtalk-markdown`,先 `markdown overwrite --dry-run` 再确认执行;其他普通文件在用户确认后执行 `dws drive upload --node --file <本地路径> --file-name "<原name>" --format json`,随后再次 `drive info` 回读。`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`(会失败)、普通文件覆盖时省略 `--file-name` 导致隐式重命名、上传或覆盖后不回读。 + +### SOP-3 文件夹 / 复制 / 移动 / 重命名(folder-ops) + +**触发**:建文件夹/复制/移动/重命名。 + +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 <目标>` 回读。 + +**禁止**:未确认就移动/覆盖他人文件、跳过回读。 + +### SOP-4 回收站(recycle) + +**触发**:删文件/回收站/还原。 + +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。 + +### SOP-5 互联网公开(publish) + +**触发**:互联网公开/取消公开/查公开状态。 + +1. **执行(必须)**:查状态 `dws drive publish get --node --format json`;开启公开 `dws drive publish set --node --yes`(**[危险]** 必须用户确认);关闭公开 `dws drive publish unset --node --yes`。 +2. **边界(必须)**:对外公开前**必须**与用户确认边界与后果。 + +**禁止**:未确认就 `publish set`、跳过 `--yes`。 + +## 高频硬约束 + +- 查找文件不要只看根目录后放弃;根目录没命中时,进入最相关的目标文件夹继续 `drive list --folder `,必要时用目录树脚本递归到合理深度。 +- `drive list` 默认 `--limit 20`,自动化场景里保守使用 `--limit 50` 以内并处理 `nextToken` 翻页;不要因为参数边界报错反复重试。 +- 全局找文件优先 `drive search --query`;指定目录浏览用 `drive list`,命中后必须 `drive info --node --format json` 回读元数据。 +- 删除、覆盖、移动等破坏性操作必须确认;上传、创建文件夹、下载后要读回或列目录验证。 - 所有 `dws drive` 命令加 `--format json`。 ## 跨产品协作 - 文件内容编辑(钉钉文档)→ 切到 `dingtalk-doc` -- 原生 `.md` 文件读取、创建、全量覆盖、局部替换 → 切到 `dingtalk-markdown` - 知识库空间 → 切到 `dingtalk-wiki` +## 局部意图与短流程 + +- [局部意图消歧](references/intent-guide.md);[短流程](references/lite-recipes.md)。 diff --git a/skills/multi/dingtalk-drive/references/drive.md b/skills/multi/dingtalk-drive/references/drive.md index 79e65ee6..b0c3fc20 100644 --- a/skills/multi/dingtalk-drive/references/drive.md +++ b/skills/multi/dingtalk-drive/references/drive.md @@ -1,7 +1,5 @@ # 钉盘 (drive) 命令参考 -钉盘 = DingTalk Drive,用于云端文件存储 / 上传 / 下载 / 目录管理。不是在线文档编辑;要编辑文档请用 [doc](./doc.md)。 - ## 查询命令帮助 当你不确定某个命令的具体参数、格式或可选项时,**优先执行 `--help` 查询**,不要猜测参数名或凭记忆编造。 @@ -15,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 ``` 规则: @@ -37,33 +33,26 @@ Example: Flags: --limit int 每页返回数量,默认 20,最大 50 (可选) --cursor string 分页游标,首次不传 (可选) - --order string 排序方向: asc|desc,默认 desc (可选,仅钉盘) - --order-by string 排序字段: createTime|modifyTime|name (可选,仅钉盘) + --order string 排序方向: asc|desc,默认 desc (可选) + --order-by string 排序字段: createTime|modifyTime|name (可选) --folder string 父节点 ID (dentryUuid),不传则列出空间根目录 (可选) - --space-id string 钉盘空间 ID (纯数字),不传则使用「我的文件」对应 spaceId (可选) - --workspace string 文档空间/知识库 ID (加密 string 或 URL),传入则路由到文档空间 (可选) - --thumbnail 是否返回缩略图信息 (可选,仅钉盘) + --space-id string 空间 ID,不传则使用「我的文件」对应 spaceId (可选) + --thumbnail 是否返回缩略图信息 (可选) ``` -> 统一入口:默认列钉盘空间(`--space-id` 纯数字);传 `--workspace` 时路由到文档空间/知识库列表。 - ### 获取钉盘空间列表 -> **Deprecated**:推荐改用 `dws wiki space list --type orgSpace` / `--type mySpace`(见 [wiki.md](./wiki.md))。本命令仍可用,仅作兼容保留。 -> 适用场景:复制/移动文件到「我的文件」或团队空间根目录时,先取 `rootFolderId`;或者枚举用户可访问的团队空间。 - ``` Usage: dws drive list-spaces [flags] Example: - dws wiki space list --type orgSpace # 推荐 - dws wiki space list --type mySpace # 推荐 - dws drive list-spaces # deprecated + dws drive list-spaces + dws drive list-spaces --space-type mySpace dws drive list-spaces --space-type orgSpace --limit 20 --cursor Flags: --space-type string 空间类型: orgSpace=企业空间(默认), mySpace=我的文件 (可选) --limit int 每页返回数量 (默认 20,最大 50),仅 spaceType 为 orgSpace 时有效 - --cursor string 分页游标,仅企业空间支持分页 (可选) + --cursor string 分页游标,仅企业空间支持分页 (可选) ``` spaceType 筛选规则: @@ -73,13 +62,13 @@ spaceType 筛选规则: 返回字段说明: - `spaceId` — 空间 ID,用于 `list`/`info`/`upload` 等命令的 `--space-id` - `spaceName` — 空间名称(如"全员文件夹"、"我的文件") -- `rootFolderId` — 空间根目录的 dentryUuid,可作为 `drive copy/move` 的 `--folder` 参数 +- `rootFolderId` — 空间根目录的 dentryUuid,可作为 `doc copy/move` 的 `--folder` 参数 - `spaceType` — 空间类型(如 `orgSpace`) - `nextToken` — 若不为空,表示还有更多空间可查询(仅企业空间) -### 搜索钉盘文件/文件夹/空间 +### 搜索文件/文件夹/空间 -按关键词全局搜索文件,默认同时搜索钉盘和文档空间,合并返回结果。不同于 `list`(需要明确的 spaceId/folder 逐层遍历),`search` 用于不知道具体位置、只记得名称/关键词的场景。 +按关键词搜索文件、文件夹或团队空间。不同于 `list`(需要明确的 spaceId/parentId 逐层遍历),`search` 用于不知道具体位置、只记得名称/关键词的场景。 ``` Usage: @@ -93,7 +82,7 @@ Example: dws drive search --query "报告" --limit 30 --cursor Flags: --query string 搜索关键词 (必填) - --target string 搜索范围: all(默认,聚合钉盘+文档空间) | file(仅钉盘文件) | space(仅钉盘空间) (可选) + --target string 搜索目标: all(默认) | file | space (可选) --file-types strings 按文件内容类型过滤,逗号分隔: alidoc,document,image,video,audio,archive (仅 target=file/all 生效) --extensions strings 按文件扩展名过滤,不含点号,逗号分隔 (如 pdf,docx,adoc) --creator-uids strings 按创建者用户 ID 过滤,逗号分隔 @@ -105,12 +94,12 @@ Flags: --cursor string 分页游标,从上次返回的 nextCursor 获取 (可选) ``` -搜索范围 (`--target`) 选择规则: -- `all`(默认):同时搜钉盘文件与文档空间,聚合返回 — 不确定目标位置时使用 -- `file`:只搜钉盘文件 / 文件夹,支持 `--file-types` / `--extensions` 过滤 — 明确是找钉盘文件时使用 -- `space`:只搜钉盘团队空间 — 明确知道空间名、需快速定位空间 spaceId/rootFolderId 时使用 +搜索目标 (`--target`) 选择规则: +- `all`(默认):同时搜文件与空间,返回混合结果 — 不确定目标是文件还是空间时使用 +- `file`:只搜文件 / 文件夹,支持 `--file-types` / `--extensions` 过滤 — 明确是找文件时使用 +- `space`:只搜团队空间 — 明确知道空间名、需快速定位空间 spaceId/rootFolderId 时使用 -结果中 `source` 字段区分来源:`drive` / `doc`。如果需要在某个知识库内搜索,请使用 `dws wiki node search --workspace `。 +返回结果中 `type` 字段区分:`SPACE`(空间)、`FILE`(普通文件)、`FOLDER`(文件夹)、`ALIDOC`(钉钉在线文档)。 > **提示**:结果按相关性排序,首页未命中时优先调整关键词 / 补充 `--file-types`/`--extensions` 缩小范围 / 加上时间范围,而非反复翻页。 @@ -154,45 +143,24 @@ Usage: Example: dws drive info --node Flags: - --node string 节点 ID (dentryUuid) (必填) + --node string 节点 ID (dentryUuid) (必填) --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` → `dws sheet range read` | -| able | 多维表格 | 在线查询记录 | `dws aitable base get` → `dws aitable record query` | -| 其他(pdf/docx/txt/png 等) | 普通文件 | **不支持在线分析**,需用户主动下载后本地查看 | `dws drive download --node --output ` | +| adoc | 在线文档 | 在线获取 Markdown 内容 | `dws doc read --node ` | +| axls | 在线表格 | 在线读取表格数据 | `dws sheet list --node ` → `dws sheet range read --node --sheet-id --range ` | +| able | 多维表格 | 在线查询记录 | `dws aitable table list --base-id ` → `dws aitable record query --base-id --table-id ` | +| md | Markdown 文件 | 读内容 / 创建 / 覆盖 / 局部替换 | `dws markdown fetch` / `dws markdown create` / `dws markdown overwrite` / `dws markdown patch` | +| 其他(pdf/docx/txt/png 等) | 普通文件 | **不支持在线分析**,需用户主动下载后本地查看 | `dws drive download` | -### 下载钉盘文件到本地 +### 下载文件到本地 下载流程一步到位:获取下载 URL → HTTP GET 下载文件二进制内容到本地。 @@ -203,12 +171,12 @@ Example: dws drive download --node --output ./report.pdf dws drive download --node --output ~/downloads/ Flags: - --node string 文件 ID (dentryUuid) (必填) - --output string 本地保存路径 (必填),可以是文件路径或目录;如果指定目录,文件名从下载 URL 中自动推断 + --node string 文件 ID (dentryUuid) (必填) + --output string 本地保存路径 (文件路径或目录,不传则保存到当前目录);如果指定目录,文件名从下载 URL 中自动推断 (可选) --space-id string 文件所属空间 ID (可选) ``` -> **注意**:`--output` 是必填参数,不传会报错。 +> **提示**:`--output` 为可选参数,不传则保存到当前目录,文件名从下载 URL 中自动推断。 ### 创建文件夹 @@ -219,16 +187,14 @@ Example: dws drive mkdir --name "项目资料" dws drive mkdir --name "子目录" --folder Flags: - --name string 文件夹名称,最长 50 字符 (必填) - --folder string 父节点 ID (dentryUuid),不传则在空间根目录下创建 (可选) - --space-id string 目标空间 ID,不传则使用「我的文件」 (可选) + --name string 文件夹名称,最长 50 字符 (必填) + --folder string 父节点 ID (dentryUuid),不传则在空间根目录下创建 (可选) + --space-id string 目标空间 ID,不传则使用「我的文件」 (可选) ``` -> `mkdir` 在钉盘空间创建文件夹;要在文档空间/知识库中创建文件夹,用 `dws wiki node create --type folder --workspace `(见 [wiki.md](./wiki.md))。 - ### 上传本地文件到钉盘 -> **注意:** 上传文件首选 `dws drive upload` 一条命令(内部自动完成三步流程),不要手动走 `upload-info` + `curl` + `commit` 三步。 +> **注意:** 上传文件必须使用 `dws drive upload` 命令,禁止使用 `upload-info` + `curl` + `commit` 三步流程。 ``` Usage: @@ -237,55 +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.md --node - dws drive upload --file ./updated.md --node --workspace --yes + dws drive upload --file ./updated.pdf --node --file-name "<原文件名.pdf>" Flags: --file string 本地文件路径 (必填) --file-name string 文件显示名称 (默认使用文件名) - --folder string 父节点 ID,不传则上传到空间根目录 (可选,与 --node 互斥) - --node string 覆盖目标文件 ID,传入即覆盖已有文件 (可选,与 --folder 互斥) - --space-id string 目标钉盘空间 ID,不传则使用「我的文件」 (可选) - --workspace string 目标知识库 ID,传入时路由到文档空间上传 (可选) - --convert 是否转换为钉钉在线文档 (仅文档空间上传时生效) - --mime-type string 文件 MIME 类型,不传则自动推断 (可选) -``` - -`upload` 命令内部自动完成三步流程(获取凭证 → OSS PUT → 提交入库),无需手动分步操作。上传到知识库/文档空间时加 `--workspace` 参数。 - -传 `--node` 时覆盖指定文件:钉盘路由使用 fileId,知识库路由使用 nodeId。覆盖不可逆,先用全局 `--dry-run` 预览;获得用户明确确认后再加 `--yes`。`--node` 与创建新文件所用的 `--folder` 互斥。 - -### 获取上传凭证 (手动三步·仅特殊场景) - -> 仅当需要自定义流式上传、无法使用 `upload` 一条命令时才走手动三步:`upload-info` → HTTP PUT → `commit`。 - -``` -Usage: - dws drive upload-info [flags] -Example: - dws drive upload-info --file-name "report.pdf" --file-size 102400 - dws drive upload-info --file-name "slides.pptx" --file-size 512000 --folder -Flags: - --file-name string 文件名含后缀 (必填) - --file-size int 文件大小,单位字节 (必填) - --mime-type string MIME 类型 (可选,服务端会自动推断) - --folder string 父节点 ID (dentryUuid),不传则上传到空间根目录 (可选) --space-id string 目标空间 ID,不传则使用「我的文件」 (可选) + --mime-type string 文件 MIME 类型,不传则自动推断 (可选) + --folder string 父节点 ID (dentryUuid),不传则上传到空间根目录 (可选,与 --node 互斥) + --node string 覆盖目标文件 ID,传入即覆盖已有文件(透明模式:钉盘路径映射 overwriteFileId,知识库路径映射 overwriteNodeId)(可选,与 --folder 互斥) ``` -### 提交上传 (手动三步·第三步) +`upload` 命令内部自动完成三步流程(获取凭证 → OSS PUT → 提交入库),无需手动分步操作。 -``` -Usage: - dws drive commit [flags] -Example: - dws drive commit --file-name "report.pdf" --file-size 102400 --upload-id -Flags: - --file-name string 文件名含后缀 (必填,须与 upload-info 一致) - --file-size int 文件大小,单位字节 (必填,须与 upload-info 一致) - --upload-id string upload-info 返回的 uploadId (必填) - --folder string 父节点 ID (dentryUuid),须与 upload-info 一致 (可选) - --space-id string 空间 ID,不传则使用「我的文件」 (可选) -``` +> **覆盖保名规则(实测)**:使用 `--node` 覆盖普通文件前,先执行 +> `dws drive info --node --format json` 记录原 `name`,再把该值原样传给 +> `--file-name`。省略 `--file-name` 会采用本地文件名,并同时重命名远端目标。 +> `extension=md` 不走本命令,切到 `dws markdown overwrite` 保留 Markdown diff +> 预览;`adoc` / `axls` / `able` 切对应内容产品。 ### 删除文件/文件夹到回收站 @@ -297,15 +231,10 @@ Usage: Example: dws drive delete --node --format json # 查询 fileId: dws drive list Flags: - --node string 文件/文件夹 ID (dentryUuid),即 drive list 返回的 fileId (必填) - -Global Flags: - --yes 跳过二次确认 (危险操作,建议先与用户确认) + --node string 文件/文件夹 ID (dentryUuid),即 drive list 返回的 fileId (必填) ``` -> 由当前二进制静态注册(路由到 doc 服务的 `delete_document` tool);如果当前版本未暴露,调用前可用 `dws drive delete --help` 验证。 注意:`--node` 使用的是 `drive list` 返回结果中的 `fileId` 字段(即 `dentryUuid`),**不是** `dentryId` 字段。 -删除是软删除(进回收站),但仍需用户明确确认;不要在自动化脚本里默认带 `--yes`。 ### 查看回收站文件列表 @@ -334,42 +263,110 @@ 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` 用户说"最近访问/最近打开/最近编辑/最近文档" → `recent`(默认仅最近访问,`--operate-type 1` 仅最近编辑,`--operate-type 0,1` 全部) -用户说"钉盘空间/团队文件/有哪些空间/空间列表/团队文件列表" → `wiki space list --type orgSpace`(`drive list-spaces` 已 deprecated) +用户说"钉盘空间/团队文件/有哪些空间/空间列表/团队文件列表" → `list-spaces` 用户说"搜索钉盘文件/钉盘里找个文件/查找某个钉盘文件/钉盘中搜索" → `search` 用户说"文件详情/文件信息" → `info` -用户说"文件阅读量/编辑量/评论数/下载数/节点统计" → `stats` -用户说"给文件创建快捷方式/放一个链接到目标文件夹" → `shortcut` -用户说"下载文件" → `download` 指定 `--output` 保存到本地 -用户说"新建文件夹/创建目录" → `mkdir`(钉盘空间)/ `wiki node create --type folder`(文档空间) -用户说"上传文件/传文件到钉盘" → `upload`(首选此命令,自动完成三步流程) -用户说"覆盖/替换钉盘或知识库中的已有文件" → `upload --node `(不可逆,先 `--dry-run`,确认后再加 `--yes`) +用户说"下载文件" → `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 search`(全局聚合搜索)。 +**drive search vs wiki node search**: 用户提到"钉盘/网盘/我的文件里搜" → `drive search`;提到"知识库/文档空间/workspace 里搜" → `wiki node search`;未明确目标时优先问明。 -**drive upload vs doc upload**: 文件上传统一走 `drive upload`。上传到知识库/文档空间时加 `--workspace` 参数。 +**drive upload**: 新文件上传统一走 `drive upload`。覆盖普通文件时先 `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 `。 -把图片/文件发到群里一般直接用 `chat message send --msg-type file --file-path <本地路径>`(见 [chat.md](./chat.md)),无需先经 drive 上传。 +**版本管理**: 当前 CLI 仅支持钉钉在线文档(adoc)的版本管理(`dws doc version list/save/revert`);普通文件的历史版本列出/下载/回滚暂不支持。 + +**.md 文件的内容操作路由**: 当 `drive info` 返回 `extension=md` 时,文件管理操作(移动/重命名/删除/下载文件)留在 `drive`,但**读取或改写原文内容必须切换到 `markdown` 产品**: +- 用户说"读取/看一下 markdown 内容/获取 .md 原文" → `dws markdown fetch --node `(非 `drive download`) +- 详见 `dingtalk-markdown` 的 [markdown.md](../../dingtalk-markdown/references/markdown.md) ## 核心工作流 @@ -389,14 +386,10 @@ dws drive download --node --output /tmp/ --format json # 5. 创建文件夹 dws drive mkdir --name "项目资料" --format json -# 6. 上传文件(首选 upload 命令,自动完成三步流程) +# 6. 上传文件(必须使用 upload 命令,禁止手动分步操作) dws drive upload --file ./报告.pdf --format json dws drive upload --file ./报告.pdf --folder --format json -# 6b. 覆盖已有文件(先预览;用户确认后再执行) -dws drive upload --file ./更新版.pdf --node --dry-run --format json -dws drive upload --file ./更新版.pdf --node --yes --format json - # 7. 删除文件/文件夹到回收站(危险操作:必须先向用户确认,用户同意后才加 --yes 执行) # 正确流程:1.向用户展示"即将删除「文件名」到回收站" → 2.等用户确认 → 3.执行下面命令 dws drive delete --node --yes --format json @@ -404,11 +397,40 @@ 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 服务。 +> 以下命令操作的是**文档空间**(知识库 / 我的文档),底层路由到 doc MCP server。 > 与钉盘命令(list / mkdir / upload 等)的区别:钉盘命令操作钉盘空间(spaceId 纯数字),文档空间命令操作知识库/我的文档(workspaceId 加密 string)。 ### 复制/移动/重命名文件 @@ -425,46 +447,229 @@ Flags: --name string 新名称 (仅 rename 必填) ``` -> **rename 会按真实节点元数据处理扩展名**:实际执行前先读取节点类型和当前扩展名。文件的新名称若以当前扩展名结尾,只去掉完全匹配的一层再交给会保留原后缀的服务端;文件夹(包括 `release.v2` 这类带点名称)保持原样,也不再依赖扩展名白名单。`--dry-run` 不读取远端元数据,因此预览保留输入名称。 - -权限要求:copy 需对源文档有"阅读"权限且对目标文件夹有"编辑"权限;move 需对源文档有"管理"权限且对目标文件夹有"编辑"权限;rename 需对文档有"编辑"权限。 - > **字段选择**:`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-version [flags] +Example: + dws drive download-version --node --version 3 --output ./report_v3.pdf --format json + dws drive download-version --node --version 3 --output --format json +Flags: + --node string 文件 ID (dentryUuid) 或 URL (必填) + --version int 历史版本号 (必填,正整数,从 list --versions 获取) + --output string 本地保存路径 (文件路径或目录,必填) +``` + +> **两步下载流程**:先调用 MCP 工具获取历史版本下载 URL 和签名头,再 HTTP GET 下载文件内容到本地。 +> `--output` 指定目录时,优先从文件信息中获取原始文件名,获取不到时从下载 URL 推断。 + +#### 回滚文件到指定历史版本 + +> **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` 仅用于一致性校验。 +> **权限要求**:对源节点有“可查看”权限,对目标文件夹有“编辑”权限。 +> **目标位置限制**:若目标位置不支持在其下创建子节点(如快捷方式节点、非文件夹),会返回错误,请改指定一个文件夹作为目标位置。 + ### 创建文件夹(文档空间) -drive 没有独立的文档空间建文件夹命令,在知识库/文档空间中创建文件夹走: - -```bash -dws wiki node create --type folder --name "文件夹名" --workspace --format json +``` +Usage: + dws drive folder create --name "文件夹名" +Flags: + --name string 名称 (必填) + --folder string 父文件夹 nodeId + --workspace string 目标知识库 ID ``` -详见 [wiki.md](./wiki.md)。 - ### 权限管理(文档节点级) -> 仅适用于文档空间节点,不适用于钉盘文件。 - ``` 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 列表,逗号分隔 (add/update/remove 必填) - --role string 角色: MANAGER / EDITOR / DOWNLOADER / READER (add/update 必填) - --workspace string 知识库 ID (选填) + --users string 用户 userId 列表,逗号分隔 + --role string 角色: MANAGER / EDITOR / DOWNLOADER / READER --limit int 返回成员数上限 (仅 list,默认 30,最大 200) - --filter-role string 按角色过滤: OWNER / MANAGER / EDITOR / DOWNLOADER / READER (仅 list) + --filter-role string 按角色过滤 (仅 list) + --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` 前,向用户回显「资源 / 申请角色 / 审批人 / 理由」并取得用户确认后再执行。 + ### 文件互联网公开发布 管理文件的互联网公开发布状态。公开后任何人通过链接即可访问,无需登录钉钉。操作者需要是文件的管理员或拥有者。 -> **`publish set` 和 `publish unset` 为 [危险] 操作,执行前需要向用户确认。确认后传入 `--yes` 跳过交互式确认。** +> **`publish set` 和 `publish unset` 为 [危险] 操作,执行前需要确认。必须传入 `--yes` 跳过交互式确认。** ``` Usage: @@ -484,7 +689,7 @@ Flags: 子命令说明: - `publish set` — [危险] 设置文件为互联网公开,可选指定公开权限 - `publish unset` — [危险] 关闭文件互联网公开 -- `publish get` — 查询文件当前的公开发布状态 +- `publish get`(别名 `status`)— 查询文件当前的公开发布状态 返回字段说明: - `published` — true=已公开,false=未公开 @@ -498,77 +703,72 @@ Flags: | 目标位置 | 参数传递方式 | 前置步骤 | |---------|-----------|---------| -| 未指定目标(默认) | `--folder ` | 先 `dws wiki space list --type mySpace` 获取「我的文件」的 `rootFolderId` | -| 知识库空间根目录 | `--workspace ` | 无需额外步骤,直接传入 workspaceId | -| 钉盘 space 根目录 | `--folder ` | 先 `dws wiki space list --type orgSpace` 获取目标 space 的 `rootFolderId` | -| 钉盘 space 下的子文件夹 | `--folder ` | 先 `dws drive list --space-id ` 逐层浏览,获取目标文件夹的 `fileId`(dentryUuid 格式) | +| 未指定目标(默认) | `--folder ` | 先 `dws drive list-spaces --space-type mySpace` 获取「我的文件」的 `rootFolderId` | +| 知识库空间根目录 | `--workspace ` | 无需额外步骤 | +| 钉盘 space 根目录 | `--folder ` | 先 `dws drive list-spaces` 获取目标 space 的 `rootFolderId` | +| 钉盘 space 下的子文件夹 | `--folder ` | 先 `dws drive list --space-id ` 逐层浏览 | ### 工作流示例 ```bash -# ── 场景默认: 用户未指定目标位置 → 复制/移动到「我的文件」根目录 ── -dws drive list --space-id --format json # 获取源文件 dentryUuid -dws wiki space list --type mySpace --format json # 获取「我的文件」rootFolderId +# ── 场景 默认: 复制/移动到「我的文件」根目录 ── +dws drive list --space-id --format json +dws drive list-spaces --space-type mySpace --format json dws drive copy --node <源文件dentryUuid> --folder <我的文件rootFolderId> --format json -# ── 场景 A: 复制钉盘文件到知识库空间根目录 ── +# ── 场景 A: 复制到知识库空间根目录 ── dws drive copy --node <源文件dentryUuid> --workspace --format json -# ── 场景 B: 移动钉盘文件到另一个钉盘 space 根目录 ── -dws wiki space list --type orgSpace --format json +# ── 场景 B: 移动到另一个钉盘 space 根目录 ── +dws drive list-spaces --format json dws drive move --node <源文件dentryUuid> --folder <目标space的rootFolderId> --format json -# 注意:移动到其他 space 时只传 --folder,不传 --workspace -# ── 场景 C: 复制钉盘文件到钉盘 space 下的子文件夹 ── +# ── 场景 C: 复制到钉盘子文件夹 ── dws drive list --space-id --format json dws drive copy --node <源文件dentryUuid> --folder <目标文件夹fileId> --format json ``` ## 上下文传递表 -| 操作 | 从返回中提取 | 用于 | -|------|-------------|------| -| `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` / `nodeId` | download / info / 后续 `upload --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`。 -- `upload --node` 会覆盖已有文件且不可逆;`--node` 与 `--folder` 互斥。先 dry-run,得到用户明确确认后再加 `--yes`。 - ## 注意事项 - 不传 `--space-id` 时默认使用「我的文件」空间 - 不传 `--folder` 时默认操作空间根目录 -- `--folder` 只能使用父文件夹的 `dentryUuid`。不要把 `drive info` 返回的数字型 `dentryId` 当作父目录 -- **`--limit` 有效上限为 50**:CLI 不做本地校验,传 `--limit 100` 不会报错,但服务端每页最多返回 50 条,超出部分无效。用户要求超过 50 条时,应使用 `--limit 50` 配合 `--cursor` 分页查询 +- `--folder` 只能使用父文件夹的 `dentryUuid`。不要把 `drive info` 返回的数字型 `dentryId` 当作父目录;`dentryId` 只用于 `chat message send --dentry-id` +- **`--limit` 最大值为 50**,禁止传入超过 50 的值(如 `--limit 100`)。用户要求超过 50 条时,应使用 `--limit 50` 配合 `--cursor` 分页查询,不要直接传大于 50 的值 - `--order-by` 支持: `createTime`、`modifyTime`、`name` -- **上传文件首选 `dws drive upload` 命令**;手动三步(`upload-info` → HTTP PUT → `commit`)仅用于自定义流式上传等特殊场景。手动三步时 HTTP PUT 必须把 upload-info 返回的 `headers` 全部回传,`Content-Type` 通常要留空;PUT 返回 200 后才能调 `commit`;`uploadId` 有过期时间,过期需重新 `upload-info`;`--folder` 在 upload-info / commit 中要保持一致 +- **上传文件必须使用 `dws drive upload` 命令**,禁止使用 `upload-info` + `curl` + `commit` 三步手动流程 - `--file-name` 必须包含扩展名(如 `report.pdf`) -- `download` 需要指定 `--output`,CLI 会把文件保存到本地路径或目录 -- 文件名规则:头尾不能有空格;不能含 `*`、`"`、`<`、`>`、`|`、制表符;不能以 `.` 结尾 -- `shortcut` 会创建新节点,执行后必须通过 `drive list` 回读确认目标位置;`stats` 为只读命令 ## 自动化脚本 -| 脚本 | 场景 | 用法 | -|------|------|------| -| [drive_tree_list.py](../../scripts/drive_tree_list.py) | 递归列出钉盘目录树结构 | `python drive_tree_list.py --depth 2` | +| 脚本 | 场景 | 用法 | +| ------------------------------------------------------ | ----------- | ------------------------------------- | +| [drive_tree_list.py](../scripts/drive_tree_list.py) | 递归列出钉盘目录树结构 | `python drive_tree_list.py --depth 2` | ## 相关产品 -- [doc](./doc.md) — 文档内容读写(Markdown/块级编辑/导出),不是文件存储 -- [markdown](../../dingtalk-markdown/references/markdown.md) — 钉盘或文档空间中原生 `.md` 文件的读取、创建、覆盖与局部替换 -- [wiki](./wiki.md) — 知识库/空间管理层(空间列表、节点创建、空间内搜索、成员管理) -- [chat](./chat.md) — 发送图片/文件消息用 `chat message send --msg-type file --file-path` +- [doc](../../dingtalk-doc/references/doc.md) — 文档内容读写/知识库空间,不是文件存储 +- [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 new file mode 100644 index 00000000..14ff825c --- /dev/null +++ b/skills/multi/dingtalk-drive/references/intent-guide.md @@ -0,0 +1,17 @@ +# drive 局部意图消歧 + +本文件从单 Skill `intent-guide.md` 拆分而来,仅保留与本产品相关的跨产品消歧规则。 + +| 用户说... | 真实意图 | 应该用 | 不要用 | 理由 | +|---|---|---|---|---| +| "参照这个生成同样的 / 按模板生成 / 复刻 X / 同样的模板 X 月份的" + 已有 alidocs URL | 模板保形生成同形态变体 | `drive copy + drive rename + doc block update` → 见 [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 | +| "上传文件到钉盘/我的文件" | 钉盘上传 | `drive upload` | — | 提到"钉盘/网盘/我的文件"→ drive | +| "上传文件"(未指定目标) | 默认钉盘 | `drive upload` | — | 未明确目标时默认上传到钉盘 | +| "帮我看看知识库里的文件" | 知识库节点列表 | `wiki node list --workspace` | `drive list` | 明确"知识库"上下文 → wiki node list | +| "列出钉盘团队空间" | 列出钉盘空间 | `wiki space list --type orgSpace` | `drive list-spaces` | 空间管理归 wiki,drive list-spaces 已 deprecated | +| "在知识库里搜方案" | 空间内搜索 | `wiki node search --workspace` | `drive search` | 指定了空间上下文 → wiki node search | +| "搜一下有没有叫XX的文件" | 全局搜索 | `drive search` | `wiki node search` | 未指定空间 → drive search 全局聚合搜索 | +| "整理一下XX项目的所有讨论" | 跨源主题归档 | #5 generate-topic-report | #4 write-doc | #4 侧重单篇文档创作;按主题跨听记/群消息汇总属于工作汇报 | diff --git a/skills/multi/dingtalk-drive/references/lite-recipes.md b/skills/multi/dingtalk-drive/references/lite-recipes.md new file mode 100644 index 00000000..206c781d --- /dev/null +++ b/skills/multi/dingtalk-drive/references/lite-recipes.md @@ -0,0 +1,45 @@ +# drive Lite Recipe + +本文件从单 Skill `lite-recipes.md` 拆分而来,仅保留与本产品相关的轻量流程。 + +## #4 文档知识 + +### query-doc + +1. 全局搜索:`drive search --query "<关键词>"` → `nodeId`(聚合钉盘+文档空间) +2. 空间内搜索:`wiki node search --workspace --query "<关键词>"` → `nodeId` +3. `doc read --node `(按需;大文档只抽章节) + +### list-folder-docs + +`drive list --workspace ` 或 `wiki node list --workspace ` + +### import-file + +将本地文件导入为钉钉在线文档。**一条命令完成上传+格式转换+创建**,无需先读取文件内容。 + +```bash +dws doc import --file ./report.docx --format json +``` + +1. 确认文件路径(用户提供的本地文件路径) +2. 执行:`dws doc import --file <文件路径> --format json`(可选 `--folder <文件夹ID>` / `--workspace <知识库ID>` / `--name "文档名"`) +3. 从返回中提取 `documentUrl`,告知用户导入完成并提供链接 +4. 超时或中断时 CLI 返回 `taskId`,用 `dws doc import get --task-id --format json` 手动查询 + +**`--folder` 参数传值规则**: +- 首选路径:用户提供 alidocs URL 时,直接将完整 URL 传入 `--folder`,无需先调 `drive info` +- 预检路径:若需确认 URL 指向的是文件夹,可先调 `dws drive info --node `: + - `nodeType == "folder"` → 使用 `nodeId` 或原始 URL 作为 `--folder` 值 + - `nodeType` 不是 folder → 提示用户:该链接指向的不是文件夹 +- 禁止:不得使用 `drive info` 返回的 `folderId` 字段作为 `--folder` 的值(`folderId` 是父文件夹 ID,非当前节点 ID) + +格式与文档类型映射: +- `.docx` / `.doc` → 文字文档(DOC) +- `.xlsx` / `.xls` → 电子表格(SHEET) +- `.xmind` / `.mark` → 脑图(MIND) +- `.md` / `.txt` → 文字文档(DOC) + +> **禁止先 Read 文件再 `doc create` + `doc update`**。`doc import` 是服务端格式转换,客户端无需解析文件内容。 +> 详见 [./doc/doc-import.md](../../dingtalk-doc/references/doc/doc-import.md)。 + diff --git a/skills/multi/dingtalk-drive/scripts/drive_tree_list.py b/skills/multi/dingtalk-drive/scripts/drive_tree_list.py index 95ccb1c2..a7e3439b 100644 --- a/skills/multi/dingtalk-drive/scripts/drive_tree_list.py +++ b/skills/multi/dingtalk-drive/scripts/drive_tree_list.py @@ -3,16 +3,10 @@ 递归列出钉盘目录树结构(可指定深度) 用法: - python drive_tree_list.py # 列出根目录 - python drive_tree_list.py --depth 2 # 递归 2 层 - python drive_tree_list.py --folder # 指定目录 (传 drive list 返回的 fileId) + python drive_tree_list.py # 列出根目录 + python drive_tree_list.py --depth 2 # 递归 2 层 + python drive_tree_list.py --parent-id # 指定目录 python drive_tree_list.py --dry-run - -说明: - `dws drive list` 返回的每个 item 有两个 ID: - - dentryId:纯数字串,`--folder` 不接受,别用它递归; - - fileId:字母数字串,即 CLI 所称 dentryUuid,`--folder` 只认它。 - 递归子目录必须用 fileId 作为 `--folder` 的值。 """ import sys @@ -44,13 +38,13 @@ def run_dws( def list_dir( - folder: str = '', dry_run: bool = False, + parent_id: str = '', dry_run: bool = False, ) -> list: cmd_args = [ - 'drive', 'list', '--limit', '50', '--format', 'json', + 'drive', 'list', '--max', '50', '--format', 'json', ] - if folder: - cmd_args.extend(['--folder', folder]) + if parent_id: + cmd_args.extend(['--parent-id', parent_id]) data = run_dws(cmd_args, dry_run=dry_run) if not data: return [] @@ -75,7 +69,7 @@ def print_tree( name = item.get('name') or item.get('fileName', '?') item_type = item.get('type') or item.get('dentryType', '') is_dir = str(item_type).lower() in ( - 'folder', 'directory', '1', + 'folder', 'directory', '1', 'FOLDER' ) icon = '📁' if is_dir else '📄' size_str = '' @@ -93,10 +87,10 @@ def print_tree( if is_dir and depth < max_depth: child_prefix = prefix + (' ' if is_last else '│ ') - # `--folder` 只认 fileId (dentryUuid),不认纯数字 dentryId - folder_id = item.get('fileId', '') - if folder_id: - children = list_dir(folder_id, dry_run=dry_run) + dentry_id = (item.get('dentryUuid') + or item.get('id', '')) + if dentry_id: + children = list_dir(dentry_id, dry_run=dry_run) print_tree( children, depth + 1, max_depth, child_prefix, dry_run, @@ -108,8 +102,7 @@ def main(): description='递归列出钉盘目录树' ) parser.add_argument( - '--folder', default='', - help='起始目录 ID (传 drive list 返回的 fileId)', + '--parent-id', default='', help='起始目录 ID' ) parser.add_argument( '--depth', type=int, default=1, @@ -119,10 +112,10 @@ def main(): args = parser.parse_args() args.depth = min(args.depth, 5) - root_name = args.folder or '我的文件' + root_name = args.parent_id or '我的文件' print(f"📁 {root_name}") - items = list_dir(args.folder, dry_run=args.dry_run) + items = list_dir(args.parent_id, dry_run=args.dry_run) if args.dry_run: return if not items: diff --git a/skills/multi/dingtalk-live/SKILL.md b/skills/multi/dingtalk-live/SKILL.md deleted file mode 100644 index d86e0b6b..00000000 --- a/skills/multi/dingtalk-live/SKILL.md +++ /dev/null @@ -1,30 +0,0 @@ ---- -name: dingtalk-live -description: 钉钉直播。Use when 用户说 直播/我的直播/直播列表。命令前缀: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 反馈。 - -> **PREREQUISITE:** Read the `dws-shared` skill first for auth, global flags, product routing, URL preflight, error codes, and safety rules. The `dws` binary must be on PATH. - - - -> ⚠️ **命令可用性以当前 dws 二进制为准**。服务发现已下线,本文档随内置 skill 发布;如果 `dws --help` 不存在,说明当前版本未暴露该命令。若命令存在但调用失败,请按错误中的 endpoint 或 tool 提示确认静态端点目录和后端工具注册。实际调用前可用 `dws --help` 或 `--dry-run` 验证。 - - -> 命令参考:[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 4e83d9b4..46b3bfeb 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 用户说 发邮件/查邮件/回邮件/转发邮件/未读邮件/邮件搜索。Distinct from 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,14 +11,9 @@ metadata: # 钉钉邮箱 Skill -> 🧪 **EXPERIMENTAL · 试验版 / Preview** — multi 模式当前未达 stable 标准。全部 dingtalk-* skill 已通过 dispatch verifier,但接口、命名、跨 skill 引用后续可能调整;生产 / 共享环境请优先使用 mono 模式(`dws skill setup --mode mono`)。问题请提 issue 反馈。 - -> **PREREQUISITE:** Read the `dws-shared` skill first for auth, global flags, product routing, URL preflight, error codes, and safety rules. The `dws` binary must be on PATH. - - - -> ⚠️ **命令可用性以当前 dws 二进制为准**。服务发现已下线,本文档随内置 skill 发布;如果 `dws --help` 不存在,说明当前版本未暴露该命令。若命令存在但调用失败,请按错误中的 endpoint 或 tool 提示确认静态端点目录和后端工具注册。实际调用前可用 `dws --help` 或 `--dry-run` 验证。 +## 前置条件 — 执行操作前必读 +> **CRITICAL — 执行任何 `dws` 操作前,MUST 先用 Read 工具完整读取 [`dws-shared`](../dws-shared/SKILL.md)。**该轻量文件包含全局执行契约、安全底线及 shared references 的按需加载导航;不要预加载其全部 references。 > 命令参考:[mail.md](references/mail.md)。复杂搜索、附件、批量处理、草稿等多步邮件场景参考:[09-mail.md](references/09-mail.md)。 @@ -46,18 +40,65 @@ metadata: | 用户说 | 命令 | |--------|------| -| "发邮件给 a@b.com" | `dws mail message send --from <自己邮箱> --to a@b.com --subject "<标题>" --content "<正文>"`(正文规范 flag 是 `--content`,`--body` 为隐藏别名) | +| "发邮件给 a@b.com" | `dws mail mailbox list --format json` → `dws mail message send --from <邮箱> --to a@b.com --subject "<标题>" --content "<正文>" --format json` | +| "回复 / 全部回复 / 转发" | `dws mail message reply` / `reply-all` / `forward` | | "今天未读邮件" | `python scripts/mail_unread_summary.py` | | "带抄送发送" | `python scripts/mail_send_with_cc.py --to a@b.com --cc c@d.com --subject "<标题>" --body "<正文>"` | -## 评测高频硬约束 +## 标准 SOP(必遵流程) + +> 命中以下意图**必须**按对应 SOP 顺序执行;**禁止**跳步、替换命令、编造 email/messageId。每条命令必须带 `--format json`。收件邮箱**必须**真实解析,**禁止**猜测。 + +### SOP-1 拿邮箱(get-mailbox) + +**触发**:我的邮箱/发件需要邮箱/查邮件需要邮箱。 + +1. **执行(必须)**:`dws mail mailbox list --format json`,取自己的 `email`(默认选企业邮箱 `type:ORG`);查**他人**邮箱用 `dws mail user search --keyword "<姓名>" --format json`,**禁止**用 `mailbox list` 查他人。 + +**禁止**:把 `mailbox list` 当作他人邮箱查询、猜测邮箱地址。 + +### SOP-2 查 / 搜邮件(search-mail) + +**触发**:查邮件/搜邮件/某主题邮件/某人发的邮件。 + +1. **拿邮箱(必须)**:先按 SOP-1 取 `email`;用户已明确提供可跳过。 +2. **执行(必须)**:浏览文件夹 `dws mail message list --email <邮箱> --limit --format json`;KQL 搜索 `dws mail message search --email <邮箱> --query "" --limit 20 --format json`(KQL 如 `subject:周报`、`from:alice@x.com`、`folderId:2`、`hasAttachments:true`,**只通过 `--query` 传**)。 +3. **取正文(必须)**:`dws mail message get --email <邮箱> --id --format json`;`messageId` 从列表/搜索结果取,**禁止**编造。 + +**禁止**:把 KQL 拆成多个 flag、跳过 `message list/search` 直接猜 messageId。 + +### SOP-3 发邮件(send-mail) + +**触发**:发邮件/写邮件/群发。 + +1. **发件邮箱(必须)**:`dws mail mailbox list` 取自己邮箱。 +2. **收件邮箱(必须)**:地址直接用;姓名按 [mail.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` 查发送状态;不要把普通 `messageId` 传给 verify。 + +**禁止**:猜测收件邮箱、发送后不确认状态就答复"已发送"。 + +### SOP-4 回复 / 转发(reply-forward) + +**触发**:回复邮件/回复全部/转发。 + +1. **拿邮箱 + 原邮件(必须)**:SOP-1 取邮箱;用户未给 `messageId` 时**必须**先走 SOP-2 定位原邮件 `messageId`。 +2. **执行(必须)**:回复 `dws mail message reply --from <邮箱> --id --content "<正文>" --format json`;回复全部用 `reply-all`;转发 `dws mail message forward --from <邮箱> --to <收件邮箱> --id --content "<附言>" --format json`。 + +**禁止**:未定位原邮件就回复/转发、编造 messageId。 + +## 高频硬约束 - 用户要"完整内容/看看这封邮件/正文"时,`message search` 命中后必须继续调用 `dws mail message get --email <邮箱> --id --format json`;不要只列候选后停下。 - 搜到多封邮件时,若用户给了明确主题、附件名、发件人或时间线索,先选最匹配的一封执行 `message get`;只有同等候选无法判断时才询问用户。 -- 写入类操作(发送)按安全策略确认;只读查看、搜索不需要确认。 +- 附件链路固定三步:`message search` → `attachment list --email <邮箱> --id ` → `attachment download --email <邮箱> --message-id --attachment-id --name <文件名>`;不存在批量下载命令。 +- 写入类操作(发送、回复、转发、删除、批量移动)按安全策略确认;只读查看、搜索、附件列表、下载不需要确认。 - 所有 `dws mail` 命令加 `--format json`,并复用同一封邮件的 `messageId`,不要重新搜索导致目标漂移。 ## 跨产品协作 - 收件人是人名 → 先用 `dingtalk-contact` 取 `orgAuthEmail` - 钉钉内消息 → 切到 `dingtalk-chat` +## 局部意图与短流程 + +- [局部意图消歧](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 5ac7a50f..dcf365ee 100644 --- a/skills/multi/dingtalk-mail/references/09-mail.md +++ b/skills/multi/dingtalk-mail/references/09-mail.md @@ -1,12 +1,12 @@ # 邮件 -> **SKILL.md** 中 #9 内联 4 条 **lite**:`mail-list-mailbox`、`mail-search`、`mail-send`、`mail-reply-forward`,见 `dws-shared/references/best_practices/_common/lite-recipes.md`。下列 recipe、专用规则与消歧请在命中 #9 且**超出**上述 lite 时阅读本文。 -> 产品命令见 [mail.md](./mail.md)。通用批量/并行见 `dws-shared/references/best_practices/_common/conventions.md`。 +> 邮件的 4 条 **lite** 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.md](./mail.md) 中 KQL 查询字段说明。 -- **邮箱地址前置**:大部分邮件命令需要 `--email` 或 `--from` 参数,执行前**必须**先通过 `mail mailbox list` 获取当前用户邮箱(返回字段是 `emailAccounts`),禁止猜测邮箱地址。 +- **邮箱地址前置**:大部分邮件命令需要 `--email` 或 `--from` 参数,执行前**必须**先通过 `mail mailbox list` 获取当前用户邮箱,禁止猜测邮箱地址。 - **查找他人邮箱**:需要获取某人邮箱地址时,**不要用 `mailbox list`**(只返回自己的),必须走三路并发查询流程(见 [mail.md](./mail.md) 中「查找他人邮箱地址」章节)。 - **附件下载三步走**:先 `message search` 搜索邮件获取 messageId,再 `attachment list` 获取附件 ID 和文件名,最后逐个 `attachment download` 下载。**不存在 `download_batch` / `download_all` 等批量下载命令,禁止编造**。 - **危险操作确认**:`batch-delete` 执行前必须向用户确认,同意后加 `--yes`。 @@ -24,33 +24,22 @@ | Recipe | 步骤 | |--------|------| | `mail-get` | `mail message get --email <邮箱> --id ` → 查看邮件完整内容(含正文) | -| `mail-message-list` | `mail message list --email <邮箱>` → 列出文件夹中的邮件(默认收件箱);`--folder-id ` 指定文件夹、`--limit ` 分页 | | `mail-folder-list` | `mail folder list --email <邮箱>` → 列举文件夹;`--folder ` 查子文件夹 | -| `mail-folder-manage` | `mail folder create/update/delete`:创建/重命名/删除文件夹(先 `folder list` 取 `folders[].id`) | | `mail-tag-list` | `mail tag list --email <邮箱>` → 列举邮件标签 | -| `mail-tag-manage` | `mail tag create/update/delete`:创建/重命名/删除标签(先 `tag list` 取 `tags[].id`,系统标签不可改删) | | `mail-thread-get` | `mail thread get --email <邮箱> --id ` → 获取会话(邮件线程)详情 | -| `mail-thread-list` | `mail thread list --email <邮箱> --folder --limit ` → 列出文件夹下的会话 | -| `mail-thread-manage` | `mail thread update/batch-update`(标记已读未读/加移标签)、`thread trash/batch-trash`(移入已删除,须 `--yes`) | | `mail-attachment-list` | `mail attachment list --email <邮箱> --id ` → 列举指定邮件的附件 | | `mail-attachment-download` | 1. `mail attachment list --email <邮箱> --id ` → 取附件 `id` 和 `name`
2. `mail attachment download --email <邮箱> --message-id --attachment-id --name <文件名>` | | `mail-batch-move` | `mail message batch-move --email <邮箱> --ids --folder `(常用 folderId: 2=收件箱, 6=已删除) | -| `mail-batch-delete` | `mail message batch-delete --email <邮箱> --ids `(**危险操作,须先确认**;仅移入已删除文件夹,非物理永久删除) | -| `mail-batch-update` | `mail message batch-update --email <邮箱> --ids --action markRead/markUnread/addTags/removeTags`(加移标签需 `--tags`) | -| `mail-verify` | `mail message verify --email <邮箱> --internet-message-id ` → 查发送状态(`sendStatus` 在 `message.sendStatus`) | -| `mail-draft-create` | `mail draft create --from <邮箱> --subject "<标题>"` → 取草稿 ID `result.message.id`(可选 `--to`、`--content`、`--cc`) | +| `mail-batch-delete` | `mail message batch-delete --email <邮箱> --ids --yes`(**危险操作,须先确认**) | +| `mail-draft-create` | `mail draft create --from <邮箱> --subject "<标题>"` → 取 `messageId`(可选 `--to`、`--content`、`--cc`) | | `mail-draft-update` | `mail draft update --from <邮箱> --id --subject "<新标题>"`(可选 `--content`、`--to`、`--cc`) | | `mail-draft-send` | `mail draft send --from <邮箱> --id ` | -| `mail-template` | `mail template create/list/get/update/delete`(草稿模板 `--is-draft` 不出现在 list,只能 get;非草稿模板不可 update) | -| `mail-contact` | `mail contact create/list/update/batch-delete`:管理个人联系人(非通讯录搜人,搜人用 `user search`) | -| `mail-auto-reply-get` | `mail auto-reply get --email <邮箱>` → 获取自动回复配置 | -| `mail-rule` | `mail rule list/create/update/delete/adjust`:管理个人收信规则 | ## Full / 多步组合 | Recipe | 行动指南(固定路线) | |--------|---------------------| -| search-and-download-attachment | 1. `mail mailbox list` → 从 `emailAccounts` 取邮箱
2. `mail message search --email <邮箱> --query "" --limit 20` → 取 `messageId` 列表
3. 对每封邮件执行 `mail attachment list --email <邮箱> --id ` → 列出附件取 `id` 和 `name`
4. 对每个附件逐个执行 `mail attachment download --email <邮箱> --message-id --attachment-id --name <文件名>`(**仅支持逐个下载,不存在批量下载命令**) | +| search-and-download-attachment | 1. `mail mailbox list` → 取邮箱
2. `mail message search --email <邮箱> --query "" --limit 20` → 取 `messageId` 列表
3. 对每封邮件执行 `mail attachment list --email <邮箱> --id ` → 列出附件取 `id` 和 `name`
4. 对每个附件逐个执行 `mail attachment download --email <邮箱> --message-id --attachment-id --name <文件名>`(**仅支持逐个下载,不存在批量下载命令**) | | search-reply-forward | 1. `mail mailbox list` → 取邮箱
2. `mail message search --email <邮箱> --query "" --limit 10` → 取 `messageId`
3. 展示搜索结果供用户选择
4. 按用户指示执行 reply / reply-all / forward(参见 lite `mail-reply-forward`) | -| batch-mail-cleanup | 1. `mail mailbox list` → 从 `emailAccounts` 取邮箱
2. `mail message search --email <邮箱> --query "" --limit 100` → 取多个 `messageId`
3. 展示列表供用户确认
4. `mail message batch-move --email <邮箱> --ids --folder 6 ` 或 `batch-delete` **都是移入「已删除」文件夹,并非物理永久删除**(对已在已删除文件夹的邮件再执行返回 success 但无效;CLI 无永久删除路径,需在客户端手动清空) | +| 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.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 new file mode 100644 index 00000000..803212b8 --- /dev/null +++ b/skills/multi/dingtalk-mail/references/_common/conventions.md @@ -0,0 +1,44 @@ +# 业务域通用规范 + +> 仅服务本 skill 已迁入的行动指南。安全门控、危险操作确认、`--format json` 等已在本 skill 的 `SKILL.md` 中定义,此处不重复。 + +## 批量查询规范 + +| # | 规范 | +|---|------| +| 1 | **并行查详情**:拿到多个 ID 后,用 `&` 合并到同一条 Shell 命令并行执行 + `wait`,**严禁逐条串行** | +| 2 | **翻页**:分页接口须拉全直至无更多 | +| 3 | **优先批量 API**:有批量接口则用批量;无则按 #1 并行 | +| 4 | **群消息**:必须先 `chat search --query` 得 `openConversationId`,再 `chat message list --group --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-mail/references/intent-guide.md b/skills/multi/dingtalk-mail/references/intent-guide.md new file mode 100644 index 00000000..9a012701 --- /dev/null +++ b/skills/multi/dingtalk-mail/references/intent-guide.md @@ -0,0 +1,8 @@ +# mail 局部意图消歧 + +本文件从单 Skill `intent-guide.md` 拆分而来,仅保留与本产品相关的跨产品消歧规则。 + +| 用户说... | 真实意图 | 应该用 | 不要用 | 理由 | +|---|---|---|---|---| +| "搜一下智能化方案/最近 OKR 相关邮件/最近发版相关消息" | 搜企业知识内容 | `aisearch enterprise` | `doc search` / `mail search` / `chat message search` | 跨文档、消息、日程、听记、邮件等企业内容语义检索走 enterprise;具体 `queries/types/time-range` 抽槽见 `aisearch.md` | +| "我发给某人的消息/邮件/文档/今天我干了什么" | 搜行为记录 | `aisearch behavior` | `chat` / `mail` / `doc` / `report` | 关注“谁对什么做过什么”,走 behavior;具体 `behavior-type/direction/chat-scope` 抽槽见 `aisearch.md` | diff --git a/skills/multi/dingtalk-mail/references/lite-recipes.md b/skills/multi/dingtalk-mail/references/lite-recipes.md new file mode 100644 index 00000000..a3065e88 --- /dev/null +++ b/skills/multi/dingtalk-mail/references/lite-recipes.md @@ -0,0 +1,45 @@ +# mail Lite Recipe + +本文件从单 Skill `lite-recipes.md` 拆分而来,仅保留与本产品相关的轻量流程。 + +## #9 邮件 + +### mail-list-mailbox + +查询当前用户自己的可用邮箱地址列表。**仅返回自己的邮箱**,不能查他人邮箱(查他人邮箱请走 [mail.md](./mail.md) 中「查找他人邮箱地址」三路并发查询流程)。 + +`mail mailbox list` + +### mail-search + +搜索邮件。**必须使用 KQL 语法通过 `--query` 传递查询条件**,禁止臆造 `--subject`、`--from` 等不存在的 flag。 + +1. 获取邮箱地址:`mail mailbox list` → 取用户邮箱地址。若用户已提供邮箱可跳过。 +2. 构造 KQL 查询:根据用户意图将搜索条件转为 KQL 表达式(详见 [mail.md](./mail.md) 中 KQL 查询字段说明)。 + - 按主题:`subject:周报`、`subject:"项目 进展"`(含空格须加双引号) + - 按发件人:`from:alice@company.com` 或 `from:"张三"` + - 按日期:`date>2025-06-01T00:00:00Z`(ISO8601 格式,必须含时间部分) + - 按文件夹:`folderId:2`(2=收件箱, 1=已发送, 5=草稿, 6=已删除) + - 按是否有附件:`hasAttachments:true` + - 组合:`from:alice AND subject:周报 AND date>2025-06-01T00:00:00Z` +3. 执行搜索:`mail message search --email <邮箱> --query "" --limit 20` +4. 查看详情(按需):`mail message get --email <邮箱> --id ` + +### mail-send + +发送邮件。 + +1. 获取邮箱地址:`mail mailbox list` → 取用户邮箱作为 `--from`。 +2. 确定收件人:用户直接提供邮箱地址 → 直接使用;用户提供姓名或工号 → 走「查找他人邮箱地址」三路并发流程(见 [mail.md](./mail.md))。 +3. 发送:`mail message send --from <发件邮箱> --to <收件邮箱> --subject "<主题>" --content "<正文>"`(可选 `--cc`、`--attachment`、`--inline-attachment`)。 + +### mail-reply-forward + +回复或转发邮件。 + +1. 获取邮箱地址:`mail mailbox list` → 取用户邮箱。 +2. 定位原始邮件:若用户未提供 messageId → 先用 `mail-search` 搜索定位。 +3. 执行: + - 回复:`mail message reply --from <邮箱> --id `(可选 `--to`、`--subject`、`--content`) + - 回复全部:`mail message reply-all --from <邮箱> --id `(可选 `--to`、`--subject`、`--content`) + - 转发:`mail message forward --from <邮箱> --to <收件邮箱> --id `(可选 `--subject`、`--content`) diff --git a/skills/multi/dingtalk-mail/references/mail.md b/skills/multi/dingtalk-mail/references/mail.md index bea9ebdb..cbcfabd4 100644 --- a/skills/multi/dingtalk-mail/references/mail.md +++ b/skills/multi/dingtalk-mail/references/mail.md @@ -39,7 +39,7 @@ | `dws mail thread batch-update` | 批量修改邮件会话的状态或标签(单次最多 100 个) | | `dws mail thread trash` | 将单个邮件会话移动到已删除文件夹(不会永久删除) | | `dws mail thread batch-trash` | 将多个邮件会话批量移动到已删除文件夹(单次最多 100 个,不会永久删除) | -| `dws mail user search` | 搜索通讯录用户(**按姓名查他人邮箱**,不是搜邮件) | +| `dws mail user search` | 搜索通讯录用户(**按姓名或工号查他人邮箱**,不是搜邮件) | | `dws mail template create` | 创建邮件模板 | | `dws mail template list` | 列举邮件模板 | | `dws mail template get` | 获取邮件模板详情 | @@ -76,7 +76,7 @@ **默认选择策略:** 1. 调用 `dws mail mailbox list --format json` 获取当前用户的所有邮箱。 -2. 从返回的 `emailAccounts` 中**优先选择企业邮箱**(账号类型为企业邮箱、域名非 `@dingtalk.com` 的邮箱),将其作为 `--email` / `--from` 的默认值。 +2. 从返回的 `mailboxes` 中**优先选择企业邮箱**(账号类型为企业邮箱、域名非 `@dingtalk.com` 的邮箱),将其作为 `--email` / `--from` 的默认值。 3. 仅当用户在指令中**明确指定**「用我的个人邮箱」「用 dingtalk.com 邮箱」「用我的私人邮箱」等表述时,才选择个人邮箱(`@dingtalk.com` 域名)。 4. 若用户同时拥有多个企业邮箱(如分属多家公司),优先选择与当前会话上下文匹配的企业邮箱;若仍无法判断,向用户确认后再操作。 5. 若用户**仅拥有个人邮箱**(无企业邮箱),可直接使用个人邮箱,但需注意 `mail user search` 等仅企业邮箱可用的命令会因权限报错,需走「查找他人邮箱地址」章节的替代路径。 @@ -102,7 +102,7 @@ Example: | 字段 | 类型 | 说明 | |------|------|------| -| `emailAccounts` | `List[]` | 邮箱列表,每条包含 `email`(邮箱地址)、`orgName`(所属企业,个人邮箱为 null)、`type`(`ORG`=企业邮箱 / `PERSONAL`=个人邮箱) | +| `mailboxes` | `List[]` | 邮箱列表,每条包含邮箱地址、账号类型、所属企业 | ### 获取用户邮箱信息 @@ -119,11 +119,15 @@ Flags: | 字段 | 类型 | 说明 | |------|------|------| -| `email` | string | 邮箱地址 | -| `emailAliases` | List[] | 邮件地址别名列表 | -| `name` / `nickname` / `displayName` | string | 用户名、昵称、显示名 | -| `mboxSize` / `mboxSizeUsed` | int64 | 邮箱容量与已用容量(字节) | -| `createdTime` / `modifiedTime` | string | 创建时间与更新时间 | +| `email` | `string` | 邮箱地址 | +| `emailAliases` | `string[]` | 邮件地址别名列表 | +| `name` | `string` | 用户名 | +| `nickname` | `string` | 用户昵称 | +| `displayName` | `string` | 用户显示名 | +| `mboxSize` | `string` | 邮箱容量(字节) | +| `mboxSizeUsed` | `string` | 已使用的邮箱容量(字节) | +| `createdTime` | `string` | 创建时间 | +| `modifiedTime` | `string` | 修改时间 | ### 查找他人邮箱地址(通讯录查人) @@ -134,23 +138,19 @@ Flags: **三路并发查询流程:** ```bash -# 同时发起以下三路,取最先返回有效邮箱的结果 -# 路径 1:aisearch + contact user get +# 主路径: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(仅企业邮箱可用,个人邮箱会报权限错误可忽略) +# 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 - -# 路径 3:contact user search -dws contact user search --keyword "姓名" --format json -# → 提取用户邮箱字段 ``` -若三路均无有效邮箱,必须 `ask_human` 请用户手动提供,**严禁臆测**。 +若两步均无有效邮箱,必须 `ask_human` 请用户手动提供,**严禁臆测**。 ### 列出文件夹中的邮件 > **注意:** `message list` 用于按文件夹列出邮件;若需根据主题/发件人/日期等条件精确搜索,请使用 `message search`。 @@ -164,8 +164,8 @@ Example: dws mail message list --email user@company.com --cursor Flags: --email string 邮件所属邮箱地址 (必填) - --folder-id string 文件夹 ID(1=已发送, 2=收件箱, 3=垃圾邮件, 5=草稿, 6=已删除),默认为收件箱 - --limit string 每页返回数量(最大限制 100, 默认 20) + --folder-id string 文件夹 ID(1=已发送, 2=收件箱, 3=垃圾邮件, 5=草稿, 6=已删除),默认为收件箱,别名: --folder + --limit string 每页返回数量(最大限制 100, 默认 20),别名: --size, --page-size --cursor string 邮件的起始偏移标识, 其值取自响应中的nextCursor字段。""表示从头开始 ``` @@ -188,7 +188,7 @@ Flags: --cursor string 邮件的起始偏移标识, 其值取自响应中的nextCursor字段。""表示从头开始 --email string 搜索目标邮箱地址 (必填) --query string KQL 查询表达式 (必填), 其中 date 格式需遵循 ISO8601 规范 - --limit string 每页返回数量(最大限制 100, 默认 20) + --limit string 每页返回数量(最大限制 100, 默认 20),别名: --size, --page-size ``` KQL 查询字段: date, size, tag, folderId, isRead, hasAttachments, subject, attachname, body, from, to @@ -246,9 +246,7 @@ Flags: | 字段 | 类型 | 说明 | |------|------|------| -| `message` | `object` | 邮件信息,含 subject/from/toRecipients/ccRecipients/markdownBody/conversationId/receivedDateTime/size 等 | - -> **注意:** 即使邮件确有附件,`message get` 返回的 `message` 对象也**不含** `attachments`/`isRead`/`tags` 字段(实测 keys 仅上表所列这些)。要取附件请另用 `dws mail attachment list`。 +| `message` | `object` | 邮件完整信息,包含主题、发件人、收件人、正文、附件等 | ### 批量获取邮件详情 @@ -263,7 +261,21 @@ Flags: --ids string 要获取的邮件 ID 列表,逗号分隔,最多 20 个 (必填) ``` -单次最多获取 20 封邮件。CLI 会逐个调用 `get_email_by_message_id`,并以 `messages` 数组聚合返回。 +单次最多获取 20 封邮件。 + +**返回 JSON:** + +```json +{ + "success": true, + "messages": [ + { "subject": "...", "from": "...", "to": [...], "body": "...", ... }, + { "subject": "...", "from": "...", "to": [...], "body": "...", ... } + ] +} +``` + +> **注意:** 如果某个邮件 ID 获取失败,整个命令会报错并中止。建议先通过 `message search` 或 `message list` 确认邮件 ID 有效后再批量获取。 ### 发送邮件 ``` @@ -281,7 +293,7 @@ Example: 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 邮件正文 (必填) + --content string 邮件正文 (必填),别名: --body --cc string 抄送人列表 --from string 发件人邮箱 (必填),别名: --sender --subject string 邮件标题 (必填) @@ -306,7 +318,7 @@ Flags: - 仅支持图片类型:`jpg` / `jpeg` / `png` / `gif` / `webp` / `bmp` / `svg` - CLI 自动生成 contentId,格式:`inline-{文件名(不含扩展名)}-{序号}@alimail.com`,例:`inline-chart-1@alimail.com` - 在 `--content` 中使用占位符 `[inline:文件名]` 引用图片,CLI 自动替换为 `` 标签 -- 若 body 中没有对应占位符,内联图片会自动追加到正文末尾 +- 若 content 中没有对应占位符,内联图片会自动追加到正文末尾 - 非图片类型(PDF、视频、音频等)请改用 `--attachment` ### 列举邮件文件夹 @@ -317,8 +329,8 @@ Example: dws mail folder list --email user@company.com dws mail folder list --email user@company.com --folder Flags: - --email string 邮件所属邮箱地址 (必填) - --folder string 父文件夹唯一标识,不传则返回顶层文件夹 (可选) + --email string 邮件所属邮箱地址 (必填) + --folder string 父文件夹唯一标识,不传则返回顶层文件夹 (可选),别名: --folder-id ``` 不传 `--folder` 返回顶层文件夹列表;传入则返回该文件夹的子文件夹列表。 @@ -400,7 +412,8 @@ Flags: ```json { - "success": true + "success": true, + "result": {} } ``` @@ -422,7 +435,8 @@ Flags: ```json { - "success": true + "success": true, + "result": {} } ``` @@ -448,7 +462,7 @@ Flags: |------|------|------| | `id` | `string` | 附件唯一标识 | | `name` | `string` | 附件文件名 | -| `isInline` | `bool` | 是否为内联附件(`true`=正文内联图片,`false`=普通附件) | +| `contentType` | `string` | 附件 MIME 类型 | | `size` | `int` | 附件大小(字节) | ### 下载邮件附件 @@ -568,7 +582,8 @@ Flags: ```json { - "success": true + "success": true, + "result": {} } ``` @@ -592,7 +607,8 @@ Flags: ```json { - "success": true + "success": true, + "result": {} } ``` @@ -672,10 +688,8 @@ Flags: | `senders` | `List[{email, name}]` | 会话发件人列表 | | `isRead` | `boolean` | 会话是否已读(全部已读/未读) | | `priority` | `string` | 会话重要性,取会话内邮件最高优先级(`PRY_HIGH` / `PRY_NORMAL`) | +| `flag` | `string` | 会话标识,取会话内最近邮件的标识(`FLAG_NONE` / `FLAG_REPLY` / `FLAG_FORWARD`) | | `hasAttachments` | `boolean` | 会话是否包含附件(不含 inline 资源) | -| `messages` | `List[]` | 会话内的邮件列表;默认即返回,但各邮件字段(正文、收件人等)多为 null,需在 `--select` 中额外指定才有值 | - -> **注意:** `thread get` 返回的 `conversation` **不含 `flag` 字段**(`flag` 仅出现在 `thread list` 的会话项中)。会话标识请从 `thread list` 获取。 ### 修改邮件会话状态 @@ -703,11 +717,20 @@ Flags: | `addTags` | 给会话增加标签 | 是 | | `removeTags` | 从会话移除标签 | 是 | +**常用标签 ID:** + +| 标签 ID | 名称 | 图标 | +|---------|------|------| +| `1` | 跟进事项 | 小红旗 | +| `2` | 完成事项 | 绿色小勾 | +| `11` | 重要 | 星标 | + 成功时返回: ```json { - "success": true + "success": true, + "result": {} } ``` @@ -732,7 +755,8 @@ Flags: ```json { - "success": true + "success": true, + "result": {} } ``` @@ -746,12 +770,12 @@ Example: Flags: --email string 会话所属邮箱地址 (必填) --id string 要删除的会话 ID (必填) - --yes 确认执行此危险操作 (必填) + --yes 跳过确认提示,直接执行 (可选) ``` > ⚠️ **危险操作**:此命令会将邮件会话移动到已删除文件夹。建议先通过 `thread get` 确认目标会话后再执行。 -将指定邮件会话移动到已删除文件夹,不会永久删除邮件。`--id` 必须填写会话 ID,不是邮件 ID。执行前需向用户确认,确认后传入 `--yes` 执行。 +将指定邮件会话移动到已删除文件夹,不会永久删除邮件。`--id` 必须填写会话 ID,不是邮件 ID。默认需要用户确认,传入 `--yes` 可跳过确认。 成功时返回: @@ -772,12 +796,12 @@ Example: Flags: --email string 会话所属邮箱地址 (必填) --ids string 要删除的会话 ID 列表,多个用英文逗号分隔,最多 100 个 (必填) - --yes 确认执行此危险操作 (必填) + --yes 跳过确认提示,直接执行 (可选) ``` > ⚠️ **危险操作**:此命令会批量将邮件会话移动到已删除文件夹。建议先通过 `thread list` 确认目标会话后再执行。 -将指定邮件会话批量移动到已删除文件夹,单次最多 100 个会话。不会永久删除邮件。`--ids` 必须填写会话 ID 列表,不是邮件 ID 列表。执行前需向用户确认,确认后传入 `--yes` 执行。 +将指定邮件会话批量移动到已删除文件夹,单次最多 100 个会话。不会永久删除邮件。`--ids` 必须填写会话 ID 列表,不是邮件 ID 列表。默认需要用户确认,传入 `--yes` 可跳过确认。 成功时返回: @@ -800,7 +824,7 @@ Flags: --to string 收件人列表(可选) --id string 要回复的邮件 ID (必填) --subject string 回复邮件标题(可选) - --content string 回复正文(可选) + --content string 回复正文(可选),别名: --body --attachment stringArray 附件文件路径,可多次指定 (可选) --inline-attachment stringArray 内联图片路径,可多次指定,cid 自动生成 (可选) ``` @@ -832,7 +856,7 @@ Flags: --to string 收件人列表(可选,包含发件人及所有原始收件人) --id string 要回复的邮件 ID (必填) --subject string 回复邮件标题(可选) - --content string 回复正文(可选) + --content string 回复正文(可选),别名: --body --attachment stringArray 附件文件路径,可多次指定 (可选) --inline-attachment stringArray 内联图片路径,可多次指定,cid 自动生成 (可选) ``` @@ -864,7 +888,7 @@ Flags: --to string 转发收件人列表(可选) --id string 要转发的邮件 ID (必填) --subject string 转发邮件标题(可选) - --content string 转发附言(可选) + --content string 转发附言(可选),别名: --body --attachment stringArray 附件文件路径,可多次指定 (可选) --inline-attachment stringArray 内联图片路径,可多次指定,cid 自动生成 (可选) ``` @@ -967,9 +991,7 @@ Flags: | 字段 | 类型 | 说明 | |------|------|------| | `message` | `object` | 邮件完整信息 | -| `message.sendStatus` | `string` | 发送状态,**嵌在 `message` 对象内部**(非顶层字段),取值见下表 | - -> **注意:** 顶层只有 `message` 和 `success` 两个字段,`sendStatus` 位于 `message.sendStatus`,不是与 `message` 平级的顶层字段。 +| `sendStatus` | `string` | 发送状态,取值见下表 | **`sendStatus` 取值说明:** @@ -996,15 +1018,16 @@ Flags: --yes 跳过确认提示,直接执行 (可选) ``` -> CAUTION: 此命令会撤回已发送邮件。仅支持撤回同组织内未读邮件,执行前必须确认目标邮件、主题和影响范围。 +> ⚠️ **危险操作**:此命令会撤回已发送的邮件。仅支持撤回同组织内未读邮件。 **返回字段:** | 字段 | 类型 | 说明 | |------|------|------| -| `id` | string | 撤回任务 ID,可用于 `recall-detail` 查询进度 | -| `success` | boolean | 接口调用是否成功 | -| `errorCode` / `errorMsg` | string | 错误码和错误信息(仅失败时存在) | +| `id` | `string` | 撤回任务 ID(可用于 recall-detail 查询进度) | +| `success` | `boolean` | 接口调用是否成功 | +| `errorCode` | `string` | 错误码(仅失败时存在) | +| `errorMsg` | `string` | 错误信息(仅失败时存在) | ### 查询邮件撤回进度 @@ -1018,16 +1041,28 @@ Flags: --id string 撤回任务 ID (必填),由 recall 命令返回 ``` -根据撤回任务 ID 查询邮件撤回进度。任务状态包括 `UNINITED` / `SUBMITTED` / `RUNNING` / `FINISHED` / `CANCELED` / `FAILED`。 +根据撤回任务 ID 查询邮件撤回的详细进度。撤回任务 ID 来源:`sent-message recall` 命令返回值中的 `id` 字段。 **返回字段:** | 字段 | 类型 | 说明 | |------|------|------| -| `id` | string | 撤回任务 ID | -| `status` | string | 任务状态 | -| `totalCount` / `succeededCount` / `failedCount` | int | 总数、成功数、失败数 | -| `details` | List[] | 每封邮件的撤回结果 | +| `success` | `boolean` | 调用是否成功 | +| `id` | `string` | 任务 ID | +| `status` | `string` | 任务状态(见下方枚举) | +| `errorCode` | `string` | 错误码(仅失败时存在) | +| `errorMsg` | `string` | 错误信息(仅失败时存在) | + +**任务状态枚举:** + +| 状态值 | 说明 | +|--------|------| +| `UNINITED` | 未初始化 | +| `SUBMITTED` | 已提交 | +| `RUNNING` | 执行中 | +| `FINISHED` | 已完成 | +| `CANCELED` | 已取消 | +| `FAILED` | 失败 | ### 创建草稿 ``` @@ -1046,7 +1081,7 @@ Flags: --subject string 邮件标题 (必填) --to string 收件人列表(可选,有确定收件人时才传) --cc string 抄送人列表(可选,有确定抄送人时才传) - --content string 邮件正文(可选,有正文内容时才传) + --content string 邮件正文(可选,有正文内容时才传),别名: --body --attachment stringArray 附件文件路径,可多次指定 (可选) --inline-attachment stringArray 内联图片路径,可多次指定,cid 自动生成 (可选) ``` @@ -1061,7 +1096,7 @@ Flags: | 字段 | 类型 | 说明 | |------|------|------| -| `result.message.id` | `string` | 新建草稿的邮件 ID(**嵌在 `result.message` 内**,无顶层 `messageId` 字段);`result.message` 还含 `internetMessageId`、`folderId`(草稿箱为 `5`)、`subject`、`from` 等 | +| `messageId` | `string` | 新建草稿的邮件 ID | ### 更新草稿 ``` @@ -1078,7 +1113,7 @@ Flags: --to string 收件人列表(可选) --cc string 抄送人列表(可选) --subject string 邮件标题(可选) - --content string 邮件正文(可选) + --content string 邮件正文(可选),别名: --body --attachment stringArray 附件文件路径,可多次指定 (可选) --inline-attachment stringArray 内联图片路径,可多次指定,cid 自动生成 (可选) ``` @@ -1109,21 +1144,25 @@ Example: 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 不再必填 (可选) + --employee-no string 按工号搜索用户;提供此参数时 keyword 不再必填 --cursor string 分页游标,取自响应中的 nextCursor 字段(可选) - --limit string 每页返回数量(可选) + --limit string 每页返回数量(可选),别名: --size ``` -> **重要区别:** -> - `mail user search` — 搜索**通讯录联系人/邮箱用户**(按姓名/关键词找人),用于获取某人的邮箱地址 +> **重要区别(三个容易混淆的命令):** +> - `mail user search` — 搜索**企业通讯录用户**(按姓名/关键词或工号找人),用于获取某人的邮箱地址。需要企业邮箱权限。 +> - `mail contact list` — 列举**个人联系人**(用户自己保存/创建的联系人列表),不需要关键词,返回自己的联系人。 > - `mail message search` — 搜索**邮件内容**(按 KQL 语法搜邮件,如主题、发件人、日期等) > -> 不要混淆:查找"某人的邮箱地址"用 `user search`;查找"某封邮件"用 `message search`。 +> 不要混淆:查找"某人的邮箱地址"用 `user search`;查看"自己保存的联系人"用 `contact list`;查找"某封邮件"用 `message search`。 > > 仅企业邮箱(非 `@dingtalk.com` 个人邮箱)可使用 `user search`;使用个人邮箱调用将因无权限而报错。 +> +> `--keyword` 与 `--employee-no` 至少需要提供一个;当提供 `--employee-no` 时,`--keyword` 不再是必填字段。 **返回字段:** @@ -1156,7 +1195,7 @@ Flags: --email string 用户邮箱地址 (必填) --from string 模板发件人邮箱 (可选) --subject string 模板邮件标题 (必填) - --content string 模板邮件正文 (必填) + --content string 模板邮件正文 (必填),别名: --body --name string 模板名称 (必填) --to string 模板收件人列表,逗号分隔 (可选) --cc string 模板抄送人列表,逗号分隔 (可选) @@ -1164,10 +1203,6 @@ Flags: ``` > **草稿模板说明:** 传入 `--is-draft` 创建的模板为草稿模板,草稿模板支持后续通过 `template update` 修改内容。**非草稿模板创建后不可修改**(`template update` 仅对草稿模板有效)。 -> -> **草稿模板不出现在 `template list`(重要):** 实测 `template list` **只返回非草稿模板**;草稿模板(`--is-draft`)不在列表里,只能用 `template get --id <模板ID>` 直接获取。因此**不要用 `template list` 是否出现来判断草稿模板是否创建成功**——`template create` 返回 `success:true` 即已创建。 -> -> **模板 ID 前缀区分类型:** `template create` 返回的 `id` 前缀标识类型——非草稿模板为 `1:0:` 开头(如 `1:0:8bbeea56-...`),草稿模板为 `0:0:` 开头(如 `0:0:e3e2d134-...`)。 ### 列举邮件模板 ``` @@ -1179,7 +1214,7 @@ Example: Flags: --email string 用户邮箱地址 (必填) --cursor string 分页游标,取自响应中的 nextCursor 字段 (可选) - --limit string 每页返回数量 (必填) + --limit string 每页返回数量 (必填),别名: --size ``` ### 获取邮件模板详情 @@ -1195,8 +1230,6 @@ Flags: ### 更新邮件模板 -> ⚠️ 服务端限制:仅草稿模板(创建时带 `--is-draft`)可更新;非草稿模板更新会返回 1001 Invalid parameter。需要修改非草稿模板时,建议删除后用 `--is-draft` 重建。 - > **重要限制:** `template update` **仅对草稿模板有效**。只有通过 `template create --is-draft` 创建的草稿模板才支持更新,非草稿模板调用 update 会返回 `Invalid parameter` 错误。 ``` @@ -1210,7 +1243,7 @@ Flags: --id string 模板唯一标识 (必填,必须是草稿模板的 ID) --from string 模板发件人邮箱 (可选) --subject string 模板邮件标题 (可选) - --content string 模板邮件正文 (可选) + --content string 模板邮件正文 (可选),别名: --body --name string 模板名称 (可选) --to string 模板收件人列表,逗号分隔 (可选) --cc string 模板抄送人列表,逗号分隔 (可选) @@ -1255,11 +1288,9 @@ Example: Flags: --email string 用户邮箱地址 (必填) --cursor string 分页游标,取自响应中的 nextCursor 字段 (可选) - --limit string 每页返回数量 (必填) + --limit string 每页返回数量 (必填),别名: --size ``` -> **重要区别:** `contact list` 列举的是**个人联系人**(用户自己保存/创建的联系人列表),不需要关键词;搜索**企业通讯录用户**(按姓名找人查邮箱)请用 `user search`。 - ### 更新邮件联系人 ``` Usage: @@ -1316,7 +1347,7 @@ Flags: ### 更新自动回复配置 -更新或设置用户的邮件自动回复配置。建议先通过 `auto-reply get` 获取当前配置,再传入需要修改的字段值。 +更新或设置用户的邮件自动回复配置。所有参数均为必填。建议先通过 `auto-reply get` 获取当前配置,再传入需要修改的字段值。 ``` Usage: @@ -1337,36 +1368,107 @@ Flags: --content string 自动回复内容 (必填) ``` +**返回字段:** + +| 字段 | 类型 | 说明 | +|------|------|------| +| `success` | boolean | 更新是否成功 | +| `result` | object | 更新结果,成功时为空对象 | +| `errorCode` | string | 错误码(仅失败时存在) | +| `errorMsg` | string | 错误信息(仅失败时存在) | + ### 个人收信白名单管理 +#### 列出白名单 + ``` Usage: dws mail allow-list list [flags] - dws mail allow-list add [flags] - dws mail allow-list remove [flags] -Examples: +Example: dws mail allow-list list --email user@company.com - dws mail allow-list add --email user@company.com --entries a@b.com,@example.com - dws mail allow-list remove --email user@company.com --entries a@b.com,@example.com +Flags: + --email string 用户的邮箱地址 (必填) +``` + +**返回字段:** + +| 字段 | 类型 | 说明 | +|------|------|------| +| `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 逗号分隔的地址列表,支持邮件地址或 @domain.com 域名(add/remove 必填) + --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] - dws mail block-list add [flags] - dws mail block-list remove [flags] -Examples: +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 逗号分隔的地址列表,支持邮件地址或 @domain.com 域名(add/remove 必填) + --entries string 逗号分隔的地址列表,支持邮件地址(如123@domain.com)或域名(如@domain.com) ``` ### 收信规则管理 @@ -1395,7 +1497,7 @@ Flags: | `rules[].enabled` | bool | 是否启用 | | `rules[].conditions` | List[] | 规则条件列表 | | `rules[].actions` | List[] | 规则动作列表 | -| `rules[].order` | null | 排序字段;实测存量与新建规则该字段**恒为 `null`**,不返回有效排序值,不要依赖它判断规则顺序 | +| `rules[].order` | int | 规则排序 | #### 创建收信规则 @@ -1504,7 +1606,7 @@ Example: dws mail rule create --email user@company.com --name "VIP邮件标记" --enabled true \ --conditions '[{"object":"from","or":[{"and":[{"operation":"include","keyword":"vip@company.com","ignoreCase":true}]}]}]' \ --actions '[{"action":"ActFlagMail2","parameters":["asread"]}]' - dws mail rule create --email user@company.com --name "大附件归档" --enabled true \ + 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: @@ -1515,24 +1617,22 @@ Flags: --actions string 规则动作 JSON 数组 (必填) ``` -> **⚠️ 实测:** 尽管 `--help` 把 `--conditions` 标为(可选),服务端实际要求它**非空**——省略或传空会返回 `209 SYS_UNKNOWN_ERROR(1001)` 失败,帮助文本承诺的"命中所有邮件"无条件匹配**不可用**。create 请始终传入有效条件。 - **返回字段:** | 字段 | 类型 | 说明 | |------|------|------| | `success` | bool | 创建是否成功 | +| `errorCode` | string | 错误码 | +| `errorMsg` | string | 错误消息 | | `id` | string | 新建规则 ID | -> 实测 `rule create` 成功仅返回 `{id, success}`,**不返回** `errorCode` / `errorMsg`。 - #### 更新收信规则 -更新已有的收信规则。**所有参数(含 `--conditions`)均须传入有效值**——见下方警告。 +更新已有的收信规则。**除 `--conditions` 外所有参数均为必填**。 > **建议工作流:** 先通过 `dws mail rule list` 获取当前规则的完整配置,再传入需要修改的字段值。 > -> **⚠️ 实测:** `--help` 称 `--conditions` 为空即命中所有邮件,但该无条件匹配**不可用**——省略或传空会返回 `209 SYS_UNKNOWN_ERROR(1001)` 失败。update 请始终传入有效条件。`--actions` 格式同 create 命令。 +> `--conditions` 为空或不传表示命中所有邮件(无条件匹配)。`--actions` 格式同 create 命令。 ``` Usage: @@ -1552,16 +1652,14 @@ Flags: --actions string 规则动作 JSON 数组 (必填) ``` -> **⚠️ 实测:** 上方 `--conditions` 的"(可选,为空表示命中所有邮件)"是 `--help` 原文,但服务端实际要求它**非空**——省略或传空会返回 `209 SYS_UNKNOWN_ERROR(1001)` 失败。update 须传入有效条件。 - **返回字段:** | 字段 | 类型 | 说明 | |------|------|------| | `success` | bool | 更新是否成功 | -| `result` | object | 更新结果(实测为空对象 `{}`) | - -> 实测 `rule update` 成功仅返回 `{result:{}, success}`,**不返回** `errorCode` / `errorMsg`。 +| `errorCode` | string | 错误码 | +| `errorMsg` | string | 错误信息 | +| `result` | object | 更新结果 | #### 删除收信规则 @@ -1582,9 +1680,9 @@ Flags: | 字段 | 类型 | 说明 | |------|------|------| | `success` | bool | 删除是否成功 | -| `result` | object | 删除结果(实测为空对象 `{}`) | - -> 实测 `rule delete` 成功仅返回 `{result:{}, success}`,**不返回** `errorCode` / `errorMsg`。 +| `errorCode` | string | 错误码 | +| `errorMsg` | string | 错误信息 | +| `result` | object | 删除结果 | #### 调整收信规则排序 @@ -1607,9 +1705,9 @@ Flags: | 字段 | 类型 | 说明 | |------|------|------| | `success` | bool | 调整是否成功 | -| `result` | object | 调整结果(实测为空对象 `{}`) | - -> 实测 `rule adjust` 成功仅返回 `{result:{}, success}`,**不返回** `errorCode` / `errorMsg`。 +| `errorCode` | string | 错误码 | +| `errorMsg` | string | 错误消息 | +| `result` | object | 调整结果 | ## 通用错误说明 @@ -1622,17 +1720,12 @@ Flags: ## 意图判断 用户说"我的邮箱/邮箱地址" → `mailbox list`(**仅限查询自己的邮箱,不能查他人**) -用户说"邮箱详情/邮箱容量/邮箱别名/邮箱 profile" → `mailbox profile --email <邮箱>` 用户说"获取/查找/得到 某人的邮箱地址" → **不是 `mailbox list`**,走三路并发查询流程(见「查找他人邮箱地址」章节) 用户说"找邮件/搜邮件/查邮件" → `message search` 用户说"看邮件/打开邮件/邮件内容" → 先 `message search` 获取 messageId,再 `message get` -用户说"批量查看邮件详情/批量打开这些邮件/按多个邮件ID取详情" → `message batch-get --ids `(单次最多 20 个) 用户说"发邮件/写邮件" → 先 `mailbox list` 获取发件地址,再 `message send` 用户说“给(某人名字)发邮件” / “查询某人发给我的邮件” / “查询发给某人的邮件” / 任何涉及按人名查找邮箱的场景 → - **第一步**:并发同时发起以下三路查询,取最先返回有效邮箱的结果;若三路均无有效邮箱,ask_human 请用户提供,禁止臆测: - 1. `aisearch person --keyword <姓名>` → `contact user get --ids `,提取 `orgAuthEmail` - 2. `mail user search --email <当前邮箱> --keyword <姓名>`,提取 `users[].email`(仅企业邮箱可用) - 3. `contact user search --keyword <姓名>`,提取用户邮箱字段 + **第一步**:`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` @@ -1649,22 +1742,16 @@ Flags: 用户说"删除邮件标签/删除邮箱标签/删除 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 -用户说"批量标记邮件已读/未读/给邮件加标签/移除邮件标签" → `message batch-update --action markRead/markUnread/addTags/removeTags`(操作对象是邮件 ID;会话级操作用 `thread update/batch-update`) -用户说"邮件发出去了吗/查邮件发送状态/确认邮件是否发送成功/邮件投递结果" → 用发送类命令返回的 `internetMessageId`,调用 `message verify` 查询 `sendStatus` -用户说"撤回邮件/撤回已发送邮件" → `sent-message recall --email <邮箱> --id --subject "<主题>" --yes`(危险操作,先确认) -用户说"查询邮件撤回进度/撤回任务状态" → `sent-message recall-detail --email <邮箱> --id ` -用户说"设置/更新/关闭自动回复" → `auto-reply update`(所有字段必填,先 `auto-reply get` 获取当前配置) -用户说"收信白名单/添加白名单/移除白名单" → `allow-list list/add/remove` -用户说"收信黑名单/添加黑名单/移除黑名单" → `block-list list/add/remove` -用户说"查看会话/获取会话/看这封邮件的会话" → 先 `message search` 或 `message get` 获取邮件中的 `conversationId`,再 `thread get` 用户说"搜索/查找/联系 邮箱用户/联系人/某人的邮箱地址" → `user search`(搜索通讯录人员,不是搜邮件内容) 用户说"发送草稿/把草稿发出去/发这封草稿" → 先 `message search --query "folderId:5"` 找到草稿 messageId,再 `draft send` +用户说"邮件发出去了吗/查邮件发送状态/确认邮件是否发送成功/邮件投递结果" → 用发送类命令返回的 `internetMessageId`,调用 `message verify` 查询 `sendStatus` 用户说"翻页继续搜索联系人/通讯录" → `user search --cursor `(注意:不是 `message search`) **`user search` vs `message search` 关键区别:** -- `user search`:搜索的是**人**(通讯录联系人),入参是 `--keyword 姓名`,返回用户信息 +- `user search`:搜索的是**人**(通讯录联系人),入参是 `--keyword 姓名` 或 `--employee-no 工号`,返回用户信息 - `message search`:搜索的是**邮件**(邮件内容),入参是 `--query KQL表达式`,返回邮件列表 @@ -1719,8 +1806,8 @@ dws mail thread list --email user@company.com --folder --limit 10 --f # 1i. 修改或删除邮件会话 — 先通过 thread list 获取 conversationId dws mail thread update --email user@company.com --id --action markRead --format json dws mail thread batch-update --email user@company.com --ids , --action markUnread --format json -dws mail thread trash --email user@company.com --id --yes --format json -dws mail thread batch-trash --email user@company.com --ids , --yes --format json +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 \ @@ -1756,17 +1843,14 @@ dws mail attachment list --email user@company.com --id --format json dws mail attachment download --email user@company.com \ --message-id --attachment-id --name report.pdf --output ~/invoices/ -# 5. 获取邮件所属会话详情(thread) -# 步骤 5.1:先通过 message search 或 message get 获取邮件中的 conversationId +# 6. 获取邮件所属会话详情(thread) +# 步骤 6.1:先通过 message search 或 message get 获取邮件中的 conversationId dws mail message search --email user@company.com \ --query "subject:\"周报\"" --limit 5 --format json # 从返回的邮件列表中提取 conversationId 字段 -# 步骤 5.2:用 conversationId 获取会话详情 +# 步骤 6.2:用 conversationId 获取会话详情 dws mail thread get --email user@company.com --id --format json - -# 步骤 5.3(可选):同时返回会话内所有邮件列表 -dws mail thread get --email user@company.com --id --select messages --format json ``` ## 上下文传递表 @@ -1774,9 +1858,7 @@ dws mail thread get --email user@company.com --id --select mess | 操作 | 从返回中提取 | 用于 | |------|-------------|------| | `mailbox list` | 邮箱地址 | message search/get/send/thread get 的 --email/--from | -| `mailbox profile` | `emailAliases` / `mboxSize` / `mboxSizeUsed` | 展示邮箱别名与容量信息 | | `message search` | `messageId` | message get 的 --id | -| `message search` | `messageId` | message batch-get 的 --ids | | `message search` | `conversationId` | thread get 的 --id | | `message search` | `messageId` | attachment list 的 --id | | `attachment list` | `attachments[].id` / `attachments[].name` | attachment download 的 --attachment-id / --name | @@ -1785,14 +1867,12 @@ dws mail thread get --email user@company.com --id --select mess | `folder create` | `result.folder.id` | 后续创建子文件夹或移动邮件时作为 --folder;更新/删除该文件夹时作为 --id | | `folder list` | `folders[].id` | thread list 的 --folder | | `thread list` | `conversations[].id` | thread get/update/trash/batch-update/batch-trash 的 --id/--ids | -| `tag list` | `tags[].id` | message batch-update 的 --tags;thread update/batch-update 的 --tag-ids | +| `tag list` | `tags[].id` | 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(三路并发,取先到结果) | +| `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 | -| `sent-message recall` | `id` | sent-message recall-detail 的 --id | -| `auto-reply get` | `enabled` / `startTime` / `endTime` / `scope` / `content` | auto-reply update 的当前配置基线 | ## 注意事项 @@ -1800,17 +1880,10 @@ dws mail thread get --email user@company.com --id --select mess - `message search` 返回邮件 ID 和元信息(不含正文),需 `message get` 获取完整内容 - KQL 查询支持 AND/OR/NOT 组合,字段值含空格时需用双引号 - `--cc` 抄送人支持多人,逗号分隔 -- 收件人邮箱获取:用户只知道同事名字时,**并发**同时执行以下三路查询,取最先返回有效邮箱的结果,无需等待其他路完成: - 1. `dws aisearch person --keyword "名字" --dimension name` → `dws contact user get --ids `,提取 `orgAuthEmail` - 2. `dws mail user search --email <发件人邮箱> --keyword "名字"`,提取 `users[].email`(仅企业邮箱账号可调用,个人 @dingtalk.com 邮箱会报权限错误可忽略) - 3. `dws contact user search --keyword "名字"`,提取用户邮箱字段 - 若三路均无有效邮箱,必须 ask_human 请用户手动提供收件人邮箱,严禁臆测和假设 -- `thread get` 无法直接通过邮箱地址查询会话列表,**必须先有 conversationId**;conversationId 来自 `message search` 或 `message get` 返回的邮件字段 `conversationId` -- `thread get` 默认即返回会话内 `messages` 列表,但列表中每封邮件的正文(`body`)、收件人(`toRecipients`)等字段默认为 null;需在 `--select` 中额外指定才有值,如 `--select messages,internetMessageId`(多个字段用英文逗号分隔) -- `thread get` 返回的 `conversation` 不含 `flag` 字段,会话标识请从 `thread list` 获取 -- `user search` 仅支持企业邮箱(非 `@dingtalk.com` 个人邮箱),使用个人邮箱将因无权限报错;搜到的用户邮箱(`email` 字段)可直接用于 `message send` 的 `--to`/`--cc` 参数 +- 收件人邮箱获取:用户只知道同事名字时,先 `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 b8ad2a31..207f64f6 100644 --- a/skills/multi/dingtalk-mail/scripts/mail_send_with_cc.py +++ b/skills/multi/dingtalk-mail/scripts/mail_send_with_cc.py @@ -55,18 +55,6 @@ def validate_emails(emails_str: str) -> bool: return True -def unwrap_result(data: Any) -> Any: - while isinstance(data, dict): - for key in ('result', 'content', 'data'): - nested = data.get(key) - if isinstance(nested, (dict, list)): - data = nested - break - else: - return data - return data - - def get_my_email(dry_run: bool = False) -> Optional[str]: data = run_dws([ 'mail', 'mailbox', 'list', '--format', 'json', @@ -75,16 +63,6 @@ def get_my_email(dry_run: bool = False) -> Optional[str]: return '' if not data: return None - data = unwrap_result(data) - if isinstance(data, dict) and isinstance(data.get('emailAccounts'), list): - accounts = data['emailAccounts'] - # 优先企业邮箱(type=ORG),否则取第一个 - for acc in accounts: - if isinstance(acc, dict) and acc.get('type') == 'ORG' and acc.get('email'): - return acc['email'] - if accounts and isinstance(accounts[0], dict): - return accounts[0].get('email') - return None if isinstance(data, list) and data: item = data[0] return (item.get('email') or item.get('address') @@ -121,7 +99,7 @@ def main(): '--from', from_email or '', '--to', args.to, '--subject', args.subject, - '--content', args.body, + '--body', args.body, '--format', 'json', ] if args.cc: diff --git a/skills/multi/dingtalk-mail/scripts/mail_unread_summary.py b/skills/multi/dingtalk-mail/scripts/mail_unread_summary.py index 5588db97..7005d787 100644 --- a/skills/multi/dingtalk-mail/scripts/mail_unread_summary.py +++ b/skills/multi/dingtalk-mail/scripts/mail_unread_summary.py @@ -39,18 +39,6 @@ def run_dws( return None -def unwrap_result(data: Any) -> Any: - while isinstance(data, dict): - for key in ('result', 'content', 'data'): - nested = data.get(key) - if isinstance(nested, (dict, list)): - data = nested - break - else: - return data - return data - - def get_my_email(dry_run: bool = False) -> Optional[str]: data = run_dws([ 'mail', 'mailbox', 'list', '--format', 'json', @@ -59,16 +47,6 @@ def get_my_email(dry_run: bool = False) -> Optional[str]: return '' if not data: return None - data = unwrap_result(data) - if isinstance(data, dict) and isinstance(data.get('emailAccounts'), list): - accounts = data['emailAccounts'] - # 优先企业邮箱(type=ORG),否则取第一个 - for acc in accounts: - if isinstance(acc, dict) and acc.get('type') == 'ORG' and acc.get('email'): - return acc['email'] - if accounts and isinstance(accounts[0], dict): - return accounts[0].get('email') - return None if isinstance(data, list) and data: item = data[0] return (item.get('email') or item.get('address') @@ -102,7 +80,7 @@ def main(): 'mail', 'message', 'search', '--email', email or '', '--query', kql, - '--limit', str(args.size), + '--size', str(args.size), '--format', 'json', ], dry_run=args.dry_run) @@ -112,7 +90,6 @@ def main(): print('未查到邮件') return - data = unwrap_result(data) messages = (data if isinstance(data, list) else data.get('items', data.get('messages', []))) diff --git a/skills/multi/dingtalk-minutes/SKILL.md b/skills/multi/dingtalk-minutes/SKILL.md index f98151bf..c331d37a 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摘要/转写/关键字/听记标题/会后待办提取/分享听记。Distinct from dingtalk-calendar(日程)、dingtalk-report(日报)、dingtalk-doc(普通文档)。命令前缀:dws minutes。URL `shanji.dingtalk.com` 一定路由到此 skill。 -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,14 +11,9 @@ metadata: # 钉钉 AI 听记 Skill -> 🧪 **EXPERIMENTAL · 试验版 / Preview** — multi 模式当前未达 stable 标准。全部 dingtalk-* skill 已通过 dispatch verifier,但接口、命名、跨 skill 引用后续可能调整;生产 / 共享环境请优先使用 mono 模式(`dws skill setup --mode mono`)。问题请提 issue 反馈。 - -> **PREREQUISITE:** Read the `dws-shared` skill first for auth, global flags, product routing, URL preflight, error codes, and safety rules. The `dws` binary must be on PATH. - - - -> ⚠️ **命令可用性以当前 dws 二进制为准**。服务发现已下线,本文档随内置 skill 发布;如果 `dws --help` 不存在,说明当前版本未暴露该命令。若命令存在但调用失败,请按错误中的 endpoint 或 tool 提示确认静态端点目录和后端工具注册。实际调用前可用 `dws --help` 或 `--dry-run` 验证。 +## 前置条件 — 执行操作前必读 +> **CRITICAL — 执行任何 `dws` 操作前,MUST 先用 Read 工具完整读取 [`dws-shared`](../dws-shared/SKILL.md)。**该轻量文件包含全局执行契约、安全底线及 shared references 的按需加载导航;不要预加载其全部 references。 > 命令参考:[minutes.md](references/minutes.md);剧本:[07-minutes.md](references/07-minutes.md)。 @@ -46,11 +40,45 @@ metadata: | "查某段时间/最近/本周/上月的听记" | `dws minutes list all --start "" --end "" [--query "<关键词>"]` | | "看一篇听记摘要" | `dws minutes get summary --id ` | | "看转写 / 原文" | `dws minutes get transcription --id ` | +| "下载/导出最近一周听记原文/逐字稿" | `dws minutes list all --start "" --end ""` → 逐条 `dws minutes get transcription --id ` | | "近期听记摘要合并" | `python scripts/minutes_recent_summary.py --max 5` | | "提取会议待办" | `python scripts/minutes_extract_todos.py --id ` | | "改听记标题" | `dws minutes update title --id --title "<新标题>"` | -## 评测高频硬约束 +## 标准 SOP(必遵流程) + +> 命中以下意图**必须**按对应 SOP 顺序执行;**禁止**跳步、替换命令、编造 taskUuid。每条命令必须带 `--format json`。锁定目标**必须**按 `taskUuid + title + organizationName + 时间` 精准匹配,**禁止**凭标题相似度乱选。 + +### SOP-1 查听记列表(query-minutes) + +**触发**:查听记/会议记录/某会议的纪要/按关键词或时间找听记。 + +1. **选 scope(必须·铁律)**:`mine`=我创建/发起的;`shared`=他人共享给我的;`all`=我可访问的全部(含 mine+shared)。用户说"我能访问/可见/所有/我的听记"等覆盖语义**一律 `all`**;只有明确"我创建的/我发起的/我录的"才用 `mine`。 +2. **执行(必须)**:`dws minutes list all|mine|shared --format json`;按关键词加 `--query "<关键词>"`,按时间加 `--start "" --end ""`,限制条数 `--limit `(`--max` 兼容别名),翻页用 `--cursor `(`--next-token` 兼容别名)。 +3. **解析(必须)**:从 `itemList[]` 取真实 `taskUuid` + `title` + 时间;多候选必须让用户确认,**禁止**默认取第一条。 + +**禁止**:把 `mine` 当全量、凭标题相似度锁定、跳过 `--format json`。 + +### SOP-2 取听记详情(get-minute-detail) + +**触发**:要摘要/转写原文/关键词/待办/基础信息/音频。 + +1. **前置(必须)**:先按 SOP-1 拿到目标 `taskUuid`。 +2. **执行(必须·按需选一)**:摘要 `dws minutes get summary --id `;转写原文 `dws minutes get transcription --id `(返回 `cursor`/`nextToken` 时**必须**继续 `--cursor` 翻页拉全);关键词 `get keywords`;待办 `get todos`;基础信息 `get info`;音频地址 `get audio`;批量 `get batch --ids `。全部带 `--format json`。 +3. **解析(必须)**:`--id`/`--uuid`/`--task-uuid` 三者等价,**推荐统一 `--id`**;转写/摘要按用户需要抽取,**禁止**无差别全文贴出。 + +**禁止**:编造 taskUuid、转写有分页不翻完就总结、跳过 SOP-1 直接猜 ID。 + +### SOP-3 听记转文档 / 待办回写(handoff) + +**触发**:把会议纪要转成文档/把听记待办建到待办系统。 + +1. **转文档(必须)**:取 `minutes get summary`/`transcription` 内容 → 切 `dingtalk-doc` 用 `dws doc create --content-file ` 落盘。 +2. **待办回写(必须)**:取 `minutes get todos` 的待办项 → 切 `dingtalk-todo` 按 SOP-1 解析执行者 userId 后 `todo task create`。 + +**禁止**:在 minutes 内直接写文档/建待办(应切对应 skill)。 + +## 高频硬约束 - `shanji.dingtalk.com` URL 必须走 `dws minutes`,禁止用浏览器或 `read_file` 打开链接。自动提取 taskUuid 后调用 `get info/summary/transcription/todos`。 - 用户给了时间线索(今天、本周、上周、上月、最近 N 天、某日期范围)时,必须自行计算 `--start` / `--end`,格式用 ISO-8601,如 `2026-05-11T00:00:00+08:00`。不要反问用户时间范围。 @@ -58,10 +86,14 @@ metadata: - 不要全量拉取后本地过滤时间。时间范围和关键词能服务端过滤时必须放进同一条 `list all --start --end --query`。 - 列表为空时按顺序兜底:同范围 `list all` → 去掉关键词但保留时间范围 → 明确告知无数据。禁止用模板或虚构听记内容继续生成纪要/周报。 - 生成纪要、文档、待办、周报前,必须先完成 `list` → 选定真实 `taskUuid` → `get summary`;需要原文或行动项时继续 `get transcription` / `get todos`。前置数据没拿到就停止并说明卡点。 +- 用户明确说"下载/导出/文件"且对象是"听记原文/逐字稿/转写/完整记录"时,不能降级为摘要汇总:必须 `list all --start --end` 找到范围内听记;列表为空则明确无数据;列表非空则对每条听记逐一 `get transcription` 并翻页到结束,交付内容必须包含真实转写段落。可附标题、时间等元数据,但不能只给 summary,也不能要求用户再指定某一条。 - 所有 dws 命令带 `--format json`,不要用 shell 管道、重定向、`head`、`grep`、`jq`。 ## 跨产品协作 -- 提取的待办批量建任务 → 切到 `dingtalk-todo`(`scripts/todo_batch_create.py`) +- 提取的待办批量建任务 → 切到 `dingtalk-todo`,按其批量创建 SOP 执行;批量脚本仅在 `dingtalk-todo` sub-skill 内可用,未切换前不要在当前 skill 运行。 - 摘要发给同事 → 切到 `dingtalk-chat` - 日程 / 会议室 → 切到 `dingtalk-calendar` +## 局部意图与短流程 + +- [局部意图消歧](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 6a540e57..54534645 100644 --- a/skills/multi/dingtalk-minutes/references/07-minutes.md +++ b/skills/multi/dingtalk-minutes/references/07-minutes.md @@ -1,11 +1,142 @@ # 听记与会后 -> lite(`list-minutes`、`get-minutes-summary`、`get-minutes-transcription`)见本 skill 的 [SKILL.md](../SKILL.md)。full recipe 见下表。 -> 日程、订会议室、`schedule-meeting` 在 `dingtalk-calendar` skill 的 03-meeting.md。产品命令见 [minutes.md](./minutes.md)。 +> lite(`minutes-query`)见 [lite-recipes.md](./lite-recipes.md)。full recipe 见下表。 +> 日程、订会议室、`schedule-meeting` 见 `dingtalk-calendar/references/03-meeting.md`。产品命令见 [minutes.md](./minutes.md)。 -| Recipe | 行动指南(固定路线) | -| ---------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| meeting-followup | 1. **提取待办优先**:`python scripts/minutes_extract_todos.py [--id ] [--max N]` → 获取行动项列表 无脚本时按「多源并行采集」(见 dws-shared 的 conventions.md)执行 2. 提取行动项 3. `aisearch person --keyword "<姓名>" --dimension name` → 取 `userId` → `todo task create --title "<行动项>" --executors `(每条行动项;批量时通过 `dingtalk-todo` skill:`python scripts/todo_batch_create.py todos.json`,或循环 `dws todo task create`) 4. `chat message send --group --text "<通知内容>"` 通知已创建 | -| share-minutes | **拉摘要优先**:`python scripts/minutes_recent_summary.py [--max N] [--output summary.md]` → 获取近期听记摘要 备选手动:1. `minutes list mine` → 取 `taskUuid` 2. 用户选定 3. `minutes get summary --id ` → 取摘要 4. 单聊:`contact user search --query "<姓名>"` → 取 `openDingTalkId` → `chat message send --open-dingtalk-id --text "<摘要内容>"`(推荐);群聊:`--group --text "<摘要内容>"` 发送。仅当无法获取 openDingTalkId 时才用 `--user `(备选) | -| browse-minutes | 1. `minutes list all --query "<关键词>" --max ` → 取全量 `taskUuid`(翻页直至无更多;无主题筛选用 `minutes list mine`) 2. **详情/元数据优先** `minutes get batch --ids `(仅 API 上限时拆多批);**摘要专项**且无 batch 字段时再用并行 `get summary` 或 `**python scripts/minutes_recent_summary.py --max `** 3. 汇总展示;**同一用户诉求下避免**「半截 list → 再 list 剩余」「拆 4 条一批 summary 连跑多轮」等无必要拆分 | -| minutes-detail | 1. 确定 `taskUuid`:用户已提供 → 直接用;未提供 → `minutes list mine --max 5` 让用户选 2. 依次拉取四维信息(**逐条串行执行,禁止用 `&` 后台与 `wait`——违反 Shell 安全铁律,Windows cmd 下会命令错乱**):`minutes get info --id `、`minutes get summary --id `、`minutes get keywords --id `、`minutes get todos --id ` 3. 整合输出:基础信息(标题、时长、开始时间;get info 不返回参会人)→ AI 摘要 → 关键字 → 行动项/待办 4.(可选)用户要求看原文 → `minutes get transcription --id ` | +### 听记列表参数速查 + +`list mine` / `list shared` / `list all` 均支持以下筛选参数(**有时间/关键词条件时优先使用服务端过滤;无筛选条件时可全量拉取**): + +| 参数 | 说明 | 示例 | +|------|------|------| +| `--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 --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 +``` + +| 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) | + +--- + +### 听记场景执行规范(避坑指南) + +> 以下规范基于历史高频失败模式总结,**必须严格遵守**。 + +#### 0. 选对象铁律(命令对了但选错听记 = 整任务失败,0605 P2 EDD badcase 提炼) + +> 这是历史任务中**最致命**的一类失败:命令写得完全正确,但 list/搜索拿到结果后**选错了听记对象**,导致后续所有 `get summary` / `get keywords` / `update` 全部作用在错误条目上。详见 [minutes.md](./minutes.md)「选对象铁律 S1~S6」,速记如下: + +| 铁律 | 场景 | 正确做法 | 真实 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 +> ``` + +#### 1. 空数据场景处理(最高频失败原因) + +`minutes list` 返回空列表时的正确行为: + +``` +正确做法: +1. 调用 dws minutes list(mine/shared/all 均尝试) +2. 若全部返回空 → 明确告知用户「当前没有找到听记记录」 +3. 给出替代建议(如:检查是否有进行中的会议、确认听记权限等) + +错误做法: +- 沉默放弃,不给用户任何反馈 +- 反问用户「你有听记吗?」而不先主动查询 +- 在无数据时仍尝试调用 get summary / get transcription(会因缺少 taskUuid 报错) +``` + +#### 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 new file mode 100644 index 00000000..671041c6 --- /dev/null +++ b/skills/multi/dingtalk-minutes/references/10-minutes-speaker-match.md @@ -0,0 +1,145 @@ +# 听记发言人智能匹配 + +> 从钉钉听记中定向提取并总结指定人物的发言内容。 +> 听记产品命令见 [minutes.md](./minutes.md);跨产品检索/消息/文档能力按需加载 `dingtalk-aisearch`、`dingtalk-chat`、`dingtalk-doc`。 + +| Recipe | 行动指南(固定路线) | +| ------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| minutes-speaker-summarize | 1. **读取转写**:有 URL → 提取 `taskUuid` → `minutes get transcription --id `;有时间/关键词 → `minutes list all` 筛选 → 获取转写;无信息 → 必须询问 2. **声纹标注检查**:已标注真实姓名 → 直接跳 Step 6;仅匿名编号(发言人1/2/3)→ 继续 3. **转写原文推断**:利用称呼、自我介绍、上下文指代、发言顺序推断,高置信度 → 跳 Step 6;无法确定 → 继续 4. **并发身份推断**:`aisearch person --keyword "<姓名>" --dimension name` & `chat message list` 取近期 IM & `drive search --query "<姓名>"` 前 3 篇 `& wait`;两路以上一致才下结论 5. **置信度判断**:>70% → 直接输出;≤70% → 展示 TOP3 候选音频片段让用户选(最多一次,"都不是"则终止) 6. **结构化总结输出**:核心观点 + 问题/关注点 + Action Item + 立场态度;输出后追问是否替换发言人标注 | + +--- + +## 触发条件 + +**TRIGGER** when ANY of the following are true: +- 用户提供了听记来源(URL / 时间 / 关键词可定位)**且**指定了一个具体人名(花名/真名/职位称谓均可),目标是获取该人在会议中说了什么 +- 用户从 `minutes-detail` 的发言人列表中选定某位发言人,要求查看其内容总结 +- 用户贴了听记链接,发言人未完全识别(存在匿名编号),需要做批量智能匹配 + +**DO NOT TRIGGER** when: +- 用户只要整体纪要,未指定特定人物 → 用 `minutes get summary` +- 用户想纠错发言人标注 → 告知在听记详情页 GUI 操作 +- 用户想提取待办 → `meeting-followup` recipe + +--- + +## 详细步骤 + +### Step 1:读取听记转写 + +- 有 URL → 从 URL 提取 `taskUuid` → `dws minutes get transcription --id ` +- 有时间/关键词 → `dws minutes list all` 筛选 → 获取转写 +- 什么都没给 → **必须询问** + +### Step 2:声纹标注检查 + +- **已标注真实姓名** → 直接跳 Step 6 +- **仅有匿名编号**(发言人1/2/3)→ 继续 Step 3 + +### Step 3:转写原文推断(优先) + +充分利用转写文本中的称呼、自我介绍、上下文指代、发言顺序、内容特征进行推断。能从原文高置信度确定发言人 → 直接跳 Step 6。无法确定 → 继续 Step 4。 + +### Step 4:并发身份推断 + +**同时**发起多路查询,不串行等待: + +| 优先级 | 路径 | 做什么 | +| ------ | ------------------ | ---------------------------------------------------------------------------------------------------- | +| ① 最高 | 日程参与人 | 从听记 info 提取会议时间 → `dws calendar event list --start <会议开始-30min> --end <会议结束+30min>` → 匹配同时段日程 → `dws calendar participant list --event ` → 获取参与人姓名列表,与转写中匿名发言人做数量/顺序对照 | +| ② 高 | 人员 + 组织架构 | `dws aisearch person --keyword <姓名> --dimension name --format json` → userId,再 `dws contact user get --ids ` 补详情 | +| ③ 高 | 聊天记录 | `dws chat message list` 获取与目标人的近期 IM 消息 → 工作内容/语言风格/职责线索 | +| ④ 兜底 | 本人文档 | `dws drive search --query <姓名>` 取前 3 篇标题+摘要 → 角色精确信号 | + +**日程参与人匹配规则**(路径 ① 专用): +- 日程参与人数 = 转写发言人数 → 高置信度,按发言顺序/内容特征一一对应 +- 日程参与人数 > 转写发言人数 → 有人未发言,需结合角色推断缩小范围 +- 日程参与人数 < 转写发言人数 → 有人未在日程中,需补充其他路径 +- **未找到同时段日程** → 主动引导用户:「未找到与该听记时间匹配的日程,是否可以提供会议对应的日程链接或参会人员名单?这将帮助更准确地识别发言人」 + +**判定规则**: +- 日程参与人 + 通讯录两路信号一致 → 确认身份,置信度最高 +- 通讯录 + 聊天两路信号一致 → 确认角色,置信度高,通常无需再查文档 +- 仅一路可用或信号矛盾 → 补查文档兜底,两路以上一致才下结论 + +> 部门名 ≠ 角色,不能用「产品设计部」直接推断是产品设计师;聊天记录往往最直接反映日常工作内容。 + +**文档类型与角色对照**(仅在文档兜底时参考): +- PRD/需求文档/商业化方案 → 产品经理 +- 视觉规范/原型说明/交互评审 → 产品设计师 +- 技术方案/架构设计 → 研发工程师 +- 分析报告/底表说明 → 数据分析师 + +### Step 5:置信度分支 + +基于 Step 4 角色,在转写中匹配发言模式(产品经理提需求/定优先级,研发讲技术约束,管理者高占比做决策,设计师讲交互细节)。 + +#### 分支 A:置信度 > 70% → 直接输出总结 + +无需任何确认,直接进入 Step 6。 + +#### 分支 B:置信度 ≤ 70% → TOP3 候选音频(仅一次) + +列出置信度最高的 TOP3 候选发言人(必须来自不同发言人,不重复),每人附一段代表性转写片段: + +> 以下是本次会议中可能是 [人名] 的候选,请确认: +> +> **候选 1**(置信度最高) +> 对应文字:"[转写文本片段]" +> +> **候选 2** +> 对应文字:"[转写文本片段]" +> +> **候选 3** +> 对应文字:"[转写文本片段]" +> +> 请问 [人名] 是哪一位?(1 / 2 / 3 / 都不是) + +- 用户选 1/2/3 → 进入 Step 6 +- 用户说"都不是" → 告知无法确认,建议在听记详情页手动标注,**不再重复** + +### Step 6:总结输出 + +提取该发言人的全部发言,结合会议上下文综合总结: + +``` +[人名] 在本次会议中的发言总结 + +**核心观点** +- 观点1 +- 观点2 + +**提出的问题 / 关注点**(如有) +- ... + +**Action Item / 承诺事项**(如有) +- ... + +**立场 / 态度**(如有明确倾向) +- ... +``` + +输出完成后追问: + +> 要在本篇听记里把「发言人 X」替换成「[人名]」吗?替换后系统会记住声纹特征,下次会议自动识别。[替换] [暂不] + +--- + +## 核心约束 + +- **禁止暴露 Speaker_X 编号**:追问中用「发言人 X」等用户可感知的描述,不暴露内部格式 +- **禁止碰运气播音频**:必须先做身份预推断,再定向选片段 +- **声纹库命中时直接输出**:不做任何额外确认 +- **置信度 > 70% 直接输出**:不需要确认 +- **音频确认最多一次**:用户说"都不是"则终止,不重复 +- **先完成任务,后引导替换**:总结输出完成后再追问替换,不打断主流程 + +--- + +## 常见陷阱 + +- 部门名 ≠ 角色:不能用部门名称代替角色判断 +- 聊天记录是快速判断工作内容的捷径,优先于文档 +- 单篇文档不够:至少看 3 篇,单篇可能是跨角色协作产出 +- 访谈记录 PM 和 UXR 都会写,不能单独判断角色 +- TOP3 候选必须来自不同发言人,展示前做去重检查 diff --git a/skills/multi/dingtalk-minutes/references/11-minutes-speaker-correct.md b/skills/multi/dingtalk-minutes/references/11-minutes-speaker-correct.md new file mode 100644 index 00000000..0d21e734 --- /dev/null +++ b/skills/multi/dingtalk-minutes/references/11-minutes-speaker-correct.md @@ -0,0 +1,152 @@ +# 听记发言人识别与标注 (speaker-correct) + +> 触发条件:用户提供了一篇钉钉听记(URL 或 ID),表达了识别/标注/替换发言人的意图。即使用户只说"帮我标注发言人"也应触发本技能。 + +## 执行流程 + +### Step 1:查同时段日程,获取参会人名单 + +1. 从听记元数据获取录音**开始时间**(精确到分钟)。 +2. 调用日历接口查询该时间点前后 30 分钟内的日程:`dws calendar event list --start --end --format json` +3. 若匹配到日程,提取参会人名单(姓名/花名),输出: + > 根据录音时间,匹配到日程「{日程名称}」,参会人为:{姓名1}、{姓名2}、{姓名3}... + > 未识别的发言人大概率来自以上名单。 +4. 未匹配到日程 → 跳过,直接进入 Step 2。 + +### Step 2:展示发言人状态表格 + +列出所有发言人及其当前标注状态: + +| # | 当前标注 | 状态 | +|---|---------|------| +| 1 | 张三 | 已识别 | +| 2 | 发言人 2 | 未标注 | +| 3 | 发言人 3 | 未标注 | +| 4 | 发言人 4 | 未标注,发言较少 | + +### Step 3:让用户选择识别方式 + +``` +你希望如何识别未标注的发言人? + +A. 听音识人 — 为每位未识别发言人裁剪一段音频片段,你听后辨认是谁 +B. 手动设置 — 直接告诉我对应关系,如「发言人2:王五, 发言人3:赵六」 +C. 智能匹配 — 提供参会人名单或其他信息,我来尝试推断 +``` + +**等待用户选择后再执行后续操作,不提前切片。** + +--- + +#### 选项 A:听音识人(用户选择后执行) + +为每位未标注发言人裁剪一段代表性音频片段。 + +**片段选择策略:** + +1. **语义质量过滤**(先排除低质量段落): + - 排除含明显口吃/重复词的段落(同一字/词连续出现 2 次及以上,如"你你你"、"跨跨跨") + - 排除文本字数 < 8 字的段落 + - 排除纯单字/双字回应("嗯"、"对"、"是"等) + - 排除句意明显不完整的段落(以"就是"、"然后"、"比如说"等连接词结尾) + +2. **综合评分排序**,取得分最高的段落: + - **评分公式**:`score = 时长(秒) x 语义完整度系数` + - 语义完整度系数:`1.0`(句意完整)/ `0.8`(略显口语化)/ `0.5`(不完整,一般已被过滤) + - 两段得分相差 <= 20% 时,优先选语义系数更高的 + - 截取时长 = `min(该段时长, 10秒)`,**不足 5 秒的片段跳过** + +3. **兜底逻辑(发言较少时)**:若过滤后无满足条件的单段,执行**片段合并**: + - 收集该发言人所有时长 >= 1 秒的片段(按时间顺序),用 ffmpeg `-filter_complex` 合并为一个文件 + - 告知用户:「发言人 X 发言较少,已将所有片段拼接在一起,方便你听取辨认。」 + - 若合并后总时长仍不足 3 秒,放弃,告知用户:「发言人 X 发言太少,建议结合参会人名单自行标注。」 + +**执行命令示例:** +```bash +ffmpeg -y -ss 202 -i audio.mp3 -t 8 -c copy tmp/发言人2_片段.mp3 -loglevel error +ffmpeg -y -ss 465 -i audio.mp3 -t 10 -c copy tmp/发言人3_片段.mp3 -loglevel error +``` + +**输出文件命名:** `tmp/发言人{编号}_片段.mp3` + +切片完成后,通过钉钉单聊**逐个发送**音频文件给用户(使用 `dws` 技能发送文件消息),不输出表格、不输出下载链接。全部发送完毕后,仅用一句话提示用户听完后回复对应关系,例如:「以上是各发言人的音频片段,听完后告诉我对应关系,如「发言人2:王五, 发言人3:赵六」。」 + +#### 选项 B:手动设置(用户选择后执行) + +直接等待用户输入对应关系(如「发言人2:王五, 发言人3:赵六」),收到后进入 Step 4 批量替换。 + +#### 选项 C:智能匹配(用户选择后执行) + +若 Step 1 已获取参会人名单,结合发言内容特征尝试推断;否则请用户补充名单后推断。 + +**查询参会人名单信息——同时发起三路查询,不串行等待:** + +| 优先级 | 路径 | 做什么 | +|--------|------|--------| +| ① 最高 | 人员 + 组织架构 | `dws aisearch person --keyword <姓名> --dimension name` → userId,再 `dws contact user get --ids ` 补部门/职级等详情 | +| ② 高 | 聊天记录 | `dws chat message list` 获取与目标人的近期 IM 消息 → 工作内容/语言风格/职责线索 | +| ③ 兜底 | 本人文档 | `dws drive search --query <姓名>` 取前 3 篇标题+摘要 → 角色精确信号 | + +**判定规则:** +- 通讯录 + 聊天两路信号一致 → 确认角色,置信度高,通常无需再查文档 +- 仅一路可用或信号矛盾 → 补查文档兜底,两路以上一致才下结论 + +> 部门名 ≠ 角色,不能用「产品设计部」直接推断是产品设计师;聊天记录往往最直接反映日常工作内容。 + +**文档类型与角色对照**(仅在文档兜底时参考): +- PRD / 需求文档 / 商业化方案 → 产品经理 +- 视觉规范 / 原型说明 / 交互评审 → 产品设计师 +- 技术方案 / 架构设计 → 研发工程师 +- 分析报告 / 底表说明 → 数据分析师 + +**置信度分析与输出:** + +基于上述信号,在转写中匹配发言模式(产品经理提需求/定优先级,研发讲技术约束,管理者高占比做决策,设计师讲交互细节): + +- **分支 A:置信度 > 70%** → 输出匹配结果,进入 Step 4 执行替换 +- **分支 B:置信度 < 70%** → 不输出结果,请用户补充信息或改用「听音识人 / 手动设置」 + +--- + +### Step 4:通讯录查询 + 执行替换 + +确认对应关系后,**必须先查询通讯录获取 dingUid**: + +1. 调用 `dws aisearch person --keyword "<姓名>" --dimension name --format json` +2. 从返回结果提取 `userId`(长整型数字,即 dingUid) +3. 根据匹配结果执行替换: + +| 情况 | 处理 | +|------|------| +| 唯一匹配(如 userId=123456789) | `dws minutes speaker replace --id --from "发言人X" --to "<姓名>" --target-uid 123456789 --format json` | +| 多个匹配 | 列出候选(姓名 + 部门 + userId)让用户选择后执行 | +| 无匹配 | 提示通讯录未找到,执行不带 `--target-uid` 的替换:`dws minutes speaker replace --id --from "发言人X" --to "<姓名>" --format json` | + +### Step 5:追问 + +替换成功后: +> 还有其他发言人需要我帮你识别和替换吗? + +--- + +## 注意事项 + +- **静默执行,绝不透出中间过程**:从用户选择「听音识人」到钉钉发送完毕,所有操作均静默完成。严禁向用户输出任何中间状态,包括:下载进度("音频下载完成")、裁剪阶段说明("开始裁剪"、"裁剪完成,现在发送")、工具执行统计("已运行 X 条命令"、"编辑 X 个文件")、ffmpeg 命令/评分逻辑/时间戳/选取依据,以及一切"正在......"/"现在......"/"接下来......"的过程描述。全部音频发送完毕后,只说一句话请用户回复对应关系 +- **不输出表格和链接**:不向用户展示音频文件列表表格、不输出任何下载链接,音频一律通过钉钉单聊文件消息直接发送 +- **不做身份画像分析**:不提供语言风格、决策权等文字画像 +- **串行处理**:逐条执行裁剪命令,不使用 shell `&` 并行 +- **全已识别情况**:若所有发言人均已识别,直接告知「所有发言人已识别,无需修改」 +- **人员搜索必跑**:替换前必须调用 `dws aisearch person` 获取 userId,严禁跳过 +- **依赖工具**:需要 `ffmpeg` 可用,以及能够访问听记原始音频的权限(通过 `dws` 技能获取) + +## 异常处理 + +| 情况 | 处理方式 | +|------|----------| +| 无法获取原始音频 | 告知权限不足或链接失效,建议用户本地提供音频文件或选择手动设置 | +| 未匹配到日程 | 跳过参会人提示,直接展示发言人表格 | +| 通讯录查不到目标人 | 执行不带 `--target-uid` 的替换,告知用户仅修改显示名 | +| 未找到未识别发言人 | 告知用户所有发言人均已识别 | +| speaker replace 报权限错误 | 告知用户需听记创建者/协作者权限,引导联系原作者 | +| ffmpeg 不可用 | 告知用户环境缺少 ffmpeg,无法执行切片,建议选择手动设置或智能匹配 | +| 切片失败 | 告知具体哪位发言人切片失败,重试或跳过该发言人 | diff --git a/skills/multi/dingtalk-minutes/references/_common/conventions.md b/skills/multi/dingtalk-minutes/references/_common/conventions.md new file mode 100644 index 00000000..803212b8 --- /dev/null +++ b/skills/multi/dingtalk-minutes/references/_common/conventions.md @@ -0,0 +1,44 @@ +# 业务域通用规范 + +> 仅服务本 skill 已迁入的行动指南。安全门控、危险操作确认、`--format json` 等已在本 skill 的 `SKILL.md` 中定义,此处不重复。 + +## 批量查询规范 + +| # | 规范 | +|---|------| +| 1 | **并行查详情**:拿到多个 ID 后,用 `&` 合并到同一条 Shell 命令并行执行 + `wait`,**严禁逐条串行** | +| 2 | **翻页**:分页接口须拉全直至无更多 | +| 3 | **优先批量 API**:有批量接口则用批量;无则按 #1 并行 | +| 4 | **群消息**:必须先 `chat search --query` 得 `openConversationId`,再 `chat message list --group --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-minutes/references/intent-guide.md b/skills/multi/dingtalk-minutes/references/intent-guide.md new file mode 100644 index 00000000..47c7b2a1 --- /dev/null +++ b/skills/multi/dingtalk-minutes/references/intent-guide.md @@ -0,0 +1,7 @@ +# minutes 局部意图消歧 + +本文件从单 Skill `intent-guide.md` 拆分而来,仅保留与本产品相关的跨产品消歧规则。 + +| 用户说... | 真实意图 | 应该用 | 不要用 | 理由 | +|---|---|---|---|---| +| "把最近几次关于XX的会议汇总成报告" | 按主题汇总多次听记 | #5 generate-topic-report | #7 meeting-followup | #7 是单次会议听记跟进;多次会议按主题汇总属于工作汇报 | diff --git a/skills/multi/dingtalk-minutes/references/lite-recipes.md b/skills/multi/dingtalk-minutes/references/lite-recipes.md new file mode 100644 index 00000000..c5ffd820 --- /dev/null +++ b/skills/multi/dingtalk-minutes/references/lite-recipes.md @@ -0,0 +1,136 @@ +# minutes Lite Recipe + +本文件从单 Skill `lite-recipes.md` 拆分而来,仅保留与本产品相关的轻量流程。 + +## #7 听记与会后 + +> 产品命令完整参考见 [minutes.md](./minutes.md)。full recipe 见 [07-minutes.md](../../dingtalk-minutes/references/07-minutes.md)。 + +### minutes-query(查询与获取) + +> **scope 选择铁律(P2 真实 badcase)**:`list` 后的 scope 决定查询范围,最高频误判是把"我能访问的所有听记"错选成 `mine`: +> - `mine` = **仅我自己创建/发起**的听记(范围最窄) +> - `shared` = **仅他人共享给我**的听记 +> - `all` = **我可访问的全部**(= mine ∪ shared,范围最广) +> - **判定口诀**:query 含"访问/权限/可见/能看到/所有/我的"等覆盖范围语义 → 一律走 `all`;**仅当**明确说"我创建的/我发起的/我录的" → 才走 `mine`。**不要因为句子里有"我"字就退化成 `mine`。** +> - 错误:`我能访问的所有听记` → `list mine`(漏掉共享给我的,判定不通过) +> - 正确:`我能访问的所有听记` → `dws minutes list all --format json` + +> **选对象铁律(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` 跳过定位。 + +**列表查询**(`list` 后**必须**跟 scope:`mine`/`shared`/`all`,默认补 `all`): + +```bash +# 我可访问的所有听记(默认) +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 +``` + +| 参数 | 说明 | +|------|------| +| `--query "<关键词>"` | 服务端关键词搜索 | +| `--start ""` | 开始时间 | +| `--end ""` | 结束时间 | +| `--limit ` | 每页条数,默认 10(`--max` 为兼容别名) | +| `--cursor ""` | 分页 token,首页留空(`--next-token` 为兼容别名) | + +**获取详情**: + +- 批量基础信息:`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.md b/skills/multi/dingtalk-minutes/references/minutes.md index cc4c5a8c..7a8c9b19 100644 --- a/skills/multi/dingtalk-minutes/references/minutes.md +++ b/skills/multi/dingtalk-minutes/references/minutes.md @@ -7,8 +7,8 @@ minutes 模块的命令是**两级或三级结构**,不同层级之间不能 > **默认 scope 规则**:`dws minutes list` 后**必须**跟 scope 子命令。若用户未明确指定查询范围,**一律默认补 `all`**(查询我可访问的所有听记 = 我创建的 + 他人共享给我的,覆盖面最广)。 > > **`list` 不带 scope 的陷阱(0519 P2 Golden Case 提炼)**: -> - 裸 `dws minutes list`(不跟 mine/shared/all)**不会返回任何听记数据**——它只打印该命令的帮助/用法信息(Usage、Available Commands: all/mine/shared、Flags),等于什么都没查到 -> - 必须带 scope 子命令(`dws minutes list all` / `list mine` / `list shared`)才会真正返回听记列表 +> - 裸 `dws minutes list`(不跟 mine/shared/all)虽然不会报错,但返回结果**不完整**(仅返回最近少量条目,约 913 字节) +> - `dws minutes list all` 才能返回完整列表(约 3362 字节) > - **AI 严禁使用裸 `dws minutes list`**,必须始终带 scope 子命令 > - 如果 LLM 不确定用哪个 scope,**一律用 `all`** > @@ -18,6 +18,15 @@ minutes 模块的命令是**两级或三级结构**,不同层级之间不能 > - `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 @@ -26,7 +35,7 @@ dws minutes │ ├── shared [--query] [--start] [--end] [--max] [--next-token] # 仅查询他人共享给我的听记(不含我自己创建的) │ └── all [--query] [--start] [--end] [--max] [--next-token] # 查询我可访问的所有听记(= mine ∪ shared,覆盖面最广,默认 scope) ├── get # 获取听记详情(get 后必须跟子命令名) -│ ├── info --id # 获取听记基础信息(标题、时长、开始时间、taskUuid、访问链接;不含参会人) +│ ├── info --id # 获取听记基础信息(标题、时长、参与人、状态等) │ ├── summary --id # 获取听记 AI 摘要(结构化纪要) │ ├── transcription --id # 获取听记语音转写原文(逐字稿,支持分页) │ ├── keywords --id # 获取听记关键词 @@ -57,9 +66,12 @@ dws minutes │ ├── 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 # 移除成员权限 +├── 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 提炼):** @@ -69,15 +81,15 @@ dws minutes | `dws minutes info --id ` | `info` 不是顶层子命令,应在 `get` 下 | `dws minutes get info --id ` | | `dws minutes summary --id ` | `summary` 不是顶层子命令,应在 `get` 下 | `dws minutes get summary --id ` | | `dws minutes transcription --id ` | `transcription` 不是顶层子命令,应在 `get` 下 | `dws minutes get transcription --id ` | -| `dws minutes get --uuid ` | `get` 后缺少子命令(info/summary/transcription 等) | `dws minutes get summary --id ` | -| `dws minutes get --task-uuid ` | 同上,`get` 后缺子命令。注意:`--task-uuid` 在子命令层是合法别名,但 `get` 层级不认识它 | `dws minutes get info --id ` | +| `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 ` | -| `dws minutes list --start "2026-04-01"` | `list` 后缺少 scope(mine/shared/all),**缺省时默认补 `all`** | `dws minutes list all --start "2026-04-01"` | +| `minutes list --start "2026-04-01"` | `list` 后缺少 scope(mine/shared/all),**缺省时默认补 `all`** | `dws minutes list all --start "2026-04-01"` | | `dws minutes list all --start-time "..."` | 参数名是 `--start` 不是 `--start-time` | `dws minutes list all --start "2026-04-01"` | | `dws minutes list all --end-time "..."` | 参数名是 `--end` 不是 `--end-time` | `dws minutes list all --end "2026-04-30"` | -| `dws minutes list --page-size 10` | `--page-size` 不存在,分页用 `--max`;且 `list` 后缺 scope | `dws minutes list mine --max 10` | -| `dws minutes list --date-range 2026-05-04 2026-05-10` | `--date-range` 不存在,时间范围用 `--start` + `--end` 两个参数;且 `list` 后缺 scope | `dws minutes list mine --start "2026-05-04T00:00:00+08:00" --end "2026-05-10T23:59:59+08:00"` | -| `dws minutes list mine --limit 10` | 不报错——`--limit` 已注册为 `--max` 的合法别名,两者完全等价(`--limit 3` 与 `--max 3` 返回相同条数) | `dws minutes list mine --max 10`(或 `--limit 10`,二选一即可) | +| `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 在内存中截断处理) | @@ -86,11 +98,11 @@ dws minutes | `dws minutes get transcription --id ` 只调用一次就"总结整篇听记" | **单次调用最多返回 50 段**,不翻页就只能看到前 1/3~1/8 的内容 | **必须自动循环翻页**:检查返回中的 `nextToken`,非空则继续调用 `--next-token `,直到拉完(详见「转写接口分页机制」) | | `dws minutes get summary --id A & dws minutes get summary --id B & wait` | Windows cmd 下 `&` 是命令分隔符不是 background,`wait` 不存在 | 逐条串行调用,或让 AI 在内存中合并结果;**严禁使用 shell 并行/管道/重定向** | | `dws minutes get transcription --id --next-token ` 报 unknown flag | **不会报错**——`--next-token` 是 `get transcription` 的合法参数(见命令总览) | 确保写法正确:`dws minutes get transcription --id --next-token --format json` | -| `dws minutes get transcription --id --page-token ` | `--page-token` 不存在,正确参数名是 `--next-token` | `dws minutes get transcription --id --next-token ` | +| `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 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` | @@ -102,28 +114,76 @@ dws minutes > > | 语义 | 规约 Primary | minutes 当前 CLI 实际参数 | alias 注册状态 | 说明 | > |------|-------------|------------------------|--------------|------| -> | 单页大小 (Group 13) | `--limit` | `--max`(`--limit` 为合法别名) | `--limit` 已注册 | minutes 推荐 `--max`,但 `--limit` 也可用,两者等价 | +> | 单页大小 (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`(`--limit` 也是合法别名,等价),aitable/calendar/chat/drive 用 `--limit` +> - 分页大小: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 mine`(或 `list all`)获取列表,从返回结果中按标题/参会人/时间匹配选出正确条目,再用其 taskUuid 调用后续命令。**绝不允许凭空编造 hex 字符串作为 --id 的值。** +用户未直接提供 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 开会的听记摘要" -错误: LLM 直接编造: dws minutes get summary --id "7632756964..." -- 这个 id 是捏造的 -正确: 先 dws minutes list mine → 从结果按标题/参会人匹配 → 再 get summary --id <真实id> +错误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 提炼):** | 用户意图关键词 | 对应命令 | 说明 | @@ -131,11 +191,60 @@ dws minutes | 总结、摘要、重点、概述、主要内容、按模板整理 | `get summary` | 返回结构化摘要 | | 逐字稿、全文、原文、完整记录、转写、录音文字 | `get transcription` | 返回完整转写原文 | | "帮我看/总结某次会议"(未给 id) | 先 `list all`(默认) | 按标题/时间匹配后再 get | -| "我可访问的听记"/"我能看到的"/"我有权限的"/"所有听记" | `list all` | **不是 `list mine`!** `all` = 我可访问的全部(mine + shared),`mine` 仅限我自己创建的 | +| "我能访问的所有听记列表"/"我可访问的听记"/"我能看到的"/"我有权限的"/"所有听记" | `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 提炼):** @@ -260,8 +369,8 @@ dws minutes > | `missing required flag(s): --session-id` | cancel 命令必须传 session-id | 从之前 upload create 的返回中提取 session-id;若丢失则暂无法取消 | > > **上传流程三步走**(AI 必须按此顺序执行,不可跳步): -> 1. `dws minutes upload create --file-name "xxx.mp3" --file-size <字节数> --format json` → 获取 `sessionId` + `presignedUrl` -> 2. 将文件通过 `presignedUrl` 直传(由客户端侧完成,AI 不介入) +> 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 不要帮用户估算大小,应让用户确认文件实际字节数。 @@ -306,9 +415,12 @@ dws minutes │ ├── 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 +├── permission +│ ├── add --ids --member-uids --policy <0-4> [--cover] [--sub-resources "..."] +│ └── remove --ids --member-uids +└── tag + ├── list + └── query --tag-id [--limit] [--cursor] ``` ## 命令总览 @@ -380,7 +492,7 @@ Flags: --id string 听记 taskUuid (必填),取值逻辑参考 ## 注意事项 ``` -返回字段(result 内): `duration`(时长, 秒)、`startTime`(开始时间, 毫秒时间戳)、`endTime`(结束时间, 仅较长会议返回, 短录音无此字段)、`taskUuid`(听记 ID)、`title`(听记标题)、`url`(听记访问链接)。注意: **不返回创建人(creator)字段**。 +返回字段: 创建人、开始时间、截止时间、听记标题、听记访问链接URL **发言人列表输出规范(查询详情后必须执行)**: @@ -410,7 +522,7 @@ Flags: > 4. **跳过** — 暂不处理发言人识别 根据用户选择分别处理: - - 选 1 → 进入 `minutes-speaker-summarize` recipe 的智能推断流程(见 [10-minutes-speaker-match.md](../best_practices/10-minutes-speaker-match.md)) + - 选 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` @@ -615,7 +727,7 @@ AI 基于已拉取的转写数据,**本地完成**聚类与摘要(无需新 > **铁律 2:严禁用 AI 摘要里的"参与人/参会人"字段判断某人是否参会** > - AI 摘要中的"参与人"字段往往**只截取最显著的 1-2 个名字**(通常是创建人或发言最多的人),**不是完整参会人列表** > - "AI 摘要参与人只有故愚 → 灵麦没参会" 是典型的错误推理链 -> - 如果一定要列出参会人,只能从 `get transcription` 的转写发言人(`speakerName`/`speakerId`)汇总——`get info` / `get batch` **均不返回** `participants` 结构化字段(get info 只返回 duration/startTime/taskUuid/title/url,get batch 只返回 creatorNick/creatorUnionId/status 等),别指望从它们拿参会名单 +> - 如果一定要列出参会人,应使用 `dws minutes get info` / `get batch` 返回的 `participants` 字段,而不是摘要文本里的描述 > > **铁律 3:通讯录查询是 Step 4 的强制起点,不依赖 Step 3 是否成功** > - Step 2 一旦返回"未命中真名标注",**Step 3 与 Step 4 ① 必须并发触发**,不要等 Step 3 失败再补查 @@ -664,7 +776,7 @@ Step 7: 引导用户替换发言人(调用 speaker replace 写回听记) > - **未命中真名标注 ≠ 目标人物没参会**——绝大多数听记的发言人都是匿名编号,"未命中"是**默认场景**,恰恰是发言人识别功能要解决的问题 > - **不要在转写文本里 grep 目标人名**作为存在性判断——花名/真名通常不会出现在 TA 自己的发言里(参见铁律 1) > - **不要把 `dws minutes get summary` 摘要文本里写到的"参与人"当作完整参会列表**——AI 摘要里的"参与人"字段只截取最显著的 1-2 个名字,不是完整名册(参见铁律 2) -> - 听记各接口都拿不到权威的完整参会名册:`get info` / `get batch` **均不返回** `participants` 字段,转写发言人列表又常是匿名编号。因此**几乎无法**仅凭听记数据下"目标人物没参会"的结论;只有当转写发言人已全部实名标注且不含目标、且 `dws contact user search` 也搜不到该人时,才可谨慎告知"未在可见发言人中找到该人" +> - 唯一能下"目标人物没参会"结论的场景是:`get info` / `get batch` 返回的结构化 `participants` 字段里**完整列出参会人且不含目标**,且 `dws aisearch person` 也搜不到该人 → 才允许告知用户"该人不在参会列表" ##### Step 3: 转写原文内推断(优先) @@ -697,7 +809,7 @@ Step 7: 引导用户替换发言人(调用 speaker replace 写回听记) | 路径 | 命令 | 得到什么 | |------|------|----------| -| ① 通讯录组织架构 | `dws contact user search --keyword "目标人名"` → 部门/职级/上级/真名 | 职能大类(技术/产品/设计/管理)+ 是否存在该人 | +| ① 人员与组织架构 | `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 消息 | 语言风格/工作内容/职责线索 | @@ -773,7 +885,7 @@ Step 7: 引导用户替换发言人(调用 speaker replace 写回听记) > "目前这篇听记中 [人名] 的发言仍显示为『发言人X』。要我帮你把听记里的『发言人X』全部替换为『[人名]』吗?替换后纪要和待办中的发言人也会同步更新。 > 确认后我会执行:`dws minutes speaker replace --id --from "发言人X" --to "[人名]"`" -- 用户确认 → **先主动调用通讯录模糊查询** `dws contact user search --query "[人名]" --format json` 获取该人员的 userId(长整型 dingUid): +- 用户确认 → **先统一搜人** `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` @@ -810,7 +922,12 @@ Flags: --ids string 听记 taskUuid 列表,逗号分隔 (必填) ``` -返回字段: `result.minutesDetails` 数组,每项含 `title`(标题)、`durationMicros`(时长, 微秒)、`startTime`(开始时间, 毫秒)、`taskUuid`、`status`(状态)、`creatorNick`(创建人昵称)、`creatorUnionId`、`size`、`bizType`、`isDeleted`。**不返回参会人列表**。 +返回字段: 听记标题、时长、参与人列表、创建时间、taskUuid、听记状态 + +**批量查询容错铁律(0030 P2 badcase 提炼):** +> - `get batch` 部分条目权限失败时,**展示成功条目的完整信息 + 列出失败条目的 id 与错误原因**,不要因部分失败就整批放弃改为逐个查询 +> - `get batch` 整体报错(如接口级权限/参数问题)时,**降级为逐个 `get info`**,但必须在最终输出中说明"批量接口不可用,已逐个查询",且所有条目都必须尝试 +> - **严禁**:① 部分失败就整批丢弃改逐个查,不展示已成功的数据;② 某一条失败就放弃后续条目;③ 降级后不说明降级原因 ### 修改听记标题 ``` @@ -1207,6 +1324,41 @@ Flags: - 如果成员是听记的创建者(所有者),无法通过此命令移除其权限 - 建议在移除前先确认成员的当前权限,避免误操作 +### 查询我的听记标签/分组列表 +``` +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 创建标签 + ## 意图判断 ### 发起听记 @@ -1304,7 +1456,7 @@ Flags: 1. 用户提供目标姓名(如"张三")后,**AI 主动调用通讯录模糊查询**: ``` - dws contact user search --query "张三" --format json + dws aisearch person --keyword "张三" --dimension name --format json ``` 2. 从返回结果中提取匹配的人员列表,每条包含 `userId`(长整型数字,即 dingUid)和姓名: - **唯一匹配** → 直接向用户确认:"通讯录中找到『张三(userId: 123456789)』,确认将发言人替换为此人并关联通讯录身份?" @@ -1320,7 +1472,7 @@ Flags: - **无匹配** → 提示用户:"通讯录中未找到『张三』,可以直接替换发言人名称(不关联通讯录身份),或提供更精确的姓名重新查询。直接替换?[是] [重新查询]" 3. 获取到 userId 后,在 `speaker replace` 中附加 `--target-uid `,实现发言人关联通讯录身份 -> **注意**:`dws contact user search` 返回的 `userId` 是**长整型数字**(如 `123456789`),这就是 `--target-uid` 需要的值。不要与 `staffId`、`unionId` 等其他 ID 混淆。 +> **注意**:`dws aisearch person` 返回的 `userId` 是**长整型数字**(如 `123456789`),这就是 `--target-uid` 需要的值。不要与 `staffId`、`unionId` 等其他 ID 混淆。 **发言人映射模式识别(高频场景):** @@ -1335,7 +1487,7 @@ Flags: **AI 识别到上述模式时,必须执行以下流程:** 1. **解析映射关系**:提取所有 `源发言人 → 目标姓名` 的配对 -2. **批量通讯录查询**:对每个目标姓名调用 `dws contact user search --query "<姓名>" --format json`,获取 userId(dingUid)。将查询结果汇总后一并向用户确认 +2. **批量人员查询**:对每个目标姓名调用 `dws aisearch person --keyword "<姓名>" --dimension name --format json`,获取 userId(dingUid)。将查询结果汇总后一并向用户确认 3. **向用户确认**:展示解析结果(含通讯录匹配)并请求确认,格式如下: > 我识别到以下发言人对应关系,并已从通讯录中匹配到对应人员: @@ -1580,13 +1732,34 @@ Flags: - 用户提到"添加参会者/加参会者/批量添加参会者/添加成员/加成员/批量添加成员/加参会人/添加参会人"等成员语义 → `permission add`(听记没有独立的"添加成员"接口,**统一走权限接口**) - 用户提到"移除/取消/删除/不让...看"等移除权限语义 → `permission remove` - 如果用户未指定权限类型,默认使用 `--policy 4`(可查看/下载/编辑) -- 如果用户未提供 member-uids,需要先引导用户提供目标成员的钉钉 UID(可通过 `dws contact user search --query "姓名"` 查询) +- 如果用户未提供 member-uids,需要先引导用户提供目标成员的钉钉 UID(可通过 `dws aisearch person --keyword "姓名" --dimension name` 查询) **典型执行链路:** 1. 用户说"把张三加到这个听记中" → AI 需获取张三的 UID -2. 调用 `dws contact user search --query "张三" --format json` 获取 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` @@ -1801,7 +1974,7 @@ dws minutes list mine --query "技术方案" --format json loop: 调用 get transcription --id [--next-token 当前token] - + if stdout 为空 or 返回无效: if 当前token == 上一次token: 连续空响应计数 += 1 @@ -1812,18 +1985,18 @@ loop: 连续空响应计数 = 1 上一次token = 当前token continue - + 连续空响应计数 = 0 拼合本页转写文本到已累积文本 - + if len(已累积文本) > 12000: 暂停翻页 询问用户:"当前已拉取约 N 字符,已达上限,是否继续?" if 用户拒绝: break - + if 返回中无 nextToken 或 nextToken 为空: break(最后一页) - + 上一次token = 当前token 当前token = 返回的 nextToken ``` @@ -1866,7 +2039,7 @@ loop: | 错误类型 | 错误信息特征 | 恢复动作 | 严禁行为 | |----------|-------------|----------|----------| -| 命令结构错误 | `unknown command "info"` / `unknown command "get"` (后无子命令) / `error[validation]: unknown flag: --start` (在 `list` 而非 `list mine/shared/all` 上) | **立即参照本文档顶部"命令层级结构"纠正**。常见错误:① `dws minutes info` → 应为 `dws minutes get info`;② `dws minutes get --id` → `get` 后缺子命令,应为 `get info/summary/transcription` 等;③ `dws minutes list --start` → `list` 后缺 scope,应为 `list mine/shared/all --start` | 严禁在命令结构报错后只换参数名不修正命令层级;严禁把 `get` 当作独立命令使用(后面必须跟 info/summary/transcription/keywords/todos/audio/batch) | +| 命令结构错误 | `unknown 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 次;严禁把空返回当作"没有内容"直接告知用户而不做任何排查 | @@ -1913,8 +2086,8 @@ loop: | 脚本 | 场景 | 用法 | |------|------|------| -| [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` | +| [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` | ## 反例 / 回归案例 @@ -2015,6 +2188,28 @@ https://shanji.dingtalk.com/app/transcribes/76327569643236343831373737345f363438 - **超过 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:拉完转写后只输出时间线原文,未引导发言人聚类与替换 **用户输入:** @@ -2071,7 +2266,7 @@ https://shanji.dingtalk.com/app/transcribes/76327569643231383535353939365f343638 5. 用户确认 → **Step 6 结构化总结输出**:提取发言人2 的全部发言,输出张三的核心观点、关键决策、待办等 6. **Step 7 引导替换发言人**(必须执行): > "目前这篇听记中张三的发言仍显示为『发言人2』。要我帮你把听记里的『发言人2』全部替换为『张三』吗?替换后纪要和待办中的发言人也会同步更新。" -7. 用户确认 → **先通讯录查询获取 dingUid**:调用 `dws contact user search --query "张三" --format json` +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` @@ -2116,8 +2311,8 @@ https://shanji.dingtalk.com/app/transcribes/ 分析下木兰讲了什 | 路径 | 命令 | 结果 | |------|------|------| -| ① 通讯录组织架构 | `dws contact user search --keyword "木兰"` | 木兰 = **王佳明**,X 事业群-X 事业部-X-X-**产品设计部**,上级临渊(王临一)| -| ② 文档产出 | `dws doc search --keyword "王佳明"`(按需)| 多为设计稿/原型,进一步印证设计师角色 | +| ① 人员与组织架构 | `dws aisearch person --keyword "木兰" --dimension name` → `dws contact user get --ids ` | 木兰 = **王佳明**,X 事业群-X 事业部-X-X-**产品设计部**,上级临渊(王临一)| +| ② 文档产出 | `dws drive search --query "王佳明"`(按需)| 多为设计稿/原型,进一步印证设计师角色 | **Step 5:定向匹配 + 置信度判断** @@ -2221,7 +2416,7 @@ Step 2 **[禁止]** 看到全是匿名编号 → 没有继续走 Step 3-4,反 |--------|------------|------| | 在转写文本里 grep "木兰" 字符串作为存在性判断 | 铁律 1 | 99% 听记的发言人都是匿名编号,搜不到字面是默认场景,根本不构成"没参会"的证据 | | 用 AI 摘要的"参与人=拾光"推断"木兰没参会" | 铁律 2 | AI 摘要"参与人"字段只截取最显著的 1-2 人,**不是**完整参会名册 | -| 全程没调用过一次 `dws contact user search --keyword "木兰"` | 铁律 3 | 通讯录查询是 Step 4 的必跑项,单次调用就能拿到"木兰=王佳明,产品设计部" | +| 全程没调用过一次 `dws aisearch person --keyword "木兰" --dimension name` | 铁律 3 | 人员搜索是 Step 4 的必跑项,拿到 userId 后再由 contact 补详情 | | 一旦字面搜不到就放弃身份推断,把任务甩给用户 | 铁律 4 | 这恰恰把发言人识别功能的核心价值(把匿名编号映射到真实人)完全抹掉了 | **[正确] 应该这样执行(与案例 6 一致):** @@ -2229,7 +2424,7 @@ Step 2 **[禁止]** 看到全是匿名编号 → 没有继续走 Step 3-4,反 1. **Step 1**:从 URL 提取 taskUuid → `dws minutes get transcription` 自动翻页拉全部 2. **Step 2**:检查 `speakerNick` 字段是否含"木兰"——发现全是匿名编号 → **不要在转写文本里 grep "木兰",立即并发触发 Step 3 + Step 4 ①** 3. **Step 3**(与 Step 4 ① 并发):在转写里做发言人画像(每位发言人的发言量、主题、互斥线索) -4. **Step 4 ①**(与 Step 3 并发,必跑):`dws contact user search --keyword "木兰"` → 拿到"木兰=王佳明,产品设计部,上级临渊" +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/立场态度) @@ -2239,13 +2434,13 @@ Step 2 **[禁止]** 看到全是匿名编号 → 没有继续走 Step 3-4,反 1. **"在转写里搜不到目标人名"绝不构成"没参会"的证据**:花名/真名通常不出现在 TA 自己的发言里,这是默认场景而非例外。99% 的听记发言人都是匿名编号——这正是发言人识别功能要解决的问题。 -2. **AI 摘要的"参与人"字段是低保真信号,不能作为参会判断依据**:`get summary` 返回的是 AI 生成的自然语言摘要,里面提到的"参与人"通常只是最显著的 1-2 人。而 `get info` / `get batch` **均不返回**结构化 `participants` 字段,无法从中拿完整参会列表;能拿到的只有 `get transcription` 的转写发言人(常为匿名编号)。 +2. **AI 摘要的"参与人"字段是低保真信号,不能作为参会判断依据**:`get summary` 返回的是 AI 生成的自然语言摘要,里面提到的"参与人"通常只是最显著的 1-2 人;要拿完整参会列表,应使用 `get info` / `get batch` 返回的结构化 `participants` 字段。 -3. **`dws contact user search` 是 Step 4 ① 的必跑项,单次调用就能突破死局**:本案例的整个失败链路只要有 1 次通讯录查询就能立即扭转——拿到"木兰=王佳明,产品设计部"后,Step 5 的角色匹配就有了锚点,再也不会得出"没参会"的错误结论。 +3. **`dws aisearch person` 是 Step 4 ① 的必跑项**:先找到人员并取得 userId,再用 `dws contact user get` 补部门等详情;拿到"木兰=王佳明,产品设计部"后,Step 5 的角色匹配才有可靠锚点。 4. **想说"找不到 X"前的四个自检问题**(任何一个回答"没"都禁止给"找不到"结论): - - 通讯录查了吗?(`dws contact user search --keyword "X"`) - - 文档查了吗?(`dws doc search --keyword "X"`) + - 人员搜了吗?(`dws aisearch person --keyword "X" --dimension name`) + - 文档查了吗?(`dws drive search --query "X"`) - 聊天记录查了吗?(`dws chat message list`) - 基于角色在转写里做模式匹配了吗?(设计师 vs 研发 vs 管理者的发言特征) @@ -2254,141 +2449,67 @@ Step 2 **[禁止]** 看到全是匿名编号 → 没有继续走 Step 3-4,反 **绝对禁止:** - **[禁止]** Step 2 看到匿名编号后,直接在转写文本里 grep 目标人名 → 没找到就退出 - **[禁止]** 用 `get summary` 摘要里写到的"参与人"判断某人是否参会 -- **[禁止]** 全流程不调用 `dws contact user search` 就给出"找不到 X"的结论 +- **[禁止]** 全流程不调用 `dws aisearch person` 就给出"找不到 X"的结论 - **[禁止]** 把身份推断的责任甩回给用户:"请你告诉我哪位是木兰" / "请去客户端看参会人"——发言人识别功能的存在意义就是 AI 来做这件事 -### 案例 8:听记/纪要类 query 不走 dws 技能(基础评测集 16 例 badcase 复盘) -> 这是一组**最高频的失败模式**——用户提出听记/纪要/会议总结/待办提取/链接解析等典型 dws 场景请求,AI 却用 `session_search` / `memory_search` / `activity:search` / `browser_use` / `read_file` / 直接反问 等"伪替代"路径绕过 dws 技能,导致核心链路 0 命中。下面把基础评测集(minutes-base, evalrun_4ab46f8da846)中**全部 16 个该模式失败 query 完整列出**,遇到形似输入请直接对照本案例处理。 +### 案例 9:长听记按时间区间总结,转写没翻页到目标区间就硬总结 → 答非所问(202606 P0 实锤复盘) -#### 一、五类典型 badcase 模式(按失败动作归类) +> 这是一次**有 SLS 日志实锤根因**的真实事故:同一篇 1 小时+ 的长听记、同一个"总结某时间区间"的诉求,弱档位答错、强档位答对,但根因**不是模型能力,而是转写没拉到用户要的时间区间**。本案例用于固化"转写覆盖校验"铁律(详见案例 3 末尾的铁律)。 -**模式 A:模糊/省略型 query → AI 直接反问要细节,不主动 list** +**用户输入:** +``` +总结里面 57:52 开始到 01:05:44 的内容给我,形成关键的三件事及拆解 +https://shanji.dingtalk.com/app/transcribes/ (一篇时长 1 小时+ 的会议听记) +``` -涉及 query: +**[错误] 真实失败链路(SLS 日志实锤):** -| case_id | 用户原始 query | -|---------|----------------| -| `dws_minutes_hotquery_0049` | 按关键词搜索我的听记 | -| `dws_minutes_hotquery_0057` | 查列表+看摘要 | -| `dws_minutes_hotquery_0063` | 周会回顾整理 | -| `dws_minutes_hotquery_0064` | 评测工作复盘 | -| `dws_minutes_hotquery_0042` | 把所有听记内容添加到汇报中 | +``` +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 分钟那段",拼出一个"关键三件事" + ↓ +后果:总结的内容和用户要的那段完全对不上,用户评"总结的一般",被迫换更强模型重做 +``` -**典型错误动作**:`tool_calls = []`,AI 回复"请告诉我具体的关键词/时间范围/会议名"就停下,等待用户补充。 +> **强档位为什么"答对了"**:换档位重做时,翻页继续推进到了 **63:53**,**覆盖了 57:52~01:05:44 整段**,所以答对了——**对的是"把转写翻到了目标区间",不是"模型更聪明"**。SLS 日志清晰记录了两轮的翻页进度:弱档位止于 31:09(漏掉目标区间),强档位推进到 63:53(覆盖目标区间)。 -**模式 B:用 `session_search` / `memory_search` 搜索历史会话假装"找过了"** +**这条链路的核心错误:** -涉及 query: +| 反模式 | 后果 | +|--------|------| +| 用户给了明确时间区间(57:52~01:05:44),却没翻页到该区间就停 | 目标区间的转写**根本没进上下文**,总结无源可依 | +| 拿"前 31 分钟"的内容冒充"用户要的 57 分钟那段" | 答非所问,且用户不知道 AI 其实只拉到了 31:09 | +| 撞字符上限时只想着"是否继续",没意识到"目标区间还没拉到" | 即使提示了"是否继续"也没说清"为什么必须继续",提示无效 | +| 答错后归因为"模型不行、换强模型" | 真正要修的是取数覆盖校验,归因错了优化方向就错了 | -| case_id | 用户原始 query | -|---------|----------------| -| `dws_minutes_hotquery_0003` | 帮我查一下最近一次会议的纪要内容 | -| `dws_minutes_hotquery_0017` | 总结下我的会议 | -| `dws_minutes_hotquery_0020` | 我昨天那个会的重点帮我提炼一下 | -| `dws_minutes_hotquery_0027` | 把最近一次会议的待办整理出来 | +**[正确] 应该这样执行:** -**典型错误动作**:调用 `session_search` 搜以前的对话记录,把以前 AI 自己生成过的"会议纪要文件描述"当作真实数据复述出来;从未触发 `dws minutes list / get summary / get todos`。 +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 条的话术),让用户在"继续翻页 / 缩小范围"间选择,**绝不拿没覆盖的内容硬总结**。 -**模式 C:用 `activity:search` / web 搜索把"找听记"做成"搜网页"** +**本案例固化的关键经验(强制吸收):** -涉及 query: +1. **用户给了时间区间 = 必须翻页到该区间才能总结**:`get transcription` 是从头顺序分页的,区间靠后就得一直翻到那里。中途停 = 目标区间没进上下文。 +2. **"撞字符上限"不是停止翻页的理由,"已覆盖目标区间"才是**:用户明确指定区间时,覆盖校验优先于字符上限——没覆盖就得继续翻(或准确提示用户),不能闷头停。 +3. **答错后先查"取数是否拉全",再怀疑模型**:本案例换强模型答对是假象,真因是取数覆盖。遇到"总结不准"的长听记 case,第一反应应是"目标区间的转写拉到了吗"。 +4. **提示必须准确到"已拉到哪 / 目标区间在不在"**:模糊的"是否继续拉取"对用户无意义;必须告诉用户"已拉到 31:09,你要的 57:52 还没拉到"。 -| case_id | 用户原始 query | -|---------|----------------| -| `dws_minutes_hotquery_0025` | 调取某某项目讨论的两个听记内容 | -| `dws_minutes_hotquery_0060` | 搜索+摘要+关键词 | - -**典型错误动作**:调用 `activity:search` 搜公网,返回的是"钉钉 AI 听记产品介绍"页面,与用户的私人听记数据毫不相关。 - -**模式 D:钉钉听记/文档 URL 走 `browser_use` / `read_file` 而非 `dws`** - -涉及 query: - -| case_id | 用户原始 query | -|---------|----------------| -| `dws_minutes_hotquery_0045` | `https://shanji.dingtalk.com/meeting/minutes?taskUuid=sample004` | -| `dws_minutes_hotquery_0046` | `https://alidocs.dingtalk.com/i/nodes/sampleDocNode01` 帮我读取这个文档内容 | -| `dws_minutes_hotquery_0047` | 这个听记链接你能打开看内容吗 `https://shanji.dingtalk.com/meeting/minutes?taskUuid=sample006` | - -**典型错误动作**:`browser_use` 打开页面遇到登录墙就回复"需要登录";或 `read_file` 当本地文件读 → 失败 → 把锅甩给用户。完全没有意识到 dws 技能本身已携带账号态,能直接通过 taskUuid/dentryUuid 拿到内容。 - -**模式 E:多源数据生成日报/汇报/总结/报告,听记侧 0 调用** - -涉及 query: - -| case_id | 用户原始 query | -|---------|----------------| -| `dws_minutes_hotquery_0040` | 根据今天的聊天记录和听记总结工作日报 | -| `dws_minutes_hotquery_0005` | 把会议纪要写入钉钉文档 | -| `minutes-001` (P0) | 从悟空的商业模式分析,重新写一下市场感知报告 | -| `minutes-002` (P0) | 最新文档啥也没有啊,我要你根据我的工作情况总结,分成问题应收、异常事故处理、ai相关工作和其他几部分总结 | -| `minutes-004` (P0) | 你这个好像只是基于日程,我要的是我的对话,发出去的文字,私聊,群聊,日程,会议,文档修改等相关的所有动作,参考这个生成日报,并且把这个封装成日报skill | - -**典型错误动作**:识别出"日报/报告/总结/写文档"场景后,只想着调用周报技能 / 文档写入技能,**完全跳过听记数据获取**这一步。更隐蔽的变体是:用户 query 中**没有出现"听记"二字**(如"根据工作情况总结"、"重新写市场感知报告"),但任务的完成**实际依赖会议讨论内容**,AI 却完全没有意识到需要从听记中采集素材,结果输出空洞的报告或反问"请告诉我会议纪要内容"。 - -**P0 case 暴露的三种变体(必须全部识别):** -- **变体 1(minutes-001)**:用户要写报告,报告素材来源于历史会议讨论 → AI 没识别出需要查听记 -- **变体 2(minutes-002)**:用户要分类总结工作,且明确说"文档啥也没有" → AI 没切到听记作为替代数据源 -- **变体 3(minutes-004)**:用户明确抱怨"只是基于日程"并点名要求"会议"等多数据源 → AI 仍然只用日程数据 - -#### 二、五类共性反模式(必须全部识别为禁止动作) - -| 反模式 | 错误根因 | 正确做法 | -|--------|----------|----------| -| **R1:模糊请求 → 反问要细节** | AI 把"信息不足"当成必须澄清的前置条件 | 听记类模糊请求 → **默认先调用 `dws minutes list mine --max 10 --format json`** 把最近的听记列出来,让用户从列表里挑,而不是反问关键词 | -| **R2:用 `session_search`/`memory_search` 替代 dws** | AI 把"历史会话里聊过的纪要描述"误认为是真实数据源 | 历史会话只能回忆"以前我们聊过什么",**不是真实听记数据**。听记数据**必须**从 `dws minutes` 实时拉取 | -| **R3:用 `activity:search` 通用 web 搜索听记** | 把"听记"理解成公网信息 | 听记是用户私人钉钉数据,**只能**通过 `dws minutes list/get` 获取,公网搜不到也不该搜 | -| **R4:钉钉 URL 走 browser_use/read_file** | 把钉钉 URL 当成普通网页 | 钉钉听记 URL(`shanji.dingtalk.com/meeting/minutes?taskUuid=xxx` 或 `shanji.dingtalk.com/app/transcribes/xxx`)→ **提取 taskUuid → 走 `dws minutes get summary/get transcription`**;钉钉文档 URL(`alidocs.dingtalk.com/i/nodes/xxx`)→ **提取 dentryUuid → 走 `dws doc read`** | -| **R5:日报/汇报/总结/报告场景跳过听记数据采集** | 只看到"日报/报告/总结"就直奔输出端,忘了用户指定的(或隐含的)数据源包含会议/听记。**不仅限于用户说了"听记"二字的场景**——"根据我的工作情况总结"(minutes-002)、"重新写一下市场感知报告"(minutes-001)、"我要的是我的对话...会议...生成日报"(minutes-004)都属于此类 | query 中出现"总结/报告/汇报/日报/周报/工作情况/商业分析/市场感知/复盘"等产出类关键词时,必须自检:**用户的产出是否依赖会议讨论内容?** 如果是 → **第一步必须** `dws minutes list mine` 拿听记 → `get summary` 逐篇拉摘要 → 再汇总。详见「间接意图识别」章节 | - -#### 三、五类 badcase 的统一正确链路(速查表) - -| 用户 query 形态 | 第一步必跑命令 | 关键说明 | -|------------------|------------------|----------| -| 模糊请求:"总结下我的会议" / "周会回顾整理" / "查列表+看摘要" / "评测工作复盘" / "按关键词搜索我的听记" | `dws minutes list mine --max 10 --format json` | 拿到最近听记列表后,对前 1~3 篇 `get summary`,引导用户挑选目标 | -| 含时间词:"最近一次/昨天/本周/上周的会议" | `dws minutes list mine --start --end --max 20 --format json` | 时间范围按用户描述折算,不要让用户自己提供日期 | -| 含主题/项目关键词:"某某项目讨论" / "搜索+摘要+关键词" | `dws minutes list all --query "<关键词>" --max 20 --format json` | 用 `--query` 而不是 `activity:search` | -| 钉钉听记 URL(`shanji.dingtalk.com/...?taskUuid=xxx`) | `dws minutes get summary --id --format json` | 从 URL 提取 taskUuid,禁用 browser_use | -| 钉钉文档 URL(`alidocs.dingtalk.com/i/nodes/xxx`) | `dws doc read --node --format json` | 走 doc 技能而非 read_file/browser_use | -| 多篇听记对比:"对比一下这几个听记 [URL1] [URL2]" | 对每个 URL 分别 `dws minutes get summary --id --format json` | 失败的 URL 给出明确说明,不要把锅全甩给用户 | -| 日报/汇报含"听记"/"会议纪要"关键词 | 先 `dws minutes list mine --start <今日 0 点> --max 20`,再对每篇 `get summary`,最后才汇总 | 听记数据采集是必跑前置,不能直接跳到周报技能 | -| 报告/分析类(query 未显式提"听记"但依赖会议讨论素材):"重新写一下市场感知报告" / "帮我生成商业分析" | `dws minutes list all --query "<主题关键词>" --max 20 --format json` → 逐篇 `get summary` 提取素材 | 详见「间接意图识别」,用户的报告素材来源于历史会议讨论 | -| 分类工作总结(query 未显式提"听记"但需要工作记录):"根据我的工作情况总结,分成…几部分" / "文档啥也没有,根据工作情况总结" | `dws minutes list mine --start <起始日期> --end <截止日期> --format json` → 逐篇 `get summary` → 按分类维度归类 | 文档不足时听记是唯一剩余数据源;详见「间接意图识别」 | -| 多源聚合(用户抱怨数据源不全或点名要求会议数据):"只是基于日程,我要的是对话+会议+文档等所有动作" / "聊天记录和听记总结日报" | 听记侧必跑 `dws minutes list mine --start <起始日期> --end <截止日期>` → `get summary`,再并行采集其他源 | 用户点名要了"会议"就不能省略听记采集 | -| 待办提取:"最近一次会议的待办" | `dws minutes list mine --max 1` → `dws minutes get todos --id ` | 用 `get todos`,不要自己从转写里硬抠 | -| 写入钉钉文档:"把会议纪要写入钉钉文档" | 先 `dws minutes get summary --id ` 拿到内容 → 再 `dws doc create` / `dws doc update` 写入 | 听记数据采集 + 文档写入是两步,缺一不可 | - -#### 四、绝对禁止(任何一条触发即视为严重失败) - -- **[禁止]** 听记/纪要/会议类请求 → `tool_calls = []` 直接反问要细节(任何模糊请求至少要先 `dws minutes list mine` 跑一次,让用户在列表里挑) -- **[禁止]** 用 `session_search` / `memory_search` 搜以前的会话记录当作"真实听记数据"复述给用户——历史会话不是数据源 -- **[禁止]** 用 `activity:search` / web 搜索找用户私人听记——听记是私域数据,公网搜不到 -- **[禁止]** 钉钉听记 URL(`shanji.dingtalk.com/meeting/minutes?taskUuid=xxx` / `shanji.dingtalk.com/app/transcribes/xxx`)走 `browser_use` 打开页面——必须提取 taskUuid 走 `dws minutes get` -- **[禁止]** 钉钉文档 URL(`alidocs.dingtalk.com/i/nodes/xxx`)走 `read_file` / `browser_use`——必须走 `dws doc read` -- **[禁止]** 遇到登录墙 / URL 无效就只回复"需要登录" / "请提供正确 URL" 然后停下——必须 fallback 到 `dws minutes list mine` 让用户从自己的听记列表里挑替代项 -- **[禁止]** 日报/周报/汇报/总结/报告场景,任务产出依赖会议讨论内容,却跳过 `dws minutes` 数据采集——**不限于 query 中出现"听记"二字的场景**,"根据工作情况总结"(minutes-002)、"重新写市场感知报告"(minutes-001)等间接意图同样适用 -- **[禁止]** "把会议纪要写入钉钉文档"类请求只调文档写入工具不调 `dws minutes get summary`——会议纪要内容必须先实时获取,不能让用户自己粘贴 -- **[禁止]** `taskUuid is invalid` 报错后用同一个无效 uuid 重试(哪怕 1 次)——必须立即切换到 `dws minutes list mine` 获取真实 uuid -- **[禁止]** 从历史对话、文档内容、十六进制编码字符串中猜测或拼凑 uuid——uuid **只能**来自 `list mine/shared/all` 返回或用户提供的听记 URL -- **[禁止]** 使用错误的命令层级结构:`dws minutes info`(应为 `get info`)、`dws minutes get`(缺子命令)、`dws minutes list --start`(缺 scope)——参照文档顶部"命令层级结构" -- **[禁止]** 工具调用缺少 `--format json` 参数——所有 `dws` 命令都应带上以便结构化解析 - -#### 五、自检清单(执行前 5 秒强制走一遍) - -收到任何含以下信号词的 query 时,**第一个动作必须是 `dws minutes` 或 `dws doc`,否则视为走错链路**: - -> **直接信号词**(query 中直接提到听记相关概念): -> "听记 / 纪要 / 摘要 / 转写 / 录音 / 会议 / 周会 / 日会 / 评审会 / 复盘 / 回顾 / 待办 / Action Item / 关键词 / 我的会 / 昨天那个会 / 上周的会 / 共享听记 / shanji.dingtalk.com / alidocs.dingtalk.com" - -> **间接信号词**(query 未提"听记"但任务产出依赖会议讨论内容,需要额外判断——详见「间接意图识别」章节): -> "报告 / 市场感知 / 商业分析 / 工作情况总结 / 分类总结 / 日报 / 周报 / 月报 / 工作汇报 / 文档啥也没有 / 只是基于日程 / 所有动作 / 封装成 skill" -> **判断标准**:用户的产出是否依赖"会议上说了什么/讨论了什么"?如果是 → 听记采集是必跑前置步骤 - -强制问自己 5 个问题(任何一个回答"否"都禁止开始执行): - -1. 我接下来的第一个 tool call 是 `dws minutes ...` 或 `dws doc ...` 吗? -2. 我有没有在用 `session_search` / `memory_search` / `activity:search` / `browser_use` / `read_file` 替代 dws? -3. 用户给了钉钉 URL 时,我有没有从 URL 提取 taskUuid/dentryUuid 后走 dws,而不是 browser_use? -4. 用户的请求模糊(如"总结下我的会议")时,我是先 `dws minutes list mine` 列出来,还是反问要细节? -5. 用户要写报告/总结/日报但没提"听记"时,我有没有判断"任务产出是否依赖会议讨论内容"?如果依赖 → 听记采集不能省略(参见间接意图识别三条铁律) +**绝对禁止:** +- **[禁止]** 用户指定了时间区间,却没翻页到该区间(按 `endTime` 校验)就开始总结 +- **[禁止]** 拿"已拉到的前半段"冒充"用户要的后半段区间"硬凑答案,且不告知用户实际只拉到了哪里 +- **[禁止]** 因字符上限/步数预算停止翻页时,只说"已达上限是否继续"而不说明"目标区间尚未拉到" +- **[禁止]** 把"总结不准"归因为模型能力、直接建议换模型——先核对取数是否覆盖了用户请求的时间区间 diff --git a/skills/multi/dingtalk-minutes/scripts/minutes_extract_todos.py b/skills/multi/dingtalk-minutes/scripts/minutes_extract_todos.py index 10123377..7ca3d651 100644 --- a/skills/multi/dingtalk-minutes/scripts/minutes_extract_todos.py +++ b/skills/multi/dingtalk-minutes/scripts/minutes_extract_todos.py @@ -44,58 +44,6 @@ def run_dws( return None -def todos_from_payload(payload: Any) -> List[dict]: - """解析 `minutes get todos` 返回为待办列表。 - - 返回结构: result.dingtalkTodoList(对象数组, 含 title 等)与 - result.actions(JSON 字符串数组, 每条形如 - {"mark":[],"value":"..."}). 二者无 todos 键。 - 优先取 dingtalkTodoList; 为空时回退解析 actions。 - """ - if isinstance(payload, dict): - inner = payload.get('result', payload) - else: - inner = payload - out: List[dict] = [] - if isinstance(inner, dict): - ding_list = inner.get('dingtalkTodoList') - if isinstance(ding_list, list) and ding_list: - for t in ding_list: - if isinstance(t, dict): - content = t.get('title') or t.get('content') or '' - if content: - out.append({'content': str(content), '_raw': t}) - if out: - return out - actions = inner.get('actions') - if isinstance(actions, list): - for a in actions: - content = '' - if isinstance(a, str): - text = a.strip() - if text.startswith('{'): - try: - parsed = json.loads(text) - content = parsed.get('value') or '' - except json.JSONDecodeError: - content = text - else: - content = text - elif isinstance(a, dict): - content = (a.get('value') or a.get('content') - or a.get('title') or '') - if content: - out.append({'content': str(content)}) - elif isinstance(inner, list): - for t in inner: - if isinstance(t, dict): - content = (t.get('content') or t.get('text') - or t.get('title') or t.get('value') or '') - if content: - out.append({'content': str(content), '_raw': t}) - return out - - def main(): parser = argparse.ArgumentParser( description='从听记中提取待办事项' @@ -134,9 +82,21 @@ def main(): ]) if not todos_data: continue - items = todos_from_payload(todos_data) + if isinstance(todos_data, list): + items = todos_data + elif isinstance(todos_data, dict): + inner = todos_data.get('result', todos_data) + if isinstance(inner, dict): + items = inner.get('todos', []) + elif isinstance(inner, list): + items = inner + else: + items = [] + else: + items = [] for t in items: - t['_source'] = title + if isinstance(t, dict): + t['_source'] = title all_todos.extend(items) print(f"\n📋 听记待办汇总") diff --git a/skills/multi/dingtalk-minutes/scripts/minutes_list_parse.py b/skills/multi/dingtalk-minutes/scripts/minutes_list_parse.py index d6aa83ea..712327a9 100644 --- a/skills/multi/dingtalk-minutes/scripts/minutes_list_parse.py +++ b/skills/multi/dingtalk-minutes/scripts/minutes_list_parse.py @@ -17,7 +17,7 @@ def _unwrap_rows(payload: Any) -> List[Any]: return value if isinstance(value, dict): for inner_key in ( - 'itemList', 'items', 'list', 'records', 'minutes', + 'items', 'list', 'records', 'minutes', ): inner = value.get(inner_key) if isinstance(inner, list): @@ -30,8 +30,7 @@ def uuid_title_pairs_from_payload(payload: Any) -> List[Tuple[str, str]]: out: List[Tuple[str, str]] = [] for item in _unwrap_rows(payload): if isinstance(item, dict): - uuid = (item.get('taskUuid') or item.get('uuid') - or item.get('id') or item.get('task_uuid')) + uuid = item.get('taskUuid') or item.get('id') or item.get('task_uuid') if not uuid: continue title = item.get('title') or item.get('name') or '无标题' @@ -54,7 +53,6 @@ def uuid_title_pairs_from_payload(payload: Any) -> List[Tuple[str, str]]: continue uuid = ( parsed.get('taskUuid') - or parsed.get('uuid') or parsed.get('id') or parsed.get('task_uuid') ) diff --git a/skills/multi/dingtalk-minutes/scripts/minutes_recent_summary.py b/skills/multi/dingtalk-minutes/scripts/minutes_recent_summary.py index f136f290..24e8ed44 100644 --- a/skills/multi/dingtalk-minutes/scripts/minutes_recent_summary.py +++ b/skills/multi/dingtalk-minutes/scripts/minutes_recent_summary.py @@ -93,16 +93,9 @@ def main(): if isinstance(summary_data, str): summary_text = summary_data elif isinstance(summary_data, dict): - # 兼容 {result: {fullSummary}} 包裹结构 - inner = summary_data.get('result') - container = (inner if isinstance(inner, dict) - else summary_data) - summary_text = (container.get('fullSummary') - or container.get('summary') - or container.get('content') - or (inner if isinstance(inner, str) - else '') - or json.dumps(container, + summary_text = (summary_data.get('summary') + or summary_data.get('content') + or json.dumps(summary_data, ensure_ascii=False)) output_lines.append(f"## {i}. {title}\n") diff --git a/skills/multi/dingtalk-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-misc/references/05-reporting.md b/skills/multi/dingtalk-misc/references/05-reporting.md new file mode 100644 index 00000000..503d4b52 --- /dev/null +++ b/skills/multi/dingtalk-misc/references/05-reporting.md @@ -0,0 +1,29 @@ +# 工作汇报 + +> lite recipe(`view-report-inbox`、`check-report-read-status`)见 [report-lite-recipes.md](./report-lite-recipes.md)。 + +## 路径分歧(先判定后选 recipe) + +`dws report` 与 `dws doc` 是两个不同的产品,覆盖不同的"周报 / 日报"场景。在选 recipe 前先做一次判定: + +| query 中是否含强信号 | 选哪个 recipe | 默认值 | +|---------------------|---------------|--------| +| 含「钉钉日志 / OA 周报 / 我的钉钉日志 / 日报模板 / 周报模板 / 提交日志 / 填模版」 | `submit-report`(走 dws report entry submit) | — | +| 含「在线文档 / 写一篇文档 / 整理成文档 / 文档保存」 | `generate-*-report`(走 dws doc create) | — | +| **无强信号**(如"写日报"、"写周报"、"整理本周工作")| `generate-*-report`(走 dws doc create) | 默认 | + +注: + +- "钉钉日志"在用户口语中多指 OA 周报应用,但偶有泛指日志/记录,必要时反问澄清。 +- 仅当用户**明确**说"钉钉日志(OA 应用)"或类似强信号时才切到 `submit-report`;否则不要主动推荐 OA 日志路径——多数用户的"周报"实际期望是文档(可分享、可编辑、长文本)。 + +## Recipe 速查 + +| 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. 按[「多源并行采集」](./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-misc/references/agoal.md b/skills/multi/dingtalk-misc/references/agoal.md new file mode 100644 index 00000000..f76d6bee --- /dev/null +++ b/skills/multi/dingtalk-misc/references/agoal.md @@ -0,0 +1,295 @@ +# Agoal(目标管理) + +## 产品说明 + +Agoal 是钉钉目标管理工具,支持战略解码、经营合约、计分卡、用户目标、目标模板、周月报六大模块,帮助组织将战略目标从顶层分解到个人并持续跟踪。 + +**CLI 前缀**: `dws agoal` + +## 命令总览 + +### strategy (战略解码管理) + +| 命令 | 用途 | 必填参数 | 备注 | +|------|------|----------|------| +| `strategy list` | 获取战略解码列表 | `--scope-type` `--scope-id` | scopeType: DEPT/PERSONAL;scope-id 为 scope-type 对应的部门 id 或用户 id | +| `strategy detail` | 获取战略解码详情 | `--profile-id` | 根据战略解码 id 查询 | +| `strategy update` | 更新战略解码 | `--profile-id` `--content` | **覆盖逻辑,必须基于查询返回的老数据修改后再传入**;`--content` 为 JSON 数组 | + +> **`strategy update` 是覆盖式更新**:一定要先 `strategy detail` 获取完整数据,在原数据基础上修改后再传入,会根据战略解码id进行对应的修改。 + +#### strategy update --content 实体字段说明 + +| 字段 | 说明 | +|------|------| +| `id` | 实体 id | +| `title` | 标题对象,如 `{"title":"标题文本"}` | +| `linkEntityId` | 所属实体 ID(查询接口中有值时必须回传) | +| `entityType` | 类型枚举:`OGSM_OBJECTIVE`(目的)、`OGSM_GOAL`(目标)、`OGSM_STRATEGY`(策略)、`OGSM_MEASUREMENT`(衡量标准)、`OGSM_TACTICS`(行动方案) | +| `status` | 状态枚举:`NORMAL`(正常)、`PRE_PUBLISH_THEN_UPDATE`(预发布更新)、`PRE_PUBLISH_THEN_CREATE`(预发布新增)、`PRE_PUBLISH_THEN_DELETE`(预发布删除) | +| `supporters` | 承接人数组 `[{type, dingId, staffId}]`;type: `USER`/`DEPARTMENT`;staffId 在 type=USER 时必填 | +| `indicators` | 关键指标 id 字符串数组 | +| `linkSources` | 资源关联数组 `[{id, linkType, linkId, source, objectId, keyResultId}]`;linkType: project/task/campaign/product/doc/standard;source: teambition | +| `executors` | 人员 dingId 字符串数组 | +| `teams` | 部门 dingId 字符串数组 | + +### contract (经营合约管理) + +| 命令 | 用途 | 必填参数 | 备注 | +|------|------|----------|------| +| `contract list` | 获取经营合约列表 | `--scope-type` `--scope-id` | scopeType: DEPT/PERSONAL;scope-id 为 scope-type 对应的部门 id 或用户 id | +| `contract fields` | 获取经营合约字段列表 | — | 获取组织下经营合约的字段配置 | +| `contract detail` | 获取经营合约详情 | `--contract-id` | 根据合约 id 查询 | +| `contract update` | 更新经营合约 | `--contract-id` `--dimensions` | **覆盖逻辑**;可选 `--audit-config`、`--objective-template` | + +> **`contract update` 同样是覆盖式更新**:必须基于 `contract detail` 返回的数据修改后再传入。 + +#### contract update --dimensions 维度字段说明 + +| 字段 | 说明 | +|------|------| +| `id` | 维度 id | +| `title` | 维度名称 | +| `description` | 维度描述 | +| `weight` | 维度权重 | +| `objectives` | 目标列表 | +| `dimensionConfig` | 维度配置 | +| `children` | 子维度列表 | + +#### contract update 可选参数 + +| 参数 | 说明 | 示例 | +|------|------|------| +| `--audit-config` | 审批配置 JSON | `{"needAudit":true,"processTemplateId":"TPL_ID"}` | +| `--objective-template` | 合约模板 JSON | `{"id":"TPL_ID","title":"模板名称"}` | + +### scorecard (计分卡管理) + +| 命令 | 用途 | 必填参数 | 备注 | +|------|------|----------|------| +| `scorecard detail` | 获取计分卡详情 | `--selected-time` `--dept-id` | selectedTime 为 ISO-8601 字符串,如 `"2026-01-01T00:00:00+08:00"` | +| `scorecard entity-detail` | 获取计分卡实体详情 | `--sc-id` `--entity-id` | 根据计分卡 id 和实体 id 查询 | +| `scorecard update` | 更新计分卡 | `--dept-id` `--selected-time` `--id` `--tracking-period-type` `--content` | trackingPeriodType: MONTHLY/QUARTERLY | + +#### selectedTime 时间说明 + +`--selected-time` 接受 ISO-8601 字符串,传入对应周期起始时刻: + +- **2026年** → `"2026-01-01T00:00:00+08:00"` +- **2026年5月** → `"2026-05-01T00:00:00+08:00"` + +#### scorecard update --content 维度字段说明 + +| 字段 | 说明 | +|------|------| +| `id` | 维度 id | +| `title` | 维度名称 | +| `items` | 指标或关键事项列表 | + +items 每项包含: + +| 字段 | 说明 | +|------|------| +| `id` | 实体 id | +| `title` | 名称 | +| `reference` | 参考信息 | +| `start` | 起始值 | +| `target` | 目标值 | +| `executors` | 负责人列表,每项包含 `openId` | + +#### trackingPeriodType 枚举 + +| 值 | 说明 | +|------|------| +| `MONTHLY` | 月度追踪 | +| `QUARTERLY` | 季度追踪 | + +### user (用户目标管理) + +| 命令 | 用途 | 必填参数 | 备注 | +|------|------|----------|------| +| `user rules` | 获取用户规则周期列表 | — | 可选 `--user-id`,不传则默认取操作人自己 | +| `user objectives` | 查询用户目标列表 | `--user-id` `--rule-id` `--period-ids` | `--period-ids` 为逗号分隔的周期 id 列表 | + +### obj-template (目标模板管理) + +| 命令 | 用途 | 必填参数 | 备注 | +|------|------|----------|------| +| `obj-template list` | 获取目标模板列表 | — | 可选 `--keyword` 搜索关键词、`--page` 页码、`--page-size` 每页数量 | +| `obj-template create-or-update` | 新增或更新目标模板 | `--dimensions` | **覆盖逻辑**;新增时 `--title` 必填;更新时 `--template-id` 必填,dimensions 必须基于老数据修改 | + +> **`obj-template create-or-update` 同样是覆盖式更新**:更新时必须基于 `obj-template list` 返回的数据修改后再传入。新增时建议先参考已存在的模板数据再构建 dimensions。 + +#### obj-template create-or-update 参数说明 + +| 参数 | 说明 | 类型 | +|------|------|------| +| `--template-id` | 模板 id(更新时必填) | string | +| `--title` | 模板标题(新增时必填) | string | +| `--objective-weight` | 是否启用目标权重 | bool | +| `--dimension-weight` | 是否启用维度权重 | bool | +| `--compute-by-weight` | 维度是否参与计算 | bool | +| `--dimensions` | 模板关联的维度 JSON 字符串 | string | + +### report (周月报管理) + +| 命令 | 用途 | 必填参数 | 备注 | +|------|------|----------|------| +| `report list-statistics` | 获取周月报数据跟催列表 | — | 返回各规则的人员提交情况统计(按时/迟交/未提交人数);可选 `--keyword` 搜索规则名称 | +| `report submit-detail` | 获取周月报规则提交详情 | `--template-id` `--submit-state` | submitState: `ON_TIME`(按时)/`LATE`(迟交)/`NOT_SUBMITTED`(未提交);可选 `--query-date`(ISO-8601)、`--page`、`--page-size`、`--keyword`(搜索员工名称) | + +#### report submit-detail --submit-state 枚举 + +| 值 | 说明 | +|------|------| +| `ON_TIME` | 按时提交 | +| `LATE` | 迟交 | +| `NOT_SUBMITTED` | 未提交 | + +## 意图判断 + +用户说"战略解码/战略目标/OGSM": +- 查看/列表 → `strategy list` +- 详情 → `strategy detail` +- 修改/更新 → `strategy update`(先查后改) + +用户说"经营合约/合约/KPI合约": +- 查看/列表 → `contract list` +- 字段配置 → `contract fields` +- 详情 → `contract detail` +- 修改/更新 → `contract update`(先查后改) + +用户说"计分卡/scorecard/绩效看板": +- 查看详情 → `scorecard detail` +- 实体详情 → `scorecard entity-detail` +- 修改/更新 → `scorecard update` + +用户说"目标/OKR/我的目标/个人目标": +- 规则周期 → `user rules` +- 目标列表 → `user objectives` + +用户说"目标模板/模板管理": +- 查看模板列表 → `obj-template list` +- 新增模板 → `obj-template create-or-update --title "模板名称"` +- 更新模板 → `obj-template create-or-update --template-id TPL_ID` + +用户说"周月报/周报统计/提交情况/跟催/迟交/未提交": +- 查看提交统计列表 → `report list-statistics` +- 查看某规则的提交详情(按时/迟交/未提交人员明细) → `report submit-detail` + +## 核心工作流 + +```bash +# 1. 查看战略解码列表(按部门) +dws agoal strategy list --scope-type DEPT --scope-id DEPT_ID + +# 1.1 查看战略解码列表(按个人) +dws agoal strategy list --scope-type PERSONAL --scope-id USER_ID + +# 2. 查看战略解码详情 +dws agoal strategy detail --profile-id PROFILE_ID + +# 3. 更新战略解码(基于详情返回的数据修改后传入) +dws agoal strategy update --profile-id PROFILE_ID \ + --content '[{"id":"entity1","title":{"title":"新目标"},"entityType":"OGSM_OBJECTIVE","status":"NORMAL","executors":["dingId1"]}]' + +# 4. 查看经营合约列表(按个人) +dws agoal contract list --scope-type PERSONAL --scope-id USER_ID + +# 4.1 查看经营合约列表(按部门) +dws agoal contract list --scope-type DEPT --scope-id DEPT_ID + +# 5. 查看经营合约字段列表 +dws agoal contract fields + +# 6. 查看经营合约详情 +dws agoal contract detail --contract-id CONTRACT_ID + +# 7 更新经营合约(基于详情返回的数据修改后传入) +dws agoal contract update --contract-id CONTRACT_ID \ + --dimensions '[{"id":"dim1","title":"维度名称","objectives":[...]}]' + +# 8. 查看计分卡详情 +dws agoal scorecard detail --selected-time "2025-01-01T00:00:00+08:00" --dept-id DEPT_ID + +# 9. 查看计分卡实体详情 +dws agoal scorecard entity-detail --sc-id SC_ID --entity-id ENTITY_ID + +# 10. 更新计分卡 +dws agoal scorecard update --dept-id DEPT_ID --selected-time "2025-01-01T00:00:00+08:00" --id SC_ID --tracking-period-type MONTHLY --content '[{"id":"dim1","title":"业绩","items":[{"id":"item1","title":"收入","target":"100"}]}]' + +# 11. 查看用户规则 → 提取 ruleId 和 periodId +dws agoal user rules --user-id USER_ID + +# 12. 查看用户目标 +dws agoal user objectives --user-id USER_ID --rule-id RULE_ID --period-ids "period1,period2" + +# 13. 查看周月报提交统计列表 +dws agoal report list-statistics + +# 13.1 按关键词搜索规则 +dws agoal report list-statistics --keyword "周报规则" + +# 14. 查看某规则的按时提交详情 +dws agoal report submit-detail --template-id TPL_ID --submit-state ON_TIME + +# 14.1 查看迟交详情(带分页和日期) +dws agoal report submit-detail --template-id TPL_ID --submit-state LATE --query-date "2026-06-18T00:00:00+08:00" --page 1 --page-size 20 + +# 15. 获取目标模板列表 +dws agoal obj-template list + +# 15.1 搜索目标模板 +dws agoal obj-template list --keyword "业绩" + +# 16. 新增目标模板 +dws agoal obj-template create-or-update --title "业绩模板" --objective-weight --dimension-weight --dimensions '[...]' + +# 16.1 更新目标模板 +dws agoal obj-template create-or-update --template-id TPL_ID --title "业绩模板" --dimensions '[...]' +``` + +## 上下文传递表 + +| 操作 | 从返回中提取 | 用于 | +|------|-------------|------| +| `strategy list` | `profileId` | `strategy detail` / `strategy update` 的 `--profile-id` | +| `strategy detail` | 完整实体数据 | `strategy update` 的 `--content`(基于此修改) | +| `contract list` | `contractId` | `contract detail` / `contract update` 的 `--contract-id` | +| `contract detail` | 完整维度数据 | `contract update` 的 `--dimensions`(基于此修改) | +| `scorecard detail` | `scId`、`entityId` | `scorecard entity-detail` / `scorecard update` 的 `--id` | +| `user rules` | `ruleId`、`periodIds` | `user objectives` 的 `--rule-id` `--period-ids` | +| `report list-statistics` | `templateId`(列表项中) | `report submit-detail` 的 `--template-id` | +| `obj-template list` | `templateId` | `obj-template create-or-update` 的 `--template-id`(更新时) | + +## 注意事项 + +- **所有 update 命令都是覆盖逻辑**:必须先用对应的 detail/list 查询到完整数据,在原数据基础上修改后再传入,否则未传入的数据会被删除 +- 所有命令支持可选参数 `--request-id` +- `--scope-type` 仅支持 `DEPT`(按部门)和 `PERSONAL`(按个人)两种 +- `--selected-time` 接受 ISO-8601 字符串(如 `"2026-01-01T00:00:00+08:00"`) +- `--period-ids` 为逗号分隔的字符串,如 `"period1,period2"` +- `report submit-detail` 的 `--query-date` 接受 ISO-8601 字符串(如 `"2026-06-18T00:00:00+08:00"`),不传则默认当天 +- `report submit-detail` 的 `--page` 和 `--page-size` 默认为 1 和 10,不传时由服务端使用默认值 + +--- + +## 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-misc/references/attendance-intent-guide.md b/skills/multi/dingtalk-misc/references/attendance-intent-guide.md new file mode 100644 index 00000000..7a6bc6b8 --- /dev/null +++ b/skills/multi/dingtalk-misc/references/attendance-intent-guide.md @@ -0,0 +1,7 @@ +# attendance 局部意图消歧 + +本文件从单 Skill `intent-guide.md` 拆分而来,仅保留与本产品相关的跨产品消歧规则。 + +| 用户说... | 真实意图 | 应该用 | 不要用 | 理由 | +|---|---|---|---|---| +| "查/提交 请假/加班/外出/出差/补卡 审批单" | 考勤业务审批单 | `attendance approve`(查询走 `attendance approve list`;提交走 `attendance approve templates --type leave\ | overtime\ | repair-check\ | diff --git a/skills/multi/dingtalk-attendance/references/attendance-report.md b/skills/multi/dingtalk-misc/references/attendance-report.md similarity index 92% rename from skills/multi/dingtalk-attendance/references/attendance-report.md rename to skills/multi/dingtalk-misc/references/attendance-report.md index 43d54547..bbfc5cbf 100644 --- a/skills/multi/dingtalk-attendance/references/attendance-report.md +++ b/skills/multi/dingtalk-misc/references/attendance-report.md @@ -11,7 +11,7 @@ 1. 未按"阶段 1"做人员解析 → `--users` 传入部门 ID 而非员工 userId,脚本虽内置回退但会浪费一次失败的接口调用 2. 未按"阶段 0"判断报表类型 → 用户说"汇总"被理解成"明细",导致输出粒度错误 3. 未按"列选择"判断是否传 `--column-keywords` → 用户要"迟到情况报表"被输出成全字段默认报表 -4. 未按"错误处理"规则处理无权限错误(errorCode 6001「无权限操作」/ `AUTH_ERROR`)/ `HSF_ILLEGALPARAMS` → 把环境错误当成业务错误反馈给用户 +4. 未按"错误处理"规则处理 403 / `HSF_ILLEGALPARAMS` → 把环境错误当成业务错误反馈给用户 5. 未按"阶段 4"返回结果 → 把 Excel 内容贴在对话里,或者裸 userId 直接输出 **执行前自检(必须能在心中回答)**: @@ -22,7 +22,7 @@ 如果上述任何一项答不出,**回到本文档对应章节重新阅读**,禁止凭记忆/想象组装命令。 -**前提**:当前用户必须是钉钉管理员,否则 report 系列接口返回业务权限错误(`server_error_code` 为 `AUTH_ERROR`、errorCode `6001`「无权限操作」),**不是 HTTP 403**。提示需要管理员权限,不要重试。 +**前提**:当前用户必须是钉钉管理员,否则 report 系列接口返回 403 权限错误。 ## 核心原则 @@ -46,7 +46,7 @@ Agent 解析用户意图(报表类型、人员范围、时间范围、关注 - 所有 `dws` 命令必须携带 `--format json` - 时间参数 `--start` / `--end` 格式必须为 `yyyy-MM-dd HH:mm:ss` - 字段 ID 与字段名的映射必须从 `report columns` 实时建立,禁止硬编码 -- 任何接口失败(含无权限错误 6001/`AUTH_ERROR`)必须向用户清晰报错,禁止静默吞掉 +- 任何接口失败(含 403)必须向用户清晰报错,禁止静默吞掉 ## 涉及工具 @@ -102,7 +102,7 @@ Agent 解析用户意图(报表类型、人员范围、时间范围、关注 | 用户说 | 应路由到 | |--------|---------| | "今天打卡了吗" | `dws attendance record get`(单次查询,非报表) | -| "帮我排班" | [attendance-schedule.md](./attendance-schedule.md) 排班工作流(排班,非报表) | +| "帮我排班" | `dws attendance schedule import`(导入排班,非报表;先确认考勤组、人员、日期、班次和休息日) | | "我的假期还剩多少" | `dws attendance vacation balance`(单次查询) | | "帮我请假" | `dws oa`(审批流程,非报表) | | "我这个月的考勤怎么样" | `dws attendance summary`(个人统计,非报表) | @@ -246,7 +246,7 @@ python scripts/attendance_report_checkin.py \ - 脚本依赖 `openpyxl`,若未安装需先 `pip install openpyxl` - 脚本摘要输出到 stdout,进度日志输出到 stderr -- 首次调试可加 `--inspect` 参数查看首条记录原始结构(`attendance_report_detail.py` / `attendance_report_monthly.py` / `attendance_report_daily.py` / `attendance_report_checkin.py` 支持;**`attendance_report_record.py` 无 `--inspect` 参数**) +- 首次调试可加 `--inspect` 参数查看首条记录原始结构 - 脚本执行失败(exit ≠ 0)时,stderr 中有具体错误信息 ### 阶段 4: 返回结果给用户 @@ -488,11 +488,11 @@ python scripts/attendance_report_checkin.py \ | 错误 | 原因 | 处理方式 | |------|------|---------| -| 权限错误(errorCode 6001「无权限操作」/ `AUTH_ERROR`,非 HTTP 403) | 当前账号非管理员 | 提示需要管理员权限,不要重试 | +| 权限错误(403) | 当前账号非管理员 | 提示需要管理员权限,不要重试 | | userId 无效 | 用户 ID 错误或已离职 | 脚本跳过并在摘要中标注 | | 时间区间超长 | 接口可能性能不佳 | 提示"超过 1 年的数据建议分阶段导出" | | openpyxl 未安装 | 环境缺包 | 输出 `pip install openpyxl` 安装提示 | -| 脚本执行失败 | 接口异常/配置问题 | 将 stderr 错误信息转告用户,可加 `--inspect` 重试(`attendance_report_record.py` 不支持 `--inspect`) | +| 脚本执行失败 | 接口异常/配置问题 | 将 stderr 错误信息转告用户,可加 `--inspect` 重试 | ## 使用示例 @@ -611,9 +611,9 @@ python scripts/attendance_report_checkin.py \ | 脚本 | 报表类型 | 数据来源 | CLI 参数 | |------|---------|---------|---------| -| [attendance_report_detail.py](../../scripts/attendance_report_detail.py) | 明细 | `check result` + `check record` | `--users --start --end [--out]` | -| [attendance_report_monthly.py](../../scripts/attendance_report_monthly.py) | 月度汇总(默认) | `report columns` + `report query-data` | `--users --start --end [--column-keywords] [--out]` | -| [attendance_report_daily.py](../../scripts/attendance_report_daily.py) | 每日统计 | `report columns` + `report query-data` | `--users --start --end [--column-keywords] [--out]` | -| [attendance_report_record.py](../../scripts/attendance_report_record.py) | 考勤记录 | `attendance approve list` + `oa approval detail` | `--type --users --start --end [--out]` | -| [attendance_report_checkin.py](../../scripts/attendance_report_checkin.py) | 签到报表 | `attendance checkin records` | `--users --start --end [--out]` | -| [attendance_report_common.py](../../scripts/attendance_report_common.py) | 公共模块(不可单独执行) | — | — | +| [attendance_report_detail.py](../scripts/attendance_report_detail.py) | 明细 | `check result` + `check record` | `--users --start --end [--out]` | +| [attendance_report_monthly.py](../scripts/attendance_report_monthly.py) | 月度汇总(默认) | `report columns` + `report query-data` | `--users --start --end [--column-keywords] [--out]` | +| [attendance_report_daily.py](../scripts/attendance_report_daily.py) | 每日统计 | `report columns` + `report query-data` | `--users --start --end [--column-keywords] [--out]` | +| [attendance_report_record.py](../scripts/attendance_report_record.py) | 考勤记录 | `attendance approve list` + `oa approval detail` | `--type --users --start --end [--out]` | +| [attendance_report_checkin.py](../scripts/attendance_report_checkin.py) | 签到报表 | `attendance checkin records` | `--users --start --end [--out]` | +| [attendance_report_common.py](../scripts/attendance_report_common.py) | 公共模块(不可单独执行) | — | — | diff --git a/skills/multi/dingtalk-attendance/references/attendance-schedule.md b/skills/multi/dingtalk-misc/references/attendance-schedule.md similarity index 98% rename from skills/multi/dingtalk-attendance/references/attendance-schedule.md rename to skills/multi/dingtalk-misc/references/attendance-schedule.md index 05bcc8a3..9175261a 100644 --- a/skills/multi/dingtalk-attendance/references/attendance-schedule.md +++ b/skills/multi/dingtalk-misc/references/attendance-schedule.md @@ -508,8 +508,8 @@ python scripts/attendance_schedule_import.py \ | 脚本 | 用途 | CLI 参数 | |------|------|---------| -| [attendance_schedule_import.py](../../scripts/attendance_schedule_import.py) | 排班导入(含校验、回显、执行) | `--group-id --schedules --confirm` | -| [attendance_schedule_export.py](../../scripts/attendance_schedule_export.py) | 排班查询导出(分批查询、排班表 Excel) | `--users --start --end [--output]` | +| [attendance_schedule_import.py](../scripts/attendance_schedule_import.py) | 排班导入(含校验、回显、执行) | `--group-id --schedules --confirm` | +| [attendance_schedule_export.py](../scripts/attendance_schedule_export.py) | 排班查询导出(分批查询、排班表 Excel) | `--users --start --end [--output]` | --- diff --git a/skills/multi/dingtalk-attendance/references/attendance-vacation.md b/skills/multi/dingtalk-misc/references/attendance-vacation.md similarity index 97% rename from skills/multi/dingtalk-attendance/references/attendance-vacation.md rename to skills/multi/dingtalk-misc/references/attendance-vacation.md index 6c59e739..afeee1eb 100644 --- a/skills/multi/dingtalk-attendance/references/attendance-vacation.md +++ b/skills/multi/dingtalk-misc/references/attendance-vacation.md @@ -137,4 +137,4 @@ python scripts/attendance_vacation_balance.py \ | 脚本 | 场景 | CLI 参数 | |------|------|---------| -| [attendance_vacation_balance.py](../../scripts/attendance_vacation_balance.py) | 假期余额列表 Excel 导出;脚本按假期名称关键词筛选规则,并逐个 `leaveCode` 调用余额查询 | `--users [--leave-keywords] [--out] [--inspect]` | +| [attendance_vacation_balance.py](../scripts/attendance_vacation_balance.py) | 假期余额列表 Excel 导出;脚本按假期名称关键词筛选规则,并逐个 `leaveCode` 调用余额查询 | `--users [--leave-keywords] [--out] [--inspect]` | diff --git a/skills/multi/dingtalk-attendance/references/attendance.md b/skills/multi/dingtalk-misc/references/attendance.md similarity index 81% rename from skills/multi/dingtalk-attendance/references/attendance.md rename to skills/multi/dingtalk-misc/references/attendance.md index 86e20d66..26871e9e 100644 --- a/skills/multi/dingtalk-attendance/references/attendance.md +++ b/skills/multi/dingtalk-misc/references/attendance.md @@ -1,7 +1,5 @@ # 考勤 (attendance) 命令参考 -> **【命令可用性提示】** 当前 dws 已注册全部考勤子命令组(`record` / `check` / `approve` / `shift` / `schedule` / `class` / `adjustment` / `overtime` / `group` / `summary` / `rules` / `selfsetting` / `globalsetting` / `vacation` / `checkin` / `report` / `boss-check`)。查询与写操作大多可直接调用后端,不会再返回 `unknown command`,不要以"开源版不支持"为由拒答。个别命令返回受账号权限和组织数据影响:`report` 系列仅管理员可用;非管理员或数据为空时可能返回空列表或权限错误。执行前可用 `dws --help` 或 `--dry-run` 验证参数。 - > **【必读】日期范围严格计算规则 — 所有含 --start/--end 或 --from/--to 的命令均适用** > > **禁止随意猜测日期范围,必须按以下规则精确计算后再组装命令参数:** @@ -22,6 +20,40 @@ > 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 + ## 命令总览 @@ -55,35 +87,32 @@ Flags: 返回每条记录含:用户 ID、实际打卡时间、打卡地址、打卡经纬度、打卡类型(OnDuty/OffDuty)、定位方式(Map/Wifi/etc)。时间跨度不超过 1 个月。 -### 查询个人某日考勤详情 -``` -Usage: - dws attendance record get [flags] -Example: - dws attendance record get --user 011769261608 --date 2026-03-08 -Flags: - --user string 钉钉用户 ID (必填,别名 --users 亦可) - --date string 查询日期, 格式 YYYY-MM-DD (必填) -``` - -查询单个用户在**某一天**的考勤详情(区别于 `check record`/`check result` 的多人时间段批量查询)。返回 `result` 对象含 `isHasSchedule`(当日是否有排班)、`isRest`(是否休息日)、`isUnSigned`(是否未打卡)、`recordList`(打卡明细)、`approveList`(当日审批单)、`workOvertime`、`workTimeDesc` 等字段。`--user` 只接受**单个** userId;查 userId 用 `dws contact user search --query "姓名"`。 - -### 查询审批单 +### 查询审批单(补卡/加班/请假/出差外出) ``` Usage: dws attendance approve list [flags] Example: dws attendance approve list --users userId1 --types overtime,leave --start 2026-04-01 --end 2026-04-30 + dws attendance approve list --users userId1 --types trip --start 2026-04-01 --end 2026-04-30 # 同时返回出差与外出 + dws attendance approve list --users userId1 --types 加班,请假,补卡 --start 2026-04-01 --end 2026-04-30 Flags: --start string 起始日期, 格式 YYYY-MM-DD (必填) --end string 结束日期, 格式 YYYY-MM-DD (必填) - --types string 审批类型, 逗号分隔: overtime/trip/leave/patch (必填) + --types string 审批类型, 逗号分隔: overtime/加班、trip/travel/business_trip/出差/外出、leave/请假、patch/repair-check/补卡 (必填) --users string 用户 ID 列表, 逗号分隔 (必填) ``` -审批类型映射:overtime=加班, trip=出差, leave=请假, patch=补卡。返回每条记录含:用户 ID、审批标签、审批子类型、审批类型、生效时间、时长、时长单位、流程实例 ID。 +审批类型映射(关键词 → bizType): +- `overtime` / `加班` → `1` +- `trip` / `travel` / `business_trip` / `business-trip` / `出差` / `外出` → `2`(**服务端查询接口 bizType=2 同时覆盖出差与外出,两者合并为同一类、不再细分**;传入任一别名都会返回这两类记录) +- `leave` / `请假` → `3` +- `patch` / `repair-check` / `repair_check` / `补卡` → `4` -### 查询补卡/请假/加班审批提交链接 (必须走引导流程) +> 查询不区分外出与出差,如果需要在提交入口区分外出(`TRAVEL`)与出差(`OUT`),请改用 `dws attendance approve templates --type travel|out`。 + +返回每条记录含:用户 ID、审批标签、审批子类型、审批类型、生效时间、时长、时长单位、流程实例 ID。 + +### 查询补卡/请假/加班/外出/出差审批提交链接 (必须走引导流程) ``` Usage: dws attendance approve templates [flags] @@ -91,13 +120,15 @@ Example: dws attendance approve templates --type leave dws attendance approve templates --type REPAIR_CHECK dws attendance approve templates --type 加班 + dws attendance approve templates --type travel # 外出,等价 --type TRAVEL + dws attendance approve templates --type 出差 # 出差,等价 --type OUT Flags: - --type string 审批类型:repair-check/patch/补卡、leave/请假、overtime/加班,或 REPAIR_CHECK/LEAVE/OVERTIME(必填) + --type string 审批类型:repair-check/patch/补卡、leave/请假、overtime/加班、travel/外出、out/trip/出差,或 REPAIR_CHECK/LEAVE/OVERTIME/TRAVEL/OUT(必填) ``` -当用户提到需要提交补卡、请假或加班时,优先使用该命令查询考勤审批表单模板提交链接,并引导用户点击返回的 `submitUrl` 提交。 +当用户提到需要提交补卡、请假、加班、外出或出差时,优先使用该命令查询考勤审批表单模板提交链接,并引导用户点击返回的 `submitUrl` 提交。 `corpId` 和 `opUserId` 由系统参数自动注入,无需通过命令参数传入。 -审批类型映射:补卡=`REPAIR_CHECK`,请假=`LEAVE`,加班=`OVERTIME`。返回结果为列表,每条记录包含 `approveType`、`formName`、`processCode`、`submitUrl`。 +审批类型映射:补卡=`REPAIR_CHECK`,请假=`LEAVE`,加班=`OVERTIME`,外出=`TRAVEL`,出差=`OUT`(`trip` / `business_trip` / `business-trip` 亦映射为 `OUT`)。返回结果为列表,每条记录包含 `approveType`、`formName`、`processCode`、`submitUrl`。 #### 引导用户自主选择合适的表单模板流程 如果返回多个表单模板,必须将多个可用模板都返回给用户,并引导用户根据实际场景自主选择合适的模板提交: - 请假场景:可根据 `formName` 将与用户请假类型更匹配的模板放在前面展示。例如用户明确说年假、事假、病假、调休时,将名称中包含对应假期类型的模板靠前;如果用户只泛化表达“请假”,将名称最通用的请假模板靠前,例如“请假”“员工请假”“通用请假”等,避免把专项或特殊场景模板放在最前。 @@ -110,20 +141,21 @@ Usage: dws attendance schedule import [flags] Example: dws attendance schedule import --group-id 123456 \ - --schedules '[{"userId":"user001","classId":123,"workDate":"2026-04-22","isRest":"N"}]' \ + --schedules '[{"userId":"user001","classId":123,"workDate":"2026-04-22","checkBeginTime":"09:00","checkEndTime":"18:00"}]' \ --yes Flags: - --group-id string 考勤组(必填,传入考勤组ID);--help 主名为 --groupId,--group-id 为别名 - --schedules string 排班记录 JSON 数组(必填);--help 主名为 --scheduleVOS,--schedules 为别名 - --yes 跳过确认提示;--help 主名为 --user-say-yes,--yes 为别名 + --group-id string 考勤组(必填,传入考勤组ID) + --schedules string 排班记录 JSON 数组(必填) + --yes 跳过确认提示 ``` 为排班制考勤组导入排班记录。`--schedules` 为 JSON 数组,每条记录包含: -- `userId`: 员工ID(必填) -- `classId`: 班次ID(必填) -- `workDate`: 工作日期(YYYY-MM-DD),如 2026-04-22(必填) -- `isRest`: 是否休息日 Y/N(**必填**,服务端要求传入) -- `checkBeginTime` / `checkEndTime`: 开始/结束打卡时间(可传,但当前不会进入后端 payload,实际打卡时段以 `classId` 对应班次为准) +- `userId`: 员工ID +- `classId`: 班次ID +- `workDate`: 工作日期(YYYY-MM-DD),如 2026-04-22 +- `checkBeginTime`: 开始打卡时间 +- `checkEndTime`: 结束打卡时间 +- `isRest`: 是否休息日 Y/N(可选) #### AI 调用 `schedule import` 的二次确认流程 @@ -271,7 +303,7 @@ Flags: --adjustment-id int 补卡规则主键 ID (必填) ``` -**注意:本命令当前拿不到有效的补卡规则详情,不要依赖它。** 服务端对任意 `--adjustment-id`(含不存在的 ID)都返回同一个 `{"success":true,"有效期类型":"..."}`,不返回规则明细,也无法据此判断规则是否存在;且 `adjustment search` 返回的默认补卡规则 `entityVO.id` 为 `null`,`search → get` 取 id 的链路走不通。补卡规则内容请直接看 `adjustment search` 的返回结果。 +根据补卡规则主键 ID 查询对应的补卡规则详情。主键 ID 可从 `adjustment search` 返回结果中提取,也有可能来源于用户手动输入。**注意:已被删除或被更新覆盖的补卡规则无法查询到。** ### 分页查询加班规则,支持按名称搜素 ``` @@ -367,8 +399,8 @@ Flags: --remove-users string 删除考勤人员 userId 列表, 逗号分隔, 最多 20 个 (可选) --add-extra-users string 添加无需考勤的人员 userId 列表, 逗号分隔, 最多 20 个 (可选) --remove-extra-users string 删除无需考勤的成员 userId 列表, 逗号分隔, 最多 20 个 (可选) - --add-depts string 添加考勤部门 ID 列表, 逗号分隔, 最多 20 个 (可选) - --remove-depts string 删除考勤部门 ID 列表, 逗号分隔, 最多 20 个 (可选) + --add-depts string 添加考勤部门 ID 列表, 逗号分隔, 最多 20 个 (可选),若要添加全公司,根部门id为-1 + --remove-depts string 删除考勤部门 ID 列表, 逗号分隔, 最多 20 个 (可选),全公司根部门id为-1 ``` 对指定考勤组的成员进行增删操作。--group-id 必填,其余参数均为可选,但至少需要传入一个变更项,否则命令拒绝执行。每次调用各参数最多传 20 个 ID。"无需考勤"人员指考勤组内豁免打卡的成员(如高管)。 @@ -539,6 +571,8 @@ Flags: 当使用 `--group-vo` 传入完整 JSON 时,`--name`、`--type`、`--owner` 仍可覆写 JSON 中的同名字段。由于保存考勤组耗时较久,建议加 `--timeout 10`。 +> **创建成功后的跳转链接**:`group create` 调用成功后,CLI 会在标准输出之后额外打印一条钉钉 PC 端跳转链接(仅在响应中同时包含 `corpId` 与 `groupId`/`id` 时输出),格式为 `dingtalk://dingtalkclient/page/link?url=https%3A%2F%2Fhrmregister.dingtalk.com%2Fsubapp%2Fattend%2Findex%3Fcode%3Dattend%26corpId%3D{corpId}%26ddtab%3Dtrue%26from%3Dattend%23%2FgroupModify%3Fid%3D{groupId}`,用于用户在钉钉 PC 客户端一键打开该考勤组详情页验证创建结果。Agent 遇到该跳转链接时应原样呈现给用户,不要对链接进行二次编码或裁剪。 + > **新建考勤组并同时添加成员的工作流**:`group create` 不支持在创建时直接传入成员列表。若用户需要在新建考勤组后立即添加成员,必须先执行 `group create` 创建考勤组,从返回结果中提取 `groupId`,再执行 `group update-members --group-id --add-users ...` 完成成员添加。 ### 查询某个人的考勤统计摘要 @@ -546,16 +580,12 @@ 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" --stats-type month + 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 (必填) ``` -`summary` 必须同时传 `--user`、`--date`、`--stats-type`,缺一即报错(如 C0002)。`--stats-type` 只能是 `week`(周统计)或 `month`(月统计)。 - ### 查询考勤组与考勤规则 ``` Usage: @@ -817,10 +847,10 @@ Example: dws attendance vacation balance --users userId1,userId2 --leave-code a1b2c3d4-e5f6-7890-abcd-ef1234567890 Flags: --users string 目标员工 ID 列表, 逗号分隔 (必填) - --leave-code string 假期规则 code (必填,服务端要求非空,不传返回 INVALID_PARAMS) + --leave-code string 假期规则 code (选填,不传则查询所有假期规则余额) ``` -调用 MCP 工具 get_leave_balance_quota 查询指定员工的假期余额。例如:查询某员工年假还剩多少、病假额度等。`--leave-code` 可通过 `vacation types` 获取。**注意:`--help` 虽标"选填",但服务端要求 `leaveCode` 非空,实际必填;不传会返回 `INVALID_PARAMS`(corpId、opUserId、leaveCode、targetUserIds 不能为空)。** 若要一次查所有假期规则余额,必须走 [attendance-vacation.md](./attendance-vacation.md) 工作流脚本逐个规则遍历。认证信息(corpId、opUserId)由系统自动注入。 +调用 MCP 工具 get_leave_balance_quota 查询指定员工的假期余额。例如:查询某员工年假还剩多少、病假额度等。`--leave-code` 可通过 `vacation types` 获取;不传 `--leave-code` 时查询所有假期规则余额。认证信息(corpId、opUserId)由系统自动注入。 如用户需要“所有假期规则余额 / 导出假期余额列表 / 所有假期规则余额 Excel / 按截图样式导出假期余额”,必须先读取 [attendance-vacation.md](./attendance-vacation.md),再按其中工作流调用脚本生成 Excel。 @@ -837,7 +867,7 @@ Flags: --end string 查询结束日期, 格式 YYYY-MM-DD (必填) ``` -调用 MCP 工具 get_leave_balance_records_v2 查询指定员工的假期余额变更记录。例如:查询某员工年假变更历史、请假扣减记录等。`--leave-code` 必填,可通过 `vacation types` 获取。认证信息(corpId、opUserId)由系统自动注入。 +调用 MCP 工具 get_leave_balance_records 查询指定员工的假期余额变更记录。例如:查询某员工年假变更历史、请假扣减记录等。`--leave-code` 可通过 `vacation types` 获取。认证信息(corpId、opUserId)由系统自动注入。 ### 更新假期规则(写场景接口,必须走二次确认流程) @@ -858,8 +888,11 @@ Example: # 更新假期单位 dws attendance vacation update-type --leave-code a1b2c3d4-e5f6-7890-abcd-ef1234567890 --unit hour --per-hours 8 - # 更新适用范围 + # 改为指定部门可见 dws attendance vacation update-type --leave-code a1b2c3d4-e5f6-7890-abcd-ef1234567890 --visibility-rules '[{"type":"dept","visible":["1","2","3"]}]' + + # 改为全公司可见(哨兵约定:必须显式传 "-1",空数组 [] 不生效) + dws attendance vacation update-type --leave-code a1b2c3d4-e5f6-7890-abcd-ef1234567890 --visibility-rules '[{"type":"dept","visible":["-1"]}]' Flags: --leave-code string 假期编码(必填) --name string 假期名称(可选) @@ -868,12 +901,29 @@ Flags: --per-hours int 一天折算小时数(可选) --when-can-leave string 新员工请假规则:entry/formal(可选) --visibility-rules string 适用范围规则 JSON 数组(可选) + - 不传:不修改原有可见范围 + - 改为指定范围:[{"type":"staff|dept|label","visible":["id1",...]}, ...] + - 改为全公司可见(哨兵):必须显式传 [{"type":"dept","visible":["-1"]}] + - 空数组 []、[{}]、visible 为空等 → CLI 报错(HSF 端会静默忽略) --user-say-yes 用户已确认,跳过交互式确认提示 Agent 调用时传 true 前必须完成用户二次确认 ``` 调用 MCP 工具 save_leave_type 更新已有假期规则。`--leave-code` 必填,指定要更新的假期规则编码。其他字段均为可选,仅需传入要修改的字段。除 `--leave-code` 外,必须至少传入一个更新字段。 +**`--visibility-rules` HSF 反序列化约定(必读)**: + +| 入参形态 | 业务语义 | CLI / 服务端处理 | +|---|---|---| +| 不传 | 不修改可见范围 | 服务端保留原有 `visibilityRules` | +| `[{"type":"dept","visible":["-1"]}]`(哨兵) | 改为全公司可见 | 服务端落库为空数组(全公司语义) | +| `[{"type":"staff","visible":["uid1"]}, ...]` 等有效规则 | 改为指定可见范围 | 覆盖原有 `visibilityRules` | +| 空数组 `[]` / `[{}]` / `visible` 为空 | — (反例) | **CLI 直接报错**,避免 HSF 端被静默忽略 | + +- `type` 可取 `dept`(部门 ID,`-1` 是根部门 = 全公司哨兵)、`staff`(userId)、`label`(角色 ID)。 +- 哨兵识别采用宽松匹配:传入列表中任意一条规则满足 `type=dept` 且 `visible` 包含 `"-1"`,即视为全公司可见,其它规则被忽略。 +- 若意图是“改为全公司可见”,**必须**显式传哨兵值 `["-1"]`,**不能**传空数组 `[]`。 + #### 强制执行流程:Agent 调用 `vacation update-type` `vacation update-type` 是写操作,会修改假期规则配置。Agent 调用时 **必须按以下流程执行**,**禁止**在未确认的情况下直接提交: @@ -972,7 +1022,7 @@ Usage: dws attendance checkin records [flags] Example: dws attendance checkin records \ - --operator-corp-id corp001 --operator-staff-id op001 --staff-ids user001,user002 --start "2026-04-01 00:00:00" --end "2026-04-07 00:00:00" + --operator-staff-id op001 --staff-ids user001,user002 --start "2026-04-01 00:00:00" --end "2026-04-07 00:00:00" Flags: --end string 结束时间, 格式 yyyy-MM-dd HH:mm:ss(必填) --operator-corp-id string 操作者企业 ID(必填) @@ -983,8 +1033,27 @@ Flags: 调用 MCP 工具 get_checkin_record 查询指定员工在一段时间内的签到记录。权限说明:Boss/超级管理员可查看全公司员工,子管理员可查看管理范围内员工,部门主管可查看所管理部门员工,普通员工只能查询自己。接口单次最多返回100条签到记录。 +返回结构:`result.list` 为签到记录数组,每条记录包含以下字段(按 sortedProps 顺序): +- `corpId`(string):企业 ID +- `name`(string):用户名称 +- `userId`(string):用户 ID +- `timestamp`(number):签到时间 +- `place`(string):签到地点 +- `detailPlace`(string):签到详细地点 +- `longitude`(number):签到地点经度 +- `latitude`(number):签到地点纬度 +- `remark`(string):备注信息 +- `imageList`(string[]):图片列表 +- `customers`(string):拜访客户 +- `checkinType`(string):签到类型 +- `mobileId`(string):设备 ID + +顶层还包含 `success`(boolean)和 `arguments`(object[])字段。 + ## 意图判断 +用户说"签到记录/签到数据/签到明细/外勤签到/导出签到" → `checkin records`(查询签到记录)。如果用户意图是**导出签到报表/签到Excel**,则走下方的签到报表导出工作流。 + - **优先级**:只要用户句中含"签到"二字,就走本条,不要走 `check record`。"签到" ≠ "打卡",签到是外勤场景的独立功能 用户说"打卡记录/出勤/考勤" → `check record` 用户说"指定用户打卡结果/考勤结果/迟到早退/缺卡异常" → `check result` 用户说"指定用户打卡流水/打卡详情/打卡时间地点/打卡记录详情" → `check record` @@ -1002,7 +1071,7 @@ Flags: 用户说"班次详情/某个班次的具体信息" → `class search --name "..."`(search 直出,直接返回详情)。`class get` 仅在需要按已知 classId 精确查询时使用 用户说"更新班次/修改班次/班次改名/修改上下班时间" → `class update` 用户说"补卡规则/补卡设置" → `adjustment search`(返回结果已包含全量属性,无需再调 get) -用户说"补卡规则详情/某条补卡规则的具体信息" → `adjustment search --name "..."`(search 直出,已含全量属性)。**不要用 `adjustment get`**:当前服务端对任意 id 都只返回"有效期类型"、拿不到规则明细 +用户说"补卡规则详情/某条补卡规则的具体信息" → `adjustment search --name "..."`(search 直出)。`adjustment get` 仅在需要按已知 adjustmentId 精确查询时使用 用户说"加班规则/加班设置/加班计算" → `overtime search`(返回结果已包含全量属性,无需再调 get) 用户说"加班规则详情/某条加班规则的具体信息" → `overtime search --name "..."`(search 直出)。如需查已删除/被覆盖的历史记录 → `overtime get` 用户说"考勤组列表/有哪些考勤组" → `group search` @@ -1011,18 +1080,27 @@ Flags: 用户说"更新考勤组成员/添加考勤人员/删除考勤人员/添加考勤部门/删除考勤部门/加入考勤组/移出考勤组/设置无需考勤/取消无需考勤" → `group update-members` 用户说"修改考勤组/更新考勤组配置/考勤组改名/改变考勤组绑定的班次/修改打卡范围/设置考勤组负责人" → `group update` 用户说"创建考勤组/新建考勤组/添加考勤组" → `group create` -用户说"考勤汇总/统计" → `summary` +用户说"查询某人的考勤汇总/考勤统计/周统计/月统计" → `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` @@ -1038,7 +1116,7 @@ Flags: ```bash # 导入排班记录 dws attendance schedule import --group-id 123456 \ - --schedules '[{"userId":"user001","classId":123,"workDate":"2026-04-22","isRest":"N"}]' \ + --schedules '[{"userId":"user001","classId":123,"workDate":"2026-04-22","checkBeginTime":"09:00","checkEndTime":"18:00"}]' \ --yes --format json # 获取排班记录 — 禁止直接调用,必须走 attendance-schedule.md 排班查询导出工作流 @@ -1094,7 +1172,7 @@ dws attendance group create --name "研发考勤组" --type FIXED --group-vo '{" 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 summary --user --date "2026-03-12 15:00:00" --format json # 查看考勤组和规则 dws attendance rules --date 2026-03-14 --format json @@ -1127,13 +1205,13 @@ dws attendance report query-leave --users userId1,userId2 \ dws attendance vacation types --format json # 查看指定员工假期余额 -dws attendance vacation balance --users userId1,userId2 --leave-code <假期类型code> --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 --leave-code <假期类型code> --start 2026-04-01 --end 2026-04-22 --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 \ @@ -1143,10 +1221,14 @@ dws attendance vacation update-type --leave-code a1b2c3d4-e5f6-7890-abcd-ef12345 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 \ @@ -1172,7 +1254,7 @@ dws attendance vacation save-balance --target user001 \ --num 8 --reason "绩效奖励发放3天" --format json # 查询签到记录 -dws attendance checkin records --operator-corp-id corp001 --operator-staff-id op001 --staff-ids user001,user002 \ +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 ``` @@ -1182,9 +1264,9 @@ dws attendance checkin records --operator-corp-id corp001 --operator-staff-id op | `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 | +| `aisearch person` | `userId` | schedule import/get 的 userId | -| `contact user get-self` / `contact user search` | `userId` | summary 的 --user, vacation records 的 --user;selfsetting get/save 的 --user(必填) | +| `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 | ## 注意事项 @@ -1203,7 +1285,7 @@ dws attendance checkin records --operator-corp-id corp001 --operator-staff-id op - `class get` 的 `--class-id` 必填,班次 ID 可从 `class search` 结果中提取 - `class search` 返回结果已包含全量属性,无需再调用 `class get`;`class get` 仅在需要按已知 classId 精确查询时使用 - `class update` 的 `--class-id` 必填,其余均可选,仅需对要修改的字段赋值,未传字段会自动从已有配置补充;由于保存班次耗时较久,建议加 `--timeout 10` -- `adjustment search` 返回结果已包含全量属性;`adjustment get` 当前服务端对任意 id 都只返回"有效期类型"、无规则明细,不可用,补卡规则详情一律看 `adjustment search` 返回 +- `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 @@ -1212,7 +1294,7 @@ dws attendance checkin records --operator-corp-id corp001 --operator-staff-id op - `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` +- `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 @@ -1225,11 +1307,16 @@ dws attendance checkin records --operator-corp-id corp001 --operator-staff-id op - 用户 ID 需从 `contact user get-self` 或 `aisearch person` 获取 - 考勤组 ID 需从 `rules` 命令返回结果中获取 - `vacation types` 无需任何参数,认证信息自动注入 -- `vacation balance` 的 `--users` 为目标员工 ID 列表,逗号分隔;`--leave-code` 服务端要求非空、实际必填(`--help` 标"选填"不准),不传返回 `INVALID_PARAMS`,可通过 `vacation types` 获取 -- `vacation records` 的 `--start/--end` 使用 YYYY-MM-DD 格式,CLI 自动转换为毫秒时间戳;`--leave-code` 必填(不传无法查询),底层 MCP 工具为 `get_leave_balance_records_v2` +- `vacation balance` 的 `--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 数组字符串,格式:`[{"type":"dept","visible":["1","2","3"]}]`,type 可取值 staff/label/dept +- `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` 查询当前余额,再计算新值,避免误操作 @@ -1292,18 +1379,19 @@ dws attendance boss-check --plan-id 948964045503 --time "2026-05-13 18:00" --res | 脚本 | 场景 | 用法 | |------|------|------| -| [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_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) 工作流编排使用,自动处理 `--users` 超过 20 人分批、`--start/--end` 超过 32 天按月切片,输出 `attendance_report___<粒度>.xlsx` +> - `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 @@ -1315,3 +1403,63 @@ dws attendance boss-check --plan-id 948964045503 --time "2026-05-13 18:00" --res - 遇到时长字段时,注意区分单位是秒、分钟还是小时 - 遇到意图不清晰的场景不要猜测,主动询问用户明确意图 - 如果查询结果很多时,不要自作主张省略,必须明确告知用户或者用表格或展示所有。 + +--- + +## SKILL 摘要(原 dingtalk-attendance/SKILL.md 正文) + + +## Shortcuts(无专用脚本/recipe 时优先) + +以下 shortcut 同时进入公开 catalog 与 Runtime Schema。先按本 skill 的意图表、脚本和 recipe 路由:存在精确覆盖该场景的专用脚本/recipe 时按其执行;否则用户意图命中时,shortcut 优先于手写原子命令。用 leaf Schema(例如 `dws schema --cli-path "attendance +" --format json`)读取 Agent 选择、参数、约束、风险和确认语义;用 `dws shortcut list --service attendance --format json` 批量发现;最后以 `dws attendance --help` 核对当前 Cobra flags。 + +| 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-misc/references/devdoc-intent-guide.md b/skills/multi/dingtalk-misc/references/devdoc-intent-guide.md new file mode 100644 index 00000000..36b1d57b --- /dev/null +++ b/skills/multi/dingtalk-misc/references/devdoc-intent-guide.md @@ -0,0 +1,7 @@ +# devdoc 局部意图消歧 + +本文件从单 Skill `intent-guide.md` 拆分而来,仅保留与本产品相关的跨产品消歧规则。 + +| 用户说... | 真实意图 | 应该用 | 不要用 | 理由 | +|---|---|---|---|---| +| "搜一下 OAuth2 接入文档" | 搜索开发文档 | `devdoc` | `doc search` | 搜索开放平台技术文档,不是钉钉内部内容 | diff --git a/skills/multi/dingtalk-misc/references/devdoc.md b/skills/multi/dingtalk-misc/references/devdoc.md new file mode 100644 index 00000000..a47c872b --- /dev/null +++ b/skills/multi/dingtalk-misc/references/devdoc.md @@ -0,0 +1,42 @@ +# devdoc — 开放平台文档 + +## 搜索开放平台文档 +``` +Usage: + dws devdoc article search [flags] +Example: + dws devdoc article search --query "OAuth2 接入" --page 1 --size 10 --format json +Flags: + --query string 搜索关键词 (必填) + --page string 页码 (默认 1) + --size string 每页数量 (默认 10) +``` + +## 意图判断 + +- 用户说"开发文档 / API 文档 / 接口文档 / 调用报错" → `devdoc article search` + +## 上下文传递 + +| 操作 | 从返回中提取 | 用于 | +|------|-------------|------| +| `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-misc/references/ding-intent-guide.md b/skills/multi/dingtalk-misc/references/ding-intent-guide.md new file mode 100644 index 00000000..23e0da9a --- /dev/null +++ b/skills/multi/dingtalk-misc/references/ding-intent-guide.md @@ -0,0 +1,13 @@ +# ding 局部意图消歧 + +本文件从单 Skill `intent-guide.md` 拆分而来,仅保留与本产品相关的跨产品消歧规则。 + +| 用户说... | 真实意图 | 应该用 | 不要用 | 理由 | +|---|---|---|---|---| +| "DING消息/查DING/DING历史" | 查询 DING 消息列表 | `ding message list` | `chat message list` | ding 是独立顶层命令;ding message list 查 DING 消息;chat message list 查普通聊天消息 | +| "DING接收状态/谁收到了DING" | DING 接收状态 | `ding message receiver-status` | `chat message read-status` | ding 是独立顶层命令;receiver-status 查 DING 接收;chat message read-status 查普通消息已读 | +| "发DING/DING通知" | 发送 DING 消息 | `ding message send` | `chat message send` | DING 是钉钉的强提醒(应用内/短信/电话),独立顶层命令;普通群消息用 chat | +| "撤回DING" | 撤回 DING 消息 | `ding message recall` | `chat message recall` | DING 撤回独立命令;chat recall 是撤回普通聊天消息 | +| "以我的名义发DING/个人发DING/用户身份DING" | 以用户身份发 DING | `ding message send-personal` | `ding message send` | send-personal 以用户身份发送,无需 robot-code;send 以机器人身份发送 | +| "以我的名义撤回DING/个人撤回DING" | 以用户身份撤回 DING | `ding message recall-personal` | `ding message recall` | recall-personal 以用户身份撤回;recall 以机器人身份撤回 | +| "消息转DING/把这条消息DING给某人/转发为DING" | 消息转 DING | `ding message send-by-message` | `ding message send-personal` | send-by-message 是将已有消息转为 DING,需指定原消息;send-personal 是直接发新 DING | diff --git a/skills/multi/dingtalk-misc/references/ding-lite-recipes.md b/skills/multi/dingtalk-misc/references/ding-lite-recipes.md new file mode 100644 index 00000000..9f71da04 --- /dev/null +++ b/skills/multi/dingtalk-misc/references/ding-lite-recipes.md @@ -0,0 +1,7 @@ +# ding Lite Recipe + +本文件从单 Skill `lite-recipes.md` 拆分而来,仅保留与本产品相关的轻量流程。 + +## #1 消息沟通 + +所有消息沟通相关的命令详情、参数说明、意图路由和复合工作流,请查阅 [chat.md](../../dingtalk-chat/references/chat.md)。 diff --git a/skills/multi/dingtalk-misc/references/ding.md b/skills/multi/dingtalk-misc/references/ding.md new file mode 100644 index 00000000..4c0c3543 --- /dev/null +++ b/skills/multi/dingtalk-misc/references/ding.md @@ -0,0 +1,200 @@ +# DING 消息 (ding) 命令参考 + +## 命令总览 + +### 发送 DING 消息 +``` +Usage: + dws ding message send [flags] +Example: + dws ding message send --robot-code --users , --content "请查看" +Flags: + --content string 消息内容 (必填) + --robot-code string 机器人 ID (必填, 可从 应用管理→机器人 获取, 或设 DINGTALK_DING_ROBOT_CODE) + --users string 接收人 userId 列表 (必填) + --type string 提醒类型: app/sms/call (默认 app) +``` + +### 撤回 DING 消息 +``` +Usage: + dws ding message recall [flags] +Example: + dws ding message recall --robot-code --id +Flags: + --id string DING 消息 ID (必填) + --robot-code string 机器人 ID (必填, 或设 DINGTALK_DING_ROBOT_CODE) +``` + +### 查询 DING 消息历史 +``` +Usage: + dws ding message list [flags] +Example: + dws ding message list + dws ding message list --type UNREAD + dws ding message list --type SEND --cursor 10 +Flags: + --cursor int 分页游标 (首次传 0, 翻页传返回的 nextCursor) + --type string 消息类型: ALL / UNREAD / SEND / NEW_COMMENT / DELETED (可选, 不传返回全部) +``` + +### 查看 DING 接收状态 +``` +Usage: + dws ding message receiver-status [flags] +Example: + dws ding message receiver-status --ding-id + # 查询 dingId: dws ding message list +Flags: + --ding-id string DING 消息 openDingId (必填) +``` + +### 以用户身份发送 DING — 以当前用户身份(非机器人)发送 DING 消息 +``` +Usage: + dws ding message send-personal [flags] +Example: + dws ding message send-personal --users openDingTalkId1,openDingTalkId2 --content "请查看" + dws ding message send-personal --type call --users openDingTalkId1 --content "紧急告警" + # 查询 openDingTalkId: dws aisearch person --keyword "姓名" --dimension name +Flags: + --users string 接收者 openDingTalkId 列表,逗号分隔 (必填) + --content string DING 内容 (必填) + --type string 提醒类型: app/sms/call (默认 app) + --uuid string 幂等唯一标识(可选,不传由服务端生成) + +注意: + - 与 `ding message send`(机器人身份)不同:send-personal 以当前用户身份发送,无需 --robot-code + - 接收者使用 openDingTalkId(非 userId),可通过 `dws aisearch person --keyword "姓名" --dimension name` 获取 + - sms/call 类型有通信费用,使用前需和用户确认 +``` + +### 以用户身份撤回 DING — 以当前用户身份撤回已发送的 DING 消息 +``` +Usage: + dws ding message recall-personal [flags] +Example: + dws ding message recall-personal --id + # 查询 openDingId: dws ding message list +Flags: + --id string DING 消息 openDingId (必填) + +注意: + - 与 `ding message recall`(机器人身份)不同:recall-personal 以当前用户身份撤回,无需 --robot-code + - openDingId 可通过 `dws ding message list` 或 `send-personal` 返回值获取 +``` + +### 消息转 DING — 将聊天消息转为 DING 通知发送给指定接收者 +``` +Usage: + dws ding message send-by-message [flags] +Example: + dws ding message send-by-message --group --message-id --users id1,id2 + dws ding message send-by-message --group --message-id --users id1 --type sms + # 查询 openDingTalkId: dws aisearch person --keyword "姓名" --dimension name + # 查询 openConversationId: dws chat search --keyword "群名" +Flags: + --group string 原消息所在会话 openConversationId (必填) + --message-id string 原消息 openMessageId (必填) + --users string 接收者 openDingTalkId 列表,逗号分隔 (必填) + --type string 提醒类型: app/sms/call (默认 app) + --uuid string 幂等唯一标识(可选,不传由服务端生成) + +注意: + - 与 `send-personal` 不同: send-by-message 是将已有聊天消息转发为 DING,需要指定原消息的会话和消息 ID + - 接收者使用 openDingTalkId,可通过 `dws aisearch person --keyword "姓名" --dimension name` 获取 + - sms/call 类型有通信费用,使用前需和用户确认 +``` + +## 意图判断 + +用户说"DING 一下/紧急通知/电话提醒" → `message send` +用户说"以我的名义 DING/个人发 DING/用户身份 DING" → `message send-personal` +用户说"消息转 DING/把这条消息 DING 给某人/转发为 DING" → `message send-by-message` +用户说"撤回 DING" → `message recall` +用户说"以我的名义撤回 DING/个人撤回 DING" → `message recall-personal` +用户说"DING 消息/查 DING/DING 历史/我的 DING" → `message list` +用户说"DING 接收状态/谁收到了 DING/DING 已读" → `message receiver-status` + +关键区分: +- `ding message send`(机器人身份,需 --robot-code) vs `ding message send-personal`(用户身份,无需 robot-code) vs `ding message send-by-message`(消息转 DING,需指定原消息) +- `ding message recall`(机器人身份) vs `ding message recall-personal`(用户身份) +- ding(紧急提醒, 支持电话/短信) vs bot(常规群/单聊消息) +- sms/call 类型有通信费用 + +## 核心工作流 + +```bash +# 机器人身份: 应用内 DING (免费) +dws ding message send --robot-code --type app --users userId1,userId2 --content "请查看" --format json + +# 机器人身份: 电话 DING (紧急, 有成本!) +dws ding message send --robot-code --type call --users userId1 --content "紧急告警" --format json + +# 机器人身份: 撤回 +dws ding message recall --robot-code --id --format json + +# 用户身份: 应用内 DING +dws ding message send-personal --users openDingTalkId1,openDingTalkId2 --content "请查看" --format json + +# 用户身份: 电话 DING (紧急, 有成本!) +dws ding message send-personal --type call --users openDingTalkId1 --content "紧急告警" --format json + +# 用户身份: 消息转 DING +dws ding message send-by-message --group --message-id --users openDingTalkId1,openDingTalkId2 --format json + +# 用户身份: 撤回 +dws ding message recall-personal --id --format json +``` +## 上下文传递表 +| 操作 | 提取 | 用于 | +|------|------|------| +| `message send` | `openDingId` | message recall 的 --id | +| `message send-personal` | `openDingId` | message recall-personal 的 --id | +| `message list` | `openDingId` | message receiver-status 的 --ding-id | +| `message send-by-message` | `openDingId` | message recall-personal 的 --id | +## 注意事项 +- `--robot-code` 从钉钉开放平台 应用管理 → 机器人 中获取,也可设环境变量 `DINGTALK_DING_ROBOT_CODE` +- `send` / `recall` 是机器人身份,需要 --robot-code;`send-personal` / `recall-personal` / `send-by-message` 是用户身份,无需 robot-code +- `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 时优先) + +以下 shortcut 同时进入公开 catalog 与 Runtime Schema。先按本 skill 的意图表、脚本和 recipe 路由:存在精确覆盖该场景的专用脚本/recipe 时按其执行;否则用户意图命中时,shortcut 优先于手写原子命令。用 leaf Schema(例如 `dws schema --cli-path "ding +" --format json`)读取 Agent 选择、参数、约束、风险和确认语义;用 `dws shortcut list --service ding --format json` 批量发现;最后以 `dws ding --help` 核对当前 Cobra flags。 + +| 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 53% rename from skills/multi/dingtalk-oa/references/oa.md rename to skills/multi/dingtalk-misc/references/oa.md index 643b0ac7..a973af4d 100644 --- a/skills/multi/dingtalk-oa/references/oa.md +++ b/skills/multi/dingtalk-misc/references/oa.md @@ -12,9 +12,13 @@ Example: Flags: --end string 结束时间 ISO-8601 (如 2026-03-10T23:59:59+08:00) (必填) --page string 分页页码 (可选) - --limit string 每页大小 (可选) + --limit string 每页大小 (可选) --start string 开始时间 ISO-8601 (如 2026-03-10T00:00:00+08:00) (必填) --query string 关键字搜索 (可选) + +**默认时间窗口:** 当用户未指定 --start / --end 时,默认查询最近 30 天的待处理审批。 + +> **IMPORTANT:** 当 `list-pending` 返回空时,必须明确告知用户"当前暂无待处理审批",并建议扩大时间范围或检查关键词。 ``` ### 获取审批实例详情 @@ -88,8 +92,8 @@ Example: dws oa approval list-initiated --process-code --start "2026-03-10T00:00:00+08:00" --end "2026-03-10T23:59:59+08:00" --cursor 0 --limit 20 Flags: --end string 结束时间 ISO-8601 (如 2026-03-10T23:59:59+08:00) (必填) - --limit string 每页大小,最大 20 (必填) - --cursor string 分页游标,首次传 0 (必填) + --limit string 每页大小,最大 20 (必填) + --cursor string 分页游标,首次传 0 (必填) --process-code string 表单 processCode (必填) --start string 开始时间 ISO-8601 (如 2026-03-10T00:00:00+08:00) (必填) ``` @@ -102,9 +106,8 @@ Example: dws oa approval list-forms --cursor 0 --limit 100 Flags: --cursor string 分页游标,首次传 0 (默认 "0") - --limit string 每页大小,最大 100 (默认 "100") + --limit string 每页大小,最大 100 (默认 "100") ``` -MCP 工具: `list_user_visible_process`;参数: cursor, pageSize(对应 --cursor/--limit)。返回结果含 processCode,可用于 list-initiated 的 --process-code。 ### 按关键字模糊搜索审批表单 ``` @@ -153,6 +156,7 @@ dws ding message send --robot-code $DINGTALK_DING_ROBOT_CODE --users --content "请尽快审批《XXX》" --type sms --format json ``` + ### 获取任务可回退的节点信息 > **IMPORTANT:** 退回任务前**必须先调用此命令**获取可回退节点列表,从中提取 `activityId` 和 `revertAction` 作为 `revert-task` 的入参。若无返回值,明确告知用户"当前任务无可回退节点"。 @@ -180,6 +184,7 @@ Flags: **无返回值处理:** 若 `instRevertActivities` 为空或不存在,必须明确告知用户"当前任务无可回退节点",不得继续执行退回操作。 + ### 查询待我审批的任务 ID ``` Usage: @@ -189,7 +194,6 @@ Example: Flags: --instance-id string 审批实例 ID (必填) ``` -MCP 工具: `list_pending_tasks`。 ### 查询我处理过的审批单 @@ -238,7 +242,6 @@ Flags: --to-actioner-id string 转交目标用户 ID (必填) --remark string 转交说明 (可选) ``` -MCP 工具: `redirect_task`;参数: taskId, toActionerId, remark(对应 --task-id/--to-actioner-id/--remark)。taskId 可通过 `tasks` 命令获取,toActionerId 可通过 `dws contact user search` 获取。 ### 对审批实例添加评论 ``` @@ -248,9 +251,8 @@ Example: dws oa approval oa-comments --instance-id --content "同意,请尽快处理" Flags: --instance-id string 审批实例 ID (必填) - --content string 评论内容 (必填) + --content string 评论内容 (必填) ``` -MCP 工具: `dingflow_comments`;参数: processInstanceId, text(对应 --instance-id/--content)。processInstanceId 可通过 `list-pending` 或 `detail` 获取。 ### 对审批实例进行抄送 ``` @@ -258,13 +260,11 @@ Usage: dws oa approval oa-cc-noticer [flags] Example: dws oa approval oa-cc-noticer --instance-id --users "68674200835816" - dws oa approval oa-cc-noticer --instance-id --users "userId1,userId2" --operator-id "123123" + dws oa approval oa-cc-noticer --instance-id --users "userId1,userId2" Flags: --instance-id string 审批实例 ID (必填) - --users string 抄送用户 ID 列表,多个用逗号分隔 (必填) - --operator-id string 操作人 ID (可选) + --users string 抄送用户 ID 列表,多个用逗号分隔 (必填) ``` -MCP 工具: `oa_cc_noticer`;参数: processInstanceId, userList(对应 --instance-id/--users)。processInstanceId 可通过 `list-pending` 或 `detail` 获取,抄送用户 ID 可通过 `dws contact user search` 获取。 ### 对审批任务进行加签 @@ -281,7 +281,7 @@ Flags: --type string 加签类型:before(前加签),after(后加签),Parallel(并加签)(必填) --appender-user-ids string 被加签用户 ID 列表,多个用逗号分隔 (必填) --activate-type string 任务激活类型:ALL(或签),ONE_BY_ONE(依次审批)(必填) - --agree-all string 是否需要全部同意 (必填) 是 true 否 false + --agree-all 是否需要全部同意 (必填) 是 true 否 false ``` ### 退回审批任务 @@ -308,21 +308,45 @@ Flags: ## 意图判断 -用户说"待审批/待处理审批" → `approval list-pending` +用户说"待审批/待处理审批/查询XX审批/查XX审批/有没有XX审批/XX的审批单" → `approval list-pending`,将 XX 作为 `--query` 关键字传入(可搜索表单名称或表单详情内容) + - 示例:"帮我查询补卡的审批单" → `approval list-pending --query 补卡` + - 示例:"有没有外出申请的审批" → `approval list-pending --query 外出申请` + - 示例:"待审批"(无关键词)→ `approval list-pending` 用户说"审批详情/看审批" → `approval detail` 用户说"同意审批/批准" → 先 `tasks` 获取 taskId,再 `approve` 用户说"拒绝审批/驳回" → 先 `tasks` 获取 taskId,再 `reject` 用户说"撤回审批/取消审批" → `approval revoke` 用户说"审批记录/操作历史" → `approval records` -用户说"我发起的审批" → `approval list-initiated`(需 --process-code,可从 list-forms / search-forms / detail 获取) +用户说"我发起的审批" → `approval list-initiated`(需 --process-code,可从 list-forms 或 detail 获取) 用户说"有哪些审批表单/可见表单" → `approval list-forms` +用户说"搜索审批表单/查找xx审批表单/有没有xx表单" → `approval search-forms`(需 --query) +用户说"催办审批/DING 一下审批人/提醒审批/催一下审批/催批/提醒审批人" → 先 `approval ding-info`(拿到被催办人 `userId`),再 `ding message send`(将 userId 作为 `--users` 传入;`--robot-code` 优先走 `$DINGTALK_DING_ROBOT_CODE` 或向用户确认;`--content` 由 agent 根据审批上下文撰写) + - **禁止跳过 ding-info:** 不得自行猜测或编造 userId,必须先调用 `ding-info` 获取 + - **机器人编码获取顺序:** ① `$DINGTALK_DING_ROBOT_CODE` 环境变量 → ② 用户显式提供 → ③ 询问用户 + - **催办内容建议:** `"请尽快审批《{表单名}》(提交人:{发起人},提交时间:{时间})"` + - **ding-info 返回空:** 若接口返回空或报错,告知用户"无法获取该任务的被催办人信息"并停止 用户说"我有哪些待审的任务" → `approval tasks` -用户说"我发起的审批单" -> `approval list-submitted` -用户说"我审批/处理过的审批单" -> `approval list-executed` -用户说"抄送我的审批单" -> `approval list-cc` +用户说"我发起的审批单/我发起的XX审批/我提交的XX审批/查我发起的XX" → `approval list-submitted`,将 XX 作为 `--query` 关键字传入(可搜索表单名称或表单详情内容) + - 示例:"查我发起的补卡审批单" → `approval list-submitted --query 补卡` + - 示例:"我发起的审批单"(无关键词)→ `approval list-submitted` +用户说"我审批/处理过的审批单/我处理过的XX审批/我审批过的XX/查我处理过的XX" → `approval list-executed`,将 XX 作为 `--query` 关键字传入(可搜索表单名称或表单详情内容) + - 示例:"查我处理过的补卡审批单" → `approval list-executed --query 补卡` + - 示例:"我审批过的审批单"(无关键词)→ `approval list-executed` +用户说"抄送我的审批单/抄送我的XX审批/CC我的XX/查抄送我的XX" → `approval list-cc`,将 XX 作为 `--query` 关键字传入(可搜索表单名称或表单详情内容) + - 示例:"查抄送我的补卡审批单" → `approval list-cc --query 补卡` + - 示例:"抄送我的审批单"(无关键词)→ `approval list-cc` 用户说"转交审批/转交任务" → `approval redirect-task`(需 --task-id 和 --to-actioner-id) 用户说"评论审批/添加评论/写评论" → `approval oa-comments`(需 --instance-id 和 --content) 用户说"抄送审批/添加抄送人" → `approval oa-cc-noticer`(需 --instance-id 和 --users) +用户说"加签/前加签/后加签/并加签/增加审批人/追加审批人" → `approval append-task`(需 --instance-id, --task-id, --type, --appender-user-ids, --activate-type, --agree-all) + - `--type` 映射:前加签 → before,后加签 → after,并加签 → Parallel + - `--activate-type` 映射:或签 → ALL,依次审批 → ONE_BY_ONE + - `--appender-user-ids` 可通过 `dws 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` 为空),必须明确告知用户"当前任务无可回退节点",不得继续执行退回操作 + - `--action` 映射:退回发起人/打回重交 → REVERT_FOR_RESUBMIT;退回到审批人/重新审批 → REVERT_FOR_APPROVAL + - `--target-activity-id`:退回发起人时固定传 `sid-startevent`;退回到审批人时从 `revert-activities` 返回中获取 `activityId` ## 核心工作流 @@ -351,19 +375,22 @@ dws oa approval records --instance-id --format json # 7. 获取可见审批表单(得到 processCode) dws oa approval list-forms --cursor 0 --limit 100 --format json -# 8. 查看自己发起的审批列表(--process-code 来自 list-forms / search-forms / detail) +# 7b. 按关键字模糊搜索表单(快速定位 processCode) +dws oa approval search-forms --query AI --format json + +# 8. 查看自己发起的审批列表(--process-code 来自 list-forms 或 detail) dws oa approval list-initiated --process-code \ --start "2026-03-10T00:00:00+08:00" --end "2026-03-10T23:59:59+08:00" \ --cursor 0 --limit 20 --format json - + # 9. 我处理过的审批单 dws oa approval list-executed --limit --page --query 关键词 --format json -# 10. 我发起的审批单 +# 10. 我发起的审批单 dws oa approval list-submitted --limit --page --query 关键词 --format json -# 11. 抄送我的审批单 +# 11. 抄送我的审批单 dws oa approval list-cc --limit --page --query 关键词 --format json -# 12. 转交审批任务(taskId 来自 tasks,toActionerId 来自 contact user search) +# 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 @@ -371,33 +398,110 @@ dws oa approval redirect-task --task-id --to-actioner-id --rem dws oa approval oa-comments --instance-id --content "同意,请尽快处理" --format json # 14. 对审批实例进行抄送(processInstanceId 来自 list-pending 或 detail) -dws oa approval oa-cc-noticer --instance-id --users "68674200835816" --format json -dws oa approval oa-cc-noticer --instance-id --users "userId1,userId2" --format json +dws oa approval oa-cc-noticer --instance-id --user-list "68674200835816" --format json +dws oa approval oa-cc-noticer --instance-id --user-list "userId1,userId2" --format json + +# 15. 催办审批(必须两步串联:先拿被催办人 userId,再发 DING) +# 15a. Step 1: 调用 ding-info 拿到被催办人 userId(来自 list-pending 或 tasks 中的 taskId) +dws oa approval ding-info --task-id --format json +# 15b. Step 2: 将 userId 填入 --users;robot-code 优先走环境变量 $DINGTALK_DING_ROBOT_CODE(或向用户确认);content 由 agent 根据审批上下文撰写 +dws ding message send --robot-code $DINGTALK_DING_ROBOT_CODE --users --content "请尽快审批《XXX》" --format json +# 15c (可选): 如需短信/电话提醒,加 --type sms 或 --type call +dws ding message send --robot-code $DINGTALK_DING_ROBOT_CODE --users --content "请尽快审批《XXX》" --type sms --format json + +# 16. 对审批任务进行加签(instanceId 来自 list-pending/list-submitted/list-executed/detail,taskId 来自list-pending/list-submitted/list-executed/detail中 ,appenderUserIds 来自 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 + +# 17. 退回审批任务(instanceId/taskId 来自 list-pending、tasks;targetActivityId 和 action 来自 revert-activities) +# 17a. 获取可回退节点(必须先调用,从此返回中提取 activityId 和 revertAction) +dws oa approval revert-activities --task-id --format json +# 17b. 退回到发起人重提(targetActivityId 固定 sid-startevent,action=REVERT_FOR_RESUBMIT) +dws oa approval revert-task --instance-id --task-id --target-activity-id sid-startevent --action REVERT_FOR_RESUBMIT --remark "补充说明后重提" --format json +# 17c. 退回到某个审批节点重新审批(targetActivityId 和 action 从 revert-activities 返回中获取) +dws oa approval revert-task --instance-id --task-id --target-activity-id --action REVERT_FOR_APPROVAL --remark "重新审批" --format json ``` ## 上下文传递表 | 操作 | 从返回中提取 | 用于 | |------|-------------|------| -| `list-pending` | `processInstanceId` | detail / tasks / records / revoke / oa-comments / oa-cc-noticer 的 --instance-id | -| `tasks` | `taskId` | approve / reject / redirect-task 的 --task-id | +| `list-pending` | `processInstanceId` | detail / tasks / records / revoke / oa-comments / oa-cc-noticer / append-task / revert-task 的 --instance-id | +| `tasks` | `taskId` | approve / reject / redirect-task / append-task / revert-task 的 --task-id | | `detail` | `processCode` | list-initiated 的 --process-code | | `list-forms` | `processCode` | list-initiated 的 --process-code | | `search-forms` | `processCode` | list-initiated 的 --process-code | +| `ding-info` | `userId` | ding message send 的 --users(多个逗号拼接);**robotCode 优先走 `$DINGTALK_DING_ROBOT_CODE` 环境变量,content 由 agent 根据审批上下文撰写;返回空时报错并停止** | +| `revert-activities` | `activityId`, `revertAction`, `activityName` | revert-task 的 --target-activity-id 和 --action;**返回空时必须告知用户"无可回退节点"** | ## 注意事项 - `--start` / `--end` 使用 ISO-8601 格式(如 2026-03-10T00:00:00+08:00) -- `approve` / `reject` / `redirect-task` 需先通过 `tasks` 获取 `taskId` -- `redirect-task` 的 `--to-actioner-id` 可通过 `dws contact user search` 获取目标用户 userId +- `list-pending` 默认时间窗口为最近 30 天;若用户未指定 `--start` / `--end`,自动使用当前时间往前推 7 天 +- `list-pending` 返回空时必须明确告知用户"当前暂无待处理审批",不得沉默或跳过 +- `approve` / `reject` / `redirect-task` / `append-task` / `revert-task` 需先通过 `tasks` 获取 `taskId` +- `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(不需要全部同意) +- `revert-task` 的 `--action` 值:REVERT_FOR_APPROVAL(退回到某审批节点)、REVERT_FOR_RESUBMIT(退回到发起人) +- `revert-task` 退回前**必须先调用** `revert-activities --task-id ` 获取可回退节点列表 +- `revert-task` 的 `--target-activity-id` 和 `--action` **必须来自** `revert-activities` 返回,禁止自行编造或猜测 +- `revert-activities` 返回空 (`instRevertActivities` 为空) 时,必须明确告知用户"当前任务无可回退节点",禁止继续执行退回 +- `revert-task` 是不可撤回操作,执行前必须向用户确认退回方式及目标节点 - `revoke` 只能撤销自己发起的审批 - `--remark` 审批意见虽为可选,但建议填写以留存审批痕迹 -- `list-initiated` 的 `--process-code` 可从 `list-forms`、`search-forms` 或 `detail` 返回中提取。当 `list-forms` 返回 `processCodeList` 为空(`totalCount -1`)时,用 `search-forms --query <表单名>`(如 `--query 报销`)按名称精准拿 `processCode` 更稳 -- `list-initiated` 的 `--start` / `--end` 区间有后端上限(约 120 天)。超过上限会返回误导性的 `business_error: 时间戳无效`(实为区间过长,不是时间格式问题)。跨度大时请拆成多段短区间分别查询 +- `list-initiated` 的 `--process-code` 可从 `list-forms`、`search-forms` 或 `detail` 返回中提取 +- 已知表单名称关键字时优先用 `search-forms`;需枚举全部表单时用 `list-forms` +- 催办必须两步串联:`ding-info` 仅返回被催办人 `userId`,不返回 robotCode/content;需再调用 `dws ding message send`,其中 `--robot-code` 优先使用环境变量 `$DINGTALK_DING_ROBOT_CODE`,若无则向用户确认;`--content` 由 agent 根据审批上下文撰写催办文案;**严禁跳过 `ding-info` 直接猜测 userId** +- 催办文案建议格式:`"请尽快审批《{表单名}》(提交人:{发起人},提交时间:{时间})"` +- `ding-info` 返回空或报错时,必须明确告知用户"无法获取该任务的被催办人信息"并停止 +- DING 默认发应用内提醒(无成本);如需短信/电话提醒可加 `--type sms` 或 `--type call`(有成本,建议向用户确认) ## 自动化脚本 | 脚本 | 场景 | 用法 | |------|------|------| -| [oa_pending_review.py](../../scripts/oa_pending_review.py) | 查看待审批列表+逐条显示详情 | `python oa_pending_review.py --days 7` | -| [oa_batch_approve.py](../../scripts/oa_batch_approve.py) | 批量同意/拒绝审批项 | `python oa_batch_approve.py --action approve --days 7` | +| [oa_pending_review.py](../scripts/oa_pending_review.py) | 查看待审批列表+逐条显示详情 | `python oa_pending_review.py --days 7` | +| [oa_batch_approve.py](../scripts/oa_batch_approve.py) | 批量同意/拒绝审批项 | `python oa_batch_approve.py --action approve --days 7` | + +--- + +## SKILL 摘要(原 dingtalk-oa/SKILL.md 正文) + + +## Shortcuts(无专用脚本/recipe 时优先) + +以下 shortcut 同时进入公开 catalog 与 Runtime Schema。先按本 skill 的意图表、脚本和 recipe 路由:存在精确覆盖该场景的专用脚本/recipe 时按其执行;否则用户意图命中时,shortcut 优先于手写原子命令。用 leaf Schema(例如 `dws schema --cli-path "oa +" --format json`)读取 Agent 选择、参数、约束、风险和确认语义;用 `dws shortcut list --service oa --format json` 批量发现;最后以 `dws oa --help` 核对当前 Cobra flags。 + +| 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-misc/references/report-conventions.md b/skills/multi/dingtalk-misc/references/report-conventions.md new file mode 100644 index 00000000..803212b8 --- /dev/null +++ b/skills/multi/dingtalk-misc/references/report-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-misc/references/report-intent-guide.md b/skills/multi/dingtalk-misc/references/report-intent-guide.md new file mode 100644 index 00000000..4f19e0c6 --- /dev/null +++ b/skills/multi/dingtalk-misc/references/report-intent-guide.md @@ -0,0 +1,11 @@ +# report 局部意图消歧 + +本文件从单 Skill `intent-guide.md` 拆分而来,仅保留与本产品相关的跨产品消歧规则。 + +| 用户说... | 真实意图 | 应该用 | 不要用 | 理由 | +|---|---|---|---|---| +| "帮我看看收到的日报" | 收到的日志 | `report` | `doc` | 钉钉日志系统(日报/周报),不是文档 | +| "帮我创建一个待办提醒" | 个人待办 | `todo` | `report` | 个人任务提醒,不是日志汇报 | +| "把最近几次关于XX的会议汇总成报告" | 按主题汇总多次听记 | #5 generate-topic-report | #7 meeting-followup | #7 是单次会议听记跟进;多次会议按主题汇总属于工作汇报 | +| "整理一下XX项目的所有讨论" | 跨源主题归档 | #5 generate-topic-report | #4 write-doc | #4 侧重单篇文档创作;按主题跨听记/群消息汇总属于工作汇报 | +| "我发给某人的消息/邮件/文档/今天我干了什么" | 搜行为记录 | `aisearch behavior` | `chat` / `mail` / `doc` / `report` | 关注“谁对什么做过什么”,走 behavior;具体 `behavior-type/direction/chat-scope` 抽槽见 `aisearch.md` | diff --git a/skills/multi/dingtalk-misc/references/report-lite-recipes.md b/skills/multi/dingtalk-misc/references/report-lite-recipes.md new file mode 100644 index 00000000..0931801a --- /dev/null +++ b/skills/multi/dingtalk-misc/references/report-lite-recipes.md @@ -0,0 +1,25 @@ +# report Lite Recipe + +本文件从单 Skill `lite-recipes.md` 拆分而来,仅保留与本产品相关的轻量流程。 + +## #5 工作汇报 + +### query-report-list + +1. 收到的日志:先把用户时间词转成起止时间,再执行 `report inbox list --start "" --end "" --cursor 0 --size 20 --format json`。用户只说“最近/近期/最近收到”时默认最近 7 天。 +2. 我发过/我创建的日志:首条查询必须用 `report outbox list --cursor 0 --size 20 --format json`;如用户指定时间,补 `--start "" --end ""`。 +3. 按发件人过滤收件箱:先 `aisearch person --keyword "<姓名>" --dimension name --format json` 取 `userId/staffId`,再加 `--sender-user-ids `;空结果必须说明未找到该发件人的日志,不得改选其他人。 +4. 面向用户时必须基于 `result[]` 拼 Markdown 表,表头固定为 `日期 | 标题 | 发送人 | 状态 | 钉钉链接`;每条 `result[]` 都会带这五个中文字段,不要把 `reportId` / `日志ID` 作为主列。 +5. 用户要正文、详情、统计、汇总或总结多篇日志时,必须用内部保留的 `reportId` 逐篇执行 `report entry get --report-id --format json` 或 `report entry stats --report-id --format json`;选前 5 篇时调用次数应等于实际选中篇数。 + +时间 flag 硬约束:只允许 `--start` / `--end`;禁止 `--start-date` / `--end-date` / `--date`。不要只传 `2026-05-04`,必须展开成 `2026-05-04T00:00:00+08:00` 这种完整 ISO;禁止 UTC `Z` / `date -u`。 + +硬约束:`report inbox list` 是收到的日志(别人发给我),`report outbox list` 是我创建/发出的日志(我发给别人)。不要混淆方向;不要回答"API 不支持收到的日志"。 + +> 旧命令兼容:`report list` / `report inbox` / `report sent` / `report created` / `report detail` / `report stats` 仍可执行,但已 deprecated,stderr 会打废弃提醒,新计划一律使用 `inbox list` / `outbox list` / `entry get` / `entry stats`。 + +禁止:不要先查 help,不要为了格式化列表创建脚本;不要传 `--size 50/100`。`report inbox` 可作为兼容入口使用,但新计划优先写规范命令 `report inbox list --start "" --end "" --cursor 0 --size 20 --format json`。 + +### check-report-read-status + +`report entry stats --report-id ` → 已读/未读 diff --git a/skills/multi/dingtalk-report/references/report.md b/skills/multi/dingtalk-misc/references/report.md similarity index 66% rename from skills/multi/dingtalk-report/references/report.md rename to skills/multi/dingtalk-misc/references/report.md index c4c6e37a..3e533f8f 100644 --- a/skills/multi/dingtalk-report/references/report.md +++ b/skills/multi/dingtalk-misc/references/report.md @@ -1,6 +1,6 @@ # 日志 (report) 命令参考 -> **注意**:钉钉日志 = 「OA 周报应用」(按模版填报 / 收件箱 / 已发列表 / 统计),不是通用日志或记录系统。`dws report` 与其别名 `dws log` 自动由 envelope 注册——`dws report --help` 输出会列出全部别名,文档不再单独声明。 +> 命令别名: `dws log` 等价于 `dws report`(注意:此处 `log` 特指 OA 周报应用,不是通用日志/记录) ## 载体辨义(首屏必读) @@ -22,8 +22,8 @@ - `dws report inbox list` = 列出**我收到**的日报(别人发给我的)。 - `dws report outbox list` = 列出**我发出**的日报(我创建或提交的)。 -- `dws report entry get --report-id ` = 读取单份日报正文 + 钉钉跳转链接。 -- `dws report entry stats --report-id ` = 读取单份日报的已读统计。 +- `dws report entry get --report-id --format json` = 读取单份日报正文 + 钉钉跳转链接。 +- `dws report entry stats --report-id --format json` = 读取单份日报的已读统计。 - `dws report entry submit --template-id ... --contents-file ...` = 按模版提交一份新日报。 - `dws report template list` = 列出可用日报模版。 - `dws report template get --name "<模版名>"` = 读取单个模版的字段定义(contents 拼装来源)。 @@ -31,29 +31,46 @@ | 用户意图 | 第一条有效指令 | 后续动作 | |----------|----------------|----------| | 查我发过的日志 / 我创建的日志 | `dws report outbox list --cursor 0 --size 20 --format json` | 从返回里取 `reportId`,再执行 `dws report entry get --report-id --format json` | -| 查我收到的日志 / 别人发给我的日志 | `dws report inbox list --start "" --end "" --cursor 0 --size 20 --format json` | 必须先按用户时间词补齐完整 ISO 起止时间;取 `reportId` 后调用 `entry get` | +| 查我收到的日志 / 别人发给我的日志 | `dws report inbox list --start "" --end "" --cursor 0 --size 20 --format json` | 必须先按用户时间词补齐完整 ISO 起止时间;用户只说“最近/近期/最近收到”时默认最近 7 天;取 `reportId` 后调用 `entry get` | +| 按发件人查收到的日志 | 先 `dws aisearch person --keyword "<姓名>" --dimension name --format json`,再 `dws report inbox list ... --sender-user-ids --format json` | 只能在收件箱中过滤发件人;没有匹配时说明未找到,不得改选其他发件人 | | 查看某条日志正文 / 日志详情 | `dws report entry get --report-id --format json` | 如果用户没给 `reportId`,先用 `outbox list` 或 `inbox list` 找候选 | +| 总结最近收到的日志 / 汇总多篇日志内容 | 先 `dws report inbox list ... --size 20 --format json`,选前 5 篇或实际返回数量,再对每个 `reportId` 逐条执行 `dws report entry get --report-id --format json` | 不得只调用一次 list 后直接总结;不得把多篇合并成一次 detail 调用 | | 查某条日志统计 / 已读统计 | `dws report entry stats --report-id --format json` | 如果用户没给 `reportId`,先用 `outbox list` 或 `inbox list` 找候选 | | 查日志模版 / 有哪些周报模板 | `dws report template list --format json` | 需要字段定义时继续 `dws report template get --name "<模版名>" --format json` | | 提交 / 填写 / 创建钉钉日志 | `dws report template list --format json` | 再 `template get` 取字段定义,最后 `entry submit`;禁止直接编 contents | +### 句式映射补充(Agent 必须遵守) + +| 用户说法 | 必须执行 | 禁止行为 | +|----------|---------|----------| +| "有哪些日志模板 / 日报模板 / 模板列表 / 有什么模板可以用" | `dws report template list --format json` | 禁止 ai-seek / enterprise_search 等通用搜索代理绕行 | +| "日报模板 / 周报模板"(指定类型) | 先 `dws report template list --format json`,再从返回结果中按名称包含「日报」或「周报」筛选 | 不得虚构模板;不得跳过 `template list` 直接编造模板名 | +| "今天收到的日志 / 最近收到的日志 / 别人发我的日志" | `dws report inbox list --start ... --end ... --cursor 0 --size 20 --format json`;“最近/近期/最近收到”默认最近 7 天 | 禁止 grep_search / session_search / ai-seek 替代 | +| "过山发给我的日志 / 按发件人查收件箱" | 先 `dws aisearch person --keyword "<姓名>" --dimension name --format json` 取 ID,再 `dws report inbox list ... --sender-user-ids --format json` | 禁止用 `outbox list --user-id` 查他人已发日志;禁止返回其他发件人的日志 | +| "总结最近收到的 5 篇日志 / 合并多篇日志内容" | 先 `inbox list` 获取候选,再对选中的每个 `reportId` 逐条 `dws report entry get --report-id --format json` | 禁止停在 skill 激活或反问时间范围;不足 5 篇按实际数量总结并说明 | +| "日志模板详情 / 模板具体结构 / 模板有哪些字段" | 两步:① `dws report template list --format json` 获取模板名 → ② `dws report template get --name "<模版名>" --format json` | 禁止跳过第①步直接猜模板名调 `template get` | +| "日志ID xxx 详情 / 查一下日志编号 xxx" | 直接 `dws report entry get --report-id --format json` | 禁止反问用户确认 ID 是否正确;返回不存在则如实告知,不得虚构 | +| "我创建的 / 我发过的 / 已发的日志" | `dws report outbox list --cursor 0 --size 20 --format json` | 禁止用 `dws report inbox list` 替代 | + 效率约束: - 不要先调用 `dws report --help` / `dws report inbox list --help`。本页已经给出可执行命令。 - 查询收到的日志统一使用 `dws report inbox list`;查询发出的日志统一使用 `dws report outbox list`。 - 不要为了格式化结果创建脚本。直接从 JSON 里抽取关键字段,返回用户可读列表。 -- 对"最近一周"这类常用查询,直接把当前日期换算成 7 天窗口后执行一次 `inbox list`;如返回分页标记再继续翻页。 +- 对"最近 / 近期 / 最近收到 / 最近一周"这类常用查询,直接把当前日期换算成最近 7 天窗口后执行一次 `inbox list`;如返回分页标记再继续翻页。 +- `--size` 最大只能传 20;需要更多结果时按 `cursor` 分页,禁止传 50/100。 时间窗口默认规则: - `outbox list` 不传 `--start` / `--end` 时 CLI 默认最近 20 天,适合"我发过的日志 / 最近日志"。 -- `inbox list` 必须传 `--start` / `--end`;用户说"最近一周/今天/本周/昨天"时,Agent 必须先转成 `YYYY-MM-DDT00:00:00+08:00` 到 `YYYY-MM-DDT23:59:59+08:00`。 +- `inbox list` 必须传 `--start` / `--end`;用户说"最近/近期/最近收到/最近一周/今天/本周/昨天"时,Agent 必须先转成 `YYYY-MM-DDT00:00:00+08:00` 到 `YYYY-MM-DDT23:59:59+08:00`,其中“最近/近期/最近收到”默认最近 7 天。 - 查更早日志时每次窗口不要超过 20 天,按窗口滚动查询。 时间参数硬约束: - 只允许使用 `--start` 和 `--end` 两个 flag;禁止写 `--start-date`、`--end-date`、`--date`、`startDate`、`endDate`。 - 时间值推荐使用完整 ISO-8601 + 时区格式:`YYYY-MM-DDTHH:mm:ss+08:00`。 +- 必须使用 Asia/Shanghai 的 `+08:00` 时区;禁止用 UTC `Z`、`date -u` 或无时区裸时间。 - Agent 不要只传裸日期;即使 CLI 能兼容 `YYYY-MM-DD`,生成命令时也必须展开成当天起止时间,避免工具层把日期误解析成普通字符串。 - "最近一周"固定展开为:起始日 `T00:00:00+08:00`,结束日 `T23:59:59+08:00`。 @@ -81,6 +98,15 @@ dws report entry get --report-id --format json dws report inbox list --start "" --end "" --cursor 0 --size 20 --format json dws report entry get --report-id --format json +# 按发件人过滤收件箱 +dws aisearch person --keyword "<姓名>" --dimension name --format json +dws report inbox list --start "" --end "" --sender-user-ids --cursor 0 --size 20 --format json + +# 总结最近收到的多篇日志 +dws report inbox list --start "<最近7天起点+08:00>" --end "<当天终点+08:00>" --cursor 0 --size 20 --format json +dws report entry get --report-id <第1篇reportId> --format json +dws report entry get --report-id <第2篇reportId> --format json + # 已知 reportId dws report entry get --report-id --format json ``` @@ -101,13 +127,13 @@ dws report entry get --report-id --format json | 查看详情 | 从 `_internalDetailCommands[].command` 取内部详情命令;面向用户展示为“查看详情”,不要展示命令本身 | | 钉钉链接 | 详情接口返回 `dingtalkOpenMarkdownLink` / `dingtalkOpenUrl` / `result.url` 后,展示成 markdown 链接 | -CLI 列表命令只返回 JSON-first 数据,不把 Markdown 表作为裸文本直接输出。`report inbox list` / `report outbox list` 会对当前页 `result[]` 中所有带 `reportId` 的日志逐条调用 `dws report entry get --report-id --format json` 补齐展示字段;返回 JSON 会携带 `agentDisplayRequired=true`、`agentDisplayRequiredColumns`、`agentDisplayLinkColumn=钉钉链接`、`agentDisplayMarkdownRequired=true`、`agentDisplayMarkdown`、`agentDisplayMarkdownHeader` 等字段,这些是面向 Agent 的强制展示合同,不是可选建议。Agent 必须在 final reply 直接原样输出 `agentDisplayMarkdown`,让客户端按 Markdown 渲染出带可点击 `钉钉链接` 列的表格;不要自行重组列,不要改表头。 +CLI 列表命令只返回 JSON-first 数据,不把 Markdown 表作为裸文本直接输出。`report inbox list` / `report outbox list` 用于定位候选日志和展示列表;返回 JSON 可能携带 `agentDisplayRequired=true`、`agentDisplayRequiredColumns`、`agentDisplayLinkColumn=钉钉链接`、`agentDisplayMarkdownRequired=true`、`agentDisplayMarkdown`、`agentDisplayMarkdownHeader` 等字段,这些是面向 Agent 的强制展示合同,不是可选建议。用户只要列表时,Agent 必须在 final reply 直接原样输出 `agentDisplayMarkdown`,让客户端按 Markdown 渲染出带可点击 `钉钉链接` 列的表格;不要自行重组列,不要改表头。用户要正文、详情、统计、汇总或总结多篇日志时,列表只负责提供 `reportId`,必须再显式逐条调用 `dws report entry get` 或 `dws report entry stats`。 展示列按方向区分:`inbox list` 是收件箱,只展示 `日期 | 标题 | 发送人 | 状态 | 钉钉链接`,**禁止返回或展示日志正文、完整内容或日志内容摘要**;`outbox list` 是发件箱,可展示 `日期 | 标题 | 发送人 | 状态 | 日志内容 | 钉钉链接`,其中 `日志内容` 来自详情补齐,方便用户快速确认自己发出的内容。凡用户说"列出 / 找到 / 查询 / 搜索 / 看看日志",默认都要在 final reply 渲染 Markdown 表;只有用户明确表示"不关心列表内容 / 只要原始 JSON / 只要数量 / 只要 ID"时,才可以不渲染表。推荐用户可读输出: | 日期 | 标题 | 发送人 | 状态 | 钉钉链接 | |------|------|--------|------|----------| -| 2026-05-09 23:08 | 张成强的周报 | 张成强 | 未读 | [在钉钉中查看日志](...) | +| 2026-05-09 23:08 | 张成强的周报 | 张成强 | 未读 | 在钉钉中查看日志(链接取自 `result[].钉钉链接`) | 操作列规则: @@ -116,8 +142,8 @@ CLI 列表命令只返回 JSON-first 数据,不把 Markdown 表作为裸文本 - 详情阶段:`entry get` 返回里如果有 `dingtalkOpenMarkdownLink`,优先把操作列替换为该 markdown 链接;否则用 `dingtalkOpenUrl` 或 `result.url` 包成 `[在钉钉中查看日志](url)`。 - `钉钉链接` 是强制列:禁止省略、改名、合并到标题里,也禁止改成不含链接列的摘要表。 - 不要在用户表格里展示 `_internalDetailCommands`、raw `reportId`、raw `dingtalk://...`。链接必须是 markdown 可点击文本。 -- `dws report inbox list` 默认会对当前页所有可查看日志自动补 `entry get`,但只返回日期、标题、发送人、状态与可点击钉钉链接;需要正文时必须显式调用 `dws report entry get --report-id ... --format json`。 -- `dws report outbox list` 默认会对当前页所有可查看日志自动补 `entry get`,并可把 `日志内容` 纳入发件箱表格展示。 +- `dws report inbox list` 列表阶段只用于展示和定位;需要正文、详情、汇总或总结时必须显式调用 `dws report entry get --report-id ... --format json`。 +- `dws report outbox list` 列表阶段只用于展示和定位;即使返回中带摘要或部分内容,用户要求正文、详情或统计时仍必须继续调用 `entry get` / `entry stats`。 只有在用户明确要求"给我日志 ID / 方便我后续查询"时,才额外展示 `reportId`。否则 final reply 应保留可读信息,并说明"需要看正文或打开钉钉,我可以继续打开某一条"。 @@ -206,7 +232,7 @@ Usage: dws report entry get [flags] Example: # 先通过 report inbox list / outbox list / entry submit 取得 reportId,再查正文与跳转链接 - dws report entry get --report-id + dws report entry get --report-id --format json Flags: --report-id string 日志 ID (必填) ``` @@ -225,11 +251,11 @@ Flags: Usage: dws report inbox list [flags] Example: - dws report inbox list --start "2026-03-10T00:00:00+08:00" --end "2026-03-10T23:59:59+08:00" --cursor 0 --size 20 - dws report inbox list --start "2026-03-10T00:00:00+08:00" --end "2026-03-10T23:59:59+08:00" --sender-user-ids staffId1,staffId2 + dws report inbox list --start "2026-03-10T00:00:00+08:00" --end "2026-03-10T23:59:59+08:00" --cursor 0 --size 20 --format json + dws report inbox list --start "2026-03-10T00:00:00+08:00" --end "2026-03-10T23:59:59+08:00" --sender-user-ids staffId1,staffId2 --format json # 禁止:--start-date / --end-date / --date # 从列表结果提取 reportId 后,再查正文 - dws report entry get --report-id + dws report entry get --report-id --format json Flags: --cursor int 分页游标,首次传 0 (默认 0) --end string 结束时间 ISO-8601 (如 2026-03-10T23:59:59+08:00) (必填) @@ -239,14 +265,14 @@ Flags: ``` -`inbox list` 默认返回 JSON-first 结构:不会把 Markdown 表作为裸文本直接输出,但 JSON 里会返回 `agentDisplayMarkdown`。Agent 必须在 final reply 直接原样输出 `agentDisplayMarkdown`,让客户端按 Markdown 渲染出表格;不要基于个人偏好重新摘要。表头固定且必须逐字为 `| 日期 | 标题 | 发送人 | 状态 | 钉钉链接 |`,并按返回顺序展示(CLI 已按创建时间从近到远排序)。返回 JSON 中的 `agentDisplayRequired=true`、`agentDisplayRequiredColumns`、`agentDisplayLinkColumn=钉钉链接`、`agentDisplayMarkdownRequired=true`、`agentDisplayMarkdownHeader` 都是强制展示合同。凡用户说"列出 / 找到 / 查询 / 搜索 / 看看日志",final reply 默认必须渲染这张 Markdown 表;只有用户明确表示"不关心列表内容 / 只要原始 JSON / 只要数量 / 只要 ID"时,才可以不渲染表。`result[]` 每项都包含且只展示这五个字段:`日期`、`标题`、`发送人`、`状态`、`钉钉链接`。`钉钉链接` 是强制可点击 markdown 链接列,禁止省略、改名、合并到标题里;`inbox list` 禁止返回或展示 `日志内容` / `日志内容摘要`。`reportId` 不在主结果里,只通过 `_internalDetailCommands` 保留给 Agent 后续调用 `entry get` / `entry stats`;final reply 禁止展示 `_internalDetailCommands`、毫秒时间戳、raw ID 或日志正文。 +`inbox list` 默认返回 JSON-first 结构:不会把 Markdown 表作为裸文本直接输出,但 JSON 里会返回 `agentDisplayMarkdown`。Agent 必须在 final reply 直接原样输出 `agentDisplayMarkdown`,让客户端按 Markdown 渲染出表格;不要基于个人偏好重新摘要。表头固定且必须逐字为 `| 日期 | 标题 | 发送人 | 状态 | 钉钉链接 |`,并按返回顺序展示(CLI 已按创建时间从近到远排序)。返回 JSON 中的 `agentDisplayRequired=true`、`agentDisplayRequiredColumns`、`agentDisplayLinkColumn=钉钉链接`、`agentDisplayMarkdownRequired=true`、`agentDisplayMarkdownHeader` 都是强制展示合同。凡用户说"列出 / 找到 / 查询 / 搜索 / 看看日志",final reply 默认必须渲染这张 Markdown 表;只有用户明确表示"不关心列表内容 / 只要原始 JSON / 只要数量 / 只要 ID"时,才可以不渲染表。`result[]` 每项都包含且只展示这五个字段:`日期`、`标题`、`发送人`、`状态`、`钉钉链接`。`钉钉链接` 是强制可点击 markdown 链接列,禁止省略、改名、合并到标题里;`inbox list` 禁止返回或展示 `日志内容` / `日志内容摘要`。Agent 必须从 `result[]` 或 `_internalDetailCommands` 保留每条日志的 `reportId` 和时间字段,用于后续 `entry get` / `entry stats`;final reply 禁止展示 `_internalDetailCommands`、毫秒时间戳、raw ID 或日志正文。 ### 读取单份日报的已读统计 ``` Usage: dws report entry stats [flags] Example: - dws report entry stats --report-id + dws report entry stats --report-id --format json Flags: --report-id string 日志 ID (必填) ``` @@ -257,11 +283,11 @@ Flags: Usage: dws report outbox list [flags] Example: - dws report outbox list --cursor 0 --size 20 - dws report outbox list --cursor 0 --size 20 --start "2026-03-10T00:00:00+08:00" --end "2026-03-10T23:59:59+08:00" - dws report outbox list --cursor 0 --size 20 --template-name "日报" + dws report outbox list --cursor 0 --size 20 --format json + dws report outbox list --cursor 0 --size 20 --start "2026-03-10T00:00:00+08:00" --end "2026-03-10T23:59:59+08:00" --format json + dws report outbox list --cursor 0 --size 20 --template-name "日报" --format json # 从列表结果提取 reportId 后,再查正文 - dws report entry get --report-id + dws report entry get --report-id --format json Flags: --cursor int 分页游标,首次传 0 (默认 0) --size int 每页条数,最大 20 (默认 20),别名: --limit @@ -288,7 +314,7 @@ Flags: 读取已有日志内容时,统一按下面两步走,不要把列表接口当正文接口: 1. `dws report inbox list ...` 或 `dws report outbox list ...`,先拿到目标日志的 `reportId` -2. `dws report entry get --report-id `,再读取正文和字段明细 +2. `dws report entry get --report-id --format json`,再读取正文和字段明细 适用场景: @@ -343,8 +369,8 @@ dws report outbox list --cursor 0 --size 20 --format json | 操作 | 从返回中提取 | 用于 | |------|-------------|------| -| `template list` | template 名称(`items[].report_template_name`;顶层键只有 `items` 和 `success`,**无 `result` 包裹**) | `template get` 的 --name | -| `template list` | `items[].report_template_id` | `entry submit` 的 --template-id | +| `template list` | template 名称(result.items[].report_template_name) | `template get` 的 --name | +| `template list` | `report_template_id` | `entry submit` 的 --template-id | | `template get` | `result.report_template_fields[].field_name` / `field_sort` / `field_type` | 拼 `entry submit` 的 --contents JSON(按下表映射)| | `entry submit` | `reportId`、`dingtalkOpenMarkdownLink`、`dingtalkOpenUrl`(CLI 自动反查详情后追加)| final reply 优先直接使用 `dingtalkOpenMarkdownLink`;需要结构化展示时用 `dingtalkOpenLink.title` + `dingtalkOpenLink.url` | | `inbox list` / `outbox list` | `reportId` | `entry get` / `entry stats` 的 --report-id | @@ -376,11 +402,11 @@ dws report outbox list --cursor 0 --size 20 --format json | Code | ExitCode | 真实含义 | 建议动作 | |------|---------|---------|---------| | `INPUT_INVALID_JSON` | 3 | `--contents` 或 `--contents-file` 内容非合法 JSON | 检查 JSON 数组结构,每项必须是 object,含 `key`/`sort`/`content`/`contentType`/`type` 五个字段 | -| `INPUT_FILE_NOT_FOUND` | 3 | `--contents-file` 指向的文件不存在(路径必须在当前工作目录 CWD 之下)| 确认文件存在且位于 CWD 内;把 contents JSON 写到 CWD 下的相对路径再传 | -| `INPUT_INVALID_PATH` | 3 | `--contents-file` 路径越出当前工作目录(如 `/tmp`、`os.tmpdir()`、`../` 或指向目录外的符号链接)| **不要**用 `/tmp` / `os.tmpdir()` / `../`;把 contents JSON 写到当前工作目录之下再传 | +| `INPUT_FILE_NOT_FOUND` | 3 | `--contents-file` 路径不存在 / sandbox OS 风格不匹配(macOS 路径在 Windows 沙箱)| 先确认 sandbox OS 与路径风格;改写到 `os.tmpdir()` 等可移植目录 | | `INPUT_MISSING_PARAM` | 3 | `--template-id` / `--contents` 必填缺失 | 显式传值;从 `template list` 取合法 templateId | | `INPUT_TOO_LARGE` | 3 | contents JSON 超过 10MB 限制 | **不支持分批次提交**。需精简内容或拆分为多个独立日志分别提交 | -| `MCP_TOOL_ERROR` / `[UNCLASSIFIED] business error` | 1 | 服务端业务错,`success=false`、`code=1`。覆盖:submit 时 templateId 错 / 字段名错 / 字段值错 / contents 空(`server_error_code: PARAM_ERROR`);`entry get` / `entry stats` 传了不存在的 reportId(`server_error_code: BUSINESS_ERROR`,technical_detail 含 `report not exist`);`template get` 传了不存在的模版名(`server_error_code: PARAM_ERROR`)| 查看 `server_error_code` / `technical_detail`;reportId / 模版名找不到时先用 `inbox list` / `outbox list` / `template list` 重新取合法值,不要猜;submit 类错误按链路重走 `template list → template get → entry submit`,连续 ≥ 2 次仍失败必须停止重试,降级 final_reply | +| `MCP_TOOL_ERROR` | 1 | 服务端业务错(含 `server_error_code: PARAM_ERROR`,覆盖 templateId 错 / 字段名错 / 字段值错 / contents 空等多种形态)| 查看 `server_error_code` / `technical_detail`;服务端不区分具体子错因,按提交链路重新走 `template list → template get → entry submit`;连续 ≥ 2 次仍失败必须停止重试,降级 final_reply | +| `RESOURCE_NOT_FOUND` | 1 | reportId / templateId 在服务端找不到 | 用 `list` 或 `template list` 重新获取 | ## 何时停止重试 @@ -411,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 时优先) + +以下 shortcut 同时进入公开 catalog 与 Runtime Schema。先按本 skill 的意图表、脚本和 recipe 路由:存在精确覆盖该场景的专用脚本/recipe 时按其执行;否则用户意图命中时,shortcut 优先于手写原子命令。用 leaf Schema(例如 `dws schema --cli-path "report +" --format json`)读取 Agent 选择、参数、约束、风险和确认语义;用 `dws shortcut list --service report --format json` 批量发现;最后以 `dws report --help` 核对当前 Cobra flags。 + +| 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-misc/references/sheet-intent-guide.md b/skills/multi/dingtalk-misc/references/sheet-intent-guide.md new file mode 100644 index 00000000..3f157c5f --- /dev/null +++ b/skills/multi/dingtalk-misc/references/sheet-intent-guide.md @@ -0,0 +1,12 @@ +# sheet 局部意图消歧 + +本文件从单 Skill `intent-guide.md` 拆分而来,仅保留与本产品相关的跨产品消歧规则。 + +| 用户说... | 真实意图 | 应该用 | 不要用 | 理由 | +|---|---|---|---|---| +| "帮我建一个项目跟踪表" | 创建数据表格 | `aitable` | `doc` / `sheet` | 涉及结构化数据/行列操作,不是富文本文档或电子表格 | +| "创建一个电子表格" | 创建表格文档 | `sheet` | `aitable` | Excel 式表格/单元格操作,不是多维表记录 | +| "帮我读一下表格 A1:D10 的数据" | 读取单元格数据 | `sheet` | `aitable` | 按单元格区域读写,不是按记录查询 | +| "这个 alidocs 表格链接帮我看下"(粘贴原始 URL) | 先 probe 节点类型 | `dws 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..fa8ced42 --- /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 时优先) + +以下 shortcut 同时进入公开 catalog 与 Runtime Schema。先按本 skill 的意图表、脚本和 recipe 路由:存在精确覆盖该场景的专用脚本/recipe 时按其执行;否则用户意图命中时,shortcut 优先于手写原子命令。用 leaf Schema(例如 `dws schema --cli-path "sheet +" --format json`)读取 Agent 选择、参数、约束、风险和确认语义;用 `dws shortcut list --service sheet --format json` 批量发现;最后以 `dws sheet --help` 核对当前 Cobra flags。 + +| 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 79% 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 819a654d..8975870d 100644 --- a/skills/multi/dingtalk-sheet/references/sheet/sheet-batch-operations.md +++ b/skills/multi/dingtalk-misc/references/sheet/sheet-batch-operations.md @@ -5,9 +5,8 @@ `batch-update` 把多次写入打包成单次请求,但每个子操作仍受编辑类任务硬性默认规则约束: 1. **目标 range 必须落在用户授权范围内**:除用户明示要修改的区域外,子操作禁止扩张到无关单元格 / 列 / 工作表。规划 range 时先确认每个子操作的边界。 -2. **批次完成后必须回读校验**:整个 `batch-update` 执行成功后,用 `range read` 或 `csv-get` 抽样回读受影响区域,至少校验 3-5 个代表性单元格(首 / 中 / 末),与本地预先计算的预期值对照。 +2. **批次完成后必须回读校验**:整个 `batch-update` 执行成功后,单元格值用 `range read` 或 `csv-get` 抽样回读受影响区域;合并、行列分组等结构元数据用 `sheet info` 回读校验。 3. **预期条数前置断言**:涉及"批量填充 N 行"或"对 M 个区域分别写入"时,先把 N、M 硬编码进代码,回读后断言实际等于预期;不一致就再发一轮 `batch-update` 补齐,禁止交付半成品。 -4. **统一确认语义**:`range batch-clear` 和 `batch-update` 都可删除内容或结构;先用 `--dry-run` 预览,真实执行必须获得用户确认后追加 `--yes`。 ## 使用场景 @@ -23,17 +22,28 @@ - 原子事务:任一操作失败则整批回滚 - 传 `--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` +- 分组回读用 `sheet info` +- `group-dimension` 在 batch 中只适合默认展开分组;需要创建后立即折叠时,用独立 `dws sheet group-dimension --group-state fold` + **何时推荐使用 `batch-update`**: - 需要对**多个**不同区域执行 `merge-cells` / `unmerge-cells` 时(如按分组合并多列相同内容) - 需要对**多个**不同区域执行 `update-dimension` 时(如统一调整多列列宽或多行行高,含 hidden + pixelSize) +- 需要对**多个**连续行/列范围执行 `group-dimension` / `ungroup-dimension` 时(默认展开分组) - 需要先插入行列再写入数据时(`add-dimension` + `range update` / `csv-put`) -- 需要批量创建或取消行列分组时(`group-dimension` / `ungroup-dimension`) - 需要对多个区域执行不同写入操作时(多次 `range update` + `range clear` 等组合) 当同一工具需要对多个区域重复调用时,**推荐**改用 `batch-update` 合并为单次请求——`batch-update` 是原子提交(要么全成功要么整批回滚);逐个调用非原子,中途失败会留下半成品。 **不可放进 `--operations` 的操作**(强行写入会被校验拒或行为未定义): -- `range read` / `csv-get`(只读操作,不属于写入) +- `range read` / `csv-get` / `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`(自身已是批量入口,不可再嵌套) @@ -50,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 前缀 (必填) @@ -72,9 +82,10 @@ Example: {"toolName":"range clear","input":{"sheet-id":"Sheet1","range":"A1:B3","type":"content"}}, {"toolName":"range update","input":{"sheet-id":"Sheet1","range":"A1","values":[[{"type":"text","text":"hello"}]]}}, {"toolName":"merge-cells","input":{"sheet-id":"Sheet1","range":"A1:B1","merge-type":"mergeAll"}}, - {"toolName":"update-dimension","input":{"sheet-id":"Sheet1","dimension":"ROWS","start-index":"1","length":1,"pixel-size":40}} - ]' --yes - dws sheet batch-update --node --continue-on-error --operations '[...]' --yes + {"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"}} + ]' + dws sheet batch-update --node --continue-on-error --operations '[...]' Flags: --node string 表格文档 ID 或 URL (必填) --operations string 操作数组 JSON (必填,每项 {toolName, input}) @@ -90,7 +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` ``` ### 子操作定位规则 @@ -106,6 +118,8 @@ Notes: ] ``` +`table-put` / `table-get` 不支持放进 `batch-update`。结构化 table 写入请直接调用 `dws sheet table-put`,写入完成后在 batch 外单独调用 `table-get` 回读校验。 + ## 校验与预览 ### Validate 阶段 @@ -120,27 +134,10 @@ 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 不提前拦截——传入空值或缺失必填字段时,请求会到达服务端再返回错误。 -### DryRun 阶段 - -加 `--dry-run` 预览翻译后的完整 MCP 请求,不实际执行任何操作: - -```bash -dws sheet batch-update --node NODE_ID --dry-run --operations '[...]' -``` - -DryRun 输出内容: -- `Tool: batch_update`(最终调用的 MCP 工具名) -- `Arguments:` 完整 JSON(含翻译后的 `operations` 数组,展示每个子操作的 MCP toolName + 参数名 + 参数值) -- 若传了 `--continue-on-error`,输出 JSON 中会包含 `"continueOnError": true` - -DryRun 适用场景: -- 确认 CLI 命令名 → MCP toolName 的翻译是否正确 -- 确认参数名转换(如 `--range` → `rangeAddress`)是否符合预期 -- 检查 `csv-put` 的 `@filepath` 是否被正确解析为文件内容 - ### --continue-on-error 行为 | 模式 | flag | 子操作失败时 | 返回格式 | @@ -148,7 +145,7 @@ DryRun 适用场景: | 严格事务(默认) | 不传 | 整批回滚到初始状态,后续子操作不执行 | 顶层 `error` 对象 | | 宽松模式 | `--continue-on-error` | 已执行的子操作不回滚,继续执行后续子操作 | `results` 数组 | -**严格事务失败的错误信息**由服务端(flex-table-app)生成,包含以下内容: +**严格事务失败的错误信息**由服务端生成,包含以下内容: - 失败操作索引:`operations[N]`(0-based,即 `--operations` JSON 数组中的下标) - 回滚告知:"All previously executed operations have been rolled back." - 失败原因:原始错误码和错误消息 @@ -176,13 +173,13 @@ Cause: The requested resource was not found by the identifier 'NonExistentSheet' | `merge-cells` | [sheet-style-format](./sheet-style-format.md) | | `unmerge-cells` | [sheet-style-format](./sheet-style-format.md) | | `update-dimension` | [sheet-dimension-operations](./sheet-dimension-operations.md) | +| `group-dimension` | [sheet-dimension-operations](./sheet-dimension-operations.md) | +| `ungroup-dimension` | [sheet-dimension-operations](./sheet-dimension-operations.md) | | `range fill` | [sheet-range-operations](./sheet-range-operations.md) | | `range copy-to` | [sheet-range-operations](./sheet-range-operations.md) | | `add-dimension` | [sheet-dimension-operations](./sheet-dimension-operations.md) | | `delete-dimension` | [sheet-dimension-operations](./sheet-dimension-operations.md) | | `move-dimension` | [sheet-dimension-operations](./sheet-dimension-operations.md) | -| `group-dimension` | [sheet-dimension-operations](./sheet-dimension-operations.md) | -| `ungroup-dimension` | [sheet-dimension-operations](./sheet-dimension-operations.md) | | `set-dropdown` | [sheet-dropdown](./sheet-dropdown.md) | | `delete-dropdown` | [sheet-dropdown](./sheet-dropdown.md) | | `csv-put` | [sheet-write-data](./sheet-write-data.md) | @@ -236,32 +233,44 @@ Cause: The requested resource was not found by the identifier 'NonExistentSheet' ] ``` -### 批量创建行列分组 +### 批量创建/取消行列分组 + +分组属于工作表结构元数据,批次完成后用 `sheet info` 回读 `rowGroups` / `columnGroups`,不要用 `range read` 校验。回读项中的 `level` 是 1-based,`collapsed` 表示当前折叠状态。 ```jsonc [ {"toolName":"group-dimension", "input":{"sheet-id":"...","range":"3:7","group-state":"expand"}}, {"toolName":"group-dimension", - "input":{"sheet-id":"...","range":"C:F"}}, - {"toolName":"ungroup-dimension", - "input":{"sheet-id":"...","range":"10:12"}} + "input":{"sheet-id":"...","range":"C:F","group-state":"expand"}} ] ``` +```jsonc +[ + {"toolName":"ungroup-dimension", + "input":{"sheet-id":"...","range":"3:7"}}, + {"toolName":"ungroup-dimension", + "input":{"sheet-id":"...","range":"C:F"}} +] +``` + +注意:batch 中不要用 `group-state:"fold"` 来创建折叠分组;需要折叠初始状态时,用独立 `dws sheet group-dimension --group-state fold`。 + ## 上下文传递 | 操作 | 从返回中提取 | 用于 | |------|-------------|------| | `list` | 工作表的 `sheetId` | batch-clear 的 --ranges sheet 前缀 / batch-update 子操作 input 的 sheet-id | -| `batch-update` | 各子操作的执行结果 | 回读校验(range read / csv-get) | +| `batch-update` | 各子操作的执行结果 | 回读校验(值用 range read / csv-get;合并、分组等结构用 sheet info) | ## 注意事项 - ★ **`--sheet-id` 获取规范(强制)**:`sheetId` 未知时必须先通过 `dws sheet list --node --format json` 查询真实的 `sheetId` / 工作表名称后再调用,禁止凭空编造(如臆测为 `Sheet1`、`sheet1`、`0`、`default` 等) - ★ **需要对多个区域执行相同清除操作时,用 `range batch-clear`**:一次原子请求清除多个区域(可跨工作表),失败时整批回滚 - ★ **需要组合多个不同写操作(清除+写入等)时,用 `batch-update`**:原子事务,任一操作失败则整批回滚,避免留下半成品 -- ★ **批次完成后必须回读校验**:用 `range read` 或 `csv-get` 抽样回读受影响区域,至少校验 3-5 个代表性单元格(首 / 中 / 末),与预期值对照 +- ★ **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`;`input.range` 只接受整行或整列范围,如 `3:7`、`C:F` -- 需要创建后立即折叠分组时,优先使用独立 `dws sheet group-dimension --group-state fold` +- `batch-update` 支持 `group-dimension` / `ungroup-dimension`;分组结果用 `sheet info` 回读 +- `batch-update` 中 `group-dimension` 只适合默认展开分组;需要 `fold` 初始状态时使用独立 `group-dimension` diff --git a/skills/multi/dingtalk-sheet/references/sheet/sheet-chart.md b/skills/multi/dingtalk-misc/references/sheet/sheet-chart.md similarity index 85% rename from skills/multi/dingtalk-sheet/references/sheet/sheet-chart.md rename to skills/multi/dingtalk-misc/references/sheet/sheet-chart.md index 0295faae..df84da69 100644 --- a/skills/multi/dingtalk-sheet/references/sheet/sheet-chart.md +++ b/skills/multi/dingtalk-misc/references/sheet/sheet-chart.md @@ -34,6 +34,7 @@ | "百分比面积" | 百分比堆积面积图(areaPercentStacked) | 占比式面积 | | "散点图"、"相关性" | 散点图(scatter) | 两变量相关性 | | "雷达图"、"多维度" | 雷达图(radar) | 多维度对比 | +| "柱+线"、"组合图"、"双轴" | 组合图(combo) | 多种图表叠加,支持副轴 | **多图表需求**:当用户同时提到多种分析(如"统计占比 + 对比数量"),必须创建多个图表,每个对应一种类型,不要只做一个。 @@ -55,8 +56,6 @@ Flags: --chart-id string 浮动图表 ID (可选,不传则返回全部) ``` -- **判空看 `totalCount` / `message`,不要看数组长度**:无图表时 `floatCharts` 数组里仍会带 1 个全 null 的幽灵元素(`chart.category`/`chart.series` 全 null,无 id),但 `totalCount` 为 0、`message` 为「Successfully retrieved 0 float chart(s).」。判断有没有图表以 `totalCount` 为准,或过滤掉没有 `id` 的元素。 - ### 创建浮动图表 ``` Usage: @@ -130,7 +129,7 @@ Flags: | 字段 | 类型 | 必填 | 说明 | |------|------|------|------| | `row` | number | 是 | 锚点行索引(0-based),如 0 表示第一行 | -| `col` | string | 是 | 锚点列。使用字母表示法(`"A"`=第1列, `"B"`=第2列, `"Z"`=第26列, `"AA"`=第27列),不支持数字形式 | +| `col` | string/number | 是 | 锚点列。支持字母表示法(`"A"`=第1列, `"B"`=第2列, `"Z"`=第26列, `"AA"`=第27列)或 0-based 数字(`0`=第1列) | **offset 字段**(可选,不传时默认 0): @@ -150,9 +149,10 @@ Flags: | 字段 | 类型 | 必填 | 说明 | |------|------|------|------| -| `type` | string | 是 | 图表类型,枚举值:`column` / `bar` / `columnStacked` / `barStacked` / `line` / `lineStacked` / `area` / `areaStacked` / `areaPercentStacked` / `pie` / `doughnut` / `scatter` / `radar` | -| `series` | object[] | 是 | 数据系列数组,至少一项,详见下方 series 字段表 | -| `category` | string[] | 否 | 分类轴(X轴)数据区域,A1 表示法字符串数组(如 `["A2:A10"]`) | +| `type` | string | 是 | 图表类型,枚举值:`column` / `bar` / `columnStacked` / `barStacked` / `line` / `lineStacked` / `area` / `areaStacked` / `areaPercentStacked` / `pie` / `doughnut` / `scatter` / `radar` / `combo` | +| `series` | object[] | 非 combo 必填 | 数据系列数组,至少一项,详见下方 series 字段表。combo 类型不使用此字段 | +| `subCharts` | object[] | combo 必填 | 组合图子图数组,至少一项,详见下方 subCharts 字段表。仅 combo 类型使用 | +| `category` | string[] | 否 | 分类轴(X轴)数据区域,A1 表示法字符串数组(如 `["A2:A10"]`)。combo 类型在 subCharts 内声明 | | `title` | object | 否 | 图表标题配置,详见下方 title 字段表 | | `legend` | object | 否 | 图例配置,详见下方 legend 字段表 | | `catAx` | object | 否 | 分类轴(X轴)样式配置,详见下方轴配置表 | @@ -165,6 +165,15 @@ Flags: | `name` | string | 否 | 系列名称的单元格引用,A1 表示法(如 `"B1"`)。不传时图例显示为"系列1"等默认名 | | `value` | string[] | 是 | 系列数据区域,A1 表示法字符串数组(如 `["B2:B10"]`)。每个元素为一段连续区域 | +**subCharts 数组项**(仅 combo 类型): + +| 字段 | 类型 | 必填 | 说明 | +|------|------|------|------| +| `type` | string | 是 | 子图类型,仅允许笛卡尔坐标系类型:`column` / `bar` / `columnStacked` / `barStacked` / `line` / `lineStacked` / `area` / `areaStacked` / `areaPercentStacked`。不允许 pie/doughnut/scatter/radar/combo | +| `series` | object[] | 是 | 子图数据系列,格式同上方 series 数组项 | +| `category` | string[] | 是 | 子图分类轴数据区域(笛卡尔坐标系必须提供) | +| `useSecondaryAxis` | boolean | 否 | 是否使用副轴(右侧 Y 轴),默认 false | + **title 字段**: | 字段 | 类型 | 必填 | 说明 | @@ -199,7 +208,7 @@ Flags: - 格式:`"A2:B10"`(默认使用 `--sheet-id` 对应的工作表) - 跨 sheet 引用:`"SHEET_ID!A2:B10"`(前缀为工作表 ID) - 单元格引用:`"B1"`(等同于 `"B1:B1"`) -- MCP 内部自动将 A1 引用转换为 ISheetRange 结构,CLI 直接透传即可 +- 直接传 A1 表示法字符串即可,命令会处理成图表所需的区域引用 ## 图表位置选择(创建前必做) @@ -277,32 +286,19 @@ dws sheet chart create --node --sheet-id --properties '{ } }' -# ── 工作流 4: 多图表组合分析 ── -# 当用户需要"柱+线"等多种图表组合分析时,分别创建对应类型的图表: +# ── 工作流 4: 创建组合图(combo)── +# 当用户需要"柱+线"叠加、双轴对比时,使用 combo 类型: -# 创建柱形图 dws sheet chart create --node --sheet-id --properties '{ "position": {"row": 40, "col": "A"}, - "dimensions": {"width": 700, "height": 450}, + "dimensions": {"width": 800, "height": 500}, "chart": { - "type": "column", - "series": [{"name": "B1", "value": ["B2:B10"]}], - "category": ["A2:A10"], - "title": {"show": true, "text": "销售额"}, - "legend": {"show": true, "pos": "t"} - } -}' - -# 创建折线图 -dws sheet chart create --node --sheet-id --properties '{ - "position": {"row": 40, "col": "J"}, - "dimensions": {"width": 700, "height": 450}, - "chart": { - "type": "line", - "series": [{"name": "C1", "value": ["C2:C10"]}], - "category": ["A2:A10"], - "title": {"show": true, "text": "增长率"}, - "legend": {"show": true, "pos": "t"} + "type": "combo", + "subCharts": [ + {"type": "column", "series": [{"name": "B1", "value": ["B2:B10"]}], "category": ["A2:A10"]}, + {"type": "line", "series": [{"name": "C1", "value": ["C2:C10"]}], "category": ["A2:A10"], "useSecondaryAxis": true} + ], + "title": {"show": true, "text": "销售额与增长率"} } }' ``` @@ -344,10 +340,10 @@ dws sheet chart update --node --sheet-id --chart-id --format json` 查询,禁止凭空编造(如臆测为 `Sheet1`、`sheet1`、`0`、`default` 等) - [强制] **创建后必须验证**:图表创建后必须调用 `chart list` 验证配置是否正确 -- ⚠️ **`chart list` 判空看 `totalCount` 不看数组长度**:无图表时 `floatCharts` 仍含 1 个全 null 幽灵元素(无 id),`totalCount` 为 0、`message` 说明为空。判断有没有图表以 `totalCount` 为准或过滤无 `id` 的元素 - [强制] **chart-id 禁止臆测**:必须通过 `chart list` 获取真实的图表 ID,不可编造 -- **图表类型映射**:用户说"柱形图"用 `column`,"条形图"(横向)用 `bar`,"散点图"用 `scatter`,"雷达图"用 `radar`,"环形图"用 `doughnut` +- **图表类型映射**:用户说"柱形图"用 `column`,"条形图"(横向)用 `bar`,"散点图"用 `scatter`,"雷达图"用 `radar`,"环形图"用 `doughnut`,"柱+线/组合/双轴"用 `combo` - **堆积类型后缀**:堆叠版本的图表在基本类型后加 `Stacked`(如 `columnStacked`、`lineStacked`、`areaStacked`) +- **combo 组合图**:不用顶层 `series`/`category`,改用 `chart.subCharts` 数组,每个子图独立声明 `type`/`series`/`category`,可选 `useSecondaryAxis: true` 指定副轴。子图 type 仅允许笛卡尔坐标系类型(column/bar/line/area 及其 Stacked 变体),不允许 pie/doughnut/scatter/radar/combo - **位置不得压在数据区上**:图表浮层会遮挡原始数据,必须放在空白区域 - **多图表需求**:多次调用 `chart create`,不要试图用一个图表包含所有分析 - **跨 sheet 引用数据**:在 A1 区域前加 `SHEET_ID!` 前缀,如 `"sheetXXX!A2:B10"` diff --git a/skills/multi/dingtalk-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 94% rename from skills/multi/dingtalk-sheet/references/sheet/sheet-conditional-format.md rename to skills/multi/dingtalk-misc/references/sheet/sheet-conditional-format.md index 70bc77b7..ccee67c4 100644 --- a/skills/multi/dingtalk-sheet/references/sheet/sheet-conditional-format.md +++ b/skills/multi/dingtalk-misc/references/sheet/sheet-conditional-format.md @@ -64,14 +64,13 @@ **正确做法(两步走)**: ```bash -# Step 1: 用 range update 在新列写判断公式(形成"是/否"辅助列) -# 每个单元格必须是 object,公式写在 text(以 = 开头);不支持裸字符串 -dws sheet range update --node NODE_ID --sheet-id SHEET_ID --range "H2:H100" \ - --values '[[{"type":"text","text":"=IF(A2>B2,\"是\",\"否\")"}],...]' +# Step 1: 用 range update 在新列写判断公式(形成"是/否"辅助列,以 H2:H4 为例) +dws sheet range update --node NODE_ID --sheet-id SHEET_ID --range "H2:H4" \ + --values '[[{"type":"text","text":"=IF(A2>B2,\"是\",\"否\")"}],[{"type":"text","text":"=IF(A3>B3,\"是\",\"否\")"}],[{"type":"text","text":"=IF(A4>B4,\"是\",\"否\")"}]]' # Step 2: 基于辅助列值做条件格式(用 formulaCondition 引用辅助列) dws sheet cond-format create --node NODE_ID --sheet-id SHEET_ID \ - --ranges '["A2:H100"]' \ + --ranges '["A2:H4"]' \ --condition '{"formulaCondition":{"formula":"=$H2=\"是\""}}' \ --cell-style '{"backgroundColor":"#FFECEC"}' ``` @@ -143,8 +142,7 @@ Flags: - **用途**:查看指定工作表中已有的条件格式规则,或获取单个规则的详情。 - **场景**:创建/更新/删除条件格式前后验证规则状态;获取 ruleId 供后续 update/delete 使用。 -- **返回**:`rules` 数组,每条规则包含 id、type、ranges、条件参数、cellStyle/dataBarStyle 等。 -- **判空要看 `message`,不要看数组长度**:无规则时 `rules` 数组里仍会带 1 个全 null 的幽灵元素(`colorScaleCondition`/`dataBarStyle`/`iconSetCondition` 全 null,无 id),`message` 会是「No conditional formatting rules found.」。该命令**没有 `totalCount` 字段**,判空以 message 为准,或过滤掉没有 `id` 的元素。 +- **返回**:rules 数组,每条规则包含 id、type、ranges、条件参数、cellStyle/dataBarStyle 等。 ### 创建条件格式规则 ``` @@ -271,4 +269,3 @@ Flags: - **辅助列+条件格式两步走**:用户明确要求"辅助列"时,必须按两步走(先建辅助列 → 再基于辅助列做条件格式),禁止直接用 `formulaCondition` 一步绕过 - **大数据量优势**:当数据量 > 1000 行时,条件格式是首选——它由服务端自身渲染,不需要逐行调用 `range set-style`,性能远优于静态样式写入 - **判断标准**:交付后 `cond-format list` 必须能返回该规则;否则视为违规 -- ⚠️ **`cond-format list` 判空看 `message` 不看数组长度**:无规则时 `rules` 仍含 1 个全 null 幽灵元素(无 id),`message` 为「No conditional formatting rules found.」。该命令无 `totalCount`,判空以 message 为准或过滤无 `id` 的元素 diff --git a/skills/multi/dingtalk-sheet/references/sheet/sheet-dimension-operations.md b/skills/multi/dingtalk-misc/references/sheet/sheet-dimension-operations.md similarity index 84% 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 afc2f5ac..751feec0 100644 --- a/skills/multi/dingtalk-sheet/references/sheet/sheet-dimension-operations.md +++ b/skills/multi/dingtalk-misc/references/sheet/sheet-dimension-operations.md @@ -26,11 +26,12 @@ - 注意与 `append`(追加数据行)区分:`add-dimension` 追加的是空行/空列,`append` 追加的是带数据的行 - 请勿用 `range update` 写空数据来模拟追加,`add-dimension` 直接扩展表格维度 -用户说"创建行分组/创建列分组/折叠几行/折叠几列/展开行列分组/取消行列分组": -- 创建分组 → `group-dimension --range <整行或整列范围>` -- 创建并折叠 → `group-dimension --group-state fold` -- 取消分组 → `ungroup-dimension --range <整行或整列范围>` -- `--range` 只能传整行或整列范围,如 `3:7`、`C:F`、`Sheet1!3:7` +用户说"创建行分组/创建列分组/新建分组并折叠/新建分组并展开/取消分组": +- 创建连续行/列分组 → `group-dimension` +- 取消连续行/列分组 → `ungroup-dimension` +- 分组创建后用 `sheet info` 回读 `rowGroups` / `columnGroups` +- 仅创建分组时可用 `--group-state expand|fold` 指定初始展开/折叠;当前没有单独调整已有分组折叠状态的命令 +- 用户要求"折叠/展开已有分组"时,当前不支持直接修改已有分组的 `collapsed` 状态,不能承诺可用 `group-dimension` 完成 **结构预检**:插入、删除、移动行列前,必须先执行 `dws sheet info --node --sheet-id --format json` 查看 `mergedRanges`。合并区域跨过操作位置时,行列变更可能导致表头/分组标题断裂、空白或错位;需要先向用户说明影响,必要时取消合并后操作,再按原模式重新 `merge-cells`。 @@ -183,32 +184,51 @@ Flags: Usage: dws sheet group-dimension [flags] Example: + # 创建第 3~7 行分组,默认展开 dws sheet group-dimension --node --sheet-id --range "3:7" + + # 创建 C~F 列分组,并在创建后折叠 dws sheet group-dimension --node --sheet-id --range "C:F" --group-state fold + + # 使用工作表前缀(忽略 --sheet-id) dws sheet group-dimension --node --sheet-id --range "Sheet1!3:7" Flags: --node string 表格文档 ID 或 URL (必填) --sheet-id string 工作表 ID 或名称 (必填) - --range string 整行/整列范围,如 "3:7" 或 "C:F" (必填) - --group-state string 创建后的分组状态: expand 或 fold,默认 expand + --range string 整行或整列范围,A1 表示法 (必填)。行如 "3:7",列如 "C:F" + --group-state string 创建后的分组状态: expand 或 fold (默认 expand) ``` -为连续行或连续列创建分组。`--range` 必须是整行或整列范围,不支持 `A1:C5` 这类普通单元格矩形范围。 +创建连续整行或整列分组。`--range` 只能是整行范围或整列范围,不支持普通单元格矩形(如 `A1:C5`)。支持单行/单列(如 `"3:3"` / `"C:C"`)以及带工作表前缀(如 `Sheet1!3:7`),带前缀时忽略 `--sheet-id`。 + +返回中使用 `level` 表示分组展示层级,`level` 为 1-based;不要查找或依赖 `depth`。`--group-state fold` 只用于创建后的初始折叠状态,当前不提供"仅修改已有分组 collapsed 状态"的独立命令。 + +创建后用以下命令回读: +```bash +dws sheet info --node --sheet-id --format json +``` + +回读字段为 `rowGroups` / `columnGroups`,单项包含 `range`、起止行列、`count`、`level`、`collapsed`。`collapsed=true` 表示当前分组折叠。 ### 取消行或列分组 ``` Usage: dws sheet ungroup-dimension [flags] Example: + # 取消第 3~7 行分组 dws sheet ungroup-dimension --node --sheet-id --range "3:7" + + # 取消 C~F 列分组 dws sheet ungroup-dimension --node --sheet-id --range "C:F" Flags: --node string 表格文档 ID 或 URL (必填) --sheet-id string 工作表 ID 或名称 (必填) - --range string 整行/整列范围,如 "3:7" 或 "C:F" (必填) + --range string 整行或整列范围,A1 表示法 (必填)。行如 "3:7",列如 "C:F" ``` -取消指定连续行或连续列的分组。 +取消指定连续整行或整列范围上的分组。取消后同样用 `sheet info` 回读确认目标 `range` 已从 `rowGroups` / `columnGroups` 中移除。 + +`group-dimension` / `ungroup-dimension` 可以放进 `batch-update` 做原子组合;但 batch 中的 `group-dimension` 只适合默认展开分组。需要创建后立即折叠时,请使用独立 `dws sheet group-dimension --group-state fold`。 ## 核心工作流 @@ -282,15 +302,15 @@ dws sheet update-dimension --node --sheet-id \ | `update-dimension` | `a1Notation` 被更新区域范围、`hidden` 生效的显隐状态、`pixelSize` 生效的尺寸 | 确认更新结果 | | `move-dimension` | `sheetId` 工作表 ID | 确认操作完成 | | `add-dimension` | `sheetId` 工作表 ID | 确认操作完成 | -| `group-dimension` | `range` / `level` / `groupState` | 确认行列分组层级和展开状态 | -| `ungroup-dimension` | `range` / `level` | 确认行列分组已取消 | +| `group-dimension` | `range` / `level` / `groupState` | 确认创建的分组范围、层级和初始状态 | +| `ungroup-dimension` | `range` / `level` | 确认取消分组的范围和层级 | | `list` | 工作表的 `sheetId` | info / range read / range update / find 的 --sheet-id | ## 注意事项 - ★ **`--sheet-id` 获取规范(强制)**:`sheetId` 未知时必须先通过 `dws sheet list --node --format json` 查询,禁止凭空编造(如臆测为 `Sheet1`、`sheet1`、`0`、`default` 等) - `sheet info` 的 `mergedRanges` 是行列结构操作的重要预检信息。插入列时尤其要检查多行表头合并区,原有合并区域通常不会自动扩展到新列,必要时需重新设置合并区域 -- `insert-dimension` 在指定位置之前插入空行或空列,不写入数据;如需在末尾追加行/列,使用 `append` +- `insert-dimension` 在指定位置之前插入空行或空列,不写入数据;如需在末尾追加行/列,使用 `append`;如需"先插入行列再写入数据"的组合操作,使用 [batch-update](./sheet-batch-operations.md) 打包为原子事务 - `insert-dimension` 的 `--dimension` 只接受 `ROWS` 或 `COLUMNS` - `insert-dimension` 的 `--position` 支持工作表前缀(如 `Sheet1!3`),此时忽略 `--sheet-id` - `insert-dimension` 的 `--length` 最大为 5000 @@ -313,7 +333,8 @@ dws sheet update-dimension --node --sheet-id \ - `add-dimension` vs `range update`:需要在末尾追加空行/空列时,必须使用 `add-dimension` 命令,禁止用 `range update` 写空数据来模拟追加效果 - `add-dimension` 追加的是空行/空列,与 `append`(追加带数据的行)不同 - `add-dimension` 的 `--length` 必须为正整数(>= 1),行列均不超过 5000 -- `group-dimension` / `ungroup-dimension` 的 `--range` 只接受整行或整列范围,如 `3:7`、`C:F`、`Sheet1!3:7`,不支持 `A1:C5` -- `group-dimension` 的 `--group-state` 只接受 `expand` 或 `fold`,默认 `expand` -- 分组操作返回 `level` 表示当前分组层级;不要把它误认为固定深度参数 -- `batch-update` 支持 `group-dimension` / `ungroup-dimension`;需要创建后立即折叠时,优先使用独立 `group-dimension --group-state fold` +- `group-dimension` / `ungroup-dimension` 的 `--range` 只接受整行或整列范围,不接受普通单元格矩形 +- `group-dimension` 输出使用 `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 82% rename from skills/multi/dingtalk-sheet/references/sheet/sheet-dropdown.md rename to skills/multi/dingtalk-misc/references/sheet/sheet-dropdown.md index 173594e2..0b135aa4 100644 --- a/skills/multi/dingtalk-sheet/references/sheet/sheet-dropdown.md +++ b/skills/multi/dingtalk-misc/references/sheet/sheet-dropdown.md @@ -55,11 +55,10 @@ Flags: --range string 查询范围,A1 表示法,如 A1:A100 (必填) ``` -查询指定范围内的下拉列表配置信息,包括选项值和颜色。 +查询指定范围内的下拉列表配置信息,包括选项值、颜色和是否多选。 - **用途**:查看单元格已设置的下拉列表选项和配置。 - **场景**:在修改下拉列表前先查询现有配置;确认下拉列表是否设置成功。 -- **返回**:`dataValidations` 数组,相同选项的单元格聚合为一组,每组包含 `conditionValues`(选项值)、`ranges`(覆盖范围)、`options`(含 `multipleValues` 和 `colorValueMap`)。**判空以 `hasDropdown` 为准**:范围内无下拉列表时 `hasDropdown` 为 false,但 `dataValidations` 里仍会带 1 个全 null 的幽灵条目,不要用数组长度判空。 -- ⚠️ **已知限制**:`options.multipleValues` 服务端恒返回 `null`(即使该下拉是用 `--multi-select` 建的),也不返回 `enableMultiSelect` 字段——**`get-dropdown` 读回无法判断该下拉是否多选**。要判断是否多选,改用 `range read`:它返回的 `cells[].dataValidation.enableMultiSelect` 字段是准确的(实测有效)。 +- **返回**:`dataValidations` 数组,相同选项的单元格聚合为一组,每组包含 `conditionValues`(选项值)、`ranges`(覆盖范围)、`options`(含 `enableMultiSelect` 和 `colorValueMap`)。范围内无下拉列表时 `hasDropdown` 为 false。 ### 删除下拉列表 ``` @@ -91,6 +90,5 @@ Flags: - ★ **`--sheet-id` 获取规范(强制)**:`sheetId` 未知时必须先通过 `dws sheet list --node --format json` 查询,禁止凭空编造(如臆测为 `Sheet1`、`sheet1`、`0`、`default` 等) - `set-dropdown` 在指定范围内设置下拉列表,`--options` 为 JSON 数组,每个元素包含 `value`(必填)和 `color`(可选,`#RRGGBB` 格式)。选项值不能包含英文逗号。`--multi-select` 启用多选模式。如果目标范围已存在下拉列表,会被新配置覆盖 -- `get-dropdown` 查询指定范围内的下拉列表配置,返回 `dataValidations` 数组,相同选项的单元格聚合为一组。判空以 `hasDropdown` 为准(无下拉时为 false,但 `dataValidations` 仍含 1 个全 null 幽灵条目) -- ⚠️ `get-dropdown` 的 `options.multipleValues` 恒为 `null`、无 `enableMultiSelect` 字段,读回无法判断是否多选;要判断是否多选改用 `range read`(其 `dataValidation.enableMultiSelect` 准确) +- `get-dropdown` 查询指定范围内的下拉列表配置,返回 `dataValidations` 数组,相同选项的单元格聚合为一组。无下拉列表时 `hasDropdown` 为 false - `delete-dropdown` 删除指定范围内的下拉列表配置,单元格恢复为普通文本格式。已填写的值不会被清除。目标范围不存在下拉列表时操作仍返回成功 diff --git a/skills/multi/dingtalk-sheet/references/sheet/sheet-export.md b/skills/multi/dingtalk-misc/references/sheet/sheet-export.md similarity index 80% rename from skills/multi/dingtalk-sheet/references/sheet/sheet-export.md rename to skills/multi/dingtalk-misc/references/sheet/sheet-export.md index 891da573..99b38f57 100644 --- a/skills/multi/dingtalk-sheet/references/sheet/sheet-export.md +++ b/skills/multi/dingtalk-misc/references/sheet/sheet-export.md @@ -47,16 +47,16 @@ Flags: - 第 21~30 次:每次间隔 15 秒 - **硬上限:最多轮询 30 次(约 5 分钟)**,超时后命令返回错误 -**命令返回**(`--format json`,默认):输出规整 JSON `{success, jobId, downloadUrl[, outputPath]}`。轮询进度以 `[INFO] [N/30] 状态: ...` 打到 stderr,不污染 stdout 的 JSON(可直接 `python3 -m json.tool` 解析 stdout)。 -- `--output` 未指定:JSON 含 `jobId` + `downloadUrl`(链接有时效性,请尽快下载) -- `--output` 指定为文件路径:下载到该路径,JSON 额外含 `outputPath` -- `--output` 指定为已存在目录:自动从 `downloadUrl` 推断文件名保存,JSON 含 `outputPath` +**命令返回**: +- `--output` 未指定:进度日志 + 末尾输出 `jobId` 和 `downloadUrl`(链接有时效性,请尽快下载) +- `--output` 指定为文件路径:下载到该路径并输出 `导出完成: ` +- `--output` 指定为已存在目录:自动从 `downloadUrl` 推断文件名并保存到该目录下 **失败处理(命令内部已处理,Agent 仅需转述)**: -- MCP 返回 `FAILED`:命令立即返回错误并附带失败原因,**禁止自动重试 `dws sheet export`**,告知用户稍后再试 +- 导出任务返回 `FAILED`:命令立即返回错误并附带失败原因,**禁止自动重试 `dws sheet export`**,告知用户稍后再试 - 轮询 30 次仍 `PROCESSING`:命令返回超时错误,告知用户稍后再试 -**限制**:仅支持钉钉在线电子表格(axls)→ xlsx。导出钉钉文字文档请使用 `doc` 产品对应的导出工具。 +**限制**:仅支持钉钉在线电子表格(alxs)→ xlsx。导出钉钉文字文档请使用 `doc` 产品对应的导出工具。 ## 核心工作流 @@ -78,15 +78,24 @@ dws sheet export --node --output ./ # 若命令返回失败或超时,直接告知用户稍后再试,不要自动重调 dws sheet export。 ``` +```bash +# ── 工作流 13: 导出超时后查询任务状态(手动兜底)── + +# 1. 执行导出(一体化命令,内部自动轮询约 5 分钟) +dws sheet export --node --format json +# 若超时,命令返回错误;等待一段时间后重新执行导出即可 +``` + ## 上下文传递 | 操作 | 从返回中提取 | 用于 | |------|-------------|------| -| `export` | `downloadUrl`(未指定 --output)/ `outputPath`(指定 --output) | 直接下发给用户或告知文件已保存到本地。命令内部已完成轮询,不要再调用其他 export 相关命令 | +| `export` | `downloadUrl`(未指定 --output)/ `导出完成: `(指定 --output) | 直接下发给用户或告知文件已保存到本地。命令内部已完成轮询,不要再调用其他 export 相关命令 | +| `export` 超时中断 | 错误信息 | 告知用户稍后重试 | ## 注意事项 -- ★ `export` 仅支持钉钉在线电子表格(axls)→ xlsx;传入钉钉文字文档会报 `invalidRequest.document.typeIllegal` +- ★ `export` 仅支持钉钉在线电子表格(alxs)→ xlsx;传入钉钉文字文档会报 `invalidRequest.document.typeIllegal` - ★ `export` 为单命令一站式,CLI 内部已自动完成「提交 → 渐进式退避轮询 → 可选下载」,**Agent 不得在外部实现轮询或重试**;命令返回成功后不再调用其他 export 相关命令 - `export` 内置轮询策略:1~5 次间隔 2s、6~10 次间隔 5s、11~20 次间隔 10s、21~30 次间隔 15s,硬上限 30 次(约 5 分钟);超时后命令返回错误,告知用户稍后再试即可 - ★ `export` 命令返回失败或超时时,**禁止自动重调 `dws sheet export`**;直接告知用户导出失败并建议稍后再试 diff --git a/skills/multi/dingtalk-sheet/references/sheet/sheet-filter-view.md b/skills/multi/dingtalk-misc/references/sheet/sheet-filter-view.md similarity index 98% rename from skills/multi/dingtalk-sheet/references/sheet/sheet-filter-view.md rename to skills/multi/dingtalk-misc/references/sheet/sheet-filter-view.md index 631a6c6f..89b1e0d3 100644 --- a/skills/multi/dingtalk-sheet/references/sheet/sheet-filter-view.md +++ b/skills/multi/dingtalk-misc/references/sheet/sheet-filter-view.md @@ -219,7 +219,6 @@ Flags: - **用途**:查看某个筛选视图的当前配置,包括已设置的所有筛选条件详情。 - **场景**:在修改或删除筛选视图前,先确认其当前状态;或在 `update-criteria` 后验证条件是否生效。 - **区分**:`info` 返回单个视图的完整信息(含 criteria);`list` 返回所有视图的列表概要。`info` 需要指定 `--filter-view-id`,ID 可通过 `list` 获取。 -- **实现**:内部调用 `get_filter_views` 获取全部列表后按 ID 过滤。 ### 列出筛选视图所有列条件 ``` @@ -238,7 +237,6 @@ Flags: - **用途**:查看某个筛选视图当前设置了哪些列的筛选条件,包括每列的条件类型和具体规则。 - **场景**:在管理筛选条件(修改/删除特定列条件)前,先了解当前视图有哪些条件;或排查筛选结果不符合预期时检查条件配置。 - **区分**:`list-criteria` 返回所有列的条件(按列偏移量为 key 的对象);`get-criteria` 只返回指定列的条件。如果没有设置任何条件,返回空对象 `{}`。 -- **实现**:内部调用 `get_filter_views` 获取视图详情后提取 `criteria` 字段。 ### 获取单列筛选条件 ``` @@ -261,7 +259,6 @@ Flags: - **用途**:查看某个筛选视图中指定列当前设置的筛选条件,包括条件类型、运算符和比较值。 - **场景**:在修改某列条件前,先查看其当前配置;或验证 `update-criteria` 后该列条件是否正确。 - **区分**:`get-criteria` 只返回指定列的条件;`list-criteria` 返回所有列的条件。`--column` 为列偏移量(从 0 开始),相对于筛选视图范围首列。 -- **实现**:内部调用 `get_filter_views` 获取视图详情后按列偏移量过滤 `criteria` 中的对应条件。 ## 核心工作流 @@ -328,7 +325,7 @@ dws sheet filter-view create --node --sheet-id \ - ★ **`--sheet-id` 获取规范(强制)**:`sheetId` 未知时必须先通过 `dws sheet list --node --format json` 查询,禁止凭空编造(如臆测为 `Sheet1`、`sheet1`、`0`、`default` 等) - ★ **全局筛选(filter)与筛选视图(filter-view)的区别**:全局筛选影响所有协作者看到的数据展示,每个工作表最多一个;筛选视图是个人化的,互不影响。用户只说"筛选"时默认走 `filter` 系列 - `filter-view list` 获取指定工作表的所有筛选视图列表,返回的 `id` 可用于后续 info / update / delete / update-criteria / delete-criteria / list-criteria / get-criteria 的 `--filter-view-id` -- `filter-view info` 获取单个筛选视图的完整信息(含 criteria),内部复用 `get_filter_views` MCP 按 ID 过滤 +- `filter-view info` 获取单个筛选视图的完整信息(含 criteria),按 `--filter-view-id` 返回对应视图 - `filter-view list-criteria` 列出指定筛选视图已设置的所有列条件,返回按列偏移量为 key 的对象;无条件时返回空对象 `{}` - `filter-view get-criteria` 获取指定列的条件详情,`--column` 为列偏移量(从 0 开始);该列无条件时返回错误提示 - `filter-view create` 创建筛选视图时 `--range` 应包含表头行。`--criteria` 可选,不传则创建后无筛选条件,后续可通过 `filter-view update-criteria` 设置 diff --git a/skills/multi/dingtalk-sheet/references/sheet/sheet-filter.md b/skills/multi/dingtalk-misc/references/sheet/sheet-filter.md similarity index 95% rename from skills/multi/dingtalk-sheet/references/sheet/sheet-filter.md rename to skills/multi/dingtalk-misc/references/sheet/sheet-filter.md index 36d69dc4..0e65607b 100644 --- a/skills/multi/dingtalk-sheet/references/sheet/sheet-filter.md +++ b/skills/multi/dingtalk-misc/references/sheet/sheet-filter.md @@ -32,7 +32,7 @@ Flags: - **用途**:查看当前工作表上是否存在全局筛选及其配置。 - **场景**:在修改或删除筛选前,先读取当前筛选配置;创建筛选前先确认是否已存在(每个工作表只能有一个筛选)。 - **区分**:全局筛选(filter)影响所有协作者看到的数据展示;筛选视图(filter-view)是个人化的。 -- **返回**:`range`(筛选范围,A1 表示法)、`id`(筛选 ID)和 `criteria`(各列条件对象,key 为列偏移量;无条件时为 `{}`)。如果未设置筛选,返回筛选信息为空。 +- **返回**:`range`(筛选范围,A1 表示法)和 `columnFilterCriteria`(各列条件,key 为列偏移量)。如果未设置筛选,返回筛选信息为空。 ### 创建筛选 ``` @@ -152,7 +152,7 @@ Flags: | 操作 | 从返回中提取 | 用于 | |------|-------------|------| -| `filter get` | `range`(筛选范围)、`id`、`criteria`(各列条件对象,无条件为 `{}`) | 查看当前筛选配置,确认筛选是否存在 | +| `filter get` | `range`(筛选范围)、`columnFilterCriteria`(各列条件) | 查看当前筛选配置,确认筛选是否存在 | | `filter create` | 筛选创建成功的确认 | 确认筛选已建立,后续可通过 `filter update` 设置条件 | | `filter delete` | 删除成功的确认 | 确认筛选已删除 | | `filter update` | 更新成功的确认 | 确认条件已设置 | @@ -164,7 +164,7 @@ Flags: - ★ **`--sheet-id` 获取规范(强制)**:`sheetId` 未知时必须先通过 `dws sheet list --node --format json` 查询,禁止凭空编造(如臆测为 `Sheet1`、`sheet1`、`0`、`default` 等) - ★ **全局筛选(filter)与筛选视图(filter-view)的区别**:全局筛选影响所有协作者看到的数据展示,每个工作表最多一个;筛选视图是个人化的,互不影响。用户只说"筛选"时默认走 `filter` 系列 -- `filter get` 获取工作表的全局筛选信息,返回 `range`(筛选范围)、`id` 和 `criteria`(各列条件对象,无条件时为 `{}`)。无筛选时返回空 +- `filter get` 获取工作表的全局筛选信息,返回 `range`(筛选范围)和 `columnFilterCriteria`(各列条件)。无筛选时返回空 - `filter create` 创建全局筛选时 `--range` 必须包含表头行(如 `A1:E100`),不能只包含数据行。每个工作表只能有一个筛选,已存在时报错 - `filter create` 的 `--criteria` 可选,不传则仅创建空筛选框架,后续通过 `filter update` 设置条件 - `filter delete` 删除后所有筛选条件丢失且所有被隐藏行重新显示,不可恢复 diff --git a/skills/multi/dingtalk-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 93% rename from skills/multi/dingtalk-sheet/references/sheet/sheet-media-image.md rename to skills/multi/dingtalk-misc/references/sheet/sheet-media-image.md index 060c55c9..06aba4b7 100644 --- a/skills/multi/dingtalk-sheet/references/sheet/sheet-media-image.md +++ b/skills/multi/dingtalk-misc/references/sheet/sheet-media-image.md @@ -11,7 +11,7 @@ 用户说"写入图片/插入图片/加图片/放图片到单元格/嵌入图片到表格": - 写入图片 → `write-image`(需表格 ID + 工作表 ID + 单元格范围 + 本地图片路径) -- 禁止使用 `range update` 写入图片,因为 `update_range` 的 MCP 工具不支持图片类型参数,调用必定失败。必须使用 `write-image` 命令 +- 禁止使用 `range update` 写入图片;图片对象必须使用 `write-image` 命令 - 用户指定了图片尺寸 → `write-image --width N --height M` ### 浮动图片 @@ -48,8 +48,6 @@ Flags: --mime-type string 文件 MIME 类型 (默认根据扩展名推断) ``` -`--format json` 输出规整 JSON:`{success, resourceId, resourceUrl, fileName, fileSize}`。`resourceUrl` 可用于 `create-float-image` 的 `--src`。 - ### 上传图片并写入表格单元格 ``` Usage: @@ -205,10 +203,13 @@ dws sheet write-image --node --sheet-id --range C3:C3 --fil # 4. 完整流程: 创建表格 → 写表头 → 写入图片 dws sheet create --name "产品目录" -f json -# 提取 nodeId 后: -dws sheet range update --node --sheet-id Sheet1 --range "A1:B1" --values '[[{"type":"text","text":"产品名称"},{"type":"text","text":"产品图片"}]]' -f json -dws sheet range update --node --sheet-id Sheet1 --range "A2:A2" --values '[[{"type":"text","text":"MacBook Pro"}]]' -f json -dws sheet write-image --node --sheet-id Sheet1 --range B2:B2 --file ./macbook.png --width 150 --height 100 -f json +# 提取 nodeId 后,先用 list 获取真实 sheetId: +dws sheet list --node -f json +dws sheet range update --node --sheet-id --range "A1:B1" \ + --values '[[{"type":"text","text":"产品名称"},{"type":"text","text":"产品图片"}]]' -f json +dws sheet range update --node --sheet-id --range "A2:A2" \ + --values '[[{"type":"text","text":"MacBook Pro"}]]' -f json +dws sheet write-image --node --sheet-id --range B2:B2 --file ./macbook.png --width 150 --height 100 -f json ``` ## 上下文传递 @@ -229,7 +230,7 @@ dws sheet write-image --node --sheet-id Sheet1 --range B2:B2 --file ./ - ★ **`--sheet-id` 获取规范(强制)**:`sheetId` 未知时必须先通过 `dws sheet list --node --format json` 查询,禁止凭空编造(如臆测为 `Sheet1`、`sheet1`、`0`、`default` 等) - `media-upload` 是两步自动完成的流程 (获取附件上传凭证 → OSS 上传),无需手动分步操作 - `write-image` 是三步自动完成的流程 (获取附件上传凭证 → OSS 上传 → 写入图片到单元格),无需手动分步操作 -- ★ 向表格单元格中写入图片必须使用 `write-image`,禁止使用 `range update`。`range update` 底层调用的 `update_range` MCP 工具不支持图片类型参数,调用会失败 +- ★ 向表格单元格中写入图片必须使用 `write-image`,禁止使用 `range update`。`range update` 不支持图片对象 - `write-image` 与 `media-upload` 的区别:`media-upload` 仅上传附件到表格获取 resourceId;`write-image` 在上传后还会将图片写入指定单元格 - `create-float-image` 创建浮动图片前必须先通过 `media-upload` 上传图片获取 `resourceUrl`,再将其作为 `--src` 传入。`--src` 的格式为 `/core/api/resources/img/...`,不能直接传外部 URL - `create-float-image` 的 `--range` 使用 A1 表示法指定锚点单元格(如 `A1`、`B3`),支持带工作表前缀(如 `Sheet1!A1`) diff --git a/skills/multi/dingtalk-misc/references/sheet/sheet-pivot-table.md b/skills/multi/dingtalk-misc/references/sheet/sheet-pivot-table.md new file mode 100644 index 00000000..2073a4e5 --- /dev/null +++ b/skills/multi/dingtalk-misc/references/sheet/sheet-pivot-table.md @@ -0,0 +1,292 @@ +# 透视表 (pivot-table) + +## 真对象硬约束 + +当用户要求"透视表 / 分组汇总 / 交叉分析 / 按 X 统计 Y"时,**必须**通过 `pivot-table create` 创建真实的透视表对象。**禁止**用 `SUMIFS` / `COUNTIFS` 等普通公式 + `csv-put` 在原表中拼一张"看起来像透视表的汇总表"来代替——静态公式无法随源数据动态更新,且失去交互能力。判断标准:交付后 `pivot-table list` 必须能返回该对象。 + +## 使用场景 + +读写透视表对象。本 reference 覆盖 4 个命令: + +| 操作需求 | 使用命令 | 说明 | +|---------|---------|------| +| 查看已有透视表 | `pivot-table list` | 获取透视表的结构、数据源和配置 | +| 创建透视表 | `pivot-table create` | 创建透视表对象 | +| 更新透视表 | `pivot-table update` | 更新透视表配置(行/列/值/筛选字段) | +| 删除透视表 | `pivot-table delete` | 删除透视表 | + +典型工作流:先读取现有透视表了解配置 -> 执行创建/更新/删除 -> 再次读取验证结果。 + +## 行/值字段映射(创建前必做) + +创建透视表前先识别用户需求中的分组维度和聚合指标,**不要搞反**: + +- **rows(行字段)** = 分组维度,即"按什么分组"。例:部门、地区、医生、产品类别 +- **values(值字段)** = 聚合指标,即"统计什么数值"。例:销售额(`summarize_by: "sum"`)、订单数(`summarize_by: "count"`) +- **columns(列字段)** = 交叉维度(可选),即"再按什么横向展开"。例:月份、性别 + +| 用户说 | rows | values | columns | +|--------|------|--------|---------| +| "按部门统计人数" | 部门 | 姓名(`"count"`) | -- | +| "按医生统计费用和结余" | 主管医生 | 费用(`"sum"`)、结余(`"sum"`) | -- | +| "各部门男女人数" | 部门 | 姓名(`"count"`) | 性别 | + +**常见配置错误(必须注意)**: +- **数据源范围必须精确**:透视表的数据源范围必须包含表头行,且精确覆盖全部数据行列。范围过大(包含空行/空列)或过小(遗漏数据列)都会导致透视表结果错误 +- **行列字段选择要匹配用户意图**:用户说"按商品统计金额" -> 行字段=商品,值字段=金额(`summarize_by: "sum"`)。不要把行列字段搞反 +- **聚合类型要匹配**:用户说"统计数量" -> `"count"`;"统计总额" -> `"sum"`;"统计平均" -> `"average"` +- **创建后必须验证**:调用 `pivot-table list` 确认透视表结构正确 + +## 命令详细参考 + +### 获取透视表 +``` +Usage: + dws sheet pivot-table list [flags] +Example: + # 列出所有透视表 + dws sheet pivot-table list --node NODE_ID --sheet-id SHEET_ID + + # 获取单个透视表详情 + dws sheet pivot-table list --node NODE_ID --sheet-id SHEET_ID --pivot-table-id PT_ID +Flags: + --node string 表格文档 ID 或 URL (必填) + --sheet-id string 工作表 ID 或名称 (必填) + --pivot-table-id string 透视表 ID (可选,不传则返回全部) +``` + +### 创建透视表 +``` +Usage: + dws sheet pivot-table create [flags] +Example: + # 按部门统计销售额(默认自动新建工作表存放) + dws sheet pivot-table create --node NODE_ID \ + --source "'Sheet1'!A1:D100" \ + --properties '{ + "rows": [{"field": "部门"}], + "values": [{"field": "销售额", "summarize_by": "sum"}], + "show_row_grand_total": true + }' + + # 指定放置到已有工作表的特定位置 + dws sheet pivot-table create --node NODE_ID \ + --source "'Sheet1'!A1:E200" \ + --target-sheet-id TARGET_SHEET_ID --target-position "A1" \ + --properties '{ + "rows": [{"field": "部门"}], + "values": [{"field": "销售额", "summarize_by": "sum"}] + }' + + # 通过文件传入配置 + dws sheet pivot-table create --node NODE_ID \ + --source "'Sheet1'!A1:D50" --properties @pivot.json +Flags: + --node string 表格文档 ID 或 URL (必填) + --source string 数据源区域,A1 表示法含 sheet 前缀 (必填,如 "'Sheet1'!A1:D100") + --properties string 透视表配置 JSON (必填,含 rows/columns/values/filters) + --target-sheet-id string 目标工作表 ID 或名称 (可选,不传则自动新建工作表) + --target-position string 透视表放置位置 (可选,A1 格式单个 cell,如 "B5",不传默认 A1) +``` + +### 更新透视表 +``` +Usage: + dws sheet pivot-table update [flags] +Example: + # 先获取现有配置 + dws sheet pivot-table list --node NODE_ID --sheet-id SHEET_ID --pivot-table-id PT_ID + + # 修改后回写 + dws sheet pivot-table update --node NODE_ID --sheet-id SHEET_ID --pivot-table-id PT_ID \ + --properties '{ + "rows": [{"field": "部门"}], + "values": [ + {"field": "销售额", "summarize_by": "sum"}, + {"field": "订单号", "summarize_by": "count", "display_name": "订单数"} + ], + "show_row_grand_total": true + }' +Flags: + --node string 表格文档 ID 或 URL (必填) + --sheet-id string 工作表 ID 或名称 (必填) + --pivot-table-id string 透视表 ID (必填,可通过 pivot-table list 获取) + --properties string 透视表配置 JSON (必填) +``` + +### 删除透视表 +``` +Usage: + dws sheet pivot-table delete [flags] +Example: + dws sheet pivot-table delete --node NODE_ID --sheet-id SHEET_ID --pivot-table-id PT_ID +Flags: + --node string 表格文档 ID 或 URL (必填) + --sheet-id string 工作表 ID 或名称 (必填) + --pivot-table-id string 透视表 ID (必填,可通过 pivot-table list 获取) +``` + +> [强制] 危险操作:删除不可恢复。必须先向用户展示操作摘要并获得明确同意,用户同意后才加 `--yes` 执行。 + +## `--properties` JSON Schema 速查 + +**顶层字段**: + +| 字段 | 类型 | 必填 | 说明 | +|------|------|------|------| +| `rows` | object[] | 否 | 行字段数组(分组维度),详见下方「rows/columns 字段项」表 | +| `columns` | object[] | 否 | 列字段数组(交叉维度),结构同 rows | +| `values` | object[] | 是 | 值字段数组(聚合指标,至少一项),详见下方「values 字段项」表 | +| `filters` | object[] | 否 | 筛选字段数组,详见下方「filters 字段项」表 | +| `show_row_grand_total` | boolean | 否 | 是否显示行总计,默认 true | +| `show_col_grand_total` | boolean | 否 | 是否显示列总计,默认 true | +| `show_subtotals` | boolean | 否 | 是否显示分类小计,默认 true | +| `repeat_row_labels` | boolean | 否 | 是否显示重复项标签,默认 false | +| `collapse` | object | 否 | 行字段折叠状态:字段名 -> 要折叠的项目列表,如 `{"部门": ["A组", "B组"]}` | + +**rows/columns 字段项**: + +| 字段 | 类型 | 必填 | 说明 | +|------|------|------|------| +| `field` | string | 是 | 列名(表头文本),必须与数据源首行的列名完全匹配 | +| `display_name` | string | 否 | 显示名称(不传时使用 field 值) | + +**filters 字段项**: + +| 字段 | 类型 | 必填 | 说明 | +|------|------|------|------| +| `field` | string | 是 | 列名(表头文本),必须与数据源首行的列名完全匹配 | +| `display_name` | string | 否 | 显示名称(不传时使用 field 值) | + +**values 字段项**: + +| 字段 | 类型 | 必填 | 说明 | +|------|------|------|------| +| `field` | string | 是 | 列名(表头文本),必须与数据源首行的列名完全匹配 | +| `summarize_by` | string | 否 | 聚合方式,默认 sum,详见下方枚举表 | +| `display_name` | string | 否 | 显示名称(不传时自动生成,如"求和 - 销售额") | +| `show_data_as` | string | 否 | 值显示方式:`normal`/`percent_of_row_total`/`percent_of_col_total`/`percent_of_grand_total` | + +**summarize_by 枚举值**: + +| 值 | 说明 | +|----|------| +| `sum` | 求和(默认) | +| `count` | 计数 | +| `average` | 平均值 | +| `max` | 最大值 | +| `min` | 最小值 | +| `product` | 乘积 | +| `count_numbers` | 数值计数 | +| `std_dev` | 标准偏差 | +| `std_dev_p` | 总体标准偏差 | +| `var` | 方差 | +| `var_p` | 总体方差 | +| `distinct` | 去重计数 | +| `median` | 中位数 | + +## `--source` 数据源格式 + +- 格式:`'SheetName'!StartCell:EndCell` +- 示例:`'Sheet1'!A1:D100`、`'销售数据'!A1:F500` +- 必须包含表头行(通常从第 1 行开始) +- sheet 名称用单引号包裹(含空格或特殊字符时必须) +- MCP 内部自动将 source 解析为内部 sheetId + ISheetRange,CLI 直接透传即可 + +## 高级功能示例 + +```bash +# 含折叠 + show_data_as +dws sheet pivot-table create --node \ + --source "'Sheet1'!A1:E200" \ + --properties '{ + "rows": [{"field": "部门"}], + "values": [ + {"field": "销售额", "summarize_by": "sum"}, + {"field": "订单数", "summarize_by": "count", "show_data_as": "percent_of_col_total"} + ], + "collapse": {"部门": ["A组"]}, + "show_row_grand_total": true + }' +``` + +> collapse/show_data_as 为可选透传字段,CLI 和 MCP 层不做额外校验,直接传递给底层引擎处理。 + +## 核心工作流 + +```bash +# -- 工作流 1: 创建简单分组汇总 -- + +# 1. 先查 sheetId +dws sheet list --node -f json + +# 2. 查看数据范围确认列名和边界 +dws sheet csv-get --node --sheet-id --range "A1:F5" + +# 3. 创建透视表(按部门统计销售额) +dws sheet pivot-table create --node \ + --source "'Sheet1'!A1:D100" \ + --properties '{ + "rows": [{"field": "部门"}], + "values": [{"field": "销售额", "summarize_by": "sum"}], + "show_row_grand_total": true + }' + +# 4. 验证创建结果(用 create 返回的 targetSheetId 查询) +dws sheet pivot-table list --node --sheet-id + +# -- 工作流 2: 多维度交叉分析 -- + +dws sheet pivot-table create --node \ + --source "'Sheet1'!A1:E200" \ + --properties '{ + "rows": [{"field": "部门"}, {"field": "产品"}], + "columns": [{"field": "季度"}], + "values": [ + {"field": "销售额", "summarize_by": "sum"}, + {"field": "订单号", "summarize_by": "count", "display_name": "订单数"} + ], + "show_row_grand_total": true, + "show_col_grand_total": true, + "show_subtotals": true + }' + +# -- 工作流 3: 更新透视表配置 -- + +# 先获取现有配置 +dws sheet pivot-table list --node --sheet-id --pivot-table-id + +# 修改后回写(增加一个值字段) +dws sheet pivot-table update --node --sheet-id --pivot-table-id \ + --properties '{ + "rows": [{"field": "部门"}], + "values": [ + {"field": "销售额", "summarize_by": "sum"}, + {"field": "利润", "summarize_by": "average", "display_name": "平均利润"} + ], + "show_row_grand_total": true + }' +``` + +## 上下文传递 + +| 操作 | 从返回中提取 | 用于 | +|------|-------------|------| +| `pivot-table list` | `pivotTables[].id` | 后续 update / delete 的 `--pivot-table-id` | +| `pivot-table list --pivot-table-id` | 完整配置(rows/columns/values/filters/collapse/options) | update 时作为基础配置修改后回写 | +| `pivot-table create` | `pivotTable.id` | 后续 update / delete 的 `--pivot-table-id` | +| `pivot-table create` | `pivotTable.targetSheetId` | 后续 list / update / delete 的 `--sheet-id`(透视表所在工作表) | +| `pivot-table delete` | `message` | 确认删除完成 | +| `sheet list` | 工作表的 `sheetId` | 所有 pivot-table 命令的 `--sheet-id` | + +## 注意事项 + +- [强制] **`--sheet-id` 获取规范**:`sheetId` 未知时必须先通过 `dws sheet list --node --format json` 查询,禁止凭空编造(如臆测为 `Sheet1`、`sheet1`、`0`、`default` 等) +- [强制] **创建后必须验证**:透视表创建后必须调用 `pivot-table list` 验证配置是否正确 +- [强制] **pivot-table-id 禁止臆测**:必须通过 `pivot-table list` 获取真实的透视表 ID,不可编造 +- [强制] **source 必须精确**:数据源范围必须从表头行开始,精确覆盖数据区域,先用 `csv-get` 确认数据边界 +- **field 名称必须准确**:rows/columns/values/filters 中的 field 值必须与源数据表头完全一致(区分大小写) +- **透视表自动新建子表**:创建的透视表默认放置在自动新建的子表中,不会覆盖源数据。可通过 `--target-sheet-id` 和 `--target-position` 指定放置到已有工作表 +- **不支持修改数据源**:update 仅可修改字段配置和显示选项,不可修改 source +- **折叠状态**:collapse 字段用于控制行字段的展开/折叠,格式为 {字段名: [要折叠的项]} +- **大 JSON 用 @file**:`--properties` 支持 `@文件路径` 读取本地 JSON 文件 diff --git a/skills/multi/dingtalk-sheet/references/sheet/sheet-range-operations.md b/skills/multi/dingtalk-misc/references/sheet/sheet-range-operations.md similarity index 91% rename from skills/multi/dingtalk-sheet/references/sheet/sheet-range-operations.md rename to skills/multi/dingtalk-misc/references/sheet/sheet-range-operations.md index af6c076d..54bc630e 100644 --- a/skills/multi/dingtalk-sheet/references/sheet/sheet-range-operations.md +++ b/skills/multi/dingtalk-misc/references/sheet/sheet-range-operations.md @@ -22,6 +22,11 @@ - 自动填充 → `range fill` - 请勿用 `range read` 读取源数据后手动计算规律再 `range update` 写入,`range fill` 支持服务端智能填充 +用户说"批量清除/批量操作/一次执行多个写操作/原子批量/先清除再写入": +- 批量清除多个区域 → `range batch-clear` +- 组合多个不同写操作 → `batch-update` +- 详见 [sheet-batch-operations](./sheet-batch-operations.md) + 用户说"复制区域/把这块数据复制到/复制到另一个工作表": - 复制区域 → `range copy-to` - 跨工作表 → `range copy-to --target-sheet-id Sheet2` 或 `--target-range "Sheet2!A1"` @@ -82,11 +87,9 @@ Flags: --sheet-id string 工作表 ID 或名称 (必填) --source-range string 源数据范围,A1 表示法 (必填) --target-range string 目标填充范围,A1 表示法 (必填) - --fill-type string 填充类型: 不传则自动检测 / copy(复制) / onlystyle(仅格式) / withoutstyle(仅值) + --fill-type string 填充类型: series(序列,默认) / copy(复制) / onlystyle(仅格式) / withoutstyle(仅值) ``` -`--fill-type` **不传时自动检测**(根据源数据智能判断:数值序列递增、日期递增、文本复制等),这是填序列的默认行为,无需显式传值。`--help` 枚举的可选值只有 `copy` / `onlystyle` / `withoutstyle`;服务端另外也接受 `series`(强制按序列填充),但它不在 `--help` 列表里,一般用不到——要序列递增直接不传 `--fill-type` 即可。 - 目标范围须与源范围在行或列维度对齐(不支持对角填充)。 ### 复制区域 @@ -144,6 +147,8 @@ Flags: |------|-------------|------| | `list` | 工作表的 `sheetId` | range clear / range sort / range fill / range copy-to / range move-to 的 --sheet-id | +> **批量操作**(`range batch-clear` / `batch-update`)已拆分至 [sheet-batch-operations](./sheet-batch-operations.md),含写入边界 + 回读校验规范、典型组合场景示例等。 + ## 注意事项 - ★ **`--sheet-id` 获取规范(强制)**:`sheetId` 未知时必须先通过 `dws sheet list --node --format json` 查询真实的 `sheetId` / 工作表名称后再调用,禁止凭空编造(如臆测为 `Sheet1`、`sheet1`、`0`、`default` 等);用户仅给出工作表名称时,也应通过 `list` 校验该名称是否存在,避免名称大小写或拼写不一致导致失败 @@ -153,3 +158,4 @@ Flags: - ★ **排序用 `range sort` 不用 `range read` + 客户端排序 + `range update`**:服务端原子操作,支持多级排序 - ★ **排序前必须 `range read` 前几行判断表头**:读取排序范围前 3-5 行,对比首行与后续行的数据模式(类型、语义)来判断是否有表头。禁止不读就排,表头被排入数据不可撤销 - ★ **填充用 `range fill` 不用 `range read` + 手动计算 + `range update`**:服务端智能填充,支持序列递增、公式扩展等 +- ★ **批量操作详见 [sheet-batch-operations](./sheet-batch-operations.md)**:`range batch-clear` 多区域清除、`batch-update` 组合写操作,均原子事务 diff --git a/skills/multi/dingtalk-sheet/references/sheet/sheet-read-data.md b/skills/multi/dingtalk-misc/references/sheet/sheet-read-data.md similarity index 68% 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 03d52d67..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,43 +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` 回读公式文本,再用 `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` + `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: @@ -77,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: @@ -107,7 +138,7 @@ Flags: | `dataValidation` | object | 仅有数据验证时出现,无则省略 | 数据验证配置,见下表 | | `hyperlink` | object | 仅有单元格级超链接时出现,无则省略 | 整格超链接,结构为 `{type, link, text?}`,见下表 | | `richText` | object | 仅富文本单元格出现 | 富文本结构(含超链接、附件、图片、样式片段等),普通纯文本不含此字段 | -| `cellStyles` | object | 仅有显式设置的样式时出现;MCP 序列化层也可能返回全 null 空壳 | cell-level 样式,见下表。读取时只看非 null 字段;全 null 等同不存在 | +| `cellStyles` | object | 仅有显式设置的样式时出现;部分返回链路也可能返回全 null 空壳 | cell-level 样式,见下表。读取时只看非 null 字段;全 null 等同不存在 | `range read` / `range get` 不返回合并单元格结构。要看合并单元格,请先或另行调用 `dws sheet info --node --sheet-id --format json`,使用其中的 `mergedRanges`。 @@ -174,7 +205,7 @@ Flags: } ``` -说明:第一行表头有 `cellStyles`(加粗 + 背景色),第二行第二格有单元格级 `hyperlink`。注意:MCP 平台序列化会将未设置的字段填充为 null(如 `"fontStyle": null`),读取时应忽略值为 null 的字段,仅关注非 null 的属性;如果 `cellStyles` 全字段都是 null,视同不存在。`richText` 字段同理——无富文本的普通单元格可能返回 `{"type": null, "texts": null}`,视同不存在。 +说明:第一行表头有 `cellStyles`(加粗 + 背景色),第二行第二格有单元格级 `hyperlink`。注意:部分返回链路会将未设置的字段填充为 null(如 `"fontStyle": null`),读取时应忽略值为 null 的字段,仅关注非 null 的属性;如果 `cellStyles` 全字段都是 null,视同不存在。`richText` 字段同理——无富文本的普通单元格可能返回 `{"type": null, "texts": null}`,视同不存在。 **取值模式说明**: | 模式 | value 返回内容 | 适用场景 | @@ -183,6 +214,8 @@ Flags: | `raw_value` | 原始值(如 1000、45808) | 数据处理、计算 | | `formula` | 公式文本(如 =SUM(A1:A10)),无公式时回退原始值 | 查看/复制公式 | +**公式校验建议**:写公式后不要只看写入返回结果。先用 `formula` 模式确认公式文本已落表,再运行 `formula-verify` 聚合扫描错误;关键业务数值继续用 `raw_value` 抽样对账。详见 [sheet-formula](./sheet-formula.md)。 + **超时处理建议**:读取大范围数据时若出现超时或响应过慢,请主动缩小 `--range` 查询范围,**建议单次读取的单元格数量控制在 5000 个以内**(例如 50 行 × 100 列、100 行 × 50 列)。对于大表可采用分页读取策略: - 先通过 `info` 获取 `nonEmptyRange.range`,或用 `nonEmptyRange.lastRow` / `nonEmptyRange.lastColumn` 确定 A1 边界 - 按行分批读取,如 `A1:J500`、`A501:J1000`、`A1001:J1500` …… @@ -219,5 +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` -- `range read` 遇到超时或响应过慢时,应缩小 `--range` 查询范围,**单次读取的单元格数量建议控制在 5000 个以内**;数据量较大时通过 `info` 获取边界后分批读取,避免不传 `--range` 直接读取整个大工作表 +- `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 100% rename from skills/multi/dingtalk-sheet/references/sheet/sheet-search-replace.md rename to skills/multi/dingtalk-misc/references/sheet/sheet-search-replace.md 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 98% rename from skills/multi/dingtalk-sheet/references/sheet/sheet-style-format.md rename to skills/multi/dingtalk-misc/references/sheet/sheet-style-format.md index bfa4bc1f..da8e6aaa 100644 --- a/skills/multi/dingtalk-sheet/references/sheet/sheet-style-format.md +++ b/skills/multi/dingtalk-misc/references/sheet/sheet-style-format.md @@ -26,7 +26,7 @@ 用户说"设置样式/改颜色/设背景色/加粗/居中/换行/字体颜色/字号": - 仅设样式不改值 → `range set-style` -- 批量设置不同 range 的样式 → `range batch-set-style --batch ./styles.json`(内部顺序循环调 `update_range`) +- 批量设置不同 range 的样式 → `range batch-set-style --batch ./styles.json`(按配置顺序执行多条样式更新) - 写值同时附带样式 → `range update --values` 中使用 `cellStyles` 字段(参见 sheet-write-data.md) - 请勿用 `range update --values` 写空/重写来模拟纯样式变更 @@ -121,7 +121,7 @@ Flags: ``` **特性说明**: -- CLI 侧顺序循环逐条调用 `update_range`(非服务端批量),运行时输出 `[N/M]` 进度 +- CLI 按配置顺序逐条执行样式更新(非原子事务),运行时输出 `[N/M]` 进度 - 每条记录执行与 `set-style` 一致的校验:至少一项样式字段 + rows ≤ 1000 + rows×cols ≤ 30000 + 枚举合法 - 默认遇错即停(返回非 0),`--continue-on-error` 时所有条目跑完再返回首个错误 @@ -219,7 +219,7 @@ Flags: ```bash # ── 工作流 4: 写入数据并设置样式 ── -# 1. 写入数据(每个单元格必须是 object;数字也写成字符串) +# 1. 写入数据 dws sheet range update --node --sheet-id --range "A1:C3" \ --values '[[{"type":"text","text":"商品"},{"type":"text","text":"单价"},{"type":"text","text":"数量"}],[{"type":"text","text":"苹果"},{"type":"text","text":"5.5"},{"type":"text","text":"100"}],[{"type":"text","text":"香蕉"},{"type":"text","text":"3.2"},{"type":"text","text":"200"}]]' --format json diff --git a/skills/multi/dingtalk-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 71% rename from skills/multi/dingtalk-sheet/references/sheet/sheet-workbook.md rename to skills/multi/dingtalk-misc/references/sheet/sheet-workbook.md index 1d1006b1..fef1381c 100644 --- a/skills/multi/dingtalk-sheet/references/sheet/sheet-workbook.md +++ b/skills/multi/dingtalk-misc/references/sheet/sheet-workbook.md @@ -26,13 +26,15 @@ - 复制并指定名称 → `copy --name "副本名称"` - 复制并指定位置 → `copy --index N` +用户说"显示网格线/显示网格/恢复网格线": +- 显示网格线 → `show-gridline`(连续多次调用幂等,不报错) + +用户说"隐藏网格线/去掉网格/关闭网格线/展示模式/看板模式": +- 隐藏网格线 → `hide-gridline`(连续多次调用幂等,不报错) + 用户说"删除工作表/移除工作表/删掉这个Sheet": - 删除工作表 → `delete-sheet`(不可逆操作,执行前必须向用户确认) -用户说"显示网格线/隐藏网格线/去掉单元格网格": -- 显示网格线 → `show-gridline` -- 隐藏网格线 → `hide-gridline` - ## 命令详细参考 ### 创建钉钉表格文档 @@ -70,13 +72,33 @@ Example: dws sheet info --node dws sheet info --node --sheet-id dws sheet info --node --sheet-id "Sheet1" + 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 / row_heights / col_widths / hidden_rows / hidden_cols / frozen ``` 返回字段中 `mergedRanges` 是当前工作表的合并单元格范围列表(A1 表示法,如 `["C7:D11"]`)。它属于工作表结构/布局元数据:读写单元格内容前,如需判断表头、分组标题、续写位置或避开合并冲突,应先看 `sheet info`,不要在 `range read` / `csv-get` 的单元格值里寻找合并信息。 +返回字段中 `frozenRowCount` / `frozenColumnCount` 是冻结行列数量:冻结总是从工作表顶部第 1 行、左侧第 A 列开始连续计算;`0` 表示未冻结。它们是工作表级元数据,默认随 `sheet info` 返回,不需要额外 `include`。 + +最后非空数据边界通过 `nonEmptyRange` 返回,字段均为 A1/UI 语义:`range` 是从 `A1` 到最后非空单元格的范围,`lastCell` 是最后非空单元格地址,`lastRow` 是 1-based 行号,`lastColumn` 是列字母。空表时 `nonEmptyRange` 为 `null`。不要使用旧的 0-based 字段 `lastNonEmptyRow` / `lastNonEmptyColumn`。 + +返回字段: +- `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: @@ -112,16 +134,14 @@ Flags: --node string 表格文档 ID 或 URL (必填) --sheet-id string 工作表 ID 或名称 (必填) --name string 新名称,最长 100 字符,不能包含 / \ ? * [ ] : - --title string --name 的别名(兼容) --index int 新位置(从 0 开始) --hidden --hidden=true 隐藏,--hidden=false 取消隐藏 - --tab-color string 工作表标签颜色,Hex 如 #FF0000;传空字符串清除颜色 --frozen-row-count int 冻结行数,0 表示取消冻结 --frozen-column-count int 冻结列数,0 表示取消冻结 ``` -更新工作表名称、位置、隐藏状态、标签颜色、冻结行列。 -`--name`(别名 `--title`)/ `--index` / `--hidden` / `--tab-color` / `--frozen-row-count` / `--frozen-column-count` 至少提供一个;多个属性可同时传入,将在同一次请求中更新。 +更新工作表名称、位置、隐藏状态、冻结行列。 +`--name` / `--index` / `--hidden` / `--frozen-row-count` / `--frozen-column-count` 至少提供一个;多个属性可同时传入,将在同一次请求中更新。 注意: - 至少需要保留一个可见的工作表,不能将所有工作表都隐藏 @@ -169,17 +189,33 @@ Flags: - 不能删除隐藏的工作表(需先通过 `sheet update --hidden false` 取消隐藏再删除) - 不能删除最后一个可见工作表(至少保留一个可见工作表) -### 显示或隐藏网格线 +### 显示工作表网格线 ``` Usage: + dws sheet show-gridline [flags] +Example: dws sheet show-gridline --node --sheet-id - dws sheet hide-gridline --node --sheet-id + dws sheet show-gridline --node --sheet-id "Sheet1" Flags: --node string 表格文档 ID 或 URL (必填) --sheet-id string 工作表 ID 或名称 (必填) ``` -这两个命令只修改指定工作表的网格线显示状态,不修改单元格内容或边框样式。执行后可用同一命令反向恢复。 +### 隐藏工作表网格线 +``` +Usage: + dws sheet hide-gridline [flags] +Example: + dws sheet hide-gridline --node --sheet-id + dws sheet hide-gridline --node --sheet-id "Sheet1" +Flags: + --node string 表格文档 ID 或 URL (必填) + --sheet-id string 工作表 ID 或名称 (必填) +``` + +切换子表网格线显隐;二态语义在命令名里,无需额外参数(同 `update --hidden` / `update --hidden=false` 的隐藏/显示工作表模式)。 +网格线默认显示;隐藏后工作表背景为纯白色,适合截图、演示、仪表盘/看板场景(不影响打印和数据)。 +连续多次 show 或多次 hide 均为幂等操作,不会报错。 ## 核心工作流 @@ -192,13 +228,16 @@ dws sheet create --name "销售数据" --format json # 2. 查看工作表列表 — 提取 sheetId dws sheet list --node --format json -# 3. 写入表头和数据(每个单元格必须是 object;数字也写成字符串) +# 3. 写入表头和数据 dws sheet range update --node --sheet-id --range "A1:C1" \ --values '[[{"type":"text","text":"姓名"},{"type":"text","text":"部门"},{"type":"text","text":"销售额"}]]' --format json 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. 获取工作表列表 @@ -218,7 +257,7 @@ dws sheet range read --node --sheet-id --range "A1:D10" --f # 1. 新建工作表 dws sheet new --node --name "汇总" --format json -# 2. 在新工作表中写入汇总公式(公式写在 text,以 = 开头) +# 2. 在新工作表中写入汇总公式 dws sheet range update --node --sheet-id --range "A1:B1" \ --values '[[{"type":"text","text":"指标"},{"type":"text","text":"数值"}]]' --format json @@ -233,7 +272,7 @@ dws sheet range update --node --sheet-id --range "A2:B2 | `create` | `nodeId` | list / info / new / range read / range update / find 的 --node | | `list` | 工作表的 `sheetId` | info / range read / range update / find 的 --sheet-id | | `new` | 新工作表的 `sheetId` | range read / range update / find 的 --sheet-id | -| `info` | `rowCount` / `nonEmptyRange.range` / `nonEmptyRange.lastRow` / `nonEmptyRange.lastColumn` / `mergedRanges` | 确定数据范围、追加写入起始行、判断合并单元格结构 | +| `info` | `rowCount` / `nonEmptyRange.range` / `nonEmptyRange.lastRow` / `nonEmptyRange.lastColumn` / `mergedRanges` / `frozenRowCount` / `frozenColumnCount` / `rowGroups` / `columnGroups` / `rowHeights` / `colWidths` / `hiddenRows` / `hiddenCols` | 确定数据范围、追加写入起始行、判断合并单元格结构、回读冻结与行列分组、回读行高列宽与隐藏行列 | ## 注意事项 @@ -257,4 +296,6 @@ dws sheet range update --node --sheet-id --range "A2:B2 - `delete-sheet` 为不可逆操作,执行前必须向用户确认 - `delete-sheet` 不能删除隐藏的工作表,需先通过 `update --hidden=false` 取消隐藏再删除 - `delete-sheet` 不能删除最后一个可见工作表,至少保留一个可见工作表 +- `show-gridline` / `hide-gridline` 为幂等写操作:连续调用同一命令不会报错,适合 Agent 不确定当前状态时直接调用 +- `show-gridline` / `hide-gridline` 仅控制网格线视觉显示,不影响数据、打印、冻结行列等任何其他属性 - ★ 关键区分: sheet(电子表格/单元格读写) vs aitable(AI多维表/结构化记录/字段定义) vs doc(文档编辑/阅读) diff --git a/skills/multi/dingtalk-sheet/references/sheet/sheet-write-data.md b/skills/multi/dingtalk-misc/references/sheet/sheet-write-data.md similarity index 69% 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 c56c7921..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,12 +3,18 @@ ## 使用场景 用户说"写数据/填表/更新单元格/写入公式": -- 从 DataFrame/结构化对象一次写入一个或多个工作表 → `table-put` - 更新数据 → `range update` +- 写入公式前先读 [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` @@ -17,38 +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[]` | + +公式写入与校验的完整流程见 [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: @@ -80,7 +82,7 @@ Flags: --values string 单元格内容,二维 JSON 数组 (必填);每个元素必须是 object:{type:text,text:...}、{type:richText,texts:[...]}、{dataValidation:...}、{cellStyles:...}、{hyperlink:...} 或 {}(详见下文 values 参数格式说明) ``` -**合并单元格注意(`range update`)**:这里说的是 `range update` 写入单元格对象这一路径,不是所有写入命令的统一行为。目标范围与已有合并区域冲突时,MCP 服务端会拦截并返回 `MERGED_CELLS_CONFLICT` 错误,错误消息中通常会列出具体冲突的合并区域地址。收到此错误时按以下流程处理: +**合并单元格注意(`range update`)**:这里说的是 `range update` 写入单元格对象这一路径,不是所有写入命令的统一行为。目标范围与已有合并区域冲突时,服务端会拦截并返回 `MERGED_CELLS_CONFLICT` 错误,错误消息中通常会列出具体冲突的合并区域地址。收到此错误时按以下流程处理: 1. 从错误消息中获取冲突的合并区域地址(如 `A1:B2, C3:D4`),或通过 `dws sheet info --node --sheet-id --format json` 查询完整的合并区域列表(`mergedRanges` 数组) 2. 用 `dws sheet unmerge-cells --range <冲突区域>` 取消这些合并 3. 执行 `range update` 写入数据 @@ -94,6 +96,8 @@ Flags: **范围职责**:`range update` 负责写入单元格内容(原始值/公式/富文本对象),并支持通过 `cellStyles` 附带 per-cell 样式。如需批量设置整片区域的样式(不写值),请使用 `dws sheet range set-style`。 +**公式校验要求**:公式用 `{"type":"text","text":"=..."}` 写入。写完后先执行 `range read --value-render-option formula` 确认公式文本,再执行 `formula-verify` 聚合扫描错误;关键业务结果继续用 `range read --value-render-option raw_value` 抽样对账。详见 [sheet-formula](./sheet-formula.md)。 + ### 在工作表末尾追加数据 ``` Usage: @@ -116,11 +120,9 @@ Flags: Usage: dws sheet csv-put [flags] Example: - # 内联多行 CSV:必须用 $'...' 让 \n 变成真换行;普通单引号 '...\n...' 里的 \n 是字面量,会写成一行含字面 \n 的错误数据 dws sheet csv-put --node --sheet-id --start-cell A1 \ - --csv $'name,score\nAlice,95\nBob,87' + --csv 'name,score\nAlice,95\nBob,87' - # 多行数据推荐用 @文件,最稳妥 dws sheet csv-put --node --sheet-id --start-cell B2 \ --csv @data.csv --allow-overwrite @@ -140,7 +142,7 @@ Flags: 将 RFC 4180 格式的 CSV 文本写入指定工作表的指定单元格位置。 - **分隔符必须是英文逗号 `,`**(ASCII 0x2C),禁止使用中文逗号 `,`(U+FF0C)。中文逗号不会被识别为分隔符,会导致整行被写入同一个单元格。生成 CSV 内容时务必检查分隔符 - 只写纯值,不支持公式/样式/批注。`=` 开头的内容当文本处理,不会被解析为公式 -- 数字/日期/百分数由表格引擎自动识别类型(如 `95` 存为数字,`2025-03-01` 存为日期) +- 数字/日期/百分数由表格系统自动识别类型(如 `95` 存为数字,`2025-03-01` 存为日期) - 自动扩容行列:CSV 数据超出当前工作表维度时自动追加行/列 - 与 `range update` 不同,目标区域如含合并单元格,`csv-put` 会打散合并并写入纯值 - 若需要保留原有合并结构,写入前先用 `sheet info` 记录 `mergedRanges`,写入后用 `merge-cells` 恢复对应区域 @@ -149,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 是以下之一: @@ -184,7 +301,7 @@ Flags: `hyperlink` 作用于整个单元格,适合“这个单元格整体可点击跳转”的场景。它和 richText 的片段级 `link` 不同。 > **hyperlink 三种语义**: -> - **不传 `hyperlink` 字段** → 保留原超链接(引擎自动 readback 回写) +> - **不传 `hyperlink` 字段** → 保留原超链接(系统自动保留) > - **`hyperlink: {"type":"none"}`** → 显式清除单元格超链接 > - **`hyperlink: {"type":"path"/"sheet"/"range", link, text?}`** → 写新超链接(覆盖) > @@ -205,7 +322,7 @@ Flags: 注意: - 不传 `hyperlink` 字段同于 “保留原超链接”,无需先 read 再回传 -- Agent 调用统一使用 `hyperlink: {type:"none"}` 清除超链接;底层 REST 兼容 `hyperlink:null`,但 MCP schema / 网关可能过滤 null 字段,不要把 null 当默认写法 +- Agent 调用统一使用 `hyperlink: {type:"none"}` 清除超链接;不要把 `hyperlink:null` 当默认写法,避免空字段在调用链路中被过滤后语义不清 - `hyperlink` 可以不带 `type/text` cell 单独出现,用于只设置或清理链接并保留原值 - 不要把 `hyperlink` 和 `type:"richText"` 混用;整格链接用 `hyperlink`,片段链接用 richText 子项 `type:"link"` @@ -338,7 +455,7 @@ dws sheet range update --node NODE_ID --sheet-id SHEET_ID --range "A1:C1" \ ### 重要约束 -- 不再支持 `{type:"number"}` / `{type:"boolean"}` / `{type:"null"}` —— MCP `complexValues` 仅接受 `text` / `richText` 两种 type,或 `{}` 跳过。数字 / 布尔走 `{type:"text","text":"<字符串形式>"}` +- 不再支持 `{type:"number"}` / `{type:"boolean"}` / `{type:"null"}` —— 当前单元格对象协议仅接受 `text` / `richText` 两种 type,或 `{}` 跳过。数字 / 布尔走 `{type:"text","text":"<字符串形式>"}` - 不支持直接传入原始值(字符串、数字、布尔、null、空字符串);`null` 不等同于 `{}`,`null` 会报错 - 维度必须与 `--range` 范围完全一致,例如 `--range "A1:B3"` 需要 3 行 2 列的数组 - 清理整格超链接使用 `{"hyperlink":{"type":"none"}}`;不要使用 `{"hyperlink":null}` 作为 agent 默认调用形态 @@ -363,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. 写入数据 @@ -404,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` 回读类型与格式 | ## 注意事项 @@ -420,14 +541,17 @@ dws sheet append --node --sheet-id \ - 维度不足请按行 / 列补齐为同等大小;不需要修改的位置用 `{}` 跳过(保留原值),需要清空的位置用 `{"type":"text","text":""}`;禁止出现各行列数不一致或与 `--range` 不匹配的情况,否则调用会直接报错 - 如需写整格超链接,把 `{"type":"text","text":"...","hyperlink":{"type":"path","link":"..."}}` 放进 `--values` 二维数组对应的单元格里;富文本片段链接才使用 richText 子项 `type:"link"` - ★ **清空区域优先用 `range clear`(强制)**:需要清空整片区域时必须使用 `range clear`,禁止用 `range update` 模拟。仅在 `range update` 写入混合数据时个别 cell 需要清空,才在 `--values` 中用 `{"type":"text","text":""}` -- ★ **不再支持 `{type:"number"}` / `{type:"boolean"}` / `{type:"null"}`(强制)**:MCP `complexValues` 仅接受 `type:"text"` 与 `type:"richText"` 两种,CLI 会在本地直接拦截非法 type 并报错。写数字 / 布尔请用 `{"type":"text","text":"<字符串形式>"}`(服务端按内容自动识别),不要再用旧的 `value` 字段 -- **dataValidation 三语义**:不传字段=保留;`{type:"none"}`=清除;`{type:"dropdown"/"checkbox",...}`=覆盖。无需先 read 再回传,引擎自动保留原 DV +- ★ **不再支持 `{type:"number"}` / `{type:"boolean"}` / `{type:"null"}`(强制)**:当前单元格对象协议仅接受 `type:"text"` 与 `type:"richText"` 两种,CLI 会在本地直接拦截非法 type 并报错。写数字 / 布尔请用 `{"type":"text","text":"<字符串形式>"}`(服务端按内容自动识别),不要再用旧的 `value` 字段 +- **dataValidation 三语义**:不传字段=保留;`{type:"none"}`=清除;`{type:"dropdown"/"checkbox",...}`=覆盖。无需先 read 再回传,系统会保留原 DV - **hyperlink 三语义**:不传字段=保留;`{type:"none"}`=清除;`{type:"path"/"sheet"/"range",...}`=覆盖。Agent 调用不要使用 `hyperlink:null` - ★ **单次调用上限(强制)**:`range update` / `set-style` 行数 ≤ 1000,单元格总数建议 ≤ 5000(硬限 30000) - ★ **大批量纯值写入用 `csv-put` 不用 `range update`**:当写入纯值(无公式、无超链接、无富文本对象)且数据量较大时(>5 行或 >20 单元格),必须使用 `csv-put`。`csv-put` 接受 CSV 文本直接写入,无需构造二维 JSON 数组,支持自动扩容,更简洁高效。仅在需要写入公式、单元格级超链接、富文本对象,或仅更新少量单元格时才使用 `range update` +- ★ **公式写完必须分层校验**:先用 `range read --value-render-option formula` 确认公式文本,再用 `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`。`append` 和 `csv-put` 只接受原始值(字符串/数字/布尔),走的是不同的 MCP tool(`append_rows` / `set_range_from_csv`)。需要写入公式、超链接、下拉列表或跳过部分单元格时,必须使用 `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 65% rename from skills/multi/dingtalk-sheet/references/url-patterns.md rename to skills/multi/dingtalk-misc/references/url-patterns.md index 908e07ef..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-类型探测流程) 执行 @@ -19,6 +32,8 @@ | 产品 | 用途 | URL 格式 | ID 来源 | |------|------|----------|---------| | `aitable` | AI表格 Base 链接 | `https://alidocs.dingtalk.com/i/nodes/{baseId}` | `base list/search/create/get` 返回的 `baseId` | +| `aitable` | AI表格指定数据表链接 | `https://alidocs.dingtalk.com/i/nodes/{baseId}?iframeQuery=sheetId%3D{tableId}` | `baseId` + `table create/get` 或 `base get` 返回的 `tableId` | +| `aitable` | AI表格指定数据表+视图链接 | `https://alidocs.dingtalk.com/i/nodes/{baseId}?iframeQuery=sheetId%3D{tableId}%26viewId%3D{viewId}` | `baseId` + `tableId` + `view create/get` 返回的 `viewId` | | `aitable` | AI表格模板预览 | `https://docs.dingtalk.com/table/template/{templateId}` | `template search` 返回的 `templateId` | | `doc` | 文档链接 | `https://alidocs.dingtalk.com/i/nodes/{dentryUuid}` | `doc` 命令返回的 `dentryUuid` | | `sheet` | 电子表格链接 | `https://alidocs.dingtalk.com/i/nodes/{dentryUuid}` | `sheet create` 返回的 `dentryUuid` | @@ -67,7 +82,7 @@ dws doc read --node "https://alidocs.dingtalk.com/i/p/Y7kmbokZp3pgGLq2/docs/AY39 > > 你可以: > 1. 在钉钉客户端中打开该文档,将正文内容复制粘贴给我 -> 2. 如果文档已保存在你的文档空间中,可以告诉我文档名称,我通过 `dws doc search` 搜索后再读取 +> 2. 如果文档已保存在你的文档空间中,可以告诉我文档名称,我通过 `dws drive search` 搜索后再读取 --- @@ -78,45 +93,48 @@ 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` skill,按其中 `references/doc.md` 操作 | -| `contentType=ALIDOC`, `extension=axls` | `sheet` | 按 [sheet.md](./sheet.md) 操作(仅 `axls` 在线电子表格) | -| `contentType=ALIDOC`, `extension=able` | `aitable` | 将 nodeId 作为 baseId,切到 `dingtalk-aitable` skill 操作 | -| `contentType=DOCUMENT`, `extension=xlsx` / `xls` / `xlsm` / `csv` | `doc` | 必须用 `dws doc download` 下载到本地处理,禁止走 `sheet`(非在线表格,sheet 命令无法操作) | -| `contentType≠ALIDOC`, `nodeType=file` | `doc` | 调用 `dws doc download` 下载,返回文件下载链接 | -| `nodeType=folder` | `doc` | 调用 `dws doc list --folder ` 列出指定文件夹直接子节点列表 | +| `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` 列出子节点 | | 以上均不匹配 | — | 告知用户当前暂不支持该类型 | > axls vs xlsx 关键区分: > - `axls`(钉钉在线电子表格,`contentType=ALIDOC`)→ 走 `sheet` 产品线(读/写/筛选/导出等服务端原子操作) -> - `xlsx` / `xls` / `xlsm` / `csv`(上传到文档空间的本地表格文件,`contentType=DOCUMENT`)→ 必须走 `dws doc download` 下载到本地后再解析处理,严禁错误路由到 `sheet` 产品线(sheet 命令只支持在线表格,调用 xlsx 节点会直接报错) -> - 用户想把在线表格导出为 xlsx 文件 → 开源 dws CLI 暂未暴露在线表格导出能力(旧动态 schema 曾包含 `submit_export_job` / `query_export_job`,但当前 cobra 未注册),需要在钉钉客户端手动导出 xlsx +> - `xlsx` / `xls` / `xlsm` / `csv`(上传到文档空间的本地表格文件,`contentType=DOCUMENT`)→ 必须走 `dws drive download` 下载到本地后再解析处理,严禁错误路由到 `sheet` 产品线(sheet 命令只支持在线表格,调用 xlsx 节点会直接报错) +> - 用户想把在线表格导出为 xlsx 文件 → 用 `dws sheet export`(输入是 `axls`,输出是 xlsx,这是 axls → xlsx 的格式转换,不属于 xlsx 读取场景) ### 示例 ```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 doc 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 doc 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 doc list --folder "https://alidocs.dingtalk.com/i/nodes/ghi789" --format json +dws drive list --workspace --format json ``` ### 何时可跳过探测 @@ -124,4 +142,3 @@ dws doc list --folder "https://alidocs.dingtalk.com/i/nodes/ghi789" --format jso 当用户指令中已明确指定产品(如"帮我读这个文档"、"看下这个表格的数据"),可结合用户意图**跳过探测**直接路由。仅在以下情况**必须执行探测**: - 用户只粘贴 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 79% rename from skills/multi/dingtalk-attendance/scripts/attendance_my_record.py rename to skills/multi/dingtalk-misc/scripts/attendance_my_record.py index 88f6cd97..89898507 100644 --- a/skills/multi/dingtalk-attendance/scripts/attendance_my_record.py +++ b/skills/multi/dingtalk-misc/scripts/attendance_my_record.py @@ -48,25 +48,7 @@ def get_my_user_id(dry_run: bool = False) -> Optional[str]: return '' if not data or not isinstance(data, dict): return None - # 兼容两种结构: - # 1) 顶层直接给 userId - # 2) {result: [{orgEmployeeModel: {userId}}]} 包裹 - uid = data.get('userId') or data.get('userid') - if uid: - return uid - inner = data.get('result') - if isinstance(inner, dict): - inner = [inner] - if isinstance(inner, list): - for item in inner: - if not isinstance(item, dict): - continue - emp = item.get('orgEmployeeModel') - if isinstance(emp, dict) and emp.get('userId'): - return emp['userId'] - if item.get('userId'): - return item['userId'] - return None + return data.get('userId') or data.get('userid') def main(): diff --git a/skills/multi/dingtalk-attendance/scripts/attendance_report_checkin.py b/skills/multi/dingtalk-misc/scripts/attendance_report_checkin.py similarity index 99% rename from skills/multi/dingtalk-attendance/scripts/attendance_report_checkin.py rename to skills/multi/dingtalk-misc/scripts/attendance_report_checkin.py index 6b362c7c..4b5f86be 100644 --- a/skills/multi/dingtalk-attendance/scripts/attendance_report_checkin.py +++ b/skills/multi/dingtalk-misc/scripts/attendance_report_checkin.py @@ -3,7 +3,7 @@ 考勤报表导出 — 签到记录粒度 [AI Agent 强制门禁] 调用本脚本前必须先阅读: - dingtalk-workspace/references/products/attendance-report.md + references/attendance-report.md 本脚本是"签到报表导出工作流"的执行末端,工作流完整定义在 attendance-report.md。 @@ -142,7 +142,7 @@ def parse_args() -> argparse.Namespace: parser = argparse.ArgumentParser( description=( "导出签到报表 — 签到记录粒度。" - "[强制] AI Agent 必须先读 references/products/attendance-report.md 再调用本脚本。" + "[强制] AI Agent 必须先读 references/attendance-report.md 再调用本脚本。" ), ) parser.add_argument("--users", required=True, diff --git a/skills/multi/dingtalk-attendance/scripts/attendance_report_common.py b/skills/multi/dingtalk-misc/scripts/attendance_report_common.py similarity index 99% rename from skills/multi/dingtalk-attendance/scripts/attendance_report_common.py rename to skills/multi/dingtalk-misc/scripts/attendance_report_common.py index 34dce90b..da292ad4 100644 --- a/skills/multi/dingtalk-attendance/scripts/attendance_report_common.py +++ b/skills/multi/dingtalk-misc/scripts/attendance_report_common.py @@ -4,7 +4,7 @@ [AI Agent 强制门禁] 本模块不可单独执行,且任何调用方脚本 (attendance_report_detail/monthly/daily.py)执行前都必须先阅读: - dingtalk-workspace/references/products/attendance-report.md + references/attendance-report.md 工作流细节、报表类型判断、人员获取、列选择、错误处理等约束 全部在 attendance-report.md,禁止凭本脚本源码或 --help 自行组装命令。 diff --git a/skills/multi/dingtalk-attendance/scripts/attendance_report_daily.py b/skills/multi/dingtalk-misc/scripts/attendance_report_daily.py similarity index 99% rename from skills/multi/dingtalk-attendance/scripts/attendance_report_daily.py rename to skills/multi/dingtalk-misc/scripts/attendance_report_daily.py index 05e82732..fc07e49e 100644 --- a/skills/multi/dingtalk-attendance/scripts/attendance_report_daily.py +++ b/skills/multi/dingtalk-misc/scripts/attendance_report_daily.py @@ -3,7 +3,7 @@ 考勤报表导出 — 每日统计粒度 ⛔ 【AI Agent 强制门禁】调用本脚本前必须先阅读: - dingtalk-workspace/references/products/attendance-report.md + references/attendance-report.md 本脚本仅是"考勤报表导出工作流"的执行末端,工作流完整定义在 attendance-report.md, 包含但不限于: @@ -113,7 +113,7 @@ def parse_args() -> argparse.Namespace: p = argparse.ArgumentParser( description=( "导出考勤报表 — 每日统计粒度。" - "⛔ AI Agent 必须先读 references/products/attendance-report.md 再调用本脚本," + "⛔ AI Agent 必须先读 references/attendance-report.md 再调用本脚本," "禁止凭 --help 或脚本路径自行拼命令。" ), ) diff --git a/skills/multi/dingtalk-attendance/scripts/attendance_report_detail.py b/skills/multi/dingtalk-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 c3086ad7..e6a94cfb 100644 --- a/skills/multi/dingtalk-attendance/scripts/attendance_report_detail.py +++ b/skills/multi/dingtalk-misc/scripts/attendance_report_detail.py @@ -8,7 +8,7 @@ [AI Agent 强制门禁] 调用本脚本前必须先阅读: - dingtalk-workspace/references/products/attendance-report.md + references/attendance-report.md 本脚本仅是"考勤报表导出工作流"的执行末端,工作流完整定义在 attendance-report.md, 包含但不限于: @@ -88,7 +88,7 @@ def parse_args() -> argparse.Namespace: p = argparse.ArgumentParser( description=( "导出考勤报表 — 明细粒度(打卡记录)。" - "[强制] AI Agent 必须先读 references/products/attendance-report.md 再调用本脚本," + "[强制] AI Agent 必须先读 references/attendance-report.md 再调用本脚本," "禁止凭 --help 或脚本路径自行拼命令。" ), ) diff --git a/skills/multi/dingtalk-attendance/scripts/attendance_report_monthly.py b/skills/multi/dingtalk-misc/scripts/attendance_report_monthly.py similarity index 99% rename from skills/multi/dingtalk-attendance/scripts/attendance_report_monthly.py rename to skills/multi/dingtalk-misc/scripts/attendance_report_monthly.py index b9323b2b..61fdb684 100644 --- a/skills/multi/dingtalk-attendance/scripts/attendance_report_monthly.py +++ b/skills/multi/dingtalk-misc/scripts/attendance_report_monthly.py @@ -3,7 +3,7 @@ 考勤报表导出 — 月度汇总粒度 [AI Agent 强制门禁] 调用本脚本前必须先阅读: - dingtalk-workspace/references/products/attendance-report.md + references/attendance-report.md 本脚本仅是"考勤报表导出工作流"的执行末端,工作流完整定义在 attendance-report.md, 包含但不限于: @@ -108,7 +108,7 @@ def parse_args() -> argparse.Namespace: p = argparse.ArgumentParser( description=( "导出考勤报表 — 月度汇总粒度。" - "[强制] AI Agent 必须先读 references/products/attendance-report.md 再调用本脚本," + "[强制] AI Agent 必须先读 references/attendance-report.md 再调用本脚本," "禁止凭 --help 或脚本路径自行拼命令。" ), ) diff --git a/skills/multi/dingtalk-attendance/scripts/attendance_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 279ce6c8..db2cac02 100644 --- a/skills/multi/dingtalk-attendance/scripts/attendance_schedule_export.py +++ b/skills/multi/dingtalk-misc/scripts/attendance_schedule_export.py @@ -3,7 +3,7 @@ 考勤排班查询导出脚本 [AI Agent 强制门禁] 本脚本执行前必须先阅读: - dingtalk-workspace/references/products/attendance-schedule.md + references/attendance-schedule.md 职责: 1. 分批查询排班记录(支持大量用户自动分批) diff --git a/skills/multi/dingtalk-attendance/scripts/attendance_schedule_import.py b/skills/multi/dingtalk-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 5d914e07..2ca45322 100644 --- a/skills/multi/dingtalk-attendance/scripts/attendance_schedule_import.py +++ b/skills/multi/dingtalk-misc/scripts/attendance_schedule_import.py @@ -3,7 +3,7 @@ 考勤排班导入脚本 [AI Agent 强制门禁] 本脚本执行前必须先阅读: - dingtalk-workspace/references/products/attendance-schedule.md + references/attendance-schedule.md 排班工作流、参数校验、班次校验、回显确认等约束全部在 attendance-schedule.md,禁止凭本脚本源码或 --help 自行组装命令。 diff --git a/skills/multi/dingtalk-attendance/scripts/attendance_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 99% rename from skills/multi/dingtalk-attendance/scripts/attendance_vacation_balance.py rename to skills/multi/dingtalk-misc/scripts/attendance_vacation_balance.py index 675368e7..dcb1f1c0 100644 --- a/skills/multi/dingtalk-attendance/scripts/attendance_vacation_balance.py +++ b/skills/multi/dingtalk-misc/scripts/attendance_vacation_balance.py @@ -3,7 +3,7 @@ 假期余额 Excel 导出脚本。 [AI Agent 强制门禁] 调用本脚本前必须先阅读: - dingtalk-workspace/references/products/attendance-vacation.md + references/attendance-vacation.md 本脚本负责: 1. 通过 dws attendance vacation types 获取假期规则列表,用于确定列顺序 @@ -86,7 +86,7 @@ def parse_args() -> argparse.Namespace: parser = argparse.ArgumentParser( description=( "导出假期余额 Excel。AI Agent 必须先读 " - "references/products/attendance-vacation.md 再调用本脚本。" + "references/attendance-vacation.md 再调用本脚本。" ), ) parser.add_argument("--users", required=True, help="userId 或 deptId 列表,逗号分隔") diff --git a/skills/multi/dingtalk-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', + '