mirror of
https://github.com/Ark0N/Codeman.git
synced 2026-10-03 14:09:42 +02:00
chore: bump version to 0.1498
This commit is contained in:
+96
-1
@@ -1242,6 +1242,11 @@ class ClaudemanApp {
|
||||
// Sequential input send chain — ensures keystroke ordering across async fetches
|
||||
this._inputSendChain = Promise.resolve();
|
||||
|
||||
// Speculative local echo (mosh-style) — echo printable chars immediately,
|
||||
// strip redundant server echo when it arrives. Only active during idle state.
|
||||
this._localEchoBuffer = '';
|
||||
this._localEchoTimeout = null;
|
||||
|
||||
// Accessibility: Focus trap for modals
|
||||
this.activeFocusTrap = null;
|
||||
|
||||
@@ -1571,6 +1576,24 @@ class ClaudemanApp {
|
||||
// Patterns: \x1b[?...c (DA1), \x1b[>...c (DA2), \x1b[...R (CPR), \x1b[...n (DSR)
|
||||
if (/^\x1b\[[\?>=]?[\d;]*[cnR]$/.test(data)) return;
|
||||
|
||||
// Speculative local echo: show printable chars instantly during idle state
|
||||
const session = this.sessions.get(this.activeSessionId);
|
||||
const isIdle = session && session.status === 'idle';
|
||||
if (isIdle && data.length === 1) {
|
||||
const code = data.charCodeAt(0);
|
||||
if (code >= 32 && code < 127) {
|
||||
// Printable ASCII — echo immediately, strip server echo later
|
||||
this.terminal.write(data);
|
||||
this._localEchoBuffer += data;
|
||||
this._resetLocalEchoTimeout();
|
||||
} else if ((code === 0x7f || code === 0x08) && this._localEchoBuffer.length > 0) {
|
||||
// Backspace — undo last local echo char
|
||||
this._localEchoBuffer = this._localEchoBuffer.slice(0, -1);
|
||||
this.terminal.write('\b \b');
|
||||
this._resetLocalEchoTimeout();
|
||||
}
|
||||
}
|
||||
|
||||
this._pendingInput += data;
|
||||
|
||||
// Flush immediately for control characters (Enter, Ctrl+C, etc.)
|
||||
@@ -1843,7 +1866,13 @@ class ClaudemanApp {
|
||||
// xterm.js internally batches multiple write() calls within same frame
|
||||
for (const segment of segments) {
|
||||
if (segment && !segment.startsWith(DEC_SYNC_START)) {
|
||||
this.terminal.write(segment);
|
||||
// Strip locally-echoed characters from server output to prevent doubles
|
||||
if (this._localEchoBuffer.length > 0) {
|
||||
const stripped = this._stripLocalEcho(segment);
|
||||
if (stripped) this.terminal.write(stripped);
|
||||
} else {
|
||||
this.terminal.write(segment);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2250,6 +2279,11 @@ class ClaudemanApp {
|
||||
const session = this.sessions.get(data.id);
|
||||
if (session) {
|
||||
session.status = 'busy';
|
||||
// Clear local echo buffer — server is now generating output
|
||||
if (data.id === this.activeSessionId) {
|
||||
this._localEchoBuffer = '';
|
||||
this._clearLocalEchoTimeout();
|
||||
}
|
||||
// Only clear tab alert if no pending hooks (permission_prompt, elicitation_dialog, etc.)
|
||||
if (!this.pendingHooks.has(data.id)) {
|
||||
this.tabAlerts.delete(data.id);
|
||||
@@ -2994,6 +3028,67 @@ class ClaudemanApp {
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Strip locally-echoed characters from server output to prevent doubles.
|
||||
* Walks output char-by-char, skipping ANSI escape sequences (passed through),
|
||||
* and consuming matching chars from the echo buffer.
|
||||
*/
|
||||
_stripLocalEcho(serverOutput) {
|
||||
let echoIdx = 0;
|
||||
let outIdx = 0;
|
||||
let result = '';
|
||||
|
||||
while (outIdx < serverOutput.length && echoIdx < this._localEchoBuffer.length) {
|
||||
// Skip ANSI escape sequences — pass them through to output
|
||||
if (serverOutput[outIdx] === '\x1b') {
|
||||
const seqStart = outIdx;
|
||||
outIdx++; // skip ESC
|
||||
// CSI sequence: ESC [ ... <letter>
|
||||
if (outIdx < serverOutput.length && serverOutput[outIdx] === '[') {
|
||||
outIdx++;
|
||||
while (outIdx < serverOutput.length && !/[A-Za-z]/.test(serverOutput[outIdx])) outIdx++;
|
||||
if (outIdx < serverOutput.length) outIdx++; // include final letter
|
||||
}
|
||||
result += serverOutput.slice(seqStart, outIdx);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (serverOutput[outIdx] === this._localEchoBuffer[echoIdx]) {
|
||||
// Match — consume from echo buffer, skip in output (already displayed)
|
||||
echoIdx++;
|
||||
outIdx++;
|
||||
} else {
|
||||
// Mismatch — stop stripping, pass rest through
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Consumed echoIdx chars from buffer
|
||||
this._localEchoBuffer = this._localEchoBuffer.slice(echoIdx);
|
||||
if (this._localEchoBuffer.length === 0) this._clearLocalEchoTimeout();
|
||||
|
||||
// Append any remaining server output
|
||||
result += serverOutput.slice(outIdx);
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reset the local echo safety timeout. If the echo buffer isn't consumed
|
||||
* within 3 seconds (e.g., dropped connection), clear it to prevent stuck state.
|
||||
*/
|
||||
_resetLocalEchoTimeout() {
|
||||
clearTimeout(this._localEchoTimeout);
|
||||
this._localEchoTimeout = setTimeout(() => {
|
||||
this._localEchoBuffer = '';
|
||||
this._localEchoTimeout = null;
|
||||
}, 3000);
|
||||
}
|
||||
|
||||
_clearLocalEchoTimeout() {
|
||||
clearTimeout(this._localEchoTimeout);
|
||||
this._localEchoTimeout = null;
|
||||
}
|
||||
|
||||
_enqueueInput(sessionId, input) {
|
||||
const existing = this._inputQueue.get(sessionId) || '';
|
||||
let combined = existing + input;
|
||||
|
||||
Reference in New Issue
Block a user