@team-agent/installer 0.5.112 → 0.5.114

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 (77) 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 +119 -50
  5. package/crates/team-agent/src/cli/diagnose.rs +36 -13
  6. package/crates/team-agent/src/cli/emit.rs +173 -109
  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/leaders.rs +32 -32
  10. package/crates/team-agent/src/cli/mod.rs +158 -14
  11. package/crates/team-agent/src/cli/models.rs +68 -18
  12. package/crates/team-agent/src/cli/named_address.rs +17 -16
  13. package/crates/team-agent/src/cli/route.rs +4 -4
  14. package/crates/team-agent/src/cli/send/presentation.rs +19 -18
  15. package/crates/team-agent/src/cli/send/resolve.rs +11 -11
  16. package/crates/team-agent/src/cli/send.rs +4 -4
  17. package/crates/team-agent/src/cli/spec.rs +35 -29
  18. package/crates/team-agent/src/cli/status.rs +7 -7
  19. package/crates/team-agent/src/cli/status_port/brief.rs +268 -101
  20. package/crates/team-agent/src/cli/tests/base.rs +94 -6
  21. package/crates/team-agent/src/cli/tests/divergence.rs +3 -2
  22. package/crates/team-agent/src/cli/tests/lane_c.rs +1 -1
  23. package/crates/team-agent/src/cli/tests/named_address.rs +3 -3
  24. package/crates/team-agent/src/cli/tests/status_send.rs +9 -7
  25. package/crates/team-agent/src/cli/triage.rs +53 -2
  26. package/crates/team-agent/src/cli/types.rs +8 -8
  27. package/crates/team-agent/src/compiler/tests.rs +12 -0
  28. package/crates/team-agent/src/compiler.rs +65 -26
  29. package/crates/team-agent/src/contract_runtime/backend.rs +646 -0
  30. package/crates/team-agent/src/contract_runtime/config.rs +309 -0
  31. package/crates/team-agent/src/contract_runtime/discovery.rs +237 -0
  32. package/crates/team-agent/src/contract_runtime/forward.rs +229 -0
  33. package/crates/team-agent/src/contract_runtime/framework.rs +261 -0
  34. package/crates/team-agent/src/contract_runtime/mcp.rs +396 -0
  35. package/crates/team-agent/src/contract_runtime/mod.rs +14 -0
  36. package/crates/team-agent/src/contract_runtime/prompt.rs +216 -0
  37. package/crates/team-agent/src/contract_runtime/pump.rs +207 -0
  38. package/crates/team-agent/src/contract_runtime/registry.rs +30 -0
  39. package/crates/team-agent/src/coordinator/tick.rs +26 -8
  40. package/crates/team-agent/src/leader/mod.rs +1 -0
  41. package/crates/team-agent/src/leader/prompt.rs +906 -0
  42. package/crates/team-agent/src/leader/start.rs +265 -16
  43. package/crates/team-agent/src/leader/tests/identity.rs +21 -0
  44. package/crates/team-agent/src/leader/tests/mod.rs +2 -0
  45. package/crates/team-agent/src/leader/tests/prompt_contract.rs +268 -0
  46. package/crates/team-agent/src/leader/types.rs +6 -0
  47. package/crates/team-agent/src/lib.rs +4 -0
  48. package/crates/team-agent/src/lifecycle/launch/identity.rs +172 -13
  49. package/crates/team-agent/src/lifecycle/launch/quick_start.rs +760 -365
  50. package/crates/team-agent/src/lifecycle/launch/spawn.rs +38 -9
  51. package/crates/team-agent/src/lifecycle/launch/state_projection.rs +17 -0
  52. package/crates/team-agent/src/lifecycle/launch.rs +39 -28
  53. package/crates/team-agent/src/lifecycle/profile_launch.rs +58 -46
  54. package/crates/team-agent/src/lifecycle/restart/rebuild.rs +97 -52
  55. package/crates/team-agent/src/lifecycle/restart.rs +1 -1
  56. package/crates/team-agent/src/lifecycle/tests/stale_team_saveconflict_contract.rs +3 -2
  57. package/crates/team-agent/src/lifecycle/types.rs +4 -1
  58. package/crates/team-agent/src/lifecycle/worker_command_context.rs +37 -27
  59. package/crates/team-agent/src/main.rs +6 -0
  60. package/crates/team-agent/src/messaging/delivery.rs +50 -1
  61. package/crates/team-agent/src/messaging/send.rs +10 -0
  62. package/crates/team-agent/src/messaging/tests/leader_inject_acceptance.rs +131 -0
  63. package/crates/team-agent/src/model/spec.rs +24 -12
  64. package/crates/team-agent/src/provider/adapter.rs +8 -59
  65. package/crates/team-agent/src/provider/adapters/claude.rs +6 -14
  66. package/crates/team-agent/src/provider/adapters/codex.rs +8 -18
  67. package/crates/team-agent/src/provider/adapters/copilot.rs +5 -22
  68. package/crates/team-agent/src/provider/adapters/cursor_agent.rs +8 -45
  69. package/crates/team-agent/src/provider/adapters/grok.rs +7 -31
  70. package/crates/team-agent/src/provider/adapters/pi.rs +7 -18
  71. package/crates/team-agent/src/provider/command_helpers.rs +45 -0
  72. package/crates/team-agent/src/provider/mod.rs +1 -0
  73. package/crates/team-agent/src/provider/model_catalog.rs +21 -28
  74. package/crates/team-agent/src/provider/tests/adapter.rs +70 -0
  75. package/crates/team-agent/src/provider/tests/command_helpers.rs +55 -0
  76. package/crates/team-agent/src/state/persist.rs +73 -0
  77. package/package.json +4 -4
