package daemon

import (
	"context"
	"encoding/json"
	"errors"
	"log/slog"
	"strings"

	"github.com/tidbcloud/AgentTeam/internal/runtimecontrol"
	"github.com/tidbcloud/AgentTeam/internal/store/agent"
)

type machineWorkspaceScanRemoteRequestPayload struct {
	Limit  int    `json:"limit,omitempty"`
	Cursor string `json:"cursor,omitempty"`
}

type machineWorkspaceScanRemoteRequestResponse struct {
	Directories []agent.WorkspaceDirectory `json:"directories"`
	Limit       int                        `json:"limit,omitempty"`
	Cursor      string                     `json:"cursor,omitempty"`
	HasMore     bool                       `json:"hasMore,omitempty"`
	NextCursor  string                     `json:"nextCursor,omitempty"`
}

type machineWorkspaceDeleteRemoteRequestPayload struct {
	DirectoryName string `json:"directoryName"`
}

type machineWorkspaceDeleteRemoteRequestResponse struct {
	DirectoryName string `json:"directoryName"`
	Success       bool   `json:"success"`
}

type machineDashboardRemoteRequestPayload struct {
	WorkspaceID string   `json:"workspaceId,omitempty"`
	MachineID   string   `json:"machineId,omitempty"`
	Window      string   `json:"window"`
	AgentIDs    []string `json:"agentIds,omitempty"`
}

type machineDashboardRemoteRequestResponse = runtimecontrol.MachineDashboardResponse

// ---------------------------------------------------------------------------
// Machine settings / runtimes / mcp-servers remote endpoints.
//
// These mirror queryMachineDashboard end-to-end: typed payload struct, typed
// Hub method, BuildSpec factory, MapResult factory, and classifier. Response
// payloads from the daemon are intentionally typed as json.RawMessage so the
// server never owns the daemon's response schema — the daemon is the single
// source of truth for the shape of desktop-local state. We only validate that
// the payload is syntactically valid JSON before echoing it back.
// ---------------------------------------------------------------------------

// machineSettingsRemoteRequestPayload is the wire shape for both
// machine.settings (read) and machine.settings.update (write): only the
// desktop field is mutable on update; system and daemon are read-only and
// returned in the bundle response.
type machineSettingsRemoteRequestPayload struct {
	WorkspaceID string          `json:"workspaceId,omitempty"`
	MachineID   string          `json:"machineId,omitempty"`
	Desktop     json.RawMessage `json:"desktop,omitempty"`
}

// machineSettingsUpdateRemoteRequestResponse is the small typed response for
// the write endpoint — {"ok":true}. The read endpoint returns the bundle as
// raw JSON via machineRawBundleFromRemoteResult.
type machineSettingsUpdateRemoteRequestResponse struct {
	OK bool `json:"ok"`
}

type machineRuntimeConfigRemoteRequestPayload struct {
	WorkspaceID string          `json:"workspaceId,omitempty"`
	MachineID   string          `json:"machineId,omitempty"`
	Name        string          `json:"name,omitempty"`
	Config      json.RawMessage `json:"config,omitempty"`
}

type machineMCPServerUpsertRemoteRequestPayload struct {
	WorkspaceID string          `json:"workspaceId,omitempty"`
	MachineID   string          `json:"machineId,omitempty"`
	Server      json.RawMessage `json:"server,omitempty"`
}

type machineMCPServerKeyedRemoteRequestPayload struct {
	WorkspaceID string `json:"workspaceId,omitempty"`
	MachineID   string `json:"machineId,omitempty"`
	Name        string `json:"name,omitempty"`
}

type machineMCPServerDeleteRemoteRequestResponse struct {
	OK bool `json:"ok"`
}

type machineMCPServerTestRemoteRequestResponse struct {
	OK    bool   `json:"ok"`
	Error string `json:"error,omitempty"`
}

type runtimeTrajectoryHistoryRemoteRequestPayload struct {
	AgentID string `json:"agentId"`
	Limit   int    `json:"limit,omitempty"`
	Before  int64  `json:"before,omitempty"`
}

type runtimeTrajectoryHistoryRemoteRequestResponse struct {
	AgentID string                               `json:"agentId"`
	Items   []runtimecontrol.AgentTrajectoryItem `json:"items"`
}

type runtimeRestoreRecoveryPointRemoteRequestPayload struct {
	AgentID     string `json:"agentId"`
	Runtime     string `json:"runtime"`
	Model       string `json:"model,omitempty"`
	SessionID   string `json:"sessionId,omitempty"`
	Strategy    string `json:"strategy"`
	RecoveryRef string `json:"recoveryRef"`
}

type runtimeRestoreRecoveryPointRemoteRequestResponse struct {
	AgentID string `json:"agentId"`
	Success bool   `json:"success"`
}

func (hub *Hub) scanMachineWorkspace(ctx context.Context, client *clientState, opts agent.WorkspaceListOptions) (agent.WorkspaceDirectoryPage, error) {
	if client == nil {
		return agent.WorkspaceDirectoryPage{}, errors.New("desktop_unavailable")
	}
	opts = normalizeWorkspaceListOptions(opts)
	descriptor, ok := hub.remoteEndpointDescriptor(remoteEndpointMachineWorkspaceScan, 1)
	if !ok || hub.elasticRequestLinks == nil {
		return client.scanWorkspace(ctx, opts)
	}
	return invokeRemoteEndpoint(RemoteEndpointInvocation[agent.WorkspaceDirectoryPage]{
		Context:      ctx,
		Client:       client,
		Manager:      hub.elasticRequestLinks,
		Descriptor:   descriptor,
		RouteOptions: machineWorkspaceScanRouteOptions(ctx, opts),
		Fallback: func() (agent.WorkspaceDirectoryPage, error) {
			return client.scanWorkspace(ctx, opts)
		},
		BuildSpec: func() (RemoteRequestAssignmentSpec, error) {
			return newMachineWorkspaceScanRemoteRequestSpec(ctx, descriptor, opts)
		},
		MapResult: machineWorkspaceScanFromRemoteResult,
	})
}

func (hub *Hub) deleteMachineWorkspaceDirectory(ctx context.Context, client *clientState, directoryName string) (bool, error) {
	if client == nil {
		return false, errors.New("desktop_unavailable")
	}
	descriptor, ok := hub.remoteEndpointDescriptor(remoteEndpointMachineWorkspaceDelete, 1)
	if !ok || hub.elasticRequestLinks == nil {
		return client.deleteWorkspaceDirectory(ctx, directoryName)
	}
	return invokeRemoteEndpoint(RemoteEndpointInvocation[bool]{
		Context:      ctx,
		Client:       client,
		Manager:      hub.elasticRequestLinks,
		Descriptor:   descriptor,
		RouteOptions: remoteRequestRouteOptionsFromContext(ctx),
		Fallback: func() (bool, error) {
			return client.deleteWorkspaceDirectory(ctx, directoryName)
		},
		BuildSpec: func() (RemoteRequestAssignmentSpec, error) {
			return newMachineWorkspaceDeleteRemoteRequestSpec(ctx, descriptor, directoryName)
		},
		MapResult: machineWorkspaceDeleteFromRemoteResult,
	})
}

