fix(ci): repair phone control and cron schema gates

This commit is contained in:
Vincent Koc
2026-06-01 03:25:20 +01:00
parent c317fd2bd7
commit f42cf9059e
5 changed files with 47 additions and 21 deletions

View File

@@ -2,6 +2,7 @@ import fs from "node:fs/promises";
import os from "node:os";
import path from "node:path";
import type { OpenKeyedStoreOptions } from "openclaw/plugin-sdk/plugin-state-runtime";
import type { PluginStateKeyedStore } from "openclaw/plugin-sdk/plugin-state-runtime";
import {
createPluginStateKeyedStoreForTests,
resetPluginStateStoreForTests,
@@ -101,6 +102,25 @@ function createPhoneControlConfig(): Record<string, unknown> {
};
}
function createMockOpenKeyedStore(params: {
lookup: ReturnType<typeof vi.fn>;
delete?: ReturnType<typeof vi.fn>;
}): OpenClawPluginApi["runtime"]["state"]["openKeyedStore"] {
return <T>() => {
const store: PluginStateKeyedStore<T> = {
register: vi.fn(async () => {}),
registerIfAbsent: vi.fn(async () => true),
update: vi.fn(async () => true),
lookup: params.lookup as (key: string) => Promise<T | undefined>,
consume: vi.fn(async () => undefined),
delete: (params.delete ?? vi.fn(async () => true)) as (key: string) => Promise<boolean>,
entries: vi.fn(async () => []),
clear: vi.fn(async () => {}),
};
return store;
};
}
async function withRegisteredPhoneControl(
run: (params: {
command: OpenClawPluginCommandDefinition;
@@ -343,7 +363,7 @@ describe("phone-control plugin", () => {
it("does not block service startup on the initial expiry check", async () => {
const stateDir = await fs.mkdtemp(path.join(os.tmpdir(), PHONE_CONTROL_STATE_PREFIX));
try {
const lookup = vi.fn(async () => null);
const lookup = vi.fn(async () => undefined);
let service: OpenClawPluginService | undefined;
registerPhoneControl.register(
@@ -355,12 +375,7 @@ describe("phone-control plugin", () => {
registerService: (registeredService) => {
service = registeredService;
},
openKeyedStore: () =>
({
lookup,
delete: vi.fn(),
register: vi.fn(),
}) as ReturnType<OpenClawPluginApi["runtime"]["state"]["openKeyedStore"]>,
openKeyedStore: createMockOpenKeyedStore({ lookup }),
}),
);
@@ -376,7 +391,9 @@ describe("phone-control plugin", () => {
expect(lookup).not.toHaveBeenCalled();
await new Promise<void>((resolve) => setImmediate(resolve));
await new Promise<void>((resolve) => {
setImmediate(resolve);
});
expect(lookup).toHaveBeenCalledWith("current");
@@ -425,12 +442,7 @@ describe("phone-control plugin", () => {
registerService: (registeredService) => {
service = registeredService;
},
openKeyedStore: () =>
({
lookup,
delete: removeState,
register: vi.fn(),
}) as ReturnType<OpenClawPluginApi["runtime"]["state"]["openKeyedStore"]>,
openKeyedStore: createMockOpenKeyedStore({ lookup, delete: removeState }),
}),
);

View File

@@ -35,6 +35,14 @@ type ArmStateFileV2 = {
};
type ArmStateFile = ArmStateFileV1 | ArmStateFileV2;
type PhoneControlConfigView = {
readonly gateway?: {
readonly nodes?: {
readonly allowCommands?: readonly string[];
readonly denyCommands?: readonly string[];
};
};
};
const STATE_VERSION = 2;
const ARM_STATE_NAMESPACE = "armed";
@@ -119,15 +127,15 @@ async function writeArmState(api: OpenClawPluginApi, state: ArmStateFile | null)
await store.register(ARM_STATE_KEY, state);
}
function normalizeDenyList(cfg: OpenClawPluginApi["config"]): string[] {
function normalizeDenyList(cfg: PhoneControlConfigView): string[] {
return uniqSorted([...(cfg.gateway?.nodes?.denyCommands ?? [])]);
}
function normalizeAllowList(cfg: OpenClawPluginApi["config"]): string[] {
function normalizeAllowList(cfg: PhoneControlConfigView): string[] {
return uniqSorted([...(cfg.gateway?.nodes?.allowCommands ?? [])]);
}
function hasPhoneControlAllowOverride(cfg: OpenClawPluginApi["config"]): boolean {
function hasPhoneControlAllowOverride(cfg: PhoneControlConfigView): boolean {
const allow = new Set(normalizeAllowList(cfg));
return PHONE_CONTROL_COMMANDS.some((cmd) => allow.has(cmd));
}

View File

@@ -64,11 +64,17 @@ function isMissingOrEmptyObject(value: unknown): boolean {
}
function nullableStringSchema(description: string) {
return Type.Optional(Type.Union([Type.String(), Type.Null()], { description }));
return Type.Optional(Type.Unsafe<string | null>({ type: "string", description }));
}
function nullableStringArraySchema(description: string) {
return Type.Optional(Type.Union([Type.Array(Type.String()), Type.Null()], { description }));
return Type.Optional(
Type.Unsafe<string[] | null>({
type: "array",
items: { type: "string" },
description,
}),
);
}
function deliveryStringSchema(params: { description: string; nullableClears: boolean }) {

View File

@@ -212,7 +212,7 @@ async function prepareCodexHomeForLiveBindTest(tempRoot: string): Promise<void>
() => {
hasAuthFile = true;
},
(error) => {
(error: unknown) => {
if ((error as NodeJS.ErrnoException)?.code !== "ENOENT") {
throw error;
}

View File

@@ -465,8 +465,8 @@ function loadBundledEntryModuleSync(
return loaded;
}
// oxlint-disable-next-line typescript/no-unnecessary-type-parameters -- Dynamic entry export loaders use caller-supplied export types.
/** Loads one export from a bundled channel sidecar module through the guarded entry boundary. */
// oxlint-disable-next-line typescript/no-unnecessary-type-parameters -- Dynamic entry export loaders use caller-supplied export types.
export function loadBundledEntryExportSync<T>(
importMetaUrl: string,
reference: BundledEntryModuleRef,