fix: preserve openclaw gateway token mask

Co-authored-by: multica-agent <github@multica.ai>
This commit is contained in:
J
2026-06-15 17:33:39 +08:00
parent 3ce4cf6f2f
commit c0f5397452
2 changed files with 70 additions and 10 deletions

View File

@@ -0,0 +1,63 @@
import { describe, expect, it } from "vitest";
import {
OPENCLAW_GATEWAY_TOKEN_MASK,
serializeOpenclawRuntimeConfig,
} from "./openclaw-runtime-config";
describe("serializeOpenclawRuntimeConfig", () => {
it("keeps the masked gateway token sentinel so the API can preserve the persisted token", () => {
expect(
serializeOpenclawRuntimeConfig({
mode: "gateway",
gateway: {
host: "gw.internal",
port: 18789,
token: OPENCLAW_GATEWAY_TOKEN_MASK,
tls: true,
},
}),
).toEqual({
mode: "gateway",
gateway: {
host: "gw.internal",
port: 18789,
token: OPENCLAW_GATEWAY_TOKEN_MASK,
tls: true,
},
});
});
it("omits an empty gateway token so users can clear a persisted token", () => {
expect(
serializeOpenclawRuntimeConfig({
mode: "gateway",
gateway: {
host: "gw.internal",
port: 18789,
},
}),
).toEqual({
mode: "gateway",
gateway: {
host: "gw.internal",
port: 18789,
},
});
});
it("passes through a real gateway token value", () => {
expect(
serializeOpenclawRuntimeConfig({
mode: "gateway",
gateway: {
token: "rotated-secret",
},
}),
).toEqual({
mode: "gateway",
gateway: {
token: "rotated-secret",
},
});
});
});

View File

@@ -20,10 +20,9 @@ export interface OpenclawRuntimeConfig {
}
// Sentinel the API substitutes for a non-empty `gateway.token` on every read.
// When the form re-submits the same sentinel we strip the field client-side
// so the backend's matching preserve hook restores the persisted token
// instead of overwriting it. Mirrors `runtimeConfigGatewayTokenMask` in
// server/internal/handler/agent.go.
// When the form re-submits the same sentinel, the backend's matching
// preserve hook restores the persisted token instead of overwriting it.
// Mirrors `runtimeConfigGatewayTokenMask` in server/internal/handler/agent.go.
export const OPENCLAW_GATEWAY_TOKEN_MASK = "***";
// Parse an arbitrary runtime_config payload into the typed schema. Unknown
@@ -65,12 +64,10 @@ export function serializeOpenclawRuntimeConfig(
if (cfg.gateway.host) gw.host = cfg.gateway.host;
if (cfg.gateway.port) gw.port = cfg.gateway.port;
if (cfg.gateway.tls) gw.tls = true;
// The mask sentinel must NEVER round-trip back to the server; the
// matching preserve hook on the backend treats its presence as "keep
// the persisted token", but emitting it here would still hit the wire
// and trip any future schema validation. Drop it client-side so the
// payload genuinely omits the field.
if (cfg.gateway.token && cfg.gateway.token !== OPENCLAW_GATEWAY_TOKEN_MASK) {
// The mask sentinel is the explicit "keep persisted token" signal for
// the API. Omitting the field means "clear/no token" for partial
// gateway pins, so the sentinel must survive serialization.
if (cfg.gateway.token) {
gw.token = cfg.gateway.token;
}
if (Object.keys(gw).length > 0) out.gateway = gw;