delete api routes with no consumers
from a full audit of all 179 route definitions under src/servers/api, tracing
consumers through useClient, raw fetch, EventSource, the capabilities repo and
the mobile monorepo. only routes with zero consumers anywhere are removed.
server-settings/applications.ts whole file — an app install/update registry
with no settings section to drive it
server-settings/claude-code.ts whole file — the ai settings screen talks
to chat-providers/* exclusively
GET browser/extension-download superseded by a static asset; BrowserRelay
links at /browser-relay-extension.zip
GET integrations/ a stub returning []
GET chat-providers/auth /api-keys says the same thing with more detail
GET desktop/vnc-status and with it the vnc:status command and reply,
which existed only to serve this route.
docs/sidecar-audit-2026-07.md called this
one dead months ago
deliberately KEPT, because "no caller" turned out not to mean "dead":
POST activity/announce not orphaned — it is the missing PRODUCER for the
detached[] list GET activity/tasks already returns
and ActivityScreen already renders. an unbuilt
feature, not dead code, and finishing or dropping
it is a product decision.
GET agents/runs three days old. part of agent grounds, still being
built. "not yet consumed" is not "dead".
PUT/GET vault/unlock-key six days old, storage half of a feature whose
client half is unwritten. the vault is off limits.
DELETE integrations/google/connection caller exists but is deliberately
commented out of the tree. dormant on purpose.
vnc-manager's getSession is now orphaned too, but it is sidecar-internal and
was not in scope; noted rather than chased.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -1,5 +1,4 @@
|
|||||||
import { randomUUID } from 'node:crypto';
|
import { randomUUID } from 'node:crypto';
|
||||||
import { join } from 'node:path';
|
|
||||||
import { createRouter } from '@@/create-router';
|
import { createRouter } from '@@/create-router';
|
||||||
import * as errors from '@@/custom-errors';
|
import * as errors from '@@/custom-errors';
|
||||||
import { getUserIntegration, upsertUserIntegration, deleteUserIntegration } from 'officerdb';
|
import { getUserIntegration, upsertUserIntegration, deleteUserIntegration } from 'officerdb';
|
||||||
@@ -9,8 +8,6 @@ import { captureScreenshot, evaluateJS, navigateTo } from './cdp';
|
|||||||
|
|
||||||
export const browserRouter = createRouter();
|
export const browserRouter = createRouter();
|
||||||
|
|
||||||
const EXTENSION_DIR = join(import.meta.dir, '../../../extensions/browser-relay');
|
|
||||||
|
|
||||||
async function getOpts(userId: number) {
|
async function getOpts(userId: number) {
|
||||||
const port = getRelayPort();
|
const port = getRelayPort();
|
||||||
const integration = await getUserIntegration(userId, 'browser-relay');
|
const integration = await getUserIntegration(userId, 'browser-relay');
|
||||||
@@ -22,22 +19,8 @@ async function getOpts(userId: number) {
|
|||||||
return { relayPort: port, userToken: token };
|
return { relayPort: port, userToken: token };
|
||||||
}
|
}
|
||||||
|
|
||||||
browserRouter.get('/extension-download', async (ctx) => {
|
// GET /extension-download used to zip the extension directory on the fly, shelling out to `zip`. It had
|
||||||
const proc = Bun.spawn(['zip', '-r', '-', '.'], {
|
// no caller: BrowserRelay.tsx links straight at /browser-relay-extension.zip, a real file in public/.
|
||||||
cwd: EXTENSION_DIR,
|
|
||||||
stdout: 'pipe',
|
|
||||||
stderr: 'pipe',
|
|
||||||
});
|
|
||||||
const blob = await new Response(proc.stdout).blob();
|
|
||||||
await proc.exited;
|
|
||||||
if (proc.exitCode !== 0) throw errors.INTERNAL_SERVER_ERROR('Failed to create zip');
|
|
||||||
return new Response(blob, {
|
|
||||||
headers: {
|
|
||||||
'Content-Type': 'application/zip',
|
|
||||||
'Content-Disposition': 'attachment; filename="officer-browser-relay.zip"',
|
|
||||||
},
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
browserRouter.get('/status', async (ctx) => {
|
browserRouter.get('/status', async (ctx) => {
|
||||||
const user = ctx.get('user');
|
const user = ctx.get('user');
|
||||||
@@ -51,7 +34,11 @@ browserRouter.get('/relay-token', async (ctx) => {
|
|||||||
let integration = await getUserIntegration(user.id, 'browser-relay');
|
let integration = await getUserIntegration(user.id, 'browser-relay');
|
||||||
if (!integration) {
|
if (!integration) {
|
||||||
const salt = randomUUID();
|
const salt = randomUUID();
|
||||||
integration = await upsertUserIntegration({ userId: user.id, provider: 'browser-relay', config: { tokenSalt: salt } });
|
integration = await upsertUserIntegration({
|
||||||
|
userId: user.id,
|
||||||
|
provider: 'browser-relay',
|
||||||
|
config: { tokenSalt: salt },
|
||||||
|
});
|
||||||
}
|
}
|
||||||
const salt = (integration.config as { tokenSalt: string }).tokenSalt;
|
const salt = (integration.config as { tokenSalt: string }).tokenSalt;
|
||||||
const token = registerUserToken(user.id, port, salt);
|
const token = registerUserToken(user.id, port, salt);
|
||||||
|
|||||||
@@ -28,11 +28,6 @@ desktopRouter.get('/vnc-password', async (ctx) => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
desktopRouter.get('/vnc-status', async (ctx) => {
|
// GET /vnc-status is gone. It was never called — DesktopView asks only for /vnc-password — and it was
|
||||||
const user = ctx.get('user');
|
// the sole reason the `vnc:status` command existed on the sidecar wire protocol at all. Noted as dead
|
||||||
if (!sidecar.isVncConnected()) {
|
// by docs/sidecar-audit-2026-07.md months ago.
|
||||||
return ctx.json({ connected: false, session: null });
|
|
||||||
}
|
|
||||||
const session = await sidecar.getVncStatus(user.email);
|
|
||||||
return ctx.json({ connected: true, session });
|
|
||||||
});
|
|
||||||
|
|||||||
@@ -30,9 +30,7 @@ export const readGoogleConfig = async (): Promise<GoogleConfig | null> => {
|
|||||||
|
|
||||||
export const integrationsRouter = createRouter();
|
export const integrationsRouter = createRouter();
|
||||||
|
|
||||||
integrationsRouter.get('/', async (ctx) => {
|
// GET / was a stub that returned []. Never wired to anything, in either direction.
|
||||||
return ctx.json([]);
|
|
||||||
});
|
|
||||||
|
|
||||||
// --- Apify config ---
|
// --- Apify config ---
|
||||||
|
|
||||||
|
|||||||
@@ -1,211 +0,0 @@
|
|||||||
import { createRouter } from '../../create-router';
|
|
||||||
|
|
||||||
type AppSpec = {
|
|
||||||
id: string;
|
|
||||||
name: string;
|
|
||||||
description: string;
|
|
||||||
versionCommand: string[];
|
|
||||||
versionParser: (output: string) => string | null;
|
|
||||||
installCommand?: string[];
|
|
||||||
updateCommand?: string[];
|
|
||||||
manualInstallCommand?: string;
|
|
||||||
manualUpdateCommand?: string;
|
|
||||||
processName?: string;
|
|
||||||
};
|
|
||||||
|
|
||||||
type AppStatus = {
|
|
||||||
id: string;
|
|
||||||
name: string;
|
|
||||||
description: string;
|
|
||||||
installed: boolean;
|
|
||||||
version: string | null;
|
|
||||||
running: boolean | null;
|
|
||||||
hasInstall: boolean;
|
|
||||||
hasUpdate: boolean;
|
|
||||||
manualInstallCommand: string | null;
|
|
||||||
manualUpdateCommand: string | null;
|
|
||||||
};
|
|
||||||
|
|
||||||
const detectPackageManager = (): 'pacman' | 'apt' | 'brew' | null => {
|
|
||||||
for (const [cmd, name] of [
|
|
||||||
['pacman', 'pacman'],
|
|
||||||
['apt', 'apt'],
|
|
||||||
['brew', 'brew'],
|
|
||||||
] as const) {
|
|
||||||
try {
|
|
||||||
const proc = Bun.spawnSync(['which', cmd], { stdout: 'pipe', stderr: 'pipe' });
|
|
||||||
if (proc.exitCode === 0) return name;
|
|
||||||
} catch {
|
|
||||||
// continue
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
};
|
|
||||||
|
|
||||||
type FfmpegCommands = {
|
|
||||||
installCommand?: string[];
|
|
||||||
updateCommand?: string[];
|
|
||||||
manualInstallCommand?: string;
|
|
||||||
manualUpdateCommand?: string;
|
|
||||||
};
|
|
||||||
|
|
||||||
const getFfmpegCommands = (): FfmpegCommands => {
|
|
||||||
const pm = detectPackageManager();
|
|
||||||
if (pm === 'pacman')
|
|
||||||
return {
|
|
||||||
manualInstallCommand: 'sudo pacman -S ffmpeg',
|
|
||||||
manualUpdateCommand: 'sudo pacman -Syu ffmpeg',
|
|
||||||
};
|
|
||||||
if (pm === 'apt')
|
|
||||||
return {
|
|
||||||
manualInstallCommand: 'sudo apt install ffmpeg',
|
|
||||||
manualUpdateCommand: 'sudo apt install --only-upgrade ffmpeg',
|
|
||||||
};
|
|
||||||
if (pm === 'brew')
|
|
||||||
return {
|
|
||||||
installCommand: ['brew', 'install', 'ffmpeg'],
|
|
||||||
updateCommand: ['brew', 'upgrade', 'ffmpeg'],
|
|
||||||
};
|
|
||||||
return {};
|
|
||||||
};
|
|
||||||
|
|
||||||
const ffmpegCmds = getFfmpegCommands();
|
|
||||||
|
|
||||||
const apps: AppSpec[] = [
|
|
||||||
{
|
|
||||||
id: 'ffmpeg',
|
|
||||||
name: 'FFmpeg',
|
|
||||||
description: 'Audio and video processing toolkit',
|
|
||||||
versionCommand: ['ffmpeg', '-version'],
|
|
||||||
versionParser: (output) => {
|
|
||||||
const match = output.match(/ffmpeg version (\S+)/);
|
|
||||||
return match?.[1] ?? null;
|
|
||||||
},
|
|
||||||
installCommand: ffmpegCmds.installCommand,
|
|
||||||
updateCommand: ffmpegCmds.updateCommand,
|
|
||||||
manualInstallCommand: ffmpegCmds.manualInstallCommand,
|
|
||||||
manualUpdateCommand: ffmpegCmds.manualUpdateCommand,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 'sharp',
|
|
||||||
name: 'Sharp',
|
|
||||||
description: 'High-performance image processing library',
|
|
||||||
versionCommand: ['bun', '--eval', "console.log(require('sharp').versions.sharp)"],
|
|
||||||
versionParser: (output) => output.trim() || null,
|
|
||||||
installCommand: ['bun', 'add', 'sharp'],
|
|
||||||
updateCommand: ['bun', 'add', 'sharp@latest'],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 'whisper-cpp',
|
|
||||||
name: 'Whisper.cpp',
|
|
||||||
description: 'Speech-to-text inference engine',
|
|
||||||
versionCommand: ['whisper-cpp', '--version'],
|
|
||||||
versionParser: (output) => output.trim() || null,
|
|
||||||
installCommand: detectPackageManager() === 'brew' ? ['brew', 'install', 'whisper-cpp'] : undefined,
|
|
||||||
processName: 'whisper-server',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 'mlx-audio',
|
|
||||||
name: 'MLX Audio',
|
|
||||||
description: 'Audio processing with Apple MLX framework',
|
|
||||||
versionCommand: ['pip', 'show', 'mlx-audio'],
|
|
||||||
versionParser: (output) => {
|
|
||||||
const match = output.match(/Version:\s*(\S+)/);
|
|
||||||
return match?.[1] ?? null;
|
|
||||||
},
|
|
||||||
installCommand: ['pip', 'install', 'mlx-audio'],
|
|
||||||
updateCommand: ['pip', 'install', '--upgrade', 'mlx-audio'],
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
const COMMAND_TIMEOUT_MS = 30_000;
|
|
||||||
|
|
||||||
const runCommand = async (command: string[]): Promise<{ exitCode: number; stdout: string; stderr: string }> => {
|
|
||||||
try {
|
|
||||||
const proc = Bun.spawn(command, { stdout: 'pipe', stderr: 'pipe' });
|
|
||||||
const timeout = new Promise<never>((_, reject) =>
|
|
||||||
setTimeout(() => {
|
|
||||||
proc.kill();
|
|
||||||
reject(new Error('Command timed out'));
|
|
||||||
}, COMMAND_TIMEOUT_MS),
|
|
||||||
);
|
|
||||||
const result = Promise.all([new Response(proc.stdout).text(), new Response(proc.stderr).text(), proc.exited]);
|
|
||||||
const [stdout, stderr] = await Promise.race([result, timeout]);
|
|
||||||
return { exitCode: proc.exitCode ?? 1, stdout, stderr };
|
|
||||||
} catch (err) {
|
|
||||||
const message = err instanceof Error ? err.message : 'Command not found or failed to execute';
|
|
||||||
return { exitCode: 1, stdout: '', stderr: message };
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const checkVersion = async (app: AppSpec): Promise<string | null> => {
|
|
||||||
const result = await runCommand(app.versionCommand);
|
|
||||||
if (result.exitCode !== 0) return null;
|
|
||||||
return app.versionParser(result.stdout);
|
|
||||||
};
|
|
||||||
|
|
||||||
const checkRunning = async (processName: string): Promise<boolean> => {
|
|
||||||
const result = await runCommand(['pgrep', '-x', processName]);
|
|
||||||
return result.exitCode === 0;
|
|
||||||
};
|
|
||||||
|
|
||||||
const getAppStatus = async (app: AppSpec): Promise<AppStatus> => {
|
|
||||||
const version = await checkVersion(app);
|
|
||||||
const running = app.processName ? await checkRunning(app.processName) : null;
|
|
||||||
return {
|
|
||||||
id: app.id,
|
|
||||||
name: app.name,
|
|
||||||
description: app.description,
|
|
||||||
installed: version !== null,
|
|
||||||
version,
|
|
||||||
running,
|
|
||||||
hasInstall: !!(app.installCommand || app.manualInstallCommand),
|
|
||||||
hasUpdate: !!(app.updateCommand ?? app.installCommand ?? app.manualUpdateCommand ?? app.manualInstallCommand),
|
|
||||||
manualInstallCommand: app.manualInstallCommand ?? null,
|
|
||||||
manualUpdateCommand: app.manualUpdateCommand ?? null,
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
export const applicationsRouter = createRouter();
|
|
||||||
|
|
||||||
applicationsRouter.get('/', async (ctx) => {
|
|
||||||
const statuses = await Promise.all(apps.map(getAppStatus));
|
|
||||||
return ctx.json(statuses);
|
|
||||||
});
|
|
||||||
|
|
||||||
applicationsRouter.get('/:id', async (ctx) => {
|
|
||||||
const app = apps.find((a) => a.id === ctx.req.param('id'));
|
|
||||||
if (!app) return ctx.json({ error: 'Application not found' }, 404);
|
|
||||||
const status = await getAppStatus(app);
|
|
||||||
return ctx.json(status);
|
|
||||||
});
|
|
||||||
|
|
||||||
applicationsRouter.post('/:id/install', async (ctx) => {
|
|
||||||
const app = apps.find((a) => a.id === ctx.req.param('id'));
|
|
||||||
if (!app) return ctx.json({ error: 'Application not found' }, 404);
|
|
||||||
if (!app.installCommand) return ctx.json({ error: 'No install command available for this platform' }, 400);
|
|
||||||
|
|
||||||
const result = await runCommand(app.installCommand);
|
|
||||||
if (result.exitCode !== 0) {
|
|
||||||
return ctx.json({ error: result.stderr.trim() || 'Installation failed' }, 500);
|
|
||||||
}
|
|
||||||
|
|
||||||
const status = await getAppStatus(app);
|
|
||||||
return ctx.json(status);
|
|
||||||
});
|
|
||||||
|
|
||||||
applicationsRouter.post('/:id/update', async (ctx) => {
|
|
||||||
const app = apps.find((a) => a.id === ctx.req.param('id'));
|
|
||||||
if (!app) return ctx.json({ error: 'Application not found' }, 404);
|
|
||||||
|
|
||||||
const command = app.updateCommand ?? app.installCommand;
|
|
||||||
if (!command) return ctx.json({ error: 'No update command available for this platform' }, 400);
|
|
||||||
|
|
||||||
const result = await runCommand(command);
|
|
||||||
if (result.exitCode !== 0) {
|
|
||||||
return ctx.json({ error: result.stderr.trim() || 'Update failed' }, 500);
|
|
||||||
}
|
|
||||||
|
|
||||||
const status = await getAppStatus(app);
|
|
||||||
return ctx.json(status);
|
|
||||||
});
|
|
||||||
@@ -370,11 +370,8 @@ chatProvidersRouter.get('/providers', async (ctx) => {
|
|||||||
return ctx.json(PROVIDERS);
|
return ctx.json(PROVIDERS);
|
||||||
});
|
});
|
||||||
|
|
||||||
chatProvidersRouter.get('/auth', async (ctx) => {
|
// GET /auth reported which providers had a key set. No caller — AIHarnessesSection reads /api-keys and
|
||||||
const auth = await readAuthJson();
|
// /api-keys/health instead, which say the same thing with more detail.
|
||||||
const providers = PROVIDERS.filter((p) => auth[p.providerId]?.key?.trim()).map((p) => p.key);
|
|
||||||
return ctx.json({ authenticated: providers.length > 0, providers });
|
|
||||||
});
|
|
||||||
|
|
||||||
const maskValue = (value: string) => {
|
const maskValue = (value: string) => {
|
||||||
if (value.length <= 8) return '***';
|
if (value.length <= 8) return '***';
|
||||||
|
|||||||
@@ -1,80 +0,0 @@
|
|||||||
import { createRouter } from '../../create-router';
|
|
||||||
|
|
||||||
export const claudeCodeRouter = createRouter();
|
|
||||||
|
|
||||||
const GLOBAL_DIRS = ['/usr/local/bin', '/usr/bin'];
|
|
||||||
|
|
||||||
const getPaths = async () => {
|
|
||||||
try {
|
|
||||||
const proc = Bun.spawn(['which', '-a', 'claude'], { stdout: 'pipe', stderr: 'pipe' });
|
|
||||||
const output = await new Response(proc.stdout).text();
|
|
||||||
await proc.exited;
|
|
||||||
if (proc.exitCode !== 0) return { path: null, globalPath: null };
|
|
||||||
const paths = [...new Set(output.trim().split('\n'))];
|
|
||||||
const path = paths[0] ?? null;
|
|
||||||
const globalPath = paths.find((p) => GLOBAL_DIRS.some((dir) => p.startsWith(dir))) ?? null;
|
|
||||||
return { path, globalPath };
|
|
||||||
} catch {
|
|
||||||
return { path: null, globalPath: null };
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
claudeCodeRouter.post('/install', async (ctx) => {
|
|
||||||
try {
|
|
||||||
const proc = Bun.spawn(['bash', '-c', 'curl -fsSL https://claude.ai/install.sh | bash'], {
|
|
||||||
stdout: 'pipe',
|
|
||||||
stderr: 'pipe',
|
|
||||||
});
|
|
||||||
await proc.exited;
|
|
||||||
if (proc.exitCode !== 0) {
|
|
||||||
const stderr = await new Response(proc.stderr).text();
|
|
||||||
return ctx.json({ version: null, path: null, globalPath: null, error: stderr.trim() }, 500);
|
|
||||||
}
|
|
||||||
const versionProc = Bun.spawn(['claude', '--version'], { stdout: 'pipe', stderr: 'pipe' });
|
|
||||||
const output = await new Response(versionProc.stdout).text();
|
|
||||||
await versionProc.exited;
|
|
||||||
const { path, globalPath } = await getPaths();
|
|
||||||
return ctx.json({ version: output.trim(), path, globalPath });
|
|
||||||
} catch {
|
|
||||||
return ctx.json({ version: null, path: null, globalPath: null, error: 'Installation failed' }, 500);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
claudeCodeRouter.post('/auth/login', async (ctx) => {
|
|
||||||
try {
|
|
||||||
const env = { ...process.env };
|
|
||||||
delete env.CLAUDECODE;
|
|
||||||
Bun.spawn(['claude', 'auth', 'login'], { stdout: 'ignore', stderr: 'ignore', env });
|
|
||||||
return ctx.json({ started: true });
|
|
||||||
} catch {
|
|
||||||
return ctx.json({ started: false, error: 'Failed to start login' }, 500);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
claudeCodeRouter.get('/auth', async (ctx) => {
|
|
||||||
try {
|
|
||||||
const proc = Bun.spawn(['claude', 'auth', 'status'], { stdout: 'pipe', stderr: 'pipe' });
|
|
||||||
const output = await new Response(proc.stdout).text();
|
|
||||||
await proc.exited;
|
|
||||||
if (proc.exitCode === 0) {
|
|
||||||
const status = JSON.parse(output.trim());
|
|
||||||
if (status.loggedIn) return ctx.json({ authenticated: true, ...status });
|
|
||||||
}
|
|
||||||
return ctx.json({ authenticated: false });
|
|
||||||
} catch {
|
|
||||||
return ctx.json({ authenticated: false });
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
claudeCodeRouter.get('/version', async (ctx) => {
|
|
||||||
try {
|
|
||||||
const proc = Bun.spawn(['claude', '--version'], { stdout: 'pipe', stderr: 'pipe' });
|
|
||||||
const output = await new Response(proc.stdout).text();
|
|
||||||
await proc.exited;
|
|
||||||
if (proc.exitCode !== 0) return ctx.json({ version: null, path: null, globalPath: null });
|
|
||||||
const { path, globalPath } = await getPaths();
|
|
||||||
return ctx.json({ version: output.trim(), path, globalPath });
|
|
||||||
} catch {
|
|
||||||
return ctx.json({ version: null, path: null, globalPath: null });
|
|
||||||
}
|
|
||||||
});
|
|
||||||
@@ -2,9 +2,7 @@ import { createRouter } from '../../create-router';
|
|||||||
import { readdirSync, existsSync } from 'node:fs';
|
import { readdirSync, existsSync } from 'node:fs';
|
||||||
import { join } from 'node:path';
|
import { join } from 'node:path';
|
||||||
import { readServerSettings, writeServerSettings } from 'officerdb';
|
import { readServerSettings, writeServerSettings } from 'officerdb';
|
||||||
import { claudeCodeRouter } from './claude-code';
|
|
||||||
import { chatProvidersRouter } from './chat-providers';
|
import { chatProvidersRouter } from './chat-providers';
|
||||||
import { applicationsRouter } from './applications';
|
|
||||||
import { smtpRouter } from './smtp';
|
import { smtpRouter } from './smtp';
|
||||||
import { ttsRouter } from './tts';
|
import { ttsRouter } from './tts';
|
||||||
import { sttRouter } from './stt';
|
import { sttRouter } from './stt';
|
||||||
@@ -12,9 +10,7 @@ import { ocrRouter } from './ocr';
|
|||||||
|
|
||||||
export const serverSettingsRouter = createRouter();
|
export const serverSettingsRouter = createRouter();
|
||||||
|
|
||||||
serverSettingsRouter.route('/claude-code', claudeCodeRouter);
|
|
||||||
serverSettingsRouter.route('/chat-providers', chatProvidersRouter);
|
serverSettingsRouter.route('/chat-providers', chatProvidersRouter);
|
||||||
serverSettingsRouter.route('/applications', applicationsRouter);
|
|
||||||
serverSettingsRouter.route('/smtp', smtpRouter);
|
serverSettingsRouter.route('/smtp', smtpRouter);
|
||||||
serverSettingsRouter.route('/tts', ttsRouter);
|
serverSettingsRouter.route('/tts', ttsRouter);
|
||||||
serverSettingsRouter.route('/stt', sttRouter);
|
serverSettingsRouter.route('/stt', sttRouter);
|
||||||
|
|||||||
@@ -8,7 +8,6 @@ import type {
|
|||||||
ClaudeCodeResult,
|
ClaudeCodeResult,
|
||||||
OpenCodeRunParams,
|
OpenCodeRunParams,
|
||||||
VncStartParams,
|
VncStartParams,
|
||||||
VncSessionInfo,
|
|
||||||
} from './sidecar/protocol';
|
} from './sidecar/protocol';
|
||||||
import type { SidecarRegistration } from './sidecar/registration-protocol';
|
import type { SidecarRegistration } from './sidecar/registration-protocol';
|
||||||
import type { TurnMessage } from './api/chat/types';
|
import type { TurnMessage } from './api/chat/types';
|
||||||
@@ -348,12 +347,6 @@ export function stopVnc(email: string): void {
|
|||||||
sendFire('vnc', { type: 'vnc:stop', id: nextId(), email });
|
sendFire('vnc', { type: 'vnc:stop', id: nextId(), email });
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function getVncStatus(email: string): Promise<VncSessionInfo | null> {
|
|
||||||
const res = await sendCommand('vnc', { type: 'vnc:status', id: nextId(), email });
|
|
||||||
if (res.type === 'vnc:status') return res.session;
|
|
||||||
throw new Error('Unexpected response');
|
|
||||||
}
|
|
||||||
|
|
||||||
export function isVncConnected(): boolean {
|
export function isVncConnected(): boolean {
|
||||||
return findSidecarByCapability('vnc') !== undefined;
|
return findSidecarByCapability('vnc') !== undefined;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -24,8 +24,7 @@ export type SidecarCommand =
|
|||||||
| { type: 'vnc:start'; id: string; params: VncStartParams }
|
| { type: 'vnc:start'; id: string; params: VncStartParams }
|
||||||
// Provision the VNC password without starting a server — the UI needs it before it can connect
|
// Provision the VNC password without starting a server — the UI needs it before it can connect
|
||||||
| { type: 'vnc:ensure-password'; id: string; email: string }
|
| { type: 'vnc:ensure-password'; id: string; email: string }
|
||||||
| { type: 'vnc:stop'; id: string; email: string }
|
| { type: 'vnc:stop'; id: string; email: string };
|
||||||
| { type: 'vnc:status'; id: string; email: string };
|
|
||||||
|
|
||||||
// ── Responses/Events (sidecar → API server) ──
|
// ── Responses/Events (sidecar → API server) ──
|
||||||
|
|
||||||
@@ -48,7 +47,6 @@ export type SidecarEvent =
|
|||||||
| { type: 'vnc:started'; id: string; port: number; display: number }
|
| { type: 'vnc:started'; id: string; port: number; display: number }
|
||||||
| { type: 'vnc:password'; id: string; password: string }
|
| { type: 'vnc:password'; id: string; password: string }
|
||||||
| { type: 'vnc:stopped'; id: string }
|
| { type: 'vnc:stopped'; id: string }
|
||||||
| { type: 'vnc:status'; id: string; session: VncSessionInfo | null }
|
|
||||||
| { type: 'vnc:error'; id: string; error: string }
|
| { type: 'vnc:error'; id: string; error: string }
|
||||||
// Email — new mail no longer crosses this socket; the sidecar owns the SSE stream and pushes directly
|
// Email — new mail no longer crosses this socket; the sidecar owns the SSE stream and pushes directly
|
||||||
// OpenCode — the sidecar reports where its `opencode serve` is listening (random port) on connect
|
// OpenCode — the sidecar reports where its `opencode serve` is listening (random port) on connect
|
||||||
|
|||||||
@@ -40,12 +40,6 @@ async function handleCommand(cmd: SidecarCommand, reply: ReplyFn) {
|
|||||||
reply({ type: 'vnc:stopped', id: cmd.id });
|
reply({ type: 'vnc:stopped', id: cmd.id });
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 'vnc:status': {
|
|
||||||
const session = vncManager.getSession(cmd.email);
|
|
||||||
reply({ type: 'vnc:status', id: cmd.id, session });
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
default:
|
default:
|
||||||
reply({
|
reply({
|
||||||
type: 'error',
|
type: 'error',
|
||||||
|
|||||||
Reference in New Issue
Block a user