Canvas: improve A2UI asset resolution and empty state (#20312)

Merged via /review-pr -> /prepare-pr -> /merge-pr.

Prepared head SHA: adce485695
Co-authored-by: mbelinky <132747814+mbelinky@users.noreply.github.com>
Co-authored-by: mbelinky <132747814+mbelinky@users.noreply.github.com>
Reviewed-by: @mbelinky
This commit is contained in:
Mariano
2026-02-18 19:44:55 +00:00
committed by GitHub
parent fe3f0759b5
commit 264131eb9f
3 changed files with 24 additions and 3 deletions

View File

@@ -14,6 +14,7 @@ Docs: https://docs.openclaw.ai
### Fixes
- Canvas/A2UI: improve bundled-asset resolution and empty-state handling so UI fallbacks render reliably. (#20312) Thanks @mbelinky.
- UI/Sessions: accept the canonical main session-key alias in Chat UI flows so main-session routing stays consistent. (#20311) Thanks @mbelinky.
- iOS/Onboarding: prevent pairing-status flicker during auto-resume by keeping resumed state transitions stable. (#20310) Thanks @mbelinky.
- OpenClawKit/Protocol: preserve JSON boolean literals (`true`/`false`) when bridging through `AnyCodable` so Apple client RPC params no longer re-encode booleans as `1`/`0`. Thanks @mbelinky.

View File

@@ -451,7 +451,6 @@ class OpenClawA2UIHost extends LitElement {
if (this.surfaces.length === 0) {
return html`<div class="empty">
<div class="empty-title">Canvas (A2UI)</div>
<div>Waiting for A2UI messages…</div>
</div>`;
}

View File

@@ -13,14 +13,29 @@ export const CANVAS_WS_PATH = "/__openclaw__/ws";
let cachedA2uiRootReal: string | null | undefined;
let resolvingA2uiRoot: Promise<string | null> | null = null;
let cachedA2uiResolvedAtMs = 0;
const A2UI_ROOT_RETRY_NULL_AFTER_MS = 10_000;
async function resolveA2uiRoot(): Promise<string | null> {
const here = path.dirname(fileURLToPath(import.meta.url));
const entryDir = process.argv[1] ? path.dirname(path.resolve(process.argv[1])) : null;
const candidates = [
// Running from source (bun) or dist (tsc + copied assets).
// Running from source (bun) or dist/canvas-host chunk.
path.resolve(here, "a2ui"),
// Running from dist root chunk (common launchd path).
path.resolve(here, "canvas-host/a2ui"),
path.resolve(here, "../canvas-host/a2ui"),
// Entry path fallbacks (helps when cwd is not the repo root).
...(entryDir
? [
path.resolve(entryDir, "a2ui"),
path.resolve(entryDir, "canvas-host/a2ui"),
path.resolve(entryDir, "../canvas-host/a2ui"),
]
: []),
// Running from dist without copied assets (fallback to source).
path.resolve(here, "../../src/canvas-host/a2ui"),
path.resolve(here, "../src/canvas-host/a2ui"),
// Running from repo root.
path.resolve(process.cwd(), "src/canvas-host/a2ui"),
path.resolve(process.cwd(), "dist/canvas-host/a2ui"),
@@ -44,13 +59,19 @@ async function resolveA2uiRoot(): Promise<string | null> {
}
async function resolveA2uiRootReal(): Promise<string | null> {
if (cachedA2uiRootReal !== undefined) {
const nowMs = Date.now();
if (
cachedA2uiRootReal !== undefined &&
(cachedA2uiRootReal !== null || nowMs - cachedA2uiResolvedAtMs < A2UI_ROOT_RETRY_NULL_AFTER_MS)
) {
return cachedA2uiRootReal;
}
if (!resolvingA2uiRoot) {
resolvingA2uiRoot = (async () => {
const root = await resolveA2uiRoot();
cachedA2uiRootReal = root ? await fs.realpath(root) : null;
cachedA2uiResolvedAtMs = Date.now();
resolvingA2uiRoot = null;
return cachedA2uiRootReal;
})();
}