Skip to content

Commit 93e477a

Browse files
authored
Merge branch 'main' into feat/issue-73713
2 parents 9754118 + 7bd533a commit 93e477a

135 files changed

Lines changed: 4039 additions & 658 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

docs/concepts/qa-e2e-automation.md

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -787,9 +787,10 @@ the source of truth for one test run and should define:
787787
- docs and code refs
788788
- optional plugin requirements
789789
- optional gateway config patch
790-
- the executable `qa-flow`
790+
- an executable `qa-flow` block for flow scenarios, or `execution.kind`/`execution.path`
791+
for Vitest and Playwright scenarios
791792

792-
The reusable runtime surface that backs `qa-flow` is allowed to stay generic
793+
The reusable runtime surface that backs `qa-flow` blocks is allowed to stay generic
793794
and cross-cutting. For example, markdown scenarios can combine transport-side
794795
helpers with browser-side helpers that drive the embedded Control UI through the
795796
Gateway `browser.request` seam without adding a special-case runner.
@@ -915,6 +916,7 @@ The report should answer:
915916
For the inventory of available scenarios - useful when sizing follow-up work or wiring a new transport - run `pnpm openclaw qa coverage` (add `--json` for machine-readable output).
916917
When choosing focused proof for a touched behavior or file path, run `pnpm openclaw qa coverage --match <query>`.
917918
The match report searches scenario metadata, docs refs, code refs, coverage IDs, plugins, and provider requirements, then prints matching `qa suite --scenario ...` targets.
919+
Every `qa suite` scenario execution writes a `qa-evidence.json` artifact. Flow scenarios also write `qa-suite-summary.json` for existing suite/report tooling; scenarios that declare `execution.kind: vitest` or `execution.kind: playwright` run the matching test path and write `qa-vitest-report.md` or `qa-playwright-report.md` plus per-scenario logs.
918920
Treat it as a discovery aid, not a gate replacement; the selected scenario still needs the right provider mode, live transport, Multipass, Testbox, or release lane for the behavior under test.
919921

920922
For character and style checks, run the same scenario across multiple live model

docs/gateway/health.md

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,21 @@ health commands above for live connectivity checks.
4242
- `channels.<provider>.accounts.<accountId>.healthMonitor.enabled`: multi-account override that wins over the channel-level setting.
4343
- These per-channel overrides apply to the built-in channel monitors that expose them today: Discord, Google Chat, iMessage, Microsoft Teams, Signal, Slack, Telegram, and WhatsApp.
4444

45+
## Uptime monitoring
46+
47+
External uptime monitoring services should use the dedicated `/health` endpoint, not `/v1/chat/completions`.
48+
49+
- **DO use:** `GET /health` — instant response, no session created, no LLM call, returns `{"ok":true,"status":"live"}`
50+
- **DON'T use:** `/v1/chat/completions` for health checks — each request creates a full agent session with skill snapshot, context assembly, and LLM calls
51+
52+
When no `x-openclaw-session-key` header or `user` field is provided, `/v1/chat/completions` generates a new random session for each request. Monitoring services that ping every 15 minutes create ~96 sessions/day, each consuming 4–22KB. Over time this causes session store bloat and can lead to context window overflow.
53+
54+
### Monitoring service setup examples
55+
56+
- **BetterStack:** Set health check URL to `https://<your-gateway-host>:<port>/health`
57+
- **UptimeRobot:** Add a new HTTP monitor with URL `https://<your-gateway-host>:<port>/health`
58+
- **Generic:** Any HTTP GET to `/health` returns 200 with `{"ok":true}` when the gateway is healthy
59+
4560
## When something fails
4661

4762
- `logged out` or status 409–515 → relink with `openclaw channels logout` then `openclaw channels login`.

docs/platforms/windows.md

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -18,11 +18,13 @@ most Linux-compatible Gateway runtime.
1818
Windows Hub is the native WinUI companion app for Windows 10 20H2+ and Windows 11. It installs without administrator privileges and is published with signed
1919
x64 and ARM64 installers on OpenClaw releases.
2020

