feat(predictive-echo): Codeman integration + Layer 4 vm tests

terminal-ui.js: _localEchoPolicy ('buffer'|'predict'|'off') computed at the
end of _updateLocalEchoState with _localEchoEnabled keeping its exact 1.12.2
values; _predictHookOnData called as a plain statement between the buffer
block and Normal Mode (visual-only, try/catch, never returns, never touches
_pendingInput); classifyPredictInput + isCodexComposerRow (baseY-based,
measured /^> /-signature gate) on CodemanTerminalInput; construction beside
the LocalEchoOverlay from the separate bundle with graceful absence;
insertTerminalText/clearTerminalInput/setFontSize/applyTerminalSkin clear or
refresh predictions. app.js: fields + tab-switch and SSE-reconnect clears.
voice-input '\r' branch and keyboard-accessory sendKey clear predictions
(both bypass onData). sendEnterKey needs no change: codex falls through to
the immediate-flush branch.

Layer 4 vm tests: classify truth table (20 cases), composer-row gate incl.
the baseY pin, policy matrix with the 1.12.2 invariants untouched, wire
neutrality + throwing-predictor pins. Stale mobile keyboard codex-buffering
tests repointed at claude; new codex twin asserts write-through streaming,
prediction spans and TTL self-heal.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Codeman maintainer
2026-08-09 04:33:09 +02:00
parent 6c55ce3f8d
commit 5bde897752
6 changed files with 390 additions and 4 deletions
+54 -2
View File
@@ -841,7 +841,7 @@ describe('Virtual Keyboard', () => {
app.activeSessionId = 'mobile-visible-input-test';
app.sessions.set('mobile-visible-input-test', {
id: 'mobile-visible-input-test',
mode: 'codex',
mode: 'claude',
status: 'running',
});
app.hideWelcome();
@@ -887,7 +887,7 @@ describe('Virtual Keyboard', () => {
app.activeSessionId = 'mobile-cursor-fallback-test';
app.sessions.set('mobile-cursor-fallback-test', {
id: 'mobile-cursor-fallback-test',
mode: 'codex',
mode: 'claude',
status: 'running',
});
app.hideWelcome();
@@ -915,6 +915,58 @@ describe('Virtual Keyboard', () => {
expect(state.overlayState?.visible).toBe(true);
expect(state.overlayState?.promptPosition).not.toBeNull();
});
it('codex: streams keystrokes write-through and paints predictions (no buffering)', async () => {
await page.evaluate(async () => {
window.__sentInputs = [];
app.activeSessionId = 'mobile-codex-predict-test';
app.sessions.set('mobile-codex-predict-test', {
id: 'mobile-codex-predict-test',
mode: 'codex',
status: 'running',
});
app.hideWelcome();
app._sendInputAsync = (_sessionId: string, input: string) => {
window.__sentInputs.push(input);
};
const settings = app.loadAppSettingsFromStorage();
settings.cjkInputEnabled = false;
settings.localEchoEnabled = true;
app.saveAppSettingsToStorage(settings);
app._updateCjkInputState();
app._updateLocalEchoState();
// Paint a codex-like composer row so the predictWhen gate passes
app.terminal.reset();
await new Promise<void>((resolve) => app.terminal.write('\u203a ', resolve));
app.terminal.focus();
});
await page.locator('#terminalContainer').tap({ position: { x: 40, y: 40 } });
await page.keyboard.type('hey');
// Write-through: the keystrokes reach the send path BEFORE any Enter,
// and the buffer overlay holds nothing
await page.waitForFunction(() => window.__sentInputs?.join('') === 'hey');
const typed = await page.evaluate(() => ({
policy: app._localEchoPolicy,
pendingText: app._localEchoOverlay?.pendingText ?? '',
outstanding: app._predictiveEcho?.state.outstanding ?? -1,
spans: document.querySelectorAll('.xterm-screen [data-predictive-echo] span').length,
}));
expect(typed.policy).toBe('predict');
expect(typed.pendingText).toBe('');
expect(typed.outstanding).toBeGreaterThan(0);
expect(typed.spans).toBe(typed.outstanding);
// No echo ever arrives (stubbed session): TTL self-heals within ~1s
await page.waitForFunction(
() => document.querySelectorAll('.xterm-screen [data-predictive-echo] span').length === 0,
undefined,
{ timeout: 4000 }
);
const settled = await page.evaluate(() => app._predictiveEcho?.state.outstanding);
expect(settled).toBe(0);
});
});
// ── Cross-device keyboard behavior ────────────────────────────────────