@team-agent/installer 0.5.109 → 0.5.111

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 (34) hide show
  1. package/Cargo.lock +1 -1
  2. package/Cargo.toml +1 -1
  3. package/README.md +33 -0
  4. package/README.zh.md +30 -0
  5. package/crates/team-agent/src/cli/adapters.rs +234 -73
  6. package/crates/team-agent/src/cli/emit.rs +494 -201
  7. package/crates/team-agent/src/cli/leader.rs +4 -9
  8. package/crates/team-agent/src/cli/leaders.rs +90 -29
  9. package/crates/team-agent/src/cli/mod.rs +348 -161
  10. package/crates/team-agent/src/cli/models.rs +80 -24
  11. package/crates/team-agent/src/cli/named_address.rs +114 -25
  12. package/crates/team-agent/src/cli/route.rs +212 -0
  13. package/crates/team-agent/src/cli/send/presentation.rs +60 -41
  14. package/crates/team-agent/src/cli/send/resolve.rs +25 -18
  15. package/crates/team-agent/src/cli/send.rs +4 -4
  16. package/crates/team-agent/src/cli/spec.rs +56 -106
  17. package/crates/team-agent/src/cli/tests/base.rs +189 -82
  18. package/crates/team-agent/src/cli/tests/lane_c.rs +1 -2
  19. package/crates/team-agent/src/cli/tests/named_address.rs +19 -22
  20. package/crates/team-agent/src/cli/tests/status_send.rs +15 -10
  21. package/crates/team-agent/src/cli/tests/verb_install_skill.rs +2 -2
  22. package/crates/team-agent/src/cli/types.rs +22 -13
  23. package/crates/team-agent/src/leader/registry.rs +3 -3
  24. package/crates/team-agent/src/leader/start.rs +5 -1
  25. package/crates/team-agent/src/lifecycle/launch/spawn.rs +2 -0
  26. package/crates/team-agent/src/lifecycle/restart/common.rs +2 -0
  27. package/crates/team-agent/src/lifecycle/tests/cli_argv_routing_red.rs +1457 -0
  28. package/crates/team-agent/src/lifecycle/tests/pi_dual_entry_red.rs +4 -9
  29. package/crates/team-agent/src/lifecycle/tests/stale_team_saveconflict_contract.rs +3 -1
  30. package/crates/team-agent/src/lifecycle/tests.rs +1 -0
  31. package/crates/team-agent/src/provider/argv_route.rs +409 -0
  32. package/crates/team-agent/src/provider/mod.rs +3 -2
  33. package/package.json +4 -4
  34. package/skills/team-agent/SKILL.md +32 -0
@@ -105,24 +105,19 @@ pub fn cmd_leader_passthrough(
105
105
  cwd: &Path,
106
106
  ) -> Result<CmdResult, CliError> {
107
107
  if provider_args == ["-h"] || provider_args == ["--help"] {
108
- return Ok(CmdResult::none());
108
+ return Ok(CmdResult::human(&super::emit::command_help(Some(command))));
109
109
  }
110
110
  let as_json = leader_launcher_json(provider_args);
111
111
  let launcher_args = without_leader_json(provider_args);
112
112
  let attach = leader_launcher_args(&launcher_args)?;
113
- let provider = leader_passthrough_provider(command).ok_or_else(|| {
114
- CliError::Usage(format!(
115
- "unknown leader passthrough command: {command}"
116
- ))
117
- })?;
113
+ let provider = leader_passthrough_provider(command)
114
+ .ok_or_else(|| CliError::Usage(format!("unknown leader passthrough command: {command}")))?;
118
115
  let value = lifecycle_port::start_leader(provider, &attach.provider_args, cwd, &attach)?;
119
116
  Ok(CmdResult::from_json(value, as_json))
120
117
  }
121
118
 
122
119
  /// Closed-set verb → Provider. `None` = not a passthrough verb (must not become ClaudeCode).
