@team-agent/installer 0.5.73 → 0.5.75

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 (38) hide show
  1. package/Cargo.lock +1 -1
  2. package/Cargo.toml +1 -1
  3. package/crates/team-agent/src/cli/adapters.rs +19 -13
  4. package/crates/team-agent/src/cli/emit.rs +6 -3
  5. package/crates/team-agent/src/cli/mod.rs +151 -0
  6. package/crates/team-agent/src/cli/tests/leader_watch.rs +28 -0
  7. package/crates/team-agent/src/cli/tests/run_delegation.rs +3 -0
  8. package/crates/team-agent/src/cli/types.rs +6 -0
  9. package/crates/team-agent/src/compiler.rs +14 -1
  10. package/crates/team-agent/src/layout/worker_env.rs +366 -0
  11. package/crates/team-agent/src/leader/lease.rs +334 -53
  12. package/crates/team-agent/src/leader/registry.rs +177 -108
  13. package/crates/team-agent/src/lifecycle/launch/pi_mcp.rs +12 -1
  14. package/crates/team-agent/src/lifecycle/launch/quick_start.rs +2272 -128
  15. package/crates/team-agent/src/lifecycle/launch/quick_start_transport.rs +16 -6
  16. package/crates/team-agent/src/lifecycle/launch/spawn.rs +4 -0
  17. package/crates/team-agent/src/lifecycle/launch/spec_state.rs +138 -13
  18. package/crates/team-agent/src/lifecycle/launch/state_projection.rs +5 -3
  19. package/crates/team-agent/src/lifecycle/restart/common.rs +3 -11
  20. package/crates/team-agent/src/lifecycle/tests/launch_spawn.rs +60 -0
  21. package/crates/team-agent/src/lifecycle/tests/pi_compiler_red.rs +31 -0
  22. package/crates/team-agent/src/lifecycle/tests/pi_dual_entry_red.rs +25 -23
  23. package/crates/team-agent/src/lifecycle/tests/worker_spawn_env_red.rs +110 -31
  24. package/crates/team-agent/src/messaging/delivery.rs +226 -37
  25. package/crates/team-agent/src/messaging/leader_channel.rs +12 -3
  26. package/crates/team-agent/src/messaging/tests/runtime.rs +234 -0
  27. package/crates/team-agent/src/model/spec.rs +81 -7
  28. package/crates/team-agent/src/provider/adapters/pi.rs +14 -0
  29. package/crates/team-agent/src/state/persist.rs +433 -4
  30. package/crates/team-agent/src/state/repository.rs +45 -2
  31. package/crates/team-agent/src/tmux_backend/tests.rs +414 -7
  32. package/crates/team-agent/src/tmux_backend.rs +274 -26
  33. package/crates/team-agent/src/transport/test_support.rs +18 -3
  34. package/crates/team-agent/src/transport.rs +99 -0
  35. package/package.json +4 -4
  36. package/skills/team-agent/SKILL.md +3 -0
  37. package/skills/team-agent/command-coverage.json +21 -0
  38. package/skills/team-agent/references/team-agent-operator.md +9 -2
