diff --git a/CHANGELOG.md b/CHANGELOG.md index 0b0f74ece8..348adb01b9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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. diff --git a/apps/shared/OpenClawKit/Tools/CanvasA2UI/bootstrap.js b/apps/shared/OpenClawKit/Tools/CanvasA2UI/bootstrap.js index 563adcc3b1..a9cb659876 100644 --- a/apps/shared/OpenClawKit/Tools/CanvasA2UI/bootstrap.js +++ b/apps/shared/OpenClawKit/Tools/CanvasA2UI/bootstrap.js @@ -451,7 +451,6 @@ class OpenClawA2UIHost extends LitElement { if (this.surfaces.length === 0) { return html`
Canvas (A2UI)
-
Waiting for A2UI messages…
`; } diff --git a/src/canvas-host/a2ui.ts b/src/canvas-host/a2ui.ts index 0f65ab67ed..bac09a4438 100644 --- a/src/canvas-host/a2ui.ts +++ b/src/canvas-host/a2ui.ts @@ -13,14 +13,29 @@ export const CANVAS_WS_PATH = "/__openclaw__/ws"; let cachedA2uiRootReal: string | null | undefined; let resolvingA2uiRoot: Promise | null = null; +let cachedA2uiResolvedAtMs = 0; +const A2UI_ROOT_RETRY_NULL_AFTER_MS = 10_000; async function resolveA2uiRoot(): Promise { 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 { } async function resolveA2uiRootReal(): Promise { - 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; })(); }