feat(terminal): refactor runtime and add mobile workspace (#2280)
Replace the legacy terminal flow with a shared authenticated WebSocket runtime used across web, desktop, relay, and mobile surfaces. - introduce the v3 terminal protocol with scoped attachments, snapshots, ordered output, bounded replay history, reconnects, and explicit lifecycle - harden PTY creation, restart, resize, close, force-kill, idle cleanup, shell selection, login mode, environment sanitization, and appearance sync - add runtime-aware terminal APIs with relay authentication and Electron parity - add a fullscreen mobile terminal workspace with touch scrolling, long-press selection, safe-area controls, quick keys, and Ctrl/Alt input - add terminal selection attachments, preview detection, project actions, shell settings, and localized UI - harden Ghostty rendering, resize recovery, Unicode handling, block characters, line height, and stale-row behavior - remove the obsolete terminal SSE path and update reverse-proxy guidance - expand terminal runtime, transport, input, selection, and store coverage - avoid duplicate web builds when preparing mobile assets in root CI builds
This commit is contained in:
committed by
GitHub
parent
f5b4a267c0
commit
d4a8c4d2e1
@@ -17,6 +17,7 @@ export type RuntimeEndpointChangedDetail = {
|
||||
};
|
||||
|
||||
const RUNTIME_ENDPOINT_CHANGED_EVENT = 'openchamber:runtime-endpoint-changed';
|
||||
const RUNTIME_ENDPOINT_WILL_CHANGE_EVENT = 'openchamber:runtime-endpoint-will-change';
|
||||
|
||||
let activeApiBaseUrl = '';
|
||||
let activeRuntimeKey = '';
|
||||
@@ -43,7 +44,10 @@ const normalizeRuntimeUrlKey = (value: string): string => {
|
||||
const url = new URL(value);
|
||||
url.hash = '';
|
||||
url.search = '';
|
||||
// Normalise pathname so root `/` becomes empty and no path ends with `/`.
|
||||
url.pathname = url.pathname.replace(/\/+$/, '') || '/';
|
||||
// url.toString() still appends `/` when pathname is `/`; strip it
|
||||
// so every key uses the bare-origin form: `url:https://example.com`.
|
||||
return `url:${url.toString().replace(/\/+$/, '')}`;
|
||||
} catch {
|
||||
return `url:${value.trim().replace(/\/+$/, '') || 'default'}`;
|
||||
@@ -98,6 +102,10 @@ export const switchRuntimeEndpoint = (options: { apiBaseUrl: string; clientToken
|
||||
const previousApiBaseUrl = getRuntimeApiBaseUrl();
|
||||
const previousRuntimeKey = getRuntimeKey();
|
||||
const runtimeKey = options.runtimeKey?.trim() || normalizeRuntimeUrlKey(apiBaseUrl);
|
||||
const detail = { apiBaseUrl, previousApiBaseUrl, runtimeKey, previousRuntimeKey };
|
||||
if (typeof window !== 'undefined') {
|
||||
window.dispatchEvent(new CustomEvent<RuntimeEndpointChangedDetail>(RUNTIME_ENDPOINT_WILL_CHANGE_EVENT, { detail }));
|
||||
}
|
||||
activeApiBaseUrl = apiBaseUrl;
|
||||
activeRuntimeKey = runtimeKey;
|
||||
if (typeof window !== 'undefined') {
|
||||
@@ -124,11 +132,20 @@ export const switchRuntimeEndpoint = (options: { apiBaseUrl: string; clientToken
|
||||
void refreshRuntimeUrlAuthToken(apiBaseUrl).catch(() => {});
|
||||
if (typeof window !== 'undefined') {
|
||||
window.dispatchEvent(new CustomEvent<RuntimeEndpointChangedDetail>(RUNTIME_ENDPOINT_CHANGED_EVENT, {
|
||||
detail: { apiBaseUrl, previousApiBaseUrl, runtimeKey, previousRuntimeKey },
|
||||
detail,
|
||||
}));
|
||||
}
|
||||
};
|
||||
|
||||
export const subscribeRuntimeEndpointWillChange = (callback: (detail: RuntimeEndpointChangedDetail) => void): (() => void) => {
|
||||
if (typeof window === 'undefined') return () => {};
|
||||
const listener = (event: Event) => {
|
||||
callback((event as CustomEvent<RuntimeEndpointChangedDetail>).detail);
|
||||
};
|
||||
window.addEventListener(RUNTIME_ENDPOINT_WILL_CHANGE_EVENT, listener);
|
||||
return () => window.removeEventListener(RUNTIME_ENDPOINT_WILL_CHANGE_EVENT, listener);
|
||||
};
|
||||
|
||||
export const subscribeRuntimeEndpointChanged = (callback: (detail: RuntimeEndpointChangedDetail) => void): (() => void) => {
|
||||
if (typeof window === 'undefined') return () => {};
|
||||
const listener = (event: Event) => {
|
||||
|
||||
Reference in New Issue
Block a user