diff --git a/docs/heicode-manager-standalone-execution-plan.md b/docs/heicode-manager-standalone-execution-plan.md index 05138d0c..46d7eeca 100644 --- a/docs/heicode-manager-standalone-execution-plan.md +++ b/docs/heicode-manager-standalone-execution-plan.md @@ -1,6 +1,6 @@ # Heicode Manager 可独立执行任务计划 -更新时间:2026-05-26 +更新时间:2026-05-27 负责人范围:Heicode Manager 端 用途:后续开发按本文逐项执行、验收和更新状态。 @@ -46,6 +46,7 @@ | 11 | 前端页面补齐独立闭环展示 | P1 | 是 | 用户能看到任务、deployment、事件、artifact、审批和审计关系 | | 12 | 文档口径清理 | P2 | 是 | 避免旧 Vault/OpenBao/Secret Provider 表述误导 | | 13 | AWS/GCP 占位提示 | P2 | 是 | 避免用户误以为 AWS/GCP 已可用 | +| 14 | 请求 body 加密策略确认 | P2 | 是 | 明确 HTTPS 传输加密、应用层 body 加密是否需要、敏感字段禁止入参和日志 | ## 四、任务明细 @@ -252,6 +253,24 @@ - 禁用项不能提交到后端创建真实资源发现。 - 已有 Azure 流程不受影响。 +### 任务 14:请求 body 加密策略确认 + +| 项 | 内容 | +|---|---| +| 目标 | 明确桌面客户端调用 Manager 时,请求体当前依赖 HTTPS/TLS 传输加密;是否需要额外应用层 body 加密需单独评估,不默认混入 sub 敏捷接口 | +| 修改文件 | `docs/integration/heicode-desktop-sub-agile-api.md`、`docs/heicode-runtime-auth-newapi-secret-design.md`、必要时补充 `heicode/middleware` 或客户端 SDK 加解密模块 | +| 当前现实 | 生产入口应使用 `https://code.xinghanlab.com`;JSON body 在网络传输中由 TLS 加密,到 Manager 后端后按普通 JSON 解析 | +| 安全红线 | 请求体、回调、日志、artifact metadata、timeline 中禁止出现明文密码、Token、私钥、连接串、云 access key、模型 key;长期凭证只传 `secret_ref` | +| 后续判断 | 如有合规要求再设计应用层加密,例如请求体 AES-GCM + 短期会话密钥/设备公钥;同时要设计密钥分发、轮换、重放保护、版本兼容和排障方案 | +| 测试 | 检查生产 API 只走 HTTPS;敏感字段扫描;单测覆盖疑似明文凭据拒绝;如实现应用层加密,再补加解密兼容测试 | + +验收标准: + +- 文档明确“HTTPS 传输加密已覆盖 body 在网络中的保密性”,不误导为当前已有业务层 body 加密。 +- 客户端对接文档明确禁止传明文长期凭据,只允许 `secret_ref` / `lease://...` / 资源元数据。 +- 服务端日志不打印请求 body 中的敏感字段。 +- 如果后续实现应用层 body 加密,必须保留版本字段和灰度开关,避免旧客户端直接不可用。 + ## 五、推荐执行批次 ### 批次 A:最小 Manager 闭环 @@ -285,6 +304,7 @@ | 3 | 前端闭环展示 | | 4 | 文档口径清理 | | 5 | AWS/GCP 占位提示 | +| 6 | 请求 body 加密策略确认 | 完成批次 C 后,Manager 端应具备清晰的任务视角、持久化上下文、准确页面口径和更少误导。 @@ -333,6 +353,7 @@ Manager 独立任务完成,不等于蜂群生产闭环完成。本文完成的 | 幂等 | 重复回调不重复写入 | | 审计 | task/deployment/correlation_id 下能聚合审计、审批、artifact、callback | | 安全 | API、日志、页面不出现明文长期密钥 | +| 请求加密 | 文档明确当前依赖 HTTPS/TLS 传输加密;应用层 body 加密如需实现,有清晰设计和兼容策略 | | 口径 | 页面和文档不把本地占位/模拟事件说成真实 Runtime | ## 八、执行时不能突破的边界 @@ -344,4 +365,4 @@ Manager 独立任务完成,不等于蜂群生产闭环完成。本文完成的 5. 不把本地模拟事件当真实蜂群完成证据。 6. 不把 Azure Key Vault 暴露成普通用户要直接操作的后台。 7. 不绕过客户端高危审批的产品边界;Manager 只能先提供记录和 API。 - +8. 不在未完成密钥协商、轮换和兼容设计前,临时硬塞应用层 body 加密,避免客户端和 Manager 联调不可排障。 diff --git a/docs/integration/heicode-desktop-sub-agile-api.md b/docs/integration/heicode-desktop-sub-agile-api.md new file mode 100644 index 00000000..8573c7bb --- /dev/null +++ b/docs/integration/heicode-desktop-sub-agile-api.md @@ -0,0 +1,1010 @@ +# Heicode 桌面客户端 sub 敏捷流程 API 对接文档 + +更新时间:2026-05-27 +适用范围:Heicode Desktop / 本地服务对接 Heicode Manager,跑通普通 sub 模式敏捷开发流程。 +Manager 生产地址:`https://code.xinghanlab.com` + +## 1. 对接目标 + +桌面客户端负责用户主体验:输入想法、回答追问、持续推进任务、查看子环节反馈、处理高危审批、接收交付结果。 + +Manager 负责辅助控制面:任务草稿桥接、资源/权限、Agnet deployment、状态/timeline/artifact/SK 查询、审批记录和短期凭证 lease。 + +本文只描述普通 sub 敏捷流程,不包含蜂群 task graph、claim、heartbeat、handoff 等蜂群模式能力。 + +## 2. 认证与公共约定 + +### 2.1 API 分组 + +| API 前缀 | 用途 | 认证 | +|---|---|---| +| `/api/heicode-auth/api/user/tasks/*` | HeicodeTask 任务编排代理,创建任务、追问、查询任务 | `Authorization: Bearer ` | +| `/api/agnet/user/*` | Manager 用户态 Agnet 控制面,deployment、timeline、artifact、审批 | Manager 登录 session cookie + `New-Api-User: ` | +| `/api/user/self` | 查询当前 Manager 用户 | Manager 登录 session cookie | + +### 2.2 Manager 用户态 Header + +调用 `/api/agnet/user/*` 时必须带: + +```http +Cookie: session= +New-Api-User: +Content-Type: application/json +Accept: application/json +``` + +`New-Api-User` 必须等于当前登录用户 ID,否则会返回未授权。 + +### 2.3 统一响应 Envelope + +成功: + +```json +{ + "success": true, + "message": "", + "data": {} +} +``` + +失败: + +```json +{ + "success": false, + "message": "human readable message", + "error": { + "code": "ERROR_CODE", + "message": "human readable message" + } +} +``` + +## 3. 推荐完整流程 + +```text +1. 桌面端确认 Manager 登录态,获取 /api/user/self +2. 创建 HeicodeTask:POST /api/heicode-auth/api/user/tasks/intent +3. 如果 status=configuring,回答追问:POST /api/heicode-auth/api/user/tasks/{task_id}/answer +4. 当任务卡生成后,创建 deployment draft:POST /api/agnet/user/tasks/{task_id}/deployment-draft +5. 创建 Manager deployment:POST /api/agnet/user/deployments +6. 轮询 deployment detail / events / timeline +7. 展示 artifacts / sk-snapshots / logs / metrics +8. 如出现 approval,桌面端展示审批并调用 approve/reject +9. 完成后继续迭代或停止 deployment +``` + +## 4. 当前用户信息 + +### `GET /api/user/self` + +获取当前 Manager 登录用户,用于拿 `id` 并设置 `New-Api-User`。 + +响应关键字段: + +```json +{ + "success": true, + "data": { + "id": 22, + "username": "chenchen", + "email": "", + "group": "default", + "role": 1, + "status": 1 + } +} +``` + +客户端处理: + +- 保存 `data.id`。 +- 后续 `/api/agnet/user/*` 请求带 `New-Api-User: `。 + +## 5. HeicodeTask 任务编排 + +这些接口通过 Manager 同源代理访问 mcp-server: + +```text +Base: /api/heicode-auth +``` + +### 5.1 创建任务 + +#### `POST /api/heicode-auth/api/user/tasks/intent` + +描述用户想做什么,创建 HeicodeTask。 + +请求: + +```json +{ + "intent": "做一个客户工单管理系统,支持登录、工单列表、状态流转和后台统计", + "name": "客户工单管理系统" +} +``` + +字段: + +| 字段 | 类型 | 必需 | 说明 | +|---|---|---:|---| +| `intent` | string | 是 | 用户自然语言目标 | +| `name` | string | 否 | 任务名称,不传则由服务端生成 | + +响应: + +```json +{ + "success": true, + "data": { + "id": "task_abc123", + "user_id": "22", + "name": "客户工单管理系统", + "status": "configuring", + "status_caption": "需要补充几个问题", + "intent": "做一个客户工单管理系统...", + "thread": [ + { + "kind": "user", + "text": "做一个客户工单管理系统...", + "at": 1779850000000 + }, + { + "kind": "heicode", + "text": "请选择第一版范围", + "at": 1779850001000, + "followups": [ + { + "id": "scope", + "question": "第一版优先做什么?", + "options": [ + { "id": "mvp", "label": "MVP 基础功能" } + ] + } + ] + } + ], + "card": null, + "created_at": 1779850000000, + "updated_at": 1779850001000 + } +} +``` + +### 5.2 查询任务列表 + +#### `GET /api/heicode-auth/api/user/tasks?status=running&limit=20&offset=0` + +查询当前用户任务。 + +查询参数: + +| 参数 | 类型 | 必需 | 说明 | +|---|---|---:|---| +| `status` | string | 否 | `draft/configuring/running/awaiting_approval/completed/failed/paused` | +| `limit` | number | 否 | 默认由服务端决定 | +| `offset` | number | 否 | 分页偏移 | + +响应: + +```json +{ + "success": true, + "data": { + "items": [], + "total": 0, + "offset": 0, + "limit": 20 + } +} +``` + +### 5.3 查询任务详情 + +#### `GET /api/heicode-auth/api/user/tasks/{task_id}` + +响应同 HeicodeTask。 + +### 5.4 回答追问 + +#### `POST /api/heicode-auth/api/user/tasks/{task_id}/answer` + +请求: + +```json +{ + "question_id": "scope", + "option_id": "mvp" +} +``` + +响应: + +```json +{ + "success": true, + "data": { + "id": "task_abc123", + "status": "running", + "card": { + "goal": "客户工单管理系统第一版", + "scope": ["登录", "工单列表", "状态流转"], + "generated_artifacts": ["产品说明", "接口草案", "开发任务"], + "manager_actions": [ + { + "label": "绑定 Git", + "deeplink": "/sk-sources" + } + ] + } + } +} +``` + +客户端处理: + +- `status=configuring`:继续展示 `thread[].followups`。 +- `status=running` 且 `card` 存在:允许用户创建 Manager deployment。 +- `status=awaiting_approval`:轮询审批接口。 +- `status=completed/failed/paused`:停止高频轮询。 + +## 6. 从任务生成 Agnet Deployment Draft + +### `POST /api/agnet/user/tasks/{task_id}/deployment-draft` + +把 HeicodeTask 快照转换成 Manager 可创建的 Agnet orchestration plan。 + +请求: + +```json +{ + "task": { + "id": "task_abc123", + "name": "客户工单管理系统", + "intent": "做一个客户工单管理系统...", + "status": "running", + "card": { + "goal": "客户工单管理系统第一版", + "scope": ["登录", "工单列表", "状态流转"], + "generated_artifacts": ["产品说明", "接口草案", "开发任务"] + } + }, + "sub_mode": "agile", + "binding_scope": "task-task_abc123", + "role_templates": ["backend", "frontend", "reviewer"], + "default_model_id": "agnet-model-builder", + "budget": { + "max_tokens": 120000, + "max_cost_usd": 8, + "max_duration_sec": 3600 + }, + "resource_grants": [] +} +``` + +字段: + +| 字段 | 类型 | 必需 | 说明 | +|---|---|---:|---| +| `task.id` | string | 是 | 必须和 URL `{task_id}` 一致 | +| `task.name` | string | 否 | 任务名称 | +| `task.intent` | string | 否 | 用户原始目标 | +| `task.card.goal` | string | 否 | 优先作为 objective | +| `sub_mode` | string | 是 | `agile` 或 `waterfall`,桌面客户端默认传 `agile` | +| `binding_scope` | string | 否 | 建议 `task-` | +| `role_templates` | string[] | 否 | 默认 `["backend"]` | +| `default_model_id` | string | 否 | 子 Agnet 默认运行模型 | +| `budget` | object | 否 | 不传使用默认预算 | +| `resource_grants` | array | 否 | 不传时 Manager 生成只读 task context grant | + +响应: + +```json +{ + "success": true, + "data": { + "task_id": "task_abc123", + "orchestration_plan": { + "intent_id": "task_abc123", + "template_hint": "heicode-task", + "objective": "客户工单管理系统第一版", + "sub_mode": "agile", + "risk_level": "low", + "budget": { + "max_tokens": 120000, + "max_cost_usd": 8, + "max_duration_sec": 3600 + }, + "user_context": { + "user_id": "22", + "role": "user", + "channel_id": "default" + }, + "billing_context": { + "provider": "newapi", + "newapi_group": "default" + }, + "agent_runtime": { + "platform": "agnet", + "agents": [ + { + "role": "backend", + "model_ref": "agnet-model-builder", + "instance_count": 1 + } + ] + }, + "agents": [ + { + "role_template": "backend", + "goal": "Execute the Heicode task as backend within the approved resource scope.", + "default_model_id": "agnet-model-builder", + "resource_grants": [] + } + ], + "constraints": { + "allowed_model_ids": [] + }, + "metadata": { + "correlation_id": "task-task_abc123-xxxxxxxx" + } + } + } +} +``` + +## 7. 创建 Manager Deployment + +### `POST /api/agnet/user/deployments` + +使用上一步 `orchestration_plan` 创建 Manager deployment。 + +请求: + +```json +{ + "orchestration_plan": { + "intent_id": "task_abc123", + "template_hint": "heicode-task", + "objective": "客户工单管理系统第一版", + "sub_mode": "agile", + "risk_level": "low", + "budget": { + "max_tokens": 120000, + "max_cost_usd": 8, + "max_duration_sec": 3600 + }, + "user_context": { + "user_id": "22", + "role": "user", + "channel_id": "default" + }, + "billing_context": { + "provider": "newapi", + "newapi_group": "default" + }, + "agent_runtime": { + "platform": "agnet", + "agents": [ + { + "role": "backend", + "model_ref": "agnet-model-builder", + "instance_count": 1 + } + ] + }, + "agents": [ + { + "role_template": "backend", + "goal": "Execute the Heicode task as backend within the approved resource scope.", + "default_model_id": "agnet-model-builder", + "resource_grants": [] + } + ], + "constraints": { + "allowed_model_ids": [] + }, + "metadata": { + "correlation_id": "task-task_abc123-xxxxxxxx" + } + } +} +``` + +响应: + +```json +{ + "success": true, + "data": { + "deployment_id": "dep_464a08b7b751", + "sub_mode": "agile", + "status": "accepted", + "phase": "pending", + "runtime_state": "queued", + "runtime_deployment_id": "", + "runtime_swarm_id": "", + "failure_reason": "", + "agent_instances": [ + { + "instance_id": "agi_1fddac9cded5", + "role": "backend", + "phase": "pending", + "runtime_state": "queued", + "failure_reason": "" + } + ], + "permission_manifest": { + "user_id": "22", + "binding_scope": "task-task_abc123", + "agent_role": "backend", + "target_agent_ref": "agent-backend-1", + "resource_grants": [] + } + } +} +``` + +客户端处理: + +- 保存 `deployment_id`。 +- `runtime_state=queued` 表示 Manager 已建立本地控制面记录。 +- 如果生产 Runtime 未配置,deployment 仍可创建,但不会进入真实执行。 + +## 8. 查询 Deployment + +### 8.1 列表 + +#### `GET /api/agnet/user/deployments` + +响应: + +```json +{ + "success": true, + "data": { + "items": [ + { + "deployment_id": "dep_464a08b7b751", + "sub_mode": "agile", + "status": "accepted", + "phase": "pending", + "runtime_state": "queued", + "failure_reason": "", + "created_at": "2026-05-27T11:44:30+08:00", + "updated_at": "2026-05-27T11:44:30+08:00", + "permission_manifest": {}, + "orchestration_plan": {} + } + ] + } +} +``` + +### 8.2 详情 + +#### `GET /api/agnet/user/deployments/{deployment_id}` + +响应字段同列表单项,包含完整 `orchestration_plan`。 + +### 8.3 停止 + +#### `POST /api/agnet/user/deployments/{deployment_id}/stop` + +请求: + +```json +{ + "reason": "用户停止本轮 sub 敏捷任务" +} +``` + +响应: + +```json +{ + "success": true, + "data": { + "deployment_id": "dep_464a08b7b751", + "status": "stopped", + "phase": "stopped" + } +} +``` + +## 9. 执行反馈查询 + +### 9.1 Events + +#### `GET /api/agnet/user/deployments/{deployment_id}/events` + +用于展示 deployment 事件流。 + +响应: + +```json +{ + "success": true, + "data": { + "items": [ + { + "event_id": "evt_xxx", + "event": "deployment.accepted", + "deployment_id": "dep_464a08b7b751", + "occurred_at": "2026-05-27T11:44:30+08:00", + "result": "ok" + } + ] + } +} +``` + +### 9.2 Logs + +#### `GET /api/agnet/user/deployments/{deployment_id}/logs` + +用于展示最近日志。当前 Manager 未接真实 Runtime 时主要是审计日志。 + +响应: + +```json +{ + "success": true, + "data": { + "items": [ + { + "timestamp": "2026-05-27T11:44:30+08:00", + "level": "info", + "message": "deployment accepted", + "source": "manager-audit" + } + ] + } +} +``` + +### 9.3 Metrics + +#### `GET /api/agnet/user/deployments/{deployment_id}/metrics` + +用于展示成本、耗时、token、资源指标。真实 Runtime 未接入时可能为空或为本地占位。 + +响应: + +```json +{ + "success": true, + "data": { + "deployment_id": "dep_464a08b7b751", + "runtime_state": "queued", + "metrics": {} + } +} +``` + +### 9.4 Artifacts + +#### `GET /api/agnet/user/deployments/{deployment_id}/artifacts` + +查询中间交付物和最终交付物摘要。 + +响应: + +```json +{ + "success": true, + "data": { + "deployment_id": "dep_464a08b7b751", + "artifacts": [ + { + "artifact_id": "art_test_report_001", + "artifact_type": "test_report", + "title": "测试报告", + "summary": "本轮后端接口测试通过", + "uri": "artifact://task_abc123/test-report/art_test_report_001", + "created_at": 1779850000000 + } + ], + "items": [], + "total": 1 + } +} +``` + +### 9.5 SK Snapshots + +#### `GET /api/agnet/user/deployments/{deployment_id}/sk-snapshots` + +查询本轮任务使用的 SK 快照。 + +响应: + +```json +{ + "success": true, + "data": { + "deployment_id": "dep_464a08b7b751", + "sk_snapshots": [ + { + "snapshot_id": "sks_task_abc123_backend_001", + "deployment_id": "dep_464a08b7b751", + "agent_role": "backend", + "source_type": "git", + "source_ref": "git:https://example.com/tools.git#main:backend", + "content_hash": "sha256:abc123", + "tool_name": "repo_write", + "created_at": "2026-05-27T11:45:00+08:00", + "metadata": { + "redacted": true + } + } + ], + "items": [], + "total": 1 + } +} +``` + +### 9.6 Timeline + +#### `GET /api/agnet/user/deployments/{deployment_id}/timeline` + +聚合审计事件、Runtime callback、artifact、SK snapshot。桌面客户端推荐优先使用这个接口渲染“当前子环节进度”。 + +响应: + +```json +{ + "success": true, + "data": { + "deployment_id": "dep_464a08b7b751", + "deployment": { + "deployment_id": "dep_464a08b7b751", + "sub_mode": "agile", + "status": "accepted", + "phase": "pending", + "runtime_state": "queued" + }, + "events": [], + "callbacks": [], + "artifacts": [], + "sk_snapshots": [], + "timeline": [ + { + "kind": "audit", + "at": 1779850000000, + "event": "deployment.accepted" + }, + { + "kind": "callback", + "at": "2026-05-27T11:45:00+08:00", + "event": "phase.changed", + "event_id": "evt_phase_001", + "event_type": "phase.changed" + }, + { + "kind": "artifact", + "at": 1779850060000, + "event": "test_report", + "artifact_id": "art_test_report_001" + } + ] + } +} +``` + +客户端建议: + +- 轮询间隔:运行中 3-5 秒;终态 15-30 秒或停止。 +- 优先展示 `timeline[]`。 +- 如果存在 `artifact`,提供“查看交付物”入口。 +- 如果存在 `callback.event_type=approval.requested` 或任务状态 `awaiting_approval`,拉取审批接口。 + +## 10. 审批与短期凭证 + +高危操作审批必须由桌面客户端作为主体验展示。Manager 只提供记录、approve/reject 和 lease。 + +### 10.1 查询待审批 + +#### `GET /api/agnet/approvals?status=pending&deployment_id={deployment_id}` + +查询参数: + +| 参数 | 类型 | 必需 | 说明 | +|---|---|---:|---| +| `status` | string | 否 | `pending/approved/rejected/expired` | +| `deployment_id` | string | 否 | 按 deployment 过滤 | + +响应: + +```json +{ + "success": true, + "data": { + "items": [ + { + "approval_id": "appr_xxx", + "user_id": 22, + "deployment_id": "dep_464a08b7b751", + "binding_scope": "task-task_abc123", + "operation": "git.write", + "resource_id": "res_git_main", + "resource_type": "git", + "resource_scope": "feature/*", + "target_role": "backend", + "risk_level": "high", + "requires_credential": true, + "credential_lease_id": "", + "status": "pending", + "requested_by": "agnet-runtime", + "request_reason": "需要写入功能分支", + "ttl_seconds": 900, + "expires_at": 1779850900000, + "created_at": 1779850000000 + } + ] + } +} +``` + +### 10.2 同意审批 + +#### `POST /api/agnet/approvals/{approval_id}/approve` + +请求: + +```json +{ + "reason": "用户确认允许本轮任务写入功能分支" +} +``` + +响应: + +```json +{ + "success": true, + "data": { + "approval_id": "appr_xxx", + "status": "approved", + "credential_lease_id": "lease_xxx", + "credential_lease": { + "lease_id": "lease_xxx", + "credential_ref": "lease://agnet/lease_xxx", + "status": "active", + "expires_at": 1779850900000 + } + } +} +``` + +注意:响应只会返回 `lease://...` 引用,不返回明文凭证。 + +### 10.3 拒绝审批 + +#### `POST /api/agnet/approvals/{approval_id}/reject` + +请求: + +```json +{ + "reason": "用户拒绝生产环境写入" +} +``` + +响应: + +```json +{ + "success": true, + "data": { + "approval_id": "appr_xxx", + "status": "rejected", + "decision_reason": "用户拒绝生产环境写入" + } +} +``` + +### 10.4 查询 lease + +#### `GET /api/agnet/credential-leases?status=active&deployment_id={deployment_id}` + +响应: + +```json +{ + "success": true, + "data": { + "items": [ + { + "lease_id": "lease_xxx", + "credential_ref": "lease://agnet/lease_xxx", + "approval_id": "appr_xxx", + "deployment_id": "dep_464a08b7b751", + "resource_id": "res_git_main", + "resource_type": "git", + "target_role": "backend", + "status": "active", + "expires_at": 1779850900000 + } + ] + } +} +``` + +### 10.5 撤销 lease + +#### `POST /api/agnet/credential-leases/{lease_id}/revoke` + +请求: + +```json +{ + "reason": "用户停止任务" +} +``` + +响应: + +```json +{ + "success": true, + "data": { + "lease_id": "lease_xxx", + "status": "revoked" + } +} +``` + +## 11. 角色模板 + +### `GET /api/agnet/role-templates` + +获取推荐子 Agnet 角色,桌面端可用于角色选择页。 + +响应: + +```json +{ + "success": true, + "data": { + "items": [ + { + "key": "backend", + "display_name": "Backend Agnet", + "summary": "实现后端接口、数据模型和服务逻辑", + "default_model": "agnet-model-backend", + "default_permissions": ["repo:read", "repo:write"], + "risk_level": "medium" + } + ] + } +} +``` + +## 12. 状态枚举 + +### HeicodeTask.status + +| 状态 | 说明 | 客户端动作 | +|---|---|---| +| `draft` | 草稿 | 可继续编辑 | +| `configuring` | 等待回答追问 | 展示 followups | +| `running` | 可推进/运行中 | 创建 deployment 或展示 timeline | +| `awaiting_approval` | 等待审批 | 拉取 `/api/agnet/approvals` | +| `completed` | 完成 | 展示交付物 | +| `failed` | 失败 | 展示失败原因 | +| `paused` | 暂停 | 允许继续或停止 | + +### Agnet Deployment.status + +| 状态 | 说明 | +|---|---| +| `accepted` | Manager 已接受并落本地记录 | +| `running` | Runtime 已开始执行 | +| `stopped` | 已停止 | +| `failed` | 失败 | + +### Agnet Deployment.runtime_state + +| 状态 | 说明 | +|---|---| +| `queued` | Manager 本地队列/占位,尚未同步真实 Runtime | +| `runtime_syncing` | 正在同步 Runtime | +| `runtime_accepted` | Runtime 接受 | +| `runtime_sync_failed` | Runtime 同步失败 | +| `not_configured` | 生产 Runtime 未配置 | + +### 普通 sub 子环节建议值 + +Runtime callback / timeline 中可使用: + +| 阶段 | 说明 | +|---|---| +| `requirements` | 需求 | +| `design` | 设计 | +| `backend` | 后端 | +| `frontend` | 前端 | +| `review` | 检查 | +| `test` | 测试 | +| `deploy` | 部署 | + +## 13. 错误码 + +| 错误码 | 场景 | 客户端处理 | +|---|---|---| +| `TASK_NOT_FOUND` | 任务不存在或 task snapshot 缺失 | 重新拉取任务 | +| `TASK_CONFLICT` | URL task_id 与 body task.id 不一致 | 修正请求 | +| `POLICY_REJECTED` | 参数不符合策略 | 展示错误并阻止继续 | +| `MODEL_NOT_ALLOWED` | 模型不在允许列表 | 让用户换模型或联系管理员 | +| `BUDGET_EXCEEDED` | 预算超过平台策略 | 调低预算 | +| `RESOURCE_GRANT_INVALID` | Resource Grant 字段缺失或角色不匹配 | 重新选择资源权限 | +| `RESOURCE_GRANT_SECRET_REF_REQUIRED` | 凭据型资源缺少 `secret_ref` | 引导用户绑定资源/密钥 | +| `SECRET_REF_INVALID` | `secret_ref` 不是 `azkv://...` | 禁止继续 | +| `RESOURCE_GRANT_SECRET_REJECTED` | metadata/constraints/audit 疑似包含明文密钥 | 禁止继续并提示脱敏 | +| `CALLBACK_SECRET_REJECTED` | Runtime 回调含明文密钥 | 展示安全错误 | + +## 14. 桌面端最小伪代码 + +```ts +const self = await manager.get('/api/user/self') +const userId = self.data.id + +const task = await mcp.post('/api/heicode-auth/api/user/tasks/intent', { + intent: userInput, +}) + +let current = task.data +while (current.status === 'configuring') { + const followup = findNextFollowup(current) + const answer = await askUser(followup) + current = await mcp.post( + `/api/heicode-auth/api/user/tasks/${current.id}/answer`, + { + question_id: followup.id, + option_id: answer.id, + } + ).then((r) => r.data) +} + +const draft = await manager.post( + `/api/agnet/user/tasks/${current.id}/deployment-draft`, + { + task: current, + sub_mode: 'agile', + binding_scope: `task-${current.id}`, + role_templates: ['backend', 'frontend', 'reviewer'], + default_model_id: 'agnet-model-builder', + }, + { headers: { 'New-Api-User': String(userId) } } +) + +const deployment = await manager.post( + '/api/agnet/user/deployments', + { orchestration_plan: draft.data.orchestration_plan }, + { headers: { 'New-Api-User': String(userId) } } +) + +const deploymentId = deployment.data.deployment_id + +setInterval(async () => { + const timeline = await manager.get( + `/api/agnet/user/deployments/${deploymentId}/timeline`, + { headers: { 'New-Api-User': String(userId) } } + ) + renderTimeline(timeline.data.timeline) + + const approvals = await manager.get( + `/api/agnet/approvals?status=pending&deployment_id=${deploymentId}`, + { headers: { 'New-Api-User': String(userId) } } + ) + renderApprovals(approvals.data.items) +}, 5000) +``` + +## 15. 当前生产注意事项 + +1. `https://code.xinghanlab.com` 的 Manager 用户态接口已上线。 +2. Manager 本地控制面可创建 `sub_mode=agile/waterfall` deployment。 +3. 当前外部 Agnet Runtime 如未配置,`runtime_state` 会停留在本地队列/占位状态,真实执行、日志、指标和 artifact 需要 Runtime 接入后产生。 +4. 桌面端可以先按本文完整接入 Manager 流程;真实 Runtime 接入后,timeline/artifacts/sk-snapshots 会自然出现更多回传数据。 diff --git a/docs/integration/manager-side-contract-patches.md b/docs/integration/manager-side-contract-patches.md index 58eff516..201862ee 100644 --- a/docs/integration/manager-side-contract-patches.md +++ b/docs/integration/manager-side-contract-patches.md @@ -47,7 +47,7 @@ **阻塞解除条件**: - agent-manager 侧给 mcp-server 派发 service token -- mcp-server 把 stub 换成对 `20.212.121.126:80` 的真实转发(HTTP/80,Host 头 `agent-manager.taijiagnet.com`) +- mcp-server 把 stub 换成对 `http://20.212.121.126` 的真实转发;域名 `agent-manager.taijiagnet.com` 的 DNS / HTTPS 后续单独修复,不作为当前联调阻塞项 Manager 前端已按 §5 契约消费,stub → 真实数据无需前端改动。 diff --git a/heicode/VERSION b/heicode/VERSION index e516bb9d..c514bd85 100644 --- a/heicode/VERSION +++ b/heicode/VERSION @@ -1 +1 @@ -1.4.5 +1.4.6 diff --git a/heicode/controller/agnet_control_plane_test.go b/heicode/controller/agnet_control_plane_test.go index 67ac1532..fc86126b 100644 --- a/heicode/controller/agnet_control_plane_test.go +++ b/heicode/controller/agnet_control_plane_test.go @@ -359,6 +359,15 @@ func TestAgnetRuntimeShadowCreateStoresRuntimeMapping(t *testing.T) { require.NoError(t, err) require.Contains(t, string(body), `"sub_mode":"agile"`) require.Contains(t, string(body), `"manager_deployment_id"`) + var runtimeBody map[string]any + require.NoError(t, common.Unmarshal(body, &runtimeBody)) + agents := runtimeBody["agents"].([]any) + require.Equal(t, "builder", agents[0].(map[string]any)["role"]) + metadata := runtimeBody["metadata"].(map[string]any) + require.NotEmpty(t, metadata["manager_deployment_id"]) + grants := runtimeBody["resource_grants"].([]any) + require.NotEmpty(t, grants) + require.NotEmpty(t, grants[0].(map[string]any)["ref"]) w.Header().Set("Content-Type", "application/json") _, _ = w.Write([]byte(`{"success":true,"data":{"deployment_id":"runtime-dep-1","swarm_id":"swarm-1","runtime_status":"accepted"}}`)) })) diff --git a/heicode/controller/agnet_runtime_client.go b/heicode/controller/agnet_runtime_client.go index d6a58b1d..bcc6d271 100644 --- a/heicode/controller/agnet_runtime_client.go +++ b/heicode/controller/agnet_runtime_client.go @@ -128,6 +128,123 @@ func agnetRuntimeHeaders(req *http.Request, cfg agnetRuntimeConfig, record agnet } } +func agnetRuntimeRequestAgents(plan agnetOrchestrationPlan) []gin.H { + byRole := make(map[string]agnetAgentPlan, len(plan.Agents)) + for _, agent := range plan.Agents { + role := strings.TrimSpace(agent.RoleTemplate) + if role != "" { + byRole[role] = agent + } + } + + items := make([]gin.H, 0, len(plan.AgentRuntime.Agents)) + for _, runtimeAgent := range plan.AgentRuntime.Agents { + role := strings.TrimSpace(runtimeAgent.Role) + if role == "" { + continue + } + item := gin.H{"role": role} + if agent, ok := byRole[role]; ok { + if len(agent.SKSources) > 0 { + item["sk_sources"] = agent.SKSources + } + if len(agent.ResourceGrants) > 0 { + item["resource_grants"] = agnetRuntimeResourceGrantPayloads(agent.ResourceGrants) + } + } + items = append(items, item) + } + + if len(items) > 0 { + return items + } + for _, agent := range plan.Agents { + role := strings.TrimSpace(agent.RoleTemplate) + if role == "" { + continue + } + item := gin.H{"role": role} + if len(agent.SKSources) > 0 { + item["sk_sources"] = agent.SKSources + } + if len(agent.ResourceGrants) > 0 { + item["resource_grants"] = agnetRuntimeResourceGrantPayloads(agent.ResourceGrants) + } + items = append(items, item) + } + return items +} + +func agnetRuntimeResourceGrantPayloads(grants []agnetResourceGrant) []gin.H { + items := make([]gin.H, 0, len(grants)) + for _, grant := range grants { + secretRef := strings.TrimSpace(grant.SecretRef) + if secretRef == "" { + continue + } + item := gin.H{ + "grant_id": grant.GrantID, + "resource_id": grant.ResourceID, + "resource_type": grant.ResourceType, + "type": grant.ResourceType, + "user_id": grant.UserID, + "binding_scope": grant.BindingScope, + "target_role": grant.TargetRole, + "target_agent_ref": grant.TargetAgentRef, + "permission_scope": grant.PermissionScope, + "permissions": grant.PermissionScope, + "status": grant.Status, + "ref": secretRef, + "secret_ref": secretRef, + "allowed_ref": agnetGrantResourceRef(grant), + "resource_ref_hint": agnetGrantResourceRef(grant), + } + if grant.TenantID != "" { + item["tenant_id"] = grant.TenantID + } + if grant.ProjectID != "" { + item["project_id"] = grant.ProjectID + } + if len(grant.Constraints) > 0 { + item["constraints"] = grant.Constraints + } + if len(grant.Metadata) > 0 { + item["metadata"] = grant.Metadata + } + if len(grant.Audit) > 0 { + item["audit"] = grant.Audit + } + items = append(items, item) + } + return items +} + +func agnetRuntimeRequestResourceGrants(plan agnetOrchestrationPlan) []gin.H { + items := make([]agnetResourceGrant, 0, len(plan.ResourceGrants)) + items = append(items, plan.ResourceGrants...) + for _, agent := range plan.Agents { + items = append(items, agent.ResourceGrants...) + } + return agnetRuntimeResourceGrantPayloads(items) +} + +func agnetRuntimeRequestMetadata(record agnetDeploymentRecord, source string) gin.H { + metadata := gin.H{ + "correlation_id": record.Plan.Metadata.CorrelationID, + "manager_deployment_id": record.DeploymentID, + "source": source, + "heicode_deployment_id": record.DeploymentID, + "heicode_runtime_bridge": true, + } + if record.Plan.Metadata.TenantID != "" { + metadata["tenant_id"] = record.Plan.Metadata.TenantID + } + if record.Plan.Metadata.ProjectID != "" { + metadata["project_id"] = record.Plan.Metadata.ProjectID + } + return metadata +} + func agnetRuntimeCreatePayload(record agnetDeploymentRecord, source string) gin.H { callback := gin.H{ "url": agnetRuntimeCallbackURL(), @@ -137,11 +254,16 @@ func agnetRuntimeCreatePayload(record agnetDeploymentRecord, source string) gin. callback["signing_secret_ref"] = ref } return gin.H{ - "deployment_id": record.DeploymentID, - "manager_deployment_id": record.DeploymentID, - "source": source, - "callback": callback, - "orchestration_plan": record.Plan, + "orchestration_plan": record.Plan, + "agents": agnetRuntimeRequestAgents(record.Plan), + "risk_level": record.Plan.RiskLevel, + "budget": record.Plan.Budget, + "billing_context": record.Plan.BillingContext, + "resource_grants": agnetRuntimeRequestResourceGrants(record.Plan), + "callback": callback, + "agile_context": record.Plan.AgileContext, + "sub_mode": record.Plan.SubMode, + "metadata": agnetRuntimeRequestMetadata(record, source), } } diff --git a/heicode/docker-compose.azure-vm.yml b/heicode/docker-compose.azure-vm.yml index dea2bf9c..da720d7f 100644 --- a/heicode/docker-compose.azure-vm.yml +++ b/heicode/docker-compose.azure-vm.yml @@ -52,6 +52,14 @@ services: # the VM managed identity. No static secret-store token is required. - AZURE_KEY_VAULT_URL=${AZURE_KEY_VAULT_URL:-} - AZURE_CLIENT_ID=${AZURE_CLIENT_ID:-} + # Agent Manager Runtime: use the current production IP directly. + # The public domain is not used until DNS/HTTPS is fixed. + - AGNET_RUNTIME_ENABLED=${AGNET_RUNTIME_ENABLED:-false} + - AGNET_RUNTIME_BASE_URL=${AGNET_RUNTIME_BASE_URL:-http://20.212.121.126} + - AGNET_RUNTIME_CREATE_PATH=${AGNET_RUNTIME_CREATE_PATH:-/api/agnet/deployments} + - AGNET_RUNTIME_HEALTH_PATH=${AGNET_RUNTIME_HEALTH_PATH:-/api/agnet/health} + - AGNET_RUNTIME_SERVICE_TOKEN=${AGNET_RUNTIME_SERVICE_TOKEN:-} + - AGNET_RUNTIME_CALLBACK_SIGNING_SECRET_REF=${AGNET_RUNTIME_CALLBACK_SIGNING_SECRET_REF:-} networks: - heicode-network healthcheck: diff --git a/heicode/router/agnet_runtime_http_smoke_test.go b/heicode/router/agnet_runtime_http_smoke_test.go index 93b6ad51..097b56c2 100644 --- a/heicode/router/agnet_runtime_http_smoke_test.go +++ b/heicode/router/agnet_runtime_http_smoke_test.go @@ -125,6 +125,15 @@ func TestAgnetRuntimeRealHTTPHealthAndShadowCreateSmoke(t *testing.T) { require.Contains(t, string(body), `"manager_deployment_id"`) require.Contains(t, string(body), `"signing_secret_ref":"azkv://heicode-kv.vault.azure.net/secrets/callback-signing"`) require.Contains(t, string(body), `"subscribed_events"`) + var runtimeBody map[string]any + require.NoError(t, common.Unmarshal(body, &runtimeBody)) + agents := runtimeBody["agents"].([]any) + require.Equal(t, "builder", agents[0].(map[string]any)["role"]) + grants := runtimeBody["resource_grants"].([]any) + require.NotEmpty(t, grants) + require.NotEmpty(t, grants[0].(map[string]any)["ref"]) + metadata := runtimeBody["metadata"].(map[string]any) + require.NotEmpty(t, metadata["manager_deployment_id"]) w.Header().Set("Content-Type", "application/json") _, _ = w.Write([]byte(`{"success":true,"data":{"deployment_id":"runtime-http-dep","swarm_id":"runtime-http-swarm","runtime_status":"accepted"}}`)) default: @@ -159,7 +168,7 @@ func TestAgnetRuntimeRealHTTPHealthAndShadowCreateSmoke(t *testing.T) { "billing_context":{"provider":"newapi","newapi_user_ref":"newapi-http-smoke"}, "agile_context":{"iteration":"2026-05-27~2026-05-28","stage":"development","checkpoint":"ready_for_test","acceptance_criteria":["接口返回成功"],"next_action":"submit_test_result","requires_user_approval":false}, "agent_runtime":{"platform":"agnet","agents":[{"role":"builder","model_ref":"model-http-smoke","instance_count":1}]}, - "agents":[{"role_template":"builder","goal":"smoke","default_model_id":"model-http-smoke","resource_grants":[{"grant_id":"grant-http-doc","resource_id":"doc-http","resource_type":"project_doc","user_id":"101","binding_scope":"task-http-smoke","target_role":"builder","target_agent_ref":"agent-builder-1","permission_scope":["doc:read"],"status":"active"}]}], + "agents":[{"role_template":"builder","goal":"smoke","default_model_id":"model-http-smoke","resource_grants":[{"grant_id":"grant-http-git","resource_id":"git-http","resource_type":"git","user_id":"101","binding_scope":"https://example.invalid/heicode/smoke.git#main","target_role":"builder","target_agent_ref":"agent-builder-1","permission_scope":["repo:read"],"metadata":{"repo_url":"https://example.invalid/heicode/smoke.git"},"secret_ref":"azkv://heicode-kv.vault.azure.net/secrets/http-smoke-git","status":"active"}]}], "constraints":{"allowed_model_ids":["model-http-smoke"]}, "metadata":{"correlation_id":"corr-http-smoke"} }