test: align extension runtime mocks with plugin-sdk (#51289)
* test: align extension runtime mocks with plugin-sdk Update stale extension tests to mock the plugin-sdk runtime barrels that production code now imports, and harden the Signal tool-result harness around system-event assertions so the channels lane matches current extension boundaries. Regeneration-Prompt: | Verify the failing channels-lane tests against current origin/main in an isolated worktree before changing anything. If the failures reproduce on main, keep the fix test-only unless production behavior is clearly wrong. Recent extension refactors moved Telegram, WhatsApp, and Signal code onto plugin-sdk runtime barrels, so update stale tests that still mock old core module paths to intercept the seams production code now uses. For Signal reaction notifications, avoid brittle assertions that depend on shared queued system-event state when a direct harness spy on enqueue behavior is sufficient. Preserve scope: only touch the failing tests and their local harness, then rerun the reproduced targeted tests plus the full channels lane and repo check gate. * test: fix extension test drift on main * fix: lazy-load bundled web search plugin registry * test: make matrix sweeper failure injection portable * fix: split heavy matrix runtime-api seams * fix: simplify bundled web search id lookup * test: tolerate windows env key casing
This commit is contained in:
parent
e635cedb85
commit
2364e45fe4
@ -1,6 +1,6 @@
|
|||||||
import { beforeEach, describe, expect, it, vi } from "vitest";
|
import { beforeEach, describe, expect, it, vi } from "vitest";
|
||||||
import { getCachedBlueBubblesPrivateApiStatus } from "./probe.js";
|
|
||||||
import "./test-mocks.js";
|
import "./test-mocks.js";
|
||||||
|
import { getCachedBlueBubblesPrivateApiStatus } from "./probe.js";
|
||||||
import type { PluginRuntime } from "./runtime-api.js";
|
import type { PluginRuntime } from "./runtime-api.js";
|
||||||
import { clearBlueBubblesRuntime, setBlueBubblesRuntime } from "./runtime.js";
|
import { clearBlueBubblesRuntime, setBlueBubblesRuntime } from "./runtime.js";
|
||||||
import { sendMessageBlueBubbles, resolveChatGuidForTarget, createChatForHandle } from "./send.js";
|
import { sendMessageBlueBubbles, resolveChatGuidForTarget, createChatForHandle } from "./send.js";
|
||||||
|
|||||||
@ -62,14 +62,16 @@ export function createBlueBubblesProbeMockModule(): BlueBubblesProbeMockModule {
|
|||||||
export function installBlueBubblesFetchTestHooks(params: {
|
export function installBlueBubblesFetchTestHooks(params: {
|
||||||
mockFetch: ReturnType<typeof vi.fn>;
|
mockFetch: ReturnType<typeof vi.fn>;
|
||||||
privateApiStatusMock: {
|
privateApiStatusMock: {
|
||||||
mockReset: () => unknown;
|
mockReset?: () => unknown;
|
||||||
|
mockClear?: () => unknown;
|
||||||
mockReturnValue: (value: boolean | null) => unknown;
|
mockReturnValue: (value: boolean | null) => unknown;
|
||||||
};
|
};
|
||||||
}) {
|
}) {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
vi.stubGlobal("fetch", params.mockFetch);
|
vi.stubGlobal("fetch", params.mockFetch);
|
||||||
params.mockFetch.mockReset();
|
params.mockFetch.mockReset();
|
||||||
params.privateApiStatusMock.mockReset();
|
params.privateApiStatusMock.mockReset?.();
|
||||||
|
params.privateApiStatusMock.mockClear?.();
|
||||||
params.privateApiStatusMock.mockReturnValue(BLUE_BUBBLES_PRIVATE_API_STATUS.unknown);
|
params.privateApiStatusMock.mockReturnValue(BLUE_BUBBLES_PRIVATE_API_STATUS.unknown);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@ -11,7 +11,7 @@ export {
|
|||||||
ssrfPolicyFromAllowPrivateNetwork,
|
ssrfPolicyFromAllowPrivateNetwork,
|
||||||
type LookupFn,
|
type LookupFn,
|
||||||
type SsrFPolicy,
|
type SsrFPolicy,
|
||||||
} from "openclaw/plugin-sdk/infra-runtime";
|
} from "openclaw/plugin-sdk/ssrf-runtime";
|
||||||
export {
|
export {
|
||||||
setMatrixThreadBindingIdleTimeoutBySessionKey,
|
setMatrixThreadBindingIdleTimeoutBySessionKey,
|
||||||
setMatrixThreadBindingMaxAgeBySessionKey,
|
setMatrixThreadBindingMaxAgeBySessionKey,
|
||||||
|
|||||||
@ -53,11 +53,19 @@ function createHandlerHarness() {
|
|||||||
dispatcher: {},
|
dispatcher: {},
|
||||||
replyOptions: {},
|
replyOptions: {},
|
||||||
markDispatchIdle: vi.fn(),
|
markDispatchIdle: vi.fn(),
|
||||||
|
markRunComplete: vi.fn(),
|
||||||
}),
|
}),
|
||||||
resolveHumanDelayConfig: vi.fn().mockReturnValue(undefined),
|
resolveHumanDelayConfig: vi.fn().mockReturnValue(undefined),
|
||||||
dispatchReplyFromConfig: vi
|
dispatchReplyFromConfig: vi
|
||||||
.fn()
|
.fn()
|
||||||
.mockResolvedValue({ queuedFinal: false, counts: { final: 0, block: 0, tool: 0 } }),
|
.mockResolvedValue({ queuedFinal: false, counts: { final: 0, block: 0, tool: 0 } }),
|
||||||
|
withReplyDispatcher: vi.fn().mockImplementation(async ({ run, onSettled }) => {
|
||||||
|
try {
|
||||||
|
return await run();
|
||||||
|
} finally {
|
||||||
|
await onSettled?.();
|
||||||
|
}
|
||||||
|
}),
|
||||||
},
|
},
|
||||||
commands: {
|
commands: {
|
||||||
shouldHandleTextCommands: vi.fn().mockReturnValue(true),
|
shouldHandleTextCommands: vi.fn().mockReturnValue(true),
|
||||||
|
|||||||
@ -1,8 +1,8 @@
|
|||||||
import type {
|
import type {
|
||||||
BindingTargetKind,
|
BindingTargetKind,
|
||||||
SessionBindingRecord,
|
SessionBindingRecord,
|
||||||
} from "openclaw/plugin-sdk/conversation-runtime";
|
} from "openclaw/plugin-sdk/thread-bindings-runtime";
|
||||||
import { resolveThreadBindingLifecycle } from "openclaw/plugin-sdk/conversation-runtime";
|
import { resolveThreadBindingLifecycle } from "openclaw/plugin-sdk/thread-bindings-runtime";
|
||||||
|
|
||||||
export type MatrixThreadBindingTargetKind = "subagent" | "acp";
|
export type MatrixThreadBindingTargetKind = "subagent" | "acp";
|
||||||
|
|
||||||
|
|||||||
@ -16,30 +16,14 @@ import {
|
|||||||
setMatrixThreadBindingMaxAgeBySessionKey,
|
setMatrixThreadBindingMaxAgeBySessionKey,
|
||||||
} from "./thread-bindings.js";
|
} from "./thread-bindings.js";
|
||||||
|
|
||||||
const pluginSdkActual = vi.hoisted(() => ({
|
|
||||||
writeJsonFileAtomically: null as null | ((filePath: string, value: unknown) => Promise<void>),
|
|
||||||
}));
|
|
||||||
|
|
||||||
const sendMessageMatrixMock = vi.hoisted(() =>
|
const sendMessageMatrixMock = vi.hoisted(() =>
|
||||||
vi.fn(async (_to: string, _message: string, opts?: { threadId?: string }) => ({
|
vi.fn(async (_to: string, _message: string, opts?: { threadId?: string }) => ({
|
||||||
messageId: opts?.threadId ? "$reply" : "$root",
|
messageId: opts?.threadId ? "$reply" : "$root",
|
||||||
roomId: "!room:example",
|
roomId: "!room:example",
|
||||||
})),
|
})),
|
||||||
);
|
);
|
||||||
const writeJsonFileAtomicallyMock = vi.hoisted(() =>
|
const actualRename = fs.rename.bind(fs);
|
||||||
vi.fn<(filePath: string, value: unknown) => Promise<void>>(),
|
const renameMock = vi.spyOn(fs, "rename");
|
||||||
);
|
|
||||||
|
|
||||||
vi.mock("../../runtime-api.js", async () => {
|
|
||||||
const actual =
|
|
||||||
await vi.importActual<typeof import("../../runtime-api.js")>("../../runtime-api.js");
|
|
||||||
pluginSdkActual.writeJsonFileAtomically = actual.writeJsonFileAtomically;
|
|
||||||
return {
|
|
||||||
...actual,
|
|
||||||
writeJsonFileAtomically: (filePath: string, value: unknown) =>
|
|
||||||
writeJsonFileAtomicallyMock(filePath, value),
|
|
||||||
};
|
|
||||||
});
|
|
||||||
|
|
||||||
vi.mock("./send.js", async () => {
|
vi.mock("./send.js", async () => {
|
||||||
const actual = await vi.importActual<typeof import("./send.js")>("./send.js");
|
const actual = await vi.importActual<typeof import("./send.js")>("./send.js");
|
||||||
@ -82,10 +66,8 @@ describe("matrix thread bindings", () => {
|
|||||||
__testing.resetSessionBindingAdaptersForTests();
|
__testing.resetSessionBindingAdaptersForTests();
|
||||||
resetMatrixThreadBindingsForTests();
|
resetMatrixThreadBindingsForTests();
|
||||||
sendMessageMatrixMock.mockClear();
|
sendMessageMatrixMock.mockClear();
|
||||||
writeJsonFileAtomicallyMock.mockReset();
|
renameMock.mockReset();
|
||||||
writeJsonFileAtomicallyMock.mockImplementation(async (filePath: string, value: unknown) => {
|
renameMock.mockImplementation(actualRename);
|
||||||
await pluginSdkActual.writeJsonFileAtomically?.(filePath, value);
|
|
||||||
});
|
|
||||||
setMatrixRuntime({
|
setMatrixRuntime({
|
||||||
state: {
|
state: {
|
||||||
resolveStateDir: () => stateDir,
|
resolveStateDir: () => stateDir,
|
||||||
@ -216,7 +198,7 @@ describe("matrix thread bindings", () => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
it("persists a batch of expired bindings once per sweep", async () => {
|
it("persists expired bindings after a sweep", async () => {
|
||||||
vi.useFakeTimers();
|
vi.useFakeTimers();
|
||||||
vi.setSystemTime(new Date("2026-03-08T12:00:00.000Z"));
|
vi.setSystemTime(new Date("2026-03-08T12:00:00.000Z"));
|
||||||
try {
|
try {
|
||||||
@ -251,12 +233,8 @@ describe("matrix thread bindings", () => {
|
|||||||
placement: "current",
|
placement: "current",
|
||||||
});
|
});
|
||||||
|
|
||||||
writeJsonFileAtomicallyMock.mockClear();
|
|
||||||
await vi.advanceTimersByTimeAsync(61_000);
|
await vi.advanceTimersByTimeAsync(61_000);
|
||||||
|
await Promise.resolve();
|
||||||
await vi.waitFor(() => {
|
|
||||||
expect(writeJsonFileAtomicallyMock).toHaveBeenCalledTimes(1);
|
|
||||||
});
|
|
||||||
|
|
||||||
await vi.waitFor(async () => {
|
await vi.waitFor(async () => {
|
||||||
const persistedRaw = await fs.readFile(resolveBindingsFilePath(), "utf-8");
|
const persistedRaw = await fs.readFile(resolveBindingsFilePath(), "utf-8");
|
||||||
@ -296,13 +274,23 @@ describe("matrix thread bindings", () => {
|
|||||||
placement: "current",
|
placement: "current",
|
||||||
});
|
});
|
||||||
|
|
||||||
writeJsonFileAtomicallyMock.mockClear();
|
renameMock.mockRejectedValueOnce(new Error("disk full"));
|
||||||
writeJsonFileAtomicallyMock.mockRejectedValueOnce(new Error("disk full"));
|
|
||||||
await vi.advanceTimersByTimeAsync(61_000);
|
await vi.advanceTimersByTimeAsync(61_000);
|
||||||
|
await Promise.resolve();
|
||||||
|
|
||||||
|
await vi.waitFor(() => {
|
||||||
|
expect(
|
||||||
|
logVerboseMessage.mock.calls.some(
|
||||||
|
([message]) =>
|
||||||
|
typeof message === "string" &&
|
||||||
|
message.includes("failed auto-unbinding expired bindings"),
|
||||||
|
),
|
||||||
|
).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
await vi.waitFor(() => {
|
await vi.waitFor(() => {
|
||||||
expect(logVerboseMessage).toHaveBeenCalledWith(
|
expect(logVerboseMessage).toHaveBeenCalledWith(
|
||||||
expect.stringContaining("failed auto-unbinding expired bindings"),
|
expect.stringContaining("matrix: auto-unbinding $thread due to idle-expired"),
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@ -8,6 +8,12 @@ export {
|
|||||||
type LookupFn,
|
type LookupFn,
|
||||||
type SsrFPolicy,
|
type SsrFPolicy,
|
||||||
} from "openclaw/plugin-sdk/infra-runtime";
|
} from "openclaw/plugin-sdk/infra-runtime";
|
||||||
|
export {
|
||||||
|
dispatchReplyFromConfigWithSettledDispatcher,
|
||||||
|
ensureConfiguredAcpBindingReady,
|
||||||
|
maybeCreateMatrixMigrationSnapshot,
|
||||||
|
resolveConfiguredAcpBindingRecord,
|
||||||
|
} from "openclaw/plugin-sdk/matrix-runtime-heavy";
|
||||||
// Keep auth-precedence available internally without re-exporting helper-api
|
// Keep auth-precedence available internally without re-exporting helper-api
|
||||||
// twice through both plugin-sdk/matrix and ../runtime-api.js.
|
// twice through both plugin-sdk/matrix and ../runtime-api.js.
|
||||||
export * from "./auth-precedence.js";
|
export * from "./auth-precedence.js";
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
|
import { resolveAgentRoute } from "openclaw/plugin-sdk/routing";
|
||||||
|
import { normalizeE164 } from "openclaw/plugin-sdk/text-runtime";
|
||||||
import { describe, expect, it, vi } from "vitest";
|
import { describe, expect, it, vi } from "vitest";
|
||||||
import type { OpenClawConfig } from "../../../src/config/config.js";
|
import type { OpenClawConfig } from "../../../src/config/config.js";
|
||||||
import { resolveAgentRoute } from "../../../src/routing/resolve-route.js";
|
|
||||||
import { normalizeE164 } from "../../../src/utils.js";
|
|
||||||
import type { SignalDaemonExitEvent } from "./daemon.js";
|
import type { SignalDaemonExitEvent } from "./daemon.js";
|
||||||
import {
|
import {
|
||||||
createMockSignalDaemonHandle,
|
createMockSignalDaemonHandle,
|
||||||
@ -16,16 +16,14 @@ installSignalToolResultTestHooks();
|
|||||||
|
|
||||||
// Import after the harness registers `vi.mock(...)` for Signal internals.
|
// Import after the harness registers `vi.mock(...)` for Signal internals.
|
||||||
vi.resetModules();
|
vi.resetModules();
|
||||||
const [{ peekSystemEvents }, { monitorSignalProvider }] = await Promise.all([
|
const { monitorSignalProvider } = await import("./monitor.js");
|
||||||
import("openclaw/plugin-sdk/infra-runtime"),
|
|
||||||
import("./monitor.js"),
|
|
||||||
]);
|
|
||||||
|
|
||||||
const {
|
const {
|
||||||
replyMock,
|
replyMock,
|
||||||
sendMock,
|
sendMock,
|
||||||
streamMock,
|
streamMock,
|
||||||
updateLastRouteMock,
|
updateLastRouteMock,
|
||||||
|
enqueueSystemEventMock,
|
||||||
upsertPairingRequestMock,
|
upsertPairingRequestMock,
|
||||||
waitForTransportReadyMock,
|
waitForTransportReadyMock,
|
||||||
spawnSignalDaemonMock,
|
spawnSignalDaemonMock,
|
||||||
@ -109,14 +107,23 @@ async function receiveSignalPayloads(params: {
|
|||||||
await flush();
|
await flush();
|
||||||
}
|
}
|
||||||
|
|
||||||
function getDirectSignalEventsFor(sender: string) {
|
function hasQueuedReactionEventFor(sender: string) {
|
||||||
const route = resolveAgentRoute({
|
const route = resolveAgentRoute({
|
||||||
cfg: config as OpenClawConfig,
|
cfg: config as OpenClawConfig,
|
||||||
channel: "signal",
|
channel: "signal",
|
||||||
accountId: "default",
|
accountId: "default",
|
||||||
peer: { kind: "direct", id: normalizeE164(sender) },
|
peer: { kind: "direct", id: normalizeE164(sender) },
|
||||||
});
|
});
|
||||||
return peekSystemEvents(route.sessionKey);
|
return enqueueSystemEventMock.mock.calls.some(([text, options]) => {
|
||||||
|
return (
|
||||||
|
typeof text === "string" &&
|
||||||
|
text.includes("Signal reaction added") &&
|
||||||
|
typeof options === "object" &&
|
||||||
|
options !== null &&
|
||||||
|
"sessionKey" in options &&
|
||||||
|
(options as { sessionKey?: string }).sessionKey === route.sessionKey
|
||||||
|
);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function makeBaseEnvelope(overrides: Record<string, unknown> = {}) {
|
function makeBaseEnvelope(overrides: Record<string, unknown> = {}) {
|
||||||
@ -383,8 +390,7 @@ describe("monitorSignalProvider tool results", () => {
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
const events = getDirectSignalEventsFor("+15550001111");
|
expect(hasQueuedReactionEventFor("+15550001111")).toBe(true);
|
||||||
expect(events.some((text) => text.includes("Signal reaction added"))).toBe(true);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it.each([
|
it.each([
|
||||||
@ -424,8 +430,7 @@ describe("monitorSignalProvider tool results", () => {
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
const events = getDirectSignalEventsFor("+15550001111");
|
expect(hasQueuedReactionEventFor("+15550001111")).toBe(shouldEnqueue);
|
||||||
expect(events.some((text) => text.includes("Signal reaction added"))).toBe(shouldEnqueue);
|
|
||||||
expect(sendMock).not.toHaveBeenCalled();
|
expect(sendMock).not.toHaveBeenCalled();
|
||||||
expect(upsertPairingRequestMock).not.toHaveBeenCalled();
|
expect(upsertPairingRequestMock).not.toHaveBeenCalled();
|
||||||
});
|
});
|
||||||
@ -442,8 +447,7 @@ describe("monitorSignalProvider tool results", () => {
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
const events = getDirectSignalEventsFor("+15550001111");
|
expect(hasQueuedReactionEventFor("+15550001111")).toBe(true);
|
||||||
expect(events.some((text) => text.includes("Signal reaction added"))).toBe(true);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it("processes messages when reaction metadata is present", async () => {
|
it("processes messages when reaction metadata is present", async () => {
|
||||||
|
|||||||
@ -4,6 +4,7 @@ import type { SignalDaemonExitEvent, SignalDaemonHandle } from "./daemon.js";
|
|||||||
|
|
||||||
type SignalToolResultTestMocks = {
|
type SignalToolResultTestMocks = {
|
||||||
waitForTransportReadyMock: MockFn;
|
waitForTransportReadyMock: MockFn;
|
||||||
|
enqueueSystemEventMock: MockFn;
|
||||||
sendMock: MockFn;
|
sendMock: MockFn;
|
||||||
replyMock: MockFn;
|
replyMock: MockFn;
|
||||||
updateLastRouteMock: MockFn;
|
updateLastRouteMock: MockFn;
|
||||||
@ -16,6 +17,7 @@ type SignalToolResultTestMocks = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const waitForTransportReadyMock = vi.hoisted(() => vi.fn()) as unknown as MockFn;
|
const waitForTransportReadyMock = vi.hoisted(() => vi.fn()) as unknown as MockFn;
|
||||||
|
const enqueueSystemEventMock = vi.hoisted(() => vi.fn()) as unknown as MockFn;
|
||||||
const sendMock = vi.hoisted(() => vi.fn()) as unknown as MockFn;
|
const sendMock = vi.hoisted(() => vi.fn()) as unknown as MockFn;
|
||||||
const replyMock = vi.hoisted(() => vi.fn()) as unknown as MockFn;
|
const replyMock = vi.hoisted(() => vi.fn()) as unknown as MockFn;
|
||||||
const updateLastRouteMock = vi.hoisted(() => vi.fn()) as unknown as MockFn;
|
const updateLastRouteMock = vi.hoisted(() => vi.fn()) as unknown as MockFn;
|
||||||
@ -29,6 +31,7 @@ const spawnSignalDaemonMock = vi.hoisted(() => vi.fn()) as unknown as MockFn;
|
|||||||
export function getSignalToolResultTestMocks(): SignalToolResultTestMocks {
|
export function getSignalToolResultTestMocks(): SignalToolResultTestMocks {
|
||||||
return {
|
return {
|
||||||
waitForTransportReadyMock,
|
waitForTransportReadyMock,
|
||||||
|
enqueueSystemEventMock,
|
||||||
sendMock,
|
sendMock,
|
||||||
replyMock,
|
replyMock,
|
||||||
updateLastRouteMock,
|
updateLastRouteMock,
|
||||||
@ -162,6 +165,10 @@ vi.mock("openclaw/plugin-sdk/infra-runtime", async () => {
|
|||||||
return {
|
return {
|
||||||
...actual,
|
...actual,
|
||||||
waitForTransportReady: (...args: unknown[]) => waitForTransportReadyMock(...args),
|
waitForTransportReady: (...args: unknown[]) => waitForTransportReadyMock(...args),
|
||||||
|
enqueueSystemEvent: (...args: Parameters<typeof actual.enqueueSystemEvent>) => {
|
||||||
|
enqueueSystemEventMock(...args);
|
||||||
|
return actual.enqueueSystemEvent(...args);
|
||||||
|
},
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -189,6 +196,7 @@ export function installSignalToolResultTestHooks() {
|
|||||||
readAllowFromStoreMock.mockReset().mockResolvedValue([]);
|
readAllowFromStoreMock.mockReset().mockResolvedValue([]);
|
||||||
upsertPairingRequestMock.mockReset().mockResolvedValue({ code: "PAIRCODE", created: true });
|
upsertPairingRequestMock.mockReset().mockResolvedValue({ code: "PAIRCODE", created: true });
|
||||||
waitForTransportReadyMock.mockReset().mockResolvedValue(undefined);
|
waitForTransportReadyMock.mockReset().mockResolvedValue(undefined);
|
||||||
|
enqueueSystemEventMock.mockReset();
|
||||||
|
|
||||||
resetSystemEventsForTest();
|
resetSystemEventsForTest();
|
||||||
});
|
});
|
||||||
|
|||||||
@ -21,8 +21,10 @@ const { resolveTelegramFetch } = vi.hoisted(() => ({
|
|||||||
resolveTelegramFetch: vi.fn(),
|
resolveTelegramFetch: vi.fn(),
|
||||||
}));
|
}));
|
||||||
|
|
||||||
vi.mock("../../../src/config/config.js", async (importOriginal) => {
|
vi.mock("openclaw/plugin-sdk/config-runtime", async () => {
|
||||||
const actual = await importOriginal<typeof import("../../../src/config/config.js")>();
|
const actual = await vi.importActual<typeof import("openclaw/plugin-sdk/config-runtime")>(
|
||||||
|
"openclaw/plugin-sdk/config-runtime",
|
||||||
|
);
|
||||||
return {
|
return {
|
||||||
...actual,
|
...actual,
|
||||||
loadConfig,
|
loadConfig,
|
||||||
|
|||||||
@ -8,8 +8,10 @@ const readAllowFromStoreMock = vi.fn().mockResolvedValue([]);
|
|||||||
const upsertPairingRequestMock = vi.fn().mockResolvedValue({ code: "PAIRCODE", created: true });
|
const upsertPairingRequestMock = vi.fn().mockResolvedValue({ code: "PAIRCODE", created: true });
|
||||||
const saveMediaBufferSpy = vi.fn();
|
const saveMediaBufferSpy = vi.fn();
|
||||||
|
|
||||||
vi.mock("../../../src/config/config.js", async (importOriginal) => {
|
vi.mock("openclaw/plugin-sdk/config-runtime", async () => {
|
||||||
const actual = await importOriginal<typeof import("../../../src/config/config.js")>();
|
const actual = await vi.importActual<typeof import("openclaw/plugin-sdk/config-runtime")>(
|
||||||
|
"openclaw/plugin-sdk/config-runtime",
|
||||||
|
);
|
||||||
return {
|
return {
|
||||||
...actual,
|
...actual,
|
||||||
loadConfig: vi.fn().mockReturnValue({
|
loadConfig: vi.fn().mockReturnValue({
|
||||||
@ -37,8 +39,10 @@ vi.mock("../../../src/pairing/pairing-store.js", () => {
|
|||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
vi.mock("../../../src/media/store.js", async (importOriginal) => {
|
vi.mock("openclaw/plugin-sdk/media-runtime", async () => {
|
||||||
const actual = await importOriginal<typeof import("../../../src/media/store.js")>();
|
const actual = await vi.importActual<typeof import("openclaw/plugin-sdk/media-runtime")>(
|
||||||
|
"openclaw/plugin-sdk/media-runtime",
|
||||||
|
);
|
||||||
return {
|
return {
|
||||||
...actual,
|
...actual,
|
||||||
saveMediaBuffer: vi.fn(async (...args: Parameters<typeof actual.saveMediaBuffer>) => {
|
saveMediaBuffer: vi.fn(async (...args: Parameters<typeof actual.saveMediaBuffer>) => {
|
||||||
|
|||||||
@ -19,25 +19,30 @@ function resolveTestAuthDir() {
|
|||||||
|
|
||||||
const authDir = resolveTestAuthDir();
|
const authDir = resolveTestAuthDir();
|
||||||
|
|
||||||
vi.mock("../../../src/config/config.js", () => ({
|
vi.mock("openclaw/plugin-sdk/config-runtime", async () => {
|
||||||
loadConfig: () =>
|
const actual = await vi.importActual<typeof import("openclaw/plugin-sdk/config-runtime")>(
|
||||||
({
|
"openclaw/plugin-sdk/config-runtime",
|
||||||
channels: {
|
);
|
||||||
whatsapp: {
|
return {
|
||||||
accounts: {
|
...actual,
|
||||||
default: { enabled: true, authDir: resolveTestAuthDir() },
|
loadConfig: () =>
|
||||||
|
({
|
||||||
|
channels: {
|
||||||
|
whatsapp: {
|
||||||
|
accounts: {
|
||||||
|
default: { enabled: true, authDir: resolveTestAuthDir() },
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
}) as never,
|
||||||
}) as never,
|
};
|
||||||
}));
|
});
|
||||||
|
|
||||||
vi.mock("./session.js", () => {
|
vi.mock("./session.js", () => {
|
||||||
const authDir = resolveTestAuthDir();
|
const authDir = resolveTestAuthDir();
|
||||||
const sockA = { ws: { close: vi.fn() } };
|
const sockA = { ws: { close: vi.fn() } };
|
||||||
const sockB = { ws: { close: vi.fn() } };
|
const sockB = { ws: { close: vi.fn() } };
|
||||||
let call = 0;
|
const createWaSocket = vi.fn(async () => (createWaSocket.mock.calls.length <= 1 ? sockA : sockB));
|
||||||
const createWaSocket = vi.fn(async () => (call++ === 0 ? sockA : sockB));
|
|
||||||
const waitForWaConnection = vi.fn();
|
const waitForWaConnection = vi.fn();
|
||||||
const formatError = vi.fn((err: unknown) => `formatted:${String(err)}`);
|
const formatError = vi.fn((err: unknown) => `formatted:${String(err)}`);
|
||||||
const getStatusCode = vi.fn(
|
const getStatusCode = vi.fn(
|
||||||
@ -78,6 +83,10 @@ describe("loginWeb coverage", () => {
|
|||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
vi.useFakeTimers();
|
vi.useFakeTimers();
|
||||||
vi.clearAllMocks();
|
vi.clearAllMocks();
|
||||||
|
createWaSocketMock.mockClear();
|
||||||
|
waitForWaConnectionMock.mockReset().mockResolvedValue(undefined);
|
||||||
|
waitForCredsSaveQueueWithTimeoutMock.mockReset().mockResolvedValue(undefined);
|
||||||
|
formatErrorMock.mockReset().mockImplementation((err: unknown) => `formatted:${String(err)}`);
|
||||||
rmMock.mockClear();
|
rmMock.mockClear();
|
||||||
});
|
});
|
||||||
afterEach(() => {
|
afterEach(() => {
|
||||||
|
|||||||
16
package.json
16
package.json
@ -121,6 +121,10 @@
|
|||||||
"types": "./dist/plugin-sdk/infra-runtime.d.ts",
|
"types": "./dist/plugin-sdk/infra-runtime.d.ts",
|
||||||
"default": "./dist/plugin-sdk/infra-runtime.js"
|
"default": "./dist/plugin-sdk/infra-runtime.js"
|
||||||
},
|
},
|
||||||
|
"./plugin-sdk/ssrf-runtime": {
|
||||||
|
"types": "./dist/plugin-sdk/ssrf-runtime.d.ts",
|
||||||
|
"default": "./dist/plugin-sdk/ssrf-runtime.js"
|
||||||
|
},
|
||||||
"./plugin-sdk/media-runtime": {
|
"./plugin-sdk/media-runtime": {
|
||||||
"types": "./dist/plugin-sdk/media-runtime.d.ts",
|
"types": "./dist/plugin-sdk/media-runtime.d.ts",
|
||||||
"default": "./dist/plugin-sdk/media-runtime.js"
|
"default": "./dist/plugin-sdk/media-runtime.js"
|
||||||
@ -133,6 +137,18 @@
|
|||||||
"types": "./dist/plugin-sdk/conversation-runtime.d.ts",
|
"types": "./dist/plugin-sdk/conversation-runtime.d.ts",
|
||||||
"default": "./dist/plugin-sdk/conversation-runtime.js"
|
"default": "./dist/plugin-sdk/conversation-runtime.js"
|
||||||
},
|
},
|
||||||
|
"./plugin-sdk/matrix-runtime-heavy": {
|
||||||
|
"types": "./dist/plugin-sdk/matrix-runtime-heavy.d.ts",
|
||||||
|
"default": "./dist/plugin-sdk/matrix-runtime-heavy.js"
|
||||||
|
},
|
||||||
|
"./plugin-sdk/matrix-runtime-shared": {
|
||||||
|
"types": "./dist/plugin-sdk/matrix-runtime-shared.d.ts",
|
||||||
|
"default": "./dist/plugin-sdk/matrix-runtime-shared.js"
|
||||||
|
},
|
||||||
|
"./plugin-sdk/thread-bindings-runtime": {
|
||||||
|
"types": "./dist/plugin-sdk/thread-bindings-runtime.d.ts",
|
||||||
|
"default": "./dist/plugin-sdk/thread-bindings-runtime.js"
|
||||||
|
},
|
||||||
"./plugin-sdk/text-runtime": {
|
"./plugin-sdk/text-runtime": {
|
||||||
"types": "./dist/plugin-sdk/text-runtime.d.ts",
|
"types": "./dist/plugin-sdk/text-runtime.d.ts",
|
||||||
"default": "./dist/plugin-sdk/text-runtime.js"
|
"default": "./dist/plugin-sdk/text-runtime.js"
|
||||||
|
|||||||
@ -20,9 +20,13 @@
|
|||||||
"channel-runtime",
|
"channel-runtime",
|
||||||
"interactive-runtime",
|
"interactive-runtime",
|
||||||
"infra-runtime",
|
"infra-runtime",
|
||||||
|
"ssrf-runtime",
|
||||||
"media-runtime",
|
"media-runtime",
|
||||||
"media-understanding-runtime",
|
"media-understanding-runtime",
|
||||||
"conversation-runtime",
|
"conversation-runtime",
|
||||||
|
"matrix-runtime-heavy",
|
||||||
|
"matrix-runtime-shared",
|
||||||
|
"thread-bindings-runtime",
|
||||||
"text-runtime",
|
"text-runtime",
|
||||||
"agent-runtime",
|
"agent-runtime",
|
||||||
"speech-runtime",
|
"speech-runtime",
|
||||||
|
|||||||
@ -13,14 +13,49 @@ type RegistrablePlugin = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export const bundledWebSearchPluginRegistrations: ReadonlyArray<{
|
export const bundledWebSearchPluginRegistrations: ReadonlyArray<{
|
||||||
plugin: RegistrablePlugin;
|
readonly plugin: RegistrablePlugin;
|
||||||
credentialValue: unknown;
|
credentialValue: unknown;
|
||||||
}> = [
|
}> = [
|
||||||
{ plugin: bravePlugin, credentialValue: "BSA-test" },
|
{
|
||||||
{ plugin: firecrawlPlugin, credentialValue: "fc-test" },
|
get plugin() {
|
||||||
{ plugin: googlePlugin, credentialValue: "AIza-test" },
|
return bravePlugin;
|
||||||
{ plugin: moonshotPlugin, credentialValue: "sk-test" },
|
},
|
||||||
{ plugin: perplexityPlugin, credentialValue: "pplx-test" },
|
credentialValue: "BSA-test",
|
||||||
{ plugin: tavilyPlugin, credentialValue: "tvly-test" },
|
},
|
||||||
{ plugin: xaiPlugin, credentialValue: "xai-test" },
|
{
|
||||||
|
get plugin() {
|
||||||
|
return firecrawlPlugin;
|
||||||
|
},
|
||||||
|
credentialValue: "fc-test",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
get plugin() {
|
||||||
|
return googlePlugin;
|
||||||
|
},
|
||||||
|
credentialValue: "AIza-test",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
get plugin() {
|
||||||
|
return moonshotPlugin;
|
||||||
|
},
|
||||||
|
credentialValue: "sk-test",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
get plugin() {
|
||||||
|
return perplexityPlugin;
|
||||||
|
},
|
||||||
|
credentialValue: "pplx-test",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
get plugin() {
|
||||||
|
return tavilyPlugin;
|
||||||
|
},
|
||||||
|
credentialValue: "tvly-test",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
get plugin() {
|
||||||
|
return xaiPlugin;
|
||||||
|
},
|
||||||
|
credentialValue: "xai-test",
|
||||||
|
},
|
||||||
];
|
];
|
||||||
|
|||||||
@ -3,6 +3,19 @@ import { withEnv } from "../test-utils/env.js";
|
|||||||
import { decodeCapturedOutputBuffer, parseWindowsCodePage, sanitizeEnv } from "./invoke.js";
|
import { decodeCapturedOutputBuffer, parseWindowsCodePage, sanitizeEnv } from "./invoke.js";
|
||||||
import { buildNodeInvokeResultParams } from "./runner.js";
|
import { buildNodeInvokeResultParams } from "./runner.js";
|
||||||
|
|
||||||
|
function getEnvValueCaseInsensitive(
|
||||||
|
env: Record<string, string>,
|
||||||
|
expectedKey: string,
|
||||||
|
): string | undefined {
|
||||||
|
const direct = env[expectedKey];
|
||||||
|
if (direct !== undefined) {
|
||||||
|
return direct;
|
||||||
|
}
|
||||||
|
const upper = expectedKey.toUpperCase();
|
||||||
|
const actualKey = Object.keys(env).find((key) => key.toUpperCase() === upper);
|
||||||
|
return actualKey ? env[actualKey] : undefined;
|
||||||
|
}
|
||||||
|
|
||||||
describe("node-host sanitizeEnv", () => {
|
describe("node-host sanitizeEnv", () => {
|
||||||
it("ignores PATH overrides", () => {
|
it("ignores PATH overrides", () => {
|
||||||
withEnv({ PATH: "/usr/bin" }, () => {
|
withEnv({ PATH: "/usr/bin" }, () => {
|
||||||
@ -55,7 +68,7 @@ describe("node-host sanitizeEnv", () => {
|
|||||||
it("preserves inherited non-portable Windows-style env keys", () => {
|
it("preserves inherited non-portable Windows-style env keys", () => {
|
||||||
withEnv({ "ProgramFiles(x86)": "C:\\Program Files (x86)" }, () => {
|
withEnv({ "ProgramFiles(x86)": "C:\\Program Files (x86)" }, () => {
|
||||||
const env = sanitizeEnv(undefined);
|
const env = sanitizeEnv(undefined);
|
||||||
expect(env["ProgramFiles(x86)"]).toBe("C:\\Program Files (x86)");
|
expect(getEnvValueCaseInsensitive(env, "ProgramFiles(x86)")).toBe("C:\\Program Files (x86)");
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
7
src/plugin-sdk/matrix-runtime-heavy.ts
Normal file
7
src/plugin-sdk/matrix-runtime-heavy.ts
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
// Matrix runtime helpers that are needed internally by the bundled extension
|
||||||
|
// but are too heavy for the light external runtime-api surface.
|
||||||
|
|
||||||
|
export { ensureConfiguredAcpBindingReady } from "../acp/persistent-bindings.lifecycle.js";
|
||||||
|
export { resolveConfiguredAcpBindingRecord } from "../acp/persistent-bindings.resolve.js";
|
||||||
|
export { maybeCreateMatrixMigrationSnapshot } from "../infra/matrix-migration-snapshot.js";
|
||||||
|
export { dispatchReplyFromConfigWithSettledDispatcher } from "./inbound-reply-dispatch.js";
|
||||||
11
src/plugin-sdk/matrix-runtime-shared.ts
Normal file
11
src/plugin-sdk/matrix-runtime-shared.ts
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
// Narrow shared Matrix runtime exports for light runtime-api consumers.
|
||||||
|
|
||||||
|
export type {
|
||||||
|
ChannelDirectoryEntry,
|
||||||
|
ChannelMessageActionContext,
|
||||||
|
} from "../channels/plugins/types.js";
|
||||||
|
export type { OpenClawConfig } from "../config/config.js";
|
||||||
|
export { formatZonedTimestamp } from "../infra/format-time/format-datetime.js";
|
||||||
|
export type { PluginRuntime, RuntimeLogger } from "../plugins/runtime/types.js";
|
||||||
|
export type { RuntimeEnv } from "../runtime.js";
|
||||||
|
export type { WizardPrompter } from "../wizard/prompts.js";
|
||||||
@ -27,8 +27,6 @@ export {
|
|||||||
patchAllowlistUsersInConfigEntries,
|
patchAllowlistUsersInConfigEntries,
|
||||||
summarizeMapping,
|
summarizeMapping,
|
||||||
} from "../channels/allowlists/resolve-utils.js";
|
} from "../channels/allowlists/resolve-utils.js";
|
||||||
export { ensureConfiguredAcpBindingReady } from "../acp/persistent-bindings.lifecycle.js";
|
|
||||||
export { resolveConfiguredAcpBindingRecord } from "../acp/persistent-bindings.resolve.js";
|
|
||||||
export { resolveControlCommandGate } from "../channels/command-gating.js";
|
export { resolveControlCommandGate } from "../channels/command-gating.js";
|
||||||
export type { NormalizedLocation } from "../channels/location.js";
|
export type { NormalizedLocation } from "../channels/location.js";
|
||||||
export { formatLocationText, toLocationContext } from "../channels/location.js";
|
export { formatLocationText, toLocationContext } from "../channels/location.js";
|
||||||
@ -112,7 +110,6 @@ export { ToolPolicySchema } from "../config/zod-schema.agent-runtime.js";
|
|||||||
export { MarkdownConfigSchema } from "../config/zod-schema.core.js";
|
export { MarkdownConfigSchema } from "../config/zod-schema.core.js";
|
||||||
export { formatZonedTimestamp } from "../infra/format-time/format-datetime.js";
|
export { formatZonedTimestamp } from "../infra/format-time/format-datetime.js";
|
||||||
export { fetchWithSsrFGuard } from "../infra/net/fetch-guard.js";
|
export { fetchWithSsrFGuard } from "../infra/net/fetch-guard.js";
|
||||||
export { maybeCreateMatrixMigrationSnapshot } from "../infra/matrix-migration-snapshot.js";
|
|
||||||
export {
|
export {
|
||||||
getSessionBindingService,
|
getSessionBindingService,
|
||||||
registerSessionBindingAdapter,
|
registerSessionBindingAdapter,
|
||||||
@ -150,7 +147,6 @@ export { readJsonFileWithFallback, writeJsonFileAtomically } from "./json-store.
|
|||||||
export { formatResolvedUnresolvedNote } from "./resolution-notes.js";
|
export { formatResolvedUnresolvedNote } from "./resolution-notes.js";
|
||||||
export { runPluginCommandWithTimeout } from "./run-command.js";
|
export { runPluginCommandWithTimeout } from "./run-command.js";
|
||||||
export { createLoggerBackedRuntime, resolveRuntimeEnv } from "./runtime.js";
|
export { createLoggerBackedRuntime, resolveRuntimeEnv } from "./runtime.js";
|
||||||
export { dispatchReplyFromConfigWithSettledDispatcher } from "./inbound-reply-dispatch.js";
|
|
||||||
export {
|
export {
|
||||||
buildProbeChannelStatusSummary,
|
buildProbeChannelStatusSummary,
|
||||||
collectStatusIssuesFromLastError,
|
collectStatusIssuesFromLastError,
|
||||||
|
|||||||
@ -38,7 +38,7 @@ const RUNTIME_API_EXPORT_GUARDS: Record<string, readonly string[]> = {
|
|||||||
"extensions/matrix/runtime-api.ts": [
|
"extensions/matrix/runtime-api.ts": [
|
||||||
'export * from "./src/auth-precedence.js";',
|
'export * from "./src/auth-precedence.js";',
|
||||||
'export * from "./helper-api.js";',
|
'export * from "./helper-api.js";',
|
||||||
'export { assertHttpUrlTargetsPrivateNetwork, closeDispatcher, createPinnedDispatcher, resolvePinnedHostnameWithPolicy, ssrfPolicyFromAllowPrivateNetwork, type LookupFn, type SsrFPolicy } from "openclaw/plugin-sdk/infra-runtime";',
|
'export { assertHttpUrlTargetsPrivateNetwork, closeDispatcher, createPinnedDispatcher, resolvePinnedHostnameWithPolicy, ssrfPolicyFromAllowPrivateNetwork, type LookupFn, type SsrFPolicy } from "openclaw/plugin-sdk/ssrf-runtime";',
|
||||||
'export { setMatrixThreadBindingIdleTimeoutBySessionKey, setMatrixThreadBindingMaxAgeBySessionKey } from "./thread-bindings-runtime.js";',
|
'export { setMatrixThreadBindingIdleTimeoutBySessionKey, setMatrixThreadBindingMaxAgeBySessionKey } from "./thread-bindings-runtime.js";',
|
||||||
'export { writeJsonFileAtomically } from "../../src/plugin-sdk/json-store.js";',
|
'export { writeJsonFileAtomically } from "../../src/plugin-sdk/json-store.js";',
|
||||||
'export type { ChannelDirectoryEntry, ChannelMessageActionContext, OpenClawConfig, PluginRuntime, RuntimeLogger, RuntimeEnv, WizardPrompter } from "../../src/plugin-sdk/matrix.js";',
|
'export type { ChannelDirectoryEntry, ChannelMessageActionContext, OpenClawConfig, PluginRuntime, RuntimeLogger, RuntimeEnv, WizardPrompter } from "../../src/plugin-sdk/matrix.js";',
|
||||||
|
|||||||
14
src/plugin-sdk/ssrf-runtime.ts
Normal file
14
src/plugin-sdk/ssrf-runtime.ts
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
// Narrow SSRF helpers for extensions that need pinned-dispatcher and policy
|
||||||
|
// utilities without loading the full infra-runtime surface.
|
||||||
|
|
||||||
|
export {
|
||||||
|
closeDispatcher,
|
||||||
|
createPinnedDispatcher,
|
||||||
|
resolvePinnedHostnameWithPolicy,
|
||||||
|
type LookupFn,
|
||||||
|
type SsrFPolicy,
|
||||||
|
} from "../infra/net/ssrf.js";
|
||||||
|
export {
|
||||||
|
assertHttpUrlTargetsPrivateNetwork,
|
||||||
|
ssrfPolicyFromAllowPrivateNetwork,
|
||||||
|
} from "./ssrf-policy.js";
|
||||||
@ -36,6 +36,7 @@ import type {
|
|||||||
import * as directoryRuntimeSdk from "openclaw/plugin-sdk/directory-runtime";
|
import * as directoryRuntimeSdk from "openclaw/plugin-sdk/directory-runtime";
|
||||||
import * as infraRuntimeSdk from "openclaw/plugin-sdk/infra-runtime";
|
import * as infraRuntimeSdk from "openclaw/plugin-sdk/infra-runtime";
|
||||||
import * as lazyRuntimeSdk from "openclaw/plugin-sdk/lazy-runtime";
|
import * as lazyRuntimeSdk from "openclaw/plugin-sdk/lazy-runtime";
|
||||||
|
import * as matrixRuntimeSharedSdk from "openclaw/plugin-sdk/matrix-runtime-shared";
|
||||||
import * as mediaRuntimeSdk from "openclaw/plugin-sdk/media-runtime";
|
import * as mediaRuntimeSdk from "openclaw/plugin-sdk/media-runtime";
|
||||||
import * as ollamaSetupSdk from "openclaw/plugin-sdk/ollama-setup";
|
import * as ollamaSetupSdk from "openclaw/plugin-sdk/ollama-setup";
|
||||||
import * as providerAuthSdk from "openclaw/plugin-sdk/provider-auth";
|
import * as providerAuthSdk from "openclaw/plugin-sdk/provider-auth";
|
||||||
@ -50,7 +51,9 @@ import * as sandboxSdk from "openclaw/plugin-sdk/sandbox";
|
|||||||
import * as secretInputSdk from "openclaw/plugin-sdk/secret-input";
|
import * as secretInputSdk from "openclaw/plugin-sdk/secret-input";
|
||||||
import * as selfHostedProviderSetupSdk from "openclaw/plugin-sdk/self-hosted-provider-setup";
|
import * as selfHostedProviderSetupSdk from "openclaw/plugin-sdk/self-hosted-provider-setup";
|
||||||
import * as setupSdk from "openclaw/plugin-sdk/setup";
|
import * as setupSdk from "openclaw/plugin-sdk/setup";
|
||||||
|
import * as ssrfRuntimeSdk from "openclaw/plugin-sdk/ssrf-runtime";
|
||||||
import * as testingSdk from "openclaw/plugin-sdk/testing";
|
import * as testingSdk from "openclaw/plugin-sdk/testing";
|
||||||
|
import * as threadBindingsRuntimeSdk from "openclaw/plugin-sdk/thread-bindings-runtime";
|
||||||
import * as webhookIngressSdk from "openclaw/plugin-sdk/webhook-ingress";
|
import * as webhookIngressSdk from "openclaw/plugin-sdk/webhook-ingress";
|
||||||
import { describe, expect, expectTypeOf, it } from "vitest";
|
import { describe, expect, expectTypeOf, it } from "vitest";
|
||||||
import type { ChannelMessageActionContext } from "../channels/plugins/types.js";
|
import type { ChannelMessageActionContext } from "../channels/plugins/types.js";
|
||||||
@ -523,6 +526,22 @@ describe("plugin-sdk subpath exports", () => {
|
|||||||
expect(typeof conversationRuntimeSdk.createTopLevelChannelReplyToModeResolver).toBe("function");
|
expect(typeof conversationRuntimeSdk.createTopLevelChannelReplyToModeResolver).toBe("function");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("exports narrow binding lifecycle helpers from the dedicated subpath", () => {
|
||||||
|
expect(typeof threadBindingsRuntimeSdk.resolveThreadBindingLifecycle).toBe("function");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("exports narrow matrix runtime helpers from the dedicated subpath", () => {
|
||||||
|
expect(typeof matrixRuntimeSharedSdk.formatZonedTimestamp).toBe("function");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("exports narrow ssrf helpers from the dedicated subpath", () => {
|
||||||
|
expect(typeof ssrfRuntimeSdk.closeDispatcher).toBe("function");
|
||||||
|
expect(typeof ssrfRuntimeSdk.createPinnedDispatcher).toBe("function");
|
||||||
|
expect(typeof ssrfRuntimeSdk.resolvePinnedHostnameWithPolicy).toBe("function");
|
||||||
|
expect(typeof ssrfRuntimeSdk.assertHttpUrlTargetsPrivateNetwork).toBe("function");
|
||||||
|
expect(typeof ssrfRuntimeSdk.ssrfPolicyFromAllowPrivateNetwork).toBe("function");
|
||||||
|
});
|
||||||
|
|
||||||
it("exports provider setup helpers from the dedicated subpath", () => {
|
it("exports provider setup helpers from the dedicated subpath", () => {
|
||||||
expect(typeof providerSetupSdk.buildVllmProvider).toBe("function");
|
expect(typeof providerSetupSdk.buildVllmProvider).toBe("function");
|
||||||
expect(typeof providerSetupSdk.discoverOpenAICompatibleSelfHostedProvider).toBe("function");
|
expect(typeof providerSetupSdk.discoverOpenAICompatibleSelfHostedProvider).toBe("function");
|
||||||
|
|||||||
9
src/plugin-sdk/thread-bindings-runtime.ts
Normal file
9
src/plugin-sdk/thread-bindings-runtime.ts
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
// Narrow thread-binding lifecycle helpers for extensions that need binding
|
||||||
|
// expiry and session-binding record types without loading the full
|
||||||
|
// conversation-runtime surface.
|
||||||
|
|
||||||
|
export { resolveThreadBindingLifecycle } from "../channels/thread-bindings-policy.js";
|
||||||
|
export type {
|
||||||
|
BindingTargetKind,
|
||||||
|
SessionBindingRecord,
|
||||||
|
} from "../infra/outbound/session-binding-service.js";
|
||||||
@ -4,23 +4,58 @@ import type { PluginLoadOptions } from "./loader.js";
|
|||||||
import { loadPluginManifestRegistry } from "./manifest-registry.js";
|
import { loadPluginManifestRegistry } from "./manifest-registry.js";
|
||||||
import type { PluginWebSearchProviderEntry } from "./types.js";
|
import type { PluginWebSearchProviderEntry } from "./types.js";
|
||||||
|
|
||||||
export const BUNDLED_WEB_SEARCH_PLUGIN_IDS = bundledWebSearchPluginRegistrations
|
|
||||||
.map((entry) => entry.plugin.id)
|
|
||||||
.toSorted((left, right) => left.localeCompare(right));
|
|
||||||
|
|
||||||
const bundledWebSearchPluginIdSet = new Set<string>(BUNDLED_WEB_SEARCH_PLUGIN_IDS);
|
|
||||||
|
|
||||||
type BundledWebSearchProviderEntry = PluginWebSearchProviderEntry & { pluginId: string };
|
type BundledWebSearchProviderEntry = PluginWebSearchProviderEntry & { pluginId: string };
|
||||||
|
type BundledWebSearchPluginRegistration = (typeof bundledWebSearchPluginRegistrations)[number];
|
||||||
|
|
||||||
let bundledWebSearchProvidersCache: BundledWebSearchProviderEntry[] | null = null;
|
let bundledWebSearchProvidersCache: BundledWebSearchProviderEntry[] | null = null;
|
||||||
|
let bundledWebSearchPluginIdsCache: string[] | null = null;
|
||||||
|
|
||||||
|
function resolveBundledWebSearchPlugin(
|
||||||
|
entry: BundledWebSearchPluginRegistration,
|
||||||
|
): BundledWebSearchPluginRegistration["plugin"] | null {
|
||||||
|
try {
|
||||||
|
return entry.plugin;
|
||||||
|
} catch {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function listBundledWebSearchPluginRegistrations() {
|
||||||
|
return bundledWebSearchPluginRegistrations
|
||||||
|
.map((entry) => {
|
||||||
|
const plugin = resolveBundledWebSearchPlugin(entry);
|
||||||
|
return plugin ? { ...entry, plugin } : null;
|
||||||
|
})
|
||||||
|
.filter(
|
||||||
|
(
|
||||||
|
entry,
|
||||||
|
): entry is BundledWebSearchPluginRegistration & {
|
||||||
|
plugin: BundledWebSearchPluginRegistration["plugin"];
|
||||||
|
} => Boolean(entry),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function loadBundledWebSearchPluginIds(): string[] {
|
||||||
|
if (!bundledWebSearchPluginIdsCache) {
|
||||||
|
bundledWebSearchPluginIdsCache = listBundledWebSearchPluginRegistrations()
|
||||||
|
.map(({ plugin }) => plugin.id)
|
||||||
|
.toSorted((left, right) => left.localeCompare(right));
|
||||||
|
}
|
||||||
|
return bundledWebSearchPluginIdsCache;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function listBundledWebSearchPluginIds(): string[] {
|
||||||
|
return loadBundledWebSearchPluginIds();
|
||||||
|
}
|
||||||
|
|
||||||
function loadBundledWebSearchProviders(): BundledWebSearchProviderEntry[] {
|
function loadBundledWebSearchProviders(): BundledWebSearchProviderEntry[] {
|
||||||
if (!bundledWebSearchProvidersCache) {
|
if (!bundledWebSearchProvidersCache) {
|
||||||
bundledWebSearchProvidersCache = bundledWebSearchPluginRegistrations.flatMap(({ plugin }) =>
|
bundledWebSearchProvidersCache = listBundledWebSearchPluginRegistrations().flatMap(
|
||||||
capturePluginRegistration(plugin).webSearchProviders.map((provider) => ({
|
({ plugin }) =>
|
||||||
...provider,
|
capturePluginRegistration(plugin).webSearchProviders.map((provider) => ({
|
||||||
pluginId: plugin.id,
|
...provider,
|
||||||
})),
|
pluginId: plugin.id,
|
||||||
|
})),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
return bundledWebSearchProvidersCache;
|
return bundledWebSearchProvidersCache;
|
||||||
@ -36,6 +71,7 @@ export function resolveBundledWebSearchPluginIds(params: {
|
|||||||
workspaceDir: params.workspaceDir,
|
workspaceDir: params.workspaceDir,
|
||||||
env: params.env,
|
env: params.env,
|
||||||
});
|
});
|
||||||
|
const bundledWebSearchPluginIdSet = new Set<string>(loadBundledWebSearchPluginIds());
|
||||||
return registry.plugins
|
return registry.plugins
|
||||||
.filter((plugin) => plugin.origin === "bundled" && bundledWebSearchPluginIdSet.has(plugin.id))
|
.filter((plugin) => plugin.origin === "bundled" && bundledWebSearchPluginIdSet.has(plugin.id))
|
||||||
.map((plugin) => plugin.id)
|
.map((plugin) => plugin.id)
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
import type { OpenClawConfig } from "../config/config.js";
|
import type { OpenClawConfig } from "../config/config.js";
|
||||||
import { resolveSecretInputRef } from "../config/types.secrets.js";
|
import { resolveSecretInputRef } from "../config/types.secrets.js";
|
||||||
import {
|
import {
|
||||||
BUNDLED_WEB_SEARCH_PLUGIN_IDS,
|
listBundledWebSearchPluginIds,
|
||||||
resolveBundledWebSearchPluginId,
|
resolveBundledWebSearchPluginId,
|
||||||
} from "../plugins/bundled-web-search.js";
|
} from "../plugins/bundled-web-search.js";
|
||||||
import type {
|
import type {
|
||||||
@ -82,7 +82,7 @@ function hasCustomWebSearchPluginRisk(config: OpenClawConfig): boolean {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
const bundledPluginIds = new Set<string>(BUNDLED_WEB_SEARCH_PLUGIN_IDS);
|
const bundledPluginIds = new Set<string>(listBundledWebSearchPluginIds());
|
||||||
const hasNonBundledPluginId = (pluginId: string) => !bundledPluginIds.has(pluginId.trim());
|
const hasNonBundledPluginId = (pluginId: string) => !bundledPluginIds.has(pluginId.trim());
|
||||||
if (Array.isArray(plugins.allow) && plugins.allow.some(hasNonBundledPluginId)) {
|
if (Array.isArray(plugins.allow) && plugins.allow.some(hasNonBundledPluginId)) {
|
||||||
return true;
|
return true;
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user