Reduce lint suppressions in core tests and runtime

This commit is contained in:
Tak Hoffman
2026-03-27 01:26:57 -05:00
parent 7c00cc9d0a
commit f5643544c2
11 changed files with 115 additions and 112 deletions

View File

@@ -91,7 +91,8 @@
- Language: TypeScript (ESM). Prefer strict typing; avoid `any`.
- Formatting/linting via Oxlint and Oxfmt.
- Never add `@ts-nocheck` and do not disable `no-explicit-any`; fix root causes and update Oxlint/Oxfmt config only when required.
- Never add `@ts-nocheck` and do not add inline lint suppressions by default. Fix root causes first; only keep a suppression when the code is intentionally correct, the rule cannot express that safely, and the comment explains why.
- Do not disable `no-explicit-any`; prefer real types, `unknown`, or a narrow adapter/helper instead. Update Oxlint/Oxfmt config only when required.
- Dynamic import guardrail: do not mix `await import("x")` and static `import ... from "x"` for the same module in production code paths. If you need lazy loading, create a dedicated `*.runtime.ts` boundary (that re-exports from `x`) and dynamically import that boundary from lazy callers only.
- Dynamic import verification: after refactors that touch lazy-loading/module boundaries, run `pnpm build` and check for `[INEFFECTIVE_DYNAMIC_IMPORT]` warnings before submitting.
- Extension SDK self-import guardrail: inside an extension package, do not import that same extension via `openclaw/plugin-sdk/<extension>` from production files. Route internal imports through a local barrel such as `./api.ts` or `./runtime-api.ts`, and keep the `plugin-sdk/<extension>` path as the external contract only.

View File

