test: align roster fixtures with Doctor migrations

This commit is contained in:
Peter Steinberger 2026-10-02 04:50:34 -07:00
parent cae2f937ce
commit 0c1714d2ae
No known key found for this signature in database
8 changed files with 308 additions and 201 deletions

View file

@ -8,7 +8,7 @@ import { performance } from "node:perf_hooks";
import { setTimeout as delay } from "node:timers/promises";
import { GatewayClient } from "openclaw/plugin-sdk/gateway-runtime";
import type { OpenClawPluginNodeHostCommand } from "openclaw/plugin-sdk/plugin-entry";
import { stopChildProcess } from "openclaw/plugin-sdk/test-env";
import { createCanonicalAgentConfigFixture, stopChildProcess } from "openclaw/plugin-sdk/test-env";
import { createOpenClawTestState } from "openclaw/plugin-sdk/test-state";
import { describe, expect, it, vi } from "vitest";
import { createOllamaNodeHostCommands } from "./node-inference.js";
@ -60,28 +60,30 @@ describe("Ollama paired-node Gateway inference", () => {
let node: GatewayClient | undefined;
try {
await state.writeConfig({
gateway: {
mode: "local",
port: gatewayPort,
bind: "loopback",
auth: { mode: "token", token: gatewayToken },
controlUi: { enabled: false },
nodes: { commands: { allow: ["ollama.models", "ollama.chat"] } },
},
plugins: {
allow: ["ollama"],
},
agents: {
defaults: { heartbeat: { every: "0m" }, skipBootstrap: true },
entries: { main: { default: true, tools: { allow: ["node_inference"] } } },
},
models: {
providers: {
ollama: { api: "ollama", baseUrl: gatewayOllama.baseUrl, models: [] },
await state.writeConfig(
createCanonicalAgentConfigFixture({
gateway: {
mode: "local",
port: gatewayPort,
bind: "loopback",
auth: { mode: "token", token: gatewayToken },
controlUi: { enabled: false },
nodes: { commands: { allow: ["ollama.models", "ollama.chat"] } },
},
},
});
plugins: {
allow: ["ollama"],
},
agents: {
defaults: { heartbeat: { every: "0m" }, skipBootstrap: true },
entries: { main: { default: true, tools: { allow: ["node_inference"] } } },
},
models: {
providers: {
ollama: { api: "ollama", baseUrl: gatewayOllama.baseUrl, models: [] },
},
},
}).config,
);
const gatewayEntryArgs =
process.env.OPENCLAW_E2E_USE_PREBUILT_DIST === "1"

View file

@ -2,7 +2,7 @@ import fs from "node:fs/promises";
import path from "node:path";
import { readConfigFileSnapshot, resolveAgentWorkspaceDir } from "openclaw/plugin-sdk/health";
import { resolveAgentRoute } from "openclaw/plugin-sdk/routing";
import { withTempHome } from "openclaw/plugin-sdk/test-env";
import { createCanonicalAgentConfigFixture, withTempHome } from "openclaw/plugin-sdk/test-env";
import { describe, expect, it } from "vitest";
import { buildQaGatewayConfig } from "../../qa-gateway-config.js";
import { buildWhatsAppQaConfig } from "./whatsapp-live.config.js";
@ -53,7 +53,25 @@ describe("WhatsApp QA broadcast config", () => {
sutAccountId: "sut",
});
await fs.writeFile(path.join(home, ".openclaw", "openclaw.json"), JSON.stringify(cfg));
const configPath = path.join(home, ".openclaw", "openclaw.json");
const authored = JSON.stringify(cfg);
await fs.writeFile(configPath, authored);
if (roster === "legacy-default") {
const before = await readConfigFileSnapshot({
pluginValidation: "core-only",
observe: false,
});
expect(before.valid).toBe(false);
expect(before.sourceConfig.agents?.entries?.main?.default).toBe(true);
expect(await fs.readFile(configPath, "utf8")).toBe(authored);
}
const runtimeConfig = createCanonicalAgentConfigFixture(cfg, {
homedir: () => home,
}).config;
const originalRuntimeConfig = createCanonicalAgentConfigFixture(original, {
homedir: () => home,
}).config;
await fs.writeFile(configPath, JSON.stringify(runtimeConfig));
const snapshot = await readConfigFileSnapshot({
pluginValidation: "core-only",
observe: false,
@ -61,12 +79,12 @@ describe("WhatsApp QA broadcast config", () => {
expect(snapshot.valid, JSON.stringify(snapshot.issues)).toBe(true);
expect(cfg.agents?.defaults).toMatchObject(original.agents?.defaults ?? {});
const route = { channel: "whatsapp", accountId: "sut" };
expect(resolveAgentRoute({ cfg, ...route }).agentId).toBe(
resolveAgentRoute({ cfg: original, ...route }).agentId,
expect(resolveAgentRoute({ cfg: runtimeConfig, ...route }).agentId).toBe(
resolveAgentRoute({ cfg: originalRuntimeConfig, ...route }).agentId,
);
for (const agentId of Object.keys(original.agents?.entries ?? {})) {
expect(resolveAgentWorkspaceDir(cfg, agentId)).toBe(
resolveAgentWorkspaceDir(original, agentId),
expect(resolveAgentWorkspaceDir(runtimeConfig, agentId)).toBe(
resolveAgentWorkspaceDir(originalRuntimeConfig, agentId),
);
}
expect(cfg.agents?.entries).toMatchObject(original.agents?.entries ?? {});

View file

@ -4,7 +4,11 @@ import path from "node:path";
import { createPluginRuntimeMock } from "openclaw/plugin-sdk/channel-test-helpers";
import type { OpenClawConfig } from "openclaw/plugin-sdk/config-contracts";
import { readConfigFileSnapshotForWrite } from "openclaw/plugin-sdk/config-mutation";
import { withEnv, withTempHome } from "openclaw/plugin-sdk/test-env";
import {
createCanonicalAgentConfigFixture,
withEnv,
withTempHome,
} from "openclaw/plugin-sdk/test-env";
import { afterEach, describe, expect, it } from "vitest";
import {
listEnabledTelegramAccounts,
@ -183,7 +187,7 @@ describe("resolveDefaultTelegramAccountId", () => {
expect(resolveDefaultTelegramAccountId(cfg)).toBe("work");
});
it("preserves a loaded legacy owner's account until explicit fleet ownership is applied", async () => {
it("uses explicit channel defaults after Doctor repairs a loaded legacy roster", async () => {
await withTempHome(
async (home) => {
const config: OpenClawConfig = {
@ -196,13 +200,25 @@ describe("resolveDefaultTelegramAccountId", () => {
},
bindings: [{ agentId: "main", match: { channel: "telegram", accountId: "work" } }],
};
await fs.writeFile(path.join(home, ".openclaw", "openclaw.json"), JSON.stringify(config));
const configPath = path.join(home, ".openclaw", "openclaw.json");
const original = JSON.stringify(config);
await fs.writeFile(configPath, original);
const { snapshot } = await readConfigFileSnapshotForWrite();
expect(snapshot.valid).toBe(true);
expect(resolveDefaultTelegramAccountId(snapshot.config)).toBe("work");
snapshot.config.agents!.ownership = "explicit";
expect(resolveDefaultTelegramAccountId(snapshot.config)).toBe("alerts");
expect(snapshot.valid).toBe(false);
expect(snapshot.sourceConfig.agents?.entries?.main?.default).toBe(true);
expect(snapshot.sourceConfig.bindings).toEqual(config.bindings);
expect(await fs.readFile(configPath, "utf8")).toBe(original);
const { config: repaired } = createCanonicalAgentConfigFixture(snapshot.sourceConfig, {
homedir: () => home,
});
expect(repaired.agents?.ownership).toBe("explicit");
expect(repaired.agents?.entries?.main).not.toHaveProperty("default");
expect(repaired.agents?.defaults?.systemAgent).toEqual({ agentId: "main" });
expect(repaired.bindings).toContainEqual(config.bindings![0]);
expect(resolveDefaultTelegramAccountId(repaired)).toBe("alerts");
expect(await fs.readFile(configPath, "utf8")).toBe(original);
},
{
env: {

View file

@ -10,6 +10,7 @@ import type { OpenClawConfig } from "../../config/types.openclaw.js";
import type { RuntimeEnv } from "../../runtime.js";
import { closeOpenClawAgentDatabasesForTest } from "../../state/openclaw-agent-db.js";
import { closeOpenClawStateDatabaseForTest } from "../../state/openclaw-state-db.js";
import { runDoctorConfigPreflight } from "../doctor-config-preflight.js";
import {
applyOnboardingPrimaryModel,
type OnboardingAgentTarget,
@ -45,162 +46,227 @@ describe("local setup fleet owner persistence", () => {
{ legacy: true, include: true },
{ legacy: false, include: false },
{ legacy: false, include: true },
])("preserves a fleet through two real setup writes: %j", async ({ legacy, include }) => {
const { ensureOnboardingAgent } =
await vi.importActual<typeof import("../onboard-agent.js")>("../onboard-agent.js");
mocks.ensureOnboardingAgent.mockImplementation(ensureOnboardingAgent);
await withTempHome(async (rawHome) => {
const home = await fs.realpath(rawHome);
const stateDir = path.join(home, ".openclaw");
const configPath = path.join(stateDir, "openclaw.json");
const includePath = path.join(stateDir, "roster.json");
const workspace = path.join(home, "existing-workspace");
const siblingWorkspace = path.join(home, "alpha-workspace");
const agentDir = path.join(stateDir, "agents", "beta", "agent");
const entries = {
alpha: { model: "fixture/alpha", workspace: siblingWorkspace },
beta: { model: "fixture/beta", ...(!legacy ? { workspace } : {}), agentDir },
};
// The legacy default is deliberately neither the first nor lexical agent.
const list = [
{ id: "alpha", ...entries.alpha },
{ id: "beta", default: true, ...entries.beta },
];
const roster = legacy ? { list } : { entries };
const includeRaw = JSON.stringify(roster);
if (include) {
await fs.writeFile(includePath, includeRaw);
}
const bindings = [
{ type: "route", agentId: "alpha", match: { channel: "discord", accountId: "*" } },
];
const raw = JSON.stringify({
agents: {
...(include ? { $include: "./roster.json" } : roster),
...(!legacy ? { ownership: "explicit" } : {}),
defaults: {
workspace,
model: "fixture/shared",
...(!legacy ? { systemAgent: { agentId: "beta" } } : {}),
])(
"preserves a fleet through Doctor and two real setup writes: %j",
async ({ legacy, include }) => {
const { ensureOnboardingAgent } =
await vi.importActual<typeof import("../onboard-agent.js")>("../onboard-agent.js");
mocks.ensureOnboardingAgent.mockImplementation(ensureOnboardingAgent);
await withTempHome(async (rawHome) => {
const home = await fs.realpath(rawHome);
const stateDir = path.join(home, ".openclaw");
const configPath = path.join(stateDir, "openclaw.json");
const includePath = path.join(stateDir, "roster.json");
const workspace = path.join(home, "existing-workspace");
const siblingWorkspace = path.join(home, "alpha-workspace");
const agentDir = path.join(stateDir, "agents", "beta", "agent");
const entries = {
alpha: { model: "fixture/alpha", workspace: siblingWorkspace },
beta: { model: "fixture/beta", ...(!legacy ? { workspace } : {}), agentDir },
};
// The legacy default is deliberately neither the first nor lexical agent.
const list = [
{ id: "alpha", ...entries.alpha },
{ id: "beta", default: true, ...entries.beta },
];
const roster = legacy ? { list } : { entries };
const includeRaw = JSON.stringify(roster);
if (include) {
await fs.writeFile(includePath, includeRaw);
}
const bindings = [
{ type: "route", agentId: "alpha", match: { channel: "discord", accountId: "*" } },
];
const raw = JSON.stringify({
agents: {
...(include ? { $include: "./roster.json" } : roster),
...(!legacy ? { ownership: "explicit" } : {}),
defaults: {
workspace,
model: "fixture/shared",
...(!legacy ? { systemAgent: { agentId: "beta" } } : {}),
},
},
},
bindings,
gateway: { mode: "local", port: 24680 },
});
await fs.writeFile(configPath, raw);
resetConfigRuntimeState();
const before = await readConfigFileSnapshot();
expect(before.valid, JSON.stringify(before.issues)).toBe(true);
expect(before.sourceConfigBeforeMigrations?.agents).toMatchObject(roster);
expect(before.sourceConfig.agents?.entries).toEqual(entries);
expect(before.sourceConfig.agents?.list).toBeUndefined();
expect(await fs.readFile(configPath, "utf8")).toBe(raw);
const runtime = { log: vi.fn(), error: vi.fn(), exit: vi.fn() } as unknown as RuntimeEnv;
const profileId = "fixture:setup";
mocks.applyAuthChoice.mockImplementation(
async ({
nextConfig,
target,
}: {
nextConfig: OpenClawConfig;
target: OnboardingAgentTarget;
}) => {
expect(target).toEqual({ agentId: "beta", agentDir, workspaceDir: workspace });
upsertAuthProfile({
agentDir: target.agentDir,
profileId,
credential: { type: "api_key", provider: "fixture", key: "synthetic-setup-key" },
});
// Include-owned entries remain authored in their own file. Root-owned
// model edits exercise the writer's existing ownership projection.
return include
? { ...nextConfig }
: applyOnboardingPrimaryModel(nextConfig, target, "fixture/selected");
},
);
for (let rerun = 0; rerun < 2; rerun += 1) {
await runNonInteractiveSetup(
{
nonInteractive: true,
mode: "local",
agentName: "ignored",
workspace: path.join(home, "requested-workspace"),
authChoice: "fixture-api-key",
skipBootstrap: true,
skipSkills: true,
skipHealth: true,
installDaemon: false,
},
runtime,
);
await expect(mocks.ensureOnboardingAgent.mock.results[rerun]?.value).resolves.toMatchObject(
{
agentId: "beta",
createdAgent: false,
},
);
expect(mocks.ensureWorkspaceAndSessions).toHaveBeenLastCalledWith(
workspace,
runtime,
expect.objectContaining({ agentId: "beta", skipBootstrap: true }),
);
closeOpenClawAgentDatabasesForTest();
closeOpenClawStateDatabaseForTest();
resetConfigRuntimeState();
const reopened = await readConfigFileSnapshot();
expect(reopened.valid, JSON.stringify(reopened.issues)).toBe(true);
const expectedEntries = include
? entries
: {
...entries,
beta: {
...entries.beta,
workspace,
model: { primary: "fixture/selected" },
models: { "fixture/selected": {} },
bindings,
gateway: { mode: "local", port: 24680 },
models: {
providers: {
fixture: {
api: "openai-completions",
baseUrl: "https://fixture.example/v1",
models: ["alpha", "beta", "shared", "selected"].map((id) => ({
id,
name: id,
contextWindow: 8192,
maxTokens: 1024,
})),
},
};
expect(reopened.sourceConfig.agents?.entries).toEqual(expectedEntries);
expect(reopened.sourceConfig.agents?.defaults).toMatchObject({
workspace,
model: "fixture/shared",
skipBootstrap: true,
systemAgent: { agentId: "beta" },
},
},
});
expect(reopened.sourceConfig.bindings).toEqual(bindings);
expect(reopened.sourceConfig.gateway?.port).toBe(24680);
expect(reopened.sourceConfig.hooks?.internal?.entries?.["session-memory"]?.enabled).toBe(
true,
);
await fs.writeFile(configPath, raw);
resetConfigRuntimeState();
const before = await readConfigFileSnapshot();
expect(before.valid, JSON.stringify(before.issues)).toBe(!legacy);
expect(before.sourceConfig.agents).toMatchObject(roster);
expect(await fs.readFile(configPath, "utf8")).toBe(raw);
if (include) {
expect(await fs.readFile(includePath, "utf8")).toBe(includeRaw);
expect(JSON.parse(await fs.readFile(configPath, "utf8")).agents.$include).toBe(
"./roster.json",
}
const canonicalEntries = {
...entries,
beta: { ...entries.beta, workspace },
};
let setupIncludeRaw = includeRaw;
if (legacy) {
if (include) {
// Doctor needs the original list and owner marker when repair spans
// an included roster and root-owned roles. Re-split only after repair.
await fs.writeFile(configPath, JSON.stringify(before.sourceConfig));
resetConfigRuntimeState();
}
await runDoctorConfigPreflight({
observe: false,
invocationPurpose: "doctor",
repairPrefixedConfig: true,
doctorOnlyStateMigrations: true,
preparePluginMetadataSnapshot: true,
});
resetConfigRuntimeState();
const repaired = await readConfigFileSnapshot();
expect(repaired.valid, JSON.stringify(repaired.issues)).toBe(true);
expect(repaired.sourceConfig.agents?.entries).toEqual(canonicalEntries);
expect(repaired.sourceConfig.agents?.list).toBeUndefined();
expect(repaired.sourceConfig.agents?.ownership).toBe("explicit");
expect(repaired.sourceConfig.agents?.defaults?.systemAgent).toEqual({ agentId: "beta" });
expect(repaired.sourceConfig.bindings).toEqual(bindings);
if (include) {
expect(await fs.readFile(includePath, "utf8")).toBe(includeRaw);
setupIncludeRaw = JSON.stringify({ entries: canonicalEntries });
await fs.writeFile(includePath, setupIncludeRaw);
const agents = repaired.sourceConfig.agents;
if (!agents) {
throw new Error("Doctor did not retain the fleet");
}
const { entries: _entries, ...rootOwnedAgents } = agents;
await fs.writeFile(
configPath,
JSON.stringify({
...repaired.sourceConfig,
agents: { $include: "./roster.json", ...rootOwnedAgents },
}),
);
resetConfigRuntimeState();
expect((await readConfigFileSnapshot()).valid).toBe(true);
}
}
const runtime = { log: vi.fn(), error: vi.fn(), exit: vi.fn() } as unknown as RuntimeEnv;
const profileId = "fixture:setup";
mocks.applyAuthChoice.mockImplementation(
async ({
nextConfig,
target,
}: {
nextConfig: OpenClawConfig;
target: OnboardingAgentTarget;
}) => {
expect(target).toEqual({ agentId: "beta", agentDir, workspaceDir: workspace });
upsertAuthProfile({
agentDir: target.agentDir,
profileId,
credential: { type: "api_key", provider: "fixture", key: "synthetic-setup-key" },
});
// Include-owned entries remain authored in their own file. Root-owned
// model edits exercise the writer's existing ownership projection.
return include
? { ...nextConfig }
: applyOnboardingPrimaryModel(nextConfig, target, "fixture/selected");
},
);
for (let rerun = 0; rerun < 2; rerun += 1) {
await runNonInteractiveSetup(
{
nonInteractive: true,
mode: "local",
agentName: "ignored",
workspace: path.join(home, "requested-workspace"),
authChoice: "fixture-api-key",
skipBootstrap: true,
skipSkills: true,
skipHealth: true,
installDaemon: false,
},
runtime,
);
} else {
const persisted = JSON.parse(await fs.readFile(configPath, "utf8"));
expect(persisted.agents.entries).toEqual(expectedEntries);
expect(persisted.agents.list).toBeUndefined();
expect(persisted.agents.ownership).toBe("explicit");
expect(persisted.agents.defaults.systemAgent).toEqual({ agentId: "beta" });
}
expect(
loadAuthProfileStoreWithoutExternalProfiles(agentDir).profiles[profileId],
).toMatchObject({ provider: "fixture" });
for (const nonOwner of ["alpha", "ignored"]) {
await expect(
mocks.ensureOnboardingAgent.mock.results[rerun]?.value,
).resolves.toMatchObject({
agentId: "beta",
createdAgent: false,
});
expect(mocks.ensureWorkspaceAndSessions).toHaveBeenLastCalledWith(
workspace,
runtime,
expect.objectContaining({ agentId: "beta", skipBootstrap: true }),
);
closeOpenClawAgentDatabasesForTest();
closeOpenClawStateDatabaseForTest();
resetConfigRuntimeState();
const reopened = await readConfigFileSnapshot();
expect(reopened.valid, JSON.stringify(reopened.issues)).toBe(true);
const expectedEntries = include
? canonicalEntries
: {
...canonicalEntries,
beta: {
...entries.beta,
workspace,
model: { primary: "fixture/selected" },
models: { "fixture/selected": {} },
},
};
expect(reopened.sourceConfig.agents?.entries).toEqual(expectedEntries);
expect(reopened.sourceConfig.agents?.defaults).toMatchObject({
workspace,
model: "fixture/shared",
skipBootstrap: true,
systemAgent: { agentId: "beta" },
});
expect(reopened.sourceConfig.bindings).toEqual(bindings);
expect(reopened.sourceConfig.gateway?.port).toBe(24680);
expect(reopened.sourceConfig.hooks?.internal?.entries?.["session-memory"]?.enabled).toBe(
true,
);
if (include) {
expect(await fs.readFile(includePath, "utf8")).toBe(setupIncludeRaw);
expect(JSON.parse(await fs.readFile(configPath, "utf8")).agents.$include).toBe(
"./roster.json",
);
} else {
const persisted = JSON.parse(await fs.readFile(configPath, "utf8"));
expect(persisted.agents.entries).toEqual(expectedEntries);
expect(persisted.agents.list).toBeUndefined();
expect(persisted.agents.ownership).toBe("explicit");
expect(persisted.agents.defaults.systemAgent).toEqual({ agentId: "beta" });
}
expect(
loadAuthProfileStoreWithoutExternalProfiles(
path.join(stateDir, "agents", nonOwner, "agent"),
).profiles[profileId],
`credentials must not be stored for ${nonOwner}`,
).toBeUndefined();
loadAuthProfileStoreWithoutExternalProfiles(agentDir).profiles[profileId],
).toMatchObject({ provider: "fixture" });
for (const nonOwner of ["alpha", "ignored"]) {
expect(
loadAuthProfileStoreWithoutExternalProfiles(
path.join(stateDir, "agents", nonOwner, "agent"),
).profiles[profileId],
`credentials must not be stored for ${nonOwner}`,
).toBeUndefined();
}
}
}
expect(runtime.exit).not.toHaveBeenCalled();
expect(runtime.error).toHaveBeenCalledWith(
expect.stringContaining("existing agents keep their current workspace"),
);
});
});
expect(runtime.exit).not.toHaveBeenCalled();
expect(runtime.error).toHaveBeenCalledWith(
expect.stringContaining("existing agents keep their current workspace"),
);
});
},
);
});

View file

@ -14,6 +14,7 @@ export {
export { createWindowsCmdShimFixture } from "../test-helpers/windows-cmd-shim.js";
export { createProviderUsageFetch, makeResponse } from "../test-utils/provider-usage-fetch.js";
export { useIsolatedStateGuard } from "../test-utils/state-path-guard.js";
export { createCanonicalAgentConfigFixture } from "../test-utils/config-roster.js";
export { withStateDirEnv } from "../test-helpers/state-dir-env.js";
export { captureEnv, withEnv, withEnvAsync } from "../test-utils/env.js";
export { withFetchPreconnect, type FetchMock } from "../test-utils/fetch-mock.js";

View file

@ -1,7 +1,7 @@
import "../src/test-utils/prepare-compiled-subprocesses.js";
import fs from "node:fs/promises";
import path from "node:path";
import { withTempHome } from "openclaw/plugin-sdk/test-env";
import { createCanonicalAgentConfigFixture, withTempHome } from "openclaw/plugin-sdk/test-env";
import { describe, expect, it } from "vitest";
import { runBuiltCli } from "./cli-json-stdout.test-support.js";
@ -184,18 +184,20 @@ describe("cli json stdout contract", () => {
await withTempHome(
async (tempHome) => {
const configPath = path.join(tempHome, "openclaw.json");
const existingConfig = `${JSON.stringify({
agents: {
ownership: "explicit",
list: [
{ id: "main", workspace: path.join(tempHome, "main") },
{ id: "ops", workspace: path.join(tempHome, "ops") },
const existingConfig = `${JSON.stringify(
createCanonicalAgentConfigFixture({
agents: {
ownership: "explicit",
list: [
{ id: "main", workspace: path.join(tempHome, "main") },
{ id: "ops", workspace: path.join(tempHome, "ops") },
],
},
bindings: [
{ type: "route", agentId: "ops", match: { channel: "telegram", accountId: "work" } },
],
},
bindings: [
{ type: "route", agentId: "ops", match: { channel: "telegram", accountId: "work" } },
],
})}\n`;
}).config,
)}\n`;
if ("conflict" in testCase) {
await fs.writeFile(configPath, existingConfig, "utf8");
}

View file

@ -24,6 +24,7 @@ import {
restoreActivePluginRegistrySnapshot,
setActivePluginRegistry,
} from "../../../../src/plugins/runtime.js";
import { createCanonicalAgentConfigFixture } from "../../../../src/test-utils/config-roster.js";
import {
GATEWAY_CLIENT_MODES,
GATEWAY_CLIENT_NAMES,
@ -113,7 +114,7 @@ describe("Canvas agent tool over a paired macOS node", () => {
// Released here until the started Gateway owns the claim.
let unstartedPortClaim: typeof portClaim | undefined = portClaim;
const gatewayToken = "qa-canvas-agent-node-token";
const config: OpenClawConfig = {
const config: OpenClawConfig = createCanonicalAgentConfigFixture({
gateway: {
mode: "local",
port,
@ -129,7 +130,7 @@ describe("Canvas agent tool over a paired macOS node", () => {
allow: ["canvas"],
entries: { canvas: { enabled: true } },
},
};
}).config;
await state.writeConfig(config);
const invocations: CapturedInvocation[] = [];

View file

@ -13,6 +13,7 @@ import {
disconnectGatewayClient,
} from "../../../../src/gateway/test-helpers.e2e.js";
import { loadOrCreateDeviceIdentity } from "../../../../src/infra/device-identity.js";
import { createCanonicalAgentConfigFixture } from "../../../../src/test-utils/config-roster.js";
import { stopQaGatewayFixture } from "../../../helpers/qa-gateway-cleanup.js";
import { stopChildProcess } from "../../../helpers/stop-child-process.js";
@ -130,7 +131,7 @@ describe("node workspace document access", () => {
});
const nodeId = nodeIdentity.deviceId;
const attachmentFixture = await writeAttachmentFixture(state.root);
const config: OpenClawConfig = {
const config: OpenClawConfig = createCanonicalAgentConfigFixture({
gateway: {
mode: "local",
bind: "loopback",
@ -176,7 +177,7 @@ describe("node workspace document access", () => {
},
},
},
};
}).config;
const gatewayOwner = createQaGatewayChild();
let owner: GatewayClient | undefined;
let reader: GatewayClient | undefined;