fix: align draft/outbound typings and tests

This commit is contained in:
Peter Steinberger 2026-02-22 08:03:05 +00:00
parent 0ae7f962f9
commit 0c1a52307c
12 changed files with 49 additions and 33 deletions

View File

@ -31,7 +31,7 @@ function requireGatewayTool(agentSessionKey?: string) {
function expectConfigMutationCall(params: {
callGatewayTool: {
mock: {
calls: Array<[string, unknown, Record<string, unknown>]>;
calls: Array<readonly unknown[]>;
};
};
action: "config.apply" | "config.patch";

View File

@ -1,4 +1,4 @@
import { createServer } from "node:http";
import { createServer, type IncomingMessage, type ServerResponse } from "node:http";
import type { AddressInfo } from "node:net";
import { afterEach, beforeEach, describe, expect, it } from "vitest";
import {
@ -8,7 +8,7 @@ import {
import { getFreePort } from "./test-port.js";
async function withRelayServer(
handler: Parameters<typeof createServer>[0],
handler: (req: IncomingMessage, res: ServerResponse) => void,
run: (params: { port: number }) => Promise<void>,
) {
const port = await getFreePort();

View File

@ -51,7 +51,7 @@ describe("draft-stream-controls", () => {
it("clearFinalizableDraftMessage skips invalid message ids", async () => {
const deleteMessage = vi.fn(async () => {});
await clearFinalizableDraftMessage({
await clearFinalizableDraftMessage<unknown>({
stopForClear: async () => {},
readMessageId: () => 123,
clearMessageId: () => {},

View File

@ -19,7 +19,10 @@ type ClearFinalizableDraftMessageParams<T> = StopAndClearMessageIdParams<T> & {
warnPrefix: string;
};
type FinalizableDraftLifecycleParams<T> = ClearFinalizableDraftMessageParams<T> & {
type FinalizableDraftLifecycleParams<T> = Omit<
ClearFinalizableDraftMessageParams<T>,
"stopForClear"
> & {
throttleMs: number;
state: FinalizableDraftStreamState;
sendOrEditStreamMessage: (text: string) => Promise<boolean>;

View File

@ -43,10 +43,14 @@ export async function runChannelLogin(
runtime: RuntimeEnv = defaultRuntime,
) {
const { channelInput, plugin } = resolveChannelPluginForMode(opts, "login");
const login = plugin.auth?.login;
if (!login) {
throw new Error(`Channel ${channelInput} does not support login`);
}
// Auth-only flow: do not mutate channel config here.
setVerbose(Boolean(opts.verbose));
const { cfg, accountId } = resolveAccountContext(plugin, opts);
await plugin.auth!.login({
await login({
cfg,
accountId,
runtime,
@ -59,11 +63,15 @@ export async function runChannelLogout(
opts: ChannelAuthOptions,
runtime: RuntimeEnv = defaultRuntime,
) {
const { plugin } = resolveChannelPluginForMode(opts, "logout");
const { channelInput, plugin } = resolveChannelPluginForMode(opts, "logout");
const logoutAccount = plugin.gateway?.logoutAccount;
if (!logoutAccount) {
throw new Error(`Channel ${channelInput} does not support logout`);
}
// Auth-only flow: resolve account + clear session state only.
const { cfg, accountId } = resolveAccountContext(plugin, opts);
const account = plugin.config.resolveAccount(cfg, accountId);
await plugin.gateway!.logoutAccount({
await logoutAccount({
cfg,
accountId,
account,

View File

@ -114,7 +114,9 @@ export function createDiscordDraftStream(params: {
streamMessageId = undefined;
},
isValidMessageId: (value): value is string => typeof value === "string",
deleteMessage: (messageId) => rest.delete(Routes.channelMessage(channelId, messageId)),
deleteMessage: async (messageId) => {
await rest.delete(Routes.channelMessage(channelId, messageId));
},
warn: params.warn,
warnPrefix: "discord stream preview cleanup failed",
});

View File

@ -1,5 +1,6 @@
import { beforeEach, describe, expect, it, vi } from "vitest";
import { packNpmSpecToArchive, withTempDir } from "./install-source-utils.js";
import type { NpmIntegrityDriftPayload } from "./npm-integrity.js";
import { installFromNpmSpecArchive } from "./npm-pack-install.js";
vi.mock("./install-source-utils.js", async (importOriginal) => {
@ -37,12 +38,7 @@ describe("installFromNpmSpecArchive", () => {
const runInstall = async (overrides: {
expectedIntegrity?: string;
onIntegrityDrift?: (payload: {
spec: string;
expectedIntegrity: string;
actualIntegrity: string;
resolvedSpec: string;
}) => boolean | Promise<boolean>;
onIntegrityDrift?: (payload: NpmIntegrityDriftPayload) => boolean | Promise<boolean>;
warn?: (message: string) => void;
installFromArchive: (params: {
archivePath: string;

View File

@ -4,8 +4,6 @@ import path from "node:path";
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
import type { ReplyPayload } from "../../auto-reply/types.js";
import type { OpenClawConfig } from "../../config/config.js";
import { setActivePluginRegistry } from "../../plugins/runtime.js";
import { createTestRegistry } from "../../test-utils/channel-plugins.js";
import { typedCases } from "../../test-utils/typed-cases.js";
import {
ackDelivery,

View File

@ -160,7 +160,7 @@ export function resolveOutboundTarget(params: {
};
}
const allowFrom =
const allowFromRaw =
params.allowFrom ??
(params.cfg && plugin.config.resolveAllowFrom
? plugin.config.resolveAllowFrom({
@ -168,6 +168,7 @@ export function resolveOutboundTarget(params: {
accountId: params.accountId ?? undefined,
})
: undefined);
const allowFrom = allowFromRaw?.map((entry) => String(entry));
// Fall back to per-channel defaultTo when no explicit target is provided.
const effectiveTo =
@ -360,12 +361,13 @@ export function resolveHeartbeatSenderContext(params: {
const accountId =
params.delivery.accountId ??
(provider === params.delivery.lastChannel ? params.delivery.lastAccountId : undefined);
const allowFrom = provider
const allowFromRaw = provider
? (getChannelPlugin(provider)?.config.resolveAllowFrom?.({
cfg: params.cfg,
accountId,
}) ?? [])
: [];
const allowFrom = allowFromRaw.map((entry) => String(entry));
const sender = resolveHeartbeatSenderId({
allowFrom,

View File

@ -1,5 +1,9 @@
import { vi } from "vitest";
import { buildTelegramMessageContext } from "./bot-message-context.js";
import {
buildTelegramMessageContext,
type BuildTelegramMessageContextParams,
type TelegramMediaRef,
} from "./bot-message-context.js";
export const baseTelegramMessageContextConfig = {
agents: { defaults: { model: "anthropic/claude-opus-4-5", workspace: "/tmp/openclaw" } },
@ -9,15 +13,12 @@ export const baseTelegramMessageContextConfig = {
type BuildTelegramMessageContextForTestParams = {
message: Record<string, unknown>;
allMedia?: Array<Record<string, unknown>>;
options?: Record<string, unknown>;
allMedia?: TelegramMediaRef[];
options?: BuildTelegramMessageContextParams["options"];
cfg?: Record<string, unknown>;
resolveGroupActivation?: () => boolean | undefined;
resolveGroupRequireMention?: () => boolean;
resolveTelegramGroupConfig?: () => {
groupConfig?: { requireMention?: boolean };
topicConfig?: unknown;
};
resolveGroupActivation?: BuildTelegramMessageContextParams["resolveGroupActivation"];
resolveGroupRequireMention?: BuildTelegramMessageContextParams["resolveGroupRequireMention"];
resolveTelegramGroupConfig?: BuildTelegramMessageContextParams["resolveTelegramGroupConfig"];
};
export async function buildTelegramMessageContextForTest(

View File

@ -153,7 +153,9 @@ export function createTelegramDraftStream(params: {
},
isValidMessageId: (value): value is number =>
typeof value === "number" && Number.isFinite(value),
deleteMessage: (messageId) => params.api.deleteMessage(chatId, messageId),
deleteMessage: async (messageId) => {
await params.api.deleteMessage(chatId, messageId);
},
onDeleteSuccess: (messageId) => {
params.log?.(`telegram stream preview deleted (chat=${chatId}, message=${messageId})`);
},

View File

@ -1,19 +1,23 @@
import { describe, expect, it, vi } from "vitest";
import { createCommandHandlers } from "./tui-command-handlers.js";
type LoadHistoryMock = ReturnType<typeof vi.fn> & (() => Promise<void>);
type SetActivityStatusMock = ReturnType<typeof vi.fn> & ((text: string) => void);
function createHarness(params?: {
sendChat?: ReturnType<typeof vi.fn>;
resetSession?: ReturnType<typeof vi.fn>;
loadHistory?: ReturnType<typeof vi.fn>;
setActivityStatus?: ReturnType<typeof vi.fn>;
loadHistory?: LoadHistoryMock;
setActivityStatus?: SetActivityStatusMock;
}) {
const sendChat = params?.sendChat ?? vi.fn().mockResolvedValue({ runId: "r1" });
const resetSession = params?.resetSession ?? vi.fn().mockResolvedValue({ ok: true });
const addUser = vi.fn();
const addSystem = vi.fn();
const requestRender = vi.fn();
const loadHistory = params?.loadHistory ?? vi.fn().mockResolvedValue(undefined);
const setActivityStatus = params?.setActivityStatus ?? vi.fn();
const loadHistory =
params?.loadHistory ?? (vi.fn().mockResolvedValue(undefined) as LoadHistoryMock);
const setActivityStatus = params?.setActivityStatus ?? (vi.fn() as SetActivityStatusMock);
const { handleCommand } = createCommandHandlers({
client: { sendChat, resetSession } as never,