1453 lines
60 KiB
Markdown
1453 lines
60 KiB
Markdown
# yulan Open Platform App Command Routing
|
|
|
|
> Status: draft, 2026-06-05
|
|
> Branch: `feat/dws-devapp`
|
|
> Scope: open-source DWS integration for DingTalk Open Platform enterprise app management.
|
|
|
|
This document splits the application-side command set for yulan. It is written for the open-source DWS repository and must not contain personal MCP gateway keys, pre-release tokens, cookies, or production app data.
|
|
|
|
## 0. Current Repository Deliverables
|
|
|
|
The current branch contains the application-side command contract, Agent
|
|
routing assets, and a hardcoded `devapp` helper command tree. `devapp` does not
|
|
depend on MCP service discovery or registry overlay publication. Runtime calls
|
|
resolve the MCP endpoint from the internal edition's `SupplementServers` or
|
|
`StaticServers` hook; local debug may override it with
|
|
`DINGTALK_DEVAPP_MCP_URL`. Do not commit the endpoint URL or its `?key=` value.
|
|
|
|
| Artifact | Status | Evidence |
|
|
| --- | --- | --- |
|
|
| Main design document | Done in repo | `docs/devapp-yulan-command-routing.md` covers naming, command tree, P0 RPC contracts, permissions, Agent routing, rollout, and acceptance cases. |
|
|
| README discoverability | Done in repo | `README.md` and `README_zh.md` link to this design under Reference/参考文档. |
|
|
| Mono skill routing | Done in repo | `skills/mono/SKILL.md` routes Open Platform app intents to `devapp`. |
|
|
| Mono product reference | Done in repo | `skills/mono/references/products/devapp.md` summarizes app CRUD, permissions, credentials, events, versions, and troubleshooting. |
|
|
| Multi product skill | Done in repo | `skills/multi/dingtalk-devapp/` defines the standalone `dingtalk-devapp` skill and reference file. |
|
|
| Agent intent test cases | Done in repo | `test/skill_tests.md` includes 21 devapp cases, devdoc/workbench counterexamples, and routing clarification cases for the overloaded word `应用`; `test/skill_tests_results.md` reports those cases passing. |
|
|
| Skill test parser | Done in repo | `test/run_skill_tests.py` now preserves the current `#### dws ...` command section for each case. |
|
|
| Static/e2e skill verifier handling | Done in repo | `test/skill_static` and `test/skill_e2e` cover routing docs; runtime smoke uses an externally configured MCP endpoint. |
|
|
| Sensitive config guard | Done in repo | `.gitignore` excludes local `docs/mcp/serviceconfig-pre*` files so personal MCP gateway keys are not staged. |
|
|
| MCP HSF tool configuration | External dependency | Configure and publish tools on the DingTalk MCP developer platform using sections 3, 4, and 13. |
|
|
| DWS runtime command availability | Done in repo | `dws devapp ...` and alias `dws app ...` are hardcoded helper commands. No service discovery is required for this command tree. |
|
|
| Full production readiness | Not yet proven | Requires section 13 release exit criteria, including MCP `tools/list`, DWS schema, APP/SNS permission smoke, and write guard smoke. |
|
|
|
|
State labels:
|
|
|
|
1. `Done in repo` means the open-source branch contains the artifact and it is
|
|
covered by local checks.
|
|
2. `External dependency` means backend/MCP tool publication must happen outside
|
|
this repository.
|
|
3. `Not yet proven` means do not claim the application command set is ready for
|
|
Agent production usage until runtime smoke evidence exists.
|
|
|
|
## 1. Naming
|
|
|
|
Use `devapp` as the canonical open-source product id:
|
|
|
|
```text
|
|
dws devapp ...
|
|
```
|
|
|
|
`app` is kept as a compatibility alias by the hardcoded helper. Do not
|
|
introduce `opendev` or `apps` as new canonical roots; if they are needed for
|
|
migration, make them redirect or hidden aliases.
|
|
|
|
Reasoning:
|
|
|
|
1. `devapp` is specific enough for Open Platform developer applications.
|
|
2. `app` is the PRD/user-facing shorthand and can remain as an alias.
|
|
3. The open-source DWS repository is protocol-first; `devapp` uses helper commands so it can run without service discovery.
|
|
|
|
Branch context:
|
|
|
|
```text
|
|
branch: feat/dws-devapp
|
|
base: latest upstream/main has been merged into the local design branch
|
|
runtime: devapp command availability does not depend on MCP service discovery
|
|
```
|
|
|
|
The checked-in open-source helper code owns the `dws devapp` command tree. The
|
|
MCP endpoint is owned by the internal edition layer, typically through
|
|
`edition.SupplementServers`; `DINGTALK_DEVAPP_MCP_URL` remains a local debug
|
|
override.
|
|
|
|
## 2. Command Tree
|
|
|
|
```text
|
|
dws devapp
|
|
list Search app list with listByCondition semantics
|
|
get Get one app detail
|
|
create Create an enterprise internal app
|
|
update Update app base information
|
|
delete Delete an enterprise internal app
|
|
credentials get Controlled credential read, especially appSecret/clientSecret
|
|
webapp get Get web app configuration
|
|
permission list List APP and SNS permissions
|
|
permission search Search permission candidates, alias over list semantics
|
|
permission detail Show one permission's full API coverage
|
|
permission add Apply permissions into current app version changes
|
|
permission remove Remove authorized permissions
|
|
event list List subscribable events and current subscription state
|
|
event subscribe Subscribe events by eventCodes
|
|
event unsubscribe Unsubscribe events by eventCodes
|
|
robot create Create a new agent app + robot synchronously
|
|
robot submit Submit async robot-create task (supports retry by taskId)
|
|
robot result Query async robot-create task result
|
|
robot get Get robot config of an existing app
|
|
robot config Create or update robot config on an existing app
|
|
robot enable Enable / re-enable robot capability
|
|
robot disable Disable robot capability
|
|
version create Save app version
|
|
version list List app versions (cursor paged)
|
|
version get Get one version detail
|
|
version check-approval Precheck publish approval requirements (no publish)
|
|
version publish Submit publish, possibly creating approval
|
|
version status Poll publish and approval status
|
|
```
|
|
|
|
Robot, version, and event commands are hardcoded helper leaves over the `op-app`
|
|
MCP server (same pinned endpoint as the rest of `devapp`, no service discovery).
|
|
|
|
Open-source examples should use `--format json` for agent consumption:
|
|
|
|
```bash
|
|
dws devapp list --name DemoApp --page-size 20 --format json
|
|
dws devapp get --unified-app-id UNIFIED_APP_ID --format json
|
|
dws devapp permission list --unified-app-id UNIFIED_APP_ID --format json
|
|
```
|
|
|
|
For write operations, DWS should use the existing open-source safety model:
|
|
|
|
```bash
|
|
dws devapp create --name DemoApp --type internal --desc "internal app" --dry-run --format json
|
|
dws devapp create --name DemoApp --type internal --desc "internal app" --yes --format json
|
|
```
|
|
|
|
Do not send confirmation fields such as `confirmCreate`, `confirmUpdate`, `confirmDelete`, `confirmPermission`, or `confirmSensitive` to MCP or HSF. Confirmation is only a CLI/Agent guard.
|
|
|
|
## 3. Capability Matrix
|
|
|
|
| Area | CLI command | MCP tool | Backend HSF facade | Current backend status | Open-source DWS status |
|
|
| --- | --- | --- | --- | --- | --- |
|
|
| App query | `devapp list` | `list_dev_app` | `OpenInnerAppQueryFacade.listByCondition` | Implemented | Hardcoded helper |
|
|
| App detail | `devapp get` | `get_dev_app` | `OpenInnerAppQueryFacade.getDetail` | Implemented | Hardcoded helper |
|
|
| App create | `devapp create` | `create_dev_app` | `OpenInnerAppManageFacade.create` | Implemented | Hardcoded helper |
|
|
| App update | `devapp update` | `update_dev_app` | `OpenInnerAppManageFacade.update` | Implemented | Hardcoded helper |
|
|
| App delete | `devapp delete` | `delete_dev_app` | `OpenInnerAppManageFacade.delete` | Implemented | Hardcoded helper |
|
|
| Credentials | `devapp credentials get` | `get_dev_app_credentials` | `OpenInnerAppQueryFacade.getCredentials` | Implemented | Hardcoded helper |
|
|
| Web app | `devapp webapp get/config` | `get_extension_webapp_config`/`set_extension_webapp_config` | Webapp facade | Implemented | Hardcoded helper; locator field is `unifiedAppId`; config fields are `h5PageType/homepageUrl/pcHomepageUrl/ompUrl`. |
|
|
| Permission list | `devapp permission list/search/detail` | `list_dev_app_permissions` | `OpenInnerAppPermissionFacade.list` | Implemented | Hardcoded helper |
|
|
| Permission apply | `devapp permission add` | `apply_dev_app_permissions` | `OpenInnerAppPermissionFacade.apply` | Implemented | Hardcoded helper |
|
|
| Permission remove | `devapp permission remove` | `remove_dev_app_permissions` | `OpenInnerAppPermissionFacade.remove` | Implemented | Hardcoded helper |
|
|
| Events | `devapp event list/subscribe/unsubscribe` | `list_dev_app_events`, `subscribe_dev_app_events`, `unsubscribe_dev_app_events` | Implemented | Hardcoded helper |
|
|
| Robot create | `devapp robot create/submit/result` | `create_dingtalk_robot`/`submit_robot_create_task`/`query_robot_create_result` | Implemented | Hardcoded helper |
|
|
| Robot config | `devapp robot get/config/enable/disable` | `get_extension_robot_config`, `set_extension_robot_config`, `enable_dev_app_robot`, `disable_dev_app_robot` | Implemented | Hardcoded helper |
|
|
| Version | `devapp version create/list/get/check-approval/publish/status` | `create_dev_app_version`, `list_dev_app_versions`, `get_dev_app_version_detail`, `publish_dev_app_version`, `get_dev_app_version_status` | Implemented | Hardcoded helper |
|
|
|
|
P0 for yulan is app CRUD, permissions, credentials, and web app config. Robot,
|
|
version, and event subscription are now all implemented as hardcoded helper
|
|
leaves over the same `op-app` MCP server.
|
|
|
|
### 3.1 P0 RPC Contract
|
|
|
|
All P0 MCP tools should be configured as HSF-backed tools that call one facade
|
|
method with one business request object. Do not expose a separate
|
|
`BaseOpenRequestVO` to the Agent. The provider should receive caller identity
|
|
from MCP system context and merge it into the business request before executing
|
|
the facade method.
|
|
|
|
Common HSF mapping:
|
|
|
|
| Source | Target request field | Public CLI flag | Required | Notes |
|
|
| --- | --- | --- | --- | --- |
|
|
| MCP system org context | `corpId` | No | Yes | DingTalk corpId. Do not ask the user for internal `orgId`. |
|
|
| MCP system user context | `userId` | No | Yes | DingTalk user/staff id. Do not ask the user for internal `uid`. |
|
|
| CLI/MCP tool input | business fields | Yes | Per tool | Search, app locator, update fields, permission scopes. |
|
|
|
|
Common result wrapper:
|
|
|
|
```json
|
|
{
|
|
"success": true,
|
|
"result": {},
|
|
"errorCode": "",
|
|
"errorMsg": "",
|
|
"arguments": []
|
|
}
|
|
```
|
|
|
|
MCP output configuration should keep the wrapper fields and configure success as
|
|
`ServiceResult.success == true`. DWS/Agent normalization can add `ok`, `reason`,
|
|
`summary`, and `nextAction`, but raw `errorCode/errorMsg` must remain visible.
|
|
|
|
Naming convention:
|
|
|
|
- Public CLI flags use user-facing names: `--name`, `--desc`, `--icon`, `--homepage-url`.
|
|
- MCP app create/update use the current backend request field names: `name`, `desc`, `icon`.
|
|
- MCP app list/detail use the simplified locator/search fields: `unifiedAppId`, `appKey`, `name`.
|
|
- The CLI helper keeps public flags stable, for example `--name` -> `name` for create/update/list/detail.
|
|
|
|
### 3.2 P0 Tool Contracts
|
|
|
|
#### list_dev_app
|
|
|
|
| Item | Contract |
|
|
| --- | --- |
|
|
| CLI | `dws devapp list` |
|
|
| HSF facade | `OpenInnerAppQueryFacade.listByCondition` |
|
|
| Request shape | One object with system context and optional list filters. |
|
|
| Required public fields | None; default `pageSize=20`. |
|
|
| Identity source | `corpId/userId` from MCP system context. |
|
|
|
|
Input fields:
|
|
|
|
| Field | Type | CLI flag | Required | Notes |
|
|
| --- | --- | --- | --- | --- |
|
|
| `pageSize` | number | `--page-size` | no | Default 20; cap by backend. |
|
|
| `cursor` | string | `--cursor` | no | Cursor from previous response; empty for first page. |
|
|
| `name` | string | `--name` | no | App name keyword. |
|
|
|
|
Normalized response:
|
|
|
|
```json
|
|
{
|
|
"ok": true,
|
|
"operation": "app.list",
|
|
"page": {"pageSize": 20, "nextCursor": ""},
|
|
"apps": [
|
|
{
|
|
"unifiedAppId": "UNIFIED_APP_ID",
|
|
"name": "DemoApp",
|
|
"appKey": "dingxxx",
|
|
"creator": "Creator",
|
|
"robotName": "BotName",
|
|
"modifiedAt": "2026-06-05 00:00:00"
|
|
}
|
|
],
|
|
"raw": {}
|
|
}
|
|
```
|
|
|
|
#### get_dev_app
|
|
|
|
| Item | Contract |
|
|
| --- | --- |
|
|
| CLI | `dws devapp get` |
|
|
| HSF facade | `OpenInnerAppQueryFacade.getDetail` |
|
|
| Request shape | One object with system context and `unifiedAppId`. |
|
|
| Required public fields | `unifiedAppId`. |
|
|
| Identity source | `corpId/userId` from MCP system context. |
|
|
|
|
Locator field:
|
|
|
|
| Field | Type | CLI flag | Notes |
|
|
| --- | --- | --- | --- |
|
|
| `unifiedAppId` | string | `--unified-app-id` | Required app identity. |
|
|
|
|
Detail response may include identifiers, configuration summary, and credentials.
|
|
Treat `clientSecret/appSecret` as sensitive values in all user-visible output:
|
|
|
|
```json
|
|
{
|
|
"ok": true,
|
|
"operation": "app.get",
|
|
"app": {
|
|
"unifiedAppId": "UNIFIED_APP_ID",
|
|
"name": "DemoApp",
|
|
"appKey": "dingxxx",
|
|
"developType": "internal",
|
|
"creator": "Creator",
|
|
"visibility": "partial_members",
|
|
"status": "online",
|
|
"gmtModified": "2026-06-05T00:00:00+08:00"
|
|
},
|
|
"raw": {}
|
|
}
|
|
```
|
|
|
|
#### create_dev_app
|
|
|
|
| Item | Contract |
|
|
| --- | --- |
|
|
| CLI | `dws devapp create` |
|
|
| HSF facade | `OpenInnerAppManageFacade.create` |
|
|
| Request shape | One object with system context and app base fields. |
|
|
| Required public fields | `name`; `--type` is a CLI-only guard and currently must be `internal`. |
|
|
| Write guard | CLI `--dry-run`, then `--yes`; no MCP confirm field. |
|
|
|
|
Input fields:
|
|
|
|
| Field | Type | CLI flag | Required | Notes |
|
|
| --- | --- | --- | --- | --- |
|
|
| `name` | string | `--name` | yes | Enterprise internal app name. |
|
|
| `desc` | string | `--desc` | no | App description. |
|
|
| `icon` | string | `--icon` | no | Existing media/resource id if backend accepts it. |
|
|
|
|
Response:
|
|
|
|
```json
|
|
{
|
|
"ok": true,
|
|
"operation": "app.create",
|
|
"app": {
|
|
"unifiedAppId": "UNIFIED_APP_ID",
|
|
"name": "DemoApp",
|
|
"appKey": "dingxxx"
|
|
},
|
|
"nextAction": "run devapp get or configure permissions",
|
|
"raw": {}
|
|
}
|
|
```
|
|
|
|
#### update_dev_app
|
|
|
|
| Item | Contract |
|
|
| --- | --- |
|
|
| CLI | `dws devapp update` |
|
|
| HSF facade | `OpenInnerAppManageFacade.update` |
|
|
| Request shape | One object with system context, locator, and changed fields. |
|
|
| Required public fields | One app locator and at least one changed field. |
|
|
| Write guard | CLI `--dry-run`, then `--yes`; no MCP confirm field. |
|
|
|
|
Locator fields are the same as `get_dev_app`, but the Agent should
|
|
prefer `unifiedAppId` after resolving a unique app.
|
|
|
|
Update fields:
|
|
|
|
| Field | Type | CLI flag | Notes |
|
|
| --- | --- | --- | --- |
|
|
| `name` | string | `--name` | New app name. |
|
|
| `desc` | string | `--desc` | New app description. |
|
|
| `icon` | string | `--icon` | New icon resource id if backend accepts it. |
|
|
| `international` | object/string | `--international` | Only expose when schema describes the shape. |
|
|
| `supportHarmony` | boolean | `--support-harmony` | Harmony support switch if backend exposes it. |
|
|
|
|
Response should return the updated app summary plus a `changedFields` array when
|
|
available.
|
|
|
|
#### delete_dev_app
|
|
|
|
| Item | Contract |
|
|
| --- | --- |
|
|
| CLI | `dws devapp delete` |
|
|
| HSF facade | `OpenInnerAppManageFacade.delete` |
|
|
| Request shape | One object with system context and one unique app locator. |
|
|
| Required public fields | One unique app locator. |
|
|
| Write guard | CLI `--dry-run`, then `--yes`; no MCP confirm field. |
|
|
|
|
The Agent must display the resolved app summary before executing delete with
|
|
`--yes`. The response should include `deleted=true`, the app summary, and raw
|
|
backend fields.
|
|
|
|
#### list_dev_app_permissions
|
|
|
|
| Item | Contract |
|
|
| --- | --- |
|
|
| CLI | `dws devapp permission list/search/detail` |
|
|
| HSF facade | `OpenInnerAppPermissionFacade.list` |
|
|
| Request shape | One object with system context, app locator, and optional filters. |
|
|
| Required public fields | `unifiedAppId`. |
|
|
| Web semantics | Equivalent to grouped `scope/list` for APP and SNS scopes. |
|
|
|
|
Input fields:
|
|
|
|
| Field | Type | CLI flag | Required | Notes |
|
|
| --- | --- | --- | --- | --- |
|
|
| `unifiedAppId` | string | `--unified-app-id` | yes | Unified app id. |
|
|
| `keyword` | string | `--keyword` | no | Maps to Web `key`. |
|
|
| `apiStatus` | string | `--api-status` | no | Web `apiStatus` filter. |
|
|
| `authStatus` | string | `--status` | no | `ALL/AUTHED/UNAUTHED`, post-filter. |
|
|
| `scopeType` | string | `--scope-type` | no | `APP/SNS`; empty returns both. |
|
|
| `scopeValue` | string | `--scope` | no | Detail mode and exact post-filter. |
|
|
| `pageSize` | number | `--page-size` | no | Return count cap; default 20. |
|
|
| `cursor` | string | `--cursor` | no | Cursor from previous response; empty for first page. |
|
|
|
|
Normalized response should flatten `appScopes` and `snsScopes` into one
|
|
`permissions[]` list while preserving group metadata:
|
|
|
|
```json
|
|
{
|
|
"ok": true,
|
|
"operation": "permission.list",
|
|
"mode": "list",
|
|
"permissions": [
|
|
{
|
|
"scopeValue": "qyapi_robot_sendmsg",
|
|
"scopeName": "企业内机器人发消息权限",
|
|
"scopeType": "APP",
|
|
"categoryTitle": "机器人",
|
|
"authed": true,
|
|
"canEdit": true,
|
|
"requiredApproval": false,
|
|
"allowedActions": ["view", "detail", "remove"],
|
|
"apiCount": 4,
|
|
"apiPreview": [{"name": "API name"}]
|
|
}
|
|
],
|
|
"summary": {"totalMatched": 1, "returnedCount": 1},
|
|
"nextCursor": "",
|
|
"raw": {}
|
|
}
|
|
```
|
|
|
|
#### apply_dev_app_permissions
|
|
|
|
| Item | Contract |
|
|
| --- | --- |
|
|
| CLI | `dws devapp permission add` |
|
|
| HSF facade | `OpenInnerAppPermissionFacade.apply` |
|
|
| Request shape | One object with system context, app locator, and `scopeValues`. |
|
|
| Required public fields | `unifiedAppId` and non-empty `scopeValues`. |
|
|
| Write guard | CLI `--dry-run`, then `--yes`; no MCP confirm field. |
|
|
|
|
Input fields:
|
|
|
|
| Field | Type | CLI flag | Required | Notes |
|
|
| --- | --- | --- | --- | --- |
|
|
| `unifiedAppId` | string | `--unified-app-id` | yes | Unified app id. |
|
|
| `scopeValues` | string[] | `--permissions` | yes | Comma-separated in CLI; must come from list response. |
|
|
|
|
Response must distinguish direct apply, version-staged apply, rejected scopes, and
|
|
skipped scopes:
|
|
|
|
```json
|
|
{
|
|
"ok": true,
|
|
"operation": "permission.add",
|
|
"appliedScopeValues": ["Contact.User.mobile"],
|
|
"directAppliedScopeValues": [],
|
|
"versionStagedScopeValues": ["Contact.User.mobile"],
|
|
"rejectedScopeValues": [],
|
|
"skippedScopeValues": [],
|
|
"nextAction": "run devapp version check-approval",
|
|
"raw": {}
|
|
}
|
|
```
|
|
|
|
`requiredApproval=true` scopes must be accepted when editable and staged into the
|
|
current app version. Approver selection is handled by the version flow.
|
|
|
|
#### remove_dev_app_permissions
|
|
|
|
| Item | Contract |
|
|
| --- | --- |
|
|
| CLI | `dws devapp permission remove` |
|
|
| HSF facade | `OpenInnerAppPermissionFacade.remove` |
|
|
| Request shape | One object with system context, app locator, and `scopeValues`. |
|
|
| Required public fields | `unifiedAppId` and non-empty `scopeValues`. |
|
|
| Write guard | CLI `--dry-run`, then `--yes`; no MCP confirm field. |
|
|
|
|
Input fields:
|
|
|
|
| Field | Type | CLI flag | Required | Notes |
|
|
| --- | --- | --- | --- | --- |
|
|
| `unifiedAppId` | string | `--unified-app-id` | yes | Unified app id. |
|
|
| `scopeValues` | string[] | `--permissions` | yes | Remove one or more permissions. |
|
|
|
|
Expected structured failures:
|
|
|
|
| Condition | Normalized reason |
|
|
| --- | --- |
|
|
| Scope is not currently authorized | `PERMISSION_NOT_AUTHED` |
|
|
| Scope cannot be edited | `PERMISSION_NOT_EDITABLE` |
|
|
| Scope cannot be found | `PERMISSION_NOT_FOUND` |
|
|
|
|
## 4. Hardcoded Helper Mapping
|
|
|
|
The open-source branch hardcodes the following command root and tool mapping.
|
|
This JSON is kept as a compact mapping reference only; `devapp` runtime command
|
|
availability must not depend on MCP registry publication or service discovery.
|
|
|
|
```json
|
|
{
|
|
"com.dingtalk.dws/helper/cli": {
|
|
"id": "devapp",
|
|
"command": "devapp",
|
|
"description": "开放平台应用管理",
|
|
"aliases": ["app"],
|
|
"groups": {
|
|
"credentials": {"description": "应用凭证"},
|
|
"permission": {"description": "应用权限"},
|
|
"webapp": {"description": "网页应用"},
|
|
"event": {"description": "事件订阅"},
|
|
"version": {"description": "版本发布"}
|
|
}
|
|
}
|
|
}
|
|
```
|
|
|
|
Tool override examples:
|
|
|
|
```json
|
|
{
|
|
"toolOverrides": {
|
|
"list_dev_app": {
|
|
"cliName": "list",
|
|
"description": "查询开放平台企业内部应用列表",
|
|
"flags": {
|
|
"pageSize": {"alias": "page-size", "default": 20},
|
|
"cursor": {"alias": "cursor"},
|
|
"name": {"alias": "name"}
|
|
}
|
|
},
|
|
"get_dev_app": {
|
|
"cliName": "get",
|
|
"description": "查询开放平台企业内部应用详情",
|
|
"flags": {
|
|
"unifiedAppId": {"alias": "unified-app-id", "required": true}
|
|
}
|
|
},
|
|
"create_dev_app": {
|
|
"cliName": "create",
|
|
"description": "创建开放平台企业内部应用",
|
|
"isSensitive": true,
|
|
"flags": {
|
|
"name": {"alias": "name", "required": true},
|
|
"desc": {"alias": "desc"},
|
|
"icon": {"alias": "icon"}
|
|
}
|
|
},
|
|
"update_dev_app": {
|
|
"cliName": "update",
|
|
"description": "修改开放平台企业内部应用基础信息",
|
|
"isSensitive": true,
|
|
"flags": {
|
|
"unifiedAppId": {"alias": "unified-app-id", "required": true},
|
|
"name": {"alias": "name"},
|
|
"desc": {"alias": "desc"},
|
|
"icon": {"alias": "icon"}
|
|
}
|
|
},
|
|
"delete_dev_app": {
|
|
"cliName": "delete",
|
|
"description": "删除开放平台企业内部应用",
|
|
"isSensitive": true,
|
|
"flags": {
|
|
"unifiedAppId": {"alias": "unified-app-id", "required": true}
|
|
}
|
|
},
|
|
"list_dev_app_permissions": {
|
|
"cliName": "list",
|
|
"group": "permission",
|
|
"description": "查询开放平台应用权限列表,默认同时返回 APP 应用权限和 SNS 个人权限",
|
|
"flags": {
|
|
"unifiedAppId": {"alias": "unified-app-id"},
|
|
"keyword": {"alias": "keyword"},
|
|
"apiStatus": {"alias": "api-status"},
|
|
"authStatus": {"alias": "status", "default": "ALL"},
|
|
"scopeType": {"alias": "scope-type"},
|
|
"scopeValue": {"alias": "scope"},
|
|
"pageSize": {"alias": "page-size", "default": "20"},
|
|
"cursor": {"alias": "cursor"}
|
|
}
|
|
},
|
|
"apply_dev_app_permissions": {
|
|
"cliName": "add",
|
|
"group": "permission",
|
|
"description": "申请开放平台应用权限点;需审核权限写入当前应用版本变更",
|
|
"isSensitive": true,
|
|
"flags": {
|
|
"unifiedAppId": {"alias": "unified-app-id"},
|
|
"scopeValues": {
|
|
"alias": "permissions",
|
|
"type": "stringSlice",
|
|
"required": true,
|
|
"description": "待申请权限点 scopeValue,多个用逗号分隔"
|
|
}
|
|
}
|
|
},
|
|
"remove_dev_app_permissions": {
|
|
"cliName": "remove",
|
|
"group": "permission",
|
|
"description": "取消一个或多个已开通的开放平台应用权限点",
|
|
"isSensitive": true,
|
|
"flags": {
|
|
"unifiedAppId": {"alias": "unified-app-id"},
|
|
"scopeValues": {
|
|
"alias": "permissions",
|
|
"type": "stringSlice",
|
|
"required": true,
|
|
"description": "待取消权限点 scopeValue,多个用逗号分隔"
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
```
|
|
|
|
`permission search/detail` are UX aliases over `list_dev_app_permissions`. The helper exposes one canonical MCP call and keeps alias behavior at the CLI layer:
|
|
|
|
```bash
|
|
dws devapp permission list --keyword "机器人发送消息"
|
|
dws devapp permission list --scope qyapi_robot_sendmsg
|
|
```
|
|
|
|
If a helper command is added, map aliases without creating new MCP tools:
|
|
|
|
| Helper command | Underlying tool | Forced/derived args |
|
|
| --- | --- | --- |
|
|
| `devapp permission search` | `list_dev_app_permissions` | Requires `keyword` or `scopeValue`; returns candidate summaries. |
|
|
| `devapp permission detail` | `list_dev_app_permissions` | Requires `scopeValue`; returns one scope with full `apiList`. |
|
|
|
|
## 5. App Identity Resolution
|
|
|
|
Application identity must be resolved before detail and write commands.
|
|
|
|
| Priority | Identifier | Rule |
|
|
| --- | --- | --- |
|
|
| 1 | `unifiedAppId` | Preferred unique id for write operations. |
|
|
| 2 | `appKey/clientId` | Call list/detail and require one result. |
|
|
| 3 | `name` | Fuzzy search; write operations require one result. |
|
|
|
|
Agent rule:
|
|
|
|
1. Never choose the first row when multiple apps match.
|
|
2. Show candidates with `name`, `unifiedAppId`, `appKey/clientId`, `creator`, and modified time.
|
|
3. Ask the user to pick `unifiedAppId` for write operations when multiple apps match.
|
|
|
|
MCP calls must rely on system context for caller identity:
|
|
|
|
| System context | HSF request field | Notes |
|
|
| --- | --- | --- |
|
|
| Caller org | `corpId` | DingTalk corpId, not internal orgId. |
|
|
| Caller user | `userId` | DingTalk userId/staffId; backend resolves uid. |
|
|
|
|
Open-source DWS and Agent must not pass `orgId` or `uid` as user input.
|
|
|
|
## 6. App CRUD
|
|
|
|
### 6.1 List
|
|
|
|
```bash
|
|
dws devapp list --page-size 20 --format json
|
|
dws devapp list --name DemoApp --format json
|
|
dws devapp list --page-size 20 --cursor NEXT_CURSOR --format json
|
|
```
|
|
|
|
MCP payload:
|
|
|
|
```json
|
|
{
|
|
"pageSize": 20,
|
|
"name": "DemoApp"
|
|
}
|
|
```
|
|
|
|
Pagination is cursor based. A baseline call should pass only `pageSize`; use the response `nextCursor` for the next page.
|
|
|
|
### 6.2 Get
|
|
|
|
```bash
|
|
dws devapp get --unified-app-id UNIFIED_APP_ID --format json
|
|
```
|
|
|
|
MCP payload:
|
|
|
|
```json
|
|
{
|
|
"unifiedAppId": "UNIFIED_APP_ID"
|
|
}
|
|
```
|
|
|
|
`get` may show `clientId/appKey/clientSecret/appSecret`; callers must treat secret fields as sensitive. Active secret reads still prefer `credentials get`.
|
|
|
|
### 6.3 Create
|
|
|
|
```bash
|
|
dws devapp create --name DemoApp --desc "internal app" --type internal --dry-run --format json
|
|
dws devapp create --name DemoApp --desc "internal app" --type internal --yes --format json
|
|
```
|
|
|
|
Current P0 create supports enterprise internal application basics:
|
|
|
|
| Type | Supported now | Notes |
|
|
| --- | --- | --- |
|
|
| `internal` | Yes | Default enterprise internal app. |
|
|
| `h5` | Yes, as internal web capability flavor if backend accepts it | Use only if backend schema exposes it. |
|
|
| `robot` | Not as app-create P0 | Bot config is a separate capability. |
|
|
| `miniapp` | Not P0 | Needs separate capability/backend confirmation. |
|
|
| `isv/connector` | Not P0 | Not yulan app CRUD. |
|
|
|
|
MCP payload:
|
|
|
|
```json
|
|
{
|
|
"name": "DemoApp",
|
|
"desc": "internal app"
|
|
}
|
|
```
|
|
|
|
### 6.4 Update
|
|
|
|
```bash
|
|
dws devapp update --unified-app-id UNIFIED_APP_ID --name DemoApp2 --desc "new desc" --dry-run --format json
|
|
dws devapp update --unified-app-id UNIFIED_APP_ID --name DemoApp2 --desc "new desc" --yes --format json
|
|
```
|
|
|
|
At least one update field is required. Supported P0 fields:
|
|
|
|
| CLI flag | MCP field | Notes |
|
|
| --- | --- | --- |
|
|
| `--name` | `name` | New app name. |
|
|
| `--desc` | `desc` | New description. |
|
|
| `--icon` | `icon` | Existing media id or backend-supported resource. |
|
|
| `--international` | `international` | JSON string if exposed. |
|
|
| `--support-harmony` | `supportHarmony` | Boolean if exposed. |
|
|
|
|
### 6.5 Delete
|
|
|
|
```bash
|
|
dws devapp delete --unified-app-id UNIFIED_APP_ID --dry-run --format json
|
|
dws devapp delete --unified-app-id UNIFIED_APP_ID --yes --format json
|
|
```
|
|
|
|
Delete is destructive. The CLI/Agent must show the target app summary before calling the tool with `--yes`.
|
|
|
|
MCP payload:
|
|
|
|
```json
|
|
{
|
|
"unifiedAppId": "UNIFIED_APP_ID"
|
|
}
|
|
```
|
|
|
|
Valid public locator field is `unifiedAppId`.
|
|
|
|
## 7. Credentials
|
|
|
|
Target command:
|
|
|
|
```bash
|
|
dws devapp credentials get --unified-app-id UNIFIED_APP_ID --format json
|
|
```
|
|
|
|
Current status:
|
|
|
|
1. Backend facade is `OpenInnerAppQueryFacade.getCredentials`.
|
|
2. MCP tool is `get_dev_app_credentials`.
|
|
3. CLI sends only application locator fields and does not add `showSecret`, `confirmSecret`, or masking fields.
|
|
4. The MCP response follows the backend credential contract and may include `clientSecret/appSecret`; callers must treat command output as sensitive.
|
|
|
|
Target fields:
|
|
|
|
| Field | Notes |
|
|
| --- | --- |
|
|
| `unifiedAppId/name` | App summary. |
|
|
| `clientId/appKey` | Non-secret identifiers. |
|
|
| `clientSecret/appSecret` | Returned by the dedicated credentials tool; treat as sensitive output. |
|
|
| `currentSecretStatus` | Current secret state. |
|
|
| `pendingExpireTask` | Pending secret-expiration task information when present. |
|
|
|
|
## 8. Permissions
|
|
|
|
### 8.1 Web Semantics
|
|
|
|
Permission list must align with the developer console endpoint:
|
|
|
|
```text
|
|
GET /openapp/unifiedapp/{unifiedAppId}/scope/list?from=inner&key={keyword}&apiStatus={apiStatus}
|
|
```
|
|
|
|
Returned groups:
|
|
|
|
```json
|
|
{
|
|
"success": true,
|
|
"data": {
|
|
"snsScopes": [
|
|
{"title": "个人权限", "scopeType": "SNS", "scopeList": []}
|
|
],
|
|
"appScopes": [
|
|
{"title": "通讯录管理", "scopeType": "APP", "scopeList": []}
|
|
]
|
|
}
|
|
}
|
|
```
|
|
|
|
Do not split the main list into old `listAuthed/listAvailable` APIs. Use one list operation that returns both authorized and unauthorized scopes.
|
|
|
|
### 8.2 List, Search, Detail
|
|
|
|
```bash
|
|
dws devapp permission list --unified-app-id UNIFIED_APP_ID --format json
|
|
dws devapp permission list --unified-app-id UNIFIED_APP_ID --status unauthed --keyword "发送消息" --format json
|
|
dws devapp permission list --unified-app-id UNIFIED_APP_ID --scope-type SNS --format json
|
|
dws devapp permission list --unified-app-id UNIFIED_APP_ID --scope qyapi_robot_sendmsg --format json
|
|
```
|
|
|
|
MCP payload for normal list:
|
|
|
|
```json
|
|
{
|
|
"unifiedAppId": "UNIFIED_APP_ID",
|
|
"authStatus": "ALL",
|
|
"pageSize": 20
|
|
}
|
|
```
|
|
|
|
MCP payload for search:
|
|
|
|
```json
|
|
{
|
|
"unifiedAppId": "UNIFIED_APP_ID",
|
|
"keyword": "机器人发送消息",
|
|
"authStatus": "UNAUTHED",
|
|
"pageSize": 10
|
|
}
|
|
```
|
|
|
|
MCP payload for detail:
|
|
|
|
```json
|
|
{
|
|
"unifiedAppId": "UNIFIED_APP_ID",
|
|
"scopeValue": "qyapi_robot_sendmsg",
|
|
"pageSize": 1
|
|
}
|
|
```
|
|
|
|
Input fields:
|
|
|
|
| Field | Type | Required | Notes |
|
|
| --- | --- | --- | --- |
|
|
| `unifiedAppId` | string | yes | Unified app id. |
|
|
| `keyword` | string | no | Maps to Web `key`. |
|
|
| `apiStatus` | string | no | Maps to Web `apiStatus`. |
|
|
| `authStatus` | string | no | `ALL/AUTHED/UNAUTHED`, post-filter. |
|
|
| `scopeType` | string | no | `APP/SNS`; empty returns both. |
|
|
| `scopeValue` | string | no | Detail mode and exact post-filter. |
|
|
| `pageSize` | number | no | Default 20, hard cap 50. |
|
|
| `cursor` | string | no | Cursor from previous response; empty for first page. |
|
|
|
|
Permission list uses cursor pagination. Use `keyword` to reduce server-side results and `pageSize/cursor` to control Agent context.
|
|
|
|
Default response must flatten APP and SNS scopes into one stable `permissions[]` array:
|
|
|
|
```json
|
|
{
|
|
"mode": "list",
|
|
"permissions": [
|
|
{
|
|
"scopeValue": "qyapi_robot_sendmsg",
|
|
"scopeName": "企业内机器人发消息权限",
|
|
"scopeDesc": "企业内机器人发消息权限",
|
|
"scopeType": "APP",
|
|
"categoryTitle": "机器人",
|
|
"authed": true,
|
|
"canEdit": true,
|
|
"authedStatusDesc": "已开通",
|
|
"requiredApproval": false,
|
|
"allowedActions": ["view", "detail", "remove"],
|
|
"apiCount": 4,
|
|
"apiPreview": {
|
|
"name": "人与人会话中机器人发送普通消息"
|
|
},
|
|
"canApplyDirectly": false,
|
|
"canRemove": true
|
|
}
|
|
],
|
|
"summary": {
|
|
"totalMatched": 1,
|
|
"returnedCount": 1,
|
|
"authedCount": 1,
|
|
"unauthedCount": 0,
|
|
"requiredApprovalCount": 0
|
|
},
|
|
"truncated": false,
|
|
"nextAction": "none"
|
|
}
|
|
```
|
|
|
|
`requiredApproval=true` must be shown and must still be appliable:
|
|
|
|
```json
|
|
{
|
|
"scopeValue": "Contact.User.mobile",
|
|
"scopeName": "个人手机号信息",
|
|
"scopeType": "SNS",
|
|
"authed": false,
|
|
"canEdit": true,
|
|
"requiredApproval": true,
|
|
"allowedActions": ["view", "detail", "apply"],
|
|
"canApplyDirectly": true,
|
|
"displayMessage": "该权限可申请;申请后加入当前应用版本变更,发布时进入审核流程。"
|
|
}
|
|
```
|
|
|
|
Agent selection order:
|
|
|
|
1. Exact `scopeValue`.
|
|
2. Matched `apiList[].name`.
|
|
3. Scope name or description.
|
|
4. Category title and `scopeType`.
|
|
|
|
Agent must pass final `scopeValue` to add/remove. Do not pass API name, API uuid, or category title into write tools.
|
|
|
|
### 8.3 Add
|
|
|
|
```bash
|
|
dws devapp permission add --unified-app-id UNIFIED_APP_ID --permissions qyapi_robot_sendmsg --dry-run --format json
|
|
dws devapp permission add --unified-app-id UNIFIED_APP_ID --permissions qyapi_robot_sendmsg --yes --format json
|
|
```
|
|
|
|
MCP payload:
|
|
|
|
```json
|
|
{
|
|
"unifiedAppId": "UNIFIED_APP_ID",
|
|
"scopeValues": ["qyapi_robot_sendmsg"]
|
|
}
|
|
```
|
|
|
|
Rules:
|
|
|
|
1. Already authorized scopes are skipped or reported, not duplicated.
|
|
2. Missing scopes are rejected.
|
|
3. `canEdit=false` scopes are rejected.
|
|
4. `requiredApproval=true` scopes are accepted and staged into current app version changes.
|
|
5. Approver selection is not part of permission add; it belongs to version publish.
|
|
|
|
Expected response:
|
|
|
|
```json
|
|
{
|
|
"applied": true,
|
|
"appliedScopeValues": ["Contact.User.mobile"],
|
|
"directAppliedScopeValues": [],
|
|
"versionStagedScopeValues": ["Contact.User.mobile"],
|
|
"rejectedScopeValues": [],
|
|
"skippedScopeValues": [],
|
|
"message": "权限申请成功;权限变更已加入应用版本,请在版本发布时继续审核流程。"
|
|
}
|
|
```
|
|
|
|
### 8.4 Remove
|
|
|
|
```bash
|
|
dws devapp permission remove --unified-app-id UNIFIED_APP_ID --permissions qyapi_robot_sendmsg --dry-run --format json
|
|
dws devapp permission remove --unified-app-id UNIFIED_APP_ID --permissions qyapi_robot_sendmsg --yes --format json
|
|
```
|
|
|
|
MCP payload:
|
|
|
|
```json
|
|
{
|
|
"unifiedAppId": "UNIFIED_APP_ID",
|
|
"scopeValues": ["qyapi_robot_sendmsg"]
|
|
}
|
|
```
|
|
|
|
Rules:
|
|
|
|
1. Remove only one scope per call.
|
|
2. `authed=false` returns `NOT_AUTHED`.
|
|
3. `canEdit=false` returns a no-edit reason.
|
|
4. Removing an authorized permission is not the same as withdrawing a pending approval.
|
|
|
|
## 9. Event And Version Boundary
|
|
|
|
Event and version commands are needed for a full application lifecycle but should not be folded into permission add.
|
|
|
|
Implemented event tools (verified against the `op-app` MCP `tools/list`). The
|
|
real model is "list subscribable events + subscribe/unsubscribe events by
|
|
`eventCodes`", not a single callbackUrl/token/aesKey config call:
|
|
|
|
| CLI | MCP tool | Notes |
|
|
| --- | --- | --- |
|
|
| `dev app event list` | `list_dev_app_events` | Args `unifiedAppId/keyword/cursor/pageSize`; `keyword` searches `eventCode` or `eventName`; returns `pushType=STREAM` + `events[]` (`eventCode`/`eventName`/`subscribed`) + `hasMore/nextCursor/pageSize`. Read. |
|
|
| `dev app event subscribe` | `subscribe_dev_app_events` | Args `unifiedAppId` + required array `eventCodes`. Returns `success/operation/eventCodes/needsPublish/versionRequiredAction`; failures include `errorCode/errorMsg/reason/retryable/action` such as `STREAM_NOT_CONNECTED`. Write (`--dry-run`/`--yes`). |
|
|
| `dev app event unsubscribe` | `unsubscribe_dev_app_events` | Args `unifiedAppId` + required array `eventCodes`. Returns `success/operation/eventCodes/needsPublish/versionRequiredAction`; failures include `errorCode/errorMsg/reason/retryable/action`. Write. |
|
|
|
|
Note: for gray unified apps, subscribe/unsubscribe are staged into version
|
|
metadata and only take effect after `version publish`. The event callback URL
|
|
itself is not part of this model; it lives in robot config (`eventCallbackUrl`,
|
|
exposed as `robot config --event-url`).
|
|
|
|
Implemented version tools (verified against the `op-app` MCP `tools/list`):
|
|
|
|
| CLI | MCP tool | Notes |
|
|
| --- | --- | --- |
|
|
| `devapp version create` | `create_dev_app_version` | Save a new version from current config. Omit `version` by default so the server auto-increments from the latest released version; only pass `version` when the user explicitly requests a higher version. |
|
|
| `devapp version list` | `list_dev_app_versions` | Cursor-paged list (`cursor`, `pageSize`; response `items/nextCursor/hasMore`). |
|
|
| `devapp version get` | `get_dev_app_version_detail` | One version detail by `versionId`. |
|
|
| `devapp version check-approval` | `publish_dev_app_version` (`precheckOnly=true`) | Precheck approval requirement / approvers via `requiresApproval/publishable/approvalMode/approvalCandidates`; does not publish. |
|
|
| `devapp version publish` | `publish_dev_app_version` (`precheckOnly=false`) | Publish or submit approval; inspect `published/approvalSubmitted/processId/versionStatus`. |
|
|
| `devapp version status` | `get_dev_app_version_status` | Poll publish and approval state. |
|
|
|
|
Note: the MCP `precheckOnly` field is the server-side approval precheck and maps to
|
|
`version check-approval`. It is distinct from the CLI global `--dry-run` (preview
|
|
without execution). `check-approval` is read-only (no write guard); `publish`
|
|
uses the standard `--dry-run`/`--yes` write guard.
|
|
|
|
Implemented robot tools:
|
|
|
|
| CLI | MCP tool | Notes |
|
|
| --- | --- | --- |
|
|
| `devapp robot create` | `create_dingtalk_robot` | Synchronously create a new agent app + robot. |
|
|
| `devapp robot submit` | `submit_robot_create_task` | Async submit; supports retry via original `taskId`. |
|
|
| `devapp robot result` | `query_robot_create_result` | Poll async task by `taskId`. |
|
|
| `devapp robot get` | `get_extension_robot_config` | Read robot config of an existing app. |
|
|
| `devapp robot config` | `set_extension_robot_config` | Create or update robot config on an existing app. |
|
|
| `devapp robot enable` | `enable_dev_app_robot` | Enable / re-enable robot capability. |
|
|
| `devapp robot disable` | `disable_dev_app_robot` | Disable robot capability. |
|
|
|
|
Robot create/submit and config/enable/disable are write operations and
|
|
use the `--dry-run`/`--yes` guard; `get` and `result` are reads.
|
|
|
|
Stitching rule:
|
|
|
|
```text
|
|
permission add requiredApproval=true
|
|
-> versionStagedScopeValues returned
|
|
-> devapp version check-approval
|
|
-> choose approver if required
|
|
-> devapp version publish
|
|
-> devapp version status
|
|
```
|
|
|
|
Approval details may only be visible in the DingTalk app. CLI should return audit id, approval url if backend provides one, and a clear status.
|
|
|
|
## 10. Agent Routing
|
|
|
|
### 10.1 Application Intent Disambiguation
|
|
|
|
`应用` is an overloaded word in DingTalk. A yulan-style Agent must not route every
|
|
generic app request to `devapp`. Use `devapp` only when the request is about
|
|
DingTalk Open Platform / developer backend enterprise internal applications.
|
|
|
|
Positive `devapp` signals:
|
|
|
|
| Signal | Route |
|
|
| --- | --- |
|
|
| `开放平台应用`, `开发者后台应用`, `企业内部应用`, `内部应用` | `devapp` |
|
|
| `unifiedAppId`, `clientId`, `appKey`, `appSecret`, `clientSecret` | `devapp` |
|
|
| `应用权限`, `权限点`, `API 权限`, `APP/SNS 权限`, `scopeValue` | `devapp permission ...` |
|
|
| `事件订阅`, `订阅事件`, `退订事件`, `eventCode`, `可订阅事件` in an app context | `devapp event ...` |
|
|
| `应用版本`, `版本发布`, `选审批人`, `发布审核` in developer backend context | `devapp version ...` |
|
|
| yulan's current `开放平台应用 CLI 化 / dws app` workstream | `devapp` |
|
|
|
|
Non-`devapp` signals:
|
|
|
|
| Request shape | Correct route |
|
|
| --- | --- |
|
|
| `开放平台接口文档`, `API 怎么调用`, `错误码`, `字段说明` | `devdoc` |
|
|
| `钉钉文档`, `云文档`, `知识库里的文档`, document read/write/export | `doc` or `wiki` preflight |
|
|
| `工作台应用`, `应用 app001`, `钉钉工作台上有哪些应用` | `workbench app ...` |
|
|
| `OA 审批单`, `同意/拒绝/撤销审批`, approval task detail | `oa` |
|
|
| `MCP 服务`, `connector`, `创建工具`, `HSF 映射`, `上架 MCP` | OpenDev MCP platform workflow, not `dws devapp` |
|
|
| `AI 应用`, `智能体`, `机器人对话能力` without developer backend identifiers | Ask or route to the relevant product; do not default to `devapp` |
|
|
|
|
If the user says only `应用` and gives no developer-backend identifiers, inspect
|
|
the surrounding context. If there is still no context, ask one short clarifying
|
|
question: whether they mean an Open Platform enterprise internal app, a workbench
|
|
app, a document/app capability, or an MCP connector/tool.
|
|
|
|
When the user is already in this yulan workstream, prefer `devapp` for ambiguous
|
|
phrases such as `应用查询`, `创建应用`, `应用权限`, or `应用版本` because the active
|
|
domain is OpenDev enterprise internal applications. This preference is not a
|
|
global DWS rule; it is scoped to this developer-platform application CLI design.
|
|
|
|
Agent should use this state machine:
|
|
|
|
```text
|
|
user intent
|
|
-> normalize command
|
|
-> collect slots
|
|
-> resolve unique app
|
|
-> for permission intents, resolve scopeValue
|
|
-> dry-run plan for writes
|
|
-> require user confirmation or --yes
|
|
-> call MCP tool
|
|
-> normalize ServiceResult
|
|
-> report next action
|
|
```
|
|
|
|
Intent table:
|
|
|
|
| User intent | Command | Tool |
|
|
| --- | --- | --- |
|
|
| "查应用/搜索应用" | `devapp list` | `list_dev_app` |
|
|
| "看应用详情/clientId/appKey" | `devapp get` | `get_dev_app` |
|
|
| "创建应用" | `devapp create` | `create_dev_app` |
|
|
| "修改名称/描述/图标" | `devapp update` | `update_dev_app` |
|
|
| "删除应用" | `devapp delete` | `delete_dev_app` |
|
|
| "查权限/搜索 API 权限" | `permission list` | `list_dev_app_permissions` |
|
|
| "这个权限覆盖哪些 API" | `permission list --scope` | `list_dev_app_permissions` |
|
|
| "申请权限/开通权限" | `permission add` | `apply_dev_app_permissions` |
|
|
| "取消权限/移除权限" | `permission remove` | `remove_dev_app_permissions` |
|
|
| "拿 appSecret/clientSecret" | `credentials get` | `get_dev_app_credentials` |
|
|
| "查可订阅事件/订阅/退订事件" | `event list/subscribe/unsubscribe` | `list_open_dev_app_events`, `subscribe/unsubscribe_open_dev_app_event` |
|
|
| "发布/审核/选审批人" | `version ...` | `publish_open_dev_app_version` etc. |
|
|
|
|
Yulan invocation examples:
|
|
|
|
| User wording | Expected first command | Reason |
|
|
| --- | --- | --- |
|
|
| "查一下开发者后台应用 DemoApp" | `dws devapp list --name DemoApp --format json` | OpenDev context plus app name means listByCondition search. |
|
|
| "把应用图标换成 ICON_RESOURCE" | `dws devapp update --unified-app-id ... --icon ICON_RESOURCE --dry-run --format json` | Icon change is app base info update and must be guarded. |
|
|
| "已确认删除这个开放平台应用" | `dws devapp delete --unified-app-id ... --yes --format json` | Destructive write only after unique app resolution and confirmation. |
|
|
| "手机号权限要申请,虽然需要审核" | `dws devapp permission add --permissions Contact.User.mobile --dry-run/--yes` | `requiredApproval=true` is still appliable; approval moves to version publish. |
|
|
| "订阅通讯录用户增加事件" | `dws dev app event list ...` then `dws dev app event subscribe --event-codes user_add_org --dry-run --format json` | Pick eventCode from list, then subscribe; gray apps need a version publish to take effect. |
|
|
| "保存版本并看审核状态" | `version create`, `check-approval`, `publish`, `status` | Version flow owns approver selection and publish audit state. |
|
|
| "创建 MCP 服务并配置 HSF tool" | OpenDev MCP platform workflow, not `dws devapp create` | MCP connector/tool is a platform artifact, not an enterprise internal app. |
|
|
|
|
ServiceResult handling:
|
|
|
|
| Result | Agent behavior |
|
|
| --- | --- |
|
|
| `success=true` with `result` | Show structured result and next action. |
|
|
| `success=false` | Preserve `errorCode/errorMsg`; do not rewrite as a generic failure. |
|
|
| `versionStagedScopeValues` non-empty | Tell user to continue version approval/publish. |
|
|
| multiple app candidates | Stop and ask for a unique id. |
|
|
| permission list `truncated=true` | Ask for a narrower keyword or status filter. |
|
|
|
|
## 11. Agent Invocation Runbook
|
|
|
|
This section is the operational rulebook for yulan-style Agents. It is meant to
|
|
avoid the common failure mode where an Agent knows the right business concept but
|
|
calls the wrong DWS product, stale MCP tool, or over-broad permission list.
|
|
|
|
### 11.1 Runtime Preflight
|
|
|
|
Before running a devapp command in a new environment, the Agent should verify the
|
|
hardcoded helper surface. Endpoint configuration is an internal edition concern;
|
|
the Agent should not require users to paste or export MCP gateway keys.
|
|
|
|
```bash
|
|
dws devapp --help
|
|
dws app --help
|
|
```
|
|
|
|
If the command or endpoint is missing:
|
|
|
|
1. If `dws devapp --help` fails, report `DEVAPP_COMMAND_UNAVAILABLE`.
|
|
2. If execution reports `endpoint_not_resolved`, report
|
|
`DEVAPP_ENDPOINT_NOT_CONFIGURED` and ask the owner to check the internal
|
|
edition `SupplementServers/StaticServers` injection. Local developers may
|
|
override with `DINGTALK_DEVAPP_MCP_URL`, but Agents should not request it
|
|
from end users.
|
|
3. Do not reroute the task to `devdoc`, `doc`, or generic `mcp` commands unless
|
|
the user is asking for documentation rather than application management.
|
|
|
|
If a command flag in this document conflicts with `--help`, use `--help`. The
|
|
document defines the target contract; the checked-in helper code defines the
|
|
current callable command surface.
|
|
|
|
### 11.2 Command Construction
|
|
|
|
Use this order when constructing a call:
|
|
|
|
1. Identify the product as `devapp` only when the request matches section 10.1
|
|
positive signals or the active yulan OpenDev app workstream. Do not use
|
|
`devapp` for every bare `应用`.
|
|
2. Resolve application identity before any detail or write operation.
|
|
3. Prefer checked-in CLI flags over raw `--json`.
|
|
4. Use raw `--json` only when a required MCP field has no CLI flag yet.
|
|
5. Always add `--format json` for Agent execution.
|
|
6. For writes, call `--dry-run --format json` first, then call the same command
|
|
with `--yes --format json` only after user confirmation or an explicit
|
|
higher-level instruction.
|
|
|
|
Do not add confirmation booleans to MCP payloads. These belong to the CLI layer:
|
|
|
|
| Bad MCP field | Correct DWS guard |
|
|
| --- | --- |
|
|
| `confirmCreate=true` | `dws devapp create ... --yes` |
|
|
| `confirmUpdate=true` | `dws devapp update ... --yes` |
|
|
| `confirmDelete=true` | `dws devapp delete ... --yes` |
|
|
| `confirmPermission=true` | `dws devapp permission add/remove ... --yes` |
|
|
| `confirmSecret=true` | Do not send; `credentials get` has no confirm/show-secret payload field. |
|
|
|
|
### 11.3 Business Recipes
|
|
|
|
| User request | Agent recipe |
|
|
| --- | --- |
|
|
| "查 DemoApp 应用" | `devapp list --name DemoApp`; if one result, optionally `devapp get`; if multiple, show candidates. |
|
|
| "这个 appKey 是哪个应用" | 当前 CLI 不用 appKey 定位详情;先让用户提供 `unifiedAppId`,或用 `devapp list --name` 缩小候选后再 `devapp get --unified-app-id ...`。 |
|
|
| "创建企业内部应用" | `devapp create --name ... --type internal --dry-run`; after confirmation, repeat with `--yes`. |
|
|
| "修改应用描述/图标" | Resolve `unifiedAppId`; `devapp update --unified-app-id ... --desc/icon ... --dry-run`; after confirmation, `--yes`. |
|
|
| "删除应用" | Resolve unique app; show app summary; `devapp delete ... --dry-run`; after confirmation, `--yes`. |
|
|
| "查询机器人发消息权限" | `devapp permission list --keyword "机器人发消息"`; if candidates are broad, require `scopeValue`. |
|
|
| "这个 API 需要哪个权限" | Search by API name; inspect `apiPreview`; if uncertain, use `permission list --scope SCOPE` for detail. |
|
|
| "申请通讯录手机号权限" | Resolve app; `permission list --keyword "手机号"`; choose `scopeValue`; `permission add --permissions SCOPE --dry-run`; then `--yes`. |
|
|
| "权限已进版本,继续发布" | `version check-approval`; choose approver if needed; `version publish --yes`; poll `version status`. |
|
|
| "拿 clientSecret/appSecret" | Use only `credentials get`; never fall back to `devapp get`. |
|
|
|
|
### 11.4 Permission Candidate Selection
|
|
|
|
Permission list is intentionally one grouped list, not separate
|
|
`listAuthed/listAvailable` calls. The Agent must reduce the result set before
|
|
writing:
|
|
|
|
1. If the user gives a `scopeValue`, use exact match.
|
|
2. If the user gives an API name, search with `keyword`, then match
|
|
`apiList[].name` or `apiPreview.name`.
|
|
3. If the user gives a human permission name, match `scopeName/scopeDesc`.
|
|
4. If multiple scopes remain, show `scopeValue`, `scopeName`, `scopeType`,
|
|
`authed`, `requiredApproval`, and `apiPreview`, then ask for one.
|
|
5. For list output, cap the displayed list with `pageSize`; for detail output, call
|
|
with `scopeValue` and return the full API coverage for one scope only.
|
|
|
|
`requiredApproval=true` is not a blocker for permission add. It means the add
|
|
operation stages the permission into the current application version and the
|
|
approval decision happens during version publish.
|
|
|
|
### 11.5 Normalized Errors
|
|
|
|
DWS may return CLI-layer errors (`category/reason/hint/actions`) or MCP/HSF
|
|
business errors (`ServiceResult.success=false`, `errorCode`, `errorMsg`). The
|
|
Agent should normalize behavior without hiding raw backend fields.
|
|
|
|
| Normalized reason | Typical source | Agent behavior |
|
|
| --- | --- | --- |
|
|
| `DEVAPP_COMMAND_UNAVAILABLE` | `unknown command` for `dws devapp` | Report that the local CLI build does not contain the devapp helper. Do not try cache refresh as the primary fix. |
|
|
| `DEVAPP_ENDPOINT_NOT_CONFIGURED` | `endpoint_not_resolved`, missing internal edition endpoint | Ask the owner to check `SupplementServers/StaticServers`; local debug may use `DINGTALK_DEVAPP_MCP_URL`, but do not ask users for gateway keys. |
|
|
| `DEVAPP_TOOL_NOT_PUBLISHED` | Product exists but one tool is missing | Report the missing MCP tool key and avoid substituting a different operation. |
|
|
| `AUTH_CONTEXT_MISSING` | Backend cannot resolve `corpId/userId` | Tell the user to re-login or check organization context; do not ask for internal `orgId/uid`. |
|
|
| `APP_NOT_FOUND` | Empty list/detail result | Ask for another app identifier; do not create or update implicitly. |
|
|
| `APP_AMBIGUOUS` | Multiple apps match name/key | Show candidate ids and stop before write. |
|
|
| `PERMISSION_NOT_FOUND` | Scope search has no exact candidate | Ask for another keyword or API name. |
|
|
| `PERMISSION_ALREADY_AUTHED` | Add receives an already authorized scope | Report skipped scope and continue only for remaining unauth scopes. |
|
|
| `PERMISSION_NOT_AUTHED` | Remove receives an unauthorized scope | Report that there is nothing to remove. |
|
|
| `PERMISSION_NOT_EDITABLE` | `canEdit=false` or backend no-edit error | Show the no-edit reason; do not retry as a write. |
|
|
| `requiresApproval=true` | `check-approval` says approval is required | Use `approvalMode/approvalCandidates`; ask for approver only in `SELECT_APPROVER` mode. |
|
|
Raw `errorCode/errorMsg` must remain in JSON output or the Agent response so the
|
|
backend owner can diagnose actual HSF failures.
|
|
|
|
### 11.6 Output Shape For Agent Consumption
|
|
|
|
For every command, prefer a stable shape that has a small summary and raw backend
|
|
details:
|
|
|
|
```json
|
|
{
|
|
"ok": true,
|
|
"operation": "permission.add",
|
|
"app": {
|
|
"unifiedAppId": "UNIFIED_APP_ID",
|
|
"name": "DemoApp"
|
|
},
|
|
"summary": {
|
|
"appliedCount": 1,
|
|
"skippedCount": 0,
|
|
"rejectedCount": 0,
|
|
"requiresVersionPublish": true
|
|
},
|
|
"result": {},
|
|
"raw": {
|
|
"success": true,
|
|
"errorCode": "",
|
|
"errorMsg": ""
|
|
},
|
|
"nextAction": "run devapp version check-approval"
|
|
}
|
|
```
|
|
|
|
For failures:
|
|
|
|
```json
|
|
{
|
|
"ok": false,
|
|
"operation": "app.get",
|
|
"reason": "APP_AMBIGUOUS",
|
|
"message": "Multiple applications match the provided name.",
|
|
"candidates": [],
|
|
"raw": {
|
|
"success": false,
|
|
"errorCode": "BACKEND_ERROR_CODE",
|
|
"errorMsg": "backend message"
|
|
}
|
|
}
|
|
```
|
|
|
|
The `raw` object is allowed to include backend fields, but it must not include
|
|
tokens, cookies, MCP gateway keys, full app secrets, or unrelated page request
|
|
headers.
|
|
|
|
## 12. Open-Source DWS Implementation Notes
|
|
|
|
The open-source branch uses a hardcoded helper path for `devapp`; do not depend
|
|
on service discovery for command visibility.
|
|
|
|
1. Add `internal/helpers/devapp.go`, following `internal/helpers/devdoc.go`.
|
|
2. Register the root with `RegisterPublic`.
|
|
3. Route helper invocations with `executor.NewHelperInvocation`.
|
|
4. Keep command aliases such as `app` and `permission search/detail` in the
|
|
helper layer, not as separate MCP tools.
|
|
5. Add tests for dry-run payloads, `--yes` guard, app resolution, and permission routing.
|
|
|
|
Internal edition endpoint injection:
|
|
|
|
```go
|
|
SupplementServers: func() []edition.ServerInfo {
|
|
return []edition.ServerInfo{
|
|
{
|
|
ID: "devapp",
|
|
Name: "钉钉开放平台应用管理",
|
|
Endpoint: loadDevappMCPEndpointFromInternalConfig(),
|
|
Prefixes: []string{"devapp", "app"},
|
|
},
|
|
}
|
|
}
|
|
```
|
|
|
|
The endpoint loader must read from internal config or secret storage. Do not put
|
|
the Streamable HTTP URL or `?key=` value in this repository. `StaticServers` is
|
|
also valid for an internal build that wants to skip Market discovery entirely.
|
|
|
|
Recommended helper root:
|
|
|
|
```text
|
|
Name(): "devapp"
|
|
Aliases: ["app"] if supported by cobra and no conflict
|
|
```
|
|
|
|
Do not manually edit generated command indexes unless the generator is part of the change. The runtime command surface should be checked with:
|
|
|
|
```bash
|
|
dws devapp --help
|
|
dws app --help
|
|
```
|
|
|
|
## 13. Rollout And Verification
|
|
|
|
Use this checklist when moving from backend HSF implementation to usable DWS
|
|
commands. The goal is to prove that the same operation works at the MCP layer,
|
|
the DWS helper layer, and the Agent recipe layer.
|
|
|
|
### 13.1 MCP Tool Configuration Checklist
|
|
|
|
For each P0 tool:
|
|
|
|
1. Configure on the pre-release MCP developer platform before online release.
|
|
2. Use the deterministic tool key from section 3.2.
|
|
3. Select HSF and fill interface, version, and method exactly.
|
|
4. Generate API input/output from the HSF method.
|
|
5. Keep a single business request object. Remove or avoid separate
|
|
`BaseOpenRequestVO` public mapping.
|
|
6. Configure tool input JSON Schema with only Agent-facing fields.
|
|
7. Map `corpId/userId` from system context to the business request object.
|
|
8. Review every recommended mapping manually, especially fields named
|
|
`unifiedAppId`, `appKey`, `name`, or `userId`.
|
|
9. Keep `ServiceResult` output wrapper and success rule
|
|
`ServiceResult.success == true`.
|
|
10. Remove blank error-text optimization rows; preserve raw
|
|
`errorCode/errorMsg`.
|
|
11. Save draft and verify the page shows a new draft version.
|
|
12. Debug with a baseline call, then one filter/write scenario.
|
|
13. Publish only after debug passes.
|
|
|
|
P0 debug inputs:
|
|
|
|
| Tool | Baseline debug input |
|
|
| --- | --- |
|
|
| `list_dev_app` | `{"pageSize":10}` |
|
|
| `get_dev_app` | `{"unifiedAppId":"UNIFIED_APP_ID"}` |
|
|
| `create_dev_app` | `{"name":"DemoApp","desc":"internal app"}` |
|
|
| `update_dev_app` | `{"unifiedAppId":"UNIFIED_APP_ID","desc":"new desc"}` |
|
|
| `delete_dev_app` | `{"unifiedAppId":"UNIFIED_APP_ID"}` |
|
|
| `list_dev_app_permissions` | `{"unifiedAppId":"UNIFIED_APP_ID","authStatus":"ALL","pageSize":20}` |
|
|
| `apply_dev_app_permissions` | `{"unifiedAppId":"UNIFIED_APP_ID","scopeValues":["SCOPE_VALUE"]}` |
|
|
| `remove_dev_app_permissions` | `{"unifiedAppId":"UNIFIED_APP_ID","scopeValues":["SCOPE_VALUE"]}` |
|
|
|
|
For destructive debug cases, use a disposable application and permission scope.
|
|
Do not use production data in long-lived examples.
|
|
|
|
### 13.2 MCP Direct Smoke
|
|
|
|
After publish, verify the service through MCP before using DWS:
|
|
|
|
1. Run MCP `initialize`.
|
|
2. Run `tools/list` and confirm all expected P0 tool keys exist.
|
|
3. Call `list_dev_app` with only pagination.
|
|
4. Call one exact locator scenario, such as `get_dev_app`.
|
|
5. Call permission list with `authStatus=ALL` and confirm APP and SNS scopes are
|
|
both represented.
|
|
6. Confirm failures preserve `errorCode/errorMsg`.
|
|
|
|
Do not store the Streamable HTTP URL or `?key=` value in repository files,
|
|
README, skills, or issue text.
|
|
|
|
### 13.3 DWS Helper Smoke
|
|
|
|
After the MCP service is published and the internal edition injects the endpoint
|
|
through `SupplementServers` or `StaticServers`, verify the hardcoded helper path:
|
|
|
|
```bash
|
|
dws devapp --help
|
|
dws app --help
|
|
dws devapp list --page 1 --page-size 10 --format json
|
|
```
|
|
|
|
Expected:
|
|
|
|
1. Product id is `devapp`.
|
|
2. Command alias `app` exists, but `devapp` is canonical.
|
|
3. P0 command leaves appear in help.
|
|
4. `--format json` is available through global output flags.
|
|
5. Sensitive writes are marked or guarded so Agent uses `--dry-run` then `--yes`.
|
|
|
|
If `devapp` is missing, treat it as `DEVAPP_COMMAND_UNAVAILABLE`. If command
|
|
execution reports `endpoint_not_resolved`, check the internal edition endpoint
|
|
injection. Local debug may use `DINGTALK_DEVAPP_MCP_URL` as a temporary override.
|
|
If the endpoint is configured but a backend tool is unavailable, treat it as
|
|
`DEVAPP_TOOL_NOT_PUBLISHED`.
|
|
|
|
### 13.4 Agent Smoke
|
|
|
|
Use these smoke cases to prove the yulan invocation problem is solved for an
|
|
Agent. They intentionally cover routing, narrowing, write guard, and backend
|
|
error propagation.
|
|
|
|
| Scenario | Smoke command | Pass criteria |
|
|
| --- | --- | --- |
|
|
| Discover product | `dws devapp --help` | Helper command exists and exposes devapp CLI metadata. |
|
|
| List baseline | `dws devapp list --page 1 --page-size 10 --format json` | JSON contains page summary and app array. |
|
|
| Search by name | `dws devapp list --name DemoApp --format json` | One result opens path to get; multiple results return candidates. |
|
|
| Detail by id | `dws devapp get --unified-app-id UNIFIED_APP_ID --format json` | Response includes ids but no full secret. |
|
|
| Create guard | `dws devapp create --name DemoApp --type internal --format json` | Refuses without `--yes` or shows dry-run requirement. |
|
|
| Create dry-run | `dws devapp create --name DemoApp --type internal --dry-run --format json` | Prints payload and does not call HSF. |
|
|
| Permission search | `dws devapp permission list --unified-app-id UNIFIED_APP_ID --keyword "发送消息" --format json` | Returns candidate scopes and no write happens. |
|
|
| Permission detail | `dws devapp permission list --unified-app-id UNIFIED_APP_ID --scope SCOPE_VALUE --format json` | Returns full API coverage for one scope. |
|
|
| Permission approval | `dws devapp permission add --unified-app-id UNIFIED_APP_ID --permissions SCOPE_VALUE --dry-run --format json` | Shows direct/staged/rejected plan; `requiredApproval=true` is allowed. |
|
|
| Backend failure | Invalid app id or scope | JSON preserves `errorCode/errorMsg` and normalized reason. |
|
|
|
|
### 13.5 Release Exit Criteria
|
|
|
|
The application-side command set is ready for yulan Agent usage only when all of
|
|
the following are true:
|
|
|
|
1. P0 MCP tools are published and visible through `tools/list`.
|
|
2. `dws devapp --help` shows P0 helper leaves and `dws app --help` resolves as
|
|
the compatibility alias.
|
|
3. App list/detail/create/update/delete smoke cases pass.
|
|
4. Permission list/add/remove smoke cases pass for APP and SNS scopes.
|
|
5. `requiredApproval=true` permission add stages into version changes instead of
|
|
being rejected by the Agent.
|
|
6. Credential access is handled only by `credentials get`; event subscription
|
|
(`event list/subscribe/unsubscribe`) and version flow are implemented over
|
|
`op-app`, and any still-missing tool is reported as unsupported, not silently
|
|
emulated by other commands.
|
|
7. No repository doc, skill, log, or fixture contains MCP gateway keys, cookies,
|
|
access tokens, full app secrets, or production app data.
|
|
8. Existing skill setup and generator tests pass.
|
|
|
|
## 14. Acceptance Cases
|
|
|
|
| Case | Expected behavior |
|
|
| --- | --- |
|
|
| `dws devapp list --name DemoApp --format json` | Calls listByCondition and returns pagination summary. |
|
|
| `dws devapp get --name DemoApp --format json` with multiple matches | Stops and returns candidates. |
|
|
| `dws devapp create --name T --type internal --dry-run --format json` | Prints payload only; no MCP call. |
|
|
| `dws devapp create --name T --type internal --format json` | Refuses because write confirmation is missing. |
|
|
| `dws devapp create --name T --type internal --yes --format json` | Calls `create_dev_app`. |
|
|
| `dws devapp update --unified-app-id X --desc Y --yes --format json` | Calls `update_dev_app`. |
|
|
| `dws devapp delete --unified-app-id X --format json` | Refuses because delete confirmation is missing. |
|
|
| `dws devapp permission list --unified-app-id X --format json` | Returns flattened APP and SNS permissions. |
|
|
| `dws devapp permission list --unified-app-id X --keyword "发送消息" --format json` | Returns candidate `permissions[]`; no automatic apply. |
|
|
| `dws devapp permission add --unified-app-id X --permissions Contact.User.mobile --yes --format json` | Applies or stages permission; `requiredApproval=true` is allowed. |
|
|
| `dws devapp permission remove --unified-app-id X --permissions qyapi_robot_sendmsg --yes --format json` | Removes authorized scopes or returns a structured reason. |
|
|
| `dws devapp credentials get --unified-app-id X --format json` | Calls `get_dev_app_credentials`; does not use `devapp get` as a secret fallback. |
|
|
| `查应用 appXYZ 的详情` without OpenDev context | Does not blindly route to `devapp`; uses context such as `workbench app get` or asks. |
|
|
| `查询开放平台 API 错误码` | Routes to `devdoc`, not `devapp`. |
|
|
| `创建 MCP 服务并配置 HSF tool` | Uses OpenDev MCP platform workflow, not `dws devapp create`. |
|