merge: resolve v1.23.0 upstream conflicts, preserve custom git provider config

Resolved conflicts in 8 files by taking upstream refactored code:
- desktop.ts: re-export DesktopSettings from registry
- openchamberConfig.ts: simplified project setup client
- persistence.ts: registry-derived settings, add git provider hydration
- search.ts: upstream search entries + git provider entries
- useConfigStore.ts: loadDesktopSettings() path
- settings-helpers.js: add gitProviderId/gitModelId/gitProviders sanitization
- DOCUMENTATION.md: upstream walkthrough docs
- vite.config.ts: upstream SW glob patterns

Custom fork additions preserved:
- gitProviderId, gitModelId, gitProviders fields in settings registry
- Git provider domain store hydration in persistence.ts
- Git provider search entries in search.ts
- Git provider sanitization in settings-helpers.js
This commit is contained in:
2026-09-10 10:11:50 +00:00
559 changed files with 36139 additions and 9087 deletions
+45 -9
View File
@@ -97,6 +97,7 @@ import { createSessionSteerRuntime, registerSessionSteerRoutes } from './lib/ses
import { createPlanGateRuntime, registerPlanGateRoutes } from './lib/plan-gate/runtime.js';
import { createGracefulShutdownRuntime } from './lib/opencode/shutdown-runtime.js';
import { createProjectConfigRuntime } from './lib/projects/project-config.js';
import { migrateLegacyUserDirs } from './lib/data-dir-migration.js';
import { createProjectContextRuntime } from './lib/project-context/runtime.js';
import { createAgentMemoryRuntime } from './lib/agent-memory/runtime.js';
import { createAgentMemoryActions } from './lib/agent-memory/actions.js';
@@ -120,6 +121,10 @@ import { createScheduledTaskService } from './lib/scheduled-tasks/service.js';
import { createOpenChamberControlService } from './lib/openchamber-control/service.js';
import { OpenChamberControlError } from './lib/openchamber-control/error.js';
import webPush from 'web-push';
import { applyConnectAttemptTimeout } from './lib/network-defaults.js';
// Background CLI launches enter here in a fresh process, without CLI defaults.
applyConnectAttemptTimeout();
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
@@ -263,14 +268,20 @@ const 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');
// Every OpenChamber-owned file and folder hangs off one root: the default
// `~/.config/openchamber`, or `OPENCHAMBER_DATA_DIR` when set. The user
// folders (`projects/`, `themes/`, `speech-models/`) are copied into a custom
// root once at startup (`migrateLegacyUserDirs`), because they used to ignore
// the variable.
const OPENCHAMBER_DEFAULT_CONFIG_ROOT = path.join(os.homedir(), '.config', 'openchamber');
const OPENCHAMBER_USER_CONFIG_ROOT = process.env.OPENCHAMBER_DATA_DIR
? path.resolve(process.env.OPENCHAMBER_DATA_DIR)
: OPENCHAMBER_DEFAULT_CONFIG_ROOT;
const OPENCHAMBER_USER_THEMES_DIR = path.join(OPENCHAMBER_USER_CONFIG_ROOT, 'themes');
const OPENCHAMBER_PROJECTS_CONFIG_DIR = path.join(OPENCHAMBER_USER_CONFIG_ROOT, 'projects');
// OPENCHAMBER_CHATS_DIR relocates managed chat worktrees — needed when the
@@ -309,9 +320,7 @@ const maybeCacheSessionInfoFromEvent = (...args) => notificationTemplateRuntime.
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 OPENCHAMBER_DATA_DIR = OPENCHAMBER_USER_CONFIG_ROOT;
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 APNS_TOKENS_FILE_PATH = path.join(OPENCHAMBER_DATA_DIR, 'apns-tokens.json');
@@ -349,7 +358,6 @@ const settingsHelpers = createSettingsHelpers({
normalizeManagedRemoteTunnelHostname,
normalizeManagedRemoteTunnelPresets,
normalizeManagedRemoteTunnelPresetTokens,
sanitizeTypographySizesPartial,
normalizeStringArray,
sanitizeModelRefs,
sanitizeSkillCatalogs,
@@ -488,6 +496,17 @@ const getUpstreamStallTimeoutMs = () => (
: DEFAULT_UPSTREAM_STALL_TIMEOUT_MS
);
const movedUserDirs = await migrateLegacyUserDirs({
fsPromises,
path,
dataDir: OPENCHAMBER_USER_CONFIG_ROOT,
legacyRoot: OPENCHAMBER_DEFAULT_CONFIG_ROOT,
warn: (message) => console.warn(`[data-dir] ${message}`),
});
if (movedUserDirs.length > 0) {
console.log(`[data-dir] Copied ${movedUserDirs.join(', ')} into ${OPENCHAMBER_USER_CONFIG_ROOT}`);
}
const projectConfigRuntime = createProjectConfigRuntime({
fsPromises,
path,
@@ -498,6 +517,7 @@ const projectContextRuntime = createProjectContextRuntime({
fsPromises,
path,
projectsDirPath: OPENCHAMBER_PROJECTS_CONFIG_DIR,
resolveSharedPlansDir: (projectId) => projectConfigRuntime.resolveSharedPlansDir(projectId),
});
const agentMemoryRuntime = createAgentMemoryRuntime({
@@ -897,7 +917,13 @@ const messageQueueRuntime = createMessageQueueRuntime({
buildOpenCodeUrl,
getOpenCodeAuthHeaders,
sessionKnowledgeRuntime,
broadcastGlobalUiEvent,
// OpenCode's /global/event SSE proxy cannot carry OpenChamber-owned events.
// Use the shared control stream for SSE clients and the existing WS fan-out.
broadcastGlobalUiEvent: createGlobalUiEventBroadcaster({
sseClients: uiOpenChamberEventClients,
wsClients: uiNotificationWsClients,
writeSseEvent,
}),
onPromptSent: (sessionId) => sessionRuntime.markUserMessageSent(sessionId),
dataDir: OPENCHAMBER_DATA_DIR,
});
@@ -1669,6 +1695,12 @@ async function main(options = {}) {
const getDesktopRuntimeConfig = typeof options.getDesktopRuntimeConfig === 'function'
? options.getDesktopRuntimeConfig
: null;
const desktopUpdater = options.desktopUpdater
&& typeof options.desktopUpdater.check === 'function'
&& typeof options.desktopUpdater.install === 'function'
&& typeof options.desktopUpdater.restart === 'function'
? options.desktopUpdater
: null;
console.log(`Starting OpenChamber on port ${port === 0 ? 'auto' : port}`);
@@ -1703,7 +1735,10 @@ async function main(options = {}) {
res.setHeader('Access-Control-Allow-Origin', origin);
res.setHeader('Access-Control-Allow-Credentials', 'true');
res.setHeader('Access-Control-Allow-Methods', 'GET,POST,PUT,PATCH,DELETE,OPTIONS');
res.setHeader('Access-Control-Allow-Headers', 'Content-Type,Authorization,Accept,X-Requested-With,Cache-Control,X-OpenCode-Directory,X-OpenCode-Directory-Encoding,Ngrok-Skip-Browser-Warning');
// The packaged desktop UI (openchamber-ui://) and the dev UI sit on a
// different origin, so every custom request header must be listed here or
// the browser refuses the request at preflight, before it reaches a route.
res.setHeader('Access-Control-Allow-Headers', 'Content-Type,Authorization,Accept,X-Requested-With,Cache-Control,X-OpenCode-Directory,X-OpenCode-Directory-Encoding,Ngrok-Skip-Browser-Warning,X-OpenChamber-Surface');
res.setHeader('Access-Control-Expose-Headers', 'x-next-cursor');
res.setHeader('Vary', 'Origin');
if (req.method === 'OPTIONS') {
@@ -1844,6 +1879,7 @@ async function main(options = {}) {
getCachedZenModels,
setAutoAcceptSession,
agentToolRuntime,
desktopUpdater,
});
uiAuthController = bootstrapResult.uiAuthController;
realtimeProxyRuntime = attachRealtimeProxy({