package/Cargo.lock CHANGED
@@ -581,7 +581,7 @@ dependencies = [
581
581
 
582
582
  [[package]]
583
583
  name = "team-agent"
584
- version = "0.5.73"
584
+ version = "0.5.75"
585
585
  dependencies = [
586
586
  "anyhow",
587
587
  "chrono",
package/Cargo.toml CHANGED
@@ -9,7 +9,7 @@ members = ["crates/team-agent", "crates/win-conpty-phase0", "crates/conpty-trans
9
9
 
10
10
  [workspace.package]
11
11
  edition = "2021"
12
- version = "0.5.73"
12
+ version = "0.5.75"
13
13
  license = "AGPL-3.0"
14
14
  rust-version = "1.95"
15
15
 
@@ -129,6 +129,9 @@ pub fn cmd_quick_start(args: &QuickStartArgs) -> Result<CmdResult, CliError> {
129
129
  .get("status")
130
130
  .and_then(Value::as_str)
131
131
  .map(str::to_string);
132
+ if !args.detail {
133
+ lifecycle_port::compact_quick_start_value(&mut value);
134
+ }
132
135
  if args.json
133
136
  || value.get("ok").and_then(Value::as_bool) == Some(false)
134
137
  || session_capture_incomplete
@@ -1377,10 +1380,12 @@ pub fn cmd_takeover(args: &TakeoverArgs) -> Result<CmdResult, CliError> {
1377
1380
 
1378
1381
  /// `cmd_claim_leader`(`commands.py:156`)。
1379
1382
  pub fn cmd_claim_leader(args: &ClaimLeaderArgs) -> Result<CmdResult, CliError> {
1380
- Ok(CmdResult::from_json(
1381
- leader_port::claim_leader(&args.workspace, args.team.as_deref(), args.confirm)?,
1382
- args.json,
1383
- ))
1383
+ let mut value =
1384
+ leader_port::claim_leader(&args.workspace, args.team.as_deref(), args.confirm)?;
1385
+ if !args.detail {
1386
+ leader_port::compact_lease_value(&mut value);
1387
+ }
1388
+ Ok(CmdResult::from_json(value, args.json))
1384
1389
  }
1385
1390
 
1386
1391
  /// `cmd_identity`(`commands.py:160`)。
@@ -1401,15 +1406,16 @@ pub fn cmd_shutdown(args: &ShutdownArgs) -> Result<CmdResult, CliError> {
1401
1406
 
1402
1407
  /// `cmd_restart`(`commands.py:344`)。
1403
1408
  pub fn cmd_restart(args: &RestartArgs) -> Result<CmdResult, CliError> {
1404
- Ok(CmdResult::from_json(
1405
- lifecycle_port::restart(
1406
- &args.workspace,
1407
- args.allow_fresh,
1408
- args.team.as_deref(),
1409
- args.session_converge_deadline_ms,
1410
- )?,
1411
- args.json,
1412
- ))
1409
+ let mut value = lifecycle_port::restart(
1410
+ &args.workspace,
1411
+ args.allow_fresh,
1412
+ args.team.as_deref(),
1413
+ args.session_converge_deadline_ms,
1414
+ )?;
1415
+ if !args.detail {
1416
+ lifecycle_port::compact_restart_value(&mut value);
1417
+ }
1418
+ Ok(CmdResult::from_json(value, args.json))
1413
1419
  }
1414
1420
 
1415
1421
  /// `cmd_start_agent`(`commands.py:348`)。
@@ -374,7 +374,7 @@ fn command_help(command: Option<&str>) -> String {
374
374
  match command {
375
375
  None => default_help(),
376
376
  Some("init") => compat_hidden_help("init", "usage: team-agent init [--workspace WORKSPACE] [--force] [--json]"),
377
- Some("quick-start") => "usage: team-agent quick-start [TEAMDIR] [--workspace WORKSPACE] [--name NAME] [--team-id TEAM|--team TEAM] [--yes] [--no-display] [--backend tmux|conpty] [--json]\n\ndefaults: display_backend=adaptive; set display_backend: none in TEAM.md or pass --no-display to use one worker window per agent.\n\n--backend selects the worker transport (Phase 1d Batch 2): tmux (default on POSIX; unchanged behavior), conpty (Windows-native ConPTY worker transport; requires the shim binary and Windows host).\n\nAfter a successful start, use the returned `send_commands` (or choose an agent explicitly) with `team-agent send AGENT MESSAGE`.".to_string(),
377
+ Some("quick-start") => "usage: team-agent quick-start [TEAMDIR] [--workspace WORKSPACE] [--name NAME] [--team-id TEAM|--team TEAM] [--yes] [--no-display] [--backend tmux|conpty] [--json] [--detail]\n\ndefaults: display_backend=adaptive; set display_backend: none in TEAM.md or pass --no-display to use one worker window per agent.\n\n--backend selects the worker transport (Phase 1d Batch 2): tmux (default on POSIX; unchanged behavior), conpty (Windows-native ConPTY worker transport; requires the shim binary and Windows host).\n\n--detail includes internal receiver/topology diagnostics in JSON output.\n\nAfter a successful start, use the returned `send_commands` (or choose an agent explicitly) with `team-agent send AGENT MESSAGE`.".to_string(),
378
378
  Some("start") => compat_hidden_help("start", "usage: team-agent start [TEAMDIR] [--yes] [--fresh] [--json]"),
379
379
  Some("compile") => "usage: team-agent compile --team TEAM [--out FILE] [--json]".to_string(),
380
380
  Some("send") => concat!(
@@ -391,7 +391,7 @@ fn command_help(command: Option<&str>) -> String {
391
391
  Some("models") => "usage: team-agent models --provider pi [--search TEXT] [--json]\n\nPrints models.v1 exact role_model entries; each entry includes current=true|false. Catalog readiness is reported as auth=ok|not_ready with auth_basis=catalog_visibility.".to_string(),
392
392
  Some("stop") => compat_hidden_help("stop", "usage: team-agent stop [--workspace WORKSPACE] [--team TEAM] [--keep-logs] [--json]"),
393
393
  Some("shutdown") => "usage: team-agent shutdown [--workspace WORKSPACE] [--team TEAM] [--keep-logs] [--json]".to_string(),
394
- Some("restart") => "usage: team-agent restart [WORKSPACE] [--team TEAM] [--allow-fresh] [--session-converge-deadline SECONDS] [--json]".to_string(),
394
+ Some("restart") => "usage: team-agent restart [WORKSPACE] [--team TEAM] [--allow-fresh] [--session-converge-deadline SECONDS] [--json] [--detail]".to_string(),
395
395
  Some("restart-agent") => compat_hidden_help("restart-agent", "usage: team-agent restart-agent AGENT [--workspace WORKSPACE] [--team TEAM] [--discard-session] [--no-display] [--json]"),
396
396
  Some("reset-agent") => "usage: team-agent reset-agent AGENT [--workspace WORKSPACE] [--team TEAM] [--discard-session] [--no-display] [--json]".to_string(),
397
397
  Some("start-agent") => "usage: team-agent start-agent AGENT [--workspace WORKSPACE] [--team TEAM] [--force] [--allow-fresh] [--no-display] [--json]\n\nAfter a successful start, use the returned `send_commands` with `team-agent send AGENT MESSAGE`.".to_string(),
@@ -405,7 +405,7 @@ fn command_help(command: Option<&str>) -> String {
405
405
  Some("stuck-cancel") => "usage: team-agent stuck-cancel AGENT [--workspace WORKSPACE] [--alert-type stuck|idle_fallback|cross_worker_deadlock|all] [--json]".to_string(),
406
406
  Some("acknowledge-idle") => "usage: team-agent acknowledge-idle [--workspace WORKSPACE] [--team TEAM] [--json]".to_string(),
407
407
  Some("takeover") => "usage: team-agent takeover [--workspace WORKSPACE] [--team TEAM] [--confirm] [--json]".to_string(),
408
- Some("claim-leader") => "usage: team-agent claim-leader [--workspace WORKSPACE] [--team TEAM] [--confirm] [--json]".to_string(),
408
+ Some("claim-leader") => "usage: team-agent claim-leader [--workspace WORKSPACE] [--team TEAM] [--confirm] [--json] [--detail]".to_string(),
409
409
  Some("attach-leader") => "usage: team-agent attach-leader [--workspace WORKSPACE] [--team TEAM] [--pane PANE] [--provider PROVIDER] [--confirm] [--json]".to_string(),
410
410
  Some("attach-app-server-leader") => "usage: team-agent attach-app-server-leader [--workspace WORKSPACE] [--team TEAM] --socket unix:///path.sock --thread-id THREAD_ID [--json]".to_string(),
411
411
  Some("identity") => "usage: team-agent identity [--workspace WORKSPACE] [--team TEAM] [--json]".to_string(),
@@ -1063,6 +1063,7 @@ fn quick_start_args(args: &[String], cwd: &Path) -> Result<QuickStartArgs, CliEr
1063
1063
  yes: parsed.yes,
1064
1064
  no_display: parsed.no_display,
1065
1065
  json: parsed.json,
1066
+ detail: parsed.detail,
1066
1067
  backend: parsed.backend,
1067
1068
  })
1068
1069
  }
@@ -1404,6 +1405,7 @@ fn claim_leader_args(args: &[String], cwd: &Path) -> ClaimLeaderArgs {
1404
1405
  team: parsed.team,
1405
1406
  confirm: parsed.confirm,
1406
1407
  json: parsed.json,
1408
+ detail: parsed.detail,
1407
1409
  }
1408
1410
  }
1409
1411
 
@@ -1477,6 +1479,7 @@ fn restart_args(args: &[String], cwd: &Path) -> Result<RestartArgs, CliError> {
1477
1479
  allow_fresh: parsed.allow_fresh,
1478
1480
  session_converge_deadline_ms: parsed.session_converge_deadline_ms,
1479
1481
  json: parsed.json,
1482
+ detail: parsed.detail,
1480
1483
  })
1481
1484
  }
1482
1485
 
@@ -3233,6 +3233,33 @@ pub mod lifecycle_port {
3233
3233
  );
3234
3234
  }
3235
3235
 
3236
+ const COMPACT_READINESS_KEYS: [&str; 6] = [
3237
+ "all_workers_spawned",
3238
+ "state",
3239
+ "ready",
3240
+ "reason",
3241
+ "next_action",
3242
+ "unhealthy_agents",
3243
+ ];
3244
+
3245
+ fn compact_readiness_object(value: Option<&mut Value>) {
3246
+ let Some(object) = value.and_then(Value::as_object_mut) else {
3247
+ return;
3248
+ };
3249
+ object.retain(|key, _| COMPACT_READINESS_KEYS.contains(&key.as_str()));
3250
+ }
3251
+
3252
+ pub(crate) fn compact_quick_start_value(value: &mut Value) {
3253
+ if let Some(object) = value.as_object_mut() {
3254
+ // Default contract: outcome, error/next action, attach/send, and a
3255
+ // short readiness verdict. Diagnostic aliases stay behind --detail.
3256
+ object.remove("agent_ids");
3257
+ object.remove("dry_run");
3258
+ compact_readiness_object(object.get_mut("readiness"));
3259
+ compact_readiness_object(object.get_mut("worker_readiness"));
3260
+ }
3261
+ }
3262
+
3236
3263
  fn quick_start_value(report: crate::lifecycle::QuickStartReport) -> Value {
3237
3264
  match report {
3238
3265
  crate::lifecycle::QuickStartReport::Ready {
@@ -3265,6 +3292,7 @@ pub mod lifecycle_port {
3265
3292
  ),
3266
3293
  false,
3267
3294
  json!({
3295
+ "reason": "worker_unhealthy",
3268
3296
  "all_spawned": all_spawned,
3269
3297
  "all_workers_spawned": all_workers_spawned,
3270
3298
  "all_attached_receiver": all_attached_receiver,
@@ -3383,6 +3411,8 @@ pub mod lifecycle_port {
3383
3411
  attach_commands,
3384
3412
  } => json!({
3385
3413
  "ok": false,
3414
+ "status": "existing_runtime",
3415
+ "reason": "team already has runtime state; use restart",
3386
3416
  "summary": "existing runtime",
3387
3417
  "team": team,
3388
3418
  "session_name": session_name.map(|s| s.as_str().to_string()),
@@ -3398,6 +3428,8 @@ pub mod lifecycle_port {
3398
3428
  attach_commands,
3399
3429
  } => json!({
3400
3430
  "ok": false,
3431
+ "status": "preflight_blocked",
3432
+ "reason": "quick-start preflight blocked",
3401
3433
  "summary": summary,
3402
3434
  "blockers": blockers,
3403
3435
  "next_actions": next_actions,
@@ -3411,6 +3443,89 @@ pub mod lifecycle_port {
3411
3443
  mod quick_start_value_tests {
3412
3444
  use super::*;
3413
3445
 
3446
+ #[test]
3447
+ fn quick_start_default_receipt_is_compact_but_detail_preserves_readiness() {
3448
+ let readiness = json!({
3449
+ "all_spawned": true,
3450
+ "all_workers_spawned": true,
3451
+ "all_attached_receiver": false,
3452
+ "attached_receiver": false,
3453
+ "leader_receiver_attached": false,
3454
+ "all_resumable_have_session": true,
3455
+ "all_resumable_agents_have_sessions": true,
3456
+ "ready": false,
3457
+ "state": "leader_receiver_unbound",
3458
+ "session_capture_complete": true,
3459
+ "session_capture_incomplete": false,
3460
+ "incomplete_session_capture_agents": [],
3461
+ "pending_session_agent_ids": [],
3462
+ "reason": "launched team has no attached leader receiver",
3463
+ "next_action": "claim-leader"
3464
+ });
3465
+ let detail = json!({
3466
+ "ok": false,
3467
+ "status": "leader_receiver_unbound",
3468
+ "reason": "launched team has no attached leader receiver",
3469
+ "ready": false,
3470
+ "session_name": "team-fresh",
3471
+ "next_actions": ["claim-leader"],
3472
+ "attach_commands": ["tmux attach"],
3473
+ "send_commands": ["team-agent send worker hi"],
3474
+ "agent_ids": ["worker"],
3475
+ "readiness": readiness.clone(),
3476
+ "worker_readiness": readiness,
3477
+ "dry_run": false
3478
+ });
3479
+ let mut default = detail.clone();
3480
+ compact_quick_start_value(&mut default);
3481
+ assert_eq!(default["status"], json!("leader_receiver_unbound"));
3482
+ assert_eq!(
3483
+ default["reason"],
3484
+ json!("launched team has no attached leader receiver")
3485
+ );
3486
+ assert_eq!(default["next_actions"], json!(["claim-leader"]));
3487
+ assert_eq!(default["attach_commands"], json!(["tmux attach"]));
3488
+ assert_eq!(
3489
+ default["send_commands"],
3490
+ json!(["team-agent send worker hi"])
3491
+ );
3492
+ assert!(default.get("agent_ids").is_none());
3493
+ assert!(default.get("dry_run").is_none());
3494
+ assert_eq!(default["readiness"]["all_workers_spawned"], json!(true));
3495
+ assert_eq!(default["readiness"]["state"], json!("leader_receiver_unbound"));
3496
+ assert_eq!(default["readiness"]["next_action"], json!("claim-leader"));
3497
+ assert!(default["readiness"].get("all_spawned").is_none());
3498
+ assert!(default["readiness"].get("all_attached_receiver").is_none());
3499
+ assert!(default["readiness"].get("leader_receiver_attached").is_none());
3500
+ assert_eq!(
3501
+ default["worker_readiness"]["all_workers_spawned"],
3502
+ json!(true)
3503
+ );
3504
+ assert!(default["worker_readiness"].get("all_spawned").is_none());
3505
+ assert!(detail["readiness"].get("all_spawned").is_some());
3506
+ assert!(detail["worker_readiness"].get("leader_receiver_attached").is_some());
3507
+ }
3508
+
3509
+ #[test]
3510
+ fn restart_default_receipt_is_compact_but_detail_preserves_coordinator() {
3511
+ let detail = json!({
3512
+ "ok": false,
3513
+ "status": "partial",
3514
+ "reason": "restart_agent_failed",
3515
+ "failed_agents": [{"agent_id": "worker", "error": "spawn failed"}],
3516
+ "next_actions": ["restart-agent worker"],
3517
+ "attach_commands": [],
3518
+ "coordinator": {"status": "running", "transport": "default"}
3519
+ });
3520
+ let mut default = detail.clone();
3521
+ compact_restart_value(&mut default);
3522
+ assert_eq!(default["status"], json!("partial"));
3523
+ assert_eq!(default["reason"], json!("restart_agent_failed"));
3524
+ assert_eq!(default["next_actions"], json!(["restart-agent worker"]));
3525
+ assert!(default.get("coordinator").is_none());
3526
+ assert!(detail.get("coordinator").is_some());
3527
+ }
3528
+
3414
3529
  #[test]
3415
3530
  fn existing_runtime_json_includes_attach_commands() {
3416
3531
  let value = quick_start_value(crate::lifecycle::QuickStartReport::ExistingRuntime {
@@ -3500,6 +3615,15 @@ pub mod lifecycle_port {
3500
3615
  }
3501
3616
  }
3502
3617
 
3618
+ pub(crate) fn compact_restart_value(value: &mut Value) {
3619
+ if let Some(object) = value.as_object_mut() {
3620
+ // Coordinator internals remain available only through --detail;
3621
+ // outcome, failed-agent errors, attach commands and next actions
3622
+ // stay in the default receipt.
3623
+ object.remove("coordinator");
3624
+ }
3625
+ }
3626
+
3503
3627
  fn restart_value(report: crate::lifecycle::RestartReport, team: Option<&str>) -> Value {
3504
3628
  match report {
3505
3629
  crate::lifecycle::RestartReport::Restarted {
@@ -3855,6 +3979,7 @@ pub mod lifecycle_port {
3855
3979
  "status_class": "refused_resume_atomicity",
3856
3980
  "allow_fresh": allow_fresh,
3857
3981
  "error": error_str,
3982
+ "next_action": "pass --allow-fresh to start fresh, or restore the provider session backing files",
3858
3983
  "unresumable": unresumable_detail,
3859
3984
  "unresumable_ids": unresumable_ids,
3860
3985
  "reminder": crate::cli::QUICK_START_REMINDER,
@@ -3894,6 +4019,7 @@ pub mod lifecycle_port {
3894
4019
  "status": "refused_invalid_first_send_at",
3895
4020
  "allow_fresh": allow_fresh,
3896
4021
  "error": error,
4022
+ "next_action": "pass --allow-fresh to discard the invalid session marker and start fresh",
3897
4023
  "invalid": invalid.iter().map(|w| w.worker_id.as_str()).collect::<Vec<_>>(),
3898
4024
  "reminder": crate::cli::QUICK_START_REMINDER,
3899
4025
  }),
@@ -5075,6 +5201,31 @@ pub mod leader_port {
5075
5201
  })
5076
5202
  }
5077
5203
 
5204
+ pub(crate) fn compact_lease_value(value: &mut Value) {
5205
+ let topology_status = value
5206
+ .get("topology_convergence")
5207
+ .and_then(|topology| topology.get("status"))
5208
+ .cloned();
5209
+ if value.get("reason").is_none() {
5210
+ if let Some(status) = topology_status {
5211
+ if status.as_str() != Some("converged") {
5212
+ value["reason"] = status;
5213
+ }
5214
+ }
5215
+ }
5216
+ if let Some(obj) = value.as_object_mut() {
5217
+ for key in [
5218
+ "owner_epoch",
5219
+ "leader_receiver",
5220
+ "team_owner",
5221
+ "topology_convergence",
5222
+ "leader_registry",
5223
+ ] {
5224
+ obj.remove(key);
5225
+ }
5226
+ }
5227
+ }
5228
+
5078
5229
  fn lease_value(result: crate::leader::LeaseResult) -> Value {
5079
5230
  let mut out = serde_json::Map::new();
5080
5231
  out.insert("ok".to_string(), json!(result.ok));
@@ -238,6 +238,34 @@ fn leader_port_takeover_confirm_without_pane_refuses_caller_pane_missing() {
238
238
  );
239
239
  }
240
240
 
241
+ #[test]
242
+ fn claim_leader_default_response_hides_internal_binding_details() {
243
+ let mut value = serde_json::json!({
244
+ "ok": true,
245
+ "status": "already_bound",
246
+ "owner_epoch": 3,
247
+ "bound_pane_id": "%77",
248
+ "leader_receiver": {"pane_id": "%77"},
249
+ "team_owner": {"pane_id": "%77"},
250
+ "topology_convergence": {"status": "not_converged_old_endpoint_live"},
251
+ "leader_registry": {"status": "registered"}
252
+ });
253
+ super::leader_port::compact_lease_value(&mut value);
254
+ assert_eq!(value["ok"], json!(true));
255
+ assert_eq!(value["status"], json!("already_bound"));
256
+ assert_eq!(value["bound_pane_id"], json!("%77"));
257
+ assert_eq!(value["reason"], json!("not_converged_old_endpoint_live"));
258
+ for field in [
259
+ "owner_epoch",
260
+ "leader_receiver",
261
+ "team_owner",
262
+ "topology_convergence",
263
+ "leader_registry",
264
+ ] {
265
+ assert!(value.get(field).is_none(), "default claim response leaked {field}");
266
+ }
267
+ }
268
+
241
269
  // RED — claim_leader(confirm=false) with no $TMUX_PANE: runtime.claim_leader is
242
270
  // ALSO Family A (runtime.py:791) -> the bind gate fires FIRST, so the no-pane
243
271
  // refusal is caller_pane_missing (NOT the leader-lane "not_in_tmux_pane").
@@ -225,6 +225,7 @@ fn cli_quick_start_invokes_real_lifecycle_compiles_spec() {
225
225
  no_display: false,
226
226
  backend: None,
227
227
  json: true,
228
+ detail: false,
228
229
  };
229
230
  let _ = cmd_quick_start(&args); // real quick_start compiles the spec before any coordinator/launch step
230
231
  // E5: spec compiled to .team/runtime/<team_key>/, NOT the user team dir.
@@ -260,6 +261,7 @@ fn cli_quick_start_invalid_spec_surfaces_real_compile_error() {
260
261
  no_display: false,
261
262
  backend: None,
262
263
  json: true,
264
+ detail: false,
263
265
  };
264
266
  let text = outcome_text(cmd_quick_start(&args));
265
267
  assert!(
@@ -281,6 +283,7 @@ fn cli_restart_missing_spec_surfaces_real_teamselect() {
281
283
  allow_fresh: false,
282
284
  session_converge_deadline_ms: None,
283
285
  json: true,
286
+ detail: false,
284
287
  };
285
288
  let text = outcome_text(cmd_restart(&args));
286
289
  // RED-2-STILL: entry gate now resolves via canonical_run_workspace; for a no-spec workspace
@@ -297,6 +297,8 @@ pub struct QuickStartArgs {
297
297
  pub yes: bool,
298
298
  pub no_display: bool,
299
299
  pub json: bool,
300
+ /// Include internal receiver/topology diagnostics in the response.
301
+ pub detail: bool,
300
302
  /// 0.5.x Phase 1d Batch 2: explicit backend override
301
303
  /// (`--backend <tmux|conpty>`). `None` = default (factory 5-layer
302
304
  /// resolution; almost always `tmux` on a cold quick-start).
@@ -433,6 +435,8 @@ pub struct ClaimLeaderArgs {
433
435
  pub team: Option<String>,
434
436
  pub confirm: bool,
435
437
  pub json: bool,
438
+ /// Include internal receiver/topology diagnostics in the response.
439
+ pub detail: bool,
436
440
  }
437
441
 
438
442
  /// `attach-leader` public CLI args. `cmd_attach_leader` consumes the typed pane/provider
@@ -482,6 +486,8 @@ pub struct RestartArgs {
482
486
  pub allow_fresh: bool,
483
487
  pub session_converge_deadline_ms: Option<u64>,
484
488
  pub json: bool,
489
+ /// Include internal receiver/topology diagnostics in the response.
490
+ pub detail: bool,
485
491
  }
486
492
 
487
493
  /// `start-agent`(`parser.py:369`)。
@@ -30,7 +30,8 @@ use std::path::Path;
30
30
  use crate::communication_mode::CommunicationMode;
31
31
  use crate::model::enums::{Provider, ProviderEffort};
32
32
  use crate::model::yaml::Value;
33
- use crate::model::{paths, spec, yaml, ModelError};
33
+ use crate::model::{paths, permissions, spec, yaml, ModelError};
34
+ use crate::provider::adapters::pi::first_unsupported_pi_tool_category;
34
35
  use crate::provider::wire::{
35
36
  builtin_provider_model as wire_builtin_provider_model, is_claude_family,
36
37
  parse_canonical_provider, provider_model_keys,
@@ -764,6 +765,18 @@ fn validate_pi_role_fields(meta: &Value, path: &Path, provider: &str) -> Result<
764
765
  path.display()
765
766
  )));
766
767
  }
768
+ let expanded = permissions::expand_tool_strings(tools.iter().map(String::as_str));
769
+ if let Some(category) = first_unsupported_pi_tool_category(
770
+ expanded
771
+ .iter()
772
+ .filter(|category| permissions::is_canonical_tool(category))
773
+ .map(String::as_str),
774
+ ) {
775
+ return Err(ModelError::Validation(format!(
776
+ "{}: Pi does not support Team Agent tool category {category:?}; remove it from the role's tools",
777
+ path.display()
778
+ )));
779
+ }
767
780
  Ok(())
768
781
  }
769
782