@team-agent/installer 0.5.50 → 0.5.52

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 (93) hide show
  1. package/Cargo.lock +1 -1
  2. package/Cargo.toml +1 -1
  3. package/crates/team-agent/src/cli/adapters.rs +5 -3
  4. package/crates/team-agent/src/cli/emit.rs +2 -1
  5. package/crates/team-agent/src/cli/mod.rs +32 -14
  6. package/crates/team-agent/src/cli/named_address.rs +46 -115
  7. package/crates/team-agent/src/cli/send/coordinator.rs +163 -0
  8. package/crates/team-agent/src/cli/send/mailbox.rs +99 -0
  9. package/crates/team-agent/src/cli/send/persist.rs +154 -0
  10. package/crates/team-agent/src/cli/send/presentation.rs +333 -0
  11. package/crates/team-agent/src/cli/send/resolve.rs +361 -0
  12. package/crates/team-agent/src/cli/send.rs +44 -1169
  13. package/crates/team-agent/src/cli/spec.rs +1 -1
  14. package/crates/team-agent/src/cli/status_port/agents.rs +358 -0
  15. package/crates/team-agent/src/cli/status_port/approvals.rs +79 -0
  16. package/crates/team-agent/src/cli/status_port/compact.rs +207 -0
  17. package/crates/team-agent/src/cli/status_port/format.rs +145 -0
  18. package/crates/team-agent/src/cli/status_port/inbox.rs +36 -0
  19. package/crates/team-agent/src/cli/status_port/runtime.rs +195 -0
  20. package/crates/team-agent/src/cli/status_port/snapshot.rs +181 -0
  21. package/crates/team-agent/src/cli/status_port/store.rs +412 -0
  22. package/crates/team-agent/src/cli/status_port/tests.rs +54 -0
  23. package/crates/team-agent/src/cli/status_port.rs +47 -1548
  24. package/crates/team-agent/src/cli/tests/main_preserved.rs +1 -0
  25. package/crates/team-agent/src/cli/tests/run_delegation.rs +1 -0
  26. package/crates/team-agent/src/cli/types.rs +1 -0
  27. package/crates/team-agent/src/coordinator/conpty_shim.rs +34 -30
  28. package/crates/team-agent/src/coordinator/steps/abnormal.rs +135 -13
  29. package/crates/team-agent/src/coordinator/tick.rs +37 -0
  30. package/crates/team-agent/src/db/agent_health_capture.rs +18 -13
  31. package/crates/team-agent/src/db/message_store.rs +154 -44
  32. package/crates/team-agent/src/event_log.rs +73 -0
  33. package/crates/team-agent/src/leader/lease.rs +202 -16
  34. package/crates/team-agent/src/leader/start.rs +28 -4
  35. package/crates/team-agent/src/lifecycle/launch/add_agent.rs +424 -0
  36. package/crates/team-agent/src/lifecycle/launch/add_agent_state.rs +303 -0
  37. package/crates/team-agent/src/lifecycle/launch/agent_state.rs +160 -0
  38. package/crates/team-agent/src/lifecycle/launch/approval.rs +134 -0
  39. package/crates/team-agent/src/lifecycle/launch/fork_agent.rs +492 -0
  40. package/crates/team-agent/src/lifecycle/launch/fork_state.rs +297 -0
  41. package/crates/team-agent/src/lifecycle/launch/identity.rs +372 -0
  42. package/crates/team-agent/src/lifecycle/launch/layout.rs +313 -0
  43. package/crates/team-agent/src/lifecycle/launch/leader_context.rs +478 -0
  44. package/crates/team-agent/src/lifecycle/launch/mcp_config.rs +201 -0
  45. package/crates/team-agent/src/lifecycle/launch/ownership.rs +66 -0
  46. package/crates/team-agent/src/lifecycle/launch/quick_start.rs +477 -0
  47. package/crates/team-agent/src/lifecycle/launch/quick_start_transport.rs +278 -0
  48. package/crates/team-agent/src/lifecycle/launch/readiness.rs +123 -0
  49. package/crates/team-agent/src/lifecycle/launch/spawn.rs +377 -0
  50. package/crates/team-agent/src/lifecycle/launch/spec_state.rs +434 -0
  51. package/crates/team-agent/src/lifecycle/launch/state_projection.rs +499 -0
  52. package/crates/team-agent/src/lifecycle/launch/worker_env.rs +438 -0
  53. package/crates/team-agent/src/lifecycle/launch.rs +119 -5351
  54. package/crates/team-agent/src/lifecycle/restart/agent.rs +63 -29
  55. package/crates/team-agent/src/lifecycle/restart/common.rs +53 -27
  56. package/crates/team-agent/src/lifecycle/restart/rebuild.rs +167 -27
  57. package/crates/team-agent/src/lifecycle/restart/remove.rs +503 -77
  58. package/crates/team-agent/src/lifecycle/restart.rs +26 -2
  59. package/crates/team-agent/src/lifecycle/tests/lane_ops.rs +906 -17
  60. package/crates/team-agent/src/lifecycle/tests/launch_spawn.rs +67 -2
  61. package/crates/team-agent/src/lifecycle/tests/lifecycle_lock.rs +24 -1
  62. package/crates/team-agent/src/mcp_server/lifecycle_tools/agent_ops.rs +30 -7
  63. package/crates/team-agent/src/mcp_server/lifecycle_tools/state_status.rs +8 -4
  64. package/crates/team-agent/src/mcp_server/mod.rs +1 -1
  65. package/crates/team-agent/src/mcp_server/tests/send.rs +6 -10
  66. package/crates/team-agent/src/mcp_server/tests/tools.rs +18 -1
  67. package/crates/team-agent/src/mcp_server/tests.rs +11 -4
  68. package/crates/team-agent/src/mcp_server/tools.rs +14 -5
  69. package/crates/team-agent/src/messaging/activity.rs +4 -2
  70. package/crates/team-agent/src/messaging/address.rs +86 -0
  71. package/crates/team-agent/src/messaging/delivery.rs +288 -183
  72. package/crates/team-agent/src/messaging/helpers.rs +17 -13
  73. package/crates/team-agent/src/messaging/leader_channel.rs +171 -0
  74. package/crates/team-agent/src/messaging/leader_receiver.rs +60 -35
  75. package/crates/team-agent/src/messaging/mod.rs +15 -2
  76. package/crates/team-agent/src/messaging/persist.rs +309 -0
  77. package/crates/team-agent/src/messaging/results.rs +16 -24
  78. package/crates/team-agent/src/messaging/scheduler.rs +4 -2
  79. package/crates/team-agent/src/messaging/selftest.rs +19 -12
  80. package/crates/team-agent/src/messaging/send.rs +101 -49
  81. package/crates/team-agent/src/messaging/tests/leader_channel.rs +169 -0
  82. package/crates/team-agent/src/messaging/tests/leader_inject_acceptance.rs +305 -0
  83. package/crates/team-agent/src/messaging/tests/mod.rs +2 -0
  84. package/crates/team-agent/src/messaging/tests/runtime.rs +38 -17
  85. package/crates/team-agent/src/messaging/watchers.rs +13 -3
  86. package/crates/team-agent/src/redaction.rs +72 -2
  87. package/crates/team-agent/src/state/persist.rs +284 -21
  88. package/crates/team-agent/src/state/projection.rs +245 -30
  89. package/crates/team-agent/src/state/repository/intent.rs +13 -0
  90. package/crates/team-agent/src/state/repository/tests.rs +47 -0
  91. package/crates/team-agent/src/state/repository.rs +94 -27
  92. package/crates/team-agent/src/state/selector.rs +9 -18
  93. package/package.json +4 -4
