@team-agent/installer 0.3.14 → 0.3.16

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.
Files changed (46) hide show
  1. package/Cargo.lock +1 -1
  2. package/Cargo.toml +1 -1
  3. package/crates/team-agent/src/cli/adapters.rs +1 -0
  4. package/crates/team-agent/src/cli/emit.rs +92 -11
  5. package/crates/team-agent/src/cli/leader.rs +2 -0
  6. package/crates/team-agent/src/cli/mod.rs +255 -37
  7. package/crates/team-agent/src/cli/send.rs +252 -0
  8. package/crates/team-agent/src/cli/status.rs +5 -1
  9. package/crates/team-agent/src/cli/status_port.rs +24 -0
  10. package/crates/team-agent/src/cli/tests/base.rs +21 -3
  11. package/crates/team-agent/src/cli/tests/divergence.rs +6 -6
  12. package/crates/team-agent/src/cli/tests/run_delegation.rs +15 -3
  13. package/crates/team-agent/src/cli/tests/shutdown_kill_plan.rs +285 -2
  14. package/crates/team-agent/src/cli/tests/status_send.rs +25 -0
  15. package/crates/team-agent/src/cli/types.rs +32 -1
  16. package/crates/team-agent/src/compiler/tests.rs +2 -2
  17. package/crates/team-agent/src/compiler.rs +1 -1
  18. package/crates/team-agent/src/fake_worker.rs +32 -145
  19. package/crates/team-agent/src/leader/start.rs +272 -8
  20. package/crates/team-agent/src/leader/tests/basics.rs +1 -0
  21. package/crates/team-agent/src/leader/tests/identity.rs +121 -25
  22. package/crates/team-agent/src/leader/types.rs +9 -0
  23. package/crates/team-agent/src/lifecycle/display.rs +3 -3
  24. package/crates/team-agent/src/lifecycle/launch.rs +631 -70
  25. package/crates/team-agent/src/lifecycle/restart/agent.rs +137 -17
  26. package/crates/team-agent/src/lifecycle/restart/common.rs +38 -2
  27. package/crates/team-agent/src/lifecycle/restart/rebuild.rs +79 -4
  28. package/crates/team-agent/src/lifecycle/tests/core.rs +14 -5
  29. package/crates/team-agent/src/lifecycle/tests/launch_spawn.rs +731 -8
  30. package/crates/team-agent/src/lifecycle/types.rs +5 -0
  31. package/crates/team-agent/src/lifecycle/worker_command_context.rs +8 -0
  32. package/crates/team-agent/src/mcp_server/wire.rs +153 -3
  33. package/crates/team-agent/src/messaging/leader_receiver.rs +276 -43
  34. package/crates/team-agent/src/messaging/mod.rs +6 -3
  35. package/crates/team-agent/src/messaging/results.rs +240 -158
  36. package/crates/team-agent/src/messaging/send.rs +3 -2
  37. package/crates/team-agent/src/messaging/tests/e23.rs +35 -0
  38. package/crates/team-agent/src/messaging/tests/mod.rs +6 -2
  39. package/crates/team-agent/src/messaging/tests/runtime.rs +9 -9
  40. package/crates/team-agent/src/os_probe.rs +11 -0
  41. package/crates/team-agent/src/state/persist.rs +6 -0
  42. package/crates/team-agent/src/tmux_backend.rs +85 -0
  43. package/crates/team-agent/src/transport/test_support.rs +46 -1
  44. package/crates/team-agent/src/transport.rs +27 -0
  45. package/package.json +4 -4
  46. package/skills/team-agent/references/recovery-runbook.md +277 -0
