功能修改

This commit is contained in:
npc0-hue
2026-07-20 16:42:33 +08:00
parent 48b8ad8d6c
commit a0e69417db
224 changed files with 22015 additions and 884 deletions
+12 -1
View File
@@ -14,8 +14,10 @@ Plugins use the platform bridge for every privileged action.
- `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/key reset or a typed status, deploy, start, stop, restart, update, rollback, session-revoke, retry, or uninstall operation for a plugin-declared companion client manager.
- `plugin-lifecycle.request`: request a server-bound install, enable, disable, upgrade, rollback, retire, or dependency-check operation through Platform capacity and compatibility gates.
- `ai.invoke`: request platform-mediated AI assistance.
- `theme.tokens`: read safe platform theme tokens.
- `game-client-bridge`: query declared companion health and snapshots, and queue or cancel only manifest-declared typed commands through Platform.
## Execution Envelopes
@@ -23,16 +25,22 @@ Plugin pages build execution requests with `createBridgeExecutionRequest` and ha
Execution responses use `requestId`, plugin/page/server scope, action, status, optional result refs, and optional safe errors. Use `parseBridgeExecutionResponse` before reading results so plugin code handles denied, deferred, and failed states uniformly.
AI requests use `createAIInvocationRequest` with an explicit purpose, prompt, scoped context refs, and optional current config. Use `parseAIInvocationResponse` to consume recommendations and safe errors. Plugin code must not include provider API keys, provider base URLs, bearer tokens, or direct transport details in AI request payloads.
AI requests use `createAIInvocationRequest` with an explicit manifest-declared purpose, prompt, and scoped context refs. Use `parseAIInvocationResponse` to consume recommendations, reviewable `diffId` metadata, and safe errors. Plugin code must not choose or receive provider API keys, provider base URLs, bearer tokens, or direct transport details. Config writes require a separate Platform operator approval.
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.
SQLite reads use manifest-declared `gameClientBridge.queryTemplates`. Plugin pages send only a declared template key plus typed inputs; Platform verifies the page contract, permission, SQLite transport/target, timeout, and row limit before dispatch. Query declarations and browser envelopes never contain SQL text, DSNs, credentials, sockets, or host paths.
Run distribution, dependency, log backfill, and client-manager requests use `createRunDistributionRequest`, `createDependencyActionRequest`, `createLogBackfillRequest`, and `createClientManagerRequest`. Client-manager lifecycle envelopes carry only operation names, logical profile/installation IDs, target OS/architecture, artifact IDs, expected deployment generations, and idempotency keys. `parseClientManagerLifecycleStatus` whitelists safe state, version, health, job, artifact, and action fields. Raw run/client-manager keys, component sessions, secret refs, host paths, PIDs, sockets, credentials, and direct Run endpoint details are never plugin bridge fields.
Client-manager lifecycle requests remain Platform-mediated. A plugin declaration does not grant access by itself: Platform rechecks the installed plugin, server owner/administrator scope, runtime binding, assigned Run endpoint capabilities, current distribution target/revision/key generation, and durable installation state before dispatching a typed job.
Game-client plugin pages receive a host-provided `GameClientBridgePageClient`. The SDK defines status, command, result, snapshot, approval, and manifest declaration types but never creates its own HTTP client. Queue requests carry only a declared command type, logical profile key, bounded typed payload, expiry, priority, and idempotency key. Browser-facing types intentionally have no component session, component key, installation fence, host path, DSN, Run endpoint, socket, or storage credential fields.
Production plugin lifecycle requests use `createProductionPluginLifecycleRequest`. Envelopes contain only plugin/server scope, enumerated operation, optional target version, confirmation, and idempotency key. Platform rechecks the manifest `productionLifecycle` declaration, dependency policy, disruptive approval, endpoint capacity, compatibility, and prior idempotency inputs before dispatch.
## Forbidden Data
The bridge must not expose:
@@ -45,3 +53,6 @@ The bridge must not expose:
- unrestricted artifact storage credentials.
- direct storage URLs or presigned backend URLs.
- FTP, rsync, database, or RCON credentials.
# Client Manager lifecycle bridge
The bridge may request typed `deploy`, `start`, `stop`, `restart`, `status`, `update`, `rollback`, `revoke`, `retry`, or `uninstall` intents when Platform action gating says they are available. Results are safe logical projections with real job phase/progress and redacted recovery guidance. The bridge is not a transport for Run sessions, component keys, artifact bytes, machine paths, process IDs, sockets, or credentials; component registration and heartbeat remain component-to-Platform contracts outside the plugin page.
+238 -8
View File
@@ -11,6 +11,9 @@ export type PluginPermission =
| "server.run.distribution"
| "server.dependencies.manage"
| "server.client-manager.manage"
| "server.game-client.read"
| "server.game-client.command"
| "server.game-client.maintenance"
| "ai.invoke";
export type RunCapability =
@@ -58,6 +61,7 @@ export type PluginBridgeAction =
| "dependencies.request"
| "logs.backfill.request"
| "client-manager.request"
| "plugin-lifecycle.request"
| "ai.invoke";
export type PluginBridgeRequestPayload = Record<string, unknown>;
@@ -115,7 +119,7 @@ export interface PluginAIInvocationResponse {
purpose: AIPurpose;
status: "ok" | "denied" | "error" | string;
recommendation?: string;
suggestedConfig?: string;
configRecommendation?: { diffId: string; key: string; suggestedConfig: string; diffSummary: string; expiresAt: string };
usage?: { model?: string; mocked?: boolean; inputTokens?: number; outputTokens?: number };
error?: PluginBridgeError;
}
@@ -131,6 +135,15 @@ export type PluginLifecycleDispatchPayload = Record<string, string> & {
idempotencyKey: string;
};
export type ProductionPluginLifecycleOperation = "install" | "enable" | "disable" | "upgrade" | "rollback" | "retire" | "dependency-check";
export type PluginProductionLifecyclePayload = Record<string, string> & {
operation: ProductionPluginLifecycleOperation;
targetVersion: string;
idempotencyKey: string;
confirmed: "true" | "false";
};
export type PluginRemoteAccessPayload = Record<string, string> & {
capability: Extract<RunCapability, `remote.${string}`>;
targetKey?: string;
@@ -196,6 +209,165 @@ export interface GamePluginRemoteAccess {
logTransfer?: boolean;
}
export type GameClientBridgeApprovalLevel = "none" | "operator" | "platform-admin";
export type GameClientBridgeApprovalState = "not_required" | "pending" | "approved" | "rejected";
export type GameClientBridgeCommandState = "pending" | "claimed" | "succeeded" | "failed" | "cancelled" | "expired";
export interface GameClientBridgeCommandDeclaration {
type: string;
title: string;
permission: PluginPermission;
approvalLevel: GameClientBridgeApprovalLevel;
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;
maxRows: number;
timeoutSeconds: number;
}
export interface GameClientBridgePageContract {
pageKey: string;
commandTypes?: string[];
snapshotTypes?: string[];
queryTemplateKeys?: string[];
}
export interface GameClientBridgeCompanionDeclaration {
profileKey: string;
configTemplateKey: string;
configSchemaRef: string;
configFormat: "yaml";
platformBaseUrlSource: "run-control";
registrationProof: "hmac-sha256";
proofMaterialSource: "component-package";
proofMaterialEnv: string;
sessionMode: "component-session";
tlsPolicy: "verify-system-roots";
heartbeatIntervalSeconds: number;
commandPollIntervalSeconds: number;
requestTimeoutSeconds: number;
}
export interface GameClientBridgeManifest {
commands: GameClientBridgeCommandDeclaration[];
snapshots: GameClientBridgeSnapshotDeclaration[];
queryTemplates?: GameClientBridgeQueryTemplateDeclaration[];
commandRetentionSeconds: number;
maxCommands: number;
pages?: GameClientBridgePageContract[];
companion?: GameClientBridgeCompanionDeclaration;
}
export interface GameClientBridgeProfileStatus {
pluginId: string;
profileKey: string;
available: boolean;
reason?: string;
commandTypes: string[];
snapshotTypes: string[];
queryTemplateKeys: string[];
}
export interface GameClientBridgeStatus {
serverInstanceId: string;
pluginId: string;
available: boolean;
reason?: string;
profiles: GameClientBridgeProfileStatus[];
}
export interface GameClientBridgeCommandResult {
status: "succeeded" | "failed" | "cancelled";
summary?: string;
payload?: Record<string, unknown>;
completedAt: string;
}
export interface GameClientBridgeCommand {
id: string;
serverInstanceId: string;
pluginId: string;
profileKey: string;
commandType: string;
priority: number;
state: GameClientBridgeCommandState;
approvalState: GameClientBridgeApprovalState;
result?: GameClientBridgeCommandResult;
auditReferences?: string[];
expiresAt: string;
createdAt: string;
updatedAt: string;
completedAt?: string;
}
export interface GameClientBridgeSnapshot<TPayload extends Record<string, unknown> = Record<string, unknown>> {
id: string;
serverInstanceId: string;
pluginId: string;
profileKey: string;
type: string;
schemaVersion: string;
streamKey: string;
sequence: number;
observedAt: string;
payload: TPayload;
auditReferences?: string[];
createdAt: string;
expiresAt: string;
}
export interface GameClientBridgeQueueRequest<TPayload extends Record<string, unknown> = Record<string, unknown>> {
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<GameClientBridgeStatus>;
listCommands(serverInstanceId: string): Promise<GameClientBridgeCommand[]>;
queueCommand<TPayload extends Record<string, unknown>>(serverInstanceId: string, request: GameClientBridgeQueueRequest<TPayload>): Promise<GameClientBridgeCommand>;
cancelCommand(serverInstanceId: string, commandId: string, reason?: string): Promise<GameClientBridgeCommand>;
querySnapshots<TPayload extends Record<string, unknown> = Record<string, unknown>>(serverInstanceId: string, query?: GameClientBridgeSnapshotQuery): Promise<Array<GameClientBridgeSnapshot<TPayload>>>;
}
export function createGameClientBridgeQueueRequest<TPayload extends Record<string, unknown>>(input: GameClientBridgeQueueRequest<TPayload>): GameClientBridgeQueueRequest<TPayload> {
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 };
@@ -254,6 +426,19 @@ export interface RuntimeLogSource {
retentionDays?: number;
}
export type RuntimeLogEventSeverity = "info" | "notice" | "warning" | "critical";
export interface RuntimeLogEventDeclaration {
key: string;
title: string;
sourceKey: string;
eventType: string;
permission: PluginPermission;
schemaRef: string;
retentionDays: number;
severity: RuntimeLogEventSeverity;
}
export interface RuntimeTransportProfile {
key: string;
kind: "file" | "ftp" | "rsync" | "mysql" | "sqlite" | "rcon";
@@ -318,6 +503,7 @@ export interface GamePluginRuntimeProfiles {
dependencyProbes?: RuntimeDependencyProbe[];
installPlans?: RuntimeInstallPlan[];
logSources?: RuntimeLogSource[];
logEvents?: RuntimeLogEventDeclaration[];
transportProfiles?: RuntimeTransportProfile[];
clientManagers?: RuntimeClientManagerProfile[];
}
@@ -380,6 +566,7 @@ export const pluginBridgeActionPolicies: Record<PluginBridgeAction, PluginBridge
"dependencies.request": { permissions: ["server.dependencies.manage"] },
"logs.backfill.request": { permissions: ["server.logs.read"] },
"client-manager.request": { permissions: ["server.client-manager.manage"] },
"plugin-lifecycle.request": { permissions: ["server.lifecycle"] },
"ai.invoke": { permissions: ["ai.invoke"], aiPurposeRequired: true }
};
@@ -432,10 +619,18 @@ export interface GamePluginManifest {
permissions: PluginPermission[];
remoteAccess?: GamePluginRemoteAccess;
runtimeProfiles?: GamePluginRuntimeProfiles;
gameClientBridge?: GameClientBridgeManifest;
actions?: GamePluginActions;
productionLifecycle: {
operations: ProductionPluginLifecycleOperation[];
dependencyPolicy: "required" | "optional";
approvalRequired: Array<"disable" | "rollback" | "retire">;
};
pages?: GamePluginPage[];
ai?: {
purposes?: AIPurpose[];
mediation: "platform";
configWritePolicy: "review-required";
};
}
@@ -533,24 +728,59 @@ export function createLifecycleDispatchRequest(input: {
});
}
export function createProductionPluginLifecycleRequest(input: {
requestId: string;
context: PluginBridgeContext;
operation: ProductionPluginLifecycleOperation;
targetVersion?: string;
idempotencyKey: string;
confirmed?: boolean;
}): PluginBridgeExecutionRequest<PluginProductionLifecyclePayload> {
if (!input.context.serverInstanceId) {
throw new Error("serverInstanceId is required for plugin lifecycle requests");
}
if (["disable", "rollback", "retire"].includes(input.operation) && !input.confirmed) {
throw new Error("disruptive plugin lifecycle requests require confirmation");
}
return createBridgeExecutionRequest({
requestId: input.requestId,
context: input.context,
action: "plugin-lifecycle.request",
payload: {
operation: input.operation,
targetVersion: input.targetVersion ?? "",
idempotencyKey: input.idempotencyKey,
confirmed: input.confirmed ? "true" : "false"
}
});
}
export function createRemoteAccessRequest(input: {
requestId: string;
context: PluginBridgeContext;
capability: PluginRemoteAccessPayload["capability"];
targetKey?: string;
inputRef?: string;
inputs?: Record<string, string>;
idempotencyKey: string;
}): PluginBridgeExecutionRequest<PluginRemoteAccessPayload> {
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: {
capability: input.capability,
targetKey: input.targetKey ?? "",
inputRef: input.inputRef ?? "",
idempotencyKey: input.idempotencyKey
}
payload
});
}
@@ -781,7 +1011,7 @@ export function parseAIInvocationResponse(response: PluginAIInvocationResponse):
purpose: response.purpose,
status: response.status,
recommendation: response.recommendation,
suggestedConfig: response.suggestedConfig,
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
};