feat(swarm): HM-side Swarm Run read-only query — Phase1 (#45)

#45 Phase1 的只读查询(list/status/events?after/artifacts),全部基于 HM 已持久化的
运行时回调数据(Swarm → HM 带签名回调,见 agent_callback.go),**无需实时调 Swarm**,
因此不被 agent_swarm#2 契约冻结阻塞、返工风险低:

- GET /api/heicode/swarms — 列出当前用户的 swarm 运行(AgentDeployment, sub_mode=swarm 或有 runtime_swarm_id)
- GET /api/heicode/swarms/:id — 状态(:id = deployment_id/swarm_id/correlation_id 任一)
- GET /api/heicode/swarms/:id/events?after=&limit= — 事件增量拉取(id 游标 next_after,oldest-first)
  新增 model.ListSwarmCallbackEventsAfter(按 deployment_id/swarm_id + id>after)
- GET /api/heicode/swarms/:id/artifacts — 从已存事件(event_type 含 artifact)派生
- POST /api/heicode/swarms/:id/stop — 唯一写操作;在 agent_swarm#2 冻结 + SWARM_RUNTIME_ENABLED=true
  前默认关闭并明确提示(不臆造未冻结写接口)

字段口径对齐 agent_swarm/docs/integration/runtime-contract.md(deployment_id↔swarm_id↔
manager_deployment_id;状态机 waiting_approval→running→…)。所有查询按 user 作用域,视图脱敏
(不含 plan/payload 大字段与凭据)。

测试:TestListSwarmCallbackEventsAfter(游标/过滤/空标识);TestMain 迁移 AgentDeployment +
AgentCallbackEvent。go build/vet 干净,controller+model 全套回归通过。文档 §5.2。

Affects: Manager only(新增只读查询端点 + 一个 gated 写端点)。无计费/审计 schema 改动;
不依赖未冻结契约。stop 真实接入随 agent_swarm#2 冻结落地。

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-06-10 13:24:34 +08:00
co-authored by Claude Opus 4.8
parent da55285414
commit 59b13ba824
6 changed files with 306 additions and 0 deletions
@@ -289,6 +289,36 @@ signature = base64( ed25519_sign( device_priv, sha256(canonical) ) )
---
## 5.2 Swarm 运行查询 🟡(#45 Phase1 · 只读预览)
多 Agent 蜂群运行(`agent_swarm` / HeiCode Swarm)的**只读查询**。数据全部来自 HM 已持久化的运行时回调(Swarm → HM 带签名回调),**无需实时调 Swarm**,故不受 `agent_swarm#2` 契约冻结阻塞。
| 方法 | 路径 | 说明 | 状态 |
|---|---|---|---|
| GET | `/api/heicode/swarms` | 列出我的 swarm 运行 | 🟢 本地数据 |
| GET | `/api/heicode/swarms/:id` | 单个运行状态(:id = deployment_id / swarm_id / correlation_id 任一) | 🟢 |
| GET | `/api/heicode/swarms/:id/events?after=&limit=` | 事件增量拉取(`after`=上次返回的 `next_after`,oldest-first) | 🟢 |
| GET | `/api/heicode/swarms/:id/artifacts` | 从已存事件派生的产物 | 🟢(派生) |
| POST | `/api/heicode/swarms/:id/stop` | 停止运行(**写**) | 🟡 待 `agent_swarm#2` 冻结 + `SWARM_RUNTIME_ENABLED=true` |
```json
// GET /api/heicode/swarms/:id
{ "success": true, "data": {
"deployment_id":"dep_…", "swarm_id":"swarm-…", "correlation_id":"…",
"status":"running", "phase":"…", "runtime_state":"…", "failure_reason":"",
"created_at":"…","updated_at":"…","runtime_last_sync_at":"…" }}
// GET /api/heicode/swarms/:id/events?after=120
{ "success": true, "data": {
"items":[ {"id":121,"event_type":"task.completed","task_id":"…","result":"ok","occurred_at":"…","payload":{…}} ],
"next_after":121, "count":1 }}
```
- 状态机(契约 §4):`waiting_approval → running →(blocked ⇄ running)→ completed/failed/stopped`。
- `events` 用 `id` 游标(`next_after`)增量轮询;`stop` 在契约冻结前返回明确未启用提示(不臆造未冻结写接口)。
---
## 6. 直连 Agent(客户端 ↔ agent,A2A 协议)
> 客户端**直接连 agent 子域名**(§5 的 `subdomain`),不经过 HM。agent 是 AM 的 `coding_a2a_agent`,走 **A2A 协议**。