@team-agent/installer 0.5.97 → 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 (62) hide show
  1. package/Cargo.lock +1 -1
  2. package/Cargo.toml +1 -1
  3. package/crates/team-agent/src/cli/adapters.rs +0 -64
  4. package/crates/team-agent/src/cli/diagnose.rs +0 -16
  5. package/crates/team-agent/src/cli/emit.rs +15 -42
  6. package/crates/team-agent/src/cli/mod.rs +130 -31
  7. package/crates/team-agent/src/cli/spec.rs +7 -10
  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/mod.rs +0 -1
  11. package/crates/team-agent/src/cli/tests/run_delegation.rs +0 -3
  12. package/crates/team-agent/src/cli/tests/verb_validate.rs +0 -1
  13. package/crates/team-agent/src/cli/types.rs +0 -14
  14. package/crates/team-agent/src/compiler/tests.rs +5 -8
  15. package/crates/team-agent/src/compiler.rs +6 -14
  16. package/crates/team-agent/src/lifecycle/launch/add_agent.rs +1 -1
  17. package/crates/team-agent/src/lifecycle/launch/add_agent_state.rs +1 -1
  18. package/crates/team-agent/src/lifecycle/launch/agent_state.rs +5 -25
  19. package/crates/team-agent/src/lifecycle/launch/identity.rs +2 -18
  20. package/crates/team-agent/src/lifecycle/launch/layout.rs +7 -29
  21. package/crates/team-agent/src/lifecycle/launch/leader_context.rs +1 -1
  22. package/crates/team-agent/src/lifecycle/launch/mcp_config.rs +1 -1
  23. package/crates/team-agent/src/lifecycle/launch/ownership.rs +1 -1
  24. package/crates/team-agent/src/lifecycle/launch/pi_mcp.rs +7 -7
  25. package/crates/team-agent/src/lifecycle/launch/quick_start.rs +12 -49
  26. package/crates/team-agent/src/lifecycle/launch/quick_start_transport.rs +3 -9
  27. package/crates/team-agent/src/lifecycle/launch/readiness.rs +1 -1
  28. package/crates/team-agent/src/lifecycle/launch/spawn.rs +2 -61
  29. package/crates/team-agent/src/lifecycle/launch/spec_state.rs +2 -15
  30. package/crates/team-agent/src/lifecycle/launch/state_projection.rs +3 -7
  31. package/crates/team-agent/src/lifecycle/launch/worker_env.rs +1 -1
  32. package/crates/team-agent/src/lifecycle/launch.rs +3 -4
  33. package/crates/team-agent/src/lifecycle/mod.rs +0 -8
  34. package/crates/team-agent/src/lifecycle/restart/agent.rs +62 -8
  35. package/crates/team-agent/src/lifecycle/restart/common.rs +0 -32
  36. package/crates/team-agent/src/lifecycle/restart/rebuild.rs +0 -9
  37. package/crates/team-agent/src/lifecycle/tests/clone_agent_preserves_source_tools.rs +1 -2
  38. package/crates/team-agent/src/lifecycle/tests/clone_fork_copilot_perms_red.rs +0 -1
  39. package/crates/team-agent/src/lifecycle/tests/core.rs +0 -213
  40. package/crates/team-agent/src/lifecycle/tests/issue238_codex_effort_contract.rs +96 -0
  41. package/crates/team-agent/src/lifecycle/tests/lane_ops.rs +1 -0
  42. package/crates/team-agent/src/lifecycle/tests/launch_spawn.rs +31 -77
  43. package/crates/team-agent/src/lifecycle/tests/main_preserved.rs +2 -2
  44. package/crates/team-agent/src/lifecycle/tests/phase_b_contracts.rs +4 -8
  45. package/crates/team-agent/src/lifecycle/tests/phase_golden.rs +7 -7
  46. package/crates/team-agent/src/lifecycle/tests/pi_executable_mcp_red.rs +1 -1
  47. package/crates/team-agent/src/lifecycle/tests/startup_latency_contract.rs +3 -4
  48. package/crates/team-agent/src/lifecycle/tests.rs +1 -2
  49. package/crates/team-agent/src/lifecycle/types.rs +3 -133
  50. package/crates/team-agent/src/model/enums.rs +21 -71
  51. package/crates/team-agent/src/model/spec.rs +5 -20
  52. package/crates/team-agent/src/provider/adapters/codex.rs +2 -2
  53. package/crates/team-agent/src/provider/types.rs +6 -5
  54. package/examples/team.spec.yaml +0 -1
  55. package/package.json +4 -4
  56. package/schemas/team.schema.json +2 -2
  57. package/skills/team-agent/SKILL.md +1 -1
  58. package/skills/team-agent/references/team-agent-operator.md +23 -38
  59. package/crates/team-agent/src/cli/tests/compile.rs +0 -192
  60. package/crates/team-agent/src/lifecycle/display.rs +0 -550
  61. package/crates/team-agent/src/lifecycle/tests/core_034_real_red.rs +0 -884
  62. package/crates/team-agent/src/lifecycle/tests/display_adaptive_red.rs +0 -481
