@openchambery/web 2.0.0-beta.26 → 2.0.0-beta.29
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 +14 -0
- package/dist/assets/{AgentAvatar-FyTdEOMr.js → AgentAvatar-O2x_1cFU.js} +1 -1
- package/dist/assets/{AssistantView-DvOO2Kf5.js → AssistantView-Bphg-QIn.js} +3 -3
- package/dist/assets/{DiagramView-BqjeSOIg.js → DiagramView-CRD_GFch.js} +1 -1
- package/dist/assets/{FadeInOnReveal-DHDffzHs.js → FadeInOnReveal-CMBd5BTd.js} +1 -1
- package/dist/assets/{Icon-D7NdMmbi.js → Icon-BN87CyS4.js} +1 -1
- package/dist/assets/{MarkdownRenderer-kpkRqS42.js → MarkdownRenderer-D5KEGdBn.js} +2 -2
- package/dist/assets/{MarkdownRendererImpl-CzBos-Gt.js → MarkdownRendererImpl-C143YQsV.js} +2 -2
- package/dist/assets/{MarkstreamRendererImpl-RY_rAgKD.js → MarkstreamRendererImpl-C4rgOuOP.js} +1 -1
- package/dist/assets/{MobileDetailNavigation-DgtRh0zu.js → MobileDetailNavigation-D5tcF2Kg.js} +1 -1
- package/dist/assets/{MobileShareBridge-BM3cN-gG.js → MobileShareBridge-Bm0JZKHc.js} +3 -3
- package/dist/assets/MobileSurface-BzzBCVkG.js +2 -0
- package/dist/assets/{MultiRunWindow-Bq4gz78C.js → MultiRunWindow-B6Sb3aEG.js} +1 -1
- package/dist/assets/{OnboardingScreen-DMbPMwlp.js → OnboardingScreen-BLjZIBOs.js} +1 -1
- package/dist/assets/{OverlayScrollbar-mI9WVa7E.js → OverlayScrollbar-BOqRklf0.js} +1 -1
- package/dist/assets/SettingsView-Ds6x2z0F.js +25 -0
- package/dist/assets/{SettingsWindow-CHPjbEnI.js → SettingsWindow-D6CN5ahJ.js} +1 -1
- package/dist/assets/{TerminalView-DCJuL2nP.js → TerminalView-DkzaJJfh.js} +1 -1
- package/dist/assets/{ToolOutputDialog-Cg_Q1t3X.js → ToolOutputDialog-BegFpg1h.js} +1 -1
- package/dist/assets/{appThemeRegistry-BxuflaQt.js → appThemeRegistry-BMzuqD8_.js} +1 -1
- package/dist/assets/{button-e8fYWIMM.js → button-Ck8ipzBm.js} +1 -1
- package/dist/assets/{collapsible-B6wLXHi3.js → collapsible-nU99prkD.js} +1 -1
- package/dist/assets/{context-menu-Bq7W1d7y.js → context-menu-DMzZN1oi.js} +1 -1
- package/dist/assets/{desktopHosts-C8vf9vRY.js → desktopHosts-DuftEj8B.js} +1 -1
- package/dist/assets/{desktopRelayRestore-DDJtljeu.js → desktopRelayRestore-g378BdZf.js} +1 -1
- package/dist/assets/{diagram-BBL-LfsC.js → diagram-CvO4P8ds.js} +1 -1
- package/dist/assets/{dialog-MRsJpHzu.js → dialog-ByGUkS3c.js} +1 -1
- package/dist/assets/{es-DvPwAj0y.js → es-DCjl24dV.js} +3 -3
- package/dist/assets/fileMentionTouchSelection-Cuk9C_jN.js +2 -0
- package/dist/assets/{fileQueries-CBu-ytXS.js → fileQueries-DuF89nFO.js} +1 -1
- package/dist/assets/{fr-XQsoaNKY.js → fr-DfJ5j0t5.js} +3 -3
- package/dist/assets/{gitApi-CuIUGt04.js → gitApi-BlWYyRdx.js} +3 -3
- package/dist/assets/{input-DbOe1mdT.js → input-CXhFmHcq.js} +1 -1
- package/dist/assets/insertionBoundaries-lAwXo-R-.js +1 -0
- package/dist/assets/{ja-DslzO96s.js → ja-B_9_w2Xf.js} +3 -3
- package/dist/assets/{ko-C_9XOAL0.js → ko-DlGbo3kV.js} +3 -3
- package/dist/assets/{main-Sd4Auh_x.js → main-BmiKRvkB.js} +2 -2
- package/dist/assets/main-D5SdJE5d.js +169 -0
- package/dist/assets/main-qSeeJRW1.css +1 -0
- package/dist/assets/{markdownHeightCache-B5VSLl79.js → markdownHeightCache-g_UsWzbA.js} +1 -1
- package/dist/assets/{mcpQueries-BYNrkehM.js → mcpQueries-0Oe-8vHC.js} +1 -1
- package/dist/assets/{miniChat-Hp9bNmS0.js → miniChat-CSwFHa9K.js} +2 -2
- package/dist/assets/mobile-DzJO7usb.js +2 -0
- package/dist/assets/multirun-Bdd0yE3M.js +3 -0
- package/dist/assets/persistence-Dnj1QOdX.js +3 -0
- package/dist/assets/{pl-Dst9Wk53.js → pl-DjUsS8Ei.js} +3 -3
- package/dist/assets/platform-pfYy32vr.js +6 -0
- package/dist/assets/projectMeta-2dbON46t.js +1 -0
- package/dist/assets/{pt-BR-Dq394BPb.js → pt-BR-B8sV7qo1.js} +3 -3
- package/dist/assets/renderElectronMiniChatApp-Bjf5yS-2.js +2 -0
- package/dist/assets/renderMobileApp-Bv0VxQo1.js +28 -0
- package/dist/assets/{runtimeConfig-pTYamMLy.js → runtimeConfig-BOk04YEP.js} +1 -1
- package/dist/assets/runtimeEndpointReset-DsuP5FPT.css +1 -0
- package/dist/assets/runtimeEndpointReset-Oo4f7T5P.js +148 -0
- package/dist/assets/{sessionChangeSummary-DcnZyuWt.js → sessionChangeSummary-Bf5wJLSv.js} +2 -2
- package/dist/assets/{sessionLookup-B3t-FerN.js → sessionLookup-pga3-Etf.js} +1 -1
- package/dist/assets/{sortable-tabs-strip-B0sZ7mm6.js → sortable-tabs-strip-CHqMkTI3.js} +1 -1
- package/dist/assets/{store-D8fmTWK3.js → store-Cuktref-.js} +3 -3
- package/dist/assets/{terminalApi-CJyWCuc8.js → terminalApi-BgGraKxs.js} +1 -1
- package/dist/assets/{textarea-B5GMntHk.js → textarea-B0Od84ZM.js} +1 -1
- package/dist/assets/{tooltip-C64SdRvR.js → tooltip-BKL7J5hx.js} +1 -1
- package/dist/assets/{uk-8XAR75Qh.js → uk-Bh7HXyLp.js} +3 -3
- package/dist/assets/{url-DF1NFwt_.js → url-DNRMq_0R.js} +1 -1
- package/dist/assets/{useAppFontEffects-BQklk6XB.js → useAppFontEffects-gMl7z7An.js} +53 -53
- package/dist/assets/{useAssistantUIStore-B75cQm_z.js → useAssistantUIStore-DBwfg9nX.js} +1 -1
- package/dist/assets/useDesktopWindowControlsLayout-GOgauAay.js +1 -0
- package/dist/assets/{useEffectiveDirectory-Ddx7sZ5A.js → useEffectiveDirectory-DgAB7HWq.js} +1 -1
- package/dist/assets/{useFontPreferences-CrteQiAp.js → useFontPreferences-Djxu_uRy.js} +1 -1
- package/dist/assets/useMobileNavigationStore-D7uAoBXe.js +415 -0
- package/dist/assets/useSessionAutoCleanup-Cg7KJFGa.js +2 -0
- package/dist/assets/{useUIStore-Brsa2mKs.js → useUIStore-Dfx6TKvi.js} +1 -1
- package/dist/assets/{useWorkerHighlightedLines-VOtVUGkO.js → useWorkerHighlightedLines-DKa2EWBH.js} +1 -1
- package/dist/assets/vendor-opencode-client-MF2GBrFx.js +12 -0
- package/dist/assets/{vendor-zustand-B4dlGEM2.js → vendor-zustand-DzRqcymP.js} +1 -1
- package/dist/assets/{zh-CN-B-ILfrqC.js → zh-CN-Uzia0gk4.js} +3 -3
- package/dist/assets/{zh-TW-5HeTVP4z.js → zh-TW-CiI7DLdB.js} +3 -3
- package/dist/index.html +10 -10
- package/dist/mini-chat.html +10 -10
- package/dist/mobile.html +10 -10
- package/package.json +3 -3
- package/server/index.js +8 -20
- package/server/lib/git/DOCUMENTATION.md +1 -0
- package/server/lib/git/routes.js +7 -4
- package/server/lib/git/routes.test.js +34 -0
- package/server/lib/git/service.js +3 -1
- package/server/lib/llm/DOCUMENTATION.md +6 -4
- package/server/lib/llm/generate.js +8 -9
- package/server/lib/llm/generate.test.js +33 -6
- package/server/lib/opencode/DOCUMENTATION.md +24 -20
- package/server/lib/opencode/agents.js +13 -22
- package/server/lib/opencode/auth-state-runtime.js +3 -3
- package/server/lib/opencode/auth-state-runtime.test.js +59 -0
- package/server/lib/opencode/auth.d.ts +13 -0
- package/server/lib/opencode/auth.js +74 -64
- package/server/lib/opencode/auth.test.js +57 -0
- package/server/lib/opencode/commands.js +1 -1
- package/server/lib/opencode/config-entities-layering.test.js +72 -0
- package/server/lib/opencode/config-entity-routes.js +9 -122
- package/server/lib/opencode/config-entity-routes.test.js +7 -11
- package/server/lib/opencode/config-files.d.ts +17 -0
- package/server/lib/opencode/config-files.js +187 -0
- package/server/lib/opencode/config-files.test.js +201 -0
- package/server/lib/opencode/core-routes.js +0 -1
- package/server/lib/opencode/core-routes.test.js +0 -15
- package/server/lib/opencode/credential-boundary.d.ts +1 -0
- package/server/lib/opencode/credential-boundary.js +11 -0
- package/server/lib/opencode/ensure-cli.js +4 -1
- package/server/lib/opencode/env-config.js +4 -1
- package/server/lib/opencode/env-config.test.js +28 -0
- package/server/lib/opencode/env-runtime.test.js +2 -1
- package/server/lib/opencode/feature-routes-runtime.js +9 -7
- package/server/lib/opencode/hmr-state-runtime.js +4 -4
- package/server/lib/opencode/lifecycle.js +1 -49
- package/server/lib/opencode/lifecycle.test.js +30 -4
- package/server/lib/opencode/location-release.d.ts +8 -0
- package/server/lib/opencode/location-release.js +32 -0
- package/server/lib/opencode/location-release.test.js +43 -0
- package/server/lib/opencode/mcp.js +1 -1
- package/server/lib/opencode/opencode2-pin.js +2 -1
- package/server/lib/opencode/owned-runtime-upgrade.test.js +5 -5
- package/server/lib/opencode/plugin-routes.test.js +4 -2
- package/server/lib/opencode/plugins.js +12 -60
- package/server/lib/opencode/plugins.test.js +5 -3
- package/server/lib/opencode/providers.js +6 -3
- package/server/lib/opencode/proxy.js +7 -0
- package/server/lib/opencode/proxy.test.js +13 -0
- package/server/lib/opencode/routes.js +5 -5
- package/server/lib/opencode/routes.test.js +18 -18
- package/server/lib/opencode/runtime-contract.js +3 -2
- package/server/lib/opencode/runtime-contract.test.js +12 -12
- package/server/lib/opencode/shared.js +6 -175
- package/server/lib/opencode/upgrade-screen.js +4 -11
- package/server/lib/opencode/upgrade-screen.test.js +25 -25
- package/server/lib/opencode/version-alignment.test.js +40 -0
- package/server/lib/quota/DOCUMENTATION.md +9 -0
- package/server/lib/quota/provider-credentials.test.js +29 -0
- package/server/lib/quota/providers/claude.js +4 -4
- package/server/lib/quota/providers/codex.js +4 -4
- package/server/lib/quota/providers/codex.test.js +1 -1
- package/server/lib/quota/providers/copilot.js +5 -5
- package/server/lib/quota/providers/google/auth.js +3 -3
- package/server/lib/quota/providers/google/index.js +2 -2
- package/server/lib/quota/providers/index.js +3 -7
- package/server/lib/quota/providers/index.test.js +2 -2
- package/server/lib/quota/providers/kimi.js +4 -4
- package/server/lib/quota/providers/minimax-shared.js +4 -4
- package/server/lib/quota/providers/nanogpt.js +4 -4
- package/server/lib/quota/providers/openai.js +4 -4
- package/server/lib/quota/providers/openrouter.js +4 -4
- package/server/lib/quota/providers/wafer.js +4 -4
- package/server/lib/quota/providers/zai.js +4 -4
- package/server/lib/quota/providers/zhipuai-coding-plan.js +6 -20
- package/server/lib/quota/routes.js +1 -1
- package/server/lib/scheduled-tasks/DOCUMENTATION.md +1 -0
- package/server/lib/scheduled-tasks/runtime.js +2 -3
- package/server/lib/session-index/DOCUMENTATION.md +7 -0
- package/server/lib/session-index/event-ingest.js +13 -0
- package/server/lib/session-index/event-ingest.test.js +7 -0
- package/server/lib/session-index/service.js +45 -0
- package/server/lib/session-index/service.test.js +47 -0
- package/server/lib/session-metadata/DOCUMENTATION.md +6 -0
- package/server/lib/session-metadata/session-metadata-store.test.js +23 -0
- package/server/lib/session-metadata/session-projection.d.ts +1 -0
- package/server/lib/session-metadata/session-projection.js +23 -0
- package/server/lib/session-metadata/session-projection.test.js +35 -0
- package/server/lib/session-title/DOCUMENTATION.md +36 -8
- package/server/lib/session-title/native-runtime.test.js +42 -0
- package/server/lib/session-title/runtime.js +18 -8
- package/server/lib/session-title/session-access.js +108 -3
- package/server/lib/session-title/session-access.test.js +125 -7
- package/server/lib/tts/DOCUMENTATION.md +7 -5
- package/server/lib/tts/credential-source.test.js +23 -0
- package/server/lib/tts/routes.js +2 -2
- package/server/lib/tts/service.js +16 -37
- package/dist/assets/MobileSurface-ocVWenHA.js +0 -2
- package/dist/assets/SettingsView-CIZz40Jg.js +0 -25
- package/dist/assets/fileMentionTouchSelection-Bj67pPo7.js +0 -3
- package/dist/assets/insertionBoundaries-nzs1Q3YR.js +0 -1
- package/dist/assets/main-D2BzpnUt.css +0 -1
- package/dist/assets/main-DjilfYN7.js +0 -169
- package/dist/assets/mobile-fBLEOx9_.js +0 -2
- package/dist/assets/multirun-D7W7oCj4.js +0 -4
- package/dist/assets/persistence-CMWO3jFo.js +0 -3
- package/dist/assets/platform-B0p43squ.js +0 -6
- package/dist/assets/projectMeta-DrUKrYt0.js +0 -3
- package/dist/assets/renderElectronMiniChatApp-D1xipCO5.js +0 -2
- package/dist/assets/renderMobileApp-DSk7Qs-_.js +0 -28
- package/dist/assets/runtimeEndpointReset-B5HEMuiB.js +0 -149
- package/dist/assets/useDesktopWindowControlsLayout-CrzYwZDQ.js +0 -1
- package/dist/assets/useMobileNavigationStore-i4cXNoOf.js +0 -414
- package/dist/assets/useSessionAutoCleanup-Dzd0fGSe.js +0 -2
- package/dist/assets/vendor-opencode-client-C12gfjag.js +0 -12
- package/server/lib/opencode/snippets.js +0 -242
- package/server/lib/opencode/snippets.test.js +0 -97
|
@@ -29,7 +29,7 @@ function validateMcpName(name) {
|
|
|
29
29
|
*/
|
|
30
30
|
function resolveMcpScopeFromPath(layers, sourcePath) {
|
|
31
31
|
if (!sourcePath) return null;
|
|
32
|
-
return
|
|
32
|
+
return layers.projectLayers.some((layer) => layer.path === sourcePath) ? AGENT_SCOPE.PROJECT : AGENT_SCOPE.USER;
|
|
33
33
|
}
|
|
34
34
|
|
|
35
35
|
function ensureProjectMcpConfigPath(workingDirectory) {
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
// Pinned desktop/runtime opencode2. Upgrade and prepare must not fall back to 1.18.x.
|
|
2
|
-
export const PINNED_OPENCODE2_VERSION = '2.0.
|
|
2
|
+
export const PINNED_OPENCODE2_VERSION = '2.0.23';
|
|
3
|
+
export const MINIMUM_OPENCODE2_VERSION = '2.0.20';
|
|
3
4
|
|
|
4
5
|
// Global npm/bun package that installs the opencode v2 binary (not 1.x opencode-ai).
|
|
5
6
|
export const OPENCODE2_NPM_PACKAGE = '@opencode/cli';
|
|
@@ -86,15 +86,15 @@ describe('owned runtime upgrade (ticket 12)', () => {
|
|
|
86
86
|
|
|
87
87
|
it('verifies running serve identity before upgrade success', () => {
|
|
88
88
|
const contractOk = evaluateRuntimeContract({
|
|
89
|
-
serveVersion: '2.0.
|
|
89
|
+
serveVersion: '2.0.23',
|
|
90
90
|
reachable: true,
|
|
91
91
|
authenticated: true,
|
|
92
92
|
healthOk: true,
|
|
93
93
|
migrationAdmitTranscript: true,
|
|
94
94
|
});
|
|
95
95
|
expect(evaluateOwnedUpgradeResult({
|
|
96
|
-
targetVersion: '2.0.
|
|
97
|
-
serveVersion: '2.0.
|
|
96
|
+
targetVersion: '2.0.23',
|
|
97
|
+
serveVersion: '2.0.23',
|
|
98
98
|
binaryPath: ownedBinary,
|
|
99
99
|
expectedBinaryPath: ownedBinary,
|
|
100
100
|
contract: contractOk,
|
|
@@ -116,8 +116,8 @@ describe('owned runtime upgrade (ticket 12)', () => {
|
|
|
116
116
|
});
|
|
117
117
|
|
|
118
118
|
expect(evaluateOwnedUpgradeResult({
|
|
119
|
-
targetVersion: '2.0.
|
|
120
|
-
serveVersion: '2.0.
|
|
119
|
+
targetVersion: '2.0.23',
|
|
120
|
+
serveVersion: '2.0.23',
|
|
121
121
|
binaryPath: '/usr/bin/opencode',
|
|
122
122
|
expectedBinaryPath: ownedBinary,
|
|
123
123
|
contract: contractOk,
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { afterAll, afterEach, beforeAll, beforeEach, describe, expect, mock, test } from 'bun:test';
|
|
2
|
+
import { vi } from 'vitest';
|
|
2
3
|
import express from 'express';
|
|
3
4
|
import fs from 'fs';
|
|
4
5
|
import os from 'os';
|
|
@@ -67,7 +68,8 @@ describe('opencode plugin routes', () => {
|
|
|
67
68
|
beforeAll(async () => {
|
|
68
69
|
rootDir = fs.mkdtempSync(path.join(os.tmpdir(), 'openchamber-plugin-routes-'));
|
|
69
70
|
userConfigPath = path.join(rootDir, 'user-opencode.json');
|
|
70
|
-
|
|
71
|
+
vi.stubEnv('OPENCODE_CONFIG', userConfigPath);
|
|
72
|
+
vi.stubEnv('OPENCODE_CONFIG_DIR', path.join(rootDir, 'global-config'));
|
|
71
73
|
plugins = await import('./plugins.js');
|
|
72
74
|
});
|
|
73
75
|
|
|
@@ -91,7 +93,7 @@ describe('opencode plugin routes', () => {
|
|
|
91
93
|
|
|
92
94
|
afterAll(() => {
|
|
93
95
|
fs.rmSync(rootDir, { recursive: true, force: true });
|
|
94
|
-
|
|
96
|
+
vi.unstubAllEnvs();
|
|
95
97
|
});
|
|
96
98
|
|
|
97
99
|
test('GET /api/config/plugins empty returns entries and files arrays', async () => {
|
|
@@ -4,6 +4,8 @@ import path from 'path';
|
|
|
4
4
|
import {
|
|
5
5
|
AGENT_SCOPE,
|
|
6
6
|
readConfigFile,
|
|
7
|
+
readConfigLayers,
|
|
8
|
+
getConfigForPath,
|
|
7
9
|
writeConfig,
|
|
8
10
|
} from './shared.js';
|
|
9
11
|
import { isPathSpec } from './plugin-spec.js';
|
|
@@ -74,54 +76,8 @@ function getActiveOpencodeConfigDir() {
|
|
|
74
76
|
return path.join(os.homedir(), '.config', 'opencode');
|
|
75
77
|
}
|
|
76
78
|
|
|
77
|
-
function getActiveUserConfigPaths() {
|
|
78
|
-
const configDir = getActiveOpencodeConfigDir();
|
|
79
|
-
return [
|
|
80
|
-
path.join(configDir, 'config.json'),
|
|
81
|
-
path.join(configDir, 'opencode.json'),
|
|
82
|
-
path.join(configDir, 'opencode.jsonc'),
|
|
83
|
-
];
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
function getActiveCustomConfigPath() {
|
|
87
|
-
return process.env.OPENCODE_CONFIG ? path.resolve(process.env.OPENCODE_CONFIG) : null;
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
function getPrimaryUserConfigPath() {
|
|
91
|
-
const [defaultPath, ...fallbackPaths] = getActiveUserConfigPaths();
|
|
92
|
-
for (const userPath of [defaultPath, ...fallbackPaths]) {
|
|
93
|
-
if (fs.existsSync(userPath)) {
|
|
94
|
-
return userPath;
|
|
95
|
-
}
|
|
96
|
-
}
|
|
97
|
-
return defaultPath;
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
function getProjectConfigPath(workingDirectory) {
|
|
101
|
-
if (!workingDirectory) return null;
|
|
102
|
-
const candidates = [
|
|
103
|
-
path.join(workingDirectory, 'opencode.json'),
|
|
104
|
-
path.join(workingDirectory, 'opencode.jsonc'),
|
|
105
|
-
path.join(workingDirectory, '.opencode', 'opencode.json'),
|
|
106
|
-
path.join(workingDirectory, '.opencode', 'opencode.jsonc'),
|
|
107
|
-
];
|
|
108
|
-
return candidates.find((candidate) => fs.existsSync(candidate)) || candidates[0];
|
|
109
|
-
}
|
|
110
|
-
|
|
111
79
|
function readPluginConfigLayers(workingDirectory) {
|
|
112
|
-
|
|
113
|
-
const userPath = getPrimaryUserConfigPath();
|
|
114
|
-
const projectPath = getProjectConfigPath(workingDirectory);
|
|
115
|
-
return {
|
|
116
|
-
userConfig: readConfigFile(userPath),
|
|
117
|
-
projectConfig: readConfigFile(projectPath),
|
|
118
|
-
customConfig: readConfigFile(customPath),
|
|
119
|
-
paths: {
|
|
120
|
-
userPath,
|
|
121
|
-
projectPath,
|
|
122
|
-
customPath,
|
|
123
|
-
},
|
|
124
|
-
};
|
|
80
|
+
return readConfigLayers(workingDirectory);
|
|
125
81
|
}
|
|
126
82
|
|
|
127
83
|
function validateFileName(fileName) {
|
|
@@ -144,16 +100,10 @@ function ensureProjectConfigPath(workingDirectory) {
|
|
|
144
100
|
}
|
|
145
101
|
|
|
146
102
|
function configSources(layers) {
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
}
|
|
151
|
-
sources.push({ config: layers.userConfig, filePath: layers.paths.userPath, scope: AGENT_SCOPE.USER });
|
|
152
|
-
}
|
|
153
|
-
if (layers.paths.projectPath) {
|
|
154
|
-
sources.push({ config: layers.projectConfig, filePath: layers.paths.projectPath, scope: AGENT_SCOPE.PROJECT });
|
|
155
|
-
}
|
|
156
|
-
return sources;
|
|
103
|
+
return [...layers.documents.filter((layer) => layer.scope !== 'project').reverse(), ...layers.projectLayers.toReversed()].map((layer) => ({
|
|
104
|
+
config: layer.config, filePath: layer.path,
|
|
105
|
+
scope: layer.scope === 'project' ? AGENT_SCOPE.PROJECT : AGENT_SCOPE.USER,
|
|
106
|
+
}));
|
|
157
107
|
}
|
|
158
108
|
|
|
159
109
|
function splitScopedValue(value) {
|
|
@@ -175,7 +125,9 @@ function getPluginTarget(id, workingDirectory) {
|
|
|
175
125
|
const { scope, value: spec } = splitScopedValue(decoded.value);
|
|
176
126
|
validateScope(scope);
|
|
177
127
|
const layers = readPluginConfigLayers(workingDirectory);
|
|
178
|
-
const source = configSources(layers).find((candidate) => candidate.scope === scope
|
|
128
|
+
const source = configSources(layers).find((candidate) => candidate.scope === scope
|
|
129
|
+
&& Array.isArray(candidate.config?.plugin)
|
|
130
|
+
&& candidate.config.plugin.some((raw) => parsePluginRaw(raw).spec === spec));
|
|
179
131
|
const plugin = Array.isArray(source?.config?.plugin) ? source.config.plugin : [];
|
|
180
132
|
const index = plugin.findIndex((raw) => parsePluginRaw(raw).spec === spec);
|
|
181
133
|
if (!source || index === -1) {
|
|
@@ -281,14 +233,14 @@ function createPluginEntry(entry, workingDirectory) {
|
|
|
281
233
|
throw codedError(`Plugin "${spec}" already exists`, 'ENTRY_EXISTS');
|
|
282
234
|
}
|
|
283
235
|
|
|
284
|
-
let targetPath =
|
|
236
|
+
let targetPath = layers.paths.userPath;
|
|
285
237
|
let config = {};
|
|
286
238
|
if (scope === AGENT_SCOPE.PROJECT) {
|
|
287
239
|
targetPath = ensureProjectConfigPath(workingDirectory);
|
|
288
240
|
config = fs.existsSync(targetPath) ? readConfigFile(targetPath) : {};
|
|
289
241
|
} else {
|
|
290
242
|
targetPath = layers.paths.customPath || layers.paths.userPath;
|
|
291
|
-
config = layers
|
|
243
|
+
config = getConfigForPath(layers, targetPath);
|
|
292
244
|
}
|
|
293
245
|
|
|
294
246
|
if (!Array.isArray(config.plugin)) {
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { afterAll, beforeAll, beforeEach, describe, expect, test } from 'bun:test';
|
|
2
|
+
import { vi } from 'vitest';
|
|
2
3
|
import fs from 'fs';
|
|
3
4
|
import os from 'os';
|
|
4
5
|
import path from 'path';
|
|
@@ -30,19 +31,20 @@ describe('opencode plugins data layer', () => {
|
|
|
30
31
|
beforeAll(async () => {
|
|
31
32
|
rootDir = fs.mkdtempSync(path.join(os.tmpdir(), 'openchamber-plugins-'));
|
|
32
33
|
userConfigPath = path.join(rootDir, 'user-opencode.json');
|
|
33
|
-
|
|
34
|
+
vi.stubEnv('OPENCODE_CONFIG', userConfigPath);
|
|
35
|
+
vi.stubEnv('OPENCODE_CONFIG_DIR', path.join(rootDir, 'global-config'));
|
|
34
36
|
plugins = await import('./plugins.js');
|
|
35
37
|
});
|
|
36
38
|
|
|
37
39
|
beforeEach(() => {
|
|
38
|
-
|
|
40
|
+
vi.stubEnv('OPENCODE_CONFIG', userConfigPath);
|
|
39
41
|
projectDir = fs.mkdtempSync(path.join(rootDir, 'project-'));
|
|
40
42
|
fs.rmSync(userConfigPath, { force: true });
|
|
41
43
|
});
|
|
42
44
|
|
|
43
45
|
afterAll(() => {
|
|
44
46
|
fs.rmSync(rootDir, { recursive: true, force: true });
|
|
45
|
-
|
|
47
|
+
vi.unstubAllEnvs();
|
|
46
48
|
});
|
|
47
49
|
|
|
48
50
|
test('parses raw plugin entries', () => {
|
|
@@ -30,8 +30,8 @@ function getProviderSources(providerId, workingDirectory) {
|
|
|
30
30
|
return {
|
|
31
31
|
sources: {
|
|
32
32
|
auth: { exists: false },
|
|
33
|
-
user: { exists: userExists, path: paths.userPath },
|
|
34
|
-
project: { exists: projectExists, path: paths.projectPath || null },
|
|
33
|
+
user: { exists: userExists, path: layers.userLayers.findLast((layer) => layer.config?.provider?.[providerId] !== undefined || layer.config?.providers?.[providerId] !== undefined)?.path || paths.userPath },
|
|
34
|
+
project: { exists: projectExists, path: layers.projectLayers.findLast((layer) => layer.config?.provider?.[providerId] !== undefined || layer.config?.providers?.[providerId] !== undefined)?.path || paths.projectPath || null },
|
|
35
35
|
custom: { exists: customExists, path: paths.customPath }
|
|
36
36
|
}
|
|
37
37
|
};
|
|
@@ -44,17 +44,20 @@ function removeProviderConfig(providerId, workingDirectory, scope = 'user') {
|
|
|
44
44
|
|
|
45
45
|
const layers = readConfigLayers(workingDirectory);
|
|
46
46
|
let targetPath = layers.paths.userPath;
|
|
47
|
+
const sources = getProviderSources(providerId, workingDirectory).sources;
|
|
47
48
|
|
|
48
49
|
if (scope === 'project') {
|
|
49
50
|
if (!workingDirectory) {
|
|
50
51
|
throw new Error('Working directory is required for project scope');
|
|
51
52
|
}
|
|
52
|
-
targetPath =
|
|
53
|
+
targetPath = sources.project.path || targetPath;
|
|
53
54
|
} else if (scope === 'custom') {
|
|
54
55
|
if (!layers.paths.customPath) {
|
|
55
56
|
return false;
|
|
56
57
|
}
|
|
57
58
|
targetPath = layers.paths.customPath;
|
|
59
|
+
} else {
|
|
60
|
+
targetPath = sources.user.path;
|
|
58
61
|
}
|
|
59
62
|
|
|
60
63
|
const targetConfig = getConfigForPath(layers, targetPath);
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { createProxyMiddleware } from 'http-proxy-middleware';
|
|
2
|
+
import { isSensitiveCredentialResponse } from './credential-boundary.js';
|
|
2
3
|
|
|
3
4
|
import {
|
|
4
5
|
applyForwardProxyResponseHeaders,
|
|
@@ -244,6 +245,12 @@ export const resolveSessionTurnAdmissionRequest = (req) => {
|
|
|
244
245
|
};
|
|
245
246
|
|
|
246
247
|
export const registerOpenCodeProxy = (app, deps) => {
|
|
248
|
+
app.use((req, res, next) => {
|
|
249
|
+
if (isSensitiveCredentialResponse(req.method, req.originalUrl || req.url)) {
|
|
250
|
+
return res.status(403).json({ code: 'credential_response_refused', error: 'Provider credential values are host-only.' });
|
|
251
|
+
}
|
|
252
|
+
return next();
|
|
253
|
+
});
|
|
247
254
|
const {
|
|
248
255
|
fs,
|
|
249
256
|
os,
|
|
@@ -57,6 +57,19 @@ const mountProxy = () => {
|
|
|
57
57
|
return app;
|
|
58
58
|
};
|
|
59
59
|
|
|
60
|
+
describe('credential response boundary', () => {
|
|
61
|
+
it.each(['/api/credential', '/api/credential/', '/api/%63redential?directory=/repo'])('refuses raw credential list %s before upstream', async (path) => {
|
|
62
|
+
const fetchSpy = vi.fn();
|
|
63
|
+
globalThis.fetch = fetchSpy;
|
|
64
|
+
const response = await request(mountProxy()).get(path).expect(403);
|
|
65
|
+
expect(response.body.code).toBe('credential_response_refused');
|
|
66
|
+
expect(fetchSpy).not.toHaveBeenCalled();
|
|
67
|
+
});
|
|
68
|
+
it('refuses credential create replies which echo values', async () => {
|
|
69
|
+
await request(mountProxy()).post('/api/credential').send({}).expect(403);
|
|
70
|
+
});
|
|
71
|
+
});
|
|
72
|
+
|
|
60
73
|
const sseBody = (blocks) => {
|
|
61
74
|
const encoder = new TextEncoder();
|
|
62
75
|
const text = blocks.join('');
|
|
@@ -709,7 +709,7 @@ export const registerOpenCodeRoutes = (app, dependencies) => {
|
|
|
709
709
|
|
|
710
710
|
const sources = getProviderSources(providerId, directory);
|
|
711
711
|
const { getProviderAuth } = await getAuthLibrary();
|
|
712
|
-
const auth = getProviderAuth(providerId);
|
|
712
|
+
const auth = await getProviderAuth(providerId);
|
|
713
713
|
sources.sources.auth.exists = Boolean(auth);
|
|
714
714
|
|
|
715
715
|
return res.json({
|
|
@@ -753,12 +753,12 @@ export const registerOpenCodeRoutes = (app, dependencies) => {
|
|
|
753
753
|
let removed = false;
|
|
754
754
|
if (scope === 'auth') {
|
|
755
755
|
const { removeProviderAuth } = await getAuthLibrary();
|
|
756
|
-
removed = removeProviderAuth(providerId);
|
|
756
|
+
removed = await removeProviderAuth(providerId);
|
|
757
757
|
} else if (scope === 'user' || scope === 'project' || scope === 'custom') {
|
|
758
758
|
removed = removeProviderConfig(providerId, directory, scope);
|
|
759
759
|
} else if (scope === 'all') {
|
|
760
760
|
const { removeProviderAuth } = await getAuthLibrary();
|
|
761
|
-
const authRemoved = removeProviderAuth(providerId);
|
|
761
|
+
const authRemoved = await removeProviderAuth(providerId);
|
|
762
762
|
const userRemoved = removeProviderConfig(providerId, directory, 'user');
|
|
763
763
|
const projectRemoved = directory ? removeProviderConfig(providerId, directory, 'project') : false;
|
|
764
764
|
const customRemoved = removeProviderConfig(providerId, directory, 'custom');
|
|
@@ -767,14 +767,14 @@ export const registerOpenCodeRoutes = (app, dependencies) => {
|
|
|
767
767
|
return res.status(400).json({ error: 'Invalid scope' });
|
|
768
768
|
}
|
|
769
769
|
|
|
770
|
-
if (removed) {
|
|
770
|
+
if (removed && scope !== 'auth') {
|
|
771
771
|
await refreshOpenCodeAfterConfigChange(`provider ${providerId} disconnected (${scope})`);
|
|
772
772
|
}
|
|
773
773
|
|
|
774
774
|
return res.json({
|
|
775
775
|
success: true,
|
|
776
776
|
removed,
|
|
777
|
-
requiresReload: removed,
|
|
777
|
+
requiresReload: removed && scope !== 'auth',
|
|
778
778
|
message: removed ? 'Provider disconnected successfully' : 'Provider was not connected',
|
|
779
779
|
reloadDelayMs: removed ? clientReloadDelayMs : undefined,
|
|
780
780
|
});
|
|
@@ -233,39 +233,39 @@ describe('opencode2 upgrade pin (ticket 12)', () => {
|
|
|
233
233
|
});
|
|
234
234
|
|
|
235
235
|
it('upgrades a shared service using the owned cache and verifies the running version', async () => {
|
|
236
|
-
const binary = '/cache/opencode-cli/2.0.
|
|
236
|
+
const binary = '/cache/opencode-cli/2.0.23/opencode';
|
|
237
237
|
vi.mocked(installPinnedOpenCode2Cli).mockResolvedValueOnce(binary);
|
|
238
|
-
vi.mocked(readOpenCode2BinaryVersion).mockReturnValueOnce('2.0.
|
|
239
|
-
vi.stubGlobal('fetch', vi.fn(async () => new Response(JSON.stringify({ version: '2.0.
|
|
238
|
+
vi.mocked(readOpenCode2BinaryVersion).mockReturnValueOnce('2.0.23');
|
|
239
|
+
vi.stubGlobal('fetch', vi.fn(async () => new Response(JSON.stringify({ version: '2.0.23', pid: 42 }))));
|
|
240
240
|
let resolvedBinary = '/usr/local/bin/opencode';
|
|
241
241
|
const restartOpenCode = vi.fn(async () => {});
|
|
242
242
|
const { app } = createUpgradeApp({
|
|
243
243
|
getIsExternalOpenCode: () => true,
|
|
244
244
|
getIsSharedOpenCodeService: () => true,
|
|
245
245
|
getResolvedOpenCodeBinary: () => resolvedBinary,
|
|
246
|
-
getOpenCodeCliVersion: () => '2.0.
|
|
246
|
+
getOpenCodeCliVersion: () => '2.0.23',
|
|
247
247
|
forceResolvedOpenCodeBinary: (value) => { resolvedBinary = value; },
|
|
248
248
|
restartOpenCode,
|
|
249
249
|
waitForOpenCodeReady: vi.fn(),
|
|
250
250
|
openchamberDataDir: '/cache',
|
|
251
251
|
});
|
|
252
252
|
|
|
253
|
-
const response = await request(app).post('/api/opencode/upgrade').send({ target: '2.0.
|
|
253
|
+
const response = await request(app).post('/api/opencode/upgrade').send({ target: '2.0.23' }).expect(200);
|
|
254
254
|
expect(restartOpenCode).toHaveBeenCalledWith({ binaryPath: binary });
|
|
255
|
-
expect(response.body).toMatchObject({ success: true, version: '2.0.
|
|
255
|
+
expect(response.body).toMatchObject({ success: true, version: '2.0.23', ownership: 'shared-service' });
|
|
256
256
|
});
|
|
257
257
|
|
|
258
|
-
it('discovers the remote release and updates a shared 2.0.16 service to 2.0.
|
|
258
|
+
it('discovers the remote release and updates a shared 2.0.16 service to 2.0.23', async () => {
|
|
259
259
|
let serveVersion = '2.0.16';
|
|
260
260
|
let binary = '/usr/local/bin/opencode';
|
|
261
|
-
const installed = '/cache/opencode-cli/2.0.
|
|
261
|
+
const installed = '/cache/opencode-cli/2.0.23/opencode';
|
|
262
262
|
const fetchMock = vi.fn(async (url) => new Response(JSON.stringify({
|
|
263
|
-
version: String(url).includes('registry.npmjs.org') ? '2.0.
|
|
263
|
+
version: String(url).includes('registry.npmjs.org') ? '2.0.23' : serveVersion,
|
|
264
264
|
})));
|
|
265
265
|
vi.stubGlobal('fetch', fetchMock);
|
|
266
|
-
vi.mocked(readOpenCode2BinaryVersion).mockReturnValue('2.0.
|
|
266
|
+
vi.mocked(readOpenCode2BinaryVersion).mockReturnValue('2.0.23');
|
|
267
267
|
vi.mocked(installPinnedOpenCode2Cli).mockResolvedValueOnce(installed);
|
|
268
|
-
const restartOpenCode = vi.fn(async () => { serveVersion = '2.0.
|
|
268
|
+
const restartOpenCode = vi.fn(async () => { serveVersion = '2.0.23'; });
|
|
269
269
|
const { app } = createUpgradeApp({
|
|
270
270
|
discoverOpenCodeUpdate: createOpenCodeUpdateDiscovery(),
|
|
271
271
|
getIsExternalOpenCode: () => true,
|
|
@@ -277,12 +277,12 @@ describe('opencode2 upgrade pin (ticket 12)', () => {
|
|
|
277
277
|
openchamberDataDir: '/cache',
|
|
278
278
|
});
|
|
279
279
|
const before = await request(app).get('/api/opencode/upgrade-status').expect(200);
|
|
280
|
-
expect(before.body).toMatchObject({ available: true, serveVersion: '2.0.16', targetVersion: '2.0.
|
|
280
|
+
expect(before.body).toMatchObject({ available: true, serveVersion: '2.0.16', targetVersion: '2.0.23', canManage: true });
|
|
281
281
|
const result = await request(app).post('/api/opencode/upgrade').send({}).expect(200);
|
|
282
|
-
expect(result.body).toMatchObject({ success: true, restarted: true, version: '2.0.
|
|
282
|
+
expect(result.body).toMatchObject({ success: true, restarted: true, version: '2.0.23' });
|
|
283
283
|
expect(restartOpenCode).toHaveBeenCalledWith({ binaryPath: installed });
|
|
284
284
|
const after = await request(app).get('/api/opencode/upgrade-status').expect(200);
|
|
285
|
-
expect(after.body).toMatchObject({ available: false, serveVersion: '2.0.
|
|
285
|
+
expect(after.body).toMatchObject({ available: false, serveVersion: '2.0.23' });
|
|
286
286
|
expect(fetchMock.mock.calls.filter(([url]) => String(url).includes('registry.npmjs.org'))).toHaveLength(1);
|
|
287
287
|
vi.mocked(readOpenCode2BinaryVersion).mockReset();
|
|
288
288
|
});
|
|
@@ -385,7 +385,7 @@ describe('runtime contract routes (ticket 11)', () => {
|
|
|
385
385
|
ok: true,
|
|
386
386
|
status: 200,
|
|
387
387
|
statusText: 'OK',
|
|
388
|
-
json: async () => ({ version: '2.0.
|
|
388
|
+
json: async () => ({ version: '2.0.20', pid: 1 }),
|
|
389
389
|
})));
|
|
390
390
|
const { app } = createContractApp();
|
|
391
391
|
|
|
@@ -395,7 +395,7 @@ describe('runtime contract routes (ticket 11)', () => {
|
|
|
395
395
|
expect(health.body.executionAllowed).toBe(true);
|
|
396
396
|
|
|
397
397
|
const contract = await request(app).get('/api/opencode/contract').expect(200);
|
|
398
|
-
expect(contract.body.serveVersion).toBe('2.0.
|
|
398
|
+
expect(contract.body.serveVersion).toBe('2.0.20');
|
|
399
399
|
expect(contract.body.capabilities['core.protocol'].available).toBe(true);
|
|
400
400
|
});
|
|
401
401
|
|
|
@@ -439,7 +439,7 @@ describe('GET /api/opencode/health', () => {
|
|
|
439
439
|
status: 200,
|
|
440
440
|
statusText: 'OK',
|
|
441
441
|
json: async () => ({
|
|
442
|
-
version: '2.0.
|
|
442
|
+
version: '2.0.20',
|
|
443
443
|
pid: 4242,
|
|
444
444
|
urls: { api: 'http://127.0.0.1:4096' },
|
|
445
445
|
paths: { config: '/tmp/config', data: '/tmp/data' },
|
|
@@ -453,7 +453,7 @@ describe('GET /api/opencode/health', () => {
|
|
|
453
453
|
|
|
454
454
|
expect(response.body).toMatchObject({
|
|
455
455
|
healthy: true,
|
|
456
|
-
version: '2.0.
|
|
456
|
+
version: '2.0.20',
|
|
457
457
|
executionAllowed: true,
|
|
458
458
|
protocolCompatible: true,
|
|
459
459
|
});
|
|
@@ -7,20 +7,21 @@
|
|
|
7
7
|
* execution semantics.
|
|
8
8
|
*
|
|
9
9
|
* Evidence bounds (ticket 11):
|
|
10
|
-
* - MIN_VERIFIED:
|
|
10
|
+
* - MIN_VERIFIED: credential API availability (`2.0.20`)
|
|
11
11
|
* Any acceptable OpenCode 2.x at or above MIN_VERIFIED may execute. Below-min
|
|
12
12
|
* 2.x and 1.x are refused with upgrade guidance; there is no upper version cap.
|
|
13
13
|
*/
|
|
14
14
|
|
|
15
15
|
import {
|
|
16
16
|
PINNED_OPENCODE2_VERSION,
|
|
17
|
+
MINIMUM_OPENCODE2_VERSION,
|
|
17
18
|
compareOpenCode2Versions,
|
|
18
19
|
isAcceptableOpenCode2HealthVersion,
|
|
19
20
|
isOpenCode1xVersion,
|
|
20
21
|
} from './opencode2-pin.js';
|
|
21
22
|
|
|
22
23
|
/** Lowest version with documented OpenChamber + OpenCode 2 contract tests. */
|
|
23
|
-
export const RUNTIME_CONTRACT_MIN_VERIFIED =
|
|
24
|
+
export const RUNTIME_CONTRACT_MIN_VERIFIED = MINIMUM_OPENCODE2_VERSION;
|
|
24
25
|
|
|
25
26
|
/**
|
|
26
27
|
* Optional / core capabilities and the minimum serve version that admits them.
|
|
@@ -13,14 +13,14 @@ import {
|
|
|
13
13
|
} from './runtime-contract.js';
|
|
14
14
|
|
|
15
15
|
describe('runtime contract admission (ticket 11)', () => {
|
|
16
|
-
it('records
|
|
17
|
-
expect(RUNTIME_CONTRACT_MIN_VERIFIED).toBe('2.0.
|
|
16
|
+
it('records the credential API floor independently of the install pin', () => {
|
|
17
|
+
expect(RUNTIME_CONTRACT_MIN_VERIFIED).toBe('2.0.20');
|
|
18
18
|
});
|
|
19
19
|
|
|
20
20
|
it('classifies version bands with a 2.x floor only', () => {
|
|
21
|
-
expect(classifyRuntimeVersionBand('2.0.
|
|
22
|
-
expect(classifyRuntimeVersionBand('2.0.
|
|
23
|
-
expect(classifyRuntimeVersionBand('2.0.
|
|
21
|
+
expect(classifyRuntimeVersionBand('2.0.19')).toBe('below-min');
|
|
22
|
+
expect(classifyRuntimeVersionBand('2.0.20')).toBe('verified');
|
|
23
|
+
expect(classifyRuntimeVersionBand('2.0.23')).toBe('verified');
|
|
24
24
|
expect(classifyRuntimeVersionBand('2.1.0')).toBe('verified');
|
|
25
25
|
expect(classifyRuntimeVersionBand('2.0.5')).toBe('below-min');
|
|
26
26
|
expect(classifyRuntimeVersionBand('1.18.18')).toBe('1x');
|
|
@@ -30,8 +30,8 @@ describe('runtime contract admission (ticket 11)', () => {
|
|
|
30
30
|
|
|
31
31
|
it('admits verified serve for core protocol and optional caps', () => {
|
|
32
32
|
const result = evaluateRuntimeContract({
|
|
33
|
-
serveVersion: '2.0.
|
|
34
|
-
cliVersion: '2.0.
|
|
33
|
+
serveVersion: '2.0.20',
|
|
34
|
+
cliVersion: '2.0.20',
|
|
35
35
|
reachable: true,
|
|
36
36
|
authenticated: true,
|
|
37
37
|
healthOk: true,
|
|
@@ -65,7 +65,7 @@ describe('runtime contract admission (ticket 11)', () => {
|
|
|
65
65
|
|
|
66
66
|
it('admits newer 2.x above the documented floor for execution', () => {
|
|
67
67
|
const result = evaluateRuntimeContract({
|
|
68
|
-
serveVersion: '2.0.
|
|
68
|
+
serveVersion: '2.0.23',
|
|
69
69
|
reachable: true,
|
|
70
70
|
authenticated: true,
|
|
71
71
|
healthOk: true,
|
|
@@ -105,12 +105,12 @@ describe('runtime contract admission (ticket 11)', () => {
|
|
|
105
105
|
expect(evaluateRuntimeContract({
|
|
106
106
|
reachable: true,
|
|
107
107
|
authenticated: false,
|
|
108
|
-
serveVersion: '2.0.
|
|
108
|
+
serveVersion: '2.0.20',
|
|
109
109
|
healthOk: true,
|
|
110
110
|
}).phase).toBe('unauthenticated');
|
|
111
111
|
|
|
112
112
|
const migration = evaluateRuntimeContract({
|
|
113
|
-
serveVersion: '2.0.
|
|
113
|
+
serveVersion: '2.0.20',
|
|
114
114
|
reachable: true,
|
|
115
115
|
authenticated: true,
|
|
116
116
|
healthOk: true,
|
|
@@ -122,7 +122,7 @@ describe('runtime contract admission (ticket 11)', () => {
|
|
|
122
122
|
expect(migration.migrationExecutable).toBe(false);
|
|
123
123
|
|
|
124
124
|
const mismatch = evaluateRuntimeContract({
|
|
125
|
-
serveVersion: '2.0.
|
|
125
|
+
serveVersion: '2.0.20',
|
|
126
126
|
cliVersion: '2.0.14',
|
|
127
127
|
reachable: true,
|
|
128
128
|
authenticated: true,
|
|
@@ -179,7 +179,7 @@ describe('runtime contract admission (ticket 11)', () => {
|
|
|
179
179
|
migrationAdmitTranscript: true,
|
|
180
180
|
});
|
|
181
181
|
const body = runtimeContractExecutionBlockedBody(contract);
|
|
182
|
-
expect(body.error).toContain('2.0.
|
|
182
|
+
expect(body.error).toContain('2.0.20');
|
|
183
183
|
expect(body.error).toMatch(/Settings → About/i);
|
|
184
184
|
expect(formatRuntimeContractBlockedError(contract)).toBe(body.error);
|
|
185
185
|
});
|