@@ -49,12 +49,12 @@ use crate::messaging::{self, MessageTarget, SendOptions, TrustedSender};
49
49
  use crate::model::ids::{TaskId, TeamKey};
50
50
 
51
51
  pub(crate) const COMMS_BOUNDARY_TEXT: &str =
52
- "这里只检查终端连接是否一致,不实际发送消息,也不证明队友已回复。";
53
- pub(crate) const QUICK_START_REMINDER: &str = "下一步:向队友派发任务;用 team-agent status 看状态,用 team-agent inbox leader -n 3 看回复,不读取队友终端内容。";
52
+ "This checks terminal connection consistency only; it does not send a message or prove that a worker replied.";
53
+ pub(crate) const QUICK_START_REMINDER: &str = "Next: send a task. Use team-agent status for status and team-agent inbox leader -n 3 for replies; do not inspect worker terminal content.";
54
54
  pub(crate) const SEND_REMINDER: &str =
55
- "等队友真正回复,用 team-agent inbox leader -n 3 查看;不要读取队友终端内容。";
55
+ "Wait for an actual reply; use team-agent inbox leader -n 3. Do not inspect worker terminal content.";
56
56
  pub(crate) const STATUS_REMINDER: &str =
57
- "下一步:用 team-agent inbox leader -n 3 查看回复;状态不是任务完成证明,不读取队友终端内容。";
57
+ "Next: use team-agent inbox leader -n 3 for replies. Status is not proof of task completion; do not inspect worker terminal content.";
58
58
 
59
59
  pub mod adapters;
60
60
  pub mod attach_app_server_leader;
@@ -66,6 +66,7 @@ pub mod leaders;
66
66
  pub mod models;
67
67
  pub mod named_address;
68
68
  pub mod profile;
69
+ pub(crate) mod leader_prompt;
69
70
  pub(crate) mod route;
70
71
  pub mod send;
71
72
  pub(crate) mod spec;
@@ -172,12 +173,18 @@ pub mod lifecycle_port {
172
173
  Err(crate::leader::start::PrepareLeaderStartError::PaneAuthorityRefused(refusal)) => {
173
174
  return Ok(pane_authority_refusal_value(provider, attach, &refusal));
174
175
  }
176
+ Err(crate::leader::start::PrepareLeaderStartError::Leader(
177
+ crate::leader::LeaderError::Prompt(error),
178
+ )) => return Ok(error.report()),
175
179
  Err(crate::leader::start::PrepareLeaderStartError::Leader(error)) => {
176
180
  return Err(CliError::Runtime(error.to_string()));
177
181
  }
178
182
  };
179
- let outcome = crate::leader::start::execute_prepared_leader_start(&prepared, cwd)
180
- .map_err(|e| CliError::Runtime(e.to_string()))?;
183
+ let outcome = match crate::leader::start::execute_prepared_leader_start(&prepared, cwd) {
184
+ Ok(outcome) => outcome,
185
+ Err(crate::leader::LeaderError::Prompt(error)) => return Ok(error.report()),
186
+ Err(error) => return Err(CliError::Runtime(error.to_string())),
187
+ };
181
188
  let plan = prepared.plan();
