@team-agent/installer 0.3.3 → 0.3.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -72,9 +72,8 @@ pub fn launch_with_transport_in_workspace(
72
72
  spec_path.display()
73
73
  )));
74
74
  }
75
- let text = std::fs::read_to_string(spec_path).map_err(|e| {
76
- LifecycleError::Compile(format!("{}: {e}", spec_path.display()))
77
- })?;
75
+ let text = std::fs::read_to_string(spec_path)
76
+ .map_err(|e| LifecycleError::Compile(format!("{}: {e}", spec_path.display())))?;
78
77
  let spec = yaml::loads(&text).map_err(|e| LifecycleError::Compile(e.to_string()))?;
79
78
  let session_name = spec_session_name(&spec);
80
79
  let safety = effective_runtime_config(&spec)?;
@@ -101,8 +100,23 @@ pub fn launch_with_transport_in_workspace(
101
100
  let started = if dry_run {
102
101
  Vec::new()
103
102
  } else {
104
- let started = spawn_agents(workspace, spec_path, &spec, &session_name, &safety, transport)?;
105
- persist_spawn_agent_state(workspace, spec_path, &spec, &session_name, transport, &started, &safety)?;
103
+ let started = spawn_agents(
104
+ workspace,
105
+ spec_path,
106
+ &spec,
107
+ &session_name,
108
+ &safety,
109
+ transport,
110
+ )?;
111
+ persist_spawn_agent_state(
112
+ workspace,
113
+ spec_path,
114
+ &spec,
115
+ &session_name,
116
+ transport,
117
+ &started,
118
+ &safety,
119
+ )?;
106
120
  started
107
121
  };
108
122
  Ok(LaunchReport {
@@ -152,9 +166,19 @@ fn spawn_agents(
152
166
  // has both the role instruction AND the callable Team Agent MCP capability.
153
167
  // probe5 RED proved that `build_command(.., None, None, ..)` left the worker
154
168
  // without `report_result`; placeholders are substituted at spawn time.
155
- let role = agent.get("role").and_then(Value::as_str);
156
- let tools = worker_tool_refs(agent_tool_strings(agent), safety);
157
- let tool_refs: Vec<&str> = tools.iter().map(String::as_str).collect();
169
+ let command_agent = crate::lifecycle::worker_command_context::WorkerCommandAgent::from_yaml(
170
+ agent,
171
+ Some(agent_id_raw),
172
+ provider,
173
+ );
174
+ let system_prompt =
175
+ crate::lifecycle::worker_command_context::compile_worker_system_prompt(&command_agent)?;
176
+ let tools = crate::lifecycle::worker_command_context::resolved_tool_strings_for_command(
177
+ &command_agent,
178
+ provider,
179
+ safety,
180
+ )?;
181
+ let resolved_tool_refs: Vec<&str> = tools.iter().map(String::as_str).collect();
158
182
  let mcp_team_id =
159
183
  runtime_active_team_key_for_spawn(workspace, spec_path, spec, session_name);
160
184
  let mcp_config = adapter
@@ -163,43 +187,32 @@ fn spawn_agents(
163
187
  let mcp_config = resolve_mcp_config(mcp_config, workspace, agent_id_raw, &mcp_team_id);
164
188
  let mcp_config_path = write_worker_mcp_config(workspace, agent_id_raw, &mcp_config)?;
165
189
  let profile_dir = team_dir.join("profiles");
166
- let profile_launch = crate::lifecycle::profile_launch::prepare_provider_profile_launch_with_profile_dir(
167
- workspace,
168
- agent_id_raw,
169
- agent,
170
- Some(&profile_dir),
171
- Some(&mcp_config),
172
- )?;
173
- let command_model = profile_launch
174
- .command_overrides
175
- .model
176
- .as_deref()
177
- .or(model);
190
+ let profile_launch =
191
+ crate::lifecycle::profile_launch::prepare_provider_profile_launch_with_profile_dir(
192
+ workspace,
193
+ agent_id_raw,
194
+ agent,
195
+ Some(&profile_dir),
196
+ Some(&mcp_config),
197
+ )?;
198
+ let command_model = profile_launch.command_overrides.model.as_deref().or(model);
178
199
  let mut plan = adapter
179
200
  .build_command_plan(crate::provider::ProviderCommandContext {
180
201
  auth_mode,
181
202
  mcp_config: Some(&mcp_config),
182
- system_prompt: role,
203
+ system_prompt: Some(system_prompt.as_str()),
183
204
  model: command_model,
184
- tools: &tool_refs,
205
+ tools: &resolved_tool_refs,
185
206
  profile_launch: Some(&profile_launch),
186
207
  })
187
208
  .map_err(|e| LifecycleError::Provider(e.to_string()))?;
188
209
  if !plan.managed_mcp_config && !profile_launch.managed_mcp_config {
189
210
  point_native_mcp_config_at_file(&mut plan.argv, provider, &mcp_config_path);
190
211
  }
191
- fill_spawn_placeholders_full(
192
- &mut plan.argv,
193
- workspace,
194
- agent_id_raw,
195
- Some(&mcp_team_id),
196
- );
212
+ fill_spawn_placeholders_full(&mut plan.argv, workspace, agent_id_raw, Some(&mcp_team_id));
197
213
  let window = WindowName::new(agent_id_raw);
198
- let mut env = inherited_env_with_team_overrides(
199
- workspace,
200
- agent_id_raw,
201
- Some(&mcp_team_id),
202
- );
214
+ let mut env =
215
+ inherited_env_with_team_overrides(workspace, agent_id_raw, Some(&mcp_team_id));
203
216
  apply_profile_launch_env(&mut env, &profile_launch);
204
217
  let spawn = if started.is_empty() {
205
218
  transport.spawn_first(session_name, &window, &plan.argv, team_dir, &env)
@@ -258,12 +271,7 @@ fn persist_spawn_agent_state(
258
271
  let team_id = explicit_active_team_key(&state)
259
272
  .unwrap_or_else(|| runtime_team_key_for_spec(spec_path, spec, session_name));
260
273
  let worker_tmux_socket = launched_worker_tmux_socket(transport, workspace);
261
- drop_worker_pane_seeded_owner(
262
- &mut state,
263
- &team_id,
264
- started,
265
- worker_tmux_socket.as_deref(),
266
- );
274
+ drop_worker_pane_seeded_owner(&mut state, &team_id, started, worker_tmux_socket.as_deref());
267
275
  // Only persist running state for agents whose spawn still has a live target.
268
276
  let live_windows: BTreeSet<String> = transport
269
277
  .list_windows(session_name)
@@ -276,10 +284,7 @@ fn persist_spawn_agent_state(
276
284
  .map(|agent| agent.agent_id.as_str().to_string())
277
285
  .collect();
278
286
  let pane_pids_by_agent = pane_pids_by_started_agent(transport, started);
279
- let profile_dir = spec_path
280
- .parent()
281
- .unwrap_or(workspace)
282
- .join("profiles");
287
+ let profile_dir = spec_path.parent().unwrap_or(workspace).join("profiles");
283
288
  let mut agents = serde_json::Map::new();
284
289
  let mut spawn_index = 0_u32;
285
290
  for agent in spec_agent_values(spec) {
@@ -317,9 +322,7 @@ fn persist_spawn_agent_state(
317
322
  let pane_pid = pane_pids_by_agent.get(id).copied();
318
323
  let spawned_at = spawn_timestamp_for_agent(spawn_index);
319
324
  spawn_index = spawn_index.saturating_add(1);
320
- let started_agent = started
321
- .iter()
322
- .find(|agent| agent.agent_id.as_str() == id);
325
+ let started_agent = started.iter().find(|agent| agent.agent_id.as_str() == id);
323
326
  agents.insert(
324
327
  id.to_string(),
325
328
  running_agent_state(
@@ -373,7 +376,10 @@ fn agent_id_to_pane_id<'a>(started: &'a [StartedAgent], agent_id: &str) -> &'a s
373
376
  .unwrap_or("")
374
377
  }
375
378
 
376
- fn save_launched_team_state(workspace: &Path, launched: &serde_json::Value) -> Result<(), LifecycleError> {
379
+ fn save_launched_team_state(
380
+ workspace: &Path,
381
+ launched: &serde_json::Value,
382
+ ) -> Result<(), LifecycleError> {
377
383
  save_launched_team_state_for_key(workspace, launched, None)
378
384
  }
379
385
 
@@ -404,7 +410,8 @@ fn save_launched_team_state_for_key(
404
410
  };
405
411
  let mut projected = crate::state::projection::project_top_level_view(&merged, &launched_key);
406
412
  drop_unbound_top_level_owner(&mut projected);
407
- save_runtime_state(workspace, &projected).map_err(|e| LifecycleError::StatePersist(e.to_string()))
413
+ save_runtime_state(workspace, &projected)
414
+ .map_err(|e| LifecycleError::StatePersist(e.to_string()))
408
415
  }
409
416
 
410
417
  fn drop_bare_worker_seeded_owner(launched: &mut serde_json::Value, launched_key: &str) {
@@ -506,7 +513,11 @@ fn drop_unbound_top_level_owner(state: &mut serde_json::Value) {
506
513
  }
507
514
  }
508
515
 
509
- fn drop_foreign_seeded_owner(existing: &serde_json::Value, launched_key: &str, launched: &mut serde_json::Value) {
516
+ fn drop_foreign_seeded_owner(
517
+ existing: &serde_json::Value,
518
+ launched_key: &str,
519
+ launched: &mut serde_json::Value,
520
+ ) {
510
521
  let Some(pane) = launched
511
522
  .get("team_owner")
512
523
  .and_then(|owner| owner.get("pane_id"))
@@ -549,8 +560,7 @@ fn drop_worker_pane_seeded_owner(
549
560
  .ok()
550
561
  .filter(|value| !value.is_empty());
551
562
  let has_leader_identity_env = has_positive_caller_leader_env();
552
- let seeded_from_bare_tmux =
553
- !has_leader_identity_env && tmux_pane.as_deref() == Some(pane);
563
+ let seeded_from_bare_tmux = !has_leader_identity_env && tmux_pane.as_deref() == Some(pane);
554
564
  let caller_tmux_socket = crate::tmux_backend::socket_name_from_tmux_env();
555
565
  if seeded_from_bare_tmux
556
566
  && (tmux_sockets_match_or_unknown(caller_tmux_socket.as_deref(), worker_tmux_socket)
@@ -563,19 +573,14 @@ fn drop_worker_pane_seeded_owner(
563
573
 
564
574
  fn seeded_pane_looks_like_worker(pane: &str, started: &[StartedAgent]) -> bool {
565
575
  pane.ends_with("-first")
566
- || started
567
- .iter()
568
- .any(|agent| {
569
- pane == agent.target
570
- || pane.starts_with(agent.target.as_str())
571
- || agent.target.starts_with(pane)
572
- })
576
+ || started.iter().any(|agent| {
577
+ pane == agent.target
578
+ || pane.starts_with(agent.target.as_str())
579
+ || agent.target.starts_with(pane)
580
+ })
573
581
  }
574
582
 
575
- fn launched_worker_tmux_socket(
576
- transport: &dyn Transport,
577
- workspace: &Path,
578
- ) -> Option<String> {
583
+ fn launched_worker_tmux_socket(transport: &dyn Transport, workspace: &Path) -> Option<String> {
579
584
  if matches!(transport.kind(), crate::transport::BackendKind::Tmux) {
580
585
  Some(crate::tmux_backend::socket_name_for_workspace(workspace))
581
586
  } else {
@@ -583,10 +588,7 @@ fn launched_worker_tmux_socket(
583
588
  }
584
589
  }
585
590
 
586
- fn tmux_sockets_match_or_unknown(
587
- caller_socket: Option<&str>,
588
- worker_socket: Option<&str>,
589
- ) -> bool {
591
+ fn tmux_sockets_match_or_unknown(caller_socket: Option<&str>, worker_socket: Option<&str>) -> bool {
590
592
  match (caller_socket, worker_socket) {
591
593
  (Some(caller), Some(worker)) => caller == worker,
592
594
  (Some(_), None) => false,
@@ -595,7 +597,9 @@ fn tmux_sockets_match_or_unknown(
595
597
  }
596
598
 
597
599
  fn env_nonempty(key: &str) -> bool {
598
- std::env::var(key).ok().is_some_and(|value| !value.is_empty())
600
+ std::env::var(key)
601
+ .ok()
602
+ .is_some_and(|value| !value.is_empty())
599
603
  }
600
604
 
601
605
  fn seed_unbound_launched_owner(launched: &mut serde_json::Value, launched_key: &str) {
@@ -664,7 +668,11 @@ fn unbound_launched_owner(
664
668
  }))
665
669
  }
666
670
 
667
- fn owner_pane_belongs_to_other_team(existing: &serde_json::Value, launched_key: &str, pane: &str) -> bool {
671
+ fn owner_pane_belongs_to_other_team(
672
+ existing: &serde_json::Value,
673
+ launched_key: &str,
674
+ pane: &str,
675
+ ) -> bool {
668
676
  existing
669
677
  .get("teams")
670
678
  .and_then(serde_json::Value::as_object)
@@ -700,7 +708,10 @@ fn running_agent_state(
700
708
  .and_then(Value::as_str)
701
709
  .and_then(parse_auth_mode)
702
710
  .unwrap_or(AuthMode::Subscription);
703
- let profile = agent.get("profile").map(yaml_value_to_json).unwrap_or(serde_json::Value::Null);
711
+ let profile = agent
712
+ .get("profile")
713
+ .map(yaml_value_to_json)
714
+ .unwrap_or(serde_json::Value::Null);
704
715
  let window = agent.get("window").and_then(Value::as_str).unwrap_or(id);
705
716
  let mcp_config = crate::provider::get_adapter(provider)
706
717
  .mcp_config(auth_mode)
@@ -711,7 +722,10 @@ fn running_agent_state(
711
722
  state.insert("status".to_string(), serde_json::json!("running"));
712
723
  state.insert("provider".to_string(), serde_json::json!(provider));
713
724
  state.insert("agent_id".to_string(), serde_json::json!(id));
714
- state.insert("model".to_string(), model.map_or(serde_json::Value::Null, |m| serde_json::json!(m)));
725
+ state.insert(
726
+ "model".to_string(),
727
+ model.map_or(serde_json::Value::Null, |m| serde_json::json!(m)),
728
+ );
715
729
  state.insert("auth_mode".to_string(), serde_json::json!(auth_mode));
716
730
  state.insert("profile".to_string(), profile);
717
731
  if agent.get("profile").is_some() {
@@ -737,7 +751,10 @@ fn running_agent_state(
737
751
  state.insert("rollout_path".to_string(), serde_json::Value::Null);
738
752
  state.insert("captured_at".to_string(), serde_json::Value::Null);
739
753
  state.insert("captured_via".to_string(), serde_json::Value::Null);
740
- state.insert("attribution_confidence".to_string(), serde_json::Value::Null);
754
+ state.insert(
755
+ "attribution_confidence".to_string(),
756
+ serde_json::Value::Null,
757
+ );
741
758
  if let Some(started_agent) = started_agent {
742
759
  persist_started_agent_plan_state(&mut state, started_agent);
743
760
  }
@@ -847,7 +864,11 @@ pub(crate) fn write_worker_mcp_config(
847
864
  Ok(path)
848
865
  }
849
866
 
850
- pub(crate) fn point_native_mcp_config_at_file(argv: &mut [String], provider: Provider, path: &Path) {
867
+ pub(crate) fn point_native_mcp_config_at_file(
868
+ argv: &mut [String],
869
+ provider: Provider,
870
+ path: &Path,
871
+ ) {
851
872
  if !matches!(provider, Provider::Claude | Provider::ClaudeCode) {
852
873
  return;
853
874
  }
@@ -874,13 +895,19 @@ fn permissions_json(
874
895
  let resolved = permissions::resolve_permissions(&AgentPermissionInput {
875
896
  id: Some(AgentId::new(id)),
876
897
  provider,
877
- role: agent.get("role").and_then(Value::as_str).map(str::to_string),
898
+ role: agent
899
+ .get("role")
900
+ .and_then(Value::as_str)
901
+ .map(str::to_string),
878
902
  tools,
879
903
  })?;
880
904
  let mut out = serde_json::Map::new();
881
905
  out.insert("agent_id".to_string(), serde_json::json!(id));
882
906
  out.insert("provider".to_string(), serde_json::json!(provider));
883
- out.insert("tools".to_string(), serde_json::json!(resolved.sorted_tool_strings()));
907
+ out.insert(
908
+ "tools".to_string(),
909
+ serde_json::json!(resolved.sorted_tool_strings()),
910
+ );
884
911
  out.insert(
885
912
  "resolved_tools".to_string(),
886
913
  serde_json::Value::Array(
@@ -896,7 +923,10 @@ fn permissions_json(
896
923
  .collect(),
897
924
  ),
898
925
  );
899
- out.insert("has_prompt_only".to_string(), serde_json::json!(resolved.has_prompt_only));
926
+ out.insert(
927
+ "has_prompt_only".to_string(),
928
+ serde_json::json!(resolved.has_prompt_only),
929
+ );
900
930
  Ok(serde_json::Value::Object(out))
901
931
  }
902
932
 
@@ -920,9 +950,10 @@ fn spawn_timestamp_for_agent(offset_micros: u32) -> String {
920
950
  match std::env::var("TEAM_AGENT_TEST_FIXED_SPAWNED_AT") {
921
951
  Ok(value) => chrono::DateTime::parse_from_rfc3339(&value)
922
952
  .map(|dt| {
923
- (dt.with_timezone(&chrono::Utc) + chrono::Duration::microseconds(i64::from(offset_micros)))
924
- .format("%Y-%m-%dT%H:%M:%S%.6f+00:00")
925
- .to_string()
953
+ (dt.with_timezone(&chrono::Utc)
954
+ + chrono::Duration::microseconds(i64::from(offset_micros)))
955
+ .format("%Y-%m-%dT%H:%M:%S%.6f+00:00")
956
+ .to_string()
926
957
  })
927
958
  .unwrap_or(value),
928
959
  Err(_) => spawn_timestamp(),
@@ -1078,7 +1109,10 @@ pub(crate) fn fill_spawn_placeholders_full(
1078
1109
  *arg = workspace_text.clone();
1079
1110
  } else if arg == "{agent_id}" {
1080
1111
  *arg = agent_id.to_string();
1081
- } else if arg.contains("{workspace}") || arg.contains("{agent_id}") || arg.contains("{team_id}") {
1112
+ } else if arg.contains("{workspace}")
1113
+ || arg.contains("{agent_id}")
1114
+ || arg.contains("{team_id}")
1115
+ {
1082
1116
  *arg = arg
1083
1117
  .replace("{workspace}", &workspace_text)
1084
1118
  .replace("{agent_id}", agent_id)
@@ -1087,30 +1121,12 @@ pub(crate) fn fill_spawn_placeholders_full(
1087
1121
  }
1088
1122
  }
1089
1123
 
1090
- fn agent_tool_strings(agent: &Value) -> Vec<String> {
1091
- agent
1092
- .get("tools")
1093
- .and_then(Value::as_list)
1094
- .map(|items| {
1095
- items
1096
- .iter()
1097
- .filter_map(Value::as_str)
1098
- .map(str::to_string)
1099
- .collect()
1100
- })
1101
- .unwrap_or_default()
1102
- }
1103
-
1104
1124
  fn spec_team_id(spec: &Value) -> Option<String> {
1105
1125
  spec.get("team")
1106
1126
  .and_then(|v| v.get("id").or_else(|| v.get("name")))
1107
1127
  .and_then(Value::as_str)
1108
1128
  .map(str::to_string)
1109
- .or_else(|| {
1110
- spec.get("name")
1111
- .and_then(Value::as_str)
1112
- .map(str::to_string)
1113
- })
1129
+ .or_else(|| spec.get("name").and_then(Value::as_str).map(str::to_string))
1114
1130
  }
1115
1131
 
1116
1132
  fn runtime_active_team_key_for_spawn(
@@ -1173,7 +1189,10 @@ fn parse_auth_mode(raw: &str) -> Option<AuthMode> {
1173
1189
  }
1174
1190
  }
1175
1191
 
1176
- fn quick_start_requested_team_key<'a>(team_id: Option<&'a str>, name: Option<&'a str>) -> Option<&'a str> {
1192
+ fn quick_start_requested_team_key<'a>(
1193
+ team_id: Option<&'a str>,
1194
+ name: Option<&'a str>,
1195
+ ) -> Option<&'a str> {
1177
1196
  team_id.or(name).filter(|team| !team.is_empty())
1178
1197
  }
1179
1198
 
@@ -1239,7 +1258,7 @@ fn quick_start_depth_guard(
1239
1258
  Ok(QuickStartDepth {
1240
1259
  parent_team_key: Some(parent_key),
1241
1260
  team_depth,
1242
- })
1261
+ })
1243
1262
  }
1244
1263
 
1245
1264
  fn infer_parent_team_from_active_state(state: &serde_json::Value) -> Option<String> {
@@ -1255,9 +1274,7 @@ fn has_live_runtime_teams(state: &serde_json::Value) -> bool {
1255
1274
  state
1256
1275
  .get("teams")
1257
1276
  .and_then(serde_json::Value::as_object)
1258
- .is_some_and(|teams| {
1259
- teams.values().any(team_has_running_agent)
1260
- })
1277
+ .is_some_and(|teams| teams.values().any(team_has_running_agent))
1261
1278
  }
1262
1279
 
1263
1280
  fn team_has_running_agent(team: &serde_json::Value) -> bool {
@@ -1265,20 +1282,18 @@ fn team_has_running_agent(team: &serde_json::Value) -> bool {
1265
1282
  .and_then(serde_json::Value::as_object)
1266
1283
  .is_some_and(|agents| {
1267
1284
  agents.values().any(|agent| {
1268
- agent
1269
- .get("status")
1270
- .and_then(serde_json::Value::as_str)
1271
- == Some("running")
1285
+ agent.get("status").and_then(serde_json::Value::as_str) == Some("running")
1272
1286
  })
1273
1287
  })
1274
1288
  }
1275
1289
 
1276
1290
  fn looks_ambiguous_child_team_key(team: &str) -> bool {
1277
1291
  let team = team.trim().to_ascii_lowercase();
1278
- team != "child" && (team.starts_with("child-")
1279
- || team.starts_with("child_")
1280
- || team.starts_with("child.")
1281
- || team.starts_with("child"))
1292
+ team != "child"
1293
+ && (team.starts_with("child-")
1294
+ || team.starts_with("child_")
1295
+ || team.starts_with("child.")
1296
+ || team.starts_with("child"))
1282
1297
  }
1283
1298
 
1284
1299
  fn looks_grandchild_team_key(team: &str) -> bool {
@@ -1290,7 +1305,11 @@ fn looks_grandchild_team_key(team: &str) -> bool {
1290
1305
  || team.starts_with("grandchild")
1291
1306
  }
1292
1307
 
1293
- fn annotate_team_depth(state: &mut serde_json::Value, parent_team_key: Option<&str>, team_depth: u64) {
1308
+ fn annotate_team_depth(
1309
+ state: &mut serde_json::Value,
1310
+ parent_team_key: Option<&str>,
1311
+ team_depth: u64,
1312
+ ) {
1294
1313
  let Some(obj) = state.as_object_mut() else {
1295
1314
  return;
1296
1315
  };
@@ -1337,9 +1356,7 @@ fn runtime_state_has_quick_start_team(state: &serde_json::Value, team: &str) ->
1337
1356
  || state
1338
1357
  .get("session_name")
1339
1358
  .and_then(serde_json::Value::as_str)
1340
- .is_some_and(|session| {
1341
- session == team || session.strip_prefix("team-") == Some(team)
1342
- })
1359
+ .is_some_and(|session| session == team || session.strip_prefix("team-") == Some(team))
1343
1360
  }
1344
1361
 
1345
1362
  fn json_team_identity_matches(state: &serde_json::Value, team: &str) -> bool {
@@ -1412,7 +1429,9 @@ pub fn quick_start_with_transport(
1412
1429
  transport: &dyn Transport,
1413
1430
  ) -> Result<QuickStartReport, LifecycleError> {
1414
1431
  let workspace = team_workspace(agents_dir);
1415
- quick_start_with_transport_in_workspace(&workspace, agents_dir, name, yes, fresh, team_id, transport)
1432
+ quick_start_with_transport_in_workspace(
1433
+ &workspace, agents_dir, name, yes, fresh, team_id, transport,
1434
+ )
1416
1435
  }
1417
1436
 
1418
1437
  pub fn quick_start_with_transport_in_workspace(
@@ -1468,11 +1487,12 @@ pub fn quick_start_with_transport_in_workspace(
1468
1487
  .map(SessionName::new),
1469
1488
  state_path: Some(state_path),
1470
1489
  next_actions: vec![
1471
- "run restart to resume the existing team or pass --fresh to replace it".to_string(),
1490
+ "run restart to resume the existing team or pass --fresh to replace it"
1491
+ .to_string(),
1472
1492
  ],
1473
- });
1474
- }
1475
- }
1493
+ });
1494
+ }
1495
+ }
1476
1496
  }
1477
1497
  // CR-040/042: repeated quick-start from one template with distinct --team-id/--name
1478
1498
  // must NOT collide on the template-derived tmux session. Override the compiled
@@ -1488,12 +1508,12 @@ pub fn quick_start_with_transport_in_workspace(
1488
1508
  runtime_team_key_for_spec(&spec_path, &spec, &session_name)
1489
1509
  });
1490
1510
  let spec_path = agents_dir.join("team.spec.yaml");
1491
- std::fs::write(&spec_path, yaml::dumps(&spec)).map_err(|e| {
1492
- LifecycleError::StatePersist(format!("{}: {e}", spec_path.display()))
1493
- })?;
1511
+ std::fs::write(&spec_path, yaml::dumps(&spec))
1512
+ .map_err(|e| LifecycleError::StatePersist(format!("{}: {e}", spec_path.display())))?;
1494
1513
  let _store = crate::message_store::MessageStore::open(&workspace)
1495
1514
  .map_err(|e| LifecycleError::StatePersist(e.to_string()))?;
1496
- let resolved_spec_path = std::fs::canonicalize(&spec_path).unwrap_or_else(|_| spec_path.clone());
1515
+ let resolved_spec_path =
1516
+ std::fs::canonicalize(&spec_path).unwrap_or_else(|_| spec_path.clone());
1497
1517
  let state = initial_runtime_state(&spec, &resolved_spec_path, &workspace, agents_dir);
1498
1518
  save_launched_team_state_for_key(&workspace, &state, Some(&state_team_key))?;
1499
1519
  annotate_persisted_team_depth(
@@ -1505,14 +1525,16 @@ pub fn quick_start_with_transport_in_workspace(
1505
1525
  // FIX (rt-host-a real-machine finding): dry_run=false so launch_with_transport calls spawn_agents
1506
1526
  // and really creates the tmux session + worker windows (was hardcoded true → never spawned, which
1507
1527
  // also starved the coordinator: no session → first tick TmuxSessionMissing → run_daemon loop exits).
1508
- let mut launch = launch_with_transport_in_workspace(&workspace, &spec_path, false, yes, true, transport)?;
1528
+ let mut launch =
1529
+ launch_with_transport_in_workspace(&workspace, &spec_path, false, yes, true, transport)?;
1509
1530
  annotate_persisted_team_depth(
1510
1531
  &workspace,
1511
1532
  &state_team_key,
1512
1533
  team_depth.parent_team_key.as_deref(),
1513
1534
  team_depth.team_depth,
1514
1535
  )?;
1515
- launch.leader_receiver_attached = launched_team_receiver_is_attached(&workspace, &state_team_key);
1536
+ launch.leader_receiver_attached =
1537
+ launched_team_receiver_is_attached(&workspace, &state_team_key);
1516
1538
  launch.session_capture_incomplete_agents =
1517
1539
  quick_start_session_capture_incomplete_agents(&workspace, &state_team_key);
1518
1540
  let coordinator_workspace = crate::coordinator::WorkspacePath::new(workspace.clone());
@@ -1532,12 +1554,29 @@ pub fn quick_start_with_transport_in_workspace(
1532
1554
  // asynchronously after spawn), so the verdict is PendingToolLoad — never
1533
1555
  // bare Ready.
1534
1556
  let worker_readiness = quick_start_worker_readiness(&workspace, &state_team_key);
1557
+ let attach_commands = crate::tmux_backend::attach_commands_for_windows(
1558
+ &workspace,
1559
+ &session_name,
1560
+ launch
1561
+ .started
1562
+ .iter()
1563
+ .map(|started| started.agent_id.as_str()),
1564
+ );
1565
+ let mut next_actions = vec![format!(
1566
+ "team compiled; real spawn is behind the transport/provider boundary; {coordinator_action}"
1567
+ )];
1568
+ next_actions.extend(attach_commands.iter().cloned());
1569
+ let display_backend = state
1570
+ .get("display_backend")
1571
+ .and_then(serde_json::Value::as_str)
1572
+ .unwrap_or("none")
1573
+ .to_string();
1535
1574
  Ok(QuickStartReport::Ready {
1536
1575
  session_name,
1537
1576
  launch: Box::new(launch),
1538
- next_actions: vec![format!(
1539
- "team compiled; real spawn is behind the transport/provider boundary; {coordinator_action}"
1540
- )],
1577
+ next_actions,
1578
+ attach_commands,
1579
+ display_backend,
1541
1580
  worker_readiness,
1542
1581
  })
1543
1582
  }
@@ -1556,7 +1595,10 @@ fn quick_start_worker_readiness(workspace: &Path, team_key: &str) -> QuickStartR
1556
1595
  .and_then(serde_json::Value::as_object)
1557
1596
  .and_then(|teams| teams.get(team_key))
1558
1597
  .unwrap_or(&state);
1559
- let Some(agents) = team_state.get("agents").and_then(serde_json::Value::as_object) else {
1598
+ let Some(agents) = team_state
1599
+ .get("agents")
1600
+ .and_then(serde_json::Value::as_object)
1601
+ else {
1560
1602
  return QuickStartReadiness::PendingToolLoad;
1561
1603
  };
1562
1604
  let all_spawned = !agents.is_empty();
@@ -1575,9 +1617,12 @@ fn quick_start_worker_readiness(workspace: &Path, team_key: &str) -> QuickStartR
1575
1617
  if !unhealthy.is_empty() {
1576
1618
  unhealthy.sort();
1577
1619
  unhealthy.dedup();
1578
- QuickStartReadiness::Degraded { unhealthy_agents: unhealthy }
1620
+ QuickStartReadiness::Degraded {
1621
+ unhealthy_agents: unhealthy,
1622
+ }
1579
1623
  } else {
1580
- let incomplete_agents = crate::session_capture::incomplete_interacted_resumable_agent_ids(team_state);
1624
+ let incomplete_agents =
1625
+ crate::session_capture::incomplete_interacted_resumable_agent_ids(team_state);
1581
1626
  let all_resumable_have_session = incomplete_agents.is_empty();
1582
1627
  let _readiness_ready = all_spawned && all_attached_receiver && all_resumable_have_session;
1583
1628
  QuickStartReadiness::PendingToolLoad
@@ -1621,10 +1666,7 @@ fn team_uses_fake_model_harness(team_state: &serde_json::Value) -> bool {
1621
1666
  .is_some_and(|agents| {
1622
1667
  !agents.is_empty()
1623
1668
  && agents.values().all(|agent| {
1624
- agent
1625
- .get("model")
1626
- .and_then(serde_json::Value::as_str)
1627
- == Some("fake")
1669
+ agent.get("model").and_then(serde_json::Value::as_str) == Some("fake")
1628
1670
  })
1629
1671
  })
1630
1672
  }
@@ -1649,9 +1691,11 @@ fn leader_receiver_is_attached(team_state: &serde_json::Value) -> bool {
1649
1691
  /// `--dangerously-*` flag,产出危险审批继承态。launch 在 inherited=false 且无 --yes 时拒。
1650
1692
  pub fn detect_dangerous_approval() -> Result<DangerousApproval, LifecycleError> {
1651
1693
  if let Ok(raw) = std::env::var("TEAM_AGENT_TEST_PROCESS_ANCESTRY_ARGV_JSON") {
1652
- let argv_tokens = serde_json::from_str::<Vec<String>>(&raw)
1653
- .map_err(|e| LifecycleError::StatePersist(format!("invalid test ancestry argv: {e}")))?;
1654
- return Ok(detect_dangerous_approval_in_argv(&argv_tokens).unwrap_or_else(disabled_dangerous_approval));
1694
+ let argv_tokens = serde_json::from_str::<Vec<String>>(&raw).map_err(|e| {
1695
+ LifecycleError::StatePersist(format!("invalid test ancestry argv: {e}"))
1696
+ })?;
1697
+ return Ok(detect_dangerous_approval_in_argv(&argv_tokens)
1698
+ .unwrap_or_else(disabled_dangerous_approval));
1655
1699
  }
1656
1700
  for argv_tokens in process_ancestry_argv(std::process::id()) {
1657
1701
  if let Some(detected) = detect_dangerous_approval_in_argv(&argv_tokens) {
@@ -1667,7 +1711,8 @@ fn detect_dangerous_approval_in_argv(argv_tokens: &[String]) -> Option<Dangerous
1667
1711
  for token in argv_tokens {
1668
1712
  for (provider, flag) in dangerous_leader_flags() {
1669
1713
  if token == flag {
1670
- let unexpected_binary = !binary_matches_provider(provider, ancestry_binary_name.as_deref());
1714
+ let unexpected_binary =
1715
+ !binary_matches_provider(provider, ancestry_binary_name.as_deref());
1671
1716
  return Some(DangerousApproval {
1672
1717
  enabled: true,
1673
1718
  source: DangerousApprovalSource::LeaderProcess,
@@ -1857,9 +1902,9 @@ pub fn add_agent(
1857
1902
  }
1858
1903
  Err(error) => return Err(LifecycleError::TeamSelect(error.to_string())),
1859
1904
  };
1860
- let team_dir = selected
1861
- .spec_workspace
1862
- .ok_or_else(|| LifecycleError::TeamSelect("active team spec workspace not found".to_string()))?;
1905
+ let team_dir = selected.spec_workspace.ok_or_else(|| {
1906
+ LifecycleError::TeamSelect("active team spec workspace not found".to_string())
1907
+ })?;
1863
1908
  add_agent_with_transport_at_paths(
1864
1909
  &selected.run_workspace,
1865
1910
  &team_dir,
@@ -1910,8 +1955,9 @@ fn add_agent_with_transport_at_paths(
1910
1955
  .map(str::to_string)
1911
1956
  .or_else(|| explicit_active_team_key(&runtime_state))
1912
1957
  .unwrap_or_else(|| crate::state::projection::team_state_key(&runtime_state));
1913
- let owner_state = crate::state::projection::select_runtime_state(run_workspace, Some(&canonical_team_key))
1914
- .map_err(|e| LifecycleError::TeamSelect(e.to_string()))?;
1958
+ let owner_state =
1959
+ crate::state::projection::select_runtime_state(run_workspace, Some(&canonical_team_key))
1960
+ .map_err(|e| LifecycleError::TeamSelect(e.to_string()))?;
1915
1961
  ensure_owner_allowed_for_state(&owner_state, Some(agent_id))?;
1916
1962
  if !role_file_path.exists() {
1917
1963
  return Err(LifecycleError::Compile(format!(
@@ -1929,9 +1975,8 @@ fn add_agent_with_transport_at_paths(
1929
1975
  .map_err(|e| LifecycleError::Compile(e.to_string()))?;
1930
1976
  let safety = effective_runtime_config(&spec)?;
1931
1977
  let spec_path = team_dir.join("team.spec.yaml");
1932
- std::fs::write(&spec_path, yaml::dumps(&spec)).map_err(|e| {
1933
- LifecycleError::StatePersist(format!("{}: {e}", spec_path.display()))
1934
- })?;
1978
+ std::fs::write(&spec_path, yaml::dumps(&spec))
1979
+ .map_err(|e| LifecycleError::StatePersist(format!("{}: {e}", spec_path.display())))?;
1935
1980
  let (meta, _) = crate::compiler::read_front_matter(&dynamic_role_file)
1936
1981
  .map_err(|e| LifecycleError::Compile(e.to_string()))?;
1937
1982
  upsert_agent_state_from_role(
@@ -1978,8 +2023,9 @@ fn upsert_agent_state_from_role(
1978
2023
  dynamic_role_file: &Path,
1979
2024
  safety: &DangerousApproval,
1980
2025
  ) -> Result<(), LifecycleError> {
1981
- let mut state = crate::state::projection::select_runtime_state(workspace, Some(canonical_team_key))
1982
- .map_err(|e| LifecycleError::TeamSelect(e.to_string()))?;
2026
+ let mut state =
2027
+ crate::state::projection::select_runtime_state(workspace, Some(canonical_team_key))
2028
+ .map_err(|e| LifecycleError::TeamSelect(e.to_string()))?;
1983
2029
  if !state.is_object() {
1984
2030
  state = serde_json::json!({});
1985
2031
  }
@@ -2027,10 +2073,7 @@ fn upsert_agent_state_from_role(
2027
2073
  if let Some(profile) = meta.get("profile").and_then(Value::as_str) {
2028
2074
  if let Some(obj) = entry.as_object_mut() {
2029
2075
  obj.insert("profile".to_string(), serde_json::json!(profile));
2030
- if let Some(team_dir) = dynamic_role_file
2031
- .parent()
2032
- .and_then(Path::parent)
2033
- {
2076
+ if let Some(team_dir) = dynamic_role_file.parent().and_then(Path::parent) {
2034
2077
  obj.insert(
2035
2078
  "_profile_dir".to_string(),
2036
2079
  serde_json::json!(team_dir.join("profiles").to_string_lossy().to_string()),
@@ -2111,9 +2154,9 @@ pub fn fork_agent_with_transport(
2111
2154
  crate::state::selector::SelectorMode::RequireSpec,
2112
2155
  )
2113
2156
  .map_err(|e| LifecycleError::TeamSelect(e.to_string()))?;
2114
- let spec_workspace = selected
2115
- .spec_workspace
2116
- .ok_or_else(|| LifecycleError::TeamSelect("active team spec workspace not found".to_string()))?;
2157
+ let spec_workspace = selected.spec_workspace.ok_or_else(|| {
2158
+ LifecycleError::TeamSelect("active team spec workspace not found".to_string())
2159
+ })?;
2117
2160
  let workspace = selected.run_workspace;
2118
2161
  let state = selected.state;
2119
2162
  ensure_owner_allowed_for_state(&state, Some(source_agent_id))?;
@@ -2126,8 +2169,9 @@ pub fn fork_agent_with_transport(
2126
2169
  "agent id already exists: {as_agent_id}"
2127
2170
  )));
2128
2171
  }
2129
- let source_agent = find_spec_agent(&spec, source_agent_id)
2130
- .ok_or_else(|| LifecycleError::RequirementUnmet(format!("unknown worker agent id: {source_agent_id}")))?;
2172
+ let source_agent = find_spec_agent(&spec, source_agent_id).ok_or_else(|| {
2173
+ LifecycleError::RequirementUnmet(format!("unknown worker agent id: {source_agent_id}"))
2174
+ })?;
2131
2175
  let session_id = state
2132
2176
  .get("agents")
2133
2177
  .and_then(|v| v.get(source_agent_id.as_str()))
@@ -2162,8 +2206,9 @@ pub fn fork_agent_with_transport(
2162
2206
  .map_err(|e| LifecycleError::Compile(e.to_string()))?;
2163
2207
  std::fs::write(&spec_path, yaml::dumps(&new_spec))
2164
2208
  .map_err(|e| LifecycleError::StatePersist(format!("{}: {e}", spec_path.display())))?;
2165
- let new_agent = find_spec_agent(&new_spec, as_agent_id)
2166
- .ok_or_else(|| LifecycleError::RequirementUnmet(format!("unknown worker agent id: {as_agent_id}")))?;
2209
+ let new_agent = find_spec_agent(&new_spec, as_agent_id).ok_or_else(|| {
2210
+ LifecycleError::RequirementUnmet(format!("unknown worker agent id: {as_agent_id}"))
2211
+ })?;
2167
2212
  let provider = new_agent
2168
2213
  .get("provider")
2169
2214
  .and_then(Value::as_str)
@@ -2185,18 +2230,26 @@ pub fn fork_agent_with_transport(
2185
2230
  "{provider_str} does not support native session fork"
2186
2231
  )));
2187
2232
  }
2188
- let role = new_agent.get("role").and_then(Value::as_str);
2189
2233
  let model = new_agent.get("model").and_then(Value::as_str);
2190
2234
  let safety = effective_runtime_config(&new_spec)?;
2191
- let tools = worker_tool_refs(agent_tool_strings(new_agent), &safety);
2192
- let tool_refs: Vec<&str> = tools.iter().map(String::as_str).collect();
2235
+ let command_agent = crate::lifecycle::worker_command_context::WorkerCommandAgent::from_yaml(
2236
+ new_agent,
2237
+ Some(as_agent_id.as_str()),
2238
+ provider,
2239
+ );
2240
+ let system_prompt =
2241
+ crate::lifecycle::worker_command_context::compile_worker_system_prompt(&command_agent)?;
2242
+ let tools = crate::lifecycle::worker_command_context::resolved_tool_strings_for_command(
2243
+ &command_agent,
2244
+ provider,
2245
+ &safety,
2246
+ )?;
2247
+ let resolved_tool_refs: Vec<&str> = tools.iter().map(String::as_str).collect();
2193
2248
  let fork_team = crate::messaging::leader_receiver::active_team_key(&workspace, &state);
2194
- let mcp_config = adapter
2195
- .mcp_config(auth_mode)
2196
- .map_err(|e| {
2197
- let _ = std::fs::write(&spec_path, text.as_bytes());
2198
- LifecycleError::Provider(e.to_string())
2199
- })?;
2249
+ let mcp_config = adapter.mcp_config(auth_mode).map_err(|e| {
2250
+ let _ = std::fs::write(&spec_path, text.as_bytes());
2251
+ LifecycleError::Provider(e.to_string())
2252
+ })?;
2200
2253
  let mcp_config = resolve_mcp_config(mcp_config, &workspace, as_agent_id.as_str(), &fork_team);
2201
2254
  let mcp_config_path = write_worker_mcp_config(&workspace, as_agent_id.as_str(), &mcp_config)
2202
2255
  .map_err(|e| {
@@ -2204,27 +2257,24 @@ pub fn fork_agent_with_transport(
2204
2257
  e
2205
2258
  })?;
2206
2259
  let profile_dir = spec_workspace.join("profiles");
2207
- let profile_launch = crate::lifecycle::profile_launch::prepare_provider_profile_launch_with_profile_dir(
2208
- &workspace,
2209
- as_agent_id.as_str(),
2210
- new_agent,
2211
- Some(&profile_dir),
2212
- Some(&mcp_config),
2213
- )?;
2214
- let command_model = profile_launch
2215
- .command_overrides
2216
- .model
2217
- .as_deref()
2218
- .or(model);
2260
+ let profile_launch =
2261
+ crate::lifecycle::profile_launch::prepare_provider_profile_launch_with_profile_dir(
2262
+ &workspace,
2263
+ as_agent_id.as_str(),
2264
+ new_agent,
2265
+ Some(&profile_dir),
2266
+ Some(&mcp_config),
2267
+ )?;
2268
+ let command_model = profile_launch.command_overrides.model.as_deref().or(model);
2219
2269
  let mut plan = adapter
2220
2270
  .fork_plan(
2221
2271
  Some(&session_id),
2222
2272
  crate::provider::ProviderCommandContext {
2223
2273
  auth_mode,
2224
2274
  mcp_config: Some(&mcp_config),
2225
- system_prompt: role,
2275
+ system_prompt: Some(system_prompt.as_str()),
2226
2276
  model: command_model,
2227
- tools: &tool_refs,
2277
+ tools: &resolved_tool_refs,
2228
2278
  profile_launch: Some(&profile_launch),
2229
2279
  },
2230
2280
  )
@@ -2235,14 +2285,16 @@ pub fn fork_agent_with_transport(
2235
2285
  if !plan.managed_mcp_config && !profile_launch.managed_mcp_config {
2236
2286
  point_native_mcp_config_at_file(&mut plan.argv, provider, &mcp_config_path);
2237
2287
  }
2238
- fill_spawn_placeholders_full(&mut plan.argv, &workspace, as_agent_id.as_str(), Some(&fork_team));
2239
- let window = WindowName::new(as_agent_id.as_str());
2240
- // fork inherits the parent agent's owner team via runtime state (`active_team_key`).
2241
- let mut env = inherited_env_with_team_overrides(
2288
+ fill_spawn_placeholders_full(
2289
+ &mut plan.argv,
2242
2290
  &workspace,
2243
2291
  as_agent_id.as_str(),
2244
2292
  Some(&fork_team),
2245
2293
  );
2294
+ let window = WindowName::new(as_agent_id.as_str());
2295
+ // fork inherits the parent agent's owner team via runtime state (`active_team_key`).
2296
+ let mut env =
2297
+ inherited_env_with_team_overrides(&workspace, as_agent_id.as_str(), Some(&fork_team));
2246
2298
  apply_profile_launch_env(&mut env, &profile_launch);
2247
2299
  // golden operations.py:336 -> _tmux_start_command_for_agent_window (runtime.py:1017-1020): branch on
2248
2300
  // _tmux_session_exists — an ABSENT session => new-session (spawn_first), present => new-window
@@ -2324,26 +2376,25 @@ pub fn fork_agent_with_transport(
2324
2376
  );
2325
2377
  return Err(e);
2326
2378
  }
2327
- let coordinator_started =
2328
- crate::coordinator::start_coordinator(&crate::coordinator::WorkspacePath::new(
2329
- workspace.to_path_buf(),
2330
- ))
2331
- .map(|report| report.ok)
2332
- .map_err(|e| {
2333
- rollback_fork_after_spawn(
2334
- &workspace,
2335
- &spec_path,
2336
- &text,
2337
- &old_state,
2338
- transport,
2339
- &session_name,
2340
- &window,
2341
- &mcp_config_path,
2342
- as_agent_id,
2343
- &profile_launch,
2344
- );
2345
- LifecycleError::StatePersist(e.to_string())
2346
- })?;
2379
+ let coordinator_started = crate::coordinator::start_coordinator(
2380
+ &crate::coordinator::WorkspacePath::new(workspace.to_path_buf()),
2381
+ )
2382
+ .map(|report| report.ok)
2383
+ .map_err(|e| {
2384
+ rollback_fork_after_spawn(
2385
+ &workspace,
2386
+ &spec_path,
2387
+ &text,
2388
+ &old_state,
2389
+ transport,
2390
+ &session_name,
2391
+ &window,
2392
+ &mcp_config_path,
2393
+ as_agent_id,
2394
+ &profile_launch,
2395
+ );
2396
+ LifecycleError::StatePersist(e.to_string())
2397
+ })?;
2347
2398
  Ok(ForkAgentReport {
2348
2399
  source_agent_id: source_agent_id.clone(),
2349
2400
  new_agent_id: as_agent_id.clone(),
@@ -2384,8 +2435,7 @@ fn maybe_fail_fork_after_spawn(step: &str) -> Result<(), LifecycleError> {
2384
2435
  if reason.is_empty() {
2385
2436
  return Ok(());
2386
2437
  }
2387
- let should_fail = reason == step
2388
- || (step == "start_coordinator" && reason == "coordinator");
2438
+ let should_fail = reason == step || (step == "start_coordinator" && reason == "coordinator");
2389
2439
  if !should_fail {
2390
2440
  return Ok(());
2391
2441
  }
@@ -2429,16 +2479,13 @@ fn find_spec_agent<'a>(spec: &'a Value, agent_id: &AgentId) -> Option<&'a Value>
2429
2479
  if leader_is_agent {
2430
2480
  return None;
2431
2481
  }
2432
- spec.get("agents")?
2433
- .as_list()?
2434
- .iter()
2435
- .find(|agent| {
2436
- agent
2437
- .get("id")
2438
- .and_then(Value::as_str)
2439
- .map(|id| id == agent_id.as_str())
2440
- .unwrap_or(false)
2441
- })
2482
+ spec.get("agents")?.as_list()?.iter().find(|agent| {
2483
+ agent
2484
+ .get("id")
2485
+ .and_then(Value::as_str)
2486
+ .map(|id| id == agent_id.as_str())
2487
+ .unwrap_or(false)
2488
+ })
2442
2489
  }
2443
2490
 
2444
2491
  fn append_forked_agent(
@@ -2477,12 +2524,17 @@ fn append_forked_agent(
2477
2524
  )?;
2478
2525
 
2479
2526
  let Value::Map(pairs) = spec else {
2480
- return Err(LifecycleError::Compile("spec root is not a map".to_string()));
2527
+ return Err(LifecycleError::Compile(
2528
+ "spec root is not a map".to_string(),
2529
+ ));
2481
2530
  };
2482
2531
  let mut out = Vec::new();
2483
2532
  for (key, value) in pairs {
2484
2533
  if key == "agents" {
2485
- let mut agents = value.as_list().map(|items| items.to_vec()).unwrap_or_default();
2534
+ let mut agents = value
2535
+ .as_list()
2536
+ .map(|items| items.to_vec())
2537
+ .unwrap_or_default();
2486
2538
  agents.push(new_agent.clone());
2487
2539
  out.push((key.clone(), Value::List(agents)));
2488
2540
  } else if key == "runtime" {
@@ -2496,7 +2548,9 @@ fn append_forked_agent(
2496
2548
 
2497
2549
  fn set_yaml_map_value(value: &mut Value, key: &str, next: Value) -> Result<(), LifecycleError> {
2498
2550
  let Value::Map(pairs) = value else {
2499
- return Err(LifecycleError::Compile("agent entry is not a map".to_string()));
2551
+ return Err(LifecycleError::Compile(
2552
+ "agent entry is not a map".to_string(),
2553
+ ));
2500
2554
  };
2501
2555
  if let Some((_, existing)) = pairs.iter_mut().find(|(k, _)| k == key) {
2502
2556
  *existing = next;
@@ -2515,10 +2569,15 @@ fn runtime_with_startup_agent(runtime: &Value, agent_id: &AgentId) -> Value {
2515
2569
  for (key, value) in pairs {
2516
2570
  if key == "startup_order" {
2517
2571
  saw_startup = true;
2518
- let mut order = value.as_list().map(|items| items.to_vec()).unwrap_or_default();
2519
- let already_present = order
2520
- .iter()
2521
- .any(|item| item.as_str().map(|id| id == agent_id.as_str()).unwrap_or(false));
2572
+ let mut order = value
2573
+ .as_list()
2574
+ .map(|items| items.to_vec())
2575
+ .unwrap_or_default();
2576
+ let already_present = order.iter().any(|item| {
2577
+ item.as_str()
2578
+ .map(|id| id == agent_id.as_str())
2579
+ .unwrap_or(false)
2580
+ });
2522
2581
  if !already_present {
2523
2582
  order.push(Value::Str(agent_id.as_str().to_string()));
2524
2583
  }
@@ -2574,18 +2633,37 @@ fn upsert_forked_agent_state(
2574
2633
  let mut entry = serde_json::Map::new();
2575
2634
  entry.insert("status".to_string(), serde_json::json!("running"));
2576
2635
  entry.insert("provider".to_string(), serde_json::json!(provider));
2577
- entry.insert("agent_id".to_string(), serde_json::json!(as_agent_id.as_str()));
2578
- entry.insert("window".to_string(), serde_json::json!(as_agent_id.as_str()));
2579
- entry.insert("forked_from".to_string(), serde_json::json!(source_agent_id.as_str()));
2636
+ entry.insert(
2637
+ "agent_id".to_string(),
2638
+ serde_json::json!(as_agent_id.as_str()),
2639
+ );
2640
+ entry.insert(
2641
+ "window".to_string(),
2642
+ serde_json::json!(as_agent_id.as_str()),
2643
+ );
2644
+ entry.insert(
2645
+ "forked_from".to_string(),
2646
+ serde_json::json!(source_agent_id.as_str()),
2647
+ );
2580
2648
  entry.insert(
2581
2649
  "spawn_cwd".to_string(),
2582
2650
  serde_json::json!(spawn_cwd.to_string_lossy().to_string()),
2583
2651
  );
2584
- entry.insert("pane_id".to_string(), serde_json::json!(spawn.pane_id.as_str()));
2652
+ entry.insert(
2653
+ "pane_id".to_string(),
2654
+ serde_json::json!(spawn.pane_id.as_str()),
2655
+ );
2585
2656
  if let Some(pid) = spawn.child_pid {
2586
2657
  entry.insert("pane_pid".to_string(), serde_json::json!(pid));
2587
2658
  }
2588
- for key in ["auth_mode", "model", "model_source", "profile", "_profile_dir", "role"] {
2659
+ for key in [
2660
+ "auth_mode",
2661
+ "model",
2662
+ "model_source",
2663
+ "profile",
2664
+ "_profile_dir",
2665
+ "role",
2666
+ ] {
2589
2667
  if let Some(value) = spec_agent.get(key) {
2590
2668
  entry.insert(key.to_string(), yaml_value_to_json(value));
2591
2669
  }
@@ -2602,9 +2680,15 @@ fn upsert_forked_agent_state(
2602
2680
  entry.insert("rollout_path".to_string(), serde_json::Value::Null);
2603
2681
  entry.insert("captured_at".to_string(), serde_json::Value::Null);
2604
2682
  entry.insert("captured_via".to_string(), serde_json::Value::Null);
2605
- entry.insert("attribution_confidence".to_string(), serde_json::Value::Null);
2683
+ entry.insert(
2684
+ "attribution_confidence".to_string(),
2685
+ serde_json::Value::Null,
2686
+ );
2606
2687
  persist_command_plan_state(&mut entry, plan, profile_launch);
2607
- agent_map.insert(as_agent_id.as_str().to_string(), serde_json::Value::Object(entry));
2688
+ agent_map.insert(
2689
+ as_agent_id.as_str().to_string(),
2690
+ serde_json::Value::Object(entry),
2691
+ );
2608
2692
  if let Some(entry) = agent_map
2609
2693
  .get_mut(as_agent_id.as_str())
2610
2694
  .and_then(serde_json::Value::as_object_mut)
@@ -2642,12 +2726,9 @@ pub(crate) fn ensure_owner_allowed_for_state(
2642
2726
  None,
2643
2727
  )
2644
2728
  .map_err(|e| LifecycleError::StatePersist(e.to_string()))?;
2645
- if let Some(refusal) = crate::state::owner_gate::check_team_owner(
2646
- state,
2647
- &caller,
2648
- false,
2649
- &NoopLiveness,
2650
- ) {
2729
+ if let Some(refusal) =
2730
+ crate::state::owner_gate::check_team_owner(state, &caller, false, &NoopLiveness)
2731
+ {
2651
2732
  return Err(LifecycleError::OwnerRefused(refusal.to_string()));
2652
2733
  }
2653
2734
  Ok(())
@@ -2676,7 +2757,10 @@ fn initial_runtime_state(
2676
2757
  let Some(id) = agent.get("id").and_then(Value::as_str) else {
2677
2758
  continue;
2678
2759
  };
2679
- let provider = agent.get("provider").and_then(Value::as_str).unwrap_or("codex");
2760
+ let provider = agent
2761
+ .get("provider")
2762
+ .and_then(Value::as_str)
2763
+ .unwrap_or("codex");
2680
2764
  let role = agent.get("role").and_then(Value::as_str).unwrap_or(id);
2681
2765
  let model = agent.get("model").and_then(Value::as_str);
2682
2766
  let auth_mode = agent.get("auth_mode").and_then(Value::as_str);
@@ -2698,7 +2782,9 @@ fn initial_runtime_state(
2698
2782
  .get("runtime")
2699
2783
  .and_then(|runtime| runtime.get("display_backend"))
2700
2784
  .and_then(Value::as_str)
2701
- .and_then(|backend| serde_json::from_value::<DisplayBackend>(serde_json::json!(backend)).ok());
2785
+ .and_then(|backend| {
2786
+ serde_json::from_value::<DisplayBackend>(serde_json::json!(backend)).ok()
2787
+ });
2702
2788
  let display_backend =
2703
2789
  crate::lifecycle::display::resolve_display_backend(requested_display, None).backend;
2704
2790
  let mut state = serde_json::Map::new();
@@ -2720,11 +2806,16 @@ fn initial_runtime_state(
2720
2806
  );
2721
2807
  state.insert(
2722
2808
  "leader".to_string(),
2723
- spec.get("leader").map(yaml_value_to_json).unwrap_or(serde_json::Value::Null),
2809
+ spec.get("leader")
2810
+ .map(yaml_value_to_json)
2811
+ .unwrap_or(serde_json::Value::Null),
2724
2812
  );
2725
2813
  state.insert("agents".to_string(), serde_json::Value::Object(agents));
2726
2814
  state.insert("tasks".to_string(), spec_tasks_json(spec));
2727
- state.insert("display_backend".to_string(), serde_json::json!(display_backend));
2815
+ state.insert(
2816
+ "display_backend".to_string(),
2817
+ serde_json::json!(display_backend),
2818
+ );
2728
2819
  let mut state = serde_json::Value::Object(state);
2729
2820
  if !seed_launched_owner_from_env(&mut state) {
2730
2821
  let team_id = crate::state::projection::team_state_key(&state);
@@ -2800,9 +2891,7 @@ fn has_positive_caller_leader_env() -> bool {
2800
2891
  fn spec_tasks_json(spec: &Value) -> serde_json::Value {
2801
2892
  spec.get("tasks")
2802
2893
  .and_then(Value::as_list)
2803
- .map(|tasks| {
2804
- serde_json::Value::Array(tasks.iter().map(yaml_value_to_json).collect())
2805
- })
2894
+ .map(|tasks| serde_json::Value::Array(tasks.iter().map(yaml_value_to_json).collect()))
2806
2895
  .unwrap_or_else(|| serde_json::json!([]))
2807
2896
  }
2808
2897
 
@@ -2841,7 +2930,10 @@ fn override_spec_session_name(spec: &mut Value, session_name: &str) {
2841
2930
  if let Some((_, existing)) = runtime.iter_mut().find(|(k, _)| k == "session_name") {
2842
2931
  *existing = Value::Str(session_name.to_string());
2843
2932
  } else {
2844
- runtime.push(("session_name".to_string(), Value::Str(session_name.to_string())));
2933
+ runtime.push((
2934
+ "session_name".to_string(),
2935
+ Value::Str(session_name.to_string()),
2936
+ ));
2845
2937
  }
2846
2938
  }
2847
2939
  Some(other) => {
@@ -2948,20 +3040,11 @@ fn disabled_dangerous_approval() -> DangerousApproval {
2948
3040
  }
2949
3041
  }
2950
3042
 
2951
- pub(crate) fn effective_runtime_config_for_worker_spawn() -> Result<DangerousApproval, LifecycleError> {
3043
+ pub(crate) fn effective_runtime_config_for_worker_spawn(
3044
+ ) -> Result<DangerousApproval, LifecycleError> {
2952
3045
  detect_dangerous_approval()
2953
3046
  }
2954
3047
 
2955
- pub(crate) fn worker_tool_refs(
2956
- mut tools: Vec<String>,
2957
- safety: &DangerousApproval,
2958
- ) -> Vec<String> {
2959
- if safety.enabled && !tools.iter().any(|tool| tool == "dangerous_auto_approve") {
2960
- tools.push("dangerous_auto_approve".to_string());
2961
- }
2962
- tools
2963
- }
2964
-
2965
3048
  fn write_launch_permission_audit(
2966
3049
  workspace: &Path,
2967
3050
  safety: &DangerousApproval,
@@ -3019,6 +3102,5 @@ fn agent_id_exists_in_team_dir(team_dir: &Path, agent_id: &AgentId) -> bool {
3019
3102
  .exists()
3020
3103
  }
3021
3104
 
3022
-
3023
3105
  mod plan;
3024
3106
  pub use plan::{handle_report_result, start_plan};