export type PluginPermission = | "server.create" | "server.read" | "server.lifecycle" | "server.files.read" | "server.files.write" | "server.logs.read" | "server.artifacts.read" | "server.artifacts.write" | "server.remote.access" | "server.run.distribution" | "server.dependencies.manage" | "server.game-client.read" | "server.game-client.command" | "server.game-client.maintenance" | "ai.invoke"; export type RunCapability = | "process.install" | "process.start" | "process.stop" | "process.restart" | "process.status" | "deployment.plan.v1" | "config.write" | "files.list" | "files.read" | "files.write" | "files.patch" | "logs.read" | "run.self-update" | "distribution.build" | "dependencies.check" | "dependencies.install" | "logs.backfill" | "remote.ftp.read" | "remote.ftp.write" | "remote.rsync.read" | "remote.rsync.write" | "remote.run.files.read" | "remote.run.files.write" | "remote.run.process.start" | "remote.run.process.stop" | "remote.run.db.mysql.query" | "remote.run.db.mysql.execute" | "remote.run.db.sqlite.query" | "remote.run.db.sqlite.execute" | "remote.run.logs.transfer" | "remote.run.rcon.command" | "remote.run.program.command" | "artifacts.read" | "artifacts.write" | "ai.invoke"; export type AIPurpose = "config.read" | "config.generate" | "config.suggest" | "logs.diagnose" | "files.suggest"; export type PluginBridgeAction = | "server.instances.read" | "jobs.dispatch" | "logs.query" | "artifacts.open" | "files.request" | "remote.access.request" | "run.distribution.request" | "dependencies.request" | "logs.backfill.request" | "plugin-lifecycle.request" | "ai.invoke"; export type PluginBridgeRequestPayload = Record; export type PluginBridgeRequest = { id: string; pluginId: string; routeKey: string; serverInstanceId?: string; action: PluginBridgeAction; payload: TPayload; }; export type PluginBridgeResponse = | { id: string; ok: true; result: TResult; } | { id: string; ok: false; error: PluginBridgeError; }; export interface PluginBridgeError { code: "unsupported_action" | "missing_permission" | "invalid_payload" | "denied" | "permission_denied" | "unsafe_payload" | "platform_error" | "deferred"; message: string; details?: string[]; } export interface PluginBridgeExecutionRequest = Record> { requestId: string; pluginId: string; routeKey: string; serverInstanceId?: string; action: PluginBridgeAction; aiPurpose?: AIPurpose; payload?: TPayload; } export interface PluginAIInvocationRequest { requestId: string; pluginId: string; routeKey: string; serverInstanceId?: string; purpose: AIPurpose; prompt: string; currentConfig?: string; contextRefs?: Record; } export interface PluginAIInvocationResponse { requestId: string; purpose: AIPurpose; status: "ok" | "denied" | "error" | string; recommendation?: string; configRecommendation?: { diffId: string; key: string; suggestedConfig: string; diffSummary: string; expiresAt: string }; usage?: { model?: string; mocked?: boolean; inputTokens?: number; outputTokens?: number }; error?: PluginBridgeError; } export type PluginArtifactOpenPayload = Record & { artifactId: string; }; export type PluginLifecycleDispatchPayload = Record & { lifecycleAction: "start" | "stop"; capability: "process.start" | "process.stop"; expectedConfigVersion: string; idempotencyKey: string; }; export type ProductionPluginLifecycleOperation = "install" | "enable" | "disable" | "upgrade" | "rollback" | "retire" | "dependency-check"; export type PluginProductionLifecyclePayload = Record & { operation: ProductionPluginLifecycleOperation; targetVersion: string; idempotencyKey: string; }; export type PluginRemoteAccessPayload = Record & { capability: Extract; targetKey?: string; inputRef?: string; idempotencyKey: string; }; export type PluginRunDistributionPayload = Record & { operation: "generate" | "download" | "reset-key" | "update"; targetOS?: RuntimePlatform; targetArch?: RuntimeArch; artifactId?: string; idempotencyKey: string; }; export type PluginDependencyActionPayload = Record & { operation: "check" | "install"; probeKey?: string; planKey?: string; planDigest?: string; idempotencyKey: string; }; export type PluginLogBackfillPayload = Record & { sourceKey: string; cursor?: string; limit?: string; idempotencyKey: string; }; export type RemoteAccessMethod = "ftp" | "rsync" | "run"; export type RemoteDatabaseEngine = "mysql" | "sqlite"; export interface GamePluginRemoteAccess { methods: RemoteAccessMethod[]; runCapabilities?: Array>; databaseEngines?: RemoteDatabaseEngine[]; rcon?: boolean; logTransfer?: boolean; } export type GameClientBridgeCommandState = "pending" | "claimed" | "succeeded" | "failed" | "unknown" | "cancelled" | "expired"; export interface GameClientBridgeCommandDeclaration { type: string; title: string; permission: PluginPermission; payloadSchemaRef: string; resultSchemaRef?: string; timeoutSeconds: number; maxPayloadBytes: number; } export interface GameClientBridgeSnapshotDeclaration { type: string; schemaVersion: string; schemaRef: string; keepForSeconds: number; maxRecords: number; } export interface GameClientBridgeQueryTemplateDeclaration { key: string; title: string; permission: PluginPermission; engine: "sqlite"; transportKey: string; targetKey: string; parameterSchemaRef: string; resultSchemaRef: string; sqlRef?: string; maxRows: number; timeoutSeconds: number; pollIntervalSeconds?: number; projections?: GameClientBridgeQueryProjectionDeclaration[]; } export interface GameClientBridgeQueryProjectionDeclaration { collection: string; rowPath: "rows"; matchField?: string; matchValue?: string; upsertKeys: string[]; fieldMappings?: Record; fixedValues?: Record; observedAtField?: string; mergeExisting?: boolean; } export interface GameClientBridgeDataPackDeclaration { key: string; databaseUserVersion: number; configMapRefs: string[]; } export interface GameClientBridgePageContract { pageKey: string; commandTypes?: string[]; snapshotTypes?: string[]; queryTemplateKeys?: string[]; featureKeys?: string[]; } export interface GameClientBridgeFeatureDeclaration { key: string; title: string; permission: PluginPermission; requiredHandlers?: string[]; requiredEventProducers?: string[]; } export interface GameClientBridgeManifest { commands: GameClientBridgeCommandDeclaration[]; snapshots: GameClientBridgeSnapshotDeclaration[]; queryTemplates?: GameClientBridgeQueryTemplateDeclaration[]; dataPacks?: GameClientBridgeDataPackDeclaration[]; commandRetentionSeconds: number; maxCommands: number; pages?: GameClientBridgePageContract[]; features?: GameClientBridgeFeatureDeclaration[]; } export interface GameClientBridgeProfileStatus { pluginId: string; profileKey: string; available: boolean; reason?: string; commandTypes: string[]; snapshotTypes: string[]; queryTemplateKeys: string[]; handlerTypes?: string[]; eventProducerTypes?: string[]; } export interface GameClientBridgeFeatureAvailability { key: string; available: boolean; reason?: string; } export interface GameClientBridgeStatus { serverInstanceId: string; pluginId: string; available: boolean; reason?: string; profiles: GameClientBridgeProfileStatus[]; features?: GameClientBridgeFeatureAvailability[]; } export interface GameClientBridgeCommandResult { status: "succeeded" | "failed" | "unknown" | "cancelled"; summary?: string; payload?: Record; completedAt: string; } export interface GameClientBridgeCommand { id: string; serverInstanceId: string; pluginId: string; profileKey: string; commandType: string; priority: number; state: GameClientBridgeCommandState; result?: GameClientBridgeCommandResult; expiresAt: string; createdAt: string; updatedAt: string; completedAt?: string; } export interface GameClientBridgeSnapshot = Record> { id: string; serverInstanceId: string; pluginId: string; profileKey: string; type: string; schemaVersion: string; streamKey: string; sequence: number; observedAt: string; payload: TPayload; createdAt: string; expiresAt: string; } export interface GameClientBridgeQueueRequest = Record> { profileKey: string; commandType: string; payload: TPayload; idempotencyKey: string; priority?: number; expiresAt: string; } export interface GameClientBridgeSnapshotQuery { profileKey?: string; type?: string; streamKey?: string; observedAfter?: string; limit?: number; } export interface GameClientBridgePageClient { getStatus(serverInstanceId: string): Promise; listCommands(serverInstanceId: string): Promise; queueCommand>(serverInstanceId: string, request: GameClientBridgeQueueRequest): Promise; cancelCommand(serverInstanceId: string, commandId: string, reason?: string): Promise; querySnapshots = Record>(serverInstanceId: string, query?: GameClientBridgeSnapshotQuery): Promise>>; } export function createGameClientBridgeQueueRequest>(input: GameClientBridgeQueueRequest): GameClientBridgeQueueRequest { if (!input.profileKey || !input.commandType || !input.idempotencyKey || !input.expiresAt) { throw new Error("profileKey, commandType, idempotencyKey, and expiresAt are required"); } return { profileKey: input.profileKey, commandType: input.commandType, payload: { ...input.payload }, idempotencyKey: input.idempotencyKey, priority: input.priority, expiresAt: input.expiresAt }; } export type RuntimePlatform = "windows" | "linux" | "darwin"; export type RuntimeArch = "amd64" | "arm64"; export type RuntimeTarget = { os: RuntimePlatform; arch: RuntimeArch }; export interface RuntimeDiscoveryProbe { key: string; kind: "file.exists" | "command.version" | "service.status" | "port.open" | "steam.app" | "docker.container"; targetKey: string; required?: boolean; expected?: string; platforms?: RuntimePlatform[]; } export interface RuntimeLifecycleProfile { key: string; mode: "local-process" | "hosted-ftp-rcon" | "ftp-only"; capabilities: RunCapability[]; actionRefs?: Partial>; transportKeys?: string[]; dllExtensionRefs?: string[]; platforms?: RuntimePlatform[]; } export interface RuntimeDependencyProbe { key: string; kind: "command.version" | "service.exists" | "port.available" | "steam.app" | "java.version" | "docker.available" | "package.installed" | "file.exists"; targetKey: string; required?: boolean; minimumVersion?: string; platforms?: RuntimePlatform[]; } export interface RuntimeInstallStep { type: "package" | "verified-download" | "steamcmd-app" | "manual"; targetKey: string; packageManager?: "winget" | "choco" | "scoop" | "apt" | "yum" | "dnf" | "pacman" | "zypper" | "brew" | "steamcmd" | "manual"; packageName?: string; version?: string; downloadRef?: string; checksum?: `sha256:${string}`; } export interface RuntimeInstallPlan { key: string; title: string; platforms?: RuntimePlatform[]; steps: RuntimeInstallStep[]; } export interface RuntimeLogSource { key: string; kind: "process.stdout" | "process.stderr" | "file.tail" | "ftp.poll" | "sql.query"; targetKey?: string; streamKey: string; cursorKind?: "sequence" | "offset" | "fingerprint" | "ftp-listing" | "sql-cursor"; retentionDays?: number; } export interface RuntimeTransportProfile { key: string; kind: "file" | "ftp" | "rsync" | "mysql" | "sqlite" | "rcon"; targetKey?: string; capabilities: RunCapability[]; } export interface RuntimeDataTarget { key: string; kind: "sqlite.snapshot"; transportKey: string; sourceRootKey: string; sourcePath: string; workspaceKey: string; refreshPolicy: "on-demand-snapshot"; maxBytes: number; platforms?: RuntimePlatform[]; } export interface RuntimeDLLExtensionProfile { key: string; displayName: string; kind: "ue4ss-dll"; activation: "server-start"; version: string; releaseState: "unpublished" | "ready"; releaseUrl?: string; checksum?: `sha256:${string}`; sizeBytes?: number; targetKey: string; modKey: string; dllRef: string; scumExecutableChecksum?: `sha256:${string}`; ue4ssAbi?: string; supportedTargets: [{ os: "windows"; arch: "amd64" }]; updateOnStart: true; rconPort: number; } export interface GamePluginRuntimeProfiles { discovery?: RuntimeDiscoveryProbe[]; lifecycleProfiles?: RuntimeLifecycleProfile[]; dependencyProbes?: RuntimeDependencyProbe[]; installPlans?: RuntimeInstallPlan[]; logSources?: RuntimeLogSource[]; transportProfiles?: RuntimeTransportProfile[]; dataTargets?: RuntimeDataTarget[]; dllExtensions?: RuntimeDLLExtensionProfile[]; } export interface PluginArtifactReference { artifactId: string; filename: string; contentType: string; sizeBytes: number; checksum: string; downloadUrl: string; expiresAt: string; rangeSupported: boolean; chunkSizeBytes: number; storageBehavior?: string; } export type PluginBridgeExecutionResponse = Record> = { requestId: string; pluginId: string; routeKey: string; serverInstanceId?: string; action: PluginBridgeAction; status: "ok" | "queued" | "denied" | "unsupported" | "cancelled" | "error" | string; result?: TResult; error?: PluginBridgeError; }; export type PluginBridgeActionPolicy = { permissions: PluginPermission[]; aiPurposeRequired?: boolean; }; export const pluginBridgeActionPolicies: Record = { "server.instances.read": { permissions: ["server.read"] }, "jobs.dispatch": { permissions: ["server.lifecycle"] }, "logs.query": { permissions: ["server.logs.read"] }, "artifacts.open": { permissions: ["server.artifacts.read"] }, "files.request": { permissions: ["server.files.read"] }, "remote.access.request": { permissions: ["server.remote.access"] }, "run.distribution.request": { permissions: ["server.run.distribution"] }, "dependencies.request": { permissions: ["server.dependencies.manage"] }, "logs.backfill.request": { permissions: ["server.logs.read"] }, "plugin-lifecycle.request": { permissions: ["server.lifecycle"] }, "ai.invoke": { permissions: ["ai.invoke"], aiPurposeRequired: true } }; export type PluginLifecycleAction = "install" | "start" | "stop" | "restart" | "status"; export interface PluginLifecycleActionDeclaration { version: 1; action: PluginLifecycleAction; mode: "oneshot" | "supervised" | "control"; executableKey?: string; arguments?: string[]; environment?: Record<`GAME_${string}` | `SERVER_${string}` | `RUN_${string}`, string>; outputMode?: "pipes" | "console"; timeoutMs?: number; stopTimeoutMs?: number; } export type GamePluginActions = Partial> & { install: string; start: string; stop: string; }; export interface GamePluginPage { key: string; title: string; path: string; bundleKey: string; bundleVersion: string; bundleIntegritySha256: `sha256:${string}`; permissions?: PluginPermission[]; bridgeActions?: PluginBridgeAction[]; } export interface GamePluginBridge { actions: PluginBridgeAction[]; } export interface PluginAssetFile { path: string; mode?: 384 | 448; } export interface GamePluginManifest { id: `game.${string}`; name: string; description?: string; version: string; kind: "game-plugin"; tags?: string[]; server: { type: string; displayName: string; supportedOS?: Array<"windows" | "linux" | "darwin">; createFormSchema: string; }; bridge?: GamePluginBridge; capabilities: RunCapability[]; permissions: PluginPermission[]; remoteAccess?: GamePluginRemoteAccess; runtimeProfiles?: GamePluginRuntimeProfiles; gameClientBridge?: GameClientBridgeManifest; actions?: GamePluginActions; assetFiles?: PluginAssetFile[]; productionLifecycle: { operations: ProductionPluginLifecycleOperation[]; dependencyPolicy: "required" | "optional"; }; pages?: GamePluginPage[]; ai?: { purposes?: AIPurpose[]; mediation: "platform"; configWritePolicy: "review-required"; }; } export interface PluginBridgeContext { pluginId: string; routeKey: string; serverInstanceId?: string; permissions: PluginPermission[]; aiPurposes?: AIPurpose[]; } export function hasPluginPermission(context: PluginBridgeContext, permission: PluginPermission): boolean { return context.permissions.includes(permission); } export function canRequestBridgeAction( context: PluginBridgeContext, action: PluginBridgeAction, options: { aiPurpose?: AIPurpose } = {} ): boolean { const policy = pluginBridgeActionPolicies[action]; if (!policy.permissions.every((permission) => hasPluginPermission(context, permission))) { return false; } if (policy.aiPurposeRequired) { return typeof options.aiPurpose === "string" && (context.aiPurposes ?? []).includes(options.aiPurpose); } return true; } export function createBridgeRequest(input: { id: string; context: PluginBridgeContext; action: PluginBridgeAction; payload: TPayload; }): PluginBridgeRequest { return { id: input.id, pluginId: input.context.pluginId, routeKey: input.context.routeKey, serverInstanceId: input.context.serverInstanceId, action: input.action, payload: input.payload }; } export function createBridgeExecutionRequest>(input: { requestId: string; context: PluginBridgeContext; action: PluginBridgeAction; aiPurpose?: AIPurpose; payload?: TPayload; }): PluginBridgeExecutionRequest { return { requestId: input.requestId, pluginId: input.context.pluginId, routeKey: input.context.routeKey, serverInstanceId: input.context.serverInstanceId, action: input.action, aiPurpose: input.aiPurpose, payload: input.payload }; } export function createArtifactOpenRequest(input: { requestId: string; context: PluginBridgeContext; artifactId: string; }): PluginBridgeExecutionRequest { return createBridgeExecutionRequest({ requestId: input.requestId, context: input.context, action: "artifacts.open", payload: { artifactId: input.artifactId } }); } export function createLifecycleDispatchRequest(input: { requestId: string; context: PluginBridgeContext; action: "start" | "stop"; expectedConfigVersion: number; idempotencyKey: string; }): PluginBridgeExecutionRequest { return createBridgeExecutionRequest({ requestId: input.requestId, context: input.context, action: "jobs.dispatch", payload: { lifecycleAction: input.action, capability: input.action === "start" ? "process.start" : "process.stop", expectedConfigVersion: String(input.expectedConfigVersion), idempotencyKey: input.idempotencyKey } }); } export function createProductionPluginLifecycleRequest(input: { requestId: string; context: PluginBridgeContext; operation: ProductionPluginLifecycleOperation; targetVersion?: string; idempotencyKey: string; }): PluginBridgeExecutionRequest { if (!input.context.serverInstanceId) { throw new Error("serverInstanceId is required for plugin lifecycle requests"); } return createBridgeExecutionRequest({ requestId: input.requestId, context: input.context, action: "plugin-lifecycle.request", payload: { operation: input.operation, targetVersion: input.targetVersion ?? "", idempotencyKey: input.idempotencyKey } }); } export function createRemoteAccessRequest(input: { requestId: string; context: PluginBridgeContext; capability: PluginRemoteAccessPayload["capability"]; targetKey?: string; inputRef?: string; inputs?: Record; idempotencyKey: string; }): PluginBridgeExecutionRequest { const payload: PluginRemoteAccessPayload = { capability: input.capability, targetKey: input.targetKey ?? "", inputRef: input.inputRef ?? "", idempotencyKey: input.idempotencyKey }; for (const [key, value] of Object.entries(input.inputs ?? {})) { if (!/^[A-Za-z0-9][A-Za-z0-9._:-]{0,159}$/.test(key) || /(sql|query|shell|script|password|secret|token|credential|dsn|path)/i.test(key)) { throw new Error(`remote adapter input key is unsafe: ${key}`); } payload[`input.${key}`] = value; } return createBridgeExecutionRequest({ requestId: input.requestId, context: input.context, action: "remote.access.request", payload }); } export function createRunDistributionRequest(input: { requestId: string; context: PluginBridgeContext; operation: PluginRunDistributionPayload["operation"]; targetOS?: RuntimePlatform; targetArch?: RuntimeArch; artifactId?: string; idempotencyKey: string; }): PluginBridgeExecutionRequest { const payload: PluginRunDistributionPayload = { operation: input.operation, artifactId: input.artifactId ?? "", idempotencyKey: input.idempotencyKey }; if (input.targetOS) { payload.targetOS = input.targetOS; } if (input.targetArch) { payload.targetArch = input.targetArch; } return createBridgeExecutionRequest({ requestId: input.requestId, context: input.context, action: "run.distribution.request", payload }); } export function createDependencyActionRequest(input: { requestId: string; context: PluginBridgeContext; operation: PluginDependencyActionPayload["operation"]; probeKey?: string; planKey?: string; planDigest?: string; idempotencyKey: string; }): PluginBridgeExecutionRequest { if (input.operation === "install" && !/^sha256:[a-fA-F0-9]{64}$/.test(input.planDigest ?? "")) { throw new Error("dependency install requires the reviewed plan SHA-256 digest"); } return createBridgeExecutionRequest({ requestId: input.requestId, context: input.context, action: "dependencies.request", payload: { operation: input.operation, probeKey: input.probeKey ?? "", planKey: input.planKey ?? "", planDigest: input.planDigest ?? "", idempotencyKey: input.idempotencyKey } }); } export function createLogBackfillRequest(input: { requestId: string; context: PluginBridgeContext; sourceKey: string; cursor?: string; limit?: number; idempotencyKey: string; }): PluginBridgeExecutionRequest { return createBridgeExecutionRequest({ requestId: input.requestId, context: input.context, action: "logs.backfill.request", payload: { sourceKey: input.sourceKey, cursor: input.cursor ?? "", limit: typeof input.limit === "number" ? String(input.limit) : "", idempotencyKey: input.idempotencyKey } }); } export function parseArtifactReference(result: Record | undefined): PluginArtifactReference | undefined { if (!result) { return undefined; } const sizeBytes = Number(result.sizeBytes); const chunkSizeBytes = Number(result.chunkSizeBytes); const reference: PluginArtifactReference = { artifactId: result.artifactId ?? "", filename: result.filename ?? "artifact.bin", contentType: result.contentType ?? "application/octet-stream", sizeBytes, checksum: result.checksum ?? "", downloadUrl: result.downloadUrl ?? "", expiresAt: result.expiresAt ?? "", rangeSupported: result.rangeSupported === "true", chunkSizeBytes, storageBehavior: result.storageBehavior }; if (!reference.artifactId || !Number.isFinite(sizeBytes) || sizeBytes <= 0 || !Number.isFinite(chunkSizeBytes) || chunkSizeBytes <= 0) { return undefined; } if (!reference.downloadUrl.startsWith(`/api/v1/artifacts/${encodeURIComponent(reference.artifactId)}/content`)) { return undefined; } return reference; } export function parseBridgeExecutionResponse>(response: PluginBridgeExecutionResponse): PluginBridgeExecutionResponse { const safeError = response.error ? bridgeError(response.error.code, response.error.message, response.error.details ?? []) : undefined; return { requestId: response.requestId, pluginId: response.pluginId, routeKey: response.routeKey, serverInstanceId: response.serverInstanceId, action: response.action, status: response.status, result: response.result ? { ...response.result } : undefined, error: safeError }; } export function createAIInvocationRequest(input: { requestId: string; context: PluginBridgeContext; purpose: AIPurpose; prompt: string; currentConfig?: string; contextRefs?: Record; }): PluginAIInvocationRequest { return { requestId: input.requestId, pluginId: input.context.pluginId, routeKey: input.context.routeKey, serverInstanceId: input.context.serverInstanceId, purpose: input.purpose, prompt: input.prompt, currentConfig: input.currentConfig, contextRefs: input.contextRefs }; } export function parseAIInvocationResponse(response: PluginAIInvocationResponse): PluginAIInvocationResponse { return { requestId: response.requestId, purpose: response.purpose, status: response.status, recommendation: response.recommendation, configRecommendation: response.configRecommendation ? { ...response.configRecommendation } : undefined, usage: response.usage ? { ...response.usage } : undefined, error: response.error ? bridgeError(response.error.code, response.error.message, response.error.details ?? []) : undefined }; } export function bridgeError( code: PluginBridgeError["code"], message: string, details: string[] = [] ): PluginBridgeError { return { code, message, details }; }