182
189
  let ok = match outcome.status {
183
190
  crate::leader::LeaderLaunchStatus::Exited => outcome.exit_code == Some(0),
@@ -1297,6 +1304,16 @@ pub mod lifecycle_port {
1297
1304
  Some(state) => state,
1298
1305
  None => shutdown_state_for_team(&run_workspace, team)?,
1299
1306
  };
1307
+ #[cfg(unix)]
1308
+ if crate::contract_runtime::framework::has_contract(&state) {
1309
+ crate::contract_runtime::framework::stop_selected(&run_workspace, &state).map_err(
1310
+ |error| {
1311
+ CliError::Runtime(format!(
1312
+ "native shutdown failed; owned resources preserved: {error}"
1313
+ ))
1314
+ },
1315
+ )?;
1316
+ }
1300
1317
  deadline.check("refresh_provider_sessions")?;
1301
1318
  let captured_missing_sessions =
1302
1319
  crate::lifecycle::restart::refresh_missing_provider_sessions(&mut state)
@@ -3108,6 +3125,10 @@ pub mod lifecycle_port {
3108
3125
  team: Option<&str>,
3109
3126
  session_converge_deadline_ms: Option<u64>,
3110
3127
  ) -> Result<Value, CliError> {
3128
+ #[cfg(unix)]
3129
+ crate::contract_runtime::framework::require_legacy_lifecycle(
3130
+ workspace, team, None, "restart",
3131
+ )?;
3111
3132
  match crate::lifecycle::restart_with_session_convergence_deadline(
3112
3133
  workspace,
3113
3134
  allow_fresh,
@@ -3128,6 +3149,19 @@ pub mod lifecycle_port {
3128
3149
  team: Option<&str>,
3129
3150
  role_config: &crate::lifecycle::role_config::RoleConfigPatch,
3130
3151
  ) -> Result<Value, CliError> {
3152
+ #[cfg(unix)]
3153
+ {
3154
+ crate::contract_runtime::framework::require_legacy_lifecycle(
3155
+ workspace,
3156
+ team,
3157
+ Some(agent),
3158
+ "start-agent",
3159
+ )?;
3160
+ crate::contract_runtime::framework::require_legacy_role(
3161
+ None,
3162
+ role_config.provider.as_deref(),
3163
+ )?;
3164
+ }
3131
3165
  let _ = force; // Never grants authority to stop an already-running seat.
3132
3166
  let agent_id = crate::model::ids::AgentId::new(agent);
3133
3167
  match crate::lifecycle::role_config::start_agent_from_role(
@@ -3195,6 +3229,13 @@ pub mod lifecycle_port {
3195
3229
  team: Option<&str>,
3196
3230
  ) -> Result<Value, CliError> {
3197
3231
  let agent_id = crate::model::ids::AgentId::new(agent);
3232
+ #[cfg(unix)]
3233
+ crate::contract_runtime::framework::require_legacy_lifecycle(
3234
+ workspace,
3235
+ team,
3236
+ Some(agent),
3237
+ "stop-agent",
3238
+ )?;
3198
3239
  match crate::lifecycle::stop_agent(workspace, &agent_id, team) {
3199
3240
  Ok(report) => Ok(json!({"ok": true, "agent_id": agent, "stopped": report.stopped})),
3200
3241
  Err(e) => Ok(error_value(e)),
@@ -3209,6 +3250,13 @@ pub mod lifecycle_port {
3209
3250
  team: Option<&str>,
3210
3251
  ) -> Result<Value, CliError> {
3211
3252
  let agent_id = crate::model::ids::AgentId::new(agent);
3253
+ #[cfg(unix)]
3254
+ crate::contract_runtime::framework::require_legacy_lifecycle(
3255
+ workspace,
3256
+ team,
3257
+ Some(agent),
3258
+ "reset-agent",
3259
+ )?;
3212
3260
  match crate::lifecycle::reset_agent(workspace, &agent_id, discard_session, true, team) {
3213
3261
  Ok(crate::lifecycle::ResetAgentOutcome::Reset {
3214
3262
  env,
@@ -3256,6 +3304,19 @@ pub mod lifecycle_port {
3256
3304
  ) -> Result<Value, CliError> {
3257
3305
  let agent_id = crate::model::ids::AgentId::new(agent);
3258
3306
  let source = (!role_file.is_empty()).then(|| Path::new(role_file));
3307
+ #[cfg(unix)]
3308
+ {
3309
+ crate::contract_runtime::framework::require_legacy_lifecycle(
3310
+ workspace,
3311
+ team,
3312
+ Some(agent),
3313
+ "add-agent",
3314
+ )?;
3315
+ crate::contract_runtime::framework::require_legacy_role(
3316
+ source,
3317
+ role_config.provider.as_deref(),
3318
+ )?;
3319
+ }
3259
3320
  match crate::lifecycle::role_config::add_agent_from_role(
3260
3321
  workspace,
3261
3322
  &agent_id,
@@ -3308,6 +3369,13 @@ pub mod lifecycle_port {
3308
3369
  ) -> Result<Value, CliError> {
3309
3370
  let source = crate::model::ids::AgentId::new(source_agent);
3310
3371
  let dest = crate::model::ids::AgentId::new(as_agent_id);
3372
+ #[cfg(unix)]
3373
+ crate::contract_runtime::framework::require_legacy_lifecycle(
3374
+ workspace,
3375
+ team,
3376
+ Some(source_agent),
3377
+ "fork-agent",
3378
+ )?;
3311
3379
  match crate::lifecycle::fork_agent(workspace, &source, &dest, label, true, team) {
3312
3380
  Ok(report) => {
3313
3381
  let mut value = json!({
@@ -3345,6 +3413,13 @@ pub mod lifecycle_port {
3345
3413
  ) -> Result<Value, CliError> {
3346
3414
  let source = crate::model::ids::AgentId::new(source_agent);
3347
3415
  let dest = crate::model::ids::AgentId::new(as_agent_id);
3416
+ #[cfg(unix)]
3417
+ crate::contract_runtime::framework::require_legacy_lifecycle(
3418
+ workspace,
3419
+ team,
3420
+ Some(source_agent),
3421
+ "clone-agent",
3422
+ )?;
3348
3423
  match crate::lifecycle::clone_agent(workspace, &source, &dest, label, true, team) {
3349
3424
  Ok(report) => Ok(json!({
3350
3425
  "ok": true,
@@ -3370,6 +3445,13 @@ pub mod lifecycle_port {
3370
3445
  team: Option<&str>,
3371
3446
  ) -> Result<Value, CliError> {
3372
3447
  let agent_id = crate::model::ids::AgentId::new(agent);
3448
+ #[cfg(unix)]
3449
+ crate::contract_runtime::framework::require_legacy_lifecycle(
3450
+ workspace,
3451
+ team,
3452
+ Some(agent),
3453
+ "remove-agent",
3454
+ )?;
3373
3455
  match crate::lifecycle::remove_agent_flag_requirements(workspace, &agent_id, team) {
3374
3456
  Ok(requirements) => {
3375
3457
  if !remove_agent_missing_flags(from_spec, confirm, force, &requirements).is_empty()
@@ -3712,7 +3794,13 @@ pub mod lifecycle_port {
3712
3794
  // tool-set load has not been confirmed yet (PendingToolLoad).
3713
3795
  let incomplete_session_capture_agents =
3714
3796
  launch.session_capture_incomplete_agents.clone();
3715
- let all_spawned = !launch.started.is_empty();
3797
+ let agent_ids: Vec<_> = launch
3798
+ .started
3799
+ .iter()
3800
+ .map(|agent| agent.agent_id.as_str())
3801
+ .chain(launch.contract_started.iter().map(|id| id.as_str()))
3802
+ .collect();
3803
+ let all_spawned = !agent_ids.is_empty();
3716
3804
  let leader_receiver_attached = launch.leader_receiver_attached;
3717
3805
  let all_resumable_have_session = incomplete_session_capture_agents.is_empty();
3718
3806
  let all_workers_spawned = all_spawned;
@@ -3840,7 +3928,7 @@ pub mod lifecycle_port {
3840
3928
  "ready": readiness_json.get("ready").cloned().unwrap_or(Value::Bool(false)),
3841
3929
  "session_name": session_name.as_str(),
3842
3930
  "team": team,
3843
- "agent_ids": launch.started.iter().map(|agent| agent.agent_id.as_str()).collect::<Vec<_>>(),
3931
+ "agent_ids": agent_ids,
3844
3932
  "dry_run": launch.dry_run,
3845
3933
  "next_actions": next_actions,
3846
3934
  "attach_commands": attach_commands,
@@ -3961,9 +4049,8 @@ pub mod lifecycle_port {
3961
4049
  .is_some());
3962
4050
  }
3963
4051
 
3964
- #[test]
3965
- fn pending_tool_load_success_output_explains_bound_send_without_doctor() {
3966
- let value = quick_start_value(crate::lifecycle::QuickStartReport::Ready {
4052
+ fn pending_tool_load_report() -> crate::lifecycle::QuickStartReport {
4053
+ crate::lifecycle::QuickStartReport::Ready {
3967
4054
  session_name: crate::transport::SessionName::new("team-demo"),
3968
4055
  launch: Box::new(crate::lifecycle::LaunchReport {
3969
4056
  session_name: crate::transport::SessionName::new("team-demo"),
@@ -3979,6 +4066,7 @@ pub mod lifecycle_port {
3979
4066
  provider_projects_root: None,
3980
4067
  managed_mcp_config: false,
3981
4068
  }],
4069
+ contract_started: Vec::new(),
3982
4070
  dry_run: false,
3983
4071
  tmux_endpoint: None,
3984
4072
  routes: Vec::new(),
@@ -3991,7 +4079,63 @@ pub mod lifecycle_port {
3991
4079
  attach_commands: Vec::new(),
3992
4080
  worker_readiness: crate::lifecycle::QuickStartReadiness::PendingToolLoad,
3993
4081
  team: "team-demo".to_string(),
3994
- });
4082
+ }
4083
+ }
4084
+
4085
+ #[test]
4086
+ fn contract_start_receipts_join_summary_without_bypassing_leader_binding() {
4087
+ for (legacy, contract) in [(false, true), (true, true), (true, false), (false, false)] {
4088
+ for bound in [false, true] {
4089
+ let mut report = pending_tool_load_report();
4090
+ let crate::lifecycle::QuickStartReport::Ready { launch, .. } = &mut report else {
4091
+ panic!("ready fixture required");
4092
+ };
4093
+ if !legacy {
4094
+ launch.started.clear();
4095
+ }
4096
+ if contract {
4097
+ launch
4098
+ .contract_started
4099
+ .push(crate::model::ids::AgentId::new("kiro-worker"));
4100
+ }
4101
+ launch.leader_receiver_attached = bound;
4102
+ launch.leader_bind_stage = (!bound).then(|| "caller_pane".into());
4103
+ launch.leader_bind_reason = (!bound).then(|| "caller_pane_missing".into());
4104
+ assert_eq!(launch.started.len(), usize::from(legacy));
4105
+ let expected: Vec<_> = [(legacy, "worker"), (contract, "kiro-worker")]
4106
+ .into_iter()
4107
+ .filter_map(|(started, id)| started.then_some(id))
4108
+ .collect();
4109
+ let value = quick_start_value(report);
4110
+ assert_eq!(value["agent_ids"], json!(expected));
4111
+ assert_eq!(
4112
+ value["readiness"]["all_workers_spawned"],
4113
+ json!(legacy || contract)
4114
+ );
4115
+ assert_eq!(value["readiness"]["all_spawned"], json!(legacy || contract));
4116
+ assert_eq!(value["readiness"]["leader_receiver_attached"], json!(bound));
4117
+ assert_eq!(value["ok"], json!((legacy || contract) && bound));
4118
+ assert_eq!(value["ready"], json!((legacy || contract) && bound));
4119
+ if !bound {
4120
+ assert_eq!(value["status"], "leader_binding_incomplete");
4121
+ assert_eq!(value["reason"], "caller_pane_missing");
4122
+ } else {
4123
+ assert_eq!(value["status"], "pending_tool_load");
4124
+ }
4125
+ let mut compact = value.clone();
4126
+ compact_quick_start_value(&mut compact);
4127
+ assert_eq!(
4128
+ compact["worker_readiness"]["all_workers_spawned"],
4129
+ json!(legacy || contract)
4130
+ );
4131
+ assert_eq!(compact["ready"], value["ready"]);
4132
+ }
4133
+ }
4134
+ }
4135
+
4136
+ #[test]
4137
+ fn pending_tool_load_success_output_explains_bound_send_without_doctor() {
4138
+ let value = quick_start_value(pending_tool_load_report());
3995
4139
  assert_eq!(
3996
4140
  value.get("summary").and_then(Value::as_str),
3997
4141
  Some("team started; leader bound; send a task next (worker tool load unverified): team-demo")
@@ -4176,7 +4320,7 @@ pub mod lifecycle_port {
4176
4320
  "team-agent".to_string(),
4177
4321
  "send".to_string(),
4178
4322
  "implementer".to_string(),
4179
- "请完成任务并把答案回复给 leader。".to_string(),
4323
+ "Complete the task and reply to the leader.".to_string(),
4180
4324
  "--workspace".to_string(),
4181
4325
  team_dir.to_string_lossy().into_owned(),
4182
4326
  "--team".to_string(),
@@ -4213,7 +4357,7 @@ pub mod lifecycle_port {
4213
4357
  "team-agent".to_string(),
4214
4358
  "send".to_string(),
4215
4359
  "implementer".to_string(),
4216
- "请完成任务并把答案回复给 leader。".to_string(),
4360
+ "Complete the task and reply to the leader.".to_string(),
4217
4361
  "--workspace".to_string(),
4218
4362
  team_dir.to_string_lossy().into_owned(),
4219
4363
  "--team".to_string(),
@@ -22,6 +22,10 @@ enum CatalogFormat {
22
22
  }
23
23
 
24
24
  pub fn cmd_models(args: &ModelsArgs) -> Result<CmdResult, CliError> {
25
+ #[cfg(unix)]
26
+ if crate::contract_runtime::registry::descriptor(&args.provider).is_some() {
27
+ return crate::contract_runtime::discovery::models(args);
28
+ }
25
29
  let records = match crate::provider::model_catalog::discover_model_catalog(&args.provider) {
26
30
  Ok(records) => records,
27
31
  Err(error) => {
@@ -36,7 +40,12 @@ pub fn cmd_models(args: &ModelsArgs) -> Result<CmdResult, CliError> {
36
40
  fn render_catalog(args: &ModelsArgs, all: Vec<ModelRecord>) -> Result<CmdResult, CliError> {
37
41
  let visible = args.search.as_deref().map_or_else(
38
42
  || all.clone(),
39
- |search| all.iter().filter(|model| crate::provider::model_catalog::model_matches(model, search)).cloned().collect(),
43
+ |search| {
44
+ all.iter()
45
+ .filter(|model| crate::provider::model_catalog::model_matches(model, search))
46
+ .cloned()
47
+ .collect()
48
+ },
40
49
  );
41
50
  let current = if args.provider == "pi" {
42
51
  current_role_model(&all.iter().map(|model| model.id.clone()).collect::<Vec<_>>())
@@ -66,14 +75,31 @@ fn render_catalog(args: &ModelsArgs, all: Vec<ModelRecord>) -> Result<CmdResult,
66
75
  preserve_json_order: true,
67
76
  })
68
77
  } else {
69
- let mut lines = vec![format!("models.v1 | {provider} models (copyable model_id):")];
78
+ let mut lines = vec![format!(
79
+ "models.v1 | {provider} models (copyable model_id):"
80
+ )];
70
81
  lines.push("PROVIDER VENDOR MODEL_ID DISPLAY_NAME CURRENT DEFAULT".to_string());
71
- lines.extend(visible.iter().map(|model| format!(
72
- "{} {} {} {} {} {}", model.provider, model.vendor, model.id, model.display_name,
73
- if args.provider == "pi" { (current.as_deref() == Some(model.id.as_str())).to_string() } else { "—".to_string() },
74
- model.default.map_or_else(|| "—".to_string(), |value| value.to_string()),
75
- )));
76
- lines.push(format!("auth: ok (catalog_visibility); {} model(s)", visible.len()));
82
+ lines.extend(visible.iter().map(|model| {
83
+ format!(
84
+ "{} {} {} {} {} {}",
85
+ model.provider,
86
+ model.vendor,
87
+ model.id,
88
+ model.display_name,
89
+ if args.provider == "pi" {
90
+ (current.as_deref() == Some(model.id.as_str())).to_string()
91
+ } else {
92
+ "—".to_string()
93
+ },
94
+ model
95
+ .default
96
+ .map_or_else(|| "—".to_string(), |value| value.to_string()),
97
+ )
98
+ }));
99
+ lines.push(format!(
100
+ "auth: ok (catalog_visibility); {} model(s)",
101
+ visible.len()
102
+ ));
77
103
  if visible.is_empty() {
78
104
  lines.push("No models matched --search/query; rerun without a search to list the full catalog.".to_string());
79
105
  }
@@ -83,12 +109,23 @@ fn render_catalog(args: &ModelsArgs, all: Vec<ModelRecord>) -> Result<CmdResult,
83
109
 
84
110
  /// Existing injectable Pi boundary retained for deterministic command/timeout tests.
85
111
  #[cfg(test)]
86
- fn cmd_models_with(args: &ModelsArgs, program: &Path, timeout: Duration, max_bytes: u64) -> Result<CmdResult, CliError> {
112
+ fn cmd_models_with(
113
+ args: &ModelsArgs,
114
+ program: &Path,
115
+ timeout: Duration,
116
+ max_bytes: u64,
117
+ ) -> Result<CmdResult, CliError> {
87
118
  cmd_models_with_format(args, program, CatalogFormat::Pi, timeout, max_bytes)
88
119
  }
89
120
 
90
121
  #[cfg(test)]
91
- fn cmd_models_with_format(args: &ModelsArgs, program: &Path, format: CatalogFormat, timeout: Duration, max_bytes: u64) -> Result<CmdResult, CliError> {
122
+ fn cmd_models_with_format(
123
+ args: &ModelsArgs,
124
+ program: &Path,
125
+ format: CatalogFormat,
126
+ timeout: Duration,
127
+ max_bytes: u64,
128
+ ) -> Result<CmdResult, CliError> {
92
129
  let bytes = match run_catalog(program, timeout, max_bytes) {
93
130
  Ok(bytes) => bytes,
94
131
  Err(message) => return Ok(failure(args, &message)),
@@ -102,7 +139,10 @@ fn cmd_models_with_format(args: &ModelsArgs, program: &Path, format: CatalogForm
102
139
 
103
140
  #[cfg(test)]
104
141
  fn parse_catalog(bytes: &[u8], format: CatalogFormat) -> Result<Vec<ModelRecord>, String> {
105
- let provider = match format { CatalogFormat::Pi => "pi", CatalogFormat::CursorAgent => "cursor_agent" };
142
+ let provider = match format {
143
+ CatalogFormat::Pi => "pi",
144
+ CatalogFormat::CursorAgent => "cursor_agent",
145
+ };
106
146
  let result = match format {
107
147
  CatalogFormat::Pi => crate::provider::model_catalog::parse_pi_catalog(bytes),
108
148
  CatalogFormat::CursorAgent => crate::provider::model_catalog::parse_cursor_catalog(bytes),
@@ -124,7 +164,12 @@ fn model_matches(model: &ModelRecord, search: &str) -> bool {
124
164
  #[cfg(test)]
125
165
  fn current_model_for(format: CatalogFormat, models: &[ModelRecord]) -> Option<String> {
126
166
  match format {
127
- CatalogFormat::Pi => current_role_model(&models.iter().map(|model| model.id.clone()).collect::<Vec<_>>()),
167
+ CatalogFormat::Pi => current_role_model(
168
+ &models
169
+ .iter()
170
+ .map(|model| model.id.clone())
171
+ .collect::<Vec<_>>(),
172
+ ),
128
173
  CatalogFormat::CursorAgent => None,
129
174
  }
130
175
  }
@@ -148,8 +193,14 @@ fn failure(args: &ModelsArgs, message: &str) -> CmdResult {
148
193
  fn failure_with_action(args: &ModelsArgs, message: &str, action: &str) -> CmdResult {
149
194
  let value = json!({ "schema_version": "models.v1", "ok": false, "provider": args.provider, "auth": "not_ready", "auth_basis": "catalog_visibility", "models": Value::Array(Vec::new()), "current_role_model": Value::Null, "error": message, "action": action });
150
195
  if args.json {
151
- let text = serde_json::to_string_pretty(&value).unwrap_or_else(|_| "{\"schema_version\":\"models.v1\",\"ok\":false}".to_string());
152
- CmdResult { output: CmdOutput::Human(text), exit: ExitCode::Error, as_json: false, preserve_json_order: true }
196
+ let text = serde_json::to_string_pretty(&value)
197
+ .unwrap_or_else(|_| "{\"schema_version\":\"models.v1\",\"ok\":false}".to_string());
198
+ CmdResult {
199
+ output: CmdOutput::Human(text),
200
+ exit: ExitCode::Error,
201
+ as_json: false,
202
+ preserve_json_order: true,
203
+ }
153
204
  } else {
154
205
  CmdResult { output: CmdOutput::Human(format!("models.v1 | error: {message}\naction: {action}\nauth: not_ready (catalog_visibility)")), exit: ExitCode::Error, as_json: false, preserve_json_order: false }
155
206
  }
@@ -1271,10 +1322,9 @@ mod tests {
1271
1322
  ),
1272
1323
  ExitCode::Error
1273
1324
  );
1274
- assert_eq!(
1275
- crate::cli::spec::command_spec("models").unwrap().usage,
1276
- "team-agent models [--provider pi|cursor_agent|codex|claude|claude_code|grok] [QUERY|--search TEXT] [--json]"
1277
- );
1325
+ let usage = "team-agent models [--provider pi|cursor_agent|codex|claude|claude_code|grok|kiro] [QUERY|--search TEXT] [--json]";
1326
+ assert_eq!(crate::cli::spec::command_spec("models").unwrap().usage, usage);
1327
+ assert!(crate::cli::emit::command_help(Some("models")).contains(usage));
1278
1328
  }
1279
1329
 
1280
1330
  #[test]
@@ -78,16 +78,16 @@ pub(crate) struct NamedAddressError {
78
78
 
79
79
  pub(crate) fn human_address_reason(reason: &str) -> Option<&'static str> {
80
80
  Some(match reason {
81
- "name_invalid" => "收信队友名不正确;请使用队友列表中的名称。",
82
- "workspace_not_found" => "所选项目目录不存在;请核对项目路径。",
81
+ "name_invalid" => "Invalid recipient name; use a name from the agent list.",
82
+ "workspace_not_found" => "The selected workspace directory does not exist; verify the path.",
83
83
  "state_not_found" | "workspace_no_state" => {
84
- "所选项目还没有可用队伍;首次起队用 quick-start,已有队伍恢复用 restart。"
84
+ "No usable team exists in the selected workspace; use quick-start for initial creation or restart for an initialized team."
85
85
  }
86
- "team_key_not_found" => "所选项目没有这支队伍;请核对 --workspace 和 --team。",
87
- "leader_not_attached" => "主控还没有连接到这支队伍;请先体检,按提示恢复连接。",
88
- "name_not_resolvable" => "当前队伍没有这个队友;请先查看队友列表。",
89
- "name_not_live" => "该队友当前未运行或连接已失效;请查看状态再恢复。",
90
- "name_ambiguous" => "有多个同名对象;请明确选择项目和队伍,不猜对象。",
86
+ "team_key_not_found" => "The selected workspace has no such team; verify --workspace and --team.",
87
+ "leader_not_attached" => "No leader is attached to this team; diagnose it and follow the recovery action.",
88
+ "name_not_resolvable" => "This team has no such agent; check the agent list.",
89
+ "name_not_live" => "The agent is not running or its channel is stale; check status before recovery.",
90
+ "name_ambiguous" => "Multiple targets share this name; select an explicit workspace and team.",
91
91
  _ => return None,
92
92
  })
93
93
  }
@@ -121,23 +121,24 @@ impl NamedAddressError {
121
121
  pub(crate) fn human_guidance(&self, requested: &str) -> (String, String) {
122
122
  let workspace = self.human_scope.as_deref();
123
123
  let mut message = format!(
124
- "{}\n你刚才填写的是 {requested:?}。",
125
- human_address_reason(self.kind.as_str()).unwrap_or("未能找到可用的收信队友。")
124
+ "{}\nRequested recipient: {requested:?}\nReason: {}",
125
+ human_address_reason(self.kind.as_str()).unwrap_or("No usable recipient could be resolved."),
126
+ self.message
126
127
  );
127
128
  if let Some(workspace) = workspace {
128
- message.push_str(&format!("\n所选项目:{:?}。", workspace.to_string_lossy()));
129
+ message.push_str(&format!("\nSelected workspace: {:?}", workspace.to_string_lossy()));
129
130
  }
130
131
  if let Some(team) = self.human_team.as_deref() {
131
- message.push_str(&format!("\n所选队伍:{team:?}。"));
132
+ message.push_str(&format!("\nSelected team: {team:?}"));
132
133
  }
133
- let mut action = "没有可靠的拼写建议;请先核对项目和队伍,不猜对象。".to_string();
134
+ let mut action = "No reliable spelling suggestion is available; verify the workspace and team before choosing a target.".to_string();
134
135
  for candidate in &self.candidates {
135
136
  if let (Some(agent), Some(team)) = (
136
137
  candidate.get("agent_id").and_then(Value::as_str),
137
138
  candidate.get("team_key").and_then(Value::as_str),
138
139
  ) {
139
140
  message.push_str(&format!(
140
- "\n候选队伍 {team:?},队友 {agent:?};请确认是否是你要找的对象。"
141
+ "\nCandidate team {team:?}, agent {agent:?}; confirm the intended target."
141
142
  ));
142
143
  }
143
144
  }
@@ -162,7 +163,7 @@ impl NamedAddressError {
162
163
  if let Some(command) =
163
164
  super::adapters::send_command(agent, workspace, Some(team))
164
165
  {
165
- action = format!("你是否想发给 {agent:?}、队伍 {team:?}?确认后将任务内容替换到这个例子;本次没有发送:\n{command}");
166
+ action = format!("Did you mean agent {agent:?} in team {team:?}? After confirmation, replace the example message below. Nothing was sent:\n{command}");
166
167
  }
167
168
  }
168
169
  }
@@ -171,7 +172,7 @@ impl NamedAddressError {
171
172
  workspace.filter(|path| !path.to_string_lossy().chars().any(char::is_control))
172
173
  {
173
174
  action.push_str(&format!(
174
- "\n也可查看可用队友列表:\nteam-agent status --workspace {}",
175
+ "\nList available agents with:\nteam-agent status --workspace {}",
175
176
  super::adapters::shell_quote(&workspace.to_string_lossy())
176
177
  ));
177
178
  }
@@ -5,7 +5,7 @@ use crate::provider::argv_route::{self, Config, Mutation, Override, RouteError};
5
5
  use crate::provider::Provider;
6
6
  use serde_json::{json, Value};
7
7
 
8
- pub(crate) const HELP: &str = "做什么:\n设置工具启动时额外使用的命令行参数,默认关闭。\n用法:team-agent route [status|enable|disable|show [TOOL]|set TOOL -- ARG...|add TOOL -- ARG...|clear TOOL] [--json]\n\n怎么用:\nstatus 看开关;enable 开启;disable 关闭;show 看全部或指定工具的参数。\nset 替换参数;add 追加参数;clear 清除指定工具的参数;--json 给程序读取。\nTOOL 是已支持的工具名称,如 pi/codex/claude/copilot/grok/cursor_agent。\n第一个 -- 后全是工具的字面参数,包含 --help/--json 也不会被这里解析。\n配置保存在 ~/.team-agent/argv-routing.json;影响全机,不按项目或队伍隔离。\nTEAM_AGENT_CLI_ARGV_ROUTING 环境变量优先于保存的开关。\nExamples(可复制):\nteam-agent route status\nteam-agent route set pi -- --mode text\nteam-agent route enable\n\n下一步(Next Action):\n用 route show pi 核对参数;仅影响下一次启动,不改变正在运行的对话。\n没有开启或没有配置时,工具仍按原参数启动;不了解参数用途时保持关闭。";
8
+ pub(crate) const HELP: &str = "Purpose:\nConfigure additional provider launch arguments. Disabled by default.\nUsage: team-agent route [status|enable|disable|show [TOOL]|set TOOL -- ARG...|add TOOL -- ARG...|clear TOOL] [--json]\n\nOptions:\nstatus reports the switch; enable/disable change it; show lists all or one provider's arguments.\nset replaces arguments; add appends; clear removes a provider's arguments. Use --json for structured output.\nTOOL is a supported provider name, such as pi/codex/claude/copilot/grok/cursor_agent.\nEverything after the first -- is a literal provider argument, including --help and --json.\nConfiguration: ~/.team-agent/argv-routing.json. This is host-global, not isolated by workspace or team.\nTEAM_AGENT_CLI_ARGV_ROUTING overrides the persisted switch.\nExamples:\nteam-agent route status\nteam-agent route set pi -- --mode text\nteam-agent route enable\n\nNext Action:\nVerify arguments with route show pi. Changes apply to the next launch, not an existing session.\nWithout an enabled configuration, providers retain their original arguments. Leave routing disabled unless the arguments are understood.";
9
9
 
10
10
  struct Request {
11
11
  operation: String,
@@ -16,9 +16,9 @@ struct Request {
16
16
 
17
17
  fn usage() -> RouteError {
18
18
  RouteError {
19
- error: "参数不完整或不正确;set/add 需要工具名、-- 和至少一个工具参数".to_string(),
19
+ error: "Invalid or incomplete arguments; set/add require a provider name, -- and at least one provider argument".to_string(),
20
20
  reason: "argv_route_usage",
21
- action: "请按 team-agent route --help 的 Examples 填写;例如 team-agent route set pi -- --mode text".to_string(),
21
+ action: "Follow the Examples in team-agent route --help, such as team-agent route set pi -- --mode text".to_string(),
22
22
  config_path: None,
23
23
  }
24
24
  }
@@ -118,7 +118,7 @@ fn execute(request: &Request) -> Result<Value, RouteError> {
118
118
  }
119
119
  if override_state != Override::Unset {
120
120
  value["notice"] = json!(format!(
121
- "当前开关由环境变量 {} 决定({})",
121
+ "Switch overridden by environment variable {} ({})",
122
122
  argv_route::ENV_NAME,
123
123
  override_state.status()
124
124
  ));