Complete platform management workflows

This commit is contained in:
npc0-hue
2026-07-14 16:39:37 +08:00
parent 7e05d0a4e7
commit 4f33f761a3
106 changed files with 11313 additions and 460 deletions
+10
View File
@@ -9,6 +9,11 @@ Plugins use the platform bridge for every privileged action.
- `logs.query`: query historical logs by server, stream, time range, cursor, or analysis window.
- `artifacts.open`: request platform-mediated artifact download references.
- `files.request`: request scoped file list/read/patch/replace operations through platform jobs.
- `remote.access.request`: request plugin-declared FTP, rsync, or run-mediated remote operations through platform jobs.
- `run.distribution.request`: request platform-mediated run package generation, download, key reset, or self-update orchestration.
- `dependencies.request`: request typed dependency checks or approved install plans declared by the plugin runtime profile.
- `logs.backfill.request`: request historical log backfill for a declared log source.
- `client-manager.request`: request generation, download, or key reset for a plugin-declared companion client manager.
- `ai.invoke`: request platform-mediated AI assistance.
- `theme.tokens`: read safe platform theme tokens.
@@ -22,6 +27,10 @@ AI requests use `createAIInvocationRequest` with an explicit purpose, prompt, sc
Artifact open requests use `createArtifactOpenRequest` with an artifact ID that belongs to the current server/job scope. Use `parseArtifactReference` to consume the bridge result. Parsed references contain platform-owned download URLs, filename, content type, size, checksum, expiry, range support, and chunk size; they do not contain bytes or raw storage adapter locations.
Remote access requests use `createRemoteAccessRequest` with a plugin-declared `remote.*` capability, logical target key, optional scoped `input://` or `artifact://` ref, and idempotency key. The SDK never accepts FTP passwords, rsync endpoints, database DSNs, RCON passwords, run sockets, or raw host paths in these envelopes.
Run distribution, dependency, log backfill, and client-manager requests use `createRunDistributionRequest`, `createDependencyActionRequest`, `createLogBackfillRequest`, and `createClientManagerRequest`. These helpers carry operation names, logical profile keys, target OS/architecture, artifact IDs, cursors, and idempotency keys only; raw run keys and client-manager keys are written only into generated packages by platform services.
## Forbidden Data
The bridge must not expose:
@@ -33,3 +42,4 @@ The bridge must not expose:
- storage backend endpoints.
- unrestricted artifact storage credentials.
- direct storage URLs or presigned backend URLs.
- FTP, rsync, database, or RCON credentials.
+293
View File
@@ -7,6 +7,10 @@ export type PluginPermission =
| "server.logs.read"
| "server.artifacts.read"
| "server.artifacts.write"
| "server.remote.access"
| "server.run.distribution"
| "server.dependencies.manage"
| "server.client-manager.manage"
| "ai.invoke";
export type RunCapability =
@@ -20,6 +24,18 @@ export type RunCapability =
| "files.write"
| "files.patch"
| "logs.read"
| "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.sqlite.query"
| "remote.run.logs.transfer"
| "remote.run.rcon.command"
| "artifacts.read"
| "artifacts.write"
| "ai.invoke";
@@ -32,6 +48,11 @@ export type PluginBridgeAction =
| "logs.query"
| "artifacts.open"
| "files.request"
| "remote.access.request"
| "run.distribution.request"
| "dependencies.request"
| "logs.backfill.request"
| "client-manager.request"
| "ai.invoke";
export type PluginBridgeRequestPayload = Record<string, unknown>;
@@ -105,6 +126,150 @@ export type PluginLifecycleDispatchPayload = Record<string, string> & {
idempotencyKey: string;
};
export type PluginRemoteAccessPayload = Record<string, string> & {
capability: Extract<RunCapability, `remote.${string}`>;
targetKey?: string;
inputRef?: string;
idempotencyKey: string;
};
export type PluginRunDistributionPayload = Record<string, string> & {
operation: "generate" | "download" | "reset-key" | "update";
targetOS?: RuntimePlatform;
targetArch?: RuntimeArch;
artifactId?: string;
idempotencyKey: string;
};
export type PluginDependencyActionPayload = Record<string, string> & {
operation: "check" | "install";
probeKey?: string;
planKey?: string;
idempotencyKey: string;
};
export type PluginLogBackfillPayload = Record<string, string> & {
sourceKey: string;
cursor?: string;
limit?: string;
idempotencyKey: string;
};
export type PluginClientManagerPayload = Record<string, string> & {
operation: "generate" | "download" | "reset-key";
profileKey: string;
targetOS?: RuntimePlatform;
targetArch?: RuntimeArch;
artifactId?: string;
idempotencyKey: string;
};
export type RemoteAccessMethod = "ftp" | "rsync" | "run";
export type RemoteDatabaseEngine = "mysql" | "sqlite";
export interface GamePluginRemoteAccess {
methods: RemoteAccessMethod[];
runCapabilities?: Array<Extract<RunCapability, `remote.${string}`>>;
databaseEngines?: RemoteDatabaseEngine[];
rcon?: boolean;
logTransfer?: boolean;
}
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" | "custom-client";
capabilities: RunCapability[];
actionRefs?: Partial<Record<PluginLifecycleAction, string>>;
transportKeys?: string[];
clientManagerRef?: 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" | "client-manager";
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 RuntimeClientManagerProfile {
key: string;
displayName?: string;
repository: {
url: string;
revisionPolicy: "pinned" | "branch" | "tag";
branch?: string;
tag?: string;
revision?: string;
};
supportedTargets: RuntimeTarget[];
build: {
system: "go" | "npm" | "cargo" | "make";
workspaceRef?: string;
entryRef?: string;
};
configTemplates?: Array<{ key: string; templateRef: string; outputRef: string }>;
outputArtifacts: string[];
}
export interface GamePluginRuntimeProfiles {
discovery?: RuntimeDiscoveryProbe[];
lifecycleProfiles?: RuntimeLifecycleProfile[];
dependencyProbes?: RuntimeDependencyProbe[];
installPlans?: RuntimeInstallPlan[];
logSources?: RuntimeLogSource[];
transportProfiles?: RuntimeTransportProfile[];
clientManagers?: RuntimeClientManagerProfile[];
}
export interface PluginArtifactReference {
artifactId: string;
filename: string;
@@ -140,6 +305,11 @@ export const pluginBridgeActionPolicies: Record<PluginBridgeAction, PluginBridge
"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"] },
"client-manager.request": { permissions: ["server.client-manager.manage"] },
"ai.invoke": { permissions: ["ai.invoke"], aiPurposeRequired: true }
};
@@ -179,6 +349,8 @@ export interface GamePluginManifest {
bridge?: GamePluginBridge;
capabilities: RunCapability[];
permissions: PluginPermission[];
remoteAccess?: GamePluginRemoteAccess;
runtimeProfiles?: GamePluginRuntimeProfiles;
actions?: GamePluginActions;
pages?: GamePluginPage[];
ai?: {
@@ -280,6 +452,127 @@ export function createLifecycleDispatchRequest(input: {
});
}
export function createRemoteAccessRequest(input: {
requestId: string;
context: PluginBridgeContext;
capability: PluginRemoteAccessPayload["capability"];
targetKey?: string;
inputRef?: string;
idempotencyKey: string;
}): PluginBridgeExecutionRequest<PluginRemoteAccessPayload> {
return createBridgeExecutionRequest({
requestId: input.requestId,
context: input.context,
action: "remote.access.request",
payload: {
capability: input.capability,
targetKey: input.targetKey ?? "",
inputRef: input.inputRef ?? "",
idempotencyKey: input.idempotencyKey
}
});
}
export function createRunDistributionRequest(input: {
requestId: string;
context: PluginBridgeContext;
operation: PluginRunDistributionPayload["operation"];
targetOS?: RuntimePlatform;
targetArch?: RuntimeArch;
artifactId?: string;
idempotencyKey: string;
}): PluginBridgeExecutionRequest<PluginRunDistributionPayload> {
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;
idempotencyKey: string;
}): PluginBridgeExecutionRequest<PluginDependencyActionPayload> {
return createBridgeExecutionRequest({
requestId: input.requestId,
context: input.context,
action: "dependencies.request",
payload: {
operation: input.operation,
probeKey: input.probeKey ?? "",
planKey: input.planKey ?? "",
idempotencyKey: input.idempotencyKey
}
});
}
export function createLogBackfillRequest(input: {
requestId: string;
context: PluginBridgeContext;
sourceKey: string;
cursor?: string;
limit?: number;
idempotencyKey: string;
}): PluginBridgeExecutionRequest<PluginLogBackfillPayload> {
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 createClientManagerRequest(input: {
requestId: string;
context: PluginBridgeContext;
operation: PluginClientManagerPayload["operation"];
profileKey: string;
targetOS?: RuntimePlatform;
targetArch?: RuntimeArch;
artifactId?: string;
idempotencyKey: string;
}): PluginBridgeExecutionRequest<PluginClientManagerPayload> {
const payload: PluginClientManagerPayload = {
operation: input.operation,
profileKey: input.profileKey,
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: "client-manager.request",
payload
});
}
export function parseArtifactReference(result: Record<string, string> | undefined): PluginArtifactReference | undefined {
if (!result) {
return undefined;