@@ -4,6 +4,23 @@
4
4
  //! `spawn_first_with_env_unset` and `spawn_into_with_env_unset` from
5
5
  //! launch.rs:376-405 here.
6
6
 
7
+ use std::collections::{BTreeMap, BTreeSet};
8
+ use std::path::{Path, PathBuf};
9
+ use std::process::Command;
10
+
11
+ use crate::lifecycle::profile_launch::parse_provider;
12
+ use crate::lifecycle::*;
13
+ use crate::model::enums::{AuthMode, DisplayBackend, PaneLiveness, Provider, ProviderEffort};
14
+ use crate::model::ids::AgentId;
15
+ use crate::model::permissions::{self, AgentPermissionInput};
16
+ use crate::model::yaml::{self, Value};
17
+ use crate::state::persist::load_runtime_state;
18
+ use crate::transport::{PaneId, SessionName, Target, Transport, WindowName};
19
+
20
+ use crate::lifecycle::lock::{acquire_agent_lifecycle_lock, LifecycleLockRequest};
21
+
22
+ use super::*;
23
+
7
24
  #[derive(Debug, Clone, Copy, PartialEq, Eq)]
8
25
  pub enum SpawnPhase {
9
26
  SpawnFirstWithEnvUnset,
@@ -30,3 +47,363 @@ mod tests {
30
47
  .starts_with("launch.spawn."));
31
48
  }
