@@ -46,6 +46,21 @@ def _slug_filename(value: str) -> str:
4646 s = re .sub (r"[^a-zA-Z0-9]+" , "-" , str (value or "" ).strip ()).strip ("-" ).lower ()
4747 return s or "group"
4848
49+
50+ def _normalize_capability_id_list (raw : Any ) -> List [str ]:
51+ out : List [str ] = []
52+ if not isinstance (raw , list ):
53+ return out
54+ seen : set [str ] = set ()
55+ for item in raw :
56+ cap_id = str (item or "" ).strip ()
57+ if not cap_id or cap_id in seen :
58+ continue
59+ seen .add (cap_id )
60+ out .append (cap_id )
61+ return out
62+
63+
4964def _remove_runner_state_files (group_id : str , actor_id : str ) -> None :
5065 home = ensure_home ()
5166 gid = str (group_id or "" ).strip ()
@@ -98,6 +113,14 @@ def group_template_export(args: Dict[str, Any]) -> DaemonResponse:
98113 actor_tpl .runner = str (profile .get ("runner" ) or actor_tpl .runner ) # type: ignore[assignment]
99114 actor_tpl .command = list (profile .get ("command" ) or [])
100115 actor_tpl .submit = str (profile .get ("submit" ) or actor_tpl .submit ) # type: ignore[assignment]
116+ actor_autoload = _normalize_capability_id_list (actor_doc .get ("capability_autoload" ))
117+ defaults = profile .get ("capability_defaults" ) if isinstance (profile .get ("capability_defaults" ), dict ) else {}
118+ profile_autoload = []
119+ # Templates materialize linked actors as portable custom actors. Snapshot only
120+ # actor-scoped profile defaults here; session-scoped autoload remains runtime-specific.
121+ if str (defaults .get ("default_scope" ) or "actor" ).strip ().lower () == "actor" :
122+ profile_autoload = _normalize_capability_id_list (defaults .get ("autoload_capabilities" ))
123+ actor_tpl .capability_autoload = [* profile_autoload , * [cap for cap in actor_autoload if cap not in profile_autoload ]]
101124 except Exception as e :
102125 return _error ("template_export_failed" , str (e ))
103126 text = dump_group_template (tpl )
@@ -151,6 +174,7 @@ def _prompt_preview(kind: str, limit: int = 2000) -> Dict[str, Any]:
151174 "runner" : a .runner ,
152175 "command" : a .command ,
153176 "submit" : a .submit ,
177+ "capability_autoload" : list (a .capability_autoload or []),
154178 "enabled" : bool (a .enabled ),
155179 }
156180 for a in tpl .actors
@@ -268,6 +292,12 @@ def _int(k: str, *, min_v: int = 0, max_v: Optional[int] = None) -> None:
268292 n = 20
269293 patch ["terminal_transcript_notify_lines" ] = max (1 , min (80 , n ))
270294
295+ # Group feature toggles
296+ if "panorama_enabled" in settings :
297+ patch ["panorama_enabled" ] = coerce_bool (settings .get ("panorama_enabled" ), default = False )
298+ if "desktop_pet_enabled" in settings :
299+ patch ["desktop_pet_enabled" ] = coerce_bool (settings .get ("desktop_pet_enabled" ), default = False )
300+
271301 delivery_keys = {"min_interval_seconds" , "auto_mark_on_delivery" }
272302 automation_keys = {
273303 "nudge_after_seconds" ,
@@ -285,6 +315,7 @@ def _int(k: str, *, min_v: int = 0, max_v: Optional[int] = None) -> None:
285315 "help_nudge_min_messages" ,
286316 }
287317 messaging_keys = {"default_send_to" }
318+ feature_keys = {"panorama_enabled" , "desktop_pet_enabled" }
288319
289320 delivery = group .doc .get ("delivery" ) if isinstance (group .doc .get ("delivery" ), dict ) else {}
290321 automation = group .doc .get ("automation" ) if isinstance (group .doc .get ("automation" ), dict ) else {}
@@ -304,6 +335,12 @@ def _int(k: str, *, min_v: int = 0, max_v: Optional[int] = None) -> None:
304335 group .doc ["delivery" ] = delivery
305336 group .doc ["automation" ] = automation
306337 group .doc ["messaging" ] = messaging
338+ if feature_keys & set (patch .keys ()):
339+ features = group .doc .get ("features" ) if isinstance (group .doc .get ("features" ), dict ) else {}
340+ for k in feature_keys :
341+ if k in patch :
342+ features [k ] = coerce_bool (patch .get (k ), default = False )
343+ group .doc ["features" ] = features
307344
308345 tt_patch : Dict [str , Any ] = {}
309346 if "terminal_transcript_visibility" in patch :
@@ -462,6 +499,7 @@ def group_template_import_replace(args: Dict[str, Any]) -> DaemonResponse:
462499 "enabled" : bool (actor_tpl .enabled ),
463500 "submit" : str (actor_tpl .submit or "enter" ),
464501 "command" : _normalize_template_actor_command (actor_tpl ),
502+ "capability_autoload" : _normalize_capability_id_list (actor_tpl .capability_autoload ),
465503 }
466504
467505 existing = find_actor (group , aid )
@@ -484,6 +522,7 @@ def group_template_import_replace(args: Dict[str, Any]) -> DaemonResponse:
484522 env = {},
485523 default_scope_key = "" ,
486524 submit = str (patch .get ("submit" ) or "enter" ), # type: ignore[arg-type]
525+ capability_autoload = list (patch .get ("capability_autoload" ) or []),
487526 enabled = bool (patch .get ("enabled" , True )),
488527 runner = str (patch .get ("runner" ) or "pty" ), # type: ignore[arg-type]
489528 runtime = str (patch .get ("runtime" ) or "codex" ), # type: ignore[arg-type]
0 commit comments