fix(test): stabilize low-mem parallel runner and cron session mock (#26324)

* fix(test): stabilize low-mem parallel lane and cron session mock

* feat(android): make QR scanning first-class onboarding

* docs(android): update README for native Android workflow

* fix(android): stabilize chat composer ime and tab layout

* fix(android): stabilize chat ime insets and tab bar

* fix(android): remove tab bar gap above system nav

* fix(android): harden scanned setup code parsing

* test(android): cover non-string setupCode QR payload

* fix(test): add changelog note for low-mem test runner (#26324) (thanks @ngutman)

---------

Co-authored-by: Ayaan Zaidi <zaidi@uplause.io>
This commit is contained in:
Nimrod Gutman
2026-02-25 12:16:17 +02:00
committed by GitHub
parent ed34129637
commit b3f46f0e28
3 changed files with 20 additions and 11 deletions

View File

@@ -23,6 +23,7 @@ Docs: https://docs.openclaw.ai
- Hooks/Inbound metadata: include `guildId` and `channelName` in `message_received` metadata for both plugin and internal hook paths. (#26115) Thanks @davidrudduck.
- Discord/Component auth: evaluate guild component interactions with command-gating authorizers so unauthorized users no longer get `CommandAuthorized: true` on modal/button events. (#26119) Thanks @bmendonca3.
- Discord/Typing indicator: prevent stuck typing indicators by sealing channel typing keepalive callbacks after idle/cleanup and ensuring Discord dispatch always marks typing idle even if preview-stream cleanup fails. (#26295) Thanks @ngutman.
- Tests/Low-memory stability: disable Vitest `vmForks` by default on low-memory local hosts (`<64 GiB`), keep low-profile extension lane parallelism at 4 workers, and align cron isolated-agent tests with `setSessionRuntimeModel` usage to avoid deterministic suite failures. (#26324) Thanks @ngutman.
- Slack/Inbound media fallback: deliver file-only messages even when Slack media downloads fail by adding a filename placeholder fallback, capping fallback names to the shared media-file limit, and normalizing empty filenames to `file` so attachment-only messages are not silently dropped. (#25181) Thanks @justinhuangcode.
## 2026.2.24

View File

@@ -88,14 +88,20 @@ const isCI = process.env.CI === "true" || process.env.GITHUB_ACTIONS === "true";
const isMacOS = process.platform === "darwin" || process.env.RUNNER_OS === "macOS";
const isWindows = process.platform === "win32" || process.env.RUNNER_OS === "Windows";
const isWindowsCi = isCI && isWindows;
const hostCpuCount = os.cpus().length;
const hostMemoryGiB = Math.floor(os.totalmem() / 1024 ** 3);
// Keep aggressive local defaults for high-memory workstations (Mac Studio class).
const highMemLocalHost = !isCI && hostMemoryGiB >= 96;
const lowMemLocalHost = !isCI && hostMemoryGiB < 64;
const nodeMajor = Number.parseInt(process.versions.node.split(".")[0] ?? "", 10);
// vmForks is a big win for transform/import heavy suites, but Node 24 had
// regressions with Vitest's vm runtime in this repo. Keep it opt-out via
// regressions with Vitest's vm runtime in this repo, and low-memory local hosts
// are more likely to hit per-worker V8 heap ceilings. Keep it opt-out via
// OPENCLAW_TEST_VM_FORKS=0, and let users force-enable with =1.
const supportsVmForks = Number.isFinite(nodeMajor) ? nodeMajor !== 24 : true;
const useVmForks =
process.env.OPENCLAW_TEST_VM_FORKS === "1" ||
(process.env.OPENCLAW_TEST_VM_FORKS !== "0" && !isWindows && supportsVmForks);
(process.env.OPENCLAW_TEST_VM_FORKS !== "0" && !isWindows && supportsVmForks && !lowMemLocalHost);
const disableIsolation = process.env.OPENCLAW_TEST_NO_ISOLATE === "1";
const runs = [
...(useVmForks
@@ -176,11 +182,6 @@ const testProfile =
const overrideWorkers = Number.parseInt(process.env.OPENCLAW_TEST_WORKERS ?? "", 10);
const resolvedOverride =
Number.isFinite(overrideWorkers) && overrideWorkers > 0 ? overrideWorkers : null;
const hostCpuCount = os.cpus().length;
const hostMemoryGiB = Math.floor(os.totalmem() / 1024 ** 3);
// Keep aggressive local defaults for high-memory workstations (Mac Studio class).
const highMemLocalHost = !isCI && hostMemoryGiB >= 96;
const lowMemLocalHost = !isCI && hostMemoryGiB < 64;
const parallelGatewayEnabled =
process.env.OPENCLAW_TEST_PARALLEL_GATEWAY === "1" || (!isCI && highMemLocalHost);
// Keep gateway serial by default except when explicitly requested or on high-memory local hosts.
@@ -206,7 +207,7 @@ const defaultWorkerBudget =
? {
unit: 2,
unitIsolated: 1,
extensions: 1,
extensions: 4,
gateway: 1,
}
: testProfile === "serial"
@@ -236,7 +237,7 @@ const defaultWorkerBudget =
// Sub-64 GiB local hosts are prone to OOM with large vmFork runs.
unit: 2,
unitIsolated: 1,
extensions: 1,
extensions: 4,
gateway: 1,
}
: {
@@ -335,9 +336,15 @@ const runOnce = (entry, extraArgs = []) =>
new Promise((resolve) => {
const maxWorkers = maxWorkersForRun(entry.name);
const reporterArgs = buildReporterArgs(entry, extraArgs);
// vmForks with a single worker has shown cross-file leakage in extension suites.
// Fall back to process forks when we intentionally clamp that lane to one worker.
const entryArgs =
entry.name === "extensions" && maxWorkers === 1 && entry.args.includes("--pool=vmForks")
? entry.args.map((arg) => (arg === "--pool=vmForks" ? "--pool=forks" : arg))
: entry.args;
const args = maxWorkers
? [
...entry.args,
...entryArgs,
"--maxWorkers",
String(maxWorkers),
...silentArgs,
@@ -345,7 +352,7 @@ const runOnce = (entry, extraArgs = []) =>
...windowsCiArgs,
...extraArgs,
]
: [...entry.args, ...silentArgs, ...reporterArgs, ...windowsCiArgs, ...extraArgs];
: [...entryArgs, ...silentArgs, ...reporterArgs, ...windowsCiArgs, ...extraArgs];
const nodeOptions = process.env.NODE_OPTIONS ?? "";
const nextNodeOptions = WARNING_SUPPRESSION_FLAGS.reduce(
(acc, flag) => (acc.includes(flag) ? acc : `${acc} ${flag}`.trim()),

View File

@@ -112,6 +112,7 @@ vi.mock("../../cli/outbound-send-deps.js", () => ({
vi.mock("../../config/sessions.js", () => ({
resolveAgentMainSessionKey: vi.fn().mockReturnValue("main:default"),
resolveSessionTranscriptPath: vi.fn().mockReturnValue("/tmp/transcript.jsonl"),
setSessionRuntimeModel: vi.fn(),
updateSessionStore: vi.fn().mockResolvedValue(undefined),
}));