@vantran-se/openchamber-web 2.0.3 → 2.0.5
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 +20 -28
- package/bin/cli.js +3 -1
- package/bin/cli.test.js +13 -4
- package/bin/lib/DOCUMENTATION.md +4 -1
- package/bin/lib/commands-serve.js +9 -0
- package/dist/assets/{CodeMirrorCommentWidgets-CWhdh0wf.js → CodeMirrorCommentWidgets-DCAQTLMM.js} +1 -1
- package/dist/assets/DiffView-uzNpHBlV.js +4 -0
- package/dist/assets/FilesView-Bkq0sjNx.js +17 -0
- package/dist/assets/{GitView-DKIjpnu4.js → GitView-BmibZC7n.js} +4 -4
- package/dist/assets/{GuestAttachDialog-BIa9jCfL.js → GuestAttachDialog-D7TJG1rt.js} +1 -1
- package/dist/assets/GuestSurfacePane-PAcYaFzP.js +1 -0
- package/dist/assets/{JsonTreeView-CtI1Mqwo.js → JsonTreeView-BNT2yUVu.js} +1 -1
- package/dist/assets/{LinearIssuesView-BD2zLu-r.js → LinearIssuesView-D3Azksju.js} +1 -1
- package/dist/assets/MarkdownImageGallery-BGY6tuiK.js +1 -0
- package/dist/assets/MarkdownRendererImpl-DJmhQ37f.js +12 -0
- package/dist/assets/{OnboardingScreen-B8TxloXZ.js → OnboardingScreen-D_41H2qC.js} +1 -1
- package/dist/assets/{PatchDiff--Civs1xY.js → PatchDiff-C0RSqa9W.js} +1 -1
- package/dist/assets/{PierreDiffViewer-BlDmw3-A.js → PierreDiffViewer-BBE7tTY2.js} +3 -3
- package/dist/assets/{PlanView-DyH2SyCP.js → PlanView-Bqc-xuDe.js} +4 -4
- package/dist/assets/PluginPane-DQ0k41Q-.js +3 -0
- package/dist/assets/SettingsView-BAiJU5hJ.js +30 -0
- package/dist/assets/{SettingsWindow-y9WpZqK6.js → SettingsWindow-DuC4ecLw.js} +1 -1
- package/dist/assets/{SubmoduleDiffSummary-DeFmhPFZ.js → SubmoduleDiffSummary-0HgmcMXz.js} +1 -1
- package/dist/assets/{ToolOutputDialog-8DmF7_PZ.js → ToolOutputDialog-DdHkQJEE.js} +6 -6
- package/dist/assets/{ToolPartDiffPreview-Ca15DKE1.js → ToolPartDiffPreview-DWdN0r5K.js} +2 -2
- package/dist/assets/{VirtualizedFileDiff-BfEBad09.js → VirtualizedFileDiff-ClDezf2d.js} +1 -1
- package/dist/assets/{WalkthroughView-Cj3K8Xz6.js → WalkthroughView-BJicp0RC.js} +2 -2
- package/dist/assets/{appThemeRegistry-DAdawJZn.js → appThemeRegistry-CiuyzfSW.js} +1 -1
- package/dist/assets/changesTree-CePHbFcj.js +1 -0
- package/dist/assets/commitAndPush-DTKb6IwT.js +1 -0
- package/dist/assets/{computeVirtualFileMetrics-RF-KjfUH.js → computeVirtualFileMetrics-kM_zJNMt.js} +2 -2
- package/dist/assets/de-BMnfXm-G.js +10 -0
- package/dist/assets/es-DKEvCCL6.js +16 -0
- package/dist/assets/fr-D9fgE2xE.js +14 -0
- package/dist/assets/index-DUke1nme.css +1 -0
- package/dist/assets/{index-CL7ZQ94-.js → index-DWB8bR_g.js} +1 -1
- package/dist/assets/index-Di6zUe5h.js +41 -0
- package/dist/assets/{isDiffPlainText-D2yJ9fO6.js → isDiffPlainText-DOoIsGqs.js} +1 -1
- package/dist/assets/{isFilePlainText-BwMEq1Ey.js → isFilePlainText-bt5VHvcd.js} +1 -1
- package/dist/assets/ja-BEXad-KW.js +16 -0
- package/dist/assets/ko-DGrQ0lEe.js +16 -0
- package/dist/assets/{main-quLSA7jc.js → main-BpLQbZC2.js} +2 -2
- package/dist/assets/main-CI9DtHJv.js +896 -0
- package/dist/assets/{markdown-shiki.worker-CahLvHIq.js → markdown-shiki.worker-Bg8SoAJJ.js} +8 -5
- package/dist/assets/markdownCore-DK2KK5RW.js +7 -0
- package/dist/assets/{miniChat-D-lJJibr.js → miniChat-CIWaR8wG.js} +2 -2
- package/dist/assets/{mobile-VTtphbW6.js → mobile-CnWKEYIP.js} +2 -2
- package/dist/assets/patchFileDiff-Cqn0C0az.js +13 -0
- package/dist/assets/pl-BlhkmR5b.js +16 -0
- package/dist/assets/pt-BR-Bs96Ew9I.js +16 -0
- package/dist/assets/{renderElectronMiniChatApp-DF7ch5eM.js → renderElectronMiniChatApp-D9t0-9nV.js} +2 -2
- package/dist/assets/renderMobileApp-CW7fVs_s.js +25 -0
- package/dist/assets/runtimeEndpointReset-D0PcCb3K.js +27 -0
- package/dist/assets/scheduledTasksApi-Dahv3ws0.js +1 -0
- package/dist/assets/{shikiHighlight-_HeCCHx1.js → shikiHighlight-l-MbaNS4.js} +1 -1
- package/dist/assets/{textMateThemeFromAppTheme-C95XdFGj.js → textMateThemeFromAppTheme-C-kGcOpD.js} +1 -1
- package/dist/assets/tr-CehUK1M9.js +7 -0
- package/dist/assets/uk-Bno5WGNz.js +16 -0
- package/dist/assets/{useAppFontEffects-ZM9ij19-.js → useAppFontEffects-DDjuwVqi.js} +160 -156
- package/dist/assets/{useInlineCommentController-CkfxMJPy.js → useInlineCommentController-Clg8EO3A.js} +2 -2
- package/dist/assets/{useRootScrollLock-BtiAC1fj.js → useRootScrollLock-nDcRrgJ1.js} +2 -2
- package/dist/assets/{vendor-codemirror-language-data-U-lXzXNK.js → vendor-codemirror-language-data-oKF0MAwd.js} +2 -2
- package/dist/assets/vendor-opencode-client-DSZl7jSi.js +12 -0
- package/dist/assets/vendor-zod-BSoePkpP.js +39 -0
- package/dist/assets/{web-components-C583Pmfm.js → web-components-Bc1WPd4i.js} +1 -1
- package/dist/assets/zh-CN-DnAweIWo.js +16 -0
- package/dist/assets/zh-TW-BbBBK25p.js +16 -0
- package/dist/index.html +5 -5
- package/dist/mini-chat.html +5 -5
- package/dist/mobile.html +5 -5
- package/package.json +3 -3
- package/server/index.d.ts +2 -0
- package/server/index.js +196 -52
- package/server/lib/agent-tool/DOCUMENTATION.md +5 -0
- package/server/lib/agent-tool/runtime.js +89 -51
- package/server/lib/agent-tool/runtime.test.js +17 -0
- package/server/lib/browser-control/DOCUMENTATION.md +14 -2
- package/server/lib/event-stream/DOCUMENTATION.md +2 -2
- package/server/lib/event-stream/global-hub.js +45 -7
- package/server/lib/event-stream/global-hub.test.js +128 -2
- package/server/lib/event-stream/global-ws-bridge.js +3 -1
- package/server/lib/event-stream/rebind.test.js +1 -0
- package/server/lib/event-stream/runtime.js +1 -2
- package/server/lib/event-stream/upstream-reader.js +3 -2
- package/server/lib/git/DOCUMENTATION.md +1 -1
- package/server/lib/git/service.js +22 -0
- package/server/lib/git/service.test.js +8 -0
- package/server/lib/guests/DOCUMENTATION.md +4 -3
- package/server/lib/guests/catalog.js +19 -1
- package/server/lib/guests/catalog.test.js +22 -0
- package/server/lib/guests/html-styles.js +5 -3
- package/server/lib/guests/html-styles.test.js +3 -1
- package/server/lib/guests/routes.js +11 -3
- package/server/lib/guests/service.js +5 -0
- package/server/lib/guests/surface.js +55 -9
- package/server/lib/guests/surface.test.js +56 -6
- package/server/lib/notifications/DOCUMENTATION.md +10 -0
- package/server/lib/notifications/emit-route.js +118 -0
- package/server/lib/notifications/emit-route.test.js +112 -0
- package/server/lib/openchamber-control/actions.js +16 -2
- package/server/lib/openchamber-control/service.js +23 -0
- package/server/lib/openchamber-control/service.test.js +31 -0
- package/server/lib/openchamber-sessions/fork-inheritance.js +103 -0
- package/server/lib/openchamber-sessions/fork-inheritance.test.js +69 -0
- package/server/lib/openchamber-sessions/routes.js +11 -0
- package/server/lib/openchamber-sessions/routes.test.js +18 -0
- package/server/lib/opencode/DOCUMENTATION.md +23 -23
- package/server/lib/opencode/bootstrap-runtime.js +14 -1
- package/server/lib/opencode/compatibility.js +6 -3
- package/server/lib/opencode/compatibility.test.js +8 -0
- package/server/lib/opencode/config-entity-v2.test.js +27 -0
- package/server/lib/opencode/config-paths.test.js +12 -0
- package/server/lib/opencode/config-v2.d.ts +2 -0
- package/server/lib/opencode/config-v2.js +42 -0
- package/server/lib/opencode/config-v2.test.js +17 -0
- package/server/lib/opencode/core-routes.js +20 -4
- package/server/lib/opencode/core-routes.test.js +41 -0
- package/server/lib/opencode/env-runtime.js +1 -8
- package/server/lib/opencode/feature-routes-runtime.js +2 -1
- package/server/lib/opencode/lifecycle.js +156 -2
- package/server/lib/opencode/lifecycle.test.js +255 -1
- package/server/lib/opencode/managed-config-file.js +18 -6
- package/server/lib/opencode/managed-config-file.test.js +23 -7
- package/server/lib/opencode/mcp.js +7 -2
- package/server/lib/opencode/network-runtime.js +16 -7
- package/server/lib/opencode/network-runtime.test.js +29 -1
- package/server/lib/opencode/npm-registry-config.d.ts +1 -0
- package/server/lib/opencode/npm-registry-config.js +123 -0
- package/server/lib/opencode/npm-registry-config.test.js +167 -0
- package/server/lib/opencode/npm-registry.js +5 -6
- package/server/lib/opencode/npm-registry.test.js +30 -0
- package/server/lib/opencode/openchamber-routes.js +29 -11
- package/server/lib/opencode/openchamber-routes.test.js +18 -0
- package/server/lib/opencode/project-directory-runtime.js +8 -0
- package/server/lib/opencode/project-directory-runtime.test.js +13 -0
- package/server/lib/opencode/provider-source-route.test.js +59 -0
- package/server/lib/opencode/providers.js +3 -0
- package/server/lib/opencode/providers.test.js +62 -0
- package/server/lib/opencode/proxy.js +53 -16
- package/server/lib/opencode/routes-upgrade.test.js +1 -1
- package/server/lib/opencode/routes.js +36 -1
- package/server/lib/opencode/server-startup-runtime.js +25 -0
- package/server/lib/opencode/server-startup-runtime.test.js +67 -1
- package/server/lib/opencode/server-utils-runtime.js +5 -0
- package/server/lib/opencode/settings-helpers.js +10 -0
- package/server/lib/opencode/settings-helpers.test.js +2 -2
- package/server/lib/opencode/settings-registry.json +17 -0
- package/server/lib/opencode/settings-runtime.js +1 -0
- package/server/lib/opencode/shared-service-runtime.js +155 -0
- package/server/lib/opencode/shared-service-runtime.test.js +180 -0
- package/server/lib/opencode/shared.js +49 -4
- package/server/lib/opencode/shared.test.js +78 -1
- package/server/lib/opencode/shutdown-runtime.js +14 -5
- package/server/lib/opencode/shutdown-runtime.test.js +77 -2
- package/server/lib/opencode/skill-routes.js +28 -12
- package/server/lib/opencode/skill-routes.test.js +121 -1
- package/server/lib/opencode/startup-pipeline-runtime.js +5 -4
- package/server/lib/opencode/startup-pipeline-runtime.test.js +18 -7
- package/server/lib/opencode/upgrade-capability.js +0 -14
- package/server/lib/opencode/v1-migration-topup.js +21 -15
- package/server/lib/opencode/v1-migration-topup.test.js +14 -6
- package/server/lib/opencode/v2-install.d.ts +1 -0
- package/server/lib/opencode/v2-install.js +8 -7
- package/server/lib/opencode/v2-install.test.js +13 -4
- package/server/lib/opencode/watcher.js +3 -1
- package/server/lib/opencode/watcher.test.js +54 -0
- package/server/lib/opencode/websearch-config.js +13 -1
- package/server/lib/quota/providers/kimi.js +1 -1
- package/server/lib/realtime-proxy.js +5 -1
- package/server/lib/realtime-proxy.test.js +29 -0
- package/server/lib/relay/tunnel-host.js +4 -1
- package/server/lib/relay/tunnel-host.test.js +11 -0
- package/server/lib/session-assist/DOCUMENTATION.md +17 -5
- package/server/lib/session-assist/runtime.js +19 -2
- package/server/lib/session-assist/runtime.test.js +8 -0
- package/server/lib/session-goal/DOCUMENTATION.md +11 -0
- package/server/lib/small-model/DOCUMENTATION.md +11 -10
- package/server/lib/small-model/index.js +27 -32
- package/server/lib/small-model/index.test.js +28 -16
- package/server/lib/spaces/DOCUMENTATION.md +404 -32
- package/server/lib/spaces/code-in-bait.js +145 -6
- package/server/lib/spaces/code-in.js +10 -150
- package/server/lib/spaces/code-in.test.js +7 -83
- package/server/lib/spaces/code-out.js +1819 -0
- package/server/lib/spaces/code-out.test.js +3945 -0
- package/server/lib/spaces/code-transfer.js +173 -0
- package/server/lib/spaces/command-stream.test.js +181 -0
- package/server/lib/spaces/dispatcher.js +670 -0
- package/server/lib/spaces/dispatcher.test.js +582 -0
- package/server/lib/spaces/gatekeeper-channel.js +61 -7
- package/server/lib/spaces/gatekeeper-channel.test.js +43 -7
- package/server/lib/spaces/gatekeeper-program.cjs +68 -14
- package/server/lib/spaces/gatekeeper-program.test.js +118 -3
- package/server/lib/spaces/hardening.js +20 -4
- package/server/lib/spaces/hardening.test.js +18 -3
- package/server/lib/spaces/host-git.js +7 -4
- package/server/lib/spaces/host-git.test.js +6 -2
- package/server/lib/spaces/host.js +310 -0
- package/server/lib/spaces/host.test.js +273 -0
- package/server/lib/spaces/journey.js +552 -0
- package/server/lib/spaces/journey.test.js +589 -0
- package/server/lib/spaces/layout.js +42 -8
- package/server/lib/spaces/manager.js +4 -3
- package/server/lib/spaces/manager.test.js +6 -0
- package/server/lib/spaces/places/code-out.docker.live.test.js +412 -0
- package/server/lib/spaces/places/contract-suite.js +36 -0
- package/server/lib/spaces/places/dispatcher.docker.live.test.js +296 -0
- package/server/lib/spaces/places/docker-live-support.js +27 -4
- package/server/lib/spaces/places/docker.js +33 -5
- package/server/lib/spaces/places/docker.test.js +42 -3
- package/server/lib/spaces/places/escape-suite.js +118 -14
- package/server/lib/spaces/places/fake-docker.js +3 -1
- package/server/lib/spaces/places/grants.docker.live.test.js +197 -0
- package/server/lib/spaces/places/memory-place.js +24 -1
- package/server/lib/spaces/places/registry.js +1 -1
- package/server/lib/spaces/places/server.docker.live.test.js +69 -33
- package/server/lib/spaces/places/sessions.docker.live.test.js +222 -0
- package/server/lib/spaces/routes.js +216 -0
- package/server/lib/spaces/routes.test.js +213 -0
- package/server/lib/spaces/run-command.js +261 -8
- package/server/lib/spaces/run-command.test.js +193 -3
- package/server/lib/spaces/space-events.js +153 -0
- package/server/lib/spaces/space-events.test.js +160 -0
- package/server/lib/spaces/space-opencode.js +52 -0
- package/server/lib/spaces/space-opencode.test.js +41 -0
- package/server/lib/spaces/space-records.js +150 -0
- package/server/lib/spaces/space-records.test.js +113 -0
- package/server/lib/spaces/space-sessions.js +210 -0
- package/server/lib/spaces/space-sessions.test.js +161 -0
- package/server/lib/spaces/websocket.js +329 -0
- package/server/lib/spaces/websocket.test.js +345 -0
- package/server/lib/ui-auth/ui-auth.js +8 -2
- package/server/lib/ui-auth/ui-auth.test.js +29 -0
- package/server/opencode-proxy.test.js +271 -0
- package/server/proxy-headers.js +8 -2
- package/server/proxy-headers.test.js +20 -0
- package/dist/assets/DiffView-CX3wAX2S.js +0 -4
- package/dist/assets/FilesView-OL3CON3r.js +0 -17
- package/dist/assets/GuestSurfacePane-B_cqX5I5.js +0 -1
- package/dist/assets/MarkdownImageGallery-DFiTFZgj.js +0 -1
- package/dist/assets/MarkdownRendererImpl-DZm5sf1W.js +0 -12
- package/dist/assets/PluginPane-BibJit86.js +0 -3
- package/dist/assets/SettingsView-h-VpVO47.js +0 -29
- package/dist/assets/commitAndPush-BIPfOMXY.js +0 -1
- package/dist/assets/de-BqCNGROu.js +0 -10
- package/dist/assets/es-BTPpTRgN.js +0 -16
- package/dist/assets/fr-hxbzMdAg.js +0 -14
- package/dist/assets/index-CLcM8HKx.css +0 -1
- package/dist/assets/index-CvLgnuNI.js +0 -41
- package/dist/assets/ja-_zLwwYiy.js +0 -16
- package/dist/assets/ko-CFn3Dzhc.js +0 -16
- package/dist/assets/main-BLJaug9I.js +0 -896
- package/dist/assets/markdownCore-DS9FdwrI.js +0 -5
- package/dist/assets/patchFileDiff-D9iBcTEi.js +0 -13
- package/dist/assets/pl-DFHE7xac.js +0 -16
- package/dist/assets/pt-BR-CUjZF179.js +0 -16
- package/dist/assets/renderMobileApp-CNQhdXsi.js +0 -25
- package/dist/assets/runtimeEndpointReset-DeaERc0M.js +0 -27
- package/dist/assets/scheduledTasksApi-BD6Vx9pu.js +0 -1
- package/dist/assets/tr-BB6sA_zH.js +0 -7
- package/dist/assets/uk-CB4o8g-o.js +0 -16
- package/dist/assets/vendor-opencode-client-_BAHQo4R.js +0 -12
- package/dist/assets/vendor-zod-Gea9uBlY.js +0 -39
- package/dist/assets/zh-CN-BRyDuWhc.js +0 -16
- package/dist/assets/zh-TW-BDGbWA1Q.js +0 -16
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import { describe, expect, it, vi } from 'vitest';
|
|
2
|
+
import express from 'express';
|
|
3
|
+
import request from 'supertest';
|
|
4
|
+
import { registerOpenCodeRoutes } from './routes.js';
|
|
5
|
+
|
|
6
|
+
const createApp = (getProviderSources) => {
|
|
7
|
+
const app = express();
|
|
8
|
+
app.use(express.json());
|
|
9
|
+
registerOpenCodeRoutes(app, {
|
|
10
|
+
resolveProjectDirectory: vi.fn(async () => ({ directory: '/projects/app' })),
|
|
11
|
+
getProviderSources,
|
|
12
|
+
});
|
|
13
|
+
return app;
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
describe('GET /api/provider/:providerId/source', () => {
|
|
17
|
+
it('returns the stored config entry next to the layer sources', async () => {
|
|
18
|
+
const stored = {
|
|
19
|
+
name: 'Stored',
|
|
20
|
+
package: 'aisdk:@ai-sdk/openai-compatible',
|
|
21
|
+
env: ['STORED_KEY'],
|
|
22
|
+
settings: { baseURL: 'https://stored.example.com/v1' },
|
|
23
|
+
models: { m: { modelID: 'm', name: 'M' } },
|
|
24
|
+
};
|
|
25
|
+
const getProviderSources = vi.fn(() => ({
|
|
26
|
+
sources: {
|
|
27
|
+
auth: { exists: false },
|
|
28
|
+
user: { exists: false, path: '/user.json' },
|
|
29
|
+
project: { exists: true, path: '/projects/app/opencode.json' },
|
|
30
|
+
custom: { exists: false, path: null },
|
|
31
|
+
},
|
|
32
|
+
config: stored,
|
|
33
|
+
}));
|
|
34
|
+
|
|
35
|
+
const response = await request(createApp(getProviderSources))
|
|
36
|
+
.get('/api/provider/stored-llm/source?directory=/projects/app')
|
|
37
|
+
.expect(200);
|
|
38
|
+
|
|
39
|
+
expect(getProviderSources).toHaveBeenCalledWith('stored-llm', '/projects/app');
|
|
40
|
+
expect(response.body.config).toEqual(stored);
|
|
41
|
+
expect(response.body.sources.project.exists).toBe(true);
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
it('returns null config when no layer defines the provider', async () => {
|
|
45
|
+
const response = await request(createApp(() => ({
|
|
46
|
+
sources: {
|
|
47
|
+
auth: { exists: false },
|
|
48
|
+
user: { exists: false, path: '/user.json' },
|
|
49
|
+
project: { exists: false, path: null },
|
|
50
|
+
custom: { exists: false, path: null },
|
|
51
|
+
},
|
|
52
|
+
config: null,
|
|
53
|
+
})))
|
|
54
|
+
.get('/api/provider/other/source')
|
|
55
|
+
.expect(200);
|
|
56
|
+
|
|
57
|
+
expect(response.body.config).toBeNull();
|
|
58
|
+
});
|
|
59
|
+
});
|
|
@@ -11,6 +11,7 @@ import {
|
|
|
11
11
|
deleteSectionEntry,
|
|
12
12
|
toModelVariants,
|
|
13
13
|
toProviderEntity,
|
|
14
|
+
readStoredProviderEntry,
|
|
14
15
|
toProviderPackage,
|
|
15
16
|
toNpmPackage,
|
|
16
17
|
} from './config-v2.js';
|
|
@@ -43,6 +44,8 @@ function getProviderSources(providerId, workingDirectory) {
|
|
|
43
44
|
project: { exists: providerExistsIn(projectConfig, providerId), path: paths.projectPath || null },
|
|
44
45
|
custom: { exists: providerExistsIn(customConfig, providerId), path: paths.customPath },
|
|
45
46
|
},
|
|
47
|
+
// Same precedence as the edit scope: custom > project > user.
|
|
48
|
+
config: readStoredProviderEntry([customConfig, projectConfig, userConfig], providerId),
|
|
46
49
|
};
|
|
47
50
|
}
|
|
48
51
|
|
|
@@ -436,6 +436,68 @@ describe('custom provider config persistence', () => {
|
|
|
436
436
|
}
|
|
437
437
|
});
|
|
438
438
|
|
|
439
|
+
test('getProviderSources returns the stored entry from a JSONC config, v2 shape, without the key', () => {
|
|
440
|
+
const configPath = path.join(projectDir, '.opencode', 'opencode.jsonc');
|
|
441
|
+
fs.mkdirSync(path.dirname(configPath), { recursive: true });
|
|
442
|
+
fs.writeFileSync(configPath, `{
|
|
443
|
+
// v1 spelling, still decoded by OpenCode 2
|
|
444
|
+
"provider": {
|
|
445
|
+
"stored-llm": {
|
|
446
|
+
"name": "Stored",
|
|
447
|
+
"npm": "@ai-sdk/openai-compatible",
|
|
448
|
+
"env": ["STORED_KEY"],
|
|
449
|
+
"options": { "baseURL": "https://stored.example.com/v1", "apiKey": "sk-secret", "timeout": 5 },
|
|
450
|
+
"models": { "m": { "name": "M", "variants": { "high": { "reasoningEffort": "high" } } } },
|
|
451
|
+
},
|
|
452
|
+
},
|
|
453
|
+
}`, 'utf8');
|
|
454
|
+
|
|
455
|
+
const { config } = getProviderSources('stored-llm', projectDir);
|
|
456
|
+
expect(config).toEqual({
|
|
457
|
+
name: 'Stored',
|
|
458
|
+
package: 'aisdk:@ai-sdk/openai-compatible',
|
|
459
|
+
env: ['STORED_KEY'],
|
|
460
|
+
settings: { baseURL: 'https://stored.example.com/v1', timeout: 5 },
|
|
461
|
+
models: { m: { name: 'M', variants: [{ id: 'high', settings: { reasoningEffort: 'high' } }] } },
|
|
462
|
+
});
|
|
463
|
+
expect(getProviderSources('missing-llm', projectDir).config).toBeNull();
|
|
464
|
+
});
|
|
465
|
+
|
|
466
|
+
test('saving the stored entry back keeps env, settings and model fields it does not edit', () => {
|
|
467
|
+
const providerId = 'keep-llm';
|
|
468
|
+
const configPath = path.join(projectDir, '.opencode', 'opencode.json');
|
|
469
|
+
writeJson(configPath, {
|
|
470
|
+
providers: {
|
|
471
|
+
[providerId]: {
|
|
472
|
+
name: 'Keep',
|
|
473
|
+
package: 'aisdk:@ai-sdk/openai-compatible',
|
|
474
|
+
env: ['KEEP_KEY'],
|
|
475
|
+
settings: { baseURL: 'https://keep.example.com/v1', timeout: 5 },
|
|
476
|
+
body: { store: false },
|
|
477
|
+
models: { m: { modelID: 'm', name: 'M', limit: { context: 1000 } } },
|
|
478
|
+
},
|
|
479
|
+
},
|
|
480
|
+
});
|
|
481
|
+
|
|
482
|
+
const { config } = getProviderSources(providerId, projectDir);
|
|
483
|
+
upsertProviderConfig(providerId, {
|
|
484
|
+
name: config.name,
|
|
485
|
+
package: config.package,
|
|
486
|
+
env: config.env,
|
|
487
|
+
settings: { baseURL: config.settings.baseURL },
|
|
488
|
+
models: { m: { name: 'M renamed' } },
|
|
489
|
+
}, projectDir, 'project', { hasStoredAuth: true });
|
|
490
|
+
|
|
491
|
+
expect(readJson(configPath).providers[providerId]).toEqual({
|
|
492
|
+
name: 'Keep',
|
|
493
|
+
package: 'aisdk:@ai-sdk/openai-compatible',
|
|
494
|
+
env: ['KEEP_KEY'],
|
|
495
|
+
settings: { baseURL: 'https://keep.example.com/v1', timeout: 5 },
|
|
496
|
+
body: { store: false },
|
|
497
|
+
models: { m: { modelID: 'm', name: 'M renamed', limit: { context: 1000 } } },
|
|
498
|
+
});
|
|
499
|
+
});
|
|
500
|
+
|
|
439
501
|
test('custom-scope edit updates custom layer without creating a user entry', () => {
|
|
440
502
|
const providerId = `custom-scope-${Date.now()}`;
|
|
441
503
|
const customPath = path.join(projectDir, 'custom-opencode.json');
|
|
@@ -236,7 +236,7 @@ const SESSION_LIST_ALLOWED_FIELDS = [
|
|
|
236
236
|
'fork',
|
|
237
237
|
];
|
|
238
238
|
|
|
239
|
-
const sanitizeSessionListItem = (session) => {
|
|
239
|
+
export const sanitizeSessionListItem = (session) => {
|
|
240
240
|
if (!session || typeof session !== 'object' || Array.isArray(session)) {
|
|
241
241
|
return session;
|
|
242
242
|
}
|
|
@@ -307,6 +307,11 @@ export const registerOpenCodeProxy = (app, deps) => {
|
|
|
307
307
|
// migrated.
|
|
308
308
|
getArchivedSessions = null,
|
|
309
309
|
getStoredSessionMetadata = null,
|
|
310
|
+
// Isolated spaces, when the feature's switch is on: the merged session list, and the hub
|
|
311
|
+
// whose space events the global SSE stream carries beside the host's. Both absent means
|
|
312
|
+
// the host's own answers go out exactly as before spaces.
|
|
313
|
+
mergeSpaceSessionList = null,
|
|
314
|
+
spaceEventHub = null,
|
|
310
315
|
} = deps;
|
|
311
316
|
|
|
312
317
|
/**
|
|
@@ -477,18 +482,15 @@ export const registerOpenCodeProxy = (app, deps) => {
|
|
|
477
482
|
const resolveProxyTarget = () => {
|
|
478
483
|
const runtimeState = getRuntime();
|
|
479
484
|
|
|
480
|
-
//
|
|
481
|
-
//
|
|
482
|
-
//
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
if (resolved) {
|
|
488
|
-
return resolved;
|
|
489
|
-
}
|
|
490
|
-
} catch {
|
|
485
|
+
// The shared-service endpoint can remain authoritative even when it has no
|
|
486
|
+
// explicit port (for example, HTTPS on its default port). Resolve it before
|
|
487
|
+
// consulting the legacy runtime snapshot.
|
|
488
|
+
try {
|
|
489
|
+
const resolved = normalizeProxyTarget(buildOpenCodeUrl('/', ''));
|
|
490
|
+
if (resolved) {
|
|
491
|
+
return resolved;
|
|
491
492
|
}
|
|
493
|
+
} catch {
|
|
492
494
|
}
|
|
493
495
|
|
|
494
496
|
const externalBase = normalizeProxyTarget(runtimeState.openCodeBaseUrl);
|
|
@@ -550,6 +552,7 @@ export const registerOpenCodeProxy = (app, deps) => {
|
|
|
550
552
|
let heartbeatTimer = null;
|
|
551
553
|
let upstreamStallTimer = null;
|
|
552
554
|
let didUpstreamStall = false;
|
|
555
|
+
let unsubscribeSpaceEvents = null;
|
|
553
556
|
let writeQueue = Promise.resolve(true);
|
|
554
557
|
const sseBoundary = createSseBoundaryTracker();
|
|
555
558
|
|
|
@@ -644,6 +647,26 @@ export const registerOpenCodeProxy = (app, deps) => {
|
|
|
644
647
|
return writeQueue;
|
|
645
648
|
};
|
|
646
649
|
|
|
650
|
+
// The events of isolated spaces ride the global stream too, one block each, written
|
|
651
|
+
// only between the upstream's own blocks so a block of the host's is never cut.
|
|
652
|
+
// A directory in the query or in the header scopes the stream to the host's one directory.
|
|
653
|
+
const isGlobalStream = !new URL(requestUrl, 'http://localhost').searchParams.get('directory') && !req.get('x-opencode-directory');
|
|
654
|
+
const pendingSpaceBlocks = [];
|
|
655
|
+
const flushSpaceBlocks = async () => {
|
|
656
|
+
while (pendingSpaceBlocks.length > 0 && sseBoundary.isAtBoundary() && !abortController.signal.aborted) {
|
|
657
|
+
const canContinue = await enqueueSseWrite(pendingSpaceBlocks.shift());
|
|
658
|
+
if (!canContinue) return false;
|
|
659
|
+
}
|
|
660
|
+
return true;
|
|
661
|
+
};
|
|
662
|
+
if (spaceEventHub && isGlobalStream) {
|
|
663
|
+
unsubscribeSpaceEvents = spaceEventHub.subscribeEvent((event) => {
|
|
664
|
+
if (event.spaceId === null) return;
|
|
665
|
+
pendingSpaceBlocks.push(`data: ${JSON.stringify(event.payload)}\n\n`);
|
|
666
|
+
void flushSpaceBlocks();
|
|
667
|
+
}, { spaces: true });
|
|
668
|
+
}
|
|
669
|
+
|
|
647
670
|
scheduleHeartbeat();
|
|
648
671
|
resetUpstreamStallTimer();
|
|
649
672
|
|
|
@@ -660,6 +683,9 @@ export const registerOpenCodeProxy = (app, deps) => {
|
|
|
660
683
|
if (!canContinue) {
|
|
661
684
|
break;
|
|
662
685
|
}
|
|
686
|
+
if (!await flushSpaceBlocks()) {
|
|
687
|
+
break;
|
|
688
|
+
}
|
|
663
689
|
}
|
|
664
690
|
}
|
|
665
691
|
|
|
@@ -679,6 +705,7 @@ export const registerOpenCodeProxy = (app, deps) => {
|
|
|
679
705
|
res.end();
|
|
680
706
|
}
|
|
681
707
|
} finally {
|
|
708
|
+
unsubscribeSpaceEvents?.();
|
|
682
709
|
if (heartbeatTimer) {
|
|
683
710
|
clearTimeout(heartbeatTimer);
|
|
684
711
|
heartbeatTimer = null;
|
|
@@ -763,7 +790,13 @@ export const registerOpenCodeProxy = (app, deps) => {
|
|
|
763
790
|
}
|
|
764
791
|
|
|
765
792
|
res.setHeader('content-type', result.contentType);
|
|
766
|
-
|
|
793
|
+
const hostList = await overlayOwnedStateOnList(sanitizeSessionListPayload(result.payload));
|
|
794
|
+
// The first page of the global list carries every space's sessions after the host's; a
|
|
795
|
+
// later page, and a list scoped to one directory, are the host's alone.
|
|
796
|
+
const listQuery = new URL(upstreamPath, 'http://localhost').searchParams;
|
|
797
|
+
const scopedToDirectory = Boolean(listQuery.get('directory') || req.get('x-opencode-directory'));
|
|
798
|
+
const wantsSpaces = typeof mergeSpaceSessionList === 'function' && !listQuery.get('cursor') && !scopedToDirectory;
|
|
799
|
+
res.json(wantsSpaces ? await mergeSpaceSessionList(hostList) : hostList);
|
|
767
800
|
} catch (error) {
|
|
768
801
|
if (isAbortError(error)) {
|
|
769
802
|
return;
|
|
@@ -973,10 +1006,14 @@ export const registerOpenCodeProxy = (app, deps) => {
|
|
|
973
1006
|
router: () => resolveProxyTarget(),
|
|
974
1007
|
on: {
|
|
975
1008
|
proxyReq: (proxyReq, req) => {
|
|
976
|
-
//
|
|
1009
|
+
// The proxy middleware starts with browser headers. Never let an
|
|
1010
|
+
// OpenChamber client credential reach OpenCode, even when the active
|
|
1011
|
+
// OpenCode connection itself has no authorization header.
|
|
1012
|
+
proxyReq.removeHeader('authorization');
|
|
977
1013
|
const authHeaders = getOpenCodeAuthHeaders();
|
|
978
|
-
|
|
979
|
-
proxyReq.
|
|
1014
|
+
for (const [key, value] of Object.entries(authHeaders)) {
|
|
1015
|
+
proxyReq.removeHeader(key);
|
|
1016
|
+
proxyReq.setHeader(key, value);
|
|
980
1017
|
}
|
|
981
1018
|
|
|
982
1019
|
if (req.headers?.['x-opencode-directory-encoding'] === 'uri') {
|
|
@@ -86,7 +86,7 @@ describe('OpenCode upgrade routes', () => {
|
|
|
86
86
|
expect(globalThis.fetch).not.toHaveBeenCalled();
|
|
87
87
|
});
|
|
88
88
|
|
|
89
|
-
it.each(['external', 'unavailable'
|
|
89
|
+
it.each(['external', 'unavailable'])('rejects %s on the server', async (reason) => {
|
|
90
90
|
const { app, dependencies } = createApp({ getOpenCodeUpgradeCapability: () => ({ supported: false, reason }) });
|
|
91
91
|
await request(app).post('/api/opencode/upgrade').send({}).expect(409);
|
|
92
92
|
expect(dependencies.upgradeOpenCodeCli).not.toHaveBeenCalled();
|
|
@@ -10,7 +10,7 @@ import { getClaudeCliAuthStatus } from './claude-cli-auth.js';
|
|
|
10
10
|
import { OPENCODE_CONFIG_DIR } from './shared.js';
|
|
11
11
|
import { settingsSurfaceOf } from './settings-files.js';
|
|
12
12
|
import { parseWebSearchSelection } from './config-v2.js';
|
|
13
|
-
import { getWebSearchSource, setWebSearchSelection } from './websearch-config.js';
|
|
13
|
+
import { getWebSearchSource, setWarmingEnabled, setWebSearchSelection } from './websearch-config.js';
|
|
14
14
|
|
|
15
15
|
export const registerOpenCodeRoutes = (app, dependencies) => {
|
|
16
16
|
const {
|
|
@@ -287,6 +287,7 @@ export const registerOpenCodeRoutes = (app, dependencies) => {
|
|
|
287
287
|
return res.json({
|
|
288
288
|
providerId,
|
|
289
289
|
sources: sources.sources,
|
|
290
|
+
config: sources.config,
|
|
290
291
|
});
|
|
291
292
|
} catch (error) {
|
|
292
293
|
console.error('Failed to get provider sources:', error);
|
|
@@ -379,6 +380,22 @@ export const registerOpenCodeRoutes = (app, dependencies) => {
|
|
|
379
380
|
}
|
|
380
381
|
});
|
|
381
382
|
|
|
383
|
+
// Session warming (`warming` in OpenCode config), written like the web
|
|
384
|
+
// search choice above. Settings reads the effective value from OpenCode.
|
|
385
|
+
app.put('/api/config/warming', (req, res) => {
|
|
386
|
+
const enabled = req.body?.enabled;
|
|
387
|
+
if (typeof enabled !== 'boolean') {
|
|
388
|
+
return res.status(400).json({ error: 'enabled must be a boolean' });
|
|
389
|
+
}
|
|
390
|
+
try {
|
|
391
|
+
const result = setWarmingEnabled(enabled);
|
|
392
|
+
return res.json({ success: true, changed: result.changed });
|
|
393
|
+
} catch (error) {
|
|
394
|
+
console.error('Failed to save session warming:', error);
|
|
395
|
+
return res.status(500).json({ error: error.message || 'Failed to save session warming' });
|
|
396
|
+
}
|
|
397
|
+
});
|
|
398
|
+
|
|
382
399
|
app.delete('/api/provider/:providerId/auth', async (req, res) => {
|
|
383
400
|
try {
|
|
384
401
|
const { providerId } = req.params;
|
|
@@ -528,6 +545,24 @@ export const registerOpenCodeRoutes = (app, dependencies) => {
|
|
|
528
545
|
return res.status(413).json({ error: `Content exceeds maximum size of ${MAX_BEHAVIOR_PROMPT_SIZE} bytes` });
|
|
529
546
|
}
|
|
530
547
|
|
|
548
|
+
// `expectedContent` is what the editor loaded (null: no file). A file
|
|
549
|
+
// changed on disk since then is not overwritten with the stale copy.
|
|
550
|
+
if (req.body && Object.prototype.hasOwnProperty.call(req.body, 'expectedContent')) {
|
|
551
|
+
const expected = req.body.expectedContent;
|
|
552
|
+
let current = null;
|
|
553
|
+
try {
|
|
554
|
+
current = await fs.promises.readFile(AGENTS_MD_PATH, 'utf8');
|
|
555
|
+
} catch (error) {
|
|
556
|
+
if (error?.code !== 'ENOENT') throw error;
|
|
557
|
+
}
|
|
558
|
+
if (current !== expected) {
|
|
559
|
+
return res.status(409).json({
|
|
560
|
+
error: 'AGENTS.md changed on disk since it was loaded',
|
|
561
|
+
code: 'AGENTS_MD_CONFLICT',
|
|
562
|
+
});
|
|
563
|
+
}
|
|
564
|
+
}
|
|
565
|
+
|
|
531
566
|
// Ensure parent directory exists
|
|
532
567
|
const parentDir = path.dirname(AGENTS_MD_PATH);
|
|
533
568
|
try {
|
|
@@ -31,6 +31,7 @@ export const createServerStartupRuntime = (dependencies) => {
|
|
|
31
31
|
bindHost,
|
|
32
32
|
startupTunnelRequest,
|
|
33
33
|
onTunnelReady,
|
|
34
|
+
afterListening,
|
|
34
35
|
}) => {
|
|
35
36
|
let activePort = port;
|
|
36
37
|
|
|
@@ -45,6 +46,30 @@ export const createServerStartupRuntime = (dependencies) => {
|
|
|
45
46
|
try {
|
|
46
47
|
const addressInfo = server.address();
|
|
47
48
|
activePort = typeof addressInfo === 'object' && addressInfo ? addressInfo.port : port;
|
|
49
|
+
try {
|
|
50
|
+
await afterListening?.({ activePort, bindHost });
|
|
51
|
+
} catch (error) {
|
|
52
|
+
if (typeof process.send === 'function' && process.connected) {
|
|
53
|
+
const startupError = {
|
|
54
|
+
type: 'openchamber:error',
|
|
55
|
+
code: error?.code || 'WEB_STARTUP_ERROR',
|
|
56
|
+
message: error instanceof Error ? error.message : String(error),
|
|
57
|
+
};
|
|
58
|
+
if (error?.url) startupError.url = error.url;
|
|
59
|
+
await new Promise((resolveStartupError) => {
|
|
60
|
+
try {
|
|
61
|
+
process.send(startupError, () => resolveStartupError());
|
|
62
|
+
} catch {
|
|
63
|
+
resolveStartupError();
|
|
64
|
+
}
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
await new Promise((resolveClose) => {
|
|
68
|
+
server.close(() => resolveClose());
|
|
69
|
+
server.closeAllConnections?.();
|
|
70
|
+
});
|
|
71
|
+
throw error;
|
|
72
|
+
}
|
|
48
73
|
|
|
49
74
|
if (typeof process.send === 'function') {
|
|
50
75
|
if (!process.connected) {
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { describe, expect, test } from 'bun:test';
|
|
1
|
+
import { describe, expect, test, mock } from 'bun:test';
|
|
2
2
|
import { EventEmitter } from 'node:events';
|
|
3
3
|
import { createServerStartupRuntime } from './server-startup-runtime.js';
|
|
4
4
|
|
|
@@ -7,6 +7,72 @@ import { createServerStartupRuntime } from './server-startup-runtime.js';
|
|
|
7
7
|
* on a single uncaught exception turned every stray socket error into "the
|
|
8
8
|
* instance is unreachable until restarted". Only a sustained storm shuts down.
|
|
9
9
|
*/
|
|
10
|
+
describe('server startup readiness', () => {
|
|
11
|
+
test('reports daemon ready only after listener-dependent startup finishes', async () => {
|
|
12
|
+
const order = [];
|
|
13
|
+
const fakeProcess = {
|
|
14
|
+
connected: true,
|
|
15
|
+
send: mock((message, callback) => {
|
|
16
|
+
order.push(message.type);
|
|
17
|
+
callback();
|
|
18
|
+
}),
|
|
19
|
+
};
|
|
20
|
+
const server = new EventEmitter();
|
|
21
|
+
server.address = () => ({ port: 3901 });
|
|
22
|
+
server.listen = (_port, _host, callback) => callback();
|
|
23
|
+
const runtime = createServerStartupRuntime({
|
|
24
|
+
process: fakeProcess,
|
|
25
|
+
crypto: {},
|
|
26
|
+
server,
|
|
27
|
+
readSettingsFromDiskMigrated: async () => ({}),
|
|
28
|
+
tunnelAuthController: {},
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
await runtime.startListeningAndMaybeTunnel({
|
|
32
|
+
port: 0,
|
|
33
|
+
bindHost: '127.0.0.1',
|
|
34
|
+
afterListening: async () => order.push('prepared'),
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
expect(order).toEqual(['prepared', 'openchamber:ready']);
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
test('closes the listener and reports startup failure when listener-dependent startup fails', async () => {
|
|
41
|
+
const sent = [];
|
|
42
|
+
const fakeProcess = {
|
|
43
|
+
connected: true,
|
|
44
|
+
send: mock((message, callback) => {
|
|
45
|
+
sent.push(message);
|
|
46
|
+
callback();
|
|
47
|
+
}),
|
|
48
|
+
};
|
|
49
|
+
const server = new EventEmitter();
|
|
50
|
+
server.address = () => ({ port: 3901 });
|
|
51
|
+
server.listen = (_port, _host, callback) => callback();
|
|
52
|
+
server.close = mock((callback) => callback());
|
|
53
|
+
server.closeAllConnections = mock(() => {});
|
|
54
|
+
const runtime = createServerStartupRuntime({ process: fakeProcess, crypto: {}, server });
|
|
55
|
+
const startupError = Object.assign(new Error('shared service rejected'), {
|
|
56
|
+
code: 'OPENCODE_INCOMPATIBLE',
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
await expect(runtime.startListeningAndMaybeTunnel({
|
|
60
|
+
port: 0,
|
|
61
|
+
bindHost: '127.0.0.1',
|
|
62
|
+
afterListening: async () => { throw startupError; },
|
|
63
|
+
})).rejects.toBe(startupError);
|
|
64
|
+
|
|
65
|
+
expect(server.close).toHaveBeenCalledTimes(1);
|
|
66
|
+
expect(server.closeAllConnections).toHaveBeenCalledTimes(1);
|
|
67
|
+
expect(sent).toEqual([expect.objectContaining({
|
|
68
|
+
type: 'openchamber:error',
|
|
69
|
+
code: 'OPENCODE_INCOMPATIBLE',
|
|
70
|
+
})]);
|
|
71
|
+
expect(sent).not.toContainEqual(expect.objectContaining({ type: 'openchamber:ready' }));
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
});
|
|
75
|
+
|
|
10
76
|
describe('uncaught exception policy', () => {
|
|
11
77
|
const setup = () => {
|
|
12
78
|
const fakeProcess = new EventEmitter();
|
|
@@ -23,6 +23,8 @@ export const createServerUtilsRuntime = (dependencies) => {
|
|
|
23
23
|
clearLastOpenCodeError,
|
|
24
24
|
getLoginShellPath,
|
|
25
25
|
getArchivedSessions = null,
|
|
26
|
+
getMergeSpaceSessionList = null,
|
|
27
|
+
getSpaceEventHub = null,
|
|
26
28
|
getStoredSessionMetadata = null,
|
|
27
29
|
} = dependencies;
|
|
28
30
|
|
|
@@ -221,6 +223,9 @@ export const createServerUtilsRuntime = (dependencies) => {
|
|
|
221
223
|
getUiNotificationClients,
|
|
222
224
|
getArchivedSessions,
|
|
223
225
|
getStoredSessionMetadata,
|
|
226
|
+
// Read when the proxy is set up, after `main` decided whether the spaces host exists.
|
|
227
|
+
mergeSpaceSessionList: typeof getMergeSpaceSessionList === 'function' ? getMergeSpaceSessionList() : null,
|
|
228
|
+
spaceEventHub: typeof getSpaceEventHub === 'function' ? getSpaceEventHub() : null,
|
|
224
229
|
});
|
|
225
230
|
};
|
|
226
231
|
|
|
@@ -65,6 +65,7 @@ export const createSettingsHelpers = (dependencies) => {
|
|
|
65
65
|
const SIDEBAR_PROJECT_DISPLAY_MODE_VALUES = new Set(['all', 'single']);
|
|
66
66
|
const SIDEBAR_VIEW_MODE_VALUES = new Set(['projects', 'timeline']);
|
|
67
67
|
const SIDEBAR_PROJECT_SORT_ORDER_VALUES = new Set(['manual', 'a-z', 'z-a', 'date-added', 'recent']);
|
|
68
|
+
const SIDEBAR_WORKTREE_SORT_ORDER_VALUES = new Set(['recent', 'manual', 'a-z']);
|
|
68
69
|
const HIDDEN_MODELS_MAX = 1024;
|
|
69
70
|
const RECENT_EFFORTS_MAX_KEYS = 128;
|
|
70
71
|
const RECENT_EFFORTS_MAX_VARIANTS_PER_KEY = 5;
|
|
@@ -302,6 +303,9 @@ export const createSettingsHelpers = (dependencies) => {
|
|
|
302
303
|
if (SIDEBAR_PROJECT_SORT_ORDER_VALUES.has(candidate.sidebarProjectSortOrder)) {
|
|
303
304
|
result.sidebarProjectSortOrder = candidate.sidebarProjectSortOrder;
|
|
304
305
|
}
|
|
306
|
+
if (SIDEBAR_WORKTREE_SORT_ORDER_VALUES.has(candidate.sidebarWorktreeSortOrder)) {
|
|
307
|
+
result.sidebarWorktreeSortOrder = candidate.sidebarWorktreeSortOrder;
|
|
308
|
+
}
|
|
305
309
|
if (typeof candidate.sidebarShowRecentSection === 'boolean') {
|
|
306
310
|
result.sidebarShowRecentSection = candidate.sidebarShowRecentSection;
|
|
307
311
|
}
|
|
@@ -607,6 +611,12 @@ export const createSettingsHelpers = (dependencies) => {
|
|
|
607
611
|
if (typeof candidate.agentMemoryToolEnabled === 'boolean') {
|
|
608
612
|
result.agentMemoryToolEnabled = candidate.agentMemoryToolEnabled;
|
|
609
613
|
}
|
|
614
|
+
if (typeof candidate.agentNotifyToolEnabled === 'boolean') {
|
|
615
|
+
result.agentNotifyToolEnabled = candidate.agentNotifyToolEnabled;
|
|
616
|
+
}
|
|
617
|
+
if (typeof candidate.isolatedSpacesEnabled === 'boolean') {
|
|
618
|
+
result.isolatedSpacesEnabled = candidate.isolatedSpacesEnabled;
|
|
619
|
+
}
|
|
610
620
|
if (typeof candidate.openCodeUpdateToastDismissedVersion === 'string') {
|
|
611
621
|
const version = candidate.openCodeUpdateToastDismissedVersion.trim();
|
|
612
622
|
result.openCodeUpdateToastDismissedVersion = version.slice(0, VERSION_STRING_MAX_LENGTH);
|
|
@@ -764,13 +764,13 @@ describe('settings registry gate', () => {
|
|
|
764
764
|
desktopLanAccessEnabled: true, desktopKeepAwakeEnabled: true, desktopMinimizeToTrayEnabled: true, desktopMacMenuBarEnabled: true,
|
|
765
765
|
desktopUiPassword: 'secret', githubClientId: 'client', githubScopes: 'repo', skillCatalogs: [{ id: 'c', label: 'C', source: 'https://x' }],
|
|
766
766
|
defaultGitIdentityId: 'global', permissionAutoAccept: { sessions: { s: true }, revision: 1 },
|
|
767
|
-
agentControlToolEnabled: true, agentWebToolEnabled: true, browserProvider: 'builtin', agentMemoryToolEnabled: true, openCodeUpdateToastDismissedVersion: '1.0.0',
|
|
767
|
+
agentControlToolEnabled: true, agentWebToolEnabled: true, browserProvider: 'builtin', agentMemoryToolEnabled: true, agentNotifyToolEnabled: true, isolatedSpacesEnabled: true, openCodeUpdateToastDismissedVersion: '1.0.0',
|
|
768
768
|
autoDeleteEnabled: true, autoDeleteAfterDays: 30, sessionRetentionOnlyArchived: false, sessionRetentionAction: 'archive', terminalShell: 'zsh', terminalLoginShells: ['zsh'],
|
|
769
769
|
openInAppId: 'vscode', dictationEnabled: true, sttProvider: 'local', sttServerUrl: 'http://localhost:8001/v1', sttModel: 'm', sttLocalModel: 'm', sttLanguage: 'en',
|
|
770
770
|
tunnelProvider: 'cloudflare', tunnelMode: 'quick', tunnelBootstrapTtlMs: 600000, tunnelSessionTtlMs: 86400000, managedLocalTunnelConfigPath: '/tmp/x',
|
|
771
771
|
managedRemoteTunnelHostname: 'x.example', managedRemoteTunnelToken: 'token', managedRemoteTunnelPresets: [{ id: 'a', name: 'A', hostname: 'a.example' }],
|
|
772
772
|
managedRemoteTunnelSelectedPresetId: 'a', managedRemoteTunnelPresetTokens: { a: 'token' },
|
|
773
|
-
sidebarProjectDisplayMode: 'all', sidebarViewMode: 'timeline', sidebarProjectSortOrder: 'manual', sidebarShowRecentSection: true,
|
|
773
|
+
sidebarProjectDisplayMode: 'all', sidebarViewMode: 'timeline', sidebarProjectSortOrder: 'manual', sidebarWorktreeSortOrder: 'recent', sidebarShowRecentSection: true,
|
|
774
774
|
workStatusPanelEnabled: true, workStatusHiddenSections: ['mcp'], workStatusHiddenSectionsExplicit: true, workStatusSectionOrder: ['mcp', 'session'],
|
|
775
775
|
showReasoningTraces: true, streamingAutoFollowEnabled: true, collapsibleThinkingBlocks: true, showTextJustificationActivity: true,
|
|
776
776
|
chatRenderMode: 'live', activityRenderMode: 'summary', mermaidRenderingMode: 'svg', userMessageRenderingMode: 'markdown', collapsibleUserMessages: true,
|
|
@@ -123,9 +123,15 @@
|
|
|
123
123
|
"browserProvider": {
|
|
124
124
|
"scope": "instance"
|
|
125
125
|
},
|
|
126
|
+
"agentNotifyToolEnabled": {
|
|
127
|
+
"scope": "instance"
|
|
128
|
+
},
|
|
126
129
|
"agentMemoryToolEnabled": {
|
|
127
130
|
"scope": "instance"
|
|
128
131
|
},
|
|
132
|
+
"isolatedSpacesEnabled": {
|
|
133
|
+
"scope": "instance"
|
|
134
|
+
},
|
|
129
135
|
"agentMemoryFeatureAvailable": {
|
|
130
136
|
"scope": "instance",
|
|
131
137
|
"computed": true
|
|
@@ -222,6 +228,9 @@
|
|
|
222
228
|
"sidebarProjectSortOrder": {
|
|
223
229
|
"scope": "profile"
|
|
224
230
|
},
|
|
231
|
+
"sidebarWorktreeSortOrder": {
|
|
232
|
+
"scope": "profile"
|
|
233
|
+
},
|
|
225
234
|
"sidebarShowRecentSection": {
|
|
226
235
|
"scope": "profile"
|
|
227
236
|
},
|
|
@@ -674,6 +683,14 @@
|
|
|
674
683
|
"scope": "device",
|
|
675
684
|
"local": true
|
|
676
685
|
},
|
|
686
|
+
"diffFileListMode": {
|
|
687
|
+
"scope": "device",
|
|
688
|
+
"local": true
|
|
689
|
+
},
|
|
690
|
+
"diffFileTreeWidth": {
|
|
691
|
+
"scope": "device",
|
|
692
|
+
"local": true
|
|
693
|
+
},
|
|
677
694
|
"linearIssueListStatus": {
|
|
678
695
|
"scope": "device",
|
|
679
696
|
"local": true
|