func (hub *Hub) machineDashboardSupported(client *clientState) bool {
	if client == nil {
		return false
	}
	descriptor, ok := hub.remoteEndpointDescriptor(remoteEndpointMachineDashboard, remoteEndpointMachineDashboardVersion)
	if !ok {
		return false
	}
	return client.supportsRemoteEndpoint(descriptor.EndpointID, descriptor.Version, descriptor.RegistryVersion)
}

func (hub *Hub) queryMachineDashboard(ctx context.Context, client *clientState, request runtimecontrol.MachineDashboardRequest) (runtimecontrol.MachineDashboardResponse, error) {
	if client == nil {
		return runtimecontrol.MachineDashboardResponse{}, runtimecontrol.ErrMachineDashboardUnavailable
	}
	descriptor, ok := hub.remoteEndpointDescriptor(remoteEndpointMachineDashboard, remoteEndpointMachineDashboardVersion)
	if !ok || !client.supportsRemoteEndpoint(remoteEndpointMachineDashboard, remoteEndpointMachineDashboardVersion, descriptor.RegistryVersion) {
		return runtimecontrol.MachineDashboardResponse{}, runtimecontrol.ErrMachineDashboardUnsupported
	}
	if hub.elasticRequestLinks == nil {
		return runtimecontrol.MachineDashboardResponse{}, remoteRequestStableError(RemoteRequestStableErrorDesktopUnavailable)
	}
	return invokeRemoteEndpoint(RemoteEndpointInvocation[runtimecontrol.MachineDashboardResponse]{
		Context:      ctx,
		Client:       client,
		Manager:      hub.elasticRequestLinks,
		Descriptor:   descriptor,
		RouteOptions: remoteRequestRouteOptionsFromContext(ctx),
		Fallback: func() (runtimecontrol.MachineDashboardResponse, error) {
			return runtimecontrol.MachineDashboardResponse{}, runtimecontrol.ErrMachineDashboardUnsupported
		},
		BuildSpec: func() (RemoteRequestAssignmentSpec, error) {
			return newMachineDashboardRemoteRequestSpec(ctx, descriptor, request)
		},
		MapResult: machineDashboardFromRemoteResult,
	})
}

func (hub *Hub) trajectoryHistory(ctx context.Context, client *clientState, agentID string, limit int, before int64) ([]runtimecontrol.AgentTrajectoryItem, error) {
	if client == nil {
		return nil, errors.New("desktop_unavailable")
	}
	descriptor, ok := hub.remoteEndpointDescriptor(remoteEndpointRuntimeTrajectoryHistory, 1)
	if !ok || hub.elasticRequestLinks == nil {
		return client.trajectoryHistory(ctx, agentID, limit, before)
	}
	return invokeRemoteEndpoint(RemoteEndpointInvocation[[]runtimecontrol.AgentTrajectoryItem]{
		Context:      ctx,
		Client:       client,
		Manager:      hub.elasticRequestLinks,
		Descriptor:   descriptor,
		RouteOptions: remoteRequestRouteOptionsFromContext(ctx),
		Fallback: func() ([]runtimecontrol.AgentTrajectoryItem, error) {
			return client.trajectoryHistory(ctx, agentID, limit, before)
		},
		BuildSpec: func() (RemoteRequestAssignmentSpec, error) {
			return newRuntimeTrajectoryHistoryRemoteRequestSpec(ctx, descriptor, agentID, limit, before)
		},
		MapResult: runtimeTrajectoryHistoryFromRemoteResult,
	})
}

func (hub *Hub) restoreAgentRecoveryPoint(ctx context.Context, client *clientState, request runtimecontrol.RestoreAgentRecoveryPointRequest) error {
	if client == nil {
		return errors.New("desktop_unavailable")
	}
	descriptor, ok := hub.remoteEndpointDescriptor(remoteEndpointRuntimeRestoreRecoveryPoint, 1)
	if !ok || hub.elasticRequestLinks == nil {
		return client.restoreAgentRecoveryPoint(ctx, request)
	}
	_, err := invokeRemoteEndpoint(RemoteEndpointInvocation[struct{}]{
		Context:      ctx,
		Client:       client,
		Manager:      hub.elasticRequestLinks,
		Descriptor:   descriptor,
		RouteOptions: remoteRequestRouteOptionsFromContext(ctx),
		Fallback: func() (struct{}, error) {
			return struct{}{}, client.restoreAgentRecoveryPoint(ctx, request)
		},
		BuildSpec: func() (RemoteRequestAssignmentSpec, error) {
			return newRuntimeRestoreRecoveryPointRemoteRequestSpec(ctx, descriptor, request)
		},
		MapResult: runtimeRestoreRecoveryPointFromRemoteResult,
	})
	return err
}

func machineWorkspaceScanRouteOptions(ctx context.Context, opts agent.WorkspaceListOptions) RemoteRequestRouteOptions {
	routeOpts := remoteRequestRouteOptionsFromContext(ctx)
	if opts.Limit > agent.DefaultWorkspaceListLimit {
		routeOpts.SessionLinkUnderPressure = true
	}
	return routeOpts
}

func newMachineWorkspaceScanRemoteRequestSpec(ctx context.Context, descriptor RemoteEndpointDescriptor, opts agent.WorkspaceListOptions) (RemoteRequestAssignmentSpec, error) {
	payload, err := json.Marshal(machineWorkspaceScanRemoteRequestPayload{
		Limit:  opts.Limit,
		Cursor: opts.Cursor,
	})
	if err != nil {
		return RemoteRequestAssignmentSpec{}, err
	}
	return newRemoteRequestAssignmentSpec(ctx, descriptor, "machine_workspace_scan", json.RawMessage(payload))
}

func newMachineWorkspaceDeleteRemoteRequestSpec(ctx context.Context, descriptor RemoteEndpointDescriptor, directoryName string) (RemoteRequestAssignmentSpec, error) {
	payload, err := json.Marshal(machineWorkspaceDeleteRemoteRequestPayload{DirectoryName: strings.TrimSpace(directoryName)})
	if err != nil {
		return RemoteRequestAssignmentSpec{}, err
	}
	return newRemoteRequestAssignmentSpec(ctx, descriptor, "machine_workspace_delete", json.RawMessage(payload))
}

func newMachineDashboardRemoteRequestSpec(ctx context.Context, descriptor RemoteEndpointDescriptor, request runtimecontrol.MachineDashboardRequest) (RemoteRequestAssignmentSpec, error) {
	request = normalizeMachineDashboardRequest(request)
	payload, err := json.Marshal(machineDashboardRemoteRequestPayload{
		WorkspaceID: request.WorkspaceID,
		MachineID:   request.MachineID,
		Window:      string(request.Window),
		AgentIDs:    request.AgentIDs,
	})
	if err != nil {
		return RemoteRequestAssignmentSpec{}, err
	}
	if int64(len(payload)) > descriptor.Limits.RequestBytes {
		return RemoteRequestAssignmentSpec{}, runtimecontrol.ErrMachineDashboardInvalid
	}
	return newRemoteRequestAssignmentSpec(ctx, descriptor, "machine_dashboard", json.RawMessage(payload))
}

