Compare commits

...
6 Commits
Author SHA1 Message Date
修雨 7a868ddf39 docs(help): document --contents.key=field_name and mark required flags (#106 #107) (#217)
- chat message send-by-bot --robot-code/--title/--text: append (必填) marker
  (RunE already enforces these as required; help text was missing the tag,
  causing downstream MCP wrappers to generate schemas with required fields
  missing).
- report create --contents: description appended "key must exactly equal the
  template field_name (look it up via report template detail --name <template>)";
  Long now warns about the API's SYSTEM_ERROR on key mismatch; Examples
  rewritten as a two-step pipeline (template detail → create).

Closes #106
Closes #107
2026-05-04 17:57:23 +08:00
ybc❤️zyrand猷诺 89d7c5f11b docs: clarify message list pagination to prevent nextCursor misuse (fixes #195) (#218)
* docs: clarify message list pagination to prevent nextCursor misuse (fixes #195)

- Add detailed pagination walkthrough section for 'message list' in chat.md
- Add comparison table distinguishing 'message list' (--time with createTime)
  from 'message list-all' (--cursor with nextCursor)
- Add warning about common error: using nextCursor as --time value
  causes infinite loop returning same page

* docs: fix --time required label and add --forward misuse warning (fixes #195)

---------

Co-authored-by: 猷诺 <bicheng.ybc@alibaba-inc.com>
2026-05-04 16:38:09 +08:00
ybc❤️zyrand猷诺 efb61cae02 fix: restore unconditional MCP re-fetch in login to prevent stale clientId errors (#213)
PR #184 merge conflict resolution inadvertently introduced a conditional
branch that preserved stale clientID from previous login sessions instead
of always re-fetching from MCP server. This caused exchangeCode() to use
direct DingTalk API mode (which requires clientSecret) instead of MCP
proxy mode, resulting in 'clientId或者clientSecret错误' errors.

This commit restores the original PR #157 logic: both DeviceFlowProvider
and OAuthProvider unconditionally call resetCredentialState() followed by
FetchClientIDFromMCP() + SetClientIDFromMCP(), ensuring exchangeCode()
always uses the MCP proxy path regardless of prior login state.

Root cause: the conditional branch treated any non-empty clientID (from
runtimeClientID or app.json) as a user-provided --client-id flag value,
skipping MCP re-fetch and leaving clientIDFromMCP=false after reset.

Affected scenarios:
- OAuth login → device flow login
- Any login → --force login
- New terminal with existing app.json → device flow login

Fixes regression introduced in PR #184 (commit 46192d6).
Restores fix from PR #157 (commit ad33a46).

Co-authored-by: 猷诺 <bicheng.ybc@alibaba-inc.com>
2026-05-04 09:26:25 +08:00
ybc❤️zyrand猷诺 fb88c6ace9 docs(todo): 增强待办命令帮助文本,补充字段语义说明 (#205)
基于实际 API 返回验证,所有描述均经过 dws 命令实测确认。

- todo task get: 新增 Long 说明
  · creatorId/executorIds/participantIds/modifierId 是待办内部短数字 ID
    (如 6380165826),非通讯录 userId (如 035551044606950179),已实测确认
  · 提示 creatorInfo/executorInfos/participantInfos 包含 name 属性可获取姓名
  · bizTag/source 返回 teambition 是引擎实现标识,已实测确认
  · tenantId 非 corpId,补充 tenantType 维度说明
- todo task list: 新增 Long 说明
  · 覆盖范围: 仅返回执行者维度待办,不含仅参与/仅创建的待办
  · 仅限个人待办,不含 OA 审批流/Teambition 项目任务
  · 自动分页说明
- todo task create: --executors flag 描述增强
  · 明确此处 userId 是通讯录 userId 并提示查询方式

Fixes #105

Co-authored-by: 猷诺 <bicheng.ybc@alibaba-inc.com>
2026-05-04 09:26:12 +08:00
ybc❤️zyrand猷诺 5258959a14 docs: update contact search examples from --keyword to --query (#209)
The --keyword flag was renamed to --query for contact user search,
contact dept search, and devdoc article search (as noted in CHANGELOG).
This commit updates all documentation references for the contact
commands. devdoc will be addressed in a follow-up PR.

Refs #105

Co-authored-by: 猷诺 <bicheng.ybc@alibaba-inc.com>
2026-04-30 16:32:34 +08:00
xianfeng wangandgithub-actions[bot] c515fc1001 1.0.19 CHANGELOG (#204)
* chore: update coverage badge [skip ci]

* chore: update coverage badge [skip ci]

* chore: update coverage badge [skip ci]

* chore: update coverage badge [skip ci]

* chore: update coverage badge [skip ci]

* 1.0.19 changelog

---------

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-04-30 14:36:58 +08:00
14 changed files with 169 additions and 82 deletions
+1 -1
View File
@@ -1 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="108" height="20" role="img" aria-label="coverage: 52.8%"><title>coverage: 52.8%</title><linearGradient id="s" x2="0" y2="100%"><stop offset="0" stop-color="#bbb" stop-opacity=".1"/><stop offset="1" stop-opacity=".1"/></linearGradient><clipPath id="r"><rect width="108" height="20" rx="3" fill="#fff"/></clipPath><g clip-path="url(#r)"><rect width="61" height="20" fill="#555"/><rect x="61" width="47" height="20" fill="#e05d44"/><rect width="108" height="20" fill="url(#s)"/></g><g fill="#fff" text-anchor="middle" font-family="Verdana,Geneva,DejaVu Sans,sans-serif" text-rendering="geometricPrecision" font-size="110"><text aria-hidden="true" x="315" y="150" fill="#010101" fill-opacity=".3" transform="scale(.1)" textLength="510">coverage</text><text x="315" y="140" transform="scale(.1)" fill="#fff" textLength="510">coverage</text><text aria-hidden="true" x="835" y="150" fill="#010101" fill-opacity=".3" transform="scale(.1)" textLength="370">52.8%</text><text x="835" y="140" transform="scale(.1)" fill="#fff" textLength="370">52.8%</text></g></svg>
<svg xmlns="http://www.w3.org/2000/svg" width="108" height="20" role="img" aria-label="coverage: 54.2%"><title>coverage: 54.2%</title><linearGradient id="s" x2="0" y2="100%"><stop offset="0" stop-color="#bbb" stop-opacity=".1"/><stop offset="1" stop-opacity=".1"/></linearGradient><clipPath id="r"><rect width="108" height="20" rx="3" fill="#fff"/></clipPath><g clip-path="url(#r)"><rect width="61" height="20" fill="#555"/><rect x="61" width="47" height="20" fill="#e05d44"/><rect width="108" height="20" fill="url(#s)"/></g><g fill="#fff" text-anchor="middle" font-family="Verdana,Geneva,DejaVu Sans,sans-serif" text-rendering="geometricPrecision" font-size="110"><text aria-hidden="true" x="315" y="150" fill="#010101" fill-opacity=".3" transform="scale(.1)" textLength="510">coverage</text><text x="315" y="140" transform="scale(.1)" fill="#fff" textLength="510">coverage</text><text aria-hidden="true" x="835" y="150" fill="#010101" fill-opacity=".3" transform="scale(.1)" textLength="370">54.2%</text><text x="835" y="140" transform="scale(.1)" fill="#fff" textLength="370">54.2%</text></g></svg>

Before

Width:  |  Height:  |  Size: 1.1 KiB

After

Width:  |  Height:  |  Size: 1.1 KiB

+34
View File
@@ -4,6 +4,40 @@ All notable changes to this project will be documented in this file.
The format is inspired by [Keep a Changelog](https://keepachangelog.com/) and this project follows [Semantic Versioning](https://semver.org/).
## [1.0.19] - 2026-04-30
Discovery hardening for edition overlays: `edition.SupplementServers` / `FallbackServers` hooks now consistently surface through the **runtime catalog loader**, not just the static command tree, so overlay products that live outside the Portal envelope (e.g. Wukong gray-release `conference`) resolve an endpoint on both the cold-cache and tool-not-in-catalog paths. Ships with per-edition cache partitioning to stop cross-edition disk-cache leakage, plus a small todo fix.
### Added
- **`pkg/config.EditionPartition(name)`** (#197) — returns the cache partition key for a given edition. Open-source core (`""` / `"open"`) keeps using `DefaultPartition` (`default/default`); every other edition gets its own namespace (`<edition>/default`), preventing cross-edition data leakage in the shared `~/.dws` disk cache. Lives in `pkg/config` as a leaf helper so `internal/cli`, `internal/app`, and `internal/cache` can all call it without risking import cycles.
- **`internal/editionmerge` shared package** (#197) — single source of truth for converting `edition.ServerInfo` into `market.ServerDescriptor` (`ToDescriptor`) and for merging `SupplementServers` / `FallbackServers` into a descriptor list. Both `internal/cli` (command tree) and `internal/app` (runtime catalog) now apply the edition hooks against the same discovery pipeline.
### Changed
- **`EnvironmentLoader.loadFromCache` honors `SupplementServers` even on empty registry** (#197) — when the Portal registry cache is missing or empty, the catalog loader still materialises the edition's `SupplementServers` as endpoint-only `discovery.RuntimeServer` entries (source: `edition_supplement`), so hardcoded overlay commands for supplement-only products can still resolve an endpoint via the catalog path. Previously `loadFromCache` short-circuited to an empty catalog whenever the registry snapshot was empty, silently dropping gray-release products.
- **Cache loader switches from `DefaultPartition` to `EditionPartition(edition.Get().Name)`** (#197) — the runtime catalog, registry snapshot, and tools snapshot are now partitioned per edition instead of all editions sharing `default/default`.
- **`loadFromCache` appends supplement servers alongside fresh-cache servers** (#197) — supplement entries whose `CLI.ID` / `Key` are already present in the cached registry are skipped, so the hook never shadows Portal-published servers; only new products are added.
- **`runtimeRunner.Run` falls through to `directRuntimeEndpoint` for supplement products** (#197) — when the catalog contains the product (e.g. supplied by `SupplementServers`) but the specific tool is not declared, the runner now trusts `directRuntimeEndpoint` to resolve a working endpoint for the tool before returning the explicit catalog-miss error. Supplement entries intentionally carry no tool list, so this is the path that makes overlay-only tools executable.
- **Legacy `mergeSupplementServers` / `fallbackToDescriptors` moved out of `internal/app/legacy.go`** (#197) — relocated into `internal/editionmerge` and reused by the catalog loader, eliminating the duplicate `edition.ServerInfo → market.ServerDescriptor` logic that previously only ran on the static command-tree path.
### Fixed
- **`dws todo task get` returns empty** (#202) — the helper was calling `query_todo_detail`, which is not a valid MCP tool and returns empty. Switched to `get_todo_detail` as declared in `discovery.json`, restoring correct task-detail behaviour.
- **Conference and other Wukong gray-release products miss endpoint on cold cache** (#197) — products registered only via `edition.SupplementServers` (not yet in the Portal envelope) now resolve an endpoint through the catalog path in both cold-start and tool-not-declared scenarios.
### Tests
- `internal/editionmerge/merge_test.go` — descriptor conversion + supplement/fallback merge semantics.
- `internal/cli/loader_partition_test.go` + `loader_supplement_test.go` — edition-partitioned cache reads and supplement hook surfacing from `loadFromCache` (including empty-registry cold path and existing-ID deduplication).
- `internal/app/legacy_wukong_partition_e2e_test.go` — end-to-end cache partition isolation for the Wukong edition.
- `internal/app/runner_supplement_fallback_test.go` — runner falls through to `directRuntimeEndpoint` when the tool isn't declared by a supplement-sourced catalog entry.
- `pkg/config/constants_test.go` — `EditionPartition` name handling (`""`, `"open"`, custom edition).
### Docs
- **CHANGELOG v1.0.18 rewrite** (#193) — previous release notes expanded to call out the PAT host-owned A-core flow, exit-code contract change (auth `4`, Discovery/cache/protocol `6`), `dws pat chmod` / `pat browser-policy` entry points, stderr-JSON classifier updates, and host-control metadata injection.
## [1.0.18] - 2026-04-28
Raw DingTalk OpenAPI access lands as a new `dws api` surface for both `api.dingtalk.com` and `oapi.dingtalk.com`, backed by app-level token caching and guarded host allowlists. PAT enters the host-owned **A-core** loop: agent hosts can own authorization UI through `DINGTALK_DWS_AGENTCODE`, parse single-line stderr JSON, call `dws pat chmod`, and replay the original command. Chat helper regressions are fixed, skill references are brought back in line with shipped commands, and the v1.0.17 Mail release notes are backfilled into README / CHANGELOG.
+2 -2
View File
@@ -207,7 +207,7 @@ dws is designed as an AI-native CLI. Complete [Installation](#installation) and
dws todo task create --title "Review PR" --executors "<your-userId>" --yes
# Use --dry-run to preview operations (safe execution)
dws contact user search --keyword "engineering" --dry-run
dws contact user search --query "engineering" --dry-run
# Use --jq to extract precisely (save tokens)
dws contact user get-self --jq '.result[0].orgEmployeeModel | {name: .orgUserName, dept: .depts[0].deptName, userId}'
@@ -332,7 +332,7 @@ Built-in pipeline engine that normalizes flag names, splits sticky arguments, an
dws aitable record query --baseId BASE_ID --tableId TABLE_ID # auto-corrected to --base-id --table-id
# Sticky argument splitting
dws contact user search --keyword "engineering" --timeout30 # auto-split to --timeout 30
dws contact user search --query "engineering" --timeout30 # auto-split to --timeout 30
# Fuzzy flag name matching
dws aitable record query --base-id BASE_ID --tabel-id TABLE_ID # --tabel-id -> --table-id
+2 -2
View File
@@ -207,7 +207,7 @@ dws 是为 AI Agent 设计的 CLI 工具。请先完成[安装](#安装)和[开
dws todo task create --title "Review PR" --executors "<your-userId>" --yes
# 使用 --dry-run 预览操作(安全执行)
dws contact user search --keyword "张三" --dry-run
dws contact user search --query "张三" --dry-run
# 使用 --jq 精确提取(节省 token)
dws contact user get-self --jq '.result[0].orgEmployeeModel | {name: .orgUserName, dept: .depts[0].deptName, userId}'
@@ -332,7 +332,7 @@ dws api GET /v1.0/microApp/allApps --jq '.agentId' # jq 过滤
dws aitable record query --baseId BASE_ID --tableId TABLE_ID # 自动纠正为 --base-id --table-id
# 粘连参数自动拆分
dws contact user search --keyword "张三" --timeout30 # 自动拆分为 --timeout 30
dws contact user search --query "张三" --timeout30 # 自动拆分为 --timeout 30
# 拼写错误模糊匹配
dws aitable record query --base-id BASE_ID --tabel-id TABLE_ID # --tabel-id → --table-id
+4 -4
View File
@@ -30,9 +30,9 @@ With `-f json`, error responses include structured payloads: `category`, `reason
## Output Formats / 输出格式
```bash
dws contact user search --keyword "Alice" -f table # Table (default, human-friendly / 表格,默认)
dws contact user search --keyword "Alice" -f json # JSON (for agents and piping / 适合 agent)
dws contact user search --keyword "Alice" -f raw # Raw API response / 原始响应
dws contact user search --query "Alice" -f table # Table (default, human-friendly / 表格,默认)
dws contact user search --query "Alice" -f json # JSON (for agents and piping / 适合 agent)
dws contact user search --query "Alice" -f raw # Raw API response / 原始响应
dws schema -f pretty ding.send_ding_message # Pretty (ANSI-colored, schema-aware / 彩色分区,专为 schema 设计)
```
@@ -45,7 +45,7 @@ dws todo task list --dry-run # Preview MCP call without executing / 预览但
## Output to File / 输出到文件
```bash
dws contact user search --keyword "Alice" -o result.json
dws contact user search --query "Alice" -o result.json
```
## Schema Introspection / Schema 查询
+14 -23
View File
@@ -161,31 +161,22 @@ func (p *DeviceFlowProvider) resetCredentialState() {
}
func (p *DeviceFlowProvider) Login(ctx context.Context) (*TokenData, error) {
// Defensive reset: clear stale credential state from previous login methods,
// but preserve user-provided --client-id if present.
userClientID := p.clientID
// Defensive reset: clear any stale credential state from previous login
// methods (OAuth scan, PAT, etc.) so we always re-fetch from MCP.
// This ensures --device login works regardless of what app.json contains.
p.resetCredentialState()
if userClientID != "" && userClientID != DefaultClientID {
// User provided --client-id flag: use it directly, skip MCP fetch.
p.clientID = userClientID
if p.logger != nil {
p.logger.Debug("using user-provided client ID, skipping MCP fetch", "clientID", userClientID)
}
} else {
// No user-provided client ID: fetch from MCP server.
if p.logger != nil {
p.logger.Debug("fetching client ID from MCP server")
}
mcpClientID, mcpErr := FetchClientIDFromMCP(ctx)
if mcpErr != nil {
return nil, fmt.Errorf("%s: %w", i18n.T("获取 Client ID 失败"), mcpErr)
}
p.clientID = mcpClientID
SetClientIDFromMCP(mcpClientID)
if p.logger != nil {
p.logger.Debug("fetched client ID from MCP server", "clientID", mcpClientID)
}
if p.logger != nil {
p.logger.Debug("fetching client ID from MCP server (device flow always re-fetches)")
}
mcpClientID, mcpErr := FetchClientIDFromMCP(ctx)
if mcpErr != nil {
return nil, fmt.Errorf("%s: %w", i18n.T("获取 Client ID 失败"), mcpErr)
}
p.clientID = mcpClientID
SetClientIDFromMCP(mcpClientID)
if p.logger != nil {
p.logger.Debug("fetched client ID from MCP server", "clientID", mcpClientID)
}
const maxAttempts = 3
+14 -23
View File
@@ -109,31 +109,22 @@ func (p *OAuthProvider) Login(ctx context.Context, force bool) (*TokenData, erro
}
// Fall through: full browser OAuth flow.
// Defensive reset: clear stale credential state from previous login methods,
// but preserve user-provided --client-id if present.
userClientID := p.clientID
// Defensive reset: clear any stale credential state from previous login
// methods so we always re-fetch clientID from MCP. This ensures
// --force login works regardless of what app.json contains.
p.resetCredentialState()
if userClientID != "" && userClientID != DefaultClientID {
// User provided --client-id flag: use it directly, skip MCP fetch.
p.clientID = userClientID
if p.logger != nil {
p.logger.Debug("using user-provided client ID, skipping MCP fetch", "clientID", userClientID)
}
} else {
// No user-provided client ID: fetch from MCP server.
if p.logger != nil {
p.logger.Debug("fetching client ID from MCP server")
}
mcpClientID, mcpErr := FetchClientIDFromMCP(ctx)
if mcpErr != nil {
return nil, fmt.Errorf("%s: %w", i18n.T("获取 Client ID 失败"), mcpErr)
}
p.clientID = mcpClientID
SetClientIDFromMCP(mcpClientID)
if p.logger != nil {
p.logger.Debug("fetched client ID from MCP server", "clientID", mcpClientID)
}
if p.logger != nil {
p.logger.Debug("fetching client ID from MCP server (OAuth flow always re-fetches)")
}
mcpClientID, mcpErr := FetchClientIDFromMCP(ctx)
if mcpErr != nil {
return nil, fmt.Errorf("%s: %w", i18n.T("获取 Client ID 失败"), mcpErr)
}
p.clientID = mcpClientID
SetClientIDFromMCP(mcpClientID)
if p.logger != nil {
p.logger.Debug("fetched client ID from MCP server", "clientID", mcpClientID)
}
// Find a free port for the callback server.
+3 -3
View File
@@ -252,9 +252,9 @@ func newChatMessageSendByBotCommand(runner executor.Runner) *cobra.Command {
preferLegacyLeaf(cmd)
cmd.Flags().String("group", "", "群会话 openConversationId (群聊必填)")
cmd.Flags().String("robot-code", "", "机器人 Code")
cmd.Flags().String("text", "", "消息内容 (Markdown)")
cmd.Flags().String("title", "", "消息标题")
cmd.Flags().String("robot-code", "", "机器人 Code (必填)")
cmd.Flags().String("text", "", "消息内容 Markdown (必填)")
cmd.Flags().String("title", "", "消息标题 (必填)")
cmd.Flags().String("users", "", "接收者 userId 列表,逗号分隔,最多 20 个 (单聊必填)")
return cmd
}
+13 -3
View File
@@ -204,8 +204,18 @@ func newReportCreateCommand(runner executor.Runner) *cobra.Command {
Use: "create",
Short: "创建日志",
Long: `按模版创建一条日志。--contents 为 JSON 数组,每项需含 key、sort、content、contentType、type,
与远程 create_report 一致;可先通过 report template list / template detail 取得 templateId 与控件定义。`,
Example: ` dws report create --template-id TPL_ID --contents '[{"content":"完成开发","sort":"0","key":"今日完成","contentType":"markdown","type":"1"}]'
与远程 create_report 一致;可先通过 report template list / template detail 取得 templateId 与控件定义。
注意:每个 contents 项的 key 必须精确等于模板的 field_name(中文/英文逐字匹配,不是控件 ID 或别名)。
key 与 field_name 不一致时钉钉 API 会返回 SYSTEM_ERROR (success=false),CLI 层不会预先拦截。
请先用 report template detail 查到 report_template_fields[].field_name 后再填。`,
Example: ` # Step 1:查模板,取 report_template_fields[].field_name 当作 contents[].key
dws report template detail --name "周报"
# Step 2:用上一步拿到的 field_name 作为 key 创建日志
dws report create --template-id TPL_ID --contents '[{"key":"<field_name>","sort":"0","content":"完成开发","contentType":"markdown","type":"1"}]'
# 同时通知到接收人单聊
dws report create --template-id TPL_ID --contents '[...]' --to-chat --to-user-ids userId1,userId2`,
Args: cobra.NoArgs,
DisableAutoGenTag: true,
@@ -251,7 +261,7 @@ func newReportCreateCommand(runner executor.Runner) *cobra.Command {
},
}
cmd.Flags().String("template-id", "", "日志模版 ID (必填)")
cmd.Flags().String("contents", "", "日志内容 JSON 数组 (必填),每项含 key/sort/content/contentType/type")
cmd.Flags().String("contents", "", "日志内容 JSON 数组 (必填),每项含 key/sort/content/contentType/type;key 必须精确等于模板 field_name (用 report template detail --name <模板名> 查询)")
cmd.Flags().String("dd-from", "dws", "创建来源标识")
cmd.Flags().Bool("to-chat", false, "是否发送到日志接收人单聊")
cmd.Flags().String("to-user-ids", "", "接收人 userId,逗号分隔 (可选)")
+33 -4
View File
@@ -83,7 +83,7 @@ func newTodoTaskCreateCommand(runner executor.Runner) *cobra.Command {
Example: ` dws todo task create --title "修复线上Bug" --executors userId1,userId2 --priority 40
dws todo task create --title "提交报告" --executors userId1 --due "2026-03-10T18:00:00+08:00"
# 查询 userId: dws contact user search --keyword "姓名"`,
# 查询 userId: dws contact user search --query "姓名"`,
Args: cobra.NoArgs,
DisableAutoGenTag: true,
RunE: func(cmd *cobra.Command, args []string) error {
@@ -135,7 +135,7 @@ func newTodoTaskCreateCommand(runner executor.Runner) *cobra.Command {
preferLegacyLeaf(cmd)
cmd.Flags().String("title", "", i18n.T("待办标题 (必填)"))
cmd.Flags().String("executors", "", i18n.T("执行者 userId 列表 (必填)"))
cmd.Flags().String("executors", "", i18n.T("执行者 userId 列表,逗号分隔 (必填)。注意: 此处是通讯录 userId,可通过 dws contact user search --query 姓名 查询"))
cmd.Flags().String("due", "", i18n.T("截止时间 ISO-8601 (如 2026-03-10T18:00:00+08:00)"))
cmd.Flags().String("priority", "", i18n.T("优先级: 10低/20普通/30较高/40紧急"))
cmd.Flags().String("recurrence", "", i18n.T("循环待办 (需先设置 --due); 格式: DTSTART:...\\nRRULE:FREQ=DAILY;INTERVAL=1"))
@@ -152,8 +152,20 @@ func newTodoTaskCreateCommand(runner executor.Runner) *cobra.Command {
func newTodoTaskListCommand(runner executor.Runner) *cobra.Command {
cmd := &cobra.Command{
Use: "list",
Short: i18n.T("查询待办列表"),
Use: "list",
Short: i18n.T("查询待办列表"),
Long: i18n.T(`查询当前用户在当前企业的待办列表。
覆盖范围:
返回当前用户作为"执行者"(executor) 的待办。
仅参与但不执行的待办、自己创建但交给他人执行的待办不在返回范围内。
当前列表能力面向"个人待办",即钉钉待办模块中展示的待办任务,
不包含 OA 审批流待办、Teambition 项目任务等其他业务线的待办。
分页:
默认每页 20 条。--size 超过 20 时,CLI 会自动进行多次 API 调用
并合并结果(自动分页),无需手动翻页。`),
Example: ` dws todo task list --page 1 --size 20 --status false`,
Args: cobra.NoArgs,
DisableAutoGenTag: true,
@@ -362,6 +374,23 @@ func newTodoTaskGetCommand(runner executor.Runner) *cobra.Command {
cmd := &cobra.Command{
Use: "get",
Short: i18n.T("待办详情"),
Long: i18n.T(`查看待办任务详情。
返回字段说明:
creatorId / executorIds / participantIds / modifierId
待办系统内部人员标识(短数字 ID,如 6380165826),
不是通讯录 userId(如 035551044606950179)或 unionid。
这些 ID 在待办系统内对同一用户稳定,但无法直接用于通讯录 API 查询。
如需获取人员姓名,可参考返回中的 creatorInfo / executorInfos /
participantInfos 字段(包含 name 属性)。
bizTag / source
底层待办引擎的实现标识。即使是在钉钉客户端直接创建的普通个人待办,
也会返回 "teambition",这是内核实现细节,不代表来自 Teambition 产品。
tenantId / tenantType
待办所属的租户标识,非企业 corpId。tenantType 为 "user" 时
tenantId 是用户维度标识;为 "org" 时是组织维度标识。`),
Example: ` dws todo task get --task-id <taskId>
# 查询 taskId: dws todo task list`,
+2 -2
View File
@@ -91,7 +91,7 @@
```bash
# 1. 搜索同事 userId
dws contact user search --keyword "张三" --format json
dws contact user search --query "张三" --format json
# 2. 创建日程
dws calendar event create --title "会议" \
@@ -107,7 +107,7 @@ dws calendar participant add --event <EVENT_ID> --users <USER_ID> --format json
```bash
# 1. 搜索同事 userId
dws contact user search --keyword "张三" --format json
dws contact user search --query "张三" --format json
# 2. 创建待办
dws todo task create --title "任务内容" --executors <USER_ID> --format json
+35 -3
View File
@@ -232,31 +232,63 @@ Flags:
拉取指定群聊或单聊的会话消息内容。
--group 指定群聊,--user 指定单聊用户(通过 userId),--open-dingtalk-id 指定单聊用户(通过 openDingTalkId),三者互斥。默认拉取给定时间之后的消息,--forward=false 拉之前的。hasMore=true 时用结果中的边界 createTime 作为下次 --time 翻页。
--group 指定群聊,--user 指定单聊用户(通过 userId),--open-dingtalk-id 指定单聊用户(通过 openDingTalkId),三者互斥。默认拉取给定时间之后的消息,--forward=false 拉之前的。
```
Usage:
dws chat message list [flags]
Example:
# 拉取群聊中某个时间点之后的消息
dws chat message list --group <openconversation_id> --time "2025-03-01 00:00:00"
# 拉取单聊消息(通过 userId)
dws chat message list --user <userId> --time "2025-03-01 00:00:00" --limit 50
# 拉取单聊消息(通过 openDingTalkId)
dws chat message list --open-dingtalk-id <openDingTalkId> --time "2025-03-01 00:00:00" --limit 50
# 拉取某个时间点之前的消息(向过去翻页)
dws chat message list --group <openconversation_id> --time "2025-03-01 00:00:00" --forward=false
Flags:
--forward true=拉给定时间之后的消息,false=拉给定时间之前的消息 (default true)
--group string 群聊 openconversation_id(群聊时必填)
--limit int 返回数量,不传则不限制
--time string 开始时间,格式: yyyy-MM-dd HH:mm:ss (必填)
--time string 开始时间,格式: yyyy-MM-dd HH:mm:ss(不传则默认拉取最新消息)
--user string 单聊用户 userId(单聊时与 --open-dingtalk-id 二选一)
--open-dingtalk-id string 单聊用户 openDingTalkId(单聊时与 --user 二选一,适用于三方应用等无法获取 userId 的场景)
注意:
- --group、--user、--open-dingtalk-id 三者互斥,只需指定其一:群聊用 --group,单聊用 --user 或 --open-dingtalk-id
- --group 的别名: --id, --chat, --conversation-id (均可替代 --group)
- 翻页:hasMore=true 时,用结果中的边界 createTime 作为下次 --time
- 如果返回的会话消息中包含 openConvThreadId 字段,说明是话题类消息,需要调用 dws chat message list-topic-replies 拉取话题的回复内容列表,openConvThreadId 作为 --topic-id 参数
```
### 分页翻页说明(重要)
`message list` 的翻页方式与 `message list-all` **完全不同**,请勿混淆:
| 命令 | 翻页参数 | 翻页值来源 | 值格式 |
|------|---------|-----------|--------|
| `message list` | `--time` | 上一页结果中**最后一条消息的 `createTime` 字段** | `yyyy-MM-dd HH:mm:ss`(如 `"2025-03-01 14:30:00"`) |
| `message list-all` | `--cursor` | 上一页响应中的 `nextCursor` 字段 | 字符串(如 `"abc123token"`) |
**翻页步骤(message list):**
1. **首次请求**:指定起始时间
```bash
dws chat message list --group <id> --time "2025-03-01 00:00:00" --limit 50 --format json
```
2. **检查响应**:查看 `hasMore` 字段
- `hasMore: false` → 没有更多消息,翻页结束
- `hasMore: true` → 还有更多消息,继续下一步
3. **获取翻页时间**:取返回结果中**最后一条消息**的 `createTime` 字段值(如 `"2025-03-01 14:30:00"`)
4. **下一页请求**:将该 `createTime` 作为 `--time` 传入
```bash
dws chat message list --group <id> --time "2025-03-01 14:30:00" --limit 50 --format json
```
5. 重复步骤 2-4 直到 `hasMore: false`
> ⚠️ **常见错误**:
> - **不要把 `nextCursor` 传给 `--time`**:响应中的 `nextCursor` 字段(纯数字时间戳如 `1776684611219`)**不是给 `--time` 用的**。`--time` 只接受 `yyyy-MM-dd HH:mm:ss` 格式。将 `nextCursor` 传给 `--time` 会导致返回相同页面,陷入死循环。`nextCursor` 仅用于 `message list-all` 的 `--cursor` 参数。
> - **不要把 `nextCursor` 传给 `--forward`**:`--forward` 只接受 `true`(拉给定时间之后的消息)或 `false`(拉给定时间之前的消息),不是时间戳或游标参数。
---
## message list-all — 拉取指定时间范围内当前用户的所有会话消息
+8 -8
View File
@@ -776,13 +776,13 @@ Agent 安装 dws skill 后,仅依据 skill 提供的参考文档,将自然
**contact_contact_dept_search_001**
- Prompt: 搜索部门,keyword 为 技术部
- Expected: `dws contact dept search --keyword 技术部 --format json`
- Flags: `--keyword` = `技术部`
- Expected: `dws contact dept search --query 技术部 --format json`
- Flags: `--query` = `技术部`
**contact_contact_dept_search_002**
- Prompt: 搜一下技术部的部门信息
- Expected: `dws contact dept search --keyword "技术部" --format json`
- Flags: `--keyword` = `技术部`
- Expected: `dws contact dept search --query "技术部" --format json`
- Flags: `--query` = `技术部`
#### `dws contact user get`
@@ -810,13 +810,13 @@ Agent 安装 dws skill 后,仅依据 skill 提供的参考文档,将自然
**contact_contact_user_search_001**
- Prompt: 按关键词搜索用户,keyword 为 张三
- Expected: `dws contact user search --keyword 张三 --format json`
- Flags: `--keyword` = `张三`
- Expected: `dws contact user search --query 张三 --format json`
- Flags: `--query` = `张三`
**contact_contact_user_search_002**
- Prompt: 帮我找一下张三的联系方式
- Expected: `dws contact user search --keyword "张三" --format json`
- Flags: `--keyword` = `张三`
- Expected: `dws contact user search --query "张三" --format json`
- Flags: `--query` = `张三`
#### `dws contact user search-mobile`
+4 -4
View File
@@ -1014,7 +1014,7 @@
**contact_contact_dept_search_001** ✅ PASS
- Prompt: 搜索部门,keyword 为 技术部
- Expected: `dws contact dept search --keyword 技术部 --format json`
- Expected: `dws contact dept search --query 技术部 --format json`
- Skill Reference: references/products/contact.md
- Command path: PASS (contact dept search)
- Flags: PASS (1 flags validated)
@@ -1022,7 +1022,7 @@
**contact_contact_dept_search_002** ✅ PASS
- Prompt: 搜一下技术部的部门信息
- Expected: `dws contact dept search --keyword "技术部" --format json`
- Expected: `dws contact dept search --query "技术部" --format json`
- Skill Reference: references/products/contact.md
- Command path: PASS (contact dept search)
- Flags: PASS (1 flags validated)
@@ -1062,7 +1062,7 @@
**contact_contact_user_search_001** ✅ PASS
- Prompt: 按关键词搜索用户,keyword 为 张三
- Expected: `dws contact user search --keyword 张三 --format json`
- Expected: `dws contact user search --query 张三 --format json`
- Skill Reference: references/products/contact.md
- Command path: PASS (contact user search)
- Flags: PASS (1 flags validated)
@@ -1070,7 +1070,7 @@
**contact_contact_user_search_002** ✅ PASS
- Prompt: 帮我找一下张三的联系方式
- Expected: `dws contact user search --keyword "张三" --format json`
- Expected: `dws contact user search --query "张三" --format json`
- Skill Reference: references/products/contact.md
- Command path: PASS (contact user search)
- Flags: PASS (1 flags validated)