feat: 完整游戏运维功能
This commit is contained in:
+141
-1
@@ -36,6 +36,11 @@ export type RunCapability =
|
||||
| "remote.run.db.sqlite.query"
|
||||
| "remote.run.logs.transfer"
|
||||
| "remote.run.rcon.command"
|
||||
| "client-manager.deploy"
|
||||
| "client-manager.control"
|
||||
| "client-manager.update"
|
||||
| "client-manager.rollback"
|
||||
| "client-manager.uninstall"
|
||||
| "artifacts.read"
|
||||
| "artifacts.write"
|
||||
| "ai.invoke";
|
||||
@@ -145,6 +150,7 @@ export type PluginDependencyActionPayload = Record<string, string> & {
|
||||
operation: "check" | "install";
|
||||
probeKey?: string;
|
||||
planKey?: string;
|
||||
planDigest?: string;
|
||||
idempotencyKey: string;
|
||||
};
|
||||
|
||||
@@ -156,11 +162,26 @@ export type PluginLogBackfillPayload = Record<string, string> & {
|
||||
};
|
||||
|
||||
export type PluginClientManagerPayload = Record<string, string> & {
|
||||
operation: "generate" | "download" | "reset-key";
|
||||
operation:
|
||||
| "generate"
|
||||
| "download"
|
||||
| "reset-key"
|
||||
| "status"
|
||||
| "deploy"
|
||||
| "start"
|
||||
| "stop"
|
||||
| "restart"
|
||||
| "update"
|
||||
| "rollback"
|
||||
| "revoke-session"
|
||||
| "retry"
|
||||
| "uninstall";
|
||||
profileKey: string;
|
||||
targetOS?: RuntimePlatform;
|
||||
targetArch?: RuntimeArch;
|
||||
artifactId?: string;
|
||||
installationId?: string;
|
||||
expectedDeploymentGeneration?: string;
|
||||
idempotencyKey: string;
|
||||
};
|
||||
|
||||
@@ -243,6 +264,7 @@ export interface RuntimeTransportProfile {
|
||||
export interface RuntimeClientManagerProfile {
|
||||
key: string;
|
||||
displayName?: string;
|
||||
version?: string;
|
||||
repository: {
|
||||
url: string;
|
||||
revisionPolicy: "pinned" | "branch" | "tag";
|
||||
@@ -258,6 +280,36 @@ export interface RuntimeClientManagerProfile {
|
||||
};
|
||||
configTemplates?: Array<{ key: string; templateRef: string; outputRef: string }>;
|
||||
outputArtifacts: string[];
|
||||
deployment?: {
|
||||
mode: "run-supervised";
|
||||
executableRef: string;
|
||||
arguments?: string[];
|
||||
autoStart?: boolean;
|
||||
requiredRunCapabilities: Array<"client-manager.deploy" | "client-manager.control" | "client-manager.update" | "client-manager.rollback" | "client-manager.uninstall">;
|
||||
};
|
||||
lifecycle?: {
|
||||
actions: Array<"start" | "stop" | "restart" | "status" | "update" | "rollback" | "uninstall">;
|
||||
startupTimeoutSeconds: number;
|
||||
stopTimeoutSeconds: number;
|
||||
};
|
||||
health?: {
|
||||
mode: "component-heartbeat" | "process";
|
||||
intervalSeconds: number;
|
||||
degradedAfterSeconds: number;
|
||||
offlineAfterSeconds: number;
|
||||
requiredCapabilities: Array<"component.register" | "component.heartbeat" | "component.health" | "component.control" | "game-client.bridge" | "logs.stream">;
|
||||
};
|
||||
compatibility?: {
|
||||
minimumVersion?: string;
|
||||
maximumVersion?: string;
|
||||
allowDowngrade: boolean;
|
||||
};
|
||||
updatePolicy?: {
|
||||
strategy: "manual-staged";
|
||||
requireApproval: true;
|
||||
healthConfirmationSeconds: number;
|
||||
retainPrevious: true;
|
||||
};
|
||||
}
|
||||
|
||||
export interface GamePluginRuntimeProfiles {
|
||||
@@ -283,6 +335,24 @@ export interface PluginArtifactReference {
|
||||
storageBehavior?: string;
|
||||
}
|
||||
|
||||
export interface PluginClientManagerLifecycleStatus {
|
||||
installationId: string;
|
||||
profileKey: string;
|
||||
status: string;
|
||||
phase?: string;
|
||||
targetOS?: RuntimePlatform;
|
||||
targetArch?: RuntimeArch;
|
||||
version?: string;
|
||||
previousVersion?: string;
|
||||
artifactId?: string;
|
||||
currentJobId?: string;
|
||||
deploymentGeneration?: number;
|
||||
health?: string;
|
||||
healthReason?: string;
|
||||
lastSeenAt?: string;
|
||||
actions: string[];
|
||||
}
|
||||
|
||||
export type PluginBridgeExecutionResponse<TResult extends Record<string, string> = Record<string, string>> = {
|
||||
requestId: string;
|
||||
pluginId: string;
|
||||
@@ -315,6 +385,17 @@ export const pluginBridgeActionPolicies: Record<PluginBridgeAction, PluginBridge
|
||||
|
||||
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>;
|
||||
timeoutMs?: number;
|
||||
stopTimeoutMs?: number;
|
||||
}
|
||||
|
||||
export type GamePluginActions = Partial<Record<PluginLifecycleAction, string>> & {
|
||||
install: string;
|
||||
start: string;
|
||||
@@ -507,8 +588,12 @@ export function createDependencyActionRequest(input: {
|
||||
operation: PluginDependencyActionPayload["operation"];
|
||||
probeKey?: string;
|
||||
planKey?: string;
|
||||
planDigest?: string;
|
||||
idempotencyKey: string;
|
||||
}): PluginBridgeExecutionRequest<PluginDependencyActionPayload> {
|
||||
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,
|
||||
@@ -517,6 +602,7 @@ export function createDependencyActionRequest(input: {
|
||||
operation: input.operation,
|
||||
probeKey: input.probeKey ?? "",
|
||||
planKey: input.planKey ?? "",
|
||||
planDigest: input.planDigest ?? "",
|
||||
idempotencyKey: input.idempotencyKey
|
||||
}
|
||||
});
|
||||
@@ -551,12 +637,16 @@ export function createClientManagerRequest(input: {
|
||||
targetOS?: RuntimePlatform;
|
||||
targetArch?: RuntimeArch;
|
||||
artifactId?: string;
|
||||
installationId?: string;
|
||||
expectedDeploymentGeneration?: number;
|
||||
idempotencyKey: string;
|
||||
}): PluginBridgeExecutionRequest<PluginClientManagerPayload> {
|
||||
const payload: PluginClientManagerPayload = {
|
||||
operation: input.operation,
|
||||
profileKey: input.profileKey,
|
||||
artifactId: input.artifactId ?? "",
|
||||
installationId: input.installationId ?? "",
|
||||
expectedDeploymentGeneration: typeof input.expectedDeploymentGeneration === "number" ? String(input.expectedDeploymentGeneration) : "",
|
||||
idempotencyKey: input.idempotencyKey
|
||||
};
|
||||
if (input.targetOS) {
|
||||
@@ -605,6 +695,52 @@ export function parseArtifactReference(result: Record<string, string> | undefine
|
||||
return reference;
|
||||
}
|
||||
|
||||
export function parseClientManagerLifecycleStatus(result: Record<string, string> | undefined): PluginClientManagerLifecycleStatus | undefined {
|
||||
if (!result) {
|
||||
return undefined;
|
||||
}
|
||||
const deploymentGeneration = Number(result.deploymentGeneration ?? "0");
|
||||
const values = [
|
||||
result.installationId,
|
||||
result.profileKey,
|
||||
result.status,
|
||||
result.phase,
|
||||
result.targetOS,
|
||||
result.targetArch,
|
||||
result.version,
|
||||
result.previousVersion,
|
||||
result.artifactId,
|
||||
result.currentJobId,
|
||||
result.health,
|
||||
result.healthReason,
|
||||
result.lastSeenAt,
|
||||
result.actions
|
||||
];
|
||||
if (!result.installationId || !result.profileKey || !result.status || !Number.isSafeInteger(deploymentGeneration) || deploymentGeneration < 0) {
|
||||
return undefined;
|
||||
}
|
||||
if (values.some((value) => typeof value === "string" && containsUnsafeReferenceContent(value))) {
|
||||
return undefined;
|
||||
}
|
||||
return {
|
||||
installationId: result.installationId,
|
||||
profileKey: result.profileKey,
|
||||
status: result.status,
|
||||
phase: result.phase,
|
||||
targetOS: result.targetOS as RuntimePlatform | undefined,
|
||||
targetArch: result.targetArch as RuntimeArch | undefined,
|
||||
version: result.version,
|
||||
previousVersion: result.previousVersion,
|
||||
artifactId: result.artifactId,
|
||||
currentJobId: result.currentJobId,
|
||||
deploymentGeneration,
|
||||
health: result.health,
|
||||
healthReason: result.healthReason,
|
||||
lastSeenAt: result.lastSeenAt,
|
||||
actions: (result.actions ?? "").split(",").filter(Boolean)
|
||||
};
|
||||
}
|
||||
|
||||
export function parseBridgeExecutionResponse<TResult extends Record<string, string>>(response: PluginBridgeExecutionResponse<TResult>): PluginBridgeExecutionResponse<TResult> {
|
||||
const safeError = response.error ? bridgeError(response.error.code, response.error.message, response.error.details ?? []) : undefined;
|
||||
return {
|
||||
@@ -672,6 +808,10 @@ function containsUnsafeReferenceContent(value: string): boolean {
|
||||
lowered.includes("apikey=") ||
|
||||
lowered.includes("storage://") ||
|
||||
lowered.includes("file://") ||
|
||||
lowered.includes("sessiontoken") ||
|
||||
lowered.includes("secret://") ||
|
||||
lowered.includes("hostpath") ||
|
||||
lowered.includes("processid") ||
|
||||
lowered.startsWith("sk-")
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user