func normalizeMachineDashboardRequest(request runtimecontrol.MachineDashboardRequest) runtimecontrol.MachineDashboardRequest {
	request.WorkspaceID = strings.TrimSpace(request.WorkspaceID)
	request.MachineID = strings.TrimSpace(request.MachineID)
	if request.Window == "" {
		request.Window = runtimecontrol.MachineDashboardWindow1Day
	}
	if len(request.AgentIDs) == 0 {
		request.AgentIDs = nil
		return request
	}
	seen := make(map[string]struct{}, len(request.AgentIDs))
	agentIDs := make([]string, 0, len(request.AgentIDs))
	for _, agentID := range request.AgentIDs {
		agentID = strings.TrimSpace(agentID)
		if agentID == "" {
			continue
		}
		if _, exists := seen[agentID]; exists {
			continue
		}
		seen[agentID] = struct{}{}
		agentIDs = append(agentIDs, agentID)
	}
	request.AgentIDs = agentIDs
	return request
}

func newRuntimeTrajectoryHistoryRemoteRequestSpec(ctx context.Context, descriptor RemoteEndpointDescriptor, agentID string, limit int, before int64) (RemoteRequestAssignmentSpec, error) {
	payload, err := json.Marshal(runtimeTrajectoryHistoryRemoteRequestPayload{
		AgentID: strings.TrimSpace(agentID),
		Limit:   limit,
		Before:  before,
	})
	if err != nil {
		return RemoteRequestAssignmentSpec{}, err
	}
	return newRemoteRequestAssignmentSpec(ctx, descriptor, "trajectory_history", json.RawMessage(payload))
}

func newRuntimeRestoreRecoveryPointRemoteRequestSpec(ctx context.Context, descriptor RemoteEndpointDescriptor, request runtimecontrol.RestoreAgentRecoveryPointRequest) (RemoteRequestAssignmentSpec, error) {
	request = trimRestoreAgentRecoveryPointRequest(request)
	payload, err := json.Marshal(runtimeRestoreRecoveryPointRemoteRequestPayload{
		AgentID:     request.AgentID,
		Runtime:     request.Runtime,
		Model:       request.Model,
		SessionID:   request.SessionID,
		Strategy:    request.Strategy,
		RecoveryRef: request.RecoveryRef,
	})
	if err != nil {
		return RemoteRequestAssignmentSpec{}, err
	}
	return newRemoteRequestAssignmentSpec(ctx, descriptor, "restore_recovery_point", json.RawMessage(payload))
}

// Cause-category enum for the single path-free internal warning emitted when
// the directory post-classifier maps an unexpected error to
// ErrMachineDirectoryUnavailable. These are the only strings the warning ever
// carries; no err, outcome text, status text, or path is attached.
const (
	machineDirectoryCauseCategoryBuildSpec = "build_spec"
	machineDirectoryCauseCategoryRoute     = "route"
	machineDirectoryCauseCategorySend      = "send"
	machineDirectoryCauseCategoryWait      = "wait"
	machineDirectoryCauseCategoryDecode    = "decode"
	machineDirectoryCauseCategoryProtocol  = "protocol"
	machineDirectoryCauseCategoryUnknown   = "unknown"
)

type machineDirectoryListRemoteRequestPayload struct {
	// Path is a raw filesystem path: do not trim. Empty path resolves to the
	// daemon user's home directory; whitespace-only path is a real filesystem
	// value and the desktop classifies it as invalid rather than rewriting.
	Path   string `json:"path,omitempty"`
	Limit  int    `json:"limit,omitempty"`
	Cursor string `json:"cursor,omitempty"`
}

type machineDirectoryListRemoteRequestResponse struct {
	CurrentPath string                   `json:"currentPath"`
	ParentPath  string                   `json:"parentPath"`
	Directories []agent.MachineDirectory `json:"directories"`
	Limit       int                      `json:"limit"`
	Cursor      string                   `json:"cursor"`
	HasMore     bool                     `json:"hasMore"`
	NextCursor  string                   `json:"nextCursor"`
	Truncated   bool                     `json:"truncated"`
}

// listMachineDirectories routes one directory browse request to the connected
// desktop's machine.directory.list endpoint. Capability preflight only looks
// at descriptor + advertisement: a clean capability gap returns
// ErrMachineDirectoryUnsupported before any assignment is built, so the typed
// envelope is never sent to a daemon that did not advertise the endpoint.
// Manager nil is a Hub setup defect (not a capability gap): surface it as a
// stable unavailable error so the post-classifier maps it to
// ErrMachineDirectoryUnavailable and the Web UI retries instead of falling
// back to manual path input.
func (hub *Hub) listMachineDirectories(ctx context.Context, client *clientState, path string, opts agent.WorkspaceListOptions) (agent.MachineDirectoryPage, error) {
	descriptor, ok := hub.remoteEndpointDescriptor(remoteEndpointMachineDirectoryList, 1)
	if !ok || !client.supportsRemoteEndpoint(remoteEndpointMachineDirectoryList, 1, descriptor.RegistryVersion) {
		return agent.MachineDirectoryPage{}, runtimecontrol.ErrMachineDirectoryUnsupported
	}
	if hub.elasticRequestLinks == nil {
		return agent.MachineDirectoryPage{}, remoteRequestStableError(RemoteRequestStableErrorDesktopUnavailable)
	}
	opts = defaultAndCapMachineDirectoryListOptions(opts)
	return invokeRemoteEndpoint(RemoteEndpointInvocation[agent.MachineDirectoryPage]{
		Context:      ctx,
		Client:       client,
		Manager:      hub.elasticRequestLinks,
		Descriptor:   descriptor,
		RouteOptions: remoteRequestRouteOptionsFromContext(ctx),
		// Fallback is unreachable after successful capability preflight:
		// SafeRetryFallback=false suppresses retry and FallbackCommand=none
		// suppresses legacy routing. Returning the typed unsupported
		// sentinel here surfaces a stable signal if the invariant ever
		// breaks, instead of silently running a legacy code path.
		Fallback: func() (agent.MachineDirectoryPage, error) {
			return agent.MachineDirectoryPage{}, runtimecontrol.ErrMachineDirectoryUnsupported
		},
		BuildSpec: func() (RemoteRequestAssignmentSpec, error) {
			return newMachineDirectoryListRemoteRequestSpec(ctx, descriptor, path, opts)
		},
		MapResult: machineDirectoryListFromRemoteResult,
	})
}

// defaultAndCapMachineDirectoryListOptions defaults an unset limit and caps
// an over-limit limit, matching the workspace list page contract. It
// intentionally does NOT trim or rewrite Cursor: the directory cursor is a
// raw canonical offset form and the desktop's strict parser (Task 4 HTTP
// layer) is the only place that may reject non-canonical syntax.
func defaultAndCapMachineDirectoryListOptions(opts agent.WorkspaceListOptions) agent.WorkspaceListOptions {
	if opts.Limit <= 0 {
		opts.Limit = agent.DefaultWorkspaceListLimit
	}
	if opts.Limit > agent.MaxWorkspaceListLimit {
		opts.Limit = agent.MaxWorkspaceListLimit
	}
	return opts
}

