Add Ghostty terminal, session recovery, voice asset caching, and gitignore large assets

Integrates ghostty-web and node-pty packages, adds tmux session recovery on startup,
refactors WebSocket transport to noServer mode, caches Sherpa voice model assets via
service worker. Large assets (sherpa, ghostty-vt.wasm, fonts) are now gitignored as
they are copied from the webterm project.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-06-20 08:39:44 -04:00
parent c61712d8c4
commit edbecd2c83
7 changed files with 87 additions and 7 deletions
+43
View File
@@ -104,6 +104,7 @@ export class TmuxAdapter extends EventEmitter {
this._clientChecker = null;
this._cleanupInterval = null;
this._startSessionCleanup();
this._recoverSessions().catch((err) => console.error('[tmux] Recovery error:', err));
}
/** Set a function that checks if WS clients are connected for a session */
@@ -806,6 +807,48 @@ export class TmuxAdapter extends EventEmitter {
// === Helpers ===
private async _recoverSessions(): Promise<void> {
const UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
let windows: TmuxWindow[];
try {
windows = await tmuxManager.listWindows();
} catch (err) {
console.error('[tmux] Recovery: listWindows failed:', err);
return;
}
console.log(`[tmux] Recovery: found ${windows.length} window(s):`, windows.map(w => `${w.name}(${w.command})`).join(', '));
const candidates = windows.filter(w => UUID_RE.test(w.name));
if (candidates.length === 0) {
console.log('[tmux] Recovery: no UUID-named windows to recover');
return;
}
await Promise.all(candidates.map(async (win) => {
const sessionId = win.name;
if (this.sessions.has(sessionId)) return;
// Find the JSONL file to recover real cwd and firstPrompt
let cwd = win.cwd;
let firstPrompt: string | null = null;
const jsonlPath = await this._findJsonlPath(sessionId);
if (jsonlPath) {
try {
const header = await parseSessionHeader(jsonlPath, sessionId);
if (header.cwd) cwd = header.cwd;
firstPrompt = header.firstPrompt;
} catch {}
}
const state = this._createSession(win.id, cwd, sessionId, 'bypassPermissions');
state.firstPrompt = firstPrompt;
this.sessions.set(sessionId, state);
this._startMonitor(sessionId, win.id);
await this._ensureWatcher(sessionId);
console.log(`[tmux] Recovered session ${sessionId} (${cwd})`);
}));
}
private _createSession(windowId: string, cwd: string, cliSessionId: string, permissionMode: string): SessionState {
return {
windowId,