Files
heicode/docs/integration/heicode-desktop-unified-api.md
T
chenchenandClaude Opus 4.8 0afd9cd764 docs(integration): update desktop-unified-api to the corrected sub-mode flow
The client API doc still carried the old flow; aligned it to
heicode-sub-mode-flow-spec.md (which it now references as the authority):
- Top banner: client is Claude-Code-like agentic; HM is the model gateway +
  control plane (no AI/exec); AM is the cloud runtime; sub = offload to cloud.
- display_status reframed: HM judges presence/anti-empty-shell, NOT code
  correctness; correctness = client (runs/tests) + user review (§0, §4).
- Deploy (§7) rewritten: client-executed + mandatory user confirmation; HM only
  hands the credential from Key Vault; the old Manager-side Deploy-Worker model
  is marked deprecated (also in §0.1 flow + §3 route table).
- New §8 Resource binding (git/vm/db/blob -> KV) with the /api/resources API and
  the pending V2 client-facing read/credential endpoints.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-03 11:36:54 +08:00

401 lines
26 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Heicode 桌面客户端统一接口对接文档(v0.1)
更新时间:2026-06-03
适用范围:Heicode Desktop 对接 Heicode Manager 的**统一接口层**(Sub Agile / Swarm 两模式)。端到端流程与三端职责以 [`heicode-sub-mode-flow-spec.md`](./heicode-sub-mode-flow-spec.md) 为准,本文是其接口层。
Manager 生产地址:`https://code.xinghanlab.com`
基准:统一调用方案 v0.1 + agent_management Sub Mode Runtime 对接指南。
> 本文取代旧的 `heicode-desktop-sub-agile-api.md`(其 `/api/agnet/*` 路由已废弃)。新客户端一律使用本文的 `/api/heicode/*` 与 `/api/agent/*` 接口。
> **⚠️ 2026-06-03 流程对齐(权威端到端流程见 [`heicode-sub-mode-flow-spec.md`](./heicode-sub-mode-flow-spec.md),本文是其接口层)。几处认知已纠正:**
> - **客户端是 Claude-Code 式智能体程序**(本地能跑 agent、执行命令、本地部署),不是无 AI 的壳;**HM = 模型网关(`/v1/*`)+ 控制面**,给客户端和 agent_management 两边都提供模型;**agent_management = 云端多 agent 运行时**。
> - **sub 模式 = 把多 agent 任务派到云端 agent_management 跑**(区别于客户端本地自己跑小任务)。
> - **`display_status` 只代表"跑完没/有没有真产物(防空壳)",不代表"代码对不对"**。代码对错 = **客户端(本地能跑能测)+ 用户 review** 判,**不是 HM 判**(HM 没 AI、不读代码)。详见 spec §6。
> - **部署 = 客户端执行 + 用户必须确认**;HM 只把凭据从 Key Vault 安全发给客户端,**HM 不部署、不连 VM**(见 §7,已改)。
> - **产物归宿是用户自己的 git 仓库**(github/gitea);先绑仓库(及 vm/db/blob)→ §8 资源绑定。
>
> **2026-06-02 更新(gap-analysis P0/P1 闭环并实跑验证)**:GET 无 body 设备签名(§1)、审批列表(§3.2)、`project_folder` 归一化(§5)、`/archive` 契约(§5)、`?path=`(§5)、revision accepted/applied(§6)、Swarm 同形(§3)。
## 0. 命名与总原则
- **命名统一**:`agnet` 是历史拼写错误,已全量改为 `agent`。所有新接口为 `/api/agent/*`、`/api/heicode/*`;旧 `/api/agnet/*` 已下线(404)。
- **客户端只调 Heicode Manager(HM)**:任务/控制走 `/api/heicode/*`、`/api/agent/*`,模型走 `/v1/*`;禁止直连 agent_management / HeiCode-Swarm / Runtime artifact 接口。
- **`display_status` 是 HM 的"进度/有无产物"裁决,不是"对错"裁决**:客户端用它判断"跑完没、是不是空交付"(防空壳),**但代码对不对由客户端自己拉下来跑/测 + 用户 review 决定**(HM 没 AI、不读代码、不编译)。
- **task_id ≡ deployment_id**:统一任务接口里 `{task_id}` 即 HM 的 `deployment_id`。
## 0.1 推荐完整流程
```text
0. (一次性) 绑定资源: github/gitea 仓库 + (可选) vm/db/blob -> 凭据进 Key Vault, 拿 resource_binding_id (§8)
1. GET /api/heicode/capabilities 取模式/模型,渲染模式与模型选择
2. POST /api/heicode/sub-agile/tasks (需求包) 创建任务 -> 响应里拿 deployment_id(=task_id)
3. (可选) POST .../tasks/{id}/execute 若需"先建后跑"或重试派发
4. 轮询 GET .../tasks/{id}/workflow (运行中 3-5s) 读 display_status + 三层状态 + agents (work 视图)
5. 出现 approval 时: GET .../approvals?status=pending -> approve/reject
6. display_status=completed 后看产物 (两条路):
(a) 直接 git clone/pull 用户自己的仓库 (完整工程+历史+diff)
(b) 经 HM: GET .../artifacts -> /manifest -> /files?path= -> /archive
7. 不满意 -> 改/重做 (两条路): POST .../messages|/execute 回云端改; 或 git pull 本地用客户端自己的 agent 改
8. (可选) 部署: 用户确认 -> 客户端凭 resource_binding_id 向 HM 取凭据(V2) -> 客户端本地 git/ssh/部署 (§7, HM 不代执行)
```
> 成功判据:`display_status=completed` 且 artifacts 非空 = **跑完且有真东西**;但**"代码对不对"客户端自己拉下来跑/测 + 用户 review**(`completed` 不保证正确,见 §4)。
## 1. 认证与加密(重要)
桌面客户端调用本文所有 `/api/heicode/*` 接口,**body 加密与签名和模型调用(`/v1/*`)完全一致**——直接复用同一套 `encryptedFetch` / V2 设备签名实现,无需新增协议。
| 方式 | 适用 |
|---|---|
| **V2 加密 body + 设备签名**(`Content-Encoding: heicode-aead-v1` + `X-Heicode-*`,ChaCha20-Poly1305 + Ed25519) | 有 body 的请求(POST/PUT/DELETE) |
| **V2 无 body 设备签名**(`X-Heicode-*` 签名头,**不带** `Content-Encoding`) | GET 等无 body 请求(fetch 规范禁止 GET 带 body) |
| Manager session cookie + `New-Api-User: <user_id>` | Web 控制台兼容路径(桌面端可不用) |
要点(与模型调用相同,未变):
- `aad` / `canonical` 里的 `path_with_query` 必须是 Manager 实际收到的 path,例如 `/api/heicode/sub-agile/tasks`,不含 origin。
- 每次新 `X-Heicode-Nonce` + 新 X25519 临时 key;签名里 `sha256_hex(plaintext_body)` 与加密前 JSON 字节一致。
- 服务端在新路由上用同一中间件 `UserOrV2DeviceAuth` 解密+验签,从设备绑定 token 解析用户身份;失败读响应头 `X-Heicode-Auth-Error`。
**GET 全程无 cookie 的设备签名(已支持)**:GET 没有 body,因此**不发** `Content-Encoding`、**不加密**,但仍发同一套签名头并用相同 canonical 公式签名,`sha256_hex(plaintext_body)` 处填**空 body 的哈希** `sha256("")`:
```text
canonical = method + "\n" # "GET"
+ path_with_query + "\n" # 例如 /api/heicode/sub-agile/tasks/dep_x/workflow
+ timestamp_ms + "\n"
+ nonce_hex + "\n"
+ device_fingerprint + "\n"
+ ephemeral_pubkey_b64 + "\n" # GET 仍生成一对临时 X25519 key 并带 X-Heicode-Eph-Pubkey
+ sha256_hex("") # e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855
signature = base64( ed25519_sign(device_priv, sha256(canonical)) )
```
即:GET 与写请求共用同一签名实现,区别仅是「不加密 body、body 哈希为空串哈希」。服务端凭 `X-Heicode-Signature` + `X-Heicode-Device-Id` + `X-Heicode-Eph-Pubkey` 三个头识别该模式(gap P0-1 已闭环)。客户端可用同一 `encryptedFetch`,对 GET 走「跳过加密、空 body」分支即可。
## 2. 能力发现
### `GET /api/heicode/capabilities`
无需登录,返回模式与模型目录。
```json
{ "success": true, "data": {
"modes": [
{"id":"sub_agile","name":"Sub Agile","runtime_kind":"agent_management","model_selection":"per_role","supports_roles":true,"supports_task_graph":false,"supports_artifacts":true,"supports_continue_chat":true,"enabled":true},
{"id":"swarm","name":"Swarm","runtime_kind":"heicode_swarm","model_selection":"primary","supports_roles":false,"supports_task_graph":true,"supports_artifacts":true,"supports_continue_chat":true,"enabled":false}
],
"models": [{"id":"gpt-5.4","name":"gpt-5.4","available":true}]
}}
```
`enabled` 反映该模式 Runtime 当前是否接通。
## 3. 统一任务接口
两套前缀,按模式选择:`/api/heicode/sub-agile/*`(→ agent_management)、`/api/heicode/swarm/*`(→ HeiCode-Swarm)。**下表以 sub-agile 为例,swarm 路径把前缀 `sub-agile` 换成 `swarm` 后完全同形(含 artifacts/files/archive/revisions/local-edits/approvals/deployments 全部子接口,gap P1-4)**——两组路由由同一注册函数生成,不存在「只实现了 sub-agile」的情况。
| 方法 | 路径 | 说明 |
|---|---|---|
| POST | `/api/heicode/sub-agile/tasks` | 创建任务(body = **需求包**,见 §3.1) |
| GET | `/api/heicode/sub-agile/tasks` | 任务列表 |
| GET | `/api/heicode/sub-agile/tasks/{task_id}` | 任务详情(含 `display_status`,读取时自动从 Runtime 收敛) |
| POST | `/api/heicode/sub-agile/tasks/{task_id}/messages` | 持续对话:追加用户消息 |
| POST | `/api/heicode/sub-agile/tasks/{task_id}/execute` | 触发/确保执行(未派发则派发 Runtime) |
| DELETE | `/api/heicode/sub-agile/tasks/{task_id}` | 删除/停止任务 |
| GET | `/api/heicode/sub-agile/tasks/{task_id}/workflow` | 工作流投影(右侧面板:mode/display_status/phases/agents(含 tokens/tools/elapsed/artifact_ids)/metrics/artifacts) |
| POST | `/api/heicode/sub-agile/tasks/{task_id}/stop` | 停止 |
| GET | `/api/heicode/sub-agile/tasks/{task_id}/timeline` | 时间线(events/callbacks/artifacts 聚合) |
| GET | `/api/heicode/sub-agile/tasks/{task_id}/logs` | 日志(含 `user_logs` 友好 + `debug_logs` 原始两层) |
| GET | `/api/heicode/sub-agile/tasks/{task_id}/events` | 事件 |
| GET | `/api/heicode/sub-agile/tasks/{task_id}/metrics` | 指标 |
| GET | `/api/heicode/sub-agile/tasks/{task_id}/diagnostics` | 诊断(直查 Runtime 状态 + warnings) |
| GET | `/api/heicode/sub-agile/tasks/{task_id}/sk-snapshots` | SK 快照 |
| GET | `/api/heicode/sub-agile/tasks/{task_id}/artifacts` | 产物列表(主产物归一化为 `project_folder`,见 §5) |
| GET | `/api/heicode/sub-agile/tasks/{task_id}/artifacts/{artifact_id}/manifest` | 项目文件树 |
| GET | `/api/heicode/sub-agile/tasks/{task_id}/artifacts/{artifact_id}/files?path=` | 单文件正文 |
| GET | `/api/heicode/sub-agile/tasks/{task_id}/artifacts/{artifact_id}/archive` | 整包 zip |
| GET | `/api/heicode/sub-agile/tasks/{task_id}/artifacts/{artifact_id}/revisions` | revision 列表 |
| POST | `/api/heicode/sub-agile/tasks/{task_id}/artifacts/{artifact_id}/local-edits` | 本地修改回传(批量 `.../local-edits/batch`) |
| GET | `/api/heicode/sub-agile/tasks/{task_id}/approvals?status=pending` | 审批列表(gap P0-2) |
| POST | `/api/heicode/sub-agile/tasks/{task_id}/approvals/{approval_id}/approve` | 同意审批 |
| POST | `/api/heicode/sub-agile/tasks/{task_id}/approvals/{approval_id}/reject` | 拒绝审批 |
| GET | `/api/heicode/sub-agile/tasks/{task_id}/deployments` · POST 同路径 | ⚠️ 旧的「Manager 代部署」控制面,**不再作为部署主路径**;新部署=客户端执行(见 §7) |
### 3.1 创建 body(需求包)
客户端提交**需求包**,不提交 Runtime 原始参数;Manager 内部翻译成 orchestration_plan 并创建。
```json
{
"mode": "sub_agile",
"client_role": "main_agent",
"conversation_id": "conv_xxx",
"requirement": {
"objective": "做一个 Oracle 云代理商网站,前后端分离",
"context": [], "attachments": [], "constraints": [], "acceptance_criteria": []
},
"model_selection": {
"type": "per_role",
"default_model": "gpt-5.4",
"roles": {"backend":"gpt-5.4","frontend":"gpt-5.4"}
},
"roles": ["backend", "frontend"]
}
```
- `model_selection.type`:`default`(全局 `default_model`)/ `per_role`(`roles` 映射)/ `primary`(Swarm 用 `primary_model`)。
- `conversation_id` 作为任务关联键(落到 `correlation_id`)。
- 模型必须用生产 NewAPI 已存在模型(当前推荐 `gpt-5.4`)。
- 兼容:也接受直接传 `{ "orchestration_plan": {...} }`(高级用法)。
`/messages` body:`{"message":"继续把测试补上","role":"user"}`。
`/execute` body 可空;用于"创建后再触发"或重试派发。
**创建响应**(`deployment_id` 即后续所有 `{task_id}`):
```json
{ "success": true, "message": "", "data": {
"deployment_id": "dep_b5fab27e9255",
"sub_mode": "agile",
"status": "accepted",
"display_status": "accepted",
"runtime_state": "runtime_syncing",
"runtime_swarm_id": "",
"agent_instances": [
{"instance_id":"agi_xxx","role":"backend","phase":"pending","runtime_state":"queued","failure_reason":""}
],
"permission_manifest": {"user_id":"22","binding_scope":"","resource_grants":[]}
}}
```
**任务详情** `GET .../tasks/{task_id}` 返回同形 + 完整 `orchestration_plan`;**任务列表** `GET .../tasks` 返回 `{"items":[ ...同上... ],"total":N}`。客户端按 `display_status` 渲染,详情/列表读取时 Manager 会自动从 Runtime 收敛终态。
### 3.2 审批列表(gap P0-2)
`GET .../tasks/{task_id}/approvals?status=pending`:
```json
{ "success": true, "data": {
"task_id":"dep_xxx","total":1,
"items":[
{"approval_id":"appr_xxx","deployment_id":"dep_xxx","type":"deploy","risk_level":"high",
"title":"生产部署审批","status":"pending","created_at":"2026-06-02T00:00:00Z"}
]
}}
```
`status` 省略则返回全部;对每条 `pending` 项调 `.../approvals/{approval_id}/approve|reject` 闭环。
## 4. 状态模型(display_status)
客户端只展示 `display_status`(Manager 裁决结果):
| display_status | 含义 |
|---|---|
| `accepted` / `running` / `waiting_approval` | 进行中 |
| `completed` | 跑完且**有真实产物**(非空、非兜底)—— 注意:**不代表代码正确** |
| `completed_without_deliverable` | Runtime 完成但无产物 |
| `needs_codegen` | 只有方案/总结,需继续生成代码 |
| `failed` / `stopped` | 终态 |
裁决规则:`completed` 且存在非兜底(`metadata.synthesized!=true` 且非纯总结)的真实 artifact → `completed`;否则降级为 `needs_codegen`(有总结)或 `completed_without_deliverable`(无产物)。
> ⚠️ **`display_status` 的边界(重要,见 spec §6.6)**:HM 只判**事实**——跑完没、有没有真产物、是不是空壳/兜底;**HM 不读代码、不编译、不测试,判不了"代码对不对"**。`completed` 只保证"有真东西",**不保证代码正确**。**对错由客户端自己拉产物跑/测 + 用户 review 决定**。若 agent_management 跑了测试,会作为 `test_report` 产物给客户端看,**不是给 HM 裁决**。
### 4.1 三层状态(`GET .../workflow`)
`GET /api/heicode/sub-agile/tasks/{task_id}/workflow` 返回客户端右侧面板需要的三层状态对象:
```json
{ "success": true, "data": {
"task_id":"dep_xxx","conversation_id":"conv_xxx",
"mode":"sub_agile", // 顶层模式 sub_agile|swarm(与 capabilities 一致)
"sub_mode":"agile", // sub_agile 内的节奏 agile|waterfall
"cloud_deployment_status":"completed", // Manager 控制面状态
"runtime_execution_status":"completed", // Runtime 上报状态
"display_status":"completed", // 唯一展示状态(客户端只看这个)
"last_synced_at":"2026-06-02T...",
"title":"...","summary":"...","phase":"development","agent_count":1,
"phases":[ // 阶段列表(runtime 未给细分时为当前阶段单条)
{"phase_id":"development","name":"development","status":"completed","agents":["backend"]}
],
"agents":[
{"agent_id":"agi_x","name":"backend","role":"backend","status":"completed",
"tokens":0,"tools":0,"elapsed_seconds":0,"artifact_ids":["art_x"]}
],
"artifacts":[{"artifact_id":"art_x","title":"...","artifact_type":"code_patch"}],
"artifact_ids":["art_x"],
"metrics":{"tokens_used":618,"tools":0,"elapsed_seconds":33,"total_messages":2},
"tokens":618,"tools":0,"elapsed_seconds":33
}}
```
字段说明(gap A/D/F):
- `mode`:顶层 `sub_agile|swarm`,与 `capabilities` 一致;列表 `GET .../tasks` 的每个 item 也带 `mode` 和 `display_status`,重启后从列表恢复任务不会把 swarm 当成 sub-agile。`sub_mode` 是 sub_agile 内部节奏。
- `phases[]`:始终是数组。当前 sub-mode runtime 只上报单个 `phase`,故默认是「当前阶段」一条;待 runtime 上报阶段细分后会展开。
- `metrics` / 顶层 `tokens/tools/elapsed_seconds`:来自 runtime 状态指标(`tokens_used`/`elapsed_seconds`/`total_messages` 为真实值)。
- 每个 agent 的 `tokens`/`tools`/`elapsed_seconds`:**待 agent_management 上报 per-agent 指标**,未上报时为 `0`;`artifact_ids` 按产物的 `source_agent_role` 归到对应 agent(runtime 未标注来源角色时为空)。
`client_task_status`(本地交互态)由客户端自己维护,不由 Manager 返回。
## 4b. 响应 envelope 与实时刷新
**成功**:`{ "success": true, "message": "", "data": {...} }`
**失败**:`{ "success": false, "message": "...", "error": { "code": "...", "message": "...", "retryable": false, "request_id": "..." } }`
`task_id` / `deployment_id` / `conversation_id` / `correlation_id` 在各接口的 `data` 内返回(无独立 `trace` 字段)。
**实时刷新**:当前**未提供 SSE**;客户端按统一方案 §17.3 兜底用**轮询**刷新:
- 运行中:每 3–5 秒 `GET .../workflow`(或 `.../timeline`);
- 终态:降到 15–30 秒或停止。
不要订阅 `.../events/stream`(未提供,会 404)。
## 5. 产物:项目文件夹
Runtime 返回单个文本/markdown 多文件 artifact;Manager 解析成项目文件树后提供。
**产物列表** `GET .../tasks/{task_id}/artifacts`:
```json
{ "success": true, "data": {
"deployment_id":"dep_xxx","task_id":"dep_xxx","total":1,
"items":[
{"artifact_id":"art_swm_xxx_backend_1","artifact_type":"code_patch","title":"backend task delivery",
"summary":"...","uri":"azblob://...",
"is_project":true,"display_artifact_type":"project_folder",
"manifest_path":".../artifacts/art_swm_xxx_backend_1/manifest",
"files_path":".../artifacts/art_swm_xxx_backend_1/files",
"archive_path":".../artifacts/art_swm_xxx_backend_1/archive",
"revisions_path":".../artifacts/art_swm_xxx_backend_1/revisions"}
]
}}
```
**主产物类型以 `display_artifact_type` 为准**(gap P0-3):结构性代码任务的主交付物 Manager 一律归一化为 `display_artifact_type:"project_folder"` 且 `is_project:true`,并直接给出 manifest/files/archive/revisions 子路径——客户端不用再靠原始 `artifact_type`(可能是 `code_patch`)猜它是不是完整项目。非交付物(纯摘要)`is_project:false`,`display_artifact_type` 回落为原始类型。
判断是否真实交付:`display_status=completed` 且 `is_project:true`(Manager 已裁决,客户端无需自判)。
| 方法 | 路径 | 说明 |
|---|---|---|
| GET | `.../artifacts/{artifact_id}/content` | 原始正文(兼容) |
| GET | `.../artifacts/{artifact_id}/manifest` | 项目文件树(entries[path,type,mime,size,hash,content_path] + archive) |
| GET | `.../artifacts/{artifact_id}/files?path=<相对路径>` | 单个文件正文(见下「路径编码」) |
| GET | `.../artifacts/{artifact_id}/archive` | 整个项目 zip 下载(见下「下载契约」) |
manifest 示例:
```json
{ "success": true, "data": {
"artifact_id":"art_xxx","artifact_type":"project_folder","root_dir":"project","revision":1,
"content_hash":"sha256:...","file_count":2,
"entries":[
{"path":"app.py","type":"file","mime_type":"text/x-python","size_bytes":420,"content_hash":"sha256:...","content_path":".../files?path=app.py"},
{"path":"frontend/src/App.tsx","type":"file","mime_type":"text/typescript","size_bytes":300,"content_hash":"sha256:...","content_path":".../files?path=frontend%2Fsrc%2FApp.tsx"}
],
"archive":{"format":"zip","download_path":".../archive"}
}}
```
**单文件「路径编码」(gap P1-1)**:用 `?path=` query 传相对路径,整体做一次 URL encode(含 `/`、空格、中文、`#`、`?` 等保留字符)。例:`GET .../files?path=frontend%2Fsrc%2FApp.tsx`。直接用 manifest 里给的 `content_path` 即可,无需自己拼。找不到文件返回 `{"error":{"code":"FILE_NOT_FOUND"}}`;缺 path 返回 `FILE_PATH_REQUIRED`。
**整包「下载契约」(gap P0-4)**:`GET .../archive` 成功时返回真实 zip 二进制,响应头:
```text
HTTP 200
Content-Type: application/zip
Content-Disposition: attachment; filename="art_xxx.zip"
Content-Length: <字节数>
```
客户端必须读 `Content-Disposition` 文件名、按 `Content-Length` 校验落盘,**保存成功后才提示「已下载」**。产物尚未就绪(无文件)时返回 `HTTP 200` body 为:
```json
{ "success": false, "error": { "code": "ARTIFACT_ARCHIVE_NOT_READY", "message": "项目压缩包尚未生成(产物还未就绪),请稍后重试。", "retryable": true } }
```
此时客户端**不要**提示已下载,按 `retryable` 稍后重试。
## 6. 本地修改回传(revision)
用户在本地改了产物后,必须显式上传;Manager 存为新 revision。
| 方法 | 路径 | 说明 |
|---|---|---|
| GET | `.../artifacts/{artifact_id}/revisions` | revision 列表 + `current_project_revision` |
| POST | `.../artifacts/{artifact_id}/local-edits` | 上传单文件修改 |
| POST | `.../artifacts/{artifact_id}/local-edits/batch` | 上传多文件修改(`changes[]`,op=update/create/delete) |
单文件上传请求:
```json
{ "base_artifact_id":"art_xxx","base_revision":1,"path":"frontend/src/App.tsx",
"content":"<html>...</html>","content_hash":"sha256:new","change_summary":"改了首页标题" }
```
批量请求:`{ "base_revision":1, "changes":[ {"op":"update","path":"a.py","content":"..."},{"op":"create","path":"b.py","content":"..."},{"op":"delete","path":"c.py"} ], "change_summary":"..." }`
成功响应:`{ "success":true,"data":{ "artifact_id":"art_xxx","revision":2,"project_revision":2,"source":"client_local_edit","status":"accepted","content_hash":"sha256:new" } }`
**revision 状态语义(gap P1-2)**:
| status | 含义 |
|---|---|
| `accepted` | 已被接受为**当前可用基线**(无冲突即直接接受,Manager 持有基线) |
| `applied` | 已被 Runtime 在后续 `/messages` 或 `/execute` 消费 |
| `conflict` | 与最新 revision 冲突(见下,不落库) |
**后续执行使用最新 accepted revision(gap P1-3)**:调用 `POST .../tasks/{id}/messages` 或 `.../execute` 时,Manager 自动取该任务最新 `accepted` revision 作为基线并置为 `applied`,响应回带 `active_project_revision`(无本地修改时不返回该字段)。客户端无需手动指定 revision。
冲突:若 `base_revision` 不等于当前最新,返回 `{ "success":false,"error":{"code":"ARTIFACT_REVISION_CONFLICT","retryable":false},"data":{"current_project_revision":3} }`,客户端提示用户先同步最新再提交。
## 7. 部署(✅ 客户端执行 + 用户必须确认;HM 只发凭据)
> ⚠️ **流程已改(对齐 spec §3.8)**:**HM 不部署、不连 VM、不执行命令**。部署由**客户端**在用户机器上执行(客户端是 Claude-Code 式程序),且**必须用户显式确认**。HM 的角色仅是:**把绑定的凭据从 Key Vault 解出 → 经 V2 加密通道安全发给客户端 → 记审计**。下面这套「Manager 代部署/Deploy Worker」是**旧模型,已废弃**。
**新流程:**
1. 用户先在 HM **绑定** git/vm/db/blob 资源(§8),拿到 `resource_binding_id`(凭据已进 Key Vault,只留 `secret_ref`)。
2. 客户端发起部署 → **弹窗让用户确认**(目标 VM / 环境 / 用哪条 binding / 影响范围)。
3. 用户确认 → 客户端向 HM 的**凭据读取接口**(V2 加密)凭 `resource_binding_id` 换取该绑定的凭据(经加密通道返回,带审计)。
4. **客户端在用户机器上执行**:`git pull` 用户仓库 → ssh 到 VM → 跑部署命令 → 在 VM 上看日志。**全程 HM 不碰 VM。**
> 🔴 接口待定:凭据读取接口(`GET /api/heicode/resources/{binding_id}/credential` 之类,V2 加密 + 审计)正在 HM 侧建。旧的 `/deployment-targets`、`POST .../deployments`(executor:pending_worker)**不再作为部署主路径**。
## 8. 资源绑定(git/vm/db/blob → Key Vault)
用户先把自己的资源绑定到 HM,凭据进 **Azure Key Vault**,库里只留 `secret_ref`;任务/部署只引用 `resource_binding_id`,**绝不传明文凭据**。详见 spec §4。
**5 种类型**:`git`(github/gitea,用 `provider` 区分,即用户**自己的仓库** URL + PAT)、`vm`(ssh)、`database`、`blob`。git 仓库是产物归宿(agent 在此 commit/合并,客户端 clone/pull)。
**后端接口**(当前 `UserAuth` 会话鉴权,Manager 网页控制台「资源绑定」页已用;🔴 **面向桌面客户端的 V2 设备签名版正在建**——见下):
| 方法 | 路径 | 说明 |
|---|---|---|
| GET | `/api/resources/?status=active` | 列出已绑资源(给客户端选 `resource_binding_id`) |
| POST | `/api/resources/` | 建绑定(非密字段进 metadata,不含凭据) |
| POST | `/api/resources/{id}/secret` | 写凭据 → Key Vault,返回 `secret_ref` |
| DELETE | `/api/resources/{id}` | 解绑(吊销) |
建绑定示例(github):`{"name":"my-repo","resource_type":"git","provider":"github","external_id":"https://github.com/owner/repo","metadata":{"repo_url":"...","default_branch":"main"}}` → 再 `POST /{id}/secret {"data":{"token":"ghp_..."}}`。
> 🔴 **待建(HM 侧地基)**:① 客户端用 **V2 设备签名** 拉资源列表(`/api/heicode/resources` 之类) → 让用户/任务选 binding;② 客户端凭 `resource_binding_id` **取凭据**(部署用,§7)。这两条上线后,客户端即可"读绑定去用 + 取凭据部署"。当前 `/api/resources/*` 是会话鉴权(网页控制台用)。
## 9. Runtime 回调(仅 Runtime 用,客户端无需关心)
agent_management → HM 统一回调:`POST /api/agent/callbacks/runtime-events`。
Schema 查询:`GET /api/agent/callbacks/runtime-events/schema`。
## 10. 客户端生产约束
- 审批 approve/reject 必须调 HM,禁止本地伪造。
- 生产包禁用 mock 任务/审批/产物。
- 用 HM 的 `display_status` 判**进度/有无产物**;**代码对错客户端自己跑/测 + 用户 review**(见 §4)。
- 部署只在**用户确认后由客户端本地执行**;凭据凭 `resource_binding_id` 向 HM 取(§7),**不本地存明文凭据**。
- 普通用户文案不出现 `newapi`、上游厂商、模型网关字眼。
- 任务接口认证:写请求用 V2 加密 body + 设备签名,GET 用 V2 无 body 设备签名(见 §1),均复用模型调用同一套实现;base_url 来自 preset。
## 11. 错误码(新增)
| code | 场景 | retryable |
|---|---|---|
| `ARTIFACT_REVISION_CONFLICT` | 本地修改基于旧版本(`data.current_project_revision` 给出最新版本) | false |
| `ARTIFACT_ARCHIVE_NOT_READY` | zip 产物尚未就绪(无文件),稍后重试 | true |
| `ARTIFACT_ARCHIVE_FAILED` | 打包失败 | false |
| `RESOURCE_BINDING_INVALID` | resource_binding_id 不存在或非本人 | false |
| `DEPLOY_TARGET_DISABLED` | 云目标未启用 | false |
| `FILE_NOT_FOUND` | 项目文件不存在 | false |
| `FILE_PATH_REQUIRED` | 取单文件未带 `?path=` | false |
其余错误码沿用旧文档 §13。所有错误统一形如 `{"success":false,"error":{"code","message","retryable"}}`,可读 `error.retryable` 决定是否重试。