chore(lint): upgrade oxlint to 1.79.0 (#132682)

* chore(lint): upgrade oxlint to 1.79.0

* test(ui): make incremental markdown scan proof deterministic
This commit is contained in:
Peter Steinberger 2026-08-29 10:07:08 -07:00 committed by GitHub
parent 8b1da5547a
commit 2ac40dedbe
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
31 changed files with 333 additions and 192 deletions

View file

@ -394,6 +394,20 @@
"rules": {
"max-lines": ["error", { "max": 1200, "skipBlankLines": true, "skipComments": true }]
}
},
{
"files": ["**/*.{ts,tsx,mts,cts}"],
"rules": {
// tsgo owns TypeScript type/value namespace validity; no-var still rejects var declarations.
"eslint/no-redeclare": "off"
}
},
{
"files": ["extensions/qa-lab/src/web-runtime.ts"],
"rules": {
// Scenario scripts need page-global declarations, unlike Playwright's expression evaluation.
"eslint/no-eval": ["error", { "allowIndirect": true }]
}
}
]
}

View file

@ -442,6 +442,18 @@ ratchet-down when cleanup lowers the real count.
## Local equivalents
Oxlint keeps `eslint/no-redeclare` enabled for JavaScript. For `.ts`, `.tsx`,
`.mts`, and `.cts`, `tsgo` owns declaration validity, including intentional
type/value pairs with the same public name. `eslint/no-var` remains enabled
for all source formats; the compiler does not reject every `var` redeclaration.
`eslint/no-eval` rejects direct and indirect evaluation by default. Only
`extensions/qa-lab/src/web-runtime.ts` allows indirect evaluation, because QA
scenario scripts need page-global declaration semantics that Playwright's
expression evaluation does not preserve. Direct evaluation remains an error
there. Tests that execute emitted browser scripts use isolated `node:vm`
contexts instead of process-global evaluation.
```bash
pnpm changed:lanes # inspect the local changed-lane classifier for origin/main...HEAD
pnpm check:changed # smart local check gate: changed formatting/typecheck/lint/guards by boundary lane

View file

@ -135,7 +135,7 @@ const providerMonitorTestMocks: ProviderMonitorTestMocks = vi.hoisted(() => {
monitorLifecycleMock: vi.fn(async (params: { threadBindings: { stop: () => void } }) => {
params.threadBindings.stop();
}),
resolveDiscordAccountMock: vi.fn((_) => ({
resolveDiscordAccountMock: vi.fn((_params) => ({
accountId: "default",
token: "cfg-token",
config: baseDiscordAccountConfig(),

View file

@ -1,3 +1,4 @@
import { runInNewContext } from "node:vm";
import { Command } from "commander";
// Google Meet tests cover index.create plugin behavior.
import { createRequireRecord } from "openclaw/plugin-sdk/test-fixtures";
@ -94,8 +95,6 @@ async function runCreateMeetBrowserScript(params: { buttonText: string }) {
},
querySelectorAll: (selector: string) => (selector === "button" ? [button] : []),
};
vi.stubGlobal("document", document);
vi.stubGlobal("location", location);
type BrowserScriptResult = {
meetingUri?: string;
manualAction?: { reason: string; message: string };
@ -115,8 +114,10 @@ async function runCreateMeetBrowserScript(params: { buttonText: string }) {
if (typeof body.fn !== "string") {
throw new Error("expected browser create script");
}
const fn = (0, eval)(`(${body.fn})`) as () => Promise<BrowserScriptResult>;
scriptResult = await fn();
scriptResult = await (runInNewContext(`(${body.fn})()`, {
document,
location,
}) as Promise<BrowserScriptResult>);
return {
manualAction: {
reason: "meet-permission-required",

View file

@ -35,9 +35,9 @@ const loadConfigMock = vi.fn(() => ({}));
const withResolvedRuntimeMatrixClientMock = vi.hoisted(() => vi.fn());
const getImageMetadataMock = vi.fn().mockResolvedValue(null);
const resizeToJpegMock = vi.fn();
const mediaKindFromMimeMock = vi.fn((_: string | null | undefined) => "image");
const mediaKindFromMimeMock = vi.fn((_mime: string | null | undefined) => "image");
const isVoiceCompatibleAudioMock = vi.fn(
(_: { contentType?: string | null; fileName?: string | null }) => false,
(_options: { contentType?: string | null; fileName?: string | null }) => false,
);
const resolveTextChunkLimitMock = vi.fn<
(cfg: unknown, channel: unknown, accountId?: unknown) => number

View file

@ -24,7 +24,7 @@ const resolveOwningPluginIdsForProviderMock = vi.hoisted(() =>
vi.fn<ResolveOwningPluginIdsForProvider>(() => undefined),
);
const resolveCatalogHookProviderPluginIdsMock = vi.hoisted(() =>
vi.fn<ResolveCatalogHookProviderPluginIds>((_) => [] as string[]),
vi.fn<ResolveCatalogHookProviderPluginIds>((_params) => [] as string[]),
);
vi.mock("openclaw/plugin-sdk/provider-catalog-runtime", async () => {

View file

@ -125,8 +125,6 @@ export class QaGatewayChildLifecycle {
? new QaSuiteInfraError(error.code, message, { cause: error })
: new Error(message, { cause: error });
if (result.errors.length) {
// Oxlint 1.78 checks cause at argument 2; AggregateError takes it at 3.
// oxlint-disable-next-line preserve-caught-error
throw new AggregateError(
[primary, ...result.errors],
"qa gateway startup and cleanup failed",

View file

@ -88,10 +88,11 @@ function makeAuthorizeCtx(params?: {
},
isChannelAllowed: vi.fn(params?.isChannelAllowed ?? (() => true)),
resolveUserName: vi.fn(
params?.resolveUserName ?? ((_) => Promise.resolve({ name: undefined })),
params?.resolveUserName ?? ((_userId) => Promise.resolve({ name: undefined })),
),
resolveChannelName: vi.fn(
params?.resolveChannelName ?? ((_) => Promise.resolve({ name: "general", type: "channel" })),
params?.resolveChannelName ??
((_channelId) => Promise.resolve({ name: "general", type: "channel" })),
),
} as unknown as SlackMonitorContext;
}

View file

@ -12,7 +12,7 @@ function makeBaseReq(
req.url = opts.url ?? "/webhook/synology";
req.socket = { remoteAddress: "127.0.0.1" } as unknown as IncomingMessage["socket"];
req.destroyed = false;
req.destroy = ((_: Error | undefined) => {
req.destroy = ((_error: Error | undefined) => {
if (req.destroyed) {
return req;
}

View file

@ -231,7 +231,7 @@ export async function sendPayload(
isSingleUseReplyToMode(turn.replyToMode) &&
!targetsDifferentMessage;
const deliverablePayload = consumedSingleUseReply
? (({ replyToId: _, replyToTag: _tag, replyToCurrent: _current, ...rest }) => rest)(
? (({ replyToId: _replyToId, replyToTag: _tag, replyToCurrent: _current, ...rest }) => rest)(
targetedPayload,
)
: targetedPayload;

View file

@ -191,10 +191,14 @@ describe("registerTelegramNativeCommands", () => {
expect(registered).toEqual([
{ command: "custom_two", description: "Custom two unchanged" },
{ command: "custom_one", description: "Custom one unchanged" },
...native.filter((command) => !command.isAlias).map(({ isAlias: _, ...command }) => command),
...native
.filter((command) => !command.isAlias)
.map(({ isAlias: _isAlias, ...command }) => command),
{ command: "alpha", description: "Alpha unchanged" },
{ command: "zeta", description: "Zeta unchanged" },
...native.filter((command) => command.isAlias).map(({ isAlias: _, ...command }) => command),
...native
.filter((command) => command.isAlias)
.map(({ isAlias: _isAlias, ...command }) => command),
]);
});

View file

@ -134,8 +134,12 @@ describe("createTelegramBot command menu", () => {
expect(registered).toStrictEqual([
{ command: "custom_backup", description: "Git backup" },
{ command: "custom_generate", description: "Create an image" },
...native.filter((command) => !command.isAlias).map(({ isAlias: _, ...command }) => command),
...native.filter((command) => command.isAlias).map(({ isAlias: _, ...command }) => command),
...native
.filter((command) => !command.isAlias)
.map(({ isAlias: _isAlias, ...command }) => command),
...native
.filter((command) => command.isAlias)
.map(({ isAlias: _isAlias, ...command }) => command),
]);
});
@ -193,8 +197,12 @@ describe("createTelegramBot command menu", () => {
}
expect(registered).toStrictEqual([
{ command: "custom_backup", description: "Git backup" },
...native.filter((command) => !command.isAlias).map(({ isAlias: _, ...command }) => command),
...native.filter((command) => command.isAlias).map(({ isAlias: _, ...command }) => command),
...native
.filter((command) => !command.isAlias)
.map(({ isAlias: _isAlias, ...command }) => command),
...native
.filter((command) => command.isAlias)
.map(({ isAlias: _isAlias, ...command }) => command),
]);
expect(registered.find((command) => command.command === "status")).toEqual({
command: nativeStatus.command,

View file

@ -339,7 +339,7 @@ const grammySpies = vi.hoisted(() => ({
stopSpy: vi.fn(),
commandSpy: vi.fn(),
botCtorSpy: vi.fn(
(_: string, __?: { client?: { fetch?: typeof fetch }; botInfo?: unknown }) => undefined,
(_token: string, __?: { client?: { fetch?: typeof fetch }; botInfo?: unknown }) => undefined,
),
answerCallbackQuerySpy: vi.fn(async () => undefined) as AnyAsyncMock,
sendChatActionSpy: vi.fn(),

View file

@ -2145,7 +2145,7 @@
"markdown-it": "15.0.0",
"marked": "18.0.10",
"oxfmt": "0.60.0",
"oxlint": "1.78.0",
"oxlint": "1.79.0",
"oxlint-tsgolint": "7.0.2001",
"playwright": "1.62.1",
"postcss": "8.5.26",

162
pnpm-lock.yaml generated
View file

@ -451,8 +451,8 @@ importers:
specifier: 0.60.0
version: 0.60.0
oxlint:
specifier: 1.78.0
version: 1.78.0(oxlint-tsgolint@7.0.2001)
specifier: 1.79.0
version: 1.79.0(oxlint-tsgolint@7.0.2001)
oxlint-tsgolint:
specifier: 7.0.2001
version: 7.0.2001
@ -4621,124 +4621,124 @@ packages:
cpu: [x64]
os: [win32]
'@oxlint/binding-android-arm-eabi@1.78.0':
resolution: {integrity: sha512-Bu819lmAfZMUHErrpe0cEWj3iaefuUODHSU8+UbXy67V/r7/7f4K3FL0NmbD85E+wiFLDYuhP8Zlv0XnVeXshw==}
'@oxlint/binding-android-arm-eabi@1.79.0':
resolution: {integrity: sha512-TebFaaMklO/RXzTv7PucaCq9l3X6D1gA+C8H6K4njtjFOV+zWE9MKLpulcJZN9bzytbUbQIY0mZuz12nQ5Kv4Q==}
engines: {node: ^20.19.0 || >=22.12.0}
cpu: [arm]
os: [android]
'@oxlint/binding-android-arm64@1.78.0':
resolution: {integrity: sha512-CDfxZgB61B7buRdY2FJoAYYPPXCZ1EoC1LKscnC5dg3kjobdxiconvAvvN1BmHyW4PyFT3jRLDag/BY/roSNBQ==}
'@oxlint/binding-android-arm64@1.79.0':
resolution: {integrity: sha512-KqqnOtAVgNsPPF0YSodkFZA1O80jcKoCZCTu3bgsszxA+MrMP9TLzfXitKjEj1FmrPprKDMdRDMmY3weESO9sg==}
engines: {node: ^20.19.0 || >=22.12.0}
cpu: [arm64]
os: [android]
'@oxlint/binding-darwin-arm64@1.78.0':
resolution: {integrity: sha512-2Y2U9Ahrz+OO0Ej88f9SJYq51/jUBp1Mc7iZu0ukrbeeZ3gpRGfzIFnoqfHDY96xr0GEfNrPUBFEy0nN5aD7HA==}
'@oxlint/binding-darwin-arm64@1.79.0':
resolution: {integrity: sha512-BVC2nsMzqQzRDPc5RhixkZ+m1p7iH4bxRRvqkbwDXX0PlQKm1BPy8J8cRjnAFafOq2QzI+BfO3vE8w2GZ3CBag==}
engines: {node: ^20.19.0 || >=22.12.0}
cpu: [arm64]
os: [darwin]
'@oxlint/binding-darwin-x64@1.78.0':
resolution: {integrity: sha512-rpych6eJq6m9jDRypTEaPD1xysaEW5h9+xuxhGK/QhOg+/xaqPZrCrTNoIl/f3nEjuJeCEmstNDlrE9rJi/3/g==}
'@oxlint/binding-darwin-x64@1.79.0':
resolution: {integrity: sha512-p6Lm+snmhGuLKL1+CpCV8L6ijkE/qJzK2H2jG9+eKJT0n31RbY4FLsdhexekgP3bLpw4Kgde+9DZuDZQ4yIInA==}
engines: {node: ^20.19.0 || >=22.12.0}
cpu: [x64]
os: [darwin]
'@oxlint/binding-freebsd-x64@1.78.0':
resolution: {integrity: sha512-IcMGrQT3QizkOESUJd5et+rOhVqSkNDfNik1cvrKDqIbzqx9KMtRswpFgkCuNTSwylCFLKhGUu8KmqY1ZnC0Dg==}
'@oxlint/binding-freebsd-x64@1.79.0':
resolution: {integrity: sha512-qDMm0dXZnoHyRqSL4N4xUq82T4sqK5cbKSjvd/dF/YbMUXc2R1wEPf+vmA5S0qUmi0nwXfNbjXBtZaIqzQLIMg==}
engines: {node: ^20.19.0 || >=22.12.0}
cpu: [x64]
os: [freebsd]
'@oxlint/binding-linux-arm-gnueabihf@1.78.0':
resolution: {integrity: sha512-/uLdoJ0IXE6vo/0f0LKjinQAp+re+VMaCWaNT8ENIv2EOCkSsc8SGaflXAuW0Jua2dq5+GLVWm1NQK7P3UFSNQ==}
'@oxlint/binding-linux-arm-gnueabihf@1.79.0':
resolution: {integrity: sha512-2od7s0nuKPzqyUZAWk9KkCyGg7eI9dwFPZg+20lB15fKFkVZ0c9ZFxqPfiBAyDTlTkh9stPI0t+JlPCqMbItVA==}
engines: {node: ^20.19.0 || >=22.12.0}
cpu: [arm]
os: [linux]
'@oxlint/binding-linux-arm-musleabihf@1.78.0':
resolution: {integrity: sha512-7xi4Wb/O8NRJhLoUXmDJMUVpNYvB5kefdhFU1Jb8rtae4QoXlTiLwI14X4YvAXVZLNZChP8m5qO9SQAlWQTbkQ==}
'@oxlint/binding-linux-arm-musleabihf@1.79.0':
resolution: {integrity: sha512-ZOQUjkzDnvlhSE3+tWC3YXx94MMl+sYMlwH+u1+YGApGHOJP/YAc8ZBRFOXZ6eOBmxtXAWuS/fBcdZr8qqNO1A==}
engines: {node: ^20.19.0 || >=22.12.0}
cpu: [arm]
os: [linux]
'@oxlint/binding-linux-arm64-gnu@1.78.0':
resolution: {integrity: sha512-4hFW0+fVXa3OIh1Y4A5SPkmvI4wuuBSrCVKzOyE7PTjhc7yEqZ1pmvEEeS5Lj/MaqvegFxXyF33N+6jkehxdyg==}
'@oxlint/binding-linux-arm64-gnu@1.79.0':
resolution: {integrity: sha512-lu158FR4nGqGeRS3BQvtG85wRgU/Fy4MD5Cxp1hzJXizGiLo6u2742wJSCDKh8cFcZntvX7fcxlq4mMmfryH1g==}
engines: {node: ^20.19.0 || >=22.12.0}
cpu: [arm64]
os: [linux]
libc: [glibc]
'@oxlint/binding-linux-arm64-musl@1.78.0':
resolution: {integrity: sha512-oC0mvsgBJjlMijSDEhx9KuvR9zYeHXceA9MjbuXB1F8NSR78Yj2unOBrstEvTVaq+pko+kuue6DajC00eqvTdg==}
'@oxlint/binding-linux-arm64-musl@1.79.0':
resolution: {integrity: sha512-mbpKQeE2aflTjddaHK7MP8KP/OFbUM++lt5M635ENM8IyIdK0jm2t9pb+2v9mVVIvhF6TqA4l7F79Pll1mi+uw==}
engines: {node: ^20.19.0 || >=22.12.0}
cpu: [arm64]
os: [linux]
libc: [musl]
'@oxlint/binding-linux-ppc64-gnu@1.78.0':
resolution: {integrity: sha512-XAllT5SUZS+ohjuZ3/5S0cwe0r7eboiuigeStCZ5DXRYx/2KVM2UvQXvAfyzXEimtQjAB7cDQ2YxDe2Zl2WNQQ==}
'@oxlint/binding-linux-ppc64-gnu@1.79.0':
resolution: {integrity: sha512-WpGNua7gaxaHnpSDeog2ji8IDHn/QLPl9LPzwkR/FvVv58vT5BcXjRXnU+wbu3N75cpeha8CdC7ho/U2OIsB4g==}
engines: {node: ^20.19.0 || >=22.12.0}
cpu: [ppc64]
os: [linux]
libc: [glibc]
'@oxlint/binding-linux-riscv64-gnu@1.78.0':
resolution: {integrity: sha512-trucMER/0QtecoXvc1y/UVqE3kwJipDwrx4oHfj+nNm3dq2zjP44WT0CfHNDPM3G1DXIkx/gY6lAD21NSCZVhA==}
'@oxlint/binding-linux-riscv64-gnu@1.79.0':
resolution: {integrity: sha512-tK1E93A5LVzISg4ngpKJnfTs7EqtIUceGI7MQ4GyDjJiLi8wPCkEyKlj2xkyKWZ1yzkDJyLHTBJ5/iFWRdnJvg==}
engines: {node: ^20.19.0 || >=22.12.0}
cpu: [riscv64]
os: [linux]
libc: [glibc]
'@oxlint/binding-linux-riscv64-musl@1.78.0':
resolution: {integrity: sha512-cm3O4F/HQbdzOUX5mKHqG5KDL6E5w0pnlZ+fbBy2rmLryPOowkuLagFHTopQsEIpjcaZoPOrL+BmmAytAG9HFg==}
'@oxlint/binding-linux-riscv64-musl@1.79.0':
resolution: {integrity: sha512-qhQvUIrngXivA2A9pQ+xPCychztn/5qUv7yS3gDwXv3w7Rag+eTeeXWmRyx+t7XsW5x6LuY/8AsTq36UgFIblg==}
engines: {node: ^20.19.0 || >=22.12.0}
cpu: [riscv64]
os: [linux]
libc: [musl]
'@oxlint/binding-linux-s390x-gnu@1.78.0':
resolution: {integrity: sha512-33wRf6HqGNsybJ3qX4cGaQN2ODPxNmc1rMa0mrTmx3eFq1VzOnvQooi9bIGVYakW8a/wmqVx1mgsUm8R2xfTiw==}
'@oxlint/binding-linux-s390x-gnu@1.79.0':
resolution: {integrity: sha512-sv6AaVgU/eE6u+6WFiQVDcPPwTxP6IJMSB9k701W2r/r6Tx465e8vPvVyRxquNH4Vy6KwRNu90mVbxXJN8+5gg==}
engines: {node: ^20.19.0 || >=22.12.0}
cpu: [s390x]
os: [linux]
libc: [glibc]
'@oxlint/binding-linux-x64-gnu@1.78.0':
resolution: {integrity: sha512-rRdISSYegj6VganMZ9tjRjijowfHJ09IZU01i0toBAqr6n5LEtwHq2IeS4FjW2RoskOHlb6efB26H5izYb3GEQ==}
'@oxlint/binding-linux-x64-gnu@1.79.0':
resolution: {integrity: sha512-iFZL02deziHslb3jEX9KdqlAkYoo4fGyotchKDzdfK1f5mxlIBeiQeHhvK3iFpuEJSB4ma/qeFn9oxPiwnhUPQ==}
engines: {node: ^20.19.0 || >=22.12.0}
cpu: [x64]
os: [linux]
libc: [glibc]
'@oxlint/binding-linux-x64-musl@1.78.0':
resolution: {integrity: sha512-GmsP4rW0xTL6u5CVdcDsaN5Fbc7hBc382Wmar1kttbnwSEviM+rSINKOMQ+UQ6iH+AGwC+8gaAiwu134Tgh6Lg==}
'@oxlint/binding-linux-x64-musl@1.79.0':
resolution: {integrity: sha512-3DtZR2raqObnh7wXZoFYFd0Fw7skBvcb3f7A+/lkEiDuh8hrE6vv9b/62Qxao1a9/OeHLw/FcXlXzgsW9wTRFg==}
engines: {node: ^20.19.0 || >=22.12.0}
cpu: [x64]
os: [linux]
libc: [musl]
'@oxlint/binding-openharmony-arm64@1.78.0':
resolution: {integrity: sha512-sy9yeYuADc8a+n4TLBayzMCZiHPW78DcIFVpOXTmdKHWQeM9xe5uzkqIIZmi326D5hY9XVwacipEB1p7tQjPAg==}
'@oxlint/binding-openharmony-arm64@1.79.0':
resolution: {integrity: sha512-Oatt4GuA1WJkqzk2ozx4HrWROOi7opV3AKDw/U8qDIqeTqzsjn5K2x3REJMNjU3/KU/Bkq96Zi3CknaiDTaC/Q==}
engines: {node: ^20.19.0 || >=22.12.0}
cpu: [arm64]
os: [openharmony]
'@oxlint/binding-win32-arm64-msvc@1.78.0':
resolution: {integrity: sha512-rjc2hF1KfMi8fZj1X/m3AmnHbdsF3rL0v6KQg0Uc880Yb2khjz+3U14sfdZ7jWTpRnN1m1NQa/TT7uU9lJWPrA==}
'@oxlint/binding-win32-arm64-msvc@1.79.0':
resolution: {integrity: sha512-NAgZr9Qp8nIA9rpo0JEvwiabTF/2UVqBNnupBG9X4kxXcQoScJUTi+qHhvabb9s/thgj5wQ4XcIaJvb+ZMgoKw==}
engines: {node: ^20.19.0 || >=22.12.0}
cpu: [arm64]
os: [win32]
'@oxlint/binding-win32-ia32-msvc@1.78.0':
resolution: {integrity: sha512-zcuXFVrEFHIafRfkCQT8w/Xe41o07ozl/vwHq7p94vB29xVzsB0sZGYORU1jhcYKv3Lr0J3HbJ2T4fHH5rWmvA==}
'@oxlint/binding-win32-ia32-msvc@1.79.0':
resolution: {integrity: sha512-+KyXjIvcpaXmWW/j9NNY5yWjrIVxaX18VyIheQy3jwc2GSYgpCr7MGI/HxIGQ/shAL5IWEKbhsqoMpAO5Stiog==}
engines: {node: ^20.19.0 || >=22.12.0}
cpu: [ia32]
os: [win32]
'@oxlint/binding-win32-x64-msvc@1.78.0':
resolution: {integrity: sha512-Sb5ocmLSuYeOuXd+CFOToGKp/gjXUEWDnvIGwhnh8aq8wY4TMmEnKnvbogSW7RdMZv77JSARduS7/gv+khYEjA==}
'@oxlint/binding-win32-x64-msvc@1.79.0':
resolution: {integrity: sha512-mEelcCMMBS57sIXh2veGMNy+pQwuGtcMxHxGIZWQ5Ba9pJ5jCCUFOZB9E2JhBaxGsURe+WGe0zJp4RVre52gpQ==}
engines: {node: ^20.19.0 || >=22.12.0}
cpu: [x64]
os: [win32]
@ -8095,8 +8095,8 @@ packages:
resolution: {integrity: sha512-KjK/XLcXr1DSyonKhsuFqJRiuKqcyG9j3LJ8nkOsrLzGvodBPqzHOKauy10asLMDI0sUpvb+1sxlzff3udZvfg==}
hasBin: true
oxlint@1.78.0:
resolution: {integrity: sha512-QgQePuxIqKOzo1KSjG2EnITEeWvWnKAm77eq8nrMtf6AGoA+zyGc4PFYtDNJSD25g/ibOwfQ851hZ4/SPkMVoA==}
oxlint@1.79.0:
resolution: {integrity: sha512-hVJ9hq9m2unPS+Of4eJJgCPdIeCC+3DHEUX3tkmrPJr3OK2hz7PhXwgC+ZP71ZcYu8cCDEtQrqLxWNvxBppBVg==}
engines: {node: ^20.19.0 || >=22.12.0}
hasBin: true
peerDependencies:
@ -11841,61 +11841,61 @@ snapshots:
'@oxlint-tsgolint/win32-x64@7.0.2001':
optional: true
'@oxlint/binding-android-arm-eabi@1.78.0':
'@oxlint/binding-android-arm-eabi@1.79.0':
optional: true
'@oxlint/binding-android-arm64@1.78.0':
'@oxlint/binding-android-arm64@1.79.0':
optional: true
'@oxlint/binding-darwin-arm64@1.78.0':
'@oxlint/binding-darwin-arm64@1.79.0':
optional: true
'@oxlint/binding-darwin-x64@1.78.0':
'@oxlint/binding-darwin-x64@1.79.0':
optional: true
'@oxlint/binding-freebsd-x64@1.78.0':
'@oxlint/binding-freebsd-x64@1.79.0':
optional: true
'@oxlint/binding-linux-arm-gnueabihf@1.78.0':
'@oxlint/binding-linux-arm-gnueabihf@1.79.0':
optional: true
'@oxlint/binding-linux-arm-musleabihf@1.78.0':
'@oxlint/binding-linux-arm-musleabihf@1.79.0':
optional: true
'@oxlint/binding-linux-arm64-gnu@1.78.0':
'@oxlint/binding-linux-arm64-gnu@1.79.0':
optional: true
'@oxlint/binding-linux-arm64-musl@1.78.0':
'@oxlint/binding-linux-arm64-musl@1.79.0':
optional: true
'@oxlint/binding-linux-ppc64-gnu@1.78.0':
'@oxlint/binding-linux-ppc64-gnu@1.79.0':
optional: true
'@oxlint/binding-linux-riscv64-gnu@1.78.0':
'@oxlint/binding-linux-riscv64-gnu@1.79.0':
optional: true
'@oxlint/binding-linux-riscv64-musl@1.78.0':
'@oxlint/binding-linux-riscv64-musl@1.79.0':
optional: true
'@oxlint/binding-linux-s390x-gnu@1.78.0':
'@oxlint/binding-linux-s390x-gnu@1.79.0':
optional: true
'@oxlint/binding-linux-x64-gnu@1.78.0':
'@oxlint/binding-linux-x64-gnu@1.79.0':
optional: true
'@oxlint/binding-linux-x64-musl@1.78.0':
'@oxlint/binding-linux-x64-musl@1.79.0':
optional: true
'@oxlint/binding-openharmony-arm64@1.78.0':
'@oxlint/binding-openharmony-arm64@1.79.0':
optional: true
'@oxlint/binding-win32-arm64-msvc@1.78.0':
'@oxlint/binding-win32-arm64-msvc@1.79.0':
optional: true
'@oxlint/binding-win32-ia32-msvc@1.78.0':
'@oxlint/binding-win32-ia32-msvc@1.79.0':
optional: true
'@oxlint/binding-win32-x64-msvc@1.78.0':
'@oxlint/binding-win32-x64-msvc@1.79.0':
optional: true
'@panzoom/panzoom@4.6.2': {}
@ -15568,27 +15568,27 @@ snapshots:
'@oxlint-tsgolint/win32-arm64': 7.0.2001
'@oxlint-tsgolint/win32-x64': 7.0.2001
oxlint@1.78.0(oxlint-tsgolint@7.0.2001):
oxlint@1.79.0(oxlint-tsgolint@7.0.2001):
optionalDependencies:
'@oxlint/binding-android-arm-eabi': 1.78.0
'@oxlint/binding-android-arm64': 1.78.0
'@oxlint/binding-darwin-arm64': 1.78.0
'@oxlint/binding-darwin-x64': 1.78.0
'@oxlint/binding-freebsd-x64': 1.78.0
'@oxlint/binding-linux-arm-gnueabihf': 1.78.0
'@oxlint/binding-linux-arm-musleabihf': 1.78.0
'@oxlint/binding-linux-arm64-gnu': 1.78.0
'@oxlint/binding-linux-arm64-musl': 1.78.0
'@oxlint/binding-linux-ppc64-gnu': 1.78.0
'@oxlint/binding-linux-riscv64-gnu': 1.78.0
'@oxlint/binding-linux-riscv64-musl': 1.78.0
'@oxlint/binding-linux-s390x-gnu': 1.78.0
'@oxlint/binding-linux-x64-gnu': 1.78.0
'@oxlint/binding-linux-x64-musl': 1.78.0
'@oxlint/binding-openharmony-arm64': 1.78.0
'@oxlint/binding-win32-arm64-msvc': 1.78.0
'@oxlint/binding-win32-ia32-msvc': 1.78.0
'@oxlint/binding-win32-x64-msvc': 1.78.0
'@oxlint/binding-android-arm-eabi': 1.79.0
'@oxlint/binding-android-arm64': 1.79.0
'@oxlint/binding-darwin-arm64': 1.79.0
'@oxlint/binding-darwin-x64': 1.79.0
'@oxlint/binding-freebsd-x64': 1.79.0
'@oxlint/binding-linux-arm-gnueabihf': 1.79.0
'@oxlint/binding-linux-arm-musleabihf': 1.79.0
'@oxlint/binding-linux-arm64-gnu': 1.79.0
'@oxlint/binding-linux-arm64-musl': 1.79.0
'@oxlint/binding-linux-ppc64-gnu': 1.79.0
'@oxlint/binding-linux-riscv64-gnu': 1.79.0
'@oxlint/binding-linux-riscv64-musl': 1.79.0
'@oxlint/binding-linux-s390x-gnu': 1.79.0
'@oxlint/binding-linux-x64-gnu': 1.79.0
'@oxlint/binding-linux-x64-musl': 1.79.0
'@oxlint/binding-openharmony-arm64': 1.79.0
'@oxlint/binding-win32-arm64-msvc': 1.79.0
'@oxlint/binding-win32-ia32-msvc': 1.79.0
'@oxlint/binding-win32-x64-msvc': 1.79.0
oxlint-tsgolint: 7.0.2001
p-finally@1.0.0: {}

View file

@ -29,7 +29,7 @@ const beforeToolCallMocks = vi.hoisted(() => ({
this.reason = reason;
}
},
consumeAdjustedParamsForToolCall: vi.fn((_: string): unknown => undefined),
consumeAdjustedParamsForToolCall: vi.fn((_toolCallId: string): unknown => undefined),
recordAdjustedParamsForToolCall: vi.fn(),
recordStructuredReplayTrustForToolCall: vi.fn(),
isToolWrappedWithBeforeToolCallHook: vi.fn(() => false),

View file

@ -10,7 +10,7 @@ import { toToolDefinitions } from "./agent-tool-definition-adapter.js";
const hookMocks = vi.hoisted(() => ({
runner: {
hasHooks: vi.fn((_: string) => true),
hasHooks: vi.fn((_hookName: string) => true),
runAfterToolCall: vi.fn(async () => {}),
},
BeforeToolCallBlockedError: class BeforeToolCallBlockedError extends Error {
@ -23,7 +23,7 @@ const hookMocks = vi.hoisted(() => ({
}
},
isToolWrappedWithBeforeToolCallHook: vi.fn(() => false),
consumeAdjustedParamsForToolCall: vi.fn((_: string) => undefined as unknown),
consumeAdjustedParamsForToolCall: vi.fn((_toolCallId: string) => undefined as unknown),
recordAdjustedParamsForToolCall: vi.fn(),
recordStructuredReplayTrustForToolCall: vi.fn(),
runBeforeToolCallHook: vi.fn(async ({ params }: { params: unknown }) => ({

View file

@ -292,7 +292,7 @@ export function buildAuthHealthSummary(params: {
: null;
const profiles = Object.entries(params.store.profiles)
.filter(([_, cred]) =>
.filter(([, cred]) =>
providerFilter ? providerFilter.has(normalizeProviderId(cred.provider)) : true,
)
.map(([profileId, credential]) =>

View file

@ -30,7 +30,7 @@ const resolveQueueSettingsMock = vi.hoisted(() =>
);
const listTasksForRelatedSessionKeyForOwnerMock = vi.hoisted(() =>
vi.fn(
(_: { relatedSessionKey: string; callerOwnerKey: string }) =>
(_params: { relatedSessionKey: string; callerOwnerKey: string }) =>
[] as Array<Record<string, unknown>>,
),
);

View file

@ -59,12 +59,12 @@ const state = vi.hoisted(() => ({
runEmbeddedAgentEntryMock: vi.fn(),
runCliAgentMock: vi.fn(),
runWithModelFallbackMock: vi.fn(),
isCliProviderMock: vi.fn((_: unknown) => false),
isInternalMessageChannelMock: vi.fn((_: unknown) => false),
isCliProviderMock: vi.fn((_provider: unknown) => false),
isInternalMessageChannelMock: vi.fn((_channel: unknown) => false),
createBlockReplyDeliveryHandlerMock: vi.fn(),
isCompactionFailureErrorMock: vi.fn((_: string | undefined) => false),
isContextOverflowErrorMock: vi.fn((_: string | undefined) => false),
isLikelyContextOverflowErrorMock: vi.fn((_: string | undefined) => false),
isCompactionFailureErrorMock: vi.fn((_message: string | undefined) => false),
isContextOverflowErrorMock: vi.fn((_message: string | undefined) => false),
isLikelyContextOverflowErrorMock: vi.fn((_message: string | undefined) => false),
updateSessionStoreMock: vi.fn(),
resolveCurrentTurnImagesMock: vi.fn(),
peekSessionMcpRuntimeMock: vi.fn(),

View file

@ -82,7 +82,7 @@ describe("doctor command", () => {
await doctorCommand(createDoctorRuntime(), { nonInteractive: true });
const noteTitles = terminalNoteMock.mock.calls.map(([_, title]) => title);
const noteTitles = terminalNoteMock.mock.calls.map(([, title]) => title);
expect(noteTitles).not.toContain("Extra workspace");
homedirSpy.mockRestore();

View file

@ -58,7 +58,7 @@ type CapturingTestRuntime = {
export function createTestRuntime(): TestRuntime {
const log = vi.fn() as MockFn<RuntimeEnv["log"]>;
const error = vi.fn() as MockFn<RuntimeEnv["error"]>;
const exit = vi.fn((_: number) => undefined) as MockFn<RuntimeEnv["exit"]>;
const exit = vi.fn((_code: number) => undefined) as MockFn<RuntimeEnv["exit"]>;
return {
log,
error,

View file

@ -84,15 +84,18 @@ describe("worker placement reconciliation teardown authority", () => {
activeOwnerEpoch: null,
destroyRequestedAtMs: null,
},
])("keeps failed-placement cleanup fenced with $reason", async ({ reason: _, ...params }) => {
const { guard, resumeProvisioning } = createFailedPlacementGuard(params);
const reconcileCore = vi.fn(async () => {});
])(
"keeps failed-placement cleanup fenced with $reason",
async ({ reason: _reason, ...params }) => {
const { guard, resumeProvisioning } = createFailedPlacementGuard(params);
const reconcileCore = vi.fn(async () => {});
await expect(guard("worker-cleanup", reconcileCore)).rejects.toThrow(
"provisioning owner is failed",
);
await expect(guard("worker-cleanup", reconcileCore)).rejects.toThrow(
"provisioning owner is failed",
);
expect(reconcileCore).not.toHaveBeenCalled();
expect(resumeProvisioning).not.toHaveBeenCalled();
});
expect(reconcileCore).not.toHaveBeenCalled();
expect(resumeProvisioning).not.toHaveBeenCalled();
},
);
});

View file

@ -1389,7 +1389,6 @@ async function persistManagedSourceInstall(params: {
try {
await params.transaction?.rollback();
} catch (rollbackError) {
// oxlint-disable-next-line preserve-caught-error -- Oxlint 1.78 ignores AggregateError's third-argument cause.
throw new AggregateError(
[error, rollbackError],
"Plugin install failed and payload rollback failed",

View file

@ -36,23 +36,25 @@ type ResolveBundledProviderPolicySurface =
type ResolveProviderPolicySurface =
typeof import("./provider-public-artifacts.js").resolveProviderPolicySurface;
const resolvePluginProvidersMock = vi.fn<ResolvePluginProviders>((_) => [] as ProviderPlugin[]);
const isPluginProvidersLoadInFlightMock = vi.fn<IsPluginProvidersLoadInFlight>((_) => false);
const resolvePluginProvidersMock = vi.fn<ResolvePluginProviders>(
(_params) => [] as ProviderPlugin[],
);
const isPluginProvidersLoadInFlightMock = vi.fn<IsPluginProvidersLoadInFlight>((_params) => false);
const resolveCatalogHookProviderPluginIdsMock = vi.fn<ResolveCatalogHookProviderPluginIds>(
(_) => [] as string[],
(_params) => [] as string[],
);
const resolveUsageHookProviderPluginContractsMock = vi.fn<ResolveUsageHookProviderPluginContracts>(
(_) => [],
(_params) => [],
);
const resolveExternalAuthProfileProviderPluginIdsMock =
vi.fn<ResolveExternalAuthProfileProviderPluginIds>((_) => [] as string[]);
vi.fn<ResolveExternalAuthProfileProviderPluginIds>((_params) => [] as string[]);
const resolveOwningPluginIdsForProviderMock = vi.fn<ResolveOwningPluginIdsForProvider>(
(_) => undefined,
(_params) => undefined,
);
const resolveBundledProviderPolicySurfaceMock = vi.fn<ResolveBundledProviderPolicySurface>(
(_) => null,
(_providerId) => null,
);
const resolveProviderPolicySurfaceMock = vi.fn<ResolveProviderPolicySurface>((_) => null);
const resolveProviderPolicySurfaceMock = vi.fn<ResolveProviderPolicySurface>((_providerId) => null);
const providerRuntimeWarnMock = vi.fn();
let getAiTransportHost: typeof import("@openclaw/ai").getAiTransportHost;

View file

@ -29,7 +29,7 @@ const resolveRuntimePluginRegistryMock = vi.fn<ResolveRuntimePluginRegistry>();
const getRuntimePluginRegistryForLoadOptionsMock = vi.fn<GetRuntimePluginRegistryForLoadOptions>();
const resolveCompatibleRuntimePluginRegistryMock = vi.fn<ResolveCompatibleRuntimePluginRegistry>();
const loadOpenClawPluginsMock = vi.fn<LoadOpenClawPlugins>();
const isPluginRegistryLoadInFlightMock = vi.fn<IsPluginRegistryLoadInFlight>((_) => false);
const isPluginRegistryLoadInFlightMock = vi.fn<IsPluginRegistryLoadInFlight>((_options) => false);
const loadPluginManifestRegistryMock = vi.fn<LoadPluginManifestRegistry>();
const loadPluginMetadataSnapshotMock = vi.fn<LoadPluginMetadataSnapshot>();
const loadPluginRegistrySnapshotMock = vi.fn<LoadPluginRegistrySnapshot>();

View file

@ -158,7 +158,7 @@ export function listTaskRecords(filter?: (task: Readonly<TaskRecord>) => boolean
return (filter ? records.filter(filter) : records)
.map((task, insertionIndex) => Object.assign({}, cloneTaskRecord(task), { insertionIndex }))
.toSorted(compareTasksNewestFirst)
.map(({ insertionIndex: _, ...task }) => task);
.map(({ insertionIndex: _insertionIndex, ...task }) => task);
}
export function hasActiveTaskForChildSessionKey(params: {
@ -220,7 +220,7 @@ function listTasksFromIndex(index: Map<string, Set<string>>, key: string): TaskR
} => Boolean(task),
)
.toSorted(compareTasksNewestFirst)
.map(({ insertionIndex: _, ...task }) => task);
.map(({ insertionIndex: _insertionIndex, ...task }) => task);
}
export function listTasksForSessionKey(sessionKey: string): TaskRecord[] {
@ -273,7 +273,7 @@ export function listFreshTasksForOwnerKey(ownerKey: string): TaskRecord[] {
return [...merged.values()]
.map((task, insertionIndex) => Object.assign({}, task, { insertionIndex }))
.toSorted(compareTasksNewestFirst)
.map(({ insertionIndex: _, ...task }) => task);
.map(({ insertionIndex: _insertionIndex, ...task }) => task);
} catch (error) {
taskRegistryLog.warn("Failed to read fresh owner task registry records", {
ownerKey: key,

View file

@ -197,8 +197,6 @@ describe("production lint suppressions", () => {
"extensions/discord/src/test-support/provider.test-support.ts|typescript/no-unnecessary-type-parameters|1",
"extensions/feishu/src/bitable.ts|typescript/no-unnecessary-type-parameters|1",
"extensions/matrix/src/onboarding.test-harness.ts|typescript/no-unnecessary-type-parameters|1",
// Oxlint 1.78 checks AggregateError cause options at the wrong argument position.
"extensions/qa-lab/src/gateway-child-lifecycle.ts|preserve-caught-error|1",
"extensions/qa-lab/src/gateway-child-setup.ts|preserve-caught-error|1",
"extensions/slack/src/monitor/provider-support.ts|typescript/no-unnecessary-type-parameters|1",
"src/agents/agent-bundle-mcp-runtime.ts|unicorn/prefer-add-event-listener|1",
@ -235,7 +233,6 @@ describe("production lint suppressions", () => {
"src/plugins/hooks.ts|typescript/no-unnecessary-type-parameters|1",
"src/plugins/host-hooks.ts|typescript/no-unnecessary-type-parameters|1",
"src/plugins/lazy-service-module.ts|typescript/no-unnecessary-type-parameters|1",
"src/plugins/management-service.ts|preserve-caught-error|1",
"src/plugins/public-surface-loader.ts|typescript/no-unnecessary-type-parameters|3",
"src/plugins/runtime/runtime-plugin-boundary.ts|typescript/no-unnecessary-type-parameters|1",
"src/plugins/runtime/types-channel.ts|typescript/no-unnecessary-type-parameters|1",

View file

@ -1,7 +1,12 @@
// Oxlint Config tests cover oxlint config script behavior.
import { spawnSync } from "node:child_process";
import fs from "node:fs";
import path from "node:path";
import JSON5 from "json5";
import { describe, expect, it } from "vitest";
import { createScriptTestHarness } from "./test-helpers.js";
const { createTempDir } = createScriptTestHarness();
type OxlintConfig = {
ignorePatterns?: string[];
@ -123,11 +128,124 @@ const DEFERRED_IMPORT_RULES = [
"import/no-unassigned-import",
];
function readJson(path: string): unknown {
return JSON5.parse(fs.readFileSync(path, "utf8"));
function readJson(filePath: string): unknown {
return JSON5.parse(fs.readFileSync(filePath, "utf8"));
}
describe("oxlint config", () => {
it("enforces namespace, evaluation, and unused-binding policies with the installed binary", () => {
const tempRoot = fs.realpathSync(createTempDir("openclaw-oxlint-policy-"));
const typescriptExtensions = ["ts", "tsx", "mts", "cts"];
const javascriptExtensions = ["js", "jsx", "cjs", "mjs"];
const evaluation = 'eval("1 + 1");\nglobalThis.eval("1 + 1");\n';
const fixtures = [
...typescriptExtensions.map((extension) => ({
file: `src/namespaces.${extension}`,
source: [
"export type Profile = { ready: boolean };",
"export const Profile = { ready: true };",
"export interface Adapter { ready: boolean; }",
"export const Adapter: Adapter = { ready: true };",
].join("\n"),
rules: [],
})),
...javascriptExtensions.map((extension) => ({
file: `src/redeclaration.${extension}`,
source:
"var duplicateBinding = 1;\nvar duplicateBinding = 2;\nconsole.log(duplicateBinding);\n",
rules: ["eslint(no-redeclare)", "eslint(no-var)", "eslint(no-var)"],
})),
...typescriptExtensions.map((extension) => ({
file: `src/no-var.${extension}`,
source: "export var legacyBinding = 1;\n",
rules: ["eslint(no-var)"],
})),
...[...typescriptExtensions, ...javascriptExtensions].flatMap((extension) => [
{
file: `src/evaluation.${extension}`,
source: evaluation,
rules: ["eslint(no-eval)", "eslint(no-eval)"],
},
{
file: `src/unused.${extension}`,
source:
"function meaningful(_event) { return true; }\nfunction bare(_) { return true; }\nmeaningful(1);\nbare(1);\n",
rules: ["eslint(no-unused-vars)"],
},
]),
...[
"extensions/qa-lab/src/web-runtime.ts",
"extensions/qa-lab/src/web-runtime.test.ts",
"extensions/qa-lab/src/other-runtime.ts",
"extensions/other/src/web-runtime.ts",
].map((file) => ({
file,
source: evaluation,
rules:
file === "extensions/qa-lab/src/web-runtime.ts"
? ["eslint(no-eval)"]
: ["eslint(no-eval)", "eslint(no-eval)"],
})),
];
fs.copyFileSync(".oxlintrc.json", path.join(tempRoot, ".oxlintrc.json"));
for (const fixture of fixtures) {
const target = path.join(tempRoot, fixture.file);
fs.mkdirSync(path.dirname(target), { recursive: true });
fs.writeFileSync(target, fixture.source);
}
// These syntax-rule fixtures need no type program; one batch uses the real config and paths.
const result = spawnSync(
process.execPath,
[
path.resolve("node_modules/oxlint/bin/oxlint"),
"--config",
".oxlintrc.json",
"--format",
"json",
"--threads=1",
"--report-unused-disable-directives-severity",
"error",
...fixtures.map((fixture) => fixture.file),
],
{ cwd: tempRoot, encoding: "utf8", timeout: 10_000 },
);
expect(result.error).toBeUndefined();
expect(result.status, result.stderr).toBe(1);
const report = JSON.parse(result.stdout) as {
number_of_files: number;
diagnostics: Array<{
filename: string;
code: string;
severity: string;
labels: Array<{ span: { line: number } }>;
}>;
};
expect(report.number_of_files).toBe(fixtures.length);
for (const fixture of fixtures) {
const diagnostics = report.diagnostics.filter(
(diagnostic) => diagnostic.filename.replaceAll("\\", "/") === fixture.file,
);
expect(diagnostics.map((diagnostic) => diagnostic.code).toSorted(), fixture.file).toEqual(
fixture.rules.toSorted(),
);
expect(
diagnostics.every((diagnostic) => diagnostic.severity === "error"),
fixture.file,
).toBe(true);
}
const ownerDiagnostics = report.diagnostics.filter(
(diagnostic) =>
diagnostic.filename.replaceAll("\\", "/") === "extensions/qa-lab/src/web-runtime.ts",
);
expect(ownerDiagnostics.map((diagnostic) => diagnostic.labels[0]?.span.line)).toEqual([1]);
const unusedDiagnostics = report.diagnostics.filter(
(diagnostic) => diagnostic.code === "eslint(no-unused-vars)",
);
expect(unusedDiagnostics.map((diagnostic) => diagnostic.labels[0]?.span.line)).toEqual(
[...typescriptExtensions, ...javascriptExtensions].map(() => 2),
);
});
it("includes bundled extensions in type-aware lint coverage", () => {
const tsconfig = readJson("config/tsconfig/oxlint.json") as OxlintTsconfig;

View file

@ -1,3 +1,4 @@
import { runInNewContext } from "node:vm";
import { expectDefined } from "@openclaw/normalization-core";
import { describe, expect, it, vi } from "vitest";
import type { GatewayBrowserClient } from "../../api/gateway.ts";
@ -133,41 +134,29 @@ describe("buildBrowserAnnotationContent", () => {
it("preserves valid UTF-16 from inspected accessible names", async () => {
const element = document.createElement("button");
element.setAttribute("aria-label", `${"a".repeat(78)}${" ".repeat(41)}😀tail`);
const originalElementFromPoint = Object.getOwnPropertyDescriptor(document, "elementFromPoint");
Object.defineProperty(document, "elementFromPoint", {
configurable: true,
value: vi.fn(() => element),
});
const stubDocument = { elementFromPoint: () => element };
const client = {
request: vi.fn(async (_method: string, envelope: { body?: { fn?: string } }) => {
const fn = envelope.body?.fn;
if (!fn) {
throw new Error("missing browser evaluation function");
}
return { result: (0, eval)(`(${fn})`)() };
return { result: runInNewContext(`(${fn})()`, { document: stubDocument }) };
}),
};
try {
const inspected = await inspectBrowserElementAt(client as unknown as GatewayBrowserClient, {
targetId: "proof-tab",
x: 10,
y: 20,
});
const { modelContext } = buildBrowserAnnotationContent({
url: "https://example.com",
title: "Boundary proof",
strokes: [],
element: inspected,
});
expect(inspected?.name.charCodeAt((inspected?.name.length ?? 0) - 1)).not.toBe(0xd83d);
expect(modelContext).toContain(`button "${"a".repeat(78)}"`);
} finally {
if (originalElementFromPoint) {
Object.defineProperty(document, "elementFromPoint", originalElementFromPoint);
} else {
Reflect.deleteProperty(document, "elementFromPoint");
}
}
const inspected = await inspectBrowserElementAt(client as unknown as GatewayBrowserClient, {
targetId: "proof-tab",
x: 10,
y: 20,
});
const { modelContext } = buildBrowserAnnotationContent({
url: "https://example.com",
title: "Boundary proof",
strokes: [],
element: inspected,
});
expect(inspected?.name.charCodeAt((inspected?.name.length ?? 0) - 1)).not.toBe(0xd83d);
expect(modelContext).toContain(`button "${"a".repeat(78)}"`);
});
});

View file

@ -2,6 +2,7 @@
import { describe, expect, it, vi } from "vitest";
import { i18n } from "../i18n/index.ts";
import { handleMarkdownCodeBlockClick } from "./markdown-code-blocks.ts";
import * as markdownDetails from "./markdown-details.ts";
import { splitStableStreamingMarkdown } from "./markdown-streaming.ts";
import { toSanitizedMarkdownHtml, toStreamingMarkdownHtml } from "./markdown.ts";
@ -865,36 +866,30 @@ PY
});
describe("toStreamingMarkdownHtml", () => {
it("keeps appended-prefix splitting below repeated full-rescan cost", () => {
const splitIncrementally = splitStableStreamingMarkdown as (
markdown: string,
streamKey: string,
) => ReturnType<typeof splitStableStreamingMarkdown>;
it("does not rescan completed disclosures in appended prefixes", () => {
const prefixes: string[] = [];
let prefix = "<details><summary>Done</summary></details>\n\n";
// The ratio catches a reverted full-rescan path without making runner load
// part of the assertion by spending most of the test timeout on the baseline.
for (let index = 0; index < 48; index += 1) {
prefix += `${String(index).padStart(3, "0")} ${"streaming markdown ".repeat(30)}\n`;
prefixes.push(prefix);
}
const measure = (streamKey?: string) => {
const startedAt = performance.now();
for (const value of prefixes) {
if (streamKey) {
splitIncrementally(value, streamKey);
} else {
splitStableStreamingMarkdown(value);
}
}
return performance.now() - startedAt;
};
measure("line-scan-warmup");
const fullRescanMs = measure();
const incrementalMs = measure("line-scan-regression");
// A full rescan revisits the completed disclosure on every chunk. Observe
// the real scanner instead of comparing sub-millisecond wall-clock times.
const scanDisclosure = vi.spyOn(markdownDetails, "scanMarkdownDisclosureLine");
try {
const fullSplits = prefixes.map((value) => splitStableStreamingMarkdown(value));
expect(scanDisclosure).toHaveBeenCalledTimes(prefixes.length);
scanDisclosure.mockClear();
expect(incrementalMs).toBeLessThan(fullRescanMs / 5);
}, 5_000);
const incrementalSplits = prefixes.map((value) =>
splitStableStreamingMarkdown(value, "line-scan-regression"),
);
expect(incrementalSplits).toEqual(fullSplits);
expect(scanDisclosure).toHaveBeenCalledTimes(1);
} finally {
scanDisclosure.mockRestore();
}
});
it("keeps chunked-prefix splits identical to full splits", () => {
const splitIncrementally = splitStableStreamingMarkdown as (