fix(telegram): avoid text runtime import cycle

This commit is contained in:
MaxxxDong 2026-03-21 12:42:29 +08:00
parent 78aad71d75
commit 48badc856a
2 changed files with 62 additions and 21 deletions

View File

@ -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 = /<a\s+href="https?:\/\/([^"]+)"[^>]*>\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}<code>${escapeHtml(tld)}</code>`,
);
}

View File

@ -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<void> | 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();
}