diff --git a/src/commands/agent-via-gateway.e2e.test.ts b/src/commands/agent-via-gateway.e2e.test.ts index 2bf21697ba7..15ba8f0c677 100644 --- a/src/commands/agent-via-gateway.e2e.test.ts +++ b/src/commands/agent-via-gateway.e2e.test.ts @@ -70,8 +70,11 @@ function mockGatewaySuccessReply(text = "hello") { function mockLocalAgentReply(text = "local") { vi.mocked(agentCommand).mockImplementationOnce(async (_opts, rt) => { - rt.log?.(text); - return { payloads: [{ text }], meta: { stub: true } }; + rt?.log?.(text); + return { + payloads: [{ text }], + meta: { durationMs: 1, agentMeta: { sessionId: "s", provider: "p", model: "m" } }, + } as unknown as Awaited>; }); } diff --git a/src/commands/agent.delivery.e2e.test.ts b/src/commands/agent.delivery.e2e.test.ts index 6982812a44f..0830d20a2c2 100644 --- a/src/commands/agent.delivery.e2e.test.ts +++ b/src/commands/agent.delivery.e2e.test.ts @@ -40,7 +40,7 @@ describe("deliverAgentCommandResult", () => { function createResult(text = "hi") { return { payloads: [{ text }], - meta: {}, + meta: { durationMs: 1 }, }; } @@ -207,7 +207,7 @@ describe("deliverAgentCommandResult", () => { }); expect(runtime.log).toHaveBeenCalledTimes(1); - const line = String(runtime.log.mock.calls[0]?.[0]); + const line = String((runtime.log as ReturnType).mock.calls[0]?.[0]); expect(line).toContain("[agent:nested]"); expect(line).toContain("session=agent:main:main"); expect(line).toContain("run=run-announce"); diff --git a/src/commands/agent/session.test.ts b/src/commands/agent/session.test.ts index 1b64d1b8cf0..ecced34dac0 100644 --- a/src/commands/agent/session.test.ts +++ b/src/commands/agent/session.test.ts @@ -171,7 +171,7 @@ describe("resolveSessionKeyForRequest", () => { // loadSessionStore should be called twice: once for main, once for mybot // (not twice for main) - const storePaths = mocks.loadSessionStore.mock.calls.map((call: [string]) => call[0]); + const storePaths = mocks.loadSessionStore.mock.calls.map((call) => String(call[0])); expect(storePaths).toHaveLength(2); expect(storePaths).toContain(MAIN_STORE_PATH); expect(storePaths).toContain(MYBOT_STORE_PATH); diff --git a/src/commands/auth-choice.e2e.test.ts b/src/commands/auth-choice.e2e.test.ts index c7e6b19fdb4..2d61c858e41 100644 --- a/src/commands/auth-choice.e2e.test.ts +++ b/src/commands/auth-choice.e2e.test.ts @@ -366,7 +366,7 @@ describe("applyAuthChoice", () => { if (previousIsTTYDescriptor) { Object.defineProperty(stdin, "isTTY", previousIsTTYDescriptor); } else if (!hadOwnIsTTY) { - delete stdin.isTTY; + delete (stdin as { isTTY?: boolean }).isTTY; } } }); @@ -653,7 +653,8 @@ describe("applyAuthChoice", () => { const runtime = createExitThrowingRuntime(); const text: WizardPrompter["text"] = vi.fn(async (params) => { if (params.message === "Paste the redirect URL") { - const lastLog = runtime.log.mock.calls.at(-1)?.[0]; + const runtimeLog = runtime.log as ReturnType; + const lastLog = runtimeLog.mock.calls.at(-1)?.[0]; const urlLine = typeof lastLog === "string" ? lastLog : String(lastLog ?? ""); const urlMatch = urlLine.match(/https?:\/\/\S+/)?.[0] ?? ""; const state = urlMatch ? new URL(urlMatch).searchParams.get("state") : null; @@ -734,7 +735,7 @@ describe("applyAuthChoice", () => { }, ], }, - ]); + ] as never); const prompter = createPrompter({}); const runtime = createExitThrowingRuntime(); @@ -806,7 +807,7 @@ describe("applyAuthChoice", () => { }, ], }, - ]); + ] as never); const prompter = createPrompter({ select: vi.fn(async () => "oauth" as never) as WizardPrompter["select"], diff --git a/src/commands/channels/capabilities.e2e.test.ts b/src/commands/channels/capabilities.e2e.test.ts index b66763213eb..ba3353ea556 100644 --- a/src/commands/channels/capabilities.e2e.test.ts +++ b/src/commands/channels/capabilities.e2e.test.ts @@ -26,8 +26,12 @@ vi.mock("../../slack/scopes.js", () => ({ })); const runtime = { - log: (value: string) => logs.push(value), - error: (value: string) => errors.push(value), + log: (...args: unknown[]) => { + logs.push(args.map(String).join(" ")); + }, + error: (...args: unknown[]) => { + errors.push(args.map(String).join(" ")); + }, exit: (code: number) => { throw new Error(`exit:${code}`); }, diff --git a/src/commands/doctor-auth.deprecated-cli-profiles.e2e.test.ts b/src/commands/doctor-auth.deprecated-cli-profiles.e2e.test.ts index 705fc8bd43b..bf3e59c2d7d 100644 --- a/src/commands/doctor-auth.deprecated-cli-profiles.e2e.test.ts +++ b/src/commands/doctor-auth.deprecated-cli-profiles.e2e.test.ts @@ -2,6 +2,7 @@ import fs from "node:fs"; import os from "node:os"; import path from "node:path"; import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; +import type { OpenClawConfig } from "../config/config.js"; import { maybeRemoveDeprecatedCliAuthProfiles } from "./doctor-auth.js"; import type { DoctorPrompter } from "./doctor-prompter.js"; @@ -93,7 +94,10 @@ describe("maybeRemoveDeprecatedCliAuthProfiles", () => { }, } as const; - const next = await maybeRemoveDeprecatedCliAuthProfiles(cfg, makePrompter(true)); + const next = await maybeRemoveDeprecatedCliAuthProfiles( + cfg as unknown as OpenClawConfig, + makePrompter(true), + ); const raw = JSON.parse(fs.readFileSync(authPath, "utf8")) as { profiles?: Record; diff --git a/src/commands/doctor.runs-legacy-state-migrations-yes-mode-without.e2e.test.ts b/src/commands/doctor.runs-legacy-state-migrations-yes-mode-without.e2e.test.ts index 539b49b1cd4..a6a0f988b5b 100644 --- a/src/commands/doctor.runs-legacy-state-migrations-yes-mode-without.e2e.test.ts +++ b/src/commands/doctor.runs-legacy-state-migrations-yes-mode-without.e2e.test.ts @@ -1,4 +1,4 @@ -import { describe, expect, it } from "vitest"; +import { describe, expect, it, vi } from "vitest"; import { arrangeLegacyStateMigrationTest, confirm, @@ -15,7 +15,10 @@ describe("doctor command", () => { const { doctorCommand, runtime, runLegacyStateMigrations } = await arrangeLegacyStateMigrationTest(); - await doctorCommand(runtime, { yes: true }); + await (doctorCommand as (runtime: unknown, opts: Record) => Promise)( + runtime, + { yes: true }, + ); expect(runLegacyStateMigrations).toHaveBeenCalledTimes(1); expect(confirm).not.toHaveBeenCalled(); @@ -25,7 +28,10 @@ describe("doctor command", () => { const { doctorCommand, runtime, runLegacyStateMigrations } = await arrangeLegacyStateMigrationTest(); - await doctorCommand(runtime, { nonInteractive: true }); + await (doctorCommand as (runtime: unknown, opts: Record) => Promise)( + runtime, + { nonInteractive: true }, + ); expect(runLegacyStateMigrations).toHaveBeenCalledTimes(1); expect(confirm).not.toHaveBeenCalled(); @@ -35,7 +41,7 @@ describe("doctor command", () => { mockDoctorConfigSnapshot(); const { healthCommand } = await import("./health.js"); - healthCommand.mockRejectedValueOnce(new Error("gateway closed")); + vi.mocked(healthCommand).mockRejectedValueOnce(new Error("gateway closed")); serviceIsLoaded.mockResolvedValueOnce(true); serviceRestart.mockClear(); diff --git a/src/commands/health.e2e.test.ts b/src/commands/health.e2e.test.ts index 8fae558332c..c138932e087 100644 --- a/src/commands/health.e2e.test.ts +++ b/src/commands/health.e2e.test.ts @@ -10,7 +10,11 @@ const runtime = { exit: vi.fn(), }; -const defaultSessions = { path: "/tmp/sessions.json", count: 0, recent: [] }; +const defaultSessions: HealthSummary["sessions"] = { + path: "/tmp/sessions.json", + count: 0, + recent: [], +}; const createMainAgentSummary = (sessions = defaultSessions) => ({ agentId: "main", diff --git a/src/commands/message.e2e.test.ts b/src/commands/message.e2e.test.ts index 3ef7ec70d36..a5ab9f36d4d 100644 --- a/src/commands/message.e2e.test.ts +++ b/src/commands/message.e2e.test.ts @@ -117,6 +117,10 @@ const createStubPlugin = (params: { outbound: params.outbound, }); +type ChannelActionParams = Parameters< + NonNullable["handleAction"]> +>[0]; + const createDiscordPollPluginRegistration = () => ({ pluginId: "discord", source: "test", @@ -125,11 +129,12 @@ const createDiscordPollPluginRegistration = () => ({ label: "Discord", actions: { listActions: () => ["poll"], - handleAction: async ({ action, params, cfg, accountId }) => - await handleDiscordAction( + handleAction: (async ({ action, params, cfg, accountId }: ChannelActionParams) => { + return await handleDiscordAction( { action, to: params.to, accountId: accountId ?? undefined }, cfg, - ), + ); + }) as unknown as NonNullable["handleAction"], }, }), }); @@ -142,11 +147,12 @@ const createTelegramSendPluginRegistration = () => ({ label: "Telegram", actions: { listActions: () => ["send"], - handleAction: async ({ action, params, cfg, accountId }) => - await handleTelegramAction( + handleAction: (async ({ action, params, cfg, accountId }: ChannelActionParams) => { + return await handleTelegramAction( { action, to: params.to, accountId: accountId ?? undefined }, cfg, - ), + ); + }) as unknown as NonNullable["handleAction"], }, }), }); diff --git a/src/commands/models.list.test.ts b/src/commands/models.list.test.ts index cec01a3a4e0..acdd7ee654e 100644 --- a/src/commands/models.list.test.ts +++ b/src/commands/models.list.test.ts @@ -24,7 +24,7 @@ const modelRegistryState = { getAllError: undefined as unknown, getAvailableError: undefined as unknown, }; -let previousExitCode: number | undefined; +let previousExitCode: typeof process.exitCode; vi.mock("../config/config.js", () => ({ CONFIG_PATH: "/tmp/openclaw.json", @@ -480,7 +480,10 @@ describe("models list/status", () => { const { toModelRow } = await import("./models/list.registry.js"); const row = toModelRow({ - model: makeGoogleAntigravityTemplate("claude-opus-4-6-thinking", "Claude Opus 4.6 Thinking"), + model: makeGoogleAntigravityTemplate( + "claude-opus-4-6-thinking", + "Claude Opus 4.6 Thinking", + ) as never, key: "google-antigravity/claude-opus-4-6-thinking", tags: [], availableKeys: undefined, diff --git a/src/commands/onboard-interactive.e2e.test.ts b/src/commands/onboard-interactive.e2e.test.ts index fffa70ab9a6..ec1a4dcb3e1 100644 --- a/src/commands/onboard-interactive.e2e.test.ts +++ b/src/commands/onboard-interactive.e2e.test.ts @@ -23,9 +23,9 @@ describe("runInteractiveOnboarding", () => { createClackPrompterMock.mockReset(); runOnboardingWizardMock.mockReset(); restoreTerminalStateMock.mockReset(); - runtime.log.mockClear(); - runtime.error.mockClear(); - runtime.exit.mockClear(); + (runtime.log as ReturnType).mockClear(); + (runtime.error as ReturnType).mockClear(); + (runtime.exit as ReturnType).mockClear(); createClackPrompterMock.mockReturnValue({}); runOnboardingWizardMock.mockResolvedValue(undefined); diff --git a/src/commands/onboard-non-interactive.gateway.e2e.test.ts b/src/commands/onboard-non-interactive.gateway.e2e.test.ts index 1c06e285e32..1a69960cba1 100644 --- a/src/commands/onboard-non-interactive.gateway.e2e.test.ts +++ b/src/commands/onboard-non-interactive.gateway.e2e.test.ts @@ -1,6 +1,7 @@ import fs from "node:fs/promises"; import path from "node:path"; import { afterAll, beforeAll, describe, expect, it, vi } from "vitest"; +import type { GatewayAuthConfig } from "../config/config.js"; import { makeTempWorkspace } from "../test-helpers/workspace.js"; import { getFreePortBlockWithPermissionFallback } from "../test-utils/ports.js"; import { @@ -61,7 +62,7 @@ async function getFreeGatewayPort(): Promise { const runtime = createThrowingRuntime(); async function expectGatewayTokenAuth(params: { - authConfig: unknown; + authConfig: GatewayAuthConfig | null | undefined; token: string; env: NodeJS.ProcessEnv; }) { @@ -161,7 +162,7 @@ describe("onboard (non-interactive): gateway and remote auth", () => { const { resolveConfigPath } = await import("../config/paths.js"); const configPath = resolveConfigPath(process.env, stateDir); const cfg = await readJsonFile<{ - gateway?: { auth?: { mode?: string; token?: string } }; + gateway?: { auth?: GatewayAuthConfig }; agents?: { defaults?: { workspace?: string } }; }>(configPath); @@ -245,7 +246,7 @@ describe("onboard (non-interactive): gateway and remote auth", () => { gateway?: { bind?: string; port?: number; - auth?: { mode?: string; token?: string }; + auth?: GatewayAuthConfig; }; }>(configPath); diff --git a/src/commands/onboard-skills.e2e.test.ts b/src/commands/onboard-skills.e2e.test.ts index d7db1c05126..ddb1da94113 100644 --- a/src/commands/onboard-skills.e2e.test.ts +++ b/src/commands/onboard-skills.e2e.test.ts @@ -85,7 +85,7 @@ function mockMissingBrewStatus(skills: Array { notes.push({ title, message }); }), - select: vi.fn(async () => "npm"), - multiselect: vi.fn(async () => params.multiselect ?? ["__skip__"]), + select: vi.fn(async () => "npm") as unknown as WizardPrompter["select"], + multiselect: vi.fn( + async () => params.multiselect ?? ["__skip__"], + ) as unknown as WizardPrompter["multiselect"], text: vi.fn(async () => ""), confirm: vi.fn(async ({ message }) => { if (message === "Show Homebrew install command?") { diff --git a/src/commands/onboarding/plugin-install.e2e.test.ts b/src/commands/onboarding/plugin-install.e2e.test.ts index 7da28ca9402..fbc2049684f 100644 --- a/src/commands/onboarding/plugin-install.e2e.test.ts +++ b/src/commands/onboarding/plugin-install.e2e.test.ts @@ -52,8 +52,8 @@ function mockRepoLocalPathExists() { async function runInitialValueForChannel(channel: "dev" | "beta") { const runtime = makeRuntime(); - const select = vi.fn(async () => "skip") as WizardPrompter["select"]; - const prompter = makePrompter({ select }); + const select = vi.fn((async () => "skip" as T) as WizardPrompter["select"]); + const prompter = makePrompter({ select: select as unknown as WizardPrompter["select"] }); const cfg: OpenClawConfig = { update: { channel } }; mockRepoLocalPathExists(); @@ -64,7 +64,8 @@ async function runInitialValueForChannel(channel: "dev" | "beta") { runtime, }); - return select.mock.calls[0]?.[0]?.initialValue; + const call = select.mock.calls[0]; + return call?.[0]?.initialValue; } function expectPluginLoadedFromLocalPath( diff --git a/src/commands/openai-model-default.e2e.test.ts b/src/commands/openai-model-default.e2e.test.ts index 910c2c9c84d..faf0f1ee0b4 100644 --- a/src/commands/openai-model-default.e2e.test.ts +++ b/src/commands/openai-model-default.e2e.test.ts @@ -25,8 +25,8 @@ function makePrompter(): WizardPrompter { intro: async () => {}, outro: async () => {}, note: async () => {}, - select: async () => "", - multiselect: async () => [], + select: (async () => "" as T) as WizardPrompter["select"], + multiselect: (async () => [] as T[]) as WizardPrompter["multiselect"], text: async () => "", confirm: async () => false, progress: () => ({ update: () => {}, stop: () => {} }), @@ -161,9 +161,9 @@ describe("applyOpenAIConfig", () => { it("overrides model.primary when model object already exists", () => { const next = applyOpenAIConfig({ - agents: { defaults: { model: { primary: "anthropic/claude-opus-4-6", fallback: [] } } }, + agents: { defaults: { model: { primary: "anthropic/claude-opus-4-6", fallbacks: [] } } }, }); - expect(next.agents?.defaults?.model).toEqual({ primary: OPENAI_DEFAULT_MODEL, fallback: [] }); + expect(next.agents?.defaults?.model).toEqual({ primary: OPENAI_DEFAULT_MODEL, fallbacks: [] }); }); }); diff --git a/src/commands/sandbox.e2e.test.ts b/src/commands/sandbox.e2e.test.ts index c266587fdf1..384dc2eef41 100644 --- a/src/commands/sandbox.e2e.test.ts +++ b/src/commands/sandbox.e2e.test.ts @@ -205,7 +205,7 @@ describe("sandboxRecreateCommand", () => { mocks.listSandboxContainers.mockResolvedValue([match, noMatch]); await sandboxRecreateCommand( - { session: "target-session", browser: false, force: true }, + { session: "target-session", all: false, browser: false, force: true }, runtime as never, ); @@ -220,7 +220,7 @@ describe("sandboxRecreateCommand", () => { mocks.listSandboxContainers.mockResolvedValue([agent, agentSub, other]); await sandboxRecreateCommand( - { agent: "work", browser: false, force: true }, + { agent: "work", all: false, browser: false, force: true }, runtime as never, ); diff --git a/src/commands/status-all/channels.mattermost-token-summary.test.ts b/src/commands/status-all/channels.mattermost-token-summary.test.ts index 645bd30f81a..53614388667 100644 --- a/src/commands/status-all/channels.mattermost-token-summary.test.ts +++ b/src/commands/status-all/channels.mattermost-token-summary.test.ts @@ -17,6 +17,7 @@ function makeMattermostPlugin(): ChannelPlugin { docsPath: "/channels/mattermost", blurb: "test", }, + capabilities: { chatTypes: ["direct"] }, config: { listAccountIds: () => ["echo"], defaultAccountId: () => "echo", @@ -45,6 +46,7 @@ function makeSlackPlugin(params?: { botToken?: string; appToken?: string }): Cha docsPath: "/channels/slack", blurb: "test", }, + capabilities: { chatTypes: ["direct"] }, config: { listAccountIds: () => ["primary"], defaultAccountId: () => "primary", @@ -73,6 +75,7 @@ function makeTokenPlugin(): ChannelPlugin { docsPath: "/channels/token-only", blurb: "test", }, + capabilities: { chatTypes: ["direct"] }, config: { listAccountIds: () => ["primary"], defaultAccountId: () => "primary",