first commit

This commit is contained in:
2026-09-02 11:44:52 +08:00
commit 0c8fa2653e
309 changed files with 57278 additions and 0 deletions
@@ -0,0 +1,42 @@
# 应用基础操作
> 操作的是「应用」容器本体(见 [devapp.md](../devapp.md) 概念地图);启停/删除改的是应用 appStatus,不是版本 versionStatus。
应用列表查询、详情、创建、修改、生命周期启停和删除。参数用对应命令的 `--help` 查询。
## 应用定位
写操作与多数单应用命令统一用 `--unified-app-id`(全树主键)定位。`dev app get` 额外支持只读按 `--app-key`=clientId)查详情;`--name` 仍只在 `dev app list` 作列表过滤。拿到 appKey 时可先 `app get --app-key` 核验并拿回 `unifiedAppId`;写操作必须由用户或上游结果提供明确 `unifiedAppId`
## 应用状态 appStatus
`app get``appStatus` 是字符串,取值如 `normal``published``app list` 不回这个字段(恒 `null`),看应用状态以 `app get` 为准。应用状态 `appStatus` 和版本状态 `versionStatus` 是两套,别混。遇到没见过的 `appStatus` 值原样展示。
`app create``app update` 不返回状态字段;版本状态由 `version create` 返回的 `status`(值如 INIT)表达,见 version.md。
## 要点
- `get` 主要用于定位核验;若返回里带 `appSecret`,脱敏处理,不复制到回答;主动读凭证走 `credentials get`
- `disable/enable` 成功返回 `{disabled:true}` / `{enabled:true}` + `message`,不回 `appStatus`;以这个布尔判操作成败。要确认最终生效态再 `get``appStatus` 字符串值。
- `delete` 前必须展示应用摘要;删除是异步,成功后延迟从列表消失。
## 错误处理
| 情况 | 处理 |
|------|------|
| 多应用命中 | 展示候选,停止写操作 |
| `ServiceResult.success=false` | 透传 `errorCode/errorMsg` |
## 发现命令
调用任何方法前先查清楚再敲:
```
# 浏览命令组下的子命令与 flag
dws dev app --help
# 查某方法的必填参数、类型、默认值
dws dev <command-path> --help
```
`--help` 输出构造 flag;不要凭旧 schema 名称猜参数。
@@ -0,0 +1,108 @@
# 本地建联(把机器人接到本地 agent)
> `dws dev connect` 是 dev 顶层命令,把一个现成机器人接到当前渠道的本地 agent CLI 做调试/值守——只建联、不建号。缺机器人时优先先创建应用并用 `robot config` 配置;若走无绑定的 `robot submit/result`,缺 `unifiedAppId` 时不能续写版本发布。
## 起连接
```bash
# 用现成机器人凭证起 Stream,接到当前渠道的本地 agent(前台运行,Ctrl-C 退出)
dws dev connect --channel auto --robot-client-id <clientId> --robot-client-secret <clientSecret>
# 用统一应用 ID,复用 credentials get 自动取凭证
dws dev connect --unified-app-id <unifiedAppId> --channel qoderwork
# 预览建联方案不实际起连接
dws dev connect --robot-client-id <clientId> --robot-client-secret <clientSecret> --dry-run --format json
```
正式 connect 是前台长驻进程:在对话里跑必须后台运行并告诉用户如何停止,或引导用户自己开终端跑。
`dev connect` 只做本地 Stream 调试/值守,不会创建版本、提交审批或发布应用。dry-run JSON 的 `invocation` 会声明 `scope=local_debug_only``doesNotPublish=true``completionState=LOCAL_DEBUG_ONLY`,真实前台/daemon 启动也会打印“本地调试,不代表线上发布完成”。完成态判定(建联成功不等于线上可用)以 [devapp.md](../devapp.md)「核心规则」为准。
| flag | 说明 |
|------|------|
| `--channel` | `auto`(默认,运行时信号自动识别) / openclaw / qoder / qoderwork / hermes / workbuddy / claudecode / codebuddy / codex / gemini / opencode |
| `--robot-client-id` / `--robot-client-secret` | 现成机器人凭证(clientId=AppKey, clientSecret=AppSecret)。命名带 `robot-` 前缀以避开全局 OAuth `--client-id` flag |
| `--unified-app-id` | 统一应用 ID,内部复用 `credentials get` 自动取凭证,替代手填 robot-client-id/secret。注意 clientSecret 仅建号时返回一次、未必可取,取不到时回退手填 |
| `--agent-memory` | 按会话续聊(默认开):同一群/单聊共享 agent 会话,追问保留上下文。codex 走 app-server threadopencode 走 `opencode serve` 的 HTTP session/message APIqoder/qoderwork 走常驻 `qodercli --input-format stream-json` 并传 `session_id`claudecode/codebuddy/workbuddy 走 CLI `--session-id`/`--resume`。会话映射按机器人落盘,重启后可继续;gemini 保持无状态。`--agent-memory=false` 关闭 |
| `--agent-model` | 覆盖本地 agent 模型(如 claudecode 默认锁 haiku 求快,可改 `claude-sonnet-4-6` 换聪明)。env: `DWS_AGENT_MODEL` |
| `--agent-workdir` | agent 运行目录:放知识文件(如 CLAUDE.md)可给机器人企业上下文。默认空白临时目录(冷启动快 ~4s vs 大目录 ~29s,慢了会错过钉钉响应窗口)。env: `DWS_AGENT_WORKDIR` |
| `--reply-card` | 富回复(默认开):🤔Thinking/🥳Done 表态永远生效;**卡片需配 `--card-template` 才启用**(同 hermes:没配模板=纯文字回复),失败自动回退文字;env `DWS_REPLY_CARD=0` 全关 |
| `--card-template` | AI 卡片模板 ID。**模板按应用授权**:去开发者后台→你的应用→AI 卡片设置注册/获取模板 ID,可去掉公共模板的第三方角标;默认用公共模板 best-effort。env `DWS_CARD_TEMPLATE` |
| `--allowed-groups` | 群白名单 openConversationId(逗号分隔),配置后只有名单内的群能触发机器人。env `DWS_ALLOWED_GROUPS` |
| `--allowed-users` | 用户白名单 staffId(逗号分隔),配置后只有名单内的用户能触发。env `DWS_ALLOWED_USERS` |
| `--knowledge-dir` | 答疑知识目录(.md/.txt):每条消息本地检索 top-k 片段拼进 prompt,agent 仍在空目录跑、不拖慢回复。env `DWS_KNOWLEDGE_DIR` |
| `--user-rate-limit` | 单用户每分钟消息上限(防刷,每条消息都是一次 LLM 调用),0 关闭,默认 20。env `DWS_USER_RATE_LIMIT` |
## 建联前的依赖预检(agent 必做)
渠道背后是本地 agent CLI,用户可能没装。先 `--dry-run` 看出参 `cli` 字段再决定下一步:
```bash
dws dev connect --channel <ch> --robot-client-id x --robot-client-secret y --dry-run --format json
# 输出里的 cli 字段:
# "cli": {"required":"Claude Code","installed":false,"autoInstall":true,"installHint":"npm i -g @anthropic-ai/claude-code"}
```
| cli 状态 | agent 应该做什么 |
|----------|----------------|
| `installed: true` | 直接建联 |
| `installed: false, autoInstall: true` | 告知用户缺哪个 CLI,说明启动建联时会自动 `npm` 安装(或先手动执行 installHint 里的命令再连);`DWS_CONNECT_NO_INSTALL=1` 可禁自动安装 |
| `installed: false, autoInstall: false` | **不要直接起连接**——桌面 App 渠道(qoder/qoderwork/workbuddy)需要用户先安装对应 AppinstallHint 是下载地址),装好后 CLI 随 App 自带;openclaw/hermes 引导用户走官方 onboarding |
dry-run 出参的完整建联预检结构(channel/detectedBy/credentialSource/agent/cli/connect)见 [devapp.md](../devapp.md)「通用出参约定」。
必须检查 dry-run 顶层:
```json
{
"invocation": {
"completionState": "LOCAL_DEBUG_ONLY",
"doesNotPublish": true,
"scope": "local_debug_only",
"terminal": false
}
}
```
这几个字段表示:连接器可以起本地调试,但版本发布闭环仍由 `robot result` 的 blocking `nextSteps` 或后续 `version status` 决定(完整门禁规则见 [devapp.md](../devapp.md)「核心规则」)。
## Codex 渠道注意
```bash
dws dev connect --unified-app-id <unifiedAppId> --channel codex --format json
```
- `--channel codex` 只走 Codex app-server 的 thread/turn 协议,不再降级到 `codex exec`
- `DWS_AGENT_CMD` 不覆盖 Codex 渠道;自研或未支持的 AI 工具请用 `--channel custom --agent-cmd "<命令>"`
- 给 Codex 固定知识/项目上下文:使用 `--agent-workdir /path/to/repo`
## 机制与环境覆盖
- **stream-bridge 渠道**Go 原生进程内 Stream 转发器,订阅 `TOPIC_ROBOT`。Claude/CodeBuddy/WorkBuddy/custom 每条 @机器人消息起一个无头 CLI 实例 → stdout 回钉钉;Qoder/QoderWork 会在 connect 生命周期内复用一个常驻 `qodercli --print --output-format stream-json --input-format stream-json` 子进程。
- **会话记忆**Codex 记录 `conversationId -> threadId`opencode 记录 `conversationId -> sessionId` 并通过本地 `opencode serve` 的 HTTP API 续聊;Qoder/QoderWork 记录 `conversationId -> Qoder session_id` 并在常驻 stream-json 子进程里续聊;Claude/CodeBuddy/WorkBuddy 使用 `--session-id`/`--resume`。映射按机器人落盘,重启后可继续。
- **会话指令 `/new` vs `/clear`**(对齐各渠道真实能力):`/new`(含 `/start``/reset`)开新会话——丢掉当前映射、下一条消息起新 session**旧 session 保留**opencode/Codex/Claude 侧仍可按 id 续);`/clear` 则**真正清掉当前会话**——能删的渠道调 agent 原生删除原语(opencode `DELETE /session/:id`),不暴露删除接口的渠道(Codex/Qoder/Claude 系)降级为与 `/new` 相同的重置。两者都只回 ack、不消耗 agent turn。
- **官方渠道**openclaw/hermes):dws 不代建机器人,输出官方 onboarding 指引。
- 环境覆盖:`DWS_AGENT_CMD`(整条命令覆盖,覆盖时不再注入模型/会话参数) / `DWS_AGENT_MODEL` / `DWS_AGENT_WORKDIR` / `DWS_CONNECT_CMD` / `DWS_CONNECT_NO_INSTALL=1` / `DWS_AGENT_TIMEOUT_MS`
## 错误处理
| 情况 | 处理 |
|------|------|
| 缺凭证 | 优先用明确 `unifiedAppId``credentials get`;若只有 `robot submit/result` 的一次性 clientId/clientSecret,按敏感信息使用,缺 `unifiedAppId` 时不能续写版本发布 |
| Codex app-server 调用失败 | 检查本机 `codex` 是否可执行、是否已登录,以及 `--agent-workdir` 指向的目录是否可用;Codex 渠道不会降级到 `codex exec` |
| 桌面 App 渠道 `installed:false, autoInstall:false` | 引导用户先装对应 App(installHint 是下载地址),不要直接起连接 |
## 发现命令
起连接前先查清楚再敲:
```
# 浏览 connect 的子命令与 flag
dws dev connect --help
# 查 connect 的必填参数、类型、默认值
dws dev <command-path> --help
```
`--help` 输出构造 flag;不要凭旧 schema 名称猜参数。
@@ -0,0 +1,26 @@
# 应用凭证读取
> 凭证=应用调 OpenAPI 的身份(appKey=clientId / appSecret=clientSecret);见 [devapp.md](../devapp.md) 概念地图。
`dws dev app credentials get --unified-app-id <id>` 读取应用凭证。参数用对应命令的 `--help` 查询。
返回字段:`clientId`/`appKey`(同值)、`clientSecret`/`appSecret`(同值)、`currentSecretStatus``hasPendingExpireTask``unifiedAppId` 等。
规则:
- 该命令只需 `--unified-app-id`
- 返回里 `clientSecret/appSecret` 是明文密钥,按敏感凭证处理,不写进回答文本。
- 不能用 `dev app get` 代替;`dev app get` 也会带密钥,同样只用于内部判断并脱敏,不向用户展开。
## 发现命令
调用任何方法前先查清楚再敲:
```
# 浏览命令组下的子命令与 flag
dws dev app credentials --help
# 查某方法的必填参数、类型、默认值
dws dev <command-path> --help
```
`--help` 输出构造 flag;不要凭旧 schema 名称猜参数。
@@ -0,0 +1,12 @@
# 开发者能力索引
| 主题 | 详见 |
|---|---|
| 应用管理 | [`app.md`](./app.md) |
| 凭证/鉴权 | [`credentials.md`](./credentials.md) |
| 权限管理 | [`permission.md`](./permission.md) |
| 机器人 | [`robot.md`](./robot.md) |
| 长连接 | [`connect.md`](./connect.md) |
| 事件订阅 | [`event.md`](./event.md) |
| 成员管理 | [`member.md`](./member.md) |
| 最佳实践 | [`recipes.md`](./recipes.md) |
@@ -0,0 +1,47 @@
# 事件订阅
> 把应用关心的事件推到回调地址;见 [devapp.md](../devapp.md) 概念地图。
`dws dev app event list/subscribe/unsubscribe`,按 `--unified-app-id` 定位,订阅/退订用 `--event-codes`(逗号分隔,一次多个)。参数用对应命令的 `--help` 查询。
规则:
- 写操作先 `--dry-run` 预览,确认后 `--yes`
- 一次可订阅多个事件码,共用同一回调。
- **事件码定位优先用 `event list --keyword <关键词>` 搜索**(按事件码或事件名称模糊匹配);只有用户明确要「全部事件」时才不带 `--keyword` 翻全量。
- 可订阅的事件码通过 `event list` 查询:返回 `events[]` 列出 `eventCode/eventName/subscribed`,不用查文档。
- 退订前先 `event list` 确认当前订阅,避免退不存在的。
- 翻全量时用 `--cursor/--page-size` 逐页处理;返回 `events/hasMore/nextCursor/pageSize`,翻页继续传 `nextCursor`
- 批量或全量订阅前,先把候选 `eventCode` 列给用户确认,再 `--dry-run``--yes`
- 返回看 `events[].subscribed``pushType=STREAM`(事件走 Stream 长连推送;connect 与事件订阅的关系见下方「Stream 长连」)。
- `subscribe/unsubscribe``--event-codes` 必填,返回 `success/operation/unifiedAppId/eventCodes/needsPublish/versionRequiredAction`;失败时补 `errorCode/errorMsg/reason/retryable/action`
- `subscribe/unsubscribe` 返回 `needsPublish=true` 或非空 `versionRequiredAction` 时,按 [version.md](version.md) 继续 `version create → check-approval → publish → status`;进入 `RELEASE` 后重新 `event list`,确认目标 `events[].subscribed` 与本次操作一致。进入审核态则报告待审批;需要选择审批人时停下让用户选择。
- 如果订阅失败、返回提示长链接未在线(是泛化错误:`reason=business_error``message` 含「长链接未在线」、`server_error_code=-1`;没有 STREAM_NOT_CONNECTED 这类结构化错误码,也没有 action 字段),先执行 `dev connect` 建联,再重试订阅。
## Stream 长连:connect 与事件订阅的关系
钉钉一个应用就一条 Stream 长连(WebSocket),上面同时承载机器人消息、事件、卡片回调等多种 topic。connect 和 event 在这条长连上分工不同:
- `dev connect`:用应用凭证把这条长连建起来并保活,但只注册了机器人消息处理(收 @机器人 → 转发本地 agent),**不消费事件**。
- `event subscribe/unsubscribe`:只是**配置**操作(配应用订阅哪些事件码),自己不建长连、不收事件;服务端要求应用的 Stream 长连已在线,否则报错「长链接未在线」(泛化 business_error,不是结构化错误码)。
- 两者唯一关联:先 `dev connect` 把长连建在线,`event subscribe` 才能成功——connect 负责「让长连在线」,subscribe 负责「配置订阅」。
- 当前限制:connect 的长连不处理事件,dws 也没有「收/消费事件」的运行时命令,event 只到「配置订阅」为止。订阅成功后真正的事件推送 dws 暂不消费——要消费得用注册了事件 handler 的 SDK 自接(事件结构走 `dws devdoc article search --query` 查)。
## Stream 长连怎么建的
`dev connect` 内部用 dingtalk-stream-sdk-go,凭 `clientId/clientSecret` 建一条 WebSocket 长连并保活——底层网关握手、ticket、加解密都由 SDK 封装,agent 跑 `dev connect` 即可,不用碰这些。
dws 当前只消费机器人消息、不消费事件。要自己写事件消费程序补这个 gap 时,SDK 用法以官方文档为准(走 `dws devdoc article search --query`,或看 github.com/open-dingtalk/dingtalk-stream-sdk-go)——注意事件用 `RegisterAllEventHandler`、connect 用的机器人是 `RegisterChatBotCallbackRouter`,两套别混;版本/接口会变,不在这里固化。
## 发现命令
调用任何方法前先查清楚再敲:
```
# 浏览命令组下的子命令与 flag
dws dev app event --help
# 查某方法的必填参数、类型、默认值
dws dev <command-path> --help
```
`--help` 输出构造 flag;不要凭旧 schema 名称猜参数。
@@ -0,0 +1,19 @@
# 成员管理
> 成员=谁能改这个应用(DEVELOPER 等角色);见 [devapp.md](../devapp.md) 概念地图。
`dws dev app member list/add/remove` 管理应用成员。参数用 `dws dev app member <method> --help` 查询。add/remove 需 `--user-ids` 列表 + `--member-type`,如 DEVELOPERremove 也必须传 memberType,因为同一用户可能有多个成员身份。
## 发现命令
调用任何方法前先查清楚再敲:
```
# 浏览命令组下的子命令与 flag
dws dev app member --help
# 查某方法的必填参数、类型、默认值
dws dev <command-path> --help
```
`--help` 输出构造 flag;不要凭旧 schema 名称猜参数。
@@ -0,0 +1,56 @@
# 权限管理
> 权限点 scopeValue 是授权单元,一个权限点授权一组 OpenAPIrequiredApproval=true 的变更走版本通道生效(见 [devapp.md](../devapp.md) 生效模型)。
查询、申请、取消开放平台应用的 APP 应用权限和 SNS 个人权限。参数用对应命令的 `--help` 查询。
## 权限列表
`--scope-value` 传入即进单权限详情模式;`--scope-type``APP`/`SNS`,留空返回两者;一个应用可能 150+ 权限点,游标分页续翻、`--page-size` 不超过 50。
`--auth-status` 是查询过滤条件:
| authStatus | 含义 |
|------------|------|
| `ALL` | 不按授权状态过滤 |
| `AUTHED` | 只看已授权/已开通 |
| `UNAUTHED` | 只看未授权/未开通 |
单个权限项的状态看这几个字段:
- `authed`(布尔):是否已授权/已开通。true=已开通,不要重复申请。
- `allowedActions`(数组):本权限点当前允许的动作,如 `["view","detail","apply"]`。含 `apply` 才能申请,含 `remove` 才能取消。
- `authedStatusDesc`(中文文案):状态的中文说明,如"已开通"/"未开通",直接展示给用户。
- `apiStatus`:权限点本身的开放状态,如 `FULLY_OPEN`
- `requiredApproval`(布尔):申请是否需审批。true 的变更走版本通道,审批在版本发布时处理。
- `displayMessage`(中文文案):服务端给的提示语,能否申请的原因看它。
list 默认同时返回 APP 和 SNS 权限;列表模式和 `--scope-value` 详情模式,权限项里的 API 信息字段都叫 `apiPreview``permission search``list` 的别名。
scopeValue 选择顺序:
1. 用户给了 `scopeValue`,精确匹配
2. 给了 API 名,用 `keyword` 搜,匹配 `apiPreview.name`
3. 给了权限名,匹配 `scopeName/scopeDesc`
4. 多个候选,展示列表让用户选,不自动取第一条
## 申请权限
`--scope-values``scopeValue`,多个逗号分隔,必须来自 `permission list` 返回。已开通跳过、不可编辑拒绝。`requiredApproval=true` 允许申请——写入版本变更,审批在版本发布时处理。不在此处选审批人。
## 取消权限
`--scope-values` 多个逗号分隔。返回:`removed`(布尔,整体成败)、`removedScopeValues`(成功取消的)、`rejectedScopeValues`(被拒的)、`message`。逐条看 `removedScopeValues`/`rejectedScopeValues` 判断每个权限点的结果。
## 发现命令
调用任何方法前先查清楚再敲:
```
# 浏览命令组下的子命令与 flag
dws dev app permission --help
# 查某方法的必填参数、类型、默认值
dws dev <command-path> --help
```
`--help` 输出构造 flag;不要凭旧 schema 名称猜参数。
@@ -0,0 +1,58 @@
# 端到端链路(recipes)
dev 的端到端任务都是「定ä½åº”用,改容器æŸèŠ‚ç‚¹ï¼ŒæŒ‰å®¡æ‰¹éœ€è¦èµ°ç‰ˆæœ¬ç”Ÿæ•ˆï¼Œæœ€åŽå›žè¯»éªŒè¯ã€ã€‚æ¯æ­¥å…ˆ `--dry-run` ç¡®è®¤å† `--yes`ï¼Œå‚æ•°ç”¨å¯¹åº”命令的 `--help` 查询,¼Œç»†èŠ‚è¿›å¯¹åº” reference。
## 建一个钉钉里打开的网页应用
1. `dev app create --name <å>` 建应用,拿 unifiedAppId
2. `dev app webapp config` é…移动端/PC é¦–é¡µï¼ˆè§ webapp.md)
3. `dev app version create` 建版本
4. `dev app version check-approval` 预检是å¦éœ€å®¡æ‰¹
5. `dev app version publish` å‘布(需审批时让用户选审批人)
6. 回读 `dev app version status` 到 `RELEASE` æ‰ç®—生效
## æƒé™ä»Žç”³è¯·åˆ°ç”Ÿæ•ˆ
1. `dev app permission list` 选 `scopeValue`(选择顺åºè§ permission.md)
2. `dev app permission add --scope-values <值>` 申请
3. 若是 `requiredApproval` çš„æƒé™ï¼Œèµ°ç‰ˆæœ¬ï¼š`version create`ï¼Œå† `check-approval`ï¼Œå† `publish --approver-user-id <用户选的>`ï¼Œæœ€åŽ `version status`
4. å…审æƒé™ç›´æŽ¥å¼€é€šï¼Œä¸å¿…å‘版本
## åšä¸€ä¸ªç­”疑机器人并接到本地调试
1. `dev app create --name <å>` 创建应用,拿明确 `unifiedAppId`
2. `dev app robot config --unified-app-id <unifiedAppId>` é…置机器人能力;需è¦å¯ç”¨æ—¶å† `robot enable`
3. 线上使用闭环:`version create` → `check-approval` → `publish` → `version status`ï¼›`SELECT_APPROVER` 时必须等用户选择审批人,ä¸é»˜è®¤å–第一个
4. 本地调试/值守:`dev connect --unified-app-id <unifiedAppId>` 把机器人接到本地 agentï¼ˆè§ connect.md);注æ„订阅事件å‰è¦å…ˆå»ºè”é•¿è¿žï¼ˆè§ event.md)
5. 若走无绑定的 `robot submit/result`ï¼Œåªæœ‰ç»“果返回明确 `unifiedAppId` æ‰èƒ½ç»§ç»­ç‰ˆæœ¬å‘布
6. å®Œæˆæ€ä¸Žç¼º `unifiedAppId`ã€`SELECT_APPROVER` 等门ç¦åˆ¤å®šè§ [devapp.md](../devapp.md)「核心规则ã€ï¼šå»ºè”æˆåŠŸ + 版本进入 `RELEASE`/`AUDIT`/`UNDER_REVIEW` æ‰ç®—完æˆ
## é‡å¯å®ˆæŠ¤è¿›ç¨‹è¿žæŽ¥å™¨ï¼ˆä¸å­˜å¯†é’¥ï¼‰
守护进程被 stop / kill / 崩溃åŽï¼Œé€šè¿‡æŒä¹…化的 `unifiedAppId` 釿–°æ‹‰å–密钥并é‡å¯ï¼Œæ— éœ€æœ¬åœ°ä¿å­˜ AppSecret。
1. `dev connect --daemon --unified-app-id <id> --channel <channel>` 首次å¯åŠ¨ï¼ˆ`unifiedAppId` å’Œ `channel` 会写入 `~/.dws/connect/<key>/daemon.pid`)
2. `dev connect restart --unified-app-id <id>` é‡å¯ï¼šè‡ªåЍ stop 旧进程 → 从 dev 平尿‹‰å– AppKey/Secret → 釿–°å»ºè
3. `dev connect status --unified-app-id <id>` 确认æ¢å¤ `healthy`
4. è‹¥ daemon.pid 未æŒä¹…化 `unifiedAppId`(如用 `--robot-client-id` 直接å¯åŠ¨çš„ï¼‰ï¼Œrestart 会æç¤ºæ”¹ç”¨ `--unified-app-id` å¯åЍ
注æ„:密钥ä¸è½ç›˜ï¼Œæ¯æ¬¡ restart 动æ€ä»Žå¼€å‘者平å°èŽ·å–ï¼›`daemon.pid` åªå­˜ `unifiedAppId`ã€`channel`ã€`clientId`(公开值)。
## 上传图片拿 mediaId(应用图标 / 机器人图标)
åº”ç”¨å›¾æ ‡ã€æœºå™¨äººå›¾æ ‡éƒ½é  mediaId 指定,但 dev 命令集ä¸å«ä¸Šä¼ â€”—mediaId è¦è°ƒé’‰é’‰ OpenAPI 拿到:
1. 拿凭è¯ï¼š`dev app credentials get --unified-app-id <id>` å `appKey/appSecret`(secret æŒ‰æ•æ„Ÿå¤„ç†ï¼Œä¸å†™è¿›å›žç­”)
2. æ¢ access_token:`GET https://oapi.dingtalk.com/gettoken?appkey=<appKey>&appsecret=<appSecret>`,å–返回的 `access_token`(有效期约 7200 ç§’ã€gettoken 有频控,缓存å¤ç”¨åˆ«æ¯æ¬¡ä¸Šä¼ éƒ½æ¢ï¼‰
3. 上传图片:`POST https://oapi.dingtalk.com/media/upload?access_token=<token>&type=image`,multipart 表å•字段å `media`,返回 `media_id`(形如 `@lA...`)。图标用方形图(如 256×256 çš„ jpg/png)
```
curl -F "media=@/path/to/img.png;type=image/png" \
"https://oapi.dingtalk.com/media/upload?access_token=<token>&type=image"
```
4. 用 mediaId 更新:机器人图标 `dev app robot config --unified-app-id <id> --icon-media-id <media_id>`;应用图标 `dev app update --unified-app-id <id> --icon-media-id <media_id>`。写æ“作先 `--dry-run` å† `--yes`
5. 回读:`dev app robot get` 看 `iconMediaId` å˜ä¸ºæ–°å€¼ï¼ˆåº”用图标看 `app get` çš„ `icon`)
## 查「为什么没生效 / 机器人æœä¸åˆ° / æƒé™åŠ äº†è¿˜æŠ¥é”™ã€
å…ˆ `dev app version status`——改é…ç½®ä¸ç­‰äºŽç”Ÿæ•ˆï¼Œæœªå‘到 `RELEASE` å°±ä¸ç”Ÿæ•ˆã€‚
@@ -0,0 +1,74 @@
# 机器人能力
> 机器人是应用的能力扩展之一;建号/配置在此,接到本地 agent 调试用 `dws dev connect`(见 connect.md)。
为开放平台企业内部应用创建和配置机器人。参数用对应命令的 `--help` 查询。分两类场景:
1. 新建智能体机器人:异步创建一个新的 Agent 应用 + 承载机器人(`submit` / `result`),当前不绑定已有开放平台应用。
2. 现有应用配置机器人:在已存在的应用上配置/启用/停用机器人(`get` / `config`(upsert) / `enable` / `disable`),用 `--unified-app-id` 定位。
> `corpId` / `userId` 由系统上下文自动注入,CLI 不传。所有写操作先 `--dry-run`,确认后再 `--yes`。
## 一、新建智能体机器人(异步建号)
`submit` 提交任务拿 `taskId``result --task-id <taskId>` 轮询。`submit` 返回 `taskId/status/expiresInSeconds/intervalSeconds/retryCount/bindsUnifiedApp`,提交成功通常是 `WAITING`,且 `bindsUnifiedApp=false` 表示异步建号任务不挂到现有应用。失败重试:把上次 `taskId` 通过 `--task-id` 传回 `submit`,避免重复创建。`result` 返回 `SUCCESS``APPROVAL_REQUIRED` 时可能带 `agentId/robotCode/clientId/clientSecret`;凭证可用于本地建联,但线上搜索、加群、路由消息必须等版本发布到 `RELEASE`
异步任务状态:
| status | 含义 | 下一步 |
|--------|------|--------|
| `WAITING` | 创建中 | 按 `intervalSeconds` 轮询 `robot result` |
| `SUCCESS` | 创建完成 | 保存 `robotCode/clientId/clientSecret`,凭据按敏感处理;若结果含明确 `unifiedAppId` 才继续版本发布,否则要求用户提供 |
| `APPROVAL_REQUIRED` | 已建号但线上使用需审核 | 不要重复建号;若结果含明确 `unifiedAppId` 才提交版本发布审核,否则要求用户提供 |
| `FAIL` | 创建失败 | 读 `errorCode/errorMsg/failReason`;可带原 `taskId` 重新 `submit` |
| `EXPIRED` | `taskId` 不存在或过期 | 重新 `submit` |
`robot result` 的 JSON 会额外补 `lifecycle``nextSteps`,用于把链路闭环到版本发布:
- 顶层 `completionState=BLOCKED_BY_VERSION_PUBLISH``mustContinue=true``terminal=false` 是硬门禁;看到它就继续执行 blocking `nextSteps`,不能把后续 `dev connect` 当完成。
- 顶层 `completionState=BLOCKED_BY_MISSING_UNIFIED_APP_ID``actionRequired=provide_unified_app_id` 时,说明缺少可安全写版本的应用主键;必须要求用户提供明确的 `unifiedAppId`,不能用 `clientId/appKey` 自动反查后继续写版本。
- 后续顺序是 `create_version``check_approval``publish_version``wait_release`。所有写操作仍先 `--dry-run`,确认后再 `--yes`
- `check-approval` 若返回 `approvalMode=SELECT_APPROVER`,展示候选审批人的 `name/userId/mainAdmin`,等待用户选择后再把该 `userId` 传给 `publish --approver-user-id`;不要默认取第一个。
- `connect_local` 的命令只用 `<clientSecret-from-result>` 占位,不能把真实 `clientSecret` 写进回答或脚本;它是 `optional=true` / `scope=local_debug_only`,不能抵消版本发布审核。
- `lifecycle.overallComplete=false` 或版本未进入 `RELEASE` / `AUDIT` / `UNDER_REVIEW` 时,不要总结“全部完成”“机器人已创建并成功连接”“可以在钉钉中 @机器人使用”。只能说“本地建联成功,线上发布/审批未完成”或继续执行阻塞步骤。
完成态门禁规则的完整说明见 [devapp.md](../devapp.md)「核心规则」。
## 二、现有应用的机器人配置
`robot get` 返回机器人基础信息、回调、模式、状态、技能列表;应用尚未配置机器人时返回空态 `robotStatus=UNCONFIGURED`,不是业务错误。
状态判断:
- `robotStatus=UNCONFIGURED`:应用未配置机器人,走 `robot config`
- `robotStatus=OFFLINE`:配置存在但停用/下线,可走 `robot enable`
- `robotStatus=ONLINE`:配置已启用;`robotCode` 可用于加群、机器人身份发消息或后续建联。
- `mode` 是字符串枚举:`HTTPS` / `STREAM` / `AISKILL`
- `robot get` 正常返回是平铺字段(`configured`/`mode`/`robotStatus`/`robotCode`/`name`/`brief`/`desc`),没有 `success` 字段;拿到这组字段就是配置已落库,不是异步等待态。
- ONLINE 只代表能力已开启。要让机器人自动处理消息,还需配 `--outgoing-url`/`--event-callback-url`,或用 `dev connect` 接本地 Agent(见 connect.md)。
`config` 是 upsert:建或改都用它,不存在则建、存在则改,至少给一个配置字段。国际化字段(`--i18n-name` 等)传 JSON,如 `'{"en_US":"Bot"}'``enable` 是纯启用:只开启能力,不带配置字段(只传 `--unified-app-id`)。`config/enable/disable` 成功统一返回 `success/operation/unifiedAppId/robotCode/robotStatus/configured`;回读 `robot get` 看到 `robotStatus=ONLINE` 就别再误判"待生效"。
## 错误处理
| 情况 | 处理 |
|------|------|
| `robotStatus=UNCONFIGURED` | 应用未配置机器人,先用 `robot config` 创建 |
| 应用名重复 | `app-name` 企业内需唯一,换名 |
| `ServiceResult.success=false` | 透传 `errorCode/errorMsg` |
| 创建任务 `EXPIRED` | 任务过期,重新 `submit`(可带原 taskId |
> 把机器人接到本地 agent 调试/值守见 [connect.md](connect.md)。
## 发现命令
调用任何方法前先查清楚再敲:
```
# 浏览命令组下的子命令与 flag
dws dev app robot --help
# 查某方法的必填参数、类型、默认值
dws dev <command-path> --help
```
`--help` 输出构造 flag;不要凭旧 schema 名称猜参数。
@@ -0,0 +1,21 @@
# 安全配置
> 安全配置=应用的 IP 白名单 / 登录重定向 / 端内免登 URL;见 [devapp.md](../devapp.md) 概念地图。
`dws dev app security config` 配 IP 白名单(`--ip-whitelist`)、登录重定向(`--redirect-urls`)、端内免登(`--sso-urls`)。参数用 `dws dev app security config --help` 查询;至少给一个配置字段。
覆盖语义:未提供的字段不动;显式提供的列表是整组覆盖(传入即全量替换该项,不是追加)——要保留旧值就把旧值一起带上。
## 发现命令
调用任何方法前先查清楚再敲:
```
# 浏览命令组下的子命令与 flag
dws dev app security --help
# 查某方法的必填参数、类型、默认值
dws dev <command-path> --help
```
`--help` 输出构造 flag;不要凭旧 schema 名称猜参数。
@@ -0,0 +1,99 @@
# 版本发布
> 版本是配置变更生效的唯一通道——改配置不等于上线,发布到 RELEASE 才生效(见 [devapp.md](../devapp.md) 生效模型)。
管理应用版本:基于当前配置建版本、查列表/详情、预检审批、发布、查状态。参数用对应命令的 `--help` 查询。版本用 `--unified-app-id` 定位,单个版本再加 `--version-id``corpId`/`userId` 系统注入,CLI 不传。
## 典型流程
```text
permission addrequiredApproval=true 写入版本变更)
→ version create 创建版本
→ version check-approval 预检是否需审批 / 审批人
→ version publish 发布(含高敏权限需 --confirmed-sensitive
→ version status 轮询发布/审批状态
```
新应用如果 `version list` 返回空,先 `version create`,用返回的 `versionId` 继续 check-approval/publish;不要因列表空误判无可发布内容。
## 创建版本
默认不要传 `--version`,不传时服务端基于最新已发布版本自动递增。只有用户明确要指定时才填 `--version`,且必须大于最新 `RELEASE` 版本,否则服务端返回 `62018`(版本号需高于上个版本号)。
创建成功后,后续 `get`/`check-approval`/`publish` 必须用 `create` 返回的 `versionId`;不要通过 `list` 猜最新版本。如果创建没返回 `versionId`,停止并报错。
## check-approval 与 publish
`check-approval` 只查审批要求和候选审批人,不发布。`publish` 是真实发布;含高敏权限要加 `--confirmed-sensitive`,灰度选人模式用 `--approver-user-id` 指定审批人。
区分两个"预检"`--dry-run` 是 CLI 层的预览不调上游;`check-approval` 是服务端查审批要求不发布。发布前建议先 `check-approval`
发布/预检不返回动作枚举 `result`,看结构化字段判断下一步:
| 字段 | 含义 | 下一步 |
|------|------|--------|
| `requiresApproval=false` + `publishable=true` | 不需审批,`check-approval` 通过 | 可以执行 `version publish` |
| `requiresApproval=true` + `approvalMode=SELECT_APPROVER` | 需从候选人里选审批人 | 展示 `approvalOptions[].label`,让用户选后再 `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` |
`SELECT_APPROVER` 时 CLI 会把原始 `approvalCandidates` 增强为更容易展示的字段:
- `approvalPromptText`:预渲染的成品选择文案(带 `A.`/`B.` 序号 + `姓名(userId: xxx`);agent 原样展示这一段即可,无需自己遍历结构。
- `approvalOptions[]`:结构化选项数组,字段包括 `label/name/userId/mainAdmin/index/key`,供需要结构化数据时使用。
- `completionState=WAITING_FOR_APPROVER_SELECTION``actionRequired=select_approver``mustAskUser=true`:必须等待用户选择,不能默认取第一个。
展示审批人时,优先原样展示 `approvalPromptText`;需结构化时用 `approvalOptions[].label`(格式 `姓名(userId: xxx``mainAdmin=true` 时标注“主管理员”,仅 `name` 为空才退回 `userId: xxx`)。发布时把用户选中的 `userId` 传给 `--approver-user-id`
审批模式 `approvalMode`
| approvalMode | 含义 | 下一步 |
|--------------|------|--------|
| `SELECT_APPROVER` | 灰度选人,需在候选审批人里选一个 | 展示候选,不自动取第一个 |
| `ENTERPRISE_SELF_BUILT` | 企业自建审核 | 不传 `--approver-user-id`,按企业自建流程等待 |
## 版本状态
`version create/list/get/status` 统一返回 `versionStatus`
| versionStatus | 含义 | 下一步 |
|---------------|------|--------|
| `INIT` | 已创建或有待发布变更,未发布 | 可 `check-approval`/`publish` |
| `AUDIT` | 发布审核中 | 不要重复发布;即使没返回 `processStatus` 也按审核中处理 |
| `RELEASE` | 已发布生效 | 完成,可验证权限/机器人/网页等能力 |
| `GRAY` | 灰度 | 按灰度流程,不要当全量已发布 |
`version status``processStatus` 只在存在审批流程且后端透出时有值。`versionStatus=AUDIT` 但没 `processStatus/processInstanceId` 时不要判失败,仍是审核中。
| processStatus | 含义 | 下一步 |
|---------------|------|--------|
| `UNDER_REVIEW` | 审批中 | 等待,必要时把 `processInstanceId` 给用户去钉钉客户端看 |
| `PASS` | 审批通过 | 继续回读,确认是否进 `RELEASE` |
| `FAIL` | 审批拒绝 | 展示 `processComment`,改后重新建/发版本 |
| `WITHDRAW` / `CANCEL` | 撤回或取消 | 回到发布前,重新 `check-approval`/`publish` |
| `PUBLISH_FAILED` | 审批后发布失败 | 展示错误,重查版本状态和后端错误 |
遇到未列出的状态值,不要猜语义;原样展示,回读 `version get/status` 或查文档/后台。
## 错误处理
| 情况 | 处理 |
|------|------|
| `check-approval` 提示需审批 | 按返回选审批人,再 `publish --approver-user-id` |
| 发布报高敏权限未确认 | 加 `--confirmed-sensitive` 重新发布 |
| `ServiceResult.success=false` | 透传 `errorCode/errorMsg` |
## 发现命令
调用任何方法前先查清楚再敲:
```
# 浏览命令组下的子命令与 flag
dws dev app version --help
# 查某方法的必填参数、类型、默认值
dws dev <command-path> --help
```
`--help` 输出构造 flag;不要凭旧 schema 名称猜参数。
@@ -0,0 +1,22 @@
# 网页应用配置
> 网页应用=应用的能力扩展之一,钉钉内打开的 H5;见 [devapp.md](../devapp.md) 概念地图。
`dws dev app webapp get` 查配置,`webapp config` 配移动端/PC 首页和管理后台地址。参数用 `dws dev app webapp get --help``dws dev app webapp config --help` 查询;config 至少给一个配置字段。
- 未配置网页应用前,`get` 返回空对象 `{}`。拿到 `{}` 就是还没配过,走 `webapp config` 首次配置。
- `h5PageType` 未显式传入时不要假设固定默认值;配置后以 `webapp get` 回读为准。
## 发现命令
调用任何方法前先查清楚再敲:
```
# 浏览命令组下的子命令与 flag
dws dev app webapp --help
# 查某方法的必填参数、类型、默认值
dws dev <command-path> --help
```
`--help` 输出构造 flag;不要凭旧 schema 名称猜参数。