func newMachineDirectoryListRemoteRequestSpec(ctx context.Context, descriptor RemoteEndpointDescriptor, path string, opts agent.WorkspaceListOptions) (RemoteRequestAssignmentSpec, error) {
	payload, err := json.Marshal(machineDirectoryListRemoteRequestPayload{
		Path:   path,
		Limit:  opts.Limit,
		Cursor: opts.Cursor,
	})
	if err != nil {
		return RemoteRequestAssignmentSpec{}, err
	}
	// Byte-budget guard before assignment build: an oversized path returns a
	// typed invalid sentinel rather than a generic protocol error. The 64
	// KiB request limit is the descriptor's RequestBytes cap.
	if int64(len(payload)) > descriptor.Limits.RequestBytes {
		return RemoteRequestAssignmentSpec{}, runtimecontrol.ErrMachineDirectoryInvalid
	}
	return newRemoteRequestAssignmentSpec(ctx, descriptor, "machine_directory_list", json.RawMessage(payload))
}

func machineDashboardFromRemoteResult(result RemoteRequestResult[json.RawMessage]) (runtimecontrol.MachineDashboardResponse, error) {
	if !result.Success {
		outcome := strings.TrimSpace(result.Outcome)
		if outcome == "" {
			outcome = strings.TrimSpace(result.Error)
		}
		switch outcome {
		case runtimecontrol.MachineDashboardInvalidOutcome:
			return runtimecontrol.MachineDashboardResponse{}, runtimecontrol.ErrMachineDashboardInvalid
		case "", "desktop_unavailable", "desktop_endpoint_unavailable":
			return runtimecontrol.MachineDashboardResponse{}, remoteRequestStableError(RemoteRequestStableErrorDesktopUnavailable)
		case "desktop_busy":
			return runtimecontrol.MachineDashboardResponse{}, remoteRequestStableError(RemoteRequestStableErrorDesktopBusy)
		}
		return runtimecontrol.MachineDashboardResponse{}, remoteEndpointResultError(result)
	}
	var response machineDashboardRemoteRequestResponse
	if err := json.Unmarshal(result.Payload, &response); err != nil {
		return runtimecontrol.MachineDashboardResponse{}, err
	}
	return response, nil
}

func classifyMachineDashboardError(err error) error {
	if err == nil {
		return nil
	}
	if errors.Is(err, runtimecontrol.ErrMachineDashboardInvalid) {
		return runtimecontrol.ErrMachineDashboardInvalid
	}
	if errors.Is(err, runtimecontrol.ErrMachineDashboardUnsupported) {
		return runtimecontrol.ErrMachineDashboardUnsupported
	}
	if errors.Is(err, runtimecontrol.ErrMachineOffline) || errors.Is(err, context.DeadlineExceeded) || errors.Is(err, context.Canceled) {
		return runtimecontrol.ErrMachineDashboardUnavailable
	}
	var stable *stableRemoteRequestError
	if errors.As(err, &stable) {
		switch stable.code {
		case RemoteRequestStableErrorDesktopUnavailable, RemoteRequestStableErrorDesktopBusy:
			return runtimecontrol.ErrMachineDashboardUnavailable
		}
	}
	return runtimecontrol.ErrMachineDashboardUnavailable
}

// machineDirectoryListFromRemoteResult maps the desktop's browse response
// into the typed page. Outcome strings for invalid and unreadable are
// promoted to typed sentinels here so the post-classifier can errors.Is
// them. Empty / desktop_unavailable / desktop_endpoint_unavailable outcomes
// are mapped to the typed stable unavailable sentinel so the
// post-classifier never needs to read an unknown error's text to detect
// them; desktop_busy is mapped to the typed stable busy sentinel for the
// same reason.
func machineDirectoryListFromRemoteResult(result RemoteRequestResult[json.RawMessage]) (agent.MachineDirectoryPage, error) {
	if !result.Success {
		outcome := strings.TrimSpace(result.Outcome)
		if outcome == "" {
			outcome = strings.TrimSpace(result.Error)
		}
		switch outcome {
		case runtimecontrol.MachineDirectoryInvalidOutcome:
			return agent.MachineDirectoryPage{}, runtimecontrol.ErrMachineDirectoryInvalid
		case runtimecontrol.MachineDirectoryUnreadableOutcome:
			return agent.MachineDirectoryPage{}, runtimecontrol.ErrMachineDirectoryUnreadable
		case "", "desktop_unavailable", "desktop_endpoint_unavailable":
			return agent.MachineDirectoryPage{}, remoteRequestStableError(RemoteRequestStableErrorDesktopUnavailable)
		case "desktop_busy":
			return agent.MachineDirectoryPage{}, remoteRequestStableError(RemoteRequestStableErrorDesktopBusy)
		}
		return agent.MachineDirectoryPage{}, remoteEndpointResultError(result)
	}
	var response machineDirectoryListRemoteRequestResponse
	if err := json.Unmarshal(result.Payload, &response); err != nil {
		return agent.MachineDirectoryPage{}, err
	}
	if response.Directories == nil {
		response.Directories = []agent.MachineDirectory{}
	}
	return agent.MachineDirectoryPage{
		CurrentPath: response.CurrentPath,
		ParentPath:  response.ParentPath,
		Directories: response.Directories,
		Limit:       response.Limit,
		Cursor:      response.Cursor,
		HasMore:     response.HasMore,
		NextCursor:  response.NextCursor,
		Truncated:   response.Truncated,
	}, nil
}

// classifyMachineDirectoryError is the post-classifier that runs on every
// return path from the directory route. It maps any error to a fixed typed
// sentinel and never reads an arbitrary error's text to detect a stable
// code: stable errors are matched via errors.As on the typed
// stableRemoteRequestError, and every other branch uses errors.Is on typed
// sentinels or recognized concrete types. The returned cause_category is
// empty for known failures; when non-empty, the caller MUST emit a
// path-free internal warning carrying only that fixed category.
func classifyMachineDirectoryError(err error) (error, string) {
	if err == nil {
		return nil, ""
	}
	if errors.Is(err, runtimecontrol.ErrMachineDirectoryInvalid) {
		return runtimecontrol.ErrMachineDirectoryInvalid, ""
	}
	if errors.Is(err, runtimecontrol.ErrMachineDirectoryUnreadable) {
		return runtimecontrol.ErrMachineDirectoryUnreadable, ""
	}
	if errors.Is(err, runtimecontrol.ErrMachineDirectoryUnsupported) {
		return runtimecontrol.ErrMachineDirectoryUnsupported, ""
	}
	if errors.Is(err, runtimecontrol.ErrMachineOffline) {
		return runtimecontrol.ErrMachineDirectoryUnavailable, ""
	}
	if errors.Is(err, context.DeadlineExceeded) || errors.Is(err, context.Canceled) {
		return runtimecontrol.ErrMachineDirectoryUnavailable, machineDirectoryCauseCategoryWait
	}
	var stable *stableRemoteRequestError
	if errors.As(err, &stable) {
		switch stable.code {
		case RemoteRequestStableErrorDesktopUnavailable:
			return runtimecontrol.ErrMachineDirectoryUnavailable, ""
		case RemoteRequestStableErrorDesktopBusy:
			return runtimecontrol.ErrMachineDirectoryBusy, ""
		}
	}
	// Decode-phase errors are recognizable protocol-level failures.
	var syntaxErr *json.SyntaxError
	if errors.As(err, &syntaxErr) {
		return runtimecontrol.ErrMachineDirectoryUnavailable, machineDirectoryCauseCategoryDecode
	}
	var unmarshalErr *json.UnmarshalTypeError
	if errors.As(err, &unmarshalErr) {
		return runtimecontrol.ErrMachineDirectoryUnavailable, machineDirectoryCauseCategoryDecode
	}
	// Unknown failure: never leak raw text. Surface the typed unavailable
	// sentinel and emit one path-free internal warning.
	return runtimecontrol.ErrMachineDirectoryUnavailable, machineDirectoryCauseCategoryUnknown
}

