Remove AI config approval flow
This commit is contained in:
@@ -565,8 +565,9 @@ describe("PlatformApiClient AI providers", () => {
|
||||
providerId: "ai.openai",
|
||||
model: "gpt-4.1",
|
||||
status: "ok",
|
||||
recommendation: "Review before applying.",
|
||||
configRecommendation: { key: "server.properties", suggestedConfig: "server.name=Example Survival #1\npvp=false\n", diffSummary: "review required" },
|
||||
recommendation: "Configuration changes are dispatched automatically.",
|
||||
configRecommendation: { key: "server.properties", suggestedConfig: "server.name=Example Survival #1\npvp=false\n", diffSummary: "AI config write queued" },
|
||||
configExecution: { status: "queued", job: { ...job, id: "job-ai-config-1", capability: "config.write", targetKey: "server.properties" } },
|
||||
usage: { providerId: "ai.openai", model: "gpt-4.1", inputTokens: 20, outputTokens: 12, mocked: true }
|
||||
});
|
||||
}
|
||||
@@ -645,7 +646,7 @@ describe("PlatformApiClient AI providers", () => {
|
||||
});
|
||||
await expect(
|
||||
client.invokeAI({ requestId: "ai-1", serverInstanceId: server.id, purpose: "config.suggest", prompt: "Tune PVP safely", currentConfig: "server.name=Example Survival #1\n" })
|
||||
).resolves.toMatchObject({ status: "ok", usage: { mocked: true }, configRecommendation: { diffSummary: "review required" } });
|
||||
).resolves.toMatchObject({ status: "ok", usage: { mocked: true }, configRecommendation: { diffSummary: "AI config write queued" }, configExecution: { status: "queued", job: { id: "job-ai-config-1", capability: "config.write" } } });
|
||||
|
||||
expect(fetchMock).toHaveBeenCalledTimes(51);
|
||||
});
|
||||
@@ -799,6 +800,7 @@ describe("PlatformApiClient AI providers", () => {
|
||||
const client = new PlatformApiClient("/api/v1");
|
||||
|
||||
expect(client.serverLogEventsUrl("server/scum 1")).toBe("/api/v1/server-instances/server%2Fscum%201/logs/events");
|
||||
expect(client.serverLogEventsUrl("server-1", { jobId: "job/config write" })).toBe("/api/v1/server-instances/server-1/logs/events?jobId=job%2Fconfig%20write");
|
||||
expect(client.serverLogEventsUrl("server-1")).toBe("/api/v1/server-instances/server-1/logs/events");
|
||||
});
|
||||
|
||||
|
||||
@@ -8,8 +8,6 @@ import type {
|
||||
AiProviderUpdateRequest,
|
||||
AIInvocationRequest,
|
||||
AIInvocationResponse,
|
||||
AIConfigDiffApprovalResponse,
|
||||
AIConfigDiffListResponse,
|
||||
ApiErrorResponse,
|
||||
ArtifactContentChunk,
|
||||
ArtifactDownloadReferenceResponse,
|
||||
@@ -500,17 +498,6 @@ export class PlatformApiClient {
|
||||
return this.request<PluginLifecycleActionResponse>(`/plugin-lifecycles/${encodeURIComponent(pluginId)}/actions`, { method: "POST", body: request });
|
||||
}
|
||||
|
||||
async listAIConfigDiffs(filter: { serverInstanceId?: string; pluginId?: string; state?: string } = {}): Promise<AIConfigDiffListResponse> {
|
||||
const params = new URLSearchParams();
|
||||
Object.entries(filter).forEach(([key, value]) => { if (value) params.set(key, value); });
|
||||
const query = params.toString();
|
||||
return this.request<AIConfigDiffListResponse>(`/ai/config-diffs${query ? `?${query}` : ""}`);
|
||||
}
|
||||
|
||||
async approveAIConfigDiff(id: string, idempotencyKey: string): Promise<AIConfigDiffApprovalResponse> {
|
||||
return this.request<AIConfigDiffApprovalResponse>(`/ai/config-diffs/${encodeURIComponent(id)}/approve`, { method: "POST", body: { idempotencyKey } });
|
||||
}
|
||||
|
||||
async listServerMetrics(): Promise<ServerMetricsListResponse> {
|
||||
return this.request<ServerMetricsListResponse>("/metrics/server-instances");
|
||||
}
|
||||
@@ -641,8 +628,8 @@ export class PlatformApiClient {
|
||||
return this.request<LogStreamListResponse>(`/log-streams${query}`);
|
||||
}
|
||||
|
||||
openServerLogEvents(id: string): PlatformEventStream {
|
||||
const url = this.serverLogEventsUrl(id);
|
||||
openServerLogEvents(id: string, options: { jobId?: string } = {}): PlatformEventStream {
|
||||
const url = this.serverLogEventsUrl(id, options);
|
||||
const sessionToken = this.sessionTokenProvider();
|
||||
if (!sessionToken) {
|
||||
return new EventSource(url, { withCredentials: true });
|
||||
@@ -650,8 +637,9 @@ export class PlatformApiClient {
|
||||
return new FetchServerSentEventStream(url, sessionToken);
|
||||
}
|
||||
|
||||
serverLogEventsUrl(id: string): string {
|
||||
return `${this.baseUrl}/server-instances/${encodeURIComponent(id)}/logs/events`;
|
||||
serverLogEventsUrl(id: string, options: { jobId?: string } = {}): string {
|
||||
const query = options.jobId ? `?jobId=${encodeURIComponent(options.jobId)}` : "";
|
||||
return `${this.baseUrl}/server-instances/${encodeURIComponent(id)}/logs/events${query}`;
|
||||
}
|
||||
|
||||
async queryLogStream(request: LogStreamCursorRequest): Promise<LogStreamCursorResponse> {
|
||||
|
||||
@@ -32,7 +32,7 @@ Normal browser login uses the platform's HttpOnly SameSite cookie and `credentia
|
||||
- `listArtifacts`, `openArtifactDownload`, `downloadArtifactContent`, and `readArtifactContent` use platform artifact routes for available job/server artifacts. Browser downloads stream the full body through `/artifacts/{id}/content`; explicit range reads may still use bounded `offset`/`limit` chunks and must render only safe filenames, checksums, progress, and platform storage behavior.
|
||||
- `authorizePluginBridge` posts `PluginBridgeAuthorizeRequest` to `/plugin-bridge/authorize` for preflight decisions.
|
||||
- `executePluginBridge` posts `PluginBridgeExecuteRequest` to `/plugin-bridge/execute` from host-owned bridge dispatch utilities only. Plugin pages receive typed `PluginBridgeExecuteResponse` envelopes and never receive the platform API client, bearer token, raw provider key, run socket, host path, or storage credential.
|
||||
- `invokeAI` posts `AIInvocationRequest` to `/ai/invocations` for platform-mediated AI assistance. Responses carry redacted recommendations, usage metadata, optional reviewable config suggestions, and safe errors; they must not include provider base URLs, key refs, raw keys, or direct provider transport details.
|
||||
- `invokeAI` posts `AIInvocationRequest` to `/ai/invocations` for platform-mediated AI assistance. Config suggestions return redacted recommendation text, the proposed config, and `configExecution.job` metadata for the job dispatched immediately after Platform validation; responses must not include provider base URLs, key refs, raw keys, or direct provider transport details.
|
||||
- `listRunEndpoints` and `listJobs` provide refresh data for endpoint availability, capacity, and durable lifecycle status. Job projections include `retrying`, attempt/max-attempt counts, next retry timing, safe ack/lease deadlines, cancellation timestamps/reason, terminal time, and reconciliation outcome/count.
|
||||
- `getDependencyCatalog` reads `GET /server-instances/{id}/dependencies` and returns only target-matched probe state/evidence, typed plan step summaries, approved download hosts, and immutable SHA-256 `planDigest` values. Install requests must submit the selected digest; the browser never receives bindings, commands, paths, credentials, tokens, or private download refs.
|
||||
- `listRunUpdates` reads `GET /server-instances/{id}/run/update` and returns only target, artifact checksum, release identity, phase, bounded status message, rollback flag, and timestamps. The UI treats `restart-requested`/`activating` as non-terminal until a later safe projection confirms health.
|
||||
@@ -53,9 +53,9 @@ Existing platform APIs already cover server lifecycle, jobs, log stream metadata
|
||||
- `PUT /api/v1/users/current/theme` (`UserThemePreferenceRequest`/`UserThemePreferenceResponse`): implemented per-user theme preferences, including selected palette IDs such as `mecha-black` or `magical-girl`, uploaded background reference or safe persisted data URL metadata, and readable overlay preference.
|
||||
- `GET /api/v1/metrics/platform` (`PlatformResourceUsageResponse`): implemented platform-level CPU/memory/disk usage and LLM connectivity summary for the overview first screen.
|
||||
- `GET /api/v1/metrics/server-instances` (`ServerMetricsListResponse`): implemented per-server online state, player count, TPS, latency, CPU/memory/disk for server cards on the server list.
|
||||
- Server-scoped raw config routes (`GET /api/v1/server-instances/{id}/config`, `POST .../config/diff`, `POST .../config/approve`) are removed from the product API. AI configuration assistance uses `/api/v1/ai/invocations` plus reviewable AI config-diff approval APIs; plugin pages do not receive raw config text.
|
||||
- Server-scoped raw config routes (`GET /api/v1/server-instances/{id}/config`, `POST .../config/diff`, `POST .../config/approve`) are removed from the product API. AI configuration assistance uses `/api/v1/ai/invocations` and direct typed `configExecution` job metadata; plugin pages do not receive provider credentials.
|
||||
- `POST /api/v1/file-operations/dispatch` (`FileOperationDispatchRequest`/`FileOperationDispatchResponse`): implemented scoped file operation dispatch using logical keys and refs only.
|
||||
- `POST /api/v1/ai/config-suggestions` (`LlmConfigSuggestionRequest`/`LlmConfigSuggestionResponse`) and `POST /api/v1/ai/invocations` (`AIInvocationRequest`/`AIInvocationResponse`): platform-mediated AI recommendation or diff scoped to one server. Provider keys stay in `platform/`; responses carry only recommendation text, usage metadata, and reviewable suggestions, never keys or provider secrets.
|
||||
- `POST /api/v1/ai/config-suggestions` (`LlmConfigSuggestionRequest`/`LlmConfigSuggestionResponse`) and `POST /api/v1/ai/invocations` (`AIInvocationRequest`/`AIInvocationResponse`): platform-mediated AI recommendation scoped to one server. Config suggestions dispatch a bounded `config.write` job immediately after validation and return its job metadata; provider keys stay in `platform/` and never reach the browser or plugin pages.
|
||||
- Per-server plugin controls are rendered from installed plugin manifests (`bridgeActions`, `lifecycleActions`, `pages`, `declaredPermissions`); a richer declared-control schema remains a future plugin contract. Hosted bridge execution uses `POST /api/v1/plugin-bridge/execute` for server context, scoped file, log, job, artifact reference, and AI action envelopes instead of direct plugin fetches to platform internals.
|
||||
- Operation/job traceability reuses `GET /api/v1/jobs`, `GET /api/v1/jobs/{id}`, and `POST /api/v1/jobs/{id}/cancel`; the frontend wraps these in one visible operation lifecycle per user intent.
|
||||
|
||||
|
||||
@@ -15,14 +15,10 @@ describe("PlatformApiClient plugin operations", () => {
|
||||
|
||||
await client.listPluginLifecycles({ pluginId: "game.scum" });
|
||||
await client.runPluginLifecycle("game.scum", { serverInstanceId: "server-1", operation: "upgrade", targetVersion: "1.2.0", idempotencyKey: "upgrade-1" });
|
||||
await client.listAIConfigDiffs({ state: "pending" });
|
||||
await client.approveAIConfigDiff("diff-1", "approve-1");
|
||||
|
||||
expect(calls.map((call) => `${call.method} ${call.url}`)).toEqual([
|
||||
"GET /api/v1/plugin-lifecycles?pluginId=game.scum",
|
||||
"POST /api/v1/plugin-lifecycles/game.scum/actions",
|
||||
"GET /api/v1/ai/config-diffs?state=pending",
|
||||
"POST /api/v1/ai/config-diffs/diff-1/approve"
|
||||
"POST /api/v1/plugin-lifecycles/game.scum/actions"
|
||||
]);
|
||||
const serialized = JSON.stringify(calls);
|
||||
expect(serialized).not.toMatch(/apiKey|token|secret|providerBaseUrl|runSocket|runEndpointUrl|hostPath|credential|dsn|rcon/i);
|
||||
|
||||
@@ -1575,6 +1575,7 @@ export interface LlmConfigSuggestionResponse {
|
||||
serverInstanceId: string;
|
||||
recommendation: string;
|
||||
suggestedConfig?: string;
|
||||
configExecution?: AIConfigExecutionResponse;
|
||||
}
|
||||
|
||||
export interface AIInvocationRequest {
|
||||
@@ -1602,8 +1603,11 @@ export interface AIConfigRecommendationResponse {
|
||||
key: string;
|
||||
suggestedConfig?: string;
|
||||
diffSummary: string;
|
||||
diffId: string;
|
||||
expiresAt: string;
|
||||
}
|
||||
|
||||
export interface AIConfigExecutionResponse {
|
||||
status: string;
|
||||
job: JobResponse;
|
||||
}
|
||||
|
||||
export interface PluginProductionLifecycleDeclaration {
|
||||
@@ -1633,30 +1637,6 @@ export interface PluginLifecycleListResponse { items: PluginLifecycleInstallatio
|
||||
export interface PluginLifecycleActionRequest { serverInstanceId: string; operation: PluginLifecycleOperation; targetVersion?: string; idempotencyKey: string; }
|
||||
export interface PluginLifecycleActionResponse { status: string; installation: PluginLifecycleInstallationResponse; job: JobResponse; }
|
||||
|
||||
export interface AIConfigDiffPreviewResponse {
|
||||
id: string;
|
||||
requestId: string;
|
||||
createdBy: string;
|
||||
serverInstanceId: string;
|
||||
pluginId?: string;
|
||||
providerId?: string;
|
||||
model?: string;
|
||||
key: string;
|
||||
configVersion: number;
|
||||
currentConfigChecksum?: string;
|
||||
proposedConfig?: string;
|
||||
diffSummary: string;
|
||||
state: "pending" | "approved" | "cancelled" | "expired";
|
||||
expiresAt: string;
|
||||
approvedBy?: string;
|
||||
approvedAt?: string;
|
||||
jobId?: string;
|
||||
createdAt: string;
|
||||
updatedAt: string;
|
||||
}
|
||||
export interface AIConfigDiffListResponse { items: AIConfigDiffPreviewResponse[]; count: number; }
|
||||
export interface AIConfigDiffApprovalResponse { preview: AIConfigDiffPreviewResponse; dispatch: ServerConfigWriteDispatchResponse; }
|
||||
|
||||
export interface AIInvocationSafeErrorResponse {
|
||||
code: string;
|
||||
message: string;
|
||||
@@ -1671,6 +1651,7 @@ export interface AIInvocationResponse {
|
||||
status: "ok" | "denied" | "error" | string;
|
||||
recommendation?: string;
|
||||
configRecommendation?: AIConfigRecommendationResponse;
|
||||
configExecution?: AIConfigExecutionResponse;
|
||||
usage: AIInvocationUsageResponse;
|
||||
error?: AIInvocationSafeErrorResponse;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user