21-
Download the latest stable installer:
21+
Download the latest stable installer from the [OpenClaw releases page](https://github.com/openclaw/openclaw/releases):
2222

23-
- [OpenClawCompanion-Setup-x64.exe](https://github.com/openclaw/openclaw/releases/latest/download/OpenClawCompanion-Setup-x64.exe)
24-
- [OpenClawCompanion-Setup-arm64.exe](https://github.com/openclaw/openclaw/releases/latest/download/OpenClawCompanion-Setup-arm64.exe)
25-
- [Checksums](https://github.com/openclaw/openclaw/releases/latest/download/OpenClawCompanion-SHA256SUMS.txt)
23+
- [OpenClawCompanion-Setup-x64.exe](https://github.com/openclaw/openclaw/releases/download/v2026.6.5/OpenClawCompanion-Setup-x64.exe)
24+
- [OpenClawCompanion-Setup-arm64.exe](https://github.com/openclaw/openclaw/releases/download/v2026.6.5/OpenClawCompanion-Setup-arm64.exe)
25+
- [Checksums](https://github.com/openclaw/openclaw/releases/download/v2026.6.5/OpenClawCompanion-SHA256SUMS.txt)
26+
27+
If a download link above returns a 404, visit the [releases page](https://github.com/openclaw/openclaw/releases) and look for the `OpenClawCompanion-Setup-*` assets on the latest release.
2628

2729
After install, launch **OpenClaw Companion** from the Start menu or the system
2830
tray. The installer also adds shortcuts for Gateway Setup, Chat, Settings,

extensions/memory-core/src/memory/search-manager.test.ts

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -368,6 +368,30 @@ describe("getMemorySearchManager caching", () => {
368368
expect(searchResults).toHaveLength(1);
369369
});
370370

371+
it("returns the qmd startup failure when builtin fallback is unavailable", async () => {
372+
const cfg = createQmdCfg("missing-qmd-no-builtin");
373+
checkQmdBinaryAvailability.mockResolvedValueOnce({
374+
available: false,
375+
reason: "binary",
376+
error: "spawn qmd ENOENT",
377+
});
378+
mockMemoryIndexGet.mockRejectedValueOnce(
379+
new Error(
380+
'Memory search unavailable: embedding provider "openai" is configured but unavailable.',
381+
),
382+
);
383+
384+
const result = await getMemorySearchManager({ cfg, agentId: "missing-qmd-no-builtin" });
385+
386+
expect(result.manager).toBeNull();
387+
expect(result.error).toContain("qmd binary unavailable (qmd): spawn qmd ENOENT");
388+
expect(result.error).toContain(
389+
'builtin fallback unavailable: Memory search unavailable: embedding provider "openai" is configured but unavailable.',
390+
);
391+
expect(createQmdManagerMock).not.toHaveBeenCalled();
392+
expect(mockMemoryIndexGet).toHaveBeenCalledTimes(1);
393+
});
394+
371395
it("treats legacy qmd unavailable results without a reason as binary failures", async () => {
372396
const cfg = createQmdCfg("missing-qmd-legacy");
373397
checkQmdBinaryAvailability.mockResolvedValueOnce({

extensions/memory-core/src/memory/search-manager.ts

Lines changed: 32 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -262,16 +262,18 @@ export async function getMemorySearchManager(params: {
262262
}
263263

264264
if (transient) {
265-
const { manager } = await createPrimaryQmdManager(
265+
const { manager, failureReason } = await createPrimaryQmdManager(
266266
params.purpose === "cli" ? "cli" : "status",
267267
);
268-
return manager ? { manager } : await getBuiltinMemorySearchManager(params);
268+
return manager
269+
? { manager }
270+
: await getBuiltinMemorySearchManagerAfterQmdFailure(params, failureReason);
269271
}
270272

271273
const recentFailure = getActiveQmdManagerOpenFailure(scopeKey, identityKey);
272274
if (recentFailure) {
273275
log.debug?.(`qmd memory unavailable; using builtin during cooldown: ${recentFailure.reason}`);
274-
return await getBuiltinMemorySearchManager(params);
276+
return await getBuiltinMemorySearchManagerAfterQmdFailure(params, recentFailure.reason);
275277
}
276278

277279
const pending = PENDING_QMD_MANAGER_CREATES.get(scopeKey);
@@ -280,16 +282,14 @@ export async function getMemorySearchManager(params: {
280282
return await getMemorySearchManager(params);
281283
}
282284

285+
let pendingFailureReason: string | undefined;
283286
const pendingCreate: PendingQmdManagerCreate = {
284287
identityKey,
285288
promise: (async () => {
286289
const created = await createFullQmdManager(identityKey);
287290
if (!created.entry) {
288-
recordQmdManagerOpenFailure(
289-
scopeKey,
290-
identityKey,
291-
created.failureReason ?? "qmd memory unavailable",
292-
);
291+
pendingFailureReason = created.failureReason ?? "qmd memory unavailable";
292+
recordQmdManagerOpenFailure(scopeKey, identityKey, pendingFailureReason);
293293
return null;
294294
}
295295
QMD_MANAGER_CACHE.set(scopeKey, created.entry);
@@ -308,12 +308,35 @@ export async function getMemorySearchManager(params: {
308308
};
309309
PENDING_QMD_MANAGER_CREATES.set(scopeKey, pendingCreate);
310310
const manager = await pendingCreate.promise;
311-
return manager ? { manager } : await getBuiltinMemorySearchManager(params);
311+
return manager
312+
? { manager }
313+
: await getBuiltinMemorySearchManagerAfterQmdFailure(params, pendingFailureReason);
312314
}
313315

314316
return await getBuiltinMemorySearchManager(params);
315317
}
316318

319+
async function getBuiltinMemorySearchManagerAfterQmdFailure(
320+
params: {
321+
cfg: OpenClawConfig;
322+
agentId: string;
323+
purpose?: MemorySearchManagerPurpose;
324+
},
325+
qmdFailureReason: string | undefined,
326+
): Promise<MemorySearchManagerResult> {
327+
const fallback = await getBuiltinMemorySearchManager(params);
328+
if (fallback.manager || !qmdFailureReason) {
329+
return fallback;
330+
}
331+
const fallbackError = fallback.error?.trim();
332+
return {
333+
manager: null,
334+
error: fallbackError
335+
? `${qmdFailureReason}; builtin fallback unavailable: ${fallbackError}`
336+
: qmdFailureReason,
337+
};
338+
}
339+
317340
async function getBuiltinMemorySearchManager(params: {
318341
cfg: OpenClawConfig;
319342
agentId: string;

extensions/qa-lab/api.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -106,5 +106,6 @@ export {
106106
type QaSuiteStartLabFn,
107107
type QaSuiteSummaryJson,
108108
type QaSuiteSummaryJsonParams,
109-
runQaSuite,
109+
runQaFlowSuite,
110110
} from "./src/suite.js";
111+
export { runQaSuite, type QaSuiteRuntimeResult } from "./src/suite-launch.runtime.js";

extensions/qa-lab/src/character-eval.test.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -127,6 +127,7 @@ async function makeSuiteResult(params: {
127127
);
128128
return {
129129
outputDir: params.outputDir,
130+
evidencePath: path.join(params.outputDir, "qa-evidence.json"),
130131
reportPath: path.join(params.outputDir, "qa-suite-report.md"),
131132
summaryPath,
132133
report: "# report",

extensions/qa-lab/src/character-eval.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -426,8 +426,8 @@ async function defaultRunJudge(params: {
426426
}
427427

428428
async function defaultRunSuite(params: Parameters<RunSuiteFn>[0]) {
429-
const { runQaSuiteFromRuntime } = await import("./suite-launch.runtime.js");
430-
return await runQaSuiteFromRuntime(params);
429+
const { runQaFlowSuiteFromRuntime } = await import("./suite-launch.runtime.js");
430+
return await runQaFlowSuiteFromRuntime(params);
431431
}
432432

433433
function renderCharacterEvalReport(params: {

extensions/qa-lab/src/cli-paths.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,18 @@ import fs from "node:fs/promises";
33
import path from "node:path";
44
import { assertNoSymlinkParents, pathScope } from "openclaw/plugin-sdk/security-runtime";
55

6+
export function toRepoPath(filePath: string): string {
7+
return filePath.split(path.sep).join("/");
8+
}
9+
10+
export function toRepoRelativePath(repoRoot: string, filePath: string): string {
11+
return toRepoPath(path.relative(repoRoot, filePath));
12+
}
13+
14+
export function isRepoRootRelativeRef(value: string) {
15+
return !path.isAbsolute(value) && value.split(/[\\/]+/u).every((part) => part !== "..");
16+
}
17+
618
export function resolveRepoRelativeOutputDir(repoRoot: string, outputDir?: string) {
719
if (!outputDir) {
820
return undefined;

0 commit comments

Comments
 (0)