@team-agent/installer 0.5.113 → 0.5.115

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 (52) hide show
  1. package/Cargo.lock +252 -84
  2. package/Cargo.toml +3 -1
  3. package/crates/team-agent/Cargo.toml +3 -0
  4. package/crates/team-agent/src/cli/adapters.rs +28 -15
  5. package/crates/team-agent/src/cli/diagnose.rs +36 -13
  6. package/crates/team-agent/src/cli/emit.rs +57 -22
  7. package/crates/team-agent/src/cli/leader.rs +16 -0
  8. package/crates/team-agent/src/cli/leader_prompt.rs +261 -0
  9. package/crates/team-agent/src/cli/mod.rs +157 -8
  10. package/crates/team-agent/src/cli/models.rs +119 -18
  11. package/crates/team-agent/src/cli/spec.rs +7 -1
  12. package/crates/team-agent/src/cli/status_port/brief.rs +268 -101
  13. package/crates/team-agent/src/cli/tests/base.rs +88 -0
  14. package/crates/team-agent/src/cli/tests/status_send.rs +2 -0
  15. package/crates/team-agent/src/compiler.rs +66 -18
  16. package/crates/team-agent/src/contract_runtime/backend.rs +959 -0
  17. package/crates/team-agent/src/contract_runtime/commands.rs +245 -0
  18. package/crates/team-agent/src/contract_runtime/config.rs +340 -0
  19. package/crates/team-agent/src/contract_runtime/config_tests.rs +56 -0
  20. package/crates/team-agent/src/contract_runtime/discovery.rs +383 -0
  21. package/crates/team-agent/src/contract_runtime/forward.rs +229 -0
  22. package/crates/team-agent/src/contract_runtime/framework.rs +294 -0
  23. package/crates/team-agent/src/contract_runtime/mcp.rs +396 -0
  24. package/crates/team-agent/src/contract_runtime/mod.rs +16 -0
  25. package/crates/team-agent/src/contract_runtime/prompt.rs +216 -0
  26. package/crates/team-agent/src/contract_runtime/pump.rs +269 -0
  27. package/crates/team-agent/src/contract_runtime/registry.rs +197 -0
  28. package/crates/team-agent/src/contract_runtime/workspace.rs +160 -0
  29. package/crates/team-agent/src/coordinator/tick.rs +26 -8
  30. package/crates/team-agent/src/leader/mod.rs +1 -0
  31. package/crates/team-agent/src/leader/prompt.rs +906 -0
  32. package/crates/team-agent/src/leader/start.rs +353 -21
  33. package/crates/team-agent/src/leader/tests/mod.rs +2 -0
  34. package/crates/team-agent/src/leader/tests/prompt_contract.rs +268 -0
  35. package/crates/team-agent/src/leader/types.rs +6 -0
  36. package/crates/team-agent/src/lib.rs +4 -0
  37. package/crates/team-agent/src/lifecycle/launch/quick_start.rs +571 -389
  38. package/crates/team-agent/src/lifecycle/launch/spawn.rs +38 -9
  39. package/crates/team-agent/src/lifecycle/launch/state_projection.rs +15 -0
  40. package/crates/team-agent/src/lifecycle/launch.rs +45 -28
  41. package/crates/team-agent/src/lifecycle/restart/rebuild.rs +23 -1
  42. package/crates/team-agent/src/lifecycle/restart/selection.rs +34 -0
  43. package/crates/team-agent/src/lifecycle/role_config.rs +17 -1
  44. package/crates/team-agent/src/lifecycle/types.rs +4 -1
  45. package/crates/team-agent/src/lifecycle/worker_command_context.rs +37 -27
  46. package/crates/team-agent/src/main.rs +6 -0
  47. package/crates/team-agent/src/messaging/delivery.rs +50 -1
  48. package/crates/team-agent/src/messaging/send.rs +10 -0
  49. package/crates/team-agent/src/messaging/tests/leader_inject_acceptance.rs +131 -0
  50. package/crates/team-agent/src/model/spec.rs +24 -12
  51. package/crates/team-agent/src/state/persist.rs +73 -0
  52. package/package.json +4 -4
@@ -28,6 +28,9 @@ chrono.workspace = true
28
28
  libc.workspace = true
29
29
  rusqlite.workspace = true # step 3 db(bundled SQLite,静态链接)