// warnMachineDirectoryUnexpectedFailure emits the single path-free internal
// warning for an unexpected directory route failure. The cause_category is
// bounded to a fixed enum and no err/outcome/path data is attached.
func (hub *Hub) warnMachineDirectoryUnexpectedFailure(causeCategory string) {
	if hub == nil || hub.logger == nil {
		return
	}
	if !machineDirectoryCauseCategoryValid(causeCategory) {
		causeCategory = machineDirectoryCauseCategoryUnknown
	}
	hub.logger.Warn("machine directory browsing failed unexpectedly",
		slog.String("cause_category", causeCategory),
		slog.String("endpoint", remoteEndpointMachineDirectoryList),
	)
}

func machineDirectoryCauseCategoryValid(category string) bool {
	switch category {
	case machineDirectoryCauseCategoryBuildSpec, machineDirectoryCauseCategoryRoute,
		machineDirectoryCauseCategorySend, machineDirectoryCauseCategoryWait,
		machineDirectoryCauseCategoryDecode, machineDirectoryCauseCategoryProtocol,
		machineDirectoryCauseCategoryUnknown:
		return true
	}
	return false
}

func machineWorkspaceScanFromRemoteResult(result RemoteRequestResult[json.RawMessage]) (agent.WorkspaceDirectoryPage, error) {
	if err := remoteEndpointResultError(result); err != nil {
		return agent.WorkspaceDirectoryPage{}, err
	}
	var response machineWorkspaceScanRemoteRequestResponse
	if err := json.Unmarshal(result.Payload, &response); err != nil {
		return agent.WorkspaceDirectoryPage{}, err
	}
	if response.Directories == nil {
		response.Directories = []agent.WorkspaceDirectory{}
	}
	return agent.WorkspaceDirectoryPage{
		Directories: response.Directories,
		Limit:       response.Limit,
		Cursor:      strings.TrimSpace(response.Cursor),
		HasMore:     response.HasMore,
		NextCursor:  strings.TrimSpace(response.NextCursor),
	}, nil
}

func machineWorkspaceDeleteFromRemoteResult(result RemoteRequestResult[json.RawMessage]) (bool, error) {
	if err := remoteEndpointResultError(result); err != nil {
		return false, err
	}
	var response machineWorkspaceDeleteRemoteRequestResponse
	if err := json.Unmarshal(result.Payload, &response); err != nil {
		return false, err
	}
	return response.Success, nil
}

func runtimeTrajectoryHistoryFromRemoteResult(result RemoteRequestResult[json.RawMessage]) ([]runtimecontrol.AgentTrajectoryItem, error) {
	if err := remoteEndpointResultError(result); err != nil {
		return nil, err
	}
	var response runtimeTrajectoryHistoryRemoteRequestResponse
	if err := json.Unmarshal(result.Payload, &response); err != nil {
		return nil, err
	}
	if response.Items == nil {
		return []runtimecontrol.AgentTrajectoryItem{}, nil
	}
	return response.Items, nil
}

func runtimeRestoreRecoveryPointFromRemoteResult(result RemoteRequestResult[json.RawMessage]) (struct{}, error) {
	if err := remoteEndpointResultError(result); err != nil {
		return struct{}{}, err
	}
	var response runtimeRestoreRecoveryPointRemoteRequestResponse
	if err := json.Unmarshal(result.Payload, &response); err != nil {
		return struct{}{}, err
	}
	if !response.Success {
		return struct{}{}, errors.New("agent restore recovery point failed")
	}
	return struct{}{}, nil
}

// --- Machine settings -----------------------------------------------------

func (hub *Hub) queryMachineSettings(ctx context.Context, client *clientState, workspaceID, machineID string) (json.RawMessage, error) {
	if client == nil {
		return nil, runtimecontrol.ErrMachineSettingsUnavailable
	}
	descriptor, ok := hub.remoteEndpointDescriptor(remoteEndpointMachineSettings, 1)
	if !ok || !client.supportsRemoteEndpoint(remoteEndpointMachineSettings, 1, descriptor.RegistryVersion) {
		return nil, runtimecontrol.ErrMachineSettingsUnsupported
	}
	if hub.elasticRequestLinks == nil {
		return nil, remoteRequestStableError(RemoteRequestStableErrorDesktopUnavailable)
	}
	return invokeRemoteEndpoint(RemoteEndpointInvocation[json.RawMessage]{
		Context:      ctx,
		Client:       client,
		Manager:      hub.elasticRequestLinks,
		Descriptor:   descriptor,
		RouteOptions: remoteRequestRouteOptionsFromContext(ctx),
		Fallback: func() (json.RawMessage, error) {
			return nil, runtimecontrol.ErrMachineSettingsUnsupported
		},
		BuildSpec: func() (RemoteRequestAssignmentSpec, error) {
			return newMachineSettingsRemoteRequestSpec(ctx, descriptor, workspaceID, machineID, nil)
		},
		MapResult: machineRawBundleFromRemoteResult,
	})
}

func (hub *Hub) updateMachineSettings(ctx context.Context, client *clientState, workspaceID, machineID string, desktop json.RawMessage) (json.RawMessage, error) {
	if client == nil {
		return nil, runtimecontrol.ErrMachineSettingsUnavailable
	}
	descriptor, ok := hub.remoteEndpointDescriptor(remoteEndpointMachineSettingsUpdate, 1)
	if !ok || !client.supportsRemoteEndpoint(remoteEndpointMachineSettingsUpdate, 1, descriptor.RegistryVersion) {
		return nil, runtimecontrol.ErrMachineSettingsUnsupported
	}
	if hub.elasticRequestLinks == nil {
		return nil, remoteRequestStableError(RemoteRequestStableErrorDesktopUnavailable)
	}
	return invokeRemoteEndpoint(RemoteEndpointInvocation[json.RawMessage]{
		Context:      ctx,
		Client:       client,
		Manager:      hub.elasticRequestLinks,
		Descriptor:   descriptor,
		RouteOptions: remoteRequestRouteOptionsFromContext(ctx),
		Fallback: func() (json.RawMessage, error) {
			return nil, runtimecontrol.ErrMachineSettingsUnsupported
		},
		BuildSpec: func() (RemoteRequestAssignmentSpec, error) {
			return newMachineSettingsRemoteRequestSpec(ctx, descriptor, workspaceID, machineID, desktop)
		},
		MapResult: machineRawBundleFromRemoteResult,
	})
}

