import type { GameClientBridgeCommandDeclarationResponse, GameClientBridgeQueryTemplateDeclarationResponse, GameClientBridgeSnapshotDeclarationResponse, GamePluginResponse, PluginProductionLifecycleDeclaration, RuntimeLogEventResponse, RuntimeLogSourceResponse } from "../api/types"; import { isPluginBridgeAction, isPluginPermission, type PluginBridgeAction, type PluginPermission } from "./pluginBridge"; export const scumOperationsPluginId = "game.scum"; export const scumOperationsRouteKey = "files-config"; export interface ScumOperationsPageContract { pluginId: typeof scumOperationsPluginId; routeKey: typeof scumOperationsRouteKey; serverInstanceId: string; title: string; permissions: PluginPermission[]; bridgeActions: PluginBridgeAction[]; commands: GameClientBridgeCommandDeclarationResponse[]; snapshots: GameClientBridgeSnapshotDeclarationResponse[]; queryTemplates: GameClientBridgeQueryTemplateDeclarationResponse[]; logSources: RuntimeLogSourceResponse[]; logEvents: RuntimeLogEventResponse[]; productionLifecycle: PluginProductionLifecycleDeclaration; } export interface ScumCompanionHealthView { status: "online" | "degraded" | "offline" | "unknown"; version?: string; observedAt?: string; latencyMs?: number; capabilities: string[]; } export interface ScumSessionView { sessionId: string; playerName: string; startedAt?: string; } export interface ScumPlayerView { playerId: string; playerName: string; status: string; squadId?: string; pingMs?: number; lastSeenAt?: string; } export interface ScumSquadView { squadId: string; name: string; memberCount: number; leaderPlayerId?: string; lastActiveAt?: string; } export interface ScumVehicleView { vehicleId: string; vehicleType: string; status: string; ownerPlayerId?: string; squadId?: string; fuelPercent?: number; healthPercent?: number; lastSeenAt?: string; } export interface ScumFlagView { flagId: string; status: string; ownerPlayerId?: string; squadId?: string; radiusMeters?: number; lastUpdatedAt?: string; } export interface ScumSnapshotCollection { observedAt?: string; total: number; items: T[]; } export interface ScumOperationsSnapshotView { health?: ScumCompanionHealthView; sessions: ScumSnapshotCollection; players: ScumSnapshotCollection; squads: ScumSnapshotCollection; vehicles: ScumSnapshotCollection; flags: ScumSnapshotCollection; } export type ScumOperationsPageResolution = | { available: true; contract: ScumOperationsPageContract } | { available: false; reason: string }; type ScumPluginProjection = Pick; export function normalizeScumRouteKey(pluginId: string, routeKey: string): string { return pluginId === scumOperationsPluginId && ["overview", "operations", "config", "logs"].includes(routeKey) ? scumOperationsRouteKey : routeKey; } export function resolveScumOperationsPageContract(plugin: ScumPluginProjection, serverInstanceId: string): ScumOperationsPageResolution { if (plugin.id !== scumOperationsPluginId) { return { available: false, reason: "该路由仅承载 game.scum 插件声明的运维页。" }; } if (!serverInstanceId.trim()) { return { available: false, reason: "缺少服务器实例上下文。" }; } const page = plugin.pages.find((candidate) => candidate.key === scumOperationsRouteKey); if (!page) { return { available: false, reason: "SCUM 插件未声明 operations 页面。" }; } const manifest = plugin.gameClientBridge; const bridgePage = manifest?.pages?.find((candidate) => candidate.pageKey === scumOperationsRouteKey); if (!manifest || !bridgePage) { return { available: false, reason: "SCUM 插件未声明 operations 的 Game Client Bridge 契约。" }; } const commandTypes = new Set(bridgePage.commandTypes ?? []); const snapshotTypes = new Set(bridgePage.snapshotTypes ?? []); const queryTemplateKeys = new Set(bridgePage.queryTemplateKeys ?? []); return { available: true, contract: { pluginId: scumOperationsPluginId, routeKey: scumOperationsRouteKey, serverInstanceId, title: page.title, permissions: page.permissions.filter(isPluginPermission), bridgeActions: (page.bridgeActions ?? []).filter(isPluginBridgeAction), commands: manifest.commands.filter((command) => commandTypes.has(command.type)), snapshots: manifest.snapshots.filter((snapshot) => snapshotTypes.has(snapshot.type)), queryTemplates: (manifest.queryTemplates ?? []).filter((template) => queryTemplateKeys.has(template.key)), logSources: [...(plugin.runtimeProfiles?.logSources ?? [])], logEvents: [...(plugin.runtimeProfiles?.logEvents ?? [])], productionLifecycle: plugin.productionLifecycle } }; }