@team-agent/installer 0.5.98 → 0.5.99

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 (60) hide show
  1. package/Cargo.lock +1 -1
  2. package/Cargo.toml +1 -1
  3. package/crates/team-agent/src/cli/adapters.rs +0 -7
  4. package/crates/team-agent/src/cli/diagnose.rs +0 -16
  5. package/crates/team-agent/src/cli/emit.rs +15 -20
  6. package/crates/team-agent/src/cli/mod.rs +130 -31
  7. package/crates/team-agent/src/cli/spec.rs +7 -7
  8. package/crates/team-agent/src/cli/tests/main_preserved.rs +0 -1
  9. package/crates/team-agent/src/cli/tests/missing_subcommands.rs +0 -1
  10. package/crates/team-agent/src/cli/tests/run_delegation.rs +0 -3
  11. package/crates/team-agent/src/cli/tests/verb_validate.rs +0 -1
  12. package/crates/team-agent/src/cli/types.rs +0 -6
  13. package/crates/team-agent/src/compiler/tests.rs +5 -8
  14. package/crates/team-agent/src/compiler.rs +6 -14
  15. package/crates/team-agent/src/lifecycle/launch/add_agent.rs +1 -1
  16. package/crates/team-agent/src/lifecycle/launch/add_agent_state.rs +1 -1
  17. package/crates/team-agent/src/lifecycle/launch/agent_state.rs +5 -25
  18. package/crates/team-agent/src/lifecycle/launch/identity.rs +2 -18
  19. package/crates/team-agent/src/lifecycle/launch/layout.rs +7 -29
  20. package/crates/team-agent/src/lifecycle/launch/leader_context.rs +1 -1
  21. package/crates/team-agent/src/lifecycle/launch/mcp_config.rs +1 -1
  22. package/crates/team-agent/src/lifecycle/launch/ownership.rs +1 -1
  23. package/crates/team-agent/src/lifecycle/launch/pi_mcp.rs +7 -7
  24. package/crates/team-agent/src/lifecycle/launch/quick_start.rs +12 -49
  25. package/crates/team-agent/src/lifecycle/launch/quick_start_transport.rs +3 -9
  26. package/crates/team-agent/src/lifecycle/launch/readiness.rs +1 -1
  27. package/crates/team-agent/src/lifecycle/launch/spawn.rs +2 -61
  28. package/crates/team-agent/src/lifecycle/launch/spec_state.rs +2 -15
  29. package/crates/team-agent/src/lifecycle/launch/state_projection.rs +3 -7
  30. package/crates/team-agent/src/lifecycle/launch/worker_env.rs +1 -1
  31. package/crates/team-agent/src/lifecycle/launch.rs +3 -4
  32. package/crates/team-agent/src/lifecycle/mod.rs +0 -8
  33. package/crates/team-agent/src/lifecycle/restart/agent.rs +62 -8
  34. package/crates/team-agent/src/lifecycle/restart/common.rs +0 -32
  35. package/crates/team-agent/src/lifecycle/restart/rebuild.rs +0 -9
  36. package/crates/team-agent/src/lifecycle/tests/clone_agent_preserves_source_tools.rs +1 -2
  37. package/crates/team-agent/src/lifecycle/tests/clone_fork_copilot_perms_red.rs +0 -1
  38. package/crates/team-agent/src/lifecycle/tests/core.rs +0 -213
  39. package/crates/team-agent/src/lifecycle/tests/issue238_codex_effort_contract.rs +96 -0
  40. package/crates/team-agent/src/lifecycle/tests/lane_ops.rs +1 -0
  41. package/crates/team-agent/src/lifecycle/tests/launch_spawn.rs +31 -77
  42. package/crates/team-agent/src/lifecycle/tests/main_preserved.rs +2 -2
  43. package/crates/team-agent/src/lifecycle/tests/phase_b_contracts.rs +4 -8
  44. package/crates/team-agent/src/lifecycle/tests/phase_golden.rs +7 -7
  45. package/crates/team-agent/src/lifecycle/tests/pi_executable_mcp_red.rs +1 -1
  46. package/crates/team-agent/src/lifecycle/tests/startup_latency_contract.rs +3 -4
  47. package/crates/team-agent/src/lifecycle/tests.rs +1 -2
  48. package/crates/team-agent/src/lifecycle/types.rs +3 -133
  49. package/crates/team-agent/src/model/enums.rs +21 -71
  50. package/crates/team-agent/src/model/spec.rs +5 -20
  51. package/crates/team-agent/src/provider/adapters/codex.rs +2 -2
  52. package/crates/team-agent/src/provider/types.rs +6 -5
  53. package/examples/team.spec.yaml +0 -1
  54. package/package.json +4 -4
  55. package/schemas/team.schema.json +2 -2
  56. package/skills/team-agent/SKILL.md +1 -1
  57. package/skills/team-agent/references/team-agent-operator.md +3 -10
  58. package/crates/team-agent/src/lifecycle/display.rs +0 -550
  59. package/crates/team-agent/src/lifecycle/tests/core_034_real_red.rs +0 -884
  60. package/crates/team-agent/src/lifecycle/tests/display_adaptive_red.rs +0 -481
@@ -293,7 +293,6 @@ impl Case {
293
293
  self.ws(),
294
294
  "--team",
295
295
  TEAM_NAME,
296
- "--no-display",
297
296
  "--json",
298
297
  ]);
299
298
  let stderr = String::from_utf8_lossy(&out.stderr).to_string();
@@ -211,159 +211,6 @@ fn plan_condition_rejects_out_of_grammar() {
211
211
  }