123
- pub(crate) fn leader_passthrough_provider(
124
- command: &str,
125
- ) -> Option<crate::model::enums::Provider> {
120
+ pub(crate) fn leader_passthrough_provider(command: &str) -> Option<crate::model::enums::Provider> {
126
121
  use crate::model::enums::Provider;
127
122
  match command {
128
123
  "codex" => Some(Provider::Codex),
@@ -182,20 +182,39 @@ fn quote_cli_arg(raw: &str) -> String {
182
182
  fn format_leaders_human(rows: &[LeaderRow], view: LeadersView) -> String {
183
183
  if rows.is_empty() {
184
184
  return match view {
185
- LeadersView::Live => "no live leaders\n".to_string(),
186
- LeadersView::All => "no registered leaders\n".to_string(),
187
- LeadersView::Stale => "no stale leaders\n".to_string(),
185
+ LeadersView::Live => {
186
+ "没有可用主控;运行 team-agent doctor --workspace . 检查当前队伍。\n".to_string()
187
+ }
188
+ LeadersView::All => "没有已登记主控;首次协作用 team-agent pi 打开主控。\n".to_string(),
189
+ LeadersView::Stale => "没有已失效的主控登记。\n".to_string(),
188
190
  };
189
191
  }
190
- let mut out = String::from("STATUS\tWORKSPACE\tTEAM\tSEND\n");
192
+ let mut out = String::from("主控\t状态\t项目\t队伍\t发送命令\n");
191
193
  for row in rows {
192
- out.push_str(row.status);
194
+ out.push_str("leader\t");
195
+ out.push_str(match row.status {
196
+ "LIVE" => "可用",
197
+ "STALE" => "已失效",
198
+ "AMBIGUOUS" => "需确认归属",
199
+ other => other,
200
+ });
193
201
  out.push('\t');
194
202
  out.push_str(&row.entry.workspace.display().to_string());
195
203
  out.push('\t');
196
204
  out.push_str(&row.entry.team_key);
197
205
  out.push('\t');
198
- out.push_str(&row.send_hint);
206
+ if row.status == "STALE" {
207
+ out.push('-');
208
+ } else {
209
+ out.push_str(
210
+ &super::adapters::send_command(
211
+ "leader",
212
+ &row.entry.workspace,
213
+ Some(&row.entry.team_key),
214
+ )
215
+ .unwrap_or_else(|| "请核对项目路径".to_string()),
216
+ );
217
+ }
199
218
  out.push('\n');
200
219
  }
201
220
  out
@@ -221,22 +240,34 @@ fn prune_json(report: &RegistryPruneReport) -> Value {
221
240
  }
222
241
 
223
242
  fn format_prune_human(report: &RegistryPruneReport) -> String {
224
- let mut out = format!("dry-run: {}\n", report.dry_run);
243
+ let mut out = format!("仅预览:{}\n", report.dry_run);
225
244
  for (label, items) in [
226
- ("candidates", &report.candidates),
227
- ("removed", &report.removed),
228
- ("kept", &report.kept),
229
- ("skipped", &report.skipped),
230
- ("errors", &report.errors),
245
+ ("可清理登记", &report.candidates),
246
+ ("已清理", &report.removed),
247
+ ("保留", &report.kept),
248
+ ("跳过", &report.skipped),
249
+ ("错误", &report.errors),
231
250
  ] {
232
251
  out.push_str(&format!("{label}: {}\n", items.len()));
233
252
  for item in items {
234
253
  out.push_str(" ");
235
254
  out.push_str(&item.workspace.display().to_string());
236
- out.push_str("::");
255
+ out.push_str(",队伍:");
237
256
  out.push_str(&item.team_key);
238
257
  out.push_str(" [");
239
- out.push_str(&item.reason);
258
+ out.push_str(match item.reason.as_str() {
259
+ "LIVE" => "仍可用,保留登记",
260
+ "team_key_not_found" => "队伍已不在项目中",
261
+ "team_not_alive" => "队伍已停止",
262
+ "registry_entry_missing" => "登记已不存在",
263
+ "registry_entry_changed" | "canonical_state_changed" => {
264
+ "登记或队伍已变化,停止清理"
265
+ }
266
+ "canonical_state_unavailable" => "无法读取队伍状态,保留登记",
267
+ "registry_lock_unavailable" => "登记正被其他操作使用,停止清理",
268
+ "remove_failed" => "删除登记失败,请查看文件权限",
269
+ _ => "无法安全确认该登记;请体检所选项目",
270
+ });
240
271
  out.push_str("]\n");
241
272
  }
242
273
  }
@@ -302,9 +333,40 @@ mod tests {
302
333
  row("/Users/alauda/stale", "old-team", "STALE", "-"),
303
334
  ];
304
335
  let out = format_leaders_human(&rows, LeadersView::All);
305
- assert!(out.starts_with("STATUS\tWORKSPACE\tTEAM\tSEND\n"));
306
- assert!(out.contains("LIVE\t/Volumes/nvme/Projects/讨论team-agent\twiki-team\tsend-live\n"));
307
- assert!(out.contains("STALE\t/Users/alauda/stale\told-team\t-\n"));
336
+ let lines: Vec<_> = out.lines().collect();
337
+ assert_eq!(lines.len(), 3, "one header and both selected rows: {out}");
338
+ let header = lines[0];
339
+ assert_eq!(header.split('\t').count(), 5);
340
+ assert!(header.contains("状态") && header.contains("项目") && header.contains("队伍"));
341
+ let live: Vec<_> = lines[1].split('\t').collect();
342
+ assert_eq!(
343
+ &live[..4],
344
+ &[
345
+ "leader",
346
+ "可用",
347
+ "/Volumes/nvme/Projects/讨论team-agent",
348
+ "wiki-team"
349
+ ]
350
+ );
351
+ assert_eq!(
352
+ super::super::adapters::split_shell_argv(live[4]),
353
+ [
354
+ "team-agent",
355
+ "send",
356
+ "leader",
357
+ "请完成任务并把答案回复给 leader。",
358
+ "--workspace",
359
+ "/Volumes/nvme/Projects/讨论team-agent",
360
+ "--team",
361
+ "wiki-team"
362
+ ]
363
+ );
364
+ let stale: Vec<_> = lines[2].split('\t').collect();
365
+ assert_eq!(
366
+ &stale[..4],
367
+ &["leader", "已失效", "/Users/alauda/stale", "old-team"]
368
+ );
369
+ assert_eq!(stale[4], "-", "stale rows must never suggest a send: {out}");
308
370
  }
309
371
 
310
372
  #[test]
@@ -341,17 +403,16 @@ mod tests {
341
403
 
342
404
  #[test]
343
405
  fn empty_view_names_are_explicit() {
344
- assert_eq!(
345
- format_leaders_human(&[], LeadersView::Live),
346
- "no live leaders\n"
347
- );
348
- assert_eq!(
349
- format_leaders_human(&[], LeadersView::Stale),
350
- "no stale leaders\n"
351
- );
352
- assert_eq!(
353
- format_leaders_human(&[], LeadersView::All),
354
- "no registered leaders\n"
355
- );
406
+ for (view, empty, next) in [
407
+ (LeadersView::Live, "没有可用主控", Some("team-agent doctor")),
408
+ (LeadersView::Stale, "没有已失效的主控登记", None),
409
+ (LeadersView::All, "没有已登记主控", Some("team-agent pi")),
410
+ ] {
411
+ let out = format_leaders_human(&[], view);
412
+ assert!(out.contains(empty) && out.ends_with('\n'), "{out}");
413
+ if let Some(next) = next {
414
+ assert!(out.contains(next), "{out}");
415
+ }
416
+ }
356
417
  }
357
418
  }