Skip to content

Commit ce2425f

Browse files
committed
fix(sandbox): persist force-unregister cleanup, unify create lifecycle, best-effort skill sync
- _cleanup_provider_sandboxes_sync now calls registry.save() after deleting records so force-unregister cleanup is persisted to disk. - Extract _finalize_created_booter helper and call it from both get_or_create_booter and create_sandbox_uncontrolled so explicit sandbox creation (e.g. astrbot_create_sandbox) also runs skill sync and on_sandbox_created hooks. - Wrap auto skill sync in try/except so a sync failure logs a warning instead of leaving a live but orphaned sandbox registered in state.
1 parent 5744856 commit ce2425f

2 files changed

Lines changed: 43 additions & 16 deletions

File tree

astrbot/core/computer/computer_client.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -143,6 +143,13 @@ def _cleanup_provider_sandboxes_sync(provider_id: str) -> None:
143143
)
144144
except RuntimeError:
145145
pass # no running event loop
146+
try:
147+
sandbox_manager.registry.save()
148+
except Exception as exc:
149+
logger.warning(
150+
"[Computer] Failed to save registry after force-unregister: %s",
151+
exc,
152+
)
146153
logger.info(
147154
"Force-unregistered sandbox provider %s: sandboxes cleaned up",
148155
provider_id,

astrbot/core/computer/sandbox_manager.py

Lines changed: 36 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -274,33 +274,54 @@ async def get_or_create_booter(
274274
self.session_booter[target_sandbox_id] = client
275275
break
276276

277-
self.registry.touch_sandbox(target_sandbox_id)
278-
self.registry.update_sandbox_status(target_sandbox_id, "running")
279-
self.registry.set_current_sandbox_id(session_id, target_sandbox_id)
277+
await self._finalize_created_booter(
278+
provider, target_sandbox_id, session_id=session_id, idle_timeout=idle_timeout
279+
)
280+
return self.session_booter[target_sandbox_id]
281+
282+
async def _finalize_created_booter(
283+
self,
284+
provider: SandboxProvider,
285+
sandbox_id: str,
286+
*,
287+
session_id: str | None = None,
288+
idle_timeout: float,
289+
) -> None:
290+
"""Common post-creation steps: persist, idle cleanup, skill sync, hooks."""
291+
self.registry.touch_sandbox(sandbox_id)
292+
self.registry.update_sandbox_status(sandbox_id, "running")
293+
if session_id is not None:
294+
self.registry.set_current_sandbox_id(session_id, sandbox_id)
280295
await self.save_registry_async()
281-
self.schedule_idle_cleanup(target_sandbox_id, idle_timeout)
296+
self.schedule_idle_cleanup(sandbox_id, idle_timeout)
282297

283-
# Auto-sync skills unless the provider opts out.
298+
# Auto-sync skills unless the provider opts out. Best-effort: a sync
299+
# failure is logged but does not destroy the already-created sandbox.
284300
if getattr(provider, "auto_sync_skills", True):
285-
booter = self.session_booter[target_sandbox_id]
286-
if hasattr(booter, "shell"):
287-
await self._sync_skills_to_booter(booter)
301+
booter = self.session_booter.get(sandbox_id)
302+
if booter is not None and hasattr(booter, "shell"):
303+
try:
304+
await self._sync_skills_to_booter(booter)
305+
except Exception as sync_err:
306+
logger.warning(
307+
"[Computer] Auto skill sync failed for %s: %s",
308+
sandbox_id,
309+
sync_err,
310+
)
288311

289312
# Optional lifecycle hook.
290313
if hasattr(provider, "on_sandbox_created"):
291314
try:
292315
await provider.on_sandbox_created(
293-
self.registry.get_sandbox(target_sandbox_id) or {}
316+
self.registry.get_sandbox(sandbox_id) or {}
294317
)
295318
except Exception as hook_err:
296319
logger.warning(
297320
"[Computer] on_sandbox_created hook failed for %s: %s",
298-
target_sandbox_id,
321+
sandbox_id,
299322
hook_err,
300323
)
301324

302-
return self.session_booter[target_sandbox_id]
303-
304325
async def create_sandbox_uncontrolled(
305326
self,
306327
context: Context,
@@ -335,10 +356,9 @@ async def create_sandbox_uncontrolled(
335356
raise
336357
setattr(client, "sandbox_id", sandbox_id)
337358
self.session_booter[sandbox_id] = client
338-
self.registry.touch_sandbox(sandbox_id)
339-
self.registry.update_sandbox_status(sandbox_id, "running")
340-
await self.save_registry_async()
341-
self.schedule_idle_cleanup(sandbox_id, idle_timeout)
359+
await self._finalize_created_booter(
360+
provider, sandbox_id, session_id=None, idle_timeout=idle_timeout
361+
)
342362
return self.registry.get_sandbox(sandbox_id) or record
343363

344364
async def create_sandbox(

0 commit comments

Comments
 (0)