212
212
  }
213
213
 
214
- // ───────────────────────────────────────────────────────────────────────
215
- // resolve_display_backend — display/backend.py
216
- // 默认 adaptive;显式 none 是逃生口。
217
- // ───────────────────────────────────────────────────────────────────────
218
-
219
- #[test]
220
- fn resolve_backend_defaults_to_adaptive_when_none_requested() {
221
- let r = resolve_display_backend(None, None);
222
- assert_eq!(r.backend, DisplayBackend::Adaptive);
223
- assert!(!r.non_default, "默认 adaptive 不应标记 non_default");
224
- }
225
-
226
- #[test]
227
- fn resolve_backend_explicit_none_is_escape_hatch() {
228
- let r = resolve_display_backend(Some(DisplayBackend::None), None);
229
- assert_eq!(r.backend, DisplayBackend::None);
230
- assert!(r.non_default, "显式 none 是偏离默认 adaptive 的逃生口");
231
- }
232
-
233
- #[test]
234
- fn resolve_backend_requested_overrides_and_marks_non_default() {
235
- // 显式 requested 非 adaptive → 采用且 non_default=true(非静默发事件)。
236
- let r = resolve_display_backend(Some(DisplayBackend::GhosttyWindow), None);
237
- assert_eq!(r.backend, DisplayBackend::GhosttyWindow);
238
- assert!(r.non_default);
239
- }
240
-
241
- #[test]
242
- fn resolve_backend_recorded_used_when_no_request() {
243
- // 无 requested 但 state 有 recorded → 复用 recorded(restart 一致性)。
244
- let r = resolve_display_backend(None, Some(DisplayBackend::GhosttyWorkspace));
245
- assert_eq!(r.backend, DisplayBackend::GhosttyWorkspace);
246
- assert!(r.non_default);
247
- }
248
-
249
- // ───────────────────────────────────────────────────────────────────────
250
- // probe_display_capabilities — display/adaptive.py:31 (C13)
251
- // 分支只看 probe 结果,NOT cfg!(target_os)。golden 实跑:
252
- // linux no-tmux : in_tmux=false, adaptive_status=leader_not_in_tmux, reason=leader_not_in_tmux
253
- // linux in-tmux : in_tmux=true, adaptive_status=available(opened), reason=None, caps both true
254
- // windows/wsl : in_tmux=false, adaptive_status=not_implemented_this_platform, caps both false
255
- // blocked 是 typed outcome(DisplayStatus::Blocked + reason),NOT error。
256
- // 注:RUST 入口签名为 probe_display_capabilities(workspace) —— 它内部读环境/平台;
257
- // 本测试在干净 CI workspace 上跑,只断言"不是 Err(平台降级是 typed outcome)"
258
- // 以及 reason 必属封闭集(若有)。
259
- // ───────────────────────────────────────────────────────────────────────
260
-
261
- #[test]
262
- fn probe_never_errors_platform_degradation_is_typed_outcome() {
263
- // C13:能力性降级绝不走 LifecycleError —— 它是 DisplayStatus::Blocked + reason。
264
- let ws = temp_ws();
265
- let probe =
266
- probe_display_capabilities(&ws).expect("probe 平台降级必须是 typed outcome,不是 Err");
267
- // 若 blocked,reason 必属 AdaptiveBlockReason 封闭集且与 status 一致。
268
- match probe.adaptive_status {
269
- DisplayStatus::Blocked => {
270
- assert!(probe.reason.is_some(), "blocked 必带 reason(C16 封闭集)")
271
- }
272
- DisplayStatus::Opened => assert!(probe.reason.is_none(), "opened 不应带 block reason"),
273
- DisplayStatus::Stopped => {}
274
- }
275
- }
276
-
277
- #[test]
278
- fn probe_caps_consistent_with_in_tmux() {
279
- // caps.adaptive_display == (in_tmux && 平台支持);不在 tmux → caps 全 false。
280
- let ws = temp_ws();
281
- let probe = probe_display_capabilities(&ws).expect("probe 不应 Err");
282
- if !probe.in_tmux {
283
- assert!(!probe.caps.adaptive_display);
284
- assert!(!probe.caps.tmux_append_windows);
285
- }
286
- }
287
-
288
- // ───────────────────────────────────────────────────────────────────────
289
- // AdaptiveBlockReason 封闭集 — ADAPTIVE_BLOCK_REASONS (adaptive.py:21)
290
- // golden 实跑(6 个,无更多无更少):
291
- // aggregator_rebuild_failed, leader_not_in_tmux, not_implemented_this_platform,
292
- // split_failed, window_create_failed, worker_session_missing
293
- // serde rename = snake_case,JSON 名与 Python 字符串一致。
294
- // ───────────────────────────────────────────────────────────────────────
295
-
296
- /// 封闭集 ALL — 一处穷举所有 AdaptiveBlockReason 变体(新增第 7 个变体会编译错此 match,
297
- /// 把 cardinality 锁成编译期事实)。golden ADAPTIVE_BLOCK_REASONS 恰 6 个(adaptive.py:21)。
298
- const ALL_BLOCK_REASONS: [AdaptiveBlockReason; 6] = [
299
- AdaptiveBlockReason::LeaderNotInTmux,
300
- AdaptiveBlockReason::SplitFailed,
301
- AdaptiveBlockReason::WindowCreateFailed,
302
- AdaptiveBlockReason::WorkerSessionMissing,
303
- AdaptiveBlockReason::NotImplementedThisPlatform,
304
- AdaptiveBlockReason::AggregatorRebuildFailed,
305
- ];
306
-
307
- #[test]
308
- fn adaptive_block_reason_serde_names_match_python_and_set_is_exactly_six() {
309
- // (a) 每个变体 wire-name 与 Python 字符串一致。
310
- let expected = [
311
- "\"leader_not_in_tmux\"",
312
- "\"split_failed\"",
313
- "\"window_create_failed\"",
314
- "\"worker_session_missing\"",
315
- "\"not_implemented_this_platform\"",
316
- "\"aggregator_rebuild_failed\"",
317
- ];
318
- for (variant, want) in ALL_BLOCK_REASONS.iter().zip(expected.iter()) {
319
- assert_eq!(&serde_json::to_string(variant).unwrap(), want);
320
- }
321
- // (b) 封闭集 CARDINALITY == 6(无多无少;rogue 第 7 变体使 ALL_BLOCK_REASONS match 编译失败)。
322
- let mut names: Vec<String> = ALL_BLOCK_REASONS
323
- .iter()
324
- .map(|r| serde_json::to_string(r).unwrap())
325
- .collect();
326
- names.sort();
327
- names.dedup();
328
- assert_eq!(
329
- names.len(),
330
- 6,
331
- "ADAPTIVE_BLOCK_REASONS 恰 6 个,无重复无遗漏"
332
- );
333
- }
334
-
335
- #[test]
336
- fn adaptive_blocked_out_of_set_reason_bottoms_to_aggregator_rebuild_failed() {
337
- // adaptive.py 兜底:越界 / aggregator 重建失败 → AggregatorRebuildFailed(documented overflow)。
338
- // RED:经真实路径 —— probe 在 blocked 平台 open_worker_displays,worker display 必属封闭集;
339
- // 这里用 probe.reason 越界场景驱动 open_worker_displays 而非静态枚举,捕获 reason 发射回归。
340
- let ws = temp_ws();
341
- let probe = DisplayProbe {
342
- in_tmux: true,
343
- platform: "linux".to_string(),
344
- leader_session: Some(sess("leader")),
345
- leader_pane: None,
346
- caps: CapsFlags {
347
- tmux_append_windows: true,
348
- adaptive_display: true,
349
- },
350
- // 模拟 aggregator 重建失败封闭:open 必把 worker display 兜底成 AggregatorRebuildFailed。
351
- adaptive_status: DisplayStatus::Blocked,
352
- reason: Some(AdaptiveBlockReason::AggregatorRebuildFailed),
353
- };
354
- let rep = open_worker_displays(&ws, &sess("team-a"), DisplayBackend::Adaptive, &probe)
355
- .expect("C14:显示失败不阻塞 readiness");
356
- for (id, d) in rep.displays.iter() {
357
- match d {
358
- WorkerDisplay::Blocked { reason } => assert!(
359
- ALL_BLOCK_REASONS.contains(reason),
360
- "worker {id} 的 block reason 必属封闭集:{reason:?}"
361
- ),
362
- other => panic!("blocked probe 下 worker {id} 应 Blocked:{other:?}"),
363
- }
364
- }
365
- }
366
-
367
214
  #[test]
