fix(gateway): restore fresh dev bootstrap (#118003)

This commit is contained in:
Peter Steinberger
2026-08-02 11:16:33 -07:00
committed by GitHub
parent 8de3c11fae
commit 184c7a00fa
3 changed files with 54 additions and 1 deletions
@@ -0,0 +1,44 @@
// Proves a fresh dev gateway can replace the synthetic implicit roster through real config IO.
import { mkdtemp, readFile, rm } from "node:fs/promises";
import os from "node:os";
import path from "node:path";
import { afterEach, describe, expect, it } from "vitest";
import { resetConfigRuntimeState } from "../../config/config.js";
import { withEnvAsync } from "../../test-utils/env.js";
import { ensureDevGatewayConfig } from "./dev.js";
describe("ensureDevGatewayConfig integration", () => {
const tempDirs: string[] = [];
afterEach(async () => {
resetConfigRuntimeState();
await Promise.all(tempDirs.splice(0).map((dir) => rm(dir, { recursive: true, force: true })));
});
it("writes the dedicated dev roster into a fresh state directory", async () => {
const root = await mkdtemp(path.join(os.tmpdir(), "openclaw-dev-config-integration-"));
tempDirs.push(root);
const stateDir = path.join(root, "state");
const configPath = path.join(stateDir, "openclaw.json");
const workspace = path.join(root, "workspace");
await withEnvAsync(
{
OPENCLAW_CONFIG_PATH: configPath,
OPENCLAW_STATE_DIR: stateDir,
OPENCLAW_WORKSPACE_DIR: workspace,
},
async () => {
resetConfigRuntimeState();
await ensureDevGatewayConfig({});
},
);
const config = JSON.parse(await readFile(configPath, "utf8")) as {
agents?: { entries?: Record<string, { default?: boolean; workspace?: string }> };
};
expect(config.agents?.entries).toEqual({
dev: { default: true, workspace: `${workspace}-dev`, identity: expect.any(Object) },
});
});
});
+6 -1
View File
@@ -9,6 +9,7 @@ const mocks = vi.hoisted(() => ({
configPath: "",
workspace: "",
nextConfig: undefined as unknown,
writeOptions: undefined as unknown,
replaceConfigFile: vi.fn(),
}));
@@ -48,9 +49,12 @@ describe("ensureDevGatewayConfig", () => {
mocks.configPath = path.join(tempDir, "openclaw.json");
mocks.workspace = path.join(tempDir, "workspace");
mocks.nextConfig = undefined;
mocks.writeOptions = undefined;
mocks.replaceConfigFile.mockReset();
mocks.replaceConfigFile.mockImplementation(async (options: unknown) => {
mocks.nextConfig = (options as { nextConfig: unknown }).nextConfig;
const configOptions = options as { nextConfig: unknown; writeOptions?: unknown };
mocks.nextConfig = configOptions.nextConfig;
mocks.writeOptions = configOptions.writeOptions;
});
});
@@ -76,5 +80,6 @@ describe("ensureDevGatewayConfig", () => {
},
});
expect(OpenClawSchema.safeParse(mocks.nextConfig).success).toBe(true);
expect(mocks.writeOptions).toEqual({ allowedAgentRosterRemovals: ["main"] });
});
});
+4
View File
@@ -8,6 +8,7 @@ import { resolveWorkspaceTemplateSearchDirs } from "../../agents/workspace-templ
import { resolveDefaultAgentWorkspaceDir } from "../../agents/workspace.js";
import { handleReset } from "../../commands/onboard-helpers.js";
import { createConfigIO, replaceConfigFile } from "../../config/config.js";
import { LEGACY_IMPLICIT_AGENT_ID } from "../../routing/session-key.js";
import { defaultRuntime } from "../../runtime.js";
import { resolveUserPath, shortenHomePath } from "../../utils.js";
@@ -128,6 +129,9 @@ export async function ensureDevGatewayConfig(opts: { reset?: boolean }) {
},
},
afterWrite: { mode: "auto" },
// An absent config resolves to the implicit legacy agent before this full
// replacement. Declare only that synthetic deletion; authored rosters stay protected.
writeOptions: { allowedAgentRosterRemovals: [LEGACY_IMPLICIT_AGENT_ID] },
});
await ensureDevWorkspace(workspace);
defaultRuntime.log(`Dev config ready: ${shortenHomePath(configPath)}`);