| 540 | |
| 541 | |
| 542 | async def get_booter( |
| 543 | context: Context, |
| 544 | session_id: str, |
| 545 | ) -> ComputerBooter: |
| 546 | config = context.get_config(umo=session_id) |
| 547 | |
| 548 | runtime = config.get("provider_settings", {}).get("computer_use_runtime", "local") |
| 549 | if runtime == "local": |
| 550 | return get_local_booter() |
| 551 | elif runtime == "none": |
| 552 | raise RuntimeError("Sandbox runtime is disabled by configuration.") |
| 553 | |
| 554 | sandbox_cfg = config.get("provider_settings", {}).get("sandbox", {}) |
| 555 | booter_type = sandbox_cfg.get("booter", "shipyard_neo") |
| 556 | cua_idle_timeout = _get_cua_idle_timeout(config) if booter_type == "cua" else 0.0 |
| 557 | |
| 558 | if session_id in session_booter: |
| 559 | booter = session_booter[session_id] |
| 560 | if not await booter.available(): |
| 561 | # Clean up old booter before rebuilding so sandbox resources |
| 562 | # on Bay (containers, volumes, networks) are not leaked. |
| 563 | # Only ShipyardNeoBooter supports delete_sandbox; other booters |
| 564 | # (local, boxlite, cua, etc.) are not backed by a remote sandbox |
| 565 | # manager and don't need it. |
| 566 | try: |
| 567 | if booter_type == "shipyard_neo": |
| 568 | await booter.shutdown(delete_sandbox=True) |
| 569 | else: |
| 570 | await booter.shutdown() |
| 571 | except Exception as shutdown_err: |
| 572 | logger.warning( |
| 573 | "[Computer] Error shutting down stale booter for session %s: %s", |
| 574 | session_id, |
| 575 | shutdown_err, |
| 576 | ) |
| 577 | _clear_cua_idle_state(session_id) |
| 578 | session_booter.pop(session_id, None) |
| 579 | if session_id not in session_booter: |
| 580 | uuid_str = uuid.uuid5(uuid.NAMESPACE_DNS, session_id).hex |
| 581 | logger.info( |
| 582 | f"[Computer] Initializing booter: type={booter_type}, session={session_id}" |
| 583 | ) |
| 584 | if booter_type == "shipyard": |
| 585 | from .booters.shipyard import ShipyardBooter |
| 586 | |
| 587 | ep = sandbox_cfg.get("shipyard_endpoint", "") |
| 588 | token = sandbox_cfg.get("shipyard_access_token", "") |
| 589 | ttl = sandbox_cfg.get("shipyard_ttl", 3600) |
| 590 | max_sessions = sandbox_cfg.get("shipyard_max_sessions", 10) |
| 591 | |
| 592 | client = ShipyardBooter( |
| 593 | endpoint_url=ep, access_token=token, ttl=ttl, session_num=max_sessions |
| 594 | ) |
| 595 | elif booter_type == "shipyard_neo": |
| 596 | from .booters.shipyard_neo import ShipyardNeoBooter |
| 597 | |
| 598 | ep = sandbox_cfg.get("shipyard_neo_endpoint", "") |
| 599 | token = sandbox_cfg.get("shipyard_neo_access_token", "") |