@@ -10,7 +10,8 @@ import {
1010 createTestHostEnvironment ,
1111} from '@agent-device/test-kit' ;
1212import { createDaemonRuntimeDeps } from './daemon-composition.ts' ;
13- import { helperRuntimeSlot } from '@agent-device/platform-android/adb-host' ;
13+ import { createRuntimeStateHandle , runtimeSlot } from '@agent-device/host-kit/runtime-slots' ;
14+ import { resolveRunnerAppBundleId } from '@agent-device/platform-apple/runner/operations' ;
1415import { setActiveProviderDeviceRuntimes } from './provider-device-runtime.ts' ;
1516
1617const ports = vi . hoisted ( ( ) => ( {
@@ -126,13 +127,20 @@ test('a composition that succeeds leaves the plugins open for the daemon to clos
126127 expect ( ports . shutdownProviders ) . toHaveBeenCalledOnce ( ) ;
127128} ) ;
128129
129- test ( 'a composed daemon registers the Apple runner and Android helper state, one set per composition ' , async ( ) => {
130+ test ( 'each composition binds its own runtime state into the entered handle, not a returned one ' , async ( ) => {
130131 const compose = async ( name : string ) => {
131132 const baseDir = mkdtempForTestSync ( `agent-device-daemon-composition-states-${ name } -` ) ;
132- return await createDaemonRuntimeDeps ( {
133- host : createTestHostEnvironment ( {
134- env : { ...process . env , AGENT_DEVICE_STATE_DIR : baseDir } ,
135- } ) ,
133+ const host = createTestHostEnvironment ( {
134+ env : {
135+ ...process . env ,
136+ AGENT_DEVICE_STATE_DIR : baseDir ,
137+ // The runner's first-choice configuration key: the per-runtime read below must see this
138+ // runtime's value even when the process env (spread above) carries a different one.
139+ AGENT_DEVICE_IOS_BUNDLE_ID : `com.test.runtime.${ name } ` ,
140+ } ,
141+ } ) ;
142+ const deps = await createDaemonRuntimeDeps ( {
143+ host,
136144 baseDir,
137145 sessionsDir : `${ baseDir } /sessions` ,
138146 assertShutdownAllowed : undefined ,
@@ -142,21 +150,28 @@ test('a composed daemon registers the Apple runner and Android helper state, one
142150 } ,
143151 openWebSessionNames : ( ) => [ ] ,
144152 } ) ;
153+ return { deps, host } ;
145154 } ;
146- const first = await compose ( 'first' ) ;
147- const second = await compose ( 'second' ) ;
148155 const slotKey = Symbol ( 'composition-test' ) ;
149156
150- // Apple's runner state and Android's helper state.
151- expect ( first . runtimeStates ) . toHaveLength ( 2 ) ;
152- expect ( second . runtimeStates ) . toHaveLength ( 2 ) ;
153- // The Android handle scopes the slots the helper modules read.
154- const [ , firstAndroid ] = first . runtimeStates ;
155- const [ , secondAndroid ] = second . runtimeStates ;
156- const inFirst = firstAndroid ! . run ( ( ) => helperRuntimeSlot ( slotKey , ( ) => ( { } ) ) ) ;
157- const inSecond = secondAndroid ! . run ( ( ) => helperRuntimeSlot ( slotKey , ( ) => ( { } ) ) ) ;
157+ // One entered store per runtime (#3794): the daemon enters the handle, then composes into it,
158+ // so each composition's writes — the Apple runner host, and any slot a platform module declares
159+ // — land in that runtime's state and nowhere else. The host binding is read back through the
160+ // runner's own configuration read (`resolveRunnerAppBundleId` takes its env from the bound
161+ // host), the same path production resource cleanup uses.
162+ const first = createRuntimeStateHandle ( ) ;
163+ const second = createRuntimeStateHandle ( ) ;
164+ const firstRun = await first . run ( ( ) => compose ( 'first' ) ) ;
165+ const secondRun = await second . run ( ( ) => compose ( 'second' ) ) ;
166+ expect ( firstRun . deps ) . toBeTypeOf ( 'object' ) ;
167+ expect ( secondRun . deps ) . toBeTypeOf ( 'object' ) ;
168+
169+ const inFirst = first . run ( ( ) => runtimeSlot ( slotKey , ( ) => ( { } ) ) ) ;
170+ const inSecond = second . run ( ( ) => runtimeSlot ( slotKey , ( ) => ( { } ) ) ) ;
158171 expect ( inFirst ) . not . toBe ( inSecond ) ;
159- expect ( firstAndroid ! . run ( ( ) => helperRuntimeSlot ( slotKey , ( ) => ( { } ) ) ) ) . toBe ( inFirst ) ;
172+ expect ( first . run ( ( ) => runtimeSlot ( slotKey , ( ) => ( { } ) ) ) ) . toBe ( inFirst ) ;
173+ expect ( first . run ( ( ) => resolveRunnerAppBundleId ( ) ) ) . toBe ( 'com.test.runtime.first' ) ;
174+ expect ( second . run ( ( ) => resolveRunnerAppBundleId ( ) ) ) . toBe ( 'com.test.runtime.second' ) ;
160175} ) ;
161176
162177test ( 'the composed daemon route configures interactors from its host, not from the process (#3794)' , async ( ) => {
0 commit comments