

















@@ -97,8 +97,20 @@ vi.mock("../config/io.js", () => ({
9797},
9898}));
9999100+const capturedReplaceConfigFileCalls: Array<{
101+nextConfig: OpenClawConfig;
102+writeOptions?: { allowConfigSizeDrop?: boolean };
103+}> = [];
104+100105vi.mock("../config/config.js", () => ({
101-replaceConfigFile: async ({ nextConfig }: { nextConfig: OpenClawConfig }) => {
106+replaceConfigFile: async ({
107+ nextConfig,
108+ writeOptions,
109+}: {
110+nextConfig: OpenClawConfig;
111+writeOptions?: { allowConfigSizeDrop?: boolean };
112+}) => {
113+capturedReplaceConfigFileCalls.push({ nextConfig, ...(writeOptions ? { writeOptions } : {}) });
102114testConfigStore.set(resolveTestConfigPath(), nextConfig);
103115},
104116resolveGatewayPort: (cfg: OpenClawConfig) => cfg.gateway?.port ?? 18789,
@@ -375,6 +387,7 @@ describe("onboard (non-interactive): gateway and remote auth", () => {
375387afterEach(() => {
376388waitForGatewayReachableMock = undefined;
377389testConfigStore.clear();
390+capturedReplaceConfigFileCalls.length = 0;
378391ensureWorkspaceAndSessionsMock.mockClear();
379392installGatewayDaemonNonInteractiveMock.mockClear();
380393createPreMigrationBackupMock.mockClear();
@@ -386,6 +399,62 @@ describe("onboard (non-interactive): gateway and remote auth", () => {
386399readLastGatewayErrorLineMock.mockClear();
387400});
388401402+it("preserves existing agents.list and bindings on onboard rerun (openclaw#84692)", async () => {
403+await withStateDir("state-preserve-agents-", async (stateDir) => {
404+const workspace = path.join(stateDir, "openclaw");
405+const seededAgents = [
406+{ id: "alpha", model: "anthropic/claude-3-5-sonnet" },
407+{ id: "beta", model: "openai/gpt-4o" },
408+];
409+const seededBindings = [
410+{
411+type: "route" as const,
412+agentId: "alpha",
413+match: {
414+channel: "discord",
415+peer: { kind: "direct" as const, id: "user-1" },
416+},
417+},
418+{
419+type: "route" as const,
420+agentId: "beta",
421+match: {
422+channel: "discord",
423+peer: { kind: "direct" as const, id: "user-2" },
424+},
425+},
426+];
427+testConfigStore.set(resolveTestConfigPath(), {
428+agents: { list: seededAgents, defaults: { workspace } },
429+bindings: seededBindings,
430+gateway: { mode: "local", port: 18789, auth: { mode: "token", token: "seed_tok" } },
431+} as OpenClawConfig);
432+433+await runNonInteractiveSetup(
434+{
435+nonInteractive: true,
436+mode: "local",
437+ workspace,
438+authChoice: "skip",
439+skipSkills: true,
440+skipHealth: true,
441+installDaemon: false,
442+gatewayBind: "loopback",
443+gatewayAuth: "token",
444+gatewayToken: "seed_tok",
445+},
446+runtime,
447+);
448+449+const cfg = readTestConfig();
450+expect(cfg.agents?.list?.map((a) => a.id)).toEqual(["alpha", "beta"]);
451+expect(cfg.bindings).toEqual(seededBindings);
452+453+const onboardWrite = capturedReplaceConfigFileCalls.at(-1);
454+expect(onboardWrite?.writeOptions?.allowConfigSizeDrop).toBe(false);
455+});
456+}, 60_000);
457+389458it("writes gateway token auth into config", async () => {
390459await withStateDir("state-noninteractive-", async (stateDir) => {
391460const token = "tok_test_123";
@@ -558,6 +627,54 @@ describe("onboard (non-interactive): gateway and remote auth", () => {
558627});
559628}, 60_000);
560629630+it("preserves existing agents.list and bindings on remote onboard rerun (openclaw#84692)", async () => {
631+await withStateDir("state-remote-preserve-agents-", async (_stateDir) => {
632+const port = getPseudoPort(30_000);
633+const token = "tok_remote_seed";
634+const seededAgents = [
635+{ id: "alpha", model: "anthropic/claude-3-5-sonnet" },
636+{ id: "beta", model: "openai/gpt-4o" },
637+];
638+const seededBindings = [
639+{
640+type: "route" as const,
641+agentId: "alpha",
642+match: {
643+channel: "discord",
644+peer: { kind: "direct" as const, id: "user-1" },
645+},
646+},
647+];
648+testConfigStore.set(resolveTestConfigPath(), {
649+agents: { list: seededAgents },
650+bindings: seededBindings,
651+gateway: {
652+mode: "remote",
653+remote: { url: `ws://127.0.0.1:${port}`, token },
654+},
655+} as OpenClawConfig);
656+657+await runNonInteractiveSetup(
658+{
659+nonInteractive: true,
660+mode: "remote",
661+remoteUrl: `ws://127.0.0.1:${port}`,
662+remoteToken: token,
663+authChoice: "skip",
664+json: true,
665+},
666+runtime,
667+);
668+669+const cfg = readTestConfig();
670+expect(cfg.agents?.list?.map((a) => a.id)).toEqual(["alpha", "beta"]);
671+expect(cfg.bindings).toEqual(seededBindings);
672+673+const remoteWrite = capturedReplaceConfigFileCalls.at(-1);
674+expect(remoteWrite?.writeOptions?.allowConfigSizeDrop).toBe(false);
675+});
676+}, 60_000);
677+561678it("explains local health failure when no daemon was requested", async () => {
562679await withStateDir("state-local-health-hint-", async (stateDir) => {
563680waitForGatewayReachableMock = vi.fn(async () => ({
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。