func newMachineSettingsRemoteRequestSpec(ctx context.Context, descriptor RemoteEndpointDescriptor, workspaceID, machineID string, desktop json.RawMessage) (RemoteRequestAssignmentSpec, error) {
	payload, err := json.Marshal(machineSettingsRemoteRequestPayload{
		WorkspaceID: strings.TrimSpace(workspaceID),
		MachineID:   strings.TrimSpace(machineID),
		Desktop:     desktop,
	})
	if err != nil {
		return RemoteRequestAssignmentSpec{}, err
	}
	if int64(len(payload)) > descriptor.Limits.RequestBytes {
		return RemoteRequestAssignmentSpec{}, runtimecontrol.ErrMachineSettingsInvalid
	}
	return newRemoteRequestAssignmentSpec(ctx, descriptor, "machine_settings", json.RawMessage(payload))
}

func machineRawBundleFromRemoteResult(result RemoteRequestResult[json.RawMessage]) (json.RawMessage, error) {
	if err := remoteEndpointResultError(result); err != nil {
		return nil, err
	}
	if !json.Valid(result.Payload) {
		return nil, errors.New("machine settings payload is not valid JSON")
	}
	return result.Payload, nil
}

func classifyMachineSettingsError(err error) error {
	if err == nil {
		return nil
	}
	if errors.Is(err, runtimecontrol.ErrMachineSettingsInvalid) {
		return runtimecontrol.ErrMachineSettingsInvalid
	}
	if errors.Is(err, runtimecontrol.ErrMachineSettingsUnsupported) {
		return runtimecontrol.ErrMachineSettingsUnsupported
	}
	if errors.Is(err, runtimecontrol.ErrMachineOffline) || errors.Is(err, context.DeadlineExceeded) || errors.Is(err, context.Canceled) {
		return runtimecontrol.ErrMachineSettingsUnavailable
	}
	var stable *stableRemoteRequestError
	if errors.As(err, &stable) {
		switch stable.code {
		case RemoteRequestStableErrorDesktopUnavailable, RemoteRequestStableErrorDesktopBusy:
			return runtimecontrol.ErrMachineSettingsUnavailable
		}
	}
	return runtimecontrol.ErrMachineSettingsUnavailable
}

// --- Machine runtimes -----------------------------------------------------

func (hub *Hub) queryMachineRuntimesBundle(ctx context.Context, client *clientState, workspaceID, machineID string) (json.RawMessage, error) {
	if client == nil {
		return nil, runtimecontrol.ErrMachineRuntimesUnavailable
	}
	descriptor, ok := hub.remoteEndpointDescriptor(remoteEndpointMachineRuntimesBundle, 1)
	if !ok || !client.supportsRemoteEndpoint(remoteEndpointMachineRuntimesBundle, 1, descriptor.RegistryVersion) {
		return nil, runtimecontrol.ErrMachineRuntimesUnsupported
	}
	if hub.elasticRequestLinks == nil {
		return nil, remoteRequestStableError(RemoteRequestStableErrorDesktopUnavailable)
	}
	return invokeRemoteEndpoint(RemoteEndpointInvocation[json.RawMessage]{
		Context:      ctx,
		Client:       client,
		Manager:      hub.elasticRequestLinks,
		Descriptor:   descriptor,
		RouteOptions: remoteRequestRouteOptionsFromContext(ctx),
		Fallback: func() (json.RawMessage, error) {
			return nil, runtimecontrol.ErrMachineRuntimesUnsupported
		},
		BuildSpec: func() (RemoteRequestAssignmentSpec, error) {
			return newMachineRuntimesKeyedRemoteRequestSpec(ctx, descriptor, "machine_runtimes_bundle", workspaceID, machineID, "")
		},
		MapResult: machineRawBundleFromRemoteResult,
	})
}

func (hub *Hub) queryMachineRuntimeConfig(ctx context.Context, client *clientState, workspaceID, machineID, name string) (json.RawMessage, error) {
	if client == nil {
		return nil, runtimecontrol.ErrMachineRuntimesUnavailable
	}
	descriptor, ok := hub.remoteEndpointDescriptor(remoteEndpointMachineRuntimeConfigRead, 1)
	if !ok || !client.supportsRemoteEndpoint(remoteEndpointMachineRuntimeConfigRead, 1, descriptor.RegistryVersion) {
		return nil, runtimecontrol.ErrMachineRuntimesUnsupported
	}
	if hub.elasticRequestLinks == nil {
		return nil, remoteRequestStableError(RemoteRequestStableErrorDesktopUnavailable)
	}
	return invokeRemoteEndpoint(RemoteEndpointInvocation[json.RawMessage]{
		Context:      ctx,
		Client:       client,
		Manager:      hub.elasticRequestLinks,
		Descriptor:   descriptor,
		RouteOptions: remoteRequestRouteOptionsFromContext(ctx),
		Fallback: func() (json.RawMessage, error) {
			return nil, runtimecontrol.ErrMachineRuntimesUnsupported
		},
		BuildSpec: func() (RemoteRequestAssignmentSpec, error) {
			return newMachineRuntimesKeyedRemoteRequestSpec(ctx, descriptor, "machine_runtime_config_read", workspaceID, machineID, name)
		},
		MapResult: machineRawBundleFromRemoteResult,
	})
}

func (hub *Hub) updateMachineRuntimeConfig(ctx context.Context, client *clientState, workspaceID, machineID, name string, config json.RawMessage) (json.RawMessage, error) {
	if client == nil {
		return nil, runtimecontrol.ErrMachineRuntimesUnavailable
	}
	descriptor, ok := hub.remoteEndpointDescriptor(remoteEndpointMachineRuntimeConfigUpdate, 1)
	if !ok || !client.supportsRemoteEndpoint(remoteEndpointMachineRuntimeConfigUpdate, 1, descriptor.RegistryVersion) {
		return nil, runtimecontrol.ErrMachineRuntimesUnsupported
	}
	if hub.elasticRequestLinks == nil {
		return nil, remoteRequestStableError(RemoteRequestStableErrorDesktopUnavailable)
	}
	return invokeRemoteEndpoint(RemoteEndpointInvocation[json.RawMessage]{
		Context:      ctx,
		Client:       client,
		Manager:      hub.elasticRequestLinks,
		Descriptor:   descriptor,
		RouteOptions: remoteRequestRouteOptionsFromContext(ctx),
		Fallback: func() (json.RawMessage, error) {
			return nil, runtimecontrol.ErrMachineRuntimesUnsupported
		},
		BuildSpec: func() (RemoteRequestAssignmentSpec, error) {
			return newMachineRuntimeConfigUpdateRemoteRequestSpec(ctx, descriptor, workspaceID, machineID, name, config)
		},
		MapResult: machineRawBundleFromRemoteResult,
	})
}

func (hub *Hub) validateMachineRuntimeConfig(ctx context.Context, client *clientState, workspaceID, machineID, name string, config json.RawMessage) (json.RawMessage, error) {
	if client == nil {
		return nil, runtimecontrol.ErrMachineRuntimesUnavailable
	}
	descriptor, ok := hub.remoteEndpointDescriptor(remoteEndpointMachineRuntimeConfigValidate, 1)
	if !ok || !client.supportsRemoteEndpoint(remoteEndpointMachineRuntimeConfigValidate, 1, descriptor.RegistryVersion) {
		return nil, runtimecontrol.ErrMachineRuntimesUnsupported
	}
	if hub.elasticRequestLinks == nil {
		return nil, remoteRequestStableError(RemoteRequestStableErrorDesktopUnavailable)
	}
	return invokeRemoteEndpoint(RemoteEndpointInvocation[json.RawMessage]{
		Context:      ctx,
		Client:       client,
		Manager:      hub.elasticRequestLinks,
		Descriptor:   descriptor,
		RouteOptions: remoteRequestRouteOptionsFromContext(ctx),
		Fallback: func() (json.RawMessage, error) {
			return nil, runtimecontrol.ErrMachineRuntimesUnsupported
		},
		BuildSpec: func() (RemoteRequestAssignmentSpec, error) {
			return newMachineRuntimeConfigUpdateRemoteRequestSpec(ctx, descriptor, workspaceID, machineID, name, config)
		},
		MapResult: machineRawBundleFromRemoteResult,
	})
}

