Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4de41c27c7 | ||
|
|
5df2860e66 | ||
|
|
f3390b6875 | ||
|
|
55f25d8d48 | ||
|
|
67fbf65916 | ||
|
|
7f82e46adf | ||
|
|
1fb1ae3e23 | ||
|
|
fd0ab16c7f | ||
|
|
daaad35f5b | ||
|
|
953a36f4c9 | ||
|
|
e015f40ae2 | ||
|
|
84226b963c | ||
|
|
1d1c06aaae | ||
|
|
f34f9e8223 | ||
|
|
a54ee24acb | ||
|
|
320582f98c | ||
|
|
e04ff5a12b | ||
|
|
c569def067 | ||
|
|
584b1bd9d4 | ||
|
|
c350311048 | ||
|
|
158e7ec701 | ||
|
|
125a101487 |
@@ -6,6 +6,44 @@ The format is inspired by [Keep a Changelog](https://keepachangelog.com/) and th
|
||||
|
||||
## [Unreleased]
|
||||
|
||||
## [1.0.56-beta.2] - 2026-07-30
|
||||
|
||||
This beta adds PRs #831 and #835 on top of v1.0.56-beta.1. It separates
|
||||
Agent Product observability and IM display identity from the stable
|
||||
edition-owned PAT and routing identity, and reduces common-path Skill context
|
||||
loading without changing the public command or Runtime Schema surface.
|
||||
|
||||
### Changed
|
||||
|
||||
- **Agent Product identity separation** (#831) — sends `DWS_AGENT_PRODUCT` through the new `x-dws-agent-product` observability Header and uses a valid non-empty value for the IM `clawType` display label whenever `--ai-tag` is enabled. Because `--ai-tag` defaults to `true`, callers that set `DWS_AGENT_PRODUCT` change the displayed label by default. With `--ai-tag=false`, native `chat message send` / `reply` calls preserve their existing wire shape by sending an empty IM `clawType`, while shortcut calls omit the argument. Unset or empty Product values omit the Header and preserve the active edition's IM display default.
|
||||
- **Agent Host dimension convention** (#831) — new integrations should send the runtime form (`cloud` or `desktop`) through `DWS_AGENT_HOST` and report the product separately through `DWS_AGENT_PRODUCT`. Legacy combined labels such as `qwenwork_cloud` remain syntactically valid for compatibility.
|
||||
- **Reduced common-path Skill context** (#835) — keeps the complete 97-command Chat Shortcut inventory in Runtime Catalog and leaf Schema while routing common intents through compact Skill tables and references. The generated Skill policy now detects drift, forced full-reference loading, and context-budget regressions; the common Chat plus shared activation estimate drops from 7,301 to 4,771 `o200k_base` tokens without changing the 845-tool Schema surface.
|
||||
|
||||
### Fixed
|
||||
|
||||
- **Stable PAT/routing identity** (#831) — restores the CLI-emitted open-source HTTP `claw-type` and PAT `hostControl.clawType` to the edition-fixed `openClaw` value. `DWS_AGENT_PRODUCT` no longer changes those wire values, and the client continues to derive PAT, authentication, routing, and Discovery behaviour from the existing independent signals.
|
||||
|
||||
## [1.0.56-beta.1] - 2026-07-30
|
||||
|
||||
This beta starts the v1.0.56 line on top of v1.0.55 and packages PRs #817,
|
||||
#806, and #834, together with release-validation fixes #838 and #839. It closes
|
||||
the remaining Agent-visible IM shortcut gaps, introduces reviewed
|
||||
command-scoped parameter normalization without guessing business identifiers
|
||||
or values, and prevents deterministic personal-event subscription failures
|
||||
from becoming unbounded retry storms.
|
||||
|
||||
### Added
|
||||
|
||||
- **Complete IM shortcut workflows** (#817) — publishes the previously excluded `+chat-messages`, `+messages-send`, `+messages-send-card`, `+search-msg`, and `+thread-replies` shortcuts in Runtime Schema. Unified send, streaming-card delivery, advanced search, thread replies, and opt-in resource downloads now share reviewed parameters, selection guidance, and runtime-aligned safety semantics.
|
||||
- **Reviewed parameter concept normalization** (#806) — adds a closed parameter-concept dictionary and generated command-level alias table, covering reviewed IM synonyms while preserving the boundaries between group, conversation, user, open-user, cursor, and paging identifiers.
|
||||
|
||||
### Fixed
|
||||
|
||||
- **Message delivery and resource handling** (#817) — resolves direct recipients through exact contact search, preserves rich and nested message resources, avoids same-name download overwrites, and prevents read shortcuts from silently returning empty results on non-interactive input.
|
||||
- **Parameter parsing safety** (#806) — rejects ambiguous, blocked, or conflicting aliases before dispatch, normalizes explicit boolean values such as `--dry-run false`, and keeps internal pre-parse handler details out of user-visible errors.
|
||||
- **Personal-event subscription retry safety** (#834) — adds cross-process attempt claims, deterministic backoff and jitter, `Retry-After` handling, terminal holds, compare-and-swap completion, and fail-closed state handling across all public personal-event subscriptions, preventing deterministic failures from causing unbounded callback retries.
|
||||
- **Scoped CI and release validation reliability** (#838, #839) — keeps scoped coverage aligned with intentionally skipped supporting profiles, gives focused race and Multi-profile E2E suites enough time for the current `internal/app` workload, and preserves hidden E2E diagnostics on failure.
|
||||
|
||||
## [1.0.55-beta.8] - 2026-07-30
|
||||
|
||||
This beta revalidates the `v1.0.55-beta.7` product baseline through a complete
|
||||
|
||||
@@ -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.55-beta.8"
|
||||
version "1.0.56-beta.1"
|
||||
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.55-beta.8/dws-darwin-arm64.tar.gz"
|
||||
sha256 "07fabf720fa98f82c56027df703a3ad3f0aec16c957ea684047928f9f74fa00f"
|
||||
url "https://github.com/DingTalk-Real-AI/dingtalk-workspace-cli/releases/download/v1.0.56-beta.1/dws-darwin-arm64.tar.gz"
|
||||
sha256 "26b696a7807f87cf9c6a24634e08008bac0501f2664a85a8355904309068d55e"
|
||||
else
|
||||
url "https://github.com/DingTalk-Real-AI/dingtalk-workspace-cli/releases/download/v1.0.55-beta.8/dws-darwin-amd64.tar.gz"
|
||||
sha256 "fbec64dc5c3463a04de9720ffb1fd247196e619ea81b976b47ecbf751a17fb72"
|
||||
url "https://github.com/DingTalk-Real-AI/dingtalk-workspace-cli/releases/download/v1.0.56-beta.1/dws-darwin-amd64.tar.gz"
|
||||
sha256 "28daa9fa1fc8131e3567a39042d5f34efc61d5901bf7d88a67f075a4ff366be6"
|
||||
end
|
||||
end
|
||||
|
||||
on_linux do
|
||||
if Hardware::CPU.arm?
|
||||
url "https://github.com/DingTalk-Real-AI/dingtalk-workspace-cli/releases/download/v1.0.55-beta.8/dws-linux-arm64.tar.gz"
|
||||
sha256 "f111cdffef0188ddf954d2174fa0d318069bcec80104775483f13f645aa8089b"
|
||||
url "https://github.com/DingTalk-Real-AI/dingtalk-workspace-cli/releases/download/v1.0.56-beta.1/dws-linux-arm64.tar.gz"
|
||||
sha256 "5f9ead9b939d2db31354797cc7b7431f27aaca41f171d256d4d4e9269cc66d51"
|
||||
else
|
||||
url "https://github.com/DingTalk-Real-AI/dingtalk-workspace-cli/releases/download/v1.0.55-beta.8/dws-linux-amd64.tar.gz"
|
||||
sha256 "d69475b7f3cec4bad4c051834df22fbfadfa7075b82347e6ca7490f67d71d0b1"
|
||||
url "https://github.com/DingTalk-Real-AI/dingtalk-workspace-cli/releases/download/v1.0.56-beta.1/dws-linux-amd64.tar.gz"
|
||||
sha256 "06b950676cefdd8e054c76af32cd9758c4c7786908d654281d9c65e7a3d575cf"
|
||||
end
|
||||
end
|
||||
|
||||
resource "skills" do
|
||||
url "https://github.com/DingTalk-Real-AI/dingtalk-workspace-cli/releases/download/v1.0.55-beta.8/dws-skills.zip"
|
||||
sha256 "be8c9267704cfef1319fc9cd2fcba5aebe45b670b4dc37d3dae15f65523356f8"
|
||||
url "https://github.com/DingTalk-Real-AI/dingtalk-workspace-cli/releases/download/v1.0.56-beta.1/dws-skills.zip"
|
||||
sha256 "7448ccfc8e189b3d76b3e8f11f5dedc4f5a5b53380f9059e6af4c63a38e38f28"
|
||||
end
|
||||
|
||||
def install
|
||||
|
||||
@@ -8,7 +8,7 @@ POLICY_GOTMPDIR ?= $(DWS_POLICY_TMPDIR)/go
|
||||
POLICY_ENV = DWS_POLICY_TMPDIR="$(DWS_POLICY_TMPDIR)" GOTMPDIR="$(POLICY_GOTMPDIR)"
|
||||
GO_SOURCE_LIST = git ls-files -z --cached --others --exclude-standard -- '*.go'
|
||||
|
||||
.PHONY: all help build rebuild test test-plan test-auth-legacy-compat lint format-check fmt policy edition-test interface-integrity authoritative-interface-integrity coverage-gate coverage-gate-platform update-interface-baseline reset-interface-baseline schema-compatibility skill-command-integrity cli-smoke mock-mcp-smoke test-schema-agent-examples generate-schema generate-schema-agent-metadata fetch-mcp-metadata generate-schema-catalog package release release-pre release-stable changelog-pre changelog-stable publish-homebrew-formula setup-hooks
|
||||
.PHONY: all help build rebuild test test-plan test-auth-legacy-compat lint format-check fmt policy edition-test interface-integrity authoritative-interface-integrity coverage-gate coverage-gate-platform update-interface-baseline reset-interface-baseline schema-compatibility skill-command-integrity skill-context-budget cli-smoke mock-mcp-smoke test-schema-agent-examples generate-schema generate-schema-agent-metadata fetch-mcp-metadata generate-schema-catalog package release release-pre release-stable changelog-pre changelog-stable publish-homebrew-formula setup-hooks
|
||||
|
||||
all: setup-hooks fmt lint build test rebuild
|
||||
|
||||
@@ -30,6 +30,7 @@ help:
|
||||
@printf " make reset-interface-baseline - DANGEROUS: replace all CLI compatibility history\n"
|
||||
@printf " make schema-compatibility BASE_REF=<ref> - Check the complete Schema contract against the PR merge-base\n"
|
||||
@printf " make skill-command-integrity - Check dws commands referenced by skills exist\n"
|
||||
@printf " make skill-context-budget - Check generated Skill drift and common-path context budgets\n"
|
||||
@printf " make cli-smoke - Verify help for every public top-level command\n"
|
||||
@printf " make mock-mcp-smoke - Verify HTTP and stdio MCP request/response transport\n"
|
||||
@printf " make test-schema-agent-examples - Contract-check all Agent examples and dry-run the eligible subset\n"
|
||||
@@ -84,6 +85,7 @@ fmt:
|
||||
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-schema-command-registry.sh
|
||||
@$(POLICY_ENV) ./scripts/policy/check-command-surface.sh --strict
|
||||
@$(POLICY_ENV) ./scripts/policy/check-generated-drift.sh
|
||||
@@ -121,6 +123,9 @@ schema-compatibility:
|
||||
skill-command-integrity:
|
||||
@./scripts/policy/check-skill-commands.sh
|
||||
|
||||
skill-context-budget:
|
||||
@./scripts/policy/check-skill-context-budget.sh
|
||||
|
||||
cli-smoke:
|
||||
@./scripts/policy/check-cli-smoke.sh
|
||||
|
||||
|
||||
+47
-25
@@ -5,8 +5,8 @@
|
||||
| Variable | Purpose / 用途 |
|
||||
|---------|---------|
|
||||
| `DWS_CONFIG_DIR` | Override default config directory / 覆盖默认配置目录 |
|
||||
| `DWS_AGENT_PRODUCT` | Optional, caller-declared Agent product sent through the existing HTTP `claw-type` header (for example `qwenwork`). Surrounding ASCII spaces/tabs are trimmed; the remaining value must be at most 64 bytes and match `^[A-Za-z0-9][A-Za-z0-9_-]*$`. Unset or empty values preserve the edition default (`openClaw` in the open-source build). / 可选、由调用方声明的 Agent 产品标识,经校验后覆盖 HTTP `claw-type` 请求头;未设置或为空时保持当前发行版默认值 |
|
||||
| `DWS_AGENT_HOST` | Optional, caller-declared Agent runtime form sent as `x-dws-agent-host` (for example `cloud` or `desktop`). Surrounding ASCII spaces/tabs are trimmed; the remaining value must be at most 64 bytes and match `^[a-z0-9][a-z0-9_-]*$`; unset values are omitted. / 可选、由调用方声明的 Agent 运行形态,经校验后作为 `x-dws-agent-host` 发送;未设置时省略 |
|
||||
| `DWS_AGENT_PRODUCT` | Optional, caller-declared Agent product sent as `x-dws-agent-product` (for example `qwenwork`) for downstream logs/BI and used as the IM `clawType` display label when `--ai-tag` is enabled. `--ai-tag` defaults to `true`, so a configured Product changes the displayed label by default. With `--ai-tag=false`, native `chat message send` / `reply` calls send an empty `clawType`, while shortcut calls omit the argument. Surrounding ASCII spaces/tabs are trimmed; the remaining value must be at most 64 bytes and match `^[A-Za-z0-9][A-Za-z0-9_-]*$`. Unset or empty values omit the Header and use the edition's IM display default. This client never uses Product to change the separate HTTP `claw-type` PAT/routing label. / 可选、由调用方声明的 Agent 产品标识,经校验后作为 `x-dws-agent-product` 发送,并用于 IM 小尾巴;`--ai-tag` 默认为 `true`,因此配置 Product 后默认会改变展示标签。使用 `--ai-tag=false` 时,原生 `chat message send` / `reply` 发送空的 `clawType`,shortcut 调用则省略该参数。未设置时省略请求头且 IM 使用发行版默认值;本客户端不会用 Product 修改独立的 HTTP `claw-type` |
|
||||
| `DWS_AGENT_HOST` | Optional, caller-declared Agent runtime form sent as `x-dws-agent-host` (for example `cloud` or `desktop`) for downstream logs/BI. Surrounding ASCII spaces/tabs are trimmed; the remaining value must be at most 64 bytes and match `^[a-z0-9][a-z0-9_-]*$`; unset values are omitted. This client does not use Host for PAT, authentication, Discovery, or MCP endpoint selection. / 可选、由调用方声明的 Agent 运行形态,经校验后作为 `x-dws-agent-host` 发送给下游日志/BI;本客户端不使用该值进行 PAT、鉴权、Discovery 或 MCP 端点选择,未设置时省略 |
|
||||
| `DWS_<PRODUCT>_MCP_URL` | Override a product MCP endpoint for local development / 本地开发时覆盖指定产品 MCP endpoint |
|
||||
| `DWS_CLIENT_ID` | OAuth client ID (DingTalk AppKey) |
|
||||
| `DWS_CLIENT_SECRET` | OAuth client secret (DingTalk AppSecret) |
|
||||
@@ -14,23 +14,36 @@
|
||||
| `DWS_ALLOW_HTTP_ENDPOINTS` | Set `1` to allow HTTP for loopback during dev / 设为 `1` 允许回环地址 HTTP,仅用于开发调试 |
|
||||
| `DWS_DISABLE_KEYCHAIN` | macOS only. Set `1` to skip system Keychain for the encryption key and use file-based storage (same scheme as Linux). For sandboxed runtimes (e.g. Codex App) that block Keychain APIs. Weakens at-rest protection — DEK and ciphertext live in the same directory. / 仅 macOS。设为 `1` 时跳过系统 Keychain,密钥以文件形式存储(与 Linux 一致)。用于 Keychain API 被拦截的沙盒环境(如 Codex App)。代价是 DEK 与密文同目录,保护强度低于默认方案 |
|
||||
|
||||
### Agent Product and Host trust model / Agent 产品与运行形态的信任模型
|
||||
### Agent Product, Host, and `claw-type` / Agent 产品、运行形态与 `claw-type`
|
||||
|
||||
`DWS_AGENT_PRODUCT` and `DWS_AGENT_HOST` are caller-declared selection and
|
||||
observation signals. They are not credentials, attestations, or proof of the
|
||||
calling host's identity. DingTalk services may record them for logs/BI and may
|
||||
combine supported values with separately authenticated context for PAT
|
||||
compatibility, PAT identity/source derivation, or Discovery eligibility. A
|
||||
service must allowlist supported values and must never grant access, bypass
|
||||
authentication, or skip authorization solely because either Header claims a
|
||||
particular product or runtime form. They are not used to select ordinary MCP
|
||||
tool endpoints.
|
||||
`DWS_AGENT_PRODUCT` and `DWS_AGENT_HOST` are caller-declared observation
|
||||
signals. They are not credentials, attestations, or proof of the calling
|
||||
host's identity. The CLI validates and emits `x-dws-agent-product` and
|
||||
`x-dws-agent-host`, but does not use either value to derive its authentication,
|
||||
PAT mode, Discovery behaviour, or ordinary MCP endpoint selection. Downstream
|
||||
services own and must document their own contracts for these caller-declared
|
||||
Headers.
|
||||
|
||||
`DWS_AGENT_PRODUCT` controls only the HTTP `claw-type` Header. The similarly
|
||||
named `clawType` tool argument on IM send operations is an independent
|
||||
message-display axis used for the “Send from AI” label. It remains controlled
|
||||
by the active edition's `ClawTypeValue` and `--ai-tag`; changing
|
||||
`DWS_AGENT_PRODUCT` does not change that message label.
|
||||
Service integrators should treat both Headers as untrusted input, allowlist
|
||||
expected values, and should not grant access, bypass authentication, or skip
|
||||
authorization solely because a Header claims a particular Product or Host.
|
||||
|
||||
The HTTP `claw-type` Header is a separate, edition-fixed PAT/routing label:
|
||||
`openClaw` in the open-source build. `DWS_AGENT_PRODUCT` never changes it or
|
||||
PAT `hostControl.clawType`. On IM send/reply operations with `--ai-tag`,
|
||||
however, a valid non-empty Product value is used as the `clawType` tool
|
||||
argument so the delivered message carries the matching “Send from AI” label.
|
||||
Because `--ai-tag` defaults to `true`, this display change is enabled by
|
||||
default for callers that set Product. With `--ai-tag=false`, native
|
||||
`chat message send` / `reply` calls serialize `clawType: ""`, while shortcut
|
||||
calls omit the argument; this client does not assume downstream services treat
|
||||
an empty value and an absent key as equivalent. The display-value precedence
|
||||
when the tag is enabled is valid non-empty `DWS_AGENT_PRODUCT`, then the active
|
||||
edition's `ClawTypeValue`, then `openClaw`.
|
||||
|
||||
Do not set arbitrary Product values that the target downstream and IM services
|
||||
have not explicitly enabled; an unknown value may be ignored or may not render
|
||||
the expected label.
|
||||
|
||||
For QwenWork, report the dimensions separately:
|
||||
|
||||
@@ -39,16 +52,25 @@ DWS_AGENT_PRODUCT=qwenwork
|
||||
DWS_AGENT_HOST=cloud # or desktop
|
||||
```
|
||||
|
||||
Do not set arbitrary product values that the target service has not explicitly
|
||||
enabled. Older combined Host labels such as `qwenwork_cloud` still satisfy the
|
||||
generic syntax for compatibility, but new integrations should use the
|
||||
two-dimensional convention above.
|
||||
Older combined Host labels such as `qwenwork_cloud` still satisfy the generic
|
||||
syntax for compatibility, but new integrations should use the two-dimensional
|
||||
convention above.
|
||||
|
||||
`DWS_AGENT_PRODUCT` 和 `DWS_AGENT_HOST` 均由调用方声明,不是认证凭据,也不能证明
|
||||
真实宿主身份。服务端可以在独立认证上下文中将受支持值用于日志/BI、PAT 兼容策略、
|
||||
PAT 身份/来源派生或 Discovery 准入,但不得仅凭这两个 Header 放权、绕过认证或跳过
|
||||
授权。HTTP `claw-type` 与 IM 消息发送参数 `clawType` 是两个独立维度;后者仅控制
|
||||
“Send from AI”展示,仍由发行版 `ClawTypeValue` 和 `--ai-tag` 决定。
|
||||
真实宿主身份。CLI 只负责校验并发送 `x-dws-agent-product` 与 `x-dws-agent-host`,
|
||||
不会用它们派生本客户端的鉴权、PAT 模式、Discovery 行为或 MCP 端点;下游服务的
|
||||
使用契约由对应服务自行定义和说明。HTTP `claw-type` 是发行版固定的 PAT/路由标签,
|
||||
开源版固定为 `openClaw`,不受 `DWS_AGENT_PRODUCT` 影响。
|
||||
|
||||
服务集成方应将这两个请求头视为不可信输入并对白名单值做校验,不应仅因请求头声明了
|
||||
某个 Product 或 Host 就授予访问、绕过认证或跳过鉴权。
|
||||
|
||||
`--ai-tag` 默认为 `true`,因此配置合法非空 Product 后,默认发送的 IM 工具参数
|
||||
`clawType` 及小尾巴会随之改变。传入 `--ai-tag=false` 时,原生
|
||||
`chat message send` / `reply` 会发送 `clawType: ""`,shortcut 调用则省略该参数;
|
||||
本客户端不假定下游会将空值与键缺失等价处理。启用小尾巴时,展示值优先级依次为
|
||||
`DWS_AGENT_PRODUCT`、当前发行版的 `ClawTypeValue`、`openClaw`。不要传入目标下游及
|
||||
IM 服务未明确支持的 Product 值,否则可能被忽略或无法展示预期标签。
|
||||
|
||||
## Exit Codes / 退出码
|
||||
|
||||
|
||||
@@ -33,7 +33,7 @@ func init() {
|
||||
configmeta.Register(configmeta.ConfigItem{
|
||||
Name: envDWSAgentHost,
|
||||
Category: configmeta.CategoryExternal,
|
||||
Description: "调用 DWS 的 Agent 运行形态标识;服务端可结合产品用于观测和 PAT 兼容策略",
|
||||
Description: "调用 DWS 的 Agent 运行形态标识;作为 x-dws-agent-host 发送供下游观测,本客户端不使用该值改变 PAT、鉴权或路由",
|
||||
Example: "cloud",
|
||||
})
|
||||
}
|
||||
|
||||
@@ -24,8 +24,8 @@ func init() {
|
||||
configmeta.Register(configmeta.ConfigItem{
|
||||
Name: agentproduct.EnvName,
|
||||
Category: configmeta.CategoryExternal,
|
||||
Description: "调用方声明的 Agent 产品标识;覆盖 HTTP claw-type,但不是认证凭据",
|
||||
DefaultValue: "由当前发行版决定",
|
||||
Description: "调用方声明的 Agent 产品标识;作为 x-dws-agent-product 发送并用于 IM 小尾巴,本客户端不使用该值改变 HTTP claw-type/PAT",
|
||||
DefaultValue: "未设置(请求头省略,IM 使用当前发行版默认值)",
|
||||
Example: "qwenwork",
|
||||
})
|
||||
}
|
||||
@@ -47,25 +47,26 @@ func invalidAgentProductError() error {
|
||||
)
|
||||
}
|
||||
|
||||
// resolveEffectiveAgentProduct resolves the request-header identity with one
|
||||
// shared precedence rule: a valid non-empty runtime override wins, otherwise
|
||||
// the edition's MergeHeaders value wins, otherwise the OSS default is used.
|
||||
// Invalid runtime input falls back here for library callers that bypass root
|
||||
// validation; normal CLI execution rejects it before network access.
|
||||
func resolveEffectiveAgentProduct(headers map[string]string) string {
|
||||
fallback := edition.DefaultOSSClawType
|
||||
if value := headers[agentproduct.HeaderName]; value != "" {
|
||||
fallback = value
|
||||
// resolveEditionClawType resolves the fixed routing/PAT identity supplied by
|
||||
// the active edition. DWS_AGENT_PRODUCT is deliberately not consulted.
|
||||
func resolveEditionClawType(headers map[string]string) string {
|
||||
if value := headers["claw-type"]; value != "" {
|
||||
return value
|
||||
}
|
||||
value, err := agentproduct.ResolveFromEnv(fallback)
|
||||
if err != nil {
|
||||
return fallback
|
||||
}
|
||||
return value
|
||||
return edition.DefaultOSSClawType
|
||||
}
|
||||
|
||||
func applyAgentProductOverride(headers map[string]string) map[string]string {
|
||||
value := resolveEffectiveAgentProduct(headers)
|
||||
// applyAgentProductHeader injects only a valid, non-empty caller-declared
|
||||
// product. Invalid values are omitted on library paths that bypass root
|
||||
// validation; normal CLI execution rejects them before network access.
|
||||
func applyAgentProductHeader(headers map[string]string) map[string]string {
|
||||
value, err := agentproduct.ResolveFromEnv("")
|
||||
if err != nil || value == "" {
|
||||
if headers != nil {
|
||||
delete(headers, agentproduct.HeaderName)
|
||||
}
|
||||
return headers
|
||||
}
|
||||
if headers == nil {
|
||||
headers = make(map[string]string)
|
||||
}
|
||||
|
||||
@@ -26,13 +26,16 @@ import (
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
func TestUnsetAgentProductKeepsOpenSourceDefault(t *testing.T) {
|
||||
func TestUnsetAgentProductOmitsHeaderAndKeepsOpenSourceClawType(t *testing.T) {
|
||||
t.Setenv("DWS_CONFIG_DIR", t.TempDir())
|
||||
t.Setenv(agentproduct.EnvName, "")
|
||||
|
||||
headers := resolveIdentityHeaders()
|
||||
if got := headers[agentproduct.HeaderName]; got != edition.DefaultOSSClawType {
|
||||
t.Fatalf("%s = %q, want %q", agentproduct.HeaderName, got, edition.DefaultOSSClawType)
|
||||
if got := headers["claw-type"]; got != edition.DefaultOSSClawType {
|
||||
t.Fatalf("claw-type = %q, want %q", got, edition.DefaultOSSClawType)
|
||||
}
|
||||
if _, ok := headers[agentproduct.HeaderName]; ok {
|
||||
t.Fatalf("unset Product must omit %s", agentproduct.HeaderName)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -59,38 +62,46 @@ func TestParseAgentProductReturnsStableValidationError(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestResolveIdentityHeadersAgentProductPrecedence(t *testing.T) {
|
||||
func TestResolveIdentityHeadersSeparatesAgentProductFromClawType(t *testing.T) {
|
||||
t.Setenv("DWS_CONFIG_DIR", t.TempDir())
|
||||
|
||||
oldEdition := edition.Get()
|
||||
t.Cleanup(func() { edition.Override(oldEdition) })
|
||||
edition.Override(&edition.Hooks{
|
||||
MergeHeaders: func(headers map[string]string) map[string]string {
|
||||
headers[agentproduct.HeaderName] = "wukong"
|
||||
headers["claw-type"] = "wukong"
|
||||
headers[agentproduct.HeaderName] = "merge-product-must-not-win"
|
||||
headers["x-edition-header"] = "preserved"
|
||||
return headers
|
||||
},
|
||||
EnterpriseCredentialHeaders: func(headers map[string]string) map[string]string {
|
||||
headers[agentproduct.HeaderName] = "enterprise-default"
|
||||
headers["claw-type"] = "credential-must-not-win"
|
||||
headers[agentproduct.HeaderName] = "credential-product-must-not-win"
|
||||
headers["x-enterprise-header"] = "preserved"
|
||||
return headers
|
||||
},
|
||||
})
|
||||
|
||||
t.Run("unset keeps edition default", func(t *testing.T) {
|
||||
t.Run("unset omits Product and keeps edition claw-type", func(t *testing.T) {
|
||||
t.Setenv(agentproduct.EnvName, "")
|
||||
headers := resolveIdentityHeaders()
|
||||
if got := headers[agentproduct.HeaderName]; got != "wukong" {
|
||||
t.Fatalf("%s = %q, want wukong", agentproduct.HeaderName, got)
|
||||
if got := headers["claw-type"]; got != "wukong" {
|
||||
t.Fatalf("claw-type = %q, want wukong", got)
|
||||
}
|
||||
if _, ok := headers[agentproduct.HeaderName]; ok {
|
||||
t.Fatalf("unset Product must omit %s", agentproduct.HeaderName)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("valid override is final", func(t *testing.T) {
|
||||
t.Run("valid Product is final without changing claw-type", func(t *testing.T) {
|
||||
t.Setenv(agentproduct.EnvName, " qwenwork ")
|
||||
headers := resolveIdentityHeaders()
|
||||
if got := headers[agentproduct.HeaderName]; got != "qwenwork" {
|
||||
t.Fatalf("%s = %q, want qwenwork", agentproduct.HeaderName, got)
|
||||
}
|
||||
if got := headers["claw-type"]; got != "wukong" {
|
||||
t.Fatalf("claw-type = %q, want wukong", got)
|
||||
}
|
||||
if got := headers["x-edition-header"]; got != "preserved" {
|
||||
t.Fatalf("edition header = %q, want preserved", got)
|
||||
}
|
||||
@@ -102,21 +113,48 @@ func TestResolveIdentityHeadersAgentProductPrecedence(t *testing.T) {
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("invalid library input falls back to edition", func(t *testing.T) {
|
||||
t.Run("invalid library input omits Product and keeps edition claw-type", func(t *testing.T) {
|
||||
t.Setenv(agentproduct.EnvName, "qwen work")
|
||||
headers := resolveIdentityHeaders()
|
||||
if got := headers[agentproduct.HeaderName]; got != "wukong" {
|
||||
t.Fatalf("%s = %q, want wukong", agentproduct.HeaderName, got)
|
||||
if got := headers["claw-type"]; got != "wukong" {
|
||||
t.Fatalf("claw-type = %q, want wukong", got)
|
||||
}
|
||||
if _, ok := headers[agentproduct.HeaderName]; ok {
|
||||
t.Fatalf("invalid Product must omit %s", agentproduct.HeaderName)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func TestApplyAgentProductOverrideAllocatesHeaders(t *testing.T) {
|
||||
t.Setenv(agentproduct.EnvName, "qwenwork")
|
||||
func TestApplyAgentProductHeader(t *testing.T) {
|
||||
t.Run("valid value allocates headers", func(t *testing.T) {
|
||||
t.Setenv(agentproduct.EnvName, "qwenwork")
|
||||
|
||||
headers := applyAgentProductOverride(nil)
|
||||
if got := headers[agentproduct.HeaderName]; got != "qwenwork" {
|
||||
t.Fatalf("%s = %q, want qwenwork", agentproduct.HeaderName, got)
|
||||
headers := applyAgentProductHeader(nil)
|
||||
if got := headers[agentproduct.HeaderName]; got != "qwenwork" {
|
||||
t.Fatalf("%s = %q, want qwenwork", agentproduct.HeaderName, got)
|
||||
}
|
||||
})
|
||||
|
||||
for _, tc := range []struct {
|
||||
name string
|
||||
value string
|
||||
}{
|
||||
{name: "empty value", value: ""},
|
||||
{name: "invalid value", value: "qwen work"},
|
||||
} {
|
||||
t.Run(tc.name+" removes inherited header", func(t *testing.T) {
|
||||
t.Setenv(agentproduct.EnvName, tc.value)
|
||||
headers := applyAgentProductHeader(map[string]string{
|
||||
agentproduct.HeaderName: "must-not-leak",
|
||||
"x-preserved": "yes",
|
||||
})
|
||||
if _, ok := headers[agentproduct.HeaderName]; ok {
|
||||
t.Fatalf("%s must be omitted", agentproduct.HeaderName)
|
||||
}
|
||||
if got := headers["x-preserved"]; got != "yes" {
|
||||
t.Fatalf("x-preserved = %q, want yes", got)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -167,17 +205,17 @@ func TestEffectiveClawTypeDoesNotInvokeEnterpriseCredentialHeaders(t *testing.T)
|
||||
hookCalled := false
|
||||
edition.Override(&edition.Hooks{
|
||||
MergeHeaders: func(headers map[string]string) map[string]string {
|
||||
headers[agentproduct.HeaderName] = "wukong"
|
||||
headers["claw-type"] = "wukong"
|
||||
return headers
|
||||
},
|
||||
EnterpriseCredentialHeaders: func(headers map[string]string) map[string]string {
|
||||
hookCalled = true
|
||||
headers[agentproduct.HeaderName] = "enterprise-default"
|
||||
headers["claw-type"] = "enterprise-default"
|
||||
return headers
|
||||
},
|
||||
})
|
||||
|
||||
t.Setenv(agentproduct.EnvName, "")
|
||||
t.Setenv(agentproduct.EnvName, "qwenwork")
|
||||
if got := effectiveClawType(); got != "wukong" {
|
||||
t.Fatalf("effectiveClawType() = %q, want wukong", got)
|
||||
}
|
||||
@@ -186,7 +224,7 @@ func TestEffectiveClawTypeDoesNotInvokeEnterpriseCredentialHeaders(t *testing.T)
|
||||
}
|
||||
}
|
||||
|
||||
func TestAgentProductHeaderIsSeparateFromMessageClawType(t *testing.T) {
|
||||
func TestAgentProductControlsObservabilityHeaderAndMessageClawTypeOnly(t *testing.T) {
|
||||
t.Setenv("DWS_CONFIG_DIR", t.TempDir())
|
||||
t.Setenv(agentproduct.EnvName, "qwenwork")
|
||||
|
||||
@@ -195,20 +233,24 @@ func TestAgentProductHeaderIsSeparateFromMessageClawType(t *testing.T) {
|
||||
edition.Override(&edition.Hooks{
|
||||
ClawTypeValue: "message-brand",
|
||||
MergeHeaders: func(headers map[string]string) map[string]string {
|
||||
headers[agentproduct.HeaderName] = "wukong"
|
||||
headers["claw-type"] = "wukong"
|
||||
return headers
|
||||
},
|
||||
})
|
||||
|
||||
if got := resolveIdentityHeaders()[agentproduct.HeaderName]; got != "qwenwork" {
|
||||
headers := resolveIdentityHeaders()
|
||||
if got := headers[agentproduct.HeaderName]; got != "qwenwork" {
|
||||
t.Fatalf("HTTP %s = %q, want qwenwork", agentproduct.HeaderName, got)
|
||||
}
|
||||
if got := edition.ClawType(); got != "message-brand" {
|
||||
t.Fatalf("message clawType = %q, want message-brand", got)
|
||||
if got := headers["claw-type"]; got != "wukong" {
|
||||
t.Fatalf("HTTP claw-type = %q, want wukong", got)
|
||||
}
|
||||
if got := edition.ClawType(); got != "qwenwork" {
|
||||
t.Fatalf("message clawType = %q, want qwenwork", got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestResolveIdentityHeadersRestoresAgentProductAfterNilCredentialHeaders(t *testing.T) {
|
||||
func TestResolveIdentityHeadersRestoresIdentityAfterNilCredentialHeaders(t *testing.T) {
|
||||
t.Setenv("DWS_CONFIG_DIR", t.TempDir())
|
||||
t.Setenv(agentproduct.EnvName, "qwenwork")
|
||||
|
||||
@@ -218,7 +260,7 @@ func TestResolveIdentityHeadersRestoresAgentProductAfterNilCredentialHeaders(t *
|
||||
credentialHookCalled := false
|
||||
edition.Override(&edition.Hooks{
|
||||
MergeHeaders: func(headers map[string]string) map[string]string {
|
||||
headers[agentproduct.HeaderName] = "wukong"
|
||||
headers["claw-type"] = "wukong"
|
||||
return headers
|
||||
},
|
||||
EnterpriseCredentialHeaders: func(map[string]string) map[string]string {
|
||||
@@ -234,25 +276,49 @@ func TestResolveIdentityHeadersRestoresAgentProductAfterNilCredentialHeaders(t *
|
||||
if got := headers[agentproduct.HeaderName]; got != "qwenwork" {
|
||||
t.Fatalf("%s = %q, want qwenwork", agentproduct.HeaderName, got)
|
||||
}
|
||||
if got := headers["claw-type"]; got != "wukong" {
|
||||
t.Fatalf("claw-type = %q, want wukong", got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestEffectiveClawTypeUsesAgentProductOverride(t *testing.T) {
|
||||
func TestResolveIdentityHeadersRestoresDefaultsAfterNilMergeHeaders(t *testing.T) {
|
||||
t.Setenv("DWS_CONFIG_DIR", t.TempDir())
|
||||
t.Setenv(agentproduct.EnvName, "")
|
||||
|
||||
oldEdition := edition.Get()
|
||||
t.Cleanup(func() { edition.Override(oldEdition) })
|
||||
edition.Override(&edition.Hooks{
|
||||
MergeHeaders: func(map[string]string) map[string]string {
|
||||
return nil
|
||||
},
|
||||
})
|
||||
|
||||
headers := resolveIdentityHeaders()
|
||||
if got := headers["claw-type"]; got != edition.DefaultOSSClawType {
|
||||
t.Fatalf("claw-type = %q, want %q", got, edition.DefaultOSSClawType)
|
||||
}
|
||||
if _, ok := headers[agentproduct.HeaderName]; ok {
|
||||
t.Fatalf("unset Product must omit %s", agentproduct.HeaderName)
|
||||
}
|
||||
}
|
||||
|
||||
func TestEffectiveClawTypeIgnoresAgentProduct(t *testing.T) {
|
||||
oldEdition := edition.Get()
|
||||
t.Cleanup(func() { edition.Override(oldEdition) })
|
||||
edition.Override(&edition.Hooks{
|
||||
MergeHeaders: func(headers map[string]string) map[string]string {
|
||||
headers[agentproduct.HeaderName] = "wukong"
|
||||
headers["claw-type"] = "wukong"
|
||||
return headers
|
||||
},
|
||||
})
|
||||
|
||||
t.Setenv(agentproduct.EnvName, "qwenwork")
|
||||
if got := effectiveClawType(); got != "qwenwork" {
|
||||
t.Fatalf("effectiveClawType() = %q, want qwenwork", got)
|
||||
if got := effectiveClawType(); got != "wukong" {
|
||||
t.Fatalf("effectiveClawType() = %q, want wukong", got)
|
||||
}
|
||||
t.Setenv(authpkg.AgentCodeEnv, "agent-code")
|
||||
if got := apperrors.HostControlBlock()["clawType"]; got != "qwenwork" {
|
||||
t.Fatalf("hostControl.clawType = %q, want qwenwork", got)
|
||||
if got := apperrors.HostControlBlock()["clawType"]; got != "wukong" {
|
||||
t.Fatalf("hostControl.clawType = %q, want wukong", got)
|
||||
}
|
||||
|
||||
t.Setenv(agentproduct.EnvName, "")
|
||||
|
||||
@@ -569,9 +569,9 @@ func handlePatAuthCheck(
|
||||
// In host-controlled PAT mode (driven solely by DINGTALK_DWS_AGENTCODE),
|
||||
// or when flowId is absent, the CLI returns machine-readable JSON to
|
||||
// stderr and leaves UI/polling/retry to the host. `claw-type` is NOT
|
||||
// used for this decision — it is only forwarded on the wire via
|
||||
// the edition default / DWS_AGENT_PRODUCT override and surfaced in
|
||||
// hostControl for traceability.
|
||||
// used for this decision — its edition-fixed value is forwarded on the
|
||||
// wire and surfaced in hostControl for traceability. DWS_AGENT_PRODUCT
|
||||
// does not affect this PAT contract.
|
||||
if hostOwnedPAT || patData.Data.FlowID == "" {
|
||||
if hostOwnedPAT {
|
||||
return executor.Result{}, &apperrors.PATError{RawJSON: enrichPATErrorForHostControl(patErr.RawJSON)}
|
||||
|
||||
@@ -1007,11 +1007,11 @@ func TestHandlePatAuthCheck_HostControlledFlowIDPassthrough(t *testing.T) {
|
||||
t.Setenv("DWS_CONFIG_DIR", tmpDir)
|
||||
// Host-owned decision: driven ONLY by DINGTALK_DWS_AGENTCODE.
|
||||
// DINGTALK_AGENT is set to demonstrate it does NOT leak into
|
||||
// hostControl.clawType. With no DWS_AGENT_PRODUCT override the
|
||||
// open-source edition default remains "openClaw".
|
||||
// hostControl.clawType. DWS_AGENT_PRODUCT is also set to demonstrate
|
||||
// that Product does not change the open-source fixed "openClaw" value.
|
||||
t.Setenv(authpkg.AgentCodeEnv, "agt-sales")
|
||||
t.Setenv("DINGTALK_AGENT", "sales-copilot")
|
||||
t.Setenv(agentproduct.EnvName, "")
|
||||
t.Setenv(agentproduct.EnvName, "qwenwork")
|
||||
|
||||
mock := &mockRunner{
|
||||
runFunc: func(ctx context.Context, inv executor.Invocation) (executor.Result, error) {
|
||||
|
||||
@@ -29,9 +29,8 @@ import (
|
||||
// - Host-owned is triggered iff DINGTALK_DWS_AGENTCODE is non-empty.
|
||||
// - When triggered, `clawType` in the emitted hostControl block MUST be the
|
||||
// exact value the CLI actually injects on the wire. Each edition supplies
|
||||
// its existing default and an optional valid DWS_AGENT_PRODUCT overrides
|
||||
// it. Invalid input falls back here for library compatibility; root command
|
||||
// execution rejects it before network access.
|
||||
// its fixed value; DWS_AGENT_PRODUCT is a separate observability and IM
|
||||
// message-display signal and never affects this PAT value.
|
||||
// - When DINGTALK_DWS_AGENTCODE is empty the provider returns "" so
|
||||
// HostControlBlock yields nil and no hostControl block is emitted.
|
||||
func init() {
|
||||
@@ -59,5 +58,5 @@ func effectiveClawType() string {
|
||||
headers = h.MergeHeaders(headers)
|
||||
}
|
||||
}
|
||||
return resolveEffectiveAgentProduct(headers)
|
||||
return resolveEditionClawType(headers)
|
||||
}
|
||||
|
||||
+20
-13
@@ -1005,9 +1005,8 @@ func resolveIdentityHeaders() map[string]string {
|
||||
|
||||
// Inject environment variable based headers for MCP gateway tracking.
|
||||
// DINGTALK_AGENT, if set by the caller, is forwarded verbatim as the
|
||||
// x-dingtalk-agent header. It does NOT influence claw-type (which comes
|
||||
// from the edition default plus the explicit DWS_AGENT_PRODUCT override)
|
||||
// and it does NOT influence the host-owned PAT decision (driven solely by
|
||||
// x-dingtalk-agent header. It does NOT influence the edition-fixed
|
||||
// claw-type or the host-owned PAT decision (driven solely by
|
||||
// DINGTALK_DWS_AGENTCODE).
|
||||
sessionID := os.Getenv(envDingtalkSessionID)
|
||||
if sessionID == "" {
|
||||
@@ -1068,22 +1067,30 @@ func resolveIdentityHeaders() map[string]string {
|
||||
if fn := edition.Get().MergeHeaders; fn != nil {
|
||||
headers = fn(headers)
|
||||
}
|
||||
// Resolve the Agent Product before credential injection. The credential
|
||||
// hook has a separate contract and must not be able to replace the
|
||||
// request identity used by PAT hostControl serialization.
|
||||
headers = applyAgentProductOverride(headers)
|
||||
agentProduct := headers[agentproduct.HeaderName]
|
||||
if headers == nil {
|
||||
headers = make(map[string]string)
|
||||
}
|
||||
|
||||
// claw-type is the edition-fixed routing/PAT identity. Agent Product is a
|
||||
// separate caller-declared observability and IM-display dimension.
|
||||
clawType := resolveEditionClawType(headers)
|
||||
headers["claw-type"] = clawType
|
||||
headers = applyAgentProductHeader(headers)
|
||||
agentProduct, hasAgentProduct := headers[agentproduct.HeaderName]
|
||||
if fn := edition.Get().EnterpriseCredentialHeaders; fn != nil {
|
||||
headers = fn(headers)
|
||||
}
|
||||
if headers == nil {
|
||||
headers = make(map[string]string)
|
||||
}
|
||||
// DWS_AGENT_PRODUCT is the explicit caller override for the existing
|
||||
// claw-type wire header. Reassert the resolved product after credential
|
||||
// injection so that hook cannot alter identity. Invalid values are ignored
|
||||
// on this best-effort library path; root execution rejects them earlier.
|
||||
headers[agentproduct.HeaderName] = agentProduct
|
||||
// Credential hooks cannot alter either identity dimension. Restore the
|
||||
// fixed claw-type and the validated Product Header (or its absence).
|
||||
headers["claw-type"] = clawType
|
||||
if hasAgentProduct {
|
||||
headers[agentproduct.HeaderName] = agentProduct
|
||||
} else {
|
||||
delete(headers, agentproduct.HeaderName)
|
||||
}
|
||||
return headers
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"version": 1,
|
||||
"source_hash": "sha256:7484b82d1ca793a6129acfaa192ff08fc0864d47a6e75ecd16d8e7fa32857e16",
|
||||
"source_hash": "sha256:670ca810a83bf2aa6f387a18c3af746393d5ea9de24570cbcebbcf994eb7b613",
|
||||
"surface_hash": "sha256:60eee8e2f37d6d9d60689efce85082798eb9ad38b7ba7c0b471c3de676a85a16",
|
||||
"coverage": {
|
||||
"surface_products": 26,
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"version": 1,
|
||||
"source_hash": "sha256:7484b82d1ca793a6129acfaa192ff08fc0864d47a6e75ecd16d8e7fa32857e16",
|
||||
"source_hash": "sha256:670ca810a83bf2aa6f387a18c3af746393d5ea9de24570cbcebbcf994eb7b613",
|
||||
"surface_hash": "sha256:60eee8e2f37d6d9d60689efce85082798eb9ad38b7ba7c0b471c3de676a85a16",
|
||||
"source_files": 160,
|
||||
"hint_files": 54,
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"version": 1,
|
||||
"surface_hash": "sha256:60eee8e2f37d6d9d60689efce85082798eb9ad38b7ba7c0b471c3de676a85a16",
|
||||
"source_hash": "sha256:ba691e70f1c232fc3378a743c3fcd34113960d30b46bffa7d1fc8b524115052c",
|
||||
"source_hash": "sha256:862734fb976a4c9c78d010b7391c74822f37805f5acc650551f4ac60f701c13a",
|
||||
"catalog": {
|
||||
"agent_metadata": {
|
||||
"products_with_metadata": 26,
|
||||
"source": "embedded-skill-metadata",
|
||||
"source_hash": "sha256:7484b82d1ca793a6129acfaa192ff08fc0864d47a6e75ecd16d8e7fa32857e16",
|
||||
"source_hash": "sha256:670ca810a83bf2aa6f387a18c3af746393d5ea9de24570cbcebbcf994eb7b613",
|
||||
"surface_hash": "sha256:60eee8e2f37d6d9d60689efce85082798eb9ad38b7ba7c0b471c3de676a85a16",
|
||||
"surface_products": 26,
|
||||
"surface_tools": 845,
|
||||
|
||||
@@ -22,6 +22,7 @@ import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/DingTalk-Real-AI/dingtalk-workspace-cli/pkg/agentproduct"
|
||||
"github.com/DingTalk-Real-AI/dingtalk-workspace-cli/pkg/edition"
|
||||
)
|
||||
|
||||
@@ -217,6 +218,73 @@ func TestCrossPlatformCoverageChatSendResolvesUserBeforeDispatch(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestChatSendAndReplyDefaultToAgentProductForIMClawType(t *testing.T) {
|
||||
t.Setenv(agentproduct.EnvName, "qwenwork")
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
args []string
|
||||
}{
|
||||
{
|
||||
name: "send",
|
||||
args: []string{"message", "send", "--open-dingtalk-id", "D1", "--text", "hello"},
|
||||
},
|
||||
{
|
||||
name: "reply",
|
||||
args: []string{"message", "reply", "--conversation-id", "cid", "--ref-msg-id", "mid", "--ref-sender", "D1", "--text", "hello"},
|
||||
},
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
if got := caller.calls[0].args["clawType"]; got != "qwenwork" {
|
||||
t.Fatalf("clawType = %#v, want qwenwork", got)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestChatSendAndReplyDisableAITagWithEmptyClawType(t *testing.T) {
|
||||
t.Setenv(agentproduct.EnvName, "qwenwork")
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
args []string
|
||||
}{
|
||||
{
|
||||
name: "send",
|
||||
args: []string{"message", "send", "--open-dingtalk-id", "D1", "--text", "hello", "--ai-tag=false"},
|
||||
},
|
||||
{
|
||||
name: "reply",
|
||||
args: []string{"message", "reply", "--conversation-id", "cid", "--ref-msg-id", "mid", "--ref-sender", "D1", "--text", "hello", "--ai-tag=false"},
|
||||
},
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
got, present := caller.calls[0].args["clawType"]
|
||||
if !present || got != "" {
|
||||
t.Fatalf("clawType = %#v, present = %v; want present empty string", got, present)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestCrossPlatformCoverageChatSendFailsClosedWhenUserCannotResolve(t *testing.T) {
|
||||
caller := &chatChangedContractCaller{}
|
||||
err := executeChatChangedContract(t, caller, "message", "send", "--user", "123", "--text", "hello")
|
||||
|
||||
+9
-6
@@ -55,12 +55,15 @@ Host-owned PAT 开关:
|
||||
由宿主处理全部 UI / 交互 / 回调节奏 / 重试逻辑,
|
||||
CLI 侧不再拉起任何本地浏览器 / 轮询。
|
||||
|
||||
服务端 Agent 产品标签 claw-type:
|
||||
开源构建默认在出站 MCP 请求中注入 claw-type: openClaw。
|
||||
如设置 DWS_AGENT_PRODUCT,则使用经校验的环境变量值覆盖该默认值。
|
||||
hostControl.clawType 会回填请求实际使用的值,避免 PAT 与请求标识漂移。
|
||||
该值由调用方声明,不是认证凭据;服务端不得仅凭它放权或跳过授权。
|
||||
它也不会修改 IM 消息展示使用的 clawType 参数或 --ai-tag 行为。
|
||||
服务端 PAT / 路由标签 claw-type:
|
||||
开源构建固定在出站 MCP 请求中注入 claw-type: openClaw,
|
||||
hostControl.clawType 会回填相同值。DWS_AGENT_PRODUCT 不会修改它。
|
||||
|
||||
Agent 产品标识 DWS_AGENT_PRODUCT:
|
||||
合法非空值作为 x-dws-agent-product 请求头发送,供下游日志 / BI 使用;
|
||||
本客户端不使用该值派生或改变 PAT、鉴权或路由,下游使用契约由对应
|
||||
服务自行定义。同时该值作为启用 --ai-tag 时 IM 消息小尾巴的 clawType
|
||||
参数。未设置或为空时请求头省略,小尾巴回退发行版默认值。
|
||||
|
||||
DINGTALK_AGENT(可选,仅供 x-dingtalk-agent 使用):
|
||||
如设置,将原样注入 HTTP 请求头 x-dingtalk-agent,
|
||||
|
||||
@@ -24,6 +24,7 @@ import (
|
||||
|
||||
"github.com/DingTalk-Real-AI/dingtalk-workspace-cli/internal/helpers"
|
||||
"github.com/DingTalk-Real-AI/dingtalk-workspace-cli/internal/shortcut"
|
||||
"github.com/DingTalk-Real-AI/dingtalk-workspace-cli/pkg/agentproduct"
|
||||
"github.com/DingTalk-Real-AI/dingtalk-workspace-cli/pkg/edition"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
@@ -99,6 +100,8 @@ func newPlatformCoverageRoot() *cobra.Command {
|
||||
}
|
||||
|
||||
func TestCrossPlatformCoverageAIMessageTag(t *testing.T) {
|
||||
t.Setenv(agentproduct.EnvName, "qwenwork")
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
argv []string
|
||||
@@ -125,8 +128,8 @@ func TestCrossPlatformCoverageAIMessageTag(t *testing.T) {
|
||||
if send.product != "chat" || send.tool != "send_personal_message" {
|
||||
t.Fatalf("last call = %s/%s, want chat/send_personal_message", send.product, send.tool)
|
||||
}
|
||||
if got := send.args["clawType"]; got != edition.ClawType() {
|
||||
t.Fatalf("clawType = %#v, want %q", got, edition.ClawType())
|
||||
if got := send.args["clawType"]; got != "qwenwork" {
|
||||
t.Fatalf("clawType = %#v, want qwenwork", got)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
// limitations under the License.
|
||||
|
||||
// Package agentproduct defines the caller-provided Agent product identity
|
||||
// carried on outbound DWS requests.
|
||||
// used for outbound observability and IM message display.
|
||||
package agentproduct
|
||||
|
||||
import (
|
||||
@@ -25,8 +25,9 @@ import (
|
||||
const (
|
||||
// EnvName is the runtime override for the Agent product identity.
|
||||
EnvName = "DWS_AGENT_PRODUCT"
|
||||
// HeaderName is the existing wire header used for the Agent product.
|
||||
HeaderName = "claw-type"
|
||||
// HeaderName is the observability header used for the Agent product.
|
||||
// It is intentionally separate from the routing/PAT claw-type header.
|
||||
HeaderName = "x-dws-agent-product"
|
||||
// MaxValueBytes bounds the value because it is attached to every outbound
|
||||
// MCP request. Supported values are ASCII, so bytes and characters match.
|
||||
MaxValueBytes = 64
|
||||
|
||||
@@ -103,3 +103,12 @@ func TestResolveFromEnv(t *testing.T) {
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func TestHeaderNameIsSeparateFromClawType(t *testing.T) {
|
||||
if HeaderName != "x-dws-agent-product" {
|
||||
t.Fatalf("HeaderName = %q, want x-dws-agent-product", HeaderName)
|
||||
}
|
||||
if HeaderName == "claw-type" {
|
||||
t.Fatal("Agent Product observability Header must not reuse claw-type")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,18 +15,18 @@ package edition
|
||||
|
||||
import "github.com/DingTalk-Real-AI/dingtalk-workspace-cli/internal/syncdata"
|
||||
|
||||
// DefaultOSSClawType is the default wire value for request header claw-type
|
||||
// in the open-source build. DWS_AGENT_PRODUCT may explicitly override the
|
||||
// request identity; unrelated caller inputs such as DINGTALK_AGENT do not.
|
||||
// DefaultOSSClawType is the fixed wire value for request header claw-type in
|
||||
// the open-source build. It is intentionally independent of caller-provided
|
||||
// environment variables so PAT and routing behaviour stays predictable.
|
||||
const DefaultOSSClawType = "openClaw"
|
||||
|
||||
// defaultHooks returns the open-source edition defaults.
|
||||
//
|
||||
// MergeHeaders is the only hook that ships with behaviour: it supplies
|
||||
// DefaultOSSClawType so every open-source MCP request has a stable default.
|
||||
// The core applies an optional DWS_AGENT_PRODUCT override after edition hooks.
|
||||
// All other fields are nil — the internal code interprets nil as "use
|
||||
// standard open-source behaviour".
|
||||
// DWS_AGENT_PRODUCT is sent through a separate observability Header and never
|
||||
// changes claw-type. All other fields are nil — the internal code interprets
|
||||
// nil as "use standard open-source behaviour".
|
||||
func defaultHooks() *Hooks {
|
||||
return &Hooks{
|
||||
Name: "open",
|
||||
|
||||
+21
-14
@@ -21,6 +21,7 @@ import (
|
||||
"context"
|
||||
"sync"
|
||||
|
||||
"github.com/DingTalk-Real-AI/dingtalk-workspace-cli/pkg/agentproduct"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
@@ -87,10 +88,10 @@ type Hooks struct {
|
||||
|
||||
// ClawTypeValue is the display identity carried only in message-send tool
|
||||
// arguments (parameter clawType) so the IM server can render the
|
||||
// "Send from AI" indicator on delivered messages. It is intentionally
|
||||
// separate from the HTTP claw-type Agent Product header and is not
|
||||
// overridden by DWS_AGENT_PRODUCT. Empty → DefaultOSSClawType; overlays
|
||||
// set their own message-display value (e.g. "wukong").
|
||||
// "Send from AI" indicator on delivered messages. A valid non-empty
|
||||
// DWS_AGENT_PRODUCT overrides this display default, but never the separate
|
||||
// HTTP claw-type routing/PAT header. Empty → DefaultOSSClawType; overlays
|
||||
// set their own message-display default (e.g. "wukong").
|
||||
ClawTypeValue string
|
||||
|
||||
// PersonalEventSourceID identifies the personal-event source channel
|
||||
@@ -115,8 +116,8 @@ type Hooks struct {
|
||||
MergeHeaders func(base map[string]string) map[string]string
|
||||
|
||||
// --- EnterpriseCredential HTTP headers ---
|
||||
// This hook is only for credential material. It must not set claw-type;
|
||||
// the core reasserts that Header after the hook returns.
|
||||
// This hook is only for credential material. It must not set claw-type or
|
||||
// x-dws-agent-product; the core reasserts both after the hook returns.
|
||||
EnterpriseCredentialHeaders func(base map[string]string) map[string]string
|
||||
|
||||
// --- auth ---
|
||||
@@ -206,16 +207,22 @@ func Override(h *Hooks) {
|
||||
current = h
|
||||
}
|
||||
|
||||
// ClawType returns the message-display identity for the active edition,
|
||||
// falling back to DefaultOSSClawType when the overlay does not set one.
|
||||
// Message-send helpers attach this value as the clawType tool argument so the
|
||||
// IM server can label delivered messages as sent via AI. It is a separate axis
|
||||
// from the HTTP claw-type header and is not affected by DWS_AGENT_PRODUCT.
|
||||
// ClawType returns the message-display identity for the active edition.
|
||||
// A valid non-empty DWS_AGENT_PRODUCT wins; otherwise the active edition's
|
||||
// ClawTypeValue (or DefaultOSSClawType) is used. Message-send helpers attach
|
||||
// this value as the clawType tool argument so the IM server can label delivered
|
||||
// messages as sent via AI. It never changes the HTTP claw-type routing/PAT
|
||||
// header.
|
||||
func ClawType() string {
|
||||
if v := Get().ClawTypeValue; v != "" {
|
||||
return v
|
||||
fallback := Get().ClawTypeValue
|
||||
if fallback == "" {
|
||||
fallback = DefaultOSSClawType
|
||||
}
|
||||
return DefaultOSSClawType
|
||||
value, err := agentproduct.ResolveFromEnv(fallback)
|
||||
if err != nil {
|
||||
return fallback
|
||||
}
|
||||
return value
|
||||
}
|
||||
|
||||
// PersonalEventSourceID returns the source channel for user-level events.
|
||||
|
||||
@@ -13,9 +13,14 @@
|
||||
|
||||
package edition
|
||||
|
||||
import "testing"
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/DingTalk-Real-AI/dingtalk-workspace-cli/pkg/agentproduct"
|
||||
)
|
||||
|
||||
func TestClawTypeDefaultsToOSSValue(t *testing.T) {
|
||||
t.Setenv(agentproduct.EnvName, "")
|
||||
prev := Get()
|
||||
defer Override(prev)
|
||||
|
||||
@@ -26,6 +31,7 @@ func TestClawTypeDefaultsToOSSValue(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestClawTypeUsesOverlayValue(t *testing.T) {
|
||||
t.Setenv(agentproduct.EnvName, "")
|
||||
prev := Get()
|
||||
defer Override(prev)
|
||||
|
||||
@@ -35,6 +41,28 @@ func TestClawTypeUsesOverlayValue(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestClawTypeUsesValidAgentProduct(t *testing.T) {
|
||||
t.Setenv(agentproduct.EnvName, " qwenwork ")
|
||||
prev := Get()
|
||||
defer Override(prev)
|
||||
|
||||
Override(&Hooks{Name: "overlay", ClawTypeValue: "wukong"})
|
||||
if got := ClawType(); got != "qwenwork" {
|
||||
t.Fatalf("ClawType() = %q, want qwenwork", got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestClawTypeInvalidAgentProductFallsBackToOverlay(t *testing.T) {
|
||||
t.Setenv(agentproduct.EnvName, "qwen work")
|
||||
prev := Get()
|
||||
defer Override(prev)
|
||||
|
||||
Override(&Hooks{Name: "overlay", ClawTypeValue: "wukong"})
|
||||
if got := ClawType(); got != "wukong" {
|
||||
t.Fatalf("ClawType() = %q, want overlay fallback wukong", got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestOpenStaticServersIncludesCoreProducts(t *testing.T) {
|
||||
servers := openStaticServers()
|
||||
byID := make(map[string]ServerInfo, len(servers))
|
||||
|
||||
@@ -1,13 +1,15 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Generate shortcut sections for DWS skills.
|
||||
"""Generate shortcut discovery sections for DWS skills.
|
||||
|
||||
The skill should teach agents which high-level shortcut entries are available
|
||||
in the public catalog and Runtime Schema. Leaf Schema publishes the Agent
|
||||
contract, while leaf `--help` remains the source of truth for accepted flags.
|
||||
without forcing large product catalogs into every common task. Leaf Schema
|
||||
publishes the Agent contract, while leaf `--help` remains the source of truth
|
||||
for accepted flags.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import json
|
||||
import os
|
||||
import sys
|
||||
@@ -47,6 +49,13 @@ MONO_END = "<!-- VISIBLE_SHORTCUTS_OVERVIEW_END -->"
|
||||
PRODUCT_START = "<!-- VISIBLE_SHORTCUTS_START -->"
|
||||
PRODUCT_END = "<!-- VISIBLE_SHORTCUTS_END -->"
|
||||
|
||||
# Large, high-frequency product skills should route known intents directly and
|
||||
# keep their full shortcut inventory in Runtime Catalog/Schema. Add services
|
||||
# here only after verifying that the product skill has its own reviewed routing
|
||||
# section and intent table; compacting a sparse skill without an alternative
|
||||
# route would make its shortcuts harder to discover.
|
||||
COMPACT_PRODUCT_SERVICES = {"chat"}
|
||||
|
||||
|
||||
def md_escape(value: Any) -> str:
|
||||
text = str(value or "")
|
||||
@@ -90,21 +99,24 @@ def mono_overview(items: list[dict[str, Any]]) -> str:
|
||||
path = SERVICE_TO_SKILL.get(service)
|
||||
skill = "—"
|
||||
if path:
|
||||
skill = next((part for part in path.parts if part.startswith("dingtalk-")), path.parent.name)
|
||||
rows.append(f"| `{md_escape(service)}` | {count} | `{md_escape(skill)}` | `dws shortcut list --service {md_escape(service)} --format json` |")
|
||||
skill = next((part for part in reversed(path.parts) if part.startswith("dingtalk-")), path.parent.name)
|
||||
rows.append(f"| `{md_escape(service)}` | {count} | `{md_escape(skill)}` |")
|
||||
body = "\n".join(rows)
|
||||
return f"""{MONO_START}
|
||||
## Shortcut 总览
|
||||
|
||||
下面统计当前公开 catalog 中的 shortcut。mono 模式不展开 200+ 行明细,避免 skill 过重;需要执行时先按产品路由,再用 `dws shortcut list --service <service> --format json` 读取参数、约束、风险和示例,最后用 `dws <service> +<shortcut> --help` 核对当前 Cobra flags。multi 模式的各产品 skill 会展开该产品的 shortcut 表。
|
||||
下面只统计当前公开 catalog 中的 shortcut,不展开完整明细。已知意图应先按产品 Skill、意图表或任务 reference 选择唯一命令;命令已选中时直接执行,只在参数或安全语义不确定时读取 leaf Schema,在当前 Cobra flags 不确定时读取 leaf Help。仅当现有路由和 reference 都无法定位低频能力时,才用 `dws shortcut list --service <service> --format json` 做最后回退;不要为已知高频意图加载完整产品 Catalog。
|
||||
|
||||
| 服务 | shortcut 数 | multi skill | 发现命令 |
|
||||
|---|---:|---|---|
|
||||
| 服务 | shortcut 数 | multi skill |
|
||||
|---|---:|---|
|
||||
{body}
|
||||
{MONO_END}"""
|
||||
|
||||
|
||||
def product_section(service: str, rows: list[dict[str, Any]]) -> str:
|
||||
if service in COMPACT_PRODUCT_SERVICES:
|
||||
return compact_product_section(service, rows)
|
||||
|
||||
table = []
|
||||
for item in rows:
|
||||
table.append(
|
||||
@@ -114,7 +126,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`)读取 Agent 选择、参数、约束、风险和确认语义;用 `dws shortcut list --service {service} --format json` 批量发现;最后以 `dws {service} <shortcut> --help` 核对当前 Cobra flags。
|
||||
以下 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 | 风险 | 适用场景 |
|
||||
|---|---|---|
|
||||
@@ -122,17 +134,38 @@ def product_section(service: str, rows: list[dict[str, Any]]) -> str:
|
||||
{PRODUCT_END}"""
|
||||
|
||||
|
||||
def update_mono(items: list[dict[str, Any]]) -> None:
|
||||
def compact_product_section(service: str, rows: list[dict[str, Any]]) -> str:
|
||||
return f"""{PRODUCT_START}
|
||||
## Shortcut 发现(按需)
|
||||
|
||||
`{md_escape(service)}` 当前有 {len(rows)} 条公开 shortcut,完整清单保留在 Runtime Catalog 与 Schema,不在高频产品根 Skill 中重复展开。已知意图直接使用下方的优先路由、意图表或任务 reference;命令已选中时直接执行,只在参数/安全语义不确定时读取 leaf Schema,在当前 Cobra flags 不确定时读取 leaf Help。
|
||||
|
||||
仅当现有路由和 reference 都无法定位低频能力时,才执行 `dws shortcut list --service {md_escape(service)} --format json` 做最后回退;不要为已知高频意图加载完整 Shortcut Catalog 或产品级 Schema。
|
||||
{PRODUCT_END}"""
|
||||
|
||||
|
||||
def apply_update(path: Path, text: str, updated: str, check: bool) -> bool:
|
||||
if updated == text:
|
||||
return False
|
||||
if check:
|
||||
print(f"generated skill drift: {path.relative_to(ROOT)}", file=sys.stderr)
|
||||
else:
|
||||
path.write_text(updated, encoding="utf-8")
|
||||
return True
|
||||
|
||||
|
||||
def update_mono(items: list[dict[str, Any]], check: bool) -> list[Path]:
|
||||
text = MONO_SKILL.read_text(encoding="utf-8")
|
||||
block = mono_overview(items)
|
||||
updated = replace_block(text, MONO_START, MONO_END, block, "## 产品总览")
|
||||
MONO_SKILL.write_text(updated, encoding="utf-8")
|
||||
return [MONO_SKILL] if apply_update(MONO_SKILL, text, updated, check) else []
|
||||
|
||||
|
||||
def update_product_skills(items: list[dict[str, Any]]) -> None:
|
||||
def update_product_skills(items: list[dict[str, Any]], check: bool) -> list[Path]:
|
||||
by_service: dict[str, list[dict[str, Any]]] = defaultdict(list)
|
||||
for item in items:
|
||||
by_service[item["service"]].append(item)
|
||||
changed = []
|
||||
for service, path in SERVICE_TO_SKILL.items():
|
||||
if service not in by_service:
|
||||
continue
|
||||
@@ -142,13 +175,26 @@ def update_product_skills(items: list[dict[str, Any]]) -> None:
|
||||
block = product_section(service, by_service[service])
|
||||
anchor = "## 概念地图" if service == "devapp" else "## 意图表"
|
||||
updated = replace_block(text, PRODUCT_START, PRODUCT_END, block, anchor)
|
||||
path.write_text(updated, encoding="utf-8")
|
||||
if apply_update(path, text, updated, check):
|
||||
changed.append(path)
|
||||
return changed
|
||||
|
||||
|
||||
def main() -> int:
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument(
|
||||
"--check",
|
||||
action="store_true",
|
||||
help="verify generated skill sections are current without rewriting files",
|
||||
)
|
||||
args = parser.parse_args()
|
||||
|
||||
items = collect_visible()
|
||||
update_mono(items)
|
||||
update_product_skills(items)
|
||||
changed = update_mono(items, args.check)
|
||||
changed.extend(update_product_skills(items, args.check))
|
||||
if args.check and changed:
|
||||
print("run: python3 scripts/gen_skill_shortcut_sections.py", file=sys.stderr)
|
||||
return 1
|
||||
print(f"visible_shortcuts={len(items)} services={len(set(item['service'] for item in items))}")
|
||||
return 0
|
||||
|
||||
|
||||
Executable
+41
@@ -0,0 +1,41 @@
|
||||
#!/bin/sh
|
||||
set -eu
|
||||
|
||||
ROOT="$(CDPATH= cd -- "$(dirname -- "$0")/../.." && pwd)"
|
||||
cd "$ROOT"
|
||||
|
||||
python3 scripts/gen_skill_shortcut_sections.py --check
|
||||
|
||||
chat_skill="skills/multi/dingtalk-chat/SKILL.md"
|
||||
mono_skill="skills/mono/SKILL.md"
|
||||
chat_max_bytes=14000
|
||||
|
||||
chat_bytes="$(wc -c < "$chat_skill" | tr -d ' ')"
|
||||
if [ "$chat_bytes" -gt "$chat_max_bytes" ]; then
|
||||
printf '%s\n' \
|
||||
"skill context budget exceeded: $chat_skill is ${chat_bytes} bytes (max ${chat_max_bytes})" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
shortcut_rows="$(
|
||||
awk '
|
||||
/<!-- VISIBLE_SHORTCUTS_START -->/ { in_block = 1; next }
|
||||
/<!-- VISIBLE_SHORTCUTS_END -->/ { in_block = 0 }
|
||||
in_block && /^\| `dws chat \+/ { count++ }
|
||||
END { print count + 0 }
|
||||
' "$chat_skill"
|
||||
)"
|
||||
if [ "$shortcut_rows" -ne 0 ]; then
|
||||
printf '%s\n' \
|
||||
"skill context budget exceeded: $chat_skill re-expanded $shortcut_rows shortcut rows" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if grep -Fq "充分阅读产品参考文件" "$mono_skill"; then
|
||||
printf '%s\n' \
|
||||
"skill context budget regression: $mono_skill requires full product-reference loading" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
printf '%s\n' \
|
||||
"skill context budget: ok (chat_bytes=$chat_bytes max=$chat_max_bytes shortcut_rows=$shortcut_rows)"
|
||||
+23
-21
@@ -39,26 +39,26 @@ cli_version: ">=1.0.15"
|
||||
<!-- VISIBLE_SHORTCUTS_OVERVIEW_START -->
|
||||
## Shortcut 总览
|
||||
|
||||
下面统计当前公开 catalog 中的 shortcut。mono 模式不展开 200+ 行明细,避免 skill 过重;需要执行时先按产品路由,再用 `dws shortcut list --service <service> --format json` 读取参数、约束、风险和示例,最后用 `dws <service> +<shortcut> --help` 核对当前 Cobra flags。multi 模式的各产品 skill 会展开该产品的 shortcut 表。
|
||||
下面只统计当前公开 catalog 中的 shortcut,不展开完整明细。已知意图应先按产品 Skill、意图表或任务 reference 选择唯一命令;命令已选中时直接执行,只在参数或安全语义不确定时读取 leaf Schema,在当前 Cobra flags 不确定时读取 leaf Help。仅当现有路由和 reference 都无法定位低频能力时,才用 `dws shortcut list --service <service> --format json` 做最后回退;不要为已知高频意图加载完整产品 Catalog。
|
||||
|
||||
| 服务 | shortcut 数 | multi skill | 发现命令 |
|
||||
|---|---:|---|---|
|
||||
| `aitable` | 29 | `dingtalk-workspace` | `dws shortcut list --service aitable --format json` |
|
||||
| `attendance` | 19 | `dingtalk-workspace` | `dws shortcut list --service attendance --format json` |
|
||||
| `calendar` | 20 | `dingtalk-workspace` | `dws shortcut list --service calendar --format json` |
|
||||
| `chat` | 97 | `dingtalk-workspace` | `dws shortcut list --service chat --format json` |
|
||||
| `contact` | 14 | `dingtalk-workspace` | `dws shortcut list --service contact --format json` |
|
||||
| `devapp` | 19 | `dingtalk-workspace` | `dws shortcut list --service devapp --format json` |
|
||||
| `ding` | 4 | `dingtalk-workspace` | `dws shortcut list --service ding --format json` |
|
||||
| `doc` | 17 | `dingtalk-workspace` | `dws shortcut list --service doc --format json` |
|
||||
| `drive` | 7 | `dingtalk-workspace` | `dws shortcut list --service drive --format json` |
|
||||
| `mail` | 10 | `dingtalk-workspace` | `dws shortcut list --service mail --format json` |
|
||||
| `minutes` | 6 | `dingtalk-workspace` | `dws shortcut list --service minutes --format json` |
|
||||
| `oa` | 7 | `dingtalk-workspace` | `dws shortcut list --service oa --format json` |
|
||||
| `report` | 2 | `dingtalk-workspace` | `dws shortcut list --service report --format json` |
|
||||
| `sheet` | 2 | `dingtalk-workspace` | `dws shortcut list --service sheet --format json` |
|
||||
| `todo` | 11 | `dingtalk-workspace` | `dws shortcut list --service todo --format json` |
|
||||
| `wiki` | 1 | `dingtalk-workspace` | `dws shortcut list --service wiki --format json` |
|
||||
| 服务 | shortcut 数 | multi skill |
|
||||
|---|---:|---|
|
||||
| `aitable` | 29 | `dingtalk-aitable` |
|
||||
| `attendance` | 19 | `dingtalk-misc` |
|
||||
| `calendar` | 20 | `dingtalk-calendar` |
|
||||
| `chat` | 97 | `dingtalk-chat` |
|
||||
| `contact` | 14 | `dingtalk-contact` |
|
||||
| `devapp` | 19 | `dingtalk-dev` |
|
||||
| `ding` | 4 | `dingtalk-misc` |
|
||||
| `doc` | 17 | `dingtalk-doc` |
|
||||
| `drive` | 7 | `dingtalk-drive` |
|
||||
| `mail` | 10 | `dingtalk-mail` |
|
||||
| `minutes` | 6 | `dingtalk-minutes` |
|
||||
| `oa` | 7 | `dingtalk-misc` |
|
||||
| `report` | 2 | `dingtalk-misc` |
|
||||
| `sheet` | 2 | `dingtalk-misc` |
|
||||
| `todo` | 11 | `dingtalk-todo` |
|
||||
| `wiki` | 1 | `dingtalk-wiki` |
|
||||
<!-- VISIBLE_SHORTCUTS_OVERVIEW_END -->
|
||||
|
||||
## 多组织 / 多账号
|
||||
@@ -174,7 +174,7 @@ Step 3 → 加 --yes 执行命令
|
||||
1. 意图分类:首先,判断用户指令的核心 动词/动作 属于哪一类。这比关注名词更重要。
|
||||
2. 歧义处理与信息追问:如果用户指令模糊或包含多个产品的关键字,严禁猜测。必须主动向用户追问以澄清意图。这是你作为智能助手而非命令执行器的核心价值。
|
||||
3. 精准产品映射:在完成前两步,意图已经清晰后,参考产品总览和意图判断决策树 来选择产品。
|
||||
4. 充分阅读产品参考文件,通过编写代码或直接调用指令实现用户意图。
|
||||
4. 按任务最小化读取:已知高频意图直接使用本 Skill 或产品 reference 已给出的唯一命令,不预加载完整产品参考文件;只有路由、参数或异常恢复确实需要时,才读取对应产品或任务 reference。
|
||||
|
||||
## 命令发现(Schema 渐进查询 + --help 互为补充)
|
||||
|
||||
@@ -184,6 +184,8 @@ Step 3 → 加 --yes 执行命令
|
||||
|
||||
本节同时适用于基础/原子命令与公开内建 `+` shortcut。用户自定义或未公开 shortcut 不进入发布 Schema;其是否可执行仍以当前 Cobra help 为准。
|
||||
|
||||
**已知命令路径例外**:当本 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 投影为准。
|
||||
|
||||
```bash
|
||||
@@ -250,7 +252,7 @@ dws schema --all --format json
|
||||
|------|--------|
|
||||
| 命令是否存在、当前 Cobra 接受哪些 flags | `dws <cli_path> --help` |
|
||||
| Agent 选择、参数映射/required/组合约束、risk/confirmation(原子/基础命令) | `dws schema "<cli_path>"`(按需加 `--compact`) |
|
||||
| shortcut 的参数、组合约束、risk/confirmation、示例 | `dws shortcut list --service <service> --format json` |
|
||||
| shortcut 的参数、组合约束、risk/confirmation、示例 | 已知路径优先 `dws schema --cli-path "<service> +<shortcut>" --compact --format json`;完整 `shortcut list` 仅用于无法定位低频能力时的最后回退 |
|
||||
| 人类可读用法 | `dws <cli_path> --help` |
|
||||
| 钉钉中的文档、文件、日程、消息等实际数据 | 真正执行对应的 `read` / `search` / `list` 命令 |
|
||||
|
||||
|
||||
@@ -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`)读取 Agent 选择、参数、约束、风险和确认语义;用 `dws shortcut list --service aitable --format json` 批量发现;最后以 `dws aitable <shortcut> --help` 核对当前 Cobra flags。
|
||||
以下 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 | 风险 | 适用场景 |
|
||||
|---|---|---|
|
||||
|
||||
@@ -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`)读取 Agent 选择、参数、约束、风险和确认语义;用 `dws shortcut list --service calendar --format json` 批量发现;最后以 `dws calendar <shortcut> --help` 核对当前 Cobra flags。
|
||||
以下 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 | 风险 | 适用场景 |
|
||||
|---|---|---|
|
||||
|
||||
@@ -18,109 +18,11 @@ metadata:
|
||||
> 命令参考:[chat.md](references/chat.md);表情:[chat-emoji-list.md](references/chat-emoji-list.md);剧本:[01-messaging.md](references/01-messaging.md)。
|
||||
|
||||
<!-- VISIBLE_SHORTCUTS_START -->
|
||||
## Shortcuts(无专用脚本/recipe 时优先)
|
||||
## Shortcut 发现(按需)
|
||||
|
||||
以下 shortcut 同时进入公开 catalog 与 Runtime Schema。先按本 skill 的意图表、脚本和 recipe 路由:存在精确覆盖该场景的专用脚本/recipe 时按其执行;否则用户意图命中时,shortcut 优先于手写原子命令。用 leaf Schema(例如 `dws schema --cli-path "chat +<shortcut>" --format json`)读取 Agent 选择、参数、约束、风险和确认语义;用 `dws shortcut list --service chat --format json` 批量发现;最后以 `dws chat <shortcut> --help` 核对当前 Cobra flags。
|
||||
`chat` 当前有 97 条公开 shortcut,完整清单保留在 Runtime Catalog 与 Schema,不在高频产品根 Skill 中重复展开。已知意图直接使用下方的优先路由、意图表或任务 reference;命令已选中时直接执行,只在参数/安全语义不确定时读取 leaf Schema,在当前 Cobra flags 不确定时读取 leaf Help。
|
||||
|
||||
| Shortcut | 风险 | 适用场景 |
|
||||
|---|---|---|
|
||||
| `dws chat +at-me` | read | 查最近 @我 的消息(自动算时间窗,投影发送人/时间/内容/会话) |
|
||||
| `dws chat +bot-find` | read | 搜索全部可用机器人(含他人/官方,返回 openDingTalkId 可发单聊) |
|
||||
| `dws chat +bot-search` | read | 搜索当前用户自己创建的机器人 |
|
||||
| `dws chat +broadcast` | write | 按姓名逐一给多个人群发同一条单聊消息(自动解析 userId、逐个发送) |
|
||||
| `dws chat +category-add-conversation` | write | 将会话移动到指定的自定义分组中 |
|
||||
| `dws chat +category-create` | write | 创建用户自定义会话分组 |
|
||||
| `dws chat +category-delete` | high-risk-write | 删除用户自定义会话分组 |
|
||||
| `dws chat +category-list` | read | 获取用户自定义会话分组 |
|
||||
| `dws chat +category-list-conversations` | read | 拉取指定自定义会话分组下的会话 |
|
||||
| `dws chat +category-remove-conversation` | write | 将会话从指定的自定义分组中移出 |
|
||||
| `dws chat +category-rename` | write | 更新用户自定义会话分组的名称 |
|
||||
| `dws chat +chat-add-bot` | write | 将机器人添加到群中 |
|
||||
| `dws chat +chat-audit-join` | write | 审批入群验证(通过/拒绝/删除/忽略/拉黑) |
|
||||
| `dws chat +chat-bots` | read | 查看群内所有机器人 |
|
||||
| `dws chat +chat-create` | write | 以当前用户身份创建钉钉群聊 |
|
||||
| `dws chat +chat-dismiss` | high-risk-write | 解散群聊(不可逆,需群主权限) |
|
||||
| `dws chat +chat-get-by-id` | read | 根据群号获取群聊信息 |
|
||||
| `dws chat +chat-invite-url` | read | 获取群邀请链接 |
|
||||
| `dws chat +chat-list-all` | read | 分页拉取我加入的所有群列表 |
|
||||
| `dws chat +chat-list-join-requests` | read | 分页拉取入群验证记录 |
|
||||
| `dws chat +chat-list-mine` | read | 拉取我创建/管理的群 |
|
||||
| `dws chat +chat-members-get` | read | 根据成员 openDingTalkId 批量查询群成员详情 |
|
||||
| `dws chat +chat-members-list` | read | 列出群成员并把用户与机器人分桶(支持群名语义解析) |
|
||||
| `dws chat +chat-messages` | read | 拉取某个会话(群聊或单聊)的消息列表并投影出发言人/文本/时间 |
|
||||
| `dws chat +chat-mute` | write | 全员禁言 / 取消全员禁言 |
|
||||
| `dws chat +chat-mute-member` | write | 指定群成员禁言 / 取消禁言 |
|
||||
| `dws chat +chat-quit` | write | 退出群聊 |
|
||||
| `dws chat +chat-remove-bot` | high-risk-write | 从群内移除机器人 |
|
||||
| `dws chat +chat-role-add` | write | 添加群身份 |
|
||||
| `dws chat +chat-role-list` | read | 拉取会话的群身份列表 |
|
||||
| `dws chat +chat-role-query-user` | read | 查询群成员的群身份 |
|
||||
| `dws chat +chat-role-remove` | high-risk-write | 删除群身份 |
|
||||
| `dws chat +chat-role-remove-user` | write | 移除用户的指定群身份 |
|
||||
| `dws chat +chat-role-set-user` | write | 设置用户的群身份(覆盖该用户的全部群身份) |
|
||||
| `dws chat +chat-role-update` | write | 更新群身份名称 |
|
||||
| `dws chat +chat-search` | read | 按关键词搜索群聊 |
|
||||
| `dws chat +chat-set-admin` | write | 设置 / 取消群管理员 |
|
||||
| `dws chat +chat-set-history` | write | 设置新成员入群可查看历史消息范围 |
|
||||
| `dws chat +chat-transfer-owner` | write | 转让群主 |
|
||||
| `dws chat +chat-update` | write | 更新群名称(仅名称,不支持 description) |
|
||||
| `dws chat +chat-update-alias` | write | 设置群备注(仅自己可见) |
|
||||
| `dws chat +chat-update-icon` | write | 更新群头像 |
|
||||
| `dws chat +chat-update-nick` | write | 设置当前用户在群内的群昵称 |
|
||||
| `dws chat +chat-update-settings` | write | 更新群设置(settingKey + status) |
|
||||
| `dws chat +conversation-clear-all-red-point` | write | 清除所有会话红点(全部已读) |
|
||||
| `dws chat +conversation-clear-messages` | high-risk-write | 清空当前用户指定会话的聊天记录(仅本人视角,不可逆) |
|
||||
| `dws chat +conversation-clear-red-point` | write | 清除会话红点 |
|
||||
| `dws chat +conversation-hide` | write | 会话列表中隐藏会话(收到新消息会重新出现) |
|
||||
| `dws chat +conversation-info` | read | 获取会话信息(群聊传 --group,单聊传 --open-dingtalk-id) |
|
||||
| `dws chat +conversation-list` | read | 分页获取当前用户的全部会话列表(单聊+群聊) |
|
||||
| `dws chat +conversation-list-top` | read | 拉取置顶会话列表,可只看群聊或单聊 |
|
||||
| `dws chat +conversation-mark-read` | write | 标记消息已读(该消息及之前的消息都标记为已读) |
|
||||
| `dws chat +conversation-mark-unread` | write | 标记会话为未读 |
|
||||
| `dws chat +conversation-mute` | write | 会话消息免打扰(支持单聊/群聊) |
|
||||
| `dws chat +conversation-set-top` | write | 批量会话置顶 / 取消置顶(最多 10 个) |
|
||||
| `dws chat +dm` | write | 按姓名直接给某人发单聊消息(自动解析 userId) |
|
||||
| `dws chat +feed-group-query-item` | read | 在会话分组结果中按会话 ID 精确查询多项 |
|
||||
| `dws chat +flag-cancel` | write | 取消收藏一条或多条消息(最多 10 条) |
|
||||
| `dws chat +flag-create` | write | 收藏一条或多条消息(最多 10 条) |
|
||||
| `dws chat +flag-list` | read | 分页查询当前用户收藏的消息 |
|
||||
| `dws chat +group-members` | read | 按群名列出群成员(自动搜群解析 openConversationId) |
|
||||
| `dws chat +messages-add-emoji` | write | 对消息添加 emoji 表情回应 |
|
||||
| `dws chat +messages-add-text-emotion` | write | 对消息添加文字表情回应 |
|
||||
| `dws chat +messages-batch-recall-by-bot` | write | 机器人撤回单聊消息 |
|
||||
| `dws chat +messages-batch-send-by-bot` | write | 机器人批量向用户发送单聊 Markdown 消息 |
|
||||
| `dws chat +messages-combine-forward` | write | 合并转发多条消息 |
|
||||
| `dws chat +messages-create-text-emotion` | write | 创建文字表情(获取 emotionId) |
|
||||
| `dws chat +messages-forward` | write | 转发单条消息 |
|
||||
| `dws chat +messages-forward-topic` | write | 转发话题消息到目标会话 |
|
||||
| `dws chat +messages-list` | read | 拉取群聊会话消息 |
|
||||
| `dws chat +messages-list-direct` | read | 拉取单聊会话消息 |
|
||||
| `dws chat +messages-list-pin` | read | 拉取会话中钉住的消息列表 |
|
||||
| `dws chat +messages-list-unread-conversations` | read | 获取有未读消息的会话列表 |
|
||||
| `dws chat +messages-mget` | read | 根据消息 ID 批量查询消息(最多 50 条) |
|
||||
| `dws chat +messages-query-send-status` | read | 查询消息发送状态 |
|
||||
| `dws chat +messages-read-status` | read | 查询消息的已读/未读状态 |
|
||||
| `dws chat +messages-recall` | write | 撤回当前用户发送的消息 |
|
||||
| `dws chat +messages-recall-by-bot` | write | 机器人撤回群消息 |
|
||||
| `dws chat +messages-remove-emoji` | write | 移除消息的 emoji 表情回应 |
|
||||
| `dws chat +messages-remove-text-emotion` | write | 移除消息的文字表情回应 |
|
||||
| `dws chat +messages-reply` | write | 以当前用户身份引用回复消息(自动补全原发送者) |
|
||||
| `dws chat +messages-resource-download` | read | 安全下载消息资源(图片/视频/语音/文件)到工作目录内,无需交互确认 |
|
||||
| `dws chat +messages-resource-url` | read | 获取消息资源(图片/视频/语音)下载链接 |
|
||||
| `dws chat +messages-send` | write | 统一发送文本、Markdown、当前用户文件或已有 mediaId 图片 |
|
||||
| `dws chat +messages-send-by-bot` | write | 机器人向群聊发送 Markdown 消息 |
|
||||
| `dws chat +messages-send-by-webhook` | write | 自定义机器人 Webhook 发送群消息 |
|
||||
| `dws chat +messages-send-card` | write | 创建流式卡片,可在同一次调用中写入内容并结束 |
|
||||
| `dws chat +messages-set-pin` | write | 钉住消息(Pin) |
|
||||
| `dws chat +messages-set-top` | write | 置顶消息 |
|
||||
| `dws chat +messages-unset-pin` | write | 取消钉住消息(Unpin) |
|
||||
| `dws chat +messages-unset-top` | write | 取消置顶消息 |
|
||||
| `dws chat +messages-update-card` | write | 流式更新卡片内容(最后一次 --flow-status 应为 3) |
|
||||
| `dws chat +my-groups` | read | 列出我加入的群,可按类型过滤并投影关键字段 |
|
||||
| `dws chat +search-msg` | read | 多维搜索消息,可全量翻页并批量富化详情 |
|
||||
| `dws chat +send-to-group` | write | 按群名直接给群发消息(自动搜群解析 openConversationId) |
|
||||
| `dws chat +thread-replies` | read | 拉取某条话题消息的全部回复并投影出发言人/文本/时间 |
|
||||
| `dws chat +unread-chats` | read | 列出我有未读消息的会话(投影会话名/未读数/会话ID) |
|
||||
仅当现有路由和 reference 都无法定位低频能力时,才执行 `dws shortcut list --service chat --format json` 做最后回退;不要为已知高频意图加载完整 Shortcut Catalog 或产品级 Schema。
|
||||
<!-- VISIBLE_SHORTCUTS_END -->
|
||||
|
||||
## IM Shortcut 优先路由
|
||||
|
||||
@@ -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`)读取 Agent 选择、参数、约束、风险和确认语义;用 `dws shortcut list --service contact --format json` 批量发现;最后以 `dws contact <shortcut> --help` 核对当前 Cobra flags。
|
||||
以下 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 | 风险 | 适用场景 |
|
||||
|---|---|---|
|
||||
|
||||
@@ -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`)读取 Agent 选择、参数、约束、风险和确认语义;用 `dws shortcut list --service devapp --format json` 批量发现;最后以 `dws devapp <shortcut> --help` 核对当前 Cobra flags。
|
||||
以下 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 | 风险 | 适用场景 |
|
||||
|---|---|---|
|
||||
|
||||
@@ -29,7 +29,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`)读取 Agent 选择、参数、约束、风险和确认语义;用 `dws shortcut list --service doc --format json` 批量发现;最后以 `dws doc <shortcut> --help` 核对当前 Cobra flags。
|
||||
以下 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 | 风险 | 适用场景 |
|
||||
|---|---|---|
|
||||
|
||||
@@ -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`)读取 Agent 选择、参数、约束、风险和确认语义;用 `dws shortcut list --service drive --format json` 批量发现;最后以 `dws drive <shortcut> --help` 核对当前 Cobra flags。
|
||||
以下 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 | 风险 | 适用场景 |
|
||||
|---|---|---|
|
||||
|
||||
@@ -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`)读取 Agent 选择、参数、约束、风险和确认语义;用 `dws shortcut list --service mail --format json` 批量发现;最后以 `dws mail <shortcut> --help` 核对当前 Cobra flags。
|
||||
以下 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 | 风险 | 适用场景 |
|
||||
|---|---|---|
|
||||
|
||||
@@ -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`)读取 Agent 选择、参数、约束、风险和确认语义;用 `dws shortcut list --service minutes --format json` 批量发现;最后以 `dws minutes <shortcut> --help` 核对当前 Cobra flags。
|
||||
以下 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 | 风险 | 适用场景 |
|
||||
|---|---|---|
|
||||
|
||||
@@ -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`)读取 Agent 选择、参数、约束、风险和确认语义;用 `dws shortcut list --service attendance --format json` 批量发现;最后以 `dws attendance <shortcut> --help` 核对当前 Cobra flags。
|
||||
以下 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 | 风险 | 适用场景 |
|
||||
|---|---|---|
|
||||
|
||||
@@ -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`)读取 Agent 选择、参数、约束、风险和确认语义;用 `dws shortcut list --service ding --format json` 批量发现;最后以 `dws ding <shortcut> --help` 核对当前 Cobra flags。
|
||||
以下 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 | 风险 | 适用场景 |
|
||||
|---|---|---|
|
||||
|
||||
@@ -473,7 +473,7 @@ dws oa approval revert-task --instance-id <processInstanceId> --task-id <taskId>
|
||||
<!-- VISIBLE_SHORTCUTS_START -->
|
||||
## Shortcuts(无专用脚本/recipe 时优先)
|
||||
|
||||
以下 shortcut 同时进入公开 catalog 与 Runtime Schema。先按本 skill 的意图表、脚本和 recipe 路由:存在精确覆盖该场景的专用脚本/recipe 时按其执行;否则用户意图命中时,shortcut 优先于手写原子命令。用 leaf Schema(例如 `dws schema --cli-path "oa +<shortcut>" --format json`)读取 Agent 选择、参数、约束、风险和确认语义;用 `dws shortcut list --service oa --format json` 批量发现;最后以 `dws oa <shortcut> --help` 核对当前 Cobra flags。
|
||||
以下 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 | 风险 | 适用场景 |
|
||||
|---|---|---|
|
||||
|
||||
@@ -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`)读取 Agent 选择、参数、约束、风险和确认语义;用 `dws shortcut list --service report --format json` 批量发现;最后以 `dws report <shortcut> --help` 核对当前 Cobra flags。
|
||||
以下 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 | 风险 | 适用场景 |
|
||||
|---|---|---|
|
||||
|
||||
@@ -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`)读取 Agent 选择、参数、约束、风险和确认语义;用 `dws shortcut list --service sheet --format json` 批量发现;最后以 `dws sheet <shortcut> --help` 核对当前 Cobra flags。
|
||||
以下 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 | 风险 | 适用场景 |
|
||||
|---|---|---|
|
||||
|
||||
@@ -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`)读取 Agent 选择、参数、约束、风险和确认语义;用 `dws shortcut list --service todo --format json` 批量发现;最后以 `dws todo <shortcut> --help` 核对当前 Cobra flags。
|
||||
以下 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 | 风险 | 适用场景 |
|
||||
|---|---|---|
|
||||
|
||||
@@ -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`)读取 Agent 选择、参数、约束、风险和确认语义;用 `dws shortcut list --service wiki --format json` 批量发现;最后以 `dws wiki <shortcut> --help` 核对当前 Cobra flags。
|
||||
以下 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 | 风险 | 适用场景 |
|
||||
|---|---|---|
|
||||
|
||||
Reference in New Issue
Block a user