368
215
  fn start_mode_serde_names_match_python_start_mode_strings() {
369
216
  // 低价值 wire-format 守卫:start_mode ∈ {"resumed","fresh","fresh_after_missing_rollout","noop"}。
@@ -1300,66 +1147,6 @@ impl Drop for EnvVarGuard {
1300
1147
  }
1301
1148
  }
1302
1149
 
1303
- // ───────────────────────────────────────────────────────────────────────
1304
- // open_worker_displays — worker_window.py (C14)
1305
- // 显示失败不阻塞 readiness:probe 为 blocked 平台时,returns typed Blocked displays,
1306
- // 绝不 Err。这里用 backend=None(无 worker views)验证至少不 panic 成 Err。
1307
- // ───────────────────────────────────────────────────────────────────────
1308
-
1309
- #[test]
1310
- fn open_worker_displays_blocked_probe_yields_typed_blocked_not_error() {
1311
- let ws = temp_ws();
1312
- let probe = DisplayProbe {
1313
- in_tmux: false,
1314
- platform: "windows".to_string(),
1315
- leader_session: None,
1316
- leader_pane: None,
1317
- caps: CapsFlags {
1318
- tmux_append_windows: false,
1319
- adaptive_display: false,
1320
- },
1321
- adaptive_status: DisplayStatus::Blocked,
1322
- reason: Some(AdaptiveBlockReason::NotImplementedThisPlatform),
1323
- };
1324
- let rep = open_worker_displays(&ws, &sess("team-a"), DisplayBackend::Adaptive, &probe)
1325
- .expect("C14:显示失败不阻塞 readiness —— 不得 Err");
1326
- // 每个 worker 的 display 在 blocked 平台上应是 Blocked 变体(若有 worker)。
1327
- for (id, d) in rep.displays.iter() {
1328
- assert!(
1329
- matches!(
1330
- d,
1331
- WorkerDisplay::Blocked {
1332
- reason: AdaptiveBlockReason::NotImplementedThisPlatform
1333
- }
1334
- ),
1335
- "worker {id} 在 windows 平台应 Blocked(not_implemented):{d:?}"
1336
- );
1337
- }
1338
- }
1339
-
1340
- // ───────────────────────────────────────────────────────────────────────
1341
- // close_team_display_backends — display/close.py (C9 close-by-recorded-backend)
1342
- // 空 workspace 无 recorded backend 无 session → 空 closed / 空 orphans(不 Err)。
1343
- // adaptive 只删带 team-tag 的窗口(C2 leader pane 安全)。
1344
- // ───────────────────────────────────────────────────────────────────────
1345
-
1346
- #[test]
1347
- fn close_team_display_empty_workspace_closes_nothing_not_error() {
1348
- let ws = temp_ws();
1349
- let rep = close_team_display_backends(&ws, &sess("team-a"))
1350
- .expect("C9:无 recorded backend 应 Ok(空报告),不是 Err");
1351
- assert!(
1352
- rep.closed.is_empty(),
1353
- "无 recorded backend 不应关任何东西:{:?}",
1354
- rep.closed
1355
- );
1356
- assert!(
1357
- rep.orphans_cleaned.is_empty(),
1358
- "空 workspace 无 orphan:{:?}",
1359
- rep.orphans_cleaned
1360
- );
1361
- }
1362
-
1363
1150
  // ───────────────────────────────────────────────────────────────────────
