feat(agent): unify agnet→agent and implement client/runtime unification spec v0.1 core
按桌面客户端统一方案 v0.1 + agent_management Sub Mode Runtime 对接,强制全量统一,不留兼容。
命名统一(强制,无兼容):
- 全仓 agnet/Agnet/AGNET → agent/Agent/AGENT:后端 Go(路由 /api/agent/*、env AGENT_*、
结构体/函数、19 个文件改名)、前端(agent-console/agent-hub、/api/agent 调用、i18n)、
DB(表 agent_*、列 agent_id)、compose/.env、文档、脚本。
- DB 加幂等迁移 renameAgnetTablesToAgent():启动时 rename 老 agnet_* 表/列,保住生产数据。
统一方案核心(10 项):
- callback 统一 /api/agent/callbacks/runtime-events(路由/广播URL/函数名)。
- artifact 兜底判定改用 Runtime 权威信号 metadata.synthesized(§7.2)+ 结构化 artifact_type。
- Manager→Runtime 路径对齐 /api/agent/sub-agile/deployments(§2.2),{deployment_id} 回退 swarm_id。
- 状态裁决 display_status:Manager 唯一裁判,completed 无有效产物→needs_codegen/
completed_without_deliverable(§10.6),接入 detail/timeline/workflow。
- GET /api/heicode/capabilities 能力发现(§6)。
- 模型策略 per_role(role_models)+ 收集 allowed_model_ids(§9)。
- resource_binding_id→secret_ref 服务端解析,客户端不再 inline secret_ref(§17.6)。
- 客户端统一路由层 /api/heicode/sub-agile|swarm/*(task≡deployment,复用控制面)+ workflow 投影。
- 日志分层 user_logs/debug_logs(§13)。
验证:go build ./... + go test(controller/router/model/middleware)全绿;前端 tsc -b + rsbuild build 通过。
待部署:VM .env 的 AGNET_*→AGENT_*;启动迁移自动 rename 表;其他三仓库需同步切到 /api/agent。
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -9,7 +9,7 @@
|
||||
| `cc-haha/` | **Heicode** 客户端:CLI(Ink)+ 本地 HTTP/WS 服务 + **Desktop**(Tauri + React) | Bun + TypeScript;产品入口 `bin/heicode` |
|
||||
| `heicode/` | **Heicode Manager**:网关 + 管理控制台 | Go(Gin/GORM)+ `web/default` 前端(Bun/Rsbuild/React) |
|
||||
| `website/` | 产品介绍站点 | Next.js;根 `docker-compose.yml` 提供 `heicode-www` :8888 |
|
||||
| `docs/` | 愿景、**里程碑**(`docs/milestones/`)、**Agnet 集成**(`docs/integration/`) | Markdown |
|
||||
| `docs/` | 愿景、**里程碑**(`docs/milestones/`)、**Agent 集成**(`docs/integration/`) | Markdown |
|
||||
|
||||
根 `package.json` 仅少量 workspace 级依赖(如适配器用到的包);**主要开发依赖在 `cc-haha/package.json`**。
|
||||
|
||||
@@ -51,7 +51,7 @@ HEICODE_TAIJIAICLOUD_BASE_URL=http://localhost:3000 bun run src/server/index.ts
|
||||
|
||||
1. **改哪一层跟哪篇文档**:Go 行为以 `heicode/AGENTS.md` 为准;客户端 TS/React 以 `cc-haha/AGENTS.md` 为准。
|
||||
2. **小步提交**:沿用历史风格(如 `feat:` / `fix:` / `docs:`);PR 写清影响面与验证步骤。
|
||||
3. **集成与「SK」边界**:平台契约见 [`docs/integration/agnet-platform-api-design.md`](./docs/integration/agnet-platform-api-design.md),里程碑见 `docs/milestones/`。
|
||||
3. **集成与「SK」边界**:平台契约见 [`docs/integration/agent-platform-api-design.md`](./docs/integration/agent-platform-api-design.md),里程碑见 `docs/milestones/`。
|
||||
4. **不要臆测计费**:计费与订阅在平台侧,不在 Heicode 客户端内实现。
|
||||
|
||||
## Docker / 站点
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
| `cc-haha/` | **Heicode** 客户端:CLI(Ink)+ 本地 HTTP/WS 服务 + **Desktop**(Tauri + React) | Bun + TypeScript;产品入口 `bin/heicode` |
|
||||
| `heicode/` | **Heicode Manager**:网关 + 管理控制台 | Go(Gin/GORM)+ `web/default` 前端(Bun/Rsbuild/React) |
|
||||
| `website/` | 产品介绍站点 | Next.js;根 `docker-compose.yml` 提供 `heicode-www` :8888 |
|
||||
| `docs/` | 愿景、**里程碑**(`docs/milestones/`)、**Agnet 集成**(`docs/integration/`) | Markdown |
|
||||
| `docs/` | 愿景、**里程碑**(`docs/milestones/`)、**Agent 集成**(`docs/integration/`) | Markdown |
|
||||
|
||||
根 `package.json` 仅少量 workspace 级依赖(如适配器用到的包);**主要开发依赖在 `cc-haha/package.json`**。
|
||||
|
||||
@@ -51,7 +51,7 @@ HEICODE_TAIJIAICLOUD_BASE_URL=http://localhost:3000 bun run src/server/index.ts
|
||||
|
||||
1. **改哪一层跟哪篇文档**:Go 行为以 `heicode/CLAUDE.md` 为准;客户端 TS/React 以 `cc-haha/AGENTS.md` 为准。
|
||||
2. **小步提交**:沿用历史风格(如 `feat:` / `fix:` / `docs:`);PR 写清影响面与验证步骤。
|
||||
3. **集成与「SK」边界**:平台契约见 [`docs/integration/agnet-platform-api-design.md`](./docs/integration/agnet-platform-api-design.md),里程碑见 `docs/milestones/`。
|
||||
3. **集成与「SK」边界**:平台契约见 [`docs/integration/agent-platform-api-design.md`](./docs/integration/agent-platform-api-design.md),里程碑见 `docs/milestones/`。
|
||||
4. **不要臆测计费**:计费与订阅在平台侧,不在 Heicode 客户端内实现。
|
||||
|
||||
## Docker / 站点
|
||||
|
||||
@@ -0,0 +1,284 @@
|
||||
# Heicode Manager 生产配置与账号交接清单
|
||||
|
||||
更新时间:2026-06-01
|
||||
适用范围:Heicode Manager 生产环境、普通 sub Runtime、蜂群 Runtime、NewAPI、Azure 资源和后续 AI/运维交接。
|
||||
|
||||
## 0. 安全说明
|
||||
|
||||
本文只记录配置项、用途、位置和获取方式,不记录明文密码、Token、Key、连接串或私钥。
|
||||
|
||||
| 规则 | 要求 |
|
||||
|------|------|
|
||||
| 明文密钥 | 不写入 Markdown、git、聊天记录、日志、截图 |
|
||||
| 生产 `.env` | 只保存在 VM 或部署平台 Secret 中,不提交仓库 |
|
||||
| Azure Key Vault | 只保存长期密钥或 callback 签名密钥,代码和数据库只保存 `secret_ref` |
|
||||
| 转交给其他 AI | 只给本文档;需要明文值时由负责人通过安全渠道单独提供 |
|
||||
| 已泄漏密钥 | 一律按泄漏处理,立即轮换 |
|
||||
|
||||
## 0.1 配置可读取性结论
|
||||
|
||||
不是所有配置都能、也不应该在交接 Markdown 中读取和保存明文值。当前结论分三类:
|
||||
|
||||
| 分类 | 含义 | 处理方式 |
|
||||
|------|------|----------|
|
||||
| 可直接读取 | 仓库文档、compose、公开地址、非敏感环境变量能直接确认 | 在本文写出真实值 |
|
||||
| 可确认已配置但不读取明文 | 生产容器 env 中存在,但值是密码、token、连接串或密钥 | 在本文写出配置名、用途、保存位置和 `<REDACTED>` |
|
||||
| 当前无法确认明文或后台状态 | 需要 Azure Portal、NewAPI 后台、Gitee 凭据、VM root 权限或负责人授权 | 在本文明文写出“无法读取项名称、原因、获取入口” |
|
||||
|
||||
当前无法在本文保存明文的配置如下:
|
||||
|
||||
| 配置 / 凭据 | 是否能确认存在 | 为什么不能写明文 | 需要去哪里取 |
|
||||
|-------------|----------------|------------------|--------------|
|
||||
| VM SSH 密码/私钥 | 已知需要 | 登录凭据,写入文档会泄漏生产入口 | 负责人安全渠道、SSH keychain 或运维密码库 |
|
||||
| Gitee 用户密码/token | 已知需要 | Git 凭据,写入文档会泄漏代码仓库权限 | 本机 credential helper、Gitee 个人 token、负责人安全渠道 |
|
||||
| `SQL_DSN` | 已确认配置项存在 | 内含数据库用户名、密码、host、参数 | VM `.env`、容器 env、Azure PostgreSQL 连接串 |
|
||||
| `REDIS_CONN_STRING` | 已确认配置项存在 | 内含 Redis 密码和 TLS 连接串 | VM `.env`、容器 env、Azure Redis Access keys |
|
||||
| `SESSION_SECRET` | 已确认配置项存在 | 会话签名密钥,泄漏会影响登录安全 | VM `.env`、容器 env、部署平台 Secret |
|
||||
| `CRYPTO_SECRET` | 已确认配置项存在 | Manager 加密密钥,泄漏会影响加密数据安全 | VM `.env`、容器 env、部署平台 Secret |
|
||||
| `MCP_SERVER_INTERNAL_TOKEN` | 已确认配置项存在 | 内部服务调用 token | VM `.env`、容器 env |
|
||||
| `AGENT_RUNTIME_SERVICE_TOKEN` | 已确认配置项存在 | Manager 调 Runtime 的 Bearer token | VM `.env`、容器 env、Agent Manager 负责人 |
|
||||
| `AGENT_CALLBACK_TOKEN` | 已确认配置项存在 | Runtime 回调 Manager 的旧 token 兼容凭据 | VM `.env`、容器 env、Agent Manager 负责人 |
|
||||
| `AGENT_CALLBACK_SIGNING_SECRET` | 已确认配置项存在 | Runtime 回调 Manager 的 HMAC 签名密钥 | VM `.env`、Key Vault `agent-callback-signing-key` |
|
||||
| `AGENT_RUNTIME_CALLBACK_SIGNING_SECRET_REF` | 已确认配置项存在 | secret_ref 本身不应暴露完整细节 | VM `.env`、Key Vault 配置 |
|
||||
| NewAPI 管理员密码 | 当前未在仓库读取 | 后台管理员凭据 | NewAPI 后台、NewAPI DB、负责人安全渠道 |
|
||||
| NewAPI 渠道 Key | 当前未在仓库读取 | 上游模型供应商 key | NewAPI 渠道管理页面或 DB |
|
||||
| Azure Portal 账号/MFA | 当前未在仓库读取 | 云账号权限极高 | 负责人 Azure 账号体系 |
|
||||
| Azure Key Vault secret value | 当前未直接读取 | Key Vault 密钥值只能由授权身份读取 | Azure Portal、Azure CLI、Managed Identity |
|
||||
|
||||
后续 AI 如果必须使用明文值,应只在当前 shell/session 的环境变量中临时使用,不能写入 Markdown、git diff、日志或截图。
|
||||
|
||||
## 1. 当前生产入口与代码仓库
|
||||
|
||||
| 项目 | 当前值 / 位置 | 说明 |
|
||||
|------|---------------|------|
|
||||
| Manager 生产地址 | `https://code.xinghanlab.com` | 用户和桌面客户端访问入口 |
|
||||
| Manager 本地仓库 | `/Users/gongzhiyong/go/heicode-mananger` | 当前开发工作区 |
|
||||
| Manager Git 远程 | `http://gitee.ath.cx:3000/taijibaga/heicode-mananger.git` | 继续开发、提交、上线使用 |
|
||||
| 主分支 | `main` | 当前工作分支 |
|
||||
| 旧 bug 仓库 | `HeiCode-issues.git` | 已废弃,后续不用再传 |
|
||||
| 当前未跟踪文件 | `bak.txt` | 本地文件,不属于本交接文档内容 |
|
||||
|
||||
## 2. Azure / VM 生产资源
|
||||
|
||||
| 类型 | 当前配置 | 用途 | 备注 |
|
||||
|------|----------|------|------|
|
||||
| Azure Resource Group | `HEICODE` | Heicode 相关 Azure 资源组 | 以 Azure Portal 实际为准 |
|
||||
| 区域 | `southeastasia` | 主要生产资源区域 | 以 Azure Portal 实际为准 |
|
||||
| Manager VM | `heicode` | 当前承载 Manager Docker 容器 | 生产应用仍在 VM 上 |
|
||||
| VM SSH Host | `20.24.50.121` | 登录生产 VM | 凭据见“账号与凭据清单” |
|
||||
| VM SSH User | `heicode` | 生产 VM 运维用户 | 密码/私钥不写入本文 |
|
||||
| Manager 容器 | `heicode` | 生产 Manager 应用容器 | 端口 `3000:3000` |
|
||||
| Manager 镜像 | `heicode-manager:local` | VM 本地构建镜像 | 以 `docker ps` 为准 |
|
||||
| 数据根目录 | `/var/lib/heicode/heicode` | bind mount 根目录 | 由 `HEICODE_DATA_ROOT` 控制 |
|
||||
| 应用持久目录 | `/data` | 上传文件、桌面安装包、运行持久文件 | 容器内路径,映射到 VM |
|
||||
| 应用日志目录 | `/app/logs` | Manager 日志 | 容器内路径,映射到 VM |
|
||||
|
||||
当前 VM 上观察到的容器:
|
||||
|
||||
| 容器 | 镜像 | 端口 | 说明 |
|
||||
|------|------|------|------|
|
||||
| `heicode` | `heicode-manager:local` | `3000:3000` | Manager 生产应用 |
|
||||
| `new-api` | `calciumion/new-api:latest` | `3001:3000` | NewAPI 管理和模型网关 |
|
||||
| `heicode-openbao` | `openbao/openbao:latest` | `127.0.0.1:8200:8200` | 历史/兼容密钥服务,不作为当前正式方案 |
|
||||
| `postgres` | `postgres:15` | VM 内部 | VM 上存在容器,但 Manager 正式配置应以 Azure PostgreSQL 为准 |
|
||||
| `redis` | `redis:latest` | VM 内部 | VM 上存在容器,但 Manager 正式配置应以 Azure Redis 为准 |
|
||||
|
||||
重要:仓库约定和部署文档要求生产业务库使用 Azure 托管 PostgreSQL / Redis,不要把 VM 上的 `postgres`、`redis` 容器误认为正式托管实例。
|
||||
|
||||
## 3. 当前 VM 关键路径
|
||||
|
||||
| 路径 | 用途 | 备注 |
|
||||
|------|------|------|
|
||||
| `/home/heicode/heicode/heicode/.env` | Manager 生产环境变量文件 | **当前真实部署目录**(compose `working_dir` 实锤);仓库远程为 `taijibaga/heicode-mananger` |
|
||||
| `/home/heicode/heicode/heicode/docker-compose.azure-vm.yml` | Manager Azure VM compose | 当前 active compose(`docker inspect` 的 `config_files` 指向此文件) |
|
||||
| `/home/heicode/apps/new-api/docker-compose.yml` | NewAPI compose | NewAPI 容器配置入口 |
|
||||
| `/home/heicode/apps/_ABANDONED_heicode-git_xiaohei_20260601/` | **已废弃的旧 clone**(原 `xiaohei/heicode`,2026-06-01 改名挪走) | 不是部署源;曾误导排查。可后续硬删 |
|
||||
| `/home/heicode/deploy/` | 历史部署目录 | 可能包含旧版本 `.env`,不要直接当作当前配置 |
|
||||
| `/home/heicode/releases/` | 历史 release 目录 | 只用于回溯,不作为当前事实 |
|
||||
| `/var/lib/heicode/heicode/data` | Manager 持久数据 | 对应容器 `/data` |
|
||||
| `/var/lib/heicode/heicode/logs` | Manager 日志 | 对应容器 `/app/logs` |
|
||||
|
||||
## 4. Manager 基础环境变量
|
||||
|
||||
| 环境变量 | 当前状态 | 用途 | 值/来源 |
|
||||
|----------|----------|------|---------|
|
||||
| `TZ` | 已配置 | 时区 | `Asia/Shanghai` |
|
||||
| `NODE_NAME` | 已配置 | 节点名 | `heicode-node-1` |
|
||||
| `ERROR_LOG_ENABLED` | 已配置 | 错误日志开关 | `true` |
|
||||
| `BATCH_UPDATE_ENABLED` | 已配置 | 批量更新开关 | `true` |
|
||||
| `HEICODE_DATA_ROOT` | 已配置 | VM bind mount 根目录 | `/var/lib/heicode/heicode` |
|
||||
| `SQL_DSN` | 已配置,敏感 | PostgreSQL 连接串 | VM `.env` / 容器 env,值 `<REDACTED>` |
|
||||
| `REDIS_CONN_STRING` | 已配置,敏感 | Redis TLS 连接串 | VM `.env` / 容器 env,值 `<REDACTED>` |
|
||||
| `SESSION_SECRET` | 已配置,敏感 | 登录会话签名 | VM `.env` / 容器 env,值 `<REDACTED>` |
|
||||
| `CRYPTO_SECRET` | 已配置,敏感 | Manager 加密密钥 | VM `.env` / 容器 env,值 `<REDACTED>` |
|
||||
| `MCP_SERVER_INTERNAL_TOKEN` | 已配置,敏感 | 内部服务调用 token | VM `.env` / 容器 env,值 `<REDACTED>` |
|
||||
| `HEICODE_AUTH_BASE_URL` | 已配置 | Heicode/Agent 认证网关 | 默认参考 `https://apimtaiji.azure-api.net/api/mcp`,以 VM env 为准 |
|
||||
| `HEICODE_ROOT_EMAILS` | 已配置 | JIT 自动 root 白名单 | 当前容器 env 可见 `xiaohei@qq.com` |
|
||||
| `HEICODE_ADMIN_EMAILS` | 空 | JIT 自动 admin 白名单 | 当前未配置 |
|
||||
|
||||
生产数据库和 Redis:
|
||||
|
||||
| 服务 | 地址 | 说明 |
|
||||
|------|------|------|
|
||||
| Azure PostgreSQL | `heicode.postgres.database.azure.com` / DB `heicode` | Manager 正式数据库 |
|
||||
| Azure Redis | `heicode.redis.cache.windows.net:6380` | TLS Redis,连接串走 `REDIS_CONN_STRING` |
|
||||
|
||||
## 5. Azure Key Vault / 密钥保管库配置
|
||||
|
||||
| 项目 | 当前状态 | 用途 | 备注 |
|
||||
|------|----------|------|------|
|
||||
| Key Vault 名称 | `heicode-kv` | 长期密钥和 callback 签名密钥托管 | 以 Azure Portal 为准 |
|
||||
| Key Vault URL | `https://heicode-kv.vault.azure.net` | Manager 访问密钥保管库 | 对应 `AZURE_KEY_VAULT_URL` |
|
||||
| `AZURE_KEY_VAULT_URL` | 已配置 | Key Vault 地址 | VM env,值应为上面 URL |
|
||||
| `AZURE_CLIENT_ID` | 当前为空 | user-assigned managed identity client id | 若使用 system-assigned 可为空;若 health 报 Identity not found 需配置身份 |
|
||||
| `agent-callback-signing-key` | 必须存在 | Runtime callback HMAC 签名密钥 | 建议通过 Key Vault 存储 |
|
||||
|
||||
当前已知问题:
|
||||
|
||||
| 问题 | 含义 | 处理方式 |
|
||||
|------|------|----------|
|
||||
| `Identity not found` | VM 当前没有可用 Managed Identity,或未绑定正确 `AZURE_CLIENT_ID` | 在 VM/容器服务开启 Managed Identity,并给 `heicode-kv` 授权 |
|
||||
|
||||
Key Vault 最低权限:
|
||||
|
||||
| 用途 | 权限 / RBAC |
|
||||
|------|-------------|
|
||||
| 读取 callback 签名密钥 | `secrets/get` |
|
||||
| 健康检查列举 | `secrets/list` |
|
||||
| Manager 写入资源密钥 PutSecret | `secrets/set` |
|
||||
| RBAC 参考角色 | `Key Vault Secrets User` 或 `Key Vault Secrets Officer` |
|
||||
|
||||
## 6. 普通 sub Runtime / Agent Manager 配置
|
||||
|
||||
普通 sub 和蜂群是两套模式,不要混用。普通 sub 当前走 Agent Manager Runtime IP。
|
||||
|
||||
| 环境变量 | 当前状态 | 当前值 / 位置 | 说明 |
|
||||
|----------|----------|---------------|------|
|
||||
| `AGENT_RUNTIME_ENABLED` | 已启用 | `true` | 开启普通 sub Runtime |
|
||||
| `AGENT_RUNTIME_BASE_URL` | 已配置 | `http://20.212.121.126` | 当前联调和生产使用 IP,域名未切换 |
|
||||
| `AGENT_RUNTIME_HEALTH_PATH` | 已配置 | `/api/agent/health` | Runtime 健康检查 |
|
||||
| `AGENT_RUNTIME_CREATE_PATH` | 已配置 | `/api/swarms` | 普通 sub 创建入口 |
|
||||
| `AGENT_RUNTIME_STOP_PATH` | 已配置 | `/api/swarms/{swarm_id}/stop` | 停止 Runtime run |
|
||||
| `AGENT_RUNTIME_APPROVAL_DECISION_PATH` | 已配置 | `/api/swarms/{swarm_id}/approvals/{approval_id}` | 审批结果回传 |
|
||||
| `AGENT_RUNTIME_TIMEOUT_SECONDS` | 已配置 | `15` | Manager 调 Runtime 超时 |
|
||||
| `AGENT_RUNTIME_SERVICE_TOKEN` | 已配置,敏感 | VM env,值 `<REDACTED>` | Manager 调 Runtime 的 Bearer token |
|
||||
| `AGENT_RUNTIME_CALLBACK_SIGNING_SECRET_REF` | 已配置,敏感引用 | VM env,值 `<REDACTED>` | callback HMAC secret_ref,正式应为 `azkv://...` |
|
||||
| `AGENT_CALLBACK_TOKEN` | 已配置,敏感 | VM env,值 `<REDACTED>` | Runtime 回调 Manager 的旧 token 兼容 |
|
||||
| `AGENT_CALLBACK_SIGNING_SECRET` | 已配置,敏感 | VM env,值 `<REDACTED>` | Runtime 回调 Manager 的 HMAC fallback |
|
||||
|
||||
普通 sub 当前标准链路:
|
||||
|
||||
```text
|
||||
桌面客户端
|
||||
-> Heicode Manager /api/agent/user/*
|
||||
-> Agent Manager Runtime http://20.212.121.126/api/swarms
|
||||
-> Runtime/Agent 执行
|
||||
-> Runtime callback 到 https://code.xinghanlab.com/api/agent/callbacks/swarm-events
|
||||
-> Manager 落库
|
||||
-> 桌面客户端查询 timeline/logs/artifacts/content
|
||||
```
|
||||
|
||||
## 7. 蜂群 Runtime 配置
|
||||
|
||||
蜂群模式和普通 sub 模式是两套部署、两套语义。Manager 当前环境里蜂群 Runtime 开关是关闭状态。
|
||||
|
||||
| 环境变量 | 当前状态 | 当前值 / 位置 | 说明 |
|
||||
|----------|----------|---------------|------|
|
||||
| `SWARM_RUNTIME_ENABLED` | 当前关闭 | `false` | 蜂群 Runtime 独立开关 |
|
||||
| `SWARM_RUNTIME_BASE_URL` | 当前为空 | `<EMPTY>` | 蜂群 Runtime 地址未在 Manager 生产 env 启用 |
|
||||
| `SWARM_RUNTIME_CREATE_PATH` | 已有默认 | `/api/swarms` | 蜂群创建入口默认值 |
|
||||
| `SWARM_RUNTIME_HEALTH_PATH` | 已有默认 | `/api/agent/health` | 蜂群健康检查默认值 |
|
||||
| `SWARM_RUNTIME_STOP_PATH` | 已有默认 | `/api/swarms/{swarm_id}/stop` | 蜂群停止默认值 |
|
||||
| `SWARM_RUNTIME_APPROVAL_DECISION_PATH` | 已有默认 | `/api/swarms/{swarm_id}/approvals/{approval_id}` | 蜂群审批默认值 |
|
||||
| `SWARM_RUNTIME_SERVICE_TOKEN` | 当前为空 | `<EMPTY>` | 蜂群 Runtime token 未配置 |
|
||||
|
||||
已知蜂群 Orchestrator 地址:
|
||||
|
||||
| 项目 | 地址 | 说明 |
|
||||
|------|------|------|
|
||||
| HeiCode-Swarm Orchestrator | `http://52.139.240.116:8000` | 蜂群项目独立 Runtime / Orchestrator |
|
||||
|
||||
## 8. NewAPI / 模型网关配置
|
||||
|
||||
| 项目 | 当前配置 | 用途 | 备注 |
|
||||
|------|----------|------|------|
|
||||
| NewAPI 容器 | `new-api` | 模型网关、渠道、模型 token、用量 | VM 上运行 |
|
||||
| NewAPI 镜像 | `calciumion/new-api:latest` | NewAPI 服务镜像 | 以 `docker ps` 为准 |
|
||||
| NewAPI VM 端口 | `20.24.50.121:3001` -> container `3000` | 管理后台或内部访问 | 是否公网开放以 NSG/防火墙为准 |
|
||||
| NewAPI compose | `/home/heicode/apps/new-api/docker-compose.yml` | NewAPI 部署配置 | 明文密钥不得写入本文 |
|
||||
| 模型渠道配置 | NewAPI 管理后台 / DB | 渠道 key、base_url、模型映射 | 不在 Manager 文档保存明文 |
|
||||
| Manager 模型接口 | `https://code.xinghanlab.com/v1/...` | 桌面客户端模型调用入口 | Manager / NewAPI 转发 |
|
||||
|
||||
## 9. 账号与凭据清单
|
||||
|
||||
| 类别 | 标识 | 用途 | 明文值位置 / 获取方式 |
|
||||
|------|------|------|-----------------------|
|
||||
| VM SSH | host `20.24.50.121`, user `heicode` | 登录生产 VM、部署、看日志、清 Docker | 密码或私钥由负责人通过安全渠道提供,本文不保存 |
|
||||
| Gitee Manager 仓库 | `http://gitee.ath.cx:3000/taijibaga/heicode-mananger.git` | 拉取、提交、推送 Manager 代码 | Gitee 用户名/密码或 token 由负责人提供;可能已在本机 credential helper |
|
||||
| Manager Root 邮箱 | `xiaohei@qq.com` | 生产 root 白名单 | 只记录邮箱,不记录密码 |
|
||||
| Manager 测试用户 | `zsbgnw@gmail.com` | 桌面客户端和 Manager 联调测试 | 密码不写入本文,需负责人单独提供 |
|
||||
| Manager 管理后台用户 | 以生产数据库/后台为准 | 登录 `https://code.xinghanlab.com` 管理配置 | 密码不写入本文 |
|
||||
| NewAPI 管理员 | 以 NewAPI 后台/DB 为准 | 配置渠道、模型、用户 token、日志 | 密码不写入本文 |
|
||||
| NewAPI 渠道 Key | NewAPI 后台渠道管理 | 模型上游调用 | Key 不写入本文 |
|
||||
| Azure Portal | 负责人 Azure 账号 | VM、Key Vault、PostgreSQL、Redis、网络配置 | 账号/密码/MFA 不写入本文 |
|
||||
| Azure Key Vault Secret | `agent-callback-signing-key` | callback HMAC 签名 | 在 `heicode-kv` 获取/轮换 |
|
||||
| Runtime Service Token | `AGENT_RUNTIME_SERVICE_TOKEN` | Manager 调 Agent Manager Runtime | VM env 或部署平台 Secret,值 `<REDACTED>` |
|
||||
| Callback Token | `AGENT_CALLBACK_TOKEN` | Runtime 回调 Manager 旧 token 兼容 | VM env 或部署平台 Secret,值 `<REDACTED>` |
|
||||
| Callback HMAC Secret | `AGENT_CALLBACK_SIGNING_SECRET` | Runtime 回调 Manager HMAC fallback | VM env / Key Vault,值 `<REDACTED>` |
|
||||
| PostgreSQL 密码 | `SQL_DSN` 内 | Manager 数据库连接 | VM `.env` / Azure PostgreSQL,值 `<REDACTED>` |
|
||||
| Redis 密码 | `REDIS_CONN_STRING` 内 | Manager Redis 连接 | VM `.env` / Azure Redis,值 `<REDACTED>` |
|
||||
|
||||
## 10. 常用排障命令
|
||||
|
||||
以下命令只列配置名或状态,避免直接打印明文密钥。
|
||||
|
||||
```bash
|
||||
sudo docker ps --format 'table {{.Names}}\t{{.Image}}\t{{.Status}}\t{{.Ports}}'
|
||||
|
||||
sudo docker inspect heicode \
|
||||
--format '{{range .Config.Env}}{{println .}}{{end}}' \
|
||||
| awk -F= '{print $1}' \
|
||||
| sort
|
||||
|
||||
curl -fsS http://127.0.0.1:3000/api/status
|
||||
curl -fsS https://code.xinghanlab.com/api/status
|
||||
curl -fsS http://20.212.121.126/api/agent/health
|
||||
```
|
||||
|
||||
部署 Manager:
|
||||
|
||||
```bash
|
||||
cd /home/heicode/heicode/heicode # 真实部署目录(remote: taijibaga/heicode-mananger, 分支 main)
|
||||
git pull
|
||||
# 真实部署只有 docker-compose.azure-vm.yml + .env,无 docker-compose.override.yml
|
||||
docker compose -f docker-compose.azure-vm.yml --env-file .env up -d --build
|
||||
sudo docker image prune -f
|
||||
```
|
||||
|
||||
## 11. 后续接手必须先确认的事项
|
||||
|
||||
| 序号 | 检查项 | 标准 |
|
||||
|------|--------|------|
|
||||
| 1 | 当前线上 commit | VM 工作树和 Gitee `main` 对齐 |
|
||||
| 2 | Manager 容器健康 | `docker ps` 显示 `heicode` healthy,`/api/status` 返回成功 |
|
||||
| 3 | 数据库连接 | `SQL_DSN` 指向 Azure PostgreSQL,不误用 VM 本地容器 |
|
||||
| 4 | Redis 连接 | `REDIS_CONN_STRING` 指向 Azure Redis TLS,不误用 VM 本地容器 |
|
||||
| 5 | Key Vault | `/api/secret-store/status` 不再报 `Identity not found` |
|
||||
| 6 | 普通 sub | `AGENT_RUNTIME_BASE_URL=http://20.212.121.126`,创建、callback、artifact content 全链路可跑 |
|
||||
| 7 | 蜂群 | `SWARM_RUNTIME_ENABLED` 当前为 `false`;如启用必须单独联调 |
|
||||
| 8 | NewAPI | 模型渠道、token、日志正常;Runtime 失败时核对 `newapi_request_id` |
|
||||
| 9 | 桌面客户端 | 加密 body、登录、模型列表、普通 sub artifact 展示正常 |
|
||||
| 10 | 敏感值 | 任何 `.env`、token、密码不得进入 git diff |
|
||||
|
||||
## 12. 交接边界
|
||||
|
||||
下一位 AI 或工程师可以直接基于本文继续做:
|
||||
|
||||
- Manager 部署、版本更新、Docker 清理。
|
||||
- 普通 sub Runtime 联调、artifact content 下载链路验证。
|
||||
- 蜂群 Runtime 独立启用前的配置核对。
|
||||
- NewAPI 模型调用排障。
|
||||
- Azure Key Vault Managed Identity 修复。
|
||||
- 桌面客户端对接文档更新。
|
||||
|
||||
但不能直接从本文获得明文密码或 token。需要真实密钥时,必须由负责人通过安全渠道提供,或在 VM/Azure Portal/NewAPI 后台按权限读取。
|
||||
@@ -0,0 +1,208 @@
|
||||
# Heicode Manager 项目说明与踩坑交接
|
||||
|
||||
更新时间:2026-06-01
|
||||
用途:给下一位 AI / 工程师快速理解 Heicode Manager 的项目边界、生产拓扑、普通 sub、蜂群、NewAPI 和已踩过的坑。
|
||||
|
||||
## 1. 一句话说明
|
||||
|
||||
Heicode Manager 是 Heicode 的服务端控制面和网页管理台,负责用户登录、模型网关配置、用户/设备/模型/渠道管理、普通 sub 敏捷任务控制、Runtime callback 落库、artifact/timeline 展示,以及和桌面客户端、Agent Manager Runtime、NewAPI、Azure Key Vault 之间的编排。
|
||||
|
||||
它不是桌面客户端本体,也不是 Agent Runtime 本体,也不是 NewAPI 本体。
|
||||
|
||||
## 2. 仓库结构
|
||||
|
||||
| 路径 | 作用 | 说明 |
|
||||
|------|------|------|
|
||||
| `heicode/` | Manager 后端和默认前端 | Go + Gin/GORM,`web/default` 是当前网页端 |
|
||||
| `cc-haha/` | Heicode 桌面客户端和本地服务 | Tauri + React + Bun,客户端到 Manager 的请求 body 会走加密/签名流程 |
|
||||
| `docs/` | 项目文档、部署、集成、进度清单 | 后续交接优先看这里 |
|
||||
| `docs/deployment/` | 生产部署和迁移文档 | VM、Azure、配置交接 |
|
||||
| `docs/integration/` | Runtime、桌面客户端、蜂群、普通 sub 对接文档 | 联调时优先看 |
|
||||
|
||||
开发前先看:
|
||||
|
||||
| 范围 | 文档 |
|
||||
|------|------|
|
||||
| 根仓库规则 | `AGENTS.md` |
|
||||
| Manager 规则 | `heicode/AGENTS.md` |
|
||||
| 生产配置 | `docs/deployment/Heicode-Manager-生产配置与账号交接清单.md` |
|
||||
| 普通 sub 桌面客户端对接 | `docs/integration/heicode-desktop-sub-agile-api.md` |
|
||||
| 普通 sub Agent Manager 对接 | `docs/integration/普通sub敏捷模式-AgentManager对接任务清单.md` |
|
||||
| 蜂群 Agent Manager 对接 | `docs/integration/蜂群模式-AgentManager对接任务清单.md` |
|
||||
|
||||
## 3. 生产拓扑
|
||||
|
||||
```text
|
||||
用户/桌面客户端
|
||||
-> https://code.xinghanlab.com
|
||||
-> Heicode Manager Docker container on Azure VM
|
||||
-> Azure PostgreSQL / Azure Redis
|
||||
-> NewAPI model gateway
|
||||
-> 普通 sub Agent Manager Runtime: http://20.212.121.126
|
||||
-> 蜂群 Runtime / Orchestrator: http://52.139.240.116:8000
|
||||
-> Azure Key Vault: https://heicode-kv.vault.azure.net
|
||||
```
|
||||
|
||||
当前 Manager 部署在 Azure VM 上,容器名 `heicode`,端口 `3000:3000`。VM 上也能看到 `new-api`、`postgres`、`redis`、`heicode-openbao` 容器,但正式业务数据库和缓存应以 Azure 托管 PostgreSQL / Redis 配置为准,不要误用 VM 本地容器判断生产数据。
|
||||
|
||||
## 4. 普通 sub 和蜂群必须分开
|
||||
|
||||
这是最容易踩坑的点。
|
||||
|
||||
| 模式 | 含义 | Manager 当前配置 | Runtime |
|
||||
|------|------|------------------|---------|
|
||||
| 普通 sub 敏捷模式 | 桌面客户端把一个开发任务拆给若干子 Agent,按需求、设计、开发、测试、部署等阶段推进 | `AGENT_RUNTIME_ENABLED=true` | `http://20.212.121.126` |
|
||||
| 蜂群模式 | HeiCode-Swarm 的多 Agent swarm run / task graph 模式 | `SWARM_RUNTIME_ENABLED=false` | `http://52.139.240.116:8000`,需单独启用和联调 |
|
||||
|
||||
不要因为两个接口都可能叫 `/api/swarms` 就把它们混成一个概念。普通 sub 是 Heicode 的任务组织方式;蜂群是独立 swarm runtime 形态。
|
||||
|
||||
## 5. 普通 sub 当前主流程
|
||||
|
||||
```text
|
||||
桌面客户端创建/补充任务
|
||||
-> Manager 用户态接口生成 deployment draft
|
||||
-> Manager 调 Agent Manager Runtime POST /api/swarms
|
||||
-> Runtime 创建 run 并启动子 Agent
|
||||
-> Runtime 执行中 callback Manager
|
||||
-> Manager 保存 timeline / events / logs / artifacts / usage
|
||||
-> 桌面客户端从 Manager 查询展示
|
||||
```
|
||||
|
||||
关键点:
|
||||
|
||||
1. Runtime 执行过程会回调状态、日志、timeline、用量。
|
||||
2. 最终业务交付物不是直接塞在聊天文本里,而是通过 `artifact.created` 落库。
|
||||
3. 客户端应先查 artifact 列表,再通过 content 接口下载完整产物。
|
||||
4. 如果 `usage=0` 或只有失败摘要 artifact,不能算真实业务交付完成。
|
||||
|
||||
## 6. artifact 展示坑
|
||||
|
||||
桌面客户端曾出现“以下是作为 Frontend 角色...”这种内容,看起来像交付物,其实多半只是 Runtime 的摘要文本。
|
||||
|
||||
正常设计应该是:
|
||||
|
||||
| 层级 | 应展示什么 |
|
||||
|------|------------|
|
||||
| 聊天时间线 | 阶段进度、Agent 状态、摘要说明 |
|
||||
| 交付产物卡片 | `artifact_id`、标题、类型、摘要、大小、hash、下载入口 |
|
||||
| 完整代码/文件 | 通过 `GET /api/agent/user/deployments/{deployment_id}/artifacts/{artifact_id}/content` 下载 |
|
||||
|
||||
如果 artifact 里有 `azblob://...` 或 `runtime://...`,客户端不应该直接暴露云凭据或要求用户自己访问 Blob,而应通过 Manager / Runtime content 代理接口拿完整内容。
|
||||
|
||||
## 7. NewAPI / 模型调用边界
|
||||
|
||||
NewAPI 是模型网关和用量计费入口,不是 Heicode Manager 自己的模型执行器。
|
||||
|
||||
排查模型问题时要区分三条链路:
|
||||
|
||||
| 链路 | 调用方 | 常见问题 |
|
||||
|------|--------|----------|
|
||||
| 桌面普通聊天 | 桌面客户端 -> Manager/NewAPI | 用户 token、模型列表、渠道权限、body 加密 |
|
||||
| 普通 sub Runtime Agent | Runtime 子 Agent -> NewAPI/Manager 模型网关 | Runtime 环境变量、模型名、base_url、请求路径、上游超时 |
|
||||
| Manager 后台模型配置 | 管理员网页 -> Manager/NewAPI | 渠道配置、分组、可用模型、价格表达式 |
|
||||
|
||||
曾踩过的坑:
|
||||
|
||||
- 某个模型 503/504 时,不一定是客户端参数错,也可能是 Runtime 子 Agent 使用的模型、base_url 或请求格式不对。
|
||||
- Runtime 需要回传 `newapi_request_id` 和非零 token usage,方便定位 NewAPI 日志。
|
||||
- 一个模型失败时可以切换模型验证,但不能把失败摘要 artifact 当成业务完成。
|
||||
|
||||
## 8. Azure Key Vault / OpenBao 边界
|
||||
|
||||
当前正式方向是 Azure Key Vault,不是 OpenBao。
|
||||
|
||||
| 项目 | 结论 |
|
||||
|------|------|
|
||||
| Azure Key Vault | 正式长期密钥托管方案 |
|
||||
| OpenBao | VM 上存在历史/兼容容器,不作为当前正式方案 |
|
||||
| Managed Identity | Manager 访问 Key Vault 的推荐方式 |
|
||||
| 当前已知问题 | 之前健康检查出现过 `Identity not found`,说明 VM 身份或 `AZURE_CLIENT_ID`/Vault 权限未配好 |
|
||||
|
||||
如果后续迁移到 Container Apps / AKS / App Service,不能只切域名。必须重新配置 Managed Identity、Key Vault 权限、环境变量、持久化、数据库/Redis 网络、Runtime callback 地址。
|
||||
|
||||
## 9. 登录、设备、模型列表问题排查
|
||||
|
||||
之前遇到过用户登录成功但设备看不到、模型列表拿不到的问题。排查顺序:
|
||||
|
||||
1. Manager 是否有该用户记录。
|
||||
2. JWT/session 是否能通过 Manager 校验。
|
||||
3. 设备注册/心跳是否入库。
|
||||
4. 用户是否绑定 NewAPI channel/group/token。
|
||||
5. NewAPI 返回是否 401/403/模型列表为空。
|
||||
6. Mac/Windows 客户端请求是否走同一 base_url、同一加密/签名逻辑。
|
||||
|
||||
不要只看“客户端显示已登录”,已登录不代表模型、设备、NewAPI 绑定都完整。
|
||||
|
||||
## 10. 网页端 Manager 已做过的重点
|
||||
|
||||
已处理过的方向包括:
|
||||
|
||||
- 注册页国内邮箱提示。
|
||||
- Manager 登录后菜单跳转问题。
|
||||
- 任务总览 / deployments 页面部分英文文案中文化。
|
||||
- 普通 sub 控制面、部署草稿、运行状态、timeline、events、logs、artifacts 展示。
|
||||
- artifact content 获取链路文档。
|
||||
- Azure Key Vault secret_ref 接入方向。
|
||||
- PayPal 充值和 NewAPI 模型费用关系说明文档。
|
||||
|
||||
继续改网页端时必须真实点击验证,尤其是:
|
||||
|
||||
- 登录后左侧菜单。
|
||||
- 模型、渠道、供应商、支付、部署、任务总览。
|
||||
- 创建新运行弹窗/抽屉。
|
||||
- 产物卡片和下载入口。
|
||||
|
||||
## 11. 部署和 git 规则
|
||||
|
||||
生产部署原则:
|
||||
|
||||
```text
|
||||
本地修改
|
||||
-> git commit
|
||||
-> git push 到 heicode-mananger main
|
||||
-> VM 上 git pull
|
||||
-> docker compose ... up -d --build
|
||||
-> sudo docker image prune -f
|
||||
-> 真实接口/页面冒烟
|
||||
```
|
||||
|
||||
注意:
|
||||
|
||||
- 不要用 `scp` 传整份源码到 VM。
|
||||
- `HeiCode-issues.git` 已废弃,后续不用再更新。
|
||||
- VM 构建后必须清理 Docker 镜像,避免磁盘被旧层占满。
|
||||
- 不要提交 `.env`、密钥、token、数据库连接串。
|
||||
|
||||
## 12. 新 AI 接手建议顺序
|
||||
|
||||
1. 读 `AGENTS.md` 和 `heicode/AGENTS.md`。
|
||||
2. 读 `docs/deployment/Heicode-Manager-生产配置与账号交接清单.md`。
|
||||
3. 用 `git status` 确认是否有未提交变更,不要动无关文件。
|
||||
4. 区分当前任务是普通 sub、蜂群、网页端、NewAPI、Azure 还是桌面客户端。
|
||||
5. 先用接口确认真实状态,再下结论。
|
||||
6. 涉及生产前先确认是否需要部署,部署后必须真实冒烟。
|
||||
7. 涉及密码、token、连接串时只写配置名和获取位置,不写明文。
|
||||
|
||||
## 13. 已踩过的典型坑
|
||||
|
||||
| 坑 | 正确处理 |
|
||||
|----|----------|
|
||||
| 把普通 sub 和蜂群混在一起 | 两套模式、两套配置、两套联调清单 |
|
||||
| artifact 摘要当完整交付 | 必须通过 content 接口拿完整产物 |
|
||||
| Runtime 返回 completed 就算成功 | 还要看 usage、artifact、日志、是否失败摘要 |
|
||||
| Key Vault health 报错只改代码 | 先查 Managed Identity 和 Vault 权限 |
|
||||
| NewAPI 一个模型失败就判 Manager 错 | 查 Runtime 请求路径、模型名、request id、上游状态 |
|
||||
| VM 上有 postgres/redis 容器就当生产库 | 以 `SQL_DSN`、`REDIS_CONN_STRING` 和 Azure 托管服务为准 |
|
||||
| 修改网页后不点击验证 | 必须真实打开页面、点菜单、点按钮 |
|
||||
| 把密钥写入 md 方便交接 | 只能写配置名、用途、位置,不能写明文 |
|
||||
|
||||
## 14. 当前后续重点
|
||||
|
||||
| 方向 | 后续任务 |
|
||||
|------|----------|
|
||||
| 普通 sub | 持续和桌面客户端联调真实开发任务,确认 artifact content 是完整业务产物 |
|
||||
| 蜂群 | 如需启用,先配置 `SWARM_RUNTIME_*`,单独跑蜂群 E2E |
|
||||
| Key Vault | 修复 Managed Identity / `AZURE_CLIENT_ID` / Key Vault 权限 |
|
||||
| NewAPI | 保持 Runtime 回传 `newapi_request_id`、usage、成本信息 |
|
||||
| 网页端 | 继续中文化、交互完善、真实点击测试 |
|
||||
| 生产部署 | 每次部署后检查 `/api/status`、登录、deployments、Runtime health |
|
||||
@@ -11,7 +11,7 @@ Heicode 面向**多人协作、可追溯交付**的软件团队:把需求对
|
||||
| `cc-haha/` | **Heicode**(终端与桌面客户端及本地服务;此为源码目录名)。 |
|
||||
| `heicode/` | **Heicode Manager**(网关与管理控制台服务端;此为源码目录名)。 |
|
||||
| `website/` | 产品介绍站点(Next.js;可 `pnpm dev` 或 Docker 预览)。 |
|
||||
| `docs/` | 愿景与范式;**[`docs/milestones/`](./docs/milestones/README.md)** 交付里程碑;**[`docs/integration/`](./docs/integration/README.md)** Agnet 等平台接口设计。 |
|
||||
| `docs/` | 愿景与范式;**[`docs/milestones/`](./docs/milestones/README.md)** 交付里程碑;**[`docs/integration/`](./docs/integration/README.md)** Agent 等平台接口设计。 |
|
||||
|
||||
## 产品在解决什么问题
|
||||
|
||||
@@ -68,7 +68,7 @@ cd website && pnpm install && pnpm dev
|
||||
## 相关外部参考(概念)
|
||||
|
||||
- [oh-my-claudecode](https://ohmyclaudecode.com/) — Claude Code 类工具的高效实践参考。
|
||||
- Agnet 平台以实际部署环境与文档为准。
|
||||
- Agent 平台以实际部署环境与文档为准。
|
||||
|
||||
## 许可证
|
||||
|
||||
|
||||
@@ -7,8 +7,8 @@ Heicode 今日工作总结与后续计划
|
||||
|
||||
1. 项目主线梳理
|
||||
- 已确认 docs/heicode.md 与 docs/plan.md 是当前实施主线。
|
||||
- 已明确旧 Agnet API 草案、旧 M1-M5 计划、旧架构说明不作为当前实施依据。
|
||||
- 已恢复 docs/README.md 为主线文档入口,并加入 Agnet 平台请求契约文档入口。
|
||||
- 已明确旧 Agent API 草案、旧 M1-M5 计划、旧架构说明不作为当前实施依据。
|
||||
- 已恢复 docs/README.md 为主线文档入口,并加入 Agent 平台请求契约文档入口。
|
||||
|
||||
2. P0 文档边界收敛
|
||||
- 已完成 P0:文档入口与实施依据收敛。
|
||||
@@ -21,15 +21,15 @@ Heicode 今日工作总结与后续计划
|
||||
- 已加入 GORM 自动迁移。
|
||||
- 新增认证 API:/api/resources 与 /api/resource-grants。
|
||||
- 支持的资源类型包括:git、sk、project_document、cloud_account、cloud_resource。
|
||||
- 资源授权可表达 tenant/project/resource 到 role/子 Agnet 的最小权限关系。
|
||||
- 资源授权可表达 tenant/project/resource 到 role/子 Agent 的最小权限关系。
|
||||
- 已实现 secret_ref-only 原则:只保存密钥引用,不保存明文密钥。
|
||||
- 已加入校验,拒绝 metadata、permission_scope、constraints 中出现 password、token、secret、private_key、access_key、credential 等明文敏感字段。
|
||||
|
||||
4. Agnet 平台请求参数文档
|
||||
- 已新增文档:docs/integration/agnet-platform-request-contract.md。
|
||||
- 该文档说明 Manager 主动请求 Agnet 平台时需要携带的参数。
|
||||
4. Agent 平台请求参数文档
|
||||
- 已新增文档:docs/integration/agent-platform-request-contract.md。
|
||||
- 该文档说明 Manager 主动请求 Agent 平台时需要携带的参数。
|
||||
- 覆盖内容包括:
|
||||
- 创建子 Agent 部署 POST /api/agnet/deployments。
|
||||
- 创建子 Agent 部署 POST /api/agent/deployments。
|
||||
- orchestration_plan、agents、sk_sources、runtime_execution、sk_access_policy、resource_grants 字段说明。
|
||||
- secret_ref、安全边界、租户/项目边界、预算与模型约束。
|
||||
- 部署状态查询、停止部署。
|
||||
@@ -37,7 +37,7 @@ Heicode 今日工作总结与后续计划
|
||||
- 监控接口:项目 dashboard snapshot、单部署 metrics。
|
||||
- 事件接口、审计日志接口。
|
||||
- SK 快照解析与查询接口。
|
||||
- 已提交:ab71d5b docs: add agnet platform request contract。
|
||||
- 已提交:ab71d5b docs: add agent platform request contract。
|
||||
|
||||
5. 团队协作与校验
|
||||
- 已启动并完成 5 个 OMX worker 的协作任务。
|
||||
@@ -80,7 +80,7 @@ Heicode 今日工作总结与后续计划
|
||||
- 当前未执行生产 VM SSH 部署。
|
||||
- 当前未对 Azure PostgreSQL、Redis、AKS 或其他云资源执行写操作。
|
||||
- 当前未消耗可量化的线上运行资源。
|
||||
- 本次新增的资源模型与 Agnet 请求契约为后续生产部署和平台联调准备。
|
||||
- 本次新增的资源模型与 Agent 请求契约为后续生产部署和平台联调准备。
|
||||
|
||||
4. Git 资源
|
||||
- 当前 main 相对 origin/main ahead 15 个提交。
|
||||
@@ -100,7 +100,7 @@ Heicode 今日工作总结与后续计划
|
||||
2. 当前可部署基础
|
||||
- P0 文档边界已收敛。
|
||||
- P1 Manager 资源模型最小后端能力已实现。
|
||||
- Manager → Agnet 平台接口参数文档已补齐,包含日志和监控接口。
|
||||
- Manager → Agent 平台接口参数文档已补齐,包含日志和监控接口。
|
||||
- 变更包 controller/model/router 的测试与 vet 已通过。
|
||||
|
||||
3. 部署前阻塞/风险
|
||||
@@ -124,21 +124,21 @@ Heicode 今日工作总结与后续计划
|
||||
- 部署后检查服务健康、日志、API 可用性。
|
||||
|
||||
3. P1 后续完善
|
||||
- 将 Resource Binding / Resource Grant 与现有项目、用户、Agnet 部署流更紧密打通。
|
||||
- 将 Resource Binding / Resource Grant 与现有项目、用户、Agent 部署流更紧密打通。
|
||||
- 完善资源授权 revoke/disable/status transition。
|
||||
- 补充跨数据库兼容验证,尤其 PostgreSQL 线上迁移验证。
|
||||
- 补充前端资源绑定与授权页面,避免展示任何 secret。
|
||||
|
||||
4. Agnet 平台联调
|
||||
- 按 docs/integration/agnet-platform-request-contract.md 与 Agnet 平台确认最终字段。
|
||||
4. Agent 平台联调
|
||||
- 按 docs/integration/agent-platform-request-contract.md 与 Agent 平台确认最终字段。
|
||||
- 联调创建部署、查询状态、停止部署、日志拉取、监控快照、事件、审计日志。
|
||||
- 明确哪些接口由 Manager 内部提供,哪些接口由 Agnet 平台提供。
|
||||
- 确认 Agnet 平台对日志和监控返回的脱敏策略。
|
||||
- 明确哪些接口由 Manager 内部提供,哪些接口由 Agent 平台提供。
|
||||
- 确认 Agent 平台对日志和监控返回的脱敏策略。
|
||||
|
||||
5. 测试与质量
|
||||
- 解决或隔离 go test ./... 的既有失败。
|
||||
- 确保 frontend/web 依赖可安装并完成 typecheck/lint/build。
|
||||
- 增加 e2e 验证:创建资源 -> 创建授权 -> 创建 Agnet 部署 -> 查询事件/日志/监控。
|
||||
- 增加 e2e 验证:创建资源 -> 创建授权 -> 创建 Agent 部署 -> 查询事件/日志/监控。
|
||||
|
||||
六、安全说明
|
||||
|
||||
|
||||
@@ -0,0 +1,183 @@
|
||||
# Heicode Manager 统一改造落地计划(Manager 侧)
|
||||
|
||||
> 版本:v0.1(初稿)
|
||||
> 日期:2026-06-01
|
||||
> 负责范围:仅 `heicode-mananger`(Manager 控制面 + `web/default` 前端)
|
||||
> 性质:内部落地计划,随实现推进持续更新。不代表对外承诺。
|
||||
|
||||
> **状态更新(2026-06-01):`agnet → agent` 已按"强制全量、不留兼容"完成。**
|
||||
> 后端 Go(路由 `/api/agent/*`、env `AGENT_*`、结构体/函数/文件名)、前端(`agent-console`/`agent-hub`、`/api/agent` 调用、i18n)、DB(表 `agent_*`、列 `agent_id`,并加幂等迁移 `renameAgnetTablesToAgent` 保住老数据)、文档、compose/.env、脚本均已统一为 `agent`。
|
||||
> `go build ./...` 与 `go test ./controller ./model ./router ./middleware` 全绿;前端 `tsc -b` 通过。
|
||||
> 本文 §3 原描述的"保留 `agnet` 兼容别名/线缆令牌"策略已**作废**——实际采用一次性硬切。唯一保留 `agnet` 字样的是 `model/main.go` 的迁移源表名(迁移所必需)。
|
||||
> **部署待办**:① VM `.env` 的 `AGNET_*` 改名 `AGENT_*`;② 启动时迁移自动 rename 老表(已就绪);③ 其他三仓库(agent_management / HeiCode-Swarm / 客户端)需同步切到 `/api/agent` 与新 env,否则跨服务对接在它们切换前会中断(按你的要求强制先行)。
|
||||
|
||||
## 0. 基准文档
|
||||
|
||||
本计划是以下两份的 **Manager 侧拆解执行版**,结论以原文为准:
|
||||
|
||||
| 来源 | 位置 | 作用 |
|
||||
|---|---|---|
|
||||
| 统一调用方案 v0.1 | gitee `taijibaga/heicodedebug` → `2026-06-01_heicode客户端相关定义.md` | 四仓库总纲:接口归口、模式拆分、状态裁决、产物、回调 |
|
||||
| 全链路代码评审报告 | gitee `taijibaga/heicodedebug` → `2026-06-01_Heicode全链路代码评审报告.md` | P1–P7 问题与证据 |
|
||||
| 桌面 sub 对接文档 | `docs/integration/heicode-desktop-sub-agile-api.md` | 现行普通 sub 契约 |
|
||||
|
||||
四仓库分工(总纲 §4):客户端只调 Manager;**Manager 是唯一接口入口 + Runtime 路由器 + 唯一状态裁判**;`agent_management` 执行 Sub Agile;`HeiCode-Swarm` 执行 Swarm。
|
||||
|
||||
## 1. 贯穿全局的原则(来自总纲)
|
||||
|
||||
1. `agnet` 是历史拼写错误,统一为 `agent`;旧接口兼容期只做转发。
|
||||
2. Sub Agile 与 Swarm 是两套运行时,**不再复用 `/api/swarms` 表达两种模式**,按 `mode` 路由。
|
||||
3. Runtime 只上报**结构化事实**;Manager 据结构化字段裁决 `display_status`;客户端只消费结论,**不再用正则自判产物**。
|
||||
4. 安全红线:只传 `azkv://` 等 `secret_ref`,禁明文密钥;高危操作 Manager 先审批再下发。
|
||||
|
||||
## 2. 已完成(分支 `fix/manager-deliverable-and-secret-validation`,commit `12602eb`,已推送)
|
||||
|
||||
| 项 | 文件 | 内容 | 对应总纲 |
|
||||
|---|---|---|---|
|
||||
| P2 交付物判定 | `controller/agnet_runtime_client.go` | `runtimeArtifactsAreSummaryOnly` 改读结构化 `artifact_type`+文件信号,弃脆弱正则 | §10.6 |
|
||||
| P6a secret_ref 校验 | `controller/resource.go` | Resource CRUD 强制 `azkv://` | §16/§17.6 |
|
||||
| P6b 值级密钥扫描 | `controller/resource.go`、`controller/agnet_control_plane.go` | `containsPlaintextSecret`/`containsSensitiveGrantField` 扫字符串值(sk-/JWT/PEM) | §16 |
|
||||
| P5 默认模型收敛 | `controller/agnet_role_template.go`、`controller/agnet_task_bridge.go` | 单一来源 `defaultAgnetModelID()`(env `AGNET_DEFAULT_MODEL_ID`,默认 `gpt-5.4`);移除 `agnet-model-<role>` 占位回退 | §9 |
|
||||
| P3 状态枚举 | `docs/integration/heicode-desktop-sub-agile-api.md` | 补 `completed` 终态 + runtime_state 镜像 + 未知值兜底 | §10.4 |
|
||||
| 单测 | `controller/agnet_deliverable_secret_test.go` | 覆盖以上行为 | — |
|
||||
|
||||
> 注:`defaultAgnetModelID` 在后续命名统一中应改名 `defaultAgentModelID`,env 改 `AGENT_DEFAULT_MODEL_ID`(保留 `AGNET_*` 回退)。
|
||||
|
||||
## 3. 命名统一 `agnet → agent`
|
||||
|
||||
### 3.1 爆炸半径(仅本仓库,不含另三仓库与跨团队契约)
|
||||
|
||||
**2155 处 / 63 文件**(`rg -i agnet`)。按「客户端/其他服务是否依赖」分类:
|
||||
|
||||
| 类别 | 规模 | 对外可见 | 处理策略 | 风险 |
|
||||
|---|---|---|---|---|
|
||||
| HTTP 路由 `/api/agnet/*` | 路由表 | ✅ 客户端+Runtime | 新增 `/api/agent/*` 别名,旧保留转发 | 🔴 |
|
||||
| JSON 字段 | 仅 `agnet_id`(3 处,含 DB 列) | ✅ resource grant 契约 | 响应 dual-emit `agent_id`,请求 dual-accept | 🟠 |
|
||||
| env 变量 `AGNET_*` | compose + 代码读取 | ✅ 部署配置 | 代码先读 `AGENT_*` 回退 `AGNET_*` | 🟠 |
|
||||
| DB 表 `agnet_*` / `agnet_id` 列 | ~6 表 | ❌ 内部 | **用 GORM `TableName()` 钉住物理名不动**,物理改名作为最后单独迁移 | 🔴 |
|
||||
| Go 内部标识(`AgnetXxx`、函数、文件名 `agnet_*.go`) | ~1800 | ❌ 编译期可查 | 纯重构,分模块小步改 | 🟢 |
|
||||
| 前端(`agnet-console/`、组件、i18n key) | ~250 | ❌ 内部(调的是路由) | 重构,随路由切换 | 🟢 |
|
||||
| 文档 | 多处 | — | 新文档写 `agent`,`agnet` 标 deprecated | 🟢 |
|
||||
|
||||
### 3.2 兼容期规则(总纲 §19)
|
||||
|
||||
1. 旧接口只做转发。2. 新文档只写新接口。3. 新客户端只调新接口。4. Manager 内部存储统一 `agent` 命名。5. 日志可记 legacy route,不展示给普通用户。
|
||||
|
||||
## 4. 路由拆分与归口(总纲 §5)
|
||||
|
||||
### 4.1 客户端 → Manager(新增)
|
||||
```
|
||||
/api/heicode/capabilities
|
||||
/api/heicode/sub-agile/tasks/... (或统一 /api/heicode/tasks + body.mode)
|
||||
/api/heicode/swarm/tasks/...
|
||||
```
|
||||
|
||||
### 4.2 Manager → Runtime(新增,按 mode 分流)
|
||||
```
|
||||
/api/agent/sub-agile/deployments -> agent_management (AGENT_RUNTIME_* / 现 AGNET_RUNTIME_*)
|
||||
/api/agent/swarm/deployments -> HeiCode-Swarm (SWARM_RUNTIME_*)
|
||||
```
|
||||
Manager 已具备双 env 前缀分流机制(`agnetRuntimeClientConfigForMode`);蜂群侧配置就绪只差 `SWARM_RUNTIME_SERVICE_TOKEN`。
|
||||
|
||||
### 4.3 回调(新增 + 旧转发)
|
||||
```
|
||||
新:POST /api/agent/callbacks/runtime-events
|
||||
旧:POST /api/agnet/callbacks/swarm-events (兼容转发到同一 handler)
|
||||
```
|
||||
|
||||
## 5. 状态裁决:Manager 成为唯一裁判(总纲 §10,关联评审 P1)
|
||||
|
||||
### 5.1 三层状态 + display_status
|
||||
- `client_task_status`(客户端本地)/ `cloud_deployment_status`(Manager)/ `runtime_execution_status`(Runtime)
|
||||
- Manager 输出唯一 `display_status` 给客户端。
|
||||
|
||||
### 5.2 Runtime 必须上报的结构化交付物事实(契约)
|
||||
```json
|
||||
"deliverable": {
|
||||
"has_deliverable": true,
|
||||
"summary_only": false,
|
||||
"artifact_ids": ["art_xxx"],
|
||||
"files_modified": ["src/app.ts"],
|
||||
"has_diff": true,
|
||||
"commit_sha": ""
|
||||
}
|
||||
```
|
||||
|
||||
### 5.3 Manager 裁决规则
|
||||
| Runtime 事实 | display_status |
|
||||
|---|---|
|
||||
| `completed` + has_deliverable + !summary_only | `completed` |
|
||||
| `completed` + summary_only / 无 artifact | `completed_without_deliverable` 或 `needs_codegen` |
|
||||
| `failed` | `failed` / `stopped` / `waiting_approval` 同名透传 |
|
||||
|
||||
### 5.4 Manager 落点
|
||||
- `controller/agnet_callback.go`(`applyAgnetCallbackDeploymentState` 消费 `deliverable`,算 verdict)
|
||||
- `model/agnet_deployment.go`(加 `DeliveryVerdict` / display_status 列,三库兼容 `varchar`)
|
||||
- `controller/agnet_control_plane.go`(detail/timeline 暴露字段)
|
||||
- 复用已就绪的 `artifactIsSummaryOnly`(P2)+ usage
|
||||
- **阶段化**:先加派生字段不改 `status`(向后兼容)→ 客户端跟进后再引入新终态值。
|
||||
|
||||
## 6. 模型策略(总纲 §9)
|
||||
|
||||
- Sub Agile:`per_role` / `default`;Swarm:`primary`。
|
||||
- 单一来源默认模型(P5 已起步,待改名 `defaultAgentModelID`)。
|
||||
- Manager 校验:模型存在 / 套餐允许 / 角色允许 / 预算 / Runtime 支持。
|
||||
|
||||
## 7. 项目文件夹产物 `project_folder`(总纲 §12)—— 中期大件
|
||||
|
||||
- artifact 升为两级:`Project Artifact` → `File Artifacts / Directory Entries`。
|
||||
- 新 artifact_type:`project_folder` / `project_archive`(现有 `code_patch/document/...` 保留)。
|
||||
- 新增接口:`.../artifacts/{id}/manifest`、`.../files/{path}`、`.../archive`。
|
||||
- **本地修改回传 + revision 协议**(§12.7):`local-edits` / `batch` / 冲突 `ARTIFACT_REVISION_CONFLICT` / Manager 维护 current accepted revision。
|
||||
- 影响:新 model(artifact revision / project entries)、新 controller、content 代理扩展。**工程量大,单独立项。**
|
||||
|
||||
## 8. 云部署生命周期(总纲 §18)—— 远期
|
||||
|
||||
`project_folder` → 选 target(Azure/阿里云/AWS) → Manager 校验/审批/凭证/预算 → Deploy Worker/Runtime 执行 → `deployment_manifest` artifact 回传。**远期,本轮不展开。**
|
||||
|
||||
## 9. 安全与审计(总纲 §16/§17)
|
||||
|
||||
| 项 | 现状 | 待办 |
|
||||
|---|---|---|
|
||||
| `secret_ref` 强制 `azkv://` | ✅ agnet 路径 + Resource CRUD(P6a) | 覆盖其余写入路径 |
|
||||
| 值级密钥扫描 | ✅ P6b | — |
|
||||
| 高危操作 Manager gate | ❌ 现为立即 `accepted`,阻断依赖 Runtime | Manager 侧加 risk_level/pending 审批 gate(关联 P1/§17.1) |
|
||||
| 短期凭证 lease 真派生 | ❌ 占位(只发 `lease://`,不 mint) | 接 Key Vault 派生短期凭证 |
|
||||
| revoke 同步 Runtime | ⚠️ approve 有 sync,revoke 无 | 补 revoke→Runtime 通知 |
|
||||
| 审计 fail-closed | ⚠️ best-effort | 关键审计改 fail-closed 或告警 |
|
||||
| 客户端禁 inline secret_ref | — | Manager 改为接受 `resource_binding_id`,内部映射 secret_ref(§17.6) |
|
||||
|
||||
## 10. 落地阶段(总纲 §20,Manager 承担)
|
||||
|
||||
| 阶段 | Manager 任务 | 自主性 | 依赖 | 状态 |
|
||||
|---|---|---|---|---|
|
||||
| **1 接口/命名** | `/api/agent/*` 别名、`/api/heicode/{sub-agile,swarm}/*`、callback 新路由、`AGENT_*` env 回退、`agnet_id`→`agent_id` 双字段 | ✅ 加法自主 | — | 待开始 |
|
||||
| **2 模式路由** | 按 `mode` 路由两套 `*_RUNTIME_*` | ✅ | 蜂群 token | 机制已就绪 |
|
||||
| **3 模型策略** | per_role/primary 校验 | ✅ | — | P5 起步 |
|
||||
| **4 状态/产物裁决** | display_status + deliverable 字段 + project_folder + revision | 🟡 | Runtime 发结构化事实 | P1/P2 起步 |
|
||||
| **5 日志/回调/调试** | user_logs/debug_logs 分层 + diagnostics + 统一 callback | ✅ | Runtime callback 切换 | diagnostics 已有雏形 |
|
||||
|
||||
## 11. 跨团队依赖(Manager 做不了,需协调)
|
||||
|
||||
1. **agent_management / HeiCode-Swarm**:上报结构化 `deliverable` 事实;接入 `/api/agent/{sub-agile,swarm}/*` 路由;callback 切 `/api/agent/callbacks/runtime-events`;产出真实 `project_folder`。
|
||||
2. **蜂群 Runtime**:`SWARM_RUNTIME_SERVICE_TOKEN` 安全配置 + 修复 single-agent fallback(评审 P7)。
|
||||
3. **客户端(macOS/Windows)**:切新接口、消费 display_status、project 文件树展示、本地 edit 上传、禁 mock/直连(§17)。
|
||||
|
||||
## 12. 风险与红线
|
||||
|
||||
- **`agnet` 是 load-bearing**(路由/env/DB/跨服务契约)——**严禁全局 sed**,必须加法别名 + 兼容期。
|
||||
- DB 物理表/列名暂不改,用 `TableName()` 钉住,避免迁移风险。
|
||||
- 跨服务契约字段改动一律 **dual-emit + dual-accept**,且需与调用方协调切换节奏。
|
||||
- 状态裁决先加字段不覆盖 `status`,避免误伤「真完成但 Runtime 未回 artifact」的任务。
|
||||
|
||||
## 13. 待确认事项
|
||||
|
||||
1. 统一方案仍是 **v0.1**,落地前需与作者对齐版本/范围。
|
||||
2. approval decision 回传路径:`/api/agent/{mode}/deployments/{id}/approvals/{approval_id}` 的最终形态。
|
||||
3. 客户端→Manager 路由用「分模式」还是「统一 `/api/heicode/tasks` + body.mode」。
|
||||
4. `needs_codegen` 与 `completed_without_deliverable` 的判定边界(何时用哪个)。
|
||||
5. project_folder / 云部署的优先级与排期(是否本阶段做)。
|
||||
|
||||
---
|
||||
|
||||
*维护:本文件随实现进度更新;每完成一项在「已完成/状态」列标注 commit。*
|
||||
+4
-4
@@ -10,9 +10,9 @@
|
||||
| [`heicode-manager-sub-swarm-progress-checklist.md`](./heicode-manager-sub-swarm-progress-checklist.md) | Heicode Manager sub 模式、瀑布/敏捷、蜂群模式的已完成/未完成/依赖/风险/下一步进度清单 |
|
||||
| [`heicode-manager-standalone-execution-plan.md`](./heicode-manager-standalone-execution-plan.md) | Heicode Manager 端可独立完成任务的执行计划、顺序、验收标准和边界 |
|
||||
| [`integration/Heicode-登录接口对接文档.md`](./integration/Heicode-登录接口对接文档.md) | 已上线登录接口对接文档 |
|
||||
| [`integration/agnet-platform-request-contract.md`](./integration/agnet-platform-request-contract.md) | Manager 请求 Agnet 平台时携带的部署、日志、监控、事件与审计接口参数 |
|
||||
| [`deployment/azure-production-deploy-guardrails.md`](./deployment/azure-production-deploy-guardrails.md) | Azure VM / PostgreSQL / Redis / Agnet / NewAPI 生产部署前的安全守卫、环境变量注入和验证计划 |
|
||||
| [`integration/agent-platform-request-contract.md`](./integration/agent-platform-request-contract.md) | Manager 请求 Agent 平台时携带的部署、日志、监控、事件与审计接口参数 |
|
||||
| [`deployment/azure-production-deploy-guardrails.md`](./deployment/azure-production-deploy-guardrails.md) | Azure VM / PostgreSQL / Redis / Agent / NewAPI 生产部署前的安全守卫、环境变量注入和验证计划 |
|
||||
|
||||
旧 Agnet API 草案、旧里程碑、旧架构说明和旧上手材料不再作为实施依据。后续文档和实现以 `heicode.md` 与 `plan.md` 为准;生产部署操作以安全守卫文档约束,且不得覆盖产品/架构主线。
|
||||
旧 Agent API 草案、旧里程碑、旧架构说明和旧上手材料不再作为实施依据。后续文档和实现以 `heicode.md` 与 `plan.md` 为准;生产部署操作以安全守卫文档约束,且不得覆盖产品/架构主线。
|
||||
|
||||
代码中的过渡期命名、旧接口注释或旧 UI 文案只作为现状参考;若与 `heicode.md` / `plan.md` 冲突,应先更新实现或另行补充当前主线文档,不得恢复旧 Agnet/M1-M5 草案作为依据。
|
||||
代码中的过渡期命名、旧接口注释或旧 UI 文案只作为现状参考;若与 `heicode.md` / `plan.md` 冲突,应先更新实现或另行补充当前主线文档,不得恢复旧 Agent/M1-M5 草案作为依据。
|
||||
|
||||
@@ -43,22 +43,22 @@ HEICODE_PUBLIC_BASE_URL=https://code.xinghanlab.com
|
||||
### 2.2 Agent Manager / 普通 sub / 蜂群联调配置
|
||||
|
||||
```env
|
||||
AGNET_RUNTIME_ENABLED=true
|
||||
AGNET_RUNTIME_BASE_URL=http://20.212.121.126
|
||||
AGNET_RUNTIME_CREATE_PATH=/api/swarms
|
||||
AGNET_RUNTIME_STOP_PATH=/api/swarms/{swarm_id}/stop
|
||||
AGNET_RUNTIME_APPROVAL_DECISION_PATH=/api/swarms/{swarm_id}/approvals/{approval_id}
|
||||
AGNET_RUNTIME_HEALTH_PATH=/api/agnet/health
|
||||
AGNET_RUNTIME_TIMEOUT_SECONDS=15
|
||||
AGNET_RUNTIME_SERVICE_TOKEN=<生产 service token>
|
||||
AGNET_CALLBACK_TOKEN=<生产 callback 兼容 token>
|
||||
AGNET_RUNTIME_CALLBACK_SIGNING_SECRET_REF=azkv://heicode-kv.vault.azure.net/secrets/agnet-callback-signing-key
|
||||
AGENT_RUNTIME_ENABLED=true
|
||||
AGENT_RUNTIME_BASE_URL=http://20.212.121.126
|
||||
AGENT_RUNTIME_CREATE_PATH=/api/swarms
|
||||
AGENT_RUNTIME_STOP_PATH=/api/swarms/{swarm_id}/stop
|
||||
AGENT_RUNTIME_APPROVAL_DECISION_PATH=/api/swarms/{swarm_id}/approvals/{approval_id}
|
||||
AGENT_RUNTIME_HEALTH_PATH=/api/agent/health
|
||||
AGENT_RUNTIME_TIMEOUT_SECONDS=15
|
||||
AGENT_RUNTIME_SERVICE_TOKEN=<生产 service token>
|
||||
AGENT_CALLBACK_TOKEN=<生产 callback 兼容 token>
|
||||
AGENT_RUNTIME_CALLBACK_SIGNING_SECRET_REF=azkv://heicode-kv.vault.azure.net/secrets/agent-callback-signing-key
|
||||
```
|
||||
|
||||
当前生产为了让 Agent Manager 自动 callback 先跑通,配置了 HMAC fallback:
|
||||
|
||||
```env
|
||||
AGNET_CALLBACK_SIGNING_SECRET=<生产 callback 签名密钥>
|
||||
AGENT_CALLBACK_SIGNING_SECRET=<生产 callback 签名密钥>
|
||||
```
|
||||
|
||||
正式方案建议用 Azure Key Vault 托管该签名密钥,减少明文环境变量。
|
||||
@@ -108,7 +108,7 @@ Key Vault:`heicode-kv`
|
||||
Key Vault 里必须存在:
|
||||
|
||||
```text
|
||||
agnet-callback-signing-key
|
||||
agent-callback-signing-key
|
||||
```
|
||||
|
||||
建议 Secret value 为 JSON:
|
||||
@@ -153,7 +153,7 @@ agnet-callback-signing-key
|
||||
- Redis 防火墙/VNet 是否允许新服务访问。
|
||||
- Key Vault 防火墙是否允许新服务访问。
|
||||
- 如果使用 VNet 集成,DNS 解析是否正常。
|
||||
- Agent Manager 使用 IP 联调时,后续切域名要同步更新 `AGNET_RUNTIME_BASE_URL`。
|
||||
- Agent Manager 使用 IP 联调时,后续切域名要同步更新 `AGENT_RUNTIME_BASE_URL`。
|
||||
|
||||
## 6. 域名和回调地址
|
||||
|
||||
@@ -176,7 +176,7 @@ HEICODE_PUBLIC_BASE_URL=https://code.xinghanlab.com
|
||||
因为 Manager 发给 Agent Manager 的 callback 地址会基于它生成:
|
||||
|
||||
```text
|
||||
https://code.xinghanlab.com/api/agnet/callbacks/swarm-events
|
||||
https://code.xinghanlab.com/api/agent/callbacks/swarm-events
|
||||
```
|
||||
|
||||
如果临时域名联调,要么设置临时 `HEICODE_PUBLIC_BASE_URL`,要么确保正式域名已经能路由到新服务。
|
||||
@@ -219,7 +219,7 @@ curl -fsS https://code.xinghanlab.com/api/status
|
||||
使用测试用户登录后验证:
|
||||
|
||||
- 登录成功。
|
||||
- `/api/agnet/user/deployments` 返回正常。
|
||||
- `/api/agent/user/deployments` 返回正常。
|
||||
- 页面 `https://code.xinghanlab.com/deployments` 可打开。
|
||||
|
||||
### 8.3 Key Vault 健康
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
# Azure 生产部署安全守卫与验证计划
|
||||
|
||||
本文用于 Heicode Manager / Agnet / NewAPI 相关生产发布前的人工执行检查。它只描述安全命令、环境变量名和验证项,不保存任何真实地址、账号、密码、Token、连接串、SSH key 或云访问密钥。
|
||||
本文用于 Heicode Manager / Agent / NewAPI 相关生产发布前的人工执行检查。它只描述安全命令、环境变量名和验证项,不保存任何真实地址、账号、密码、Token、连接串、SSH key 或云访问密钥。
|
||||
|
||||
适用范围:Azure VM、Azure PostgreSQL、Azure Redis、Git 同步、Nginx 统一入口、Heicode Manager 容器、Azure Key Vault 密钥保管、Agnet 平台联调、NewAPI 网关能力验证。
|
||||
适用范围:Azure VM、Azure PostgreSQL、Azure Redis、Git 同步、Nginx 统一入口、Heicode Manager 容器、Azure Key Vault 密钥保管、Agent 平台联调、NewAPI 网关能力验证。
|
||||
|
||||
## 1. 执行原则
|
||||
|
||||
@@ -11,8 +11,8 @@
|
||||
| 凭据处理 | 只使用 VM 环境、交互式 SSH、未提交的 `.env`、Key Vault 或 `secret_ref`;禁止把真实密钥写入 Git、Markdown、终端报告或 team state。 |
|
||||
| Git 发布 | 仅允许快进同步已审核提交;禁止在生产 VM 上提交代码或保存临时补丁。 |
|
||||
| 数据库/Redis | Azure PostgreSQL / Redis 连接串只写入 VM 本地 `.env` 或 Secret Store;验证时只打印变量名和连通性结果,不打印值。 |
|
||||
| Agnet / NewAPI | Manager 只传 `secret_ref`、部署计划、资源授权和审计上下文;禁止把明文云账号、数据库密码、模型 Key 放入请求体。 |
|
||||
| Azure Key Vault | 只允许 Manager/Agnet 通过 Managed Identity 和受控网络访问;如果 Manager 提供客户端验证和绑定接口,Key Vault 不暴露普通公网访问。 |
|
||||
| Agent / NewAPI | Manager 只传 `secret_ref`、部署计划、资源授权和审计上下文;禁止把明文云账号、数据库密码、模型 Key 放入请求体。 |
|
||||
| Azure Key Vault | 只允许 Manager/Agent 通过 Managed Identity 和受控网络访问;如果 Manager 提供客户端验证和绑定接口,Key Vault 不暴露普通公网访问。 |
|
||||
| 生产动作 | 执行 `up -d`、迁移、重启、回滚前必须记录当前镜像/提交和健康检查 URL;失败时停止扩大变更。 |
|
||||
|
||||
## 2. 本地发布前检查
|
||||
@@ -97,7 +97,7 @@ ssh "$REMOTE" "cd '$REMOTE_DIR/heicode' && docker compose -f docker-compose.azur
|
||||
|
||||
- 对外域名统一使用 `heicode.xinghanlab.com`。
|
||||
- Nginx 负责按路由转发 Manager 与 NewAPI,例如 Manager 主站、NewAPI 受控 API 或健康检查路由。
|
||||
- Azure Key Vault 仅供 Manager/Agnet 服务端通过 Managed Identity 访问,不通过 `heicode.xinghanlab.com` 暴露给浏览器用户。
|
||||
- Azure Key Vault 仅供 Manager/Agent 服务端通过 Managed Identity 访问,不通过 `heicode.xinghanlab.com` 暴露给浏览器用户。
|
||||
- Key Vault 应优先使用 Private Endpoint;如临时启用公网访问,必须限制来源并在验证后关闭。
|
||||
|
||||
构建和启动:
|
||||
@@ -122,9 +122,9 @@ VM_HOST="$VM_HOST" VM_USER="$VM_USER" REMOTE_DIR="$REMOTE_DIR/heicode" \
|
||||
./heicode/bin/azure_vm_deploy.sh
|
||||
```
|
||||
|
||||
## 7. Agnet 平台联调守卫
|
||||
## 7. Agent 平台联调守卫
|
||||
|
||||
Manager 请求 Agnet 平台时遵循 `docs/integration/agnet-platform-request-contract.md`:
|
||||
Manager 请求 Agent 平台时遵循 `docs/integration/agent-platform-request-contract.md`:
|
||||
|
||||
- `resource_grants[].secret_ref` 必须是 Secret Store 引用,不能是明文密钥。
|
||||
- `repo_url` 不能包含用户名、密码或 Token。
|
||||
@@ -134,22 +134,22 @@ Manager 请求 Agnet 平台时遵循 `docs/integration/agnet-platform-request-co
|
||||
安全 smoke request 模板:
|
||||
|
||||
```bash
|
||||
export AGNET_BASE_URL='<agnet-platform-base-url>'
|
||||
export AGENT_BASE_URL='<agent-platform-base-url>'
|
||||
export MANAGER_SERVICE_TOKEN_SECRET_REF='<secret-ref-only>'
|
||||
export USER_ID='<manager-user-id>'
|
||||
export BINDING_SCOPE='<resource-binding-scope>'
|
||||
|
||||
# 真实 token 由运行环境注入;禁止把 token 字面值写入命令历史或文档。
|
||||
curl -fsS -X POST "$AGNET_BASE_URL/api/agnet/deployments" \
|
||||
curl -fsS -X POST "$AGENT_BASE_URL/api/agent/deployments" \
|
||||
-H 'Content-Type: application/json' \
|
||||
-H "X-User-Id: $USER_ID" \
|
||||
-H "X-Binding-Scope: $BINDING_SCOPE" \
|
||||
-H "Idempotency-Key: deploy-$(date +%Y%m%d%H%M%S)" \
|
||||
-H "Authorization: Bearer $MANAGER_SERVICE_TOKEN" \
|
||||
--data @docs/integration/safe-agnet-deploy-example.json
|
||||
--data @docs/integration/safe-agent-deploy-example.json
|
||||
```
|
||||
|
||||
若没有 `safe-agnet-deploy-example.json`,先用本地临时文件生成并确认只包含 `secret_ref`,不要提交包含环境特定值的 payload。
|
||||
若没有 `safe-agent-deploy-example.json`,先用本地临时文件生成并确认只包含 `secret_ref`,不要提交包含环境特定值的 payload。
|
||||
|
||||
## 8. 发布后验证清单
|
||||
|
||||
@@ -162,7 +162,7 @@ curl -fsS -X POST "$AGNET_BASE_URL/api/agnet/deployments" \
|
||||
| Git 版本 | `git rev-parse --short HEAD` | 与已审核提交一致。 |
|
||||
| DB/Redis 注入 | `awk -F= ... .env` | 只打印 key,包含 `SQL_DSN`、`REDIS_CONN_STRING`。 |
|
||||
| 登录链路 | 调用登录文档中的生产验证流程 | 不在日志或报告输出 token。 |
|
||||
| Agnet 部署 | 查询部署状态/事件/审计接口 | 能看到 deployment、events、audit,且无明文凭据。 |
|
||||
| Agent 部署 | 查询部署状态/事件/审计接口 | 能看到 deployment、events、audit,且无明文凭据。 |
|
||||
| NewAPI 网关能力 | 使用 Manager 受控模型调用或健康接口 | 只记录 request id、状态码、模型名,不记录 provider key。 |
|
||||
|
||||
## 9. 回滚与停止条件
|
||||
@@ -172,7 +172,7 @@ curl -fsS -X POST "$AGNET_BASE_URL/api/agnet/deployments" \
|
||||
1. 任一日志、响应、Markdown 或 Git diff 中出现真实密钥。
|
||||
2. `git merge --ff-only` 失败或 VM 工作区有未知改动。
|
||||
3. 健康检查连续失败。
|
||||
4. Agnet 平台返回的事件/审计中包含明文凭据。
|
||||
4. Agent 平台返回的事件/审计中包含明文凭据。
|
||||
5. 数据库迁移或容器启动错误无法在一次重试内恢复。
|
||||
|
||||
回滚指针:
|
||||
@@ -186,4 +186,4 @@ ssh "$REMOTE" "cd '$REMOTE_DIR/heicode' && IMAGE_TAG=\$(cat .last_success_image)
|
||||
|
||||
## 10. 本次文档产物状态
|
||||
|
||||
本文件仅准备部署守卫和验证计划;未执行 Git push、SSH 登录、Azure 资源变更、生产容器重启或 Agnet 实际部署。
|
||||
本文件仅准备部署守卫和验证计划;未执行 Git push、SSH 登录、Azure 资源变更、生产容器重启或 Agent 实际部署。
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
|
||||
## 一、核查结论
|
||||
|
||||
本文只列 Manager 端能独立完成的任务。判断标准是:不要求 Heicode 客户端新增功能、不要求蜂群 / Agnet Runtime 提供真实接口、不要求 AKS / NATS / Prometheus 等基础设施先上线。
|
||||
本文只列 Manager 端能独立完成的任务。判断标准是:不要求 Heicode 客户端新增功能、不要求蜂群 / Agent Runtime 提供真实接口、不要求 AKS / NATS / Prometheus 等基础设施先上线。
|
||||
|
||||
| 结论 | 说明 |
|
||||
|---|---|
|
||||
@@ -20,12 +20,12 @@
|
||||
|
||||
| 事项 | 排除原因 | 需要谁配合 |
|
||||
|---|---|---|
|
||||
| 真实创建 Swarm Run 并返回 `swarm_id` | 需要蜂群平台提供 `POST /api/swarms` 或等价生产接口 | 蜂群 / Agnet Runtime |
|
||||
| 子 Agnet claim、heartbeat、release、timeout | 需要 worker runtime 和任务池 | 蜂群 / Agnet Runtime |
|
||||
| handoff、retry、blocked 的真实状态机 | 需要 Runtime 产生任务事件 | 蜂群 / Agnet Runtime |
|
||||
| 真实创建 Swarm Run 并返回 `swarm_id` | 需要蜂群平台提供 `POST /api/swarms` 或等价生产接口 | 蜂群 / Agent Runtime |
|
||||
| 子 Agent claim、heartbeat、release、timeout | 需要 worker runtime 和任务池 | 蜂群 / Agent Runtime |
|
||||
| handoff、retry、blocked 的真实状态机 | 需要 Runtime 产生任务事件 | 蜂群 / Agent Runtime |
|
||||
| 真实日志流和 CPU/内存/耗时指标 | 需要日志/指标源 | 蜂群 / 基础设施 |
|
||||
| 客户端高危审批主弹窗 | 产品要求审批主体验在客户端 | Heicode 客户端 |
|
||||
| 短期凭证真实注入子 Agnet | 需要受控 runtime、身份和网络通道 | 蜂群 / 基础设施 |
|
||||
| 短期凭证真实注入子 Agent | 需要受控 runtime、身份和网络通道 | 蜂群 / 基础设施 |
|
||||
| SK 工具真实调用结果 | 需要 SK 平台或 Runtime 上报 invocation event | 蜂群 / SK 平台 |
|
||||
| 交付结果回到客户端 | 需要客户端展示和 Runtime artifact 输出 | 客户端 + 蜂群 |
|
||||
|
||||
@@ -34,9 +34,9 @@
|
||||
| 顺序 | 任务 | 优先级 | 是否 Manager 独立 | 完成后价值 |
|
||||
|---:|---|---|---|---|
|
||||
| 1 | 增加 sub 模式字段 | P0 | 是 | 已完成:明确任务组织方式,支撑瀑布/敏捷状态展示 |
|
||||
| 2 | 打通 HeicodeTask 到 Agnet deployment 的 Manager 桥接 | P0 | 是 | 已完成:任务可生成 deployment draft |
|
||||
| 3 | 增加用户态 Agnet deployment API | P0 | 是 | 已完成:普通用户可以在自己资源范围内创建/查看/停止 deployment |
|
||||
| 4 | 统一 `/api/swarms` 与 `/api/agnet/deployments` 边界 | P0 | 是 | 已完成:为后续蜂群联调留稳定 adapter |
|
||||
| 2 | 打通 HeicodeTask 到 Agent deployment 的 Manager 桥接 | P0 | 是 | 已完成:任务可生成 deployment draft |
|
||||
| 3 | 增加用户态 Agent deployment API | P0 | 是 | 已完成:普通用户可以在自己资源范围内创建/查看/停止 deployment |
|
||||
| 4 | 统一 `/api/swarms` 与 `/api/agent/deployments` 边界 | P0 | 是 | 已完成:为后续蜂群联调留稳定 adapter |
|
||||
| 5 | 建 callback 接收端骨架 | P0 | 是 | 已完成:先接住事件、artifact、审批请求、timeline |
|
||||
| 6 | 建 artifact 数据模型和 API | P0 | 是 | 已完成:交付物/产物摘要落库并可展示 |
|
||||
| 7 | 增加回调幂等和签名/服务身份校验骨架 | P1 | 是 | 已完成:重复回调不重复写入,支持 token/HMAC |
|
||||
@@ -57,10 +57,10 @@
|
||||
| 项 | 内容 |
|
||||
|---|---|
|
||||
| 目标 | Manager 能记录任务或部署计划采用 `waterfall` / `agile` 哪种组织方式 |
|
||||
| 修改文件 | `heicode/model/agnet_deployment.go`、`heicode/controller/agnet_control_plane.go`、`heicode/web/default/src/features/agnet-console/api.ts`、`heicode/web/default/src/features/agnet-console/create-agnet-deployment-sheet.tsx` |
|
||||
| 修改文件 | `heicode/model/agent_deployment.go`、`heicode/controller/agent_control_plane.go`、`heicode/web/default/src/features/agent-console/api.ts`、`heicode/web/default/src/features/agent-console/create-agent-deployment-sheet.tsx` |
|
||||
| 建议字段 | `sub_mode`,枚举:`waterfall`、`agile`,默认 `agile` |
|
||||
| 验收 | 创建 deployment 后 DB、API response、前端详情都能看到 `sub_mode` |
|
||||
| 测试 | `go test ./controller -run 'TestAgnet.*SubMode|TestAgnetDeployment'`;`cd web/default && bun run typecheck` |
|
||||
| 测试 | `go test ./controller -run 'TestAgent.*SubMode|TestAgentDeployment'`;`cd web/default && bun run typecheck` |
|
||||
|
||||
验收标准:
|
||||
|
||||
@@ -68,13 +68,13 @@
|
||||
- 旧数据无字段时默认按 `agile` 展示。
|
||||
- 不能把 `sub_mode` 当成蜂群 Runtime 流程,只表示 Heicode 任务组织方式。
|
||||
|
||||
### 任务 2:HeicodeTask 到 Agnet deployment 桥接
|
||||
### 任务 2:HeicodeTask 到 Agent deployment 桥接
|
||||
|
||||
| 项 | 内容 |
|
||||
|---|---|
|
||||
| 目标 | Manager 能从 HeicodeTask 的任务卡生成本地 Agnet deployment payload |
|
||||
| 修改文件 | `heicode/controller/agnet_control_plane.go`、新增 `heicode/controller/agnet_task_bridge.go`、`heicode/web/default/src/lib/heicode-mcp.ts`、`heicode/web/default/src/features/tasks/task-card-view.tsx` |
|
||||
| 新增 API | `POST /api/agnet/tasks/:task_id/deployment-draft` 或等价 user-scoped endpoint |
|
||||
| 目标 | Manager 能从 HeicodeTask 的任务卡生成本地 Agent deployment payload |
|
||||
| 修改文件 | `heicode/controller/agent_control_plane.go`、新增 `heicode/controller/agent_task_bridge.go`、`heicode/web/default/src/lib/heicode-mcp.ts`、`heicode/web/default/src/features/tasks/task-card-view.tsx` |
|
||||
| 新增 API | `POST /api/agent/tasks/:task_id/deployment-draft` 或等价 user-scoped endpoint |
|
||||
| 输入 | task id、sub_mode、预算、资源范围、角色模板 |
|
||||
| 输出 | deployment draft 或创建后的 `deployment_id` |
|
||||
| 测试 | 新增 controller 单测;前端 typecheck |
|
||||
@@ -85,14 +85,14 @@
|
||||
- draft 中不能包含明文密钥,只能出现 `secret_ref`。
|
||||
- 找不到 task 或资源授权不足时返回明确错误。
|
||||
|
||||
### 任务 3:用户态 Agnet deployment API
|
||||
### 任务 3:用户态 Agent deployment API
|
||||
|
||||
| 项 | 内容 |
|
||||
|---|---|
|
||||
| 目标 | 普通用户可以创建、查询、停止自己资源范围内的 deployment |
|
||||
| 修改文件 | `heicode/router/api-router.go`、`heicode/controller/agnet_control_plane.go`、`heicode/controller/agnet_control_plane_test.go` |
|
||||
| 当前问题 | `/api/agnet/deployments` 走 `AdminAuth` |
|
||||
| 新增建议 | 保留 admin route;新增 user route:`/api/agnet/user/deployments` 或在同一路由中按 user scope 限制 |
|
||||
| 修改文件 | `heicode/router/api-router.go`、`heicode/controller/agent_control_plane.go`、`heicode/controller/agent_control_plane_test.go` |
|
||||
| 当前问题 | `/api/agent/deployments` 走 `AdminAuth` |
|
||||
| 新增建议 | 保留 admin route;新增 user route:`/api/agent/user/deployments` 或在同一路由中按 user scope 限制 |
|
||||
| 测试 | 普通用户创建成功;越权查询别人 deployment 失败;停止别人 deployment 失败 |
|
||||
|
||||
验收标准:
|
||||
@@ -102,13 +102,13 @@
|
||||
- 请求体伪造别人 `user_context.user_id` 必须被覆盖或拒绝。
|
||||
- resource grant 必须属于当前用户。
|
||||
|
||||
### 任务 4:统一 `/api/swarms` 与 `/api/agnet/deployments` 边界
|
||||
### 任务 4:统一 `/api/swarms` 与 `/api/agent/deployments` 边界
|
||||
|
||||
| 项 | 内容 |
|
||||
|---|---|
|
||||
| 目标 | Manager 内部形成生产蜂群接口 adapter,不再让调用方混淆两个口径 |
|
||||
| 修改文件 | 新增 `heicode/controller/agnet_swarm_adapter.go` 或 `heicode/service/agnet_swarm_adapter.go`,更新 `docs/integration/agnet-platform-request-contract.md` |
|
||||
| 当前现实 | 本地已有 `/api/agnet/deployments`,蜂群资料包目标接口是 `/api/swarms` |
|
||||
| 修改文件 | 新增 `heicode/controller/agent_swarm_adapter.go` 或 `heicode/service/agent_swarm_adapter.go`,更新 `docs/integration/agent-platform-request-contract.md` |
|
||||
| 当前现实 | 本地已有 `/api/agent/deployments`,蜂群资料包目标接口是 `/api/swarms` |
|
||||
| 独立做法 | 先实现 Manager 内部 adapter 和统一 DTO,真实外呼先留配置开关,默认走本地 control-plane |
|
||||
| 测试 | adapter 单测验证 payload 字段、`secret_ref`、correlation_id、sub_mode |
|
||||
|
||||
@@ -123,8 +123,8 @@
|
||||
| 项 | 内容 |
|
||||
|---|---|
|
||||
| 目标 | Manager 先具备接收蜂群平台回调的 API 和落库能力 |
|
||||
| 修改文件 | `heicode/model/agnet_callback.go`、`heicode/controller/agnet_callback.go`、`heicode/router/api-router.go` |
|
||||
| 新增接口 | `POST /api/agnet/callbacks/swarm-events` |
|
||||
| 修改文件 | `heicode/model/agent_callback.go`、`heicode/controller/agent_callback.go`、`heicode/router/api-router.go` |
|
||||
| 新增接口 | `POST /api/agent/callbacks/swarm-events` |
|
||||
| 独立能力 | 本地模拟 payload 可保存、去重、查询;已补普通 sub 任务事件 schema 校验 |
|
||||
| 测试 | controller 单测覆盖正常保存、重复 event_id 幂等、明文密钥拒绝、handoff 缺字段拒绝 |
|
||||
|
||||
@@ -140,7 +140,7 @@
|
||||
| 项 | 内容 |
|
||||
|---|---|
|
||||
| 目标 | Manager 保存和展示 artifact 摘要,不等 Runtime 真实输出 |
|
||||
| 修改文件 | 新增 `heicode/model/agnet_artifact.go`、`heicode/controller/agnet_artifact.go`、`heicode/web/default/src/features/agnet-console/api.ts`、`heicode/web/default/src/features/agnet-console/pages.tsx` |
|
||||
| 修改文件 | 新增 `heicode/model/agent_artifact.go`、`heicode/controller/agent_artifact.go`、`heicode/web/default/src/features/agent-console/api.ts`、`heicode/web/default/src/features/agent-console/pages.tsx` |
|
||||
| 字段 | `artifact_id`、`deployment_id`、`task_id`、`correlation_id`、`artifact_type`、`title`、`summary`、`uri`、`checksum`、`metadata_json`、`created_at` |
|
||||
| 测试 | model/controller 单测;前端 typecheck |
|
||||
|
||||
@@ -155,8 +155,8 @@
|
||||
| 项 | 内容 |
|
||||
|---|---|
|
||||
| 目标 | 生产联调前先有幂等和认证形状 |
|
||||
| 修改文件 | `heicode/controller/agnet_callback.go`、新增 `heicode/middleware/agnet_callback_auth.go` |
|
||||
| 机制 | `X-Request-Id`、`X-Correlation-Id`、`Idempotency-Key`、可选 `X-Agnet-Signature` |
|
||||
| 修改文件 | `heicode/controller/agent_callback.go`、新增 `heicode/middleware/agent_callback_auth.go` |
|
||||
| 机制 | `X-Request-Id`、`X-Correlation-Id`、`Idempotency-Key`、可选 `X-Agent-Signature` |
|
||||
| 当前阶段 | 可以先用配置开关和本地测试 token,不接真实 Key Vault service token |
|
||||
| 测试 | 缺少服务 token 时拒绝;重复 key 不重复写入 |
|
||||
|
||||
@@ -170,8 +170,8 @@
|
||||
|
||||
| 项 | 内容 |
|
||||
|---|---|
|
||||
| 目标 | 替换当前 `agnetSnapshots` 内存 map |
|
||||
| 修改文件 | 新增 `heicode/model/agnet_sk_snapshot.go`,修改 `heicode/controller/agnet_control_plane.go` |
|
||||
| 目标 | 替换当前 `agentSnapshots` 内存 map |
|
||||
| 修改文件 | 新增 `heicode/model/agent_sk_snapshot.go`,修改 `heicode/controller/agent_control_plane.go` |
|
||||
| 当前问题 | 容器重启后 `/sk-snapshots` 丢失 |
|
||||
| 测试 | 创建 snapshot 后清空内存,再从 DB 查询仍存在 |
|
||||
|
||||
@@ -186,9 +186,9 @@
|
||||
| 项 | 内容 |
|
||||
|---|---|
|
||||
| 目标 | 按 task/deployment/correlation_id 聚合 Manager 已有记录 |
|
||||
| 修改文件 | `heicode/controller/agnet_control_plane.go`、`heicode/model/agnet_audit.go`、`heicode/web/default/src/features/agnet-console/pages.tsx` |
|
||||
| 修改文件 | `heicode/controller/agent_control_plane.go`、`heicode/model/agent_audit.go`、`heicode/web/default/src/features/agent-console/pages.tsx` |
|
||||
| 聚合内容 | deployment、audit events、approvals、leases、artifacts、callbacks、resource grants |
|
||||
| 新增接口 | `GET /api/agnet/tasks/:task_id/timeline` 或 `GET /api/agnet/deployments/:id/timeline` |
|
||||
| 新增接口 | `GET /api/agent/tasks/:task_id/timeline` 或 `GET /api/agent/deployments/:id/timeline` |
|
||||
| 测试 | 同一 correlation_id 下能聚合多类事件 |
|
||||
|
||||
验收标准:
|
||||
@@ -202,10 +202,10 @@
|
||||
| 项 | 内容 |
|
||||
|---|---|
|
||||
| 目标 | 在蜂群 Runtime 未接入前,Manager 能用模拟事件自测完整链路 |
|
||||
| 修改文件 | `heicode/controller/agnet_callback.go`、`heicode/router/api-router.go`、可选新增 `heicode/controller/agnet_smoke.go` |
|
||||
| 接口建议 | admin-only `POST /api/agnet/dev/simulate-run` |
|
||||
| 修改文件 | `heicode/controller/agent_callback.go`、`heicode/router/api-router.go`、可选新增 `heicode/controller/agent_smoke.go` |
|
||||
| 接口建议 | admin-only `POST /api/agent/dev/simulate-run` |
|
||||
| 生成内容 | deployment accepted、task.created、task.claimed、task.heartbeat、task.blocked、handoff.requested、handoff.completed、task.retried、task.completed、artifact.created、approval.requested、timeline.updated |
|
||||
| 测试 | 单测验证模拟后 timeline/artifacts/audit 可查;`scripts/agnet_sub_mode_smoke.py` 可跑生产健康和 deployment timeline 冒烟 |
|
||||
| 测试 | 单测验证模拟后 timeline/artifacts/audit 可查;`scripts/agent_sub_mode_smoke.py` 可跑生产健康和 deployment timeline 冒烟 |
|
||||
|
||||
验收标准:
|
||||
|
||||
@@ -219,7 +219,7 @@
|
||||
| 项 | 内容 |
|
||||
|---|---|
|
||||
| 目标 | 用户能看到 Manager 自己可提供的闭环信息 |
|
||||
| 修改文件 | `heicode/web/default/src/features/agnet-console/pages.tsx`、`api.ts`、必要时新增组件 |
|
||||
| 修改文件 | `heicode/web/default/src/features/agent-console/pages.tsx`、`api.ts`、必要时新增组件 |
|
||||
| 展示内容 | sub_mode、deployment 来源 task、callbacks、artifacts、timeline、SK snapshots 持久化状态 |
|
||||
| 测试 | `cd heicode/web/default && bun run typecheck`;本地页面点击冒烟 |
|
||||
|
||||
@@ -263,7 +263,7 @@
|
||||
|---|---|
|
||||
| 目标 | 桌面客户端调用 Manager 的 sub 流程接口时,支持与模型调用一致的 V2 应用层 body 加密;浏览器后台页面继续兼容普通 HTTPS JSON |
|
||||
| 修改文件 | `docs/integration/heicode-desktop-sub-agile-api.md`、`docs/heicode-runtime-auth-newapi-secret-design.md`、必要时补充 `heicode/middleware` 或客户端 SDK 加解密模块 |
|
||||
| 当前现实 | `/api/agnet/user/*` 与 `/api/heicode-auth/*` 已支持 `Content-Encoding: heicode-aead-v1`;Manager 会先解密并校验设备签名,再把明文 JSON 交给业务处理或代理给上游 |
|
||||
| 当前现实 | `/api/agent/user/*` 与 `/api/heicode-auth/*` 已支持 `Content-Encoding: heicode-aead-v1`;Manager 会先解密并校验设备签名,再把明文 JSON 交给业务处理或代理给上游 |
|
||||
| 安全红线 | 请求体、回调、日志、artifact metadata、timeline 中禁止出现明文密码、Token、私钥、连接串、云 access key、模型 key;长期凭证只传 `secret_ref` |
|
||||
| 后续判断 | 桌面客户端需要在 sub 流程调用中复用模型调用的 encryptedFetch/V2 签名能力;服务端保留未加密 JSON 只用于 Manager Web 控制台兼容 |
|
||||
| 测试 | 单测覆盖 V2 malformed 请求拒绝、普通代理请求兼容;生产需用真实已配对设备发起加密 sub 请求冒烟 |
|
||||
@@ -280,12 +280,12 @@
|
||||
| 项 | 内容 |
|
||||
|---|---|
|
||||
| 目标 | Manager 不依赖客户端或 Runtime 改接口,也能把“callback 已到”和“Runtime/Agent 是否真的产出交付物”分开展示 |
|
||||
| 修改文件 | `heicode/controller/agnet_runtime_client.go`、`heicode/router/api-router.go`、`heicode/web/default/src/features/agnet-console/api.ts`、`heicode/web/default/src/features/agnet-console/pages.tsx` |
|
||||
| 新增 API | `GET /api/agnet/user/deployments/:deployment_id/runtime-diagnostics` |
|
||||
| 诊断来源 | 用 deployment 记录里的 `runtime_swarm_id` / `runtime_deployment_id` 查询 Runtime status;默认路径 `/api/swarms/{swarm_id}/status`,可用 `AGNET_RUNTIME_STATUS_PATH` / `SWARM_RUNTIME_STATUS_PATH` 配置 |
|
||||
| 修改文件 | `heicode/controller/agent_runtime_client.go`、`heicode/router/api-router.go`、`heicode/web/default/src/features/agent-console/api.ts`、`heicode/web/default/src/features/agent-console/pages.tsx` |
|
||||
| 新增 API | `GET /api/agent/user/deployments/:deployment_id/runtime-diagnostics` |
|
||||
| 诊断来源 | 用 deployment 记录里的 `runtime_swarm_id` / `runtime_deployment_id` 查询 Runtime status;默认路径 `/api/swarms/{swarm_id}/status`,可用 `AGENT_RUNTIME_STATUS_PATH` / `SWARM_RUNTIME_STATUS_PATH` 配置 |
|
||||
| 普通 sub / 蜂群边界 | 返回 `runtime_mode`,页面分别显示“普通 sub 模式”或“蜂群模式”,不把二者合并成一个流程 |
|
||||
| 已识别异常 | Runtime agent failed、completed 但存在 failed agents、只有 `Runtime execution summary` 兜底产物、模型 token 用量为 0、Runtime 状态查询失败 |
|
||||
| 测试 | `go test ./controller -run TestAgnetRuntimeDiagnosticsWarnsOnCompletedRuntimeWithFailedAgents -count=1`;前端 `bun run build:check` |
|
||||
| 测试 | `go test ./controller -run TestAgentRuntimeDiagnosticsWarnsOnCompletedRuntimeWithFailedAgents -count=1`;前端 `bun run build:check` |
|
||||
|
||||
验收标准:
|
||||
|
||||
@@ -299,11 +299,11 @@
|
||||
| 项 | 内容 |
|
||||
|---|---|
|
||||
| 目标 | 对齐 Agent Manager v2.1.10 的产物获取流程:Manager / 前端先查 artifact 列表,再通过用户态 content 代理接口获取完整文件 |
|
||||
| 修改文件 | `heicode/model/agnet_artifact.go`、`heicode/controller/agnet_callback.go`、`heicode/controller/agnet_runtime_client.go`、`heicode/router/api-router.go`、`heicode/web/default/src/features/agnet-console/pages.tsx` |
|
||||
| 新增 API | `GET /api/agnet/user/deployments/:deployment_id/artifacts/:artifact_id/content` |
|
||||
| Runtime 目标路径 | 默认 `GET /api/swarms/{swarm_id}/artifacts/{artifact_id}/content`,可用 `AGNET_RUNTIME_ARTIFACT_CONTENT_PATH` / `SWARM_RUNTIME_ARTIFACT_CONTENT_PATH` 配置 |
|
||||
| 修改文件 | `heicode/model/agent_artifact.go`、`heicode/controller/agent_callback.go`、`heicode/controller/agent_runtime_client.go`、`heicode/router/api-router.go`、`heicode/web/default/src/features/agent-console/pages.tsx` |
|
||||
| 新增 API | `GET /api/agent/user/deployments/:deployment_id/artifacts/:artifact_id/content` |
|
||||
| Runtime 目标路径 | 默认 `GET /api/swarms/{swarm_id}/artifacts/{artifact_id}/content`,可用 `AGENT_RUNTIME_ARTIFACT_CONTENT_PATH` / `SWARM_RUNTIME_ARTIFACT_CONTENT_PATH` 配置 |
|
||||
| 安全边界 | Manager 先校验当前用户拥有 deployment,再校验 artifact 属于该 deployment;不暴露 Azure Blob 凭据、SAS URL 或 Runtime 内网信息 |
|
||||
| 测试 | `go test ./controller -run TestAgnetArtifactContentProxiesRuntimeContent -count=1` |
|
||||
| 测试 | `go test ./controller -run TestAgentArtifactContentProxiesRuntimeContent -count=1` |
|
||||
|
||||
验收标准:
|
||||
|
||||
@@ -319,7 +319,7 @@
|
||||
| 顺序 | 任务 |
|
||||
|---:|---|
|
||||
| 1 | sub 模式字段 |
|
||||
| 2 | 用户态 Agnet deployment API |
|
||||
| 2 | 用户态 Agent deployment API |
|
||||
| 3 | HeicodeTask 到 deployment 桥接 |
|
||||
| 4 | 本地模拟蜂群事件冒烟入口 |
|
||||
|
||||
@@ -329,7 +329,7 @@
|
||||
|
||||
| 顺序 | 任务 |
|
||||
|---:|---|
|
||||
| 1 | `/api/swarms` 与 `/api/agnet/deployments` adapter |
|
||||
| 1 | `/api/swarms` 与 `/api/agent/deployments` adapter |
|
||||
| 2 | callback 接收端骨架 |
|
||||
| 3 | artifact 数据模型和 API |
|
||||
| 4 | 回调幂等和服务身份校验骨架 |
|
||||
|
||||
@@ -10,14 +10,14 @@
|
||||
|---|---|
|
||||
| `http://gitee.ath.cx:3000/taijibaga/fengqun/src/branch/main/docs` | 蜂群设计资料包,定义目标驱动蜂群、任务图、claim、heartbeat、handoff、artifact、审批、审计和三方分工 |
|
||||
| `http://gitee.ath.cx:3000/taijibaga/HeiCode-Swarm` | 蜂群项目实现资料,当前 Orchestrator/Agent/Redis/K8s/桌面演示客户端的实际结构 |
|
||||
| `docs/product-package/07-integration-boundaries.md` | Heicode、Manager、Agnet 平台、CodeGW、Azure Key Vault 的边界 |
|
||||
| `docs/product-package/07-integration-boundaries.md` | Heicode、Manager、Agent 平台、CodeGW、Azure Key Vault 的边界 |
|
||||
| `docs/integration/heicode-desktop-sub-agile-api.md` | Heicode 桌面客户端接 Manager 的普通 sub 敏捷流程 |
|
||||
| `docs/integration/Heicode-Manager-PayPal支付接入与计费关系说明.md` | PayPal 收款、Heicode 余额/订阅、NewAPI 模型扣费和 Agent 运行费用边界 |
|
||||
| 当前仓库 `heicode/` 代码 | Manager 端实际实现核查 |
|
||||
|
||||
## 一、核心边界
|
||||
|
||||
蜂群模式不是普通 sub 敏捷/瀑布本身。普通 sub 是 Heicode 的任务组织方式;蜂群是 Agnet/Swarm Runtime 的执行方式。
|
||||
蜂群模式不是普通 sub 敏捷/瀑布本身。普通 sub 是 Heicode 的任务组织方式;蜂群是 Agent/Swarm Runtime 的执行方式。
|
||||
|
||||
| 模式 | Manager 当前职责 | Runtime / Agent Manager 当前职责 | 不能混淆的点 |
|
||||
|---|---|---|---|
|
||||
@@ -28,7 +28,7 @@
|
||||
|---|---|---|---|
|
||||
| Heicode 桌面客户端 | 用户主体验 | 输入目标、持续补充需求、查看反馈、审批高危操作、接收交付结果 | 直接配置 AKS、模型供应商、完整蜂群 payload |
|
||||
| Heicode Manager | 控制面、记录面和用户侧账本入口 | 资源绑定、`secret_ref`、权限清单、生成启动请求、记录 deployment/swarm 映射、回调、artifact、timeline、审批、审计、余额/订阅展示 | 替代客户端做主开发对话,替代 Runtime 执行任务,或把 PayPal 收款当成模型扣费链路 |
|
||||
| HeiCode-Swarm / Agnet Runtime | 执行层 | 创建 Swarm Run、任务图、Agent 编队、claim、heartbeat、handoff、执行、结果回传、真实运行 usage 回传 | 保存长期明文密钥,直接暴露给普通用户,或自行决定用户账本扣费 |
|
||||
| HeiCode-Swarm / Agent Runtime | 执行层 | 创建 Swarm Run、任务图、Agent 编队、claim、heartbeat、handoff、执行、结果回传、真实运行 usage 回传 | 保存长期明文密钥,直接暴露给普通用户,或自行决定用户账本扣费 |
|
||||
|
||||
## 二、目标调用链
|
||||
|
||||
@@ -38,7 +38,7 @@ Heicode 桌面客户端
|
||||
- V2 加密请求 body
|
||||
- task/deployment draft
|
||||
- resource_grants / secret_ref / budget / approval_policy
|
||||
-> Agnet Runtime 或 HeiCode-Swarm
|
||||
-> Agent Runtime 或 HeiCode-Swarm
|
||||
- POST /api/swarms 或兼容创建入口
|
||||
- 返回 swarm_id / runtime_deployment_id
|
||||
<- Runtime callback
|
||||
@@ -54,24 +54,24 @@ Heicode 桌面客户端
|
||||
|
||||
| 能力 | 当前状态 | 代码证据 |
|
||||
|---|---|---|
|
||||
| sub 模式字段 | 已支持 `sub_mode`,默认 `agile`,校验 `agile/waterfall` | `heicode/controller/agnet_control_plane.go`、`heicode/model/agnet_deployment.go` |
|
||||
| 用户态 deployment | 已有 `/api/agnet/user/deployments` 创建、查询、停止、日志、事件、指标、artifact、SK snapshot、timeline | `heicode/router/api-router.go` |
|
||||
| 任务到 deployment draft | 已有 `/api/agnet/user/tasks/:task_id/deployment-draft` | `heicode/controller/agnet_task_bridge.go` |
|
||||
| `/api/swarms` 兼容入口 | 已有用户态 `POST /api/swarms`,内部走 Manager deployment 创建,并作为 adapter source 记录 | `heicode/router/api-router.go`、`AgnetCreateUserSwarm` |
|
||||
| Runtime 创建桥接 | 已能按配置调用 Runtime 创建接口,默认路径 `/api/agnet/deployments`,可用环境变量改为蜂群创建路径 | `heicode/controller/agnet_runtime_client.go` |
|
||||
| Runtime stop 桥接 | 已能在停止 Manager deployment 时调用 Runtime stop | `heicode/controller/agnet_runtime_client.go` |
|
||||
| Runtime 状态诊断 | 已新增用户态只读诊断接口,按 `runtime_mode` 区分普通 sub / 蜂群,查询 Runtime status 并识别 failed agent、兜底摘要 artifact、零 token 用量等异常 | `heicode/controller/agnet_runtime_client.go`、`AgnetGetUserDeploymentRuntimeDiagnostics` |
|
||||
| callback 接收 | 已有 `POST /api/agnet/callbacks/swarm-events` | `heicode/controller/agnet_callback.go` |
|
||||
| callback 鉴权 | 支持 `X-Agnet-Service-Token` 和 HMAC 签名校验,并可从 Key Vault ref 读取签名密钥 | `heicode/controller/agnet_callback.go` |
|
||||
| callback 幂等 | `event_id` / `idempotency_key` 去重,重复回调返回成功但不重复写 | `heicode/model/agnet_callback.go` |
|
||||
| artifact 落库 | `artifact.created` 可生成 artifact 记录,支持用户态列表查询 | `heicode/model/agnet_artifact.go`、`AgnetListUserDeploymentArtifacts` |
|
||||
| artifact 完整内容代理 | 已新增用户态 content 下载接口,Manager 校验 deployment/artifact 权限后代理 Runtime content 接口读取完整产物 | `AgnetGetUserDeploymentArtifactContent`、`callAgnetRuntimeArtifactContent` |
|
||||
| approval 回调 | `approval.requested` 可转成 Manager 审批记录 | `heicode/controller/agnet_callback.go` |
|
||||
| 审批结果回传 Runtime | 用户 approve/reject 后,Manager 可按配置 POST 回 Runtime approval decision,且不发送 `secret_ref` | `heicode/controller/agnet_approval.go`、`heicode/controller/agnet_runtime_client.go` |
|
||||
| timeline 聚合 | 用户态 timeline 聚合 audit、callbacks、artifacts、sk_snapshots | `AgnetGetUserDeploymentTimeline` |
|
||||
| SK snapshot 持久化 | 已有 `agnet_sk_snapshots` 模型和列表查询 | `heicode/model/agnet_sk_snapshot.go` |
|
||||
| 本地模拟事件 | 已有用户态 `simulate-events`;默认模拟会写入 callback、artifact、approval、timeline 记录,用于 Manager 自测展示链路和脱敏检查 | `AgnetSimulateUserDeploymentEvents` |
|
||||
| V2 body 加密 | `/api/agnet/user/*`、`/api/heicode-auth/*`、`/api/swarms` 已按同一套 V2 设备签名和 body 加密路径设计;未加密 Web 控制台仍兼容 session + `New-Api-User` | `heicode/middleware/auth.go`、`heicode/router/api-router.go` |
|
||||
| sub 模式字段 | 已支持 `sub_mode`,默认 `agile`,校验 `agile/waterfall` | `heicode/controller/agent_control_plane.go`、`heicode/model/agent_deployment.go` |
|
||||
| 用户态 deployment | 已有 `/api/agent/user/deployments` 创建、查询、停止、日志、事件、指标、artifact、SK snapshot、timeline | `heicode/router/api-router.go` |
|
||||
| 任务到 deployment draft | 已有 `/api/agent/user/tasks/:task_id/deployment-draft` | `heicode/controller/agent_task_bridge.go` |
|
||||
| `/api/swarms` 兼容入口 | 已有用户态 `POST /api/swarms`,内部走 Manager deployment 创建,并作为 adapter source 记录 | `heicode/router/api-router.go`、`AgentCreateUserSwarm` |
|
||||
| Runtime 创建桥接 | 已能按配置调用 Runtime 创建接口,默认路径 `/api/agent/deployments`,可用环境变量改为蜂群创建路径 | `heicode/controller/agent_runtime_client.go` |
|
||||
| Runtime stop 桥接 | 已能在停止 Manager deployment 时调用 Runtime stop | `heicode/controller/agent_runtime_client.go` |
|
||||
| Runtime 状态诊断 | 已新增用户态只读诊断接口,按 `runtime_mode` 区分普通 sub / 蜂群,查询 Runtime status 并识别 failed agent、兜底摘要 artifact、零 token 用量等异常 | `heicode/controller/agent_runtime_client.go`、`AgentGetUserDeploymentRuntimeDiagnostics` |
|
||||
| callback 接收 | 已有 `POST /api/agent/callbacks/swarm-events` | `heicode/controller/agent_callback.go` |
|
||||
| callback 鉴权 | 支持 `X-Agent-Service-Token` 和 HMAC 签名校验,并可从 Key Vault ref 读取签名密钥 | `heicode/controller/agent_callback.go` |
|
||||
| callback 幂等 | `event_id` / `idempotency_key` 去重,重复回调返回成功但不重复写 | `heicode/model/agent_callback.go` |
|
||||
| artifact 落库 | `artifact.created` 可生成 artifact 记录,支持用户态列表查询 | `heicode/model/agent_artifact.go`、`AgentListUserDeploymentArtifacts` |
|
||||
| artifact 完整内容代理 | 已新增用户态 content 下载接口,Manager 校验 deployment/artifact 权限后代理 Runtime content 接口读取完整产物 | `AgentGetUserDeploymentArtifactContent`、`callAgentRuntimeArtifactContent` |
|
||||
| approval 回调 | `approval.requested` 可转成 Manager 审批记录 | `heicode/controller/agent_callback.go` |
|
||||
| 审批结果回传 Runtime | 用户 approve/reject 后,Manager 可按配置 POST 回 Runtime approval decision,且不发送 `secret_ref` | `heicode/controller/agent_approval.go`、`heicode/controller/agent_runtime_client.go` |
|
||||
| timeline 聚合 | 用户态 timeline 聚合 audit、callbacks、artifacts、sk_snapshots | `AgentGetUserDeploymentTimeline` |
|
||||
| SK snapshot 持久化 | 已有 `agent_sk_snapshots` 模型和列表查询 | `heicode/model/agent_sk_snapshot.go` |
|
||||
| 本地模拟事件 | 已有用户态 `simulate-events`;默认模拟会写入 callback、artifact、approval、timeline 记录,用于 Manager 自测展示链路和脱敏检查 | `AgentSimulateUserDeploymentEvents` |
|
||||
| V2 body 加密 | `/api/agent/user/*`、`/api/heicode-auth/*`、`/api/swarms` 已按同一套 V2 设备签名和 body 加密路径设计;未加密 Web 控制台仍兼容 session + `New-Api-User` | `heicode/middleware/auth.go`、`heicode/router/api-router.go` |
|
||||
| 生产普通 sub 烟测记录 | 2026-05-31 已用生产 Manager 入口完成真实普通 sub 复核:`dep_1d6d66896cc6` -> `swm_03995f7c7a27`,`gpt-5.4`,`tokens_used=2682`,`newapi_request_id=chatcmpl-DlbZce3VZnsv5DptBILFoiRYy5ZHN`,业务 `code_patch` artifact 可通过 Manager content 接口下载 | `docs/integration/heicode-desktop-sub-agile-api.md` |
|
||||
| PayPal/计费边界文档 | 已明确 PayPal 只是收款渠道;模型调用仍走 Heicode/NewAPI 的钱包或订阅额度;Agent 运行费用目前只有预算字段,真实收费需 Runtime usage 回传 | `docs/integration/Heicode-Manager-PayPal支付接入与计费关系说明.md` |
|
||||
|
||||
@@ -81,8 +81,8 @@ Heicode 桌面客户端
|
||||
|---|---|---|---|---|
|
||||
| P0 | 把 `/api/swarms` adapter 文档化并固定字段 | 已完成:`docs/integration/蜂群模式-AgentManager对接任务清单.md` 和 `docs/integration/普通sub敏捷模式-AgentManager对接任务清单.md` 已写清 Runtime create、callback、source、`deployment_id <-> swarm_id` 映射 | 是 | 文档可直接发给 Agent Manager / 蜂群侧核对 |
|
||||
| P0 | 增加 Swarm Run 显示字段 | 已完成:用户态 deployment 详情展示 `runtime_deployment_id`、`runtime_swarm_id`、`runtime_state`,并在相关记录里展示 source | 是 | 用户态 deployment 详情和后台页面能看到 Runtime 映射 |
|
||||
| P0 | 回调事件类型收敛 | 已完成:`GET /api/agnet/callbacks/swarm-events/schema` 输出事件类型、分类和必填字段;callback 接收端按 schema 校验关键 task/handoff/artifact/approval 字段 | 是 | `task.created/claimed/running/completed/failed/handoff/approval/artifact` 都有 schema |
|
||||
| P0 | Runtime 联调配置模板 | 已完成:两份 Agent Manager 对接任务清单已写清 `AGNET_RUNTIME_*`、callback URL、service token/HMAC 方式和验收步骤 | 是 | 蜂群项目按模板能调用 Manager callback |
|
||||
| P0 | 回调事件类型收敛 | 已完成:`GET /api/agent/callbacks/swarm-events/schema` 输出事件类型、分类和必填字段;callback 接收端按 schema 校验关键 task/handoff/artifact/approval 字段 | 是 | `task.created/claimed/running/completed/failed/handoff/approval/artifact` 都有 schema |
|
||||
| P0 | Runtime 联调配置模板 | 已完成:两份 Agent Manager 对接任务清单已写清 `AGENT_RUNTIME_*`、callback URL、service token/HMAC 方式和验收步骤 | 是 | 蜂群项目按模板能调用 Manager callback |
|
||||
| P1 | 审批结果回传 Runtime 联调 | Manager adapter 已有;仍需要 Runtime 提供接收接口并验证状态继续/停止 | 需要 Runtime 接口 | 审批通过/拒绝后 Runtime 状态能继续或停止 |
|
||||
| P1 | Artifact 展示优化 | Manager 端已完成:页面展示 artifact 类型、摘要和 URI;真实 `code_patch/document/test_report/deployment_manifest` 仍需 Runtime 输出 | 需要 Runtime 数据 | artifact 页面/详情能按类型展示摘要和链接 |
|
||||
| P1 | Artifact 完整内容下载 | 已完成:用户态 `/artifacts/{artifact_id}/content` 代理 Runtime content,页面提供下载入口 | 是 | artifact 属于当前用户 deployment 才能下载,响应透传 Runtime 文件内容 |
|
||||
@@ -91,8 +91,8 @@ Heicode 桌面客户端
|
||||
| P1 | 日志/指标真实来源标识 | 已完成:logs/metrics API 返回 `data_source`、`runtime_source`,当前明确是 Manager control-plane / estimated,不伪装 Runtime 真实指标 | 需要 Runtime 数据 | 页面和 API 响应能区分来源 |
|
||||
| P1 | Agent 运行费用口径收敛 | 已完成文档口径:`budget.max_tokens/max_cost_usd/max_duration_sec` 是预算约束,不等于真实扣费账本;真实收费必须依赖 Runtime 回传 usage | Manager 已完成文档,真实数据需 Runtime | 页面/文档不把 estimated budget 说成真实扣费 |
|
||||
| P1 | 高危审批客户端联动文档 | 已完成:`docs/integration/heicode-desktop-sub-agile-api.md` 已包含 approval 查询、approve/reject、awaiting_approval 流程 | 是 | 客户端文档补齐 approval flow |
|
||||
| P2 | 蜂群模式验收脚本 | 已完成:`scripts/agnet_sub_mode_smoke.py` 支持 schema 检查、生产健康检查、可选 simulate-events、可选真实 callback smoke | 是 | 本地/生产能跑出 callback、artifact、approval、timeline 可见 |
|
||||
| P2 | 生产 schema / 认证链路复测 | 本地代码和测试已覆盖;生产公开 `GET /api/agnet/callbacks/swarm-events/schema` 当前返回 404,认证接口需有效登录态或后台 token 才能测 | 是,部署后复测 | 生产 schema 返回 200,用户态/后台态 smoke 能拿到真实数据 |
|
||||
| P2 | 蜂群模式验收脚本 | 已完成:`scripts/agent_sub_mode_smoke.py` 支持 schema 检查、生产健康检查、可选 simulate-events、可选真实 callback smoke | 是 | 本地/生产能跑出 callback、artifact、approval、timeline 可见 |
|
||||
| P2 | 生产 schema / 认证链路复测 | 本地代码和测试已覆盖;生产公开 `GET /api/agent/callbacks/swarm-events/schema` 当前返回 404,认证接口需有效登录态或后台 token 才能测 | 是,部署后复测 | 生产 schema 返回 200,用户态/后台态 smoke 能拿到真实数据 |
|
||||
|
||||
## 五、需要蜂群项目配合的事项
|
||||
|
||||
|
||||
@@ -17,7 +17,7 @@ Manager 端已经具备蜂群联调需要的控制面基础:用户态 deployme
|
||||
| Manager 是受控入口,不暴露完整 payload 给普通用户 | 用户态 draft/create 已有,可由任务卡生成 plan | 部分满足 | 页面还需要更弱化底层 payload,突出启动摘要和状态 | Manager |
|
||||
| Manager 调用 `POST /api/swarms` 创建 Swarm Run | Manager 已有 `/api/swarms` adapter,Runtime create path 可配置 | 部分满足 | 是否真实创建 Swarm Run 取决于蜂群 Runtime 是否提供生产接口 | 蜂群 + Manager |
|
||||
| 保存 `swarm_id`、状态、请求摘要、`correlation_id` | 模型已有 `runtime_swarm_id`、`runtime_state`、payload JSON、correlation metadata | 基本满足 | 真实 `swarm_id` 需要 Runtime 返回 | 蜂群 |
|
||||
| 接收 `swarm-events` 回调 | 已有 `POST /api/agnet/callbacks/swarm-events` | 满足接收能力 | 还缺真实 Runtime 持续回调 | 蜂群 |
|
||||
| 接收 `swarm-events` 回调 | 已有 `POST /api/agent/callbacks/swarm-events` | 满足接收能力 | 还缺真实 Runtime 持续回调 | 蜂群 |
|
||||
| 重复回调幂等 | 已按 `event_id` / `idempotency_key` 去重 | 满足 | 需要蜂群侧稳定传唯一事件 ID | 蜂群 |
|
||||
| 接收 artifact 回调 | `artifact.created` 可落库并查询 | 满足接收能力 | 真实 artifact schema 和文件/分支引用需 Runtime 输出 | 蜂群 |
|
||||
| 展示 Swarm 状态、事件、产物 | deployment detail/events/artifacts/timeline 已有,页面已强化 runtime/simulated 来源和 artifact 类型/URI | 基本满足 | 真实展示内容仍依赖 Runtime 回调真实数据 | 蜂群 |
|
||||
@@ -85,7 +85,7 @@ desktop-client -> Orchestrator(FastAPI) -> Redis -> Agent Pods
|
||||
|
||||
| 缺失项 | 当前状态 | 处理建议 |
|
||||
|---|---|---|
|
||||
| 蜂群验收脚本 | 已新增 `scripts/agnet_sub_mode_smoke.py`,可检查生产 Manager、Agent health 和指定 deployment timeline | 后续按真实 Runtime deployment 固化执行参数 |
|
||||
| 蜂群验收脚本 | 已新增 `scripts/agent_sub_mode_smoke.py`,可检查生产 Manager、Agent health 和指定 deployment timeline | 后续按真实 Runtime deployment 固化执行参数 |
|
||||
| 页面术语统一 | sub/蜂群容易混淆 | 页面和文档统一:sub 是任务组织,swarm 是执行层 |
|
||||
| 兼容 `HeiCode-Swarm` demo client 的说明 | 容易误认为正式 Heicode 桌面客户端 | 文档明确 demo client 不等于 `cc-haha` 正式客户端 |
|
||||
|
||||
|
||||
@@ -2,26 +2,26 @@
|
||||
|
||||
日期:2026-05-04
|
||||
|
||||
本文修正 Manager、Agnet 平台、NewAPI 与 Azure Key Vault 之间的运行时边界。若本文与旧文档中 `tenant`、`project` 或平台侧批准描述冲突,以本文为准。
|
||||
本文修正 Manager、Agent 平台、NewAPI 与 Azure Key Vault 之间的运行时边界。若本文与旧文档中 `tenant`、`project` 或平台侧批准描述冲突,以本文为准。
|
||||
|
||||
## 一、用户输入在哪里
|
||||
|
||||
用户输入发生在 Heicode 的用户侧入口,也就是 Manager/客户端的“想法输入”主流程。
|
||||
|
||||
用户登录后输入目标、需求、约束、绑定的 Git/SK/云资源选择,以及是否批准高危操作。Manager 负责把这些输入整理成 Agnet 平台可执行的 work request:
|
||||
用户登录后输入目标、需求、约束、绑定的 Git/SK/云资源选择,以及是否批准高危操作。Manager 负责把这些输入整理成 Agent 平台可执行的 work request:
|
||||
|
||||
- 用户想法和自然语言需求。
|
||||
- 绑定的 Git 仓库、分支、路径范围和写入权限。
|
||||
- 绑定的 SK 仓库或技能包。
|
||||
- 绑定的云资源元数据和允许动作。
|
||||
- 子 Agnet 角色、数量、运行模型和预算限制。
|
||||
- 子 Agent 角色、数量、运行模型和预算限制。
|
||||
- 客户端已经确认的高危操作审批结果。
|
||||
|
||||
Manager 不应为了团队开发控制额外发明 `tenant/project` 产品概念。当前团队开发边界优先来自绑定的 Git 仓库、允许路径、分支策略、Agnet 角色和资源授权。
|
||||
Manager 不应为了团队开发控制额外发明 `tenant/project` 产品概念。当前团队开发边界优先来自绑定的 Git 仓库、允许路径、分支策略、Agent 角色和资源授权。
|
||||
|
||||
## 二、Manager 用户复用 Agnet 登录体系
|
||||
## 二、Manager 用户复用 Agent 登录体系
|
||||
|
||||
Manager User 应复用 Heicode/Agnet 已上线登录体系,不再另建一套独立身份。
|
||||
Manager User 应复用 Heicode/Agent 已上线登录体系,不再另建一套独立身份。
|
||||
|
||||
参考 [`integration/Heicode-登录接口对接文档.md`](./integration/Heicode-登录接口对接文档.md),登录流程已经提供:
|
||||
|
||||
@@ -51,7 +51,7 @@ JWT 中也包含:
|
||||
因此 Manager 的认证设计应是:
|
||||
|
||||
```text
|
||||
用户登录 Heicode/Agnet 认证体系
|
||||
用户登录 Heicode/Agent 认证体系
|
||||
-> Manager 校验 token 并读取 /api/auth/me
|
||||
-> Manager 使用 user.id / sub 作为业务用户 ID
|
||||
-> Manager 使用 channelId 关联 NewAPI 余额、用量和扣费查询
|
||||
@@ -62,7 +62,7 @@ Manager 可以有本地 user cache,但 canonical user identity 应来自登录
|
||||
|
||||
## 三、高危操作审批与 Azure Key Vault 密钥注入
|
||||
|
||||
高危操作审批只在客户端完成。用户在客户端明确批准后,Manager/Agnet 平台才可以执行对应动作。Agnet 平台不是审批主体,不发起额外审批;它只校验 `approval_id`、审批主体、审批范围、TTL、`risk_level` 和策略是否匹配。
|
||||
高危操作审批只在客户端完成。用户在客户端明确批准后,Manager/Agent 平台才可以执行对应动作。Agent 平台不是审批主体,不发起额外审批;它只校验 `approval_id`、审批主体、审批范围、TTL、`risk_level` 和策略是否匹配。
|
||||
|
||||
密钥处理边界如下:
|
||||
|
||||
@@ -71,16 +71,16 @@ Manager 可以有本地 user cache,但 canonical user identity 应来自登录
|
||||
-> 用户授权或绑定资源
|
||||
-> Manager 通过 VM Managed Identity 写入 Azure Key Vault
|
||||
-> Manager DB 只保存 secret_ref
|
||||
-> Azure Key Vault 通过 Private Endpoint / 防火墙限制,只允许 Manager/Agnet 受控网络访问
|
||||
-> Azure Key Vault 通过 Private Endpoint / 防火墙限制,只允许 Manager/Agent 受控网络访问
|
||||
|
||||
高危操作
|
||||
-> 客户端审批
|
||||
-> Manager/Agnet 平台按 secret_ref 从 Azure Key Vault 获取或派生短期凭证
|
||||
-> 短期凭证可注入子 Agnet
|
||||
-> 子 Agnet 完成任务后凭证过期或撤销
|
||||
-> Manager/Agent 平台按 secret_ref 从 Azure Key Vault 获取或派生短期凭证
|
||||
-> 短期凭证可注入子 Agent
|
||||
-> 子 Agent 完成任务后凭证过期或撤销
|
||||
```
|
||||
|
||||
允许注入子 Agnet 的只能是短期、最小权限、可审计的凭证。长期 Git token、云 access key、SSH 私钥、数据库密码、NewAPI key 原文不得进入 Git、Markdown、普通日志或长期 Agnet 状态。
|
||||
允许注入子 Agent 的只能是短期、最小权限、可审计的凭证。长期 Git token、云 access key、SSH 私钥、数据库密码、NewAPI key 原文不得进入 Git、Markdown、普通日志或长期 Agent 状态。
|
||||
|
||||
公网入口边界:
|
||||
|
||||
@@ -93,7 +93,7 @@ Azure Key Vault 不暴露普通公网入口的检查口径:
|
||||
|
||||
- Key Vault 应优先关闭 Public Network Access,并通过 Private Endpoint 接入 `heicode-vnet`。
|
||||
- 外部客户端只能通过 Manager 的认证、资源绑定、审批和 `secret_ref` 管理接口间接操作密钥引用。
|
||||
- Manager、Agnet 平台和子 Agnet 访问 Azure Key Vault 时必须走 Private Endpoint / Workload Identity / Managed Identity 绑定。
|
||||
- Manager、Agent 平台和子 Agent 访问 Azure Key Vault 时必须走 Private Endpoint / Workload Identity / Managed Identity 绑定。
|
||||
- 健康检查和联调报告只能证明受控网络访问可用;不得把公网可访问作为验收口径。
|
||||
|
||||
短期凭证注入必须满足:
|
||||
@@ -102,7 +102,7 @@ Azure Key Vault 不暴露普通公网入口的检查口径:
|
||||
- 有 `secret_ref` 来源。
|
||||
- 有 TTL 或明确撤销机制。
|
||||
- 有资源范围、路径范围、云资源范围或 API 范围限制。
|
||||
- 有 Agnet deployment、agent role、user id、操作类型的审计记录。
|
||||
- 有 Agent deployment、agent role、user id、操作类型的审计记录。
|
||||
|
||||
## 四、NewAPI 额度与扣费能力
|
||||
|
||||
@@ -125,22 +125,22 @@ NewAPI 官方 skill 文档明确支持:
|
||||
3. Manager 普通用户侧展示应围绕当前登录用户的模型可用性、余额、额度、调用量和调用日志。
|
||||
4. 扣费映射应优先使用 `channelId`、NewAPI user、NewAPI token 或 NewAPI group。
|
||||
5. 若未来需要团队、组织、项目维度账本,应作为独立产品决策重新设计,而不是在当前 Manager 里暗自添加。
|
||||
6. 子 Agnet 的运行模型是 Agnet 平台的部署配置,独立于 NewAPI 的用户、Token、Group 扣费映射;NewAPI 不负责决定子 Agnet 使用哪个模型。
|
||||
6. 子 Agent 的运行模型是 Agent 平台的部署配置,独立于 NewAPI 的用户、Token、Group 扣费映射;NewAPI 不负责决定子 Agent 使用哪个模型。
|
||||
|
||||
推荐映射:
|
||||
|
||||
| Heicode/Agnet 字段 | NewAPI 映射 | 用途 |
|
||||
| Heicode/Agent 字段 | NewAPI 映射 | 用途 |
|
||||
|------|------|------|
|
||||
| `user.id` / JWT `sub` | NewAPI user ref | 标识调用归属用户 |
|
||||
| `channelId` | NewAPI channel/user/group 绑定 | 关联模型渠道、额度或扣费策略 |
|
||||
| 绑定 Git 仓库 | request metadata | 审计某次开发任务来源 |
|
||||
| 预算或用量限制 | token quota 或 group policy | 限制本次任务可消耗额度 |
|
||||
|
||||
子 Agnet 角色、运行模型和实例数量应放在 Agnet 平台 deployment/runtime 配置里,不放进 NewAPI 扣费对象里。
|
||||
子 Agent 角色、运行模型和实例数量应放在 Agent 平台 deployment/runtime 配置里,不放进 NewAPI 扣费对象里。
|
||||
|
||||
## 五、传给 Agnet 平台的用户与扣费上下文
|
||||
## 五、传给 Agent 平台的用户与扣费上下文
|
||||
|
||||
Manager 请求 Agnet 平台部署或执行任务时,应携带登录用户上下文,但不携带真实密钥。
|
||||
Manager 请求 Agent 平台部署或执行任务时,应携带登录用户上下文,但不携带真实密钥。
|
||||
|
||||
建议传递结构:
|
||||
|
||||
@@ -161,11 +161,11 @@ Manager 请求 Agnet 平台部署或执行任务时,应携带登录用户上
|
||||
"quota_ref": "newapi_token_or_group_quota_ref"
|
||||
},
|
||||
"agent_runtime": {
|
||||
"platform": "agnet",
|
||||
"platform": "agent",
|
||||
"agents": [
|
||||
{
|
||||
"role": "backend",
|
||||
"model_ref": "agnet_model_profile_backend",
|
||||
"model_ref": "agent_model_profile_backend",
|
||||
"instance_count": 1
|
||||
}
|
||||
]
|
||||
@@ -190,17 +190,17 @@ Manager 请求 Agnet 平台部署或执行任务时,应携带登录用户上
|
||||
}
|
||||
```
|
||||
|
||||
这里的 `user_context` 用于确认身份,`billing_context` 用于 NewAPI 余额、用量和扣费映射,`agent_runtime` 用于 Agnet 平台独立选择子 Agnet 模型和实例数量,`work_context` 用于团队开发控制,`secret_context` 只传 `secret_ref` 和审批结果。
|
||||
这里的 `user_context` 用于确认身份,`billing_context` 用于 NewAPI 余额、用量和扣费映射,`agent_runtime` 用于 Agent 平台独立选择子 Agent 模型和实例数量,`work_context` 用于团队开发控制,`secret_context` 只传 `secret_ref` 和审批结果。
|
||||
|
||||
Agnet 平台执行时,应把用户、角色、Git 绑定、deployment、Agnet model profile 和 NewAPI 映射写入 metadata 或审计日志。真实 NewAPI key 和 Azure Key Vault 凭证由平台安全通道读取,不进入 Markdown。
|
||||
Agent 平台执行时,应把用户、角色、Git 绑定、deployment、Agent model profile 和 NewAPI 映射写入 metadata 或审计日志。真实 NewAPI key 和 Azure Key Vault 凭证由平台安全通道读取,不进入 Markdown。
|
||||
|
||||
## 六、当前主线结论
|
||||
|
||||
- 用户输入在 Manager/客户端,不在 NewAPI 后台。
|
||||
- Manager User 复用 Heicode/Agnet 登录体系。
|
||||
- Manager User 复用 Heicode/Agent 登录体系。
|
||||
- Manager 不自行发明 tenant/project 作为当前团队或扣费边界。
|
||||
- 团队开发控制优先由绑定 Git、允许路径、Agnet 角色、资源授权表达。
|
||||
- 子 Agnet 的运行模型由 Agnet 平台独立配置,不和 NewAPI 扣费对象混在一起。
|
||||
- 团队开发控制优先由绑定 Git、允许路径、Agent 角色、资源授权表达。
|
||||
- 子 Agent 的运行模型由 Agent 平台独立配置,不和 NewAPI 扣费对象混在一起。
|
||||
- NewAPI 负责模型网关、用户/Token/Group 额度、余额、日志和扣费。
|
||||
- Azure Key Vault 负责长期密钥托管,子 Agnet 只拿短期、最小权限、可审计凭证。
|
||||
- 高危操作审批发生在客户端,审批结果随任务上下文传给 Manager/Agnet 平台。
|
||||
- Azure Key Vault 负责长期密钥托管,子 Agent 只拿短期、最小权限、可审计凭证。
|
||||
- 高危操作审批发生在客户端,审批结果随任务上下文传给 Manager/Agent 平台。
|
||||
|
||||
+32
-32
@@ -21,54 +21,54 @@ Heicode 的最终形态是一款覆盖软件生命周期的智能开发 Code 工
|
||||
|
||||
| 系统 | 定位 | 负责内容 |
|
||||
|------|------|----------|
|
||||
| Heicode Manager | SaaS 用户控制台与编排中枢 | 用户输入、资源绑定、权限分配、Agnet 部署、审计、模型与余额展示 |
|
||||
| Agnet 平台 | 执行与状态平台 | 在 AKS 上部署子 Agnet、运行任务、维护状态、事件、日志和执行元数据 |
|
||||
| Heicode Manager | SaaS 用户控制台与编排中枢 | 用户输入、资源绑定、权限分配、Agent 部署、审计、模型与余额展示 |
|
||||
| Agent 平台 | 执行与状态平台 | 在 AKS 上部署子 Agent、运行任务、维护状态、事件、日志和执行元数据 |
|
||||
| NewAPI | 内部模型网关与计费服务 | 模型渠道、模型调用、额度、余额、调用日志;后台不对普通用户开放 |
|
||||
| Secret Store | 凭证保管库 | 保存 Git token、云密钥、SSH key、数据库密码等真实凭证 |
|
||||
|
||||
Manager 是用户操作入口;Agnet 是运行时执行层;NewAPI 是模型能力服务;Secret Store 是安全凭证底座。
|
||||
Manager 是用户操作入口;Agent 是运行时执行层;NewAPI 是模型能力服务;Secret Store 是安全凭证底座。
|
||||
|
||||
## 三、不可破坏的原则
|
||||
|
||||
1. 在需求和边界没有想清楚前,不改代码。
|
||||
2. NewAPI 保持独立服务,不继续改造成 Manager 的内嵌后台。
|
||||
3. NewAPI 后台不开放给普通 SaaS 用户,模型管理由内部人员完成。
|
||||
4. Manager 只补 NewAPI 没有的后端能力:用户输入编排、资源绑定、权限、Agnet 部署、审计和生命周期管理。
|
||||
5. 用户绑定的是 Agnet 可用资源,不只是 Git 来源。
|
||||
4. Manager 只补 NewAPI 没有的后端能力:用户输入编排、资源绑定、权限、Agent 部署、审计和生命周期管理。
|
||||
5. 用户绑定的是 Agent 可用资源,不只是 Git 来源。
|
||||
6. 密钥不能进入 Git、Markdown、前端、部署摘要或日志。
|
||||
7. Manager 负责资源绑定、权限分配和凭证托管能力;真实密钥放入 Secret Store。
|
||||
8. 子 Agnet 不保存长期密钥,只接收角色、资源元数据、AGENT.md 和受控访问方式。
|
||||
9. Agnet 平台在 AKS 上负责运行时身份、隔离、状态、事件和审计回传。
|
||||
10. 高危操作审批只在客户端完成;审批通过后可以把密钥保管器派生的短期、最小权限凭证注入子 Agnet,但不能注入长期密钥。
|
||||
11. 子 Agnet 的运行模型是 Agnet 平台的部署配置,独立于 NewAPI 的用户、Token、Group 扣费映射。
|
||||
8. 子 Agent 不保存长期密钥,只接收角色、资源元数据、AGENT.md 和受控访问方式。
|
||||
9. Agent 平台在 AKS 上负责运行时身份、隔离、状态、事件和审计回传。
|
||||
10. 高危操作审批只在客户端完成;审批通过后可以把密钥保管器派生的短期、最小权限凭证注入子 Agent,但不能注入长期密钥。
|
||||
11. 子 Agent 的运行模型是 Agent 平台的部署配置,独立于 NewAPI 的用户、Token、Group 扣费映射。
|
||||
|
||||
## 四、Manager 的核心功能
|
||||
|
||||
1. 绑定 GitHub、GitLab、Gitea、Gitee、自建 Git 等代码来源。
|
||||
2. 绑定云资源,例如 AWS、Azure、GCP、虚拟机、数据库、对象存储、Kubernetes 集群。
|
||||
3. 确认项目仓库、SK 仓库、项目文档仓库或二合一仓库。
|
||||
4. 按敏捷或瀑布方法分配子 Agnet 角色。
|
||||
5. 为每个子 Agnet 配置 AGENT.md、可用工具、Git 范围、云资源范围、模型和预算。
|
||||
6. 部署子 Agnet,设置数量、模型和运行环境。
|
||||
7. 观察子 Agnet 活动状态、失败原因、事件和运行日志。
|
||||
4. 按敏捷或瀑布方法分配子 Agent 角色。
|
||||
5. 为每个子 Agent 配置 AGENT.md、可用工具、Git 范围、云资源范围、模型和预算。
|
||||
6. 部署子 Agent,设置数量、模型和运行环境。
|
||||
7. 观察子 Agent 活动状态、失败原因、事件和运行日志。
|
||||
8. 查看审计日志和模型调用日志。
|
||||
9. 查看可用模型、余额、额度和使用情况。
|
||||
10. 展示 NewAPI 对普通用户有意义的能力,隐藏渠道、价格、模型后台管理等管理员能力。
|
||||
|
||||
## 五、资源绑定与密钥托管
|
||||
|
||||
绑定不是保存一串密钥,而是创建面向登录用户、绑定 Git/SK/云资源和子 Agnet 角色的 Resource Grant。
|
||||
绑定不是保存一串密钥,而是创建面向登录用户、绑定 Git/SK/云资源和子 Agent 角色的 Resource Grant。
|
||||
|
||||
```text
|
||||
用户授权 Heicode 使用外部资源
|
||||
-> Manager 记录资源元数据
|
||||
-> Manager 的 Secret Broker 把真实凭证写入 Secret Store
|
||||
-> Manager 生成可审计、可撤销、可分配给子 Agnet 的资源授权
|
||||
-> Manager 生成可审计、可撤销、可分配给子 Agent 的资源授权
|
||||
```
|
||||
|
||||
资源类型:
|
||||
|
||||
| 类型 | 示例 | 子 Agnet 可见内容 |
|
||||
| 类型 | 示例 | 子 Agent 可见内容 |
|
||||
|------|------|------------------|
|
||||
| Git 资源 | GitHub repo、自建 Git、SK repo | repo URL、ref、允许路径、读写范围 |
|
||||
| 云账号 | Azure subscription、AWS account、GCP project | account/project/subscription 元数据、允许动作 |
|
||||
@@ -87,11 +87,11 @@ Resource Binding 建议字段:
|
||||
| 字段 | 含义 | 约束 |
|
||||
|------|------|------|
|
||||
| `id` | 资源绑定 ID | Manager 内部生成 |
|
||||
| `user_id` | 登录用户 ID | 来自 Heicode/Agnet 登录体系的 `user.id` 或 JWT `sub` |
|
||||
| `user_id` | 登录用户 ID | 来自 Heicode/Agent 登录体系的 `user.id` 或 JWT `sub` |
|
||||
| `type` | 资源类型 | `git`、`sk`、`project_doc`、`cloud_account`、`cloud_resource` |
|
||||
| `name` | 用户可见名称 | 不包含密钥 |
|
||||
| `external_ref` | 外部资源定位 | repo URL、subscription ID、resource ID、文档引用等非密钥标识 |
|
||||
| `metadata` | 子 Agnet 可见元数据 | 只包含 ref、允许路径、环境、网络边界等非密钥信息 |
|
||||
| `metadata` | 子 Agent 可见元数据 | 只包含 ref、允许路径、环境、网络边界等非密钥信息 |
|
||||
| `permission_scope` | 可授权动作范围 | 例如 `read`、`write`、`deploy`、`approve_required` |
|
||||
| `constraints` | 使用限制 | 路径、分支、环境、网络、审批要求、TTL 等 |
|
||||
| `secret_ref` | Secret Store 引用 | 可为空;有凭证时只保存引用,不保存原文 |
|
||||
@@ -105,8 +105,8 @@ Resource Grant 建议字段:
|
||||
| `id` | 授权 ID | Manager 内部生成 |
|
||||
| `user_id` / `binding_scope` | 授权归属 | 来自登录用户、绑定 Git/SK/云资源和角色范围 |
|
||||
| `resource_id` | 被授权资源 | 指向 Resource Binding |
|
||||
| `role` | 子 Agnet 角色 | 例如 product、frontend、backend、reviewer、ops |
|
||||
| `agent_id` | 子 Agnet 标识 | 可为空;为空表示授予该项目角色下的下一次部署 |
|
||||
| `role` | 子 Agent 角色 | 例如 product、frontend、backend、reviewer、ops |
|
||||
| `agent_id` | 子 Agent 标识 | 可为空;为空表示授予该项目角色下的下一次部署 |
|
||||
| `allowed_actions` | 本次授权动作 | 必须是 `permission_scope` 的子集 |
|
||||
| `constraints` | 本次授权限制 | 不得放宽 Resource Binding 的限制 |
|
||||
| `status` | 授权状态 | `active`、`suspended`、`revoked`、`expired` |
|
||||
@@ -146,7 +146,7 @@ SaaS 场景下,凭证不能转嫁给用户手工管理。用户负责授权,
|
||||
|
||||
| 方案 | 判断 |
|
||||
|------|------|
|
||||
| HashiCorp Vault | 优先选择。Kubernetes Auth、Policy、TTL、动态密钥、审计能力成熟,适合 AKS 中的子 Agnet 运行时授权 |
|
||||
| HashiCorp Vault | 优先选择。Kubernetes Auth、Policy、TTL、动态密钥、审计能力成熟,适合 AKS 中的子 Agent 运行时授权 |
|
||||
| Infisical | 可选方案。产品体验较好,但需要验证 SaaS 多用户隔离策略和运行时授权能力 |
|
||||
| Azure Key Vault | 适合 Azure 优先部署,也可以作为 Secret Provider 的一种实现 |
|
||||
|
||||
@@ -160,9 +160,9 @@ Secret Broker 负责:
|
||||
- 轮换、撤销、禁用凭证。
|
||||
- 避免密钥进入日志、前端响应、Markdown 和 Git。
|
||||
|
||||
## 七、AKS 上的 Agnet 凭证访问
|
||||
## 七、AKS 上的 Agent 凭证访问
|
||||
|
||||
Agnet 平台底层是 AKS,因此运行时权限应和 Kubernetes 身份绑定。
|
||||
Agent 平台底层是 AKS,因此运行时权限应和 Kubernetes 身份绑定。
|
||||
|
||||
推荐流程:
|
||||
|
||||
@@ -170,13 +170,13 @@ Agnet 平台底层是 AKS,因此运行时权限应和 Kubernetes 身份绑定
|
||||
用户在 Manager 授权资源
|
||||
-> Manager Secret Broker 写入 Secret Store
|
||||
-> Manager 记录 Resource Grant
|
||||
-> Manager 请求 Agnet 平台部署
|
||||
-> Agnet 平台为 deployment / role 创建 K8s ServiceAccount
|
||||
-> Agnet 平台绑定 Vault policy 或 Workload Identity
|
||||
-> 子 Agnet Pod 运行时只能访问被授权的 secret
|
||||
-> Manager 请求 Agent 平台部署
|
||||
-> Agent 平台为 deployment / role 创建 K8s ServiceAccount
|
||||
-> Agent 平台绑定 Vault policy 或 Workload Identity
|
||||
-> 子 Agent Pod 运行时只能访问被授权的 secret
|
||||
```
|
||||
|
||||
子 Agnet 拿到的是角色、目标、AGENT.md、resource context 和 permission manifest,不拿长期密钥。
|
||||
子 Agent 拿到的是角色、目标、AGENT.md、resource context 和 permission manifest,不拿长期密钥。
|
||||
|
||||
运行时访问分两类:
|
||||
|
||||
@@ -209,7 +209,7 @@ Manager 不展示:
|
||||
- 系统管理员用户管理。
|
||||
- NewAPI 原生管理后台入口。
|
||||
|
||||
用户登录 Manager,不直接登录 NewAPI。Manager User 复用 Heicode/Agnet 登录体系,并维护 `user.id`、`channelId` 到 NewAPI 用户、Token、Group、quota 或 usage 的映射。当前不要把 Manager tenant/project 作为扣费和团队开发控制主轴;团队开发控制优先由绑定 Git、路径范围、资源授权和 Agnet 角色表达。子 Agnet 的模型选择、模型 profile 和实例数属于 Agnet 平台部署配置,不放进 NewAPI 扣费对象里。
|
||||
用户登录 Manager,不直接登录 NewAPI。Manager User 复用 Heicode/Agent 登录体系,并维护 `user.id`、`channelId` 到 NewAPI 用户、Token、Group、quota 或 usage 的映射。当前不要把 Manager tenant/project 作为扣费和团队开发控制主轴;团队开发控制优先由绑定 Git、路径范围、资源授权和 Agent 角色表达。子 Agent 的模型选择、模型 profile 和实例数属于 Agent 平台部署配置,不放进 NewAPI 扣费对象里。
|
||||
|
||||
## 九、Markdown 与权限清单
|
||||
|
||||
@@ -217,7 +217,7 @@ Manager 不展示:
|
||||
|
||||
Markdown 可包含:
|
||||
|
||||
- 子 Agnet 角色。
|
||||
- 子 Agent 角色。
|
||||
- 目标任务。
|
||||
- 项目背景。
|
||||
- AGENT.md 来源。
|
||||
@@ -240,7 +240,7 @@ Manager 应生成两类产物:
|
||||
|
||||
| 产物 | 用途 |
|
||||
|------|------|
|
||||
| AGENT.md / resource context | 给子 Agnet 的启动上下文,说明角色和可用资源 |
|
||||
| permission manifest | 给 Agnet 平台和审计系统的结构化权限清单 |
|
||||
| AGENT.md / resource context | 给子 Agent 的启动上下文,说明角色和可用资源 |
|
||||
| permission manifest | 给 Agent 平台和审计系统的结构化权限清单 |
|
||||
|
||||
Markdown 面向模型理解,manifest 面向系统强制执行。
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
# Agent Manager 普通 sub 产物回调缺失问题
|
||||
|
||||
更新时间:2026-05-29
|
||||
发给:Agent Manager / Agnet Runtime 负责人
|
||||
发给:Agent Manager / Agent Runtime 负责人
|
||||
范围:普通 sub 敏捷模式,不包含 HeiCode-Swarm 独立蜂群 Runtime。
|
||||
|
||||
## 1. 问题现象
|
||||
@@ -36,8 +36,8 @@ SK SNAPSHOTS = 0
|
||||
Heicode Manager 生产配置确认当前普通 sub 走:
|
||||
|
||||
```text
|
||||
AGNET_RUNTIME_BASE_URL=http://20.212.121.126
|
||||
AGNET_RUNTIME_CREATE_PATH=/api/swarms
|
||||
AGENT_RUNTIME_BASE_URL=http://20.212.121.126
|
||||
AGENT_RUNTIME_CREATE_PATH=/api/swarms
|
||||
```
|
||||
|
||||
`SWARM_RUNTIME_BASE_URL` 当前为空。
|
||||
@@ -47,7 +47,7 @@ AGNET_RUNTIME_CREATE_PATH=/api/swarms
|
||||
```text
|
||||
Heicode 桌面客户端
|
||||
-> Heicode Manager 生产
|
||||
-> Agent Manager / Agnet Runtime 普通 sub 入口
|
||||
-> Agent Manager / Agent Runtime 普通 sub 入口
|
||||
-> Heicode Manager callback
|
||||
-> 桌面客户端查询 artifacts/timeline
|
||||
```
|
||||
@@ -102,7 +102,7 @@ sk_tool.completed = 0
|
||||
Heicode Manager artifact 表查询结果:
|
||||
|
||||
```text
|
||||
agnet_artifacts where deployment_id = 'dep_fa4f43da9e0a'
|
||||
agent_artifacts where deployment_id = 'dep_fa4f43da9e0a'
|
||||
=> 0 rows
|
||||
```
|
||||
|
||||
@@ -113,7 +113,7 @@ agnet_artifacts where deployment_id = 'dep_fa4f43da9e0a'
|
||||
生产日志中,Agent Manager 在任务完成时间段连续请求:
|
||||
|
||||
```text
|
||||
POST /api/agnet/callbacks/swarm-events
|
||||
POST /api/agent/callbacks/swarm-events
|
||||
```
|
||||
|
||||
HTTP 状态均为:
|
||||
@@ -188,7 +188,7 @@ Logs will be fetched from K8s in Phase 2
|
||||
|
||||
本问题根因不在 Heicode Manager,也不在桌面客户端。
|
||||
|
||||
当前证据指向 Agent Manager / Agnet Runtime:
|
||||
当前证据指向 Agent Manager / Agent Runtime:
|
||||
|
||||
1. Runtime 将 deployment 标记为 `completed`。
|
||||
2. Runtime 自身返回 `artifacts: []`。
|
||||
@@ -206,7 +206,7 @@ Logs will be fetched from K8s in Phase 2
|
||||
普通 sub 任务完成时,Agent Manager 必须向 Heicode Manager callback:
|
||||
|
||||
```text
|
||||
POST https://code.xinghanlab.com/api/agnet/callbacks/swarm-events
|
||||
POST https://code.xinghanlab.com/api/agent/callbacks/swarm-events
|
||||
event_type = artifact.created
|
||||
```
|
||||
|
||||
@@ -370,7 +370,7 @@ Logs will be fetched from K8s in Phase 2
|
||||
|---|---|
|
||||
| Runtime 状态 | deployment 和 agents 状态一致 |
|
||||
| callback | Heicode Manager 收到 `artifact.created` |
|
||||
| Manager artifacts | `/api/agnet/user/deployments/{deployment_id}/artifacts` 返回 total > 0 |
|
||||
| Manager artifacts | `/api/agent/user/deployments/{deployment_id}/artifacts` 返回 total > 0 |
|
||||
| 客户端展示 | 右侧 `ARTIFACTS` 不再是 0 |
|
||||
| 交付物 | 能看到 Git 分支、提交、预览地址、部署清单或最终结果文档 |
|
||||
| usage | 如果真实调用模型,tokens/cost 不应一直为 0 |
|
||||
@@ -380,7 +380,7 @@ Logs will be fetched from K8s in Phase 2
|
||||
|
||||
Heicode Manager 和桌面客户端当前表现是正确反映 Runtime 数据。
|
||||
|
||||
真正缺口在 Agent Manager / Agnet Runtime:
|
||||
真正缺口在 Agent Manager / Agent Runtime:
|
||||
|
||||
```text
|
||||
任务被标记 completed,但 Runtime 没有生成或回传 artifact.created。
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
# Agent Manager 普通 sub 剩余补充要求
|
||||
|
||||
更新时间:2026-05-28
|
||||
发给:Agent Manager / Agnet Runtime 负责人
|
||||
发给:Agent Manager / Agent Runtime 负责人
|
||||
范围:普通 sub 敏捷开发模式,不包含蜂群模式完整 task graph。
|
||||
|
||||
## 1. 当前已验证事实
|
||||
@@ -59,7 +59,7 @@ Heicode Manager 生产版本 `1.4.19` 已完成并验证以下链路:
|
||||
Manager callback 地址:
|
||||
|
||||
```text
|
||||
https://code.xinghanlab.com/api/agnet/callbacks/swarm-events
|
||||
https://code.xinghanlab.com/api/agent/callbacks/swarm-events
|
||||
```
|
||||
|
||||
创建 run 后,Agent Manager 至少需要主动回调这些事件:
|
||||
@@ -224,7 +224,7 @@ Agent Manager 补完后,按以下步骤验收:
|
||||
- `artifact.created`
|
||||
- `budget.alert`
|
||||
5. Manager 查询:
|
||||
- `/api/agnet/user/deployments/{deployment_id}`
|
||||
- `/api/agent/user/deployments/{deployment_id}`
|
||||
- `/events`
|
||||
- `/timeline`
|
||||
- `/artifacts`
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
# Agent Manager 普通 sub 联调整改要求
|
||||
|
||||
更新时间:2026-05-28
|
||||
发给:Agent Manager / Agnet Runtime 负责人
|
||||
发给:Agent Manager / Agent Runtime 负责人
|
||||
范围:Heicode Manager 普通 sub 敏捷模式联调,不包含蜂群模式完整 task graph 的额外能力。
|
||||
|
||||
## 1. 当前结论
|
||||
@@ -10,7 +10,7 @@ Agent Manager 新版文档 `HEICODE_API_INTEGRATION(5).md` 已经补充了 Runti
|
||||
|
||||
按 2026-05-28 生产实测,Manager 与 Agent Manager 的普通 sub 核心通讯链路已经跑通,但还不能认为“真实开发执行结果”完整闭环。当前结论是:
|
||||
|
||||
1. Agent Manager 当前联调统一使用 IP `http://20.212.121.126`;域名 `https://agent-manager.taijiagnet.com` 后续解析和证书就绪后再切换,不作为当前联调阻塞项。
|
||||
1. Agent Manager 当前联调统一使用 IP `http://20.212.121.126`;域名 `https://agent-manager.taijiagent.com` 后续解析和证书就绪后再切换,不作为当前联调阻塞项。
|
||||
2. Manager 生产已切到 `/api/swarms` 创建入口,并验证能拿到 `runtime_swarm_id=swm_*`。
|
||||
3. Agent Manager 自动 callback 已能写入 Manager `events/timeline`,Manager `1.4.19` 开始会把 callback 反写到 deployment 快照。
|
||||
4. 当前仍缺真实产物和真实用量:Agent Manager status 接口返回 `tokens_used=0`、`artifacts=[]`,并出现整体 `completed` 但 agent 仍 `running` 的状态不一致。
|
||||
@@ -18,9 +18,9 @@ Agent Manager 新版文档 `HEICODE_API_INTEGRATION(5).md` 已经补充了 Runti
|
||||
|
||||
Heicode Manager 侧已确认:
|
||||
|
||||
- `POST https://code.xinghanlab.com/api/agnet/callbacks/swarm-events` 生产路由在线。
|
||||
- `POST https://code.xinghanlab.com/api/agent/callbacks/swarm-events` 生产路由在线。
|
||||
- callback 支持 HMAC 和旧 token 兼容;当前生产为了先跑通自动 callback,已配置 HMAC fallback。
|
||||
- `GET https://code.xinghanlab.com/api/agnet/callbacks/swarm-events/schema` 已由 Manager 提供,用于联调前核对事件类型和必填字段。
|
||||
- `GET https://code.xinghanlab.com/api/agent/callbacks/swarm-events/schema` 已由 Manager 提供,用于联调前核对事件类型和必填字段。
|
||||
- Manager 可接收 `deployment.status_changed`、`phase.changed`、`timeline.updated`、`agent.started/completed/crashed`,并在 `1.4.19` 反写 deployment 详情状态。
|
||||
|
||||
## 2. Agent Manager 必须整改的 P0
|
||||
@@ -30,7 +30,7 @@ Heicode Manager 侧已确认:
|
||||
| P0 | 固定联调地址 | 当前阶段约定先走 IP,后续再切域名 | 文档和配置先统一使用 `http://20.212.121.126`;域名切换另行确认 |
|
||||
| P0 | 创建普通 sub deployment | `/api/swarms` 已能创建并返回 `swm_*` | 保持幂等和字段稳定,后续域名切换不能破坏 |
|
||||
| P0 | deployment detail | Manager 已能记录 callback;Agent Manager status 仍有 `completed` 与 agent `running` 不一致 | 返回真实 phase、agent 状态、失败原因和更新时间 |
|
||||
| P0 | deployment stop | `/api/agnet/deployments/{deployment_id}/stop` 已可用 | 停止后 Runtime 真实停止,并回调 stopped 事件 |
|
||||
| P0 | deployment stop | `/api/agent/deployments/{deployment_id}/stop` 已可用 | 停止后 Runtime 真实停止,并回调 stopped 事件 |
|
||||
| P0 | callback 主动推送 | 已验证 status/phase/timeline/agent/budget callback 能进入 Manager | 继续补 artifact、SK、approval 的真实运行数据 |
|
||||
| P0 | usage 回传 | 当前 budget callback 中 token/cost/runtime 多为 0 | 通过 callback 或事件接口回传真实 token、成本、运行时长、资源使用 |
|
||||
|
||||
@@ -39,7 +39,7 @@ Heicode Manager 侧已确认:
|
||||
### 3.1 健康检查
|
||||
|
||||
```http
|
||||
GET /api/agnet/health
|
||||
GET /api/agent/health
|
||||
```
|
||||
|
||||
要求:
|
||||
@@ -50,10 +50,10 @@ GET /api/agnet/health
|
||||
|
||||
### 3.2 创建普通 sub 运行
|
||||
|
||||
普通 sub 当前生产联调使用 Agent Manager `/api/agnet/deployments`,蜂群 `/api/swarms` 另见 `AgentManager蜂群Runtime接口实现要求.md`。
|
||||
普通 sub 当前生产联调使用 Agent Manager `/api/agent/deployments`,蜂群 `/api/swarms` 另见 `AgentManager蜂群Runtime接口实现要求.md`。
|
||||
|
||||
```http
|
||||
POST /api/agnet/deployments
|
||||
POST /api/agent/deployments
|
||||
Authorization: Bearer <HEICODE_SERVICE_TOKEN>
|
||||
Content-Type: application/json
|
||||
X-User-ID: <user_id>
|
||||
@@ -115,8 +115,8 @@ X-Idempotency-Key: <idempotency_key>
|
||||
"resource_grants": []
|
||||
},
|
||||
"callback": {
|
||||
"url": "https://code.xinghanlab.com/api/agnet/callbacks/swarm-events",
|
||||
"signing_secret_ref": "azkv://heicode-kv.vault.azure.net/secrets/agnet-callback-signing-key",
|
||||
"url": "https://code.xinghanlab.com/api/agent/callbacks/swarm-events",
|
||||
"signing_secret_ref": "azkv://heicode-kv.vault.azure.net/secrets/agent-callback-signing-key",
|
||||
"subscribed_events": [
|
||||
"deployment.status_changed",
|
||||
"phase.changed",
|
||||
@@ -154,14 +154,14 @@ X-Idempotency-Key: <idempotency_key>
|
||||
|
||||
要求:
|
||||
|
||||
1. `deployment_id` 必须可用于 `/api/agnet/deployments/{deployment_id}`、`/api/agnet/deployments/{deployment_id}/stop`。
|
||||
1. `deployment_id` 必须可用于 `/api/agent/deployments/{deployment_id}`、`/api/agent/deployments/{deployment_id}/stop`。
|
||||
2. 同一个 `X-Idempotency-Key` 重复请求必须返回同一个运行,不应重复创建。
|
||||
3. 请求缺少 `orchestration_plan`、`callback.url`、`sub_mode`、`user_context` 时必须返回 4xx。
|
||||
|
||||
### 3.3 查询普通 sub deployment
|
||||
|
||||
```http
|
||||
GET /api/agnet/deployments/{deployment_id}
|
||||
GET /api/agent/deployments/{deployment_id}
|
||||
Authorization: Bearer <HEICODE_SERVICE_TOKEN>
|
||||
```
|
||||
|
||||
@@ -188,7 +188,7 @@ Authorization: Bearer <HEICODE_SERVICE_TOKEN>
|
||||
### 3.4 停止普通 sub deployment
|
||||
|
||||
```http
|
||||
POST /api/agnet/deployments/{deployment_id}/stop
|
||||
POST /api/agent/deployments/{deployment_id}/stop
|
||||
Authorization: Bearer <HEICODE_SERVICE_TOKEN>
|
||||
Content-Type: application/json
|
||||
```
|
||||
@@ -223,7 +223,7 @@ Content-Type: application/json
|
||||
Manager 生产 callback 地址:
|
||||
|
||||
```text
|
||||
https://code.xinghanlab.com/api/agnet/callbacks/swarm-events
|
||||
https://code.xinghanlab.com/api/agent/callbacks/swarm-events
|
||||
```
|
||||
|
||||
### 4.1 认证方式
|
||||
@@ -231,9 +231,9 @@ https://code.xinghanlab.com/api/agnet/callbacks/swarm-events
|
||||
优先使用 HMAC:
|
||||
|
||||
```http
|
||||
X-Agnet-Event-Id: evt_xxx
|
||||
X-Agnet-Timestamp: <unix_ms>
|
||||
X-Agnet-Signature: sha256=<hex>
|
||||
X-Agent-Event-Id: evt_xxx
|
||||
X-Agent-Timestamp: <unix_ms>
|
||||
X-Agent-Signature: sha256=<hex>
|
||||
X-Correlation-ID: <correlation_id>
|
||||
Content-Type: application/json
|
||||
```
|
||||
@@ -248,7 +248,7 @@ signature = HMAC_SHA256(callback_signing_secret, signature_payload)
|
||||
过渡期可使用旧 token:
|
||||
|
||||
```http
|
||||
X-Agnet-Service-Token: <HEICODE_SERVICE_TOKEN>
|
||||
X-Agent-Service-Token: <HEICODE_SERVICE_TOKEN>
|
||||
```
|
||||
|
||||
不要把真实 token 写入文档、日志或 artifact。
|
||||
@@ -397,7 +397,7 @@ Manager 端已有 approval、credential lease、approve/reject 和 Runtime decis
|
||||
推荐接口:
|
||||
|
||||
```http
|
||||
POST /api/agnet/deployments/{deployment_id}/approvals/{approval_id}
|
||||
POST /api/agent/deployments/{deployment_id}/approvals/{approval_id}
|
||||
Authorization: Bearer <HEICODE_SERVICE_TOKEN>
|
||||
Content-Type: application/json
|
||||
```
|
||||
@@ -415,7 +415,7 @@ Content-Type: application/json
|
||||
"resource_id": "repo_main",
|
||||
"risk_level": "high",
|
||||
"requires_credential": true,
|
||||
"credential_ref": "lease://agnet/cred_123",
|
||||
"credential_ref": "lease://agent/cred_123",
|
||||
"lease_id": "lease_123",
|
||||
"lease_expires_at": 1770000000000,
|
||||
"decided_by": "user_123",
|
||||
@@ -454,10 +454,10 @@ Agent Manager 改完后,按下面步骤验收。
|
||||
|
||||
| 步骤 | 操作 | 通过标准 |
|
||||
|---:|---|---|
|
||||
| 1 | `GET /api/agnet/health` | 返回 healthy |
|
||||
| 2 | `POST /api/agnet/deployments` 创建普通 sub run | 返回 `deployment_id`、`status` |
|
||||
| 1 | `GET /api/agent/health` | 返回 healthy |
|
||||
| 2 | `POST /api/agent/deployments` 创建普通 sub run | 返回 `deployment_id`、`status` |
|
||||
| 3 | 重复同一个 `X-Idempotency-Key` 创建 | 不重复创建,返回同一个 ID |
|
||||
| 4 | `GET /api/agnet/deployments/{deployment_id}` | 返回真实状态和 agents |
|
||||
| 4 | `GET /api/agent/deployments/{deployment_id}` | 返回真实状态和 agents |
|
||||
| 5 | Runtime 主动 callback `phase.changed` | Manager callback 返回 success |
|
||||
| 6 | Runtime 主动 callback `timeline.updated` | Manager timeline 可查到 |
|
||||
| 7 | Runtime 主动 callback `artifact.created` | Manager artifacts 可查到 |
|
||||
@@ -465,7 +465,7 @@ Agent Manager 改完后,按下面步骤验收。
|
||||
| 9 | Runtime 主动 callback usage | Manager 能看到 usage 摘要或 callback event |
|
||||
| 10 | Runtime 主动 callback `approval.requested` | Manager pending approval 生成 |
|
||||
| 11 | Manager approve/reject | Agent Manager 收到 decision |
|
||||
| 12 | `POST /api/agnet/deployments/{deployment_id}/stop` | Runtime 停止,状态变为 stopped |
|
||||
| 12 | `POST /api/agent/deployments/{deployment_id}/stop` | Runtime 停止,状态变为 stopped |
|
||||
| 13 | 检查日志和 artifact metadata | 不包含明文密钥 |
|
||||
|
||||
## 9. 当前实测记录
|
||||
@@ -474,12 +474,12 @@ Agent Manager 改完后,按下面步骤验收。
|
||||
|
||||
| 接口 | 结果 | 说明 |
|
||||
|---|---|---|
|
||||
| `GET http://20.212.121.126/api/agnet/health` | 200 | Agent Manager IP 健康检查正常 |
|
||||
| `GET https://agent-manager.taijiagnet.com/api/agnet/health` | 暂不作为当前验收项 | 当前约定先走 IP,域名后续再切换 |
|
||||
| `POST https://code.xinghanlab.com/api/agnet/callbacks/swarm-events` 无认证 | 业务返回 `CALLBACK_UNAUTHORIZED` | Manager callback 路由在线 |
|
||||
| `POST https://code.xinghanlab.com/api/agnet/callbacks/swarm-events` 带旧 token 但空 body | 业务返回 `CALLBACK_INVALID` | 认证通过,进入事件校验 |
|
||||
| `GET https://code.xinghanlab.com/api/agnet/callbacks/swarm-events/schema` | 404 | Manager schema GET 生产未通,Manager 侧需复核 |
|
||||
| `POST http://20.212.121.126/api/agnet/deployments` | 200 | 普通 sub Runtime deployment 创建可用 |
|
||||
| `GET http://20.212.121.126/api/agent/health` | 200 | Agent Manager IP 健康检查正常 |
|
||||
| `GET https://agent-manager.taijiagent.com/api/agent/health` | 暂不作为当前验收项 | 当前约定先走 IP,域名后续再切换 |
|
||||
| `POST https://code.xinghanlab.com/api/agent/callbacks/swarm-events` 无认证 | 业务返回 `CALLBACK_UNAUTHORIZED` | Manager callback 路由在线 |
|
||||
| `POST https://code.xinghanlab.com/api/agent/callbacks/swarm-events` 带旧 token 但空 body | 业务返回 `CALLBACK_INVALID` | 认证通过,进入事件校验 |
|
||||
| `GET https://code.xinghanlab.com/api/agent/callbacks/swarm-events/schema` | 404 | Manager schema GET 生产未通,Manager 侧需复核 |
|
||||
| `POST http://20.212.121.126/api/agent/deployments` | 200 | 普通 sub Runtime deployment 创建可用 |
|
||||
|
||||
### 9.1 2026-05-28 追加联调记录
|
||||
|
||||
@@ -491,17 +491,17 @@ Agent Manager 改完后,按下面步骤验收。
|
||||
- 因此本次没有宣称“桌面客户端登录后走 Manager 用户态创建 deployment”完整通过。
|
||||
- 本次完成的是服务到服务联调:Manager/客户端等价 payload -> Agent Manager IP,以及 Agent Manager callback 协议 -> Heicode Manager callback 接收端。
|
||||
|
||||
Agent Manager `/api/agnet/deployments` 服务到服务链路:
|
||||
Agent Manager `/api/agent/deployments` 服务到服务链路:
|
||||
|
||||
| 步骤 | 结果 | 证据 |
|
||||
|---|---|---|
|
||||
| `GET /api/agnet/health` | 通过 | 返回 `success=true`、`status=healthy` |
|
||||
| `POST /api/agnet/deployments` | 通过 | 返回 `deployment_id=dep_dbafd1ac37c3`,状态 `pending` |
|
||||
| `GET /api/agnet/deployments/{deployment_id}` | 通过 | 能查到 `user_id`、`binding_scope`、`sub_mode=agile`、`callback_configured=true` |
|
||||
| `GET /api/agnet/deployments/{deployment_id}/events` | 部分通过 | 只看到 `deployment.accepted`,未看到 `phase.changed/timeline.updated/artifact.created/sk_tool.*` |
|
||||
| `GET /api/agnet/deployments/{deployment_id}/logs` | 部分通过 | 返回系统日志 `Pod ... has no logs yet` |
|
||||
| `GET /api/agnet/deployments/{deployment_id}/metrics` | 通过但疑似占位 | 返回 CPU/内存/网络指标,但值看起来是固定模拟值,需要 Runtime 说明来源 |
|
||||
| `POST /api/agnet/deployments/{deployment_id}/stop` | 通过 | `dep_dbafd1ac37c3` 停止成功,返回 `status=stopped` |
|
||||
| `GET /api/agent/health` | 通过 | 返回 `success=true`、`status=healthy` |
|
||||
| `POST /api/agent/deployments` | 通过 | 返回 `deployment_id=dep_dbafd1ac37c3`,状态 `pending` |
|
||||
| `GET /api/agent/deployments/{deployment_id}` | 通过 | 能查到 `user_id`、`binding_scope`、`sub_mode=agile`、`callback_configured=true` |
|
||||
| `GET /api/agent/deployments/{deployment_id}/events` | 部分通过 | 只看到 `deployment.accepted`,未看到 `phase.changed/timeline.updated/artifact.created/sk_tool.*` |
|
||||
| `GET /api/agent/deployments/{deployment_id}/logs` | 部分通过 | 返回系统日志 `Pod ... has no logs yet` |
|
||||
| `GET /api/agent/deployments/{deployment_id}/metrics` | 通过但疑似占位 | 返回 CPU/内存/网络指标,但值看起来是固定模拟值,需要 Runtime 说明来源 |
|
||||
| `POST /api/agent/deployments/{deployment_id}/stop` | 通过 | `dep_dbafd1ac37c3` 停止成功,返回 `status=stopped` |
|
||||
|
||||
长等待测试:
|
||||
|
||||
@@ -535,7 +535,7 @@ source=agent-manager-ip-smoke
|
||||
结论:
|
||||
|
||||
1. Heicode Manager callback 接收端可用,旧 token 认证和幂等可用。
|
||||
2. Agent Manager IP 的 `/api/agnet/deployments` 创建、查询、停止可用。
|
||||
2. Agent Manager IP 的 `/api/agent/deployments` 创建、查询、停止可用。
|
||||
3. Agent Manager 当前没有在实测中产生普通 sub 所需的 phase/timeline/artifact/SK 主动回调。
|
||||
4. 因没有可用生产 Manager 用户 session,本次没有完成“客户端登录态 -> Manager 用户态 deployment -> Runtime”的完整端到端测试。
|
||||
|
||||
@@ -558,15 +558,15 @@ group: default
|
||||
|---|---|---|
|
||||
| 登录生产 Manager | 通过 | `/api/user/login` 返回 `success=true`,用户 `id=22` |
|
||||
| 查询用户信息 | 通过 | `/api/user/self` 返回用户 `chenchen`、`group=default` |
|
||||
| 查询 Runtime 健康 | 通过 | `/api/agnet/runtime/health` 返回 `enabled=true`、`configured=true`、`status=healthy`、`create_path=/api/agnet/deployments`、远端为 Agent Manager IP |
|
||||
| 客户端模拟创建 deployment | 通过 | `POST /api/agnet/user/deployments` 返回 Manager deployment `dep_1ba14ccfb558` |
|
||||
| 查询 Runtime 健康 | 通过 | `/api/agent/runtime/health` 返回 `enabled=true`、`configured=true`、`status=healthy`、`create_path=/api/agent/deployments`、远端为 Agent Manager IP |
|
||||
| 客户端模拟创建 deployment | 通过 | `POST /api/agent/user/deployments` 返回 Manager deployment `dep_1ba14ccfb558` |
|
||||
| Manager -> Agent Manager create | 通过 | Manager detail 写回 `runtime_deployment_id=dep_3335e54e9bdf`,`runtime_state=pending` |
|
||||
| Manager 用户态 events | 通过 | 出现 `deployment.accepted`、`runtime.sync.started`、`runtime.sync.accepted` |
|
||||
| Manager 用户态 logs | 通过 | 出现 control-plane 日志和 runtime sync 日志,均为 redacted |
|
||||
| Manager 用户态 metrics | 部分通过 | 返回 `platform_estimated=true`,说明是 Manager 估算/占位,不是 Runtime 真实资源指标 |
|
||||
| Manager 用户态 artifacts | 未产出 | 返回空列表 |
|
||||
| Manager 用户态 timeline | 部分通过 | 能返回 deployment/events,但 callbacks/artifacts 为空 |
|
||||
| Manager stop | 通过 | `POST /api/agnet/user/deployments/dep_1ba14ccfb558/stop` 返回 `status=stopped` |
|
||||
| Manager stop | 通过 | `POST /api/agent/user/deployments/dep_1ba14ccfb558/stop` 返回 `status=stopped` |
|
||||
| Runtime stop 结果 | 通过 | 直查 Agent Manager `dep_3335e54e9bdf`,状态为 `stopped` |
|
||||
|
||||
本次真实 ID:
|
||||
@@ -582,10 +582,10 @@ Agent Manager 侧直查 `dep_3335e54e9bdf`:
|
||||
|
||||
| 接口 | 结果 |
|
||||
|---|---|
|
||||
| `GET /api/agnet/deployments/dep_3335e54e9bdf` | 200,状态 `stopped`,`user_id=22` |
|
||||
| `GET /api/agnet/deployments/dep_3335e54e9bdf/events` | 200,仅有 `deployment.accepted`、`deployment.stopped` |
|
||||
| `GET /api/agnet/deployments/dep_3335e54e9bdf/logs` | 200,仅有 `Pod ... has no logs yet` |
|
||||
| `GET /api/agnet/deployments/dep_3335e54e9bdf/metrics` | 200,返回 CPU/内存/网络固定值 |
|
||||
| `GET /api/agent/deployments/dep_3335e54e9bdf` | 200,状态 `stopped`,`user_id=22` |
|
||||
| `GET /api/agent/deployments/dep_3335e54e9bdf/events` | 200,仅有 `deployment.accepted`、`deployment.stopped` |
|
||||
| `GET /api/agent/deployments/dep_3335e54e9bdf/logs` | 200,仅有 `Pod ... has no logs yet` |
|
||||
| `GET /api/agent/deployments/dep_3335e54e9bdf/metrics` | 200,返回 CPU/内存/网络固定值 |
|
||||
|
||||
本次客户端模拟链路结论:
|
||||
|
||||
|
||||
@@ -31,18 +31,18 @@ Agent Manager / Swarm Runtime 需要负责执行层:
|
||||
当前生产 Manager Runtime 配置实际为:
|
||||
|
||||
```text
|
||||
AGNET_RUNTIME_BASE_URL=http://20.212.121.126
|
||||
AGNET_RUNTIME_CREATE_PATH=/api/agnet/deployments
|
||||
AGNET_RUNTIME_STOP_PATH=/api/agnet/deployments/{deployment_id}/stop
|
||||
AGENT_RUNTIME_BASE_URL=http://20.212.121.126
|
||||
AGENT_RUNTIME_CREATE_PATH=/api/agent/deployments
|
||||
AGENT_RUNTIME_STOP_PATH=/api/agent/deployments/{deployment_id}/stop
|
||||
```
|
||||
|
||||
实测结果:
|
||||
|
||||
| 项 | 结果 |
|
||||
|---|---|
|
||||
| `GET /api/agnet/runtime/health` | 通过,远端 Agent Manager IP healthy |
|
||||
| `POST /api/swarms` 使用 `agent_runtime.platform=agnet-swarm` | 失败,Manager 返回 `agent_runtime.platform must be agnet when runtime context is present` |
|
||||
| `POST /api/swarms` 使用 `agent_runtime.platform=agnet` | 通过 |
|
||||
| `GET /api/agent/runtime/health` | 通过,远端 Agent Manager IP healthy |
|
||||
| `POST /api/swarms` 使用 `agent_runtime.platform=agent-swarm` | 失败,Manager 返回 `agent_runtime.platform must be agent when runtime context is present` |
|
||||
| `POST /api/swarms` 使用 `agent_runtime.platform=agent` | 通过 |
|
||||
| Manager deployment | `dep_72cdc138b9c8` |
|
||||
| Manager 返回 swarm_id | `dep_72cdc138b9c8`,当前是 Manager 本地 deployment id |
|
||||
| Runtime deployment | `dep_77370f27186f` |
|
||||
@@ -58,7 +58,7 @@ AGNET_RUNTIME_STOP_PATH=/api/agnet/deployments/{deployment_id}/stop
|
||||
结论:
|
||||
|
||||
1. Heicode Manager 的 `/api/swarms` adapter 能接收用户态蜂群请求,并能通过当前 Runtime bridge 调 Agent Manager IP。
|
||||
2. 当前生产链路实际走的是 Agent Manager `/api/agnet/deployments`,不是 Agent Manager `/api/swarms`。
|
||||
2. 当前生产链路实际走的是 Agent Manager `/api/agent/deployments`,不是 Agent Manager `/api/swarms`。
|
||||
3. Manager stop 可以传递到 Agent Manager deployment stop。
|
||||
4. 当前还没有真实蜂群 Runtime callback、task graph、artifact、SK、usage。
|
||||
5. Agent Manager 如果要满足正式蜂群模式,仍需实现本文后续定义的 `/api/swarms`、`/api/swarms/{swarm_id}`、`/api/swarms/{swarm_id}/stop`、callback 和 usage 约束。
|
||||
@@ -68,13 +68,13 @@ AGNET_RUNTIME_STOP_PATH=/api/agnet/deployments/{deployment_id}/stop
|
||||
```text
|
||||
Heicode Desktop Client
|
||||
-> Heicode Manager
|
||||
POST /api/swarms 或 /api/agnet/user/deployments
|
||||
POST /api/swarms 或 /api/agent/user/deployments
|
||||
-> Agent Manager / Swarm Runtime
|
||||
POST /api/swarms
|
||||
<- Runtime response
|
||||
runtime_deployment_id / swarm_id / status
|
||||
<- Runtime callback
|
||||
POST /api/agnet/callbacks/swarm-events
|
||||
POST /api/agent/callbacks/swarm-events
|
||||
-> Runtime approval decision
|
||||
POST /api/swarms/{swarm_id}/approvals/{approval_id}
|
||||
```
|
||||
@@ -98,11 +98,11 @@ Content-Type: application/json
|
||||
|
||||
| 优先级 | 方法 | 路径 | 必须 | 用途 |
|
||||
|---|---|---|---:|---|
|
||||
| P0 | `GET` | `/api/agnet/health` | 是 | 健康检查 |
|
||||
| P0 | `GET` | `/api/agent/health` | 是 | 健康检查 |
|
||||
| P0 | `POST` | `/api/swarms` | 是 | 创建真实 Swarm Run |
|
||||
| P0 | `POST` | `/api/swarms/{swarm_id}/stop` | 是 | 停止 Swarm Run |
|
||||
| P0 | `POST` | `/api/swarms/{swarm_id}/approvals/{approval_id}` | 是 | 接收 Manager 审批决定 |
|
||||
| P0 | callback | Manager `/api/agnet/callbacks/swarm-events` | 是 | 回写状态、task、handoff、artifact、approval |
|
||||
| P0 | callback | Manager `/api/agent/callbacks/swarm-events` | 是 | 回写状态、task、handoff、artifact、approval |
|
||||
| P1 | `GET` | `/api/swarms/{swarm_id}` | 建议 | 查询 Runtime 详情 |
|
||||
| P1 | `GET` | `/api/swarms/{swarm_id}/tasks` | 建议 | 查询 task graph |
|
||||
| P1 | `GET` | `/api/swarms/{swarm_id}/logs` | 建议 | 查询日志 |
|
||||
@@ -130,7 +130,7 @@ Content-Type: application/json
|
||||
3. `X-Idempotency-Key` 必须支持幂等;重复创建请求不能生成多个真实 Swarm Run。
|
||||
4. Agent Manager 不得要求 Manager 传长期明文密钥。
|
||||
|
||||
## 5. `GET /api/agnet/health`
|
||||
## 5. `GET /api/agent/health`
|
||||
|
||||
### 响应
|
||||
|
||||
@@ -227,8 +227,8 @@ Content-Type: application/json
|
||||
}
|
||||
],
|
||||
"callback": {
|
||||
"url": "https://code.xinghanlab.com/api/agnet/callbacks/swarm-events",
|
||||
"signing_secret_ref": "azkv://heicode-kv.vault.azure.net/secrets/agnet-callback-signing-key",
|
||||
"url": "https://code.xinghanlab.com/api/agent/callbacks/swarm-events",
|
||||
"signing_secret_ref": "azkv://heicode-kv.vault.azure.net/secrets/agent-callback-signing-key",
|
||||
"subscribed_events": [
|
||||
"deployment.status_changed",
|
||||
"task.created",
|
||||
@@ -342,9 +342,9 @@ Agent Manager 创建 Swarm Run 后,必须在 Runtime 内部生成任务图,
|
||||
Manager 回调地址:
|
||||
|
||||
```http
|
||||
POST https://code.xinghanlab.com/api/agnet/callbacks/swarm-events
|
||||
X-Agnet-Service-Token: <callback_token>
|
||||
X-Agnet-Event-Id: <event_id>
|
||||
POST https://code.xinghanlab.com/api/agent/callbacks/swarm-events
|
||||
X-Agent-Service-Token: <callback_token>
|
||||
X-Agent-Event-Id: <event_id>
|
||||
X-Correlation-ID: <correlation_id>
|
||||
Content-Type: application/json
|
||||
```
|
||||
@@ -352,8 +352,8 @@ Content-Type: application/json
|
||||
也支持 HMAC:
|
||||
|
||||
```http
|
||||
X-Agnet-Timestamp: <unix_ms>
|
||||
X-Agnet-Signature: sha256=<hex>
|
||||
X-Agent-Timestamp: <unix_ms>
|
||||
X-Agent-Signature: sha256=<hex>
|
||||
```
|
||||
|
||||
HMAC 签名内容:
|
||||
@@ -365,7 +365,7 @@ timestamp + "." + event_id + "." + raw_body
|
||||
联调前可拉取 Manager 当前接受的事件 schema:
|
||||
|
||||
```http
|
||||
GET https://code.xinghanlab.com/api/agnet/callbacks/swarm-events/schema
|
||||
GET https://code.xinghanlab.com/api/agent/callbacks/swarm-events/schema
|
||||
```
|
||||
|
||||
### 通用 callback envelope
|
||||
@@ -647,7 +647,7 @@ Runtime 可通过 `budget.alert` 或 `timeline.updated` 回传用量摘要。
|
||||
|
||||
| 步骤 | 操作 | 通过标准 |
|
||||
|---:|---|---|
|
||||
| 1 | Manager 调 `GET /api/agnet/health` | 返回 healthy |
|
||||
| 1 | Manager 调 `GET /api/agent/health` | 返回 healthy |
|
||||
| 2 | Manager 调 `POST /api/swarms` | 返回真实 `deployment_id` 和 `swarm_id` |
|
||||
| 3 | Runtime 回调 `deployment.status_changed` | Manager timeline 可见 |
|
||||
| 4 | Runtime 回调 `task.created` | Manager task flow / Agent task map 可见 |
|
||||
|
||||
@@ -10,10 +10,10 @@
|
||||
|
||||
| 模式 | Runtime | 当前已知地址 | 说明 |
|
||||
|---|---|---|---|
|
||||
| 普通 sub 敏捷模式 | Agent Manager / Agnet Runtime | `http://20.212.121.126` | 用于普通子 Agent 敏捷开发流程 |
|
||||
| 普通 sub 敏捷模式 | Agent Manager / Agent Runtime | `http://20.212.121.126` | 用于普通子 Agent 敏捷开发流程 |
|
||||
| 蜂群模式 | HeiCode-Swarm Orchestrator | `http://52.139.240.116:8000` | 用于蜂群任务图、Agent 协作、handoff、task graph |
|
||||
|
||||
注意:两套服务可能都提供 `/api/swarms` 这类路径,但业务含义不同。Heicode Manager 后续需要按模式分别配置,不应只用一套 `AGNET_RUNTIME_BASE_URL` 混跑。
|
||||
注意:两套服务可能都提供 `/api/swarms` 这类路径,但业务含义不同。Heicode Manager 后续需要按模式分别配置,不应只用一套 `AGENT_RUNTIME_BASE_URL` 混跑。
|
||||
|
||||
## 2. 本次读取到的蜂群 Runtime 新能力
|
||||
|
||||
@@ -21,9 +21,9 @@
|
||||
|
||||
| 能力 | 路径 | 当前判断 |
|
||||
|---|---|---|
|
||||
| 健康检查 | `GET /api/agnet/health` | 已提供 |
|
||||
| 健康检查 | `GET /api/agent/health` | 已提供 |
|
||||
| 创建蜂群 run | `POST /api/swarms` | 已提供 |
|
||||
| 兼容创建入口 | `POST /api/agnet/deployments` | 已提供 |
|
||||
| 兼容创建入口 | `POST /api/agent/deployments` | 已提供 |
|
||||
| 查询 swarm 详情 | `GET /api/swarms/{swarm_id}` | 已提供 |
|
||||
| 查询任务图 | `GET /api/swarms/{swarm_id}/tasks` | 已提供 |
|
||||
| 查询日志 | `GET /api/swarms/{swarm_id}/logs` | 已提供 |
|
||||
@@ -48,7 +48,7 @@ runtime contract checks passed
|
||||
请求:
|
||||
|
||||
```bash
|
||||
curl http://52.139.240.116:8000/api/agnet/health
|
||||
curl http://52.139.240.116:8000/api/agent/health
|
||||
```
|
||||
|
||||
返回核心内容:
|
||||
@@ -247,7 +247,7 @@ allow_handoff=false
|
||||
| 项 | 需要提供 / 确认 | 原因 |
|
||||
|---|---|---|
|
||||
| 蜂群 Runtime 调用 token | 已提供并验证可用;后续需要通过安全渠道配置到 Manager,不写入 Git | Manager 调 `POST /api/swarms` 需要 Bearer 鉴权 |
|
||||
| callback 凭据配置 | Runtime 配置 Manager 认可的 `AGNET_CALLBACK_SERVICE_TOKEN` 或 `AGNET_CALLBACK_SIGNING_SECRET` | Runtime 回调 Manager 必须通过鉴权 |
|
||||
| callback 凭据配置 | Runtime 配置 Manager 认可的 `AGENT_CALLBACK_SERVICE_TOKEN` 或 `AGENT_CALLBACK_SIGNING_SECRET` | Runtime 回调 Manager 必须通过鉴权 |
|
||||
| 两套部署边界 | 明确普通 sub 继续走 `20.212.121.126`,蜂群走 `52.139.240.116:8000` | 防止普通 sub 和蜂群混用 |
|
||||
| 创建响应字段 | `deployment_id`、`runtime_deployment_id`、`swarm_id`、`status` 字段保持稳定 | Manager 需要保存映射 |
|
||||
| callback deployment id 规则 | callback 中 `deployment_id` 应为 Manager deployment id,`runtime_deployment_id` 为 Runtime id,`swarm_id` 为 Runtime swarm id | Manager 根据这些字段落库和展示 |
|
||||
@@ -255,13 +255,13 @@ allow_handoff=false
|
||||
callback 地址:
|
||||
|
||||
```text
|
||||
https://code.xinghanlab.com/api/agnet/callbacks/swarm-events
|
||||
https://code.xinghanlab.com/api/agent/callbacks/swarm-events
|
||||
```
|
||||
|
||||
callback schema 查询:
|
||||
|
||||
```text
|
||||
GET https://code.xinghanlab.com/api/agnet/callbacks/swarm-events/schema
|
||||
GET https://code.xinghanlab.com/api/agent/callbacks/swarm-events/schema
|
||||
```
|
||||
|
||||
说明:具体 token / signing secret 不应写入 Markdown 或 Git,请通过安全渠道提供。
|
||||
@@ -340,8 +340,8 @@ Content-Type: application/json
|
||||
]
|
||||
},
|
||||
"callback": {
|
||||
"url": "https://code.xinghanlab.com/api/agnet/callbacks/swarm-events",
|
||||
"signing_secret_ref": "azkv://heicode-kv.vault.azure.net/secrets/agnet-callback-signing-key",
|
||||
"url": "https://code.xinghanlab.com/api/agent/callbacks/swarm-events",
|
||||
"signing_secret_ref": "azkv://heicode-kv.vault.azure.net/secrets/agent-callback-signing-key",
|
||||
"subscribed_events": [
|
||||
"deployment.status_changed",
|
||||
"task.created",
|
||||
@@ -416,8 +416,8 @@ Heicode Manager 需要保留普通 sub 和蜂群两套 Runtime 配置。建议
|
||||
|
||||
```text
|
||||
# 普通 sub 敏捷模式
|
||||
AGNET_RUNTIME_BASE_URL=http://20.212.121.126
|
||||
AGNET_RUNTIME_CREATE_PATH=/api/swarms
|
||||
AGENT_RUNTIME_BASE_URL=http://20.212.121.126
|
||||
AGENT_RUNTIME_CREATE_PATH=/api/swarms
|
||||
|
||||
# 蜂群模式
|
||||
SWARM_RUNTIME_BASE_URL=http://52.139.240.116:8000
|
||||
@@ -427,7 +427,7 @@ SWARM_RUNTIME_APPROVAL_DECISION_PATH=/api/swarms/{swarm_id}/approvals/{approval_
|
||||
SWARM_RUNTIME_SERVICE_TOKEN=<runtime-service-token>
|
||||
```
|
||||
|
||||
如果短期 Manager 还只有一套 `AGNET_RUNTIME_*`,只能临时切换到蜂群 Runtime 做专项联调,不能同时代表普通 sub 和蜂群都在线。
|
||||
如果短期 Manager 还只有一套 `AGENT_RUNTIME_*`,只能临时切换到蜂群 Runtime 做专项联调,不能同时代表普通 sub 和蜂群都在线。
|
||||
|
||||
## 8. 蜂群 MVP 验收口径
|
||||
|
||||
|
||||
@@ -137,13 +137,13 @@ PayPal 接入时有两种做法:
|
||||
|
||||
| 文档口径 | 含义 |
|
||||
|---|---|
|
||||
| `budget.max_tokens` | 本次 Agent / Agnet 部署允许消耗的 token 上限。 |
|
||||
| `budget.max_cost_usd` | 本次 Agent / Agnet 部署允许消耗的美元成本上限。 |
|
||||
| `budget.max_duration_sec` | 本次 Agent / Agnet 部署允许运行的时间上限。 |
|
||||
| `budget.max_tokens` | 本次 Agent / Agent 部署允许消耗的 token 上限。 |
|
||||
| `budget.max_cost_usd` | 本次 Agent / Agent 部署允许消耗的美元成本上限。 |
|
||||
| `budget.max_duration_sec` | 本次 Agent / Agent 部署允许运行的时间上限。 |
|
||||
| `billing_context.provider = newapi` | 表示模型调用费用应映射到 NewAPI 用户、Token、Group 或 quota。 |
|
||||
| `agent_runtime` | 表示子 Agent 角色、模型 profile、实例数,不能和 NewAPI 扣费对象混在一起。 |
|
||||
|
||||
产品文档明确:子 Agnet 的运行模型属于 Agnet 平台部署配置,不等同于 CodeGW 后台模型供应商配置。NewAPI 负责模型网关、余额、用量、日志和扣费;Agnet 平台负责真实执行和运行态。
|
||||
产品文档明确:子 Agent 的运行模型属于 Agent 平台部署配置,不等同于 CodeGW 后台模型供应商配置。NewAPI 负责模型网关、余额、用量、日志和扣费;Agent 平台负责真实执行和运行态。
|
||||
|
||||
所以当前要分成两类费用:
|
||||
|
||||
@@ -378,7 +378,7 @@ Heicode Manager 当前已有余额和订阅计费闭环。PayPal 接入不改变
|
||||
|
||||
- Heicode 产品资料包:`docs/product-package/`
|
||||
- NewAPI / CodeGW 边界:`docs/heicode-runtime-auth-newapi-secret-design.md`
|
||||
- Agnet 请求契约:`docs/integration/agnet-platform-request-contract.md`
|
||||
- Agent 请求契约:`docs/integration/agent-platform-request-contract.md`
|
||||
- PayPal Orders v2:`https://developer.paypal.com/docs/api/orders/v2/`
|
||||
- PayPal Subscriptions:`https://developer.paypal.com/docs/subscriptions/reference/`
|
||||
- PayPal Webhook 事件:`https://developer.paypal.com/api/rest/webhooks/event-names`
|
||||
|
||||
@@ -8,11 +8,11 @@
|
||||
|
||||
| 能力 | Manager 状态 |
|
||||
|---|---|
|
||||
| 用户态创建 | `POST /api/agnet/user/deployments`、`POST /api/swarms` 已可创建 Manager deployment |
|
||||
| Runtime 创建桥接 | 可通过 `AGNET_RUNTIME_BASE_URL` + `AGNET_RUNTIME_CREATE_PATH` 调用 Runtime |
|
||||
| Runtime 停止桥接 | 可通过 `AGNET_RUNTIME_STOP_PATH` 停止 Runtime deployment |
|
||||
| 回调接收 | `POST /api/agnet/callbacks/swarm-events` 已支持事件、artifact、approval、timeline 回调 |
|
||||
| 回调安全 | 支持 `X-Agnet-Service-Token` 或 `X-Agnet-Signature` HMAC |
|
||||
| 用户态创建 | `POST /api/agent/user/deployments`、`POST /api/swarms` 已可创建 Manager deployment |
|
||||
| Runtime 创建桥接 | 可通过 `AGENT_RUNTIME_BASE_URL` + `AGENT_RUNTIME_CREATE_PATH` 调用 Runtime |
|
||||
| Runtime 停止桥接 | 可通过 `AGENT_RUNTIME_STOP_PATH` 停止 Runtime deployment |
|
||||
| 回调接收 | `POST /api/agent/callbacks/swarm-events` 已支持事件、artifact、approval、timeline 回调 |
|
||||
| 回调安全 | 支持 `X-Agent-Service-Token` 或 `X-Agent-Signature` HMAC |
|
||||
| 幂等 | `event_id` / `idempotency_key` 去重 |
|
||||
| 审批结果回传 | 用户 approve/reject 后,Manager 可调用 Runtime approval decision 接口 |
|
||||
| 密钥边界 | Manager 只传 `secret_ref` / `credential_ref`,不传长期明文密钥 |
|
||||
@@ -22,7 +22,7 @@
|
||||
### 1. 健康检查
|
||||
|
||||
```http
|
||||
GET /api/agnet/health
|
||||
GET /api/agent/health
|
||||
```
|
||||
|
||||
如果蜂群侧沿用现有 Orchestrator,也可以临时配置为:
|
||||
@@ -59,7 +59,7 @@ Content-Type: application/json
|
||||
Manager 当前也可兼容配置:
|
||||
|
||||
```text
|
||||
AGNET_RUNTIME_CREATE_PATH=/api/swarms
|
||||
AGENT_RUNTIME_CREATE_PATH=/api/swarms
|
||||
```
|
||||
|
||||
请求核心字段:
|
||||
@@ -97,8 +97,8 @@ AGNET_RUNTIME_CREATE_PATH=/api/swarms
|
||||
}
|
||||
],
|
||||
"callback": {
|
||||
"url": "https://code.xinghanlab.com/api/agnet/callbacks/swarm-events",
|
||||
"signing_secret_ref": "azkv://heicode-kv.vault.azure.net/secrets/agnet-callback-signing-key",
|
||||
"url": "https://code.xinghanlab.com/api/agent/callbacks/swarm-events",
|
||||
"signing_secret_ref": "azkv://heicode-kv.vault.azure.net/secrets/agent-callback-signing-key",
|
||||
"subscribed_events": [
|
||||
"deployment.status_changed",
|
||||
"task.created",
|
||||
@@ -145,7 +145,7 @@ AGNET_RUNTIME_CREATE_PATH=/api/swarms
|
||||
默认路径:
|
||||
|
||||
```http
|
||||
POST /api/agnet/deployments/{deployment_id}/stop
|
||||
POST /api/agent/deployments/{deployment_id}/stop
|
||||
Authorization: Bearer <service_token>
|
||||
Content-Type: application/json
|
||||
```
|
||||
@@ -181,7 +181,7 @@ POST /api/swarms/{swarm_id}/stop
|
||||
则 Manager 可通过环境变量配置:
|
||||
|
||||
```text
|
||||
AGNET_RUNTIME_STOP_PATH=/api/swarms/{swarm_id}/stop
|
||||
AGENT_RUNTIME_STOP_PATH=/api/swarms/{swarm_id}/stop
|
||||
```
|
||||
|
||||
## 三、Runtime 回调 Manager
|
||||
@@ -189,9 +189,9 @@ AGNET_RUNTIME_STOP_PATH=/api/swarms/{swarm_id}/stop
|
||||
统一回调入口:
|
||||
|
||||
```http
|
||||
POST https://code.xinghanlab.com/api/agnet/callbacks/swarm-events
|
||||
X-Agnet-Service-Token: <callback_token>
|
||||
X-Agnet-Event-Id: <event_id>
|
||||
POST https://code.xinghanlab.com/api/agent/callbacks/swarm-events
|
||||
X-Agent-Service-Token: <callback_token>
|
||||
X-Agent-Event-Id: <event_id>
|
||||
X-Correlation-ID: <correlation_id>
|
||||
Content-Type: application/json
|
||||
```
|
||||
@@ -199,8 +199,8 @@ Content-Type: application/json
|
||||
也支持 HMAC:
|
||||
|
||||
```http
|
||||
X-Agnet-Timestamp: <unix_ms>
|
||||
X-Agnet-Signature: sha256=<hex>
|
||||
X-Agent-Timestamp: <unix_ms>
|
||||
X-Agent-Signature: sha256=<hex>
|
||||
```
|
||||
|
||||
签名内容:
|
||||
@@ -310,7 +310,7 @@ Content-Type: application/json
|
||||
Manager 可配置:
|
||||
|
||||
```text
|
||||
AGNET_RUNTIME_APPROVAL_DECISION_PATH=/api/swarms/{swarm_id}/approvals/{approval_id}
|
||||
AGENT_RUNTIME_APPROVAL_DECISION_PATH=/api/swarms/{swarm_id}/approvals/{approval_id}
|
||||
```
|
||||
|
||||
请求:
|
||||
@@ -329,7 +329,7 @@ AGNET_RUNTIME_APPROVAL_DECISION_PATH=/api/swarms/{swarm_id}/approvals/{approval_
|
||||
"target_role": "ops",
|
||||
"risk_level": "high",
|
||||
"requires_credential": true,
|
||||
"credential_ref": "lease://agnet/lease_xxx",
|
||||
"credential_ref": "lease://agent/lease_xxx",
|
||||
"lease_id": "lease_xxx",
|
||||
"lease_expires_at": 1770000000000,
|
||||
"decided_by": "user:22",
|
||||
@@ -356,7 +356,7 @@ AGNET_RUNTIME_APPROVAL_DECISION_PATH=/api/swarms/{swarm_id}/approvals/{approval_
|
||||
安全要求:
|
||||
|
||||
1. Manager 不会在该请求中发送 `secret_ref`。
|
||||
2. 如果审批通过且需要凭证,只发送短期 `credential_ref = lease://agnet/...`。
|
||||
2. 如果审批通过且需要凭证,只发送短期 `credential_ref = lease://agent/...`。
|
||||
3. Runtime 不得把 `credential_ref`、service token、签名密钥写入日志或 artifact metadata。
|
||||
4. Runtime 收到 `approved` 后继续原高危动作;收到 `rejected` 后停止该动作并回调 `timeline.updated` 或 `task.failed/blocked`。
|
||||
|
||||
@@ -378,7 +378,7 @@ AGNET_RUNTIME_APPROVAL_DECISION_PATH=/api/swarms/{swarm_id}/approvals/{approval_
|
||||
|
||||
| 当前入口 | 可用性 | 建议 |
|
||||
|---|---|---|
|
||||
| `GET /health` | 可直接作为健康检查 | Manager `AGNET_RUNTIME_HEALTH_PATH=/health` |
|
||||
| `GET /health` | 可直接作为健康检查 | Manager `AGENT_RUNTIME_HEALTH_PATH=/health` |
|
||||
| `POST /tasks` | 可临时创建任务 | 只能做兼容桥接,不建议作为最终生产契约 |
|
||||
| `GET /tasks` | 可查任务 | 后续需要映射到 callback/timeline |
|
||||
| `GET /agents` | 可查 Agent | 后续需要回调 agent status |
|
||||
@@ -392,7 +392,7 @@ AGNET_RUNTIME_APPROVAL_DECISION_PATH=/api/swarms/{swarm_id}/approvals/{approval_
|
||||
|
||||
最小验收顺序:
|
||||
|
||||
1. Manager 调 `GET /api/agnet/health` 或 `/health` 返回 healthy。
|
||||
1. Manager 调 `GET /api/agent/health` 或 `/health` 返回 healthy。
|
||||
2. Manager 调 Runtime create,拿到 `runtime_deployment_id` 和 `swarm_id`。
|
||||
3. Runtime 回调 `deployment.status_changed`,Manager timeline 可见。
|
||||
4. Runtime 回调 `task.claimed`、`task.completed`,Manager timeline 可见。
|
||||
|
||||
+74
-74
@@ -1,15 +1,15 @@
|
||||
# Manager → Agnet 平台接口参数文档
|
||||
# Manager → Agent 平台接口参数文档
|
||||
|
||||
**版本**: v0.3(P1/P5 联调契约)
|
||||
**生效日期**: 2026-05-03
|
||||
**状态**: 联调准备;当前仓库提供 Manager 侧最小验证端点,生产 Agnet 平台部署尚未在本文档中宣称完成。
|
||||
**方向**: Heicode Manager 主动请求 Agnet 平台;Agnet 平台返回部署、日志、监控与审计状态。
|
||||
**范围**: 创建/停止子 Agent 部署、查询部署、获取事件/日志/监控快照、解析 SK 快照、查询审计日志,以及 Agnet 辅助 NewAPI 重建/部署的参数约定。
|
||||
**状态**: 联调准备;当前仓库提供 Manager 侧最小验证端点,生产 Agent 平台部署尚未在本文档中宣称完成。
|
||||
**方向**: Heicode Manager 主动请求 Agent 平台;Agent 平台返回部署、日志、监控与审计状态。
|
||||
**范围**: 创建/停止子 Agent 部署、查询部署、获取事件/日志/监控快照、解析 SK 快照、查询审计日志,以及 Agent 辅助 NewAPI 重建/部署的参数约定。
|
||||
|
||||
> 2026-05-04 边界修正:Manager 当前不把 `tenant/project` 作为产品、认证或扣费主轴。新请求应使用 `user_context.user_id`、`user_context.channel_id`、`resource_grants[].binding_scope`、`billing_context(newapi)` 和 `agent_runtime(agnet)`。本文中仍出现的 `tenant_id/project_id` 只表示旧字段兼容或历史接口命名,不应作为新功能设计依据。
|
||||
> 2026-05-04 边界修正:Manager 当前不把 `tenant/project` 作为产品、认证或扣费主轴。新请求应使用 `user_context.user_id`、`user_context.channel_id`、`resource_grants[].binding_scope`、`billing_context(newapi)` 和 `agent_runtime(agent)`。本文中仍出现的 `tenant_id/project_id` 只表示旧字段兼容或历史接口命名,不应作为新功能设计依据。
|
||||
**安全红线**: 请求体只允许传资源元数据、权限范围与 `secret_ref`/环境变量名;不得传明文密码、Token、私钥、连接串或云访问密钥。
|
||||
|
||||
> 本文档描述 Manager 对 Agnet 平台的出站集成契约。当前仓库中 `/api/agnet/*` 是 Manager 侧最小控制面/模拟端点,用于校验同一套 payload 结构;生产接入时,Manager 应将下列请求发送到 Agnet 平台网关。
|
||||
> 本文档描述 Manager 对 Agent 平台的出站集成契约。当前仓库中 `/api/agent/*` 是 Manager 侧最小控制面/模拟端点,用于校验同一套 payload 结构;生产接入时,Manager 应将下列请求发送到 Agent 平台网关。
|
||||
|
||||
## 0. 概述
|
||||
|
||||
@@ -17,18 +17,18 @@
|
||||
|
||||
| 接口 | 用途 | 当前性质 |
|
||||
|---|---|---|
|
||||
| `POST /api/agnet/deployments` | 创建子 Agent/运维任务部署,含 NewAPI 重建/部署场景 | 必需 |
|
||||
| `GET /api/agnet/deployments` | 查询部署列表 | 必需 |
|
||||
| `GET /api/agnet/deployments/{deployment_id}` | 查询单个部署详情 | 必需 |
|
||||
| `POST /api/agnet/deployments/{deployment_id}/stop` | 停止部署或取消排队任务 | 必需 |
|
||||
| `GET /api/agnet/deployments/{deployment_id}/logs` | 拉取部署日志 | 必需 |
|
||||
| `GET /api/agnet/deployments/{deployment_id}/logs/stream` | 实时日志 SSE | 可选 |
|
||||
| `GET /api/agnet/projects/{project_id}/dashboard-snapshot` | 资源作用域监控快照;路径名保留旧兼容,参数值按 `binding_scope` 解释 | 必需 |
|
||||
| `GET /api/agnet/deployments/{deployment_id}/metrics` | 单部署指标序列 | 建议 |
|
||||
| `GET /api/agnet/deployments/{deployment_id}/events` | 部署事件 | 必需 |
|
||||
| `GET /api/agnet/audit-logs` | 审计日志 | 必需 |
|
||||
| `POST /api/agnet/sk-snapshots/resolve` | 触发 SK 快照解析 | 必需 |
|
||||
| `GET /api/agnet/deployments/{deployment_id}/sk-snapshots` | 查询 SK 快照 | 必需 |
|
||||
| `POST /api/agent/deployments` | 创建子 Agent/运维任务部署,含 NewAPI 重建/部署场景 | 必需 |
|
||||
| `GET /api/agent/deployments` | 查询部署列表 | 必需 |
|
||||
| `GET /api/agent/deployments/{deployment_id}` | 查询单个部署详情 | 必需 |
|
||||
| `POST /api/agent/deployments/{deployment_id}/stop` | 停止部署或取消排队任务 | 必需 |
|
||||
| `GET /api/agent/deployments/{deployment_id}/logs` | 拉取部署日志 | 必需 |
|
||||
| `GET /api/agent/deployments/{deployment_id}/logs/stream` | 实时日志 SSE | 可选 |
|
||||
| `GET /api/agent/projects/{project_id}/dashboard-snapshot` | 资源作用域监控快照;路径名保留旧兼容,参数值按 `binding_scope` 解释 | 必需 |
|
||||
| `GET /api/agent/deployments/{deployment_id}/metrics` | 单部署指标序列 | 建议 |
|
||||
| `GET /api/agent/deployments/{deployment_id}/events` | 部署事件 | 必需 |
|
||||
| `GET /api/agent/audit-logs` | 审计日志 | 必需 |
|
||||
| `POST /api/agent/sk-snapshots/resolve` | 触发 SK 快照解析 | 必需 |
|
||||
| `GET /api/agent/deployments/{deployment_id}/sk-snapshots` | 查询 SK 快照 | 必需 |
|
||||
|
||||
> 不在本文档范围:真实 Secret Store 写入、生产 SSH 登录、云账号授权回调、NewAPI 管理后台开放。生产部署动作只有实际执行并通过日志/监控/审计验证后,才能在报告中标记为“已部署”。
|
||||
|
||||
@@ -41,20 +41,20 @@
|
||||
由部署环境配置,不写入仓库。例如:
|
||||
|
||||
```text
|
||||
AGNET_PLATFORM_BASE_URL=https://agnet-platform.example.com
|
||||
AGENT_PLATFORM_BASE_URL=https://agent-platform.example.com
|
||||
```
|
||||
|
||||
联调环境建议使用独立域名或内网网关,示例不得包含真实凭据:
|
||||
|
||||
```text
|
||||
AGNET_PLATFORM_BASE_URL=https://staging-agnet.example.com
|
||||
MANAGER_SERVICE_TOKEN_SECRET_REF=azkv://heicode-kv.vault.azure.net/secrets/manager-service-agnet-platform-service-token
|
||||
AGENT_PLATFORM_BASE_URL=https://staging-agent.example.com
|
||||
MANAGER_SERVICE_TOKEN_SECRET_REF=azkv://heicode-kv.vault.azure.net/secrets/manager-service-agent-platform-service-token
|
||||
```
|
||||
|
||||
完整路径示例:
|
||||
|
||||
```http
|
||||
POST https://agnet-platform.example.com/api/agnet/deployments
|
||||
POST https://agent-platform.example.com/api/agent/deployments
|
||||
```
|
||||
|
||||
### 1.2 通用 Header
|
||||
@@ -133,7 +133,7 @@ POST https://agnet-platform.example.com/api/agnet/deployments
|
||||
### 2.1 Endpoint
|
||||
|
||||
```http
|
||||
POST /api/agnet/deployments
|
||||
POST /api/agent/deployments
|
||||
```
|
||||
|
||||
### 2.2 请求体
|
||||
@@ -164,11 +164,11 @@ POST /api/agnet/deployments
|
||||
"quota_ref": "newapi_token_or_group_quota_ref"
|
||||
},
|
||||
"agent_runtime": {
|
||||
"platform": "agnet",
|
||||
"platform": "agent",
|
||||
"agents": [
|
||||
{
|
||||
"role": "builder",
|
||||
"model_ref": "agnet_model_profile_builder",
|
||||
"model_ref": "agent_model_profile_builder",
|
||||
"instance_count": 1
|
||||
}
|
||||
]
|
||||
@@ -200,7 +200,7 @@ POST /api/agnet/deployments
|
||||
],
|
||||
"runtime_execution": {
|
||||
"profile_id": "aks-codex-standard",
|
||||
"cloud_principal_refs": ["principal://users/user_123/agnet-runtime"],
|
||||
"cloud_principal_refs": ["principal://users/user_123/agent-runtime"],
|
||||
"network_policy_ref": "netpol://bindings/repo_default/restricted-egress"
|
||||
},
|
||||
"sk_access_policy": {
|
||||
@@ -272,15 +272,15 @@ POST /api/agnet/deployments
|
||||
| 字段 | 类型 | 必填 | 约束/说明 |
|
||||
|---|---|---:|---|
|
||||
| `intent_id` | string | 是 | Manager 侧意图 ID,用于幂等、审计和追踪。 |
|
||||
| `template_hint` | string | 是 | Agnet 平台选择编排模板的提示,如 `manager-resource-binding`。 |
|
||||
| `template_hint` | string | 是 | Agent 平台选择编排模板的提示,如 `manager-resource-binding`。 |
|
||||
| `objective` | string | 是 | 本次部署目标,应是自然语言但不能含密钥。 |
|
||||
| `risk_level` | enum | 是 | `low` / `medium` / `high`。高风险必须携带客户端审批证据;缺失或不匹配时只能只读或拒绝执行。 |
|
||||
| `budget.max_tokens` | int | 是 | 当前策略上限建议不超过 `500000`。 |
|
||||
| `budget.max_cost_usd` | number | 是 | 当前策略上限建议不超过 `200`。 |
|
||||
| `budget.max_duration_sec` | int | 是 | 当前策略上限建议不超过 `86400`。 |
|
||||
| `user_context` | object | 建议 | 登录用户上下文,优先使用 Heicode/Agnet 登录返回的 `user.id`/`channelId`。 |
|
||||
| `billing_context` | object | 条件 | NewAPI 扣费上下文;只表达 user/token/group/quota 映射,不表达子 Agnet 模型或实例数。 |
|
||||
| `agent_runtime` | object | 条件 | Agnet 平台运行时上下文;表达子 Agnet 角色、模型 profile 和实例数,不承载 NewAPI key 或扣费对象。 |
|
||||
| `user_context` | object | 建议 | 登录用户上下文,优先使用 Heicode/Agent 登录返回的 `user.id`/`channelId`。 |
|
||||
| `billing_context` | object | 条件 | NewAPI 扣费上下文;只表达 user/token/group/quota 映射,不表达子 Agent 模型或实例数。 |
|
||||
| `agent_runtime` | object | 条件 | Agent 平台运行时上下文;表达子 Agent 角色、模型 profile 和实例数,不承载 NewAPI key 或扣费对象。 |
|
||||
| `constraints.allowed_model_ids` | string[] | 否 | agent 的 `default_model_id` 如填写,必须在此列表内。 |
|
||||
| `metadata.tenant_id` | string | 否 | 旧兼容字段;新实现不得作为产品租户边界。 |
|
||||
| `metadata.project_id` | string | 否 | 旧兼容字段;新实现不得作为项目账本边界。 |
|
||||
@@ -297,10 +297,10 @@ POST /api/agnet/deployments
|
||||
| `billing_context.newapi_user_ref` | string | 否 | NewAPI 用户映射引用,不是 NewAPI key。 |
|
||||
| `billing_context.newapi_group` | string | 否 | NewAPI Group 映射,用于额度或策略选择。 |
|
||||
| `billing_context.quota_ref` | string | 否 | Token 或 Group 额度引用,不得包含真实 Token 原文。 |
|
||||
| `agent_runtime.platform` | enum | 条件 | 当前只允许 `agnet`。 |
|
||||
| `agent_runtime.platform` | enum | 条件 | 当前只允许 `agent`。 |
|
||||
| `agent_runtime.agents[].role` | string | 条件 | 必须匹配 `agents[].role_template`。 |
|
||||
| `agent_runtime.agents[].model_ref` | string | 条件 | Agnet 平台模型 profile 引用;不是 NewAPI 扣费字段。 |
|
||||
| `agent_runtime.agents[].instance_count` | int | 条件 | 子 Agnet 实例数量,必须大于 0。 |
|
||||
| `agent_runtime.agents[].model_ref` | string | 条件 | Agent 平台模型 profile 引用;不是 NewAPI 扣费字段。 |
|
||||
| `agent_runtime.agents[].instance_count` | int | 条件 | 子 Agent 实例数量,必须大于 0。 |
|
||||
|
||||
#### agents[]
|
||||
|
||||
@@ -330,7 +330,7 @@ POST /api/agnet/deployments
|
||||
|
||||
| 字段 | 类型 | 必填 | 说明 |
|
||||
|---|---|---:|---|
|
||||
| `profile_id` | string | 条件必填 | Agnet 平台运行规格,如 AKS profile。 |
|
||||
| `profile_id` | string | 条件必填 | Agent 平台运行规格,如 AKS profile。 |
|
||||
| `cloud_principal_refs` | string[] | 否 | 运行身份引用,不是明文凭据。 |
|
||||
| `network_policy_ref` | string | 否 | 网络策略引用,用于限制出站/入站。 |
|
||||
|
||||
@@ -354,9 +354,9 @@ POST /api/agnet/deployments
|
||||
| `secret_ref` | string | 条件必填 | `git`、`sk`、`cloud_account`、`cloud_resource` 必填;`project_doc` 可为空。 |
|
||||
| `audit` | object<string,string> | 否 | 审计上下文;不得含密钥字段。 |
|
||||
|
||||
### 2.4 典型场景:Agnet 辅助 NewAPI 重建/部署
|
||||
### 2.4 典型场景:Agent 辅助 NewAPI 重建/部署
|
||||
|
||||
当 Manager 需要让 Agnet 平台协助重建或部署 NewAPI 时,仍使用 `POST /api/agnet/deployments`,但必须把任务表达为受控运维部署,不得把 VM、PostgreSQL、Redis、NewAPI key 等真实凭据写入请求体。
|
||||
当 Manager 需要让 Agent 平台协助重建或部署 NewAPI 时,仍使用 `POST /api/agent/deployments`,但必须把任务表达为受控运维部署,不得把 VM、PostgreSQL、Redis、NewAPI key 等真实凭据写入请求体。
|
||||
|
||||
请求体示例:
|
||||
|
||||
@@ -394,7 +394,7 @@ POST /api/agnet/deployments
|
||||
"default_model_id": "gpt-5.4",
|
||||
"runtime_execution": {
|
||||
"profile_id": "aks-codex-ops",
|
||||
"cloud_principal_refs": ["principal://users/user_123/agnet-ops"],
|
||||
"cloud_principal_refs": ["principal://users/user_123/agent-ops"],
|
||||
"network_policy_ref": "netpol://bindings/newapi-prod/ops-egress"
|
||||
},
|
||||
"resource_grants": [
|
||||
@@ -458,7 +458,7 @@ POST /api/agnet/deployments
|
||||
}
|
||||
```
|
||||
|
||||
Agnet 平台返回的部署详情、日志、监控和审计中应至少能证明:构建版本/commit、服务重启结果、健康检查结果、资源使用情况、失败回滚状态。未执行真实 SSH/生产动作时,只能返回 `phase=planned` 或 `phase=pending_approval`。
|
||||
Agent 平台返回的部署详情、日志、监控和审计中应至少能证明:构建版本/commit、服务重启结果、健康检查结果、资源使用情况、失败回滚状态。未执行真实 SSH/生产动作时,只能返回 `phase=planned` 或 `phase=pending_approval`。
|
||||
|
||||
NewAPI 重建/部署的完成判定必须同时满足:
|
||||
|
||||
@@ -494,7 +494,7 @@ NewAPI 重建/部署的完成判定必须同时满足:
|
||||
### 3.1 查询部署列表
|
||||
|
||||
```http
|
||||
GET /api/agnet/deployments?user_id=user_123&binding_scope=repo_default
|
||||
GET /api/agent/deployments?user_id=user_123&binding_scope=repo_default
|
||||
```
|
||||
|
||||
返回:
|
||||
@@ -520,15 +520,15 @@ GET /api/agnet/deployments?user_id=user_123&binding_scope=repo_default
|
||||
### 3.2 查询单个部署
|
||||
|
||||
```http
|
||||
GET /api/agnet/deployments/{deployment_id}
|
||||
GET /api/agent/deployments/{deployment_id}
|
||||
```
|
||||
|
||||
返回应包含部署状态、phase、agent_instances、最近错误、资源授权摘要和预算消耗摘要。Agnet 平台返回时必须对 `secret_ref` 以外的凭据信息做脱敏;原则上不返回任何明文凭据。
|
||||
返回应包含部署状态、phase、agent_instances、最近错误、资源授权摘要和预算消耗摘要。Agent 平台返回时必须对 `secret_ref` 以外的凭据信息做脱敏;原则上不返回任何明文凭据。
|
||||
|
||||
### 3.3 停止部署
|
||||
|
||||
```http
|
||||
POST /api/agnet/deployments/{deployment_id}/stop
|
||||
POST /api/agent/deployments/{deployment_id}/stop
|
||||
```
|
||||
|
||||
请求体可为空;如需原因可扩展:
|
||||
@@ -565,12 +565,12 @@ POST /api/agnet/deployments/{deployment_id}/stop
|
||||
|
||||
---
|
||||
|
||||
## 4. 日志接口(Manager 拉取 Agnet 平台)
|
||||
## 4. 日志接口(Manager 拉取 Agent 平台)
|
||||
|
||||
### 4.1 获取部署日志
|
||||
|
||||
```http
|
||||
GET /api/agnet/deployments/{deployment_id}/logs?agent_instance_id=agi_abc123&stream=stdout&since=2026-05-02T00:00:00Z&limit=200&cursor=cur_001
|
||||
GET /api/agent/deployments/{deployment_id}/logs?agent_instance_id=agi_abc123&stream=stdout&since=2026-05-02T00:00:00Z&limit=200&cursor=cur_001
|
||||
```
|
||||
|
||||
Query:
|
||||
@@ -609,14 +609,14 @@ Query:
|
||||
|
||||
日志要求:
|
||||
|
||||
- Agnet 平台必须在返回前完成密钥脱敏。
|
||||
- Agent 平台必须在返回前完成密钥脱敏。
|
||||
- `message` 不得包含密码、Token、私钥、连接串、云访问密钥。
|
||||
- Manager 只保存必要摘要和审计索引;长日志建议落对象存储并设置保留期。
|
||||
|
||||
### 4.2 实时日志流(可选)
|
||||
|
||||
```http
|
||||
GET /api/agnet/deployments/{deployment_id}/logs/stream?agent_instance_id=agi_abc123
|
||||
GET /api/agent/deployments/{deployment_id}/logs/stream?agent_instance_id=agi_abc123
|
||||
Accept: text/event-stream
|
||||
```
|
||||
|
||||
@@ -646,12 +646,12 @@ SSE 事件类型:
|
||||
|
||||
---
|
||||
|
||||
## 5. 监控接口(Manager 拉取 Agnet 平台)
|
||||
## 5. 监控接口(Manager 拉取 Agent 平台)
|
||||
|
||||
### 5.1 项目监控快照
|
||||
|
||||
```http
|
||||
GET /api/agnet/projects/{binding_scope}/dashboard-snapshot?window=1h
|
||||
GET /api/agent/projects/{binding_scope}/dashboard-snapshot?window=1h
|
||||
```
|
||||
|
||||
响应:
|
||||
@@ -689,7 +689,7 @@ GET /api/agnet/projects/{binding_scope}/dashboard-snapshot?window=1h
|
||||
### 5.2 单部署监控快照(建议平台实现)
|
||||
|
||||
```http
|
||||
GET /api/agnet/deployments/{deployment_id}/metrics?window=15m&step=60s
|
||||
GET /api/agent/deployments/{deployment_id}/metrics?window=15m&step=60s
|
||||
```
|
||||
|
||||
响应:
|
||||
@@ -725,7 +725,7 @@ GET /api/agnet/deployments/{deployment_id}/metrics?window=15m&step=60s
|
||||
### 6.1 部署事件
|
||||
|
||||
```http
|
||||
GET /api/agnet/deployments/{deployment_id}/events?since=2026-05-02T00:00:00Z&limit=200&cursor=cur_001
|
||||
GET /api/agent/deployments/{deployment_id}/events?since=2026-05-02T00:00:00Z&limit=200&cursor=cur_001
|
||||
```
|
||||
|
||||
响应:
|
||||
@@ -768,7 +768,7 @@ GET /api/agnet/deployments/{deployment_id}/events?since=2026-05-02T00:00:00Z&lim
|
||||
### 6.2 审计日志
|
||||
|
||||
```http
|
||||
GET /api/agnet/audit-logs?user_id=user_123&binding_scope=repo_default&actor=agnet_control_plane&action=deployment.accepted&since=2026-05-02T00:00:00Z&limit=200&cursor=cur_001
|
||||
GET /api/agent/audit-logs?user_id=user_123&binding_scope=repo_default&actor=agent_control_plane&action=deployment.accepted&since=2026-05-02T00:00:00Z&limit=200&cursor=cur_001
|
||||
```
|
||||
|
||||
响应:
|
||||
@@ -780,7 +780,7 @@ GET /api/agnet/audit-logs?user_id=user_123&binding_scope=repo_default&actor=agne
|
||||
"items": [
|
||||
{
|
||||
"audit_id": "aud_001",
|
||||
"actor": "agnet_control_plane",
|
||||
"actor": "agent_control_plane",
|
||||
"action": "deployment.accepted",
|
||||
"resource": "dep_abc123",
|
||||
"user_id": "user_123",
|
||||
@@ -805,7 +805,7 @@ GET /api/agnet/audit-logs?user_id=user_123&binding_scope=repo_default&actor=agne
|
||||
### 7.1 触发解析/刷新
|
||||
|
||||
```http
|
||||
POST /api/agnet/sk-snapshots/resolve
|
||||
POST /api/agent/sk-snapshots/resolve
|
||||
```
|
||||
|
||||
请求:
|
||||
@@ -842,7 +842,7 @@ POST /api/agnet/sk-snapshots/resolve
|
||||
### 7.2 查询部署 SK 快照
|
||||
|
||||
```http
|
||||
GET /api/agnet/deployments/{deployment_id}/sk-snapshots
|
||||
GET /api/agent/deployments/{deployment_id}/sk-snapshots
|
||||
```
|
||||
|
||||
Query:
|
||||
@@ -893,17 +893,17 @@ Query:
|
||||
|
||||
## 8. 安全校验清单
|
||||
|
||||
Manager 发给 Agnet 平台前必须执行:
|
||||
Manager 发给 Agent 平台前必须执行:
|
||||
|
||||
1. `user_id`、`binding_scope`、`target_role` 与部署计划一致。
|
||||
2. 凭据型资源只传 `secret_ref`,不传明文凭据。
|
||||
3. `metadata`、`constraints`、`audit` 的 key 中不得出现 `password`、`token`、`secret`、`private_key`、`access_key`、`credential` 等敏感词。
|
||||
4. `repo_url` 不得包含用户名、密码或访问 Token。
|
||||
5. `permission_scope` 使用最小权限;生产写操作必须携带客户端审批记录,Agnet 平台不得自行补批。
|
||||
6. 高风险操作(生产部署、云资源修改、删除、扩容)必须设置 `risk_level=high`,Agnet 平台执行前只校验客户端审批证据。
|
||||
5. `permission_scope` 使用最小权限;生产写操作必须携带客户端审批记录,Agent 平台不得自行补批。
|
||||
6. 高风险操作(生产部署、云资源修改、删除、扩容)必须设置 `risk_level=high`,Agent 平台执行前只校验客户端审批证据。
|
||||
7. 所有日志/事件/审计返回给 Manager 前必须脱敏。
|
||||
|
||||
Agnet 平台不承担高危操作审批主体。审批只发生在客户端;Agnet 平台只能在执行前校验以下字段和策略是否一致:
|
||||
Agent 平台不承担高危操作审批主体。审批只发生在客户端;Agent 平台只能在执行前校验以下字段和策略是否一致:
|
||||
|
||||
| 校验项 | 要求 |
|
||||
|---|---|
|
||||
@@ -914,14 +914,14 @@ Agnet 平台不承担高危操作审批主体。审批只发生在客户端;Ag
|
||||
| `risk_level` | 高危资源写入、生产部署、云资源修改、删除和扩容必须为 `high`。 |
|
||||
| 策略 | 平台 policy、Key Vault 访问策略、Kubernetes/Workload Identity、网络策略和最小权限约束均必须允许本次动作。 |
|
||||
|
||||
任一校验不通过时,Agnet 平台应返回 `POLICY_REJECTED` 或 `FORBIDDEN_SCOPE`,不得发起额外批准流程。
|
||||
任一校验不通过时,Agent 平台应返回 `POLICY_REJECTED` 或 `FORBIDDEN_SCOPE`,不得发起额外批准流程。
|
||||
|
||||
### 8.1 字段级约束速查
|
||||
|
||||
| 对象/接口 | 必填最小集合 | 禁止内容 |
|
||||
|---|---|---|
|
||||
| `orchestration_plan` | `intent_id`、`template_hint`、`objective`、`risk_level`、`budget`、`user_context`、`billing_context`、`agent_runtime`、`metadata.correlation_id`、`agents[]` | 密钥、连接串、真实主机登录密码、NewAPI key 原文。 |
|
||||
| `agents[]` | `role_template`、`goal` | 让子 Agent 绕过 Manager/Agnet 审计的指令。 |
|
||||
| `agents[]` | `role_template`、`goal` | 让子 Agent 绕过 Manager/Agent 审计的指令。 |
|
||||
| `runtime_execution` | 任一字段存在时 `profile_id` 必填 | 明文 kubeconfig、SSH key、云访问密钥。 |
|
||||
| `resource_grants[]` | `grant_id`、`resource_id`、`resource_type`、`user_id`、`binding_scope`、`target_role`、`target_agent_ref`、`permission_scope`、`status` | 明文 `password`、`token`、`private_key`、`access_key`、`credential`、数据库 DSN。 |
|
||||
| 日志/事件/审计返回 | `request_id` 或 `correlation_id`,以及发生时间 | 未脱敏命令行、环境变量 dump、密钥片段。 |
|
||||
@@ -934,7 +934,7 @@ Agnet 平台不承担高危操作审批主体。审批只发生在客户端;Ag
|
||||
- `risk_level=high` 的生产运维任务包含 `approval_id` 和回滚引用。
|
||||
- 日志、事件、监控、审计接口都能通过 `correlation_id` 串联。
|
||||
- NewAPI 重建/部署只在实际执行并通过健康检查后标记为已部署;未执行时状态只能是 `planned`、`pending_approval`、`accepted` 或 `running`。
|
||||
- Manager 本地 `/api/agnet/*` 占位端点通过 payload 校验不等于生产 Agnet 平台已上线。
|
||||
- Manager 本地 `/api/agent/*` 占位端点通过 payload 校验不等于生产 Agent 平台已上线。
|
||||
|
||||
---
|
||||
|
||||
@@ -944,18 +944,18 @@ Agnet 平台不承担高危操作审批主体。审批只发生在客户端;Ag
|
||||
|
||||
| Manager 路由 | 用途 |
|
||||
|---|---|
|
||||
| `POST /api/agnet/deployments` | 校验并接受 orchestration_plan。 |
|
||||
| `GET /api/agnet/deployments` | 按 user/binding scope 查询部署。 |
|
||||
| `GET /api/agnet/deployments/:deployment_id` | 查询部署详情。 |
|
||||
| `POST /api/agnet/deployments/:deployment_id/stop` | 停止部署。 |
|
||||
| `GET /api/agnet/deployments/:deployment_id/logs` | 查询脱敏日志占位/联调日志。 |
|
||||
| `GET /api/agnet/deployments/:deployment_id/metrics` | 查询单部署指标占位/联调指标。 |
|
||||
| `GET /api/agnet/deployments/:deployment_id/events` | 查询事件。 |
|
||||
| `POST /api/agnet/sk-snapshots/resolve` | 解析 SK 快照。 |
|
||||
| `GET /api/agnet/deployments/:deployment_id/sk-snapshots` | 查询 SK 快照。 |
|
||||
| `GET /api/agnet/projects/:project_id/dashboard-snapshot` | 资源作用域监控快照;路径名保留旧兼容。 |
|
||||
| `GET /api/agnet/audit-logs` | 审计日志。 |
|
||||
| `POST /api/agent/deployments` | 校验并接受 orchestration_plan。 |
|
||||
| `GET /api/agent/deployments` | 按 user/binding scope 查询部署。 |
|
||||
| `GET /api/agent/deployments/:deployment_id` | 查询部署详情。 |
|
||||
| `POST /api/agent/deployments/:deployment_id/stop` | 停止部署。 |
|
||||
| `GET /api/agent/deployments/:deployment_id/logs` | 查询脱敏日志占位/联调日志。 |
|
||||
| `GET /api/agent/deployments/:deployment_id/metrics` | 查询单部署指标占位/联调指标。 |
|
||||
| `GET /api/agent/deployments/:deployment_id/events` | 查询事件。 |
|
||||
| `POST /api/agent/sk-snapshots/resolve` | 解析 SK 快照。 |
|
||||
| `GET /api/agent/deployments/:deployment_id/sk-snapshots` | 查询 SK 快照。 |
|
||||
| `GET /api/agent/projects/:project_id/dashboard-snapshot` | 资源作用域监控快照;路径名保留旧兼容。 |
|
||||
| `GET /api/agent/audit-logs` | 审计日志。 |
|
||||
|
||||
生产对接时,Manager 应把相同契约的请求发送给 Agnet 平台;本地 Manager 端点仅作为最小验证与控制面占位,不代表所有日志/监控平台能力已完整实现。
|
||||
生产对接时,Manager 应把相同契约的请求发送给 Agent 平台;本地 Manager 端点仅作为最小验证与控制面占位,不代表所有日志/监控平台能力已完整实现。
|
||||
|
||||
当前本地 `logs` 与 `metrics` 端点只返回脱敏占位/联调数据,用于验证 Manager ↔ Agnet payload、路由和验收流程。生产级实时日志流 `GET /api/agnet/deployments/{deployment_id}/logs/stream` 仍属于 Agnet 平台能力;Manager 不得把“本地占位通过”误报为“生产日志/监控已上线”。
|
||||
当前本地 `logs` 与 `metrics` 端点只返回脱敏占位/联调数据,用于验证 Manager ↔ Agent payload、路由和验收流程。生产级实时日志流 `GET /api/agent/deployments/{deployment_id}/logs/stream` 仍属于 Agent 平台能力;Manager 不得把“本地占位通过”误报为“生产日志/监控已上线”。
|
||||
@@ -45,7 +45,7 @@ Manager 生产地址:`https://code.xinghanlab.com`
|
||||
|
||||
重要修正:
|
||||
|
||||
1. 桌面客户端不要再使用 `agnet-model-builder`、`agnet-model-reviewer`、`agnet-model-product` 这类占位模型名。生产 NewAPI 没有这些模型,会返回 `No available channel for model ...`。
|
||||
1. 桌面客户端不要再使用 `agent-model-builder`、`agent-model-reviewer`、`agent-model-product` 这类占位模型名。生产 NewAPI 没有这些模型,会返回 `No available channel for model ...`。
|
||||
2. 普通 sub 当前优先建议使用生产已复核通过的 `gpt-5.4`。`claude-sonnet-4-6` 仍可作为 NewAPI 模型存在,但当前普通 sub Runtime 若按普通 `/v1/chat/completions` 方式调用 Claude Code 类模型,可能返回上游 400;客户端不要把该错误误判为加密或 Manager 创建失败。
|
||||
3. `orchestration_plan.metadata.correlation_id` 是创建 deployment 的必填字段,必须放在 `orchestration_plan` 内,不是顶层 `metadata`。
|
||||
4. 成功态不能只看 `status=completed`。客户端还应确认 `runtime_state=completed`、`artifacts.length > 0`、artifact 不是失败摘要、`tokens_used > 0`,并优先展示可下载的业务交付物。
|
||||
@@ -56,7 +56,7 @@ Manager 生产地址:`https://code.xinghanlab.com`
|
||||
|
||||
桌面客户端负责用户主体验:输入想法、回答追问、持续推进任务、查看子环节反馈、处理高危审批、接收交付结果。
|
||||
|
||||
Manager 负责辅助控制面:任务草稿桥接、资源/权限、Agnet deployment、状态/timeline/artifact/SK 查询、审批记录和短期凭证 lease。
|
||||
Manager 负责辅助控制面:任务草稿桥接、资源/权限、Agent deployment、状态/timeline/artifact/SK 查询、审批记录和短期凭证 lease。
|
||||
|
||||
本文只描述普通 sub 敏捷流程,不包含蜂群 task graph、claim、heartbeat、handoff 等蜂群模式能力。
|
||||
|
||||
@@ -67,7 +67,7 @@ Manager 负责辅助控制面:任务草稿桥接、资源/权限、Agnet deplo
|
||||
| API 前缀 | 用途 | 认证 |
|
||||
|---|---|---|
|
||||
| `/api/heicode-auth/api/user/tasks/*` | HeicodeTask 任务编排代理,创建任务、追问、查询任务 | V2 加密 body + `Authorization: Bearer <heicode_access_token>` |
|
||||
| `/api/agnet/user/*` | Manager 用户态 Agnet 控制面,deployment、timeline、artifact、审批 | V2 加密 body;未加密 Web 控制台请求继续使用 Manager session + `New-Api-User` |
|
||||
| `/api/agent/user/*` | Manager 用户态 Agent 控制面,deployment、timeline、artifact、审批 | V2 加密 body;未加密 Web 控制台请求继续使用 Manager session + `New-Api-User` |
|
||||
| `/api/swarms` | 蜂群模式创建入口 / Runtime adapter 入口 | V2 加密 body;未加密 Web 控制台请求继续使用 Manager session + `New-Api-User` |
|
||||
| `/api/user/self` | 查询当前 Manager 用户 | Manager 登录 session cookie |
|
||||
|
||||
@@ -79,7 +79,7 @@ Manager 负责辅助控制面:任务草稿桥接、资源/权限、Agnet deplo
|
||||
|
||||
| API 前缀 | V2 加密 body | 说明 |
|
||||
|---|---|---|
|
||||
| `/api/agnet/user/*` | 支持 | Manager 解密并校验设备签名后,按当前设备对应用户执行 |
|
||||
| `/api/agent/user/*` | 支持 | Manager 解密并校验设备签名后,按当前设备对应用户执行 |
|
||||
| `/api/swarms` | 支持 | Manager 解密并校验设备签名后,按当前设备对应用户创建蜂群/Runtime adapter deployment |
|
||||
| `/api/heicode-auth/*` | 支持 | Manager 解密并校验设备签名后,把明文 body 代理给上游 HeicodeTask 服务;仍需携带 `heicode_access_token` |
|
||||
| 浏览器后台普通页面请求 | 兼容未加密 JSON | 不影响现有 Manager Web 控制台 |
|
||||
@@ -127,14 +127,14 @@ Authorization: Bearer <heicode_access_token>
|
||||
客户端实现要求:
|
||||
|
||||
1. sub 流程调用不要新增一套加密协议,直接复用模型调用的 `encryptedFetch` / V2 设备签名实现。
|
||||
2. `aad` 和 `canonical` 中的 `path_with_query` 必须是 Manager 实际收到的 path,例如 `/api/agnet/user/tasks/task-1/deployment-draft`,不能把 origin 写进去。
|
||||
2. `aad` 和 `canonical` 中的 `path_with_query` 必须是 Manager 实际收到的 path,例如 `/api/agent/user/tasks/task-1/deployment-draft`,不能把 origin 写进去。
|
||||
3. 加密前的 plaintext 必须是最终 JSON body;签名里的 `sha256_hex(plaintext_body)` 必须和该 JSON 字节完全一致。
|
||||
4. 每次请求必须使用新的 `X-Heicode-Nonce` 和新的 X25519 ephemeral key。
|
||||
5. V2 请求失败时优先读取 `X-Heicode-Auth-Error` 和 `X-Heicode-Server-Time`,用于提示设备未配对、时间漂移、nonce 重放、签名错误或解密失败。
|
||||
|
||||
### 2.3 Manager 用户态 Header
|
||||
|
||||
未加密 Web 控制台请求调用 `/api/agnet/user/*` 或 `/api/swarms` 时必须带:
|
||||
未加密 Web 控制台请求调用 `/api/agent/user/*` 或 `/api/swarms` 时必须带:
|
||||
|
||||
```http
|
||||
Cookie: session=<manager-session>
|
||||
@@ -145,7 +145,7 @@ Accept: application/json
|
||||
|
||||
`New-Api-User` 必须等于当前登录用户 ID,否则会返回未授权。
|
||||
|
||||
使用 V2 加密 body 时,`/api/agnet/user/*` 和 `/api/swarms` 不依赖浏览器 session cookie,也不需要 `New-Api-User`;Manager 会从设备绑定 token 中解析用户身份。为兼容当前 Web 控制台,未加密请求仍按 session cookie + `New-Api-User` 处理。
|
||||
使用 V2 加密 body 时,`/api/agent/user/*` 和 `/api/swarms` 不依赖浏览器 session cookie,也不需要 `New-Api-User`;Manager 会从设备绑定 token 中解析用户身份。为兼容当前 Web 控制台,未加密请求仍按 session cookie + `New-Api-User` 处理。
|
||||
|
||||
### 2.4 蜂群入口加密边界
|
||||
|
||||
@@ -153,8 +153,8 @@ Accept: application/json
|
||||
|
||||
| 接口 | 所属模式 | 加密要求 |
|
||||
|---|---|---|
|
||||
| `POST /api/agnet/user/tasks/{task_id}/deployment-draft` | 普通 sub 敏捷 | V2 body 加密 |
|
||||
| `POST /api/agnet/user/deployments` | 普通 sub 敏捷 | V2 body 加密 |
|
||||
| `POST /api/agent/user/tasks/{task_id}/deployment-draft` | 普通 sub 敏捷 | V2 body 加密 |
|
||||
| `POST /api/agent/user/deployments` | 普通 sub 敏捷 | V2 body 加密 |
|
||||
| `POST /api/swarms` | 蜂群模式 | V2 body 加密 |
|
||||
|
||||
注意:本文后续章节仍只描述普通 sub 敏捷主流程;蜂群 task graph、claim、heartbeat、handoff、approval decision 等字段以单独蜂群对接文档为准。
|
||||
@@ -190,8 +190,8 @@ Accept: application/json
|
||||
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
|
||||
4. 当任务卡生成后,创建 deployment draft:POST /api/agent/user/tasks/{task_id}/deployment-draft
|
||||
5. 创建 Manager deployment:POST /api/agent/user/deployments
|
||||
6. 轮询 deployment detail / events / timeline
|
||||
7. 查询 runtime-diagnostics,区分 callback 已到、Runtime 状态、失败 Agent 和兜底摘要 artifact
|
||||
8. 查询 artifacts 列表;如果需要完整文件,调用 artifact content 代理接口下载
|
||||
@@ -205,14 +205,14 @@ Accept: application/json
|
||||
客户端展示普通 sub 结果时,不能只看 deployment `status=completed`。Manager 已提供只读诊断接口,用来识别 Runtime 是否真的完成、是否有 failed agent、是否只返回兜底摘要 artifact。
|
||||
|
||||
```http
|
||||
GET /api/agnet/user/deployments/{deployment_id}/runtime-diagnostics
|
||||
GET /api/agent/user/deployments/{deployment_id}/runtime-diagnostics
|
||||
```
|
||||
|
||||
返回核心字段:
|
||||
|
||||
| 字段 | 说明 |
|
||||
|---|---|
|
||||
| `runtime_mode` | `agnet` 表示普通 sub Runtime;`swarm` 表示蜂群 Runtime |
|
||||
| `runtime_mode` | `agent` 表示普通 sub Runtime;`swarm` 表示蜂群 Runtime |
|
||||
| `runtime_swarm_id` / `runtime_deployment_id` | Manager 保存的 Runtime 映射 |
|
||||
| `data_source` | 当前诊断来源,正常为 `runtime_status` |
|
||||
| `status` / `phase` | Runtime 直接返回的状态和阶段 |
|
||||
@@ -235,7 +235,7 @@ GET /api/agnet/user/deployments/{deployment_id}/runtime-diagnostics
|
||||
|
||||
1. deployment detail / timeline / artifacts 仍按原接口展示。
|
||||
2. 若 `warnings` 包含 `runtime_summary_artifact_only`,需要提示“当前没有最终交付产物,请查看运行日志/等待 Runtime 修复”。
|
||||
3. 若 `runtime_mode=agnet`,按普通 sub 敏捷展示;若 `runtime_mode=swarm`,按蜂群模式展示任务图/Agent 编队,不能混用两套文案。
|
||||
3. 若 `runtime_mode=agent`,按普通 sub 敏捷展示;若 `runtime_mode=swarm`,按蜂群模式展示任务图/Agent 编队,不能混用两套文案。
|
||||
4. 该接口只读,失败时不应中断已有 timeline/artifact 展示。
|
||||
|
||||
### 3.2 Artifact 完整内容下载
|
||||
@@ -245,13 +245,13 @@ GET /api/agnet/user/deployments/{deployment_id}/runtime-diagnostics
|
||||
列表:
|
||||
|
||||
```http
|
||||
GET /api/agnet/user/deployments/{deployment_id}/artifacts
|
||||
GET /api/agent/user/deployments/{deployment_id}/artifacts
|
||||
```
|
||||
|
||||
下载:
|
||||
|
||||
```http
|
||||
GET /api/agnet/user/deployments/{deployment_id}/artifacts/{artifact_id}/content
|
||||
GET /api/agent/user/deployments/{deployment_id}/artifacts/{artifact_id}/content
|
||||
```
|
||||
|
||||
Manager 行为:
|
||||
@@ -339,26 +339,26 @@ function classifyArtifact(item: Artifact, content: string, contentType?: string)
|
||||
| Manager 版本 | `1.4.19` |
|
||||
| Agent Manager Runtime | `http://20.212.121.126` |
|
||||
| Runtime health | `healthy` |
|
||||
| Manager callback | `https://code.xinghanlab.com/api/agnet/callbacks/swarm-events` |
|
||||
| Manager callback | `https://code.xinghanlab.com/api/agent/callbacks/swarm-events` |
|
||||
|
||||
已验证成功的链路:
|
||||
|
||||
```text
|
||||
Manager 登录
|
||||
-> /api/user/self
|
||||
-> /api/agnet/runtime/health
|
||||
-> /api/agnet/user/tasks/{task_id}/deployment-draft
|
||||
-> /api/agnet/user/deployments
|
||||
-> /api/agent/runtime/health
|
||||
-> /api/agent/user/tasks/{task_id}/deployment-draft
|
||||
-> /api/agent/user/deployments
|
||||
-> Manager 调 Agent Manager Runtime create
|
||||
-> Runtime 自动 callback 到 Manager
|
||||
-> /api/agnet/user/deployments/{deployment_id}
|
||||
-> /api/agnet/user/deployments/{deployment_id}/metrics
|
||||
-> /api/agnet/user/deployments/{deployment_id}/events
|
||||
-> /api/agnet/user/deployments/{deployment_id}/logs
|
||||
-> /api/agnet/user/deployments/{deployment_id}/artifacts
|
||||
-> /api/agnet/user/deployments/{deployment_id}/sk-snapshots
|
||||
-> /api/agnet/user/deployments/{deployment_id}/timeline
|
||||
-> /api/agnet/user/deployments/{deployment_id}/stop
|
||||
-> /api/agent/user/deployments/{deployment_id}
|
||||
-> /api/agent/user/deployments/{deployment_id}/metrics
|
||||
-> /api/agent/user/deployments/{deployment_id}/events
|
||||
-> /api/agent/user/deployments/{deployment_id}/logs
|
||||
-> /api/agent/user/deployments/{deployment_id}/artifacts
|
||||
-> /api/agent/user/deployments/{deployment_id}/sk-snapshots
|
||||
-> /api/agent/user/deployments/{deployment_id}/timeline
|
||||
-> /api/agent/user/deployments/{deployment_id}/stop
|
||||
```
|
||||
|
||||
最新生产烟测 ID:
|
||||
@@ -431,7 +431,7 @@ Manager 登录
|
||||
客户端处理:
|
||||
|
||||
- 保存 `data.id`。
|
||||
- 后续 `/api/agnet/user/*` 请求带 `New-Api-User: <id>`。
|
||||
- 后续 `/api/agent/user/*` 请求带 `New-Api-User: <id>`。
|
||||
|
||||
## 5. HeicodeTask 任务编排
|
||||
|
||||
@@ -594,11 +594,11 @@ Authorization: Bearer <heicode_access_token>
|
||||
- `status=awaiting_approval`:轮询审批接口。
|
||||
- `status=completed/failed/paused`:停止高频轮询。
|
||||
|
||||
## 6. 从任务生成 Agnet Deployment Draft
|
||||
## 6. 从任务生成 Agent Deployment Draft
|
||||
|
||||
### `POST /api/agnet/user/tasks/{task_id}/deployment-draft`
|
||||
### `POST /api/agent/user/tasks/{task_id}/deployment-draft`
|
||||
|
||||
把 HeicodeTask 快照转换成 Manager 可创建的 Agnet orchestration plan。
|
||||
把 HeicodeTask 快照转换成 Manager 可创建的 Agent orchestration plan。
|
||||
|
||||
生产已验证:客户端只要能提供 task snapshot,就可以不依赖 Manager 再去拉 task,直接调用本接口生成 draft。
|
||||
|
||||
@@ -685,7 +685,7 @@ Authorization: Bearer <heicode_access_token>
|
||||
| `sub_mode` | string | 是 | `agile` 或 `waterfall`,桌面客户端默认传 `agile` |
|
||||
| `binding_scope` | string | 否 | 建议 `task-<task_id>` |
|
||||
| `role_templates` | string[] | 否 | 默认 `["backend"]` |
|
||||
| `default_model_id` | string | 否 | 子 Agnet 默认运行模型;生产默认建议 `claude-sonnet-4-6` |
|
||||
| `default_model_id` | string | 否 | 子 Agent 默认运行模型;生产默认建议 `claude-sonnet-4-6` |
|
||||
| `budget` | object | 否 | 不传使用默认预算 |
|
||||
| `resource_grants` | array | 否 | 不传时 Manager 生成只读 task context grant |
|
||||
|
||||
@@ -717,7 +717,7 @@ Authorization: Bearer <heicode_access_token>
|
||||
"newapi_group": "default"
|
||||
},
|
||||
"agent_runtime": {
|
||||
"platform": "agnet",
|
||||
"platform": "agent",
|
||||
"agents": [
|
||||
{
|
||||
"role": "backend",
|
||||
@@ -747,7 +747,7 @@ Authorization: Bearer <heicode_access_token>
|
||||
|
||||
## 7. 创建 Manager Deployment
|
||||
|
||||
### `POST /api/agnet/user/deployments`
|
||||
### `POST /api/agent/user/deployments`
|
||||
|
||||
使用上一步 `orchestration_plan` 创建 Manager deployment。
|
||||
|
||||
@@ -783,7 +783,7 @@ Authorization: Bearer <heicode_access_token>
|
||||
"newapi_group": "default"
|
||||
},
|
||||
"agent_runtime": {
|
||||
"platform": "agnet",
|
||||
"platform": "agent",
|
||||
"agents": [
|
||||
{
|
||||
"role": "backend",
|
||||
@@ -910,7 +910,7 @@ Authorization: Bearer <heicode_access_token>
|
||||
|
||||
### 8.1 列表
|
||||
|
||||
#### `GET /api/agnet/user/deployments`
|
||||
#### `GET /api/agent/user/deployments`
|
||||
|
||||
响应:
|
||||
|
||||
@@ -938,13 +938,13 @@ Authorization: Bearer <heicode_access_token>
|
||||
|
||||
### 8.2 详情
|
||||
|
||||
#### `GET /api/agnet/user/deployments/{deployment_id}`
|
||||
#### `GET /api/agent/user/deployments/{deployment_id}`
|
||||
|
||||
响应字段同列表单项,包含完整 `orchestration_plan`。
|
||||
|
||||
### 8.3 停止
|
||||
|
||||
#### `POST /api/agnet/user/deployments/{deployment_id}/stop`
|
||||
#### `POST /api/agent/user/deployments/{deployment_id}/stop`
|
||||
|
||||
请求:
|
||||
|
||||
@@ -971,7 +971,7 @@ Authorization: Bearer <heicode_access_token>
|
||||
|
||||
### 9.1 Events
|
||||
|
||||
#### `GET /api/agnet/user/deployments/{deployment_id}/events`
|
||||
#### `GET /api/agent/user/deployments/{deployment_id}/events`
|
||||
|
||||
用于展示 deployment 事件流。
|
||||
|
||||
@@ -996,7 +996,7 @@ Authorization: Bearer <heicode_access_token>
|
||||
|
||||
### 9.2 Logs
|
||||
|
||||
#### `GET /api/agnet/user/deployments/{deployment_id}/logs`
|
||||
#### `GET /api/agent/user/deployments/{deployment_id}/logs`
|
||||
|
||||
用于展示最近日志。当前 Manager 未接真实 Runtime 时主要是审计日志。
|
||||
|
||||
@@ -1020,7 +1020,7 @@ Authorization: Bearer <heicode_access_token>
|
||||
|
||||
### 9.3 Metrics
|
||||
|
||||
#### `GET /api/agnet/user/deployments/{deployment_id}/metrics`
|
||||
#### `GET /api/agent/user/deployments/{deployment_id}/metrics`
|
||||
|
||||
用于展示成本、耗时、token、资源指标。真实 Runtime 未接入时可能为空或为本地占位。
|
||||
|
||||
@@ -1039,7 +1039,7 @@ Authorization: Bearer <heicode_access_token>
|
||||
|
||||
### 9.4 Artifacts
|
||||
|
||||
#### `GET /api/agnet/user/deployments/{deployment_id}/artifacts`
|
||||
#### `GET /api/agent/user/deployments/{deployment_id}/artifacts`
|
||||
|
||||
查询中间交付物和最终交付物摘要。注意:列表接口只返回摘要和引用,不能证明代码内容有效;客户端需要按需调用 content 接口并按“Artifact 展示和有效性判断”继续分类。
|
||||
|
||||
@@ -1070,7 +1070,7 @@ Authorization: Bearer <heicode_access_token>
|
||||
}
|
||||
```
|
||||
|
||||
#### `GET /api/agnet/user/deployments/{deployment_id}/artifacts/{artifact_id}/content`
|
||||
#### `GET /api/agent/user/deployments/{deployment_id}/artifacts/{artifact_id}/content`
|
||||
|
||||
读取完整产物正文。客户端必须通过该接口判断产物是否真的是代码,而不是只看 `artifact_type`。
|
||||
|
||||
@@ -1113,7 +1113,7 @@ summary-only 示例片段:
|
||||
|
||||
### 9.5 SK Snapshots
|
||||
|
||||
#### `GET /api/agnet/user/deployments/{deployment_id}/sk-snapshots`
|
||||
#### `GET /api/agent/user/deployments/{deployment_id}/sk-snapshots`
|
||||
|
||||
查询本轮任务使用的 SK 快照。
|
||||
|
||||
@@ -1147,7 +1147,7 @@ summary-only 示例片段:
|
||||
|
||||
### 9.6 Timeline
|
||||
|
||||
#### `GET /api/agnet/user/deployments/{deployment_id}/timeline`
|
||||
#### `GET /api/agent/user/deployments/{deployment_id}/timeline`
|
||||
|
||||
聚合审计事件、Runtime callback、artifact、SK snapshot。桌面客户端推荐优先使用这个接口渲染“当前子环节进度”。
|
||||
|
||||
@@ -1206,7 +1206,7 @@ summary-only 示例片段:
|
||||
|
||||
### 10.1 查询待审批
|
||||
|
||||
#### `GET /api/agnet/approvals?status=pending&deployment_id={deployment_id}`
|
||||
#### `GET /api/agent/approvals?status=pending&deployment_id={deployment_id}`
|
||||
|
||||
查询参数:
|
||||
|
||||
@@ -1236,7 +1236,7 @@ summary-only 示例片段:
|
||||
"requires_credential": true,
|
||||
"credential_lease_id": "",
|
||||
"status": "pending",
|
||||
"requested_by": "agnet-runtime",
|
||||
"requested_by": "agent-runtime",
|
||||
"request_reason": "需要写入功能分支",
|
||||
"ttl_seconds": 900,
|
||||
"expires_at": 1779850900000,
|
||||
@@ -1249,7 +1249,7 @@ summary-only 示例片段:
|
||||
|
||||
### 10.2 同意审批
|
||||
|
||||
#### `POST /api/agnet/approvals/{approval_id}/approve`
|
||||
#### `POST /api/agent/approvals/{approval_id}/approve`
|
||||
|
||||
请求:
|
||||
|
||||
@@ -1270,7 +1270,7 @@ summary-only 示例片段:
|
||||
"credential_lease_id": "lease_xxx",
|
||||
"credential_lease": {
|
||||
"lease_id": "lease_xxx",
|
||||
"credential_ref": "lease://agnet/lease_xxx",
|
||||
"credential_ref": "lease://agent/lease_xxx",
|
||||
"status": "active",
|
||||
"expires_at": 1779850900000
|
||||
}
|
||||
@@ -1282,7 +1282,7 @@ summary-only 示例片段:
|
||||
|
||||
### 10.3 拒绝审批
|
||||
|
||||
#### `POST /api/agnet/approvals/{approval_id}/reject`
|
||||
#### `POST /api/agent/approvals/{approval_id}/reject`
|
||||
|
||||
请求:
|
||||
|
||||
@@ -1307,7 +1307,7 @@ summary-only 示例片段:
|
||||
|
||||
### 10.4 查询 lease
|
||||
|
||||
#### `GET /api/agnet/credential-leases?status=active&deployment_id={deployment_id}`
|
||||
#### `GET /api/agent/credential-leases?status=active&deployment_id={deployment_id}`
|
||||
|
||||
响应:
|
||||
|
||||
@@ -1318,7 +1318,7 @@ summary-only 示例片段:
|
||||
"items": [
|
||||
{
|
||||
"lease_id": "lease_xxx",
|
||||
"credential_ref": "lease://agnet/lease_xxx",
|
||||
"credential_ref": "lease://agent/lease_xxx",
|
||||
"approval_id": "appr_xxx",
|
||||
"deployment_id": "dep_464a08b7b751",
|
||||
"resource_id": "res_git_main",
|
||||
@@ -1334,7 +1334,7 @@ summary-only 示例片段:
|
||||
|
||||
### 10.5 撤销 lease
|
||||
|
||||
#### `POST /api/agnet/credential-leases/{lease_id}/revoke`
|
||||
#### `POST /api/agent/credential-leases/{lease_id}/revoke`
|
||||
|
||||
请求:
|
||||
|
||||
@@ -1358,9 +1358,9 @@ summary-only 示例片段:
|
||||
|
||||
## 11. 角色模板
|
||||
|
||||
### `GET /api/agnet/role-templates`
|
||||
### `GET /api/agent/role-templates`
|
||||
|
||||
获取推荐子 Agnet 角色,桌面端可用于角色选择页。
|
||||
获取推荐子 Agent 角色,桌面端可用于角色选择页。
|
||||
|
||||
响应:
|
||||
|
||||
@@ -1371,7 +1371,7 @@ summary-only 示例片段:
|
||||
"items": [
|
||||
{
|
||||
"key": "backend",
|
||||
"display_name": "Backend Agnet",
|
||||
"display_name": "Backend Agent",
|
||||
"summary": "实现后端接口、数据模型和服务逻辑",
|
||||
"default_model": "gpt-5.4",
|
||||
"default_permissions": ["repo:read", "repo:write"],
|
||||
@@ -1382,7 +1382,7 @@ summary-only 示例片段:
|
||||
}
|
||||
```
|
||||
|
||||
> `default_model` 由 Manager 单一来源 `defaultAgnetModelID()` 提供(环境变量 `AGNET_DEFAULT_MODEL_ID`,默认生产已验证的 `gpt-5.4`)。Manager 不再回退 `agnet-model-<role>` 占位名;客户端如不指定 `default_model_id`,draft/runtime agent 会自动采用该单一默认值。
|
||||
> `default_model` 由 Manager 单一来源 `defaultAgentModelID()` 提供(环境变量 `AGENT_DEFAULT_MODEL_ID`,默认生产已验证的 `gpt-5.4`)。Manager 不再回退 `agent-model-<role>` 占位名;客户端如不指定 `default_model_id`,draft/runtime agent 会自动采用该单一默认值。
|
||||
|
||||
## 12. 状态枚举
|
||||
|
||||
@@ -1393,12 +1393,12 @@ summary-only 示例片段:
|
||||
| `draft` | 草稿 | 可继续编辑 |
|
||||
| `configuring` | 等待回答追问 | 展示 followups |
|
||||
| `running` | 可推进/运行中 | 创建 deployment 或展示 timeline |
|
||||
| `awaiting_approval` | 等待审批 | 拉取 `/api/agnet/approvals` |
|
||||
| `awaiting_approval` | 等待审批 | 拉取 `/api/agent/approvals` |
|
||||
| `completed` | 完成 | 展示交付物 |
|
||||
| `failed` | 失败 | 展示失败原因 |
|
||||
| `paused` | 暂停 | 允许继续或停止 |
|
||||
|
||||
### Agnet Deployment.status
|
||||
### Agent Deployment.status
|
||||
|
||||
| 状态 | 说明 |
|
||||
|---|---|
|
||||
@@ -1410,7 +1410,7 @@ summary-only 示例片段:
|
||||
|
||||
> 注意:`status` 由 Manager 接收 Runtime `deployment.status_changed` 后镜像写入。除上述规范值外,Runtime 若回传其他自定义状态字符串,Manager 会透传保存,客户端应对未知值做兜底(按非终态处理或显示原值)。
|
||||
|
||||
### Agnet Deployment.runtime_state
|
||||
### Agent Deployment.runtime_state
|
||||
|
||||
| 状态 | 说明 |
|
||||
|---|---|
|
||||
@@ -1508,7 +1508,7 @@ while (current.status === 'configuring') {
|
||||
|
||||
const draft = await encryptedManagerRequest<Envelope<DeploymentDraft>>(
|
||||
'POST',
|
||||
`/api/agnet/user/tasks/${current.id}/deployment-draft`,
|
||||
`/api/agent/user/tasks/${current.id}/deployment-draft`,
|
||||
{
|
||||
task: current,
|
||||
sub_mode: 'agile',
|
||||
@@ -1520,7 +1520,7 @@ const draft = await encryptedManagerRequest<Envelope<DeploymentDraft>>(
|
||||
|
||||
const deployment = await encryptedManagerRequest<Envelope<Deployment>>(
|
||||
'POST',
|
||||
'/api/agnet/user/deployments',
|
||||
'/api/agent/user/deployments',
|
||||
{ orchestration_plan: draft.data.orchestration_plan }
|
||||
)
|
||||
|
||||
@@ -1528,13 +1528,13 @@ const deploymentId = deployment.data.deployment_id
|
||||
|
||||
setInterval(async () => {
|
||||
const timeline = await manager.get<Envelope<DeploymentTimeline>>(
|
||||
`/api/agnet/user/deployments/${deploymentId}/timeline`,
|
||||
`/api/agent/user/deployments/${deploymentId}/timeline`,
|
||||
{ headers: { 'New-Api-User': String(userId) } }
|
||||
)
|
||||
renderTimeline(timeline.data.timeline)
|
||||
|
||||
const approvals = await manager.get<Envelope<ApprovalList>>(
|
||||
`/api/agnet/approvals?status=pending&deployment_id=${deploymentId}`,
|
||||
`/api/agent/approvals?status=pending&deployment_id=${deploymentId}`,
|
||||
{ headers: { 'New-Api-User': String(userId) } }
|
||||
)
|
||||
renderApprovals(approvals.data.items)
|
||||
@@ -1542,13 +1542,13 @@ setInterval(async () => {
|
||||
|
||||
async function refreshSubDelivery(deploymentId: string) {
|
||||
const diagnostics = await manager.get<Envelope<RuntimeDiagnostics>>(
|
||||
`/api/agnet/user/deployments/${deploymentId}/runtime-diagnostics`,
|
||||
`/api/agent/user/deployments/${deploymentId}/runtime-diagnostics`,
|
||||
{ headers: { 'New-Api-User': String(userId) } }
|
||||
)
|
||||
renderRuntimeDiagnostics(diagnostics.data)
|
||||
|
||||
const artifacts = await manager.get<Envelope<ArtifactList>>(
|
||||
`/api/agnet/user/deployments/${deploymentId}/artifacts`,
|
||||
`/api/agent/user/deployments/${deploymentId}/artifacts`,
|
||||
{ headers: { 'New-Api-User': String(userId) } }
|
||||
)
|
||||
renderArtifacts(artifacts.data.artifacts)
|
||||
@@ -1558,7 +1558,7 @@ async function refreshSubDelivery(deploymentId: string) {
|
||||
)
|
||||
if (firstDeliverable) {
|
||||
const content = await manager.get<string>(
|
||||
`/api/agnet/user/deployments/${deploymentId}/artifacts/${firstDeliverable.artifact_id}/content`,
|
||||
`/api/agent/user/deployments/${deploymentId}/artifacts/${firstDeliverable.artifact_id}/content`,
|
||||
{
|
||||
headers: { 'New-Api-User': String(userId) },
|
||||
responseType: 'text',
|
||||
@@ -1571,10 +1571,10 @@ async function refreshSubDelivery(deploymentId: string) {
|
||||
|
||||
兼容说明:
|
||||
|
||||
- 桌面客户端对 `POST` 等有 body 的 sub 请求走 V2 加密时,`/api/agnet/user/*` 不需要 `New-Api-User`,也不依赖浏览器 cookie。
|
||||
- 桌面客户端对 `POST` 等有 body 的 sub 请求走 V2 加密时,`/api/agent/user/*` 不需要 `New-Api-User`,也不依赖浏览器 cookie。
|
||||
- `GET` 查询接口本身没有请求 body,当前生产兼容路径仍使用 Manager session cookie + `New-Api-User`。如果桌面本地服务后续要完全脱离 session cookie 查询 timeline / artifact / approval,需要再补“无 body 的 V2 设备签名 GET”协议。
|
||||
- `/api/heicode-auth/*` 仍必须带 `Authorization: Bearer <heicode_access_token>`,该 token 只用于上游 HeicodeTask 认证。
|
||||
- 如果客户端临时还没有接入 V2 加密,只能作为调试兼容路径使用 Manager session + `New-Api-User` 调 `/api/agnet/user/*`;正式桌面流程不要依赖该路径。
|
||||
- 如果客户端临时还没有接入 V2 加密,只能作为调试兼容路径使用 Manager session + `New-Api-User` 调 `/api/agent/user/*`;正式桌面流程不要依赖该路径。
|
||||
|
||||
## 15. 当前生产注意事项
|
||||
|
||||
|
||||
@@ -8,24 +8,24 @@
|
||||
当 Runtime 提供正式 `POST /api/swarms` 时,Manager 建议配置:
|
||||
|
||||
```bash
|
||||
AGNET_RUNTIME_ENABLED=true
|
||||
AGNET_RUNTIME_ASYNC=false
|
||||
AGNET_RUNTIME_BASE_URL=http://<runtime-host>
|
||||
AGNET_RUNTIME_SERVICE_TOKEN=<service-token>
|
||||
AGNET_RUNTIME_HEALTH_PATH=/api/agnet/health
|
||||
AGNET_RUNTIME_CREATE_PATH=/api/swarms
|
||||
AGNET_RUNTIME_STOP_PATH=/api/swarms/{swarm_id}/stop
|
||||
AGNET_RUNTIME_APPROVAL_DECISION_PATH=/api/swarms/{swarm_id}/approvals/{approval_id}
|
||||
AGNET_RUNTIME_TIMEOUT_SECONDS=10
|
||||
AGENT_RUNTIME_ENABLED=true
|
||||
AGENT_RUNTIME_ASYNC=false
|
||||
AGENT_RUNTIME_BASE_URL=http://<runtime-host>
|
||||
AGENT_RUNTIME_SERVICE_TOKEN=<service-token>
|
||||
AGENT_RUNTIME_HEALTH_PATH=/api/agent/health
|
||||
AGENT_RUNTIME_CREATE_PATH=/api/swarms
|
||||
AGENT_RUNTIME_STOP_PATH=/api/swarms/{swarm_id}/stop
|
||||
AGENT_RUNTIME_APPROVAL_DECISION_PATH=/api/swarms/{swarm_id}/approvals/{approval_id}
|
||||
AGENT_RUNTIME_TIMEOUT_SECONDS=10
|
||||
HEICODE_PUBLIC_BASE_URL=https://code.xinghanlab.com
|
||||
AGNET_RUNTIME_CALLBACK_URL=https://code.xinghanlab.com/api/agnet/callbacks/swarm-events
|
||||
AGNET_CALLBACK_TOKEN=<callback-token>
|
||||
AGENT_RUNTIME_CALLBACK_URL=https://code.xinghanlab.com/api/agent/callbacks/swarm-events
|
||||
AGENT_CALLBACK_TOKEN=<callback-token>
|
||||
```
|
||||
|
||||
说明:
|
||||
|
||||
1. `AGNET_RUNTIME_SERVICE_TOKEN` 是 Manager 调 Runtime 的 Bearer token。
|
||||
2. `AGNET_CALLBACK_TOKEN` 是 Runtime 回调 Manager 时放在 `X-Agnet-Service-Token` 的 token。
|
||||
1. `AGENT_RUNTIME_SERVICE_TOKEN` 是 Manager 调 Runtime 的 Bearer token。
|
||||
2. `AGENT_CALLBACK_TOKEN` 是 Runtime 回调 Manager 时放在 `X-Agent-Service-Token` 的 token。
|
||||
3. 两个 token 可以不同,不要写进前端或日志。
|
||||
4. 如果 Runtime 返回 `swarm_id`,Manager 会保存到 `runtime_swarm_id`。
|
||||
5. 如果 Runtime 返回 `deployment_id`,Manager 会保存到 `runtime_deployment_id`。
|
||||
@@ -35,18 +35,18 @@ AGNET_CALLBACK_TOKEN=<callback-token>
|
||||
如果短期只用现有 Orchestrator 的 `/health` 和 `/tasks` 先做连通性测试,可以临时配置:
|
||||
|
||||
```bash
|
||||
AGNET_RUNTIME_ENABLED=true
|
||||
AGNET_RUNTIME_ASYNC=false
|
||||
AGNET_RUNTIME_BASE_URL=http://52.139.240.116:8000
|
||||
AGNET_RUNTIME_SERVICE_TOKEN=<optional-if-supported>
|
||||
AGNET_RUNTIME_HEALTH_PATH=/health
|
||||
AGNET_RUNTIME_CREATE_PATH=/tasks
|
||||
AGNET_RUNTIME_STOP_PATH=/tasks/{runtime_deployment_id}/stop
|
||||
AGNET_RUNTIME_APPROVAL_DECISION_PATH=/api/swarms/{swarm_id}/approvals/{approval_id}
|
||||
AGNET_RUNTIME_TIMEOUT_SECONDS=10
|
||||
AGENT_RUNTIME_ENABLED=true
|
||||
AGENT_RUNTIME_ASYNC=false
|
||||
AGENT_RUNTIME_BASE_URL=http://52.139.240.116:8000
|
||||
AGENT_RUNTIME_SERVICE_TOKEN=<optional-if-supported>
|
||||
AGENT_RUNTIME_HEALTH_PATH=/health
|
||||
AGENT_RUNTIME_CREATE_PATH=/tasks
|
||||
AGENT_RUNTIME_STOP_PATH=/tasks/{runtime_deployment_id}/stop
|
||||
AGENT_RUNTIME_APPROVAL_DECISION_PATH=/api/swarms/{swarm_id}/approvals/{approval_id}
|
||||
AGENT_RUNTIME_TIMEOUT_SECONDS=10
|
||||
HEICODE_PUBLIC_BASE_URL=https://code.xinghanlab.com
|
||||
AGNET_RUNTIME_CALLBACK_URL=https://code.xinghanlab.com/api/agnet/callbacks/swarm-events
|
||||
AGNET_CALLBACK_TOKEN=<callback-token>
|
||||
AGENT_RUNTIME_CALLBACK_URL=https://code.xinghanlab.com/api/agent/callbacks/swarm-events
|
||||
AGENT_CALLBACK_TOKEN=<callback-token>
|
||||
```
|
||||
|
||||
注意:
|
||||
@@ -60,9 +60,9 @@ AGNET_CALLBACK_TOKEN=<callback-token>
|
||||
推荐使用 service token:
|
||||
|
||||
```http
|
||||
POST /api/agnet/callbacks/swarm-events
|
||||
X-Agnet-Service-Token: <callback-token>
|
||||
X-Agnet-Event-Id: <event-id>
|
||||
POST /api/agent/callbacks/swarm-events
|
||||
X-Agent-Service-Token: <callback-token>
|
||||
X-Agent-Event-Id: <event-id>
|
||||
X-Correlation-ID: <correlation-id>
|
||||
Content-Type: application/json
|
||||
```
|
||||
@@ -70,9 +70,9 @@ Content-Type: application/json
|
||||
也支持 HMAC:
|
||||
|
||||
```http
|
||||
X-Agnet-Event-Id: <event-id>
|
||||
X-Agnet-Timestamp: <unix-ms>
|
||||
X-Agnet-Signature: sha256=<hex>
|
||||
X-Agent-Event-Id: <event-id>
|
||||
X-Agent-Timestamp: <unix-ms>
|
||||
X-Agent-Signature: sha256=<hex>
|
||||
X-Correlation-ID: <correlation-id>
|
||||
Content-Type: application/json
|
||||
```
|
||||
@@ -88,13 +88,13 @@ timestamp + "." + event_id + "." + raw_body
|
||||
健康检查:
|
||||
|
||||
```bash
|
||||
curl -sS "$AGNET_RUNTIME_BASE_URL$AGNET_RUNTIME_HEALTH_PATH"
|
||||
curl -sS "$AGENT_RUNTIME_BASE_URL$AGENT_RUNTIME_HEALTH_PATH"
|
||||
```
|
||||
|
||||
Manager 本地默认模拟链路:
|
||||
|
||||
```bash
|
||||
curl -sS -X POST "https://code.xinghanlab.com/api/agnet/user/deployments/<manager-deployment-id>/simulate-events" \
|
||||
curl -sS -X POST "https://code.xinghanlab.com/api/agent/user/deployments/<manager-deployment-id>/simulate-events" \
|
||||
-H "Authorization: Bearer <manager-user-token>" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{}'
|
||||
@@ -105,10 +105,10 @@ curl -sS -X POST "https://code.xinghanlab.com/api/agnet/user/deployments/<manage
|
||||
模拟 Runtime 回调 Manager:
|
||||
|
||||
```bash
|
||||
curl -sS -X POST "https://code.xinghanlab.com/api/agnet/callbacks/swarm-events" \
|
||||
curl -sS -X POST "https://code.xinghanlab.com/api/agent/callbacks/swarm-events" \
|
||||
-H "Content-Type: application/json" \
|
||||
-H "X-Agnet-Service-Token: $AGNET_CALLBACK_TOKEN" \
|
||||
-H "X-Agnet-Event-Id: evt-smoke-001" \
|
||||
-H "X-Agent-Service-Token: $AGENT_CALLBACK_TOKEN" \
|
||||
-H "X-Agent-Event-Id: evt-smoke-001" \
|
||||
-H "X-Correlation-ID: corr-smoke-001" \
|
||||
-d '{
|
||||
"event_id": "evt-smoke-001",
|
||||
|
||||
@@ -21,9 +21,9 @@
|
||||
|
||||
## 2. §5 审计日志的敏感字段 redact
|
||||
|
||||
**当前现象**:mcp-server `/api/agnet/audit` 返回的条目里 `payload` 字段可能包含 token / secret 原文(按 §5 设计应在服务端 mask)。
|
||||
**当前现象**:mcp-server `/api/agent/audit` 返回的条目里 `payload` 字段可能包含 token / secret 原文(按 §5 设计应在服务端 mask)。
|
||||
|
||||
**前端兜底**:`src/features/agnet-console/pages.tsx::maskIfSecret()` 做了第二层 mask(按 key 名匹配 `secret|token|password|api_key`)。
|
||||
**前端兜底**:`src/features/agent-console/pages.tsx::maskIfSecret()` 做了第二层 mask(按 key 名匹配 `secret|token|password|api_key`)。
|
||||
|
||||
**建议**:mcp-server 在写审计或读出时统一 mask,前端可以删掉那一层。
|
||||
|
||||
@@ -41,13 +41,13 @@
|
||||
|
||||
**建议**:维持当前字段名即可。如果未来引入按模型聚合,建议另起 endpoint,不要在 `items` 里混。
|
||||
|
||||
## 5. §5 `/api/agnet/*` stub 数据
|
||||
## 5. §5 `/api/agent/*` stub 数据
|
||||
|
||||
**当前**:mcp-server 内存 dict(按 §5.0 设计),无 agent-manager 真实转发。
|
||||
|
||||
**阻塞解除条件**:
|
||||
- agent-manager 侧给 mcp-server 派发 service token
|
||||
- mcp-server 把 stub 换成对 `http://20.212.121.126` 的真实转发;域名 `agent-manager.taijiagnet.com` 的 DNS / HTTPS 后续单独修复,不作为当前联调阻塞项
|
||||
- mcp-server 把 stub 换成对 `http://20.212.121.126` 的真实转发;域名 `agent-manager.taijiagent.com` 的 DNS / HTTPS 后续单独修复,不作为当前联调阻塞项
|
||||
|
||||
Manager 前端已按 §5 契约消费,stub → 真实数据无需前端改动。
|
||||
|
||||
|
||||
@@ -27,14 +27,14 @@ Agent Manager 需要做的是:接收 Manager 生成的 deployment payload,
|
||||
|
||||
| 能力 | 状态 | 说明 |
|
||||
|---|---|---|
|
||||
| HeicodeTask -> deployment draft | 已完成 | `POST /api/agnet/user/tasks/{task_id}/deployment-draft` |
|
||||
| 用户态 deployment 创建 | 已完成 | `POST /api/agnet/user/deployments` |
|
||||
| HeicodeTask -> deployment draft | 已完成 | `POST /api/agent/user/tasks/{task_id}/deployment-draft` |
|
||||
| 用户态 deployment 创建 | 已完成 | `POST /api/agent/user/deployments` |
|
||||
| Runtime create bridge | 已完成 | 通过环境变量调用 Agent Manager |
|
||||
| Runtime stop bridge | 已完成 | 停止时同步 Runtime |
|
||||
| callback 接收 | 已完成 | `POST /api/agnet/callbacks/swarm-events` |
|
||||
| callback 接收 | 已完成 | `POST /api/agent/callbacks/swarm-events` |
|
||||
| phase / timeline 聚合 | 已完成 | deployment timeline 聚合 audit、callback、artifact、SK |
|
||||
| artifact 查询 | 已完成 | `GET /api/agnet/user/deployments/{deployment_id}/artifacts` |
|
||||
| approval 查询/approve/reject | 已完成 | `/api/agnet/approvals` |
|
||||
| artifact 查询 | 已完成 | `GET /api/agent/user/deployments/{deployment_id}/artifacts` |
|
||||
| approval 查询/approve/reject | 已完成 | `/api/agent/approvals` |
|
||||
| V2 body 加密 | 已完成 | 桌面客户端到 Manager 的 POST 请求复用模型调用加密协议 |
|
||||
| 生产验证 | 已完成核心链路 | 生产已验证 Manager `1.4.18` 可通过 `/api/swarms` 创建普通 sub run,持久化 `runtime_swarm_id=swm_*`,Agent Manager 自动 callback 可写入 `events/timeline`;`1.4.19` 补齐 callback 后反写 deployment 快照 |
|
||||
| callback 状态反写 | 已完成 | Manager 接收 `deployment.status_changed`、`phase.changed`、`timeline.updated`、`agent.started/completed/crashed` 后,会同步更新 deployment `status/phase/runtime_state/agent_instances`,避免详情页长期停留 `initializing/pending` |
|
||||
@@ -44,7 +44,7 @@ Agent Manager 需要做的是:接收 Manager 生成的 deployment payload,
|
||||
|
||||
| 任务 | 必需 | 原因 | 验收 |
|
||||
|---|---:|---|---|
|
||||
| 接收 Manager 创建请求 | 是 | Manager 会把普通 sub deployment 发送给 Agent Manager | `POST /api/agnet/deployments` 或配置的 create path 返回 2xx |
|
||||
| 接收 Manager 创建请求 | 是 | Manager 会把普通 sub deployment 发送给 Agent Manager | `POST /api/agent/deployments` 或配置的 create path 返回 2xx |
|
||||
| 支持 `sub_mode=agile` | 是 | 普通敏捷模式核心标识 | 不认识时不能按蜂群 task graph 强制处理 |
|
||||
| 支持 `agile_context` | 是 | 用于阶段、检查点、验收标准和下一步动作 | Runtime 能读取并在回调里更新 stage/checkpoint |
|
||||
| 支持阶段状态回传 | 是 | 客户端需要知道当前处在需求/设计/开发/测试/修复/部署哪个环节 | 回调 `phase.changed` 或 `timeline.updated` |
|
||||
@@ -60,13 +60,13 @@ Agent Manager 需要做的是:接收 Manager 生成的 deployment payload,
|
||||
Manager 当前默认 create path:
|
||||
|
||||
```text
|
||||
AGNET_RUNTIME_CREATE_PATH=/api/agnet/deployments
|
||||
AGENT_RUNTIME_CREATE_PATH=/api/agent/deployments
|
||||
```
|
||||
|
||||
如果 Agent Manager 统一使用 `/api/swarms`,Manager 也可以配置切过去。但普通 sub 敏捷模式建议先支持:
|
||||
|
||||
```http
|
||||
POST /api/agnet/deployments
|
||||
POST /api/agent/deployments
|
||||
Authorization: Bearer <service_token>
|
||||
X-User-ID: <manager_user_id>
|
||||
X-Binding-Scope: <binding_scope>
|
||||
@@ -114,7 +114,7 @@ Content-Type: application/json
|
||||
"requires_user_approval": false
|
||||
},
|
||||
"agent_runtime": {
|
||||
"platform": "agnet",
|
||||
"platform": "agent",
|
||||
"agents": [
|
||||
{
|
||||
"role": "backend",
|
||||
@@ -163,8 +163,8 @@ Content-Type: application/json
|
||||
}
|
||||
],
|
||||
"callback": {
|
||||
"url": "https://code.xinghanlab.com/api/agnet/callbacks/swarm-events",
|
||||
"signing_secret_ref": "azkv://heicode-kv.vault.azure.net/secrets/agnet-callback-signing-key",
|
||||
"url": "https://code.xinghanlab.com/api/agent/callbacks/swarm-events",
|
||||
"signing_secret_ref": "azkv://heicode-kv.vault.azure.net/secrets/agent-callback-signing-key",
|
||||
"subscribed_events": [
|
||||
"deployment.status_changed",
|
||||
"phase.changed",
|
||||
@@ -260,7 +260,7 @@ Agent Manager 至少要支持以下阶段语义,并通过 callback 回写当
|
||||
联调前可以先请求 Manager 当前接受的 callback schema:
|
||||
|
||||
```http
|
||||
GET https://code.xinghanlab.com/api/agnet/callbacks/swarm-events/schema
|
||||
GET https://code.xinghanlab.com/api/agent/callbacks/swarm-events/schema
|
||||
```
|
||||
|
||||
该接口只返回事件类型、分类和必填字段,不返回任何 token 或密钥。普通 sub 敏捷重点核对 `phase.changed`、`timeline.updated`、`artifact.created`、`approval.requested`、`sk_tool.*` 和 `budget.alert`。
|
||||
@@ -364,7 +364,7 @@ POST /api/swarms/{swarm_id}/approvals/{approval_id}
|
||||
如果普通 sub 不使用 `swarm_id`,可让 Manager 配置为:
|
||||
|
||||
```text
|
||||
AGNET_RUNTIME_APPROVAL_DECISION_PATH=/api/agnet/deployments/{deployment_id}/approvals/{approval_id}
|
||||
AGENT_RUNTIME_APPROVAL_DECISION_PATH=/api/agent/deployments/{deployment_id}/approvals/{approval_id}
|
||||
```
|
||||
|
||||
Runtime 需要接收:
|
||||
@@ -380,7 +380,7 @@ Runtime 需要接收:
|
||||
"resource_type": "git",
|
||||
"target_role": "backend",
|
||||
"requires_credential": true,
|
||||
"credential_ref": "lease://agnet/lease_xxx",
|
||||
"credential_ref": "lease://agent/lease_xxx",
|
||||
"lease_id": "lease_xxx",
|
||||
"lease_expires_at": 1779850900000
|
||||
}
|
||||
@@ -489,7 +489,7 @@ Agent Manager 回传 usage 时建议至少包含:
|
||||
| 9 | Runtime 回调 completed/failed | Manager detail/timeline 显示终态 |
|
||||
| 10 | Runtime 回调用量 | Manager 能看到 model token/cost 或 usage 摘要,且与 deployment/task/role/correlation 关联 |
|
||||
| 11 | 查日志 | 不出现明文密钥,不丢 `correlation_id` |
|
||||
| 12 | 生产路由复核 | `GET /api/agnet/callbacks/swarm-events/schema` 返回 200,用户态/后台态接口使用有效登录态通过 smoke |
|
||||
| 12 | 生产路由复核 | `GET /api/agent/callbacks/swarm-events/schema` 返回 200,用户态/后台态接口使用有效登录态通过 smoke |
|
||||
|
||||
## 13. Agent Manager 不需要处理的内容
|
||||
|
||||
@@ -509,8 +509,8 @@ Agent Manager 回传 usage 时建议至少包含:
|
||||
| 真实 Runtime 是否完整消费 `agile_context` | Agent Manager | 需要确认字段被使用,不只是透传 |
|
||||
| 阶段状态持续回传 | Agent Manager | 2026-05-31 生产复核已收到 running/completed、phase、timeline、task.completed 等真实回调;后续需在更复杂多阶段任务中继续验证 |
|
||||
| artifact 真实产出 | Agent Manager | 2026-05-31 生产复核已收到业务 `code_patch` artifact,并可通过 Manager content 代理下载;Git/部署 URL 类产物仍按具体任务继续验证 |
|
||||
| approval decision 接收路径 | Agent Manager + Manager 配置 | 需确认最终路径是 `/api/swarms/...` 还是 `/api/agnet/deployments/...` |
|
||||
| approval decision 接收路径 | Agent Manager + Manager 配置 | 需确认最终路径是 `/api/swarms/...` 还是 `/api/agent/deployments/...` |
|
||||
| 用量回传 | Agent Manager + NewAPI/CodeGW | 2026-05-31 生产复核已回传 `tokens_used=2682`、`newapi_request_id=chatcmpl-DlbZce3VZnsv5DptBILFoiRYy5ZHN`;后续需继续按 user/deployment/task/role 归属 |
|
||||
| Agent 运行费用真实结算 | Agent Manager + Manager | Runtime 已能回传 token usage;真实费用结算仍需按 Manager/NewAPI 订阅/钱包规则和 Runtime usage 口径统一 |
|
||||
| 生产 callback schema 路由 | Manager 部署/路由 | 本地代码和测试已覆盖,2026-05-28 生产公开访问 `/api/agnet/callbacks/swarm-events/schema` 返回 404,需要重新上线或核对生产镜像/路由 |
|
||||
| 生产 callback schema 路由 | Manager 部署/路由 | 本地代码和测试已覆盖,2026-05-28 生产公开访问 `/api/agent/callbacks/swarm-events/schema` 返回 404,需要重新上线或核对生产镜像/路由 |
|
||||
| 无 body GET 的 V2 签名 | 客户端 + Manager | 这是客户端全链路无 cookie 的后续项,不阻塞 Agent Manager 创建/回调联调 |
|
||||
|
||||
@@ -14,10 +14,10 @@ Agent Manager / Swarm Runtime 当前还需要补齐或确认的是:真实创
|
||||
|
||||
| 能力 | 状态 | 说明 |
|
||||
|---|---|---|
|
||||
| Runtime 创建桥接 | 已完成 | Manager 可通过 `AGNET_RUNTIME_BASE_URL` + `AGNET_RUNTIME_CREATE_PATH` 调 Agent Manager |
|
||||
| Runtime 停止桥接 | 已完成 | 默认 `POST /api/agnet/deployments/{deployment_id}/stop` |
|
||||
| Runtime 创建桥接 | 已完成 | Manager 可通过 `AGENT_RUNTIME_BASE_URL` + `AGENT_RUNTIME_CREATE_PATH` 调 Agent Manager |
|
||||
| Runtime 停止桥接 | 已完成 | 默认 `POST /api/agent/deployments/{deployment_id}/stop` |
|
||||
| `/api/swarms` adapter | 已完成 | Manager 用户态入口已存在,后续可切真实 Runtime |
|
||||
| callback 接收 | 已完成 | `POST /api/agnet/callbacks/swarm-events` |
|
||||
| callback 接收 | 已完成 | `POST /api/agent/callbacks/swarm-events` |
|
||||
| callback 鉴权 | 已完成 | 支持 service token 或 HMAC |
|
||||
| callback 幂等 | 已完成 | 按 `event_id` / `idempotency_key` 去重 |
|
||||
| task flow 校验 | 已完成 | `task.*` / `handoff.*` 缺关键字段会返回 `CALLBACK_SCHEMA_INVALID` |
|
||||
@@ -58,11 +58,11 @@ Content-Type: application/json
|
||||
Manager 当前可通过环境变量切换:
|
||||
|
||||
```text
|
||||
AGNET_RUNTIME_BASE_URL=http://20.212.121.126
|
||||
AGNET_RUNTIME_CREATE_PATH=/api/swarms
|
||||
AGNET_RUNTIME_HEALTH_PATH=/api/agnet/health
|
||||
AGNET_RUNTIME_STOP_PATH=/api/agnet/deployments/{deployment_id}/stop
|
||||
AGNET_RUNTIME_APPROVAL_DECISION_PATH=/api/swarms/{swarm_id}/approvals/{approval_id}
|
||||
AGENT_RUNTIME_BASE_URL=http://20.212.121.126
|
||||
AGENT_RUNTIME_CREATE_PATH=/api/swarms
|
||||
AGENT_RUNTIME_HEALTH_PATH=/api/agent/health
|
||||
AGENT_RUNTIME_STOP_PATH=/api/agent/deployments/{deployment_id}/stop
|
||||
AGENT_RUNTIME_APPROVAL_DECISION_PATH=/api/swarms/{swarm_id}/approvals/{approval_id}
|
||||
```
|
||||
|
||||
请求体核心形状:
|
||||
@@ -116,8 +116,8 @@ AGNET_RUNTIME_APPROVAL_DECISION_PATH=/api/swarms/{swarm_id}/approvals/{approval_
|
||||
}
|
||||
],
|
||||
"callback": {
|
||||
"url": "https://code.xinghanlab.com/api/agnet/callbacks/swarm-events",
|
||||
"signing_secret_ref": "azkv://heicode-kv.vault.azure.net/secrets/agnet-callback-signing-key",
|
||||
"url": "https://code.xinghanlab.com/api/agent/callbacks/swarm-events",
|
||||
"signing_secret_ref": "azkv://heicode-kv.vault.azure.net/secrets/agent-callback-signing-key",
|
||||
"subscribed_events": [
|
||||
"deployment.status_changed",
|
||||
"task.created",
|
||||
@@ -180,9 +180,9 @@ Manager 解析规则:
|
||||
统一入口:
|
||||
|
||||
```http
|
||||
POST https://code.xinghanlab.com/api/agnet/callbacks/swarm-events
|
||||
X-Agnet-Service-Token: <callback_token>
|
||||
X-Agnet-Event-Id: <event_id>
|
||||
POST https://code.xinghanlab.com/api/agent/callbacks/swarm-events
|
||||
X-Agent-Service-Token: <callback_token>
|
||||
X-Agent-Event-Id: <event_id>
|
||||
X-Correlation-ID: <correlation_id>
|
||||
Content-Type: application/json
|
||||
```
|
||||
@@ -190,8 +190,8 @@ Content-Type: application/json
|
||||
也支持 HMAC:
|
||||
|
||||
```http
|
||||
X-Agnet-Timestamp: <unix_ms>
|
||||
X-Agnet-Signature: sha256=<hex>
|
||||
X-Agent-Timestamp: <unix_ms>
|
||||
X-Agent-Signature: sha256=<hex>
|
||||
```
|
||||
|
||||
签名内容:
|
||||
@@ -203,7 +203,7 @@ timestamp + "." + event_id + "." + raw_body
|
||||
Agent Manager / 蜂群侧可以先拉取 Manager 当前接受的事件 schema:
|
||||
|
||||
```http
|
||||
GET https://code.xinghanlab.com/api/agnet/callbacks/swarm-events/schema
|
||||
GET https://code.xinghanlab.com/api/agent/callbacks/swarm-events/schema
|
||||
```
|
||||
|
||||
该接口不返回任何 token 或密钥,只返回事件类型、分类和必填字段,用于联调前核对 `task.*`、`handoff.*`、`artifact.created`、`approval.requested` 等 callback payload 是否符合 Manager 接收规则。
|
||||
@@ -366,7 +366,7 @@ Content-Type: application/json
|
||||
"risk_level": "high",
|
||||
"requires_credential": true,
|
||||
"user_id": "22",
|
||||
"credential_ref": "lease://agnet/lease_xxx",
|
||||
"credential_ref": "lease://agent/lease_xxx",
|
||||
"lease_id": "lease_xxx",
|
||||
"lease_expires_at": 1779850900000
|
||||
}
|
||||
@@ -379,7 +379,7 @@ Content-Type: application/json
|
||||
默认:
|
||||
|
||||
```http
|
||||
POST /api/agnet/deployments/{runtime_deployment_id}/stop
|
||||
POST /api/agent/deployments/{runtime_deployment_id}/stop
|
||||
Authorization: Bearer <service_token>
|
||||
Content-Type: application/json
|
||||
```
|
||||
@@ -410,7 +410,7 @@ Content-Type: application/json
|
||||
|
||||
| 用例 | 操作 | 期望 |
|
||||
|---|---|---|
|
||||
| health | `GET /api/agnet/health` | 200,`status=healthy` |
|
||||
| health | `GET /api/agent/health` | 200,`status=healthy` |
|
||||
| create | Manager 创建 deployment | Runtime 返回 `deployment_id` 和 `swarm_id` |
|
||||
| task flow | Runtime 回调 `task.created/claimed/heartbeat/blocked/retried/completed` | Manager timeline 和页面 Sub task flow 可见 |
|
||||
| handoff | Runtime 回调 `handoff.requested/completed` | Manager 页面可见 from/to role |
|
||||
@@ -433,7 +433,7 @@ Content-Type: application/json
|
||||
## 12. 联调顺序建议
|
||||
|
||||
1. Agent Manager 确认 health、create、stop、approval decision 路径。
|
||||
2. Manager 配置 `AGNET_RUNTIME_CREATE_PATH=/api/swarms` 后创建一个真实 deployment。
|
||||
2. Manager 配置 `AGENT_RUNTIME_CREATE_PATH=/api/swarms` 后创建一个真实 deployment。
|
||||
3. Agent Manager 回调 `task.created`、`task.claimed`、`task.heartbeat`。
|
||||
4. 回调 `task.blocked`、`handoff.requested`、`handoff.completed`、`task.retried`。
|
||||
5. 回调 `artifact.created`。
|
||||
|
||||
+22
-22
@@ -4,7 +4,7 @@
|
||||
|
||||
## P0:边界收敛
|
||||
|
||||
状态:文档边界已收敛。当前 `docs/` 只保留 `heicode.md`、`plan.md` 和已上线登录接口文档作为实施依据;旧 Agnet API 草案、旧 M1-M5 计划和旧架构说明不再作为开发输入。现有代码中仍可能存在过渡期的 `sk_sources`、Agnet control plane 或部署计划命名,不能反向覆盖本文档边界。
|
||||
状态:文档边界已收敛。当前 `docs/` 只保留 `heicode.md`、`plan.md` 和已上线登录接口文档作为实施依据;旧 Agent API 草案、旧 M1-M5 计划和旧架构说明不再作为开发输入。现有代码中仍可能存在过渡期的 `sk_sources`、Agent control plane 或部署计划命名,不能反向覆盖本文档边界。
|
||||
|
||||
目标:让团队只围绕一套产品和架构边界协作。
|
||||
|
||||
@@ -12,24 +12,24 @@
|
||||
|
||||
- 以 [`heicode.md`](./heicode.md) 作为当前产品与架构共识。
|
||||
- 保留已上线登录接口文档。
|
||||
- 不再维护旧 Agnet API 草案和旧 M1-M5 计划。
|
||||
- 后续所有实现前先确认是否符合 Manager / Agnet / NewAPI / Secret Store 的边界。
|
||||
- 不再维护旧 Agent API 草案和旧 M1-M5 计划。
|
||||
- 后续所有实现前先确认是否符合 Manager / Agent / NewAPI / Secret Store 的边界。
|
||||
- 需求和边界没有想清楚前,不改代码。
|
||||
|
||||
验收:
|
||||
|
||||
- `docs/` 中没有多套互相冲突的 Agnet、NewAPI 或 Manager 计划。
|
||||
- `docs/` 中没有多套互相冲突的 Agent、NewAPI 或 Manager 计划。
|
||||
- 新需求讨论先落到文档共识,再进入实现。
|
||||
|
||||
## P1:Manager 资源模型
|
||||
|
||||
目标:把“Git 来源”升级为面向子 Agnet 的统一资源绑定模型。
|
||||
目标:把“Git 来源”升级为面向子 Agent 的统一资源绑定模型。
|
||||
|
||||
任务:
|
||||
|
||||
- 将当前 Git 来源抽象为资源绑定模型。
|
||||
- 增加资源类型:Git、SK、项目文档、云账号、单项云资源。
|
||||
- 增加 Resource Grant,用于把资源分配给登录用户、绑定 Git/SK/云资源范围、角色和子 Agnet。
|
||||
- 增加 Resource Grant,用于把资源分配给登录用户、绑定 Git/SK/云资源范围、角色和子 Agent。
|
||||
- 定义资源元数据、权限范围、约束、状态和审计字段。
|
||||
- 前端从单点功能页逐步走向“绑定资源 -> 分配角色 -> 部署确认”的主流程。
|
||||
|
||||
@@ -39,13 +39,13 @@
|
||||
|
||||
1. 后端先落库资源绑定和 Resource Grant 两类记录,不在本阶段实现 Secret Broker 的真实写入。
|
||||
2. Resource Binding 表达登录用户绑定的资源元数据:资源类型、名称、外部标识、可见元数据、权限范围、约束、状态、`secret_ref` 和审计字段。
|
||||
3. Resource Grant 表达授权关系:user、resource、binding scope、role、子 Agnet 标识、允许动作、限制条件、状态、过期时间和审计字段。
|
||||
3. Resource Grant 表达授权关系:user、resource、binding scope、role、子 Agent 标识、允许动作、限制条件、状态、过期时间和审计字段。
|
||||
4. 提供只返回元数据和 `secret_ref` 的列表、详情、创建、授权、撤销接口;任何接口响应、日志和 Markdown 产物都不得包含真实密钥。
|
||||
5. 生成一份 permission manifest 示例,用结构化数据证明“某登录用户把某个绑定资源授予某个子 Agnet 角色使用”。
|
||||
5. 生成一份 permission manifest 示例,用结构化数据证明“某登录用户把某个绑定资源授予某个子 Agent 角色使用”。
|
||||
|
||||
验收:
|
||||
|
||||
- Manager 能表达“某登录用户把某个绑定资源授予某个子 Agnet 角色使用”。
|
||||
- Manager 能表达“某登录用户把某个绑定资源授予某个子 Agent 角色使用”。
|
||||
- 数据库不保存明文密钥,只保存 `secret_ref`。
|
||||
- P1 测试样例能覆盖 Git、SK、项目文档、云账号和单项云资源五类资源的元数据建模。
|
||||
- 撤销 Resource Grant 后,对应 permission manifest 不再包含该授权。
|
||||
@@ -68,23 +68,23 @@
|
||||
- Git token、云密钥、SSH key、数据库密码不会进入 Git、Markdown、前端响应或普通日志。
|
||||
- 用户可以授权和撤销资源,平台负责实际凭证托管。
|
||||
|
||||
## P3:Agnet 平台 AKS 身份接入
|
||||
## P3:Agent 平台 AKS 身份接入
|
||||
|
||||
目标:让子 Agnet 在 AKS 上按最小权限访问被授权资源。
|
||||
目标:让子 Agent 在 AKS 上按最小权限访问被授权资源。
|
||||
|
||||
任务:
|
||||
|
||||
- Agnet 平台支持 deployment / role 到 Kubernetes ServiceAccount 的映射。
|
||||
- Agent 平台支持 deployment / role 到 Kubernetes ServiceAccount 的映射。
|
||||
- 支持 Vault Kubernetes Auth 或等价 Workload Identity。
|
||||
- 支持按 user / resource binding / role 生成密钥访问策略。
|
||||
- 子 Agnet 运行时只能访问被授权的 secret。
|
||||
- 子 Agent 运行时只能访问被授权的 secret。
|
||||
- 普通开发资源支持受控注入。
|
||||
- 高危操作审批只在客户端完成;审批通过后允许向子 Agnet 注入密钥保管器派生的短期、最小权限凭证。
|
||||
- 高危操作审批只在客户端完成;审批通过后允许向子 Agent 注入密钥保管器派生的短期、最小权限凭证。
|
||||
|
||||
验收:
|
||||
|
||||
- 子 Agnet 不保存长期密钥。
|
||||
- 撤销 Resource Grant 后,子 Agnet 无法继续访问对应资源。
|
||||
- 子 Agent 不保存长期密钥。
|
||||
- 撤销 Resource Grant 后,子 Agent 无法继续访问对应资源。
|
||||
- 高危资源访问有审计记录。
|
||||
|
||||
## P4:NewAPI 解耦
|
||||
@@ -98,8 +98,8 @@
|
||||
- Manager 通过服务凭据调用 NewAPI。
|
||||
- Manager 展示普通用户需要的模型、余额、额度、调用日志。
|
||||
- 隐藏渠道管理、价格配置、模型供应商后台配置和 NewAPI 管理员能力。
|
||||
- 建立 Heicode/Agnet 登录用户 `user.id`、`channelId` 与 NewAPI user / token / group / quota / usage 的映射。
|
||||
- 子 Agnet 的运行模型、模型 profile 和实例数归 Agnet 平台部署配置管理,不和 NewAPI 扣费映射混用。
|
||||
- 建立 Heicode/Agent 登录用户 `user.id`、`channelId` 与 NewAPI user / token / group / quota / usage 的映射。
|
||||
- 子 Agent 的运行模型、模型 profile 和实例数归 Agent 平台部署配置管理,不和 NewAPI 扣费映射混用。
|
||||
|
||||
验收:
|
||||
|
||||
@@ -109,18 +109,18 @@
|
||||
|
||||
## P5:部署和审计闭环
|
||||
|
||||
目标:跑通从用户想法到子 Agnet 部署、执行、观测和审计的闭环。
|
||||
目标:跑通从用户想法到子 Agent 部署、执行、观测和审计的闭环。
|
||||
|
||||
任务:
|
||||
|
||||
- Manager 生成 AGENT.md、resource context 和 permission manifest。
|
||||
- Agnet 平台部署子 Agnet 后回传 deployment、agent instance、状态和事件。
|
||||
- Agent 平台部署子 Agent 后回传 deployment、agent instance、状态和事件。
|
||||
- Manager 展示活动状态、失败原因、资源使用记录、模型调用记录和审计日志。
|
||||
- 对高危权限增加审批、撤销和运行中失效机制。
|
||||
- 为每次部署保留可追溯的资源、权限、模型和上下文快照。
|
||||
|
||||
验收:
|
||||
|
||||
- 用户能看到每个子 Agnet 的角色、模型、资源权限、运行状态和失败原因。
|
||||
- 审计能回答谁在什么时候让哪个子 Agnet 使用了什么资源。
|
||||
- 用户能看到每个子 Agent 的角色、模型、资源权限、运行状态和失败原因。
|
||||
- 审计能回答谁在什么时候让哪个子 Agent 使用了什么资源。
|
||||
- Markdown 只作为上下文,permission manifest 才是系统执行依据。
|
||||
|
||||
@@ -8,7 +8,7 @@ Heicode
|
||||
|
||||
Heicode 是一款覆盖软件生命周期的智能开发 Code 工具。它面向的是“从想法到上线”的完整开发过程,而不是单个模型聊天窗口、CodeGW 管理后台或简单的 Agent 控制台。
|
||||
|
||||
用户只需要注册登录,输入想法和约束,Heicode 就帮助用户组织子 Agnet 团队,接入用户授权的 Git、SK、项目文档和云资源,完成产品设计、代码开发、代码检查、部署、观测和后续维护。
|
||||
用户只需要注册登录,输入想法和约束,Heicode 就帮助用户组织子 Agent 团队,接入用户授权的 Git、SK、项目文档和云资源,完成产品设计、代码开发、代码检查、部署、观测和后续维护。
|
||||
|
||||
## 目标用户
|
||||
|
||||
@@ -23,11 +23,11 @@ Heicode 是一款覆盖软件生命周期的智能开发 Code 工具。它面向
|
||||
|
||||
1. 把自然语言想法变成可执行的软件开发任务。
|
||||
2. 把 Git、文档、SK、云资源变成可授权、可撤销、可审计的资源。
|
||||
3. 把子 Agnet 的角色、权限、模型和上下文组织成可运行的开发团队。
|
||||
4. 把持续开发拆成需求、设计、开发、测试、修复、部署等子环节,由 Agnet 持续推进。
|
||||
5. 让 Agnet 在执行过程中可以调用项目 SK 工具和外部能力,而不是只做一次性任务分发。
|
||||
3. 把子 Agent 的角色、权限、模型和上下文组织成可运行的开发团队。
|
||||
4. 把持续开发拆成需求、设计、开发、测试、修复、部署等子环节,由 Agent 持续推进。
|
||||
5. 让 Agent 在执行过程中可以调用项目 SK 工具和外部能力,而不是只做一次性任务分发。
|
||||
6. 把模型调用、余额、额度和日志统一展示给用户。
|
||||
7. 把长期密钥放入密钥保管器,只给子 Agnet 短期、最小权限凭证。
|
||||
7. 把长期密钥放入密钥保管器,只给子 Agent 短期、最小权限凭证。
|
||||
8. 把开发、检查、部署、维护纳入同一个生命周期闭环。
|
||||
|
||||
## 产品形态
|
||||
@@ -35,12 +35,12 @@ Heicode 是一款覆盖软件生命周期的智能开发 Code 工具。它面向
|
||||
| 产品面 | 说明 |
|
||||
|--------|------|
|
||||
| Heicode 客户端 | 用户主体验,负责本地对话、输入想法、继续开发、查看执行反馈、接收交付结果和高危审批,只登录 Heicode,只使用 Heicode 提供的模型 |
|
||||
| Heicode Manager / 浏览器控制台 | 辅助控制台,负责账号与安全、客户端下载、Git/云资源绑定、Agnet 部署、任务状态总览、模型余额与用量、审计与日志查看 |
|
||||
| Agnet 平台 | 执行层,负责部署和运行子 Agnet,在任务推进过程中完成需求、开发、测试、修复、交付与部署,并回传日志、状态、事件和指标 |
|
||||
| Heicode Manager / 浏览器控制台 | 辅助控制台,负责账号与安全、客户端下载、Git/云资源绑定、Agent 部署、任务状态总览、模型余额与用量、审计与日志查看 |
|
||||
| Agent 平台 | 执行层,负责部署和运行子 Agent,在任务推进过程中完成需求、开发、测试、修复、交付与部署,并回传日志、状态、事件和指标 |
|
||||
| CodeGW | 内部模型网关与计费服务,普通用户不直接进入后台 |
|
||||
| 密钥保管器 | Azure Key Vault 实现,保存长期凭证引用,按审批和权限提供短期访问能力 |
|
||||
|
||||
说明:Heicode 客户端是用户主体验,用户不在网页上编码。Heicode Manager 是浏览器里的辅助控制台,承担资源、部署、状态、余额、审计和下载等辅助操作。真正持续推进任务的是 Heicode 调度下的 Agnet 执行闭环,Agnet 在过程中还可以调用已授权的 SK 工具和外部能力,最终完成交付与部署并把结果回传到客户端。
|
||||
说明:Heicode 客户端是用户主体验,用户不在网页上编码。Heicode Manager 是浏览器里的辅助控制台,承担资源、部署、状态、余额、审计和下载等辅助操作。真正持续推进任务的是 Heicode 调度下的 Agent 执行闭环,Agent 在过程中还可以调用已授权的 SK 工具和外部能力,最终完成交付与部署并把结果回传到客户端。
|
||||
|
||||
## 核心流程
|
||||
|
||||
@@ -49,12 +49,12 @@ Heicode 是一款覆盖软件生命周期的智能开发 Code 工具。它面向
|
||||
-> 输入产品想法
|
||||
-> 生成产品文档和任务计划
|
||||
-> 在 Manager 绑定 Git 和云资源
|
||||
-> 在 Manager 部署 Agnet
|
||||
-> 在 Manager 部署 Agent
|
||||
-> 客户端继续追加需求和修正方向
|
||||
-> Agnet 按子环节执行需求、开发、测试、修复
|
||||
-> Agnet 按需要调用已授权 SK 工具
|
||||
-> Agent 按子环节执行需求、开发、测试、修复
|
||||
-> Agent 按需要调用已授权 SK 工具
|
||||
-> 客户端确认高危操作
|
||||
-> Agnet 完成交付物整理和部署
|
||||
-> Agent 完成交付物整理和部署
|
||||
-> Manager 查看状态、余额、审计和下载
|
||||
-> 客户端展示执行反馈和交付结果
|
||||
-> 持续维护和升级
|
||||
@@ -72,11 +72,11 @@ Heicode 是一款覆盖软件生命周期的智能开发 Code 工具。它面向
|
||||
## 产品承诺
|
||||
|
||||
1. 用户不需要理解底层模型供应商。
|
||||
2. 用户不需要把密钥交给子 Agnet 长期保存。
|
||||
2. 用户不需要把密钥交给子 Agent 长期保存。
|
||||
3. 用户可以清楚知道哪些资源被哪个角色使用。
|
||||
4. 高危操作必须经过客户端审批。
|
||||
5. 所有关键动作都能审计。
|
||||
|
||||
## 当前一句话卖点
|
||||
|
||||
> 从一个想法开始,让 Heicode 组织 AI 开发团队,持续调用 Agnet 完成开发、测试、交付与部署,安全接入你的代码和云资源,把产品推进到生产环境。
|
||||
> 从一个想法开始,让 Heicode 组织 AI 开发团队,持续调用 Agent 完成开发、测试、交付与部署,安全接入你的代码和云资源,把产品推进到生产环境。
|
||||
|
||||
@@ -40,7 +40,7 @@
|
||||
首屏辅助信息:
|
||||
|
||||
- 绑定 Git 与云资源
|
||||
- 生成子 Agnet 团队
|
||||
- 生成子 Agent 团队
|
||||
- 密钥保管器托管凭证
|
||||
- CodeGW 统一模型与用量
|
||||
|
||||
@@ -91,7 +91,7 @@
|
||||
| 想法转任务 | 把自然语言目标转成产品文档、任务和角色 |
|
||||
| 资源绑定 | 接入 Git、SK、项目文档、云账号和云资源 |
|
||||
| 权限控制 | 按角色分配 Resource Grant 和审批规则 |
|
||||
| 执行闭环 | 子 Agnet 持续推进开发、测试、修复和部署,可调用 SK 工具,Heicode 展示日志和审计 |
|
||||
| 执行闭环 | 子 Agent 持续推进开发、测试、修复和部署,可调用 SK 工具,Heicode 展示日志和审计 |
|
||||
|
||||
### 4. 工作流区
|
||||
|
||||
@@ -120,7 +120,7 @@
|
||||
|
||||
- 长期密钥进入密钥保管器。
|
||||
- Heicode 服务端数据库只保存 `secret_ref`。
|
||||
- 子 Agnet 只拿短期凭证。
|
||||
- 子 Agent 只拿短期凭证。
|
||||
- 高危操作在客户端审批。
|
||||
- 日志、Markdown、前端响应不显示明文密钥。
|
||||
|
||||
@@ -134,14 +134,14 @@
|
||||
|------|------|
|
||||
| Heicode Manager | 资源、部署、状态、用量和审计辅助控制台 |
|
||||
| 客户端 | 主对话体验、任务推进和高危审批 |
|
||||
| Agnet 平台 | AKS 执行层 |
|
||||
| Agent 平台 | AKS 执行层 |
|
||||
| CodeGW | 模型网关与用量 |
|
||||
| Azure Key Vault | 密钥保管器 |
|
||||
|
||||
补充说明:
|
||||
|
||||
- 用户主要在客户端对话和推进任务。
|
||||
- Manager 主要承担资源准备、Agnet 部署、用量和审计辅助。
|
||||
- Manager 主要承担资源准备、Agent 部署、用量和审计辅助。
|
||||
|
||||
### 7. 适用场景区
|
||||
|
||||
|
||||
@@ -21,13 +21,13 @@
|
||||
| 1. 登录 | 注册或登录 Heicode | 获取用户身份和 channelId | 用户会话 |
|
||||
| 2. 输入想法 | 描述要做的产品或任务 | 生成需求摘要和任务草案 | 需求草案 |
|
||||
| 3. 绑定资源 | 授权 Git、文档、SK、云资源 | 保存资源元数据和 `secret_ref` | Resource Binding |
|
||||
| 4. 生成团队 | 确认开发方法和角色 | 推荐子 Agnet 角色 | 角色方案 |
|
||||
| 4. 生成团队 | 确认开发方法和角色 | 推荐子 Agent 角色 | 角色方案 |
|
||||
| 5. 分配权限 | 确认每个角色能用什么 | 生成 Resource Grant | 权限清单 |
|
||||
| 6. 审批高危操作 | 在客户端确认高危动作 | 记录 approval | 审批记录 |
|
||||
| 7. 执行任务 | 持续追加需求、查看进度 | Agnet 平台运行子 Agnet,按子环节推进开发 | 状态、日志、事件 |
|
||||
| 8. SK 工具调用 | 允许平台使用技能能力 | Agnet 按权限调用 SK 工具和外部能力 | 中间产物、检查结果 |
|
||||
| 9. 交付上线 | 确认部署结果 | Agnet 完成交付整理、部署并回写审计 | 生产服务 |
|
||||
| 10. 维护升级 | 提出迭代或修复 | 复用上下文和权限再次进入 Agnet 闭环 | 新版本计划 |
|
||||
| 7. 执行任务 | 持续追加需求、查看进度 | Agent 平台运行子 Agent,按子环节推进开发 | 状态、日志、事件 |
|
||||
| 8. SK 工具调用 | 允许平台使用技能能力 | Agent 按权限调用 SK 工具和外部能力 | 中间产物、检查结果 |
|
||||
| 9. 交付上线 | 确认部署结果 | Agent 完成交付整理、部署并回写审计 | 生产服务 |
|
||||
| 10. 维护升级 | 提出迭代或修复 | 复用上下文和权限再次进入 Agent 闭环 | 新版本计划 |
|
||||
|
||||
## 关键任务状态流程
|
||||
|
||||
@@ -63,7 +63,7 @@
|
||||
- 产品文档草案。
|
||||
- 原型描述。
|
||||
- 推荐资源需求。
|
||||
- 推荐子 Agnet 角色。
|
||||
- 推荐子 Agent 角色。
|
||||
|
||||
### 3. 任务上下文准备
|
||||
|
||||
@@ -84,7 +84,7 @@ Heicode 判断本任务缺少哪些上下文
|
||||
默认展示角色视角:
|
||||
|
||||
```text
|
||||
Backend Agnet
|
||||
Backend Agent
|
||||
- 可读写:后端代码路径
|
||||
- 可读:项目文档
|
||||
- 可部署:测试环境
|
||||
@@ -98,20 +98,20 @@ Backend Agnet
|
||||
展示:
|
||||
|
||||
- 本次目标。
|
||||
- 子 Agnet 数量和角色。
|
||||
- 子 Agent 数量和角色。
|
||||
- 每个角色使用的资源。
|
||||
- 是否会访问密钥。
|
||||
- 是否会部署云资源。
|
||||
- 预计模型预算。
|
||||
- 审批项。
|
||||
|
||||
用户确认后,Heicode 生成 Agnet 平台 payload。
|
||||
用户确认后,Heicode 生成 Agent 平台 payload。
|
||||
|
||||
### 6. 执行中的任务空间
|
||||
|
||||
展示:
|
||||
|
||||
- 子 Agnet 活跃状态。
|
||||
- 子 Agent 活跃状态。
|
||||
- 当前步骤。
|
||||
- 最近日志。
|
||||
- 失败原因。
|
||||
@@ -127,21 +127,21 @@ Backend Agnet
|
||||
- 撤销资源授权。
|
||||
- 发起修复或继续迭代。
|
||||
|
||||
### 7. Agnet 执行闭环
|
||||
### 7. Agent 执行闭环
|
||||
|
||||
Heicode 不是只把任务丢给 Agnet 一次就结束,而是会在开发过程中持续调用 Agnet 完成子环节。
|
||||
Heicode 不是只把任务丢给 Agent 一次就结束,而是会在开发过程中持续调用 Agent 完成子环节。
|
||||
|
||||
闭环应表达为:
|
||||
|
||||
```text
|
||||
客户端输入目标或追加需求
|
||||
-> Heicode 生成下一步任务
|
||||
-> Agnet 执行需求/设计/开发/测试/修复中的当前子环节
|
||||
-> Agnet 按需要调用已授权的 SK 工具
|
||||
-> Agent 执行需求/设计/开发/测试/修复中的当前子环节
|
||||
-> Agent 按需要调用已授权的 SK 工具
|
||||
-> Heicode 回传中间结果给客户端
|
||||
-> 用户继续追问、修正或审批
|
||||
-> Agnet 继续下一子环节
|
||||
-> 最终由 Agnet 完成交付整理与部署
|
||||
-> Agent 继续下一子环节
|
||||
-> 最终由 Agent 完成交付整理与部署
|
||||
```
|
||||
|
||||
这意味着用户看到的不是一次性“已部署 Agent”,而是一个可连续推进的开发循环。
|
||||
@@ -177,8 +177,8 @@ MVP 最小路径:
|
||||
6. 选择推荐角色。
|
||||
7. 确认资源权限。
|
||||
8. 预览 manifest。
|
||||
9. 创建 Agnet 部署占位任务。
|
||||
9. 创建 Agent 部署占位任务。
|
||||
10. 在客户端持续推进子环节开发与测试。
|
||||
11. 查看任务状态、日志、审计和交付结果。
|
||||
|
||||
当前唯一可延期项:Heicode 到 Agnet 平台真实部署 API 的完整联调。
|
||||
当前唯一可延期项:Heicode 到 Agent 平台真实部署 API 的完整联调。
|
||||
|
||||
@@ -47,7 +47,7 @@ macOS 提示说明:
|
||||
|
||||
## 第三步:在客户端输入想法
|
||||
|
||||
主要在 Heicode 客户端输入要做的事情。Manager 负责下载、资源绑定、Agnet 部署、状态和审计等辅助能力,不承担主开发对话。
|
||||
主要在 Heicode 客户端输入要做的事情。Manager 负责下载、资源绑定、Agent 部署、状态和审计等辅助能力,不承担主开发对话。
|
||||
|
||||
例如:
|
||||
|
||||
@@ -93,12 +93,12 @@ macOS 提示说明:
|
||||
|
||||
用户可以使用平台推荐角色:
|
||||
|
||||
- Product Agnet。
|
||||
- Architect Agnet。
|
||||
- Frontend Agnet。
|
||||
- Backend Agnet。
|
||||
- Reviewer Agnet。
|
||||
- Ops Agnet。
|
||||
- Product Agent。
|
||||
- Architect Agent。
|
||||
- Frontend Agent。
|
||||
- Backend Agent。
|
||||
- Reviewer Agent。
|
||||
- Ops Agent。
|
||||
|
||||
每个角色确认:
|
||||
|
||||
@@ -120,20 +120,20 @@ macOS 提示说明:
|
||||
- 访问生产密钥。
|
||||
- 大额模型预算消耗。
|
||||
|
||||
用户确认后,平台才会为子 Agnet 派生短期凭证。
|
||||
用户确认后,平台才会为子 Agent 派生短期凭证。
|
||||
|
||||
## 第七步:启动任务
|
||||
|
||||
用户确认任务后,Heicode 会生成:
|
||||
|
||||
- 子 Agnet 角色。
|
||||
- 子 Agent 角色。
|
||||
- AGENT.md。
|
||||
- resource context。
|
||||
- permission manifest。
|
||||
- 模型和预算配置。
|
||||
- 审批策略。
|
||||
|
||||
Heicode 将这些内容提交给 Agnet 平台执行。
|
||||
Heicode 将这些内容提交给 Agent 平台执行。
|
||||
|
||||
## 第八步:在客户端持续推进
|
||||
|
||||
@@ -144,8 +144,8 @@ Heicode 将这些内容提交给 Agnet 平台执行。
|
||||
```text
|
||||
客户端继续补充要求
|
||||
-> Heicode 生成下一步子任务
|
||||
-> Agnet 执行需求/开发/测试/修复/部署中的当前环节
|
||||
-> Agnet 按权限调用需要的 SK 工具
|
||||
-> Agent 执行需求/开发/测试/修复/部署中的当前环节
|
||||
-> Agent 按权限调用需要的 SK 工具
|
||||
-> 中间结果和失败原因回到客户端
|
||||
-> 用户继续修正或审批
|
||||
-> 最终返回交付物和部署结果
|
||||
@@ -156,7 +156,7 @@ Heicode 将这些内容提交给 Agnet 平台执行。
|
||||
用户可以在 Heicode 查看:
|
||||
|
||||
- 当前任务状态。
|
||||
- 子 Agnet 活动状态。
|
||||
- 子 Agent 活动状态。
|
||||
- 运行日志。
|
||||
- 失败原因。
|
||||
- 模型用量。
|
||||
|
||||
@@ -39,8 +39,8 @@ Heicode 是一款全流程智能开发 Code 工具。它让用户输入想法,
|
||||
- 用户输入自然语言目标。
|
||||
- Heicode 生成任务、角色、权限和执行上下文。
|
||||
- 客户端是主体验,Manager 负责资源、部署和审计辅助。
|
||||
- 子 Agnet 按角色推进开发、测试、修复和部署,并可调用 SK 工具。
|
||||
- 密钥进入密钥保管器,子 Agnet 只拿短期凭证。
|
||||
- 子 Agent 按角色推进开发、测试、修复和部署,并可调用 SK 工具。
|
||||
- 密钥进入密钥保管器,子 Agent 只拿短期凭证。
|
||||
- Heicode 展示模型用量、日志、事件和审计。
|
||||
|
||||
## Slide 4:产品工作流
|
||||
@@ -55,11 +55,11 @@ Heicode 是一款全流程智能开发 Code 工具。它让用户输入想法,
|
||||
登录
|
||||
-> 客户端输入想法
|
||||
-> Manager 绑定资源
|
||||
-> Manager 部署 Agnet
|
||||
-> Manager 部署 Agent
|
||||
-> 客户端继续推进任务
|
||||
-> Agnet 执行开发/测试/修复并调用 SK
|
||||
-> Agent 执行开发/测试/修复并调用 SK
|
||||
-> 审批高危操作
|
||||
-> Agnet 交付与部署
|
||||
-> Agent 交付与部署
|
||||
-> 观测与维护
|
||||
```
|
||||
|
||||
@@ -75,7 +75,7 @@ Heicode 是一款全流程智能开发 Code 工具。它让用户输入想法,
|
||||
|
||||
1. 想法转开发任务。
|
||||
2. 资源绑定和权限分配。
|
||||
3. 子 Agnet 编排和执行。
|
||||
3. 子 Agent 编排和执行。
|
||||
4. 用量、日志、安全和审计闭环。
|
||||
|
||||
## Slide 6:系统架构
|
||||
@@ -89,7 +89,7 @@ Heicode 是一款全流程智能开发 Code 工具。它让用户输入想法,
|
||||
```text
|
||||
用户 / 客户端
|
||||
-> Heicode Manager
|
||||
-> Agnet 平台
|
||||
-> Agent 平台
|
||||
-> CodeGW
|
||||
-> Azure Key Vault
|
||||
-> Git / SK / 云资源
|
||||
@@ -97,7 +97,7 @@ Heicode 是一款全流程智能开发 Code 工具。它让用户输入想法,
|
||||
|
||||
讲述:
|
||||
|
||||
客户端是主体验,Manager 是辅助控制台。Agnet 平台是执行层,执行过程中可调用 SK。CodeGW 是模型网关和计费服务。Azure Key Vault 是密钥保管器。
|
||||
客户端是主体验,Manager 是辅助控制台。Agent 平台是执行层,执行过程中可调用 SK。CodeGW 是模型网关和计费服务。Azure Key Vault 是密钥保管器。
|
||||
|
||||
## Slide 7:安全模型
|
||||
|
||||
@@ -109,7 +109,7 @@ Heicode 是一款全流程智能开发 Code 工具。它让用户输入想法,
|
||||
|
||||
- Heicode 服务端数据库只保存 `secret_ref`。
|
||||
- 长期密钥进入 Azure Key Vault。
|
||||
- 子 Agnet 只获得短期、最小权限凭证。
|
||||
- 子 Agent 只获得短期、最小权限凭证。
|
||||
- 高危操作必须在客户端审批。
|
||||
- 日志、Markdown、前端响应不包含明文密钥。
|
||||
|
||||
@@ -147,7 +147,7 @@ Heicode 是一款全流程智能开发 Code 工具。它让用户输入想法,
|
||||
|
||||
延期项:
|
||||
|
||||
- Heicode 到 Agnet 平台真实部署 API 的完整联调。
|
||||
- Heicode 到 Agent 平台真实部署 API 的完整联调。
|
||||
|
||||
## Slide 10:路线图
|
||||
|
||||
@@ -160,7 +160,7 @@ Heicode 是一款全流程智能开发 Code 工具。它让用户输入想法,
|
||||
1. 资源绑定和密钥保管。
|
||||
2. CodeGW 用户侧模型和用量展示。
|
||||
3. 客户端登录、审批和下载闭环。
|
||||
4. Agnet 平台真实部署联调。
|
||||
4. Agent 平台真实部署联调。
|
||||
5. 自动维护、升级、成本和质量观测。
|
||||
|
||||
## Slide 11:演示故事
|
||||
@@ -174,9 +174,9 @@ Heicode 是一款全流程智能开发 Code 工具。它让用户输入想法,
|
||||
1. 登录 Heicode。
|
||||
2. 客户端输入产品想法。
|
||||
3. Manager 绑定 Git 和云资源。
|
||||
4. Manager 部署 Agnet 并展示权限。
|
||||
4. Manager 部署 Agent 并展示权限。
|
||||
5. 客户端继续推进任务。
|
||||
6. 展示 Agnet 调用 SK 和当前子环节。
|
||||
6. 展示 Agent 调用 SK 和当前子环节。
|
||||
7. 客户端审批高危操作。
|
||||
8. 查看交付、部署结果、日志和模型用量。
|
||||
|
||||
|
||||
@@ -2,14 +2,14 @@
|
||||
|
||||
## 核心原则
|
||||
|
||||
Heicode 是 SaaS 产品,不能把凭证管理转嫁给用户,也不能让密钥散落在 Git、Markdown、日志或子 Agnet 长期状态中。
|
||||
Heicode 是 SaaS 产品,不能把凭证管理转嫁给用户,也不能让密钥散落在 Git、Markdown、日志或子 Agent 长期状态中。
|
||||
|
||||
安全目标:
|
||||
|
||||
1. 用户授权资源,平台托管凭证。
|
||||
2. Heicode 服务端数据库只保存元数据和 `secret_ref`。
|
||||
3. 长期密钥进入密钥保管器。
|
||||
4. 子 Agnet 只拿短期、最小权限、可审计凭证。
|
||||
4. 子 Agent 只拿短期、最小权限、可审计凭证。
|
||||
5. 高危操作必须由客户端审批。
|
||||
|
||||
## 凭证分类
|
||||
@@ -43,17 +43,17 @@ Heicode 是 SaaS 产品,不能把凭证管理转嫁给用户,也不能让密
|
||||
|
||||
Heicode 接口不得返回明文密钥。日志、错误信息、审计摘要也不得包含明文密钥。
|
||||
|
||||
## 子 Agnet 访问凭证
|
||||
## 子 Agent 访问凭证
|
||||
|
||||
子 Agnet 不保存长期密钥。
|
||||
子 Agent 不保存长期密钥。
|
||||
|
||||
推荐流程:
|
||||
|
||||
```text
|
||||
Heicode 生成 Resource Grant
|
||||
-> Agnet 平台部署子 Agnet
|
||||
-> Agnet 平台绑定运行时身份
|
||||
-> 子 Agnet 请求使用资源
|
||||
-> Agent 平台部署子 Agent
|
||||
-> Agent 平台绑定运行时身份
|
||||
-> 子 Agent 请求使用资源
|
||||
-> 高危操作先走客户端审批
|
||||
-> 审批通过后派生短期凭证
|
||||
-> 短期凭证注入运行时
|
||||
@@ -86,7 +86,7 @@ Heicode 生成 Resource Grant
|
||||
2. 禁止把密钥写入 Markdown。
|
||||
3. 禁止前端返回明文密钥。
|
||||
4. 禁止在日志中打印密钥。
|
||||
5. 禁止把长期云密钥注入子 Agnet。
|
||||
5. 禁止把长期云密钥注入子 Agent。
|
||||
6. 禁止把 Azure Key Vault 管理入口作为普通用户入口暴露。
|
||||
7. 禁止把 CodeGW key 原文交给客户端长期保存。
|
||||
|
||||
@@ -94,7 +94,7 @@ Heicode 生成 Resource Grant
|
||||
|
||||
推荐文案:
|
||||
|
||||
> Heicode 会把你的授权凭证存入密钥保管器。平台只在你授权的范围内,为对应任务和角色生成短期访问能力。密钥不会写入 Git、文档、日志或子 Agnet 长期状态。
|
||||
> Heicode 会把你的授权凭证存入密钥保管器。平台只在你授权的范围内,为对应任务和角色生成短期访问能力。密钥不会写入 Git、文档、日志或子 Agent 长期状态。
|
||||
|
||||
## 验收标准
|
||||
|
||||
@@ -103,6 +103,6 @@ Heicode 生成 Resource Grant
|
||||
| 数据库 | 只保存 `secret_ref`,不保存明文密钥 |
|
||||
| 前端 | 不显示明文密钥 |
|
||||
| 日志 | 不包含 token、password、private key、access key |
|
||||
| 子 Agnet | 不保存长期凭证 |
|
||||
| 子 Agent | 不保存长期凭证 |
|
||||
| 高危操作 | 有客户端审批记录 |
|
||||
| Azure Key Vault | 普通用户不可直接进入;Manager 仅通过受控后端身份访问 |
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
# 07. CodeGW、Azure Key Vault、Agnet 平台边界说明
|
||||
# 07. CodeGW、Azure Key Vault、Agent 平台边界说明
|
||||
|
||||
## 目标
|
||||
|
||||
本文用于讲清楚 Heicode 与 CodeGW、Azure Key Vault、Agnet 平台之间的产品和技术边界,避免再次变成缝合式系统。
|
||||
本文用于讲清楚 Heicode 与 CodeGW、Azure Key Vault、Agent 平台之间的产品和技术边界,避免再次变成缝合式系统。
|
||||
|
||||
## 总体边界
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
| Heicode | 产品总入口,包含客户端主体验与 Manager 辅助控制台 | 是 |
|
||||
| CodeGW | 模型网关、模型调用、余额、额度、用量日志 | 否 |
|
||||
| Azure Key Vault | 长期凭证托管、密钥引用、后端受控读取 | 否 |
|
||||
| Agnet 平台 | AKS 执行层、状态、日志、事件、指标 | 否 |
|
||||
| Agent 平台 | AKS 执行层、状态、日志、事件、指标 | 否 |
|
||||
|
||||
## Heicode 与 CodeGW
|
||||
|
||||
@@ -51,15 +51,15 @@ Heicode 不做:
|
||||
|
||||
产品口径:
|
||||
|
||||
> 密钥保管器负责保存凭证,Heicode 只保存引用。子 Agnet 使用资源时,平台按权限和审批结果获取短期访问能力。
|
||||
> 密钥保管器负责保存凭证,Heicode 只保存引用。子 Agent 使用资源时,平台按权限和审批结果获取短期访问能力。
|
||||
|
||||
## Heicode 与 Agnet 平台
|
||||
## Heicode 与 Agent 平台
|
||||
|
||||
Heicode 传给 Agnet 平台:
|
||||
Heicode 传给 Agent 平台:
|
||||
|
||||
- 用户上下文。
|
||||
- 任务目标。
|
||||
- 子 Agnet 角色。
|
||||
- 子 Agent 角色。
|
||||
- AGENT.md。
|
||||
- resource context。
|
||||
- permission manifest。
|
||||
@@ -69,10 +69,10 @@ Heicode 传给 Agnet 平台:
|
||||
- `sk_context` / `tool_permissions`。
|
||||
- 回调地址。
|
||||
|
||||
Agnet 平台回传 Heicode:
|
||||
Agent 平台回传 Heicode:
|
||||
|
||||
- deployment 状态。
|
||||
- 子 Agnet 状态。
|
||||
- 子 Agent 状态。
|
||||
- 事件。
|
||||
- 日志。
|
||||
- 指标。
|
||||
@@ -91,7 +91,7 @@ Heicode 不传:
|
||||
|
||||
产品口径:
|
||||
|
||||
> Heicode 决定用户想做什么、能用什么资源和谁来做;Agnet 平台负责把这些任务安全运行起来,在过程中按权限调用需要的 SK 工具,并把子环节状态、交付物和部署结果回传给 Heicode。
|
||||
> Heicode 决定用户想做什么、能用什么资源和谁来做;Agent 平台负责把这些任务安全运行起来,在过程中按权限调用需要的 SK 工具,并把子环节状态、交付物和部署结果回传给 Heicode。
|
||||
|
||||
## 客户端与 Manager 的边界
|
||||
|
||||
@@ -109,7 +109,7 @@ Manager 主做:
|
||||
- 账号与安全。
|
||||
- 客户端下载。
|
||||
- Git、文档、SK、云资源绑定。
|
||||
- Agnet 部署。
|
||||
- Agent 部署。
|
||||
- 任务状态、用量和审计查看。
|
||||
- 风险与授权范围确认。
|
||||
|
||||
@@ -128,7 +128,7 @@ Manager 不做:
|
||||
|
||||
## 当前延期项
|
||||
|
||||
Heicode 到 Agnet 平台真实部署 API 的完整联调仍可延期。
|
||||
Heicode 到 Agent 平台真实部署 API 的完整联调仍可延期。
|
||||
|
||||
但 Heicode 侧必须准备好:
|
||||
|
||||
@@ -145,4 +145,4 @@ Heicode 到 Agnet 平台真实部署 API 的完整联调仍可延期。
|
||||
2. 客户端只登录 Heicode。
|
||||
3. CodeGW 独立服务运行。
|
||||
4. Azure Key Vault 只通过 Manager 后端受控身份访问,普通用户不直接进入。
|
||||
5. Agnet 平台接口只收结构化上下文和 `secret_ref`,不收长期明文密钥。
|
||||
5. Agent 平台接口只收结构化上下文和 `secret_ref`,不收长期明文密钥。
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
|
||||
Heicode 客户端是用户主体验,承担本地对话、想法输入、任务推进、执行反馈、交付接收和高危审批。它不是独立模型配置工具,也不是 CodeGW 后台客户端。
|
||||
|
||||
Heicode Manager 不是主体验,但它仍然负责账号与安全、客户端下载、Git/云资源绑定、Agnet 部署、任务状态、余额与审计等辅助操作。用户不在网页上编码,主要在客户端里推进开发任务。
|
||||
Heicode Manager 不是主体验,但它仍然负责账号与安全、客户端下载、Git/云资源绑定、Agent 部署、任务状态、余额与审计等辅助操作。用户不在网页上编码,主要在客户端里推进开发任务。
|
||||
|
||||
客户端必须保持:
|
||||
|
||||
@@ -41,25 +41,25 @@ Heicode
|
||||
| 功能 | 说明 |
|
||||
|------|------|
|
||||
| 对话输入 | 用户描述产品想法、开发任务或维护需求 |
|
||||
| 任务推进 | 继续要求、查看 Agnet 执行反馈、接收交付结果 |
|
||||
| 任务状态 | 查看 Heicode 返回的任务和子 Agnet 进度 |
|
||||
| 任务推进 | 继续要求、查看 Agent 执行反馈、接收交付结果 |
|
||||
| 任务状态 | 查看 Heicode 返回的任务和子 Agent 进度 |
|
||||
| 模型选择 | 从 Heicode 提供的模型中选择 |
|
||||
| 高危审批 | 对生产部署、密钥访问、云操作做确认 |
|
||||
| 日志反馈 | 展示关键执行反馈和失败原因 |
|
||||
| 安装更新 | 从 Heicode 下载页获取安装包 |
|
||||
|
||||
## 客户端里的 Agnet 闭环
|
||||
## 客户端里的 Agent 闭环
|
||||
|
||||
客户端里发生的不是单次对话,而是持续推进的任务闭环:
|
||||
|
||||
```text
|
||||
用户输入想法或补充要求
|
||||
-> Heicode 判断当前子任务
|
||||
-> 调用 Agnet 执行需求/开发/测试/修复/部署中的某一环
|
||||
-> Agnet 按权限调用需要的 SK 工具
|
||||
-> 调用 Agent 执行需求/开发/测试/修复/部署中的某一环
|
||||
-> Agent 按权限调用需要的 SK 工具
|
||||
-> 中间结果、失败原因和下一步建议回到客户端
|
||||
-> 用户继续补充、修正或审批
|
||||
-> Agnet 继续推进
|
||||
-> Agent 继续推进
|
||||
-> 最终返回交付物和部署结果
|
||||
```
|
||||
|
||||
|
||||
@@ -85,7 +85,7 @@
|
||||
|
||||
话术:
|
||||
|
||||
> 资源绑定不是把密钥贴到页面里。用户授权后,真实凭证进入密钥保管器,Heicode 只保存 secret_ref。子 Agnet 能看到的是资源元数据和权限,不是长期密钥。
|
||||
> 资源绑定不是把密钥贴到页面里。用户授权后,真实凭证进入密钥保管器,Heicode 只保存 secret_ref。子 Agent 能看到的是资源元数据和权限,不是长期密钥。
|
||||
|
||||
展示:
|
||||
|
||||
@@ -97,13 +97,13 @@
|
||||
|
||||
操作:
|
||||
|
||||
1. 展示推荐子 Agnet 角色。
|
||||
1. 展示推荐子 Agent 角色。
|
||||
2. 打开某个角色权限。
|
||||
3. 展示 manifest 预览。
|
||||
|
||||
话术:
|
||||
|
||||
> Heicode 会把资源按角色授权。例如 Backend Agnet 可以读写后端路径,Ops Agnet 可以部署测试环境,生产部署需要客户端审批。
|
||||
> Heicode 会把资源按角色授权。例如 Backend Agent 可以读写后端路径,Ops Agent 可以部署测试环境,生产部署需要客户端审批。
|
||||
|
||||
展示:
|
||||
|
||||
@@ -134,18 +134,18 @@
|
||||
操作:
|
||||
|
||||
1. 切回客户端,追加一句新要求。
|
||||
2. 展示当前 Agnet 子环节和 SK 工具调用。
|
||||
2. 展示当前 Agent 子环节和 SK 工具调用。
|
||||
3. 展示任务状态、日志、模型用量和审计。
|
||||
|
||||
话术:
|
||||
|
||||
> 主体验在客户端。用户继续补充要求后,Heicode 会调用 Agnet 推进下一子环节,例如开发、测试或修复。Agnet 在过程中还可以调用已授权的 SK 工具,最后把中间结果、交付物和部署结果回传回来。Manager 负责辅助查看状态、日志、用量和审计。
|
||||
> 主体验在客户端。用户继续补充要求后,Heicode 会调用 Agent 推进下一子环节,例如开发、测试或修复。Agent 在过程中还可以调用已授权的 SK 工具,最后把中间结果、交付物和部署结果回传回来。Manager 负责辅助查看状态、日志、用量和审计。
|
||||
|
||||
展示:
|
||||
|
||||
- 客户端追加需求。
|
||||
- 当前 Agnet 子环节和 SK 工具调用。
|
||||
- 子 Agnet 状态。
|
||||
- 当前 Agent 子环节和 SK 工具调用。
|
||||
- 子 Agent 状态。
|
||||
- 日志。
|
||||
- 用量。
|
||||
- 审计记录。
|
||||
@@ -168,10 +168,10 @@
|
||||
2. 不进入 CodeGW 管理后台。
|
||||
3. 不展示 Azure Key Vault access token。
|
||||
4. 不强调底层配置复杂度,强调用户流程。
|
||||
5. 如果 Agnet 真实部署尚未联调,明确说当前展示的是 Heicode 侧 payload、manifest 和占位控制面。
|
||||
5. 如果 Agent 真实部署尚未联调,明确说当前展示的是 Heicode 侧 payload、manifest 和占位控制面。
|
||||
|
||||
## 备用 1 分钟版本
|
||||
|
||||
话术:
|
||||
|
||||
> Heicode 是从想法到上线的智能开发工具。用户主要在客户端输入想法和继续推进任务,在 Manager 绑定 Git、文档、SK 和云资源并部署 Agnet。Heicode 生成子 Agnet 团队、角色和权限,密钥进入密钥保管器,子 Agnet 只拿短期凭证。Agnet 在执行中可以调用 SK 工具,最后完成交付与部署;CodeGW 提供模型、余额和用量,Heicode 展示日志、状态和审计。用户不需要进入 CodeGW 后台,也不需要管理 Azure Key Vault,只在 Heicode 和客户端完成整个开发流程。
|
||||
> Heicode 是从想法到上线的智能开发工具。用户主要在客户端输入想法和继续推进任务,在 Manager 绑定 Git、文档、SK 和云资源并部署 Agent。Heicode 生成子 Agent 团队、角色和权限,密钥进入密钥保管器,子 Agent 只拿短期凭证。Agent 在执行中可以调用 SK 工具,最后完成交付与部署;CodeGW 提供模型、余额和用量,Heicode 展示日志、状态和审计。用户不需要进入 CodeGW 后台,也不需要管理 Azure Key Vault,只在 Heicode 和客户端完成整个开发流程。
|
||||
|
||||
@@ -177,7 +177,7 @@ Heicode 只追问影响执行路径的少量问题:
|
||||
| 区块 | 内容 |
|
||||
|------|------|
|
||||
| 当前子环节 | 需求、设计、后端、前端、检查、测试、部署的阶段状态 |
|
||||
| Agnet 调用 | 当前角色、正在调用的 SK 工具、工具输出摘要 |
|
||||
| Agent 调用 | 当前角色、正在调用的 SK 工具、工具输出摘要 |
|
||||
| 最新动态 | Heicode 翻译后的关键执行事件 |
|
||||
| 当前产物 | 产品文档、接口草案、代码变更、检查报告 |
|
||||
| 继续输入 | 用户继续追加需求或修正方向 |
|
||||
@@ -190,7 +190,7 @@ Heicode 只追问影响执行路径的少量问题:
|
||||
- 失败必须展示可理解原因和建议动作。
|
||||
- 停止任务要说明影响范围。
|
||||
- 当前子环节必须可见,不能只显示“运行中”。
|
||||
- 如果 Agnet 调用了 SK 工具,前端要展示“调用了什么、产出了什么、是否失败”。
|
||||
- 如果 Agent 调用了 SK 工具,前端要展示“调用了什么、产出了什么、是否失败”。
|
||||
- 交付和部署结果必须回到客户端任务空间,而不是只留在后台日志里。
|
||||
|
||||
## 任务用量与审计
|
||||
|
||||
@@ -7,19 +7,19 @@
|
||||
核心边界:
|
||||
|
||||
- Heicode 客户端是主体验。用户主要在客户端里对话、描述想法、推进开发任务、查看执行反馈和审批高危操作。
|
||||
- Heicode Manager / 浏览器控制台是辅助控制台。它负责账号与安全、客户端下载、Git 绑定、云资源绑定、Agnet 部署、任务状态总览、模型余额与用量、审计与日志查看。
|
||||
- Heicode Manager / 浏览器控制台是辅助控制台。它负责账号与安全、客户端下载、Git 绑定、云资源绑定、Agent 部署、任务状态总览、模型余额与用量、审计与日志查看。
|
||||
- 用户不在网页上编码,也不在网页上完成主要开发对话。
|
||||
- 用户会在 Manager 里做辅助操作和观测,但主开发体验仍然在客户端。
|
||||
- CodeGW、密钥保管器、Agnet 平台后台都不是普通用户入口,只作为 Heicode 的底层能力。
|
||||
- CodeGW、密钥保管器、Agent 平台后台都不是普通用户入口,只作为 Heicode 的底层能力。
|
||||
|
||||
## 原型范围
|
||||
|
||||
| 范围 | 是否属于本文原型 | 说明 |
|
||||
|------|------------------|------|
|
||||
| Heicode 客户端 | 是,主体验 | 本地对话、想法输入、任务推进、执行反馈、高危审批 |
|
||||
| Heicode Manager / 浏览器控制台 | 是,辅助控制台 | 账号与安全、客户端下载、Git/云资源绑定、Agnet 部署、任务总览、余额与审计 |
|
||||
| Heicode Manager / 浏览器控制台 | 是,辅助控制台 | 账号与安全、客户端下载、Git/云资源绑定、Agent 部署、任务总览、余额与审计 |
|
||||
| 网页编码界面 | 否 | 用户不在网页上编码 |
|
||||
| 底层服务后台 | 否 | CodeGW、密钥保管器、Agnet 平台后台不直接暴露给普通用户 |
|
||||
| 底层服务后台 | 否 | CodeGW、密钥保管器、Agent 平台后台不直接暴露给普通用户 |
|
||||
|
||||
## 原型原则
|
||||
|
||||
@@ -27,8 +27,8 @@
|
||||
2. Manager 承载资源准备、部署准备、任务总览、用量和审计等辅助能力。
|
||||
3. 主开发对话、编码推进和高危审批仍然以客户端为主。
|
||||
4. 高危动作必须在客户端清晰审批。
|
||||
5. 继续开发时,Heicode 会持续调用 Agnet 推进需求、开发、测试、修复和部署等子环节。
|
||||
6. Agnet 执行过程中可以按权限调用 SK 工具。
|
||||
5. 继续开发时,Heicode 会持续调用 Agent 推进需求、开发、测试、修复和部署等子环节。
|
||||
6. Agent 执行过程中可以按权限调用 SK 工具。
|
||||
7. Manager 可以查看任务状态、用量和审计,但不把网页设计成 IDE 或网页编码工作台。
|
||||
8. 客户端只登录 Heicode,只使用 Heicode 提供的模型。
|
||||
|
||||
@@ -39,8 +39,8 @@ flowchart LR
|
||||
Client["Heicode 客户端主体验"] --> Idea["输入想法 / 继续开发"]
|
||||
Idea --> Task["生成任务卡"]
|
||||
Task --> Manager["Manager 准备资源 / 查看辅助信息"]
|
||||
Manager --> Deploy["Manager 部署 Agnet"]
|
||||
Deploy --> Loop["Agnet 子环节循环"]
|
||||
Manager --> Deploy["Manager 部署 Agent"]
|
||||
Deploy --> Loop["Agent 子环节循环"]
|
||||
Loop --> Dev["需求 / 开发 / 测试 / 修复"]
|
||||
Dev --> SK["按权限调用 SK 工具"]
|
||||
SK --> Run["执行反馈和对话"]
|
||||
@@ -106,7 +106,7 @@ flowchart LR
|
||||
│ │ 后端实现中 │ │ 等待生产部署审批 │ │
|
||||
│ └────────────────────────────┘ └────────────────────────────┘ │
|
||||
│ │
|
||||
│ 辅助:打开 Manager 绑定资源、部署 Agnet、查看状态 │
|
||||
│ 辅助:打开 Manager 绑定资源、部署 Agent、查看状态 │
|
||||
└──────────────────────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
@@ -162,7 +162,7 @@ Heicode 在客户端里追问关键缺口,并生成任务卡。
|
||||
│ 产品说明 / 原型描述 / 开发任务 / 检查清单 / 部署步骤 │
|
||||
│ │
|
||||
│ 需要 Manager 辅助确认 │
|
||||
│ [绑定 Git] [绑定云资源] [部署 Agnet] [查看预算与审计] │
|
||||
│ [绑定 Git] [绑定云资源] [部署 Agent] [查看预算与审计] │
|
||||
│ │
|
||||
│ [修改目标] [去 Manager 准备] │
|
||||
└──────────────────────────────────────────────────────────────────────────────┘
|
||||
@@ -188,10 +188,10 @@ Manager 是网页端辅助控制台,不是编码主体验,但依然承载资
|
||||
│ │ ¥128.40 / 今日 ¥12.30 │ │ 1 个高危审批待处理 │ │
|
||||
│ └────────────────────────────┘ └────────────────────────────┘ │
|
||||
│ ┌────────────────────────────────────────────────────────────────────────┐ │
|
||||
│ │ Agnet 部署 │ │
|
||||
│ │ Agent 部署 │ │
|
||||
│ │ 角色:Product / Backend / Frontend / Reviewer / Ops │ │
|
||||
│ │ 状态:待部署 │ │
|
||||
│ │ [部署 Agnet] │ │
|
||||
│ │ [部署 Agent] │ │
|
||||
│ └────────────────────────────────────────────────────────────────────────┘ │
|
||||
│ │
|
||||
│ 你可以在这里做辅助操作: │
|
||||
@@ -209,7 +209,7 @@ Manager 是网页端辅助控制台,不是编码主体验,但依然承载资
|
||||
|
||||
## 5. Manager 绑定 Git 和云资源
|
||||
|
||||
Git 和云资源在 Manager 中绑定,作为部署 Agnet 的前置条件。
|
||||
Git 和云资源在 Manager 中绑定,作为部署 Agent 的前置条件。
|
||||
|
||||
```text
|
||||
┌──────────────────────────────────────────────────────────────────────────────┐
|
||||
@@ -229,7 +229,7 @@ Git 和云资源在 Manager 中绑定,作为部署 Agnet 的前置条件。
|
||||
│ │ [选择本任务允许使用的资源] │ │
|
||||
│ └──────────────────────────────────────────────────────────────────────────┘ │
|
||||
│ │
|
||||
│ [保存资源绑定] [继续部署 Agnet] │
|
||||
│ [保存资源绑定] [继续部署 Agent] │
|
||||
└──────────────────────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
@@ -237,7 +237,7 @@ Git 和云资源在 Manager 中绑定,作为部署 Agnet 的前置条件。
|
||||
|
||||
- Git 和云资源绑定属于 Manager。
|
||||
- 用户不在网页上写代码。
|
||||
- 绑定完成后可以继续部署 Agnet,也可以返回查看任务状态与辅助信息。
|
||||
- 绑定完成后可以继续部署 Agent,也可以返回查看任务状态与辅助信息。
|
||||
|
||||
## 6. Manager 云资源自动发现
|
||||
|
||||
@@ -261,11 +261,11 @@ Git 和云资源在 Manager 中绑定,作为部署 Agnet 的前置条件。
|
||||
└──────────────────────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
## 7. Manager 部署 Agnet
|
||||
## 7. Manager 部署 Agent
|
||||
|
||||
```text
|
||||
┌──────────────────────────────────────────────────────────────────────────────┐
|
||||
│ Heicode Manager / 部署 Agnet │
|
||||
│ Heicode Manager / 部署 Agent │
|
||||
├──────────────────────────────────────────────────────────────────────────────┤
|
||||
│ 任务:小团队任务管理 SaaS │
|
||||
│ │
|
||||
@@ -273,7 +273,7 @@ Git 和云资源在 Manager 中绑定,作为部署 Agnet 的前置条件。
|
||||
│ - Git:repo-main / main │
|
||||
│ - 云资源:aks-test、db-dev │
|
||||
│ │
|
||||
│ Agnet 角色 │
|
||||
│ Agent 角色 │
|
||||
│ [x] Product 需求和验收 │
|
||||
│ [x] Backend API / 数据模型 │
|
||||
│ [x] Frontend 页面和交互 │
|
||||
@@ -283,30 +283,30 @@ Git 和云资源在 Manager 中绑定,作为部署 Agnet 的前置条件。
|
||||
│ 安全边界 │
|
||||
│ - 生产部署需要客户端审批 │
|
||||
│ - 生产数据默认禁止 │
|
||||
│ - 长期密钥不下发给 Agnet │
|
||||
│ - 长期密钥不下发给 Agent │
|
||||
│ │
|
||||
│ [返回修改资源] [部署 Agnet] │
|
||||
│ [返回修改资源] [部署 Agent] │
|
||||
└──────────────────────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
### 设计意图
|
||||
|
||||
- Manager 的最终动作是部署 Agnet。
|
||||
- Manager 的最终动作是部署 Agent。
|
||||
- Manager 不继续承担开发任务执行。
|
||||
- 部署完成后主流程回到客户端,但状态和审计仍可在 Manager 查看。
|
||||
|
||||
## 8. 客户端执行空间
|
||||
|
||||
Agnet 部署完成后,主流程回到客户端。
|
||||
Agent 部署完成后,主流程回到客户端。
|
||||
|
||||
```text
|
||||
┌──────────────────────────────────────────────────────────────────────────────┐
|
||||
│ Heicode Client / 小团队任务管理 SaaS 运行中 ¥12.30 │
|
||||
├──────────────────────────────────────────────────────────────────────────────┤
|
||||
│ │
|
||||
│ Heicode:Agnet 已部署,我开始推进当前开发任务。 │
|
||||
│ Heicode:Agent 已部署,我开始推进当前开发任务。 │
|
||||
│ │
|
||||
│ 当前 Agnet 子环节 │
|
||||
│ 当前 Agent 子环节 │
|
||||
│ - 需求和原型:已完成 │
|
||||
│ - 后端开发:进行中 │
|
||||
│ - 前端开发:等待接口确认 │
|
||||
@@ -325,15 +325,15 @@ Agnet 部署完成后,主流程回到客户端。
|
||||
│ └──────────────────────────────────────────────────────────────────────────┘ │
|
||||
│ [发送] │
|
||||
│ │
|
||||
│ 辅助查看:打开 Manager 查看 Git/云资源绑定、Agnet 部署、用量和审计 │
|
||||
│ 辅助查看:打开 Manager 查看 Git/云资源绑定、Agent 部署、用量和审计 │
|
||||
└──────────────────────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
### 设计意图
|
||||
|
||||
- 主执行反馈在客户端。
|
||||
- 用户看到的是 Agnet 持续推进的子环节,而不是一次性执行结果。
|
||||
- Agnet 可以在执行过程中调用已授权的 SK 工具。
|
||||
- 用户看到的是 Agent 持续推进的子环节,而不是一次性执行结果。
|
||||
- Agent 可以在执行过程中调用已授权的 SK 工具。
|
||||
- Manager 仍然是资源、部署、状态、余额和审计的辅助入口。
|
||||
- 用户持续通过自然语言推进任务。
|
||||
|
||||
@@ -363,7 +363,7 @@ Agnet 部署完成后,主流程回到客户端。
|
||||
│ Heicode Client / 小团队任务管理 SaaS 已完成 │
|
||||
├──────────────────────────────────────────────────────────────────────────────┤
|
||||
│ │
|
||||
│ Heicode:第一版已完成,Agnet 已整理交付物并完成目标环境部署。 │
|
||||
│ Heicode:第一版已完成,Agent 已整理交付物并完成目标环境部署。 │
|
||||
│ │
|
||||
│ 交付物 │
|
||||
│ ┌────────────────────┐ ┌────────────────────┐ ┌────────────────────┐ │
|
||||
@@ -376,7 +376,7 @@ Agnet 部署完成后,主流程回到客户端。
|
||||
│ - 安全检查:1 个问题已修复 │
|
||||
│ - 部署:测试环境成功,生产环境待你确认后可继续 │
|
||||
│ │
|
||||
│ [继续迭代] [发起维护任务] [查看 Agnet 部署信息] │
|
||||
│ [继续迭代] [发起维护任务] [查看 Agent 部署信息] │
|
||||
└──────────────────────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
@@ -424,12 +424,12 @@ Agnet 部署完成后,主流程回到客户端。
|
||||
客户端说目标
|
||||
-> Heicode 生成任务
|
||||
-> Manager 绑定 Git 和云资源
|
||||
-> Manager 部署 Agnet
|
||||
-> Manager 部署 Agent
|
||||
-> 客户端持续追加需求和修正方向
|
||||
-> Agnet 执行需求、开发、测试、修复等子环节
|
||||
-> Agnet 按权限调用 SK 工具
|
||||
-> Agent 执行需求、开发、测试、修复等子环节
|
||||
-> Agent 按权限调用 SK 工具
|
||||
-> 客户端接收中间结果并审批高危动作
|
||||
-> Agnet 完成交付整理和部署
|
||||
-> Agent 完成交付整理和部署
|
||||
-> Manager 提供状态、余额、下载和审计辅助
|
||||
-> 客户端接收交付结果
|
||||
```
|
||||
@@ -441,8 +441,8 @@ flowchart TD
|
||||
ClientHome["客户端输入想法"] --> ClientAsk["客户端追问缺口"]
|
||||
ClientAsk --> Task["客户端任务卡"]
|
||||
Task --> Manager["Manager 绑定资源 / 查看状态"]
|
||||
Manager --> Deploy["Manager 部署 Agnet"]
|
||||
Deploy --> Loop["Agnet 子环节循环"]
|
||||
Manager --> Deploy["Manager 部署 Agent"]
|
||||
Deploy --> Loop["Agent 子环节循环"]
|
||||
Loop --> ClientRun["客户端执行空间"]
|
||||
Loop --> SK["SK 工具调用"]
|
||||
SK --> ClientRun
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
## 使用场景
|
||||
|
||||
本文用于产品演示、培训、录屏旁白和销售讲解。语气以真实用户操作为主,不讲复杂技术实现;当需要解释安全、模型、密钥和 Agnet 平台时,只讲用户需要理解的边界。
|
||||
本文用于产品演示、培训、录屏旁白和销售讲解。语气以真实用户操作为主,不讲复杂技术实现;当需要解释安全、模型、密钥和 Agent 平台时,只讲用户需要理解的边界。
|
||||
|
||||
## 一句话开场
|
||||
|
||||
@@ -81,7 +81,7 @@ https://code.xinghanlab.com
|
||||
- 第一版包含哪些功能。
|
||||
- 推荐哪些页面和接口。
|
||||
- 需要哪些代码仓库、文档、SK 和云资源。
|
||||
- 建议使用哪些子 Agnet 角色。
|
||||
- 建议使用哪些子 Agent 角色。
|
||||
- 哪些操作可能属于高危操作。
|
||||
|
||||
如果草案不准确,我可以直接修改。确认后,Heicode 会提示我补充这个任务需要的上下文。
|
||||
@@ -105,7 +105,7 @@ https://code.xinghanlab.com
|
||||
|
||||
如果我有产品文档、原型描述或技能仓库,我继续绑定项目文档和 SK。
|
||||
|
||||
项目文档可以作为 Product Agnet 和 Architect Agnet 的上下文。SK 可以作为子 Agnet 可调用的技能来源。
|
||||
项目文档可以作为 Product Agent 和 Architect Agent 的上下文。SK 可以作为子 Agent 可调用的技能来源。
|
||||
|
||||
绑定后,平台知道:
|
||||
|
||||
@@ -154,18 +154,18 @@ https://code.xinghanlab.com
|
||||
|
||||
我不会看到明文密钥。平台最多展示 `secret_ref` 这种引用,让我知道凭证由密钥保管器管理。
|
||||
|
||||
### 12. 生成子 Agnet 团队
|
||||
### 12. 生成子 Agent 团队
|
||||
|
||||
资源准备好后,Heicode 会推荐一组子 Agnet 角色。
|
||||
资源准备好后,Heicode 会推荐一组子 Agent 角色。
|
||||
|
||||
例如:
|
||||
|
||||
- Product Agnet:负责需求和验收标准。
|
||||
- Architect Agnet:负责架构和技术方案。
|
||||
- Frontend Agnet:负责页面和交互。
|
||||
- Backend Agnet:负责 API、数据模型和服务逻辑。
|
||||
- Reviewer Agnet:负责代码检查和安全审查。
|
||||
- Ops Agnet:负责构建、部署和运行观察。
|
||||
- Product Agent:负责需求和验收标准。
|
||||
- Architect Agent:负责架构和技术方案。
|
||||
- Frontend Agent:负责页面和交互。
|
||||
- Backend Agent:负责 API、数据模型和服务逻辑。
|
||||
- Reviewer Agent:负责代码检查和安全审查。
|
||||
- Ops Agent:负责构建、部署和运行观察。
|
||||
|
||||
我可以使用推荐团队,也可以增减角色。
|
||||
|
||||
@@ -175,18 +175,18 @@ https://code.xinghanlab.com
|
||||
|
||||
例如:
|
||||
|
||||
Backend Agnet 可以:
|
||||
Backend Agent 可以:
|
||||
|
||||
- 读项目文档。
|
||||
- 读写后端代码路径。
|
||||
- 使用开发数据库。
|
||||
|
||||
Frontend Agnet 可以:
|
||||
Frontend Agent 可以:
|
||||
|
||||
- 读项目文档。
|
||||
- 读写前端代码路径。
|
||||
|
||||
Ops Agnet 可以:
|
||||
Ops Agent 可以:
|
||||
|
||||
- 部署测试环境。
|
||||
- 请求生产部署审批。
|
||||
@@ -212,8 +212,8 @@ manifest 不会显示 Git token、云 access key、SSH 私钥、数据库密码
|
||||
在开始执行前,Heicode 会用一张确认卡展示本次任务摘要:
|
||||
|
||||
- 本次要做什么。
|
||||
- 会启动哪些子 Agnet。
|
||||
- 每个子 Agnet 使用哪些资源。
|
||||
- 会启动哪些子 Agent。
|
||||
- 每个子 Agent 使用哪些资源。
|
||||
- 是否会访问密钥。
|
||||
- 是否会部署云资源。
|
||||
- 预计模型预算。
|
||||
@@ -228,7 +228,7 @@ manifest 不会显示 Git token、云 access key、SSH 私钥、数据库密码
|
||||
例如:
|
||||
|
||||
```text
|
||||
Ops Agnet 请求部署到生产环境。
|
||||
Ops Agent 请求部署到生产环境。
|
||||
目标资源:aks-prod
|
||||
操作:cloud:deploy
|
||||
风险等级:高
|
||||
@@ -237,7 +237,7 @@ Ops Agnet 请求部署到生产环境。
|
||||
|
||||
这时我可以批准或拒绝。
|
||||
|
||||
如果我批准,平台会使用密钥保管器派生短期、最小权限凭证。子 Agnet 不会拿到长期密钥。
|
||||
如果我批准,平台会使用密钥保管器派生短期、最小权限凭证。子 Agent 不会拿到长期密钥。
|
||||
|
||||
### 17. 在客户端持续推进开发
|
||||
|
||||
@@ -248,11 +248,11 @@ Ops Agnet 请求部署到生产环境。
|
||||
```text
|
||||
我在客户端补充要求
|
||||
-> Heicode 判断下一步要推进哪个子环节
|
||||
-> Agnet 执行需求、开发、测试、修复或部署中的当前任务
|
||||
-> Agnet 按权限调用已绑定的 SK 工具
|
||||
-> Agent 执行需求、开发、测试、修复或部署中的当前任务
|
||||
-> Agent 按权限调用已绑定的 SK 工具
|
||||
-> 中间结果回到客户端
|
||||
-> 我继续修正方向或批准高危动作
|
||||
-> Agnet 继续推进直到交付和部署完成
|
||||
-> Agent 继续推进直到交付和部署完成
|
||||
```
|
||||
|
||||
### 18. 查看执行状态
|
||||
@@ -261,11 +261,11 @@ Ops Agnet 请求部署到生产环境。
|
||||
|
||||
我能看到:
|
||||
|
||||
- Product Agnet 是否完成需求。
|
||||
- Backend Agnet 是否正在写代码。
|
||||
- Frontend Agnet 是否等待后端接口。
|
||||
- Reviewer Agnet 是否发现问题。
|
||||
- Ops Agnet 是否等待审批或部署中。
|
||||
- Product Agent 是否完成需求。
|
||||
- Backend Agent 是否正在写代码。
|
||||
- Frontend Agent 是否等待后端接口。
|
||||
- Reviewer Agent 是否发现问题。
|
||||
- Ops Agent 是否等待审批或部署中。
|
||||
|
||||
如果失败,页面应该显示失败原因,而不是只显示一个错误码。
|
||||
|
||||
@@ -275,7 +275,7 @@ Ops Agnet 请求部署到生产环境。
|
||||
|
||||
这里能看到:
|
||||
|
||||
- 子 Agnet 运行日志。
|
||||
- 子 Agent 运行日志。
|
||||
- 模型调用日志。
|
||||
- 消耗了多少额度。
|
||||
- 哪个任务使用了哪个模型。
|
||||
@@ -311,15 +311,15 @@ Ops Agnet 请求部署到生产环境。
|
||||
并检查现有权限模型是否支持团队成员邀请。
|
||||
```
|
||||
|
||||
Heicode 会复用之前的项目上下文、资源绑定和 Agnet 角色,生成新的开发任务。
|
||||
Heicode 会复用之前的项目上下文、资源绑定和 Agent 角色,生成新的开发任务。
|
||||
|
||||
## 简短演示口述版
|
||||
|
||||
> 我先登录 Heicode 的浏览器控制台。这里不是 CodeGW 后台,而是资源、部署、状态和审计的辅助入口。登录后,我会在客户端输入一个产品想法,比如做一个小团队任务管理 SaaS。平台会生成需求草案、推荐资源和子 Agnet 团队。接下来我绑定 Git 仓库、项目文档、SK 和云资源。真实密钥会进入密钥保管器,Heicode 只保存 secret_ref。然后我确认每个子 Agnet 的角色和权限,比如 Backend 能写后端路径,Ops 可以部署测试环境,但生产部署需要客户端审批。任务运行后,Agnet 会持续推进开发、测试和部署,并按权限调用需要的 SK 工具;我可以看到每个子 Agnet 的状态、日志、模型用量和审计记录。客户端只登录 code.xinghanlab.com,不选择模型提供方,模型由 Heicode 提供。整个流程的重点是:从一个想法开始,把产品、代码、云资源、模型、密钥和审计组织成一个完整的软件生命周期。
|
||||
> 我先登录 Heicode 的浏览器控制台。这里不是 CodeGW 后台,而是资源、部署、状态和审计的辅助入口。登录后,我会在客户端输入一个产品想法,比如做一个小团队任务管理 SaaS。平台会生成需求草案、推荐资源和子 Agent 团队。接下来我绑定 Git 仓库、项目文档、SK 和云资源。真实密钥会进入密钥保管器,Heicode 只保存 secret_ref。然后我确认每个子 Agent 的角色和权限,比如 Backend 能写后端路径,Ops 可以部署测试环境,但生产部署需要客户端审批。任务运行后,Agent 会持续推进开发、测试和部署,并按权限调用需要的 SK 工具;我可以看到每个子 Agent 的状态、日志、模型用量和审计记录。客户端只登录 code.xinghanlab.com,不选择模型提供方,模型由 Heicode 提供。整个流程的重点是:从一个想法开始,把产品、代码、云资源、模型、密钥和审计组织成一个完整的软件生命周期。
|
||||
|
||||
## 极简销售口述版
|
||||
|
||||
> Heicode 让用户从一个想法开始,接入自己的代码和云资源,由 Heicode 生成 AI 开发团队。平台负责资源授权、密钥保管、模型用量和审计;子 Agnet 负责开发、检查和部署。用户只在 Heicode 和客户端中操作,不需要进入 CodeGW 后台,也不会把长期密钥交给子 Agnet。
|
||||
> Heicode 让用户从一个想法开始,接入自己的代码和云资源,由 Heicode 生成 AI 开发团队。平台负责资源授权、密钥保管、模型用量和审计;子 Agent 负责开发、检查和部署。用户只在 Heicode 和客户端中操作,不需要进入 CodeGW 后台,也不会把长期密钥交给子 Agent。
|
||||
|
||||
## 口述时必须强调
|
||||
|
||||
@@ -328,7 +328,7 @@ Heicode 会复用之前的项目上下文、资源绑定和 Agnet 角色,生
|
||||
3. CodeGW 是内部模型网关,不是普通用户后台。
|
||||
4. 密钥进入密钥保管器,不进入 Git、Markdown 或日志。
|
||||
5. 高危操作在客户端审批。
|
||||
6. 子 Agnet 拿短期凭证,不拿长期密钥。
|
||||
6. 子 Agent 拿短期凭证,不拿长期密钥。
|
||||
7. 资源绑定和权限分配要尽量自动化和角色化。
|
||||
|
||||
## 口述时不要说
|
||||
@@ -338,4 +338,4 @@ Heicode 会复用之前的项目上下文、资源绑定和 Agnet 角色,生
|
||||
3. 让客户端选择模型提供方。
|
||||
4. 让用户手写复杂 manifest。
|
||||
5. 把 tenant/project 当作当前产品主轴。
|
||||
6. 把 Agnet 平台说成用户控制台。
|
||||
6. 把 Agent 平台说成用户控制台。
|
||||
|
||||
@@ -14,13 +14,13 @@ Heicode 平台由五个主要部分组成:
|
||||
|
||||
| 部分 | 用户理解 | 平台职责 |
|
||||
|------|----------|----------|
|
||||
| Heicode Manager | 浏览器辅助控制台 | 账号与安全、客户端下载、绑定资源、部署 Agnet、查看状态、余额和审计 |
|
||||
| Heicode Manager | 浏览器辅助控制台 | 账号与安全、客户端下载、绑定资源、部署 Agent、查看状态、余额和审计 |
|
||||
| Heicode 客户端 | 本地主体验和审批入口 | 登录 Heicode、输入任务、继续开发、查看反馈、接收交付结果、审批高危操作 |
|
||||
| Agnet 平台 | AI 开发团队执行层 | 在 AKS 上运行子 Agnet,回传状态、日志、事件和指标 |
|
||||
| Agent 平台 | AI 开发团队执行层 | 在 AKS 上运行子 Agent,回传状态、日志、事件和指标 |
|
||||
| CodeGW | 模型与用量底座 | 提供模型调用、余额、额度、用量和调用日志 |
|
||||
| 密钥保管器 | 凭证托管服务 | 保存长期密钥,按审批和权限生成短期访问能力 |
|
||||
|
||||
用户主要使用客户端和 Manager。CodeGW、密钥保管器和 Agnet 平台是底层能力,不作为普通用户日常操作后台。
|
||||
用户主要使用客户端和 Manager。CodeGW、密钥保管器和 Agent 平台是底层能力,不作为普通用户日常操作后台。
|
||||
|
||||
## 二、平台适合谁
|
||||
|
||||
@@ -62,34 +62,34 @@ Heicode 平台由五个主要部分组成:
|
||||
|
||||
### 3. AI 开发团队生成
|
||||
|
||||
平台根据任务目标推荐子 Agnet 角色。
|
||||
平台根据任务目标推荐子 Agent 角色。
|
||||
|
||||
常见角色:
|
||||
|
||||
- Product Agnet。
|
||||
- Architect Agnet。
|
||||
- Frontend Agnet。
|
||||
- Backend Agnet。
|
||||
- Reviewer Agnet。
|
||||
- Ops Agnet。
|
||||
- Product Agent。
|
||||
- Architect Agent。
|
||||
- Frontend Agent。
|
||||
- Backend Agent。
|
||||
- Reviewer Agent。
|
||||
- Ops Agent。
|
||||
|
||||
每个子 Agnet 都有明确职责、可用资源、权限范围、模型配置和审计记录。
|
||||
每个子 Agent 都有明确职责、可用资源、权限范围、模型配置和审计记录。
|
||||
|
||||
### 3.1 Agnet 持续执行闭环
|
||||
### 3.1 Agent 持续执行闭环
|
||||
|
||||
Heicode 不是把任务分发给 Agnet 一次就结束,而是在继续开发中持续调用 Agnet 完成子环节。
|
||||
Heicode 不是把任务分发给 Agent 一次就结束,而是在继续开发中持续调用 Agent 完成子环节。
|
||||
|
||||
完整闭环应表达为:
|
||||
|
||||
```text
|
||||
客户端输入想法或追加需求
|
||||
-> Manager 准备资源和部署条件
|
||||
-> Heicode 调度 Agnet 执行当前子环节
|
||||
-> Agnet 完成需求/开发/测试/修复/部署中的一步
|
||||
-> Agnet 按权限调用 SK 工具
|
||||
-> Heicode 调度 Agent 执行当前子环节
|
||||
-> Agent 完成需求/开发/测试/修复/部署中的一步
|
||||
-> Agent 按权限调用 SK 工具
|
||||
-> 中间结果和状态回传给客户端与 Manager
|
||||
-> 用户继续补充、修正或审批
|
||||
-> 最终由 Agnet 完成交付整理和部署
|
||||
-> 最终由 Agent 完成交付整理和部署
|
||||
```
|
||||
|
||||
### 4. 权限分配
|
||||
@@ -99,7 +99,7 @@ Heicode 使用 Resource Grant 描述“哪个角色能用哪个资源、能做
|
||||
示例:
|
||||
|
||||
```text
|
||||
Backend Agnet
|
||||
Backend Agent
|
||||
-> 可读项目文档
|
||||
-> 可读写后端代码路径
|
||||
-> 可访问开发数据库
|
||||
@@ -120,7 +120,7 @@ Backend Agnet
|
||||
- 访问生产密钥。
|
||||
- 大额模型预算消耗。
|
||||
|
||||
审批通过后,平台可以从密钥保管器派生短期、最小权限凭证给子 Agnet 使用。
|
||||
审批通过后,平台可以从密钥保管器派生短期、最小权限凭证给子 Agent 使用。
|
||||
|
||||
### 6. 模型与余额
|
||||
|
||||
@@ -152,7 +152,7 @@ Heicode 统一展示任务执行、模型调用、资源访问、高危审批和
|
||||
谁
|
||||
在什么时候
|
||||
为了哪个任务
|
||||
让哪个子 Agnet
|
||||
让哪个子 Agent
|
||||
使用了哪个资源
|
||||
执行了什么操作
|
||||
是否经过审批
|
||||
@@ -161,7 +161,7 @@ Heicode 统一展示任务执行、模型调用、资源访问、高危审批和
|
||||
|
||||
### 8. 部署和维护
|
||||
|
||||
Agnet 平台负责执行开发、检查、测试、修复和部署任务。Heicode 展示部署状态、失败原因、日志、指标和审计。
|
||||
Agent 平台负责执行开发、检查、测试、修复和部署任务。Heicode 展示部署状态、失败原因、日志、指标和审计。
|
||||
|
||||
上线后,用户可以继续输入维护和升级需求,平台复用已有项目上下文、资源绑定和权限规则。
|
||||
|
||||
@@ -175,14 +175,14 @@ Agnet 平台负责执行开发、检查、测试、修复和部署任务。Heico
|
||||
-> 客户端输入产品想法
|
||||
-> 查看任务草案
|
||||
-> 绑定 Git / 文档 / SK / 云资源
|
||||
-> 生成子 Agnet 团队
|
||||
-> 生成子 Agent 团队
|
||||
-> 分配角色权限
|
||||
-> 预览 permission manifest
|
||||
-> 确认部署计划
|
||||
-> Manager 部署 Agnet
|
||||
-> Manager 部署 Agent
|
||||
-> 客户端持续推进任务
|
||||
-> 客户端审批高危操作
|
||||
-> Agnet 平台执行任务并调用 SK
|
||||
-> Agent 平台执行任务并调用 SK
|
||||
-> Heicode 查看日志、用量、审计和交付结果
|
||||
-> 后续维护和升级
|
||||
```
|
||||
@@ -198,7 +198,7 @@ Heicode Manager 是浏览器辅助控制台。
|
||||
- 登录和查看首页。
|
||||
- 绑定资源。
|
||||
- 分配角色和权限。
|
||||
- 创建或部署 Agnet 任务。
|
||||
- 创建或部署 Agent 任务。
|
||||
- 查看模型和余额。
|
||||
- 查看日志与审计。
|
||||
- 下载客户端。
|
||||
@@ -225,20 +225,20 @@ Manager 不应该变成 CodeGW 后台,也不应该要求用户直接管理底
|
||||
- 直接访问密钥保管器。
|
||||
- 保存长期云密钥。
|
||||
|
||||
### 5.3 Agnet 平台
|
||||
### 5.3 Agent 平台
|
||||
|
||||
Agnet 平台是运行时执行层。
|
||||
Agent 平台是运行时执行层。
|
||||
|
||||
它负责:
|
||||
|
||||
- 在 AKS 上部署子 Agnet。
|
||||
- 维护子 Agnet 状态。
|
||||
- 在 AKS 上部署子 Agent。
|
||||
- 维护子 Agent 状态。
|
||||
- 执行开发、检查、测试、修复、部署和维护任务。
|
||||
- 在执行过程中按权限调用 SK 工具。
|
||||
- 回传日志、事件、指标和审计数据。
|
||||
- 按 Resource Grant 和运行时身份访问资源。
|
||||
|
||||
普通用户不直接操作 Agnet 平台后台,而是在 Heicode 中查看 Agnet 状态和结果。
|
||||
普通用户不直接操作 Agent 平台后台,而是在 Heicode 中查看 Agent 状态和结果。
|
||||
|
||||
### 5.4 CodeGW
|
||||
|
||||
@@ -312,7 +312,7 @@ Heicode 判断当前任务需要哪些上下文
|
||||
- 调用日志。
|
||||
- 失败日志。
|
||||
|
||||
子 Agnet 的运行模型属于 Agnet 平台部署配置,不等同于 CodeGW 后台的模型供应商配置。
|
||||
子 Agent 的运行模型属于 Agent 平台部署配置,不等同于 CodeGW 后台的模型供应商配置。
|
||||
|
||||
## 九、密钥保管说明
|
||||
|
||||
@@ -322,9 +322,9 @@ Heicode 判断当前任务需要哪些上下文
|
||||
- Markdown。
|
||||
- 前端响应。
|
||||
- 普通日志。
|
||||
- 子 Agnet 长期状态。
|
||||
- 子 Agent 长期状态。
|
||||
|
||||
长期密钥进入密钥保管器。Heicode 保存 `secret_ref`。子 Agnet 使用资源时,只获取短期、最小权限、可审计凭证。
|
||||
长期密钥进入密钥保管器。Heicode 保存 `secret_ref`。子 Agent 使用资源时,只获取短期、最小权限、可审计凭证。
|
||||
|
||||
## 十、平台边界
|
||||
|
||||
@@ -337,7 +337,7 @@ Heicode 判断当前任务需要哪些上下文
|
||||
|
||||
- CodeGW 后台。
|
||||
- Azure Key Vault 控制台。
|
||||
- Agnet 平台后台。
|
||||
- Agent 平台后台。
|
||||
|
||||
用户不应该看到:
|
||||
|
||||
@@ -355,7 +355,7 @@ Heicode 判断当前任务需要哪些上下文
|
||||
|
||||
### 2. 我必须绑定 Git 吗?
|
||||
|
||||
如果要让子 Agnet 修改代码,建议绑定 Git 仓库。只做产品文档或原型描述时,可以先不绑定代码仓库。
|
||||
如果要让子 Agent 修改代码,建议绑定 Git 仓库。只做产品文档或原型描述时,可以先不绑定代码仓库。
|
||||
|
||||
### 3. 我必须绑定云资源吗?
|
||||
|
||||
@@ -363,7 +363,7 @@ Heicode 判断当前任务需要哪些上下文
|
||||
|
||||
### 4. 密钥安全吗?
|
||||
|
||||
平台设计上不把明文密钥保存到 Heicode 服务端数据库、Git、Markdown、日志或前端响应中。真实凭证进入密钥保管器,子 Agnet 只使用短期、最小权限凭证。
|
||||
平台设计上不把明文密钥保存到 Heicode 服务端数据库、Git、Markdown、日志或前端响应中。真实凭证进入密钥保管器,子 Agent 只使用短期、最小权限凭证。
|
||||
|
||||
### 5. 客户端必须安装吗?
|
||||
|
||||
@@ -373,9 +373,9 @@ Heicode 判断当前任务需要哪些上下文
|
||||
|
||||
CodeGW 是 Heicode 的模型网关和计费服务。普通用户不需要进入 CodeGW 后台,只在 Heicode 中查看模型、余额、额度和调用日志。
|
||||
|
||||
### 7. Agnet 是什么?
|
||||
### 7. Agent 是什么?
|
||||
|
||||
Agnet 是平台生成的 AI 开发角色。不同 Agnet 负责不同任务,例如产品、架构、前端、后端、审查和部署。
|
||||
Agent 是平台生成的 AI 开发角色。不同 Agent 负责不同任务,例如产品、架构、前端、后端、审查和部署。
|
||||
|
||||
### 8. 生产部署怎么保证安全?
|
||||
|
||||
@@ -391,4 +391,4 @@ Agnet 是平台生成的 AI 开发角色。不同 Agnet 负责不同任务,例
|
||||
|
||||
## 十二、平台说明标准话术
|
||||
|
||||
> Heicode 是一个从想法到上线的软件生命周期平台。用户在 Heicode 中输入产品想法,绑定代码、文档、SK 和云资源。Heicode 生成子 Agnet 团队和权限清单,客户端负责高危审批,Agnet 平台负责执行开发和部署,CodeGW 提供模型和用量,密钥保管器保存长期凭证。用户只需要使用 Heicode 和客户端,不需要直接进入 CodeGW 后台或密钥保管器后台。
|
||||
> Heicode 是一个从想法到上线的软件生命周期平台。用户在 Heicode 中输入产品想法,绑定代码、文档、SK 和云资源。Heicode 生成子 Agent 团队和权限清单,客户端负责高危审批,Agent 平台负责执行开发和部署,CodeGW 提供模型和用量,密钥保管器保存长期凭证。用户只需要使用 Heicode 和客户端,不需要直接进入 CodeGW 后台或密钥保管器后台。
|
||||
|
||||
@@ -14,11 +14,11 @@
|
||||
| [`04-platform-usage-guide.md`](./04-platform-usage-guide.md) | 登录平台和使用平台指南,面向真实用户上手 |
|
||||
| [`05-ppt-copy.md`](./05-ppt-copy.md) | 产品 PPT 文案,面向路演、客户介绍和团队同步 |
|
||||
| [`06-security-and-credential-management.md`](./06-security-and-credential-management.md) | 安全与凭证管理说明,解释密钥保管器、`secret_ref`、高危审批 |
|
||||
| [`07-integration-boundaries.md`](./07-integration-boundaries.md) | CodeGW、Azure Key Vault、Agnet 平台边界说明 |
|
||||
| [`07-integration-boundaries.md`](./07-integration-boundaries.md) | CodeGW、Azure Key Vault、Agent 平台边界说明 |
|
||||
| [`08-client-guide.md`](./08-client-guide.md) | Heicode 客户端说明,强调只登录 Heicode、只使用 Heicode 模型 |
|
||||
| [`09-demo-script.md`](./09-demo-script.md) | 5 分钟演示脚本,用于产品演示和录屏 |
|
||||
| [`10-frontend-detail-spec.md`](./10-frontend-detail-spec.md) | 前端细节说明,覆盖 Heicode 和客户端页面、组件、状态、文案规则 |
|
||||
| [`11-product-prototype-wireframes.md`](./11-product-prototype-wireframes.md) | 产品原型图,说明 Heicode 客户端主体验、Manager 辅助控制台与 Agnet 执行闭环的边界 |
|
||||
| [`11-product-prototype-wireframes.md`](./11-product-prototype-wireframes.md) | 产品原型图,说明 Heicode 客户端主体验、Manager 辅助控制台与 Agent 执行闭环的边界 |
|
||||
| [`12-narrated-user-operation-flow.md`](./12-narrated-user-operation-flow.md) | 口述用户操作流程,用于演示、培训、录屏旁白和销售讲解 |
|
||||
| [`13-platform-description.md`](./13-platform-description.md) | 平台说明文档,解释平台组成、使用方式、系统边界和常见问题 |
|
||||
|
||||
@@ -34,15 +34,15 @@ Heicode 是一款面向全流程智能开发的 SaaS Code 工具。用户注册
|
||||
|
||||
- 用户侧叫 `Heicode`,不要叫 CodeGW 后台。
|
||||
- `Heicode 客户端` 是用户主体验,用于对话、输入想法、推进开发任务、查看执行反馈、高危审批和接收交付结果。
|
||||
- `Heicode Manager` 是浏览器里的辅助控制台,负责账号与安全、客户端下载、Git/云资源绑定、Agnet 部署、任务状态、余额与审计。
|
||||
- 继续开发时,Heicode 会持续调用 Agnet 推进需求、开发、测试、修复和部署等子环节。
|
||||
- Agnet 在执行过程中可以调用已授权的 SK 工具,最终完成交付与部署并把结果回传给客户端和 Manager。
|
||||
- `Heicode Manager` 是浏览器里的辅助控制台,负责账号与安全、客户端下载、Git/云资源绑定、Agent 部署、任务状态、余额与审计。
|
||||
- 继续开发时,Heicode 会持续调用 Agent 推进需求、开发、测试、修复和部署等子环节。
|
||||
- Agent 在执行过程中可以调用已授权的 SK 工具,最终完成交付与部署并把结果回传给客户端和 Manager。
|
||||
- 用户侧叫 `密钥保管器`,技术实现可以说明是 Azure Key Vault;Manager 通过 VM Managed Identity 访问,不要求普通用户进入 Key Vault 后台。
|
||||
- 用户侧叫 `资源绑定`,不要叫 Git 来源。
|
||||
- 客户端不出现 `模型提供方`、`旧服务入口`、`第三方路由入口`。
|
||||
- CodeGW 是内部模型网关和计费服务,不作为普通用户产品入口。
|
||||
- Agnet 平台是执行层,不替代 Heicode 的用户控制台。
|
||||
- Agent 平台是执行层,不替代 Heicode 的用户控制台。
|
||||
|
||||
## 当前产品边界
|
||||
|
||||
当前资料包以 `docs/heicode.md`、`docs/plan.md`、`docs/product-requirements.md` 为依据。旧 Agnet 草案、旧 M1-M5 计划和旧 UI 命名不作为产品主线。
|
||||
当前资料包以 `docs/heicode.md`、`docs/plan.md`、`docs/product-requirements.md` 为依据。旧 Agent 草案、旧 M1-M5 计划和旧 UI 命名不作为产品主线。
|
||||
|
||||
@@ -0,0 +1,92 @@
|
||||
# Heicode Manager 本地交接敏感配置填写表
|
||||
# 更新时间:2026-06-01
|
||||
#
|
||||
# 说明:
|
||||
# 1. 这个文件只放在本地交接目录,不要提交 git。
|
||||
# 2. 我不能凭空写入无法读取/无法确认的明文密码;下面列出所有需要手动取值的项。
|
||||
# 3. 真实值建议从生产 VM .env、docker inspect、Azure Portal、NewAPI 后台或负责人安全渠道获取。
|
||||
# 4. 填完后只用于人工交接,不要发到公开聊天、截图、Markdown 或仓库。
|
||||
|
||||
### 1. VM SSH
|
||||
HEICODE_VM_HOST=20.24.50.121
|
||||
HEICODE_VM_USER=heicode
|
||||
HEICODE_VM_PASSWORD="<FILL_MANUALLY: 从负责人安全渠道获取,不要提交>"
|
||||
HEICODE_VM_SSH_KEY_PATH="<OPTIONAL: 如果使用私钥登录,填写本机私钥路径>"
|
||||
|
||||
### 2. Gitee 仓库账号
|
||||
HEICODE_GIT_REMOTE="http://gitee.ath.cx:3000/taijibaga/heicode-mananger.git"
|
||||
GITEE_USERNAME="<FILL_MANUALLY: Gitee 用户名>"
|
||||
GITEE_PASSWORD_OR_TOKEN="<FILL_MANUALLY: Gitee 密码或个人 token>"
|
||||
|
||||
### 3. Manager 生产数据库和缓存
|
||||
# 获取位置:
|
||||
# - VM: /home/heicode/apps/heicode-git/heicode/.env
|
||||
# - 或 docker inspect heicode 的 Config.Env
|
||||
# - 或 Azure PostgreSQL / Azure Redis 控制台
|
||||
SQL_DSN="<FILL_FROM_VM_ENV_OR_AZURE_POSTGRESQL>"
|
||||
REDIS_CONN_STRING="<FILL_FROM_VM_ENV_OR_AZURE_REDIS>"
|
||||
|
||||
### 4. Manager 基础安全密钥
|
||||
# 获取位置:
|
||||
# - VM .env
|
||||
# - docker inspect heicode
|
||||
SESSION_SECRET="<FILL_FROM_VM_ENV>"
|
||||
CRYPTO_SECRET="<FILL_FROM_VM_ENV>"
|
||||
MCP_SERVER_INTERNAL_TOKEN="<FILL_FROM_VM_ENV>"
|
||||
|
||||
### 5. Heicode / Agent 认证网关
|
||||
HEICODE_AUTH_BASE_URL="<FILL_FROM_VM_ENV_OR_DEFAULT:https://apimtaiji.azure-api.net/api/mcp>"
|
||||
HEICODE_ROOT_EMAILS="xiaohei@qq.com"
|
||||
HEICODE_ADMIN_EMAILS="<OPTIONAL>"
|
||||
|
||||
### 6. Azure Key Vault
|
||||
AZURE_KEY_VAULT_URL="https://heicode-kv.vault.azure.net"
|
||||
AZURE_CLIENT_ID="<FILL_IF_USER_ASSIGNED_IDENTITY_IS_USED>"
|
||||
AZURE_PORTAL_ACCOUNT="<FILL_MANUALLY: Azure 登录账号,不建议写密码>"
|
||||
AZURE_PORTAL_PASSWORD="<DO_NOT_STORE_HERE_UNLESS_TEMPORARY_AND_LOCAL_ONLY>"
|
||||
AZURE_KEY_VAULT_CALLBACK_SECRET_NAME="agent-callback-signing-key"
|
||||
AZURE_KEY_VAULT_CALLBACK_SECRET_VALUE="<FILL_FROM_KEY_VAULT_IF_NEEDED>"
|
||||
|
||||
### 7. 普通 sub Agent Manager Runtime
|
||||
AGENT_RUNTIME_ENABLED=true
|
||||
AGENT_RUNTIME_BASE_URL="http://20.212.121.126"
|
||||
AGENT_RUNTIME_HEALTH_PATH="/api/agent/health"
|
||||
AGENT_RUNTIME_CREATE_PATH="/api/swarms"
|
||||
AGENT_RUNTIME_STOP_PATH="/api/swarms/{swarm_id}/stop"
|
||||
AGENT_RUNTIME_APPROVAL_DECISION_PATH="/api/swarms/{swarm_id}/approvals/{approval_id}"
|
||||
AGENT_RUNTIME_TIMEOUT_SECONDS=15
|
||||
AGENT_RUNTIME_SERVICE_TOKEN="<FILL_FROM_VM_ENV_OR_AGENT_MANAGER_OWNER>"
|
||||
AGENT_RUNTIME_CALLBACK_SIGNING_SECRET_REF="<FILL_FROM_VM_ENV_OR_KEY_VAULT_SECRET_REF>"
|
||||
AGENT_CALLBACK_TOKEN="<FILL_FROM_VM_ENV_OR_AGENT_MANAGER_OWNER>"
|
||||
AGENT_CALLBACK_SIGNING_SECRET="<FILL_FROM_VM_ENV_OR_KEY_VAULT>"
|
||||
|
||||
### 8. 蜂群 Runtime / HeiCode-Swarm
|
||||
SWARM_RUNTIME_ENABLED=false
|
||||
SWARM_RUNTIME_BASE_URL="http://52.139.240.116:8000"
|
||||
SWARM_RUNTIME_CREATE_PATH="/api/swarms"
|
||||
SWARM_RUNTIME_HEALTH_PATH="/api/agent/health"
|
||||
SWARM_RUNTIME_STOP_PATH="/api/swarms/{swarm_id}/stop"
|
||||
SWARM_RUNTIME_APPROVAL_DECISION_PATH="/api/swarms/{swarm_id}/approvals/{approval_id}"
|
||||
SWARM_RUNTIME_SERVICE_TOKEN="<FILL_FROM_SWARM_OWNER_IF_ENABLED>"
|
||||
|
||||
### 9. NewAPI
|
||||
NEWAPI_CONTAINER_NAME="new-api"
|
||||
NEWAPI_VM_URL="http://20.24.50.121:3001"
|
||||
NEWAPI_COMPOSE_PATH="/home/heicode/apps/new-api/docker-compose.yml"
|
||||
NEWAPI_ADMIN_USERNAME="<FILL_MANUALLY: NewAPI 管理员账号>"
|
||||
NEWAPI_ADMIN_PASSWORD="<FILL_MANUALLY: NewAPI 管理员密码>"
|
||||
NEWAPI_CHANNEL_KEYS="<DO_NOT_STORE_MULTILINE_KEYS_HERE; 从 NewAPI 后台渠道管理读取>"
|
||||
|
||||
### 10. Manager 测试/管理员用户
|
||||
MANAGER_PUBLIC_URL="https://code.xinghanlab.com"
|
||||
MANAGER_ROOT_EMAIL="xiaohei@qq.com"
|
||||
MANAGER_TEST_EMAIL="zsbgnw@gmail.com"
|
||||
MANAGER_TEST_PASSWORD="<FILL_MANUALLY: 测试用户密码>"
|
||||
MANAGER_ADMIN_EMAIL="<FILL_MANUALLY>"
|
||||
MANAGER_ADMIN_PASSWORD="<FILL_MANUALLY>"
|
||||
|
||||
### 11. 桌面客户端安装包
|
||||
HEICODE_DESKTOP_ARTIFACT_DIR="/data/desktop-artifacts"
|
||||
HEICODE_DESKTOP_CLIENT_VERSION="0.1.0"
|
||||
HEICODE_DESKTOP_FILE_MACOS_ARM64="/data/desktop-artifacts/HeiCode_0.1.0_macos_arm64.dmg"
|
||||
HEICODE_DESKTOP_FILE_WINDOWS="/data/desktop-artifacts/Heicode_0.1.0_windows_x64_msi.msi"
|
||||
@@ -13,20 +13,20 @@ import (
|
||||
)
|
||||
|
||||
const (
|
||||
agnetApprovalStatusPending = "pending"
|
||||
agnetApprovalStatusApproved = "approved"
|
||||
agnetApprovalStatusRejected = "rejected"
|
||||
agnetApprovalStatusExpired = "expired"
|
||||
agentApprovalStatusPending = "pending"
|
||||
agentApprovalStatusApproved = "approved"
|
||||
agentApprovalStatusRejected = "rejected"
|
||||
agentApprovalStatusExpired = "expired"
|
||||
|
||||
agnetLeaseStatusActive = "active"
|
||||
agnetLeaseStatusExpired = "expired"
|
||||
agnetLeaseStatusRevoked = "revoked"
|
||||
agentLeaseStatusActive = "active"
|
||||
agentLeaseStatusExpired = "expired"
|
||||
agentLeaseStatusRevoked = "revoked"
|
||||
|
||||
defaultAgnetApprovalTTLSeconds = 15 * 60
|
||||
maxAgnetApprovalTTLSeconds = 60 * 60
|
||||
defaultAgentApprovalTTLSeconds = 15 * 60
|
||||
maxAgentApprovalTTLSeconds = 60 * 60
|
||||
)
|
||||
|
||||
type agnetApprovalPayload struct {
|
||||
type agentApprovalPayload struct {
|
||||
DeploymentID string `json:"deployment_id"`
|
||||
BindingScope string `json:"binding_scope"`
|
||||
Operation string `json:"operation"`
|
||||
@@ -41,11 +41,11 @@ type agnetApprovalPayload struct {
|
||||
Reason string `json:"reason"`
|
||||
}
|
||||
|
||||
type agnetDecisionPayload struct {
|
||||
type agentDecisionPayload struct {
|
||||
Reason string `json:"reason"`
|
||||
}
|
||||
|
||||
type agnetApprovalResponse struct {
|
||||
type agentApprovalResponse struct {
|
||||
ApprovalID string `json:"approval_id"`
|
||||
UserId int `json:"user_id"`
|
||||
DeploymentID string `json:"deployment_id"`
|
||||
@@ -68,10 +68,10 @@ type agnetApprovalResponse struct {
|
||||
DecidedAt int64 `json:"decided_at,omitempty"`
|
||||
CreatedAt int64 `json:"created_at"`
|
||||
UpdatedAt int64 `json:"updated_at"`
|
||||
CredentialLease *agnetCredentialLeaseResponse `json:"credential_lease,omitempty"`
|
||||
CredentialLease *agentCredentialLeaseResponse `json:"credential_lease,omitempty"`
|
||||
}
|
||||
|
||||
type agnetCredentialLeaseResponse struct {
|
||||
type agentCredentialLeaseResponse struct {
|
||||
LeaseID string `json:"lease_id"`
|
||||
CredentialRef string `json:"credential_ref"`
|
||||
ApprovalID string `json:"approval_id"`
|
||||
@@ -90,13 +90,13 @@ type agnetCredentialLeaseResponse struct {
|
||||
UpdatedAt int64 `json:"updated_at"`
|
||||
}
|
||||
|
||||
func CreateAgnetApprovalRequest(c *gin.Context) {
|
||||
var payload agnetApprovalPayload
|
||||
func CreateAgentApprovalRequest(c *gin.Context) {
|
||||
var payload agentApprovalPayload
|
||||
if err := common.DecodeJson(c.Request.Body, &payload); err != nil {
|
||||
common.ApiError(c, err)
|
||||
return
|
||||
}
|
||||
payload, err := normalizeAgnetApprovalPayload(payload)
|
||||
payload, err := normalizeAgentApprovalPayload(payload)
|
||||
if err != nil {
|
||||
common.ApiError(c, err)
|
||||
return
|
||||
@@ -109,7 +109,7 @@ func CreateAgnetApprovalRequest(c *gin.Context) {
|
||||
}
|
||||
|
||||
now := time.Now().UnixMilli()
|
||||
approval := model.AgnetApprovalRequest{
|
||||
approval := model.AgentApprovalRequest{
|
||||
ApprovalID: "appr_" + common.GetUUID(),
|
||||
UserId: userID,
|
||||
DeploymentID: payload.DeploymentID,
|
||||
@@ -122,8 +122,8 @@ func CreateAgnetApprovalRequest(c *gin.Context) {
|
||||
RiskLevel: payload.RiskLevel,
|
||||
RequiresCredential: payload.RequiresCredential,
|
||||
SecretRef: payload.SecretRef,
|
||||
Status: agnetApprovalStatusPending,
|
||||
RequestedBy: agnetActorForUser(userID),
|
||||
Status: agentApprovalStatusPending,
|
||||
RequestedBy: agentActorForUser(userID),
|
||||
RequestReason: payload.Reason,
|
||||
TTLSeconds: payload.TTLSeconds,
|
||||
ExpiresAt: now + int64(payload.TTLSeconds)*1000,
|
||||
@@ -132,11 +132,11 @@ func CreateAgnetApprovalRequest(c *gin.Context) {
|
||||
common.ApiError(c, err)
|
||||
return
|
||||
}
|
||||
recordAgnetApprovalAudit("approval.requested", &approval, nil, "ok", "")
|
||||
common.ApiSuccess(c, agnetApprovalToResponse(approval, nil))
|
||||
recordAgentApprovalAudit("approval.requested", &approval, nil, "ok", "")
|
||||
common.ApiSuccess(c, agentApprovalToResponse(approval, nil))
|
||||
}
|
||||
|
||||
func ListAgnetApprovalRequests(c *gin.Context) {
|
||||
func ListAgentApprovalRequests(c *gin.Context) {
|
||||
userID := c.GetInt("id")
|
||||
if userID <= 0 {
|
||||
common.ApiErrorMsg(c, "user authentication required")
|
||||
@@ -151,58 +151,58 @@ func ListAgnetApprovalRequests(c *gin.Context) {
|
||||
q = q.Where("deployment_id = ?", deploymentID)
|
||||
}
|
||||
|
||||
var approvals []model.AgnetApprovalRequest
|
||||
var approvals []model.AgentApprovalRequest
|
||||
if err := q.Order("created_at desc, id desc").Limit(200).Find(&approvals).Error; err != nil {
|
||||
common.ApiError(c, err)
|
||||
return
|
||||
}
|
||||
for i := range approvals {
|
||||
expireAgnetApprovalIfNeeded(&approvals[i])
|
||||
expireAgentApprovalIfNeeded(&approvals[i])
|
||||
}
|
||||
|
||||
items := make([]agnetApprovalResponse, 0, len(approvals))
|
||||
items := make([]agentApprovalResponse, 0, len(approvals))
|
||||
for _, approval := range approvals {
|
||||
if statusFilter != "" && approval.Status != statusFilter {
|
||||
continue
|
||||
}
|
||||
items = append(items, agnetApprovalToResponse(approval, nil))
|
||||
items = append(items, agentApprovalToResponse(approval, nil))
|
||||
}
|
||||
common.ApiSuccess(c, gin.H{"items": items})
|
||||
}
|
||||
|
||||
func GetAgnetApprovalRequest(c *gin.Context) {
|
||||
approval, ok := findAgnetApprovalForUser(c)
|
||||
func GetAgentApprovalRequest(c *gin.Context) {
|
||||
approval, ok := findAgentApprovalForUser(c)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
expireAgnetApprovalIfNeeded(&approval)
|
||||
lease := findAgnetCredentialLeaseByApproval(approval.ApprovalID)
|
||||
common.ApiSuccess(c, agnetApprovalToResponse(approval, lease))
|
||||
expireAgentApprovalIfNeeded(&approval)
|
||||
lease := findAgentCredentialLeaseByApproval(approval.ApprovalID)
|
||||
common.ApiSuccess(c, agentApprovalToResponse(approval, lease))
|
||||
}
|
||||
|
||||
func ApproveAgnetApprovalRequest(c *gin.Context) {
|
||||
approval, ok := findAgnetApprovalForUser(c)
|
||||
func ApproveAgentApprovalRequest(c *gin.Context) {
|
||||
approval, ok := findAgentApprovalForUser(c)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
if expireAgnetApprovalIfNeeded(&approval) {
|
||||
if expireAgentApprovalIfNeeded(&approval) {
|
||||
common.ApiErrorMsg(c, "approval request expired")
|
||||
return
|
||||
}
|
||||
if approval.Status != agnetApprovalStatusPending {
|
||||
if approval.Status != agentApprovalStatusPending {
|
||||
common.ApiErrorMsg(c, "approval request is not pending")
|
||||
return
|
||||
}
|
||||
|
||||
var payload agnetDecisionPayload
|
||||
var payload agentDecisionPayload
|
||||
_ = common.DecodeJson(c.Request.Body, &payload)
|
||||
now := time.Now().UnixMilli()
|
||||
approval.Status = agnetApprovalStatusApproved
|
||||
approval.DecidedBy = agnetActorForUser(c.GetInt("id"))
|
||||
approval.Status = agentApprovalStatusApproved
|
||||
approval.DecidedBy = agentActorForUser(c.GetInt("id"))
|
||||
approval.DecisionReason = strings.TrimSpace(payload.Reason)
|
||||
approval.DecidedAt = now
|
||||
|
||||
var lease *model.AgnetCredentialLease
|
||||
var lease *model.AgentCredentialLease
|
||||
err := model.DB.Transaction(func(tx *gorm.DB) error {
|
||||
if err := tx.Save(&approval).Error; err != nil {
|
||||
return err
|
||||
@@ -210,7 +210,7 @@ func ApproveAgnetApprovalRequest(c *gin.Context) {
|
||||
if !approval.RequiresCredential {
|
||||
return nil
|
||||
}
|
||||
createdLease := model.AgnetCredentialLease{
|
||||
createdLease := model.AgentCredentialLease{
|
||||
LeaseID: "lease_" + common.GetUUID(),
|
||||
ApprovalID: approval.ApprovalID,
|
||||
UserId: approval.UserId,
|
||||
@@ -221,11 +221,11 @@ func ApproveAgnetApprovalRequest(c *gin.Context) {
|
||||
ResourceScope: approval.ResourceScope,
|
||||
TargetRole: approval.TargetRole,
|
||||
SecretRef: approval.SecretRef,
|
||||
Status: agnetLeaseStatusActive,
|
||||
Status: agentLeaseStatusActive,
|
||||
TTLSeconds: approval.TTLSeconds,
|
||||
ExpiresAt: now + int64(approval.TTLSeconds)*1000,
|
||||
}
|
||||
createdLease.CredentialRef = "lease://agnet/" + createdLease.LeaseID
|
||||
createdLease.CredentialRef = "lease://agent/" + createdLease.LeaseID
|
||||
if err := tx.Create(&createdLease).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -240,44 +240,44 @@ func ApproveAgnetApprovalRequest(c *gin.Context) {
|
||||
common.ApiError(c, err)
|
||||
return
|
||||
}
|
||||
recordAgnetApprovalAudit("approval.approved", &approval, nil, "ok", "")
|
||||
recordAgentApprovalAudit("approval.approved", &approval, nil, "ok", "")
|
||||
if lease != nil {
|
||||
recordAgnetApprovalAudit("credential_lease.created", &approval, lease, "ok", "")
|
||||
recordAgentApprovalAudit("credential_lease.created", &approval, lease, "ok", "")
|
||||
}
|
||||
syncAgnetRuntimeApprovalDecision(c, &approval, lease, agnetApprovalStatusApproved)
|
||||
common.ApiSuccess(c, agnetApprovalToResponse(approval, lease))
|
||||
syncAgentRuntimeApprovalDecision(c, &approval, lease, agentApprovalStatusApproved)
|
||||
common.ApiSuccess(c, agentApprovalToResponse(approval, lease))
|
||||
}
|
||||
|
||||
func RejectAgnetApprovalRequest(c *gin.Context) {
|
||||
approval, ok := findAgnetApprovalForUser(c)
|
||||
func RejectAgentApprovalRequest(c *gin.Context) {
|
||||
approval, ok := findAgentApprovalForUser(c)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
if expireAgnetApprovalIfNeeded(&approval) {
|
||||
if expireAgentApprovalIfNeeded(&approval) {
|
||||
common.ApiErrorMsg(c, "approval request expired")
|
||||
return
|
||||
}
|
||||
if approval.Status != agnetApprovalStatusPending {
|
||||
if approval.Status != agentApprovalStatusPending {
|
||||
common.ApiErrorMsg(c, "approval request is not pending")
|
||||
return
|
||||
}
|
||||
|
||||
var payload agnetDecisionPayload
|
||||
var payload agentDecisionPayload
|
||||
_ = common.DecodeJson(c.Request.Body, &payload)
|
||||
approval.Status = agnetApprovalStatusRejected
|
||||
approval.DecidedBy = agnetActorForUser(c.GetInt("id"))
|
||||
approval.Status = agentApprovalStatusRejected
|
||||
approval.DecidedBy = agentActorForUser(c.GetInt("id"))
|
||||
approval.DecisionReason = strings.TrimSpace(payload.Reason)
|
||||
approval.DecidedAt = time.Now().UnixMilli()
|
||||
if err := model.DB.Save(&approval).Error; err != nil {
|
||||
common.ApiError(c, err)
|
||||
return
|
||||
}
|
||||
recordAgnetApprovalAudit("approval.rejected", &approval, nil, "ok", "")
|
||||
syncAgnetRuntimeApprovalDecision(c, &approval, nil, agnetApprovalStatusRejected)
|
||||
common.ApiSuccess(c, agnetApprovalToResponse(approval, nil))
|
||||
recordAgentApprovalAudit("approval.rejected", &approval, nil, "ok", "")
|
||||
syncAgentRuntimeApprovalDecision(c, &approval, nil, agentApprovalStatusRejected)
|
||||
common.ApiSuccess(c, agentApprovalToResponse(approval, nil))
|
||||
}
|
||||
|
||||
func ListAgnetCredentialLeases(c *gin.Context) {
|
||||
func ListAgentCredentialLeases(c *gin.Context) {
|
||||
userID := c.GetInt("id")
|
||||
if userID <= 0 {
|
||||
common.ApiErrorMsg(c, "user authentication required")
|
||||
@@ -292,23 +292,23 @@ func ListAgnetCredentialLeases(c *gin.Context) {
|
||||
q = q.Where("deployment_id = ?", deploymentID)
|
||||
}
|
||||
|
||||
var leases []model.AgnetCredentialLease
|
||||
var leases []model.AgentCredentialLease
|
||||
if err := q.Order("created_at desc, id desc").Limit(200).Find(&leases).Error; err != nil {
|
||||
common.ApiError(c, err)
|
||||
return
|
||||
}
|
||||
items := make([]agnetCredentialLeaseResponse, 0, len(leases))
|
||||
items := make([]agentCredentialLeaseResponse, 0, len(leases))
|
||||
for i := range leases {
|
||||
expireAgnetCredentialLeaseIfNeeded(&leases[i])
|
||||
expireAgentCredentialLeaseIfNeeded(&leases[i])
|
||||
if statusFilter != "" && leases[i].Status != statusFilter {
|
||||
continue
|
||||
}
|
||||
items = append(items, agnetLeaseToResponse(leases[i]))
|
||||
items = append(items, agentLeaseToResponse(leases[i]))
|
||||
}
|
||||
common.ApiSuccess(c, gin.H{"items": items})
|
||||
}
|
||||
|
||||
func RevokeAgnetCredentialLease(c *gin.Context) {
|
||||
func RevokeAgentCredentialLease(c *gin.Context) {
|
||||
userID := c.GetInt("id")
|
||||
if userID <= 0 {
|
||||
common.ApiErrorMsg(c, "user authentication required")
|
||||
@@ -319,7 +319,7 @@ func RevokeAgnetCredentialLease(c *gin.Context) {
|
||||
common.ApiErrorMsg(c, "lease_id required")
|
||||
return
|
||||
}
|
||||
var lease model.AgnetCredentialLease
|
||||
var lease model.AgentCredentialLease
|
||||
if err := model.DB.Where("lease_id = ? AND user_id = ?", leaseID, userID).First(&lease).Error; err != nil {
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
common.ApiErrorMsg(c, "credential lease not found")
|
||||
@@ -328,28 +328,28 @@ func RevokeAgnetCredentialLease(c *gin.Context) {
|
||||
common.ApiError(c, err)
|
||||
return
|
||||
}
|
||||
if expireAgnetCredentialLeaseIfNeeded(&lease) {
|
||||
common.ApiSuccess(c, agnetLeaseToResponse(lease))
|
||||
if expireAgentCredentialLeaseIfNeeded(&lease) {
|
||||
common.ApiSuccess(c, agentLeaseToResponse(lease))
|
||||
return
|
||||
}
|
||||
if lease.Status != agnetLeaseStatusActive {
|
||||
if lease.Status != agentLeaseStatusActive {
|
||||
common.ApiErrorMsg(c, "credential lease is not active")
|
||||
return
|
||||
}
|
||||
lease.Status = agnetLeaseStatusRevoked
|
||||
lease.Status = agentLeaseStatusRevoked
|
||||
lease.RevokedAt = time.Now().UnixMilli()
|
||||
if err := model.DB.Save(&lease).Error; err != nil {
|
||||
common.ApiError(c, err)
|
||||
return
|
||||
}
|
||||
var approval model.AgnetApprovalRequest
|
||||
var approval model.AgentApprovalRequest
|
||||
if err := model.DB.Where("approval_id = ?", lease.ApprovalID).First(&approval).Error; err == nil {
|
||||
recordAgnetApprovalAudit("credential_lease.revoked", &approval, &lease, "ok", "")
|
||||
recordAgentApprovalAudit("credential_lease.revoked", &approval, &lease, "ok", "")
|
||||
}
|
||||
common.ApiSuccess(c, agnetLeaseToResponse(lease))
|
||||
common.ApiSuccess(c, agentLeaseToResponse(lease))
|
||||
}
|
||||
|
||||
func normalizeAgnetApprovalPayload(p agnetApprovalPayload) (agnetApprovalPayload, error) {
|
||||
func normalizeAgentApprovalPayload(p agentApprovalPayload) (agentApprovalPayload, error) {
|
||||
p.DeploymentID = strings.TrimSpace(p.DeploymentID)
|
||||
p.BindingScope = strings.TrimSpace(p.BindingScope)
|
||||
p.Operation = strings.TrimSpace(p.Operation)
|
||||
@@ -380,10 +380,10 @@ func normalizeAgnetApprovalPayload(p agnetApprovalPayload) (agnetApprovalPayload
|
||||
return p, errors.New("risk_level must be low, medium, high, or critical")
|
||||
}
|
||||
if p.TTLSeconds <= 0 {
|
||||
p.TTLSeconds = defaultAgnetApprovalTTLSeconds
|
||||
p.TTLSeconds = defaultAgentApprovalTTLSeconds
|
||||
}
|
||||
if p.TTLSeconds > maxAgnetApprovalTTLSeconds {
|
||||
p.TTLSeconds = maxAgnetApprovalTTLSeconds
|
||||
if p.TTLSeconds > maxAgentApprovalTTLSeconds {
|
||||
p.TTLSeconds = maxAgentApprovalTTLSeconds
|
||||
}
|
||||
if p.RequiresCredential {
|
||||
if p.SecretRef == "" {
|
||||
@@ -396,73 +396,73 @@ func normalizeAgnetApprovalPayload(p agnetApprovalPayload) (agnetApprovalPayload
|
||||
return p, nil
|
||||
}
|
||||
|
||||
func findAgnetApprovalForUser(c *gin.Context) (model.AgnetApprovalRequest, bool) {
|
||||
func findAgentApprovalForUser(c *gin.Context) (model.AgentApprovalRequest, bool) {
|
||||
userID := c.GetInt("id")
|
||||
if userID <= 0 {
|
||||
common.ApiErrorMsg(c, "user authentication required")
|
||||
return model.AgnetApprovalRequest{}, false
|
||||
return model.AgentApprovalRequest{}, false
|
||||
}
|
||||
approvalID := strings.TrimSpace(c.Param("approval_id"))
|
||||
if approvalID == "" {
|
||||
common.ApiErrorMsg(c, "approval_id required")
|
||||
return model.AgnetApprovalRequest{}, false
|
||||
return model.AgentApprovalRequest{}, false
|
||||
}
|
||||
var approval model.AgnetApprovalRequest
|
||||
var approval model.AgentApprovalRequest
|
||||
if err := model.DB.Where("approval_id = ? AND user_id = ?", approvalID, userID).First(&approval).Error; err != nil {
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
common.ApiErrorMsg(c, "approval request not found")
|
||||
return model.AgnetApprovalRequest{}, false
|
||||
return model.AgentApprovalRequest{}, false
|
||||
}
|
||||
common.ApiError(c, err)
|
||||
return model.AgnetApprovalRequest{}, false
|
||||
return model.AgentApprovalRequest{}, false
|
||||
}
|
||||
return approval, true
|
||||
}
|
||||
|
||||
func findAgnetCredentialLeaseByApproval(approvalID string) *model.AgnetCredentialLease {
|
||||
var lease model.AgnetCredentialLease
|
||||
func findAgentCredentialLeaseByApproval(approvalID string) *model.AgentCredentialLease {
|
||||
var lease model.AgentCredentialLease
|
||||
if err := model.DB.Where("approval_id = ?", approvalID).First(&lease).Error; err != nil {
|
||||
return nil
|
||||
}
|
||||
expireAgnetCredentialLeaseIfNeeded(&lease)
|
||||
expireAgentCredentialLeaseIfNeeded(&lease)
|
||||
return &lease
|
||||
}
|
||||
|
||||
func expireAgnetApprovalIfNeeded(approval *model.AgnetApprovalRequest) bool {
|
||||
if approval == nil || approval.Status != agnetApprovalStatusPending {
|
||||
func expireAgentApprovalIfNeeded(approval *model.AgentApprovalRequest) bool {
|
||||
if approval == nil || approval.Status != agentApprovalStatusPending {
|
||||
return false
|
||||
}
|
||||
if approval.ExpiresAt <= 0 || approval.ExpiresAt > time.Now().UnixMilli() {
|
||||
return false
|
||||
}
|
||||
approval.Status = agnetApprovalStatusExpired
|
||||
approval.Status = agentApprovalStatusExpired
|
||||
approval.DecidedAt = time.Now().UnixMilli()
|
||||
if err := model.DB.Save(approval).Error; err == nil {
|
||||
recordAgnetApprovalAudit("approval.expired", approval, nil, "ok", "")
|
||||
recordAgentApprovalAudit("approval.expired", approval, nil, "ok", "")
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func expireAgnetCredentialLeaseIfNeeded(lease *model.AgnetCredentialLease) bool {
|
||||
if lease == nil || lease.Status != agnetLeaseStatusActive {
|
||||
func expireAgentCredentialLeaseIfNeeded(lease *model.AgentCredentialLease) bool {
|
||||
if lease == nil || lease.Status != agentLeaseStatusActive {
|
||||
return false
|
||||
}
|
||||
if lease.ExpiresAt <= 0 || lease.ExpiresAt > time.Now().UnixMilli() {
|
||||
return false
|
||||
}
|
||||
lease.Status = agnetLeaseStatusExpired
|
||||
lease.Status = agentLeaseStatusExpired
|
||||
if err := model.DB.Save(lease).Error; err != nil {
|
||||
return false
|
||||
}
|
||||
var approval model.AgnetApprovalRequest
|
||||
var approval model.AgentApprovalRequest
|
||||
if err := model.DB.Where("approval_id = ?", lease.ApprovalID).First(&approval).Error; err == nil {
|
||||
recordAgnetApprovalAudit("credential_lease.expired", &approval, lease, "ok", "")
|
||||
recordAgentApprovalAudit("credential_lease.expired", &approval, lease, "ok", "")
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func agnetApprovalToResponse(approval model.AgnetApprovalRequest, lease *model.AgnetCredentialLease) agnetApprovalResponse {
|
||||
resp := agnetApprovalResponse{
|
||||
func agentApprovalToResponse(approval model.AgentApprovalRequest, lease *model.AgentCredentialLease) agentApprovalResponse {
|
||||
resp := agentApprovalResponse{
|
||||
ApprovalID: approval.ApprovalID,
|
||||
UserId: approval.UserId,
|
||||
DeploymentID: approval.DeploymentID,
|
||||
@@ -487,14 +487,14 @@ func agnetApprovalToResponse(approval model.AgnetApprovalRequest, lease *model.A
|
||||
UpdatedAt: approval.UpdatedAt,
|
||||
}
|
||||
if lease != nil {
|
||||
leaseResp := agnetLeaseToResponse(*lease)
|
||||
leaseResp := agentLeaseToResponse(*lease)
|
||||
resp.CredentialLease = &leaseResp
|
||||
}
|
||||
return resp
|
||||
}
|
||||
|
||||
func agnetLeaseToResponse(lease model.AgnetCredentialLease) agnetCredentialLeaseResponse {
|
||||
return agnetCredentialLeaseResponse{
|
||||
func agentLeaseToResponse(lease model.AgentCredentialLease) agentCredentialLeaseResponse {
|
||||
return agentCredentialLeaseResponse{
|
||||
LeaseID: lease.LeaseID,
|
||||
CredentialRef: lease.CredentialRef,
|
||||
ApprovalID: lease.ApprovalID,
|
||||
@@ -514,7 +514,7 @@ func agnetLeaseToResponse(lease model.AgnetCredentialLease) agnetCredentialLease
|
||||
}
|
||||
}
|
||||
|
||||
func recordAgnetApprovalAudit(event string, approval *model.AgnetApprovalRequest, lease *model.AgnetCredentialLease, result string, message string) {
|
||||
func recordAgentApprovalAudit(event string, approval *model.AgentApprovalRequest, lease *model.AgentCredentialLease, result string, message string) {
|
||||
if approval == nil {
|
||||
return
|
||||
}
|
||||
@@ -540,7 +540,7 @@ func recordAgnetApprovalAudit(event string, approval *model.AgnetApprovalRequest
|
||||
if raw, err := common.Marshal(details); err == nil {
|
||||
detailsJSON = string(raw)
|
||||
}
|
||||
model.InsertAgnetAuditEvent(&model.AgnetAuditEvent{
|
||||
model.InsertAgentAuditEvent(&model.AgentAuditEvent{
|
||||
EventID: "evt_" + common.GetUUID(),
|
||||
Event: event,
|
||||
Actor: "manager",
|
||||
@@ -555,6 +555,6 @@ func recordAgnetApprovalAudit(event string, approval *model.AgnetApprovalRequest
|
||||
})
|
||||
}
|
||||
|
||||
func agnetActorForUser(userID int) string {
|
||||
func agentActorForUser(userID int) string {
|
||||
return fmt.Sprintf("user:%d", userID)
|
||||
}
|
||||
+49
-49
@@ -17,7 +17,7 @@ import (
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
func setupAgnetApprovalTestDB(t *testing.T) *gorm.DB {
|
||||
func setupAgentApprovalTestDB(t *testing.T) *gorm.DB {
|
||||
t.Helper()
|
||||
gin.SetMode(gin.TestMode)
|
||||
common.UsingSQLite = true
|
||||
@@ -31,10 +31,10 @@ func setupAgnetApprovalTestDB(t *testing.T) *gorm.DB {
|
||||
model.DB = db
|
||||
model.LOG_DB = db
|
||||
require.NoError(t, db.AutoMigrate(
|
||||
&model.AgnetApprovalRequest{},
|
||||
&model.AgnetCredentialLease{},
|
||||
&model.AgnetAuditEvent{},
|
||||
&model.AgnetDeployment{},
|
||||
&model.AgentApprovalRequest{},
|
||||
&model.AgentCredentialLease{},
|
||||
&model.AgentAuditEvent{},
|
||||
&model.AgentDeployment{},
|
||||
))
|
||||
t.Cleanup(func() {
|
||||
sqlDB, err := db.DB()
|
||||
@@ -45,8 +45,8 @@ func setupAgnetApprovalTestDB(t *testing.T) *gorm.DB {
|
||||
return db
|
||||
}
|
||||
|
||||
func TestAgnetApprovalApproveNotifiesRuntimeDecision(t *testing.T) {
|
||||
db := setupAgnetApprovalTestDB(t)
|
||||
func TestAgentApprovalApproveNotifiesRuntimeDecision(t *testing.T) {
|
||||
db := setupAgentApprovalTestDB(t)
|
||||
var runtimeBody string
|
||||
var runtimeAuth string
|
||||
var runtimePath string
|
||||
@@ -59,12 +59,12 @@ func TestAgnetApprovalApproveNotifiesRuntimeDecision(t *testing.T) {
|
||||
_, _ = w.Write([]byte(`{"success":true,"data":{"status":"accepted"}}`))
|
||||
}))
|
||||
defer runtimeServer.Close()
|
||||
t.Setenv("AGNET_RUNTIME_ENABLED", "true")
|
||||
t.Setenv("AGNET_RUNTIME_BASE_URL", runtimeServer.URL)
|
||||
t.Setenv("AGNET_RUNTIME_SERVICE_TOKEN", "runtime-token")
|
||||
t.Setenv("AGNET_RUNTIME_APPROVAL_DECISION_PATH", "/api/swarms/{swarm_id}/approvals/{approval_id}")
|
||||
t.Setenv("AGENT_RUNTIME_ENABLED", "true")
|
||||
t.Setenv("AGENT_RUNTIME_BASE_URL", runtimeServer.URL)
|
||||
t.Setenv("AGENT_RUNTIME_SERVICE_TOKEN", "runtime-token")
|
||||
t.Setenv("AGENT_RUNTIME_APPROVAL_DECISION_PATH", "/api/swarms/{swarm_id}/approvals/{approval_id}")
|
||||
|
||||
require.NoError(t, db.Create(&model.AgnetDeployment{
|
||||
require.NoError(t, db.Create(&model.AgentDeployment{
|
||||
DeploymentID: "dep_runtime_approval",
|
||||
UserID: "7",
|
||||
BindingScope: "project-alpha",
|
||||
@@ -90,8 +90,8 @@ func TestAgnetApprovalApproveNotifiesRuntimeDecision(t *testing.T) {
|
||||
"ttl_seconds":600
|
||||
}`
|
||||
|
||||
_, approvalID := createAgnetApprovalForTest(t, 7, body)
|
||||
w := performAgnetApprovalRequest(ApproveAgnetApprovalRequest, 7, http.MethodPost, "/approvals/:approval_id/approve", "/approvals/"+approvalID+"/approve", `{"reason":"允许写入"}`)
|
||||
_, approvalID := createAgentApprovalForTest(t, 7, body)
|
||||
w := performAgentApprovalRequest(ApproveAgentApprovalRequest, 7, http.MethodPost, "/approvals/:approval_id/approve", "/approvals/"+approvalID+"/approve", `{"reason":"允许写入"}`)
|
||||
require.Equal(t, http.StatusOK, w.Code)
|
||||
require.Equal(t, "/api/swarms/swarm-runtime-1/approvals/"+approvalID, runtimePath)
|
||||
require.Equal(t, "Bearer runtime-token", runtimeAuth)
|
||||
@@ -100,16 +100,16 @@ func TestAgnetApprovalApproveNotifiesRuntimeDecision(t *testing.T) {
|
||||
require.Contains(t, runtimeBody, `"manager_deployment_id":"dep_runtime_approval"`)
|
||||
require.Contains(t, runtimeBody, `"runtime_deployment_id":"runtime-dep-1"`)
|
||||
require.Contains(t, runtimeBody, `"swarm_id":"swarm-runtime-1"`)
|
||||
require.Contains(t, runtimeBody, `"credential_ref":"lease://agnet/`)
|
||||
require.Contains(t, runtimeBody, `"credential_ref":"lease://agent/`)
|
||||
require.NotContains(t, runtimeBody, "azkv://")
|
||||
|
||||
var auditRows []model.AgnetAuditEvent
|
||||
var auditRows []model.AgentAuditEvent
|
||||
require.NoError(t, db.Where("deployment_id = ?", "dep_runtime_approval").Order("id asc").Find(&auditRows).Error)
|
||||
require.NotEmpty(t, auditRows)
|
||||
require.Equal(t, "runtime.approval_decision.accepted", auditRows[len(auditRows)-1].Event)
|
||||
}
|
||||
|
||||
func performAgnetApprovalRequest(handler gin.HandlerFunc, userID int, method string, routePath string, requestPath string, body string) *httptest.ResponseRecorder {
|
||||
func performAgentApprovalRequest(handler gin.HandlerFunc, userID int, method string, routePath string, requestPath string, body string) *httptest.ResponseRecorder {
|
||||
r := gin.New()
|
||||
r.Handle(method, routePath, func(c *gin.Context) {
|
||||
c.Set("id", userID)
|
||||
@@ -122,18 +122,18 @@ func performAgnetApprovalRequest(handler gin.HandlerFunc, userID int, method str
|
||||
return w
|
||||
}
|
||||
|
||||
func decodeAgnetApprovalEnvelope(t *testing.T, w *httptest.ResponseRecorder) map[string]any {
|
||||
func decodeAgentApprovalEnvelope(t *testing.T, w *httptest.ResponseRecorder) map[string]any {
|
||||
t.Helper()
|
||||
var envelope map[string]any
|
||||
require.NoError(t, common.Unmarshal(w.Body.Bytes(), &envelope))
|
||||
return envelope
|
||||
}
|
||||
|
||||
func createAgnetApprovalForTest(t *testing.T, userID int, body string) (map[string]any, string) {
|
||||
func createAgentApprovalForTest(t *testing.T, userID int, body string) (map[string]any, string) {
|
||||
t.Helper()
|
||||
w := performAgnetApprovalRequest(CreateAgnetApprovalRequest, userID, http.MethodPost, "/approvals", "/approvals", body)
|
||||
w := performAgentApprovalRequest(CreateAgentApprovalRequest, userID, http.MethodPost, "/approvals", "/approvals", body)
|
||||
require.Equal(t, http.StatusOK, w.Code)
|
||||
envelope := decodeAgnetApprovalEnvelope(t, w)
|
||||
envelope := decodeAgentApprovalEnvelope(t, w)
|
||||
require.Equal(t, true, envelope["success"])
|
||||
data := envelope["data"].(map[string]any)
|
||||
approvalID := data["approval_id"].(string)
|
||||
@@ -141,8 +141,8 @@ func createAgnetApprovalForTest(t *testing.T, userID int, body string) (map[stri
|
||||
return data, approvalID
|
||||
}
|
||||
|
||||
func TestAgnetApprovalApproveCreatesShortLivedLeaseAndAudit(t *testing.T) {
|
||||
db := setupAgnetApprovalTestDB(t)
|
||||
func TestAgentApprovalApproveCreatesShortLivedLeaseAndAudit(t *testing.T) {
|
||||
db := setupAgentApprovalTestDB(t)
|
||||
body := `{
|
||||
"deployment_id":"dep_approval_1",
|
||||
"binding_scope":"project-alpha",
|
||||
@@ -158,27 +158,27 @@ func TestAgnetApprovalApproveCreatesShortLivedLeaseAndAudit(t *testing.T) {
|
||||
"reason":"需要写入功能分支"
|
||||
}`
|
||||
|
||||
_, approvalID := createAgnetApprovalForTest(t, 7, body)
|
||||
w := performAgnetApprovalRequest(ApproveAgnetApprovalRequest, 7, http.MethodPost, "/approvals/:approval_id/approve", "/approvals/"+approvalID+"/approve", `{"reason":"允许本次任务"}`)
|
||||
_, approvalID := createAgentApprovalForTest(t, 7, body)
|
||||
w := performAgentApprovalRequest(ApproveAgentApprovalRequest, 7, http.MethodPost, "/approvals/:approval_id/approve", "/approvals/"+approvalID+"/approve", `{"reason":"允许本次任务"}`)
|
||||
require.Equal(t, http.StatusOK, w.Code)
|
||||
envelope := decodeAgnetApprovalEnvelope(t, w)
|
||||
envelope := decodeAgentApprovalEnvelope(t, w)
|
||||
require.Equal(t, true, envelope["success"])
|
||||
data := envelope["data"].(map[string]any)
|
||||
require.Equal(t, "approved", data["status"])
|
||||
lease := data["credential_lease"].(map[string]any)
|
||||
require.Equal(t, "active", lease["status"])
|
||||
require.Contains(t, lease["credential_ref"], "lease://agnet/")
|
||||
require.Contains(t, lease["credential_ref"], "lease://agent/")
|
||||
require.NotContains(t, w.Body.String(), "do-not-store")
|
||||
require.NotContains(t, w.Body.String(), "azkv://", "API responses must not expose internal Key Vault secret_ref")
|
||||
|
||||
var storedLease model.AgnetCredentialLease
|
||||
var storedLease model.AgentCredentialLease
|
||||
require.NoError(t, db.Where("approval_id = ?", approvalID).First(&storedLease).Error)
|
||||
require.Equal(t, "azkv://heicode-kv.vault.azure.net/secrets/repo-main", storedLease.SecretRef)
|
||||
require.Equal(t, "active", storedLease.Status)
|
||||
require.Greater(t, storedLease.ExpiresAt, storedLease.CreatedAt)
|
||||
require.NotContains(t, storedLease.CredentialRef, "azkv://")
|
||||
|
||||
var auditRows []model.AgnetAuditEvent
|
||||
var auditRows []model.AgentAuditEvent
|
||||
require.NoError(t, db.Order("id asc").Find(&auditRows).Error)
|
||||
require.Len(t, auditRows, 3)
|
||||
require.Equal(t, "approval.requested", auditRows[0].Event)
|
||||
@@ -187,8 +187,8 @@ func TestAgnetApprovalApproveCreatesShortLivedLeaseAndAudit(t *testing.T) {
|
||||
require.NotContains(t, auditRows[2].DetailsJSON, "repo-main-secret-value")
|
||||
}
|
||||
|
||||
func TestAgnetApprovalRejectDoesNotCreateLease(t *testing.T) {
|
||||
db := setupAgnetApprovalTestDB(t)
|
||||
func TestAgentApprovalRejectDoesNotCreateLease(t *testing.T) {
|
||||
db := setupAgentApprovalTestDB(t)
|
||||
body := `{
|
||||
"deployment_id":"dep_approval_2",
|
||||
"binding_scope":"project-alpha",
|
||||
@@ -203,21 +203,21 @@ func TestAgnetApprovalRejectDoesNotCreateLease(t *testing.T) {
|
||||
"ttl_seconds":300
|
||||
}`
|
||||
|
||||
_, approvalID := createAgnetApprovalForTest(t, 8, body)
|
||||
w := performAgnetApprovalRequest(RejectAgnetApprovalRequest, 8, http.MethodPost, "/approvals/:approval_id/reject", "/approvals/"+approvalID+"/reject", `{"reason":"风险过高"}`)
|
||||
_, approvalID := createAgentApprovalForTest(t, 8, body)
|
||||
w := performAgentApprovalRequest(RejectAgentApprovalRequest, 8, http.MethodPost, "/approvals/:approval_id/reject", "/approvals/"+approvalID+"/reject", `{"reason":"风险过高"}`)
|
||||
require.Equal(t, http.StatusOK, w.Code)
|
||||
envelope := decodeAgnetApprovalEnvelope(t, w)
|
||||
envelope := decodeAgentApprovalEnvelope(t, w)
|
||||
require.Equal(t, true, envelope["success"])
|
||||
data := envelope["data"].(map[string]any)
|
||||
require.Equal(t, "rejected", data["status"])
|
||||
|
||||
var leases int64
|
||||
require.NoError(t, db.Model(&model.AgnetCredentialLease{}).Count(&leases).Error)
|
||||
require.NoError(t, db.Model(&model.AgentCredentialLease{}).Count(&leases).Error)
|
||||
require.Equal(t, int64(0), leases)
|
||||
}
|
||||
|
||||
func TestAgnetApprovalExpiredRequestCannotBeApproved(t *testing.T) {
|
||||
db := setupAgnetApprovalTestDB(t)
|
||||
func TestAgentApprovalExpiredRequestCannotBeApproved(t *testing.T) {
|
||||
db := setupAgentApprovalTestDB(t)
|
||||
body := `{
|
||||
"deployment_id":"dep_approval_3",
|
||||
"binding_scope":"project-alpha",
|
||||
@@ -232,24 +232,24 @@ func TestAgnetApprovalExpiredRequestCannotBeApproved(t *testing.T) {
|
||||
"ttl_seconds":1
|
||||
}`
|
||||
|
||||
_, approvalID := createAgnetApprovalForTest(t, 9, body)
|
||||
require.NoError(t, db.Model(&model.AgnetApprovalRequest{}).Where("approval_id = ?", approvalID).Updates(map[string]any{
|
||||
_, approvalID := createAgentApprovalForTest(t, 9, body)
|
||||
require.NoError(t, db.Model(&model.AgentApprovalRequest{}).Where("approval_id = ?", approvalID).Updates(map[string]any{
|
||||
"expires_at": 1,
|
||||
}).Error)
|
||||
|
||||
w := performAgnetApprovalRequest(ApproveAgnetApprovalRequest, 9, http.MethodPost, "/approvals/:approval_id/approve", "/approvals/"+approvalID+"/approve", `{}`)
|
||||
w := performAgentApprovalRequest(ApproveAgentApprovalRequest, 9, http.MethodPost, "/approvals/:approval_id/approve", "/approvals/"+approvalID+"/approve", `{}`)
|
||||
require.Equal(t, http.StatusOK, w.Code)
|
||||
envelope := decodeAgnetApprovalEnvelope(t, w)
|
||||
envelope := decodeAgentApprovalEnvelope(t, w)
|
||||
require.Equal(t, false, envelope["success"])
|
||||
require.Contains(t, envelope["message"], "approval request expired")
|
||||
|
||||
var approval model.AgnetApprovalRequest
|
||||
var approval model.AgentApprovalRequest
|
||||
require.NoError(t, db.Where("approval_id = ?", approvalID).First(&approval).Error)
|
||||
require.Equal(t, "expired", approval.Status)
|
||||
}
|
||||
|
||||
func TestAgnetCredentialLeaseCanBeRevoked(t *testing.T) {
|
||||
db := setupAgnetApprovalTestDB(t)
|
||||
func TestAgentCredentialLeaseCanBeRevoked(t *testing.T) {
|
||||
db := setupAgentApprovalTestDB(t)
|
||||
body := `{
|
||||
"deployment_id":"dep_approval_4",
|
||||
"binding_scope":"project-alpha",
|
||||
@@ -264,16 +264,16 @@ func TestAgnetCredentialLeaseCanBeRevoked(t *testing.T) {
|
||||
"ttl_seconds":600
|
||||
}`
|
||||
|
||||
_, approvalID := createAgnetApprovalForTest(t, 10, body)
|
||||
w := performAgnetApprovalRequest(ApproveAgnetApprovalRequest, 10, http.MethodPost, "/approvals/:approval_id/approve", "/approvals/"+approvalID+"/approve", `{}`)
|
||||
_, approvalID := createAgentApprovalForTest(t, 10, body)
|
||||
w := performAgentApprovalRequest(ApproveAgentApprovalRequest, 10, http.MethodPost, "/approvals/:approval_id/approve", "/approvals/"+approvalID+"/approve", `{}`)
|
||||
require.Equal(t, http.StatusOK, w.Code)
|
||||
var storedLease model.AgnetCredentialLease
|
||||
var storedLease model.AgentCredentialLease
|
||||
require.NoError(t, db.Where("approval_id = ?", approvalID).First(&storedLease).Error)
|
||||
|
||||
revokePath := "/credential-leases/" + storedLease.LeaseID + "/revoke"
|
||||
w = performAgnetApprovalRequest(RevokeAgnetCredentialLease, 10, http.MethodPost, "/credential-leases/:lease_id/revoke", revokePath, `{"reason":"任务结束"}`)
|
||||
w = performAgentApprovalRequest(RevokeAgentCredentialLease, 10, http.MethodPost, "/credential-leases/:lease_id/revoke", revokePath, `{"reason":"任务结束"}`)
|
||||
require.Equal(t, http.StatusOK, w.Code)
|
||||
envelope := decodeAgnetApprovalEnvelope(t, w)
|
||||
envelope := decodeAgentApprovalEnvelope(t, w)
|
||||
require.Equal(t, true, envelope["success"])
|
||||
data := envelope["data"].(map[string]any)
|
||||
require.Equal(t, "revoked", data["status"])
|
||||
@@ -19,7 +19,7 @@ import (
|
||||
"github.com/heicode/manager/model"
|
||||
)
|
||||
|
||||
type agnetCallbackEnvelope struct {
|
||||
type agentCallbackEnvelope struct {
|
||||
EventID string `json:"event_id"`
|
||||
IdempotencyKey string `json:"idempotency_key"`
|
||||
EventType string `json:"event_type"`
|
||||
@@ -32,10 +32,10 @@ type agnetCallbackEnvelope struct {
|
||||
Source string `json:"source"`
|
||||
Metadata map[string]any `json:"metadata"`
|
||||
Payload map[string]any `json:"payload"`
|
||||
Artifact agnetArtifactPayload `json:"artifact"`
|
||||
Artifact agentArtifactPayload `json:"artifact"`
|
||||
}
|
||||
|
||||
type agnetArtifactPayload struct {
|
||||
type agentArtifactPayload struct {
|
||||
ArtifactID string `json:"artifact_id"`
|
||||
ArtifactType string `json:"artifact_type"`
|
||||
Title string `json:"title"`
|
||||
@@ -45,8 +45,8 @@ type agnetArtifactPayload struct {
|
||||
Metadata map[string]any `json:"metadata"`
|
||||
}
|
||||
|
||||
func agnetCallbackTokenFromRequest(c *gin.Context) string {
|
||||
if token := strings.TrimSpace(c.GetHeader("X-Agnet-Service-Token")); token != "" {
|
||||
func agentCallbackTokenFromRequest(c *gin.Context) string {
|
||||
if token := strings.TrimSpace(c.GetHeader("X-Agent-Service-Token")); token != "" {
|
||||
return token
|
||||
}
|
||||
auth := strings.TrimSpace(c.GetHeader("Authorization"))
|
||||
@@ -56,25 +56,25 @@ func agnetCallbackTokenFromRequest(c *gin.Context) string {
|
||||
return ""
|
||||
}
|
||||
|
||||
func agnetCallbackSigningSecret() string {
|
||||
if secret := strings.TrimSpace(common.GetEnvOrDefaultString("AGNET_CALLBACK_SIGNING_SECRET", "")); secret != "" {
|
||||
func agentCallbackSigningSecret() string {
|
||||
if secret := strings.TrimSpace(common.GetEnvOrDefaultString("AGENT_CALLBACK_SIGNING_SECRET", "")); secret != "" {
|
||||
return secret
|
||||
}
|
||||
secretRef := firstNonEmpty(
|
||||
common.GetEnvOrDefaultString("AGNET_CALLBACK_SIGNING_SECRET_REF", ""),
|
||||
common.GetEnvOrDefaultString("AGNET_RUNTIME_CALLBACK_SIGNING_SECRET_REF", ""),
|
||||
common.GetEnvOrDefaultString("AGENT_CALLBACK_SIGNING_SECRET_REF", ""),
|
||||
common.GetEnvOrDefaultString("AGENT_RUNTIME_CALLBACK_SIGNING_SECRET_REF", ""),
|
||||
)
|
||||
if secretRef == "" {
|
||||
return ""
|
||||
}
|
||||
client, err := newSecretStoreClientFromEnv()
|
||||
if err != nil {
|
||||
common.SysLog("agnetCallbackSigningSecret: " + err.Error())
|
||||
common.SysLog("agentCallbackSigningSecret: " + err.Error())
|
||||
return ""
|
||||
}
|
||||
data, err := client.getJSONSecret(secretRef)
|
||||
if err != nil {
|
||||
common.SysLog("agnetCallbackSigningSecret: " + err.Error())
|
||||
common.SysLog("agentCallbackSigningSecret: " + err.Error())
|
||||
return ""
|
||||
}
|
||||
for _, key := range []string{"callback_signing_secret", "signing_secret", "secret", "value"} {
|
||||
@@ -82,38 +82,38 @@ func agnetCallbackSigningSecret() string {
|
||||
return value
|
||||
}
|
||||
}
|
||||
common.SysLog("agnetCallbackSigningSecret: signing secret is missing from Azure Key Vault payload")
|
||||
common.SysLog("agentCallbackSigningSecret: signing secret is missing from Azure Key Vault payload")
|
||||
return ""
|
||||
}
|
||||
|
||||
func agnetCallbackSignatureTolerance() time.Duration {
|
||||
seconds := common.GetEnvOrDefault("AGNET_CALLBACK_SIGNATURE_TOLERANCE_SECONDS", 300)
|
||||
func agentCallbackSignatureTolerance() time.Duration {
|
||||
seconds := common.GetEnvOrDefault("AGENT_CALLBACK_SIGNATURE_TOLERANCE_SECONDS", 300)
|
||||
if seconds <= 0 {
|
||||
seconds = 300
|
||||
}
|
||||
return time.Duration(seconds) * time.Second
|
||||
}
|
||||
|
||||
func validateAgnetCallbackHMAC(c *gin.Context, rawBody []byte, eventID string) (bool, bool) {
|
||||
secret := agnetCallbackSigningSecret()
|
||||
func validateAgentCallbackHMAC(c *gin.Context, rawBody []byte, eventID string) (bool, bool) {
|
||||
secret := agentCallbackSigningSecret()
|
||||
if secret == "" {
|
||||
return false, false
|
||||
}
|
||||
timestamp := strings.TrimSpace(c.GetHeader("X-Agnet-Timestamp"))
|
||||
signature := strings.TrimSpace(c.GetHeader("X-Agnet-Signature"))
|
||||
timestamp := strings.TrimSpace(c.GetHeader("X-Agent-Timestamp"))
|
||||
signature := strings.TrimSpace(c.GetHeader("X-Agent-Signature"))
|
||||
if timestamp == "" || signature == "" || eventID == "" {
|
||||
return false, false
|
||||
}
|
||||
tsMs, err := strconv.ParseInt(timestamp, 10, 64)
|
||||
if err != nil {
|
||||
agnetError(c, "CALLBACK_UNAUTHORIZED", "invalid callback timestamp")
|
||||
agentError(c, "CALLBACK_UNAUTHORIZED", "invalid callback timestamp")
|
||||
return true, false
|
||||
}
|
||||
now := time.Now()
|
||||
eventTime := time.UnixMilli(tsMs)
|
||||
tolerance := agnetCallbackSignatureTolerance()
|
||||
tolerance := agentCallbackSignatureTolerance()
|
||||
if eventTime.Before(now.Add(-tolerance)) || eventTime.After(now.Add(tolerance)) {
|
||||
agnetError(c, "CALLBACK_UNAUTHORIZED", "callback timestamp outside allowed window")
|
||||
agentError(c, "CALLBACK_UNAUTHORIZED", "callback timestamp outside allowed window")
|
||||
return true, false
|
||||
}
|
||||
payload := timestamp + "." + eventID + "." + string(rawBody)
|
||||
@@ -121,32 +121,32 @@ func validateAgnetCallbackHMAC(c *gin.Context, rawBody []byte, eventID string) (
|
||||
mac.Write([]byte(payload))
|
||||
expected := "sha256=" + hex.EncodeToString(mac.Sum(nil))
|
||||
if !hmac.Equal([]byte(expected), []byte(signature)) {
|
||||
agnetError(c, "CALLBACK_UNAUTHORIZED", "invalid callback signature")
|
||||
agentError(c, "CALLBACK_UNAUTHORIZED", "invalid callback signature")
|
||||
return true, false
|
||||
}
|
||||
return true, true
|
||||
}
|
||||
|
||||
func validateAgnetCallbackAuth(c *gin.Context, rawBody []byte, eventID string) bool {
|
||||
if common.GetEnvOrDefaultBool("AGNET_CALLBACK_AUTH_DISABLED", false) {
|
||||
func validateAgentCallbackAuth(c *gin.Context, rawBody []byte, eventID string) bool {
|
||||
if common.GetEnvOrDefaultBool("AGENT_CALLBACK_AUTH_DISABLED", false) {
|
||||
return true
|
||||
}
|
||||
if attempted, ok := validateAgnetCallbackHMAC(c, rawBody, eventID); attempted {
|
||||
if attempted, ok := validateAgentCallbackHMAC(c, rawBody, eventID); attempted {
|
||||
return ok
|
||||
}
|
||||
expected := strings.TrimSpace(os.Getenv("AGNET_CALLBACK_TOKEN"))
|
||||
expected := strings.TrimSpace(os.Getenv("AGENT_CALLBACK_TOKEN"))
|
||||
if expected == "" {
|
||||
agnetError(c, "CALLBACK_UNAUTHORIZED", "callback token is not configured")
|
||||
agentError(c, "CALLBACK_UNAUTHORIZED", "callback token is not configured")
|
||||
return false
|
||||
}
|
||||
if agnetCallbackTokenFromRequest(c) != expected {
|
||||
agnetError(c, "CALLBACK_UNAUTHORIZED", "invalid callback service token")
|
||||
if agentCallbackTokenFromRequest(c) != expected {
|
||||
agentError(c, "CALLBACK_UNAUTHORIZED", "invalid callback service token")
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func agnetCallbackHasPlaintextSecret(payload agnetCallbackEnvelope) bool {
|
||||
func agentCallbackHasPlaintextSecret(payload agentCallbackEnvelope) bool {
|
||||
if containsPlaintextSecret(payload.Metadata) || containsPlaintextSecret(payload.Payload) || containsPlaintextSecret(payload.Artifact.Metadata) {
|
||||
return true
|
||||
}
|
||||
@@ -158,43 +158,43 @@ func agnetCallbackHasPlaintextSecret(payload agnetCallbackEnvelope) bool {
|
||||
return containsPlaintextSecret(asMap)
|
||||
}
|
||||
|
||||
func agnetCallbackDeploymentContext(deploymentID string, swarmID string) (agnetDeploymentRecord, bool) {
|
||||
func agentCallbackDeploymentContext(deploymentID string, swarmID string) (agentDeploymentRecord, bool) {
|
||||
deploymentID = strings.TrimSpace(deploymentID)
|
||||
swarmID = strings.TrimSpace(swarmID)
|
||||
if deploymentID != "" {
|
||||
if record, ok := findAgnetDeploymentRecord(deploymentID); ok {
|
||||
if record, ok := findAgentDeploymentRecord(deploymentID); ok {
|
||||
return record, true
|
||||
}
|
||||
}
|
||||
runtimeID := firstNonEmpty(swarmID, deploymentID)
|
||||
if runtimeID == "" {
|
||||
return agnetDeploymentRecord{}, false
|
||||
return agentDeploymentRecord{}, false
|
||||
}
|
||||
|
||||
agnetMu.RLock()
|
||||
for _, record := range agnetDeployments {
|
||||
agentMu.RLock()
|
||||
for _, record := range agentDeployments {
|
||||
if strings.TrimSpace(record.RuntimeSwarmID) == runtimeID || strings.TrimSpace(record.RuntimeDeploymentID) == runtimeID {
|
||||
agnetMu.RUnlock()
|
||||
agentMu.RUnlock()
|
||||
return record, true
|
||||
}
|
||||
}
|
||||
agnetMu.RUnlock()
|
||||
agentMu.RUnlock()
|
||||
|
||||
if model.DB == nil {
|
||||
return agnetDeploymentRecord{}, false
|
||||
return agentDeploymentRecord{}, false
|
||||
}
|
||||
var row model.AgnetDeployment
|
||||
var row model.AgentDeployment
|
||||
if err := model.DB.Where("runtime_swarm_id = ? OR runtime_deployment_id = ?", runtimeID, runtimeID).First(&row).Error; err != nil {
|
||||
return agnetDeploymentRecord{}, false
|
||||
return agentDeploymentRecord{}, false
|
||||
}
|
||||
record, err := agnetDeploymentModelToRecord(row)
|
||||
record, err := agentDeploymentModelToRecord(row)
|
||||
if err != nil {
|
||||
common.SysLog("agnetCallbackDeploymentContext: " + err.Error())
|
||||
return agnetDeploymentRecord{}, false
|
||||
common.SysLog("agentCallbackDeploymentContext: " + err.Error())
|
||||
return agentDeploymentRecord{}, false
|
||||
}
|
||||
agnetMu.Lock()
|
||||
agnetDeployments[record.DeploymentID] = record
|
||||
agnetMu.Unlock()
|
||||
agentMu.Lock()
|
||||
agentDeployments[record.DeploymentID] = record
|
||||
agentMu.Unlock()
|
||||
return record, true
|
||||
}
|
||||
|
||||
@@ -232,7 +232,7 @@ func callbackMapValue(values map[string]any, key string) map[string]any {
|
||||
return nil
|
||||
}
|
||||
|
||||
func agnetCallbackEventRuntimeState(eventType string, payload map[string]any) string {
|
||||
func agentCallbackEventRuntimeState(eventType string, payload map[string]any) string {
|
||||
if state := callbackStringValue(payload, "status"); state != "" {
|
||||
return state
|
||||
}
|
||||
@@ -248,7 +248,7 @@ func agnetCallbackEventRuntimeState(eventType string, payload map[string]any) st
|
||||
}
|
||||
}
|
||||
|
||||
func upsertAgnetCallbackAgentInstance(record *agnetDeploymentRecord, payload agnetCallbackEnvelope, phase string, runtimeState string) bool {
|
||||
func upsertAgentCallbackAgentInstance(record *agentDeploymentRecord, payload agentCallbackEnvelope, phase string, runtimeState string) bool {
|
||||
if record == nil {
|
||||
return false
|
||||
}
|
||||
@@ -284,7 +284,7 @@ func upsertAgnetCallbackAgentInstance(record *agnetDeploymentRecord, payload agn
|
||||
return true
|
||||
}
|
||||
}
|
||||
record.AgentInstances = append(record.AgentInstances, agnetAgentInstance{
|
||||
record.AgentInstances = append(record.AgentInstances, agentAgentInstance{
|
||||
InstanceID: firstNonEmpty(instanceID, "agi_"+common.GetUUID()[:12]),
|
||||
Role: role,
|
||||
Phase: firstNonEmpty(phase, record.Phase),
|
||||
@@ -294,7 +294,7 @@ func upsertAgnetCallbackAgentInstance(record *agnetDeploymentRecord, payload agn
|
||||
return true
|
||||
}
|
||||
|
||||
func applyAgnetCallbackDeploymentState(payload agnetCallbackEnvelope, record agnetDeploymentRecord) (agnetDeploymentRecord, bool) {
|
||||
func applyAgentCallbackDeploymentState(payload agentCallbackEnvelope, record agentDeploymentRecord) (agentDeploymentRecord, bool) {
|
||||
if strings.TrimSpace(record.DeploymentID) == "" {
|
||||
return record, false
|
||||
}
|
||||
@@ -342,25 +342,25 @@ func applyAgnetCallbackDeploymentState(payload agnetCallbackEnvelope, record agn
|
||||
}
|
||||
case "agent.started", "agent.completed", "agent.crashed":
|
||||
phase := firstNonEmpty(callbackStringValue(source, "stage"), callbackStringValue(source, "phase"), record.Phase)
|
||||
runtimeState := agnetCallbackEventRuntimeState(payload.EventType, source)
|
||||
if upsertAgnetCallbackAgentInstance(&record, payload, phase, runtimeState) {
|
||||
runtimeState := agentCallbackEventRuntimeState(payload.EventType, source)
|
||||
if upsertAgentCallbackAgentInstance(&record, payload, phase, runtimeState) {
|
||||
changed = true
|
||||
}
|
||||
}
|
||||
if !changed {
|
||||
return record, false
|
||||
}
|
||||
record.UpdatedAt = firstNonEmpty(strings.TrimSpace(payload.OccurredAt), agnetNow())
|
||||
agnetMu.Lock()
|
||||
agnetDeployments[record.DeploymentID] = record
|
||||
agnetMu.Unlock()
|
||||
if err := updateAgnetDeploymentRecord(record); err != nil {
|
||||
common.SysLog("applyAgnetCallbackDeploymentState: " + err.Error())
|
||||
record.UpdatedAt = firstNonEmpty(strings.TrimSpace(payload.OccurredAt), agentNow())
|
||||
agentMu.Lock()
|
||||
agentDeployments[record.DeploymentID] = record
|
||||
agentMu.Unlock()
|
||||
if err := updateAgentDeploymentRecord(record); err != nil {
|
||||
common.SysLog("applyAgentCallbackDeploymentState: " + err.Error())
|
||||
}
|
||||
return record, true
|
||||
}
|
||||
|
||||
func normalizeCallbackArtifact(payload *agnetCallbackEnvelope) {
|
||||
func normalizeCallbackArtifact(payload *agentCallbackEnvelope) {
|
||||
if payload == nil || strings.TrimSpace(payload.Artifact.ArtifactID) != "" {
|
||||
return
|
||||
}
|
||||
@@ -371,7 +371,7 @@ func normalizeCallbackArtifact(payload *agnetCallbackEnvelope) {
|
||||
if len(source) == 0 {
|
||||
return
|
||||
}
|
||||
payload.Artifact = agnetArtifactPayload{
|
||||
payload.Artifact = agentArtifactPayload{
|
||||
ArtifactID: callbackStringValue(source, "artifact_id"),
|
||||
ArtifactType: callbackStringValue(source, "artifact_type"),
|
||||
Title: callbackStringValue(source, "title"),
|
||||
@@ -399,7 +399,7 @@ func normalizeCallbackArtifact(payload *agnetCallbackEnvelope) {
|
||||
}
|
||||
}
|
||||
|
||||
func persistAgnetArtifactFromCallback(payload agnetCallbackEnvelope, record agnetDeploymentRecord) error {
|
||||
func persistAgentArtifactFromCallback(payload agentCallbackEnvelope, record agentDeploymentRecord) error {
|
||||
artifact := payload.Artifact
|
||||
if strings.TrimSpace(artifact.ArtifactID) == "" {
|
||||
return nil
|
||||
@@ -410,7 +410,7 @@ func persistAgnetArtifactFromCallback(payload agnetCallbackEnvelope, record agne
|
||||
metadataJSON = string(data)
|
||||
}
|
||||
}
|
||||
return model.UpsertAgnetArtifact(&model.AgnetArtifact{
|
||||
return model.UpsertAgentArtifact(&model.AgentArtifact{
|
||||
ArtifactID: strings.TrimSpace(artifact.ArtifactID),
|
||||
DeploymentID: strings.TrimSpace(payload.DeploymentID),
|
||||
TaskID: strings.TrimSpace(payload.TaskID),
|
||||
@@ -460,7 +460,7 @@ func callbackIntValue(values map[string]any, key string) int {
|
||||
}
|
||||
}
|
||||
|
||||
var agnetCallbackEventRequiredFields = map[string][]string{
|
||||
var agentCallbackEventRequiredFields = map[string][]string{
|
||||
"deployment.status_changed": {"status"},
|
||||
"phase.changed": {"stage", "checkpoint"},
|
||||
"agent.started": {"agent_role"},
|
||||
@@ -486,7 +486,7 @@ var agnetCallbackEventRequiredFields = map[string][]string{
|
||||
"budget.alert": {"threshold_pct"},
|
||||
}
|
||||
|
||||
var agnetCallbackEventCategories = map[string]string{
|
||||
var agentCallbackEventCategories = map[string]string{
|
||||
"deployment.status_changed": "deployment",
|
||||
"phase.changed": "ordinary_sub",
|
||||
"agent.started": "ordinary_sub",
|
||||
@@ -512,33 +512,33 @@ var agnetCallbackEventCategories = map[string]string{
|
||||
"budget.alert": "budget",
|
||||
}
|
||||
|
||||
func AgnetGetSwarmEventCallbackSchema(c *gin.Context) {
|
||||
events := make([]string, 0, len(agnetCallbackEventRequiredFields))
|
||||
for eventType := range agnetCallbackEventRequiredFields {
|
||||
func AgentGetRuntimeEventCallbackSchema(c *gin.Context) {
|
||||
events := make([]string, 0, len(agentCallbackEventRequiredFields))
|
||||
for eventType := range agentCallbackEventRequiredFields {
|
||||
events = append(events, eventType)
|
||||
}
|
||||
sort.Strings(events)
|
||||
|
||||
items := make([]gin.H, 0, len(events))
|
||||
for _, eventType := range events {
|
||||
required := append([]string(nil), agnetCallbackEventRequiredFields[eventType]...)
|
||||
required := append([]string(nil), agentCallbackEventRequiredFields[eventType]...)
|
||||
sort.Strings(required)
|
||||
items = append(items, gin.H{
|
||||
"event_type": eventType,
|
||||
"category": agnetCallbackEventCategories[eventType],
|
||||
"category": agentCallbackEventCategories[eventType],
|
||||
"required_fields": required,
|
||||
"payload_location": "top-level envelope or payload object; artifact_id may also be in artifact object",
|
||||
})
|
||||
}
|
||||
|
||||
common.ApiSuccess(c, gin.H{
|
||||
"callback_path": "/api/agnet/callbacks/swarm-events",
|
||||
"callback_path": "/api/agent/callbacks/runtime-events",
|
||||
"auth": gin.H{
|
||||
"service_token_headers": []string{"X-Agnet-Service-Token", "Authorization: Bearer <token>"},
|
||||
"hmac_headers": []string{"X-Agnet-Event-Id", "X-Agnet-Timestamp", "X-Agnet-Signature"},
|
||||
"service_token_headers": []string{"X-Agent-Service-Token", "Authorization: Bearer <token>"},
|
||||
"hmac_headers": []string{"X-Agent-Event-Id", "X-Agent-Timestamp", "X-Agent-Signature"},
|
||||
"hmac_payload": "timestamp + \".\" + event_id + \".\" + raw_body",
|
||||
},
|
||||
"dedupe_keys": []string{"X-Agnet-Event-Id", "event_id", "idempotency_key"},
|
||||
"dedupe_keys": []string{"X-Agent-Event-Id", "event_id", "idempotency_key"},
|
||||
"events": items,
|
||||
"security": gin.H{
|
||||
"plaintext_secrets_allowed": false,
|
||||
@@ -547,7 +547,7 @@ func AgnetGetSwarmEventCallbackSchema(c *gin.Context) {
|
||||
})
|
||||
}
|
||||
|
||||
func callbackEnvelopeFieldValue(payload agnetCallbackEnvelope, key string) string {
|
||||
func callbackEnvelopeFieldValue(payload agentCallbackEnvelope, key string) string {
|
||||
switch key {
|
||||
case "task_id":
|
||||
return strings.TrimSpace(payload.TaskID)
|
||||
@@ -558,7 +558,7 @@ func callbackEnvelopeFieldValue(payload agnetCallbackEnvelope, key string) strin
|
||||
}
|
||||
}
|
||||
|
||||
func callbackHasFieldValue(payload agnetCallbackEnvelope, key string) bool {
|
||||
func callbackHasFieldValue(payload agentCallbackEnvelope, key string) bool {
|
||||
if callbackEnvelopeFieldValue(payload, key) != "" {
|
||||
return true
|
||||
}
|
||||
@@ -578,8 +578,8 @@ func callbackHasFieldValue(payload agnetCallbackEnvelope, key string) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func validateAgnetCallbackEventSchema(payload agnetCallbackEnvelope) error {
|
||||
required, ok := agnetCallbackEventRequiredFields[payload.EventType]
|
||||
func validateAgentCallbackEventSchema(payload agentCallbackEnvelope) error {
|
||||
required, ok := agentCallbackEventRequiredFields[payload.EventType]
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
@@ -591,7 +591,7 @@ func validateAgnetCallbackEventSchema(payload agnetCallbackEnvelope) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func persistAgnetApprovalFromCallback(payload agnetCallbackEnvelope, record agnetDeploymentRecord) error {
|
||||
func persistAgentApprovalFromCallback(payload agentCallbackEnvelope, record agentDeploymentRecord) error {
|
||||
if payload.EventType != "approval.requested" || model.DB == nil {
|
||||
return nil
|
||||
}
|
||||
@@ -601,7 +601,7 @@ func persistAgnetApprovalFromCallback(payload agnetCallbackEnvelope, record agne
|
||||
}
|
||||
approvalID := firstNonEmpty(callbackStringValue(source, "approval_id"), "appr_"+common.GetUUID())
|
||||
userID, _ := strconv.Atoi(strings.TrimSpace(record.Plan.UserContext.UserID))
|
||||
approval := model.AgnetApprovalRequest{
|
||||
approval := model.AgentApprovalRequest{
|
||||
ApprovalID: approvalID,
|
||||
UserId: userID,
|
||||
DeploymentID: strings.TrimSpace(payload.DeploymentID),
|
||||
@@ -614,16 +614,16 @@ func persistAgnetApprovalFromCallback(payload agnetCallbackEnvelope, record agne
|
||||
RiskLevel: firstNonEmpty(callbackStringValue(source, "risk_level"), "high"),
|
||||
RequiresCredential: callbackBoolValue(source, "requires_credential"),
|
||||
SecretRef: strings.TrimSpace(callbackStringValue(source, "secret_ref")),
|
||||
Status: agnetApprovalStatusPending,
|
||||
RequestedBy: firstNonEmpty(callbackStringValue(source, "requested_by"), "agnet-runtime"),
|
||||
Status: agentApprovalStatusPending,
|
||||
RequestedBy: firstNonEmpty(callbackStringValue(source, "requested_by"), "agent-runtime"),
|
||||
RequestReason: firstNonEmpty(callbackStringValue(source, "reason"), callbackStringValue(source, "summary"), "Runtime requested approval"),
|
||||
TTLSeconds: callbackIntValue(source, "ttl_seconds"),
|
||||
}
|
||||
if approval.TTLSeconds <= 0 {
|
||||
approval.TTLSeconds = defaultAgnetApprovalTTLSeconds
|
||||
approval.TTLSeconds = defaultAgentApprovalTTLSeconds
|
||||
}
|
||||
if approval.TTLSeconds > maxAgnetApprovalTTLSeconds {
|
||||
approval.TTLSeconds = maxAgnetApprovalTTLSeconds
|
||||
if approval.TTLSeconds > maxAgentApprovalTTLSeconds {
|
||||
approval.TTLSeconds = maxAgentApprovalTTLSeconds
|
||||
}
|
||||
if approval.SecretRef != "" && !strings.HasPrefix(approval.SecretRef, "azkv://") {
|
||||
return fmt.Errorf("approval secret_ref must use azkv:// Azure Key Vault reference")
|
||||
@@ -634,59 +634,59 @@ func persistAgnetApprovalFromCallback(payload agnetCallbackEnvelope, record agne
|
||||
now := time.Now().UnixMilli()
|
||||
approval.ExpiresAt = now + int64(approval.TTLSeconds)*1000
|
||||
|
||||
var existing model.AgnetApprovalRequest
|
||||
var existing model.AgentApprovalRequest
|
||||
if err := model.DB.Where("approval_id = ?", approval.ApprovalID).First(&existing).Error; err == nil {
|
||||
return nil
|
||||
}
|
||||
if err := model.DB.Create(&approval).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
recordAgnetApprovalAudit("approval.requested", &approval, nil, "ok", "")
|
||||
recordAgentApprovalAudit("approval.requested", &approval, nil, "ok", "")
|
||||
return nil
|
||||
}
|
||||
|
||||
func AgnetReceiveSwarmEventCallback(c *gin.Context) {
|
||||
func AgentReceiveRuntimeEventCallback(c *gin.Context) {
|
||||
rawBody, err := io.ReadAll(io.LimitReader(c.Request.Body, 1<<20))
|
||||
if err != nil {
|
||||
agnetError(c, "CALLBACK_INVALID", "failed to read callback body")
|
||||
agentError(c, "CALLBACK_INVALID", "failed to read callback body")
|
||||
return
|
||||
}
|
||||
var payload agnetCallbackEnvelope
|
||||
var payload agentCallbackEnvelope
|
||||
if err := common.Unmarshal(rawBody, &payload); err != nil {
|
||||
agnetError(c, "CALLBACK_INVALID", err.Error())
|
||||
agentError(c, "CALLBACK_INVALID", err.Error())
|
||||
return
|
||||
}
|
||||
if payload.EventID == "" {
|
||||
payload.EventID = strings.TrimSpace(c.GetHeader("X-Agnet-Event-Id"))
|
||||
payload.EventID = strings.TrimSpace(c.GetHeader("X-Agent-Event-Id"))
|
||||
}
|
||||
if payload.CorrelationID == "" {
|
||||
payload.CorrelationID = strings.TrimSpace(c.GetHeader("X-Correlation-ID"))
|
||||
}
|
||||
payload.EventID = strings.TrimSpace(payload.EventID)
|
||||
payload.EventType = strings.TrimSpace(payload.EventType)
|
||||
if !validateAgnetCallbackAuth(c, rawBody, payload.EventID) {
|
||||
if !validateAgentCallbackAuth(c, rawBody, payload.EventID) {
|
||||
return
|
||||
}
|
||||
if payload.EventID == "" || payload.EventType == "" {
|
||||
agnetError(c, "CALLBACK_INVALID", "event_id and event_type are required")
|
||||
agentError(c, "CALLBACK_INVALID", "event_id and event_type are required")
|
||||
return
|
||||
}
|
||||
normalizeCallbackArtifact(&payload)
|
||||
if payload.IdempotencyKey == "" {
|
||||
payload.IdempotencyKey = payload.EventID
|
||||
}
|
||||
if err := validateAgnetCallbackEventSchema(payload); err != nil {
|
||||
agnetError(c, "CALLBACK_SCHEMA_INVALID", err.Error())
|
||||
if err := validateAgentCallbackEventSchema(payload); err != nil {
|
||||
agentError(c, "CALLBACK_SCHEMA_INVALID", err.Error())
|
||||
return
|
||||
}
|
||||
if agnetCallbackHasPlaintextSecret(payload) {
|
||||
agnetError(c, "CALLBACK_SECRET_REJECTED", "callbacks must not contain plaintext credential fields")
|
||||
if agentCallbackHasPlaintextSecret(payload) {
|
||||
agentError(c, "CALLBACK_SECRET_REJECTED", "callbacks must not contain plaintext credential fields")
|
||||
return
|
||||
}
|
||||
|
||||
incomingDeploymentID := strings.TrimSpace(payload.DeploymentID)
|
||||
incomingSwarmID := strings.TrimSpace(payload.SwarmID)
|
||||
record, _ := agnetCallbackDeploymentContext(incomingDeploymentID, incomingSwarmID)
|
||||
record, _ := agentCallbackDeploymentContext(incomingDeploymentID, incomingSwarmID)
|
||||
if strings.TrimSpace(record.DeploymentID) != "" {
|
||||
payload.DeploymentID = record.DeploymentID
|
||||
if strings.TrimSpace(payload.SwarmID) == "" {
|
||||
@@ -697,7 +697,7 @@ func AgnetReceiveSwarmEventCallback(c *gin.Context) {
|
||||
payload.CorrelationID = record.Plan.Metadata.CorrelationID
|
||||
}
|
||||
payloadJSON, _ := common.Marshal(payload)
|
||||
inserted, err := model.InsertAgnetCallbackEvent(&model.AgnetCallbackEvent{
|
||||
inserted, err := model.InsertAgentCallbackEvent(&model.AgentCallbackEvent{
|
||||
EventID: payload.EventID,
|
||||
IdempotencyKey: strings.TrimSpace(payload.IdempotencyKey),
|
||||
CallbackType: "swarm-event",
|
||||
@@ -716,23 +716,23 @@ func AgnetReceiveSwarmEventCallback(c *gin.Context) {
|
||||
CreatedAtMs: time.Now().UnixMilli(),
|
||||
})
|
||||
if err != nil {
|
||||
common.SysLog("AgnetReceiveSwarmEventCallback: " + err.Error())
|
||||
agnetError(c, "CALLBACK_PERSIST_FAILED", "failed to persist callback")
|
||||
common.SysLog("AgentReceiveRuntimeEventCallback: " + err.Error())
|
||||
agentError(c, "CALLBACK_PERSIST_FAILED", "failed to persist callback")
|
||||
return
|
||||
}
|
||||
if inserted {
|
||||
if err := persistAgnetArtifactFromCallback(payload, record); err != nil {
|
||||
common.SysLog("persistAgnetArtifactFromCallback: " + err.Error())
|
||||
agnetError(c, "CALLBACK_PERSIST_FAILED", "failed to persist artifact")
|
||||
if err := persistAgentArtifactFromCallback(payload, record); err != nil {
|
||||
common.SysLog("persistAgentArtifactFromCallback: " + err.Error())
|
||||
agentError(c, "CALLBACK_PERSIST_FAILED", "failed to persist artifact")
|
||||
return
|
||||
}
|
||||
if err := persistAgnetApprovalFromCallback(payload, record); err != nil {
|
||||
common.SysLog("persistAgnetApprovalFromCallback: " + err.Error())
|
||||
agnetError(c, "CALLBACK_PERSIST_FAILED", "failed to persist approval request")
|
||||
if err := persistAgentApprovalFromCallback(payload, record); err != nil {
|
||||
common.SysLog("persistAgentApprovalFromCallback: " + err.Error())
|
||||
agentError(c, "CALLBACK_PERSIST_FAILED", "failed to persist approval request")
|
||||
return
|
||||
}
|
||||
record, _ = applyAgnetCallbackDeploymentState(payload, record)
|
||||
recordAgnetAuditEvent(agnetEvent{
|
||||
record, _ = applyAgentCallbackDeploymentState(payload, record)
|
||||
recordAgentAuditEvent(agentEvent{
|
||||
EventID: "evt_" + common.GetUUID()[:12],
|
||||
Event: "callback." + payload.EventType,
|
||||
SchemaVersion: 1,
|
||||
@@ -741,8 +741,8 @@ func AgnetReceiveSwarmEventCallback(c *gin.Context) {
|
||||
BindingScope: firstPlanBindingScope(record.Plan),
|
||||
DeploymentID: strings.TrimSpace(payload.DeploymentID),
|
||||
CorrelationID: strings.TrimSpace(payload.CorrelationID),
|
||||
OccurredAt: firstNonEmpty(strings.TrimSpace(payload.OccurredAt), agnetNow()),
|
||||
}, "agnet_callback", strings.TrimSpace(payload.DeploymentID), agnetRequestID(c), "ok")
|
||||
OccurredAt: firstNonEmpty(strings.TrimSpace(payload.OccurredAt), agentNow()),
|
||||
}, "agent_callback", strings.TrimSpace(payload.DeploymentID), agentRequestID(c), "ok")
|
||||
}
|
||||
common.ApiSuccess(c, gin.H{
|
||||
"event_id": payload.EventID,
|
||||
@@ -753,54 +753,54 @@ func AgnetReceiveSwarmEventCallback(c *gin.Context) {
|
||||
})
|
||||
}
|
||||
|
||||
func AgnetListUserDeploymentArtifacts(c *gin.Context) {
|
||||
record, ok := requireAuthenticatedUserAgnetDeployment(c)
|
||||
func AgentListUserDeploymentArtifacts(c *gin.Context) {
|
||||
record, ok := requireAuthenticatedUserAgentDeployment(c)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
items, err := model.ListAgnetArtifacts(model.ListAgnetArtifactsFilter{
|
||||
items, err := model.ListAgentArtifacts(model.ListAgentArtifactsFilter{
|
||||
DeploymentID: record.DeploymentID,
|
||||
Limit: 500,
|
||||
})
|
||||
if err != nil {
|
||||
common.SysLog("AgnetListUserDeploymentArtifacts: " + err.Error())
|
||||
agnetError(c, "ARTIFACT_QUERY_FAILED", "failed to query artifacts")
|
||||
common.SysLog("AgentListUserDeploymentArtifacts: " + err.Error())
|
||||
agentError(c, "ARTIFACT_QUERY_FAILED", "failed to query artifacts")
|
||||
return
|
||||
}
|
||||
common.ApiSuccess(c, gin.H{"deployment_id": record.DeploymentID, "artifacts": items, "items": items, "total": len(items)})
|
||||
}
|
||||
|
||||
func AgnetGetUserDeploymentArtifactContent(c *gin.Context) {
|
||||
record, ok := requireAuthenticatedUserAgnetDeployment(c)
|
||||
func AgentGetUserDeploymentArtifactContent(c *gin.Context) {
|
||||
record, ok := requireAuthenticatedUserAgentDeployment(c)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
artifactID := strings.TrimSpace(c.Param("artifact_id"))
|
||||
if artifactID == "" {
|
||||
agnetError(c, "ARTIFACT_ID_REQUIRED", "artifact_id is required")
|
||||
agentError(c, "ARTIFACT_ID_REQUIRED", "artifact_id is required")
|
||||
return
|
||||
}
|
||||
artifact, found, err := model.GetAgnetArtifactByDeployment(record.DeploymentID, artifactID)
|
||||
artifact, found, err := model.GetAgentArtifactByDeployment(record.DeploymentID, artifactID)
|
||||
if err != nil {
|
||||
common.SysLog("AgnetGetUserDeploymentArtifactContent: " + err.Error())
|
||||
agnetError(c, "ARTIFACT_QUERY_FAILED", "failed to query artifact")
|
||||
common.SysLog("AgentGetUserDeploymentArtifactContent: " + err.Error())
|
||||
agentError(c, "ARTIFACT_QUERY_FAILED", "failed to query artifact")
|
||||
return
|
||||
}
|
||||
if !found {
|
||||
agnetError(c, "ARTIFACT_NOT_FOUND", "artifact not found")
|
||||
agentError(c, "ARTIFACT_NOT_FOUND", "artifact not found")
|
||||
return
|
||||
}
|
||||
cfg := agnetRuntimeClientConfigForMode(agnetRuntimeModeForRecord(record))
|
||||
cfg := agentRuntimeClientConfigForMode(agentRuntimeModeForRecord(record))
|
||||
if !cfg.Enabled || strings.TrimSpace(cfg.BaseURL) == "" {
|
||||
agnetError(c, "RUNTIME_NOT_CONFIGURED", "runtime is not configured")
|
||||
agentError(c, "RUNTIME_NOT_CONFIGURED", "runtime is not configured")
|
||||
return
|
||||
}
|
||||
ctx, cancel := context.WithTimeout(c.Request.Context(), cfg.Timeout)
|
||||
defer cancel()
|
||||
resp, err := callAgnetRuntimeArtifactContent(ctx, cfg, record, artifact.ArtifactID)
|
||||
resp, err := callAgentRuntimeArtifactContent(ctx, cfg, record, artifact.ArtifactID)
|
||||
if err != nil {
|
||||
common.SysLog("AgnetGetUserDeploymentArtifactContent: " + err.Error())
|
||||
agnetError(c, "ARTIFACT_CONTENT_FETCH_FAILED", "failed to fetch artifact content")
|
||||
common.SysLog("AgentGetUserDeploymentArtifactContent: " + err.Error())
|
||||
agentError(c, "ARTIFACT_CONTENT_FETCH_FAILED", "failed to fetch artifact content")
|
||||
return
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
@@ -818,8 +818,8 @@ func AgnetGetUserDeploymentArtifactContent(c *gin.Context) {
|
||||
c.DataFromReader(http.StatusOK, resp.ContentLength, contentType, resp.Body, headers)
|
||||
}
|
||||
|
||||
func callbackPayloadMap(row model.AgnetCallbackEvent) map[string]any {
|
||||
var payload agnetCallbackEnvelope
|
||||
func callbackPayloadMap(row model.AgentCallbackEvent) map[string]any {
|
||||
var payload agentCallbackEnvelope
|
||||
if err := common.UnmarshalJsonStr(row.PayloadJSON, &payload); err != nil {
|
||||
return nil
|
||||
}
|
||||
@@ -829,7 +829,7 @@ func callbackPayloadMap(row model.AgnetCallbackEvent) map[string]any {
|
||||
return nil
|
||||
}
|
||||
|
||||
func timelineEntryFromCallback(callback model.AgnetCallbackEvent) gin.H {
|
||||
func timelineEntryFromCallback(callback model.AgentCallbackEvent) gin.H {
|
||||
payload := callbackPayloadMap(callback)
|
||||
entry := gin.H{
|
||||
"kind": "callback",
|
||||
@@ -852,29 +852,29 @@ func timelineEntryFromCallback(callback model.AgnetCallbackEvent) gin.H {
|
||||
return entry
|
||||
}
|
||||
|
||||
func AgnetGetUserDeploymentTimeline(c *gin.Context) {
|
||||
record, ok := requireAuthenticatedUserAgnetDeployment(c)
|
||||
func AgentGetUserDeploymentTimeline(c *gin.Context) {
|
||||
record, ok := requireAuthenticatedUserAgentDeployment(c)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
events, err := model.ListAgnetAuditEventsByDeployment(record.DeploymentID)
|
||||
events, err := model.ListAgentAuditEventsByDeployment(record.DeploymentID)
|
||||
if err != nil {
|
||||
agnetError(c, "TIMELINE_QUERY_FAILED", "failed to query audit events")
|
||||
agentError(c, "TIMELINE_QUERY_FAILED", "failed to query audit events")
|
||||
return
|
||||
}
|
||||
callbacks, err := model.ListAgnetCallbackEvents(model.ListAgnetCallbackEventsFilter{DeploymentID: record.DeploymentID, Limit: 500})
|
||||
callbacks, err := model.ListAgentCallbackEvents(model.ListAgentCallbackEventsFilter{DeploymentID: record.DeploymentID, Limit: 500})
|
||||
if err != nil {
|
||||
agnetError(c, "TIMELINE_QUERY_FAILED", "failed to query callbacks")
|
||||
agentError(c, "TIMELINE_QUERY_FAILED", "failed to query callbacks")
|
||||
return
|
||||
}
|
||||
artifacts, err := model.ListAgnetArtifacts(model.ListAgnetArtifactsFilter{DeploymentID: record.DeploymentID, Limit: 500})
|
||||
artifacts, err := model.ListAgentArtifacts(model.ListAgentArtifactsFilter{DeploymentID: record.DeploymentID, Limit: 500})
|
||||
if err != nil {
|
||||
agnetError(c, "TIMELINE_QUERY_FAILED", "failed to query artifacts")
|
||||
agentError(c, "TIMELINE_QUERY_FAILED", "failed to query artifacts")
|
||||
return
|
||||
}
|
||||
snapshots, err := model.ListAgnetSKSnapshots(record.DeploymentID)
|
||||
snapshots, err := model.ListAgentSKSnapshots(record.DeploymentID)
|
||||
if err != nil {
|
||||
agnetError(c, "TIMELINE_QUERY_FAILED", "failed to query sk snapshots")
|
||||
agentError(c, "TIMELINE_QUERY_FAILED", "failed to query sk snapshots")
|
||||
return
|
||||
}
|
||||
timeline := make([]gin.H, 0, len(events)+len(callbacks)+len(artifacts)+len(snapshots))
|
||||
@@ -892,7 +892,7 @@ func AgnetGetUserDeploymentTimeline(c *gin.Context) {
|
||||
}
|
||||
common.ApiSuccess(c, gin.H{
|
||||
"deployment_id": record.DeploymentID,
|
||||
"deployment": record,
|
||||
"deployment": withDisplayStatus(record),
|
||||
"events": events,
|
||||
"callbacks": callbacks,
|
||||
"artifacts": artifacts,
|
||||
+488
-407
File diff suppressed because it is too large
Load Diff
+486
-456
File diff suppressed because it is too large
Load Diff
+7
-7
@@ -132,21 +132,21 @@ func TestValueLooksLikeSecretAndPlaintextScan(t *testing.T) {
|
||||
}
|
||||
|
||||
// P5: a single source of truth for the default model; no placeholder fallback.
|
||||
func TestDefaultAgnetModelID_SingleSourceNoPlaceholder(t *testing.T) {
|
||||
def := defaultAgnetModelID()
|
||||
func TestDefaultAgentModelID_SingleSourceNoPlaceholder(t *testing.T) {
|
||||
def := defaultAgentModelID()
|
||||
require.Equal(t, "gpt-5.4", def)
|
||||
|
||||
// Draft builder must use the single default, never agnet-model-<role>.
|
||||
plan := buildAgnetDraftAgentPlan("backend", "", nil)
|
||||
// Draft builder must use the single default, never agent-model-<role>.
|
||||
plan := buildAgentDraftAgentPlan("backend", "", nil)
|
||||
require.Equal(t, def, plan.DefaultModelID)
|
||||
require.NotContains(t, plan.DefaultModelID, "agnet-model-")
|
||||
require.NotContains(t, plan.DefaultModelID, "agent-model-")
|
||||
|
||||
// Explicit client model is still honored.
|
||||
plan = buildAgnetDraftAgentPlan("backend", "gpt-5.4-mini", nil)
|
||||
plan = buildAgentDraftAgentPlan("backend", "gpt-5.4-mini", nil)
|
||||
require.Equal(t, "gpt-5.4-mini", plan.DefaultModelID)
|
||||
|
||||
// Every role template resolves to the single default, no claude-* hardcoding.
|
||||
for _, tpl := range agnetRoleTemplates() {
|
||||
for _, tpl := range agentRoleTemplates() {
|
||||
require.Equal(t, def, tpl.DefaultModel, "role %s", tpl.Key)
|
||||
}
|
||||
}
|
||||
+39
-39
@@ -5,7 +5,7 @@ import (
|
||||
"github.com/heicode/manager/common"
|
||||
)
|
||||
|
||||
// AgnetRoleTemplate is the platform-recommended role catalog Manager
|
||||
// AgentRoleTemplate is the platform-recommended role catalog Manager
|
||||
// surfaces to users when they assemble an AI development team. The
|
||||
// six canonical roles come from docs/product-package/13-platform-
|
||||
// description.md §3 and 04-platform-usage-guide.md §第五步.
|
||||
@@ -14,7 +14,7 @@ import (
|
||||
// - Roles are platform-defined contracts, not user-editable data.
|
||||
// Treating them like rows would invite drift between deployments.
|
||||
// - Permission hints below are *recommendations* the UI uses to
|
||||
// pre-fill the "what can this Agnet do" confirmation card —
|
||||
// pre-fill the "what can this Agent do" confirmation card —
|
||||
// the actual permission grant still goes through ResourceGrant.
|
||||
// - If we ever need per-tenant role customization, we add an
|
||||
// overlay table; the canonical set still lives here as the
|
||||
@@ -24,7 +24,7 @@ import (
|
||||
// across persistence and the client picker. Display strings can
|
||||
// be translated, but the key must NEVER change without a coordinated
|
||||
// frontend rollout.
|
||||
type AgnetRoleTemplate struct {
|
||||
type AgentRoleTemplate struct {
|
||||
Key string `json:"key"`
|
||||
DisplayName string `json:"display_name"`
|
||||
Summary string `json:"summary"`
|
||||
@@ -33,132 +33,132 @@ type AgnetRoleTemplate struct {
|
||||
RiskLevel string `json:"risk_level"`
|
||||
}
|
||||
|
||||
// defaultAgnetModelID is the single source of truth for the default sub-agent
|
||||
// defaultAgentModelID is the single source of truth for the default sub-agent
|
||||
// model. It is aligned to the production-verified NewAPI model and overridable
|
||||
// via AGNET_DEFAULT_MODEL_ID, so role templates, deployment drafts and runtime
|
||||
// agent refs never fall back to placeholder names (e.g. agnet-model-<role>)
|
||||
// via AGENT_DEFAULT_MODEL_ID, so role templates, deployment drafts and runtime
|
||||
// agent refs never fall back to placeholder names (e.g. agent-model-<role>)
|
||||
// that production NewAPI cannot route ("No available channel for model ...").
|
||||
func defaultAgnetModelID() string {
|
||||
return common.GetEnvOrDefaultString("AGNET_DEFAULT_MODEL_ID", "gpt-5.4")
|
||||
func defaultAgentModelID() string {
|
||||
return common.GetEnvOrDefaultString("AGENT_DEFAULT_MODEL_ID", "gpt-5.4")
|
||||
}
|
||||
|
||||
// agnetRoleTemplates returns the canonical six-role catalog. Order
|
||||
// agentRoleTemplates returns the canonical six-role catalog. Order
|
||||
// matches the typical lifecycle a user walks through when assembling
|
||||
// a team: discover -> design -> build -> review -> operate.
|
||||
//
|
||||
// Permission hints use the verbs from docs §13.3.4 (Resource Grant)
|
||||
// and stay deliberately broad — concrete grants come from the user
|
||||
// resource-binding flow.
|
||||
func agnetRoleTemplates() []AgnetRoleTemplate {
|
||||
return []AgnetRoleTemplate{
|
||||
func agentRoleTemplates() []AgentRoleTemplate {
|
||||
return []AgentRoleTemplate{
|
||||
{
|
||||
Key: "product",
|
||||
DisplayName: "Product Agnet",
|
||||
DisplayName: "Product Agent",
|
||||
Summary: "Refines the user idea into product scope, requirements and acceptance criteria.",
|
||||
DefaultModel: defaultAgnetModelID(),
|
||||
DefaultModel: defaultAgentModelID(),
|
||||
DefaultPermissions: []string{
|
||||
"read:project_docs",
|
||||
"write:product_spec",
|
||||
},
|
||||
RiskLevel: agnetRiskLow,
|
||||
RiskLevel: agentRiskLow,
|
||||
},
|
||||
{
|
||||
Key: "architect",
|
||||
DisplayName: "Architect Agnet",
|
||||
DisplayName: "Architect Agent",
|
||||
Summary: "Designs the technical approach, picks frameworks, and breaks work into sub-tasks.",
|
||||
DefaultModel: defaultAgnetModelID(),
|
||||
DefaultModel: defaultAgentModelID(),
|
||||
DefaultPermissions: []string{
|
||||
"read:repo",
|
||||
"write:architecture_doc",
|
||||
},
|
||||
RiskLevel: agnetRiskLow,
|
||||
RiskLevel: agentRiskLow,
|
||||
},
|
||||
{
|
||||
Key: "frontend",
|
||||
DisplayName: "Frontend Agnet",
|
||||
DisplayName: "Frontend Agent",
|
||||
Summary: "Implements UI, components and client-side state per the architect's plan.",
|
||||
DefaultModel: defaultAgnetModelID(),
|
||||
DefaultModel: defaultAgentModelID(),
|
||||
DefaultPermissions: []string{
|
||||
"read:repo",
|
||||
"write:repo:frontend",
|
||||
},
|
||||
RiskLevel: agnetRiskMedium,
|
||||
RiskLevel: agentRiskMedium,
|
||||
},
|
||||
{
|
||||
Key: "backend",
|
||||
DisplayName: "Backend Agnet",
|
||||
DisplayName: "Backend Agent",
|
||||
Summary: "Implements server-side APIs, data models and integrations.",
|
||||
DefaultModel: defaultAgnetModelID(),
|
||||
DefaultModel: defaultAgentModelID(),
|
||||
DefaultPermissions: []string{
|
||||
"read:repo",
|
||||
"write:repo:backend",
|
||||
"read:dev_database",
|
||||
},
|
||||
RiskLevel: agnetRiskMedium,
|
||||
RiskLevel: agentRiskMedium,
|
||||
},
|
||||
{
|
||||
Key: "reviewer",
|
||||
DisplayName: "Reviewer Agnet",
|
||||
DisplayName: "Reviewer Agent",
|
||||
Summary: "Performs code review, security checks and runs the test suite.",
|
||||
DefaultModel: defaultAgnetModelID(),
|
||||
DefaultModel: defaultAgentModelID(),
|
||||
DefaultPermissions: []string{
|
||||
"read:repo",
|
||||
"run:tests",
|
||||
"comment:pull_request",
|
||||
},
|
||||
RiskLevel: agnetRiskLow,
|
||||
RiskLevel: agentRiskLow,
|
||||
},
|
||||
{
|
||||
Key: "ops",
|
||||
DisplayName: "Ops Agnet",
|
||||
DisplayName: "Ops Agent",
|
||||
Summary: "Deploys to test environments, watches logs and prepares production rollouts (production requires approval).",
|
||||
DefaultModel: defaultAgnetModelID(),
|
||||
DefaultModel: defaultAgentModelID(),
|
||||
DefaultPermissions: []string{
|
||||
"read:repo",
|
||||
"deploy:test_env",
|
||||
"read:metrics",
|
||||
"approval_required:deploy_prod",
|
||||
},
|
||||
RiskLevel: agnetRiskHigh,
|
||||
RiskLevel: agentRiskHigh,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// AgnetListRoleTemplates is the GET /api/agnet/role-templates handler.
|
||||
// AgentListRoleTemplates is the GET /api/agent/role-templates handler.
|
||||
// Returns the canonical six-role catalog so the deployment-creation
|
||||
// UI can pre-populate role pickers and the documentation page can
|
||||
// render the role overview.
|
||||
//
|
||||
// Auth: requires UserAuth (mounted by router). Anyone logged in to
|
||||
// Manager can read the catalog; there are no secrets in the payload.
|
||||
func AgnetListRoleTemplates(c *gin.Context) {
|
||||
tpls := agnetRoleTemplates()
|
||||
func AgentListRoleTemplates(c *gin.Context) {
|
||||
tpls := agentRoleTemplates()
|
||||
common.ApiSuccess(c, gin.H{
|
||||
"items": tpls,
|
||||
"total": len(tpls),
|
||||
})
|
||||
}
|
||||
|
||||
// agnetRoleTemplateKeys is a helper for validation in deployment
|
||||
// agentRoleTemplateKeys is a helper for validation in deployment
|
||||
// creation — checks whether a user-provided role_template string is
|
||||
// one of the canonical six. Returns true for any of the canonical
|
||||
// keys; returns true for unknown keys too (deployment flow today
|
||||
// accepts free-form role_template strings, see agnet_control_plane.
|
||||
// accepts free-form role_template strings, see agent_control_plane.
|
||||
// go:552), so this helper is currently advisory. When we tighten
|
||||
// validation (after frontend ships the new picker), flip the
|
||||
// fallback to false and add a unit test.
|
||||
func agnetRoleTemplateKeys() map[string]bool {
|
||||
func agentRoleTemplateKeys() map[string]bool {
|
||||
keys := make(map[string]bool)
|
||||
for _, t := range agnetRoleTemplates() {
|
||||
for _, t := range agentRoleTemplates() {
|
||||
keys[t.Key] = true
|
||||
}
|
||||
return keys
|
||||
}
|
||||
|
||||
// agnetIsCanonicalRoleKey reports whether `key` matches one of the
|
||||
// agentIsCanonicalRoleKey reports whether `key` matches one of the
|
||||
// six platform-defined roles. Today the deployment endpoint accepts
|
||||
// any non-empty string; this helper is reserved for the next step
|
||||
// when we move to a closed set.
|
||||
func agnetIsCanonicalRoleKey(key string) bool {
|
||||
return agnetRoleTemplateKeys()[key]
|
||||
func agentIsCanonicalRoleKey(key string) bool {
|
||||
return agentRoleTemplateKeys()[key]
|
||||
}
|
||||
+16
-16
@@ -9,13 +9,13 @@ import (
|
||||
"github.com/heicode/manager/common"
|
||||
)
|
||||
|
||||
func TestAgnetRoleTemplates_CanonicalSetCovered(t *testing.T) {
|
||||
func TestAgentRoleTemplates_CanonicalSetCovered(t *testing.T) {
|
||||
// Pins the six canonical role keys from docs §13.3.3. Any code
|
||||
// change that adds, removes or renames a key MUST update this
|
||||
// list — guards against accidental drift between Manager and
|
||||
// the product spec.
|
||||
want := []string{"product", "architect", "frontend", "backend", "reviewer", "ops"}
|
||||
got := agnetRoleTemplates()
|
||||
got := agentRoleTemplates()
|
||||
if len(got) != len(want) {
|
||||
t.Fatalf("expected %d roles, got %d", len(want), len(got))
|
||||
}
|
||||
@@ -37,7 +37,7 @@ func TestAgnetRoleTemplates_CanonicalSetCovered(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestAgnetRoleTemplates_RiskLevels(t *testing.T) {
|
||||
func TestAgentRoleTemplates_RiskLevels(t *testing.T) {
|
||||
// Ops is the only canonical role with high risk (production
|
||||
// deployment intent). Reviewer + Product + Architect stay low
|
||||
// (read-mostly). Frontend + Backend land at medium. Pins the
|
||||
@@ -45,29 +45,29 @@ func TestAgnetRoleTemplates_RiskLevels(t *testing.T) {
|
||||
// flip an ops role to "low" and skip the high-risk approval
|
||||
// gating downstream.
|
||||
wantRisk := map[string]string{
|
||||
"product": agnetRiskLow,
|
||||
"architect": agnetRiskLow,
|
||||
"reviewer": agnetRiskLow,
|
||||
"frontend": agnetRiskMedium,
|
||||
"backend": agnetRiskMedium,
|
||||
"ops": agnetRiskHigh,
|
||||
"product": agentRiskLow,
|
||||
"architect": agentRiskLow,
|
||||
"reviewer": agentRiskLow,
|
||||
"frontend": agentRiskMedium,
|
||||
"backend": agentRiskMedium,
|
||||
"ops": agentRiskHigh,
|
||||
}
|
||||
for _, tpl := range agnetRoleTemplates() {
|
||||
for _, tpl := range agentRoleTemplates() {
|
||||
if want, ok := wantRisk[tpl.Key]; ok && tpl.RiskLevel != want {
|
||||
t.Errorf("role %q: want risk %q, got %q", tpl.Key, want, tpl.RiskLevel)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestAgnetListRoleTemplates_HTTPShape(t *testing.T) {
|
||||
func TestAgentListRoleTemplates_HTTPShape(t *testing.T) {
|
||||
gin.SetMode(gin.TestMode)
|
||||
rec := httptest.NewRecorder()
|
||||
c, _ := gin.CreateTestContext(rec)
|
||||
c.Request = httptest.NewRequest("GET", "/api/agnet/role-templates", nil)
|
||||
c.Request = httptest.NewRequest("GET", "/api/agent/role-templates", nil)
|
||||
c.Set("id", 1)
|
||||
c.Set("role", common.RoleCommonUser)
|
||||
|
||||
AgnetListRoleTemplates(c)
|
||||
AgentListRoleTemplates(c)
|
||||
|
||||
if rec.Code != 200 {
|
||||
t.Fatalf("expected 200, got %d body=%s", rec.Code, rec.Body.String())
|
||||
@@ -83,17 +83,17 @@ func TestAgnetListRoleTemplates_HTTPShape(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestAgnetIsCanonicalRoleKey(t *testing.T) {
|
||||
func TestAgentIsCanonicalRoleKey(t *testing.T) {
|
||||
// Defensive helper currently used as advisory — pins the closed
|
||||
// set so the future tighten-up to closed-set validation is one
|
||||
// flip instead of an open-ended audit.
|
||||
for _, ok := range []string{"product", "architect", "frontend", "backend", "reviewer", "ops"} {
|
||||
if !agnetIsCanonicalRoleKey(ok) {
|
||||
if !agentIsCanonicalRoleKey(ok) {
|
||||
t.Errorf("%q should be canonical", ok)
|
||||
}
|
||||
}
|
||||
for _, bad := range []string{"", "debugger", "executor", "random_string"} {
|
||||
if agnetIsCanonicalRoleKey(bad) {
|
||||
if agentIsCanonicalRoleKey(bad) {
|
||||
t.Errorf("%q should NOT be canonical", bad)
|
||||
}
|
||||
}
|
||||
+279
-214
@@ -17,15 +17,15 @@ import (
|
||||
)
|
||||
|
||||
const (
|
||||
agnetRuntimeStateSyncing = "runtime_syncing"
|
||||
agnetRuntimeStateSynced = "runtime_accepted"
|
||||
agnetRuntimeStateFailed = "runtime_sync_failed"
|
||||
agentRuntimeStateSyncing = "runtime_syncing"
|
||||
agentRuntimeStateSynced = "runtime_accepted"
|
||||
agentRuntimeStateFailed = "runtime_sync_failed"
|
||||
|
||||
agnetRuntimeModeAgnet = "agnet"
|
||||
agnetRuntimeModeSwarm = "swarm"
|
||||
agentRuntimeModeAgent = "agent"
|
||||
agentRuntimeModeSwarm = "swarm"
|
||||
)
|
||||
|
||||
type agnetRuntimeConfig struct {
|
||||
type agentRuntimeConfig struct {
|
||||
Enabled bool
|
||||
Async bool
|
||||
BaseURL string
|
||||
@@ -39,14 +39,14 @@ type agnetRuntimeConfig struct {
|
||||
Timeout time.Duration
|
||||
}
|
||||
|
||||
type agnetRuntimeSyncResult struct {
|
||||
type agentRuntimeSyncResult struct {
|
||||
RuntimeDeploymentID string
|
||||
RuntimeSwarmID string
|
||||
RuntimeStatus string
|
||||
RawStatusCode int
|
||||
}
|
||||
|
||||
type agnetRuntimeDiagnostics struct {
|
||||
type agentRuntimeDiagnostics struct {
|
||||
DeploymentID string `json:"deployment_id"`
|
||||
RuntimeMode string `json:"runtime_mode"`
|
||||
SubMode string `json:"sub_mode"`
|
||||
@@ -65,41 +65,42 @@ type agnetRuntimeDiagnostics struct {
|
||||
CheckedAt string `json:"checked_at"`
|
||||
}
|
||||
|
||||
func normalizeAgnetRuntimeMode(value string) string {
|
||||
func normalizeAgentRuntimeMode(value string) string {
|
||||
switch strings.ToLower(strings.TrimSpace(value)) {
|
||||
case agnetRuntimeModeSwarm:
|
||||
return agnetRuntimeModeSwarm
|
||||
case agentRuntimeModeSwarm:
|
||||
return agentRuntimeModeSwarm
|
||||
default:
|
||||
return agnetRuntimeModeAgnet
|
||||
return agentRuntimeModeAgent
|
||||
}
|
||||
}
|
||||
|
||||
func agnetRuntimeModeForSource(source string) string {
|
||||
func agentRuntimeModeForSource(source string) string {
|
||||
if strings.TrimSpace(source) == "api_swarms_adapter" {
|
||||
return agnetRuntimeModeSwarm
|
||||
return agentRuntimeModeSwarm
|
||||
}
|
||||
return agnetRuntimeModeAgnet
|
||||
return agentRuntimeModeAgent
|
||||
}
|
||||
|
||||
func agnetRuntimeModeForRecord(record agnetDeploymentRecord) string {
|
||||
return normalizeAgnetRuntimeMode(record.Plan.Metadata.RuntimeMode)
|
||||
func agentRuntimeModeForRecord(record agentDeploymentRecord) string {
|
||||
return normalizeAgentRuntimeMode(record.Plan.Metadata.RuntimeMode)
|
||||
}
|
||||
|
||||
func agnetRuntimeClientConfig() agnetRuntimeConfig {
|
||||
return agnetRuntimeClientConfigForMode(agnetRuntimeModeAgnet)
|
||||
func agentRuntimeClientConfig() agentRuntimeConfig {
|
||||
return agentRuntimeClientConfigForMode(agentRuntimeModeAgent)
|
||||
}
|
||||
|
||||
func agnetRuntimeClientConfigForMode(mode string) agnetRuntimeConfig {
|
||||
timeoutSec := common.GetEnvOrDefault("AGNET_RUNTIME_TIMEOUT_SECONDS", 5)
|
||||
func agentRuntimeClientConfigForMode(mode string) agentRuntimeConfig {
|
||||
timeoutSec := common.GetEnvOrDefault("AGENT_RUNTIME_TIMEOUT_SECONDS", 5)
|
||||
if timeoutSec <= 0 {
|
||||
timeoutSec = 5
|
||||
}
|
||||
mode = normalizeAgnetRuntimeMode(mode)
|
||||
prefix := "AGNET_RUNTIME_"
|
||||
defaultCreatePath := "/api/agnet/deployments"
|
||||
defaultStopPath := "/api/agnet/deployments/{deployment_id}/stop"
|
||||
mode = normalizeAgentRuntimeMode(mode)
|
||||
prefix := "AGENT_RUNTIME_"
|
||||
// Sub Agile primary route per agent_management Sub Mode Runtime §2.2.
|
||||
defaultCreatePath := "/api/agent/sub-agile/deployments"
|
||||
defaultStopPath := "/api/agent/sub-agile/deployments/{deployment_id}/stop"
|
||||
defaultApprovalPath := "/api/swarms/{swarm_id}/approvals/{approval_id}"
|
||||
if mode == agnetRuntimeModeSwarm {
|
||||
if mode == agentRuntimeModeSwarm {
|
||||
prefix = "SWARM_RUNTIME_"
|
||||
defaultCreatePath = "/api/swarms"
|
||||
defaultStopPath = "/api/swarms/{swarm_id}/stop"
|
||||
@@ -110,18 +111,18 @@ func agnetRuntimeClientConfigForMode(mode string) agnetRuntimeConfig {
|
||||
}
|
||||
baseURL := strings.TrimRight(strings.TrimSpace(common.GetEnvOrDefaultString(prefix+"BASE_URL", "")), "/")
|
||||
enabledDefault := false
|
||||
if mode == agnetRuntimeModeAgnet {
|
||||
enabledDefault = common.GetEnvOrDefaultBool("AGNET_RUNTIME_ENABLED", false)
|
||||
if mode == agentRuntimeModeAgent {
|
||||
enabledDefault = common.GetEnvOrDefaultBool("AGENT_RUNTIME_ENABLED", false)
|
||||
} else {
|
||||
enabledDefault = baseURL != ""
|
||||
}
|
||||
return agnetRuntimeConfig{
|
||||
return agentRuntimeConfig{
|
||||
Enabled: common.GetEnvOrDefaultBool(prefix+"ENABLED", enabledDefault),
|
||||
Async: common.GetEnvOrDefaultBool(prefix+"ASYNC", common.GetEnvOrDefaultBool("AGNET_RUNTIME_ASYNC", true)),
|
||||
Async: common.GetEnvOrDefaultBool(prefix+"ASYNC", common.GetEnvOrDefaultBool("AGENT_RUNTIME_ASYNC", true)),
|
||||
BaseURL: baseURL,
|
||||
Token: strings.TrimSpace(common.GetEnvOrDefaultString(prefix+"SERVICE_TOKEN", "")),
|
||||
CreatePath: common.GetEnvOrDefaultString(prefix+"CREATE_PATH", defaultCreatePath),
|
||||
HealthPath: common.GetEnvOrDefaultString(prefix+"HEALTH_PATH", "/api/agnet/health"),
|
||||
HealthPath: common.GetEnvOrDefaultString(prefix+"HEALTH_PATH", "/api/agent/health"),
|
||||
StatusPath: common.GetEnvOrDefaultString(prefix+"STATUS_PATH", "/api/swarms/{swarm_id}/status"),
|
||||
ArtifactContentPath: common.GetEnvOrDefaultString(prefix+"ARTIFACT_CONTENT_PATH", "/api/swarms/{swarm_id}/artifacts/{artifact_id}/content"),
|
||||
StopPath: common.GetEnvOrDefaultString(prefix+"STOP_PATH", defaultStopPath),
|
||||
@@ -130,19 +131,19 @@ func agnetRuntimeClientConfigForMode(mode string) agnetRuntimeConfig {
|
||||
}
|
||||
}
|
||||
|
||||
func agnetRuntimeCallbackURL() string {
|
||||
if value := strings.TrimSpace(common.GetEnvOrDefaultString("AGNET_RUNTIME_CALLBACK_URL", "")); value != "" {
|
||||
func agentRuntimeCallbackURL() string {
|
||||
if value := strings.TrimSpace(common.GetEnvOrDefaultString("AGENT_RUNTIME_CALLBACK_URL", "")); value != "" {
|
||||
return value
|
||||
}
|
||||
baseURL := strings.TrimRight(strings.TrimSpace(common.GetEnvOrDefaultString("HEICODE_PUBLIC_BASE_URL", "https://code.xinghanlab.com")), "/")
|
||||
return baseURL + "/api/agnet/callbacks/swarm-events"
|
||||
return baseURL + "/api/agent/callbacks/runtime-events"
|
||||
}
|
||||
|
||||
func agnetRuntimeCallbackSigningSecretRef() string {
|
||||
return strings.TrimSpace(common.GetEnvOrDefaultString("AGNET_RUNTIME_CALLBACK_SIGNING_SECRET_REF", ""))
|
||||
func agentRuntimeCallbackSigningSecretRef() string {
|
||||
return strings.TrimSpace(common.GetEnvOrDefaultString("AGENT_RUNTIME_CALLBACK_SIGNING_SECRET_REF", ""))
|
||||
}
|
||||
|
||||
func agnetRuntimeSubscribedEvents() []string {
|
||||
func agentRuntimeSubscribedEvents() []string {
|
||||
return []string{
|
||||
"deployment.status_changed",
|
||||
"phase.changed",
|
||||
@@ -178,7 +179,7 @@ func firstNonEmpty(values ...string) string {
|
||||
return ""
|
||||
}
|
||||
|
||||
func truncateAgnetFailureReason(value string) string {
|
||||
func truncateAgentFailureReason(value string) string {
|
||||
value = strings.TrimSpace(value)
|
||||
if len(value) <= 480 {
|
||||
return value
|
||||
@@ -186,13 +187,13 @@ func truncateAgnetFailureReason(value string) string {
|
||||
return value[:480]
|
||||
}
|
||||
|
||||
func agnetRuntimeURL(baseURL string, path string) (string, error) {
|
||||
func agentRuntimeURL(baseURL string, path string) (string, error) {
|
||||
if strings.TrimSpace(baseURL) == "" {
|
||||
return "", errors.New("AGNET_RUNTIME_BASE_URL is not configured")
|
||||
return "", errors.New("AGENT_RUNTIME_BASE_URL is not configured")
|
||||
}
|
||||
parsed, err := url.Parse(baseURL)
|
||||
if err != nil || parsed.Scheme == "" || parsed.Host == "" {
|
||||
return "", errors.New("AGNET_RUNTIME_BASE_URL must be an absolute http(s) URL")
|
||||
return "", errors.New("AGENT_RUNTIME_BASE_URL must be an absolute http(s) URL")
|
||||
}
|
||||
if strings.TrimSpace(path) == "" {
|
||||
path = "/"
|
||||
@@ -203,7 +204,7 @@ func agnetRuntimeURL(baseURL string, path string) (string, error) {
|
||||
return strings.TrimRight(baseURL, "/") + path, nil
|
||||
}
|
||||
|
||||
func agnetRuntimeHeaders(req *http.Request, cfg agnetRuntimeConfig, record agnetDeploymentRecord) {
|
||||
func agentRuntimeHeaders(req *http.Request, cfg agentRuntimeConfig, record agentDeploymentRecord) {
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
req.Header.Set("X-User-ID", record.Plan.UserContext.UserID)
|
||||
req.Header.Set("X-Binding-Scope", firstPlanBindingScope(record.Plan))
|
||||
@@ -214,8 +215,8 @@ func agnetRuntimeHeaders(req *http.Request, cfg agnetRuntimeConfig, record agnet
|
||||
}
|
||||
}
|
||||
|
||||
func agnetRuntimeRequestAgents(plan agnetOrchestrationPlan) []gin.H {
|
||||
byRole := make(map[string]agnetAgentPlan, len(plan.Agents))
|
||||
func agentRuntimeRequestAgents(plan agentOrchestrationPlan) []gin.H {
|
||||
byRole := make(map[string]agentAgentPlan, len(plan.Agents))
|
||||
for _, agent := range plan.Agents {
|
||||
role := strings.TrimSpace(agent.RoleTemplate)
|
||||
if role != "" {
|
||||
@@ -235,7 +236,7 @@ func agnetRuntimeRequestAgents(plan agnetOrchestrationPlan) []gin.H {
|
||||
item["sk_sources"] = agent.SKSources
|
||||
}
|
||||
if len(agent.ResourceGrants) > 0 {
|
||||
item["resource_grants"] = agnetRuntimeResourceGrantPayloads(agent.ResourceGrants)
|
||||
item["resource_grants"] = agentRuntimeResourceGrantPayloads(agent.ResourceGrants)
|
||||
}
|
||||
}
|
||||
items = append(items, item)
|
||||
@@ -254,14 +255,14 @@ func agnetRuntimeRequestAgents(plan agnetOrchestrationPlan) []gin.H {
|
||||
item["sk_sources"] = agent.SKSources
|
||||
}
|
||||
if len(agent.ResourceGrants) > 0 {
|
||||
item["resource_grants"] = agnetRuntimeResourceGrantPayloads(agent.ResourceGrants)
|
||||
item["resource_grants"] = agentRuntimeResourceGrantPayloads(agent.ResourceGrants)
|
||||
}
|
||||
items = append(items, item)
|
||||
}
|
||||
return items
|
||||
}
|
||||
|
||||
func agnetRuntimeRequestSwarmAgents(plan agnetOrchestrationPlan) []gin.H {
|
||||
func agentRuntimeRequestSwarmAgents(plan agentOrchestrationPlan) []gin.H {
|
||||
runtimeModels := make(map[string]string, len(plan.AgentRuntime.Agents))
|
||||
for _, runtimeAgent := range plan.AgentRuntime.Agents {
|
||||
role := strings.TrimSpace(runtimeAgent.Role)
|
||||
@@ -276,7 +277,7 @@ func agnetRuntimeRequestSwarmAgents(plan agnetOrchestrationPlan) []gin.H {
|
||||
if role == "" {
|
||||
continue
|
||||
}
|
||||
taskID := fmt.Sprintf("%s-%d", sanitizeAgnetRef(role), index+1)
|
||||
taskID := fmt.Sprintf("%s-%d", sanitizeAgentRef(role), index+1)
|
||||
item := gin.H{
|
||||
"task_id": taskID,
|
||||
"role": role,
|
||||
@@ -291,14 +292,14 @@ func agnetRuntimeRequestSwarmAgents(plan agnetOrchestrationPlan) []gin.H {
|
||||
item["sk_sources"] = agent.SKSources
|
||||
}
|
||||
if len(agent.ResourceGrants) > 0 {
|
||||
item["resource_grants"] = agnetRuntimeResourceGrantPayloads(agent.ResourceGrants)
|
||||
item["resource_grants"] = agentRuntimeResourceGrantPayloads(agent.ResourceGrants)
|
||||
}
|
||||
items = append(items, item)
|
||||
}
|
||||
return items
|
||||
}
|
||||
|
||||
func agnetRuntimeResourceGrantPayloads(grants []agnetResourceGrant) []gin.H {
|
||||
func agentRuntimeResourceGrantPayloads(grants []agentResourceGrant) []gin.H {
|
||||
items := make([]gin.H, 0, len(grants))
|
||||
for _, grant := range grants {
|
||||
secretRef := strings.TrimSpace(grant.SecretRef)
|
||||
@@ -319,8 +320,8 @@ func agnetRuntimeResourceGrantPayloads(grants []agnetResourceGrant) []gin.H {
|
||||
"status": grant.Status,
|
||||
"ref": secretRef,
|
||||
"secret_ref": secretRef,
|
||||
"allowed_ref": agnetGrantResourceRef(grant),
|
||||
"resource_ref_hint": agnetGrantResourceRef(grant),
|
||||
"allowed_ref": agentGrantResourceRef(grant),
|
||||
"resource_ref_hint": agentGrantResourceRef(grant),
|
||||
}
|
||||
if grant.TenantID != "" {
|
||||
item["tenant_id"] = grant.TenantID
|
||||
@@ -342,23 +343,23 @@ func agnetRuntimeResourceGrantPayloads(grants []agnetResourceGrant) []gin.H {
|
||||
return items
|
||||
}
|
||||
|
||||
func agnetRuntimeRequestResourceGrants(plan agnetOrchestrationPlan) []gin.H {
|
||||
items := make([]agnetResourceGrant, 0, len(plan.ResourceGrants))
|
||||
func agentRuntimeRequestResourceGrants(plan agentOrchestrationPlan) []gin.H {
|
||||
items := make([]agentResourceGrant, 0, len(plan.ResourceGrants))
|
||||
items = append(items, plan.ResourceGrants...)
|
||||
for _, agent := range plan.Agents {
|
||||
items = append(items, agent.ResourceGrants...)
|
||||
}
|
||||
return agnetRuntimeResourceGrantPayloads(items)
|
||||
return agentRuntimeResourceGrantPayloads(items)
|
||||
}
|
||||
|
||||
func agnetRuntimeRequestMetadata(record agnetDeploymentRecord, source string) gin.H {
|
||||
func agentRuntimeRequestMetadata(record agentDeploymentRecord, 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,
|
||||
"runtime_mode": agnetRuntimeModeForRecord(record),
|
||||
"runtime_mode": agentRuntimeModeForRecord(record),
|
||||
}
|
||||
if record.Plan.Metadata.TenantID != "" {
|
||||
metadata["tenant_id"] = record.Plan.Metadata.TenantID
|
||||
@@ -369,7 +370,7 @@ func agnetRuntimeRequestMetadata(record agnetDeploymentRecord, source string) gi
|
||||
return metadata
|
||||
}
|
||||
|
||||
func agnetRuntimeBudgetPayload(budget agnetBudget) gin.H {
|
||||
func agentRuntimeBudgetPayload(budget agentBudget) gin.H {
|
||||
return gin.H{
|
||||
"max_tokens": budget.MaxTokens,
|
||||
"token_limit": budget.MaxTokens,
|
||||
@@ -380,8 +381,8 @@ func agnetRuntimeBudgetPayload(budget agnetBudget) gin.H {
|
||||
}
|
||||
}
|
||||
|
||||
func agnetRuntimeOrchestrationPlanPayload(record agnetDeploymentRecord) any {
|
||||
if agnetRuntimeModeForRecord(record) != agnetRuntimeModeSwarm {
|
||||
func agentRuntimeOrchestrationPlanPayload(record agentDeploymentRecord) any {
|
||||
if agentRuntimeModeForRecord(record) != agentRuntimeModeSwarm {
|
||||
return record.Plan
|
||||
}
|
||||
plan := record.Plan
|
||||
@@ -391,50 +392,50 @@ func agnetRuntimeOrchestrationPlanPayload(record agnetDeploymentRecord) any {
|
||||
"objective": plan.Objective,
|
||||
"sub_mode": firstNonEmpty(plan.SubMode, "goal_driven_swarm"),
|
||||
"risk_level": plan.RiskLevel,
|
||||
"budget": agnetRuntimeBudgetPayload(plan.Budget),
|
||||
"budget": agentRuntimeBudgetPayload(plan.Budget),
|
||||
"user_context": plan.UserContext,
|
||||
"billing_context": plan.BillingContext,
|
||||
"agile_context": plan.AgileContext,
|
||||
"agents": agnetRuntimeRequestSwarmAgents(plan),
|
||||
"resource_grants": agnetRuntimeRequestResourceGrants(plan),
|
||||
"agents": agentRuntimeRequestSwarmAgents(plan),
|
||||
"resource_grants": agentRuntimeRequestResourceGrants(plan),
|
||||
"constraints": plan.Constraints,
|
||||
"metadata": agnetRuntimeRequestMetadata(record, "orchestration_plan"),
|
||||
"metadata": agentRuntimeRequestMetadata(record, "orchestration_plan"),
|
||||
"agent_runtime": plan.AgentRuntime,
|
||||
"acceptance": plan.AgileContext.AcceptanceCriteria,
|
||||
"acceptance_tests": plan.AgileContext.AcceptanceCriteria,
|
||||
}
|
||||
}
|
||||
|
||||
func agnetRuntimeCreatePayload(record agnetDeploymentRecord, source string) gin.H {
|
||||
func agentRuntimeCreatePayload(record agentDeploymentRecord, source string) gin.H {
|
||||
callback := gin.H{
|
||||
"url": agnetRuntimeCallbackURL(),
|
||||
"subscribed_events": agnetRuntimeSubscribedEvents(),
|
||||
"url": agentRuntimeCallbackURL(),
|
||||
"subscribed_events": agentRuntimeSubscribedEvents(),
|
||||
}
|
||||
if ref := agnetRuntimeCallbackSigningSecretRef(); ref != "" {
|
||||
if ref := agentRuntimeCallbackSigningSecretRef(); ref != "" {
|
||||
callback["signing_secret_ref"] = ref
|
||||
}
|
||||
return gin.H{
|
||||
"orchestration_plan": agnetRuntimeOrchestrationPlanPayload(record),
|
||||
"agents": agnetRuntimeRequestAgents(record.Plan),
|
||||
"orchestration_plan": agentRuntimeOrchestrationPlanPayload(record),
|
||||
"agents": agentRuntimeRequestAgents(record.Plan),
|
||||
"risk_level": record.Plan.RiskLevel,
|
||||
"budget": agnetRuntimeBudgetPayload(record.Plan.Budget),
|
||||
"budget": agentRuntimeBudgetPayload(record.Plan.Budget),
|
||||
"billing_context": record.Plan.BillingContext,
|
||||
"resource_grants": agnetRuntimeRequestResourceGrants(record.Plan),
|
||||
"resource_grants": agentRuntimeRequestResourceGrants(record.Plan),
|
||||
"callback": callback,
|
||||
"agile_context": record.Plan.AgileContext,
|
||||
"sub_mode": record.Plan.SubMode,
|
||||
"metadata": agnetRuntimeRequestMetadata(record, source),
|
||||
"metadata": agentRuntimeRequestMetadata(record, source),
|
||||
}
|
||||
}
|
||||
|
||||
func extractAgnetRuntimeData(payload map[string]any) map[string]any {
|
||||
func extractAgentRuntimeData(payload map[string]any) map[string]any {
|
||||
if data, ok := payload["data"].(map[string]any); ok {
|
||||
return data
|
||||
}
|
||||
return payload
|
||||
}
|
||||
|
||||
func agnetRuntimeEnvelopeError(payload map[string]any) string {
|
||||
func agentRuntimeEnvelopeError(payload map[string]any) string {
|
||||
success, hasSuccess := payload["success"].(bool)
|
||||
if !hasSuccess || success {
|
||||
return ""
|
||||
@@ -463,44 +464,44 @@ func stringFromMap(values map[string]any, keys ...string) string {
|
||||
return ""
|
||||
}
|
||||
|
||||
func callAgnetRuntimeCreate(ctx context.Context, cfg agnetRuntimeConfig, record agnetDeploymentRecord, source string) (agnetRuntimeSyncResult, error) {
|
||||
endpoint, err := agnetRuntimeURL(cfg.BaseURL, cfg.CreatePath)
|
||||
func callAgentRuntimeCreate(ctx context.Context, cfg agentRuntimeConfig, record agentDeploymentRecord, source string) (agentRuntimeSyncResult, error) {
|
||||
endpoint, err := agentRuntimeURL(cfg.BaseURL, cfg.CreatePath)
|
||||
if err != nil {
|
||||
return agnetRuntimeSyncResult{}, err
|
||||
return agentRuntimeSyncResult{}, err
|
||||
}
|
||||
payload, err := common.Marshal(agnetRuntimeCreatePayload(record, source))
|
||||
payload, err := common.Marshal(agentRuntimeCreatePayload(record, source))
|
||||
if err != nil {
|
||||
return agnetRuntimeSyncResult{}, err
|
||||
return agentRuntimeSyncResult{}, err
|
||||
}
|
||||
req, err := http.NewRequestWithContext(ctx, http.MethodPost, endpoint, bytes.NewReader(payload))
|
||||
if err != nil {
|
||||
return agnetRuntimeSyncResult{}, err
|
||||
return agentRuntimeSyncResult{}, err
|
||||
}
|
||||
agnetRuntimeHeaders(req, cfg, record)
|
||||
agentRuntimeHeaders(req, cfg, record)
|
||||
client := &http.Client{Timeout: cfg.Timeout}
|
||||
resp, err := client.Do(req)
|
||||
if err != nil {
|
||||
return agnetRuntimeSyncResult{}, err
|
||||
return agentRuntimeSyncResult{}, err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
body, readErr := io.ReadAll(io.LimitReader(resp.Body, 1<<20))
|
||||
if readErr != nil {
|
||||
return agnetRuntimeSyncResult{}, readErr
|
||||
return agentRuntimeSyncResult{}, readErr
|
||||
}
|
||||
if resp.StatusCode < http.StatusOK || resp.StatusCode >= http.StatusMultipleChoices {
|
||||
return agnetRuntimeSyncResult{RawStatusCode: resp.StatusCode}, fmt.Errorf("runtime create returned HTTP %d: %s", resp.StatusCode, strings.TrimSpace(string(body)))
|
||||
return agentRuntimeSyncResult{RawStatusCode: resp.StatusCode}, fmt.Errorf("runtime create returned HTTP %d: %s", resp.StatusCode, strings.TrimSpace(string(body)))
|
||||
}
|
||||
var envelope map[string]any
|
||||
if len(body) > 0 {
|
||||
if err := common.Unmarshal(body, &envelope); err != nil {
|
||||
return agnetRuntimeSyncResult{RawStatusCode: resp.StatusCode}, err
|
||||
return agentRuntimeSyncResult{RawStatusCode: resp.StatusCode}, err
|
||||
}
|
||||
}
|
||||
if message := agnetRuntimeEnvelopeError(envelope); message != "" {
|
||||
return agnetRuntimeSyncResult{RawStatusCode: resp.StatusCode}, errors.New(message)
|
||||
if message := agentRuntimeEnvelopeError(envelope); message != "" {
|
||||
return agentRuntimeSyncResult{RawStatusCode: resp.StatusCode}, errors.New(message)
|
||||
}
|
||||
data := extractAgnetRuntimeData(envelope)
|
||||
result := agnetRuntimeSyncResult{
|
||||
data := extractAgentRuntimeData(envelope)
|
||||
result := agentRuntimeSyncResult{
|
||||
RuntimeDeploymentID: stringFromMap(data, "runtime_deployment_id", "deployment_id", "id"),
|
||||
RuntimeSwarmID: stringFromMap(data, "swarm_id", "runtime_swarm_id"),
|
||||
RuntimeStatus: stringFromMap(data, "runtime_status", "status"),
|
||||
@@ -509,16 +510,18 @@ func callAgnetRuntimeCreate(ctx context.Context, cfg agnetRuntimeConfig, record
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func agnetRuntimeStopPath(cfg agnetRuntimeConfig, runtimeDeploymentID string) string {
|
||||
func agentRuntimeStopPath(cfg agentRuntimeConfig, runtimeDeploymentID string) string {
|
||||
path := strings.TrimSpace(cfg.StopPath)
|
||||
if path == "" {
|
||||
path = "/api/agnet/deployments/{deployment_id}/stop"
|
||||
path = "/api/agent/deployments/{deployment_id}/stop"
|
||||
}
|
||||
return strings.ReplaceAll(path, "{deployment_id}", url.PathEscape(runtimeDeploymentID))
|
||||
}
|
||||
|
||||
func agnetRuntimeStopPathForRecord(cfg agnetRuntimeConfig, record agnetDeploymentRecord) string {
|
||||
path := agnetRuntimeStopPath(cfg, strings.TrimSpace(record.RuntimeDeploymentID))
|
||||
func agentRuntimeStopPathForRecord(cfg agentRuntimeConfig, record agentDeploymentRecord) string {
|
||||
// {deployment_id} falls back to the swarm id so sub-agile-style paths still
|
||||
// resolve for records that only persisted a runtime swarm id.
|
||||
path := agentRuntimeStopPath(cfg, firstNonEmpty(record.RuntimeDeploymentID, record.RuntimeSwarmID))
|
||||
replacer := strings.NewReplacer(
|
||||
"{swarm_id}", url.PathEscape(strings.TrimSpace(record.RuntimeSwarmID)),
|
||||
"{runtime_swarm_id}", url.PathEscape(strings.TrimSpace(record.RuntimeSwarmID)),
|
||||
@@ -528,7 +531,7 @@ func agnetRuntimeStopPathForRecord(cfg agnetRuntimeConfig, record agnetDeploymen
|
||||
return replacer.Replace(path)
|
||||
}
|
||||
|
||||
func agnetRuntimeStatusPathForRecord(cfg agnetRuntimeConfig, record agnetDeploymentRecord) string {
|
||||
func agentRuntimeStatusPathForRecord(cfg agentRuntimeConfig, record agentDeploymentRecord) string {
|
||||
path := strings.TrimSpace(cfg.StatusPath)
|
||||
if path == "" {
|
||||
path = "/api/swarms/{swarm_id}/status"
|
||||
@@ -543,7 +546,7 @@ func agnetRuntimeStatusPathForRecord(cfg agnetRuntimeConfig, record agnetDeploym
|
||||
return replacer.Replace(path)
|
||||
}
|
||||
|
||||
func agnetRuntimeArtifactContentPathForRecord(cfg agnetRuntimeConfig, record agnetDeploymentRecord, artifactID string) string {
|
||||
func agentRuntimeArtifactContentPathForRecord(cfg agentRuntimeConfig, record agentDeploymentRecord, artifactID string) string {
|
||||
path := strings.TrimSpace(cfg.ArtifactContentPath)
|
||||
if path == "" {
|
||||
path = "/api/swarms/{swarm_id}/artifacts/{artifact_id}/content"
|
||||
@@ -559,11 +562,11 @@ func agnetRuntimeArtifactContentPathForRecord(cfg agnetRuntimeConfig, record agn
|
||||
return replacer.Replace(path)
|
||||
}
|
||||
|
||||
func callAgnetRuntimeStatus(ctx context.Context, cfg agnetRuntimeConfig, record agnetDeploymentRecord) (map[string]any, int, error) {
|
||||
func callAgentRuntimeStatus(ctx context.Context, cfg agentRuntimeConfig, record agentDeploymentRecord) (map[string]any, int, error) {
|
||||
if strings.TrimSpace(record.RuntimeSwarmID) == "" && strings.TrimSpace(record.RuntimeDeploymentID) == "" {
|
||||
return nil, 0, errors.New("runtime identifiers missing")
|
||||
}
|
||||
endpoint, err := agnetRuntimeURL(cfg.BaseURL, agnetRuntimeStatusPathForRecord(cfg, record))
|
||||
endpoint, err := agentRuntimeURL(cfg.BaseURL, agentRuntimeStatusPathForRecord(cfg, record))
|
||||
if err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
@@ -571,7 +574,7 @@ func callAgnetRuntimeStatus(ctx context.Context, cfg agnetRuntimeConfig, record
|
||||
if err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
agnetRuntimeHeaders(req, cfg, record)
|
||||
agentRuntimeHeaders(req, cfg, record)
|
||||
client := &http.Client{Timeout: cfg.Timeout}
|
||||
resp, err := client.Do(req)
|
||||
if err != nil {
|
||||
@@ -591,20 +594,20 @@ func callAgnetRuntimeStatus(ctx context.Context, cfg agnetRuntimeConfig, record
|
||||
return nil, resp.StatusCode, err
|
||||
}
|
||||
}
|
||||
if message := agnetRuntimeEnvelopeError(envelope); message != "" {
|
||||
if message := agentRuntimeEnvelopeError(envelope); message != "" {
|
||||
return nil, resp.StatusCode, errors.New(message)
|
||||
}
|
||||
return extractAgnetRuntimeData(envelope), resp.StatusCode, nil
|
||||
return extractAgentRuntimeData(envelope), resp.StatusCode, nil
|
||||
}
|
||||
|
||||
func callAgnetRuntimeArtifactContent(ctx context.Context, cfg agnetRuntimeConfig, record agnetDeploymentRecord, artifactID string) (*http.Response, error) {
|
||||
func callAgentRuntimeArtifactContent(ctx context.Context, cfg agentRuntimeConfig, record agentDeploymentRecord, artifactID string) (*http.Response, error) {
|
||||
if strings.TrimSpace(artifactID) == "" {
|
||||
return nil, errors.New("artifact_id is required")
|
||||
}
|
||||
if strings.TrimSpace(record.RuntimeSwarmID) == "" && strings.TrimSpace(record.RuntimeDeploymentID) == "" {
|
||||
return nil, errors.New("runtime identifiers missing")
|
||||
}
|
||||
endpoint, err := agnetRuntimeURL(cfg.BaseURL, agnetRuntimeArtifactContentPathForRecord(cfg, record, artifactID))
|
||||
endpoint, err := agentRuntimeURL(cfg.BaseURL, agentRuntimeArtifactContentPathForRecord(cfg, record, artifactID))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -612,7 +615,7 @@ func callAgnetRuntimeArtifactContent(ctx context.Context, cfg agnetRuntimeConfig
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
agnetRuntimeHeaders(req, cfg, record)
|
||||
agentRuntimeHeaders(req, cfg, record)
|
||||
req.Header.Set("Accept", "*/*")
|
||||
client := &http.Client{Timeout: cfg.Timeout}
|
||||
resp, err := client.Do(req)
|
||||
@@ -719,6 +722,14 @@ func anyPositiveFileSignal(values map[string]any) bool {
|
||||
// artifact_type="document" (its no-files fallback) under a uri scheme that the
|
||||
// old "/artifacts/summary" heuristic no longer matched.
|
||||
func artifactIsSummaryOnly(artifact gin.H) bool {
|
||||
// Runtime marks fallback artifacts (no real agent output) with
|
||||
// metadata.synthesized=true — the authoritative non-deliverable signal
|
||||
// (agent_management Sub Mode Runtime §7.2).
|
||||
if meta, ok := artifact["metadata"].(map[string]any); ok {
|
||||
if synth, ok := meta["synthesized"].(bool); ok && synth {
|
||||
return true
|
||||
}
|
||||
}
|
||||
atype := strings.ToLower(strings.TrimSpace(fmt.Sprint(artifact["artifact_type"])))
|
||||
if deliverableArtifactTypes[atype] || artifactHasFileChanges(artifact) {
|
||||
return false
|
||||
@@ -749,7 +760,61 @@ func runtimeArtifactsAreSummaryOnly(artifacts []gin.H) bool {
|
||||
return true
|
||||
}
|
||||
|
||||
func buildAgnetRuntimeDiagnostics(record agnetDeploymentRecord, data map[string]any, httpStatus int, source string) agnetRuntimeDiagnostics {
|
||||
// persistedArtifactToGin adapts a stored artifact to the gin.H shape consumed
|
||||
// by artifactIsSummaryOnly.
|
||||
func persistedArtifactToGin(a model.AgentArtifact) gin.H {
|
||||
g := gin.H{
|
||||
"artifact_id": a.ArtifactID,
|
||||
"artifact_type": a.ArtifactType,
|
||||
"title": a.Title,
|
||||
"summary": a.Summary,
|
||||
"uri": a.URI,
|
||||
}
|
||||
if strings.TrimSpace(a.MetadataJSON) != "" {
|
||||
var meta map[string]any
|
||||
if err := common.UnmarshalJsonStr(a.MetadataJSON, &meta); err == nil && len(meta) > 0 {
|
||||
g["metadata"] = meta
|
||||
}
|
||||
}
|
||||
return g
|
||||
}
|
||||
|
||||
// agentDeploymentDisplayStatus is the single status the client should show.
|
||||
// Manager is the sole judge (unified spec §10.6): a `completed` runtime status
|
||||
// is only surfaced as `completed` when there is a real (non-summary)
|
||||
// deliverable; otherwise it is downgraded so an empty result is not shown as
|
||||
// success — `needs_codegen` when only a plan/summary exists, or
|
||||
// `completed_without_deliverable` when no artifact exists at all.
|
||||
func agentDeploymentDisplayStatus(record agentDeploymentRecord) string {
|
||||
if strings.ToLower(strings.TrimSpace(record.Status)) != "completed" {
|
||||
return record.Status
|
||||
}
|
||||
artifacts, err := model.ListAgentArtifacts(model.ListAgentArtifactsFilter{
|
||||
DeploymentID: record.DeploymentID,
|
||||
Limit: 500,
|
||||
})
|
||||
if err != nil {
|
||||
common.SysLog("agentDeploymentDisplayStatus: " + err.Error())
|
||||
return record.Status
|
||||
}
|
||||
for _, a := range artifacts {
|
||||
if !artifactIsSummaryOnly(persistedArtifactToGin(a)) {
|
||||
return "completed"
|
||||
}
|
||||
}
|
||||
if len(artifacts) > 0 {
|
||||
return "needs_codegen"
|
||||
}
|
||||
return "completed_without_deliverable"
|
||||
}
|
||||
|
||||
// withDisplayStatus returns the record with DisplayStatus computed for response.
|
||||
func withDisplayStatus(record agentDeploymentRecord) agentDeploymentRecord {
|
||||
record.DisplayStatus = agentDeploymentDisplayStatus(record)
|
||||
return record
|
||||
}
|
||||
|
||||
func buildAgentRuntimeDiagnostics(record agentDeploymentRecord, data map[string]any, httpStatus int, source string) agentRuntimeDiagnostics {
|
||||
agents := mapSliceFromAny(data["agents"])
|
||||
artifacts := mapSliceFromAny(data["artifacts"])
|
||||
status := stringFromMap(data, "runtime_status", "status")
|
||||
@@ -771,9 +836,9 @@ func buildAgnetRuntimeDiagnostics(record agnetDeploymentRecord, data map[string]
|
||||
warnings = append(warnings, "runtime_zero_model_usage")
|
||||
}
|
||||
}
|
||||
return agnetRuntimeDiagnostics{
|
||||
return agentRuntimeDiagnostics{
|
||||
DeploymentID: record.DeploymentID,
|
||||
RuntimeMode: agnetRuntimeModeForRecord(record),
|
||||
RuntimeMode: agentRuntimeModeForRecord(record),
|
||||
SubMode: record.SubMode,
|
||||
RuntimeDeploymentID: record.RuntimeDeploymentID,
|
||||
RuntimeSwarmID: record.RuntimeSwarmID,
|
||||
@@ -787,36 +852,36 @@ func buildAgnetRuntimeDiagnostics(record agnetDeploymentRecord, data map[string]
|
||||
Artifacts: artifacts,
|
||||
Metrics: metrics,
|
||||
Warnings: warnings,
|
||||
CheckedAt: agnetNow(),
|
||||
CheckedAt: agentNow(),
|
||||
}
|
||||
}
|
||||
|
||||
func agnetRuntimeDiagnosticsForRecord(ctx context.Context, record agnetDeploymentRecord) agnetRuntimeDiagnostics {
|
||||
mode := agnetRuntimeModeForRecord(record)
|
||||
cfg := agnetRuntimeClientConfigForMode(mode)
|
||||
func agentRuntimeDiagnosticsForRecord(ctx context.Context, record agentDeploymentRecord) agentRuntimeDiagnostics {
|
||||
mode := agentRuntimeModeForRecord(record)
|
||||
cfg := agentRuntimeClientConfigForMode(mode)
|
||||
if !cfg.Enabled || strings.TrimSpace(cfg.BaseURL) == "" {
|
||||
return agnetRuntimeDiagnostics{
|
||||
return agentRuntimeDiagnostics{
|
||||
DeploymentID: record.DeploymentID,
|
||||
RuntimeMode: mode,
|
||||
SubMode: record.SubMode,
|
||||
DataSource: "not_configured",
|
||||
Warnings: []string{"runtime_not_configured"},
|
||||
CheckedAt: agnetNow(),
|
||||
CheckedAt: agentNow(),
|
||||
}
|
||||
}
|
||||
if strings.TrimSpace(record.RuntimeSwarmID) == "" && strings.TrimSpace(record.RuntimeDeploymentID) == "" {
|
||||
return agnetRuntimeDiagnostics{
|
||||
return agentRuntimeDiagnostics{
|
||||
DeploymentID: record.DeploymentID,
|
||||
RuntimeMode: mode,
|
||||
SubMode: record.SubMode,
|
||||
DataSource: "missing_runtime_id",
|
||||
Warnings: []string{"runtime_identifiers_missing"},
|
||||
CheckedAt: agnetNow(),
|
||||
CheckedAt: agentNow(),
|
||||
}
|
||||
}
|
||||
data, status, err := callAgnetRuntimeStatus(ctx, cfg, record)
|
||||
data, status, err := callAgentRuntimeStatus(ctx, cfg, record)
|
||||
if err != nil {
|
||||
return agnetRuntimeDiagnostics{
|
||||
return agentRuntimeDiagnostics{
|
||||
DeploymentID: record.DeploymentID,
|
||||
RuntimeMode: mode,
|
||||
SubMode: record.SubMode,
|
||||
@@ -824,25 +889,25 @@ func agnetRuntimeDiagnosticsForRecord(ctx context.Context, record agnetDeploymen
|
||||
RuntimeSwarmID: record.RuntimeSwarmID,
|
||||
DataSource: "runtime_status_error",
|
||||
HTTPStatus: status,
|
||||
ErrorMessage: truncateAgnetFailureReason(err.Error()),
|
||||
ErrorMessage: truncateAgentFailureReason(err.Error()),
|
||||
Warnings: []string{"runtime_status_query_failed"},
|
||||
CheckedAt: agnetNow(),
|
||||
CheckedAt: agentNow(),
|
||||
}
|
||||
}
|
||||
return buildAgnetRuntimeDiagnostics(record, data, status, "runtime_status")
|
||||
return buildAgentRuntimeDiagnostics(record, data, status, "runtime_status")
|
||||
}
|
||||
|
||||
func AgnetGetUserDeploymentRuntimeDiagnostics(c *gin.Context) {
|
||||
record, ok := requireAuthenticatedUserAgnetDeployment(c)
|
||||
func AgentGetUserDeploymentRuntimeDiagnostics(c *gin.Context) {
|
||||
record, ok := requireAuthenticatedUserAgentDeployment(c)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
ctx, cancel := context.WithTimeout(c.Request.Context(), agnetRuntimeClientConfigForMode(agnetRuntimeModeForRecord(record)).Timeout)
|
||||
ctx, cancel := context.WithTimeout(c.Request.Context(), agentRuntimeClientConfigForMode(agentRuntimeModeForRecord(record)).Timeout)
|
||||
defer cancel()
|
||||
common.ApiSuccess(c, agnetRuntimeDiagnosticsForRecord(ctx, record))
|
||||
common.ApiSuccess(c, agentRuntimeDiagnosticsForRecord(ctx, record))
|
||||
}
|
||||
|
||||
func agnetRuntimeApprovalDecisionPath(cfg agnetRuntimeConfig, record agnetDeploymentRecord, approvalID string) string {
|
||||
func agentRuntimeApprovalDecisionPath(cfg agentRuntimeConfig, record agentDeploymentRecord, approvalID string) string {
|
||||
path := strings.TrimSpace(cfg.ApprovalDecisionPath)
|
||||
if path == "" {
|
||||
path = "/api/swarms/{swarm_id}/approvals/{approval_id}"
|
||||
@@ -858,8 +923,8 @@ func agnetRuntimeApprovalDecisionPath(cfg agnetRuntimeConfig, record agnetDeploy
|
||||
return replacer.Replace(path)
|
||||
}
|
||||
|
||||
func callAgnetRuntimeApprovalDecision(ctx context.Context, cfg agnetRuntimeConfig, record agnetDeploymentRecord, approval model.AgnetApprovalRequest, lease *model.AgnetCredentialLease, decision string) error {
|
||||
endpoint, err := agnetRuntimeURL(cfg.BaseURL, agnetRuntimeApprovalDecisionPath(cfg, record, approval.ApprovalID))
|
||||
func callAgentRuntimeApprovalDecision(ctx context.Context, cfg agentRuntimeConfig, record agentDeploymentRecord, approval model.AgentApprovalRequest, lease *model.AgentCredentialLease, decision string) error {
|
||||
endpoint, err := agentRuntimeURL(cfg.BaseURL, agentRuntimeApprovalDecisionPath(cfg, record, approval.ApprovalID))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -922,87 +987,87 @@ func callAgnetRuntimeApprovalDecision(ctx context.Context, cfg agnetRuntimeConfi
|
||||
if err := common.Unmarshal(respBody, &envelope); err != nil {
|
||||
return err
|
||||
}
|
||||
if message := agnetRuntimeEnvelopeError(envelope); message != "" {
|
||||
if message := agentRuntimeEnvelopeError(envelope); message != "" {
|
||||
return errors.New(message)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func syncAgnetRuntimeApprovalDecision(c *gin.Context, approval *model.AgnetApprovalRequest, lease *model.AgnetCredentialLease, decision string) {
|
||||
func syncAgentRuntimeApprovalDecision(c *gin.Context, approval *model.AgentApprovalRequest, lease *model.AgentCredentialLease, decision string) {
|
||||
if approval == nil || strings.TrimSpace(approval.DeploymentID) == "" {
|
||||
return
|
||||
}
|
||||
if !agnetRuntimeClientConfigForMode(agnetRuntimeModeAgnet).Enabled && !agnetRuntimeClientConfigForMode(agnetRuntimeModeSwarm).Enabled {
|
||||
if !agentRuntimeClientConfigForMode(agentRuntimeModeAgent).Enabled && !agentRuntimeClientConfigForMode(agentRuntimeModeSwarm).Enabled {
|
||||
return
|
||||
}
|
||||
record, ok := findAgnetDeploymentRecord(approval.DeploymentID)
|
||||
record, ok := findAgentDeploymentRecord(approval.DeploymentID)
|
||||
if !ok {
|
||||
recordAgnetApprovalAudit("runtime.approval_decision.skipped", approval, lease, "skipped", "deployment not found")
|
||||
recordAgentApprovalAudit("runtime.approval_decision.skipped", approval, lease, "skipped", "deployment not found")
|
||||
return
|
||||
}
|
||||
cfg := agnetRuntimeClientConfigForMode(agnetRuntimeModeForRecord(record))
|
||||
cfg := agentRuntimeClientConfigForMode(agentRuntimeModeForRecord(record))
|
||||
if !cfg.Enabled {
|
||||
return
|
||||
}
|
||||
if strings.TrimSpace(record.RuntimeSwarmID) == "" && strings.TrimSpace(record.RuntimeDeploymentID) == "" {
|
||||
recordAgnetApprovalAudit("runtime.approval_decision.skipped", approval, lease, "skipped", "runtime identifiers missing")
|
||||
recordAgentApprovalAudit("runtime.approval_decision.skipped", approval, lease, "skipped", "runtime identifiers missing")
|
||||
return
|
||||
}
|
||||
ctx, cancel := context.WithTimeout(c.Request.Context(), cfg.Timeout)
|
||||
defer cancel()
|
||||
if err := callAgnetRuntimeApprovalDecision(ctx, cfg, record, *approval, lease, decision); err != nil {
|
||||
recordAgnetApprovalAudit("runtime.approval_decision.failed", approval, lease, "failed", truncateAgnetFailureReason(err.Error()))
|
||||
common.SysLog("Agnet runtime approval decision failed for " + approval.ApprovalID + ": " + err.Error())
|
||||
if err := callAgentRuntimeApprovalDecision(ctx, cfg, record, *approval, lease, decision); err != nil {
|
||||
recordAgentApprovalAudit("runtime.approval_decision.failed", approval, lease, "failed", truncateAgentFailureReason(err.Error()))
|
||||
common.SysLog("Agent runtime approval decision failed for " + approval.ApprovalID + ": " + err.Error())
|
||||
return
|
||||
}
|
||||
recordAgnetApprovalAudit("runtime.approval_decision.accepted", approval, lease, "ok", "")
|
||||
recordAgentApprovalAudit("runtime.approval_decision.accepted", approval, lease, "ok", "")
|
||||
}
|
||||
|
||||
func callAgnetRuntimeStop(ctx context.Context, cfg agnetRuntimeConfig, record agnetDeploymentRecord, reason string) (agnetRuntimeSyncResult, error) {
|
||||
func callAgentRuntimeStop(ctx context.Context, cfg agentRuntimeConfig, record agentDeploymentRecord, reason string) (agentRuntimeSyncResult, error) {
|
||||
runtimeDeploymentID := strings.TrimSpace(record.RuntimeDeploymentID)
|
||||
if runtimeDeploymentID == "" {
|
||||
return agnetRuntimeSyncResult{}, nil
|
||||
return agentRuntimeSyncResult{}, nil
|
||||
}
|
||||
endpoint, err := agnetRuntimeURL(cfg.BaseURL, agnetRuntimeStopPathForRecord(cfg, record))
|
||||
endpoint, err := agentRuntimeURL(cfg.BaseURL, agentRuntimeStopPathForRecord(cfg, record))
|
||||
if err != nil {
|
||||
return agnetRuntimeSyncResult{}, err
|
||||
return agentRuntimeSyncResult{}, err
|
||||
}
|
||||
payload, err := common.Marshal(gin.H{
|
||||
"reason": firstNonEmpty(strings.TrimSpace(reason), "Heicode Manager requested stop"),
|
||||
"manager_deployment_id": record.DeploymentID,
|
||||
})
|
||||
if err != nil {
|
||||
return agnetRuntimeSyncResult{}, err
|
||||
return agentRuntimeSyncResult{}, err
|
||||
}
|
||||
req, err := http.NewRequestWithContext(ctx, http.MethodPost, endpoint, bytes.NewReader(payload))
|
||||
if err != nil {
|
||||
return agnetRuntimeSyncResult{}, err
|
||||
return agentRuntimeSyncResult{}, err
|
||||
}
|
||||
agnetRuntimeHeaders(req, cfg, record)
|
||||
agentRuntimeHeaders(req, cfg, record)
|
||||
client := &http.Client{Timeout: cfg.Timeout}
|
||||
resp, err := client.Do(req)
|
||||
if err != nil {
|
||||
return agnetRuntimeSyncResult{}, err
|
||||
return agentRuntimeSyncResult{}, err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
body, readErr := io.ReadAll(io.LimitReader(resp.Body, 1<<20))
|
||||
if readErr != nil {
|
||||
return agnetRuntimeSyncResult{}, readErr
|
||||
return agentRuntimeSyncResult{}, readErr
|
||||
}
|
||||
if resp.StatusCode < http.StatusOK || resp.StatusCode >= http.StatusMultipleChoices {
|
||||
return agnetRuntimeSyncResult{RawStatusCode: resp.StatusCode}, fmt.Errorf("runtime stop returned HTTP %d: %s", resp.StatusCode, strings.TrimSpace(string(body)))
|
||||
return agentRuntimeSyncResult{RawStatusCode: resp.StatusCode}, fmt.Errorf("runtime stop returned HTTP %d: %s", resp.StatusCode, strings.TrimSpace(string(body)))
|
||||
}
|
||||
var envelope map[string]any
|
||||
if len(body) > 0 {
|
||||
if err := common.Unmarshal(body, &envelope); err != nil {
|
||||
return agnetRuntimeSyncResult{RawStatusCode: resp.StatusCode}, err
|
||||
return agentRuntimeSyncResult{RawStatusCode: resp.StatusCode}, err
|
||||
}
|
||||
}
|
||||
if message := agnetRuntimeEnvelopeError(envelope); message != "" {
|
||||
return agnetRuntimeSyncResult{RawStatusCode: resp.StatusCode}, errors.New(message)
|
||||
if message := agentRuntimeEnvelopeError(envelope); message != "" {
|
||||
return agentRuntimeSyncResult{RawStatusCode: resp.StatusCode}, errors.New(message)
|
||||
}
|
||||
data := extractAgnetRuntimeData(envelope)
|
||||
return agnetRuntimeSyncResult{
|
||||
data := extractAgentRuntimeData(envelope)
|
||||
return agentRuntimeSyncResult{
|
||||
RuntimeDeploymentID: firstNonEmpty(stringFromMap(data, "runtime_deployment_id", "deployment_id", "id"), runtimeDeploymentID),
|
||||
RuntimeSwarmID: stringFromMap(data, "swarm_id", "runtime_swarm_id"),
|
||||
RuntimeStatus: stringFromMap(data, "runtime_status", "status"),
|
||||
@@ -1010,25 +1075,25 @@ func callAgnetRuntimeStop(ctx context.Context, cfg agnetRuntimeConfig, record ag
|
||||
}, nil
|
||||
}
|
||||
|
||||
func syncAgnetRuntimeStop(c *gin.Context, record agnetDeploymentRecord, reason string) (agnetDeploymentRecord, bool) {
|
||||
cfg := agnetRuntimeClientConfigForMode(agnetRuntimeModeForRecord(record))
|
||||
func syncAgentRuntimeStop(c *gin.Context, record agentDeploymentRecord, reason string) (agentDeploymentRecord, bool) {
|
||||
cfg := agentRuntimeClientConfigForMode(agentRuntimeModeForRecord(record))
|
||||
if !cfg.Enabled || strings.TrimSpace(record.RuntimeDeploymentID) == "" {
|
||||
return record, true
|
||||
}
|
||||
ctx, cancel := context.WithTimeout(c.Request.Context(), cfg.Timeout)
|
||||
defer cancel()
|
||||
result, err := callAgnetRuntimeStop(ctx, cfg, record, reason)
|
||||
record.RuntimeLastSyncAt = agnetNow()
|
||||
result, err := callAgentRuntimeStop(ctx, cfg, record, reason)
|
||||
record.RuntimeLastSyncAt = agentNow()
|
||||
if err != nil {
|
||||
record.RuntimeState = agnetRuntimeStateFailed
|
||||
record.FailureReason = truncateAgnetFailureReason(err.Error())
|
||||
record.UpdatedAt = agnetNow()
|
||||
_ = updateAgnetDeploymentRecord(record)
|
||||
agnetMu.Lock()
|
||||
agnetDeployments[record.DeploymentID] = record
|
||||
agnetMu.Unlock()
|
||||
recordAgnetRuntimeSyncAudit(record, "runtime.stop.failed", "failed")
|
||||
agnetError(c, "RUNTIME_STOP_FAILED", record.FailureReason)
|
||||
record.RuntimeState = agentRuntimeStateFailed
|
||||
record.FailureReason = truncateAgentFailureReason(err.Error())
|
||||
record.UpdatedAt = agentNow()
|
||||
_ = updateAgentDeploymentRecord(record)
|
||||
agentMu.Lock()
|
||||
agentDeployments[record.DeploymentID] = record
|
||||
agentMu.Unlock()
|
||||
recordAgentRuntimeSyncAudit(record, "runtime.stop.failed", "failed")
|
||||
agentError(c, "RUNTIME_STOP_FAILED", record.FailureReason)
|
||||
return record, false
|
||||
}
|
||||
record.RuntimeState = firstNonEmpty(result.RuntimeStatus, "stopped")
|
||||
@@ -1039,33 +1104,33 @@ func syncAgnetRuntimeStop(c *gin.Context, record agnetDeploymentRecord, reason s
|
||||
record.RuntimeSwarmID = result.RuntimeSwarmID
|
||||
}
|
||||
record.FailureReason = ""
|
||||
recordAgnetRuntimeSyncAudit(record, "runtime.stop.accepted", "ok")
|
||||
recordAgentRuntimeSyncAudit(record, "runtime.stop.accepted", "ok")
|
||||
return record, true
|
||||
}
|
||||
|
||||
func updateAgnetRuntimeSyncState(record agnetDeploymentRecord, result agnetRuntimeSyncResult, syncErr error) agnetDeploymentRecord {
|
||||
record.RuntimeLastSyncAt = agnetNow()
|
||||
func updateAgentRuntimeSyncState(record agentDeploymentRecord, result agentRuntimeSyncResult, syncErr error) agentDeploymentRecord {
|
||||
record.RuntimeLastSyncAt = agentNow()
|
||||
if syncErr != nil {
|
||||
record.RuntimeState = agnetRuntimeStateFailed
|
||||
record.FailureReason = truncateAgnetFailureReason(syncErr.Error())
|
||||
record.RuntimeState = agentRuntimeStateFailed
|
||||
record.FailureReason = truncateAgentFailureReason(syncErr.Error())
|
||||
} else {
|
||||
record.RuntimeState = firstNonEmpty(result.RuntimeStatus, agnetRuntimeStateSynced)
|
||||
record.RuntimeState = firstNonEmpty(result.RuntimeStatus, agentRuntimeStateSynced)
|
||||
record.RuntimeDeploymentID = result.RuntimeDeploymentID
|
||||
record.RuntimeSwarmID = result.RuntimeSwarmID
|
||||
record.FailureReason = ""
|
||||
}
|
||||
record.UpdatedAt = agnetNow()
|
||||
if err := updateAgnetDeploymentRecord(record); err != nil {
|
||||
common.SysLog("updateAgnetRuntimeSyncState: " + err.Error())
|
||||
record.UpdatedAt = agentNow()
|
||||
if err := updateAgentDeploymentRecord(record); err != nil {
|
||||
common.SysLog("updateAgentRuntimeSyncState: " + err.Error())
|
||||
}
|
||||
agnetMu.Lock()
|
||||
agnetDeployments[record.DeploymentID] = record
|
||||
agnetMu.Unlock()
|
||||
agentMu.Lock()
|
||||
agentDeployments[record.DeploymentID] = record
|
||||
agentMu.Unlock()
|
||||
return record
|
||||
}
|
||||
|
||||
func recordAgnetRuntimeSyncAudit(record agnetDeploymentRecord, event string, result string) {
|
||||
recordAgnetAuditEvent(agnetEvent{
|
||||
func recordAgentRuntimeSyncAudit(record agentDeploymentRecord, event string, result string) {
|
||||
recordAgentAuditEvent(agentEvent{
|
||||
EventID: "evt_" + common.GetUUID()[:12],
|
||||
Event: event,
|
||||
SchemaVersion: 1,
|
||||
@@ -1074,79 +1139,79 @@ func recordAgnetRuntimeSyncAudit(record agnetDeploymentRecord, event string, res
|
||||
BindingScope: firstPlanBindingScope(record.Plan),
|
||||
DeploymentID: record.DeploymentID,
|
||||
CorrelationID: record.Plan.Metadata.CorrelationID,
|
||||
OccurredAt: agnetNow(),
|
||||
}, "agnet_runtime_bridge", record.DeploymentID, "", result)
|
||||
OccurredAt: agentNow(),
|
||||
}, "agent_runtime_bridge", record.DeploymentID, "", result)
|
||||
}
|
||||
|
||||
func dispatchAgnetRuntimeCreate(record agnetDeploymentRecord, source string, cfg agnetRuntimeConfig) agnetDeploymentRecord {
|
||||
recordAgnetRuntimeSyncAudit(record, "runtime.sync.started", "started")
|
||||
func dispatchAgentRuntimeCreate(record agentDeploymentRecord, source string, cfg agentRuntimeConfig) agentDeploymentRecord {
|
||||
recordAgentRuntimeSyncAudit(record, "runtime.sync.started", "started")
|
||||
ctx, cancel := context.WithTimeout(context.Background(), cfg.Timeout)
|
||||
defer cancel()
|
||||
result, err := callAgnetRuntimeCreate(ctx, cfg, record, source)
|
||||
record = updateAgnetRuntimeSyncState(record, result, err)
|
||||
result, err := callAgentRuntimeCreate(ctx, cfg, record, source)
|
||||
record = updateAgentRuntimeSyncState(record, result, err)
|
||||
if err != nil {
|
||||
common.SysLog("Agnet runtime shadow create failed for " + record.DeploymentID + ": " + err.Error())
|
||||
recordAgnetRuntimeSyncAudit(record, "runtime.sync.failed", "failed")
|
||||
common.SysLog("Agent runtime shadow create failed for " + record.DeploymentID + ": " + err.Error())
|
||||
recordAgentRuntimeSyncAudit(record, "runtime.sync.failed", "failed")
|
||||
return record
|
||||
}
|
||||
recordAgnetRuntimeSyncAudit(record, "runtime.sync.accepted", "ok")
|
||||
recordAgentRuntimeSyncAudit(record, "runtime.sync.accepted", "ok")
|
||||
return record
|
||||
}
|
||||
|
||||
func maybeDispatchAgnetRuntimeCreate(c *gin.Context, record agnetDeploymentRecord, source string) agnetDeploymentRecord {
|
||||
mode := agnetRuntimeModeForSource(source)
|
||||
func maybeDispatchAgentRuntimeCreate(c *gin.Context, record agentDeploymentRecord, source string) agentDeploymentRecord {
|
||||
mode := agentRuntimeModeForSource(source)
|
||||
if strings.TrimSpace(record.Plan.Metadata.RuntimeMode) == "" {
|
||||
record.Plan.Metadata.RuntimeMode = mode
|
||||
} else {
|
||||
mode = agnetRuntimeModeForRecord(record)
|
||||
mode = agentRuntimeModeForRecord(record)
|
||||
}
|
||||
cfg := agnetRuntimeClientConfigForMode(mode)
|
||||
cfg := agentRuntimeClientConfigForMode(mode)
|
||||
if !cfg.Enabled {
|
||||
return record
|
||||
}
|
||||
if _, err := agnetRuntimeURL(cfg.BaseURL, cfg.CreatePath); err != nil {
|
||||
record.RuntimeState = agnetRuntimeStateFailed
|
||||
record.RuntimeLastSyncAt = agnetNow()
|
||||
record.FailureReason = truncateAgnetFailureReason(err.Error())
|
||||
record.UpdatedAt = agnetNow()
|
||||
_ = updateAgnetDeploymentRecord(record)
|
||||
recordAgnetRuntimeSyncAudit(record, "runtime.sync.failed", "failed")
|
||||
if _, err := agentRuntimeURL(cfg.BaseURL, cfg.CreatePath); err != nil {
|
||||
record.RuntimeState = agentRuntimeStateFailed
|
||||
record.RuntimeLastSyncAt = agentNow()
|
||||
record.FailureReason = truncateAgentFailureReason(err.Error())
|
||||
record.UpdatedAt = agentNow()
|
||||
_ = updateAgentDeploymentRecord(record)
|
||||
recordAgentRuntimeSyncAudit(record, "runtime.sync.failed", "failed")
|
||||
return record
|
||||
}
|
||||
record.RuntimeState = agnetRuntimeStateSyncing
|
||||
record.RuntimeLastSyncAt = agnetNow()
|
||||
record.UpdatedAt = agnetNow()
|
||||
if err := updateAgnetDeploymentRecord(record); err != nil {
|
||||
common.SysLog("maybeDispatchAgnetRuntimeCreate: " + err.Error())
|
||||
record.RuntimeState = agentRuntimeStateSyncing
|
||||
record.RuntimeLastSyncAt = agentNow()
|
||||
record.UpdatedAt = agentNow()
|
||||
if err := updateAgentDeploymentRecord(record); err != nil {
|
||||
common.SysLog("maybeDispatchAgentRuntimeCreate: " + err.Error())
|
||||
}
|
||||
agnetMu.Lock()
|
||||
agnetDeployments[record.DeploymentID] = record
|
||||
agnetMu.Unlock()
|
||||
agentMu.Lock()
|
||||
agentDeployments[record.DeploymentID] = record
|
||||
agentMu.Unlock()
|
||||
if cfg.Async {
|
||||
sourceCopy := source
|
||||
recordCopy := record
|
||||
go dispatchAgnetRuntimeCreate(recordCopy, sourceCopy, cfg)
|
||||
go dispatchAgentRuntimeCreate(recordCopy, sourceCopy, cfg)
|
||||
return record
|
||||
}
|
||||
return dispatchAgnetRuntimeCreate(record, source, cfg)
|
||||
return dispatchAgentRuntimeCreate(record, source, cfg)
|
||||
}
|
||||
|
||||
func AgnetRuntimeHealth(c *gin.Context) {
|
||||
cfg := agnetRuntimeClientConfigForMode(c.Query("mode"))
|
||||
func AgentRuntimeHealth(c *gin.Context) {
|
||||
cfg := agentRuntimeClientConfigForMode(c.Query("mode"))
|
||||
data := gin.H{
|
||||
"enabled": cfg.Enabled,
|
||||
"configured": cfg.BaseURL != "",
|
||||
"create_path": cfg.CreatePath,
|
||||
"health_path": cfg.HealthPath,
|
||||
"stop_path": cfg.StopPath,
|
||||
"mode": normalizeAgnetRuntimeMode(c.Query("mode")),
|
||||
"mode": normalizeAgentRuntimeMode(c.Query("mode")),
|
||||
}
|
||||
if cfg.BaseURL == "" {
|
||||
data["status"] = "not_configured"
|
||||
common.ApiSuccess(c, data)
|
||||
return
|
||||
}
|
||||
endpoint, err := agnetRuntimeURL(cfg.BaseURL, cfg.HealthPath)
|
||||
endpoint, err := agentRuntimeURL(cfg.BaseURL, cfg.HealthPath)
|
||||
if err != nil {
|
||||
data["status"] = "invalid_config"
|
||||
data["message"] = err.Error()
|
||||
@@ -0,0 +1,299 @@
|
||||
package controller
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/heicode/manager/common"
|
||||
"github.com/heicode/manager/model"
|
||||
)
|
||||
|
||||
type agentTaskDeploymentDraftRequest struct {
|
||||
Task agentTaskSnapshot `json:"task"`
|
||||
SubMode string `json:"sub_mode"`
|
||||
RiskLevel string `json:"risk_level"`
|
||||
Budget agentBudget `json:"budget"`
|
||||
BindingScope string `json:"binding_scope"`
|
||||
RoleTemplates []string `json:"role_templates"`
|
||||
DefaultModelID string `json:"default_model_id"`
|
||||
RoleModels map[string]string `json:"role_models"`
|
||||
ResourceGrants []agentResourceGrant `json:"resource_grants"`
|
||||
}
|
||||
|
||||
type agentTaskSnapshot struct {
|
||||
ID string `json:"id"`
|
||||
Name string `json:"name"`
|
||||
Intent string `json:"intent"`
|
||||
Status string `json:"status"`
|
||||
Card map[string]any `json:"card"`
|
||||
}
|
||||
|
||||
func stringFromTaskCard(card map[string]any, key string) string {
|
||||
if card == nil {
|
||||
return ""
|
||||
}
|
||||
if value, ok := card[key].(string); ok {
|
||||
return strings.TrimSpace(value)
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func objectiveFromTaskSnapshot(task agentTaskSnapshot) string {
|
||||
for _, value := range []string{
|
||||
stringFromTaskCard(task.Card, "goal"),
|
||||
task.Name,
|
||||
task.Intent,
|
||||
} {
|
||||
if strings.TrimSpace(value) != "" {
|
||||
return strings.TrimSpace(value)
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func normalizeAgentDraftBudget(budget agentBudget) agentBudget {
|
||||
if budget.MaxTokens <= 0 {
|
||||
budget.MaxTokens = 120000
|
||||
}
|
||||
if budget.MaxCostUSD <= 0 {
|
||||
budget.MaxCostUSD = 8
|
||||
}
|
||||
if budget.MaxDurationSec <= 0 {
|
||||
budget.MaxDurationSec = 3600
|
||||
}
|
||||
return budget
|
||||
}
|
||||
|
||||
func normalizeAgentDraftRoleTemplates(values []string) []string {
|
||||
roles := make([]string, 0, len(values))
|
||||
for _, value := range values {
|
||||
role := strings.TrimSpace(value)
|
||||
if role == "" {
|
||||
continue
|
||||
}
|
||||
roles = append(roles, role)
|
||||
}
|
||||
if len(roles) == 0 {
|
||||
return []string{"backend"}
|
||||
}
|
||||
return roles
|
||||
}
|
||||
|
||||
func defaultAgentTaskBindingScope(taskID string) string {
|
||||
bindingScope := "task-" + sanitizeAgentRef(taskID)
|
||||
if bindingScope == "task-" {
|
||||
return "task-local"
|
||||
}
|
||||
return bindingScope
|
||||
}
|
||||
|
||||
func defaultTaskDraftResourceGrant(userID string, bindingScope string, role string, taskID string) agentResourceGrant {
|
||||
return agentResourceGrant{
|
||||
GrantID: "grant-" + sanitizeAgentRef(taskID) + "-" + sanitizeAgentRef(role),
|
||||
ResourceID: "task-" + sanitizeAgentRef(taskID) + "-context",
|
||||
ResourceType: agentResourceProjectDoc,
|
||||
UserID: userID,
|
||||
BindingScope: bindingScope,
|
||||
TargetRole: role,
|
||||
TargetAgentRef: "agent-" + sanitizeAgentRef(role) + "-1",
|
||||
PermissionScope: []string{"doc:read"},
|
||||
Constraints: map[string]string{"ref": "task-card"},
|
||||
Metadata: map[string]string{"provider": "heicode-task", "resource_ref": taskID},
|
||||
Status: agentGrantStatusActive,
|
||||
Audit: map[string]string{"source": "heicode-task-draft"},
|
||||
}
|
||||
}
|
||||
|
||||
func sanitizeAgentRef(value string) string {
|
||||
value = strings.ToLower(strings.TrimSpace(value))
|
||||
var b strings.Builder
|
||||
for _, r := range value {
|
||||
switch {
|
||||
case r >= 'a' && r <= 'z':
|
||||
b.WriteRune(r)
|
||||
case r >= '0' && r <= '9':
|
||||
b.WriteRune(r)
|
||||
case r == '-' || r == '_':
|
||||
b.WriteRune(r)
|
||||
default:
|
||||
b.WriteRune('-')
|
||||
}
|
||||
}
|
||||
return strings.Trim(b.String(), "-")
|
||||
}
|
||||
|
||||
func buildAgentDraftAgentPlan(role string, defaultModelID string, grants []agentResourceGrant) agentAgentPlan {
|
||||
if defaultModelID == "" {
|
||||
defaultModelID = defaultAgentModelID()
|
||||
}
|
||||
return agentAgentPlan{
|
||||
RoleTemplate: role,
|
||||
Goal: fmt.Sprintf("Execute the Heicode task as %s within the approved resource scope.", role),
|
||||
DefaultModelID: defaultModelID,
|
||||
ResourceGrants: grants,
|
||||
}
|
||||
}
|
||||
|
||||
func normalizeTaskDraftResourceGrants(userID string, bindingScope string, role string, taskID string, grants []agentResourceGrant) []agentResourceGrant {
|
||||
if len(grants) == 0 {
|
||||
return []agentResourceGrant{defaultTaskDraftResourceGrant(userID, bindingScope, role, taskID)}
|
||||
}
|
||||
normalized := make([]agentResourceGrant, 0, len(grants))
|
||||
for idx, grant := range grants {
|
||||
grant.UserID = userID
|
||||
if strings.TrimSpace(grant.GrantID) == "" {
|
||||
grant.GrantID = fmt.Sprintf("grant-%s-%s-%d", sanitizeAgentRef(taskID), sanitizeAgentRef(role), idx+1)
|
||||
}
|
||||
if strings.TrimSpace(grant.BindingScope) == "" {
|
||||
grant.BindingScope = bindingScope
|
||||
}
|
||||
if strings.TrimSpace(grant.TargetRole) == "" {
|
||||
grant.TargetRole = role
|
||||
}
|
||||
if strings.TrimSpace(grant.TargetAgentRef) == "" {
|
||||
grant.TargetAgentRef = "agent-" + sanitizeAgentRef(role) + "-1"
|
||||
}
|
||||
if strings.TrimSpace(grant.Status) == "" {
|
||||
grant.Status = agentGrantStatusActive
|
||||
}
|
||||
grant = resolveResourceBindingIntoGrant(userID, grant)
|
||||
normalized = append(normalized, grant)
|
||||
}
|
||||
return normalized
|
||||
}
|
||||
|
||||
// resolveResourceBindingIntoGrant injects the stored ResourceBinding's real
|
||||
// secret_ref and resource metadata when the client referenced a binding by id
|
||||
// instead of inlining a secret_ref (unified spec §17.6). The binding must be
|
||||
// owned by the requesting user; unknown/unowned ids are left untouched so the
|
||||
// existing plan validation surfaces a clear error.
|
||||
func resolveResourceBindingIntoGrant(userID string, grant agentResourceGrant) agentResourceGrant {
|
||||
if grant.ResourceBindingID <= 0 || model.DB == nil {
|
||||
return grant
|
||||
}
|
||||
uid, _ := strconv.Atoi(strings.TrimSpace(userID))
|
||||
if uid <= 0 {
|
||||
return grant
|
||||
}
|
||||
var binding model.ResourceBinding
|
||||
if err := model.DB.Where("id = ? AND user_id = ?", grant.ResourceBindingID, uid).First(&binding).Error; err != nil {
|
||||
return grant
|
||||
}
|
||||
if strings.TrimSpace(grant.SecretRef) == "" {
|
||||
grant.SecretRef = strings.TrimSpace(binding.SecretRef)
|
||||
}
|
||||
if strings.TrimSpace(grant.ResourceID) == "" {
|
||||
grant.ResourceID = fmt.Sprintf("rb_%d", binding.Id)
|
||||
}
|
||||
if strings.TrimSpace(grant.ResourceType) == "" {
|
||||
grant.ResourceType = strings.TrimSpace(binding.ResourceType)
|
||||
}
|
||||
if strings.TrimSpace(grant.BindingScope) == "" {
|
||||
grant.BindingScope = strings.TrimSpace(binding.BindingScope)
|
||||
}
|
||||
return grant
|
||||
}
|
||||
|
||||
func AgentCreateTaskDeploymentDraft(c *gin.Context) {
|
||||
taskID := strings.TrimSpace(c.Param("task_id"))
|
||||
if taskID == "" {
|
||||
agentError(c, "TASK_NOT_FOUND", "task_id is required")
|
||||
return
|
||||
}
|
||||
|
||||
var req agentTaskDeploymentDraftRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
agentError(c, "POLICY_REJECTED", err.Error())
|
||||
return
|
||||
}
|
||||
if strings.TrimSpace(req.Task.ID) == "" {
|
||||
agentError(c, "TASK_NOT_FOUND", "task snapshot is required")
|
||||
return
|
||||
}
|
||||
if strings.TrimSpace(req.Task.ID) != taskID {
|
||||
agentError(c, "TASK_CONFLICT", "task snapshot id must match route task_id")
|
||||
return
|
||||
}
|
||||
if !isValidAgentSubMode(req.SubMode) {
|
||||
agentError(c, "POLICY_REJECTED", "sub_mode must be agile or waterfall")
|
||||
return
|
||||
}
|
||||
|
||||
userID := strconv.Itoa(c.GetInt("id"))
|
||||
if userID == "0" {
|
||||
agentError(c, "POLICY_REJECTED", "authenticated user is required")
|
||||
return
|
||||
}
|
||||
objective := objectiveFromTaskSnapshot(req.Task)
|
||||
if objective == "" {
|
||||
agentError(c, "POLICY_REJECTED", "task objective is required")
|
||||
return
|
||||
}
|
||||
|
||||
bindingScope := strings.TrimSpace(req.BindingScope)
|
||||
if bindingScope == "" {
|
||||
bindingScope = defaultAgentTaskBindingScope(taskID)
|
||||
}
|
||||
roles := normalizeAgentDraftRoleTemplates(req.RoleTemplates)
|
||||
riskLevel := strings.TrimSpace(req.RiskLevel)
|
||||
if riskLevel == "" {
|
||||
riskLevel = agentRiskLow
|
||||
}
|
||||
defaultModelID := strings.TrimSpace(req.DefaultModelID)
|
||||
group := strings.TrimSpace(c.GetString("group"))
|
||||
agents := make([]agentAgentPlan, 0, len(roles))
|
||||
runtimeAgents := make([]agentRuntimeAgent, 0, len(roles))
|
||||
// per_role model selection (unified spec §9): role_models[role] wins, then
|
||||
// the request default, then the platform default. Every resolved model is
|
||||
// collected into allowed_model_ids so create-time validation accepts them.
|
||||
allowedSeen := map[string]bool{}
|
||||
allowedModels := []string{}
|
||||
addAllowedModel := func(m string) {
|
||||
m = strings.TrimSpace(m)
|
||||
if m == "" || allowedSeen[m] {
|
||||
return
|
||||
}
|
||||
allowedSeen[m] = true
|
||||
allowedModels = append(allowedModels, m)
|
||||
}
|
||||
for _, role := range roles {
|
||||
grants := normalizeTaskDraftResourceGrants(userID, bindingScope, role, taskID, req.ResourceGrants)
|
||||
modelRef := firstNonEmpty(req.RoleModels[role], defaultModelID, defaultAgentModelID())
|
||||
agents = append(agents, buildAgentDraftAgentPlan(role, modelRef, grants))
|
||||
runtimeAgents = append(runtimeAgents, agentRuntimeAgent{Role: role, ModelRef: modelRef, InstanceCount: 1})
|
||||
addAllowedModel(modelRef)
|
||||
}
|
||||
|
||||
plan := agentOrchestrationPlan{
|
||||
IntentID: taskID,
|
||||
TemplateHint: "heicode-task",
|
||||
Objective: objective,
|
||||
SubMode: normalizeAgentSubMode(req.SubMode),
|
||||
RiskLevel: riskLevel,
|
||||
Budget: normalizeAgentDraftBudget(req.Budget),
|
||||
UserContext: agentUserContext{
|
||||
UserID: userID,
|
||||
Role: "user",
|
||||
ChannelID: group,
|
||||
},
|
||||
AgentRuntime: agentAgentRuntime{Platform: "agent", Agents: runtimeAgents},
|
||||
Agents: agents,
|
||||
Constraints: agentConstraints{AllowedModelIDs: allowedModels},
|
||||
Metadata: agentMetadata{
|
||||
CorrelationID: "task-" + sanitizeAgentRef(taskID) + "-" + common.GetUUID()[:8],
|
||||
},
|
||||
}
|
||||
if group != "" {
|
||||
plan.BillingContext = agentBillingContext{Provider: "newapi", NewAPIGroup: group}
|
||||
}
|
||||
if !validateOrchestrationPlan(c, plan) {
|
||||
return
|
||||
}
|
||||
|
||||
common.ApiSuccess(c, gin.H{
|
||||
"task_id": taskID,
|
||||
"orchestration_plan": plan,
|
||||
})
|
||||
}
|
||||
@@ -1,253 +0,0 @@
|
||||
package controller
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/heicode/manager/common"
|
||||
)
|
||||
|
||||
type agnetTaskDeploymentDraftRequest struct {
|
||||
Task agnetTaskSnapshot `json:"task"`
|
||||
SubMode string `json:"sub_mode"`
|
||||
RiskLevel string `json:"risk_level"`
|
||||
Budget agnetBudget `json:"budget"`
|
||||
BindingScope string `json:"binding_scope"`
|
||||
RoleTemplates []string `json:"role_templates"`
|
||||
DefaultModelID string `json:"default_model_id"`
|
||||
ResourceGrants []agnetResourceGrant `json:"resource_grants"`
|
||||
}
|
||||
|
||||
type agnetTaskSnapshot struct {
|
||||
ID string `json:"id"`
|
||||
Name string `json:"name"`
|
||||
Intent string `json:"intent"`
|
||||
Status string `json:"status"`
|
||||
Card map[string]any `json:"card"`
|
||||
}
|
||||
|
||||
func stringFromTaskCard(card map[string]any, key string) string {
|
||||
if card == nil {
|
||||
return ""
|
||||
}
|
||||
if value, ok := card[key].(string); ok {
|
||||
return strings.TrimSpace(value)
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func objectiveFromTaskSnapshot(task agnetTaskSnapshot) string {
|
||||
for _, value := range []string{
|
||||
stringFromTaskCard(task.Card, "goal"),
|
||||
task.Name,
|
||||
task.Intent,
|
||||
} {
|
||||
if strings.TrimSpace(value) != "" {
|
||||
return strings.TrimSpace(value)
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func normalizeAgnetDraftBudget(budget agnetBudget) agnetBudget {
|
||||
if budget.MaxTokens <= 0 {
|
||||
budget.MaxTokens = 120000
|
||||
}
|
||||
if budget.MaxCostUSD <= 0 {
|
||||
budget.MaxCostUSD = 8
|
||||
}
|
||||
if budget.MaxDurationSec <= 0 {
|
||||
budget.MaxDurationSec = 3600
|
||||
}
|
||||
return budget
|
||||
}
|
||||
|
||||
func normalizeAgnetDraftRoleTemplates(values []string) []string {
|
||||
roles := make([]string, 0, len(values))
|
||||
for _, value := range values {
|
||||
role := strings.TrimSpace(value)
|
||||
if role == "" {
|
||||
continue
|
||||
}
|
||||
roles = append(roles, role)
|
||||
}
|
||||
if len(roles) == 0 {
|
||||
return []string{"backend"}
|
||||
}
|
||||
return roles
|
||||
}
|
||||
|
||||
func defaultAgnetTaskBindingScope(taskID string) string {
|
||||
bindingScope := "task-" + sanitizeAgnetRef(taskID)
|
||||
if bindingScope == "task-" {
|
||||
return "task-local"
|
||||
}
|
||||
return bindingScope
|
||||
}
|
||||
|
||||
func defaultTaskDraftResourceGrant(userID string, bindingScope string, role string, taskID string) agnetResourceGrant {
|
||||
return agnetResourceGrant{
|
||||
GrantID: "grant-" + sanitizeAgnetRef(taskID) + "-" + sanitizeAgnetRef(role),
|
||||
ResourceID: "task-" + sanitizeAgnetRef(taskID) + "-context",
|
||||
ResourceType: agnetResourceProjectDoc,
|
||||
UserID: userID,
|
||||
BindingScope: bindingScope,
|
||||
TargetRole: role,
|
||||
TargetAgentRef: "agent-" + sanitizeAgnetRef(role) + "-1",
|
||||
PermissionScope: []string{"doc:read"},
|
||||
Constraints: map[string]string{"ref": "task-card"},
|
||||
Metadata: map[string]string{"provider": "heicode-task", "resource_ref": taskID},
|
||||
Status: agnetGrantStatusActive,
|
||||
Audit: map[string]string{"source": "heicode-task-draft"},
|
||||
}
|
||||
}
|
||||
|
||||
func sanitizeAgnetRef(value string) string {
|
||||
value = strings.ToLower(strings.TrimSpace(value))
|
||||
var b strings.Builder
|
||||
for _, r := range value {
|
||||
switch {
|
||||
case r >= 'a' && r <= 'z':
|
||||
b.WriteRune(r)
|
||||
case r >= '0' && r <= '9':
|
||||
b.WriteRune(r)
|
||||
case r == '-' || r == '_':
|
||||
b.WriteRune(r)
|
||||
default:
|
||||
b.WriteRune('-')
|
||||
}
|
||||
}
|
||||
return strings.Trim(b.String(), "-")
|
||||
}
|
||||
|
||||
func buildAgnetDraftAgentPlan(role string, defaultModelID string, grants []agnetResourceGrant) agnetAgentPlan {
|
||||
if defaultModelID == "" {
|
||||
defaultModelID = defaultAgnetModelID()
|
||||
}
|
||||
return agnetAgentPlan{
|
||||
RoleTemplate: role,
|
||||
Goal: fmt.Sprintf("Execute the Heicode task as %s within the approved resource scope.", role),
|
||||
DefaultModelID: defaultModelID,
|
||||
ResourceGrants: grants,
|
||||
}
|
||||
}
|
||||
|
||||
func normalizeTaskDraftResourceGrants(userID string, bindingScope string, role string, taskID string, grants []agnetResourceGrant) []agnetResourceGrant {
|
||||
if len(grants) == 0 {
|
||||
return []agnetResourceGrant{defaultTaskDraftResourceGrant(userID, bindingScope, role, taskID)}
|
||||
}
|
||||
normalized := make([]agnetResourceGrant, 0, len(grants))
|
||||
for idx, grant := range grants {
|
||||
grant.UserID = userID
|
||||
if strings.TrimSpace(grant.GrantID) == "" {
|
||||
grant.GrantID = fmt.Sprintf("grant-%s-%s-%d", sanitizeAgnetRef(taskID), sanitizeAgnetRef(role), idx+1)
|
||||
}
|
||||
if strings.TrimSpace(grant.BindingScope) == "" {
|
||||
grant.BindingScope = bindingScope
|
||||
}
|
||||
if strings.TrimSpace(grant.TargetRole) == "" {
|
||||
grant.TargetRole = role
|
||||
}
|
||||
if strings.TrimSpace(grant.TargetAgentRef) == "" {
|
||||
grant.TargetAgentRef = "agent-" + sanitizeAgnetRef(role) + "-1"
|
||||
}
|
||||
if strings.TrimSpace(grant.Status) == "" {
|
||||
grant.Status = agnetGrantStatusActive
|
||||
}
|
||||
normalized = append(normalized, grant)
|
||||
}
|
||||
return normalized
|
||||
}
|
||||
|
||||
func AgnetCreateTaskDeploymentDraft(c *gin.Context) {
|
||||
taskID := strings.TrimSpace(c.Param("task_id"))
|
||||
if taskID == "" {
|
||||
agnetError(c, "TASK_NOT_FOUND", "task_id is required")
|
||||
return
|
||||
}
|
||||
|
||||
var req agnetTaskDeploymentDraftRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
agnetError(c, "POLICY_REJECTED", err.Error())
|
||||
return
|
||||
}
|
||||
if strings.TrimSpace(req.Task.ID) == "" {
|
||||
agnetError(c, "TASK_NOT_FOUND", "task snapshot is required")
|
||||
return
|
||||
}
|
||||
if strings.TrimSpace(req.Task.ID) != taskID {
|
||||
agnetError(c, "TASK_CONFLICT", "task snapshot id must match route task_id")
|
||||
return
|
||||
}
|
||||
if !isValidAgnetSubMode(req.SubMode) {
|
||||
agnetError(c, "POLICY_REJECTED", "sub_mode must be agile or waterfall")
|
||||
return
|
||||
}
|
||||
|
||||
userID := strconv.Itoa(c.GetInt("id"))
|
||||
if userID == "0" {
|
||||
agnetError(c, "POLICY_REJECTED", "authenticated user is required")
|
||||
return
|
||||
}
|
||||
objective := objectiveFromTaskSnapshot(req.Task)
|
||||
if objective == "" {
|
||||
agnetError(c, "POLICY_REJECTED", "task objective is required")
|
||||
return
|
||||
}
|
||||
|
||||
bindingScope := strings.TrimSpace(req.BindingScope)
|
||||
if bindingScope == "" {
|
||||
bindingScope = defaultAgnetTaskBindingScope(taskID)
|
||||
}
|
||||
roles := normalizeAgnetDraftRoleTemplates(req.RoleTemplates)
|
||||
riskLevel := strings.TrimSpace(req.RiskLevel)
|
||||
if riskLevel == "" {
|
||||
riskLevel = agnetRiskLow
|
||||
}
|
||||
defaultModelID := strings.TrimSpace(req.DefaultModelID)
|
||||
group := strings.TrimSpace(c.GetString("group"))
|
||||
agents := make([]agnetAgentPlan, 0, len(roles))
|
||||
runtimeAgents := make([]agnetRuntimeAgent, 0, len(roles))
|
||||
for _, role := range roles {
|
||||
grants := normalizeTaskDraftResourceGrants(userID, bindingScope, role, taskID, req.ResourceGrants)
|
||||
agents = append(agents, buildAgnetDraftAgentPlan(role, defaultModelID, grants))
|
||||
modelRef := defaultModelID
|
||||
if modelRef == "" {
|
||||
modelRef = defaultAgnetModelID()
|
||||
}
|
||||
runtimeAgents = append(runtimeAgents, agnetRuntimeAgent{Role: role, ModelRef: modelRef, InstanceCount: 1})
|
||||
}
|
||||
|
||||
plan := agnetOrchestrationPlan{
|
||||
IntentID: taskID,
|
||||
TemplateHint: "heicode-task",
|
||||
Objective: objective,
|
||||
SubMode: normalizeAgnetSubMode(req.SubMode),
|
||||
RiskLevel: riskLevel,
|
||||
Budget: normalizeAgnetDraftBudget(req.Budget),
|
||||
UserContext: agnetUserContext{
|
||||
UserID: userID,
|
||||
Role: "user",
|
||||
ChannelID: group,
|
||||
},
|
||||
AgentRuntime: agnetAgentRuntime{Platform: "agnet", Agents: runtimeAgents},
|
||||
Agents: agents,
|
||||
Constraints: agnetConstraints{AllowedModelIDs: []string{}},
|
||||
Metadata: agnetMetadata{
|
||||
CorrelationID: "task-" + sanitizeAgnetRef(taskID) + "-" + common.GetUUID()[:8],
|
||||
},
|
||||
}
|
||||
if group != "" {
|
||||
plan.BillingContext = agnetBillingContext{Provider: "newapi", NewAPIGroup: group}
|
||||
}
|
||||
if !validateOrchestrationPlan(c, plan) {
|
||||
return
|
||||
}
|
||||
|
||||
common.ApiSuccess(c, gin.H{
|
||||
"task_id": taskID,
|
||||
"orchestration_plan": plan,
|
||||
})
|
||||
}
|
||||
+36
-36
@@ -20,14 +20,14 @@ import (
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
// HeicodeAgnetSessionRequest accepts tokens obtained only from Agnet identity platform.
|
||||
// HeicodeAgentSessionRequest accepts tokens obtained only from Agent identity platform.
|
||||
// Manager verifies them server-side and issues the browser session cookie (same as password login).
|
||||
type HeicodeAgnetSessionRequest struct {
|
||||
type HeicodeAgentSessionRequest struct {
|
||||
AccessToken string `json:"access_token"`
|
||||
RefreshToken string `json:"refresh_token"`
|
||||
}
|
||||
|
||||
type agnetMeEnvelope struct {
|
||||
type agentMeEnvelope struct {
|
||||
Success bool `json:"success"`
|
||||
Message string `json:"message"`
|
||||
Detail string `json:"detail"`
|
||||
@@ -41,7 +41,7 @@ type agnetMeEnvelope struct {
|
||||
} `json:"data"`
|
||||
}
|
||||
|
||||
type agnetRefreshEnvelope struct {
|
||||
type agentRefreshEnvelope struct {
|
||||
Success bool `json:"success"`
|
||||
Detail string `json:"detail"`
|
||||
Message string `json:"message"`
|
||||
@@ -80,10 +80,10 @@ func parseEmailList(raw string) map[string]struct{} {
|
||||
return out
|
||||
}
|
||||
|
||||
// roleFromAgnetWithEmail decides the local role for a JIT-synced Agnet user.
|
||||
// roleFromAgentWithEmail decides the local role for a JIT-synced Agent user.
|
||||
//
|
||||
// 安全策略:管理员权限只能通过本地配置(环境变量白名单)显式授予,
|
||||
// **不信任** Agnet 平台返回的 role 字段。这样防止外部身份平台
|
||||
// **不信任** Agent 平台返回的 role 字段。这样防止外部身份平台
|
||||
// 的角色被直接映射到 Manager 的高权限角色。
|
||||
//
|
||||
// - 邮箱命中 HEICODE_ROOT_EMAILS -> RoleRootUser
|
||||
@@ -92,7 +92,7 @@ func parseEmailList(raw string) map[string]struct{} {
|
||||
//
|
||||
// 第二参数 `role` 当前未使用,保留是为了未来扩展(例如在策略中允许
|
||||
// 信任部分上游 role),不破坏调用点签名。
|
||||
func roleFromAgnetWithEmail(_ string, email string) int {
|
||||
func roleFromAgentWithEmail(_ string, email string) int {
|
||||
emailKey := strings.ToLower(strings.TrimSpace(email))
|
||||
rootEmails := parseEmailList(os.Getenv("HEICODE_ROOT_EMAILS"))
|
||||
if _, ok := rootEmails[emailKey]; ok {
|
||||
@@ -105,26 +105,26 @@ func roleFromAgnetWithEmail(_ string, email string) int {
|
||||
return common.RoleCommonUser
|
||||
}
|
||||
|
||||
func statusFromAgnet(status string) int {
|
||||
func statusFromAgent(status string) int {
|
||||
if strings.EqualFold(strings.TrimSpace(status), "active") {
|
||||
return common.UserStatusEnabled
|
||||
}
|
||||
return common.UserStatusDisabled
|
||||
}
|
||||
|
||||
func agnetHTTPClient() *http.Client {
|
||||
func agentHTTPClient() *http.Client {
|
||||
return &http.Client{Timeout: 15 * time.Second}
|
||||
}
|
||||
|
||||
func fetchAgnetMe(baseURL, accessToken string) (agnetMeEnvelope, int, error) {
|
||||
var out agnetMeEnvelope
|
||||
func fetchAgentMe(baseURL, accessToken string) (agentMeEnvelope, int, error) {
|
||||
var out agentMeEnvelope
|
||||
req, err := http.NewRequest(http.MethodGet, baseURL+"/api/auth/me", nil)
|
||||
if err != nil {
|
||||
return out, 0, err
|
||||
}
|
||||
req.Header.Set("Authorization", "Bearer "+accessToken)
|
||||
req.Header.Set("X-Request-Id", common.GetUUID())
|
||||
res, err := agnetHTTPClient().Do(req)
|
||||
res, err := agentHTTPClient().Do(req)
|
||||
if err != nil {
|
||||
return out, 0, err
|
||||
}
|
||||
@@ -134,12 +134,12 @@ func fetchAgnetMe(baseURL, accessToken string) (agnetMeEnvelope, int, error) {
|
||||
return out, res.StatusCode, err
|
||||
}
|
||||
if err := common.Unmarshal(body, &out); err != nil {
|
||||
return out, res.StatusCode, fmt.Errorf("invalid response from Agnet /me: %w", err)
|
||||
return out, res.StatusCode, fmt.Errorf("invalid response from Agent /me: %w", err)
|
||||
}
|
||||
return out, res.StatusCode, nil
|
||||
}
|
||||
|
||||
func fetchAgnetRefresh(baseURL, refreshToken string) (access string, refresh string, err error) {
|
||||
func fetchAgentRefresh(baseURL, refreshToken string) (access string, refresh string, err error) {
|
||||
req, err := http.NewRequest(http.MethodPost, baseURL+"/api/auth/refresh", nil)
|
||||
if err != nil {
|
||||
return "", "", err
|
||||
@@ -147,7 +147,7 @@ func fetchAgnetRefresh(baseURL, refreshToken string) (access string, refresh str
|
||||
req.Header.Set("Authorization", "Bearer "+refreshToken)
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
req.Header.Set("X-Request-Id", common.GetUUID())
|
||||
res, err := agnetHTTPClient().Do(req)
|
||||
res, err := agentHTTPClient().Do(req)
|
||||
if err != nil {
|
||||
return "", "", err
|
||||
}
|
||||
@@ -156,9 +156,9 @@ func fetchAgnetRefresh(baseURL, refreshToken string) (access string, refresh str
|
||||
if err != nil {
|
||||
return "", "", err
|
||||
}
|
||||
var env agnetRefreshEnvelope
|
||||
var env agentRefreshEnvelope
|
||||
if err := common.Unmarshal(body, &env); err != nil {
|
||||
return "", "", fmt.Errorf("invalid response from Agnet /refresh: %w", err)
|
||||
return "", "", fmt.Errorf("invalid response from Agent /refresh: %w", err)
|
||||
}
|
||||
if !env.Success || env.Data.Token == "" {
|
||||
msg := env.Message
|
||||
@@ -212,7 +212,7 @@ func markBillingProviderNewapi(email string) {
|
||||
req.Header.Set("Authorization", "Bearer "+tok)
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
req.Header.Set("X-Request-Id", common.GetUUID())
|
||||
res, err := agnetHTTPClient().Do(req)
|
||||
res, err := agentHTTPClient().Do(req)
|
||||
if err != nil {
|
||||
common.SysLog("markBillingProviderNewapi: send failed: " + err.Error())
|
||||
return
|
||||
@@ -228,10 +228,10 @@ func markBillingProviderNewapi(email string) {
|
||||
}(email, tok)
|
||||
}
|
||||
|
||||
func syncLocalUserFromAgnet(me agnetMeEnvelope) (*model.User, error) {
|
||||
func syncLocalUserFromAgent(me agentMeEnvelope) (*model.User, error) {
|
||||
email := strings.TrimSpace(me.Data.Email)
|
||||
if email == "" {
|
||||
return nil, errors.New("Agnet account has no email")
|
||||
return nil, errors.New("Agent account has no email")
|
||||
}
|
||||
|
||||
var user model.User
|
||||
@@ -241,7 +241,7 @@ func syncLocalUserFromAgnet(me agnetMeEnvelope) (*model.User, error) {
|
||||
if display == "" {
|
||||
display = strings.Split(email, "@")[0]
|
||||
}
|
||||
// Local model-access bucket — never seed with the Agnet channelId
|
||||
// Local model-access bucket — never seed with the Agent channelId
|
||||
// (no abilities row matches a random UUID, so the new user would land
|
||||
// with zero models on first /v1/models call). Admins control group
|
||||
// from the NewAPI dashboard after JIT-create. Fix companion to
|
||||
@@ -253,12 +253,12 @@ func syncLocalUserFromAgnet(me agnetMeEnvelope) (*model.User, error) {
|
||||
Password: common.GetRandomString(32),
|
||||
DisplayName: display,
|
||||
Email: email,
|
||||
Role: roleFromAgnetWithEmail(me.Data.Role, email),
|
||||
Status: statusFromAgnet(me.Data.Status),
|
||||
Role: roleFromAgentWithEmail(me.Data.Role, email),
|
||||
Status: statusFromAgent(me.Data.Status),
|
||||
Group: group,
|
||||
}
|
||||
if nu.Status != common.UserStatusEnabled {
|
||||
return nil, errors.New("Agnet account is not active")
|
||||
return nil, errors.New("Agent account is not active")
|
||||
}
|
||||
if err := nu.Insert(0); err != nil {
|
||||
// Possible race: duplicate email/username — reload.
|
||||
@@ -283,10 +283,10 @@ func syncLocalUserFromAgnet(me agnetMeEnvelope) (*model.User, error) {
|
||||
user.DisplayName = name
|
||||
changed = true
|
||||
}
|
||||
// Don't overwrite the existing user's group with the Agnet channelId on
|
||||
// Don't overwrite the existing user's group with the Agent channelId on
|
||||
// every login: NewAPI's `users.group` is the **local model-access bucket**
|
||||
// (must match a row in the `abilities` / `channels` group column to expose
|
||||
// any models). The Agnet channelId is a cross-platform identity that
|
||||
// any models). The Agent channelId is a cross-platform identity that
|
||||
// rarely matches a NewAPI-side group, so overwriting strands the user
|
||||
// with zero models. mcp-server side already tracks channelId separately
|
||||
// (see markBillingProviderNewapi), so we don't need it duplicated here.
|
||||
@@ -295,8 +295,8 @@ func syncLocalUserFromAgnet(me agnetMeEnvelope) (*model.User, error) {
|
||||
// is new and has no admin-set group yet). After that, NewAPI admins own
|
||||
// the group via the dashboard.
|
||||
_ = me.Data.ChannelID
|
||||
// Promote role from Agnet / email whitelist on every login (never demote).
|
||||
desiredRole := roleFromAgnetWithEmail(me.Data.Role, email)
|
||||
// Promote role from Agent / email whitelist on every login (never demote).
|
||||
desiredRole := roleFromAgentWithEmail(me.Data.Role, email)
|
||||
if desiredRole > user.Role {
|
||||
user.Role = desiredRole
|
||||
changed = true
|
||||
@@ -313,9 +313,9 @@ func syncLocalUserFromAgnet(me agnetMeEnvelope) (*model.User, error) {
|
||||
return &user, nil
|
||||
}
|
||||
|
||||
// HeicodeAgnetSessionLogin establishes Manager session after Agnet identity verified via token(s).
|
||||
func HeicodeAgnetSessionLogin(c *gin.Context) {
|
||||
var req HeicodeAgnetSessionRequest
|
||||
// HeicodeAgentSessionLogin establishes Manager session after Agent identity verified via token(s).
|
||||
func HeicodeAgentSessionLogin(c *gin.Context) {
|
||||
var req HeicodeAgentSessionRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
c.JSON(http.StatusOK, gin.H{"success": false, "message": "invalid params"})
|
||||
return
|
||||
@@ -328,19 +328,19 @@ func HeicodeAgnetSessionLogin(c *gin.Context) {
|
||||
}
|
||||
|
||||
baseURL := defaultHeicodeAuthBaseURL()
|
||||
me, status, err := fetchAgnetMe(baseURL, access)
|
||||
me, status, err := fetchAgentMe(baseURL, access)
|
||||
newAccess := ""
|
||||
newRefresh := ""
|
||||
|
||||
if (err != nil || status == http.StatusUnauthorized || !me.Success) && refresh != "" {
|
||||
na, nr, refErr := fetchAgnetRefresh(baseURL, refresh)
|
||||
na, nr, refErr := fetchAgentRefresh(baseURL, refresh)
|
||||
if refErr != nil {
|
||||
c.JSON(http.StatusOK, gin.H{"success": false, "message": refErr.Error()})
|
||||
return
|
||||
}
|
||||
newAccess = na
|
||||
newRefresh = nr
|
||||
me, _, err = fetchAgnetMe(baseURL, newAccess)
|
||||
me, _, err = fetchAgentMe(baseURL, newAccess)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
@@ -353,13 +353,13 @@ func HeicodeAgnetSessionLogin(c *gin.Context) {
|
||||
msg = me.Detail
|
||||
}
|
||||
if msg == "" {
|
||||
msg = "unable to verify identity with Agnet"
|
||||
msg = "unable to verify identity with Agent"
|
||||
}
|
||||
c.JSON(http.StatusOK, gin.H{"success": false, "message": msg})
|
||||
return
|
||||
}
|
||||
|
||||
user, err := syncLocalUserFromAgnet(me)
|
||||
user, err := syncLocalUserFromAgent(me)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusOK, gin.H{"success": false, "message": err.Error()})
|
||||
return
|
||||
@@ -0,0 +1,50 @@
|
||||
package controller
|
||||
|
||||
import (
|
||||
"github.com/gin-gonic/gin"
|
||||
|
||||
"github.com/heicode/manager/common"
|
||||
)
|
||||
|
||||
// HeicodeCapabilities exposes the client-facing mode + model catalog so the
|
||||
// desktop client can discover which modes (Sub Agile / Swarm) are available and
|
||||
// how each selects models (unified spec §6: GET /api/heicode/capabilities).
|
||||
//
|
||||
// It is intentionally unauthenticated catalog data: it returns no user-specific
|
||||
// information, only the static mode contract plus whether each runtime is
|
||||
// currently wired, and the production-aligned default model.
|
||||
func HeicodeCapabilities(c *gin.Context) {
|
||||
subCfg := agentRuntimeClientConfigForMode(agentRuntimeModeAgent)
|
||||
swarmCfg := agentRuntimeClientConfigForMode(agentRuntimeModeSwarm)
|
||||
defaultModel := defaultAgentModelID()
|
||||
|
||||
common.ApiSuccess(c, gin.H{
|
||||
"modes": []gin.H{
|
||||
{
|
||||
"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": subCfg.Enabled,
|
||||
},
|
||||
{
|
||||
"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": swarmCfg.Enabled,
|
||||
},
|
||||
},
|
||||
"models": []gin.H{
|
||||
{"id": defaultModel, "name": defaultModel, "available": true},
|
||||
},
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
package controller
|
||||
|
||||
import (
|
||||
"github.com/gin-gonic/gin"
|
||||
|
||||
"github.com/heicode/manager/common"
|
||||
"github.com/heicode/manager/model"
|
||||
)
|
||||
|
||||
// HeicodeTaskWorkflow projects a deployment into the client-facing "workflow"
|
||||
// shape consumed by the desktop right-hand task panel (unified spec §7.9 /
|
||||
// §10.2). task_id is the deployment_id. Status is the Manager-judged
|
||||
// display_status, so the client never has to interpret raw runtime state.
|
||||
func HeicodeTaskWorkflow(c *gin.Context) {
|
||||
record, ok := requireAuthenticatedUserAgentDeployment(c)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
record = withDisplayStatus(record)
|
||||
|
||||
artifacts, _ := model.ListAgentArtifacts(model.ListAgentArtifactsFilter{
|
||||
DeploymentID: record.DeploymentID,
|
||||
Limit: 500,
|
||||
})
|
||||
artItems := make([]gin.H, 0, len(artifacts))
|
||||
for _, a := range artifacts {
|
||||
artItems = append(artItems, gin.H{
|
||||
"artifact_id": a.ArtifactID,
|
||||
"title": a.Title,
|
||||
"artifact_type": a.ArtifactType,
|
||||
"summary": a.Summary,
|
||||
})
|
||||
}
|
||||
|
||||
agents := make([]gin.H, 0, len(record.AgentInstances))
|
||||
for _, inst := range record.AgentInstances {
|
||||
agents = append(agents, gin.H{
|
||||
"agent_id": inst.InstanceID,
|
||||
"name": inst.Role,
|
||||
"role": inst.Role,
|
||||
"status": firstNonEmpty(inst.RuntimeState, inst.Phase),
|
||||
})
|
||||
}
|
||||
|
||||
common.ApiSuccess(c, gin.H{
|
||||
"workflow_id": record.DeploymentID,
|
||||
"task_id": record.DeploymentID,
|
||||
"deployment_id": record.DeploymentID,
|
||||
"mode": firstNonEmpty(record.SubMode, "agile"),
|
||||
"title": firstNonEmpty(record.Plan.Objective, record.DeploymentID),
|
||||
"summary": record.Plan.Objective,
|
||||
"status": record.DisplayStatus,
|
||||
"display_status": record.DisplayStatus,
|
||||
"phase": record.Phase,
|
||||
"agent_count": len(record.AgentInstances),
|
||||
"agents": agents,
|
||||
"artifacts": artItems,
|
||||
})
|
||||
}
|
||||
@@ -94,7 +94,7 @@ type resourceGrantPayload struct {
|
||||
BindingScope string `json:"binding_scope"`
|
||||
ResourceId int `json:"resource_id"`
|
||||
Role string `json:"role"`
|
||||
AgnetId string `json:"agnet_id"`
|
||||
AgentId string `json:"agent_id"`
|
||||
PermissionScope map[string]any `json:"permission_scope"`
|
||||
Constraints map[string]any `json:"constraints"`
|
||||
Status string `json:"status"`
|
||||
@@ -108,7 +108,7 @@ type resourceGrantResponse struct {
|
||||
BindingScope string `json:"binding_scope"`
|
||||
ResourceId int `json:"resource_id"`
|
||||
Role string `json:"role"`
|
||||
AgnetId string `json:"agnet_id"`
|
||||
AgentId string `json:"agent_id"`
|
||||
PermissionScope map[string]any `json:"permission_scope"`
|
||||
Constraints map[string]any `json:"constraints"`
|
||||
Status string `json:"status"`
|
||||
@@ -190,7 +190,7 @@ func normalizeResourceGrantPayload(p resourceGrantPayload) (resourceGrantPayload
|
||||
p.ProjectId = strings.TrimSpace(p.ProjectId)
|
||||
p.BindingScope = strings.TrimSpace(p.BindingScope)
|
||||
p.Role = strings.TrimSpace(p.Role)
|
||||
p.AgnetId = strings.TrimSpace(p.AgnetId)
|
||||
p.AgentId = strings.TrimSpace(p.AgentId)
|
||||
p.Status = strings.ToLower(strings.TrimSpace(p.Status))
|
||||
|
||||
if p.ResourceId <= 0 {
|
||||
@@ -199,8 +199,8 @@ func normalizeResourceGrantPayload(p resourceGrantPayload) (resourceGrantPayload
|
||||
if p.Role == "" {
|
||||
return p, errors.New("role required")
|
||||
}
|
||||
if p.AgnetId == "" {
|
||||
return p, errors.New("agnet_id required")
|
||||
if p.AgentId == "" {
|
||||
return p, errors.New("agent_id required")
|
||||
}
|
||||
if p.Status == "" {
|
||||
p.Status = "active"
|
||||
@@ -375,7 +375,7 @@ func resourceGrantToResponse(grant model.ResourceGrant, resource *model.Resource
|
||||
BindingScope: grant.BindingScope,
|
||||
ResourceId: grant.ResourceId,
|
||||
Role: grant.Role,
|
||||
AgnetId: grant.AgnetId,
|
||||
AgentId: grant.AgentId,
|
||||
PermissionScope: unmarshalResourceJSON(grant.PermissionScope),
|
||||
Constraints: unmarshalResourceJSON(grant.Constraints),
|
||||
Status: grant.Status,
|
||||
@@ -719,8 +719,8 @@ func ListResourceGrants(c *gin.Context) {
|
||||
if bindingScope := strings.TrimSpace(c.Query("binding_scope")); bindingScope != "" {
|
||||
query = query.Where("binding_scope = ?", bindingScope)
|
||||
}
|
||||
if agnetId := strings.TrimSpace(c.Query("agnet_id")); agnetId != "" {
|
||||
query = query.Where("agnet_id = ?", agnetId)
|
||||
if agentId := strings.TrimSpace(c.Query("agent_id")); agentId != "" {
|
||||
query = query.Where("agent_id = ?", agentId)
|
||||
}
|
||||
if resourceId := strings.TrimSpace(c.Query("resource_id")); resourceId != "" {
|
||||
query = query.Where("resource_id = ?", resourceId)
|
||||
@@ -743,7 +743,7 @@ func ListResourceGrants(c *gin.Context) {
|
||||
func GenerateResourceGrantManifest(c *gin.Context) {
|
||||
userId := c.GetInt("id")
|
||||
bindingScope := strings.TrimSpace(c.Query("binding_scope"))
|
||||
agentId := strings.TrimSpace(c.Query("agnet_id"))
|
||||
agentId := strings.TrimSpace(c.Query("agent_id"))
|
||||
role := strings.TrimSpace(c.Query("role"))
|
||||
|
||||
query := model.DB.Where("user_id = ? AND status = ?", userId, "active")
|
||||
@@ -751,7 +751,7 @@ func GenerateResourceGrantManifest(c *gin.Context) {
|
||||
query = query.Where("binding_scope = ?", bindingScope)
|
||||
}
|
||||
if agentId != "" {
|
||||
query = query.Where("agnet_id = ?", agentId)
|
||||
query = query.Where("agent_id = ?", agentId)
|
||||
}
|
||||
if role != "" {
|
||||
query = query.Where("role = ?", role)
|
||||
@@ -815,7 +815,7 @@ func CreateResourceGrant(c *gin.Context) {
|
||||
BindingScope: payload.BindingScope,
|
||||
ResourceId: payload.ResourceId,
|
||||
Role: payload.Role,
|
||||
AgnetId: payload.AgnetId,
|
||||
AgentId: payload.AgentId,
|
||||
PermissionScope: permissionScope,
|
||||
Constraints: constraints,
|
||||
Status: payload.Status,
|
||||
@@ -864,7 +864,7 @@ func UpdateResourceGrant(c *gin.Context) {
|
||||
grant.BindingScope = payload.BindingScope
|
||||
grant.ResourceId = payload.ResourceId
|
||||
grant.Role = payload.Role
|
||||
grant.AgnetId = payload.AgnetId
|
||||
grant.AgentId = payload.AgentId
|
||||
grant.PermissionScope = permissionScope
|
||||
grant.Constraints = constraints
|
||||
grant.Status = payload.Status
|
||||
|
||||
@@ -120,7 +120,7 @@ func TestDeleteResourceRevokesBindingAndActiveGrants(t *testing.T) {
|
||||
BindingScope: "project-alpha",
|
||||
ResourceId: resource.Id,
|
||||
Role: "backend",
|
||||
AgnetId: "agent-backend-1",
|
||||
AgentId: "agent-backend-1",
|
||||
PermissionScope: `{"actions":["repo:read"]}`,
|
||||
Status: "active",
|
||||
}
|
||||
@@ -159,7 +159,7 @@ func TestDeleteResourceRevokesBindingAndActiveGrants(t *testing.T) {
|
||||
require.Contains(t, listActive.Body.String(), `"items":[]`)
|
||||
}
|
||||
|
||||
func TestCreateResourceGrantAssignsBoundResourceToRoleAgnet(t *testing.T) {
|
||||
func TestCreateResourceGrantAssignsBoundResourceToRoleAgent(t *testing.T) {
|
||||
db := setupResourceControllerTestDB(t)
|
||||
resource := model.ResourceBinding{
|
||||
UserId: 7,
|
||||
@@ -177,7 +177,7 @@ func TestCreateResourceGrantAssignsBoundResourceToRoleAgnet(t *testing.T) {
|
||||
"binding_scope":"https://example.com/sk.git#main",
|
||||
"resource_id":%d,
|
||||
"role":"developer",
|
||||
"agnet_id":"agnet-dev-1",
|
||||
"agent_id":"agent-dev-1",
|
||||
"permission_scope":{"actions":["read"]},
|
||||
"constraints":{"paths":["skills/**"]}
|
||||
}`, resource.Id)
|
||||
@@ -187,14 +187,14 @@ func TestCreateResourceGrantAssignsBoundResourceToRoleAgnet(t *testing.T) {
|
||||
require.Contains(t, w.Body.String(), `"success":true`)
|
||||
require.Contains(t, w.Body.String(), `"binding_scope":"https://example.com/sk.git#main"`)
|
||||
require.Contains(t, w.Body.String(), `"role":"developer"`)
|
||||
require.Contains(t, w.Body.String(), `"agnet_id":"agnet-dev-1"`)
|
||||
require.Contains(t, w.Body.String(), `"agent_id":"agent-dev-1"`)
|
||||
|
||||
var grant model.ResourceGrant
|
||||
require.NoError(t, db.First(&grant).Error)
|
||||
require.Equal(t, resource.Id, grant.ResourceId)
|
||||
require.Equal(t, "https://example.com/sk.git#main", grant.BindingScope)
|
||||
require.Equal(t, "developer", grant.Role)
|
||||
require.Equal(t, "agnet-dev-1", grant.AgnetId)
|
||||
require.Equal(t, "agent-dev-1", grant.AgentId)
|
||||
}
|
||||
|
||||
func TestCreateResourceGrantRejectsMismatchedBindingScope(t *testing.T) {
|
||||
@@ -212,7 +212,7 @@ func TestCreateResourceGrantRejectsMismatchedBindingScope(t *testing.T) {
|
||||
"binding_scope":"azure-vm-dev",
|
||||
"resource_id":%d,
|
||||
"role":"operator",
|
||||
"agnet_id":"agnet-ops-1"
|
||||
"agent_id":"agent-ops-1"
|
||||
}`, resource.Id)
|
||||
|
||||
w := performResourceRequest(CreateResourceGrant, 7, http.MethodPost, "/", body)
|
||||
@@ -241,7 +241,7 @@ func TestGenerateResourceGrantManifestIncludesActiveGrantsOnly(t *testing.T) {
|
||||
BindingScope: "repo-main",
|
||||
ResourceId: resource.Id,
|
||||
Role: "backend",
|
||||
AgnetId: "agnet-backend-1",
|
||||
AgentId: "agent-backend-1",
|
||||
PermissionScope: `{"actions":["read"]}`,
|
||||
Constraints: `{"paths":["heicode/controller/**"]}`,
|
||||
Status: "active",
|
||||
@@ -251,7 +251,7 @@ func TestGenerateResourceGrantManifestIncludesActiveGrantsOnly(t *testing.T) {
|
||||
BindingScope: "repo-main",
|
||||
ResourceId: resource.Id,
|
||||
Role: "backend",
|
||||
AgnetId: "agnet-backend-1",
|
||||
AgentId: "agent-backend-1",
|
||||
PermissionScope: `{"actions":["write"]}`,
|
||||
Status: "revoked",
|
||||
}).Error)
|
||||
@@ -261,7 +261,7 @@ func TestGenerateResourceGrantManifestIncludesActiveGrantsOnly(t *testing.T) {
|
||||
7,
|
||||
http.MethodGet,
|
||||
"/manifest",
|
||||
"/manifest?binding_scope=repo-main&role=backend&agnet_id=agnet-backend-1",
|
||||
"/manifest?binding_scope=repo-main&role=backend&agent_id=agent-backend-1",
|
||||
"",
|
||||
)
|
||||
|
||||
@@ -269,7 +269,7 @@ func TestGenerateResourceGrantManifestIncludesActiveGrantsOnly(t *testing.T) {
|
||||
require.Contains(t, w.Body.String(), `"success":true`)
|
||||
require.Contains(t, w.Body.String(), `"binding_scope":"repo-main"`)
|
||||
require.Contains(t, w.Body.String(), `"agent_role":"backend"`)
|
||||
require.Contains(t, w.Body.String(), `"target_agent_ref":"agnet-backend-1"`)
|
||||
require.Contains(t, w.Body.String(), `"target_agent_ref":"agent-backend-1"`)
|
||||
require.Contains(t, w.Body.String(), `"resource_type":"git"`)
|
||||
require.Contains(t, w.Body.String(), `"allowed_actions":["read"]`)
|
||||
require.Contains(t, w.Body.String(), `"secret_ref":"azkv://heicode-kv.vault.azure.net/secrets/repo-main"`)
|
||||
@@ -293,7 +293,7 @@ func TestDeleteResourceGrantRevokesInsteadOfDeleting(t *testing.T) {
|
||||
BindingScope: "project-alpha",
|
||||
ResourceId: resource.Id,
|
||||
Role: "backend",
|
||||
AgnetId: "agent-backend-1",
|
||||
AgentId: "agent-backend-1",
|
||||
PermissionScope: `{"actions":["repo:read","repo:write"]}`,
|
||||
Constraints: `{"paths":["src/**"]}`,
|
||||
Status: "active",
|
||||
|
||||
@@ -45,7 +45,7 @@ services:
|
||||
- NODE_NAME=heicode-node-1
|
||||
# 默认与 docs/integration/Heicode-登录接口对接文档.md §2.1 一致;覆盖仅用于非标准网关。
|
||||
- HEICODE_AUTH_BASE_URL=${HEICODE_AUTH_BASE_URL:-https://apimtaiji.azure-api.net/api/mcp}
|
||||
# Agnet 登录后 JIT 同步:邮箱命中以下白名单则自动提权
|
||||
# Agent 登录后 JIT 同步:邮箱命中以下白名单则自动提权
|
||||
- HEICODE_ROOT_EMAILS=${HEICODE_ROOT_EMAILS:-}
|
||||
- HEICODE_ADMIN_EMAILS=${HEICODE_ADMIN_EMAILS:-}
|
||||
# Long-lived resource credentials are written to Azure Key Vault via
|
||||
@@ -54,18 +54,18 @@ services:
|
||||
- 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_STOP_PATH=${AGNET_RUNTIME_STOP_PATH:-/api/agnet/deployments/{deployment_id}/stop}
|
||||
- AGNET_RUNTIME_SERVICE_TOKEN=${AGNET_RUNTIME_SERVICE_TOKEN:-}
|
||||
- AGNET_RUNTIME_CALLBACK_SIGNING_SECRET_REF=${AGNET_RUNTIME_CALLBACK_SIGNING_SECRET_REF:-}
|
||||
# HeiCode-Swarm Runtime is separate from ordinary sub Agnet Runtime.
|
||||
- AGENT_RUNTIME_ENABLED=${AGENT_RUNTIME_ENABLED:-false}
|
||||
- AGENT_RUNTIME_BASE_URL=${AGENT_RUNTIME_BASE_URL:-http://20.212.121.126}
|
||||
- AGENT_RUNTIME_CREATE_PATH=${AGENT_RUNTIME_CREATE_PATH:-/api/agent/deployments}
|
||||
- AGENT_RUNTIME_HEALTH_PATH=${AGENT_RUNTIME_HEALTH_PATH:-/api/agent/health}
|
||||
- AGENT_RUNTIME_STOP_PATH=${AGENT_RUNTIME_STOP_PATH:-/api/agent/deployments/{deployment_id}/stop}
|
||||
- AGENT_RUNTIME_SERVICE_TOKEN=${AGENT_RUNTIME_SERVICE_TOKEN:-}
|
||||
- AGENT_RUNTIME_CALLBACK_SIGNING_SECRET_REF=${AGENT_RUNTIME_CALLBACK_SIGNING_SECRET_REF:-}
|
||||
# HeiCode-Swarm Runtime is separate from ordinary sub Agent Runtime.
|
||||
- SWARM_RUNTIME_ENABLED=${SWARM_RUNTIME_ENABLED:-false}
|
||||
- SWARM_RUNTIME_BASE_URL=${SWARM_RUNTIME_BASE_URL:-}
|
||||
- SWARM_RUNTIME_CREATE_PATH=${SWARM_RUNTIME_CREATE_PATH:-/api/swarms}
|
||||
- SWARM_RUNTIME_HEALTH_PATH=${SWARM_RUNTIME_HEALTH_PATH:-/api/agnet/health}
|
||||
- SWARM_RUNTIME_HEALTH_PATH=${SWARM_RUNTIME_HEALTH_PATH:-/api/agent/health}
|
||||
- SWARM_RUNTIME_STOP_PATH=${SWARM_RUNTIME_STOP_PATH:-/api/swarms/{swarm_id}/stop}
|
||||
- SWARM_RUNTIME_APPROVAL_DECISION_PATH=${SWARM_RUNTIME_APPROVAL_DECISION_PATH:-/api/swarms/{swarm_id}/approvals/{approval_id}}
|
||||
- SWARM_RUNTIME_SERVICE_TOKEN=${SWARM_RUNTIME_SERVICE_TOKEN:-}
|
||||
|
||||
@@ -12,7 +12,7 @@ Security rule: never put real passwords, tokens, SSH keys, Redis keys, PostgreSQ
|
||||
| `heicode/docker-compose.azure-vm.yml` | Azure VM Manager service | Runs only `heicode`; PostgreSQL and Redis are expected to be managed Azure services. |
|
||||
| `heicode/docker-compose.override.yml` | Local-source image override | Builds `heicode-manager:local` from the checked-out repo. Keep it in the compose file list when deploying this repo state. |
|
||||
| `heicode/bin/azure_vm_deploy.sh` | SSH deployment helper | Uses env vars only; can fast-forward a remote branch, performs remote compose up, health gate, and rollback pointer capture. |
|
||||
| `heicode/bin/acceptance_agnet_local.sh` | Local Agnet control-plane smoke/acceptance probe | Requires an admin session cookie supplied via env; does not store credentials. |
|
||||
| `heicode/bin/acceptance_agent_local.sh` | Local Agent control-plane smoke/acceptance probe | Requires an admin session cookie supplied via env; does not store credentials. |
|
||||
| `heicode/.env.example` | Env-var reference | Placeholder-only reference; production `.env` must stay on the VM and out of Git. |
|
||||
|
||||
## 2. Required VM inputs
|
||||
@@ -114,9 +114,9 @@ The script will:
|
||||
6. Poll `/api/status` through the VM-local health URL.
|
||||
7. Update `.last_success_image` only after the health gate passes.
|
||||
|
||||
## 5.1 Agnet operator handoff
|
||||
## 5.1 Agent operator handoff
|
||||
|
||||
When Agnet is the executor, Manager should create a high-risk `newapi-rebuild-deploy` deployment using `docs/integration/agnet-platform-request-contract.md` and pass only references:
|
||||
When Agent is the executor, Manager should create a high-risk `newapi-rebuild-deploy` deployment using `docs/integration/agent-platform-request-contract.md` and pass only references:
|
||||
|
||||
| Field | Required reference |
|
||||
|---|---|
|
||||
@@ -126,7 +126,7 @@ When Agnet is the executor, Manager should create a high-risk `newapi-rebuild-de
|
||||
| `orchestration_plan.constraints.healthcheck_url_ref` | `env://NEWAPI_HEALTHCHECK_URL`, expected to resolve to the VM-local `/api/status` probe. |
|
||||
| `orchestration_plan.metadata.commit` | Intended Git commit or branch to deploy, such as `origin/main` after push. |
|
||||
|
||||
Minimum evidence Agnet must return before Manager marks the operation deployed:
|
||||
Minimum evidence Agent must return before Manager marks the operation deployed:
|
||||
|
||||
1. Remote commit after fetch/pull.
|
||||
2. `docker compose ... ps` status for `heicode`.
|
||||
@@ -149,14 +149,14 @@ curl -fsS http://127.0.0.1:3000/api/status
|
||||
|
||||
Expected health response includes `"success":true`.
|
||||
|
||||
Optional Manager / Agnet smoke probe after obtaining a safe admin session cookie without logging it:
|
||||
Optional Manager / Agent smoke probe after obtaining a safe admin session cookie without logging it:
|
||||
|
||||
```bash
|
||||
AUTH_COOKIE='REDACTED_SESSION_COOKIE' \
|
||||
BASE_URL='http://127.0.0.1:3000' \
|
||||
TENANT_ID='tenant_smoke' \
|
||||
PROJECT_ID='project_smoke' \
|
||||
./bin/acceptance_agnet_local.sh
|
||||
./bin/acceptance_agent_local.sh
|
||||
```
|
||||
|
||||
Do not commit or report the real cookie.
|
||||
|
||||
@@ -29,13 +29,13 @@ import (
|
||||
func TestUserOrV2DeviceAuthRejectsMalformedEncryptedRequest(t *testing.T) {
|
||||
gin.SetMode(gin.TestMode)
|
||||
router := gin.New()
|
||||
router.POST("/api/agnet/user/tasks/:task_id/deployment-draft", UserOrV2DeviceAuth(), func(c *gin.Context) {
|
||||
router.POST("/api/agent/user/tasks/:task_id/deployment-draft", UserOrV2DeviceAuth(), func(c *gin.Context) {
|
||||
c.JSON(http.StatusOK, gin.H{"success": true})
|
||||
})
|
||||
|
||||
req := httptest.NewRequest(
|
||||
http.MethodPost,
|
||||
"/api/agnet/user/tasks/task-v2/deployment-draft",
|
||||
"/api/agent/user/tasks/task-v2/deployment-draft",
|
||||
nil,
|
||||
)
|
||||
req.Header.Set("Content-Encoding", V2ContentEncoding)
|
||||
@@ -100,7 +100,7 @@ func TestUserOrV2DeviceAuthDecryptsValidEncryptedRequest(t *testing.T) {
|
||||
}
|
||||
|
||||
router := gin.New()
|
||||
router.POST("/api/agnet/user/tasks/:task_id/deployment-draft", UserOrV2DeviceAuth(), func(c *gin.Context) {
|
||||
router.POST("/api/agent/user/tasks/:task_id/deployment-draft", UserOrV2DeviceAuth(), func(c *gin.Context) {
|
||||
if got := c.GetInt("id"); got != 42 {
|
||||
t.Fatalf("id context = %d, want 42", got)
|
||||
}
|
||||
@@ -116,7 +116,7 @@ func TestUserOrV2DeviceAuthDecryptsValidEncryptedRequest(t *testing.T) {
|
||||
c.JSON(http.StatusOK, gin.H{"success": true})
|
||||
})
|
||||
|
||||
path := "/api/agnet/user/tasks/task-v2/deployment-draft"
|
||||
path := "/api/agent/user/tasks/task-v2/deployment-draft"
|
||||
body := []byte(`{"objective":"encrypted sub task"}`)
|
||||
req := newEncryptedV2Request(t, http.MethodPost, path, body, deviceID, fingerprint, privateKey)
|
||||
rec := httptest.NewRecorder()
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
package model
|
||||
|
||||
// AgnetApprovalRequest records a user-visible approval gate for a
|
||||
// high-risk Agnet operation. It intentionally stores only a Secret
|
||||
// AgentApprovalRequest records a user-visible approval gate for a
|
||||
// high-risk Agent operation. It intentionally stores only a Secret
|
||||
// Store reference for credential-backed operations; plaintext secrets
|
||||
// must never be written to this table.
|
||||
type AgnetApprovalRequest struct {
|
||||
type AgentApprovalRequest struct {
|
||||
Id int `json:"id" gorm:"primaryKey"`
|
||||
ApprovalID string `json:"approval_id" gorm:"type:varchar(64);uniqueIndex;not null"`
|
||||
UserId int `json:"user_id" gorm:"index;not null"`
|
||||
@@ -31,14 +31,14 @@ type AgnetApprovalRequest struct {
|
||||
UpdatedAt int64 `json:"updated_at" gorm:"autoUpdateTime;column:updated_at"`
|
||||
}
|
||||
|
||||
func (AgnetApprovalRequest) TableName() string {
|
||||
return "agnet_approval_requests"
|
||||
func (AgentApprovalRequest) TableName() string {
|
||||
return "agent_approval_requests"
|
||||
}
|
||||
|
||||
// AgnetCredentialLease is the Manager-side short-lived credential
|
||||
// AgentCredentialLease is the Manager-side short-lived credential
|
||||
// handle produced after an approval succeeds. CredentialRef is the
|
||||
// external handle; SecretRef is internal and points at Azure Key Vault.
|
||||
type AgnetCredentialLease struct {
|
||||
type AgentCredentialLease struct {
|
||||
Id int `json:"id" gorm:"primaryKey"`
|
||||
LeaseID string `json:"lease_id" gorm:"type:varchar(64);uniqueIndex;not null"`
|
||||
CredentialRef string `json:"credential_ref" gorm:"type:varchar(128);uniqueIndex;not null"`
|
||||
@@ -59,6 +59,6 @@ type AgnetCredentialLease struct {
|
||||
UpdatedAt int64 `json:"updated_at" gorm:"autoUpdateTime;column:updated_at"`
|
||||
}
|
||||
|
||||
func (AgnetCredentialLease) TableName() string {
|
||||
return "agnet_credential_leases"
|
||||
func (AgentCredentialLease) TableName() string {
|
||||
return "agent_credential_leases"
|
||||
}
|
||||
@@ -7,7 +7,7 @@ import (
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
type AgnetArtifact struct {
|
||||
type AgentArtifact struct {
|
||||
Id int `gorm:"primaryKey" json:"id"`
|
||||
ArtifactID string `gorm:"type:varchar(128);uniqueIndex" json:"artifact_id"`
|
||||
DeploymentID string `gorm:"type:varchar(64);index" json:"deployment_id"`
|
||||
@@ -24,22 +24,22 @@ type AgnetArtifact struct {
|
||||
CreatedAtMs int64 `gorm:"bigint;index" json:"created_at_ms"`
|
||||
}
|
||||
|
||||
func (AgnetArtifact) TableName() string {
|
||||
return "agnet_artifacts"
|
||||
func (AgentArtifact) TableName() string {
|
||||
return "agent_artifacts"
|
||||
}
|
||||
|
||||
type ListAgnetArtifactsFilter struct {
|
||||
type ListAgentArtifactsFilter struct {
|
||||
DeploymentID string
|
||||
TaskID string
|
||||
CorrelationID string
|
||||
Limit int
|
||||
}
|
||||
|
||||
func UpsertAgnetArtifact(row *AgnetArtifact) error {
|
||||
func UpsertAgentArtifact(row *AgentArtifact) error {
|
||||
if DB == nil || row == nil {
|
||||
return nil
|
||||
}
|
||||
var existing AgnetArtifact
|
||||
var existing AgentArtifact
|
||||
if row.ArtifactID != "" {
|
||||
if err := DB.Where("artifact_id = ?", row.ArtifactID).First(&existing).Error; err == nil {
|
||||
row.Id = existing.Id
|
||||
@@ -49,11 +49,11 @@ func UpsertAgnetArtifact(row *AgnetArtifact) error {
|
||||
return DB.Create(row).Error
|
||||
}
|
||||
|
||||
func ListAgnetArtifacts(f ListAgnetArtifactsFilter) ([]AgnetArtifact, error) {
|
||||
func ListAgentArtifacts(f ListAgentArtifactsFilter) ([]AgentArtifact, error) {
|
||||
if DB == nil {
|
||||
return nil, nil
|
||||
}
|
||||
q := DB.Model(&AgnetArtifact{})
|
||||
q := DB.Model(&AgentArtifact{})
|
||||
if f.DeploymentID != "" {
|
||||
q = q.Where("deployment_id = ?", f.DeploymentID)
|
||||
}
|
||||
@@ -67,13 +67,13 @@ func ListAgnetArtifacts(f ListAgnetArtifactsFilter) ([]AgnetArtifact, error) {
|
||||
if limit <= 0 || limit > 1000 {
|
||||
limit = 200
|
||||
}
|
||||
var items []AgnetArtifact
|
||||
var items []AgentArtifact
|
||||
err := q.Order("created_at_ms asc, id asc").Limit(limit).Find(&items).Error
|
||||
return items, err
|
||||
}
|
||||
|
||||
func GetAgnetArtifactByDeployment(deploymentID string, artifactID string) (AgnetArtifact, bool, error) {
|
||||
var row AgnetArtifact
|
||||
func GetAgentArtifactByDeployment(deploymentID string, artifactID string) (AgentArtifact, bool, error) {
|
||||
var row AgentArtifact
|
||||
if DB == nil {
|
||||
return row, false, nil
|
||||
}
|
||||
@@ -6,15 +6,15 @@ import (
|
||||
"github.com/heicode/manager/common"
|
||||
)
|
||||
|
||||
// AgnetAuditEvent is the persistent audit-trail row for the Agnet
|
||||
// AgentAuditEvent is the persistent audit-trail row for the Agent
|
||||
// control-plane. Each row records one observable transition in the
|
||||
// Agnet lifecycle — deployment accepted, instance phase changed, SK
|
||||
// Agent lifecycle — deployment accepted, instance phase changed, SK
|
||||
// snapshot refreshed, etc. — so admins can answer the "who / when /
|
||||
// for which task / against which resource / with what result" set of
|
||||
// questions even after the Manager container restarts.
|
||||
//
|
||||
// Before this table existed the control-plane stashed events in an
|
||||
// in-process `map[string][]agnetEvent` (controller/agnet_control_plane
|
||||
// in-process `map[string][]agentEvent` (controller/agent_control_plane
|
||||
// .go:219). Every restart wiped audit history — unacceptable for a
|
||||
// product where the audit page is part of the security story.
|
||||
//
|
||||
@@ -32,7 +32,7 @@ import (
|
||||
// Cross-DB compatibility (CLAUDE.md Rule 2): GORM AutoMigrate maps the
|
||||
// tags to the correct types on SQLite / MySQL / PostgreSQL. No raw
|
||||
// SQL. No DB-specific column types.
|
||||
type AgnetAuditEvent struct {
|
||||
type AgentAuditEvent struct {
|
||||
Id int `gorm:"primaryKey" json:"id"`
|
||||
EventID string `gorm:"type:varchar(64);uniqueIndex" json:"event_id"`
|
||||
Event string `gorm:"type:varchar(64);index" json:"event"`
|
||||
@@ -52,17 +52,17 @@ type AgnetAuditEvent struct {
|
||||
|
||||
// TableName pins the migration target so we can rename the Go struct
|
||||
// later without breaking the deployed schema.
|
||||
func (AgnetAuditEvent) TableName() string {
|
||||
return "agnet_audit_events"
|
||||
func (AgentAuditEvent) TableName() string {
|
||||
return "agent_audit_events"
|
||||
}
|
||||
|
||||
// InsertAgnetAuditEvent best-effort persists one audit row. Callers
|
||||
// InsertAgentAuditEvent best-effort persists one audit row. Callers
|
||||
// invoke this in a hot path (right after mutating a deployment), so:
|
||||
// - errors are logged but never returned — the audit write must NOT
|
||||
// fail the user-facing API
|
||||
// - DB is nil-guarded so unit tests / partial-init binaries don't
|
||||
// panic on a missing connection
|
||||
func InsertAgnetAuditEvent(evt *AgnetAuditEvent) {
|
||||
func InsertAgentAuditEvent(evt *AgentAuditEvent) {
|
||||
if DB == nil || evt == nil {
|
||||
return
|
||||
}
|
||||
@@ -76,14 +76,14 @@ func InsertAgnetAuditEvent(evt *AgnetAuditEvent) {
|
||||
evt.Result = "ok"
|
||||
}
|
||||
if err := DB.Create(evt).Error; err != nil {
|
||||
common.SysLog("InsertAgnetAuditEvent: " + err.Error())
|
||||
common.SysLog("InsertAgentAuditEvent: " + err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
// ListAgnetAuditEventsFilter narrows the audit query to a slice of
|
||||
// ListAgentAuditEventsFilter narrows the audit query to a slice of
|
||||
// dashboard relevant rows. Zero-value fields are ignored — callers
|
||||
// pass only the filters they care about.
|
||||
type ListAgnetAuditEventsFilter struct {
|
||||
type ListAgentAuditEventsFilter struct {
|
||||
UserID string
|
||||
BindingScope string
|
||||
DeploymentID string
|
||||
@@ -94,14 +94,14 @@ type ListAgnetAuditEventsFilter struct {
|
||||
Offset int
|
||||
}
|
||||
|
||||
// ListAgnetAuditEvents pages over audit rows ordered newest-first.
|
||||
// Used by the /api/agnet/audit-logs endpoint and the future task-
|
||||
// ListAgentAuditEvents pages over audit rows ordered newest-first.
|
||||
// Used by the /api/agent/audit-logs endpoint and the future task-
|
||||
// scoped audit drawer.
|
||||
func ListAgnetAuditEvents(f ListAgnetAuditEventsFilter) ([]AgnetAuditEvent, int64, error) {
|
||||
func ListAgentAuditEvents(f ListAgentAuditEventsFilter) ([]AgentAuditEvent, int64, error) {
|
||||
if DB == nil {
|
||||
return nil, 0, nil
|
||||
}
|
||||
q := DB.Model(&AgnetAuditEvent{})
|
||||
q := DB.Model(&AgentAuditEvent{})
|
||||
if f.UserID != "" {
|
||||
q = q.Where("user_id = ?", f.UserID)
|
||||
}
|
||||
@@ -128,19 +128,19 @@ func ListAgnetAuditEvents(f ListAgnetAuditEventsFilter) ([]AgnetAuditEvent, int6
|
||||
if limit <= 0 || limit > 1000 {
|
||||
limit = 200
|
||||
}
|
||||
var items []AgnetAuditEvent
|
||||
var items []AgentAuditEvent
|
||||
err := q.Order("occurred_at desc, id desc").Limit(limit).Offset(f.Offset).Find(&items).Error
|
||||
return items, total, err
|
||||
}
|
||||
|
||||
// ListAgnetAuditEventsByDeployment is the hot path for the deployment
|
||||
// ListAgentAuditEventsByDeployment is the hot path for the deployment
|
||||
// detail drawer — returns all events for one deployment in chronological
|
||||
// order so the timeline reads top-to-bottom.
|
||||
func ListAgnetAuditEventsByDeployment(deploymentID string) ([]AgnetAuditEvent, error) {
|
||||
func ListAgentAuditEventsByDeployment(deploymentID string) ([]AgentAuditEvent, error) {
|
||||
if DB == nil || deploymentID == "" {
|
||||
return nil, nil
|
||||
}
|
||||
var items []AgnetAuditEvent
|
||||
var items []AgentAuditEvent
|
||||
err := DB.Where("deployment_id = ?", deploymentID).
|
||||
Order("occurred_at asc, id asc").
|
||||
Find(&items).Error
|
||||
@@ -15,24 +15,24 @@ import (
|
||||
|
||||
func setupAuditTest(t *testing.T) {
|
||||
t.Helper()
|
||||
if err := DB.AutoMigrate(&AgnetAuditEvent{}); err != nil {
|
||||
if err := DB.AutoMigrate(&AgentAuditEvent{}); err != nil {
|
||||
t.Fatalf("migrate: %v", err)
|
||||
}
|
||||
if err := DB.Exec("DELETE FROM agnet_audit_events").Error; err != nil {
|
||||
if err := DB.Exec("DELETE FROM agent_audit_events").Error; err != nil {
|
||||
t.Fatalf("truncate: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestInsertAgnetAuditEvent_PersistsRow(t *testing.T) {
|
||||
func TestInsertAgentAuditEvent_PersistsRow(t *testing.T) {
|
||||
// Regression for H1 — the previous implementation appended to an
|
||||
// in-process map that was wiped on every container restart. This
|
||||
// test pins the new behaviour: rows survive in DB.
|
||||
setupAuditTest(t)
|
||||
|
||||
evt := &AgnetAuditEvent{
|
||||
evt := &AgentAuditEvent{
|
||||
EventID: "evt_test_001",
|
||||
Event: "deployment.accepted",
|
||||
Actor: "agnet_control_plane",
|
||||
Actor: "agent_control_plane",
|
||||
Resource: "dep_abc",
|
||||
UserID: "user-42",
|
||||
ChannelID: "channel-1",
|
||||
@@ -41,9 +41,9 @@ func TestInsertAgnetAuditEvent_PersistsRow(t *testing.T) {
|
||||
CorrelationID: "corr-xyz",
|
||||
RequestID: "req-001",
|
||||
}
|
||||
InsertAgnetAuditEvent(evt)
|
||||
InsertAgentAuditEvent(evt)
|
||||
|
||||
var got AgnetAuditEvent
|
||||
var got AgentAuditEvent
|
||||
if err := DB.Where("event_id = ?", "evt_test_001").First(&got).Error; err != nil {
|
||||
t.Fatalf("not persisted: %v", err)
|
||||
}
|
||||
@@ -58,24 +58,24 @@ func TestInsertAgnetAuditEvent_PersistsRow(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestInsertAgnetAuditEvent_NilGuards(t *testing.T) {
|
||||
func TestInsertAgentAuditEvent_NilGuards(t *testing.T) {
|
||||
// Production safety: audit writes run inside hot paths (right
|
||||
// after a deployment mutation). A nil DB or nil event MUST NOT
|
||||
// panic — better to drop the audit row than to fail the user
|
||||
// API call.
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
t.Fatalf("InsertAgnetAuditEvent panicked: %v", r)
|
||||
t.Fatalf("InsertAgentAuditEvent panicked: %v", r)
|
||||
}
|
||||
}()
|
||||
InsertAgnetAuditEvent(nil) // nil evt
|
||||
InsertAgentAuditEvent(nil) // nil evt
|
||||
prev := DB
|
||||
DB = nil
|
||||
InsertAgnetAuditEvent(&AgnetAuditEvent{EventID: "x"}) // nil DB
|
||||
InsertAgentAuditEvent(&AgentAuditEvent{EventID: "x"}) // nil DB
|
||||
DB = prev
|
||||
}
|
||||
|
||||
func TestListAgnetAuditEvents_FilterAndPaginate(t *testing.T) {
|
||||
func TestListAgentAuditEvents_FilterAndPaginate(t *testing.T) {
|
||||
setupAuditTest(t)
|
||||
|
||||
now := time.Now().UnixMilli()
|
||||
@@ -84,7 +84,7 @@ func TestListAgnetAuditEvents_FilterAndPaginate(t *testing.T) {
|
||||
if i%2 == 0 {
|
||||
uid = "user-B"
|
||||
}
|
||||
InsertAgnetAuditEvent(&AgnetAuditEvent{
|
||||
InsertAgentAuditEvent(&AgentAuditEvent{
|
||||
EventID: fmt.Sprintf("evt_%d", i),
|
||||
Event: "deployment.accepted",
|
||||
UserID: uid,
|
||||
@@ -94,7 +94,7 @@ func TestListAgnetAuditEvents_FilterAndPaginate(t *testing.T) {
|
||||
}
|
||||
|
||||
// All rows visible without filter.
|
||||
rows, total, err := ListAgnetAuditEvents(ListAgnetAuditEventsFilter{})
|
||||
rows, total, err := ListAgentAuditEvents(ListAgentAuditEventsFilter{})
|
||||
if err != nil {
|
||||
t.Fatalf("list: %v", err)
|
||||
}
|
||||
@@ -103,7 +103,7 @@ func TestListAgnetAuditEvents_FilterAndPaginate(t *testing.T) {
|
||||
}
|
||||
|
||||
// user_id filter narrows to 3 (i=0,2,4 → user-B).
|
||||
_, total, err = ListAgnetAuditEvents(ListAgnetAuditEventsFilter{UserID: "user-B"})
|
||||
_, total, err = ListAgentAuditEvents(ListAgentAuditEventsFilter{UserID: "user-B"})
|
||||
if err != nil {
|
||||
t.Fatalf("list with userid: %v", err)
|
||||
}
|
||||
@@ -112,28 +112,28 @@ func TestListAgnetAuditEvents_FilterAndPaginate(t *testing.T) {
|
||||
}
|
||||
|
||||
// Newest-first ordering. evt_4 inserted last → top.
|
||||
rows, _, _ = ListAgnetAuditEvents(ListAgnetAuditEventsFilter{})
|
||||
rows, _, _ = ListAgentAuditEvents(ListAgentAuditEventsFilter{})
|
||||
if rows[0].EventID != "evt_4" {
|
||||
t.Errorf("expected newest-first, got %q on top", rows[0].EventID)
|
||||
}
|
||||
}
|
||||
|
||||
func TestListAgnetAuditEventsByDeployment_Chronological(t *testing.T) {
|
||||
func TestListAgentAuditEventsByDeployment_Chronological(t *testing.T) {
|
||||
// Detail-drawer reads need oldest-first for a top-to-bottom
|
||||
// timeline. Confirms ascending order independent of insertion
|
||||
// order.
|
||||
setupAuditTest(t)
|
||||
|
||||
InsertAgnetAuditEvent(&AgnetAuditEvent{
|
||||
InsertAgentAuditEvent(&AgentAuditEvent{
|
||||
EventID: "evt_late", Event: "x", DeploymentID: "dep_T",
|
||||
OccurredAt: 9000,
|
||||
})
|
||||
InsertAgnetAuditEvent(&AgnetAuditEvent{
|
||||
InsertAgentAuditEvent(&AgentAuditEvent{
|
||||
EventID: "evt_early", Event: "x", DeploymentID: "dep_T",
|
||||
OccurredAt: 1000,
|
||||
})
|
||||
|
||||
rows, err := ListAgnetAuditEventsByDeployment("dep_T")
|
||||
rows, err := ListAgentAuditEventsByDeployment("dep_T")
|
||||
if err != nil {
|
||||
t.Fatalf("query: %v", err)
|
||||
}
|
||||
@@ -2,7 +2,7 @@ package model
|
||||
|
||||
import "errors"
|
||||
|
||||
type AgnetCallbackEvent struct {
|
||||
type AgentCallbackEvent struct {
|
||||
Id int `gorm:"primaryKey" json:"id"`
|
||||
EventID string `gorm:"type:varchar(128);uniqueIndex" json:"event_id"`
|
||||
IdempotencyKey string `gorm:"type:varchar(128);index" json:"idempotency_key"`
|
||||
@@ -22,25 +22,25 @@ type AgnetCallbackEvent struct {
|
||||
CreatedAtMs int64 `gorm:"bigint;index" json:"created_at_ms"`
|
||||
}
|
||||
|
||||
func (AgnetCallbackEvent) TableName() string {
|
||||
return "agnet_callback_events"
|
||||
func (AgentCallbackEvent) TableName() string {
|
||||
return "agent_callback_events"
|
||||
}
|
||||
|
||||
type ListAgnetCallbackEventsFilter struct {
|
||||
type ListAgentCallbackEventsFilter struct {
|
||||
DeploymentID string
|
||||
TaskID string
|
||||
CorrelationID string
|
||||
Limit int
|
||||
}
|
||||
|
||||
func InsertAgnetCallbackEvent(row *AgnetCallbackEvent) (bool, error) {
|
||||
func InsertAgentCallbackEvent(row *AgentCallbackEvent) (bool, error) {
|
||||
if DB == nil || row == nil {
|
||||
return false, nil
|
||||
}
|
||||
if row.EventID == "" {
|
||||
return false, errors.New("event_id is required")
|
||||
}
|
||||
var existing AgnetCallbackEvent
|
||||
var existing AgentCallbackEvent
|
||||
if err := DB.Where("event_id = ?", row.EventID).First(&existing).Error; err == nil {
|
||||
return false, nil
|
||||
}
|
||||
@@ -55,11 +55,11 @@ func InsertAgnetCallbackEvent(row *AgnetCallbackEvent) (bool, error) {
|
||||
return true, nil
|
||||
}
|
||||
|
||||
func ListAgnetCallbackEvents(f ListAgnetCallbackEventsFilter) ([]AgnetCallbackEvent, error) {
|
||||
func ListAgentCallbackEvents(f ListAgentCallbackEventsFilter) ([]AgentCallbackEvent, error) {
|
||||
if DB == nil {
|
||||
return nil, nil
|
||||
}
|
||||
q := DB.Model(&AgnetCallbackEvent{})
|
||||
q := DB.Model(&AgentCallbackEvent{})
|
||||
if f.DeploymentID != "" {
|
||||
q = q.Where("deployment_id = ?", f.DeploymentID)
|
||||
}
|
||||
@@ -73,7 +73,7 @@ func ListAgnetCallbackEvents(f ListAgnetCallbackEventsFilter) ([]AgnetCallbackEv
|
||||
if limit <= 0 || limit > 1000 {
|
||||
limit = 200
|
||||
}
|
||||
var items []AgnetCallbackEvent
|
||||
var items []AgentCallbackEvent
|
||||
err := q.Order("created_at_ms asc, id asc").Limit(limit).Find(&items).Error
|
||||
return items, err
|
||||
}
|
||||
@@ -1,10 +1,10 @@
|
||||
package model
|
||||
|
||||
// AgnetDeployment stores the Manager-side deployment placeholder.
|
||||
// It is intentionally a control-plane snapshot: the real Agnet platform
|
||||
// AgentDeployment stores the Manager-side deployment placeholder.
|
||||
// It is intentionally a control-plane snapshot: the real Agent platform
|
||||
// execution state can attach later, but Manager must not lose the accepted
|
||||
// plan, manifest, status, or audit context across container restarts.
|
||||
type AgnetDeployment struct {
|
||||
type AgentDeployment struct {
|
||||
Id int `gorm:"primaryKey" json:"id"`
|
||||
DeploymentID string `gorm:"type:varchar(64);uniqueIndex" json:"deployment_id"`
|
||||
UserID string `gorm:"type:varchar(64);index" json:"user_id"`
|
||||
@@ -29,6 +29,6 @@ type AgnetDeployment struct {
|
||||
PayloadJSON string `gorm:"type:text" json:"payload_json"`
|
||||
}
|
||||
|
||||
func (AgnetDeployment) TableName() string {
|
||||
return "agnet_deployments"
|
||||
func (AgentDeployment) TableName() string {
|
||||
return "agent_deployments"
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
package model
|
||||
|
||||
type AgnetSKSnapshot struct {
|
||||
type AgentSKSnapshot struct {
|
||||
Id int `gorm:"primaryKey" json:"id"`
|
||||
SnapshotID string `gorm:"type:varchar(128);uniqueIndex" json:"snapshot_id"`
|
||||
DeploymentID string `gorm:"type:varchar(64);index" json:"deployment_id"`
|
||||
@@ -12,22 +12,22 @@ type AgnetSKSnapshot struct {
|
||||
ResolvedAtMs int64 `gorm:"bigint;index" json:"resolved_at_ms"`
|
||||
}
|
||||
|
||||
func (AgnetSKSnapshot) TableName() string {
|
||||
return "agnet_sk_snapshots"
|
||||
func (AgentSKSnapshot) TableName() string {
|
||||
return "agent_sk_snapshots"
|
||||
}
|
||||
|
||||
func InsertAgnetSKSnapshots(items []AgnetSKSnapshot) error {
|
||||
func InsertAgentSKSnapshots(items []AgentSKSnapshot) error {
|
||||
if DB == nil || len(items) == 0 {
|
||||
return nil
|
||||
}
|
||||
return DB.Create(&items).Error
|
||||
}
|
||||
|
||||
func ListAgnetSKSnapshots(deploymentID string) ([]AgnetSKSnapshot, error) {
|
||||
func ListAgentSKSnapshots(deploymentID string) ([]AgentSKSnapshot, error) {
|
||||
if DB == nil || deploymentID == "" {
|
||||
return nil, nil
|
||||
}
|
||||
var items []AgnetSKSnapshot
|
||||
var items []AgentSKSnapshot
|
||||
err := DB.Where("deployment_id = ?", deploymentID).
|
||||
Order("resolved_at_ms asc, id asc").
|
||||
Find(&items).Error
|
||||
+53
-17
@@ -247,6 +247,40 @@ func InitLogDB() (err error) {
|
||||
return err
|
||||
}
|
||||
|
||||
// renameAgnetTablesToAgent renames legacy agnet_* tables (and the
|
||||
// resource_grants.agnet_id column) to their agent_* equivalents, preserving
|
||||
// existing production data after the agnet->agent terminology unification.
|
||||
// Idempotent and cross-DB (uses GORM Migrator). Runs before AutoMigrate so the
|
||||
// renamed tables are reused instead of being recreated empty.
|
||||
func renameAgnetTablesToAgent() {
|
||||
m := DB.Migrator()
|
||||
pairs := [][2]string{
|
||||
{"agnet_approval_requests", "agent_approval_requests"},
|
||||
{"agnet_credential_leases", "agent_credential_leases"},
|
||||
{"agnet_deployments", "agent_deployments"},
|
||||
{"agnet_callback_events", "agent_callback_events"},
|
||||
{"agnet_artifacts", "agent_artifacts"},
|
||||
{"agnet_sk_snapshots", "agent_sk_snapshots"},
|
||||
{"agnet_audit_events", "agent_audit_events"},
|
||||
}
|
||||
for _, p := range pairs {
|
||||
if m.HasTable(p[0]) && !m.HasTable(p[1]) {
|
||||
if err := m.RenameTable(p[0], p[1]); err != nil {
|
||||
common.SysLog("renameAgnetTablesToAgent: rename " + p[0] + " -> " + p[1] + ": " + err.Error())
|
||||
} else {
|
||||
common.SysLog("renameAgnetTablesToAgent: renamed " + p[0] + " -> " + p[1])
|
||||
}
|
||||
}
|
||||
}
|
||||
if m.HasTable("resource_grants") && m.HasColumn(&ResourceGrant{}, "agnet_id") && !m.HasColumn(&ResourceGrant{}, "agent_id") {
|
||||
if err := m.RenameColumn(&ResourceGrant{}, "agnet_id", "agent_id"); err != nil {
|
||||
common.SysLog("renameAgnetTablesToAgent: rename column agnet_id -> agent_id: " + err.Error())
|
||||
} else {
|
||||
common.SysLog("renameAgnetTablesToAgent: renamed column resource_grants.agnet_id -> agent_id")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func migrateDB() error {
|
||||
// Migrate price_amount column from float/double to decimal for existing tables
|
||||
migrateSubscriptionPlanPriceAmount()
|
||||
@@ -254,6 +288,8 @@ func migrateDB() error {
|
||||
if err := migrateTokenModelLimitsToText(); err != nil {
|
||||
return err
|
||||
}
|
||||
// Preserve data across the agnet->agent rename (idempotent, runs before AutoMigrate).
|
||||
renameAgnetTablesToAgent()
|
||||
|
||||
err := DB.AutoMigrate(
|
||||
&Channel{},
|
||||
@@ -283,19 +319,19 @@ func migrateDB() error {
|
||||
&GitSource{},
|
||||
&ResourceBinding{},
|
||||
&ResourceGrant{},
|
||||
&AgnetApprovalRequest{},
|
||||
&AgnetCredentialLease{},
|
||||
&AgnetDeployment{},
|
||||
&AgnetCallbackEvent{},
|
||||
&AgnetArtifact{},
|
||||
&AgnetSKSnapshot{},
|
||||
&AgentApprovalRequest{},
|
||||
&AgentCredentialLease{},
|
||||
&AgentDeployment{},
|
||||
&AgentCallbackEvent{},
|
||||
&AgentArtifact{},
|
||||
&AgentSKSnapshot{},
|
||||
// V2 device-binding: X25519 keypair the Manager uses for ECDH
|
||||
// body decryption. See model/server_key.go.
|
||||
&ServerKey{},
|
||||
// Agnet control-plane audit trail. Replaces the previous
|
||||
// in-process `agnetEvents map` that was wiped on every container
|
||||
// restart. See model/agnet_audit.go for the rationale.
|
||||
&AgnetAuditEvent{},
|
||||
// Agent control-plane audit trail. Replaces the previous
|
||||
// in-process `agentEvents map` that was wiped on every container
|
||||
// restart. See model/agent_audit.go for the rationale.
|
||||
&AgentAuditEvent{},
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -355,13 +391,13 @@ func migrateDBFast() error {
|
||||
{&GitSource{}, "GitSource"},
|
||||
{&ResourceBinding{}, "ResourceBinding"},
|
||||
{&ResourceGrant{}, "ResourceGrant"},
|
||||
{&AgnetApprovalRequest{}, "AgnetApprovalRequest"},
|
||||
{&AgnetCredentialLease{}, "AgnetCredentialLease"},
|
||||
{&AgnetDeployment{}, "AgnetDeployment"},
|
||||
{&AgnetCallbackEvent{}, "AgnetCallbackEvent"},
|
||||
{&AgnetArtifact{}, "AgnetArtifact"},
|
||||
{&AgnetSKSnapshot{}, "AgnetSKSnapshot"},
|
||||
{&AgnetAuditEvent{}, "AgnetAuditEvent"},
|
||||
{&AgentApprovalRequest{}, "AgentApprovalRequest"},
|
||||
{&AgentCredentialLease{}, "AgentCredentialLease"},
|
||||
{&AgentDeployment{}, "AgentDeployment"},
|
||||
{&AgentCallbackEvent{}, "AgentCallbackEvent"},
|
||||
{&AgentArtifact{}, "AgentArtifact"},
|
||||
{&AgentSKSnapshot{}, "AgentSKSnapshot"},
|
||||
{&AgentAuditEvent{}, "AgentAuditEvent"},
|
||||
}
|
||||
// 动态计算migration数量,确保errChan缓冲区足够大
|
||||
errChan := make(chan error, len(migrations))
|
||||
|
||||
@@ -22,7 +22,7 @@ type ResourceBinding struct {
|
||||
UpdatedAt int64 `json:"updated_at" gorm:"autoUpdateTime;column:updated_at"`
|
||||
}
|
||||
|
||||
// ResourceGrant assigns a ResourceBinding to a role and child Agnet.
|
||||
// ResourceGrant assigns a ResourceBinding to a role and child Agent.
|
||||
// It is the auditable Manager expression of "user grants bound resource to role".
|
||||
type ResourceGrant struct {
|
||||
Id int `json:"id"`
|
||||
@@ -32,7 +32,7 @@ type ResourceGrant struct {
|
||||
BindingScope string `json:"binding_scope" gorm:"type:varchar(512);index"`
|
||||
ResourceId int `json:"resource_id" gorm:"index;not null"`
|
||||
Role string `json:"role" gorm:"type:varchar(128);index;not null"`
|
||||
AgnetId string `json:"agnet_id" gorm:"type:varchar(128);index;not null"`
|
||||
AgentId string `json:"agent_id" gorm:"type:varchar(128);index;not null"`
|
||||
PermissionScope string `json:"permission_scope" gorm:"type:text"`
|
||||
Constraints string `json:"constraints" gorm:"type:text"`
|
||||
Status string `json:"status" gorm:"type:varchar(32);default:'active';index"`
|
||||
|
||||
+40
-40
@@ -21,7 +21,7 @@ import (
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
func setupAgnetRuntimeHTTPSmokeDB(t *testing.T) *gorm.DB {
|
||||
func setupAgentRuntimeHTTPSmokeDB(t *testing.T) *gorm.DB {
|
||||
t.Helper()
|
||||
common.UsingSQLite = true
|
||||
common.UsingMySQL = false
|
||||
@@ -35,8 +35,8 @@ func setupAgnetRuntimeHTTPSmokeDB(t *testing.T) *gorm.DB {
|
||||
model.LOG_DB = db
|
||||
require.NoError(t, db.AutoMigrate(
|
||||
&model.User{},
|
||||
&model.AgnetDeployment{},
|
||||
&model.AgnetAuditEvent{},
|
||||
&model.AgentDeployment{},
|
||||
&model.AgentAuditEvent{},
|
||||
))
|
||||
adminToken := "runtime-smoke-admin-token"
|
||||
require.NoError(t, db.Create(&model.User{
|
||||
@@ -64,7 +64,7 @@ func setupAgnetRuntimeHTTPSmokeDB(t *testing.T) *gorm.DB {
|
||||
return db
|
||||
}
|
||||
|
||||
func startAgnetRuntimeManagerSmokeServer(t *testing.T) string {
|
||||
func startAgentRuntimeManagerSmokeServer(t *testing.T) string {
|
||||
t.Helper()
|
||||
gin.SetMode(gin.TestMode)
|
||||
engine := gin.New()
|
||||
@@ -82,7 +82,7 @@ func startAgnetRuntimeManagerSmokeServer(t *testing.T) string {
|
||||
return "http://" + listener.Addr().String()
|
||||
}
|
||||
|
||||
func agnetRuntimeAdminRequest(t *testing.T, method string, url string, body string) *http.Response {
|
||||
func agentRuntimeAdminRequest(t *testing.T, method string, url string, body string) *http.Response {
|
||||
t.Helper()
|
||||
req, err := http.NewRequest(method, url, bytes.NewBufferString(body))
|
||||
require.NoError(t, err)
|
||||
@@ -96,7 +96,7 @@ func agnetRuntimeAdminRequest(t *testing.T, method string, url string, body stri
|
||||
return resp
|
||||
}
|
||||
|
||||
func readAgnetRuntimeSmokeBody(t *testing.T, resp *http.Response) string {
|
||||
func readAgentRuntimeSmokeBody(t *testing.T, resp *http.Response) string {
|
||||
t.Helper()
|
||||
defer resp.Body.Close()
|
||||
body, err := io.ReadAll(resp.Body)
|
||||
@@ -104,16 +104,16 @@ func readAgnetRuntimeSmokeBody(t *testing.T, resp *http.Response) string {
|
||||
return string(body)
|
||||
}
|
||||
|
||||
func TestAgnetRuntimeRealHTTPHealthAndShadowCreateSmoke(t *testing.T) {
|
||||
db := setupAgnetRuntimeHTTPSmokeDB(t)
|
||||
func TestAgentRuntimeRealHTTPHealthAndShadowCreateSmoke(t *testing.T) {
|
||||
db := setupAgentRuntimeHTTPSmokeDB(t)
|
||||
runtimeCreateCalled := false
|
||||
runtime := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
switch {
|
||||
case r.Method == http.MethodGet && r.URL.Path == "/api/agnet/health":
|
||||
case r.Method == http.MethodGet && r.URL.Path == "/api/agent/health":
|
||||
require.Equal(t, "Bearer runtime-service-token", r.Header.Get("Authorization"))
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
_, _ = w.Write([]byte(`{"success":true,"data":{"status":"healthy"}}`))
|
||||
case r.Method == http.MethodPost && r.URL.Path == "/api/agnet/deployments":
|
||||
case r.Method == http.MethodPost && r.URL.Path == "/api/agent/sub-agile/deployments":
|
||||
runtimeCreateCalled = true
|
||||
require.Equal(t, "Bearer runtime-service-token", r.Header.Get("Authorization"))
|
||||
require.Equal(t, "corr-http-smoke", r.Header.Get("X-Correlation-ID"))
|
||||
@@ -142,23 +142,23 @@ func TestAgnetRuntimeRealHTTPHealthAndShadowCreateSmoke(t *testing.T) {
|
||||
}))
|
||||
defer runtime.Close()
|
||||
|
||||
t.Setenv("AGNET_RUNTIME_ENABLED", "true")
|
||||
t.Setenv("AGNET_RUNTIME_ASYNC", "false")
|
||||
t.Setenv("AGNET_RUNTIME_BASE_URL", runtime.URL)
|
||||
t.Setenv("AGNET_RUNTIME_SERVICE_TOKEN", "runtime-service-token")
|
||||
t.Setenv("AGNET_RUNTIME_CALLBACK_SIGNING_SECRET_REF", "azkv://heicode-kv.vault.azure.net/secrets/callback-signing")
|
||||
t.Setenv("AGENT_RUNTIME_ENABLED", "true")
|
||||
t.Setenv("AGENT_RUNTIME_ASYNC", "false")
|
||||
t.Setenv("AGENT_RUNTIME_BASE_URL", runtime.URL)
|
||||
t.Setenv("AGENT_RUNTIME_SERVICE_TOKEN", "runtime-service-token")
|
||||
t.Setenv("AGENT_RUNTIME_CALLBACK_SIGNING_SECRET_REF", "azkv://heicode-kv.vault.azure.net/secrets/callback-signing")
|
||||
|
||||
managerURL := startAgnetRuntimeManagerSmokeServer(t)
|
||||
managerURL := startAgentRuntimeManagerSmokeServer(t)
|
||||
|
||||
healthResp := agnetRuntimeAdminRequest(t, http.MethodGet, managerURL+"/api/agnet/runtime/health", "")
|
||||
healthBody := readAgnetRuntimeSmokeBody(t, healthResp)
|
||||
healthResp := agentRuntimeAdminRequest(t, http.MethodGet, managerURL+"/api/agent/runtime/health", "")
|
||||
healthBody := readAgentRuntimeSmokeBody(t, healthResp)
|
||||
require.Equal(t, http.StatusOK, healthResp.StatusCode)
|
||||
require.Contains(t, healthBody, `"success":true`)
|
||||
require.Contains(t, healthBody, `"status":"healthy"`)
|
||||
|
||||
schemaResp, err := http.Get(managerURL + "/api/agnet/callbacks/swarm-events/schema")
|
||||
schemaResp, err := http.Get(managerURL + "/api/agent/callbacks/runtime-events/schema")
|
||||
require.NoError(t, err)
|
||||
schemaBody := readAgnetRuntimeSmokeBody(t, schemaResp)
|
||||
schemaBody := readAgentRuntimeSmokeBody(t, schemaResp)
|
||||
require.Equal(t, http.StatusOK, schemaResp.StatusCode)
|
||||
require.Contains(t, schemaBody, `"event_type":"task.claimed"`)
|
||||
require.Contains(t, schemaBody, `"event_type":"approval.requested"`)
|
||||
@@ -179,44 +179,44 @@ func TestAgnetRuntimeRealHTTPHealthAndShadowCreateSmoke(t *testing.T) {
|
||||
"user_context":{"user_id":"101","channel_id":"default"},
|
||||
"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}]},
|
||||
"agent_runtime":{"platform":"agent","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-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"}
|
||||
}
|
||||
}`
|
||||
createResp := agnetRuntimeAdminRequest(t, http.MethodPost, managerURL+"/api/agnet/deployments", createBody)
|
||||
responseBody := readAgnetRuntimeSmokeBody(t, createResp)
|
||||
createResp := agentRuntimeAdminRequest(t, http.MethodPost, managerURL+"/api/agent/deployments", createBody)
|
||||
responseBody := readAgentRuntimeSmokeBody(t, createResp)
|
||||
require.Equal(t, http.StatusOK, createResp.StatusCode)
|
||||
require.Contains(t, responseBody, `"success":true`)
|
||||
require.Contains(t, responseBody, `"runtime_deployment_id":"runtime-http-dep"`)
|
||||
require.Contains(t, responseBody, `"runtime_swarm_id":"runtime-http-swarm"`)
|
||||
require.True(t, runtimeCreateCalled)
|
||||
|
||||
var stored model.AgnetDeployment
|
||||
var stored model.AgentDeployment
|
||||
require.NoError(t, db.Where("runtime_swarm_id = ?", "runtime-http-swarm").First(&stored).Error)
|
||||
require.Equal(t, "accepted", stored.RuntimeState)
|
||||
|
||||
logsResp := agnetRuntimeAdminRequest(t, http.MethodGet, managerURL+"/api/agnet/deployments/"+stored.DeploymentID+"/logs", "")
|
||||
logsBody := readAgnetRuntimeSmokeBody(t, logsResp)
|
||||
logsResp := agentRuntimeAdminRequest(t, http.MethodGet, managerURL+"/api/agent/deployments/"+stored.DeploymentID+"/logs", "")
|
||||
logsBody := readAgentRuntimeSmokeBody(t, logsResp)
|
||||
require.Equal(t, http.StatusOK, logsResp.StatusCode)
|
||||
require.Contains(t, logsBody, `"data_source":"manager_control_plane"`)
|
||||
require.Contains(t, logsBody, `"runtime_source":"not_connected"`)
|
||||
|
||||
metricsResp := agnetRuntimeAdminRequest(t, http.MethodGet, managerURL+"/api/agnet/deployments/"+stored.DeploymentID+"/metrics", "")
|
||||
metricsBody := readAgnetRuntimeSmokeBody(t, metricsResp)
|
||||
metricsResp := agentRuntimeAdminRequest(t, http.MethodGet, managerURL+"/api/agent/deployments/"+stored.DeploymentID+"/metrics", "")
|
||||
metricsBody := readAgentRuntimeSmokeBody(t, metricsResp)
|
||||
require.Equal(t, http.StatusOK, metricsResp.StatusCode)
|
||||
require.Contains(t, metricsBody, `"data_source":"manager_control_plane"`)
|
||||
require.Contains(t, metricsBody, `"platform_estimated":true`)
|
||||
}
|
||||
|
||||
func TestSwarmRuntimeHTTPCreateUsesSwarmConfigAndPayload(t *testing.T) {
|
||||
db := setupAgnetRuntimeHTTPSmokeDB(t)
|
||||
db := setupAgentRuntimeHTTPSmokeDB(t)
|
||||
swarmCreateCalled := false
|
||||
swarmStopCalled := false
|
||||
swarm := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
switch {
|
||||
case r.Method == http.MethodGet && r.URL.Path == "/api/agnet/health":
|
||||
case r.Method == http.MethodGet && r.URL.Path == "/api/agent/health":
|
||||
require.Equal(t, "Bearer swarm-runtime-token", r.Header.Get("Authorization"))
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
_, _ = w.Write([]byte(`{"success":true,"data":{"status":"healthy","service":"heicode-swarm-runtime"}}`))
|
||||
@@ -253,16 +253,16 @@ func TestSwarmRuntimeHTTPCreateUsesSwarmConfigAndPayload(t *testing.T) {
|
||||
}))
|
||||
defer swarm.Close()
|
||||
|
||||
t.Setenv("AGNET_RUNTIME_ENABLED", "false")
|
||||
t.Setenv("AGENT_RUNTIME_ENABLED", "false")
|
||||
t.Setenv("SWARM_RUNTIME_ENABLED", "true")
|
||||
t.Setenv("SWARM_RUNTIME_ASYNC", "false")
|
||||
t.Setenv("SWARM_RUNTIME_BASE_URL", swarm.URL)
|
||||
t.Setenv("SWARM_RUNTIME_SERVICE_TOKEN", "swarm-runtime-token")
|
||||
|
||||
managerURL := startAgnetRuntimeManagerSmokeServer(t)
|
||||
managerURL := startAgentRuntimeManagerSmokeServer(t)
|
||||
|
||||
healthResp := agnetRuntimeAdminRequest(t, http.MethodGet, managerURL+"/api/agnet/runtime/health?mode=swarm", "")
|
||||
healthBody := readAgnetRuntimeSmokeBody(t, healthResp)
|
||||
healthResp := agentRuntimeAdminRequest(t, http.MethodGet, managerURL+"/api/agent/runtime/health?mode=swarm", "")
|
||||
healthBody := readAgentRuntimeSmokeBody(t, healthResp)
|
||||
require.Equal(t, http.StatusOK, healthResp.StatusCode)
|
||||
require.Contains(t, healthBody, `"mode":"swarm"`)
|
||||
require.Contains(t, healthBody, `"status":"healthy"`)
|
||||
@@ -279,7 +279,7 @@ func TestSwarmRuntimeHTTPCreateUsesSwarmConfigAndPayload(t *testing.T) {
|
||||
"user_context":{"user_id":"101","channel_id":"default"},
|
||||
"billing_context":{"provider":"newapi","newapi_user_ref":"newapi-swarm-smoke"},
|
||||
"agile_context":{"iteration":"2026-05-29","stage":"testing","checkpoint":"runtime_accepted","acceptance_criteria":["Runtime creates a swarm run"],"next_action":"submit_test_result","requires_user_approval":false},
|
||||
"agent_runtime":{"platform":"agnet","agents":[{"role":"planner","model_ref":"model-swarm","instance_count":1},{"role":"builder","model_ref":"model-swarm","instance_count":1},{"role":"reviewer","model_ref":"model-swarm","instance_count":1}]},
|
||||
"agent_runtime":{"platform":"agent","agents":[{"role":"planner","model_ref":"model-swarm","instance_count":1},{"role":"builder","model_ref":"model-swarm","instance_count":1},{"role":"reviewer","model_ref":"model-swarm","instance_count":1}]},
|
||||
"agents":[
|
||||
{"role_template":"planner","goal":"plan the swarm task","default_model_id":"model-swarm","resource_grants":[{"grant_id":"grant-swarm-plan","resource_id":"doc-swarm","resource_type":"project_doc","user_id":"101","binding_scope":"task-swarm-smoke","target_role":"planner","target_agent_ref":"agent-planner-1","permission_scope":["doc:read"],"metadata":{"resource_ref":"task-swarm-smoke"},"status":"active"}]},
|
||||
{"role_template":"builder","goal":"build the swarm output","default_model_id":"model-swarm","resource_grants":[{"grant_id":"grant-swarm-build","resource_id":"git-swarm","resource_type":"git","user_id":"101","binding_scope":"task-swarm-smoke","target_role":"builder","target_agent_ref":"agent-builder-1","permission_scope":["repo:read"],"metadata":{"repo_url":"https://example.invalid/heicode/swarm.git"},"secret_ref":"azkv://heicode-kv.vault.azure.net/secrets/swarm-git","status":"active"}]},
|
||||
@@ -289,21 +289,21 @@ func TestSwarmRuntimeHTTPCreateUsesSwarmConfigAndPayload(t *testing.T) {
|
||||
"metadata":{"correlation_id":"corr-swarm-smoke"}
|
||||
}
|
||||
}`
|
||||
createResp := agnetRuntimeAdminRequest(t, http.MethodPost, managerURL+"/api/swarms", createBody)
|
||||
responseBody := readAgnetRuntimeSmokeBody(t, createResp)
|
||||
createResp := agentRuntimeAdminRequest(t, http.MethodPost, managerURL+"/api/swarms", createBody)
|
||||
responseBody := readAgentRuntimeSmokeBody(t, createResp)
|
||||
require.Equal(t, http.StatusOK, createResp.StatusCode)
|
||||
require.Contains(t, responseBody, `"success":true`)
|
||||
require.Contains(t, responseBody, `"runtime_deployment_id":"swarm-runtime-dep"`)
|
||||
require.Contains(t, responseBody, `"runtime_swarm_id":"swarm-runtime-id"`)
|
||||
require.True(t, swarmCreateCalled)
|
||||
|
||||
var stored model.AgnetDeployment
|
||||
var stored model.AgentDeployment
|
||||
require.NoError(t, db.Where("runtime_swarm_id = ?", "swarm-runtime-id").First(&stored).Error)
|
||||
require.Equal(t, "agile", stored.SubMode)
|
||||
require.Contains(t, stored.PlanJSON, `"runtime_mode":"swarm"`)
|
||||
|
||||
stopResp := agnetRuntimeAdminRequest(t, http.MethodPost, managerURL+"/api/agnet/user/deployments/"+stored.DeploymentID+"/stop", `{"reason":"smoke done"}`)
|
||||
stopBody := readAgnetRuntimeSmokeBody(t, stopResp)
|
||||
stopResp := agentRuntimeAdminRequest(t, http.MethodPost, managerURL+"/api/agent/user/deployments/"+stored.DeploymentID+"/stop", `{"reason":"smoke done"}`)
|
||||
stopBody := readAgentRuntimeSmokeBody(t, stopResp)
|
||||
require.Equal(t, http.StatusOK, stopResp.StatusCode)
|
||||
require.Contains(t, stopBody, `"success":true`)
|
||||
require.Contains(t, stopBody, `"runtime_state":"stopped"`)
|
||||
@@ -59,9 +59,11 @@ func SetApiRouter(router *gin.Engine) {
|
||||
apiRouter.POST("/stripe/webhook", controller.StripeWebhook)
|
||||
apiRouter.POST("/creem/webhook", controller.CreemWebhook)
|
||||
apiRouter.POST("/waffo/webhook", controller.WaffoWebhook)
|
||||
apiRouter.GET("/agnet/callbacks/swarm-events/schema", controller.AgnetGetSwarmEventCallbackSchema)
|
||||
apiRouter.POST("/agnet/callbacks/swarm-events", controller.AgnetReceiveSwarmEventCallback)
|
||||
apiRouter.POST("/swarms", middleware.UserOrV2DeviceAuth(), controller.AgnetCreateUserSwarm)
|
||||
apiRouter.GET("/agent/callbacks/runtime-events/schema", controller.AgentGetRuntimeEventCallbackSchema)
|
||||
apiRouter.POST("/agent/callbacks/runtime-events", controller.AgentReceiveRuntimeEventCallback)
|
||||
// Client-facing capability discovery (unified spec §6). Catalog data only.
|
||||
apiRouter.GET("/heicode/capabilities", controller.HeicodeCapabilities)
|
||||
apiRouter.POST("/swarms", middleware.UserOrV2DeviceAuth(), controller.AgentCreateUserSwarm)
|
||||
//apiRouter.POST("/waffo-pancake/webhook", controller.WaffoPancakeWebhook)
|
||||
|
||||
// Universal secure verification routes
|
||||
@@ -81,7 +83,7 @@ func SetApiRouter(router *gin.Engine) {
|
||||
userRoute := apiRouter.Group("/user")
|
||||
{
|
||||
userRoute.POST("/register", middleware.CriticalRateLimit(), middleware.TurnstileCheck(), controller.Register)
|
||||
userRoute.POST("/session/from-agnet", middleware.CriticalRateLimit(), controller.HeicodeAgnetSessionLogin)
|
||||
userRoute.POST("/session/from-agent", middleware.CriticalRateLimit(), controller.HeicodeAgentSessionLogin)
|
||||
userRoute.POST("/login", middleware.CriticalRateLimit(), middleware.TurnstileCheck(), controller.Login)
|
||||
userRoute.POST("/login/2fa", middleware.CriticalRateLimit(), controller.Verify2FALogin)
|
||||
userRoute.POST("/passkey/login/begin", middleware.CriticalRateLimit(), controller.PasskeyLoginBegin)
|
||||
@@ -491,50 +493,80 @@ func SetApiRouter(router *gin.Engine) {
|
||||
deploymentsRoute.DELETE("/:id", controller.DeleteDeployment)
|
||||
}
|
||||
|
||||
// Agnet user approval gates and short-lived credential leases.
|
||||
agnetApprovalRoute := apiRouter.Group("/agnet")
|
||||
agnetApprovalRoute.Use(middleware.UserOrV2DeviceAuth())
|
||||
// Agent user approval gates and short-lived credential leases.
|
||||
agentApprovalRoute := apiRouter.Group("/agent")
|
||||
agentApprovalRoute.Use(middleware.UserOrV2DeviceAuth())
|
||||
{
|
||||
agnetApprovalRoute.GET("/approvals", controller.ListAgnetApprovalRequests)
|
||||
agnetApprovalRoute.POST("/approvals", controller.CreateAgnetApprovalRequest)
|
||||
agnetApprovalRoute.GET("/approvals/:approval_id", controller.GetAgnetApprovalRequest)
|
||||
agnetApprovalRoute.POST("/approvals/:approval_id/approve", controller.ApproveAgnetApprovalRequest)
|
||||
agnetApprovalRoute.POST("/approvals/:approval_id/reject", controller.RejectAgnetApprovalRequest)
|
||||
agnetApprovalRoute.GET("/credential-leases", controller.ListAgnetCredentialLeases)
|
||||
agnetApprovalRoute.POST("/credential-leases/:lease_id/revoke", controller.RevokeAgnetCredentialLease)
|
||||
agnetApprovalRoute.GET("/role-templates", controller.AgnetListRoleTemplates)
|
||||
agnetApprovalRoute.GET("/user/deployments", controller.AgnetListUserDeployments)
|
||||
agnetApprovalRoute.POST("/user/deployments", controller.AgnetCreateUserDeployment)
|
||||
agnetApprovalRoute.GET("/user/deployments/:deployment_id", controller.AgnetGetUserDeployment)
|
||||
agnetApprovalRoute.POST("/user/deployments/:deployment_id/stop", controller.AgnetStopUserDeployment)
|
||||
agnetApprovalRoute.GET("/user/deployments/:deployment_id/logs", controller.AgnetListUserDeploymentLogs)
|
||||
agnetApprovalRoute.GET("/user/deployments/:deployment_id/metrics", controller.AgnetGetUserDeploymentMetrics)
|
||||
agnetApprovalRoute.GET("/user/deployments/:deployment_id/runtime-diagnostics", controller.AgnetGetUserDeploymentRuntimeDiagnostics)
|
||||
agnetApprovalRoute.GET("/user/deployments/:deployment_id/events", controller.AgnetListUserDeploymentEvents)
|
||||
agnetApprovalRoute.POST("/user/deployments/:deployment_id/simulate-events", controller.AgnetSimulateUserDeploymentEvents)
|
||||
agnetApprovalRoute.GET("/user/deployments/:deployment_id/artifacts", controller.AgnetListUserDeploymentArtifacts)
|
||||
agnetApprovalRoute.GET("/user/deployments/:deployment_id/artifacts/:artifact_id/content", controller.AgnetGetUserDeploymentArtifactContent)
|
||||
agnetApprovalRoute.GET("/user/deployments/:deployment_id/sk-snapshots", controller.AgnetListUserSKSnapshots)
|
||||
agnetApprovalRoute.GET("/user/deployments/:deployment_id/timeline", controller.AgnetGetUserDeploymentTimeline)
|
||||
agnetApprovalRoute.POST("/user/tasks/:task_id/deployment-draft", controller.AgnetCreateTaskDeploymentDraft)
|
||||
agentApprovalRoute.GET("/approvals", controller.ListAgentApprovalRequests)
|
||||
agentApprovalRoute.POST("/approvals", controller.CreateAgentApprovalRequest)
|
||||
agentApprovalRoute.GET("/approvals/:approval_id", controller.GetAgentApprovalRequest)
|
||||
agentApprovalRoute.POST("/approvals/:approval_id/approve", controller.ApproveAgentApprovalRequest)
|
||||
agentApprovalRoute.POST("/approvals/:approval_id/reject", controller.RejectAgentApprovalRequest)
|
||||
agentApprovalRoute.GET("/credential-leases", controller.ListAgentCredentialLeases)
|
||||
agentApprovalRoute.POST("/credential-leases/:lease_id/revoke", controller.RevokeAgentCredentialLease)
|
||||
agentApprovalRoute.GET("/role-templates", controller.AgentListRoleTemplates)
|
||||
agentApprovalRoute.GET("/user/deployments", controller.AgentListUserDeployments)
|
||||
agentApprovalRoute.POST("/user/deployments", controller.AgentCreateUserDeployment)
|
||||
agentApprovalRoute.GET("/user/deployments/:deployment_id", controller.AgentGetUserDeployment)
|
||||
agentApprovalRoute.POST("/user/deployments/:deployment_id/stop", controller.AgentStopUserDeployment)
|
||||
agentApprovalRoute.GET("/user/deployments/:deployment_id/logs", controller.AgentListUserDeploymentLogs)
|
||||
agentApprovalRoute.GET("/user/deployments/:deployment_id/metrics", controller.AgentGetUserDeploymentMetrics)
|
||||
agentApprovalRoute.GET("/user/deployments/:deployment_id/runtime-diagnostics", controller.AgentGetUserDeploymentRuntimeDiagnostics)
|
||||
agentApprovalRoute.GET("/user/deployments/:deployment_id/events", controller.AgentListUserDeploymentEvents)
|
||||
agentApprovalRoute.POST("/user/deployments/:deployment_id/simulate-events", controller.AgentSimulateUserDeploymentEvents)
|
||||
agentApprovalRoute.GET("/user/deployments/:deployment_id/artifacts", controller.AgentListUserDeploymentArtifacts)
|
||||
agentApprovalRoute.GET("/user/deployments/:deployment_id/artifacts/:artifact_id/content", controller.AgentGetUserDeploymentArtifactContent)
|
||||
agentApprovalRoute.GET("/user/deployments/:deployment_id/sk-snapshots", controller.AgentListUserSKSnapshots)
|
||||
agentApprovalRoute.GET("/user/deployments/:deployment_id/timeline", controller.AgentGetUserDeploymentTimeline)
|
||||
agentApprovalRoute.POST("/user/tasks/:task_id/deployment-draft", controller.AgentCreateTaskDeploymentDraft)
|
||||
}
|
||||
|
||||
// Agnet orchestration control plane (minimal integration endpoints)
|
||||
agnetRoute := apiRouter.Group("/agnet")
|
||||
agnetRoute.Use(middleware.AdminAuth())
|
||||
// Client-facing unified task routes (unified spec §5.1). task_id == the
|
||||
// deployment_id, so these reuse the deployment control-plane handlers.
|
||||
// Sub Agile -> agent_management; Swarm -> HeiCode-Swarm (mode is carried
|
||||
// by the deployment record created at POST /tasks).
|
||||
registerHeicodeTaskRoutes := func(group *gin.RouterGroup, createHandler gin.HandlerFunc) {
|
||||
group.GET("/tasks", controller.AgentListUserDeployments)
|
||||
group.POST("/tasks", createHandler)
|
||||
group.GET("/tasks/:deployment_id", controller.AgentGetUserDeployment)
|
||||
group.POST("/tasks/:deployment_id/stop", controller.AgentStopUserDeployment)
|
||||
group.GET("/tasks/:deployment_id/timeline", controller.AgentGetUserDeploymentTimeline)
|
||||
group.GET("/tasks/:deployment_id/workflow", controller.HeicodeTaskWorkflow)
|
||||
group.GET("/tasks/:deployment_id/logs", controller.AgentListUserDeploymentLogs)
|
||||
group.GET("/tasks/:deployment_id/events", controller.AgentListUserDeploymentEvents)
|
||||
group.GET("/tasks/:deployment_id/metrics", controller.AgentGetUserDeploymentMetrics)
|
||||
group.GET("/tasks/:deployment_id/diagnostics", controller.AgentGetUserDeploymentRuntimeDiagnostics)
|
||||
group.GET("/tasks/:deployment_id/artifacts", controller.AgentListUserDeploymentArtifacts)
|
||||
group.GET("/tasks/:deployment_id/artifacts/:artifact_id/content", controller.AgentGetUserDeploymentArtifactContent)
|
||||
group.GET("/tasks/:deployment_id/sk-snapshots", controller.AgentListUserSKSnapshots)
|
||||
group.POST("/tasks/:deployment_id/approvals/:approval_id/approve", controller.ApproveAgentApprovalRequest)
|
||||
group.POST("/tasks/:deployment_id/approvals/:approval_id/reject", controller.RejectAgentApprovalRequest)
|
||||
}
|
||||
|
||||
heicodeSubAgileRoute := apiRouter.Group("/heicode/sub-agile")
|
||||
heicodeSubAgileRoute.Use(middleware.UserOrV2DeviceAuth())
|
||||
registerHeicodeTaskRoutes(heicodeSubAgileRoute, controller.AgentCreateUserDeployment)
|
||||
|
||||
heicodeSwarmRoute := apiRouter.Group("/heicode/swarm")
|
||||
heicodeSwarmRoute.Use(middleware.UserOrV2DeviceAuth())
|
||||
registerHeicodeTaskRoutes(heicodeSwarmRoute, controller.AgentCreateUserSwarm)
|
||||
|
||||
// Agent orchestration control plane (minimal integration endpoints)
|
||||
agentRoute := apiRouter.Group("/agent")
|
||||
agentRoute.Use(middleware.AdminAuth())
|
||||
{
|
||||
agnetRoute.GET("/deployments", controller.AgnetListDeployments)
|
||||
agnetRoute.POST("/deployments", controller.AgnetCreateDeployment)
|
||||
agnetRoute.GET("/deployments/:deployment_id", controller.AgnetGetDeployment)
|
||||
agnetRoute.POST("/deployments/:deployment_id/stop", controller.AgnetStopDeployment)
|
||||
agnetRoute.GET("/deployments/:deployment_id/logs", controller.AgnetListDeploymentLogs)
|
||||
agnetRoute.GET("/deployments/:deployment_id/metrics", controller.AgnetGetDeploymentMetrics)
|
||||
agnetRoute.GET("/deployments/:deployment_id/events", controller.AgnetListDeploymentEvents)
|
||||
agnetRoute.GET("/deployments/:deployment_id/sk-snapshots", controller.AgnetListSKSnapshots)
|
||||
agnetRoute.POST("/sk-snapshots/resolve", controller.AgnetResolveSKSnapshots)
|
||||
agnetRoute.GET("/runtime/health", controller.AgnetRuntimeHealth)
|
||||
agnetRoute.GET("/projects/:project_id/dashboard-snapshot", controller.AgnetProjectDashboardSnapshot)
|
||||
agnetRoute.GET("/audit-logs", controller.AgnetListAuditLogs)
|
||||
agentRoute.GET("/deployments", controller.AgentListDeployments)
|
||||
agentRoute.POST("/deployments", controller.AgentCreateDeployment)
|
||||
agentRoute.GET("/deployments/:deployment_id", controller.AgentGetDeployment)
|
||||
agentRoute.POST("/deployments/:deployment_id/stop", controller.AgentStopDeployment)
|
||||
agentRoute.GET("/deployments/:deployment_id/logs", controller.AgentListDeploymentLogs)
|
||||
agentRoute.GET("/deployments/:deployment_id/metrics", controller.AgentGetDeploymentMetrics)
|
||||
agentRoute.GET("/deployments/:deployment_id/events", controller.AgentListDeploymentEvents)
|
||||
agentRoute.GET("/deployments/:deployment_id/sk-snapshots", controller.AgentListSKSnapshots)
|
||||
agentRoute.POST("/sk-snapshots/resolve", controller.AgentResolveSKSnapshots)
|
||||
agentRoute.GET("/runtime/health", controller.AgentRuntimeHealth)
|
||||
agentRoute.GET("/projects/:project_id/dashboard-snapshot", controller.AgentProjectDashboardSnapshot)
|
||||
agentRoute.GET("/audit-logs", controller.AgentListAuditLogs)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Vendored
+2
-2
@@ -13,14 +13,14 @@
|
||||
<meta name="title" content="Heicode Manager" />
|
||||
<meta
|
||||
name="description"
|
||||
content="Heicode Manager — multi-tenant control plane for Agnet deployments, events and audit."
|
||||
content="Heicode Manager — multi-tenant control plane for Agent deployments, events and audit."
|
||||
/>
|
||||
<meta property="og:title" content="Heicode Manager" />
|
||||
<meta property="og:image" content="/logo.png?v=h-glass-2" />
|
||||
<meta property="og:type" content="website" />
|
||||
|
||||
<meta name="theme-color" content="#7B6BE3" />
|
||||
<link rel="icon" href="/favicon.ico"><script defer src="/static/js/vendor-radix.8fa3e0a349.js"></script><script defer src="/static/js/vendor-tanstack.632dbe8908.js"></script><script defer src="/static/js/lib-react.5c8909c28c.js"></script><script defer src="/static/js/9238.45c9c35ccf.js"></script><script defer src="/static/js/index.e57d82e6db.js"></script><link href="/static/css/index.fd51d44fe8.css" rel="stylesheet"></head>
|
||||
<link rel="icon" href="/favicon.ico"><script defer src="/static/js/vendor-radix.829c7e3fad.js"></script><script defer src="/static/js/vendor-tanstack.632dbe8908.js"></script><script defer src="/static/js/lib-react.5c8909c28c.js"></script><script defer src="/static/js/6374.bc21d8b214.js"></script><script defer src="/static/js/index.99cba94710.js"></script><link href="/static/css/index.cc291c3921.css" rel="stylesheet"></head>
|
||||
|
||||
<body>
|
||||
<div id="root"></div>
|
||||
|
||||
Vendored
+1
-1
@@ -13,7 +13,7 @@
|
||||
<meta name="title" content="Heicode Manager" />
|
||||
<meta
|
||||
name="description"
|
||||
content="Heicode Manager — multi-tenant control plane for Agnet deployments, events and audit."
|
||||
content="Heicode Manager — multi-tenant control plane for Agent deployments, events and audit."
|
||||
/>
|
||||
<meta property="og:title" content="Heicode Manager" />
|
||||
<meta property="og:image" content="/logo.png?v=h-glass-2" />
|
||||
|
||||
+1
-1
@@ -105,7 +105,7 @@ export function QueryState(props: QueryStateProps) {
|
||||
{loadingFallback ?? (
|
||||
// Sensible default — three skeleton rows mimicking a card
|
||||
// list. Callers pass `loadingFallback` to match their own
|
||||
// grid (e.g. AgnetDeploymentsPage uses 2-col, audit uses
|
||||
// grid (e.g. AgentDeploymentsPage uses 2-col, audit uses
|
||||
// a vertical timeline).
|
||||
<div className='space-y-3'>
|
||||
<Skeleton className='h-24 w-full rounded-2xl' />
|
||||
|
||||
+106
-106
@@ -1,23 +1,23 @@
|
||||
import { api } from '@/lib/api'
|
||||
|
||||
export type AgnetSubMode = 'agile' | 'waterfall'
|
||||
export type AgentSubMode = 'agile' | 'waterfall'
|
||||
|
||||
/** Sub-agent cloud/runtime binding (passed to Agnet on deploy). */
|
||||
export type AgnetRuntimeExecution = {
|
||||
/** Sub-agent cloud/runtime binding (passed to Agent on deploy). */
|
||||
export type AgentRuntimeExecution = {
|
||||
profile_id?: string
|
||||
cloud_principal_refs?: string[]
|
||||
network_policy_ref?: string
|
||||
}
|
||||
|
||||
/** SK allow/deny policy attached to the agent in the deployment plan. */
|
||||
export type AgnetSKAccessPolicy = {
|
||||
export type AgentSKAccessPolicy = {
|
||||
policy_ref?: string
|
||||
deny_skill_ids?: string[]
|
||||
inherit_deployment_defaults?: boolean
|
||||
}
|
||||
|
||||
/** Git-backed SK source (`type: git`). */
|
||||
export type AgnetRepoRef = {
|
||||
export type AgentRepoRef = {
|
||||
connection_id?: string
|
||||
repo_url?: string
|
||||
ref: string
|
||||
@@ -25,20 +25,20 @@ export type AgnetRepoRef = {
|
||||
}
|
||||
|
||||
/** Single SK source entry (git or upload). */
|
||||
export type AgnetSKSource = {
|
||||
export type AgentSKSource = {
|
||||
type?: string
|
||||
artifact_id?: string
|
||||
mime?: string
|
||||
repo_ref?: AgnetRepoRef
|
||||
repo_ref?: AgentRepoRef
|
||||
}
|
||||
|
||||
export type AgnetAgentPlan = {
|
||||
export type AgentAgentPlan = {
|
||||
role_template: string
|
||||
goal: string
|
||||
default_model_id?: string
|
||||
sk_sources?: AgnetSKSource[]
|
||||
runtime_execution?: AgnetRuntimeExecution
|
||||
sk_access_policy?: AgnetSKAccessPolicy
|
||||
sk_sources?: AgentSKSource[]
|
||||
runtime_execution?: AgentRuntimeExecution
|
||||
sk_access_policy?: AgentSKAccessPolicy
|
||||
resource_grants?: Array<{
|
||||
grant_id?: string
|
||||
resource_id?: string
|
||||
@@ -56,13 +56,13 @@ export type AgnetAgentPlan = {
|
||||
}>
|
||||
}
|
||||
|
||||
export type AgnetBudget = {
|
||||
export type AgentBudget = {
|
||||
max_tokens: number
|
||||
max_cost_usd: number
|
||||
max_duration_sec: number
|
||||
}
|
||||
|
||||
export type AgnetUserContext = {
|
||||
export type AgentUserContext = {
|
||||
user_id: string
|
||||
email?: string
|
||||
role?: string
|
||||
@@ -70,15 +70,15 @@ export type AgnetUserContext = {
|
||||
subscription_tier?: string
|
||||
}
|
||||
|
||||
export type AgnetBillingContext = {
|
||||
export type AgentBillingContext = {
|
||||
provider?: 'newapi'
|
||||
newapi_user_ref?: string
|
||||
newapi_group?: string
|
||||
quota_ref?: string
|
||||
}
|
||||
|
||||
export type AgnetAgentRuntime = {
|
||||
platform?: 'agnet'
|
||||
export type AgentAgentRuntime = {
|
||||
platform?: 'agent'
|
||||
agents?: Array<{
|
||||
role: string
|
||||
model_ref: string
|
||||
@@ -86,57 +86,57 @@ export type AgnetAgentRuntime = {
|
||||
}>
|
||||
}
|
||||
|
||||
export type AgnetConstraints = {
|
||||
/** Runtime model allow-list for Agnet deployments; not a NewAPI billing map. */
|
||||
export type AgentConstraints = {
|
||||
/** Runtime model allow-list for Agent deployments; not a NewAPI billing map. */
|
||||
allowed_model_ids?: string[]
|
||||
}
|
||||
|
||||
export type AgnetOrchestrationMetadata = {
|
||||
export type AgentOrchestrationMetadata = {
|
||||
/**
|
||||
* Compatibility field for Agnet routing scope; UI treats this as user scope,
|
||||
* Compatibility field for Agent routing scope; UI treats this as user scope,
|
||||
* not billing tenant.
|
||||
*/
|
||||
tenant_id: string
|
||||
/**
|
||||
* Compatibility field for Agnet routing scope; UI treats this as resource
|
||||
* Compatibility field for Agent routing scope; UI treats this as resource
|
||||
* scope, not project control.
|
||||
*/
|
||||
project_id: string
|
||||
correlation_id: string
|
||||
}
|
||||
|
||||
export type AgnetOrchestrationPlan = {
|
||||
export type AgentOrchestrationPlan = {
|
||||
intent_id: string
|
||||
template_hint: string
|
||||
objective: string
|
||||
sub_mode?: AgnetSubMode
|
||||
sub_mode?: AgentSubMode
|
||||
risk_level: 'low' | 'medium' | 'high'
|
||||
budget: AgnetBudget
|
||||
user_context: AgnetUserContext
|
||||
billing_context?: AgnetBillingContext
|
||||
agent_runtime?: AgnetAgentRuntime
|
||||
agents: AgnetAgentPlan[]
|
||||
constraints: AgnetConstraints
|
||||
metadata: AgnetOrchestrationMetadata
|
||||
budget: AgentBudget
|
||||
user_context: AgentUserContext
|
||||
billing_context?: AgentBillingContext
|
||||
agent_runtime?: AgentAgentRuntime
|
||||
agents: AgentAgentPlan[]
|
||||
constraints: AgentConstraints
|
||||
metadata: AgentOrchestrationMetadata
|
||||
}
|
||||
|
||||
export type AgnetCreateDeploymentBody = {
|
||||
orchestration_plan: AgnetOrchestrationPlan
|
||||
export type AgentCreateDeploymentBody = {
|
||||
orchestration_plan: AgentOrchestrationPlan
|
||||
}
|
||||
|
||||
export type AgnetCreateDeploymentResult = {
|
||||
export type AgentCreateDeploymentResult = {
|
||||
deployment_id: string
|
||||
sub_mode?: AgnetSubMode
|
||||
sub_mode?: AgentSubMode
|
||||
status: string
|
||||
agent_instances?: Array<{
|
||||
instance_id?: string
|
||||
role?: string
|
||||
phase?: string
|
||||
}>
|
||||
permission_manifest?: AgnetPermissionManifest
|
||||
permission_manifest?: AgentPermissionManifest
|
||||
}
|
||||
|
||||
export type AgnetPermissionManifest = {
|
||||
export type AgentPermissionManifest = {
|
||||
user_id?: string
|
||||
binding_scope?: string
|
||||
agent_role?: string
|
||||
@@ -153,9 +153,9 @@ export type AgnetPermissionManifest = {
|
||||
}>
|
||||
}
|
||||
|
||||
export type AgnetDeployment = {
|
||||
export type AgentDeployment = {
|
||||
deployment_id: string
|
||||
sub_mode?: AgnetSubMode
|
||||
sub_mode?: AgentSubMode
|
||||
status: string
|
||||
phase: string
|
||||
runtime_state?: string
|
||||
@@ -165,24 +165,24 @@ export type AgnetDeployment = {
|
||||
failure_reason?: string
|
||||
created_at: string
|
||||
updated_at: string
|
||||
permission_manifest?: AgnetPermissionManifest
|
||||
permission_manifest?: AgentPermissionManifest
|
||||
orchestration_plan: {
|
||||
intent_id?: string
|
||||
template_hint?: string
|
||||
objective?: string
|
||||
sub_mode?: AgnetSubMode
|
||||
sub_mode?: AgentSubMode
|
||||
risk_level?: string
|
||||
budget?: AgnetBudget
|
||||
agents?: AgnetAgentPlan[]
|
||||
constraints?: AgnetConstraints
|
||||
metadata?: AgnetOrchestrationMetadata
|
||||
budget?: AgentBudget
|
||||
agents?: AgentAgentPlan[]
|
||||
constraints?: AgentConstraints
|
||||
metadata?: AgentOrchestrationMetadata
|
||||
}
|
||||
}
|
||||
|
||||
export type AgnetRuntimeDiagnostics = {
|
||||
export type AgentRuntimeDiagnostics = {
|
||||
deployment_id: string
|
||||
runtime_mode?: 'agnet' | 'swarm' | string
|
||||
sub_mode?: AgnetSubMode
|
||||
runtime_mode?: 'agent' | 'swarm' | string
|
||||
sub_mode?: AgentSubMode
|
||||
runtime_deployment_id?: string
|
||||
runtime_swarm_id?: string
|
||||
data_source?: string
|
||||
@@ -198,7 +198,7 @@ export type AgnetRuntimeDiagnostics = {
|
||||
checked_at?: string
|
||||
}
|
||||
|
||||
export type AgnetApprovalRequest = {
|
||||
export type AgentApprovalRequest = {
|
||||
approval_id: string
|
||||
user_id: number
|
||||
deployment_id?: string
|
||||
@@ -221,10 +221,10 @@ export type AgnetApprovalRequest = {
|
||||
decided_at?: number
|
||||
created_at?: number
|
||||
updated_at?: number
|
||||
credential_lease?: AgnetCredentialLease
|
||||
credential_lease?: AgentCredentialLease
|
||||
}
|
||||
|
||||
export type AgnetCredentialLease = {
|
||||
export type AgentCredentialLease = {
|
||||
lease_id: string
|
||||
credential_ref: string
|
||||
approval_id: string
|
||||
@@ -273,10 +273,10 @@ export type GitSourcePayload = {
|
||||
}
|
||||
|
||||
// Platform-recommended role catalog shape. Mirrors backend
|
||||
// `AgnetRoleTemplate` in controller/agnet_role_template.go. The
|
||||
// `AgentRoleTemplate` in controller/agent_role_template.go. The
|
||||
// six canonical roles come from docs/product-package §13.3.3 —
|
||||
// keys are stable identifiers, display strings can be translated.
|
||||
export type AgnetRoleTemplate = {
|
||||
export type AgentRoleTemplate = {
|
||||
key: string
|
||||
display_name: string
|
||||
summary: string
|
||||
@@ -288,12 +288,12 @@ export type AgnetRoleTemplate = {
|
||||
// Cached at module level — the canonical six-role catalog doesn't
|
||||
// change between page loads, so we avoid an extra request every
|
||||
// time the create-deployment sheet opens.
|
||||
let _roleTemplateCache: AgnetRoleTemplate[] | null = null
|
||||
let _roleTemplateCache: AgentRoleTemplate[] | null = null
|
||||
|
||||
export async function listAgnetRoleTemplates(): Promise<AgnetRoleTemplate[]> {
|
||||
export async function listAgentRoleTemplates(): Promise<AgentRoleTemplate[]> {
|
||||
if (_roleTemplateCache) return _roleTemplateCache
|
||||
const res = await api.get<ApiEnvelope<{ items?: AgnetRoleTemplate[] }>>(
|
||||
'/api/agnet/role-templates'
|
||||
const res = await api.get<ApiEnvelope<{ items?: AgentRoleTemplate[] }>>(
|
||||
'/api/agent/role-templates'
|
||||
)
|
||||
const items = res.data?.data?.items ?? []
|
||||
if (items.length > 0) {
|
||||
@@ -302,16 +302,16 @@ export async function listAgnetRoleTemplates(): Promise<AgnetRoleTemplate[]> {
|
||||
return items
|
||||
}
|
||||
|
||||
export async function listAgnetDeployments(): Promise<AgnetDeployment[]> {
|
||||
const res = await api.get<ApiEnvelope<{ items?: AgnetDeployment[] }>>(
|
||||
'/api/agnet/user/deployments'
|
||||
export async function listAgentDeployments(): Promise<AgentDeployment[]> {
|
||||
const res = await api.get<ApiEnvelope<{ items?: AgentDeployment[] }>>(
|
||||
'/api/agent/user/deployments'
|
||||
)
|
||||
return res.data?.data?.items ?? []
|
||||
}
|
||||
|
||||
export async function listAgnetDeploymentsQuiet(): Promise<AgnetDeployment[]> {
|
||||
const res = await api.get<ApiEnvelope<{ items?: AgnetDeployment[] }>>(
|
||||
'/api/agnet/user/deployments',
|
||||
export async function listAgentDeploymentsQuiet(): Promise<AgentDeployment[]> {
|
||||
const res = await api.get<ApiEnvelope<{ items?: AgentDeployment[] }>>(
|
||||
'/api/agent/user/deployments',
|
||||
{
|
||||
skipBusinessError: true,
|
||||
skipErrorHandler: true,
|
||||
@@ -321,11 +321,11 @@ export async function listAgnetDeploymentsQuiet(): Promise<AgnetDeployment[]> {
|
||||
return res.data?.data?.items ?? []
|
||||
}
|
||||
|
||||
export async function createAgnetDeployment(
|
||||
body: AgnetCreateDeploymentBody
|
||||
): Promise<AgnetCreateDeploymentResult> {
|
||||
const res = await api.post<ApiEnvelope<AgnetCreateDeploymentResult>>(
|
||||
'/api/agnet/user/deployments',
|
||||
export async function createAgentDeployment(
|
||||
body: AgentCreateDeploymentBody
|
||||
): Promise<AgentCreateDeploymentResult> {
|
||||
const res = await api.post<ApiEnvelope<AgentCreateDeploymentResult>>(
|
||||
'/api/agent/user/deployments',
|
||||
body
|
||||
)
|
||||
const env = res.data
|
||||
@@ -339,28 +339,28 @@ export async function createAgnetDeployment(
|
||||
return data
|
||||
}
|
||||
|
||||
export async function getAgnetDeploymentEvents(deploymentId: string) {
|
||||
export async function getAgentDeploymentEvents(deploymentId: string) {
|
||||
const res = await api.get<
|
||||
ApiEnvelope<{ items?: Array<Record<string, unknown>> }>
|
||||
>(`/api/agnet/user/deployments/${deploymentId}/events`)
|
||||
>(`/api/agent/user/deployments/${deploymentId}/events`)
|
||||
return res.data?.data?.items ?? []
|
||||
}
|
||||
|
||||
export async function getAgnetDeploymentArtifacts(deploymentId: string) {
|
||||
export async function getAgentDeploymentArtifacts(deploymentId: string) {
|
||||
const res = await api.get<
|
||||
ApiEnvelope<{ items?: Array<Record<string, unknown>> }>
|
||||
>(`/api/agnet/user/deployments/${deploymentId}/artifacts`)
|
||||
>(`/api/agent/user/deployments/${deploymentId}/artifacts`)
|
||||
return res.data?.data?.items ?? []
|
||||
}
|
||||
|
||||
export async function getAgnetDeploymentSKSnapshots(deploymentId: string) {
|
||||
export async function getAgentDeploymentSKSnapshots(deploymentId: string) {
|
||||
const res = await api.get<
|
||||
ApiEnvelope<{ items?: Array<Record<string, unknown>> }>
|
||||
>(`/api/agnet/user/deployments/${deploymentId}/sk-snapshots`)
|
||||
>(`/api/agent/user/deployments/${deploymentId}/sk-snapshots`)
|
||||
return res.data?.data?.items ?? []
|
||||
}
|
||||
|
||||
export async function getAgnetDeploymentTimeline(deploymentId: string) {
|
||||
export async function getAgentDeploymentTimeline(deploymentId: string) {
|
||||
const res = await api.get<
|
||||
ApiEnvelope<{
|
||||
callbacks?: Array<Record<string, unknown>>
|
||||
@@ -368,7 +368,7 @@ export async function getAgnetDeploymentTimeline(deploymentId: string) {
|
||||
sk_snapshots?: Array<Record<string, unknown>>
|
||||
timeline?: Array<Record<string, unknown>>
|
||||
}>
|
||||
>(`/api/agnet/user/deployments/${deploymentId}/timeline`)
|
||||
>(`/api/agent/user/deployments/${deploymentId}/timeline`)
|
||||
return (
|
||||
res.data?.data ?? {
|
||||
callbacks: [],
|
||||
@@ -379,11 +379,11 @@ export async function getAgnetDeploymentTimeline(deploymentId: string) {
|
||||
)
|
||||
}
|
||||
|
||||
export async function getAgnetRuntimeDiagnostics(
|
||||
export async function getAgentRuntimeDiagnostics(
|
||||
deploymentId: string
|
||||
): Promise<AgnetRuntimeDiagnostics | null> {
|
||||
const res = await api.get<ApiEnvelope<AgnetRuntimeDiagnostics>>(
|
||||
`/api/agnet/user/deployments/${deploymentId}/runtime-diagnostics`,
|
||||
): Promise<AgentRuntimeDiagnostics | null> {
|
||||
const res = await api.get<ApiEnvelope<AgentRuntimeDiagnostics>>(
|
||||
`/api/agent/user/deployments/${deploymentId}/runtime-diagnostics`,
|
||||
{
|
||||
skipBusinessError: true,
|
||||
skipErrorHandler: true,
|
||||
@@ -393,46 +393,46 @@ export async function getAgnetRuntimeDiagnostics(
|
||||
return res.data?.data ?? null
|
||||
}
|
||||
|
||||
export async function simulateAgnetDeploymentEvents(
|
||||
export async function simulateAgentDeploymentEvents(
|
||||
deploymentId: string,
|
||||
events?: string[]
|
||||
): Promise<{ deployment_id: string; simulated: boolean; total: number }> {
|
||||
const res = await api.post<
|
||||
ApiEnvelope<{ deployment_id: string; simulated: boolean; total: number }>
|
||||
>(`/api/agnet/user/deployments/${deploymentId}/simulate-events`, {
|
||||
>(`/api/agent/user/deployments/${deploymentId}/simulate-events`, {
|
||||
events: events ?? [],
|
||||
})
|
||||
const env = res.data
|
||||
if (!env?.success || !env.data) {
|
||||
throw new Error(env?.message || 'simulateAgnetDeploymentEvents failed')
|
||||
throw new Error(env?.message || 'simulateAgentDeploymentEvents failed')
|
||||
}
|
||||
return env.data
|
||||
}
|
||||
|
||||
export async function getAgnetAuditLogs() {
|
||||
export async function getAgentAuditLogs() {
|
||||
const res = await api.get<
|
||||
ApiEnvelope<{ items?: Array<Record<string, unknown>> }>
|
||||
>('/api/agnet/audit-logs')
|
||||
>('/api/agent/audit-logs')
|
||||
return res.data?.data?.items ?? []
|
||||
}
|
||||
|
||||
export async function listAgnetApprovals(params?: {
|
||||
export async function listAgentApprovals(params?: {
|
||||
status?: string
|
||||
deployment_id?: string
|
||||
}): Promise<AgnetApprovalRequest[]> {
|
||||
const res = await api.get<ApiEnvelope<{ items?: AgnetApprovalRequest[] }>>(
|
||||
'/api/agnet/approvals',
|
||||
}): Promise<AgentApprovalRequest[]> {
|
||||
const res = await api.get<ApiEnvelope<{ items?: AgentApprovalRequest[] }>>(
|
||||
'/api/agent/approvals',
|
||||
{ params }
|
||||
)
|
||||
return res.data?.data?.items ?? []
|
||||
}
|
||||
|
||||
export async function approveAgnetApproval(
|
||||
export async function approveAgentApproval(
|
||||
approvalId: string,
|
||||
reason?: string
|
||||
): Promise<AgnetApprovalRequest> {
|
||||
const res = await api.post<ApiEnvelope<AgnetApprovalRequest>>(
|
||||
`/api/agnet/approvals/${approvalId}/approve`,
|
||||
): Promise<AgentApprovalRequest> {
|
||||
const res = await api.post<ApiEnvelope<AgentApprovalRequest>>(
|
||||
`/api/agent/approvals/${approvalId}/approve`,
|
||||
{ reason }
|
||||
)
|
||||
if (!res.data?.success || !res.data.data) {
|
||||
@@ -441,12 +441,12 @@ export async function approveAgnetApproval(
|
||||
return res.data.data
|
||||
}
|
||||
|
||||
export async function rejectAgnetApproval(
|
||||
export async function rejectAgentApproval(
|
||||
approvalId: string,
|
||||
reason?: string
|
||||
): Promise<AgnetApprovalRequest> {
|
||||
const res = await api.post<ApiEnvelope<AgnetApprovalRequest>>(
|
||||
`/api/agnet/approvals/${approvalId}/reject`,
|
||||
): Promise<AgentApprovalRequest> {
|
||||
const res = await api.post<ApiEnvelope<AgentApprovalRequest>>(
|
||||
`/api/agent/approvals/${approvalId}/reject`,
|
||||
{ reason }
|
||||
)
|
||||
if (!res.data?.success || !res.data.data) {
|
||||
@@ -455,23 +455,23 @@ export async function rejectAgnetApproval(
|
||||
return res.data.data
|
||||
}
|
||||
|
||||
export async function listAgnetCredentialLeases(params?: {
|
||||
export async function listAgentCredentialLeases(params?: {
|
||||
status?: string
|
||||
deployment_id?: string
|
||||
}): Promise<AgnetCredentialLease[]> {
|
||||
const res = await api.get<ApiEnvelope<{ items?: AgnetCredentialLease[] }>>(
|
||||
'/api/agnet/credential-leases',
|
||||
}): Promise<AgentCredentialLease[]> {
|
||||
const res = await api.get<ApiEnvelope<{ items?: AgentCredentialLease[] }>>(
|
||||
'/api/agent/credential-leases',
|
||||
{ params }
|
||||
)
|
||||
return res.data?.data?.items ?? []
|
||||
}
|
||||
|
||||
export async function revokeAgnetCredentialLease(
|
||||
export async function revokeAgentCredentialLease(
|
||||
leaseId: string,
|
||||
reason?: string
|
||||
): Promise<AgnetCredentialLease> {
|
||||
const res = await api.post<ApiEnvelope<AgnetCredentialLease>>(
|
||||
`/api/agnet/credential-leases/${leaseId}/revoke`,
|
||||
): Promise<AgentCredentialLease> {
|
||||
const res = await api.post<ApiEnvelope<AgentCredentialLease>>(
|
||||
`/api/agent/credential-leases/${leaseId}/revoke`,
|
||||
{ reason }
|
||||
)
|
||||
if (!res.data?.success || !res.data.data) {
|
||||
@@ -480,10 +480,10 @@ export async function revokeAgnetCredentialLease(
|
||||
return res.data.data
|
||||
}
|
||||
|
||||
export async function getAgnetSnapshots(deploymentId: string) {
|
||||
export async function getAgentSnapshots(deploymentId: string) {
|
||||
const res = await api.get<
|
||||
ApiEnvelope<{ items?: Array<Record<string, unknown>> }>
|
||||
>(`/api/agnet/deployments/${deploymentId}/sk-snapshots`, {
|
||||
>(`/api/agent/deployments/${deploymentId}/sk-snapshots`, {
|
||||
skipBusinessError: true,
|
||||
skipErrorHandler: true,
|
||||
} as Record<string, unknown>)
|
||||
+33
-33
@@ -40,13 +40,13 @@ import {
|
||||
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs'
|
||||
import { Textarea } from '@/components/ui/textarea'
|
||||
import {
|
||||
createAgnetDeployment,
|
||||
listAgnetRoleTemplates,
|
||||
type AgnetAgentPlan,
|
||||
type AgnetOrchestrationPlan,
|
||||
type AgnetRoleTemplate,
|
||||
type AgnetSKSource,
|
||||
type AgnetSubMode,
|
||||
createAgentDeployment,
|
||||
listAgentRoleTemplates,
|
||||
type AgentAgentPlan,
|
||||
type AgentOrchestrationPlan,
|
||||
type AgentRoleTemplate,
|
||||
type AgentSKSource,
|
||||
type AgentSubMode,
|
||||
} from './api'
|
||||
|
||||
type ResourceType =
|
||||
@@ -240,7 +240,7 @@ function formatRiskLevel(
|
||||
return t('High')
|
||||
}
|
||||
|
||||
function formatSubMode(value: AgnetSubMode, t: (key: string) => string) {
|
||||
function formatSubMode(value: AgentSubMode, t: (key: string) => string) {
|
||||
if (value === 'agile') return t('Agile')
|
||||
return t('Waterfall')
|
||||
}
|
||||
@@ -261,11 +261,11 @@ function formatRoleLabel(value: string, t: (key: string) => string) {
|
||||
function formatRoleDisplayName(value: string, t: (key: string) => string) {
|
||||
const name = value.trim().toLowerCase()
|
||||
const labels: Record<string, string> = {
|
||||
'product agnet': t('Product Agnet'),
|
||||
'frontend agnet': t('Frontend Agnet'),
|
||||
'backend agnet': t('Backend Agnet'),
|
||||
'reviewer agnet': t('Reviewer Agnet'),
|
||||
'ops agnet': t('Ops Agnet'),
|
||||
'product agent': t('Product Agent'),
|
||||
'frontend agent': t('Frontend Agent'),
|
||||
'backend agent': t('Backend Agent'),
|
||||
'reviewer agent': t('Reviewer Agent'),
|
||||
'ops agent': t('Ops Agent'),
|
||||
}
|
||||
return labels[name] || value
|
||||
}
|
||||
@@ -293,15 +293,15 @@ function buildAgent(
|
||||
resourceScopeRef: string
|
||||
correlationId: string
|
||||
}
|
||||
): AgnetAgentPlan {
|
||||
let sk_sources: AgnetSKSource[] | undefined
|
||||
): AgentAgentPlan {
|
||||
let sk_sources: AgentSKSource[] | undefined
|
||||
const raw = row.sk_sources_json.trim()
|
||||
if (raw) {
|
||||
const parsed = JSON.parse(raw) as unknown
|
||||
if (!Array.isArray(parsed)) {
|
||||
throw new Error('sk_sources_must_be_array')
|
||||
}
|
||||
sk_sources = parsed as AgnetSKSource[]
|
||||
sk_sources = parsed as AgentSKSource[]
|
||||
}
|
||||
|
||||
const role = row.role_template.trim()
|
||||
@@ -404,7 +404,7 @@ function SectionTitle({
|
||||
)
|
||||
}
|
||||
|
||||
export function CreateAgnetDeploymentSheet({
|
||||
export function CreateAgentDeploymentSheet({
|
||||
open,
|
||||
onOpenChange,
|
||||
}: {
|
||||
@@ -419,7 +419,7 @@ export function CreateAgnetDeploymentSheet({
|
||||
const [step, setStep] = useState('idea')
|
||||
const [templateHint, setTemplateHint] = useState(defaultTemplate.id)
|
||||
const [objective, setObjective] = useState(defaultTemplate.objective)
|
||||
const [subMode, setSubMode] = useState<AgnetSubMode>('agile')
|
||||
const [subMode, setSubMode] = useState<AgentSubMode>('agile')
|
||||
const [riskLevel, setRiskLevel] = useState<'low' | 'medium' | 'high'>(
|
||||
defaultTemplate.risk
|
||||
)
|
||||
@@ -442,11 +442,11 @@ export function CreateAgnetDeploymentSheet({
|
||||
// subsequent sheet opens don't re-fetch. Empty array fallback
|
||||
// means the sheet stays usable if the catalog endpoint is down —
|
||||
// the role picker falls back to free-text input.
|
||||
const [roleTemplates, setRoleTemplates] = useState<AgnetRoleTemplate[]>([])
|
||||
const [roleTemplates, setRoleTemplates] = useState<AgentRoleTemplate[]>([])
|
||||
useEffect(() => {
|
||||
if (!open) return
|
||||
let cancelled = false
|
||||
listAgnetRoleTemplates()
|
||||
listAgentRoleTemplates()
|
||||
.then((items) => {
|
||||
if (!cancelled) setRoleTemplates(items)
|
||||
})
|
||||
@@ -556,7 +556,7 @@ export function CreateAgnetDeploymentSheet({
|
||||
risk_level: riskLevel,
|
||||
resource_scope_ref: resourceScopeRef.trim(),
|
||||
agent_runtime: {
|
||||
platform: 'agnet',
|
||||
platform: 'agent',
|
||||
agents: previewAgents
|
||||
.filter((agent) => agent.role_template && agent.default_model_id)
|
||||
.map((agent) => ({
|
||||
@@ -624,7 +624,7 @@ export function CreateAgnetDeploymentSheet({
|
||||
mutationFn: async () => {
|
||||
const intentId = crypto.randomUUID()
|
||||
const allowed = splitComma(allowedModels)
|
||||
let agentPlans: AgnetAgentPlan[]
|
||||
let agentPlans: AgentAgentPlan[]
|
||||
try {
|
||||
agentPlans = agents.map((row, index) =>
|
||||
buildAgent(row, index, {
|
||||
@@ -648,7 +648,7 @@ export function CreateAgnetDeploymentSheet({
|
||||
instance_count: 1,
|
||||
}))
|
||||
|
||||
const plan: AgnetOrchestrationPlan = {
|
||||
const plan: AgentOrchestrationPlan = {
|
||||
intent_id: intentId,
|
||||
template_hint: templateHint.trim(),
|
||||
objective: objective.trim(),
|
||||
@@ -674,7 +674,7 @@ export function CreateAgnetDeploymentSheet({
|
||||
agent_runtime:
|
||||
runtimeAgents.length > 0
|
||||
? {
|
||||
platform: 'agnet',
|
||||
platform: 'agent',
|
||||
agents: runtimeAgents,
|
||||
}
|
||||
: undefined,
|
||||
@@ -689,15 +689,15 @@ export function CreateAgnetDeploymentSheet({
|
||||
},
|
||||
}
|
||||
|
||||
return createAgnetDeployment({ orchestration_plan: plan })
|
||||
return createAgentDeployment({ orchestration_plan: plan })
|
||||
},
|
||||
onSuccess: (data) => {
|
||||
toast.success(
|
||||
t('Agnet deployment created', {
|
||||
t('Agent deployment created', {
|
||||
deployment_id: data.deployment_id,
|
||||
}) as string
|
||||
)
|
||||
void queryClient.invalidateQueries({ queryKey: ['agnet', 'deployments'] })
|
||||
void queryClient.invalidateQueries({ queryKey: ['agent', 'deployments'] })
|
||||
onOpenChange(false)
|
||||
resetForm()
|
||||
},
|
||||
@@ -850,7 +850,7 @@ export function CreateAgnetDeploymentSheet({
|
||||
<Label>{t('Sub mode')}</Label>
|
||||
<Select
|
||||
value={subMode}
|
||||
onValueChange={(v) => setSubMode(v as AgnetSubMode)}
|
||||
onValueChange={(v) => setSubMode(v as AgentSubMode)}
|
||||
>
|
||||
<SelectTrigger className='h-9'>
|
||||
<SelectValue />
|
||||
@@ -896,7 +896,7 @@ export function CreateAgnetDeploymentSheet({
|
||||
/>
|
||||
</div>
|
||||
<div className='grid gap-2'>
|
||||
<Label>{t('Agnet allowed models comma')}</Label>
|
||||
<Label>{t('Agent allowed models comma')}</Label>
|
||||
<Input
|
||||
value={allowedModels}
|
||||
onChange={(e) => setAllowedModels(e.target.value)}
|
||||
@@ -952,7 +952,7 @@ export function CreateAgnetDeploymentSheet({
|
||||
icon={Bot}
|
||||
title={t('Role cards')}
|
||||
hint={t(
|
||||
'Each card maps one child Agnet role to a runtime model and one bounded resource grant.'
|
||||
'Each card maps one child Agent role to a runtime model and one bounded resource grant.'
|
||||
)}
|
||||
/>
|
||||
<Button
|
||||
@@ -1002,7 +1002,7 @@ export function CreateAgnetDeploymentSheet({
|
||||
<div className='grid gap-3'>
|
||||
<div className='grid gap-2 sm:grid-cols-3'>
|
||||
{/* Role picker — bound to the canonical six-role
|
||||
catalog from /api/agnet/role-templates. Falls
|
||||
catalog from /api/agent/role-templates. Falls
|
||||
back to a free-text input if the catalog
|
||||
failed to load. */}
|
||||
{roleTemplates.length > 0 ? (
|
||||
@@ -1039,7 +1039,7 @@ export function CreateAgnetDeploymentSheet({
|
||||
/>
|
||||
)}
|
||||
<Input
|
||||
placeholder={t('Agnet runtime model id')}
|
||||
placeholder={t('Agent runtime model id')}
|
||||
value={agent.default_model_id}
|
||||
onChange={(e) =>
|
||||
updateAgent(index, {
|
||||
@@ -1336,7 +1336,7 @@ export function CreateAgnetDeploymentSheet({
|
||||
0 ? (
|
||||
<p className='text-muted-foreground mt-2 text-sm italic'>
|
||||
{t(
|
||||
'No resources bound yet — Agnet will run with no external data access.'
|
||||
'No resources bound yet — Agent will run with no external data access.'
|
||||
)}
|
||||
</p>
|
||||
) : (
|
||||
+91
-91
@@ -61,25 +61,25 @@ import {
|
||||
import { Skeleton } from '@/components/ui/skeleton'
|
||||
import { QueryState } from '@/components/query-state'
|
||||
import {
|
||||
approveAgnetApproval,
|
||||
getAgnetDeploymentEvents,
|
||||
getAgnetRuntimeDiagnostics,
|
||||
getAgnetDeploymentTimeline,
|
||||
listAgnetApprovals,
|
||||
listAgnetCredentialLeases,
|
||||
listAgnetDeployments,
|
||||
rejectAgnetApproval,
|
||||
revokeAgnetCredentialLease,
|
||||
simulateAgnetDeploymentEvents,
|
||||
type AgnetApprovalRequest,
|
||||
type AgnetCredentialLease,
|
||||
type AgnetDeployment,
|
||||
type AgnetRuntimeDiagnostics,
|
||||
type AgnetRuntimeExecution,
|
||||
type AgnetSKAccessPolicy,
|
||||
approveAgentApproval,
|
||||
getAgentDeploymentEvents,
|
||||
getAgentRuntimeDiagnostics,
|
||||
getAgentDeploymentTimeline,
|
||||
listAgentApprovals,
|
||||
listAgentCredentialLeases,
|
||||
listAgentDeployments,
|
||||
rejectAgentApproval,
|
||||
revokeAgentCredentialLease,
|
||||
simulateAgentDeploymentEvents,
|
||||
type AgentApprovalRequest,
|
||||
type AgentCredentialLease,
|
||||
type AgentDeployment,
|
||||
type AgentRuntimeDiagnostics,
|
||||
type AgentRuntimeExecution,
|
||||
type AgentSKAccessPolicy,
|
||||
} from './api'
|
||||
import { AzureCloudBindingSheet } from './azure-cloud-binding-sheet'
|
||||
import { CreateAgnetDeploymentSheet } from './create-agnet-deployment-sheet'
|
||||
import { CreateAgentDeploymentSheet } from './create-agent-deployment-sheet'
|
||||
|
||||
type StatusKey = 'running' | 'success' | 'failed' | 'pending'
|
||||
|
||||
@@ -260,7 +260,7 @@ function MetaPill({
|
||||
)
|
||||
}
|
||||
|
||||
function describeRiskLevel(dep: AgnetDeployment): {
|
||||
function describeRiskLevel(dep: AgentDeployment): {
|
||||
label: string
|
||||
tone: 'low' | 'mid' | 'high'
|
||||
} {
|
||||
@@ -288,7 +288,7 @@ function formatRiskLabel(label: string, t: (key: string) => string): string {
|
||||
return label
|
||||
}
|
||||
|
||||
function describeSubMode(dep: AgnetDeployment): string {
|
||||
function describeSubMode(dep: AgentDeployment): string {
|
||||
return dep.sub_mode || dep.orchestration_plan?.sub_mode || 'agile'
|
||||
}
|
||||
|
||||
@@ -299,7 +299,7 @@ function formatSubModeLabel(mode: string, t: (key: string) => string): string {
|
||||
return mode
|
||||
}
|
||||
|
||||
function describeBudget(dep: AgnetDeployment): string {
|
||||
function describeBudget(dep: AgentDeployment): string {
|
||||
const budget = dep.orchestration_plan?.budget
|
||||
if (!budget) {
|
||||
const agents = dep.orchestration_plan?.agents?.length ?? 0
|
||||
@@ -314,7 +314,7 @@ function describeBudget(dep: AgnetDeployment): string {
|
||||
return parts.length > 0 ? parts.join(' / ') : '—'
|
||||
}
|
||||
|
||||
function describeScope(dep: AgnetDeployment): string {
|
||||
function describeScope(dep: AgentDeployment): string {
|
||||
const firstGrant = dep.orchestration_plan?.agents?.flatMap(
|
||||
(agent) => agent.resource_grants || []
|
||||
)[0]
|
||||
@@ -452,7 +452,7 @@ function buildTaskFlowSummaries(
|
||||
}
|
||||
|
||||
function collectResourceGrants(
|
||||
dep: AgnetDeployment
|
||||
dep: AgentDeployment
|
||||
): Record<string, unknown>[] {
|
||||
const manifestGrants = dep.permission_manifest?.resource_grants
|
||||
if (manifestGrants && manifestGrants.length > 0) {
|
||||
@@ -468,7 +468,7 @@ function collectResourceGrants(
|
||||
)
|
||||
}
|
||||
|
||||
function describeSecretRefs(dep: AgnetDeployment): string {
|
||||
function describeSecretRefs(dep: AgentDeployment): string {
|
||||
const grants = collectResourceGrants(dep)
|
||||
const refs = grants.filter((grant) => {
|
||||
const secretRef = grant.secret_ref
|
||||
@@ -568,17 +568,17 @@ function runtimeWarningLabel(
|
||||
}
|
||||
|
||||
function runtimeModeLabel(
|
||||
diagnostics: AgnetRuntimeDiagnostics | null | undefined,
|
||||
diagnostics: AgentRuntimeDiagnostics | null | undefined,
|
||||
t: (key: string) => string
|
||||
): string {
|
||||
const mode = String(diagnostics?.runtime_mode || '').toLowerCase()
|
||||
if (mode === 'swarm') return t('Swarm mode')
|
||||
if (mode === 'agnet') return t('Ordinary sub mode')
|
||||
if (mode === 'agent') return t('Ordinary sub mode')
|
||||
return mode || '—'
|
||||
}
|
||||
|
||||
function runtimeAgentRows(
|
||||
diagnostics: AgnetRuntimeDiagnostics | null | undefined
|
||||
diagnostics: AgentRuntimeDiagnostics | null | undefined
|
||||
) {
|
||||
return (diagnostics?.agents ?? []).slice(0, 4).map((agent, idx) => ({
|
||||
id: String(agent.agent_id || agent.instance_id || idx),
|
||||
@@ -592,7 +592,7 @@ function RuntimeDiagnosticsPanel({
|
||||
diagnostics,
|
||||
isLoading,
|
||||
}: {
|
||||
diagnostics?: AgnetRuntimeDiagnostics | null
|
||||
diagnostics?: AgentRuntimeDiagnostics | null
|
||||
isLoading: boolean
|
||||
}) {
|
||||
const { t } = useTranslation()
|
||||
@@ -685,7 +685,7 @@ function RuntimeDiagnosticsPanel({
|
||||
)
|
||||
}
|
||||
|
||||
function RunDetailPanel({ dep }: { dep: AgnetDeployment }) {
|
||||
function RunDetailPanel({ dep }: { dep: AgentDeployment }) {
|
||||
const { t } = useTranslation()
|
||||
const queryClient = useQueryClient()
|
||||
const currentUser = useAuthStore((state) => state.auth.user)
|
||||
@@ -694,21 +694,21 @@ function RunDetailPanel({ dep }: { dep: AgnetDeployment }) {
|
||||
const risk = describeRiskLevel(dep)
|
||||
const grants = collectResourceGrants(dep)
|
||||
const runtimeDiagnosticsQuery = useQuery({
|
||||
queryKey: ['agnet', 'runtime-diagnostics', dep.deployment_id],
|
||||
queryFn: () => getAgnetRuntimeDiagnostics(dep.deployment_id),
|
||||
queryKey: ['agent', 'runtime-diagnostics', dep.deployment_id],
|
||||
queryFn: () => getAgentRuntimeDiagnostics(dep.deployment_id),
|
||||
enabled: Boolean(dep.deployment_id),
|
||||
refetchInterval: 30_000,
|
||||
})
|
||||
const runtimeDiagnostics = runtimeDiagnosticsQuery.data
|
||||
const simulateMutation = useMutation({
|
||||
mutationFn: () => simulateAgnetDeploymentEvents(dep.deployment_id),
|
||||
mutationFn: () => simulateAgentDeploymentEvents(dep.deployment_id),
|
||||
onSuccess: () => {
|
||||
void queryClient.invalidateQueries({ queryKey: ['agnet', 'deployments'] })
|
||||
void queryClient.invalidateQueries({ queryKey: ['agent', 'deployments'] })
|
||||
void queryClient.invalidateQueries({
|
||||
queryKey: ['agnet', 'deployment-events', dep.deployment_id],
|
||||
queryKey: ['agent', 'deployment-events', dep.deployment_id],
|
||||
})
|
||||
void queryClient.invalidateQueries({
|
||||
queryKey: ['agnet', 'deployment-timeline', dep.deployment_id],
|
||||
queryKey: ['agent', 'deployment-timeline', dep.deployment_id],
|
||||
})
|
||||
toast.success(t('Simulated events recorded'))
|
||||
},
|
||||
@@ -986,7 +986,7 @@ function RunDetailPanel({ dep }: { dep: AgnetDeployment }) {
|
||||
// Deployments page
|
||||
// =============================================================================
|
||||
|
||||
export function AgnetDeploymentsPage() {
|
||||
export function AgentDeploymentsPage() {
|
||||
const { t } = useTranslation()
|
||||
const [filter, setFilter] = useState<'all' | StatusKey>('all')
|
||||
const [keyword, setKeyword] = useState('')
|
||||
@@ -999,8 +999,8 @@ export function AgnetDeploymentsPage() {
|
||||
error: deploymentsError,
|
||||
refetch: refetchDeployments,
|
||||
} = useQuery({
|
||||
queryKey: ['agnet', 'deployments'],
|
||||
queryFn: listAgnetDeployments,
|
||||
queryKey: ['agent', 'deployments'],
|
||||
queryFn: listAgentDeployments,
|
||||
refetchInterval: 30_000,
|
||||
retry: false, // QueryState handles error display; no silent retries
|
||||
})
|
||||
@@ -1034,7 +1034,7 @@ export function AgnetDeploymentsPage() {
|
||||
<PageSurface
|
||||
title={t('Task overview')}
|
||||
subtitle={t(
|
||||
'Status, latest activity and last update for every Agnet task you launched. Details live in the desktop client.'
|
||||
'Status, latest activity and last update for every Agent task you launched. Details live in the desktop client.'
|
||||
)}
|
||||
toolbar={
|
||||
<>
|
||||
@@ -1143,7 +1143,7 @@ export function AgnetDeploymentsPage() {
|
||||
</div>
|
||||
</QueryState>
|
||||
</PageSurface>
|
||||
<CreateAgnetDeploymentSheet
|
||||
<CreateAgentDeploymentSheet
|
||||
open={createOpen}
|
||||
onOpenChange={setCreateOpen}
|
||||
/>
|
||||
@@ -1199,8 +1199,8 @@ export function AgnetDeploymentsPage() {
|
||||
function RunAuditTimeline({ deploymentId }: { deploymentId: string }) {
|
||||
const { t } = useTranslation()
|
||||
const { data = [], isLoading } = useQuery({
|
||||
queryKey: ['agnet', 'deployment-events', deploymentId],
|
||||
queryFn: () => getAgnetDeploymentEvents(deploymentId),
|
||||
queryKey: ['agent', 'deployment-events', deploymentId],
|
||||
queryFn: () => getAgentDeploymentEvents(deploymentId),
|
||||
enabled: Boolean(deploymentId),
|
||||
refetchInterval: 15_000,
|
||||
})
|
||||
@@ -1286,8 +1286,8 @@ function RunAuditTimeline({ deploymentId }: { deploymentId: string }) {
|
||||
function RunRelatedRecordsPanel({ deploymentId }: { deploymentId: string }) {
|
||||
const { t } = useTranslation()
|
||||
const { data, isLoading } = useQuery({
|
||||
queryKey: ['agnet', 'deployment-timeline', deploymentId],
|
||||
queryFn: () => getAgnetDeploymentTimeline(deploymentId),
|
||||
queryKey: ['agent', 'deployment-timeline', deploymentId],
|
||||
queryFn: () => getAgentDeploymentTimeline(deploymentId),
|
||||
enabled: Boolean(deploymentId),
|
||||
refetchInterval: 15_000,
|
||||
})
|
||||
@@ -1490,7 +1490,7 @@ function RunRelatedRecordsPanel({ deploymentId }: { deploymentId: string }) {
|
||||
)}
|
||||
{Boolean(item.artifact_id) && (
|
||||
<a
|
||||
href={`/api/agnet/user/deployments/${encodeURIComponent(deploymentId)}/artifacts/${encodeURIComponent(String(item.artifact_id))}/content`}
|
||||
href={`/api/agent/user/deployments/${encodeURIComponent(deploymentId)}/artifacts/${encodeURIComponent(String(item.artifact_id))}/content`}
|
||||
target='_blank'
|
||||
rel='noreferrer'
|
||||
className='text-primary mt-2 inline-flex items-center gap-1 text-[11px] font-medium hover:underline'
|
||||
@@ -1606,13 +1606,13 @@ function classifyEventLevel(entry: Record<string, unknown>): EventLevel {
|
||||
return 'info'
|
||||
}
|
||||
|
||||
export function AgnetEventsPage() {
|
||||
export function AgentEventsPage() {
|
||||
const { t } = useTranslation()
|
||||
const [level, setLevel] = useState<EventLevel>('all')
|
||||
|
||||
const deploymentsQuery = useQuery({
|
||||
queryKey: ['agnet', 'deployments'],
|
||||
queryFn: listAgnetDeployments,
|
||||
queryKey: ['agent', 'deployments'],
|
||||
queryFn: listAgentDeployments,
|
||||
})
|
||||
const deployments = deploymentsQuery.data ?? []
|
||||
const [activeDeployment, setActiveDeployment] = useState<string | undefined>(
|
||||
@@ -1621,8 +1621,8 @@ export function AgnetEventsPage() {
|
||||
const effectiveDeployment = activeDeployment ?? deployments[0]?.deployment_id
|
||||
|
||||
const eventsQuery = useQuery({
|
||||
queryKey: ['agnet', 'events', effectiveDeployment],
|
||||
queryFn: () => getAgnetDeploymentEvents(effectiveDeployment as string),
|
||||
queryKey: ['agent', 'events', effectiveDeployment],
|
||||
queryFn: () => getAgentDeploymentEvents(effectiveDeployment as string),
|
||||
enabled: Boolean(effectiveDeployment),
|
||||
})
|
||||
|
||||
@@ -1636,7 +1636,7 @@ export function AgnetEventsPage() {
|
||||
<PageSurface
|
||||
title={t('Events')}
|
||||
subtitle={t(
|
||||
'Lifecycle and policy events emitted by Agnet deployments. Correlate with deployment cards.'
|
||||
'Lifecycle and policy events emitted by Agent deployments. Correlate with deployment cards.'
|
||||
)}
|
||||
toolbar={
|
||||
<>
|
||||
@@ -1919,14 +1919,14 @@ function formatUnixMs(value?: number) {
|
||||
return new Date(value).toLocaleString()
|
||||
}
|
||||
|
||||
function AgnetApprovalCard({
|
||||
function AgentApprovalCard({
|
||||
approval,
|
||||
approveBusy,
|
||||
rejectBusy,
|
||||
onApprove,
|
||||
onReject,
|
||||
}: {
|
||||
approval: AgnetApprovalRequest
|
||||
approval: AgentApprovalRequest
|
||||
approveBusy: boolean
|
||||
rejectBusy: boolean
|
||||
onApprove: () => void
|
||||
@@ -1977,12 +1977,12 @@ function AgnetApprovalCard({
|
||||
)
|
||||
}
|
||||
|
||||
function AgnetLeaseCard({
|
||||
function AgentLeaseCard({
|
||||
lease,
|
||||
busy,
|
||||
onRevoke,
|
||||
}: {
|
||||
lease: AgnetCredentialLease
|
||||
lease: AgentCredentialLease
|
||||
busy: boolean
|
||||
onRevoke: () => void
|
||||
}) {
|
||||
@@ -2013,7 +2013,7 @@ function AgnetLeaseCard({
|
||||
)
|
||||
}
|
||||
|
||||
export function AgnetAuditPage() {
|
||||
export function AgentAuditPage() {
|
||||
const { t } = useTranslation()
|
||||
const queryClient = useQueryClient()
|
||||
const [scope, setScope] = useState('')
|
||||
@@ -2021,30 +2021,30 @@ export function AgnetAuditPage() {
|
||||
const [actionFilter, setActionFilter] = useState('')
|
||||
|
||||
const approvalsQuery = useQuery({
|
||||
queryKey: ['agnet', 'approvals', 'pending'],
|
||||
queryFn: () => listAgnetApprovals({ status: 'pending' }),
|
||||
queryKey: ['agent', 'approvals', 'pending'],
|
||||
queryFn: () => listAgentApprovals({ status: 'pending' }),
|
||||
refetchInterval: 30_000,
|
||||
})
|
||||
|
||||
const leasesQuery = useQuery({
|
||||
queryKey: ['agnet', 'credential-leases', 'active'],
|
||||
queryFn: () => listAgnetCredentialLeases({ status: 'active' }),
|
||||
queryKey: ['agent', 'credential-leases', 'active'],
|
||||
queryFn: () => listAgentCredentialLeases({ status: 'active' }),
|
||||
refetchInterval: 30_000,
|
||||
})
|
||||
|
||||
const refreshApprovalState = () => {
|
||||
void queryClient.invalidateQueries({ queryKey: ['agnet', 'approvals'] })
|
||||
void queryClient.invalidateQueries({ queryKey: ['agent', 'approvals'] })
|
||||
void queryClient.invalidateQueries({
|
||||
queryKey: ['agnet', 'credential-leases'],
|
||||
queryKey: ['agent', 'credential-leases'],
|
||||
})
|
||||
void queryClient.invalidateQueries({
|
||||
queryKey: ['heicode', 'agnet', 'audit'],
|
||||
queryKey: ['heicode', 'agent', 'audit'],
|
||||
})
|
||||
}
|
||||
|
||||
const approveMutation = useMutation({
|
||||
mutationFn: (approvalId: string) =>
|
||||
approveAgnetApproval(approvalId, t('Approved from Manager audit page')),
|
||||
approveAgentApproval(approvalId, t('Approved from Manager audit page')),
|
||||
onSuccess: () => {
|
||||
toast.success(t('Approval accepted'))
|
||||
refreshApprovalState()
|
||||
@@ -2053,7 +2053,7 @@ export function AgnetAuditPage() {
|
||||
|
||||
const rejectMutation = useMutation({
|
||||
mutationFn: (approvalId: string) =>
|
||||
rejectAgnetApproval(approvalId, t('Rejected from Manager audit page')),
|
||||
rejectAgentApproval(approvalId, t('Rejected from Manager audit page')),
|
||||
onSuccess: () => {
|
||||
toast.success(t('Approval rejected'))
|
||||
refreshApprovalState()
|
||||
@@ -2062,7 +2062,7 @@ export function AgnetAuditPage() {
|
||||
|
||||
const revokeMutation = useMutation({
|
||||
mutationFn: (leaseId: string) =>
|
||||
revokeAgnetCredentialLease(leaseId, t('Revoked from Manager audit page')),
|
||||
revokeAgentCredentialLease(leaseId, t('Revoked from Manager audit page')),
|
||||
onSuccess: () => {
|
||||
toast.success(t('Credential lease revoked'))
|
||||
refreshApprovalState()
|
||||
@@ -2075,7 +2075,7 @@ export function AgnetAuditPage() {
|
||||
error: auditError,
|
||||
refetch: refetchAudit,
|
||||
} = useQuery({
|
||||
queryKey: ['heicode', 'agnet', 'audit'],
|
||||
queryKey: ['heicode', 'agent', 'audit'],
|
||||
queryFn: () => listMcpAuditLogs({ limit: 200 }),
|
||||
refetchInterval: 60_000,
|
||||
retry: false,
|
||||
@@ -2139,7 +2139,7 @@ export function AgnetAuditPage() {
|
||||
</h3>
|
||||
<p className='text-muted-foreground text-xs'>
|
||||
{t(
|
||||
'Approve or reject high-risk Agnet operations before credentials are leased.'
|
||||
'Approve or reject high-risk Agent operations before credentials are leased.'
|
||||
)}
|
||||
</p>
|
||||
</div>
|
||||
@@ -2156,7 +2156,7 @@ export function AgnetAuditPage() {
|
||||
>
|
||||
<div className='grid gap-3'>
|
||||
{(approvalsQuery.data ?? []).map((approval) => (
|
||||
<AgnetApprovalCard
|
||||
<AgentApprovalCard
|
||||
key={approval.approval_id}
|
||||
approval={approval}
|
||||
approveBusy={approveMutation.isPending}
|
||||
@@ -2193,7 +2193,7 @@ export function AgnetAuditPage() {
|
||||
>
|
||||
<div className='grid gap-3'>
|
||||
{(leasesQuery.data ?? []).map((lease) => (
|
||||
<AgnetLeaseCard
|
||||
<AgentLeaseCard
|
||||
key={lease.lease_id}
|
||||
lease={lease}
|
||||
busy={revokeMutation.isPending}
|
||||
@@ -2232,12 +2232,12 @@ export function AgnetAuditPage() {
|
||||
// resource-binding slice for Work/Runs.
|
||||
// =============================================================================
|
||||
|
||||
// AgnetSKSourcesPage — “准备清单” wizard. Frames the page as a 4-step list
|
||||
// AgentSKSourcesPage — “准备清单” wizard. Frames the page as a 4-step list
|
||||
// (代码 / 文档 / 云账号 / 推荐摘要) per docs/product-package/10 §"Manager 准备清单"
|
||||
// + /11 §5. Does not expose repo_url / ref / paths / usage / tenant_id as the
|
||||
// main flow — those move into a “手动补充”次级 sheet only opened when the user
|
||||
// clicks “连接代码仓库 → 高级补充”.
|
||||
// AgnetSKSourcesPage — 准备清单 wizard.
|
||||
// AgentSKSourcesPage — 准备清单 wizard.
|
||||
//
|
||||
// Data layer switched (commit ?) from the Heicode-local git_sources controller
|
||||
// to mcp-server §2 ResourceBinding (/api/resources) per the contract docs
|
||||
@@ -2258,7 +2258,7 @@ export function AgnetAuditPage() {
|
||||
// (none) → secret_ref (Azure Key Vault azkv://...
|
||||
// reference when the resource has
|
||||
// credential material)
|
||||
export function AgnetSKSourcesPage() {
|
||||
export function AgentSKSourcesPage() {
|
||||
const { t } = useTranslation()
|
||||
const queryClient = useQueryClient()
|
||||
const [advancedOpen, setAdvancedOpen] = useState(false)
|
||||
@@ -2406,7 +2406,7 @@ export function AgnetSKSourcesPage() {
|
||||
docSources.length > 0
|
||||
? t('{{n}} doc source connected', { n: docSources.length })
|
||||
: t(
|
||||
'Link product requirements, design docs or wiki repos so Agnet has project context.'
|
||||
'Link product requirements, design docs or wiki repos so Agent has project context.'
|
||||
),
|
||||
done: docSources.length > 0,
|
||||
optional: true,
|
||||
@@ -2457,7 +2457,7 @@ export function AgnetSKSourcesPage() {
|
||||
<PageSurface
|
||||
title={t('Preparation checklist')}
|
||||
subtitle={t(
|
||||
'Connect code, docs and cloud resources for the current task, then confirm the recommendation before launching Agnet.'
|
||||
'Connect code, docs and cloud resources for the current task, then confirm the recommendation before launching Agent.'
|
||||
)}
|
||||
toolbar={
|
||||
<span className='text-primary inline-flex items-center gap-1.5 rounded-full border border-[color-mix(in_oklch,var(--primary)_30%,var(--border))] bg-[color-mix(in_oklch,var(--primary)_10%,transparent)] px-3 py-1 text-[11px] font-semibold tracking-[0.12em] uppercase'>
|
||||
@@ -2524,7 +2524,7 @@ export function AgnetSKSourcesPage() {
|
||||
disabled={!prereqsDone}
|
||||
onClick={() => setSummaryOpen(true)}
|
||||
>
|
||||
{t('Confirm and launch Agnet')}
|
||||
{t('Confirm and launch Agent')}
|
||||
</Button>
|
||||
) : step.key === 'cloud' ? (
|
||||
// Open the Azure-specific sheet. Manager stores the secret in
|
||||
@@ -2558,7 +2558,7 @@ export function AgnetSKSourcesPage() {
|
||||
<p className='text-foreground font-medium'>{t('How this works')}</p>
|
||||
<p className='mt-2'>
|
||||
{t(
|
||||
'Long-lived credentials are stored in the secret vault. Agnet only requests short-lived, scoped credentials at run time. Production deploys and other high-risk actions are approved from the desktop client.'
|
||||
'Long-lived credentials are stored in the secret vault. Agent only requests short-lived, scoped credentials at run time. Production deploys and other high-risk actions are approved from the desktop client.'
|
||||
)}
|
||||
</p>
|
||||
</div>
|
||||
@@ -2812,8 +2812,8 @@ export function AgnetSKSourcesPage() {
|
||||
|
||||
// =============================================================================
|
||||
// 推荐确认卡 — docs/product-package/10 §"推荐确认卡":
|
||||
// 本次会做 / 本次允许使用 / 本次不会做 / 高危规则 / 预计消耗 / 启动 Agnet
|
||||
// 「启动 Agnet」旁边写「参数由 Heicode 自动生成」。
|
||||
// 本次会做 / 本次允许使用 / 本次不会做 / 高危规则 / 预计消耗 / 启动 Agent
|
||||
// 「启动 Agent」旁边写「参数由 Heicode 自动生成」。
|
||||
// 没有 JSON 编辑器、permission manifest、resource grant 表(§10 高级展开禁令)。
|
||||
// =============================================================================
|
||||
|
||||
@@ -2859,12 +2859,12 @@ function RecommendationSummaryDialog({
|
||||
|
||||
const handleLaunch = () => {
|
||||
setLaunching(true)
|
||||
// Real /api/agnet/deployments POST is wired separately when the task
|
||||
// Real /api/agent/deployments POST is wired separately when the task
|
||||
// object backend lands. For now the summary card matches the docs spec
|
||||
// visually; clicking captures intent + hands off to the desktop client.
|
||||
setTimeout(() => {
|
||||
toast.success(
|
||||
t('Agnet launch staged. Continue the task in the desktop client.')
|
||||
t('Agent launch staged. Continue the task in the desktop client.')
|
||||
)
|
||||
setLaunching(false)
|
||||
onClose()
|
||||
@@ -2880,7 +2880,7 @@ function RecommendationSummaryDialog({
|
||||
{t('Recommendation summary')}
|
||||
</p>
|
||||
<h3 className='mt-1 text-lg font-semibold'>
|
||||
{t('Confirm scope, risk and budget before launching Agnet')}
|
||||
{t('Confirm scope, risk and budget before launching Agent')}
|
||||
</h3>
|
||||
<p className='text-muted-foreground mt-1 text-xs'>
|
||||
{t(
|
||||
@@ -2939,7 +2939,7 @@ function RecommendationSummaryDialog({
|
||||
}}
|
||||
>
|
||||
<Rocket className='h-3.5 w-3.5' />
|
||||
{launching ? t('Launching…') : t('Launch Agnet')}
|
||||
{launching ? t('Launching…') : t('Launch Agent')}
|
||||
</Button>
|
||||
</div>
|
||||
</footer>
|
||||
@@ -2998,7 +2998,7 @@ function RecBlock({
|
||||
// Templates / Agents (kept for backward compatibility — invoked by side routes)
|
||||
// =============================================================================
|
||||
|
||||
export function AgnetTemplatesPage() {
|
||||
export function AgentTemplatesPage() {
|
||||
const { t } = useTranslation()
|
||||
const templates = [
|
||||
{
|
||||
@@ -3035,7 +3035,7 @@ export function AgnetTemplatesPage() {
|
||||
)
|
||||
}
|
||||
|
||||
function runtimeSummary(rt: AgnetRuntimeExecution | undefined): boolean {
|
||||
function runtimeSummary(rt: AgentRuntimeExecution | undefined): boolean {
|
||||
if (!rt) return false
|
||||
return Boolean(
|
||||
(rt.profile_id && rt.profile_id.trim() !== '') ||
|
||||
@@ -3044,7 +3044,7 @@ function runtimeSummary(rt: AgnetRuntimeExecution | undefined): boolean {
|
||||
)
|
||||
}
|
||||
|
||||
function policySummary(p: AgnetSKAccessPolicy | undefined): boolean {
|
||||
function policySummary(p: AgentSKAccessPolicy | undefined): boolean {
|
||||
if (!p) return false
|
||||
return Boolean(
|
||||
(p.policy_ref && p.policy_ref.trim() !== '') ||
|
||||
@@ -3053,15 +3053,15 @@ function policySummary(p: AgnetSKAccessPolicy | undefined): boolean {
|
||||
)
|
||||
}
|
||||
|
||||
export function AgnetAgentsPage() {
|
||||
export function AgentAgentsPage() {
|
||||
const { t } = useTranslation()
|
||||
const { data = [] } = useQuery({
|
||||
queryKey: ['agnet', 'deployments'],
|
||||
queryFn: listAgnetDeployments,
|
||||
queryKey: ['agent', 'deployments'],
|
||||
queryFn: listAgentDeployments,
|
||||
})
|
||||
const rows = useMemo(
|
||||
() =>
|
||||
data.flatMap((dep: AgnetDeployment) =>
|
||||
data.flatMap((dep: AgentDeployment) =>
|
||||
(dep.orchestration_plan?.agents || []).map((agent, idx) => ({
|
||||
dep: dep.deployment_id,
|
||||
id: `${dep.deployment_id}-${idx}`,
|
||||
@@ -3078,7 +3078,7 @@ export function AgnetAgentsPage() {
|
||||
return (
|
||||
<PageSurface
|
||||
title={t('Agents')}
|
||||
subtitle={t('Agent declarations parsed from each Agnet deployment plan.')}
|
||||
subtitle={t('Agent declarations parsed from each Agent deployment plan.')}
|
||||
>
|
||||
{rows.length === 0 ? (
|
||||
<EmptySurface
|
||||
@@ -3093,7 +3093,7 @@ export function AgnetAgentsPage() {
|
||||
>
|
||||
<p className='text-sm font-medium'>{row.role}</p>
|
||||
<p className='text-muted-foreground mt-1 font-mono text-[11px] tracking-[0.12em] uppercase'>
|
||||
{row.dep} · {t('Agnet runtime model')}: {row.runtimeModel}
|
||||
{row.dep} · {t('Agent runtime model')}: {row.runtimeModel}
|
||||
</p>
|
||||
<p className='mt-2 text-sm'>{row.goal}</p>
|
||||
{runtimeSummary(row.runtime) && (
|
||||
+2
-2
@@ -1,6 +1,6 @@
|
||||
import { Link } from '@tanstack/react-router'
|
||||
|
||||
type AgnetHubProps = {
|
||||
type AgentHubProps = {
|
||||
title: string
|
||||
description: string
|
||||
}
|
||||
@@ -11,7 +11,7 @@ const quickLinks = [
|
||||
{ title: 'Audit', to: '/audit' as const },
|
||||
]
|
||||
|
||||
export function AgnetHub(props: AgnetHubProps) {
|
||||
export function AgentHub(props: AgentHubProps) {
|
||||
return (
|
||||
<div className='mx-auto w-full max-w-5xl p-6'>
|
||||
<div className='mb-5'>
|
||||
+8
-8
@@ -51,10 +51,10 @@ export function clearHeicodeTokens() {
|
||||
}
|
||||
|
||||
/**
|
||||
* 外部 Agnet 登录成功后,用 token 向本站校验身份并写入 Manager 会话 Cookie。
|
||||
* 外部 Agent 登录成功后,用 token 向本站校验身份并写入 Manager 会话 Cookie。
|
||||
* 不在本站再做密码校验;本地用户按需 JIT 创建。
|
||||
*/
|
||||
async function establishManagerSessionFromAgnet(): Promise<{
|
||||
async function establishManagerSessionFromAgent(): Promise<{
|
||||
managerUserId?: number
|
||||
}> {
|
||||
const access_token = readToken(ACCESS_TOKEN_KEY)
|
||||
@@ -63,7 +63,7 @@ async function establishManagerSessionFromAgnet(): Promise<{
|
||||
throw new Error('Missing Heicode access token')
|
||||
}
|
||||
const res = await api.post(
|
||||
'/api/user/session/from-agnet',
|
||||
'/api/user/session/from-agent',
|
||||
{
|
||||
access_token,
|
||||
refresh_token: refresh_token || undefined,
|
||||
@@ -101,13 +101,13 @@ async function establishManagerSessionFromAgnet(): Promise<{
|
||||
return { managerUserId: body.data?.id }
|
||||
}
|
||||
|
||||
export async function loginWithAgnetTokens(
|
||||
export async function loginWithAgentTokens(
|
||||
accessToken: string,
|
||||
refreshToken?: string
|
||||
): Promise<{ managerUserId?: number }> {
|
||||
writeTokens(accessToken, refreshToken)
|
||||
try {
|
||||
return await establishManagerSessionFromAgnet()
|
||||
return await establishManagerSessionFromAgent()
|
||||
} catch (err) {
|
||||
clearHeicodeTokens()
|
||||
throw err
|
||||
@@ -185,7 +185,7 @@ export async function login(payload: LoginPayload) {
|
||||
if (res?.success) {
|
||||
writeTokens(res.data?.token, res.data?.refreshToken)
|
||||
try {
|
||||
const sessionRes = await establishManagerSessionFromAgnet()
|
||||
const sessionRes = await establishManagerSessionFromAgent()
|
||||
managerUserId = sessionRes.managerUserId
|
||||
} catch (syncErr) {
|
||||
if (isTwoFactorRequiredError(syncErr)) {
|
||||
@@ -330,7 +330,7 @@ export async function wechatLoginByCode(code: string): Promise<ApiResponse> {
|
||||
// Registration
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
// User registration via Agnet (太极 AI PAD)
|
||||
// User registration via Agent (太极 AI PAD)
|
||||
export async function register(payload: RegisterPayload): Promise<ApiResponse & {
|
||||
data?: {
|
||||
token?: string
|
||||
@@ -364,7 +364,7 @@ export async function register(payload: RegisterPayload): Promise<ApiResponse &
|
||||
}
|
||||
}
|
||||
|
||||
// Send email verification code via Agnet (太极 AI PAD)
|
||||
// Send email verification code via Agent (太极 AI PAD)
|
||||
export async function sendEmailVerification(
|
||||
email: string,
|
||||
_turnstile?: string
|
||||
|
||||
+1
-1
@@ -58,7 +58,7 @@ export function AuthLayout({ children }: AuthLayoutProps) {
|
||||
const pillars = [
|
||||
{
|
||||
icon: Workflow,
|
||||
title: t('Agnet orchestration'),
|
||||
title: t('Agent orchestration'),
|
||||
desc: t('Plan, dispatch and monitor multi-agent runs across tenants.'),
|
||||
},
|
||||
{
|
||||
|
||||
@@ -77,7 +77,7 @@ export function useAuthRedirect() {
|
||||
}
|
||||
|
||||
// 优先从本地 Manager 拉真实用户(含 role / status / quota),
|
||||
// 因为 Agnet 上的 role 不一定与本地 JIT/管理员白名单同步后的角色一致。
|
||||
// 因为 Agent 上的 role 不一定与本地 JIT/管理员白名单同步后的角色一致。
|
||||
let userSet = false
|
||||
try {
|
||||
const selfRes = (await getSelf()) as {
|
||||
@@ -100,7 +100,7 @@ export function useAuthRedirect() {
|
||||
userSet = true
|
||||
}
|
||||
} catch {
|
||||
// Fall through to Agnet /me / fallback below.
|
||||
// Fall through to Agent /me / fallback below.
|
||||
}
|
||||
|
||||
if (!userSet) {
|
||||
|
||||
@@ -28,7 +28,7 @@ import { Input } from '@/components/ui/input'
|
||||
import { Label } from '@/components/ui/label'
|
||||
import { PasswordInput } from '@/components/password-input'
|
||||
import { Turnstile } from '@/components/turnstile'
|
||||
import { register, loginWithAgnetTokens, wechatLoginByCode } from '@/features/auth/api'
|
||||
import { register, loginWithAgentTokens, wechatLoginByCode } from '@/features/auth/api'
|
||||
import { LegalConsent } from '@/features/auth/components/legal-consent'
|
||||
import { OAuthProviders } from '@/features/auth/components/oauth-providers'
|
||||
import { registerFormSchema } from '@/features/auth/constants'
|
||||
@@ -144,7 +144,7 @@ export function SignUpForm({
|
||||
if (res?.success) {
|
||||
toast.success(t('Account created!'))
|
||||
if (res.data?.token) {
|
||||
const sessionRes = await loginWithAgnetTokens(
|
||||
const sessionRes = await loginWithAgentTokens(
|
||||
res.data.token,
|
||||
res.data.refreshToken
|
||||
)
|
||||
|
||||
@@ -19,10 +19,10 @@ import {
|
||||
import { cn } from '@/lib/utils'
|
||||
import { Button } from '@/components/ui/button'
|
||||
import {
|
||||
getAgnetAuditLogs,
|
||||
listAgnetDeployments,
|
||||
type AgnetDeployment,
|
||||
} from '@/features/agnet-console/api'
|
||||
getAgentAuditLogs,
|
||||
listAgentDeployments,
|
||||
type AgentDeployment,
|
||||
} from '@/features/agent-console/api'
|
||||
|
||||
type StatusKey = 'running' | 'success' | 'failed' | 'pending'
|
||||
|
||||
@@ -145,18 +145,18 @@ export function CockpitView() {
|
||||
|
||||
const deploymentsQuery = useQuery({
|
||||
queryKey: ['cockpit', 'deployments'],
|
||||
queryFn: listAgnetDeployments,
|
||||
queryFn: listAgentDeployments,
|
||||
refetchInterval: 30_000,
|
||||
})
|
||||
|
||||
const auditQuery = useQuery({
|
||||
queryKey: ['cockpit', 'audit'],
|
||||
queryFn: getAgnetAuditLogs,
|
||||
queryFn: getAgentAuditLogs,
|
||||
refetchInterval: 60_000,
|
||||
})
|
||||
|
||||
const stats = useMemo(() => {
|
||||
const list: AgnetDeployment[] = deploymentsQuery.data ?? []
|
||||
const list: AgentDeployment[] = deploymentsQuery.data ?? []
|
||||
const counters: Record<StatusKey, number> = {
|
||||
running: 0,
|
||||
success: 0,
|
||||
|
||||
@@ -189,7 +189,7 @@ function IdeaInput({ t }: { t: ReturnType<typeof useTranslation>['t'] }) {
|
||||
</h2>
|
||||
<p className='text-muted-foreground mt-2 text-sm'>
|
||||
{t(
|
||||
'Describe the product, feature, or fix. Heicode will draft the task, recommend resources, and dispatch the Agnet team.'
|
||||
'Describe the product, feature, or fix. Heicode will draft the task, recommend resources, and dispatch the Agent team.'
|
||||
)}
|
||||
</p>
|
||||
</div>
|
||||
@@ -293,7 +293,7 @@ function ContinueTasks({
|
||||
) : recent.length === 0 ? (
|
||||
<p className='bg-background/40 text-muted-foreground rounded-xl border border-dashed border-[color-mix(in_oklch,var(--primary)_22%,var(--border))] p-4 text-center text-xs'>
|
||||
{t(
|
||||
'No tasks yet. Capture an idea above, then confirm the recommendation in the desktop client to launch Agnet.'
|
||||
'No tasks yet. Capture an idea above, then confirm the recommendation in the desktop client to launch Agent.'
|
||||
)}
|
||||
</p>
|
||||
) : (
|
||||
@@ -377,7 +377,7 @@ function TodayFocus({
|
||||
tone: 'running',
|
||||
title: t('Running'),
|
||||
count: buckets.running.length,
|
||||
hint: t('Active Agnet sub-loops'),
|
||||
hint: t('Active Agent sub-loops'),
|
||||
},
|
||||
]
|
||||
|
||||
@@ -477,7 +477,7 @@ function HelperEntries({ t }: { t: ReturnType<typeof useTranslation>['t'] }) {
|
||||
{
|
||||
Icon: Rocket,
|
||||
title: t('Task overview'),
|
||||
desc: t('Status of every Agnet task you launched'),
|
||||
desc: t('Status of every Agent task you launched'),
|
||||
to: '/deployments',
|
||||
},
|
||||
]
|
||||
|
||||
@@ -15,7 +15,7 @@ const MODELS: ModelConfig[] = [
|
||||
id: 'gpt-4o',
|
||||
name: 'gpt-4o',
|
||||
response:
|
||||
'Agnet planners propose orchestration runs; the platform arbitrates risk and budget before any agent executes a step.',
|
||||
'Agent planners propose orchestration runs; the platform arbitrates risk and budget before any agent executes a step.',
|
||||
tokens: 27,
|
||||
latency: 142,
|
||||
badgeClass:
|
||||
|
||||
@@ -45,9 +45,9 @@ import { cn } from '@/lib/utils'
|
||||
import { Button } from '@/components/ui/button'
|
||||
import { Skeleton } from '@/components/ui/skeleton'
|
||||
import {
|
||||
createAgnetDeployment,
|
||||
type AgnetOrchestrationPlan,
|
||||
} from '@/features/agnet-console/api'
|
||||
createAgentDeployment,
|
||||
type AgentOrchestrationPlan,
|
||||
} from '@/features/agent-console/api'
|
||||
|
||||
const route = getRouteApi('/_authenticated/tasks/$id')
|
||||
|
||||
@@ -214,13 +214,13 @@ export function TaskCardView() {
|
||||
role_templates: ['backend'],
|
||||
default_model_id: 'claude-sonnet-4-6',
|
||||
})
|
||||
return createAgnetDeployment({
|
||||
return createAgentDeployment({
|
||||
orchestration_plan:
|
||||
draft.orchestration_plan as unknown as AgnetOrchestrationPlan,
|
||||
draft.orchestration_plan as unknown as AgentOrchestrationPlan,
|
||||
})
|
||||
},
|
||||
onSuccess: (deployment) => {
|
||||
void queryClient.invalidateQueries({ queryKey: ['agnet', 'deployments'] })
|
||||
void queryClient.invalidateQueries({ queryKey: ['agent', 'deployments'] })
|
||||
toast.success(
|
||||
t('Manager deployment created', {
|
||||
deployment_id: deployment.deployment_id,
|
||||
|
||||
+1
-1
@@ -91,7 +91,7 @@ export function Wallet(props: WalletProps) {
|
||||
// the canonical balance source is mcp-server /api/user/heicode/balance
|
||||
// (server-side wraps NewAPI admin token, returns the freshest figures).
|
||||
// Fall back to NewAPI /api/user/self when the user hasn't yet been
|
||||
// mirrored into NewAPI via from-agnet (HEICODE_USER_NOT_FOUND) so a
|
||||
// mirrored into NewAPI via from-agent (HEICODE_USER_NOT_FOUND) so a
|
||||
// brand-new account still sees something instead of empty stats.
|
||||
const fetchUser = useCallback(async () => {
|
||||
try {
|
||||
|
||||
+3
-3
@@ -10,9 +10,9 @@ export type TopNavLink = {
|
||||
external?: boolean
|
||||
}
|
||||
|
||||
// Default top-nav modules — agnet command axis only.
|
||||
// Default top-nav modules — agent command axis only.
|
||||
// Pricing/Models/Channels are intentionally absent: Heicode Manager
|
||||
// is a tenant + agnet control plane, not an API gateway storefront.
|
||||
// is a tenant + agent control plane, not an API gateway storefront.
|
||||
const DEFAULT_HEADER_NAV_MODULES = {
|
||||
home: true,
|
||||
overview: true,
|
||||
@@ -26,7 +26,7 @@ const DEFAULT_HEADER_NAV_MODULES = {
|
||||
* Backend format example (stringified JSON):
|
||||
* {
|
||||
* home: true,
|
||||
* agnet: true,
|
||||
* agent: true,
|
||||
* deployments: true,
|
||||
* events: true,
|
||||
* audit: true,
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
"Account provisioning is handled by your platform administrator. Reach out to the Heicode operator to be added to a tenant.": "Account provisioning is handled by your platform administrator. Reach out to the Heicode operator to be added to a tenant.",
|
||||
"Adjust filters or trigger a new orchestration plan.": "Adjust filters or trigger a new orchestration plan.",
|
||||
"Authenticate against the Heicode identity service. Tenant scope, role and SK access will be loaded automatically.": "Authenticate against the Heicode identity service. Tenant scope, role and SK access will be loaded automatically.",
|
||||
"Card-based view of every Agnet orchestration run with risk, budget, executor and live status.": "Card-based view of every Agnet orchestration run with risk, budget, executor and live status.",
|
||||
"Card-based view of every Agent orchestration run with risk, budget, executor and live status.": "Card-based view of every Agent orchestration run with risk, budget, executor and live status.",
|
||||
"Click to view image": "Click to view image",
|
||||
"Client login API contract and integration screenshots": "Client login API contract and integration screenshots",
|
||||
"Create a new code delivery run with checks.": "Create a new code delivery run with checks.",
|
||||
@@ -13,7 +13,7 @@
|
||||
"Git sources subtitle": "Bind Git repositories for SK, then review immutable snapshot anchors tied to each deployment.",
|
||||
"Git-backed SK sources description": "Immutable snapshots from bound Git refs and uploads—wired into each run for audit.",
|
||||
"Immutable, hash-verified context bundles wired to every run.": "Immutable, hash-verified context bundles wired to every run.",
|
||||
"Lifecycle and policy events emitted by Agnet deployments. Correlate with deployment cards.": "Lifecycle and policy events emitted by Agnet deployments. Correlate with deployment cards.",
|
||||
"Lifecycle and policy events emitted by Agent deployments. Correlate with deployment cards.": "Lifecycle and policy events emitted by Agent deployments. Correlate with deployment cards.",
|
||||
"No audit entries match the filter.": "No audit entries match the filter.",
|
||||
"No resolved snapshots hint": "Resolve snapshots from the control plane after Git-backed sk_sources are configured.",
|
||||
"No deployments match the current filter": "No deployments match the current filter",
|
||||
@@ -24,7 +24,7 @@
|
||||
"Reset filters to see all entries.": "Reset filters to see all entries.",
|
||||
"Resolve snapshots from the control plane to capture SK lineage.": "Resolve snapshots from the control plane to capture SK lineage.",
|
||||
"Set custom About HTML or URL in System Settings > General > About.": "Set custom About HTML or URL in System Settings > General > About.",
|
||||
"Sign in to operate Agnet deployments, inspect events, and audit SK snapshots for every tenant under your account.": "Sign in to operate Agnet deployments, inspect events, and audit SK snapshots for every tenant under your account.",
|
||||
"Sign in to operate Agent deployments, inspect events, and audit SK snapshots for every tenant under your account.": "Sign in to operate Agent deployments, inspect events, and audit SK snapshots for every tenant under your account.",
|
||||
"Sign in to operate deployments and review Git-bound snapshot anchors for tenants under your account.": "Sign in to operate deployments and review Git-bound snapshot anchors for tenants under your account.",
|
||||
"Sign in to your workspace": "Sign in to your workspace",
|
||||
"Source → snapshot → hash chain. Every snapshot is immutable and tied to the orchestration plan.": "Source → snapshot → hash chain. Every snapshot is immutable and tied to the orchestration plan.",
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
"Agentic development control plane": "Agentic development control plane",
|
||||
"Agents": "Agents",
|
||||
"Agile Minimal": "Agile Minimal",
|
||||
"Agnet orchestration": "Agnet orchestration",
|
||||
"Agent orchestration": "Agent orchestration",
|
||||
"AIGC2D": "AIGC2D",
|
||||
"All statuses": "All statuses",
|
||||
"All usage logs": "All usage logs",
|
||||
@@ -26,7 +26,7 @@
|
||||
"Awaiting platform arbitration": "Awaiting platform arbitration",
|
||||
"AZURE_OPENAI_ENDPOINT *": "AZURE_OPENAI_ENDPOINT *",
|
||||
"budget": "budget",
|
||||
"Card-based view of every Agnet orchestration run with risk, budget, executor and live status.": "Card-based view of every Agnet orchestration run with risk, budget, executor and live status.",
|
||||
"Card-based view of every Agent orchestration run with risk, budget, executor and live status.": "Card-based view of every Agent orchestration run with risk, budget, executor and live status.",
|
||||
"channel": "channel",
|
||||
"checkout.session.completed": "checkout.session.completed",
|
||||
"checkout.session.expired": "checkout.session.expired",
|
||||
@@ -96,7 +96,7 @@
|
||||
"Insufficient permission": "Insufficient permission",
|
||||
"Jimeng": "Jimeng",
|
||||
"JustSong": "JustSong",
|
||||
"Lifecycle and policy events emitted by Agnet deployments. Correlate with deployment cards.": "Lifecycle and policy events emitted by Agnet deployments. Correlate with deployment cards.",
|
||||
"Lifecycle and policy events emitted by Agent deployments. Correlate with deployment cards.": "Lifecycle and policy events emitted by Agent deployments. Correlate with deployment cards.",
|
||||
"LingYiWanWu": "LingYiWanWu",
|
||||
"LinuxDO": "LinuxDO",
|
||||
"Live code delivery runs": "Live code delivery runs",
|
||||
@@ -161,7 +161,7 @@
|
||||
"Running deployments": "Running deployments",
|
||||
"Select deployment": "Select deployment",
|
||||
"Set custom About HTML or URL in System Settings > General > About.": "Set custom About HTML or URL in System Settings > General > About.",
|
||||
"Sign in to operate Agnet deployments, inspect events, and audit SK snapshots for every tenant under your account.": "Sign in to operate Agnet deployments, inspect events, and audit SK snapshots for every tenant under your account.",
|
||||
"Sign in to operate Agent deployments, inspect events, and audit SK snapshots for every tenant under your account.": "Sign in to operate Agent deployments, inspect events, and audit SK snapshots for every tenant under your account.",
|
||||
"Sign in to operate deployments and review Git-bound snapshot anchors for tenants under your account.": "Sign in to operate deployments and review Git-bound snapshot anchors for tenants under your account.",
|
||||
"Sign in to your workspace": "Sign in to your workspace",
|
||||
"SiliconFlow": "SiliconFlow",
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
"Agentic development control plane": "Agentic development control plane",
|
||||
"Agents": "Agents",
|
||||
"Agile Minimal": "Agile Minimal",
|
||||
"Agnet orchestration": "Agnet orchestration",
|
||||
"Agent orchestration": "Agent orchestration",
|
||||
"AI Proxy": "AI Proxy",
|
||||
"AIGC2D": "AIGC2D",
|
||||
"All statuses": "All statuses",
|
||||
@@ -30,7 +30,7 @@
|
||||
"AZURE_OPENAI_ENDPOINT *": "AZURE_OPENAI_ENDPOINT *",
|
||||
"Baidu V2": "Baidu V2",
|
||||
"budget": "budget",
|
||||
"Card-based view of every Agnet orchestration run with risk, budget, executor and live status.": "Card-based view of every Agnet orchestration run with risk, budget, executor and live status.",
|
||||
"Card-based view of every Agent orchestration run with risk, budget, executor and live status.": "Card-based view of every Agent orchestration run with risk, budget, executor and live status.",
|
||||
"channel": "channel",
|
||||
"checkout.session.completed": "checkout.session.completed",
|
||||
"checkout.session.expired": "checkout.session.expired",
|
||||
@@ -100,7 +100,7 @@
|
||||
"Insufficient permission": "Insufficient permission",
|
||||
"Jimeng": "Jimeng",
|
||||
"JustSong": "JustSong",
|
||||
"Lifecycle and policy events emitted by Agnet deployments. Correlate with deployment cards.": "Lifecycle and policy events emitted by Agnet deployments. Correlate with deployment cards.",
|
||||
"Lifecycle and policy events emitted by Agent deployments. Correlate with deployment cards.": "Lifecycle and policy events emitted by Agent deployments. Correlate with deployment cards.",
|
||||
"LingYiWanWu": "LingYiWanWu",
|
||||
"LinuxDO": "LinuxDO",
|
||||
"Live code delivery runs": "Live code delivery runs",
|
||||
@@ -164,7 +164,7 @@
|
||||
"Running deployments": "Running deployments",
|
||||
"Select deployment": "Select deployment",
|
||||
"Set custom About HTML or URL in System Settings > General > About.": "Set custom About HTML or URL in System Settings > General > About.",
|
||||
"Sign in to operate Agnet deployments, inspect events, and audit SK snapshots for every tenant under your account.": "Sign in to operate Agnet deployments, inspect events, and audit SK snapshots for every tenant under your account.",
|
||||
"Sign in to operate Agent deployments, inspect events, and audit SK snapshots for every tenant under your account.": "Sign in to operate Agent deployments, inspect events, and audit SK snapshots for every tenant under your account.",
|
||||
"Sign in to operate deployments and review Git-bound snapshot anchors for tenants under your account.": "Sign in to operate deployments and review Git-bound snapshot anchors for tenants under your account.",
|
||||
"Sign in to your workspace": "Sign in to your workspace",
|
||||
"SiliconFlow": "SiliconFlow",
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
"Account provisioning is handled by your platform administrator. Reach out to the Heicode operator to be added to a tenant.": "Account provisioning is handled by your platform administrator. Reach out to the Heicode operator to be added to a tenant.",
|
||||
"Adjust filters or trigger a new orchestration plan.": "Adjust filters or trigger a new orchestration plan.",
|
||||
"Authenticate against the Heicode identity service. Tenant scope, role and SK access will be loaded automatically.": "Authenticate against the Heicode identity service. Tenant scope, role and SK access will be loaded automatically.",
|
||||
"Card-based view of every Agnet orchestration run with risk, budget, executor and live status.": "Card-based view of every Agnet orchestration run with risk, budget, executor and live status.",
|
||||
"Card-based view of every Agent orchestration run with risk, budget, executor and live status.": "Card-based view of every Agent orchestration run with risk, budget, executor and live status.",
|
||||
"Click to view image": "Click to view image",
|
||||
"Client login API contract and integration screenshots": "Client login API contract and integration screenshots",
|
||||
"Create a new code delivery run with checks.": "Create a new code delivery run with checks.",
|
||||
@@ -13,7 +13,7 @@
|
||||
"Git sources subtitle": "Bind Git repositories for SK, then review immutable snapshot anchors tied to each deployment.",
|
||||
"Git-backed SK sources description": "Immutable snapshots from bound Git refs and uploads—wired into each run for audit.",
|
||||
"Immutable, hash-verified context bundles wired to every run.": "Immutable, hash-verified context bundles wired to every run.",
|
||||
"Lifecycle and policy events emitted by Agnet deployments. Correlate with deployment cards.": "Lifecycle and policy events emitted by Agnet deployments. Correlate with deployment cards.",
|
||||
"Lifecycle and policy events emitted by Agent deployments. Correlate with deployment cards.": "Lifecycle and policy events emitted by Agent deployments. Correlate with deployment cards.",
|
||||
"No audit entries match the filter.": "No audit entries match the filter.",
|
||||
"No resolved snapshots hint": "Resolve snapshots from the control plane after Git-backed sk_sources are configured.",
|
||||
"No deployments match the current filter": "No deployments match the current filter",
|
||||
@@ -24,7 +24,7 @@
|
||||
"Reset filters to see all entries.": "Reset filters to see all entries.",
|
||||
"Resolve snapshots from the control plane to capture SK lineage.": "Resolve snapshots from the control plane to capture SK lineage.",
|
||||
"Set custom About HTML or URL in System Settings > General > About.": "Set custom About HTML or URL in System Settings > General > About.",
|
||||
"Sign in to operate Agnet deployments, inspect events, and audit SK snapshots for every tenant under your account.": "Sign in to operate Agnet deployments, inspect events, and audit SK snapshots for every tenant under your account.",
|
||||
"Sign in to operate Agent deployments, inspect events, and audit SK snapshots for every tenant under your account.": "Sign in to operate Agent deployments, inspect events, and audit SK snapshots for every tenant under your account.",
|
||||
"Sign in to operate deployments and review Git-bound snapshot anchors for tenants under your account.": "Sign in to operate deployments and review Git-bound snapshot anchors for tenants under your account.",
|
||||
"Sign in to your workspace": "Sign in to your workspace",
|
||||
"Source → snapshot → hash chain. Every snapshot is immutable and tied to the orchestration plan.": "Source → snapshot → hash chain. Every snapshot is immutable and tied to the orchestration plan.",
|
||||
|
||||
+30
-30
@@ -78,7 +78,7 @@
|
||||
"Actions": "Actions",
|
||||
"active": "active",
|
||||
"Active": "Active",
|
||||
"Active Agnet sub-loops": "Active Agnet sub-loops",
|
||||
"Active Agent sub-loops": "Active Agent sub-loops",
|
||||
"Active Cache Count": "Active Cache Count",
|
||||
"Active code delivery runs across tenants": "Active code delivery runs across tenants",
|
||||
"Active Files": "Active Files",
|
||||
@@ -176,7 +176,7 @@
|
||||
"After invalidating, this subscription will be immediately deactivated. Historical records are not affected. Continue?": "After invalidating, this subscription will be immediately deactivated. Historical records are not affected. Continue?",
|
||||
"After scanning, the binding will complete automatically": "After scanning, the binding will complete automatically",
|
||||
"Agent": "Agent",
|
||||
"Agent declarations parsed from each Agnet deployment plan.": "Agent declarations parsed from each Agnet deployment plan.",
|
||||
"Agent declarations parsed from each Agent deployment plan.": "Agent declarations parsed from each Agent deployment plan.",
|
||||
"Agent declarations parsed from each deployment plan.": "Agent declarations parsed from each deployment plan.",
|
||||
"Agent ID *": "Agent ID *",
|
||||
"Agentic development control plane": "Agentic development control plane",
|
||||
@@ -184,12 +184,12 @@
|
||||
"Aggregated usage metrics and trend charts.": "Aggregated usage metrics and trend charts.",
|
||||
"aggregates 50+ AI providers behind one unified API. Manage access, track costs, and scale effortlessly.": "aggregates 50+ AI providers behind one unified API. Manage access, track costs, and scale effortlessly.",
|
||||
"Agile Minimal": "Agile Minimal",
|
||||
"Agnet allowed models comma": "Agnet runtime model IDs (comma-separated)",
|
||||
"Agnet deployment created": "Agnet deployment created ({{deployment_id}})",
|
||||
"Agnet launch staged. Continue the task in the desktop client.": "Agnet launch staged. Continue the task in the desktop client.",
|
||||
"Agnet orchestration": "Agnet orchestration",
|
||||
"Agnet runtime model": "Agnet runtime model",
|
||||
"Agnet runtime model id": "Agnet runtime model ID",
|
||||
"Agent allowed models comma": "Agent runtime model IDs (comma-separated)",
|
||||
"Agent deployment created": "Agent deployment created ({{deployment_id}})",
|
||||
"Agent launch staged. Continue the task in the desktop client.": "Agent launch staged. Continue the task in the desktop client.",
|
||||
"Agent orchestration": "Agent orchestration",
|
||||
"Agent runtime model": "Agent runtime model",
|
||||
"Agent runtime model id": "Agent runtime model ID",
|
||||
"ago": "ago",
|
||||
"AGPL v3.0 License": "AGPL v3.0 License",
|
||||
"AI model testing environment": "AI model testing environment",
|
||||
@@ -504,7 +504,7 @@
|
||||
"Browse and compare": "Browse and compare",
|
||||
"budget": "budget",
|
||||
"Budget & usage": "Budget & usage",
|
||||
"Budget caps": "Agnet runtime caps",
|
||||
"Budget caps": "Agent runtime caps",
|
||||
"Budget tokens = max tokens × ratio. Accepts a decimal between 0.002 and 1. Recommended to keep aligned with upstream billing.": "Budget tokens = max tokens × ratio. Accepts a decimal between 0.002 and 1. Recommended to keep aligned with upstream billing.",
|
||||
"Budget tokens = max tokens × ratio. Accepts a decimal between 0.1 and 1.": "Budget tokens = max tokens × ratio. Accepts a decimal between 0.1 and 1.",
|
||||
"Budget Tokens Ratio": "Budget Tokens Ratio",
|
||||
@@ -547,7 +547,7 @@
|
||||
"Cancelled": "Cancelled",
|
||||
"Cancelled at": "Cancelled at",
|
||||
"Capture a reusable bundle of models, tags, or endpoints.": "Capture a reusable bundle of models, tags, or endpoints.",
|
||||
"Card-based view of every Agnet orchestration run with risk, budget, executor and live status.": "Card-based view of every Agnet orchestration run with risk, runtime caps, resource scope and live status.",
|
||||
"Card-based view of every Agent orchestration run with risk, budget, executor and live status.": "Card-based view of every Agent orchestration run with risk, runtime caps, resource scope and live status.",
|
||||
"Category Name": "Category Name",
|
||||
"Category name is required": "Category name is required",
|
||||
"Category name must be less than 50 characters": "Category name must be less than 50 characters",
|
||||
@@ -771,7 +771,7 @@
|
||||
"Configure your account preferences and integrations": "Configure your account preferences and integrations",
|
||||
"Confirm": "Confirm",
|
||||
"Confirm Action": "Confirm Action",
|
||||
"Confirm and launch Agnet": "Confirm and launch Agnet",
|
||||
"Confirm and launch Agent": "Confirm and launch Agent",
|
||||
"Confirm Batch Update": "Confirm Batch Update",
|
||||
"Confirm Billing Conflicts": "Confirm Billing Conflicts",
|
||||
"Confirm Changes": "Confirm Changes",
|
||||
@@ -790,7 +790,7 @@
|
||||
"Confirm password": "Confirm password",
|
||||
"Confirm Payment": "Confirm Payment",
|
||||
"Confirm recommendation summary": "Confirm recommendation summary",
|
||||
"Confirm scope, risk and budget before launching Agnet": "Confirm scope, risk and budget before launching Agnet",
|
||||
"Confirm scope, risk and budget before launching Agent": "Confirm scope, risk and budget before launching Agent",
|
||||
"Confirm Selection": "Confirm Selection",
|
||||
"Confirm settings and finish setup": "Confirm settings and finish setup",
|
||||
"Confirm Unbind": "Confirm Unbind",
|
||||
@@ -883,8 +883,8 @@
|
||||
"Create a new code delivery run with checks.": "Create a new code delivery run with checks.",
|
||||
"Create a new user group to configure ratio overrides for.": "Create a new user group to configure ratio overrides for.",
|
||||
"Create account": "Create account",
|
||||
"Create Agnet deployment": "Create Agnet deployment",
|
||||
"Create Agnet deployment description": "Send the orchestration plan to Agnet. Model choices here are runtime policy, not billing setup.",
|
||||
"Create Agent deployment": "Create Agent deployment",
|
||||
"Create Agent deployment description": "Send the orchestration plan to Agent. Model choices here are runtime policy, not billing setup.",
|
||||
"Create an account": "Create an account",
|
||||
"Create and review invite or credit codes.": "Create and review invite or credit codes.",
|
||||
"Create API Key": "Create API Key",
|
||||
@@ -1040,7 +1040,7 @@
|
||||
"Deployments": "Deployments",
|
||||
"Desc": "Desc",
|
||||
"Describe": "Describe",
|
||||
"Describe the product, feature, or fix. Heicode will draft the task, recommend resources, and dispatch the Agnet team.": "Describe the product, feature, or fix. Heicode will draft the task, recommend resources, and dispatch the Agnet team.",
|
||||
"Describe the product, feature, or fix. Heicode will draft the task, recommend resources, and dispatch the Agent team.": "Describe the product, feature, or fix. Heicode will draft the task, recommend resources, and dispatch the Agent team.",
|
||||
"Describe this model...": "Describe this model...",
|
||||
"Describe this vendor...": "Describe this vendor...",
|
||||
"Describe what this work should achieve.": "Describe what this work should achieve.",
|
||||
@@ -1181,7 +1181,7 @@
|
||||
"e.g., us-central1 or JSON format for model-specific regions": "e.g., us-central1 or JSON format for model-specific regions",
|
||||
"e.g., v2.1": "e.g., v2.1",
|
||||
"Each backup code can only be used once.": "Each backup code can only be used once.",
|
||||
"Each card maps one child Agnet role to a runtime model and one bounded resource grant.": "Each card maps one child Agnet role to a runtime model and one bounded resource grant.",
|
||||
"Each card maps one child Agent role to a runtime model and one bounded resource grant.": "Each card maps one child Agent role to a runtime model and one bounded resource grant.",
|
||||
"Each item must be an object with a single key-value pair.": "Each item must be an object with a single key-value pair.",
|
||||
"Each item must have exactly one key-value pair.": "Each item must have exactly one key-value pair.",
|
||||
"Each line represents one keyword. Leave blank to disable the list but keep the switch states.": "Each line represents one keyword. Leave blank to disable the list but keep the switch states.",
|
||||
@@ -1677,8 +1677,8 @@
|
||||
"Get Started": "Get Started",
|
||||
"Git binding": "Git binding",
|
||||
"Git sources": "Git sources",
|
||||
"Git sources binding explainer": "Skill (SK) definitions live in Git. Manager does not edit Markdown here—register code repos, SK tool repos, and refs via the Heicode client or your deployment plan sk_sources. Pass runtime and SK-policy parameters into Agnet when it starts each deployment; effective permissions are stored and enforced on Agnet. Below lists immutable snapshot anchors (Git commit / upload artifact) Agnet resolved for auditing.",
|
||||
"Git sources subtitle": "Configure bindings and deployment parameters for Agnet, then review snapshot anchors per deployment (enforcement lives on Agnet).",
|
||||
"Git sources binding explainer": "Skill (SK) definitions live in Git. Manager does not edit Markdown here—register code repos, SK tool repos, and refs via the Heicode client or your deployment plan sk_sources. Pass runtime and SK-policy parameters into Agent when it starts each deployment; effective permissions are stored and enforced on Agent. Below lists immutable snapshot anchors (Git commit / upload artifact) Agent resolved for auditing.",
|
||||
"Git sources subtitle": "Configure bindings and deployment parameters for Agent, then review snapshot anchors per deployment (enforcement lives on Agent).",
|
||||
"Git sources workflow step 1": "Bind your team’s Git repositories that hold application code and delivery context.",
|
||||
"Git sources workflow step 2": "Bind SK tool repositories (skills registry) that Resource Grants allow this run to draw from.",
|
||||
"Git sources workflow step 3": "Allocate cloud capacity and permissions for sub-agents—for example dedicated VMs, roles, and API scopes.",
|
||||
@@ -1958,7 +1958,7 @@
|
||||
"Last updated:": "Last updated:",
|
||||
"Last Used": "Last Used",
|
||||
"Last used:": "Last used:",
|
||||
"Launch Agnet": "Launch Agnet",
|
||||
"Launch Agent": "Launch Agent",
|
||||
"Launching…": "Launching…",
|
||||
"Layout": "Layout",
|
||||
"Learn more": "Learn more",
|
||||
@@ -1978,7 +1978,7 @@
|
||||
"Leave empty to use system temp directory": "Leave empty to use system temp directory",
|
||||
"Leave empty to use username": "Leave empty to use username",
|
||||
"Less": "Less",
|
||||
"Lifecycle and policy events emitted by Agnet deployments. Correlate with deployment cards.": "Lifecycle and policy events emitted by Agnet deployments. Correlate with deployment cards.",
|
||||
"Lifecycle and policy events emitted by Agent deployments. Correlate with deployment cards.": "Lifecycle and policy events emitted by Agent deployments. Correlate with deployment cards.",
|
||||
"Light": "Light",
|
||||
"Lightning Fast": "Lightning Fast",
|
||||
"Limit period": "Limit period",
|
||||
@@ -2023,7 +2023,7 @@
|
||||
"Logo": "Logo",
|
||||
"Logo URL": "Logo URL",
|
||||
"Logs": "Logs",
|
||||
"Long-lived credentials are stored in the secret vault. Agnet only requests short-lived, scoped credentials at run time. Production deploys and other high-risk actions are approved from the desktop client.": "Long-lived credentials are stored in the secret vault. Agnet only requests short-lived, scoped credentials at run time. Production deploys and other high-risk actions are approved from the desktop client.",
|
||||
"Long-lived credentials are stored in the secret vault. Agent only requests short-lived, scoped credentials at run time. Production deploys and other high-risk actions are approved from the desktop client.": "Long-lived credentials are stored in the secret vault. Agent only requests short-lived, scoped credentials at run time. Production deploys and other high-risk actions are approved from the desktop client.",
|
||||
"m": "m",
|
||||
"Maintain a list of common questions for the dashboard help panel": "Maintain a list of common questions for the dashboard help panel",
|
||||
"Maintenance": "Maintenance",
|
||||
@@ -2381,7 +2381,7 @@
|
||||
"No Sync": "No Sync",
|
||||
"No system announcements": "No system announcements",
|
||||
"No tasks yet": "No tasks yet",
|
||||
"No tasks yet. Capture an idea above, then confirm the recommendation in the desktop client to launch Agnet.": "No tasks yet. Capture an idea above, then confirm the recommendation in the desktop client to launch Agnet.",
|
||||
"No tasks yet. Capture an idea above, then confirm the recommendation in the desktop client to launch Agent.": "No tasks yet. Capture an idea above, then confirm the recommendation in the desktop client to launch Agent.",
|
||||
"No token found.": "No token found.",
|
||||
"No tools configured": "No tools configured",
|
||||
"No Upgrade": "No Upgrade",
|
||||
@@ -3039,7 +3039,7 @@
|
||||
"Resources workflow step 1": "Bind project code, SK, document, or cloud-resource metadata without exposing raw credentials.",
|
||||
"Resources workflow step 2": "Keep secret material behind secret_ref; Manager surfaces references and status only.",
|
||||
"Resources workflow step 3": "Allocate scope, allowed paths/actions, runtime policy, and budget to the run manifest.",
|
||||
"Resources workflow step 4": "Start a Work/Run; Agnet resolves immutable anchors and enforces the effective grants.",
|
||||
"Resources workflow step 4": "Start a Work/Run; Agent resolves immutable anchors and enforces the effective grants.",
|
||||
"Resources workflow step 5": "Use snapshots, events, and audit together to replay which resource context actually ran.",
|
||||
"Resources workflow title": "Resource-to-run flow",
|
||||
"Response": "Response",
|
||||
@@ -3295,7 +3295,7 @@
|
||||
"Sidebar Personal Settings": "Sidebar Personal Settings",
|
||||
"Sign in": "Sign in",
|
||||
"Sign In": "Sign In",
|
||||
"Sign in to operate Agnet deployments, inspect events, and audit SK snapshots for every tenant under your account.": "Sign in to operate Agnet deployments, inspect events, and audit SK snapshots for every tenant under your account.",
|
||||
"Sign in to operate Agent deployments, inspect events, and audit SK snapshots for every tenant under your account.": "Sign in to operate Agent deployments, inspect events, and audit SK snapshots for every tenant under your account.",
|
||||
"Sign in to operate deployments and review Git-bound snapshot anchors for tenants under your account.": "Sign in to bind repositories and cloud permissions, deploy sub-agents, and review Git-bound snapshot anchors for tenants under your account.",
|
||||
"Sign in to your workspace": "Sign in to your workspace",
|
||||
"Sign in with Passkey": "Sign in with Passkey",
|
||||
@@ -3364,10 +3364,10 @@
|
||||
"status": "status",
|
||||
"Status & Sync": "Status & Sync",
|
||||
"Status Code Mapping": "Status Code Mapping",
|
||||
"Status of every Agnet task you launched": "Status of every Agnet task you launched",
|
||||
"Status of every Agent task you launched": "Status of every Agent task you launched",
|
||||
"Status Page Slug": "Status Page Slug",
|
||||
"Status, errors and budget burn in one auditable stream.": "Status, errors and budget burn in one auditable stream.",
|
||||
"Status, latest activity and last update for every Agnet task you launched. Details live in the desktop client.": "Status, latest activity and last update for every Agnet task you launched. Details live in the desktop client.",
|
||||
"Status, latest activity and last update for every Agent task you launched. Details live in the desktop client.": "Status, latest activity and last update for every Agent task you launched. Details live in the desktop client.",
|
||||
"Status:": "Status:",
|
||||
"Stay": "Stay",
|
||||
"Stay tuned though!": "Stay tuned though!",
|
||||
@@ -3480,7 +3480,7 @@
|
||||
"Task Logs": "Task Logs",
|
||||
"Task not found. It may have been removed or was never created.": "Task not found. It may have been removed or was never created.",
|
||||
"Task overview": "Task overview",
|
||||
"Tasks appear here after you confirm the recommendation in the desktop client and launch Agnet.": "Tasks appear here after you confirm the recommendation in the desktop client and launch Agnet.",
|
||||
"Tasks appear here after you confirm the recommendation in the desktop client and launch Agent.": "Tasks appear here after you confirm the recommendation in the desktop client and launch Agent.",
|
||||
"Runtime diagnostics": "Runtime diagnostics",
|
||||
"Manager checks Runtime status separately from callback data, without mixing ordinary sub and swarm modes.": "Manager checks Runtime status separately from callback data, without mixing ordinary sub and swarm modes.",
|
||||
"runtime mode": "runtime mode",
|
||||
@@ -3685,7 +3685,7 @@
|
||||
"Total:": "Total:",
|
||||
"TPM": "TPM",
|
||||
"Trace delivery context snapshots by hash.": "Trace delivery context snapshots by hash.",
|
||||
"Track every Agnet work run by status, risk, budget, scope and secret_ref coverage.": "Track every Agnet work run by status, risk, budget, scope and secret_ref coverage.",
|
||||
"Track every Agent work run by status, risk, budget, scope and secret_ref coverage.": "Track every Agent work run by status, risk, budget, scope and secret_ref coverage.",
|
||||
"Track per-request consumption to power usage analytics. Keeping this on increases database writes.": "Track per-request consumption to power usage analytics. Keeping this on increases database writes.",
|
||||
"Track usage, costs and performance with real-time analytics": "Track usage, costs and performance with real-time analytics",
|
||||
"Tracks current account base limits and additional metered usage on Codex upstream.": "Tracks current account base limits and additional metered usage on Codex upstream.",
|
||||
@@ -4112,7 +4112,7 @@
|
||||
"This run will do": "This run will do",
|
||||
"No objective provided yet — go back to the Idea tab.": "No objective provided yet — go back to the Idea tab.",
|
||||
"Resources this run may use": "Resources this run may use",
|
||||
"No resources bound yet — Agnet will run with no external data access.": "No resources bound yet — Agnet will run with no external data access.",
|
||||
"No resources bound yet — Agent will run with no external data access.": "No resources bound yet — Agent will run with no external data access.",
|
||||
"no actions specified": "no actions specified",
|
||||
"This run will NOT do": "This run will NOT do",
|
||||
"Production deploys without client approval": "Production deploys without client approval",
|
||||
@@ -4143,7 +4143,7 @@
|
||||
"No audit events yet for this deployment.": "No audit events yet for this deployment.",
|
||||
"Connect project docs": "Connect project docs",
|
||||
"{{n}} doc source connected": "{{n}} doc source connected",
|
||||
"Link product requirements, design docs or wiki repos so Agnet has project context.": "Link product requirements, design docs or wiki repos so Agnet has project context.",
|
||||
"Link product requirements, design docs or wiki repos so Agent has project context.": "Link product requirements, design docs or wiki repos so Agent has project context.",
|
||||
"Connect SK skill packs": "Connect SK skill packs",
|
||||
"{{n}} SK source connected": "{{n}} SK source connected",
|
||||
"Pick a reusable skill / agent toolset repository, or skip.": "Pick a reusable skill / agent toolset repository, or skip.",
|
||||
|
||||
+9
-9
@@ -211,12 +211,12 @@
|
||||
"Inherits deployment defaults": "Inherits deployment defaults",
|
||||
"Add agent": "Add agent",
|
||||
"Agent": "Agent",
|
||||
"Agnet deployment created": "Agnet deployment created ({{deployment_id}})",
|
||||
"Agent deployment created": "Agent deployment created ({{deployment_id}})",
|
||||
"Allowed models comma": "Allowed model IDs (comma-separated)",
|
||||
"Budget caps": "Budget caps",
|
||||
"Cloud principals comma": "Cloud principals (comma-separated)",
|
||||
"Create Agnet deployment": "Create Agnet deployment",
|
||||
"Create Agnet deployment description": "Send the full orchestration plan in one request. Manager stores it; execution runs on Agnet.",
|
||||
"Create Agent deployment": "Create Agent deployment",
|
||||
"Create Agent deployment description": "Send the full orchestration plan in one request. Manager stores it; execution runs on Agent.",
|
||||
"Default model id": "Default model ID",
|
||||
"Deployment plan": "Deployment plan",
|
||||
"Deployment request failed": "Deployment request failed",
|
||||
@@ -236,7 +236,7 @@
|
||||
"Aggregated usage metrics and trend charts.": "Métriques d'utilisation agrégées et graphiques de tendances.",
|
||||
"aggregates 50+ AI providers behind one unified API. Manage access, track costs, and scale effortlessly.": "agrège plus de 50 fournisseurs IA derrière une API unifiée. Gérez l'accès, suivez les coûts et évoluez sans effort.",
|
||||
"Agile Minimal": "Agile Minimal",
|
||||
"Agnet orchestration": "Agnet orchestration",
|
||||
"Agent orchestration": "Agent orchestration",
|
||||
"AGPL v3.0 License": "Licence AGPL v3.0",
|
||||
"AI model testing environment": "Environnement de test de modèle IA",
|
||||
"AI models": "Modèles d'IA",
|
||||
@@ -566,7 +566,7 @@
|
||||
"Cancelled": "Annulé",
|
||||
"Cancelled at": "Annulé le",
|
||||
"Capture a reusable bundle of models, tags, or endpoints.": "Capturez un ensemble réutilisable de modèles, d'étiquettes ou de points de terminaison.",
|
||||
"Card-based view of every Agnet orchestration run with risk, budget, executor and live status.": "Card-based view of every Agnet orchestration run with risk, budget, executor and live status.",
|
||||
"Card-based view of every Agent orchestration run with risk, budget, executor and live status.": "Card-based view of every Agent orchestration run with risk, budget, executor and live status.",
|
||||
"Category Name": "Nom de la catégorie",
|
||||
"Category name is required": "Le nom de la catégorie est requis",
|
||||
"Category name must be less than 50 characters": "Le nom de la catégorie doit contenir moins de 50 caractères",
|
||||
@@ -1640,8 +1640,8 @@
|
||||
"Get Started": "Commencer",
|
||||
"Git binding": "Git binding",
|
||||
"Git sources": "Git sources",
|
||||
"Git sources binding explainer": "Skill (SK) definitions live in Git. Manager does not edit Markdown here—register code repos, SK tool repos, and refs via the Heicode client or your deployment plan sk_sources. Pass runtime and SK-policy parameters into Agnet when it starts each deployment; effective permissions are stored and enforced on Agnet. Below lists immutable snapshot anchors (Git commit / upload artifact) Agnet resolved for auditing.",
|
||||
"Git sources subtitle": "Configure bindings and deployment parameters for Agnet, then review snapshot anchors per deployment (enforcement lives on Agnet).",
|
||||
"Git sources binding explainer": "Skill (SK) definitions live in Git. Manager does not edit Markdown here—register code repos, SK tool repos, and refs via the Heicode client or your deployment plan sk_sources. Pass runtime and SK-policy parameters into Agent when it starts each deployment; effective permissions are stored and enforced on Agent. Below lists immutable snapshot anchors (Git commit / upload artifact) Agent resolved for auditing.",
|
||||
"Git sources subtitle": "Configure bindings and deployment parameters for Agent, then review snapshot anchors per deployment (enforcement lives on Agent).",
|
||||
"Git sources workflow title": "Typical setup flow",
|
||||
"Git sources workflow step 1": "Bind your team’s Git repositories that hold application code and delivery context.",
|
||||
"Git sources workflow step 2": "Bind SK tool repositories (skills registry) your tenant is allowed to draw from.",
|
||||
@@ -1918,7 +1918,7 @@
|
||||
"Leave empty to use system temp directory": "Laisser vide pour utiliser le répertoire temporaire",
|
||||
"Leave empty to use username": "Laissez vide pour utiliser le nom d'utilisateur",
|
||||
"Less": "Moins",
|
||||
"Lifecycle and policy events emitted by Agnet deployments. Correlate with deployment cards.": "Lifecycle and policy events emitted by Agnet deployments. Correlate with deployment cards.",
|
||||
"Lifecycle and policy events emitted by Agent deployments. Correlate with deployment cards.": "Lifecycle and policy events emitted by Agent deployments. Correlate with deployment cards.",
|
||||
"Light": "Clair",
|
||||
"Lightning Fast": "Extrêmement rapide",
|
||||
"Limit period": "Période de limite",
|
||||
@@ -3146,7 +3146,7 @@
|
||||
"Sidebar Personal Settings": "Paramètres personnels de la barre latérale",
|
||||
"Sign in": "Se connecter",
|
||||
"Sign In": "Se connecter",
|
||||
"Sign in to operate Agnet deployments, inspect events, and audit SK snapshots for every tenant under your account.": "Sign in to operate Agnet deployments, inspect events, and audit SK snapshots for every tenant under your account.",
|
||||
"Sign in to operate Agent deployments, inspect events, and audit SK snapshots for every tenant under your account.": "Sign in to operate Agent deployments, inspect events, and audit SK snapshots for every tenant under your account.",
|
||||
"Sign in to operate deployments and review Git-bound snapshot anchors for tenants under your account.": "Sign in to bind repositories and cloud permissions, deploy sub-agents, and review Git-bound snapshot anchors for tenants under your account.",
|
||||
"Sign in to your workspace": "Sign in to your workspace",
|
||||
"Sign in with Passkey": "Se connecter avec Passkey",
|
||||
|
||||
+9
-9
@@ -211,12 +211,12 @@
|
||||
"Inherits deployment defaults": "Inherits deployment defaults",
|
||||
"Add agent": "Add agent",
|
||||
"Agent": "Agent",
|
||||
"Agnet deployment created": "Agnet deployment created ({{deployment_id}})",
|
||||
"Agent deployment created": "Agent deployment created ({{deployment_id}})",
|
||||
"Allowed models comma": "Allowed model IDs (comma-separated)",
|
||||
"Budget caps": "Budget caps",
|
||||
"Cloud principals comma": "Cloud principals (comma-separated)",
|
||||
"Create Agnet deployment": "Create Agnet deployment",
|
||||
"Create Agnet deployment description": "Send the full orchestration plan in one request. Manager stores it; execution runs on Agnet.",
|
||||
"Create Agent deployment": "Create Agent deployment",
|
||||
"Create Agent deployment description": "Send the full orchestration plan in one request. Manager stores it; execution runs on Agent.",
|
||||
"Default model id": "Default model ID",
|
||||
"Deployment plan": "Deployment plan",
|
||||
"Deployment request failed": "Deployment request failed",
|
||||
@@ -236,7 +236,7 @@
|
||||
"Aggregated usage metrics and trend charts.": "集計された使用量メトリクスとトレンドチャート。",
|
||||
"aggregates 50+ AI providers behind one unified API. Manage access, track costs, and scale effortlessly.": "50以上のAIプロバイダーを統一APIで集約。アクセス管理、コスト追跡、スケーリングを簡単に。",
|
||||
"Agile Minimal": "Agile Minimal",
|
||||
"Agnet orchestration": "Agnet orchestration",
|
||||
"Agent orchestration": "Agent orchestration",
|
||||
"AGPL v3.0 License": "AGPL v3.0ライセンス",
|
||||
"AI model testing environment": "AIモデルテスト環境",
|
||||
"AI models": "AIモデル",
|
||||
@@ -566,7 +566,7 @@
|
||||
"Cancelled": "キャンセル",
|
||||
"Cancelled at": "キャンセル日時",
|
||||
"Capture a reusable bundle of models, tags, or endpoints.": "モデル、タグ、またはエンドポイントの再利用可能なバンドルを保存。",
|
||||
"Card-based view of every Agnet orchestration run with risk, budget, executor and live status.": "Card-based view of every Agnet orchestration run with risk, budget, executor and live status.",
|
||||
"Card-based view of every Agent orchestration run with risk, budget, executor and live status.": "Card-based view of every Agent orchestration run with risk, budget, executor and live status.",
|
||||
"Category Name": "分類名称",
|
||||
"Category name is required": "カテゴリ名は必須です",
|
||||
"Category name must be less than 50 characters": "カテゴリ名は50文字以内にしてください",
|
||||
@@ -1640,8 +1640,8 @@
|
||||
"Get Started": "開始する",
|
||||
"Git binding": "Git binding",
|
||||
"Git sources": "Git sources",
|
||||
"Git sources binding explainer": "Skill (SK) definitions live in Git. Manager does not edit Markdown here—register code repos, SK tool repos, and refs via the Heicode client or your deployment plan sk_sources. Pass runtime and SK-policy parameters into Agnet when it starts each deployment; effective permissions are stored and enforced on Agnet. Below lists immutable snapshot anchors (Git commit / upload artifact) Agnet resolved for auditing.",
|
||||
"Git sources subtitle": "Configure bindings and deployment parameters for Agnet, then review snapshot anchors per deployment (enforcement lives on Agnet).",
|
||||
"Git sources binding explainer": "Skill (SK) definitions live in Git. Manager does not edit Markdown here—register code repos, SK tool repos, and refs via the Heicode client or your deployment plan sk_sources. Pass runtime and SK-policy parameters into Agent when it starts each deployment; effective permissions are stored and enforced on Agent. Below lists immutable snapshot anchors (Git commit / upload artifact) Agent resolved for auditing.",
|
||||
"Git sources subtitle": "Configure bindings and deployment parameters for Agent, then review snapshot anchors per deployment (enforcement lives on Agent).",
|
||||
"Git sources workflow title": "Typical setup flow",
|
||||
"Git sources workflow step 1": "Bind your team’s Git repositories that hold application code and delivery context.",
|
||||
"Git sources workflow step 2": "Bind SK tool repositories (skills registry) your tenant is allowed to draw from.",
|
||||
@@ -1918,7 +1918,7 @@
|
||||
"Leave empty to use system temp directory": "空欄でシステムの一時ディレクトリを使用",
|
||||
"Leave empty to use username": "ユーザー名を使用するには空のままにしてください",
|
||||
"Less": "少ない",
|
||||
"Lifecycle and policy events emitted by Agnet deployments. Correlate with deployment cards.": "Lifecycle and policy events emitted by Agnet deployments. Correlate with deployment cards.",
|
||||
"Lifecycle and policy events emitted by Agent deployments. Correlate with deployment cards.": "Lifecycle and policy events emitted by Agent deployments. Correlate with deployment cards.",
|
||||
"Light": "ライト",
|
||||
"Lightning Fast": "超高速",
|
||||
"Limit period": "制限期間",
|
||||
@@ -3146,7 +3146,7 @@
|
||||
"Sidebar Personal Settings": "サイドバー個人設定",
|
||||
"Sign in": "ログイン",
|
||||
"Sign In": "ログイン",
|
||||
"Sign in to operate Agnet deployments, inspect events, and audit SK snapshots for every tenant under your account.": "Sign in to operate Agnet deployments, inspect events, and audit SK snapshots for every tenant under your account.",
|
||||
"Sign in to operate Agent deployments, inspect events, and audit SK snapshots for every tenant under your account.": "Sign in to operate Agent deployments, inspect events, and audit SK snapshots for every tenant under your account.",
|
||||
"Sign in to operate deployments and review Git-bound snapshot anchors for tenants under your account.": "Sign in to bind repositories and cloud permissions, deploy sub-agents, and review Git-bound snapshot anchors for tenants under your account.",
|
||||
"Sign in to your workspace": "Sign in to your workspace",
|
||||
"Sign in with Passkey": "Passkeyでログイン",
|
||||
|
||||
+9
-9
@@ -211,12 +211,12 @@
|
||||
"Inherits deployment defaults": "Inherits deployment defaults",
|
||||
"Add agent": "Add agent",
|
||||
"Agent": "Agent",
|
||||
"Agnet deployment created": "Agnet deployment created ({{deployment_id}})",
|
||||
"Agent deployment created": "Agent deployment created ({{deployment_id}})",
|
||||
"Allowed models comma": "Allowed model IDs (comma-separated)",
|
||||
"Budget caps": "Budget caps",
|
||||
"Cloud principals comma": "Cloud principals (comma-separated)",
|
||||
"Create Agnet deployment": "Create Agnet deployment",
|
||||
"Create Agnet deployment description": "Send the full orchestration plan in one request. Manager stores it; execution runs on Agnet.",
|
||||
"Create Agent deployment": "Create Agent deployment",
|
||||
"Create Agent deployment description": "Send the full orchestration plan in one request. Manager stores it; execution runs on Agent.",
|
||||
"Default model id": "Default model ID",
|
||||
"Deployment plan": "Deployment plan",
|
||||
"Deployment request failed": "Deployment request failed",
|
||||
@@ -236,7 +236,7 @@
|
||||
"Aggregated usage metrics and trend charts.": "Агрегированные метрики использования и графики трендов.",
|
||||
"aggregates 50+ AI providers behind one unified API. Manage access, track costs, and scale effortlessly.": "объединяет 50+ ИИ-провайдеров за единым API. Управляйте доступом, отслеживайте затраты и масштабируйтесь без усилий.",
|
||||
"Agile Minimal": "Agile Minimal",
|
||||
"Agnet orchestration": "Agnet orchestration",
|
||||
"Agent orchestration": "Agent orchestration",
|
||||
"AGPL v3.0 License": "Лицензия AGPL v3.0",
|
||||
"AI model testing environment": "Среда тестирования ИИ моделей",
|
||||
"AI models": "Модели ИИ",
|
||||
@@ -566,7 +566,7 @@
|
||||
"Cancelled": "Отменено",
|
||||
"Cancelled at": "Отменено",
|
||||
"Capture a reusable bundle of models, tags, or endpoints.": "Создайте повторно используемый набор моделей, тегов или конечных точек.",
|
||||
"Card-based view of every Agnet orchestration run with risk, budget, executor and live status.": "Card-based view of every Agnet orchestration run with risk, budget, executor and live status.",
|
||||
"Card-based view of every Agent orchestration run with risk, budget, executor and live status.": "Card-based view of every Agent orchestration run with risk, budget, executor and live status.",
|
||||
"Category Name": "Название категории",
|
||||
"Category name is required": "Название категории обязательно",
|
||||
"Category name must be less than 50 characters": "Название категории должно содержать менее 50 символов",
|
||||
@@ -1640,8 +1640,8 @@
|
||||
"Get Started": "Начать",
|
||||
"Git binding": "Git binding",
|
||||
"Git sources": "Git sources",
|
||||
"Git sources binding explainer": "Skill (SK) definitions live in Git. Manager does not edit Markdown here—register code repos, SK tool repos, and refs via the Heicode client or your deployment plan sk_sources. Pass runtime and SK-policy parameters into Agnet when it starts each deployment; effective permissions are stored and enforced on Agnet. Below lists immutable snapshot anchors (Git commit / upload artifact) Agnet resolved for auditing.",
|
||||
"Git sources subtitle": "Configure bindings and deployment parameters for Agnet, then review snapshot anchors per deployment (enforcement lives on Agnet).",
|
||||
"Git sources binding explainer": "Skill (SK) definitions live in Git. Manager does not edit Markdown here—register code repos, SK tool repos, and refs via the Heicode client or your deployment plan sk_sources. Pass runtime and SK-policy parameters into Agent when it starts each deployment; effective permissions are stored and enforced on Agent. Below lists immutable snapshot anchors (Git commit / upload artifact) Agent resolved for auditing.",
|
||||
"Git sources subtitle": "Configure bindings and deployment parameters for Agent, then review snapshot anchors per deployment (enforcement lives on Agent).",
|
||||
"Git sources workflow title": "Typical setup flow",
|
||||
"Git sources workflow step 1": "Bind your team’s Git repositories that hold application code and delivery context.",
|
||||
"Git sources workflow step 2": "Bind SK tool repositories (skills registry) your tenant is allowed to draw from.",
|
||||
@@ -1918,7 +1918,7 @@
|
||||
"Leave empty to use system temp directory": "Оставьте пустым для системного временного каталога",
|
||||
"Leave empty to use username": "Оставьте пустым, чтобы использовать имя пользователя",
|
||||
"Less": "Меньше",
|
||||
"Lifecycle and policy events emitted by Agnet deployments. Correlate with deployment cards.": "Lifecycle and policy events emitted by Agnet deployments. Correlate with deployment cards.",
|
||||
"Lifecycle and policy events emitted by Agent deployments. Correlate with deployment cards.": "Lifecycle and policy events emitted by Agent deployments. Correlate with deployment cards.",
|
||||
"Light": "Светлая",
|
||||
"Lightning Fast": "Молниеносно быстро",
|
||||
"Limit period": "Период ограничения",
|
||||
@@ -3146,7 +3146,7 @@
|
||||
"Sidebar Personal Settings": "Личные настройки боковой панели",
|
||||
"Sign in": "Войти",
|
||||
"Sign In": "Войти",
|
||||
"Sign in to operate Agnet deployments, inspect events, and audit SK snapshots for every tenant under your account.": "Sign in to operate Agnet deployments, inspect events, and audit SK snapshots for every tenant under your account.",
|
||||
"Sign in to operate Agent deployments, inspect events, and audit SK snapshots for every tenant under your account.": "Sign in to operate Agent deployments, inspect events, and audit SK snapshots for every tenant under your account.",
|
||||
"Sign in to operate deployments and review Git-bound snapshot anchors for tenants under your account.": "Sign in to bind repositories and cloud permissions, deploy sub-agents, and review Git-bound snapshot anchors for tenants under your account.",
|
||||
"Sign in to your workspace": "Sign in to your workspace",
|
||||
"Sign in with Passkey": "Войти с Passkey",
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user