@@ -158,11 +158,40 @@ fn tmp_shutdown_workspace(tag: &str) -> PathBuf {
158
158
 
159
159
  struct CleanShutdownTransport {
160
160
  session_present: Mutex<bool>,
161
+ targets: Vec<PaneInfo>,
162
+ kill_server_called: Mutex<bool>,
163
+ probe_timeout_kind: Option<&'static str>,
164
+ targets_persist_after_kill: bool,
161
165
  }
162
166
 
163
167
  impl CleanShutdownTransport {
164
168
  fn new() -> Self {
165
- Self { session_present: Mutex::new(true) }
169
+ Self {
170
+ session_present: Mutex::new(true),
171
+ targets: Vec::new(),
172
+ kill_server_called: Mutex::new(false),
173
+ probe_timeout_kind: None,
174
+ targets_persist_after_kill: false,
175
+ }
176
+ }
177
+
178
+ fn with_targets(mut self, targets: Vec<PaneInfo>) -> Self {
179
+ self.targets = targets;
180
+ self
181
+ }
182
+
183
+ fn kill_server_called(&self) -> bool {
184
+ *self.kill_server_called.lock().unwrap()
185
+ }
186
+
187
+ fn with_probe_timeout(mut self, probe: &'static str) -> Self {
188
+ self.probe_timeout_kind = Some(probe);
189
+ self
190
+ }
191
+
192
+ fn with_targets_persist_after_kill(mut self) -> Self {
193
+ self.targets_persist_after_kill = true;
194
+ self
166
195
  }
167
196
  }
168
197
 
@@ -227,7 +256,11 @@ impl Transport for CleanShutdownTransport {
227
256
  }
228
257
 
229
258
  fn list_targets(&self) -> Result<Vec<PaneInfo>, TransportError> {
230
- Ok(Vec::new())
259
+ if *self.session_present.lock().unwrap() || self.targets_persist_after_kill {
260
+ Ok(self.targets.clone())
261
+ } else {
262
+ Ok(Vec::new())
263
+ }
231
264
  }
232
265
 
233
266
  fn has_session(&self, _session: &SessionName) -> Result<bool, TransportError> {
@@ -248,10 +281,18 @@ impl Transport for CleanShutdownTransport {
248
281
  }
249
282
 
250
283
  fn kill_session(&self, _session: &SessionName) -> Result<(), TransportError> {
284
+ if let Some(probe) = self.probe_timeout_kind {
285
+ crate::os_probe::set_probe_timeout_for_test(probe, None, 900);
286
+ }
251
287
  *self.session_present.lock().unwrap() = false;
252
288
  Ok(())
253
289
  }
254
290
 
291
+ fn kill_server(&self) -> Result<(), TransportError> {
292
+ *self.kill_server_called.lock().unwrap() = true;
293
+ Ok(())
294
+ }
295
+
255
296
  fn kill_window(&self, _target: &Target) -> Result<(), TransportError> {
256
297
  Ok(())
257
298
  }
@@ -260,3 +301,245 @@ impl Transport for CleanShutdownTransport {
260
301
  Ok(AttachOutcome::Attached)
261
302
  }
262
303
  }
304
+
305
+ #[test]
306
+ fn lsof_cwd_timeout_is_diagnostic_not_shutdown_partial() {
307
+ let ws = tmp_shutdown_workspace("lsof-cwd-timeout-diagnostic");
308
+ crate::state::persist::save_runtime_state(
309
+ &ws,
310
+ &json!({
311
+ "session_name": "team-lsof-cwd-timeout",
312
+ "agents": {
313
+ "fake_impl": {
314
+ "status": "running",
315
+ "provider": "fake",
316
+ "window": "fake_impl"
317
+ }
318
+ }
319
+ }),
320
+ )
321
+ .unwrap();
322
+
323
+ let out = crate::cli::lifecycle_port::shutdown_with_transport(
324
+ &ws,
325
+ true,
326
+ None,
327
+ &CleanShutdownTransport::new().with_probe_timeout("lsof_cwd"),
328
+ )
329
+ .expect("shutdown should complete");
330
+
331
+ assert_eq!(out["ok"], json!(true));
332
+ assert_eq!(out["status"], json!("ok"));
333
+ assert_eq!(out["phase"], json!(null));
334
+ assert_eq!(out["verification_degraded"], json!(true));
335
+ assert_eq!(out["probe_timeout_kind"], json!("lsof_cwd"));
336
+ assert_eq!(out["residuals"]["sessions"], json!([]));
337
+ assert_eq!(out["residuals"]["processes"], json!([]));
338
+
339
+ let events = crate::event_log::EventLog::new(&ws).tail(0).expect("events");
340
+ let shutdown = events
341
+ .iter()
342
+ .find(|event| {
343
+ event.get("event").and_then(serde_json::Value::as_str) == Some("lifecycle.shutdown")
344
+ })
345
+ .unwrap_or_else(|| panic!("missing lifecycle.shutdown event: {events:?}"));
346
+ assert_eq!(shutdown["status"], json!("ok"));
347
+ assert_eq!(shutdown["phase"], json!(null));
348
+ assert_eq!(shutdown["verification_degraded"], json!(true));
349
+ assert_eq!(shutdown["probe_timeout_kind"], json!("lsof_cwd"));
350
+ }
351
+
352
+ #[test]
353
+ fn ps_table_timeout_still_degrades_shutdown_truth() {
354
+ let ws = tmp_shutdown_workspace("ps-table-timeout-partial");
355
+ crate::state::persist::save_runtime_state(
356
+ &ws,
357
+ &json!({
358
+ "session_name": "team-ps-table-timeout",
359
+ "agents": {
360
+ "fake_impl": {
361
+ "status": "running",
362
+ "provider": "fake",
363
+ "window": "fake_impl"
364
+ }
365
+ }
366
+ }),
367
+ )
368
+ .unwrap();
369
+
370
+ let out = crate::cli::lifecycle_port::shutdown_with_transport(
371
+ &ws,
372
+ true,
373
+ None,
374
+ &CleanShutdownTransport::new().with_probe_timeout("ps_table"),
375
+ )
376
+ .expect("shutdown should complete");
377
+
378
+ assert_eq!(out["ok"], json!(false));
379
+ assert_eq!(out["status"], json!("partial"));
380
+ assert_eq!(out["phase"], json!("os_probe"));
381
+ assert_eq!(out["verification_degraded"], json!(true));
382
+ assert_eq!(out["probe_timeout_kind"], json!("ps_table"));
383
+ }
384
+
385
+ #[test]
386
+ fn bounded_coordinator_stop_returns_grace_window_late_success() {
387
+ let ws = tmp_shutdown_workspace("late-coordinator-stop-success");
388
+ let report = crate::cli::lifecycle_port::stop_coordinator_bounded_with(
389
+ crate::coordinator::WorkspacePath::new(ws),
390
+ std::time::Duration::from_millis(5),
391
+ |_workspace| {
392
+ std::thread::sleep(std::time::Duration::from_millis(25));
393
+ Ok(crate::coordinator::StopReport {
394
+ ok: true,
395
+ status: crate::coordinator::StopOutcome::Stopped,
396
+ pid: Some(crate::coordinator::Pid::new(12345)),
397
+ })
398
+ },
399
+ )
400
+ .expect("late result inside grace window must be returned")
401
+ .expect("stop result should be ok");
402
+
403
+ assert!(report.ok, "late success must not be discarded as timeout");
404
+ assert_eq!(report.status, crate::coordinator::StopOutcome::Stopped);
405
+ }
406
+
407
+ #[test]
408
+ fn shutdown_outcome_late_or_postcheck_gone_is_ok_with_lsof_diagnostic() {
409
+ let out = crate::cli::lifecycle_port::classify_shutdown_outcome(
410
+ crate::cli::lifecycle_port::ShutdownOutcomeInput {
411
+ kill_error: false,
412
+ session_residuals: false,
413
+ process_residuals: false,
414
+ cleanup_truth_degraded: false,
415
+ coordinator_timeout: true,
416
+ coordinator_stop_ok: None,
417
+ coordinator_post_stop: crate::cli::lifecycle_port::CoordinatorStopObservation::Gone,
418
+ },
419
+ );
420
+
421
+ assert!(out.ok);
422
+ assert_eq!(out.status, "ok");
423
+ assert_eq!(out.phase, None);
424
+ }
425
+
426
+ #[test]
427
+ fn shutdown_outcome_coordinator_timeout_still_running_is_timeout() {
428
+ let out = crate::cli::lifecycle_port::classify_shutdown_outcome(
429
+ crate::cli::lifecycle_port::ShutdownOutcomeInput {
430
+ kill_error: false,
431
+ session_residuals: false,
432
+ process_residuals: false,
433
+ cleanup_truth_degraded: false,
434
+ coordinator_timeout: true,
435
+ coordinator_stop_ok: None,
436
+ coordinator_post_stop: crate::cli::lifecycle_port::CoordinatorStopObservation::Running,
437
+ },
438
+ );
439
+
440
+ assert!(!out.ok);
441
+ assert_eq!(out.status, "timeout");
442
+ assert_eq!(out.phase, Some("stop_coordinator"));
443
+ }
444
+
445
+ #[test]
446
+ fn shutdown_outcome_ps_table_degraded_still_partial_after_coordinator_gone() {
447
+ let out = crate::cli::lifecycle_port::classify_shutdown_outcome(
448
+ crate::cli::lifecycle_port::ShutdownOutcomeInput {
449
+ kill_error: false,
450
+ session_residuals: false,
451
+ process_residuals: false,
452
+ cleanup_truth_degraded: true,
453
+ coordinator_timeout: true,
454
+ coordinator_stop_ok: None,
455
+ coordinator_post_stop: crate::cli::lifecycle_port::CoordinatorStopObservation::Gone,
456
+ },
457
+ );
458
+
459
+ assert!(!out.ok);
460
+ assert_eq!(out.status, "partial");
461
+ assert_eq!(out.phase, Some("os_probe"));
462
+ }
463
+
464
+ #[test]
465
+ fn leader_env_tmux_socket_never_kills_server_even_when_sessions_look_exclusive() {
466
+ let ws = tmp_shutdown_workspace("leader-env-socket-no-kill-server");
467
+ crate::state::persist::save_runtime_state(
468
+ &ws,
469
+ &json!({
470
+ "tmux_socket_source": "leader_env",
471
+ "agents": {
472
+ "fake_impl": {
473
+ "status": "running",
474
+ "provider": "fake",
475
+ "window": "fake_impl"
476
+ }
477
+ }
478
+ }),
479
+ )
480
+ .unwrap();
481
+ let transport = CleanShutdownTransport::new().with_targets(vec![PaneInfo {
482
+ pane_id: PaneId::new("%1"),
483
+ session: SessionName::new("team-layout"),
484
+ window_index: Some(0),
485
+ window_name: Some(WindowName::new("team-w1")),
486
+ pane_index: Some(0),
487
+ tty: None,
488
+ current_command: None,
489
+ current_path: None,
490
+ active: true,
491
+ pane_pid: None,
492
+ leader_env: BTreeMap::new(),
493
+ }]);
494
+
495
+ let out = crate::cli::lifecycle_port::shutdown_with_transport(&ws, true, None, &transport)
496
+ .expect("shutdown should complete");
497
+
498
+ assert_eq!(out["ok"], json!(true));
499
+ assert_eq!(out["killed_sessions"], json!(["team-layout"]));
500
+ assert_eq!(out["spared_sessions"], json!([]));
501
+ assert!(
502
+ !transport.kill_server_called(),
503
+ "leader-env/shared socket shutdown must kill sessions individually, never kill-server"
504
+ );
505
+ }
506
+
507
+ #[test]
508
+ fn managed_leader_shutdown_never_kills_server_even_when_socket_looks_exclusive() {
509
+ let ws = tmp_shutdown_workspace("managed-leader-no-kill-server");
510
+ crate::state::persist::save_runtime_state(
511
+ &ws,
512
+ &json!({
513
+ "session_name": "team-current",
514
+ "is_external_leader": false,
515
+ "agents": {}
516
+ }),
517
+ )
518
+ .unwrap();
519
+ let transport = CleanShutdownTransport::new()
520
+ .with_targets(vec![PaneInfo {
521
+ pane_id: PaneId::new("%1"),
522
+ session: SessionName::new("team-current"),
523
+ window_index: Some(0),
524
+ window_name: Some(WindowName::new("leader")),
525
+ pane_index: Some(0),
526
+ tty: None,
527
+ current_command: Some("codex".to_string()),
528
+ current_path: None,
529
+ active: true,
530
+ pane_pid: None,
531
+ leader_env: BTreeMap::new(),
532
+ }])
533
+ .with_targets_persist_after_kill();
534
+
535
+ let out = crate::cli::lifecycle_port::shutdown_with_transport(&ws, true, None, &transport)
536
+ .expect("shutdown should complete");
537
+
538
+ assert_eq!(out["ok"], json!(true));
539
+ assert_eq!(out["killed_sessions"], json!(["team-current"]));
540
+ assert_eq!(out["spared_sessions"], json!([]));
541
+ assert!(
542
+ !transport.kill_server_called(),
543
+ "managed topology must clear the team session/window without kill-server"
544
+ );
545
+ }
@@ -36,6 +36,10 @@ use super::*;
36
36
  for key in [
37
37
  "team",
38
38
  "session_name",
39
+ "leader_topology",
40
+ "is_external_leader",
41
+ "leader_attach_command",
42
+ "leader_client",
39
43
  "tmux_session_present",
40
44
  "leader_receiver",
41
45
  "agents",
@@ -61,6 +65,27 @@ use super::*;
61
65
  let _ = std::fs::remove_dir_all(&ws);
62
66
  }
63
67
 
68
+ #[test]
69
+ fn status_port_status_reports_managed_leader_topology_and_attach_command() {
70
+ let ws = seed_status_workspace();
71
+ let mut state = crate::state::persist::load_runtime_state(&ws).unwrap();
72
+ if let Some(obj) = state.as_object_mut() {
73
+ obj.insert("is_external_leader".to_string(), json!(false));
74
+ obj.insert("session_name".to_string(), json!("team-current"));
75
+ }
76
+ crate::state::persist::save_runtime_state(&ws, &state).unwrap();
77
+
78
+ let v = status_port::status(&ws, /*compact=*/ true, /*detail=*/ false).expect("status");
79
+
80
+ assert_eq!(v["leader_topology"], json!("managed"));
81
+ assert_eq!(v["is_external_leader"], json!(false));
82
+ let attach = v["leader_attach_command"]
83
+ .as_str()
84
+ .expect("managed status includes attach command");
85
+ assert!(attach.contains("attach -t team-current:leader"), "{attach}");
86
+ let _ = std::fs::remove_dir_all(&ws);
87
+ }
88
+
64
89
  #[test]
65
90
  fn status_port_status_detail_full_keeps_uncompacted_events() {
66
91
  // cmd_status --json --detail -> status_port::status(compact=false): the FULL dict
@@ -97,7 +97,8 @@ impl CliError {
97
97
  "tmux session `{session}` already exists. It may be an active team. Do not terminate existing tmux sessions from startup; use a different team name or runtime.session_name and start again."
98
98
  );
99
99
  payload.next_actions = Some(vec![
100
- "Use a different team name or runtime.session_name before starting again.".to_string(),
100
+ "Use a different team name or runtime.session_name before starting again."
101
+ .to_string(),
101
102
  ]);
102
103
  }
103
104
  }
