@openchambery/web 1.19.20 → 2.0.0-beta.1
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 +13 -11
- package/bin/cli-opencode.test.js +208 -0
- package/bin/cli.js +62 -9
- package/bin/lib/cli-startup.js +105 -2
- package/bin/lib/cli-startup.test.js +165 -0
- package/dist/assets/{AgentAvatar-NT957MuI.js → AgentAvatar-CLWWNu_Q.js} +1 -1
- package/dist/assets/AssistantView-BTCkm8yk.js +3 -0
- package/dist/assets/{DiagramView-C_JfbxNl.js → DiagramView-xuSJTnv7.js} +1 -1
- package/dist/assets/{FadeInOnReveal-CilRl4gc.js → FadeInOnReveal-Ov0D96d0.js} +1 -1
- package/dist/assets/{Icon-i5GiQRLq.js → Icon-DD-BuMfZ.js} +1 -1
- package/dist/assets/MarkdownRenderer-bc_p1H7l.js +2 -0
- package/dist/assets/{MarkdownRendererImpl-B1iUwzOz.js → MarkdownRendererImpl-SKi4oG0c.js} +1 -1
- package/dist/assets/{MarkstreamRendererImpl-BPtMj3-X.js → MarkstreamRendererImpl-CnkodGJW.js} +1 -1
- package/dist/assets/MobileDetailNavigation-Bnx59U_J.js +1 -0
- package/dist/assets/{MobileShareBridge-CKClHsWN.js → MobileShareBridge-BQlP4J-F.js} +1 -1
- package/dist/assets/MobileSurface-BXLeiZ4d.js +2 -0
- package/dist/assets/{MultiRunWindow-DiA24bnN.js → MultiRunWindow-ChZGob0v.js} +1 -1
- package/dist/assets/{OnboardingScreen-BGRLk4Wi.js → OnboardingScreen-DtQJxZG-.js} +1 -1
- package/dist/assets/{OverlayScrollbar-NuQI58XD.js → OverlayScrollbar-pMwxaCca.js} +1 -1
- package/dist/assets/SettingsView-B5I0KDTZ.js +25 -0
- package/dist/assets/{SettingsWindow-XlzRSS7E.js → SettingsWindow-DfnsFa8y.js} +1 -1
- package/dist/assets/{TerminalView-DdIaaLba.js → TerminalView-Dh9MWCpS.js} +1 -1
- package/dist/assets/{ToolOutputDialog-BbwuLdXU.js → ToolOutputDialog-BejmjrRk.js} +1 -1
- package/dist/assets/{appThemeRegistry-ChzfD7mc.js → appThemeRegistry-COGfxTFV.js} +1 -1
- package/dist/assets/{button-DtzFDvij.js → button-D-rATN7w.js} +1 -1
- package/dist/assets/{collapsible-D-nzhcyF.js → collapsible-BXRd5_Hr.js} +1 -1
- package/dist/assets/{context-menu-BzVSzHLG.js → context-menu-jGtH6fR9.js} +1 -1
- package/dist/assets/{diagram-2YWalSb5.js → diagram-DQf7Y_uR.js} +1 -1
- package/dist/assets/{dialog-DGV7DsZC.js → dialog-bfbvR4VH.js} +1 -1
- package/dist/assets/es-c52LTHHe.js +15 -0
- package/dist/assets/{fileMentionTouchSelection-DyjmxUEN.js → fileMentionTouchSelection-BvrNCroG.js} +1 -1
- package/dist/assets/{fr-DsBZeN8Y.js → fr-PisFbUE8.js} +3 -3
- package/dist/assets/gitApi-ButwzQ0N.js +9 -0
- package/dist/assets/{input-CbB2HdLg.js → input-8r6F_Fza.js} +1 -1
- package/dist/assets/insertionBoundaries-DGs7S5zv.js +1 -0
- package/dist/assets/ja-C8Ikun3Z.js +15 -0
- package/dist/assets/ko-ClFys5o2.js +15 -0
- package/dist/assets/main-CCE_4y5F.js +2 -0
- package/dist/assets/main-Dn9rpsgl.js +169 -0
- package/dist/assets/{markdownHeightCache-C9ym9Tsb.js → markdownHeightCache-1Qo6y6IJ.js} +1 -1
- package/dist/assets/miniChat-BpjxVqgg.js +2 -0
- package/dist/assets/mobile-nMJCaNq1.js +2 -0
- package/dist/assets/{multirun-8fkF0Ji_.js → multirun-BvSG94mW.js} +1 -1
- package/dist/assets/pl-CnaKWN5F.js +15 -0
- package/dist/assets/platform-CWTIp02q.js +4 -0
- package/dist/assets/{projectMeta--y6tyjrv.js → projectMeta-DOe49dM_.js} +2 -2
- package/dist/assets/pt-BR-s8OpfRI6.js +15 -0
- package/dist/assets/renderElectronMiniChatApp-R6dKh79f.js +2 -0
- package/dist/assets/renderMobileApp-y-S8y8v_.js +28 -0
- package/dist/assets/{runtimeConfig-DYWtBrkW.js → runtimeConfig-BMvqa5we.js} +1 -1
- package/dist/assets/{runtimeEndpointReset-BSWg99jc.js → runtimeEndpointReset-B6BxhvR0.js} +5 -5
- package/dist/assets/{sessionChangeSummary-BwupJF-Q.js → sessionChangeSummary-VVBlEXuh.js} +1 -1
- package/dist/assets/{sessionLookup-DP2X1qRR.js → sessionLookup-gh-hVRIc.js} +1 -1
- package/dist/assets/{sortable-tabs-strip-CfgEJfB0.js → sortable-tabs-strip-CudoSgV4.js} +1 -1
- package/dist/assets/{src-DZ60kHBP.css → src-BzX5UI-c.css} +1 -1
- package/dist/assets/{store-B6dImdlY.js → store-DJczmczv.js} +4 -4
- package/dist/assets/{terminalApi-xjqssKke.js → terminalApi-DiF6JmBP.js} +1 -1
- package/dist/assets/{textarea-BnD799LW.js → textarea-3XR8sUfy.js} +1 -1
- package/dist/assets/{tooltip-Djn7MSOX.js → tooltip-CQvpK-UV.js} +1 -1
- package/dist/assets/uk-CaX0iqkB.js +15 -0
- package/dist/assets/{useAppFontEffects-DWLxpWlS.js → useAppFontEffects-Cpmpo7Wb.js} +45 -45
- package/dist/assets/{useDesktopWindowControlsLayout-BGPR_rq6.js → useDesktopWindowControlsLayout-B1GX59-L.js} +1 -1
- package/dist/assets/{useEffectiveDirectory-CbV9_r1s.js → useEffectiveDirectory-CN6JoCFw.js} +1 -1
- package/dist/assets/{useFontPreferences-B1JYyzpK.js → useFontPreferences-DcbXQOkc.js} +1 -1
- package/dist/assets/useMobileNavigationStore-BVjFtXSy.js +414 -0
- package/dist/assets/{useSessionAutoCleanup-Dnors7hB.js → useSessionAutoCleanup-BLjymaFt.js} +1 -1
- package/dist/assets/{useWorkerHighlightedLines-DQqqrOBU.js → useWorkerHighlightedLines-Bqn4AiBL.js} +1 -1
- package/dist/assets/vendor-opencode-client-BOe2qpQb.js +12 -0
- package/dist/assets/{vendor-yaml-DIcAK3EC.js → vendor-yaml-C9KlJAlj.js} +37 -37
- package/dist/assets/zh-CN-20MYEjha.js +15 -0
- package/dist/assets/{zh-TW-BsOQbrwT.js → zh-TW-DiN04qTw.js} +3 -3
- package/dist/index.html +6 -6
- package/dist/mini-chat.html +6 -6
- package/dist/mobile.html +6 -6
- package/package.json +2 -2
- package/server/index.js +107 -0
- package/server/lib/assistants/DOCUMENTATION.md +18 -18
- package/server/lib/assistants/assign.js +2 -1
- package/server/lib/assistants/contact-store.js +37 -19
- package/server/lib/assistants/projection-history.test.js +207 -0
- package/server/lib/assistants/routes.js +5 -5
- package/server/lib/assistants/service.js +493 -123
- package/server/lib/assistants/service.test.js +203 -133
- package/server/lib/conversations/DOCUMENTATION.md +4 -4
- package/server/lib/conversations/service.js +86 -57
- package/server/lib/conversations/service.test.js +116 -100
- package/server/lib/event-stream/global-hub.js +2 -1
- package/server/lib/llm/DOCUMENTATION.md +71 -84
- package/server/lib/llm/catalog.js +111 -45
- package/server/lib/llm/catalog.test.js +121 -59
- package/server/lib/llm/completions.js +37 -12
- package/server/lib/llm/completions.test.js +45 -4
- package/server/lib/llm/generate.js +421 -450
- package/server/lib/llm/generate.test.js +553 -487
- package/server/lib/llm/routes.js +6 -4
- package/server/lib/message-queue/DOCUMENTATION.md +5 -5
- package/server/lib/message-queue/opencode-adapter.js +191 -104
- package/server/lib/message-queue/opencode-adapter.test.js +328 -58
- package/server/lib/message-queue/runtime.test.js +6 -6
- package/server/lib/message-queue/worker.js +7 -1
- package/server/lib/opencode/DOCUMENTATION.md +28 -5
- package/server/lib/opencode/config-entity-routes.js +84 -13
- package/server/lib/opencode/config-entity-routes.test.js +41 -10
- package/server/lib/opencode/ensure-cli.js +239 -0
- package/server/lib/opencode/ensure-cli.test.js +119 -0
- package/server/lib/opencode/env-runtime.js +193 -110
- package/server/lib/opencode/env-runtime.test.js +221 -17
- package/server/lib/opencode/feature-routes-runtime.js +37 -1
- package/server/lib/opencode/lifecycle.js +204 -70
- package/server/lib/opencode/lifecycle.test.js +376 -6
- package/server/lib/opencode/network-runtime.js +28 -16
- package/server/lib/opencode/network-runtime.test.js +68 -4
- package/server/lib/opencode/opencode2-pin.js +139 -0
- package/server/lib/opencode/opencode2-pin.test.js +87 -0
- package/server/lib/opencode/opencode2-sidecar.e2e.test.js +203 -0
- package/server/lib/opencode/plugin-compatibility.js +15 -0
- package/server/lib/opencode/plugin-compatibility.test.js +21 -0
- package/server/lib/opencode/plugins.js +4 -3
- package/server/lib/opencode/plugins.test.js +12 -0
- package/server/lib/opencode/proxy.js +153 -16
- package/server/lib/opencode/proxy.test.js +120 -0
- package/server/lib/opencode/routes.js +25 -32
- package/server/lib/opencode/routes.test.js +51 -52
- package/server/lib/opencode/server-utils-runtime.js +2 -0
- package/server/lib/opencode/skill-routes.js +6 -7
- package/server/lib/opencode/skill-routes.test.js +36 -5
- package/server/lib/opencode/v1-migration-gate.js +157 -0
- package/server/lib/opencode/v1-migration-gate.test.js +111 -0
- package/server/lib/opencode/v2-client.js +49 -0
- package/server/lib/opencode/watcher.js +2 -1
- package/server/lib/opencode/watcher.test.js +1 -1
- package/server/lib/scheduled-tasks/DOCUMENTATION.md +71 -39
- package/server/lib/scheduled-tasks/managed-tool-route.js +31 -1
- package/server/lib/scheduled-tasks/managed-tool-route.test.js +23 -0
- package/server/lib/scheduled-tasks/routes.js +27 -0
- package/server/lib/scheduled-tasks/routes.test.js +94 -0
- package/server/lib/scheduled-tasks/runtime.js +331 -308
- package/server/lib/scheduled-tasks/runtime.test.js +530 -318
- package/server/lib/session-goal/DOCUMENTATION.md +65 -14
- package/server/lib/session-goal/capability.js +57 -0
- package/server/lib/session-goal/capability.test.js +33 -0
- package/server/lib/session-goal/routes.js +231 -2
- package/server/lib/session-goal/routes.test.js +211 -0
- package/server/lib/session-goal/runtime.js +46 -4
- package/server/lib/session-goal/runtime.test.js +62 -1
- package/server/lib/session-index/sync-runtime.js +20 -17
- package/server/lib/session-index/sync-runtime.test.js +9 -8
- package/server/lib/session-metadata/DOCUMENTATION.md +41 -0
- package/server/lib/session-metadata/routes.js +94 -0
- package/server/lib/session-metadata/routes.test.js +111 -0
- package/server/lib/session-metadata/session-metadata-store.js +235 -0
- package/server/lib/session-metadata/session-metadata-store.test.js +184 -0
- package/server/lib/session-turn-pages/DOCUMENTATION.md +3 -3
- package/server/lib/session-turn-pages/reconcile.service.js +1 -1
- package/server/lib/session-turn-pages/reconcile.service.test.js +16 -0
- package/server/lib/session-turn-pages/routes.js +204 -71
- package/server/lib/session-turn-pages/routes.test.js +285 -0
- package/server/lib/session-turn-pages/service.js +6 -2
- package/server/lib/session-turn-pages/service.test.js +34 -0
- package/server/lib/small-model/DOCUMENTATION.md +5 -4
- package/server/lib/small-model/catalog.js +56 -6
- package/server/lib/small-model/catalog.test.js +47 -0
- package/server/lib/small-model/index.js +3 -3
- package/server/lib/small-model/opencode-session.js +50 -206
- package/server/lib/small-model/opencode-session.test.js +58 -88
- package/server/opencode-proxy.test.js +61 -12
- package/dist/assets/AssistantView-BjvfkSRS.js +0 -3
- package/dist/assets/MarkdownRenderer-Cg82L3fN.js +0 -2
- package/dist/assets/MobileDetailNavigation-s_vvUD_F.js +0 -1
- package/dist/assets/MobileSurface-DHpXHbJp.js +0 -2
- package/dist/assets/SettingsView-l0J7pfYo.js +0 -25
- package/dist/assets/es-rqMiRP7-.js +0 -15
- package/dist/assets/gitApi-CHpibtno.js +0 -7
- package/dist/assets/insertionBoundaries-Yd1Lorjw.js +0 -1
- package/dist/assets/ja-Bgr8-dOg.js +0 -15
- package/dist/assets/ko-B_Gxh-0l.js +0 -15
- package/dist/assets/main-BoRXxmB3.js +0 -2
- package/dist/assets/main-DCDcdV5o.js +0 -169
- package/dist/assets/miniChat-12fxk5KR.js +0 -2
- package/dist/assets/mobile-CkZem9Ad.js +0 -2
- package/dist/assets/pl-DW2XJGC4.js +0 -15
- package/dist/assets/platform-0w_Q9_ji.js +0 -2
- package/dist/assets/pt-BR-CIsanf4T.js +0 -15
- package/dist/assets/renderElectronMiniChatApp-COFc0ZDJ.js +0 -2
- package/dist/assets/renderMobileApp-BKzx7JL3.js +0 -28
- package/dist/assets/uk-BPmPEUYJ.js +0 -15
- package/dist/assets/useMobileNavigationStore-DrB_awgk.js +0 -413
- package/dist/assets/vendor-opencode-sdk-C07HQypa.js +0 -7
- package/dist/assets/zh-CN-R8-mDcIp.js +0 -15
|
@@ -10,6 +10,7 @@ const originalPath = process.env.PATH;
|
|
|
10
10
|
const originalLocalAppData = process.env.LOCALAPPDATA;
|
|
11
11
|
const originalSystemRoot = process.env.SystemRoot;
|
|
12
12
|
const originalBundledOpencodeCliDir = process.env.OPENCHAMBER_BUNDLED_OPENCODE_CLI_DIR;
|
|
13
|
+
const originalOpenChamberDataDir = process.env.OPENCHAMBER_DATA_DIR;
|
|
13
14
|
const originalResourcesPath = process.resourcesPath;
|
|
14
15
|
const originalWslBinary = process.env.WSL_BINARY;
|
|
15
16
|
const originalOpenChamberWslBinary = process.env.OPENCHAMBER_WSL_BINARY;
|
|
@@ -23,6 +24,16 @@ const createTempDir = (prefix) => {
|
|
|
23
24
|
return dir;
|
|
24
25
|
};
|
|
25
26
|
|
|
27
|
+
const writeVersionBinary = (filePath, version) => {
|
|
28
|
+
fs.mkdirSync(path.dirname(filePath), { recursive: true });
|
|
29
|
+
if (process.platform === 'win32') {
|
|
30
|
+
fs.writeFileSync(filePath, `@echo off\r\necho ${version}\r\n`);
|
|
31
|
+
return;
|
|
32
|
+
}
|
|
33
|
+
fs.writeFileSync(filePath, `#!${process.execPath}\nconsole.log(${JSON.stringify(version)});\n`);
|
|
34
|
+
fs.chmodSync(filePath, 0o755);
|
|
35
|
+
};
|
|
36
|
+
|
|
26
37
|
const setPlatform = (platform) => {
|
|
27
38
|
Object.defineProperty(process, 'platform', {
|
|
28
39
|
value: platform,
|
|
@@ -74,6 +85,12 @@ afterEach(() => {
|
|
|
74
85
|
delete process.env.OPENCHAMBER_BUNDLED_OPENCODE_CLI_DIR;
|
|
75
86
|
}
|
|
76
87
|
|
|
88
|
+
if (typeof originalOpenChamberDataDir === 'string') {
|
|
89
|
+
process.env.OPENCHAMBER_DATA_DIR = originalOpenChamberDataDir;
|
|
90
|
+
} else {
|
|
91
|
+
delete process.env.OPENCHAMBER_DATA_DIR;
|
|
92
|
+
}
|
|
93
|
+
|
|
77
94
|
Object.defineProperty(process, 'resourcesPath', {
|
|
78
95
|
configurable: true,
|
|
79
96
|
value: originalResourcesPath,
|
|
@@ -112,6 +129,7 @@ const createRuntime = (settings, options = {}) => {
|
|
|
112
129
|
readSettingsFromDiskMigrated: async () => settings,
|
|
113
130
|
spawnSync: options.spawnSync,
|
|
114
131
|
homedir: options.homedir,
|
|
132
|
+
ensurePinnedOpenCode2Cli: options.ensurePinnedOpenCode2Cli,
|
|
115
133
|
});
|
|
116
134
|
|
|
117
135
|
return { runtime, state };
|
|
@@ -119,11 +137,11 @@ const createRuntime = (settings, options = {}) => {
|
|
|
119
137
|
|
|
120
138
|
describe('OpenCode env runtime', () => {
|
|
121
139
|
it('throws a specific error for a missing configured OpenCode binary in strict mode', async () => {
|
|
122
|
-
const { runtime } = createRuntime({ opencodeBinary: '/missing/
|
|
140
|
+
const { runtime } = createRuntime({ opencodeBinary: '/missing/opencode2' });
|
|
123
141
|
|
|
124
142
|
await expect(runtime.applyOpencodeBinaryFromSettings({ strict: true })).rejects.toMatchObject({
|
|
125
143
|
code: 'OPENCODE_BINARY_INVALID',
|
|
126
|
-
message: expect.stringContaining('Configured OpenCode binary not found: /missing/
|
|
144
|
+
message: expect.stringContaining('Configured OpenCode binary not found: /missing/opencode2'),
|
|
127
145
|
});
|
|
128
146
|
});
|
|
129
147
|
|
|
@@ -139,7 +157,7 @@ describe('OpenCode env runtime', () => {
|
|
|
139
157
|
|
|
140
158
|
it('applies a valid configured executable OpenCode binary', async () => {
|
|
141
159
|
const dir = createTempDir('openchamber-opencode-bin-');
|
|
142
|
-
const binary = path.join(dir, '
|
|
160
|
+
const binary = path.join(dir, 'opencode2');
|
|
143
161
|
fs.writeFileSync(binary, '#!/bin/sh\nexit 0\n');
|
|
144
162
|
fs.chmodSync(binary, 0o755);
|
|
145
163
|
const { runtime, state } = createRuntime({ opencodeBinary: binary });
|
|
@@ -150,16 +168,78 @@ describe('OpenCode env runtime', () => {
|
|
|
150
168
|
expect(state.resolvedOpencodeBinarySource).toBe('settings');
|
|
151
169
|
});
|
|
152
170
|
|
|
171
|
+
it('rejects a configured 1.x opencode version in strict mode', async () => {
|
|
172
|
+
const dir = createTempDir('openchamber-opencode-1x-');
|
|
173
|
+
const binary = path.join(dir, 'opencode');
|
|
174
|
+
writeVersionBinary(binary, '1.18.4');
|
|
175
|
+
const { runtime } = createRuntime({ opencodeBinary: binary });
|
|
176
|
+
|
|
177
|
+
await expect(runtime.applyOpencodeBinaryFromSettings({ strict: true })).rejects.toMatchObject({
|
|
178
|
+
code: 'OPENCODE_BINARY_INVALID',
|
|
179
|
+
message: expect.stringMatching(/1\.x.*OpenCode v2/s),
|
|
180
|
+
});
|
|
181
|
+
});
|
|
182
|
+
|
|
183
|
+
it('rejects an explicit OPENCODE_BINARY that reports a 1.x version', () => {
|
|
184
|
+
const dir = createTempDir('openchamber-env-opencode-1x-');
|
|
185
|
+
const binary = path.join(dir, process.platform === 'win32' ? 'opencode.exe' : 'opencode');
|
|
186
|
+
writeVersionBinary(binary, '1.18.4');
|
|
187
|
+
process.env.OPENCODE_BINARY = binary;
|
|
188
|
+
const { runtime } = createRuntime({});
|
|
189
|
+
|
|
190
|
+
expect(() => runtime.resolveOpencodeCliPath()).toThrow(
|
|
191
|
+
expect.objectContaining({
|
|
192
|
+
code: 'OPENCODE_BINARY_INVALID',
|
|
193
|
+
message: expect.stringMatching(/1\.x.*OpenCode v2/s),
|
|
194
|
+
})
|
|
195
|
+
);
|
|
196
|
+
});
|
|
197
|
+
|
|
198
|
+
it('discovers opencode from a home-directory install location', () => {
|
|
199
|
+
const home = createTempDir('openchamber-home-opencode-');
|
|
200
|
+
const binary = path.join(home, '.bun', 'bin', 'opencode');
|
|
201
|
+
writeVersionBinary(binary, '2.0.12');
|
|
202
|
+
process.env.PATH = createTempDir('openchamber-empty-path-home-');
|
|
203
|
+
delete process.env.OPENCODE_BINARY;
|
|
204
|
+
delete process.env.OPENCHAMBER_BUNDLED_OPENCODE_CLI_DIR;
|
|
205
|
+
const { runtime, state } = createRuntime({}, {
|
|
206
|
+
spawnSync: () => ({ status: 1, stdout: '', stderr: '' }),
|
|
207
|
+
homedir: () => home,
|
|
208
|
+
});
|
|
209
|
+
|
|
210
|
+
expect(runtime.resolveOpencodeCliPath()).toBe(binary);
|
|
211
|
+
expect(state.resolvedOpencodeBinarySource).toBe('fallback');
|
|
212
|
+
});
|
|
213
|
+
|
|
214
|
+
it('does not treat a PATH 1.x opencode binary as a resolved CLI', () => {
|
|
215
|
+
const pathDir = createTempDir('openchamber-path-opencode-1x-');
|
|
216
|
+
const pathBinary = path.join(pathDir, process.platform === 'win32' ? 'opencode.exe' : 'opencode');
|
|
217
|
+
fs.writeFileSync(pathBinary, '#!/bin/sh\nexit 0\n');
|
|
218
|
+
if (process.platform !== 'win32') {
|
|
219
|
+
fs.chmodSync(pathBinary, 0o755);
|
|
220
|
+
}
|
|
221
|
+
process.env.PATH = pathDir;
|
|
222
|
+
delete process.env.OPENCODE_BINARY;
|
|
223
|
+
delete process.env.OPENCHAMBER_BUNDLED_OPENCODE_CLI_DIR;
|
|
224
|
+
const emptyHome = createTempDir('openchamber-empty-home-1x-');
|
|
225
|
+
const { runtime, state } = createRuntime({}, {
|
|
226
|
+
spawnSync: () => ({ status: 1, stdout: '', stderr: '' }),
|
|
227
|
+
homedir: () => emptyHome,
|
|
228
|
+
});
|
|
229
|
+
|
|
230
|
+
expect(runtime.resolveOpencodeCliPath()).toBeNull();
|
|
231
|
+
expect(state.resolvedOpencodeBinarySource).toBeNull();
|
|
232
|
+
});
|
|
233
|
+
|
|
153
234
|
it('prefers a user-installed OpenCode from PATH over the bundled CLI', () => {
|
|
154
235
|
const bundledDir = createTempDir('openchamber-bundled-opencode-');
|
|
155
|
-
const bundledBinary = path.join(bundledDir, process.platform === 'win32' ? '
|
|
236
|
+
const bundledBinary = path.join(bundledDir, process.platform === 'win32' ? 'opencode2.exe' : 'opencode2');
|
|
156
237
|
const pathDir = createTempDir('openchamber-path-opencode-');
|
|
157
238
|
const pathBinary = path.join(pathDir, process.platform === 'win32' ? 'opencode.exe' : 'opencode');
|
|
158
239
|
fs.writeFileSync(bundledBinary, '#!/bin/sh\nexit 0\n');
|
|
159
|
-
|
|
240
|
+
writeVersionBinary(pathBinary, '2.0.12');
|
|
160
241
|
if (process.platform !== 'win32') {
|
|
161
242
|
fs.chmodSync(bundledBinary, 0o755);
|
|
162
|
-
fs.chmodSync(pathBinary, 0o755);
|
|
163
243
|
}
|
|
164
244
|
process.env.OPENCHAMBER_BUNDLED_OPENCODE_CLI_DIR = bundledDir;
|
|
165
245
|
process.env.PATH = pathDir;
|
|
@@ -172,14 +252,13 @@ describe('OpenCode env runtime', () => {
|
|
|
172
252
|
|
|
173
253
|
it('keeps explicit OpenCode binary ahead of bundled CLI', () => {
|
|
174
254
|
const bundledDir = createTempDir('openchamber-bundled-opencode-');
|
|
175
|
-
const bundledBinary = path.join(bundledDir, process.platform === 'win32' ? '
|
|
255
|
+
const bundledBinary = path.join(bundledDir, process.platform === 'win32' ? 'opencode2.exe' : 'opencode2');
|
|
176
256
|
const explicitDir = createTempDir('openchamber-explicit-opencode-');
|
|
177
|
-
const explicitBinary = path.join(explicitDir, process.platform === 'win32' ? '
|
|
257
|
+
const explicitBinary = path.join(explicitDir, process.platform === 'win32' ? 'opencode2.exe' : 'opencode2');
|
|
178
258
|
fs.writeFileSync(bundledBinary, '#!/bin/sh\nexit 0\n');
|
|
179
|
-
|
|
259
|
+
writeVersionBinary(explicitBinary, '2.0.12');
|
|
180
260
|
if (process.platform !== 'win32') {
|
|
181
261
|
fs.chmodSync(bundledBinary, 0o755);
|
|
182
|
-
fs.chmodSync(explicitBinary, 0o755);
|
|
183
262
|
}
|
|
184
263
|
process.env.OPENCHAMBER_BUNDLED_OPENCODE_CLI_DIR = bundledDir;
|
|
185
264
|
process.env.OPENCODE_BINARY = explicitBinary;
|
|
@@ -189,10 +268,10 @@ describe('OpenCode env runtime', () => {
|
|
|
189
268
|
expect(state.resolvedOpencodeBinarySource).toBe('env');
|
|
190
269
|
});
|
|
191
270
|
|
|
192
|
-
it('
|
|
271
|
+
it('does not use a bundled OpenCode CLI from Electron resourcesPath', () => {
|
|
193
272
|
const resourcesPath = createTempDir('openchamber-resources-');
|
|
194
273
|
const bundledDir = path.join(resourcesPath, 'opencode-cli');
|
|
195
|
-
const bundledBinary = path.join(bundledDir, process.platform === 'win32' ? '
|
|
274
|
+
const bundledBinary = path.join(bundledDir, process.platform === 'win32' ? 'opencode2.exe' : 'opencode2');
|
|
196
275
|
fs.mkdirSync(bundledDir, { recursive: true });
|
|
197
276
|
fs.writeFileSync(bundledBinary, '#!/bin/sh\nexit 0\n');
|
|
198
277
|
if (process.platform !== 'win32') {
|
|
@@ -203,18 +282,142 @@ describe('OpenCode env runtime', () => {
|
|
|
203
282
|
value: resourcesPath,
|
|
204
283
|
});
|
|
205
284
|
process.env.PATH = createTempDir('openchamber-empty-path-');
|
|
206
|
-
|
|
285
|
+
process.env.OPENCHAMBER_BUNDLED_OPENCODE_CLI_DIR = bundledDir;
|
|
207
286
|
delete process.env.OPENCODE_BINARY;
|
|
208
|
-
// The bundled CLI is the LAST resort now — hide the machine's own installs
|
|
209
|
-
// from the home-directory fallbacks and shell discovery.
|
|
210
287
|
const emptyHome = createTempDir('openchamber-empty-home-');
|
|
211
288
|
const { runtime, state } = createRuntime({}, {
|
|
212
289
|
spawnSync: () => ({ status: 1, stdout: '', stderr: '' }),
|
|
213
290
|
homedir: () => emptyHome,
|
|
214
291
|
});
|
|
215
292
|
|
|
216
|
-
expect(runtime.resolveOpencodeCliPath()).
|
|
293
|
+
expect(runtime.resolveOpencodeCliPath()).toBeNull();
|
|
294
|
+
expect(state.resolvedOpencodeBinarySource).toBeNull();
|
|
295
|
+
});
|
|
296
|
+
|
|
297
|
+
it('discovers a previously installed pin from the OpenChamber data dir', () => {
|
|
298
|
+
const dataDir = createTempDir('openchamber-installed-cli-');
|
|
299
|
+
const installedDir = path.join(dataDir, 'opencode-cli', '2.0.12');
|
|
300
|
+
const installedBinary = path.join(installedDir, process.platform === 'win32' ? 'opencode.exe' : 'opencode');
|
|
301
|
+
fs.mkdirSync(installedDir, { recursive: true });
|
|
302
|
+
fs.writeFileSync(installedBinary, '#!/bin/sh\nexit 0\n');
|
|
303
|
+
if (process.platform !== 'win32') {
|
|
304
|
+
fs.chmodSync(installedBinary, 0o755);
|
|
305
|
+
}
|
|
306
|
+
process.env.OPENCHAMBER_DATA_DIR = dataDir;
|
|
307
|
+
process.env.PATH = createTempDir('openchamber-empty-path-installed-');
|
|
308
|
+
delete process.env.OPENCODE_BINARY;
|
|
309
|
+
delete process.env.OPENCHAMBER_BUNDLED_OPENCODE_CLI_DIR;
|
|
310
|
+
const emptyHome = createTempDir('openchamber-empty-home-installed-');
|
|
311
|
+
const { runtime, state } = createRuntime({}, {
|
|
312
|
+
spawnSync: () => ({ status: 1, stdout: '', stderr: '' }),
|
|
313
|
+
homedir: () => emptyHome,
|
|
314
|
+
});
|
|
315
|
+
|
|
316
|
+
expect(runtime.resolveOpencodeCliPath()).toBe(installedBinary);
|
|
317
|
+
expect(state.resolvedOpencodeBinarySource).toBe('installed');
|
|
318
|
+
});
|
|
319
|
+
|
|
320
|
+
it('installs the pinned CLI when discovery finds nothing', async () => {
|
|
321
|
+
const installed = path.join(createTempDir('openchamber-ensure-install-'), process.platform === 'win32' ? 'opencode2.exe' : 'opencode2');
|
|
322
|
+
fs.writeFileSync(installed, '#!/bin/sh\nexit 0\n');
|
|
323
|
+
if (process.platform !== 'win32') {
|
|
324
|
+
fs.chmodSync(installed, 0o755);
|
|
325
|
+
}
|
|
326
|
+
process.env.PATH = createTempDir('openchamber-empty-path-ensure-');
|
|
327
|
+
delete process.env.OPENCODE_BINARY;
|
|
328
|
+
delete process.env.OPENCHAMBER_BUNDLED_OPENCODE_CLI_DIR;
|
|
329
|
+
const emptyHome = createTempDir('openchamber-empty-home-ensure-');
|
|
330
|
+
const { runtime, state } = createRuntime({}, {
|
|
331
|
+
spawnSync: () => ({ status: 1, stdout: '', stderr: '' }),
|
|
332
|
+
homedir: () => emptyHome,
|
|
333
|
+
ensurePinnedOpenCode2Cli: async () => ({
|
|
334
|
+
path: installed,
|
|
335
|
+
version: '2.0.12',
|
|
336
|
+
source: 'installed',
|
|
337
|
+
installed: true,
|
|
338
|
+
}),
|
|
339
|
+
});
|
|
340
|
+
|
|
341
|
+
await expect(runtime.ensurePinnedOpenCode2CliEnv()).resolves.toBe(installed);
|
|
342
|
+
expect(state.resolvedOpencodeBinary).toBe(installed);
|
|
343
|
+
expect(state.resolvedOpencodeBinarySource).toBe('installed');
|
|
344
|
+
expect(process.env.OPENCODE_BINARY).toBe(installed);
|
|
345
|
+
});
|
|
346
|
+
|
|
347
|
+
it('falls back to a bundled 2.x CLI only after pin install fails', async () => {
|
|
348
|
+
const bundledDir = createTempDir('openchamber-bundled-fallback-');
|
|
349
|
+
const bundledName = process.platform === 'win32' ? 'opencode2.exe' : 'opencode2';
|
|
350
|
+
const bundledBinary = path.join(bundledDir, bundledName);
|
|
351
|
+
writeVersionBinary(bundledBinary, '2.0.12');
|
|
352
|
+
process.env.OPENCHAMBER_BUNDLED_OPENCODE_CLI_DIR = bundledDir;
|
|
353
|
+
process.env.PATH = createTempDir('openchamber-empty-path-bundled-fallback-');
|
|
354
|
+
process.env.OPENCHAMBER_DATA_DIR = createTempDir('openchamber-empty-data-bundled-fallback-');
|
|
355
|
+
delete process.env.OPENCODE_BINARY;
|
|
356
|
+
const emptyHome = createTempDir('openchamber-empty-home-bundled-fallback-');
|
|
357
|
+
const missingError = Object.assign(new Error('OpenCode CLI missing'), { code: 'OPENCODE_CLI_MISSING' });
|
|
358
|
+
const { runtime, state } = createRuntime({}, {
|
|
359
|
+
spawnSync: () => ({ status: 1, stdout: '', stderr: '' }),
|
|
360
|
+
homedir: () => emptyHome,
|
|
361
|
+
ensurePinnedOpenCode2Cli: async () => {
|
|
362
|
+
throw missingError;
|
|
363
|
+
},
|
|
364
|
+
});
|
|
365
|
+
|
|
366
|
+
await expect(runtime.ensurePinnedOpenCode2CliEnv()).resolves.toBe(bundledBinary);
|
|
367
|
+
expect(state.resolvedOpencodeBinary).toBe(bundledBinary);
|
|
217
368
|
expect(state.resolvedOpencodeBinarySource).toBe('bundled');
|
|
369
|
+
expect(process.env.OPENCODE_BINARY).toBe(bundledBinary);
|
|
370
|
+
});
|
|
371
|
+
|
|
372
|
+
it('falls back to bundled opencode name when pin install fails', async () => {
|
|
373
|
+
const bundledDir = createTempDir('openchamber-bundled-opencode-name-');
|
|
374
|
+
const bundledName = process.platform === 'win32' ? 'opencode.exe' : 'opencode';
|
|
375
|
+
const bundledBinary = path.join(bundledDir, bundledName);
|
|
376
|
+
writeVersionBinary(bundledBinary, '2.0.12');
|
|
377
|
+
process.env.OPENCHAMBER_BUNDLED_OPENCODE_CLI_DIR = bundledDir;
|
|
378
|
+
process.env.PATH = createTempDir('openchamber-empty-path-bundled-opencode-');
|
|
379
|
+
process.env.OPENCHAMBER_DATA_DIR = createTempDir('openchamber-empty-data-bundled-opencode-');
|
|
380
|
+
delete process.env.OPENCODE_BINARY;
|
|
381
|
+
const emptyHome = createTempDir('openchamber-empty-home-bundled-opencode-');
|
|
382
|
+
const missingError = Object.assign(new Error('OpenCode CLI missing'), { code: 'OPENCODE_CLI_MISSING' });
|
|
383
|
+
const { runtime, state } = createRuntime({}, {
|
|
384
|
+
spawnSync: () => ({ status: 1, stdout: '', stderr: '' }),
|
|
385
|
+
homedir: () => emptyHome,
|
|
386
|
+
ensurePinnedOpenCode2Cli: async () => {
|
|
387
|
+
throw missingError;
|
|
388
|
+
},
|
|
389
|
+
});
|
|
390
|
+
|
|
391
|
+
await expect(runtime.ensurePinnedOpenCode2CliEnv()).resolves.toBe(bundledBinary);
|
|
392
|
+
expect(state.resolvedOpencodeBinarySource).toBe('bundled');
|
|
393
|
+
expect(process.env.OPENCODE_BINARY).toBe(bundledBinary);
|
|
394
|
+
});
|
|
395
|
+
|
|
396
|
+
it('does not adopt a bundled 1.x CLI after pin install fails', async () => {
|
|
397
|
+
const bundledDir = createTempDir('openchamber-bundled-1x-');
|
|
398
|
+
const bundledName = process.platform === 'win32' ? 'opencode2.exe' : 'opencode2';
|
|
399
|
+
const bundledBinary = path.join(bundledDir, bundledName);
|
|
400
|
+
writeVersionBinary(bundledBinary, '1.18.4');
|
|
401
|
+
process.env.OPENCHAMBER_BUNDLED_OPENCODE_CLI_DIR = bundledDir;
|
|
402
|
+
process.env.PATH = createTempDir('openchamber-empty-path-bundled-1x-');
|
|
403
|
+
process.env.OPENCHAMBER_DATA_DIR = createTempDir('openchamber-empty-data-bundled-1x-');
|
|
404
|
+
delete process.env.OPENCODE_BINARY;
|
|
405
|
+
const emptyHome = createTempDir('openchamber-empty-home-bundled-1x-');
|
|
406
|
+
const missingError = Object.assign(new Error('OpenCode CLI missing'), { code: 'OPENCODE_CLI_MISSING' });
|
|
407
|
+
const { runtime, state } = createRuntime({}, {
|
|
408
|
+
spawnSync: () => ({ status: 1, stdout: '', stderr: '' }),
|
|
409
|
+
homedir: () => emptyHome,
|
|
410
|
+
ensurePinnedOpenCode2Cli: async () => {
|
|
411
|
+
throw missingError;
|
|
412
|
+
},
|
|
413
|
+
});
|
|
414
|
+
|
|
415
|
+
await expect(runtime.ensurePinnedOpenCode2CliEnv()).rejects.toMatchObject({
|
|
416
|
+
code: 'OPENCODE_CLI_MISSING',
|
|
417
|
+
});
|
|
418
|
+
expect(state.resolvedOpencodeBinary).toBeNull();
|
|
419
|
+
expect(state.resolvedOpencodeBinarySource).toBeNull();
|
|
420
|
+
expect(process.env.OPENCODE_BINARY).toBeUndefined();
|
|
218
421
|
});
|
|
219
422
|
|
|
220
423
|
itIf(process.platform === 'darwin')('rejects known macOS OpenCode app bundle executable paths', async () => {
|
|
@@ -265,7 +468,8 @@ describe('OpenCode env runtime', () => {
|
|
|
265
468
|
const cliBinary = path.join(createTempDir('openchamber-cli-'), 'opencode.exe');
|
|
266
469
|
fs.mkdirSync(path.dirname(desktopBinary), { recursive: true });
|
|
267
470
|
fs.writeFileSync(desktopBinary, '');
|
|
268
|
-
fs.writeFileSync(cliBinary, '');
|
|
471
|
+
fs.writeFileSync(cliBinary, `#!${process.execPath}\nconsole.log('2.0.12');\n`);
|
|
472
|
+
fs.chmodSync(cliBinary, 0o755);
|
|
269
473
|
process.env.LOCALAPPDATA = localAppData;
|
|
270
474
|
process.env.PATH = createTempDir('openchamber-empty-path-');
|
|
271
475
|
process.env.SystemRoot = createTempDir('openchamber-empty-systemroot-');
|
|
@@ -2,6 +2,7 @@ import { registerFsRoutes } from '../fs/routes.js';
|
|
|
2
2
|
import { registerQuotaRoutes } from '../quota/routes.js';
|
|
3
3
|
import { registerSmallModelRoutes } from '../small-model/routes.js';
|
|
4
4
|
import { registerSessionGoalRoutes } from '../session-goal/routes.js';
|
|
5
|
+
import { registerSessionMetadataRoutes } from '../session-metadata/routes.js';
|
|
5
6
|
import { registerGitHubRoutes } from '../github/routes.js';
|
|
6
7
|
import { registerGitRoutes } from '../git/routes.js';
|
|
7
8
|
import { registerMagicPromptRoutes } from '../magic-prompts/routes.js';
|
|
@@ -113,6 +114,7 @@ export const createFeatureRoutesRuntime = (dependencies) => {
|
|
|
113
114
|
isUnsafeSkillRelativePath,
|
|
114
115
|
buildOpenCodeUrl,
|
|
115
116
|
getOpenCodeAuthHeaders,
|
|
117
|
+
getIsExternalOpenCode = () => false,
|
|
116
118
|
getSmallModelService: routeGetSmallModelService,
|
|
117
119
|
getOpenCodePort,
|
|
118
120
|
buildAugmentedPath,
|
|
@@ -132,6 +134,10 @@ export const createFeatureRoutesRuntime = (dependencies) => {
|
|
|
132
134
|
getServerId,
|
|
133
135
|
sessionIndexService,
|
|
134
136
|
notifyContactTurnComplete,
|
|
137
|
+
sessionMetadataStore = null,
|
|
138
|
+
onSessionMetadataWritten = null,
|
|
139
|
+
persistSessionGoal = null,
|
|
140
|
+
readSessionMetadata = null,
|
|
135
141
|
} = routeDependencies;
|
|
136
142
|
|
|
137
143
|
registerSettingsUtilityRoutes(app, {
|
|
@@ -164,6 +170,7 @@ export const createFeatureRoutesRuntime = (dependencies) => {
|
|
|
164
170
|
refreshOpenCodeAfterConfigChange,
|
|
165
171
|
buildOpenCodeUrl,
|
|
166
172
|
getOpenCodeAuthHeaders,
|
|
173
|
+
getIsExternalOpenCode,
|
|
167
174
|
onSettingsPersisted: (updated, changes) => {
|
|
168
175
|
if (!questionAutoDelegateRuntime) return;
|
|
169
176
|
if (!Object.prototype.hasOwnProperty.call(changes ?? {}, 'questionAutoDelegateEnabled')) return;
|
|
@@ -293,6 +300,7 @@ export const createFeatureRoutesRuntime = (dependencies) => {
|
|
|
293
300
|
resolveOptionalProjectDirectory,
|
|
294
301
|
refreshOpenCodeAfterConfigChange,
|
|
295
302
|
clientReloadDelayMs,
|
|
303
|
+
waitForOpenCodeReady,
|
|
296
304
|
getAgentSources,
|
|
297
305
|
getAgentConfig,
|
|
298
306
|
createAgent,
|
|
@@ -387,7 +395,35 @@ export const createFeatureRoutesRuntime = (dependencies) => {
|
|
|
387
395
|
throw new Error('Small model service is not configured');
|
|
388
396
|
});
|
|
389
397
|
registerSmallModelRoutes(app, { getSmallModelService });
|
|
390
|
-
registerSessionGoalRoutes(app
|
|
398
|
+
registerSessionGoalRoutes(app, {
|
|
399
|
+
// Reuse the index-provided store seam — do not invent a second store.
|
|
400
|
+
persistSessionGoal: typeof persistSessionGoal === 'function'
|
|
401
|
+
? persistSessionGoal
|
|
402
|
+
: (sessionMetadataStore
|
|
403
|
+
? async (sessionId, _directory, goal) => {
|
|
404
|
+
const metadata = await sessionMetadataStore.setSessionMetadata(sessionId, {
|
|
405
|
+
openchamber: { goal },
|
|
406
|
+
});
|
|
407
|
+
return metadata;
|
|
408
|
+
}
|
|
409
|
+
: null),
|
|
410
|
+
readSessionMetadata: typeof readSessionMetadata === 'function'
|
|
411
|
+
? readSessionMetadata
|
|
412
|
+
: (sessionMetadataStore
|
|
413
|
+
? (sessionId) => sessionMetadataStore.get(sessionId)
|
|
414
|
+
: null),
|
|
415
|
+
// Same arm path as PUT /api/openchamber/sessions/:id/metadata.
|
|
416
|
+
onGoalPersisted: typeof onSessionMetadataWritten === 'function'
|
|
417
|
+
? onSessionMetadataWritten
|
|
418
|
+
: null,
|
|
419
|
+
});
|
|
420
|
+
if (sessionMetadataStore) {
|
|
421
|
+
registerSessionMetadataRoutes(app, {
|
|
422
|
+
sessionMetadataStore,
|
|
423
|
+
broadcastGlobalUiEvent,
|
|
424
|
+
onMetadataWritten: onSessionMetadataWritten,
|
|
425
|
+
});
|
|
426
|
+
}
|
|
391
427
|
registerGitHubRoutes(app);
|
|
392
428
|
const broadcastOpenChamberEvent = createOpenChamberEventBroadcaster({
|
|
393
429
|
getOpenChamberEventClients,
|