@@ -26,7 +26,7 @@ use std::time::{Duration, Instant};
26
26
 
27
27
  use sha2::{Digest, Sha256};
28
28
 
29
- use crate::model::enums::ProviderEffort;
29
+ use crate::model::enums::{Provider, ProviderEffort};
30
30
  use crate::provider::adapters::pi::{build_pi_command_argv, PiCommandRequest, PiSessionSelector};
31
31
  use crate::provider::{CommandPlan, McpConfig, ProviderError, SessionId};
32
32
 
@@ -559,12 +559,12 @@ pub(crate) fn parse_pi_leader_args(args: &[String]) -> Result<PiLeaderArgs, Prov
559
559
  match flag {
560
560
  "--model" if model.is_none() => model = Some(value.clone()),
561
561
  "--thinking" if effort.is_none() => {
562
- effort = ProviderEffort::parse(value);
563
- if effort.is_none() {
564
- return Err(ProviderError::Command(format!(
565
- "unknown Pi thinking effort {value:?}"
566
- )));
567
- }
562
+ let parsed = ProviderEffort::parse(value).ok_or_else(|| {
563
+ ProviderError::Command(format!("unknown Pi thinking effort {value:?}"))
564
+ })?;
565
+ effort = parsed
566
+ .resolve_for_provider(Provider::Pi)
567
+ .map_err(|reason| ProviderError::Command(reason.to_string()))?;
568
568
  }
569
569
  _ => {
570
570
  return Err(ProviderError::Command(format!(
@@ -4,9 +4,9 @@
4
4
  //! provides:
5
5
  //! - name: quick_start
6
6
  //! what: 由角色目录一键起队
7
- //! - name: quick_start_in_workspace_with_display_and_backend
8
- //! what: 带显示开关与后端选择的起队入口
9
- //! - name: quick_start_with_transport_in_workspace_with_display
7
+ //! - name: quick_start_in_workspace_with_backend
8
+ //! what: 带 transport 后端选择的起队入口
9
+ //! - name: quick_start_with_transport_in_workspace
10
10
  //! what: 起队的实体实现,含 leader pane 校验、层级门与已有 runtime 的早退
11
11
  //! depends:
12
12
  //! - crate::compiler
@@ -29,7 +29,7 @@ use std::path::{Path, PathBuf};
29
29
  use std::process::Command;
30
30
 
31
31
  use crate::lifecycle::*;
32
- use crate::model::enums::{AuthMode, DisplayBackend, PaneLiveness, Provider, ProviderEffort};
32
+ use crate::model::enums::{AuthMode, PaneLiveness, Provider, ProviderEffort};
33
33
  use crate::model::ids::AgentId;
34
34
  use crate::model::yaml::{self, Value};
35
35
  use crate::state::persist::load_runtime_state;
@@ -1042,9 +1042,8 @@ pub(crate) fn quick_start_in_workspace(
1042
1042
  }
1043
1043
 
1044
1044
  /// ---
1045
- /// purpose: 带显示开关与后端字面量的起队入口
1045
+ /// purpose: 带后端字面量的起队入口
1046
1046
  /// params:
1047
- /// open_display: 为假时把 spec 的显示后端改成 none
1048
1047
  /// backend: 未给或为 tmux 时走既有 tmux 路径,其余字面量经 transport 工厂解析
1049
1048
  /// returns: 起队报告
1050
1049
  /// errors: 后端字面量不认识时返回 TeamSelect;工厂拒绝或后端不可用时如实报错,不退回 tmux
@@ -1061,13 +1060,12 @@ pub(crate) fn quick_start_in_workspace(
1061
1060
  /// its spawn/inject/capture calls to `TransportError::MuxUnavailable`
1062
1061
  /// honestly — MUST-NOT-13 + CR C-1 ①. Users see a real "conpty
1063
1062
  /// unavailable" error rather than a silent tmux fallback.
1064
- pub fn quick_start_in_workspace_with_display_and_backend(
1063
+ pub fn quick_start_in_workspace_with_backend(
1065
1064
  workspace: &Path,
1066
1065
  agents_dir: &Path,
1067
1066
  name: Option<&str>,
1068
1067
  yes: bool,
1069
1068
  team_id: Option<&str>,
1070
- open_display: bool,
1071
1069
  backend: Option<&str>,
1072
1070
  ) -> Result<QuickStartReport, LifecycleError> {
1073
1071
  let workspace = explicit_quick_start_workspace(workspace);
@@ -1079,14 +1077,13 @@ pub fn quick_start_in_workspace_with_display_and_backend(
1079
1077
  let is_tmux_or_default = matches!(literal, None | Some("") | Some("tmux") | Some("TMUX"));
1080
1078
  if is_tmux_or_default {
1081
1079
  let transport = quick_start_tmux_backend(&workspace);
1082
- return quick_start_with_transport_in_workspace_with_display(
1080
+ return quick_start_with_transport_in_workspace(
1083
1081
  &workspace,
1084
1082
  agents_dir,
1085
1083
  name,
1086
1084
  yes,
1087
1085
  team_id,
1088
1086
  &transport,
1089
- open_display,
1090
1087
  );
1091
1088
  }
1092
1089
  // Explicit non-tmux backend: route through the factory. Parse the
@@ -1181,14 +1178,13 @@ pub fn quick_start_in_workspace_with_display_and_backend(
1181
1178
  let resolved = crate::transport_factory::resolve_transport(input)
1182
1179
  .map_err(|e| LifecycleError::TeamSelect(e.to_string()))?;
1183
1180
  // Hand the boxed backend down as `&dyn Transport`.
1184
- quick_start_with_transport_in_workspace_with_display(
1181
+ quick_start_with_transport_in_workspace(
1185
1182
  &workspace,
1186
1183
  agents_dir,
1187
1184
  name,
1188
1185
  yes,
1189
1186
  team_id,
1190
1187
  &*resolved.backend,
1191
- open_display,
1192
1188
  )
1193
1189
  }
1194
1190
 
@@ -1209,64 +1205,40 @@ pub(crate) fn quick_start_with_transport(
1209
1205
  quick_start_with_transport_in_workspace(&workspace, agents_dir, name, yes, team_id, transport)
1210
1206
  }
1211
1207
 
1212
- /// ---
1213
- /// purpose: 带注入 transport 与显式 workspace 的起队入口,默认开显示
1214
- /// returns: 起队报告
1215
- /// errors: 透传实体实现的错误
1216
- /// contract_id: lifecycle.quick_start.entry
1217
- /// ---
1218
- pub(crate) fn quick_start_with_transport_in_workspace(
1219
- workspace: &Path,
1220
- agents_dir: &Path,
1221
- name: Option<&str>,
1222
- yes: bool,
1223
- team_id: Option<&str>,
1224
- transport: &dyn Transport,
1225
- ) -> Result<QuickStartReport, LifecycleError> {
1226
- quick_start_with_transport_in_workspace_with_display(
1227
- workspace, agents_dir, name, yes, team_id, transport, true,
1228
- )
1229
- }
1230
-
1231
1208
  /// ---
1232
1209
  /// purpose: 起队的实体实现,校验 leader pane、编译 spec、定团队键、判层级、必要时早退,然后起队并给 attach 指引
1233
- /// params:
1234
- /// open_display: 为假时把显示后端改成 none
1235
1210
  /// returns: 起队报告,含 session 名与 attach 命令
1236
1211
  /// errors: leader pane 环境无效返回 RequirementUnmet;角色目录不存在或编译失败返回 Compile;嵌套层级超限返回 RequirementUnmet;读 state 失败返回 StatePersist
1237
1212
  /// ---
1238
- pub(crate) fn quick_start_with_transport_in_workspace_with_display(
1213
+ pub(crate) fn quick_start_with_transport_in_workspace(
1239
1214
  workspace: &Path,
1240
1215
  agents_dir: &Path,
1241
1216
  name: Option<&str>,
1242
1217
  yes: bool,
1243
1218
  team_id: Option<&str>,
1244
1219
  transport: &dyn Transport,
1245
- open_display: bool,
1246
1220
  ) -> Result<QuickStartReport, LifecycleError> {
1247
1221
  let mut discover = |requested: &str| {
1248
1222
  crate::lifecycle::launch::pi_mcp::pi_model_candidates(requested).map_err(|_| ())
1249
1223
  };
1250
- quick_start_with_transport_in_workspace_with_display_pi_preflight(
1224
+ quick_start_with_transport_in_workspace_pi_preflight(
1251
1225
  workspace,
1252
1226
  agents_dir,
1253
1227
  name,
1254
1228
  yes,
1255
1229
  team_id,
1256
1230
  transport,
1257
- open_display,
1258
1231
  &mut discover,
1259
1232
  )
1260
1233
  }
1261
1234
 
1262
- pub(crate) fn quick_start_with_transport_in_workspace_with_display_pi_preflight(
1235
+ pub(crate) fn quick_start_with_transport_in_workspace_pi_preflight(
1263
1236
  workspace: &Path,
1264
1237
  agents_dir: &Path,
1265
1238
  name: Option<&str>,
1266
1239
  yes: bool,
1267
1240
  team_id: Option<&str>,
1268
1241
  transport: &dyn Transport,
1269
- open_display: bool,
1270
1242
  discover: &mut dyn FnMut(&str) -> Result<Vec<String>, ()>,
1271
1243
  ) -> Result<QuickStartReport, LifecycleError> {
1272
1244
  // B-7 / 036b N38 三行 fail-fast — TEAM_AGENT_LEADER_PANE_ID 主动路径在 quick-start
@@ -1288,9 +1260,6 @@ pub(crate) fn quick_start_with_transport_in_workspace_with_display_pi_preflight(
1288
1260
  let mut spec = crate::compiler::compile_team(agents_dir)
1289
1261
  .map_err(|e| LifecycleError::Compile(e.to_string()))?;
1290
1262
  override_spec_workspace(&mut spec, &workspace);
1291
- if !open_display {
1292
- override_spec_display_backend(&mut spec, "none");
1293
- }
1294
1263
  let explicit_team_key = quick_start_requested_team_key(team_id, name).map(str::to_string);
1295
1264
  let canonical_team_key = explicit_team_key
1296
1265
  .clone()
@@ -1487,7 +1456,7 @@ pub(crate) fn quick_start_with_transport_in_workspace_with_display_pi_preflight(
1487
1456
  team_depth.team_depth,
1488
1457
  )?;
1489
1458
  // Fresh initialization owns this one fail-closed bind attempt. It is
1490
- // independent of display layout, so --no-display never suppresses receiver
1459
+ // independent of worker layout, so receiver binding is always attempted
1491
1460
  // binding. Readiness receives true only after canonical registry readback.
1492
1461
  let _ = take_fresh_bind_refusal();
1493
1462
  launch.leader_receiver_attached = bind_fresh_quick_start_leader(
@@ -1562,18 +1531,12 @@ pub(crate) fn quick_start_with_transport_in_workspace_with_display_pi_preflight(
1562
1531
  ));
1563
1532
  }
1564
1533
  next_actions.extend(attach_commands.iter().cloned());
1565
- let display_backend = state
1566
- .get("display_backend")
1567
- .and_then(serde_json::Value::as_str)
1568
- .unwrap_or("none")
1569
- .to_string();
1570
1534
  quick_start_phase_timer.emit(&workspace, "launch.phase", "completed");
1571
1535
  Ok(QuickStartReport::Ready {
1572
1536
  session_name,
1573
1537
  launch: Box::new(launch),
1574
1538
  next_actions,
1575
1539
  attach_commands,
1576
- display_backend,
1577
1540
  worker_readiness,
1578
1541
  team: state_team_key,
1579
1542
  })
@@ -26,7 +26,7 @@ use std::path::{Path, PathBuf};
26
26
  use std::process::Command;
27
27
 
28
28
  use crate::lifecycle::*;
29
- use crate::model::enums::{AuthMode, DisplayBackend, PaneLiveness, Provider, ProviderEffort};
29
+ use crate::model::enums::{AuthMode, PaneLiveness, Provider, ProviderEffort};
30
30
  use crate::model::ids::AgentId;
31
31
  use crate::model::yaml::{self, Value};
32
32
  use crate::state::persist::load_runtime_state;
@@ -270,18 +270,12 @@ pub(super) fn attach_commands_for_runtime_windows<'a>(
270
270
 
271
271
  /// ---
272
272
  /// purpose: 由本次起的席位推出窗口名
273
- /// returns: 排序去重后的窗口名,席位没有布局窗口时用 agent id
273
+ /// returns: 排序去重后的窗口名,按席位名分配独立窗口
274
274
  /// ---
275
275
  pub(super) fn started_attach_window_names(started: &[StartedAgent]) -> Vec<String> {
276
276
  let mut windows = started
277
277
  .iter()
278
- .map(|started| {
279
- started
280
- .layout_window
281
- .as_ref()
282
- .map(|window| window.as_str().to_string())
283
- .unwrap_or_else(|| started.agent_id.as_str().to_string())
284
- })
278
+ .map(|started| started.agent_id.as_str().to_string())
285
279
  .collect::<Vec<_>>();
286
280
  windows.sort();
287
281
  windows.dedup();
@@ -28,7 +28,7 @@ use std::path::{Path, PathBuf};
28
28
  use std::process::Command;
29
29
 
30
30
  use crate::lifecycle::*;
31
- use crate::model::enums::{AuthMode, DisplayBackend, PaneLiveness, Provider, ProviderEffort};
31
+ use crate::model::enums::{AuthMode, PaneLiveness, Provider, ProviderEffort};
32
32
  use crate::model::ids::AgentId;
33
33
  use crate::model::yaml::{self, Value};
34
34
  use crate::state::persist::load_runtime_state;
@@ -30,7 +30,7 @@ use std::process::Command;
30
30
 
31
31
  use crate::lifecycle::profile_launch::parse_provider;
32
32
  use crate::lifecycle::*;
33
- use crate::model::enums::{AuthMode, DisplayBackend, PaneLiveness, Provider, ProviderEffort};
33
+ use crate::model::enums::{AuthMode, PaneLiveness, Provider, ProviderEffort};
34
34
  use crate::model::ids::AgentId;
35
35
  use crate::model::yaml::{self, Value};
36
36
  use crate::state::persist::load_runtime_state;
@@ -73,22 +73,6 @@ pub(super) fn spawn_agents(
73
73
  spec.get("runtime").and_then(|v| v.get("fast")),
74
74
  Some(Value::Bool(true))
75
75
  );
76
- let display_backend = spec_display_backend(spec);
77
- let active_agent_ids = spec_agent_values(spec)
78
- .into_iter()
79
- .filter_map(|agent| {
80
- if agent_is_paused(agent) {
81
- None
82
- } else {
83
- agent.get("id").and_then(Value::as_str).map(AgentId::new)
84
- }
85
- })
86
- .collect::<Vec<_>>();
87
- let layout_plan = if display_backend == DisplayBackend::Adaptive {
88
- adaptive_layout_plan(&active_agent_ids, ADAPTIVE_LAYOUT_MAX_PER_WINDOW)
89
- } else {
90
- Vec::new()
91
- };
92
76
  let mut started = Vec::new();
93
77
  for agent in spec_agent_values(spec) {
94
78
  let Some(agent_id_raw) = agent.get("id").and_then(Value::as_str) else {
@@ -392,15 +376,7 @@ pub(super) fn spawn_agents(
392
376
  );
393
377
  }
394
378
  }
395
- // 0.3.28 Step 4b: replaced the `adaptive_layout_plan` 3-pane tiling
396
- // with Python-parity 1-window-per-agent placement. Window name =
397
- // `agent_id`; first worker creates the session via spawn_first,
398
- // subsequent workers each get a new window via spawn_into. No splits
399
- // in the worker session — Step 8's `assert_overlay_call_site` would
400
- // catch any drift if a split call snuck back in. The `placement`
401
- // variable is set to None to signal "no adaptive layout" to all
402
- // downstream consumers (display dict, layout_window persistence).
403
- let placement: Option<LayoutPlacement> = None;
379
+ // One independent tmux window per worker; no GUI/display layout.
404
380
  let window = WindowName::new(agent_id_raw);
405
381
  let spawn = if started.is_empty() {
406
382
  transport.spawn_first_with_env_unset(
@@ -436,35 +412,6 @@ pub(super) fn spawn_agents(
436
412
  if matches!(transport.liveness(&spawn.pane_id), Ok(PaneLiveness::Dead)) {
437
413
  continue;
438
414
  }
439
- if placement.is_some() {
440
- configure_adaptive_pane_title(
441
- workspace,
442
- transport,
443
- session_name,
444
- &window,
445
- &spawn.pane_id,
446
- agent_id_raw,
447
- );
448
- }
449
- let display = if placement.is_some() {
450
- WorkerDisplay::Adaptive {
451
- status: DisplayStatus::Opened,
452
- window: Some(spawn.window.clone()),
453
- workspace_window: None,
454
- pane_id: Some(spawn.pane_id.clone()),
455
- pane_title: Some(agent_id_raw.to_string()),
456
- target: Some(spawn.pane_id.as_str().to_string()),
457
- target_worker_session: Some(session_name.as_str().to_string()),
458
- linked_session: None,
459
- leader_session: Some(session_name.clone()),
460
- display_session: None,
461
- fallback: None,
462
- }
463
- } else {
464
- WorkerDisplay::Blocked {
465
- reason: AdaptiveBlockReason::NotImplementedThisPlatform,
466
- }
467
- };
468
415
  started.push(StartedAgent {
469
416
  agent_id,
470
417
  start_mode: StartMode::Fresh,
@@ -479,12 +426,6 @@ pub(super) fn spawn_agents(
479
426
  .clone()
480
427
  .or_else(|| profile_launch.claude_projects_root.clone()),
481
428
  managed_mcp_config: plan.managed_mcp_config || profile_launch.managed_mcp_config,
482
- layout_window: placement
483
- .as_ref()
484
- .map(|placement| placement.layout_window.clone()),
485
- layout_index: placement.as_ref().map(|placement| placement.layout_index),
486
- pane_index: placement.as_ref().map(|placement| placement.pane_index),
487
- display,
488
429
  });
489
430
  }
490
431
  Ok(started)
@@ -60,7 +60,7 @@ use std::path::{Path, PathBuf};
60
60
  use std::process::Command;
61
61
 
62
62
  use crate::lifecycle::*;
63
- use crate::model::enums::{AuthMode, DisplayBackend, PaneLiveness, Provider, ProviderEffort};
63
+ use crate::model::enums::{AuthMode, PaneLiveness, Provider, ProviderEffort};
64
64
  use crate::model::ids::AgentId;
65
65
  use crate::model::yaml::{self, Value};
66
66
  use crate::state::persist::load_runtime_state;
@@ -68,7 +68,6 @@ use crate::transport::{PaneId, SessionName, Target, Transport, WindowName};
68
68
 
69
69
  use crate::lifecycle::lock::{acquire_agent_lifecycle_lock, LifecycleLockRequest};
70
70
 
71
- use super::identity::spec_display_backend;
72
71
  use super::leader_context::{caller_provider_for_seed_with_lookup, seed_unbound_launched_owner};
73
72
  use super::worker_env::spawn_timestamp;
74
73
 
@@ -77,7 +76,7 @@ use super::worker_env::spawn_timestamp;
77
76
  /// params:
78
77
  /// team_key: 本团队的 runtime 键
79
78
  /// scoped_endpoint: 本次起队已选中的 transport tmux endpoint,与 fresh bind 同源
80
- /// returns: 含 spec_path、workspace、team_dir、session_name、leader、agents、tasks 与 display_backend 的 state;随后按环境种入 launched owner,种不到则种一份 unbound owner
79
+ /// returns: 含 spec_path、workspace、team_dir、session_name、leader、agents、tasks 的 state;随后按环境种入 launched owner,种不到则种一份 unbound owner
81
80
  /// ---
82
81
  pub(super) fn initial_runtime_state(
83
82
  spec: &Value,
@@ -113,7 +112,6 @@ pub(super) fn initial_runtime_state(
113
112
  }
114
113
  agents.insert(id.to_string(), value);
115
114
  }
116
- let display_backend = spec_display_backend(spec);
117
115
  let mut state = serde_json::Map::new();
118
116
  state.insert(
119
117
  "spec_path".to_string(),
@@ -140,10 +138,6 @@ pub(super) fn initial_runtime_state(
140
138
  );
141
139
  state.insert("agents".to_string(), serde_json::Value::Object(agents));
142
140
  state.insert("tasks".to_string(), spec_tasks_json(spec));
143
- state.insert(
144
- "display_backend".to_string(),
145
- serde_json::json!(display_backend),
146
- );
147
141
  state.insert("is_external_leader".to_string(), serde_json::json!(false));
148
142
  let mut state = serde_json::Value::Object(state);
149
143
  if !seed_launched_owner_from_env(&mut state, scoped_endpoint) {
@@ -377,13 +371,6 @@ pub(crate) fn override_spec_workspace(spec: &mut Value, workspace: &Path) {
377
371
  }
378
372
  }
379
373
 
380
- /// ---
381
- /// purpose: 就地把 spec 的 runtime.display_backend 改成给定值
382
- /// ---
383
- pub(super) fn override_spec_display_backend(spec: &mut Value, display_backend: &str) {
384
- override_spec_runtime_str(spec, "display_backend", display_backend);
385
- }
386
-
387
374
  /// ---
388
375
  /// purpose: 就地写 spec 的 runtime 段下某个字符串字段
389
376
  /// params:
@@ -35,11 +35,11 @@ use std::process::Command;
35
35
  use crate::lifecycle::lock::{acquire_agent_lifecycle_lock, LifecycleLockRequest};
36
36
  use crate::lifecycle::profile_launch::parse_provider;
37
37
  use crate::lifecycle::*;
38
- use crate::model::enums::{AuthMode, DisplayBackend, PaneLiveness, Provider, ProviderEffort};
38
+ use crate::model::enums::{AuthMode, PaneLiveness, Provider, ProviderEffort};
39
39
  use crate::model::ids::AgentId;
40
40
  use crate::model::yaml::{self, Value};
41
41
  use crate::state::persist::load_runtime_state;
42
- use crate::transport::{PaneId, SessionName, Target, Transport, WindowName};
42
+ use crate::transport::{PaneId, SessionName, Target, Transport};
43
43
 
44
44
  use super::agent_state::running_agent_state;
45
45
  use super::identity::{explicit_active_team_key, runtime_team_key_for_spec};
@@ -132,11 +132,7 @@ pub(super) fn persist_spawn_agent_state(
132
132
  continue;
133
133
  }
134
134
  let started_agent = started.iter().find(|agent| agent.agent_id.as_str() == id);
135
- let window = started_agent
136
- .and_then(|started| started.layout_window.as_ref())
137
- .map(WindowName::as_str)
138
- .or_else(|| agent.get("window").and_then(Value::as_str))
139
- .unwrap_or(id);
135
+ let window = agent.get("window").and_then(Value::as_str).unwrap_or(id);
140
136
  if !live_started_agents.contains(id)
141
137
  || (!live_windows.is_empty() && !live_windows.contains(window))
142
138
  {
@@ -28,7 +28,7 @@ use std::path::{Path, PathBuf};
28
28
  use std::process::Command;
29
29
 
30
30
  use crate::lifecycle::*;
31
- use crate::model::enums::{AuthMode, DisplayBackend, PaneLiveness, Provider, ProviderEffort};
31
+ use crate::model::enums::{AuthMode, PaneLiveness, Provider, ProviderEffort};
32
32
  use crate::model::ids::AgentId;
33
33
  use crate::model::yaml::{self, Value};
34
34
  use crate::state::persist::load_runtime_state;
@@ -15,7 +15,7 @@ use std::collections::{BTreeMap, BTreeSet};
15
15
  use std::path::{Path, PathBuf};
16
16
  use std::process::Command;
17
17
 
18
- use crate::model::enums::{AuthMode, DisplayBackend, PaneLiveness, Provider, ProviderEffort};
18
+ use crate::model::enums::{AuthMode, PaneLiveness, Provider, ProviderEffort};
19
19
  use crate::model::ids::AgentId;
20
20
  use crate::model::yaml::{self, Value};
21
21
  use crate::state::persist::load_runtime_state;
@@ -370,10 +370,9 @@ pub(crate) use identity::{
370
370
 
371
371
  mod quick_start;
372
372
  pub(super) use quick_start::*;
373
- pub use quick_start::{quick_start, quick_start_in_workspace_with_display_and_backend};
373
+ pub use quick_start::{quick_start, quick_start_in_workspace_with_backend};
374
374
  pub(crate) use quick_start::{
375
375
  quick_start_in_workspace, quick_start_with_transport, quick_start_with_transport_in_workspace,
376
- quick_start_with_transport_in_workspace_with_display,
377
376
  };
378
377
 
379
378
  mod quick_start_transport;
@@ -426,7 +425,7 @@ pub(crate) use spec_state::{
426
425
  };
427
426
  use spec_state::{
428
427
  env_nonempty, has_positive_caller_leader_env, initial_runtime_state,
429
- override_spec_display_backend, override_spec_runtime_str, seed_launched_owner_from_env,
428
+ override_spec_runtime_str, seed_launched_owner_from_env,
430
429
  spec_agent_values, spec_agents, spec_default_assignee, spec_routes, spec_session_name,
431
430
  spec_tasks_json, team_workspace, yaml_value_to_json,
432
431
  };
@@ -20,8 +20,6 @@
20
20
  //! (`stop_agent`/`reset_agent`/`add_agent`/`fork_agent`)、`lifecycle/agents.py`
21
21
  //! (`remove_agent` + `_RemoveRollback`)、`lifecycle/paste_buffer_hygiene.py`。
22
22
  //! - `restart/orchestration.py`(`restart` Route B)、`restart/selection.py`、`restart/snapshot.py`。
23
- //! - `display/backend.py`/`adaptive.py`/`tiling.py`/`workspace.py`/`worker_window.py`/
24
- //! `ghostty.py`/`close.py`/`rebuild.py`。
25
23
  //! - `orchestrator/__init__.py`/`plan.py`/`state.py`(plan 多 stage 状态机)。
26
24
  //! - `diagnose/quick_start.py`(`quick_start`/`prepare_quick_start_team`/`wait_ready`)。
27
25
  //!
@@ -52,7 +50,6 @@
52
50
  // (unimplemented!() stub 不被拦);tests 子模块各自 allow。
53
51
  #![deny(clippy::unwrap_used, clippy::expect_used, clippy::panic)]
54
52
 
55
- pub mod display;
56
53
  pub mod helpers;
57
54
  pub mod launch;
58
55
  pub(crate) mod lock;
@@ -70,12 +67,7 @@ use crate::model::ids::AgentId;
70
67
  use crate::provider::{RolloutPath, SessionId};
71
68
  use crate::transport::{PaneId, SessionName, WindowName};
72
69
 
73
- // 复用既有 enum(model::enums)。`DisplayBackend` 已在 step2 定义并带 `has_worker_views()`。
74
- pub use crate::model::enums::DisplayBackend;
75
-
76
70
  pub use types::*;
77
-
78
- pub use display::*;
79
71
  pub use launch::*;
80
72
  pub use restart::*;
81
73
 
@@ -228,16 +228,19 @@ pub(crate) fn start_agent_at_paths(
228
228
  // spawn (defensive). The check itself remains a no-op on healthy
229
229
  // state — assert_topology_invariants from Step 1 catches the
230
230
  // upstream corruption.
231
- let has_collision = pane_conflicts_with_leader_or_other(&state, agent_id, &raw_agent);
232
- if has_collision && noop_pane.is_none() {
231
+ let noop_pane_conflict = noop_pane.as_ref().is_some_and(|pane| {
232
+ pane_conflicts_with_pane_id(&state, agent_id, &raw_agent, pane.pane_id.as_str())
233
+ });
234
+ let has_collision = pane_conflicts_with_leader_or_other(&state, agent_id, &raw_agent)
235
+ || noop_pane_conflict;
236
+ if has_collision {
233
237
  eprintln!(
234
- "team_agent::layout e51_collision_post_step2 agent_id=`{agent_id}` \
238
+ "team_agent::layout pane_collision agent_id=`{agent_id}` \
235
239
  action=forcing_fresh_spawn \
236
- (should be impossible after Step 2 leader/worker session separation; \
237
- investigate upstream state corruption)"
240
+ (the persisted or discovered pane is owned by another live seat)"
238
241
  );
239
242
  }
240
- let agent_live = agent_live && (!has_collision || noop_pane.is_some());
243
+ let agent_live = agent_live && !has_collision;
241
244
  // 0.5.66 bypass 单源 §2.6 — dangerously_skip_permissions drift 挡板:
242
245
  // Noop 路径不 spawn,若声明面(角色 md / spec)与运行面(as-launched)不一致,
243
246
  // 拒 Noop 报明,防"改了 md 却以为生效、进程仍带旧 bypass 跑着"的暗门。
@@ -678,6 +681,15 @@ fn pane_conflicts_with_leader_or_other(
678
681
  else {
679
682
  return false;
680
683
  };
684
+ pane_conflicts_with_pane_id(state, agent_id, agent, pane_id)
685
+ }
686
+
687
+ fn pane_conflicts_with_pane_id(
688
+ state: &serde_json::Value,
689
+ agent_id: &crate::model::ids::AgentId,
690
+ agent: &serde_json::Value,
691
+ pane_id: &str,
692
+ ) -> bool {
681
693
  let state_socket = runtime_tmux_socket(state);
682
694
  let agent_socket = tmux_socket_field(agent).or(state_socket);
683
695
  // Check leader anchor.
@@ -693,7 +705,12 @@ fn pane_conflicts_with_leader_or_other(
693
705
  // Check other agents.
694
706
  if let Some(agents) = state.get("agents").and_then(serde_json::Value::as_object) {
695
707
  for (id, other) in agents {
696
- if id == agent_id.as_str() {
708
+ if id == agent_id.as_str()
709
+ || matches!(
710
+ other.get("status").and_then(serde_json::Value::as_str),
711
+ Some("stopped" | "paused" | "failed" | "removed")
712
+ )
713
+ {
697
714
  continue;
698
715
  }
699
716
  let other_socket = tmux_socket_field(other).or(state_socket);
@@ -1165,7 +1182,6 @@ pub(super) fn stop_agent_at_paths(
1165
1182
  );
1166
1183
  let _ = write_stop_drain_event(workspace, agent_id, &target_str, &drain_evidence);
1167
1184
  }
1168
- close_agent_display(&mut state, agent_id);
1169
1185
  mark_agent_stopped(&mut state, agent_id, agent, &window)?;
1170
1186
  let team_key = restart_projection_team_key(&state, team);
1171
1187
  // golden operations.py:95: save_team_scoped_state (team projection) — NOT a raw save, so a
@@ -2041,6 +2057,44 @@ mod tests {
2041
2057
  );
2042
2058
  }
2043
2059
 
2060
+ #[test]
2061
+ fn e51_restart_rejects_discovered_peer_pane_for_stale_worker() {
2062
+ let socket = "/private/tmp/tmux-501/ta-worker";
2063
+ let state = serde_json::json!({
2064
+ "tmux_endpoint": socket,
2065
+ "agents": {
2066
+ "worker": {"pane_id": "%1", "status": "running"},
2067
+ "peer": {"pane_id": "%0", "status": "running"}
2068
+ }
2069
+ });
2070
+ let agent_id = AgentId::new("worker");
2071
+ let agent = state["agents"]["worker"].clone();
2072
+
2073
+ assert!(
2074
+ pane_conflicts_with_pane_id(&state, &agent_id, &agent, "%0"),
2075
+ "a discovered live pane owned by a running peer must not be rebound"
2076
+ );
2077
+ }
2078
+
2079
+ #[test]
2080
+ fn e51_restart_ignores_stopped_peer_pane_residue() {
2081
+ let socket = "/private/tmp/tmux-501/ta-worker";
2082
+ let state = serde_json::json!({
2083
+ "tmux_endpoint": socket,
2084
+ "agents": {
2085
+ "worker": {"pane_id": "%1", "status": "running"},
2086
+ "peer": {"pane_id": "%0", "status": "stopped"}
2087
+ }
2088
+ });
2089
+ let agent_id = AgentId::new("worker");
2090
+ let agent = state["agents"]["worker"].clone();
2091
+
2092
+ assert!(
2093
+ !pane_conflicts_with_pane_id(&state, &agent_id, &agent, "%0"),
2094
+ "stopped peer residue must not claim a pane for a live restart"
2095
+ );
2096
+ }
2097
+
2044
2098
  // 0.5.66 bypass 单源 §4.1:Noop drift 检测——声明面与运行面不一致 → drift。
2045
2099
  #[test]
2046
2100
  fn test_restart_noop_denied_on_drift() {
@@ -2047,38 +2047,6 @@ pub(super) fn window_exists(
2047
2047
  }
2048
2048
  }
2049
2049
 
2050
- /// ---
2051
- /// purpose: 在 state 里把该席位的显示标记为已关
2052
- /// params:
2053
- /// state: 就地改写;只有 ghostty 工作区后端会被改写状态与标题,其余后端不动
2054
- /// ---
2055
- pub(super) fn close_agent_display(state: &mut serde_json::Value, agent_id: &AgentId) {
2056
- let Some(display) = state
2057
- .get_mut("agents")
2058
- .and_then(|v| v.as_object_mut())
2059
- .and_then(|agents| agents.get_mut(agent_id.as_str()))
2060
- .and_then(|agent| agent.get_mut("display"))
2061
- .and_then(|display| display.as_object_mut())
2062
- else {
2063
- return;
2064
- };
2065
- let backend = display
2066
- .get("backend")
2067
- .and_then(|v| v.as_str())
2068
- .unwrap_or("")
2069
- .to_string();
2070
- // golden operations.py:88-92: close_ghostty_display (display/close.py:17-48) mutates NOTHING in the
2071
- // persisted state for a ghostty_window; only the ghostty_workspace slot is relabeled
2072
- // (close.py:84-85: status="stopped", pane_title=f"stopped: {agent_id}") and re-assigned back.
2073
- if backend == "ghostty_workspace" {
2074
- display.insert("status".to_string(), serde_json::json!("stopped"));
2075
- display.insert(
2076
- "pane_title".to_string(),
2077
- serde_json::json!(format!("stopped: {}", agent_id.as_str())),
2078
- );
2079
- }
2080
- }
2081
-
2082
2050
  /// ---
2083
2051
  /// purpose: 丢弃该席位的会话捕获字段并标记为已停
2084
2052
  /// params: