mirror of
https://github.com/openclaw/openclaw.git
synced 2026-04-12 09:41:11 +00:00
fix(ci): restore contracts and type gates
This commit is contained in:
@@ -117,10 +117,10 @@ async function sendDiscordMediaOnly(params: {
|
||||
target: string;
|
||||
cfg: OpenClawConfig;
|
||||
token: string;
|
||||
rest: RequestClient;
|
||||
rest?: RequestClient;
|
||||
mediaUrl: string;
|
||||
accountId: string;
|
||||
mediaLocalRoots?: string[];
|
||||
accountId?: string;
|
||||
mediaLocalRoots?: readonly string[];
|
||||
replyTo?: string;
|
||||
retryConfig: ResolvedRetryConfig;
|
||||
}): Promise<void> {
|
||||
@@ -143,10 +143,10 @@ async function sendDiscordMediaBatch(params: {
|
||||
target: string;
|
||||
cfg: OpenClawConfig;
|
||||
token: string;
|
||||
rest: RequestClient;
|
||||
rest?: RequestClient;
|
||||
mediaUrls: string[];
|
||||
accountId: string;
|
||||
mediaLocalRoots?: string[];
|
||||
accountId?: string;
|
||||
mediaLocalRoots?: readonly string[];
|
||||
replyTo: () => string | undefined;
|
||||
retryConfig: ResolvedRetryConfig;
|
||||
}): Promise<void> {
|
||||
|
||||
@@ -8,7 +8,7 @@ import {
|
||||
|
||||
type TestEntry = {
|
||||
id: string;
|
||||
close: ReturnType<typeof vi.fn>;
|
||||
close: () => Promise<void>;
|
||||
};
|
||||
|
||||
function createTestCache(): ManagedCache<TestEntry> {
|
||||
|
||||
@@ -168,7 +168,7 @@ export const qqbotSetupAdapterShared = {
|
||||
}: {
|
||||
cfg: OpenClawConfig;
|
||||
accountId: string;
|
||||
name: string;
|
||||
name?: string;
|
||||
}) =>
|
||||
applyAccountNameToChannelSection({
|
||||
cfg,
|
||||
|
||||
@@ -15,6 +15,7 @@ export {
|
||||
buildCommandsPaginationKeyboard,
|
||||
buildTelegramModelsProviderChannelData,
|
||||
} from "./src/command-ui.js";
|
||||
export { createTelegramThreadBindingManager } from "./src/thread-bindings.js";
|
||||
export type {
|
||||
TelegramInteractiveHandlerContext,
|
||||
TelegramInteractiveHandlerRegistration,
|
||||
|
||||
@@ -105,19 +105,13 @@ function resolveGeneratedBundledChannelModulePath(params: {
|
||||
if (!params.entry) {
|
||||
return null;
|
||||
}
|
||||
const candidateRoots = [
|
||||
path.resolve(OPENCLAW_PACKAGE_ROOT, "dist", "extensions", params.metadata.dirName),
|
||||
path.resolve(OPENCLAW_PACKAGE_ROOT, "extensions", params.metadata.dirName),
|
||||
];
|
||||
for (const rootDir of candidateRoots) {
|
||||
const resolved = resolveBundledChannelGeneratedPath(
|
||||
rootDir,
|
||||
params.entry,
|
||||
params.metadata.dirName,
|
||||
);
|
||||
if (resolved) {
|
||||
return resolved;
|
||||
}
|
||||
const resolved = resolveBundledChannelGeneratedPath(
|
||||
OPENCLAW_PACKAGE_ROOT,
|
||||
params.entry,
|
||||
params.metadata.dirName,
|
||||
);
|
||||
if (resolved) {
|
||||
return resolved;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -169,8 +169,7 @@ const RUNTIME_API_EXPORT_GUARDS: Record<string, readonly string[]> = {
|
||||
'export * from "./src/send.js";',
|
||||
'export * from "./src/session.js";',
|
||||
'export { setWhatsAppRuntime } from "./src/runtime.js";',
|
||||
"export async function startWebLoginWithQr( ...args: Parameters<StartWebLoginWithQr> ): ReturnType<StartWebLoginWithQr> { const { startWebLoginWithQr } = await loadLoginQrModule(); return await startWebLoginWithQr(...args); }",
|
||||
"export async function waitForWebLogin( ...args: Parameters<WaitForWebLogin> ): ReturnType<WaitForWebLogin> { const { waitForWebLogin } = await loadLoginQrModule(); return await waitForWebLogin(...args); }",
|
||||
'export { startWebLoginWithQr, waitForWebLogin } from "./login-qr-runtime.js";',
|
||||
],
|
||||
} as const;
|
||||
|
||||
|
||||
@@ -8,58 +8,82 @@ import {
|
||||
} from "../../config/runtime-snapshot.js";
|
||||
import { fetchWithSsrFGuard } from "../../infra/net/fetch-guard.js";
|
||||
import { TEST_UNDICI_RUNTIME_DEPS_KEY } from "../../infra/net/undici-runtime.js";
|
||||
import type { PluginManifestRecord } from "../manifest-registry.js";
|
||||
|
||||
const loadPluginManifestRegistryMock = vi.fn();
|
||||
import { clearPluginDiscoveryCache } from "../discovery.js";
|
||||
import { clearPluginManifestRegistryCache } from "../manifest-registry.js";
|
||||
|
||||
const originalBundledPluginsDir = process.env.OPENCLAW_BUNDLED_PLUGINS_DIR;
|
||||
const originalStateDir = process.env.OPENCLAW_STATE_DIR;
|
||||
const originalGlobalFetch = globalThis.fetch;
|
||||
const tempDirs: string[] = [];
|
||||
|
||||
function createRuntimePluginDir(pluginId: string, marker: string): string {
|
||||
const rootDir = fs.mkdtempSync(path.join(os.tmpdir(), `openclaw-runtime-contract-${pluginId}-`));
|
||||
tempDirs.push(rootDir);
|
||||
const pluginRoot = path.join(rootDir, pluginId);
|
||||
function createInstalledRuntimePluginDir(
|
||||
pluginId: string,
|
||||
marker: string,
|
||||
): {
|
||||
bundledDir: string;
|
||||
stateDir: string;
|
||||
pluginRoot: string;
|
||||
} {
|
||||
const bundledDir = fs.mkdtempSync(
|
||||
path.join(os.tmpdir(), `openclaw-runtime-contract-bundled-${pluginId}-`),
|
||||
);
|
||||
const stateDir = fs.mkdtempSync(
|
||||
path.join(os.tmpdir(), `openclaw-runtime-contract-state-${pluginId}-`),
|
||||
);
|
||||
tempDirs.push(bundledDir, stateDir);
|
||||
const pluginRoot = path.join(stateDir, "extensions", pluginId);
|
||||
fs.mkdirSync(pluginRoot, { recursive: true });
|
||||
fs.writeFileSync(
|
||||
path.join(pluginRoot, "runtime-api.js"),
|
||||
`export const marker = ${JSON.stringify(marker)};\n`,
|
||||
"utf8",
|
||||
);
|
||||
return pluginRoot;
|
||||
}
|
||||
|
||||
function buildPluginManifestRecord(params: {
|
||||
id: string;
|
||||
origin: PluginManifestRecord["origin"];
|
||||
rootDir: string;
|
||||
}): PluginManifestRecord {
|
||||
fs.writeFileSync(
|
||||
path.join(pluginRoot, "package.json"),
|
||||
JSON.stringify({
|
||||
name: `@openclaw/${pluginId}`,
|
||||
version: "0.0.0",
|
||||
openclaw: {
|
||||
extensions: ["./runtime-api.js"],
|
||||
channel: { id: pluginId },
|
||||
},
|
||||
}),
|
||||
"utf8",
|
||||
);
|
||||
fs.writeFileSync(
|
||||
path.join(pluginRoot, "openclaw.plugin.json"),
|
||||
JSON.stringify({
|
||||
id: pluginId,
|
||||
channels: [pluginId],
|
||||
configSchema: { type: "object", additionalProperties: false, properties: {} },
|
||||
}),
|
||||
"utf8",
|
||||
);
|
||||
return {
|
||||
id: params.id,
|
||||
origin: params.origin,
|
||||
rootDir: params.rootDir,
|
||||
source: params.rootDir,
|
||||
manifestPath: path.join(params.rootDir, "openclaw.plugin.json"),
|
||||
channels: [params.id],
|
||||
providers: [],
|
||||
cliBackends: [],
|
||||
skills: [],
|
||||
hooks: [],
|
||||
bundledDir,
|
||||
stateDir,
|
||||
pluginRoot,
|
||||
};
|
||||
}
|
||||
|
||||
afterEach(() => {
|
||||
loadPluginManifestRegistryMock.mockReset();
|
||||
clearRuntimeConfigSnapshot();
|
||||
vi.restoreAllMocks();
|
||||
vi.resetModules();
|
||||
vi.doUnmock("../manifest-registry.js");
|
||||
vi.doUnmock("../../config/plugin-auto-enable.js");
|
||||
clearPluginDiscoveryCache();
|
||||
clearPluginManifestRegistryCache();
|
||||
Reflect.deleteProperty(globalThis as object, TEST_UNDICI_RUNTIME_DEPS_KEY);
|
||||
if (originalBundledPluginsDir === undefined) {
|
||||
delete process.env.OPENCLAW_BUNDLED_PLUGINS_DIR;
|
||||
} else {
|
||||
process.env.OPENCLAW_BUNDLED_PLUGINS_DIR = originalBundledPluginsDir;
|
||||
}
|
||||
if (originalStateDir === undefined) {
|
||||
delete process.env.OPENCLAW_STATE_DIR;
|
||||
} else {
|
||||
process.env.OPENCLAW_STATE_DIR = originalStateDir;
|
||||
}
|
||||
if (originalGlobalFetch) {
|
||||
(globalThis as Record<string, unknown>).fetch = originalGlobalFetch;
|
||||
} else {
|
||||
@@ -72,48 +96,45 @@ afterEach(() => {
|
||||
|
||||
describe("shared runtime seam contracts", () => {
|
||||
it("allows activated runtime facades when the resolved plugin root matches an installed-style manifest record", async () => {
|
||||
const pluginRoot = createRuntimePluginDir("line", "line-ok");
|
||||
process.env.OPENCLAW_BUNDLED_PLUGINS_DIR = path.dirname(pluginRoot);
|
||||
const pluginId = "line-contract-fixture";
|
||||
const { bundledDir, stateDir } = createInstalledRuntimePluginDir(pluginId, "line-ok");
|
||||
process.env.OPENCLAW_BUNDLED_PLUGINS_DIR = bundledDir;
|
||||
process.env.OPENCLAW_STATE_DIR = stateDir;
|
||||
setRuntimeConfigSnapshot({
|
||||
plugins: {
|
||||
entries: {
|
||||
line: {
|
||||
[pluginId]: {
|
||||
enabled: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
loadPluginManifestRegistryMock.mockReturnValue({
|
||||
plugins: [buildPluginManifestRecord({ id: "line", origin: "global", rootDir: pluginRoot })],
|
||||
diagnostics: [],
|
||||
});
|
||||
vi.doMock("../manifest-registry.js", async () => {
|
||||
const actual =
|
||||
await vi.importActual<typeof import("../manifest-registry.js")>("../manifest-registry.js");
|
||||
return {
|
||||
...actual,
|
||||
loadPluginManifestRegistry: (
|
||||
...args: Parameters<typeof actual.loadPluginManifestRegistry>
|
||||
) => loadPluginManifestRegistryMock(...args),
|
||||
};
|
||||
});
|
||||
clearPluginDiscoveryCache();
|
||||
clearPluginManifestRegistryCache();
|
||||
vi.resetModules();
|
||||
vi.doMock("../../config/plugin-auto-enable.js", () => ({
|
||||
applyPluginAutoEnable: ({ config }: { config?: unknown }) => ({
|
||||
config: config ?? {},
|
||||
autoEnabledReasons: {},
|
||||
}),
|
||||
}));
|
||||
|
||||
const facadeRuntime = await import("../../plugin-sdk/facade-runtime.js");
|
||||
facadeRuntime.resetFacadeRuntimeStateForTest();
|
||||
|
||||
expect(
|
||||
facadeRuntime.canLoadActivatedBundledPluginPublicSurface({
|
||||
dirName: "line",
|
||||
dirName: pluginId,
|
||||
artifactBasename: "runtime-api.js",
|
||||
}),
|
||||
).toBe(true);
|
||||
expect(
|
||||
facadeRuntime.loadActivatedBundledPluginPublicSurfaceModuleSync<{ marker: string }>({
|
||||
dirName: "line",
|
||||
dirName: pluginId,
|
||||
artifactBasename: "runtime-api.js",
|
||||
}).marker,
|
||||
).toBe("line-ok");
|
||||
expect(facadeRuntime.listImportedBundledPluginFacadeIds()).toEqual(["line"]);
|
||||
expect(facadeRuntime.listImportedBundledPluginFacadeIds()).toEqual([pluginId]);
|
||||
});
|
||||
|
||||
it("keeps guarded fetch on mocked global fetches even when a dispatcher is attached", async () => {
|
||||
|
||||
@@ -203,12 +203,14 @@ const sessionBindingContractEntries: Record<
|
||||
const { createThreadBindingManager } = await getContractApi<{
|
||||
createThreadBindingManager: (params: {
|
||||
accountId: string;
|
||||
cfg?: OpenClawConfig;
|
||||
persist: boolean;
|
||||
enableSweeper: boolean;
|
||||
}) => unknown;
|
||||
}>("discord");
|
||||
createThreadBindingManager({
|
||||
accountId: "default",
|
||||
cfg: baseSessionBindingCfg,
|
||||
persist: false,
|
||||
enableSweeper: false,
|
||||
});
|
||||
@@ -221,12 +223,14 @@ const sessionBindingContractEntries: Record<
|
||||
const { createThreadBindingManager } = await getContractApi<{
|
||||
createThreadBindingManager: (params: {
|
||||
accountId: string;
|
||||
cfg?: OpenClawConfig;
|
||||
persist: boolean;
|
||||
enableSweeper: boolean;
|
||||
}) => unknown;
|
||||
}>("discord");
|
||||
createThreadBindingManager({
|
||||
accountId: "default",
|
||||
cfg: baseSessionBindingCfg,
|
||||
persist: false,
|
||||
enableSweeper: false,
|
||||
});
|
||||
@@ -238,9 +242,8 @@ const sessionBindingContractEntries: Record<
|
||||
channel: "discord",
|
||||
accountId: "default",
|
||||
conversationId: "channel:123456789012345678",
|
||||
parentConversationId: "channel:123456789012345677",
|
||||
},
|
||||
placement: "child",
|
||||
placement: "current",
|
||||
metadata: {
|
||||
agentId: "discord",
|
||||
label: "discord-child",
|
||||
@@ -250,7 +253,6 @@ const sessionBindingContractEntries: Record<
|
||||
channel: "discord",
|
||||
accountId: "default",
|
||||
conversationId: "channel:123456789012345678",
|
||||
parentConversationId: "channel:123456789012345677",
|
||||
targetSessionKey: "agent:discord:child:thread-1",
|
||||
});
|
||||
return binding;
|
||||
@@ -269,20 +271,18 @@ const sessionBindingContractEntries: Record<
|
||||
adapterAvailable: true,
|
||||
bindSupported: true,
|
||||
unbindSupported: true,
|
||||
placements: ["current", "child"],
|
||||
placements: ["current"],
|
||||
},
|
||||
getCapabilities: async () => {
|
||||
const { createFeishuThreadBindingManager } = await getContractApi<{
|
||||
createFeishuThreadBindingManager: (params: {
|
||||
accountId: string;
|
||||
persist: boolean;
|
||||
enableSweeper: boolean;
|
||||
accountId?: string;
|
||||
cfg: OpenClawConfig;
|
||||
}) => unknown;
|
||||
}>("feishu");
|
||||
createFeishuThreadBindingManager({
|
||||
accountId: "default",
|
||||
persist: false,
|
||||
enableSweeper: false,
|
||||
cfg: baseSessionBindingCfg,
|
||||
});
|
||||
return getSessionBindingService().getCapabilities({
|
||||
channel: "feishu",
|
||||
@@ -292,15 +292,13 @@ const sessionBindingContractEntries: Record<
|
||||
bindAndResolve: async () => {
|
||||
const { createFeishuThreadBindingManager } = await getContractApi<{
|
||||
createFeishuThreadBindingManager: (params: {
|
||||
accountId: string;
|
||||
persist: boolean;
|
||||
enableSweeper: boolean;
|
||||
accountId?: string;
|
||||
cfg: OpenClawConfig;
|
||||
}) => unknown;
|
||||
}>("feishu");
|
||||
createFeishuThreadBindingManager({
|
||||
accountId: "default",
|
||||
persist: false,
|
||||
enableSweeper: false,
|
||||
cfg: baseSessionBindingCfg,
|
||||
});
|
||||
const service = getSessionBindingService();
|
||||
const binding = await service.bind({
|
||||
@@ -309,10 +307,10 @@ const sessionBindingContractEntries: Record<
|
||||
conversation: {
|
||||
channel: "feishu",
|
||||
accountId: "default",
|
||||
conversationId: "chat:123456",
|
||||
parentConversationId: "chat:123455",
|
||||
conversationId: "oc_group_chat:topic:om_topic_root",
|
||||
parentConversationId: "oc_group_chat",
|
||||
},
|
||||
placement: "child",
|
||||
placement: "current",
|
||||
metadata: {
|
||||
agentId: "feishu",
|
||||
label: "feishu-child",
|
||||
@@ -321,8 +319,8 @@ const sessionBindingContractEntries: Record<
|
||||
expectResolvedSessionBinding({
|
||||
channel: "feishu",
|
||||
accountId: "default",
|
||||
conversationId: "chat:123456",
|
||||
parentConversationId: "chat:123455",
|
||||
conversationId: "oc_group_chat:topic:om_topic_root",
|
||||
parentConversationId: "oc_group_chat",
|
||||
targetSessionKey: "agent:feishu:child:thread-1",
|
||||
});
|
||||
return binding;
|
||||
@@ -332,7 +330,7 @@ const sessionBindingContractEntries: Record<
|
||||
expectClearedSessionBinding({
|
||||
channel: "feishu",
|
||||
accountId: "default",
|
||||
conversationId: "chat:123456",
|
||||
conversationId: "oc_group_chat:topic:om_topic_root",
|
||||
});
|
||||
},
|
||||
},
|
||||
@@ -421,10 +419,10 @@ const sessionBindingContractEntries: Record<
|
||||
conversation: {
|
||||
channel: "matrix",
|
||||
accountId: matrixSessionBindingAuth.accountId,
|
||||
conversationId: "!room:example.org::$thread",
|
||||
conversationId: "$thread",
|
||||
parentConversationId: "!room:example.org",
|
||||
},
|
||||
placement: "child",
|
||||
placement: "current",
|
||||
metadata: {
|
||||
agentId: "matrix",
|
||||
label: "matrix-thread",
|
||||
@@ -433,7 +431,7 @@ const sessionBindingContractEntries: Record<
|
||||
expectResolvedSessionBinding({
|
||||
channel: "matrix",
|
||||
accountId: matrixSessionBindingAuth.accountId,
|
||||
conversationId: "!room:example.org::$thread",
|
||||
conversationId: "$thread",
|
||||
parentConversationId: "!room:example.org",
|
||||
targetSessionKey: "agent:matrix:thread",
|
||||
});
|
||||
@@ -444,7 +442,7 @@ const sessionBindingContractEntries: Record<
|
||||
expectClearedSessionBinding({
|
||||
channel: "matrix",
|
||||
accountId: matrixSessionBindingAuth.accountId,
|
||||
conversationId: "!room:example.org::$thread",
|
||||
conversationId: "$thread",
|
||||
});
|
||||
},
|
||||
},
|
||||
@@ -493,10 +491,9 @@ const sessionBindingContractEntries: Record<
|
||||
conversation: {
|
||||
channel: "telegram",
|
||||
accountId: "default",
|
||||
conversationId: "chat:1234:topic:5678",
|
||||
parentConversationId: "chat:1234",
|
||||
conversationId: "-100200300:topic:77",
|
||||
},
|
||||
placement: "child",
|
||||
placement: "current",
|
||||
metadata: {
|
||||
agentId: "telegram",
|
||||
label: "telegram-topic",
|
||||
@@ -505,8 +502,7 @@ const sessionBindingContractEntries: Record<
|
||||
expectResolvedSessionBinding({
|
||||
channel: "telegram",
|
||||
accountId: "default",
|
||||
conversationId: "chat:1234:topic:5678",
|
||||
parentConversationId: "chat:1234",
|
||||
conversationId: "-100200300:topic:77",
|
||||
targetSessionKey: "agent:telegram:child:thread-1",
|
||||
});
|
||||
return binding;
|
||||
@@ -516,7 +512,7 @@ const sessionBindingContractEntries: Record<
|
||||
expectClearedSessionBinding({
|
||||
channel: "telegram",
|
||||
accountId: "default",
|
||||
conversationId: "chat:1234:topic:5678",
|
||||
conversationId: "-100200300:topic:77",
|
||||
});
|
||||
},
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user