first commit

This commit is contained in:
npc0-hue
2026-07-11 14:56:10 +08:00
commit 7e05d0a4e7
660 changed files with 78119 additions and 0 deletions
+3
View File
@@ -0,0 +1,3 @@
export function cx(...tokens: Array<string | false | null | undefined>): string {
return tokens.filter(Boolean).join(" ");
}
+56
View File
@@ -0,0 +1,56 @@
import type { ConfigDiffView, DiffLine } from "../contracts/workspace";
export function computeLineDiff(previous: string, next: string): DiffLine[] {
const previousLines = previous.split("\n");
const nextLines = next.split("\n");
const m = previousLines.length;
const n = nextLines.length;
// classic LCS table; config files are small enough for O(m*n)
const lcs: number[][] = Array.from({ length: m + 1 }, () => new Array<number>(n + 1).fill(0));
for (let i = m - 1; i >= 0; i -= 1) {
for (let j = n - 1; j >= 0; j -= 1) {
lcs[i][j] = previousLines[i] === nextLines[j] ? lcs[i + 1][j + 1] + 1 : Math.max(lcs[i + 1][j], lcs[i][j + 1]);
}
}
const lines: DiffLine[] = [];
let i = 0;
let j = 0;
while (i < m && j < n) {
if (previousLines[i] === nextLines[j]) {
lines.push({ kind: "same", text: previousLines[i] });
i += 1;
j += 1;
} else if (lcs[i + 1][j] >= lcs[i][j + 1]) {
lines.push({ kind: "removed", text: previousLines[i] });
i += 1;
} else {
lines.push({ kind: "added", text: nextLines[j] });
j += 1;
}
}
while (i < m) {
lines.push({ kind: "removed", text: previousLines[i] });
i += 1;
}
while (j < n) {
lines.push({ kind: "added", text: nextLines[j] });
j += 1;
}
return lines;
}
export function buildConfigDiff(serverInstanceId: string, previous: string, next: string): ConfigDiffView {
const lines = computeLineDiff(previous, next);
const added = lines.filter((line) => line.kind === "added").length;
const removed = lines.filter((line) => line.kind === "removed").length;
return {
serverInstanceId,
summary: `+${added} / -${removed} 行变更`,
lines,
nextContent: next
};
}
export function diffHasChanges(diff: ConfigDiffView): boolean {
return diff.lines.some((line) => line.kind !== "same");
}
+171
View File
@@ -0,0 +1,171 @@
import { describe, expect, it, vi } from "vitest";
import type { PluginBridgeManifestContract } from "../contracts/pluginBridge";
import {
createPluginBridgeDispatcher,
createPluginBridgeHostContext,
filterAllowedPermissions,
parsePluginArtifactReference,
validateBridgeExecutionRequest
} from "./pluginBridgeHost";
const plugin: PluginBridgeManifestContract = {
id: "game.example",
declaredPermissions: ["server.read", "server.logs.read", "server.files.read", "server.artifacts.read", "ai.invoke"],
bridgeActions: ["server.instances.read", "logs.query", "files.request", "artifacts.open", "ai.invoke"],
pages: [
{
key: "logs",
title: "Logs",
path: "/logs",
permissions: ["server.logs.read", "server.files.read", "server.artifacts.read", "ai.invoke"],
bridgeActions: ["logs.query", "files.request", "artifacts.open", "ai.invoke"]
}
],
aiPurposes: ["logs.diagnose"]
};
describe("plugin bridge host utilities", () => {
it("filters page permissions against manifest permissions", () => {
expect(filterAllowedPermissions(plugin.declaredPermissions, ["server.logs.read", "server.artifacts.read"])).toEqual(["server.logs.read", "server.artifacts.read"]);
});
it("creates safe host context without secret-bearing fields", () => {
const context = createPluginBridgeHostContext({
plugin,
routeKey: "logs",
serverInstanceId: "server-1",
themeTokens: { colorScheme: "dark", accentColor: "#22c55e" }
});
expect(context).toEqual({
pluginId: "game.example",
routeKey: "logs",
serverInstanceId: "server-1",
themeTokens: { colorScheme: "dark", accentColor: "#22c55e" },
permissions: ["server.logs.read", "server.files.read", "server.artifacts.read", "ai.invoke"],
bridgeActions: ["logs.query", "files.request", "artifacts.open", "ai.invoke"],
aiPurposes: ["logs.diagnose"]
});
expect(context).not.toHaveProperty("apiKey");
expect(context).not.toHaveProperty("runCredential");
expect(context).not.toHaveProperty("hostPath");
});
it("builds bridge execution requests through the platform client", async () => {
const context = createPluginBridgeHostContext({
plugin,
routeKey: "logs",
serverInstanceId: "server-1",
themeTokens: { colorScheme: "dark", accentColor: "#22c55e" }
});
const client = {
executePluginBridge: vi.fn(async () => ({
requestId: "req-1",
pluginId: "game.example",
routeKey: "logs",
serverInstanceId: "server-1",
action: "logs.query",
status: "ok",
result: { entryCount: "0" }
}))
};
const dispatch = createPluginBridgeDispatcher(context, client);
await expect(dispatch({ requestId: "req-1", action: "logs.query", payload: { logStreamId: "log-1" } })).resolves.toMatchObject({
status: "ok",
result: { entryCount: "0" }
});
expect(client.executePluginBridge).toHaveBeenCalledWith({
requestId: "req-1",
pluginId: "game.example",
routeKey: "logs",
serverInstanceId: "server-1",
action: "logs.query",
aiPurpose: undefined,
payload: { logStreamId: "log-1" }
});
});
it("rejects denied, unsafe, and cancelled bridge execution locally", async () => {
const context = createPluginBridgeHostContext({
plugin,
routeKey: "logs",
serverInstanceId: "server-1",
themeTokens: { colorScheme: "dark", accentColor: "#22c55e" }
});
expect(validateBridgeExecutionRequest(context, { requestId: "req-denied", action: "server.instances.read" })).toMatchObject({ code: "unsupported_action" });
expect(
validateBridgeExecutionRequest(context, { requestId: "req-unsafe", action: "files.request", payload: { key: "/Users/tasia/.ssh/id_rsa" } })
).toMatchObject({ code: "unsafe_payload" });
const client = { executePluginBridge: vi.fn() };
const controller = new AbortController();
controller.abort();
const dispatch = createPluginBridgeDispatcher(context, client);
await expect(dispatch({ requestId: "req-cancel", action: "logs.query", payload: { logStreamId: "log-1" } }, controller.signal)).resolves.toMatchObject({
status: "cancelled",
error: { code: "cancelled" }
});
expect(client.executePluginBridge).not.toHaveBeenCalled();
});
it("dispatches mediated AI requests without provider configuration", async () => {
const context = createPluginBridgeHostContext({
plugin,
routeKey: "logs",
serverInstanceId: "server-1",
themeTokens: { colorScheme: "dark", accentColor: "#22c55e" }
});
expect(validateBridgeExecutionRequest(context, { requestId: "ai-denied", action: "ai.invoke", aiPurpose: "config.suggest" })).toMatchObject({ code: "ai_purpose_denied" });
const client = {
executePluginBridge: vi.fn(async () => ({
requestId: "ai-1",
pluginId: "game.example",
routeKey: "logs",
serverInstanceId: "server-1",
action: "ai.invoke",
status: "ok",
result: { recommendation: "Mock AI recommendation", mocked: "true" }
}))
};
const dispatch = createPluginBridgeDispatcher(context, client);
const response = await dispatch({ requestId: "ai-1", action: "ai.invoke", aiPurpose: "logs.diagnose", payload: { prompt: "Summarize logs" } });
expect(response).toMatchObject({ status: "ok", result: { mocked: "true" } });
const serialized = JSON.stringify(response);
expect(serialized).not.toContain("apiKeyRef");
expect(serialized).not.toContain("rawApiKey");
expect(serialized).not.toContain("baseUrl");
});
it("parses safe artifact bridge references and rejects backend internals", () => {
expect(
parsePluginArtifactReference({
artifactId: "artifact-1",
filename: "artifact-1.bin",
contentType: "application/octet-stream",
sizeBytes: "128",
checksum: "sha256:abc",
downloadUrl: "/api/v1/artifacts/artifact-1/content",
expiresAt: "2026-07-03T00:15:00Z",
rangeSupported: "true",
chunkSizeBytes: "1048576",
storageBehavior: "platform-memory-transfer-session"
})
).toMatchObject({ artifactId: "artifact-1", downloadUrl: "/api/v1/artifacts/artifact-1/content", rangeSupported: true });
expect(
parsePluginArtifactReference({
artifactId: "artifact-1",
filename: "/Users/tasia/artifact.bin",
contentType: "application/octet-stream",
sizeBytes: "128",
checksum: "sha256:abc",
downloadUrl: "storage://bucket/artifact-1",
expiresAt: "2026-07-03T00:15:00Z",
rangeSupported: "true",
chunkSizeBytes: "1048576"
})
).toBeNull();
});
});
+209
View File
@@ -0,0 +1,209 @@
import type {
PluginBridgeAction,
PluginBridgeExecuteEnvelope,
PluginBridgeExecutionResult,
PluginBridgeHostContext,
PluginBridgeManifestContract,
PluginBridgeThemeTokens,
PluginPermission
} from "../contracts/pluginBridge";
import type { PlatformApiClient } from "../api/client";
export function createPluginBridgeHostContext(input: {
plugin: PluginBridgeManifestContract;
routeKey: string;
serverInstanceId?: string;
themeTokens: PluginBridgeThemeTokens;
}): PluginBridgeHostContext {
const page = input.plugin.pages.find((candidate) => candidate.key === input.routeKey);
const permissions = filterAllowedPermissions(input.plugin.declaredPermissions, page?.permissions);
const bridgeActions =
page?.bridgeActions === undefined
? [...input.plugin.bridgeActions]
: page.bridgeActions.filter((action) => input.plugin.bridgeActions.includes(action));
return {
pluginId: input.plugin.id,
routeKey: input.routeKey,
serverInstanceId: input.serverInstanceId,
themeTokens: input.themeTokens,
permissions,
bridgeActions,
aiPurposes: [...(input.plugin.aiPurposes ?? [])]
};
}
export function filterAllowedPermissions(
declaredPermissions: PluginPermission[],
pagePermissions: PluginPermission[] | undefined
): PluginPermission[] {
if (pagePermissions === undefined) {
return [...declaredPermissions];
}
return pagePermissions.filter((permission) => declaredPermissions.includes(permission));
}
export function createPluginBridgeDispatcher(context: PluginBridgeHostContext, client: Pick<PlatformApiClient, "executePluginBridge">) {
return async function dispatchBridgeRequest(envelope: PluginBridgeExecuteEnvelope, signal?: AbortSignal): Promise<PluginBridgeExecutionResult> {
const localError = validateBridgeExecutionRequest(context, envelope);
if (localError) {
return {
requestId: envelope.requestId,
action: envelope.action,
status: "denied",
error: localError
};
}
if (signal?.aborted) {
return {
requestId: envelope.requestId,
action: envelope.action,
status: "cancelled",
error: { code: "cancelled", message: "bridge request was cancelled" }
};
}
try {
const response = await client.executePluginBridge({
requestId: envelope.requestId,
pluginId: context.pluginId,
routeKey: context.routeKey,
serverInstanceId: context.serverInstanceId,
action: envelope.action,
aiPurpose: envelope.aiPurpose,
payload: envelope.payload
});
return {
requestId: response.requestId,
action: response.action as PluginBridgeAction,
status: response.status,
result: response.result,
error: response.error
};
} catch (error) {
return {
requestId: envelope.requestId,
action: envelope.action,
status: "error",
error: { code: "platform_error", message: error instanceof Error ? error.message : "bridge execution failed" }
};
}
};
}
export function validateBridgeExecutionRequest(context: PluginBridgeHostContext, envelope: PluginBridgeExecuteEnvelope): PluginBridgeExecutionResult["error"] | null {
if (!envelope.requestId.trim()) {
return { code: "validation", message: "requestId is required" };
}
if (!context.bridgeActions.includes(envelope.action)) {
return { code: "unsupported_action", message: "bridge action is not allowed for this page" };
}
for (const permission of requiredPermissions(envelope.action)) {
if (!context.permissions.includes(permission)) {
return { code: "permission_denied", message: "bridge action is missing required permission" };
}
}
if (envelope.action === "ai.invoke" && (!envelope.aiPurpose || !context.aiPurposes.includes(envelope.aiPurpose))) {
return { code: "ai_purpose_denied", message: "AI purpose is not allowed for this plugin" };
}
const payload = envelope.payload ?? {};
if (Object.keys(payload).length > 16) {
return { code: "payload_too_large", message: "bridge payload has too many keys" };
}
const encodedSize = Object.entries(payload).reduce((sum, [key, value]) => sum + key.length + value.length, 0);
if (encodedSize > 4096) {
return { code: "payload_too_large", message: "bridge payload is too large" };
}
for (const [key, value] of Object.entries(payload)) {
if (!key.trim() || key !== key.trim()) {
return { code: "validation", message: "bridge payload key is invalid" };
}
if (containsUnsafeBridgeContent(key) || containsUnsafeBridgeContent(value)) {
return { code: "unsafe_payload", message: "bridge payload contains unsafe content" };
}
}
return null;
}
export interface PluginArtifactReference {
artifactId: string;
filename: string;
contentType: string;
sizeBytes: number;
checksum: string;
downloadUrl: string;
expiresAt: string;
rangeSupported: boolean;
chunkSizeBytes: number;
storageBehavior?: string;
}
export function parsePluginArtifactReference(result: Record<string, string> | undefined): PluginArtifactReference | null {
if (!result) {
return null;
}
const sizeBytes = Number(result.sizeBytes);
const chunkSizeBytes = Number(result.chunkSizeBytes);
const reference: PluginArtifactReference = {
artifactId: result.artifactId ?? "",
filename: result.filename ?? "artifact.bin",
contentType: result.contentType ?? "application/octet-stream",
sizeBytes,
checksum: result.checksum ?? "",
downloadUrl: result.downloadUrl ?? "",
expiresAt: result.expiresAt ?? "",
rangeSupported: result.rangeSupported === "true",
chunkSizeBytes,
storageBehavior: result.storageBehavior
};
if (!reference.artifactId || !Number.isFinite(sizeBytes) || sizeBytes <= 0 || !Number.isFinite(chunkSizeBytes) || chunkSizeBytes <= 0) {
return null;
}
if (!reference.downloadUrl.startsWith(`/api/v1/artifacts/${encodeURIComponent(reference.artifactId)}/content`)) {
return null;
}
for (const value of Object.values(reference)) {
if (typeof value === "string" && containsUnsafeBridgeContent(value)) {
return null;
}
}
return reference;
}
function requiredPermissions(action: PluginBridgeAction): PluginPermission[] {
switch (action) {
case "server.instances.read":
return ["server.read"];
case "jobs.dispatch":
return ["server.lifecycle"];
case "logs.query":
return ["server.logs.read"];
case "artifacts.open":
return ["server.artifacts.read"];
case "files.request":
return ["server.files.read"];
case "ai.invoke":
return ["ai.invoke"];
default:
return [];
}
}
function containsUnsafeBridgeContent(value: string): boolean {
const lowered = value.trim().toLowerCase();
if (!lowered) {
return false;
}
return (
lowered.includes("/users/") ||
lowered.includes("/private/") ||
lowered.includes("unix://") ||
lowered.includes("tcp://") ||
lowered.includes("bearer ") ||
lowered.startsWith("sk-") ||
lowered.includes("password=") ||
lowered.includes("api_key=") ||
lowered.includes("apikey=") ||
lowered.includes("host path") ||
lowered.includes("rawapikey")
);
}