30
30
 
31
+ [target.'cfg(unix)'.dependencies]
32
+ team-agent-contract = { path = "../team-agent-contract" }
33
+
31
34
  # 0.5.x Windows portability Batch 2/3: uses the same `windows = "0.61"`
32
35
  # crate as `win-conpty-phase0` so the workspace shares a single
33
36
  # Windows-API crate + already-locked Cargo.lock version.
@@ -7,6 +7,9 @@ use super::*;
7
7
 
8
8
  /// `cmd_quick_start`(`commands.py:18`)。`--json` 或 `!ok` → 整 dict;否则 `result["summary"]`。
9
9
  pub fn cmd_quick_start(args: &QuickStartArgs) -> Result<CmdResult, CliError> {
10
+ #[cfg(unix)]
11
+ crate::contract_runtime::config::read_team(args)
12
+ .map_err(|error| CliError::Runtime(error.to_string()))?;
10
13
  let mut value = lifecycle_port::quick_start(
11
14
  &args.workspace,
12
15
  &args.agents_dir,
@@ -158,7 +161,10 @@ fn quick_start_config_guidance(value: &mut Value, args: &QuickStartArgs) -> Opti
158
161
  /// E13:quick-start "team 起了" 人类输出 = summary + attach 块。所有成功出口共用(别每分支手拷)。
159
162
  /// attach_commands 缺/空 → 只 summary(向后兼容)。
160
163
  fn quickstart_human(value: &Value) -> String {
161
- let summary = value.get("summary").and_then(Value::as_str).unwrap_or("Team started.");
164
+ let summary = value
165
+ .get("summary")
166
+ .and_then(Value::as_str)
167
+ .unwrap_or("Team started.");
162
168
  let attach: Vec<&str> = value
163
169
  .get("attach_commands")
164
170
  .and_then(Value::as_array)
@@ -1088,19 +1094,13 @@ mod tests {
1088
1094
  let value = json!({"summary": "quick-start complete"});
1089
1095
  assert_eq!(
1090
1096
  quickstart_human(&value),
1091
- format!(
1092
- "quick-start complete\n{}",
1093
- crate::cli::QUICK_START_REMINDER
1094
- )
1097
+ format!("quick-start complete\n{}", crate::cli::QUICK_START_REMINDER)
1095
1098
  );
1096
1099
  // 空数组也只显示人类摘要与下一步,不凭空编造连接方式。
1097
1100
  let value2 = json!({"summary": "s", "attach_commands": []});
1098
1101
  assert_eq!(
1099
1102
  quickstart_human(&value2),
1100
- format!(
1101
- "s\n{}",
1102
- crate::cli::QUICK_START_REMINDER
1103
- )
1103
+ format!("s\n{}", crate::cli::QUICK_START_REMINDER)
1104
1104
  );
1105
1105
  }
1106
1106
 
@@ -1219,7 +1219,11 @@ mod tests {
1219
1219
 
1220
1220
  #[test]
1221
1221
  fn quick_start_human_preserves_typed_refusals_without_error_field() {
1222
- for status in ["existing_runtime", "preflight_blocked", "leader_binding_refused"] {
1222
+ for status in [
1223
+ "existing_runtime",
1224
+ "preflight_blocked",
1225
+ "leader_binding_refused",
1226
+ ] {
1223
1227
  let value = json!({
1224
1228
  "ok": false,
1225
1229
  "status": status,
@@ -1232,10 +1236,16 @@ mod tests {
1232
1236
  });
1233
1237
  let result = finish_quick_start(value.clone(), &quick_start_args(false)).unwrap();
1234
1238
  assert_eq!(result.exit, ExitCode::Error);
1235
- let CmdOutput::Human(text) = result.output else { panic!("expected human report") };
1236
- let rendered: Value = serde_json::from_str(text.strip_prefix("quick-start report:\n").unwrap()).unwrap();
1239
+ let CmdOutput::Human(text) = result.output else {
1240
+ panic!("expected human report")
1241
+ };
1242
+ let rendered: Value =
1243
+ serde_json::from_str(text.strip_prefix("quick-start report:\n").unwrap()).unwrap();
1237
1244
  assert_eq!(rendered, value, "human projection lost fields for {status}");
1238
- assert!(!text.contains("team-agent doctor"), "must not replace actual actions");
1245
+ assert!(
1246
+ !text.contains("team-agent doctor"),
1247
+ "must not replace actual actions"
1248
+ );
1239
1249
  let json_result = finish_quick_start(value.clone(), &quick_start_args(true)).unwrap();
1240
1250
  assert_eq!(json_result.exit, ExitCode::Error);
1241
1251
  assert_eq!(json_result.output, CmdOutput::Json(value));
@@ -1252,8 +1262,11 @@ mod tests {
1252
1262
  });
1253
1263
  let result = finish_quick_start(value.clone(), &quick_start_args(false)).unwrap();
1254
1264
  assert_eq!(result.exit, ExitCode::Error);
1255
- let CmdOutput::Human(text) = result.output else { panic!("expected human report") };
1256
- let rendered: Value = serde_json::from_str(text.strip_prefix("quick-start report:\n").unwrap()).unwrap();
1265
+ let CmdOutput::Human(text) = result.output else {
1266
+ panic!("expected human report")
1267
+ };
1268
+ let rendered: Value =
1269
+ serde_json::from_str(text.strip_prefix("quick-start report:\n").unwrap()).unwrap();
1257
1270
  assert_eq!(rendered, value);
1258
1271
  }
1259
1272
 
@@ -342,7 +342,13 @@ pub(crate) fn diagnose_runtime_for_workspace(
342
342
  &mut repairs,
343
343
  );
344
344
  append_legacy_snapshot_issue(workspace, state, &mut issues);
345
- append_coordinator_health_issue(workspace, state, coordinator_health, &mut issues, &mut repairs);
345
+ append_coordinator_health_issue(
346
+ workspace,
347
+ state,
348
+ coordinator_health,
349
+ &mut issues,
350
+ &mut repairs,
351
+ );
346
352
  append_runtime_bindings_stale_after_boot_issue(workspace, state, &mut issues, &mut repairs);
347
353
  (issues, repairs)
348
354
  }
@@ -373,7 +379,9 @@ pub(crate) fn workspace_has_existing_team_runtime(
373
379
  let terminal = team_state
374
380
  .get("status")
375
381
  .and_then(Value::as_str)
376
- .is_some_and(|status| matches!(status, "stopped" | "shutdown" | "archived" | "terminal"));
382
+ .is_some_and(|status| {
383
+ matches!(status, "stopped" | "shutdown" | "archived" | "terminal")
384
+ });
377
385
  if terminal {
378
386
  return false;
379
387
  }
@@ -695,14 +703,13 @@ pub(crate) fn append_registry_channel_unbound_to_report(
695
703
  repairs.push(recovery_hint(&team_key, issue_id, repair));
696
704
  object.insert("issues".to_string(), Value::Array(issues));
697
705
  object.insert("suggested_repairs".to_string(), Value::Array(repairs));
698
- let is_healthy_unattached_host =
699
- class == crate::lifecycle::launch::LeaderBindingClass::Unbound
700
- && object.get("ok").and_then(Value::as_bool) == Some(true)
701
- && object
702
- .get("profile_smoke")
703
- .and_then(|profile| profile.get("checks"))
704
- .and_then(Value::as_array)
705
- .is_some_and(|checks| !checks.is_empty());
706
+ let is_healthy_unattached_host = class == crate::lifecycle::launch::LeaderBindingClass::Unbound
707
+ && object.get("ok").and_then(Value::as_bool) == Some(true)
708
+ && object
709
+ .get("profile_smoke")
710
+ .and_then(|profile| profile.get("checks"))
711
+ .and_then(Value::as_array)
712
+ .is_some_and(|checks| !checks.is_empty());
706
713
  if !is_healthy_unattached_host {
707
714
  object.insert("ok".to_string(), Value::Bool(false));
708
715
  }
@@ -1054,8 +1061,7 @@ mod tests {
1054
1061
  pane_pid: None,
1055
1062
  leader_env: Default::default(),
1056
1063
  }]);
1057
- let Some((_, repair)) =
1058
- live_leader_workspace_mismatch(&workspace, &state, &transport)
1064
+ let Some((_, repair)) = live_leader_workspace_mismatch(&workspace, &state, &transport)
1059
1065
  else {
1060
1066
  panic!("workspace mismatch repair required");
1061
1067
  };
@@ -1109,7 +1115,8 @@ mod tests {
1109
1115
  .as_nanos()
1110
1116
  ));
1111
1117
  std::fs::create_dir_all(&workspace).unwrap();
1112
- let state = json!({"agents": {}, "tasks": [], "session_name": null, "active_team_key": null});
1118
+ let state =
1119
+ json!({"agents": {}, "tasks": [], "session_name": null, "active_team_key": null});
1113
1120
  let mut issues = json!([]);
1114
1121
  let mut repairs = json!([]);
1115
1122
  append_registry_channel_unbound_issue(
@@ -1574,6 +1581,22 @@ pub(crate) fn provider_doctor_checks() -> Value {
1574
1581
  }),
1575
1582
  );
1576
1583
  }
1584
+ #[cfg(unix)]
1585
+ for descriptor in crate::contract_runtime::registry::descriptors() {
1586
+ let identity = &descriptor.identity;
1587
+ // Inventory only: finding the launcher is not an engine, auth or readiness probe.
1588
+ providers.insert(
1589
+ identity.id.to_string(),
1590
+ json!({
1591
+ "auth": crate::provider::AuthHintStatus::Unknown,
1592
+ "command": identity.binary,
1593
+ "installed": crate::contract_runtime::discovery::executable(identity.binary).is_ok(),
1594
+ "version": "unknown",
1595
+ "probe_status": "not_run",
1596
+ "provider_probe_status": "not_run",
1597
+ }),
1598
+ );
1599
+ }
1577
1600
  Value::Object(providers)
1578
1601
  }
1579
1602
 
@@ -68,6 +68,9 @@ pub fn run(argv: &[String], cwd: &Path) -> ExitCode {
68
68
  if command == "route" {
69
69
  return super::route::run(&argv[1..]);
70
70
  }
71
+ if command == "leader-prompt" {
72
+ return super::leader_prompt::run(&argv[1..]);
73
+ }
71
74
  if is_leader_passthrough_command(command) {
72
75
  return match cmd_leader_passthrough(command, &argv[1..], cwd) {
73
76
  Ok(result) => emit_result(result),
@@ -212,6 +215,9 @@ fn dispatch(command: &str, args: &[String], cwd: &Path) -> Result<ExitCode, CliE
212
215
  if command == "route" {
213
216
  return Ok(super::route::run(args));
214
217
  }
218
+ if command == "leader-prompt" {
219
+ return Ok(super::leader_prompt::run(args));
220
+ }
215
221
  // Keep the removed flag harmless for older scripts and persisted command
216
222
  // lines; the default tmux backend is already the only runtime path.
217
223
  let filtered_args: Vec<String> = args
@@ -290,6 +296,7 @@ fn dispatch(command: &str, args: &[String], cwd: &Path) -> Result<ExitCode, CliE
290
296
  // delivery path — no separate route authority.
291
297
  "leaders" => cmd_leaders(&leaders_args(args, cwd)?).map(emit_result),
292
298
  "models" => cmd_models(&models_args(args)?).map(emit_result),
299
+ "kiro" => Ok(emit_result(cmd_kiro_leader(args))),
293
300
 
294
301
  "install-skill" => cmd_install_skill(&install_skill_args(args)?).map(emit_result),
295
302
  "profile" => cmd_profile(&profile_args(args, cwd)?).map(emit_result),
@@ -300,10 +307,7 @@ fn dispatch(command: &str, args: &[String], cwd: &Path) -> Result<ExitCode, CliE
300
307
 
301
308
  // Script compatibility is deliberately outside the human catalog and suggestions.
302
309
  fn is_machine_command(command: &str) -> bool {
303
- matches!(
304
- command,
305
- "wait" | "coordinator" | "attach-app-server-leader"
306
- )
310
+ matches!(command, "wait" | "coordinator" | "attach-app-server-leader")
307
311
  }
308
312
 
309
313
  fn dispatch_machine(command: &str, args: &[String], cwd: &Path) -> Result<ExitCode, CliError> {
@@ -365,7 +369,11 @@ pub(crate) fn default_help() -> String {
365
369
  "Observation and collaboration",
366
370
  &["leaders", "doctor", "approvals"],
367
371
  );
368
- append_help_section(&mut out, "Configuration", &["route", "profile", "install-skill"]);
372
+ append_help_section(
373
+ &mut out,
374
+ "Configuration",
375
+ &["route", "leader-prompt", "profile", "install-skill"],
376
+ );
369
377
  append_help_section(
370
378
  &mut out,
371
379
  "Guided recovery",
@@ -374,7 +382,7 @@ pub(crate) fn default_help() -> String {
374
382
  append_help_section(
375
383
  &mut out,
376
384
  "Leader launch",
377
- &["pi", "codex", "claude", "copilot", "grok", "cursor"],
385
+ &["pi", "codex", "claude", "copilot", "grok", "cursor", "kiro"],
378
386
  );
379
387
  out.push_str("\nCommand arguments and examples: team-agent <command> --help");
380
388
  out
@@ -425,6 +433,9 @@ pub(super) fn command_help(command: Option<&str>) -> String {
425
433
  if name == "route" {
426
434
  return super::route::HELP.to_string();
427
435
  }
436
+ if name == "leader-prompt" {
437
+ return super::leader_prompt::HELP.to_string();
438
+ }
428
439
  let (details, examples, next) = match name {
429
440
  "quick-start" => (
430
441
  "TEAMDIR contains TEAM.md and agents/; --workspace selects the runtime project.\n--name sets the team name; --team/--team-id selects the team; --yes retains compatibility; --detail includes full diagnostics.\nProviders must be installed and signed in. Declare provider/model/effort in role files; permission bypass defaults to false.\n--backend tmux is for POSIX; conpty requires a Windows host and an installed ConPTY shim.",
@@ -437,8 +448,8 @@ pub(super) fn command_help(command: Option<&str>) -> String {
437
448
  "team-agent inbox leader -n 3\nteam-agent inbox worker --limit 5\nteam-agent inbox leader --workspace . --team help-demo --json", "Wait for a natural reply, then check team-agent status if needed. Do not inspect worker terminal content."),
438
449
  "status" => ("Optional <agent> selects one agent. --summary/--detail retain compatibility and do not add fields.\nNine fields: name/provider/model/effort/runtime_status/activity/health/session_name/tmux_command.\nRuntime, activity and health are distinct. unknown means insufficient evidence; null means unset. No default model is guessed.",
439
450
  "team-agent status\nteam-agent status worker\nteam-agent status --workspace . --team help-demo --json", "Read replies with team-agent inbox leader -n 3; diagnose problems with team-agent doctor --workspace ."),
440
- "models" => ("--provider selects the provider, default pi. QUERY or --search performs keyword search; they are mutually exclusive.\nUse cursor_agent for Cursor. Copilot has no model discovery entrypoint here. Results come from the native catalog, not guesses.",
441
- "team-agent models --provider pi\nteam-agent models --provider pi --search luna\nteam-agent models --provider cursor_agent --json", "Use the exact returned model name in a role file or add-agent --model before launch."),
451
+ "models" => ("--provider selects the provider, default pi. QUERY or --search performs keyword search; they are mutually exclusive.\nUse cursor for the contract-native Cursor catalog; cursor_agent retains the legacy entrypoint. Copilot has no model discovery entrypoint here. Results come from the native catalog, not guesses.",
452
+ "team-agent models --provider pi\nteam-agent models --provider pi --search luna\nteam-agent models --provider cursor --json", "Use the exact returned model name in a role file or add-agent --model before launch."),
442
453
  "restart" => ("WORKSPACE defaults to the current project; saved sessions are preferred.\nUse --allow-fresh only after explicit authorization to lose saved context. --session-converge-deadline sets the wait in seconds.",
443
454
  "team-agent restart .\nteam-agent restart . --team help-demo --json", "Check status after restart. Obtain authorization before creating a fresh session; takeover is not a substitute for restart."),
444
455
  "shutdown" => ("Shuts down only the selected workspace/team. Logs are retained by default; --keep-logs remains compatible.\n--json reports scope, degradation and owned residuals. Success does not mean other teams were shut down.",
@@ -473,6 +484,8 @@ pub(super) fn command_help(command: Option<&str>) -> String {
473
484
  "team-agent takeover --workspace . --team help-demo --confirm\nteam-agent takeover --workspace . --team help-demo --confirm --json", "Verify leader/team ownership before confirmation. Check status/doctor afterwards; do not take over someone else's team."),
474
485
  "attach-leader" => ("PANE must be a verified leader terminal, not a guessed pane id.\n--provider selects the actual provider; --confirm authorizes attachment to an existing leader. Use only when doctor recommends it.",
475
486
  "team-agent attach-leader --pane \"$PANE\" --provider pi --workspace .\nteam-agent attach-leader --pane \"$PANE\" --provider pi --workspace . --team help-demo --confirm --json", "Set PANE to the verified terminal. Confirm attachment with doctor before sending a task."),
487
+ "kiro" => ("Kiro leader launching is not admitted by this candidate. This entry returns the stable capability refusal kiro_leader_not_admitted (exit 1).\nNo native process, leader binding, attach or provider fallback is attempted. --help/-h is read-only and exits 0.",
488
+ "team-agent kiro\nteam-agent kiro --json", "Use provider: kiro in a worker role with team-agent quick-start; worker support does not grant leader capability."),
476
489
  _ => ("Install the provider and complete native sign-in first. The current directory is the workspace.\nUse --attach-existing/--confirm only after verifying leader ownership and receiving authorization; --attach-session selects a verified session.\n--external-leader selects an external leader; --allow-nested-attach requires explicit authorization for nested terminals.\nArguments after -- pass through unchanged. Bare --help/-h displays this help without starting the provider.",
477
490
  "", "Run team-agent quick-start in the opened leader's tool context, not in an ordinary worker conversation."),
478
491
  };
@@ -486,12 +499,17 @@ pub(super) fn command_help(command: Option<&str>) -> String {
486
499
  } else {
487
500
  examples.to_string()
488
501
  };
489
- let scope = if matches!(spec.kind, CommandKind::LeaderPassthrough { .. }) {
502
+ let scope = if name == "kiro" {
503
+ "Use --json for the structured capability refusal. Native arguments and attach options cannot enable this capability."
504
+ } else if matches!(spec.kind, CommandKind::LeaderPassthrough { .. }) {
490
505
  "Use --json for structured output. --workspace/--team are not launcher options here; they pass to the native provider."
491
506
  } else {
492
507
  "Use --json for structured output. --workspace selects the project; supported commands use --team to select the team."
493
508
  };
494
- let mut out = format!("Purpose:\n{}.\nUsage: {}\n\nOptions:\n{}\n{}\nExamples:\n{}\n\nNext Action:\n{}", spec.summary, spec.usage, details, scope, examples, next);
509
+ let mut out = format!(
510
+ "Purpose:\n{}.\nUsage: {}\n\nOptions:\n{}\n{}\nExamples:\n{}\n\nNext Action:\n{}",
511
+ spec.summary, spec.usage, details, scope, examples, next
512
+ );
495
513
  if name == "quick-start" {
496
514
  out.push_str(&format!("\n\nMinimal two-file configuration (create manually; not written automatically):\nTEAM.md:\n{TEAM_TEMPLATE}\nagents/worker.md:\n{WORKER_TEMPLATE}"));
497
515
  }
@@ -499,7 +517,9 @@ pub(super) fn command_help(command: Option<&str>) -> String {
499
517
  out.push_str("\nDiscover models with team-agent models --provider cursor_agent.");
500
518
  }
501
519
  if name == "copilot" {
502
- out.push_str("\nCopilot has no team-agent models entrypoint; consult native provider help.");
520
+ out.push_str(
521
+ "\nCopilot has no team-agent models entrypoint; consult native provider help.",
522
+ );
503
523
  }
504
524
  out
505
525
  }
@@ -1250,16 +1270,20 @@ fn warn_send_legacy_delivery_flags(args: &[String]) {
1250
1270
  "--message-id",
1251
1271
  ];
1252
1272
  let spec = command_spec("send");
1253
- let sunset = spec.and_then(|spec| spec.sunset).unwrap_or("a future compatibility release");
1254
- let action = spec
1255
- .and_then(|spec| spec.action)
1256
- .unwrap_or("Use team-agent send <agent> 'task message'; select scope with --workspace/--team");
1273
+ let sunset = spec
1274
+ .and_then(|spec| spec.sunset)
1275
+ .unwrap_or("a future compatibility release");
1276
+ let action = spec.and_then(|spec| spec.action).unwrap_or(
1277
+ "Use team-agent send <agent> 'task message'; select scope with --workspace/--team",
1278
+ );
1257
1279
  for flag in FLAGS {
1258
1280
  if args
1259
1281
  .iter()
1260
1282
  .any(|arg| arg == flag || arg.starts_with(&format!("{flag}=")))
1261
1283
  {
1262
- eprintln!("Warning: {flag} is deprecated and will be removed in {sunset}. Next: {action}");
1284
+ eprintln!(
1285
+ "Warning: {flag} is deprecated and will be removed in {sunset}. Next: {action}"
1286
+ );
1263
1287
  }
1264
1288
  }
1265
1289
  }
@@ -2074,7 +2098,11 @@ mod tests {
2074
2098
  .filter(|spec| spec.default_help)
2075
2099
  .map(|spec| spec.name)
2076
2100
  .collect();
2077
- assert_eq!(expected.len(), 29, "the public catalog is Human29");
2101
+ assert_eq!(
2102
+ expected.len(),
2103
+ 31,
2104
+ "the public catalog includes Leader prompt management and the Kiro capability gate"
2105
+ );
2078
2106
  for spec_name in &expected {
2079
2107
  assert!(
2080
2108
  visible.iter().any(|command| command == spec_name),
@@ -2088,7 +2116,7 @@ mod tests {
2088
2116
  expected_sorted.sort();
2089
2117
  assert_eq!(
2090
2118
  actual, expected_sorted,
2091
- "default help must match the exact Human29 public spec set, not a slack threshold; got {visible:?}"
2119
+ "default help must match the exact public spec set, not a slack threshold; got {visible:?}"
2092
2120
  );
2093
2121
  assert!(
2094
2122
  top_help.contains("copilot"),
@@ -2145,7 +2173,10 @@ mod tests {
2145
2173
  !is_machine_command(command),
2146
2174
  "retired Machine route: {command}"
2147
2175
  );
2148
- assert!(!is_known_subcommand(command), "retired help gate: {command}");
2176
+ assert!(
2177
+ !is_known_subcommand(command),
2178
+ "retired help gate: {command}"
2179
+ );
2149
2180
  }
2150
2181
  }
2151
2182
 
@@ -2332,9 +2363,13 @@ mod tests {
2332
2363
  assert_english(&command_help(Some(spec.name)));
2333
2364
  }
2334
2365
  for text in [
2335
- TEAM_TEMPLATE, WORKER_TEMPLATE, HUMAN_NAVIGATION,
2336
- crate::cli::route::HELP, crate::cli::COMMS_BOUNDARY_TEXT,
2337
- crate::cli::QUICK_START_REMINDER, crate::cli::SEND_REMINDER,
2366
+ TEAM_TEMPLATE,
2367
+ WORKER_TEMPLATE,
2368
+ HUMAN_NAVIGATION,
2369
+ crate::cli::route::HELP,
2370
+ crate::cli::COMMS_BOUNDARY_TEXT,
2371
+ crate::cli::QUICK_START_REMINDER,
2372
+ crate::cli::SEND_REMINDER,
2338
2373
  crate::cli::STATUS_REMINDER,
2339
2374
  ] {
2340
2375
  assert_english(text);
@@ -116,6 +116,22 @@ pub fn cmd_leader_passthrough(
116
116
  Ok(CmdResult::from_json(value, as_json))
117
117
  }
118
118
 
119
+ /// Public capability gate, not a legacy passthrough or a native launch attempt.
120
+ pub(crate) fn cmd_kiro_leader(args: &[String]) -> CmdResult {
121
+ CmdResult::from_json(
122
+ json!({
123
+ "schema_version": "leader_launch.v1",
124
+ "ok": false,
125
+ "provider": "kiro",
126
+ "capability": "leader_launch",
127
+ "reason": "kiro_leader_not_admitted",
128
+ "error": "Kiro leader launching is not admitted by this candidate; no native process was started.",
129
+ "action": "Use provider: kiro in a worker role with team-agent quick-start. Worker support does not grant leader capability.",
130
+ }),
131
+ leader_launcher_json(args),
132
+ )
133
+ }
134
+
119
135
  /// Closed-set verb → Provider. `None` = not a passthrough verb (must not become ClaudeCode).
120
136
  pub(crate) fn leader_passthrough_provider(command: &str) -> Option<crate::model::enums::Provider> {
121
137
  use crate::model::enums::Provider;