Skip to content

Commit a88f3f2

Browse files
committed
Auto-merge upstream openclaw/openclaw
2 parents 541dcb4 + c6d0baf commit a88f3f2

35 files changed

Lines changed: 1760 additions & 74 deletions

CHANGELOG.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ Docs: https://docs.openclaw.ai
99
- Memory/Active Memory: add a new optional Active Memory plugin that gives OpenClaw a dedicated memory sub-agent right before the main reply, so ongoing chats can automatically pull in relevant preferences, context, and past details without making users remember to manually say "remember this" or "search memory" first. Includes configurable message/recent/full context modes, live `/verbose` inspection, advanced prompt/thinking overrides for tuning, and opt-in transcript persistence for debugging.
1010
- macOS/Talk: add an experimental local MLX speech provider for Talk Mode, with explicit provider selection, local utterance playback, interruption handling, and system-voice fallback. (#63539) Thanks @ImLukeF.
1111
- Docs i18n: chunk raw doc translation, reject truncated tagged outputs, avoid ambiguous body-only wrapper unwrapping, and recover from terminated Pi translation sessions without changing the default `openai/gpt-5.4` path. (#62969, #63808) Thanks @hxy91819.
12+
- QA/testing: add a `--runner multipass` lane for `openclaw qa suite` so repo-backed QA scenarios can run inside a disposable Linux VM and write back the usual report, summary, and VM logs. (#63426) Thanks @shakkernerd.
1213

1314
### Fixes
1415

@@ -42,6 +43,7 @@ Docs: https://docs.openclaw.ai
4243
- Gateway/session reset: emit the typed `before_reset` hook for gateway `/new` and `/reset`, preserving reset-hook behavior even when the previous transcript has already been archived. (#53872) thanks @VACInc
4344
- Plugins/commands: pass the active host `sessionKey` into plugin command contexts, and include `sessionId` when it is already available from the active session entry, so bundled and third-party commands can resolve the current conversation reliably. (#59044) Thanks @jalehman.
4445
- Agents/auth: honor `models.providers.*.authHeader` for pi embedded runner model requests by injecting `Authorization: Bearer <apiKey>` when requested. (#54390) Thanks @lndyzwdxhs.
46+
- Dreaming/cron: stop runtime cron reconciliation on ordinary user turns and only recover managed dreaming cron state during heartbeat-triggered dreaming checks, so unrelated chat traffic does not silently recreate removed jobs. (#63938) Thanks @mbelinky.
4547
- UI/compaction: keep the compaction indicator in a retry-pending state until the run actually finishes, so the UI does not show `Context compacted` before compaction actually finishes. (#55132) Thanks @mpz4life.
4648
- Cron/tool schemas: keep cron tool schemas strict-model-friendly while still preserving `failureAlert=false`, nullable `agentId`/`sessionKey`, and flattened add/update recovery for the newly exposed cron job fields. (#55043) Thanks @brunolorente.
4749
- BlueBubbles/config: accept `enrichGroupParticipantsFromContacts` in the core strict config schema so gateways no longer fail validation or startup when the BlueBubbles plugin writes that field. (#56889) Thanks @zqchris.
@@ -57,7 +59,7 @@ Docs: https://docs.openclaw.ai
5759
- Cron/Telegram: collapse isolated announce delivery to the final assistant-visible text only for Telegram targets, while preserving existing multi-message direct delivery semantics for other channels. (#63228) Thanks @welfo-beo.
5860
- Gateway/thread routing: preserve Slack, Telegram, and Mattermost thread-child delivery targets so bound subagent completion messages land in the originating thread instead of top-level channels. (#54840) Thanks @yzzymt.
5961
- ACP/stream relay: pass parent delivery context to ACP stream relay system events so `streamTo="parent"` updates route to the correct thread or topic instead of falling back to the main DM. (#57056) Thanks @pingren.
60-
62+
- Agents/sessions: preserve announce `threadId` when `sessions.list` fallback rehydrates agent-to-agent announce targets so final announce messages stay in the originating thread/topic. (#63506) Thanks @SnowSky1.
6163
## 2026.4.9
6264

6365
### Changes

docs/concepts/qa-e2e-automation.md

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,21 @@ pnpm qa:lab:watch
5252
rebuilds that bundle on change, and the browser auto-reloads when the QA Lab
5353
asset hash changes.
5454

55+
For a disposable Linux VM lane without bringing Docker into the QA path, run:
56+
57+
```bash
58+
pnpm openclaw qa suite --runner multipass --scenario channel-chat-baseline
59+
```
60+
61+
This boots a fresh Multipass guest, installs dependencies, builds OpenClaw
62+
inside the guest, runs `qa suite`, then copies the normal QA report and
63+
summary back into `.artifacts/qa-e2e/...` on the host.
64+
It reuses the same scenario-selection behavior as `qa suite` on the host.
65+
Live runs forward the supported QA auth inputs that are practical for the
66+
guest: env-based provider keys, the QA live provider config path, and
67+
`CODEX_HOME` when present. Keep `--output-dir` under the repo root so the guest
68+
can write back through the mounted workspace.
69+
5570
## Repo-backed seeds
5671

5772
Seed assets live in `qa/`:

docs/help/testing.md

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ Most days:
2828
- Direct file targeting now routes extension/channel paths too: `pnpm test extensions/discord/src/monitor/message-handler.preflight.test.ts`
2929
- Prefer targeted runs first when you are iterating on a single failure.
3030
- Docker-backed QA site: `pnpm qa:lab:up`
31+
- Linux VM-backed QA lane: `pnpm openclaw qa suite --runner multipass --scenario channel-chat-baseline`
3132

3233
When you touch tests or want extra confidence:
3334

@@ -41,6 +42,26 @@ When debugging real providers/models (requires real creds):
4142

4243
Tip: when you only need one failing case, prefer narrowing live tests via the allowlist env vars described below.
4344

45+
## QA-specific runners
46+
47+
These commands sit beside the main test suites when you need QA-lab realism:
48+
49+
- `pnpm openclaw qa suite`
50+
- Runs repo-backed QA scenarios directly on the host.
51+
- `pnpm openclaw qa suite --runner multipass`
52+
- Runs the same QA suite inside a disposable Multipass Linux VM.
53+
- Keeps the same scenario-selection behavior as `qa suite` on the host.
54+
- Reuses the same provider/model selection flags as `qa suite`.
55+
- Live runs forward the supported QA auth inputs that are practical for the guest:
56+
env-based provider keys, the QA live provider config path, and `CODEX_HOME`
57+
when present.
58+
- Output dirs must stay under the repo root so the guest can write back through
59+
the mounted workspace.
60+
- Writes the normal QA report + summary plus Multipass logs under
61+
`.artifacts/qa-e2e/...`.
62+
- `pnpm qa:lab:up`
63+
- Starts the Docker-backed QA site for operator-style QA work.
64+
4465
## Test suites (what runs where)
4566

4667
Think of the suites as “increasing realism” (and increasing flakiness/cost):

extensions/active-memory/index.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
import crypto from "node:crypto";
22
import fs from "node:fs/promises";
3-
import os from "node:os";
43
import path from "node:path";
54
import {
65
DEFAULT_PROVIDER,
@@ -15,6 +14,7 @@ import {
1514
type OpenClawConfig,
1615
} from "openclaw/plugin-sdk/config-runtime";
1716
import { definePluginEntry, type OpenClawPluginApi } from "openclaw/plugin-sdk/plugin-entry";
17+
import { resolvePreferredOpenClawTmpDir } from "openclaw/plugin-sdk/temp-path";
1818

1919
const DEFAULT_TIMEOUT_MS = 15_000;
2020
const DEFAULT_AGENT_ID = "main";
@@ -1210,7 +1210,7 @@ async function runRecallSubagent(params: {
12101210
: `agent:${params.agentId}:${subagentSuffix}`;
12111211
const tempDir = params.config.persistTranscripts
12121212
? undefined
1213-
: await fs.mkdtemp(path.join(os.tmpdir(), "openclaw-active-memory-"));
1213+
: await fs.mkdtemp(path.join(resolvePreferredOpenClawTmpDir(), "openclaw-active-memory-"));
12141214
const persistedDir = params.config.persistTranscripts
12151215
? resolveSafeTranscriptDir(
12161216
resolvePersistentTranscriptBaseDir(params.api, params.agentId),

extensions/browser/src/browser/chrome-mcp.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
11
import { randomUUID } from "node:crypto";
22
import fs from "node:fs/promises";
3-
import os from "node:os";
43
import path from "node:path";
54
import { Client } from "@modelcontextprotocol/sdk/client/index.js";
65
import { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js";
76
import { normalizeOptionalString, readStringValue } from "openclaw/plugin-sdk/text-runtime";
7+
import { resolvePreferredOpenClawTmpDir } from "../infra/tmp-openclaw-dir.js";
88
import { asRecord } from "../record-shared.js";
99
import type { ChromeMcpSnapshotNode } from "./chrome-mcp.snapshot.js";
1010
import type { BrowserTab } from "./client.js";
@@ -332,7 +332,7 @@ async function callTool(
332332
}
333333

334334
async function withTempFile<T>(fn: (filePath: string) => Promise<T>): Promise<T> {
335-
const dir = await fs.mkdtemp(path.join(os.tmpdir(), "openclaw-chrome-mcp-"));
335+
const dir = await fs.mkdtemp(path.join(resolvePreferredOpenClawTmpDir(), "openclaw-chrome-mcp-"));
336336
const filePath = path.join(dir, randomUUID());
337337
try {
338338
return await fn(filePath);

extensions/diffs/src/test-helpers.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,13 @@
11
import fs from "node:fs/promises";
2-
import os from "node:os";
32
import path from "node:path";
3+
import { resolvePreferredOpenClawTmpDir } from "../api.js";
44
import { DiffArtifactStore } from "./store.js";
55

66
export async function createTempDiffRoot(prefix: string): Promise<{
77
rootDir: string;
88
cleanup: () => Promise<void>;
99
}> {
10-
const rootDir = await fs.mkdtemp(path.join(os.tmpdir(), prefix));
10+
const rootDir = await fs.mkdtemp(path.join(resolvePreferredOpenClawTmpDir(), prefix));
1111
return {
1212
rootDir,
1313
cleanup: async () => {

extensions/google/video-generation-provider.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
import { mkdtemp, readFile, rm } from "node:fs/promises";
2-
import os from "node:os";
32
import path from "node:path";
43
import { GoogleGenAI } from "@google/genai";
54
import { isProviderApiKeyConfigured } from "openclaw/plugin-sdk/provider-auth";
65
import { resolveApiKeyForProvider } from "openclaw/plugin-sdk/provider-auth-runtime";
6+
import { resolvePreferredOpenClawTmpDir } from "openclaw/plugin-sdk/temp-path";
77
import { normalizeOptionalString } from "openclaw/plugin-sdk/text-runtime";
88
import type {
99
GeneratedVideoAsset,
@@ -124,7 +124,9 @@ async function downloadGeneratedVideo(params: {
124124
file: unknown;
125125
index: number;
126126
}): Promise<GeneratedVideoAsset> {
127-
const tempDir = await mkdtemp(path.join(os.tmpdir(), "openclaw-google-video-"));
127+
const tempDir = await mkdtemp(
128+
path.join(resolvePreferredOpenClawTmpDir(), "openclaw-google-video-"),
129+
);
128130
const downloadPath = path.join(tempDir, `video-${params.index + 1}.mp4`);
129131
try {
130132
await params.client.files.download({

extensions/memory-core/src/cli.runtime.ts

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import os from "node:os";
44
import path from "node:path";
55
import { resolveMemoryRemDreamingConfig } from "openclaw/plugin-sdk/memory-core-host-status";
66
import { buildAgentSessionKey } from "openclaw/plugin-sdk/routing";
7+
import { resolvePreferredOpenClawTmpDir } from "openclaw/plugin-sdk/temp-path";
78
import {
89
colorize,
910
defaultRuntime,
@@ -158,7 +159,9 @@ async function createHistoricalRemHarnessWorkspace(params: {
158159
skippedPaths: string[];
159160
}> {
160161
const sourceFiles = await listHistoricalDailyFiles(params.inputPath);
161-
const workspaceDir = await fs.mkdtemp(path.join(os.tmpdir(), "openclaw-rem-harness-"));
162+
const workspaceDir = await fs.mkdtemp(
163+
path.join(resolvePreferredOpenClawTmpDir(), "openclaw-rem-harness-"),
164+
);
162165
const memoryDir = path.join(workspaceDir, "memory");
163166
await fs.mkdir(memoryDir, { recursive: true });
164167
for (const filePath of sourceFiles) {
@@ -1720,7 +1723,9 @@ export async function runMemoryRemBackfill(opts: MemoryRemBackfillOptions) {
17201723
return;
17211724
}
17221725

1723-
const scratchDir = await fs.mkdtemp(path.join(os.tmpdir(), "openclaw-rem-backfill-"));
1726+
const scratchDir = await fs.mkdtemp(
1727+
path.join(resolvePreferredOpenClawTmpDir(), "openclaw-rem-backfill-"),
1728+
);
17241729
try {
17251730
const sourceFiles = await listHistoricalDailyFiles(opts.path);
17261731
if (sourceFiles.length === 0) {

extensions/memory-core/src/dreaming-narrative.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -296,6 +296,7 @@ async function writeDreamsFileAtomic(dreamsPath: string, content: string): Promi
296296
if (cleanupError) {
297297
throw new Error(
298298
`Atomic DREAMS.md write failed (${formatErrorMessage(err)}); cleanup also failed (${formatErrorMessage(cleanupError)})`,
299+
{ cause: err },
299300
);
300301
}
301302
throw err;

0 commit comments

Comments
 (0)