@@ -201,6 +202,8 @@ pub struct LeaderLauncherArgs {
201
202
  pub confirm_attach: bool,
202
203
  /// `--attach-session <name>` / `--attach-session=<name>`。
203
204
  pub attach_session: Option<String>,
205
+ /// 0.3.16 topology opt-out: keep the old external/current-pane leader launcher path.
206
+ pub external_leader: bool,
204
207
  }
205
208
 
206
209
  // =============================================================================
@@ -262,6 +265,7 @@ pub struct QuickStartArgs {
262
265
  pub team_id: Option<String>,
263
266
  pub yes: bool,
264
267
  pub fresh: bool,
268
+ pub no_display: bool,
265
269
  pub json: bool,
266
270
  }
267
271
 
@@ -304,6 +308,33 @@ pub struct SendArgs {
304
308
  pub message_id: Option<String>,
305
309
  }
306
310
 
311
+ /// E23 worker-side emergency fallback for `team_orchestrator.send_message`
312
+ /// transport failures. This is not a general control-plane send path.
313
+ #[derive(Debug, Clone, PartialEq, Eq)]
314
+ pub struct FallbackSendLeaderArgs {
315
+ pub workspace: PathBuf,
316
+ pub team: Option<String>,
317
+ pub sender: String,
318
+ pub task: Option<String>,
319
+ pub message_id: String,
320
+ pub content: String,
321
+ pub primary_error: String,
322
+ pub json: bool,
323
+ }
324
+
325
+ /// E23 worker-side emergency fallback for `team_orchestrator.report_result`
326
+ /// transport failures. It must still persist through the results DB.
327
+ #[derive(Debug, Clone, PartialEq, Eq)]
328
+ pub struct FallbackReportResultArgs {
329
+ pub workspace: PathBuf,
330
+ pub team: Option<String>,
331
+ pub agent_id: String,
332
+ pub task_id: String,
333
+ pub result_json: String,
334
+ pub primary_error: String,
335
+ pub json: bool,
336
+ }
337
+
307
338
  /// `allow-peer-talk`(`parser.py`): allow direct peer communication between two agents.
