From 87a9b5b9be7885455f2b305d3e36186863908758 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=BF=AE=E9=9B=A8?= <47820304+PeterGuy326@users.noreply.github.com> Date: Thu, 25 Jun 2026 21:49:10 +0800 Subject: [PATCH] chore: drop dead fork dev-app cruft (feat/dws-devapp) (#507) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Two leftovers referenced the decommissioned wxianfeng fork branch `feat/dws-devapp`, both now obsolete after the dev-app work landed on main (v1.0.42) and the installers were repointed to DingTalk-Real-AI (#505): - .github/workflows/auto-dev-release.yml — triggered only on push to feat/dws-devapp (a branch that does not exist on this repo, so it never fires). Its purpose — auto-publishing fork dev-preview releases for install-devapp.sh — is gone now that install-devapp.sh pulls stable releases from DingTalk-Real-AI. - docs/devapp-yulan-command-routing.md — a 2026-06-05 draft design doc pinned to the fork branch and the pre-rename `devapp` command tree, superseded by the shipped `dws dev` command set and the rewritten docs/devapp-agent-install-guide.md. After this, the repo has zero `wxianfeng` / `feat/dws-devapp` references. --- .github/workflows/auto-dev-release.yml | 103 -- docs/devapp-yulan-command-routing.md | 1452 ------------------------ 2 files changed, 1555 deletions(-) delete mode 100644 .github/workflows/auto-dev-release.yml delete mode 100644 docs/devapp-yulan-command-routing.md diff --git a/.github/workflows/auto-dev-release.yml b/.github/workflows/auto-dev-release.yml deleted file mode 100644 index 9cc2426f..00000000 --- a/.github/workflows/auto-dev-release.yml +++ /dev/null @@ -1,103 +0,0 @@ -name: Auto Dev Release - -# 推到 dev 分支 → 跑测试 → 通过才自动出一个 dev 预览 release。 -# install-devapp.sh / .ps1 取 fork 最新 release,所以发完安装链接即最新。 -# -# 设计要点: -# - 自包含:测试 + 算版本 + 打 tag + 发 release 全在一个 job,不依赖 tag 触发 -# release.yml(GITHUB_TOKEN 推的 tag 本来也不会触发别的 workflow)。 -# - 质量门:测试这一步失败,后面发布步骤就不会跑。 -# - 不含 npm publish,所以自动发布天然是绿的。 -on: - push: - branches: - - feat/dws-devapp - -# 串行:两次 push 不会同时算版本号撞车 -concurrency: - group: auto-dev-release - cancel-in-progress: false - -permissions: - contents: write - -jobs: - test-and-release: - runs-on: ubuntu-latest - timeout-minutes: 30 - steps: - - name: Check out repository - uses: actions/checkout@v4 - with: - fetch-depth: 0 - fetch-tags: true - - - name: Set up Go - uses: actions/setup-go@v5 - with: - go-version-file: go.mod - - - name: Install archive tooling - run: sudo apt-get update && sudo apt-get install -y zip unzip - - # ---- 质量门:测试不过就不发 ---- - - name: Build - run: make build - - - name: Test - run: go test -race -count=1 -timeout=5m ./cmd/... ./internal/... - - # ---- 算下一个 dev 版本号并打 tag ---- - - name: Compute next dev version - id: ver - run: | - set -eu - LATEST=$(git tag -l 'v*-dev.*' --sort=-v:refname | head -1 || true) - if [ -z "$LATEST" ]; then - BASE="v1.0.39"; N=0 - else - BASE=$(printf '%s' "$LATEST" | sed -E 's/-dev\.[0-9]+$//') - N=$(printf '%s' "$LATEST" | sed -E 's/.*-dev\.([0-9]+)$/\1/') - fi - NEW="${BASE}-dev.$((N + 1))" - echo "version=$NEW" >> "$GITHUB_OUTPUT" - echo "next dev release: $NEW (prev: ${LATEST:-none})" - - - name: Create and push tag - run: | - set -eu - V="${{ steps.ver.outputs.version }}" - git config user.name "github-actions[bot]" - git config user.email "github-actions[bot]@users.noreply.github.com" - git tag "$V" - git push origin "$V" - - # ---- 自己发 release(不靠 tag 触发 release.yml)---- - - name: Install rcodesign (ad-hoc sign darwin binaries from Linux) - run: | - set -eu - RCS_VERSION="0.27.0" - curl -fsSL -o /tmp/rcodesign.tar.gz \ - "https://github.com/indygreg/apple-platform-rs/releases/download/apple-codesign%2F${RCS_VERSION}/apple-codesign-${RCS_VERSION}-x86_64-unknown-linux-musl.tar.gz" - mkdir -p /tmp/rcodesign - tar -xzf /tmp/rcodesign.tar.gz -C /tmp/rcodesign --strip-components=1 - sudo install -m 0755 /tmp/rcodesign/rcodesign /usr/local/bin/rcodesign - rcodesign --version - - - name: Run GoReleaser - uses: goreleaser/goreleaser-action@v6 - with: - version: "~> v2" - args: release --clean - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - - - name: Post-release packaging - run: ./scripts/release/post-goreleaser.sh - env: - DWS_PACKAGE_VERSION: ${{ steps.ver.outputs.version }} - - - name: Upload dws-skills.zip to release - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - run: gh release upload "${{ steps.ver.outputs.version }}" dist/dws-skills.zip --clobber diff --git a/docs/devapp-yulan-command-routing.md b/docs/devapp-yulan-command-routing.md deleted file mode 100644 index 583f7739..00000000 --- a/docs/devapp-yulan-command-routing.md +++ /dev/null @@ -1,1452 +0,0 @@ -# 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`. |