@@ -11,7 +11,10 @@ import {
1111} from "../../../state/onboard-checkpoint-decision" ;
1212import { CHECKPOINT_SCHEMA_VERSION } from "../../../state/onboard-checkpoint-types" ;
1313import { createSession , type Session } from "../../../state/onboard-session" ;
14- import { detectMessagingChannelsFromEnv } from "../../messaging-channel-setup" ;
14+ import {
15+ detectMessagingChannelsFromEnv ,
16+ detectUnconfiguredMessagingChannels ,
17+ } from "../../messaging-channel-setup" ;
1518import { handleSandboxState } from "./sandbox" ;
1619import {
1720 baseOptions ,
@@ -28,6 +31,7 @@ vi.mock("../../messaging-channel-setup", () => ({
2831} ) ) ;
2932
3033const detectMessagingChannelsFromEnvMock = vi . mocked ( detectMessagingChannelsFromEnv ) ;
34+ const detectUnconfiguredMessagingChannelsMock = vi . mocked ( detectUnconfiguredMessagingChannels ) ;
3135
3236function dcodeRegistryEntry ( name : string , observabilityEnabled ?: boolean ) {
3337 return {
@@ -47,6 +51,7 @@ function dcodeRegistryEntry(name: string, observabilityEnabled?: boolean) {
4751describe ( "handleSandboxState" , ( ) => {
4852 beforeEach ( ( ) => {
4953 detectMessagingChannelsFromEnvMock . mockReturnValue ( [ ] ) ;
54+ detectUnconfiguredMessagingChannelsMock . mockReturnValue ( [ ] ) ;
5055 } ) ;
5156
5257 it ( "creates a sandbox and records messaging/web search state" , async ( ) => {
@@ -603,6 +608,38 @@ describe("handleSandboxState", () => {
603608 expect ( result . session ) . toBe ( skippedSession ) ;
604609 } ) ;
605610
611+ it ( "omits an unconfigured host-backed channel when reusing a Ready sandbox (#9283)" , async ( ) => {
612+ const registryPlan = makeMinimalPlan ( "saved" , "openclaw" , [ "discord" ] ) ;
613+ const session = createSession ( { sandboxName : "saved" , messagingPlan : registryPlan } ) ;
614+ session . steps . sandbox . status = "complete" ;
615+ vi . stubEnv ( "DISCORD_BOT_TOKEN" , "" ) ;
616+ detectUnconfiguredMessagingChannelsMock . mockReturnValue ( [ "discord" ] ) ;
617+ const { deps, calls } = createDeps ( {
618+ getSandboxReuseState : ( ) => "ready" ,
619+ getSandboxRegistryEntry : ( ) => ( {
620+ name : "saved" ,
621+ pendingRouteReservation : true ,
622+ provider : "provider" ,
623+ model : "model" ,
624+ endpointUrl : null ,
625+ preferredInferenceApi : "openai-completions" ,
626+ toolDisclosure : "progressive" ,
627+ fromDockerfile : null ,
628+ hermesAuthMethod : null ,
629+ } ) ,
630+ getRegistrySandboxMessagingAuthority : ( ) => ( { authoritative : true , plan : registryPlan } ) ,
631+ } ) ;
632+
633+ const result = await handleSandboxState ( {
634+ ...baseOptions ( deps , session ) ,
635+ resume : true ,
636+ sandboxName : "saved" ,
637+ } ) ;
638+
639+ expect ( calls . createSandbox ) . not . toHaveBeenCalled ( ) ;
640+ expect ( result . selectedMessagingChannels ) . toEqual ( [ ] ) ;
641+ } ) ;
642+
606643 it ( "treats checkpoint machine-state progress past sandbox as step-complete even when the legacy step status is stale (#6228)" , async ( ) => {
607644 const session = createSession ( {
608645 sandboxName : "saved" ,
0 commit comments