308
339
  #[derive(Debug, Clone, PartialEq, Eq)]
309
340
  pub struct AllowPeerTalkArgs {
@@ -179,7 +179,7 @@ fn front_matter_non_object_errors() {
179
179
  // separators=(",",":"))` with the workspace path templated to __WS__.
180
180
  // (team-agent-public v0.2.11, /tmp/probe_compiler.py.)
181
181
 
182
- const BASE_NOPROFILE_JSON: &str = r#"{"version":1,"team":{"name":"doc-team","mode":"supervisor_worker","objective":"Compile role docs.","workspace":"__WS__"},"leader":{"id":"leader","role":"leader","provider":"codex","model":"gpt-5.5","tools":["fs_read","fs_list","mcp_team"],"context_policy":{"keep_user_thread":true,"receive_worker_outputs":"business_messages_and_short_summaries","max_worker_result_tokens":2000}},"agents":[{"id":"implementer","role":"Implementation Engineer","provider":"codex","model":"gpt-5.5","auth_mode":"subscription","working_directory":"__WS__","system_prompt":{"inline":"Implement bounded tasks and report result_envelope_v1.","file":null},"tools":["fs_read","fs_write","execute_bash","mcp_team"],"permission_mode":"restricted","preferred_for":["implementer","Implementation Engineer"],"avoid_for":[],"output_contract":{"format":"result_envelope_v1","required_fields":["task_id","status","summary","artifacts"]}}],"routing":{"default_assignee":"implementer","rules":[{"id":"route-implementer","match":{"assignee":["implementer"]},"assign_to":"implementer","priority":10}]},"communication":{"protocol":"mcp_inbox","topology":"leader_centered","worker_to_worker":true,"ack_timeout_sec":60,"result_format":"result_envelope_v1","message_store":{"sqlite":".team/runtime/team.db","mirror_files":".team/messages"}},"runtime":{"backend":"tmux","display_backend":"none","session_name":"team-doc-team","auto_launch":true,"require_user_approval_before_launch":true,"max_active_agents":1,"startup_order":["implementer"],"dangerous_auto_approve":false,"fast":false,"tick_interval_sec":2,"push_min_interval_sec":60,"stuck_timeout_sec":300},"context":{"state_file":"team_state.md","artifact_dir":".team/artifacts","log_dir":".team/logs","summarization":{"worker_full_logs":"retain_outside_leader_context","state_update":"after_each_result"}},"tasks":[{"id":"task_initial","title":"Initial document-driven team task","type":"implementation","assignee":"implementer","deps":[],"acceptance":["Worker reports valid result_envelope_v1"],"status":"pending","requires_tools":["mcp_team"],"files":[],"risk":"low"}]}"#;
182
+ const BASE_NOPROFILE_JSON: &str = r#"{"version":1,"team":{"name":"doc-team","mode":"supervisor_worker","objective":"Compile role docs.","workspace":"__WS__"},"leader":{"id":"leader","role":"leader","provider":"codex","model":"gpt-5.5","tools":["fs_read","fs_list","mcp_team"],"context_policy":{"keep_user_thread":true,"receive_worker_outputs":"business_messages_and_short_summaries","max_worker_result_tokens":2000}},"agents":[{"id":"implementer","role":"Implementation Engineer","provider":"codex","model":"gpt-5.5","auth_mode":"subscription","working_directory":"__WS__","system_prompt":{"inline":"Implement bounded tasks and report result_envelope_v1.","file":null},"tools":["fs_read","fs_write","execute_bash","mcp_team"],"permission_mode":"restricted","preferred_for":["implementer","Implementation Engineer"],"avoid_for":[],"output_contract":{"format":"result_envelope_v1","required_fields":["task_id","status","summary","artifacts"]}}],"routing":{"default_assignee":"implementer","rules":[{"id":"route-implementer","match":{"assignee":["implementer"]},"assign_to":"implementer","priority":10}]},"communication":{"protocol":"mcp_inbox","topology":"leader_centered","worker_to_worker":true,"ack_timeout_sec":60,"result_format":"result_envelope_v1","message_store":{"sqlite":".team/runtime/team.db","mirror_files":".team/messages"}},"runtime":{"backend":"tmux","display_backend":"adaptive","session_name":"team-doc-team","auto_launch":true,"require_user_approval_before_launch":true,"max_active_agents":1,"startup_order":["implementer"],"dangerous_auto_approve":false,"fast":false,"tick_interval_sec":2,"push_min_interval_sec":60,"stuck_timeout_sec":300},"context":{"state_file":"team_state.md","artifact_dir":".team/artifacts","log_dir":".team/logs","summarization":{"worker_full_logs":"retain_outside_leader_context","state_update":"after_each_result"}},"tasks":[{"id":"task_initial","title":"Initial document-driven team task","type":"implementation","assignee":"implementer","deps":[],"acceptance":["Worker reports valid result_envelope_v1"],"status":"pending","requires_tools":["mcp_team"],"files":[],"risk":"low"}]}"#;
183
183
 
184
184
  #[test]
185
185
  fn compile_base_noprofile_matches_python_dict_order_and_values() {
@@ -352,7 +352,7 @@ tools:
352
352
  Bravo body.
353
353
  ";
354
354
 
355
- const TWO_AGENTS_JSON: &str = r#"{"version":1,"team":{"name":"doc-team","mode":"supervisor_worker","objective":"Compile role docs.","workspace":"__WS__"},"leader":{"id":"leader","role":"leader","provider":"codex","model":"gpt-5.5","tools":["fs_read","fs_list","mcp_team"],"context_policy":{"keep_user_thread":true,"receive_worker_outputs":"business_messages_and_short_summaries","max_worker_result_tokens":2000}},"agents":[{"id":"alpha","role":"Alpha Worker","provider":"codex","model":"gpt-5.5","auth_mode":"subscription","working_directory":"__WS__","system_prompt":{"inline":"Alpha body.","file":null},"tools":["mcp_team"],"permission_mode":"restricted","preferred_for":["alpha","Alpha Worker"],"avoid_for":[],"output_contract":{"format":"result_envelope_v1","required_fields":["task_id","status","summary","artifacts"]}},{"id":"bravo","role":"Bravo Worker","provider":"codex","model":"gpt-5.5","auth_mode":"subscription","working_directory":"__WS__","system_prompt":{"inline":"Bravo body.","file":null},"tools":["mcp_team"],"permission_mode":"restricted","preferred_for":["bravo","Bravo Worker"],"avoid_for":[],"output_contract":{"format":"result_envelope_v1","required_fields":["task_id","status","summary","artifacts"]}}],"routing":{"default_assignee":"alpha","rules":[{"id":"route-alpha","match":{"assignee":["alpha"]},"assign_to":"alpha","priority":10},{"id":"route-bravo","match":{"assignee":["bravo"]},"assign_to":"bravo","priority":10}]},"communication":{"protocol":"mcp_inbox","topology":"leader_centered","worker_to_worker":true,"ack_timeout_sec":60,"result_format":"result_envelope_v1","message_store":{"sqlite":".team/runtime/team.db","mirror_files":".team/messages"}},"runtime":{"backend":"tmux","display_backend":"none","session_name":"team-doc-team","auto_launch":true,"require_user_approval_before_launch":true,"max_active_agents":2,"startup_order":["alpha","bravo"],"dangerous_auto_approve":false,"fast":false,"tick_interval_sec":2,"push_min_interval_sec":60,"stuck_timeout_sec":300},"context":{"state_file":"team_state.md","artifact_dir":".team/artifacts","log_dir":".team/logs","summarization":{"worker_full_logs":"retain_outside_leader_context","state_update":"after_each_result"}},"tasks":[{"id":"task_initial","title":"Initial document-driven team task","type":"implementation","assignee":"alpha","deps":[],"acceptance":["Worker reports valid result_envelope_v1"],"status":"pending","requires_tools":["mcp_team"],"files":[],"risk":"low"}]}"#;
355
+ const TWO_AGENTS_JSON: &str = r#"{"version":1,"team":{"name":"doc-team","mode":"supervisor_worker","objective":"Compile role docs.","workspace":"__WS__"},"leader":{"id":"leader","role":"leader","provider":"codex","model":"gpt-5.5","tools":["fs_read","fs_list","mcp_team"],"context_policy":{"keep_user_thread":true,"receive_worker_outputs":"business_messages_and_short_summaries","max_worker_result_tokens":2000}},"agents":[{"id":"alpha","role":"Alpha Worker","provider":"codex","model":"gpt-5.5","auth_mode":"subscription","working_directory":"__WS__","system_prompt":{"inline":"Alpha body.","file":null},"tools":["mcp_team"],"permission_mode":"restricted","preferred_for":["alpha","Alpha Worker"],"avoid_for":[],"output_contract":{"format":"result_envelope_v1","required_fields":["task_id","status","summary","artifacts"]}},{"id":"bravo","role":"Bravo Worker","provider":"codex","model":"gpt-5.5","auth_mode":"subscription","working_directory":"__WS__","system_prompt":{"inline":"Bravo body.","file":null},"tools":["mcp_team"],"permission_mode":"restricted","preferred_for":["bravo","Bravo Worker"],"avoid_for":[],"output_contract":{"format":"result_envelope_v1","required_fields":["task_id","status","summary","artifacts"]}}],"routing":{"default_assignee":"alpha","rules":[{"id":"route-alpha","match":{"assignee":["alpha"]},"assign_to":"alpha","priority":10},{"id":"route-bravo","match":{"assignee":["bravo"]},"assign_to":"bravo","priority":10}]},"communication":{"protocol":"mcp_inbox","topology":"leader_centered","worker_to_worker":true,"ack_timeout_sec":60,"result_format":"result_envelope_v1","message_store":{"sqlite":".team/runtime/team.db","mirror_files":".team/messages"}},"runtime":{"backend":"tmux","display_backend":"adaptive","session_name":"team-doc-team","auto_launch":true,"require_user_approval_before_launch":true,"max_active_agents":2,"startup_order":["alpha","bravo"],"dangerous_auto_approve":false,"fast":false,"tick_interval_sec":2,"push_min_interval_sec":60,"stuck_timeout_sec":300},"context":{"state_file":"team_state.md","artifact_dir":".team/artifacts","log_dir":".team/logs","summarization":{"worker_full_logs":"retain_outside_leader_context","state_update":"after_each_result"}},"tasks":[{"id":"task_initial","title":"Initial document-driven team task","type":"implementation","assignee":"alpha","deps":[],"acceptance":["Worker reports valid result_envelope_v1"],"status":"pending","requires_tools":["mcp_team"],"files":[],"risk":"low"}]}"#;
356
356
 
357
357
  #[test]
358
358
  fn compile_two_agents_sorted_by_filename_with_routing_and_startup_order() {
@@ -247,7 +247,7 @@ pub fn compile_team(team_dir: &Path) -> Result<Value, ModelError> {
247
247
  "display_backend",
248
248
  Value::Str(
249
249
  string_field(&team_meta, "display_backend")
250
- .unwrap_or_else(|| "none".to_string()),
250
+ .unwrap_or_else(|| "adaptive".to_string()),
251
251
  ),
252
252
  ),
253
253
  ("session_name", Value::Str(session_name(&team_meta, &team_name))),