@@ -87,6 +87,64 @@ const INLINE_DATA_IMAGE_RE = /^data:image\/[a-z0-9.+-]+;base64,/i;
8787const HOST_LOCAL_FILE_HREF_RE =
8888 / ^ (?: ~ \/ | \/ (?: U s e r s | h o m e | t m p | p r i v a t e \/ t m p | v a r \/ f o l d e r s | p r i v a t e \/ v a r \/ f o l d e r s ) \/ | \/ [ A - Z a - z ] : \/ | [ A - Z a - z ] : [ \\ / ] ) / ;
8989const markdownCache = new Map < string , string > ( ) ;
90+ const DOCS_BASE_URL = "https://docs.openclaw.ai" ;
91+ const DOCS_ROOT_PATH_SEGMENTS = new Set ( [
92+ "agent-runtime-architecture" ,
93+ "announcements" ,
94+ "auth-credential-semantics" ,
95+ "automation" ,
96+ "channels" ,
97+ "ci" ,
98+ "clawhub" ,
99+ "cli" ,
100+ "concepts" ,
101+ "date-time" ,
102+ "debug" ,
103+ "diagnostics" ,
104+ "gateway" ,
105+ "help" ,
106+ "install" ,
107+ "logging" ,
108+ "network" ,
109+ "nodes" ,
110+ "openclaw-agent-runtime" ,
111+ "platforms" ,
112+ "plugins" ,
113+ "prose" ,
114+ "providers" ,
115+ "reference" ,
116+ "security" ,
117+ "start" ,
118+ "tools" ,
119+ "vps" ,
120+ "web" ,
121+ ] ) ;
122+ const CONTROL_UI_APP_PATHS = new Set ( [
123+ "/activity" ,
124+ "/agents" ,
125+ "/ai-agents" ,
126+ "/appearance" ,
127+ "/automation" ,
128+ "/channels" ,
129+ "/chat" ,
130+ "/communications" ,
131+ "/config" ,
132+ "/cron" ,
133+ "/debug" ,
134+ "/dreaming" ,
135+ "/dreams" ,
136+ "/infrastructure" ,
137+ "/instances" ,
138+ "/logs" ,
139+ "/mcp" ,
140+ "/nodes" ,
141+ "/overview" ,
142+ "/sessions" ,
143+ "/skills" ,
144+ "/skills/workshop" ,
145+ "/usage" ,
146+ "/workboard" ,
147+ ] ) ;
90148const TAIL_LINK_BLUR_CLASS = "chat-link-tail-blur" ;
91149const FENCE_OPEN_RE = / ^ [ \t ] { 0 , 3 } ( ` { 3 , } | ~ { 3 , } ) / ;
92150const FENCE_CONTAINER_PREFIX_RE = / ^ [ \t ] { 0 , 3 } (?: (?: > \s ? ) | (?: (?: [ - + * ] | \d { 1 , 9 } [ . ) ] ) [ \t ] + ) ) / ;
@@ -143,6 +201,22 @@ function isHostLocalFileHref(href: string): boolean {
143201 return HOST_LOCAL_FILE_HREF_RE . test ( href . trim ( ) ) ;
144202}
145203
204+ function normalizeDocsRootHref ( href : string ) : string | null {
205+ if ( ! href . startsWith ( "/" ) || href . startsWith ( "//" ) ) {
206+ return null ;
207+ }
208+ const parsed = new URL ( href , DOCS_BASE_URL ) ;
209+ const normalizedPath = parsed . pathname . replace ( / \/ + $ / , "" ) || "/" ;
210+ if ( CONTROL_UI_APP_PATHS . has ( normalizedPath ) || normalizedPath . startsWith ( "/api/" ) ) {
211+ return null ;
212+ }
213+ const firstSegment = normalizedPath . split ( "/" ) . find ( Boolean ) ;
214+ if ( ! firstSegment || ! DOCS_ROOT_PATH_SEGMENTS . has ( firstSegment ) ) {
215+ return null ;
216+ }
217+ return `${ DOCS_BASE_URL } ${ parsed . pathname } ${ parsed . search } ${ parsed . hash } ` ;
218+ }
219+
146220function installHooks ( ) {
147221 if ( hooksInstalled ) {
148222 return ;
@@ -176,6 +250,11 @@ function installHooks() {
176250 // javascript: by default. This is defense-in-depth.
177251 }
178252
253+ const normalizedDocsHref = normalizeDocsRootHref ( href ) ;
254+ if ( normalizedDocsHref ) {
255+ node . setAttribute ( "href" , normalizedDocsHref ) ;
256+ }
257+
179258 node . setAttribute ( "rel" , "noreferrer noopener" ) ;
180259 node . setAttribute ( "target" , "_blank" ) ;
181260 if ( normalizeLowercaseStringOrEmpty ( href ) . includes ( "tail" ) ) {
0 commit comments