Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
40 changes: 40 additions & 0 deletions src/lib/onboard/machine/handlers/sandbox-messaging.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -424,6 +424,22 @@ describe("reconcileReusedSandboxMessaging", () => {
expect(result.selectedChannels).toEqual(["whatsapp"]);
});

it("keeps a lifecycle-selected channel in a reused sandbox selection (#9283)", () => {
const plan = {
...discordPlan(hashCredential("previous-discord-token") ?? ""),
workflow: "add-channel" as const,
};
vi.stubEnv("DISCORD_BOT_TOKEN", "");

const result = reconcileReusedSandboxMessaging(
plan,
{ name: "openclaw" },
{ clearPlanEnv() {} },
);

expect(result).toEqual({ plan, selectedChannels: ["discord"], changed: false });
});

it("removes every unsupported channel artifact from a reused plan", () => {
// Keep the channel host-configured so this case stays about unsupported
// artifact removal, not the #9283 unconfigured-channel selection filter.
Expand Down Expand Up @@ -597,6 +613,30 @@ describe("reconcileSandboxMessaging plan authority", () => {
expect(result).toEqual({ plan: null, selectedChannels: ["whatsapp"] });
});

it("keeps a lifecycle-selected channel in a completed registry resume (#9283)", async () => {
const registryPlan = {
...discordPlan(hashCredential("previous-discord-token") ?? ""),
workflow: "add-channel" as const,
};
const deps = reconcileDeps([]);
deps.getRegistrySandboxMessagingAuthority.mockReturnValue({
authoritative: true,
plan: registryPlan,
});
vi.stubEnv("DISCORD_BOT_TOKEN", "");

const result = await reconcileSandboxMessaging({
resume: true,
session: completedCheckpointSession(registryPlan),
sandboxName: "alpha",
agent: { name: "openclaw" },
deps,
});

expect(deps.setupMessagingChannels).not.toHaveBeenCalled();
expect(result).toEqual({ plan: registryPlan, selectedChannels: ["discord"] });
});

it("keeps an in-sandbox QR channel in a completed registry resume (#9109)", async () => {
const registryPlan = whatsappPlan();
const deps = reconcileDeps([]);
Expand Down
18 changes: 10 additions & 8 deletions src/lib/onboard/machine/handlers/sandbox-messaging.ts
Original file line number Diff line number Diff line change
Expand Up @@ -435,15 +435,16 @@ export function reconcileReusedSandboxMessaging<Agent>(
const filtered = plan ? filterMessagingPlanForCurrentAgent(plan, agent) : null;
const changed = !isDeepStrictEqual(filtered, recordedPlan);
if (changed) deps.clearPlanEnv();
// The reused plan records the previous selection, not the current host
// input. Report only channels the environment still configures so the
// policies handler can classify a retired channel as unconfigured and drop
// its egress preset (#9283). The plan itself stays untouched.
const selection = {
plan: filtered,
selectedChannels: getActiveChannelsFromPlan(filtered),
};
const currentSelection =
filtered && registryPlanRecordsLifecycleSelection(filtered)
? selection
: filterUnconfiguredHostChannelsFromSelection(selection, agent);
return {
...filterUnconfiguredHostChannelsFromSelection(
{ plan: filtered, selectedChannels: getActiveChannelsFromPlan(filtered) },
agent,
),
...currentSelection,
changed,
};
}
Expand Down Expand Up @@ -614,6 +615,7 @@ async function selectionFromRegistryAuthority<Agent>(
authority.plan,
false,
);
if (authority.plan && registryPlanRecordsLifecycleSelection(authority.plan)) return selection;
return filterUnconfiguredHostChannelsFromSelection(selection, options.agent);
}
if (authority.plan) return selectionFromRegistryPlan(authority.plan, options);
Expand Down
Loading