1364
1151
  // fork_agent — operations.py:284 (native session fork eligibility)
1365
1152
  // 资格门(adapter.supports_session_fork = auth_mode != "compatible_api"):
@@ -0,0 +1,96 @@
1
+ //! Issue #238 lifecycle-side contracts for persisted/recovered effort and Pi
2
+ //! leader argument admission. This module deliberately calls the same
3
+ //! lifecycle helpers used by spawn/restart instead of duplicating resolution.
4
+
5
+ #![allow(clippy::expect_used, clippy::panic, clippy::unwrap_used)]
6
+
7
+ use crate::lifecycle::launch::{
8
+ provider_effort_for_spawn, provider_effort_for_spawn_json, provider_effort_from_raw,
9
+ };
10
+ use crate::lifecycle::launch::pi_mcp::parse_pi_leader_args;
11
+ use crate::model::enums::{Provider, ProviderEffort};
12
+ use crate::model::yaml::Value as Yaml;
13
+ use serde_json::json;
14
+
15
+ fn args(items: &[&str]) -> Vec<String> {
16
+ items.iter().map(|item| (*item).to_string()).collect()
17
+ }
18
+
19
+ #[test]
20
+ fn r6_spawn_and_persisted_recovery_preserve_codex_effort_literals() {
21
+ for raw in ["max", "ultra"] {
22
+ let yaml_agent = Yaml::Map(vec![(
23
+ "effort".to_string(),
24
+ Yaml::Str(raw.to_string()),
25
+ )]);
26
+ let json_agent = json!({"effort": raw});
27
+
28
+ let direct = provider_effort_from_raw(Some(raw), Provider::Codex)
29
+ .expect("direct lifecycle resolution")
30
+ .expect("Codex effort");
31
+ let spawned = provider_effort_for_spawn(&yaml_agent, Provider::Codex)
32
+ .expect("YAML spawn resolution")
33
+ .expect("spawned Codex effort");
34
+ let recovered = provider_effort_for_spawn_json(&json_agent, Provider::Codex)
35
+ .expect("JSON recovery resolution")
36
+ .expect("recovered Codex effort");
37
+ assert_eq!(direct.as_str(), raw);
38
+ assert_eq!(spawned.as_str(), raw);
39
+ assert_eq!(recovered.as_str(), raw);
40
+ }
41
+ }
42
+
43
+ #[test]
44
+ fn r6_unsupported_ultra_is_not_silently_dropped_on_legacy_provider() {
45
+ let error = provider_effort_from_raw(Some("ultra"), Provider::Fake)
46
+ .expect_err("ultra must not be silently ignored")
47
+ .to_string();
48
+ assert!(error.contains("effort 'ultra' is only supported by codex"), "{error}");
49
+ }
50
+
51
+ #[test]
52
+ fn r7_pi_leader_parser_resolves_thinking_by_provider_semantics() {
53
+ let parsed = parse_pi_leader_args(&args(&[
54
+ "--",
55
+ "--model",
56
+ "openai-codex/gpt-5.6-luna",
57
+ "--thinking",
58
+ "max",
59
+ ]))
60
+ .expect("Pi max arguments");
61
+ assert_eq!(parsed.model.as_deref(), Some("openai-codex/gpt-5.6-luna"));
62
+ assert_eq!(parsed.effort, Some(ProviderEffort::Max));
63
+
64
+ let error = parse_pi_leader_args(&args(&[
65
+ "--model",
66
+ "openai-codex/gpt-5.6-luna",
67
+ "--thinking",
68
+ "ultra",
69
+ ]))
70
+ .expect_err("Pi ultra must be rejected by shared resolver")
71
+ .to_string();
72
+ assert!(error.contains("effort 'ultra' is only supported by codex"), "{error}");
73
+ }
74
+
75
+ #[test]
76
+ fn r7_pi_leader_parser_keeps_boundary_errors_fail_closed() {
77
+ for invalid in [
78
+ args(&["--thinking"]),
79
+ args(&["--unknown", "value"]),
80
+ args(&["--model", "codex"]),
81
+ args(&["--model", "openai-codex/gpt-5.6-luna", "--model", "other/x"]),
82
+ ] {
83
+ assert!(parse_pi_leader_args(&invalid).is_err(), "must reject {invalid:?}");
84
+ }
85
+
86
+ let no_effort = parse_pi_leader_args(&args(&["--model", "openai-codex/gpt-5.6-luna"]))
87
+ .expect("Pi model-only arguments");
88
+ assert_eq!(no_effort.effort, None);
89
+ }
90
+
91
+ #[test]
92
+ fn r7_pi_provider_authority_is_not_model_namespace_authority() {
93
+ let error = provider_effort_from_raw(Some("ultra"), Provider::Pi)
94
+ .expect_err("Pi ultra");
95
+ assert!(error.to_string().contains("effort 'ultra' is only supported by codex"));
96
+ }
@@ -1284,6 +1284,7 @@ fn lanea_stop_window_present_kills_and_stopped_true() {
1284
1284
  // touches the display (mark_agent_stopped leaves it as-is) and hardcodes display_closed:false. RED: the
1285
1285
  // persisted display.status/pane_title are the in-process observable.
1286
1286
  #[test]
1287
+ #[ignore = "legacy external display contract removed; runtime is silent tmux only"]
1287
1288
  fn lanea_stop_ghostty_workspace_relabels_slot_to_stopped() {
1288
1289
  let ws = lanea_ws_agents(json!({
1289
1290
  "alpha": { "status": "running", "provider": "codex", "window": "alpha",
@@ -1382,13 +1382,12 @@ fn quick_start_default_adaptive_groups_workers_into_layout_panes() {
1382
1382
  let report = quick_start_with_transport(&team, None, true, None, &transport)
1383
1383
  .expect("quick_start_with_transport must reach Ready");
1384
1384
 
1385
- let (launch, attach_commands, display_backend) = match report {
1385
+ let (launch, attach_commands) = match report {
1386
1386
  QuickStartReport::Ready {
1387
1387
  launch,
1388
1388
  attach_commands,
1389
- display_backend,
1390
1389
  ..
1391
- } => (*launch, attach_commands, display_backend),
1390
+ } => (*launch, attach_commands),
1392
1391
  other => panic!("quick_start must reach Ready; got {other:?}"),
1393
1392
  };
1394
1393
  // 0.3.28 Step 4b: adaptive 3-pane tiling replaced with 1-window-per-agent
@@ -1424,7 +1423,6 @@ fn quick_start_default_adaptive_groups_workers_into_layout_panes() {
1424
1423
  attach_commands.iter().any(|cmd| cmd.contains(":w1")),
1425
1424
  "attach commands must include the per-agent windows: {attach_commands:?}"
1426
1425
  );
1427
- let _ = display_backend; // display_backend value preserved by upstream
1428
1426
  }
1429
1427
 
1430
1428
  #[test]
@@ -1602,50 +1600,6 @@ fn attach_window_names_for_state_agents_include_managed_leader_and_layout_window
1602
1600
  assert_eq!(windows, vec!["leader", "team-w1", "team-w2"]);
1603
1601
  }
1604
1602
 
1605
- #[test]
1606
- #[serial(env)]
1607
- fn quick_start_no_display_keeps_one_window_per_agent() {
1608
- let _hermetic = enter_hermetic("launch-spawn-no-display");
1609
- let roles = ["w1", "w2"]
1610
- .into_iter()
1611
- .map(|id| (format!("{id}.md"), role_doc(id)))
1612
- .collect::<Vec<_>>();
1613
- let role_refs = roles
1614
- .iter()
1615
- .map(|(file, doc)| (file.as_str(), doc.as_str()))
1616
- .collect::<Vec<_>>();
1617
- let team = quick_start_team_dir_with_roles(&role_refs);
1618
- let workspace = team.parent().expect("team_workspace(team_dir) = parent");
1619
- seed_healthy_coordinator(workspace);
1620
- let transport = OfflineTransport::new();
1621
-
1622
- let report = quick_start_with_transport_in_workspace_with_display(
1623
- workspace, &team, None, true, None, &transport, false,
1624
- )
1625
- .expect("quick_start_with_transport must reach Ready");
1626
-
1627
- let display_backend = match report {
1628
- QuickStartReport::Ready {
1629
- display_backend, ..
1630
- } => display_backend,
1631
- other => panic!("quick_start must reach Ready; got {other:?}"),
1632
- };
1633
- assert_eq!(display_backend, "none");
1634
- assert_eq!(
1635
- transport
1636
- .spawn_records()
1637
- .iter()
1638
- .map(|(kind, _)| kind.as_str())
1639
- .collect::<Vec<_>>(),
1640
- vec!["spawn_first", "spawn_into"],
1641
- "--no-display must use the legacy one-worker-window spawn path"
1642
- );
1643
- let (_raw, state) = raw_runtime_state(workspace);
1644
- assert_eq!(state["display_backend"], json!("none"));
1645
- assert_eq!(state.pointer("/agents/w1/window"), Some(&json!("w1")));
1646
- assert_eq!(state.pointer("/agents/w1/layout_window"), None);
1647
- }
1648
-
1649
1603
  // REAL-MACHINE residency boundary (acceptance framework): the PUBLIC quick_start (real TmuxBackend +
1650
1604
  // real start_coordinator) on a fresh ws must leave a LIVE tmux session AND a ps-verifiable resident
1651
1605
  // coordinator daemon (start_coordinator -> live pid). The framework verifies residency via ps; here we
@@ -2778,6 +2732,7 @@ fn fork_refuses_source_with_partial_tuple() {
2778
2732
  }
2779
2733
 
2780
2734
  #[test]
2735
+ #[ignore = "legacy adaptive display layout contract removed; runtime is silent tmux only"]
2781
2736
  fn add_agent_adaptive_splits_last_non_full_layout_window() {
2782
2737
  let roles = [("w1.md", role_doc("w1")), ("w2.md", role_doc("w2"))];
2783
2738
  let role_refs = roles
@@ -2790,7 +2745,7 @@ fn add_agent_adaptive_splits_last_non_full_layout_window() {
2790
2745
  &team,
2791
2746
  &json!({
2792
2747
  "session_name": session,
2793
- "display_backend": "adaptive",
2748
+ "legacy_display_key": "adaptive",
2794
2749
  "active_team_key": "teamdir",
2795
2750
  "agents": {
2796
2751
  "w1": {"status": "running", "provider": "codex", "window": "team-w1", "layout_window": "team-w1", "layout_index": 0, "pane_index": 0, "pane_id": "%1"},
@@ -2835,6 +2790,7 @@ fn add_agent_adaptive_splits_last_non_full_layout_window() {
2835
2790
  }
2836
2791
 
2837
2792
  #[test]
2793
+ #[ignore = "legacy adaptive display layout contract removed; runtime is silent tmux only"]
2838
2794
  fn add_agent_adaptive_creates_suffix_window_when_last_layout_full_and_name_collides() {
2839
2795
  let roles = [
2840
2796
  ("w1.md", role_doc("w1")),
@@ -2851,7 +2807,7 @@ fn add_agent_adaptive_creates_suffix_window_when_last_layout_full_and_name_colli
2851
2807
  &team,
2852
2808
  &json!({
2853
2809
  "session_name": session,
2854
- "display_backend": "adaptive",
2810
+ "legacy_display_key": "adaptive",
2855
2811
  "active_team_key": "teamdir",
2856
2812
  "agents": {
2857
2813
  "w1": {"status": "running", "provider": "codex", "window": "team-w1", "layout_window": "team-w1", "layout_index": 0, "pane_index": 0, "pane_id": "%1"},
@@ -2918,7 +2874,7 @@ fn stop_agent_adaptive_kills_target_pane_not_shared_layout_window() {
2918
2874
  &team,
2919
2875
  &json!({
2920
2876
  "session_name": session,
2921
- "display_backend": "adaptive",
2877
+ "legacy_display_key": "adaptive",
2922
2878
  "agents": {
2923
2879
  "w1": {"status": "running", "provider": "codex", "window": "team-w1", "layout_window": "team-w1", "layout_index": 0, "pane_index": 0, "pane_id": "%1"},
2924
2880
  "w2": {"status": "running", "provider": "codex", "window": "team-w1", "layout_window": "team-w1", "layout_index": 0, "pane_index": 1, "pane_id": "%2"}
@@ -2954,13 +2910,14 @@ fn stop_agent_adaptive_kills_target_pane_not_shared_layout_window() {
2954
2910
  }
2955
2911
 
2956
2912
  #[test]
2913
+ #[ignore = "legacy adaptive display layout contract removed; runtime is silent tmux only"]
2957
2914
  fn start_agent_adaptive_restarts_missing_pane_in_existing_layout_window() {
2958
2915
  let ws = temp_ws();
2959
2916
  crate::state::persist::save_runtime_state(
2960
2917
  &ws,
2961
2918
  &json!({
2962
2919
  "session_name": "team-layout-restart",
2963
- "display_backend": "adaptive",
2920
+ "legacy_display_key": "adaptive",
2964
2921
  "agents": {
2965
2922
  "w1": {"status": "running", "provider": "codex", "role": "w1", "model": "gpt-5.5", "auth_mode": "subscription", "window": "team-w1", "layout_window": "team-w1", "layout_index": 0, "pane_index": 0, "pane_id": "%1"},
2966
2923
  "w2": {"status": "running", "provider": "codex", "role": "w2", "model": "gpt-5.5", "auth_mode": "subscription", "window": "team-w1", "layout_window": "team-w1", "layout_index": 0, "pane_index": 1, "pane_id": "%2"}
@@ -3050,9 +3007,9 @@ fn quick_start_seeds_tasks_key_from_compiled_spec() {
3050
3007
  }
3051
3008
 
3052
3009
  // Stage A — golden launch/core.py:62-71 seeded top-level runtime state in insertion order:
3053
- // spec_path, workspace, team_dir, team_key, session_name, leader, agents, tasks, display_backend.
3010
+ // spec_path, workspace, team_dir, team_key, session_name, leader, agents, tasks.
3054
3011
  //
3055
- // OLD (Python parity): the top-level shape ended at `display_backend`.
3012
+ // The runtime state is tmux-only; no display backend is persisted.
3056
3013
  // NEW (Bug 1/2 — team-in-team state scope, see tests/team_in_team_state_scope_red.rs):
3057
3014
  // `active_team_key` and `teams` are appended at the tail so the runtime can carry the
3058
3015
  // nested team-in-team scope alongside the original flat fields. Owner-binding fields
@@ -3065,11 +3022,10 @@ fn quick_start_seeds_tasks_key_from_compiled_spec() {
3065
3022
  // R1: topology is explicit; fresh managed teams carry `is_external_leader=false`
3066
3023
  // before the team-in-team suffix. Canonical `team_key` is seeded before owner
3067
3024
  // binding; the remaining order stays the golden prefix + topology marker + new
3068
- // suffix (active_team_key, teams). display_backend stays the resolved backend from display/backend.py:12-29
3069
- // (default adaptive), not the raw optional spec field.
3025
+ // suffix (active_team_key, teams).
3070
3026
  #[test]
3071
3027
  #[serial(env)]
3072
- fn quick_start_state_seeds_spec_path_workspace_leader_display_backend() {
3028
+ fn quick_start_state_seeds_spec_path_workspace_leader() {
3073
3029
  let _hermetic = enter_hermetic("launch-spawn-state-seeds");
3074
3030
  // Bug 2 owner team-scope: top-level owner triple is dropped when the seeded
3075
3031
  // pane is empty; an ambient TMUX_PANE (tests run inside the dev's tmux session)
@@ -3106,7 +3062,6 @@ fn quick_start_state_seeds_spec_path_workspace_leader_display_backend() {
3106
3062
  "leader",
3107
3063
  "agents",
3108
3064
  "tasks",
3109
- "display_backend",
3110
3065
  "is_external_leader",
3111
3066
  // 0.5.x Phase 1d hot-path 接线 (裁决1 msg_76e1d98202b8):
3112
3067
  // `transport = { kind, source }` inserted here by
@@ -3155,11 +3110,6 @@ fn quick_start_state_seeds_spec_path_workspace_leader_display_backend() {
3155
3110
  "existing tasks value must remain seeded from compiled spec; got {:?}",
3156
3111
  state["tasks"]
3157
3112
  );
3158
- assert_eq!(
3159
- state["display_backend"],
3160
- json!("adaptive"),
3161
- "adaptive layout directive: resolve_display_backend(None, source='launch') defaults to adaptive"
3162
- );
3163
3113
  assert_eq!(state["is_external_leader"], json!(false));
3164
3114
  }
3165
3115
 
@@ -3602,7 +3552,7 @@ fn add_agent_reachability_gate_passes_when_spawn_pane_addressable() {
3602
3552
  // ═════════════════════════════════════════════════════════════════════════════
3603
3553
  // E43 add-agent window layout drift (0.3.24 bug#3, demo-director startup blocker).
3604
3554
  //
3605
- // Root cause (architect res_f27ff9b29957): with `display_backend=adaptive` in
3555
+ // Root cause (architect res_f27ff9b29957): with `legacy_display_key=adaptive` in
3606
3556
  // state AND stale `agents.<id>.layout_window="team-w2"` residue, the existing
3607
3557
  // `adaptive_placement_for_agent` validates a layout_window claim by checking
3608
3558
  // whether the existing agent's `pane_id` is in `live_panes` — but it does NOT
@@ -3641,7 +3591,7 @@ fn e43_adaptive_placement_skips_existing_agent_whose_live_pane_window_name_diffe
3641
3591
  use crate::transport::{SessionName, WindowName};
3642
3592
 
3643
3593
  let state = json!({
3644
- "display_backend": "adaptive",
3594
+ "legacy_display_key": "adaptive",
3645
3595
  "session_name": "team-iso",
3646
3596
  "agents": {
3647
3597
  "architect": {
@@ -3698,13 +3648,14 @@ fn e43_adaptive_placement_skips_existing_agent_whose_live_pane_window_name_diffe
3698
3648
  /// behaviour (groups the new agent into that window). This is the canonical
3699
3649
  /// adaptive 1-window-multiple-panes use case.
3700
3650
  #[test]
3651
+ #[ignore = "legacy adaptive display layout contract removed; runtime is silent tmux only"]
3701
3652
  fn e43_adaptive_placement_groups_into_layout_window_when_live_pane_matches_claim() {
3702
3653
  use crate::lifecycle::launch::adaptive_placement_for_agent;
3703
3654
  use crate::model::ids::AgentId;
3704
3655
  use crate::transport::{SessionName, WindowName};
3705
3656
 
3706
3657
  let state = json!({
3707
- "display_backend": "adaptive",
3658
+ "legacy_display_key": "adaptive",
3708
3659
  "session_name": "team-iso",
3709
3660
  "agents": {
3710
3661
  "alpha": {
@@ -3750,13 +3701,14 @@ fn e43_adaptive_placement_groups_into_layout_window_when_live_pane_matches_claim
3750
3701
  /// agent_id itself — so the subsequent spawn opens a new window, not a split
3751
3702
  /// of a phantom.
3752
3703
  #[test]
3704
+ #[ignore = "legacy adaptive display layout contract removed; runtime is silent tmux only"]
3753
3705
  fn e43_adaptive_existing_placement_falls_back_to_agent_id_window_when_claim_missing_from_live() {
3754
3706
  use crate::lifecycle::launch::adaptive_existing_placement_for_agent;
3755
3707
  use crate::model::ids::AgentId;
3756
3708
  use crate::transport::{SessionName, WindowName};
3757
3709
 
3758
3710
  let state = json!({
3759
- "display_backend": "adaptive",
3711
+ "legacy_display_key": "adaptive",
3760
3712
  "session_name": "team-iso",
3761
3713
  "agents": {
3762
3714
  "demo-director": {
@@ -3814,13 +3766,14 @@ fn e43_adaptive_existing_placement_falls_back_to_agent_id_window_when_claim_miss
3814
3766
  /// (return a starts_window=false placement that splits into the existing
3815
3767
  /// window).
3816
3768
  #[test]
3769
+ #[ignore = "legacy adaptive display layout contract removed; runtime is silent tmux only"]
3817
3770
  fn e43_adaptive_existing_placement_keeps_starts_window_false_when_claim_in_live_windows() {
3818
3771
  use crate::lifecycle::launch::adaptive_existing_placement_for_agent;
3819
3772
  use crate::model::ids::AgentId;
3820
3773
  use crate::transport::{SessionName, WindowName};
3821
3774
 
3822
3775
  let state = json!({
3823
- "display_backend": "adaptive",
3776
+ "legacy_display_key": "adaptive",
3824
3777
  "session_name": "team-iso",
3825
3778
  "agents": {
3826
3779
  "alpha": {
@@ -3861,7 +3814,7 @@ fn e43_adaptive_existing_placement_keeps_starts_window_false_when_claim_in_live_
3861
3814
 
3862
3815
  /// **RED — Fix C end-to-end**: `add_agent` end-to-end must NOT emit a
3863
3816
  /// spawn_split with the stale window name. With state carrying
3864
- /// `display_backend=adaptive` + stale `layout_window=team-w2` residue and
3817
+ /// `legacy_display_key=adaptive` + stale `layout_window=team-w2` residue and
3865
3818
  /// live tmux windows named per-agent, the spawn must be a `spawn_into`
3866
3819
  /// (new window) — NOT `spawn_split` against the missing 'team-w2'.
3867
3820
  #[test]
@@ -3885,7 +3838,7 @@ fn e43_add_agent_does_not_split_against_phantom_layout_window() {
3885
3838
  &team,
3886
3839
  &json!({
3887
3840
  "session_name": "team-iso",
3888
- "display_backend": "adaptive",
3841
+ "legacy_display_key": "adaptive",
3889
3842
  "agents": {
3890
3843
  "architect": {
3891
3844
  "status": "running",
@@ -3939,7 +3892,7 @@ fn e43_add_agent_does_not_split_against_phantom_layout_window() {
3939
3892
  // E45 per-agent window drift (0.3.24 task#326 P0).
3940
3893
  //
3941
3894
  // Surfaced AFTER E43 (c5ed576) fixed the team-w2 phantom split. New repro:
3942
- // state has display_backend=adaptive + an existing `developer` worker whose
3895
+ // state has legacy_display_key=adaptive + an existing `developer` worker whose
3943
3896
  // state row carries `layout_window=developer, layout_index=0, pane_id=%519`,
3944
3897
  // live tmux shows `developer` window with that pane. add-agent demo-director
3945
3898
  // resolves placement via `adaptive_placement_for_agent`, which finds the
@@ -3971,7 +3924,7 @@ fn e45_adaptive_placement_returns_none_when_no_real_team_w_window_present_in_sta
3971
3924
  // literally `developer` (per-agent name, NOT team-wN), pane_id %519
3972
3925
  // lives under the `developer` window.
3973
3926
  let state = json!({
3974
- "display_backend": "adaptive",
3927
+ "legacy_display_key": "adaptive",
3975
3928
  "session_name": "team-iso",
3976
3929
  "agents": {
3977
3930
  "developer": {
@@ -4014,7 +3967,7 @@ fn e45_adaptive_placement_returns_none_when_no_real_team_w_window_present_in_sta
4014
3967
  "E45 (0.3.24 bug#4): placement must return None when the live \
4015
3968
  session has NO real `team-w<N>` adaptive window. Per-agent windows \
4016
3969
  like `developer` are NOT adaptive layout windows — they are \
4017
- per-agent topology, even if display_backend=adaptive lingers in \
3970
+ per-agent topology, even if legacy_display_key=adaptive lingers in \
4018
3971
  state. With no real adaptive layout, the caller falls back to \
4019
3972
  opening a new per-agent window named after agent_id. Got \
4020
3973
  {placement:?} — must be None to prevent split-window -t :developer \
@@ -4033,7 +3986,7 @@ fn e45_adaptive_existing_placement_returns_none_when_claim_is_per_agent_window()
4033
3986
  use crate::transport::{SessionName, WindowName};
4034
3987
 
4035
3988
  let state = json!({
4036
- "display_backend": "adaptive",
3989
+ "legacy_display_key": "adaptive",
4037
3990
  "session_name": "team-iso",
4038
3991
  "agents": {
4039
3992
  "developer": {
@@ -4087,14 +4040,14 @@ fn e45_add_agent_opens_new_window_does_not_split_into_per_agent_window() {
4087
4040
  let role_file = team.join("worker2-role.md");
4088
4041
  std::fs::write(&role_file, DELEG_ROLE_WORKER2).unwrap();
4089
4042
 
4090
- // Real-machine state: display_backend=adaptive, but the existing
4043
+ // Real-machine state: legacy_display_key=adaptive, but the existing
4091
4044
  // `developer` worker carries per-agent layout_window=developer with
4092
4045
  // layout_index=0. macmini-equivalent state shape.
4093
4046
  crate::state::persist::save_runtime_state(
4094
4047
  &team,
4095
4048
  &json!({
4096
4049
  "session_name": "team-iso",
4097
- "display_backend": "adaptive",
4050
+ "legacy_display_key": "adaptive",
4098
4051
  "agents": {
4099
4052
  "developer": {
4100
4053
  "status": "running",
@@ -4159,13 +4112,14 @@ fn e45_add_agent_opens_new_window_does_not_split_into_per_agent_window() {
4159
4112
  /// existing E43+adaptive grouping behaviour is preserved (split into team-w1
4160
4113
  /// as before). E45 only changes behaviour for per-agent windows.
4161
4114
  #[test]
4115
+ #[ignore = "legacy adaptive display layout contract removed; runtime is silent tmux only"]
4162
4116
  fn e45_real_team_w_adaptive_layout_still_groups_into_layout_window() {
4163
4117
  use crate::lifecycle::launch::adaptive_placement_for_agent;
4164
4118
  use crate::model::ids::AgentId;
4165
4119
  use crate::transport::{SessionName, WindowName};
4166
4120
 
4167
4121
  let state = json!({
4168
- "display_backend": "adaptive",
4122
+ "legacy_display_key": "adaptive",
4169
4123
  "session_name": "team-iso",
4170
4124
  "agents": {
4171
4125
  "alpha": {