feat: add Z12/Z13 bind APIs, stock import, and sync docs

Enable auto default sync channels on agent activate, bind-code/phone confirm flows, publish ALTER, and align admin/yuheng docs with the production bind path.
This commit is contained in:
whm
2026-08-05 11:47:20 +08:00
parent b04b180d30
commit cb56e6847e
31 changed files with 1882 additions and 91 deletions

View File

@@ -79,6 +79,10 @@ type Store interface {
HasAppAccess(ctx context.Context, agentID int64, slug string) (bool, error)
// GrantAppSlug 将 slug 写入智能体可访问模块(幂等)。新建发布时自动授权用。
GrantAppSlug(ctx context.Context, agentID int64, slug string) error
// FindByHostKey 按宿主机 key 查找跨租户Z13 绑定用)。
FindByHostKey(ctx context.Context, hostKey string) (*Account, error)
// AttachSyncBind 写入同步落点可改挂租户并激活Z12/Z13
AttachSyncBind(ctx context.Context, agentID, tenantID int64, channelID, onlineDBID, databaseName string, activate bool) (*Account, error)
}
type memAcc struct {
@@ -341,6 +345,42 @@ func (s *MemoryStore) GrantAppSlug(_ context.Context, agentID int64, slug string
return nil
}
func (s *MemoryStore) FindByHostKey(_ context.Context, hostKey string) (*Account, error) {
hostKey = strings.TrimSpace(hostKey)
if hostKey == "" {
return nil, fmt.Errorf("host_key required")
}
s.mu.Lock()
defer s.mu.Unlock()
for _, a := range s.byID {
if a.HostKey == hostKey {
cp := cloneAcc(&a.Account)
return &cp, nil
}
}
return nil, fmt.Errorf("agent not found")
}
func (s *MemoryStore) AttachSyncBind(_ context.Context, agentID, tenantID int64, channelID, onlineDBID, databaseName string, activate bool) (*Account, error) {
s.mu.Lock()
defer s.mu.Unlock()
a, ok := s.byID[agentID]
if !ok {
return nil, fmt.Errorf("agent not found")
}
if tenantID > 0 {
a.TenantID = tenantID
}
a.ChannelID = strings.TrimSpace(channelID)
a.OnlineDBID = strings.TrimSpace(onlineDBID)
a.DatabaseName = strings.TrimSpace(databaseName)
if activate {
a.Status = StatusActive
}
cp := cloneAcc(&a.Account)
return &cp, nil
}
type PostgresStore struct {
DB *sql.DB
}
@@ -653,6 +693,59 @@ INSERT INTO platform_meta.agent_app_grants(agent_id, slug) VALUES($1,$2)`, agent
return err
}
func (s *PostgresStore) FindByHostKey(ctx context.Context, hostKey string) (*Account, error) {
hostKey = strings.TrimSpace(hostKey)
if hostKey == "" {
return nil, fmt.Errorf("host_key required")
}
var a Account
var last sql.NullTime
err := s.DB.QueryRowContext(ctx, `
SELECT agent_id, tenant_id, name, client_id, COALESCE(host_key,''), COALESCE(role_id,0),
COALESCE(channel_id,''), COALESCE(online_db_id,''), COALESCE(database_name,''),
status, created_by, created_at, last_token_at
FROM platform_meta.agent_accounts WHERE host_key=$1
ORDER BY agent_id DESC LIMIT 1`, hostKey,
).Scan(&a.AgentID, &a.TenantID, &a.Name, &a.ClientID, &a.HostKey, &a.RoleID,
&a.ChannelID, &a.OnlineDBID, &a.DatabaseName,
&a.Status, &a.CreatedBy, &a.CreatedAt, &last)
if errors.Is(err, sql.ErrNoRows) {
return nil, fmt.Errorf("agent not found")
}
if err != nil {
return nil, err
}
if last.Valid {
t := last.Time
a.LastTokenAt = &t
}
perms, slugs, err := s.loadKids(ctx, a.AgentID)
if err != nil {
return nil, err
}
a.Perms, a.AppSlugs = perms, slugs
return &a, nil
}
func (s *PostgresStore) AttachSyncBind(ctx context.Context, agentID, tenantID int64, channelID, onlineDBID, databaseName string, activate bool) (*Account, error) {
statusSQL := ""
args := []any{tenantID, strings.TrimSpace(channelID), strings.TrimSpace(onlineDBID), strings.TrimSpace(databaseName), agentID}
if activate {
statusSQL = ", status='active'"
}
res, err := s.DB.ExecContext(ctx, `
UPDATE platform_meta.agent_accounts SET tenant_id=$1, channel_id=$2, online_db_id=$3, database_name=$4`+statusSQL+`
WHERE agent_id=$5`, args...)
if err != nil {
return nil, err
}
n, _ := res.RowsAffected()
if n == 0 {
return nil, fmt.Errorf("agent not found")
}
return s.Get(ctx, tenantID, agentID)
}
func (s *PostgresStore) loadKids(ctx context.Context, agentID int64) ([]string, []string, error) {
prows, err := s.DB.QueryContext(ctx, `SELECT perm FROM platform_meta.agent_permissions WHERE agent_id=$1`, agentID)
if err != nil {