@vantran-se/openchamber-web 1.22.1 → 1.23.0
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 +2 -3
- package/bin/cli.js +11 -1
- package/bin/cli.test.js +92 -0
- package/bin/lib/cli-startup.js +49 -0
- package/bin/lib/commands-startup.js +41 -6
- package/dist/assets/{CodeMirrorCommentWidgets-C53Q7pbX.js → CodeMirrorCommentWidgets-DFg2wnpo.js} +1 -1
- package/dist/assets/DiffView-3Dn4QHbu.js +4 -0
- package/dist/assets/DirtyBranchSwitchDialog-CwOerqVi.js +1 -0
- package/dist/assets/FilesView-BdTOpew9.js +17 -0
- package/dist/assets/GitView-Dqf9vHJh.js +6 -0
- package/dist/assets/{JsonTreeView-Bl0lp9Rs.js → JsonTreeView-BKdsjg5B.js} +1 -1
- package/dist/assets/LinearIssuesView-CmbeL7I6.js +1 -0
- package/dist/assets/MarkdownImageGallery-_5gEVr0X.js +1 -0
- package/dist/assets/MarkdownRendererImpl-wVZlx4dd.js +12 -0
- package/dist/assets/OnboardingScreen-DdP4_nuc.js +1 -0
- package/dist/assets/{PatchDiff-D6NXAfJk.js → PatchDiff-am3SAss2.js} +1 -1
- package/dist/assets/PierreDiffViewer-DCsydlKs.js +161 -0
- package/dist/assets/PlanView-BoJxdRZe.js +5 -0
- package/dist/assets/SettingsView-CcM8pF5O.js +28 -0
- package/dist/assets/{SettingsWindow-BiWDBRvb.js → SettingsWindow-DVTdDSol.js} +1 -1
- package/dist/assets/SymbolsNerdFontMono-Regular-aK5vsLov.woff2 +0 -0
- package/dist/assets/{ToolOutputDialog-BQexMQIm.js → ToolOutputDialog-BgfTbDZ4.js} +6 -6
- package/dist/assets/ToolPartDiffPreview-C7WLk5i9.js +8 -0
- package/dist/assets/{VirtualizedFileDiff-Dpz2ra8h.js → VirtualizedFileDiff-DcdgkH-x.js} +1 -1
- package/dist/assets/WalkthroughView-BEVUDxuw.js +4 -0
- package/dist/assets/{computeVirtualFileMetrics-DtyozJdt.js → computeVirtualFileMetrics-C8byMnRh.js} +2 -2
- package/dist/assets/contextPanelEmbeddedChat-CirXEPJq.js +1 -0
- package/dist/assets/de-C8jV7J9y.js +10 -0
- package/dist/assets/es-BI6J1M5R.js +16 -0
- package/dist/assets/fr-D6oDFt7E.js +14 -0
- package/dist/assets/ghostty-vt-DdA0Zryv.wasm +0 -0
- package/dist/assets/{index-DmhujXbT.js → index-BB7ryz8y.js} +1 -1
- package/dist/assets/index-BqhiCmYS.js +39 -0
- package/dist/assets/index-FqSOBz0J.css +1 -0
- package/dist/assets/{isDiffPlainText-XyulMHxx.js → isDiffPlainText-45ujMPGb.js} +1 -1
- package/dist/assets/{isFilePlainText-B19Ddps1.js → isFilePlainText-Cc4KFSYi.js} +1 -1
- package/dist/assets/ja-C_4i5EQu.js +16 -0
- package/dist/assets/ko-BFbOTpbq.js +16 -0
- package/dist/assets/{main-B2a_JfKZ.js → main-C9ETLKFc.js} +2 -2
- package/dist/assets/main-CJmb2dF8.js +930 -0
- package/dist/assets/markdownCore-DVuZpnli.js +5 -0
- package/dist/assets/{miniChat-B3CSU_Ps.js → miniChat-B4Ytoq2n.js} +2 -2
- package/dist/assets/{mobile-QNcFIVCW.js → mobile-BUT6-lo1.js} +2 -2
- package/dist/assets/patchFileDiff-8j1apICU.js +13 -0
- package/dist/assets/pl-COFvT56l.js +16 -0
- package/dist/assets/pt-BR-DtVOO2Lj.js +16 -0
- package/dist/assets/renderElectronMiniChatApp-B6vUG-vF.js +2 -0
- package/dist/assets/renderMobileApp-BNgHraq6.js +25 -0
- package/dist/assets/runtimeEndpointReset-B8LYpWvr.js +17 -0
- package/dist/assets/scheduledTasksApi-GC4epIYA.js +1 -0
- package/dist/assets/{shikiHighlight-DE-OwqGw.js → shikiHighlight-BJISeZsQ.js} +1 -1
- package/dist/assets/surface-BvRzS58U.js +1 -0
- package/dist/assets/tr-B8Q0Go2l.js +7 -0
- package/dist/assets/uk-Dk4a-Fn-.js +16 -0
- package/dist/assets/{useAppFontEffects-COAeIX-D.js → useAppFontEffects-DpFNefUA.js} +153 -179
- package/dist/assets/useGitComparison-Dfxaqd0s.js +1 -0
- package/dist/assets/useInlineCommentController-CscsE4t-.js +2 -0
- package/dist/assets/useRootScrollLock-BaCvuRlT.js +1 -0
- package/dist/assets/{vendor-base-ui-BeMD8X_w.js → vendor-base-ui-DdV8vhHn.js} +1 -1
- package/dist/assets/{vendor-codemirror-language-data-BG3v1Ghq.js → vendor-codemirror-language-data-DzlSIu_W.js} +2 -2
- package/dist/assets/vendor-legendapp-list-BuBPBC3R.js +1 -0
- package/dist/assets/vendor-zod-BPAANqvH.js +39 -0
- package/dist/assets/{web-components-BNMwa0qm.js → web-components-CdGfR2XJ.js} +1 -1
- package/dist/assets/zh-CN-B_BXxG1Q.js +16 -0
- package/dist/assets/zh-TW-DLrl1SPZ.js +16 -0
- package/dist/index.html +5 -43
- package/dist/mini-chat.html +4 -4
- package/dist/mobile.html +4 -4
- package/package.json +8 -5
- package/server/index.d.ts +15 -0
- package/server/index.js +82 -13
- package/server/lib/agent-tool/runtime.js +2 -19
- package/server/lib/changelog/update-notes.js +82 -0
- package/server/lib/changelog/update-notes.test.js +59 -0
- package/server/lib/data-dir-migration.js +40 -0
- package/server/lib/data-dir-migration.test.js +59 -0
- package/server/lib/dev-tunnel/DOCUMENTATION.md +13 -4
- package/server/lib/dev-tunnel/runtime.js +4 -4
- package/server/lib/dev-tunnel/tunnel.test.js +20 -0
- package/server/lib/event-stream/runtime.test.js +12 -12
- package/server/lib/fs/DOCUMENTATION.md +5 -0
- package/server/lib/fs/routes.js +63 -21
- package/server/lib/fs/routes.test.js +187 -1
- package/server/lib/git/DOCUMENTATION.md +32 -6
- package/server/lib/git/routes.js +40 -4
- package/server/lib/git/service.js +291 -215
- package/server/lib/git/service.test.js +593 -25
- package/server/lib/inherited-env.js +16 -9
- package/server/lib/inherited-env.test.js +8 -9
- package/server/lib/mcp-reconnect/DOCUMENTATION.md +55 -0
- package/server/lib/mcp-reconnect/runtime.js +122 -0
- package/server/lib/mcp-reconnect/runtime.test.js +140 -0
- package/server/lib/message-queue/DOCUMENTATION.md +161 -0
- package/server/lib/message-queue/runtime.js +870 -0
- package/server/lib/message-queue/runtime.test.js +482 -0
- package/server/lib/network-defaults.js +23 -0
- package/server/lib/network-defaults.test.js +52 -0
- package/server/lib/openchamber-sessions/routes.js +102 -0
- package/server/lib/openchamber-sessions/routes.test.js +88 -0
- package/server/lib/opencode/DOCUMENTATION.md +30 -6
- package/server/lib/opencode/bootstrap-runtime.js +2 -0
- package/server/lib/opencode/config-paths.test.js +116 -0
- package/server/lib/opencode/core-routes.js +1 -0
- package/server/lib/opencode/feature-routes-runtime.js +7 -0
- package/server/lib/opencode/input-history-scope.js +14 -0
- package/server/lib/opencode/managed-plugin-config.js +35 -0
- package/server/lib/opencode/openchamber-routes.js +64 -4
- package/server/lib/opencode/openchamber-routes.test.js +130 -3
- package/server/lib/opencode/plugins.js +2 -2
- package/server/lib/opencode/providers.js +44 -2
- package/server/lib/opencode/providers.test.js +131 -4
- package/server/lib/opencode/routes.js +9 -7
- package/server/lib/opencode/server-startup-runtime.js +1 -1
- package/server/lib/opencode/settings-files.js +214 -0
- package/server/lib/opencode/settings-helpers.js +113 -41
- package/server/lib/opencode/settings-helpers.test.js +342 -0
- package/server/lib/opencode/settings-registry.json +747 -0
- package/server/lib/opencode/settings-runtime.js +141 -20
- package/server/lib/opencode/settings-runtime.test.js +191 -4
- package/server/lib/opencode/shared.js +4 -1
- package/server/lib/opencode/shutdown-runtime.js +2 -0
- package/server/lib/opencode/skill-routes.js +2 -1
- package/server/lib/opencode/snippets.js +1 -2
- package/server/lib/package-manager.js +4 -29
- package/server/lib/package-manager.test.js +2 -2
- package/server/lib/project-context/DOCUMENTATION.md +25 -1
- package/server/lib/project-context/routes.http.test.js +18 -0
- package/server/lib/project-context/routes.js +15 -0
- package/server/lib/project-context/runtime.js +248 -23
- package/server/lib/project-context/runtime.test.js +106 -3
- package/server/lib/projects/DOCUMENTATION.md +76 -0
- package/server/lib/projects/project-config.js +142 -9
- package/server/lib/projects/project-id.js +17 -0
- package/server/lib/projects/project-setup.js +409 -0
- package/server/lib/projects/project-setup.test.js +557 -0
- package/server/lib/projects/routes.js +59 -0
- package/server/lib/quota/DOCUMENTATION.md +40 -2
- package/server/lib/quota/credentials/providers.js +4 -0
- package/server/lib/quota/credentials/store.js +1 -1
- package/server/lib/quota/credentials/store.test.js +4 -4
- package/server/lib/quota/index.js +1 -0
- package/server/lib/quota/providers/cline-pass.js +125 -0
- package/server/lib/quota/providers/cline-pass.test.js +103 -0
- package/server/lib/quota/providers/exe-dev.js +75 -0
- package/server/lib/quota/providers/exe-dev.test.js +47 -0
- package/server/lib/quota/providers/hyper.js +118 -0
- package/server/lib/quota/providers/hyper.test.js +126 -0
- package/server/lib/quota/providers/index.js +22 -0
- package/server/lib/quota/providers/neuralwatt.js +3 -6
- package/server/lib/quota/providers/neuralwatt.test.js +7 -7
- package/server/lib/quota/providers/ollama-cloud.js +31 -0
- package/server/lib/quota/providers/ollama-cloud.test.js +33 -1
- package/server/lib/quota/providers/opencode-go.js +2 -1
- package/server/lib/quota/providers/opencode-go.test.js +31 -2
- package/server/lib/quota/providers/openrouter.js +119 -24
- package/server/lib/quota/providers/openrouter.test.js +330 -0
- package/server/lib/quota/routes.js +2 -0
- package/server/lib/quota/utils/auth.js +1 -1
- package/server/lib/relay/DOCUMENTATION.md +56 -1
- package/server/lib/relay/cross-compat.test.js +26 -0
- package/server/lib/relay/downstream-scheduler.js +154 -0
- package/server/lib/relay/downstream-scheduler.test.js +162 -0
- package/server/lib/relay/e2ee.js +10 -8
- package/server/lib/relay/flow-control.test.js +177 -0
- package/server/lib/relay/host-client.js +37 -8
- package/server/lib/relay/host-client.test.js +9 -1
- package/server/lib/relay/tunnel-codec.js +9 -0
- package/server/lib/relay/tunnel-host.js +69 -18
- package/server/lib/relay/tunnel-host.test.js +9 -1
- package/server/lib/security/request-security.js +21 -6
- package/server/lib/security/request-security.test.js +42 -8
- package/server/lib/session-assist/runtime.js +7 -10
- package/server/lib/session-goal/DOCUMENTATION.md +20 -7
- package/server/lib/session-goal/create.js +3 -1
- package/server/lib/session-goal/runtime.js +81 -20
- package/server/lib/session-goal/runtime.test.js +426 -0
- package/server/lib/skills-catalog/DOCUMENTATION.md +1 -1
- package/server/lib/skills-catalog/install.js +3 -2
- package/server/lib/small-model/DOCUMENTATION.md +7 -2
- package/server/lib/small-model/call.js +17 -6
- package/server/lib/small-model/call.test.js +52 -0
- package/server/lib/small-model/index.js +7 -11
- package/server/lib/small-model/routes.js +2 -1
- package/server/lib/small-model/runtime-providers.js +15 -7
- package/server/lib/small-model/runtime-providers.test.js +7 -1
- package/server/lib/system-prompt/runtime.js +2 -21
- package/server/lib/terminal/DOCUMENTATION.md +14 -10
- package/server/lib/terminal/runtime.js +190 -31
- package/server/lib/terminal/runtime.test.js +571 -37
- package/server/lib/terminal/shells.js +17 -0
- package/server/lib/terminal/shells.test.js +32 -1
- package/server/lib/ui-auth/ui-auth.js +4 -1
- package/server/lib/ui-auth/ui-auth.test.js +16 -0
- package/server/lib/walkthrough/DOCUMENTATION.md +33 -14
- package/server/lib/walkthrough/index.js +1 -0
- package/server/lib/walkthrough/model-settings.js +8 -12
- package/server/lib/walkthrough/prompt.js +3 -1
- package/server/lib/walkthrough/routes.test.js +26 -5
- package/server/lib/walkthrough/sources.js +23 -2
- package/dist/assets/CommitSection-X-f2RgYr.js +0 -1
- package/dist/assets/DiffView-Dh1ey2x9.js +0 -4
- package/dist/assets/FilesView-3ZYP72ki.js +0 -17
- package/dist/assets/GitView-CMuN7z2Z.js +0 -6
- package/dist/assets/LinearIssuesView-CQiUDU8b.js +0 -1
- package/dist/assets/MarkdownImageGallery-bA0PS-tn.js +0 -1
- package/dist/assets/MarkdownRendererImpl-BGQi2PLq.js +0 -12
- package/dist/assets/OnboardingScreen-D3kYJIb6.js +0 -1
- package/dist/assets/PierreDiffViewer-Ct2A13NO.js +0 -157
- package/dist/assets/PlanView-6AUjzNQn.js +0 -5
- package/dist/assets/SettingsView-CHI8fFaK.js +0 -28
- package/dist/assets/ToolPartDiffPreview-DPAATNn6.js +0 -8
- package/dist/assets/WalkthroughView-AX-u2pFg.js +0 -4
- package/dist/assets/contextPanelEmbeddedChat-C9EcRSDF.js +0 -1
- package/dist/assets/de-DqVn5vme.js +0 -9
- package/dist/assets/es-hj43Z53i.js +0 -15
- package/dist/assets/fr-BmkZt2mF.js +0 -13
- package/dist/assets/index-L_ncV-6H.css +0 -1
- package/dist/assets/index-qES19dlJ.js +0 -7
- package/dist/assets/ja-DioMIEGF.js +0 -15
- package/dist/assets/ko-BqPgaQJb.js +0 -15
- package/dist/assets/main-CYgSu6dx.js +0 -928
- package/dist/assets/markdownCore-CWRCqSH2.js +0 -3
- package/dist/assets/patchFileDiff-BpMQZnYu.js +0 -10
- package/dist/assets/pl-slULBF4R.js +0 -15
- package/dist/assets/pt-BR-BqH8fuCH.js +0 -15
- package/dist/assets/renderElectronMiniChatApp-D77bmLqo.js +0 -2
- package/dist/assets/renderMobileApp-BQ3Kdm-d.js +0 -25
- package/dist/assets/runtimeEndpointReset-CRGn1gVj.js +0 -16
- package/dist/assets/scheduledTasksApi-BSRCtP6T.js +0 -1
- package/dist/assets/tr-CjEbw1Aw.js +0 -6
- package/dist/assets/uk-BkkfxbOs.js +0 -15
- package/dist/assets/useInlineCommentController-JsBboQwt.js +0 -2
- package/dist/assets/useRootScrollLock-C0N9mCwz.js +0 -1
- package/dist/assets/vendor-ghostty-web-CrxinGGH.js +0 -13
- package/dist/assets/vendor-legendapp-list-gg7YLRW7.js +0 -1
- package/dist/assets/vendor-zod-BLfBSgBh.js +0 -39
- package/dist/assets/zh-CN-C6FMdVgv.js +0 -15
- package/dist/assets/zh-TW-CdcMjDxv.js +0 -15
|
@@ -1,8 +1,19 @@
|
|
|
1
|
+
import { execFileSync } from 'node:child_process';
|
|
2
|
+
import { mkdirSync, mkdtempSync, readdirSync, readFileSync, rmSync } from 'node:fs';
|
|
3
|
+
import { tmpdir } from 'node:os';
|
|
4
|
+
import { dirname, join } from 'node:path';
|
|
5
|
+
import { fileURLToPath, pathToFileURL } from 'node:url';
|
|
1
6
|
import { describe, expect, it } from 'vitest';
|
|
2
7
|
|
|
8
|
+
import {
|
|
9
|
+
DEFAULT_INPUT_HISTORY_LIMIT,
|
|
10
|
+
} from './input-history-scope.js';
|
|
3
11
|
import { createSettingsHelpers } from './settings-helpers.js';
|
|
4
12
|
import { createSettingsNormalizationRuntime } from './settings-normalization-runtime.js';
|
|
5
13
|
|
|
14
|
+
const testFilePath = fileURLToPath(import.meta.url);
|
|
15
|
+
const packagesWebDir = join(dirname(testFilePath), '..', '..', '..');
|
|
16
|
+
|
|
6
17
|
const createTestHelpers = () => createSettingsHelpers({
|
|
7
18
|
normalizePathForPersistence: (value) => value,
|
|
8
19
|
normalizeDirectoryPath: (value) => value,
|
|
@@ -58,6 +69,79 @@ const createTestHelpersWithRealSanitizers = () => {
|
|
|
58
69
|
};
|
|
59
70
|
|
|
60
71
|
describe('settings helpers', () => {
|
|
72
|
+
it('round-trips telemetry opt-in with the hidden list and preserves it across unrelated writes', () => {
|
|
73
|
+
const helpers = createTestHelpers();
|
|
74
|
+
const legacy = helpers.sanitizeSettingsUpdate({ workStatusHiddenSections: [] });
|
|
75
|
+
expect(legacy.workStatusHiddenSectionsExplicit).toBeUndefined();
|
|
76
|
+
const changes = helpers.sanitizeSettingsUpdate({ workStatusHiddenSections: [], workStatusHiddenSectionsExplicit: true });
|
|
77
|
+
const saved = helpers.mergePersistedSettings(legacy, changes);
|
|
78
|
+
const reloaded = helpers.formatSettingsResponse(JSON.parse(JSON.stringify(saved)));
|
|
79
|
+
expect(reloaded.workStatusHiddenSections).toEqual([]);
|
|
80
|
+
expect(reloaded.workStatusHiddenSectionsExplicit).toBe(true);
|
|
81
|
+
const next = helpers.mergePersistedSettings(reloaded, helpers.sanitizeSettingsUpdate({ workStatusPanelEnabled: false }));
|
|
82
|
+
expect(helpers.formatSettingsResponse(next).workStatusHiddenSectionsExplicit).toBe(true);
|
|
83
|
+
expect(helpers.sanitizeSettingsUpdate({ workStatusHiddenSectionsExplicit: 'true' }).workStatusHiddenSectionsExplicit).toBeUndefined();
|
|
84
|
+
});
|
|
85
|
+
it('imports from the packed @openchamber/web tarball without escaping the published package', async () => {
|
|
86
|
+
const tempRoot = mkdtempSync(join(tmpdir(), 'settings-helpers-pack-'));
|
|
87
|
+
const packDir = join(tempRoot, 'pack');
|
|
88
|
+
const extractDir = join(tempRoot, 'extract');
|
|
89
|
+
|
|
90
|
+
try {
|
|
91
|
+
mkdirSync(packDir);
|
|
92
|
+
mkdirSync(extractDir);
|
|
93
|
+
execFileSync('npm', ['pack', '--silent', '--pack-destination', packDir], {
|
|
94
|
+
cwd: packagesWebDir,
|
|
95
|
+
stdio: 'pipe',
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
const tarballName = readdirSync(packDir).find((entry) => entry.endsWith('.tgz'));
|
|
99
|
+
expect(tarballName).toBeTruthy();
|
|
100
|
+
|
|
101
|
+
execFileSync('tar', ['-xzf', join(packDir, tarballName), '-C', extractDir], {
|
|
102
|
+
stdio: 'pipe',
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
const extractedModule = await import(
|
|
106
|
+
pathToFileURL(join(extractDir, 'package', 'server', 'lib', 'opencode', 'settings-helpers.js')).href
|
|
107
|
+
);
|
|
108
|
+
|
|
109
|
+
const helpers = extractedModule.createSettingsHelpers({
|
|
110
|
+
normalizePathForPersistence: (value) => value,
|
|
111
|
+
normalizeDirectoryPath: (value) => value,
|
|
112
|
+
normalizeTunnelBootstrapTtlMs: (value) => value,
|
|
113
|
+
normalizeTunnelSessionTtlMs: (value) => value,
|
|
114
|
+
normalizeTunnelProvider: (value) => value,
|
|
115
|
+
normalizeTunnelMode: (value) => value,
|
|
116
|
+
normalizeOptionalPath: (value) => value,
|
|
117
|
+
normalizeManagedRemoteTunnelHostname: (value) => value,
|
|
118
|
+
normalizeManagedRemoteTunnelPresets: () => undefined,
|
|
119
|
+
normalizeManagedRemoteTunnelPresetTokens: () => undefined,
|
|
120
|
+
sanitizeTypographySizesPartial: () => undefined,
|
|
121
|
+
normalizeStringArray: (input) => input,
|
|
122
|
+
sanitizeModelRefs: () => undefined,
|
|
123
|
+
sanitizeSkillCatalogs: () => undefined,
|
|
124
|
+
sanitizeProjects: () => undefined,
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
expect(helpers.sanitizeSettingsUpdate({ inputHistoryScope: 'global' })).toEqual({
|
|
128
|
+
inputHistoryScope: 'global',
|
|
129
|
+
});
|
|
130
|
+
expect(helpers.sanitizeSettingsUpdate({ inputHistoryScope: 'session' })).toEqual({
|
|
131
|
+
inputHistoryScope: 'session',
|
|
132
|
+
});
|
|
133
|
+
expect(helpers.sanitizeSettingsUpdate({ inputHistoryLimit: 40 })).toEqual({
|
|
134
|
+
inputHistoryLimit: 40,
|
|
135
|
+
});
|
|
136
|
+
expect(helpers.formatSettingsResponse({})).toMatchObject({
|
|
137
|
+
inputHistoryScope: 'session',
|
|
138
|
+
inputHistoryLimit: DEFAULT_INPUT_HISTORY_LIMIT,
|
|
139
|
+
});
|
|
140
|
+
} finally {
|
|
141
|
+
rmSync(tempRoot, { recursive: true, force: true });
|
|
142
|
+
}
|
|
143
|
+
});
|
|
144
|
+
|
|
61
145
|
it('accepts only booleans for draft starter visibility', () => {
|
|
62
146
|
const helpers = createTestHelpers();
|
|
63
147
|
|
|
@@ -66,6 +150,14 @@ describe('settings helpers', () => {
|
|
|
66
150
|
expect(helpers.sanitizeSettingsUpdate({ draftStartersVisible: 'false' })).toEqual({});
|
|
67
151
|
});
|
|
68
152
|
|
|
153
|
+
it('sanitizes both Enter settings independently', () => {
|
|
154
|
+
const helpers = createTestHelpers();
|
|
155
|
+
|
|
156
|
+
expect(helpers.sanitizeSettingsUpdate({ enterToSend: true })).toEqual({ enterToSend: true });
|
|
157
|
+
expect(helpers.sanitizeSettingsUpdate({ enterToSendConfigured: false })).toEqual({ enterToSendConfigured: false });
|
|
158
|
+
expect(helpers.sanitizeSettingsUpdate({ enterToSend: 'true', enterToSendConfigured: 1 })).toEqual({});
|
|
159
|
+
});
|
|
160
|
+
|
|
69
161
|
it('sanitizes shared sidebar display preferences', () => {
|
|
70
162
|
const helpers = createTestHelpers();
|
|
71
163
|
|
|
@@ -88,6 +180,15 @@ describe('settings helpers', () => {
|
|
|
88
180
|
})).toEqual({});
|
|
89
181
|
});
|
|
90
182
|
|
|
183
|
+
it('persists valid tool JSON view modes', () => {
|
|
184
|
+
const helpers = createTestHelpers();
|
|
185
|
+
|
|
186
|
+
expect(helpers.sanitizeSettingsUpdate({ toolJsonViewMode: 'summary' })).toEqual({ toolJsonViewMode: 'summary' });
|
|
187
|
+
expect(helpers.sanitizeSettingsUpdate({ toolJsonViewMode: 'formatted' })).toEqual({ toolJsonViewMode: 'formatted' });
|
|
188
|
+
expect(helpers.sanitizeSettingsUpdate({ toolJsonViewMode: 'raw' })).toEqual({ toolJsonViewMode: 'raw' });
|
|
189
|
+
expect(helpers.sanitizeSettingsUpdate({ toolJsonViewMode: 'unknown' })).toEqual({});
|
|
190
|
+
});
|
|
191
|
+
|
|
91
192
|
it('accepts only booleans for wide chat layout', () => {
|
|
92
193
|
const helpers = createTestHelpers();
|
|
93
194
|
|
|
@@ -134,6 +235,74 @@ describe('settings helpers', () => {
|
|
|
134
235
|
expect(helpers.sanitizeSettingsUpdate({ messageStreamTransport: 'websocket' })).toEqual({});
|
|
135
236
|
});
|
|
136
237
|
|
|
238
|
+
it('accepts inputHistoryScope as a persisted shared setting', () => {
|
|
239
|
+
const helpers = createTestHelpers();
|
|
240
|
+
|
|
241
|
+
expect(helpers.sanitizeSettingsUpdate({ inputHistoryScope: 'global' })).toEqual({
|
|
242
|
+
inputHistoryScope: 'global',
|
|
243
|
+
});
|
|
244
|
+
expect(helpers.sanitizeSettingsUpdate({ inputHistoryScope: 'session' })).toEqual({
|
|
245
|
+
inputHistoryScope: 'session',
|
|
246
|
+
});
|
|
247
|
+
});
|
|
248
|
+
|
|
249
|
+
it('accepts valid inputHistoryLimit values as a persisted shared setting', () => {
|
|
250
|
+
const helpers = createTestHelpers();
|
|
251
|
+
|
|
252
|
+
expect(helpers.sanitizeSettingsUpdate({ inputHistoryLimit: 1 })).toEqual({
|
|
253
|
+
inputHistoryLimit: 1,
|
|
254
|
+
});
|
|
255
|
+
expect(helpers.sanitizeSettingsUpdate({ inputHistoryLimit: 40 })).toEqual({
|
|
256
|
+
inputHistoryLimit: 40,
|
|
257
|
+
});
|
|
258
|
+
expect(helpers.sanitizeSettingsUpdate({ inputHistoryLimit: 100 })).toEqual({
|
|
259
|
+
inputHistoryLimit: 100,
|
|
260
|
+
});
|
|
261
|
+
});
|
|
262
|
+
|
|
263
|
+
it('rejects invalid inputHistoryLimit values', () => {
|
|
264
|
+
const helpers = createTestHelpers();
|
|
265
|
+
|
|
266
|
+
expect(helpers.sanitizeSettingsUpdate({ inputHistoryLimit: 0 })).toEqual({});
|
|
267
|
+
expect(helpers.sanitizeSettingsUpdate({ inputHistoryLimit: 101 })).toEqual({});
|
|
268
|
+
expect(helpers.sanitizeSettingsUpdate({ inputHistoryLimit: 1.5 })).toEqual({});
|
|
269
|
+
expect(helpers.sanitizeSettingsUpdate({ inputHistoryLimit: '40' })).toEqual({});
|
|
270
|
+
expect(helpers.sanitizeSettingsUpdate({ inputHistoryLimit: Number.NaN })).toEqual({});
|
|
271
|
+
expect(helpers.sanitizeSettingsUpdate({ inputHistoryLimit: Number.POSITIVE_INFINITY })).toEqual({});
|
|
272
|
+
expect(helpers.sanitizeSettingsUpdate({ inputHistoryLimit: Number.NEGATIVE_INFINITY })).toEqual({});
|
|
273
|
+
});
|
|
274
|
+
|
|
275
|
+
it('rejects invalid inputHistoryScope values', () => {
|
|
276
|
+
const helpers = createTestHelpers();
|
|
277
|
+
|
|
278
|
+
expect(helpers.sanitizeSettingsUpdate({ inputHistoryScope: 'workspace' })).toEqual({});
|
|
279
|
+
});
|
|
280
|
+
|
|
281
|
+
it('defaults inputHistoryScope to session in formatted settings responses', () => {
|
|
282
|
+
const helpers = createTestHelpers();
|
|
283
|
+
|
|
284
|
+
expect(helpers.formatSettingsResponse({ inputHistoryScope: 'global' })).toMatchObject({
|
|
285
|
+
inputHistoryScope: 'global',
|
|
286
|
+
});
|
|
287
|
+
expect(helpers.formatSettingsResponse({})).toMatchObject({
|
|
288
|
+
inputHistoryScope: 'session',
|
|
289
|
+
});
|
|
290
|
+
});
|
|
291
|
+
|
|
292
|
+
it('defaults missing inputHistoryLimit to 40 in formatted settings responses and preserves valid values', () => {
|
|
293
|
+
const helpers = createTestHelpers();
|
|
294
|
+
|
|
295
|
+
expect(helpers.formatSettingsResponse({})).toMatchObject({
|
|
296
|
+
inputHistoryLimit: DEFAULT_INPUT_HISTORY_LIMIT,
|
|
297
|
+
});
|
|
298
|
+
expect(helpers.formatSettingsResponse({ inputHistoryLimit: 1 })).toMatchObject({
|
|
299
|
+
inputHistoryLimit: 1,
|
|
300
|
+
});
|
|
301
|
+
expect(helpers.formatSettingsResponse({ inputHistoryLimit: 100 })).toMatchObject({
|
|
302
|
+
inputHistoryLimit: 100,
|
|
303
|
+
});
|
|
304
|
+
});
|
|
305
|
+
|
|
137
306
|
it('sanitizes the persisted terminal shell', () => {
|
|
138
307
|
const helpers = createTestHelpers();
|
|
139
308
|
|
|
@@ -316,6 +485,27 @@ describe('settings helpers', () => {
|
|
|
316
485
|
});
|
|
317
486
|
});
|
|
318
487
|
|
|
488
|
+
it('accepts and rejects invalid Enter-to-send preference values', () => {
|
|
489
|
+
const helpers = createTestHelpers();
|
|
490
|
+
|
|
491
|
+
expect(helpers.sanitizeSettingsUpdate({ enterToSend: true, enterToSendConfigured: true })).toEqual({
|
|
492
|
+
enterToSend: true,
|
|
493
|
+
enterToSendConfigured: true,
|
|
494
|
+
});
|
|
495
|
+
expect(helpers.sanitizeSettingsUpdate({ enterToSend: false, enterToSendConfigured: true })).toEqual({
|
|
496
|
+
enterToSend: false,
|
|
497
|
+
enterToSendConfigured: true,
|
|
498
|
+
});
|
|
499
|
+
expect(helpers.sanitizeSettingsUpdate({ enterToSend: false, enterToSendConfigured: false })).toEqual({
|
|
500
|
+
enterToSend: false,
|
|
501
|
+
enterToSendConfigured: false,
|
|
502
|
+
});
|
|
503
|
+
expect(helpers.sanitizeSettingsUpdate({ enterToSend: 'true' })).toEqual({});
|
|
504
|
+
expect(helpers.sanitizeSettingsUpdate({ enterToSend: 1 })).toEqual({});
|
|
505
|
+
expect(helpers.sanitizeSettingsUpdate({ enterToSendConfigured: 'true' })).toEqual({});
|
|
506
|
+
expect(helpers.sanitizeSettingsUpdate({ enterToSendConfigured: 1 })).toEqual({});
|
|
507
|
+
});
|
|
508
|
+
|
|
319
509
|
it('accepts dismissed OpenCode update toast version as a persisted shared setting', () => {
|
|
320
510
|
const helpers = createTestHelpers();
|
|
321
511
|
|
|
@@ -534,3 +724,155 @@ describe('settings helpers', () => {
|
|
|
534
724
|
});
|
|
535
725
|
});
|
|
536
726
|
});
|
|
727
|
+
|
|
728
|
+
describe('settings registry gate', () => {
|
|
729
|
+
const registryPath = join(dirname(testFilePath), 'settings-registry.json');
|
|
730
|
+
const registry = JSON.parse(readFileSync(registryPath, 'utf8'));
|
|
731
|
+
const persistableKeys = Object.entries(registry.fields)
|
|
732
|
+
.filter(([, field]) => !field.computed && !field.local && field.owner !== 'desktop-shell')
|
|
733
|
+
.map(([key]) => key);
|
|
734
|
+
|
|
735
|
+
// One valid value per persistable registry key. The test below fails when a
|
|
736
|
+
// key is added to the registry without a line here, and when the sanitizer
|
|
737
|
+
// stops accepting a key the registry still lists — that is the drift the
|
|
738
|
+
// registry exists to end.
|
|
739
|
+
const validValues = {
|
|
740
|
+
themeId: 'openchamber-dark', useSystemTheme: true, themeVariant: 'dark', lightThemeId: 'openchamber-light', darkThemeId: 'openchamber-dark',
|
|
741
|
+
splashBgLight: '#fff', splashFgLight: '#000', splashBgDark: '#000', splashFgDark: '#fff',
|
|
742
|
+
lastDirectory: '/home/testuser/project', homeDirectory: '/home/testuser', opencodeBinary: '/usr/local/bin/opencode',
|
|
743
|
+
projects: [{ id: 'p', path: '/home/testuser/project' }], activeProjectId: 'p',
|
|
744
|
+
securityScopedBookmarks: ['bookmark'], pinnedDirectories: ['/home/testuser/project'],
|
|
745
|
+
desktopLanAccessEnabled: true, desktopKeepAwakeEnabled: true, desktopMinimizeToTrayEnabled: true, desktopMacMenuBarEnabled: true,
|
|
746
|
+
desktopUiPassword: 'secret', githubClientId: 'client', githubScopes: 'repo', skillCatalogs: [{ id: 'c', label: 'C', source: 'https://x' }],
|
|
747
|
+
defaultGitIdentityId: 'global', permissionAutoAccept: { sessions: { s: true }, revision: 1 },
|
|
748
|
+
agentControlToolEnabled: true, agentWebToolEnabled: true, agentMemoryToolEnabled: true, openCodeUpdateToastDismissedVersion: '1.0.0',
|
|
749
|
+
autoDeleteEnabled: true, autoDeleteAfterDays: 30, sessionRetentionAction: 'archive', terminalShell: 'zsh', terminalLoginShells: ['zsh'],
|
|
750
|
+
openInAppId: 'vscode', dictationEnabled: true, sttProvider: 'local', sttServerUrl: 'http://localhost:8001/v1', sttModel: 'm', sttLocalModel: 'm', sttLanguage: 'en',
|
|
751
|
+
tunnelProvider: 'cloudflare', tunnelMode: 'quick', tunnelBootstrapTtlMs: 600000, tunnelSessionTtlMs: 86400000, managedLocalTunnelConfigPath: '/tmp/x',
|
|
752
|
+
managedRemoteTunnelHostname: 'x.example', managedRemoteTunnelToken: 'token', managedRemoteTunnelPresets: [{ id: 'a', name: 'A', hostname: 'a.example' }],
|
|
753
|
+
managedRemoteTunnelSelectedPresetId: 'a', managedRemoteTunnelPresetTokens: { a: 'token' },
|
|
754
|
+
sidebarProjectDisplayMode: 'all', sidebarSessionGroupingMode: 'flat', sidebarProjectSortOrder: 'manual', sidebarShowRecentSection: true,
|
|
755
|
+
workStatusPanelEnabled: true, workStatusHiddenSections: ['mcp'], workStatusHiddenSectionsExplicit: true,
|
|
756
|
+
showReasoningTraces: true, streamingAutoFollowEnabled: true, collapsibleThinkingBlocks: true, showTextJustificationActivity: true,
|
|
757
|
+
chatRenderMode: 'live', activityRenderMode: 'summary', mermaidRenderingMode: 'svg', userMessageRenderingMode: 'markdown', collapsibleUserMessages: true,
|
|
758
|
+
stickyUserHeader: true, promptNavigatorEnabled: true, wideChatLayoutEnabled: true, showSplitAssistantMessageActions: true, showToolFileIcons: true,
|
|
759
|
+
codeBlockLineWrap: true, showTurnChangedFiles: true, showExpandedBashTools: true, showExpandedEditTools: true, toolJsonViewMode: 'raw',
|
|
760
|
+
timeFormatPreference: '24h', weekStartPreference: 'monday', messageStreamTransport: 'ws', diffLayoutPreference: 'inline', diffWrapLines: true,
|
|
761
|
+
gitChangesViewMode: 'tree', gitmojiEnabled: true, defaultFileViewerPreview: true, directoryShowHidden: true, filesViewShowGitignored: true,
|
|
762
|
+
fileEditorKeymap: 'vim', autoSaveEnabled: true, autoCreateWorktree: true, sessionTabsEnabled: true, showOpenCodeRestartConfirm: true,
|
|
763
|
+
allowPromptingSubagentSessions: true, inputSpellcheckEnabled: true, enterToSend: true, enterToSendConfigured: true, persistChatDraft: true,
|
|
764
|
+
largeTextPasteBehavior: 'attach', followUpBehavior: 'steer', queueModeEnabled: true, inputHistoryScope: 'global', inputHistoryLimit: 40,
|
|
765
|
+
draftStarters: [{ type: 'command', name: 'plan-feature' }], draftStartersVisible: true, draftStartersCraftGoalAdded: true, draftStartersScheduleTaskAdded: true,
|
|
766
|
+
fontSize: 100, terminalFontSize: 14, editorFontSize: 14, uiFont: 'inter', monoFont: 'jetbrains-mono', padding: 100, cornerRadius: 8,
|
|
767
|
+
shortcutOverrides: { 'chat.send': 'mod+enter' },
|
|
768
|
+
defaultModel: 'anthropic/claude', defaultVariant: 'high', defaultAgent: 'build', smallModelUseDefault: false, smallModelOverride: 'anthropic/haiku',
|
|
769
|
+
walkthroughModelOverride: 'anthropic/claude', zenModel: 'zen/model',
|
|
770
|
+
favoriteModels: [{ providerID: 'anthropic', modelID: 'claude' }], hiddenModels: [{ providerID: 'openai', modelID: 'gpt' }], collapsedModelProviders: ['openai'],
|
|
771
|
+
recentModels: [{ providerID: 'anthropic', modelID: 'claude' }], recentAgents: ['build'], recentEfforts: { 'anthropic/claude': ['high'] }, providerOrder: ['anthropic'],
|
|
772
|
+
sessionRecapEnabled: true, sessionSuggestionEnabled: true, sessionGoalEnabled: true, sessionGoalDefaultBudgetEnabled: true, sessionGoalDefaultBudget: 5,
|
|
773
|
+
summarizeLastMessage: true, summaryThreshold: 100, summaryLength: 50, maxLastMessageLength: 200, showDeletionDialog: true,
|
|
774
|
+
nativeNotificationsEnabled: true, notificationMode: 'always', notifyOnSubtasks: true, notifyOnCompletion: true, notifyOnError: true, notifyOnQuestion: true,
|
|
775
|
+
notificationTemplates: { completion: { title: 't', message: 'm' } }, showOpenCodeUpdateNotifications: true, reportUsage: true,
|
|
776
|
+
usageDisplayMode: 'usage', usageDropdownProviders: ['anthropic'], usageSelectedModels: { anthropic: ['claude'] }, usageCollapsedFamilies: { anthropic: ['f'] },
|
|
777
|
+
usageExpandedFamilies: { anthropic: ['f'] }, usageModelGroups: { anthropic: { customGroups: [{ id: 'g', label: 'G', models: ['claude'], order: 0 }] } },
|
|
778
|
+
globalBehaviorPrompt: 'Be brief.', responseStyleEnabled: true, responseStylePreset: 'concise', responseStyleCustomInstructions: 'x', optimizeSystemPrompt: true,
|
|
779
|
+
pwaAppName: 'OpenChamber', pwaOrientation: 'portrait', mobileKeyboardMode: 'native', desktopWindowControlsPosition: 'left', desktopWindowControlsStyle: 'classic',
|
|
780
|
+
inputBarOffset: 10,
|
|
781
|
+
};
|
|
782
|
+
|
|
783
|
+
it('accepts a valid value for every persistable registry key (no server-side drift)', () => {
|
|
784
|
+
// The shared test helpers stub the injected list sanitizers to `undefined`
|
|
785
|
+
// (they are covered by their own suites); here they must pass values through
|
|
786
|
+
// so a key is judged by the sanitizer's own branch, not by a stub.
|
|
787
|
+
const helpers = createSettingsHelpers({
|
|
788
|
+
normalizePathForPersistence: (value) => value,
|
|
789
|
+
normalizeDirectoryPath: (value) => value,
|
|
790
|
+
normalizeTunnelBootstrapTtlMs: (value) => value,
|
|
791
|
+
normalizeTunnelSessionTtlMs: (value) => value,
|
|
792
|
+
normalizeTunnelProvider: (value) => value,
|
|
793
|
+
normalizeTunnelMode: (value) => value,
|
|
794
|
+
normalizeOptionalPath: (value) => value,
|
|
795
|
+
normalizeManagedRemoteTunnelHostname: (value) => value,
|
|
796
|
+
normalizeManagedRemoteTunnelPresets: (value) => value,
|
|
797
|
+
normalizeManagedRemoteTunnelPresetTokens: (value) => value,
|
|
798
|
+
normalizeStringArray: (input) => input,
|
|
799
|
+
sanitizeModelRefs: (value) => value,
|
|
800
|
+
sanitizeSkillCatalogs: (value) => value,
|
|
801
|
+
sanitizeProjects: (value) => value,
|
|
802
|
+
});
|
|
803
|
+
const missingFixture = persistableKeys.filter((key) => !(key in validValues));
|
|
804
|
+
expect(missingFixture).toEqual([]);
|
|
805
|
+
|
|
806
|
+
const rejected = persistableKeys.filter((key) => {
|
|
807
|
+
const result = helpers.sanitizeSettingsUpdate({ [key]: validValues[key] });
|
|
808
|
+
// `queueModeEnabled` is absorbed into `followUpBehavior` on purpose.
|
|
809
|
+
const landedAs = key === 'queueModeEnabled' ? 'followUpBehavior' : key;
|
|
810
|
+
return result[landedAs] === undefined;
|
|
811
|
+
});
|
|
812
|
+
expect(rejected).toEqual([]);
|
|
813
|
+
});
|
|
814
|
+
|
|
815
|
+
it('drops keys the registry does not list, computed flags, and desktop-shell-owned keys', () => {
|
|
816
|
+
const helpers = createTestHelpers();
|
|
817
|
+
expect(helpers.sanitizeSettingsUpdate({
|
|
818
|
+
markdownDisplayMode: 'raw',
|
|
819
|
+
toolCallExpansion: 'collapsed',
|
|
820
|
+
expandedEditorToolbar: true,
|
|
821
|
+
typographySizes: { base: 14 },
|
|
822
|
+
gitProviderId: 'anthropic',
|
|
823
|
+
gitModelId: 'claude',
|
|
824
|
+
messageLimit: 200,
|
|
825
|
+
agentMemoryFeatureAvailable: true,
|
|
826
|
+
desktopHosts: [],
|
|
827
|
+
notARealKey: 1,
|
|
828
|
+
})).toEqual({});
|
|
829
|
+
});
|
|
830
|
+
|
|
831
|
+
it('never returns secret keys from a formatted response', () => {
|
|
832
|
+
const helpers = createTestHelpers();
|
|
833
|
+
const secretKeys = Object.entries(registry.fields).filter(([, field]) => field.secret).map(([key]) => key);
|
|
834
|
+
expect(secretKeys).toContain('managedRemoteTunnelToken');
|
|
835
|
+
expect(secretKeys).toContain('desktopUiPassword');
|
|
836
|
+
expect(secretKeys).toContain('managedRemoteTunnelPresetTokens');
|
|
837
|
+
const response = helpers.formatSettingsResponse({
|
|
838
|
+
managedRemoteTunnelToken: 'token',
|
|
839
|
+
desktopUiPassword: 'pw',
|
|
840
|
+
managedRemoteTunnelPresetTokens: { a: 'tok' },
|
|
841
|
+
themeId: 'x',
|
|
842
|
+
});
|
|
843
|
+
for (const key of secretKeys) {
|
|
844
|
+
expect(response).not.toHaveProperty(key);
|
|
845
|
+
}
|
|
846
|
+
expect(response.hasManagedRemoteTunnelToken).toBe(true);
|
|
847
|
+
expect(response.hasDesktopUiPassword).toBe(true);
|
|
848
|
+
expect(helpers.formatSettingsResponse({ desktopUiPassword: '' }).hasDesktopUiPassword).toBe(false);
|
|
849
|
+
});
|
|
850
|
+
|
|
851
|
+
it('accepts the newly shared profile fields', () => {
|
|
852
|
+
const helpers = createTestHelpersWithRealSanitizers();
|
|
853
|
+
expect(helpers.sanitizeSettingsUpdate({
|
|
854
|
+
providerOrder: ['b', 'a', 'a'],
|
|
855
|
+
diffWrapLines: true,
|
|
856
|
+
persistChatDraft: false,
|
|
857
|
+
largeTextPasteBehavior: 'inline',
|
|
858
|
+
fileEditorKeymap: 'vim',
|
|
859
|
+
allowPromptingSubagentSessions: true,
|
|
860
|
+
showOpenCodeRestartConfirm: false,
|
|
861
|
+
codeBlockLineWrap: true,
|
|
862
|
+
streamingAutoFollowEnabled: false,
|
|
863
|
+
autoSaveEnabled: false,
|
|
864
|
+
})).toEqual({
|
|
865
|
+
providerOrder: ['b', 'a'],
|
|
866
|
+
diffWrapLines: true,
|
|
867
|
+
persistChatDraft: false,
|
|
868
|
+
largeTextPasteBehavior: 'inline',
|
|
869
|
+
fileEditorKeymap: 'vim',
|
|
870
|
+
allowPromptingSubagentSessions: true,
|
|
871
|
+
showOpenCodeRestartConfirm: false,
|
|
872
|
+
codeBlockLineWrap: true,
|
|
873
|
+
streamingAutoFollowEnabled: false,
|
|
874
|
+
autoSaveEnabled: false,
|
|
875
|
+
});
|
|
876
|
+
expect(helpers.sanitizeSettingsUpdate({ largeTextPasteBehavior: 'maybe', fileEditorKeymap: 'emacs' })).toEqual({});
|
|
877
|
+
});
|
|
878
|
+
});
|