fix cr problem
This commit is contained in:
@@ -138,7 +138,7 @@ Do not send confirmation fields such as `confirmCreate`, `confirmUpdate`, `confi
|
||||
| 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_open_dev_app_events`, `subscribe/unsubscribe_open_dev_app_event` | 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 |
|
||||
@@ -953,9 +953,9 @@ real model is "list subscribable events + subscribe/unsubscribe events by
|
||||
|
||||
| CLI | MCP tool | Notes |
|
||||
| --- | --- | --- |
|
||||
| `devapp event list` | `list_dev_app_events` | Args `unifiedAppId`; returns `pushType` + `events[]` (`eventCode`/`eventName`/`subscribed`). Read. |
|
||||
| `devapp event subscribe` | `subscribe_dev_app_events` | Args `unifiedAppId` + `eventCodes`. Write (`--dry-run`/`--yes`). |
|
||||
| `devapp event unsubscribe` | `unsubscribe_dev_app_events` | Args `unifiedAppId` + `eventCodes`. Write. |
|
||||
| `dev app event list` | `list_dev_app_events` | Args `unifiedAppId/cursor/pageSize`; 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
|
||||
@@ -969,8 +969,8 @@ Implemented version tools (verified against the `op-app` MCP `tools/list`):
|
||||
| `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; does not publish. |
|
||||
| `devapp version publish` | `publish_dev_app_version` (`precheckOnly=false`) | Publish; `confirmedSensitive` for high-sensitivity scopes, optional `approverUserId`. |
|
||||
| `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
|
||||
@@ -1086,7 +1086,7 @@ Yulan invocation examples:
|
||||
| "把应用图标换成 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 devapp event list ...` then `dws devapp 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. |
|
||||
| "订阅通讯录用户增加事件" | `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. |
|
||||
|
||||
@@ -1209,7 +1209,7 @@ Agent should normalize behavior without hiding raw backend fields.
|
||||
| `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. |
|
||||
| `VERSION_APPROVAL_REQUIRED` | `check-approval` says approver is required | Ask for approver or use returned candidate when the user gave an explicit approver. |
|
||||
| `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.
|
||||
|
||||
|
||||
+34
-2
@@ -726,13 +726,23 @@ func resolveIdentityHeaders() map[string]string {
|
||||
// errors (success=false + errorCode/errorMsg) that are not flagged at the MCP
|
||||
// protocol level. Returns the error message, or "" if the response is OK.
|
||||
func detectBusinessError(content map[string]any) string {
|
||||
return detectBusinessErrorAtDepth(content, 0)
|
||||
}
|
||||
|
||||
func detectBusinessErrorAtDepth(content map[string]any, depth int) string {
|
||||
if content == nil || depth > 8 {
|
||||
return ""
|
||||
}
|
||||
success, ok := content["success"]
|
||||
if !ok {
|
||||
return ""
|
||||
return detectNestedBusinessError(content, depth)
|
||||
}
|
||||
b, ok := success.(bool)
|
||||
if !ok || b {
|
||||
return ""
|
||||
return detectNestedBusinessError(content, depth)
|
||||
}
|
||||
if nested := detectNestedBusinessError(content, depth); nested != "" {
|
||||
return nested
|
||||
}
|
||||
if msg, ok := content["errorMsg"].(string); ok && strings.TrimSpace(msg) != "" {
|
||||
return strings.TrimSpace(msg)
|
||||
@@ -743,6 +753,28 @@ func detectBusinessError(content map[string]any) string {
|
||||
return "business error: success=false"
|
||||
}
|
||||
|
||||
func detectNestedBusinessError(content map[string]any, depth int) string {
|
||||
for _, key := range []string{"content", "result", "data"} {
|
||||
switch child := content[key].(type) {
|
||||
case map[string]any:
|
||||
if msg := detectBusinessErrorAtDepth(child, depth+1); msg != "" {
|
||||
return msg
|
||||
}
|
||||
case []any:
|
||||
for _, item := range child {
|
||||
childMap, ok := item.(map[string]any)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
if msg := detectBusinessErrorAtDepth(childMap, depth+1); msg != "" {
|
||||
return msg
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
// extractMCPErrorMessage builds an error message from a ToolCallResult with
|
||||
// isError=true. It extracts text from content blocks when available.
|
||||
func extractMCPErrorMessage(result transport.ToolCallResult) string {
|
||||
|
||||
@@ -896,6 +896,20 @@ func jsonRPCToolName(req map[string]any) string {
|
||||
return name
|
||||
}
|
||||
|
||||
func TestDetectBusinessErrorNestedServiceResult(t *testing.T) {
|
||||
content := map[string]any{
|
||||
"success": false,
|
||||
"result": map[string]any{
|
||||
"success": false,
|
||||
"errorCode": "ROBOT_NOT_FOUND",
|
||||
"errorMsg": "robot info is not exist",
|
||||
},
|
||||
}
|
||||
if got := detectBusinessError(content); got != "robot info is not exist" {
|
||||
t.Fatalf("detectBusinessError() = %q, want nested errorMsg", got)
|
||||
}
|
||||
}
|
||||
|
||||
func writeJSONRPCToolResult(t *testing.T, w http.ResponseWriter, req map[string]any, content map[string]any, isError bool) {
|
||||
t.Helper()
|
||||
_ = json.NewEncoder(w).Encode(map[string]any{
|
||||
|
||||
@@ -285,9 +285,11 @@ func newDevAppEventSubscribeCommand(runner executor.Runner) *cobra.Command {
|
||||
}
|
||||
params := map[string]any{"unifiedAppId": appID}
|
||||
// 服务端 subscribe/unsubscribe 工具收 eventCodes 数组(事件码列表)。
|
||||
if types := parseDevAppListFlag(cmd, "event-codes"); len(types) > 0 {
|
||||
params["eventCodes"] = types
|
||||
eventCodes, err := requiredDevAppEventCodes(cmd)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
params["eventCodes"] = eventCodes
|
||||
return runDevAppTool(runner, cmd, devAppEventSubscribeTool, params)
|
||||
},
|
||||
}
|
||||
@@ -314,9 +316,11 @@ func newDevAppEventUnsubscribeCommand(runner executor.Runner) *cobra.Command {
|
||||
return err
|
||||
}
|
||||
params := map[string]any{"unifiedAppId": appID}
|
||||
if types := parseDevAppListFlag(cmd, "event-codes"); len(types) > 0 {
|
||||
params["eventCodes"] = types
|
||||
eventCodes, err := requiredDevAppEventCodes(cmd)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
params["eventCodes"] = eventCodes
|
||||
return runDevAppTool(runner, cmd, devAppEventUnsubscribeTool, params)
|
||||
},
|
||||
}
|
||||
@@ -1489,6 +1493,14 @@ func parseDevAppListFlag(cmd *cobra.Command, name string) []string {
|
||||
return splitDevAppList(raw)
|
||||
}
|
||||
|
||||
func requiredDevAppEventCodes(cmd *cobra.Command) ([]string, error) {
|
||||
eventCodes := parseDevAppListFlag(cmd, "event-codes")
|
||||
if len(eventCodes) == 0 {
|
||||
return nil, apperrors.NewValidation("--event-codes 为必填")
|
||||
}
|
||||
return eventCodes, nil
|
||||
}
|
||||
|
||||
func splitDevAppList(raw string) []string {
|
||||
raw = strings.TrimSpace(raw)
|
||||
if raw == "" {
|
||||
|
||||
@@ -22,9 +22,7 @@ import (
|
||||
|
||||
// devapp_pretty annotates --format pretty output with human-readable status
|
||||
// labels. Upstream payloads use a numeric enum for app status and the bare
|
||||
// INIT/AUDIT/... enum for version status, and the same key `status` means
|
||||
// different things on different tools — fine for agents reading JSON against
|
||||
// the skill's enum tables, hostile to humans. JSON output is NEVER touched
|
||||
// INIT/AUDIT/... enum for versionStatus. JSON output is NEVER touched
|
||||
// (transparency contract); only the pretty view gets `*Text` companions.
|
||||
|
||||
var devAppStatusLabels = map[int]string{
|
||||
@@ -50,8 +48,8 @@ var devAppToolStatusKind = map[string]string{
|
||||
"create_dev_app": "version", // create/update 返回 versionStatus
|
||||
"update_dev_app": "version",
|
||||
"create_dev_app_version": "version",
|
||||
"list_dev_app_version": "version",
|
||||
"get_dev_app_version": "version",
|
||||
"list_dev_app_versions": "version",
|
||||
"get_dev_app_version_detail": "version",
|
||||
"get_dev_app_version_status": "version",
|
||||
"publish_dev_app_version": "version",
|
||||
}
|
||||
@@ -96,11 +94,9 @@ func devAppAnnotateMap(m map[string]any, kind string) {
|
||||
}
|
||||
return
|
||||
}
|
||||
for _, key := range []string{"status", "versionStatus"} {
|
||||
if s, ok := m[key].(string); ok {
|
||||
if label, ok := devAppVersionStatusLabels[s]; ok {
|
||||
m[key+"Text"] = label
|
||||
}
|
||||
if s, ok := m["versionStatus"].(string); ok {
|
||||
if label, ok := devAppVersionStatusLabels[s]; ok {
|
||||
m["versionStatusText"] = label
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -35,12 +35,15 @@ func TestDevAppPrettyAnnotateAppStatus(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestDevAppPrettyAnnotateVersionStatusKindSeparation(t *testing.T) {
|
||||
// 版本工具的字符串枚举要注解
|
||||
// 版本工具只注解 versionStatus;旧 status 字段不再参与版本契约。
|
||||
version := map[string]any{"status": "INIT", "versionStatus": "RELEASE"}
|
||||
devAppPrettyAnnotate("get_dev_app_version", version)
|
||||
if version["statusText"] != "未发布(有待发布变更)" || version["versionStatusText"] != "已发布生效" {
|
||||
devAppPrettyAnnotate("get_dev_app_version_detail", version)
|
||||
if version["versionStatusText"] != "已发布生效" {
|
||||
t.Fatalf("version annotations wrong: %#v", version)
|
||||
}
|
||||
if _, exists := version["statusText"]; exists {
|
||||
t.Fatalf("version status should not be annotated: %#v", version)
|
||||
}
|
||||
|
||||
// 应用工具的数字枚举不能套用版本语义;未登记的工具不动
|
||||
unknown := map[string]any{"status": float64(1)}
|
||||
|
||||
@@ -568,6 +568,32 @@ func TestDevAppEventSubscribeUsesEventCodes(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestDevAppEventSubscribeRequiresEventCodes(t *testing.T) {
|
||||
for _, tc := range []struct {
|
||||
name string
|
||||
args []string
|
||||
}{
|
||||
{"subscribe", []string{"dev", "app", "event", "subscribe", "--unified-app-id", "u-1", "--yes"}},
|
||||
{"unsubscribe", []string{"dev", "app", "event", "unsubscribe", "--unified-app-id", "u-1", "--yes"}},
|
||||
} {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
runner := &captureRunner{}
|
||||
root := newDevAppTestRoot(runner)
|
||||
var out bytes.Buffer
|
||||
root.SetOut(&out)
|
||||
root.SetErr(&out)
|
||||
root.SetArgs(tc.args)
|
||||
err := root.Execute()
|
||||
if err == nil || !strings.Contains(err.Error(), "--event-codes 为必填") {
|
||||
t.Fatalf("Execute() error = %v, want --event-codes 为必填", err)
|
||||
}
|
||||
if runner.last.Tool != "" {
|
||||
t.Fatalf("runner should not be called, got tool %q", runner.last.Tool)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestDevAppWebappCommandsBuildParams(t *testing.T) {
|
||||
cases := []struct {
|
||||
name string
|
||||
|
||||
@@ -16,6 +16,7 @@ package transport
|
||||
import (
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
apperrors "github.com/DingTalk-Real-AI/dingtalk-workspace-cli/internal/errors"
|
||||
)
|
||||
@@ -38,16 +39,11 @@ func ExtractServerDiagnostics(data json.RawMessage) apperrors.ServerDiagnostics
|
||||
if len(data) == 0 {
|
||||
return apperrors.ServerDiagnostics{}
|
||||
}
|
||||
var fields serverDiagFields
|
||||
if json.Unmarshal(data, &fields) != nil {
|
||||
var content map[string]any
|
||||
if json.Unmarshal(data, &content) != nil {
|
||||
return apperrors.ServerDiagnostics{}
|
||||
}
|
||||
return apperrors.ServerDiagnostics{
|
||||
TraceID: coalesceStr(fields.TraceID, fields.TraceIDCamel),
|
||||
ServerErrorCode: coalesceStr(fields.Code, fields.ErrorCode),
|
||||
TechnicalDetail: fields.TechnicalDetail,
|
||||
ServerRetryable: fields.Retryable,
|
||||
}
|
||||
return ExtractServerDiagnosticsFromMap(content)
|
||||
}
|
||||
|
||||
// ExtractServerDiagnosticsFromMap parses server diagnostic fields from a
|
||||
@@ -58,7 +54,7 @@ func ExtractServerDiagnosticsFromMap(content map[string]any) apperrors.ServerDia
|
||||
}
|
||||
diag := apperrors.ServerDiagnostics{
|
||||
TraceID: stringFromMap(content, "trace_id", "traceId"),
|
||||
ServerErrorCode: stringFromMap(content, "code", "errorCode"),
|
||||
ServerErrorCode: serverErrorCodeFromMap(content, 0),
|
||||
TechnicalDetail: stringFromMap(content, "technical_detail"),
|
||||
}
|
||||
if v, ok := content["retryable"].(bool); ok {
|
||||
@@ -102,3 +98,50 @@ func stringFromMap(m map[string]any, keys ...string) string {
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func serverErrorCodeFromMap(content map[string]any, depth int) string {
|
||||
if content == nil || depth > 8 {
|
||||
return ""
|
||||
}
|
||||
direct := stringFromMap(content, "errorCode", "code", "server_error_code")
|
||||
if isWrapperServerCode(direct) {
|
||||
if nested := nestedServerErrorCode(content, depth); nested != "" {
|
||||
return nested
|
||||
}
|
||||
}
|
||||
if direct != "" {
|
||||
return direct
|
||||
}
|
||||
return nestedServerErrorCode(content, depth)
|
||||
}
|
||||
|
||||
func nestedServerErrorCode(content map[string]any, depth int) string {
|
||||
for _, key := range []string{"content", "result", "data"} {
|
||||
switch child := content[key].(type) {
|
||||
case map[string]any:
|
||||
if code := serverErrorCodeFromMap(child, depth+1); code != "" {
|
||||
return code
|
||||
}
|
||||
case []any:
|
||||
for _, item := range child {
|
||||
childMap, ok := item.(map[string]any)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
if code := serverErrorCodeFromMap(childMap, depth+1); code != "" {
|
||||
return code
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func isWrapperServerCode(code string) bool {
|
||||
switch strings.ToUpper(strings.TrimSpace(code)) {
|
||||
case "ERROR", "BUSINESS_ERROR", "-1":
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
@@ -76,6 +76,26 @@ func TestExtractServerDiagnosticsFromMap(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestExtractServerDiagnosticsFromMapNestedBusinessCode(t *testing.T) {
|
||||
t.Parallel()
|
||||
content := map[string]any{
|
||||
"trace_id": "trace-002",
|
||||
"code": "BUSINESS_ERROR",
|
||||
"result": map[string]any{
|
||||
"success": false,
|
||||
"errorCode": "ROBOT_NOT_FOUND",
|
||||
"errorMsg": "robot info is not exist",
|
||||
},
|
||||
}
|
||||
diag := ExtractServerDiagnosticsFromMap(content)
|
||||
if diag.TraceID != "trace-002" {
|
||||
t.Fatalf("TraceID = %q, want trace-002", diag.TraceID)
|
||||
}
|
||||
if diag.ServerErrorCode != "ROBOT_NOT_FOUND" {
|
||||
t.Fatalf("ServerErrorCode = %q, want ROBOT_NOT_FOUND", diag.ServerErrorCode)
|
||||
}
|
||||
}
|
||||
|
||||
func TestExtractServerDiagnosticsFromMap_Empty(t *testing.T) {
|
||||
t.Parallel()
|
||||
diag := ExtractServerDiagnosticsFromMap(nil)
|
||||
|
||||
@@ -109,4 +109,4 @@ metadata:
|
||||
|
||||
## Gotchas
|
||||
- 新应用 `version list` 返回空不等于无可发布内容:先 `version create`,用返回的 `versionId` 继续 check-approval/publish。
|
||||
- `robot info is not exist` 是「应用还没配过机器人」,走 `robot config` 首次创建,不是 `enable`。
|
||||
- `robotStatus=UNCONFIGURED` 是「应用还没配过机器人」,走 `robot config` 首次创建,不是 `enable`。
|
||||
|
||||
@@ -4,11 +4,12 @@
|
||||
|
||||
`dws dev app event list/subscribe/unsubscribe`,按 `--unified-app-id` 定位,订阅/退订用 `--event-codes`(逗号分隔,一次多个)。参数查 `dws schema dev.app.event.<method>`。
|
||||
|
||||
前置条件(业务规则):订阅/退订前必须先建立长连(`dev connect`,见 connect.md)。长连未在线时服务端报「长链接未在线」(errorCode -1),这是预期,不是故障——先建联再订阅。
|
||||
|
||||
规则:
|
||||
- 写操作先 `--dry-run` 预览,确认后 `--yes`。
|
||||
- 一次可订阅多个事件码,共用同一回调。
|
||||
- 事件码取值以开放平台文档为准;不确定走 `dws dev doc search`。
|
||||
- 退订前先 `event list` 确认当前订阅,避免退不存在的。
|
||||
- 返回看 `events`(已订阅列表)和 `pushType`(推送方式)。
|
||||
- `event list` 支持 `--cursor/--page-size`,返回 `events/hasMore/nextCursor/pageSize`;翻页继续传 `nextCursor`。
|
||||
- 返回看 `events[].subscribed` 和 `pushType=STREAM`。
|
||||
- `subscribe/unsubscribe` 的 `--event-codes` 必填,返回 `success/operation/unifiedAppId/eventCodes/needsPublish/versionRequiredAction`;失败时补 `errorCode/errorMsg/reason/retryable/action`。
|
||||
- 如果服务端返回 `errorCode=STREAM_NOT_CONNECTED`、`reason=STREAM_NOT_CONNECTED`、`retryable=false`、`action=run connect`,先执行 `dev connect` 建联,再重试订阅。
|
||||
|
||||
@@ -11,13 +11,13 @@
|
||||
|
||||
## 一、新建智能体机器人(异步建号)
|
||||
|
||||
`submit` 提交任务拿 `taskId`,`result --task-id <taskId>` 轮询。`submit` 返回 `taskId/status/expiresIn/interval/retryCount`,提交成功通常是 `WAITING`。失败重试:把上次 `taskId` 通过 `--task-id` 传回 `submit`,避免重复创建。只有 `result` 返回 `SUCCESS` 才能用返回的 `agentId/robotCode/clientId/clientSecret`。
|
||||
`submit` 提交任务拿 `taskId`,`result --task-id <taskId>` 轮询。`submit` 返回 `taskId/status/expiresInSeconds/intervalSeconds/retryCount/bindsUnifiedApp`,提交成功通常是 `WAITING`,且 `bindsUnifiedApp=false` 表示异步建号任务不挂到现有应用。失败重试:把上次 `taskId` 通过 `--task-id` 传回 `submit`,避免重复创建。只有 `result` 返回 `SUCCESS` 才能用返回的 `agentId/robotCode/clientId/clientSecret`。
|
||||
|
||||
异步任务状态:
|
||||
|
||||
| status | 含义 | 下一步 |
|
||||
|--------|------|--------|
|
||||
| `WAITING` | 创建中 | 按 `interval` 轮询 `robot result` |
|
||||
| `WAITING` | 创建中 | 按 `intervalSeconds` 轮询 `robot result` |
|
||||
| `SUCCESS` | 创建完成 | 保存 `robotCode/clientId/clientSecret`,凭据按敏感处理 |
|
||||
| `APPROVAL_REQUIRED` | 创建编排需审批 | 不要重复建号;按返回信息或后台审批后再继续 |
|
||||
| `FAIL` | 创建失败 | 读 `errorCode/errorMsg/failReason`;可带原 `taskId` 重新 `submit` |
|
||||
@@ -25,22 +25,23 @@
|
||||
|
||||
## 二、现有应用的机器人配置
|
||||
|
||||
`robot get` 返回机器人基础信息、回调、模式、状态、技能列表;应用尚未配置机器人时后端返回 `robot info is not exist`。
|
||||
`robot get` 返回机器人基础信息、回调、模式、状态、技能列表;应用尚未配置机器人时返回空态 `robotStatus=UNCONFIGURED`,不是业务错误。
|
||||
|
||||
状态判断:
|
||||
- `status=1`:OFFLINE,配置存在但停用/下线。
|
||||
- `status=2`:ONLINE,配置已生效;`robotCode` 可用于加群、机器人身份发消息或后续建联。
|
||||
- `robotStatus=UNCONFIGURED`:应用未配置机器人,走 `robot config`。
|
||||
- `robotStatus=OFFLINE`:配置存在但停用/下线,可走 `robot enable`。
|
||||
- `robotStatus=ONLINE`:配置已启用;`robotCode` 可用于加群、机器人身份发消息或后续建联。
|
||||
- `mode` 是字符串枚举:`HTTPS` / `STREAM` / `AISKILL`。
|
||||
- `robot get` 返回 `success=true` 且含 `robotCode` = 配置已落库,不是异步等待态。
|
||||
- ONLINE 只代表能力已开启。要让机器人自动处理消息,还需配 `--outgoing-url`/`--event-callback-url`,或用 `dev connect` 接本地 Agent(见 connect.md)。
|
||||
- 未配置机器人时不返回 `status`,而是业务错误 `robot info is not exist`;这时走 `robot config`,不是 `enable`。
|
||||
|
||||
`config` 是 upsert:建或改都用它,不存在则建、存在则改,至少给一个配置字段。国际化字段(`--i18n-name` 等)传 JSON,如 `'{"en_US":"Bot"}'`。`enable` 是纯启用:只开启能力,不带配置字段(只传 `--unified-app-id`)。旧的独立 `update` 已并入 `config`。`config` 成功后回读 `robot get`:返回 `status=2` 就别再误判"待生效",只有 `status=1` 或要重新上架才调 `enable`。
|
||||
`config` 是 upsert:建或改都用它,不存在则建、存在则改,至少给一个配置字段。国际化字段(`--i18n-name` 等)传 JSON,如 `'{"en_US":"Bot"}'`。`enable` 是纯启用:只开启能力,不带配置字段(只传 `--unified-app-id`)。旧的独立 `update` 已并入 `config`。`config/enable/disable` 成功统一返回 `success/operation/unifiedAppId/robotCode/robotStatus/configured`;回读 `robot get` 看到 `robotStatus=ONLINE` 就别再误判"待生效"。
|
||||
|
||||
## 错误处理
|
||||
|
||||
| 情况 | 处理 |
|
||||
|------|------|
|
||||
| `robot info is not exist` | 应用未配置机器人,先用 `robot config` 创建 |
|
||||
| `robotStatus=UNCONFIGURED` | 应用未配置机器人,先用 `robot config` 创建 |
|
||||
| 应用名重复 | `app-name` 企业内需唯一,换名 |
|
||||
| `ServiceResult.success=false` | 透传 `errorCode/errorMsg` |
|
||||
| 创建任务 `EXPIRED` | 任务过期,重新 `submit`(可带原 taskId) |
|
||||
|
||||
@@ -28,13 +28,15 @@ permission add(requiredApproval=true 写入版本变更)
|
||||
|
||||
区分两个"预检":`--dry-run` 是 CLI 层的预览不调上游;`check-approval` 是服务端查审批要求不发布。发布前建议先 `check-approval`。
|
||||
|
||||
发布响应 `result`:
|
||||
发布/预检不返回动作枚举 `result`,看结构化字段判断下一步:
|
||||
|
||||
| result | 含义 | 下一步 |
|
||||
|--------|------|--------|
|
||||
| `NOT_REQUIRED` | 不需审批;预检=可直接发布,真实发布=已直接上线 | 回读 `version status/get` 验证 `RELEASE` |
|
||||
| `APPROVAL_REQUIRED` | 需审批,常见于 `check-approval` 或未指定审批人 | 看 `approvalMode/approvalCandidates`,让用户选审批人后再 `publish --approver-user-id` |
|
||||
| `SUBMITTED` | 已提交审批 | 保存 `processId`,轮询 `version status` |
|
||||
| 字段 | 含义 | 下一步 |
|
||||
|------|------|--------|
|
||||
| `requiresApproval=false` + `publishable=true` | 不需审批,`check-approval` 通过 | 可以执行 `version publish` |
|
||||
| `requiresApproval=true` + `approvalMode=SELECT_APPROVER` | 需从候选人里选审批人 | 展示 `approvalCandidates`,让用户选后再 `publish --approver-user-id` |
|
||||
| `requiresApproval=true` + `approvalMode=ENTERPRISE_SELF_BUILT` | 企业自建审核 | 不传 `--approver-user-id`,直接 `publish` 提交审批 |
|
||||
| `published=true` | 本次 `publish` 已直接发布 | 回读 `version status/get` 验证 `versionStatus=RELEASE` |
|
||||
| `approvalSubmitted=true` | 本次 `publish` 已提交审批 | 保存 `processId`,轮询 `version status` |
|
||||
|
||||
审批模式 `approvalMode`:
|
||||
|
||||
|
||||
@@ -7,10 +7,10 @@
|
||||
## 可订阅事件列表
|
||||
|
||||
```bash
|
||||
dws devapp event list --unified-app-id <unifiedAppId> --format json
|
||||
dws dev app event list --unified-app-id <unifiedAppId> --page-size 20 --format json
|
||||
```
|
||||
|
||||
MCP tool: `list_dev_app_events`。返回 `pushType`(当前推送类型)和 `events[]`,每项含:
|
||||
MCP tool: `list_dev_app_events`。返回 `pushType=STREAM`、`events[]`、`hasMore`、`nextCursor`、`pageSize`;`hasMore=true` 时下一页继续传 `--cursor <nextCursor>`。`events[]` 每项含:
|
||||
|
||||
| 字段 | 含义 |
|
||||
|------|------|
|
||||
@@ -23,26 +23,28 @@ MCP tool: `list_dev_app_events`。返回 `pushType`(当前推送类型)和 `
|
||||
## 订阅事件
|
||||
|
||||
```bash
|
||||
dws devapp event subscribe --unified-app-id <unifiedAppId> --event-codes user_add_org,org_dept_modify --dry-run --format json
|
||||
dws devapp event subscribe --unified-app-id <unifiedAppId> --event-codes user_add_org,org_dept_modify --yes --format json
|
||||
dws dev app event subscribe --unified-app-id <unifiedAppId> --event-codes user_add_org,org_dept_modify --dry-run --format json
|
||||
dws dev app event subscribe --unified-app-id <unifiedAppId> --event-codes user_add_org,org_dept_modify --yes --format json
|
||||
```
|
||||
|
||||
MCP tool: `subscribe_dev_app_events`(入参 `unifiedAppId` + `eventCodes`,一次订阅多个事件码)。
|
||||
MCP tool: `subscribe_dev_app_events`(入参 `unifiedAppId` + 必填数组 `eventCodes`,一次订阅多个事件码)。成功返回 `success/operation=SUBSCRIBE/unifiedAppId/eventCodes/needsPublish/versionRequiredAction`;失败时补 `errorCode/errorMsg/reason/retryable/action`。
|
||||
|
||||
## 退订事件
|
||||
|
||||
```bash
|
||||
dws devapp event unsubscribe --unified-app-id <unifiedAppId> --event-codes user_add_org,org_dept_modify --dry-run --format json
|
||||
dws devapp event unsubscribe --unified-app-id <unifiedAppId> --event-codes user_add_org,org_dept_modify --yes --format json
|
||||
dws dev app event unsubscribe --unified-app-id <unifiedAppId> --event-codes user_add_org,org_dept_modify --dry-run --format json
|
||||
dws dev app event unsubscribe --unified-app-id <unifiedAppId> --event-codes user_add_org,org_dept_modify --yes --format json
|
||||
```
|
||||
|
||||
MCP tool: `unsubscribe_dev_app_events`(入参 `unifiedAppId` + `eventCodes`,一次退订多个事件码)。
|
||||
MCP tool: `unsubscribe_dev_app_events`(入参 `unifiedAppId` + 必填数组 `eventCodes`,一次退订多个事件码)。成功返回 `success/operation=UNSUBSCRIBE/unifiedAppId/eventCodes/needsPublish/versionRequiredAction`;失败时补 `errorCode/errorMsg/reason/retryable/action`。
|
||||
|
||||
## 字段映射
|
||||
|
||||
| CLI | MCP | 必填 | 说明 |
|
||||
|-----|-----|------|------|
|
||||
| `--unified-app-id` | `unifiedAppId` | 是 | 统一应用 ID |
|
||||
| `--cursor` | `cursor` | list 可选 | 服务端返回的下一页游标 |
|
||||
| `--page-size` | `pageSize` | list 可选 | 单页数量 |
|
||||
| `--event-codes` | `eventCodes` | subscribe/unsubscribe 必填 | 事件码列表,逗号分隔,取自 `event list` |
|
||||
|
||||
## 灰度应用需发布版本生效(重要)
|
||||
@@ -57,21 +59,21 @@ event subscribe/unsubscribe 订阅变更写入版本元数据
|
||||
→ version status 轮询发布/审批状态
|
||||
```
|
||||
|
||||
详见 [version.md](version.md)。
|
||||
详见版本发布文档。
|
||||
|
||||
## 单步流程
|
||||
|
||||
```text
|
||||
1. 查看可订阅事件及当前状态
|
||||
dws devapp event list --unified-app-id <ID> --format json
|
||||
dws dev app event list --unified-app-id <ID> --format json
|
||||
→ 从返回里挑 eventCode
|
||||
|
||||
2. 订阅
|
||||
dws devapp event subscribe --unified-app-id <ID> --event-codes <CODE1>,<CODE2> --dry-run --format json
|
||||
dws dev app event subscribe --unified-app-id <ID> --event-codes <CODE1>,<CODE2> --dry-run --format json
|
||||
→ 确认后加 --yes
|
||||
|
||||
3. 验证
|
||||
dws devapp event list --unified-app-id <ID> --format json
|
||||
dws dev app event list --unified-app-id <ID> --format json
|
||||
→ 确认对应事件 subscribed=true(灰度应用需先发布版本)
|
||||
```
|
||||
|
||||
@@ -81,4 +83,5 @@ event subscribe/unsubscribe 订阅变更写入版本元数据
|
||||
|------|------|
|
||||
| `--event-codes` 缺失 | CLI 直接报错,先 `event list` 取事件码 |
|
||||
| 订阅后 `subscribed` 仍为 false | 灰度应用需先 `version publish` 发布版本 |
|
||||
| `errorCode=STREAM_NOT_CONNECTED` / `reason=STREAM_NOT_CONNECTED` | 先执行 `dev connect` 建联,再重试订阅 |
|
||||
| `ServiceResult.success=false` | 透传 `errorCode/errorMsg` |
|
||||
|
||||
Reference in New Issue
Block a user