diff --git a/extensions/telegram/src/message-dispatch-dedupe.test.ts b/extensions/telegram/src/message-dispatch-dedupe.test.ts index 69d6b277ed4f..8f5335b99f4d 100644 --- a/extensions/telegram/src/message-dispatch-dedupe.test.ts +++ b/extensions/telegram/src/message-dispatch-dedupe.test.ts @@ -115,7 +115,7 @@ describe("Telegram message dispatch replay guard", () => { await expect(reader.warmup("default")).resolves.toBe(keys.length); }); - it("falls back to same-process replay protection when plugin-state cannot open", async () => { + it("falls back to same-process replay protection when plugin-state is unavailable", async () => { setTelegramMessageDispatchDedupeStoreForTest(undefined); const errors: unknown[] = []; const storePath = createStorePath(); @@ -142,7 +142,7 @@ describe("Telegram message dispatch replay guard", () => { }), ).resolves.toEqual({ kind: "duplicate" }); await expect(guard.hasRecent(first.key, { namespace: "default" })).resolves.toBe(true); - expect(errors.length).toBeGreaterThan(0); + expect(errors).toEqual([]); }); it("keeps same-process replay protection when plugin-state commit fails", async () => { diff --git a/extensions/telegram/src/message-dispatch-dedupe.ts b/extensions/telegram/src/message-dispatch-dedupe.ts index 529942460fb8..dacd3f50129d 100644 --- a/extensions/telegram/src/message-dispatch-dedupe.ts +++ b/extensions/telegram/src/message-dispatch-dedupe.ts @@ -11,7 +11,7 @@ import type { PluginStateSyncKeyedStore, } from "openclaw/plugin-sdk/plugin-state-runtime"; import { normalizeStringEntries, uniqueStrings } from "openclaw/plugin-sdk/string-coerce-runtime"; -import { getTelegramRuntime } from "./runtime.js"; +import { getOptionalTelegramRuntime } from "./runtime.js"; const TELEGRAM_MESSAGE_DISPATCH_TTL_MS = 7 * 24 * 60 * 60 * 1000; export const TELEGRAM_MESSAGE_DISPATCH_DEDUPE_NAMESPACE = "telegram.message-dispatch-dedupe"; @@ -53,14 +53,14 @@ export type TelegramMessageDispatchClaim = | { kind: "duplicate" } | { kind: "invalid" }; -function openDispatchDedupeStore(): TelegramMessageDispatchDedupeStore { - return ( - dispatchDedupeStoreForTest ?? - getTelegramRuntime().state.openKeyedStore({ +function openDispatchDedupeStore(): TelegramMessageDispatchDedupeStore | undefined { + if (dispatchDedupeStoreForTest) { + return dispatchDedupeStoreForTest; + } + return getOptionalTelegramRuntime()?.state.openKeyedStore({ namespace: TELEGRAM_MESSAGE_DISPATCH_DEDUPE_NAMESPACE, maxEntries: TELEGRAM_MESSAGE_DISPATCH_DEDUPE_MAX_ENTRIES, - }) - ); + }); } function resolveDispatchScopeKey(storePath: string): string { diff --git a/packages/sdk/src/package.e2e.test.ts b/packages/sdk/src/package.e2e.test.ts index 4ab9a6c74d14..d1939465a657 100644 --- a/packages/sdk/src/package.e2e.test.ts +++ b/packages/sdk/src/package.e2e.test.ts @@ -108,6 +108,24 @@ function tarballFileName(manifest: PackageManifest): string { return `${manifest.name.replace(/^@/, "").replace("/", "-")}-${manifest.version}.tgz`; } +async function createPackStagingRoot(packageRoot: string, destinationRoot: string): Promise { + const manifest = await readPackageManifest(packageRoot); + const packageSlug = manifest.name.replace(/^@/, "").replace("/", "-"); + const stagingRoot = path.join(destinationRoot, `pack-${packageSlug}`); + await fs.mkdir(stagingRoot, { recursive: true }); + await fs.writeFile(path.join(stagingRoot, "package.json"), JSON.stringify(manifest, null, 2)); + const files = Array.isArray(manifest.files) ? manifest.files : []; + for (const entry of files) { + if (typeof entry !== "string") { + continue; + } + await fs.cp(path.join(packageRoot, entry), path.join(stagingRoot, entry), { + recursive: true, + }); + } + return stagingRoot; +} + function closeServer(server: Server): Promise { return new Promise((resolve, reject) => { server.close((error) => (error ? reject(error) : resolve())); @@ -204,8 +222,9 @@ describe("OpenClaw SDK package e2e", () => { }); } for (const packageRoot of packageRoots) { - await runCommand("pnpm", ["pack", "--pack-destination", tempDir], { - cwd: packageRoot, + const stagingRoot = await createPackStagingRoot(packageRoot, tempDir); + await runCommand("npm", ["pack", "--ignore-scripts", "--pack-destination", tempDir], { + cwd: stagingRoot, }); } diff --git a/src/agents/cli-runner.bundle-mcp.e2e.test.ts b/src/agents/cli-runner.bundle-mcp.e2e.test.ts index 1403e7b95458..c605a363304b 100644 --- a/src/agents/cli-runner.bundle-mcp.e2e.test.ts +++ b/src/agents/cli-runner.bundle-mcp.e2e.test.ts @@ -81,7 +81,14 @@ async function restoreCliRunnerPrepareDeps() { } beforeEach(async () => { + const { setCliRunnerTestDeps } = await import("./cli-runner.js"); const { setCliRunnerPrepareTestDeps } = await import("./cli-runner/prepare.js"); + setCliRunnerTestDeps({ + // Bundle MCP wiring is the behavior under test; transcript flush has + // dedicated coverage and the fake Claude binary does not write real + // Claude transcript files. + claudeCliSessionTranscriptHasContent: vi.fn(async () => true), + }); setCliRunnerPrepareTestDeps({ // This test validates downstream bundle MCP config injection. The generic // OpenClaw loopback tool inventory is covered by prepare-level tests and is @@ -91,7 +98,9 @@ beforeEach(async () => { }); afterEach(async () => { + const { restoreCliRunnerTestDeps } = await import("./cli-runner.js"); cliBackendsTesting.resetDepsForTest(); + restoreCliRunnerTestDeps(); await restoreCliRunnerPrepareDeps(); await resetBundleMcpPluginState(); }); diff --git a/src/commands/doctor.warns-state-directory-is-missing.e2e.test.ts b/src/commands/doctor.warns-state-directory-is-missing.e2e.test.ts index 8d46fb18ed57..08a217f18573 100644 --- a/src/commands/doctor.warns-state-directory-is-missing.e2e.test.ts +++ b/src/commands/doctor.warns-state-directory-is-missing.e2e.test.ts @@ -13,13 +13,14 @@ import "./doctor.fast-path-mocks.js"; let doctorCommand: typeof import("./doctor.js").doctorCommand; -const CODEX_PROVIDER_ID = "openai"; +const OPENAI_PROVIDER_ID = "openai"; +const LEGACY_CODEX_PROVIDER_ID = "openai-codex"; const CODEX_PROFILE_ID = "openai:user@example.com"; const CODEX_PROFILE_EMAIL = "user@example.com"; function configCodexOAuthProfile() { return { - provider: CODEX_PROVIDER_ID, + provider: OPENAI_PROVIDER_ID, mode: "oauth", email: CODEX_PROFILE_EMAIL, }; @@ -28,7 +29,7 @@ function configCodexOAuthProfile() { function storedCodexOAuthProfile() { return { type: "oauth", - provider: CODEX_PROVIDER_ID, + provider: OPENAI_PROVIDER_ID, access: "access-token", refresh: "refresh-token", expires: Date.now() + 60_000, @@ -51,7 +52,7 @@ function mockCodexProviderSnapshot(params: { config: { models: { providers: { - [CODEX_PROVIDER_ID]: params.provider, + [LEGACY_CODEX_PROVIDER_ID]: params.provider, }, }, ...(params.withConfigOAuth @@ -194,7 +195,7 @@ describe("doctor command", () => { expect(warned).toBe(true); }); - it("warns when a legacy OpenAI provider override shadows configured Codex OAuth", async () => { + it("warns when a legacy Codex provider override shadows configured Codex OAuth", async () => { mockCodexProviderSnapshot({ provider: { api: "openai-responses", @@ -206,10 +207,10 @@ describe("doctor command", () => { await runDoctorNonInteractive(); - expect(hasCodexOAuthWarning("models.providers.openai")).toBe(true); + expect(hasCodexOAuthWarning("models.providers.openai-codex")).toBe(true); }); - it("warns when a legacy OpenAI provider override shadows stored Codex OAuth", async () => { + it("warns when a legacy Codex provider override shadows stored Codex OAuth", async () => { mockCodexProviderSnapshot({ provider: { api: "openai-responses", @@ -222,7 +223,7 @@ describe("doctor command", () => { await runDoctorNonInteractive(); - expect(hasCodexOAuthWarning("models.providers.openai")).toBe(true); + expect(hasCodexOAuthWarning("models.providers.openai-codex")).toBe(true); }); it("warns when an inline OpenAI model keeps the legacy OpenAI transport", async () => { @@ -275,7 +276,7 @@ describe("doctor command", () => { expect(hasCodexOAuthWarning()).toBe(false); }); - it("does not warn about an OpenAI provider override without Codex OAuth", async () => { + it("does not warn about a legacy Codex provider override without Codex OAuth", async () => { mockCodexProviderSnapshot({ provider: { api: "openai-responses",