* 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>
409 lines
17 KiB
TypeScript
409 lines
17 KiB
TypeScript
import { useSessionUIStore } from '@/sync/session-ui-store';
|
|
import { getSyncSessions } from '@/sync/sync-refs';
|
|
import { useUIStore } from '@/stores/useUIStore';
|
|
import { getRuntimeUrlResolver } from './runtime-url';
|
|
import { opencodeClient } from './opencode/client';
|
|
import { runtimeFetch } from './runtime-fetch';
|
|
|
|
declare const __APP_VERSION__: string | undefined;
|
|
|
|
type ProbeResult = {
|
|
ok: boolean;
|
|
status: number;
|
|
elapsedMs: number;
|
|
summary: string;
|
|
};
|
|
|
|
type OpenChamberHealthSnapshot = {
|
|
openCodePort?: unknown;
|
|
openCodeRunning?: unknown;
|
|
openCodeSecureConnection?: unknown;
|
|
openCodeAuthSource?: unknown;
|
|
isOpenCodeReady?: unknown;
|
|
lastOpenCodeError?: unknown;
|
|
lastOpenCodeLaunchDiagnostics?: unknown;
|
|
opencodeBinaryResolved?: unknown;
|
|
opencodeBinarySource?: unknown;
|
|
opencodeLaunchBinary?: unknown;
|
|
opencodeLaunchArgs?: unknown;
|
|
opencodeLaunchWrapperType?: unknown;
|
|
nodeBinaryResolved?: unknown;
|
|
bunBinaryResolved?: unknown;
|
|
};
|
|
|
|
type OpenChamberOpencodeResolution = {
|
|
configured?: unknown;
|
|
resolved?: unknown;
|
|
resolvedDir?: unknown;
|
|
source?: unknown;
|
|
detectedNow?: unknown;
|
|
detectedSourceNow?: unknown;
|
|
launchBinary?: unknown;
|
|
launchArgs?: unknown;
|
|
launchWrapperType?: unknown;
|
|
node?: unknown;
|
|
bun?: unknown;
|
|
};
|
|
|
|
const getCurrentDirectory = (): string => {
|
|
const state = useSessionUIStore.getState();
|
|
const currentSessionId = state.currentSessionId;
|
|
if (!currentSessionId) return '';
|
|
const sessions = getSyncSessions();
|
|
const session = sessions.find((s) => s.id === currentSessionId);
|
|
return typeof session?.directory === 'string' ? session.directory : '';
|
|
};
|
|
|
|
const safeFetch = async (input: string, timeoutMs = 6000): Promise<ProbeResult> => {
|
|
const controller = new AbortController();
|
|
const timeout = setTimeout(() => controller.abort(), timeoutMs);
|
|
const startedAt = Date.now();
|
|
|
|
try {
|
|
const resp = await runtimeFetch(input, {
|
|
method: 'GET',
|
|
headers: { Accept: 'application/json' },
|
|
signal: controller.signal,
|
|
});
|
|
|
|
const elapsedMs = Date.now() - startedAt;
|
|
const contentType = resp.headers.get('content-type') || '';
|
|
const lower = contentType.toLowerCase();
|
|
const isJson = lower.includes('json') && !lower.includes('text/html');
|
|
|
|
let summary = '';
|
|
if (isJson) {
|
|
const json = await resp.json().catch(() => null);
|
|
if (Array.isArray(json)) {
|
|
summary = `json[array] len=${json.length}`;
|
|
} else if (json && typeof json === 'object') {
|
|
const keys = Object.keys(json).slice(0, 8);
|
|
summary = `json[object] keys=${keys.join(',')}${Object.keys(json).length > keys.length ? ',…' : ''}`;
|
|
} else {
|
|
summary = `json[${typeof json}]`;
|
|
}
|
|
} else {
|
|
summary = contentType ? `content-type=${contentType}` : 'no content-type';
|
|
}
|
|
|
|
return { ok: resp.ok && isJson, status: resp.status, elapsedMs, summary };
|
|
} catch (error) {
|
|
const elapsedMs = Date.now() - startedAt;
|
|
const isAbort =
|
|
controller.signal.aborted ||
|
|
(error instanceof Error && (error.name === 'AbortError' || error.message.toLowerCase().includes('aborted')));
|
|
const message = isAbort
|
|
? `timeout after ${timeoutMs}ms`
|
|
: error instanceof Error
|
|
? error.message
|
|
: String(error);
|
|
return { ok: false, status: 0, elapsedMs, summary: `error=${message}` };
|
|
} finally {
|
|
clearTimeout(timeout);
|
|
}
|
|
};
|
|
|
|
const formatIso = (timestamp: number | null | undefined): string => {
|
|
if (!timestamp || !Number.isFinite(timestamp)) return '(n/a)';
|
|
try {
|
|
return new Date(timestamp).toISOString();
|
|
} catch {
|
|
return '(invalid)';
|
|
}
|
|
};
|
|
|
|
const normalizePort = (value: unknown): number | null => {
|
|
if (typeof value === 'number' && Number.isFinite(value) && value > 0) {
|
|
return Math.trunc(value);
|
|
}
|
|
if (typeof value === 'string') {
|
|
const parsed = Number.parseInt(value, 10);
|
|
if (Number.isFinite(parsed) && parsed > 0) {
|
|
return parsed;
|
|
}
|
|
}
|
|
return null;
|
|
};
|
|
|
|
const isRecord = (value: unknown): value is Record<string, unknown> =>
|
|
!!value && typeof value === 'object' && !Array.isArray(value);
|
|
|
|
const formatUnknown = (value: unknown, fallback = '(n/a)'): string => {
|
|
if (typeof value === 'string') return value.trim() || fallback;
|
|
if (typeof value === 'number' && Number.isFinite(value)) return String(value);
|
|
if (typeof value === 'boolean') return value ? 'true' : 'false';
|
|
return fallback;
|
|
};
|
|
|
|
const formatLaunchRuntime = (wrapperType: string, node: string, bun: string): string => {
|
|
if (wrapperType === 'node-shebang' || wrapperType === 'node-launcher') {
|
|
return node ? `node (${node})` : 'node';
|
|
}
|
|
if (wrapperType === 'bun-shebang') {
|
|
return bun ? `bun (${bun})` : 'bun';
|
|
}
|
|
if (wrapperType) {
|
|
return wrapperType;
|
|
}
|
|
return 'direct executable';
|
|
};
|
|
|
|
const buildOpenCodeStatusReport = async (): Promise<string> => {
|
|
const now = new Date();
|
|
const appVersion = typeof __APP_VERSION__ !== 'undefined' ? __APP_VERSION__ : '(unknown)';
|
|
const platform = typeof navigator !== 'undefined' ? navigator.userAgent : '(no navigator)';
|
|
const directory = getCurrentDirectory();
|
|
const eventStreamStatus = useUIStore.getState().eventStreamStatus;
|
|
const origin = typeof window !== 'undefined' ? window.location.origin : '';
|
|
const urls = getRuntimeUrlResolver();
|
|
const healthUrl = urls.health();
|
|
const apiBase = urls.api('/api/');
|
|
|
|
const openChamberHealth: OpenChamberHealthSnapshot | null = await (async () => {
|
|
if (!healthUrl) return null;
|
|
const controller = new AbortController();
|
|
const timeout = setTimeout(() => controller.abort(), 5000);
|
|
try {
|
|
const resp = await runtimeFetch(healthUrl, {
|
|
method: 'GET',
|
|
headers: { Accept: 'application/json' },
|
|
signal: controller.signal,
|
|
});
|
|
if (!resp.ok) return null;
|
|
const json = (await resp.json().catch(() => null)) as unknown;
|
|
if (!json || typeof json !== 'object' || Array.isArray(json)) return null;
|
|
return json as OpenChamberHealthSnapshot;
|
|
} catch {
|
|
return null;
|
|
} finally {
|
|
clearTimeout(timeout);
|
|
}
|
|
})();
|
|
|
|
const openChamberOpencodeResolutionResult: {
|
|
data: OpenChamberOpencodeResolution | null;
|
|
status: number | null;
|
|
error: string | null;
|
|
} = await (async () => {
|
|
if (!apiBase) return null;
|
|
const controller = new AbortController();
|
|
const timeout = setTimeout(() => controller.abort(), 7000);
|
|
try {
|
|
const resp = await runtimeFetch(urls.api('/api/config/opencode-resolution'), {
|
|
method: 'GET',
|
|
headers: { Accept: 'application/json' },
|
|
signal: controller.signal,
|
|
});
|
|
const contentType = resp.headers.get('content-type') || '(none)';
|
|
if (!resp.ok) {
|
|
return { data: null, status: resp.status, error: `http ${resp.status} content-type=${contentType}` };
|
|
}
|
|
const raw = await resp.text();
|
|
let json: unknown = null;
|
|
try {
|
|
json = JSON.parse(raw);
|
|
} catch {
|
|
const snippet = raw.replace(/\s+/g, ' ').slice(0, 120);
|
|
return {
|
|
data: null,
|
|
status: resp.status,
|
|
error: `invalid json content-type=${contentType} body=${snippet || '(empty)'}`,
|
|
};
|
|
}
|
|
if (!json || typeof json !== 'object' || Array.isArray(json)) {
|
|
return { data: null, status: resp.status, error: `invalid json-shape content-type=${contentType}` };
|
|
}
|
|
return { data: json as OpenChamberOpencodeResolution, status: resp.status, error: null };
|
|
} catch (error) {
|
|
return {
|
|
data: null,
|
|
status: null,
|
|
error: error instanceof Error ? error.message : String(error),
|
|
};
|
|
} finally {
|
|
clearTimeout(timeout);
|
|
}
|
|
})() || { data: null, status: null, error: null };
|
|
|
|
const buildProbeUrl = (pathname: string, includeDirectory = true): string | null => {
|
|
if (!apiBase) return null;
|
|
const url = new URL(pathname.replace(/^\/+/, ''), apiBase);
|
|
if (includeDirectory && directory) {
|
|
url.searchParams.set('directory', directory);
|
|
}
|
|
return url.toString();
|
|
};
|
|
|
|
const probeTargets: Array<{ label: string; path: string; includeDirectory?: boolean; timeoutMs?: number }> = [
|
|
{ label: 'health', path: '/health', includeDirectory: false },
|
|
{ label: 'config', path: '/config', includeDirectory: true },
|
|
{ label: 'providers', path: '/config/providers', includeDirectory: true },
|
|
{ label: 'agents', path: '/agent', includeDirectory: true, timeoutMs: 12000 },
|
|
{ label: 'commands', path: '/command', includeDirectory: true, timeoutMs: 10000 },
|
|
{ label: 'project', path: '/project/current', includeDirectory: true },
|
|
{ label: 'path', path: '/path', includeDirectory: true },
|
|
{ label: 'sessions', path: '/session', includeDirectory: true, timeoutMs: 12000 },
|
|
{ label: 'sessionStatus', path: '/session/status', includeDirectory: true },
|
|
];
|
|
|
|
const probes = apiBase
|
|
? await Promise.all(
|
|
probeTargets.map(async (entry) => {
|
|
const url = buildProbeUrl(entry.path, entry.includeDirectory !== false);
|
|
if (!url) return { label: entry.label, url: '(none)', result: null as ProbeResult | null };
|
|
const result = await safeFetch(url, typeof entry.timeoutMs === 'number' ? entry.timeoutMs : undefined);
|
|
return { label: entry.label, url, result };
|
|
})
|
|
)
|
|
: [];
|
|
|
|
const lines: string[] = [];
|
|
lines.push(`Time: ${now.toISOString()}`);
|
|
lines.push(`OpenChamber version: ${appVersion}`);
|
|
lines.push(`Runtime: ${origin || '(unknown)'} (api=${apiBase || '(unknown)'})`);
|
|
lines.push(`OpenCode SDK base: ${opencodeClient.getBaseUrl()}`);
|
|
lines.push(`Event stream: ${eventStreamStatus}`);
|
|
lines.push(`Directory: ${directory || '(none)'}`);
|
|
lines.push(`Platform: ${platform}`);
|
|
|
|
const runtimeOpenCodePort = normalizePort(openChamberHealth?.openCodePort);
|
|
lines.push(`OpenCode runtime port: ${runtimeOpenCodePort ?? '(unknown)'}`);
|
|
if (typeof openChamberHealth?.openCodeRunning === 'boolean') {
|
|
lines.push(`OpenCode runtime running: ${openChamberHealth.openCodeRunning ? 'yes' : 'no'}`);
|
|
}
|
|
if (typeof openChamberHealth?.openCodeSecureConnection === 'boolean') {
|
|
lines.push(`Secure OpenCode connection: ${openChamberHealth.openCodeSecureConnection ? 'true' : 'false'}`);
|
|
}
|
|
if (typeof openChamberHealth?.openCodeAuthSource === 'string' && openChamberHealth.openCodeAuthSource.trim()) {
|
|
lines.push(`OpenCode auth source: ${openChamberHealth.openCodeAuthSource}`);
|
|
}
|
|
|
|
if (typeof window !== 'undefined') {
|
|
const injected = (window as unknown as { __OPENCHAMBER_MACOS_MAJOR__?: unknown }).__OPENCHAMBER_MACOS_MAJOR__;
|
|
if (typeof injected === 'number' && Number.isFinite(injected) && injected > 0) {
|
|
lines.push(`macOS major: ${injected}`);
|
|
}
|
|
}
|
|
|
|
const isLikelyMac = /Mac OS X|Macintosh/.test(platform);
|
|
if (isLikelyMac) {
|
|
lines.push('');
|
|
lines.push('OpenCode CLI resolution:');
|
|
|
|
const launchDiagnostics = isRecord(openChamberHealth?.lastOpenCodeLaunchDiagnostics)
|
|
? openChamberHealth.lastOpenCodeLaunchDiagnostics
|
|
: null;
|
|
const actualLaunchArgs = launchDiagnostics && Array.isArray(launchDiagnostics.args)
|
|
? launchDiagnostics.args.filter((value): value is string => typeof value === 'string')
|
|
: [];
|
|
const openChamberOpencodeResolution = openChamberOpencodeResolutionResult.data;
|
|
const configured =
|
|
openChamberOpencodeResolution && typeof openChamberOpencodeResolution.configured === 'string'
|
|
? openChamberOpencodeResolution.configured
|
|
: null;
|
|
const resolved =
|
|
openChamberOpencodeResolution && typeof openChamberOpencodeResolution.resolved === 'string'
|
|
? openChamberOpencodeResolution.resolved
|
|
: (openChamberHealth && typeof openChamberHealth.opencodeBinaryResolved === 'string' ? openChamberHealth.opencodeBinaryResolved : '');
|
|
const resolvedDir =
|
|
openChamberOpencodeResolution && typeof openChamberOpencodeResolution.resolvedDir === 'string'
|
|
? openChamberOpencodeResolution.resolvedDir
|
|
: '';
|
|
const source =
|
|
openChamberOpencodeResolution && typeof openChamberOpencodeResolution.source === 'string'
|
|
? openChamberOpencodeResolution.source
|
|
: (openChamberHealth && typeof openChamberHealth.opencodeBinarySource === 'string' ? openChamberHealth.opencodeBinarySource : '');
|
|
const configuredLaunchBinary =
|
|
openChamberOpencodeResolution && typeof openChamberOpencodeResolution.launchBinary === 'string'
|
|
? openChamberOpencodeResolution.launchBinary
|
|
: (openChamberHealth && typeof openChamberHealth.opencodeLaunchBinary === 'string' ? openChamberHealth.opencodeLaunchBinary : '');
|
|
const configuredLaunchWrapperType =
|
|
openChamberOpencodeResolution && typeof openChamberOpencodeResolution.launchWrapperType === 'string'
|
|
? openChamberOpencodeResolution.launchWrapperType
|
|
: (openChamberHealth && typeof openChamberHealth.opencodeLaunchWrapperType === 'string' ? openChamberHealth.opencodeLaunchWrapperType : '');
|
|
const configuredLaunchArgs =
|
|
openChamberOpencodeResolution && Array.isArray(openChamberOpencodeResolution.launchArgs)
|
|
? openChamberOpencodeResolution.launchArgs.filter((value): value is string => typeof value === 'string')
|
|
: (openChamberHealth && Array.isArray(openChamberHealth.opencodeLaunchArgs)
|
|
? openChamberHealth.opencodeLaunchArgs.filter((value): value is string => typeof value === 'string')
|
|
: []);
|
|
const node =
|
|
openChamberOpencodeResolution && typeof openChamberOpencodeResolution.node === 'string'
|
|
? openChamberOpencodeResolution.node
|
|
: (openChamberHealth && typeof openChamberHealth.nodeBinaryResolved === 'string' ? openChamberHealth.nodeBinaryResolved : '');
|
|
const bun =
|
|
openChamberOpencodeResolution && typeof openChamberOpencodeResolution.bun === 'string'
|
|
? openChamberOpencodeResolution.bun
|
|
: (openChamberHealth && typeof openChamberHealth.bunBinaryResolved === 'string' ? openChamberHealth.bunBinaryResolved : '');
|
|
const detectedNow =
|
|
openChamberOpencodeResolution && typeof openChamberOpencodeResolution.detectedNow === 'string'
|
|
? openChamberOpencodeResolution.detectedNow
|
|
: '';
|
|
const detectedSourceNow =
|
|
openChamberOpencodeResolution && typeof openChamberOpencodeResolution.detectedSourceNow === 'string'
|
|
? openChamberOpencodeResolution.detectedSourceNow
|
|
: '';
|
|
|
|
if (configured !== null) {
|
|
lines.push(`- configured: ${configured.trim().length === 0 ? '(cleared)' : configured}`);
|
|
}
|
|
|
|
if (resolved) {
|
|
const dir = resolvedDir || (resolved.includes('/') ? resolved.split('/').slice(0, -1).join('/') || '/' : '');
|
|
lines.push(`- opencode: ${resolved}${dir ? ` (dir=${dir})` : ''}`);
|
|
} else {
|
|
lines.push('- opencode: (n/a)');
|
|
}
|
|
|
|
lines.push(`- source: ${source || '(n/a)'}`);
|
|
if (detectedNow) {
|
|
lines.push(`- detected-now: ${detectedNow}`);
|
|
lines.push(`- detected-source: ${detectedSourceNow || '(n/a)'}`);
|
|
}
|
|
if (launchDiagnostics) {
|
|
lines.push(`- launched-at: ${formatUnknown(launchDiagnostics.launchedAt)}`);
|
|
lines.push(`- launch: ${formatUnknown(launchDiagnostics.binary)} ${actualLaunchArgs.join(' ')}`.trim());
|
|
lines.push(`- cwd: ${formatUnknown(launchDiagnostics.cwd)}`);
|
|
lines.push(`- wrapper: ${formatUnknown(launchDiagnostics.wrapperType)}`);
|
|
lines.push(`- runtime: ${formatLaunchRuntime(formatUnknown(launchDiagnostics.wrapperType, ''), node, bun)}`);
|
|
lines.push(`- PATH entries: ${formatUnknown(launchDiagnostics.pathEntryCount, '(unknown)')}`);
|
|
lines.push(`- shell env: ${formatUnknown(launchDiagnostics.hasShellEnv, '(unknown)')} (${formatUnknown(launchDiagnostics.shellEnvKeysCount, '?')} keys)`);
|
|
} else {
|
|
lines.push(`- launch-binary: ${configuredLaunchBinary || '(n/a)'}`);
|
|
lines.push(`- launch-wrapper: ${configuredLaunchWrapperType || '(n/a)'}`);
|
|
lines.push(`- launch-args: ${configuredLaunchArgs.length ? configuredLaunchArgs.join(' ') : '(none)'}`);
|
|
lines.push(`- runtime: ${formatLaunchRuntime(configuredLaunchWrapperType || '', node, bun)}`);
|
|
}
|
|
if (!openChamberOpencodeResolution && openChamberOpencodeResolutionResult.error) {
|
|
lines.push(`- resolution-endpoint: ${openChamberOpencodeResolutionResult.error}`);
|
|
}
|
|
}
|
|
|
|
lines.push('');
|
|
if (probes.length) {
|
|
lines.push('OpenCode API probes:');
|
|
for (const probe of probes) {
|
|
if (!probe.result) {
|
|
lines.push(`- ${probe.label}: (no url)`);
|
|
continue;
|
|
}
|
|
const { ok, status, elapsedMs, summary } = probe.result;
|
|
const suffix = ok ? '' : ` url=${probe.url}`;
|
|
lines.push(`- ${probe.label}: ${ok ? 'ok' : 'fail'} status=${status} time=${elapsedMs}ms ${summary}${suffix}`);
|
|
}
|
|
} else {
|
|
lines.push('OpenCode API probes: (skipped)');
|
|
}
|
|
|
|
lines.push('');
|
|
lines.push(`Generated: ${formatIso(Date.now())}`);
|
|
return lines.join('\n');
|
|
};
|
|
|
|
export const showOpenCodeStatus = async (): Promise<void> => {
|
|
const text = await buildOpenCodeStatusReport();
|
|
const ui = useUIStore.getState();
|
|
ui.setOpenCodeStatusText(text);
|
|
ui.setOpenCodeStatusDialogOpen(true);
|
|
};
|