




















@@ -38,6 +38,111 @@ describe("config io write prepare", () => {
3838expect(persisted).not.toHaveProperty("sessions.persistence");
3939});
404041+it("preserves authored source-only nested fields during partial writes", () => {
42+const persisted = resolvePersistCandidateForWrite({
43+runtimeConfig: {
44+plugins: {
45+entries: {},
46+},
47+},
48+sourceConfig: {
49+plugins: {
50+entries: {},
51+installs: {
52+"openclaw-web-search": {
53+source: "npm",
54+spec: "@ollama/openclaw-web-search",
55+installPath: "/tmp/openclaw-web-search",
56+resolvedName: "@ollama/openclaw-web-search",
57+resolvedVersion: "0.2.2",
58+},
59+},
60+},
61+},
62+nextConfig: {
63+plugins: {
64+entries: {},
65+installs: {
66+"openclaw-web-search": {
67+source: "npm",
68+spec: "@ollama/openclaw-web-search@0.2.2",
69+installPath: "/tmp/openclaw-web-search",
70+resolvedName: "@ollama/openclaw-web-search",
71+resolvedVersion: "0.2.2",
72+},
73+},
74+},
75+},
76+}) as {
77+plugins?: {
78+installs?: Record<string, Record<string, unknown>>;
79+};
80+};
81+82+expect(persisted.plugins?.installs?.["openclaw-web-search"]).toEqual({
83+source: "npm",
84+spec: "@ollama/openclaw-web-search@0.2.2",
85+installPath: "/tmp/openclaw-web-search",
86+resolvedName: "@ollama/openclaw-web-search",
87+resolvedVersion: "0.2.2",
88+});
89+});
90+91+it("preserves untouched include-owned subtrees during unrelated writes", () => {
92+const persisted = resolvePersistCandidateForWrite({
93+runtimeConfig: {
94+agents: {
95+defaults: { model: "openai/gpt-5.4" },
96+},
97+gateway: { mode: "local" },
98+},
99+sourceConfig: {
100+agents: {
101+defaults: { model: "openai/gpt-5.4" },
102+},
103+gateway: { mode: "local" },
104+},
105+rootAuthoredConfig: {
106+agents: { $include: "./config/agents.json" },
107+gateway: { mode: "local" },
108+},
109+nextConfig: {
110+agents: {
111+defaults: { model: "openai/gpt-5.4" },
112+},
113+gateway: { mode: "local", port: 18789 },
114+},
115+}) as Record<string, unknown>;
116+117+expect(persisted.agents).toEqual({ $include: "./config/agents.json" });
118+expect(persisted.gateway).toEqual({ mode: "local", port: 18789 });
119+});
120+121+it("rejects writes that would flatten include-owned subtrees", () => {
122+expect(() =>
123+resolvePersistCandidateForWrite({
124+runtimeConfig: {
125+agents: {
126+defaults: { model: "openai/gpt-5.4" },
127+},
128+},
129+sourceConfig: {
130+agents: {
131+defaults: { model: "openai/gpt-5.4" },
132+},
133+},
134+rootAuthoredConfig: {
135+agents: { $include: "./config/agents.json" },
136+},
137+nextConfig: {
138+agents: {
139+defaults: { model: "anthropic/sonnet-4.5" },
140+},
141+},
142+}),
143+).toThrow("Config write would flatten $include-owned config at agents");
144+});
145+41146it('formats actionable guidance for dmPolicy="open" without wildcard allowFrom', () => {
42147const message = formatConfigValidationFailure(
43148"channels.telegram.allowFrom",
@@ -434,26 +539,25 @@ describe("config io write prepare", () => {
434539expect(persisted.gateway).toEqual({ mode: "local", port: 18789 });
435540});
436541437-it("does not preserve include-only $schema into the root persisted candidate", () => {
542+it("rejects writes that would flatten a root include", () => {
438543const sourceConfig = {
439544$schema: "https://openclaw.ai/config-from-include.json",
440545gateway: { mode: "local" },
441546};
442547443-const persisted = resolvePersistCandidateForWrite({
444-runtimeConfig: sourceConfig,
445- sourceConfig,
446-rootAuthoredConfig: {
447-$include: "./extra.json5",
448-gateway: { mode: "local" },
449-},
450-nextConfig: {
451-gateway: { mode: "local", port: 18789 },
452-},
453-}) as Record<string, unknown>;
454-455-expect(persisted).not.toHaveProperty("$schema");
456-expect(persisted.gateway).toEqual({ mode: "local", port: 18789 });
548+expect(() =>
549+resolvePersistCandidateForWrite({
550+runtimeConfig: sourceConfig,
551+ sourceConfig,
552+rootAuthoredConfig: {
553+$include: "./extra.json5",
554+gateway: { mode: "local" },
555+},
556+nextConfig: {
557+gateway: { mode: "local", port: 18789 },
558+},
559+}),
560+).toThrow("Config write would flatten $include-owned config at <root>");
457561});
458562459563it("does not restore root $schema when the next config explicitly clears it", () => {
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。