Compare commits

...
Author SHA1 Message Date
玉澜andCursor 96774e6e23 Remove retired discovery cache CLI and doctor cache check.
Drop the no-op dws cache stubs and the doctor cache health item, and
scrub skill/AGENTS guidance that still pointed agents at them.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-06 10:28:05 +08:00
玉澜 c1d90672a8 test(schema): drop compact leaf size ceiling 2026-08-06 00:56:13 +08:00
玉澜 3d2d287723 feat(schema): bound agent views and prevent instruction drift 2026-08-06 00:49:31 +08:00
玉澜andCursor 969292a8d7 declare(ding): semantic ParamDecl backfill for schema inference residuals
Complete ding leaf Property/Required from Execute CallMCP keys and live
help semantics, and allowlist the four inference→declare property remaps
so schema-compat does not freeze wrong camelCase flag names.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-05 23:19:19 +08:00
github-actions[bot] 4bcf71fb9e Merge pull request #881 from Anonymity-0/feat/chat-reply-mentions
feat(chat): support mentions in message replies
2026-08-05 23:10:42 +08:00
前津 545ee17316 fix(chat): add missing reply mention placeholders 2026-08-05 21:23:12 +08:00
前津 0c62938f74 feat(chat): support mentions in message replies 2026-08-05 21:23:12 +08:00
github-actions[bot] 45b43e52bb chore: update beta formula for v1.0.57-beta.2 [skip ci] 2026-08-05 11:49:09 +00:00
chichuan 95a5cc42ce Merge pull request #879 from DingTalk-Real-AI/codex/changelog-v1.0.57-beta.2
docs: seal v1.0.57-beta.2 changelog
2026-08-05 19:36:25 +08:00
chichuan fec750b09e docs: remove duplicate beta.2 changelog entry 2026-08-05 19:26:27 +08:00
chichuan ddd5f15b91 docs: seal v1.0.57-beta.2 changelog 2026-08-05 19:19:49 +08:00
github-actions[bot] db50be868b Merge pull request #876 from DingTalk-Real-AI/codex/restore-chat-im-compat
fix(chat): restore stable send and history compatibility
2026-08-05 19:13:50 +08:00
Dennis a6220d7d8b fix(chat): preserve migration hints with legacy flags 2026-08-05 18:19:16 +08:00
Dennis 81bf0d2a6b test(coverage): stabilize drive worker cancellation branch 2026-08-05 18:05:54 +08:00
Dennis f3a95d34a3 fix(chat): restore stable send and history compatibility 2026-08-05 17:32:12 +08:00
57 changed files with 827 additions and 420 deletions
+15 -12
View File
@@ -302,9 +302,8 @@ on the leaf:
```bash
dws auth status # token_valid should be true
dws cache refresh # deprecated no-op: prints a retirement notice (discovery cache is gone; refreshes nothing)
dws schema <mcp-canonical> -f json
# or CLI path: dws schema --cli-path "drive copy" -f json
dws schema <mcp-canonical> --jq '{canonical_path,interface_ref,parameters}' -f json
# or CLI path: dws schema --cli-path "drive copy" --jq '{canonical_path,interface_ref,parameters}' -f json
```
Resolve MCP identity via declared `interface_ref` when CLI canonical ≠ MCP path
@@ -321,8 +320,10 @@ Skill (evidence only)**.
Split work by product groups. Each agent must:
- Read Skill, Cobra/`--help`, Runtime confirmation sites, and live `dws schema`
for its tools.
- Read Skill, Cobra/`--help`, Runtime confirmation sites, and live
`dws schema <leaf> --compact` for its tools. Mapping/interface/provenance
audits may query the full leaf only through a narrow `--jq` / `--fields`
projection; do not load an entire full leaf into Agent context.
- Hand-write selection prose and leaf Contract / ProductDecl declarations;
forbid wholesale JSON merges from review dumps.
- Edit only its product’s leaf declarations (and `ProductDecl` when needed).
@@ -471,13 +472,15 @@ path; a generator unit test or JSON count alone is insufficient.
`parameters` object for commands without flags. Keep it suitable for the #602
compatibility baseline and fail rather than silently emitting a partial
export.
- `schema --all` is not normal command discovery. Use overview -> product/group
-> leaf for routine Agent work. `--compact` is supported for context-saving
projections, but a compact full export is not a complete compatibility
baseline.
- `schema --all` is not normal command discovery. Use overview -> compact
product/group -> compact leaf for routine Agent work. `--compact` is the
reviewed positive-field allowlist for Agent context: new full/audit fields
must not appear there until explicitly reviewed. A compact full export is not
a complete compatibility baseline.
- `dws <path> --help` defines whether Cobra exposes a path and which flags the
executable accepts. A leaf Schema defines Agent selection, parameter mapping
and constraints, and safety/confirmation semantics. A conflict is contract
drift, not permission to guess.
executable accepts. A compact leaf defines Agent selection, CLI parameters,
constraints, and safety/confirmation semantics. Full leaf fields such as
`property`, `interface_ref`, and provenance are audit facts. A conflict is
contract drift, not permission to guess.
- Schema and Help describe commands; neither returns DingTalk business data.
After discovery, execute the real read/search/list command to obtain data.
+14
View File
@@ -6,6 +6,19 @@ The format is inspired by [Keep a Changelog](https://keepachangelog.com/) and th
## [Unreleased]
## [1.0.57-beta.2] - 2026-08-05
### Fixed
- **Stable Chat command compatibility** (#876) — restores the hidden migration
entries for `chat send`, `chat history`, and their `im` aliases, preserving
the v1.0.56 command surface while directing callers to the supported
`chat message send/list` commands. Legacy flags now reach the same migration
hints instead of failing during flag parsing.
- **Drive download cancellation-test stability** (#876) — replaces a
timing-sensitive worker-cancellation coverage test with a deterministic seam,
reducing flaky CI without changing download behavior.
## [1.0.57-beta.1] - 2026-08-05
This beta starts the v1.0.57 line on top of v1.0.56. It packages the unified
@@ -59,6 +72,7 @@ and compatibility and CI reliability fixes.
### Changed
- **Chat reply mentions** — `dws chat message reply` can @ specified group members with `--at-open-dingtalk-ids` or @ everyone with `--at-all`, forwarding the existing `send_personal_message` mention fields and automatically adding missing current-user `<@id>` / `<@all>` placeholders.
- **Pinned MCP metadata retired** — deletes `internal/cli/schema_mcp_metadata.json` and removes its embed/loader/fallback role from Schema assembly. Catalog now assembles from Contract/ParamDecl/Interface + Cobra only; `make fetch-mcp-metadata` remains an optional diagnostic dump under `artifacts/` and refuses the retired pin path. Policy bans the pin from reappearing.
- **MCP service review retired** — deletes `schema_mcp_service_review.json` and removes its policy jq / outputguard / test disposition gate (`notify` → `out_of_surface`, snapshot hash pin). No replacement ledger.
- **Hints retired; ContractDecl is the leaf Schema source** (#830) — `schema_hints/`, Manual/Schema hint overlays, and `schema_agent_metadata/` delivery are removed. Selection, safety, parameters, and interface facts declare on ProductDecl / leaf `Contract` (`corecmd.ContractDecl` + `contract.ParamDecl` / `Safety`). Authoring renamed `SchemaDecl` → `ContractDecl`; nested fields reuse `contract.*` directly.
+11 -11
View File
@@ -1,33 +1,33 @@
class DingtalkWorkspaceCliBeta < Formula
desc "Automate DingTalk workspace tasks from the terminal (beta channel)"
homepage "https://github.com/DingTalk-Real-AI/dingtalk-workspace-cli"
version "1.0.56-beta.4"
version "1.0.57-beta.2"
license "Apache-2.0"
keg_only "it is the beta channel and conflicts with dingtalk-workspace-cli"
on_macos do
if Hardware::CPU.arm?
url "https://github.com/DingTalk-Real-AI/dingtalk-workspace-cli/releases/download/v1.0.56-beta.4/dws-darwin-arm64.tar.gz"
sha256 "f1f9b6394137edbd0b08d632aab34e92a0f3f81d80107a47de1bec9b384f0515"
url "https://github.com/DingTalk-Real-AI/dingtalk-workspace-cli/releases/download/v1.0.57-beta.2/dws-darwin-arm64.tar.gz"
sha256 "2119754d4c6f6be2b4856ab559ad44ac582a3b3abc76ff907927f62c7a4a3d29"
else
url "https://github.com/DingTalk-Real-AI/dingtalk-workspace-cli/releases/download/v1.0.56-beta.4/dws-darwin-amd64.tar.gz"
sha256 "cd3c64d20723c420e2490405d0bf8eecfd7e2b8fc352f63f23de5847a1d38f55"
url "https://github.com/DingTalk-Real-AI/dingtalk-workspace-cli/releases/download/v1.0.57-beta.2/dws-darwin-amd64.tar.gz"
sha256 "a453341d6df1a78b7d74bd624842503d857a41f73fa1ac36394e4594e4961e8d"
end
end
on_linux do
if Hardware::CPU.arm?
url "https://github.com/DingTalk-Real-AI/dingtalk-workspace-cli/releases/download/v1.0.56-beta.4/dws-linux-arm64.tar.gz"
sha256 "910918d88074534e680a2e320d3cb364ad092e96b9c422f9e75d11c9c0815dd8"
url "https://github.com/DingTalk-Real-AI/dingtalk-workspace-cli/releases/download/v1.0.57-beta.2/dws-linux-arm64.tar.gz"
sha256 "734df2c7f34ca36aa48151fda2b18e1c2c90fe812fb5ab13e8c00e074cca43af"
else
url "https://github.com/DingTalk-Real-AI/dingtalk-workspace-cli/releases/download/v1.0.56-beta.4/dws-linux-amd64.tar.gz"
sha256 "172fe0d84443be953d0c6f2c2433540e4b972fbe7776cff1417ec9c73723552b"
url "https://github.com/DingTalk-Real-AI/dingtalk-workspace-cli/releases/download/v1.0.57-beta.2/dws-linux-amd64.tar.gz"
sha256 "f602a63ab6afd2e24db7b7dabfddb0cdcf3a7bd55b0cc60a99013bac5cacc56f"
end
end
resource "skills" do
url "https://github.com/DingTalk-Real-AI/dingtalk-workspace-cli/releases/download/v1.0.56-beta.4/dws-skills.zip"
sha256 "a3457befe858cbf3fe85848428b630bfd3a5f626256ed6b49415267948915152"
url "https://github.com/DingTalk-Real-AI/dingtalk-workspace-cli/releases/download/v1.0.57-beta.2/dws-skills.zip"
sha256 "486f5ef30a88a293c14df1ff0768760284179993c51f898fa2bee2c9391d8607"
end
def install
+1
View File
@@ -88,6 +88,7 @@ policy: test-auth-legacy-compat
@mkdir -p "$(POLICY_GOTMPDIR)"
@$(POLICY_ENV) ./scripts/policy/check-open-source-assets.sh
@$(POLICY_ENV) ./scripts/policy/check-skill-context-budget.sh
@$(POLICY_ENV) ./scripts/policy/check-skill-commands.sh
@$(POLICY_ENV) ./scripts/policy/check-multi-im-skill-chain.sh
@$(POLICY_ENV) ./scripts/policy/check-command-surface.sh --strict
@$(POLICY_ENV) ./scripts/policy/check-generated-drift.sh
+8 -8
View File
@@ -371,7 +371,7 @@ dws contact user get-self --jq '.result[0].orgEmployeeModel | {name: .orgUserNam
Use Cobra help and Schema for different parts of the command contract:
- `dws <path> --help` is the source of truth for whether a command exists and which flags the binary accepts.
- `dws schema "<path>"` is the Agent contract for command selection, parameter mappings and constraints, risk, and confirmation semantics.
- `dws schema "<path>" --compact` is the normative Agent view for command selection, CLI parameters and constraints, risk, and confirmation; use a full leaf with a narrow `--jq` projection for mapping or provenance audits.
- If Help and Schema disagree, treat it as contract drift: pass only flags accepted by Cobra and use the more conservative safety semantics.
- Schema describes commands; it does not read or search DingTalk business data. Execute the real product command after discovery.
@@ -380,14 +380,14 @@ Use Cobra help and Schema for different parts of the command contract:
dws aitable record query --help
# Discover within a product, then inspect the selected leaf contract
dws schema aitable
dws schema "aitable record query"
dws schema aitable --compact
dws schema "aitable record query" --compact
# Execute the real business query
dws aitable record query --base-id BASE_ID --table-id TABLE_ID --limit 10
```
`dws schema --all` exports the complete contract for tooling, CI, audits, and compatibility baselines. Agents should prefer product/group discovery followed by a leaf query to avoid loading the full Catalog into context.
`dws schema --all` exports the complete contract for tooling, CI, audits, and compatibility baselines. Agents should query progressively with `--compact`; its positive field allowlist prevents new full/audit fields from silently expanding Agent context.
### Agent Skills
@@ -624,7 +624,7 @@ dws aitable record query --base-id BASE_ID --tabel-id TABLE_ID # --tabel-i
```bash
# Built-in jq expressions
dws aitable record query --base-id BASE_ID --table-id TABLE_ID --jq '.invocation.params'
dws schema "dev app create" --jq '.tool.required'
dws schema "dev app create" --jq '.parameters'
# Return only specific fields
dws aitable record query --base-id BASE_ID --table-id TABLE_ID --fields invocation,response
@@ -636,9 +636,9 @@ dws aitable record query --base-id BASE_ID --table-id TABLE_ID --fields invocati
<summary><strong>Schema Introspection</strong> — Agent command discovery and execution contracts</summary>
```bash
dws schema aitable # discover product commands
dws schema "aitable record query" # view the selected leaf contract
dws schema "aitable record query" --jq '.tool.required' # view required fields
dws schema aitable --compact # discover product commands
dws schema "aitable record query" --compact # view the selected Agent leaf contract
dws schema "aitable record query" --jq '[.parameters | to_entries[] | select(.value.required)]' # view required fields
dws schema --all # full export for CI/audit/baselines
```
+8 -8
View File
@@ -365,7 +365,7 @@ dws contact user get-self --jq '.result[0].orgEmployeeModel | {name: .orgUserNam
命令帮助和 Schema 分别负责命令契约的不同部分:
- `dws <path> --help` 是命令是否存在、当前二进制接受哪些 flags 的事实源。
- `dws schema "<path>"` 是 Agent 选命令、参数映射与约束、风险和确认语义的契约。
- `dws schema "<path>" --compact` 是 Agent 选命令、CLI 参数与约束、风险和确认语义的规范视图;映射或 provenance 审计使用 full leaf 配合 `--jq` 精确投影。
- Help 与 Schema 冲突时视为契约漂移:执行只传 Cobra 接受的参数,安全语义取更保守值。
- Schema 只描述命令,不读取或搜索钉钉业务数据;发现命令后仍需执行真实产品命令。
@@ -374,14 +374,14 @@ dws contact user get-self --jq '.result[0].orgEmployeeModel | {name: .orgUserNam
dws aitable record query --help
# 先在产品内发现命令,再查看选中 leaf 的契约
dws schema aitable
dws schema "aitable record query"
dws schema aitable --compact
dws schema "aitable record query" --compact
# 执行真实业务查询
dws aitable record query --base-id BASE_ID --table-id TABLE_ID --limit 10
```
`dws schema --all` 会完整导出命令契约,供工具、CI、审计和兼容性基线使用。Agent 应优先按产品/分组发现后查询 leaf,避免把整个 Catalog 加载进上下文。
`dws schema --all` 会完整导出命令契约,供工具、CI、审计和兼容性基线使用。Agent 应使用 `--compact` 渐进查询;该视图采用正向字段白名单,full 新增的审计字段不会自动进入 Agent 上下文。
### Agent Skills
@@ -618,7 +618,7 @@ dws aitable record query --base-id BASE_ID --tabel-id TABLE_ID # --tabel-i
```bash
# 内置 jq 表达式
dws aitable record query --base-id BASE_ID --table-id TABLE_ID --jq '.invocation.params'
dws schema "dev app create" --jq '.tool.required'
dws schema "dev app create" --jq '.parameters'
# 只返回指定字段
dws aitable record query --base-id BASE_ID --table-id TABLE_ID --fields invocation,response
@@ -630,9 +630,9 @@ dws aitable record query --base-id BASE_ID --table-id TABLE_ID --fields invocati
<summary><strong>Schema 自省</strong> — Agent 命令发现与执行契约</summary>
```bash
dws schema aitable # 发现产品命令
dws schema "aitable record query" # 查看选中 leaf 契约
dws schema "aitable record query" --jq '.tool.required' # 查看必填字段
dws schema aitable --compact # 发现产品命令
dws schema "aitable record query" --compact # 查看 Agent leaf 契约
dws schema "aitable record query" --jq '[.parameters | to_entries[] | select(.value.required)]' # 定向查看必填字段
dws schema --all # CI/审计/基线的全量导出
```
+11 -10
View File
@@ -94,7 +94,7 @@ With `-f json`, error responses include structured payloads: `category`, `reason
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 "calendar event create" # Pretty Agent schema view / Agent Schema 彩色查看
dws schema -f pretty "calendar event create" --compact # Pretty Agent schema view / Agent Schema 彩色查看
```
## Dry Run / 试运行
@@ -119,27 +119,28 @@ Schema 的稳定 `canonical_path`、主 CLI 路径和 aliases 收集自命令树
```bash
dws schema # 当前公开产品面的紧凑概览
dws schema calendar # 展开一个产品
dws schema "calendar event" # 展开一个命令分组
dws schema "calendar event create" # 按 CLI 空格路径查询工具
dws schema calendar.create_calendar_event # 按 canonical path 查询工具
dws schema --cli-path "calendar event create" # 显式 CLI path
dws schema "calendar event create" --compact # 支持:省略 provenance/debug 字段
dws schema calendar --compact # Agent 产品视图
dws schema "calendar event" --compact # Agent 分组视图
dws schema "calendar event create" --compact # Agent leaf(CLI 空格路径)
dws schema calendar.create_calendar_event --compact # Agent leaf(canonical path)
dws schema --cli-path "calendar event create" --compact # Agent leaf(显式 CLI path)
dws schema "calendar event create" # full leaf,仅用于映射/provenance 审计
dws schema --all # 全部工具的完整 leaf Schema,用于审计/CI/baseline
```
兼容入口 `dws schema list` 等价于根概览。`schema --all` 是完整导出:每个工具都包含完整 leaf 参数、约束和安全语义。它输出很大,只用于明确要求的全量导出、审计、CI 或参数 baseline;普通 Agent 任务应按概览、产品/分组、leaf 渐进查询,不要把 `--all` 直接注入上下文。`schema --all --compact` 虽受支持,但会裁掉 provenance 和接口映射字段,不能作为完整 baseline。
Leaf 查询、`--all` 中对应工具和 Catalog full tool 均由同一个 resolved `ToolSpec` 投影,内容必须一致;概览、产品/分组和 Catalog summary 也由该 `ToolSpec` 的统一 summary 投影生成。通过 alias 查询时,只允许 `cli_path` 和 `is_alias` 发生视图变化,参数、安全和接口契约不得变化。
省略 `--compact` 的 full leaf、`--all` 中对应工具和 Catalog full tool 均由同一个 resolved `ToolSpec` 投影,内容必须一致;compact leaf 仅做字段白名单投影,不重新解析语义。概览、产品/分组和 Catalog summary 也来自同一 `ToolSpec`。通过 alias 查询时,只允许路径视图发生变化,参数、安全和接口契约不得变化。
`--compact` 是 Schema 的展示选项。当前版本支持该 flag;若兼容旧二进制时收到 `unknown_flag: --compact`,用同一个 Schema 查询去掉 `--compact` 重试。这只降低输出裁剪能力,不表示 leaf 不存在,也不能改用 Schema 查询业务数据。
`--compact` 是 Schema 的稳定 Agent 字段白名单,也是普通 Agent 查询的规范选项。它保留 CLI 参数、组合约束、选择和安全语义,但有意省略 `interface_ref`、参数 `property/interface_type` 与 provenance。检查这些映射/审计字段时,使用 full leaf 并通过 `--jq` / `--fields` 精确投影。若兼容旧二进制时收到 `unknown_flag: --compact`,用同一个 Schema 查询去掉 `--compact` 重试;这只降低输出裁剪能力,不表示 leaf 缺失。
### Schema、Help 与业务数据的边界
| 问题 | 事实源 |
|------|--------|
| 命令是否由当前二进制暴露、Cobra 接受哪些 flags | `dws <path> --help` |
| Agent 选哪个命令、参数映射与组合约束、risk/confirmation | 对应的 leaf `dws schema "<path>"` |
| Agent 选哪个命令、CLI 参数与组合约束、risk/confirmation | 对应的 Agent leaf `dws schema "<path>" --compact` |
| CLI↔RPC 参数映射、接口绑定与 provenance | full leaf 配合 `--jq` / `--fields` 精确投影 |
| 当前钉钉中的文档、文件、日程、消息等业务数据 | 实际执行 `dws doc read`、`dws drive search` 等 read/search/list 命令 |
Schema 与 Help 冲突表示发布契约漂移,不能静默猜测。执行参数必须以 Cobra 实际接受的 flag 为准;安全语义冲突时采用更保守的处理(例如先确认)或停止执行并报告漂移。完成命令发现后,仍必须执行真实业务命令;`dws schema` 本身不会读取或搜索业务内容。
+7 -6
View File
@@ -231,7 +231,8 @@ Cobra hard-required 是独立的 executable fact,并通过 `cli_required`/prov
| 问题 | 事实源 |
|---|---|
| 当前二进制是否暴露命令、Cobra 接受哪些 flags | `dws <path> --help` |
| Agent 选哪个命令、参数映射/required/约束、risk/confirmation | 对应 leaf `dws schema "<path>"` |
| Agent 选哪个命令、CLI 参数/required/约束、risk/confirmation | Agent leaf `dws schema "<path>" --compact` |
| CLI↔RPC 参数映射、接口绑定、provenance | full leaf 配合 `--jq` / `--fields` 精确投影 |
| 钉钉中的文档、文件、日程、消息等实际数据 | 真正执行 `dws doc read`、`dws drive search` 等 read/search/list 命令 |
Schema 和 Help 冲突是契约漂移,不能静默猜测:
@@ -246,10 +247,10 @@ Schema 和 Help 冲突是契约漂移,不能静默猜测:
```bash
dws schema # 产品紧凑概览
dws schema calendar # 产品摘要
dws schema "calendar event" # 分组摘要
dws schema "calendar event create" # 完整 leaf
dws schema "calendar event create" --compact # 支持:裁掉 provenance/debug 字段
dws schema calendar --compact # Agent 产品摘要
dws schema "calendar event" --compact # Agent 分组摘要
dws schema "calendar event create" --compact # Agent leaf
dws schema "calendar event create" # full leaf,仅用于映射/provenance 审计
dws schema --all # 所有工具的完整 leaf 导出
```
@@ -257,7 +258,7 @@ dws schema --all # 所有工具的完整 leaf 导
`schema --all` 必须包含最终 `SchemaIndex` 中每个 tool 的完整 leaf 参数、约束和安全语义;无业务参数的命令也要包含空 `parameters` 对象。它用于审计、CI 和参数防丢 baseline,但输出很大,普通 Agent 命令发现不得使用,应按 overview -> product/group -> leaf 渐进查询。
`--compact` 当前受支持,适合减少常规 leaf 查询上下文。`schema --all --compact` 也可执行,但会移除 provenance/debug 和接口映射字段,不能作为完整兼容性 baseline。
`--compact` 是普通 Agent 查询的规范视图:通过正向字段白名单保留选参、约束与安全语义,full 新增字段不会自动进入 Agent 上下文。省略它的 leaf 包含参数 property、接口绑定和 provenance,只用于定向审计;`schema --all --compact` 也可执行,但不能作为完整兼容性 baseline。
兼容旧二进制时,如果 Schema 查询返回 `unknown_flag: --compact`,只去掉 `--compact` 重试同一个查询。这是展示能力降级,不代表 leaf 缺失,也不能改用 Schema 查询业务数据。
+5 -28
View File
@@ -358,12 +358,11 @@ func TestCrossPlatformCoverageOverlayRecoveryHostAndHelperRemainingCoverage(t *t
}
}
func TestCrossPlatformCoverageConfigAndCacheCommandRemainingCoverage(t *testing.T) {
for _, command := range []*cobra.Command{newConfigCommand(), newCacheCommand()} {
command.SetOut(io.Discard)
if err := command.RunE(command, nil); err != nil {
t.Fatal(err)
}
func TestCrossPlatformCoverageConfigCommandRemainingCoverage(t *testing.T) {
command := newConfigCommand()
command.SetOut(io.Discard)
if err := command.RunE(command, nil); err != nil {
t.Fatal(err)
}
t.Setenv("DWS_CONFIG_DIR", "configured")
configCmd := &cobra.Command{Use: "config"}
@@ -381,26 +380,4 @@ func TestCrossPlatformCoverageConfigAndCacheCommandRemainingCoverage(t *testing.
if err := runConfigList(list, nil); err != nil {
t.Fatal(err)
}
cacheRoot := &cobra.Command{Use: "root"}
cacheRoot.PersistentFlags().String("format", "", "")
cacheCmd := &cobra.Command{Use: "cache"}
cacheRoot.AddCommand(cacheCmd)
for _, format := range []string{"json", "pretty", "table"} {
_ = cacheRoot.PersistentFlags().Set("format", format)
cacheCmd.SetOut(io.Discard)
if err := printCacheCompatNotice(cacheCmd, "status"); err != nil {
t.Fatal(err)
}
}
fail := errors.New("write")
cacheCmd.SetOut(appFailWriter{err: fail})
_ = cacheRoot.PersistentFlags().Set("format", "pretty")
if err := printCacheCompatNotice(cacheCmd, "status"); !errors.Is(err, fail) {
t.Fatalf("pretty write error = %v", err)
}
_ = cacheRoot.PersistentFlags().Set("format", "table")
if err := printCacheCompatNotice(cacheCmd, "status"); !errors.Is(err, fail) {
t.Fatalf("table write error = %v", err)
}
}
-76
View File
@@ -1,76 +0,0 @@
// Copyright 2026 Alibaba Group
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package app
import (
"encoding/json"
"fmt"
"strings"
"github.com/spf13/cobra"
)
type cacheCompatNotice struct {
Status string `json:"status"`
Command string `json:"command"`
Message string `json:"message"`
Replacement string `json:"replacement,omitempty"`
}
func newCacheCommand() *cobra.Command {
cmd := &cobra.Command{
Use: "cache",
Short: "服务发现缓存兼容入口(静态端点模式已弃用)",
Hidden: true,
DisableAutoGenTag: true,
RunE: func(cmd *cobra.Command, args []string) error {
return cmd.Help()
},
}
for _, name := range []string{"refresh", "status", "clean"} {
sub := &cobra.Command{
Use: name,
Short: "已弃用:静态端点模式无需服务发现缓存",
Args: cobra.NoArgs,
DisableAutoGenTag: true,
RunE: func(cmd *cobra.Command, args []string) error {
return printCacheCompatNotice(cmd, name)
},
}
cmd.AddCommand(sub)
}
return cmd
}
func printCacheCompatNotice(cmd *cobra.Command, command string) error {
notice := cacheCompatNotice{
Status: "deprecated",
Command: "dws cache " + command,
Message: "服务发现已下线,当前版本使用编译期静态端点目录;dws cache 仅保留为兼容入口,不会刷新端点。",
Replacement: "如遇 endpoint_not_resolved,请先执行 dws upgrade 获取包含最新 internal/syncdata 端点的版本;仍失败时检查 internal/syncdata.StaticServers() 是否覆盖目标 product/server。",
}
format, _ := cmd.Root().PersistentFlags().GetString("format")
switch strings.ToLower(strings.TrimSpace(format)) {
case "", "json":
return json.NewEncoder(cmd.OutOrStdout()).Encode(notice)
case "pretty":
data, _ := json.MarshalIndent(notice, "", " ")
var err error
_, err = fmt.Fprintln(cmd.OutOrStdout(), string(data))
return err
default:
_, err := fmt.Fprintf(cmd.OutOrStdout(), "%s: %s\n%s\n", notice.Command, notice.Message, notice.Replacement)
return err
}
}
+1 -23
View File
@@ -732,7 +732,7 @@ func TestCrossPlatformCoverageEventCommandValidationCoverage(t *testing.T) {
}
}
func TestCrossPlatformCoverageVersionCacheCompletionCoverage(t *testing.T) {
func TestCrossPlatformCoverageVersionCompletionCoverage(t *testing.T) {
oldVersion, oldBuild, oldCommit := version, buildTime, gitCommit
t.Cleanup(func() { version, buildTime, gitCommit = oldVersion, oldBuild, oldCommit })
version, buildTime, gitCommit = "1.0", "unknown", "unknown"
@@ -753,25 +753,6 @@ func TestCrossPlatformCoverageVersionCacheCompletionCoverage(t *testing.T) {
t.Fatal(err)
}
root := &cobra.Command{Use: "dws"}
root.PersistentFlags().String("format", "json", "")
for _, format := range []string{"json", "pretty", "table"} {
_ = root.PersistentFlags().Set("format", format)
child := &cobra.Command{Use: "child"}
root.AddCommand(child)
child.SetOut(io.Discard)
if err := printCacheCompatNotice(child, "status"); err != nil {
t.Fatalf("cache %s: %v", format, err)
}
root.RemoveCommand(child)
}
cache := newCacheCommand()
cache.SetOut(io.Discard)
cache.SetArgs([]string{"status"})
if err := cache.Execute(); err != nil {
t.Fatal(err)
}
completionRoot := &cobra.Command{Use: "dws"}
completionRoot.AddCommand(&cobra.Command{Use: "child"})
for _, shell := range []string{"bash", "zsh", "fish"} {
@@ -1747,9 +1728,6 @@ func TestCrossPlatformCoverageDoctorCommandCoverage(t *testing.T) {
}
for _, jsonOut := range []bool{false, true} {
if got := doctorCheckCache(io.Discard, jsonOut); got.Status != statusPass {
t.Fatal("cache check failed")
}
if got := doctorCheckPerf(io.Discard, jsonOut); got.Status != statusPass {
t.Fatalf("perf check = %#v", got)
}
-1
View File
@@ -300,7 +300,6 @@ func TestCrossPlatformCoverageRootUtilityAndTimingCoverage(t *testing.T) {
_ = newCompletionCommand(dedupRoot)
_ = newCatalogCommand()
_ = newConfigCommand()
_ = newCacheCommand()
_ = newVersionCommand()
_ = newRecoveryCommand(&GlobalFlags{})
_ = newAPICommand(&GlobalFlags{})
+1 -22
View File
@@ -61,7 +61,7 @@ func newDoctorCommand() *cobra.Command {
cmd := &cobra.Command{
Use: "doctor",
Short: "环境健康检查",
Long: "一键检查登录态、网络连通性、缓存状态和版本更新,快速定位常见问题。",
Long: "一键检查登录态、网络连通性和版本更新,快速定位常见问题。",
Args: cobra.NoArgs,
DisableAutoGenTag: true,
RunE: runDoctor,
@@ -92,9 +92,6 @@ func runDoctor(cmd *cobra.Command, _ []string) error {
networkResult := doctorCheckNetwork(cmd.Context(), w, jsonOut, networkTimeout)
checks = append(checks, networkResult)
cacheResult := doctorCheckCache(w, jsonOut)
checks = append(checks, cacheResult)
versionResult := doctorCheckVersion(w, jsonOut, networkTimeout)
checks = append(checks, versionResult)
@@ -297,24 +294,6 @@ func doctorCheckNetwork(ctx context.Context, w io.Writer, jsonOut bool, timeout
return r
}
// ── Cache check ─────────────────────────────────────────────────────────
func doctorCheckCache(w io.Writer, jsonOut bool) checkResult {
if !jsonOut {
fmt.Fprint(w, tui.Dim("检查缓存状态... "))
}
r := checkResult{
Name: "cache",
Status: statusPass,
Message: "静态端点模式, 无需缓存",
}
if !jsonOut {
printCheckResult(w, r)
}
return r
}
// ── Version check ───────────────────────────────────────────────────────
func doctorCheckVersion(w io.Writer, jsonOut bool, timeout time.Duration) checkResult {
-25
View File
@@ -109,31 +109,6 @@ func TestPrintCheckResultNoHint(t *testing.T) {
}
}
func TestDoctorCheckCacheEmpty(t *testing.T) {
t.Setenv("DWS_CACHE_DIR", t.TempDir())
var buf bytes.Buffer
r := doctorCheckCache(&buf, false)
if r.Status != statusPass {
t.Errorf("expected pass for static endpoint mode, got %s", r.Status)
}
}
func TestDoctorCheckCacheEmptyJSON(t *testing.T) {
t.Setenv("DWS_CACHE_DIR", t.TempDir())
var buf bytes.Buffer
r := doctorCheckCache(&buf, true)
if r.Status != statusPass {
t.Errorf("expected pass for static endpoint mode, got %s", r.Status)
}
if buf.Len() != 0 {
t.Error("expected no output in JSON mode")
}
}
func TestDoctorCheckAuthReportsKeychainUnavailable(t *testing.T) {
t.Setenv("DWS_CONFIG_DIR", filepath.Join(t.TempDir(), "config"))
+1 -2
View File
@@ -473,7 +473,6 @@ func newRootCommandWithEngine(rootCtx context.Context, engine *pipeline.Engine,
newProfileCommand(),
newAPICommand(flags),
newSkillCommand(),
newCacheCommand(),
newCatalogCommand(),
newConfigCommand(),
newDoctorCommand(),
@@ -722,7 +721,7 @@ func hideNonDirectRuntimeCommands(root *cobra.Command) {
// hideNonDirectRuntimeCommands) and reservedCommands (the plugin-override
// blocklist) derive from this single set so they cannot drift apart.
var builtinCommandNames = map[string]bool{
"auth": true, "api": true, "audit": true, "cache": true, "config": true,
"auth": true, "api": true, "audit": true, "config": true,
"doctor": true, "event": true, "completion": true, "skill": true,
"plugin": true, "profile": true, "version": true, "help": true,
"recovery": true, "schema": true, "mcp": true, "upgrade": true,
+37 -14
View File
@@ -72,6 +72,39 @@ func TestCalendarEventCreateHelpKeepsRoomsStringMetavar(t *testing.T) {
func TestRootKeepsMainBranchChatCompatibilityCommands(t *testing.T) {
root := NewRootCommand()
for _, path := range []string{
"chat send",
"chat history",
"im send",
"im history",
} {
command, remaining, err := root.Find(strings.Fields(path))
if err != nil {
t.Fatalf("find %s: %v", path, err)
}
if len(remaining) != 0 || !command.Hidden || !command.Runnable() {
t.Fatalf("%s compatibility contract: remaining=%v hidden=%v runnable=%v", path, remaining, command.Hidden, command.Runnable())
}
}
for _, tc := range []struct {
args []string
hint string
}{
{args: []string{"chat", "send", "--group", "cid-stable", "--text", "hello"}, hint: "dws chat message send"},
{args: []string{"im", "send", "--group", "cid-stable", "--text", "hello"}, hint: "dws chat message send"},
{args: []string{"chat", "history", "--group", "cid-stable", "--limit", "20"}, hint: "dws chat message list --group <GROUP_OPEN_CONVERSATION_ID>"},
{args: []string{"im", "history", "--group", "cid-stable", "--limit", "20"}, hint: "dws chat message list --group <GROUP_OPEN_CONVERSATION_ID>"},
} {
command := NewRootCommand()
command.SilenceErrors = true
command.SilenceUsage = true
command.SetArgs(tc.args)
err := command.Execute()
if err == nil || !strings.Contains(err.Error(), "ambiguous command") || !strings.Contains(err.Error(), tc.hint) {
t.Fatalf("dws %s error = %v, want migration hint %q", strings.Join(tc.args, " "), err, tc.hint)
}
}
listDirect := mustFindCommand(t, root, "chat", "message", "list-direct")
for _, flag := range []string{"user", "open-dingtalk-id", "time", "forward", "limit"} {
if listDirect.Flags().Lookup(flag) == nil {
@@ -386,20 +419,10 @@ func TestRootKeepsSVIPChatCompatibilityFlags(t *testing.T) {
}
}
func TestCacheRefreshCompatibilityStub(t *testing.T) {
cmd := NewRootCommand()
var out bytes.Buffer
cmd.SetOut(&out)
cmd.SetErr(&out)
cmd.SetArgs([]string{"cache", "refresh", "--format", "json"})
if err := cmd.Execute(); err != nil {
t.Fatalf("cache refresh compatibility stub: %v\n%s", err, out.String())
}
got := out.String()
for _, want := range []string{`"status":"deprecated"`, `"command":"dws cache refresh"`, "服务发现已下线"} {
if !strings.Contains(got, want) {
t.Fatalf("cache refresh output missing %q:\n%s", want, got)
}
func TestCacheCommandRemoved(t *testing.T) {
root := NewRootCommand()
if cmd, _, err := root.Find([]string{"cache"}); err == nil && cmd != nil && cmd != root {
t.Fatalf("dws cache compatibility stub must be removed, found %q", cmd.CommandPath())
}
}
+2 -2
View File
@@ -71,7 +71,7 @@ func NewSchemaCommand() *cobra.Command {
Short: "渐进查看命令 Schema (产品 / 分组 / 工具参数)",
Long: `查看当前可运行命令的 Schema 元数据。
不带参数时列出产品和工具数量;传产品或分组路径逐层展开;传具体工具路径输出扁平参数 Schema(对齐 GWS:parameters 内联 required,键为 CLI flag)。--all 输出全部工具的完整 leaf Schema(包括参数和约束,用于审计/CI)。--compact 去除 provenance / debug 字段,仅保留 Agent 选参所需信息(适合 Agent 上下文)。helper、MCP 与本地 Cobra 命令均须通过 ContractFinal.Identity 声明进入收集的身份集,并从同一声明装配的 ToolSpec 投影;查询不执行服务发现或临时合成第二份 Schema。`,
不带参数时列出产品和工具数量;传产品或分组路径逐层展开;传具体工具路径输出扁平参数 Schema(对齐 GWS:parameters 内联 required,键为 CLI flag)。普通 Agent 查询应使用 --compact:它按稳定字段白名单输出选参、约束和安全语义。省略 --compact 的 full leaf 保留参数映射、接口绑定和 provenance,仅用于定向审计;--all 输出全部工具的完整 leaf Schema,用于审计/CI。helper、MCP 与本地 Cobra 命令均须通过 ContractFinal.Identity 声明进入收集的身份集,并从同一声明装配的 ToolSpec 投影;查询不执行服务发现或临时合成第二份 Schema。`,
Args: cobra.MaximumNArgs(1),
DisableAutoGenTag: true,
RunE: func(cmd *cobra.Command, args []string) error {
@@ -113,7 +113,7 @@ func NewSchemaCommand() *cobra.Command {
},
}
cmd.Flags().Bool("all", false, "输出全部工具的完整 leaf Schema(包括参数和约束,用于审计/CI)")
cmd.Flags().Bool("compact", false, "去除 provenance/debug 字段,仅保留 Agent 选参所需信息")
cmd.Flags().Bool("compact", false, "按稳定字段白名单输出 Agent 选参、约束和安全语义")
cmd.Flags().String("cli-path", "", "按 CLI 命令路径查询")
return cmd
}
+64 -46
View File
@@ -1162,69 +1162,87 @@ func strconvQuote(value string) string {
// ─── --compact mode ──────────────────────────────────────────────────────────
// schemaCompactStripKeys are top-level tool/product keys removed in --compact mode.
var schemaCompactStripKeys = map[string]bool{
// provenance / debug
"agent_metadata_source": true,
"agent_source_refs": true,
"agent_summary_source": true,
"effect_source": true,
"metadata_source": true,
"source": true,
"agent_metadata": true,
"field_provenance": true,
"reviewed": true,
// redundant with canonical_path / cli_path
"name": true,
"path": true,
"cli_name": true,
"primary_cli_path": true,
"is_alias": true,
"has_parameters": true,
"parameter_count": true,
"product_id": true,
"display": true,
"title": true,
"group": true,
"source_product_id": true,
"aliases": true,
"catalog_hash": true,
"surface_hash": true,
"workflow_refs": true,
"prerequisites": true,
"tips": true,
"interface_ref": true,
// schemaCompactPayloadKeys is the reviewed Agent-view allowlist. Keep this a
// positive list: a new full/audit field must not silently expand routine Agent
// context just because it was added to ToolSpec.ToPayload.
var schemaCompactPayloadKeys = map[string]bool{
// Navigation envelopes.
"kind": true, "level": true, "count": true, "tool_count": true,
"products": true, "product": true, "tools": true,
"id": true, "schema_path": true, "runtime": true,
// Leaf identity and execution semantics.
"canonical_path": true, "cli_path": true,
"agent_summary": true, "description": true,
"effect": true, "risk": true, "confirmation": true, "idempotency": true,
"interface_mode": true, "availability": true, "interface_reason": true,
"parameters": true, "constraints": true, "positionals": true, "dry_run": true,
"examples": true, "use_when": true, "avoid_when": true,
}
// schemaCompactParamStripKeys are per-parameter keys removed in --compact mode.
var schemaCompactParamStripKeys = map[string]bool{
"interface_description": true,
"interface_type": true,
"property": true,
"field_provenance": true,
// schemaCompactParamKeys is the reviewed parameter allowlist for Agent command
// construction. RPC mapping and provenance fields intentionally remain in the
// full/audit view.
var schemaCompactParamKeys = map[string]bool{
"type": true, "description": true, "required": true,
"cli_required": true, "required_when": true,
"default": true, "interface_default": true, "example": true,
"format": true, "enum": true,
}
// stripSchemaPayloadCompact walks a schema payload map and removes provenance,
// debug and redundant keys so that only agent-essential fields remain.
// It operates recursively on nested maps, slices, and parameter objects.
// stripSchemaPayloadCompact projects a full Schema payload onto the reviewed
// Agent-view allowlist. Structural product/tool children are projected
// recursively; constraint, positional and dry-run values are already typed
// contract data and are retained verbatim.
func stripSchemaPayloadCompact(payload map[string]any) map[string]any {
if payload == nil {
return nil
}
result := make(map[string]any, len(payload))
for k, v := range payload {
if schemaCompactStripKeys[k] {
if !schemaCompactPayloadKeys[k] {
continue
}
if k == "parameters" {
switch k {
case "parameters":
result[k] = stripSchemaParametersCompact(v)
continue
case "product":
if product, ok := v.(map[string]any); ok {
result[k] = stripSchemaPayloadCompact(product)
} else {
result[k] = v
}
case "products", "tools":
result[k] = stripSchemaPayloadCollectionCompact(v)
default:
result[k] = v
}
result[k] = stripSchemaValueCompact(v)
}
return result
}
func stripSchemaPayloadCollectionCompact(value any) any {
switch values := value.(type) {
case []map[string]any:
result := make([]map[string]any, len(values))
for i, item := range values {
result[i] = stripSchemaPayloadCompact(item)
}
return result
case []any:
result := make([]any, len(values))
for i, item := range values {
if payload, ok := item.(map[string]any); ok {
result[i] = stripSchemaPayloadCompact(payload)
} else {
result[i] = item
}
}
return result
default:
return value
}
}
func stripSchemaParametersCompact(value any) any {
parameters, ok := value.(map[string]any)
if !ok {
@@ -1278,7 +1296,7 @@ func stripSchemaValueCompact(v any) any {
func stripSchemaParamCompact(param map[string]any) map[string]any {
result := make(map[string]any, len(param))
for k, v := range param {
if schemaCompactParamStripKeys[k] {
if !schemaCompactParamKeys[k] {
continue
}
result[k] = v
+60 -2
View File
@@ -508,6 +508,10 @@ func TestStripSchemaPayloadCompactLeaf(t *testing.T) {
if err != nil {
t.Fatal(err)
}
leaf["future_audit_field"] = "must not leak into Agent view"
for _, raw := range schemaMap(leaf["parameters"]) {
raw["future_mapping_field"] = "must not leak into Agent view"
}
stripped := stripSchemaPayloadCompact(leaf)
// Must keep agent-essential fields.
@@ -518,7 +522,7 @@ func TestStripSchemaPayloadCompactLeaf(t *testing.T) {
}
// Must strip provenance / redundant fields.
for _, key := range []string{"agent_metadata_source", "agent_source_refs", "agent_summary_source", "effect_source", "metadata_source", "primary_cli_path", "parameter_count", "has_parameters", "interface_ref", "source", "title", "display"} {
for _, key := range []string{"agent_metadata_source", "agent_source_refs", "agent_summary_source", "effect_source", "metadata_source", "primary_cli_path", "parameter_count", "has_parameters", "interface_ref", "source", "title", "display", "future_audit_field"} {
if _, ok := stripped[key]; ok {
t.Fatalf("compact leaf still contains stripped key %q", key)
}
@@ -528,7 +532,7 @@ func TestStripSchemaPayloadCompactLeaf(t *testing.T) {
if params, ok := stripped["parameters"].(map[string]any); ok {
for name, p := range params {
if pm, ok := p.(map[string]any); ok {
for _, stripped := range []string{"interface_description", "interface_type", "property"} {
for _, stripped := range []string{"interface_description", "interface_type", "property", "future_mapping_field"} {
if _, present := pm[stripped]; present {
t.Fatalf("compact param %q still contains %q", name, stripped)
}
@@ -1412,3 +1416,57 @@ func TestDeliveryCatalogChatParamDeclsFrom87910880Reviewed(t *testing.T) {
}
}
}
// TestDeliveryCatalogDingSemanticParamDeclsSpotCheck locks the ding pilot
// ParamDecl backfill: Execute/CallMCP keys win over flag-name inference, and
// required stays declare/cobra-backed rather than usage_required_inference.
func TestDeliveryCatalogDingSemanticParamDeclsSpotCheck(t *testing.T) {
cases := []struct {
path string
flag string
property string
required bool
}{
{"ding +list", "cursor", "cursor", false},
{"ding +list", "type", "type", false},
{"ding +receiver-status", "ding-id", "openDingId", true},
{"ding +recall-personal", "id", "openDingId", true},
{"ding +send-personal", "users", "receiverOpenDingTalkIds", true},
{"ding +send-personal", "content", "content", true},
{"ding +send-personal", "type", "remindType", false},
{"ding +send-personal", "uuid", "uuid", false},
{"ding message send", "content", "content", true},
{"ding message send", "robot-code", "robotCode", true},
{"ding message send", "type", "remindType", false},
{"ding message send", "users", "receiverUserIdList", true},
{"ding message recall", "id", "openDingId", true},
{"ding message recall", "robot-code", "robotCode", true},
}
for _, tc := range cases {
leaf, err := queryDeliverySchemaPayload([]string{tc.path})
if err != nil {
t.Fatalf("%s: %v", tc.path, err)
}
param := schemaMap(leaf["parameters"])[tc.flag]
if param == nil {
t.Fatalf("%s missing --%s", tc.path, tc.flag)
}
if got, _ := param["property"].(string); got != tc.property {
t.Fatalf("%s --%s property = %q, want %q", tc.path, tc.flag, got, tc.property)
}
propProv := schemaMap(param["field_provenance"])["property"]
if src, _ := propProv["source"].(string); src == "flag_name_inference" {
t.Fatalf("%s --%s property still flag_name_inference", tc.path, tc.flag)
}
gotRequired, _ := param["required"].(bool)
if gotRequired != tc.required {
t.Fatalf("%s --%s required = %v, want %v", tc.path, tc.flag, gotRequired, tc.required)
}
if tc.required {
reqProv := schemaMap(param["field_provenance"])["required"]
if src, _ := reqProv["source"].(string); src == "usage_required_inference" {
t.Fatalf("%s --%s required still usage_required_inference", tc.path, tc.flag)
}
}
}
}
+85 -27
View File
@@ -54,6 +54,14 @@ func resolveMessageForward(cmd *cobra.Command, defaultForward bool) (bool, error
}
}
func chatCompatibilityHintSubCmd(use, hint string) *cobra.Command {
command := hintSubCmd(use, hint)
// Legacy callers may still pass the old command's flags. Let the migration
// command consume them so Cobra reaches RunE and returns the replacement path.
command.DisableFlagParsing = true
return command
}
type nativeChatTargetReader struct{}
func (nativeChatTargetReader) CallMCPData(product, tool string, params map[string]any) (map[string]any, error) {
@@ -396,6 +404,50 @@ func NormalizeMessageMentions(text string, ids []string, atAll, wrapAngle bool)
return text
}
// applyCurrentUserGroupMentions keeps the body placeholders and
// send_personal_message mention arguments aligned for send and reply.
func applyCurrentUserGroupMentions(params map[string]any, text, rawOpenIDs string, atAll bool) string {
var atOpenIDs []string
if rawOpenIDs != "" {
atOpenIDs = strings.Split(rawOpenIDs, ",")
}
if atAll && !strings.Contains(text, "<@all>") {
text = "<@all> " + text
}
text = normalizeAtPlaceholders(text, atOpenIDs, true)
if atAll {
params["atAll"] = true
}
if len(atOpenIDs) > 0 {
params["atOpenDingTalkIds"] = atOpenIDs
}
return text
}
func addMissingCurrentUserMentionPlaceholders(text, rawOpenIDs string) string {
if rawOpenIDs == "" {
return text
}
missing := make([]string, 0)
probeText := text
for _, id := range parseCSVValues(rawOpenIDs) {
placeholder := "<@" + id + ">"
if strings.Contains(probeText, placeholder) {
continue
}
missing = append(missing, placeholder)
probeText += placeholder
}
if len(missing) == 0 {
return text
}
prefix := strings.Join(missing, " ")
if strings.HasPrefix(text, "<@all> ") {
return "<@all> " + prefix + " " + strings.TrimPrefix(text, "<@all> ")
}
return prefix + " " + text
}
func containsMessageMention(text, placeholder string) bool {
if strings.HasPrefix(placeholder, "<") {
return strings.Contains(text, placeholder)
@@ -2029,29 +2081,15 @@ func newChatCommand() *cobra.Command {
if groupID != "" {
atAll, _ := cmd.Flags().GetBool("at-all")
atOpenIdsStr, _ := cmd.Flags().GetString("at-open-dingtalk-ids")
var atOpenIds []string
if atOpenIdsStr != "" {
atOpenIds = strings.Split(atOpenIdsStr, ",")
}
if atAll && !strings.Contains(text, "<@all>") {
text = "<@all> " + text
}
// 用户身份发消息要求 @ 占位符为 <@openDingTalkId>;模型若写成裸 @id 自动补全,已有 <@id> 不变
text = normalizeAtPlaceholders(text, atOpenIds, true)
// 群聊统一走 openDingTalkId @ 人接口。
contentJSON, _ := marshalJSONRaw(map[string]string{"title": title, "text": text})
newParams := map[string]any{
"openConversationId": groupID,
"msgType": "markdown",
"content": string(contentJSON),
"clawType": clawType,
}
if atAll {
newParams["atAll"] = true
}
if len(atOpenIds) > 0 {
newParams["atOpenDingTalkIds"] = atOpenIds
}
text = applyCurrentUserGroupMentions(newParams, text, atOpenIdsStr, atAll)
contentJSON, _ := marshalJSONRaw(map[string]string{"title": title, "text": text})
newParams["content"] = string(contentJSON)
if msgUuid != "" {
newParams["uuid"] = msgUuid
}
@@ -5368,13 +5406,14 @@ flow-status 取值:1=处理中(PROCESSING),2=输入中(INPUTTING),3=完成
chatMessageReplyCmd := &cobra.Command{
Use: "reply",
Short: "引用回复消息(支持单聊/群聊)",
Long: `以当前用户身份引用某条消息并回复。需要指定会话 ID、被引用消息 ID、原消息发送者 openDingTalkId,以及回复内容。
Long: `以当前用户身份引用某条消息并回复。需要指定会话 ID、被引用消息 ID、原消息发送者 openDingTalkId,以及回复内容。群聊回复可通过 --at-open-dingtalk-ids @指定成员,或通过 --at-all @所有人;正文中的裸 @openDingTalkId 会自动规范化为 <@openDingTalkId>,缺少对应成员或 <@all> 占位符时会自动补齐。
如何获取 openConversationId(如果上层已有则直接使用,不必再查):
- 群聊:dws chat search --query "群名"
- 单聊:dws chat conversation-info --open-dingtalk-id <openDingTalkId>
(人员信息可通过 dws contact user search --keyword "姓名" --format json 获取)`,
Example: ` dws chat message reply --conversation-id <openConversationId> --ref-msg-id <openMessageId> --ref-sender <openDingTalkId> --text "收到,马上处理"`,
Example: ` dws chat message reply --conversation-id <openConversationId> --ref-msg-id <openMessageId> --ref-sender <openDingTalkId> --text "收到,马上处理"
dws chat message reply --conversation-id <openConversationId> --ref-msg-id <openMessageId> --ref-sender <openDingTalkId> --text "请看一下" --at-open-dingtalk-ids <mentionedOpenDingTalkId>`,
RunE: func(cmd *cobra.Command, args []string) error {
if err := validateRequiredFlags(cmd, "conversation-id", "ref-msg-id", "ref-sender", "text"); err != nil {
return err
@@ -5387,13 +5426,6 @@ flow-status 取值:1=处理中(PROCESSING),2=输入中(INPUTTING),3=完成
}
refSender = resolved
}
replyContent := map[string]string{
"referenceOpenMessageId": mustGetFlag(cmd, "ref-msg-id"),
"srcMsgSendOpenDingTalkId": refSender,
"replyMsgType": "text",
"content": mustGetFlag(cmd, "text"),
}
contentJSON, _ := marshalJSONRaw(replyContent)
clawType := ""
aiTag, _ := cmd.Flags().GetBool("ai-tag")
if aiTag {
@@ -5402,9 +5434,25 @@ flow-status 取值:1=处理中(PROCESSING),2=输入中(INPUTTING),3=完成
toolArgs := map[string]any{
"openConversationId": mustGetFlag(cmd, "conversation-id"),
"msgType": "reply",
"content": string(contentJSON),
"clawType": clawType,
}
atAll, _ := cmd.Flags().GetBool("at-all")
atOpenIDs := mustGetFlag(cmd, "at-open-dingtalk-ids")
replyText := applyCurrentUserGroupMentions(
toolArgs,
mustGetFlag(cmd, "text"),
atOpenIDs,
atAll,
)
replyText = addMissingCurrentUserMentionPlaceholders(replyText, atOpenIDs)
replyContent := map[string]string{
"referenceOpenMessageId": mustGetFlag(cmd, "ref-msg-id"),
"srcMsgSendOpenDingTalkId": refSender,
"replyMsgType": "text",
"content": replyText,
}
contentJSON, _ := marshalJSONRaw(replyContent)
toolArgs["content"] = string(contentJSON)
if v, _ := cmd.Flags().GetString("uuid"); v != "" {
toolArgs["uuid"] = v
}
@@ -5438,6 +5486,8 @@ flow-status 取值:1=处理中(PROCESSING),2=输入中(INPUTTING),3=完成
},
Parameters: []contract.ParamDecl{
{Name: "ai-tag", Property: "clawType", InterfaceType: "string"},
{Name: "at-all", Property: "atAll", Required: boolPtr(false), InterfaceType: "boolean"},
{Name: "at-open-dingtalk-ids", Property: "atOpenDingTalkIds", Required: boolPtr(false), InterfaceType: "array"},
{Name: "conversation-id", Property: "openConversationId"},
},
},
@@ -5452,6 +5502,8 @@ flow-status 取值:1=处理中(PROCESSING),2=输入中(INPUTTING),3=完成
_ = chatMessageReplyCmd.MarkFlagRequired("text")
chatMessageReplyCmd.Flags().String("uuid", "", "幂等键(可选)")
chatMessageReplyCmd.Flags().Bool("ai-tag", true, "消息是否带 AI 发送角标(默认 true)")
chatMessageReplyCmd.Flags().Bool("at-all", false, "@所有人(仅群聊时生效;正文缺少 <@all> 时自动补齐)")
chatMessageReplyCmd.Flags().String("at-open-dingtalk-ids", "", "@指定成员的 openDingTalkId 列表,逗号分隔(仅群聊时生效;正文缺少对应 <@id> 时自动补齐,裸 @id 自动规范化)")
cli.AttachRuntimeSchema(chatMessageReplyCmd, "chat", "reply_personal_message", "hardcoded:chat")
// ── message forward: 转发单条消息 ────────────────────────
@@ -8166,5 +8218,11 @@ 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)
// Keep the v1.0.56 command surface recognizable while directing callers to
// the supported nested commands. The chat root's "im" alias makes these
// compatibility hints available through both chat and im.
root.AddCommand(chatCompatibilityHintSubCmd("send", "use: dws chat message send"))
root.AddCommand(chatCompatibilityHintSubCmd("history", "use: dws chat message list --group <GROUP_OPEN_CONVERSATION_ID>"))
return root
}
+21 -12
View File
@@ -81,24 +81,33 @@ func TestCrossPlatformCoverageEvaluationRegressionChatSearchSpellingsAndNaturalB
})
}
func TestCrossPlatformCoverageChatMisroutedPathsRemainUnknownSubcommands(t *testing.T) {
func TestCrossPlatformCoverageChatStableCompatibilityHintsRemainAvailable(t *testing.T) {
root := newChatCommand()
if len(root.Aliases) != 1 || root.Aliases[0] != "im" {
t.Fatalf("chat aliases = %v, want [im]", root.Aliases)
}
for _, tc := range []struct {
path string
flag string
args []string
hint string
}{
{path: "send", flag: "--group"},
{path: "history", flag: "--group"},
{path: "send", args: []string{"send", "--group", "cid-stable", "--text", "hello"}, hint: "dws chat message send"},
{path: "history", args: []string{"history", "--group", "cid-stable", "--limit", "20"}, hint: "dws chat message list --group <GROUP_OPEN_CONVERSATION_ID>"},
} {
caller := &productExampleCaller{}
err := runChatCoverageCommand(t, caller, tc.path, tc.flag, "cid")
if err == nil || !strings.Contains(err.Error(), "unknown command") || !strings.Contains(err.Error(), tc.path) {
t.Fatalf("chat %s error = %v, want unknown command", tc.path, err)
command, remaining, err := root.Find([]string{tc.path})
if err != nil {
t.Fatalf("find chat %s: %v", tc.path, err)
}
if strings.Contains(err.Error(), "unknown flag") {
t.Fatalf("chat %s was misreported as a flag error: %v", tc.path, err)
if len(remaining) != 0 || command.Name() != tc.path {
t.Fatalf("find chat %s = command %q, remaining %v", tc.path, command.Name(), remaining)
}
if caller.calls != 0 {
t.Fatalf("chat %s tool calls = %d, want 0", tc.path, caller.calls)
if !command.Hidden || !command.Runnable() {
t.Fatalf("chat %s compatibility contract: hidden=%v runnable=%v", tc.path, command.Hidden, command.Runnable())
}
root.SetArgs(tc.args)
err = root.ExecuteContext(context.Background())
if err == nil || !strings.Contains(err.Error(), "ambiguous command") || !strings.Contains(err.Error(), tc.hint) {
t.Fatalf("chat %s with legacy flags error = %v, want migration hint %q", tc.path, err, tc.hint)
}
}
}
@@ -15,6 +15,7 @@ package helpers
import (
"context"
"encoding/json"
"io"
"os"
"reflect"
@@ -285,6 +286,150 @@ func TestChatSendAndReplyDisableAITagWithEmptyClawType(t *testing.T) {
}
}
func TestCrossPlatformCoverageChatCurrentUserSendAndReplyMentions(t *testing.T) {
tests := []struct {
name string
args []string
contentField string
wantContent string
wantAtAll bool
wantOpenIDs []string
}{
{
name: "send",
args: []string{
"message", "send", "--group", "cid",
"--text", "收到 @D-target 和 <@D-second>",
"--at-open-dingtalk-ids", "D-target,D-second",
"--at-all",
},
contentField: "text",
wantContent: "<@all> 收到 <@D-target> 和 <@D-second>",
wantAtAll: true,
wantOpenIDs: []string{"D-target", "D-second"},
},
{
name: "send keeps missing member placeholders unchanged",
args: []string{
"message", "send", "--group", "cid",
"--text", "DWS 发消息自测",
"--at-open-dingtalk-ids", "D-target",
},
contentField: "text",
wantContent: "DWS 发消息自测",
wantOpenIDs: []string{"D-target"},
},
{
name: "reply",
args: []string{
"message", "reply",
"--conversation-id", "cid",
"--ref-msg-id", "mid",
"--ref-sender", "D-sender",
"--text", "收到 @D-target 和 <@D-second>",
"--at-open-dingtalk-ids", "D-target,D-second",
"--at-all",
},
contentField: "content",
wantContent: "<@all> 收到 <@D-target> 和 <@D-second>",
wantAtAll: true,
wantOpenIDs: []string{"D-target", "D-second"},
},
{
name: "reply adds missing member placeholders",
args: []string{
"message", "reply",
"--conversation-id", "cid",
"--ref-msg-id", "mid",
"--ref-sender", "D-sender",
"--text", "DWS 回复艾特前津(非主用)自测",
"--at-open-dingtalk-ids", "D-target,D-second,D-target",
},
contentField: "content",
wantContent: "<@D-target> <@D-second> DWS 回复艾特前津(非主用)自测",
wantOpenIDs: []string{"D-target", "D-second", "D-target"},
},
{
name: "reply adds missing member placeholders after at-all",
args: []string{
"message", "reply",
"--conversation-id", "cid",
"--ref-msg-id", "mid",
"--ref-sender", "D-sender",
"--text", "请大家确认",
"--at-open-dingtalk-ids", "D-target",
"--at-all",
},
contentField: "content",
wantContent: "<@all> <@D-target> 请大家确认",
wantAtAll: true,
wantOpenIDs: []string{"D-target"},
},
{
name: "reply at-all preserves alliance word",
args: []string{
"message", "reply",
"--conversation-id", "cid",
"--ref-msg-id", "mid",
"--ref-sender", "D-sender",
"--text", "联系 @alliance",
"--at-all",
},
contentField: "content",
wantContent: "<@all> 联系 @alliance",
wantAtAll: true,
},
{
name: "reply without at flags preserves alliance word",
args: []string{
"message", "reply",
"--conversation-id", "cid",
"--ref-msg-id", "mid",
"--ref-sender", "D-sender",
"--text", "联系 @alliance",
},
contentField: "content",
wantContent: "联系 @alliance",
},
}
for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) {
caller := &chatChangedContractCaller{}
if err := executeChatChangedContract(t, caller, tc.args...); err != nil {
t.Fatal(err)
}
if len(caller.calls) != 1 || caller.calls[0].toolName != "send_personal_message" {
t.Fatalf("calls = %#v", caller.calls)
}
args := caller.calls[0].args
gotAtAll, hasAtAll := args["atAll"]
if tc.wantAtAll {
if !hasAtAll || gotAtAll != true {
t.Fatalf("atAll = %#v, present = %v; want true", gotAtAll, hasAtAll)
}
} else if hasAtAll {
t.Fatalf("atAll = %#v; want absent", gotAtAll)
}
gotOpenIDs, hasOpenIDs := args["atOpenDingTalkIds"]
if len(tc.wantOpenIDs) > 0 {
if !hasOpenIDs || !reflect.DeepEqual(gotOpenIDs, tc.wantOpenIDs) {
t.Fatalf("atOpenDingTalkIds = %#v, present = %v; want %#v", gotOpenIDs, hasOpenIDs, tc.wantOpenIDs)
}
} else if hasOpenIDs {
t.Fatalf("atOpenDingTalkIds = %#v; want absent", gotOpenIDs)
}
var content map[string]string
if err := json.Unmarshal([]byte(args["content"].(string)), &content); err != nil {
t.Fatal(err)
}
if got := content[tc.contentField]; got != tc.wantContent {
t.Fatalf("content[%q] = %q; want %q", tc.contentField, got, tc.wantContent)
}
})
}
}
func TestCrossPlatformCoverageChatSendFailsClosedWhenUserCannotResolve(t *testing.T) {
caller := &chatChangedContractCaller{}
err := executeChatChangedContract(t, caller, "message", "send", "--user", "123", "--text", "hello")
+6 -3
View File
@@ -115,8 +115,8 @@ func newDingCommand() *cobra.Command {
},
},
Parameters: []contract.ParamDecl{
{Name: "content", Required: boolPtr(true)},
{Name: "robot-code", Required: boolPtr(true)},
{Name: "content", Property: "content", Required: boolPtr(true)},
{Name: "robot-code", Property: "robotCode", Required: boolPtr(true)},
{Name: "type", Property: "remindType"},
{Name: "users", Property: "receiverUserIdList", Required: boolPtr(true), InterfaceType: "array"},
},
@@ -170,7 +170,10 @@ func newDingCommand() *cobra.Command {
Examples: []string{"dws ding message recall --robot-code <ROBOT_CODE> --id <OPEN_DING_ID> --format json"},
},
Parameters: []contract.ParamDecl{
{Name: "id", Property: "openDingId"},
// id is validateRequiredFlags-hard; robot-code is required unless
// DINGTALK_DING_ROBOT_CODE is set (same Agent-facing contract as send).
{Name: "id", Property: "openDingId", Required: boolPtr(true)},
{Name: "robot-code", Property: "robotCode", Required: boolPtr(true)},
},
},
})
+3 -1
View File
@@ -66,6 +66,8 @@ var (
driveFileStat = (*os.File).Stat
)
var driveWorkerContextErr = func(ctx context.Context) error { return ctx.Err() }
// ──────────────────────────────────────────────────────────
// HTTP 状态错误
// ──────────────────────────────────────────────────────────
@@ -629,7 +631,7 @@ func downloadRangedParts(ctx context.Context, creds *driveCredentialState, destP
go func() {
defer wg.Done()
for part := range jobs {
if runCtx.Err() != nil {
if driveWorkerContextErr(runCtx) != nil {
return
}
if err := downloadOnePart(runCtx, creds, f, part, totalSize); err != nil {
+25 -39
View File
@@ -13,6 +13,8 @@ import (
"sync/atomic"
"testing"
"time"
"github.com/DingTalk-Real-AI/dingtalk-workspace-cli/internal/testseam"
)
// ──────────────────────────────────────────────────────────
@@ -2868,48 +2870,32 @@ func TestCrossPlatformCoverageDriveDownloadVersionCancelNoResume(t *testing.T) {
func TestCrossPlatformCoverageDriveTransferWorkerCtxCancelBeforeProcess(t *testing.T) {
// 目标:覆盖 downloadRangedParts worker 中 "if runCtx.Err() != nil { return }"。
// 策略:让 workers 正常处理分片,通过 context timeout 在处理过程中过期。
// 当 worker 完成某个分片后循环回来收到新 job 时,发现 runCtx 已取消。
// transport 每次请求加 50μs 延迟,使总处理时间接近 timeout,最大化命中率。
totalSize := int64(200)
content := makeTestContent(int(totalSize))
origClient := driveRangeClient
t.Cleanup(func() { driveRangeClient = origClient })
driveRangeClient = &http.Client{
// 通过结构化 seam 让 worker 在收到唯一分片后确定性观察到取消状态;
// 不再依赖微秒级 timeout 与 goroutine 调度概率。
var checks atomic.Int32
testseam.Swap(t, &driveWorkerContextErr, func(context.Context) error {
checks.Add(1)
return context.Canceled
})
var requests atomic.Int32
testseam.Swap(t, &driveRangeClient, &http.Client{
Transport: roundTripFunc(func(req *http.Request) (*http.Response, error) {
// 每次请求加小延迟,让总处理时间接近 deadline
time.Sleep(50 * time.Microsecond)
var start, end int64
if _, err := fmt.Sscanf(req.Header.Get("Range"), "bytes=%d-%d", &start, &end); err != nil {
return &http.Response{StatusCode: 400, Body: io.NopCloser(strings.NewReader("bad"))}, nil
}
if end >= int64(len(content)) {
end = int64(len(content)) - 1
}
resp := &http.Response{
StatusCode: http.StatusPartialContent,
Header: make(http.Header),
Body: io.NopCloser(strings.NewReader(string(content[start : end+1]))),
}
resp.Header.Set("Content-Range", fmt.Sprintf("bytes %d-%d/%d", start, end, len(content)))
return resp, nil
requests.Add(1)
return nil, errors.New("worker context guard did not stop the request")
}),
})
creds := &driveCredentialState{url: "http://127.0.0.1:1/fake"}
dest := filepath.Join(t.TempDir(), "worker-context-guard.bin")
opts := driveDownloadOptions{partSize: 1, parallel: 1, resume: false, knownSize: 1}
if err := downloadRangedParts(context.Background(), creds, dest, 1, opts); err != nil {
t.Fatalf("downloadRangedParts context guard: %v", err)
}
// 多次尝试以确保覆盖(goroutine 调度非确定性)
for attempt := 0; attempt < 50; attempt++ {
// timeout 设为约为总处理时间的50%,确保在处理过程中过期
// 40分片/4workers=10轮*50μs=500μs,timeout设300μs使其在中间过期
ctx, cancel := context.WithTimeout(context.Background(), 300*time.Microsecond)
creds := &driveCredentialState{url: "http://127.0.0.1:1/fake"}
dest := filepath.Join(t.TempDir(), fmt.Sprintf("wkr-%d.bin", attempt))
opts := driveDownloadOptions{partSize: 5, parallel: 4, resume: false, knownSize: totalSize}
_ = downloadRangedParts(ctx, creds, dest, totalSize, opts)
cancel()
if checks.Load() != 1 {
t.Fatalf("worker context checks = %d, want 1", checks.Load())
}
if requests.Load() != 0 {
t.Fatalf("worker requests = %d, want 0", requests.Load())
}
}
+21
View File
@@ -49,6 +49,12 @@ var List = shortcut.Shortcut{
AvoidWhen: []string{"需要该 Shortcut 未公开的底层参数、原始响应或不同执行语义时,改用对应原子命令"},
Examples: []string{"dws ding +list"},
},
// Property matches CallMCP keys in Execute; type stays optional because
// the CLI default ALL satisfies the server non-empty requirement.
Parameters: []contract.ParamDecl{
{Name: "cursor", Property: "cursor"},
{Name: "type", Property: "type"},
},
},
Flags: []shortcut.Flag{
{Name: "cursor", Type: shortcut.FlagInt, Desc: "分页游标 (可选)"},
@@ -95,6 +101,9 @@ var ReceiverStatus = shortcut.Shortcut{
AvoidWhen: []string{"需要该 Shortcut 未公开的底层参数、原始响应或不同执行语义时,改用对应原子命令"},
Examples: []string{"dws ding +receiver-status --ding-id <DING_ID>"},
},
Parameters: []contract.ParamDecl{
{Name: "ding-id", Property: "openDingId", Required: boolPtr(true)},
},
},
Flags: []shortcut.Flag{
{Name: "ding-id", Type: shortcut.FlagString, Desc: "openDingId", Required: true},
@@ -133,6 +142,13 @@ var SendPersonal = shortcut.Shortcut{
AvoidWhen: []string{"需要该 Shortcut 未公开的底层参数、原始响应或不同执行语义时,改用对应原子命令"},
Examples: []string{"dws ding +send-personal --users <VALUES> --content <CONTENT>"},
},
// Property keys match send_personal_ding toolArgs (not flag-name camelCase).
Parameters: []contract.ParamDecl{
{Name: "users", Property: "receiverOpenDingTalkIds", Required: boolPtr(true)},
{Name: "content", Property: "content", Required: boolPtr(true)},
{Name: "type", Property: "remindType"},
{Name: "uuid", Property: "uuid"},
},
},
Flags: []shortcut.Flag{
{Name: "users", Type: shortcut.FlagStringSlice, Desc: "接收人 openDingTalkId 列表 (CSV)", Required: true},
@@ -204,6 +220,9 @@ var RecallPersonal = shortcut.Shortcut{
AvoidWhen: []string{"需要该 Shortcut 未公开的底层参数、原始响应或不同执行语义时,改用对应原子命令"},
Examples: []string{"dws ding +recall-personal --id <ID>"},
},
Parameters: []contract.ParamDecl{
{Name: "id", Property: "openDingId", Required: boolPtr(true)},
},
},
Flags: []shortcut.Flag{
{Name: "id", Type: shortcut.FlagString, Desc: "openDingId", Required: true},
@@ -215,6 +234,8 @@ var RecallPersonal = shortcut.Shortcut{
},
}
func boolPtr(v bool) *bool { return &v }
func init() {
shortcut.Register(
List,
+1 -1
View File
@@ -154,7 +154,7 @@ def product_section(service: str, rows: list[dict[str, Any]]) -> str:
return f"""{PRODUCT_START}
## Shortcuts(无专用脚本/recipe 时优先)
以下 shortcut 同时进入公开 catalog 与 Runtime Schema。先按本 skill 的意图表、脚本和 recipe 路由:存在精确覆盖该场景的专用脚本/recipe 时按其执行;否则用户意图命中时,shortcut 优先于手写原子命令。命令已选中时直接执行;只在参数或安全语义不确定时读取 leaf Schema(例如 `dws schema --cli-path "{service} +<shortcut>" --format json`),在当前 Cobra flags 不确定时读取 `dws {service} <shortcut> --help`。仅当现有路由和 reference 都无法定位低频能力时,才用 `dws shortcut list --service {service} --format json` 批量发现。
以下 shortcut 同时进入公开 catalog 与 Runtime Schema。先按本 skill 的意图表、脚本和 recipe 路由:存在精确覆盖该场景的专用脚本/recipe 时按其执行;否则用户意图命中时,shortcut 优先于手写原子命令。命令已选中时直接执行;只在参数或安全语义不确定时读取 Agent leaf Schema(例如 `dws schema --cli-path "{service} +<shortcut>" --compact --format json`),在当前 Cobra flags 不确定时读取 `dws {service} <shortcut> --help`。只有参数映射、接口绑定或 provenance 审计才省略 `--compact`。仅当现有路由和 reference 都无法定位低频能力时,才用 `dws shortcut list --service {service} --format json` 批量发现。
| Shortcut | 风险 | 适用场景 |
|---|---|---|
+62 -1
View File
@@ -730,7 +730,6 @@ func checkParameterCompatibility(toolPath, name string, oldParameter, newParamet
new string
}{
{name: "type", old: oldParameter.Type, new: newParameter.Type},
{name: "property", old: oldParameter.Property, new: newParameter.Property},
{name: "default", old: oldParameter.Default, new: newParameter.Default},
{name: "interface_default", old: oldParameter.InterfaceDefault, new: newParameter.InterfaceDefault},
{name: "format", old: oldParameter.Format, new: newParameter.Format},
@@ -739,6 +738,13 @@ func checkParameterCompatibility(toolPath, name string, oldParameter, newParamet
failures = append(failures, fmt.Sprintf("schema tool %q parameter %q changed %s", toolPath, name, field.name))
}
}
// Property remaps are incompatible unless a reviewed correction replaces
// flag-name inference with the ParamDecl-declared MCP field. Arbitrary
// non-empty A→B remaps remain a contract break.
if oldParameter.Property != newParameter.Property &&
!compatibleReviewedPropertyCorrection(toolPath, name, oldParameter.Property, newParameter.Property) {
failures = append(failures, fmt.Sprintf("schema tool %q parameter %q changed property", toolPath, name))
}
// Clearing interface_type is accepted as compatible: a deliberate,
// wire-visible policy decision taken with the pinned MCP metadata
// retirement. Production no longer projects MCP-sourced types unless
@@ -783,6 +789,61 @@ func enumNarrowed(oldValues, newValues []string) bool {
return false
}
// reviewedPropertyCorrection is an exact allowlist entry for replacing a
// historical wire property (typically flag-name inference) with the MCP field
// declared on leaf ParamDecl.Property. Do not use this to bless arbitrary
// remaps: each entry must name tool path, parameter, old property, and new
// property exactly.
type reviewedPropertyCorrection struct {
toolPath string
param string
oldProperty string
newProperty string
}
// Keep this list tiny and evidence-backed. Prefer ParamDecl declaration; only
// add an entry when schema-compat would otherwise freeze a wrong inferred
// property that agents bind against. ding pilot: Execute/CallMCP keys are the
// ground truth (see internal/shortcut/ding/ding.go).
var reviewedPropertyCorrections = []reviewedPropertyCorrection{
{
toolPath: "ding/ding.shortcut_receiver_status",
param: "ding-id",
oldProperty: "dingId",
newProperty: "openDingId",
},
{
toolPath: "ding/ding.shortcut_recall_personal",
param: "id",
oldProperty: "id",
newProperty: "openDingId",
},
{
toolPath: "ding/ding.shortcut_send_personal",
param: "users",
oldProperty: "users",
newProperty: "receiverOpenDingTalkIds",
},
{
toolPath: "ding/ding.shortcut_send_personal",
param: "type",
oldProperty: "type",
newProperty: "remindType",
},
}
func compatibleReviewedPropertyCorrection(toolPath, param, oldProperty, newProperty string) bool {
for _, entry := range reviewedPropertyCorrections {
if entry.toolPath == toolPath &&
entry.param == param &&
entry.oldProperty == oldProperty &&
entry.newProperty == newProperty {
return true
}
}
return false
}
func mergeContracts(historical, current schemaContract) (schemaContract, []string) {
failures := checkCompatibility(historical, current)
if len(failures) > 0 {
+86
View File
@@ -596,6 +596,92 @@ func TestCrossPlatformCoverageSchemaCompatMCPRetirementAndConstraintExpansion(t
}
}
func TestReviewedDingPropertyCorrections(t *testing.T) {
if !compatibleReviewedPropertyCorrection(
"ding/ding.shortcut_send_personal", "users", "users", "receiverOpenDingTalkIds",
) {
t.Fatal("reviewed ding +send-personal --users correction must be accepted")
}
if !compatibleReviewedPropertyCorrection(
"ding/ding.shortcut_send_personal", "type", "type", "remindType",
) {
t.Fatal("reviewed ding +send-personal --type correction must be accepted")
}
if !compatibleReviewedPropertyCorrection(
"ding/ding.shortcut_receiver_status", "ding-id", "dingId", "openDingId",
) {
t.Fatal("reviewed ding +receiver-status --ding-id correction must be accepted")
}
if !compatibleReviewedPropertyCorrection(
"ding/ding.shortcut_recall_personal", "id", "id", "openDingId",
) {
t.Fatal("reviewed ding +recall-personal --id correction must be accepted")
}
if compatibleReviewedPropertyCorrection(
"ding/ding.shortcut_send_personal", "users", "users", "userIds",
) {
t.Fatal("non-reviewed new property must remain incompatible")
}
if compatibleReviewedPropertyCorrection(
"ding/ding.send_ding_message", "users", "users", "receiverUserIdList",
) {
t.Fatal("unlisted tool/param remap must remain incompatible")
}
baseline := schemaContract{
Products: map[string]productSchema{
"ding": {Tools: map[string]toolSchema{
"ding.shortcut_send_personal": {
PrimaryCLIPath: "ding +send-personal",
InterfaceMode: "composite",
Availability: "available",
Effect: "write",
Risk: "medium",
Confirmation: "user_required",
Idempotency: "unknown",
Parameters: map[string]parameterSchema{
"users": {Type: "array", Property: "users"},
"type": {Type: "string", Property: "type"},
},
},
"ding.shortcut_receiver_status": {
PrimaryCLIPath: "ding +receiver-status",
InterfaceMode: "composite",
Availability: "available",
Effect: "read",
Risk: "low",
Confirmation: "not_required",
Idempotency: "idempotent",
Parameters: map[string]parameterSchema{
"ding-id": {Type: "string", Property: "dingId", Required: true},
},
},
}},
},
}
corrected := cloneContract(baseline)
corrected.Products["ding"].Tools["ding.shortcut_send_personal"].Parameters["users"] = parameterSchema{
Type: "array", Property: "receiverOpenDingTalkIds",
}
corrected.Products["ding"].Tools["ding.shortcut_send_personal"].Parameters["type"] = parameterSchema{
Type: "string", Property: "remindType",
}
corrected.Products["ding"].Tools["ding.shortcut_receiver_status"].Parameters["ding-id"] = parameterSchema{
Type: "string", Property: "openDingId", Required: true,
}
if failures := checkCompatibility(baseline, corrected); len(failures) != 0 {
t.Fatalf("reviewed ding property corrections should pass: %v", failures)
}
broken := cloneContract(baseline)
broken.Products["ding"].Tools["ding.shortcut_send_personal"].Parameters["users"] = parameterSchema{
Type: "array", Property: "userIds",
}
if failures := checkCompatibility(baseline, broken); !strings.Contains(strings.Join(failures, "\n"), "changed property") {
t.Fatalf("unreviewed property remap should fail: %v", failures)
}
}
func TestCrossPlatformCoverageSchemaCompatAdditiveConstraintEvolution(t *testing.T) {
oldTool := toolSchema{
Parameters: map[string]parameterSchema{
@@ -77,6 +77,10 @@ func run(rootPath string, root *cobra.Command, stdout, stderr io.Writer) int {
if skip || path == "" || antiCommands[path] {
continue
}
if issue := schemaProjectionIssue(ref.Text); issue != "" {
failures = append(failures, formatFailure(rootPath, ref, issue))
continue
}
if checked[path] {
continue
}
@@ -104,6 +108,55 @@ func run(rootPath string, root *cobra.Command, stdout, stderr io.Writer) int {
return 0
}
// schemaProjectionIssue keeps published Agent instructions on the bounded
// Schema projection. A targeted full leaf is intentionally reserved for
// mapping/provenance audits; those callers must select the exact fields they
// need with --jq/--fields instead of loading the full payload into context.
func schemaProjectionIssue(raw string) string {
tokens := shellFields(raw)
if len(tokens) < 2 || tokens[0] != "dws" || tokens[1] != "schema" {
return ""
}
var targeted, compact, selected, all bool
for i := 2; i < len(tokens); i++ {
token := tokens[i]
switch {
case token == "--compact":
compact = true
case token == "--all":
all = true
case token == "--cli-path":
targeted = true
if i+1 < len(tokens) {
i++
}
case strings.HasPrefix(token, "--cli-path="):
targeted = true
case token == "--jq" || token == "--fields":
selected = true
if i+1 < len(tokens) {
i++
}
case strings.HasPrefix(token, "--jq=") || strings.HasPrefix(token, "--fields="):
selected = true
case token == "--format" || token == "-f":
if i+1 < len(tokens) {
i++
}
case strings.HasPrefix(token, "--format="):
// Output encoding does not bound the Schema fields.
case strings.HasPrefix(token, "-"):
// Other global flags do not affect projection size.
default:
targeted = true
}
}
if targeted && !all && !compact && !selected {
return "targeted Schema queries in Agent instructions must use --compact (or an explicit --jq/--fields projection)"
}
return ""
}
func extractReferences(root string) ([]commandRef, error) {
var refs []commandRef
err := filepath.WalkDir(root, func(path string, entry fs.DirEntry, err error) error {
@@ -169,3 +169,27 @@ func TestIsPlaceholder(t *testing.T) {
}
}
}
func TestSchemaProjectionIssue(t *testing.T) {
tests := []struct {
command string
wantErr bool
}{
{command: `dws schema`, wantErr: false},
{command: `dws schema --all --format json`, wantErr: false},
{command: `dws schema calendar`, wantErr: true},
{command: `dws schema "calendar event create" -f json`, wantErr: true},
{command: `dws schema "calendar event create" --compact -f json`, wantErr: false},
{command: `dws schema --cli-path "chat +messages-send" --compact --format json`, wantErr: false},
{command: `dws schema --cli-path="chat +messages-send" --jq '.parameters'`, wantErr: false},
{command: `dws schema "ding message send" --fields parameters`, wantErr: false},
{command: `dws doc read --node <DOC_ID>`, wantErr: false},
}
for _, test := range tests {
t.Run(test.command, func(t *testing.T) {
if got := schemaProjectionIssue(test.command); (got != "") != test.wantErr {
t.Fatalf("schemaProjectionIssue() = %q, wantErr=%v", got, test.wantErr)
}
})
}
}
+12 -14
View File
@@ -9,7 +9,7 @@ cli_version: ">=1.0.15"
通过 `dws` 命令管理钉钉产品能力。
> ⚠️ **命令可用性以当前 dws 二进制为准**。服务发现已下线,本文档随内置 skill 发布;如果 `dws <cmd> --help` 不存在,说明当前版本未暴露该命令。`--help` 决定 Cobra 实际接受的 flags;公开基础命令和内建 `+` shortcut 的 leaf Schema 决定 Agent 选择、参数映射/约束和安全确认语义。若命令存在但调用失败,请按错误中的 endpoint 或 tool 提示确认静态端点目录和后端工具注册。实际调用前可用 `dws <cmd> --help` 或 `--dry-run` 验证。
> **命令可用性以当前 dws 二进制为准**。本文档随内置 skill 发布,可能滞后于二进制;如果 `dws <cmd> --help` 不存在,说明当前版本未暴露该命令。`--help` 决定 Cobra 实际接受的 flags;公开基础命令和内建 `+` shortcut 的 leaf Schema(常规用 `--compact`)决定 Agent 选择、参数/约束和安全确认语义。实际调用前可用 `dws <cmd> --help` 或 `--dry-run` 验证。
## 严格禁止 (NEVER DO)
- 不要使用 dws 命令以外的方式操作(禁止 curl、HTTP API、浏览器)
@@ -29,7 +29,7 @@ cli_version: ">=1.0.15"
`shortcut` 是对常用操作的高层封装,适合优先承担用户意图;产品参考文档和本 skill 负责判断意图、风险、跨产品流程和复杂参数,CLI 帮助负责声明当前版本真正可调用的命令。
- 先按产品参考、意图表和 recipe 路由。用户意图可由可见 Shortcut 满足时,优先使用 `dws <service> +<verb> ... --format json`,不要手写等价的多步原子命令。只有脚本明确补足 Shortcut 未覆盖的复合交付物且其安全/完整性契约仍适用时才选择脚本。
- 公开内建 shortcut 同时进入 Runtime Schema。用 `dws schema --cli-path "<service> +<verb>" --format json` 读取 Agent 选择、参数、跨参数约束、risk/confirmation 与接口语义;`dws shortcut list --service <service> --format json` 只作为轻量批量发现入口。
- 公开内建 shortcut 同时进入 Runtime Schema。用 `dws schema --cli-path "<service> +<verb>" --compact --format json` 读取 Agent 选择、参数、跨参数约束和 risk/confirmation;只有参数映射、接口绑定或 provenance 审计才通过 `--jq` 精确读取 full leaf;`dws shortcut list --service <service> --format json` 只作为轻量批量发现入口。
- 真正组装参数前用叶子帮助 `dws <service> +<verb> --help` 核对当前 Cobra 接受的 flags。父级 `dws <service> --help` 只能发现子命令,不能替代叶子参数帮助。
- shortcut catalog 中 `confirmation=user_required` 时,必须先获得用户确认,确认后才加 `--yes`;`not_required` 不额外确认。
- 如果 shortcut 不在 help / list 中,改用产品参考里的原子命令、脚本或标准流程;不要猜测未展示的 `+` 命令。
@@ -207,22 +207,19 @@ Step 3 → 加 --yes 执行命令
**已知命令路径例外**:当本 Skill、产品意图表或任务 reference 已经给出精确 CLI path 时,不要再查询产品级/分组级 Schema,也不要调用完整 Shortcut Catalog;可直接执行。只有参数、约束或安全语义不确定时才读取该命令的 leaf Schema,只有当前 Cobra flags 不确定时才补读 leaf Help。
稳定 command identity、主 CLI path 和 alias 已在构建时由 reviewed registry 与真实 Cobra tree 精确绑定。Agent 不应读取 Catalog 文件、native annotation 或其他生成 JSON 来重新推断命令;所有运行时查询都以当前二进制交付的 Schema 投影为准。
稳定 command identity、主 CLI path 和 alias 由 leaf `ContractFinal.Identity` 与真实 Cobra tree 精确绑定。Agent 不应读取 Catalog 文件、native annotation 或其他生成 JSON 来重新推断命令;所有运行时查询都以当前二进制交付的 Schema 投影为准。
```bash
# 第 1 层:产品概览(~4.5KB,列出全部产品 + 工具数 + 用途摘要)
dws schema
# 第 2 层:产品级(列出该产品下全部工具的 cli_path + description + effect/risk)
dws schema calendar
dws schema calendar --compact
# 第 3 层:分组级(按命令分组列出工具摘要)
dws schema "calendar event"
dws schema "calendar event" --compact
# 第 4 层:完整 leaf(参数契约:type/required/description/constraints/examples)
dws schema "calendar event create"
# --compact:当前支持;去除 provenance/debug 字段,仅保留 Agent 选参所需
# 第 4 层:Agent leaf(参数契约:type/required/description/constraints/examples)
dws schema "calendar event create" --compact
# --all:导出所有工具的完整 leaf Schema,仅用于 CI / 审计 / 参数 baseline
@@ -231,9 +228,9 @@ dws schema --all --format json
**`--all` 使用边界(强制)**:`--all` 会返回每个工具的完整参数、约束和安全语义,输出体积很大。仅在用户明确要求全量导出,或执行 CI、Catalog 审计、参数防丢 baseline 时使用。普通业务任务严禁使用 `--all` 做命令发现,也不要把全量结果直接注入 Agent 上下文;必须按“产品概览 → 产品/分组 → leaf”渐进查询。完整兼容性 baseline 必须使用未裁剪的 `schema --all`;`schema --all --compact` 会移除 provenance 和接口映射字段,不得作为完整 baseline。
同一个工具的 leaf 查询与 `--all` 条目是同一份 `ToolSpec` 契约的投影,参数、安全和接口语义必须一致。Alias 查询只改变路径视图;不得根据 alias 重写或补猜参数。若观察到内容差异,应作为契约漂移报告,而不是选择其中一份继续执行。
同一个工具省略 `--compact` 的 full leaf 与 `--all` 条目是同一份 `ToolSpec` 契约;compact leaf 只做展示投影,不重新解析语义。Alias 查询不得根据 alias 重写或补猜参数。若同一视图观察到内容差异,应作为契约漂移报告,而不是选择其中一份继续执行。
**--compact 模式**去掉的字段:`agent_metadata_source`、`agent_source_refs`、`agent_summary_source`、`effect_source`、`metadata_source`、`interface_ref`、`interface_description`、`property`、`primary_cli_path`、`parameter_count` 等 provenance/debug 字段。保留的字段:`cli_path`、`canonical_path`、`description`、`effect`、`risk`、`confirmation`、`interface_mode`、`availability`、`interface_reason`、`parameters`(含 `type`/`required`/`description`/`default`/`enum`)、`constraints`、`examples`、`use_when`、`avoid_when`。
**`--compact` Agent 模式**采用正向字段白名单。保留 `cli_path`、`canonical_path`、`description`、`effect`、`risk`、`confirmation`、`interface_mode`、`availability`、`interface_reason`、`parameters`(含 `type`/`required`/`description`/`default`/`enum`)、`constraints`、`examples`、`use_when`、`avoid_when`;新增 full/audit 字段不会自动泄漏进 Agent 上下文。它有意不返回 `interface_ref`、参数 `property/interface_type` 和 provenance;检查这些映射事实时,用 full leaf 配合 `--jq` 精确投影。
`--compact` 是 Schema 展示能力。当前版本支持;若兼容旧二进制时收到 `unknown_flag: --compact`,仅去掉 `--compact` 重跑同一个 Schema 查询。不要因此判定 leaf 不存在,也不要改用 Schema 查询业务数据。
@@ -272,7 +269,8 @@ dws schema --all --format json
| 信息 | 事实源 |
|------|--------|
| 命令是否存在、当前 Cobra 接受哪些 flags | `dws <cli_path> --help` |
| Agent 选择、参数映射/required/组合约束、risk/confirmation(原子/基础命令) | `dws schema "<cli_path>"`(按需加 `--compact`) |
| Agent 选择、CLI 参数/required/组合约束、risk/confirmation(原子/基础命令) | `dws schema "<cli_path>" --compact` |
| CLI↔RPC 参数映射、接口绑定或 provenance 审计 | full leaf 配合 `--jq` / `--fields` 精确投影;不要把整个 full leaf 注入 Agent 上下文 |
| shortcut 的参数、组合约束、risk/confirmation、示例 | 已知路径优先 `dws schema --cli-path "<service> +<shortcut>" --compact --format json`;完整 `shortcut list` 仅用于无法定位低频能力时的最后回退 |
| 人类可读用法 | `dws <cli_path> --help` |
| 钉钉中的文档、文件、日程、消息等实际数据 | 真正执行对应的 `read` / `search` / `list` 命令 |
@@ -287,9 +285,9 @@ Schema 与 Help 冲突是**契约漂移**,不得静默猜测或把两边字段
### Helper-only 与本地 Cobra 命令
`dev.*` 包含 helper-only 执行面,其中远端 helper 未进入 pinned metadata 时标记为 `composite`,不能伪装成 `local`。`event list` / `event schema` 读取内置目录和 payload 定义,属于 `local`;`event consume` / `event status` / `event stop` 同时编排远端个人订阅控制面与本地 bus/consume,属于 `composite`。实现来源不同,不改变统一查询边界:进入全局 `dws schema` 的命令必须先进入 reviewed CommandRegistry,并由同一 `ToolSpec` 投影到 leaf、产品/分组、`--all` 与 Catalog。不得在查询时重新调用 MCP `tools/list`,也不得把 Cobra 临时合成结果作为第二条 Schema 数据路径。
`dev.*` 包含 helper-only 执行面,其中远端 helper 未进入 pinned metadata 时标记为 `composite`,不能伪装成 `local`。`event list` / `event schema` 读取内置目录和 payload 定义,属于 `local`;`event consume` / `event status` / `event stop` 同时编排远端个人订阅控制面与本地 bus/consume,属于 `composite`。实现来源不同,不改变统一查询边界:进入全局 `dws schema` 的命令须由 leaf `ContractFinal.Identity` 声明收集,并由同一 `ToolSpec` 投影到 leaf、产品/分组、`--all` 与 Catalog。不得把 Cobra 临时合成结果作为第二条 Schema 数据路径。
事件需要区分两种 Schema:`dws event schema <event_key> --flatten` 查询 Agent 要消费的顶层业务字段;`dws schema "event consume"` 查询 CLI 命令参数。前者是真实业务命令,后者只读取最终内嵌 SchemaRegistry;不能相互替代。
事件需要区分两种 Schema:`dws event schema <event_key> --flatten` 查询 Agent 要消费的顶层业务字段;`dws schema "event consume" --compact` 查询 CLI 命令参数。前者是真实业务命令,后者只读取最终内嵌 SchemaRegistry;不能相互替代。
`source` 表示最终命令 identity 的来源,不表示运行时 backing;helper/local/MCP 实现机制读取 `interface_mode`、`availability` 和 provenance,不要假定 `dev.*` 必然是 `source=mcp:<server>`,也不要假定本地命令必然是 `source=cobra`。
+5 -1
View File
@@ -4,7 +4,7 @@
## Shortcut 优先路由
常见 Agent 意图优先使用公开 `+` Shortcut;下面的原子命令章节保留给需要特定原始返回结构、兼容参数或 Shortcut 未覆盖字段的场景。执行前用 `dws schema --cli-path "chat +<shortcut>" --format json` 读取最终参数、约束和确认语义。
常见 Agent 意图优先使用公开 `+` Shortcut;下面的原子命令章节保留给需要特定原始返回结构、兼容参数或 Shortcut 未覆盖字段的场景。执行前用 `dws schema --cli-path "chat +<shortcut>" --compact --format json` 读取最终参数、约束和确认语义。
| 意图 | 首选 |
|---|---|
@@ -1245,8 +1245,11 @@ Usage:
dws chat message reply [flags]
Example:
dws chat message reply --conversation-id <openConversationId> --ref-msg-id <openMessageId> --ref-sender <openDingTalkId> --text "收到,马上处理"
dws chat message reply --conversation-id <openConversationId> --ref-msg-id <openMessageId> --ref-sender <openDingTalkId> --text "请看一下" --at-open-dingtalk-ids <mentionedOpenDingTalkId>
# 被引用消息的 openMessageId、发送者 openDingTalkId 通过 dws chat message list 获取
Flags:
--at-all @所有人(仅群聊时生效;正文缺少 <@all> 时自动补齐)
--at-open-dingtalk-ids string @指定成员的 openDingTalkId 列表,逗号分隔(仅群聊时生效;正文缺少对应 <@id> 时自动补齐)
--conversation-id string 会话 openConversationId (必填,支持单聊/群聊)
--ref-msg-id string 被引用的消息 openMessageId (必填)
--ref-sender string 被引用消息的发送者 openDingTalkId (必填)
@@ -1256,6 +1259,7 @@ Flags:
注意:
- 以当前用户身份引用回复,语义同 chat message send;目前回复类型仅支持 text
- 群聊 @指定成员时,正文缺少对应 <@openDingTalkId> 会自动补齐,已有裸 @openDingTalkId 会规范化;--at-all 会自动补齐 <@all>
```
#### 转发单条消息 — 将一条消息从源会话转发到目标会话(源/目标均支持单聊/群聊)
+1 -1
View File
@@ -20,7 +20,7 @@
| `dws event stop <subscribe_id> --dry-run` / `--yes` | 先预览,再确认取消订阅并停止对应本地消费 |
| `dws event stop --all --dry-run` / `--yes` | 先预览,再确认清理当前身份下全部个人订阅 |
注意区分两个 schema:`dws event schema <event_key>` 查事件的输出字段;`dws schema "event consume"` 查 consume 命令自身的入参(统一内嵌 ToolSpec,含 parameters + 位置参数)。`source` 是 reviewed command identity 的 provenance;`event list/schema` 是 `interface_mode=local`,`event consume/status/stop` 因同时编排远端订阅控制面与本地 bus 而是 `interface_mode=composite`,不要把 identity 与实现机制混为一谈。
注意区分两个 schema:`dws event schema <event_key>` 查事件的输出字段;`dws schema "event consume" --compact` 查 consume 命令自身的入参(统一内嵌 ToolSpec,含 parameters + 位置参数)。`source` 是 reviewed command identity 的 provenance;`event list/schema` 是 `interface_mode=local`,`event consume/status/stop` 因同时编排远端订阅控制面与本地 bus 而是 `interface_mode=composite`,不要把 identity 与实现机制混为一谈。
## Event catalog
+1 -1
View File
@@ -20,7 +20,7 @@ metadata:
<!-- VISIBLE_SHORTCUTS_START -->
## Shortcuts(无专用脚本/recipe 时优先)
以下 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` 批量发现。
以下 shortcut 同时进入公开 catalog 与 Runtime Schema。先按本 skill 的意图表、脚本和 recipe 路由:存在精确覆盖该场景的专用脚本/recipe 时按其执行;否则用户意图命中时,shortcut 优先于手写原子命令。命令已选中时直接执行;只在参数或安全语义不确定时读取 Agent leaf Schema(例如 `dws schema --cli-path "aitable +<shortcut>" --compact --format json`),在当前 Cobra flags 不确定时读取 `dws aitable <shortcut> --help`。只有参数映射、接口绑定或 provenance 审计才省略 `--compact`。仅当现有路由和 reference 都无法定位低频能力时,才用 `dws shortcut list --service aitable --format json` 批量发现。
| Shortcut | 风险 | 适用场景 |
|---|---|---|
+1 -1
View File
@@ -20,7 +20,7 @@ metadata:
<!-- VISIBLE_SHORTCUTS_START -->
## Shortcuts(无专用脚本/recipe 时优先)
以下 shortcut 同时进入公开 catalog 与 Runtime Schema。先按本 skill 的意图表、脚本和 recipe 路由:存在精确覆盖该场景的专用脚本/recipe 时按其执行;否则用户意图命中时,shortcut 优先于手写原子命令。命令已选中时直接执行;只在参数或安全语义不确定时读取 leaf Schema(例如 `dws schema --cli-path "calendar +<shortcut>" --format json`),在当前 Cobra flags 不确定时读取 `dws calendar <shortcut> --help`。仅当现有路由和 reference 都无法定位低频能力时,才用 `dws shortcut list --service calendar --format json` 批量发现。
以下 shortcut 同时进入公开 catalog 与 Runtime Schema。先按本 skill 的意图表、脚本和 recipe 路由:存在精确覆盖该场景的专用脚本/recipe 时按其执行;否则用户意图命中时,shortcut 优先于手写原子命令。命令已选中时直接执行;只在参数或安全语义不确定时读取 Agent leaf Schema(例如 `dws schema --cli-path "calendar +<shortcut>" --compact --format json`),在当前 Cobra flags 不确定时读取 `dws calendar <shortcut> --help`。只有参数映射、接口绑定或 provenance 审计才省略 `--compact`。仅当现有路由和 reference 都无法定位低频能力时,才用 `dws shortcut list --service calendar --format json` 批量发现。
| Shortcut | 风险 | 适用场景 |
|---|---|---|
@@ -9,6 +9,6 @@ DWS 当前公开的是 `im.streaming-card.v1` 工作流契约,不是任意组
- callback:不支持。
参数、required 和 confirmation 读取
`dws schema --cli-path "chat +messages-send-card" -f json` 或
`dws schema --cli-path "chat +messages-update-card" -f json`。不要把 Lark card JSON 字段翻译成
`dws schema --cli-path "chat +messages-send-card" --compact -f json` 或
`dws schema --cli-path "chat +messages-update-card" --compact -f json`。不要把 Lark card JSON 字段翻译成
未发布的 DWS flags。
@@ -180,11 +180,18 @@ dws chat message edit --group <openConversationId> --msg-id <openMessageId> --co
| 命令 | 用途 | 必填参数 |
|------|------|----------|
| `message reply` | 引用回复,单聊/群聊均可 | `--conversation-id` `--ref-msg-id` `--ref-sender` `--text` |
| `message reply` | 引用回复,单聊/群聊均可;群聊可 @指定成员或 @所有人 | `--conversation-id` `--ref-msg-id` `--ref-sender` `--text`;可选 `--at-open-dingtalk-ids` `--at-all` |
| `message forward` | 转发单条消息,源/目标均支持单聊/群聊 | `--src-conversation-id` `--msg-id` `--dest-conversation-id` |
| `message combine-forward` | 多条消息合并为一条转发 | `--src-conversation-id` `--msg-ids` `--dest-conversation-id`,可选 `--uuid` |
| `message forward-topic` | 转发话题消息 | `--src-msg-id` `--src-conversation-id` `--src-thread-id` `--dest-conversation-id` |
群聊引用回复使用 `--at-open-dingtalk-ids` 传 `atOpenDingTalkIds`;正文缺少对应 `<@openDingTalkId>` 时自动补齐,已有裸 `@openDingTalkId` 会规范化。`--at-all` 会传 `atAll=true`,正文缺少 `<@all>` 时自动补齐。
```bash
dws chat message reply --conversation-id <openConversationId> --ref-msg-id <openMessageId> --ref-sender <senderOpenDingTalkId> --text "请看一下" --at-open-dingtalk-ids <mentionedOpenDingTalkId>
dws chat message reply --conversation-id <openConversationId> --ref-msg-id <openMessageId> --ref-sender <senderOpenDingTalkId> --text "请大家确认" --at-all
```
### 话题与卡片
话题完整读取流程:
+1 -1
View File
@@ -20,7 +20,7 @@ metadata:
<!-- VISIBLE_SHORTCUTS_START -->
## Shortcuts(无专用脚本/recipe 时优先)
以下 shortcut 同时进入公开 catalog 与 Runtime Schema。先按本 skill 的意图表、脚本和 recipe 路由:存在精确覆盖该场景的专用脚本/recipe 时按其执行;否则用户意图命中时,shortcut 优先于手写原子命令。命令已选中时直接执行;只在参数或安全语义不确定时读取 leaf Schema(例如 `dws schema --cli-path "contact +<shortcut>" --format json`),在当前 Cobra flags 不确定时读取 `dws contact <shortcut> --help`。仅当现有路由和 reference 都无法定位低频能力时,才用 `dws shortcut list --service contact --format json` 批量发现。
以下 shortcut 同时进入公开 catalog 与 Runtime Schema。先按本 skill 的意图表、脚本和 recipe 路由:存在精确覆盖该场景的专用脚本/recipe 时按其执行;否则用户意图命中时,shortcut 优先于手写原子命令。命令已选中时直接执行;只在参数或安全语义不确定时读取 Agent leaf Schema(例如 `dws schema --cli-path "contact +<shortcut>" --compact --format json`),在当前 Cobra flags 不确定时读取 `dws contact <shortcut> --help`。只有参数映射、接口绑定或 provenance 审计才省略 `--compact`。仅当现有路由和 reference 都无法定位低频能力时,才用 `dws shortcut list --service contact --format json` 批量发现。
| Shortcut | 风险 | 适用场景 |
|---|---|---|
+1 -1
View File
@@ -29,7 +29,7 @@ metadata:
<!-- VISIBLE_SHORTCUTS_START -->
## Shortcuts(无专用脚本/recipe 时优先)
以下 shortcut 同时进入公开 catalog 与 Runtime Schema。先按本 skill 的意图表、脚本和 recipe 路由:存在精确覆盖该场景的专用脚本/recipe 时按其执行;否则用户意图命中时,shortcut 优先于手写原子命令。命令已选中时直接执行;只在参数或安全语义不确定时读取 leaf Schema(例如 `dws schema --cli-path "devapp +<shortcut>" --format json`),在当前 Cobra flags 不确定时读取 `dws devapp <shortcut> --help`。仅当现有路由和 reference 都无法定位低频能力时,才用 `dws shortcut list --service devapp --format json` 批量发现。
以下 shortcut 同时进入公开 catalog 与 Runtime Schema。先按本 skill 的意图表、脚本和 recipe 路由:存在精确覆盖该场景的专用脚本/recipe 时按其执行;否则用户意图命中时,shortcut 优先于手写原子命令。命令已选中时直接执行;只在参数或安全语义不确定时读取 Agent leaf Schema(例如 `dws schema --cli-path "devapp +<shortcut>" --compact --format json`),在当前 Cobra flags 不确定时读取 `dws devapp <shortcut> --help`。只有参数映射、接口绑定或 provenance 审计才省略 `--compact`。仅当现有路由和 reference 都无法定位低频能力时,才用 `dws shortcut list --service devapp --format json` 批量发现。
| Shortcut | 风险 | 适用场景 |
|---|---|---|
+1 -1
View File
@@ -30,7 +30,7 @@ metadata:
<!-- VISIBLE_SHORTCUTS_START -->
## Shortcuts(无专用脚本/recipe 时优先)
以下 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` 批量发现。
以下 shortcut 同时进入公开 catalog 与 Runtime Schema。先按本 skill 的意图表、脚本和 recipe 路由:存在精确覆盖该场景的专用脚本/recipe 时按其执行;否则用户意图命中时,shortcut 优先于手写原子命令。命令已选中时直接执行;只在参数或安全语义不确定时读取 Agent leaf Schema(例如 `dws schema --cli-path "doc +<shortcut>" --compact --format json`),在当前 Cobra flags 不确定时读取 `dws doc <shortcut> --help`。只有参数映射、接口绑定或 provenance 审计才省略 `--compact`。仅当现有路由和 reference 都无法定位低频能力时,才用 `dws shortcut list --service doc --format json` 批量发现。
| Shortcut | 风险 | 适用场景 |
|---|---|---|
+1 -1
View File
@@ -20,7 +20,7 @@ metadata:
<!-- VISIBLE_SHORTCUTS_START -->
## Shortcuts(无专用脚本/recipe 时优先)
以下 shortcut 同时进入公开 catalog 与 Runtime Schema。先按本 skill 的意图表、脚本和 recipe 路由:存在精确覆盖该场景的专用脚本/recipe 时按其执行;否则用户意图命中时,shortcut 优先于手写原子命令。命令已选中时直接执行;只在参数或安全语义不确定时读取 leaf Schema(例如 `dws schema --cli-path "drive +<shortcut>" --format json`),在当前 Cobra flags 不确定时读取 `dws drive <shortcut> --help`。仅当现有路由和 reference 都无法定位低频能力时,才用 `dws shortcut list --service drive --format json` 批量发现。
以下 shortcut 同时进入公开 catalog 与 Runtime Schema。先按本 skill 的意图表、脚本和 recipe 路由:存在精确覆盖该场景的专用脚本/recipe 时按其执行;否则用户意图命中时,shortcut 优先于手写原子命令。命令已选中时直接执行;只在参数或安全语义不确定时读取 Agent leaf Schema(例如 `dws schema --cli-path "drive +<shortcut>" --compact --format json`),在当前 Cobra flags 不确定时读取 `dws drive <shortcut> --help`。只有参数映射、接口绑定或 provenance 审计才省略 `--compact`。仅当现有路由和 reference 都无法定位低频能力时,才用 `dws shortcut list --service drive --format json` 批量发现。
| Shortcut | 风险 | 适用场景 |
|---|---|---|
+1 -1
View File
@@ -94,7 +94,7 @@ kind + events + target
- 已知 Golden Route 时直接执行,不先跑 `event list`。
- 只有解析业务字段时才用 `dws event schema <event_key> --flatten`。
- 只有参数或安全不确定时才用 `dws schema --cli-path "event +listen-im"` 或对应 leaf。
- 只有参数或安全不确定时才用 `dws schema --cli-path "event +listen-im" --compact` 或对应 compact leaf。
- `event schema` 描述事件 payload;顶层 `dws schema` 描述 CLI 命令,两者不要混用。
## Reference
@@ -54,5 +54,5 @@
## Schema 边界
- 业务 payload:`dws event schema <event_key> --flatten`。
- CLI 参数/安全:`dws schema --cli-path "event +listen-im" -f json` 或精确 consume leaf。
- CLI 参数/安全:`dws schema --cli-path "event +listen-im" --compact -f json` 或精确 compact consume leaf。
- `--flatten` 的 `jq_root_path` 为 `.`;兼容 transport envelope 才使用 `.data | fromjson`。
+1 -1
View File
@@ -18,7 +18,7 @@ metadata:
<!-- SAFETY_PREAMBLE_INJECT -->
> ⚠️ **命令可用性以当前 dws 二进制为准**。服务发现已下线,本文档随内置 skill 发布;如果 `dws <cmd> --help` 不存在,说明当前版本未暴露该命令。若命令存在但调用失败,请按错误中的 endpoint 或 tool 提示确认静态端点目录和后端工具注册。实际调用前可用 `dws <cmd> --help` 或 `--dry-run` 验证。
> **命令可用性以当前 dws 二进制为准**。本文档随内置 skill 发布,可能滞后于二进制;如果 `dws <cmd> --help` 不存在,说明当前版本未暴露该命令。实际调用前可用 `dws <cmd> --help` 或 `--dry-run` 验证。
> 命令参考:[hrbrain.md](references/hrbrain.md)。
+1 -1
View File
@@ -20,7 +20,7 @@ metadata:
<!-- VISIBLE_SHORTCUTS_START -->
## Shortcuts(无专用脚本/recipe 时优先)
以下 shortcut 同时进入公开 catalog 与 Runtime Schema。先按本 skill 的意图表、脚本和 recipe 路由:存在精确覆盖该场景的专用脚本/recipe 时按其执行;否则用户意图命中时,shortcut 优先于手写原子命令。命令已选中时直接执行;只在参数或安全语义不确定时读取 leaf Schema(例如 `dws schema --cli-path "mail +<shortcut>" --format json`),在当前 Cobra flags 不确定时读取 `dws mail <shortcut> --help`。仅当现有路由和 reference 都无法定位低频能力时,才用 `dws shortcut list --service mail --format json` 批量发现。
以下 shortcut 同时进入公开 catalog 与 Runtime Schema。先按本 skill 的意图表、脚本和 recipe 路由:存在精确覆盖该场景的专用脚本/recipe 时按其执行;否则用户意图命中时,shortcut 优先于手写原子命令。命令已选中时直接执行;只在参数或安全语义不确定时读取 Agent leaf Schema(例如 `dws schema --cli-path "mail +<shortcut>" --compact --format json`),在当前 Cobra flags 不确定时读取 `dws mail <shortcut> --help`。只有参数映射、接口绑定或 provenance 审计才省略 `--compact`。仅当现有路由和 reference 都无法定位低频能力时,才用 `dws shortcut list --service mail --format json` 批量发现。
| Shortcut | 风险 | 适用场景 |
|---|---|---|
+1 -1
View File
@@ -18,7 +18,7 @@ metadata:
<!-- SAFETY_PREAMBLE_INJECT -->
> ⚠️ **命令可用性以当前 dws 二进制为准**。服务发现已下线,本文档随内置 skill 发布;如果 `dws <cmd> --help` 不存在,说明当前版本未暴露该命令。若命令存在但调用失败,请按错误中的 endpoint 或 tool 提示确认静态端点目录和后端工具注册。实际调用前可用 `dws <cmd> --help` 或 `--dry-run` 验证。
> **命令可用性以当前 dws 二进制为准**。本文档随内置 skill 发布,可能滞后于二进制;如果 `dws <cmd> --help` 不存在,说明当前版本未暴露该命令。实际调用前可用 `dws <cmd> --help` 或 `--dry-run` 验证。
> 命令参考:[markdown.md](references/markdown.md)。
+1 -1
View File
@@ -20,7 +20,7 @@ metadata:
<!-- VISIBLE_SHORTCUTS_START -->
## Shortcuts(无专用脚本/recipe 时优先)
以下 shortcut 同时进入公开 catalog 与 Runtime Schema。先按本 skill 的意图表、脚本和 recipe 路由:存在精确覆盖该场景的专用脚本/recipe 时按其执行;否则用户意图命中时,shortcut 优先于手写原子命令。命令已选中时直接执行;只在参数或安全语义不确定时读取 leaf Schema(例如 `dws schema --cli-path "minutes +<shortcut>" --format json`),在当前 Cobra flags 不确定时读取 `dws minutes <shortcut> --help`。仅当现有路由和 reference 都无法定位低频能力时,才用 `dws shortcut list --service minutes --format json` 批量发现。
以下 shortcut 同时进入公开 catalog 与 Runtime Schema。先按本 skill 的意图表、脚本和 recipe 路由:存在精确覆盖该场景的专用脚本/recipe 时按其执行;否则用户意图命中时,shortcut 优先于手写原子命令。命令已选中时直接执行;只在参数或安全语义不确定时读取 Agent leaf Schema(例如 `dws schema --cli-path "minutes +<shortcut>" --compact --format json`),在当前 Cobra flags 不确定时读取 `dws minutes <shortcut> --help`。只有参数映射、接口绑定或 provenance 审计才省略 `--compact`。仅当现有路由和 reference 都无法定位低频能力时,才用 `dws shortcut list --service minutes --format json` 批量发现。
| Shortcut | 风险 | 适用场景 |
|---|---|---|
@@ -1411,7 +1411,7 @@ dws attendance boss-check --plan-id 948964045503 --time "2026-05-13 18:00" --res
<!-- VISIBLE_SHORTCUTS_START -->
## Shortcuts(无专用脚本/recipe 时优先)
以下 shortcut 同时进入公开 catalog 与 Runtime Schema。先按本 skill 的意图表、脚本和 recipe 路由:存在精确覆盖该场景的专用脚本/recipe 时按其执行;否则用户意图命中时,shortcut 优先于手写原子命令。命令已选中时直接执行;只在参数或安全语义不确定时读取 leaf Schema(例如 `dws schema --cli-path "attendance +<shortcut>" --format json`),在当前 Cobra flags 不确定时读取 `dws attendance <shortcut> --help`。仅当现有路由和 reference 都无法定位低频能力时,才用 `dws shortcut list --service attendance --format json` 批量发现。
以下 shortcut 同时进入公开 catalog 与 Runtime Schema。先按本 skill 的意图表、脚本和 recipe 路由:存在精确覆盖该场景的专用脚本/recipe 时按其执行;否则用户意图命中时,shortcut 优先于手写原子命令。命令已选中时直接执行;只在参数或安全语义不确定时读取 Agent leaf Schema(例如 `dws schema --cli-path "attendance +<shortcut>" --compact --format json`),在当前 Cobra flags 不确定时读取 `dws attendance <shortcut> --help`。只有参数映射、接口绑定或 provenance 审计才省略 `--compact`。仅当现有路由和 reference 都无法定位低频能力时,才用 `dws shortcut list --service attendance --format json` 批量发现。
| Shortcut | 风险 | 适用场景 |
|---|---|---|
@@ -169,7 +169,7 @@ dws ding message recall-personal --id <OPEN_DING_ID> --format json
<!-- VISIBLE_SHORTCUTS_START -->
## Shortcuts(无专用脚本/recipe 时优先)
以下 shortcut 同时进入公开 catalog 与 Runtime Schema。先按本 skill 的意图表、脚本和 recipe 路由:存在精确覆盖该场景的专用脚本/recipe 时按其执行;否则用户意图命中时,shortcut 优先于手写原子命令。命令已选中时直接执行;只在参数或安全语义不确定时读取 leaf Schema(例如 `dws schema --cli-path "ding +<shortcut>" --format json`),在当前 Cobra flags 不确定时读取 `dws ding <shortcut> --help`。仅当现有路由和 reference 都无法定位低频能力时,才用 `dws shortcut list --service ding --format json` 批量发现。
以下 shortcut 同时进入公开 catalog 与 Runtime Schema。先按本 skill 的意图表、脚本和 recipe 路由:存在精确覆盖该场景的专用脚本/recipe 时按其执行;否则用户意图命中时,shortcut 优先于手写原子命令。命令已选中时直接执行;只在参数或安全语义不确定时读取 Agent leaf Schema(例如 `dws schema --cli-path "ding +<shortcut>" --compact --format json`),在当前 Cobra flags 不确定时读取 `dws ding <shortcut> --help`。只有参数映射、接口绑定或 provenance 审计才省略 `--compact`。仅当现有路由和 reference 都无法定位低频能力时,才用 `dws shortcut list --service ding --format json` 批量发现。
| Shortcut | 风险 | 适用场景 |
|---|---|---|
+1 -1
View File
@@ -875,7 +875,7 @@ dws oa approval create-instance --request '{"processCode":"PROC-xxx","deptId":-1
<!-- VISIBLE_SHORTCUTS_START -->
## Shortcuts(无专用脚本/recipe 时优先)
以下 shortcut 同时进入公开 catalog 与 Runtime Schema。先按本 skill 的意图表、脚本和 recipe 路由:存在精确覆盖该场景的专用脚本/recipe 时按其执行;否则用户意图命中时,shortcut 优先于手写原子命令。命令已选中时直接执行;只在参数或安全语义不确定时读取 leaf Schema(例如 `dws schema --cli-path "oa +<shortcut>" --format json`),在当前 Cobra flags 不确定时读取 `dws oa <shortcut> --help`。仅当现有路由和 reference 都无法定位低频能力时,才用 `dws shortcut list --service oa --format json` 批量发现。
以下 shortcut 同时进入公开 catalog 与 Runtime Schema。先按本 skill 的意图表、脚本和 recipe 路由:存在精确覆盖该场景的专用脚本/recipe 时按其执行;否则用户意图命中时,shortcut 优先于手写原子命令。命令已选中时直接执行;只在参数或安全语义不确定时读取 Agent leaf Schema(例如 `dws schema --cli-path "oa +<shortcut>" --compact --format json`),在当前 Cobra flags 不确定时读取 `dws oa <shortcut> --help`。只有参数映射、接口绑定或 provenance 审计才省略 `--compact`。仅当现有路由和 reference 都无法定位低频能力时,才用 `dws shortcut list --service oa --format json` 批量发现。
| Shortcut | 风险 | 适用场景 |
|---|---|---|
@@ -445,7 +445,7 @@ dws report outbox list --cursor 0 --size 20 --format json
<!-- VISIBLE_SHORTCUTS_START -->
## Shortcuts(无专用脚本/recipe 时优先)
以下 shortcut 同时进入公开 catalog 与 Runtime Schema。先按本 skill 的意图表、脚本和 recipe 路由:存在精确覆盖该场景的专用脚本/recipe 时按其执行;否则用户意图命中时,shortcut 优先于手写原子命令。命令已选中时直接执行;只在参数或安全语义不确定时读取 leaf Schema(例如 `dws schema --cli-path "report +<shortcut>" --format json`),在当前 Cobra flags 不确定时读取 `dws report <shortcut> --help`。仅当现有路由和 reference 都无法定位低频能力时,才用 `dws shortcut list --service report --format json` 批量发现。
以下 shortcut 同时进入公开 catalog 与 Runtime Schema。先按本 skill 的意图表、脚本和 recipe 路由:存在精确覆盖该场景的专用脚本/recipe 时按其执行;否则用户意图命中时,shortcut 优先于手写原子命令。命令已选中时直接执行;只在参数或安全语义不确定时读取 Agent leaf Schema(例如 `dws schema --cli-path "report +<shortcut>" --compact --format json`),在当前 Cobra flags 不确定时读取 `dws report <shortcut> --help`。只有参数映射、接口绑定或 provenance 审计才省略 `--compact`。仅当现有路由和 reference 都无法定位低频能力时,才用 `dws shortcut list --service report --format json` 批量发现。
| Shortcut | 风险 | 适用场景 |
|---|---|---|
@@ -210,7 +210,7 @@ Flags:
<!-- VISIBLE_SHORTCUTS_START -->
## Shortcuts(无专用脚本/recipe 时优先)
以下 shortcut 同时进入公开 catalog 与 Runtime Schema。先按本 skill 的意图表、脚本和 recipe 路由:存在精确覆盖该场景的专用脚本/recipe 时按其执行;否则用户意图命中时,shortcut 优先于手写原子命令。命令已选中时直接执行;只在参数或安全语义不确定时读取 leaf Schema(例如 `dws schema --cli-path "sheet +<shortcut>" --format json`),在当前 Cobra flags 不确定时读取 `dws sheet <shortcut> --help`。仅当现有路由和 reference 都无法定位低频能力时,才用 `dws shortcut list --service sheet --format json` 批量发现。
以下 shortcut 同时进入公开 catalog 与 Runtime Schema。先按本 skill 的意图表、脚本和 recipe 路由:存在精确覆盖该场景的专用脚本/recipe 时按其执行;否则用户意图命中时,shortcut 优先于手写原子命令。命令已选中时直接执行;只在参数或安全语义不确定时读取 Agent leaf Schema(例如 `dws schema --cli-path "sheet +<shortcut>" --compact --format json`),在当前 Cobra flags 不确定时读取 `dws sheet <shortcut> --help`。只有参数映射、接口绑定或 provenance 审计才省略 `--compact`。仅当现有路由和 reference 都无法定位低频能力时,才用 `dws shortcut list --service sheet --format json` 批量发现。
| Shortcut | 风险 | 适用场景 |
|---|---|---|
+1 -1
View File
@@ -20,7 +20,7 @@ metadata:
<!-- VISIBLE_SHORTCUTS_START -->
## Shortcuts(无专用脚本/recipe 时优先)
以下 shortcut 同时进入公开 catalog 与 Runtime Schema。先按本 skill 的意图表、脚本和 recipe 路由:存在精确覆盖该场景的专用脚本/recipe 时按其执行;否则用户意图命中时,shortcut 优先于手写原子命令。命令已选中时直接执行;只在参数或安全语义不确定时读取 leaf Schema(例如 `dws schema --cli-path "todo +<shortcut>" --format json`),在当前 Cobra flags 不确定时读取 `dws todo <shortcut> --help`。仅当现有路由和 reference 都无法定位低频能力时,才用 `dws shortcut list --service todo --format json` 批量发现。
以下 shortcut 同时进入公开 catalog 与 Runtime Schema。先按本 skill 的意图表、脚本和 recipe 路由:存在精确覆盖该场景的专用脚本/recipe 时按其执行;否则用户意图命中时,shortcut 优先于手写原子命令。命令已选中时直接执行;只在参数或安全语义不确定时读取 Agent leaf Schema(例如 `dws schema --cli-path "todo +<shortcut>" --compact --format json`),在当前 Cobra flags 不确定时读取 `dws todo <shortcut> --help`。只有参数映射、接口绑定或 provenance 审计才省略 `--compact`。仅当现有路由和 reference 都无法定位低频能力时,才用 `dws shortcut list --service todo --format json` 批量发现。
| Shortcut | 风险 | 适用场景 |
|---|---|---|
+1 -1
View File
@@ -20,7 +20,7 @@ metadata:
<!-- VISIBLE_SHORTCUTS_START -->
## Shortcuts(无专用脚本/recipe 时优先)
以下 shortcut 同时进入公开 catalog 与 Runtime Schema。先按本 skill 的意图表、脚本和 recipe 路由:存在精确覆盖该场景的专用脚本/recipe 时按其执行;否则用户意图命中时,shortcut 优先于手写原子命令。命令已选中时直接执行;只在参数或安全语义不确定时读取 leaf Schema(例如 `dws schema --cli-path "wiki +<shortcut>" --format json`),在当前 Cobra flags 不确定时读取 `dws wiki <shortcut> --help`。仅当现有路由和 reference 都无法定位低频能力时,才用 `dws shortcut list --service wiki --format json` 批量发现。
以下 shortcut 同时进入公开 catalog 与 Runtime Schema。先按本 skill 的意图表、脚本和 recipe 路由:存在精确覆盖该场景的专用脚本/recipe 时按其执行;否则用户意图命中时,shortcut 优先于手写原子命令。命令已选中时直接执行;只在参数或安全语义不确定时读取 Agent leaf Schema(例如 `dws schema --cli-path "wiki +<shortcut>" --compact --format json`),在当前 Cobra flags 不确定时读取 `dws wiki <shortcut> --help`。只有参数映射、接口绑定或 provenance 审计才省略 `--compact`。仅当现有路由和 reference 都无法定位低频能力时,才用 `dws shortcut list --service wiki --format json` 批量发现。
| Shortcut | 风险 | 适用场景 |
|---|---|---|
@@ -112,7 +112,7 @@ JSON 模式下,失败写到 stderr,稳定外层结构为:
```bash
# 决定 Agent 选路、参数映射与确认语义
dws schema --cli-path "chat +messages-send" --format json
dws schema --cli-path "chat +messages-send" --compact --format json
# 核对当前二进制真实接受的 flag
dws chat +messages-send --help
+1 -1
View File
@@ -83,7 +83,7 @@ var (
// Layer A still validates their Cobra registration. Executing them here can
// trigger local setup, authentication, upgrades, or long-running consumers.
mockUnsupportedRoots = map[string]bool{
"api": true, "auth": true, "cache": true, "catalog": true,
"api": true, "auth": true, "catalog": true,
"completion": true, "config": true, "doctor": true, "event": true,
"mcp": true, "plugin": true, "profile": true, "recovery": true,
"schema": true, "skill": true, "upgrade": true, "version": true,