@@ -111,7 +111,6 @@ vi.mock("./manager-runtime.js", () => ({
import { QmdMemoryManager } from "./qmd-manager.js";
import { closeAllMemorySearchManagers, getMemorySearchManager } from "./search-manager.js";
// eslint-disable-next-line @typescript-eslint/unbound-method -- mocked static function
const createQmdManagerMock = vi.mocked(QmdMemoryManager.create);
type SearchManagerResult = Awaited<ReturnType<typeof getMemorySearchManager>>;
@@ -169,8 +168,7 @@ describe("getMemorySearchManager caching", () => {
const second = await getMemorySearchManager({ cfg, agentId: "main" });
expect(first.manager).toBe(second.manager);
// eslint-disable-next-line @typescript-eslint/unbound-method
expect(createQmdManagerMock).toHaveBeenCalledTimes(1);
expect(createQmdManagerMock.mock.calls).toHaveLength(1);
});
it("evicts failed qmd wrapper so next call retries qmd", async () => {
@@ -191,8 +189,7 @@ describe("getMemorySearchManager caching", () => {
const second = await getMemorySearchManager({ cfg, agentId: retryAgentId });
requireManager(second);
expect(second.manager).not.toBe(first.manager);
// eslint-disable-next-line @typescript-eslint/unbound-method
expect(createQmdManagerMock).toHaveBeenCalledTimes(2);
expect(createQmdManagerMock.mock.calls).toHaveLength(2);
});
it("does not cache qmd managers for status-only requests", async () => {
@@ -210,8 +207,7 @@ describe("getMemorySearchManager caching", () => {
model: "qmd",
requestedProvider: "qmd",
});
// eslint-disable-next-line @typescript-eslint/unbound-method
expect(createQmdManagerMock).toHaveBeenCalledTimes(2);
expect(createQmdManagerMock.mock.calls).toHaveLength(2);
expect(mockMemoryIndexGet).not.toHaveBeenCalled();
await first.manager?.close?.();
@@ -244,8 +240,7 @@ describe("getMemorySearchManager caching", () => {
chunks: 42,
sourceCounts: [{ source: "memory", files: 10, chunks: 42 }],
});
// eslint-disable-next-line @typescript-eslint/unbound-method
expect(createQmdManagerMock).toHaveBeenCalledWith(
expect(createQmdManagerMock.mock.calls[0]?.[0]).toEqual(
expect.objectContaining({ agentId, mode: "status" }),
);
});
@@ -260,8 +255,7 @@ describe("getMemorySearchManager caching", () => {
requireManager(full);
requireManager(status);
expect(status.manager).not.toBe(full.manager);
// eslint-disable-next-line @typescript-eslint/unbound-method
expect(createQmdManagerMock).toHaveBeenCalledTimes(1);
expect(createQmdManagerMock.mock.calls).toHaveLength(1);
await status.manager?.close?.();
expect(mockPrimary.close).not.toHaveBeenCalled();
@@ -280,8 +274,7 @@ describe("getMemorySearchManager caching", () => {
const second = await getMemorySearchManager({ cfg, agentId, purpose: "status" });
requireManager(second);
// eslint-disable-next-line @typescript-eslint/unbound-method
expect(createQmdManagerMock).toHaveBeenCalledTimes(2);
expect(createQmdManagerMock.mock.calls).toHaveLength(2);
expect(mockPrimary.close).toHaveBeenCalledTimes(1);
});
@@ -305,8 +298,7 @@ describe("getMemorySearchManager caching", () => {
const third = await getMemorySearchManager({ cfg, agentId: retryAgentId });
expect(third.manager).toBe(secondManager);
// eslint-disable-next-line @typescript-eslint/unbound-method
expect(createQmdManagerMock).toHaveBeenCalledTimes(2);
expect(createQmdManagerMock.mock.calls).toHaveLength(2);
});
it("falls back to builtin search when qmd fails with sqlite busy", async () => {
@@ -346,8 +338,7 @@ describe("getMemorySearchManager caching", () => {
const second = await getMemorySearchManager({ cfg, agentId: "teardown-agent" });
expect(second.manager).toBeTruthy();
expect(second.manager).not.toBe(firstManager);
// eslint-disable-next-line @typescript-eslint/unbound-method
expect(createQmdManagerMock).toHaveBeenCalledTimes(2);
expect(createQmdManagerMock.mock.calls).toHaveLength(2);
});
it("closes builtin index managers on teardown after runtime is loaded", async () => {

View File

@@ -1,12 +1,52 @@
import type { AssistantMessage } from "@mariozechner/pi-ai";
import { beforeAll, beforeEach, describe, expect, it } from "vitest";
import {
loadRunOverflowCompactionHarness,
mockedEnsureRuntimePluginsLoaded,
mockedRunEmbeddedAttempt,
} from "./run.overflow-compaction.harness.js";
import type { EmbeddedRunAttemptResult } from "./run/types.js";
let runEmbeddedPiAgent: typeof import("./run.js").runEmbeddedPiAgent;
function makeAttemptResult(
overrides: Partial<EmbeddedRunAttemptResult> = {},
): EmbeddedRunAttemptResult {
return {
aborted: false,
timedOut: false,
timedOutDuringCompaction: false,
promptError: null,
sessionIdUsed: "test-session",
messagesSnapshot: [],
assistantTexts: [],
toolMetas: [],
lastAssistant: undefined,
didSendViaMessagingTool: false,
messagingToolSentTexts: [],
messagingToolSentMediaUrls: [],
messagingToolSentTargets: [],
cloudCodeAssistFormatError: false,
...overrides,
};
}
function makeAssistantMessage(
overrides: Partial<AssistantMessage> = {},
): NonNullable<EmbeddedRunAttemptResult["lastAssistant"]> {
return {
role: "assistant",
api: "openai-responses",
provider: "openai",
model: "gpt-5.2",
usage: { input: 0, output: 0 } as AssistantMessage["usage"],
stopReason: "end_turn" as AssistantMessage["stopReason"],
timestamp: Date.now(),
content: [],
...overrides,
};
}
describe("runEmbeddedPiAgent usage reporting", () => {
beforeAll(async () => {
({ runEmbeddedPiAgent } = await loadRunOverflowCompactionHarness());
@@ -18,14 +58,11 @@ describe("runEmbeddedPiAgent usage reporting", () => {
});
it("bootstraps runtime plugins with the resolved workspace before running", async () => {
mockedRunEmbeddedAttempt.mockResolvedValueOnce({
aborted: false,
promptError: null,
timedOut: false,
sessionIdUsed: "test-session",
assistantTexts: ["Response 1"],
// eslint-disable-next-line @typescript-eslint/no-explicit-any
} as any);
mockedRunEmbeddedAttempt.mockResolvedValueOnce(
makeAttemptResult({
assistantTexts: ["Response 1"],
}),
);
await runEmbeddedPiAgent({
sessionId: "test-session",
@@ -44,14 +81,11 @@ describe("runEmbeddedPiAgent usage reporting", () => {
});
it("forwards gateway subagent binding opt-in to runtime plugin bootstrap", async () => {
mockedRunEmbeddedAttempt.mockResolvedValueOnce({
aborted: false,
promptError: null,
timedOut: false,
sessionIdUsed: "test-session",
assistantTexts: ["Response 1"],
// eslint-disable-next-line @typescript-eslint/no-explicit-any
} as any);
mockedRunEmbeddedAttempt.mockResolvedValueOnce(
makeAttemptResult({
assistantTexts: ["Response 1"],
}),
);
await runEmbeddedPiAgent({
sessionId: "test-session",
@@ -77,14 +111,11 @@ describe("runEmbeddedPiAgent usage reporting", () => {
});
it("forwards sender identity fields into embedded attempts", async () => {
mockedRunEmbeddedAttempt.mockResolvedValueOnce({
aborted: false,
promptError: null,
timedOut: false,
sessionIdUsed: "test-session",
assistantTexts: ["Response 1"],
// eslint-disable-next-line @typescript-eslint/no-explicit-any
} as any);
mockedRunEmbeddedAttempt.mockResolvedValueOnce(
makeAttemptResult({
assistantTexts: ["Response 1"],
}),
);
await runEmbeddedPiAgent({
sessionId: "test-session",
@@ -111,14 +142,11 @@ describe("runEmbeddedPiAgent usage reporting", () => {
});
it("forwards memory flush write paths into memory-triggered attempts", async () => {
mockedRunEmbeddedAttempt.mockResolvedValueOnce({
aborted: false,
promptError: null,
timedOut: false,
sessionIdUsed: "test-session",
assistantTexts: [],
// eslint-disable-next-line @typescript-eslint/no-explicit-any
} as any);
mockedRunEmbeddedAttempt.mockResolvedValueOnce(
makeAttemptResult({
assistantTexts: [],
}),
);
await runEmbeddedPiAgent({
sessionId: "test-session",
@@ -156,19 +184,15 @@ describe("runEmbeddedPiAgent usage reporting", () => {
// We expect result.meta.agentMeta.usage.total to be 200 (last turn total).
// The bug causes it to be 350 (accumulated total).
mockedRunEmbeddedAttempt.mockResolvedValueOnce({
aborted: false,
promptError: null,
timedOut: false,
sessionIdUsed: "test-session",
assistantTexts: ["Response 1", "Response 2"],
lastAssistant: {
usage: { input: 150, output: 50, total: 200 },
stopReason: "end_turn",
},
attemptUsage: { input: 250, output: 100, total: 350 },
// eslint-disable-next-line @typescript-eslint/no-explicit-any
} as any);
mockedRunEmbeddedAttempt.mockResolvedValueOnce(
makeAttemptResult({
assistantTexts: ["Response 1", "Response 2"],
lastAssistant: makeAssistantMessage({
usage: { input: 150, output: 50, total: 200 } as unknown as AssistantMessage["usage"],
}),
attemptUsage: { input: 250, output: 100, total: 350 },
}),
);
const result = await runEmbeddedPiAgent({
sessionId: "test-session",

View File

@@ -198,9 +198,8 @@ async function runLockWatchdogCheck(nowMs = Date.now()): Promise<number> {
continue;
}
// eslint-disable-next-line no-console
console.warn(
`[session-write-lock] releasing lock held for ${heldForMs}ms (max=${held.maxHoldMs}ms): ${held.lockPath}`,
process.stderr.write(
`[session-write-lock] releasing lock held for ${heldForMs}ms (max=${held.maxHoldMs}ms): ${held.lockPath}\n`,
);
const didRelease = await releaseHeldLock(sessionFile, held, { force: true });

View File

@@ -226,8 +226,7 @@ export async function runGatewayLoop(params: {
// Keep process alive; SIGUSR1 triggers an in-process restart (no supervisor required).
// SIGTERM/SIGINT still exit after a graceful shutdown.
let isFirstStart = true;
// eslint-disable-next-line no-constant-condition
while (true) {
for (;;) {
onIteration();
try {
server = await params.start();

View File

@@ -1,6 +1,7 @@
import type { AgentMessage } from "@mariozechner/pi-agent-core";
import { beforeEach, describe, expect, it, vi } from "vitest";
import { compactEmbeddedPiSessionDirect } from "../agents/pi-embedded-runner/compact.runtime.js";
import type { OpenClawConfig } from "../config/types.openclaw.js";
// ---------------------------------------------------------------------------
// We dynamically import the registry so we can get a fresh module per test
// group when needed. For most groups we use the shared singleton directly.
@@ -46,8 +47,7 @@ const mockedCompactEmbeddedPiSessionDirect = vi.mocked(compactEmbeddedPiSessionD
// ---------------------------------------------------------------------------
/** Build a config object with a contextEngine slot for testing. */
// eslint-disable-next-line @typescript-eslint/no-explicit-any
function configWithSlot(engineId: string): any {
function configWithSlot(engineId: string): OpenClawConfig {
return { plugins: { slots: { contextEngine: engineId } } };
}

View File

@@ -19,11 +19,13 @@ export async function delegateCompactionToRuntime(
// Import through a dedicated runtime boundary so the lazy edge remains effective.
const { compactEmbeddedPiSessionDirect } =
await import("../agents/pi-embedded-runner/compact.runtime.js");
type RuntimeCompactionParams = Parameters<typeof compactEmbeddedPiSessionDirect>[0];
// runtimeContext carries the full CompactEmbeddedPiSessionParams fields set
// by runtime callers. We spread them and override the fields that come from
// the public ContextEngine compact() signature directly.
const runtimeContext: ContextEngineRuntimeContext = params.runtimeContext ?? {};
const runtimeContext = (params.runtimeContext ?? {}) as ContextEngineRuntimeContext &
Partial<RuntimeCompactionParams>;
const currentTokenCount =
params.currentTokenCount ??
(typeof runtimeContext.currentTokenCount === "number" &&
@@ -32,7 +34,6 @@ export async function delegateCompactionToRuntime(
? Math.floor(runtimeContext.currentTokenCount)
: undefined);
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- bridge runtimeContext matches CompactEmbeddedPiSessionParams
const result = await compactEmbeddedPiSessionDirect({
...runtimeContext,
sessionId: params.sessionId,
@@ -41,8 +42,9 @@ export async function delegateCompactionToRuntime(
...(currentTokenCount !== undefined ? { currentTokenCount } : {}),
force: params.force,
customInstructions: params.customInstructions,
workspaceDir: (runtimeContext.workspaceDir as string) ?? process.cwd(),
} as Parameters<typeof compactEmbeddedPiSessionDirect>[0]);
workspaceDir:
typeof runtimeContext.workspaceDir === "string" ? runtimeContext.workspaceDir : process.cwd(),
});
return {
ok: result.ok,

View File

@@ -75,15 +75,13 @@ vi.mock("node:fs", async (importOriginal) => {
...actual.promises,
mkdir: async (p: string) => {
if (!isFixtureInMock(p)) {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
return await (actual.promises.mkdir as any)(p, { recursive: true });
return await actual.promises.mkdir(p, { recursive: true });
}
ensureDir(p);
},
readFile: async (p: string) => {
if (!isFixtureInMock(p)) {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
return await (actual.promises.readFile as any)(p, "utf-8");
return await actual.promises.readFile(p, "utf-8");
}
const entry = fsState.entries.get(absInMock(p));
if (!entry || entry.kind !== "file") {
@@ -93,16 +91,14 @@ vi.mock("node:fs", async (importOriginal) => {
},
writeFile: async (p: string, data: string | Uint8Array) => {
if (!isFixtureInMock(p)) {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
return await (actual.promises.writeFile as any)(p, data, "utf-8");
return await actual.promises.writeFile(p, data, "utf-8");
}
const content = typeof data === "string" ? data : Buffer.from(data).toString("utf-8");
setFile(p, content);
},
rename: async (from: string, to: string) => {
if (!isFixtureInMock(from) || !isFixtureInMock(to)) {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
return await (actual.promises.rename as any)(from, to);
return await actual.promises.rename(from, to);
}
const fromAbs = absInMock(from);
const toAbs = absInMock(to);
@@ -116,8 +112,7 @@ vi.mock("node:fs", async (importOriginal) => {
},
copyFile: async (from: string, to: string) => {
if (!isFixtureInMock(from) || !isFixtureInMock(to)) {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
return await (actual.promises.copyFile as any)(from, to);
return await actual.promises.copyFile(from, to);
}
const entry = fsState.entries.get(absInMock(from));
if (!entry || entry.kind !== "file") {
@@ -127,8 +122,7 @@ vi.mock("node:fs", async (importOriginal) => {
},
stat: async (p: string) => {
if (!isFixtureInMock(p)) {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
return await (actual.promises.stat as any)(p);
return await actual.promises.stat(p);
}
const entry = fsState.entries.get(absInMock(p));
if (!entry) {
@@ -142,8 +136,7 @@ vi.mock("node:fs", async (importOriginal) => {
},
access: async (p: string) => {
if (!isFixtureInMock(p)) {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
return await (actual.promises.access as any)(p);
return await actual.promises.access(p);
}
const entry = fsState.entries.get(absInMock(p));
if (!entry) {
@@ -152,8 +145,7 @@ vi.mock("node:fs", async (importOriginal) => {
},
unlink: async (p: string) => {
if (!isFixtureInMock(p)) {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
return await (actual.promises.unlink as any)(p);
return await actual.promises.unlink(p);
}
fsState.entries.delete(absInMock(p));
},
@@ -169,15 +161,13 @@ vi.mock("node:fs/promises", async (importOriginal) => {
...actual,
mkdir: async (p: string, _opts?: unknown) => {
if (!isFixturePath(p)) {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
return await (actual.mkdir as any)(p, { recursive: true });
return await actual.mkdir(p, { recursive: true });
}
ensureDir(p);
},
writeFile: async (p: string, data: string, _enc?: unknown) => {
if (!isFixturePath(p)) {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
return await (actual.writeFile as any)(p, data, "utf-8");
return await actual.writeFile(p, data, "utf-8");
}
setFile(p, data);
},

View File

@@ -34,10 +34,9 @@ vi.mock("node:fs", async (importOriginal) => {
...actual,
existsSync: (p: string) =>
isFixturePath(p) ? state.entries.has(absInMock(p)) : actual.existsSync(p),
readFileSync: (p: string, encoding?: unknown) => {
readFileSync: (p: string, encoding?: BufferEncoding) => {
if (!isFixturePath(p)) {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
return actual.readFileSync(p as any, encoding as any) as unknown;
return actual.readFileSync(p, encoding);
}
const entry = readFixtureEntry(p);
if (entry?.kind === "file") {
@@ -47,8 +46,7 @@ vi.mock("node:fs", async (importOriginal) => {
},
statSync: (p: string) => {
if (!isFixturePath(p)) {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
return actual.statSync(p as any) as unknown;
return actual.statSync(p);
}
const entry = readFixtureEntry(p);
if (entry?.kind === "file") {

View File

@@ -31,10 +31,9 @@ vi.mock("node:fs", async (importOriginal) => {
...actual,
existsSync: (p: string) =>
isFixturePath(p) ? state.entries.has(abs(p)) : actual.existsSync(p),
readFileSync: (p: string, encoding?: unknown) => {
readFileSync: (p: string, encoding?: BufferEncoding) => {
if (!isFixturePath(p)) {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
return actual.readFileSync(p as any, encoding as any) as unknown;
return actual.readFileSync(p, encoding);
}
const entry = state.entries.get(abs(p));
if (!entry || entry.kind !== "file") {
@@ -44,8 +43,7 @@ vi.mock("node:fs", async (importOriginal) => {
},
statSync: (p: string) => {
if (!isFixturePath(p)) {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
return actual.statSync(p as any) as unknown;
return actual.statSync(p);
}
const entry = state.entries.get(abs(p));
if (!entry) {
@@ -74,10 +72,9 @@ vi.mock("node:fs/promises", async (importOriginal) => {
const actual = await importOriginal<typeof import("node:fs/promises")>();
const wrapped = {
...actual,
readFile: async (p: string, encoding?: unknown) => {
readFile: async (p: string, encoding?: BufferEncoding) => {
if (!isFixturePath(p)) {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
return (await actual.readFile(p as any, encoding as any)) as unknown;
return await actual.readFile(p, encoding);
}
const entry = state.entries.get(abs(p));
if (!entry || entry.kind !== "file") {

View File

@@ -17,9 +17,9 @@ import { VERSION } from "../version.js";
import { ensureNodeHostConfig, saveNodeHostConfig, type NodeHostGatewayConfig } from "./config.js";
import {
coerceNodeInvokePayload,
handleInvoke,
type SkillBinsProvider,
buildNodeInvokeResultParams,
handleInvoke,
} from "./invoke.js";
export { buildNodeInvokeResultParams };
@@ -35,6 +35,10 @@ type NodeHostRunOptions = {
const DEFAULT_NODE_PATH = "/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin";
function writeStderrLine(message: string): void {
process.stderr.write(`${message}\n`);
}
function resolveExecutablePathFromEnv(bin: string, pathEnv: string): string | null {
if (bin.includes("/") || bin.includes("\\")) {
return null;
@@ -208,12 +212,10 @@ export async function runNodeHost(opts: NodeHostRunOptions): Promise<void> {
},
onConnectError: (err) => {
// keep retrying (handled by GatewayClient)
// eslint-disable-next-line no-console
console.error(`node host gateway connect failed: ${err.message}`);
writeStderrLine(`node host gateway connect failed: ${err.message}`);
},
onClose: (code, reason) => {
// eslint-disable-next-line no-console
console.error(`node host gateway closed (${code}): ${reason}`);
writeStderrLine(`node host gateway closed (${code}): ${reason}`);
},
});