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:
@@ -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",
|
||||
|
||||
+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.": "Chỉ số sử dụng tổng hợp và biểu đồ xu hướng.",
|
||||
"aggregates 50+ AI providers behind one unified API. Manage access, track costs, and scale effortlessly.": "tổng hợp hơn 50 nhà cung cấp AI sau một API thống nhất. Quản lý truy cập, theo dõi chi phí và mở rộng dễ dàng.",
|
||||
"Agile Minimal": "Agile Minimal",
|
||||
"Agnet orchestration": "Agnet orchestration",
|
||||
"Agent orchestration": "Agent orchestration",
|
||||
"AGPL v3.0 License": "Giấy phép AGPL v3.0",
|
||||
"AI model testing environment": "Môi trường thử nghiệm mô hình AI",
|
||||
"AI models": "mô hình AI",
|
||||
@@ -566,7 +566,7 @@
|
||||
"Cancelled": "Đã hủy",
|
||||
"Cancelled at": "Đã hủy lúc",
|
||||
"Capture a reusable bundle of models, tags, or endpoints.": "Đóng gói một bộ có thể tái sử dụng gồm các mô hình, thẻ hoặc điểm cuối.",
|
||||
"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": "Tên danh mục",
|
||||
"Category name is required": "Tên danh mục là bắt buộc",
|
||||
"Category name must be less than 50 characters": "Tên danh mục phải ít hơn 50 ký tự",
|
||||
@@ -1640,8 +1640,8 @@
|
||||
"Get Started": "Bắt đầu",
|
||||
"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": "Để trống để sử dụng thư mục tạm của hệ thống",
|
||||
"Leave empty to use username": "Để trống để sử dụng tên người dùng",
|
||||
"Less": "Ít hơn",
|
||||
"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": "Ánh sáng",
|
||||
"Lightning Fast": "Nhanh như chớp",
|
||||
"Limit period": "Thời hiệu",
|
||||
@@ -3146,7 +3146,7 @@
|
||||
"Sidebar Personal Settings": "Cài đặt cá nhân thanh bên",
|
||||
"Sign in": "Đăng nhập",
|
||||
"Sign In": "Đăng nhập",
|
||||
"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": "Đăng nhập bằng Passkey",
|
||||
|
||||
+38
-38
@@ -78,7 +78,7 @@
|
||||
"Actions": "操作",
|
||||
"active": "活跃",
|
||||
"Active": "生效",
|
||||
"Active Agnet sub-loops": "正在推进的 Agnet 子环节",
|
||||
"Active Agent sub-loops": "正在推进的 Agent 子环节",
|
||||
"Active Cache Count": "活跃缓存数",
|
||||
"Active code delivery runs across tenants": "当前各租户正在进行的代码交付任务",
|
||||
"Active Files": "活跃文件",
|
||||
@@ -176,20 +176,20 @@
|
||||
"After invalidating, this subscription will be immediately deactivated. Historical records are not affected. Continue?": "作废后该订阅将立即失效,历史记录不受影响。是否继续?",
|
||||
"After scanning, the binding will complete automatically": "扫描后,绑定将自动完成",
|
||||
"Agent": "子 Agent",
|
||||
"Agent declarations parsed from each Agnet deployment plan.": "从各 Agnet 部署计划中解析的 Agent 声明。",
|
||||
"Agent declarations parsed from each deployment plan.": "从各 Agnet 部署计划中解析的 Agent 声明。",
|
||||
"Agent declarations parsed from each Agent deployment plan.": "从各 Agent 部署计划中解析的 Agent 声明。",
|
||||
"Agent declarations parsed from each deployment plan.": "从各 Agent 部署计划中解析的 Agent 声明。",
|
||||
"Agent ID *": "代理 ID *",
|
||||
"Agentic development control plane": "智能体研发控制面",
|
||||
"Agents": "Agent",
|
||||
"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": "敏捷最小编队",
|
||||
"Agnet allowed models comma": "Agnet 运行模型 ID(逗号分隔)",
|
||||
"Agnet deployment created": "Agnet 部署已创建({{deployment_id}})",
|
||||
"Agnet launch staged. Continue the task in the desktop client.": "已准备启动 Agnet,请回到客户端继续推进任务。",
|
||||
"Agnet orchestration": "Agnet 编排",
|
||||
"Agnet runtime model": "Agnet 运行模型",
|
||||
"Agnet runtime model id": "Agnet 运行模型 ID",
|
||||
"Agent allowed models comma": "Agent 运行模型 ID(逗号分隔)",
|
||||
"Agent deployment created": "Agent 部署已创建({{deployment_id}})",
|
||||
"Agent launch staged. Continue the task in the desktop client.": "已准备启动 Agent,请回到客户端继续推进任务。",
|
||||
"Agent orchestration": "Agent 编排",
|
||||
"Agent runtime model": "Agent 运行模型",
|
||||
"Agent runtime model id": "Agent 运行模型 ID",
|
||||
"ago": "前",
|
||||
"AGPL v3.0 License": "AGPL v3.0 协议",
|
||||
"AI model testing environment": "AI模型测试环境",
|
||||
@@ -505,7 +505,7 @@
|
||||
"Browse and compare": "浏览和比较",
|
||||
"budget": "预算",
|
||||
"Budget & usage": "预算与用量",
|
||||
"Budget caps": "Agnet 运行上限",
|
||||
"Budget caps": "Agent 运行上限",
|
||||
"Budget tokens = max tokens × ratio. Accepts a decimal between 0.002 and 1. Recommended to keep aligned with upstream billing.": "预算令牌 = 最大令牌数 × 比例。接受 0.002 到 1 之间的十进制数。建议与上游计费保持一致。",
|
||||
"Budget tokens = max tokens × ratio. Accepts a decimal between 0.1 and 1.": "预算令牌 = 最大令牌数 × 比例。接受 0.1 到 1 之间的十进制数。",
|
||||
"Budget Tokens Ratio": "预算令牌比例",
|
||||
@@ -548,7 +548,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.": "以卡片形式展示每次 Agnet 编排运行,包含风险、运行上限、资源作用域与实时状态。",
|
||||
"Card-based view of every Agent orchestration run with risk, budget, executor and live status.": "以卡片形式展示每次 Agent 编排运行,包含风险、运行上限、资源作用域与实时状态。",
|
||||
"Category Name": "分类名称",
|
||||
"Category name is required": "分类名称不能为空",
|
||||
"Category name must be less than 50 characters": "分类名称不能超过 50 个字符",
|
||||
@@ -772,7 +772,7 @@
|
||||
"Configure your account preferences and integrations": "配置您的账户偏好和集成",
|
||||
"Confirm": "确认",
|
||||
"Confirm Action": "确认操作",
|
||||
"Confirm and launch Agnet": "确认并启动 Agnet",
|
||||
"Confirm and launch Agent": "确认并启动 Agent",
|
||||
"Confirm Batch Update": "确认批量更新",
|
||||
"Confirm Billing Conflicts": "确认账单冲突",
|
||||
"Confirm Changes": "确认更改",
|
||||
@@ -791,7 +791,7 @@
|
||||
"Confirm password": "确认密码",
|
||||
"Confirm Payment": "确认付款",
|
||||
"Confirm recommendation summary": "确认推荐摘要",
|
||||
"Confirm scope, risk and budget before launching Agnet": "在启动 Agnet 之前,确认范围、风险和预算",
|
||||
"Confirm scope, risk and budget before launching Agent": "在启动 Agent 之前,确认范围、风险和预算",
|
||||
"Confirm Selection": "确认选择",
|
||||
"Confirm settings and finish setup": "确认设置并完成安装",
|
||||
"Confirm Unbind": "确认解绑",
|
||||
@@ -885,8 +885,8 @@
|
||||
"Create a new code delivery run with checks.": "创建新的带检查项的代码交付任务。",
|
||||
"Create a new user group to configure ratio overrides for.": "创建一个新的用户分组来配置比例覆盖。",
|
||||
"Create account": "创建账户",
|
||||
"Create Agnet deployment": "创建 Agnet 部署",
|
||||
"Create Agnet deployment description": "向 Agnet 提交编排计划。这里的模型选择是运行策略,不是计费配置。",
|
||||
"Create Agent deployment": "创建 Agent 部署",
|
||||
"Create Agent deployment description": "向 Agent 提交编排计划。这里的模型选择是运行策略,不是计费配置。",
|
||||
"Create an account": "创建一个账户",
|
||||
"Create and review invite or credit codes.": "创建和审查邀请或信用代码。",
|
||||
"Create API Key": "创建 API 密钥",
|
||||
@@ -1042,7 +1042,7 @@
|
||||
"Deployments": "部署",
|
||||
"Desc": "描述",
|
||||
"Describe": "图生文",
|
||||
"Describe the product, feature, or fix. Heicode will draft the task, recommend resources, and dispatch the Agnet team.": "用自然语言描述产品、功能或修复点。Heicode 会生成任务草案、推荐需要的资源,并调度 Agnet 团队执行。",
|
||||
"Describe the product, feature, or fix. Heicode will draft the task, recommend resources, and dispatch the Agent team.": "用自然语言描述产品、功能或修复点。Heicode 会生成任务草案、推荐需要的资源,并调度 Agent 团队执行。",
|
||||
"Describe this model...": "描述此模型...",
|
||||
"Describe this vendor...": "描述此供应商...",
|
||||
"Describe what this work should achieve.": "描述这次工作要达成什么。",
|
||||
@@ -1183,7 +1183,7 @@
|
||||
"e.g., us-central1 or JSON format for model-specific regions": "例如,us-central1 或模型特定区域的 JSON 格式",
|
||||
"e.g., v2.1": "例如,v2.1",
|
||||
"Each backup code can only be used once.": "每个备份代码只能使用一次。",
|
||||
"Each card maps one child Agnet role to a runtime model and one bounded resource grant.": "每张卡把一个子 Agnet 角色映射到运行模型和一个受限资源授权。",
|
||||
"Each card maps one child Agent role to a runtime model and one bounded resource grant.": "每张卡把一个子 Agent 角色映射到运行模型和一个受限资源授权。",
|
||||
"Each item must be an object with a single 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.": "每行代表一个关键词。留空以禁用列表,但保留开关状态。",
|
||||
@@ -1679,8 +1679,8 @@
|
||||
"Get Started": "开始使用",
|
||||
"Git binding": "Git 绑定",
|
||||
"Git sources": "Git 来源",
|
||||
"Git sources binding explainer": "Skill(SK)定义以 Git 为唯一事实源。Manager 不在此编辑 Markdown:请在 Heicode 客户端或部署计划的 sk_sources 中登记仓库与引用;运行时与 SK 策略等参数在 Agnet 拉起编队/子 Agent 时传入。有效权限与策略落账在 Agnet 侧并由其执行;Manager 仅展示 Agnet 回传的不可变快照锚点(Git commit / 上传制品)供审计。",
|
||||
"Git sources subtitle": "在部署参数中把绑定与策略交给 Agnet,在此按部署查看快照锚点(执行权在 Agnet)。",
|
||||
"Git sources binding explainer": "Skill(SK)定义以 Git 为唯一事实源。Manager 不在此编辑 Markdown:请在 Heicode 客户端或部署计划的 sk_sources 中登记仓库与引用;运行时与 SK 策略等参数在 Agent 拉起编队/子 Agent 时传入。有效权限与策略落账在 Agent 侧并由其执行;Manager 仅展示 Agent 回传的不可变快照锚点(Git commit / 上传制品)供审计。",
|
||||
"Git sources subtitle": "在部署参数中把绑定与策略交给 Agent,在此按部署查看快照锚点(执行权在 Agent)。",
|
||||
"Git sources workflow step 1": "绑定团队用于应用代码与交付上下文的 Git 仓库。",
|
||||
"Git sources workflow step 2": "绑定 SK 工具仓库(技能来源),声明本次运行可通过 Resource Grant 引用的工具集。",
|
||||
"Git sources workflow step 3": "为子 Agent 分配云上资源与权限(如独立虚拟机、角色与其他访问边界)。",
|
||||
@@ -1960,7 +1960,7 @@
|
||||
"Last updated:": "上次更新时间:",
|
||||
"Last Used": "最后使用时间",
|
||||
"Last used:": "上次使用时间:",
|
||||
"Launch Agnet": "启动 Agnet",
|
||||
"Launch Agent": "启动 Agent",
|
||||
"Launching…": "正在准备启动…",
|
||||
"Layout": "布局",
|
||||
"Learn more": "了解更多",
|
||||
@@ -1980,7 +1980,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.": "Agnet 部署发出的生命周期与策略事件,可与部署卡片关联。",
|
||||
"Lifecycle and policy events emitted by Agent deployments. Correlate with deployment cards.": "Agent 部署发出的生命周期与策略事件,可与部署卡片关联。",
|
||||
"Light": "浅色",
|
||||
"Lightning Fast": "极速",
|
||||
"Limit period": "限制周期",
|
||||
@@ -2025,7 +2025,7 @@
|
||||
"Logo": "徽标",
|
||||
"Logo URL": "徽标 URL",
|
||||
"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.": "长期凭证保存在密钥保管器。Agnet 执行时只申请短期、最小权限凭证。生产部署等高危操作需在客户端审批。",
|
||||
"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.": "长期凭证保存在密钥保管器。Agent 执行时只申请短期、最小权限凭证。生产部署等高危操作需在客户端审批。",
|
||||
"m": "分钟",
|
||||
"Maintain a list of common questions for the dashboard help panel": "维护仪表板帮助面板的常见问题列表",
|
||||
"Maintenance": "维护",
|
||||
@@ -2385,7 +2385,7 @@
|
||||
"No Sync": "不同步",
|
||||
"No system announcements": "暂无系统公告",
|
||||
"No tasks yet": "暂无任务",
|
||||
"No tasks yet. Capture an idea above, then confirm the recommendation in the desktop client to launch Agnet.": "暂无任务。在上方暂存一个想法,然后在客户端确认推荐摘要,启动 Agnet。",
|
||||
"No tasks yet. Capture an idea above, then confirm the recommendation in the desktop client to launch Agent.": "暂无任务。在上方暂存一个想法,然后在客户端确认推荐摘要,启动 Agent。",
|
||||
"No token found.": "未找到令牌。",
|
||||
"No tools configured": "未配置工具",
|
||||
"No Upgrade": "不升级",
|
||||
@@ -3043,7 +3043,7 @@
|
||||
"Resources workflow step 1": "绑定项目代码、SK、文档或云资源 metadata,不暴露原始凭据。",
|
||||
"Resources workflow step 2": "密钥材料留在 secret_ref 后面;Manager 只展示引用和状态。",
|
||||
"Resources workflow step 3": "为运行 manifest 分配范围、允许路径/动作、运行策略和预算。",
|
||||
"Resources workflow step 4": "启动 Work/Run;Agnet 解析不可变锚点并执行有效授权。",
|
||||
"Resources workflow step 4": "启动 Work/Run;Agent 解析不可变锚点并执行有效授权。",
|
||||
"Resources workflow step 5": "结合快照、事件和审计回放实际运行的资源上下文。",
|
||||
"Resources workflow title": "资源到运行流程",
|
||||
"Response": "响应",
|
||||
@@ -3299,7 +3299,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.": "登录后即可操作 Agnet 部署、查看事件,并审计你账号下各租户的 SK 快照。",
|
||||
"Sign in to operate Agent deployments, inspect events, and audit SK snapshots for every tenant under your account.": "登录后即可操作 Agent 部署、查看事件,并审计你账号下各租户的 SK 快照。",
|
||||
"Sign in to operate deployments and review Git-bound snapshot anchors for tenants under your account.": "登录后即可绑定仓库与云上权限、部署子 Agent,并审阅你账号下各租户与 Git 绑定的快照锚点。",
|
||||
"Sign in to your workspace": "登录到你的工作空间",
|
||||
"Sign in with Passkey": "使用 Passkey 登录",
|
||||
@@ -3368,10 +3368,10 @@
|
||||
"status": "状态",
|
||||
"Status & Sync": "状态与同步",
|
||||
"Status Code Mapping": "状态码映射",
|
||||
"Status of every Agnet task you launched": "查看你启动的每个 Agnet 任务的状态",
|
||||
"Status of every Agent task you launched": "查看你启动的每个 Agent 任务的状态",
|
||||
"Status Page Slug": "状态页面 Slug",
|
||||
"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.": "查看每个启动的 Agnet 任务的状态、最近动态和更新时间。详细对话与产物在客户端中查看。",
|
||||
"Status, latest activity and last update for every Agent task you launched. Details live in the desktop client.": "查看每个启动的 Agent 任务的状态、最近动态和更新时间。详细对话与产物在客户端中查看。",
|
||||
"Status:": "状态:",
|
||||
"Stay": "留下来",
|
||||
"Stay tuned though!": "敬请期待!",
|
||||
@@ -3484,7 +3484,7 @@
|
||||
"Task Logs": "任务日志",
|
||||
"Task not found. It may have been removed or was never created.": "任务不存在,可能已被删除或从未创建。",
|
||||
"Task overview": "任务总览",
|
||||
"Tasks appear here after you confirm the recommendation in the desktop client and launch Agnet.": "在客户端确认推荐摘要并启动 Agnet 后,任务会出现在这里。",
|
||||
"Tasks appear here after you confirm the recommendation in the desktop client and launch Agent.": "在客户端确认推荐摘要并启动 Agent 后,任务会出现在这里。",
|
||||
"Runtime diagnostics": "运行时诊断",
|
||||
"Manager checks Runtime status separately from callback data, without mixing ordinary sub and swarm modes.": "Manager 会独立检查运行时状态,并明确区分普通 sub 与蜂群模式,不混用回调数据。",
|
||||
"runtime mode": "运行模式",
|
||||
@@ -3685,7 +3685,7 @@
|
||||
"Total:": "总计:",
|
||||
"TPM": "TPM",
|
||||
"Trace delivery context snapshots by hash.": "按哈希追溯交付上下文快照。",
|
||||
"Track every Agnet work run by status, risk, budget, scope and secret_ref coverage.": "按状态、风险、预算、作用域和 secret_ref 覆盖情况跟踪每次 Agnet 工作运行。",
|
||||
"Track every Agent work run by status, risk, budget, scope and secret_ref coverage.": "按状态、风险、预算、作用域和 secret_ref 覆盖情况跟踪每次 Agent 工作运行。",
|
||||
"Track per-request consumption to power usage analytics. Keeping this on increases database writes.": "跟踪每个请求的消耗,以支持使用情况分析。保持开启会增加数据库写入。",
|
||||
"Track usage, costs and performance with real-time analytics": "通过实时分析跟踪用量、成本和性能",
|
||||
"Tracks current account base limits and additional metered usage on Codex upstream.": "跟踪当前账号在 Codex 上游的基础限额与附加计费用量。",
|
||||
@@ -4139,11 +4139,11 @@
|
||||
"Reviewer role": "评审",
|
||||
"Ops role": "运维",
|
||||
"Builder role": "构建",
|
||||
"Product Agnet": "产品智能体",
|
||||
"Frontend Agnet": "前端智能体",
|
||||
"Backend Agnet": "后端智能体",
|
||||
"Reviewer Agnet": "评审智能体",
|
||||
"Ops Agnet": "运维智能体",
|
||||
"Product Agent": "产品智能体",
|
||||
"Frontend Agent": "前端智能体",
|
||||
"Backend Agent": "后端智能体",
|
||||
"Reviewer Agent": "评审智能体",
|
||||
"Ops Agent": "运维智能体",
|
||||
"Git repository": "Git 仓库",
|
||||
"SK skill pack": "SK 技能包",
|
||||
"Cloud account": "云账号",
|
||||
@@ -4179,7 +4179,7 @@
|
||||
"All levels": "全部级别",
|
||||
"Info": "信息",
|
||||
"Active credential leases": "有效凭证租约",
|
||||
"Approve or reject high-risk Agnet operations before credentials are leased.": "在下发凭证租约前,审批或拒绝高危 Agnet 操作。",
|
||||
"Approve or reject high-risk Agent operations before credentials are leased.": "在下发凭证租约前,审批或拒绝高危 Agent 操作。",
|
||||
"No pending approvals": "暂无待审批项",
|
||||
"High-risk operations will appear here.": "高危操作会显示在这里。",
|
||||
"No active credential leases": "暂无有效凭证租约",
|
||||
@@ -4195,14 +4195,14 @@
|
||||
"Approved from Manager audit page": "从管理端审计页批准",
|
||||
"Rejected from Manager audit page": "从管理端审计页拒绝",
|
||||
"Revoked from Manager audit page": "从管理端审计页撤销",
|
||||
"Connect code, docs and cloud resources for the current task, then confirm the recommendation before launching Agnet.": "为当前任务绑定代码、文档和云资源,然后确认推荐方案再启动 Agnet。",
|
||||
"Connect code, docs and cloud resources for the current task, then confirm the recommendation before launching Agent.": "为当前任务绑定代码、文档和云资源,然后确认推荐方案再启动 Agent。",
|
||||
"Plaintext credentials are never shown. The secret_ref column is a vault pointer, not the secret itself.": "永远不展示明文密钥。表中的密钥引用只是密钥保管器里的指针,不是密钥本体。",
|
||||
"Confirm before launch": "确认后启动",
|
||||
"Heicode summarises what this run will do, what resources it can use, what stays off-limits, and the expected cost. Confirm to launch.": "Heicode 已汇总本次会做什么、能用哪些资源、哪些事情不会做、以及预计消耗。请确认后启动。",
|
||||
"This run will do": "本次会做",
|
||||
"No objective provided yet — go back to the Idea tab.": "还没填写目标 — 请回到「想法」标签页补充。",
|
||||
"Resources this run may use": "本次允许使用的资源",
|
||||
"No resources bound yet — Agnet will run with no external data access.": "还没绑定资源 — Agnet 将在无外部数据访问的情况下运行。",
|
||||
"No resources bound yet — Agent will run with no external data access.": "还没绑定资源 — Agent 将在无外部数据访问的情况下运行。",
|
||||
"no actions specified": "未指定动作",
|
||||
"This run will NOT do": "本次不会做",
|
||||
"Production deploys without client approval": "未经客户端审批的生产部署",
|
||||
@@ -4233,7 +4233,7 @@
|
||||
"No audit events yet for this deployment.": "该部署暂无审计事件。",
|
||||
"Connect project docs": "绑定项目文档",
|
||||
"{{n}} doc source connected": "已绑定 {{n}} 份项目文档",
|
||||
"Link product requirements, design docs or wiki repos so Agnet has project context.": "绑定需求文档、设计文档或 wiki 仓库,让 Agnet 拿到项目上下文。",
|
||||
"Link product requirements, design docs or wiki repos so Agent has project context.": "绑定需求文档、设计文档或 wiki 仓库,让 Agent 拿到项目上下文。",
|
||||
"Connect SK skill packs": "绑定 SK 技能包",
|
||||
"{{n}} SK source connected": "已绑定 {{n}} 个 SK 来源",
|
||||
"Pick a reusable skill / agent toolset repository, or skip.": "选一个可复用的技能包 / Agent 工具集仓库,也可以跳过。",
|
||||
|
||||
Vendored
+1
-1
@@ -79,7 +79,7 @@ api.interceptors.response.use(
|
||||
const url = String(error?.config?.url || '')
|
||||
|
||||
// Only treat 401 on identity/self endpoints as a real session expiry.
|
||||
// Admin-only endpoints (/api/agnet, /api/channel, etc.) returning 401 for
|
||||
// Admin-only endpoints (/api/agent, /api/channel, etc.) returning 401 for
|
||||
// non-admin users should NOT reset the session.
|
||||
const isIdentityEndpoint =
|
||||
url.includes('/api/user/self') ||
|
||||
|
||||
+11
-11
@@ -15,12 +15,12 @@ import { api } from '@/lib/api'
|
||||
* §2 /api/resources/* ResourceBinding (5)
|
||||
* §3 /api/resource-grants/* ResourceGrant (4)
|
||||
* §4 /api/user/heicode/* NewAPI metadata passthrough (4)
|
||||
* §5 /api/agnet/* Agnet platform stub (12 mock endpoints)
|
||||
* §5 /api/agent/* Agent platform stub (12 mock endpoints)
|
||||
* §6 /api/user/tasks/* HeicodeTask orchestration (5)
|
||||
*
|
||||
* The shapes below are what mcp-server actually returns — they intentionally
|
||||
* differ from the Heicode-local /api/agnet/* shapes used in earlier UI work.
|
||||
* That earlier work treated AgnetDeployment as the user-facing task object;
|
||||
* differ from the Heicode-local /api/agent/* shapes used in earlier UI work.
|
||||
* That earlier work treated AgentDeployment as the user-facing task object;
|
||||
* the contract document is clear that HeicodeTask (§6) is the right object.
|
||||
*/
|
||||
|
||||
@@ -227,7 +227,7 @@ export async function createDeploymentDraftFromHeicodeTask(
|
||||
options: HeicodeTaskDeploymentDraftOptions = {}
|
||||
): Promise<HeicodeTaskDeploymentDraft> {
|
||||
const res = await api.post<Envelope<HeicodeTaskDeploymentDraft>>(
|
||||
`/api/agnet/user/tasks/${encodeURIComponent(task.id)}/deployment-draft`,
|
||||
`/api/agent/user/tasks/${encodeURIComponent(task.id)}/deployment-draft`,
|
||||
{
|
||||
task,
|
||||
sub_mode: options.sub_mode ?? 'agile',
|
||||
@@ -339,10 +339,10 @@ export async function getHeicodeLogs(
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// §5 Agnet stub — only the endpoints UI needs.
|
||||
// §5 Agent stub — only the endpoints UI needs.
|
||||
// =============================================================================
|
||||
|
||||
export type McpAgnetDeployment = {
|
||||
export type McpAgentDeployment = {
|
||||
deployment_id: string
|
||||
status: string
|
||||
phase?: string
|
||||
@@ -360,11 +360,11 @@ export type McpAgnetDeployment = {
|
||||
}
|
||||
}
|
||||
|
||||
export async function listMcpAgnetDeployments(params?: {
|
||||
export async function listMcpAgentDeployments(params?: {
|
||||
status?: string
|
||||
limit?: number
|
||||
binding_scope?: string
|
||||
}): Promise<McpAgnetDeployment[]> {
|
||||
}): Promise<McpAgentDeployment[]> {
|
||||
const qs = new URLSearchParams()
|
||||
if (params?.status) qs.set('status', params.status)
|
||||
if (params?.limit != null) qs.set('limit', String(params.limit))
|
||||
@@ -372,8 +372,8 @@ export async function listMcpAgnetDeployments(params?: {
|
||||
const suffix = qs.toString() ? `?${qs.toString()}` : ''
|
||||
try {
|
||||
const env = await mcpFetch<
|
||||
Envelope<{ items?: McpAgnetDeployment[]; total?: number }>
|
||||
>(`/api/agnet/deployments${suffix}`)
|
||||
Envelope<{ items?: McpAgentDeployment[]; total?: number }>
|
||||
>(`/api/agent/deployments${suffix}`)
|
||||
return env.data?.items ?? []
|
||||
} catch {
|
||||
return []
|
||||
@@ -639,7 +639,7 @@ export async function listMcpAuditLogs(params?: {
|
||||
try {
|
||||
const env = await mcpFetch<
|
||||
Envelope<{ items?: McpAuditEntry[]; total?: number }>
|
||||
>(`/api/agnet/audit-logs${suffix}`)
|
||||
>(`/api/agent/audit-logs${suffix}`)
|
||||
return env.data?.items ?? []
|
||||
} catch {
|
||||
return []
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { createFileRoute } from '@tanstack/react-router'
|
||||
import { AgnetAgentsPage } from '@/features/agnet-console/pages'
|
||||
import { AgentAgentsPage } from '@/features/agent-console/pages'
|
||||
|
||||
export const Route = createFileRoute('/_authenticated/agents/')({
|
||||
component: AgnetAgentsPage,
|
||||
component: AgentAgentsPage,
|
||||
})
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { createFileRoute } from '@tanstack/react-router'
|
||||
import { AgnetAuditPage } from '@/features/agnet-console/pages'
|
||||
import { AgentAuditPage } from '@/features/agent-console/pages'
|
||||
|
||||
export const Route = createFileRoute('/_authenticated/audit/')({
|
||||
component: AgnetAuditPage,
|
||||
component: AgentAuditPage,
|
||||
})
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { createFileRoute } from '@tanstack/react-router'
|
||||
import { AgnetDeploymentsPage } from '@/features/agnet-console/pages'
|
||||
import { AgentDeploymentsPage } from '@/features/agent-console/pages'
|
||||
|
||||
export const Route = createFileRoute('/_authenticated/deployments/')({
|
||||
component: AgnetDeploymentsPage,
|
||||
component: AgentDeploymentsPage,
|
||||
})
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { createFileRoute } from '@tanstack/react-router'
|
||||
import { AgnetEventsPage } from '@/features/agnet-console/pages'
|
||||
import { AgentEventsPage } from '@/features/agent-console/pages'
|
||||
|
||||
export const Route = createFileRoute('/_authenticated/events/')({
|
||||
component: AgnetEventsPage,
|
||||
component: AgentEventsPage,
|
||||
})
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { createFileRoute } from '@tanstack/react-router'
|
||||
import { AgnetSKSourcesPage } from '@/features/agnet-console/pages'
|
||||
import { AgentSKSourcesPage } from '@/features/agent-console/pages'
|
||||
|
||||
export const Route = createFileRoute('/_authenticated/sk-sources/')({
|
||||
component: AgnetSKSourcesPage,
|
||||
component: AgentSKSourcesPage,
|
||||
})
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { createFileRoute } from '@tanstack/react-router'
|
||||
import { AgnetTemplatesPage } from '@/features/agnet-console/pages'
|
||||
import { AgentTemplatesPage } from '@/features/agent-console/pages'
|
||||
|
||||
export const Route = createFileRoute('/_authenticated/templates/')({
|
||||
component: AgnetTemplatesPage,
|
||||
component: AgentTemplatesPage,
|
||||
})
|
||||
|
||||
+1
-1
@@ -2,7 +2,7 @@
|
||||
* Heicode Manager design tokens
|
||||
*
|
||||
* One source of truth for radii, spacing, elevations, semantic colors and
|
||||
* surface treatments used by the Agnet command-center UI. Pages and components
|
||||
* surface treatments used by the Agent command-center UI. Pages and components
|
||||
* should reference these tokens (via Tailwind or CSS variables) instead of
|
||||
* declaring ad-hoc values.
|
||||
* ==========================================================================*/
|
||||
|
||||
Reference in New Issue
Block a user