diff --git a/scripts/lib/openclaw-test-state.mts b/scripts/lib/openclaw-test-state.mts index 3cd019d5fe74..53c525be2dcd 100644 --- a/scripts/lib/openclaw-test-state.mts +++ b/scripts/lib/openclaw-test-state.mts @@ -127,17 +127,20 @@ function scenarioConfig(scenario: string, options: TestStateOptions = {}) { }, }, agents: { + ownership: "explicit", defaults: { model: { primary: "openai/gpt-5.6-luna", }, contextTokens: 64000, skills: ["memory"], + authInheritance: { agentId: "main" }, + heartbeat: { agentId: "main" }, sessionStore: { agentId: "main" }, + systemAgent: { agentId: "main" }, }, entries: { main: { - default: true, name: "Main", workspace: "~/workspace", model: { @@ -157,6 +160,11 @@ function scenarioConfig(scenario: string, options: TestStateOptions = {}) { }, }, }, + bindings: [ + { agentId: "main", match: { channel: "discord", accountId: "*" } }, + { agentId: "main", match: { channel: "telegram", accountId: "*" } }, + { agentId: "main", match: { channel: "whatsapp", accountId: "*" } }, + ], skills: { allowBundled: ["memory", "openclaw-testing"], limits: { @@ -177,10 +185,8 @@ function scenarioConfig(scenario: string, options: TestStateOptions = {}) { discord: { enabled: true, token: { source: "env", provider: "default", id: "DISCORD_BOT_TOKEN" }, - dm: { - policy: "allowlist", - allowFrom: ["111111111111111111"], - }, + dmPolicy: "allowlist", + allowFrom: ["111111111111111111"], groupPolicy: "allowlist", guilds: { "222222222222222222": { @@ -473,6 +479,7 @@ OPENCLAW_TEST_STATE_JSON } }, "agents": { + "ownership": "explicit", "defaults": { "model": { "primary": "openai/gpt-5.6-luna" @@ -481,14 +488,21 @@ OPENCLAW_TEST_STATE_JSON "skills": [ "memory" ], + "authInheritance": { + "agentId": "main" + }, + "heartbeat": { + "agentId": "main" + }, "sessionStore": { "agentId": "main" + }, + "systemAgent": { + "agentId": "main" } }, - "list": [ - { - "id": "main", - "default": true, + "entries": { + "main": { "name": "Main", "workspace": "~/workspace", "model": { @@ -500,8 +514,7 @@ OPENCLAW_TEST_STATE_JSON ], "contextTokens": 64000 }, - { - "id": "ops", + "ops": { "name": "Ops", "workspace": "~/workspace/ops", "model": { @@ -509,8 +522,31 @@ OPENCLAW_TEST_STATE_JSON }, "fastModeDefault": true } - ] + } }, + "bindings": [ + { + "agentId": "main", + "match": { + "channel": "discord", + "accountId": "*" + } + }, + { + "agentId": "main", + "match": { + "channel": "telegram", + "accountId": "*" + } + }, + { + "agentId": "main", + "match": { + "channel": "whatsapp", + "accountId": "*" + } + } + ], "skills": { "allowBundled": [ "memory", @@ -549,12 +585,10 @@ OPENCLAW_TEST_STATE_JSON "provider": "default", "id": "DISCORD_BOT_TOKEN" }, - "dm": { - "policy": "allowlist", - "allowFrom": [ - "111111111111111111" - ] - }, + "dmPolicy": "allowlist", + "allowFrom": [ + "111111111111111111" + ], "groupPolicy": "allowlist", "guilds": { "222222222222222222": { diff --git a/src/commands/onboard-helpers.ts b/src/commands/onboard-helpers.ts index 73cd0ce1e808..d385c29bb600 100644 --- a/src/commands/onboard-helpers.ts +++ b/src/commands/onboard-helpers.ts @@ -15,6 +15,7 @@ import { stylePromptTitle } from "../../packages/terminal-core/src/prompt-style. import { resolveAgentEffectiveModelPrimary, resolveDefaultAgentId } from "../agents/agent-scope.js"; import { DEFAULT_AGENT_WORKSPACE_DIR, ensureAgentWorkspace } from "../agents/workspace.js"; import { printClawBanner } from "../cli/claw-banner.js"; +import { inheritLegacyDefaultAgentId } from "../config/legacy.default-agent-owner.js"; import { resolveAgentModelPrimaryValue } from "../config/model-input.js"; import { resolveConfigPath, resolveStateDir } from "../config/paths.js"; import { resolveSessionTranscriptsDirForAgent } from "../config/sessions/paths.js"; @@ -190,7 +191,7 @@ export function applyWizardMetadata( ): OpenClawConfig { const commit = normalizeOptionalString(process.env.GIT_COMMIT) ?? normalizeOptionalString(process.env.GIT_SHA); - return { + return inheritLegacyDefaultAgentId(cfg, { ...cfg, wizard: { ...cfg.wizard, @@ -200,7 +201,7 @@ export function applyWizardMetadata( lastRunCommand: params.command, lastRunMode: params.mode, }, - }; + }); } /** Formats the no-GUI SSH tunnel hint for opening the Control UI remotely. */ diff --git a/src/commands/onboard-helpers.wizard-metadata.test.ts b/src/commands/onboard-helpers.wizard-metadata.test.ts new file mode 100644 index 000000000000..37d7a4dfa7bc --- /dev/null +++ b/src/commands/onboard-helpers.wizard-metadata.test.ts @@ -0,0 +1,22 @@ +// Wizard metadata tests cover config provenance without losing migration ownership. +import { describe, expect, it } from "vitest"; +import { tryResolveLegacyCompatibilityAgentId } from "../config/legacy.default-agent-owner.js"; +import { migratePersistedImplicitMainRoster } from "../config/legacy.roster.js"; +import type { OpenClawConfig } from "../config/types.openclaw.js"; +import { applyWizardMetadata } from "./onboard-helpers.js"; + +describe("applyWizardMetadata", () => { + it("preserves the migrated legacy owner across the config replacement", () => { + const cfg = migratePersistedImplicitMainRoster({ + agents: { + list: [{ id: "main", default: true }, { id: "ops" }], + }, + }).config as OpenClawConfig; + expect(tryResolveLegacyCompatibilityAgentId(cfg)).toBe("main"); + + const result = applyWizardMetadata(cfg, { command: "doctor", mode: "local" }); + + expect(result).not.toBe(cfg); + expect(tryResolveLegacyCompatibilityAgentId(result)).toBe("main"); + }); +}); diff --git a/test/scripts/openclaw-test-state.test.ts b/test/scripts/openclaw-test-state.test.ts index 070a11daeff2..b2de2419a72f 100644 --- a/test/scripts/openclaw-test-state.test.ts +++ b/test/scripts/openclaw-test-state.test.ts @@ -233,11 +233,28 @@ describe("scripts/lib/openclaw-test-state", () => { source: "env", }, }); - expect(payload.config.channels.discord.enabled).toBe(true); - expect(payload.config.channels.discord.dm).toStrictEqual({ - allowFrom: ["111111111111111111"], - policy: "allowlist", + expect(payload.config.agents.ownership).toBe("explicit"); + expect(payload.config.agents.defaults).toMatchObject({ + authInheritance: { agentId: "main" }, + heartbeat: { agentId: "main" }, + sessionStore: { agentId: "main" }, + systemAgent: { agentId: "main" }, }); + expect(payload.config.bindings).toEqual([ + { agentId: "main", match: { channel: "discord", accountId: "*" } }, + { agentId: "main", match: { channel: "telegram", accountId: "*" } }, + { agentId: "main", match: { channel: "whatsapp", accountId: "*" } }, + ]); + expect(Object.keys(payload.config.agents.entries)).toEqual(["main", "ops"]); + expect(payload.config.agents).not.toHaveProperty("list"); + for (const agent of Object.values(payload.config.agents.entries)) { + expect(agent).not.toHaveProperty("default"); + } + expect(payload.config.channels.discord.enabled).toBe(true); + expect(payload.config.channels.discord.dmPolicy).toBe("allowlist"); + expect(payload.config.channels.discord.allowFrom).toEqual(["111111111111111111"]); + expect(payload.config.channels.discord.dm?.policy).toBeUndefined(); + expect(payload.config.channels.discord.dm?.allowFrom).toBeUndefined(); expect(payload.config.channels.telegram.enabled).toBe(true); expect(payload.config.channels.whatsapp.enabled).toBe(true); } finally { @@ -273,6 +290,33 @@ describe("scripts/lib/openclaw-test-state", () => { expect(payload.secretKey).toMatch(secretKeyPattern); expect(payload.config).toStrictEqual({}); + const upgradeProbe = await execFileAsync("bash", [ + "-lc", + `${cleanupTestStateHomeTrap()}; export OPENCLAW_TEST_STATE_TMPDIR=${shellQuote(path.join(tempRoot, "upgrade-function-tmp"))}; source ${shellQuote(snippetFile)}; openclaw_test_state_create "upgrade case" upgrade-survivor; node -e 'const fs=require("node:fs"); process.stdout.write(fs.readFileSync(process.env.OPENCLAW_CONFIG_PATH,"utf8"));'`, + ]); + const upgradeConfig = JSON.parse(upgradeProbe.stdout); + expect(upgradeConfig.agents.ownership).toBe("explicit"); + expect(upgradeConfig.agents.defaults).toMatchObject({ + authInheritance: { agentId: "main" }, + heartbeat: { agentId: "main" }, + sessionStore: { agentId: "main" }, + systemAgent: { agentId: "main" }, + }); + expect(upgradeConfig.bindings).toEqual([ + { agentId: "main", match: { channel: "discord", accountId: "*" } }, + { agentId: "main", match: { channel: "telegram", accountId: "*" } }, + { agentId: "main", match: { channel: "whatsapp", accountId: "*" } }, + ]); + expect(Object.keys(upgradeConfig.agents.entries)).toEqual(["main", "ops"]); + expect(upgradeConfig.agents).not.toHaveProperty("list"); + for (const agent of Object.values(upgradeConfig.agents.entries)) { + expect(agent).not.toHaveProperty("default"); + } + expect(upgradeConfig.channels.discord.dmPolicy).toBe("allowlist"); + expect(upgradeConfig.channels.discord.allowFrom).toEqual(["111111111111111111"]); + expect(upgradeConfig.channels.discord.dm?.policy).toBeUndefined(); + expect(upgradeConfig.channels.discord.dm?.allowFrom).toBeUndefined(); + const trailingTmpDir = path.join(tempRoot, "function-trailing-tmp"); const trailingProbe = await execFileAsync("bash", [ "-lc",