From 7c109f5737f562383cd0c71c36ff349e6211ac6c Mon Sep 17 00:00:00 2001 From: Peter Steinberger Date: Sun, 22 Feb 2026 21:35:11 +0000 Subject: [PATCH] fix: resolve ci type errors and reconnect test flake --- ...skills.buildworkspaceskillsnapshot.test.ts | 11 ++++++-- src/agents/tools/browser-tool.ts | 8 ++++-- src/commands/channel-account-context.ts | 2 +- .../onboard-auth.config-shared.test.ts | 5 ++-- src/infra/device-pairing.ts | 6 ++++- src/infra/node-pairing.ts | 6 ++++- src/memory/batch-error-utils.ts | 25 +++++++++++-------- ....reconnects-after-connection-close.test.ts | 19 +++++++++++--- 8 files changed, 59 insertions(+), 23 deletions(-) diff --git a/src/agents/skills.buildworkspaceskillsnapshot.test.ts b/src/agents/skills.buildworkspaceskillsnapshot.test.ts index 82ed358a89a..5e24e31b085 100644 --- a/src/agents/skills.buildworkspaceskillsnapshot.test.ts +++ b/src/agents/skills.buildworkspaceskillsnapshot.test.ts @@ -76,8 +76,15 @@ describe("buildWorkspaceSkillSnapshot", () => { config, managedSkillsDir: path.join(workspaceDir, ".managed"), bundledSkillsDir: path.join(workspaceDir, ".bundled"), - eligibility: { remote: { note: "Remote note" } }, - } as const; + eligibility: { + remote: { + platforms: ["linux"], + hasBin: (_bin: string) => true, + hasAnyBin: (_bins: string[]) => true, + note: "Remote note", + }, + }, + }; const snapshot = buildWorkspaceSkillSnapshot(workspaceDir, opts); const prompt = buildWorkspaceSkillsPrompt(workspaceDir, opts); diff --git a/src/agents/tools/browser-tool.ts b/src/agents/tools/browser-tool.ts index 27a0609f654..63f24a075bb 100644 --- a/src/agents/tools/browser-tool.ts +++ b/src/agents/tools/browser-tool.ts @@ -1,4 +1,5 @@ import crypto from "node:crypto"; +import type { AgentToolResult } from "@mariozechner/pi-agent-core"; import { browserAct, browserArmDialog, @@ -54,14 +55,17 @@ function wrapBrowserExternalJson(params: { }; } -function formatTabsToolResult(tabs: unknown[]) { +function formatTabsToolResult(tabs: unknown[]): AgentToolResult { const wrapped = wrapBrowserExternalJson({ kind: "tabs", payload: { tabs }, includeWarning: false, }); + const content: AgentToolResult["content"] = [ + { type: "text", text: wrapped.wrappedText }, + ]; return { - content: [{ type: "text", text: wrapped.wrappedText }], + content, details: { ...wrapped.safeDetails, tabCount: tabs.length }, }; } diff --git a/src/commands/channel-account-context.ts b/src/commands/channel-account-context.ts index 99b73e62b81..36ce8c53e72 100644 --- a/src/commands/channel-account-context.ts +++ b/src/commands/channel-account-context.ts @@ -4,7 +4,7 @@ import type { OpenClawConfig } from "../config/config.js"; export type ChannelDefaultAccountContext = { accountIds: string[]; - defaultAccountId?: string; + defaultAccountId: string; account: unknown; enabled: boolean; configured: boolean; diff --git a/src/commands/onboard-auth.config-shared.test.ts b/src/commands/onboard-auth.config-shared.test.ts index cf4f2238f2f..de2dc9adb62 100644 --- a/src/commands/onboard-auth.config-shared.test.ts +++ b/src/commands/onboard-auth.config-shared.test.ts @@ -1,4 +1,5 @@ import { describe, expect, it } from "vitest"; +import type { OpenClawConfig } from "../config/config.js"; import type { AgentModelEntryConfig } from "../config/types.agent-defaults.js"; import type { ModelDefinitionConfig } from "../config/types.models.js"; import { @@ -25,7 +26,7 @@ describe("onboard auth provider config merges", () => { }; it("appends missing default models to existing provider models", () => { - const cfg = { + const cfg: OpenClawConfig = { models: { providers: { custom: { @@ -56,7 +57,7 @@ describe("onboard auth provider config merges", () => { }); it("merges model catalogs without duplicating existing model ids", () => { - const cfg = { + const cfg: OpenClawConfig = { models: { providers: { custom: { diff --git a/src/infra/device-pairing.ts b/src/infra/device-pairing.ts index 53d362397b6..3990af3340c 100644 --- a/src/infra/device-pairing.ts +++ b/src/infra/device-pairing.ts @@ -383,7 +383,11 @@ export async function rejectDevicePairing( baseDir?: string, ): Promise<{ requestId: string; deviceId: string } | null> { return await withLock(async () => { - return await rejectPendingPairingRequest({ + return await rejectPendingPairingRequest< + DevicePairingPendingRequest, + DevicePairingStateFile, + "deviceId" + >({ requestId, idKey: "deviceId", loadState: () => loadState(baseDir), diff --git a/src/infra/node-pairing.ts b/src/infra/node-pairing.ts index 4990a28b435..8699b54ae79 100644 --- a/src/infra/node-pairing.ts +++ b/src/infra/node-pairing.ts @@ -195,7 +195,11 @@ export async function rejectNodePairing( baseDir?: string, ): Promise<{ requestId: string; nodeId: string } | null> { return await withLock(async () => { - return await rejectPendingPairingRequest({ + return await rejectPendingPairingRequest< + NodePairingPendingRequest, + NodePairingStateFile, + "nodeId" + >({ requestId, idKey: "nodeId", loadState: () => loadState(baseDir), diff --git a/src/memory/batch-error-utils.ts b/src/memory/batch-error-utils.ts index 95a812c3669..b6ee9d28c65 100644 --- a/src/memory/batch-error-utils.ts +++ b/src/memory/batch-error-utils.ts @@ -1,20 +1,25 @@ type BatchOutputErrorLike = { error?: { message?: string }; response?: { - body?: { - error?: { message?: string }; - }; + body?: + | string + | { + error?: { message?: string }; + }; }; }; +function getResponseErrorMessage(line: BatchOutputErrorLike | undefined): string | undefined { + const body = line?.response?.body; + if (!body || typeof body !== "object") { + return undefined; + } + return typeof body.error?.message === "string" ? body.error.message : undefined; +} + export function extractBatchErrorMessage(lines: BatchOutputErrorLike[]): string | undefined { - const first = lines.find((line) => line.error?.message || line.response?.body?.error); - return ( - first?.error?.message ?? - (typeof first?.response?.body?.error?.message === "string" - ? first?.response?.body?.error?.message - : undefined) - ); + const first = lines.find((line) => line.error?.message || getResponseErrorMessage(line)); + return first?.error?.message ?? getResponseErrorMessage(first); } export function formatUnavailableBatchError(err: unknown): string | undefined { diff --git a/src/web/auto-reply.web-auto-reply.reconnects-after-connection-close.test.ts b/src/web/auto-reply.web-auto-reply.reconnects-after-connection-close.test.ts index 80e46446af0..f2e545fc680 100644 --- a/src/web/auto-reply.web-auto-reply.reconnects-after-connection-close.test.ts +++ b/src/web/auto-reply.web-auto-reply.reconnects-after-connection-close.test.ts @@ -127,6 +127,7 @@ describe("web auto-reply", () => { try { const sleep = vi.fn(async () => {}); const closeResolvers: Array<(reason: unknown) => void> = []; + const signalCloseSpy = vi.fn(); let capturedOnMessage: | ((msg: import("./inbound.js").WebInboundMessage) => Promise) | undefined; @@ -143,11 +144,14 @@ describe("web auto-reply", () => { return { close: vi.fn(), onClose, - signalClose: (reason?: unknown) => resolveClose(reason), + signalClose: (reason?: unknown) => { + signalCloseSpy(reason); + resolveClose(reason); + }, }; }, ); - const { controller, run } = startMonitorWebChannel({ + const { runtime, controller, run } = startMonitorWebChannel({ monitorWebChannelFn: monitorWebChannel as never, listenerFactory, sleep, @@ -179,8 +183,15 @@ describe("web auto-reply", () => { await Promise.resolve(); await vi.advanceTimersByTimeAsync(1); - await Promise.resolve(); - expect(listenerFactory).toHaveBeenCalledTimes(2); + expect(signalCloseSpy).toHaveBeenCalledWith( + expect.objectContaining({ status: 499, isLoggedOut: false, error: "watchdog-timeout" }), + ); + for (let i = 0; i < 20 && listenerFactory.mock.calls.length < 2; i += 1) { + await vi.advanceTimersByTimeAsync(50); + await Promise.resolve(); + } + expect(listenerFactory.mock.calls.length).toBeGreaterThanOrEqual(2); + expect(runtime.error).toHaveBeenCalledWith(expect.stringContaining("Retry 1")); controller.abort(); closeResolvers[1]?.({ status: 499, isLoggedOut: false });