diff --git a/scripts/build.mjs b/scripts/build.mjs
index 830d5ee1..eac90a18 100644
--- a/scripts/build.mjs
+++ b/scripts/build.mjs
@@ -65,6 +65,22 @@ appendFileSync(
'}\n'
);
+// Predictive echo (codex): separate bundle so the zerolag bundle stays byte-identical
+run('xterm-predictive-echo', 'npx esbuild packages/xterm-zerolag-input/src/predictive-echo-addon.ts --bundle --minify --format=iife --global-name=XtermPredictiveEcho --outfile=dist/web/public/vendor/xterm-predictive-echo.js');
+appendFileSync(
+ join(ROOT, 'dist/web/public/vendor/xterm-predictive-echo.js'),
+ '\n// Global aliases for browser usage\n' +
+ 'if(typeof window!=="undefined"){' +
+ 'window.PredictiveEchoAddon=XtermPredictiveEcho.PredictiveEchoAddon;' +
+ 'window.PredictiveEchoOverlay=class extends XtermPredictiveEcho.PredictiveEchoAddon{' +
+ 'constructor(terminal){' +
+ 'super({});' +
+ 'this.activate(terminal);' +
+ '}' +
+ '};' +
+ '}\n'
+);
+
// 4. Minify frontend assets
run('minify input-cjk.js', 'npx esbuild dist/web/public/input-cjk.js --minify --outfile=dist/web/public/input-cjk.js --allow-overwrite');
run('minify i18n.js', 'npx esbuild dist/web/public/i18n.js --minify --outfile=dist/web/public/i18n.js --allow-overwrite');
@@ -106,6 +122,7 @@ console.log('\n[build] content-hash cache busting');
'subagent-windows.js',
'image-input.js',
'vendor/xterm-zerolag-input.js',
+ 'vendor/xterm-predictive-echo.js',
];
const manifest = {};
for (const file of HASHABLE) {
diff --git a/scripts/postinstall.js b/scripts/postinstall.js
index 7654705f..02d23f59 100644
--- a/scripts/postinstall.js
+++ b/scripts/postinstall.js
@@ -304,6 +304,35 @@ if (isGlobalInstall) {
} catch {
console.log(colors.yellow('⚠ Failed to bundle xterm-zerolag-input — overlay may not work in dev mode'));
}
+
+ // Predictive echo (codex): SEPARATE bundle so the zerolag bundle above stays
+ // byte-identical. If this file is missing or broken, codex simply falls back
+ // to plain PTY echo (pre-predictive behavior); nothing else is affected.
+ try {
+ const predSrc = join(import.meta.dirname, '..', 'packages', 'xterm-zerolag-input', 'src', 'predictive-echo-addon.ts');
+ const predOut = join(vendorDir, 'xterm-predictive-echo.js');
+ execSync(
+ `npx esbuild "${predSrc}" --bundle --format=iife --global-name=XtermPredictiveEcho --outfile="${predOut}"`,
+ { stdio: 'pipe' }
+ );
+ const { appendFileSync } = await import('fs');
+ appendFileSync(
+ predOut,
+ '\n// Global aliases for browser usage\n' +
+ 'if(typeof window!=="undefined"){' +
+ 'window.PredictiveEchoAddon=XtermPredictiveEcho.PredictiveEchoAddon;' +
+ 'window.PredictiveEchoOverlay=class extends XtermPredictiveEcho.PredictiveEchoAddon{' +
+ 'constructor(terminal){' +
+ 'super({});' +
+ 'this.activate(terminal);' +
+ '}' +
+ '};' +
+ '}\n'
+ );
+ console.log(colors.green('✓ xterm-predictive-echo bundled to vendor/'));
+ } catch (e) {
+ console.log(colors.yellow('⚠ predictive-echo bundle failed (codex uses plain echo): ' + e.message));
+ }
} catch (err) {
hasWarnings = true;
console.log(colors.yellow('⚠ Failed to copy xterm vendor files'));
diff --git a/src/web/public/index.html b/src/web/public/index.html
index 48cf978f..adebd440 100644
--- a/src/web/public/index.html
+++ b/src/web/public/index.html
@@ -38,6 +38,7 @@
+
diff --git a/src/web/public/sw.js b/src/web/public/sw.js
index ca1b2f12..454d96b1 100644
--- a/src/web/public/sw.js
+++ b/src/web/public/sw.js
@@ -40,6 +40,7 @@ const APP_SHELL = [
'/vendor/xterm-addon-fit.min.js',
'/vendor/xterm-addon-unicode11.min.js',
'/vendor/xterm-zerolag-input.js',
+ '/vendor/xterm-predictive-echo.js',
'/vendor/xterm.css',
'/icon-192.png',
'/icon-512.png',