feat: complete device onboarding and mobile binding workflows
ci / verify (push) Canceled after 0s

This commit is contained in:
Codex Local
2026-09-12 08:57:29 +08:00
parent 7846f30d0b
commit 98fb2965d0
116 changed files with 15669 additions and 64 deletions
+22 -5
View File
@@ -32,10 +32,12 @@ func (s *Service) CreateHardwareAgent(ctx context.Context, identity deviceauth.I
request := idempotency.Request{TenantID: &tenantID, ActorKey: "device:" + identity.PublicID,
Scope: "POST /device/v3/agent-sessions", Key: idempotencyKey,
RequestHash: idempotency.Hash(rawRequest), ExpiresAfter: 24 * time.Hour}
if cached, found, err := s.executor.Lookup(ctx, request); err != nil {
return HardwareAgentCreateResponse{}, false, err
} else if found {
return s.finalizeHardwareCreate(cached)
if identity.EnrollmentMode != "physical_confirm" {
if cached, found, err := s.executor.Lookup(ctx, request); err != nil {
return HardwareAgentCreateResponse{}, false, err
} else if found {
return s.finalizeHardwareCreate(cached)
}
}
memoryVector, memoryPolicy, err := s.prepareMemoryQuery(ctx, identity, input.RequestedGoal)
if err != nil {
@@ -66,7 +68,7 @@ func (s *Service) CreateHardwareAgent(ctx context.Context, identity deviceauth.I
}
}
}
result, err := s.executor.Execute(ctx, request, func(operationContext context.Context, tx pgx.Tx) (idempotency.Result, error) {
result, err := s.executor.ExecuteGuarded(ctx, request, onboardingCreateGuard(identity, input), func(operationContext context.Context, tx pgx.Tx) (idempotency.Result, error) {
now := s.now().UTC()
created, internalID, createErr := s.repository.Create(operationContext, tx, identity, CreateInput{
DeviceTime: input.DeviceTime, MonotonicMS: input.MonotonicMS, Network: input.Network,
@@ -86,9 +88,18 @@ func (s *Service) CreateHardwareAgent(ctx context.Context, identity deviceauth.I
if createErr != nil {
return idempotency.Result{}, createErr
}
if err := attachOnboardingConversation(operationContext, tx, identity, input, internalID); err != nil {
return idempotency.Result{}, err
}
if created.Hardware == nil {
return idempotency.Result{}, errors.New("hardware agent runtime was not allocated")
}
if input.Intent == "activation_test" {
created.Hardware.Session.MaximumDurationSeconds = 60
if created.Hardware.Session.AllocationExpiresAt.After(now.Add(time.Minute)) {
created.Hardware.Session.AllocationExpiresAt = now.Add(time.Minute)
}
}
body, marshalErr := json.Marshal(created.Hardware)
if marshalErr != nil {
return idempotency.Result{}, marshalErr
@@ -144,6 +155,9 @@ func (s *Service) ProcessHardwareAgentEvent(ctx context.Context, identity device
return HardwareAgentEventResponse{}, err
}
defer tx.Rollback(context.Background())
if err := guardOnboardingEvent(ctx, tx, identity, conversationPublicID, input); err != nil {
return HardwareAgentEventResponse{}, err
}
now := s.now().UTC()
var internalID, tenantID, providerSessionRef, nonceDigest, expectedBotID, expectedTransport, expectedConfigDigest string
var lastEventSequence int64
@@ -248,6 +262,9 @@ func (s *Service) ProcessHardwareAgentEvent(ctx context.Context, identity device
currentStatus = conversation.StatusFailed
} else {
_, err = tx.Exec(ctx, `UPDATE hardware_agent_session SET last_event_at=$2,last_event_sequence=GREATEST(last_event_sequence,$3),updated_at=$2 WHERE conversation_id=$1::uuid`, internalID, now, input.SequenceNo)
if input.EventType == "first_audio_received" && currentStatus != conversation.StatusActive {
return HardwareAgentEventResponse{}, ErrConversationStateConflict
}
if err == nil && currentStatus == conversation.StatusActive {
leaseExpires := now.Add(time.Duration(s.options.HeartbeatInterval*5) * time.Second)
_, err = tx.Exec(ctx, `UPDATE conversation SET lease_expires_at=$2,updated_at=$3,last_heartbeat_seq=GREATEST(last_heartbeat_seq,$4) WHERE id=$1::uuid AND status='ACTIVE'`, internalID, leaseExpires, now, input.SequenceNo)