* chore: remove dead/unreferenced files across ui, vscode Remove 59 unused source files (components, hooks, lib utils, stores, barrels, and orphaned vscode github modules) that are not imported by any entry-reachable code. Also drop a stale test mock for the removed execCommands module. Co-authored-by: Serhii Dziupin <makeittech@users.noreply.github.com> * refactor: remove unused exported symbols (types, functions, consts, hooks) Remove exported symbols whose identifier is referenced nowhere in the repository (verified via repo-wide search), across ui types/contracts, lib utilities, sync layer, stores, and components. Also drop the few imports/private helpers orphaned by these removals. Co-authored-by: Serhii Dziupin <makeittech@users.noreply.github.com> * refactor: remove more unused exports (desktop, shortcuts, worktree, vscode) Continue removing repo-wide unreferenced exported functions, consts and types across lib/desktop, shortcuts, worktreeSessionCreator, sync, and vscode gitService, with cascading orphaned helpers/imports cleaned up. Co-authored-by: Serhii Dziupin <makeittech@users.noreply.github.com> * chore: add dead-code cleanup tooling * refactor: checkpoint dead-code cleanup * refactor: remove dead-code suppressions --------- Co-authored-by: Serhii Dziupin <makeittech@users.noreply.github.com> Co-authored-by: Bohdan Triapitsyn <artmore@protonmail.com>
159 lines
4.1 KiB
JavaScript
159 lines
4.1 KiB
JavaScript
import { createUpstreamSseReader } from './upstream-reader.js';
|
|
|
|
// Raised from 512 → 2048 to improve recovery after brief disconnects during
|
|
// long-running agent sessions where many events accumulate quickly.
|
|
const MESSAGE_STREAM_GLOBAL_REPLAY_LIMIT = 2048;
|
|
|
|
export function createGlobalMessageStreamHub({
|
|
buildOpenCodeUrl,
|
|
getOpenCodeAuthHeaders,
|
|
fetchImpl = fetch,
|
|
upstreamStallTimeoutMs,
|
|
upstreamReconnectDelayMs,
|
|
replayLimit = MESSAGE_STREAM_GLOBAL_REPLAY_LIMIT,
|
|
}) {
|
|
const eventSubscribers = new Set();
|
|
const statusSubscribers = new Set();
|
|
const replay = [];
|
|
|
|
let controller = null;
|
|
let reader = null;
|
|
let connected = false;
|
|
let everConnected = false;
|
|
let buildUrlFailed = false;
|
|
|
|
const notifySubscriber = (kind, subscriber, payload) => {
|
|
try {
|
|
const result = subscriber(payload);
|
|
if (result && typeof result.catch === 'function') {
|
|
result.catch((error) => {
|
|
console.warn(`Global message stream ${kind} subscriber failed:`, error);
|
|
});
|
|
}
|
|
} catch (error) {
|
|
console.warn(`Global message stream ${kind} subscriber failed:`, error);
|
|
}
|
|
};
|
|
|
|
const notifyStatus = (status) => {
|
|
for (const subscriber of Array.from(statusSubscribers)) {
|
|
notifySubscriber('status', subscriber, status);
|
|
}
|
|
};
|
|
|
|
const normalizeEvent = ({ envelope, payload }) => {
|
|
const directory =
|
|
typeof envelope?.directory === 'string' && envelope.directory.length > 0 ? envelope.directory : 'global';
|
|
const eventId = typeof envelope?.eventId === 'string' && envelope.eventId.length > 0 ? envelope.eventId : undefined;
|
|
return {
|
|
envelope,
|
|
payload,
|
|
directory,
|
|
eventId,
|
|
};
|
|
};
|
|
|
|
const start = () => {
|
|
if (reader) {
|
|
return;
|
|
}
|
|
|
|
controller = new AbortController();
|
|
reader = createUpstreamSseReader({
|
|
signal: controller.signal,
|
|
stallTimeoutMs: upstreamStallTimeoutMs,
|
|
reconnectDelayMs: upstreamReconnectDelayMs,
|
|
fetchImpl,
|
|
buildUrl: () => {
|
|
buildUrlFailed = false;
|
|
try {
|
|
return new URL(buildOpenCodeUrl('/global/event', ''));
|
|
} catch {
|
|
buildUrlFailed = true;
|
|
throw new Error('OpenCode service unavailable');
|
|
}
|
|
},
|
|
getHeaders: getOpenCodeAuthHeaders,
|
|
onConnect() {
|
|
connected = true;
|
|
const wasReady = everConnected;
|
|
everConnected = true;
|
|
notifyStatus({ type: 'connect', wasReady });
|
|
},
|
|
onDisconnect({ reason }) {
|
|
connected = false;
|
|
notifyStatus({ type: 'disconnect', reason });
|
|
},
|
|
onEvent(event) {
|
|
const normalized = normalizeEvent(event);
|
|
if (normalized.eventId) {
|
|
replay.push(normalized);
|
|
if (replay.length > replayLimit) {
|
|
replay.splice(0, replay.length - replayLimit);
|
|
}
|
|
}
|
|
|
|
for (const subscriber of Array.from(eventSubscribers)) {
|
|
notifySubscriber('event', subscriber, normalized);
|
|
}
|
|
},
|
|
onError(error) {
|
|
if (controller?.signal.aborted) {
|
|
return;
|
|
}
|
|
|
|
notifyStatus({
|
|
type: everConnected ? 'error' : 'initial-error',
|
|
error,
|
|
buildUrlFailed,
|
|
});
|
|
},
|
|
});
|
|
|
|
void reader.start();
|
|
};
|
|
|
|
const stop = () => {
|
|
connected = false;
|
|
reader?.stop();
|
|
if (controller && !controller.signal.aborted) {
|
|
controller.abort();
|
|
}
|
|
reader = null;
|
|
controller = null;
|
|
everConnected = false;
|
|
buildUrlFailed = false;
|
|
};
|
|
|
|
return {
|
|
start,
|
|
stop,
|
|
isConnected() {
|
|
return connected;
|
|
},
|
|
hasConnected() {
|
|
return everConnected;
|
|
},
|
|
subscribeEvent(subscriber) {
|
|
eventSubscribers.add(subscriber);
|
|
return () => {
|
|
eventSubscribers.delete(subscriber);
|
|
};
|
|
},
|
|
subscribeStatus(subscriber) {
|
|
statusSubscribers.add(subscriber);
|
|
return () => {
|
|
statusSubscribers.delete(subscriber);
|
|
};
|
|
},
|
|
replayAfter(eventId) {
|
|
if (!eventId) {
|
|
return [];
|
|
}
|
|
|
|
const index = replay.findIndex((entry) => entry.eventId === eventId);
|
|
return index === -1 ? [] : replay.slice(index + 1);
|
|
},
|
|
};
|
|
}
|