From 60ab9f1f4d5f08e86e051a5c0dfc559b40c02638 Mon Sep 17 00:00:00 2001 From: Warfront1 <1920038+Warfront1@users.noreply.github.com> Date: Thu, 8 Oct 2026 01:05:04 -0400 Subject: [PATCH] Updates to v0.11.4 Upgrade to upstream v0.11.4; add preprocessor call-arg masker to fix terminal double-prefix regression * Update UPSTREAM_VERSION, docker-compose.yml, and e2e compose files to v0.11.4 * Fix v0.11.4 terminal double-prefix: makeCallArgMasker() masks terminalRequest(...) arg spans in the preprocessor script hook so catch-all ${base} injection cannot double-prefix paths that terminalRequest already base-prefixed via connection.baseUrl --- UPSTREAM_VERSION | 2 +- docker-compose.yml | 2 +- e2e/docker-compose.e2e-direct.yml | 2 +- e2e/docker-compose.e2e-nginx.yml | 2 +- inject/src/lib/root-path-preprocess.js | 83 +++++++++++++++++++++++++- 5 files changed, 85 insertions(+), 6 deletions(-) diff --git a/UPSTREAM_VERSION b/UPSTREAM_VERSION index 1417ff6..edf6d96 100644 --- a/UPSTREAM_VERSION +++ b/UPSTREAM_VERSION @@ -1 +1 @@ -v0.11.3 \ No newline at end of file +v0.11.4 \ No newline at end of file diff --git a/docker-compose.yml b/docker-compose.yml index a9873e2..05e96db 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -6,7 +6,7 @@ services: context: . args: # Read from UPSTREAM_VERSION file: UPSTREAM_VERSION=$(cat UPSTREAM_VERSION) docker compose build - UPSTREAM_VERSION: ${UPSTREAM_VERSION:-v0.11.3} + UPSTREAM_VERSION: ${UPSTREAM_VERSION:-v0.11.4} environment: - WEBUI_ROOT_PATH=/openwebui - ENABLE_PERSISTENT_CONFIG=False diff --git a/e2e/docker-compose.e2e-direct.yml b/e2e/docker-compose.e2e-direct.yml index cde3d76..50ebea4 100644 --- a/e2e/docker-compose.e2e-direct.yml +++ b/e2e/docker-compose.e2e-direct.yml @@ -3,7 +3,7 @@ services: build: context: .. args: - UPSTREAM_VERSION: ${UPSTREAM_VERSION:-v0.11.3} + UPSTREAM_VERSION: ${UPSTREAM_VERSION:-v0.11.4} environment: - WEBUI_ROOT_PATH=/openwebui - ENABLE_PERSISTENT_CONFIG=False diff --git a/e2e/docker-compose.e2e-nginx.yml b/e2e/docker-compose.e2e-nginx.yml index 6a0d8ea..b7eebe1 100644 --- a/e2e/docker-compose.e2e-nginx.yml +++ b/e2e/docker-compose.e2e-nginx.yml @@ -3,7 +3,7 @@ services: build: context: .. args: - UPSTREAM_VERSION: ${UPSTREAM_VERSION:-v0.11.3} + UPSTREAM_VERSION: ${UPSTREAM_VERSION:-v0.11.4} environment: - WEBUI_ROOT_PATH=/openwebui - ENABLE_PERSISTENT_CONFIG=False diff --git a/inject/src/lib/root-path-preprocess.js b/inject/src/lib/root-path-preprocess.js index 6211862..cdff749 100644 --- a/inject/src/lib/root-path-preprocess.js +++ b/inject/src/lib/root-path-preprocess.js @@ -4,6 +4,84 @@ function escapeRegex(string) { return string.replace(/[.*+?^${}()|[\]\\\/]/g, '\\$&'); } +// Masks the argument spans of helper calls that already prepend a base-prefixed +// URL (e.g. terminalRequest(connection, chatId, `/api/terminals/...`) prepends +// WEBUI_API_BASE_URL/terminals/). The catch-all template-literal rule would +// otherwise inject ${base} into those paths, producing double-prefixed URLs. +// Call sites live in .svelte files only; plain .ts modules are never preprocessed. +let callArgMaskSeq = 0; + +// start points at the opening char; returns the index just past the matching +// close char, or -1 if unbalanced. Quote-aware (' " `). +function skipBalanced(text, start, open, close) { + let depth = 0; + let inString = null; + for (let i = start; i < text.length; i++) { + const ch = text[i]; + if (inString) { + if (ch === '\\') { + i++; + } else if (ch === inString) { + inString = null; + } + } else if (ch === "'" || ch === '"' || ch === '`') { + inString = ch; + } else if (ch === open) { + depth++; + } else if (ch === close && --depth === 0) { + return i + 1; + } + } + return -1; +} + +export function makeCallArgMasker(helperNames) { + const masks = []; + const tokenBase = `__RPP_MASK_${callArgMaskSeq++}_`; + + function mask(text) { + const spans = []; + for (const helperName of helperNames) { + const re = new RegExp(`\\b${helperName}\\b`, 'g'); + let match; + while ((match = re.exec(text)) !== null) { + // Skip property accesses like x.terminalRequest(...) + if (match.index > 0 && /[\w$.]/.test(text[match.index - 1])) continue; + let j = match.index + match[0].length; + // Optional generic type params, e.g. terminalRequest(...) + if (text[j] === '<') { + j = skipBalanced(text, j, '<', '>'); + if (j === -1) continue; + } + if (text[j] !== '(') continue; + const end = skipBalanced(text, j, '(', ')'); + if (end === -1) continue; + spans.push([j + 1, end - 1]); // span inside the parens, ')' excluded + } + } + let result = ''; + let cursor = 0; + spans.forEach(([from, to], n) => { + const token = `${tokenBase}${n}__`; + result += text.slice(cursor, from) + token; + masks.push([token, text.slice(from, to)]); + cursor = to; + }); + return result + text.slice(cursor); + } + + function unmask(code) { + let out = code; + for (const [token, args] of masks) { + const at = out.indexOf(token); + if (at !== -1) out = out.slice(0, at) + args + out.slice(at + token.length); + } + return out; + } + + return { mask, unmask }; +} + export function rootPathPreprocess() { return { name: 'root-path-preprocess', @@ -11,7 +89,8 @@ export function rootPathPreprocess() { script({ content, filename, attributes }) { if (!content) return; - let modified = content; + const argMasker = makeCallArgMasker(['terminalRequest']); + let modified = argMasker.mask(content); let needsBase = false; const isModuleScript = attributes && attributes.context === 'module'; @@ -168,7 +247,7 @@ export function rootPathPreprocess() { if (!needsBase) return undefined; - return { code: modified }; + return { code: argMasker.unmask(modified) }; }, markup({ content, filename }) {