Compare commits
10
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7c1edadd4d | ||
|
|
2699d2cfbe | ||
|
|
798def7337 | ||
|
|
5af3f0a8ba | ||
|
|
36803ac9bb | ||
|
|
31cec9efa7 | ||
|
|
88f5a5a0e8 | ||
|
|
9dd8e232bb | ||
|
|
7094e5eceb | ||
|
|
e909f1083b |
@@ -15,11 +15,75 @@ package app
|
||||
|
||||
import (
|
||||
stderrors "errors"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
apperrors "github.com/DingTalk-Real-AI/dingtalk-workspace-cli/internal/errors"
|
||||
)
|
||||
|
||||
func TestChatGroupCreateMembersGuidanceUsesGenericExample(t *testing.T) {
|
||||
root := NewRootCommand()
|
||||
cmd := mustFindCommand(t, root, "chat", "group", "create")
|
||||
err := enrichChatWorkbookError(cmd, stderrors.New("unknown flag: --members"))
|
||||
|
||||
var typed *apperrors.Error
|
||||
if !stderrors.As(err, &typed) {
|
||||
t.Fatalf("error = %T, want *errors.Error", err)
|
||||
}
|
||||
joined := strings.Join(typed.Examples, "\n")
|
||||
for _, unwanted := range []string{"V2评审小组", "489149", "550582"} {
|
||||
if strings.Contains(joined, unwanted) {
|
||||
t.Fatalf("example is fitted to evaluation data %q: %s", unwanted, joined)
|
||||
}
|
||||
}
|
||||
for _, want := range []string{"<群名称>", "<userId1>,<userId2>"} {
|
||||
if !strings.Contains(joined, want) {
|
||||
t.Fatalf("generic example missing %q: %s", want, joined)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestChatHintPathsAreReportedAsUnknownSubcommandsBeforeFlagParsing(t *testing.T) {
|
||||
for _, tc := range []struct {
|
||||
name string
|
||||
args []string
|
||||
message string
|
||||
flag string
|
||||
}{
|
||||
{name: "group search", args: []string{"chat", "group", "search", "--query", "1", "--format", "json"}, message: "chat group 下不存在 search 子命令", flag: "--query"},
|
||||
{name: "send", args: []string{"chat", "send", "--group", "cid", "--text", "hi", "--format", "json"}, message: "chat 下不存在 send 子命令", flag: "--group"},
|
||||
{name: "history", args: []string{"chat", "history", "--group", "cid", "--time", "2026-08-03 10:00:00", "--format", "json"}, message: "chat 下不存在 history 子命令", flag: "--group"},
|
||||
} {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
err := validateChatWorkbookRawArgs(tc.args)
|
||||
var typed *apperrors.Error
|
||||
if !stderrors.As(err, &typed) {
|
||||
t.Fatalf("error = %T, want *errors.Error", err)
|
||||
}
|
||||
if typed.Message != tc.message || typed.Reason != "unknown_subcommand" {
|
||||
t.Fatalf("guidance = %#v", typed)
|
||||
}
|
||||
if strings.Contains(typed.Message, tc.flag) || strings.Contains(typed.Reason, "unknown flag") {
|
||||
t.Fatalf("subcommand error was misreported as a flag error: %#v", typed)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
root := NewRootCommand()
|
||||
chat := mustFindCommand(t, root, "chat")
|
||||
for _, child := range chat.Commands() {
|
||||
if child.Name() == "send" || child.Name() == "history" {
|
||||
t.Fatalf("chat %s must not be registered as a hint subcommand", child.Name())
|
||||
}
|
||||
}
|
||||
group := mustFindCommand(t, root, "chat", "group")
|
||||
for _, child := range group.Commands() {
|
||||
if child.Name() == "search" {
|
||||
t.Fatal("chat group search must not be registered as a hint subcommand")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestValidateChatWorkbookRawArgs(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
|
||||
+22
-1
@@ -168,6 +168,27 @@ func suppressJSONDeprecationPreamble(root *cobra.Command, args []string) {
|
||||
func validateChatWorkbookRawArgs(args []string) error {
|
||||
path := strings.Join(args, " ")
|
||||
switch {
|
||||
case len(args) >= 3 && args[0] == "chat" && args[1] == "group" && args[2] == "search":
|
||||
return apperrors.NewValidation(
|
||||
"chat group 下不存在 search 子命令",
|
||||
apperrors.WithReason("unknown_subcommand"),
|
||||
apperrors.WithActions("群聊搜索使用 dws chat search,而不是 dws chat group search", "移除路径中的 group 后重试"),
|
||||
apperrors.WithExamples(`dws chat search --query <群名关键词> --format json`),
|
||||
)
|
||||
case len(args) >= 2 && args[0] == "chat" && args[1] == "send":
|
||||
return apperrors.NewValidation(
|
||||
"chat 下不存在 send 子命令",
|
||||
apperrors.WithReason("unknown_subcommand"),
|
||||
apperrors.WithActions("发送消息使用 dws chat message send,而不是 dws chat send", "在路径中补充 message 后重试"),
|
||||
apperrors.WithExamples(`dws chat message send --group <openConversationId> --text <消息正文> --format json`),
|
||||
)
|
||||
case len(args) >= 2 && args[0] == "chat" && args[1] == "history":
|
||||
return apperrors.NewValidation(
|
||||
"chat 下不存在 history 子命令",
|
||||
apperrors.WithReason("unknown_subcommand"),
|
||||
apperrors.WithActions("查询会话消息使用 dws chat message list,而不是 dws chat history", "改用 message list 并按帮助补充目标和时间参数"),
|
||||
apperrors.WithExamples(`dws chat message list --group <openConversationId> --time <YYYY-MM-DD HH:mm:ss> --format json`),
|
||||
)
|
||||
case strings.HasPrefix(path, "chat message send ") && rawArgsFlagValue(args, "msg-type") == "file" &&
|
||||
rawArgsContainFlag(args, "media-id"):
|
||||
return apperrors.NewValidation(
|
||||
@@ -441,7 +462,7 @@ func enrichChatWorkbookError(cmd *cobra.Command, err error) error {
|
||||
"建群命令不支持 --members",
|
||||
"chat group create 使用 --users 接收逗号分隔的成员 userId;--members 是其他命令的参数名",
|
||||
[]string{"将 --members 改为 --users", "成员标识不确定时先查询 userId"},
|
||||
[]string{`dws chat group create --name "V2评审小组" --users 489149,550582 --format json`},
|
||||
[]string{`dws chat group create --name "<群名称>" --users <userId1>,<userId2> --format json`},
|
||||
}
|
||||
case path == "chat group bots" && strings.Contains(message, "unknown flag: --id"):
|
||||
guide = chatWorkbookGuidance{
|
||||
|
||||
@@ -90,6 +90,56 @@ func TestChatAgentGuidanceRendersOnlyOnStdout(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestCandidateAlignmentChatGuidance(t *testing.T) {
|
||||
tests := []struct {
|
||||
args []string
|
||||
wants []string
|
||||
}{
|
||||
{[]string{"chat", "message", "query-send-status", "--help"}, []string{"sendStatus=SUCCESS", "FAILED"}},
|
||||
{[]string{"chat", "+messages-query-send-status", "--help"}, []string{"sendStatus=SUCCESS", "openTaskId"}},
|
||||
{[]string{"chat", "message", "set-pin-msg", "--help"}, []string{"openTaskId", "set-top-msg", "chat set-top"}},
|
||||
{[]string{"chat", "message", "unset-pin-msg", "--help"}, []string{"复用", "unset-top-msg"}},
|
||||
{[]string{"chat", "message", "add-emoji", "--help"}, []string{"同一条真实消息", "openTaskId"}},
|
||||
{[]string{"chat", "message", "remove-emoji", "--help"}, []string{"复用", "表情名称"}},
|
||||
{[]string{"chat", "group", "members", "remove", "--help"}, []string{"群主", "转让群主"}},
|
||||
{[]string{"chat", "group", "update-icon", "--help"}, []string{"dentryId", "能力边界"}},
|
||||
{[]string{"chat", "group", "update-settings", "--help"}, []string{"群级设置", "user-settings set"}},
|
||||
{[]string{"chat", "group", "user-settings", "query", "--help"}, []string{"当前用户视角", "保存原值"}},
|
||||
{[]string{"chat", "group", "user-settings", "set", "--help"}, []string{"再次 query", "真实值恢复"}},
|
||||
}
|
||||
for _, tc := range tests {
|
||||
cmd := NewRootCommand()
|
||||
var stdout bytes.Buffer
|
||||
cmd.SetOut(&stdout)
|
||||
cmd.SetErr(io.Discard)
|
||||
cmd.SetArgs(tc.args)
|
||||
if err := cmd.Execute(); err != nil {
|
||||
t.Fatalf("%v: %v", tc.args, err)
|
||||
}
|
||||
for _, want := range tc.wants {
|
||||
if !strings.Contains(stdout.String(), want) {
|
||||
t.Fatalf("%v help missing %q:\n%s", tc.args, want, stdout.String())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestCandidateAlignmentDriveUploadSchemaGuidance(t *testing.T) {
|
||||
cmd := NewRootCommand()
|
||||
var stdout bytes.Buffer
|
||||
cmd.SetOut(&stdout)
|
||||
cmd.SetErr(io.Discard)
|
||||
cmd.SetArgs([]string{"schema", "drive.upload", "--format", "json"})
|
||||
if err := cmd.Execute(); err != nil {
|
||||
t.Fatalf("drive.upload schema: %v", err)
|
||||
}
|
||||
for _, want := range []string{"暂时不要发送", "不会发送聊天消息"} {
|
||||
if !strings.Contains(stdout.String(), want) {
|
||||
t.Fatalf("drive.upload schema missing %q:\n%s", want, stdout.String())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestRootKeepsMainBranchChatCompatibilityCommands(t *testing.T) {
|
||||
root := NewRootCommand()
|
||||
listDirect := mustFindCommand(t, root, "chat", "message", "list-direct")
|
||||
|
||||
@@ -1099,7 +1099,7 @@ var generatedParamAliases = []ParamAliasEntry{
|
||||
"open-conversation-ids": "conversation-ids",
|
||||
"user-ids": "users",
|
||||
},
|
||||
Blocked: []string{"at-user-ids", "chat-id", "conversation-id", "group-id", "group-ids", "open-conversation-id", "staff-id", "uid", "userid"},
|
||||
Blocked: []string{"at-user-ids", "chat-id", "group-id", "group-ids", "open-conversation-id", "staff-id", "uid", "userid"},
|
||||
},
|
||||
{
|
||||
CLIPath: "chat message send",
|
||||
|
||||
@@ -264,7 +264,7 @@
|
||||
"agent_summary_source": "dws-agent-selection/aitable",
|
||||
"availability": "available",
|
||||
"avoid_when": [
|
||||
"需要该 Shortcut 未公开的底层参数、原始响应或不同执行语义时,改用对应原子命令"
|
||||
"知道名称并要解析唯一 baseId 用 +resolve-base;按关键词要完整候选用 +base-search;不要把最近访问列表描述成全部 Base"
|
||||
],
|
||||
"confirmation": "not_required",
|
||||
"effect": "read",
|
||||
@@ -308,7 +308,7 @@
|
||||
},
|
||||
"avoid_when": {
|
||||
"value": [
|
||||
"需要该 Shortcut 未公开的底层参数、原始响应或不同执行语义时,改用对应原子命令"
|
||||
"知道名称并要解析唯一 baseId 用 +resolve-base;按关键词要完整候选用 +base-search;不要把最近访问列表描述成全部 Base"
|
||||
],
|
||||
"source": "internal/cli/schema_hints/selection/aitable.json",
|
||||
"precedence": "reviewed_explicit",
|
||||
@@ -317,7 +317,7 @@
|
||||
"candidates": [
|
||||
{
|
||||
"value": [
|
||||
"需要该 Shortcut 未公开的底层参数、原始响应或不同执行语义时,改用对应原子命令"
|
||||
"知道名称并要解析唯一 baseId 用 +resolve-base;按关键词要完整候选用 +base-search;不要把最近访问列表描述成全部 Base"
|
||||
],
|
||||
"source": "internal/cli/schema_hints/selection/aitable.json",
|
||||
"precedence": "reviewed_explicit",
|
||||
@@ -525,7 +525,7 @@
|
||||
"agent_summary_source": "dws-agent-selection/aitable",
|
||||
"availability": "available",
|
||||
"avoid_when": [
|
||||
"需要该 Shortcut 未公开的底层参数、原始响应或不同执行语义时,改用对应原子命令"
|
||||
"只需唯一 baseId 用 +resolve-base;浏览最近访问用 +base-list;需要未公开底层参数或原始响应才用 base search"
|
||||
],
|
||||
"confirmation": "not_required",
|
||||
"effect": "read",
|
||||
@@ -568,7 +568,7 @@
|
||||
},
|
||||
"avoid_when": {
|
||||
"value": [
|
||||
"需要该 Shortcut 未公开的底层参数、原始响应或不同执行语义时,改用对应原子命令"
|
||||
"只需唯一 baseId 用 +resolve-base;浏览最近访问用 +base-list;需要未公开底层参数或原始响应才用 base search"
|
||||
],
|
||||
"source": "internal/cli/schema_hints/selection/aitable.json",
|
||||
"precedence": "reviewed_explicit",
|
||||
@@ -577,7 +577,7 @@
|
||||
"candidates": [
|
||||
{
|
||||
"value": [
|
||||
"需要该 Shortcut 未公开的底层参数、原始响应或不同执行语义时,改用对应原子命令"
|
||||
"只需唯一 baseId 用 +resolve-base;浏览最近访问用 +base-list;需要未公开底层参数或原始响应才用 base search"
|
||||
],
|
||||
"source": "internal/cli/schema_hints/selection/aitable.json",
|
||||
"precedence": "reviewed_explicit",
|
||||
@@ -1815,7 +1815,7 @@
|
||||
"agent_summary_source": "dws-agent-selection/aitable",
|
||||
"availability": "available",
|
||||
"avoid_when": [
|
||||
"需要该 Shortcut 未公开的底层参数、原始响应或不同执行语义时,改用对应原子命令"
|
||||
"只需精简 fieldId/type 目录用 +table-get;创建或修改字段用 field create/update;需要未公开底层语义才用 field get"
|
||||
],
|
||||
"confirmation": "not_required",
|
||||
"effect": "read",
|
||||
@@ -1858,7 +1858,7 @@
|
||||
},
|
||||
"avoid_when": {
|
||||
"value": [
|
||||
"需要该 Shortcut 未公开的底层参数、原始响应或不同执行语义时,改用对应原子命令"
|
||||
"只需精简 fieldId/type 目录用 +table-get;创建或修改字段用 field create/update;需要未公开底层语义才用 field get"
|
||||
],
|
||||
"source": "internal/cli/schema_hints/selection/aitable.json",
|
||||
"precedence": "reviewed_explicit",
|
||||
@@ -1867,7 +1867,7 @@
|
||||
"candidates": [
|
||||
{
|
||||
"value": [
|
||||
"需要该 Shortcut 未公开的底层参数、原始响应或不同执行语义时,改用对应原子命令"
|
||||
"只需精简 fieldId/type 目录用 +table-get;创建或修改字段用 field create/update;需要未公开底层语义才用 field get"
|
||||
],
|
||||
"source": "internal/cli/schema_hints/selection/aitable.json",
|
||||
"precedence": "reviewed_explicit",
|
||||
@@ -3624,7 +3624,7 @@
|
||||
"agent_summary_source": "dws-agent-selection/aitable",
|
||||
"availability": "available",
|
||||
"avoid_when": [
|
||||
"需要该 Shortcut 未公开的底层参数、原始响应或不同执行语义时,改用对应原子命令"
|
||||
"完全空行扫描用 +record-query-empty;变更历史用 +record-history-list;需要 --all/--page-limit 或未公开底层语义才用 record query"
|
||||
],
|
||||
"confirmation": "not_required",
|
||||
"effect": "read",
|
||||
@@ -3667,7 +3667,7 @@
|
||||
},
|
||||
"avoid_when": {
|
||||
"value": [
|
||||
"需要该 Shortcut 未公开的底层参数、原始响应或不同执行语义时,改用对应原子命令"
|
||||
"完全空行扫描用 +record-query-empty;变更历史用 +record-history-list;需要 --all/--page-limit 或未公开底层语义才用 record query"
|
||||
],
|
||||
"source": "internal/cli/schema_hints/selection/aitable.json",
|
||||
"precedence": "reviewed_explicit",
|
||||
@@ -3676,7 +3676,7 @@
|
||||
"candidates": [
|
||||
{
|
||||
"value": [
|
||||
"需要该 Shortcut 未公开的底层参数、原始响应或不同执行语义时,改用对应原子命令"
|
||||
"完全空行扫描用 +record-query-empty;变更历史用 +record-history-list;需要 --all/--page-limit 或未公开底层语义才用 record query"
|
||||
],
|
||||
"source": "internal/cli/schema_hints/selection/aitable.json",
|
||||
"precedence": "reviewed_explicit",
|
||||
@@ -4659,7 +4659,7 @@
|
||||
"agent_summary_source": "dws-agent-selection/aitable",
|
||||
"availability": "available",
|
||||
"avoid_when": [
|
||||
"需要该 Shortcut 未公开的底层参数、原始响应或不同执行语义时,改用对应原子命令"
|
||||
"多候选时必须让用户消歧;只要完整候选列表用 +base-search;浏览最近访问用 +base-list;已知 baseId 不再重复搜索"
|
||||
],
|
||||
"confirmation": "not_required",
|
||||
"effect": "read",
|
||||
@@ -4702,7 +4702,7 @@
|
||||
},
|
||||
"avoid_when": {
|
||||
"value": [
|
||||
"需要该 Shortcut 未公开的底层参数、原始响应或不同执行语义时,改用对应原子命令"
|
||||
"多候选时必须让用户消歧;只要完整候选列表用 +base-search;浏览最近访问用 +base-list;已知 baseId 不再重复搜索"
|
||||
],
|
||||
"source": "internal/cli/schema_hints/selection/aitable.json",
|
||||
"precedence": "reviewed_explicit",
|
||||
@@ -4711,7 +4711,7 @@
|
||||
"candidates": [
|
||||
{
|
||||
"value": [
|
||||
"需要该 Shortcut 未公开的底层参数、原始响应或不同执行语义时,改用对应原子命令"
|
||||
"多候选时必须让用户消歧;只要完整候选列表用 +base-search;浏览最近访问用 +base-list;已知 baseId 不再重复搜索"
|
||||
],
|
||||
"source": "internal/cli/schema_hints/selection/aitable.json",
|
||||
"precedence": "reviewed_explicit",
|
||||
@@ -4877,7 +4877,7 @@
|
||||
},
|
||||
"use_when": {
|
||||
"value": [
|
||||
"当你只知道某个多维表 Base 的名称(或名称里的关键词)、想把它解析成可直接用于后续工具的 baseId 时使用;内部按 --name 关键词调用 search_bases 搜索 Base,再在本地投影出每个候选的 baseId 和 name。如果只命中一个 Base 就直接返回它的 baseId;如果命中多个则列出全部候选让你消歧,绝不替你瞎猜;如果一个都没命中则提示未找到。这是纯只读操作,只做搜索与本地投影,不会修改任何 Base。"
|
||||
"只知道 Base 名称或关键词,需要解析成唯一 baseId 供后续 Table/Record 操作时"
|
||||
],
|
||||
"source": "internal/cli/schema_hints/selection/aitable.json",
|
||||
"precedence": "reviewed_explicit",
|
||||
@@ -4886,7 +4886,7 @@
|
||||
"candidates": [
|
||||
{
|
||||
"value": [
|
||||
"当你只知道某个多维表 Base 的名称(或名称里的关键词)、想把它解析成可直接用于后续工具的 baseId 时使用;内部按 --name 关键词调用 search_bases 搜索 Base,再在本地投影出每个候选的 baseId 和 name。如果只命中一个 Base 就直接返回它的 baseId;如果命中多个则列出全部候选让你消歧,绝不替你瞎猜;如果一个都没命中则提示未找到。这是纯只读操作,只做搜索与本地投影,不会修改任何 Base。"
|
||||
"只知道 Base 名称或关键词,需要解析成唯一 baseId 供后续 Table/Record 操作时"
|
||||
],
|
||||
"source": "internal/cli/schema_hints/selection/aitable.json",
|
||||
"precedence": "reviewed_explicit",
|
||||
@@ -4909,7 +4909,7 @@
|
||||
"internal/cli/schema_hints/selection/aitable.json"
|
||||
],
|
||||
"use_when": [
|
||||
"当你只知道某个多维表 Base 的名称(或名称里的关键词)、想把它解析成可直接用于后续工具的 baseId 时使用;内部按 --name 关键词调用 search_bases 搜索 Base,再在本地投影出每个候选的 baseId 和 name。如果只命中一个 Base 就直接返回它的 baseId;如果命中多个则列出全部候选让你消歧,绝不替你瞎猜;如果一个都没命中则提示未找到。这是纯只读操作,只做搜索与本地投影,不会修改任何 Base。"
|
||||
"只知道 Base 名称或关键词,需要解析成唯一 baseId 供后续 Table/Record 操作时"
|
||||
]
|
||||
},
|
||||
"aitable +resolve-table": {
|
||||
@@ -4917,7 +4917,7 @@
|
||||
"agent_summary_source": "dws-agent-selection/aitable",
|
||||
"availability": "available",
|
||||
"avoid_when": [
|
||||
"需要该 Shortcut 未公开的底层参数、原始响应或不同执行语义时,改用对应原子命令"
|
||||
"多候选时必须让用户消歧;需要所有表及字段/视图目录用 +table-get;已知 tableId 不再重复解析"
|
||||
],
|
||||
"confirmation": "not_required",
|
||||
"effect": "read",
|
||||
@@ -4960,7 +4960,7 @@
|
||||
},
|
||||
"avoid_when": {
|
||||
"value": [
|
||||
"需要该 Shortcut 未公开的底层参数、原始响应或不同执行语义时,改用对应原子命令"
|
||||
"多候选时必须让用户消歧;需要所有表及字段/视图目录用 +table-get;已知 tableId 不再重复解析"
|
||||
],
|
||||
"source": "internal/cli/schema_hints/selection/aitable.json",
|
||||
"precedence": "reviewed_explicit",
|
||||
@@ -4969,7 +4969,7 @@
|
||||
"candidates": [
|
||||
{
|
||||
"value": [
|
||||
"需要该 Shortcut 未公开的底层参数、原始响应或不同执行语义时,改用对应原子命令"
|
||||
"多候选时必须让用户消歧;需要所有表及字段/视图目录用 +table-get;已知 tableId 不再重复解析"
|
||||
],
|
||||
"source": "internal/cli/schema_hints/selection/aitable.json",
|
||||
"precedence": "reviewed_explicit",
|
||||
@@ -5135,7 +5135,7 @@
|
||||
},
|
||||
"use_when": {
|
||||
"value": [
|
||||
"当你已经知道某个多维表 Base 的 baseId、又只记得里面某张数据表(table)的名称或名称关键词、想把它解析成可直接用于后续工具的 tableId 时使用;内部先用 get_tables(只传 baseId)列出该 Base 下的全部数据表,再在本地把每张表投影成 tableId、name,并按 --name 关键词做大小写不敏感的包含匹配来筛选候选。如果只命中一张表就直接返回它的 tableId;如果命中多张则列出全部候选让你消歧,绝不替你瞎猜;如果一张都没命中则提示未找到。这是纯只读操作,只做列举、本地匹配与投影,不会创建、修改或删除任何数据表。"
|
||||
"已有真实 baseId、只知道数据表名称或关键词,需要解析成唯一 tableId 时"
|
||||
],
|
||||
"source": "internal/cli/schema_hints/selection/aitable.json",
|
||||
"precedence": "reviewed_explicit",
|
||||
@@ -5144,7 +5144,7 @@
|
||||
"candidates": [
|
||||
{
|
||||
"value": [
|
||||
"当你已经知道某个多维表 Base 的 baseId、又只记得里面某张数据表(table)的名称或名称关键词、想把它解析成可直接用于后续工具的 tableId 时使用;内部先用 get_tables(只传 baseId)列出该 Base 下的全部数据表,再在本地把每张表投影成 tableId、name,并按 --name 关键词做大小写不敏感的包含匹配来筛选候选。如果只命中一张表就直接返回它的 tableId;如果命中多张则列出全部候选让你消歧,绝不替你瞎猜;如果一张都没命中则提示未找到。这是纯只读操作,只做列举、本地匹配与投影,不会创建、修改或删除任何数据表。"
|
||||
"已有真实 baseId、只知道数据表名称或关键词,需要解析成唯一 tableId 时"
|
||||
],
|
||||
"source": "internal/cli/schema_hints/selection/aitable.json",
|
||||
"precedence": "reviewed_explicit",
|
||||
@@ -5167,7 +5167,7 @@
|
||||
"internal/cli/schema_hints/selection/aitable.json"
|
||||
],
|
||||
"use_when": [
|
||||
"当你已经知道某个多维表 Base 的 baseId、又只记得里面某张数据表(table)的名称或名称关键词、想把它解析成可直接用于后续工具的 tableId 时使用;内部先用 get_tables(只传 baseId)列出该 Base 下的全部数据表,再在本地把每张表投影成 tableId、name,并按 --name 关键词做大小写不敏感的包含匹配来筛选候选。如果只命中一张表就直接返回它的 tableId;如果命中多张则列出全部候选让你消歧,绝不替你瞎猜;如果一张都没命中则提示未找到。这是纯只读操作,只做列举、本地匹配与投影,不会创建、修改或删除任何数据表。"
|
||||
"已有真实 baseId、只知道数据表名称或关键词,需要解析成唯一 tableId 时"
|
||||
]
|
||||
},
|
||||
"aitable +role-list": {
|
||||
@@ -5949,7 +5949,7 @@
|
||||
"agent_summary_source": "dws-agent-selection/aitable",
|
||||
"availability": "available",
|
||||
"avoid_when": [
|
||||
"需要该 Shortcut 未公开的底层参数、原始响应或不同执行语义时,改用对应原子命令"
|
||||
"只知道表名并要解析唯一 tableId 用 +resolve-table;需要字段完整 config 用 +field-get;需要未公开底层语义才用 table get"
|
||||
],
|
||||
"confirmation": "not_required",
|
||||
"effect": "read",
|
||||
@@ -5993,7 +5993,7 @@
|
||||
},
|
||||
"avoid_when": {
|
||||
"value": [
|
||||
"需要该 Shortcut 未公开的底层参数、原始响应或不同执行语义时,改用对应原子命令"
|
||||
"只知道表名并要解析唯一 tableId 用 +resolve-table;需要字段完整 config 用 +field-get;需要未公开底层语义才用 table get"
|
||||
],
|
||||
"source": "internal/cli/schema_hints/selection/aitable.json",
|
||||
"precedence": "reviewed_explicit",
|
||||
@@ -6002,7 +6002,7 @@
|
||||
"candidates": [
|
||||
{
|
||||
"value": [
|
||||
"需要该 Shortcut 未公开的底层参数、原始响应或不同执行语义时,改用对应原子命令"
|
||||
"只知道表名并要解析唯一 tableId 用 +resolve-table;需要字段完整 config 用 +field-get;需要未公开底层语义才用 table get"
|
||||
],
|
||||
"source": "internal/cli/schema_hints/selection/aitable.json",
|
||||
"precedence": "reviewed_explicit",
|
||||
@@ -11191,11 +11191,11 @@
|
||||
]
|
||||
},
|
||||
"aitable base search": {
|
||||
"agent_summary": "按名称搜索 AI 表格 Base(优先于仅最近访问的 list)。",
|
||||
"agent_summary": "底层按名称搜索 AI 表格 Base,返回原始候选列表。",
|
||||
"agent_summary_source": "dws-agent-selection/aitable",
|
||||
"availability": "available",
|
||||
"avoid_when": [
|
||||
"只要最近访问列表用 base list;已知 baseId 取详情用 base get;电子表格 axls 用 sheet"
|
||||
"普通按名称解析唯一 baseId 优先用 +resolve-base;只要候选列表用 +base-search;最近访问用 +base-list;电子表格 axls 用 sheet"
|
||||
],
|
||||
"confirmation": "not_required",
|
||||
"effect": "read",
|
||||
@@ -11205,14 +11205,14 @@
|
||||
],
|
||||
"field_provenance": {
|
||||
"agent_summary": {
|
||||
"value": "按名称搜索 AI 表格 Base(优先于仅最近访问的 list)。",
|
||||
"value": "底层按名称搜索 AI 表格 Base,返回原始候选列表。",
|
||||
"source": "internal/cli/schema_hints/selection/aitable.json",
|
||||
"precedence": "reviewed_explicit",
|
||||
"resolution": "highest_precedence",
|
||||
"review_reason": "人工结合实时 dws schema MCP 描述(或 helper 无 live 时用 Skill/Cobra)、兄弟命令分流与 Runtime 确认门禁审阅选型语义。",
|
||||
"candidates": [
|
||||
{
|
||||
"value": "按名称搜索 AI 表格 Base(优先于仅最近访问的 list)。",
|
||||
"value": "底层按名称搜索 AI 表格 Base,返回原始候选列表。",
|
||||
"source": "internal/cli/schema_hints/selection/aitable.json",
|
||||
"precedence": "reviewed_explicit",
|
||||
"selected": true,
|
||||
@@ -11244,7 +11244,7 @@
|
||||
},
|
||||
"avoid_when": {
|
||||
"value": [
|
||||
"只要最近访问列表用 base list;已知 baseId 取详情用 base get;电子表格 axls 用 sheet"
|
||||
"普通按名称解析唯一 baseId 优先用 +resolve-base;只要候选列表用 +base-search;最近访问用 +base-list;电子表格 axls 用 sheet"
|
||||
],
|
||||
"source": "internal/cli/schema_hints/selection/aitable.json",
|
||||
"precedence": "reviewed_explicit",
|
||||
@@ -11253,7 +11253,7 @@
|
||||
"candidates": [
|
||||
{
|
||||
"value": [
|
||||
"只要最近访问列表用 base list;已知 baseId 取详情用 base get;电子表格 axls 用 sheet"
|
||||
"普通按名称解析唯一 baseId 优先用 +resolve-base;只要候选列表用 +base-search;最近访问用 +base-list;电子表格 axls 用 sheet"
|
||||
],
|
||||
"source": "internal/cli/schema_hints/selection/aitable.json",
|
||||
"precedence": "reviewed_explicit",
|
||||
@@ -11407,7 +11407,7 @@
|
||||
},
|
||||
"use_when": {
|
||||
"value": [
|
||||
"用户要找某个 AI 表格/多维表,按名称检索时优先使用"
|
||||
"需要 Shortcut 未公开的底层参数、原始 search_bases 响应或自定义候选处理时"
|
||||
],
|
||||
"source": "internal/cli/schema_hints/selection/aitable.json",
|
||||
"precedence": "reviewed_explicit",
|
||||
@@ -11416,7 +11416,7 @@
|
||||
"candidates": [
|
||||
{
|
||||
"value": [
|
||||
"用户要找某个 AI 表格/多维表,按名称检索时优先使用"
|
||||
"需要 Shortcut 未公开的底层参数、原始 search_bases 响应或自定义候选处理时"
|
||||
],
|
||||
"source": "internal/cli/schema_hints/selection/aitable.json",
|
||||
"precedence": "reviewed_explicit",
|
||||
@@ -11441,10 +11441,12 @@
|
||||
"internal/cli/schema_hints/metadata/aitable.json",
|
||||
"internal/cli/schema_hints/selection/aitable.json",
|
||||
"internal/cli/schema_mcp_metadata.json#tools.aitable.base_search",
|
||||
"skills/mono/references/products/aitable.md"
|
||||
"skills/mono/references/products/aitable.md",
|
||||
"skills/multi/dingtalk-aitable/SKILL.md",
|
||||
"skills/multi/dingtalk-aitable/references/aitable.md"
|
||||
],
|
||||
"use_when": [
|
||||
"用户要找某个 AI 表格/多维表,按名称检索时优先使用"
|
||||
"需要 Shortcut 未公开的底层参数、原始 search_bases 响应或自定义候选处理时"
|
||||
]
|
||||
},
|
||||
"aitable base update": {
|
||||
@@ -16699,11 +16701,11 @@
|
||||
]
|
||||
},
|
||||
"aitable field get": {
|
||||
"agent_summary": "获取字段完整配置。",
|
||||
"agent_summary": "底层获取字段完整类型与 config。",
|
||||
"agent_summary_source": "dws-agent-selection/aitable",
|
||||
"availability": "available",
|
||||
"avoid_when": [
|
||||
"表级字段目录也可用 table get;创建字段用 field create;不可用本命令改类型"
|
||||
"正常按字段 ID 展开类型/config 优先用 +field-get;精简字段目录用 +table-get;创建字段用 field create"
|
||||
],
|
||||
"confirmation": "not_required",
|
||||
"effect": "read",
|
||||
@@ -16713,14 +16715,14 @@
|
||||
],
|
||||
"field_provenance": {
|
||||
"agent_summary": {
|
||||
"value": "获取字段完整配置。",
|
||||
"value": "底层获取字段完整类型与 config。",
|
||||
"source": "internal/cli/schema_hints/selection/aitable.json",
|
||||
"precedence": "reviewed_explicit",
|
||||
"resolution": "highest_precedence",
|
||||
"review_reason": "人工结合实时 dws schema MCP 描述(或 helper 无 live 时用 Skill/Cobra)、兄弟命令分流与 Runtime 确认门禁审阅选型语义。",
|
||||
"candidates": [
|
||||
{
|
||||
"value": "获取字段完整配置。",
|
||||
"value": "底层获取字段完整类型与 config。",
|
||||
"source": "internal/cli/schema_hints/selection/aitable.json",
|
||||
"precedence": "reviewed_explicit",
|
||||
"selected": true,
|
||||
@@ -16752,7 +16754,7 @@
|
||||
},
|
||||
"avoid_when": {
|
||||
"value": [
|
||||
"表级字段目录也可用 table get;创建字段用 field create;不可用本命令改类型"
|
||||
"正常按字段 ID 展开类型/config 优先用 +field-get;精简字段目录用 +table-get;创建字段用 field create"
|
||||
],
|
||||
"source": "internal/cli/schema_hints/selection/aitable.json",
|
||||
"precedence": "reviewed_explicit",
|
||||
@@ -16761,7 +16763,7 @@
|
||||
"candidates": [
|
||||
{
|
||||
"value": [
|
||||
"表级字段目录也可用 table get;创建字段用 field create;不可用本命令改类型"
|
||||
"正常按字段 ID 展开类型/config 优先用 +field-get;精简字段目录用 +table-get;创建字段用 field create"
|
||||
],
|
||||
"source": "internal/cli/schema_hints/selection/aitable.json",
|
||||
"precedence": "reviewed_explicit",
|
||||
@@ -16913,7 +16915,7 @@
|
||||
},
|
||||
"use_when": {
|
||||
"value": [
|
||||
"需要字段类型/config(选项、公式等)详情时"
|
||||
"需要 +field-get 未公开的底层参数、原始响应或不同执行语义时"
|
||||
],
|
||||
"source": "internal/cli/schema_hints/selection/aitable.json",
|
||||
"precedence": "reviewed_explicit",
|
||||
@@ -16922,7 +16924,7 @@
|
||||
"candidates": [
|
||||
{
|
||||
"value": [
|
||||
"需要字段类型/config(选项、公式等)详情时"
|
||||
"需要 +field-get 未公开的底层参数、原始响应或不同执行语义时"
|
||||
],
|
||||
"source": "internal/cli/schema_hints/selection/aitable.json",
|
||||
"precedence": "reviewed_explicit",
|
||||
@@ -16950,10 +16952,12 @@
|
||||
"internal/cli/schema_hints/selection/aitable.json",
|
||||
"internal/cli/schema_mcp_metadata.json#tools.aitable.field_get",
|
||||
"skills/mono/references/products/aitable.md",
|
||||
"skills/mono/references/products/aitable/aitable-field.md"
|
||||
"skills/mono/references/products/aitable/aitable-field.md",
|
||||
"skills/multi/dingtalk-aitable/SKILL.md",
|
||||
"skills/multi/dingtalk-aitable/references/aitable/aitable-field.md"
|
||||
],
|
||||
"use_when": [
|
||||
"需要字段类型/config(选项、公式等)详情时"
|
||||
"需要 +field-get 未公开的底层参数、原始响应或不同执行语义时"
|
||||
]
|
||||
},
|
||||
"aitable field list": {
|
||||
@@ -22339,11 +22343,11 @@
|
||||
]
|
||||
},
|
||||
"aitable record create": {
|
||||
"agent_summary": "新增记录(cells 的 key 必须是 fieldId)。",
|
||||
"agent_summary": "新增记录;使用 fieldId 写入并从 newRecordIds 回读验证。",
|
||||
"agent_summary_source": "dws-agent-selection/aitable",
|
||||
"availability": "available",
|
||||
"avoid_when": [
|
||||
"更新已有行用 record update;有则更无则增用 upsert;批量同 patch 用 batch-update"
|
||||
"未核对字段类型时先 field get;更新已有行用 record update;有则更无则增用 upsert;本地 CSV/JSON 批量追加可用已评审脚本"
|
||||
],
|
||||
"confirmation": "not_required",
|
||||
"effect": "write",
|
||||
@@ -22353,14 +22357,14 @@
|
||||
],
|
||||
"field_provenance": {
|
||||
"agent_summary": {
|
||||
"value": "新增记录(cells 的 key 必须是 fieldId)。",
|
||||
"value": "新增记录;使用 fieldId 写入并从 newRecordIds 回读验证。",
|
||||
"source": "internal/cli/schema_hints/selection/aitable.json",
|
||||
"precedence": "reviewed_explicit",
|
||||
"resolution": "highest_precedence",
|
||||
"review_reason": "人工结合实时 dws schema MCP 描述(或 helper 无 live 时用 Skill/Cobra)、兄弟命令分流与 Runtime 确认门禁审阅选型语义。",
|
||||
"candidates": [
|
||||
{
|
||||
"value": "新增记录(cells 的 key 必须是 fieldId)。",
|
||||
"value": "新增记录;使用 fieldId 写入并从 newRecordIds 回读验证。",
|
||||
"source": "internal/cli/schema_hints/selection/aitable.json",
|
||||
"precedence": "reviewed_explicit",
|
||||
"selected": true,
|
||||
@@ -22392,7 +22396,7 @@
|
||||
},
|
||||
"avoid_when": {
|
||||
"value": [
|
||||
"更新已有行用 record update;有则更无则增用 upsert;批量同 patch 用 batch-update"
|
||||
"未核对字段类型时先 field get;更新已有行用 record update;有则更无则增用 upsert;本地 CSV/JSON 批量追加可用已评审脚本"
|
||||
],
|
||||
"source": "internal/cli/schema_hints/selection/aitable.json",
|
||||
"precedence": "reviewed_explicit",
|
||||
@@ -22401,7 +22405,7 @@
|
||||
"candidates": [
|
||||
{
|
||||
"value": [
|
||||
"更新已有行用 record update;有则更无则增用 upsert;批量同 patch 用 batch-update"
|
||||
"未核对字段类型时先 field get;更新已有行用 record update;有则更无则增用 upsert;本地 CSV/JSON 批量追加可用已评审脚本"
|
||||
],
|
||||
"source": "internal/cli/schema_hints/selection/aitable.json",
|
||||
"precedence": "reviewed_explicit",
|
||||
@@ -22553,7 +22557,7 @@
|
||||
},
|
||||
"use_when": {
|
||||
"value": [
|
||||
"需要插入新行数据时"
|
||||
"已取得 baseId/tableId 与字段完整配置,需要插入一条或多条新记录并回读时"
|
||||
],
|
||||
"source": "internal/cli/schema_hints/selection/aitable.json",
|
||||
"precedence": "reviewed_explicit",
|
||||
@@ -22562,7 +22566,7 @@
|
||||
"candidates": [
|
||||
{
|
||||
"value": [
|
||||
"需要插入新行数据时"
|
||||
"已取得 baseId/tableId 与字段完整配置,需要插入一条或多条新记录并回读时"
|
||||
],
|
||||
"source": "internal/cli/schema_hints/selection/aitable.json",
|
||||
"precedence": "reviewed_explicit",
|
||||
@@ -22592,10 +22596,12 @@
|
||||
"skills/mono/references/products/aitable-record-ops.md",
|
||||
"skills/mono/references/products/aitable.md",
|
||||
"skills/mono/references/products/aitable/aitable-attachment.md",
|
||||
"skills/mono/references/products/aitable/aitable-record-create.md"
|
||||
"skills/mono/references/products/aitable/aitable-record-create.md",
|
||||
"skills/multi/dingtalk-aitable/SKILL.md",
|
||||
"skills/multi/dingtalk-aitable/references/aitable/aitable-record-create.md"
|
||||
],
|
||||
"use_when": [
|
||||
"需要插入新行数据时"
|
||||
"已取得 baseId/tableId 与字段完整配置,需要插入一条或多条新记录并回读时"
|
||||
]
|
||||
},
|
||||
"aitable record delete": {
|
||||
@@ -23929,11 +23935,11 @@
|
||||
]
|
||||
},
|
||||
"aitable record query": {
|
||||
"agent_summary": "查询/搜索记录(filters/sort/分页/--all;cells 键为 fieldId)。",
|
||||
"agent_summary": "底层查询/搜索记录,额外支持原子命令的完整分页控制。",
|
||||
"agent_summary_source": "dws-agent-selection/aitable",
|
||||
"availability": "available",
|
||||
"avoid_when": [
|
||||
"已知 recordId 窄查可用 record get;空行用 query-empty;写入用 create/update;电子表格单元格用 sheet"
|
||||
"普通按 ID、关键词、filters、sort 或 cursor 查询优先用 +record-query;空行用 +record-query-empty;写入用 create/update;电子表格单元格用 sheet"
|
||||
],
|
||||
"confirmation": "not_required",
|
||||
"effect": "read",
|
||||
@@ -23943,14 +23949,14 @@
|
||||
],
|
||||
"field_provenance": {
|
||||
"agent_summary": {
|
||||
"value": "查询/搜索记录(filters/sort/分页/--all;cells 键为 fieldId)。",
|
||||
"value": "底层查询/搜索记录,额外支持原子命令的完整分页控制。",
|
||||
"source": "internal/cli/schema_hints/selection/aitable.json",
|
||||
"precedence": "reviewed_explicit",
|
||||
"resolution": "highest_precedence",
|
||||
"review_reason": "人工结合实时 dws schema MCP 描述(或 helper 无 live 时用 Skill/Cobra)、兄弟命令分流与 Runtime 确认门禁审阅选型语义。",
|
||||
"candidates": [
|
||||
{
|
||||
"value": "查询/搜索记录(filters/sort/分页/--all;cells 键为 fieldId)。",
|
||||
"value": "底层查询/搜索记录,额外支持原子命令的完整分页控制。",
|
||||
"source": "internal/cli/schema_hints/selection/aitable.json",
|
||||
"precedence": "reviewed_explicit",
|
||||
"selected": true,
|
||||
@@ -23982,7 +23988,7 @@
|
||||
},
|
||||
"avoid_when": {
|
||||
"value": [
|
||||
"已知 recordId 窄查可用 record get;空行用 query-empty;写入用 create/update;电子表格单元格用 sheet"
|
||||
"普通按 ID、关键词、filters、sort 或 cursor 查询优先用 +record-query;空行用 +record-query-empty;写入用 create/update;电子表格单元格用 sheet"
|
||||
],
|
||||
"source": "internal/cli/schema_hints/selection/aitable.json",
|
||||
"precedence": "reviewed_explicit",
|
||||
@@ -23991,7 +23997,7 @@
|
||||
"candidates": [
|
||||
{
|
||||
"value": [
|
||||
"已知 recordId 窄查可用 record get;空行用 query-empty;写入用 create/update;电子表格单元格用 sheet"
|
||||
"普通按 ID、关键词、filters、sort 或 cursor 查询优先用 +record-query;空行用 +record-query-empty;写入用 create/update;电子表格单元格用 sheet"
|
||||
],
|
||||
"source": "internal/cli/schema_hints/selection/aitable.json",
|
||||
"precedence": "reviewed_explicit",
|
||||
@@ -24151,7 +24157,7 @@
|
||||
},
|
||||
"use_when": {
|
||||
"value": [
|
||||
"查看、筛选、全文搜索或遍历记录时的主入口"
|
||||
"需要 +record-query 未公开的 --all/--page-limit、底层原始响应或不同执行语义时"
|
||||
],
|
||||
"source": "internal/cli/schema_hints/selection/aitable.json",
|
||||
"precedence": "reviewed_explicit",
|
||||
@@ -24160,7 +24166,7 @@
|
||||
"candidates": [
|
||||
{
|
||||
"value": [
|
||||
"查看、筛选、全文搜索或遍历记录时的主入口"
|
||||
"需要 +record-query 未公开的 --all/--page-limit、底层原始响应或不同执行语义时"
|
||||
],
|
||||
"source": "internal/cli/schema_hints/selection/aitable.json",
|
||||
"precedence": "reviewed_explicit",
|
||||
@@ -24189,10 +24195,12 @@
|
||||
"internal/cli/schema_mcp_metadata.json#tools.aitable.query_records",
|
||||
"skills/mono/references/products/aitable-record-ops.md",
|
||||
"skills/mono/references/products/aitable.md",
|
||||
"skills/mono/references/products/aitable/aitable-record-query.md"
|
||||
"skills/mono/references/products/aitable/aitable-record-query.md",
|
||||
"skills/multi/dingtalk-aitable/SKILL.md",
|
||||
"skills/multi/dingtalk-aitable/references/aitable/aitable-record-query.md"
|
||||
],
|
||||
"use_when": [
|
||||
"查看、筛选、全文搜索或遍历记录时的主入口"
|
||||
"需要 +record-query 未公开的 --all/--page-limit、底层原始响应或不同执行语义时"
|
||||
]
|
||||
},
|
||||
"aitable record query-empty": {
|
||||
@@ -24711,11 +24719,11 @@
|
||||
]
|
||||
},
|
||||
"aitable record update": {
|
||||
"agent_summary": "更新已有记录字段(先 query 拿 recordId;只传需改字段)。",
|
||||
"agent_summary": "更新已有记录字段;先 query 拿 recordId,使用 fieldId 写入并回读。",
|
||||
"agent_summary_source": "dws-agent-selection/aitable",
|
||||
"availability": "available",
|
||||
"avoid_when": [
|
||||
"新建用 create;多条共用同一 cells patch 用 batch-update;混合创建/更新用 upsert"
|
||||
"未定位 recordId 或字段配置时先 query/field get;新建用 create;多条共用同一 cells patch 用 batch-update;混合创建/更新用 upsert"
|
||||
],
|
||||
"confirmation": "not_required",
|
||||
"effect": "write",
|
||||
@@ -24725,14 +24733,14 @@
|
||||
],
|
||||
"field_provenance": {
|
||||
"agent_summary": {
|
||||
"value": "更新已有记录字段(先 query 拿 recordId;只传需改字段)。",
|
||||
"value": "更新已有记录字段;先 query 拿 recordId,使用 fieldId 写入并回读。",
|
||||
"source": "internal/cli/schema_hints/selection/aitable.json",
|
||||
"precedence": "reviewed_explicit",
|
||||
"resolution": "highest_precedence",
|
||||
"review_reason": "人工结合实时 dws schema MCP 描述(或 helper 无 live 时用 Skill/Cobra)、兄弟命令分流与 Runtime 确认门禁审阅选型语义。",
|
||||
"candidates": [
|
||||
{
|
||||
"value": "更新已有记录字段(先 query 拿 recordId;只传需改字段)。",
|
||||
"value": "更新已有记录字段;先 query 拿 recordId,使用 fieldId 写入并回读。",
|
||||
"source": "internal/cli/schema_hints/selection/aitable.json",
|
||||
"precedence": "reviewed_explicit",
|
||||
"selected": true,
|
||||
@@ -24764,7 +24772,7 @@
|
||||
},
|
||||
"avoid_when": {
|
||||
"value": [
|
||||
"新建用 create;多条共用同一 cells patch 用 batch-update;混合创建/更新用 upsert"
|
||||
"未定位 recordId 或字段配置时先 query/field get;新建用 create;多条共用同一 cells patch 用 batch-update;混合创建/更新用 upsert"
|
||||
],
|
||||
"source": "internal/cli/schema_hints/selection/aitable.json",
|
||||
"precedence": "reviewed_explicit",
|
||||
@@ -24773,7 +24781,7 @@
|
||||
"candidates": [
|
||||
{
|
||||
"value": [
|
||||
"新建用 create;多条共用同一 cells patch 用 batch-update;混合创建/更新用 upsert"
|
||||
"未定位 recordId 或字段配置时先 query/field get;新建用 create;多条共用同一 cells patch 用 batch-update;混合创建/更新用 upsert"
|
||||
],
|
||||
"source": "internal/cli/schema_hints/selection/aitable.json",
|
||||
"precedence": "reviewed_explicit",
|
||||
@@ -24925,7 +24933,7 @@
|
||||
},
|
||||
"use_when": {
|
||||
"value": [
|
||||
"需要修改已有记录若干字段时"
|
||||
"已查询得到真实 recordId 并核对字段类型,需要修改每条记录各自的 cells 时"
|
||||
],
|
||||
"source": "internal/cli/schema_hints/selection/aitable.json",
|
||||
"precedence": "reviewed_explicit",
|
||||
@@ -24934,7 +24942,7 @@
|
||||
"candidates": [
|
||||
{
|
||||
"value": [
|
||||
"需要修改已有记录若干字段时"
|
||||
"已查询得到真实 recordId 并核对字段类型,需要修改每条记录各自的 cells 时"
|
||||
],
|
||||
"source": "internal/cli/schema_hints/selection/aitable.json",
|
||||
"precedence": "reviewed_explicit",
|
||||
@@ -24963,10 +24971,12 @@
|
||||
"internal/cli/schema_mcp_metadata.json#tools.aitable.record_update",
|
||||
"skills/mono/references/products/aitable-record-ops.md",
|
||||
"skills/mono/references/products/aitable.md",
|
||||
"skills/mono/references/products/aitable/aitable-attachment.md"
|
||||
"skills/mono/references/products/aitable/aitable-attachment.md",
|
||||
"skills/multi/dingtalk-aitable/SKILL.md",
|
||||
"skills/multi/dingtalk-aitable/references/aitable/aitable-record-update.md"
|
||||
],
|
||||
"use_when": [
|
||||
"需要修改已有记录若干字段时"
|
||||
"已查询得到真实 recordId 并核对字段类型,需要修改每条记录各自的 cells 时"
|
||||
]
|
||||
},
|
||||
"aitable record upsert": {
|
||||
@@ -27862,11 +27872,11 @@
|
||||
]
|
||||
},
|
||||
"aitable table get": {
|
||||
"agent_summary": "获取数据表结构(字段+视图目录)。",
|
||||
"agent_summary": "底层获取数据表结构、精简字段目录与视图目录。",
|
||||
"agent_summary_source": "dws-agent-selection/aitable",
|
||||
"availability": "available",
|
||||
"avoid_when": [
|
||||
"只关心 Base 级 tables 列表可先 base get;字段完整配置也可用 field get"
|
||||
"正常获取表、精简字段和视图目录优先用 +table-get;按表名解析 ID 用 +resolve-table;完整字段 config 用 +field-get"
|
||||
],
|
||||
"confirmation": "not_required",
|
||||
"effect": "read",
|
||||
@@ -27876,14 +27886,14 @@
|
||||
],
|
||||
"field_provenance": {
|
||||
"agent_summary": {
|
||||
"value": "获取数据表结构(字段+视图目录)。",
|
||||
"value": "底层获取数据表结构、精简字段目录与视图目录。",
|
||||
"source": "internal/cli/schema_hints/selection/aitable.json",
|
||||
"precedence": "reviewed_explicit",
|
||||
"resolution": "highest_precedence",
|
||||
"review_reason": "人工结合实时 dws schema MCP 描述(或 helper 无 live 时用 Skill/Cobra)、兄弟命令分流与 Runtime 确认门禁审阅选型语义。",
|
||||
"candidates": [
|
||||
{
|
||||
"value": "获取数据表结构(字段+视图目录)。",
|
||||
"value": "底层获取数据表结构、精简字段目录与视图目录。",
|
||||
"source": "internal/cli/schema_hints/selection/aitable.json",
|
||||
"precedence": "reviewed_explicit",
|
||||
"selected": true,
|
||||
@@ -27915,7 +27925,7 @@
|
||||
},
|
||||
"avoid_when": {
|
||||
"value": [
|
||||
"只关心 Base 级 tables 列表可先 base get;字段完整配置也可用 field get"
|
||||
"正常获取表、精简字段和视图目录优先用 +table-get;按表名解析 ID 用 +resolve-table;完整字段 config 用 +field-get"
|
||||
],
|
||||
"source": "internal/cli/schema_hints/selection/aitable.json",
|
||||
"precedence": "reviewed_explicit",
|
||||
@@ -27924,7 +27934,7 @@
|
||||
"candidates": [
|
||||
{
|
||||
"value": [
|
||||
"只关心 Base 级 tables 列表可先 base get;字段完整配置也可用 field get"
|
||||
"正常获取表、精简字段和视图目录优先用 +table-get;按表名解析 ID 用 +resolve-table;完整字段 config 用 +field-get"
|
||||
],
|
||||
"source": "internal/cli/schema_hints/selection/aitable.json",
|
||||
"precedence": "reviewed_explicit",
|
||||
@@ -28076,7 +28086,7 @@
|
||||
},
|
||||
"use_when": {
|
||||
"value": [
|
||||
"需要字段 fieldId 或视图目录以继续 record/field 操作时优先 table get"
|
||||
"需要 +table-get 未公开的底层参数、原始 get_tables 响应或不同执行语义时"
|
||||
],
|
||||
"source": "internal/cli/schema_hints/selection/aitable.json",
|
||||
"precedence": "reviewed_explicit",
|
||||
@@ -28085,7 +28095,7 @@
|
||||
"candidates": [
|
||||
{
|
||||
"value": [
|
||||
"需要字段 fieldId 或视图目录以继续 record/field 操作时优先 table get"
|
||||
"需要 +table-get 未公开的底层参数、原始 get_tables 响应或不同执行语义时"
|
||||
],
|
||||
"source": "internal/cli/schema_hints/selection/aitable.json",
|
||||
"precedence": "reviewed_explicit",
|
||||
@@ -28115,10 +28125,12 @@
|
||||
"skills/mono/references/products/aitable.md",
|
||||
"skills/mono/references/products/aitable/aitable-advperm.md",
|
||||
"skills/mono/references/products/aitable/aitable-primary-doc.md",
|
||||
"skills/mono/references/products/aitable/aitable-record-create.md"
|
||||
"skills/mono/references/products/aitable/aitable-record-create.md",
|
||||
"skills/multi/dingtalk-aitable/SKILL.md",
|
||||
"skills/multi/dingtalk-aitable/references/aitable.md"
|
||||
],
|
||||
"use_when": [
|
||||
"需要字段 fieldId 或视图目录以继续 record/field 操作时优先 table get"
|
||||
"需要 +table-get 未公开的底层参数、原始 get_tables 响应或不同执行语义时"
|
||||
]
|
||||
},
|
||||
"aitable table list": {
|
||||
|
||||
@@ -9301,11 +9301,11 @@
|
||||
]
|
||||
},
|
||||
"chat +messages-query-send-status": {
|
||||
"agent_summary": "查询消息发送状态",
|
||||
"agent_summary": "查询消息发送任务的业务终态,只有 sendStatus=SUCCESS 才表示真正发送成功",
|
||||
"agent_summary_source": "dws-agent-selection/chat",
|
||||
"availability": "available",
|
||||
"avoid_when": [
|
||||
"需要该 Shortcut 未公开的底层参数、原始响应或不同执行语义时,改用对应原子命令"
|
||||
"需要该 Shortcut 未公开的底层参数、原始响应或不同执行语义时,改用对应原子命令;不要把 openTaskId 当 openMessageId 做 Pin、置顶或表情操作"
|
||||
],
|
||||
"confirmation": "not_required",
|
||||
"effect": "read",
|
||||
@@ -9315,14 +9315,14 @@
|
||||
],
|
||||
"field_provenance": {
|
||||
"agent_summary": {
|
||||
"value": "查询消息发送状态",
|
||||
"value": "查询消息发送任务的业务终态,只有 sendStatus=SUCCESS 才表示真正发送成功",
|
||||
"source": "internal/cli/schema_hints/selection/chat.json",
|
||||
"precedence": "reviewed_explicit",
|
||||
"resolution": "highest_precedence",
|
||||
"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.",
|
||||
"candidates": [
|
||||
{
|
||||
"value": "查询消息发送状态",
|
||||
"value": "查询消息发送任务的业务终态,只有 sendStatus=SUCCESS 才表示真正发送成功",
|
||||
"source": "internal/cli/schema_hints/selection/chat.json",
|
||||
"precedence": "reviewed_explicit",
|
||||
"selected": true,
|
||||
@@ -9348,7 +9348,7 @@
|
||||
},
|
||||
"avoid_when": {
|
||||
"value": [
|
||||
"需要该 Shortcut 未公开的底层参数、原始响应或不同执行语义时,改用对应原子命令"
|
||||
"需要该 Shortcut 未公开的底层参数、原始响应或不同执行语义时,改用对应原子命令;不要把 openTaskId 当 openMessageId 做 Pin、置顶或表情操作"
|
||||
],
|
||||
"source": "internal/cli/schema_hints/selection/chat.json",
|
||||
"precedence": "reviewed_explicit",
|
||||
@@ -9357,7 +9357,7 @@
|
||||
"candidates": [
|
||||
{
|
||||
"value": [
|
||||
"需要该 Shortcut 未公开的底层参数、原始响应或不同执行语义时,改用对应原子命令"
|
||||
"需要该 Shortcut 未公开的底层参数、原始响应或不同执行语义时,改用对应原子命令;不要把 openTaskId 当 openMessageId 做 Pin、置顶或表情操作"
|
||||
],
|
||||
"source": "internal/cli/schema_hints/selection/chat.json",
|
||||
"precedence": "reviewed_explicit",
|
||||
@@ -9523,7 +9523,7 @@
|
||||
},
|
||||
"use_when": {
|
||||
"value": [
|
||||
"当你发消息后拿到 openTaskId、想确认这条消息是否发送成功时使用;只读返回发送状态,需传 --open-task-id。"
|
||||
"当你发消息后拿到 openTaskId、想确认这条消息是否发送成功时使用;只读返回发送状态,需传 --open-task-id;FAILED 必须如实报告,不能因外层 success=true 宣称成功。"
|
||||
],
|
||||
"source": "internal/cli/schema_hints/selection/chat.json",
|
||||
"precedence": "reviewed_explicit",
|
||||
@@ -9532,7 +9532,7 @@
|
||||
"candidates": [
|
||||
{
|
||||
"value": [
|
||||
"当你发消息后拿到 openTaskId、想确认这条消息是否发送成功时使用;只读返回发送状态,需传 --open-task-id。"
|
||||
"当你发消息后拿到 openTaskId、想确认这条消息是否发送成功时使用;只读返回发送状态,需传 --open-task-id;FAILED 必须如实报告,不能因外层 success=true 宣称成功。"
|
||||
],
|
||||
"source": "internal/cli/schema_hints/selection/chat.json",
|
||||
"precedence": "reviewed_explicit",
|
||||
@@ -9555,7 +9555,7 @@
|
||||
"internal/cli/schema_hints/selection/chat.json"
|
||||
],
|
||||
"use_when": [
|
||||
"当你发消息后拿到 openTaskId、想确认这条消息是否发送成功时使用;只读返回发送状态,需传 --open-task-id。"
|
||||
"当你发消息后拿到 openTaskId、想确认这条消息是否发送成功时使用;只读返回发送状态,需传 --open-task-id;FAILED 必须如实报告,不能因外层 success=true 宣称成功。"
|
||||
]
|
||||
},
|
||||
"chat +messages-read-status": {
|
||||
@@ -20567,7 +20567,7 @@
|
||||
"agent_summary_source": "dws-agent-selection/chat",
|
||||
"availability": "available",
|
||||
"avoid_when": [
|
||||
"移除机器人时使用 chat group members remove-bot"
|
||||
"移除机器人时使用 chat group members remove-bot;不要把建群者或群主加入 --users,也不要为了清理成员而转让群主"
|
||||
],
|
||||
"confirmation": "not_required",
|
||||
"effect": "write",
|
||||
@@ -20616,7 +20616,7 @@
|
||||
},
|
||||
"avoid_when": {
|
||||
"value": [
|
||||
"移除机器人时使用 chat group members remove-bot"
|
||||
"移除机器人时使用 chat group members remove-bot;不要把建群者或群主加入 --users,也不要为了清理成员而转让群主"
|
||||
],
|
||||
"source": "internal/cli/schema_hints/selection/chat.json",
|
||||
"precedence": "reviewed_explicit",
|
||||
@@ -20625,7 +20625,7 @@
|
||||
"candidates": [
|
||||
{
|
||||
"value": [
|
||||
"移除机器人时使用 chat group members remove-bot"
|
||||
"移除机器人时使用 chat group members remove-bot;不要把建群者或群主加入 --users,也不要为了清理成员而转让群主"
|
||||
],
|
||||
"source": "internal/cli/schema_hints/selection/chat.json",
|
||||
"precedence": "reviewed_explicit",
|
||||
@@ -20804,7 +20804,7 @@
|
||||
},
|
||||
"use_when": {
|
||||
"value": [
|
||||
"群管理员明确要移除一个或多个普通成员时"
|
||||
"群管理员明确要移除一个或多个普通成员时;清理临时群只传本次加入的普通成员"
|
||||
],
|
||||
"source": "internal/cli/schema_hints/selection/chat.json",
|
||||
"precedence": "reviewed_explicit",
|
||||
@@ -20813,7 +20813,7 @@
|
||||
"candidates": [
|
||||
{
|
||||
"value": [
|
||||
"群管理员明确要移除一个或多个普通成员时"
|
||||
"群管理员明确要移除一个或多个普通成员时;清理临时群只传本次加入的普通成员"
|
||||
],
|
||||
"source": "internal/cli/schema_hints/selection/chat.json",
|
||||
"precedence": "reviewed_explicit",
|
||||
@@ -20844,7 +20844,7 @@
|
||||
"skills/mono/references/products/chat.md"
|
||||
],
|
||||
"use_when": [
|
||||
"群管理员明确要移除一个或多个普通成员时"
|
||||
"群管理员明确要移除一个或多个普通成员时;清理临时群只传本次加入的普通成员"
|
||||
]
|
||||
},
|
||||
"chat group members remove-bot": {
|
||||
@@ -24064,7 +24064,7 @@
|
||||
"agent_summary_source": "dws-agent-selection/chat",
|
||||
"availability": "available",
|
||||
"avoid_when": [
|
||||
"没有真实可用 mediaId 时先完成媒体上传"
|
||||
"本地图片路径、钉盘 dentryId、聊天文件 uploadKey 都不能代替 icon-media-id;当前 CLI 无本地图片转群头像 mediaId 的能力,缺少 mediaId 时应如实报告能力边界"
|
||||
],
|
||||
"confirmation": "not_required",
|
||||
"effect": "write",
|
||||
@@ -24113,7 +24113,7 @@
|
||||
},
|
||||
"avoid_when": {
|
||||
"value": [
|
||||
"没有真实可用 mediaId 时先完成媒体上传"
|
||||
"本地图片路径、钉盘 dentryId、聊天文件 uploadKey 都不能代替 icon-media-id;当前 CLI 无本地图片转群头像 mediaId 的能力,缺少 mediaId 时应如实报告能力边界"
|
||||
],
|
||||
"source": "internal/cli/schema_hints/selection/chat.json",
|
||||
"precedence": "reviewed_explicit",
|
||||
@@ -24122,7 +24122,7 @@
|
||||
"candidates": [
|
||||
{
|
||||
"value": [
|
||||
"没有真实可用 mediaId 时先完成媒体上传"
|
||||
"本地图片路径、钉盘 dentryId、聊天文件 uploadKey 都不能代替 icon-media-id;当前 CLI 无本地图片转群头像 mediaId 的能力,缺少 mediaId 时应如实报告能力边界"
|
||||
],
|
||||
"source": "internal/cli/schema_hints/selection/chat.json",
|
||||
"precedence": "reviewed_explicit",
|
||||
@@ -24268,7 +24268,7 @@
|
||||
},
|
||||
"use_when": {
|
||||
"value": [
|
||||
"已有上传后的头像 mediaId 并要修改群头像时"
|
||||
"已有可用于群头像接口的真实 mediaId 并要修改群头像时"
|
||||
],
|
||||
"source": "internal/cli/schema_hints/selection/chat.json",
|
||||
"precedence": "reviewed_explicit",
|
||||
@@ -24277,7 +24277,7 @@
|
||||
"candidates": [
|
||||
{
|
||||
"value": [
|
||||
"已有上传后的头像 mediaId 并要修改群头像时"
|
||||
"已有可用于群头像接口的真实 mediaId 并要修改群头像时"
|
||||
],
|
||||
"source": "internal/cli/schema_hints/selection/chat.json",
|
||||
"precedence": "reviewed_explicit",
|
||||
@@ -24306,7 +24306,7 @@
|
||||
"skills/mono/references/products/chat.md"
|
||||
],
|
||||
"use_when": [
|
||||
"已有上传后的头像 mediaId 并要修改群头像时"
|
||||
"已有可用于群头像接口的真实 mediaId 并要修改群头像时"
|
||||
]
|
||||
},
|
||||
"chat group update-nick": {
|
||||
@@ -24602,7 +24602,7 @@
|
||||
"agent_summary_source": "dws-agent-selection/chat",
|
||||
"availability": "available",
|
||||
"avoid_when": [
|
||||
"全员禁言和成员禁言使用专门的 mute 命令"
|
||||
"全员禁言和成员禁言使用专门的 mute 命令;当前用户自己的会话置顶或免打扰使用 group user-settings set"
|
||||
],
|
||||
"confirmation": "not_required",
|
||||
"effect": "write",
|
||||
@@ -24651,7 +24651,7 @@
|
||||
},
|
||||
"avoid_when": {
|
||||
"value": [
|
||||
"全员禁言和成员禁言使用专门的 mute 命令"
|
||||
"全员禁言和成员禁言使用专门的 mute 命令;当前用户自己的会话置顶或免打扰使用 group user-settings set"
|
||||
],
|
||||
"source": "internal/cli/schema_hints/selection/chat.json",
|
||||
"precedence": "reviewed_explicit",
|
||||
@@ -24660,7 +24660,7 @@
|
||||
"candidates": [
|
||||
{
|
||||
"value": [
|
||||
"全员禁言和成员禁言使用专门的 mute 命令"
|
||||
"全员禁言和成员禁言使用专门的 mute 命令;当前用户自己的会话置顶或免打扰使用 group user-settings set"
|
||||
],
|
||||
"source": "internal/cli/schema_hints/selection/chat.json",
|
||||
"precedence": "reviewed_explicit",
|
||||
@@ -24806,7 +24806,7 @@
|
||||
},
|
||||
"use_when": {
|
||||
"value": [
|
||||
"需要调整 searchable、入群验证或群权限等设置时"
|
||||
"需要调整 searchable、入群验证、@所有人权限等群级设置时;修改后用群信息或设置查询核对终态"
|
||||
],
|
||||
"source": "internal/cli/schema_hints/selection/chat.json",
|
||||
"precedence": "reviewed_explicit",
|
||||
@@ -24815,7 +24815,7 @@
|
||||
"candidates": [
|
||||
{
|
||||
"value": [
|
||||
"需要调整 searchable、入群验证或群权限等设置时"
|
||||
"需要调整 searchable、入群验证、@所有人权限等群级设置时;修改后用群信息或设置查询核对终态"
|
||||
],
|
||||
"source": "internal/cli/schema_hints/selection/chat.json",
|
||||
"precedence": "reviewed_explicit",
|
||||
@@ -24844,7 +24844,7 @@
|
||||
"skills/mono/references/products/chat.md"
|
||||
],
|
||||
"use_when": [
|
||||
"需要调整 searchable、入群验证或群权限等设置时"
|
||||
"需要调整 searchable、入群验证、@所有人权限等群级设置时;修改后用群信息或设置查询核对终态"
|
||||
]
|
||||
},
|
||||
"chat group upgrade-to-external": {
|
||||
@@ -25112,7 +25112,7 @@
|
||||
"agent_summary_source": "dws-agent-selection/chat",
|
||||
"availability": "available",
|
||||
"avoid_when": [
|
||||
"管理员级群功能开关用 chat group update-settings"
|
||||
"管理员级群功能开关用 chat group update-settings;查询结果是当前用户视角,不代表群级设置"
|
||||
],
|
||||
"confirmation": "not_required",
|
||||
"effect": "read",
|
||||
@@ -25155,7 +25155,7 @@
|
||||
},
|
||||
"avoid_when": {
|
||||
"value": [
|
||||
"管理员级群功能开关用 chat group update-settings"
|
||||
"管理员级群功能开关用 chat group update-settings;查询结果是当前用户视角,不代表群级设置"
|
||||
],
|
||||
"source": "internal/cli/schema_hints/selection/chat.json",
|
||||
"precedence": "reviewed_explicit",
|
||||
@@ -25164,7 +25164,7 @@
|
||||
"candidates": [
|
||||
{
|
||||
"value": [
|
||||
"管理员级群功能开关用 chat group update-settings"
|
||||
"管理员级群功能开关用 chat group update-settings;查询结果是当前用户视角,不代表群级设置"
|
||||
],
|
||||
"source": "internal/cli/schema_hints/selection/chat.json",
|
||||
"precedence": "reviewed_explicit",
|
||||
@@ -25330,7 +25330,7 @@
|
||||
},
|
||||
"use_when": {
|
||||
"value": [
|
||||
"用户说 看下这些群我的置顶和免打扰设置"
|
||||
"用户说看下这些群中我自己的置顶和免打扰设置;修改前先查询并保存原值用于恢复"
|
||||
],
|
||||
"source": "internal/cli/schema_hints/selection/chat.json",
|
||||
"precedence": "reviewed_explicit",
|
||||
@@ -25339,7 +25339,7 @@
|
||||
"candidates": [
|
||||
{
|
||||
"value": [
|
||||
"用户说 看下这些群我的置顶和免打扰设置"
|
||||
"用户说看下这些群中我自己的置顶和免打扰设置;修改前先查询并保存原值用于恢复"
|
||||
],
|
||||
"source": "internal/cli/schema_hints/selection/chat.json",
|
||||
"precedence": "reviewed_explicit",
|
||||
@@ -25362,7 +25362,7 @@
|
||||
"wukong-develop:wukong/products"
|
||||
],
|
||||
"use_when": [
|
||||
"用户说 看下这些群我的置顶和免打扰设置"
|
||||
"用户说看下这些群中我自己的置顶和免打扰设置;修改前先查询并保存原值用于恢复"
|
||||
]
|
||||
},
|
||||
"chat group user-settings set": {
|
||||
@@ -25370,7 +25370,7 @@
|
||||
"agent_summary_source": "dws-agent-selection/chat",
|
||||
"availability": "available",
|
||||
"avoid_when": [
|
||||
"单个群昵称优先 chat group update-nick"
|
||||
"单个群昵称优先 chat group update-nick;不要用本命令修改 searchable、@所有人权限等群级开关;临时测试结束时按修改前 query 的真实值恢复"
|
||||
],
|
||||
"confirmation": "not_required",
|
||||
"effect": "write",
|
||||
@@ -25413,7 +25413,7 @@
|
||||
},
|
||||
"avoid_when": {
|
||||
"value": [
|
||||
"单个群昵称优先 chat group update-nick"
|
||||
"单个群昵称优先 chat group update-nick;不要用本命令修改 searchable、@所有人权限等群级开关;临时测试结束时按修改前 query 的真实值恢复"
|
||||
],
|
||||
"source": "internal/cli/schema_hints/selection/chat.json",
|
||||
"precedence": "reviewed_explicit",
|
||||
@@ -25422,7 +25422,7 @@
|
||||
"candidates": [
|
||||
{
|
||||
"value": [
|
||||
"单个群昵称优先 chat group update-nick"
|
||||
"单个群昵称优先 chat group update-nick;不要用本命令修改 searchable、@所有人权限等群级开关;临时测试结束时按修改前 query 的真实值恢复"
|
||||
],
|
||||
"source": "internal/cli/schema_hints/selection/chat.json",
|
||||
"precedence": "reviewed_explicit",
|
||||
@@ -25588,7 +25588,7 @@
|
||||
},
|
||||
"use_when": {
|
||||
"value": [
|
||||
"用户说 把这些群都设为免打扰/置顶"
|
||||
"用户说把这些群都设为免打扰或置顶;items 中只写需要改变的当前用户设置,完成后再次 query 验证"
|
||||
],
|
||||
"source": "internal/cli/schema_hints/selection/chat.json",
|
||||
"precedence": "reviewed_explicit",
|
||||
@@ -25597,7 +25597,7 @@
|
||||
"candidates": [
|
||||
{
|
||||
"value": [
|
||||
"用户说 把这些群都设为免打扰/置顶"
|
||||
"用户说把这些群都设为免打扰或置顶;items 中只写需要改变的当前用户设置,完成后再次 query 验证"
|
||||
],
|
||||
"source": "internal/cli/schema_hints/selection/chat.json",
|
||||
"precedence": "reviewed_explicit",
|
||||
@@ -25620,7 +25620,7 @@
|
||||
"wukong-develop:wukong/products"
|
||||
],
|
||||
"use_when": [
|
||||
"用户说 把这些群都设为免打扰/置顶"
|
||||
"用户说把这些群都设为免打扰或置顶;items 中只写需要改变的当前用户设置,完成后再次 query 验证"
|
||||
]
|
||||
},
|
||||
"chat group-mute": {
|
||||
@@ -29273,11 +29273,11 @@
|
||||
]
|
||||
},
|
||||
"chat message add-emoji": {
|
||||
"agent_summary": "给指定消息添加表情回应",
|
||||
"agent_summary": "使用同一会话中真实的 openMessageId 给指定消息添加表情回应",
|
||||
"agent_summary_source": "dws-agent-selection/chat",
|
||||
"availability": "available",
|
||||
"avoid_when": [
|
||||
"发送文本消息或文字表情时不要使用"
|
||||
"发送文本消息或文字表情时不要使用;不要把 openTaskId 当作 msg-id"
|
||||
],
|
||||
"confirmation": "not_required",
|
||||
"effect": "write",
|
||||
@@ -29287,14 +29287,14 @@
|
||||
],
|
||||
"field_provenance": {
|
||||
"agent_summary": {
|
||||
"value": "给指定消息添加表情回应",
|
||||
"value": "使用同一会话中真实的 openMessageId 给指定消息添加表情回应",
|
||||
"source": "internal/cli/schema_hints/selection/chat.json",
|
||||
"precedence": "reviewed_explicit",
|
||||
"resolution": "highest_precedence",
|
||||
"review_reason": "AI 生成并复核的 Agent 选择语义,依据精确 CommandRegistry identity、真实 Cobra help 与产品参考,不改写执行、安全或接口事实。",
|
||||
"candidates": [
|
||||
{
|
||||
"value": "给指定消息添加表情回应",
|
||||
"value": "使用同一会话中真实的 openMessageId 给指定消息添加表情回应",
|
||||
"source": "internal/cli/schema_hints/selection/chat.json",
|
||||
"precedence": "reviewed_explicit",
|
||||
"selected": true,
|
||||
@@ -29326,7 +29326,7 @@
|
||||
},
|
||||
"avoid_when": {
|
||||
"value": [
|
||||
"发送文本消息或文字表情时不要使用"
|
||||
"发送文本消息或文字表情时不要使用;不要把 openTaskId 当作 msg-id"
|
||||
],
|
||||
"source": "internal/cli/schema_hints/selection/chat.json",
|
||||
"precedence": "reviewed_explicit",
|
||||
@@ -29335,7 +29335,7 @@
|
||||
"candidates": [
|
||||
{
|
||||
"value": [
|
||||
"发送文本消息或文字表情时不要使用"
|
||||
"发送文本消息或文字表情时不要使用;不要把 openTaskId 当作 msg-id"
|
||||
],
|
||||
"source": "internal/cli/schema_hints/selection/chat.json",
|
||||
"precedence": "reviewed_explicit",
|
||||
@@ -29489,7 +29489,7 @@
|
||||
},
|
||||
"use_when": {
|
||||
"value": [
|
||||
"需要对已有消息添加一个 emoji reaction 时"
|
||||
"需要对已有消息添加一个 emoji reaction 时;conversation-id 与 msg-id 必须来自同一条真实消息"
|
||||
],
|
||||
"source": "internal/cli/schema_hints/selection/chat.json",
|
||||
"precedence": "reviewed_explicit",
|
||||
@@ -29498,7 +29498,7 @@
|
||||
"candidates": [
|
||||
{
|
||||
"value": [
|
||||
"需要对已有消息添加一个 emoji reaction 时"
|
||||
"需要对已有消息添加一个 emoji reaction 时;conversation-id 与 msg-id 必须来自同一条真实消息"
|
||||
],
|
||||
"source": "internal/cli/schema_hints/selection/chat.json",
|
||||
"precedence": "reviewed_explicit",
|
||||
@@ -29526,7 +29526,7 @@
|
||||
"skills/mono/references/products/chat.md"
|
||||
],
|
||||
"use_when": [
|
||||
"需要对已有消息添加一个 emoji reaction 时"
|
||||
"需要对已有消息添加一个 emoji reaction 时;conversation-id 与 msg-id 必须来自同一条真实消息"
|
||||
]
|
||||
},
|
||||
"chat message add-favorite": {
|
||||
@@ -34634,11 +34634,11 @@
|
||||
]
|
||||
},
|
||||
"chat message query-send-status": {
|
||||
"agent_summary": "查询异步消息发送任务的状态",
|
||||
"agent_summary": "查询异步消息发送任务的业务终态,并按 sendStatus 判断是否真正投递成功",
|
||||
"agent_summary_source": "dws-agent-selection/chat",
|
||||
"availability": "available",
|
||||
"avoid_when": [
|
||||
"没有 message send 真实返回的 openTaskId 时不要使用;openMessageId 和会话 ID 都不能代替 openTaskId"
|
||||
"没有 message send 真实返回的 openTaskId 时不要使用;openMessageId 和会话 ID 都不能代替 openTaskId;FAILED 不得当作接口成功"
|
||||
],
|
||||
"confirmation": "not_required",
|
||||
"effect": "read",
|
||||
@@ -34648,14 +34648,14 @@
|
||||
],
|
||||
"field_provenance": {
|
||||
"agent_summary": {
|
||||
"value": "查询异步消息发送任务的状态",
|
||||
"value": "查询异步消息发送任务的业务终态,并按 sendStatus 判断是否真正投递成功",
|
||||
"source": "internal/cli/schema_hints/selection/chat.json",
|
||||
"precedence": "reviewed_explicit",
|
||||
"resolution": "highest_precedence",
|
||||
"review_reason": "AI 生成并复核的 Agent 选择语义,依据精确 CommandRegistry identity、真实 Cobra help 与产品参考,不改写执行、安全或接口事实。",
|
||||
"candidates": [
|
||||
{
|
||||
"value": "查询异步消息发送任务的状态",
|
||||
"value": "查询异步消息发送任务的业务终态,并按 sendStatus 判断是否真正投递成功",
|
||||
"source": "internal/cli/schema_hints/selection/chat.json",
|
||||
"precedence": "reviewed_explicit",
|
||||
"selected": true,
|
||||
@@ -34687,7 +34687,7 @@
|
||||
},
|
||||
"avoid_when": {
|
||||
"value": [
|
||||
"没有 message send 真实返回的 openTaskId 时不要使用;openMessageId 和会话 ID 都不能代替 openTaskId"
|
||||
"没有 message send 真实返回的 openTaskId 时不要使用;openMessageId 和会话 ID 都不能代替 openTaskId;FAILED 不得当作接口成功"
|
||||
],
|
||||
"source": "internal/cli/schema_hints/selection/chat.json",
|
||||
"precedence": "reviewed_explicit",
|
||||
@@ -34696,7 +34696,7 @@
|
||||
"candidates": [
|
||||
{
|
||||
"value": [
|
||||
"没有 message send 真实返回的 openTaskId 时不要使用;openMessageId 和会话 ID 都不能代替 openTaskId"
|
||||
"没有 message send 真实返回的 openTaskId 时不要使用;openMessageId 和会话 ID 都不能代替 openTaskId;FAILED 不得当作接口成功"
|
||||
],
|
||||
"source": "internal/cli/schema_hints/selection/chat.json",
|
||||
"precedence": "reviewed_explicit",
|
||||
@@ -34842,7 +34842,7 @@
|
||||
},
|
||||
"use_when": {
|
||||
"value": [
|
||||
"发送命令返回 openTaskId 后需要确认投递结果时"
|
||||
"发送命令返回 openTaskId 后需要确认投递结果时;只有 sendStatus=SUCCESS 才能宣称消息发送成功"
|
||||
],
|
||||
"source": "internal/cli/schema_hints/selection/chat.json",
|
||||
"precedence": "reviewed_explicit",
|
||||
@@ -34851,7 +34851,7 @@
|
||||
"candidates": [
|
||||
{
|
||||
"value": [
|
||||
"发送命令返回 openTaskId 后需要确认投递结果时"
|
||||
"发送命令返回 openTaskId 后需要确认投递结果时;只有 sendStatus=SUCCESS 才能宣称消息发送成功"
|
||||
],
|
||||
"source": "internal/cli/schema_hints/selection/chat.json",
|
||||
"precedence": "reviewed_explicit",
|
||||
@@ -34880,7 +34880,7 @@
|
||||
"skills/mono/references/products/chat.md"
|
||||
],
|
||||
"use_when": [
|
||||
"发送命令返回 openTaskId 后需要确认投递结果时"
|
||||
"发送命令返回 openTaskId 后需要确认投递结果时;只有 sendStatus=SUCCESS 才能宣称消息发送成功"
|
||||
]
|
||||
},
|
||||
"chat message read-status": {
|
||||
@@ -35691,11 +35691,11 @@
|
||||
]
|
||||
},
|
||||
"chat message remove-emoji": {
|
||||
"agent_summary": "移除指定消息上的表情回应",
|
||||
"agent_summary": "从同一条真实消息移除此前添加的同名表情回应",
|
||||
"agent_summary_source": "dws-agent-selection/chat",
|
||||
"availability": "available",
|
||||
"avoid_when": [
|
||||
"移除文字表情时使用 chat message remove-text-emotion"
|
||||
"移除文字表情时使用 chat message remove-text-emotion;不要猜测新的消息 ID 或表情名称"
|
||||
],
|
||||
"confirmation": "not_required",
|
||||
"effect": "write",
|
||||
@@ -35705,14 +35705,14 @@
|
||||
],
|
||||
"field_provenance": {
|
||||
"agent_summary": {
|
||||
"value": "移除指定消息上的表情回应",
|
||||
"value": "从同一条真实消息移除此前添加的同名表情回应",
|
||||
"source": "internal/cli/schema_hints/selection/chat.json",
|
||||
"precedence": "reviewed_explicit",
|
||||
"resolution": "highest_precedence",
|
||||
"review_reason": "AI 生成并复核的 Agent 选择语义,依据精确 CommandRegistry identity、真实 Cobra help 与产品参考,不改写执行、安全或接口事实。",
|
||||
"candidates": [
|
||||
{
|
||||
"value": "移除指定消息上的表情回应",
|
||||
"value": "从同一条真实消息移除此前添加的同名表情回应",
|
||||
"source": "internal/cli/schema_hints/selection/chat.json",
|
||||
"precedence": "reviewed_explicit",
|
||||
"selected": true,
|
||||
@@ -35744,7 +35744,7 @@
|
||||
},
|
||||
"avoid_when": {
|
||||
"value": [
|
||||
"移除文字表情时使用 chat message remove-text-emotion"
|
||||
"移除文字表情时使用 chat message remove-text-emotion;不要猜测新的消息 ID 或表情名称"
|
||||
],
|
||||
"source": "internal/cli/schema_hints/selection/chat.json",
|
||||
"precedence": "reviewed_explicit",
|
||||
@@ -35753,7 +35753,7 @@
|
||||
"candidates": [
|
||||
{
|
||||
"value": [
|
||||
"移除文字表情时使用 chat message remove-text-emotion"
|
||||
"移除文字表情时使用 chat message remove-text-emotion;不要猜测新的消息 ID 或表情名称"
|
||||
],
|
||||
"source": "internal/cli/schema_hints/selection/chat.json",
|
||||
"precedence": "reviewed_explicit",
|
||||
@@ -35907,7 +35907,7 @@
|
||||
},
|
||||
"use_when": {
|
||||
"value": [
|
||||
"需要取消此前添加的 emoji reaction 时"
|
||||
"需要取消此前添加的 emoji reaction 时;复用添加时的 conversation-id、msg-id 和 emoji"
|
||||
],
|
||||
"source": "internal/cli/schema_hints/selection/chat.json",
|
||||
"precedence": "reviewed_explicit",
|
||||
@@ -35916,7 +35916,7 @@
|
||||
"candidates": [
|
||||
{
|
||||
"value": [
|
||||
"需要取消此前添加的 emoji reaction 时"
|
||||
"需要取消此前添加的 emoji reaction 时;复用添加时的 conversation-id、msg-id 和 emoji"
|
||||
],
|
||||
"source": "internal/cli/schema_hints/selection/chat.json",
|
||||
"precedence": "reviewed_explicit",
|
||||
@@ -35944,7 +35944,7 @@
|
||||
"skills/mono/references/products/chat.md"
|
||||
],
|
||||
"use_when": [
|
||||
"需要取消此前添加的 emoji reaction 时"
|
||||
"需要取消此前添加的 emoji reaction 时;复用添加时的 conversation-id、msg-id 和 emoji"
|
||||
]
|
||||
},
|
||||
"chat message remove-favorite": {
|
||||
@@ -38319,11 +38319,11 @@
|
||||
]
|
||||
},
|
||||
"chat message set-pin-msg": {
|
||||
"agent_summary": "把指定消息设为会话置顶消息",
|
||||
"agent_summary": "使用同源的 openConversationId 和 openMessageId 把指定消息设为 Pin",
|
||||
"agent_summary_source": "dws-agent-selection/chat",
|
||||
"availability": "available",
|
||||
"avoid_when": [
|
||||
"取消置顶使用 chat message unset-pin-msg"
|
||||
"取消 Pin 使用 chat message unset-pin-msg;置顶到会话顶部使用 set-top-msg,置顶整个会话使用 chat set-top"
|
||||
],
|
||||
"confirmation": "not_required",
|
||||
"effect": "write",
|
||||
@@ -38333,14 +38333,14 @@
|
||||
],
|
||||
"field_provenance": {
|
||||
"agent_summary": {
|
||||
"value": "把指定消息设为会话置顶消息",
|
||||
"value": "使用同源的 openConversationId 和 openMessageId 把指定消息设为 Pin",
|
||||
"source": "internal/cli/schema_hints/selection/chat.json",
|
||||
"precedence": "reviewed_explicit",
|
||||
"resolution": "highest_precedence",
|
||||
"review_reason": "AI 生成并复核的 Agent 选择语义,依据精确 CommandRegistry identity、真实 Cobra help 与产品参考,不改写执行、安全或接口事实。",
|
||||
"candidates": [
|
||||
{
|
||||
"value": "把指定消息设为会话置顶消息",
|
||||
"value": "使用同源的 openConversationId 和 openMessageId 把指定消息设为 Pin",
|
||||
"source": "internal/cli/schema_hints/selection/chat.json",
|
||||
"precedence": "reviewed_explicit",
|
||||
"selected": true,
|
||||
@@ -38372,7 +38372,7 @@
|
||||
},
|
||||
"avoid_when": {
|
||||
"value": [
|
||||
"取消置顶使用 chat message unset-pin-msg"
|
||||
"取消 Pin 使用 chat message unset-pin-msg;置顶到会话顶部使用 set-top-msg,置顶整个会话使用 chat set-top"
|
||||
],
|
||||
"source": "internal/cli/schema_hints/selection/chat.json",
|
||||
"precedence": "reviewed_explicit",
|
||||
@@ -38381,7 +38381,7 @@
|
||||
"candidates": [
|
||||
{
|
||||
"value": [
|
||||
"取消置顶使用 chat message unset-pin-msg"
|
||||
"取消 Pin 使用 chat message unset-pin-msg;置顶到会话顶部使用 set-top-msg,置顶整个会话使用 chat set-top"
|
||||
],
|
||||
"source": "internal/cli/schema_hints/selection/chat.json",
|
||||
"precedence": "reviewed_explicit",
|
||||
@@ -38527,7 +38527,7 @@
|
||||
},
|
||||
"use_when": {
|
||||
"value": [
|
||||
"需要在会话中置顶一条已知消息时"
|
||||
"需要在会话中钉住一条已知消息时;若发送只返回 openTaskId,先确认发送终态,再从消息查询结果取得真实 openMessageId"
|
||||
],
|
||||
"source": "internal/cli/schema_hints/selection/chat.json",
|
||||
"precedence": "reviewed_explicit",
|
||||
@@ -38536,7 +38536,7 @@
|
||||
"candidates": [
|
||||
{
|
||||
"value": [
|
||||
"需要在会话中置顶一条已知消息时"
|
||||
"需要在会话中钉住一条已知消息时;若发送只返回 openTaskId,先确认发送终态,再从消息查询结果取得真实 openMessageId"
|
||||
],
|
||||
"source": "internal/cli/schema_hints/selection/chat.json",
|
||||
"precedence": "reviewed_explicit",
|
||||
@@ -38565,7 +38565,7 @@
|
||||
"skills/mono/references/products/chat.md"
|
||||
],
|
||||
"use_when": [
|
||||
"需要在会话中置顶一条已知消息时"
|
||||
"需要在会话中钉住一条已知消息时;若发送只返回 openTaskId,先确认发送终态,再从消息查询结果取得真实 openMessageId"
|
||||
]
|
||||
},
|
||||
"chat message set-top-msg": {
|
||||
@@ -38854,11 +38854,11 @@
|
||||
]
|
||||
},
|
||||
"chat message unset-pin-msg": {
|
||||
"agent_summary": "取消指定消息的会话置顶",
|
||||
"agent_summary": "使用设置 Pin 时的同源会话和消息 ID 取消指定消息的 Pin",
|
||||
"agent_summary_source": "dws-agent-selection/chat",
|
||||
"availability": "available",
|
||||
"avoid_when": [
|
||||
"新增置顶使用 chat message set-pin-msg"
|
||||
"新增 Pin 使用 chat message set-pin-msg;不要改用 unset-top-msg 或 chat set-top --off"
|
||||
],
|
||||
"confirmation": "not_required",
|
||||
"effect": "write",
|
||||
@@ -38868,14 +38868,14 @@
|
||||
],
|
||||
"field_provenance": {
|
||||
"agent_summary": {
|
||||
"value": "取消指定消息的会话置顶",
|
||||
"value": "使用设置 Pin 时的同源会话和消息 ID 取消指定消息的 Pin",
|
||||
"source": "internal/cli/schema_hints/selection/chat.json",
|
||||
"precedence": "reviewed_explicit",
|
||||
"resolution": "highest_precedence",
|
||||
"review_reason": "AI 生成并复核的 Agent 选择语义,依据精确 CommandRegistry identity、真实 Cobra help 与产品参考,不改写执行、安全或接口事实。",
|
||||
"candidates": [
|
||||
{
|
||||
"value": "取消指定消息的会话置顶",
|
||||
"value": "使用设置 Pin 时的同源会话和消息 ID 取消指定消息的 Pin",
|
||||
"source": "internal/cli/schema_hints/selection/chat.json",
|
||||
"precedence": "reviewed_explicit",
|
||||
"selected": true,
|
||||
@@ -38907,7 +38907,7 @@
|
||||
},
|
||||
"avoid_when": {
|
||||
"value": [
|
||||
"新增置顶使用 chat message set-pin-msg"
|
||||
"新增 Pin 使用 chat message set-pin-msg;不要改用 unset-top-msg 或 chat set-top --off"
|
||||
],
|
||||
"source": "internal/cli/schema_hints/selection/chat.json",
|
||||
"precedence": "reviewed_explicit",
|
||||
@@ -38916,7 +38916,7 @@
|
||||
"candidates": [
|
||||
{
|
||||
"value": [
|
||||
"新增置顶使用 chat message set-pin-msg"
|
||||
"新增 Pin 使用 chat message set-pin-msg;不要改用 unset-top-msg 或 chat set-top --off"
|
||||
],
|
||||
"source": "internal/cli/schema_hints/selection/chat.json",
|
||||
"precedence": "reviewed_explicit",
|
||||
@@ -39062,7 +39062,7 @@
|
||||
},
|
||||
"use_when": {
|
||||
"value": [
|
||||
"需要移除一条已知置顶消息时"
|
||||
"需要移除一条已知 Pin 消息时;复用 set-pin-msg 成功时的 openConversationId 和 openMessageId"
|
||||
],
|
||||
"source": "internal/cli/schema_hints/selection/chat.json",
|
||||
"precedence": "reviewed_explicit",
|
||||
@@ -39071,7 +39071,7 @@
|
||||
"candidates": [
|
||||
{
|
||||
"value": [
|
||||
"需要移除一条已知置顶消息时"
|
||||
"需要移除一条已知 Pin 消息时;复用 set-pin-msg 成功时的 openConversationId 和 openMessageId"
|
||||
],
|
||||
"source": "internal/cli/schema_hints/selection/chat.json",
|
||||
"precedence": "reviewed_explicit",
|
||||
@@ -39100,7 +39100,7 @@
|
||||
"skills/mono/references/products/chat.md"
|
||||
],
|
||||
"use_when": [
|
||||
"需要移除一条已知置顶消息时"
|
||||
"需要移除一条已知 Pin 消息时;复用 set-pin-msg 成功时的 openConversationId 和 openMessageId"
|
||||
]
|
||||
},
|
||||
"chat message unset-top-msg": {
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -10478,8 +10478,10 @@
|
||||
"agent_summary_source": "dws-agent-selection/drive",
|
||||
"availability": "available",
|
||||
"avoid_when": [
|
||||
"用户要把文件发送到群聊或单聊时使用 chat message send --msg-type file --file-path;drive upload 不会发送聊天消息",
|
||||
"常规场景不要拆成 upload-info + 手动 PUT + commit;仅自定义流式上传才用三步",
|
||||
"要把文件作为文档正文附件插入改用 dws doc media insert",
|
||||
"Word/Excel/Markdown 要求上传后在线编辑、大家直接在线改或转钉钉文档时改用 dws doc import;普通 drive upload 返回 docx/xlsx 文件不能宣称可在线编辑",
|
||||
"用户明确说文档空间且走 doc 兼容入口时可用 dws doc upload,默认仍推荐本命令",
|
||||
"用户没有明确同意替换目标文件时不要使用 --node;新建上传应使用 --folder 或目标根目录"
|
||||
],
|
||||
@@ -10496,14 +10498,14 @@
|
||||
"source": "internal/cli/schema_hints/selection/drive.json",
|
||||
"precedence": "reviewed_explicit",
|
||||
"resolution": "highest_precedence",
|
||||
"review_reason": "人工审阅:结合本机 dws schema 实时 MCP description/parameters(经 interface_ref)、产品 Skill、Cobra Long 与 Runtime 确认门禁,按飞书风格重写选型文案;不改变命令身份、参数契约或接口绑定。",
|
||||
"review_reason": "人工对齐普通文件存储与在线文档导入边界:在线编辑需求硬路由 doc import,drive upload 不承诺转换结果;不改变命令身份、参数契约或接口绑定。",
|
||||
"candidates": [
|
||||
{
|
||||
"value": "上传本地文件到钉盘或文档空间,或按节点 ID 确认覆盖已有文件",
|
||||
"source": "internal/cli/schema_hints/selection/drive.json",
|
||||
"precedence": "reviewed_explicit",
|
||||
"selected": true,
|
||||
"review_reason": "人工审阅:结合本机 dws schema 实时 MCP description/parameters(经 interface_ref)、产品 Skill、Cobra Long 与 Runtime 确认门禁,按飞书风格重写选型文案;不改变命令身份、参数契约或接口绑定。"
|
||||
"review_reason": "人工对齐普通文件存储与在线文档导入边界:在线编辑需求硬路由 doc import,drive upload 不承诺转换结果;不改变命令身份、参数契约或接口绑定。"
|
||||
}
|
||||
]
|
||||
},
|
||||
@@ -10525,27 +10527,31 @@
|
||||
},
|
||||
"avoid_when": {
|
||||
"value": [
|
||||
"用户要把文件发送到群聊或单聊时使用 chat message send --msg-type file --file-path;drive upload 不会发送聊天消息",
|
||||
"常规场景不要拆成 upload-info + 手动 PUT + commit;仅自定义流式上传才用三步",
|
||||
"要把文件作为文档正文附件插入改用 dws doc media insert",
|
||||
"Word/Excel/Markdown 要求上传后在线编辑、大家直接在线改或转钉钉文档时改用 dws doc import;普通 drive upload 返回 docx/xlsx 文件不能宣称可在线编辑",
|
||||
"用户明确说文档空间且走 doc 兼容入口时可用 dws doc upload,默认仍推荐本命令",
|
||||
"用户没有明确同意替换目标文件时不要使用 --node;新建上传应使用 --folder 或目标根目录"
|
||||
],
|
||||
"source": "internal/cli/schema_hints/selection/drive.json",
|
||||
"precedence": "reviewed_explicit",
|
||||
"resolution": "highest_precedence",
|
||||
"review_reason": "人工审阅:结合本机 dws schema 实时 MCP description/parameters(经 interface_ref)、产品 Skill、Cobra Long 与 Runtime 确认门禁,按飞书风格重写选型文案;不改变命令身份、参数契约或接口绑定。",
|
||||
"review_reason": "人工对齐普通文件存储与在线文档导入边界:在线编辑需求硬路由 doc import,drive upload 不承诺转换结果;不改变命令身份、参数契约或接口绑定。",
|
||||
"candidates": [
|
||||
{
|
||||
"value": [
|
||||
"用户要把文件发送到群聊或单聊时使用 chat message send --msg-type file --file-path;drive upload 不会发送聊天消息",
|
||||
"常规场景不要拆成 upload-info + 手动 PUT + commit;仅自定义流式上传才用三步",
|
||||
"要把文件作为文档正文附件插入改用 dws doc media insert",
|
||||
"Word/Excel/Markdown 要求上传后在线编辑、大家直接在线改或转钉钉文档时改用 dws doc import;普通 drive upload 返回 docx/xlsx 文件不能宣称可在线编辑",
|
||||
"用户明确说文档空间且走 doc 兼容入口时可用 dws doc upload,默认仍推荐本命令",
|
||||
"用户没有明确同意替换目标文件时不要使用 --node;新建上传应使用 --folder 或目标根目录"
|
||||
],
|
||||
"source": "internal/cli/schema_hints/selection/drive.json",
|
||||
"precedence": "reviewed_explicit",
|
||||
"selected": true,
|
||||
"review_reason": "人工审阅:结合本机 dws schema 实时 MCP description/parameters(经 interface_ref)、产品 Skill、Cobra Long 与 Runtime 确认门禁,按飞书风格重写选型文案;不改变命令身份、参数契约或接口绑定。"
|
||||
"review_reason": "人工对齐普通文件存储与在线文档导入边界:在线编辑需求硬路由 doc import,drive upload 不承诺转换结果;不改变命令身份、参数契约或接口绑定。"
|
||||
}
|
||||
]
|
||||
},
|
||||
@@ -10601,7 +10607,7 @@
|
||||
"source": "internal/cli/schema_hints/selection/drive.json",
|
||||
"precedence": "reviewed_explicit",
|
||||
"resolution": "highest_precedence",
|
||||
"review_reason": "人工审阅:结合本机 dws schema 实时 MCP description/parameters(经 interface_ref)、产品 Skill、Cobra Long 与 Runtime 确认门禁,按飞书风格重写选型文案;不改变命令身份、参数契约或接口绑定。",
|
||||
"review_reason": "人工对齐普通文件存储与在线文档导入边界:在线编辑需求硬路由 doc import,drive upload 不承诺转换结果;不改变命令身份、参数契约或接口绑定。",
|
||||
"candidates": [
|
||||
{
|
||||
"value": [
|
||||
@@ -10611,7 +10617,7 @@
|
||||
"source": "internal/cli/schema_hints/selection/drive.json",
|
||||
"precedence": "reviewed_explicit",
|
||||
"selected": true,
|
||||
"review_reason": "人工审阅:结合本机 dws schema 实时 MCP description/parameters(经 interface_ref)、产品 Skill、Cobra Long 与 Runtime 确认门禁,按飞书风格重写选型文案;不改变命令身份、参数契约或接口绑定。"
|
||||
"review_reason": "人工对齐普通文件存储与在线文档导入边界:在线编辑需求硬路由 doc import,drive upload 不承诺转换结果;不改变命令身份、参数契约或接口绑定。"
|
||||
}
|
||||
]
|
||||
},
|
||||
@@ -10704,7 +10710,7 @@
|
||||
"source": "internal/cli/schema_hints/selection/drive.json",
|
||||
"precedence": "reviewed_explicit",
|
||||
"selected": false,
|
||||
"review_reason": "人工审阅:结合本机 dws schema 实时 MCP description/parameters(经 interface_ref)、产品 Skill、Cobra Long 与 Runtime 确认门禁,按飞书风格重写选型文案;不改变命令身份、参数契约或接口绑定。"
|
||||
"review_reason": "人工对齐普通文件存储与在线文档导入边界:在线编辑需求硬路由 doc import,drive upload 不承诺转换结果;不改变命令身份、参数契约或接口绑定。"
|
||||
}
|
||||
]
|
||||
},
|
||||
@@ -10733,24 +10739,26 @@
|
||||
"use_when": {
|
||||
"value": [
|
||||
"用户要把本地文件上传到钉盘/我的文件(首选一条命令自动完成凭证+PUT+提交)时",
|
||||
"上传到知识库/文档空间时加 --workspace;需要转在线文档时加 --convert",
|
||||
"用户明确要求只上传或暂存附件、暂时不要发送到任何聊天会话时",
|
||||
"用户只要保留原始文件供存储或下载时使用;上传到知识库/文档空间时可加 --workspace",
|
||||
"用户明确要求用本地文件替换已有钉盘/文档空间文件时传 --node;该模式会覆盖远端内容并要求确认"
|
||||
],
|
||||
"source": "internal/cli/schema_hints/selection/drive.json",
|
||||
"precedence": "reviewed_explicit",
|
||||
"resolution": "highest_precedence",
|
||||
"review_reason": "人工审阅:结合本机 dws schema 实时 MCP description/parameters(经 interface_ref)、产品 Skill、Cobra Long 与 Runtime 确认门禁,按飞书风格重写选型文案;不改变命令身份、参数契约或接口绑定。",
|
||||
"review_reason": "人工对齐普通文件存储与在线文档导入边界:在线编辑需求硬路由 doc import,drive upload 不承诺转换结果;不改变命令身份、参数契约或接口绑定。",
|
||||
"candidates": [
|
||||
{
|
||||
"value": [
|
||||
"用户要把本地文件上传到钉盘/我的文件(首选一条命令自动完成凭证+PUT+提交)时",
|
||||
"上传到知识库/文档空间时加 --workspace;需要转在线文档时加 --convert",
|
||||
"用户明确要求只上传或暂存附件、暂时不要发送到任何聊天会话时",
|
||||
"用户只要保留原始文件供存储或下载时使用;上传到知识库/文档空间时可加 --workspace",
|
||||
"用户明确要求用本地文件替换已有钉盘/文档空间文件时传 --node;该模式会覆盖远端内容并要求确认"
|
||||
],
|
||||
"source": "internal/cli/schema_hints/selection/drive.json",
|
||||
"precedence": "reviewed_explicit",
|
||||
"selected": true,
|
||||
"review_reason": "人工审阅:结合本机 dws schema 实时 MCP description/parameters(经 interface_ref)、产品 Skill、Cobra Long 与 Runtime 确认门禁,按飞书风格重写选型文案;不改变命令身份、参数契约或接口绑定。"
|
||||
"review_reason": "人工对齐普通文件存储与在线文档导入边界:在线编辑需求硬路由 doc import,drive upload 不承诺转换结果;不改变命令身份、参数契约或接口绑定。"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -10774,7 +10782,8 @@
|
||||
],
|
||||
"use_when": [
|
||||
"用户要把本地文件上传到钉盘/我的文件(首选一条命令自动完成凭证+PUT+提交)时",
|
||||
"上传到知识库/文档空间时加 --workspace;需要转在线文档时加 --convert",
|
||||
"用户明确要求只上传或暂存附件、暂时不要发送到任何聊天会话时",
|
||||
"用户只要保留原始文件供存储或下载时使用;上传到知识库/文档空间时可加 --workspace",
|
||||
"用户明确要求用本地文件替换已有钉盘/文档空间文件时传 --node;该模式会覆盖远端内容并要求确认"
|
||||
]
|
||||
},
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"version": 1,
|
||||
"source_hash": "sha256:eedf9163f496888aa3a065bdbd85647e3702a73ce74703f27083059e22a74098",
|
||||
"source_hash": "sha256:f239237a9b87fa5a95a0520b2e2f2a112e117b273de8418763ba0ecd8652b317",
|
||||
"surface_hash": "sha256:4897d582a472d9d3596f88fb4546e7795d6b9bc334d020712f5d895ce894f7a7",
|
||||
"coverage": {
|
||||
"surface_products": 26,
|
||||
@@ -12,8 +12,8 @@
|
||||
"tools_with_avoid_when": 875,
|
||||
"tools_with_examples": 875,
|
||||
"tools_with_interface_mode": 875,
|
||||
"unmatched_skill_tools": 96,
|
||||
"unreviewed_skill_tools": 11
|
||||
"unmatched_skill_tools": 97,
|
||||
"unreviewed_skill_tools": 12
|
||||
},
|
||||
"products": {
|
||||
"aisearch": {
|
||||
@@ -88,20 +88,20 @@
|
||||
]
|
||||
},
|
||||
"aitable": {
|
||||
"agent_summary": "管理 AI 表格 Base、数据表、字段、记录、视图、表单、仪表盘、权限、导入导出与自动化工作流。",
|
||||
"agent_summary": "管理 AI 表格的 Base、Table、Field、Record、视图表单、仪表盘、权限、导入导出与自动化工作流。",
|
||||
"agent_summary_source": "dws-agent-selection/aitable",
|
||||
"avoid_when": [
|
||||
"目标是在线电子表格单元格读写时用 sheet;普通文档用 doc"
|
||||
"在线电子表格工作表、单元格或公式用 sheet;普通文档用 doc;钉盘普通文件用 drive;类型不明的 alidocs URL 先做类型预检"
|
||||
],
|
||||
"field_provenance": {
|
||||
"agent_summary": {
|
||||
"value": "管理 AI 表格 Base、数据表、字段、记录、视图、表单、仪表盘、权限、导入导出与自动化工作流。",
|
||||
"value": "管理 AI 表格的 Base、Table、Field、Record、视图表单、仪表盘、权限、导入导出与自动化工作流。",
|
||||
"source": "internal/cli/schema_hints/selection/aitable.json",
|
||||
"precedence": "reviewed_explicit",
|
||||
"resolution": "highest_precedence",
|
||||
"candidates": [
|
||||
{
|
||||
"value": "管理 AI 表格 Base、数据表、字段、记录、视图、表单、仪表盘、权限、导入导出与自动化工作流。",
|
||||
"value": "管理 AI 表格的 Base、Table、Field、Record、视图表单、仪表盘、权限、导入导出与自动化工作流。",
|
||||
"source": "internal/cli/schema_hints/selection/aitable.json",
|
||||
"precedence": "reviewed_explicit",
|
||||
"selected": true
|
||||
@@ -110,7 +110,7 @@
|
||||
},
|
||||
"avoid_when": {
|
||||
"value": [
|
||||
"目标是在线电子表格单元格读写时用 sheet;普通文档用 doc"
|
||||
"在线电子表格工作表、单元格或公式用 sheet;普通文档用 doc;钉盘普通文件用 drive;类型不明的 alidocs URL 先做类型预检"
|
||||
],
|
||||
"source": "internal/cli/schema_hints/selection/aitable.json",
|
||||
"precedence": "reviewed_explicit",
|
||||
@@ -118,7 +118,7 @@
|
||||
"candidates": [
|
||||
{
|
||||
"value": [
|
||||
"目标是在线电子表格单元格读写时用 sheet;普通文档用 doc"
|
||||
"在线电子表格工作表、单元格或公式用 sheet;普通文档用 doc;钉盘普通文件用 drive;类型不明的 alidocs URL 先做类型预检"
|
||||
],
|
||||
"source": "internal/cli/schema_hints/selection/aitable.json",
|
||||
"precedence": "reviewed_explicit",
|
||||
@@ -128,7 +128,7 @@
|
||||
},
|
||||
"use_when": {
|
||||
"value": [
|
||||
"需要读取或管理 AI 表格中的结构、数据、视图、权限、导入导出或工作流时"
|
||||
"目标是 AI 表格/多维表中的结构化 Base、数据表、字段、记录、视图、权限、文件导入导出或自动化工作流时"
|
||||
],
|
||||
"source": "internal/cli/schema_hints/selection/aitable.json",
|
||||
"precedence": "reviewed_explicit",
|
||||
@@ -136,7 +136,7 @@
|
||||
"candidates": [
|
||||
{
|
||||
"value": [
|
||||
"需要读取或管理 AI 表格中的结构、数据、视图、权限、导入导出或工作流时"
|
||||
"目标是 AI 表格/多维表中的结构化 Base、数据表、字段、记录、视图、权限、文件导入导出或自动化工作流时"
|
||||
],
|
||||
"source": "internal/cli/schema_hints/selection/aitable.json",
|
||||
"precedence": "reviewed_explicit",
|
||||
@@ -153,10 +153,11 @@
|
||||
"internal/cli/schema_hints/selection/aitable.json",
|
||||
"skills/mono/SKILL.md",
|
||||
"skills/mono/references/intent-guide.md",
|
||||
"skills/mono/references/products/aitable.md"
|
||||
"skills/multi/dingtalk-aitable/SKILL.md",
|
||||
"skills/multi/dingtalk-aitable/references/aitable.md"
|
||||
],
|
||||
"use_when": [
|
||||
"需要读取或管理 AI 表格中的结构、数据、视图、权限、导入导出或工作流时"
|
||||
"目标是 AI 表格/多维表中的结构化 Base、数据表、字段、记录、视图、权限、文件导入导出或自动化工作流时"
|
||||
]
|
||||
},
|
||||
"attendance": {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"version": 1,
|
||||
"source_hash": "sha256:eedf9163f496888aa3a065bdbd85647e3702a73ce74703f27083059e22a74098",
|
||||
"source_hash": "sha256:f239237a9b87fa5a95a0520b2e2f2a112e117b273de8418763ba0ecd8652b317",
|
||||
"surface_hash": "sha256:4897d582a472d9d3596f88fb4546e7795d6b9bc334d020712f5d895ce894f7a7",
|
||||
"source_files": 160,
|
||||
"hint_files": 54,
|
||||
@@ -36,8 +36,8 @@
|
||||
"tools_with_avoid_when": 875,
|
||||
"tools_with_examples": 875,
|
||||
"tools_with_interface_mode": 875,
|
||||
"unmatched_skill_tools": 96,
|
||||
"unreviewed_skill_tools": 11
|
||||
"unmatched_skill_tools": 97,
|
||||
"unreviewed_skill_tools": 12
|
||||
},
|
||||
"source_products": [
|
||||
"agoal",
|
||||
@@ -1301,6 +1301,30 @@
|
||||
"reason": "旧版 Skill 或说明性引用,当前公开命令面无等价 leaf,禁止词法模糊映射"
|
||||
}
|
||||
},
|
||||
{
|
||||
"tool_path": "doc drive upload",
|
||||
"source": "skills/mono/references/products/doc.md",
|
||||
"line": 668,
|
||||
"candidates": [
|
||||
"doc upload",
|
||||
"drive upload",
|
||||
"doc +comment-create"
|
||||
]
|
||||
},
|
||||
{
|
||||
"tool_path": "doc import",
|
||||
"source": "skills/mono/references/products/doc.md",
|
||||
"line": 669,
|
||||
"candidates": [
|
||||
"doc import get",
|
||||
"doc +comment-create",
|
||||
"doc +comment-list"
|
||||
],
|
||||
"review": {
|
||||
"status": "stale",
|
||||
"reason": "旧版 Skill 或说明性引用,当前公开命令面无等价 leaf,禁止词法模糊映射"
|
||||
}
|
||||
},
|
||||
{
|
||||
"tool_path": "doc import",
|
||||
"source": "skills/mono/references/products/doc.md",
|
||||
@@ -1315,6 +1339,30 @@
|
||||
"reason": "旧版 Skill 或说明性引用,当前公开命令面无等价 leaf,禁止词法模糊映射"
|
||||
}
|
||||
},
|
||||
{
|
||||
"tool_path": "doc drive upload",
|
||||
"source": "skills/mono/references/products/doc.md",
|
||||
"line": 745,
|
||||
"candidates": [
|
||||
"doc upload",
|
||||
"drive upload",
|
||||
"doc +comment-create"
|
||||
]
|
||||
},
|
||||
{
|
||||
"tool_path": "doc import",
|
||||
"source": "skills/mono/references/products/doc.md",
|
||||
"line": 746,
|
||||
"candidates": [
|
||||
"doc import get",
|
||||
"doc +comment-create",
|
||||
"doc +comment-list"
|
||||
],
|
||||
"review": {
|
||||
"status": "stale",
|
||||
"reason": "旧版 Skill 或说明性引用,当前公开命令面无等价 leaf,禁止词法模糊映射"
|
||||
}
|
||||
},
|
||||
{
|
||||
"tool_path": "doc import",
|
||||
"source": "skills/mono/references/products/doc.md",
|
||||
@@ -1357,34 +1405,6 @@
|
||||
"reason": "旧版 Skill 或说明性引用,当前公开命令面无等价 leaf,禁止词法模糊映射"
|
||||
}
|
||||
},
|
||||
{
|
||||
"tool_path": "doc import",
|
||||
"source": "skills/mono/references/products/doc/doc-import.md",
|
||||
"line": 12,
|
||||
"candidates": [
|
||||
"doc import get",
|
||||
"doc +comment-create",
|
||||
"doc +comment-list"
|
||||
],
|
||||
"review": {
|
||||
"status": "stale",
|
||||
"reason": "旧版 Skill 或说明性引用,当前公开命令面无等价 leaf,禁止词法模糊映射"
|
||||
}
|
||||
},
|
||||
{
|
||||
"tool_path": "doc import",
|
||||
"source": "skills/mono/references/products/doc/doc-import.md",
|
||||
"line": 13,
|
||||
"candidates": [
|
||||
"doc import get",
|
||||
"doc +comment-create",
|
||||
"doc +comment-list"
|
||||
],
|
||||
"review": {
|
||||
"status": "stale",
|
||||
"reason": "旧版 Skill 或说明性引用,当前公开命令面无等价 leaf,禁止词法模糊映射"
|
||||
}
|
||||
},
|
||||
{
|
||||
"tool_path": "doc import",
|
||||
"source": "skills/mono/references/products/doc/doc-import.md",
|
||||
@@ -1413,6 +1433,34 @@
|
||||
"reason": "旧版 Skill 或说明性引用,当前公开命令面无等价 leaf,禁止词法模糊映射"
|
||||
}
|
||||
},
|
||||
{
|
||||
"tool_path": "doc import",
|
||||
"source": "skills/mono/references/products/doc/doc-import.md",
|
||||
"line": 16,
|
||||
"candidates": [
|
||||
"doc import get",
|
||||
"doc +comment-create",
|
||||
"doc +comment-list"
|
||||
],
|
||||
"review": {
|
||||
"status": "stale",
|
||||
"reason": "旧版 Skill 或说明性引用,当前公开命令面无等价 leaf,禁止词法模糊映射"
|
||||
}
|
||||
},
|
||||
{
|
||||
"tool_path": "doc import",
|
||||
"source": "skills/mono/references/products/doc/doc-import.md",
|
||||
"line": 17,
|
||||
"candidates": [
|
||||
"doc import get",
|
||||
"doc +comment-create",
|
||||
"doc +comment-list"
|
||||
],
|
||||
"review": {
|
||||
"status": "stale",
|
||||
"reason": "旧版 Skill 或说明性引用,当前公开命令面无等价 leaf,禁止词法模糊映射"
|
||||
}
|
||||
},
|
||||
{
|
||||
"tool_path": "event consume user_im_message_receive_o2o_all",
|
||||
"source": "skills/mono/references/products/event.md",
|
||||
|
||||
@@ -1,18 +1,18 @@
|
||||
{
|
||||
"version": 1,
|
||||
"surface_hash": "sha256:4897d582a472d9d3596f88fb4546e7795d6b9bc334d020712f5d895ce894f7a7",
|
||||
"source_hash": "sha256:113dbc3507c5e4d871802d567e960c4947b8066472c28fb8a6dc64e9fef36da9",
|
||||
"source_hash": "sha256:eddcc39cfdf906e0b47c896abc081fd2b5fc81150023f99938deb9def8b07d9e",
|
||||
"catalog": {
|
||||
"agent_metadata": {
|
||||
"products_with_metadata": 26,
|
||||
"source": "embedded-skill-metadata",
|
||||
"source_hash": "sha256:eedf9163f496888aa3a065bdbd85647e3702a73ce74703f27083059e22a74098",
|
||||
"source_hash": "sha256:f239237a9b87fa5a95a0520b2e2f2a112e117b273de8418763ba0ecd8652b317",
|
||||
"surface_hash": "sha256:4897d582a472d9d3596f88fb4546e7795d6b9bc334d020712f5d895ce894f7a7",
|
||||
"surface_products": 26,
|
||||
"surface_tools": 875,
|
||||
"tools_with_agent_summary": 875,
|
||||
"tools_with_metadata": 875,
|
||||
"unmatched_skill_tools": 96,
|
||||
"unmatched_skill_tools": 97,
|
||||
"version": 1
|
||||
},
|
||||
"count": 26,
|
||||
@@ -225,12 +225,13 @@
|
||||
"internal/cli/schema_hints/selection/aitable.json",
|
||||
"skills/mono/SKILL.md",
|
||||
"skills/mono/references/intent-guide.md",
|
||||
"skills/mono/references/products/aitable.md"
|
||||
"skills/multi/dingtalk-aitable/SKILL.md",
|
||||
"skills/multi/dingtalk-aitable/references/aitable.md"
|
||||
],
|
||||
"agent_summary": "管理 AI 表格 Base、数据表、字段、记录、视图、表单、仪表盘、权限、导入导出与自动化工作流。",
|
||||
"agent_summary": "管理 AI 表格的 Base、Table、Field、Record、视图表单、仪表盘、权限、导入导出与自动化工作流。",
|
||||
"agent_summary_source": "dws-agent-selection/aitable",
|
||||
"avoid_when": [
|
||||
"目标是在线电子表格单元格读写时用 sheet;普通文档用 doc"
|
||||
"在线电子表格工作表、单元格或公式用 sheet;普通文档用 doc;钉盘普通文件用 drive;类型不明的 alidocs URL 先做类型预检"
|
||||
],
|
||||
"description": "AI 表格操作",
|
||||
"field_provenance": {
|
||||
@@ -240,13 +241,13 @@
|
||||
"precedence": "reviewed_explicit",
|
||||
"selected": true,
|
||||
"source": "internal/cli/schema_hints/selection/aitable.json",
|
||||
"value": "管理 AI 表格 Base、数据表、字段、记录、视图、表单、仪表盘、权限、导入导出与自动化工作流。"
|
||||
"value": "管理 AI 表格的 Base、Table、Field、Record、视图表单、仪表盘、权限、导入导出与自动化工作流。"
|
||||
}
|
||||
],
|
||||
"precedence": "reviewed_explicit",
|
||||
"resolution": "highest_precedence",
|
||||
"source": "internal/cli/schema_hints/selection/aitable.json",
|
||||
"value": "管理 AI 表格 Base、数据表、字段、记录、视图、表单、仪表盘、权限、导入导出与自动化工作流。"
|
||||
"value": "管理 AI 表格的 Base、Table、Field、Record、视图表单、仪表盘、权限、导入导出与自动化工作流。"
|
||||
},
|
||||
"avoid_when": {
|
||||
"candidates": [
|
||||
@@ -255,7 +256,7 @@
|
||||
"selected": true,
|
||||
"source": "internal/cli/schema_hints/selection/aitable.json",
|
||||
"value": [
|
||||
"目标是在线电子表格单元格读写时用 sheet;普通文档用 doc"
|
||||
"在线电子表格工作表、单元格或公式用 sheet;普通文档用 doc;钉盘普通文件用 drive;类型不明的 alidocs URL 先做类型预检"
|
||||
]
|
||||
}
|
||||
],
|
||||
@@ -263,7 +264,7 @@
|
||||
"resolution": "highest_precedence",
|
||||
"source": "internal/cli/schema_hints/selection/aitable.json",
|
||||
"value": [
|
||||
"目标是在线电子表格单元格读写时用 sheet;普通文档用 doc"
|
||||
"在线电子表格工作表、单元格或公式用 sheet;普通文档用 doc;钉盘普通文件用 drive;类型不明的 alidocs URL 先做类型预检"
|
||||
]
|
||||
},
|
||||
"use_when": {
|
||||
@@ -273,7 +274,7 @@
|
||||
"selected": true,
|
||||
"source": "internal/cli/schema_hints/selection/aitable.json",
|
||||
"value": [
|
||||
"需要读取或管理 AI 表格中的结构、数据、视图、权限、导入导出或工作流时"
|
||||
"目标是 AI 表格/多维表中的结构化 Base、数据表、字段、记录、视图、权限、文件导入导出或自动化工作流时"
|
||||
]
|
||||
}
|
||||
],
|
||||
@@ -281,7 +282,7 @@
|
||||
"resolution": "highest_precedence",
|
||||
"source": "internal/cli/schema_hints/selection/aitable.json",
|
||||
"value": [
|
||||
"需要读取或管理 AI 表格中的结构、数据、视图、权限、导入导出或工作流时"
|
||||
"目标是 AI 表格/多维表中的结构化 Base、数据表、字段、记录、视图、权限、文件导入导出或自动化工作流时"
|
||||
]
|
||||
}
|
||||
},
|
||||
@@ -726,11 +727,11 @@
|
||||
},
|
||||
{
|
||||
"agent_metadata_source": "embedded-skill-metadata",
|
||||
"agent_summary": "按名称搜索 AI 表格 Base(优先于仅最近访问的 list)。",
|
||||
"agent_summary": "底层按名称搜索 AI 表格 Base,返回原始候选列表。",
|
||||
"agent_summary_source": "dws-agent-selection/aitable",
|
||||
"availability": "available",
|
||||
"avoid_when": [
|
||||
"只要最近访问列表用 base list;已知 baseId 取详情用 base get;电子表格 axls 用 sheet"
|
||||
"普通按名称解析唯一 baseId 优先用 +resolve-base;只要候选列表用 +base-search;最近访问用 +base-list;电子表格 axls 用 sheet"
|
||||
],
|
||||
"canonical_path": "aitable.base_search",
|
||||
"cli_name": "search",
|
||||
@@ -752,7 +753,7 @@
|
||||
"risk": "low",
|
||||
"title": "搜索 AI 表格",
|
||||
"use_when": [
|
||||
"用户要找某个 AI 表格/多维表,按名称检索时优先使用"
|
||||
"需要 Shortcut 未公开的底层参数、原始 search_bases 响应或自定义候选处理时"
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -1375,11 +1376,11 @@
|
||||
},
|
||||
{
|
||||
"agent_metadata_source": "embedded-skill-metadata",
|
||||
"agent_summary": "获取字段完整配置。",
|
||||
"agent_summary": "底层获取字段完整类型与 config。",
|
||||
"agent_summary_source": "dws-agent-selection/aitable",
|
||||
"availability": "available",
|
||||
"avoid_when": [
|
||||
"表级字段目录也可用 table get;创建字段用 field create;不可用本命令改类型"
|
||||
"正常按字段 ID 展开类型/config 优先用 +field-get;精简字段目录用 +table-get;创建字段用 field create"
|
||||
],
|
||||
"canonical_path": "aitable.field_get",
|
||||
"cli_name": "get",
|
||||
@@ -1401,7 +1402,7 @@
|
||||
"risk": "low",
|
||||
"title": "获取字段详情",
|
||||
"use_when": [
|
||||
"需要字段类型/config(选项、公式等)详情时"
|
||||
"需要 +field-get 未公开的底层参数、原始响应或不同执行语义时"
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -1948,14 +1949,14 @@
|
||||
},
|
||||
{
|
||||
"agent_metadata_source": "embedded-skill-metadata",
|
||||
"agent_summary": "查询/搜索记录(filters/sort/分页/--all;cells 键为 fieldId)。",
|
||||
"agent_summary": "底层查询/搜索记录,额外支持原子命令的完整分页控制。",
|
||||
"agent_summary_source": "dws-agent-selection/aitable",
|
||||
"aliases": [
|
||||
"aitable record list"
|
||||
],
|
||||
"availability": "available",
|
||||
"avoid_when": [
|
||||
"已知 recordId 窄查可用 record get;空行用 query-empty;写入用 create/update;电子表格单元格用 sheet"
|
||||
"普通按 ID、关键词、filters、sort 或 cursor 查询优先用 +record-query;空行用 +record-query-empty;写入用 create/update;电子表格单元格用 sheet"
|
||||
],
|
||||
"canonical_path": "aitable.query_records",
|
||||
"cli_name": "query",
|
||||
@@ -1977,7 +1978,7 @@
|
||||
"risk": "low",
|
||||
"title": "获取行记录",
|
||||
"use_when": [
|
||||
"查看、筛选、全文搜索或遍历记录时的主入口"
|
||||
"需要 +record-query 未公开的 --all/--page-limit、底层原始响应或不同执行语义时"
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -2009,11 +2010,11 @@
|
||||
},
|
||||
{
|
||||
"agent_metadata_source": "embedded-skill-metadata",
|
||||
"agent_summary": "新增记录(cells 的 key 必须是 fieldId)。",
|
||||
"agent_summary": "新增记录;使用 fieldId 写入并从 newRecordIds 回读验证。",
|
||||
"agent_summary_source": "dws-agent-selection/aitable",
|
||||
"availability": "available",
|
||||
"avoid_when": [
|
||||
"更新已有行用 record update;有则更无则增用 upsert;批量同 patch 用 batch-update"
|
||||
"未核对字段类型时先 field get;更新已有行用 record update;有则更无则增用 upsert;本地 CSV/JSON 批量追加可用已评审脚本"
|
||||
],
|
||||
"canonical_path": "aitable.record_create",
|
||||
"cli_name": "create",
|
||||
@@ -2035,7 +2036,7 @@
|
||||
"risk": "medium",
|
||||
"title": "新增记录",
|
||||
"use_when": [
|
||||
"需要插入新行数据时"
|
||||
"已取得 baseId/tableId 与字段完整配置,需要插入一条或多条新记录并回读时"
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -2257,11 +2258,11 @@
|
||||
},
|
||||
{
|
||||
"agent_metadata_source": "embedded-skill-metadata",
|
||||
"agent_summary": "更新已有记录字段(先 query 拿 recordId;只传需改字段)。",
|
||||
"agent_summary": "更新已有记录字段;先 query 拿 recordId,使用 fieldId 写入并回读。",
|
||||
"agent_summary_source": "dws-agent-selection/aitable",
|
||||
"availability": "available",
|
||||
"avoid_when": [
|
||||
"新建用 create;多条共用同一 cells patch 用 batch-update;混合创建/更新用 upsert"
|
||||
"未定位 recordId 或字段配置时先 query/field get;新建用 create;多条共用同一 cells patch 用 batch-update;混合创建/更新用 upsert"
|
||||
],
|
||||
"canonical_path": "aitable.record_update",
|
||||
"cli_name": "update",
|
||||
@@ -2283,7 +2284,7 @@
|
||||
"risk": "medium",
|
||||
"title": "更新记录",
|
||||
"use_when": [
|
||||
"需要修改已有记录若干字段时"
|
||||
"已查询得到真实 recordId 并核对字段类型,需要修改每条记录各自的 cells 时"
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -2596,7 +2597,7 @@
|
||||
"agent_summary_source": "dws-agent-selection/aitable",
|
||||
"availability": "available",
|
||||
"avoid_when": [
|
||||
"需要该 Shortcut 未公开的底层参数、原始响应或不同执行语义时,改用对应原子命令"
|
||||
"知道名称并要解析唯一 baseId 用 +resolve-base;按关键词要完整候选用 +base-search;不要把最近访问列表描述成全部 Base"
|
||||
],
|
||||
"canonical_path": "aitable.shortcut_base_list",
|
||||
"cli_name": "+base-list",
|
||||
@@ -2622,7 +2623,7 @@
|
||||
"agent_summary_source": "dws-agent-selection/aitable",
|
||||
"availability": "available",
|
||||
"avoid_when": [
|
||||
"需要该 Shortcut 未公开的底层参数、原始响应或不同执行语义时,改用对应原子命令"
|
||||
"只需唯一 baseId 用 +resolve-base;浏览最近访问用 +base-list;需要未公开底层参数或原始响应才用 base search"
|
||||
],
|
||||
"canonical_path": "aitable.shortcut_base_search",
|
||||
"cli_name": "+base-search",
|
||||
@@ -2752,7 +2753,7 @@
|
||||
"agent_summary_source": "dws-agent-selection/aitable",
|
||||
"availability": "available",
|
||||
"avoid_when": [
|
||||
"需要该 Shortcut 未公开的底层参数、原始响应或不同执行语义时,改用对应原子命令"
|
||||
"只需精简 fieldId/type 目录用 +table-get;创建或修改字段用 field create/update;需要未公开底层语义才用 field get"
|
||||
],
|
||||
"canonical_path": "aitable.shortcut_field_get",
|
||||
"cli_name": "+field-get",
|
||||
@@ -2934,7 +2935,7 @@
|
||||
"agent_summary_source": "dws-agent-selection/aitable",
|
||||
"availability": "available",
|
||||
"avoid_when": [
|
||||
"需要该 Shortcut 未公开的底层参数、原始响应或不同执行语义时,改用对应原子命令"
|
||||
"完全空行扫描用 +record-query-empty;变更历史用 +record-history-list;需要 --all/--page-limit 或未公开底层语义才用 record query"
|
||||
],
|
||||
"canonical_path": "aitable.shortcut_record_query",
|
||||
"cli_name": "+record-query",
|
||||
@@ -3038,7 +3039,7 @@
|
||||
"agent_summary_source": "dws-agent-selection/aitable",
|
||||
"availability": "available",
|
||||
"avoid_when": [
|
||||
"需要该 Shortcut 未公开的底层参数、原始响应或不同执行语义时,改用对应原子命令"
|
||||
"多候选时必须让用户消歧;只要完整候选列表用 +base-search;浏览最近访问用 +base-list;已知 baseId 不再重复搜索"
|
||||
],
|
||||
"canonical_path": "aitable.shortcut_resolve_base",
|
||||
"cli_name": "+resolve-base",
|
||||
@@ -3055,7 +3056,7 @@
|
||||
"risk": "low",
|
||||
"title": "按名称搜索多维表 Base 并解析出唯一 baseId(只读)",
|
||||
"use_when": [
|
||||
"当你只知道某个多维表 Base 的名称(或名称里的关键词)、想把它解析成可直接用于后续工具的 baseId 时使用;内部按 --name 关键词调用 search_bases 搜索 Base,再在本地投影出每个候选的 baseId 和 name。如果只命中一个 Base 就直接返回它的 baseId;如果命中多个则列出全部候选让你消歧,绝不替你瞎猜;如果一个都没命中则提示未找到。这是纯只读操作,只做搜索与本地投影,不会修改任何 Base。"
|
||||
"只知道 Base 名称或关键词,需要解析成唯一 baseId 供后续 Table/Record 操作时"
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -3064,7 +3065,7 @@
|
||||
"agent_summary_source": "dws-agent-selection/aitable",
|
||||
"availability": "available",
|
||||
"avoid_when": [
|
||||
"需要该 Shortcut 未公开的底层参数、原始响应或不同执行语义时,改用对应原子命令"
|
||||
"多候选时必须让用户消歧;需要所有表及字段/视图目录用 +table-get;已知 tableId 不再重复解析"
|
||||
],
|
||||
"canonical_path": "aitable.shortcut_resolve_table",
|
||||
"cli_name": "+resolve-table",
|
||||
@@ -3081,7 +3082,7 @@
|
||||
"risk": "low",
|
||||
"title": "在某个多维表 Base 内按名称解析出唯一的数据表 tableId(只读)",
|
||||
"use_when": [
|
||||
"当你已经知道某个多维表 Base 的 baseId、又只记得里面某张数据表(table)的名称或名称关键词、想把它解析成可直接用于后续工具的 tableId 时使用;内部先用 get_tables(只传 baseId)列出该 Base 下的全部数据表,再在本地把每张表投影成 tableId、name,并按 --name 关键词做大小写不敏感的包含匹配来筛选候选。如果只命中一张表就直接返回它的 tableId;如果命中多张则列出全部候选让你消歧,绝不替你瞎猜;如果一张都没命中则提示未找到。这是纯只读操作,只做列举、本地匹配与投影,不会创建、修改或删除任何数据表。"
|
||||
"已有真实 baseId、只知道数据表名称或关键词,需要解析成唯一 tableId 时"
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -3168,7 +3169,7 @@
|
||||
"agent_summary_source": "dws-agent-selection/aitable",
|
||||
"availability": "available",
|
||||
"avoid_when": [
|
||||
"需要该 Shortcut 未公开的底层参数、原始响应或不同执行语义时,改用对应原子命令"
|
||||
"只知道表名并要解析唯一 tableId 用 +resolve-table;需要字段完整 config 用 +field-get;需要未公开底层语义才用 table get"
|
||||
],
|
||||
"canonical_path": "aitable.shortcut_table_get",
|
||||
"cli_name": "+table-get",
|
||||
@@ -3382,11 +3383,11 @@
|
||||
},
|
||||
{
|
||||
"agent_metadata_source": "embedded-skill-metadata",
|
||||
"agent_summary": "获取数据表结构(字段+视图目录)。",
|
||||
"agent_summary": "底层获取数据表结构、精简字段目录与视图目录。",
|
||||
"agent_summary_source": "dws-agent-selection/aitable",
|
||||
"availability": "available",
|
||||
"avoid_when": [
|
||||
"只关心 Base 级 tables 列表可先 base get;字段完整配置也可用 field get"
|
||||
"正常获取表、精简字段和视图目录优先用 +table-get;按表名解析 ID 用 +resolve-table;完整字段 config 用 +field-get"
|
||||
],
|
||||
"canonical_path": "aitable.table_get",
|
||||
"cli_name": "get",
|
||||
@@ -3408,7 +3409,7 @@
|
||||
"risk": "low",
|
||||
"title": "获取数据表",
|
||||
"use_when": [
|
||||
"需要字段 fieldId 或视图目录以继续 record/field 操作时优先 table get"
|
||||
"需要 +table-get 未公开的底层参数、原始 get_tables 响应或不同执行语义时"
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -4571,7 +4572,7 @@
|
||||
}
|
||||
],
|
||||
"use_when": [
|
||||
"需要读取或管理 AI 表格中的结构、数据、视图、权限、导入导出或工作流时"
|
||||
"目标是 AI 表格/多维表中的结构化 Base、数据表、字段、记录、视图、权限、文件导入导出或自动化工作流时"
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -7784,11 +7785,11 @@
|
||||
},
|
||||
{
|
||||
"agent_metadata_source": "embedded-skill-metadata",
|
||||
"agent_summary": "给指定消息添加表情回应",
|
||||
"agent_summary": "使用同一会话中真实的 openMessageId 给指定消息添加表情回应",
|
||||
"agent_summary_source": "dws-agent-selection/chat",
|
||||
"availability": "available",
|
||||
"avoid_when": [
|
||||
"发送文本消息或文字表情时不要使用"
|
||||
"发送文本消息或文字表情时不要使用;不要把 openTaskId 当作 msg-id"
|
||||
],
|
||||
"canonical_path": "chat.add_emoji_reaction",
|
||||
"cli_name": "add-emoji",
|
||||
@@ -7810,7 +7811,7 @@
|
||||
"risk": "medium",
|
||||
"title": "对消息添加 emoji 表情回应",
|
||||
"use_when": [
|
||||
"需要对已有消息添加一个 emoji reaction 时"
|
||||
"需要对已有消息添加一个 emoji reaction 时;conversation-id 与 msg-id 必须来自同一条真实消息"
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -7967,7 +7968,7 @@
|
||||
"agent_summary_source": "dws-agent-selection/chat",
|
||||
"availability": "available",
|
||||
"avoid_when": [
|
||||
"管理员级群功能开关用 chat group update-settings"
|
||||
"管理员级群功能开关用 chat group update-settings;查询结果是当前用户视角,不代表群级设置"
|
||||
],
|
||||
"canonical_path": "chat.batch_query_group_chat_settings",
|
||||
"cli_name": "query",
|
||||
@@ -7985,7 +7986,7 @@
|
||||
"risk": "low",
|
||||
"title": "批量查询当前用户的群会话设置",
|
||||
"use_when": [
|
||||
"用户说 看下这些群我的置顶和免打扰设置"
|
||||
"用户说看下这些群中我自己的置顶和免打扰设置;修改前先查询并保存原值用于恢复"
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -7994,7 +7995,7 @@
|
||||
"agent_summary_source": "dws-agent-selection/chat",
|
||||
"availability": "available",
|
||||
"avoid_when": [
|
||||
"单个群昵称优先 chat group update-nick"
|
||||
"单个群昵称优先 chat group update-nick;不要用本命令修改 searchable、@所有人权限等群级开关;临时测试结束时按修改前 query 的真实值恢复"
|
||||
],
|
||||
"canonical_path": "chat.batch_update_group_chat_settings",
|
||||
"cli_name": "set",
|
||||
@@ -8012,7 +8013,7 @@
|
||||
"risk": "medium",
|
||||
"title": "批量更新当前用户的群会话设置",
|
||||
"use_when": [
|
||||
"用户说 把这些群都设为免打扰/置顶"
|
||||
"用户说把这些群都设为免打扰或置顶;items 中只写需要改变的当前用户设置,完成后再次 query 验证"
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -9430,11 +9431,11 @@
|
||||
},
|
||||
{
|
||||
"agent_metadata_source": "embedded-skill-metadata",
|
||||
"agent_summary": "查询异步消息发送任务的状态",
|
||||
"agent_summary": "查询异步消息发送任务的业务终态,并按 sendStatus 判断是否真正投递成功",
|
||||
"agent_summary_source": "dws-agent-selection/chat",
|
||||
"availability": "available",
|
||||
"avoid_when": [
|
||||
"没有 message send 真实返回的 openTaskId 时不要使用;openMessageId 和会话 ID 都不能代替 openTaskId"
|
||||
"没有 message send 真实返回的 openTaskId 时不要使用;openMessageId 和会话 ID 都不能代替 openTaskId;FAILED 不得当作接口成功"
|
||||
],
|
||||
"canonical_path": "chat.query_message_send_status",
|
||||
"cli_name": "query-send-status",
|
||||
@@ -9456,7 +9457,7 @@
|
||||
"risk": "low",
|
||||
"title": "查询消息发送状态",
|
||||
"use_when": [
|
||||
"发送命令返回 openTaskId 后需要确认投递结果时"
|
||||
"发送命令返回 openTaskId 后需要确认投递结果时;只有 sendStatus=SUCCESS 才能宣称消息发送成功"
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -9672,11 +9673,11 @@
|
||||
},
|
||||
{
|
||||
"agent_metadata_source": "embedded-skill-metadata",
|
||||
"agent_summary": "移除指定消息上的表情回应",
|
||||
"agent_summary": "从同一条真实消息移除此前添加的同名表情回应",
|
||||
"agent_summary_source": "dws-agent-selection/chat",
|
||||
"availability": "available",
|
||||
"avoid_when": [
|
||||
"移除文字表情时使用 chat message remove-text-emotion"
|
||||
"移除文字表情时使用 chat message remove-text-emotion;不要猜测新的消息 ID 或表情名称"
|
||||
],
|
||||
"canonical_path": "chat.remove_emoji_reaction",
|
||||
"cli_name": "remove-emoji",
|
||||
@@ -9698,7 +9699,7 @@
|
||||
"risk": "medium",
|
||||
"title": "移除消息的 emoji 表情回应",
|
||||
"use_when": [
|
||||
"需要取消此前添加的 emoji reaction 时"
|
||||
"需要取消此前添加的 emoji reaction 时;复用添加时的 conversation-id、msg-id 和 emoji"
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -9707,7 +9708,7 @@
|
||||
"agent_summary_source": "dws-agent-selection/chat",
|
||||
"availability": "available",
|
||||
"avoid_when": [
|
||||
"移除机器人时使用 chat group members remove-bot"
|
||||
"移除机器人时使用 chat group members remove-bot;不要把建群者或群主加入 --users,也不要为了清理成员而转让群主"
|
||||
],
|
||||
"canonical_path": "chat.remove_group_member",
|
||||
"cli_name": "remove",
|
||||
@@ -9729,7 +9730,7 @@
|
||||
"risk": "medium",
|
||||
"title": "移除群成员",
|
||||
"use_when": [
|
||||
"群管理员明确要移除一个或多个普通成员时"
|
||||
"群管理员明确要移除一个或多个普通成员时;清理临时群只传本次加入的普通成员"
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -10342,11 +10343,11 @@
|
||||
},
|
||||
{
|
||||
"agent_metadata_source": "embedded-skill-metadata",
|
||||
"agent_summary": "把指定消息设为会话置顶消息",
|
||||
"agent_summary": "使用同源的 openConversationId 和 openMessageId 把指定消息设为 Pin",
|
||||
"agent_summary_source": "dws-agent-selection/chat",
|
||||
"availability": "available",
|
||||
"avoid_when": [
|
||||
"取消置顶使用 chat message unset-pin-msg"
|
||||
"取消 Pin 使用 chat message unset-pin-msg;置顶到会话顶部使用 set-top-msg,置顶整个会话使用 chat set-top"
|
||||
],
|
||||
"canonical_path": "chat.set_pin_message",
|
||||
"cli_name": "set-pin-msg",
|
||||
@@ -10368,7 +10369,7 @@
|
||||
"risk": "medium",
|
||||
"title": "钉住消息(Pin)",
|
||||
"use_when": [
|
||||
"需要在会话中置顶一条已知消息时"
|
||||
"需要在会话中钉住一条已知消息时;若发送只返回 openTaskId,先确认发送终态,再从消息查询结果取得真实 openMessageId"
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -11393,11 +11394,11 @@
|
||||
},
|
||||
{
|
||||
"agent_metadata_source": "embedded-skill-metadata",
|
||||
"agent_summary": "查询消息发送状态",
|
||||
"agent_summary": "查询消息发送任务的业务终态,只有 sendStatus=SUCCESS 才表示真正发送成功",
|
||||
"agent_summary_source": "dws-agent-selection/chat",
|
||||
"availability": "available",
|
||||
"avoid_when": [
|
||||
"需要该 Shortcut 未公开的底层参数、原始响应或不同执行语义时,改用对应原子命令"
|
||||
"需要该 Shortcut 未公开的底层参数、原始响应或不同执行语义时,改用对应原子命令;不要把 openTaskId 当 openMessageId 做 Pin、置顶或表情操作"
|
||||
],
|
||||
"canonical_path": "chat.shortcut_messages_query_send_status",
|
||||
"cli_name": "+messages-query-send-status",
|
||||
@@ -11414,7 +11415,7 @@
|
||||
"risk": "low",
|
||||
"title": "查询消息发送状态",
|
||||
"use_when": [
|
||||
"当你发消息后拿到 openTaskId、想确认这条消息是否发送成功时使用;只读返回发送状态,需传 --open-task-id。"
|
||||
"当你发消息后拿到 openTaskId、想确认这条消息是否发送成功时使用;只读返回发送状态,需传 --open-task-id;FAILED 必须如实报告,不能因外层 success=true 宣称成功。"
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -11768,11 +11769,11 @@
|
||||
},
|
||||
{
|
||||
"agent_metadata_source": "embedded-skill-metadata",
|
||||
"agent_summary": "取消指定消息的会话置顶",
|
||||
"agent_summary": "使用设置 Pin 时的同源会话和消息 ID 取消指定消息的 Pin",
|
||||
"agent_summary_source": "dws-agent-selection/chat",
|
||||
"availability": "available",
|
||||
"avoid_when": [
|
||||
"新增置顶使用 chat message set-pin-msg"
|
||||
"新增 Pin 使用 chat message set-pin-msg;不要改用 unset-top-msg 或 chat set-top --off"
|
||||
],
|
||||
"canonical_path": "chat.unset_pin_message",
|
||||
"cli_name": "unset-pin-msg",
|
||||
@@ -11794,7 +11795,7 @@
|
||||
"risk": "medium",
|
||||
"title": "取消钉住消息(Unpin)",
|
||||
"use_when": [
|
||||
"需要移除一条已知置顶消息时"
|
||||
"需要移除一条已知 Pin 消息时;复用 set-pin-msg 成功时的 openConversationId 和 openMessageId"
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -11918,7 +11919,7 @@
|
||||
"agent_summary_source": "dws-agent-selection/chat",
|
||||
"availability": "available",
|
||||
"avoid_when": [
|
||||
"没有真实可用 mediaId 时先完成媒体上传"
|
||||
"本地图片路径、钉盘 dentryId、聊天文件 uploadKey 都不能代替 icon-media-id;当前 CLI 无本地图片转群头像 mediaId 的能力,缺少 mediaId 时应如实报告能力边界"
|
||||
],
|
||||
"canonical_path": "chat.update_group_icon",
|
||||
"cli_name": "update-icon",
|
||||
@@ -11940,7 +11941,7 @@
|
||||
"risk": "medium",
|
||||
"title": "更新群头像",
|
||||
"use_when": [
|
||||
"已有上传后的头像 mediaId 并要修改群头像时"
|
||||
"已有可用于群头像接口的真实 mediaId 并要修改群头像时"
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -12007,7 +12008,7 @@
|
||||
"agent_summary_source": "dws-agent-selection/chat",
|
||||
"availability": "available",
|
||||
"avoid_when": [
|
||||
"全员禁言和成员禁言使用专门的 mute 命令"
|
||||
"全员禁言和成员禁言使用专门的 mute 命令;当前用户自己的会话置顶或免打扰使用 group user-settings set"
|
||||
],
|
||||
"canonical_path": "chat.update_group_settings",
|
||||
"cli_name": "update-settings",
|
||||
@@ -12029,7 +12030,7 @@
|
||||
"risk": "medium",
|
||||
"title": "更新群设置",
|
||||
"use_when": [
|
||||
"需要调整 searchable、入群验证或群权限等设置时"
|
||||
"需要调整 searchable、入群验证、@所有人权限等群级设置时;修改后用群信息或设置查询核对终态"
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -15377,13 +15378,13 @@
|
||||
},
|
||||
{
|
||||
"agent_metadata_source": "embedded-skill-metadata",
|
||||
"agent_summary": "创建一篇新的在线文档",
|
||||
"agent_summary": "在默认根目录、文档文件夹或知识库根创建带可选初始内容的 adoc",
|
||||
"agent_summary_source": "dws-agent-selection/doc",
|
||||
"availability": "available",
|
||||
"avoid_when": [
|
||||
"创建表格/脑图/白板/多维表/演示改用 dws wiki node create --type \u003ctype\u003e(勿用 doc create)",
|
||||
"在知识库建空节点实体也可用 wiki node create;本命令侧重可写初始内容的 adoc",
|
||||
"导入本地 Word/Markdown 为在线文档改用 dws doc import(若可用)或 upload --convert"
|
||||
"只管理知识库空节点或层级时用 wiki node create;本命令侧重创建并写入 adoc",
|
||||
"导入本地 Word/Markdown 并保留服务端转换语义时用 dws doc import;不要用自写分片脚本替代原生 create"
|
||||
],
|
||||
"canonical_path": "doc.create_document",
|
||||
"cli_name": "create",
|
||||
@@ -15404,8 +15405,8 @@
|
||||
"risk": "medium",
|
||||
"title": "创建文档",
|
||||
"use_when": [
|
||||
"用户要新建一篇文字在线文档(adoc),可空文档或带初始 Markdown 时",
|
||||
"创建到指定文件夹 --folder、知识库根 --workspace,或默认「我的文档」根目录时"
|
||||
"用户要新建文字在线文档(adoc),可空文档或用 --content-file 写入明确要求的初始 Markdown/JSONML;用户显式要求正文 H1 时必须保留,不能用 --name 替代",
|
||||
"已知目标文档文件夹 --folder 或知识库 --workspace;长 Markdown 仍直接使用本命令,由原生写入管道自动分片;返回 nodeId 绑定同一请求的后续 list/insert/export,后续可观察操作仍须分别执行"
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -15665,7 +15666,7 @@
|
||||
"agent_summary_source": "dws-agent-selection/doc",
|
||||
"availability": "available",
|
||||
"avoid_when": [
|
||||
"非 adoc(表格/多维表/普通文件)不要用本命令;先 doc info 再路由",
|
||||
"原始 alidocs URL 类型未知或目标不是 adoc 时先用 drive info 探测并路由;表格/多维表/普通文件不要用本命令",
|
||||
"要元信息用 doc info;要块结构用 doc block list",
|
||||
"Markdown 为有损投影:保形复制模板请用 doc copy,不要 read→create"
|
||||
],
|
||||
@@ -15688,20 +15689,21 @@
|
||||
"risk": "low",
|
||||
"title": "读取文档内容 (Markdown)",
|
||||
"use_when": [
|
||||
"用户要读取钉钉在线文字文档(adoc)正文(Markdown)时",
|
||||
"用户直接粘贴文档 URL 且无其他指令时(默认读内容)",
|
||||
"已由 drive info 确认 extension=adoc,用户要读取正文(Markdown)时",
|
||||
"用户提供已知 adoc nodeId/URL,且要读取内容或抽取指定章节时",
|
||||
"只需标题大纲、指定块区间/单块或特定 JSONML tags 时使用 --content-format jsonml 与 --scope"
|
||||
]
|
||||
},
|
||||
{
|
||||
"agent_metadata_source": "embedded-skill-metadata",
|
||||
"agent_summary": "获取文档元信息(标题/类型/创建者/权限等)",
|
||||
"agent_summary": "在已确认是 ALIDOC 后读取文档专属元信息",
|
||||
"agent_summary_source": "dws-agent-selection/doc",
|
||||
"availability": "available",
|
||||
"avoid_when": [
|
||||
"已确认是 adoc 且只要正文改用 dws doc read",
|
||||
"原始 alidocs URL 的类型探测、extension 路由或可靠 fileSize 使用 dws drive info;不要先猜是文档",
|
||||
"已确认是 adoc 且只要正文时改用 dws doc read",
|
||||
"只要目录列表改用 dws drive list / wiki node list",
|
||||
"需要可靠文件大小 fileSize 时改用 dws drive info;文档元信息接口可能不返回大小"
|
||||
"普通文件、电子表格或 AI 表格不使用本命令"
|
||||
],
|
||||
"canonical_path": "doc.get_document_info",
|
||||
"cli_name": "info",
|
||||
@@ -15722,8 +15724,8 @@
|
||||
"risk": "low",
|
||||
"title": "获取文档元信息",
|
||||
"use_when": [
|
||||
"用户要查看文档/节点元信息(标题、类型、创建者、权限)时",
|
||||
"准备读内容前必须先看 contentType/extension 以路由到 read/sheet/aitable/download 时"
|
||||
"drive info 已确认是 ALIDOC,用户还要标题、创建者、权限或 docUrl 等文档专属元信息时",
|
||||
"创建响应只有 nodeId、缺少 docUrl,需要补查文档链接时"
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -15789,7 +15791,7 @@
|
||||
"agent_summary_source": "dws-agent-selection/doc",
|
||||
"availability": "available",
|
||||
"avoid_when": [
|
||||
"整篇追加 Markdown 优先 doc update --mode append",
|
||||
"仅在用户没有指定块操作、只要整篇追加 Markdown 时优先 doc update --mode append;显式 insert/标题块/列表块不能折叠",
|
||||
"插入本地文件附件优先 doc media insert",
|
||||
"删块用 block delete;改已有块用 block update"
|
||||
],
|
||||
@@ -15813,7 +15815,8 @@
|
||||
"risk": "medium",
|
||||
"title": "插入块元素",
|
||||
"use_when": [
|
||||
"在文档中插入新块(段落/标题等);简单场景用 --text/--heading,复杂块用 --element JSON"
|
||||
"用户明确要求在文档中插入新块(段落/标题/列表等)时;简单段落/标题用 --text/--heading,复杂块用 --element JSON",
|
||||
"用户要求有序列表块时使用 JSONML p.list.isOrdered=true(同一 listId)或等价原生 orderedList 结构,不能用普通 Markdown 代替"
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -15876,7 +15879,7 @@
|
||||
"risk": "low",
|
||||
"title": "查询块元素",
|
||||
"use_when": [
|
||||
"查看文档一级块结构、拿 blockId,供 insert/update/delete 或划词评论定位时"
|
||||
"查看文档一级块结构、拿 blockId,供 insert/update/delete 或划词评论定位时;用户在显式工作流中点名 list 时必须真实执行并使用返回结果"
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -16244,7 +16247,7 @@
|
||||
"agent_summary_source": "dws-agent-selection/doc",
|
||||
"availability": "available",
|
||||
"avoid_when": [
|
||||
"需要该 Shortcut 未公开的底层参数、原始响应或不同执行语义时,改用对应原子命令"
|
||||
"常规文件管理优先 drive copy;要搬走原件用 move;复制后必须从真实返回取副本 nodeId,禁止继续编辑源文档"
|
||||
],
|
||||
"canonical_path": "doc.shortcut_copy",
|
||||
"cli_name": "+copy",
|
||||
@@ -16270,7 +16273,7 @@
|
||||
"agent_summary_source": "dws-agent-selection/doc",
|
||||
"availability": "available",
|
||||
"avoid_when": [
|
||||
"需要该 Shortcut 未公开的底层参数、原始响应或不同执行语义时,改用对应原子命令"
|
||||
"长、多行、表格或文件内容使用 doc update --mode append --content-file;指定位置或富结构使用 block insert;不要跳过 user_required 确认"
|
||||
],
|
||||
"canonical_path": "doc.shortcut_doc_append",
|
||||
"cli_name": "+doc-append",
|
||||
@@ -16322,7 +16325,7 @@
|
||||
"agent_summary_source": "dws-agent-selection/doc",
|
||||
"availability": "available",
|
||||
"avoid_when": [
|
||||
"需要该 Shortcut 未公开的底层参数、原始响应或不同执行语义时,改用对应原子命令"
|
||||
"用户要直接拿到本地 docx/markdown/pdf 文件时使用一体化 dws doc export;不要默认手工编排 submit/get 轮询"
|
||||
],
|
||||
"canonical_path": "doc.shortcut_export_submit",
|
||||
"cli_name": "+export-submit",
|
||||
@@ -16348,7 +16351,7 @@
|
||||
"agent_summary_source": "dws-agent-selection/doc",
|
||||
"availability": "available",
|
||||
"avoid_when": [
|
||||
"需要该 Shortcut 未公开的底层参数、原始响应或不同执行语义时,改用对应原子命令"
|
||||
"需要最近访问、extension/时间/创建者等组合过滤时使用 +search;目标已给 nodeId/URL 时直接进入 info/read,不再搜索"
|
||||
],
|
||||
"canonical_path": "doc.shortcut_find_doc",
|
||||
"cli_name": "+find-doc",
|
||||
@@ -16374,7 +16377,7 @@
|
||||
"agent_summary_source": "dws-agent-selection/doc",
|
||||
"availability": "available",
|
||||
"avoid_when": [
|
||||
"需要该 Shortcut 未公开的底层参数、原始响应或不同执行语义时,改用对应原子命令"
|
||||
"全局或钉盘目录浏览优先 drive list,知识库节点树优先 wiki node list;本 Shortcut 只列已知 doc folder/workspace 的直接子节点"
|
||||
],
|
||||
"canonical_path": "doc.shortcut_list",
|
||||
"cli_name": "+list",
|
||||
@@ -16400,7 +16403,7 @@
|
||||
"agent_summary_source": "dws-agent-selection/doc",
|
||||
"availability": "available",
|
||||
"avoid_when": [
|
||||
"需要该 Shortcut 未公开的底层参数、原始响应或不同执行语义时,改用对应原子命令"
|
||||
"常规文件管理优先 drive move;要保留原位置副本用 copy;目标位置不明确或未确认时不要移动"
|
||||
],
|
||||
"canonical_path": "doc.shortcut_move",
|
||||
"cli_name": "+move",
|
||||
@@ -16426,7 +16429,7 @@
|
||||
"agent_summary_source": "dws-agent-selection/doc",
|
||||
"availability": "available",
|
||||
"avoid_when": [
|
||||
"需要该 Shortcut 未公开的底层参数、原始响应或不同执行语义时,改用对应原子命令"
|
||||
"只有一个关键词且只要紧凑标题/URL/type/token 投影时优先 +find-doc;目标已给 nodeId/URL 时不要再搜索"
|
||||
],
|
||||
"canonical_path": "doc.shortcut_search",
|
||||
"cli_name": "+search",
|
||||
@@ -16452,7 +16455,7 @@
|
||||
"agent_summary_source": "dws-agent-selection/doc",
|
||||
"availability": "available",
|
||||
"avoid_when": [
|
||||
"需要该 Shortcut 未公开的底层参数、原始响应或不同执行语义时,改用对应原子命令"
|
||||
"同名人员未消歧、缺少真实文档 URL 或用户未确认时不要发送;本命令不授予文档权限,授权应走 drive permission"
|
||||
],
|
||||
"canonical_path": "doc.shortcut_share_doc",
|
||||
"cli_name": "+share-doc",
|
||||
@@ -16556,7 +16559,7 @@
|
||||
"agent_summary_source": "dws-agent-selection/doc",
|
||||
"availability": "available",
|
||||
"avoid_when": [
|
||||
"需要该 Shortcut 未公开的底层参数、原始响应或不同执行语义时,改用对应原子命令"
|
||||
"只查看历史用 +version-list,只保存当前快照用 +version-save;版本号未核实、用户未确认或只需改单块时不要回滚"
|
||||
],
|
||||
"canonical_path": "doc.shortcut_version_revert",
|
||||
"cli_name": "+version-revert",
|
||||
@@ -16797,7 +16800,8 @@
|
||||
"agent_summary_source": "dws-agent-selection/doc",
|
||||
"availability": "available",
|
||||
"avoid_when": [
|
||||
"删除评论用 delete;回复用 reply"
|
||||
"删除评论用 delete;回复用 reply",
|
||||
"响应为 null/空对象或回查仍是旧正文时不能判成功,必须报告更新未生效或部分完成"
|
||||
],
|
||||
"canonical_path": "doc.update_comment",
|
||||
"cli_name": "update",
|
||||
@@ -16815,18 +16819,19 @@
|
||||
"risk": "medium",
|
||||
"title": "更新文档评论",
|
||||
"use_when": [
|
||||
"修改已有评论正文;可选更新 --mention 或 --mentioned-open-conversation-id"
|
||||
"修改已有评论正文;可选更新 --mention 或 --mentioned-open-conversation-id;执行后必须用 comment list 回查同一 commentKey 的目标字段"
|
||||
]
|
||||
},
|
||||
{
|
||||
"agent_metadata_source": "embedded-skill-metadata",
|
||||
"agent_summary": "更新文档内容(追加 / 覆盖;覆盖需 --yes)",
|
||||
"agent_summary": "用原生自动分片管道追加或整篇覆盖 adoc 内容",
|
||||
"agent_summary_source": "dws-agent-selection/doc",
|
||||
"availability": "available",
|
||||
"avoid_when": [
|
||||
"目标不是 adoc 或只要改单个块时改用 doc block update",
|
||||
"只在末尾补一小段纯文本优先 +doc-append;只改一个块用 doc block update",
|
||||
"目标不是 adoc 时按 drive info 的 extension 切对应产品",
|
||||
"覆盖模式用户未确认前不要执行;可先 --dry-run 预览",
|
||||
"创建新文档用 doc create,不要用 update 冒充创建"
|
||||
"创建新文档用 doc create;不要预先手工分片或循环重试覆盖"
|
||||
],
|
||||
"canonical_path": "doc.update_document",
|
||||
"cli_name": "update",
|
||||
@@ -16847,7 +16852,7 @@
|
||||
"risk": "medium",
|
||||
"title": "更新文档内容",
|
||||
"use_when": [
|
||||
"用户要向已有 adoc 追加内容时用 --mode append(更安全)",
|
||||
"用户要向已有 adoc 追加长、多行或文件内容时用 --mode append + --content-file;CLI 自动分片",
|
||||
"用户明确要求整篇覆盖替换时用 --mode overwrite(破坏性)",
|
||||
"append 且要插到第 N 个 block 前时加 --index N(先 block list)"
|
||||
]
|
||||
@@ -18283,8 +18288,10 @@
|
||||
"agent_summary_source": "dws-agent-selection/drive",
|
||||
"availability": "available",
|
||||
"avoid_when": [
|
||||
"用户要把文件发送到群聊或单聊时使用 chat message send --msg-type file --file-path;drive upload 不会发送聊天消息",
|
||||
"常规场景不要拆成 upload-info + 手动 PUT + commit;仅自定义流式上传才用三步",
|
||||
"要把文件作为文档正文附件插入改用 dws doc media insert",
|
||||
"Word/Excel/Markdown 要求上传后在线编辑、大家直接在线改或转钉钉文档时改用 dws doc import;普通 drive upload 返回 docx/xlsx 文件不能宣称可在线编辑",
|
||||
"用户明确说文档空间且走 doc 兼容入口时可用 dws doc upload,默认仍推荐本命令",
|
||||
"用户没有明确同意替换目标文件时不要使用 --node;新建上传应使用 --folder 或目标根目录"
|
||||
],
|
||||
@@ -18307,7 +18314,8 @@
|
||||
"title": "上传本地文件到钉盘或文档空间",
|
||||
"use_when": [
|
||||
"用户要把本地文件上传到钉盘/我的文件(首选一条命令自动完成凭证+PUT+提交)时",
|
||||
"上传到知识库/文档空间时加 --workspace;需要转在线文档时加 --convert",
|
||||
"用户明确要求只上传或暂存附件、暂时不要发送到任何聊天会话时",
|
||||
"用户只要保留原始文件供存储或下载时使用;上传到知识库/文档空间时可加 --workspace",
|
||||
"用户明确要求用本地文件替换已有钉盘/文档空间文件时传 --node;该模式会覆盖远端内容并要求确认"
|
||||
]
|
||||
}
|
||||
|
||||
@@ -8704,13 +8704,15 @@
|
||||
"internal/cli/schema_hints/metadata/aitable.json",
|
||||
"internal/cli/schema_hints/selection/aitable.json",
|
||||
"internal/cli/schema_mcp_metadata.json#tools.aitable.base_search",
|
||||
"skills/mono/references/products/aitable.md"
|
||||
"skills/mono/references/products/aitable.md",
|
||||
"skills/multi/dingtalk-aitable/SKILL.md",
|
||||
"skills/multi/dingtalk-aitable/references/aitable.md"
|
||||
],
|
||||
"agent_summary": "按名称搜索 AI 表格 Base(优先于仅最近访问的 list)。",
|
||||
"agent_summary": "底层按名称搜索 AI 表格 Base,返回原始候选列表。",
|
||||
"agent_summary_source": "dws-agent-selection/aitable",
|
||||
"availability": "available",
|
||||
"avoid_when": [
|
||||
"只要最近访问列表用 base list;已知 baseId 取详情用 base get;电子表格 axls 用 sheet"
|
||||
"普通按名称解析唯一 baseId 优先用 +resolve-base;只要候选列表用 +base-search;最近访问用 +base-list;电子表格 axls 用 sheet"
|
||||
],
|
||||
"canonical_path": "aitable.base_search",
|
||||
"cli_name": "search",
|
||||
@@ -8731,14 +8733,14 @@
|
||||
"review_reason": "人工结合实时 dws schema MCP 描述(或 helper 无 live 时用 Skill/Cobra)、兄弟命令分流与 Runtime 确认门禁审阅选型语义。",
|
||||
"selected": true,
|
||||
"source": "internal/cli/schema_hints/selection/aitable.json",
|
||||
"value": "按名称搜索 AI 表格 Base(优先于仅最近访问的 list)。"
|
||||
"value": "底层按名称搜索 AI 表格 Base,返回原始候选列表。"
|
||||
}
|
||||
],
|
||||
"precedence": "reviewed_explicit",
|
||||
"resolution": "highest_precedence",
|
||||
"review_reason": "人工结合实时 dws schema MCP 描述(或 helper 无 live 时用 Skill/Cobra)、兄弟命令分流与 Runtime 确认门禁审阅选型语义。",
|
||||
"source": "internal/cli/schema_hints/selection/aitable.json",
|
||||
"value": "按名称搜索 AI 表格 Base(优先于仅最近访问的 list)。"
|
||||
"value": "底层按名称搜索 AI 表格 Base,返回原始候选列表。"
|
||||
},
|
||||
"availability": {
|
||||
"candidates": [
|
||||
@@ -8770,7 +8772,7 @@
|
||||
"selected": true,
|
||||
"source": "internal/cli/schema_hints/selection/aitable.json",
|
||||
"value": [
|
||||
"只要最近访问列表用 base list;已知 baseId 取详情用 base get;电子表格 axls 用 sheet"
|
||||
"普通按名称解析唯一 baseId 优先用 +resolve-base;只要候选列表用 +base-search;最近访问用 +base-list;电子表格 axls 用 sheet"
|
||||
]
|
||||
}
|
||||
],
|
||||
@@ -8779,7 +8781,7 @@
|
||||
"review_reason": "人工结合实时 dws schema MCP 描述(或 helper 无 live 时用 Skill/Cobra)、兄弟命令分流与 Runtime 确认门禁审阅选型语义。",
|
||||
"source": "internal/cli/schema_hints/selection/aitable.json",
|
||||
"value": [
|
||||
"只要最近访问列表用 base list;已知 baseId 取详情用 base get;电子表格 axls 用 sheet"
|
||||
"普通按名称解析唯一 baseId 优先用 +resolve-base;只要候选列表用 +base-search;最近访问用 +base-list;电子表格 axls 用 sheet"
|
||||
]
|
||||
},
|
||||
"canonical_path": {
|
||||
@@ -9009,7 +9011,7 @@
|
||||
"selected": true,
|
||||
"source": "internal/cli/schema_hints/selection/aitable.json",
|
||||
"value": [
|
||||
"用户要找某个 AI 表格/多维表,按名称检索时优先使用"
|
||||
"需要 Shortcut 未公开的底层参数、原始 search_bases 响应或自定义候选处理时"
|
||||
]
|
||||
}
|
||||
],
|
||||
@@ -9018,7 +9020,7 @@
|
||||
"review_reason": "人工结合实时 dws schema MCP 描述(或 helper 无 live 时用 Skill/Cobra)、兄弟命令分流与 Runtime 确认门禁审阅选型语义。",
|
||||
"source": "internal/cli/schema_hints/selection/aitable.json",
|
||||
"value": [
|
||||
"用户要找某个 AI 表格/多维表,按名称检索时优先使用"
|
||||
"需要 Shortcut 未公开的底层参数、原始 search_bases 响应或自定义候选处理时"
|
||||
]
|
||||
}
|
||||
},
|
||||
@@ -9237,7 +9239,7 @@
|
||||
"source": "reviewed_command_registry",
|
||||
"title": "搜索 AI 表格",
|
||||
"use_when": [
|
||||
"用户要找某个 AI 表格/多维表,按名称检索时优先使用"
|
||||
"需要 Shortcut 未公开的底层参数、原始 search_bases 响应或自定义候选处理时"
|
||||
]
|
||||
},
|
||||
"aitable.base_update": {
|
||||
@@ -23545,13 +23547,15 @@
|
||||
"internal/cli/schema_hints/selection/aitable.json",
|
||||
"internal/cli/schema_mcp_metadata.json#tools.aitable.field_get",
|
||||
"skills/mono/references/products/aitable.md",
|
||||
"skills/mono/references/products/aitable/aitable-field.md"
|
||||
"skills/mono/references/products/aitable/aitable-field.md",
|
||||
"skills/multi/dingtalk-aitable/SKILL.md",
|
||||
"skills/multi/dingtalk-aitable/references/aitable/aitable-field.md"
|
||||
],
|
||||
"agent_summary": "获取字段完整配置。",
|
||||
"agent_summary": "底层获取字段完整类型与 config。",
|
||||
"agent_summary_source": "dws-agent-selection/aitable",
|
||||
"availability": "available",
|
||||
"avoid_when": [
|
||||
"表级字段目录也可用 table get;创建字段用 field create;不可用本命令改类型"
|
||||
"正常按字段 ID 展开类型/config 优先用 +field-get;精简字段目录用 +table-get;创建字段用 field create"
|
||||
],
|
||||
"canonical_path": "aitable.field_get",
|
||||
"cli_name": "get",
|
||||
@@ -23572,14 +23576,14 @@
|
||||
"review_reason": "人工结合实时 dws schema MCP 描述(或 helper 无 live 时用 Skill/Cobra)、兄弟命令分流与 Runtime 确认门禁审阅选型语义。",
|
||||
"selected": true,
|
||||
"source": "internal/cli/schema_hints/selection/aitable.json",
|
||||
"value": "获取字段完整配置。"
|
||||
"value": "底层获取字段完整类型与 config。"
|
||||
}
|
||||
],
|
||||
"precedence": "reviewed_explicit",
|
||||
"resolution": "highest_precedence",
|
||||
"review_reason": "人工结合实时 dws schema MCP 描述(或 helper 无 live 时用 Skill/Cobra)、兄弟命令分流与 Runtime 确认门禁审阅选型语义。",
|
||||
"source": "internal/cli/schema_hints/selection/aitable.json",
|
||||
"value": "获取字段完整配置。"
|
||||
"value": "底层获取字段完整类型与 config。"
|
||||
},
|
||||
"availability": {
|
||||
"candidates": [
|
||||
@@ -23611,7 +23615,7 @@
|
||||
"selected": true,
|
||||
"source": "internal/cli/schema_hints/selection/aitable.json",
|
||||
"value": [
|
||||
"表级字段目录也可用 table get;创建字段用 field create;不可用本命令改类型"
|
||||
"正常按字段 ID 展开类型/config 优先用 +field-get;精简字段目录用 +table-get;创建字段用 field create"
|
||||
]
|
||||
}
|
||||
],
|
||||
@@ -23620,7 +23624,7 @@
|
||||
"review_reason": "人工结合实时 dws schema MCP 描述(或 helper 无 live 时用 Skill/Cobra)、兄弟命令分流与 Runtime 确认门禁审阅选型语义。",
|
||||
"source": "internal/cli/schema_hints/selection/aitable.json",
|
||||
"value": [
|
||||
"表级字段目录也可用 table get;创建字段用 field create;不可用本命令改类型"
|
||||
"正常按字段 ID 展开类型/config 优先用 +field-get;精简字段目录用 +table-get;创建字段用 field create"
|
||||
]
|
||||
},
|
||||
"canonical_path": {
|
||||
@@ -23848,7 +23852,7 @@
|
||||
"selected": true,
|
||||
"source": "internal/cli/schema_hints/selection/aitable.json",
|
||||
"value": [
|
||||
"需要字段类型/config(选项、公式等)详情时"
|
||||
"需要 +field-get 未公开的底层参数、原始响应或不同执行语义时"
|
||||
]
|
||||
}
|
||||
],
|
||||
@@ -23857,7 +23861,7 @@
|
||||
"review_reason": "人工结合实时 dws schema MCP 描述(或 helper 无 live 时用 Skill/Cobra)、兄弟命令分流与 Runtime 确认门禁审阅选型语义。",
|
||||
"source": "internal/cli/schema_hints/selection/aitable.json",
|
||||
"value": [
|
||||
"需要字段类型/config(选项、公式等)详情时"
|
||||
"需要 +field-get 未公开的底层参数、原始响应或不同执行语义时"
|
||||
]
|
||||
}
|
||||
},
|
||||
@@ -24201,7 +24205,7 @@
|
||||
"source": "reviewed_command_registry",
|
||||
"title": "获取字段详情",
|
||||
"use_when": [
|
||||
"需要字段类型/config(选项、公式等)详情时"
|
||||
"需要 +field-get 未公开的底层参数、原始响应或不同执行语义时"
|
||||
]
|
||||
},
|
||||
"aitable.field_list": {
|
||||
@@ -38119,16 +38123,18 @@
|
||||
"internal/cli/schema_mcp_metadata.json#tools.aitable.query_records",
|
||||
"skills/mono/references/products/aitable-record-ops.md",
|
||||
"skills/mono/references/products/aitable.md",
|
||||
"skills/mono/references/products/aitable/aitable-record-query.md"
|
||||
"skills/mono/references/products/aitable/aitable-record-query.md",
|
||||
"skills/multi/dingtalk-aitable/SKILL.md",
|
||||
"skills/multi/dingtalk-aitable/references/aitable/aitable-record-query.md"
|
||||
],
|
||||
"agent_summary": "查询/搜索记录(filters/sort/分页/--all;cells 键为 fieldId)。",
|
||||
"agent_summary": "底层查询/搜索记录,额外支持原子命令的完整分页控制。",
|
||||
"agent_summary_source": "dws-agent-selection/aitable",
|
||||
"aliases": [
|
||||
"aitable record list"
|
||||
],
|
||||
"availability": "available",
|
||||
"avoid_when": [
|
||||
"已知 recordId 窄查可用 record get;空行用 query-empty;写入用 create/update;电子表格单元格用 sheet"
|
||||
"普通按 ID、关键词、filters、sort 或 cursor 查询优先用 +record-query;空行用 +record-query-empty;写入用 create/update;电子表格单元格用 sheet"
|
||||
],
|
||||
"canonical_path": "aitable.query_records",
|
||||
"cli_name": "query",
|
||||
@@ -38149,14 +38155,14 @@
|
||||
"review_reason": "人工结合实时 dws schema MCP 描述(或 helper 无 live 时用 Skill/Cobra)、兄弟命令分流与 Runtime 确认门禁审阅选型语义。",
|
||||
"selected": true,
|
||||
"source": "internal/cli/schema_hints/selection/aitable.json",
|
||||
"value": "查询/搜索记录(filters/sort/分页/--all;cells 键为 fieldId)。"
|
||||
"value": "底层查询/搜索记录,额外支持原子命令的完整分页控制。"
|
||||
}
|
||||
],
|
||||
"precedence": "reviewed_explicit",
|
||||
"resolution": "highest_precedence",
|
||||
"review_reason": "人工结合实时 dws schema MCP 描述(或 helper 无 live 时用 Skill/Cobra)、兄弟命令分流与 Runtime 确认门禁审阅选型语义。",
|
||||
"source": "internal/cli/schema_hints/selection/aitable.json",
|
||||
"value": "查询/搜索记录(filters/sort/分页/--all;cells 键为 fieldId)。"
|
||||
"value": "底层查询/搜索记录,额外支持原子命令的完整分页控制。"
|
||||
},
|
||||
"availability": {
|
||||
"candidates": [
|
||||
@@ -38188,7 +38194,7 @@
|
||||
"selected": true,
|
||||
"source": "internal/cli/schema_hints/selection/aitable.json",
|
||||
"value": [
|
||||
"已知 recordId 窄查可用 record get;空行用 query-empty;写入用 create/update;电子表格单元格用 sheet"
|
||||
"普通按 ID、关键词、filters、sort 或 cursor 查询优先用 +record-query;空行用 +record-query-empty;写入用 create/update;电子表格单元格用 sheet"
|
||||
]
|
||||
}
|
||||
],
|
||||
@@ -38197,7 +38203,7 @@
|
||||
"review_reason": "人工结合实时 dws schema MCP 描述(或 helper 无 live 时用 Skill/Cobra)、兄弟命令分流与 Runtime 确认门禁审阅选型语义。",
|
||||
"source": "internal/cli/schema_hints/selection/aitable.json",
|
||||
"value": [
|
||||
"已知 recordId 窄查可用 record get;空行用 query-empty;写入用 create/update;电子表格单元格用 sheet"
|
||||
"普通按 ID、关键词、filters、sort 或 cursor 查询优先用 +record-query;空行用 +record-query-empty;写入用 create/update;电子表格单元格用 sheet"
|
||||
]
|
||||
},
|
||||
"canonical_path": {
|
||||
@@ -38447,7 +38453,7 @@
|
||||
"selected": true,
|
||||
"source": "internal/cli/schema_hints/selection/aitable.json",
|
||||
"value": [
|
||||
"查看、筛选、全文搜索或遍历记录时的主入口"
|
||||
"需要 +record-query 未公开的 --all/--page-limit、底层原始响应或不同执行语义时"
|
||||
]
|
||||
}
|
||||
],
|
||||
@@ -38456,7 +38462,7 @@
|
||||
"review_reason": "人工结合实时 dws schema MCP 描述(或 helper 无 live 时用 Skill/Cobra)、兄弟命令分流与 Runtime 确认门禁审阅选型语义。",
|
||||
"source": "internal/cli/schema_hints/selection/aitable.json",
|
||||
"value": [
|
||||
"查看、筛选、全文搜索或遍历记录时的主入口"
|
||||
"需要 +record-query 未公开的 --all/--page-limit、底层原始响应或不同执行语义时"
|
||||
]
|
||||
}
|
||||
},
|
||||
@@ -39699,7 +39705,7 @@
|
||||
"source": "reviewed_command_registry",
|
||||
"title": "获取行记录",
|
||||
"use_when": [
|
||||
"查看、筛选、全文搜索或遍历记录时的主入口"
|
||||
"需要 +record-query 未公开的 --all/--page-limit、底层原始响应或不同执行语义时"
|
||||
]
|
||||
},
|
||||
"aitable.record_batch_update": {
|
||||
@@ -40397,13 +40403,15 @@
|
||||
"skills/mono/references/products/aitable-record-ops.md",
|
||||
"skills/mono/references/products/aitable.md",
|
||||
"skills/mono/references/products/aitable/aitable-attachment.md",
|
||||
"skills/mono/references/products/aitable/aitable-record-create.md"
|
||||
"skills/mono/references/products/aitable/aitable-record-create.md",
|
||||
"skills/multi/dingtalk-aitable/SKILL.md",
|
||||
"skills/multi/dingtalk-aitable/references/aitable/aitable-record-create.md"
|
||||
],
|
||||
"agent_summary": "新增记录(cells 的 key 必须是 fieldId)。",
|
||||
"agent_summary": "新增记录;使用 fieldId 写入并从 newRecordIds 回读验证。",
|
||||
"agent_summary_source": "dws-agent-selection/aitable",
|
||||
"availability": "available",
|
||||
"avoid_when": [
|
||||
"更新已有行用 record update;有则更无则增用 upsert;批量同 patch 用 batch-update"
|
||||
"未核对字段类型时先 field get;更新已有行用 record update;有则更无则增用 upsert;本地 CSV/JSON 批量追加可用已评审脚本"
|
||||
],
|
||||
"canonical_path": "aitable.record_create",
|
||||
"cli_name": "create",
|
||||
@@ -40424,14 +40432,14 @@
|
||||
"review_reason": "人工结合实时 dws schema MCP 描述(或 helper 无 live 时用 Skill/Cobra)、兄弟命令分流与 Runtime 确认门禁审阅选型语义。",
|
||||
"selected": true,
|
||||
"source": "internal/cli/schema_hints/selection/aitable.json",
|
||||
"value": "新增记录(cells 的 key 必须是 fieldId)。"
|
||||
"value": "新增记录;使用 fieldId 写入并从 newRecordIds 回读验证。"
|
||||
}
|
||||
],
|
||||
"precedence": "reviewed_explicit",
|
||||
"resolution": "highest_precedence",
|
||||
"review_reason": "人工结合实时 dws schema MCP 描述(或 helper 无 live 时用 Skill/Cobra)、兄弟命令分流与 Runtime 确认门禁审阅选型语义。",
|
||||
"source": "internal/cli/schema_hints/selection/aitable.json",
|
||||
"value": "新增记录(cells 的 key 必须是 fieldId)。"
|
||||
"value": "新增记录;使用 fieldId 写入并从 newRecordIds 回读验证。"
|
||||
},
|
||||
"availability": {
|
||||
"candidates": [
|
||||
@@ -40463,7 +40471,7 @@
|
||||
"selected": true,
|
||||
"source": "internal/cli/schema_hints/selection/aitable.json",
|
||||
"value": [
|
||||
"更新已有行用 record update;有则更无则增用 upsert;批量同 patch 用 batch-update"
|
||||
"未核对字段类型时先 field get;更新已有行用 record update;有则更无则增用 upsert;本地 CSV/JSON 批量追加可用已评审脚本"
|
||||
]
|
||||
}
|
||||
],
|
||||
@@ -40472,7 +40480,7 @@
|
||||
"review_reason": "人工结合实时 dws schema MCP 描述(或 helper 无 live 时用 Skill/Cobra)、兄弟命令分流与 Runtime 确认门禁审阅选型语义。",
|
||||
"source": "internal/cli/schema_hints/selection/aitable.json",
|
||||
"value": [
|
||||
"更新已有行用 record update;有则更无则增用 upsert;批量同 patch 用 batch-update"
|
||||
"未核对字段类型时先 field get;更新已有行用 record update;有则更无则增用 upsert;本地 CSV/JSON 批量追加可用已评审脚本"
|
||||
]
|
||||
},
|
||||
"canonical_path": {
|
||||
@@ -40700,7 +40708,7 @@
|
||||
"selected": true,
|
||||
"source": "internal/cli/schema_hints/selection/aitable.json",
|
||||
"value": [
|
||||
"需要插入新行数据时"
|
||||
"已取得 baseId/tableId 与字段完整配置,需要插入一条或多条新记录并回读时"
|
||||
]
|
||||
}
|
||||
],
|
||||
@@ -40709,7 +40717,7 @@
|
||||
"review_reason": "人工结合实时 dws schema MCP 描述(或 helper 无 live 时用 Skill/Cobra)、兄弟命令分流与 Runtime 确认门禁审阅选型语义。",
|
||||
"source": "internal/cli/schema_hints/selection/aitable.json",
|
||||
"value": [
|
||||
"需要插入新行数据时"
|
||||
"已取得 baseId/tableId 与字段完整配置,需要插入一条或多条新记录并回读时"
|
||||
]
|
||||
}
|
||||
},
|
||||
@@ -41156,7 +41164,7 @@
|
||||
"source": "reviewed_command_registry",
|
||||
"title": "新增记录",
|
||||
"use_when": [
|
||||
"需要插入新行数据时"
|
||||
"已取得 baseId/tableId 与字段完整配置,需要插入一条或多条新记录并回读时"
|
||||
]
|
||||
},
|
||||
"aitable.record_delete": {
|
||||
@@ -46549,13 +46557,15 @@
|
||||
"internal/cli/schema_mcp_metadata.json#tools.aitable.record_update",
|
||||
"skills/mono/references/products/aitable-record-ops.md",
|
||||
"skills/mono/references/products/aitable.md",
|
||||
"skills/mono/references/products/aitable/aitable-attachment.md"
|
||||
"skills/mono/references/products/aitable/aitable-attachment.md",
|
||||
"skills/multi/dingtalk-aitable/SKILL.md",
|
||||
"skills/multi/dingtalk-aitable/references/aitable/aitable-record-update.md"
|
||||
],
|
||||
"agent_summary": "更新已有记录字段(先 query 拿 recordId;只传需改字段)。",
|
||||
"agent_summary": "更新已有记录字段;先 query 拿 recordId,使用 fieldId 写入并回读。",
|
||||
"agent_summary_source": "dws-agent-selection/aitable",
|
||||
"availability": "available",
|
||||
"avoid_when": [
|
||||
"新建用 create;多条共用同一 cells patch 用 batch-update;混合创建/更新用 upsert"
|
||||
"未定位 recordId 或字段配置时先 query/field get;新建用 create;多条共用同一 cells patch 用 batch-update;混合创建/更新用 upsert"
|
||||
],
|
||||
"canonical_path": "aitable.record_update",
|
||||
"cli_name": "update",
|
||||
@@ -46576,14 +46586,14 @@
|
||||
"review_reason": "人工结合实时 dws schema MCP 描述(或 helper 无 live 时用 Skill/Cobra)、兄弟命令分流与 Runtime 确认门禁审阅选型语义。",
|
||||
"selected": true,
|
||||
"source": "internal/cli/schema_hints/selection/aitable.json",
|
||||
"value": "更新已有记录字段(先 query 拿 recordId;只传需改字段)。"
|
||||
"value": "更新已有记录字段;先 query 拿 recordId,使用 fieldId 写入并回读。"
|
||||
}
|
||||
],
|
||||
"precedence": "reviewed_explicit",
|
||||
"resolution": "highest_precedence",
|
||||
"review_reason": "人工结合实时 dws schema MCP 描述(或 helper 无 live 时用 Skill/Cobra)、兄弟命令分流与 Runtime 确认门禁审阅选型语义。",
|
||||
"source": "internal/cli/schema_hints/selection/aitable.json",
|
||||
"value": "更新已有记录字段(先 query 拿 recordId;只传需改字段)。"
|
||||
"value": "更新已有记录字段;先 query 拿 recordId,使用 fieldId 写入并回读。"
|
||||
},
|
||||
"availability": {
|
||||
"candidates": [
|
||||
@@ -46615,7 +46625,7 @@
|
||||
"selected": true,
|
||||
"source": "internal/cli/schema_hints/selection/aitable.json",
|
||||
"value": [
|
||||
"新建用 create;多条共用同一 cells patch 用 batch-update;混合创建/更新用 upsert"
|
||||
"未定位 recordId 或字段配置时先 query/field get;新建用 create;多条共用同一 cells patch 用 batch-update;混合创建/更新用 upsert"
|
||||
]
|
||||
}
|
||||
],
|
||||
@@ -46624,7 +46634,7 @@
|
||||
"review_reason": "人工结合实时 dws schema MCP 描述(或 helper 无 live 时用 Skill/Cobra)、兄弟命令分流与 Runtime 确认门禁审阅选型语义。",
|
||||
"source": "internal/cli/schema_hints/selection/aitable.json",
|
||||
"value": [
|
||||
"新建用 create;多条共用同一 cells patch 用 batch-update;混合创建/更新用 upsert"
|
||||
"未定位 recordId 或字段配置时先 query/field get;新建用 create;多条共用同一 cells patch 用 batch-update;混合创建/更新用 upsert"
|
||||
]
|
||||
},
|
||||
"canonical_path": {
|
||||
@@ -46852,7 +46862,7 @@
|
||||
"selected": true,
|
||||
"source": "internal/cli/schema_hints/selection/aitable.json",
|
||||
"value": [
|
||||
"需要修改已有记录若干字段时"
|
||||
"已查询得到真实 recordId 并核对字段类型,需要修改每条记录各自的 cells 时"
|
||||
]
|
||||
}
|
||||
],
|
||||
@@ -46861,7 +46871,7 @@
|
||||
"review_reason": "人工结合实时 dws schema MCP 描述(或 helper 无 live 时用 Skill/Cobra)、兄弟命令分流与 Runtime 确认门禁审阅选型语义。",
|
||||
"source": "internal/cli/schema_hints/selection/aitable.json",
|
||||
"value": [
|
||||
"需要修改已有记录若干字段时"
|
||||
"已查询得到真实 recordId 并核对字段类型,需要修改每条记录各自的 cells 时"
|
||||
]
|
||||
}
|
||||
},
|
||||
@@ -47308,7 +47318,7 @@
|
||||
"source": "reviewed_command_registry",
|
||||
"title": "更新记录",
|
||||
"use_when": [
|
||||
"需要修改已有记录若干字段时"
|
||||
"已查询得到真实 recordId 并核对字段类型,需要修改每条记录各自的 cells 时"
|
||||
]
|
||||
},
|
||||
"aitable.record_upsert": {
|
||||
@@ -53447,7 +53457,7 @@
|
||||
"agent_summary_source": "dws-agent-selection/aitable",
|
||||
"availability": "available",
|
||||
"avoid_when": [
|
||||
"需要该 Shortcut 未公开的底层参数、原始响应或不同执行语义时,改用对应原子命令"
|
||||
"知道名称并要解析唯一 baseId 用 +resolve-base;按关键词要完整候选用 +base-search;不要把最近访问列表描述成全部 Base"
|
||||
],
|
||||
"canonical_path": "aitable.shortcut_base_list",
|
||||
"cli_name": "+base-list",
|
||||
@@ -53502,7 +53512,7 @@
|
||||
"selected": true,
|
||||
"source": "internal/cli/schema_hints/selection/aitable.json",
|
||||
"value": [
|
||||
"需要该 Shortcut 未公开的底层参数、原始响应或不同执行语义时,改用对应原子命令"
|
||||
"知道名称并要解析唯一 baseId 用 +resolve-base;按关键词要完整候选用 +base-search;不要把最近访问列表描述成全部 Base"
|
||||
]
|
||||
}
|
||||
],
|
||||
@@ -53511,7 +53521,7 @@
|
||||
"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": "internal/cli/schema_hints/selection/aitable.json",
|
||||
"value": [
|
||||
"需要该 Shortcut 未公开的底层参数、原始响应或不同执行语义时,改用对应原子命令"
|
||||
"知道名称并要解析唯一 baseId 用 +resolve-base;按关键词要完整候选用 +base-search;不要把最近访问列表描述成全部 Base"
|
||||
]
|
||||
},
|
||||
"canonical_path": {
|
||||
@@ -53943,7 +53953,7 @@
|
||||
"agent_summary_source": "dws-agent-selection/aitable",
|
||||
"availability": "available",
|
||||
"avoid_when": [
|
||||
"需要该 Shortcut 未公开的底层参数、原始响应或不同执行语义时,改用对应原子命令"
|
||||
"只需唯一 baseId 用 +resolve-base;浏览最近访问用 +base-list;需要未公开底层参数或原始响应才用 base search"
|
||||
],
|
||||
"canonical_path": "aitable.shortcut_base_search",
|
||||
"cli_name": "+base-search",
|
||||
@@ -53997,7 +54007,7 @@
|
||||
"selected": true,
|
||||
"source": "internal/cli/schema_hints/selection/aitable.json",
|
||||
"value": [
|
||||
"需要该 Shortcut 未公开的底层参数、原始响应或不同执行语义时,改用对应原子命令"
|
||||
"只需唯一 baseId 用 +resolve-base;浏览最近访问用 +base-list;需要未公开底层参数或原始响应才用 base search"
|
||||
]
|
||||
}
|
||||
],
|
||||
@@ -54006,7 +54016,7 @@
|
||||
"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": "internal/cli/schema_hints/selection/aitable.json",
|
||||
"value": [
|
||||
"需要该 Shortcut 未公开的底层参数、原始响应或不同执行语义时,改用对应原子命令"
|
||||
"只需唯一 baseId 用 +resolve-base;浏览最近访问用 +base-list;需要未公开底层参数或原始响应才用 base search"
|
||||
]
|
||||
},
|
||||
"canonical_path": {
|
||||
@@ -56196,7 +56206,7 @@
|
||||
"agent_summary_source": "dws-agent-selection/aitable",
|
||||
"availability": "available",
|
||||
"avoid_when": [
|
||||
"需要该 Shortcut 未公开的底层参数、原始响应或不同执行语义时,改用对应原子命令"
|
||||
"只需精简 fieldId/type 目录用 +table-get;创建或修改字段用 field create/update;需要未公开底层语义才用 field get"
|
||||
],
|
||||
"canonical_path": "aitable.shortcut_field_get",
|
||||
"cli_name": "+field-get",
|
||||
@@ -56250,7 +56260,7 @@
|
||||
"selected": true,
|
||||
"source": "internal/cli/schema_hints/selection/aitable.json",
|
||||
"value": [
|
||||
"需要该 Shortcut 未公开的底层参数、原始响应或不同执行语义时,改用对应原子命令"
|
||||
"只需精简 fieldId/type 目录用 +table-get;创建或修改字段用 field create/update;需要未公开底层语义才用 field get"
|
||||
]
|
||||
}
|
||||
],
|
||||
@@ -56259,7 +56269,7 @@
|
||||
"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": "internal/cli/schema_hints/selection/aitable.json",
|
||||
"value": [
|
||||
"需要该 Shortcut 未公开的底层参数、原始响应或不同执行语义时,改用对应原子命令"
|
||||
"只需精简 fieldId/type 目录用 +table-get;创建或修改字段用 field create/update;需要未公开底层语义才用 field get"
|
||||
]
|
||||
},
|
||||
"canonical_path": {
|
||||
@@ -60316,7 +60326,7 @@
|
||||
"agent_summary_source": "dws-agent-selection/aitable",
|
||||
"availability": "available",
|
||||
"avoid_when": [
|
||||
"需要该 Shortcut 未公开的底层参数、原始响应或不同执行语义时,改用对应原子命令"
|
||||
"完全空行扫描用 +record-query-empty;变更历史用 +record-history-list;需要 --all/--page-limit 或未公开底层语义才用 record query"
|
||||
],
|
||||
"canonical_path": "aitable.shortcut_record_query",
|
||||
"cli_name": "+record-query",
|
||||
@@ -60370,7 +60380,7 @@
|
||||
"selected": true,
|
||||
"source": "internal/cli/schema_hints/selection/aitable.json",
|
||||
"value": [
|
||||
"需要该 Shortcut 未公开的底层参数、原始响应或不同执行语义时,改用对应原子命令"
|
||||
"完全空行扫描用 +record-query-empty;变更历史用 +record-history-list;需要 --all/--page-limit 或未公开底层语义才用 record query"
|
||||
]
|
||||
}
|
||||
],
|
||||
@@ -60379,7 +60389,7 @@
|
||||
"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": "internal/cli/schema_hints/selection/aitable.json",
|
||||
"value": [
|
||||
"需要该 Shortcut 未公开的底层参数、原始响应或不同执行语义时,改用对应原子命令"
|
||||
"完全空行扫描用 +record-query-empty;变更历史用 +record-history-list;需要 --all/--page-limit 或未公开底层语义才用 record query"
|
||||
]
|
||||
},
|
||||
"canonical_path": {
|
||||
@@ -63485,7 +63495,7 @@
|
||||
"agent_summary_source": "dws-agent-selection/aitable",
|
||||
"availability": "available",
|
||||
"avoid_when": [
|
||||
"需要该 Shortcut 未公开的底层参数、原始响应或不同执行语义时,改用对应原子命令"
|
||||
"多候选时必须让用户消歧;只要完整候选列表用 +base-search;浏览最近访问用 +base-list;已知 baseId 不再重复搜索"
|
||||
],
|
||||
"canonical_path": "aitable.shortcut_resolve_base",
|
||||
"cli_name": "+resolve-base",
|
||||
@@ -63539,7 +63549,7 @@
|
||||
"selected": true,
|
||||
"source": "internal/cli/schema_hints/selection/aitable.json",
|
||||
"value": [
|
||||
"需要该 Shortcut 未公开的底层参数、原始响应或不同执行语义时,改用对应原子命令"
|
||||
"多候选时必须让用户消歧;只要完整候选列表用 +base-search;浏览最近访问用 +base-list;已知 baseId 不再重复搜索"
|
||||
]
|
||||
}
|
||||
],
|
||||
@@ -63548,7 +63558,7 @@
|
||||
"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": "internal/cli/schema_hints/selection/aitable.json",
|
||||
"value": [
|
||||
"需要该 Shortcut 未公开的底层参数、原始响应或不同执行语义时,改用对应原子命令"
|
||||
"多候选时必须让用户消歧;只要完整候选列表用 +base-search;浏览最近访问用 +base-list;已知 baseId 不再重复搜索"
|
||||
]
|
||||
},
|
||||
"canonical_path": {
|
||||
@@ -63758,7 +63768,7 @@
|
||||
"selected": true,
|
||||
"source": "internal/cli/schema_hints/selection/aitable.json",
|
||||
"value": [
|
||||
"当你只知道某个多维表 Base 的名称(或名称里的关键词)、想把它解析成可直接用于后续工具的 baseId 时使用;内部按 --name 关键词调用 search_bases 搜索 Base,再在本地投影出每个候选的 baseId 和 name。如果只命中一个 Base 就直接返回它的 baseId;如果命中多个则列出全部候选让你消歧,绝不替你瞎猜;如果一个都没命中则提示未找到。这是纯只读操作,只做搜索与本地投影,不会修改任何 Base。"
|
||||
"只知道 Base 名称或关键词,需要解析成唯一 baseId 供后续 Table/Record 操作时"
|
||||
]
|
||||
}
|
||||
],
|
||||
@@ -63767,7 +63777,7 @@
|
||||
"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": "internal/cli/schema_hints/selection/aitable.json",
|
||||
"value": [
|
||||
"当你只知道某个多维表 Base 的名称(或名称里的关键词)、想把它解析成可直接用于后续工具的 baseId 时使用;内部按 --name 关键词调用 search_bases 搜索 Base,再在本地投影出每个候选的 baseId 和 name。如果只命中一个 Base 就直接返回它的 baseId;如果命中多个则列出全部候选让你消歧,绝不替你瞎猜;如果一个都没命中则提示未找到。这是纯只读操作,只做搜索与本地投影,不会修改任何 Base。"
|
||||
"只知道 Base 名称或关键词,需要解析成唯一 baseId 供后续 Table/Record 操作时"
|
||||
]
|
||||
}
|
||||
},
|
||||
@@ -63884,7 +63894,7 @@
|
||||
"source": "reviewed_command_registry",
|
||||
"title": "按名称搜索多维表 Base 并解析出唯一 baseId(只读)",
|
||||
"use_when": [
|
||||
"当你只知道某个多维表 Base 的名称(或名称里的关键词)、想把它解析成可直接用于后续工具的 baseId 时使用;内部按 --name 关键词调用 search_bases 搜索 Base,再在本地投影出每个候选的 baseId 和 name。如果只命中一个 Base 就直接返回它的 baseId;如果命中多个则列出全部候选让你消歧,绝不替你瞎猜;如果一个都没命中则提示未找到。这是纯只读操作,只做搜索与本地投影,不会修改任何 Base。"
|
||||
"只知道 Base 名称或关键词,需要解析成唯一 baseId 供后续 Table/Record 操作时"
|
||||
]
|
||||
},
|
||||
"aitable.shortcut_resolve_table": {
|
||||
@@ -63900,7 +63910,7 @@
|
||||
"agent_summary_source": "dws-agent-selection/aitable",
|
||||
"availability": "available",
|
||||
"avoid_when": [
|
||||
"需要该 Shortcut 未公开的底层参数、原始响应或不同执行语义时,改用对应原子命令"
|
||||
"多候选时必须让用户消歧;需要所有表及字段/视图目录用 +table-get;已知 tableId 不再重复解析"
|
||||
],
|
||||
"canonical_path": "aitable.shortcut_resolve_table",
|
||||
"cli_name": "+resolve-table",
|
||||
@@ -63954,7 +63964,7 @@
|
||||
"selected": true,
|
||||
"source": "internal/cli/schema_hints/selection/aitable.json",
|
||||
"value": [
|
||||
"需要该 Shortcut 未公开的底层参数、原始响应或不同执行语义时,改用对应原子命令"
|
||||
"多候选时必须让用户消歧;需要所有表及字段/视图目录用 +table-get;已知 tableId 不再重复解析"
|
||||
]
|
||||
}
|
||||
],
|
||||
@@ -63963,7 +63973,7 @@
|
||||
"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": "internal/cli/schema_hints/selection/aitable.json",
|
||||
"value": [
|
||||
"需要该 Shortcut 未公开的底层参数、原始响应或不同执行语义时,改用对应原子命令"
|
||||
"多候选时必须让用户消歧;需要所有表及字段/视图目录用 +table-get;已知 tableId 不再重复解析"
|
||||
]
|
||||
},
|
||||
"canonical_path": {
|
||||
@@ -64173,7 +64183,7 @@
|
||||
"selected": true,
|
||||
"source": "internal/cli/schema_hints/selection/aitable.json",
|
||||
"value": [
|
||||
"当你已经知道某个多维表 Base 的 baseId、又只记得里面某张数据表(table)的名称或名称关键词、想把它解析成可直接用于后续工具的 tableId 时使用;内部先用 get_tables(只传 baseId)列出该 Base 下的全部数据表,再在本地把每张表投影成 tableId、name,并按 --name 关键词做大小写不敏感的包含匹配来筛选候选。如果只命中一张表就直接返回它的 tableId;如果命中多张则列出全部候选让你消歧,绝不替你瞎猜;如果一张都没命中则提示未找到。这是纯只读操作,只做列举、本地匹配与投影,不会创建、修改或删除任何数据表。"
|
||||
"已有真实 baseId、只知道数据表名称或关键词,需要解析成唯一 tableId 时"
|
||||
]
|
||||
}
|
||||
],
|
||||
@@ -64182,7 +64192,7 @@
|
||||
"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": "internal/cli/schema_hints/selection/aitable.json",
|
||||
"value": [
|
||||
"当你已经知道某个多维表 Base 的 baseId、又只记得里面某张数据表(table)的名称或名称关键词、想把它解析成可直接用于后续工具的 tableId 时使用;内部先用 get_tables(只传 baseId)列出该 Base 下的全部数据表,再在本地把每张表投影成 tableId、name,并按 --name 关键词做大小写不敏感的包含匹配来筛选候选。如果只命中一张表就直接返回它的 tableId;如果命中多张则列出全部候选让你消歧,绝不替你瞎猜;如果一张都没命中则提示未找到。这是纯只读操作,只做列举、本地匹配与投影,不会创建、修改或删除任何数据表。"
|
||||
"已有真实 baseId、只知道数据表名称或关键词,需要解析成唯一 tableId 时"
|
||||
]
|
||||
}
|
||||
},
|
||||
@@ -64395,7 +64405,7 @@
|
||||
"source": "reviewed_command_registry",
|
||||
"title": "在某个多维表 Base 内按名称解析出唯一的数据表 tableId(只读)",
|
||||
"use_when": [
|
||||
"当你已经知道某个多维表 Base 的 baseId、又只记得里面某张数据表(table)的名称或名称关键词、想把它解析成可直接用于后续工具的 tableId 时使用;内部先用 get_tables(只传 baseId)列出该 Base 下的全部数据表,再在本地把每张表投影成 tableId、name,并按 --name 关键词做大小写不敏感的包含匹配来筛选候选。如果只命中一张表就直接返回它的 tableId;如果命中多张则列出全部候选让你消歧,绝不替你瞎猜;如果一张都没命中则提示未找到。这是纯只读操作,只做列举、本地匹配与投影,不会创建、修改或删除任何数据表。"
|
||||
"已有真实 baseId、只知道数据表名称或关键词,需要解析成唯一 tableId 时"
|
||||
]
|
||||
},
|
||||
"aitable.shortcut_role_list": {
|
||||
@@ -65656,7 +65666,7 @@
|
||||
"agent_summary_source": "dws-agent-selection/aitable",
|
||||
"availability": "available",
|
||||
"avoid_when": [
|
||||
"需要该 Shortcut 未公开的底层参数、原始响应或不同执行语义时,改用对应原子命令"
|
||||
"只知道表名并要解析唯一 tableId 用 +resolve-table;需要字段完整 config 用 +field-get;需要未公开底层语义才用 table get"
|
||||
],
|
||||
"canonical_path": "aitable.shortcut_table_get",
|
||||
"cli_name": "+table-get",
|
||||
@@ -65711,7 +65721,7 @@
|
||||
"selected": true,
|
||||
"source": "internal/cli/schema_hints/selection/aitable.json",
|
||||
"value": [
|
||||
"需要该 Shortcut 未公开的底层参数、原始响应或不同执行语义时,改用对应原子命令"
|
||||
"只知道表名并要解析唯一 tableId 用 +resolve-table;需要字段完整 config 用 +field-get;需要未公开底层语义才用 table get"
|
||||
]
|
||||
}
|
||||
],
|
||||
@@ -65720,7 +65730,7 @@
|
||||
"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": "internal/cli/schema_hints/selection/aitable.json",
|
||||
"value": [
|
||||
"需要该 Shortcut 未公开的底层参数、原始响应或不同执行语义时,改用对应原子命令"
|
||||
"只知道表名并要解析唯一 tableId 用 +resolve-table;需要字段完整 config 用 +field-get;需要未公开底层语义才用 table get"
|
||||
]
|
||||
},
|
||||
"canonical_path": {
|
||||
@@ -70522,13 +70532,15 @@
|
||||
"skills/mono/references/products/aitable.md",
|
||||
"skills/mono/references/products/aitable/aitable-advperm.md",
|
||||
"skills/mono/references/products/aitable/aitable-primary-doc.md",
|
||||
"skills/mono/references/products/aitable/aitable-record-create.md"
|
||||
"skills/mono/references/products/aitable/aitable-record-create.md",
|
||||
"skills/multi/dingtalk-aitable/SKILL.md",
|
||||
"skills/multi/dingtalk-aitable/references/aitable.md"
|
||||
],
|
||||
"agent_summary": "获取数据表结构(字段+视图目录)。",
|
||||
"agent_summary": "底层获取数据表结构、精简字段目录与视图目录。",
|
||||
"agent_summary_source": "dws-agent-selection/aitable",
|
||||
"availability": "available",
|
||||
"avoid_when": [
|
||||
"只关心 Base 级 tables 列表可先 base get;字段完整配置也可用 field get"
|
||||
"正常获取表、精简字段和视图目录优先用 +table-get;按表名解析 ID 用 +resolve-table;完整字段 config 用 +field-get"
|
||||
],
|
||||
"canonical_path": "aitable.table_get",
|
||||
"cli_name": "get",
|
||||
@@ -70549,14 +70561,14 @@
|
||||
"review_reason": "人工结合实时 dws schema MCP 描述(或 helper 无 live 时用 Skill/Cobra)、兄弟命令分流与 Runtime 确认门禁审阅选型语义。",
|
||||
"selected": true,
|
||||
"source": "internal/cli/schema_hints/selection/aitable.json",
|
||||
"value": "获取数据表结构(字段+视图目录)。"
|
||||
"value": "底层获取数据表结构、精简字段目录与视图目录。"
|
||||
}
|
||||
],
|
||||
"precedence": "reviewed_explicit",
|
||||
"resolution": "highest_precedence",
|
||||
"review_reason": "人工结合实时 dws schema MCP 描述(或 helper 无 live 时用 Skill/Cobra)、兄弟命令分流与 Runtime 确认门禁审阅选型语义。",
|
||||
"source": "internal/cli/schema_hints/selection/aitable.json",
|
||||
"value": "获取数据表结构(字段+视图目录)。"
|
||||
"value": "底层获取数据表结构、精简字段目录与视图目录。"
|
||||
},
|
||||
"availability": {
|
||||
"candidates": [
|
||||
@@ -70588,7 +70600,7 @@
|
||||
"selected": true,
|
||||
"source": "internal/cli/schema_hints/selection/aitable.json",
|
||||
"value": [
|
||||
"只关心 Base 级 tables 列表可先 base get;字段完整配置也可用 field get"
|
||||
"正常获取表、精简字段和视图目录优先用 +table-get;按表名解析 ID 用 +resolve-table;完整字段 config 用 +field-get"
|
||||
]
|
||||
}
|
||||
],
|
||||
@@ -70597,7 +70609,7 @@
|
||||
"review_reason": "人工结合实时 dws schema MCP 描述(或 helper 无 live 时用 Skill/Cobra)、兄弟命令分流与 Runtime 确认门禁审阅选型语义。",
|
||||
"source": "internal/cli/schema_hints/selection/aitable.json",
|
||||
"value": [
|
||||
"只关心 Base 级 tables 列表可先 base get;字段完整配置也可用 field get"
|
||||
"正常获取表、精简字段和视图目录优先用 +table-get;按表名解析 ID 用 +resolve-table;完整字段 config 用 +field-get"
|
||||
]
|
||||
},
|
||||
"canonical_path": {
|
||||
@@ -70825,7 +70837,7 @@
|
||||
"selected": true,
|
||||
"source": "internal/cli/schema_hints/selection/aitable.json",
|
||||
"value": [
|
||||
"需要字段 fieldId 或视图目录以继续 record/field 操作时优先 table get"
|
||||
"需要 +table-get 未公开的底层参数、原始 get_tables 响应或不同执行语义时"
|
||||
]
|
||||
}
|
||||
],
|
||||
@@ -70834,7 +70846,7 @@
|
||||
"review_reason": "人工结合实时 dws schema MCP 描述(或 helper 无 live 时用 Skill/Cobra)、兄弟命令分流与 Runtime 确认门禁审阅选型语义。",
|
||||
"source": "internal/cli/schema_hints/selection/aitable.json",
|
||||
"value": [
|
||||
"需要字段 fieldId 或视图目录以继续 record/field 操作时优先 table get"
|
||||
"需要 +table-get 未公开的底层参数、原始 get_tables 响应或不同执行语义时"
|
||||
]
|
||||
}
|
||||
},
|
||||
@@ -71081,7 +71093,7 @@
|
||||
"source": "reviewed_command_registry",
|
||||
"title": "获取数据表",
|
||||
"use_when": [
|
||||
"需要字段 fieldId 或视图目录以继续 record/field 操作时优先 table get"
|
||||
"需要 +table-get 未公开的底层参数、原始 get_tables 响应或不同执行语义时"
|
||||
]
|
||||
},
|
||||
"aitable.table_list": {
|
||||
|
||||
@@ -1158,11 +1158,11 @@
|
||||
"skills/mono/SKILL.md",
|
||||
"skills/mono/references/products/chat.md"
|
||||
],
|
||||
"agent_summary": "给指定消息添加表情回应",
|
||||
"agent_summary": "使用同一会话中真实的 openMessageId 给指定消息添加表情回应",
|
||||
"agent_summary_source": "dws-agent-selection/chat",
|
||||
"availability": "available",
|
||||
"avoid_when": [
|
||||
"发送文本消息或文字表情时不要使用"
|
||||
"发送文本消息或文字表情时不要使用;不要把 openTaskId 当作 msg-id"
|
||||
],
|
||||
"canonical_path": "chat.add_emoji_reaction",
|
||||
"cli_name": "add-emoji",
|
||||
@@ -1193,14 +1193,14 @@
|
||||
"review_reason": "AI 生成并复核的 Agent 选择语义,依据精确 CommandRegistry identity、真实 Cobra help 与产品参考,不改写执行、安全或接口事实。",
|
||||
"selected": true,
|
||||
"source": "internal/cli/schema_hints/selection/chat.json",
|
||||
"value": "给指定消息添加表情回应"
|
||||
"value": "使用同一会话中真实的 openMessageId 给指定消息添加表情回应"
|
||||
}
|
||||
],
|
||||
"precedence": "reviewed_explicit",
|
||||
"resolution": "highest_precedence",
|
||||
"review_reason": "AI 生成并复核的 Agent 选择语义,依据精确 CommandRegistry identity、真实 Cobra help 与产品参考,不改写执行、安全或接口事实。",
|
||||
"source": "internal/cli/schema_hints/selection/chat.json",
|
||||
"value": "给指定消息添加表情回应"
|
||||
"value": "使用同一会话中真实的 openMessageId 给指定消息添加表情回应"
|
||||
},
|
||||
"availability": {
|
||||
"candidates": [
|
||||
@@ -1232,7 +1232,7 @@
|
||||
"selected": true,
|
||||
"source": "internal/cli/schema_hints/selection/chat.json",
|
||||
"value": [
|
||||
"发送文本消息或文字表情时不要使用"
|
||||
"发送文本消息或文字表情时不要使用;不要把 openTaskId 当作 msg-id"
|
||||
]
|
||||
}
|
||||
],
|
||||
@@ -1241,7 +1241,7 @@
|
||||
"review_reason": "AI 生成并复核的 Agent 选择语义,依据精确 CommandRegistry identity、真实 Cobra help 与产品参考,不改写执行、安全或接口事实。",
|
||||
"source": "internal/cli/schema_hints/selection/chat.json",
|
||||
"value": [
|
||||
"发送文本消息或文字表情时不要使用"
|
||||
"发送文本消息或文字表情时不要使用;不要把 openTaskId 当作 msg-id"
|
||||
]
|
||||
},
|
||||
"canonical_path": {
|
||||
@@ -1479,7 +1479,7 @@
|
||||
"selected": true,
|
||||
"source": "internal/cli/schema_hints/selection/chat.json",
|
||||
"value": [
|
||||
"需要对已有消息添加一个 emoji reaction 时"
|
||||
"需要对已有消息添加一个 emoji reaction 时;conversation-id 与 msg-id 必须来自同一条真实消息"
|
||||
]
|
||||
}
|
||||
],
|
||||
@@ -1488,7 +1488,7 @@
|
||||
"review_reason": "AI 生成并复核的 Agent 选择语义,依据精确 CommandRegistry identity、真实 Cobra help 与产品参考,不改写执行、安全或接口事实。",
|
||||
"source": "internal/cli/schema_hints/selection/chat.json",
|
||||
"value": [
|
||||
"需要对已有消息添加一个 emoji reaction 时"
|
||||
"需要对已有消息添加一个 emoji reaction 时;conversation-id 与 msg-id 必须来自同一条真实消息"
|
||||
]
|
||||
}
|
||||
},
|
||||
@@ -2220,7 +2220,7 @@
|
||||
"source": "reviewed_command_registry",
|
||||
"title": "对消息添加 emoji 表情回应",
|
||||
"use_when": [
|
||||
"需要对已有消息添加一个 emoji reaction 时"
|
||||
"需要对已有消息添加一个 emoji reaction 时;conversation-id 与 msg-id 必须来自同一条真实消息"
|
||||
]
|
||||
},
|
||||
"chat.add_group_member": {
|
||||
@@ -6372,7 +6372,7 @@
|
||||
"agent_summary_source": "dws-agent-selection/chat",
|
||||
"availability": "available",
|
||||
"avoid_when": [
|
||||
"管理员级群功能开关用 chat group update-settings"
|
||||
"管理员级群功能开关用 chat group update-settings;查询结果是当前用户视角,不代表群级设置"
|
||||
],
|
||||
"canonical_path": "chat.batch_query_group_chat_settings",
|
||||
"cli_name": "query",
|
||||
@@ -6426,7 +6426,7 @@
|
||||
"selected": true,
|
||||
"source": "internal/cli/schema_hints/selection/chat.json",
|
||||
"value": [
|
||||
"管理员级群功能开关用 chat group update-settings"
|
||||
"管理员级群功能开关用 chat group update-settings;查询结果是当前用户视角,不代表群级设置"
|
||||
]
|
||||
}
|
||||
],
|
||||
@@ -6435,7 +6435,7 @@
|
||||
"review_reason": "人工审阅:对照悟空 develop 同名命令实现、Cobra Long/Example 与 Runtime 确认门禁移植;不改变命令身份、参数契约或接口绑定。",
|
||||
"source": "internal/cli/schema_hints/selection/chat.json",
|
||||
"value": [
|
||||
"管理员级群功能开关用 chat group update-settings"
|
||||
"管理员级群功能开关用 chat group update-settings;查询结果是当前用户视角,不代表群级设置"
|
||||
]
|
||||
},
|
||||
"canonical_path": {
|
||||
@@ -6645,7 +6645,7 @@
|
||||
"selected": true,
|
||||
"source": "internal/cli/schema_hints/selection/chat.json",
|
||||
"value": [
|
||||
"用户说 看下这些群我的置顶和免打扰设置"
|
||||
"用户说看下这些群中我自己的置顶和免打扰设置;修改前先查询并保存原值用于恢复"
|
||||
]
|
||||
}
|
||||
],
|
||||
@@ -6654,7 +6654,7 @@
|
||||
"review_reason": "人工审阅:对照悟空 develop 同名命令实现、Cobra Long/Example 与 Runtime 确认门禁移植;不改变命令身份、参数契约或接口绑定。",
|
||||
"source": "internal/cli/schema_hints/selection/chat.json",
|
||||
"value": [
|
||||
"用户说 看下这些群我的置顶和免打扰设置"
|
||||
"用户说看下这些群中我自己的置顶和免打扰设置;修改前先查询并保存原值用于恢复"
|
||||
]
|
||||
}
|
||||
},
|
||||
@@ -6773,7 +6773,7 @@
|
||||
"source": "reviewed_command_registry",
|
||||
"title": "批量查询当前用户的群会话设置",
|
||||
"use_when": [
|
||||
"用户说 看下这些群我的置顶和免打扰设置"
|
||||
"用户说看下这些群中我自己的置顶和免打扰设置;修改前先查询并保存原值用于恢复"
|
||||
]
|
||||
},
|
||||
"chat.batch_update_group_chat_settings": {
|
||||
@@ -6789,7 +6789,7 @@
|
||||
"agent_summary_source": "dws-agent-selection/chat",
|
||||
"availability": "available",
|
||||
"avoid_when": [
|
||||
"单个群昵称优先 chat group update-nick"
|
||||
"单个群昵称优先 chat group update-nick;不要用本命令修改 searchable、@所有人权限等群级开关;临时测试结束时按修改前 query 的真实值恢复"
|
||||
],
|
||||
"canonical_path": "chat.batch_update_group_chat_settings",
|
||||
"cli_name": "set",
|
||||
@@ -6843,7 +6843,7 @@
|
||||
"selected": true,
|
||||
"source": "internal/cli/schema_hints/selection/chat.json",
|
||||
"value": [
|
||||
"单个群昵称优先 chat group update-nick"
|
||||
"单个群昵称优先 chat group update-nick;不要用本命令修改 searchable、@所有人权限等群级开关;临时测试结束时按修改前 query 的真实值恢复"
|
||||
]
|
||||
}
|
||||
],
|
||||
@@ -6852,7 +6852,7 @@
|
||||
"review_reason": "人工审阅:对照悟空 develop 同名命令实现、Cobra Long/Example 与 Runtime 确认门禁移植;不改变命令身份、参数契约或接口绑定。",
|
||||
"source": "internal/cli/schema_hints/selection/chat.json",
|
||||
"value": [
|
||||
"单个群昵称优先 chat group update-nick"
|
||||
"单个群昵称优先 chat group update-nick;不要用本命令修改 searchable、@所有人权限等群级开关;临时测试结束时按修改前 query 的真实值恢复"
|
||||
]
|
||||
},
|
||||
"canonical_path": {
|
||||
@@ -7062,7 +7062,7 @@
|
||||
"selected": true,
|
||||
"source": "internal/cli/schema_hints/selection/chat.json",
|
||||
"value": [
|
||||
"用户说 把这些群都设为免打扰/置顶"
|
||||
"用户说把这些群都设为免打扰或置顶;items 中只写需要改变的当前用户设置,完成后再次 query 验证"
|
||||
]
|
||||
}
|
||||
],
|
||||
@@ -7071,7 +7071,7 @@
|
||||
"review_reason": "人工审阅:对照悟空 develop 同名命令实现、Cobra Long/Example 与 Runtime 确认门禁移植;不改变命令身份、参数契约或接口绑定。",
|
||||
"source": "internal/cli/schema_hints/selection/chat.json",
|
||||
"value": [
|
||||
"用户说 把这些群都设为免打扰/置顶"
|
||||
"用户说把这些群都设为免打扰或置顶;items 中只写需要改变的当前用户设置,完成后再次 query 验证"
|
||||
]
|
||||
}
|
||||
},
|
||||
@@ -7190,7 +7190,7 @@
|
||||
"source": "reviewed_command_registry",
|
||||
"title": "批量更新当前用户的群会话设置",
|
||||
"use_when": [
|
||||
"用户说 把这些群都设为免打扰/置顶"
|
||||
"用户说把这些群都设为免打扰或置顶;items 中只写需要改变的当前用户设置,完成后再次 query 验证"
|
||||
]
|
||||
},
|
||||
"chat.clear_all_red_point": {
|
||||
@@ -38313,11 +38313,11 @@
|
||||
"skills/mono/references/intent-guide.md",
|
||||
"skills/mono/references/products/chat.md"
|
||||
],
|
||||
"agent_summary": "查询异步消息发送任务的状态",
|
||||
"agent_summary": "查询异步消息发送任务的业务终态,并按 sendStatus 判断是否真正投递成功",
|
||||
"agent_summary_source": "dws-agent-selection/chat",
|
||||
"availability": "available",
|
||||
"avoid_when": [
|
||||
"没有 message send 真实返回的 openTaskId 时不要使用;openMessageId 和会话 ID 都不能代替 openTaskId"
|
||||
"没有 message send 真实返回的 openTaskId 时不要使用;openMessageId 和会话 ID 都不能代替 openTaskId;FAILED 不得当作接口成功"
|
||||
],
|
||||
"canonical_path": "chat.query_message_send_status",
|
||||
"cli_name": "query-send-status",
|
||||
@@ -38338,14 +38338,14 @@
|
||||
"review_reason": "AI 生成并复核的 Agent 选择语义,依据精确 CommandRegistry identity、真实 Cobra help 与产品参考,不改写执行、安全或接口事实。",
|
||||
"selected": true,
|
||||
"source": "internal/cli/schema_hints/selection/chat.json",
|
||||
"value": "查询异步消息发送任务的状态"
|
||||
"value": "查询异步消息发送任务的业务终态,并按 sendStatus 判断是否真正投递成功"
|
||||
}
|
||||
],
|
||||
"precedence": "reviewed_explicit",
|
||||
"resolution": "highest_precedence",
|
||||
"review_reason": "AI 生成并复核的 Agent 选择语义,依据精确 CommandRegistry identity、真实 Cobra help 与产品参考,不改写执行、安全或接口事实。",
|
||||
"source": "internal/cli/schema_hints/selection/chat.json",
|
||||
"value": "查询异步消息发送任务的状态"
|
||||
"value": "查询异步消息发送任务的业务终态,并按 sendStatus 判断是否真正投递成功"
|
||||
},
|
||||
"availability": {
|
||||
"candidates": [
|
||||
@@ -38377,7 +38377,7 @@
|
||||
"selected": true,
|
||||
"source": "internal/cli/schema_hints/selection/chat.json",
|
||||
"value": [
|
||||
"没有 message send 真实返回的 openTaskId 时不要使用;openMessageId 和会话 ID 都不能代替 openTaskId"
|
||||
"没有 message send 真实返回的 openTaskId 时不要使用;openMessageId 和会话 ID 都不能代替 openTaskId;FAILED 不得当作接口成功"
|
||||
]
|
||||
}
|
||||
],
|
||||
@@ -38386,7 +38386,7 @@
|
||||
"review_reason": "AI 生成并复核的 Agent 选择语义,依据精确 CommandRegistry identity、真实 Cobra help 与产品参考,不改写执行、安全或接口事实。",
|
||||
"source": "internal/cli/schema_hints/selection/chat.json",
|
||||
"value": [
|
||||
"没有 message send 真实返回的 openTaskId 时不要使用;openMessageId 和会话 ID 都不能代替 openTaskId"
|
||||
"没有 message send 真实返回的 openTaskId 时不要使用;openMessageId 和会话 ID 都不能代替 openTaskId;FAILED 不得当作接口成功"
|
||||
]
|
||||
},
|
||||
"canonical_path": {
|
||||
@@ -38608,7 +38608,7 @@
|
||||
"selected": true,
|
||||
"source": "internal/cli/schema_hints/selection/chat.json",
|
||||
"value": [
|
||||
"发送命令返回 openTaskId 后需要确认投递结果时"
|
||||
"发送命令返回 openTaskId 后需要确认投递结果时;只有 sendStatus=SUCCESS 才能宣称消息发送成功"
|
||||
]
|
||||
}
|
||||
],
|
||||
@@ -38617,7 +38617,7 @@
|
||||
"review_reason": "AI 生成并复核的 Agent 选择语义,依据精确 CommandRegistry identity、真实 Cobra help 与产品参考,不改写执行、安全或接口事实。",
|
||||
"source": "internal/cli/schema_hints/selection/chat.json",
|
||||
"value": [
|
||||
"发送命令返回 openTaskId 后需要确认投递结果时"
|
||||
"发送命令返回 openTaskId 后需要确认投递结果时;只有 sendStatus=SUCCESS 才能宣称消息发送成功"
|
||||
]
|
||||
}
|
||||
},
|
||||
@@ -38767,7 +38767,7 @@
|
||||
"source": "reviewed_command_registry",
|
||||
"title": "查询消息发送状态",
|
||||
"use_when": [
|
||||
"发送命令返回 openTaskId 后需要确认投递结果时"
|
||||
"发送命令返回 openTaskId 后需要确认投递结果时;只有 sendStatus=SUCCESS 才能宣称消息发送成功"
|
||||
]
|
||||
},
|
||||
"chat.query_msg_read_status": {
|
||||
@@ -43352,11 +43352,11 @@
|
||||
"skills/mono/SKILL.md",
|
||||
"skills/mono/references/products/chat.md"
|
||||
],
|
||||
"agent_summary": "移除指定消息上的表情回应",
|
||||
"agent_summary": "从同一条真实消息移除此前添加的同名表情回应",
|
||||
"agent_summary_source": "dws-agent-selection/chat",
|
||||
"availability": "available",
|
||||
"avoid_when": [
|
||||
"移除文字表情时使用 chat message remove-text-emotion"
|
||||
"移除文字表情时使用 chat message remove-text-emotion;不要猜测新的消息 ID 或表情名称"
|
||||
],
|
||||
"canonical_path": "chat.remove_emoji_reaction",
|
||||
"cli_name": "remove-emoji",
|
||||
@@ -43387,14 +43387,14 @@
|
||||
"review_reason": "AI 生成并复核的 Agent 选择语义,依据精确 CommandRegistry identity、真实 Cobra help 与产品参考,不改写执行、安全或接口事实。",
|
||||
"selected": true,
|
||||
"source": "internal/cli/schema_hints/selection/chat.json",
|
||||
"value": "移除指定消息上的表情回应"
|
||||
"value": "从同一条真实消息移除此前添加的同名表情回应"
|
||||
}
|
||||
],
|
||||
"precedence": "reviewed_explicit",
|
||||
"resolution": "highest_precedence",
|
||||
"review_reason": "AI 生成并复核的 Agent 选择语义,依据精确 CommandRegistry identity、真实 Cobra help 与产品参考,不改写执行、安全或接口事实。",
|
||||
"source": "internal/cli/schema_hints/selection/chat.json",
|
||||
"value": "移除指定消息上的表情回应"
|
||||
"value": "从同一条真实消息移除此前添加的同名表情回应"
|
||||
},
|
||||
"availability": {
|
||||
"candidates": [
|
||||
@@ -43426,7 +43426,7 @@
|
||||
"selected": true,
|
||||
"source": "internal/cli/schema_hints/selection/chat.json",
|
||||
"value": [
|
||||
"移除文字表情时使用 chat message remove-text-emotion"
|
||||
"移除文字表情时使用 chat message remove-text-emotion;不要猜测新的消息 ID 或表情名称"
|
||||
]
|
||||
}
|
||||
],
|
||||
@@ -43435,7 +43435,7 @@
|
||||
"review_reason": "AI 生成并复核的 Agent 选择语义,依据精确 CommandRegistry identity、真实 Cobra help 与产品参考,不改写执行、安全或接口事实。",
|
||||
"source": "internal/cli/schema_hints/selection/chat.json",
|
||||
"value": [
|
||||
"移除文字表情时使用 chat message remove-text-emotion"
|
||||
"移除文字表情时使用 chat message remove-text-emotion;不要猜测新的消息 ID 或表情名称"
|
||||
]
|
||||
},
|
||||
"canonical_path": {
|
||||
@@ -43673,7 +43673,7 @@
|
||||
"selected": true,
|
||||
"source": "internal/cli/schema_hints/selection/chat.json",
|
||||
"value": [
|
||||
"需要取消此前添加的 emoji reaction 时"
|
||||
"需要取消此前添加的 emoji reaction 时;复用添加时的 conversation-id、msg-id 和 emoji"
|
||||
]
|
||||
}
|
||||
],
|
||||
@@ -43682,7 +43682,7 @@
|
||||
"review_reason": "AI 生成并复核的 Agent 选择语义,依据精确 CommandRegistry identity、真实 Cobra help 与产品参考,不改写执行、安全或接口事实。",
|
||||
"source": "internal/cli/schema_hints/selection/chat.json",
|
||||
"value": [
|
||||
"需要取消此前添加的 emoji reaction 时"
|
||||
"需要取消此前添加的 emoji reaction 时;复用添加时的 conversation-id、msg-id 和 emoji"
|
||||
]
|
||||
}
|
||||
},
|
||||
@@ -44414,7 +44414,7 @@
|
||||
"source": "reviewed_command_registry",
|
||||
"title": "移除消息的 emoji 表情回应",
|
||||
"use_when": [
|
||||
"需要取消此前添加的 emoji reaction 时"
|
||||
"需要取消此前添加的 emoji reaction 时;复用添加时的 conversation-id、msg-id 和 emoji"
|
||||
]
|
||||
},
|
||||
"chat.remove_group_member": {
|
||||
@@ -44435,7 +44435,7 @@
|
||||
"agent_summary_source": "dws-agent-selection/chat",
|
||||
"availability": "available",
|
||||
"avoid_when": [
|
||||
"移除机器人时使用 chat group members remove-bot"
|
||||
"移除机器人时使用 chat group members remove-bot;不要把建群者或群主加入 --users,也不要为了清理成员而转让群主"
|
||||
],
|
||||
"canonical_path": "chat.remove_group_member",
|
||||
"cli_name": "remove",
|
||||
@@ -44495,7 +44495,7 @@
|
||||
"selected": true,
|
||||
"source": "internal/cli/schema_hints/selection/chat.json",
|
||||
"value": [
|
||||
"移除机器人时使用 chat group members remove-bot"
|
||||
"移除机器人时使用 chat group members remove-bot;不要把建群者或群主加入 --users,也不要为了清理成员而转让群主"
|
||||
]
|
||||
}
|
||||
],
|
||||
@@ -44504,7 +44504,7 @@
|
||||
"review_reason": "AI 生成并复核的 Agent 选择语义,依据精确 CommandRegistry identity、真实 Cobra help 与产品参考,不改写执行、安全或接口事实。",
|
||||
"source": "internal/cli/schema_hints/selection/chat.json",
|
||||
"value": [
|
||||
"移除机器人时使用 chat group members remove-bot"
|
||||
"移除机器人时使用 chat group members remove-bot;不要把建群者或群主加入 --users,也不要为了清理成员而转让群主"
|
||||
]
|
||||
},
|
||||
"canonical_path": {
|
||||
@@ -44759,7 +44759,7 @@
|
||||
"selected": true,
|
||||
"source": "internal/cli/schema_hints/selection/chat.json",
|
||||
"value": [
|
||||
"群管理员明确要移除一个或多个普通成员时"
|
||||
"群管理员明确要移除一个或多个普通成员时;清理临时群只传本次加入的普通成员"
|
||||
]
|
||||
}
|
||||
],
|
||||
@@ -44768,7 +44768,7 @@
|
||||
"review_reason": "AI 生成并复核的 Agent 选择语义,依据精确 CommandRegistry identity、真实 Cobra help 与产品参考,不改写执行、安全或接口事实。",
|
||||
"source": "internal/cli/schema_hints/selection/chat.json",
|
||||
"value": [
|
||||
"群管理员明确要移除一个或多个普通成员时"
|
||||
"群管理员明确要移除一个或多个普通成员时;清理临时群只传本次加入的普通成员"
|
||||
]
|
||||
}
|
||||
},
|
||||
@@ -45075,7 +45075,7 @@
|
||||
"source": "reviewed_command_registry",
|
||||
"title": "移除群成员",
|
||||
"use_when": [
|
||||
"群管理员明确要移除一个或多个普通成员时"
|
||||
"群管理员明确要移除一个或多个普通成员时;清理临时群只传本次加入的普通成员"
|
||||
]
|
||||
},
|
||||
"chat.remove_message_favorite": {
|
||||
@@ -65014,11 +65014,11 @@
|
||||
"skills/mono/references/intent-guide.md",
|
||||
"skills/mono/references/products/chat.md"
|
||||
],
|
||||
"agent_summary": "把指定消息设为会话置顶消息",
|
||||
"agent_summary": "使用同源的 openConversationId 和 openMessageId 把指定消息设为 Pin",
|
||||
"agent_summary_source": "dws-agent-selection/chat",
|
||||
"availability": "available",
|
||||
"avoid_when": [
|
||||
"取消置顶使用 chat message unset-pin-msg"
|
||||
"取消 Pin 使用 chat message unset-pin-msg;置顶到会话顶部使用 set-top-msg,置顶整个会话使用 chat set-top"
|
||||
],
|
||||
"canonical_path": "chat.set_pin_message",
|
||||
"cli_name": "set-pin-msg",
|
||||
@@ -65039,14 +65039,14 @@
|
||||
"review_reason": "AI 生成并复核的 Agent 选择语义,依据精确 CommandRegistry identity、真实 Cobra help 与产品参考,不改写执行、安全或接口事实。",
|
||||
"selected": true,
|
||||
"source": "internal/cli/schema_hints/selection/chat.json",
|
||||
"value": "把指定消息设为会话置顶消息"
|
||||
"value": "使用同源的 openConversationId 和 openMessageId 把指定消息设为 Pin"
|
||||
}
|
||||
],
|
||||
"precedence": "reviewed_explicit",
|
||||
"resolution": "highest_precedence",
|
||||
"review_reason": "AI 生成并复核的 Agent 选择语义,依据精确 CommandRegistry identity、真实 Cobra help 与产品参考,不改写执行、安全或接口事实。",
|
||||
"source": "internal/cli/schema_hints/selection/chat.json",
|
||||
"value": "把指定消息设为会话置顶消息"
|
||||
"value": "使用同源的 openConversationId 和 openMessageId 把指定消息设为 Pin"
|
||||
},
|
||||
"availability": {
|
||||
"candidates": [
|
||||
@@ -65078,7 +65078,7 @@
|
||||
"selected": true,
|
||||
"source": "internal/cli/schema_hints/selection/chat.json",
|
||||
"value": [
|
||||
"取消置顶使用 chat message unset-pin-msg"
|
||||
"取消 Pin 使用 chat message unset-pin-msg;置顶到会话顶部使用 set-top-msg,置顶整个会话使用 chat set-top"
|
||||
]
|
||||
}
|
||||
],
|
||||
@@ -65087,7 +65087,7 @@
|
||||
"review_reason": "AI 生成并复核的 Agent 选择语义,依据精确 CommandRegistry identity、真实 Cobra help 与产品参考,不改写执行、安全或接口事实。",
|
||||
"source": "internal/cli/schema_hints/selection/chat.json",
|
||||
"value": [
|
||||
"取消置顶使用 chat message unset-pin-msg"
|
||||
"取消 Pin 使用 chat message unset-pin-msg;置顶到会话顶部使用 set-top-msg,置顶整个会话使用 chat set-top"
|
||||
]
|
||||
},
|
||||
"canonical_path": {
|
||||
@@ -65309,7 +65309,7 @@
|
||||
"selected": true,
|
||||
"source": "internal/cli/schema_hints/selection/chat.json",
|
||||
"value": [
|
||||
"需要在会话中置顶一条已知消息时"
|
||||
"需要在会话中钉住一条已知消息时;若发送只返回 openTaskId,先确认发送终态,再从消息查询结果取得真实 openMessageId"
|
||||
]
|
||||
}
|
||||
],
|
||||
@@ -65318,7 +65318,7 @@
|
||||
"review_reason": "AI 生成并复核的 Agent 选择语义,依据精确 CommandRegistry identity、真实 Cobra help 与产品参考,不改写执行、安全或接口事实。",
|
||||
"source": "internal/cli/schema_hints/selection/chat.json",
|
||||
"value": [
|
||||
"需要在会话中置顶一条已知消息时"
|
||||
"需要在会话中钉住一条已知消息时;若发送只返回 openTaskId,先确认发送终态,再从消息查询结果取得真实 openMessageId"
|
||||
]
|
||||
}
|
||||
},
|
||||
@@ -65598,7 +65598,7 @@
|
||||
"source": "reviewed_command_registry",
|
||||
"title": "钉住消息(Pin)",
|
||||
"use_when": [
|
||||
"需要在会话中置顶一条已知消息时"
|
||||
"需要在会话中钉住一条已知消息时;若发送只返回 openTaskId,先确认发送终态,再从消息查询结果取得真实 openMessageId"
|
||||
]
|
||||
},
|
||||
"chat.set_top_conversation": {
|
||||
@@ -87865,11 +87865,11 @@
|
||||
"internal/cli/schema_hints/metadata/chat.json",
|
||||
"internal/cli/schema_hints/selection/chat.json"
|
||||
],
|
||||
"agent_summary": "查询消息发送状态",
|
||||
"agent_summary": "查询消息发送任务的业务终态,只有 sendStatus=SUCCESS 才表示真正发送成功",
|
||||
"agent_summary_source": "dws-agent-selection/chat",
|
||||
"availability": "available",
|
||||
"avoid_when": [
|
||||
"需要该 Shortcut 未公开的底层参数、原始响应或不同执行语义时,改用对应原子命令"
|
||||
"需要该 Shortcut 未公开的底层参数、原始响应或不同执行语义时,改用对应原子命令;不要把 openTaskId 当 openMessageId 做 Pin、置顶或表情操作"
|
||||
],
|
||||
"canonical_path": "chat.shortcut_messages_query_send_status",
|
||||
"cli_name": "+messages-query-send-status",
|
||||
@@ -87890,14 +87890,14 @@
|
||||
"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.",
|
||||
"selected": true,
|
||||
"source": "internal/cli/schema_hints/selection/chat.json",
|
||||
"value": "查询消息发送状态"
|
||||
"value": "查询消息发送任务的业务终态,只有 sendStatus=SUCCESS 才表示真正发送成功"
|
||||
}
|
||||
],
|
||||
"precedence": "reviewed_explicit",
|
||||
"resolution": "highest_precedence",
|
||||
"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": "internal/cli/schema_hints/selection/chat.json",
|
||||
"value": "查询消息发送状态"
|
||||
"value": "查询消息发送任务的业务终态,只有 sendStatus=SUCCESS 才表示真正发送成功"
|
||||
},
|
||||
"availability": {
|
||||
"candidates": [
|
||||
@@ -87923,7 +87923,7 @@
|
||||
"selected": true,
|
||||
"source": "internal/cli/schema_hints/selection/chat.json",
|
||||
"value": [
|
||||
"需要该 Shortcut 未公开的底层参数、原始响应或不同执行语义时,改用对应原子命令"
|
||||
"需要该 Shortcut 未公开的底层参数、原始响应或不同执行语义时,改用对应原子命令;不要把 openTaskId 当 openMessageId 做 Pin、置顶或表情操作"
|
||||
]
|
||||
}
|
||||
],
|
||||
@@ -87932,7 +87932,7 @@
|
||||
"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": "internal/cli/schema_hints/selection/chat.json",
|
||||
"value": [
|
||||
"需要该 Shortcut 未公开的底层参数、原始响应或不同执行语义时,改用对应原子命令"
|
||||
"需要该 Shortcut 未公开的底层参数、原始响应或不同执行语义时,改用对应原子命令;不要把 openTaskId 当 openMessageId 做 Pin、置顶或表情操作"
|
||||
]
|
||||
},
|
||||
"canonical_path": {
|
||||
@@ -88142,7 +88142,7 @@
|
||||
"selected": true,
|
||||
"source": "internal/cli/schema_hints/selection/chat.json",
|
||||
"value": [
|
||||
"当你发消息后拿到 openTaskId、想确认这条消息是否发送成功时使用;只读返回发送状态,需传 --open-task-id。"
|
||||
"当你发消息后拿到 openTaskId、想确认这条消息是否发送成功时使用;只读返回发送状态,需传 --open-task-id;FAILED 必须如实报告,不能因外层 success=true 宣称成功。"
|
||||
]
|
||||
}
|
||||
],
|
||||
@@ -88151,7 +88151,7 @@
|
||||
"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": "internal/cli/schema_hints/selection/chat.json",
|
||||
"value": [
|
||||
"当你发消息后拿到 openTaskId、想确认这条消息是否发送成功时使用;只读返回发送状态,需传 --open-task-id。"
|
||||
"当你发消息后拿到 openTaskId、想确认这条消息是否发送成功时使用;只读返回发送状态,需传 --open-task-id;FAILED 必须如实报告,不能因外层 success=true 宣称成功。"
|
||||
]
|
||||
}
|
||||
},
|
||||
@@ -88268,7 +88268,7 @@
|
||||
"source": "reviewed_command_registry",
|
||||
"title": "查询消息发送状态",
|
||||
"use_when": [
|
||||
"当你发消息后拿到 openTaskId、想确认这条消息是否发送成功时使用;只读返回发送状态,需传 --open-task-id。"
|
||||
"当你发消息后拿到 openTaskId、想确认这条消息是否发送成功时使用;只读返回发送状态,需传 --open-task-id;FAILED 必须如实报告,不能因外层 success=true 宣称成功。"
|
||||
]
|
||||
},
|
||||
"chat.shortcut_messages_read_status": {
|
||||
@@ -101127,11 +101127,11 @@
|
||||
"skills/mono/references/intent-guide.md",
|
||||
"skills/mono/references/products/chat.md"
|
||||
],
|
||||
"agent_summary": "取消指定消息的会话置顶",
|
||||
"agent_summary": "使用设置 Pin 时的同源会话和消息 ID 取消指定消息的 Pin",
|
||||
"agent_summary_source": "dws-agent-selection/chat",
|
||||
"availability": "available",
|
||||
"avoid_when": [
|
||||
"新增置顶使用 chat message set-pin-msg"
|
||||
"新增 Pin 使用 chat message set-pin-msg;不要改用 unset-top-msg 或 chat set-top --off"
|
||||
],
|
||||
"canonical_path": "chat.unset_pin_message",
|
||||
"cli_name": "unset-pin-msg",
|
||||
@@ -101152,14 +101152,14 @@
|
||||
"review_reason": "AI 生成并复核的 Agent 选择语义,依据精确 CommandRegistry identity、真实 Cobra help 与产品参考,不改写执行、安全或接口事实。",
|
||||
"selected": true,
|
||||
"source": "internal/cli/schema_hints/selection/chat.json",
|
||||
"value": "取消指定消息的会话置顶"
|
||||
"value": "使用设置 Pin 时的同源会话和消息 ID 取消指定消息的 Pin"
|
||||
}
|
||||
],
|
||||
"precedence": "reviewed_explicit",
|
||||
"resolution": "highest_precedence",
|
||||
"review_reason": "AI 生成并复核的 Agent 选择语义,依据精确 CommandRegistry identity、真实 Cobra help 与产品参考,不改写执行、安全或接口事实。",
|
||||
"source": "internal/cli/schema_hints/selection/chat.json",
|
||||
"value": "取消指定消息的会话置顶"
|
||||
"value": "使用设置 Pin 时的同源会话和消息 ID 取消指定消息的 Pin"
|
||||
},
|
||||
"availability": {
|
||||
"candidates": [
|
||||
@@ -101191,7 +101191,7 @@
|
||||
"selected": true,
|
||||
"source": "internal/cli/schema_hints/selection/chat.json",
|
||||
"value": [
|
||||
"新增置顶使用 chat message set-pin-msg"
|
||||
"新增 Pin 使用 chat message set-pin-msg;不要改用 unset-top-msg 或 chat set-top --off"
|
||||
]
|
||||
}
|
||||
],
|
||||
@@ -101200,7 +101200,7 @@
|
||||
"review_reason": "AI 生成并复核的 Agent 选择语义,依据精确 CommandRegistry identity、真实 Cobra help 与产品参考,不改写执行、安全或接口事实。",
|
||||
"source": "internal/cli/schema_hints/selection/chat.json",
|
||||
"value": [
|
||||
"新增置顶使用 chat message set-pin-msg"
|
||||
"新增 Pin 使用 chat message set-pin-msg;不要改用 unset-top-msg 或 chat set-top --off"
|
||||
]
|
||||
},
|
||||
"canonical_path": {
|
||||
@@ -101422,7 +101422,7 @@
|
||||
"selected": true,
|
||||
"source": "internal/cli/schema_hints/selection/chat.json",
|
||||
"value": [
|
||||
"需要移除一条已知置顶消息时"
|
||||
"需要移除一条已知 Pin 消息时;复用 set-pin-msg 成功时的 openConversationId 和 openMessageId"
|
||||
]
|
||||
}
|
||||
],
|
||||
@@ -101431,7 +101431,7 @@
|
||||
"review_reason": "AI 生成并复核的 Agent 选择语义,依据精确 CommandRegistry identity、真实 Cobra help 与产品参考,不改写执行、安全或接口事实。",
|
||||
"source": "internal/cli/schema_hints/selection/chat.json",
|
||||
"value": [
|
||||
"需要移除一条已知置顶消息时"
|
||||
"需要移除一条已知 Pin 消息时;复用 set-pin-msg 成功时的 openConversationId 和 openMessageId"
|
||||
]
|
||||
}
|
||||
},
|
||||
@@ -101711,7 +101711,7 @@
|
||||
"source": "reviewed_command_registry",
|
||||
"title": "取消钉住消息(Unpin)",
|
||||
"use_when": [
|
||||
"需要移除一条已知置顶消息时"
|
||||
"需要移除一条已知 Pin 消息时;复用 set-pin-msg 成功时的 openConversationId 和 openMessageId"
|
||||
]
|
||||
},
|
||||
"chat.unset_top_message": {
|
||||
@@ -104378,7 +104378,7 @@
|
||||
"agent_summary_source": "dws-agent-selection/chat",
|
||||
"availability": "available",
|
||||
"avoid_when": [
|
||||
"没有真实可用 mediaId 时先完成媒体上传"
|
||||
"本地图片路径、钉盘 dentryId、聊天文件 uploadKey 都不能代替 icon-media-id;当前 CLI 无本地图片转群头像 mediaId 的能力,缺少 mediaId 时应如实报告能力边界"
|
||||
],
|
||||
"canonical_path": "chat.update_group_icon",
|
||||
"cli_name": "update-icon",
|
||||
@@ -104438,7 +104438,7 @@
|
||||
"selected": true,
|
||||
"source": "internal/cli/schema_hints/selection/chat.json",
|
||||
"value": [
|
||||
"没有真实可用 mediaId 时先完成媒体上传"
|
||||
"本地图片路径、钉盘 dentryId、聊天文件 uploadKey 都不能代替 icon-media-id;当前 CLI 无本地图片转群头像 mediaId 的能力,缺少 mediaId 时应如实报告能力边界"
|
||||
]
|
||||
}
|
||||
],
|
||||
@@ -104447,7 +104447,7 @@
|
||||
"review_reason": "AI 生成并复核的 Agent 选择语义,依据精确 CommandRegistry identity、真实 Cobra help 与产品参考,不改写执行、安全或接口事实。",
|
||||
"source": "internal/cli/schema_hints/selection/chat.json",
|
||||
"value": [
|
||||
"没有真实可用 mediaId 时先完成媒体上传"
|
||||
"本地图片路径、钉盘 dentryId、聊天文件 uploadKey 都不能代替 icon-media-id;当前 CLI 无本地图片转群头像 mediaId 的能力,缺少 mediaId 时应如实报告能力边界"
|
||||
]
|
||||
},
|
||||
"canonical_path": {
|
||||
@@ -104669,7 +104669,7 @@
|
||||
"selected": true,
|
||||
"source": "internal/cli/schema_hints/selection/chat.json",
|
||||
"value": [
|
||||
"已有上传后的头像 mediaId 并要修改群头像时"
|
||||
"已有可用于群头像接口的真实 mediaId 并要修改群头像时"
|
||||
]
|
||||
}
|
||||
],
|
||||
@@ -104678,7 +104678,7 @@
|
||||
"review_reason": "AI 生成并复核的 Agent 选择语义,依据精确 CommandRegistry identity、真实 Cobra help 与产品参考,不改写执行、安全或接口事实。",
|
||||
"source": "internal/cli/schema_hints/selection/chat.json",
|
||||
"value": [
|
||||
"已有上传后的头像 mediaId 并要修改群头像时"
|
||||
"已有可用于群头像接口的真实 mediaId 并要修改群头像时"
|
||||
]
|
||||
}
|
||||
},
|
||||
@@ -105000,7 +105000,7 @@
|
||||
"source": "reviewed_command_registry",
|
||||
"title": "更新群头像",
|
||||
"use_when": [
|
||||
"已有上传后的头像 mediaId 并要修改群头像时"
|
||||
"已有可用于群头像接口的真实 mediaId 并要修改群头像时"
|
||||
]
|
||||
},
|
||||
"chat.update_group_name": {
|
||||
@@ -106217,7 +106217,7 @@
|
||||
"agent_summary_source": "dws-agent-selection/chat",
|
||||
"availability": "available",
|
||||
"avoid_when": [
|
||||
"全员禁言和成员禁言使用专门的 mute 命令"
|
||||
"全员禁言和成员禁言使用专门的 mute 命令;当前用户自己的会话置顶或免打扰使用 group user-settings set"
|
||||
],
|
||||
"canonical_path": "chat.update_group_settings",
|
||||
"cli_name": "update-settings",
|
||||
@@ -106277,7 +106277,7 @@
|
||||
"selected": true,
|
||||
"source": "internal/cli/schema_hints/selection/chat.json",
|
||||
"value": [
|
||||
"全员禁言和成员禁言使用专门的 mute 命令"
|
||||
"全员禁言和成员禁言使用专门的 mute 命令;当前用户自己的会话置顶或免打扰使用 group user-settings set"
|
||||
]
|
||||
}
|
||||
],
|
||||
@@ -106286,7 +106286,7 @@
|
||||
"review_reason": "AI 生成并复核的 Agent 选择语义,依据精确 CommandRegistry identity、真实 Cobra help 与产品参考,不改写执行、安全或接口事实。",
|
||||
"source": "internal/cli/schema_hints/selection/chat.json",
|
||||
"value": [
|
||||
"全员禁言和成员禁言使用专门的 mute 命令"
|
||||
"全员禁言和成员禁言使用专门的 mute 命令;当前用户自己的会话置顶或免打扰使用 group user-settings set"
|
||||
]
|
||||
},
|
||||
"canonical_path": {
|
||||
@@ -106508,7 +106508,7 @@
|
||||
"selected": true,
|
||||
"source": "internal/cli/schema_hints/selection/chat.json",
|
||||
"value": [
|
||||
"需要调整 searchable、入群验证或群权限等设置时"
|
||||
"需要调整 searchable、入群验证、@所有人权限等群级设置时;修改后用群信息或设置查询核对终态"
|
||||
]
|
||||
}
|
||||
],
|
||||
@@ -106517,7 +106517,7 @@
|
||||
"review_reason": "AI 生成并复核的 Agent 选择语义,依据精确 CommandRegistry identity、真实 Cobra help 与产品参考,不改写执行、安全或接口事实。",
|
||||
"source": "internal/cli/schema_hints/selection/chat.json",
|
||||
"value": [
|
||||
"需要调整 searchable、入群验证或群权限等设置时"
|
||||
"需要调整 searchable、入群验证、@所有人权限等群级设置时;修改后用群信息或设置查询核对终态"
|
||||
]
|
||||
}
|
||||
},
|
||||
@@ -106921,7 +106921,7 @@
|
||||
"source": "reviewed_command_registry",
|
||||
"title": "更新群设置",
|
||||
"use_when": [
|
||||
"需要调整 searchable、入群验证或群权限等设置时"
|
||||
"需要调整 searchable、入群验证、@所有人权限等群级设置时;修改后用群信息或设置查询核对终态"
|
||||
]
|
||||
},
|
||||
"chat.update_notification_off": {
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -27242,8 +27242,10 @@
|
||||
"agent_summary_source": "dws-agent-selection/drive",
|
||||
"availability": "available",
|
||||
"avoid_when": [
|
||||
"用户要把文件发送到群聊或单聊时使用 chat message send --msg-type file --file-path;drive upload 不会发送聊天消息",
|
||||
"常规场景不要拆成 upload-info + 手动 PUT + commit;仅自定义流式上传才用三步",
|
||||
"要把文件作为文档正文附件插入改用 dws doc media insert",
|
||||
"Word/Excel/Markdown 要求上传后在线编辑、大家直接在线改或转钉钉文档时改用 dws doc import;普通 drive upload 返回 docx/xlsx 文件不能宣称可在线编辑",
|
||||
"用户明确说文档空间且走 doc 兼容入口时可用 dws doc upload,默认仍推荐本命令",
|
||||
"用户没有明确同意替换目标文件时不要使用 --node;新建上传应使用 --folder 或目标根目录"
|
||||
],
|
||||
@@ -27267,7 +27269,7 @@
|
||||
"candidates": [
|
||||
{
|
||||
"precedence": "reviewed_explicit",
|
||||
"review_reason": "人工审阅:结合本机 dws schema 实时 MCP description/parameters(经 interface_ref)、产品 Skill、Cobra Long 与 Runtime 确认门禁,按飞书风格重写选型文案;不改变命令身份、参数契约或接口绑定。",
|
||||
"review_reason": "人工对齐普通文件存储与在线文档导入边界:在线编辑需求硬路由 doc import,drive upload 不承诺转换结果;不改变命令身份、参数契约或接口绑定。",
|
||||
"selected": true,
|
||||
"source": "internal/cli/schema_hints/selection/drive.json",
|
||||
"value": "上传本地文件到钉盘或文档空间,或按节点 ID 确认覆盖已有文件"
|
||||
@@ -27275,7 +27277,7 @@
|
||||
],
|
||||
"precedence": "reviewed_explicit",
|
||||
"resolution": "highest_precedence",
|
||||
"review_reason": "人工审阅:结合本机 dws schema 实时 MCP description/parameters(经 interface_ref)、产品 Skill、Cobra Long 与 Runtime 确认门禁,按飞书风格重写选型文案;不改变命令身份、参数契约或接口绑定。",
|
||||
"review_reason": "人工对齐普通文件存储与在线文档导入边界:在线编辑需求硬路由 doc import,drive upload 不承诺转换结果;不改变命令身份、参数契约或接口绑定。",
|
||||
"source": "internal/cli/schema_hints/selection/drive.json",
|
||||
"value": "上传本地文件到钉盘或文档空间,或按节点 ID 确认覆盖已有文件"
|
||||
},
|
||||
@@ -27299,12 +27301,14 @@
|
||||
"candidates": [
|
||||
{
|
||||
"precedence": "reviewed_explicit",
|
||||
"review_reason": "人工审阅:结合本机 dws schema 实时 MCP description/parameters(经 interface_ref)、产品 Skill、Cobra Long 与 Runtime 确认门禁,按飞书风格重写选型文案;不改变命令身份、参数契约或接口绑定。",
|
||||
"review_reason": "人工对齐普通文件存储与在线文档导入边界:在线编辑需求硬路由 doc import,drive upload 不承诺转换结果;不改变命令身份、参数契约或接口绑定。",
|
||||
"selected": true,
|
||||
"source": "internal/cli/schema_hints/selection/drive.json",
|
||||
"value": [
|
||||
"用户要把文件发送到群聊或单聊时使用 chat message send --msg-type file --file-path;drive upload 不会发送聊天消息",
|
||||
"常规场景不要拆成 upload-info + 手动 PUT + commit;仅自定义流式上传才用三步",
|
||||
"要把文件作为文档正文附件插入改用 dws doc media insert",
|
||||
"Word/Excel/Markdown 要求上传后在线编辑、大家直接在线改或转钉钉文档时改用 dws doc import;普通 drive upload 返回 docx/xlsx 文件不能宣称可在线编辑",
|
||||
"用户明确说文档空间且走 doc 兼容入口时可用 dws doc upload,默认仍推荐本命令",
|
||||
"用户没有明确同意替换目标文件时不要使用 --node;新建上传应使用 --folder 或目标根目录"
|
||||
]
|
||||
@@ -27312,11 +27316,13 @@
|
||||
],
|
||||
"precedence": "reviewed_explicit",
|
||||
"resolution": "highest_precedence",
|
||||
"review_reason": "人工审阅:结合本机 dws schema 实时 MCP description/parameters(经 interface_ref)、产品 Skill、Cobra Long 与 Runtime 确认门禁,按飞书风格重写选型文案;不改变命令身份、参数契约或接口绑定。",
|
||||
"review_reason": "人工对齐普通文件存储与在线文档导入边界:在线编辑需求硬路由 doc import,drive upload 不承诺转换结果;不改变命令身份、参数契约或接口绑定。",
|
||||
"source": "internal/cli/schema_hints/selection/drive.json",
|
||||
"value": [
|
||||
"用户要把文件发送到群聊或单聊时使用 chat message send --msg-type file --file-path;drive upload 不会发送聊天消息",
|
||||
"常规场景不要拆成 upload-info + 手动 PUT + commit;仅自定义流式上传才用三步",
|
||||
"要把文件作为文档正文附件插入改用 dws doc media insert",
|
||||
"Word/Excel/Markdown 要求上传后在线编辑、大家直接在线改或转钉钉文档时改用 dws doc import;普通 drive upload 返回 docx/xlsx 文件不能宣称可在线编辑",
|
||||
"用户明确说文档空间且走 doc 兼容入口时可用 dws doc upload,默认仍推荐本命令",
|
||||
"用户没有明确同意替换目标文件时不要使用 --node;新建上传应使用 --folder 或目标根目录"
|
||||
]
|
||||
@@ -27429,7 +27435,7 @@
|
||||
"candidates": [
|
||||
{
|
||||
"precedence": "reviewed_explicit",
|
||||
"review_reason": "人工审阅:结合本机 dws schema 实时 MCP description/parameters(经 interface_ref)、产品 Skill、Cobra Long 与 Runtime 确认门禁,按飞书风格重写选型文案;不改变命令身份、参数契约或接口绑定。",
|
||||
"review_reason": "人工对齐普通文件存储与在线文档导入边界:在线编辑需求硬路由 doc import,drive upload 不承诺转换结果;不改变命令身份、参数契约或接口绑定。",
|
||||
"selected": true,
|
||||
"source": "internal/cli/schema_hints/selection/drive.json",
|
||||
"value": [
|
||||
@@ -27440,7 +27446,7 @@
|
||||
],
|
||||
"precedence": "reviewed_explicit",
|
||||
"resolution": "highest_precedence",
|
||||
"review_reason": "人工审阅:结合本机 dws schema 实时 MCP description/parameters(经 interface_ref)、产品 Skill、Cobra Long 与 Runtime 确认门禁,按飞书风格重写选型文案;不改变命令身份、参数契约或接口绑定。",
|
||||
"review_reason": "人工对齐普通文件存储与在线文档导入边界:在线编辑需求硬路由 doc import,drive upload 不承诺转换结果;不改变命令身份、参数契约或接口绑定。",
|
||||
"source": "internal/cli/schema_hints/selection/drive.json",
|
||||
"value": [
|
||||
"dws drive upload --file ./report.pdf --format json",
|
||||
@@ -27528,7 +27534,7 @@
|
||||
},
|
||||
{
|
||||
"precedence": "reviewed_explicit",
|
||||
"review_reason": "人工审阅:结合本机 dws schema 实时 MCP description/parameters(经 interface_ref)、产品 Skill、Cobra Long 与 Runtime 确认门禁,按飞书风格重写选型文案;不改变命令身份、参数契约或接口绑定。",
|
||||
"review_reason": "人工对齐普通文件存储与在线文档导入边界:在线编辑需求硬路由 doc import,drive upload 不承诺转换结果;不改变命令身份、参数契约或接口绑定。",
|
||||
"selected": false,
|
||||
"source": "internal/cli/schema_hints/selection/drive.json",
|
||||
"value": true
|
||||
@@ -27580,23 +27586,25 @@
|
||||
"candidates": [
|
||||
{
|
||||
"precedence": "reviewed_explicit",
|
||||
"review_reason": "人工审阅:结合本机 dws schema 实时 MCP description/parameters(经 interface_ref)、产品 Skill、Cobra Long 与 Runtime 确认门禁,按飞书风格重写选型文案;不改变命令身份、参数契约或接口绑定。",
|
||||
"review_reason": "人工对齐普通文件存储与在线文档导入边界:在线编辑需求硬路由 doc import,drive upload 不承诺转换结果;不改变命令身份、参数契约或接口绑定。",
|
||||
"selected": true,
|
||||
"source": "internal/cli/schema_hints/selection/drive.json",
|
||||
"value": [
|
||||
"用户要把本地文件上传到钉盘/我的文件(首选一条命令自动完成凭证+PUT+提交)时",
|
||||
"上传到知识库/文档空间时加 --workspace;需要转在线文档时加 --convert",
|
||||
"用户明确要求只上传或暂存附件、暂时不要发送到任何聊天会话时",
|
||||
"用户只要保留原始文件供存储或下载时使用;上传到知识库/文档空间时可加 --workspace",
|
||||
"用户明确要求用本地文件替换已有钉盘/文档空间文件时传 --node;该模式会覆盖远端内容并要求确认"
|
||||
]
|
||||
}
|
||||
],
|
||||
"precedence": "reviewed_explicit",
|
||||
"resolution": "highest_precedence",
|
||||
"review_reason": "人工审阅:结合本机 dws schema 实时 MCP description/parameters(经 interface_ref)、产品 Skill、Cobra Long 与 Runtime 确认门禁,按飞书风格重写选型文案;不改变命令身份、参数契约或接口绑定。",
|
||||
"review_reason": "人工对齐普通文件存储与在线文档导入边界:在线编辑需求硬路由 doc import,drive upload 不承诺转换结果;不改变命令身份、参数契约或接口绑定。",
|
||||
"source": "internal/cli/schema_hints/selection/drive.json",
|
||||
"value": [
|
||||
"用户要把本地文件上传到钉盘/我的文件(首选一条命令自动完成凭证+PUT+提交)时",
|
||||
"上传到知识库/文档空间时加 --workspace;需要转在线文档时加 --convert",
|
||||
"用户明确要求只上传或暂存附件、暂时不要发送到任何聊天会话时",
|
||||
"用户只要保留原始文件供存储或下载时使用;上传到知识库/文档空间时可加 --workspace",
|
||||
"用户明确要求用本地文件替换已有钉盘/文档空间文件时传 --node;该模式会覆盖远端内容并要求确认"
|
||||
]
|
||||
}
|
||||
@@ -28319,7 +28327,8 @@
|
||||
"title": "上传本地文件到钉盘或文档空间",
|
||||
"use_when": [
|
||||
"用户要把本地文件上传到钉盘/我的文件(首选一条命令自动完成凭证+PUT+提交)时",
|
||||
"上传到知识库/文档空间时加 --workspace;需要转在线文档时加 --convert",
|
||||
"用户明确要求只上传或暂存附件、暂时不要发送到任何聊天会话时",
|
||||
"用户只要保留原始文件供存储或下载时使用;上传到知识库/文档空间时可加 --workspace",
|
||||
"用户明确要求用本地文件替换已有钉盘/文档空间文件时传 --node;该模式会覆盖远端内容并要求确认"
|
||||
]
|
||||
}
|
||||
|
||||
@@ -289,12 +289,12 @@
|
||||
]
|
||||
},
|
||||
"aitable.base_search": {
|
||||
"agent_summary": "按名称搜索 AI 表格 Base(优先于仅最近访问的 list)。",
|
||||
"agent_summary": "底层按名称搜索 AI 表格 Base,返回原始候选列表。",
|
||||
"use_when": [
|
||||
"用户要找某个 AI 表格/多维表,按名称检索时优先使用"
|
||||
"需要 Shortcut 未公开的底层参数、原始 search_bases 响应或自定义候选处理时"
|
||||
],
|
||||
"avoid_when": [
|
||||
"只要最近访问列表用 base list;已知 baseId 取详情用 base get;电子表格 axls 用 sheet"
|
||||
"普通按名称解析唯一 baseId 优先用 +resolve-base;只要候选列表用 +base-search;最近访问用 +base-list;电子表格 axls 用 sheet"
|
||||
],
|
||||
"examples": [
|
||||
"dws aitable base search --query \"项目\""
|
||||
@@ -304,7 +304,8 @@
|
||||
"source_refs": [
|
||||
"internal/cli/schema_command_registry.json",
|
||||
"cobra-help:dws aitable base search --help",
|
||||
"skills/mono/references/products/aitable.md",
|
||||
"skills/multi/dingtalk-aitable/SKILL.md",
|
||||
"skills/multi/dingtalk-aitable/references/aitable.md",
|
||||
"dws-schema-live:aitable.search_bases"
|
||||
]
|
||||
},
|
||||
@@ -710,12 +711,12 @@
|
||||
]
|
||||
},
|
||||
"aitable.field_get": {
|
||||
"agent_summary": "获取字段完整配置。",
|
||||
"agent_summary": "底层获取字段完整类型与 config。",
|
||||
"use_when": [
|
||||
"需要字段类型/config(选项、公式等)详情时"
|
||||
"需要 +field-get 未公开的底层参数、原始响应或不同执行语义时"
|
||||
],
|
||||
"avoid_when": [
|
||||
"表级字段目录也可用 table get;创建字段用 field create;不可用本命令改类型"
|
||||
"正常按字段 ID 展开类型/config 优先用 +field-get;精简字段目录用 +table-get;创建字段用 field create"
|
||||
],
|
||||
"examples": [
|
||||
"dws aitable field get --base-id <BASE_ID> --table-id <TABLE_ID>"
|
||||
@@ -725,7 +726,8 @@
|
||||
"source_refs": [
|
||||
"internal/cli/schema_command_registry.json",
|
||||
"cobra-help:dws aitable field get --help",
|
||||
"skills/mono/references/products/aitable.md",
|
||||
"skills/multi/dingtalk-aitable/SKILL.md",
|
||||
"skills/multi/dingtalk-aitable/references/aitable/aitable-field.md",
|
||||
"dws-schema-live:aitable.get_fields"
|
||||
]
|
||||
},
|
||||
@@ -1110,12 +1112,12 @@
|
||||
]
|
||||
},
|
||||
"aitable.query_records": {
|
||||
"agent_summary": "查询/搜索记录(filters/sort/分页/--all;cells 键为 fieldId)。",
|
||||
"agent_summary": "底层查询/搜索记录,额外支持原子命令的完整分页控制。",
|
||||
"use_when": [
|
||||
"查看、筛选、全文搜索或遍历记录时的主入口"
|
||||
"需要 +record-query 未公开的 --all/--page-limit、底层原始响应或不同执行语义时"
|
||||
],
|
||||
"avoid_when": [
|
||||
"已知 recordId 窄查可用 record get;空行用 query-empty;写入用 create/update;电子表格单元格用 sheet"
|
||||
"普通按 ID、关键词、filters、sort 或 cursor 查询优先用 +record-query;空行用 +record-query-empty;写入用 create/update;电子表格单元格用 sheet"
|
||||
],
|
||||
"examples": [
|
||||
"dws aitable record query --base-id <BASE_ID> --table-id <TABLE_ID>"
|
||||
@@ -1125,7 +1127,8 @@
|
||||
"source_refs": [
|
||||
"internal/cli/schema_command_registry.json",
|
||||
"cobra-help:dws aitable record query --help",
|
||||
"skills/mono/references/products/aitable.md",
|
||||
"skills/multi/dingtalk-aitable/SKILL.md",
|
||||
"skills/multi/dingtalk-aitable/references/aitable/aitable-record-query.md",
|
||||
"dws-schema-live:aitable.query_records"
|
||||
]
|
||||
},
|
||||
@@ -1150,12 +1153,12 @@
|
||||
]
|
||||
},
|
||||
"aitable.record_create": {
|
||||
"agent_summary": "新增记录(cells 的 key 必须是 fieldId)。",
|
||||
"agent_summary": "新增记录;使用 fieldId 写入并从 newRecordIds 回读验证。",
|
||||
"use_when": [
|
||||
"需要插入新行数据时"
|
||||
"已取得 baseId/tableId 与字段完整配置,需要插入一条或多条新记录并回读时"
|
||||
],
|
||||
"avoid_when": [
|
||||
"更新已有行用 record update;有则更无则增用 upsert;批量同 patch 用 batch-update"
|
||||
"未核对字段类型时先 field get;更新已有行用 record update;有则更无则增用 upsert;本地 CSV/JSON 批量追加可用已评审脚本"
|
||||
],
|
||||
"examples": [
|
||||
"dws aitable record create --base-id <BASE_ID> --table-id <TABLE_ID> --records '[{\"cells\":{\"fldXXX\":\"值\"}}]'"
|
||||
@@ -1165,7 +1168,8 @@
|
||||
"source_refs": [
|
||||
"internal/cli/schema_command_registry.json",
|
||||
"cobra-help:dws aitable record create --help",
|
||||
"skills/mono/references/products/aitable.md",
|
||||
"skills/multi/dingtalk-aitable/SKILL.md",
|
||||
"skills/multi/dingtalk-aitable/references/aitable/aitable-record-create.md",
|
||||
"dws-schema-live:aitable.create_records"
|
||||
]
|
||||
},
|
||||
@@ -1310,12 +1314,12 @@
|
||||
]
|
||||
},
|
||||
"aitable.record_update": {
|
||||
"agent_summary": "更新已有记录字段(先 query 拿 recordId;只传需改字段)。",
|
||||
"agent_summary": "更新已有记录字段;先 query 拿 recordId,使用 fieldId 写入并回读。",
|
||||
"use_when": [
|
||||
"需要修改已有记录若干字段时"
|
||||
"已查询得到真实 recordId 并核对字段类型,需要修改每条记录各自的 cells 时"
|
||||
],
|
||||
"avoid_when": [
|
||||
"新建用 create;多条共用同一 cells patch 用 batch-update;混合创建/更新用 upsert"
|
||||
"未定位 recordId 或字段配置时先 query/field get;新建用 create;多条共用同一 cells patch 用 batch-update;混合创建/更新用 upsert"
|
||||
],
|
||||
"examples": [
|
||||
"dws aitable record update --base-id <BASE_ID> --table-id <TABLE_ID> --records '[{\"recordId\":\"recXXX\",\"cells\":{\"fldYYY\":\"新值\"}}]'"
|
||||
@@ -1325,7 +1329,8 @@
|
||||
"source_refs": [
|
||||
"internal/cli/schema_command_registry.json",
|
||||
"cobra-help:dws aitable record update --help",
|
||||
"skills/mono/references/products/aitable.md",
|
||||
"skills/multi/dingtalk-aitable/SKILL.md",
|
||||
"skills/multi/dingtalk-aitable/references/aitable/aitable-record-update.md",
|
||||
"dws-schema-live:aitable.update_records"
|
||||
]
|
||||
},
|
||||
@@ -1550,12 +1555,12 @@
|
||||
]
|
||||
},
|
||||
"aitable.table_get": {
|
||||
"agent_summary": "获取数据表结构(字段+视图目录)。",
|
||||
"agent_summary": "底层获取数据表结构、精简字段目录与视图目录。",
|
||||
"use_when": [
|
||||
"需要字段 fieldId 或视图目录以继续 record/field 操作时优先 table get"
|
||||
"需要 +table-get 未公开的底层参数、原始 get_tables 响应或不同执行语义时"
|
||||
],
|
||||
"avoid_when": [
|
||||
"只关心 Base 级 tables 列表可先 base get;字段完整配置也可用 field get"
|
||||
"正常获取表、精简字段和视图目录优先用 +table-get;按表名解析 ID 用 +resolve-table;完整字段 config 用 +field-get"
|
||||
],
|
||||
"examples": [
|
||||
"dws aitable table get --base-id BASE_ID"
|
||||
@@ -1565,7 +1570,8 @@
|
||||
"source_refs": [
|
||||
"internal/cli/schema_command_registry.json",
|
||||
"cobra-help:dws aitable table get --help",
|
||||
"skills/mono/references/products/aitable.md",
|
||||
"skills/multi/dingtalk-aitable/SKILL.md",
|
||||
"skills/multi/dingtalk-aitable/references/aitable.md",
|
||||
"dws-schema-live:aitable.get_tables"
|
||||
]
|
||||
},
|
||||
@@ -2335,7 +2341,7 @@
|
||||
"当你不知道具体 baseId、想先浏览自己最近用过或可访问的 AI 表格清单以便定位目标时使用;支持游标分页,返回 Base 列表及其 baseId。"
|
||||
],
|
||||
"avoid_when": [
|
||||
"需要该 Shortcut 未公开的底层参数、原始响应或不同执行语义时,改用对应原子命令"
|
||||
"知道名称并要解析唯一 baseId 用 +resolve-base;按关键词要完整候选用 +base-search;不要把最近访问列表描述成全部 Base"
|
||||
],
|
||||
"examples": [
|
||||
"dws aitable +base-list",
|
||||
@@ -2355,7 +2361,7 @@
|
||||
"当你知道某个 AI 表格的名字或部分关键词、想直接定位到它并拿到 baseId 时使用;输入名称关键词,返回匹配的 Base 列表。"
|
||||
],
|
||||
"avoid_when": [
|
||||
"需要该 Shortcut 未公开的底层参数、原始响应或不同执行语义时,改用对应原子命令"
|
||||
"只需唯一 baseId 用 +resolve-base;浏览最近访问用 +base-list;需要未公开底层参数或原始响应才用 base search"
|
||||
],
|
||||
"examples": [
|
||||
"dws aitable +base-search --query \"项目管理\""
|
||||
@@ -2393,7 +2399,7 @@
|
||||
"当你已进入某个 Base、需要了解其中某些数据表有哪些字段(拿 fieldId)、有哪些视图(拿 viewId)以便读写数据时使用;批量返回表信息、字段目录和视图目录。"
|
||||
],
|
||||
"avoid_when": [
|
||||
"需要该 Shortcut 未公开的底层参数、原始响应或不同执行语义时,改用对应原子命令"
|
||||
"只知道表名并要解析唯一 tableId 用 +resolve-table;需要字段完整 config 用 +field-get;需要未公开底层语义才用 table get"
|
||||
],
|
||||
"examples": [
|
||||
"dws aitable +table-get --base-id BASE_ID",
|
||||
@@ -2413,7 +2419,7 @@
|
||||
"当你需要查看字段的完整类型配置(如单选选项、关联表设置、AI 配置)以便正确写入数据或改配置时使用;批量返回字段详情。"
|
||||
],
|
||||
"avoid_when": [
|
||||
"需要该 Shortcut 未公开的底层参数、原始响应或不同执行语义时,改用对应原子命令"
|
||||
"只需精简 fieldId/type 目录用 +table-get;创建或修改字段用 field create/update;需要未公开底层语义才用 field get"
|
||||
],
|
||||
"examples": [
|
||||
"dws aitable +field-get --base-id B --table-id T"
|
||||
@@ -2432,7 +2438,7 @@
|
||||
"当你要读取表格里的行数据——按 recordId 精确取、按结构化条件筛选、按关键词全文搜索或分页遍历时使用;返回匹配记录及其单元格值。"
|
||||
],
|
||||
"avoid_when": [
|
||||
"需要该 Shortcut 未公开的底层参数、原始响应或不同执行语义时,改用对应原子命令"
|
||||
"完全空行扫描用 +record-query-empty;变更历史用 +record-history-list;需要 --all/--page-limit 或未公开底层语义才用 record query"
|
||||
],
|
||||
"examples": [
|
||||
"dws aitable +record-query --base-id B --table-id T --query \"关键词\" --limit 50"
|
||||
@@ -2849,10 +2855,10 @@
|
||||
"aitable.shortcut_resolve_base": {
|
||||
"agent_summary": "按名称搜索多维表 Base 并解析出唯一 baseId(只读)",
|
||||
"use_when": [
|
||||
"当你只知道某个多维表 Base 的名称(或名称里的关键词)、想把它解析成可直接用于后续工具的 baseId 时使用;内部按 --name 关键词调用 search_bases 搜索 Base,再在本地投影出每个候选的 baseId 和 name。如果只命中一个 Base 就直接返回它的 baseId;如果命中多个则列出全部候选让你消歧,绝不替你瞎猜;如果一个都没命中则提示未找到。这是纯只读操作,只做搜索与本地投影,不会修改任何 Base。"
|
||||
"只知道 Base 名称或关键词,需要解析成唯一 baseId 供后续 Table/Record 操作时"
|
||||
],
|
||||
"avoid_when": [
|
||||
"需要该 Shortcut 未公开的底层参数、原始响应或不同执行语义时,改用对应原子命令"
|
||||
"多候选时必须让用户消歧;只要完整候选列表用 +base-search;浏览最近访问用 +base-list;已知 baseId 不再重复搜索"
|
||||
],
|
||||
"examples": [
|
||||
"dws aitable +resolve-base --name 项目管理"
|
||||
@@ -2868,10 +2874,10 @@
|
||||
"aitable.shortcut_resolve_table": {
|
||||
"agent_summary": "在某个多维表 Base 内按名称解析出唯一的数据表 tableId(只读)",
|
||||
"use_when": [
|
||||
"当你已经知道某个多维表 Base 的 baseId、又只记得里面某张数据表(table)的名称或名称关键词、想把它解析成可直接用于后续工具的 tableId 时使用;内部先用 get_tables(只传 baseId)列出该 Base 下的全部数据表,再在本地把每张表投影成 tableId、name,并按 --name 关键词做大小写不敏感的包含匹配来筛选候选。如果只命中一张表就直接返回它的 tableId;如果命中多张则列出全部候选让你消歧,绝不替你瞎猜;如果一张都没命中则提示未找到。这是纯只读操作,只做列举、本地匹配与投影,不会创建、修改或删除任何数据表。"
|
||||
"已有真实 baseId、只知道数据表名称或关键词,需要解析成唯一 tableId 时"
|
||||
],
|
||||
"avoid_when": [
|
||||
"需要该 Shortcut 未公开的底层参数、原始响应或不同执行语义时,改用对应原子命令"
|
||||
"多候选时必须让用户消歧;需要所有表及字段/视图目录用 +table-get;已知 tableId 不再重复解析"
|
||||
],
|
||||
"examples": [
|
||||
"dws aitable +resolve-table --base B --name 任务"
|
||||
@@ -2887,18 +2893,19 @@
|
||||
},
|
||||
"products": {
|
||||
"aitable": {
|
||||
"agent_summary": "管理 AI 表格 Base、数据表、字段、记录、视图、表单、仪表盘、权限、导入导出与自动化工作流。",
|
||||
"agent_summary": "管理 AI 表格的 Base、Table、Field、Record、视图表单、仪表盘、权限、导入导出与自动化工作流。",
|
||||
"use_when": [
|
||||
"需要读取或管理 AI 表格中的结构、数据、视图、权限、导入导出或工作流时"
|
||||
"目标是 AI 表格/多维表中的结构化 Base、数据表、字段、记录、视图、权限、文件导入导出或自动化工作流时"
|
||||
],
|
||||
"avoid_when": [
|
||||
"目标是在线电子表格单元格读写时用 sheet;普通文档用 doc"
|
||||
"在线电子表格工作表、单元格或公式用 sheet;普通文档用 doc;钉盘普通文件用 drive;类型不明的 alidocs URL 先做类型预检"
|
||||
],
|
||||
"reviewed": true,
|
||||
"review_reason": "人工结合实时 MCP/Skill/Cobra 审阅产品级选择边界。",
|
||||
"review_reason": "人工结合多产品边界、根 Skill 的渐进加载顺序、真实 Cobra/Shortcut 路径与 MCP 语义审阅产品级选择边界。",
|
||||
"source_refs": [
|
||||
"internal/cli/schema_command_registry.json",
|
||||
"skills/mono/references/products/aitable.md",
|
||||
"skills/multi/dingtalk-aitable/SKILL.md",
|
||||
"skills/multi/dingtalk-aitable/references/aitable.md",
|
||||
"dws-schema-live:product-index"
|
||||
]
|
||||
}
|
||||
|
||||
@@ -29,12 +29,12 @@
|
||||
]
|
||||
},
|
||||
"chat.add_emoji_reaction": {
|
||||
"agent_summary": "给指定消息添加表情回应",
|
||||
"agent_summary": "使用同一会话中真实的 openMessageId 给指定消息添加表情回应",
|
||||
"use_when": [
|
||||
"需要对已有消息添加一个 emoji reaction 时"
|
||||
"需要对已有消息添加一个 emoji reaction 时;conversation-id 与 msg-id 必须来自同一条真实消息"
|
||||
],
|
||||
"avoid_when": [
|
||||
"发送文本消息或文字表情时不要使用"
|
||||
"发送文本消息或文字表情时不要使用;不要把 openTaskId 当作 msg-id"
|
||||
],
|
||||
"examples": [
|
||||
"dws chat message add-emoji --conversation-id <openConversationId> --msg-id <openMessageId> --emoji \"赞\""
|
||||
@@ -658,12 +658,12 @@
|
||||
]
|
||||
},
|
||||
"chat.query_message_send_status": {
|
||||
"agent_summary": "查询异步消息发送任务的状态",
|
||||
"agent_summary": "查询异步消息发送任务的业务终态,并按 sendStatus 判断是否真正投递成功",
|
||||
"use_when": [
|
||||
"发送命令返回 openTaskId 后需要确认投递结果时"
|
||||
"发送命令返回 openTaskId 后需要确认投递结果时;只有 sendStatus=SUCCESS 才能宣称消息发送成功"
|
||||
],
|
||||
"avoid_when": [
|
||||
"没有 message send 真实返回的 openTaskId 时不要使用;openMessageId 和会话 ID 都不能代替 openTaskId"
|
||||
"没有 message send 真实返回的 openTaskId 时不要使用;openMessageId 和会话 ID 都不能代替 openTaskId;FAILED 不得当作接口成功"
|
||||
],
|
||||
"examples": [
|
||||
"dws chat message query-send-status --open-task-id <openTaskId>"
|
||||
@@ -802,12 +802,12 @@
|
||||
]
|
||||
},
|
||||
"chat.remove_emoji_reaction": {
|
||||
"agent_summary": "移除指定消息上的表情回应",
|
||||
"agent_summary": "从同一条真实消息移除此前添加的同名表情回应",
|
||||
"use_when": [
|
||||
"需要取消此前添加的 emoji reaction 时"
|
||||
"需要取消此前添加的 emoji reaction 时;复用添加时的 conversation-id、msg-id 和 emoji"
|
||||
],
|
||||
"avoid_when": [
|
||||
"移除文字表情时使用 chat message remove-text-emotion"
|
||||
"移除文字表情时使用 chat message remove-text-emotion;不要猜测新的消息 ID 或表情名称"
|
||||
],
|
||||
"examples": [
|
||||
"dws chat message remove-emoji --conversation-id <openConversationId> --msg-id <openMessageId> --emoji \"赞\""
|
||||
@@ -824,10 +824,10 @@
|
||||
"chat.remove_group_member": {
|
||||
"agent_summary": "从指定群聊移除成员",
|
||||
"use_when": [
|
||||
"群管理员明确要移除一个或多个普通成员时"
|
||||
"群管理员明确要移除一个或多个普通成员时;清理临时群只传本次加入的普通成员"
|
||||
],
|
||||
"avoid_when": [
|
||||
"移除机器人时使用 chat group members remove-bot"
|
||||
"移除机器人时使用 chat group members remove-bot;不要把建群者或群主加入 --users,也不要为了清理成员而转让群主"
|
||||
],
|
||||
"examples": [
|
||||
"dws chat group members remove --id <openConversationId> --users userId1,userId2"
|
||||
@@ -1236,12 +1236,12 @@
|
||||
]
|
||||
},
|
||||
"chat.set_pin_message": {
|
||||
"agent_summary": "把指定消息设为会话置顶消息",
|
||||
"agent_summary": "使用同源的 openConversationId 和 openMessageId 把指定消息设为 Pin",
|
||||
"use_when": [
|
||||
"需要在会话中置顶一条已知消息时"
|
||||
"需要在会话中钉住一条已知消息时;若发送只返回 openTaskId,先确认发送终态,再从消息查询结果取得真实 openMessageId"
|
||||
],
|
||||
"avoid_when": [
|
||||
"取消置顶使用 chat message unset-pin-msg"
|
||||
"取消 Pin 使用 chat message unset-pin-msg;置顶到会话顶部使用 set-top-msg,置顶整个会话使用 chat set-top"
|
||||
],
|
||||
"examples": [
|
||||
"dws chat message set-pin-msg --open-conversation-id <openConversationId> --msg-id <openMessageId>"
|
||||
@@ -1316,12 +1316,12 @@
|
||||
]
|
||||
},
|
||||
"chat.unset_pin_message": {
|
||||
"agent_summary": "取消指定消息的会话置顶",
|
||||
"agent_summary": "使用设置 Pin 时的同源会话和消息 ID 取消指定消息的 Pin",
|
||||
"use_when": [
|
||||
"需要移除一条已知置顶消息时"
|
||||
"需要移除一条已知 Pin 消息时;复用 set-pin-msg 成功时的 openConversationId 和 openMessageId"
|
||||
],
|
||||
"avoid_when": [
|
||||
"新增置顶使用 chat message set-pin-msg"
|
||||
"新增 Pin 使用 chat message set-pin-msg;不要改用 unset-top-msg 或 chat set-top --off"
|
||||
],
|
||||
"examples": [
|
||||
"dws chat message unset-pin-msg --open-conversation-id <openConversationId> --msg-id <openMessageId>"
|
||||
@@ -1378,10 +1378,10 @@
|
||||
"chat.update_group_icon": {
|
||||
"agent_summary": "使用真实媒体 ID 更新群头像",
|
||||
"use_when": [
|
||||
"已有上传后的头像 mediaId 并要修改群头像时"
|
||||
"已有可用于群头像接口的真实 mediaId 并要修改群头像时"
|
||||
],
|
||||
"avoid_when": [
|
||||
"没有真实可用 mediaId 时先完成媒体上传"
|
||||
"本地图片路径、钉盘 dentryId、聊天文件 uploadKey 都不能代替 icon-media-id;当前 CLI 无本地图片转群头像 mediaId 的能力,缺少 mediaId 时应如实报告能力边界"
|
||||
],
|
||||
"examples": [
|
||||
"dws chat group update-icon --group <openConversationId> --icon-media-id @mediaId"
|
||||
@@ -1418,10 +1418,10 @@
|
||||
"chat.update_group_settings": {
|
||||
"agent_summary": "更新指定群聊的一项设置开关",
|
||||
"use_when": [
|
||||
"需要调整 searchable、入群验证或群权限等设置时"
|
||||
"需要调整 searchable、入群验证、@所有人权限等群级设置时;修改后用群信息或设置查询核对终态"
|
||||
],
|
||||
"avoid_when": [
|
||||
"全员禁言和成员禁言使用专门的 mute 命令"
|
||||
"全员禁言和成员禁言使用专门的 mute 命令;当前用户自己的会话置顶或免打扰使用 group user-settings set"
|
||||
],
|
||||
"examples": [
|
||||
"dws chat group update-settings --group <openConversationId> --setting-key searchable --status 1"
|
||||
@@ -2845,12 +2845,12 @@
|
||||
]
|
||||
},
|
||||
"chat.shortcut_messages_query_send_status": {
|
||||
"agent_summary": "查询消息发送状态",
|
||||
"agent_summary": "查询消息发送任务的业务终态,只有 sendStatus=SUCCESS 才表示真正发送成功",
|
||||
"use_when": [
|
||||
"当你发消息后拿到 openTaskId、想确认这条消息是否发送成功时使用;只读返回发送状态,需传 --open-task-id。"
|
||||
"当你发消息后拿到 openTaskId、想确认这条消息是否发送成功时使用;只读返回发送状态,需传 --open-task-id;FAILED 必须如实报告,不能因外层 success=true 宣称成功。"
|
||||
],
|
||||
"avoid_when": [
|
||||
"需要该 Shortcut 未公开的底层参数、原始响应或不同执行语义时,改用对应原子命令"
|
||||
"需要该 Shortcut 未公开的底层参数、原始响应或不同执行语义时,改用对应原子命令;不要把 openTaskId 当 openMessageId 做 Pin、置顶或表情操作"
|
||||
],
|
||||
"examples": [
|
||||
"dws chat +messages-query-send-status --open-task-id <openTaskId>"
|
||||
@@ -3142,10 +3142,10 @@
|
||||
"chat.batch_query_group_chat_settings": {
|
||||
"agent_summary": "批量查询当前用户自己的群会话设置(置顶/免打扰/群昵称/群备注)",
|
||||
"use_when": [
|
||||
"用户说 看下这些群我的置顶和免打扰设置"
|
||||
"用户说看下这些群中我自己的置顶和免打扰设置;修改前先查询并保存原值用于恢复"
|
||||
],
|
||||
"avoid_when": [
|
||||
"管理员级群功能开关用 chat group update-settings"
|
||||
"管理员级群功能开关用 chat group update-settings;查询结果是当前用户视角,不代表群级设置"
|
||||
],
|
||||
"examples": [
|
||||
"dws chat group user-settings query --groups cid1,cid2 --format json"
|
||||
@@ -3161,10 +3161,10 @@
|
||||
"chat.batch_update_group_chat_settings": {
|
||||
"agent_summary": "批量更新当前用户自己的群会话设置(置顶/免打扰/群昵称/群备注)",
|
||||
"use_when": [
|
||||
"用户说 把这些群都设为免打扰/置顶"
|
||||
"用户说把这些群都设为免打扰或置顶;items 中只写需要改变的当前用户设置,完成后再次 query 验证"
|
||||
],
|
||||
"avoid_when": [
|
||||
"单个群昵称优先 chat group update-nick"
|
||||
"单个群昵称优先 chat group update-nick;不要用本命令修改 searchable、@所有人权限等群级开关;临时测试结束时按修改前 query 的真实值恢复"
|
||||
],
|
||||
"examples": [
|
||||
"dws chat group user-settings set --items '[{\"openConversationId\":\"cid1\",\"top\":true,\"mute\":false}]' --format json"
|
||||
|
||||
@@ -82,22 +82,22 @@
|
||||
]
|
||||
},
|
||||
"doc.create_document": {
|
||||
"agent_summary": "创建一篇新的在线文档",
|
||||
"agent_summary": "在默认根目录、文档文件夹或知识库根创建带可选初始内容的 adoc",
|
||||
"use_when": [
|
||||
"用户要新建一篇文字在线文档(adoc),可空文档或带初始 Markdown 时",
|
||||
"创建到指定文件夹 --folder、知识库根 --workspace,或默认「我的文档」根目录时"
|
||||
"用户要新建文字在线文档(adoc),可空文档或用 --content-file 写入明确要求的初始 Markdown/JSONML;用户显式要求正文 H1 时必须保留,不能用 --name 替代",
|
||||
"已知目标文档文件夹 --folder 或知识库 --workspace;长 Markdown 仍直接使用本命令,由原生写入管道自动分片;返回 nodeId 绑定同一请求的后续 list/insert/export,后续可观察操作仍须分别执行"
|
||||
],
|
||||
"avoid_when": [
|
||||
"创建表格/脑图/白板/多维表/演示改用 dws wiki node create --type <type>(勿用 doc create)",
|
||||
"在知识库建空节点实体也可用 wiki node create;本命令侧重可写初始内容的 adoc",
|
||||
"导入本地 Word/Markdown 为在线文档改用 dws doc import(若可用)或 upload --convert"
|
||||
"只管理知识库空节点或层级时用 wiki node create;本命令侧重创建并写入 adoc",
|
||||
"导入本地 Word/Markdown 并保留服务端转换语义时用 dws doc import;不要用自写分片脚本替代原生 create"
|
||||
],
|
||||
"examples": [
|
||||
"dws doc create --name \"项目周报\" --format json",
|
||||
"dws doc create --name \"Q1 总结\" --content \"# Q1 总结\" --folder <FOLDER_ID> --format json"
|
||||
"dws doc create --name \"Q1 总结\" --content-file ./q1.md --workspace <WORKSPACE_ID> --format json"
|
||||
],
|
||||
"reviewed": true,
|
||||
"review_reason": "人工审阅:结合本机 dws schema 实时 MCP description/parameters(经 interface_ref)、产品 Skill、Cobra Long 与 Runtime 确认门禁,按飞书风格重写选型文案;不改变命令身份、参数契约或接口绑定。",
|
||||
"review_reason": "人工对齐当前 Cobra 原生内容写入管道、显式正文 H1、同请求新 nodeId 绑定和可观察块操作边界;强调长内容无需外部分片,不改变命令身份、参数或安全事实。",
|
||||
"source_refs": [
|
||||
"CommandRegistry:canonical_path=doc.create_document",
|
||||
"cobra-help:dws doc create",
|
||||
@@ -295,12 +295,12 @@
|
||||
"doc.get_document_content": {
|
||||
"agent_summary": "读取完整文档内容,或按 outline/range/section/tags 获取 JSONML fragment",
|
||||
"use_when": [
|
||||
"用户要读取钉钉在线文字文档(adoc)正文(Markdown)时",
|
||||
"用户直接粘贴文档 URL 且无其他指令时(默认读内容)",
|
||||
"已由 drive info 确认 extension=adoc,用户要读取正文(Markdown)时",
|
||||
"用户提供已知 adoc nodeId/URL,且要读取内容或抽取指定章节时",
|
||||
"只需标题大纲、指定块区间/单块或特定 JSONML tags 时使用 --content-format jsonml 与 --scope"
|
||||
],
|
||||
"avoid_when": [
|
||||
"非 adoc(表格/多维表/普通文件)不要用本命令;先 doc info 再路由",
|
||||
"原始 alidocs URL 类型未知或目标不是 adoc 时先用 drive info 探测并路由;表格/多维表/普通文件不要用本命令",
|
||||
"要元信息用 doc info;要块结构用 doc block list",
|
||||
"Markdown 为有损投影:保形复制模板请用 doc copy,不要 read→create"
|
||||
],
|
||||
@@ -309,7 +309,7 @@
|
||||
"dws doc read --node <DOC_ID> --content-format jsonml --scope outline --max-depth 3"
|
||||
],
|
||||
"reviewed": true,
|
||||
"review_reason": "人工审阅:结合本机 dws schema 实时 MCP description/parameters(经 interface_ref)、产品 Skill、Cobra Long 与 Runtime 确认门禁,按飞书风格重写选型文案;不改变命令身份、参数契约或接口绑定。",
|
||||
"review_reason": "人工对齐 doc Skill 与 doc-info reference 的 extension 预检边界,并保留 Markdown/JSONML scope 的真实 Cobra 契约。",
|
||||
"source_refs": [
|
||||
"CommandRegistry:canonical_path=doc.get_document_content",
|
||||
"cobra-help:dws doc read",
|
||||
@@ -321,22 +321,23 @@
|
||||
]
|
||||
},
|
||||
"doc.get_document_info": {
|
||||
"agent_summary": "获取文档元信息(标题/类型/创建者/权限等)",
|
||||
"agent_summary": "在已确认是 ALIDOC 后读取文档专属元信息",
|
||||
"use_when": [
|
||||
"用户要查看文档/节点元信息(标题、类型、创建者、权限)时",
|
||||
"准备读内容前必须先看 contentType/extension 以路由到 read/sheet/aitable/download 时"
|
||||
"drive info 已确认是 ALIDOC,用户还要标题、创建者、权限或 docUrl 等文档专属元信息时",
|
||||
"创建响应只有 nodeId、缺少 docUrl,需要补查文档链接时"
|
||||
],
|
||||
"avoid_when": [
|
||||
"已确认是 adoc 且只要正文改用 dws doc read",
|
||||
"原始 alidocs URL 的类型探测、extension 路由或可靠 fileSize 使用 dws drive info;不要先猜是文档",
|
||||
"已确认是 adoc 且只要正文时改用 dws doc read",
|
||||
"只要目录列表改用 dws drive list / wiki node list",
|
||||
"需要可靠文件大小 fileSize 时改用 dws drive info;文档元信息接口可能不返回大小"
|
||||
"普通文件、电子表格或 AI 表格不使用本命令"
|
||||
],
|
||||
"examples": [
|
||||
"dws doc info --node <DOC_ID> --format json",
|
||||
"dws doc info --node \"https://alidocs.dingtalk.com/i/nodes/<DOC_UUID>\" --format json"
|
||||
],
|
||||
"reviewed": true,
|
||||
"review_reason": "人工审阅:结合本机 dws schema 实时 MCP description/parameters(经 interface_ref)、产品 Skill、Cobra Long 与 Runtime 确认门禁,按飞书风格重写选型文案;不改变命令身份、参数契约或接口绑定。",
|
||||
"review_reason": "人工对齐 drive info 统一类型探测入口与 doc info 的文档专属补查职责;不改变 node 参数或接口绑定。",
|
||||
"source_refs": [
|
||||
"CommandRegistry:canonical_path=doc.get_document_info",
|
||||
"cobra-help:dws doc info",
|
||||
@@ -371,10 +372,11 @@
|
||||
"doc.insert_document_block": {
|
||||
"agent_summary": "向文档插入块元素",
|
||||
"use_when": [
|
||||
"在文档中插入新块(段落/标题等);简单场景用 --text/--heading,复杂块用 --element JSON"
|
||||
"用户明确要求在文档中插入新块(段落/标题/列表等)时;简单段落/标题用 --text/--heading,复杂块用 --element JSON",
|
||||
"用户要求有序列表块时使用 JSONML p.list.isOrdered=true(同一 listId)或等价原生 orderedList 结构,不能用普通 Markdown 代替"
|
||||
],
|
||||
"avoid_when": [
|
||||
"整篇追加 Markdown 优先 doc update --mode append",
|
||||
"仅在用户没有指定块操作、只要整篇追加 Markdown 时优先 doc update --mode append;显式 insert/标题块/列表块不能折叠",
|
||||
"插入本地文件附件优先 doc media insert",
|
||||
"删块用 block delete;改已有块用 block update"
|
||||
],
|
||||
@@ -383,7 +385,7 @@
|
||||
"dws doc block insert --node <DOC_ID> --heading \"二级标题\" --level 2 --format json"
|
||||
],
|
||||
"reviewed": true,
|
||||
"review_reason": "人工审阅:结合本机 dws schema 实时 MCP description/parameters(经 interface_ref)、产品 Skill、Cobra Long 与 Runtime 确认门禁,按飞书风格重写选型文案;不改变命令身份、参数契约或接口绑定。",
|
||||
"review_reason": "人工对齐显式 block insert 的可观察操作语义和有序列表原生结构;不改变命令身份、参数契约、接口绑定或 Runtime 门禁。",
|
||||
"source_refs": [
|
||||
"CommandRegistry:canonical_path=doc.insert_document_block",
|
||||
"cobra-help:dws doc block insert",
|
||||
@@ -420,7 +422,7 @@
|
||||
"doc.list_document_blocks": {
|
||||
"agent_summary": "查询文档一级块元素列表",
|
||||
"use_when": [
|
||||
"查看文档一级块结构、拿 blockId,供 insert/update/delete 或划词评论定位时"
|
||||
"查看文档一级块结构、拿 blockId,供 insert/update/delete 或划词评论定位时;用户在显式工作流中点名 list 时必须真实执行并使用返回结果"
|
||||
],
|
||||
"avoid_when": [
|
||||
"只要全文 Markdown 用 doc read",
|
||||
@@ -431,7 +433,7 @@
|
||||
"dws doc block list --node <DOC_ID> --start-index 0 --end-index 5 --format json"
|
||||
],
|
||||
"reviewed": true,
|
||||
"review_reason": "人工审阅:结合本机 dws schema 实时 MCP description/parameters(经 interface_ref)、产品 Skill、Cobra Long 与 Runtime 确认门禁,按飞书风格重写选型文案;不改变命令身份、参数契约或接口绑定。",
|
||||
"review_reason": "人工对齐 block list 的显式可观察步骤和真实 blockId 上下文传递;不改变命令身份、参数契约或接口绑定。",
|
||||
"source_refs": [
|
||||
"CommandRegistry:canonical_path=doc.list_document_blocks",
|
||||
"cobra-help:dws doc block list",
|
||||
@@ -719,17 +721,18 @@
|
||||
"doc.update_comment": {
|
||||
"agent_summary": "更新指定文档评论的文字内容和可选 @用户/@群。",
|
||||
"use_when": [
|
||||
"修改已有评论正文;可选更新 --mention 或 --mentioned-open-conversation-id"
|
||||
"修改已有评论正文;可选更新 --mention 或 --mentioned-open-conversation-id;执行后必须用 comment list 回查同一 commentKey 的目标字段"
|
||||
],
|
||||
"avoid_when": [
|
||||
"删除评论用 delete;回复用 reply"
|
||||
"删除评论用 delete;回复用 reply",
|
||||
"响应为 null/空对象或回查仍是旧正文时不能判成功,必须报告更新未生效或部分完成"
|
||||
],
|
||||
"examples": [
|
||||
"dws doc comment update --node <DOC_ID> --comment-key <COMMENT_KEY> --content \"已按最新数据修正\" --format json",
|
||||
"dws doc comment update --node <DOC_ID> --comment-key <COMMENT_KEY> --content \"请群内确认\" --mentioned-open-conversation-id <openConversationId>"
|
||||
],
|
||||
"reviewed": true,
|
||||
"review_reason": "人工审阅:结合本机 dws schema 实时 MCP description/parameters(经 interface_ref)、产品 Skill、Cobra Long 与 Runtime 确认门禁,按飞书风格重写选型文案;不改变命令身份、参数契约或接口绑定。",
|
||||
"review_reason": "人工对齐评论更新的业务结果与 comment list 回查语义;null/空响应不能单独证明成功,不改变命令身份、参数契约或接口绑定。",
|
||||
"source_refs": [
|
||||
"internal/cli/schema_command_registry.json#doc.update_comment",
|
||||
"cobra-help:dws doc comment update --help",
|
||||
@@ -739,23 +742,24 @@
|
||||
]
|
||||
},
|
||||
"doc.update_document": {
|
||||
"agent_summary": "更新文档内容(追加 / 覆盖;覆盖需 --yes)",
|
||||
"agent_summary": "用原生自动分片管道追加或整篇覆盖 adoc 内容",
|
||||
"use_when": [
|
||||
"用户要向已有 adoc 追加内容时用 --mode append(更安全)",
|
||||
"用户要向已有 adoc 追加长、多行或文件内容时用 --mode append + --content-file;CLI 自动分片",
|
||||
"用户明确要求整篇覆盖替换时用 --mode overwrite(破坏性)",
|
||||
"append 且要插到第 N 个 block 前时加 --index N(先 block list)"
|
||||
],
|
||||
"avoid_when": [
|
||||
"目标不是 adoc 或只要改单个块时改用 doc block update",
|
||||
"只在末尾补一小段纯文本优先 +doc-append;只改一个块用 doc block update",
|
||||
"目标不是 adoc 时按 drive info 的 extension 切对应产品",
|
||||
"覆盖模式用户未确认前不要执行;可先 --dry-run 预览",
|
||||
"创建新文档用 doc create,不要用 update 冒充创建"
|
||||
"创建新文档用 doc create;不要预先手工分片或循环重试覆盖"
|
||||
],
|
||||
"examples": [
|
||||
"dws doc update --node <DOC_ID> --content \"# 追加内容\" --mode append --format json",
|
||||
"dws doc update --node <DOC_ID> --content-file ./body.md --mode overwrite --dry-run"
|
||||
],
|
||||
"reviewed": true,
|
||||
"review_reason": "人工审阅:结合本机 dws schema 实时 MCP description/parameters(经 interface_ref)、产品 Skill、Cobra Long 与 Runtime 确认门禁,按飞书风格重写选型文案;不改变命令身份、参数契约或接口绑定。",
|
||||
"review_reason": "人工对齐 Runtime 的 10000 字符自动分片、append/overwrite 动态门禁与根 Skill 的写后回读流程;不改变参数和安全事实。",
|
||||
"source_refs": [
|
||||
"CommandRegistry:canonical_path=doc.update_document",
|
||||
"cobra-help:dws doc update",
|
||||
@@ -905,14 +909,14 @@
|
||||
"当你只记得文档的标题或主题词、需要先定位到某篇钉钉文档拿到它的 nodeId/URL 以便后续阅读或编辑时使用;可按关键词、扩展名、创建/访问时间、创建者等条件过滤,不传关键词则返回最近访问的文档,返回匹配的文档列表。"
|
||||
],
|
||||
"avoid_when": [
|
||||
"需要该 Shortcut 未公开的底层参数、原始响应或不同执行语义时,改用对应原子命令"
|
||||
"只有一个关键词且只要紧凑标题/URL/type/token 投影时优先 +find-doc;目标已给 nodeId/URL 时不要再搜索"
|
||||
],
|
||||
"examples": [
|
||||
"dws doc +search --query \"会议纪要\"",
|
||||
"dws doc +search --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.",
|
||||
"review_reason": "人工区分 +search 的丰富过滤/最近访问能力与 +find-doc 的单关键词紧凑投影,避免两个同源搜索 Shortcut 互相争抢。",
|
||||
"source_refs": [
|
||||
"internal/cli/schema_command_registry.json#doc.shortcut_search",
|
||||
"cobra-help:dws doc +search",
|
||||
@@ -925,14 +929,14 @@
|
||||
"当你已知某个文档文件夹或知识库的 ID、想浏览它下面直接包含的文档与子文件夹(不递归深层)以便逐层导航时使用;输入 folder 或 workspace,返回该层级的子节点列表。"
|
||||
],
|
||||
"avoid_when": [
|
||||
"需要该 Shortcut 未公开的底层参数、原始响应或不同执行语义时,改用对应原子命令"
|
||||
"全局或钉盘目录浏览优先 drive list,知识库节点树优先 wiki node list;本 Shortcut 只列已知 doc folder/workspace 的直接子节点"
|
||||
],
|
||||
"examples": [
|
||||
"dws doc +list --folder DOC_FOLDER_NODE_ID",
|
||||
"dws doc +list --workspace WS_ID --limit 20"
|
||||
],
|
||||
"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.",
|
||||
"review_reason": "人工对齐 doc/drive/wiki 产品边界:保留已知文档文件夹/知识库的直接子节点投影,不替代常规目录管理。",
|
||||
"source_refs": [
|
||||
"internal/cli/schema_command_registry.json#doc.shortcut_list",
|
||||
"cobra-help:dws doc +list",
|
||||
@@ -945,13 +949,13 @@
|
||||
"当你想保留原件、在另一个文件夹或知识库里生成一份文档/文件副本(例如以某篇文档为模板另存)时使用;输入源 node 与目标 folder/workspace,会实际创建一个副本。"
|
||||
],
|
||||
"avoid_when": [
|
||||
"需要该 Shortcut 未公开的底层参数、原始响应或不同执行语义时,改用对应原子命令"
|
||||
"常规文件管理优先 drive copy;要搬走原件用 move;复制后必须从真实返回取副本 nodeId,禁止继续编辑源文档"
|
||||
],
|
||||
"examples": [
|
||||
"dws doc +copy --node DOC_ID --folder TARGET_FOLDER_NODE_ID"
|
||||
],
|
||||
"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.",
|
||||
"review_reason": "人工对齐保形复制、真实副本 ID 与 drive 文件管理边界;保留 Runtime user_required 门禁。",
|
||||
"source_refs": [
|
||||
"internal/cli/schema_command_registry.json#doc.shortcut_copy",
|
||||
"cobra-help:dws doc +copy",
|
||||
@@ -964,13 +968,13 @@
|
||||
"当你要整理文档归属、把某篇文档/文件从当前位置挪到另一个文件夹或知识库(原位置不再保留)时使用;输入 node 与目标 folder/workspace,会实际改变文件的存放位置。"
|
||||
],
|
||||
"avoid_when": [
|
||||
"需要该 Shortcut 未公开的底层参数、原始响应或不同执行语义时,改用对应原子命令"
|
||||
"常规文件管理优先 drive move;要保留原位置副本用 copy;目标位置不明确或未确认时不要移动"
|
||||
],
|
||||
"examples": [
|
||||
"dws doc +move --node DOC_ID --folder TARGET_FOLDER_NODE_ID"
|
||||
],
|
||||
"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.",
|
||||
"review_reason": "人工对齐 move 的原位置消失语义、目标位置确认与 drive 文件管理边界;保留 Runtime user_required 门禁。",
|
||||
"source_refs": [
|
||||
"internal/cli/schema_command_registry.json#doc.shortcut_move",
|
||||
"cobra-help:dws doc +move",
|
||||
@@ -1041,13 +1045,13 @@
|
||||
"当你想把在线文档导出成 docx/markdown/pdf 文件(例如离线保存或外发)时使用;这是异步任务的第一步,输入 node 与 export-format 提交导出,返回 jobId,随后用 +export-get 轮询结果。"
|
||||
],
|
||||
"avoid_when": [
|
||||
"需要该 Shortcut 未公开的底层参数、原始响应或不同执行语义时,改用对应原子命令"
|
||||
"用户要直接拿到本地 docx/markdown/pdf 文件时使用一体化 dws doc export;不要默认手工编排 submit/get 轮询"
|
||||
],
|
||||
"examples": [
|
||||
"dws doc +export-submit --node DOC_ID --export-format markdown"
|
||||
],
|
||||
"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.",
|
||||
"review_reason": "人工对齐 atomic doc export 的一体化提交/轮询/下载路径,将 +export-submit 限定为明确异步控制场景。",
|
||||
"source_refs": [
|
||||
"internal/cli/schema_command_registry.json#doc.shortcut_export_submit",
|
||||
"cobra-help:dws doc +export-submit",
|
||||
@@ -1117,13 +1121,13 @@
|
||||
"当文档被误改、你想把它整体恢复到某个历史版本时使用;先用 +version-list 找到目标版本号,再输入 node 与 version,会实际把文档内容覆盖回该版本,属于高风险写操作,需谨慎确认。"
|
||||
],
|
||||
"avoid_when": [
|
||||
"需要该 Shortcut 未公开的底层参数、原始响应或不同执行语义时,改用对应原子命令"
|
||||
"只查看历史用 +version-list,只保存当前快照用 +version-save;版本号未核实、用户未确认或只需改单块时不要回滚"
|
||||
],
|
||||
"examples": [
|
||||
"dws doc +version-revert --node DOC_ID --version 3"
|
||||
],
|
||||
"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.",
|
||||
"review_reason": "人工对齐 version list → 明确确认 → revert → 回读的 ID 与高风险边界;不改变 typed_yes 安全门禁。",
|
||||
"source_refs": [
|
||||
"internal/cli/schema_command_registry.json#doc.shortcut_version_revert",
|
||||
"cobra-help:dws doc +version-revert",
|
||||
@@ -1174,14 +1178,14 @@
|
||||
"当你只想往一篇钉钉文档的最后面补一段文字、又不想动原有内容时使用;内部用文档更新的“追加(append)”模式,把你给的文本安全地拼到文档末尾,不需要你先去查文档块列表、算末尾位置或手工拼块结构。会真实写入文档内容。"
|
||||
],
|
||||
"avoid_when": [
|
||||
"需要该 Shortcut 未公开的底层参数、原始响应或不同执行语义时,改用对应原子命令"
|
||||
"长、多行、表格或文件内容使用 doc update --mode append --content-file;指定位置或富结构使用 block insert;不要跳过 user_required 确认"
|
||||
],
|
||||
"examples": [
|
||||
"dws doc +doc-append --doc DOC_ID --text \"补充说明:本方案已评审通过。\"",
|
||||
"dws doc +doc-append --doc \"https://alidocs.dingtalk.com/i/nodes/<DOC_UUID>\" --text \"追加一行备注\""
|
||||
],
|
||||
"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.",
|
||||
"review_reason": "人工将 Shortcut 限定为短纯文本末尾追加,并对齐 user_required 与写后回读;长内容交给原生自动分片管道。",
|
||||
"source_refs": [
|
||||
"internal/cli/schema_command_registry.json#doc.shortcut_doc_append",
|
||||
"cobra-help:dws doc +doc-append",
|
||||
@@ -1194,14 +1198,14 @@
|
||||
"当你只记得云文档标题或内容里的某个关键词,想快速按关键词找到匹配的文档、拿到它的标题、URL、类型和 token 以便后续查看或编辑,却不想拿到一大坨原始字段时使用;内部调用云文档的 search_documents 工具,把 --query 作为搜索关键词(keyword),可选地用 --limit 限制返回条数(pageSize),再在本地把每条命中结果精简为「标题、URL、类型、token」四个字段后打印。这是纯只读操作,只做搜索与本地投影,不会创建、修改或删除任何文档;未命中时提示「没搜到文档」。"
|
||||
],
|
||||
"avoid_when": [
|
||||
"需要该 Shortcut 未公开的底层参数、原始响应或不同执行语义时,改用对应原子命令"
|
||||
"需要最近访问、extension/时间/创建者等组合过滤时使用 +search;目标已给 nodeId/URL 时直接进入 info/read,不再搜索"
|
||||
],
|
||||
"examples": [
|
||||
"dws doc +find-doc --query 季度汇报",
|
||||
"dws doc +find-doc --query 合同 --limit 10"
|
||||
],
|
||||
"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.",
|
||||
"review_reason": "人工将 +find-doc 定位为高频单关键词紧凑投影,并与 +search 的丰富过滤能力做互斥路由。",
|
||||
"source_refs": [
|
||||
"internal/cli/schema_command_registry.json#doc.shortcut_find_doc",
|
||||
"cobra-help:dws doc +find-doc",
|
||||
@@ -1214,13 +1218,13 @@
|
||||
"当你手上已经有一个文档链接、想直接私信发给某个人而不必先查 userId 时使用;内部先按姓名搜通讯录解析出唯一用户,再用 openDingTalkId 把链接拼成一条 Markdown 消息发出去,姓名匹配到多人时会列出候选让你区分。只发链接、不读取或改动文档本身,会真实发出消息。"
|
||||
],
|
||||
"avoid_when": [
|
||||
"需要该 Shortcut 未公开的底层参数、原始响应或不同执行语义时,改用对应原子命令"
|
||||
"同名人员未消歧、缺少真实文档 URL 或用户未确认时不要发送;本命令不授予文档权限,授权应走 drive permission"
|
||||
],
|
||||
"examples": [
|
||||
"dws doc +share-doc --to 张三 --url https://docs.dingtalk.com/xxx --note \"帮忙过一下\""
|
||||
],
|
||||
"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.",
|
||||
"review_reason": "人工对齐人员消歧、真实 docUrl、user_required 消息发送门禁与文档权限边界。",
|
||||
"source_refs": [
|
||||
"internal/cli/schema_command_registry.json#doc.shortcut_share_doc",
|
||||
"cobra-help:dws doc +share-doc",
|
||||
|
||||
@@ -910,12 +910,15 @@
|
||||
"agent_summary": "上传本地文件到钉盘或文档空间,或按节点 ID 确认覆盖已有文件",
|
||||
"use_when": [
|
||||
"用户要把本地文件上传到钉盘/我的文件(首选一条命令自动完成凭证+PUT+提交)时",
|
||||
"上传到知识库/文档空间时加 --workspace;需要转在线文档时加 --convert",
|
||||
"用户明确要求只上传或暂存附件、暂时不要发送到任何聊天会话时",
|
||||
"用户只要保留原始文件供存储或下载时使用;上传到知识库/文档空间时可加 --workspace",
|
||||
"用户明确要求用本地文件替换已有钉盘/文档空间文件时传 --node;该模式会覆盖远端内容并要求确认"
|
||||
],
|
||||
"avoid_when": [
|
||||
"用户要把文件发送到群聊或单聊时使用 chat message send --msg-type file --file-path;drive upload 不会发送聊天消息",
|
||||
"常规场景不要拆成 upload-info + 手动 PUT + commit;仅自定义流式上传才用三步",
|
||||
"要把文件作为文档正文附件插入改用 dws doc media insert",
|
||||
"Word/Excel/Markdown 要求上传后在线编辑、大家直接在线改或转钉钉文档时改用 dws doc import;普通 drive upload 返回 docx/xlsx 文件不能宣称可在线编辑",
|
||||
"用户明确说文档空间且走 doc 兼容入口时可用 dws doc upload,默认仍推荐本命令",
|
||||
"用户没有明确同意替换目标文件时不要使用 --node;新建上传应使用 --folder 或目标根目录"
|
||||
],
|
||||
@@ -924,7 +927,7 @@
|
||||
"dws drive upload --file ./README.md --node <dentryUuid> --format json"
|
||||
],
|
||||
"reviewed": true,
|
||||
"review_reason": "人工审阅:结合本机 dws schema 实时 MCP description/parameters(经 interface_ref)、产品 Skill、Cobra Long 与 Runtime 确认门禁,按飞书风格重写选型文案;不改变命令身份、参数契约或接口绑定。",
|
||||
"review_reason": "人工对齐普通文件存储与在线文档导入边界:在线编辑需求硬路由 doc import,drive upload 不承诺转换结果;不改变命令身份、参数契约或接口绑定。",
|
||||
"source_refs": [
|
||||
"CommandRegistry:canonical_path=drive.upload",
|
||||
"cobra-help:dws drive upload",
|
||||
|
||||
+11
-10
@@ -462,9 +462,12 @@ func guardGroupOwnerRemoval(ctx context.Context, openConversationID string, remo
|
||||
if err != nil || ownerOpenID == "" {
|
||||
return nil
|
||||
}
|
||||
ownerErr := fmt.Errorf(
|
||||
"refusing to remove the group owner: 被移除列表包含群主,移出群主将导致群无群主(孤儿群)\n hint: 先执行 dws chat group transfer-owner --group %s --user <newOwnerUserId> 转让群主后再移除",
|
||||
openConversationID,
|
||||
ownerErr := apperrors.NewValidation(
|
||||
"被移除列表包含群主,不能直接移出群主",
|
||||
apperrors.WithReason("group_owner_in_remove_list"),
|
||||
apperrors.WithHint("清理临时群成员时应从 --users 中移除群主,只移除本次加入的普通成员;只有用户明确要求变更群主时才单独执行 transfer-owner。"),
|
||||
apperrors.WithActions("从 --users 中移除群主后重试", "若用户明确要求转让群主,先确认新群主再单独执行 transfer-owner"),
|
||||
apperrors.WithExamples(fmt.Sprintf("dws chat group members remove --id %s --users <普通成员userId列表> --format json", openConversationID)),
|
||||
)
|
||||
userIDs, openDingTalkIDs := splitChatIDValues(removeValues)
|
||||
for _, id := range openDingTalkIDs {
|
||||
@@ -2238,7 +2241,7 @@ func newChatCommand() *cobra.Command {
|
||||
hasCondition := false
|
||||
for _, name := range []string{
|
||||
"query", "keyword", "user", "users", "userId", "sender-ids", "senders", "sender",
|
||||
"at-ids", "conversation-ids", "groups", "group", "message-type",
|
||||
"at-ids", "conversation-ids", "conversation-id", "groups", "group", "message-type",
|
||||
"conversation-type", "search-conv-type", "start", "end",
|
||||
} {
|
||||
if value, _ := cmd.Flags().GetString(name); strings.TrimSpace(value) != "" {
|
||||
@@ -2291,6 +2294,8 @@ func newChatCommand() *cobra.Command {
|
||||
convIds := ""
|
||||
if v, _ := cmd.Flags().GetString("conversation-ids"); v != "" {
|
||||
convIds = v
|
||||
} else if v, _ := cmd.Flags().GetString("conversation-id"); v != "" {
|
||||
convIds = v
|
||||
} else if v, _ := cmd.Flags().GetString("groups"); v != "" {
|
||||
convIds = v
|
||||
} else if v, _ := cmd.Flags().GetString("group"); v != "" {
|
||||
@@ -2581,7 +2586,6 @@ func newChatCommand() *cobra.Command {
|
||||
_ = chatGroupMemberRemoveCmd.MarkFlagRequired("users")
|
||||
|
||||
chatGroupCmd.AddCommand(chatGroupCreateCmd, chatGroupMembersCmd, chatGroupRenameCmd)
|
||||
chatGroupCmd.AddCommand(hintSubCmd("search", "use: dws chat search --query <关键词>"))
|
||||
chatGroupMembersCmd.AddCommand(chatGroupMemberAddCmd, chatGroupMemberRemoveCmd, chatGroupMembersAddBotCmd)
|
||||
|
||||
// message 子命令 flags
|
||||
@@ -2816,6 +2820,8 @@ func newChatCommand() *cobra.Command {
|
||||
chatMessageSearchAdvancedCmd.Flags().Bool("at-me", false, "只搜索 @我 的消息(可选,默认 false)")
|
||||
chatMessageSearchAdvancedCmd.Flags().String("at-ids", "", "@指定人的 openDingTalkId 列表,逗号分隔(可选)")
|
||||
chatMessageSearchAdvancedCmd.Flags().String("conversation-ids", "", "会话 openConversationId 列表,逗号分隔(可选,群聊或单聊均可,不传则搜索所有会话)")
|
||||
chatMessageSearchAdvancedCmd.Flags().String("conversation-id", "", "--conversation-ids 的单值兼容别名")
|
||||
_ = chatMessageSearchAdvancedCmd.Flags().MarkHidden("conversation-id")
|
||||
chatMessageSearchAdvancedCmd.Flags().String("groups", "", "--conversation-ids 的别名")
|
||||
_ = chatMessageSearchAdvancedCmd.Flags().MarkHidden("groups")
|
||||
chatMessageSearchAdvancedCmd.Flags().String("group", "", "")
|
||||
@@ -5736,10 +5742,5 @@ pl_PL, sv_SE, fi_FI, cs_CZ, ar_SA, tl_PH, he_IL, nl_NL, lo_LA, it_IT`,
|
||||
|
||||
root.AddCommand(chatChmodCmd, chatDataAuthCmd, chatGroupCmd, chatSearchCmd, chatSearchCommonCmd, chatMessageCmd, chatFileCmd, newChatMediaGroup(), chatBotCmd, chatMessageListTopConversationsCmd, chatConversationInfoCmd, chatCategoryCmd, chatGroupRoleCmd, chatMuteCmd, chatSetTopCmd, chatGroupMuteCmd, chatGroupMuteMemberCmd, chatHideCmd, chatMuteAtAllCmd, chatMuteRedEnvelopeCmd, chatMarkUnreadCmd, chatClearRedPointCmd, chatClearAllRedPointCmd, chatListAllConversationsCmd, chatClearMessagesCmd, chatMarkReadCmd, chatTextCmd)
|
||||
|
||||
// hint: dws chat send → dws chat message send
|
||||
root.AddCommand(hintSubCmd("send", "use: dws chat message send"))
|
||||
// hint: dws chat history → dws chat message list
|
||||
root.AddCommand(hintSubCmd("history", "use: dws chat message list --group <GROUP_OPEN_CONVERSATION_ID>"))
|
||||
|
||||
return root
|
||||
}
|
||||
|
||||
@@ -10,6 +10,7 @@ import (
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
apperrors "github.com/DingTalk-Real-AI/dingtalk-workspace-cli/internal/errors"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
@@ -406,7 +407,13 @@ func TestCrossPlatformCoverageGuardGroupOwnerRemovalCoverage(t *testing.T) {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
caller := &scriptedToolCaller{steps: tc.steps}
|
||||
installScriptedCaller(t, caller)
|
||||
_ = guardGroupOwnerRemoval(context.Background(), "group", tc.remove)
|
||||
err := guardGroupOwnerRemoval(context.Background(), "group", tc.remove)
|
||||
if tc.name == "owner-open" {
|
||||
var typed *apperrors.Error
|
||||
if !errors.As(err, &typed) || typed.Reason != "group_owner_in_remove_list" || !strings.Contains(typed.Hint, "从 --users 中移除群主") || strings.Contains(typed.Hint, "先执行 dws chat group transfer-owner") {
|
||||
t.Fatalf("owner removal hint = %v", err)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
caller := &scriptedToolCaller{steps: []scriptedToolStep{{text: `{}`}}}
|
||||
|
||||
@@ -1070,9 +1070,6 @@ func newDocCommand() *cobra.Command {
|
||||
})
|
||||
}
|
||||
if md != "" {
|
||||
if name, ok := toolArgs["name"].(string); ok && name != "" {
|
||||
md = stripDuplicateTitle(md, name)
|
||||
}
|
||||
toolArgs["markdown"] = md
|
||||
}
|
||||
if md != "" {
|
||||
@@ -3218,53 +3215,6 @@ func pollDocExportJob(ctx context.Context, jobID string) (downloadURL string, er
|
||||
return "", fmt.Errorf("导出任务超时:已轮询 %d 次仍在处理中 (jobId=%s),请稍后使用 dws doc export get --job-id %s 手动查询", maxPolls, jobID, jobID)
|
||||
}
|
||||
|
||||
// stripDuplicateTitle removes the leading H1 heading from markdown content
|
||||
// when it matches the document name (set via --name). This prevents the title
|
||||
// from appearing twice: once as document metadata and once in the body.
|
||||
func stripDuplicateTitle(markdown, name string) string {
|
||||
trimmed := strings.TrimLeft(markdown, " \t\n\r")
|
||||
if !strings.HasPrefix(trimmed, "# ") {
|
||||
return markdown
|
||||
}
|
||||
newlineIdx := strings.Index(trimmed, "\n")
|
||||
var headingRaw string
|
||||
if newlineIdx < 0 {
|
||||
headingRaw = trimmed[2:]
|
||||
} else {
|
||||
headingRaw = trimmed[2:newlineIdx]
|
||||
}
|
||||
|
||||
if normalizeHeadingText(headingRaw) != normalizeHeadingText(name) {
|
||||
return markdown
|
||||
}
|
||||
|
||||
if newlineIdx < 0 {
|
||||
return ""
|
||||
}
|
||||
rest := trimmed[newlineIdx+1:]
|
||||
rest = strings.TrimLeft(rest, "\n")
|
||||
return rest
|
||||
}
|
||||
|
||||
// normalizeHeadingText strips trailing ATX hashes, inline markdown formatting
|
||||
// markers, then returns a lowercased, trimmed string for comparison.
|
||||
func normalizeHeadingText(s string) string {
|
||||
s = strings.TrimSpace(s)
|
||||
if s == "" {
|
||||
return ""
|
||||
}
|
||||
if i := strings.LastIndexByte(s, ' '); i >= 0 {
|
||||
suffix := s[i+1:]
|
||||
if len(suffix) > 0 && strings.Trim(suffix, "#") == "" {
|
||||
s = strings.TrimSpace(s[:i])
|
||||
}
|
||||
}
|
||||
for _, m := range []string{"**", "__", "~~", "*", "_", "`"} {
|
||||
s = strings.ReplaceAll(s, m, "")
|
||||
}
|
||||
return strings.TrimSpace(strings.ToLower(s))
|
||||
}
|
||||
|
||||
// parseCommentMentionIds splits a comma-separated string of user IDs into a slice.
|
||||
func parseCommentMentionIds(raw string) []string {
|
||||
parts := strings.Split(raw, ",")
|
||||
|
||||
@@ -295,12 +295,6 @@ func TestCrossPlatformCoverageDocCreateUpdateAndBlockCommandEdges(t *testing.T)
|
||||
})
|
||||
}
|
||||
|
||||
for _, value := range []string{"plain", "# Other\nbody", "# Name", "# **Name** ###\n\nbody"} {
|
||||
_ = stripDuplicateTitle(value, "Name")
|
||||
}
|
||||
for _, value := range []string{"", " Name ### ", "**Bold**", "__Under__ ~~Strike~~ `Code`"} {
|
||||
_ = normalizeHeadingText(value)
|
||||
}
|
||||
for _, name := range []string{"file.pdf", "file.md", "file.unknown"} {
|
||||
_ = inferMimeType(name)
|
||||
}
|
||||
|
||||
@@ -0,0 +1,74 @@
|
||||
package helpers
|
||||
|
||||
import (
|
||||
"context"
|
||||
"io"
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
"github.com/DingTalk-Real-AI/dingtalk-workspace-cli/pkg/edition"
|
||||
)
|
||||
|
||||
type docCreateRecordingCall struct {
|
||||
tool string
|
||||
args map[string]any
|
||||
}
|
||||
|
||||
type docCreateRecordingCaller struct {
|
||||
calls []docCreateRecordingCall
|
||||
}
|
||||
|
||||
func (c *docCreateRecordingCaller) CallTool(_ context.Context, _ string, tool string, args map[string]any) (*edition.ToolResult, error) {
|
||||
copied := make(map[string]any, len(args))
|
||||
for key, value := range args {
|
||||
copied[key] = value
|
||||
}
|
||||
c.calls = append(c.calls, docCreateRecordingCall{tool: tool, args: copied})
|
||||
return textToolResult(`{"nodeId":"node-1","success":true}`), nil
|
||||
}
|
||||
|
||||
func (*docCreateRecordingCaller) Format() string { return "json" }
|
||||
func (*docCreateRecordingCaller) DryRun() bool { return false }
|
||||
func (*docCreateRecordingCaller) Fields() string { return "" }
|
||||
func (*docCreateRecordingCaller) JQ() string { return "" }
|
||||
|
||||
func TestDocCreatePreservesExplicitLeadingH1MatchingName(t *testing.T) {
|
||||
oldArgs := os.Args
|
||||
os.Args = []string{"dws", "doc"}
|
||||
t.Cleanup(func() { os.Args = oldArgs })
|
||||
|
||||
for _, content := range []string{
|
||||
"# 需求清单",
|
||||
"# 需求清单\n\n以上需求已与产品确认",
|
||||
} {
|
||||
t.Run(content, func(t *testing.T) {
|
||||
previous := deps
|
||||
caller := &docCreateRecordingCaller{}
|
||||
InitDeps(caller)
|
||||
deps.Out.w = io.Discard
|
||||
deps.Out.errW = io.Discard
|
||||
t.Cleanup(func() { deps = previous })
|
||||
|
||||
root := newDocCommand()
|
||||
root.SilenceErrors = true
|
||||
root.SilenceUsage = true
|
||||
root.SetOut(io.Discard)
|
||||
root.SetErr(io.Discard)
|
||||
root.SetArgs([]string{"create", "--name", "需求清单", "--content", content})
|
||||
|
||||
if err := root.ExecuteContext(context.Background()); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if len(caller.calls) != 1 {
|
||||
t.Fatalf("tool calls = %#v, want one create_document call", caller.calls)
|
||||
}
|
||||
call := caller.calls[0]
|
||||
if call.tool != "create_document" {
|
||||
t.Fatalf("tool = %q, want create_document", call.tool)
|
||||
}
|
||||
if got := call.args["markdown"]; got != content {
|
||||
t.Fatalf("markdown = %#v, want exact explicit body H1 %#v", got, content)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -65,7 +65,7 @@ var AtMe = shortcut.Shortcut{
|
||||
`dws chat +at-me`,
|
||||
`dws chat +at-me --days 3`,
|
||||
},
|
||||
Validate: chatshortcut.ValidateMessageResourceDownload,
|
||||
Validate: validateAtMe,
|
||||
Execute: func(rt *shortcut.RuntimeContext) error {
|
||||
// Step 1 — look-back window [now-Nd, now] in epoch millis. days defaults
|
||||
// to 7; guard against non-positive overrides so the window stays sane.
|
||||
@@ -109,6 +109,23 @@ var AtMe = shortcut.Shortcut{
|
||||
},
|
||||
}
|
||||
|
||||
func validateAtMe(rt *shortcut.RuntimeContext) error {
|
||||
if err := chatshortcut.ValidateMessageResourceDownload(rt); err != nil {
|
||||
return err
|
||||
}
|
||||
days := rt.Int("days")
|
||||
if days <= 0 {
|
||||
return localChatOptionError("invalid_lookback_window", "+at-me 的 --days 必须大于 0", "--days")
|
||||
}
|
||||
if days > 3650 {
|
||||
return localChatOptionError("lookback_window_too_large", "+at-me 的 --days 超出支持范围 1-3650", "--days")
|
||||
}
|
||||
if rt.Int("limit") <= 0 {
|
||||
return localChatOptionError("invalid_page_size", "+at-me 的 --limit 必须大于 0", "--limit")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// atMeMessageItems locates the message list inside a search_at_me_message
|
||||
// response, probing common container keys at the top level and nested under
|
||||
// "result". Returns nil when no list is found.
|
||||
|
||||
@@ -22,6 +22,22 @@ func TestPreferExactGroupMatches(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestLooksLikeOpenConversationID(t *testing.T) {
|
||||
for _, value := range []string{
|
||||
"cidayZx5r0T+UiMi6NrO1048A==",
|
||||
"cidr0zKX5dvj/c0wDO+wupqyg==",
|
||||
} {
|
||||
if !looksLikeOpenConversationID(value) {
|
||||
t.Errorf("looksLikeOpenConversationID(%q) = false", value)
|
||||
}
|
||||
}
|
||||
for _, value := range []string{"项目冲刺", "cid项目群", "city project"} {
|
||||
if looksLikeOpenConversationID(value) {
|
||||
t.Errorf("looksLikeOpenConversationID(%q) = true", value)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestResolveMemberTypes(t *testing.T) {
|
||||
users, bots, err := resolveMemberTypes(nil)
|
||||
if err != nil || !users || !bots {
|
||||
|
||||
@@ -17,6 +17,7 @@ import (
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
apperrors "github.com/DingTalk-Real-AI/dingtalk-workspace-cli/internal/errors"
|
||||
"github.com/DingTalk-Real-AI/dingtalk-workspace-cli/internal/shortcut"
|
||||
chatshortcut "github.com/DingTalk-Real-AI/dingtalk-workspace-cli/internal/shortcut/chat"
|
||||
"github.com/DingTalk-Real-AI/dingtalk-workspace-cli/internal/shortcut/chatmsg"
|
||||
@@ -66,8 +67,10 @@ var ChatMessages = shortcut.Shortcut{
|
||||
{Name: "user", Type: shortcut.FlagString, Desc: "单聊对方的 userId,与 --group 互斥"},
|
||||
{Name: "open-dingtalk-id", Type: shortcut.FlagString, Desc: "单聊对方的 openDingTalkId,与 --group/--user 互斥"},
|
||||
{Name: "time", Type: shortcut.FlagString, Desc: "时间边界,如 \"2025-03-01 00:00:00\";省略时从当前时间向前读取最近消息"},
|
||||
{Name: "before", Type: shortcut.FlagString, Desc: "--time <值> --direction older 的兼容写法", Hidden: true},
|
||||
{Name: "limit", Type: shortcut.FlagInt, Desc: "每页拉取的消息条数(可选)"},
|
||||
{Name: "size", Type: shortcut.FlagInt, Desc: "--limit 的旧版别名", Hidden: true},
|
||||
{Name: "page-all", Type: shortcut.FlagBool, Desc: "暂不支持;请按返回游标显式翻页", Hidden: true},
|
||||
{Name: "direction", Type: shortcut.FlagString, Enum: []string{"newer", "older"}, Desc: "时间方向 newer/older;省略时为 older,从时间边界向前读取"},
|
||||
{Name: "no-reactions", Type: shortcut.FlagBool, Desc: "不输出消息 reaction(默认输出)"},
|
||||
}, chatshortcut.MessageResourceDownloadFlags()...),
|
||||
@@ -79,7 +82,7 @@ var ChatMessages = shortcut.Shortcut{
|
||||
`dws chat +chat-messages --user <userId> --time "2025-03-01 00:00:00" --limit 50`,
|
||||
`dws chat +chat-messages --group <openconversation_id> --direction older`,
|
||||
},
|
||||
Validate: chatshortcut.ValidateMessageResourceDownload,
|
||||
Validate: validateChatMessages,
|
||||
Execute: func(rt *shortcut.RuntimeContext) error {
|
||||
// Step 1 — build params and pick the right tool. Param keys
|
||||
// (openconversation_id / userId / time / forward / limit) match the MCP server
|
||||
@@ -88,8 +91,8 @@ var ChatMessages = shortcut.Shortcut{
|
||||
params := map[string]any{}
|
||||
fallbackConversationID := ""
|
||||
|
||||
if rt.Changed("time") && rt.Str("time") != "" {
|
||||
params["time"] = rt.Str("time")
|
||||
if boundary := rt.StrFirst("time", "before"); boundary != "" {
|
||||
params["time"] = boundary
|
||||
} else {
|
||||
params["time"] = formatDingTalkMessageBoundary(time.Now())
|
||||
}
|
||||
@@ -99,7 +102,9 @@ var ChatMessages = shortcut.Shortcut{
|
||||
// direction newer/older maps to the tools' boolean `forward` param
|
||||
// (newer -> forward=true, older -> forward=false), matching chat.go's
|
||||
// resolveMessageForward.
|
||||
if rt.Changed("direction") {
|
||||
if rt.Changed("before") {
|
||||
params["forward"] = false
|
||||
} else if rt.Changed("direction") {
|
||||
switch strings.TrimSpace(strings.ToLower(rt.Str("direction"))) {
|
||||
case "newer":
|
||||
params["forward"] = true
|
||||
@@ -148,6 +153,45 @@ var ChatMessages = shortcut.Shortcut{
|
||||
},
|
||||
}
|
||||
|
||||
func validateChatMessages(rt *shortcut.RuntimeContext) error {
|
||||
if err := chatshortcut.ValidateMessageResourceDownload(rt); err != nil {
|
||||
return err
|
||||
}
|
||||
if rt.Changed("time") && rt.Changed("before") {
|
||||
return localChatOptionError("conflicting_time_options", "+chat-messages 的 --time 与 --before 不能同时使用", "--time", "--before")
|
||||
}
|
||||
if rt.Changed("before") && rt.Changed("direction") && strings.ToLower(rt.Str("direction")) != "older" {
|
||||
return localChatOptionError("incompatible_time_direction", "+chat-messages 的 --before 不能与 --direction newer 同时使用", "--before", "--direction")
|
||||
}
|
||||
if groupID := rt.StrFirst("group", "conversation-id", "id"); groupID != "" && looksLikeHumanGroupName(groupID) {
|
||||
flag := changedConversationIDFlag(rt)
|
||||
return localChatOptionError("group_name_used_as_conversation_id", "+chat-messages 的 "+flag+" 需要 openConversationId,当前值像群名", flag)
|
||||
}
|
||||
if (rt.Changed("limit") && rt.Int("limit") <= 0) || (rt.Changed("size") && rt.Int("size") <= 0) {
|
||||
flag := "--limit"
|
||||
if rt.Changed("size") {
|
||||
flag = "--size"
|
||||
}
|
||||
return localChatOptionError("invalid_page_size", "+chat-messages 的 "+flag+" 必须大于 0", flag)
|
||||
}
|
||||
if boundary := rt.StrFirst("time", "before"); boundary != "" && !validChatTime(boundary) {
|
||||
flag := "--time"
|
||||
if rt.Changed("before") {
|
||||
flag = "--before"
|
||||
}
|
||||
return localChatOptionError("invalid_time_boundary", "+chat-messages 的 "+flag+" 格式无效", flag)
|
||||
}
|
||||
if rt.Bool("page-all") {
|
||||
return apperrors.NewValidation(
|
||||
"+chat-messages 暂不支持 --page-all",
|
||||
apperrors.WithReason("page_all_not_supported"),
|
||||
apperrors.WithActions("先执行单页查询,再使用返回的游标继续翻页"),
|
||||
apperrors.WithExamples(`dws chat +chat-messages --group <openConversationId> --time "2026-07-30 16:51:39" --direction older --format json`),
|
||||
)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// chatMessageItems defensively unwraps the message list from the response,
|
||||
// tolerating the common container keys and one level of nesting under a
|
||||
// "result"/"data" wrapper.
|
||||
|
||||
@@ -260,3 +260,117 @@ func TestCrossPlatformCoverageCompatibilityAliases(t *testing.T) {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestSendToGroupRejectsNonNameInputsBeforeMCP(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
argv []string
|
||||
want string
|
||||
}{
|
||||
{
|
||||
name: "conversation id used as group name",
|
||||
argv: []string{"chat", "+send-to-group", "--group", "cidayZx5r0T+UiMi6NrO1048A==", "--text", "你好", "--yes"},
|
||||
want: "只接受群名关键词",
|
||||
},
|
||||
{
|
||||
name: "at all belongs to messages send",
|
||||
argv: []string{"chat", "+send-to-group", "--group", "项目冲刺", "--text", "你好", "--at-all", "--yes"},
|
||||
want: "--at-all",
|
||||
},
|
||||
{
|
||||
name: "idempotency belongs to messages send",
|
||||
argv: []string{"chat", "+send-to-group", "--group", "项目冲刺", "--text", "你好", "--idempotency-key", "case-1", "--yes"},
|
||||
want: "--idempotency-key",
|
||||
},
|
||||
}
|
||||
for _, tc := range tests {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
fake := &platformCoverageCaller{}
|
||||
helpers.InitDeps(fake)
|
||||
root := newPlatformCoverageRoot()
|
||||
root.SetArgs(tc.argv)
|
||||
err := root.Execute()
|
||||
if err == nil || !strings.Contains(err.Error(), tc.want) {
|
||||
t.Fatalf("Execute() error = %v, want %q", err, tc.want)
|
||||
}
|
||||
if len(fake.calls) != 0 {
|
||||
t.Fatalf("MCP calls = %#v, want none", fake.calls)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestChatMessagesBeforeAliasAndPageAllGuard(t *testing.T) {
|
||||
t.Run("before maps to time and older", func(t *testing.T) {
|
||||
fake := &platformCoverageCaller{}
|
||||
helpers.InitDeps(fake)
|
||||
root := newPlatformCoverageRoot()
|
||||
root.SetArgs([]string{"chat", "+chat-messages", "--group", "cid-1", "--before", "2026-07-30 16:51:39"})
|
||||
if err := root.Execute(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
call := fake.calls[len(fake.calls)-1]
|
||||
if call.args["time"] != "2026-07-30 16:51:39" || call.args["forward"] != false {
|
||||
t.Fatalf("args = %#v", call.args)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("page all is blocked before MCP", func(t *testing.T) {
|
||||
fake := &platformCoverageCaller{}
|
||||
helpers.InitDeps(fake)
|
||||
root := newPlatformCoverageRoot()
|
||||
root.SetArgs([]string{"chat", "+chat-messages", "--group", "cid-1", "--page-all"})
|
||||
err := root.Execute()
|
||||
if err == nil || !strings.Contains(err.Error(), "暂不支持 --page-all") {
|
||||
t.Fatalf("Execute() error = %v", err)
|
||||
}
|
||||
if len(fake.calls) != 0 {
|
||||
t.Fatalf("MCP calls = %#v, want none", fake.calls)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func TestRelatedChatOptionsRejectBeforeMCP(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
argv []string
|
||||
want string
|
||||
}{
|
||||
{"group members rejects cid as name", []string{"chat", "+group-members", "--group", "cidayZx5r0T+UiMi6NrO1048A=="}, "--group"},
|
||||
{"members list rejects cid as name", []string{"chat", "+chat-members-list", "--group", "cidayZx5r0T+UiMi6NrO1048A=="}, "--group"},
|
||||
{"members list rejects name as cid", []string{"chat", "+chat-members-list", "--conversation-id", "测试群"}, "--conversation-id"},
|
||||
{"members list reports id alias", []string{"chat", "+chat-members-list", "--id", "测试群"}, "--id"},
|
||||
{"members list rejects unknown member type", []string{"chat", "+chat-members-list", "--group", "测试群", "--member-types", "admin"}, "--member-types"},
|
||||
{"unread chats rejects nonpositive count", []string{"chat", "+unread-chats", "--count", "0"}, "--count"},
|
||||
{"messages rejects time and before", []string{"chat", "+chat-messages", "--group", "cid", "--time", "2026-08-01", "--before", "2026-08-02"}, "--time"},
|
||||
{"messages rejects before newer", []string{"chat", "+chat-messages", "--group", "cid", "--before", "2026-08-02", "--direction", "newer"}, "--direction"},
|
||||
{"messages rejects group name as cid", []string{"chat", "+chat-messages", "--group", "测试群"}, "--group"},
|
||||
{"messages rejects nonpositive limit", []string{"chat", "+chat-messages", "--group", "cid", "--limit", "0"}, "--limit"},
|
||||
{"messages reports size alias", []string{"chat", "+chat-messages", "--group", "cid", "--size", "0"}, "--size"},
|
||||
{"messages rejects invalid time", []string{"chat", "+chat-messages", "--group", "cid", "--time", "yesterday-ish"}, "--time"},
|
||||
{"messages reports before alias", []string{"chat", "+chat-messages", "--group", "cid", "--before", "yesterday-ish"}, "--before"},
|
||||
{"messages reports conversation id alias", []string{"chat", "+chat-messages", "--conversation-id", "测试群"}, "--conversation-id"},
|
||||
{"thread replies rejects group name as cid", []string{"chat", "+thread-replies", "--group", "测试群", "--thread-id", "thread-1"}, "--group"},
|
||||
{"thread replies rejects nonpositive limit", []string{"chat", "+thread-replies", "--group", "cid", "--thread-id", "thread-1", "--limit", "0"}, "--limit"},
|
||||
{"thread replies rejects invalid time", []string{"chat", "+thread-replies", "--group", "cid", "--thread-id", "thread-1", "--time", "last-week"}, "--time"},
|
||||
{"at me rejects nonpositive days", []string{"chat", "+at-me", "--days", "0"}, "--days"},
|
||||
{"at me rejects oversized days", []string{"chat", "+at-me", "--days", "3651"}, "--days"},
|
||||
{"at me rejects nonpositive limit", []string{"chat", "+at-me", "--limit", "0"}, "--limit"},
|
||||
}
|
||||
|
||||
for _, tc := range tests {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
fake := &platformCoverageCaller{}
|
||||
helpers.InitDeps(fake)
|
||||
root := newPlatformCoverageRoot()
|
||||
root.SetArgs(tc.argv)
|
||||
err := root.Execute()
|
||||
if err == nil || !strings.Contains(err.Error(), tc.want) {
|
||||
t.Fatalf("Execute() error = %v, want %q", err, tc.want)
|
||||
}
|
||||
if len(fake.calls) != 0 {
|
||||
t.Fatalf("MCP calls = %#v, want none", fake.calls)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -47,6 +47,12 @@ var GroupMembers = shortcut.Shortcut{
|
||||
{Name: "group", Type: shortcut.FlagString, Desc: "群名称(搜群关键词,用群名里连续的核心词)", Required: true},
|
||||
},
|
||||
Tips: []string{`dws chat +group-members --group 项目冲刺`},
|
||||
Validate: func(rt *shortcut.RuntimeContext) error {
|
||||
if looksLikeOpenConversationID(rt.Str("group")) {
|
||||
return localChatOptionError("group_name_expected", "+group-members 的 --group 需要群名,当前值像 openConversationId", "--group")
|
||||
}
|
||||
return nil
|
||||
},
|
||||
Execute: func(rt *shortcut.RuntimeContext) error {
|
||||
groupName := rt.Str("group")
|
||||
|
||||
@@ -121,6 +127,7 @@ var ChatMembersList = shortcut.Shortcut{
|
||||
`dws chat +chat-members-list --group "项目冲刺"`,
|
||||
`dws chat +chat-members-list --conversation-id <openConversationId> --member-types user,bot`,
|
||||
},
|
||||
Validate: validateChatMembersList,
|
||||
Execute: func(rt *shortcut.RuntimeContext) error {
|
||||
groupID := strings.TrimSpace(rt.StrFirst("conversation-id", "id", "chat-id", "open-conversation-id"))
|
||||
groupName := strings.TrimSpace(rt.Str("group"))
|
||||
@@ -194,6 +201,25 @@ var ChatMembersList = shortcut.Shortcut{
|
||||
},
|
||||
}
|
||||
|
||||
func validateChatMembersList(rt *shortcut.RuntimeContext) error {
|
||||
groupName := strings.TrimSpace(rt.Str("group"))
|
||||
groupID := strings.TrimSpace(rt.StrFirst("conversation-id", "id", "chat-id", "open-conversation-id"))
|
||||
if groupName != "" && looksLikeOpenConversationID(groupName) {
|
||||
return localChatOptionError("conversation_id_used_as_group_name", "+chat-members-list 的 --group 需要群名,当前值像 openConversationId", "--group")
|
||||
}
|
||||
if groupID != "" && looksLikeHumanGroupName(groupID) {
|
||||
flag := changedConversationIDFlag(rt)
|
||||
return localChatOptionError("group_name_used_as_conversation_id", "+chat-members-list 的 "+flag+" 需要 openConversationId,当前值像群名", flag)
|
||||
}
|
||||
if rt.Changed("member-types") {
|
||||
users, bots, err := resolveMemberTypes(rt.StrSlice("member-types"))
|
||||
if err != nil || (!users && !bots) {
|
||||
return localChatOptionError("invalid_member_types", "+chat-members-list 的 --member-types 包含不支持的值", "--member-types")
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func resolveGroupName(rt *shortcut.RuntimeContext, groupName string) (string, error) {
|
||||
data, err := rt.CallMCPData("im", "search_groups", map[string]any{
|
||||
"keyword": groupName,
|
||||
|
||||
@@ -0,0 +1,67 @@
|
||||
// Copyright 2026 Alibaba Group
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
|
||||
package smart
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"time"
|
||||
"unicode"
|
||||
|
||||
apperrors "github.com/DingTalk-Real-AI/dingtalk-workspace-cli/internal/errors"
|
||||
"github.com/DingTalk-Real-AI/dingtalk-workspace-cli/internal/shortcut"
|
||||
)
|
||||
|
||||
func looksLikeOpenConversationID(value string) bool {
|
||||
value = strings.TrimSpace(value)
|
||||
if !strings.HasPrefix(strings.ToLower(value), "cid") {
|
||||
return false
|
||||
}
|
||||
return len(value) >= 24 || strings.ContainsAny(value, "/+=")
|
||||
}
|
||||
|
||||
func looksLikeHumanGroupName(value string) bool {
|
||||
value = strings.TrimSpace(value)
|
||||
if value == "" || looksLikeOpenConversationID(value) || strings.EqualFold(value, "cid") {
|
||||
return false
|
||||
}
|
||||
for _, r := range value {
|
||||
if unicode.Is(unicode.Han, r) || unicode.IsSpace(r) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func validChatTime(value string) bool {
|
||||
value = strings.TrimSpace(value)
|
||||
for _, layout := range []string{time.RFC3339, "2006-01-02 15:04:05", "2006-01-02"} {
|
||||
if _, err := time.Parse(layout, value); err == nil {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func changedConversationIDFlag(rt *shortcut.RuntimeContext) string {
|
||||
for _, name := range []string{"group", "conversation-id", "id", "chat-id", "open-conversation-id"} {
|
||||
if rt.Changed(name) {
|
||||
return "--" + name
|
||||
}
|
||||
}
|
||||
return "会话 ID 参数"
|
||||
}
|
||||
|
||||
func localChatOptionError(reason, message string, flags ...string) error {
|
||||
flagText := strings.Join(flags, "、")
|
||||
action := "修正参数后重试,或查看当前命令帮助"
|
||||
if flagText != "" {
|
||||
action = "检查 " + flagText + " 后重试,或查看当前命令帮助"
|
||||
}
|
||||
return apperrors.NewValidation(
|
||||
message,
|
||||
apperrors.WithReason(reason),
|
||||
apperrors.WithActions(action),
|
||||
apperrors.WithExamples("dws chat --help"),
|
||||
)
|
||||
}
|
||||
@@ -45,9 +45,12 @@ var SendToGroup = shortcut.Shortcut{
|
||||
Flags: []shortcut.Flag{
|
||||
{Name: "group", Type: shortcut.FlagString, Desc: "群名称(搜群关键词,用群名里连续的核心词)", Required: true},
|
||||
{Name: "text", Type: shortcut.FlagString, Desc: "消息内容(支持 Markdown)", Required: true},
|
||||
{Name: "at-all", Type: shortcut.FlagBool, Desc: "不支持;需要 @所有人时改用 +messages-send", Hidden: true},
|
||||
{Name: "idempotency-key", Type: shortcut.FlagString, Desc: "不支持;需要幂等键时改用 +messages-send", Hidden: true},
|
||||
shortcut.AIMessageTagFlag(),
|
||||
},
|
||||
Tips: []string{`dws chat +send-to-group --group 项目冲刺 --text "今天 5 点前提交进度"`},
|
||||
Tips: []string{`dws chat +send-to-group --group 项目冲刺 --text "今天 5 点前提交进度"`},
|
||||
Validate: validateSendToGroup,
|
||||
Execute: func(rt *shortcut.RuntimeContext) error {
|
||||
groupName := rt.Str("group")
|
||||
text := rt.Str("text")
|
||||
@@ -82,6 +85,35 @@ var SendToGroup = shortcut.Shortcut{
|
||||
},
|
||||
}
|
||||
|
||||
func validateSendToGroup(rt *shortcut.RuntimeContext) error {
|
||||
group := rt.Str("group")
|
||||
if looksLikeOpenConversationID(group) {
|
||||
return apperrors.NewValidation(
|
||||
"+send-to-group 的 --group 只接受群名关键词,不能传 openConversationId",
|
||||
apperrors.WithReason("conversation_id_used_as_group_name"),
|
||||
apperrors.WithActions("已有群 openConversationId 时改用 chat +messages-send --chat-id"),
|
||||
apperrors.WithExamples(`dws chat +messages-send --chat-id <openConversationId> --text "消息内容" --format json`),
|
||||
)
|
||||
}
|
||||
if rt.Changed("at-all") || rt.Changed("idempotency-key") {
|
||||
unsupported := make([]string, 0, 2)
|
||||
if rt.Changed("at-all") {
|
||||
unsupported = append(unsupported, "--at-all")
|
||||
}
|
||||
if rt.Changed("idempotency-key") {
|
||||
unsupported = append(unsupported, "--idempotency-key")
|
||||
}
|
||||
flagText := strings.Join(unsupported, "、")
|
||||
return apperrors.NewValidation(
|
||||
"+send-to-group 不支持当前使用的发送选项:"+flagText,
|
||||
apperrors.WithReason("unsupported_send_to_group_option"),
|
||||
apperrors.WithActions("移除 "+flagText+",或改用支持该选项的消息发送命令"),
|
||||
apperrors.WithExamples(`dws chat +messages-send --help`),
|
||||
)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// sendGroupMatch is a single group candidate resolved from a name search.
|
||||
type sendGroupMatch struct {
|
||||
id string
|
||||
|
||||
@@ -14,6 +14,8 @@
|
||||
package smart
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"github.com/DingTalk-Real-AI/dingtalk-workspace-cli/internal/shortcut"
|
||||
chatshortcut "github.com/DingTalk-Real-AI/dingtalk-workspace-cli/internal/shortcut/chat"
|
||||
"github.com/DingTalk-Real-AI/dingtalk-workspace-cli/internal/shortcut/chatmsg"
|
||||
@@ -63,7 +65,7 @@ var ThreadReplies = shortcut.Shortcut{
|
||||
`dws chat +thread-replies --group <openconversationId> --thread-id <threadId>`,
|
||||
`dws chat +thread-replies --group <openconversationId> --thread-id <threadId> --time "2025-03-01 00:00:00" --limit 20`,
|
||||
},
|
||||
Validate: chatshortcut.ValidateMessageResourceDownload,
|
||||
Validate: validateThreadReplies,
|
||||
Execute: func(rt *shortcut.RuntimeContext) error {
|
||||
// Step 1 — fetch the topic replies. Param keys (openconversationId /
|
||||
// topicId / startTime / pageSize) are copied verbatim from chat.go's
|
||||
@@ -105,6 +107,22 @@ var ThreadReplies = shortcut.Shortcut{
|
||||
},
|
||||
}
|
||||
|
||||
func validateThreadReplies(rt *shortcut.RuntimeContext) error {
|
||||
if err := chatshortcut.ValidateMessageResourceDownload(rt); err != nil {
|
||||
return err
|
||||
}
|
||||
if looksLikeHumanGroupName(rt.Str("group")) {
|
||||
return localChatOptionError("group_name_used_as_conversation_id", "+thread-replies 的 --group 需要 openConversationId,当前值像群名", "--group")
|
||||
}
|
||||
if rt.Changed("limit") && rt.Int("limit") <= 0 {
|
||||
return localChatOptionError("invalid_page_size", "+thread-replies 的 --limit 必须大于 0", "--limit")
|
||||
}
|
||||
if value := strings.TrimSpace(rt.Str("time")); value != "" && !validChatTime(value) {
|
||||
return localChatOptionError("invalid_time_boundary", "+thread-replies 的 --time 格式无效", "--time")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// threadReplyItems defensively unwraps the reply list from the response,
|
||||
// tolerating the common container keys and one level of nesting under a
|
||||
// "result"/"data" wrapper.
|
||||
|
||||
@@ -59,6 +59,12 @@ var UnreadChats = shortcut.Shortcut{
|
||||
`dws chat +unread-chats --count 20`,
|
||||
`dws chat +unread-chats --exclude-muted`,
|
||||
},
|
||||
Validate: func(rt *shortcut.RuntimeContext) error {
|
||||
if rt.Changed("count") && rt.Int("count") <= 0 {
|
||||
return localChatOptionError("invalid_page_size", "+unread-chats 的 --count 必须大于 0", "--count")
|
||||
}
|
||||
return nil
|
||||
},
|
||||
Execute: func(rt *shortcut.RuntimeContext) error {
|
||||
// Build params exactly like chatMessageListUnreadConversationsCmd: count is
|
||||
// only sent when > 0, excludeMuted only when true.
|
||||
|
||||
@@ -55,7 +55,12 @@ PRODUCT_END = "<!-- VISIBLE_SHORTCUTS_END -->"
|
||||
# only after verifying that the product skill has its own reviewed routing
|
||||
# section and intent table; compacting a sparse skill without an alternative
|
||||
# route would make its shortcuts harder to discover.
|
||||
COMPACT_PRODUCT_SERVICES = {"chat"}
|
||||
COMPACT_PRODUCT_SERVICES = {"aitable", "chat", "doc"}
|
||||
|
||||
# These compact products have every public Shortcut curated into Runtime
|
||||
# Schema. Keep this separate from COMPACT_PRODUCT_SERVICES because Chat still
|
||||
# has reviewed exclusions pending curation.
|
||||
FULLY_CURATED_COMPACT_PRODUCT_SERVICES = {"doc"}
|
||||
|
||||
def md_escape(value: Any) -> str:
|
||||
text = str(value or "")
|
||||
@@ -135,10 +140,22 @@ def product_section(service: str, rows: list[dict[str, Any]]) -> str:
|
||||
|
||||
|
||||
def compact_product_section(service: str, rows: list[dict[str, Any]]) -> str:
|
||||
if service in FULLY_CURATED_COMPACT_PRODUCT_SERVICES:
|
||||
inventory = (
|
||||
f"`{md_escape(service)}` 当前有 {len(rows)} 条公开 Shortcut,"
|
||||
"已全部进入 Runtime Schema。完整清单保留在 Runtime Shortcut "
|
||||
"Catalog,根 Skill 不重复展开;单条参数与安全契约按需查询 leaf Schema。"
|
||||
)
|
||||
else:
|
||||
inventory = (
|
||||
f"`{md_escape(service)}` 当前有 {len(rows)} 条公开 shortcut。"
|
||||
"完整清单保留在 Runtime Shortcut Catalog;已完成 Schema curation "
|
||||
"的子集可通过 leaf Schema 查询。高频产品根 Skill 不重复展开完整清单。"
|
||||
)
|
||||
return f"""{PRODUCT_START}
|
||||
## Shortcut 发现(按需)
|
||||
|
||||
`{md_escape(service)}` 当前有 {len(rows)} 条公开 shortcut。完整清单保留在 Runtime Shortcut Catalog;已完成 Schema curation 的子集可通过 leaf Schema 查询。高频产品根 Skill 不重复展开完整清单。已知意图直接使用下方的优先路由、意图表或任务 reference;命令已选中时直接执行,只在参数/安全语义不确定时读取 leaf Schema,在当前 Cobra flags 不确定时读取 leaf Help。
|
||||
{inventory}已知意图直接使用下方的优先路由、意图表或任务 reference;命令已选中时直接执行,只在参数/安全语义不确定时读取 leaf Schema,在当前 Cobra flags 不确定时读取 leaf Help。
|
||||
|
||||
仅当现有路由和 reference 都无法定位低频能力时,才执行 `dws shortcut list --service {md_escape(service)} --compact --format json` 做最后回退;不要为已知高频意图加载完整 Shortcut Catalog 或产品级 Schema。
|
||||
{PRODUCT_END}"""
|
||||
|
||||
@@ -7,8 +7,12 @@ cd "$ROOT"
|
||||
python3 scripts/gen_skill_shortcut_sections.py --check
|
||||
|
||||
chat_skill="skills/multi/dingtalk-chat/SKILL.md"
|
||||
aitable_skill="skills/multi/dingtalk-aitable/SKILL.md"
|
||||
doc_skill="skills/multi/dingtalk-doc/SKILL.md"
|
||||
mono_skill="skills/mono/SKILL.md"
|
||||
chat_max_bytes=14000
|
||||
aitable_max_bytes=11000
|
||||
doc_max_bytes=9500
|
||||
|
||||
chat_bytes="$(wc -c < "$chat_skill" | tr -d ' ')"
|
||||
if [ "$chat_bytes" -gt "$chat_max_bytes" ]; then
|
||||
@@ -17,6 +21,20 @@ if [ "$chat_bytes" -gt "$chat_max_bytes" ]; then
|
||||
exit 1
|
||||
fi
|
||||
|
||||
aitable_bytes="$(wc -c < "$aitable_skill" | tr -d ' ')"
|
||||
if [ "$aitable_bytes" -gt "$aitable_max_bytes" ]; then
|
||||
printf '%s\n' \
|
||||
"skill context budget exceeded: $aitable_skill is ${aitable_bytes} bytes (max ${aitable_max_bytes})" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
doc_bytes="$(wc -c < "$doc_skill" | tr -d ' ')"
|
||||
if [ "$doc_bytes" -gt "$doc_max_bytes" ]; then
|
||||
printf '%s\n' \
|
||||
"skill context budget exceeded: $doc_skill is ${doc_bytes} bytes (max ${doc_max_bytes})" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
shortcut_rows="$(
|
||||
awk '
|
||||
/<!-- VISIBLE_SHORTCUTS_START -->/ { in_block = 1; next }
|
||||
@@ -31,6 +49,34 @@ if [ "$shortcut_rows" -ne 0 ]; then
|
||||
exit 1
|
||||
fi
|
||||
|
||||
aitable_shortcut_rows="$(
|
||||
awk '
|
||||
/<!-- VISIBLE_SHORTCUTS_START -->/ { in_block = 1; next }
|
||||
/<!-- VISIBLE_SHORTCUTS_END -->/ { in_block = 0 }
|
||||
in_block && /^\|[[:space:]]*`/ { count++ }
|
||||
END { print count + 0 }
|
||||
' "$aitable_skill"
|
||||
)"
|
||||
if [ "$aitable_shortcut_rows" -ne 0 ]; then
|
||||
printf '%s\n' \
|
||||
"skill context budget exceeded: $aitable_skill re-expanded $aitable_shortcut_rows shortcut rows" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
doc_shortcut_rows="$(
|
||||
awk '
|
||||
/<!-- VISIBLE_SHORTCUTS_START -->/ { in_block = 1; next }
|
||||
/<!-- VISIBLE_SHORTCUTS_END -->/ { in_block = 0 }
|
||||
in_block && /^\|[[:space:]]*`/ { count++ }
|
||||
END { print count + 0 }
|
||||
' "$doc_skill"
|
||||
)"
|
||||
if [ "$doc_shortcut_rows" -ne 0 ]; then
|
||||
printf '%s\n' \
|
||||
"skill context budget exceeded: $doc_skill re-expanded $doc_shortcut_rows shortcut rows" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if grep -Fq "充分阅读产品参考文件" "$mono_skill"; then
|
||||
printf '%s\n' \
|
||||
"skill context budget regression: $mono_skill requires full product-reference loading" >&2
|
||||
@@ -38,4 +84,4 @@ if grep -Fq "充分阅读产品参考文件" "$mono_skill"; then
|
||||
fi
|
||||
|
||||
printf '%s\n' \
|
||||
"skill context budget: ok (chat_bytes=$chat_bytes max=$chat_max_bytes shortcut_rows=$shortcut_rows)"
|
||||
"skill context budget: ok (chat_bytes=$chat_bytes max=$chat_max_bytes shortcut_rows=$shortcut_rows; aitable_bytes=$aitable_bytes max=$aitable_max_bytes shortcut_rows=$aitable_shortcut_rows; doc_bytes=$doc_bytes max=$doc_max_bytes shortcut_rows=$doc_shortcut_rows)"
|
||||
|
||||
@@ -2,6 +2,13 @@
|
||||
|
||||
> 通用规范见 [_common/conventions.md](_common/conventions.md)。
|
||||
|
||||
## 显式工作流与事实保真
|
||||
|
||||
- 用户点名的 `create → list → insert/append/update` 是可观察命令链,必须保持顺序逐项执行;create 只承载明确的初始正文。有序列表块必须验证回读结构中的 `list.isOrdered=true`。
|
||||
- `--name` 不替代用户显式要求的正文 H1;新建资源返回 ID 后,同一请求的指代绑定该新资源,禁止搜索同名旧资源替换。
|
||||
- Word/Excel 需要“在线编辑/直接在线改”时使用 `doc import`,普通 `drive upload` 只保留原文件。
|
||||
- 汇总时保留证据强度:验证数量不等于通过数量,整理问题不等于根因分析。任一步骤返回 `null`/空结果或回查不一致时只能报告部分完成。
|
||||
|
||||
| Recipe | 行动指南(固定路线) |
|
||||
|--------|-------------------|
|
||||
| write-doc | 1. 按[「多源并行采集」](_common/conventions.md#多源并行采集公共模式)执行<br>2. **先把内容写入临时文件**(Linux/Mac `/tmp/<name>.md`,Windows `%TEMP%\<name>.md`)—— 含多行/表格/长文本必须走文件,不要把 markdown 直接作为命令行字符串<br>3. **单步创建**(< 200KB):`doc create --name "<文档名>" --content-file <tmp> [--folder <DOC_FOLDER_NODE_ID>] [--workspace <WS_ID>]`(`--folder` 只传文档文件夹 nodeId / alidocs 文件夹 URL,不传数字 dentryId)<br>4. **超长兜底**(> 200KB):**必须先向用户提示截断风险**(详见下方「分块 append 截断风险提示」),用户确认后再执行:`doc create --name "<文档名>" [--folder/--workspace]` → `nodeId` → 按段落切 ≤200KB 片段(不断表格) → 每片 `doc update --node <nodeId> --content-file <part> --mode append`<br>5. **回读校验**(必须):所有写入完成后,执行 `doc read --node <nodeId>` 回读文档,校验关键标题/段落是否完整写入(详见下方「doc update 回读校验规范」)<br>备选(仅短内容 <2KB 且无换行/表格):`doc create --name "..." --content "..."` |
|
||||
|
||||
@@ -665,8 +665,8 @@ Flags:
|
||||
- 知识库内 → `dws wiki node create --workspace <WS_ID> --type folder`(`doc folder create` / `doc file create --type folder` 已弃用)
|
||||
|
||||
用户说"上传文件/传文件/上传到文档/上传到知识库":
|
||||
- 上传 → `upload`(需本地文件路径)
|
||||
- 上传并转换 → `upload --convert`
|
||||
- 仅保留原始文件用于存储/下载 → `drive upload`(需本地文件路径)
|
||||
- 用户明确要求“在线编辑/大家直接在线改/转在线文档” → `doc import --file <本地路径>`;不得用普通 upload 的成功响应宣称可在线编辑
|
||||
|
||||
用户说"导入文件/导入为在线文档/导入 Word/导入 Excel/导入 xmind/导入 Markdown/把本地文件转在线文档":
|
||||
- 导入并转换为在线文档 → `doc import --file <本地路径>`
|
||||
@@ -742,8 +742,8 @@ Flags:
|
||||
关键区分: doc(文档编辑/阅读) vs aitable(数据表格操作) vs drive(钉盘文件管理)
|
||||
|
||||
用户说"上传文件/传文件/上传到文档/上传到知识库":
|
||||
- 上传 → `upload`(需本地文件路径)
|
||||
- 上传并转换 → `upload --convert`
|
||||
- 仅保留原始文件用于存储/下载 → `drive upload`(需本地文件路径)
|
||||
- 要转换为可在线编辑文档 → `doc import --file <本地路径>`,导入后验证在线类型与目标文件夹
|
||||
|
||||
用户说"下载文件/导出文件/下载到本地":
|
||||
- 下载 → `download`(需文件节点 ID 或 URL)
|
||||
@@ -1050,14 +1050,18 @@ EOF
|
||||
- `read` 返回的内容中,文档里的附件会以 OSS 临时下载链接形式给出(如 `https://alidocs2.oss-cn-zhangjiakou.aliyuncs.com/res/.../att/<resourceId>.ext?Expires=...`),该链接会过期。链接过期后,可从 URL 路径中提取 `<resourceId>`(即 `/att/` 后、扩展名前的 UUID 部分),然后使用 `media download --node <DOC_ID> --resource-id <resourceId>` 重新获取下载链接
|
||||
- `create` 不传 `--folder` 和 `--workspace` 时,默认创建在"我的文档"根目录
|
||||
- `create` 只能建"文档"(adoc);要建表格/脑图/白板/多维表/演示,用 `dws wiki node create --workspace <id> --type <type>`(`doc file create` 已弃用);建普通文件夹用 `dws drive mkdir`
|
||||
- `block list/insert/update/delete` 是块级精细编辑,适合结构化修改;简单内容追加建议用 `update --mode append`
|
||||
- `block list/insert/update/delete` 是块级精细编辑,适合结构化修改;只有用户未指定块操作的纯文本追加才建议 `update --mode append`。用户点名 list/insert/update/append 时必须逐项真实调用,不得折叠进 create
|
||||
- `block insert` 优先使用 `--text` 或 `--heading` 快捷方式;复杂块类型 (table, callout 等) 使用 `--element` JSON
|
||||
- 用户要求“有序列表块”时必须写真实列表结构(JSONML `p.list.isOrdered=true` 或等价 orderedList element),普通 Markdown/数字前缀段落不算完成
|
||||
- `--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 文件转换为钉钉在线文档
|
||||
- `drive upload` / `doc upload` 是普通文件存储路径;用户要求 Word/Excel “在线编辑/直接在线改”时硬路由到 `doc import`,并验证导入后的在线类型和文件夹。只有用户明确同时要原文件与在线版时才分别 upload + import
|
||||
- 同一请求中新建、复制或导入返回的 `nodeId` 必须绑定后续“这篇/刚才那篇/上次那篇”;禁止搜索同名旧资源覆盖绑定
|
||||
- `--name` 只是文档外壳标题,不能替代用户显式要求的正文 H1;用户说“正文先起一级标题”时必须写入或插入真实 H1
|
||||
- 汇总只能保持用户事实强度:“验证 12 条”不等于“12 条全部通过”,“整理问题清单”不等于“输出根因分析”
|
||||
- 写操作响应为 `null`/空对象或回查未变化时,该步骤失败;必须报告部分完成,禁止用其他成功步骤把整体说成“全部完成”
|
||||
- `upload` 是三步自动完成的流程 (获取凭证 → OSS 上传 → 提交入库),无需手动分步操作
|
||||
- `download` 是两步自动完成的流程 (获取下载链接 → HTTP GET 下载),支持自动推断文件名;`--output` 可指定文件路径或目录
|
||||
- `media insert` 是三步自动完成的流程 (获取附件上传凭证 → OSS 上传 → 插入附件块到文档),无需手动分步操作
|
||||
|
||||
@@ -1,15 +1,11 @@
|
||||
# doc block(块级精细编辑:list / insert / update / delete)
|
||||
|
||||
> **前置条件(MUST READ):** 执行本命令前,必须先用 Read 工具读取以下文件:
|
||||
> 1. [`../doc.md`](../doc.md) — 命令路由 + 场景索引 + 意图判断 + 工作流
|
||||
> 2. [`./style/doc-update-workflow.md`](./style/doc-update-workflow.md) — 改写流程(编辑形态优先级、JSONML validator 行为)
|
||||
> 3. [`./format/doc-jsonml-cookbook.md`](./format/doc-jsonml-cookbook.md) — JSONML 范例(含 callout / 分栏 / 表格 / 标题等节点的完整命令)
|
||||
> 4. [`./format/doc-jsonml-schema.md`](./format/doc-jsonml-schema.md) — JSONML 节点结构字段定义
|
||||
>
|
||||
> **同任务常配合**:[`doc-update.md`](./doc-update.md)(整篇 overwrite / 末尾追加纯文本)/ [`./format/doc-jsonml-cookbook.md`](./format/doc-jsonml-cookbook.md)(JSONML 复制范例)
|
||||
> 本文件自包含简单 list/insert/update/delete 契约,不要递归预读路由或 style reference。只有实际构造复杂 JSONML 节点时,才读取 [`doc-jsonml-cookbook.md`](./format/doc-jsonml-cookbook.md);字段仍不确定时再查 [`doc-jsonml-schema.md`](./format/doc-jsonml-schema.md)。整篇 overwrite 或纯文本 append 才转读 [`doc-update.md`](./doc-update.md)。
|
||||
|
||||
> **改写已有文档优先 JSONML**:保真度最高、callout / 分栏 / 表格 / @人 / 附件 / 颜色 / 嵌套都能 1:1 round-trip;写入端有 validator 兜底。详见 [`./style/doc-update-workflow.md` §1.3 编辑形态优先级](./style/doc-update-workflow.md)。
|
||||
|
||||
> **显式块操作不可折叠**:用户说“先 create,再 list/insert/update/append”时按原顺序真实调用;不能因为最终正文相似,就把后续块操作合并进 create 或一次 Markdown 写入。
|
||||
|
||||
---
|
||||
|
||||
## doc block list(查询块元素)
|
||||
@@ -173,7 +169,8 @@ dws doc block delete --node DOC_ID --block-id UUID
|
||||
|
||||
- **块类型**:paragraph、heading、blockquote、callout、columns、orderedList、unorderedList、table、sheet、attachment、slot。
|
||||
- **快捷 vs --element**:`block insert` 优先使用 `--text` 或 `--heading` 快捷方式;复杂块类型(table、callout、columns 等)使用 `--element` JSON 或 `--content-format jsonml`。
|
||||
- **简单内容追加**:建议用 [`./doc-update.md`](./doc-update.md) `--mode append`,不必走 block insert。
|
||||
- **有序列表块**:用户明确要求 ordered list / 有序列表块时,必须用 JSONML `p` 节点的 `list.isOrdered=true`(同一 `listId`;仅首项设 `start:1`)或等价原生 orderedList element;带 `1.` 前缀的普通段落、普通 Markdown 或一次 create 不满足要求。
|
||||
- **简单内容追加**:用户只说追加纯文本且不强调块操作时可用 [`./doc-update.md`](./doc-update.md) `--mode append`;用户明确说 block insert / 插入段落 / 插入标题 / 插入列表块时必须走 block insert。
|
||||
- **JSONML validator**(写入端默认行为):
|
||||
- 裸字符串、缺 uuid 等结构错误会被 validator 抦下并返回带 path 的错误(如 `$[2][2]: paragraph child must be span wrapper, got raw string.`)。
|
||||
- `--fix-jsonml` 开启 JSON 语法修复,推荐 agent 调用。
|
||||
@@ -241,6 +238,12 @@ dws doc block list --node <DOC_ID> --content-format jsonml --block-id <UUID>
|
||||
dws doc block insert --node <DOC_ID> --content-format jsonml --ref-block <UUID> --where after \
|
||||
--element '["p",{},["span",{"data-type":"text"},["span",{"data-type":"leaf"},"新段落"]]]'
|
||||
|
||||
# 插入有序列表块(3 项共用 listId,仅首项有 start)
|
||||
dws doc block insert --node <DOC_ID> --content-format jsonml \
|
||||
--element '["p",{"uuid":"ol1","list":{"listId":"actions","level":0,"isOrdered":true,"start":1}},["span",{"data-type":"text"},["span",{"data-type":"leaf"},"第一项"]]]'
|
||||
dws doc block insert --node <DOC_ID> --content-format jsonml \
|
||||
--element '["p",{"uuid":"ol2","list":{"listId":"actions","level":0,"isOrdered":true}},["span",{"data-type":"text"},["span",{"data-type":"leaf"},"第二项"]]]'
|
||||
|
||||
# 插入 callout(colorBlocks)
|
||||
dws doc block insert --node <DOC_ID> --content-format jsonml --ref-block <UUID> --where after \
|
||||
--element '["container",{"uuid":"co1","subType":"colorBlocks","metadata":{"bgcolor":"#FDE2E0","border":"#F5C2C7"}},["p",{"uuid":"co1p1"},["span",{"data-type":"text"},["span",{"data-type":"leaf"},"高风险操作,先备份"]]]]'
|
||||
|
||||
@@ -1,9 +1,6 @@
|
||||
# doc comment(文档评论:list / create / reply / update / delete / create-inline)
|
||||
|
||||
> **前置条件(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 文本)
|
||||
> 本文件自包含评论命令契约。仅在需要 mention 时查询真实 userId/openConversationId;仅在划词评论尚无 blockId 与 paragraph 文本时读取 [`doc-block.md`](./doc-block.md) 并执行 block list。
|
||||
|
||||
---
|
||||
|
||||
@@ -131,6 +128,7 @@ Flags:
|
||||
- 划词评论的 `--start` / `--end` 是块内文本字符偏移量,从 0 开始;通过 [`./doc-block.md`](./doc-block.md) `block list` 取 `paragraph.text` 后人工或脚本计算。
|
||||
- `reply` 加 `--emoji` 时 `--content` 填表情名称(如 `比心`、`赞`),不是文字内容。
|
||||
- `reply --emoji` 不能同时 @群。
|
||||
- `comment create/reply/update/delete` 的退出码 0 不等于业务成功。响应为 `null`、空对象或缺少可核验字段时,立即执行 `comment list` 回查目标 `commentKey`。若 update 后正文仍是旧值,必须判定“更新未生效”;即使其他步骤成功或评论随后被删除,也只能报告部分完成,禁止写“全部完成”。
|
||||
|
||||
## 上下文传递
|
||||
|
||||
|
||||
@@ -1,11 +1,6 @@
|
||||
# doc create(创建文档)
|
||||
|
||||
> **前置条件(MUST READ):** 执行本命令前,必须先用 Read 工具读取以下文件:
|
||||
> 1. [`../doc.md`](../doc.md) — 命令路由 + 场景索引 + 意图判断 + 工作流
|
||||
> 2. [`./style/doc-create-workflow.md`](./style/doc-create-workflow.md) — 创建工作流(标题、位置、骨架、回读校验)
|
||||
> 3. [`./style/doc-style-guideline.md`](./style/doc-style-guideline.md) — 排版规范(草稿元素清单、骨架样板)
|
||||
> 4. [`./doc-update.md` §内容写入管道](./doc-update.md#内容写入管道createupdate-共用) — 长内容自动分片、`--content-file` vs `--content` 选择
|
||||
> 5. [`./format/doc-jsonml-cookbook.md`](./format/doc-jsonml-cookbook.md) — 仅当使用 `--content-format jsonml` 时必读
|
||||
> 本文件自包含普通 Markdown 创建契约,不要递归预读 `doc.md`、style 或 update reference。仅当用户要求复杂版式并实际选择 JSONML 时,读取 [`doc-jsonml-cookbook.md`](./format/doc-jsonml-cookbook.md);需要文档骨架建议时才读取对应 style 章节。
|
||||
|
||||
## 创建路由前置判断(必看)
|
||||
|
||||
@@ -40,7 +35,7 @@ Flags:
|
||||
|
||||
## 关键说明
|
||||
|
||||
- **`--name` 是 H1**:正文从 `##` 开始;正文内不要再写 `#` 一级标题(除非确需且已说明动机)。
|
||||
- **标题优先级**:`--name` 是文档外壳标题,默认可视作 H1;但它不能替代用户显式要求的正文一级标题。用户说“正文写 `# ...`”“正文先起个一级标题”时,必须在初始内容中保留该 `#` H1;用户未要求正文 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 -`。
|
||||
@@ -54,6 +49,12 @@ Flags:
|
||||
| `docUrl` | 最终交付给用户的链接;缺失时用 [`./doc-info.md`](./doc-info.md) 补查 |
|
||||
| `chunksWritten` | 判断是否触发自动分片;> 1 时重点检查章节顺序 |
|
||||
|
||||
同一请求后续出现“这篇/刚才那篇/上次那篇”时,直接续用本次 create 返回的 `nodeId`;禁止先搜索同名文档再把后续操作指向旧节点。
|
||||
|
||||
## 显式操作序列
|
||||
|
||||
用户点名 `block list`、插入、追加、更新等后续动作时,必须按原顺序逐项执行。`doc create` 只写用户指定的初始内容,不能为了减少调用把后续标题、列表或段落提前塞进 create。例:`创建 → 查看块结构 → 末尾插入段落` 必须真实执行 create、block list、block insert 三步。
|
||||
|
||||
## 回读验收(必读)
|
||||
|
||||
CLI **不会**自动回读校验。**每次创建后**都必须执行 `doc read --node <nodeId>` 校验关键标题、段落首句、表格表头是否完整。详见 [`./style/doc-create-workflow.md` «回读验收»](./style/doc-create-workflow.md)。
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
# doc export(在线文档导出为 docx)
|
||||
|
||||
> **前置条件(MUST READ):** 执行本命令前,必须先用 Read 工具读取以下文件:
|
||||
> 1. [`../doc.md`](../doc.md) — 命令路由 + 场景索引 + 意图判断 + 工作流
|
||||
> 本文件自包含 export 契约。已有当前请求返回的 adoc nodeId 时直接导出;目标类型未知时只执行一次 info 检查,不要递归读取 `doc.md`。
|
||||
|
||||
> **路由前置判断**:用户说「下载/导出」时**必须**先用 [`./doc-info.md`](./doc-info.md) `info --node <ID> --format json` 查 `contentType`:
|
||||
> - `contentType` 为 `ALIDOC`(在线文档)→ **必须用 `export`**,禁止用 `download`
|
||||
@@ -45,6 +44,7 @@ Flags:
|
||||
|
||||
## 关键说明
|
||||
|
||||
- 同一请求中刚执行 create/copy/import 并紧接着说“这篇/刚才那篇/上次那篇”时,`--node` 必须使用该写操作真实返回的新 `nodeId`;不得预先搜索同名文档,也不得用搜索结果中的旧节点替换它。
|
||||
- `export` 是一体化命令,一条命令自动完成提交→轮询→下载,**无需手动编排轮询**。CLI 内部使用渐进式退避轮询(最多约 5 分钟)。
|
||||
- `export` 超时或中断后,CLI 会输出 `jobId`,可用 `dws doc export get --job-id <jobId>` 手动查询任务状态。
|
||||
- `export` 当前仅支持钉钉在线文档(alidocs,`contentType=ALIDOC`)导出为 `docx`,**在线表格导出请使用其他命令**。
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
# doc 文件操作(upload / download / copy / move / rename / delete + folder create)
|
||||
|
||||
> **前置条件(MUST READ):** 执行本命令前,必须先用 Read 工具读取以下文件:
|
||||
> 1. [`../doc.md`](../doc.md) — 命令路由 + 场景索引 + 意图判断 + 工作流
|
||||
> **按需使用**:本文件自包含弃用命令的兼容说明,不要求先读总路由;优先按下方提示改用 `drive` / `wiki`。
|
||||
|
||||
> **弃用提示(文件管理命令正在迁移到 drive / wiki)**:本文所列 `doc` 文件管理命令虽仍能跑,但执行时会打印弃用警告,请优先改用 `drive` / `wiki` 对应命令:
|
||||
> - `doc download` → **`dws drive download`**(下载已有文件;在线文档导出 docx 仍走 `doc export`)
|
||||
@@ -32,6 +31,7 @@ Flags:
|
||||
- `upload` 是三步自动完成的流程(获取凭证 → OSS 上传 → 提交入库),无需手动分步操作。
|
||||
- 支持上传任意类型文件(PDF、Office、图片等)到钉钉文档空间或知识库。
|
||||
- `--convert` 可将 Office 文件转换为钉钉在线文档。
|
||||
- **在线编辑硬路由**:用户说“大家直接在线改/上传后在线编辑/转成钉钉文档”时使用 [`./doc-import.md`](./doc-import.md) `doc import`,并回查在线类型;普通 `doc/drive upload` 只用于保留文件,不能据此承诺可在线编辑。
|
||||
- **`doc upload` vs `drive upload`**:用户提到「知识库 / 文档空间 / workspace」→ `doc upload`;提到「钉盘 / 网盘 / 我的文件」→ `drive upload`;未明确目标时默认 `drive upload`。
|
||||
- 与 [`./doc-media.md`](./doc-media.md) `media insert` 的区别:`upload` 上传到文档空间作为**独立文件**;`media insert` 作为**附件块插入到文档正文中**。
|
||||
|
||||
|
||||
@@ -6,6 +6,8 @@
|
||||
|
||||
不要先读取文件内容再调用 `doc create` 或 `doc update`。`doc import` 会按文件格式走导入任务,保留更完整的原始结构。
|
||||
|
||||
> **在线编辑硬路由**:用户说“上传后在线编辑/大家直接在线改/转成钉钉文档”时必须使用 `doc import`。`drive upload` 只保留原始 `.docx/.xlsx/...` 普通文件,不能据此宣称已可在线编辑。若用户明确要同时保留原文件和在线版,才先 `drive upload`,再单独 `doc import`,并分别验证两个返回节点。
|
||||
|
||||
## 命令
|
||||
|
||||
```bash
|
||||
@@ -38,6 +40,7 @@ dws doc import get --task-id <TASK_ID> --format json
|
||||
3. 执行 `dws doc import --file ... --format json`。
|
||||
4. 正常情况下 CLI 会自动提交、上传并轮询导入任务。
|
||||
5. 如果命令超时或中断,从输出中提取 `taskId`,再执行 `dws doc import get --task-id <TASK_ID> --format json`。
|
||||
6. 用返回的 `documentUrl`/`nodeId` 执行 `drive info` 或 `doc info`,确认在线类型和目标文件夹;验证通过后才能说“可直接在线编辑”。
|
||||
|
||||
## 上下文传递
|
||||
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
# doc info(获取文档元信息 + URL 解析)
|
||||
|
||||
> **前置条件(MUST READ):** 执行本命令前,必须先用 Read 工具读取以下文件:
|
||||
> 1. [`../doc.md`](../doc.md) — 命令路由 + 场景索引 + 意图判断 + 工作流
|
||||
> 2. [`../../url-patterns.md`](../../url-patterns.md) — 仅当用户原始 `alidocs` URL 需要 probe 时
|
||||
> 本文件自包含已知 nodeId/URL 的 info 契约。只有原始 alidocs URL 类型仍不明确时,才读取 [`url-patterns.md`](../../url-patterns.md);不要递归读取 `doc.md`。
|
||||
>
|
||||
> **同任务常配合**:`dws drive search` / `dws wiki node search`(先定位 nodeId)/ [`doc-read.md`](./doc-read.md)(确认是 ALIDOC 后读正文)
|
||||
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
# doc media(附件 / 图片:download / insert)
|
||||
|
||||
> **前置条件(MUST READ):** 执行本命令前,必须先用 Read 工具读取以下文件:
|
||||
> 1. [`../doc.md`](../doc.md) — 命令路由 + 场景索引 + 意图判断 + 工作流
|
||||
> 本文件自包含 media insert/download 契约。nodeId、文件路径或 resourceId 已知时直接执行;只在需要相对块定位且 blockId 未知时读取 [`doc-block.md`](./doc-block.md)。
|
||||
|
||||
> ⚠️ **图片插入硬规则**:
|
||||
> - 图片来源如果是钉盘/文档空间中的文件,**必须先下载到本地**(`dws drive download --node <图片nodeId> --output /tmp/xxx.png`),再执行 `media insert`
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
# doc permission(文档权限:add / update / list)
|
||||
|
||||
> **前置条件(MUST READ):** 执行本命令前,必须先用 Read 工具读取以下文件:
|
||||
> 1. [`../doc.md`](../doc.md) — 命令路由 + 场景索引 + 意图判断 + 工作流
|
||||
> **按需使用**:本文件自包含文档节点权限命令;不要求先读总路由。知识库整体成员权限改读 `dingtalk-wiki`。
|
||||
|
||||
> **关键区分**:
|
||||
> - "把**某篇文档**授权给某人" → `doc permission add`(节点级,包括「我的文档」下的文档都支持)
|
||||
|
||||
@@ -1,10 +1,6 @@
|
||||
# doc read(读取文档内容)
|
||||
|
||||
> **前置条件(MUST READ):** 执行本命令前,必须先用 Read 工具读取以下文件:
|
||||
> 1. [`../doc.md`](../doc.md) — 命令路由 + 场景索引 + 意图判断 + 工作流
|
||||
> 2. [`./format/doc-jsonml-cookbook.md`](./format/doc-jsonml-cookbook.md) — 仅当使用 `--content-format jsonml` 时必读
|
||||
>
|
||||
> **同任务常配合**:[`doc-info.md`](./doc-info.md)(先解析 URL,确认 contentType=ALIDOC、extension=adoc)/ [`doc-update.md`](./doc-update.md)(读后改写)/ [`doc-block.md`](./doc-block.md)(块级精修前先读结构)
|
||||
> 本文件自包含普通 read 契约。用户已给当前 adoc nodeId/URL 时直接读取;类型未知时才先执行 info。选择 JSONML 只为获取结构,不要求预读 cookbook;实际构造 JSONML 写入时再按需加载。
|
||||
|
||||
## 命令格式
|
||||
|
||||
|
||||
@@ -1,12 +1,6 @@
|
||||
# doc update(更新文档内容)
|
||||
|
||||
> **前置条件(MUST READ):** 执行本命令前,必须先用 Read 工具读取以下文件:
|
||||
> 1. [`../doc.md`](../doc.md) — 命令路由 + 场景索引 + 意图判断 + 工作流
|
||||
> 2. [`./style/doc-update-workflow.md`](./style/doc-update-workflow.md) — 改写流程(编辑形态优先级、分片 append、回读验收)
|
||||
> 3. [`./style/doc-style-guideline.md`](./style/doc-style-guideline.md) — 排版规范
|
||||
> 4. [`./format/doc-jsonml-cookbook.md`](./format/doc-jsonml-cookbook.md) — 仅当使用 `--content-format jsonml` 时必读
|
||||
>
|
||||
> **同任务常配合**:[`doc-read.md`](./doc-read.md)(改写前必读,jsonml 模式拿当前结构;担心被并发覆盖时再取 revision)/ [`doc-block.md`](./doc-block.md)(单 block 改写优先;本命令更适合追加 / 整篇 overwrite)
|
||||
> 本文件自包含普通 append/overwrite 契约,不要递归预读路由或 style reference。纯文本 append 可直接执行;overwrite 先 read/dry-run/确认。只有保真改写或复杂 JSONML 才读取 [`doc-update-workflow.md`](./style/doc-update-workflow.md) 与 cookbook;单块修改改用 [`doc-block.md`](./doc-block.md)。
|
||||
|
||||
## 命令格式
|
||||
|
||||
|
||||
@@ -4,11 +4,9 @@
|
||||
|
||||
> 改写已有文档见 [doc-update-workflow.md](./doc-update-workflow.md)。排版规范见 [doc-style-guideline.md](./doc-style-guideline.md)。
|
||||
|
||||
## 前置必读
|
||||
## 按需使用
|
||||
|
||||
> **同时读取 [doc-style-guideline.md](./doc-style-guideline.md):**
|
||||
> - **§2.0 类型判断决策表** → 锁定文档类型(决策型 / 执行型 / 说明型 / 知识沉淀型)和骨架
|
||||
> - **§1 硬规则** → 全程生效(`--name` 已是 H1、不编造 URL、Markdown 草稿不写 callout 等)
|
||||
普通 Markdown 创建不需要先读本文件或 style guideline。只有用户要求设计文档骨架或复杂版式时,才查看下方对应章节;实际选择 JSONML 后再读取 cookbook。需要按文档类型选骨架时,按需读取 [doc-style-guideline.md](./doc-style-guideline.md) 的对应一节,不要通读。
|
||||
|
||||
### 关键词速查(用户意图 → 起稿路径)
|
||||
|
||||
@@ -45,7 +43,7 @@
|
||||
|
||||
| 项目 | 要求 |
|
||||
|------|------|
|
||||
| 标题 | 用 `--name` 传入;正文不要再重复同名一级标题 |
|
||||
| 标题 | 用 `--name` 传入;用户显式要求正文 H1 时原样保留,未要求时正文默认从 H2 开始 |
|
||||
| 位置 | 默认创建到我的文档;指定目录时只接受文档文件夹 `nodeId` 或 alidocs 文件夹 URL |
|
||||
| 正文 | 多行、表格、代码块、特殊字符或长度 >= 2KB 时必须写入 UTF-8 临时 `.md` 文件 |
|
||||
| 格式 | 按 §JSONML 起稿判定 决定起稿路径:命中 JSONML 起稿条件时**直接用 JSONML 构造**(跳过 markdown);未命中时用 Markdown 起稿,创建后按 [doc-update-workflow.md](./doc-update-workflow.md) 精修 |
|
||||
@@ -226,7 +224,7 @@ callout 可通过 `"showstk": true, "sticker": "图标名"` 配置顶部贴纸
|
||||
|
||||
> **脚手架策略警示**:Markdown 无法表达分栏/callout/色彩表头,拉回的 JSONML 只有纯文本骨架。精修阶段不是“在现有结构上加色”,而是“参照 RFC/Spec 重组结构”。
|
||||
|
||||
> **MUST READ**:动手写 JSONML 前,必须先用 Read 工具读取 [doc-jsonml-cookbook.md](../format/doc-jsonml-cookbook.md) — 其中 §决策型文档骨架范例 有可直接复制修改的完整模板。
|
||||
> **JSONML 条件加载**:仅在确定使用 JSONML 后读取 [doc-jsonml-cookbook.md](../format/doc-jsonml-cookbook.md);其中“决策型文档骨架范例”可直接改写。
|
||||
> 节点类型和属性的权威定义见 [doc-jsonml-schema.md](../format/doc-jsonml-schema.md)。
|
||||
|
||||
### ⚠️ JSONML 降级约束
|
||||
@@ -274,7 +272,7 @@ callout 可通过 `"showstk": true, "sticker": "图标名"` 配置顶部贴纸
|
||||
```
|
||||
|
||||
- 根节点固定 `"root"`(不是 `"body"`)
|
||||
- `--name` 已是 H1,JSONML 从 `h2` 开始
|
||||
- 用户未要求正文 H1 时,JSONML 从 `h2` 开始;用户明确要求“正文一级标题/插入一级标题”时必须构造 `h1`
|
||||
- 表格结构是 `table → tr → tc`(无 `th`/`td`)
|
||||
- 分栏是 `table` + `"sr": true`,`tc` 建议设 `fill` 背景色
|
||||
- 有序列表:仅第一项设 `"start": 1`,后续项不设 `start`(系统自动递增)
|
||||
@@ -315,7 +313,7 @@ dws doc read --node <nodeId> --content-format jsonml --output /tmp/<name>-readba
|
||||
- 只使用用户已提供或对话中已确认的正文素材。
|
||||
- 如果正文素材不足,先补齐文档目标、受众、章节和缺口;不要在本文中临时扩展跨产品采集流程。
|
||||
- **先按 [doc-style-guideline.md §2.0 类型判断决策表](./doc-style-guideline.md) 确定文档类型,再用对应类型的骨架样板(§2.1 决策型 / §2.2 执行型 / §2.3 说明型 / §2.4 知识沉淀型)**。不要套通用三段式。
|
||||
- **`--name` 已是 H1,正文从 `##` 开始**;正文内不要再写 `#` 一级标题(除非确实需要正文内再造一级 H1 并说明动机)。
|
||||
- **`--name` 是外壳标题,不覆盖显式正文 H1**:用户未要求正文一级标题时从 `##` 开始;用户明确给出 `# ...` 或要求“先起一级标题”时,正文必须保留该 H1。
|
||||
- 摘要、bullet、引用块、callout 等元素的使用边界以 style-guideline §3-§7 为准。
|
||||
- 同类信息保持一致:风险、状态、行动项各用一种元素 + 一种视觉语义(style-guideline §1.2 / §5)。
|
||||
- 临时文件必须保留真实换行,不能把换行写成字面量 `\n`。
|
||||
|
||||
@@ -25,7 +25,7 @@
|
||||
|
||||
## 一、硬规则
|
||||
|
||||
1. **`--name` 是 H1**:正文从 `##` 开始;正文内不写 `#`(除非确需正文内再造一级 H1 并说明动机)
|
||||
1. **用户显式正文 H1 优先**:`--name` 是文档外壳标题;用户未要求正文一级标题时从 `##` 开始。用户明确说“正文写 `# ...` / 先起一级标题 / 插入一级标题”时必须保留或插入真实 H1,不得用 `--name` 代替
|
||||
2. **同类信息同表达**:风险、状态、行动项、证据,每类只用一种元素 + 一种视觉语义(见 §5)
|
||||
3. **Markdown 草稿阶段只用稳定元素**:标题、段落、列表、checklist、表格、代码块;callout / 分栏 / 附件 / 复杂嵌套留到创建后用 `doc block insert` / `doc media insert` 精修
|
||||
4. **引用块只用于原文**:用户原话、会议摘录、外部材料原文;不许包装作者自己的结论
|
||||
@@ -209,7 +209,7 @@
|
||||
|
||||
### 4.1 标题与段落
|
||||
|
||||
- 正文从 `##` 开始(H1 已被 `--name` 占用)
|
||||
- 用户未要求正文 H1 时从 `##` 开始;用户明确要求正文 H1 时按原文使用 `#` 或 heading level 1
|
||||
- 标题层级 ≤ 4 层(§7)
|
||||
- 单段过长先拆段,再考虑换元素
|
||||
|
||||
@@ -217,6 +217,7 @@
|
||||
|
||||
- 普通列表:并列要点
|
||||
- 有序列表:顺序步骤
|
||||
- 用户明确要求“有序列表块”时必须使用真实列表结构;JSONML 为带 `list.isOrdered=true` 的多个 `p` 节点,不能只写带数字前缀的普通段落或以整篇 Markdown 代替显式 block insert
|
||||
- checklist:待办状态(含 `- [ ]` / `- [x]`)
|
||||
|
||||
列表项里开始出现"负责人 / 截止时间 / 状态"这类字段时,改用表格。
|
||||
|
||||
@@ -1,181 +1,183 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
在指定目录创建文档并写入 Markdown 内容(一键完成)
|
||||
"""用原生 dws 写入管道创建文档,并回读验证。"""
|
||||
|
||||
用法:
|
||||
python doc_create_and_write.py \
|
||||
--name "项目周报" \
|
||||
--content "# 本周总结\n\n## 完成事项\n- 任务A"
|
||||
from __future__ import annotations
|
||||
|
||||
python doc_create_and_write.py \
|
||||
--name "会议纪要" \
|
||||
--content-file notes.md
|
||||
|
||||
python doc_create_and_write.py \
|
||||
--name "知识库文档" --content "# 内容" --folder FOLDER_ID
|
||||
|
||||
python doc_create_and_write.py --name "test" --content "hello" --dry-run
|
||||
"""
|
||||
|
||||
import sys
|
||||
import json
|
||||
import time
|
||||
import subprocess
|
||||
import argparse
|
||||
import json
|
||||
import shlex
|
||||
import subprocess
|
||||
import sys
|
||||
import tempfile
|
||||
from pathlib import Path
|
||||
from typing import List, Any, Optional
|
||||
from typing import Any, Optional, Sequence
|
||||
|
||||
|
||||
def run_dws(
|
||||
args: List[str], dry_run: bool = False,
|
||||
) -> Optional[Any]:
|
||||
cmd = ['dws'] + args
|
||||
class ScriptError(RuntimeError):
|
||||
"""可预期的脚本执行错误。"""
|
||||
|
||||
|
||||
def decode_json_output(output: str) -> Any:
|
||||
"""解析 JSON;兼容长内容写入前置的进度行。"""
|
||||
text = output.strip()
|
||||
if not text:
|
||||
raise ScriptError("dws 未返回 JSON")
|
||||
try:
|
||||
return json.loads(text)
|
||||
except json.JSONDecodeError:
|
||||
decoder = json.JSONDecoder()
|
||||
for offset, character in enumerate(text):
|
||||
if character not in "[{":
|
||||
continue
|
||||
try:
|
||||
value, end = decoder.raw_decode(text, offset)
|
||||
except json.JSONDecodeError:
|
||||
continue
|
||||
if not text[end:].strip():
|
||||
return value
|
||||
raise ScriptError("dws 返回的不是合法 JSON")
|
||||
|
||||
|
||||
def run_dws(args: Sequence[str], dry_run: bool = False) -> Any:
|
||||
"""执行一条 dws 命令,并把命令/业务失败统一转成 ScriptError。"""
|
||||
command = ["dws", *args]
|
||||
if dry_run:
|
||||
print(f"[dry-run] {' '.join(cmd)}")
|
||||
return {'dry_run': True}
|
||||
print(f"[dry-run] {shlex.join(command)}")
|
||||
return {"dry_run": True}
|
||||
try:
|
||||
result = subprocess.run(
|
||||
cmd, capture_output=True, text=True, timeout=60
|
||||
command,
|
||||
capture_output=True,
|
||||
text=True,
|
||||
timeout=120,
|
||||
check=False,
|
||||
)
|
||||
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
|
||||
except (subprocess.TimeoutExpired, FileNotFoundError) as exc:
|
||||
raise ScriptError(f"执行 dws 失败:{exc}") from exc
|
||||
if result.returncode != 0:
|
||||
detail = result.stderr.strip() or result.stdout.strip()
|
||||
raise ScriptError(
|
||||
f"dws 命令失败:{detail or f'退出码 {result.returncode}'}"
|
||||
)
|
||||
data = decode_json_output(result.stdout)
|
||||
if data is None or data == {}:
|
||||
raise ScriptError("dws 返回空业务结果,无法确认操作成功")
|
||||
if isinstance(data, dict) and data.get("success") is False:
|
||||
detail = data.get("errorMsg") or data.get("message") or "未知错误"
|
||||
raise ScriptError(f"dws 业务调用失败:{detail}")
|
||||
return data
|
||||
|
||||
|
||||
def run_dws_with_retry(
|
||||
args: List[str],
|
||||
dry_run: bool = False,
|
||||
max_retries: int = 3,
|
||||
retry_delay: float = 1.0,
|
||||
) -> Optional[Any]:
|
||||
"""带重试机制的 dws 命令执行"""
|
||||
last_error = None
|
||||
for attempt in range(1, max_retries + 1):
|
||||
result = run_dws(args, dry_run=dry_run)
|
||||
if result is not None:
|
||||
return result
|
||||
if attempt < max_retries:
|
||||
print(f" ⚠️ 第 {attempt} 次尝试失败,{retry_delay}秒后重试...")
|
||||
time.sleep(retry_delay)
|
||||
retry_delay *= 1.5 # 指数退避
|
||||
return None
|
||||
def first_value(payload: Any, keys: Sequence[str]) -> str:
|
||||
"""从嵌套响应中提取第一个非空稳定字段。"""
|
||||
if isinstance(payload, dict):
|
||||
for key in keys:
|
||||
value = payload.get(key)
|
||||
if value is not None and str(value).strip():
|
||||
return str(value).strip()
|
||||
for value in payload.values():
|
||||
found = first_value(value, keys)
|
||||
if found:
|
||||
return found
|
||||
elif isinstance(payload, list):
|
||||
for value in payload:
|
||||
found = first_value(value, keys)
|
||||
if found:
|
||||
return found
|
||||
return ""
|
||||
|
||||
|
||||
def main():
|
||||
def run(argv: Optional[Sequence[str]] = None) -> int:
|
||||
parser = argparse.ArgumentParser(
|
||||
description='创建文档并写入内容'
|
||||
description="使用 dws doc create 创建文档并回读验证"
|
||||
)
|
||||
parser.add_argument('--name', required=True, help='文档名称')
|
||||
parser.add_argument('--content', default='', help='Markdown 内容')
|
||||
parser.add_argument('--content-file', default='', help='内容文件')
|
||||
parser.add_argument('--folder', default='', help='目标文件夹 ID 或 URL')
|
||||
parser.add_argument('--workspace', default='', help='目标知识库 ID')
|
||||
parser.add_argument(
|
||||
'--mode', default='append', choices=['overwrite', 'append'],
|
||||
help='写入模式: overwrite=覆盖, append=追加 (默认 append)',
|
||||
parser.add_argument("--name", required=True, help="文档名称")
|
||||
content_group = parser.add_mutually_exclusive_group(required=True)
|
||||
content_group.add_argument("--content", help="Markdown 内容")
|
||||
content_group.add_argument("--content-file", help="UTF-8 Markdown 文件")
|
||||
location_group = parser.add_mutually_exclusive_group()
|
||||
location_group.add_argument(
|
||||
"--folder", default="", help="目标文档文件夹 ID 或 URL"
|
||||
)
|
||||
parser.add_argument(
|
||||
'--max-retries', type=int, default=3,
|
||||
help='每块写入失败时的最大重试次数 (默认 3)',
|
||||
location_group.add_argument(
|
||||
"--workspace", default="", help="目标知识库 ID 或 URL"
|
||||
)
|
||||
parser.add_argument('--dry-run', action='store_true')
|
||||
args = parser.parse_args()
|
||||
parser.add_argument("--dry-run", action="store_true")
|
||||
args = parser.parse_args(argv)
|
||||
|
||||
content = args.content
|
||||
supplied_path: Optional[Path] = None
|
||||
temporary_path: Optional[Path] = None
|
||||
if args.content_file:
|
||||
p = Path(args.content_file)
|
||||
if not p.exists():
|
||||
print(f"错误:文件不存在: {p}")
|
||||
sys.exit(1)
|
||||
content = p.read_text(encoding='utf-8')
|
||||
if not content:
|
||||
print('错误:需要 --content 或 --content-file')
|
||||
sys.exit(1)
|
||||
chunk_size = 30000
|
||||
supplied_path = Path(args.content_file)
|
||||
if not supplied_path.is_file():
|
||||
raise ScriptError(f"内容文件不存在:{supplied_path}")
|
||||
elif not args.content or not args.content.strip():
|
||||
raise ScriptError("--content 不能为空")
|
||||
|
||||
create_args = ['doc', 'create', '--name', args.name, '--format', 'json']
|
||||
if args.folder:
|
||||
create_args.extend(['--folder', args.folder])
|
||||
if args.workspace:
|
||||
create_args.extend(['--workspace', args.workspace])
|
||||
try:
|
||||
if supplied_path is None and not args.dry_run:
|
||||
with tempfile.NamedTemporaryFile(
|
||||
mode="w", encoding="utf-8", suffix=".md", delete=False
|
||||
) as handle:
|
||||
handle.write(args.content)
|
||||
temporary_path = Path(handle.name)
|
||||
supplied_path = temporary_path
|
||||
|
||||
print(f'\n📝 创建文档: {args.name}')
|
||||
create_data = run_dws(create_args, dry_run=args.dry_run)
|
||||
content_path = str(supplied_path) if supplied_path else "<TEMP_CONTENT.md>"
|
||||
create_args = [
|
||||
"doc", "create",
|
||||
"--name", args.name,
|
||||
"--content-file", content_path,
|
||||
"--content-format", "markdown",
|
||||
"--format", "json",
|
||||
]
|
||||
if args.folder:
|
||||
create_args.extend(["--folder", args.folder])
|
||||
if args.workspace:
|
||||
create_args.extend(["--workspace", args.workspace])
|
||||
|
||||
node_id = None
|
||||
if not args.dry_run:
|
||||
if not create_data:
|
||||
sys.exit(1)
|
||||
node_id = (create_data.get('nodeId')
|
||||
or create_data.get('dentryUuid')
|
||||
or create_data.get('id', ''))
|
||||
print(f" ✓ 文档已创建 (ID: {node_id})")
|
||||
created = run_dws(create_args, dry_run=args.dry_run)
|
||||
node_id = "<NODE_ID>" if args.dry_run else first_value(
|
||||
created, ("nodeId", "dentryUuid")
|
||||
)
|
||||
if not node_id:
|
||||
raise ScriptError("文档创建响应缺少 nodeId,无法验证")
|
||||
|
||||
if len(content) <= chunk_size:
|
||||
mode_label = '追加' if args.mode == 'append' else '覆盖'
|
||||
print(f'\n✍️ 写入内容 (模式: {mode_label}, {len(content)} 字符)...')
|
||||
write_data = run_dws([
|
||||
'doc', 'update',
|
||||
'--node', node_id or '<NODE_ID>',
|
||||
'--content', content,
|
||||
'--mode', args.mode,
|
||||
'--format', 'json',
|
||||
], dry_run=args.dry_run)
|
||||
if write_data:
|
||||
print(f" ✓ 内容已写入 ({len(content)} 字符)")
|
||||
else:
|
||||
chunks = []
|
||||
pos = 0
|
||||
while pos < len(content):
|
||||
end = min(pos + chunk_size, len(content))
|
||||
if end < len(content):
|
||||
newline_pos = content.rfind('\n', pos, end)
|
||||
if newline_pos > pos:
|
||||
end = newline_pos + 1
|
||||
chunks.append(content[pos:end])
|
||||
pos = end
|
||||
info = run_dws(
|
||||
["doc", "info", "--node", node_id, "--format", "json"],
|
||||
dry_run=args.dry_run,
|
||||
)
|
||||
readback = run_dws(
|
||||
["doc", "read", "--node", node_id, "--format", "json"],
|
||||
dry_run=args.dry_run,
|
||||
)
|
||||
if args.dry_run:
|
||||
return 0
|
||||
if not first_value(readback, ("markdown", "jsonml", "content")):
|
||||
raise ScriptError("文档回读未返回正文,无法确认写入成功")
|
||||
|
||||
total_chunks = len(chunks)
|
||||
print(f'\n✍️ 内容较长 ({len(content)} 字符), 分 {total_chunks} 块写入...')
|
||||
|
||||
success_chunks = 0
|
||||
for idx, chunk in enumerate(chunks):
|
||||
chunk_mode = args.mode if idx == 0 else 'append'
|
||||
write_data = run_dws_with_retry(
|
||||
[
|
||||
'doc', 'update',
|
||||
'--node', node_id or '<NODE_ID>',
|
||||
'--content', chunk,
|
||||
'--mode', chunk_mode,
|
||||
'--format', 'json',
|
||||
],
|
||||
dry_run=args.dry_run,
|
||||
max_retries=args.max_retries,
|
||||
)
|
||||
if write_data:
|
||||
print(f" ✓ 块 {idx + 1}/{total_chunks} 已写入 ({len(chunk)} 字符)")
|
||||
success_chunks += 1
|
||||
elif not args.dry_run:
|
||||
# 写入失败,报告部分写入状态
|
||||
print(f"\n❌ 块 {idx + 1}/{total_chunks} 写入失败(已重试 {args.max_retries} 次)")
|
||||
print(f"\n⚠️ 文档处于部分写入状态:")
|
||||
print(f" - 文档 ID: {node_id}")
|
||||
print(f" - 已写入: {success_chunks}/{total_chunks} 块")
|
||||
print(f" - 失败位置: 第 {idx + 1} 块")
|
||||
if args.mode == 'overwrite':
|
||||
print(f" - 模式: 覆盖模式,文档可能包含不完整内容")
|
||||
print(f" - 建议: 手动检查文档内容,或删除后重新创建")
|
||||
else:
|
||||
print(f" - 模式: 追加模式,已写入内容已保存")
|
||||
print(f" - 建议: 可手动补充剩余内容,或重新运行脚本")
|
||||
sys.exit(1)
|
||||
print('\n✅ 完成!')
|
||||
summary = {
|
||||
"success": True,
|
||||
"nodeId": node_id,
|
||||
"docUrl": first_value(info, ("docUrl", "documentUrl", "url"))
|
||||
or first_value(created, ("docUrl", "documentUrl", "url")),
|
||||
"chunksWritten": first_value(created, ("chunksWritten",)),
|
||||
"verified": True,
|
||||
}
|
||||
print(json.dumps(summary, ensure_ascii=False))
|
||||
return 0
|
||||
finally:
|
||||
if temporary_path is not None:
|
||||
temporary_path.unlink(missing_ok=True)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
def main() -> None:
|
||||
try:
|
||||
raise SystemExit(run())
|
||||
except ScriptError as exc:
|
||||
print(f"错误:{exc}", file=sys.stderr)
|
||||
raise SystemExit(1) from exc
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
---
|
||||
name: dingtalk-aitable
|
||||
description: 钉钉 AI 表格(多维表)。Use when 用户说 AI表格/多维表/数据表/base/table/建表/查记录/写数据/字段/记录增删改查/筛选/排序/公式/模板搜索/批量导入CSV或JSON/导出/仪表盘/图表/上传附件到表格/按字段类型建表。不做电子表格单元格读写(走 dingtalk-misc)、文档编辑(走 dingtalk-doc);听记待办入表先用 dingtalk-minutes 提取,再由本 skill 写入。命令前缀:dws aitable。
|
||||
description: 钉钉 AI 表格(多维表)。Use when 用户说 AI表格/多维表/Base/数据表/字段/记录增删改查/筛选排序/公式与跨表引用/视图表单/仪表盘图表/高级权限/自动化工作流/模板/CSV或JSON批量导入/Excel导入导出/记录附件。不做电子表格单元格读写与工作表公式(走 dingtalk-sheet)、普通文档编辑(走 dingtalk-doc)或钉盘文件管理(走 dingtalk-drive);听记待办入表先用 dingtalk-minutes 提取,再由本 skill 写入。命令前缀:dws aitable。
|
||||
metadata:
|
||||
cli_version: ">=0.2.14"
|
||||
category: product
|
||||
@@ -15,146 +15,89 @@ metadata:
|
||||
|
||||
> **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)。
|
||||
## 加载与路由顺序
|
||||
|
||||
1. 命中下方高频意图时直接使用精确骨架,不先查 Help 或产品级 Schema。
|
||||
2. 路由优先级固定为:精确 recipe / 可运行脚本 > 匹配的公开 Shortcut > 原子命令。命令已确定且参数清楚时直接执行。
|
||||
3. 参数、约束或安全语义不确定时只读 leaf Schema:`dws schema --cli-path "aitable <leaf>" --format json`;只有当前 Cobra flag 不确定时才读对应 `--help`。
|
||||
4. 复杂字段、筛选、导入导出、视图、权限或工作流任务,按“低频能力与 Reference”只加载相关文件,不预读整个 `references/aitable/`。
|
||||
5. 现有骨架和 reference 都无法定位能力时,才用 Runtime Shortcut Catalog 做最后发现;不得猜 `cli_path` 或 flag。
|
||||
6. Schema、Help、reference 与实际返回冲突时采用更安全的解释并报告契约漂移;`confirmation=user_required` 时先确认,再添加 `--yes`。
|
||||
7. 用户已给足名称、字段、数据和目标时,直接按依赖链完成全部步骤;不要调用 todo 工具、分步汇报或追问已明确的信息。中间返回只用于提取下一步 ID 和判断失败,完成所有请求后再统一回读并答复。
|
||||
|
||||
<!-- VISIBLE_SHORTCUTS_START -->
|
||||
## Shortcuts(无专用脚本/recipe 时优先)
|
||||
## Shortcut 发现(按需)
|
||||
|
||||
以下 shortcut 同时进入公开 catalog 与 Runtime Schema。先按本 skill 的意图表、脚本和 recipe 路由:存在精确覆盖该场景的专用脚本/recipe 时按其执行;否则用户意图命中时,shortcut 优先于手写原子命令。命令已选中时直接执行;只在参数或安全语义不确定时读取 leaf Schema(例如 `dws schema --cli-path "aitable +<shortcut>" --format json`),在当前 Cobra flags 不确定时读取 `dws aitable <shortcut> --help`。仅当现有路由和 reference 都无法定位低频能力时,才用 `dws shortcut list --service aitable --format json` 批量发现。
|
||||
`aitable` 当前有 29 条公开 shortcut。完整清单保留在 Runtime Shortcut Catalog;已完成 Schema curation 的子集可通过 leaf Schema 查询。高频产品根 Skill 不重复展开完整清单。已知意图直接使用下方的优先路由、意图表或任务 reference;命令已选中时直接执行,只在参数/安全语义不确定时读取 leaf Schema,在当前 Cobra flags 不确定时读取 leaf Help。
|
||||
|
||||
| Shortcut | 风险 | 适用场景 |
|
||||
|---|---|---|
|
||||
| `dws aitable +base-get` | read | 获取指定 Base 的目录信息(tables / dashboards summary) |
|
||||
| `dws aitable +base-list` | read | 获取当前用户可访问的 AI 表格 Base 列表(最近访问,支持游标分页) |
|
||||
| `dws aitable +base-search` | read | 按名称关键词搜索 AI 表格 Base |
|
||||
| `dws aitable +chart-get` | read | 获取指定 chart 的详细信息 |
|
||||
| `dws aitable +chart-widgets-example` | read | 获取所有图表类型的 widget config 示例 |
|
||||
| `dws aitable +dashboard-config-example` | read | 获取 dashboard config 的结构示例 |
|
||||
| `dws aitable +dashboard-get` | read | 获取指定 dashboard 的详细信息(含 charts summary) |
|
||||
| `dws aitable +field-get` | read | 批量获取字段详情(含类型相关完整配置) |
|
||||
| `dws aitable +find-record` | read | 在指定多维表里按关键词查记录(只读) |
|
||||
| `dws aitable +form-field-list` | read | 列出表单视图当前可见的字段及其配置 |
|
||||
| `dws aitable +form-list` | read | 列出指定数据表下的所有表单视图 |
|
||||
| `dws aitable +form-share-get` | read | 读取视图当前的分享表单配置 |
|
||||
| `dws aitable +list-tables` | read | 列出某个多维表(base)里的所有数据表(只读,投影 tableId/tableName) |
|
||||
| `dws aitable +record-history-list` | read | 按 recordId 查询单条记录的变更历史 |
|
||||
| `dws aitable +record-query` | read | 查询表格记录(按 ID 取 / 条件筛选 / 关键词 / 分页) |
|
||||
| `dws aitable +record-query-empty` | read | 扫描并过滤出完全没填用户字段的空行 |
|
||||
| `dws aitable +record-share-links` | read | 批量(可 >20 条)获取多维表记录分享链接:去重+分片+合并 |
|
||||
| `dws aitable +record-share-url` | read | 按 recordId 批量获取记录分享链接,单次最多 20 条 |
|
||||
| `dws aitable +resolve-base` | read | 按名称搜索多维表 Base 并解析出唯一 baseId(只读) |
|
||||
| `dws aitable +resolve-table` | read | 在某个多维表 Base 内按名称解析出唯一的数据表 tableId(只读) |
|
||||
| `dws aitable +role-list` | read | 列出指定 Base 下的全部角色 |
|
||||
| `dws aitable +section-list-empty` | read | 列出指定 Base 下所有没有子节点的空文件夹 |
|
||||
| `dws aitable +section-list-nodes` | read | 列出指定 Base 当前版本下的全部 nsheet 节点 |
|
||||
| `dws aitable +table-get` | read | 批量获取指定数据表的表级信息、字段目录与视图目录 |
|
||||
| `dws aitable +template-search` | read | 按名称关键词搜索 AI 表格模板 |
|
||||
| `dws aitable +view-get` | read | 获取视图完整信息(列顺序、筛选、排序、分组等) |
|
||||
| `dws aitable +view-get-frozen-cols` | read | 获取视图当前冻结的左侧列数 |
|
||||
| `dws aitable +view-get-lock` | read | 获取视图锁定状态 |
|
||||
| `dws aitable +view-get-row-height` | read | 获取视图单元格行高(像素) |
|
||||
仅当现有路由和 reference 都无法定位低频能力时,才执行 `dws shortcut list --service aitable --compact --format json` 做最后回退;不要为已知高频意图加载完整 Shortcut Catalog 或产品级 Schema。
|
||||
<!-- VISIBLE_SHORTCUTS_END -->
|
||||
|
||||
## 意图表
|
||||
## 核心对象与 ID
|
||||
|
||||
| 用户说 | 命令 |
|
||||
|--------|------|
|
||||
| "搜表格 / 找一个 base" | `dws aitable base search --query "<名>"` |
|
||||
| "创建 AI 表格 / 多维表" | `dws aitable base create --name "<名称>" [--template-id <id>]` |
|
||||
| "查数据表 / 建数据表" | `dws aitable table get --base-id <baseId>` / `dws aitable table create --base-id <baseId> --name "<表名>" --fields '[...]'` |
|
||||
| "查字段 / 字段类型" | `dws aitable field get --base-id <id> --table-id <id>` |
|
||||
| "查记录 / 搜索记录" | `dws aitable record query --base-id <baseId> --table-id <tableId> [--filters '...']` |
|
||||
| "写记录 / 更新记录 / 删除记录" | `dws aitable record create/update/delete --base-id <baseId> --table-id <tableId> ...` |
|
||||
| "筛选 / 排序 / 公式 / 跨表引用" | 先读 `references/aitable/aitable-filter-sort.md` / `aitable-formula-guide.md` |
|
||||
| "批量导入 JSON / CSV" | `python scripts/import_records.py <baseId> <tableId> data.csv\|data.json` |
|
||||
| "批量加字段" | `python scripts/bulk_add_fields.py --base-id <id> --table-id <id> --fields fields.json` |
|
||||
| "导入 / 导出表格" | 先读 `references/aitable/aitable-export-import.md`;导出优先 `python scripts/aitable_export_via_task.py <baseId> --scope table --table-id <tableId>` |
|
||||
| "仪表盘 / 图表" | 先读 `references/aitable/aitable-dashboard-chart.md` |
|
||||
| "上传附件到记录" | 先读 `references/aitable/aitable-attachment.md`;可用 `python scripts/upload_attachment.py --base-id <id> --file <path>` |
|
||||
| 对象 | 标识与执行边界 |
|
||||
|---|---|
|
||||
| Base | `baseId` 标识一个 AI 表格文件;名称只用于搜索或消歧,不能当 ID |
|
||||
| Table | `tableId` 标识 Base 内的数据表;必须来自 `+resolve-table` / `+table-get` / 创建返回 |
|
||||
| Field | `fieldId` 标识列;写入、筛选、排序和字段变更优先使用真实 `fieldId` |
|
||||
| Record | `recordId` 标识行;更新、删除和分享前必须先查询得到真实 ID |
|
||||
| View / Dashboard / Chart | `viewId` / `dashboardId` / `chartId` 各自绑定当前 Base/Table,不跨对象复用 |
|
||||
| 异步任务 | `taskId` / `importId` 只用于对应导出或导入任务,不能替代业务对象 ID |
|
||||
|
||||
## 标准 SOP(必遵流程)
|
||||
所有下游 ID 都从当前链路的结构化返回中提取;同名多候选必须让用户消歧,不默认取第一项,也不复用未经本轮校验的旧 ID。
|
||||
|
||||
> 命中以下意图**必须**按对应 SOP 顺序执行;**禁止**跳步、替换命令、编造 flag/ID。每条命令必须带 `--format json`,执行后必须按"解析"步取真实字段,不得凭返回结构猜测。`baseId`/`tableId`/`fieldId`/`recordId` 一律先查后用,**禁止默认/编造**。
|
||||
## 核心意图与执行骨架
|
||||
|
||||
### SOP-1 定位 Base 与 Table(list / search → table get)
|
||||
| 用户意图 | 首选骨架 | 必须保留的执行边界 |
|
||||
|---|---|---|
|
||||
| 按名称找 Base | `dws aitable +resolve-base --name "<名称>" --format json` | 唯一命中才继续;多候选停止并消歧 |
|
||||
| 浏览最近访问 | `dws aitable +base-list --format json` | 只代表最近访问,不得宣称全量 |
|
||||
| 搜索模板 | `dws aitable +template-search --query "<关键词>" --format json` | 关键词参数是 `--query`,只返回真实候选,不擅自创建 Base |
|
||||
| 按名称找 Table | `dws aitable +resolve-table --base <baseId> --name "<表名>" --format json` | `baseId` 必须来自上一步真实返回 |
|
||||
| 取表、字段与视图目录 | `dws aitable +table-get --base-id <baseId> [--table-ids <tableId>] --format json` | `tables[].fields[]` 是字段目录;完整类型/config 再用 `+field-get` |
|
||||
| 取字段完整配置 | `dws aitable +field-get --base-id <baseId> --table-id <tableId> [--field-ids <ids>] --format json` | 写入前核对类型、只读性和 select options;按需展开以控制返回体 |
|
||||
| 查/搜/筛记录 | `dws aitable +record-query --base-id <baseId> --table-id <tableId> [--query <词>\|--filters '<JSON>'\|--record-ids <ids>] --format json` | ID 模式忽略 filter/sort;全量结论必须完整分页 |
|
||||
| 新增记录 | `dws aitable record create --base-id <baseId> --table-id <tableId> --records '[{"cells":{"<fieldId>":<值>}}]' --format json` | 单次最多 100;取 `data.newRecordIds[]` 后立即按 ID 回读 |
|
||||
| 更新记录 | `dws aitable record update --base-id <baseId> --table-id <tableId> --records '[{"recordId":"<id>","cells":{"<fieldId>":<值>}}]' --format json` | 先 query 拿 recordId;只传需改字段;取 `data.recordIds[]` 后回读 |
|
||||
| 删除记录 | 先 `dws aitable +record-query ...` 定位,再 `dws aitable record delete --base-id <baseId> --table-id <tableId> --record-ids <ids>` | 展示目标与影响,得到明确确认后才加 `--yes` |
|
||||
| 创建 Base / Table | `dws aitable base create --name "<名>"` / `dws aitable table create --base-id <id> --name "<名>" --fields '[...]'` | 使用创建返回的真实 ID;系统改名/加后缀时不得继续猜原名 |
|
||||
| 复制视图 | `dws aitable view duplicate --base-id <baseId> --table-id <tableId> --view-id <源viewId> --new-name "<新名称>" --format json` | 源 viewId 来自当前表的真实返回;不要复制数据表或创建仪表盘替代 |
|
||||
| 批量追加 CSV / JSON 到已有表 | `python3 scripts/import_records.py <baseId> <tableId> <file> [batch_size]` | CSV 表头必须是 fieldId;脚本返回不完整 ledger 时不得宣称全成功 |
|
||||
| 文件导入为新数据表 | `python3 scripts/aitable_import_via_task.py <baseId> <file>` | 与“追加已有 table”不同;走 prepare → PUT → import task |
|
||||
| 批量创建字段 | `python3 scripts/bulk_add_fields.py <baseId> <tableId> fields.json` | 单次最多 15;逐项检查成功/失败结果 |
|
||||
| 导出 Base / Table / View | `python3 scripts/aitable_export_via_task.py <baseId> --scope all\|table\|view [...]` | 保存路径、覆盖与异步未完成状态必须显式处理 |
|
||||
| 上传记录附件 | `python3 scripts/upload_attachment.py <baseId> <file>` | 返回 `fileToken` 后仍需按字段格式写入记录并回读 |
|
||||
|
||||
**触发**:找/打开某张 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 <baseId> --format json` → 从 `data.tables[].tableId` 取目标表 ID,并记录 `views[]`。枚举模式不返回 `fields[]`;需要字段目录时必须继续执行 SOP-2 的 `field get`。若只核对某张表,可显式加 `--table-ids <tableId>` 控制返回体。
|
||||
5. **失败(必须)**:`base list` 为空或不命中 → 换 `base search --query` 关键词重试一次;仍无果**必须如实告知**,禁止臆造 baseId/tableId。
|
||||
- `record create/update` 前必须获取目标字段的 `fieldId`、`type` 与 `config`;`filterUp`、`lookup` 等只读字段不可写。完整格式只在需要时读 [aitable-cell-value.md](references/aitable/aitable-cell-value.md)。
|
||||
- 筛选和排序字段使用 `fieldId`;`--filters` 最外层是 `and|or + operands`,`--sort` 使用 `direction: asc|desc`。日期和跨表字段规则按需读 [aitable-filter-sort.md](references/aitable/aitable-filter-sort.md)。
|
||||
- `record query --all` 仍受 `--page-limit` 约束;分页中断或局部富化失败时保留已有结果,输出 completeness 与逐项失败 ledger,不把部分结果描述为全量。
|
||||
- 创建、更新、导入、批量建字段等写操作必须检查业务 `status`、逐项结果与返回 ID;普通写入按用户明确要求执行后回读,不能只凭退出码宣称成功。
|
||||
- 长 JSON 使用 `--records-file` / 任务文件;不得为绕过字段错误而静默丢列、改类型或删除失败项。
|
||||
|
||||
**禁止**:跳过 `table get` 直接用字段名写记录、用模糊名匹配当 baseId、用旧会话里的 ID 不再校验。
|
||||
## 低频能力与 Reference
|
||||
|
||||
### SOP-2 拿字段定义(field get,写记录/改字段前置)
|
||||
| 场景 | 按需读取 |
|
||||
|---|---|
|
||||
| 完整命令索引、对象 URL 与一级路由 | [aitable.md](references/aitable.md) |
|
||||
| 记录 query/create/update/delete/upsert/history/share | 对应 `references/aitable/aitable-record-*.md` |
|
||||
| 字段创建、字段 config、cellValue、公式与跨表引用 | [aitable-field.md](references/aitable/aitable-field.md)、[aitable-field-properties.md](references/aitable/aitable-field-properties.md)、[aitable-cell-value.md](references/aitable/aitable-cell-value.md)、[aitable-formula-guide.md](references/aitable/aitable-formula-guide.md) |
|
||||
| 筛选、排序、统计、全量分析 | [aitable-filter-sort.md](references/aitable/aitable-filter-sort.md)、[aitable-data-analysis-sop.md](references/aitable/aitable-data-analysis-sop.md) |
|
||||
| 导入导出、附件 | [aitable-export-import.md](references/aitable/aitable-export-import.md)、[aitable-attachment.md](references/aitable/aitable-attachment.md) |
|
||||
| 视图、表单、仪表盘与图表 | [aitable-view-config.md](references/aitable/aitable-view-config.md)、[aitable-view-extras.md](references/aitable/aitable-view-extras.md)、[aitable-form.md](references/aitable/aitable-form.md)、[aitable-dashboard-chart.md](references/aitable/aitable-dashboard-chart.md) |
|
||||
| 高级权限、自动化工作流、导航节点 | [aitable-advperm.md](references/aitable/aitable-advperm.md)、[aitable-workflow.md](references/aitable/aitable-workflow.md)、[aitable.md](references/aitable.md) 的 section 路由 |
|
||||
|
||||
**触发**:建/改/写记录、改字段名或 options、按字段类型拼写入参前。
|
||||
## 错误恢复
|
||||
|
||||
1. **前置(必须)**:先按 SOP-1 拿到 `baseId` + `tableId`。
|
||||
2. **执行(必须)**:`dws aitable field get --base-id <baseId> --table-id <tableId> --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 <fieldId> --name <新名>|--config <JSON> --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 <baseId> --table-id <tableId> --records '[{"cells":{"<fieldId>":<值>}}]' --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 <baseId> --table-id <tableId> --record-ids <id1,id2,...> --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 <baseId> --table-id <tableId> --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 <baseId> --table-id <tableId> --records '[{"recordId":"recXXX","cells":{"<fieldId>":<新值>}}]' --format json`(每条必含 `recordId`+`cells`,单次≤100;超长用 `--records-file`);只传需改字段,未传保持原值。
|
||||
3. **解析与验证(必须)**:写入格式同 SOP-3;从返回 `data.recordIds[]` 取实际更新的记录 ID。更新响应不返回“受影响字段”,必须立即用 `record query --record-ids <id1,id2,...> --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 <baseId> --table-id <tableId> --field-id <fieldId> --name "<新名称>" --format json`;先 `field get` 找真实 `fieldId`,不要猜字段名能直接更新。
|
||||
- 写记录前必须 `field get` 获取 `fieldId` 与类型;`record create/update` 的 `cells` key 用 `fieldId`,不是字段中文名。长 JSON 使用 `--records-file`。
|
||||
- 表或字段创建返回名称被系统自动加后缀时,后续必须使用返回的真实 `tableId`/`fieldId`,不要继续按原名称猜。
|
||||
- `record update/delete` 先 `record query/list` 定位 `recordId`;删除必须确认,普通新增/更新按用户明确要求可直接执行后读回验证。
|
||||
- `record query/create/update/delete`、`field create`、导入导出、图表和附件场景必须先读对应 `references/aitable/*.md`,不要凭旧单文件参数猜 flag。
|
||||
|
||||
## 字段类型规则
|
||||
|
||||
详见本 skill 的 [field-rules.md](references/field-rules.md)。
|
||||
- 路径或 flag 错误:按既定的 leaf Schema → leaf Help 顺序校正一次;仍失败则停止,不连续尝试猜测别名。
|
||||
- 命令非零、输出非 JSON、业务 `status != success`、必需 ID 缺失、批处理部分失败均视为失败;保留成功项与 ledger,禁止吞错。
|
||||
- 同名歧义、权限不足、资源不存在、字段类型漂移、分页无法推进或 Schema/Help 冲突时停止并报告。具体恢复动作按需读 [aitable-error-recovery.md](references/aitable/aitable-error-recovery.md)。
|
||||
- 每次重试都从最新实际输出重新提取下游 ID;删除和其他 `confirmation=user_required` 操作不得自动重试或静默确认。
|
||||
|
||||
## 跨产品协作
|
||||
|
||||
- 单元格 / 工作表 / 公式 → 切到 `dingtalk-misc`(`references/sheet.md`,命令前缀:`dws sheet`)
|
||||
## 局部意图
|
||||
|
||||
- [局部意图消歧](references/intent-guide.md)。
|
||||
- 电子表格工作表、单元格与公式 → `dingtalk-sheet`;结构化 Base/Table/Field/Record 才走本 skill。
|
||||
- 普通文档内容 → `dingtalk-doc`;钉盘普通文件与文件夹 → `dingtalk-drive`;记录附件上传仍走本 skill。
|
||||
- 用户直接提供类型不明的 alidocs URL 时,按 `dws-shared` 的 URL 预检导航确认 `extension=able` 后再执行。
|
||||
- 听记内容入表:先用 `dingtalk-minutes` 提取结构化结果,再按本 skill 的字段与记录规则写入。
|
||||
|
||||
@@ -2,6 +2,8 @@
|
||||
|
||||
> **渐进式文档**:本文件为路由层(索引 + 意图判断),各命令的详细参数、示例和踩坑说明在 [aitable/](./aitable/) 目录下按需加载。
|
||||
|
||||
已知高频意图优先使用根 Skill 的精确 Shortcut/脚本骨架;本文件只在需要完整一级命令索引、对象 URL 或低频分支导航时加载。参数与安全不确定时读 leaf Schema,Cobra flag 不确定时才读 leaf Help,不要把本文件当作参数事实源。
|
||||
|
||||
## 文档地址 (URI)
|
||||
|
||||
| 资源 | URI 格式 |
|
||||
@@ -35,7 +37,7 @@
|
||||
|
||||
| 命令 | 用途 | 必填参数 | 路由提醒 |
|
||||
|------|------|----------|----------|
|
||||
| `table get` | 获取数据表/视图目录 | `--base-id` | 不传 `--table-ids` 枚举全部表,但不返回字段;字段目录使用 `field get` |
|
||||
| `table get` | 获取表级信息、字段目录与视图目录 | `--base-id` | 不传 `--table-ids` 枚举全部表并返回精简 `fields[]`/`views[]`;字段完整 config 使用 `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` | 不可逆 |
|
||||
@@ -427,17 +429,18 @@ dws aitable export data --base-id <BASE_ID> --task-id <TASK_ID> --timeout-ms 300
|
||||
## 核心工作流
|
||||
|
||||
```bash
|
||||
# 1. 搜索/列出 Base — 提取 baseId
|
||||
dws aitable base search --query "项目" --format json
|
||||
# 1. 按名称解析唯一 Base — 提取 baseId;多候选必须消歧
|
||||
dws aitable +resolve-base --name "项目" --format json
|
||||
|
||||
# 2. 获取 Base 信息 — 提取 tableId
|
||||
dws aitable base get --base-id <BASE_ID> --format json
|
||||
# 2. 按名称解析唯一 Table — 提取 tableId
|
||||
dws aitable +resolve-table --base <BASE_ID> --name "任务" --format json
|
||||
|
||||
# 3. 获取字段目录 — 提取 fieldId
|
||||
dws aitable field get --base-id <BASE_ID> --table-id <TABLE_ID> --format json
|
||||
# 3. 获取字段目录;需要完整类型 config 时再调用 field get
|
||||
dws aitable +table-get --base-id <BASE_ID> --table-ids <TABLE_ID> --format json
|
||||
dws aitable +field-get --base-id <BASE_ID> --table-id <TABLE_ID> --format json
|
||||
|
||||
# 4. 查询记录
|
||||
dws aitable record query --base-id <BASE_ID> --table-id <TABLE_ID> --format json
|
||||
dws aitable +record-query --base-id <BASE_ID> --table-id <TABLE_ID> --format json
|
||||
|
||||
# 5. 新增记录 (cells 用 fieldId 作 key)
|
||||
dws aitable record create --base-id <BASE_ID> --table-id <TABLE_ID> \
|
||||
@@ -452,8 +455,8 @@ dws aitable record create --base-id <BASE_ID> --table-id <TABLE_ID> \
|
||||
| `base create` | `baseId` | 后续命令 + 文档 URI |
|
||||
| `base get` | `tables[].tableId` | --table-id,拼接指定数据表 URI |
|
||||
| `table create` | `tableId` | 后续命令 + 拼接指定数据表 URI |
|
||||
| `table get` | `tables[].tableId`、视图目录 | 定位数据表和视图;字段需继续调用 `field get` |
|
||||
| `field get` | `fields[].fieldId` | record 操作的 cells key, field update/delete |
|
||||
| `table get` | `tables[].tableId`、精简 `fields[]`、`views[]` | 定位数据表、字段目录和视图;完整字段 config 再用 `field get` |
|
||||
| `field get` | `fields[].fieldId/type/config` | record 操作的 cells key、类型校验、field update/delete |
|
||||
| `record query` | `recordId` | record update/delete;按 ID 反查字段值用 `record get` |
|
||||
| `template search` | `templateId` | base create --template-id,拼接模板预览 URI |
|
||||
|
||||
@@ -480,6 +483,7 @@ dws aitable record create --base-id <BASE_ID> --table-id <TABLE_ID> \
|
||||
|------|------|
|
||||
| [bulk_add_fields.py](../scripts/bulk_add_fields.py) | 批量添加字段 |
|
||||
| [import_records.py](../scripts/import_records.py) | 从 JSON/CSV 批量导入记录 |
|
||||
| [aitable_import_via_task.py](../scripts/aitable_import_via_task.py) | 导入 CSV/XLS/XLSX 并新建数据表(prepare + PUT + import) |
|
||||
| [aitable_export_via_task.py](../scripts/aitable_export_via_task.py) | 文件导出(export_data 轮询 + 下载) |
|
||||
| [upload_attachment.py](../scripts/upload_attachment.py) | 上传附件到 AI 表格记录 |
|
||||
|
||||
|
||||
@@ -32,6 +32,7 @@ dws aitable record create --base-id <BASE_ID> --table-id <TABLE_ID> \
|
||||
```
|
||||
|
||||
> `uploadUrl` 有时效性(`expiresAt`),脚本会自动在获取后立即上传。
|
||||
> 脚本只接受成功的业务响应和 HTTPS `uploadUrl`;它返回 `fileToken` 不等于记录已经写入,后续仍需执行 record create/update 并按 recordId 回读。
|
||||
|
||||
## 手动流程(不使用脚本)
|
||||
|
||||
|
||||
@@ -34,11 +34,11 @@ dws aitable chart get --base-id <BASE_ID> --dashboard-id <DASHBOARD_ID> --chart-
|
||||
| 命令 | 用途 | 必填参数 |
|
||||
|------|------|----------|
|
||||
| `chart get` | 获取图表详情 | `--base-id` `--dashboard-id` `--chart-id` |
|
||||
| `chart create` | 创建图表 | `--base-id` `--dashboard-id` `--config` |
|
||||
| `chart create` | 创建图表 | `--base-id` `--dashboard-id` `--config` `--layout` |
|
||||
| `chart update` | 更新图表配置 | `--base-id` `--dashboard-id` `--chart-id` `--config` |
|
||||
| `chart delete` | 删除图表 | `--base-id` `--dashboard-id` `--chart-id` `--yes` |
|
||||
| `chart widgets-example` | 查看图表 widgets 配置模板 | 无 |
|
||||
|
||||
## 配置获取流程
|
||||
|
||||
创建图表前,必须先调用 `chart widgets-example` 查看配置模板,了解每种图表类型需要的字段结构,然后根据实际 tableId 和 fieldId 填充配置。
|
||||
创建图表前至多调用一次 `chart widgets-example`,不要为解析其 JSONC 重复调用或编写临时解析脚本;根据真实 tableId 和 fieldId 填充 `--config`。`chart create` 必须同时传 `--layout '{"x":0,"y":0,"w":6,"h":4}'`,多图表使用互不重叠的位置。用户未指定数量时只创建足以表达目标的 1–2 个图表。取返回 chartId 后立即 `chart get`,或最后 `dashboard get` 核对 charts;回读成功即答复,缺失时不得声称成功或重复提交同一条缺参命令。
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
- `status: "error"` 表示操作失败
|
||||
- `summary` 包含错误摘要信息
|
||||
- `trace_id` 用于问题追踪
|
||||
- 命令退出码为 0 但 `status != "success"`、必需 ID 缺失或逐项结果含失败,也属于业务失败
|
||||
|
||||
## 2. 常见错误与恢复动作
|
||||
|
||||
@@ -42,7 +43,7 @@
|
||||
|-------------------|------|---------|
|
||||
| `base not found` | base-id 错误或无权限 | 确认 base-id 正确;尝试 `base list` 或 `base search` 重新定位 |
|
||||
| `table not found` | table-id 错误 | 用 `table get --base-id <baseId>` 不带 table-ids 查看所有表 |
|
||||
| 表名重复 | 同 Base 下已存在同名表 | 系统会自动续号(如"原名 1"),无需额外处理 |
|
||||
| 表名重复 | 同 Base 下已存在同名表 | 使用创建返回的真实 `tableId/tableName`;禁止继续按原名猜测目标 |
|
||||
|
||||
### 2.4 视图操作错误
|
||||
|
||||
@@ -57,7 +58,7 @@
|
||||
|-------------------|------|---------|
|
||||
| filters 无效被忽略 | 根节点不是 and/or,或 operands 格式错误 | 确保 filters 根节点是 `{"operator":"and"/"or", "operands":[...]}` 结构 |
|
||||
| sort 无效 | fieldId 不存在 | 先 `field get` 确认字段 ID |
|
||||
| 筛选结果为空 | 条件过严或字段值不匹配 | 放宽条件验证;注意 singleSelect 筛选值用 option name 或 id |
|
||||
| 筛选结果为空 | 条件过严或字段值不匹配 | 放宽条件验证;singleSelect/multipleSelect 筛选值使用 option name 字面量 |
|
||||
|
||||
### 2.6 导入导出错误
|
||||
|
||||
@@ -65,6 +66,7 @@
|
||||
|-------------------|------|---------|
|
||||
| 导出任务超时 | 数据量大,异步任务未完成 | 用 `export data --task-id <taskId>` 轮询直到完成 |
|
||||
| 导入文件格式错误 | 不支持的文件格式或文件损坏 | 确认文件为 .xlsx 格式且未加密 |
|
||||
| 批处理只成功一部分 | 某批或某个字段返回失败 | 保留成功 ID,输出失败 batch/item ledger,以非零状态结束;不要整批重放 |
|
||||
|
||||
## 3. 重试策略
|
||||
|
||||
@@ -106,18 +108,6 @@ dws aitable record create \
|
||||
|
||||
`--verbose` 会输出请求/响应的详细信息,帮助定位问题。
|
||||
|
||||
### 4.2 使用 --dry-run 预览
|
||||
|
||||
```bash
|
||||
dws aitable record create \
|
||||
--base-id <baseId> \
|
||||
--table-id <tableId> \
|
||||
--records '[...]' \
|
||||
--dry-run --format json
|
||||
```
|
||||
|
||||
`--dry-run` 只预览不执行,适合在不确定参数是否正确时先验证。
|
||||
|
||||
## 5. 错误预防最佳实践
|
||||
|
||||
1. **写记录前先读字段结构** — `field get` 确认字段类型和 ID
|
||||
|
||||
@@ -4,6 +4,8 @@
|
||||
|
||||
`export data` 为异步任务:首次调用可能只返回 `taskId`,需要继续轮询。
|
||||
|
||||
优先使用 `python3 scripts/aitable_export_via_task.py <baseId> --scope all|table|view [...]`:它检查业务状态、持续轮询、要求 HTTPS 下载地址,并在本地文件已存在时停止而不是静默覆盖。只有需要控制底层轮询参数时才走下面的原子命令。
|
||||
|
||||
> ⚠️ **`--format` 冲突警告**:`export data` 的 `--format` 是**导出格式**(excel/attachment 等),不是全局输出格式。**此命令禁止追加全局 `--format json`**,否则会覆盖导出格式导致 `INVALID_EXPORT_FORMAT` 错误。输出默认就是 JSON,无需额外指定。
|
||||
|
||||
```bash
|
||||
@@ -28,6 +30,8 @@ dws aitable export data --base-id <BASE_ID> --task-id <TASK_ID> --timeout-ms 300
|
||||
|
||||
> **无需手动解析 CSV/Excel 再逐条 record create**,效率极低且容易出错。
|
||||
|
||||
新建数据表导入优先使用 `python3 scripts/aitable_import_via_task.py <baseId> <file>`,脚本封装 prepare → PUT → import 并检查每一步业务状态。追加到已有表且需要字段级类型控制时,使用 `python3 scripts/import_records.py <baseId> <tableId> <file> [batch_size]`;二者语义不同,不要自动互换。
|
||||
|
||||
```bash
|
||||
# 第 1 步:申请上传凭证
|
||||
dws aitable import upload --base-id <BASE_ID> \
|
||||
|
||||
@@ -14,7 +14,7 @@ Flags:
|
||||
--table-id string Table ID (必填)
|
||||
```
|
||||
|
||||
返回字段的完整配置(含 options 等)。不要假设未指定 `--table-ids` 的 `table get` 枚举结果含字段;字段目录和配置以 `field get` 返回为准。
|
||||
返回字段的完整配置(含 options 等)。`table get` 会返回精简字段目录,但写入、筛选或修改前需要类型相关完整 `config` 时,以 `field get` 返回为准。
|
||||
|
||||
## field create — 创建字段
|
||||
|
||||
|
||||
@@ -47,6 +47,8 @@ dws aitable record query --base-id <BASE_ID> --table-id <TABLE_ID> \
|
||||
|
||||
创建成功以 `data.newRecordIds[]` 为 ID 来源;不要把整个 `data` 当作单个 recordId,也不要只以退出码作为写入成功证据。
|
||||
|
||||
批量追加本地 JSON/CSV 到已有表时可使用 `python3 scripts/import_records.py <baseId> <tableId> <file> [batch_size]`。脚本会逐批检查业务状态、收集 `newRecordIds` 并按 ID 回读;任何批次失败或回读不完整都会输出 ledger 并以非零状态结束,已有成功批次仍会保留在结果中。
|
||||
|
||||
## cells 写入格式
|
||||
|
||||
各字段类型的写入格式见 [aitable-cell-value.md](./aitable-cell-value.md)。
|
||||
|
||||
@@ -60,7 +60,7 @@ dws aitable record query --base-id X --table-id Y --all --cursor "上次返回
|
||||
{"operator":"and","operands":[{"operator":"eq","operands":["<fieldId>","<value>"]}]}
|
||||
```
|
||||
|
||||
> **singleSelect/multipleSelect 过滤**:filters 中可传 option id 或 option name,但建议优先用 **option id**(通过 `field get` 获取),更可靠。
|
||||
> **singleSelect/multipleSelect 过滤**:filters 中传选项名称字面量,不传 option id。选项名称先通过 `field get` 的 `config.options[]` 核对。
|
||||
|
||||
## 减少响应体积
|
||||
|
||||
|
||||
@@ -17,23 +17,17 @@ Flags:
|
||||
|
||||
只需传入需修改的字段,未传入的保持原值。每条记录必须含 recordId 和 cells。
|
||||
|
||||
## cells key:优先使用 fieldId,也支持唯一字段名
|
||||
## cells key:自动化与 Agent 路径使用 fieldId
|
||||
|
||||
`cells` 的 key 有两种写法:
|
||||
公开稳定的 Agent/自动化写法是 `fieldId`:它不受字段重命名或重名影响,并可通过 `field get` 获取。
|
||||
|
||||
- fieldId(推荐):不受字段重命名或重名影响,通过 `field get` 获取。
|
||||
- 当前表内唯一的字段名:按名称精确匹配;如果存在同名字段,必须改用 fieldId。
|
||||
|
||||
同一字段同时通过 fieldId 和字段名传入时,fieldId 对应的值优先。
|
||||
Runtime 仍兼容当前表内唯一字段名,但这属于便捷兼容路径:重名或重命名会改变解析结果,不能用于脚本、跨步骤调用或从旧上下文重放。字段名与 fieldId 同时出现时也不要依赖覆盖顺序。
|
||||
|
||||
```bash
|
||||
# 推荐:fieldId
|
||||
dws aitable record update --base-id <BASE_ID> --table-id <TABLE_ID> \
|
||||
--records '[{"recordId":"recXXX","cells":{"fldStatusId":"已完成"}}]' --format json
|
||||
|
||||
# 便捷写法:当前表内唯一字段名
|
||||
dws aitable record update --base-id <BASE_ID> --table-id <TABLE_ID> \
|
||||
--records '[{"recordId":"recXXX","cells":{"状态":"已完成"}}]' --format json
|
||||
```
|
||||
|
||||
## 推荐参数形式
|
||||
|
||||
@@ -54,15 +54,20 @@ def parse_json_output(raw: str) -> Optional[Dict[str, Any]]:
|
||||
|
||||
|
||||
def normalize_download_url(url: str) -> str:
|
||||
if url.startswith("http://") or url.startswith("https://"):
|
||||
return url
|
||||
return f"https://{url}"
|
||||
normalized = url if "://" in url else f"https://{url}"
|
||||
parsed = urlparse(normalized)
|
||||
if parsed.scheme != "https" or not parsed.hostname:
|
||||
raise ValueError("downloadUrl 必须是有效的 HTTPS URL")
|
||||
return normalized
|
||||
|
||||
|
||||
def download_file(url: str, output_path: Path) -> Tuple[bool, str]:
|
||||
req = Request(url, method="GET")
|
||||
try:
|
||||
with urlopen(req, timeout=180) as resp:
|
||||
redirected = urlparse(resp.geturl())
|
||||
if redirected.scheme != "https" or not redirected.hostname:
|
||||
return False, "download redirect is not HTTPS"
|
||||
if resp.status != 200:
|
||||
return False, f"download http status: {resp.status}"
|
||||
output_path.write_bytes(resp.read())
|
||||
@@ -79,6 +84,20 @@ def fail(msg: str, code: int = 1) -> None:
|
||||
sys.exit(code)
|
||||
|
||||
|
||||
def resolve_output_path(value: Optional[str], file_name: str, overwrite: bool) -> Path:
|
||||
root = Path.cwd().resolve()
|
||||
candidate = Path(value) if value else Path(file_name).name
|
||||
output_path = candidate.resolve() if candidate.is_absolute() else (root / candidate).resolve()
|
||||
try:
|
||||
output_path.relative_to(root)
|
||||
except ValueError as exc:
|
||||
raise ValueError("输出路径必须位于当前工作目录内") from exc
|
||||
if output_path.exists() and not overwrite:
|
||||
raise ValueError(f"输出文件已存在:{output_path};如需覆盖请显式传 --overwrite")
|
||||
output_path.parent.mkdir(parents=True, exist_ok=True)
|
||||
return output_path
|
||||
|
||||
|
||||
def build_start_args(args: argparse.Namespace) -> list[str]:
|
||||
cmd = [
|
||||
"aitable",
|
||||
@@ -113,6 +132,7 @@ def main() -> None:
|
||||
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,不下载文件")
|
||||
parser.add_argument("--overwrite", action="store_true", help="允许覆盖当前工作目录内的已有输出文件")
|
||||
args = parser.parse_args()
|
||||
|
||||
if not validate_resource_id(args.base_id):
|
||||
@@ -121,6 +141,10 @@ def main() -> None:
|
||||
fail("scope=table/view 时必须传 --table-id")
|
||||
if args.scope == "view" and not args.view_id:
|
||||
fail("scope=view 时必须传 --view-id")
|
||||
if args.table_id and not validate_resource_id(args.table_id):
|
||||
fail("无效的 tableId 格式")
|
||||
if args.view_id and not validate_resource_id(args.view_id):
|
||||
fail("无效的 viewId 格式")
|
||||
|
||||
print("[1/2] start export task", file=sys.stderr)
|
||||
rc, out, err = run_dws(args.dws, build_start_args(args), timeout_sec=120)
|
||||
@@ -132,7 +156,7 @@ def main() -> None:
|
||||
|
||||
data = obj.get("data", {}) or {}
|
||||
status = obj.get("status")
|
||||
if status == "error":
|
||||
if status != "success":
|
||||
fail(f"export_data 返回失败: {json.dumps(obj, ensure_ascii=False)}")
|
||||
|
||||
download_url = data.get("downloadUrl")
|
||||
@@ -163,7 +187,7 @@ def main() -> None:
|
||||
obj2 = parse_json_output(out2)
|
||||
if not obj2:
|
||||
fail(f"export_data 轮询返回非 JSON: {out2[:300]}")
|
||||
if obj2.get("status") == "error":
|
||||
if obj2.get("status") != "success":
|
||||
fail(f"export_data 轮询返回失败: {json.dumps(obj2, ensure_ascii=False)}")
|
||||
d2 = obj2.get("data", {}) or {}
|
||||
download_url = d2.get("downloadUrl") or download_url
|
||||
@@ -194,8 +218,11 @@ def main() -> None:
|
||||
print(json.dumps(result, ensure_ascii=False, indent=2))
|
||||
return
|
||||
|
||||
norm_url = normalize_download_url(download_url)
|
||||
output_path = Path(args.output).expanduser().resolve() if args.output else Path.cwd() / file_name
|
||||
try:
|
||||
norm_url = normalize_download_url(download_url)
|
||||
output_path = resolve_output_path(args.output, file_name, args.overwrite)
|
||||
except ValueError as exc:
|
||||
fail(str(exc))
|
||||
ok, dl_err = download_file(norm_url, output_path)
|
||||
if not ok:
|
||||
fail(f"downloadUrl 下载失败: {dl_err}")
|
||||
|
||||
@@ -22,6 +22,7 @@ import sys
|
||||
from pathlib import Path
|
||||
from typing import Any, Dict, Optional, Tuple
|
||||
from urllib.error import HTTPError, URLError
|
||||
from urllib.parse import urlparse
|
||||
from urllib.request import Request, urlopen
|
||||
|
||||
RESOURCE_ID_PATTERN = re.compile(r"^[A-Za-z0-9_-]{8,128}$")
|
||||
@@ -52,6 +53,9 @@ def parse_json_output(raw: str) -> Optional[Dict[str, Any]]:
|
||||
|
||||
|
||||
def put_file(upload_url: str, file_path: Path) -> Tuple[bool, str]:
|
||||
parsed = urlparse(upload_url)
|
||||
if parsed.scheme != "https" or not parsed.hostname:
|
||||
return False, "uploadUrl must be a valid HTTPS URL"
|
||||
payload = file_path.read_bytes()
|
||||
req = Request(upload_url, data=payload, method="PUT")
|
||||
# 关键:清空 Content-Type,避免 SignatureDoesNotMatch。
|
||||
|
||||
@@ -1,273 +1,276 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
批量添加字段到钉钉 AI 表格数据表(新版 schema)
|
||||
"""批量添加字段到钉钉 AI 表格数据表。
|
||||
|
||||
用法:
|
||||
python bulk_add_fields.py <baseId> <tableId> fields.json
|
||||
python3 bulk_add_fields.py <baseId> <tableId> fields.json
|
||||
|
||||
fields.json 格式:
|
||||
[
|
||||
{"fieldName": "字段 1", "type": "text"},
|
||||
{"fieldName": "字段 2", "type": "number", "config": {"formatter": "INT"}},
|
||||
{"fieldName": "字段 3", "type": "singleSelect", "config": {"options": [{"name": "高"}]}}
|
||||
]
|
||||
|
||||
兼容写法:
|
||||
- name 会自动映射为 fieldName
|
||||
- phone 会自动映射为 telephone
|
||||
脚本检查业务状态和逐项结果,并回读成功字段。部分成功会输出 ledger,但整体以非零
|
||||
状态结束。fields.json 单次最多 15 个字段;name 会映射为 fieldName,phone 会映射为
|
||||
telephone。
|
||||
"""
|
||||
|
||||
import sys
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import json
|
||||
import subprocess
|
||||
import os
|
||||
import re
|
||||
import subprocess
|
||||
import sys
|
||||
from pathlib import Path
|
||||
from typing import Union, List, Dict, Any, Optional, Tuple
|
||||
from typing import Any, Dict, List, Optional, Tuple, Union
|
||||
|
||||
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_-]{8,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', 'address', 'primaryDoc', 'formula',
|
||||
'unidirectionalLink', 'bidirectionalLink', 'lookup', 'filterUp',
|
||||
'creator', 'lastModifier', 'createdTime', 'lastModifiedTime',
|
||||
}
|
||||
FIELD_TYPE_ALIASES = {
|
||||
'phone': 'telephone',
|
||||
"text", "number", "singleSelect", "multipleSelect", "date", "currency",
|
||||
"user", "department", "group", "progress", "rating", "checkbox",
|
||||
"attachment", "url", "richText", "telephone", "email", "idCard",
|
||||
"barcode", "geolocation", "address", "primaryDoc", "formula",
|
||||
"unidirectionalLink", "bidirectionalLink", "lookup", "filterUp",
|
||||
"creator", "lastModifier", "createdTime", "lastModifiedTime",
|
||||
}
|
||||
|
||||
|
||||
def resolve_safe_path(path: str, allowed_root: Optional[str] = None) -> Path:
|
||||
if allowed_root is None:
|
||||
allowed_root = os.environ.get('OPENCLAW_WORKSPACE', os.getcwd())
|
||||
|
||||
allowed_root = Path(allowed_root).resolve()
|
||||
target_path = (
|
||||
Path(path).resolve()
|
||||
if Path(path).is_absolute()
|
||||
else (Path.cwd() / path).resolve()
|
||||
)
|
||||
|
||||
root = Path(allowed_root or os.environ.get("OPENCLAW_WORKSPACE", os.getcwd())).resolve()
|
||||
candidate = Path(path)
|
||||
target = candidate.resolve() if candidate.is_absolute() else (Path.cwd() / candidate).resolve()
|
||||
try:
|
||||
target_path.relative_to(allowed_root)
|
||||
return target_path
|
||||
except ValueError:
|
||||
raise ValueError(
|
||||
f"路径超出允许范围:{path}\n"
|
||||
f"目标路径:{target_path}\n"
|
||||
f"允许根目录:{allowed_root}\n"
|
||||
f"提示:设置 OPENCLAW_WORKSPACE 环境变量或确保文件在工作目录内"
|
||||
)
|
||||
target.relative_to(root)
|
||||
except ValueError as exc:
|
||||
raise ValueError(f"路径超出允许范围:{path}(允许根目录:{root})") from exc
|
||||
return target
|
||||
|
||||
|
||||
def validate_resource_id(resource_id: str) -> bool:
|
||||
return bool(resource_id and RESOURCE_ID_PATTERN.match(resource_id.strip()))
|
||||
return bool(resource_id and RESOURCE_ID_PATTERN.fullmatch(resource_id.strip()))
|
||||
|
||||
|
||||
def validate_file_extension(filename: str, allowed_extensions: list) -> bool:
|
||||
return any(filename.lower().endswith(ext) for ext in allowed_extensions)
|
||||
|
||||
|
||||
def safe_json_load(file_path: Path, max_size: int = MAX_FILE_SIZE) -> JsonData:
|
||||
file_size = file_path.stat().st_size
|
||||
if file_size > max_size:
|
||||
raise ValueError(
|
||||
f"文件过大:{file_size:,} 字节 (限制:{max_size:,} 字节)"
|
||||
)
|
||||
with open(file_path, 'r', encoding='utf-8') as f:
|
||||
return json.load(f)
|
||||
def safe_json_load(file_path: Path) -> JsonData:
|
||||
if file_path.stat().st_size > MAX_FILE_SIZE:
|
||||
raise ValueError(f"文件过大(限制 {MAX_FILE_SIZE:,} 字节)")
|
||||
with file_path.open("r", encoding="utf-8") as stream:
|
||||
return json.load(stream)
|
||||
|
||||
|
||||
def normalize_field_config(field: Dict[str, Any]) -> Dict[str, Any]:
|
||||
normalized = dict(field)
|
||||
if 'fieldName' not in normalized and 'name' in normalized:
|
||||
normalized['fieldName'] = normalized.pop('name')
|
||||
normalized['type'] = FIELD_TYPE_ALIASES.get(
|
||||
normalized.get('type', 'text'), normalized.get('type', 'text')
|
||||
)
|
||||
if "fieldName" not in normalized and "name" in normalized:
|
||||
normalized["fieldName"] = normalized.pop("name")
|
||||
if normalized.get("type") == "phone":
|
||||
normalized["type"] = "telephone"
|
||||
return normalized
|
||||
|
||||
|
||||
def validate_field_config(field: Dict[str, Any]) -> Tuple[bool, str]:
|
||||
def validate_field_config(field: Any) -> Tuple[bool, str]:
|
||||
if not isinstance(field, dict):
|
||||
return False, '字段配置必须是对象'
|
||||
|
||||
field = normalize_field_config(field)
|
||||
|
||||
if 'fieldName' not in field:
|
||||
return False, '缺少必需字段:fieldName'
|
||||
if not isinstance(field['fieldName'], str) or not field['fieldName'].strip():
|
||||
return False, 'fieldName 必须是非空字符串'
|
||||
|
||||
field_type = field.get('type', 'text')
|
||||
return False, "字段配置必须是对象"
|
||||
normalized = normalize_field_config(field)
|
||||
name = normalized.get("fieldName")
|
||||
if not isinstance(name, str) or not name.strip():
|
||||
return False, "fieldName 必须是非空字符串"
|
||||
field_type = normalized.get("type", "text")
|
||||
if field_type not in ALLOWED_FIELD_TYPES:
|
||||
return False, f"不支持的字段类型:{field_type}"
|
||||
|
||||
config = field.get('config')
|
||||
config = normalized.get("config")
|
||||
if config is not None and not isinstance(config, dict):
|
||||
return False, 'config 必须是对象'
|
||||
|
||||
if field_type in {'singleSelect', 'multipleSelect'}:
|
||||
options = (config or {}).get('options')
|
||||
if not options or not isinstance(options, list):
|
||||
return False, (
|
||||
'singleSelect / multipleSelect 必须提供 config.options 数组'
|
||||
)
|
||||
|
||||
if field_type in {'unidirectionalLink', 'bidirectionalLink'}:
|
||||
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, ''
|
||||
return False, "config 必须是对象"
|
||||
ai_config = normalized.get("aiConfig")
|
||||
if ai_config is not None and not isinstance(ai_config, dict):
|
||||
return False, "aiConfig 必须是对象"
|
||||
if field_type in {"singleSelect", "multipleSelect"}:
|
||||
options = (config or {}).get("options")
|
||||
if not isinstance(options, list) or not options:
|
||||
return False, "singleSelect / multipleSelect 必须提供 config.options 数组"
|
||||
if field_type in {"unidirectionalLink", "bidirectionalLink"}:
|
||||
linked_table_id = (config or {}).get("linkedTableId")
|
||||
if not validate_resource_id(str(linked_table_id or "")):
|
||||
return False, "关联字段必须提供合法的 config.linkedTableId"
|
||||
if field_type == "lookup":
|
||||
required = ("associateField", "valuesField", "aggregator")
|
||||
missing = [name for name in required if not (config or {}).get(name)]
|
||||
if missing:
|
||||
return False, f"lookup 缺少 config.{missing[0]}"
|
||||
if field_type == "filterUp":
|
||||
required = ("targetSheet", "filters", "valuesField", "aggregator")
|
||||
missing = [name for name in required if not (config or {}).get(name)]
|
||||
if missing:
|
||||
return False, f"filterUp 缺少 config.{missing[0]}"
|
||||
if not isinstance((config or {}).get("filters"), list):
|
||||
return False, "filterUp config.filters 必须是数组"
|
||||
return True, ""
|
||||
|
||||
|
||||
def build_fields_json(fields: List[Dict[str, Any]]) -> str:
|
||||
"""构建 --fields 参数的 JSON 字符串。"""
|
||||
payload_fields = []
|
||||
def build_fields_payload(fields: List[Dict[str, Any]]) -> List[Dict[str, Any]]:
|
||||
payload: List[Dict[str, Any]] = []
|
||||
for field in fields:
|
||||
normalized = normalize_field_config(field)
|
||||
item: Dict[str, Any] = {
|
||||
'fieldName': normalized['fieldName'].strip(),
|
||||
'type': normalized.get('type', 'text'),
|
||||
"fieldName": normalized["fieldName"].strip(),
|
||||
"type": normalized.get("type", "text"),
|
||||
}
|
||||
if 'config' in normalized and normalized['config'] is not None:
|
||||
item['config'] = normalized['config']
|
||||
payload_fields.append(item)
|
||||
return json.dumps(payload_fields, ensure_ascii=False)
|
||||
for key in ("config", "aiConfig"):
|
||||
if normalized.get(key) is not None:
|
||||
item[key] = normalized[key]
|
||||
payload.append(item)
|
||||
return payload
|
||||
|
||||
|
||||
def run_dws(args: List[str]) -> Optional[Dict[str, Any]]:
|
||||
if not args:
|
||||
print('错误:空命令')
|
||||
return None
|
||||
|
||||
cmd = ['dws'] + args
|
||||
def run_dws(dws_bin: str, args: List[str], timeout_sec: int = 120) -> Tuple[Optional[Dict[str, Any]], str]:
|
||||
try:
|
||||
result = subprocess.run(
|
||||
cmd, capture_output=True, text=True, timeout=60
|
||||
[dws_bin] + args,
|
||||
capture_output=True,
|
||||
text=True,
|
||||
timeout=timeout_sec,
|
||||
)
|
||||
if result.returncode != 0:
|
||||
print(f"错误:{result.stderr.strip()}")
|
||||
return None
|
||||
try:
|
||||
return json.loads(result.stdout)
|
||||
except json.JSONDecodeError as e:
|
||||
print(f"无法解析响应:{result.stdout[:200]}...")
|
||||
print(f"JSON 解析错误:{e}")
|
||||
return None
|
||||
except subprocess.TimeoutExpired:
|
||||
print('错误:命令执行超时(60 秒)')
|
||||
return None
|
||||
return None, f"dws 命令超时({timeout_sec} 秒)"
|
||||
except FileNotFoundError:
|
||||
print('错误:未找到 dws 命令,请确认已安装')
|
||||
return None
|
||||
return None, f"未找到 dws 命令:{dws_bin}"
|
||||
if result.returncode != 0:
|
||||
return None, (result.stderr or result.stdout).strip() or f"dws 退出码 {result.returncode}"
|
||||
try:
|
||||
payload = json.loads(result.stdout)
|
||||
except json.JSONDecodeError as exc:
|
||||
return None, f"dws 返回非 JSON:{exc}"
|
||||
if not isinstance(payload, dict):
|
||||
return None, "dws 返回的 JSON 不是对象"
|
||||
if payload.get("status") != "success":
|
||||
detail = payload.get("summary") or payload.get("error") or payload
|
||||
return None, f"业务失败:{detail}"
|
||||
return payload, ""
|
||||
|
||||
|
||||
def normalize_result_item(index: int, item: Any) -> Dict[str, Any]:
|
||||
if not isinstance(item, dict):
|
||||
return {"index": index, "status": "failed", "error": "逐项结果不是对象"}
|
||||
field_id = item.get("fieldId") or (item.get("data") or {}).get("fieldId")
|
||||
succeeded = item.get("success") is True or item.get("status") == "success"
|
||||
if "success" not in item and "status" not in item:
|
||||
succeeded = bool(field_id)
|
||||
result: Dict[str, Any] = {
|
||||
"index": index,
|
||||
"status": "success" if succeeded and field_id else "failed",
|
||||
}
|
||||
if field_id:
|
||||
result["fieldId"] = str(field_id)
|
||||
if result["status"] != "success":
|
||||
result["error"] = item.get("reason") or item.get("error") or "字段创建未返回 fieldId"
|
||||
return result
|
||||
|
||||
|
||||
def bulk_add_fields(
|
||||
base_id: str, table_id: str, fields_file: str
|
||||
) -> bool:
|
||||
base_id: str,
|
||||
table_id: str,
|
||||
fields: List[Dict[str, Any]],
|
||||
dws_bin: str = "dws",
|
||||
) -> Dict[str, Any]:
|
||||
created, error = run_dws(
|
||||
dws_bin,
|
||||
[
|
||||
"aitable", "field", "create",
|
||||
"--base-id", base_id,
|
||||
"--table-id", table_id,
|
||||
"--fields", json.dumps(build_fields_payload(fields), ensure_ascii=False),
|
||||
"--format", "json",
|
||||
],
|
||||
)
|
||||
if created is None:
|
||||
return {
|
||||
"status": "failed",
|
||||
"complete": False,
|
||||
"requestedCount": len(fields),
|
||||
"verifiedCount": 0,
|
||||
"ledger": [{"status": "failed", "error": error}],
|
||||
}
|
||||
|
||||
data = created.get("data") if isinstance(created.get("data"), dict) else {}
|
||||
raw_results = data.get("results")
|
||||
if not isinstance(raw_results, list) or len(raw_results) != len(fields):
|
||||
return {
|
||||
"status": "failed",
|
||||
"complete": False,
|
||||
"requestedCount": len(fields),
|
||||
"verifiedCount": 0,
|
||||
"ledger": [{"status": "failed", "error": "响应缺少与输入数量一致的 data.results[]"}],
|
||||
}
|
||||
ledger = [normalize_result_item(index, item) for index, item in enumerate(raw_results)]
|
||||
field_ids = [item["fieldId"] for item in ledger if item["status"] == "success"]
|
||||
verified_ids: List[str] = []
|
||||
if field_ids:
|
||||
queried, query_error = run_dws(
|
||||
dws_bin,
|
||||
[
|
||||
"aitable", "field", "get",
|
||||
"--base-id", base_id,
|
||||
"--table-id", table_id,
|
||||
"--field-ids", ",".join(field_ids),
|
||||
"--format", "json",
|
||||
],
|
||||
)
|
||||
if queried is None:
|
||||
for item in ledger:
|
||||
if item["status"] == "success":
|
||||
item["status"] = "verify_failed"
|
||||
item["error"] = query_error
|
||||
else:
|
||||
query_data = queried.get("data") if isinstance(queried.get("data"), dict) else {}
|
||||
raw_fields = query_data.get("fields") or query_data.get("items") or []
|
||||
found = {
|
||||
str(item.get("fieldId"))
|
||||
for item in raw_fields
|
||||
if isinstance(item, dict) and item.get("fieldId")
|
||||
}
|
||||
verified_ids = [field_id for field_id in field_ids if field_id in found]
|
||||
for item in ledger:
|
||||
if item.get("fieldId") in field_ids and item.get("fieldId") not in found:
|
||||
item["status"] = "verify_failed"
|
||||
item["error"] = "回读未返回该字段"
|
||||
complete = len(verified_ids) == len(fields) and all(item["status"] == "success" for item in ledger)
|
||||
return {
|
||||
"status": "success" if complete else "partial",
|
||||
"complete": complete,
|
||||
"requestedCount": len(fields),
|
||||
"verifiedCount": len(verified_ids),
|
||||
"fieldIds": verified_ids,
|
||||
"ledger": ledger,
|
||||
}
|
||||
|
||||
|
||||
def main() -> None:
|
||||
parser = argparse.ArgumentParser(description=__doc__)
|
||||
parser.add_argument("base_id")
|
||||
parser.add_argument("table_id")
|
||||
parser.add_argument("fields_file")
|
||||
parser.add_argument("--dws", default="dws", help="dws 可执行文件路径")
|
||||
args = parser.parse_args()
|
||||
if not validate_resource_id(args.base_id):
|
||||
parser.error("无效的 baseId 格式")
|
||||
if not validate_resource_id(args.table_id):
|
||||
parser.error("无效的 tableId 格式")
|
||||
try:
|
||||
safe_path = resolve_safe_path(fields_file)
|
||||
except ValueError as e:
|
||||
print(f"路径验证失败:{e}")
|
||||
return False
|
||||
|
||||
if not validate_file_extension(fields_file, ALLOWED_FILE_EXTENSIONS):
|
||||
print(f"错误:只允许 {', '.join(ALLOWED_FILE_EXTENSIONS)} 文件")
|
||||
return False
|
||||
if not safe_path.exists():
|
||||
print(f"错误:文件不存在:{safe_path}")
|
||||
return False
|
||||
|
||||
try:
|
||||
fields = safe_json_load(safe_path)
|
||||
except ValueError as e:
|
||||
print(f"错误:{e}")
|
||||
return False
|
||||
except json.JSONDecodeError as e:
|
||||
print(f"错误:JSON 格式无效:{e}")
|
||||
return False
|
||||
|
||||
if not isinstance(fields, list) or not fields:
|
||||
print('错误:fields.json 必须是非空 JSON 数组')
|
||||
return False
|
||||
if len(fields) > 15:
|
||||
print('错误:单次最多创建 15 个字段,请拆分后重试')
|
||||
return False
|
||||
|
||||
for i, field in enumerate(fields):
|
||||
valid, error = validate_field_config(field)
|
||||
if not valid:
|
||||
print(f"错误:字段 #{i+1} 配置无效:{error}")
|
||||
return False
|
||||
|
||||
fields_json = build_fields_json(fields)
|
||||
result = run_dws([
|
||||
'aitable', 'field', 'create',
|
||||
'--base-id', base_id,
|
||||
'--table-id', table_id,
|
||||
'--fields', fields_json,
|
||||
'--format', 'json',
|
||||
])
|
||||
|
||||
if not result:
|
||||
return False
|
||||
|
||||
path = resolve_safe_path(args.fields_file)
|
||||
if path.suffix.lower() != ".json" or not path.exists() or not path.is_file():
|
||||
raise ValueError("fields_file 必须是工作区内存在的 .json 文件")
|
||||
fields = safe_json_load(path)
|
||||
if not isinstance(fields, list) or not fields:
|
||||
raise ValueError("fields.json 必须是非空 JSON 数组")
|
||||
if len(fields) > 15:
|
||||
raise ValueError("单次最多创建 15 个字段,请拆分后重试")
|
||||
for index, field in enumerate(fields, start=1):
|
||||
valid, error = validate_field_config(field)
|
||||
if not valid:
|
||||
raise ValueError(f"字段 #{index} 配置无效:{error}")
|
||||
result = bulk_add_fields(args.base_id, args.table_id, fields, args.dws)
|
||||
except (ValueError, OSError, json.JSONDecodeError) as exc:
|
||||
print(f"错误:{exc}", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
print(json.dumps(result, ensure_ascii=False, indent=2))
|
||||
return True
|
||||
sys.exit(0 if result["complete"] else 2)
|
||||
|
||||
|
||||
def main():
|
||||
if len(sys.argv) != 4:
|
||||
print(__doc__)
|
||||
print('用法示例:')
|
||||
print(' python bulk_add_fields.py basexxx tablexxx fields.json')
|
||||
sys.exit(1)
|
||||
|
||||
base_id = sys.argv[1]
|
||||
table_id = sys.argv[2]
|
||||
fields_file = sys.argv[3]
|
||||
|
||||
if not validate_resource_id(base_id):
|
||||
print('错误:无效的 baseId 格式')
|
||||
sys.exit(1)
|
||||
if not validate_resource_id(table_id):
|
||||
print('错误:无效的 tableId 格式')
|
||||
sys.exit(1)
|
||||
|
||||
success = bulk_add_fields(base_id, table_id, fields_file)
|
||||
sys.exit(0 if success else 1)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
|
||||
@@ -1,333 +1,264 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
从 CSV / JSON 批量导入记录到钉钉 AI 表格(新版 schema)
|
||||
"""批量追加 CSV / JSON 记录到已有钉钉 AI 表格数据表。
|
||||
|
||||
用法:
|
||||
python import_records.py <baseId> <tableId> data.csv [batch_size]
|
||||
python import_records.py <baseId> <tableId> data.json [batch_size]
|
||||
python3 import_records.py <baseId> <tableId> data.csv [batch_size]
|
||||
python3 import_records.py <baseId> <tableId> data.json [batch_size]
|
||||
|
||||
说明:
|
||||
- CSV 表头默认视为 fieldId
|
||||
- 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 写入前给字段值前缀加引号 / 改为非纯数字。
|
||||
CSV 表头必须是 fieldId。CSV 值保持字符串;需要布尔、数组、对象等精确类型时使用
|
||||
JSON。JSON 支持 [{"cells": {...}}] 或 [{"fld...": value}] 两种格式。
|
||||
脚本逐批检查业务状态、提取 newRecordIds 并回读验证;部分成功会保留 ledger,
|
||||
但整体以非零状态结束。
|
||||
"""
|
||||
|
||||
import sys
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import csv
|
||||
import json
|
||||
import subprocess
|
||||
import os
|
||||
import re
|
||||
import subprocess
|
||||
import sys
|
||||
from pathlib import Path
|
||||
from typing import Union, List, Dict, Any, Optional, Tuple
|
||||
from typing import Any, Dict, List, Optional, Tuple, Union
|
||||
|
||||
JsonData = Union[List[Any], Dict[str, Any]]
|
||||
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_-]{8,128}$')
|
||||
RESOURCE_ID_PATTERN = re.compile(r"^[A-Za-z0-9_-]{8,128}$")
|
||||
MAX_RECORDS_PER_BATCH = 100
|
||||
DEFAULT_BATCH_SIZE = 50
|
||||
|
||||
|
||||
def resolve_safe_path(
|
||||
path: str, allowed_root: Optional[str] = None
|
||||
) -> Path:
|
||||
if allowed_root is None:
|
||||
allowed_root = os.environ.get('OPENCLAW_WORKSPACE', os.getcwd())
|
||||
allowed_root = Path(allowed_root).resolve()
|
||||
target_path = (
|
||||
Path(path).resolve()
|
||||
if Path(path).is_absolute()
|
||||
else (Path.cwd() / path).resolve()
|
||||
)
|
||||
def resolve_safe_path(path: str, allowed_root: Optional[str] = None) -> Path:
|
||||
root = Path(allowed_root or os.environ.get("OPENCLAW_WORKSPACE", os.getcwd())).resolve()
|
||||
candidate = Path(path)
|
||||
target = candidate.resolve() if candidate.is_absolute() else (Path.cwd() / candidate).resolve()
|
||||
try:
|
||||
target_path.relative_to(allowed_root)
|
||||
return target_path
|
||||
except ValueError:
|
||||
raise ValueError(
|
||||
f"路径超出允许范围:{path}\n"
|
||||
f"目标路径:{target_path}\n"
|
||||
f"允许根目录:{allowed_root}\n"
|
||||
f"提示:设置 OPENCLAW_WORKSPACE 环境变量或确保文件在工作目录内"
|
||||
)
|
||||
target.relative_to(root)
|
||||
except ValueError as exc:
|
||||
raise ValueError(f"路径超出允许范围:{path}(允许根目录:{root})") from exc
|
||||
return target
|
||||
|
||||
|
||||
def validate_resource_id(resource_id: str) -> bool:
|
||||
return bool(
|
||||
resource_id and RESOURCE_ID_PATTERN.match(resource_id.strip())
|
||||
)
|
||||
return bool(resource_id and RESOURCE_ID_PATTERN.fullmatch(resource_id.strip()))
|
||||
|
||||
|
||||
def validate_file_extension(
|
||||
filename: str, allowed_extensions: list
|
||||
) -> bool:
|
||||
return any(filename.lower().endswith(ext) for ext in allowed_extensions)
|
||||
def safe_csv_load(file_path: Path) -> List[Dict[str, str]]:
|
||||
if file_path.stat().st_size > MAX_FILE_SIZE:
|
||||
raise ValueError(f"文件过大(限制 {MAX_FILE_SIZE:,} 字节)")
|
||||
with file_path.open("r", encoding="utf-8-sig", newline="") as stream:
|
||||
reader = csv.DictReader(stream)
|
||||
if not reader.fieldnames or any(not str(name).strip() for name in reader.fieldnames):
|
||||
raise ValueError("CSV 必须包含非空 fieldId 表头")
|
||||
return list(reader)
|
||||
|
||||
|
||||
def safe_csv_load(
|
||||
file_path: Path, max_size: int = MAX_FILE_SIZE
|
||||
) -> List[RecordDict]:
|
||||
file_size = file_path.stat().st_size
|
||||
if file_size > max_size:
|
||||
raise ValueError(
|
||||
f"文件过大:{file_size:,} 字节 (限制:{max_size:,} 字节)"
|
||||
)
|
||||
with open(file_path, 'r', encoding='utf-8', newline='') as f:
|
||||
return list(csv.DictReader(f))
|
||||
|
||||
|
||||
def safe_json_load(
|
||||
file_path: Path, max_size: int = MAX_FILE_SIZE
|
||||
) -> JsonData:
|
||||
file_size = file_path.stat().st_size
|
||||
if file_size > max_size:
|
||||
raise ValueError(
|
||||
f"文件过大:{file_size:,} 字节 (限制:{max_size:,} 字节)"
|
||||
)
|
||||
with open(file_path, 'r', encoding='utf-8') as f:
|
||||
return json.load(f)
|
||||
|
||||
|
||||
def sanitize_record_value(
|
||||
value: Any,
|
||||
) -> Optional[Union[str, int, float, bool, list, dict]]:
|
||||
if value is None:
|
||||
return None
|
||||
if isinstance(value, (bool, int, float, list, dict)):
|
||||
return value
|
||||
if not isinstance(value, str):
|
||||
return value
|
||||
if not value.strip():
|
||||
return None
|
||||
|
||||
value = value.strip()
|
||||
if value.lower() == 'true':
|
||||
return True
|
||||
if value.lower() == 'false':
|
||||
return False
|
||||
|
||||
try:
|
||||
if '.' in value:
|
||||
return float(value)
|
||||
return int(value)
|
||||
except ValueError:
|
||||
return value
|
||||
def safe_json_load(file_path: Path) -> JsonData:
|
||||
if file_path.stat().st_size > MAX_FILE_SIZE:
|
||||
raise ValueError(f"文件过大(限制 {MAX_FILE_SIZE:,} 字节)")
|
||||
with file_path.open("r", encoding="utf-8") as stream:
|
||||
return json.load(stream)
|
||||
|
||||
|
||||
def normalize_record(record: Dict[str, Any]) -> Dict[str, Any]:
|
||||
if 'cells' in record and isinstance(record['cells'], dict):
|
||||
cells = record['cells']
|
||||
else:
|
||||
cells = record
|
||||
normalized = {}
|
||||
cells = record.get("cells") if isinstance(record.get("cells"), dict) else record
|
||||
normalized: Dict[str, Any] = {}
|
||||
for key, value in cells.items():
|
||||
sanitized = sanitize_record_value(value)
|
||||
if sanitized is not None:
|
||||
normalized[key] = sanitized
|
||||
return {'cells': normalized}
|
||||
if not isinstance(key, str) or not key.strip():
|
||||
continue
|
||||
if value is None:
|
||||
continue
|
||||
if isinstance(value, str):
|
||||
value = value.strip()
|
||||
if not value:
|
||||
continue
|
||||
normalized[key.strip()] = value
|
||||
return {"cells": normalized}
|
||||
|
||||
|
||||
def validate_record(record: Dict[str, Any]) -> Tuple[bool, str]:
|
||||
def validate_record(record: Any) -> Tuple[bool, str]:
|
||||
if not isinstance(record, dict):
|
||||
return False, '记录必须是对象'
|
||||
normalized = normalize_record(record)
|
||||
cells = normalized.get('cells', {})
|
||||
if not cells or not isinstance(cells, dict):
|
||||
return False, '记录必须包含非空 cells 对象'
|
||||
return True, ''
|
||||
return False, "记录必须是对象"
|
||||
cells = normalize_record(record).get("cells")
|
||||
if not isinstance(cells, dict) or not cells:
|
||||
return False, "记录必须包含非空 cells 对象"
|
||||
return True, ""
|
||||
|
||||
|
||||
def run_dws(args: List[str]) -> Optional[Dict[str, Any]]:
|
||||
if not args:
|
||||
print('错误:空命令')
|
||||
return None
|
||||
cmd = ['dws'] + args
|
||||
def run_dws(dws_bin: str, args: List[str], timeout_sec: int = 120) -> Tuple[Optional[Dict[str, Any]], str]:
|
||||
try:
|
||||
result = subprocess.run(
|
||||
cmd, capture_output=True, text=True, timeout=120
|
||||
[dws_bin] + args,
|
||||
capture_output=True,
|
||||
text=True,
|
||||
timeout=timeout_sec,
|
||||
)
|
||||
if result.returncode != 0:
|
||||
print(f"错误:{result.stderr.strip()}")
|
||||
return None
|
||||
try:
|
||||
return json.loads(result.stdout)
|
||||
except json.JSONDecodeError as e:
|
||||
print(f"无法解析响应:{result.stdout[:200]}...")
|
||||
print(f"JSON 解析错误:{e}")
|
||||
return None
|
||||
except subprocess.TimeoutExpired:
|
||||
print('错误:命令执行超时(120 秒)')
|
||||
return None
|
||||
return None, f"dws 命令超时({timeout_sec} 秒)"
|
||||
except FileNotFoundError:
|
||||
print('错误:未找到 dws 命令,请确认已安装')
|
||||
return None
|
||||
|
||||
|
||||
def import_from_csv(
|
||||
base_id: str, table_id: str, csv_file: str,
|
||||
batch_size: int = DEFAULT_BATCH_SIZE,
|
||||
) -> bool:
|
||||
return None, f"未找到 dws 命令:{dws_bin}"
|
||||
if result.returncode != 0:
|
||||
return None, (result.stderr or result.stdout).strip() or f"dws 退出码 {result.returncode}"
|
||||
try:
|
||||
safe_path = resolve_safe_path(csv_file)
|
||||
except ValueError as e:
|
||||
print(f"路径验证失败:{e}")
|
||||
return False
|
||||
payload = json.loads(result.stdout)
|
||||
except json.JSONDecodeError as exc:
|
||||
return None, f"dws 返回非 JSON:{exc}"
|
||||
if not isinstance(payload, dict):
|
||||
return None, "dws 返回的 JSON 不是对象"
|
||||
if payload.get("status") != "success":
|
||||
detail = payload.get("summary") or payload.get("error") or payload
|
||||
return None, f"业务失败:{detail}"
|
||||
return payload, ""
|
||||
|
||||
if not validate_file_extension(csv_file, ALLOWED_CSV_EXTENSIONS):
|
||||
print(f"错误:只允许 {', '.join(ALLOWED_CSV_EXTENSIONS)} 文件")
|
||||
return False
|
||||
if not safe_path.exists():
|
||||
print(f"错误:文件不存在:{safe_path}")
|
||||
return False
|
||||
|
||||
try:
|
||||
rows = safe_csv_load(safe_path)
|
||||
except ValueError as e:
|
||||
print(f"错误:{e}")
|
||||
return False
|
||||
except csv.Error as e:
|
||||
print(f"错误:CSV 格式无效:{e}")
|
||||
return False
|
||||
|
||||
if not rows:
|
||||
print('错误:CSV 文件为空或没有有效数据行')
|
||||
return False
|
||||
|
||||
records = [
|
||||
normalize_record(row)
|
||||
for row in rows
|
||||
if normalize_record(row)['cells']
|
||||
def extract_query_record_ids(payload: Dict[str, Any]) -> List[str]:
|
||||
data = payload.get("data") if isinstance(payload.get("data"), dict) else {}
|
||||
records = data.get("records") or data.get("items") or []
|
||||
if not isinstance(records, list):
|
||||
return []
|
||||
return [
|
||||
str(item.get("recordId"))
|
||||
for item in records
|
||||
if isinstance(item, dict) and item.get("recordId")
|
||||
]
|
||||
return import_records(base_id, table_id, records, batch_size)
|
||||
|
||||
|
||||
def import_from_json(
|
||||
base_id: str, table_id: str, json_file: str,
|
||||
batch_size: int = DEFAULT_BATCH_SIZE,
|
||||
) -> bool:
|
||||
try:
|
||||
safe_path = resolve_safe_path(json_file)
|
||||
except ValueError as e:
|
||||
print(f"路径验证失败:{e}")
|
||||
return False
|
||||
|
||||
if not validate_file_extension(json_file, ALLOWED_JSON_EXTENSIONS):
|
||||
print(f"错误:只允许 {', '.join(ALLOWED_JSON_EXTENSIONS)} 文件")
|
||||
return False
|
||||
if not safe_path.exists():
|
||||
print(f"错误:文件不存在:{safe_path}")
|
||||
return False
|
||||
|
||||
try:
|
||||
records = safe_json_load(safe_path)
|
||||
except ValueError as e:
|
||||
print(f"错误:{e}")
|
||||
return False
|
||||
except json.JSONDecodeError as e:
|
||||
print(f"错误:JSON 格式无效:{e}")
|
||||
return False
|
||||
|
||||
if not isinstance(records, list) or not records:
|
||||
print('错误:JSON 文件必须是非空数组')
|
||||
return False
|
||||
|
||||
for i, record in enumerate(records):
|
||||
valid, error = validate_record(record)
|
||||
if not valid:
|
||||
print(f"错误:记录 #{i+1} 格式无效:{error}")
|
||||
return False
|
||||
|
||||
return import_records(
|
||||
base_id, table_id,
|
||||
[normalize_record(r) for r in records], batch_size,
|
||||
)
|
||||
|
||||
|
||||
def import_records(
|
||||
base_id: str, table_id: str,
|
||||
records: List[Dict[str, Any]], batch_size: int,
|
||||
) -> bool:
|
||||
if batch_size <= 0:
|
||||
print('错误:batch_size 必须大于 0')
|
||||
return False
|
||||
if batch_size > MAX_RECORDS_PER_BATCH:
|
||||
batch_size = MAX_RECORDS_PER_BATCH
|
||||
base_id: str,
|
||||
table_id: str,
|
||||
records: List[Dict[str, Any]],
|
||||
batch_size: int,
|
||||
dws_bin: str = "dws",
|
||||
) -> Dict[str, Any]:
|
||||
if batch_size <= 0 or batch_size > MAX_RECORDS_PER_BATCH:
|
||||
raise ValueError(f"batch_size 必须在 1..{MAX_RECORDS_PER_BATCH} 之间")
|
||||
|
||||
ledger: List[Dict[str, Any]] = []
|
||||
verified_ids: List[str] = []
|
||||
total_batches = (len(records) + batch_size - 1) // batch_size
|
||||
success = True
|
||||
|
||||
for i in range(0, len(records), batch_size):
|
||||
batch = records[i:i + batch_size]
|
||||
batch_num = (i // batch_size) + 1
|
||||
records_json = json.dumps(batch, ensure_ascii=False)
|
||||
result = run_dws([
|
||||
'aitable', 'record', 'create',
|
||||
'--base-id', base_id,
|
||||
'--table-id', table_id,
|
||||
'--records', records_json,
|
||||
'--format', 'json',
|
||||
])
|
||||
if result:
|
||||
print(
|
||||
f"[{batch_num}/{total_batches}] "
|
||||
f"✓ 已提交 {len(batch)} 条记录"
|
||||
)
|
||||
else:
|
||||
print(f"[{batch_num}/{total_batches}] ✗ 导入失败")
|
||||
success = False
|
||||
|
||||
return success
|
||||
|
||||
|
||||
def main():
|
||||
if len(sys.argv) < 4 or len(sys.argv) > 5:
|
||||
print(__doc__)
|
||||
print('用法示例:')
|
||||
print(
|
||||
' python import_records.py basexxx tablexxx data.csv 50'
|
||||
for start in range(0, len(records), batch_size):
|
||||
batch = records[start : start + batch_size]
|
||||
batch_number = start // batch_size + 1
|
||||
print(f"[{batch_number}/{total_batches}] 创建 {len(batch)} 条记录", file=sys.stderr)
|
||||
created, error = run_dws(
|
||||
dws_bin,
|
||||
[
|
||||
"aitable", "record", "create",
|
||||
"--base-id", base_id,
|
||||
"--table-id", table_id,
|
||||
"--records", json.dumps(batch, ensure_ascii=False),
|
||||
"--format", "json",
|
||||
],
|
||||
)
|
||||
sys.exit(1)
|
||||
entry: Dict[str, Any] = {
|
||||
"batch": batch_number,
|
||||
"inputCount": len(batch),
|
||||
"status": "failed",
|
||||
"recordIds": [],
|
||||
}
|
||||
if created is None:
|
||||
entry["error"] = error
|
||||
ledger.append(entry)
|
||||
continue
|
||||
|
||||
base_id = sys.argv[1]
|
||||
table_id = sys.argv[2]
|
||||
input_file = sys.argv[3]
|
||||
batch_size = (
|
||||
int(sys.argv[4]) if len(sys.argv) == 5
|
||||
else DEFAULT_BATCH_SIZE
|
||||
)
|
||||
data = created.get("data") if isinstance(created.get("data"), dict) else {}
|
||||
record_ids = data.get("newRecordIds")
|
||||
if not isinstance(record_ids, list) or len(record_ids) != len(batch) or not all(record_ids):
|
||||
entry["error"] = "创建响应缺少与输入数量一致的 data.newRecordIds[]"
|
||||
ledger.append(entry)
|
||||
continue
|
||||
record_ids = [str(value) for value in record_ids]
|
||||
entry["recordIds"] = record_ids
|
||||
|
||||
if not validate_resource_id(base_id):
|
||||
print('错误:无效的 baseId 格式')
|
||||
sys.exit(1)
|
||||
if not validate_resource_id(table_id):
|
||||
print('错误:无效的 tableId 格式')
|
||||
sys.exit(1)
|
||||
|
||||
if input_file.lower().endswith('.csv'):
|
||||
success = import_from_csv(
|
||||
base_id, table_id, input_file, batch_size
|
||||
)
|
||||
elif input_file.lower().endswith('.json'):
|
||||
success = import_from_json(
|
||||
base_id, table_id, input_file, batch_size
|
||||
queried, query_error = run_dws(
|
||||
dws_bin,
|
||||
[
|
||||
"aitable", "record", "query",
|
||||
"--base-id", base_id,
|
||||
"--table-id", table_id,
|
||||
"--record-ids", ",".join(record_ids),
|
||||
"--format", "json",
|
||||
],
|
||||
)
|
||||
if queried is None:
|
||||
entry["status"] = "verify_failed"
|
||||
entry["error"] = query_error
|
||||
ledger.append(entry)
|
||||
continue
|
||||
found = set(extract_query_record_ids(queried))
|
||||
missing = [record_id for record_id in record_ids if record_id not in found]
|
||||
if missing:
|
||||
entry["status"] = "verify_failed"
|
||||
entry["missingRecordIds"] = missing
|
||||
entry["error"] = "回读未返回全部新记录"
|
||||
ledger.append(entry)
|
||||
continue
|
||||
entry["status"] = "success"
|
||||
verified_ids.extend(record_ids)
|
||||
ledger.append(entry)
|
||||
|
||||
complete = all(item["status"] == "success" for item in ledger)
|
||||
return {
|
||||
"status": "success" if complete else "partial",
|
||||
"complete": complete,
|
||||
"requestedCount": len(records),
|
||||
"verifiedCount": len(verified_ids),
|
||||
"recordIds": verified_ids,
|
||||
"ledger": ledger,
|
||||
}
|
||||
|
||||
|
||||
def load_records(input_file: str) -> List[Dict[str, Any]]:
|
||||
path = resolve_safe_path(input_file)
|
||||
if not path.exists() or not path.is_file():
|
||||
raise ValueError(f"文件不存在或不可读:{path}")
|
||||
suffix = path.suffix.lower()
|
||||
if suffix == ".csv":
|
||||
raw: Any = safe_csv_load(path)
|
||||
elif suffix == ".json":
|
||||
raw = safe_json_load(path)
|
||||
else:
|
||||
print('错误:仅支持 .csv 或 .json 文件')
|
||||
raise ValueError("仅支持 .csv 或 .json 文件")
|
||||
if not isinstance(raw, list) or not raw:
|
||||
raise ValueError("输入文件必须包含至少一条记录")
|
||||
normalized: List[Dict[str, Any]] = []
|
||||
for index, record in enumerate(raw, start=1):
|
||||
valid, error = validate_record(record)
|
||||
if not valid:
|
||||
raise ValueError(f"记录 #{index} 格式无效:{error}")
|
||||
normalized.append(normalize_record(record))
|
||||
return normalized
|
||||
|
||||
|
||||
def main() -> None:
|
||||
parser = argparse.ArgumentParser(description=__doc__)
|
||||
parser.add_argument("base_id")
|
||||
parser.add_argument("table_id")
|
||||
parser.add_argument("input_file")
|
||||
parser.add_argument("batch_size", nargs="?", type=int, default=DEFAULT_BATCH_SIZE)
|
||||
parser.add_argument("--dws", default="dws", help="dws 可执行文件路径")
|
||||
args = parser.parse_args()
|
||||
if not validate_resource_id(args.base_id):
|
||||
parser.error("无效的 baseId 格式")
|
||||
if not validate_resource_id(args.table_id):
|
||||
parser.error("无效的 tableId 格式")
|
||||
try:
|
||||
records = load_records(args.input_file)
|
||||
result = import_records(
|
||||
args.base_id,
|
||||
args.table_id,
|
||||
records,
|
||||
args.batch_size,
|
||||
args.dws,
|
||||
)
|
||||
except (ValueError, OSError, csv.Error, json.JSONDecodeError) as exc:
|
||||
print(f"错误:{exc}", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
|
||||
sys.exit(0 if success else 1)
|
||||
print(json.dumps(result, ensure_ascii=False, indent=2))
|
||||
sys.exit(0 if result["complete"] else 2)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
|
||||
@@ -18,6 +18,7 @@
|
||||
--records '[{"cells":{"fldAttachId":[{"fileToken":"ft_xxx"}]}}]' --format json
|
||||
"""
|
||||
|
||||
import argparse
|
||||
import sys
|
||||
import json
|
||||
import subprocess
|
||||
@@ -28,6 +29,7 @@ from pathlib import Path
|
||||
from typing import Optional, Dict, Any
|
||||
from urllib.request import Request, urlopen
|
||||
from urllib.error import HTTPError, URLError
|
||||
from urllib.parse import urlparse
|
||||
|
||||
RESOURCE_ID_PATTERN = re.compile(r'^[A-Za-z0-9_-]{8,128}$')
|
||||
MAX_FILE_SIZE = 100 * 1024 * 1024 # 100MB
|
||||
@@ -43,16 +45,24 @@ def detect_mime_type(file_path: Path) -> str:
|
||||
return mime_type or 'application/octet-stream'
|
||||
|
||||
|
||||
def run_dws(args: list) -> Optional[Dict[str, Any]]:
|
||||
def run_dws(args: list, dws_bin: str = 'dws') -> Optional[Dict[str, Any]]:
|
||||
"""调用 dws 命令并返回解析后的 JSON 结果。"""
|
||||
cmd = ['dws'] + args
|
||||
cmd = [dws_bin] + args
|
||||
try:
|
||||
result = subprocess.run(cmd, capture_output=True, text=True, timeout=60)
|
||||
if result.returncode != 0:
|
||||
print(f"错误:dws 命令失败: {result.stderr.strip()}", file=sys.stderr)
|
||||
return None
|
||||
try:
|
||||
return json.loads(result.stdout)
|
||||
payload = json.loads(result.stdout)
|
||||
if not isinstance(payload, dict):
|
||||
print('错误:dws 响应 JSON 不是对象', file=sys.stderr)
|
||||
return None
|
||||
if payload.get('status') != 'success':
|
||||
detail = payload.get('summary') or payload.get('error') or payload
|
||||
print(f"错误:dws 业务失败: {detail}", file=sys.stderr)
|
||||
return None
|
||||
return payload
|
||||
except json.JSONDecodeError:
|
||||
print(f"错误:无法解析 dws 响应: {result.stdout[:300]}", file=sys.stderr)
|
||||
return None
|
||||
@@ -66,6 +76,10 @@ def run_dws(args: list) -> Optional[Dict[str, Any]]:
|
||||
|
||||
def upload_to_oss(upload_url: str, file_path: Path, mime_type: str) -> bool:
|
||||
"""通过 HTTP PUT 上传文件到 OSS。"""
|
||||
parsed = urlparse(upload_url)
|
||||
if parsed.scheme != 'https' or not parsed.hostname:
|
||||
print('错误:uploadUrl 必须是有效的 HTTPS URL', file=sys.stderr)
|
||||
return False
|
||||
file_data = file_path.read_bytes()
|
||||
req = Request(upload_url, data=file_data, method='PUT')
|
||||
req.add_header('Content-Type', mime_type)
|
||||
@@ -84,7 +98,9 @@ def upload_to_oss(upload_url: str, file_path: Path, mime_type: str) -> bool:
|
||||
return False
|
||||
|
||||
|
||||
def upload_attachment(base_id: str, file_path_str: str) -> Optional[Dict[str, Any]]:
|
||||
def upload_attachment(
|
||||
base_id: str, file_path_str: str, dws_bin: str = 'dws'
|
||||
) -> Optional[Dict[str, Any]]:
|
||||
"""
|
||||
执行完整的附件上传流程:
|
||||
1. prepare_attachment_upload → uploadUrl + fileToken
|
||||
@@ -121,7 +137,7 @@ def upload_attachment(base_id: str, file_path_str: str) -> Optional[Dict[str, An
|
||||
'--mime-type', mime_type,
|
||||
'--format', 'json',
|
||||
]
|
||||
result = run_dws(dws_args)
|
||||
result = run_dws(dws_args, dws_bin)
|
||||
if not result:
|
||||
return None
|
||||
|
||||
@@ -157,27 +173,20 @@ def upload_attachment(base_id: str, file_path_str: str) -> Optional[Dict[str, An
|
||||
|
||||
|
||||
def main():
|
||||
if len(sys.argv) != 3:
|
||||
print(__doc__)
|
||||
print('用法:')
|
||||
print(' python upload_attachment.py <baseId> <filePath>')
|
||||
print()
|
||||
print('示例:')
|
||||
print(' python upload_attachment.py G1DKw2zgV2bEk6PMSBooNxlEVB5r9YAn ./report.pdf')
|
||||
print()
|
||||
print('然后在 record create 中使用返回的 fileToken:')
|
||||
print(' dws aitable record create --base-id <BASE_ID> --table-id <TABLE_ID> \\')
|
||||
print(' --records \'[{"cells":{"fldAttachId":[{"fileToken":"ft_xxx"}]}}]\' --format json')
|
||||
sys.exit(1)
|
||||
parser = argparse.ArgumentParser(description=__doc__)
|
||||
parser.add_argument('base_id')
|
||||
parser.add_argument('file_path')
|
||||
parser.add_argument('--dws', default='dws', help='dws 可执行文件路径')
|
||||
args = parser.parse_args()
|
||||
|
||||
base_id = sys.argv[1]
|
||||
file_path = sys.argv[2]
|
||||
base_id = args.base_id
|
||||
file_path = args.file_path
|
||||
|
||||
if not validate_resource_id(base_id):
|
||||
print('错误:无效的 baseId 格式', file=sys.stderr)
|
||||
sys.exit(1)
|
||||
parser.error('无效的 baseId 格式')
|
||||
|
||||
result = upload_attachment(base_id, file_path)
|
||||
result = upload_attachment(base_id, file_path, args.dws)
|
||||
if result is None:
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
---
|
||||
name: dingtalk-doc
|
||||
description: 钉钉文档(adoc):创建、读取、编辑、块、评论、附件、导出、版本及Markdown/JSONML写入。原生 .md→dingtalk-misc;文件→dingtalk-drive;知识库→dingtalk-wiki;axls→dingtalk-misc,able→dingtalk-aitable。
|
||||
description: 钉钉在线文字文档(adoc):创建、读取、追加、覆盖、块级编辑、评论、附件、导入导出、版本、模板、封面/背景,以及 Markdown/JSONML 保真写入。Use when 用户要写文档、读文档、改正文、处理富文本块或文档评论。原生 .md 文件走 dingtalk-markdown;普通文件存储与上传下载走 dingtalk-drive;知识库空间/节点管理走 dingtalk-wiki;电子表格走 dingtalk-sheet,AI 表格走 dingtalk-aitable。命令前缀:dws doc。
|
||||
metadata:
|
||||
cli_version: ">=0.2.14"
|
||||
category: product
|
||||
@@ -11,152 +11,81 @@ metadata:
|
||||
|
||||
# 钉钉文档 Skill
|
||||
|
||||
## 前置条件 — 执行操作前必读
|
||||
## 执行入口
|
||||
|
||||
> **CRITICAL — 执行任何 `dws` 操作前,MUST 先用 Read 工具完整读取 [`dws-shared`](../dws-shared/SKILL.md)。**该轻量文件包含全局执行契约、安全底线及 shared references 的按需加载导航;不要预加载其全部 references。
|
||||
|
||||
> 命令参考:[doc.md](references/doc.md);剧本:[04-document.md](references/04-document.md)。
|
||||
|
||||
## 参数硬约束
|
||||
|
||||
- 创建文档只用 `--name`,不要写 `--title`。
|
||||
- 目标文件夹只用 `--folder <文档文件夹nodeId或URL>`,不要写 `--parent` / `--parent-node` / `--parent-id`。
|
||||
- 目标知识库只用 `--workspace <workspaceId或URL>`,不要写 `--space-id` / `--spaceId`。
|
||||
- 文档内容只用 `--content` / `--content-file`,不要写 `--markdown`。
|
||||
- 复杂内容(换行、表格、代码块、长 Markdown)先写临时 `.md`,再用 `--content-file`,不要把大段 Markdown 塞进命令行。
|
||||
- 每次 `create` / `update` / `block insert` / `media insert` 后必须 `dws doc read` 或 `dws doc block list` 回读关键内容。
|
||||
执行前完整读取 [`dws-shared`](../dws-shared/SKILL.md)。高频意图用本文件骨架;仅特殊参数、复杂格式或边界不明时读取一个 branch reference。优先级:`骨架/recipe > Shortcut > atomic fallback`。
|
||||
|
||||
<!-- VISIBLE_SHORTCUTS_START -->
|
||||
## Shortcuts(无专用脚本/recipe 时优先)
|
||||
## Shortcut 发现(按需)
|
||||
|
||||
以下 shortcut 同时进入公开 catalog 与 Runtime Schema。先按本 skill 的意图表、脚本和 recipe 路由:存在精确覆盖该场景的专用脚本/recipe 时按其执行;否则用户意图命中时,shortcut 优先于手写原子命令。命令已选中时直接执行;只在参数或安全语义不确定时读取 leaf Schema(例如 `dws schema --cli-path "doc +<shortcut>" --format json`),在当前 Cobra flags 不确定时读取 `dws doc <shortcut> --help`。仅当现有路由和 reference 都无法定位低频能力时,才用 `dws shortcut list --service doc --format json` 批量发现。
|
||||
`doc` 当前有 17 条公开 Shortcut,已全部进入 Runtime Schema。完整清单保留在 Runtime Shortcut Catalog,根 Skill 不重复展开;单条参数与安全契约按需查询 leaf Schema。已知意图直接使用下方的优先路由、意图表或任务 reference;命令已选中时直接执行,只在参数/安全语义不确定时读取 leaf Schema,在当前 Cobra flags 不确定时读取 leaf Help。
|
||||
|
||||
| Shortcut | 风险 | 适用场景 |
|
||||
|---|---|---|
|
||||
| `dws doc +comment-create` | write | 在文档上创建一条评论 |
|
||||
| `dws doc +comment-list` | read | 查询文档评论列表 |
|
||||
| `dws doc +comment-reply` | write | 回复文档中的一条评论 |
|
||||
| `dws doc +copy` | write | 复制文档/文件到指定文件夹或知识库 |
|
||||
| `dws doc +doc-append` | write | 在文档末尾追加一段文本(安全追加,不改动原有内容) |
|
||||
| `dws doc +export-get` | read | 根据 jobId 查询文档导出任务结果 |
|
||||
| `dws doc +export-submit` | read | 提交在线文档导出任务 (docx/markdown/pdf),返回 jobId |
|
||||
| `dws doc +find-doc` | read | 按关键词搜索云文档并投影关键字段(只读) |
|
||||
| `dws doc +list` | read | 列出文件夹或知识库下的直接子节点 |
|
||||
| `dws doc +move` | write | 移动文档/文件到指定文件夹或知识库 |
|
||||
| `dws doc +search` | read | 按关键词搜索有权限的文档 (不传则返回最近访问) |
|
||||
| `dws doc +share-doc` | write | 按姓名把文档链接私信发给某人(自动解析 userId) |
|
||||
| `dws doc +template-list` | read | 获取文档模板列表 |
|
||||
| `dws doc +template-search` | read | 根据关键词搜索文档模板 |
|
||||
| `dws doc +version-list` | read | 查看文档历史版本列表 |
|
||||
| `dws doc +version-revert` | high-risk-write | 回滚文档到指定历史版本 |
|
||||
| `dws doc +version-save` | write | 手动保存文档版本快照 |
|
||||
仅当现有路由和 reference 都无法定位低频能力时,才执行 `dws shortcut list --service doc --compact --format json` 做最后回退;不要为已知高频意图加载完整 Shortcut Catalog 或产品级 Schema。
|
||||
<!-- VISIBLE_SHORTCUTS_END -->
|
||||
|
||||
## 意图表
|
||||
命令和参数清楚时直接执行。只用真实 `cli_path`;`confirmation=user_required` 时先确认再加 `--yes`。普通创建直接调用 `dws doc create`,仅用户要求本地包装器时用 `doc_create_and_write.py`。
|
||||
|
||||
| 用户说 | 命令 |
|
||||
|--------|------|
|
||||
| "创建文档(短内容)" | `dws doc create --name "<标题>" --content "<内容>"` |
|
||||
| "创建+写入(长内容自动分块)" | `python scripts/doc_create_and_write.py --name "<标题>" --content "<内容>" [--mode append\|overwrite]` |
|
||||
| "搜在线文字文档 / 找在线文字文档" | `dws drive search --query "<关键词>" --format json` → `dws drive info --node <nodeId> --format json` → 仅 `extension=adoc` 使用 `dws doc read --node <nodeId> --format json` |
|
||||
| "读在线文字文档(adoc)内容" | `dws doc read --node <nodeId> --format json` |
|
||||
| "更新文档内容 / 分块追加" | `dws doc update --node <nodeId> --content "<分块>" --mode append` |
|
||||
| "删除块" | `dws doc block delete`(需用户确认) |
|
||||
| "导出 docx / markdown / pdf" | `dws doc export --node <nodeId> --export-format <docx|markdown|pdf> --output <path>` |
|
||||
| "导入本地文件为在线文档" | `dws doc import --file <path> --folder <FOLDER_NODE_ID> --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(必遵流程)
|
||||
| 对象 | 核心标识与边界 |
|
||||
|---|---|
|
||||
| 文档 | 使用真实 `nodeId` / `dentryUuid` 或完整 alidocs URL;纯数字 `dentryId`、单独 `dentryKey` 不能替代 |
|
||||
| 目标位置 | `--folder` 只接文档文件夹 nodeId/URL;`--workspace` 只接知识库 ID/URL;不要猜 `--parent*` |
|
||||
| 块 | `blockId` / JSONML `uuid` 必须来自 `block list`,更新节点的 uuid 必须与目标块一致 |
|
||||
| 评论 | `commentKey` 来自评论 list/create;划词评论还需同一块的真实 `start/end` |
|
||||
| 异步任务 | 导出 `jobId` 与导入 `taskId` 只查询对应任务,不能替代 nodeId |
|
||||
| 新建资源续用 | create/mkdir/import/copy 返回的新 `nodeId` / `fileId` 立即绑定后续“这篇/刚才那篇/这个文件夹”;禁止同名搜索改用旧资源 |
|
||||
| 内容格式 | Markdown 适合线性正文;已有富结构优先 JSONML/块级编辑,禁止用 Markdown overwrite 误称保真 |
|
||||
| 普通文件 | adoc 才用 `doc read/export`;`.md`、axls、able 和普通文件按真实 `extension` 切对应 Skill |
|
||||
|
||||
> 命中以下意图**必须**按对应 SOP 顺序执行;**禁止**跳步、替换命令、编造 nodeId/blockId。结构化命令必须带 `--format json`,执行后必须按"验证"步回读真实字段。文件类操作(上传/下载/复制/移动)切 `dingtalk-drive`;知识库节点管理切 `dingtalk-wiki`。
|
||||
## 核心意图与执行骨架
|
||||
|
||||
### SOP-1 查找并读取文档(query-doc)
|
||||
结构化命令加 `--format json`,ID 只取真实输出。写入后回读;未回读不能宣称内容完整。
|
||||
|
||||
**触发**:查文档/读文档/某文档在哪/搜文档内容。
|
||||
### 短链路 Fast Path
|
||||
|
||||
1. **定位(必须)**:用户已提供 URL / `nodeId` 时直接使用原值;未提供目标时才执行 `dws drive search --query "<关键词>" --format json`,再取候选结果的真实 `nodeId`。
|
||||
2. **探测(必须)**:对选中的候选执行 `dws drive info --node <nodeId> --format json`,从真实返回读取 `extension`;不得因为搜索结果标题像“文档”就跳过探测。
|
||||
3. **按类型读取(必须)**:
|
||||
- `extension=adoc`:`dws doc read --node <nodeId> --format json`;大文档只抽取用户需要的章节。
|
||||
- `extension=md`:切到 `dingtalk-markdown` 用 `dws markdown fetch --node <nodeId> --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`。
|
||||
- 不超过 5 个确定性 DWS 操作时,不创建 Todo、不逐步汇报、不预读 Reference;保存真实 ID 连续执行,最终回查后答复。
|
||||
- 按“先/再/然后”切分操作阶段。阶段中的 `insert/插入`、`append/追加/补一段`、`update/改成`、`list/查看块`、`delete/删除` 必须映射为对应真实命令,不能提前折叠进 create。
|
||||
- create 只承载首阶段的初始正文,后续续用其 `nodeId`。当前请求将先创建资源时,禁止预先搜索同名资源解析“这篇/那篇”。
|
||||
|
||||
**禁止**:用户未提供目标时跳过搜索并猜 nodeId、未探测类型就执行 `doc read`、把整篇文档原样贴给用户。
|
||||
| 用户意图 | 精确骨架 | 必须保留的执行边界 |
|
||||
|---|---|---|
|
||||
| 按名称找文档 | `+find-doc --query <关键词>`;需最近访问/扩展名/创建者等过滤用 `+search` | 候选不唯一先消歧;随后 `drive info --node <nodeId>` 判 `extension` |
|
||||
| 读取 adoc | `drive info --node <nodeId>` → `doc read --node <nodeId>` | 用户已给 nodeId/URL 时不再搜索;非 adoc 不调用 `doc read` |
|
||||
| 创建文档 | `doc create --name <标题> --content-file <tmp.md> [--folder <folder> | --workspace <ws>]` | 原生写入管道自动分片;取 `nodeId` 后 `doc read`,缺链接再 `doc info` |
|
||||
| 显式块工作流 | 按用户原顺序执行 `create → block insert/list/update/delete` | 每个阶段是真实调用;标题、段落、列表等显式插入走 block insert |
|
||||
| 末尾补短文本 | `+doc-append --doc <nodeId> --text <内容>` | 该 Shortcut 为 write/user_required;确认后执行并 `doc read` 核对 |
|
||||
| 改写正文 | `doc read --content-format jsonml` → `block update` 或 `doc update --content-format jsonml --mode overwrite` | 单块优先块级编辑;整篇 overwrite 先预览/确认,Markdown overwrite 不保富结构 |
|
||||
| 评论与回复 | `+comment-list --node <nodeId>` → `+comment-create` / `+comment-reply` | `commentKey` 来自真实结果;写 Shortcut 先确认;划词评论走 atomic `comment create-inline` |
|
||||
| 导入 / 导出 | `doc import --file <path> ...` / `doc export --node <nodeId> --export-format <fmt> --output <path>` | Word/Excel 等本地文件要求“在线编辑/转在线文档”必须 import;drive upload 只保留普通文件。仅超时/中断后用 `import get` / `export get` |
|
||||
| 版本操作 | `+version-list --node <nodeId>` → `+version-save` / `+version-revert --version <N>` | save/revert 先确认;revert 版本号必须来自 list,完成后回读 |
|
||||
| 模板创建 | `+template-list` / `+template-search --query <词>` → atomic `template apply --template-id <id>` | templateId 来自真实列表;要复刻已有文档形态时用 drive copy + 副本块级更新 |
|
||||
| 分享链接给某人 | `+share-doc --to <姓名> --url <docUrl> [--note <附言>]` | 会真实发消息,确认后执行;同名人员必须消歧,不改变文档权限 |
|
||||
|
||||
### SOP-2 创建文档并写入(create-doc)
|
||||
## 写入与验证边界
|
||||
|
||||
**触发**:新建文档/写一篇/建文字文档。
|
||||
- `--name` 是文档外壳标题,但不能覆盖用户显式要求的正文 H1。用户说“正文写 `# ...` / 先起一级标题 / 插入一级标题”时,必须原样创建正文 H1;只有用户未要求正文 H1 时才默认从 H2 开始以避免重复。
|
||||
- 用户显式列出的操作是验收步骤:create 只能承载明确要求的初始内容;后续 `list`、`insert`、`append`、`update` 必须逐项真实调用。若要求“有序列表块”,必须写入 JSONML `p.list.isOrdered=true`(或等价原生列表块),普通 Markdown/普通段落不算完成。
|
||||
- 创建只用 `--name`;内容只用 `--content` / `--content-file`。长、多行、表格或特殊字符必须用临时 UTF-8 文件和 `--content-file`。
|
||||
- 原生 Markdown 写入管道在内容超过 10,000 个 Unicode 字符时自动按结构分片;不要在 Skill 或脚本中预先复制分片循环。仅在 `CONTENT_TRUNCATED`、中断或回读缺失时按 [04-document.md](references/04-document.md) 恢复。
|
||||
- `doc update --mode append` 不清空原文;`--mode overwrite` 会清空后重写,先 `--dry-run`,得到确认后才加 `--yes`。
|
||||
- 已有 callout、分栏、样式、@人、图片或附件时,先读 JSONML;局部改动优先 `block update`,不要用 Markdown 整篇重写。
|
||||
- `block insert` 默认追加;只有明确相对位置时才传真实 `--ref-block` / `--parent-block`。`block delete` 和评论删除必须确认。
|
||||
- 写后按对象验证:正文用 `doc read`,块/附件用 `doc block list`,元信息/链接用 `doc info`,版本用 `version list`。
|
||||
- 工具退出码 0、`success=true`、空对象或 `null` 都不能单独证明成功。每个写步骤必须同时有非空业务结果和针对目标字段的回查;例如 comment update 返回 `null` 且 list 仍是旧内容时,该步骤失败,最终必须报告“部分完成/更新未生效”,不得以“全部完成”开头。
|
||||
- 汇总和改写只能重组用户给出的事实,不得增强确定性或新增任务:“验证 12 条用例”不能写成“12 条全部通过”,“整理问题清单”不能扩写成“输出根因分析”。数字、状态、结论和承诺逐项保持原义。
|
||||
|
||||
1. **执行(必须)**:`dws doc create --name "<标题>" --content-file <tmp.md> [--folder <FOLDER_NODE_ID> | --workspace <WORKSPACE_ID>] --format json`(长/多行内容用 `--content-file`,不要用 `--content` 拼长串;用户未指定位置时省略两个位置参数,创建到“我的文档”根目录)。
|
||||
2. **验证(必须)**:从返回取 `nodeId`,立即 `dws doc info --node <nodeId> --format json` 回读确认。
|
||||
## 低频 Reference
|
||||
|
||||
**禁止**:创建后不回读就答复"已创建"、把 `--folder` 当成空间 ID 传入。
|
||||
[doc.md](references/doc.md) 只是 atomic 分支索引。每次只读一个对应 branch reference;JSONML workflow/cookbook/schema 仅在构造复杂 JSONML 后加载,不递归预读。
|
||||
|
||||
### SOP-3 覆盖/追加内容(write-content)
|
||||
## 错误恢复
|
||||
|
||||
**触发**:覆盖写/追加内容/改文档正文。
|
||||
|
||||
1. **执行(必须)**:覆盖先执行 `dws doc update --node <nodeId> --mode overwrite --content-file <tmp.md> --dry-run --format json` 预览,用户确认后改用 `--yes` 实际覆盖;追加执行 `dws doc update --node <nodeId> --mode append --content-file <tmp.md> --format json`。
|
||||
2. **验证(必须)**:写后 `dws doc read --node <nodeId> --format json` 抽取受影响段落核对。
|
||||
|
||||
**禁止**:不加 `--yes` 反复重试覆盖、跳过 `--dry-run` 直接覆盖未确认的长文档。
|
||||
|
||||
### SOP-4 导出 / 下载(export-doc)
|
||||
|
||||
**触发**:导出文档/下载文档/转 PDF·Markdown。
|
||||
|
||||
1. **判类型(必须)**:先 `dws drive info --node <nodeId> --format json`;`extension=adoc` → `dws doc export --node <nodeId> --export-format <pdf|markdown|docx> --output <path> --format json`;普通文件 → 切 `dingtalk-drive` 用 `dws drive download --node <nodeId> --output <path> --format json`。
|
||||
|
||||
**禁止**:不分类型一律走 `doc export`(普通文件会失败)、跳过 `drive info` 判断。
|
||||
|
||||
### SOP-5 块级编辑(block-edit)
|
||||
|
||||
**触发**:插引用块/代码块/表格/分栏/图片/附件,或删除某块。
|
||||
|
||||
1. **先列块(必须)**:`dws doc block list --node <nodeId> --format json`,当前响应的可操作块 ID 位于 `blocks[].element.id`(部分版本可能回显为 `blockId`);必须从目标内容对应项读取,不得编造。空文档的占位空段落可能不能作为 `--ref-block`。
|
||||
2. **按动作执行(必须)**:
|
||||
- 插入:默认追加用 `dws doc block insert --node <nodeId> --text "<内容>" --format json`;只有明确要求相对位置时才加 `--ref-block <非空参照块ID> --where before|after`,容器内插入使用 `--parent-block <父块ID> --index <位置>`。插入命令**不接受** `--block-id`。
|
||||
- 更新:`dws doc block update --node <nodeId> --block-id <目标blockId> --text "<新内容>" --format json`。
|
||||
- 删除:用户确认后执行 `dws doc block delete --node <nodeId> --block-id <目标blockId> --yes --format json`。
|
||||
3. **验证(必须)**:再次执行 `dws doc block list --node <nodeId> --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 <path> --folder <FOLDER_NODE_ID> --name "<标题>" --format json`;复杂参数和限制见 [doc-import.md](references/doc/doc-import.md)。
|
||||
3. **验证(必须)**:拿到返回 `nodeId` 后执行 `dws doc info --node <nodeId> --format json`,必要时 `dws doc read --node <nodeId> --format json` 抽样核对内容。
|
||||
|
||||
**禁止**:把上传文件到钉盘误当成 doc import;不知道目标文件夹 nodeId 时先切 `dingtalk-drive`/`dingtalk-wiki` 查询。
|
||||
|
||||
## 多步文档短路径
|
||||
|
||||
- 在目标文件夹创建文字文档:`dws doc create --name "<标题>" --folder <FOLDER_NODE_ID> --content-file <tmp.md> --format json`。拿到 `nodeId` 后立即回读。
|
||||
- 块级编辑固定顺序:`doc block list --node <nodeId>` → 插入用 `--ref-block`/`--parent-block`,更新或删除用 `--block-id` → `doc block list` 验证。删除块必须已有用户明确删除意图或二次确认。
|
||||
- 插入引用块、代码块、表格、分栏、附件、图片时,优先读 [doc.md](references/doc.md) 对应小节,不要只停在"准备查看 help"。说出"我将插入..."后必须立即执行对应 terminal 调用。
|
||||
- 用户要求多个子文档/附件/块操作时,按 checklist 串行完成;最后一条 assistant 消息不能停在"接下来我要...",必须有实际工具调用或明确失败原因。
|
||||
- 用户说“读取并下载/导出”时,先 `drive info --node ... --format json` 按
|
||||
`extension` 判断类型:`adoc` 用 `doc export`,普通文件切到
|
||||
`dingtalk-drive` 用 `drive download`。
|
||||
- 所有结构化 dws 命令带 `--format json`。仅参数不确定时查 `--help`,不要把完整 help 当成最终结果。
|
||||
|
||||
## 危险操作
|
||||
|
||||
`block delete` 不可逆,必须确认再加 `--yes`。
|
||||
- 路径或参数错误:按既定顺序查 leaf Schema、再查 leaf Help,校正一次;不要连续尝试近似参数。
|
||||
- 始终从实际输出提取并续用 `nodeId`、`blockId`、`commentKey`、`jobId` 或 `taskId`,不得搜索同名项覆盖当前请求的新 ID。
|
||||
- 部分写入或回读缺失:保留已创建的 nodeId,报告已完成范围与缺失位置;先读回,再只补缺失内容,禁止无条件重新创建副本。
|
||||
- 权限不足、候选未消歧、目标类型不符、没有可推进的任务 ID 或 Schema/Help 冲突时停止并报告。
|
||||
|
||||
## 跨产品协作
|
||||
|
||||
- 文件存储 / 上传下载 → 切到 `dingtalk-drive`
|
||||
- 知识库空间管理 → 切到 `dingtalk-wiki`
|
||||
- 数据表 → 切到 `dingtalk-aitable`
|
||||
- 原生 `.md` 文件读取、创建、全量覆盖或局部替换 → 切到 `dingtalk-markdown`
|
||||
- 长篇报告生成(多源采集 + 写文档)→ 此 skill 提供 `doc_create_and_write.py` 脚本
|
||||
## 局部意图与短流程
|
||||
|
||||
- [局部意图消歧](references/intent-guide.md);[短流程](references/lite-recipes.md)。
|
||||
`.md` 走 `dingtalk-markdown`;普通文件走 `dingtalk-drive`;知识库走 `dingtalk-wiki`;表格走 `dingtalk-sheet` / `dingtalk-aitable`;评论 userId 走人员 Skill。边界读 [intent-guide.md](references/intent-guide.md),固定流程读 [lite-recipes.md](references/lite-recipes.md)。
|
||||
|
||||
@@ -5,10 +5,10 @@
|
||||
| Recipe | 行动指南(固定路线) |
|
||||
|--------|-------------------|
|
||||
| import-file | 1. **直接执行** `dws doc import --file <本地文件路径> --format json`(一条命令完成上传+转换+创建)<br>2. 从返回中提取 `documentUrl` 并告知用户<br>3. **禁止先 Read 文件内容再 `doc create` + `doc update`**——`doc import` 是服务端格式转换,客户端无需解析文件内容<br>4. 可选参数:`--folder <文件夹ID>` 指定目标文件夹、`--workspace <知识库ID>` 指定目标知识库、`--name "文档名"` 自定义名称<br>5. 格式映射:docx/doc→文档, xlsx/xls→表格, xmind/mark→脑图, md/txt→文档<br>6. 超时或中断时 CLI 返回 `taskId`,用 `dws doc import get --task-id <taskId>` 手动查询<br>详见 [doc-import.md](./doc/doc-import.md) |
|
||||
| write-doc | 0. 阅读 [doc-create-workflow.md](./doc/style/doc-create-workflow.md) 的 §前置必读 + §关键词速查表,锁定文档类型和起稿路径:**决策型/含对比的知识沉淀型/用户要求美观 → JSONML 起稿**(`.json`);执行型/说明型 → Markdown 起稿(`.md`)<br>1. 按选定路径执行 doc-create-workflow.md(JSONML 路径有骨架范例可直接复制修改)<br>2. `doc create --content-file /tmp/<name>.json --content-format jsonml`(或 `.md` + `--content-format markdown`)<br>3. 大内容默认依赖 DWS 自动分片;只有 `CONTENT_TRUNCATED`、部分写入失败或回读发现缺失时,才按 workflow 的恢复流程手工补片<br>4. **回读校验(必须)**:所有写入完成后,执行 `doc read --node <nodeId>`,校验关键标题/段落/表格是否完整写入 |
|
||||
| write-doc | 1. 普通线性正文直接写入 UTF-8 `.md`,执行 `doc create --name <标题> --content-file <tmp.md> --content-format markdown --format json`<br>2. 仅当用户要求复杂版式且确实选择 JSONML 时,读取 [doc-create-workflow.md](./doc/style/doc-create-workflow.md) 对应章节,不预读整套 style/reference<br>3. 大内容依赖 DWS 自动分片;仅在 `CONTENT_TRUNCATED`、中断或回读缺失时恢复<br>4. 取 create 返回的 `nodeId` 执行 `doc read --node <nodeId> --format json`,核对明确要求的标题、段落和结构 |
|
||||
| search-docs-and-share | 1. `dws drive search --query "<关键词>" --format json` → 取候选 `nodeId` + 标题建索引(不读全文)<br>2. 对追问选中的候选执行 `dws drive info --node <nodeId> --format json`<br>3. 仅 `extension=adoc` 使用 `dws doc read --node <nodeId> --format json`(最多 2 篇);`md` / `axls` / `able` / 普通文件分别切到 markdown / sheet / aitable / drive,禁止固定执行 `doc read` |
|
||||
| create-knowledge-base | 1. 创建知识库空间取 `WS_ID`<br>2. `wiki node create --workspace <WS_ID> --name "<文档名>"` → 取 `nodeId`<br>3. `wiki node list --workspace <WS_ID>` 确认 |
|
||||
| migrate-doc | 1. `doc read --node <源nodeId>` → 取正文并写入临时文件 `<tmp>.md`<br>2. `doc create --name "<文档名>" --folder <DOC_FOLDER_NODE_ID> --content-file <tmp>.md` → 取新 `nodeId`(`--folder` 只传文档文件夹 nodeId / alidocs 文件夹 URL,不传数字 dentryId;正文 <200KB 单步到位)<br>2a. 若正文 >200KB:**必须先向用户提示截断风险**(详见下方「分块 append 截断风险提示」),用户确认后再执行:`doc create --name "<文档名>" --folder <DOC_FOLDER_NODE_ID>` → `nodeId` → 按段落切片 → 每片 `doc update --node <nodeId> --content-file <part> --mode append`<br>3. **回读校验**:`doc read --node <nodeId>` 校验内容完整性 |
|
||||
| migrate-doc | 1. `doc read --node <源nodeId>` → 取正文并写入临时文件 `<tmp>.md`<br>2. `doc create --name "<文档名>" --folder <DOC_FOLDER_NODE_ID> --content-file <tmp>.md` → 取新 `nodeId`;所有长度都先走这一条原生命令,由 CLI 自动分片(`--folder` 只传文档文件夹 nodeId / alidocs 文件夹 URL,不传数字 dentryId)<br>3. **回读校验**:`doc read --node <nodeId>` 校验内容完整性;仅在 `CONTENT_TRUNCATED`、中断或回读缺失时,从真实断点补写缺失部分 |
|
||||
| update-doc-section | 1. `dws drive search --query "<关键词>" --format json` → 取 `nodeId`<br>2. `dws drive info --node <nodeId> --format json`,仅 `extension=adoc` 继续;其他类型切到对应 skill/reference<br>3. **形态选择(按 [doc-update-workflow.md §1.3](./doc/style/doc-update-workflow.md) 优先级)**:目标段落含 callout / 分栏 / 颜色 / @人 / 附件 / 嵌套结构 → 走 `jsonml-node-edit`;纯文本替换且确认无富结构 → 继续本 recipe<br>4. `dws doc read --node <nodeId> --format json` 定位目标章节<br>5. `dws doc update --node <nodeId> --content "<替换内容>" --mode overwrite --yes --format json`<br>6. **回读校验**:`dws doc read --node <nodeId> --format json` 确认 overwrite 未被降级为 append、内容完整无截断<br>**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 对应小节执行<br>2. 单块改写 / 含富结构 → §4.4 路径 B;多处保真改写或改 root → §4.4 路径 A;纯文本骨架重写 → §4.5 markdown<br>3. 整篇 overwrite 前必须按 workflow §4.5 向用户提示风险并等待确认<br>4. **回读校验(必须)**:按 workflow §6 的校验要点逐项核查;@人、附件、图片等保真要素必须原样保留<br>**适用场景**:用户提供已有 nodeId/链接,需要改写、润色、章节补充、段落形态转换、整篇重写 |
|
||||
| doc-to-message | 1. `doc read --node <nodeId>` → 取正文(大文档只摘要+链接)<br>2. `aisearch person --keyword "<姓名>" --dimension name` → 取 `openDingTalkId`(推荐);或 `chat search --query "<群名>"` → 取 `openConversationId`<br>3. `chat message send --open-dingtalk-id <openDingTalkId> --text "<内容>"`(推荐)或 `--group <openConversationId> --text "<内容>"` 发送。仅当无法获取 openDingTalkId 时才用 `--user <userId>`(备选) |
|
||||
@@ -29,28 +29,17 @@
|
||||
|
||||
---
|
||||
|
||||
## 分块 append 截断风险提示
|
||||
## 自动分片失败恢复
|
||||
|
||||
### 触发条件
|
||||
`doc create` / `doc update` 的 Markdown 写入管道会从 10,000 个 Unicode 字符开始自动分片,超时后降到 5,000 字符重试。不要在调用前手工复制分片逻辑。
|
||||
|
||||
当内容总大小 **超过 200KB**,需要拆分为多片通过 `doc update --mode append` 分块写入时,**必须在执行前向用户发出截断风险提示**,等待用户确认后再继续。
|
||||
只有返回 `CONTENT_TRUNCATED`、写入被中断或回读发现缺失时,才执行恢复:
|
||||
|
||||
### 提示话术(参考模板)
|
||||
|
||||
> 注意: 内容较长(约 {size}),需要分 {n} 片写入。分块 append 存在以下风险:
|
||||
> - 部分片段可能写入失败但返回 success,导致文档**内容截断或缺失**
|
||||
> - 片段之间的表格、代码块等跨块元素可能**被截断破坏**
|
||||
> - 写入顺序异常可能导致**段落错乱**
|
||||
>
|
||||
> 建议:写入完成后我会回读校验文档完整性。是否继续?
|
||||
|
||||
### 执行规范
|
||||
|
||||
1. **提示时机**:在执行第一片 append **之前**提示,而非写入过程中
|
||||
2. **分片原则**:按段落/标题边界切分,**禁止**在表格、代码块、列表内部截断
|
||||
3. **逐片校验**(推荐):每写入一片后记录已写入的最后一个标题/段落标记,供最终回读时比对
|
||||
4. **最终回读**(必须):所有片段写入完成后,执行 `doc read --node <nodeId>` 回读全文,逐片比对关键标记是否完整(详见下方「doc update 回读校验规范」)
|
||||
5. **失败处理**:若回读发现缺失片段,向用户报告具体缺失位置,建议针对缺失部分单独重试 append
|
||||
1. 保存命令返回的真实 `nodeId` 和 `chunksWritten`,不要重新创建文档。
|
||||
2. `doc read --node <nodeId>` 定位最后一个完整标题/段落。
|
||||
3. 从原始输入中只提取缺失后缀,写入一个新的 UTF-8 文件。
|
||||
4. 向用户报告已完成范围与待补范围;得到继续指示后,用一次 `doc update --mode append --content-file <missing.md>` 补写。
|
||||
5. 再次回读并核对关键标题、表格、列表和代码块;仍缺失则停止,不循环追加。
|
||||
|
||||
---
|
||||
|
||||
@@ -68,3 +57,10 @@ dws doc read --node <nodeId> # 校验关键标题、段落首句、表格、@
|
||||
```
|
||||
|
||||
**禁止**在未回读的情况下向用户报告「已完成」。
|
||||
|
||||
## 显式工作流与事实保真
|
||||
|
||||
- 用户点名的 `create → list → insert/append/update` 是可观察命令链,必须保持顺序逐项执行;create 只承载明确的初始正文。有序列表块必须验证回读结构中的 `list.isOrdered=true`。
|
||||
- 新建资源返回 ID 后,同一请求的指代默认绑定该新资源;禁止搜索同名旧资源替换绑定。
|
||||
- 汇总用户材料时保留证据强度:验证数量不等于通过数量,计划整理问题不等于承诺根因分析。不得为“更专业”而补造结论、状态或任务。
|
||||
- 任一步骤返回 `null`/空结果或回查不一致时,该步骤未完成;最终按步骤报告成功与失败,不能用其他成功步骤把整体描述成“全部完成”。
|
||||
|
||||
@@ -11,44 +11,35 @@
|
||||
|
||||
> **操作后请返回文档 URI**:每次执行 create / read / update 等操作后,从返回数据中提取 `docUrl` 直接返回;缺失时用 `doc info --node <ID>` 补查。
|
||||
|
||||
## 前置条件 — 执行操作前必读
|
||||
## 按需加载边界
|
||||
|
||||
**CRITICAL — 执行对应操作前,MUST 先用 Read 工具读取以下子文件:**
|
||||
本文件只做低频 atomic 路由,不是任何命令的前置必读。根 Skill 已覆盖的普通 create/read/block/import/export 直接执行;仅在命令已选中但特殊参数或边界仍不明确时,读取下方对应的一个 branch reference。只有实际执行复杂 JSONML 保真写入时才加载 workflow/cookbook/schema,禁止递归预读整组文件。
|
||||
|
||||
1. **解析 URL / 定位文档**(几乎所有命令都需要先拿 nodeId)
|
||||
→ 必读 [`doc/doc-info.md`](doc/doc-info.md)(URL/dentryKey 提取规则、ID 边界、extension 路由、**获取 nodeId 三种方式 A/B/C**)
|
||||
## Atomic 命令加载契约
|
||||
|
||||
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)
|
||||
本文件只负责 atomic branch 路由;公开 Shortcut 的选择与发现只在根
|
||||
[`SKILL.md`](../SKILL.md) 维护,不在 references 重复列出。
|
||||
|
||||
**未读以上文件就改写已有文档会导致富结构丢失、参数错误或样式不达标。其他命令(阅读 / 评论 / 权限 / 附件 / 下载导出 / 文件操作)按需查下方 §命令索引表跳转对应子文件加载,不必提前加载。**
|
||||
|
||||
## 查询命令帮助
|
||||
|
||||
当你不确定某个命令的具体参数、格式或可选项时,**优先执行 `--help` 查询**,不要猜测参数名或凭记忆编造。
|
||||
命令已经选中且参数清楚时直接执行,不做重复发现。参数、约束或安全语义不确定时,
|
||||
先查询精确 leaf Schema;只有当前 Cobra flag 不确定时才查询精确 leaf `--help`。
|
||||
不要加载产品级全量 Schema,也不要猜测参数名。
|
||||
|
||||
```bash
|
||||
# 查看 doc 下所有子命令
|
||||
dws doc --help
|
||||
# 参数、约束或安全语义不确定
|
||||
dws schema --cli-path "doc update" --format json
|
||||
|
||||
# 查看具体命令的完整参数说明
|
||||
# 仅 Cobra flag 不确定
|
||||
dws doc read --help
|
||||
dws doc create --help
|
||||
dws doc block insert --help
|
||||
|
||||
# 查看子命令组下的所有命令
|
||||
dws doc block --help
|
||||
dws doc media --help
|
||||
```
|
||||
|
||||
规则:
|
||||
|
||||
- 参数名不确定时 → 先 `--help`,再调用
|
||||
- 报错 "unknown flag" 时 → `--help` 确认正确的 flag 名称
|
||||
- 不确定某个功能是否存在时 → `dws doc --help` 查看命令列表
|
||||
- 参数、约束、安全不确定 → leaf Schema
|
||||
- `unknown flag` 或当前 Cobra flag 不确定 → leaf `--help`,修正一次
|
||||
- 能力或分支未知 → 先用下方场景/命令索引;仍无法定位才回根 Skill 的 Runtime Shortcut Catalog
|
||||
- Schema 与 Help 冲突 → 采用更安全解释并报告契约漂移
|
||||
|
||||
## 命令索引表
|
||||
|
||||
|
||||
@@ -1,15 +1,11 @@
|
||||
# doc block(块级精细编辑:list / insert / update / delete)
|
||||
|
||||
> **前置条件(MUST READ):** 执行本命令前,必须先用 Read 工具读取以下文件:
|
||||
> 1. [`../doc.md`](../doc.md) — 命令路由 + 场景索引 + 意图判断 + 工作流
|
||||
> 2. [`./style/doc-update-workflow.md`](./style/doc-update-workflow.md) — 改写流程(编辑形态优先级、JSONML validator 行为)
|
||||
> 3. [`./format/doc-jsonml-cookbook.md`](./format/doc-jsonml-cookbook.md) — JSONML 范例(含 callout / 分栏 / 表格 / 标题等节点的完整命令)
|
||||
> 4. [`./format/doc-jsonml-schema.md`](./format/doc-jsonml-schema.md) — JSONML 节点结构字段定义
|
||||
>
|
||||
> **同任务常配合**:[`doc-update.md`](./doc-update.md)(整篇 overwrite / 末尾追加纯文本)/ [`./format/doc-jsonml-cookbook.md`](./format/doc-jsonml-cookbook.md)(JSONML 复制范例)
|
||||
> 本文件自包含简单 list/insert/update/delete 契约,不要递归预读路由或 style reference。只有实际构造复杂 JSONML 节点时,才读取 [`doc-jsonml-cookbook.md`](./format/doc-jsonml-cookbook.md);字段仍不确定时再查 [`doc-jsonml-schema.md`](./format/doc-jsonml-schema.md)。整篇 overwrite 或纯文本 append 才转读 [`doc-update.md`](./doc-update.md)。
|
||||
|
||||
> **改写已有文档优先 JSONML**:保真度最高、callout / 分栏 / 表格 / @人 / 附件 / 颜色 / 嵌套都能 1:1 round-trip;写入端有 validator 兜底。详见 [`./style/doc-update-workflow.md` §1.3 编辑形态优先级](./style/doc-update-workflow.md)。
|
||||
|
||||
> **显式块操作不可折叠**:用户说“先 create,再 list/insert/update/append”时按原顺序真实调用;不能因为最终正文相似,就把后续块操作合并进 create 或一次 Markdown 写入。
|
||||
|
||||
---
|
||||
|
||||
## doc block list(查询块元素)
|
||||
@@ -173,7 +169,8 @@ dws doc block delete --node DOC_ID --block-id UUID
|
||||
|
||||
- **块类型**:paragraph、heading、blockquote、callout、columns、orderedList、unorderedList、table、sheet、attachment、slot。
|
||||
- **快捷 vs --element**:`block insert` 优先使用 `--text` 或 `--heading` 快捷方式;复杂块类型(table、callout、columns 等)使用 `--element` JSON 或 `--content-format jsonml`。
|
||||
- **简单内容追加**:建议用 [`./doc-update.md`](./doc-update.md) `--mode append`,不必走 block insert。
|
||||
- **有序列表块**:用户明确要求 ordered list / 有序列表块时,必须用 JSONML `p` 节点的 `list.isOrdered=true`(同一 `listId`;仅首项设 `start:1`)或等价原生 orderedList element;带 `1.` 前缀的普通段落、普通 Markdown 或一次 create 不满足要求。
|
||||
- **简单内容追加**:用户只说追加纯文本且不强调块操作时可用 [`./doc-update.md`](./doc-update.md) `--mode append`;用户明确说 block insert / 插入段落 / 插入标题 / 插入列表块时必须走 block insert。
|
||||
- **JSONML validator**(写入端默认行为):
|
||||
- 裸字符串、缺 uuid 等结构错误会被 validator 抦下并返回带 path 的错误(如 `$[2][2]: paragraph child must be span wrapper, got raw string.`)。
|
||||
- `--fix-jsonml` 开启 JSON 语法修复,推荐 agent 调用。
|
||||
@@ -241,6 +238,12 @@ dws doc block list --node <DOC_ID> --content-format jsonml --block-id <UUID>
|
||||
dws doc block insert --node <DOC_ID> --content-format jsonml --ref-block <UUID> --where after \
|
||||
--element '["p",{},["span",{"data-type":"text"},["span",{"data-type":"leaf"},"新段落"]]]'
|
||||
|
||||
# 插入有序列表块(3 项共用 listId,仅首项有 start)
|
||||
dws doc block insert --node <DOC_ID> --content-format jsonml \
|
||||
--element '["p",{"uuid":"ol1","list":{"listId":"actions","level":0,"isOrdered":true,"start":1}},["span",{"data-type":"text"},["span",{"data-type":"leaf"},"第一项"]]]'
|
||||
dws doc block insert --node <DOC_ID> --content-format jsonml \
|
||||
--element '["p",{"uuid":"ol2","list":{"listId":"actions","level":0,"isOrdered":true}},["span",{"data-type":"text"},["span",{"data-type":"leaf"},"第二项"]]]'
|
||||
|
||||
# 插入 callout(colorBlocks)
|
||||
dws doc block insert --node <DOC_ID> --content-format jsonml --ref-block <UUID> --where after \
|
||||
--element '["container",{"uuid":"co1","subType":"colorBlocks","metadata":{"bgcolor":"#FDE2E0","border":"#F5C2C7"}},["p",{"uuid":"co1p1"},["span",{"data-type":"text"},["span",{"data-type":"leaf"},"高风险操作,先备份"]]]]'
|
||||
|
||||
@@ -1,9 +1,6 @@
|
||||
# doc comment(文档评论:list / create / reply / update / delete / create-inline)
|
||||
|
||||
> **前置条件(MUST READ):** 执行本命令前,必须先用 Read 工具读取以下文件:
|
||||
> 1. [`../doc.md`](../doc.md) — 命令路由 + 场景索引 + 意图判断 + 工作流
|
||||
>
|
||||
> **同任务常配合**:`dws aisearch person`(查 `--mention` 用 userId)/ `dws chat search`(查群用 openConversationId)/ [`doc-block.md`](./doc-block.md)(划词评论必须先取 blockId 与 paragraph 文本)
|
||||
> 本文件自包含评论命令契约。仅在需要 mention 时查询真实 userId/openConversationId;仅在划词评论尚无 blockId 与 paragraph 文本时读取 [`doc-block.md`](./doc-block.md) 并执行 block list。
|
||||
|
||||
---
|
||||
|
||||
@@ -135,6 +132,7 @@ Flags:
|
||||
- `reply` 加 `--emoji` 时 `--content` 填表情名称(如 `比心`、`赞`),不是文字内容。
|
||||
- `reply --emoji` 与群 mention 冲突;CLI 会在调用服务端前报错,不会静默忽略。
|
||||
- `delete` 是不可逆操作;AI Agent 必须先让用户确认,再追加 `--yes`,避免 CLI 进入交互等待。
|
||||
- `comment create/reply/update/delete` 的退出码 0 不等于业务成功。响应为 `null`、空对象或缺少可核验字段时,立即执行 `comment list` 回查目标 `commentKey`。若 update 后正文仍是旧值,必须判定“更新未生效”;即使其他步骤成功或评论随后被删除,也只能报告部分完成,禁止写“全部完成”。
|
||||
|
||||
## 上下文传递
|
||||
|
||||
|
||||
@@ -1,11 +1,6 @@
|
||||
# doc create(创建文档)
|
||||
|
||||
> **前置条件(MUST READ):** 执行本命令前,必须先用 Read 工具读取以下文件:
|
||||
> 1. [`../doc.md`](../doc.md) — 命令路由 + 场景索引 + 意图判断 + 工作流
|
||||
> 2. [`./style/doc-create-workflow.md`](./style/doc-create-workflow.md) — 创建工作流(标题、位置、骨架、回读校验)
|
||||
> 3. [`./style/doc-style-guideline.md`](./style/doc-style-guideline.md) — 排版规范(草稿元素清单、骨架样板)
|
||||
> 4. [`./doc-update.md` §内容写入管道](./doc-update.md) — 长内容自动分片、`--content-file` vs `--content` 选择
|
||||
> 5. [`./format/doc-jsonml-cookbook.md`](./format/doc-jsonml-cookbook.md) — 仅当使用 `--content-format jsonml` 时必读
|
||||
> 本文件自包含普通 Markdown 创建契约,不要递归预读 `doc.md`、style 或 update reference。仅当用户要求复杂版式并实际选择 JSONML 时,读取 [`doc-jsonml-cookbook.md`](./format/doc-jsonml-cookbook.md);需要文档骨架建议时才读取对应 style 章节。
|
||||
|
||||
## 创建路由前置判断(必看)
|
||||
|
||||
@@ -40,11 +35,11 @@ Flags:
|
||||
|
||||
## 关键说明
|
||||
|
||||
- **`--name` 是 H1**:正文从 `##` 开始;正文内不要再写 `#` 一级标题(除非确需且已说明动机)。
|
||||
- **标题优先级**:`--name` 是文档外壳标题,默认可视作 H1;但它不能替代用户显式要求的正文一级标题。用户说“正文写 `# ...`”“正文先起个一级标题”时,必须在初始内容中保留该 `#` H1;用户未要求正文 H1 时,正文默认从 `##` 开始以避免重复。
|
||||
- 不传 `--folder` 和 `--workspace` 时,默认创建在「我的文档」根目录。
|
||||
- `--folder` 仅接受文档文件夹 `nodeId` / `dentryUuid` / alidocs 文件夹 URL;**禁止**传入 drive `dentryId`、`parentId`、`spaceId` 这类纯数字 ID。
|
||||
- 输入方式选择见 [`./doc-update.md` §内容写入管道](./doc-update.md)(与 update 共用)。短文本字面量可 `--content`,多行/表格/特殊字符必须 `--content-file` 或 `--content -`。
|
||||
- 长内容(>30000 字符)CLI 自动分片:先创建空文档拿 `nodeId`,再按 markdown 标题边界切分后逐片 append;调用方无需手动编排。
|
||||
- Markdown 超过 10,000 个 Unicode 字符时,CLI 自动按结构分片:第一片随 `create` 写入并取得 `nodeId`,后续片自动 append;调用方不要手动预分片。
|
||||
|
||||
## 上下文传递
|
||||
|
||||
@@ -54,6 +49,12 @@ Flags:
|
||||
| `docUrl` | 最终交付给用户的链接;缺失时用 [`./doc-info.md`](./doc-info.md) 补查 |
|
||||
| `chunksWritten` | 判断是否触发自动分片;> 1 时重点检查章节顺序 |
|
||||
|
||||
同一请求后续出现“这篇/刚才那篇/上次那篇”时,直接续用本次 create 返回的 `nodeId`;禁止先搜索同名文档再把后续操作指向旧节点。
|
||||
|
||||
## 显式操作序列
|
||||
|
||||
用户点名 `block list`、插入、追加、更新等后续动作时,必须按原顺序逐项执行。`doc create` 只写用户指定的初始内容,不能为了减少调用把后续标题、列表或段落提前塞进 create。例:`创建 → 查看块结构 → 末尾插入段落` 必须真实执行 create、block list、block insert 三步。
|
||||
|
||||
## 回读验收(必读)
|
||||
|
||||
CLI **不会**自动回读校验。**每次创建后**都必须执行 `doc read --node <nodeId>` 校验关键标题、段落首句、表格表头是否完整。详见 [`./style/doc-create-workflow.md` «回读验收»](./style/doc-create-workflow.md)。
|
||||
@@ -70,7 +71,7 @@ dws doc create --name "<文档名>" --content-file /tmp/<name>.md --folder <DOC_
|
||||
# 创建到知识库
|
||||
dws doc create --name "<文档名>" --content-file /tmp/<name>.md --workspace <WS_ID> --content-format markdown
|
||||
|
||||
# 创建空文档(仅取 nodeId 后再分步写入,适合 >200KB 兜底)
|
||||
# 创建空文档(用户明确需要空文档时)
|
||||
dws doc create --name "<文档名>" [--folder <ID> | --workspace <ID>] --content-format markdown
|
||||
|
||||
# 短纯文本字面量(< 2KB 且无换行/表格才允许)
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
# doc export(在线文档导出为 docx/markdown/pdf)
|
||||
|
||||
> **前置条件(MUST READ):** 执行本命令前,必须先用 Read 工具读取以下文件:
|
||||
> 1. [`../doc.md`](../doc.md) — 命令路由 + 场景索引 + 意图判断 + 工作流
|
||||
> 本文件自包含 export 契约。已有当前请求返回的 adoc nodeId 时直接导出;目标类型未知时只执行一次 `drive info`,不要递归读取 `doc.md`。
|
||||
|
||||
> **路由前置判断**:用户说「下载/导出」时**必须**先用 `dws drive info --node <ID> --format json` 查 `extension`:
|
||||
> - `extension` 为 `adoc`(在线文档)→ **必须用 `export`**,禁止用 `download`
|
||||
@@ -51,6 +50,7 @@ Flags:
|
||||
|
||||
## 关键说明
|
||||
|
||||
- 同一请求中刚执行 create/copy/import 并紧接着说“这篇/刚才那篇/上次那篇”时,`--node` 必须使用该写操作真实返回的新 `nodeId`;不得预先搜索同名文档,也不得用搜索结果中的旧节点替换它。
|
||||
- `export` 是一体化命令,一条命令自动完成提交→轮询→下载,**无需手动编排轮询**。CLI 内部使用渐进式退避轮询(最多约 5 分钟)。
|
||||
- `export` 超时或中断后,CLI 会输出 `jobId`,可用 `dws doc export get --job-id <jobId>` 手动查询任务状态。
|
||||
- `export` 支持钉钉在线文档(alidocs,`contentType=ALIDOC`)导出为 `docx`、`markdown` 或 `pdf`,**在线表格导出请使用其他命令**。
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
# doc import(本地文件导入为在线文档)
|
||||
|
||||
> **前置条件(MUST READ):** 执行本命令前,必须先用 Read 工具读取以下文件:
|
||||
> 1. [`../doc.md`](../doc.md) — 命令路由 + 场景索引 + 意图判断 + 工作流
|
||||
> 本文件自包含 import 契约。文件、目标 folder/workspace 与参数已知时直接执行;只有参数或安全语义不确定时查询精确 leaf Schema,不要递归读取 `doc.md`。
|
||||
|
||||
> **支持的文件格式**:docx, doc, xlsx, xls, md, txt, xmind, mark
|
||||
> **文件大小限制**:20MB
|
||||
|
||||
> **在线编辑硬路由**:用户说“上传后在线编辑/大家直接在线改/转成钉钉文档”时必须使用 `doc import`。`drive upload` 只保留原始 `.docx/.xlsx/...` 普通文件,不能据此宣称已可在线编辑。若用户明确要同时保留原文件和在线版,才先 `drive upload`,再单独 `doc import`,并分别验证两个返回节点。
|
||||
|
||||
---
|
||||
|
||||
## doc import(一体化命令)
|
||||
@@ -63,6 +64,7 @@ Flags:
|
||||
## 关键说明
|
||||
|
||||
- `import` 是一体化命令,一条命令自动完成创建会话→上传→确认→轮询,**无需手动编排**。CLI 内部使用渐进式退避轮询(最多约 5 分钟)。
|
||||
- 导入完成后必须用返回的 `documentUrl`/`nodeId` 执行 `drive info` 或 `doc info`,确认 `extension=adoc`(Word/文本)或对应在线类型,并确认目标 `folderId`;只有验证后才能说“可直接在线编辑”。
|
||||
- `import` 超时或中断后,CLI 会输出 `taskId`,可用 `dws doc import get --task-id <taskId>` 手动查询任务状态。
|
||||
- 支持的文件格式:docx, doc, xlsx, xls, md, txt, xmind, mark(共 8 种)。
|
||||
- 文件大小限制:20MB。超过限制时 CLI 会直接报错,不会发起网络请求。
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
# doc info(获取文档元信息 + URL 解析)
|
||||
|
||||
> **前置条件(MUST READ):** 执行本命令前,必须先用 Read 工具读取以下文件:
|
||||
> 1. [`../doc.md`](../doc.md) — 命令路由 + 场景索引 + 意图判断 + 工作流
|
||||
> 2. [`url-patterns.md`](../../../dws-shared/references/url-patterns.md) — 仅当用户原始 `alidocs` URL 需要 probe 时
|
||||
> 本文件自包含已知 nodeId/URL 的 info 契约。只有原始 alidocs URL 类型仍不明确时,才读取 [`url-patterns.md`](../../../dws-shared/references/url-patterns.md);不要递归读取 `doc.md`。
|
||||
>
|
||||
> **探测入口变更**: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`。
|
||||
>
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
# doc media(附件 / 图片:download / insert)
|
||||
|
||||
> **前置条件(MUST READ):** 执行本命令前,必须先用 Read 工具读取以下文件:
|
||||
> 1. [`../doc.md`](../doc.md) — 命令路由 + 场景索引 + 意图判断 + 工作流
|
||||
> 本文件自包含 media insert/download 契约。nodeId、文件路径或 resourceId 已知时直接执行;只在需要相对块定位且 blockId 未知时读取 [`doc-block.md`](./doc-block.md)。
|
||||
|
||||
> ⚠️ **图片插入硬规则**:
|
||||
> - 图片来源如果是钉盘/文档空间中的文件,**必须先下载到本地**(`dws drive download --node <图片nodeId> --output /tmp/xxx.png`),再执行 `media insert`
|
||||
|
||||
@@ -1,10 +1,6 @@
|
||||
# doc read(读取文档内容)
|
||||
|
||||
> **前置条件(MUST READ):** 执行本命令前,必须先用 Read 工具读取以下文件:
|
||||
> 1. [`../doc.md`](../doc.md) — 命令路由 + 场景索引 + 意图判断 + 工作流
|
||||
> 2. [`./format/doc-jsonml-cookbook.md`](./format/doc-jsonml-cookbook.md) — 仅当使用 `--content-format jsonml` 时必读
|
||||
>
|
||||
> **同任务常配合**:[`doc-info.md`](doc-info.md)(先解析 URL,确认 extension=adoc)/ [`doc-update.md`](doc-update.md)(读后改写)/ [`doc-block.md`](doc-block.md)(块级精修前先读结构)
|
||||
> 本文件自包含普通 read 契约。用户已给当前 adoc nodeId/URL 时直接读取;类型未知时才先执行 `drive info`。选择 JSONML 只为获取结构,不要求预读 cookbook;实际构造 JSONML 写入时再按需加载。
|
||||
|
||||
## 命令格式
|
||||
|
||||
|
||||
@@ -1,12 +1,6 @@
|
||||
# doc update(更新文档内容)
|
||||
|
||||
> **前置条件(MUST READ):** 执行本命令前,必须先用 Read 工具读取以下文件:
|
||||
> 1. [`../doc.md`](../doc.md) — 命令路由 + 场景索引 + 意图判断 + 工作流
|
||||
> 2. [`./style/doc-update-workflow.md`](./style/doc-update-workflow.md) — 改写流程(编辑形态优先级、分片 append、回读验收)
|
||||
> 3. [`./style/doc-style-guideline.md`](./style/doc-style-guideline.md) — 排版规范
|
||||
> 4. [`./format/doc-jsonml-cookbook.md`](./format/doc-jsonml-cookbook.md) — 仅当使用 `--content-format jsonml` 时必读
|
||||
>
|
||||
> **同任务常配合**:[`doc-read.md`](./doc-read.md)(改写前必读,jsonml 模式拿当前结构;担心被并发覆盖时再取 revision)/ [`doc-block.md`](./doc-block.md)(单 block 改写优先;本命令更适合追加 / 整篇 overwrite)
|
||||
> 本文件自包含普通 append/overwrite 契约,不要递归预读路由或 style reference。纯文本 append 可直接执行;overwrite 先 read/dry-run/确认。只有保真改写或复杂 JSONML 才读取 [`doc-update-workflow.md`](./style/doc-update-workflow.md) 与 cookbook;单块修改改用 [`doc-block.md`](./doc-block.md)。
|
||||
|
||||
## 命令格式
|
||||
|
||||
@@ -75,7 +69,7 @@ Flags:
|
||||
|
||||
## 内容写入管道(create / update 共用)
|
||||
|
||||
> **关键原则**:CLI 内置自动分片。超长内容(>30000 字符)自动按 markdown 结构切分后逐片写入,对调用方透明。写入完成后由调用方自行决定是否回读确认。
|
||||
> **关键原则**:CLI 内置自动分片。Markdown 超过 10,000 个 Unicode 字符时自动按结构切分后逐片写入,对调用方透明。调用方必须在写入后回读确认。
|
||||
|
||||
### 输入方式选择
|
||||
|
||||
@@ -88,9 +82,9 @@ Flags:
|
||||
|
||||
### 自动分片行为
|
||||
|
||||
当内容超过 30000 字符时,CLI 自动执行:
|
||||
当 Markdown 内容超过 10,000 个 Unicode 字符时,CLI 自动执行:
|
||||
|
||||
1. **create**: 先创建空文档拿 `nodeId`,再按 markdown 标题边界切分后逐片 append
|
||||
1. **create**: 第一片随 create 写入并取得 `nodeId`,后续片 append
|
||||
2. **update (overwrite)**: 第一片用 overwrite,后续片用 append
|
||||
3. **update (append)**: 所有片段用 append
|
||||
|
||||
@@ -124,13 +118,13 @@ CLI **不会**自动执行回读验证。**你必须在文档写入完成后主
|
||||
### 进度输出示例
|
||||
|
||||
```
|
||||
[INFO] 内容较长 (45000 字符),自动分片写入...
|
||||
[INFO] 已创建空文档 (nodeId=abc123),开始分片写入...
|
||||
[INFO] 写入分片 (1/3),15000 字符...
|
||||
[INFO] 写入分片 (2/3),15000 字符...
|
||||
[INFO] 写入分片 (3/3),15000 字符...
|
||||
[INFO] 内容较长 (25000 字符),自动分片写入...
|
||||
[INFO] 写入分片 (1/3),10000 字符 (create)...
|
||||
[INFO] 文档已创建 (nodeId=abc123)
|
||||
[INFO] 写入分片 (2/3),10000 字符...
|
||||
[INFO] 写入分片 (3/3),5000 字符...
|
||||
[INFO] 全部 3 个分片写入完成
|
||||
{"success": true, "nodeId": "abc123", "chunksWritten": 3}
|
||||
{"success":true,"nodeId":"abc123","chunksWritten":3}
|
||||
```
|
||||
|
||||
### CONTENT_TRUNCATED 错误
|
||||
@@ -153,24 +147,16 @@ CLI **不会**自动执行回读验证。**你必须在文档写入完成后主
|
||||
| `--content -` | 从 stdin 读取(可配合 heredoc/pipe) |
|
||||
| `--content-file path` | 从文件读取(UTF-8),推荐 |
|
||||
|
||||
### 短/中等长度(< 200KB)— 单步写入
|
||||
### 单一原生命令(所有长度默认路径)
|
||||
|
||||
```bash
|
||||
# 1. 把内容写入 UTF-8 文本文件:
|
||||
# Linux/Mac: /tmp/<name>.md;Windows: %TEMP%\<name>.md
|
||||
# 2. 一步写入:
|
||||
# 2. 一次调用;CLI 按需自动分片:
|
||||
dws doc update --node <DOC_ID> --content-file <tmp> --mode overwrite --content-format markdown
|
||||
```
|
||||
|
||||
### 超长(> 200KB 兜底)— 分片追加
|
||||
|
||||
```bash
|
||||
# 1. 按 markdown 标题或段落边界切成 ≤200KB 的片段(不要切断表格)
|
||||
# 2. 逐个追加:
|
||||
dws doc update --node <nodeId> --content-file <part> --mode append --content-format markdown
|
||||
```
|
||||
|
||||
> **注意**:分块 append 存在静默失败风险(部分片段返回 success 但实际未写入),执行前**必须**向用户发出截断风险提示并等待确认。完整规范见 [`../../best_practices/04-document.md` «分块 append 截断风险提示»](../04-document.md)。
|
||||
只有命令返回 `CONTENT_TRUNCATED`、被中断或回读确认缺失时,才先读取已写入内容定位断点,再用一次 `doc update --mode append --content-file <missing.md>` 补缺失部分。不要在调用原生命令前复制一套手工分片循环。
|
||||
|
||||
### stdin 变体
|
||||
|
||||
|
||||
@@ -4,11 +4,9 @@
|
||||
|
||||
> 改写已有文档见 [doc-update-workflow.md](./doc-update-workflow.md)。排版规范见 [doc-style-guideline.md](./doc-style-guideline.md)。
|
||||
|
||||
## 前置必读
|
||||
## 按需使用
|
||||
|
||||
> **同时读取 [doc-style-guideline.md](./doc-style-guideline.md):**
|
||||
> - **§2.0 类型判断决策表** → 锁定文档类型(决策型 / 执行型 / 说明型 / 知识沉淀型)和骨架
|
||||
> - **§1 硬规则** → 全程生效(`--name` 已是 H1、不编造 URL、Markdown 草稿不写 callout 等)
|
||||
普通 Markdown 创建不需要先读本文件或 style guideline。只有用户要求设计文档骨架或复杂版式时,才查看下方对应章节;实际选择 JSONML 后再读取 cookbook。需要按文档类型选骨架时,按需读取 [doc-style-guideline.md](./doc-style-guideline.md) 的对应一节,不要通读。
|
||||
|
||||
### 关键词速查(用户意图 → 起稿路径)
|
||||
|
||||
@@ -45,7 +43,7 @@
|
||||
|
||||
| 项目 | 要求 |
|
||||
|------|------|
|
||||
| 标题 | 用 `--name` 传入;正文不要再重复同名一级标题 |
|
||||
| 标题 | 用 `--name` 传入;用户显式要求正文 H1 时原样保留,未要求时正文默认从 H2 开始 |
|
||||
| 位置 | 默认创建到我的文档;指定目录时只接受文档文件夹 `nodeId` 或 alidocs 文件夹 URL |
|
||||
| 正文 | 多行、表格、代码块、特殊字符或长度 >= 2KB 时必须写入 UTF-8 临时 `.md` 文件 |
|
||||
| 格式 | 按 §JSONML 起稿判定 决定起稿路径:命中 JSONML 起稿条件时**直接用 JSONML 构造**(跳过 markdown);未命中时用 Markdown 起稿,创建后按 [doc-update-workflow.md](./doc-update-workflow.md) 精修 |
|
||||
@@ -226,7 +224,7 @@ callout 可通过 `"showstk": true, "sticker": "图标名"` 配置顶部贴纸
|
||||
|
||||
> **脚手架策略警示**:Markdown 无法表达分栏/callout/色彩表头,拉回的 JSONML 只有纯文本骨架。精修阶段不是“在现有结构上加色”,而是“参照 RFC/Spec 重组结构”。
|
||||
|
||||
> **MUST READ**:动手写 JSONML 前,必须先用 Read 工具读取 [doc-jsonml-cookbook.md](../format/doc-jsonml-cookbook.md) — 其中 §决策型文档骨架范例 有可直接复制修改的完整模板。
|
||||
> **JSONML 条件加载**:仅在确定使用 JSONML 后读取 [doc-jsonml-cookbook.md](../format/doc-jsonml-cookbook.md);其中“决策型文档骨架范例”可直接改写。
|
||||
> 节点类型和属性的权威定义见 [doc-jsonml-schema.md](../format/doc-jsonml-schema.md)。
|
||||
|
||||
### ⚠️ JSONML 降级约束
|
||||
@@ -274,7 +272,7 @@ callout 可通过 `"showstk": true, "sticker": "图标名"` 配置顶部贴纸
|
||||
```
|
||||
|
||||
- 根节点固定 `"root"`(不是 `"body"`)
|
||||
- `--name` 已是 H1,JSONML 从 `h2` 开始
|
||||
- 用户未要求正文 H1 时,JSONML 从 `h2` 开始;用户明确要求“正文一级标题/插入一级标题”时必须构造 `h1`
|
||||
- 表格结构是 `table → tr → tc`(无 `th`/`td`)
|
||||
- 分栏是 `table` + `"sr": true`,`tc` 建议设 `fill` 背景色
|
||||
- 有序列表:仅第一项设 `"start": 1`,后续项不设 `start`(系统自动递增)
|
||||
@@ -315,7 +313,7 @@ dws doc read --node <nodeId> --content-format jsonml --output /tmp/<name>-readba
|
||||
- 只使用用户已提供或对话中已确认的正文素材。
|
||||
- 如果正文素材不足,先补齐文档目标、受众、章节和缺口;不要在本文中临时扩展跨产品采集流程。
|
||||
- **先按 [doc-style-guideline.md §2.0 类型判断决策表](./doc-style-guideline.md) 确定文档类型,再用对应类型的骨架样板(§2.1 决策型 / §2.2 执行型 / §2.3 说明型 / §2.4 知识沉淀型)**。不要套通用三段式。
|
||||
- **`--name` 已是 H1,正文从 `##` 开始**;正文内不要再写 `#` 一级标题(除非确实需要正文内再造一级 H1 并说明动机)。
|
||||
- **`--name` 是外壳标题,不覆盖显式正文 H1**:用户未要求正文一级标题时从 `##` 开始;用户明确给出 `# ...` 或要求“先起一级标题”时,正文必须保留该 H1。
|
||||
- 摘要、bullet、引用块、callout 等元素的使用边界以 style-guideline §3-§7 为准。
|
||||
- 同类信息保持一致:风险、状态、行动项各用一种元素 + 一种视觉语义(style-guideline §1.2 / §5)。
|
||||
- 临时文件必须保留真实换行,不能把换行写成字面量 `\n`。
|
||||
|
||||
@@ -25,7 +25,7 @@
|
||||
|
||||
## 一、硬规则
|
||||
|
||||
1. **`--name` 是 H1**:正文从 `##` 开始;正文内不写 `#`(除非确需正文内再造一级 H1 并说明动机)
|
||||
1. **用户显式正文 H1 优先**:`--name` 是文档外壳标题;用户未要求正文一级标题时从 `##` 开始。用户明确说“正文写 `# ...` / 先起一级标题 / 插入一级标题”时必须保留或插入真实 H1,不得用 `--name` 代替
|
||||
2. **同类信息同表达**:风险、状态、行动项、证据,每类只用一种元素 + 一种视觉语义(见 §5)
|
||||
3. **Markdown 草稿阶段只用稳定元素**:标题、段落、列表、checklist、表格、代码块;callout / 分栏 / 附件 / 复杂嵌套留到创建后用 `doc block insert` / `doc media insert` 精修
|
||||
4. **引用块只用于原文**:用户原话、会议摘录、外部材料原文;不许包装作者自己的结论
|
||||
@@ -209,7 +209,7 @@
|
||||
|
||||
### 4.1 标题与段落
|
||||
|
||||
- 正文从 `##` 开始(H1 已被 `--name` 占用)
|
||||
- 用户未要求正文 H1 时从 `##` 开始;用户明确要求正文 H1 时按原文使用 `#` 或 heading level 1
|
||||
- 标题层级 ≤ 4 层(§7)
|
||||
- 单段过长先拆段,再考虑换元素
|
||||
|
||||
@@ -217,6 +217,7 @@
|
||||
|
||||
- 普通列表:并列要点
|
||||
- 有序列表:顺序步骤
|
||||
- 用户明确要求“有序列表块”时必须使用真实列表结构;JSONML 为带 `list.isOrdered=true` 的多个 `p` 节点,不能只写带数字前缀的普通段落或以整篇 Markdown 代替显式 block insert
|
||||
- checklist:待办状态(含 `- [ ]` / `- [x]`)
|
||||
|
||||
列表项里开始出现"负责人 / 截止时间 / 状态"这类字段时,改用表格。
|
||||
|
||||
@@ -99,7 +99,7 @@ JSONML 模式下这些元素的节点结构见 [doc-jsonml-schema.md](../format/
|
||||
| 整篇按新骨架重写 | overwrite 全文(优先 JSONML;纯文本可用 markdown) | §4.5 |
|
||||
| 段落转表格 / 表格转段落 | block update --content-format jsonml;或 markdown overwrite 单段 | §4.4 / §4.1 |
|
||||
| 插入附件 / 图片 | doc media insert | §4.3 |
|
||||
| 一次追加 >200KB 内容 | 分块 append + 用户风险确认 + 逐片记录 | §4.6 |
|
||||
| 长 Markdown 追加 | 单一 `doc update --content-file`,由 CLI 自动分片;失败时再按真实断点恢复 | §4.6 |
|
||||
| 兜底:纯文本快速替换某段 | doc update --content overwrite(markdown) | §4.1 |
|
||||
|
||||
---
|
||||
@@ -254,17 +254,11 @@ dws doc update --node <nodeId> --content-file /tmp/<name>-full.md --mode overwri
|
||||
|
||||
**写入后必须回读**(§6)。如果发现旧内容残留,按 §6 的修复路径处理。
|
||||
|
||||
### 4.6 超长内容追加(分块 append)
|
||||
### 4.6 长 Markdown 追加与失败恢复
|
||||
|
||||
当一次性追加内容 **超过 200KB** 时,必须拆分为多片 `--mode append`,并在执行第一片**之前**向用户发出截断风险提示等待确认。
|
||||
默认只执行一条 `doc update --node <nodeId> --mode append --content-file <long.md>`。CLI 在超过 10,000 个 Unicode 字符时自动按结构分片,并在超时后缩小分片重试;不要预先手工拆片。
|
||||
|
||||
完整规范(提示话术模板、触发条件、失败处理)见 [04-document.md «分块 append 截断风险提示»](../../04-document.md)。
|
||||
|
||||
update 场景下的额外约束:
|
||||
|
||||
1. 按段落/标题边界切分,**禁止**在表格、代码块、列表内部截断
|
||||
2. 每写一片记录已写入的最后一个标题/段落标记,供 §6 回读比对
|
||||
3. 与既有内容衔接位置不能产生悬空标题或断列表
|
||||
只有返回 `CONTENT_TRUNCATED`、中断或 §6 回读发现缺失时,才按 [04-document.md «自动分片失败恢复»](../../04-document.md) 从最后一个真实完整段落补写缺失后缀。补写前报告已完成范围,补写后再次回读;同一缺失后缀最多自动校正一次。
|
||||
|
||||
---
|
||||
|
||||
|
||||
@@ -10,10 +10,11 @@
|
||||
| "参照这个生成同样的 / 按模板生成 / 复刻 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 是向已有文档写入内容,不是创建文件节点 |
|
||||
| "在这个知识库里写一篇/创建带内容的文档" | 在已知知识库根创建 adoc 并写入正文 | `doc create --workspace <workspaceId> --name ... --content-file ...` | 先建空 wiki 节点再重复写入 | `doc create` 的真实 Cobra/Schema 支持 `--workspace`,适合一步创建带内容 adoc |
|
||||
| "只在知识库里建一个空文档节点/整理节点层级" | 创建或管理知识库节点实体 | `wiki node create --type adoc` | `doc create` | 空节点与目录层级归 wiki;正文创作归 doc |
|
||||
| "帮我看看收到的日报" | 收到的日志 | `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 仅支持纯文本,需先提取文档内容 |
|
||||
| "帮我把这个文档翻译成日文" | 文档内容翻译 | 先 `doc read --node <nodeId>` 再 `chat text translate` | `chat text translate` 直接传文件 | translate 仅支持纯文本,需先读取 adoc 正文 |
|
||||
|
||||
@@ -1,181 +1,183 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
在指定目录创建文档并写入 Markdown 内容(一键完成)
|
||||
"""用原生 dws 写入管道创建文档,并回读验证。"""
|
||||
|
||||
用法:
|
||||
python doc_create_and_write.py \
|
||||
--name "项目周报" \
|
||||
--content "# 本周总结\n\n## 完成事项\n- 任务A"
|
||||
from __future__ import annotations
|
||||
|
||||
python doc_create_and_write.py \
|
||||
--name "会议纪要" \
|
||||
--content-file notes.md
|
||||
|
||||
python doc_create_and_write.py \
|
||||
--name "知识库文档" --content "# 内容" --folder FOLDER_ID
|
||||
|
||||
python doc_create_and_write.py --name "test" --content "hello" --dry-run
|
||||
"""
|
||||
|
||||
import sys
|
||||
import json
|
||||
import time
|
||||
import subprocess
|
||||
import argparse
|
||||
import json
|
||||
import shlex
|
||||
import subprocess
|
||||
import sys
|
||||
import tempfile
|
||||
from pathlib import Path
|
||||
from typing import List, Any, Optional
|
||||
from typing import Any, Optional, Sequence
|
||||
|
||||
|
||||
def run_dws(
|
||||
args: List[str], dry_run: bool = False,
|
||||
) -> Optional[Any]:
|
||||
cmd = ['dws'] + args
|
||||
class ScriptError(RuntimeError):
|
||||
"""可预期的脚本执行错误。"""
|
||||
|
||||
|
||||
def decode_json_output(output: str) -> Any:
|
||||
"""解析 JSON;兼容长内容写入前置的进度行。"""
|
||||
text = output.strip()
|
||||
if not text:
|
||||
raise ScriptError("dws 未返回 JSON")
|
||||
try:
|
||||
return json.loads(text)
|
||||
except json.JSONDecodeError:
|
||||
decoder = json.JSONDecoder()
|
||||
for offset, character in enumerate(text):
|
||||
if character not in "[{":
|
||||
continue
|
||||
try:
|
||||
value, end = decoder.raw_decode(text, offset)
|
||||
except json.JSONDecodeError:
|
||||
continue
|
||||
if not text[end:].strip():
|
||||
return value
|
||||
raise ScriptError("dws 返回的不是合法 JSON")
|
||||
|
||||
|
||||
def run_dws(args: Sequence[str], dry_run: bool = False) -> Any:
|
||||
"""执行一条 dws 命令,并把命令/业务失败统一转成 ScriptError。"""
|
||||
command = ["dws", *args]
|
||||
if dry_run:
|
||||
print(f"[dry-run] {' '.join(cmd)}")
|
||||
return {'dry_run': True}
|
||||
print(f"[dry-run] {shlex.join(command)}")
|
||||
return {"dry_run": True}
|
||||
try:
|
||||
result = subprocess.run(
|
||||
cmd, capture_output=True, text=True, timeout=60
|
||||
command,
|
||||
capture_output=True,
|
||||
text=True,
|
||||
timeout=120,
|
||||
check=False,
|
||||
)
|
||||
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
|
||||
except (subprocess.TimeoutExpired, FileNotFoundError) as exc:
|
||||
raise ScriptError(f"执行 dws 失败:{exc}") from exc
|
||||
if result.returncode != 0:
|
||||
detail = result.stderr.strip() or result.stdout.strip()
|
||||
raise ScriptError(
|
||||
f"dws 命令失败:{detail or f'退出码 {result.returncode}'}"
|
||||
)
|
||||
data = decode_json_output(result.stdout)
|
||||
if data is None or data == {}:
|
||||
raise ScriptError("dws 返回空业务结果,无法确认操作成功")
|
||||
if isinstance(data, dict) and data.get("success") is False:
|
||||
detail = data.get("errorMsg") or data.get("message") or "未知错误"
|
||||
raise ScriptError(f"dws 业务调用失败:{detail}")
|
||||
return data
|
||||
|
||||
|
||||
def run_dws_with_retry(
|
||||
args: List[str],
|
||||
dry_run: bool = False,
|
||||
max_retries: int = 3,
|
||||
retry_delay: float = 1.0,
|
||||
) -> Optional[Any]:
|
||||
"""带重试机制的 dws 命令执行"""
|
||||
last_error = None
|
||||
for attempt in range(1, max_retries + 1):
|
||||
result = run_dws(args, dry_run=dry_run)
|
||||
if result is not None:
|
||||
return result
|
||||
if attempt < max_retries:
|
||||
print(f" ⚠️ 第 {attempt} 次尝试失败,{retry_delay}秒后重试...")
|
||||
time.sleep(retry_delay)
|
||||
retry_delay *= 1.5 # 指数退避
|
||||
return None
|
||||
def first_value(payload: Any, keys: Sequence[str]) -> str:
|
||||
"""从嵌套响应中提取第一个非空稳定字段。"""
|
||||
if isinstance(payload, dict):
|
||||
for key in keys:
|
||||
value = payload.get(key)
|
||||
if value is not None and str(value).strip():
|
||||
return str(value).strip()
|
||||
for value in payload.values():
|
||||
found = first_value(value, keys)
|
||||
if found:
|
||||
return found
|
||||
elif isinstance(payload, list):
|
||||
for value in payload:
|
||||
found = first_value(value, keys)
|
||||
if found:
|
||||
return found
|
||||
return ""
|
||||
|
||||
|
||||
def main():
|
||||
def run(argv: Optional[Sequence[str]] = None) -> int:
|
||||
parser = argparse.ArgumentParser(
|
||||
description='创建文档并写入内容'
|
||||
description="使用 dws doc create 创建文档并回读验证"
|
||||
)
|
||||
parser.add_argument('--name', required=True, help='文档名称')
|
||||
parser.add_argument('--content', default='', help='Markdown 内容')
|
||||
parser.add_argument('--content-file', default='', help='内容文件')
|
||||
parser.add_argument('--folder', default='', help='目标文件夹 ID 或 URL')
|
||||
parser.add_argument('--workspace', default='', help='目标知识库 ID')
|
||||
parser.add_argument(
|
||||
'--mode', default='append', choices=['overwrite', 'append'],
|
||||
help='写入模式: overwrite=覆盖, append=追加 (默认 append)',
|
||||
parser.add_argument("--name", required=True, help="文档名称")
|
||||
content_group = parser.add_mutually_exclusive_group(required=True)
|
||||
content_group.add_argument("--content", help="Markdown 内容")
|
||||
content_group.add_argument("--content-file", help="UTF-8 Markdown 文件")
|
||||
location_group = parser.add_mutually_exclusive_group()
|
||||
location_group.add_argument(
|
||||
"--folder", default="", help="目标文档文件夹 ID 或 URL"
|
||||
)
|
||||
parser.add_argument(
|
||||
'--max-retries', type=int, default=3,
|
||||
help='每块写入失败时的最大重试次数 (默认 3)',
|
||||
location_group.add_argument(
|
||||
"--workspace", default="", help="目标知识库 ID 或 URL"
|
||||
)
|
||||
parser.add_argument('--dry-run', action='store_true')
|
||||
args = parser.parse_args()
|
||||
parser.add_argument("--dry-run", action="store_true")
|
||||
args = parser.parse_args(argv)
|
||||
|
||||
content = args.content
|
||||
supplied_path: Optional[Path] = None
|
||||
temporary_path: Optional[Path] = None
|
||||
if args.content_file:
|
||||
p = Path(args.content_file)
|
||||
if not p.exists():
|
||||
print(f"错误:文件不存在: {p}")
|
||||
sys.exit(1)
|
||||
content = p.read_text(encoding='utf-8')
|
||||
if not content:
|
||||
print('错误:需要 --content 或 --content-file')
|
||||
sys.exit(1)
|
||||
chunk_size = 10000
|
||||
supplied_path = Path(args.content_file)
|
||||
if not supplied_path.is_file():
|
||||
raise ScriptError(f"内容文件不存在:{supplied_path}")
|
||||
elif not args.content or not args.content.strip():
|
||||
raise ScriptError("--content 不能为空")
|
||||
|
||||
create_args = ['doc', 'create', '--name', args.name, '--format', 'json']
|
||||
if args.folder:
|
||||
create_args.extend(['--folder', args.folder])
|
||||
if args.workspace:
|
||||
create_args.extend(['--workspace', args.workspace])
|
||||
try:
|
||||
if supplied_path is None and not args.dry_run:
|
||||
with tempfile.NamedTemporaryFile(
|
||||
mode="w", encoding="utf-8", suffix=".md", delete=False
|
||||
) as handle:
|
||||
handle.write(args.content)
|
||||
temporary_path = Path(handle.name)
|
||||
supplied_path = temporary_path
|
||||
|
||||
print(f'\n📝 创建文档: {args.name}')
|
||||
create_data = run_dws(create_args, dry_run=args.dry_run)
|
||||
content_path = str(supplied_path) if supplied_path else "<TEMP_CONTENT.md>"
|
||||
create_args = [
|
||||
"doc", "create",
|
||||
"--name", args.name,
|
||||
"--content-file", content_path,
|
||||
"--content-format", "markdown",
|
||||
"--format", "json",
|
||||
]
|
||||
if args.folder:
|
||||
create_args.extend(["--folder", args.folder])
|
||||
if args.workspace:
|
||||
create_args.extend(["--workspace", args.workspace])
|
||||
|
||||
node_id = None
|
||||
if not args.dry_run:
|
||||
if not create_data:
|
||||
sys.exit(1)
|
||||
node_id = (create_data.get('nodeId')
|
||||
or create_data.get('dentryUuid')
|
||||
or create_data.get('id', ''))
|
||||
print(f" ✓ 文档已创建 (ID: {node_id})")
|
||||
created = run_dws(create_args, dry_run=args.dry_run)
|
||||
node_id = "<NODE_ID>" if args.dry_run else first_value(
|
||||
created, ("nodeId", "dentryUuid")
|
||||
)
|
||||
if not node_id:
|
||||
raise ScriptError("文档创建响应缺少 nodeId,无法验证")
|
||||
|
||||
if len(content) <= chunk_size:
|
||||
mode_label = '追加' if args.mode == 'append' else '覆盖'
|
||||
print(f'\n✍️ 写入内容 (模式: {mode_label}, {len(content)} 字符)...')
|
||||
write_data = run_dws([
|
||||
'doc', 'update',
|
||||
'--node', node_id or '<NODE_ID>',
|
||||
'--content', content,
|
||||
'--mode', args.mode,
|
||||
'--format', 'json',
|
||||
], dry_run=args.dry_run)
|
||||
if write_data:
|
||||
print(f" ✓ 内容已写入 ({len(content)} 字符)")
|
||||
else:
|
||||
chunks = []
|
||||
pos = 0
|
||||
while pos < len(content):
|
||||
end = min(pos + chunk_size, len(content))
|
||||
if end < len(content):
|
||||
newline_pos = content.rfind('\n', pos, end)
|
||||
if newline_pos > pos:
|
||||
end = newline_pos + 1
|
||||
chunks.append(content[pos:end])
|
||||
pos = end
|
||||
info = run_dws(
|
||||
["doc", "info", "--node", node_id, "--format", "json"],
|
||||
dry_run=args.dry_run,
|
||||
)
|
||||
readback = run_dws(
|
||||
["doc", "read", "--node", node_id, "--format", "json"],
|
||||
dry_run=args.dry_run,
|
||||
)
|
||||
if args.dry_run:
|
||||
return 0
|
||||
if not first_value(readback, ("markdown", "jsonml", "content")):
|
||||
raise ScriptError("文档回读未返回正文,无法确认写入成功")
|
||||
|
||||
total_chunks = len(chunks)
|
||||
print(f'\n✍️ 内容较长 ({len(content)} 字符), 分 {total_chunks} 块写入...')
|
||||
|
||||
success_chunks = 0
|
||||
for idx, chunk in enumerate(chunks):
|
||||
chunk_mode = args.mode if idx == 0 else 'append'
|
||||
write_data = run_dws_with_retry(
|
||||
[
|
||||
'doc', 'update',
|
||||
'--node', node_id or '<NODE_ID>',
|
||||
'--content', chunk,
|
||||
'--mode', chunk_mode,
|
||||
'--format', 'json',
|
||||
],
|
||||
dry_run=args.dry_run,
|
||||
max_retries=args.max_retries,
|
||||
)
|
||||
if write_data:
|
||||
print(f" ✓ 块 {idx + 1}/{total_chunks} 已写入 ({len(chunk)} 字符)")
|
||||
success_chunks += 1
|
||||
elif not args.dry_run:
|
||||
# 写入失败,报告部分写入状态
|
||||
print(f"\n❌ 块 {idx + 1}/{total_chunks} 写入失败(已重试 {args.max_retries} 次)")
|
||||
print(f"\n⚠️ 文档处于部分写入状态:")
|
||||
print(f" - 文档 ID: {node_id}")
|
||||
print(f" - 已写入: {success_chunks}/{total_chunks} 块")
|
||||
print(f" - 失败位置: 第 {idx + 1} 块")
|
||||
if args.mode == 'overwrite':
|
||||
print(f" - 模式: 覆盖模式,文档可能包含不完整内容")
|
||||
print(f" - 建议: 手动检查文档内容,或删除后重新创建")
|
||||
else:
|
||||
print(f" - 模式: 追加模式,已写入内容已保存")
|
||||
print(f" - 建议: 可手动补充剩余内容,或重新运行脚本")
|
||||
sys.exit(1)
|
||||
print('\n✅ 完成!')
|
||||
summary = {
|
||||
"success": True,
|
||||
"nodeId": node_id,
|
||||
"docUrl": first_value(info, ("docUrl", "documentUrl", "url"))
|
||||
or first_value(created, ("docUrl", "documentUrl", "url")),
|
||||
"chunksWritten": first_value(created, ("chunksWritten",)),
|
||||
"verified": True,
|
||||
}
|
||||
print(json.dumps(summary, ensure_ascii=False))
|
||||
return 0
|
||||
finally:
|
||||
if temporary_path is not None:
|
||||
temporary_path.unlink(missing_ok=True)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
def main() -> None:
|
||||
try:
|
||||
raise SystemExit(run())
|
||||
except ScriptError as exc:
|
||||
print(f"错误:{exc}", file=sys.stderr)
|
||||
raise SystemExit(1) from exc
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
|
||||
@@ -0,0 +1,208 @@
|
||||
import importlib.util
|
||||
import json
|
||||
import os
|
||||
import subprocess
|
||||
import sys
|
||||
import tempfile
|
||||
import unittest
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[2]
|
||||
SCRIPTS = ROOT / "skills" / "multi" / "dingtalk-aitable" / "scripts"
|
||||
|
||||
|
||||
def load_module(name: str, filename: str):
|
||||
spec = importlib.util.spec_from_file_location(name, SCRIPTS / filename)
|
||||
module = importlib.util.module_from_spec(spec)
|
||||
assert spec.loader is not None
|
||||
spec.loader.exec_module(module)
|
||||
return module
|
||||
|
||||
|
||||
IMPORT_RECORDS = load_module("aitable_import_records", "import_records.py")
|
||||
BULK_FIELDS = load_module("aitable_bulk_fields", "bulk_add_fields.py")
|
||||
UPLOAD_ATTACHMENT = load_module("aitable_upload_attachment", "upload_attachment.py")
|
||||
EXPORT_TASK = load_module("aitable_export_task", "aitable_export_via_task.py")
|
||||
IMPORT_TASK = load_module("aitable_import_task", "aitable_import_via_task.py")
|
||||
|
||||
|
||||
class AITableSkillScriptsTest(unittest.TestCase):
|
||||
def make_fake_dws(self, directory: Path, body: str) -> Path:
|
||||
script = directory / "fake-dws"
|
||||
script.write_text("#!/usr/bin/env python3\n" + body, encoding="utf-8")
|
||||
script.chmod(0o755)
|
||||
return script
|
||||
|
||||
def run_script(self, script: str, args, cwd: Path):
|
||||
env = os.environ.copy()
|
||||
env["OPENCLAW_WORKSPACE"] = str(cwd)
|
||||
return subprocess.run(
|
||||
[sys.executable, str(SCRIPTS / script), *map(str, args)],
|
||||
cwd=cwd,
|
||||
env=env,
|
||||
text=True,
|
||||
capture_output=True,
|
||||
)
|
||||
|
||||
def test_csv_values_remain_strings(self):
|
||||
with tempfile.TemporaryDirectory() as raw:
|
||||
root = Path(raw)
|
||||
source = root / "records.csv"
|
||||
source.write_text("fldPhone01,fldBool01\n00123,true\n", encoding="utf-8")
|
||||
previous = os.getcwd()
|
||||
os.chdir(root)
|
||||
try:
|
||||
records = IMPORT_RECORDS.load_records(str(source))
|
||||
finally:
|
||||
os.chdir(previous)
|
||||
self.assertEqual(records[0]["cells"]["fldPhone01"], "00123")
|
||||
self.assertEqual(records[0]["cells"]["fldBool01"], "true")
|
||||
|
||||
def test_import_records_checks_ids_and_readback(self):
|
||||
with tempfile.TemporaryDirectory() as raw:
|
||||
root = Path(raw)
|
||||
source = root / "records.json"
|
||||
source.write_text('[{"cells":{"fldText01":"值"}}]', encoding="utf-8")
|
||||
fake = self.make_fake_dws(
|
||||
root,
|
||||
"""import json, sys
|
||||
args = sys.argv[1:]
|
||||
if args[:3] == ['aitable', 'record', 'create']:
|
||||
print(json.dumps({'status':'success','data':{'newRecordIds':['rec12345678']}}))
|
||||
elif args[:3] == ['aitable', 'record', 'query']:
|
||||
print(json.dumps({'status':'success','data':{'records':[{'recordId':'rec12345678'}]}}))
|
||||
else:
|
||||
print(json.dumps({'status':'error','summary':'unexpected command'}))
|
||||
""",
|
||||
)
|
||||
result = self.run_script(
|
||||
"import_records.py",
|
||||
["base12345678", "table1234567", source, "--dws", fake],
|
||||
root,
|
||||
)
|
||||
self.assertEqual(result.returncode, 0, result.stderr)
|
||||
payload = json.loads(result.stdout)
|
||||
self.assertTrue(payload["complete"])
|
||||
self.assertEqual(payload["recordIds"], ["rec12345678"])
|
||||
self.assertEqual(payload["ledger"][0]["status"], "success")
|
||||
|
||||
def test_import_records_treats_exit_zero_business_error_as_failure(self):
|
||||
with tempfile.TemporaryDirectory() as raw:
|
||||
root = Path(raw)
|
||||
source = root / "records.json"
|
||||
source.write_text('[{"cells":{"fldText01":"值"}}]', encoding="utf-8")
|
||||
fake = self.make_fake_dws(
|
||||
root,
|
||||
"import json; print(json.dumps({'status':'error','summary':'denied'}))\n",
|
||||
)
|
||||
result = self.run_script(
|
||||
"import_records.py",
|
||||
["base12345678", "table1234567", source, "--dws", fake],
|
||||
root,
|
||||
)
|
||||
self.assertEqual(result.returncode, 2)
|
||||
payload = json.loads(result.stdout)
|
||||
self.assertFalse(payload["complete"])
|
||||
self.assertIn("业务失败", payload["ledger"][0]["error"])
|
||||
|
||||
def test_bulk_fields_preserves_ai_config_and_verifies(self):
|
||||
with tempfile.TemporaryDirectory() as raw:
|
||||
root = Path(raw)
|
||||
source = root / "fields.json"
|
||||
source.write_text(
|
||||
json.dumps([
|
||||
{
|
||||
"fieldName": "AI 摘要",
|
||||
"type": "text",
|
||||
"aiConfig": {"outputType": "text", "prompt": [{"type": "text", "value": "摘要"}]},
|
||||
}
|
||||
], ensure_ascii=False),
|
||||
encoding="utf-8",
|
||||
)
|
||||
fake = self.make_fake_dws(
|
||||
root,
|
||||
"""import json, sys
|
||||
args = sys.argv[1:]
|
||||
if args[:3] == ['aitable', 'field', 'create']:
|
||||
fields = json.loads(args[args.index('--fields') + 1])
|
||||
if 'aiConfig' not in fields[0]:
|
||||
print(json.dumps({'status':'error','summary':'aiConfig lost'}))
|
||||
else:
|
||||
print(json.dumps({'status':'success','data':{'results':[{'success':True,'fieldId':'field123456'}]}}))
|
||||
elif args[:3] == ['aitable', 'field', 'get']:
|
||||
print(json.dumps({'status':'success','data':{'fields':[{'fieldId':'field123456'}]}}))
|
||||
else:
|
||||
print(json.dumps({'status':'error','summary':'unexpected command'}))
|
||||
""",
|
||||
)
|
||||
result = self.run_script(
|
||||
"bulk_add_fields.py",
|
||||
["base12345678", "table1234567", source, "--dws", fake],
|
||||
root,
|
||||
)
|
||||
self.assertEqual(result.returncode, 0, result.stderr)
|
||||
payload = json.loads(result.stdout)
|
||||
self.assertTrue(payload["complete"])
|
||||
self.assertEqual(payload["fieldIds"], ["field123456"])
|
||||
|
||||
def test_bulk_fields_reports_partial_result(self):
|
||||
with tempfile.TemporaryDirectory() as raw:
|
||||
root = Path(raw)
|
||||
source = root / "fields.json"
|
||||
source.write_text(
|
||||
'[{"fieldName":"A","type":"text"},{"fieldName":"B","type":"text"}]',
|
||||
encoding="utf-8",
|
||||
)
|
||||
fake = self.make_fake_dws(
|
||||
root,
|
||||
"""import json, sys
|
||||
args = sys.argv[1:]
|
||||
if args[:3] == ['aitable', 'field', 'create']:
|
||||
print(json.dumps({'status':'success','data':{'results':[{'success':True,'fieldId':'field123456'},{'success':False,'reason':'duplicate'}]}}))
|
||||
elif args[:3] == ['aitable', 'field', 'get']:
|
||||
print(json.dumps({'status':'success','data':{'fields':[{'fieldId':'field123456'}]}}))
|
||||
""",
|
||||
)
|
||||
result = self.run_script(
|
||||
"bulk_add_fields.py",
|
||||
["base12345678", "table1234567", source, "--dws", fake],
|
||||
root,
|
||||
)
|
||||
self.assertEqual(result.returncode, 2)
|
||||
payload = json.loads(result.stdout)
|
||||
self.assertFalse(payload["complete"])
|
||||
self.assertEqual(payload["verifiedCount"], 1)
|
||||
self.assertEqual(payload["ledger"][1]["error"], "duplicate")
|
||||
|
||||
def test_upload_helpers_reject_plain_http_urls(self):
|
||||
with tempfile.TemporaryDirectory() as raw:
|
||||
file_path = Path(raw) / "file.bin"
|
||||
file_path.write_bytes(b"x")
|
||||
self.assertFalse(
|
||||
UPLOAD_ATTACHMENT.upload_to_oss(
|
||||
"http://example.com/upload", file_path, "application/octet-stream"
|
||||
)
|
||||
)
|
||||
ok, error = IMPORT_TASK.put_file("http://example.com/upload", file_path)
|
||||
self.assertFalse(ok)
|
||||
self.assertIn("HTTPS", error)
|
||||
|
||||
def test_export_helpers_reject_http_and_existing_output(self):
|
||||
with self.assertRaises(ValueError):
|
||||
EXPORT_TASK.normalize_download_url("http://example.com/file.xlsx")
|
||||
with tempfile.TemporaryDirectory() as raw:
|
||||
root = Path(raw)
|
||||
output = root / "existing.xlsx"
|
||||
output.write_bytes(b"existing")
|
||||
previous = os.getcwd()
|
||||
os.chdir(root)
|
||||
try:
|
||||
with self.assertRaises(ValueError):
|
||||
EXPORT_TASK.resolve_output_path("existing.xlsx", "ignored.xlsx", False)
|
||||
finally:
|
||||
os.chdir(previous)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
@@ -0,0 +1,309 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Regression tests for the Doc Skill and its optional wrapper script."""
|
||||
|
||||
import contextlib
|
||||
import importlib.util
|
||||
import io
|
||||
import json
|
||||
import subprocess
|
||||
import unittest
|
||||
from pathlib import Path
|
||||
from unittest import mock
|
||||
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[2]
|
||||
SKILL_ROOT = ROOT / "skills" / "multi" / "dingtalk-doc"
|
||||
SCRIPT_PATH = SKILL_ROOT / "scripts" / "doc_create_and_write.py"
|
||||
MONO_DOC_ROOT = ROOT / "skills" / "mono" / "references" / "products" / "doc"
|
||||
MONO_SCRIPT_PATH = ROOT / "skills" / "mono" / "scripts" / "doc_create_and_write.py"
|
||||
|
||||
|
||||
def load_script():
|
||||
spec = importlib.util.spec_from_file_location("doc_create_and_write", SCRIPT_PATH)
|
||||
if spec is None or spec.loader is None:
|
||||
raise RuntimeError(f"cannot load {SCRIPT_PATH}")
|
||||
module = importlib.util.module_from_spec(spec)
|
||||
spec.loader.exec_module(module)
|
||||
return module
|
||||
|
||||
|
||||
class DocSkillAlignmentTest(unittest.TestCase):
|
||||
def test_shortcuts_use_progressive_discovery(self):
|
||||
skill = (SKILL_ROOT / "SKILL.md").read_text(encoding="utf-8")
|
||||
catalog = json.loads(
|
||||
(ROOT / "docs" / "shortcut-public-catalog.json").read_text(
|
||||
encoding="utf-8"
|
||||
)
|
||||
)
|
||||
runtime_shortcuts = [
|
||||
row for row in catalog["results"] if row.get("service") == "doc"
|
||||
]
|
||||
schema = json.loads(
|
||||
(
|
||||
ROOT
|
||||
/ "internal"
|
||||
/ "cli"
|
||||
/ "schema_catalog"
|
||||
/ "tools"
|
||||
/ "doc.json"
|
||||
).read_text(encoding="utf-8")
|
||||
)
|
||||
schema_shortcuts = [
|
||||
path for path in schema["tools"] if path.startswith("doc.shortcut_")
|
||||
]
|
||||
self.assertEqual(17, len(runtime_shortcuts))
|
||||
self.assertEqual(17, len(schema_shortcuts))
|
||||
self.assertIn("17 条公开 Shortcut,已全部进入 Runtime Schema", skill)
|
||||
self.assertIn(
|
||||
"dws shortcut list --service doc --compact --format json", skill
|
||||
)
|
||||
self.assertNotIn("| `dws doc +", skill)
|
||||
|
||||
def test_native_pipeline_is_the_only_default_chunker(self):
|
||||
texts = [
|
||||
path.read_text(encoding="utf-8")
|
||||
for path in [SKILL_ROOT / "SKILL.md", *SKILL_ROOT.rglob("*.md")]
|
||||
]
|
||||
combined = "\n".join(texts)
|
||||
self.assertNotIn(">30000", combined)
|
||||
self.assertNotIn("超过 30000", combined)
|
||||
self.assertNotIn(">200KB", combined)
|
||||
self.assertNotIn("超过 200KB", combined)
|
||||
self.assertNotIn("doc get", combined)
|
||||
|
||||
def test_common_routes_do_not_require_recursive_reference_loading(self):
|
||||
skill = (SKILL_ROOT / "SKILL.md").read_text(encoding="utf-8")
|
||||
branch_names = [
|
||||
"doc-create.md",
|
||||
"doc-block.md",
|
||||
"doc-comment.md",
|
||||
"doc-import.md",
|
||||
"doc-export.md",
|
||||
"doc-media.md",
|
||||
"doc-read.md",
|
||||
"doc-update.md",
|
||||
"doc-info.md",
|
||||
]
|
||||
for name in branch_names:
|
||||
text = (
|
||||
SKILL_ROOT / "references" / "doc" / name
|
||||
).read_text(encoding="utf-8")
|
||||
self.assertNotIn("前置条件(MUST READ)", text, name)
|
||||
self.assertNotIn("必须先用 Read 工具读取以下文件", text, name)
|
||||
|
||||
self.assertIn("不超过 5 个确定性 DWS 操作", skill)
|
||||
self.assertIn("不创建 Todo", skill)
|
||||
self.assertIn("按“先/再/然后”切分操作阶段", skill)
|
||||
self.assertLessEqual(len(skill.encode("utf-8")), 9500)
|
||||
|
||||
def test_workflow_identity_and_fidelity_rules_are_explicit(self):
|
||||
skill = (SKILL_ROOT / "SKILL.md").read_text(encoding="utf-8")
|
||||
create_refs = "\n".join(
|
||||
path.read_text(encoding="utf-8")
|
||||
for path in [
|
||||
SKILL_ROOT / "references" / "doc" / "doc-create.md",
|
||||
MONO_DOC_ROOT / "doc-create.md",
|
||||
]
|
||||
)
|
||||
block_refs = "\n".join(
|
||||
path.read_text(encoding="utf-8")
|
||||
for path in [
|
||||
SKILL_ROOT / "references" / "doc" / "doc-block.md",
|
||||
MONO_DOC_ROOT / "doc-block.md",
|
||||
]
|
||||
)
|
||||
import_refs = "\n".join(
|
||||
path.read_text(encoding="utf-8")
|
||||
for path in [
|
||||
SKILL_ROOT / "references" / "doc" / "doc-import.md",
|
||||
MONO_DOC_ROOT / "doc-import.md",
|
||||
]
|
||||
)
|
||||
comment_refs = "\n".join(
|
||||
path.read_text(encoding="utf-8")
|
||||
for path in [
|
||||
SKILL_ROOT / "references" / "doc" / "doc-comment.md",
|
||||
MONO_DOC_ROOT / "doc-comment.md",
|
||||
]
|
||||
)
|
||||
|
||||
self.assertIn("不能覆盖用户显式要求的正文 H1", skill)
|
||||
self.assertIn("禁止先搜索同名文档", create_refs)
|
||||
self.assertIn("显式块操作不可折叠", block_refs)
|
||||
self.assertIn("list.isOrdered=true", block_refs)
|
||||
self.assertIn("在线编辑硬路由", import_refs)
|
||||
self.assertIn("update 后正文仍是旧值", comment_refs)
|
||||
self.assertIn("验证 12 条用例", skill)
|
||||
self.assertIn("部分完成/更新未生效", skill)
|
||||
|
||||
def test_schema_selection_preserves_doc_drive_boundaries(self):
|
||||
doc = json.loads(
|
||||
(
|
||||
ROOT / "internal" / "cli" / "schema_hints" / "selection" / "doc.json"
|
||||
).read_text(encoding="utf-8")
|
||||
)["tools"]
|
||||
drive = json.loads(
|
||||
(
|
||||
ROOT
|
||||
/ "internal"
|
||||
/ "cli"
|
||||
/ "schema_hints"
|
||||
/ "selection"
|
||||
/ "drive.json"
|
||||
).read_text(encoding="utf-8")
|
||||
)["tools"]
|
||||
|
||||
self.assertIn(
|
||||
"正文 H1",
|
||||
" ".join(doc["doc.create_document"]["use_when"]),
|
||||
)
|
||||
self.assertIn(
|
||||
"list.isOrdered=true",
|
||||
" ".join(doc["doc.insert_document_block"]["use_when"]),
|
||||
)
|
||||
self.assertIn(
|
||||
"null/空对象",
|
||||
" ".join(doc["doc.update_comment"]["avoid_when"]),
|
||||
)
|
||||
self.assertIn(
|
||||
"dws doc import",
|
||||
" ".join(drive["drive.upload"]["avoid_when"]),
|
||||
)
|
||||
|
||||
def test_mono_and_multi_wrappers_stay_aligned(self):
|
||||
self.assertEqual(
|
||||
SCRIPT_PATH.read_text(encoding="utf-8"),
|
||||
MONO_SCRIPT_PATH.read_text(encoding="utf-8"),
|
||||
)
|
||||
|
||||
|
||||
class DocCreateAndWriteTest(unittest.TestCase):
|
||||
def setUp(self):
|
||||
self.module = load_script()
|
||||
|
||||
def test_decode_json_output_accepts_progress_lines(self):
|
||||
data = self.module.decode_json_output(
|
||||
'[INFO] 写入分片 (1/2)\n{\n "success": true,\n'
|
||||
' "nodeId": "doc-1"\n}\n'
|
||||
)
|
||||
self.assertEqual("doc-1", data["nodeId"])
|
||||
|
||||
def test_folder_and_workspace_are_mutually_exclusive(self):
|
||||
with self.assertRaises(SystemExit):
|
||||
self.module.run(
|
||||
[
|
||||
"--name",
|
||||
"周报",
|
||||
"--content",
|
||||
"hello",
|
||||
"--folder",
|
||||
"folder-1",
|
||||
"--workspace",
|
||||
"workspace-1",
|
||||
"--dry-run",
|
||||
]
|
||||
)
|
||||
|
||||
def test_run_dws_rejects_nonzero_and_business_failure(self):
|
||||
with mock.patch.object(
|
||||
self.module.subprocess,
|
||||
"run",
|
||||
return_value=subprocess.CompletedProcess(
|
||||
["dws"], 2, stdout="", stderr="unknown flag"
|
||||
),
|
||||
):
|
||||
with self.assertRaisesRegex(self.module.ScriptError, "unknown flag"):
|
||||
self.module.run_dws(["doc", "create"])
|
||||
|
||||
with mock.patch.object(
|
||||
self.module.subprocess,
|
||||
"run",
|
||||
return_value=subprocess.CompletedProcess(
|
||||
["dws"],
|
||||
0,
|
||||
stdout='{"success":false,"message":"denied"}',
|
||||
stderr="",
|
||||
),
|
||||
):
|
||||
with self.assertRaisesRegex(self.module.ScriptError, "denied"):
|
||||
self.module.run_dws(["doc", "create"])
|
||||
|
||||
with mock.patch.object(
|
||||
self.module.subprocess,
|
||||
"run",
|
||||
return_value=subprocess.CompletedProcess(
|
||||
["dws"], 0, stdout="null", stderr=""
|
||||
),
|
||||
):
|
||||
with self.assertRaisesRegex(self.module.ScriptError, "空业务结果"):
|
||||
self.module.run_dws(["doc", "create"])
|
||||
|
||||
def test_wrapper_uses_create_then_info_and_read_without_manual_update(self):
|
||||
calls = []
|
||||
|
||||
def fake_run(args, dry_run=False):
|
||||
calls.append(list(args))
|
||||
if args[:2] == ["doc", "create"]:
|
||||
return {"success": True, "nodeId": "doc-1", "chunksWritten": 2}
|
||||
if args[:2] == ["doc", "info"]:
|
||||
return {"success": True, "docUrl": "https://example.test/doc-1"}
|
||||
return {"success": True, "markdown": "hello"}
|
||||
|
||||
stdout = io.StringIO()
|
||||
with mock.patch.object(self.module, "run_dws", side_effect=fake_run):
|
||||
with contextlib.redirect_stdout(stdout):
|
||||
code = self.module.run(["--name", "周报", "--content", "hello"])
|
||||
|
||||
self.assertEqual(0, code)
|
||||
self.assertEqual(["create", "info", "read"], [call[1] for call in calls])
|
||||
self.assertFalse(any(call[:2] == ["doc", "update"] for call in calls))
|
||||
summary = json.loads(stdout.getvalue().splitlines()[-1])
|
||||
self.assertEqual("doc-1", summary["nodeId"])
|
||||
self.assertTrue(summary["verified"])
|
||||
|
||||
def test_wrapper_preserves_explicit_body_h1(self):
|
||||
seen_content = []
|
||||
|
||||
def fake_run(args, dry_run=False):
|
||||
if args[:2] == ["doc", "create"]:
|
||||
content_path = Path(args[args.index("--content-file") + 1])
|
||||
seen_content.append(content_path.read_text(encoding="utf-8"))
|
||||
return {"success": True, "nodeId": "doc-1", "chunksWritten": 1}
|
||||
if args[:2] == ["doc", "info"]:
|
||||
return {"success": True, "docUrl": "https://example.test/doc-1"}
|
||||
return {"success": True, "markdown": "# 周报"}
|
||||
|
||||
with mock.patch.object(self.module, "run_dws", side_effect=fake_run):
|
||||
with contextlib.redirect_stdout(io.StringIO()):
|
||||
code = self.module.run(["--name", "周报", "--content", "# 周报"])
|
||||
|
||||
self.assertEqual(0, code)
|
||||
self.assertEqual(["# 周报"], seen_content)
|
||||
|
||||
def test_wrapper_rejects_empty_readback(self):
|
||||
def fake_run(args, dry_run=False):
|
||||
if args[:2] == ["doc", "create"]:
|
||||
return {"success": True, "nodeId": "doc-1"}
|
||||
if args[:2] == ["doc", "info"]:
|
||||
return {"success": True, "docUrl": "https://example.test/doc-1"}
|
||||
return {"success": True, "markdown": ""}
|
||||
|
||||
with mock.patch.object(self.module, "run_dws", side_effect=fake_run):
|
||||
with self.assertRaisesRegex(self.module.ScriptError, "回读未返回正文"):
|
||||
self.module.run(["--name", "周报", "--content", "hello"])
|
||||
|
||||
def test_dry_run_shows_create_and_verification_commands(self):
|
||||
stdout = io.StringIO()
|
||||
with contextlib.redirect_stdout(stdout):
|
||||
code = self.module.run(
|
||||
["--name", "周报", "--content", "hello", "--dry-run"]
|
||||
)
|
||||
self.assertEqual(0, code)
|
||||
output = stdout.getvalue()
|
||||
self.assertIn("dws doc create", output)
|
||||
self.assertIn("dws doc info", output)
|
||||
self.assertIn("dws doc read", output)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
@@ -0,0 +1,30 @@
|
||||
package unit_test
|
||||
|
||||
import (
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestAITableSkillPythonScripts(t *testing.T) {
|
||||
python, err := exec.LookPath("python3")
|
||||
if err != nil {
|
||||
t.Skip("python3 is not installed")
|
||||
}
|
||||
|
||||
_, filename, _, ok := runtime.Caller(0)
|
||||
if !ok {
|
||||
t.Fatal("runtime.Caller(0) failed")
|
||||
}
|
||||
root := filepath.Clean(filepath.Join(filepath.Dir(filename), "..", ".."))
|
||||
cmd := exec.Command(
|
||||
python,
|
||||
"test/scripts/aitable_skill_scripts_test.py",
|
||||
)
|
||||
cmd.Dir = root
|
||||
output, err := cmd.CombinedOutput()
|
||||
if err != nil {
|
||||
t.Fatalf("AI Table skill Python tests failed: %v\n%s", err, output)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user