@aiworker/web 1.22.2 → 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/bin/cli.js +7 -0
- package/dist/assets/{CodeMirrorCommentWidgets-CSICpWQD.js → CodeMirrorCommentWidgets-B1Gizi3a.js} +1 -1
- package/dist/assets/DiffView-DXjVsEOw.js +4 -0
- package/dist/assets/{DirtyBranchSwitchDialog-C22HdWj4.js → DirtyBranchSwitchDialog-aig6zJjg.js} +1 -1
- package/dist/assets/FilesView-CJdG0-wk.js +17 -0
- package/dist/assets/{GitView-VQQkYuUw.js → GitView-BXsS3jG6.js} +4 -4
- package/dist/assets/{JsonTreeView-6lrhwD3q.js → JsonTreeView-D_hpXBnF.js} +1 -1
- package/dist/assets/{LinearIssuesView-Ba6Q9569.js → LinearIssuesView-BoUiLQG1.js} +1 -1
- package/dist/assets/MarkdownImageGallery-B_B_a9KS.js +1 -0
- package/dist/assets/MarkdownRendererImpl-xgyyLiBM.js +12 -0
- package/dist/assets/OnboardingScreen-DIp84OWt.js +1 -0
- package/dist/assets/{PatchDiff-BzcZPlOq.js → PatchDiff-8p6nLJaU.js} +1 -1
- package/dist/assets/PierreDiffViewer-DjC8nTk9.js +161 -0
- package/dist/assets/{PlanView-DyDt_nZ_.js → PlanView-CJTmOF6I.js} +2 -2
- package/dist/assets/SettingsView-DD-JqIJx.js +28 -0
- package/dist/assets/{SettingsWindow-DDQYaKWQ.js → SettingsWindow-Cpb4hM6q.js} +1 -1
- package/dist/assets/SymbolsNerdFontMono-Regular-aK5vsLov.woff2 +0 -0
- package/dist/assets/{ToolOutputDialog-iYF8Y2bz.js → ToolOutputDialog-DfaKZXNU.js} +6 -6
- package/dist/assets/{ToolPartDiffPreview-CdjbOVMM.js → ToolPartDiffPreview-DtZLzrSK.js} +2 -2
- package/dist/assets/{VirtualizedFileDiff-fFFm2QgH.js → VirtualizedFileDiff-C8i9HpTB.js} +1 -1
- package/dist/assets/WalkthroughView-DeKMvZlL.js +4 -0
- package/dist/assets/{computeVirtualFileMetrics-CzgLum_K.js → computeVirtualFileMetrics-BBI7ZwrC.js} +2 -2
- package/dist/assets/contextPanelEmbeddedChat-BdA6bdky.js +1 -0
- package/dist/assets/de-DCrsQ5zS.js +10 -0
- package/dist/assets/es-Dwv4YfH6.js +16 -0
- package/dist/assets/fr-BCWg4Cnx.js +14 -0
- package/dist/assets/ghostty-vt-DdA0Zryv.wasm +0 -0
- package/dist/assets/index-B68h5S5B.css +1 -0
- package/dist/assets/index-DVwMarHI.js +39 -0
- package/dist/assets/{index-Bl3LSbjn.js → index-jXSK5RrB.js} +1 -1
- package/dist/assets/{isDiffPlainText-Dx-dB_8l.js → isDiffPlainText-B_x_rVon.js} +1 -1
- package/dist/assets/{isFilePlainText-CWFPwmnU.js → isFilePlainText-DW_xMer1.js} +1 -1
- package/dist/assets/ja-Bruw3RYC.js +16 -0
- package/dist/assets/ko-idhXGF8j.js +16 -0
- package/dist/assets/main-BkyaQhJX.js +930 -0
- package/dist/assets/{main-DnBAoktx.js → main-CzdPuL0s.js} +2 -2
- package/dist/assets/markdownCore-Cn4Tyejo.js +5 -0
- package/dist/assets/{miniChat-BKyvLseN.js → miniChat-CCkqKOLp.js} +2 -2
- package/dist/assets/{mobile-DKjeFgMN.js → mobile-mvxTo055.js} +2 -2
- package/dist/assets/patchFileDiff-Csk4iNUi.js +13 -0
- package/dist/assets/pl-CGZWUqZJ.js +16 -0
- package/dist/assets/pt-BR-B8V7u7tA.js +16 -0
- package/dist/assets/renderElectronMiniChatApp-BxoyujA5.js +2 -0
- package/dist/assets/renderMobileApp-B48CsP4r.js +25 -0
- package/dist/assets/runtimeEndpointReset-Bg5gx18H.js +17 -0
- package/dist/assets/scheduledTasksApi-BW0Etzwz.js +1 -0
- package/dist/assets/{shikiHighlight-TEQCmzW8.js → shikiHighlight-CRl0SUC9.js} +1 -1
- package/dist/assets/surface-oMvhsog3.js +1 -0
- package/dist/assets/tr-BK_KiO2V.js +7 -0
- package/dist/assets/uk-CG74xV8N.js +16 -0
- package/dist/assets/{useAppFontEffects-D7sNpuL2.js → useAppFontEffects-CPKeOpUo.js} +160 -157
- package/dist/assets/useGitComparison-DH220WmO.js +1 -0
- package/dist/assets/useInlineCommentController-BiWz4AVb.js +2 -0
- package/dist/assets/useRootScrollLock-BS4-kr7P.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-Dp3KMRQG.js → vendor-codemirror-language-data-Bg4AEeCr.js} +2 -2
- package/dist/assets/vendor-zod-BPAANqvH.js +39 -0
- package/dist/assets/{web-components-_mNEMjFY.js → web-components-z7hfnS8n.js} +1 -1
- package/dist/assets/zh-CN-T4AXofZI.js +16 -0
- package/dist/assets/zh-TW-Dte40sxp.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 +2 -3
- package/server/index.d.ts +15 -0
- package/server/index.js +45 -9
- package/server/lib/data-dir-migration.js +40 -0
- package/server/lib/data-dir-migration.test.js +59 -0
- package/server/lib/event-stream/runtime.test.js +12 -12
- package/server/lib/fs/DOCUMENTATION.md +3 -0
- package/server/lib/fs/routes.js +31 -0
- package/server/lib/fs/routes.test.js +110 -1
- package/server/lib/git/DOCUMENTATION.md +29 -5
- package/server/lib/git/routes.js +24 -4
- package/server/lib/git/service.js +184 -198
- package/server/lib/git/service.test.js +422 -25
- package/server/lib/inherited-env.js +16 -9
- package/server/lib/inherited-env.test.js +8 -9
- package/server/lib/message-queue/DOCUMENTATION.md +4 -1
- package/server/lib/network-defaults.js +23 -0
- package/server/lib/network-defaults.test.js +52 -0
- package/server/lib/opencode/DOCUMENTATION.md +17 -1
- package/server/lib/opencode/bootstrap-runtime.js +2 -0
- package/server/lib/opencode/feature-routes-runtime.js +2 -0
- package/server/lib/opencode/openchamber-routes.js +64 -4
- package/server/lib/opencode/openchamber-routes.test.js +128 -1
- package/server/lib/opencode/routes.js +5 -3
- package/server/lib/opencode/settings-files.js +214 -0
- package/server/lib/opencode/settings-helpers.js +85 -41
- package/server/lib/opencode/settings-helpers.test.js +166 -1
- package/server/lib/opencode/settings-registry.json +747 -0
- package/server/lib/opencode/settings-runtime.js +139 -18
- package/server/lib/opencode/settings-runtime.test.js +163 -5
- 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 +31 -1
- 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/index.js +7 -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 +1 -1
- package/server/lib/quota/providers/opencode-go.test.js +26 -1
- package/server/lib/quota/providers/openrouter.js +119 -24
- package/server/lib/quota/providers/openrouter.test.js +330 -0
- package/server/lib/relay/DOCUMENTATION.md +55 -0
- 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/tunnel-codec.js +9 -0
- package/server/lib/relay/tunnel-host.js +66 -17
- package/server/lib/session-assist/runtime.js +6 -10
- package/server/lib/session-goal/runtime.js +4 -9
- package/server/lib/small-model/DOCUMENTATION.md +3 -2
- package/server/lib/small-model/call.js +9 -4
- package/server/lib/small-model/call.test.js +29 -0
- package/server/lib/small-model/index.js +5 -10
- package/server/lib/small-model/runtime-providers.js +15 -7
- package/server/lib/small-model/runtime-providers.test.js +7 -1
- package/server/lib/terminal/DOCUMENTATION.md +4 -4
- package/server/lib/terminal/runtime.js +11 -6
- package/server/lib/terminal/runtime.test.js +20 -13
- package/server/lib/walkthrough/DOCUMENTATION.md +33 -14
- package/server/lib/walkthrough/model-settings.js +8 -12
- package/server/lib/walkthrough/prompt.js +3 -1
- package/server/lib/walkthrough/sources.js +23 -2
- package/dist/assets/DiffView-hDBKesA_.js +0 -4
- package/dist/assets/FilesView-CdWzeOp9.js +0 -17
- package/dist/assets/MarkdownImageGallery-Czv_mhvS.js +0 -1
- package/dist/assets/MarkdownRendererImpl-BrMcswiy.js +0 -12
- package/dist/assets/OnboardingScreen-ChxQoAkx.js +0 -1
- package/dist/assets/PierreDiffViewer-BchBtZlp.js +0 -157
- package/dist/assets/SettingsView-BKf5xBsP.js +0 -28
- package/dist/assets/WalkthroughView-DOeCsMju.js +0 -4
- package/dist/assets/contextPanelEmbeddedChat-C0ZLAepL.js +0 -1
- package/dist/assets/de-B5JaA3SA.js +0 -10
- package/dist/assets/es-EPIH88ii.js +0 -16
- package/dist/assets/fr-CAMfxUdZ.js +0 -14
- package/dist/assets/index-DF5kMP-i.js +0 -39
- package/dist/assets/index-Dl3xbAB_.css +0 -1
- package/dist/assets/ja-CifOE1VN.js +0 -16
- package/dist/assets/ko-BBDyAyYQ.js +0 -16
- package/dist/assets/main-MhSaOZoB.js +0 -930
- package/dist/assets/markdownCore-BrBwOOMn.js +0 -3
- package/dist/assets/patchFileDiff-o8ms1me8.js +0 -10
- package/dist/assets/pl-DgEHYfFM.js +0 -16
- package/dist/assets/pt-BR-DuWHojUw.js +0 -16
- package/dist/assets/renderElectronMiniChatApp-CDgBwwCS.js +0 -2
- package/dist/assets/renderMobileApp-DJIKIK1r.js +0 -25
- package/dist/assets/runtimeEndpointReset-CYJ9O_xX.js +0 -17
- package/dist/assets/scheduledTasksApi-D8yamh1b.js +0 -1
- package/dist/assets/tr-D5m2yIWL.js +0 -7
- package/dist/assets/uk-D_q60oog.js +0 -16
- package/dist/assets/useInlineCommentController-Brz_cLcQ.js +0 -2
- package/dist/assets/useRootScrollLock-lXEnWlaU.js +0 -1
- package/dist/assets/vendor-ghostty-web-CrxinGGH.js +0 -13
- package/dist/assets/vendor-zod-CK21ihK-.js +0 -39
- package/dist/assets/zh-CN-D8QvsVeg.js +0 -16
- package/dist/assets/zh-TW-B9t7rQQO.js +0 -16
|
@@ -1,4 +1,32 @@
|
|
|
1
|
+
import { createRequire } from 'node:module';
|
|
2
|
+
|
|
1
3
|
import { isAgentMemoryFeatureAvailable } from '../agent-memory/feature-flag.js';
|
|
4
|
+
|
|
5
|
+
// Generated from packages/ui/src/lib/settings/registry.ts by
|
|
6
|
+
// `bun run settings-registry:generate`; `registry.test.ts` fails when stale.
|
|
7
|
+
// The server is plain ESM without a bundler, so the snapshot is read with
|
|
8
|
+
// `createRequire` (import attributes differ across the Node versions we run on).
|
|
9
|
+
const settingsRegistry = createRequire(import.meta.url)('./settings-registry.json');
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Whether a client may persist this key through PUT /api/config/settings:
|
|
13
|
+
* it must be a registry key, not a server-computed flag, not a device field
|
|
14
|
+
* that only lives in the browser, and not one the desktop shell writes itself.
|
|
15
|
+
*/
|
|
16
|
+
const isPersistableSettingsKey = (key) => {
|
|
17
|
+
const field = settingsRegistry.fields[key];
|
|
18
|
+
if (!field) return false;
|
|
19
|
+
if (field.computed || field.local) return false;
|
|
20
|
+
if (field.owner === 'desktop-shell') return false;
|
|
21
|
+
return true;
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
/** Keys accepted on write but never returned by a read. */
|
|
25
|
+
const SECRET_SETTINGS_KEYS = Object.freeze(
|
|
26
|
+
Object.entries(settingsRegistry.fields)
|
|
27
|
+
.filter(([, field]) => field.secret === true)
|
|
28
|
+
.map(([key]) => key),
|
|
29
|
+
);
|
|
2
30
|
import {
|
|
3
31
|
DEFAULT_INPUT_HISTORY_LIMIT,
|
|
4
32
|
DEFAULT_INPUT_HISTORY_SCOPE,
|
|
@@ -18,7 +46,6 @@ export const createSettingsHelpers = (dependencies) => {
|
|
|
18
46
|
normalizeManagedRemoteTunnelHostname,
|
|
19
47
|
normalizeManagedRemoteTunnelPresets,
|
|
20
48
|
normalizeManagedRemoteTunnelPresetTokens,
|
|
21
|
-
sanitizeTypographySizesPartial,
|
|
22
49
|
normalizeStringArray,
|
|
23
50
|
sanitizeModelRefs,
|
|
24
51
|
sanitizeSkillCatalogs,
|
|
@@ -203,6 +230,9 @@ export const createSettingsHelpers = (dependencies) => {
|
|
|
203
230
|
...new Set(candidate.workStatusHiddenSections.filter((entry) => typeof entry === 'string' && entry.length > 0)),
|
|
204
231
|
];
|
|
205
232
|
}
|
|
233
|
+
if (typeof candidate.workStatusHiddenSectionsExplicit === 'boolean') {
|
|
234
|
+
result.workStatusHiddenSectionsExplicit = candidate.workStatusHiddenSectionsExplicit;
|
|
235
|
+
}
|
|
206
236
|
if (typeof candidate.desktopLanAccessEnabled === 'boolean') {
|
|
207
237
|
result.desktopLanAccessEnabled = candidate.desktopLanAccessEnabled;
|
|
208
238
|
}
|
|
@@ -313,9 +343,6 @@ export const createSettingsHelpers = (dependencies) => {
|
|
|
313
343
|
if (typeof candidate.monoFont === 'string' && candidate.monoFont.length > 0) {
|
|
314
344
|
result.monoFont = candidate.monoFont;
|
|
315
345
|
}
|
|
316
|
-
if (typeof candidate.markdownDisplayMode === 'string' && candidate.markdownDisplayMode.length > 0) {
|
|
317
|
-
result.markdownDisplayMode = candidate.markdownDisplayMode;
|
|
318
|
-
}
|
|
319
346
|
if (typeof candidate.githubClientId === 'string') {
|
|
320
347
|
const trimmed = candidate.githubClientId.trim();
|
|
321
348
|
if (trimmed.length > 0) {
|
|
@@ -331,6 +358,45 @@ export const createSettingsHelpers = (dependencies) => {
|
|
|
331
358
|
if (typeof candidate.showReasoningTraces === 'boolean') {
|
|
332
359
|
result.showReasoningTraces = candidate.showReasoningTraces;
|
|
333
360
|
}
|
|
361
|
+
if (typeof candidate.streamingAutoFollowEnabled === 'boolean') {
|
|
362
|
+
result.streamingAutoFollowEnabled = candidate.streamingAutoFollowEnabled;
|
|
363
|
+
}
|
|
364
|
+
if (typeof candidate.codeBlockLineWrap === 'boolean') {
|
|
365
|
+
result.codeBlockLineWrap = candidate.codeBlockLineWrap;
|
|
366
|
+
}
|
|
367
|
+
if (typeof candidate.autoSaveEnabled === 'boolean') {
|
|
368
|
+
result.autoSaveEnabled = candidate.autoSaveEnabled;
|
|
369
|
+
}
|
|
370
|
+
if (typeof candidate.diffWrapLines === 'boolean') {
|
|
371
|
+
result.diffWrapLines = candidate.diffWrapLines;
|
|
372
|
+
}
|
|
373
|
+
if (typeof candidate.persistChatDraft === 'boolean') {
|
|
374
|
+
result.persistChatDraft = candidate.persistChatDraft;
|
|
375
|
+
}
|
|
376
|
+
if (typeof candidate.allowPromptingSubagentSessions === 'boolean') {
|
|
377
|
+
result.allowPromptingSubagentSessions = candidate.allowPromptingSubagentSessions;
|
|
378
|
+
}
|
|
379
|
+
if (typeof candidate.showOpenCodeRestartConfirm === 'boolean') {
|
|
380
|
+
result.showOpenCodeRestartConfirm = candidate.showOpenCodeRestartConfirm;
|
|
381
|
+
}
|
|
382
|
+
if (typeof candidate.sessionTabsEnabled === 'boolean') {
|
|
383
|
+
result.sessionTabsEnabled = candidate.sessionTabsEnabled;
|
|
384
|
+
}
|
|
385
|
+
if (typeof candidate.largeTextPasteBehavior === 'string') {
|
|
386
|
+
const mode = candidate.largeTextPasteBehavior.trim();
|
|
387
|
+
if (mode === 'ask' || mode === 'attach' || mode === 'inline') {
|
|
388
|
+
result.largeTextPasteBehavior = mode;
|
|
389
|
+
}
|
|
390
|
+
}
|
|
391
|
+
if (typeof candidate.fileEditorKeymap === 'string') {
|
|
392
|
+
const mode = candidate.fileEditorKeymap.trim();
|
|
393
|
+
if (mode === 'default' || mode === 'vim') {
|
|
394
|
+
result.fileEditorKeymap = mode;
|
|
395
|
+
}
|
|
396
|
+
}
|
|
397
|
+
if (Array.isArray(candidate.providerOrder)) {
|
|
398
|
+
result.providerOrder = normalizeStringArray(candidate.providerOrder);
|
|
399
|
+
}
|
|
334
400
|
if (typeof candidate.sessionRecapEnabled === 'boolean') {
|
|
335
401
|
result.sessionRecapEnabled = candidate.sessionRecapEnabled;
|
|
336
402
|
}
|
|
@@ -452,11 +518,6 @@ export const createSettingsHelpers = (dependencies) => {
|
|
|
452
518
|
result.managedRemoteTunnelSelectedPresetId = id || undefined;
|
|
453
519
|
}
|
|
454
520
|
|
|
455
|
-
const typography = sanitizeTypographySizesPartial(candidate.typographySizes);
|
|
456
|
-
if (typography) {
|
|
457
|
-
result.typographySizes = typography;
|
|
458
|
-
}
|
|
459
|
-
|
|
460
521
|
if (typeof candidate.defaultModel === 'string') {
|
|
461
522
|
const trimmed = candidate.defaultModel.trim();
|
|
462
523
|
result.defaultModel = trimmed.length > 0 ? trimmed : undefined;
|
|
@@ -502,14 +563,6 @@ export const createSettingsHelpers = (dependencies) => {
|
|
|
502
563
|
const trimmed = candidate.zenModel.trim();
|
|
503
564
|
result.zenModel = trimmed.length > 0 ? trimmed : undefined;
|
|
504
565
|
}
|
|
505
|
-
if (typeof candidate.gitProviderId === 'string') {
|
|
506
|
-
const trimmed = candidate.gitProviderId.trim();
|
|
507
|
-
result.gitProviderId = trimmed.length > 0 ? trimmed : undefined;
|
|
508
|
-
}
|
|
509
|
-
if (typeof candidate.gitModelId === 'string') {
|
|
510
|
-
const trimmed = candidate.gitModelId.trim();
|
|
511
|
-
result.gitModelId = trimmed.length > 0 ? trimmed : undefined;
|
|
512
|
-
}
|
|
513
566
|
if (typeof candidate.pwaAppName === 'string') {
|
|
514
567
|
result.pwaAppName = normalizePwaAppName(candidate.pwaAppName, undefined);
|
|
515
568
|
}
|
|
@@ -522,12 +575,6 @@ export const createSettingsHelpers = (dependencies) => {
|
|
|
522
575
|
result.mobileKeyboardMode = mode;
|
|
523
576
|
}
|
|
524
577
|
}
|
|
525
|
-
if (typeof candidate.toolCallExpansion === 'string') {
|
|
526
|
-
const mode = candidate.toolCallExpansion.trim();
|
|
527
|
-
if (mode === 'collapsed' || mode === 'activity' || mode === 'detailed' || mode === 'changes') {
|
|
528
|
-
result.toolCallExpansion = mode;
|
|
529
|
-
}
|
|
530
|
-
}
|
|
531
578
|
if (typeof candidate.inputSpellcheckEnabled === 'boolean') {
|
|
532
579
|
result.inputSpellcheckEnabled = candidate.inputSpellcheckEnabled;
|
|
533
580
|
}
|
|
@@ -619,9 +666,6 @@ export const createSettingsHelpers = (dependencies) => {
|
|
|
619
666
|
if (typeof candidate.promptNavigatorEnabled === 'boolean') {
|
|
620
667
|
result.promptNavigatorEnabled = candidate.promptNavigatorEnabled;
|
|
621
668
|
}
|
|
622
|
-
if (typeof candidate.expandedEditorToolbar === 'boolean') {
|
|
623
|
-
result.expandedEditorToolbar = candidate.expandedEditorToolbar;
|
|
624
|
-
}
|
|
625
669
|
if (typeof candidate.wideChatLayoutEnabled === 'boolean') {
|
|
626
670
|
result.wideChatLayoutEnabled = candidate.wideChatLayoutEnabled;
|
|
627
671
|
}
|
|
@@ -724,11 +768,6 @@ export const createSettingsHelpers = (dependencies) => {
|
|
|
724
768
|
}
|
|
725
769
|
}
|
|
726
770
|
|
|
727
|
-
// Message limit — single setting for fetch / trim / Load More chunk
|
|
728
|
-
if (typeof candidate.messageLimit === 'number' && Number.isFinite(candidate.messageLimit)) {
|
|
729
|
-
result.messageLimit = Math.max(10, Math.min(500, Math.round(candidate.messageLimit)));
|
|
730
|
-
}
|
|
731
|
-
|
|
732
771
|
const skillCatalogs = sanitizeSkillCatalogs(candidate.skillCatalogs);
|
|
733
772
|
if (skillCatalogs) {
|
|
734
773
|
result.skillCatalogs = skillCatalogs;
|
|
@@ -912,6 +951,16 @@ export const createSettingsHelpers = (dependencies) => {
|
|
|
912
951
|
}
|
|
913
952
|
}
|
|
914
953
|
|
|
954
|
+
// The registry is the last word on what a client may persist: a key the
|
|
955
|
+
// code above still names but the registry no longer lists is dropped here,
|
|
956
|
+
// so the two cannot drift apart silently (settings-helpers.test.js checks
|
|
957
|
+
// the other direction).
|
|
958
|
+
for (const key of Object.keys(result)) {
|
|
959
|
+
if (!isPersistableSettingsKey(key)) {
|
|
960
|
+
delete result[key];
|
|
961
|
+
}
|
|
962
|
+
}
|
|
963
|
+
|
|
915
964
|
return result;
|
|
916
965
|
};
|
|
917
966
|
|
|
@@ -922,13 +971,6 @@ export const createSettingsHelpers = (dependencies) => {
|
|
|
922
971
|
? current.securityScopedBookmarks
|
|
923
972
|
: [];
|
|
924
973
|
|
|
925
|
-
const nextTypographySizes = changes.typographySizes
|
|
926
|
-
? {
|
|
927
|
-
...(current.typographySizes || {}),
|
|
928
|
-
...changes.typographySizes
|
|
929
|
-
}
|
|
930
|
-
: current.typographySizes;
|
|
931
|
-
|
|
932
974
|
const next = {
|
|
933
975
|
...current,
|
|
934
976
|
...changes,
|
|
@@ -937,7 +979,6 @@ export const createSettingsHelpers = (dependencies) => {
|
|
|
937
979
|
baseBookmarks.filter((entry) => typeof entry === 'string' && entry.length > 0)
|
|
938
980
|
)
|
|
939
981
|
),
|
|
940
|
-
typographySizes: nextTypographySizes
|
|
941
982
|
};
|
|
942
983
|
|
|
943
984
|
return next;
|
|
@@ -945,9 +986,12 @@ export const createSettingsHelpers = (dependencies) => {
|
|
|
945
986
|
|
|
946
987
|
const formatSettingsResponse = (settings) => {
|
|
947
988
|
const sanitized = sanitizeSettingsUpdate(settings);
|
|
948
|
-
|
|
989
|
+
for (const key of SECRET_SETTINGS_KEYS) {
|
|
990
|
+
delete sanitized[key];
|
|
991
|
+
}
|
|
949
992
|
const bookmarks = normalizeStringArray(settings.securityScopedBookmarks);
|
|
950
993
|
const hasManagedRemoteTunnelToken = typeof settings?.managedRemoteTunnelToken === 'string' && settings.managedRemoteTunnelToken.trim().length > 0;
|
|
994
|
+
const hasDesktopUiPassword = typeof settings?.desktopUiPassword === 'string' && settings.desktopUiPassword.trim().length > 0;
|
|
951
995
|
const pwaAppName = normalizePwaAppName(settings?.pwaAppName, '');
|
|
952
996
|
const pwaOrientation = normalizePwaOrientation(settings?.pwaOrientation, 'system');
|
|
953
997
|
const mobileKeyboardMode = normalizeMobileKeyboardMode(settings?.mobileKeyboardMode, 'native');
|
|
@@ -957,6 +1001,7 @@ export const createSettingsHelpers = (dependencies) => {
|
|
|
957
1001
|
return {
|
|
958
1002
|
...sanitized,
|
|
959
1003
|
hasManagedRemoteTunnelToken,
|
|
1004
|
+
hasDesktopUiPassword,
|
|
960
1005
|
// Tells the client whether agent memory exists in this build at all, so
|
|
961
1006
|
// its settings row and panel tab can be absent rather than merely off.
|
|
962
1007
|
agentMemoryFeatureAvailable: isAgentMemoryFeatureAvailable(),
|
|
@@ -967,7 +1012,6 @@ export const createSettingsHelpers = (dependencies) => {
|
|
|
967
1012
|
inputHistoryLimit,
|
|
968
1013
|
securityScopedBookmarks: bookmarks,
|
|
969
1014
|
pinnedDirectories: normalizeStringArray(settings.pinnedDirectories),
|
|
970
|
-
typographySizes: sanitizeTypographySizesPartial(settings.typographySizes),
|
|
971
1015
|
...(process.env.AIWORKER_RUNTIME === 'desktop'
|
|
972
1016
|
? {
|
|
973
1017
|
desktopLanAccessActive: process.env.AIWORKER_DESKTOP_LAN_ACCESS_ACTIVE === 'true',
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { execFileSync } from 'node:child_process';
|
|
2
|
-
import { mkdirSync, mkdtempSync, readdirSync, rmSync } from 'node:fs';
|
|
2
|
+
import { mkdirSync, mkdtempSync, readdirSync, readFileSync, rmSync } from 'node:fs';
|
|
3
3
|
import { tmpdir } from 'node:os';
|
|
4
4
|
import { dirname, join } from 'node:path';
|
|
5
5
|
import { fileURLToPath, pathToFileURL } from 'node:url';
|
|
@@ -69,6 +69,19 @@ const createTestHelpersWithRealSanitizers = () => {
|
|
|
69
69
|
};
|
|
70
70
|
|
|
71
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
|
+
});
|
|
72
85
|
it('imports from the packed @aiworker/web tarball without escaping the published package', async () => {
|
|
73
86
|
const tempRoot = mkdtempSync(join(tmpdir(), 'settings-helpers-pack-'));
|
|
74
87
|
const packDir = join(tempRoot, 'pack');
|
|
@@ -711,3 +724,155 @@ describe('settings helpers', () => {
|
|
|
711
724
|
});
|
|
712
725
|
});
|
|
713
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: 'AiWorker', 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
|
+
});
|