Skip to content
Merged
Show file tree
Hide file tree
Changes from 9 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .gitattributes
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
* text=auto eol=lf
*.{cmd,bat} text eol=crlf
5 changes: 3 additions & 2 deletions cli.js
Original file line number Diff line number Diff line change
Expand Up @@ -10461,6 +10461,7 @@ function assertRequestAuthorized(req, res) {

function isProtectedWebSurfacePath(requestPath) {
return requestPath === '/'
|| requestPath === '/session'
|| requestPath === '/web-ui/index.html'
|| requestPath.startsWith('/web-ui/')
|| requestPath.startsWith('/res/');
Expand Down Expand Up @@ -11889,8 +11890,8 @@ function createWebServer({ htmlPath, assetsDir, webDir, host, port, openBrowser
});
fs.createReadStream(filePath).pipe(res);
} else {
// Only serve HTML for root path; /web-ui returns 404.
if (requestPath === '/') {
// Serve the SPA shell for routable entry points. Keep /web-ui as 404.
if (requestPath === '/' || requestPath === '/session') {
try {
const html = readBundledWebUiHtml(htmlPath);
res.writeHead(200, {
Expand Down
12 changes: 11 additions & 1 deletion package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,6 @@
},
"dependencies": {
"@iarna/toml": "^2.2.5",
"@vue/compiler-dom": "^3.5.30",
"json5": "^2.2.3",
"yauzl": "^3.2.1",
"zip-lib": "^1.2.1"
Expand All @@ -72,6 +71,7 @@
"author": "ymkiux",
"license": "Apache-2.0",
"devDependencies": {
"@vue/compiler-dom": "^3.5.30",
"vitepress": "^1.6.4"
}
}
19 changes: 19 additions & 0 deletions tests/unit/agents-modal-guards.test.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,9 @@ const { createAgentsMethods } = await import(
const { createCodexConfigMethods } = await import(
pathToFileURL(path.join(__dirname, '..', '..', 'web-ui', 'modules', 'app.methods.codex-config.mjs'))
);
const { createI18nMethods } = await import(
pathToFileURL(path.join(__dirname, '..', '..', 'web-ui', 'modules', 'i18n.mjs'))
);

test('closeConfigTemplateModal ignores user close attempts while template apply is busy', () => {
const methods = createCodexConfigMethods({
Expand All @@ -20,7 +23,9 @@ test('closeConfigTemplateModal ignores user close attempts while template apply
}
});
const context = {
...createI18nMethods(),
...methods,
lang: 'zh',
showConfigTemplateModal: true,
configTemplateApplying: true,
configTemplateContent: 'draft-template'
Expand Down Expand Up @@ -52,7 +57,9 @@ test('applyConfigTemplate force closes the modal after a successful apply', asyn
}
});
const context = {
...createI18nMethods(),
...methods,
lang: 'zh',
showConfigTemplateModal: true,
configTemplateApplying: false,
configTemplateContent: 'draft-template',
Expand Down Expand Up @@ -103,7 +110,9 @@ test('applyConfigTemplate keeps the successful apply result when only the refres
}
});
const context = {
...createI18nMethods(),
...methods,
lang: 'zh',
showConfigTemplateModal: true,
configTemplateApplying: false,
configTemplateContent: 'draft-template',
Expand Down Expand Up @@ -161,7 +170,9 @@ test('applyConfigTemplate applies immediately when diff confirm is disabled', as
}
});
const context = {
...createI18nMethods(),
...methods,
lang: 'zh',
showConfigTemplateModal: true,
configTemplateApplying: false,
configTemplateContent: 'draft-template',
Expand Down Expand Up @@ -192,7 +203,9 @@ test('runHealthCheck treats backend error payloads as failures', async () => {
}
});
const context = {
...createI18nMethods(),
...methods,
lang: 'zh',
providersList: ['alpha'],
speedResults: {},
speedLoading: {},
Expand Down Expand Up @@ -227,7 +240,9 @@ test('runHealthCheck skips Claude speed tests when the primary health check alre
});
let claudeSpeedTestCalls = 0;
const context = {
...createI18nMethods(),
...methods,
lang: 'zh',
providersList: ['alpha'],
speedResults: {},
speedLoading: {},
Expand Down Expand Up @@ -276,7 +291,9 @@ test('runHealthCheck preserves backend remote health result while appending spee
}
});
const context = {
...createI18nMethods(),
...methods,
lang: 'zh',
providersList: ['alpha', 'beta'],
speedResults: {},
speedLoading: {},
Expand Down Expand Up @@ -324,7 +341,9 @@ test('applyCodexConfigDirect keeps the successful apply result when only the ref
}
});
const context = {
...createI18nMethods(),
...methods,
lang: 'zh',
codexApplying: false,
_pendingCodexApplyOptions: null,
currentProvider: 'alpha',
Expand Down
36 changes: 36 additions & 0 deletions tests/unit/claude-settings-sync.test.mjs
Original file line number Diff line number Diff line change
@@ -1,6 +1,15 @@
import assert from 'assert';
import path from 'path';
import { fileURLToPath, pathToFileURL } from 'url';
import { readBundledWebUiScript, readProjectFile } from './helpers/web-ui-source.mjs';

const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);

const { createI18nMethods } = await import(
pathToFileURL(path.join(__dirname, '..', '..', 'web-ui', 'modules', 'i18n.mjs'))
);

const appSource = readBundledWebUiScript();
const claudeConfigModuleSource = readProjectFile('web-ui/modules/app.methods.claude-config.mjs');

Expand Down Expand Up @@ -224,7 +233,10 @@ test('addClaudeConfig requires a visible model value before saving', () => {
const addClaudeConfig = instantiateFunction(source, 'addClaudeConfig');
const messages = [];
let saveCount = 0;
const i18nMethods = createI18nMethods();
const context = {
...i18nMethods,
lang: 'zh',
newClaudeConfig: {
name: 'Claude Test',
apiKey: 'sk-test',
Expand Down Expand Up @@ -256,7 +268,10 @@ ${extractMethodAsFunction(appSource, 'claudeConfigFieldError')}`;
${extractMethodAsFunction(appSource, 'canSubmitClaudeConfig')}`;
const claudeConfigFieldError = instantiateFunction(fieldErrorSource, 'claudeConfigFieldError');
const canSubmitClaudeConfig = instantiateFunction(canSubmitSource, 'canSubmitClaudeConfig');
const i18nMethods = createI18nMethods();
const context = {
...i18nMethods,
lang: 'zh',
newClaudeConfig: {
name: 'Existing',
apiKey: '',
Expand Down Expand Up @@ -297,7 +312,10 @@ ${extractMethodAsFunction(appSource, 'claudeConfigFieldError')}`;
${extractMethodAsFunction(appSource, 'canSubmitClaudeConfig')}`;
const claudeConfigFieldError = instantiateFunction(fieldErrorSource, 'claudeConfigFieldError');
const canSubmitClaudeConfig = instantiateFunction(canSubmitSource, 'canSubmitClaudeConfig');
const i18nMethods = createI18nMethods();
const context = {
...i18nMethods,
lang: 'zh',
newClaudeConfig: { name: '', apiKey: '', baseUrl: '', model: '' },
editingConfig: {
name: 'Imported Auth Token',
Expand Down Expand Up @@ -350,7 +368,10 @@ test('addClaudeConfig trims and persists the entered model', () => {
let saveCount = 0;
let closed = false;
let refreshed = false;
const i18nMethods = createI18nMethods();
const context = {
...i18nMethods,
lang: 'zh',
newClaudeConfig: {
name: 'Claude Test',
apiKey: 'sk-test',
Expand Down Expand Up @@ -548,7 +569,10 @@ test('saveAndApplyConfig writes the edited Claude model through apply api', asyn
let saveCount = 0;
let closed = false;
let refreshCount = 0;
const i18nMethods = createI18nMethods();
const context = {
...i18nMethods,
lang: 'zh',
editingConfig: {
name: 'UI Claude Use',
apiKey: 'sk-test',
Expand Down Expand Up @@ -607,7 +631,10 @@ test('saveAndApplyConfig saves external credential config without api key', asyn
let saveCount = 0;
let closed = false;
let refreshCount = 0;
const i18nMethods = createI18nMethods();
const context = {
...i18nMethods,
lang: 'zh',
editingConfig: {
name: 'Imported Auth Token',
apiKey: '',
Expand Down Expand Up @@ -654,7 +681,10 @@ test('applyClaudeConfig reports informative message for external credential only

const messages = [];
let refreshCount = 0;
const i18nMethods = createI18nMethods();
const context = {
...i18nMethods,
lang: 'zh',
claudeConfigs: {
imported: {
apiKey: '',
Expand Down Expand Up @@ -689,7 +719,10 @@ test('onClaudeModelChange applies external credential config without api key', (
let updateCount = 0;
const applyCalls = [];
const messages = [];
const i18nMethods = createI18nMethods();
const context = {
...i18nMethods,
lang: 'zh',
currentClaudeConfig: 'imported',
currentClaudeModel: ' claude-opus-4-6 ',
claudeConfigs: {
Expand Down Expand Up @@ -735,7 +768,10 @@ test('onClaudeModelChange still requires api key when no external credential is
let updateCount = 0;
const applyCalls = [];
const messages = [];
const i18nMethods = createI18nMethods();
const context = {
...i18nMethods,
lang: 'zh',
currentClaudeConfig: 'local',
currentClaudeModel: ' claude-opus-4-6 ',
claudeConfigs: {
Expand Down
5 changes: 5 additions & 0 deletions tests/unit/openclaw-persist-regression.test.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,9 @@ const __dirname = path.dirname(__filename);
const { createOpenclawPersistMethods, DEFAULT_OPENCLAW_CONFIG_NAME } = await import(
pathToFileURL(path.join(__dirname, '..', '..', 'web-ui', 'modules', 'app.methods.openclaw-persist.mjs'))
);
const { createI18nMethods } = await import(
pathToFileURL(path.join(__dirname, '..', '..', 'web-ui', 'modules', 'i18n.mjs'))
);

function deferred() {
let resolve;
Expand All @@ -21,7 +24,9 @@ function deferred() {

function createContext(methods, overrides = {}) {
return {
...createI18nMethods(),
...methods,
lang: 'zh',
openclawConfigs: {
saved: {
content: 'saved-local'
Expand Down
13 changes: 13 additions & 0 deletions tests/unit/provider-switch-regression.test.mjs
Original file line number Diff line number Diff line change
@@ -1,9 +1,18 @@
import assert from 'assert';
import path from 'path';
import { fileURLToPath, pathToFileURL } from 'url';
import {
captureCurrentBundledAppOptions,
withGlobalOverrides
} from './helpers/web-ui-app-options.mjs';

const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);

const { createI18nMethods } = await import(
pathToFileURL(path.join(__dirname, '..', '..', 'web-ui', 'modules', 'i18n.mjs'))
);

const currentAppOptions = await captureCurrentBundledAppOptions();
const currentMethods = currentAppOptions.methods;
const currentComputed = currentAppOptions.computed;
Expand All @@ -13,6 +22,8 @@ function createProviderSwitchContext() {
const messages = [];

return {
...createI18nMethods(),
lang: 'zh',
currentProvider: 'alpha',
currentModel: 'alpha-model',
models: ['alpha-model'],
Expand Down Expand Up @@ -58,6 +69,8 @@ function createProviderSwitchContext() {
function createProviderUpdateContext() {
const messages = [];
return {
...createI18nMethods(),
lang: 'zh',
editingProvider: {
name: 'alpha',
url: ' https://api.example.com/v1 ',
Expand Down
Loading
Loading