Files
openchamber/packages/web/server/index.js
T
Bohdan Triapitsyn 844052e599 fix: restore desktop startup and align tool previews with SDK updates
Add reflect-metadata bootstrap so desktop sidecar no longer crashes on startup.
Update SDK v1.4 compatibility for model variant and diff payload handling.
Unify write/edit/apply patch expanded previews and hide write success output noise.
2026-04-12 00:40:49 +03:00

1050 lines
43 KiB
JavaScript

import 'reflect-metadata';
import express from 'express';
import path from 'path';
import { spawn, spawnSync } from 'child_process';
import fs from 'fs';
import http from 'http';
import net from 'net';
import { fileURLToPath } from 'url';
import os from 'os';
import crypto from 'crypto';
import { createUiAuth } from './lib/ui-auth/ui-auth.js';
import { createTunnelAuth } from './lib/opencode/tunnel-auth.js';
import { createManagedTunnelConfigRuntime } from './lib/tunnels/managed-config.js';
import { createTunnelProviderRegistry } from './lib/tunnels/registry.js';
import { createCloudflareTunnelProvider } from './lib/tunnels/providers/cloudflare.js';
import { createRequestSecurityRuntime } from './lib/security/request-security.js';
import {
TUNNEL_MODE_MANAGED_LOCAL,
TUNNEL_MODE_MANAGED_REMOTE,
TUNNEL_MODE_QUICK,
TUNNEL_PROVIDER_CLOUDFLARE,
TunnelServiceError,
isSupportedTunnelMode,
normalizeOptionalPath,
normalizeTunnelStartRequest,
normalizeTunnelMode,
normalizeTunnelProvider,
} from './lib/tunnels/types.js';
import { prepareNotificationLastMessage } from './lib/notifications/index.js';
import { registerTtsRoutes } from './lib/tts/routes.js';
import { detectSayTtsCapability } from './lib/tts/capability-runtime.js';
import { createTerminalRuntime } from './lib/terminal/runtime.js';
import { createFsSearchRuntime as createFsSearchRuntimeFactory } from './lib/fs/search.js';
import { createOpenCodeLifecycleRuntime } from './lib/opencode/lifecycle.js';
import { createOpenCodeEnvRuntime } from './lib/opencode/env-runtime.js';
import { resolveOpenCodeEnvConfig } from './lib/opencode/env-config.js';
import { createHmrStateRuntime } from './lib/opencode/hmr-state-runtime.js';
import { createOpenCodeNetworkRuntime } from './lib/opencode/network-runtime.js';
import { createOpenCodeAuthStateRuntime } from './lib/opencode/auth-state-runtime.js';
import { createProjectDirectoryRuntime } from './lib/opencode/project-directory-runtime.js';
import { createSettingsNormalizationRuntime } from './lib/opencode/settings-normalization-runtime.js';
import { createSettingsHelpers } from './lib/opencode/settings-helpers.js';
import { createThemeRuntime } from './lib/opencode/theme-runtime.js';
import { createFeatureRoutesRuntime } from './lib/opencode/feature-routes-runtime.js';
import { parseServeCliOptions } from './lib/opencode/cli-options.js';
import {
registerAuthAndAccessRoutes,
registerCommonRequestMiddleware,
registerServerStatusRoutes,
} from './lib/opencode/core-routes.js';
import { registerOpenChamberRoutes } from './lib/opencode/openchamber-routes.js';
import { createServerUtilsRuntime } from './lib/opencode/server-utils-runtime.js';
import { createStaticRoutesRuntime } from './lib/opencode/static-routes-runtime.js';
import { createSettingsRuntime } from './lib/opencode/settings-runtime.js';
import { createOpenCodeResolutionRuntime } from './lib/opencode/opencode-resolution-runtime.js';
import { createBootstrapRuntime } from './lib/opencode/bootstrap-runtime.js';
import { createSessionRuntime } from './lib/opencode/session-runtime.js';
import { createOpenCodeWatcherRuntime } from './lib/opencode/watcher.js';
import { createServerStartupRuntime } from './lib/opencode/server-startup-runtime.js';
import { createTunnelWiringRuntime } from './lib/opencode/tunnel-wiring-runtime.js';
import { createStartupPipelineRuntime } from './lib/opencode/startup-pipeline-runtime.js';
import { runCliEntryIfMain } from './lib/opencode/cli-entry-runtime.js';
import { registerNotificationRoutes } from './lib/notifications/routes.js';
import { createNotificationEmitterRuntime } from './lib/notifications/emitter-runtime.js';
import { createNotificationTriggerRuntime } from './lib/notifications/runtime.js';
import { createPushRuntime } from './lib/notifications/push-runtime.js';
import { createNotificationTemplateRuntime } from './lib/notifications/template-runtime.js';
import { createGracefulShutdownRuntime } from './lib/opencode/shutdown-runtime.js';
import webPush from 'web-push';
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
const DEFAULT_PORT = 3000;
const DESKTOP_NOTIFY_PREFIX = '[OpenChamberDesktopNotify] ';
const uiNotificationClients = new Set();
const HEALTH_CHECK_INTERVAL = 15000;
const SHUTDOWN_TIMEOUT = 10000;
const MODELS_DEV_API_URL = 'https://models.dev/api.json';
const MODELS_METADATA_CACHE_TTL = 5 * 60 * 1000;
const CLIENT_RELOAD_DELAY_MS = 800;
const OPEN_CODE_READY_GRACE_MS = 12000;
const LONG_REQUEST_TIMEOUT_MS = 4 * 60 * 1000;
const TUNNEL_BOOTSTRAP_TTL_DEFAULT_MS = 30 * 60 * 1000;
const TUNNEL_BOOTSTRAP_TTL_MIN_MS = 60 * 1000;
const TUNNEL_BOOTSTRAP_TTL_MAX_MS = 24 * 60 * 60 * 1000;
const TUNNEL_SESSION_TTL_DEFAULT_MS = 8 * 60 * 60 * 1000;
const TUNNEL_SESSION_TTL_MIN_MS = 5 * 60 * 1000;
const TUNNEL_SESSION_TTL_MAX_MS = 30 * 24 * 60 * 60 * 1000;
const OPENCHAMBER_VERSION = (() => {
try {
const packagePath = path.resolve(__dirname, '..', 'package.json');
const raw = fs.readFileSync(packagePath, 'utf8');
const pkg = JSON.parse(raw);
if (pkg && typeof pkg.version === 'string' && pkg.version.trim().length > 0) {
return pkg.version.trim();
}
} catch {
}
return 'unknown';
})();
const isEnvFlagEnabled = (value) => {
if (value === true || value === 1) return true;
if (typeof value !== 'string') return false;
const normalized = value.trim().toLowerCase();
return normalized === '1' || normalized === 'true';
};
const PLAN_MODE_EXPERIMENT_ENABLED =
isEnvFlagEnabled(process.env.OPENCODE_EXPERIMENTAL_PLAN_MODE)
|| isEnvFlagEnabled(process.env.OPENCODE_EXPERIMENTAL);
const fsPromises = fs.promises;
const settingsNormalizationRuntime = createSettingsNormalizationRuntime({
os,
path,
processLike: process,
tunnelBootstrapTtlDefaultMs: TUNNEL_BOOTSTRAP_TTL_DEFAULT_MS,
tunnelBootstrapTtlMinMs: TUNNEL_BOOTSTRAP_TTL_MIN_MS,
tunnelBootstrapTtlMaxMs: TUNNEL_BOOTSTRAP_TTL_MAX_MS,
tunnelSessionTtlDefaultMs: TUNNEL_SESSION_TTL_DEFAULT_MS,
tunnelSessionTtlMinMs: TUNNEL_SESSION_TTL_MIN_MS,
tunnelSessionTtlMaxMs: TUNNEL_SESSION_TTL_MAX_MS,
});
const normalizeDirectoryPath = (...args) => settingsNormalizationRuntime.normalizeDirectoryPath(...args);
const normalizePathForPersistence = (...args) => settingsNormalizationRuntime.normalizePathForPersistence(...args);
const normalizeSettingsPaths = (...args) => settingsNormalizationRuntime.normalizeSettingsPaths(...args);
const normalizeTunnelBootstrapTtlMs = (...args) => settingsNormalizationRuntime.normalizeTunnelBootstrapTtlMs(...args);
const normalizeTunnelSessionTtlMs = (...args) => settingsNormalizationRuntime.normalizeTunnelSessionTtlMs(...args);
const normalizeManagedRemoteTunnelHostname = (...args) =>
settingsNormalizationRuntime.normalizeManagedRemoteTunnelHostname(...args);
const normalizeManagedRemoteTunnelPresets = (...args) =>
settingsNormalizationRuntime.normalizeManagedRemoteTunnelPresets(...args);
const normalizeManagedRemoteTunnelPresetTokens = (...args) =>
settingsNormalizationRuntime.normalizeManagedRemoteTunnelPresetTokens(...args);
const isUnsafeSkillRelativePath = (...args) => settingsNormalizationRuntime.isUnsafeSkillRelativePath(...args);
const sanitizeTypographySizesPartial = (...args) =>
settingsNormalizationRuntime.sanitizeTypographySizesPartial(...args);
const normalizeStringArray = (...args) => settingsNormalizationRuntime.normalizeStringArray(...args);
const sanitizeModelRefs = (...args) => settingsNormalizationRuntime.sanitizeModelRefs(...args);
const sanitizeSkillCatalogs = (...args) => settingsNormalizationRuntime.sanitizeSkillCatalogs(...args);
const sanitizeProjects = (...args) => settingsNormalizationRuntime.sanitizeProjects(...args);
const OPENCHAMBER_USER_CONFIG_ROOT = path.join(os.homedir(), '.config', 'openchamber');
const OPENCHAMBER_USER_THEMES_DIR = path.join(OPENCHAMBER_USER_CONFIG_ROOT, 'themes');
const MAX_THEME_JSON_BYTES = 512 * 1024;
const themeRuntime = createThemeRuntime({
fsPromises,
path,
themesDir: OPENCHAMBER_USER_THEMES_DIR,
maxThemeJsonBytes: MAX_THEME_JSON_BYTES,
logger: console,
});
const readCustomThemesFromDisk = (...args) => themeRuntime.readCustomThemesFromDisk(...args);
let notificationTemplateRuntime = null;
const createTimeoutSignal = (...args) => notificationTemplateRuntime.createTimeoutSignal(...args);
const formatProjectLabel = (...args) => notificationTemplateRuntime.formatProjectLabel(...args);
const resolveNotificationTemplate = (...args) => notificationTemplateRuntime.resolveNotificationTemplate(...args);
const shouldApplyResolvedTemplateMessage = (...args) => notificationTemplateRuntime.shouldApplyResolvedTemplateMessage(...args);
const fetchFreeZenModels = (...args) => notificationTemplateRuntime.fetchFreeZenModels(...args);
const resolveZenModel = (...args) => notificationTemplateRuntime.resolveZenModel(...args);
const validateZenModelAtStartup = (...args) => notificationTemplateRuntime.validateZenModelAtStartup(...args);
const summarizeText = (...args) => notificationTemplateRuntime.summarizeText(...args);
const extractTextFromParts = (...args) => notificationTemplateRuntime.extractTextFromParts(...args);
const extractLastMessageText = (...args) => notificationTemplateRuntime.extractLastMessageText(...args);
const fetchLastAssistantMessageText = (...args) => notificationTemplateRuntime.fetchLastAssistantMessageText(...args);
const maybeCacheSessionInfoFromEvent = (...args) => notificationTemplateRuntime.maybeCacheSessionInfoFromEvent(...args);
const buildTemplateVariables = (...args) => notificationTemplateRuntime.buildTemplateVariables(...args);
const getCachedZenModels = (...args) => notificationTemplateRuntime.getCachedZenModels(...args);
const OPENCHAMBER_DATA_DIR = process.env.OPENCHAMBER_DATA_DIR
? path.resolve(process.env.OPENCHAMBER_DATA_DIR)
: path.join(os.homedir(), '.config', 'openchamber');
const SETTINGS_FILE_PATH = path.join(OPENCHAMBER_DATA_DIR, 'settings.json');
const PUSH_SUBSCRIPTIONS_FILE_PATH = path.join(OPENCHAMBER_DATA_DIR, 'push-subscriptions.json');
const CLOUDFLARE_MANAGED_REMOTE_TUNNELS_FILE_PATH = path.join(OPENCHAMBER_DATA_DIR, 'cloudflare-managed-remote-tunnels.json');
const CLOUDFLARE_LEGACY_NAMED_TUNNELS_FILE_PATH = path.join(OPENCHAMBER_DATA_DIR, 'cloudflare-named-tunnels.json');
const CLOUDFLARE_MANAGED_REMOTE_TUNNELS_VERSION = 1;
const managedTunnelConfigRuntime = createManagedTunnelConfigRuntime({
fsPromises,
path,
normalizeManagedRemoteTunnelHostname,
normalizeManagedRemoteTunnelPresets,
constants: {
CLOUDFLARE_MANAGED_REMOTE_TUNNELS_FILE_PATH,
CLOUDFLARE_LEGACY_NAMED_TUNNELS_FILE_PATH,
CLOUDFLARE_MANAGED_REMOTE_TUNNELS_VERSION,
},
});
const readManagedRemoteTunnelConfigFromDisk = (...args) => managedTunnelConfigRuntime.readManagedRemoteTunnelConfigFromDisk(...args);
const syncManagedRemoteTunnelConfigWithPresets = (...args) => managedTunnelConfigRuntime.syncManagedRemoteTunnelConfigWithPresets(...args);
const upsertManagedRemoteTunnelToken = (...args) => managedTunnelConfigRuntime.upsertManagedRemoteTunnelToken(...args);
const resolveManagedRemoteTunnelToken = (...args) => managedTunnelConfigRuntime.resolveManagedRemoteTunnelToken(...args);
const settingsHelpers = createSettingsHelpers({
normalizePathForPersistence,
normalizeDirectoryPath,
normalizeTunnelBootstrapTtlMs,
normalizeTunnelSessionTtlMs,
normalizeTunnelProvider,
normalizeTunnelMode,
normalizeOptionalPath,
normalizeManagedRemoteTunnelHostname,
normalizeManagedRemoteTunnelPresets,
normalizeManagedRemoteTunnelPresetTokens,
sanitizeTypographySizesPartial,
normalizeStringArray,
sanitizeModelRefs,
sanitizeSkillCatalogs,
sanitizeProjects,
});
const normalizePwaAppName = (...args) => settingsHelpers.normalizePwaAppName(...args);
const sanitizeSettingsUpdate = (...args) => settingsHelpers.sanitizeSettingsUpdate(...args);
const mergePersistedSettings = (...args) => settingsHelpers.mergePersistedSettings(...args);
const formatSettingsResponse = (...args) => settingsHelpers.formatSettingsResponse(...args);
const projectDirectoryRuntime = createProjectDirectoryRuntime({
fsPromises,
path,
normalizeDirectoryPath,
getReadSettingsFromDiskMigrated: () => readSettingsFromDiskMigrated,
sanitizeProjects,
});
const resolveDirectoryCandidate = (...args) => projectDirectoryRuntime.resolveDirectoryCandidate(...args);
const validateDirectoryPath = (...args) => projectDirectoryRuntime.validateDirectoryPath(...args);
const resolveProjectDirectory = (...args) => projectDirectoryRuntime.resolveProjectDirectory(...args);
const resolveOptionalProjectDirectory = (...args) => projectDirectoryRuntime.resolveOptionalProjectDirectory(...args);
const settingsRuntime = createSettingsRuntime({
fsPromises,
path,
crypto,
SETTINGS_FILE_PATH,
sanitizeProjects,
sanitizeSettingsUpdate,
mergePersistedSettings,
normalizeSettingsPaths,
normalizeStringArray,
formatSettingsResponse,
resolveDirectoryCandidate,
normalizeManagedRemoteTunnelHostname,
normalizeManagedRemoteTunnelPresets,
normalizeManagedRemoteTunnelPresetTokens,
syncManagedRemoteTunnelConfigWithPresets,
upsertManagedRemoteTunnelToken,
});
const readSettingsFromDiskMigrated = (...args) => settingsRuntime.readSettingsFromDiskMigrated(...args);
const readSettingsFromDisk = (...args) => settingsRuntime.readSettingsFromDisk(...args);
const writeSettingsToDisk = (...args) => settingsRuntime.writeSettingsToDisk(...args);
const persistSettings = (...args) => settingsRuntime.persistSettings(...args);
const requestSecurityRuntime = createRequestSecurityRuntime({
readSettingsFromDiskMigrated,
});
const getUiSessionTokenFromRequest = (...args) => requestSecurityRuntime.getUiSessionTokenFromRequest(...args);
const pushRuntime = createPushRuntime({
fsPromises,
path,
webPush,
PUSH_SUBSCRIPTIONS_FILE_PATH,
readSettingsFromDiskMigrated,
writeSettingsToDisk,
});
const getOrCreateVapidKeys = (...args) => pushRuntime.getOrCreateVapidKeys(...args);
const addOrUpdatePushSubscription = (...args) => pushRuntime.addOrUpdatePushSubscription(...args);
const removePushSubscription = (...args) => pushRuntime.removePushSubscription(...args);
const sendPushToAllUiSessions = (...args) => pushRuntime.sendPushToAllUiSessions(...args);
const updateUiVisibility = (...args) => pushRuntime.updateUiVisibility(...args);
const isAnyUiVisible = (...args) => pushRuntime.isAnyUiVisible(...args);
const isUiVisible = (...args) => pushRuntime.isUiVisible(...args);
const ensurePushInitialized = (...args) => pushRuntime.ensurePushInitialized(...args);
const setPushInitialized = (...args) => pushRuntime.setPushInitialized(...args);
const TERMINAL_INPUT_WS_MAX_REBINDS_PER_WINDOW = 128;
const TERMINAL_INPUT_WS_REBIND_WINDOW_MS = 60 * 1000;
const TERMINAL_INPUT_WS_HEARTBEAT_INTERVAL_MS = 15 * 1000;
const rejectWebSocketUpgrade = (...args) => requestSecurityRuntime.rejectWebSocketUpgrade(...args);
const isRequestOriginAllowed = (...args) => requestSecurityRuntime.isRequestOriginAllowed(...args);
const notificationEmitterRuntime = createNotificationEmitterRuntime({
process,
getDesktopNotifyEnabled: () => ENV_DESKTOP_NOTIFY,
desktopNotifyPrefix: DESKTOP_NOTIFY_PREFIX,
getUiNotificationClients: () => uiNotificationClients,
});
const writeSseEvent = (...args) => notificationEmitterRuntime.writeSseEvent(...args);
const emitDesktopNotification = (...args) => notificationEmitterRuntime.emitDesktopNotification(...args);
const broadcastUiNotification = (...args) => notificationEmitterRuntime.broadcastUiNotification(...args);
const sessionRuntime = createSessionRuntime({
writeSseEvent,
getNotificationClients: () => uiNotificationClients,
});
// HMR-persistent state via globalThis
// These values survive Vite HMR reloads to prevent zombie OpenCode processes
const hmrStateRuntime = createHmrStateRuntime({
globalThisLike: globalThis,
os,
processLike: process,
stateKey: '__openchamberHmrState',
});
const hmrState = hmrStateRuntime.getOrCreateHmrState();
hmrStateRuntime.ensureUserProvidedOpenCodePassword(hmrState);
// Non-HMR state (safe to reset on reload)
let healthCheckInterval = null;
let server = null;
let expressApp = null;
let currentRestartPromise = null;
let isRestartingOpenCode = false;
let openCodeApiPrefix = '';
let openCodeApiPrefixDetected = true;
let openCodeApiDetectionTimer = null;
let lastOpenCodeError = null;
let isOpenCodeReady = false;
let openCodeNotReadySince = 0;
let isExternalOpenCode = false;
let exitOnShutdown = true;
let uiAuthController = null;
let activeTunnelController = null;
let globalWatcherStartPromise = null;
const tunnelProviderRegistry = createTunnelProviderRegistry([
createCloudflareTunnelProvider(),
]);
tunnelProviderRegistry.seal();
const tunnelAuthController = createTunnelAuth();
let runtimeManagedRemoteTunnelToken = '';
let runtimeManagedRemoteTunnelHostname = '';
let terminalRuntime = null;
const userProvidedOpenCodePassword = hmrStateRuntime.getUserProvidedOpenCodePassword(hmrState);
const initialOpenCodeAuthState = hmrStateRuntime.resolveOpenCodeAuthFromState({
hmrState,
userProvidedOpenCodePassword,
});
let openCodeAuthPassword = initialOpenCodeAuthState.openCodeAuthPassword;
let openCodeAuthSource = initialOpenCodeAuthState.openCodeAuthSource;
// Sync helper - call after modifying any HMR state variable
const syncToHmrState = () => {
hmrStateRuntime.syncStateFromRuntime(hmrState, {
openCodeProcess,
openCodePort,
openCodeBaseUrl,
isShuttingDown,
signalsAttached,
openCodeWorkingDirectory,
openCodeAuthPassword,
openCodeAuthSource,
});
};
// Sync helper - call to restore state from HMR (e.g., on module reload)
const syncFromHmrState = () => {
const restored = hmrStateRuntime.restoreRuntimeFromState({
hmrState,
userProvidedOpenCodePassword,
});
openCodeProcess = restored.openCodeProcess;
openCodePort = restored.openCodePort;
openCodeBaseUrl = restored.openCodeBaseUrl;
isShuttingDown = restored.isShuttingDown;
signalsAttached = restored.signalsAttached;
openCodeWorkingDirectory = restored.openCodeWorkingDirectory;
openCodeAuthPassword = restored.openCodeAuthPassword;
openCodeAuthSource = restored.openCodeAuthSource;
};
// Module-level variables that shadow HMR state
// These are synced to/from hmrState to survive HMR reloads
let openCodeProcess = hmrState.openCodeProcess;
let openCodePort = hmrState.openCodePort;
let openCodeBaseUrl = hmrState.openCodeBaseUrl ?? null;
let isShuttingDown = hmrState.isShuttingDown;
let signalsAttached = hmrState.signalsAttached;
let openCodeWorkingDirectory = hmrState.openCodeWorkingDirectory;
const {
configuredOpenCodePort: ENV_CONFIGURED_OPENCODE_PORT,
configuredOpenCodeHost: ENV_CONFIGURED_OPENCODE_HOST,
effectivePort: ENV_EFFECTIVE_PORT,
configuredOpenCodeHostname: ENV_CONFIGURED_OPENCODE_HOSTNAME,
} = resolveOpenCodeEnvConfig({
env: process.env,
logger: console,
});
const ENV_SKIP_OPENCODE_START = process.env.OPENCODE_SKIP_START === 'true' ||
process.env.OPENCHAMBER_SKIP_OPENCODE_START === 'true';
const ENV_DESKTOP_NOTIFY = (() => {
if (process.env.OPENCHAMBER_DESKTOP_NOTIFY === 'true') {
return true;
}
if (process.env.OPENCHAMBER_RUNTIME === 'desktop') {
return true;
}
const argv0 = typeof process.argv?.[0] === 'string' ? process.argv[0] : '';
const argv1 = typeof process.argv?.[1] === 'string' ? process.argv[1] : '';
return /openchamber-server/i.test(argv0) || /openchamber-server/i.test(argv1);
})();
const ENV_CONFIGURED_OPENCODE_WSL_DISTRO =
typeof process.env.OPENCODE_WSL_DISTRO === 'string' && process.env.OPENCODE_WSL_DISTRO.trim().length > 0
? process.env.OPENCODE_WSL_DISTRO.trim()
: (
typeof process.env.OPENCHAMBER_OPENCODE_WSL_DISTRO === 'string' &&
process.env.OPENCHAMBER_OPENCODE_WSL_DISTRO.trim().length > 0
? process.env.OPENCHAMBER_OPENCODE_WSL_DISTRO.trim()
: null
);
const openCodeAuthStateRuntime = createOpenCodeAuthStateRuntime({
crypto,
process,
getAuthPassword: () => openCodeAuthPassword,
setAuthPassword: (value) => {
openCodeAuthPassword = value;
},
getAuthSource: () => openCodeAuthSource,
setAuthSource: (value) => {
openCodeAuthSource = value;
},
getUserProvidedPassword: () => userProvidedOpenCodePassword,
syncToHmrState,
});
const getOpenCodeAuthHeaders = (...args) => openCodeAuthStateRuntime.getOpenCodeAuthHeaders(...args);
const isOpenCodeConnectionSecure = (...args) => openCodeAuthStateRuntime.isOpenCodeConnectionSecure(...args);
const ensureLocalOpenCodeServerPassword = (...args) => openCodeAuthStateRuntime.ensureLocalOpenCodeServerPassword(...args);
const openCodeNetworkState = {};
Object.defineProperties(openCodeNetworkState, {
openCodePort: { get: () => openCodePort, set: (value) => { openCodePort = value; } },
openCodeBaseUrl: { get: () => openCodeBaseUrl, set: (value) => { openCodeBaseUrl = value; } },
openCodeApiPrefix: { get: () => openCodeApiPrefix, set: (value) => { openCodeApiPrefix = value; } },
openCodeApiPrefixDetected: { get: () => openCodeApiPrefixDetected, set: (value) => { openCodeApiPrefixDetected = value; } },
openCodeApiDetectionTimer: { get: () => openCodeApiDetectionTimer, set: (value) => { openCodeApiDetectionTimer = value; } },
});
const openCodeNetworkRuntime = createOpenCodeNetworkRuntime({
state: openCodeNetworkState,
getOpenCodeAuthHeaders,
});
const waitForReady = (...args) => openCodeNetworkRuntime.waitForReady(...args);
const normalizeApiPrefix = (...args) => openCodeNetworkRuntime.normalizeApiPrefix(...args);
const setDetectedOpenCodeApiPrefix = (...args) => openCodeNetworkRuntime.setDetectedOpenCodeApiPrefix(...args);
const buildOpenCodeUrl = (...args) => openCodeNetworkRuntime.buildOpenCodeUrl(...args);
const ensureOpenCodeApiPrefix = (...args) => openCodeNetworkRuntime.ensureOpenCodeApiPrefix(...args);
const scheduleOpenCodeApiDetection = (...args) => openCodeNetworkRuntime.scheduleOpenCodeApiDetection(...args);
const ENV_CONFIGURED_API_PREFIX = normalizeApiPrefix(
process.env.OPENCODE_API_PREFIX || process.env.OPENCHAMBER_API_PREFIX || ''
);
if (ENV_CONFIGURED_API_PREFIX && ENV_CONFIGURED_API_PREFIX !== '') {
console.warn('Ignoring configured OpenCode API prefix; API runs at root.');
}
let cachedLoginShellEnvSnapshot;
let resolvedOpencodeBinary = null;
let resolvedOpencodeBinarySource = null;
let resolvedNodeBinary = null;
let resolvedBunBinary = null;
let resolvedGitBinary = null;
let useWslForOpencode = false;
let resolvedWslBinary = null;
let resolvedWslOpencodePath = null;
let resolvedWslDistro = null;
const openCodeEnvState = {};
Object.defineProperties(openCodeEnvState, {
cachedLoginShellEnvSnapshot: { get: () => cachedLoginShellEnvSnapshot, set: (value) => { cachedLoginShellEnvSnapshot = value; } },
resolvedOpencodeBinary: { get: () => resolvedOpencodeBinary, set: (value) => { resolvedOpencodeBinary = value; } },
resolvedOpencodeBinarySource: { get: () => resolvedOpencodeBinarySource, set: (value) => { resolvedOpencodeBinarySource = value; } },
resolvedNodeBinary: { get: () => resolvedNodeBinary, set: (value) => { resolvedNodeBinary = value; } },
resolvedBunBinary: { get: () => resolvedBunBinary, set: (value) => { resolvedBunBinary = value; } },
resolvedGitBinary: { get: () => resolvedGitBinary, set: (value) => { resolvedGitBinary = value; } },
useWslForOpencode: { get: () => useWslForOpencode, set: (value) => { useWslForOpencode = value; } },
resolvedWslBinary: { get: () => resolvedWslBinary, set: (value) => { resolvedWslBinary = value; } },
resolvedWslOpencodePath: { get: () => resolvedWslOpencodePath, set: (value) => { resolvedWslOpencodePath = value; } },
resolvedWslDistro: { get: () => resolvedWslDistro, set: (value) => { resolvedWslDistro = value; } },
});
const openCodeEnvRuntime = createOpenCodeEnvRuntime({
state: openCodeEnvState,
normalizeDirectoryPath,
readSettingsFromDiskMigrated,
ENV_CONFIGURED_OPENCODE_WSL_DISTRO,
});
const applyLoginShellEnvSnapshot = (...args) => openCodeEnvRuntime.applyLoginShellEnvSnapshot(...args);
const getLoginShellEnvSnapshot = (...args) => openCodeEnvRuntime.getLoginShellEnvSnapshot(...args);
const ensureOpencodeCliEnv = (...args) => openCodeEnvRuntime.ensureOpencodeCliEnv(...args);
const applyOpencodeBinaryFromSettings = (...args) => openCodeEnvRuntime.applyOpencodeBinaryFromSettings(...args);
const resolveOpencodeCliPath = (...args) => openCodeEnvRuntime.resolveOpencodeCliPath(...args);
const isExecutable = (...args) => openCodeEnvRuntime.isExecutable(...args);
const searchPathFor = (...args) => openCodeEnvRuntime.searchPathFor(...args);
const resolveGitBinaryForSpawn = (...args) => openCodeEnvRuntime.resolveGitBinaryForSpawn(...args);
const resolveWslExecutablePath = (...args) => openCodeEnvRuntime.resolveWslExecutablePath(...args);
const buildWslExecArgs = (...args) => openCodeEnvRuntime.buildWslExecArgs(...args);
const resolveManagedOpenCodeLaunchSpec = (...args) => openCodeEnvRuntime.resolveManagedOpenCodeLaunchSpec(...args);
const clearResolvedOpenCodeBinary = (...args) => openCodeEnvRuntime.clearResolvedOpenCodeBinary(...args);
const openCodeResolutionRuntime = createOpenCodeResolutionRuntime({
path,
resolveOpencodeCliPath,
applyOpencodeBinaryFromSettings,
ensureOpencodeCliEnv,
resolveManagedOpenCodeLaunchSpec,
getResolvedState: () => ({
resolvedOpencodeBinary,
resolvedOpencodeBinarySource,
useWslForOpencode,
resolvedWslBinary,
resolvedWslOpencodePath,
resolvedWslDistro,
resolvedNodeBinary,
resolvedBunBinary,
}),
setResolvedOpencodeBinarySource: (value) => {
resolvedOpencodeBinarySource = value;
},
});
const getOpenCodeResolutionSnapshot = (...args) =>
openCodeResolutionRuntime.getOpenCodeResolutionSnapshot(...args);
applyLoginShellEnvSnapshot();
notificationTemplateRuntime = createNotificationTemplateRuntime({
readSettingsFromDisk,
persistSettings,
buildOpenCodeUrl,
getOpenCodeAuthHeaders,
resolveGitBinaryForSpawn,
});
const notificationTriggerRuntime = createNotificationTriggerRuntime({
readSettingsFromDisk,
prepareNotificationLastMessage,
summarizeText,
resolveZenModel,
buildTemplateVariables,
extractLastMessageText,
fetchLastAssistantMessageText,
resolveNotificationTemplate,
shouldApplyResolvedTemplateMessage,
emitDesktopNotification,
broadcastUiNotification,
sendPushToAllUiSessions,
buildOpenCodeUrl,
getOpenCodeAuthHeaders,
});
const maybeSendPushForTrigger = (...args) => notificationTriggerRuntime.maybeSendPushForTrigger(...args);
const openCodeWatcherRuntime = createOpenCodeWatcherRuntime({
waitForOpenCodePort: (...args) => waitForOpenCodePort(...args),
buildOpenCodeUrl,
getOpenCodeAuthHeaders,
parseSseDataPayload: (...args) => parseSseDataPayload(...args),
onPayload: (payload) => {
maybeCacheSessionInfoFromEvent(payload);
void maybeSendPushForTrigger(payload);
sessionRuntime.processOpenCodeSsePayload(payload);
},
});
const serverUtilsRuntime = createServerUtilsRuntime({
fs,
os,
path,
process,
openCodeReadyGraceMs: OPEN_CODE_READY_GRACE_MS,
longRequestTimeoutMs: LONG_REQUEST_TIMEOUT_MS,
getRuntime: () => ({
openCodePort,
openCodeBaseUrl,
openCodeNotReadySince,
isOpenCodeReady,
isRestartingOpenCode,
}),
getOpenCodeAuthHeaders,
buildOpenCodeUrl,
ensureOpenCodeApiPrefix,
getUiNotificationClients: () => uiNotificationClients,
getOpenCodePort: () => openCodePort,
setOpenCodePortState: (value) => {
openCodePort = value;
},
syncToHmrState,
markOpenCodeNotReady: () => {
isOpenCodeReady = false;
},
setOpenCodeNotReadySince: (value) => {
openCodeNotReadySince = value;
},
clearLastOpenCodeError: () => {
lastOpenCodeError = null;
},
getLoginShellPath: () => {
const snapshot = getLoginShellEnvSnapshot();
if (!snapshot || typeof snapshot.PATH !== 'string' || snapshot.PATH.length === 0) {
return null;
}
return snapshot.PATH;
},
});
const setOpenCodePort = (...args) => serverUtilsRuntime.setOpenCodePort(...args);
const waitForOpenCodePort = (...args) => serverUtilsRuntime.waitForOpenCodePort(...args);
const buildAugmentedPath = (...args) => serverUtilsRuntime.buildAugmentedPath(...args);
const parseSseDataPayload = (...args) => serverUtilsRuntime.parseSseDataPayload(...args);
const staticRoutesRuntime = createStaticRoutesRuntime({
fs,
path,
process,
__dirname,
express,
resolveProjectDirectory,
buildOpenCodeUrl,
getOpenCodeAuthHeaders,
readSettingsFromDiskMigrated,
normalizePwaAppName,
});
const featureRoutesRuntime = createFeatureRoutesRuntime({
clientReloadDelayMs: CLIENT_RELOAD_DELAY_MS,
});
const bootstrapRuntime = createBootstrapRuntime({
createUiAuth,
registerServerStatusRoutes,
registerCommonRequestMiddleware,
registerAuthAndAccessRoutes,
registerTtsRoutes,
registerNotificationRoutes,
registerOpenChamberRoutes,
express,
});
const tunnelWiringRuntime = createTunnelWiringRuntime({
crypto,
URL,
tunnelProviderRegistry,
tunnelAuthController,
readSettingsFromDiskMigrated,
readManagedRemoteTunnelConfigFromDisk,
normalizeTunnelProvider,
normalizeTunnelMode,
normalizeOptionalPath,
normalizeManagedRemoteTunnelHostname,
normalizeTunnelBootstrapTtlMs,
normalizeTunnelSessionTtlMs,
isSupportedTunnelMode,
upsertManagedRemoteTunnelToken,
resolveManagedRemoteTunnelToken,
TUNNEL_MODE_QUICK,
TUNNEL_MODE_MANAGED_LOCAL,
TUNNEL_MODE_MANAGED_REMOTE,
TUNNEL_PROVIDER_CLOUDFLARE,
TunnelServiceError,
getActiveTunnelController: () => activeTunnelController,
setActiveTunnelController: (value) => {
activeTunnelController = value;
},
getRuntimeManagedRemoteTunnelHostname: () => runtimeManagedRemoteTunnelHostname,
setRuntimeManagedRemoteTunnelHostname: (value) => {
runtimeManagedRemoteTunnelHostname = value;
},
getRuntimeManagedRemoteTunnelToken: () => runtimeManagedRemoteTunnelToken,
setRuntimeManagedRemoteTunnelToken: (value) => {
runtimeManagedRemoteTunnelToken = value;
},
});
const startupPipelineRuntime = createStartupPipelineRuntime({
createTerminalRuntime,
createServerStartupRuntime,
});
const openCodeLifecycleState = {};
Object.defineProperties(openCodeLifecycleState, {
openCodeProcess: { get: () => openCodeProcess, set: (value) => { openCodeProcess = value; } },
openCodePort: { get: () => openCodePort, set: (value) => { openCodePort = value; } },
openCodeBaseUrl: { get: () => openCodeBaseUrl, set: (value) => { openCodeBaseUrl = value; } },
openCodeWorkingDirectory: { get: () => openCodeWorkingDirectory, set: (value) => { openCodeWorkingDirectory = value; } },
currentRestartPromise: { get: () => currentRestartPromise, set: (value) => { currentRestartPromise = value; } },
isRestartingOpenCode: { get: () => isRestartingOpenCode, set: (value) => { isRestartingOpenCode = value; } },
openCodeApiPrefix: { get: () => openCodeApiPrefix, set: (value) => { openCodeApiPrefix = value; } },
openCodeApiPrefixDetected: { get: () => openCodeApiPrefixDetected, set: (value) => { openCodeApiPrefixDetected = value; } },
openCodeApiDetectionTimer: { get: () => openCodeApiDetectionTimer, set: (value) => { openCodeApiDetectionTimer = value; } },
lastOpenCodeError: { get: () => lastOpenCodeError, set: (value) => { lastOpenCodeError = value; } },
isOpenCodeReady: { get: () => isOpenCodeReady, set: (value) => { isOpenCodeReady = value; } },
openCodeNotReadySince: { get: () => openCodeNotReadySince, set: (value) => { openCodeNotReadySince = value; } },
isExternalOpenCode: { get: () => isExternalOpenCode, set: (value) => { isExternalOpenCode = value; } },
isShuttingDown: { get: () => isShuttingDown, set: (value) => { isShuttingDown = value; } },
healthCheckInterval: { get: () => healthCheckInterval, set: (value) => { healthCheckInterval = value; } },
expressApp: { get: () => expressApp, set: (value) => { expressApp = value; } },
useWslForOpencode: { get: () => useWslForOpencode, set: (value) => { useWslForOpencode = value; } },
resolvedWslBinary: { get: () => resolvedWslBinary, set: (value) => { resolvedWslBinary = value; } },
resolvedWslOpencodePath: { get: () => resolvedWslOpencodePath, set: (value) => { resolvedWslOpencodePath = value; } },
resolvedWslDistro: { get: () => resolvedWslDistro, set: (value) => { resolvedWslDistro = value; } },
});
const openCodeLifecycleRuntime = createOpenCodeLifecycleRuntime({
state: openCodeLifecycleState,
env: {
ENV_CONFIGURED_OPENCODE_PORT,
ENV_CONFIGURED_OPENCODE_HOST,
ENV_EFFECTIVE_PORT,
ENV_CONFIGURED_OPENCODE_HOSTNAME,
ENV_SKIP_OPENCODE_START,
},
syncToHmrState,
syncFromHmrState,
getOpenCodeAuthHeaders,
buildOpenCodeUrl,
waitForReady,
normalizeApiPrefix,
applyOpencodeBinaryFromSettings,
ensureOpencodeCliEnv,
ensureLocalOpenCodeServerPassword,
buildWslExecArgs,
resolveWslExecutablePath,
resolveManagedOpenCodeLaunchSpec,
setOpenCodePort,
setDetectedOpenCodeApiPrefix,
setupProxy: (...args) => setupProxy(...args),
ensureOpenCodeApiPrefix,
clearResolvedOpenCodeBinary,
});
const restartOpenCode = (...args) => openCodeLifecycleRuntime.restartOpenCode(...args);
const waitForOpenCodeReady = (...args) => openCodeLifecycleRuntime.waitForOpenCodeReady(...args);
const waitForAgentPresence = (...args) => openCodeLifecycleRuntime.waitForAgentPresence(...args);
const refreshOpenCodeAfterConfigChange = (...args) => openCodeLifecycleRuntime.refreshOpenCodeAfterConfigChange(...args);
const startHealthMonitoring = () => openCodeLifecycleRuntime.startHealthMonitoring(HEALTH_CHECK_INTERVAL);
const ensureGlobalWatcherStarted = async () => {
if (globalWatcherStartPromise) {
return globalWatcherStartPromise;
}
globalWatcherStartPromise = openCodeWatcherRuntime.start().catch((error) => {
globalWatcherStartPromise = null;
throw error;
});
return globalWatcherStartPromise;
};
const bootstrapOpenCodeAtStartup = async (...args) => {
await openCodeLifecycleRuntime.bootstrapOpenCodeAtStartup(...args);
scheduleOpenCodeApiDetection();
if (openCodeLifecycleState.openCodeProcess && !openCodeLifecycleState.isExternalOpenCode) {
startHealthMonitoring();
}
if (ENV_DESKTOP_NOTIFY) {
void ensureGlobalWatcherStarted().catch((error) => {
console.warn(`Global event watcher startup failed: ${error?.message || error}`);
});
}
};
const killProcessOnPort = (...args) => openCodeLifecycleRuntime.killProcessOnPort(...args);
const waitForPortRelease = (...args) => openCodeLifecycleRuntime.waitForPortRelease(...args);
const fetchAgentsSnapshot = (...args) => serverUtilsRuntime.fetchAgentsSnapshot(...args);
const fetchProvidersSnapshot = (...args) => serverUtilsRuntime.fetchProvidersSnapshot(...args);
const fetchModelsSnapshot = (...args) => serverUtilsRuntime.fetchModelsSnapshot(...args);
const setupProxy = (...args) => serverUtilsRuntime.setupProxy(...args);
const gracefulShutdownRuntime = createGracefulShutdownRuntime({
process,
shutdownTimeoutMs: SHUTDOWN_TIMEOUT,
getExitOnShutdown: () => exitOnShutdown,
getIsShuttingDown: () => isShuttingDown,
setIsShuttingDown: (value) => {
isShuttingDown = value;
},
syncToHmrState,
openCodeWatcherRuntime,
sessionRuntime,
getHealthCheckInterval: () => healthCheckInterval,
clearHealthCheckInterval: (value) => clearInterval(value),
getTerminalRuntime: () => terminalRuntime,
setTerminalRuntime: (value) => {
terminalRuntime = value;
},
shouldSkipOpenCodeStop: () => ENV_SKIP_OPENCODE_START || isExternalOpenCode,
getOpenCodePort: () => openCodePort,
getOpenCodeProcess: () => openCodeProcess,
setOpenCodeProcess: (value) => {
openCodeProcess = value;
},
killProcessOnPort,
waitForPortRelease,
getServer: () => server,
getUiAuthController: () => uiAuthController,
setUiAuthController: (value) => {
uiAuthController = value;
},
getActiveTunnelController: () => activeTunnelController,
setActiveTunnelController: (value) => {
activeTunnelController = value;
},
tunnelAuthController,
});
const gracefulShutdown = (...args) => gracefulShutdownRuntime.gracefulShutdown(...args);
async function main(options = {}) {
const port = Number.isFinite(options.port) && options.port >= 0 ? Math.trunc(options.port) : DEFAULT_PORT;
const host = typeof options.host === 'string' && options.host.length > 0 ? options.host : undefined;
const tryCfTunnel = options.tryCfTunnel === true;
const shouldUseCanonicalTunnelConfig = typeof options.tunnelMode === 'string'
|| typeof options.tunnelProvider === 'string'
|| options.tunnelConfigPath === null
|| typeof options.tunnelConfigPath === 'string'
|| typeof options.tunnelToken === 'string'
|| typeof options.tunnelHostname === 'string';
const startupTunnelRequest = shouldUseCanonicalTunnelConfig
? normalizeTunnelStartRequest({
provider: normalizeTunnelProvider(options.tunnelProvider),
mode: options.tunnelMode,
configPath: normalizeOptionalPath(options.tunnelConfigPath),
token: typeof options.tunnelToken === 'string' ? options.tunnelToken.trim() : '',
hostname: normalizeManagedRemoteTunnelHostname(options.tunnelHostname),
})
: (tryCfTunnel
? {
provider: TUNNEL_PROVIDER_CLOUDFLARE,
mode: TUNNEL_MODE_QUICK,
configPath: undefined,
token: '',
hostname: undefined,
}
: null);
const attachSignals = options.attachSignals !== false;
const onTunnelReady = typeof options.onTunnelReady === 'function' ? options.onTunnelReady : null;
if (typeof options.exitOnShutdown === 'boolean') {
exitOnShutdown = options.exitOnShutdown;
}
console.log(`Starting OpenChamber on port ${port === 0 ? 'auto' : port}`);
const sayTTSCapability = await detectSayTtsCapability(process);
// Startup model validation is best-effort and runs in background.
void validateZenModelAtStartup();
const app = express();
const serverStartedAt = new Date().toISOString();
app.set('trust proxy', true);
expressApp = app;
server = http.createServer(app);
const uiPassword = typeof options.uiPassword === 'string' ? options.uiPassword : null;
const bootstrapResult = bootstrapRuntime.setupBaseRoutes(app, {
process,
openchamberVersion: OPENCHAMBER_VERSION,
runtimeName: process.env.OPENCHAMBER_RUNTIME || 'web',
serverStartedAt,
gracefulShutdown,
getHealthSnapshot: () => {
const launchSpec = resolvedOpencodeBinary && !useWslForOpencode
? resolveManagedOpenCodeLaunchSpec(resolvedOpencodeBinary)
: null;
return {
openCodePort,
openCodeRunning: Boolean(openCodePort && isOpenCodeReady && !isRestartingOpenCode),
openCodeSecureConnection: isOpenCodeConnectionSecure(),
openCodeAuthSource: openCodeAuthSource || null,
openCodeApiPrefix: '',
openCodeApiPrefixDetected: true,
isOpenCodeReady,
lastOpenCodeError,
opencodeBinaryResolved: resolvedOpencodeBinary || null,
opencodeBinarySource: resolvedOpencodeBinarySource || null,
opencodeLaunchBinary: launchSpec?.binary || null,
opencodeLaunchArgs: launchSpec?.args || [],
opencodeLaunchWrapperType: launchSpec?.wrapperType || null,
opencodeViaWsl: useWslForOpencode,
opencodeWslBinary: resolvedWslBinary || null,
opencodeWslPath: resolvedWslOpencodePath || null,
opencodeWslDistro: resolvedWslDistro || null,
nodeBinaryResolved: resolvedNodeBinary || null,
bunBinaryResolved: resolvedBunBinary || null,
desktopNotifyEnabled: ENV_DESKTOP_NOTIFY,
planModeExperimentalEnabled: PLAN_MODE_EXPERIMENT_ENABLED,
};
},
uiPassword,
tunnelAuthController,
readSettingsFromDiskMigrated,
normalizeTunnelSessionTtlMs,
resolveZenModel,
sayTTSCapability,
ensurePushInitialized,
ensureGlobalWatcherStarted,
getOrCreateVapidKeys,
getUiSessionTokenFromRequest,
writeSettingsToDisk,
addOrUpdatePushSubscription,
removePushSubscription,
updateUiVisibility,
isUiVisible,
getUiNotificationClients: () => uiNotificationClients,
writeSseEvent,
sessionRuntime,
setPushInitialized,
fs,
os,
path,
server,
__dirname,
openchamberDataDir: OPENCHAMBER_DATA_DIR,
modelsDevApiUrl: MODELS_DEV_API_URL,
modelsMetadataCacheTtl: MODELS_METADATA_CACHE_TTL,
fetchFreeZenModels,
getCachedZenModels,
});
uiAuthController = bootstrapResult.uiAuthController;
const tunnelRuntimeContext = tunnelWiringRuntime.initialize(app, port);
const { tunnelService, startTunnelWithNormalizedRequest } = tunnelRuntimeContext;
await featureRoutesRuntime.registerRoutes(app, {
crypto,
fs,
os,
path,
fsPromises,
spawn,
resolveGitBinaryForSpawn,
createFsSearchRuntime: createFsSearchRuntimeFactory,
openchamberDataDir: OPENCHAMBER_DATA_DIR,
openchamberUserConfigRoot: OPENCHAMBER_USER_CONFIG_ROOT,
normalizeDirectoryPath,
resolveProjectDirectory,
resolveOptionalProjectDirectory,
validateDirectoryPath,
readCustomThemesFromDisk,
refreshOpenCodeAfterConfigChange,
getOpenCodeResolutionSnapshot,
formatSettingsResponse,
readSettingsFromDisk,
readSettingsFromDiskMigrated,
persistSettings,
sanitizeProjects,
sanitizeSkillCatalogs,
isUnsafeSkillRelativePath,
buildOpenCodeUrl,
getOpenCodeAuthHeaders,
getOpenCodePort: () => openCodePort,
buildAugmentedPath,
});
const startupPipelineResult = await startupPipelineRuntime.run({
app,
server,
express,
fs,
path,
uiAuthController,
buildAugmentedPath,
searchPathFor,
isExecutable,
isRequestOriginAllowed,
rejectWebSocketUpgrade,
terminalHeartbeatIntervalMs: TERMINAL_INPUT_WS_HEARTBEAT_INTERVAL_MS,
terminalRebindWindowMs: TERMINAL_INPUT_WS_REBIND_WINDOW_MS,
terminalMaxRebindsPerWindow: TERMINAL_INPUT_WS_MAX_REBINDS_PER_WINDOW,
setupProxy,
scheduleOpenCodeApiDetection,
bootstrapOpenCodeAtStartup,
staticRoutesRuntime,
process,
crypto,
normalizeTunnelBootstrapTtlMs,
readSettingsFromDiskMigrated,
tunnelAuthController,
startTunnelWithNormalizedRequest,
gracefulShutdown,
getSignalsAttached: () => signalsAttached,
setSignalsAttached: (value) => {
signalsAttached = value;
},
syncToHmrState,
TUNNEL_MODE_QUICK,
TUNNEL_MODE_MANAGED_LOCAL,
TUNNEL_MODE_MANAGED_REMOTE,
host,
port,
startupTunnelRequest,
onTunnelReady,
tunnelRuntimeContext,
attachSignals,
});
terminalRuntime = startupPipelineResult.terminalRuntime;
return {
expressApp: app,
httpServer: server,
getPort: () => tunnelRuntimeContext.getActivePort(),
getOpenCodePort: () => openCodePort,
getTunnelUrl: () => tunnelService.getPublicUrl(),
isReady: () => isOpenCodeReady,
restartOpenCode: () => restartOpenCode(),
stop: (shutdownOptions = {}) =>
gracefulShutdown({ exitProcess: shutdownOptions.exitProcess ?? false })
};
}
runCliEntryIfMain({
process,
currentFilename: __filename,
parseServeCliOptions,
defaultPort: DEFAULT_PORT,
cloudflareProvider: TUNNEL_PROVIDER_CLOUDFLARE,
managedLocalMode: TUNNEL_MODE_MANAGED_LOCAL,
setExitOnShutdown: (value) => {
exitOnShutdown = value;
},
startServer: main,
});
export {
gracefulShutdown,
setupProxy,
restartOpenCode,
main as startWebUiServer,
parseServeCliOptions as parseArgs,
};