Files
geo/server/internal/tenant/app/desktop_dispatch_publish.go
T
root 4142c53fa6 feat(monitoring): dispatch monitoring tasks to desktop via AMQP outbox
Replace SSE /desktop/events with priority AMQP dispatch for monitoring
runs, and add phase1/phase2 infrastructure so desktop workers can lease,
resume, report, skip, and cancel monitoring tasks over the existing
dispatch WebSocket.

- Add monitoring collect outbox worker and phase2 desktop task fields
- Add /desktop/monitoring/tasks/{lease,resume,result,skip,cancel} routes
- Introduce execution-devtools and network-observer on desktop runtime
- Refactor runtime-controller, LoginView, and doubao adapter for the new flow
- Add channelName column to tracking views

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-22 00:24:21 +08:00

136 lines
3.5 KiB
Go

package app
import (
"context"
"encoding/json"
"fmt"
"strings"
"time"
"go.uber.org/zap"
"github.com/geo-platform/tenant-api/internal/shared/config"
"github.com/geo-platform/tenant-api/internal/shared/messaging/rabbitmq"
"github.com/geo-platform/tenant-api/internal/shared/stream"
)
func publishDesktopDispatchEvent(
ctx context.Context,
client *rabbitmq.Client,
logger *zap.Logger,
event stream.DesktopDispatchEvent,
) error {
if client == nil || strings.TrimSpace(event.TargetClientID) == "" {
return nil
}
routingKey := desktopDispatchRoutingKey(client.Config(), event.Kind, event.Lane, event.TargetClientID)
body, err := json.Marshal(event)
if err != nil {
return err
}
if err := client.PublishDesktopDispatch(ctx, routingKey, body); err != nil {
return err
}
return nil
}
func desktopDispatchRoutingKey(cfg config.RabbitMQConfig, kind, lane, targetClientID string) string {
prefix := strings.TrimSpace(cfg.DesktopDispatchPublishPrefix)
if strings.TrimSpace(kind) == "monitor" {
prefix = strings.TrimSpace(cfg.DesktopDispatchMonitorPrefix)
}
if prefix == "" {
if strings.TrimSpace(kind) == "monitor" {
prefix = "monitor"
} else {
prefix = "publish"
}
}
if strings.TrimSpace(kind) == "monitor" && strings.TrimSpace(lane) != "" {
return fmt.Sprintf("%s.%s.%s", prefix, monitorDispatchLaneSegment(lane), targetClientID)
}
if strings.TrimSpace(kind) == "monitor" {
return fmt.Sprintf("%s.normal.%s", prefix, targetClientID)
}
return fmt.Sprintf("%s.%s", prefix, targetClientID)
}
func monitorDispatchLaneSegment(lane string) string {
switch strings.TrimSpace(lane) {
case "high":
return "high"
case "retry":
return "retry"
default:
return "normal"
}
}
func publishMonitorDispatchSignal(
ctx context.Context,
client *rabbitmq.Client,
logger *zap.Logger,
workspaceID int64,
targetClientID string,
lane string,
priority int,
interruptGeneration int,
) {
event := stream.DesktopDispatchEvent{
Type: "task_available",
TaskID: "",
JobID: "",
WorkspaceID: workspaceID,
TargetClientID: targetClientID,
Status: "queued",
Kind: "monitor",
Priority: priority,
Lane: strings.TrimSpace(lane),
InterruptGeneration: interruptGeneration,
SignalOnly: true,
UpdatedAt: time.Now().UTC(),
}
if err := publishDesktopDispatchEvent(ctx, client, logger, event); err != nil && logger != nil {
logger.Warn("monitor dispatch signal publish failed",
zap.String("target_client_id", targetClientID),
zap.String("lane", lane),
zap.Error(err),
)
}
}
func publishMonitorControlEvent(
ctx context.Context,
client *rabbitmq.Client,
logger *zap.Logger,
workspaceID int64,
targetClientID string,
taskID int64,
reason string,
interruptGeneration int,
) {
event := stream.DesktopDispatchEvent{
Type: "task_control",
TaskID: fmt.Sprintf("%d", taskID),
JobID: "",
WorkspaceID: workspaceID,
TargetClientID: targetClientID,
Status: "queued",
Kind: "monitor",
Lane: "high",
InterruptGeneration: interruptGeneration,
Control: "interrupt_requested",
Reason: reason,
UpdatedAt: time.Now().UTC(),
}
if err := publishDesktopDispatchEvent(ctx, client, logger, event); err != nil && logger != nil {
logger.Warn("monitor control event publish failed",
zap.String("target_client_id", targetClientID),
zap.Int64("task_id", taskID),
zap.Error(err),
)
}
}