mirror of
https://github.com/openclaw/openclaw.git
synced 2026-08-07 18:42:25 +00:00
fix(gateway): validate plugin descriptors and compact refresh
This commit is contained in:
@@ -210,7 +210,9 @@ import {
|
||||
PluginsSessionActionParamsSchema,
|
||||
PluginsSessionActionResultSchema,
|
||||
type PluginsUiDescriptorsParams,
|
||||
type PluginsUiDescriptorsResult,
|
||||
PluginsUiDescriptorsParamsSchema,
|
||||
PluginsUiDescriptorsResultSchema,
|
||||
ErrorCodes,
|
||||
type EnvironmentSummary,
|
||||
EnvironmentSummarySchema,
|
||||
@@ -880,6 +882,9 @@ export const validatePluginApprovalResolveParams = lazyCompile<PluginApprovalRes
|
||||
export const validatePluginsUiDescriptorsParams = lazyCompile<PluginsUiDescriptorsParams>(
|
||||
PluginsUiDescriptorsParamsSchema,
|
||||
);
|
||||
export const validatePluginsUiDescriptorsResult = lazyCompile<PluginsUiDescriptorsResult>(
|
||||
PluginsUiDescriptorsResultSchema,
|
||||
);
|
||||
export const validatePluginsSessionActionParams = lazyCompile<PluginsSessionActionParams>(
|
||||
PluginsSessionActionParamsSchema,
|
||||
);
|
||||
@@ -1133,6 +1138,7 @@ export {
|
||||
PluginsSessionActionParamsSchema,
|
||||
PluginsSessionActionResultSchema,
|
||||
PluginsUiDescriptorsParamsSchema,
|
||||
PluginsUiDescriptorsResultSchema,
|
||||
ModelsListParamsSchema,
|
||||
SkillsStatusParamsSchema,
|
||||
ToolsCatalogParamsSchema,
|
||||
|
||||
@@ -993,7 +993,10 @@ describe("session accessor file-backed seam", () => {
|
||||
expect(updatedEntry?.outputTokens).toBeUndefined();
|
||||
expect(updatedEntry?.totalTokens).toBeUndefined();
|
||||
expect(updatedEntry?.totalTokensFresh).toBeUndefined();
|
||||
expect(updates).toEqual([{ sessionFile: archived }]);
|
||||
expect(updates).toEqual([
|
||||
{ sessionFile: archived },
|
||||
{ sessionFile: fs.realpathSync(manualTranscriptPath) },
|
||||
]);
|
||||
});
|
||||
|
||||
it("keeps retained messages reachable through an out-of-window label", async () => {
|
||||
|
||||
@@ -1148,6 +1148,7 @@ async function replaceTranscriptForManualCompact(
|
||||
throw err;
|
||||
}
|
||||
emitSessionTranscriptUpdate({ sessionFile: archived });
|
||||
emitSessionTranscriptUpdate({ sessionFile: filePath });
|
||||
return archived;
|
||||
}
|
||||
|
||||
|
||||
@@ -9,6 +9,7 @@ import {
|
||||
validatePluginsSessionActionParams,
|
||||
validatePluginsSessionActionResult,
|
||||
validatePluginsUiDescriptorsParams,
|
||||
validatePluginsUiDescriptorsResult,
|
||||
} from "../../../packages/gateway-protocol/src/index.js";
|
||||
import { formatErrorMessage } from "../../infra/errors.js";
|
||||
import { createSubsystemLogger } from "../../logging/subsystem.js";
|
||||
@@ -54,13 +55,44 @@ export const pluginHostHookHandlers: GatewayRequestHandlers = {
|
||||
);
|
||||
return;
|
||||
}
|
||||
const descriptors = (getActivePluginRegistry()?.controlUiDescriptors ?? []).map((entry) =>
|
||||
Object.assign({}, entry.descriptor, {
|
||||
const descriptors = (getActivePluginRegistry()?.controlUiDescriptors ?? []).map((entry) => {
|
||||
const descriptor: Record<string, unknown> = {
|
||||
id: entry.descriptor.id,
|
||||
pluginId: entry.pluginId,
|
||||
pluginName: entry.pluginName,
|
||||
}),
|
||||
);
|
||||
respond(true, { ok: true, descriptors }, undefined);
|
||||
surface: entry.descriptor.surface,
|
||||
label: entry.descriptor.label,
|
||||
};
|
||||
if (entry.descriptor.description !== undefined) {
|
||||
descriptor.description = entry.descriptor.description;
|
||||
}
|
||||
if (entry.descriptor.placement !== undefined) {
|
||||
descriptor.placement = entry.descriptor.placement;
|
||||
}
|
||||
if (entry.descriptor.schema !== undefined) {
|
||||
descriptor.schema = entry.descriptor.schema;
|
||||
}
|
||||
if (entry.descriptor.requiredScopes !== undefined) {
|
||||
descriptor.requiredScopes = entry.descriptor.requiredScopes;
|
||||
}
|
||||
return descriptor;
|
||||
});
|
||||
const result = { ok: true, descriptors };
|
||||
if (!validatePluginsUiDescriptorsResult(result)) {
|
||||
log.warn("invalid plugins.uiDescriptors result", {
|
||||
errors: validatePluginsUiDescriptorsResult.errors,
|
||||
});
|
||||
respond(
|
||||
false,
|
||||
undefined,
|
||||
errorShape(
|
||||
ErrorCodes.UNAVAILABLE,
|
||||
`invalid plugins.uiDescriptors result: ${formatValidationErrors(validatePluginsUiDescriptorsResult.errors)}`,
|
||||
),
|
||||
);
|
||||
return;
|
||||
}
|
||||
respond(true, result, undefined);
|
||||
},
|
||||
"plugins.sessionAction": async ({ params, client, respond }) => {
|
||||
if (!validatePluginsSessionActionParams(params)) {
|
||||
|
||||
@@ -7,11 +7,13 @@ import {
|
||||
} from "openclaw/plugin-sdk/plugin-test-contracts";
|
||||
import { afterEach, describe, expect, it } from "vitest";
|
||||
import {
|
||||
validatePluginsUiDescriptorsResult,
|
||||
validatePluginsUiDescriptorsParams,
|
||||
validateSessionsPluginPatchParams,
|
||||
} from "../../../packages/gateway-protocol/src/index.js";
|
||||
import { loadSessionStore, updateSessionStore, type SessionEntry } from "../../config/sessions.js";
|
||||
import { APPROVALS_SCOPE, READ_SCOPE, WRITE_SCOPE } from "../../gateway/operator-scopes.js";
|
||||
import { pluginHostHookHandlers } from "../../gateway/server-methods/plugin-host-hooks.js";
|
||||
import { buildGatewaySessionRow } from "../../gateway/session-utils.js";
|
||||
import { withTempConfig } from "../../gateway/test-temp-config.js";
|
||||
import { emitAgentEvent, resetAgentEventsForTest } from "../../infra/agent-events.js";
|
||||
@@ -1892,6 +1894,84 @@ describe("host-hook fixture plugin contract", () => {
|
||||
).toBe(false);
|
||||
expect(validatePluginsUiDescriptorsParams({})).toBe(true);
|
||||
expect(validatePluginsUiDescriptorsParams({ pluginId: "host-hook-fixture" })).toBe(false);
|
||||
expect(
|
||||
validatePluginsUiDescriptorsResult({
|
||||
ok: true,
|
||||
descriptors: [
|
||||
{
|
||||
id: "approval-panel",
|
||||
pluginId: "host-hook-fixture",
|
||||
surface: "session",
|
||||
label: "Approval panel",
|
||||
},
|
||||
],
|
||||
}),
|
||||
).toBe(true);
|
||||
expect(
|
||||
validatePluginsUiDescriptorsResult({
|
||||
ok: true,
|
||||
descriptors: [
|
||||
{
|
||||
id: "approval-panel",
|
||||
pluginId: "host-hook-fixture",
|
||||
surface: "session",
|
||||
label: "Approval panel",
|
||||
leakedRegistryField: true,
|
||||
},
|
||||
],
|
||||
}),
|
||||
).toBe(false);
|
||||
});
|
||||
|
||||
it("projects plugin UI descriptors through the strict gateway result shape", () => {
|
||||
const { config, registry } = createPluginRegistryFixture();
|
||||
registerTestPlugin({
|
||||
registry,
|
||||
config,
|
||||
record: createPluginRecord({
|
||||
id: "host-hook-fixture",
|
||||
name: "Host Hook Fixture",
|
||||
}),
|
||||
register(api) {
|
||||
api.registerControlUiDescriptor({
|
||||
id: "approval-panel",
|
||||
surface: "session",
|
||||
label: "Approval panel",
|
||||
});
|
||||
},
|
||||
});
|
||||
const descriptorEntry = registry.registry.controlUiDescriptors?.[0];
|
||||
if (!descriptorEntry) {
|
||||
throw new Error("expected control UI descriptor registration");
|
||||
}
|
||||
Object.assign(descriptorEntry.descriptor, { leakedRegistryField: true });
|
||||
setActivePluginRegistry(registry.registry);
|
||||
|
||||
const calls: Array<[boolean, unknown, unknown]> = [];
|
||||
void pluginHostHookHandlers["plugins.uiDescriptors"]({
|
||||
params: {},
|
||||
respond: (ok: boolean, payload: unknown, error: unknown) => {
|
||||
calls.push([ok, payload, error]);
|
||||
},
|
||||
} as never);
|
||||
|
||||
expect(calls).toHaveLength(1);
|
||||
const [ok, payload, error] = calls[0] ?? [];
|
||||
expect(ok).toBe(true);
|
||||
expect(error).toBeUndefined();
|
||||
expect(validatePluginsUiDescriptorsResult(payload)).toBe(true);
|
||||
expect(payload).toEqual({
|
||||
ok: true,
|
||||
descriptors: [
|
||||
{
|
||||
id: "approval-panel",
|
||||
pluginId: "host-hook-fixture",
|
||||
pluginName: "Host Hook Fixture",
|
||||
surface: "session",
|
||||
label: "Approval panel",
|
||||
},
|
||||
],
|
||||
});
|
||||
});
|
||||
|
||||
it("enforces command requiredScopes for gateway clients and command owners", async () => {
|
||||
|
||||
Reference in New Issue
Block a user