From 955270fb73a16fd7378254ea05c1eac0f72b403b Mon Sep 17 00:00:00 2001 From: Vincent Koc Date: Mon, 13 Apr 2026 23:49:59 +0100 Subject: [PATCH] fix(ci): repair telegram ui and watch regressions --- .../telegram/src/topic-name-cache.test.ts | 15 ++++++++--- scripts/run-node.mjs | 10 +++++++ src/canvas-host/a2ui/.bundle.hash | 2 +- src/infra/run-node.test.ts | 26 +++++++++++++++++++ ui/src/ui/navigation.browser.test.ts | 6 ++--- 5 files changed, 52 insertions(+), 7 deletions(-) diff --git a/extensions/telegram/src/topic-name-cache.test.ts b/extensions/telegram/src/topic-name-cache.test.ts index b11dff4be5c..35afd6cf175 100644 --- a/extensions/telegram/src/topic-name-cache.test.ts +++ b/extensions/telegram/src/topic-name-cache.test.ts @@ -1,4 +1,4 @@ -import { describe, expect, it, beforeEach } from "vitest"; +import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; import { clearTopicNameCache, getTopicEntry, @@ -9,9 +9,14 @@ import { describe("topic-name-cache", () => { beforeEach(() => { + vi.useRealTimers(); clearTopicNameCache(); }); + afterEach(() => { + vi.useRealTimers(); + }); + it("stores and retrieves a topic name", () => { updateTopicName(-100123, 42, { name: "Deployments" }); expect(getTopicName(-100123, 42)).toBe("Deployments"); @@ -58,9 +63,11 @@ describe("topic-name-cache", () => { }); it("updates timestamps on write", async () => { + vi.useFakeTimers(); + vi.setSystemTime(new Date("2026-04-13T22:00:00.000Z")); updateTopicName(-100123, 42, { name: "A" }); const t1 = getTopicEntry(-100123, 42)?.updatedAt ?? 0; - await new Promise((r) => setTimeout(r, 10)); + await vi.advanceTimersByTimeAsync(10); updateTopicName(-100123, 42, { name: "B" }); const t2 = getTopicEntry(-100123, 42)?.updatedAt ?? 0; expect(t2).toBeGreaterThan(t1); @@ -81,8 +88,10 @@ describe("topic-name-cache", () => { }); it("refreshes recency on read so active topics survive eviction", async () => { + vi.useFakeTimers(); + vi.setSystemTime(new Date("2026-04-13T22:00:00.000Z")); updateTopicName(-100000, 1, { name: "Active" }); - await new Promise((r) => setTimeout(r, 10)); + await vi.advanceTimersByTimeAsync(10); for (let i = 2; i <= 2048; i++) { updateTopicName(-100000, i, { name: `Topic ${i}` }); } diff --git a/scripts/run-node.mjs b/scripts/run-node.mjs index 43568017860..abc9e0550fc 100644 --- a/scripts/run-node.mjs +++ b/scripts/run-node.mjs @@ -17,6 +17,10 @@ const compilerArgs = [buildScript, "--no-clean"]; const runNodeSourceRoots = ["src", BUNDLED_PLUGIN_ROOT_DIR]; const runNodeConfigFiles = ["tsconfig.json", "package.json", "tsdown.config.ts"]; export const runNodeWatchedPaths = [...runNodeSourceRoots, ...runNodeConfigFiles]; +const ignoredRunNodeRepoPaths = new Set([ + "src/canvas-host/a2ui/.bundle.hash", + "src/canvas-host/a2ui/a2ui.bundle.js", +]); const extensionSourceFilePattern = /\.(?:[cm]?[jt]sx?)$/; const extensionRestartMetadataFiles = new Set(["openclaw.plugin.json", "package.json"]); @@ -38,6 +42,9 @@ const isBuildRelevantSourcePath = (relativePath) => { export const isBuildRelevantRunNodePath = (repoPath) => { const normalizedPath = normalizePath(repoPath).replace(/^\.\/+/, ""); + if (ignoredRunNodeRepoPaths.has(normalizedPath)) { + return false; + } if (runNodeConfigFiles.includes(normalizedPath)) { return true; } @@ -60,6 +67,9 @@ const isRestartRelevantExtensionPath = (relativePath) => { export const isRestartRelevantRunNodePath = (repoPath) => { const normalizedPath = normalizePath(repoPath).replace(/^\.\/+/, ""); + if (ignoredRunNodeRepoPaths.has(normalizedPath)) { + return false; + } if (runNodeConfigFiles.includes(normalizedPath)) { return true; } diff --git a/src/canvas-host/a2ui/.bundle.hash b/src/canvas-host/a2ui/.bundle.hash index b2d78db7154..65aea792d1b 100644 --- a/src/canvas-host/a2ui/.bundle.hash +++ b/src/canvas-host/a2ui/.bundle.hash @@ -1 +1 @@ -e8d410067136069ba072e3b325e62c31cd0421499aea202823b4b99cbbc961d8 +cd6eb24a2a2a09f6c4e06cb58120b1faf9f9270c3f636ac1179ce8f5f07cda58 diff --git a/src/infra/run-node.test.ts b/src/infra/run-node.test.ts index 481f872f8d2..87e21acae6b 100644 --- a/src/infra/run-node.test.ts +++ b/src/infra/run-node.test.ts @@ -15,6 +15,8 @@ const ROOT_SRC = "src/index.ts"; const ROOT_TSCONFIG = "tsconfig.json"; const ROOT_PACKAGE = "package.json"; const ROOT_TSDOWN = "tsdown.config.ts"; +const GENERATED_A2UI_BUNDLE = "src/canvas-host/a2ui/a2ui.bundle.js"; +const GENERATED_A2UI_BUNDLE_HASH = "src/canvas-host/a2ui/.bundle.hash"; const DIST_ENTRY = "dist/entry.js"; const BUILD_STAMP = "dist/.buildstamp"; const EXTENSION_SRC = bundledPluginFile("demo", "src/index.ts"); @@ -608,6 +610,30 @@ describe("run-node script", () => { }); }); + it("ignores dirty generated A2UI bundle artifacts when dist is current", async () => { + await withTempDir({ prefix: "openclaw-run-node-" }, async (tmp) => { + await setupTrackedProject(tmp, { + files: { + [ROOT_SRC]: "export const value = 1;\n", + }, + oldPaths: [ROOT_SRC, ROOT_TSCONFIG, ROOT_PACKAGE], + buildPaths: [DIST_ENTRY, BUILD_STAMP], + }); + + const requirement = resolveBuildRequirement( + createBuildRequirementDeps(tmp, { + gitHead: "abc123\n", + gitStatus: ` M ${GENERATED_A2UI_BUNDLE_HASH}\n M ${GENERATED_A2UI_BUNDLE}\n`, + }), + ); + + expect(requirement).toEqual({ + shouldBuild: false, + reason: "clean", + }); + }); + }); + it("repairs missing bundled plugin metadata without rerunning tsdown", async () => { await withTempDir({ prefix: "openclaw-run-node-" }, async (tmp) => { await setupTrackedProject(tmp, { diff --git a/ui/src/ui/navigation.browser.test.ts b/ui/src/ui/navigation.browser.test.ts index e63205ac09e..6b8a25b15e9 100644 --- a/ui/src/ui/navigation.browser.test.ts +++ b/ui/src/ui/navigation.browser.test.ts @@ -239,7 +239,7 @@ describe("control UI routing", () => { expect(header.querySelector(".nav-collapse-toggle")).not.toBeNull(); }); - it("resets to the main session when opening chat from sidebar navigation", async () => { + it("preserves the active session when opening chat from sidebar navigation", async () => { const app = mountApp("/sessions?session=agent:main:subagent:task-123"); await app.updateComplete; @@ -249,9 +249,9 @@ describe("control UI routing", () => { await app.updateComplete; expect(app.tab).toBe("chat"); - expect(app.sessionKey).toBe("main"); + expect(app.sessionKey).toBe("agent:main:subagent:task-123"); expect(window.location.pathname).toBe("/chat"); - expect(window.location.search).toBe("?session=main"); + expect(window.location.search).toBe("?session=agent%3Amain%3Asubagent%3Atask-123"); }); it("keeps chat and nav usable on narrow viewports", async () => {