func (hub *Hub) detectMachineRuntimes(ctx context.Context, client *clientState, workspaceID, machineID string) (json.RawMessage, error) {
	if client == nil {
		return nil, runtimecontrol.ErrMachineRuntimesUnavailable
	}
	descriptor, ok := hub.remoteEndpointDescriptor(remoteEndpointMachineRuntimesDetect, 1)
	if !ok || !client.supportsRemoteEndpoint(remoteEndpointMachineRuntimesDetect, 1, descriptor.RegistryVersion) {
		return nil, runtimecontrol.ErrMachineRuntimesUnsupported
	}
	if hub.elasticRequestLinks == nil {
		return nil, remoteRequestStableError(RemoteRequestStableErrorDesktopUnavailable)
	}
	return invokeRemoteEndpoint(RemoteEndpointInvocation[json.RawMessage]{
		Context:      ctx,
		Client:       client,
		Manager:      hub.elasticRequestLinks,
		Descriptor:   descriptor,
		RouteOptions: remoteRequestRouteOptionsFromContext(ctx),
		Fallback: func() (json.RawMessage, error) {
			return nil, runtimecontrol.ErrMachineRuntimesUnsupported
		},
		BuildSpec: func() (RemoteRequestAssignmentSpec, error) {
			return newMachineRuntimesKeyedRemoteRequestSpec(ctx, descriptor, "machine_runtimes_detect", workspaceID, machineID, "")
		},
		MapResult: machineRawBundleFromRemoteResult,
	})
}

func newMachineRuntimesKeyedRemoteRequestSpec(ctx context.Context, descriptor RemoteEndpointDescriptor, requestKind, workspaceID, machineID, name string) (RemoteRequestAssignmentSpec, error) {
	payload, err := json.Marshal(machineRuntimeConfigRemoteRequestPayload{
		WorkspaceID: strings.TrimSpace(workspaceID),
		MachineID:   strings.TrimSpace(machineID),
		Name:        strings.TrimSpace(name),
	})
	if err != nil {
		return RemoteRequestAssignmentSpec{}, err
	}
	if int64(len(payload)) > descriptor.Limits.RequestBytes {
		return RemoteRequestAssignmentSpec{}, runtimecontrol.ErrMachineRuntimesInvalid
	}
	return newRemoteRequestAssignmentSpec(ctx, descriptor, requestKind, json.RawMessage(payload))
}

func newMachineRuntimeConfigUpdateRemoteRequestSpec(ctx context.Context, descriptor RemoteEndpointDescriptor, workspaceID, machineID, name string, config json.RawMessage) (RemoteRequestAssignmentSpec, error) {
	payload, err := json.Marshal(machineRuntimeConfigRemoteRequestPayload{
		WorkspaceID: strings.TrimSpace(workspaceID),
		MachineID:   strings.TrimSpace(machineID),
		Name:        strings.TrimSpace(name),
		Config:      config,
	})
	if err != nil {
		return RemoteRequestAssignmentSpec{}, err
	}
	if int64(len(payload)) > descriptor.Limits.RequestBytes {
		return RemoteRequestAssignmentSpec{}, runtimecontrol.ErrMachineRuntimesInvalid
	}
	return newRemoteRequestAssignmentSpec(ctx, descriptor, "machine_runtime_config_update", json.RawMessage(payload))
}

func classifyMachineRuntimesError(err error) error {
	if err == nil {
		return nil
	}
	if errors.Is(err, runtimecontrol.ErrMachineRuntimesInvalid) {
		return runtimecontrol.ErrMachineRuntimesInvalid
	}
	if errors.Is(err, runtimecontrol.ErrMachineRuntimesUnsupported) {
		return runtimecontrol.ErrMachineRuntimesUnsupported
	}
	if errors.Is(err, runtimecontrol.ErrMachineOffline) || errors.Is(err, context.DeadlineExceeded) || errors.Is(err, context.Canceled) {
		return runtimecontrol.ErrMachineRuntimesUnavailable
	}
	var stable *stableRemoteRequestError
	if errors.As(err, &stable) {
		switch stable.code {
		case RemoteRequestStableErrorDesktopUnavailable, RemoteRequestStableErrorDesktopBusy:
			return runtimecontrol.ErrMachineRuntimesUnavailable
		}
	}
	return runtimecontrol.ErrMachineRuntimesUnavailable
}

// --- Machine MCP servers --------------------------------------------------

func (hub *Hub) queryMachineMCPServers(ctx context.Context, client *clientState, workspaceID, machineID string) (json.RawMessage, error) {
	if client == nil {
		return nil, runtimecontrol.ErrMachineMCPServersUnavailable
	}
	descriptor, ok := hub.remoteEndpointDescriptor(remoteEndpointMachineMCPServerList, 1)
	if !ok || !client.supportsRemoteEndpoint(remoteEndpointMachineMCPServerList, 1, descriptor.RegistryVersion) {
		return nil, runtimecontrol.ErrMachineMCPServersUnsupported
	}
	if hub.elasticRequestLinks == nil {
		return nil, remoteRequestStableError(RemoteRequestStableErrorDesktopUnavailable)
	}
	return invokeRemoteEndpoint(RemoteEndpointInvocation[json.RawMessage]{
		Context:      ctx,
		Client:       client,
		Manager:      hub.elasticRequestLinks,
		Descriptor:   descriptor,
		RouteOptions: remoteRequestRouteOptionsFromContext(ctx),
		Fallback: func() (json.RawMessage, error) {
			return nil, runtimecontrol.ErrMachineMCPServersUnsupported
		},
		BuildSpec: func() (RemoteRequestAssignmentSpec, error) {
			return newMachineMCPServerKeyedRemoteRequestSpec(ctx, descriptor, "machine_mcp_servers_list", workspaceID, machineID, "")
		},
		MapResult: machineRawBundleFromRemoteResult,
	})
}

