Complete platform management workflows
This commit is contained in:
@@ -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.
|
||||
|
||||
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user