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>
401 lines
26 KiB
Markdown
401 lines
26 KiB
Markdown
# 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` 决定是否重试。
|