func (hub *Hub) upsertMachineMCPServer(ctx context.Context, client *clientState, workspaceID, machineID string, server json.RawMessage) (json.RawMessage, error) {
	if client == nil {
		return nil, runtimecontrol.ErrMachineMCPServersUnavailable
	}
	descriptor, ok := hub.remoteEndpointDescriptor(remoteEndpointMachineMCPServerUpsert, 1)
	if !ok || !client.supportsRemoteEndpoint(remoteEndpointMachineMCPServerUpsert, 1, descriptor.RegistryVersion) {
		return nil, runtimecontrol.ErrMachineMCPServersUnsupported
	}
	if hub.elasticRequestLinks == nil {
		return nil, remoteRequestStableError(RemoteRequestStableErrorDesktopUnavailable)
	}
	return invokeRemoteEndpoint(RemoteEndpointInvocation[json.RawMessage]{
		Context:      ctx,
		Client:       client,
		Manager:      hub.elasticRequestLinks,
		Descriptor:   descriptor,
		RouteOptions: remoteRequestRouteOptionsFromContext(ctx),
		Fallback: func() (json.RawMessage, error) {
			return nil, runtimecontrol.ErrMachineMCPServersUnsupported
		},
		BuildSpec: func() (RemoteRequestAssignmentSpec, error) {
			return newMachineMCPServerUpsertRemoteRequestSpec(ctx, descriptor, workspaceID, machineID, server)
		},
		MapResult: machineRawBundleFromRemoteResult,
	})
}

func (hub *Hub) deleteMachineMCPServer(ctx context.Context, client *clientState, workspaceID, machineID, name string) (json.RawMessage, error) {
	if client == nil {
		return nil, runtimecontrol.ErrMachineMCPServersUnavailable
	}
	descriptor, ok := hub.remoteEndpointDescriptor(remoteEndpointMachineMCPServerDelete, 1)
	if !ok || !client.supportsRemoteEndpoint(remoteEndpointMachineMCPServerDelete, 1, descriptor.RegistryVersion) {
		return nil, runtimecontrol.ErrMachineMCPServersUnsupported
	}
	if hub.elasticRequestLinks == nil {
		return nil, remoteRequestStableError(RemoteRequestStableErrorDesktopUnavailable)
	}
	return invokeRemoteEndpoint(RemoteEndpointInvocation[json.RawMessage]{
		Context:      ctx,
		Client:       client,
		Manager:      hub.elasticRequestLinks,
		Descriptor:   descriptor,
		RouteOptions: remoteRequestRouteOptionsFromContext(ctx),
		Fallback: func() (json.RawMessage, error) {
			return nil, runtimecontrol.ErrMachineMCPServersUnsupported
		},
		BuildSpec: func() (RemoteRequestAssignmentSpec, error) {
			return newMachineMCPServerKeyedRemoteRequestSpec(ctx, descriptor, "machine_mcp_servers_delete", workspaceID, machineID, name)
		},
		MapResult: machineMCPServerDeleteFromRemoteResult,
	})
}

func (hub *Hub) testMachineMCPServer(ctx context.Context, client *clientState, workspaceID, machineID, name string) (json.RawMessage, error) {
	if client == nil {
		return nil, runtimecontrol.ErrMachineMCPServersUnavailable
	}
	descriptor, ok := hub.remoteEndpointDescriptor(remoteEndpointMachineMCPServerTest, 1)
	if !ok || !client.supportsRemoteEndpoint(remoteEndpointMachineMCPServerTest, 1, descriptor.RegistryVersion) {
		return nil, runtimecontrol.ErrMachineMCPServersUnsupported
	}
	if hub.elasticRequestLinks == nil {
		return nil, remoteRequestStableError(RemoteRequestStableErrorDesktopUnavailable)
	}
	return invokeRemoteEndpoint(RemoteEndpointInvocation[json.RawMessage]{
		Context:      ctx,
		Client:       client,
		Manager:      hub.elasticRequestLinks,
		Descriptor:   descriptor,
		RouteOptions: remoteRequestRouteOptionsFromContext(ctx),
		Fallback: func() (json.RawMessage, error) {
			return nil, runtimecontrol.ErrMachineMCPServersUnsupported
		},
		BuildSpec: func() (RemoteRequestAssignmentSpec, error) {
			return newMachineMCPServerKeyedRemoteRequestSpec(ctx, descriptor, "machine_mcp_servers_test", workspaceID, machineID, name)
		},
		MapResult: machineRawBundleFromRemoteResult,
	})
}

func newMachineMCPServerKeyedRemoteRequestSpec(ctx context.Context, descriptor RemoteEndpointDescriptor, requestKind, workspaceID, machineID, name string) (RemoteRequestAssignmentSpec, error) {
	payload, err := json.Marshal(machineMCPServerKeyedRemoteRequestPayload{
		WorkspaceID: strings.TrimSpace(workspaceID),
		MachineID:   strings.TrimSpace(machineID),
		Name:        strings.TrimSpace(name),
	})
	if err != nil {
		return RemoteRequestAssignmentSpec{}, err
	}
	if int64(len(payload)) > descriptor.Limits.RequestBytes {
		return RemoteRequestAssignmentSpec{}, runtimecontrol.ErrMachineMCPServersInvalid
	}
	return newRemoteRequestAssignmentSpec(ctx, descriptor, requestKind, json.RawMessage(payload))
}

func newMachineMCPServerUpsertRemoteRequestSpec(ctx context.Context, descriptor RemoteEndpointDescriptor, workspaceID, machineID string, server json.RawMessage) (RemoteRequestAssignmentSpec, error) {
	payload, err := json.Marshal(machineMCPServerUpsertRemoteRequestPayload{
		WorkspaceID: strings.TrimSpace(workspaceID),
		MachineID:   strings.TrimSpace(machineID),
		Server:      server,
	})
	if err != nil {
		return RemoteRequestAssignmentSpec{}, err
	}
	if int64(len(payload)) > descriptor.Limits.RequestBytes {
		return RemoteRequestAssignmentSpec{}, runtimecontrol.ErrMachineMCPServersInvalid
	}
	return newRemoteRequestAssignmentSpec(ctx, descriptor, "machine_mcp_servers_upsert", json.RawMessage(payload))
}

func machineMCPServerDeleteFromRemoteResult(result RemoteRequestResult[json.RawMessage]) (json.RawMessage, error) {
	if err := remoteEndpointResultError(result); err != nil {
		return nil, err
	}
	// Daemon may return either a typed {"ok":true} envelope or an empty body
	// for a successful delete. Synthesize {"ok":true} when the payload is
	// empty so the web client gets a stable shape.
	if len(strings.TrimSpace(string(result.Payload))) == 0 {
		return json.RawMessage(`{"ok":true}`), nil
	}
	if !json.Valid(result.Payload) {
		return nil, errors.New("machine mcp server delete payload is not valid JSON")
	}
	return result.Payload, nil
}

func classifyMachineMCPServersError(err error) error {
	if err == nil {
		return nil
	}
	if errors.Is(err, runtimecontrol.ErrMachineMCPServersInvalid) {
		return runtimecontrol.ErrMachineMCPServersInvalid
	}
	if errors.Is(err, runtimecontrol.ErrMachineMCPServersUnsupported) {
		return runtimecontrol.ErrMachineMCPServersUnsupported
	}
	if errors.Is(err, runtimecontrol.ErrMachineOffline) || errors.Is(err, context.DeadlineExceeded) || errors.Is(err, context.Canceled) {
		return runtimecontrol.ErrMachineMCPServersUnavailable
	}
	var stable *stableRemoteRequestError
	if errors.As(err, &stable) {
		switch stable.code {
		case RemoteRequestStableErrorDesktopUnavailable, RemoteRequestStableErrorDesktopBusy:
			return runtimecontrol.ErrMachineMCPServersUnavailable
		}
	}
	return runtimecontrol.ErrMachineMCPServersUnavailable
}
