Compare commits
2 Commits
main
...
fix/locals
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6061a6acef | ||
|
|
f100c96b53 |
@ -78,6 +78,7 @@ Docs: https://docs.openclaw.ai
|
|||||||
- Gateway/config validation: stop treating the implicit default memory slot as a required explicit plugin config, so startup no longer fails with `plugins.slots.memory: plugin not found: memory-core` when `memory-core` was only inferred. (#47494) Thanks @ngutman.
|
- Gateway/config validation: stop treating the implicit default memory slot as a required explicit plugin config, so startup no longer fails with `plugins.slots.memory: plugin not found: memory-core` when `memory-core` was only inferred. (#47494) Thanks @ngutman.
|
||||||
- CLI/startup: lazy-load channel add and root help startup paths to trim avoidable RSS and help latency on constrained hosts. (#46784) Thanks @vincentkoc.
|
- CLI/startup: lazy-load channel add and root help startup paths to trim avoidable RSS and help latency on constrained hosts. (#46784) Thanks @vincentkoc.
|
||||||
- CLI/onboarding: import static provider definitions directly for onboarding model/config helpers so those paths no longer pull provider discovery just for built-in defaults. (#47467) Thanks @vincentkoc.
|
- CLI/onboarding: import static provider definitions directly for onboarding model/config helpers so those paths no longer pull provider discovery just for built-in defaults. (#47467) Thanks @vincentkoc.
|
||||||
|
- Control UI: scope persisted settings by page base path while preserving custom gateway choices and migrating legacy shared storage on first load. (#47932) Thanks @bobBot-claw.
|
||||||
- CLI/auth choice: lazy-load plugin/provider fallback resolution so mapped auth choices stay on the static path and only unknown choices pay the heavy provider load. (#47495) Thanks @vincentkoc.
|
- CLI/auth choice: lazy-load plugin/provider fallback resolution so mapped auth choices stay on the static path and only unknown choices pay the heavy provider load. (#47495) Thanks @vincentkoc.
|
||||||
- CLI/completion: reduce recursive completion-script string churn and fix nested PowerShell command-path matching so generated nested completions resolve on PowerShell too. (#45537) Thanks @yiShanXin and @vincentkoc.
|
- CLI/completion: reduce recursive completion-script string churn and fix nested PowerShell command-path matching so generated nested completions resolve on PowerShell too. (#45537) Thanks @yiShanXin and @vincentkoc.
|
||||||
- Gateway/startup: load bundled channel plugins from compiled `dist/extensions` entries in built installs, so gateway boot no longer recompiles bundled extension TypeScript on every startup and WhatsApp-class cold starts drop back to seconds instead of tens of seconds or worse.
|
- Gateway/startup: load bundled channel plugins from compiled `dist/extensions` entries in built installs, so gateway boot no longer recompiles bundled extension TypeScript on every startup and WhatsApp-class cold starts drop back to seconds instead of tens of seconds or worse.
|
||||||
|
|||||||
@ -1,5 +1,6 @@
|
|||||||
import { describe, expect, it } from "vitest";
|
import { describe, expect, it } from "vitest";
|
||||||
import "../styles.css";
|
import "../styles.css";
|
||||||
|
import { inferBasePathFromPathname } from "./navigation.ts";
|
||||||
import { mountApp as mountTestApp, registerAppMountHooks } from "./test-helpers/app-mount.ts";
|
import { mountApp as mountTestApp, registerAppMountHooks } from "./test-helpers/app-mount.ts";
|
||||||
|
|
||||||
registerAppMountHooks();
|
registerAppMountHooks();
|
||||||
@ -8,6 +9,19 @@ function mountApp(pathname: string) {
|
|||||||
return mountTestApp(pathname);
|
return mountTestApp(pathname);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function normalizeScopedUrl(url: string): string {
|
||||||
|
const parsed = new URL(url);
|
||||||
|
const pathname =
|
||||||
|
parsed.pathname === "/" ? "" : parsed.pathname.replace(/\/+$/, "") || parsed.pathname;
|
||||||
|
return `${parsed.protocol}//${parsed.host}${pathname}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
function currentSettingsKey() {
|
||||||
|
const proto = window.location.protocol === "https:" ? "wss" : "ws";
|
||||||
|
const basePath = inferBasePathFromPathname(window.location.pathname);
|
||||||
|
return `openclaw.control.settings.v1:${normalizeScopedUrl(`${proto}//${window.location.host}${basePath}`)}`;
|
||||||
|
}
|
||||||
|
|
||||||
function nextFrame() {
|
function nextFrame() {
|
||||||
return new Promise<void>((resolve) => {
|
return new Promise<void>((resolve) => {
|
||||||
requestAnimationFrame(() => resolve());
|
requestAnimationFrame(() => resolve());
|
||||||
@ -320,9 +334,7 @@ describe("control UI routing", () => {
|
|||||||
await app.updateComplete;
|
await app.updateComplete;
|
||||||
|
|
||||||
expect(app.settings.token).toBe("");
|
expect(app.settings.token).toBe("");
|
||||||
expect(JSON.parse(localStorage.getItem("openclaw.control.settings.v1") ?? "{}").token).toBe(
|
expect(JSON.parse(localStorage.getItem(currentSettingsKey()) ?? "{}").token).toBe(undefined);
|
||||||
undefined,
|
|
||||||
);
|
|
||||||
expect(window.location.pathname).toBe("/ui/overview");
|
expect(window.location.pathname).toBe("/ui/overview");
|
||||||
expect(window.location.search).toBe("");
|
expect(window.location.search).toBe("");
|
||||||
});
|
});
|
||||||
@ -345,12 +357,10 @@ describe("control UI routing", () => {
|
|||||||
await app.updateComplete;
|
await app.updateComplete;
|
||||||
|
|
||||||
expect(app.settings.token).toBe("abc123");
|
expect(app.settings.token).toBe("abc123");
|
||||||
expect(JSON.parse(localStorage.getItem("openclaw.control.settings.v1") ?? "{}")).toMatchObject({
|
expect(JSON.parse(localStorage.getItem(currentSettingsKey()) ?? "{}")).toMatchObject({
|
||||||
gatewayUrl: "wss://gateway.example/openclaw",
|
gatewayUrl: "wss://gateway.example/openclaw",
|
||||||
});
|
});
|
||||||
expect(JSON.parse(localStorage.getItem("openclaw.control.settings.v1") ?? "{}").token).toBe(
|
expect(JSON.parse(localStorage.getItem(currentSettingsKey()) ?? "{}").token).toBe(undefined);
|
||||||
undefined,
|
|
||||||
);
|
|
||||||
expect(window.location.pathname).toBe("/ui/overview");
|
expect(window.location.pathname).toBe("/ui/overview");
|
||||||
expect(window.location.hash).toBe("");
|
expect(window.location.hash).toBe("");
|
||||||
});
|
});
|
||||||
@ -360,9 +370,7 @@ describe("control UI routing", () => {
|
|||||||
await app.updateComplete;
|
await app.updateComplete;
|
||||||
|
|
||||||
expect(app.settings.token).toBe("abc123");
|
expect(app.settings.token).toBe("abc123");
|
||||||
expect(JSON.parse(localStorage.getItem("openclaw.control.settings.v1") ?? "{}").token).toBe(
|
expect(JSON.parse(localStorage.getItem(currentSettingsKey()) ?? "{}").token).toBe(undefined);
|
||||||
undefined,
|
|
||||||
);
|
|
||||||
expect(window.location.pathname).toBe("/ui/overview");
|
expect(window.location.pathname).toBe("/ui/overview");
|
||||||
expect(window.location.hash).toBe("");
|
expect(window.location.hash).toBe("");
|
||||||
});
|
});
|
||||||
@ -414,8 +422,6 @@ describe("control UI routing", () => {
|
|||||||
await refreshed.updateComplete;
|
await refreshed.updateComplete;
|
||||||
|
|
||||||
expect(refreshed.settings.token).toBe("abc123");
|
expect(refreshed.settings.token).toBe("abc123");
|
||||||
expect(JSON.parse(localStorage.getItem("openclaw.control.settings.v1") ?? "{}").token).toBe(
|
expect(JSON.parse(localStorage.getItem(currentSettingsKey()) ?? "{}").token).toBe(undefined);
|
||||||
undefined,
|
|
||||||
);
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@ -62,6 +62,18 @@ function expectedGatewayUrl(basePath: string): string {
|
|||||||
return `${proto}://${location.host}${basePath}`;
|
return `${proto}://${location.host}${basePath}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function normalizeScopedUrl(url: string): string {
|
||||||
|
const parsed = new URL(url);
|
||||||
|
const pathname =
|
||||||
|
parsed.pathname === "/" ? "" : parsed.pathname.replace(/\/+$/, "") || parsed.pathname;
|
||||||
|
return `${parsed.protocol}//${parsed.host}${pathname}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
function expectedSettingsStorageKey(basePath: string): string {
|
||||||
|
const normalizedBasePath = basePath === "/" ? "" : basePath;
|
||||||
|
return `openclaw.control.settings.v1:${normalizeScopedUrl(expectedGatewayUrl(normalizedBasePath))}`;
|
||||||
|
}
|
||||||
|
|
||||||
describe("loadSettings default gateway URL derivation", () => {
|
describe("loadSettings default gateway URL derivation", () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
vi.resetModules();
|
vi.resetModules();
|
||||||
@ -124,7 +136,7 @@ describe("loadSettings default gateway URL derivation", () => {
|
|||||||
token: "",
|
token: "",
|
||||||
sessionKey: "agent",
|
sessionKey: "agent",
|
||||||
});
|
});
|
||||||
expect(JSON.parse(localStorage.getItem("openclaw.control.settings.v1") ?? "{}")).toEqual({
|
expect(JSON.parse(localStorage.getItem(expectedSettingsStorageKey("/")) ?? "{}")).toEqual({
|
||||||
gatewayUrl: "wss://gateway.example:8443/openclaw",
|
gatewayUrl: "wss://gateway.example:8443/openclaw",
|
||||||
theme: "claw",
|
theme: "claw",
|
||||||
themeMode: "system",
|
themeMode: "system",
|
||||||
@ -182,23 +194,12 @@ describe("loadSettings default gateway URL derivation", () => {
|
|||||||
pathname: "/",
|
pathname: "/",
|
||||||
});
|
});
|
||||||
|
|
||||||
const { loadSettings, saveSettings } = await import("./storage.ts");
|
sessionStorage.setItem(
|
||||||
saveSettings({
|
"openclaw.control.token.v1:wss://gateway.example:8443/openclaw",
|
||||||
gatewayUrl: "wss://gateway.example:8443/openclaw",
|
"gateway-a-token",
|
||||||
token: "gateway-a-token",
|
);
|
||||||
sessionKey: "main",
|
|
||||||
lastActiveSessionKey: "main",
|
|
||||||
theme: "claw",
|
|
||||||
themeMode: "system",
|
|
||||||
chatFocusMode: false,
|
|
||||||
chatShowThinking: true,
|
|
||||||
chatShowToolCalls: true,
|
|
||||||
splitRatio: 0.6,
|
|
||||||
navCollapsed: false,
|
|
||||||
navWidth: 220,
|
|
||||||
navGroupsCollapsed: {},
|
|
||||||
});
|
|
||||||
|
|
||||||
|
const { loadSettings } = await import("./storage.ts");
|
||||||
localStorage.setItem(
|
localStorage.setItem(
|
||||||
"openclaw.control.settings.v1",
|
"openclaw.control.settings.v1",
|
||||||
JSON.stringify({
|
JSON.stringify({
|
||||||
@ -221,6 +222,17 @@ describe("loadSettings default gateway URL derivation", () => {
|
|||||||
gatewayUrl: "wss://other-gateway.example:8443/openclaw",
|
gatewayUrl: "wss://other-gateway.example:8443/openclaw",
|
||||||
token: "",
|
token: "",
|
||||||
});
|
});
|
||||||
|
expect(JSON.parse(localStorage.getItem(expectedSettingsStorageKey("/")) ?? "{}")).toMatchObject(
|
||||||
|
{
|
||||||
|
gatewayUrl: "wss://other-gateway.example:8443/openclaw",
|
||||||
|
sessionsByGateway: {
|
||||||
|
"wss://other-gateway.example:8443/openclaw": {
|
||||||
|
sessionKey: "main",
|
||||||
|
lastActiveSessionKey: "main",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("does not persist gateway tokens when saving settings", async () => {
|
it("does not persist gateway tokens when saving settings", async () => {
|
||||||
@ -251,7 +263,7 @@ describe("loadSettings default gateway URL derivation", () => {
|
|||||||
token: "memory-only-token",
|
token: "memory-only-token",
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(JSON.parse(localStorage.getItem("openclaw.control.settings.v1") ?? "{}")).toEqual({
|
expect(JSON.parse(localStorage.getItem(expectedSettingsStorageKey("/")) ?? "{}")).toEqual({
|
||||||
gatewayUrl: "wss://gateway.example:8443/openclaw",
|
gatewayUrl: "wss://gateway.example:8443/openclaw",
|
||||||
theme: "claw",
|
theme: "claw",
|
||||||
themeMode: "system",
|
themeMode: "system",
|
||||||
@ -272,6 +284,51 @@ describe("loadSettings default gateway URL derivation", () => {
|
|||||||
expect(sessionStorage.length).toBe(1);
|
expect(sessionStorage.length).toBe(1);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("migrates tokenless legacy settings on first load", async () => {
|
||||||
|
setTestLocation({
|
||||||
|
protocol: "https:",
|
||||||
|
host: "gateway.example:8443",
|
||||||
|
pathname: "/gateway-a/chat",
|
||||||
|
});
|
||||||
|
|
||||||
|
localStorage.setItem(
|
||||||
|
"openclaw.control.settings.v1",
|
||||||
|
JSON.stringify({
|
||||||
|
gatewayUrl: "wss://custom-gateway.example:8443/openclaw",
|
||||||
|
sessionKey: "agent",
|
||||||
|
lastActiveSessionKey: "agent",
|
||||||
|
theme: "claw",
|
||||||
|
themeMode: "system",
|
||||||
|
chatFocusMode: false,
|
||||||
|
chatShowThinking: true,
|
||||||
|
chatShowToolCalls: true,
|
||||||
|
splitRatio: 0.6,
|
||||||
|
navCollapsed: false,
|
||||||
|
navWidth: 220,
|
||||||
|
navGroupsCollapsed: {},
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
const { loadSettings } = await import("./storage.ts");
|
||||||
|
|
||||||
|
expect(loadSettings()).toMatchObject({
|
||||||
|
gatewayUrl: "wss://custom-gateway.example:8443/openclaw",
|
||||||
|
sessionKey: "agent",
|
||||||
|
lastActiveSessionKey: "agent",
|
||||||
|
});
|
||||||
|
expect(
|
||||||
|
JSON.parse(localStorage.getItem(expectedSettingsStorageKey("/gateway-a")) ?? "{}"),
|
||||||
|
).toMatchObject({
|
||||||
|
gatewayUrl: "wss://custom-gateway.example:8443/openclaw",
|
||||||
|
sessionsByGateway: {
|
||||||
|
"wss://custom-gateway.example:8443/openclaw": {
|
||||||
|
sessionKey: "agent",
|
||||||
|
lastActiveSessionKey: "agent",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
it("clears the current-tab token when saving an empty token", async () => {
|
it("clears the current-tab token when saving an empty token", async () => {
|
||||||
setTestLocation({
|
setTestLocation({
|
||||||
protocol: "https:",
|
protocol: "https:",
|
||||||
@ -339,11 +396,13 @@ describe("loadSettings default gateway URL derivation", () => {
|
|||||||
navGroupsCollapsed: {},
|
navGroupsCollapsed: {},
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(JSON.parse(localStorage.getItem("openclaw.control.settings.v1") ?? "{}")).toMatchObject({
|
expect(JSON.parse(localStorage.getItem(expectedSettingsStorageKey("/")) ?? "{}")).toMatchObject(
|
||||||
theme: "dash",
|
{
|
||||||
themeMode: "light",
|
theme: "dash",
|
||||||
navWidth: 320,
|
themeMode: "light",
|
||||||
});
|
navWidth: 320,
|
||||||
|
},
|
||||||
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("scopes persisted session selection per gateway", async () => {
|
it("scopes persisted session selection per gateway", async () => {
|
||||||
@ -388,9 +447,9 @@ describe("loadSettings default gateway URL derivation", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
localStorage.setItem(
|
localStorage.setItem(
|
||||||
"openclaw.control.settings.v1",
|
expectedSettingsStorageKey("/"),
|
||||||
JSON.stringify({
|
JSON.stringify({
|
||||||
...JSON.parse(localStorage.getItem("openclaw.control.settings.v1") ?? "{}"),
|
...JSON.parse(localStorage.getItem(expectedSettingsStorageKey("/")) ?? "{}"),
|
||||||
gatewayUrl: "wss://gateway-a.example:8443/openclaw",
|
gatewayUrl: "wss://gateway-a.example:8443/openclaw",
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
@ -402,9 +461,9 @@ describe("loadSettings default gateway URL derivation", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
localStorage.setItem(
|
localStorage.setItem(
|
||||||
"openclaw.control.settings.v1",
|
expectedSettingsStorageKey("/"),
|
||||||
JSON.stringify({
|
JSON.stringify({
|
||||||
...JSON.parse(localStorage.getItem("openclaw.control.settings.v1") ?? "{}"),
|
...JSON.parse(localStorage.getItem(expectedSettingsStorageKey("/")) ?? "{}"),
|
||||||
gatewayUrl: "wss://gateway-b.example:8443/openclaw",
|
gatewayUrl: "wss://gateway-b.example:8443/openclaw",
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
@ -443,7 +502,7 @@ describe("loadSettings default gateway URL derivation", () => {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
const persisted = JSON.parse(localStorage.getItem("openclaw.control.settings.v1") ?? "{}");
|
const persisted = JSON.parse(localStorage.getItem(expectedSettingsStorageKey("/")) ?? "{}");
|
||||||
const scopes = Object.keys(persisted.sessionsByGateway ?? {});
|
const scopes = Object.keys(persisted.sessionsByGateway ?? {});
|
||||||
|
|
||||||
expect(scopes).toHaveLength(10);
|
expect(scopes).toHaveLength(10);
|
||||||
@ -451,4 +510,121 @@ describe("loadSettings default gateway URL derivation", () => {
|
|||||||
expect(scopes).not.toContain("wss://gateway-1.example:8443/openclaw");
|
expect(scopes).not.toContain("wss://gateway-1.example:8443/openclaw");
|
||||||
expect(scopes).toContain("wss://gateway-11.example:8443/openclaw");
|
expect(scopes).toContain("wss://gateway-11.example:8443/openclaw");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("scopes page settings separately even when gatewayUrl matches", async () => {
|
||||||
|
setTestLocation({
|
||||||
|
protocol: "https:",
|
||||||
|
host: "gateway.example:8443",
|
||||||
|
pathname: "/gateway-a/chat",
|
||||||
|
});
|
||||||
|
|
||||||
|
const { loadSettings, saveSettings } = await import("./storage.ts");
|
||||||
|
|
||||||
|
saveSettings({
|
||||||
|
gatewayUrl: "wss://shared-gateway.example:8443/openclaw",
|
||||||
|
token: "",
|
||||||
|
sessionKey: "main",
|
||||||
|
lastActiveSessionKey: "main",
|
||||||
|
theme: "dash",
|
||||||
|
themeMode: "light",
|
||||||
|
chatFocusMode: false,
|
||||||
|
chatShowThinking: true,
|
||||||
|
chatShowToolCalls: true,
|
||||||
|
splitRatio: 0.55,
|
||||||
|
navCollapsed: false,
|
||||||
|
navWidth: 240,
|
||||||
|
navGroupsCollapsed: {},
|
||||||
|
});
|
||||||
|
|
||||||
|
setTestLocation({
|
||||||
|
protocol: "https:",
|
||||||
|
host: "gateway.example:8443",
|
||||||
|
pathname: "/gateway-b/chat",
|
||||||
|
});
|
||||||
|
|
||||||
|
saveSettings({
|
||||||
|
gatewayUrl: "wss://shared-gateway.example:8443/openclaw",
|
||||||
|
token: "",
|
||||||
|
sessionKey: "main",
|
||||||
|
lastActiveSessionKey: "main",
|
||||||
|
theme: "claw",
|
||||||
|
themeMode: "system",
|
||||||
|
chatFocusMode: false,
|
||||||
|
chatShowThinking: true,
|
||||||
|
chatShowToolCalls: true,
|
||||||
|
splitRatio: 0.6,
|
||||||
|
navCollapsed: true,
|
||||||
|
navWidth: 220,
|
||||||
|
navGroupsCollapsed: {},
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(
|
||||||
|
JSON.parse(localStorage.getItem(expectedSettingsStorageKey("/gateway-a")) ?? "{}"),
|
||||||
|
).toMatchObject({
|
||||||
|
theme: "dash",
|
||||||
|
themeMode: "light",
|
||||||
|
splitRatio: 0.55,
|
||||||
|
});
|
||||||
|
expect(
|
||||||
|
JSON.parse(localStorage.getItem(expectedSettingsStorageKey("/gateway-b")) ?? "{}"),
|
||||||
|
).toMatchObject({
|
||||||
|
theme: "claw",
|
||||||
|
themeMode: "system",
|
||||||
|
navCollapsed: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
setTestLocation({
|
||||||
|
protocol: "https:",
|
||||||
|
host: "gateway.example:8443",
|
||||||
|
pathname: "/gateway-a/chat",
|
||||||
|
});
|
||||||
|
expect(loadSettings()).toMatchObject({
|
||||||
|
gatewayUrl: "wss://shared-gateway.example:8443/openclaw",
|
||||||
|
theme: "dash",
|
||||||
|
themeMode: "light",
|
||||||
|
splitRatio: 0.55,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it("keeps a custom gatewayUrl across reloads on the same page scope", async () => {
|
||||||
|
setTestLocation({
|
||||||
|
protocol: "https:",
|
||||||
|
host: "gateway.example:8443",
|
||||||
|
pathname: "/gateway-a/chat",
|
||||||
|
});
|
||||||
|
|
||||||
|
const { loadSettings, saveSettings } = await import("./storage.ts");
|
||||||
|
saveSettings({
|
||||||
|
gatewayUrl: "wss://custom-gateway.example:8443/openclaw",
|
||||||
|
token: "",
|
||||||
|
sessionKey: "agent:custom:main",
|
||||||
|
lastActiveSessionKey: "agent:custom:main",
|
||||||
|
theme: "claw",
|
||||||
|
themeMode: "system",
|
||||||
|
chatFocusMode: false,
|
||||||
|
chatShowThinking: true,
|
||||||
|
chatShowToolCalls: true,
|
||||||
|
splitRatio: 0.6,
|
||||||
|
navCollapsed: false,
|
||||||
|
navWidth: 220,
|
||||||
|
navGroupsCollapsed: {},
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(loadSettings()).toMatchObject({
|
||||||
|
gatewayUrl: "wss://custom-gateway.example:8443/openclaw",
|
||||||
|
sessionKey: "agent:custom:main",
|
||||||
|
lastActiveSessionKey: "agent:custom:main",
|
||||||
|
});
|
||||||
|
expect(
|
||||||
|
JSON.parse(localStorage.getItem(expectedSettingsStorageKey("/gateway-a")) ?? "{}"),
|
||||||
|
).toMatchObject({
|
||||||
|
gatewayUrl: "wss://custom-gateway.example:8443/openclaw",
|
||||||
|
sessionsByGateway: {
|
||||||
|
"wss://custom-gateway.example:8443/openclaw": {
|
||||||
|
sessionKey: "agent:custom:main",
|
||||||
|
lastActiveSessionKey: "agent:custom:main",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@ -1,8 +1,13 @@
|
|||||||
const KEY = "openclaw.control.settings.v1";
|
const LEGACY_SETTINGS_KEY = "openclaw.control.settings.v1";
|
||||||
|
const SETTINGS_KEY_PREFIX = `${LEGACY_SETTINGS_KEY}:`;
|
||||||
const LEGACY_TOKEN_SESSION_KEY = "openclaw.control.token.v1";
|
const LEGACY_TOKEN_SESSION_KEY = "openclaw.control.token.v1";
|
||||||
const TOKEN_SESSION_KEY_PREFIX = "openclaw.control.token.v1:";
|
const TOKEN_SESSION_KEY_PREFIX = "openclaw.control.token.v1:";
|
||||||
const MAX_SCOPED_SESSION_ENTRIES = 10;
|
const MAX_SCOPED_SESSION_ENTRIES = 10;
|
||||||
|
|
||||||
|
function settingsKeyForPage(pageUrl: string): string {
|
||||||
|
return `${SETTINGS_KEY_PREFIX}${normalizeGatewayTokenScope(pageUrl)}`;
|
||||||
|
}
|
||||||
|
|
||||||
type ScopedSessionSelection = {
|
type ScopedSessionSelection = {
|
||||||
sessionKey: string;
|
sessionKey: string;
|
||||||
lastActiveSessionKey: string;
|
lastActiveSessionKey: string;
|
||||||
@ -170,6 +175,7 @@ function persistSessionToken(gatewayUrl: string, token: string) {
|
|||||||
export function loadSettings(): UiSettings {
|
export function loadSettings(): UiSettings {
|
||||||
const { pageUrl: pageDerivedUrl, effectiveUrl: defaultUrl } = deriveDefaultGatewayUrl();
|
const { pageUrl: pageDerivedUrl, effectiveUrl: defaultUrl } = deriveDefaultGatewayUrl();
|
||||||
const storage = getSafeLocalStorage();
|
const storage = getSafeLocalStorage();
|
||||||
|
const scopedKey = settingsKeyForPage(pageDerivedUrl);
|
||||||
|
|
||||||
const defaults: UiSettings = {
|
const defaults: UiSettings = {
|
||||||
gatewayUrl: defaultUrl,
|
gatewayUrl: defaultUrl,
|
||||||
@ -188,10 +194,11 @@ export function loadSettings(): UiSettings {
|
|||||||
};
|
};
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const raw = storage?.getItem(KEY);
|
const raw = storage?.getItem(scopedKey) ?? storage?.getItem(LEGACY_SETTINGS_KEY);
|
||||||
if (!raw) {
|
if (!raw) {
|
||||||
return defaults;
|
return defaults;
|
||||||
}
|
}
|
||||||
|
const usedLegacyKey = storage?.getItem(scopedKey) == null;
|
||||||
const parsed = JSON.parse(raw) as PersistedUiSettings;
|
const parsed = JSON.parse(raw) as PersistedUiSettings;
|
||||||
const parsedGatewayUrl =
|
const parsedGatewayUrl =
|
||||||
typeof parsed.gatewayUrl === "string" && parsed.gatewayUrl.trim()
|
typeof parsed.gatewayUrl === "string" && parsed.gatewayUrl.trim()
|
||||||
@ -239,7 +246,12 @@ export function loadSettings(): UiSettings {
|
|||||||
: defaults.navGroupsCollapsed,
|
: defaults.navGroupsCollapsed,
|
||||||
locale: isSupportedLocale(parsed.locale) ? parsed.locale : undefined,
|
locale: isSupportedLocale(parsed.locale) ? parsed.locale : undefined,
|
||||||
};
|
};
|
||||||
if ("token" in parsed) {
|
if (
|
||||||
|
usedLegacyKey ||
|
||||||
|
"token" in parsed ||
|
||||||
|
typeof parsed.sessionKey === "string" ||
|
||||||
|
typeof parsed.lastActiveSessionKey === "string"
|
||||||
|
) {
|
||||||
persistSettings(settings);
|
persistSettings(settings);
|
||||||
}
|
}
|
||||||
return settings;
|
return settings;
|
||||||
@ -256,9 +268,10 @@ function persistSettings(next: UiSettings) {
|
|||||||
persistSessionToken(next.gatewayUrl, next.token);
|
persistSessionToken(next.gatewayUrl, next.token);
|
||||||
const storage = getSafeLocalStorage();
|
const storage = getSafeLocalStorage();
|
||||||
const scope = normalizeGatewayTokenScope(next.gatewayUrl);
|
const scope = normalizeGatewayTokenScope(next.gatewayUrl);
|
||||||
|
const scopedKey = settingsKeyForPage(deriveDefaultGatewayUrl().pageUrl);
|
||||||
let existingSessionsByGateway: Record<string, ScopedSessionSelection> = {};
|
let existingSessionsByGateway: Record<string, ScopedSessionSelection> = {};
|
||||||
try {
|
try {
|
||||||
const raw = storage?.getItem(KEY);
|
const raw = storage?.getItem(scopedKey) ?? storage?.getItem(LEGACY_SETTINGS_KEY);
|
||||||
if (raw) {
|
if (raw) {
|
||||||
const parsed = JSON.parse(raw) as PersistedUiSettings;
|
const parsed = JSON.parse(raw) as PersistedUiSettings;
|
||||||
if (parsed.sessionsByGateway && typeof parsed.sessionsByGateway === "object") {
|
if (parsed.sessionsByGateway && typeof parsed.sessionsByGateway === "object") {
|
||||||
@ -294,5 +307,5 @@ function persistSettings(next: UiSettings) {
|
|||||||
sessionsByGateway,
|
sessionsByGateway,
|
||||||
...(next.locale ? { locale: next.locale } : {}),
|
...(next.locale ? { locale: next.locale } : {}),
|
||||||
};
|
};
|
||||||
storage?.setItem(KEY, JSON.stringify(persisted));
|
storage?.setItem(scopedKey, JSON.stringify(persisted));
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user