32
49
  }
50
+
51
+ pub(super) fn spawn_agents(
52
+ workspace: &Path,
53
+ spec_path: &Path,
54
+ spec: &Value,
55
+ session_name: &SessionName,
56
+ safety: &DangerousApproval,
57
+ transport: &dyn Transport,
58
+ ) -> Result<Vec<StartedAgent>, LifecycleError> {
59
+ // E5 解耦:team_dir(角色定义 + profiles 所在)≠ spec_path.parent()(spec 已迁出到 .team/runtime)。
60
+ // 优先取 state.team_dir(角色目录),回落 spec_path.parent()(legacy 同目录布局)。
61
+ let team_dir_buf = crate::state::persist::load_runtime_state(workspace)
62
+ .ok()
63
+ .and_then(|state| {
64
+ state
65
+ .get("team_dir")
66
+ .and_then(serde_json::Value::as_str)
67
+ .filter(|s| !s.is_empty())
68
+ .map(PathBuf::from)
69
+ });
70
+ let team_dir = team_dir_buf
71
+ .as_deref()
72
+ .unwrap_or_else(|| spec_path.parent().unwrap_or_else(|| Path::new(".")));
73
+ let runtime_fast = matches!(
74
+ spec.get("runtime").and_then(|v| v.get("fast")),
75
+ Some(Value::Bool(true))
76
+ );
77
+ let display_backend = spec_display_backend(spec);
78
+ let active_agent_ids = spec_agent_values(spec)
79
+ .into_iter()
80
+ .filter_map(|agent| {
81
+ if agent_is_paused(agent) {
82
+ None
83
+ } else {
84
+ agent.get("id").and_then(Value::as_str).map(AgentId::new)
85
+ }
86
+ })
87
+ .collect::<Vec<_>>();
88
+ let layout_plan = if display_backend == DisplayBackend::Adaptive {
89
+ adaptive_layout_plan(&active_agent_ids, ADAPTIVE_LAYOUT_MAX_PER_WINDOW)
90
+ } else {
91
+ Vec::new()
92
+ };
93
+ let mut started = Vec::new();
94
+ for agent in spec_agent_values(spec) {
95
+ let Some(agent_id_raw) = agent.get("id").and_then(Value::as_str) else {
96
+ continue;
97
+ };
98
+ if agent_is_paused(agent) {
99
+ continue;
100
+ }
101
+ let agent_id = AgentId::new(agent_id_raw);
102
+ let provider = agent
103
+ .get("provider")
104
+ .and_then(Value::as_str)
105
+ .and_then(parse_provider)
106
+ .unwrap_or(Provider::Codex);
107
+ let auth_mode = agent
108
+ .get("auth_mode")
109
+ .and_then(Value::as_str)
110
+ .and_then(parse_auth_mode)
111
+ .unwrap_or(AuthMode::Subscription);
112
+ let model = agent.get("model").and_then(Value::as_str);
113
+ let adapter = crate::provider::get_adapter(provider);
114
+ // Contract C / F6.4: pass the COMPILED agent context (resolved role/system prompt,
115
+ // tools list, per-worker MCP config) into command construction so a real worker
116
+ // has both the role instruction AND the callable Team Agent MCP capability.
117
+ // probe5 RED proved that `build_command(.., None, None, ..)` left the worker
118
+ // without `report_result`; placeholders are substituted at spawn time.
119
+ let command_agent = crate::lifecycle::worker_command_context::WorkerCommandAgent::from_yaml(
120
+ agent,
121
+ Some(agent_id_raw),
122
+ provider,
123
+ );
124
+ let system_prompt =
125
+ crate::lifecycle::worker_command_context::compile_worker_system_prompt(&command_agent)?;
126
+ let tools = crate::lifecycle::worker_command_context::resolved_tool_strings_for_command(
127
+ &command_agent,
128
+ provider,
129
+ safety,
130
+ )?;
131
+ let resolved_tool_refs: Vec<&str> = tools.iter().map(String::as_str).collect();
132
+ let mcp_team_id = runtime_team_key_for_spec(spec_path, spec, session_name);
133
+ let mcp_config = adapter
134
+ .mcp_config(auth_mode)
135
+ .map_err(|e| LifecycleError::Provider(e.to_string()))?;
136
+ let mcp_config = resolve_mcp_config(mcp_config, workspace, agent_id_raw, &mcp_team_id);
137
+ let mcp_config_path = write_worker_mcp_config_for_provider(
138
+ workspace,
139
+ agent_id_raw,
140
+ &mcp_config,
141
+ Some(provider),
142
+ )?;
143
+ let profile_dir = team_dir.join("profiles");
144
+ let profile_launch =
145
+ crate::lifecycle::profile_launch::prepare_provider_profile_launch_with_profile_dir(
146
+ workspace,
147
+ agent_id_raw,
148
+ agent,
149
+ Some(&profile_dir),
150
+ Some(&mcp_config),
151
+ )?;
152
+ let command_model = profile_launch.command_overrides.model.as_deref().or(model);
153
+ // 0.4.x provider effort MVP step 4 + 7: resolve effort and emit
154
+ // unsupported warning event when the spec asked for an effort the
155
+ // provider can't satisfy.
156
+ let agent_effort = provider_effort_for_spawn(agent, provider);
157
+ if let Some(event_value) = provider_effort_event_if_dropped(agent, provider, agent_id_raw) {
158
+ let _ = crate::event_log::EventLog::new(workspace)
159
+ .write("provider.effort_unsupported", event_value);
160
+ }
161
+ let mut plan = adapter
162
+ .build_command_plan(crate::provider::ProviderCommandContext {
163
+ auth_mode,
164
+ mcp_config: Some(&mcp_config),
165
+ system_prompt: Some(system_prompt.as_str()),
166
+ model: command_model,
167
+ tools: &resolved_tool_refs,
168
+ profile_launch: Some(&profile_launch),
169
+ // Layer 1 self-healing (architect probe 2026-06-22): expose
170
+ // agent_id as a display-name hint so Claude / Copilot
171
+ // adapters can pass `--name <agent_id>`. Codex has no
172
+ // equivalent flag and ignores the hint.
173
+ agent_id_hint: Some(agent_id_raw),
174
+ effort: agent_effort,
175
+ })
176
+ .map_err(|e| LifecycleError::Provider(e.to_string()))?;
177
+ if !plan.managed_mcp_config && !profile_launch.managed_mcp_config {
178
+ point_native_mcp_config_at_file(&mut plan.argv, provider, &mcp_config_path);
179
+ }
180
+ // C-A-4 cr verdict v2 — Copilot BYOK(compatible_api)硬性校验:
181
+ // "A model is required for BYOK"(help-providers 原文)。检查 agent
182
+ // 的 model 来源:角色 spec.model > profile COPILOT_MODEL(经 env_overlay)
183
+ // > --model 旗(本 worker 路径不在 argv 后追加用户 --model)。三者全空 → 报错
184
+ // 含 "model" 字面,失败信息透传给 leader。
185
+ if matches!(provider, Provider::Copilot) && auth_mode == AuthMode::CompatibleApi {
186
+ let has_model = model.is_some_and(|s| !s.is_empty())
187
+ || profile_launch
188
+ .command_overrides
189
+ .model
190
+ .as_deref()
191
+ .is_some_and(|s| !s.is_empty())
192
+ || profile_launch
193
+ .env_overlay
194
+ .get("COPILOT_MODEL")
195
+ .is_some_and(|v| !v.is_empty());
196
+ if !has_model {
197
+ return Err(LifecycleError::RequirementUnmet(
198
+ "copilot BYOK profile requires a model (set COPILOT_MODEL, agent.model, or --model)"
199
+ .to_string(),
200
+ ));
201
+ }
202
+ }
203
+ // §B1 + C-7-1 + C-6-2 + C-3-2 cr verdict v2 — Copilot launch-time argv 注入:
204
+ // -n <agent_id> 会话命名(main-help:104)→ resume-by-name + 人查 双键
205
+ // -C <workspace> 双保险 cwd(main-help:55-56),防 shell 包装意外
206
+ // --log-dir <path> per-worker 定向日志(help-logging)→ 故障期可读 + N18 隔离
207
+ // --log-level info 配套日志级别
208
+ // --disable-mcp-server <n>... C-3-2 残留 MCP server 按名禁(扫 mcp list)
209
+ if matches!(provider, Provider::Copilot) {
210
+ plan.argv.push("-n".to_string());
211
+ plan.argv.push(agent_id_raw.to_string());
212
+ plan.argv.push("-C".to_string());
213
+ plan.argv.push(workspace.to_string_lossy().to_string());
214
+ let log_dir = workspace
215
+ .join(".team")
216
+ .join("logs")
217
+ .join("copilot")
218
+ .join(agent_id_raw);
219
+ std::fs::create_dir_all(&log_dir)
220
+ .map_err(|e| LifecycleError::StatePersist(format!("{}: {e}", log_dir.display())))?;
221
+ plan.argv.push("--log-dir".to_string());
222
+ plan.argv.push(log_dir.to_string_lossy().to_string());
223
+ plan.argv.push("--log-level".to_string());
224
+ plan.argv.push("info".to_string());
225
+ // C-3-2/C-3-3 cr verdict v2 — spawn 前扫 `copilot mcp list` 找用户全局/
226
+ // workspace 的 MCP 残留,对每个非 team_orchestrator server 追加
227
+ // --disable-mcp-server <name>,并落 mcp-residual.txt + event。
228
+ apply_copilot_mcp_residual_disables(
229
+ &workspace,
230
+ agent_id_raw,
231
+ &mut plan.argv,
232
+ &log_dir,
233
+ )?;
234
+ }
235
+ fill_spawn_placeholders_full(&mut plan.argv, workspace, agent_id_raw, Some(&mcp_team_id));
236
+ let mut env =
237
+ inherited_env_with_team_overrides(workspace, agent_id_raw, Some(&mcp_team_id));
238
+ apply_profile_launch_env(&mut env, &profile_launch);
239
+ apply_mcp_auto_approval_env(&mut env, &safety);
240
+ // Python providers.py:145 + launch/core.py:253 — fresh launch runs the worker
241
+ // with cwd=workspace, same as the RS fork/add and restart paths.
242
+ let env_unset = crate::layout::worker_env::isolate_worker_spawn_env(
243
+ provider,
244
+ &mut env,
245
+ extend_worker_env_unset_for_effort(
246
+ profile_launch.env_unset.iter().cloned().collect(),
247
+ provider,
248
+ ),
249
+ );
250
+ // BUG / C-1-2 / C-6-1 cr verdict — Copilot system_prompt 走 spawn env overlay +
251
+ // per-worker AGENTS.md(B2 灵魂件降级):写
252
+ // <workspace>/.team/runtime/copilot-instructions/<agent_id>/AGENTS.md
253
+ // 全文 == compile_worker_system_prompt 输出,并通过 spawn env
254
+ // `COPILOT_CUSTOM_INSTRUCTIONS_DIRS=<该目录>` 让 copilot CLI 加载。
255
+ // **禁** silent 写 ~/.copilot/AGENTS.md(C-1-2)+ **禁** -i 作首条消息(C-1-5)。
256
+ if matches!(provider, Provider::Copilot) {
257
+ apply_copilot_instructions_overlay(
258
+ workspace,
259
+ agent_id_raw,
260
+ system_prompt.as_str(),
261
+ &mut env,
262
+ )?;
263
+ // C-A-6 cr verdict v2 — Copilot worker env 全量继承下,用户 shell 的
264
+ // COPILOT_GITHUB_TOKEN / GH_TOKEN / GITHUB_TOKEN 会穿透 + 按 cmd-login 实证
265
+ // **优先于凭据库**(可能静默改变 auth 通道)。一期只观测不剥除(剥除是
266
+ // 行为变更,cr 裁);命中任一就发 warn event 让 user 可见。
267
+ let mut passthrough: Vec<String> = Vec::new();
268
+ for key in ["COPILOT_GITHUB_TOKEN", "GH_TOKEN", "GITHUB_TOKEN"] {
269
+ if env.get(key).is_some_and(|v| !v.is_empty()) {
270
+ passthrough.push(key.to_string());
271
+ }
272
+ }
273
+ if !passthrough.is_empty() {
274
+ let event_log = crate::event_log::EventLog::new(workspace);
275
+ let _ = event_log.write(
276
+ "provider.copilot.token_passthrough_warning",
277
+ serde_json::json!({
278
+ "agent_id": agent_id_raw,
279
+ "tokens": passthrough,
280
+ "reason": "user shell GITHUB_TOKEN family takes precedence over copilot credential store (cmd-login)",
281
+ }),
282
+ );
283
+ }
284
+ }
285
+ let spawn_epoch = u64::try_from(started.len()).unwrap_or(u64::MAX);
286
+ let spawned_at = spawn_timestamp_for_agent(u32::try_from(spawn_epoch).unwrap_or(u32::MAX));
287
+ // E6 层1 实证3 + 诊断留痕:落最终 worker argv(spawn 前的真实形态)。
288
+ // 任何"--session-id 预定 UUID 没生效"必须能从 events.jsonl 回答:argv 里到底有没有它。
289
+ // 抽出 --session-id 值单列,方便和盘上 ~/.claude/projects/<cwd> 实际落的 UUID 对账。
290
+ {
291
+ let session_id_in_argv = plan
292
+ .argv
293
+ .iter()
294
+ .position(|a| a == "--session-id")
295
+ .and_then(|i| plan.argv.get(i + 1))
296
+ .cloned();
297
+ let event_log = crate::event_log::EventLog::new(workspace);
298
+ let _ = event_log.write(
299
+ crate::event_log::PROVIDER_WORKER_SPAWN_ARGV,
300
+ crate::event_log::provider_worker_spawn_argv_fields(serde_json::json!({
301
+ "agent_id": agent_id_raw,
302
+ "provider": provider,
303
+ "argv": plan.argv,
304
+ "session_id_in_argv": session_id_in_argv,
305
+ "expected_session_id": plan.expected_session_id.as_ref().map(|s| s.as_str()),
306
+ "spawn_cwd": workspace.to_string_lossy(),
307
+ "spawned_at": spawned_at.as_str(),
308
+ "source": "launch",
309
+ "spawn_epoch": spawn_epoch,
310
+ })),
311
+ );
312
+ }
313
+ // 0.3.28 Step 4b: replaced the `adaptive_layout_plan` 3-pane tiling
314
+ // with Python-parity 1-window-per-agent placement. Window name =
315
+ // `agent_id`; first worker creates the session via spawn_first,
316
+ // subsequent workers each get a new window via spawn_into. No splits
317
+ // in the worker session — Step 8's `assert_overlay_call_site` would
318
+ // catch any drift if a split call snuck back in. The `placement`
319
+ // variable is set to None to signal "no adaptive layout" to all
320
+ // downstream consumers (display dict, layout_window persistence).
321
+ let placement: Option<LayoutPlacement> = None;
322
+ let window = WindowName::new(agent_id_raw);
323
+ let spawn = if started.is_empty() {
324
+ transport.spawn_first_with_env_unset(
325
+ session_name,
326
+ &window,
327
+ &plan.argv,
328
+ workspace,
329
+ &env,
330
+ &env_unset,
331
+ )
332
+ } else {
333
+ transport.spawn_into_with_env_unset(
334
+ session_name,
335
+ &window,
336
+ &plan.argv,
337
+ workspace,
338
+ &env,
339
+ &env_unset,
340
+ )
341
+ }
342
+ .map_err(|e| LifecycleError::Transport(e.to_string()))?;
343
+ let _ = adapter.handle_startup_prompts(
344
+ transport,
345
+ &Target::Pane(spawn.pane_id.clone()),
346
+ 30,
347
+ 0.5,
348
+ );
349
+ // Python launch/core.py:235-237 — runtime.fast toggles the provider's fast mode
350
+ // after spawn; provider specifics live behind the adapter (F032).
351
+ if runtime_fast {
352
+ let _ = adapter.enable_fast_mode(transport, &Target::Pane(spawn.pane_id.clone()));
353
+ }
354
+ if matches!(transport.liveness(&spawn.pane_id), Ok(PaneLiveness::Dead)) {
355
+ continue;
356
+ }
357
+ if placement.is_some() {
358
+ configure_adaptive_pane_title(
359
+ workspace,
360
+ transport,
361
+ session_name,
362
+ &window,
363
+ &spawn.pane_id,
364
+ agent_id_raw,
365
+ );
366
+ }
367
+ let display = if placement.is_some() {
368
+ WorkerDisplay::Adaptive {
369
+ status: DisplayStatus::Opened,
370
+ window: Some(spawn.window.clone()),
371
+ workspace_window: None,
372
+ pane_id: Some(spawn.pane_id.clone()),
373
+ pane_title: Some(agent_id_raw.to_string()),
374
+ target: Some(spawn.pane_id.as_str().to_string()),
375
+ target_worker_session: Some(session_name.as_str().to_string()),
376
+ linked_session: None,
377
+ leader_session: Some(session_name.clone()),
378
+ display_session: None,
379
+ fallback: None,
380
+ }
381
+ } else {
382
+ WorkerDisplay::Blocked {
383
+ reason: AdaptiveBlockReason::NotImplementedThisPlatform,
384
+ }
385
+ };
386
+ started.push(StartedAgent {
387
+ agent_id,
388
+ start_mode: StartMode::Fresh,
389
+ target: spawn.pane_id.as_str().to_string(),
390
+ spawned_at,
391
+ session_id: None,
392
+ rollout_path: None,
393
+ pending_session_id: plan.expected_session_id.clone(),
394
+ claude_config_dir: profile_launch.claude_config_dir.clone(),
395
+ provider_projects_root: plan
396
+ .provider_projects_root
397
+ .clone()
398
+ .or_else(|| profile_launch.claude_projects_root.clone()),
399
+ managed_mcp_config: plan.managed_mcp_config || profile_launch.managed_mcp_config,
400
+ layout_window: placement
401
+ .as_ref()
402
+ .map(|placement| placement.layout_window.clone()),
403
+ layout_index: placement.as_ref().map(|placement| placement.layout_index),
404
+ pane_index: placement.as_ref().map(|placement| placement.pane_index),
405
+ display,
406
+ });
407
+ }
408
+ Ok(started)
409
+ }