@openchamber/web 1.11.6 → 1.11.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +6 -0
- package/bin/cli.js +443 -2
- package/dist/assets/{MarkdownRendererImpl-COdbjw73.js → MarkdownRendererImpl-DaF15QNC.js} +3 -3
- package/dist/assets/{MultiRunWindow-BKSHxjMq.js → MultiRunWindow-Cl7wS_CB.js} +1 -1
- package/dist/assets/{OnboardingScreen-Chjg337p.js → OnboardingScreen-DTv6YJI1.js} +2 -2
- package/dist/assets/{SettingsWindow-C0lRRW8M.js → SettingsWindow-_c3TTL2z.js} +1 -1
- package/dist/assets/{TerminalView-Bvil3j1u.js → TerminalView-CuXkDROt.js} +3 -3
- package/dist/assets/es-CYoUf2D-.js +15 -0
- package/dist/assets/{index-B9LvUHdG.js → index-3WXrN3AX.js} +1 -1
- package/dist/assets/index-BREIbhcb.css +1 -0
- package/dist/assets/ko-2tM0fIna.js +15 -0
- package/dist/assets/main-BF3kWAJ9.js +239 -0
- package/dist/assets/{main-Blhx9Fp5.js → main-o8ZERrmU.js} +2 -2
- package/dist/assets/miniChat-BZQjpK23.js +2 -0
- package/dist/assets/{modelPrefsAutoSave-DRJSYigo.js → modelPrefsAutoSave-wwnbqBk7.js} +110 -108
- package/dist/assets/pl-Dq8uAotM.js +15 -0
- package/dist/assets/pt-BR-nh9s9DFT.js +15 -0
- package/dist/assets/{renderElectronMiniChatApp-BxZRI73j.js → renderElectronMiniChatApp-C-Ezew9P.js} +2 -2
- package/dist/assets/uk-BZtz0wUV.js +15 -0
- package/dist/assets/{vendor-.bun-Bum-iBXX.js → vendor-.bun-CV3tusA8.js} +1 -1
- package/dist/assets/zh-CN-j_nYMchE.js +15 -0
- package/dist/assets/zh-TW-B11UpkDJ.js +15 -0
- package/dist/index.html +11 -28
- package/dist/mini-chat.html +4 -4
- package/package.json +1 -1
- package/server/lib/fs/routes.js +5 -0
- package/server/lib/fs/routes.test.js +61 -1
- package/server/lib/git/DOCUMENTATION.md +1 -0
- package/server/lib/git/routes.js +82 -1
- package/server/lib/git/service.js +338 -19
- package/server/lib/git/service.test.js +414 -8
- package/server/lib/opencode/env-runtime.js +52 -4
- package/server/lib/opencode/env-runtime.test.js +82 -6
- package/server/lib/opencode/openchamber-routes.js +9 -7
- package/server/lib/opencode/settings-helpers.js +3 -0
- package/server/lib/opencode/settings-runtime.js +39 -1
- package/server/lib/opencode/settings-runtime.test.js +39 -0
- package/server/lib/skills-catalog/source.js +1 -1
- package/dist/assets/es-BZIAUghG.js +0 -15
- package/dist/assets/index-UcCH2KN9.css +0 -1
- package/dist/assets/ko-DU9l-zox.js +0 -15
- package/dist/assets/main-d2-dY4er.js +0 -232
- package/dist/assets/miniChat-CJ7-rZFl.js +0 -2
- package/dist/assets/pl-CdqzokG-.js +0 -15
- package/dist/assets/pt-BR-Bknbr_Y3.js +0 -15
- package/dist/assets/uk-Be4E8ZNO.js +0 -15
- package/dist/assets/zh-CN-qpPiaZMg.js +0 -15
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
export const registerOpenChamberRoutes = (app, dependencies) => {
|
|
2
2
|
const {
|
|
3
3
|
fs,
|
|
4
|
-
os,
|
|
5
4
|
path,
|
|
6
5
|
process,
|
|
7
6
|
server,
|
|
@@ -104,14 +103,15 @@ export const registerOpenChamberRoutes = (app, dependencies) => {
|
|
|
104
103
|
}
|
|
105
104
|
|
|
106
105
|
const currentPort = server.address()?.port || 3000;
|
|
107
|
-
const
|
|
108
|
-
const instanceFilePath = path.join(tmpDir, `openchamber-${currentPort}.json`);
|
|
106
|
+
const instanceFilePath = path.join(openchamberDataDir, 'run', `openchamber-${currentPort}.json`);
|
|
109
107
|
let storedOptions = { port: currentPort, daemon: true };
|
|
110
108
|
try {
|
|
111
109
|
const content = await fs.promises.readFile(instanceFilePath, 'utf8');
|
|
112
110
|
storedOptions = JSON.parse(content);
|
|
113
111
|
} catch {
|
|
114
112
|
}
|
|
113
|
+
const launchMode = storedOptions.launchMode === 'foreground' ? 'foreground' : 'daemon';
|
|
114
|
+
const isForegroundService = launchMode === 'foreground';
|
|
115
115
|
|
|
116
116
|
const isWindows = process.platform === 'win32';
|
|
117
117
|
const quotePosix = (value) => `'${String(value).replace(/'/g, "'\\''")}'`;
|
|
@@ -152,7 +152,7 @@ export const registerOpenChamberRoutes = (app, dependencies) => {
|
|
|
152
152
|
restartCmdFallback += ` --ui-password '${escapedPw}'`;
|
|
153
153
|
}
|
|
154
154
|
}
|
|
155
|
-
const restartCmd = `(${restartCmdPrimary}) || (${restartCmdFallback})`;
|
|
155
|
+
const restartCmd = isForegroundService ? '' : `(${restartCmdPrimary}) || (${restartCmdFallback})`;
|
|
156
156
|
const updateLogPath = path.join(openchamberDataDir, 'update-install.log');
|
|
157
157
|
const logPreamble = [
|
|
158
158
|
'',
|
|
@@ -165,8 +165,9 @@ export const registerOpenChamberRoutes = (app, dependencies) => {
|
|
|
165
165
|
`packagePath=${pmDetails.packagePath || 'unknown'}`,
|
|
166
166
|
`globalNodeModulesRoot=${pmDetails.globalNodeModulesRoot || 'unknown'}`,
|
|
167
167
|
`mode=${isContainer ? 'container' : 'restart'}`,
|
|
168
|
+
`launchMode=${launchMode}`,
|
|
168
169
|
`updateCommand=${updateCmd}`,
|
|
169
|
-
`restartCommand=${restartCmd}`,
|
|
170
|
+
`restartCommand=${restartCmd || 'service-manager'}`,
|
|
170
171
|
`logPath=${updateLogPath}`,
|
|
171
172
|
].join('\n');
|
|
172
173
|
|
|
@@ -176,6 +177,7 @@ export const registerOpenChamberRoutes = (app, dependencies) => {
|
|
|
176
177
|
version: updateInfo.version,
|
|
177
178
|
packageManager: pm,
|
|
178
179
|
autoRestart: true,
|
|
180
|
+
restartManager: isForegroundService ? 'service' : 'cli',
|
|
179
181
|
});
|
|
180
182
|
|
|
181
183
|
setTimeout(() => {
|
|
@@ -192,7 +194,7 @@ export const registerOpenChamberRoutes = (app, dependencies) => {
|
|
|
192
194
|
${updateCmd}
|
|
193
195
|
if %ERRORLEVEL% EQU 0 (
|
|
194
196
|
echo Update successful, restarting OpenChamber...
|
|
195
|
-
${restartCmd}
|
|
197
|
+
${restartCmd || 'echo Service manager will restart OpenChamber.'}
|
|
196
198
|
) else (
|
|
197
199
|
echo Update failed
|
|
198
200
|
exit /b 1
|
|
@@ -204,7 +206,7 @@ export const registerOpenChamberRoutes = (app, dependencies) => {
|
|
|
204
206
|
${updateCmd}
|
|
205
207
|
if [ $? -eq 0 ]; then
|
|
206
208
|
echo "Update successful, restarting OpenChamber..."
|
|
207
|
-
${restartCmd}
|
|
209
|
+
${restartCmd || 'echo "Service manager will restart OpenChamber."'}
|
|
208
210
|
else
|
|
209
211
|
echo "Update failed"
|
|
210
212
|
exit 1
|
|
@@ -225,6 +225,9 @@ export const createSettingsHelpers = (dependencies) => {
|
|
|
225
225
|
if (candidate.usageDisplayMode === 'usage' || candidate.usageDisplayMode === 'remaining') {
|
|
226
226
|
result.usageDisplayMode = candidate.usageDisplayMode;
|
|
227
227
|
}
|
|
228
|
+
if (typeof candidate.usageShowPredValues === 'boolean') {
|
|
229
|
+
result.usageShowPredValues = candidate.usageShowPredValues;
|
|
230
|
+
}
|
|
228
231
|
if (Array.isArray(candidate.usageDropdownProviders)) {
|
|
229
232
|
result.usageDropdownProviders = normalizeStringArray(candidate.usageDropdownProviders);
|
|
230
233
|
}
|
|
@@ -438,6 +438,44 @@ export const createSettingsRuntime = (deps) => {
|
|
|
438
438
|
}
|
|
439
439
|
};
|
|
440
440
|
|
|
441
|
+
const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
|
|
442
|
+
|
|
443
|
+
const isTransientWindowsReplaceError = (error) => {
|
|
444
|
+
if (process.platform !== 'win32' || !error || typeof error !== 'object') {
|
|
445
|
+
return false;
|
|
446
|
+
}
|
|
447
|
+
return error.code === 'EPERM' || error.code === 'EACCES' || error.code === 'EBUSY';
|
|
448
|
+
};
|
|
449
|
+
|
|
450
|
+
const replaceFile = async (tmp, target) => {
|
|
451
|
+
const maxAttempts = process.platform === 'win32' ? 6 : 1;
|
|
452
|
+
let lastError = null;
|
|
453
|
+
|
|
454
|
+
for (let attempt = 1; attempt <= maxAttempts; attempt += 1) {
|
|
455
|
+
try {
|
|
456
|
+
await fsPromises.rename(tmp, target);
|
|
457
|
+
return;
|
|
458
|
+
} catch (error) {
|
|
459
|
+
lastError = error;
|
|
460
|
+
if (!isTransientWindowsReplaceError(error) || attempt === maxAttempts) {
|
|
461
|
+
break;
|
|
462
|
+
}
|
|
463
|
+
await sleep(25 * attempt);
|
|
464
|
+
}
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
if (!isTransientWindowsReplaceError(lastError)) {
|
|
468
|
+
throw lastError;
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
// Windows can transiently reject atomic replacement when another process
|
|
472
|
+
// briefly opens the target file. Preserve atomic rename everywhere it works,
|
|
473
|
+
// but fall back to a direct replacement so settings persistence does not
|
|
474
|
+
// get permanently wedged on Windows desktop installs.
|
|
475
|
+
await fsPromises.copyFile(tmp, target);
|
|
476
|
+
await fsPromises.rm(tmp, { force: true });
|
|
477
|
+
};
|
|
478
|
+
|
|
441
479
|
const writeSettingsToDisk = async (settings) => {
|
|
442
480
|
try {
|
|
443
481
|
await fsPromises.mkdir(path.dirname(SETTINGS_FILE_PATH), { recursive: true });
|
|
@@ -447,7 +485,7 @@ export const createSettingsRuntime = (deps) => {
|
|
|
447
485
|
// read-modify-write wipe the settings file.
|
|
448
486
|
const tmp = `${SETTINGS_FILE_PATH}.tmp-${process.pid}-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
|
|
449
487
|
await fsPromises.writeFile(tmp, JSON.stringify(settings, null, 2), 'utf8');
|
|
450
|
-
await
|
|
488
|
+
await replaceFile(tmp, SETTINGS_FILE_PATH);
|
|
451
489
|
} catch (error) {
|
|
452
490
|
console.warn('Failed to write settings file:', error);
|
|
453
491
|
throw error;
|
|
@@ -82,4 +82,43 @@ describe('settings runtime', () => {
|
|
|
82
82
|
await cleanup();
|
|
83
83
|
}
|
|
84
84
|
});
|
|
85
|
+
|
|
86
|
+
it.skipIf(process.platform !== 'win32')('falls back when Windows blocks atomic settings replacement', async () => {
|
|
87
|
+
const tempRoot = await fsPromises.mkdtemp(path.join(os.tmpdir(), 'oc-settings-runtime-'));
|
|
88
|
+
const settingsFilePath = path.join(tempRoot, 'settings.json');
|
|
89
|
+
const wrappedFs = {
|
|
90
|
+
...fsPromises,
|
|
91
|
+
rename: async () => {
|
|
92
|
+
const error = new Error('operation not permitted');
|
|
93
|
+
error.code = 'EPERM';
|
|
94
|
+
throw error;
|
|
95
|
+
},
|
|
96
|
+
};
|
|
97
|
+
const runtime = createSettingsRuntime({
|
|
98
|
+
fsPromises: wrappedFs,
|
|
99
|
+
path,
|
|
100
|
+
crypto,
|
|
101
|
+
SETTINGS_FILE_PATH: settingsFilePath,
|
|
102
|
+
sanitizeProjects: (projects) => Array.isArray(projects) ? projects : [],
|
|
103
|
+
sanitizeSettingsUpdate: (settings) => settings,
|
|
104
|
+
mergePersistedSettings: (_current, changes) => changes,
|
|
105
|
+
normalizeSettingsPaths: (settings) => ({ settings, changed: false }),
|
|
106
|
+
normalizeStringArray: (values) => Array.isArray(values) ? values.filter((value) => typeof value === 'string') : [],
|
|
107
|
+
formatSettingsResponse: (settings) => settings,
|
|
108
|
+
resolveDirectoryCandidate: (value) => value,
|
|
109
|
+
normalizeManagedRemoteTunnelHostname: (value) => value,
|
|
110
|
+
normalizeManagedRemoteTunnelPresets: (value) => value,
|
|
111
|
+
normalizeManagedRemoteTunnelPresetTokens: (value) => value,
|
|
112
|
+
syncManagedRemoteTunnelConfigWithPresets: async () => {},
|
|
113
|
+
upsertManagedRemoteTunnelToken: async () => {},
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
try {
|
|
117
|
+
await runtime.writeSettingsToDisk({ theme: 'dark' });
|
|
118
|
+
|
|
119
|
+
await expect(fsPromises.readFile(settingsFilePath, 'utf8')).resolves.toBe(JSON.stringify({ theme: 'dark' }, null, 2));
|
|
120
|
+
} finally {
|
|
121
|
+
await fsPromises.rm(tempRoot, { recursive: true, force: true });
|
|
122
|
+
}
|
|
123
|
+
});
|
|
85
124
|
});
|
|
@@ -17,7 +17,7 @@ export function parseSkillRepoSource(input, options = {}) {
|
|
|
17
17
|
return { ok: false, error: { kind: 'invalidSource', message: 'Repository source is required' } };
|
|
18
18
|
}
|
|
19
19
|
const explicitSubpath = typeof options.subpath === 'string' && options.subpath.trim() ? options.subpath.trim() : null;
|
|
20
|
-
|
|
20
|
+
|
|
21
21
|
const urlFormat = raw.startsWith('https://') ? 'https' : raw.startsWith('git@') ? 'ssh' : 'shorthand';
|
|
22
22
|
const gitHost = urlFormat === 'https' ? raw.split('/')[2] : urlFormat === 'ssh' ? raw.split('@')[1].split(':')[0] : null;
|
|
23
23
|
|