feat(doubao): implement challenge handling and monitoring improvements
Desktop Client Build / Resolve Build Metadata (push) Successful in 26s
Frontend CI / Frontend (push) Successful in 3m12s
Backend CI / Backend (push) Failing after 10m8s
Desktop Client Build / Build Desktop Client (push) Successful in 23m53s
Desktop Client Build / Publish Client Artifacts to NAS (push) Successful in 26s
Desktop Client Build / Resolve Build Metadata (push) Successful in 26s
Frontend CI / Frontend (push) Successful in 3m12s
Backend CI / Backend (push) Failing after 10m8s
Desktop Client Build / Build Desktop Client (push) Successful in 23m53s
Desktop Client Build / Publish Client Artifacts to NAS (push) Successful in 26s
- Added classification for `doubao_challenge_required` as a challenge in platform-auth-adapters. - Enhanced account action summaries to include specific messages for Doubao human verification. - Introduced monitoring functions to filter out blocked platforms based on account health. - Updated task leasing to support platform-specific filtering for monitor tasks. - Refined issue aggregation in HomeView to consolidate Doubao challenge failures into a single actionable item. - Improved backend logic to handle platform IDs in task leasing requests and responses. - Added tests for new functionality, ensuring proper handling of Doubao challenges and task leasing logic.
This commit is contained in:
@@ -103,8 +103,9 @@ type DesktopTaskView struct {
|
||||
}
|
||||
|
||||
type LeaseDesktopTaskRequest struct {
|
||||
TaskID *string `json:"task_id"`
|
||||
Kind *string `json:"kind" binding:"omitempty,oneof=publish monitor"`
|
||||
TaskID *string `json:"task_id"`
|
||||
Kind *string `json:"kind" binding:"omitempty,oneof=publish monitor"`
|
||||
PlatformIDs []string `json:"platform_ids"`
|
||||
}
|
||||
|
||||
type LeaseDesktopTaskResponse struct {
|
||||
@@ -213,6 +214,7 @@ func (s *DesktopTaskService) Lease(ctx context.Context, client *repository.Deskt
|
||||
AttemptID: attemptID,
|
||||
LeaseTokenHash: tokenHash,
|
||||
}
|
||||
eligiblePlatformIDs := normalizeDesktopTaskLeasePlatformIDs(req.PlatformIDs)
|
||||
|
||||
if s.logger != nil {
|
||||
fields := []zap.Field{
|
||||
@@ -232,7 +234,7 @@ func (s *DesktopTaskService) Lease(ctx context.Context, client *repository.Deskt
|
||||
case taskID != nil:
|
||||
task, err = s.leaseQueuedDesktopTaskByID(ctx, client, *taskID, leaseParams)
|
||||
case req.Kind != nil && strings.TrimSpace(*req.Kind) == "monitor":
|
||||
task, err = s.leaseNextQueuedMonitorTask(ctx, client, leaseParams)
|
||||
task, err = s.leaseNextQueuedMonitorTask(ctx, client, leaseParams, eligiblePlatformIDs)
|
||||
case req.Kind != nil && strings.TrimSpace(*req.Kind) == "publish":
|
||||
task, err = s.leaseNextQueuedPublishTask(ctx, client, leaseParams)
|
||||
default:
|
||||
@@ -519,6 +521,7 @@ func (s *DesktopTaskService) leaseNextQueuedMonitorTask(
|
||||
ctx context.Context,
|
||||
client *repository.DesktopClient,
|
||||
params repository.DesktopTaskLeaseParams,
|
||||
eligiblePlatformIDs []string,
|
||||
) (*repository.DesktopTask, error) {
|
||||
tx, err := beginDesktopMonitorLeaseTx(ctx, s.pool, client.ID)
|
||||
if err != nil {
|
||||
@@ -533,6 +536,7 @@ func (s *DesktopTaskService) leaseNextQueuedMonitorTask(
|
||||
params.AttemptID,
|
||||
params.LeaseTokenHash,
|
||||
maxConcurrentMonitorPlatformsPerDesktopClient,
|
||||
eligiblePlatformIDs,
|
||||
)
|
||||
task, err := scanRepositoryDesktopTask(row)
|
||||
if err != nil {
|
||||
@@ -577,6 +581,10 @@ func leaseNextQueuedMonitorTaskSQL() string {
|
||||
AND dt.platform_id = ca.platform_id
|
||||
AND dt.kind = 'monitor'
|
||||
AND dt.status = 'queued'
|
||||
AND (
|
||||
COALESCE(array_length($7::text[], 1), 0) = 0
|
||||
OR dt.platform_id = ANY($7::text[])
|
||||
)
|
||||
WHERE NOT EXISTS (
|
||||
SELECT 1
|
||||
FROM desktop_tasks AS active
|
||||
@@ -668,6 +676,30 @@ func leaseNextQueuedMonitorTaskSQL() string {
|
||||
RETURNING ` + desktopTaskRepositoryReturningColumns
|
||||
}
|
||||
|
||||
func normalizeDesktopTaskLeasePlatformIDs(values []string) []string {
|
||||
if len(values) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
result := make([]string, 0, len(values))
|
||||
seen := make(map[string]struct{}, len(values))
|
||||
for _, value := range values {
|
||||
platformID := strings.ToLower(strings.TrimSpace(value))
|
||||
if platformID == "" {
|
||||
continue
|
||||
}
|
||||
if _, ok := seen[platformID]; ok {
|
||||
continue
|
||||
}
|
||||
seen[platformID] = struct{}{}
|
||||
result = append(result, platformID)
|
||||
}
|
||||
if len(result) == 0 {
|
||||
return nil
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
func monitorAccountLeaseLockSQL(alias string) string {
|
||||
return fmt.Sprintf(`hashtextextended(
|
||||
concat_ws(
|
||||
|
||||
@@ -2,6 +2,7 @@ package app
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"reflect"
|
||||
"regexp"
|
||||
"strings"
|
||||
"testing"
|
||||
@@ -339,6 +340,7 @@ func TestLeaseNextQueuedMonitorTaskSQLUsesAccountIdentityAndClientSlots(t *testi
|
||||
for _, fragment := range []string{
|
||||
"WITH current_accounts AS",
|
||||
"client_id = $3",
|
||||
"COALESCE(array_length($7::text[], 1), 0) = 0 OR dt.platform_id = ANY($7::text[])",
|
||||
"dt.target_account_id = target_account.desktop_id",
|
||||
"target_account.account_fingerprint",
|
||||
"target_account.platform_uid",
|
||||
@@ -366,6 +368,19 @@ func TestLeaseNextQueuedMonitorTaskSQLUsesAccountIdentityAndClientSlots(t *testi
|
||||
}
|
||||
}
|
||||
|
||||
func TestNormalizeDesktopTaskLeasePlatformIDs(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
got := normalizeDesktopTaskLeasePlatformIDs([]string{" Doubao ", "qwen", "", "DOUBAO"})
|
||||
want := []string{"doubao", "qwen"}
|
||||
if !reflect.DeepEqual(got, want) {
|
||||
t.Fatalf("normalizeDesktopTaskLeasePlatformIDs() = %#v, want %#v", got, want)
|
||||
}
|
||||
if got := normalizeDesktopTaskLeasePlatformIDs([]string{" ", ""}); got != nil {
|
||||
t.Fatalf("normalizeDesktopTaskLeasePlatformIDs(blank) = %#v, want nil", got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestLeaseQueuedDesktopTaskByIDSQLUsesAccountIdentityAndClientSlots(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
|
||||
@@ -420,6 +420,11 @@ func (s *MonitoringCallbackService) handleTaskResultForInstallation(
|
||||
}, nil
|
||||
}
|
||||
|
||||
if response := idempotentDesktopMonitoringResultResponse(task); response != nil {
|
||||
_ = s.touchInstallation(ctx, installation.ID)
|
||||
return response, nil
|
||||
}
|
||||
|
||||
if task.ExecutionOwner == "desktop_tasks" {
|
||||
if err := s.validateDesktopMonitorExecutionLease(ctx, task, installation.ID, req.LeaseToken); err != nil {
|
||||
return nil, err
|
||||
@@ -3538,6 +3543,24 @@ func validateMonitoringTaskResultSubmission(platformID string, runStatus string,
|
||||
return nil
|
||||
}
|
||||
|
||||
func idempotentDesktopMonitoringResultResponse(task *monitoringCollectTask) *MonitoringTaskResultResponse {
|
||||
if task == nil || task.ExecutionOwner != "desktop_tasks" || !task.CallbackReceivedAt.Valid {
|
||||
return nil
|
||||
}
|
||||
|
||||
switch task.Status {
|
||||
case "received", "failed", "skipped":
|
||||
return &MonitoringTaskResultResponse{
|
||||
TaskID: task.ID,
|
||||
TaskStatus: task.Status,
|
||||
CitationSourceCount: 0,
|
||||
ContentCitationCount: 0,
|
||||
}
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func normalizeMonitoringLeaseLimit(value int) int {
|
||||
if value <= 0 {
|
||||
return defaultMonitoringLeaseLimit
|
||||
|
||||
@@ -384,6 +384,68 @@ func TestMonitoringTaskReadyForQueuedResultAcceptsDesktopReceivedStatus(t *testi
|
||||
}
|
||||
}
|
||||
|
||||
func TestIdempotentDesktopMonitoringResultResponseAcceptsReceivedTerminalStates(t *testing.T) {
|
||||
for _, status := range []string{"received", "failed", "skipped"} {
|
||||
t.Run(status, func(t *testing.T) {
|
||||
got := idempotentDesktopMonitoringResultResponse(&monitoringCollectTask{
|
||||
ID: 123,
|
||||
ExecutionOwner: "desktop_tasks",
|
||||
Status: status,
|
||||
CallbackReceivedAt: sqlNullTimeForTest(),
|
||||
})
|
||||
|
||||
if got == nil {
|
||||
t.Fatal("idempotentDesktopMonitoringResultResponse() = nil, want response")
|
||||
}
|
||||
if got.TaskID != 123 {
|
||||
t.Fatalf("TaskID = %d, want 123", got.TaskID)
|
||||
}
|
||||
if got.TaskStatus != status {
|
||||
t.Fatalf("TaskStatus = %q, want %q", got.TaskStatus, status)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestIdempotentDesktopMonitoringResultResponseRequiresDesktopCallback(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
task monitoringCollectTask
|
||||
}{
|
||||
{
|
||||
name: "legacy received",
|
||||
task: monitoringCollectTask{
|
||||
ExecutionOwner: "legacy",
|
||||
Status: "received",
|
||||
CallbackReceivedAt: sqlNullTimeForTest(),
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "desktop callback missing",
|
||||
task: monitoringCollectTask{
|
||||
ExecutionOwner: "desktop_tasks",
|
||||
Status: "received",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "desktop pending",
|
||||
task: monitoringCollectTask{
|
||||
ExecutionOwner: "desktop_tasks",
|
||||
Status: "pending",
|
||||
CallbackReceivedAt: sqlNullTimeForTest(),
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
if got := idempotentDesktopMonitoringResultResponse(&tt.task); got != nil {
|
||||
t.Fatalf("idempotentDesktopMonitoringResultResponse() = %#v, want nil", got)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func sqlNullTimeForTest() sql.NullTime {
|
||||
return sql.NullTime{Time: time.Now(), Valid: true}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user