From 48badc856af9a0bb7f1b37a3ded0b81448228ec3 Mon Sep 17 00:00:00 2001 From: MaxxxDong <186893345+MaxxxDong@users.noreply.github.com> Date: Sat, 21 Mar 2026 12:42:29 +0800 Subject: [PATCH] fix(telegram): avoid text runtime import cycle --- extensions/telegram/src/format.ts | 39 +++++++++++++++++++-------- src/logging/diagnostic.ts | 44 ++++++++++++++++++++++++------- 2 files changed, 62 insertions(+), 21 deletions(-) diff --git a/extensions/telegram/src/format.ts b/extensions/telegram/src/format.ts index 4d14f179b2f..dcaeca8a37c 100644 --- a/extensions/telegram/src/format.ts +++ b/extensions/telegram/src/format.ts @@ -103,16 +103,32 @@ function escapeRegex(str: string): string { return str.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); } -const FILE_EXTENSIONS_PATTERN = Array.from(FILE_REF_EXTENSIONS_WITH_TLD).map(escapeRegex).join("|"); +type FileReferencePatterns = { + fileReferencePattern: RegExp; + orphanedTldPattern: RegExp; +}; + +let cachedFileReferencePatterns: FileReferencePatterns | null = null; + +function getFileReferencePatterns(): FileReferencePatterns { + if (cachedFileReferencePatterns) { + return cachedFileReferencePatterns; + } + const fileExtensionsPattern = Array.from(FILE_REF_EXTENSIONS_WITH_TLD).map(escapeRegex).join("|"); + cachedFileReferencePatterns = { + fileReferencePattern: new RegExp( + `(^|[^a-zA-Z0-9_\\-/])([a-zA-Z0-9_.\\-./]+\\.(?:${fileExtensionsPattern}))(?=$|[^a-zA-Z0-9_\\-/])`, + "gi", + ), + orphanedTldPattern: new RegExp( + `([^a-zA-Z0-9]|^)([A-Za-z]\\.(?:${fileExtensionsPattern}))(?=[^a-zA-Z0-9/]|$)`, + "g", + ), + }; + return cachedFileReferencePatterns; +} + const AUTO_LINKED_ANCHOR_PATTERN = /]*>\1<\/a>/gi; -const FILE_REFERENCE_PATTERN = new RegExp( - `(^|[^a-zA-Z0-9_\\-/])([a-zA-Z0-9_.\\-./]+\\.(?:${FILE_EXTENSIONS_PATTERN}))(?=$|[^a-zA-Z0-9_\\-/])`, - "gi", -); -const ORPHANED_TLD_PATTERN = new RegExp( - `([^a-zA-Z0-9]|^)([A-Za-z]\\.(?:${FILE_EXTENSIONS_PATTERN}))(?=[^a-zA-Z0-9/]|$)`, - "g", -); const HTML_TAG_PATTERN = /(<\/?)([a-zA-Z][a-zA-Z0-9-]*)\b[^>]*?>/gi; function wrapStandaloneFileRef(match: string, prefix: string, filename: string): string { @@ -134,8 +150,9 @@ function wrapSegmentFileRefs( if (!text || codeDepth > 0 || preDepth > 0 || anchorDepth > 0) { return text; } - const wrappedStandalone = text.replace(FILE_REFERENCE_PATTERN, wrapStandaloneFileRef); - return wrappedStandalone.replace(ORPHANED_TLD_PATTERN, (match, prefix: string, tld: string) => + const { fileReferencePattern, orphanedTldPattern } = getFileReferencePatterns(); + const wrappedStandalone = text.replace(fileReferencePattern, wrapStandaloneFileRef); + return wrappedStandalone.replace(orphanedTldPattern, (match, prefix: string, tld: string) => prefix === ">" ? match : `${prefix}${escapeHtml(tld)}`, ); } diff --git a/src/logging/diagnostic.ts b/src/logging/diagnostic.ts index 2fb2f2f6ed6..3a75563bbc0 100644 --- a/src/logging/diagnostic.ts +++ b/src/logging/diagnostic.ts @@ -1,4 +1,3 @@ -import { loadConfig } from "../config/config.js"; import type { OpenClawConfig } from "../config/config.js"; import { emitDiagnosticEvent } from "../infra/diagnostic-events.js"; import { @@ -10,9 +9,35 @@ import { type SessionRef, type SessionStateValue, } from "./diagnostic-session-state.js"; -import { createSubsystemLogger } from "./subsystem.js"; +import { createSubsystemLogger, type SubsystemLogger } from "./subsystem.js"; -const diag = createSubsystemLogger("diagnostic"); +let diagnosticLoggerInstance: SubsystemLogger | null = null; +let cachedLoadedDiagnosticConfig: OpenClawConfig | undefined; +let diagnosticConfigRefreshPromise: Promise | null = null; + +function getDiagnosticLogger(): SubsystemLogger { + diagnosticLoggerInstance ??= createSubsystemLogger("diagnostic"); + return diagnosticLoggerInstance; +} + +const diag = new Proxy({} as SubsystemLogger, { + get(_target, prop, receiver) { + return Reflect.get(getDiagnosticLogger() as object, prop, receiver); + }, +}); + +function refreshDiagnosticConfigSnapshot(): void { + diagnosticConfigRefreshPromise ??= import("../config/config.js") + .then(({ loadConfig }) => { + cachedLoadedDiagnosticConfig = loadConfig(); + }) + .catch(() => { + cachedLoadedDiagnosticConfig = undefined; + }) + .finally(() => { + diagnosticConfigRefreshPromise = null; + }); +} const webhookStats = { received: 0, @@ -335,13 +360,9 @@ export function startDiagnosticHeartbeat(config?: OpenClawConfig) { return; } heartbeatInterval = setInterval(() => { - let heartbeatConfig = config; - if (!heartbeatConfig) { - try { - heartbeatConfig = loadConfig(); - } catch { - heartbeatConfig = undefined; - } + let heartbeatConfig = config ?? cachedLoadedDiagnosticConfig; + if (!heartbeatConfig && !diagnosticConfigRefreshPromise) { + refreshDiagnosticConfigSnapshot(); } const stuckSessionWarnMs = resolveStuckSessionWarnMs(heartbeatConfig); const now = Date.now(); @@ -427,6 +448,9 @@ export function resetDiagnosticStateForTest(): void { webhookStats.errors = 0; webhookStats.lastReceived = 0; lastActivityAt = 0; + cachedLoadedDiagnosticConfig = undefined; + diagnosticConfigRefreshPromise = null; + diagnosticLoggerInstance = null; stopDiagnosticHeartbeat(); }