@team-agent/installer 0.5.111 → 0.5.112
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.
- package/Cargo.lock +1 -1
- package/Cargo.toml +1 -1
- package/crates/team-agent/src/cli/adapters.rs +1 -743
- package/crates/team-agent/src/cli/diagnose.rs +2 -424
- package/crates/team-agent/src/cli/emit.rs +31 -275
- package/crates/team-agent/src/cli/mod.rs +2 -9
- package/crates/team-agent/src/cli/spec.rs +0 -3
- package/crates/team-agent/src/cli/tests/leader_watch.rs +2 -86
- package/crates/team-agent/src/cli/tests/main_preserved.rs +15 -140
- package/crates/team-agent/src/cli/tests/missing_subcommands.rs +1 -258
- package/crates/team-agent/src/cli/tests/mod.rs +0 -2
- package/crates/team-agent/src/cli/types.rs +4 -108
- package/crates/team-agent/src/coordinator/health.rs +3 -283
- package/crates/team-agent/src/coordinator/mod.rs +0 -5
- package/crates/team-agent/src/coordinator/tests/basics.rs +0 -9
- package/crates/team-agent/src/coordinator/tests/mod.rs +0 -1
- package/crates/team-agent/src/coordinator/tests/tick_core.rs +0 -17
- package/crates/team-agent/src/coordinator/types.rs +1 -21
- package/crates/team-agent/src/db/message_store.rs +0 -42
- package/crates/team-agent/src/db/migration.rs +49 -9
- package/crates/team-agent/src/db/schema.rs +27 -20
- package/crates/team-agent/src/leader/owner_bind.rs +0 -24
- package/crates/team-agent/src/leader/tests/byte_findings.rs +0 -20
- package/crates/team-agent/src/leader/tests/identity.rs +0 -32
- package/crates/team-agent/src/leader/tests/lease_claim.rs +0 -55
- package/crates/team-agent/src/lifecycle/tests/restart_session_capture_red.rs +2 -51
- package/crates/team-agent/src/lifecycle/tests/status_credential_redaction_contract.rs +23 -23
- package/crates/team-agent/src/lifecycle/tests/subprep_codex_trust_roundtrip_red.rs +29 -55
- package/crates/team-agent/src/messaging/mod.rs +0 -2
- package/crates/team-agent/src/messaging/results.rs +0 -58
- package/crates/team-agent/src/messaging/tests/runtime.rs +0 -46
- package/crates/team-agent/src/state/repository.rs +1 -5
- package/package.json +4 -4
- package/templates/team_state.md +1 -1
- package/crates/team-agent/src/cli/tests/peer_allow.rs +0 -164
- package/crates/team-agent/src/cli/tests/verb_validate.rs +0 -195
- package/crates/team-agent/src/coordinator/tests/watch.rs +0 -223
- package/crates/team-agent/src/messaging/peers.rs +0 -34
|
@@ -251,7 +251,6 @@ fn dispatch(command: &str, args: &[String], cwd: &Path) -> Result<ExitCode, CliE
|
|
|
251
251
|
| "clone-agent"
|
|
252
252
|
| "fork-agent"
|
|
253
253
|
| "remove-agent"
|
|
254
|
-
| "allow-peer-talk"
|
|
255
254
|
| "profile"
|
|
256
255
|
)
|
|
257
256
|
{
|
|
@@ -262,9 +261,6 @@ fn dispatch(command: &str, args: &[String], cwd: &Path) -> Result<ExitCode, CliE
|
|
|
262
261
|
match command {
|
|
263
262
|
"quick-start" => cmd_quick_start(&quick_start_args(args, cwd)?).map(emit_result),
|
|
264
263
|
"send" => cmd_send(&send_args(args, cwd)?).map(emit_result),
|
|
265
|
-
"allow-peer-talk" => {
|
|
266
|
-
cmd_allow_peer_talk(&allow_peer_talk_args(args, cwd)?).map(emit_result)
|
|
267
|
-
}
|
|
268
264
|
"status" => cmd_status_for_team(&status_args(args, cwd), parse_args(args).team.as_deref())
|
|
269
265
|
.map(emit_result),
|
|
270
266
|
"shutdown" => cmd_shutdown(&shutdown_args(args, cwd)?).map(emit_result),
|
|
@@ -306,33 +302,13 @@ fn dispatch(command: &str, args: &[String], cwd: &Path) -> Result<ExitCode, CliE
|
|
|
306
302
|
fn is_machine_command(command: &str) -> bool {
|
|
307
303
|
matches!(
|
|
308
304
|
command,
|
|
309
|
-
"
|
|
310
|
-
| "wait"
|
|
311
|
-
| "wait-ready"
|
|
312
|
-
| "preflight"
|
|
313
|
-
| "validate"
|
|
314
|
-
| "identity"
|
|
315
|
-
| "sessions"
|
|
316
|
-
| "watch"
|
|
317
|
-
| "e2e"
|
|
318
|
-
| "peek"
|
|
319
|
-
| "coordinator"
|
|
320
|
-
| "attach-app-server-leader"
|
|
305
|
+
"wait" | "coordinator" | "attach-app-server-leader"
|
|
321
306
|
)
|
|
322
307
|
}
|
|
323
308
|
|
|
324
309
|
fn dispatch_machine(command: &str, args: &[String], cwd: &Path) -> Result<ExitCode, CliError> {
|
|
325
310
|
match command {
|
|
326
|
-
"results" => cmd_results(&results_args(args, cwd)?).map(emit_result),
|
|
327
311
|
"wait" => cmd_wait(&wait_args(args, cwd)?).map(emit_result),
|
|
328
|
-
"wait-ready" => cmd_wait_ready(&wait_ready_args(args, cwd)).map(emit_result),
|
|
329
|
-
"preflight" => cmd_preflight(&preflight_args(args, cwd)).map(emit_result),
|
|
330
|
-
"validate" => cmd_validate(&validate_args(args, cwd)).map(emit_result),
|
|
331
|
-
"identity" => cmd_identity(&identity_args(args, cwd)).map(emit_result),
|
|
332
|
-
"sessions" => cmd_sessions(&sessions_args(args, cwd)).map(emit_result),
|
|
333
|
-
"watch" => cmd_watch(&watch_args(args, cwd)).map(emit_result),
|
|
334
|
-
"e2e" => cmd_e2e(&e2e_args(args, cwd)).map(emit_result),
|
|
335
|
-
"peek" => cmd_peek(&peek_args(args, cwd)?).map(emit_result),
|
|
336
312
|
"coordinator" => run_coordinator(args, cwd),
|
|
337
313
|
"attach-app-server-leader" => {
|
|
338
314
|
cmd_attach_app_server_leader(&attach_app_server_leader_args(args, cwd)?)
|
|
@@ -387,7 +363,7 @@ pub(crate) fn default_help() -> String {
|
|
|
387
363
|
append_help_section(
|
|
388
364
|
&mut out,
|
|
389
365
|
"观察与协作",
|
|
390
|
-
&["leaders", "doctor", "approvals"
|
|
366
|
+
&["leaders", "doctor", "approvals"],
|
|
391
367
|
);
|
|
392
368
|
append_help_section(&mut out, "设置", &["route", "profile", "install-skill"]);
|
|
393
369
|
append_help_section(
|
|
@@ -487,8 +463,6 @@ pub(super) fn command_help(command: Option<&str>) -> String {
|
|
|
487
463
|
"team-agent doctor --workspace .\nteam-agent doctor --workspace . --team help-demo --json", "按实际问题与所选队伍范围处理;不要把体检建议当成已执行的修复。"),
|
|
488
464
|
"approvals" => ("可选 <agent> 查看一位队友;仅观察权限询问,不自动批准。",
|
|
489
465
|
"team-agent approvals\nteam-agent approvals worker --json", "确认权限用途后,在工具的实际权限询问处处理,再看 status。"),
|
|
490
|
-
"allow-peer-talk" => ("指定两位现有队友;--workspace 选择项目。此命令不支持 --team。",
|
|
491
|
-
"team-agent allow-peer-talk worker reviewer\nteam-agent allow-peer-talk worker reviewer --workspace . --json", "再向队友发送交流任务;允许交流不等于已经互发消息。"),
|
|
492
466
|
"profile" => ("init 创建配置,doctor 检查配置,show 显示设置;NAME 是配置名。\n--auth-mode 登录方式;--proxy-mode direct 不走代理、inherit 沿用环境代理。\n配置存放在所选工作区,不因 --team 自动变成队伍独立配置;不要输出凭证。",
|
|
493
467
|
"team-agent profile init local --auth-mode subscription\nteam-agent profile doctor local\nteam-agent profile show local", "检查通过后用 add-agent/start-agent --profile local;需要登录时使用工具原生登录入口。"),
|
|
494
468
|
"install-skill" => ("--source 必须是已核实的指南目录;--target 选择 codex/claude/copilot/all。\n--dest 覆盖安装位置;--dry-run 只预览;--uninstall 移除目标指南。此项不是起队前提。",
|
|
@@ -572,7 +546,6 @@ fn emit_usage_error(message: &str) {
|
|
|
572
546
|
eprintln!("错误:{message}");
|
|
573
547
|
}
|
|
574
548
|
|
|
575
|
-
/// `cmd_validate` delegates to runtime validate_file.
|
|
576
549
|
/// `install-skill` 参数(RED-1 根治:把 skill 安装单源收敛到二进制,install.mjs 调它)。
|
|
577
550
|
struct InstallSkillArgs {
|
|
578
551
|
target: crate::packaging::SkillTarget,
|
|
@@ -688,93 +661,10 @@ fn cmd_install_skill(args: &InstallSkillArgs) -> Result<CmdResult, CliError> {
|
|
|
688
661
|
))
|
|
689
662
|
}
|
|
690
663
|
|
|
691
|
-
pub fn cmd_validate(args: &ValidateArgs) -> Result<CmdResult, CliError> {
|
|
692
|
-
let spec = resolve_path(&args.spec);
|
|
693
|
-
let value = if spec.is_dir() {
|
|
694
|
-
validate_team_dir(&spec)?
|
|
695
|
-
} else {
|
|
696
|
-
validate_spec_file(&spec)?
|
|
697
|
-
};
|
|
698
|
-
Ok(CmdResult::from_json(value, args.json))
|
|
699
|
-
}
|
|
700
|
-
|
|
701
|
-
fn validate_spec_file(spec_path: &Path) -> Result<Value, CliError> {
|
|
702
|
-
let text = std::fs::read_to_string(spec_path)?;
|
|
703
|
-
let base_dir = spec_path.parent().unwrap_or_else(|| Path::new("."));
|
|
704
|
-
let spec =
|
|
705
|
-
crate::model::spec::load_and_validate_spec(&text, base_dir).map_err(model_error_to_cli)?;
|
|
706
|
-
let team = spec
|
|
707
|
-
.get("team")
|
|
708
|
-
.and_then(|team| team.get("name"))
|
|
709
|
-
.and_then(crate::model::yaml::Value::as_str)
|
|
710
|
-
.unwrap_or("");
|
|
711
|
-
let workspace = spec
|
|
712
|
-
.get("team")
|
|
713
|
-
.and_then(|team| team.get("workspace"))
|
|
714
|
-
.and_then(crate::model::yaml::Value::as_str)
|
|
715
|
-
.map(PathBuf::from)
|
|
716
|
-
.unwrap_or_else(|| base_dir.to_path_buf());
|
|
717
|
-
let mut obj = Map::new();
|
|
718
|
-
obj.insert("ok".to_string(), Value::Bool(true));
|
|
719
|
-
obj.insert("team".to_string(), Value::String(team.to_string()));
|
|
720
|
-
obj.insert(
|
|
721
|
-
"workspace".to_string(),
|
|
722
|
-
Value::String(workspace.to_string_lossy().to_string()),
|
|
723
|
-
);
|
|
724
|
-
Ok(Value::Object(obj))
|
|
725
|
-
}
|
|
726
|
-
|
|
727
|
-
fn validate_team_dir(team_dir: &Path) -> Result<Value, CliError> {
|
|
728
|
-
let spec = crate::compiler::compile_team(team_dir).map_err(model_error_to_cli)?;
|
|
729
|
-
let team = spec
|
|
730
|
-
.get("team")
|
|
731
|
-
.and_then(|team| team.get("name"))
|
|
732
|
-
.and_then(crate::model::yaml::Value::as_str)
|
|
733
|
-
.unwrap_or("");
|
|
734
|
-
let workspace = spec
|
|
735
|
-
.get("team")
|
|
736
|
-
.and_then(|team| team.get("workspace"))
|
|
737
|
-
.and_then(crate::model::yaml::Value::as_str)
|
|
738
|
-
.map(PathBuf::from)
|
|
739
|
-
.unwrap_or_else(|| team_dir.to_path_buf());
|
|
740
|
-
let agents = spec
|
|
741
|
-
.get("agents")
|
|
742
|
-
.and_then(crate::model::yaml::Value::as_list)
|
|
743
|
-
.map(|agents| {
|
|
744
|
-
agents
|
|
745
|
-
.iter()
|
|
746
|
-
.filter_map(|agent| {
|
|
747
|
-
agent
|
|
748
|
-
.get("id")
|
|
749
|
-
.and_then(crate::model::yaml::Value::as_str)
|
|
750
|
-
.map(|id| Value::String(id.to_string()))
|
|
751
|
-
})
|
|
752
|
-
.collect::<Vec<_>>()
|
|
753
|
-
})
|
|
754
|
-
.unwrap_or_default();
|
|
755
|
-
let mut obj = Map::new();
|
|
756
|
-
obj.insert("ok".to_string(), Value::Bool(true));
|
|
757
|
-
obj.insert("type".to_string(), Value::String("team_dir".to_string()));
|
|
758
|
-
obj.insert(
|
|
759
|
-
"workspace".to_string(),
|
|
760
|
-
Value::String(workspace.to_string_lossy().to_string()),
|
|
761
|
-
);
|
|
762
|
-
obj.insert("team".to_string(), Value::String(team.to_string()));
|
|
763
|
-
obj.insert("agents".to_string(), Value::Array(agents));
|
|
764
|
-
Ok(Value::Object(obj))
|
|
765
|
-
}
|
|
766
|
-
|
|
767
664
|
fn resolve_path(path: &Path) -> PathBuf {
|
|
768
665
|
std::fs::canonicalize(path).unwrap_or_else(|_| path.to_path_buf())
|
|
769
666
|
}
|
|
770
667
|
|
|
771
|
-
fn model_error_to_cli(error: crate::model::errors::ModelError) -> CliError {
|
|
772
|
-
match error {
|
|
773
|
-
crate::model::errors::ModelError::Validation(message) => CliError::Runtime(message),
|
|
774
|
-
other => CliError::Runtime(other.to_string()),
|
|
775
|
-
}
|
|
776
|
-
}
|
|
777
|
-
|
|
778
668
|
fn emit_cli_error(command: &str, args: &[String], cwd: &Path, error: &CliError) -> ExitCode {
|
|
779
669
|
let parsed = parse_args(args);
|
|
780
670
|
let workspace = workspace(&parsed, cwd);
|
|
@@ -951,14 +841,9 @@ struct ParsedArgs {
|
|
|
951
841
|
once: bool,
|
|
952
842
|
tick_interval: Option<f64>,
|
|
953
843
|
status_value: Option<String>,
|
|
954
|
-
providers: Option<String>,
|
|
955
|
-
allow_raw_screen: bool,
|
|
956
|
-
tail: Option<usize>,
|
|
957
|
-
head: Option<usize>,
|
|
958
844
|
search: Option<String>,
|
|
959
845
|
file: Option<PathBuf>,
|
|
960
846
|
result: Option<String>,
|
|
961
|
-
real: bool,
|
|
962
847
|
assignee: Option<String>,
|
|
963
848
|
auth_mode: Option<String>,
|
|
964
849
|
proxy_mode: Option<String>,
|
|
@@ -1045,14 +930,9 @@ fn parse_args(args: &[String]) -> ParsedArgs {
|
|
|
1045
930
|
parsed.tick_interval = next_arg(args, &mut i).and_then(|v| v.parse::<f64>().ok())
|
|
1046
931
|
}
|
|
1047
932
|
"--status" => parsed.status_value = next_arg(args, &mut i),
|
|
1048
|
-
"--providers" => parsed.providers = next_arg(args, &mut i),
|
|
1049
|
-
"--allow-raw-screen" => parsed.allow_raw_screen = true,
|
|
1050
|
-
"--tail" => parsed.tail = next_arg(args, &mut i).and_then(|v| v.parse::<usize>().ok()),
|
|
1051
|
-
"--head" => parsed.head = next_arg(args, &mut i).and_then(|v| v.parse::<usize>().ok()),
|
|
1052
933
|
"--search" => parsed.search = next_arg(args, &mut i),
|
|
1053
934
|
"--file" => parsed.file = next_arg(args, &mut i).map(PathBuf::from),
|
|
1054
935
|
"--result" => parsed.result = next_arg(args, &mut i),
|
|
1055
|
-
"--real" => parsed.real = true,
|
|
1056
936
|
"--assignee" => parsed.assignee = next_arg(args, &mut i),
|
|
1057
937
|
"--auth-mode" => parsed.auth_mode = next_arg(args, &mut i),
|
|
1058
938
|
"--proxy-mode" => parsed.proxy_mode = next_arg(args, &mut i),
|
|
@@ -1458,17 +1338,6 @@ fn refuse_if_multi_alive_team_missing_scope(
|
|
|
1458
1338
|
Ok(())
|
|
1459
1339
|
}
|
|
1460
1340
|
|
|
1461
|
-
fn allow_peer_talk_args(args: &[String], cwd: &Path) -> Result<AllowPeerTalkArgs, CliError> {
|
|
1462
|
-
let parsed = parse_args(args);
|
|
1463
|
-
Ok(AllowPeerTalkArgs {
|
|
1464
|
-
a: required_pos(&parsed, 0, "a")?,
|
|
1465
|
-
b: required_pos(&parsed, 1, "b")?,
|
|
1466
|
-
workspace: workspace(&parsed, cwd),
|
|
1467
|
-
json: parsed.json,
|
|
1468
|
-
team: parsed.team,
|
|
1469
|
-
})
|
|
1470
|
-
}
|
|
1471
|
-
|
|
1472
1341
|
fn status_args(args: &[String], cwd: &Path) -> StatusArgs {
|
|
1473
1342
|
let parsed = parse_args(args);
|
|
1474
1343
|
StatusArgs {
|
|
@@ -1481,14 +1350,6 @@ fn status_args(args: &[String], cwd: &Path) -> StatusArgs {
|
|
|
1481
1350
|
}
|
|
1482
1351
|
}
|
|
1483
1352
|
|
|
1484
|
-
fn watch_args(args: &[String], cwd: &Path) -> WatchArgs {
|
|
1485
|
-
let parsed = parse_args(args);
|
|
1486
|
-
WatchArgs {
|
|
1487
|
-
workspace: workspace(&parsed, cwd),
|
|
1488
|
-
team: parsed.team,
|
|
1489
|
-
}
|
|
1490
|
-
}
|
|
1491
|
-
|
|
1492
1353
|
fn wait_args(args: &[String], cwd: &Path) -> Result<WaitArgs, CliError> {
|
|
1493
1354
|
let parsed = parse_args(args);
|
|
1494
1355
|
let workspace = workspace(&parsed, cwd);
|
|
@@ -1586,15 +1447,6 @@ fn attach_app_server_leader_args(
|
|
|
1586
1447
|
})
|
|
1587
1448
|
}
|
|
1588
1449
|
|
|
1589
|
-
fn identity_args(args: &[String], cwd: &Path) -> IdentityArgs {
|
|
1590
|
-
let parsed = parse_args(args);
|
|
1591
|
-
IdentityArgs {
|
|
1592
|
-
workspace: workspace(&parsed, cwd),
|
|
1593
|
-
team: parsed.team,
|
|
1594
|
-
json: parsed.json,
|
|
1595
|
-
}
|
|
1596
|
-
}
|
|
1597
|
-
|
|
1598
1450
|
fn shutdown_args(args: &[String], cwd: &Path) -> Result<ShutdownArgs, CliError> {
|
|
1599
1451
|
let parsed = parse_args(args);
|
|
1600
1452
|
let workspace = workspace(&parsed, cwd);
|
|
@@ -1917,15 +1769,6 @@ fn doctor_gate(raw: Option<&str>) -> Option<DoctorGate> {
|
|
|
1917
1769
|
}
|
|
1918
1770
|
}
|
|
1919
1771
|
|
|
1920
|
-
fn sessions_args(args: &[String], cwd: &Path) -> SessionsArgs {
|
|
1921
|
-
let parsed = parse_args(args);
|
|
1922
|
-
SessionsArgs {
|
|
1923
|
-
workspace: workspace(&parsed, cwd),
|
|
1924
|
-
json: parsed.json,
|
|
1925
|
-
team: parsed.team,
|
|
1926
|
-
}
|
|
1927
|
-
}
|
|
1928
|
-
|
|
1929
1772
|
fn leaders_args(args: &[String], _cwd: &Path) -> Result<LeadersArgs, CliError> {
|
|
1930
1773
|
let mut view = None;
|
|
1931
1774
|
let mut query = None;
|
|
@@ -2031,18 +1874,6 @@ fn leaders_args(args: &[String], _cwd: &Path) -> Result<LeadersArgs, CliError> {
|
|
|
2031
1874
|
})
|
|
2032
1875
|
}
|
|
2033
1876
|
|
|
2034
|
-
fn validate_args(args: &[String], cwd: &Path) -> ValidateArgs {
|
|
2035
|
-
let parsed = parse_args(args);
|
|
2036
|
-
ValidateArgs {
|
|
2037
|
-
spec: parsed
|
|
2038
|
-
.positionals
|
|
2039
|
-
.first()
|
|
2040
|
-
.map(PathBuf::from)
|
|
2041
|
-
.unwrap_or_else(|| cwd.join("team.spec.yaml")),
|
|
2042
|
-
json: parsed.json,
|
|
2043
|
-
}
|
|
2044
|
-
}
|
|
2045
|
-
|
|
2046
1877
|
fn profile_args(args: &[String], cwd: &Path) -> Result<ProfileArgs, CliError> {
|
|
2047
1878
|
let parsed = parse_args(args);
|
|
2048
1879
|
let workspace = resolve_cli_path(cwd, &workspace(&parsed, cwd));
|
|
@@ -2057,106 +1888,6 @@ fn profile_args(args: &[String], cwd: &Path) -> Result<ProfileArgs, CliError> {
|
|
|
2057
1888
|
})
|
|
2058
1889
|
}
|
|
2059
1890
|
|
|
2060
|
-
fn results_args(args: &[String], cwd: &Path) -> Result<ResultsArgs, CliError> {
|
|
2061
|
-
if args
|
|
2062
|
-
.iter()
|
|
2063
|
-
.any(|arg| arg == "--to" || arg.starts_with("--to="))
|
|
2064
|
-
{
|
|
2065
|
-
return Err(CliError::Usage(
|
|
2066
|
-
"results does not accept --to; it is a read-only command".to_string(),
|
|
2067
|
-
));
|
|
2068
|
-
}
|
|
2069
|
-
let parsed = parse_args(args);
|
|
2070
|
-
let case_id = option_value(args, "--case")
|
|
2071
|
-
.filter(|value| !value.is_empty())
|
|
2072
|
-
.ok_or_else(|| CliError::Usage("missing --case".to_string()))?;
|
|
2073
|
-
Ok(ResultsArgs {
|
|
2074
|
-
case_id,
|
|
2075
|
-
workspace: workspace(&parsed, cwd),
|
|
2076
|
-
team: parsed.team,
|
|
2077
|
-
json: parsed.json,
|
|
2078
|
-
})
|
|
2079
|
-
}
|
|
2080
|
-
|
|
2081
|
-
fn option_value(args: &[String], flag: &str) -> Option<String> {
|
|
2082
|
-
let prefix = format!("{flag}=");
|
|
2083
|
-
let mut i = 0usize;
|
|
2084
|
-
while i < args.len() {
|
|
2085
|
-
let arg = args.get(i)?;
|
|
2086
|
-
if let Some(value) = arg.strip_prefix(&prefix) {
|
|
2087
|
-
return Some(value.to_string());
|
|
2088
|
-
}
|
|
2089
|
-
if arg == flag {
|
|
2090
|
-
return args
|
|
2091
|
-
.get(i.saturating_add(1))
|
|
2092
|
-
.filter(|value| !value.starts_with('-'))
|
|
2093
|
-
.cloned();
|
|
2094
|
-
}
|
|
2095
|
-
i = i.saturating_add(1);
|
|
2096
|
-
}
|
|
2097
|
-
None
|
|
2098
|
-
}
|
|
2099
|
-
|
|
2100
|
-
fn preflight_args(args: &[String], cwd: &Path) -> PreflightArgs {
|
|
2101
|
-
let parsed = parse_args(args);
|
|
2102
|
-
let team = parsed
|
|
2103
|
-
.team
|
|
2104
|
-
.as_deref()
|
|
2105
|
-
.map(PathBuf::from)
|
|
2106
|
-
.or_else(|| parsed.positionals.first().map(PathBuf::from))
|
|
2107
|
-
.unwrap_or_else(|| cwd.to_path_buf());
|
|
2108
|
-
PreflightArgs {
|
|
2109
|
-
team,
|
|
2110
|
-
json: parsed.json,
|
|
2111
|
-
}
|
|
2112
|
-
}
|
|
2113
|
-
|
|
2114
|
-
fn wait_ready_args(args: &[String], cwd: &Path) -> WaitReadyArgs {
|
|
2115
|
-
let parsed = parse_args(args);
|
|
2116
|
-
WaitReadyArgs {
|
|
2117
|
-
workspace: workspace(&parsed, cwd),
|
|
2118
|
-
timeout: parsed.timeout.unwrap_or(60.0),
|
|
2119
|
-
json: parsed.json,
|
|
2120
|
-
team: parsed.team,
|
|
2121
|
-
}
|
|
2122
|
-
}
|
|
2123
|
-
|
|
2124
|
-
fn e2e_args(args: &[String], cwd: &Path) -> E2eArgs {
|
|
2125
|
-
let parsed = parse_args(args);
|
|
2126
|
-
let providers = parsed
|
|
2127
|
-
.providers
|
|
2128
|
-
.as_deref()
|
|
2129
|
-
.unwrap_or("fake")
|
|
2130
|
-
.split(',')
|
|
2131
|
-
.filter_map(|p| {
|
|
2132
|
-
let trimmed = p.trim();
|
|
2133
|
-
if trimmed.is_empty() {
|
|
2134
|
-
None
|
|
2135
|
-
} else {
|
|
2136
|
-
Some(trimmed.to_string())
|
|
2137
|
-
}
|
|
2138
|
-
})
|
|
2139
|
-
.collect();
|
|
2140
|
-
E2eArgs {
|
|
2141
|
-
workspace: workspace(&parsed, cwd),
|
|
2142
|
-
providers,
|
|
2143
|
-
real: parsed.real,
|
|
2144
|
-
json: parsed.json,
|
|
2145
|
-
}
|
|
2146
|
-
}
|
|
2147
|
-
|
|
2148
|
-
fn peek_args(args: &[String], cwd: &Path) -> Result<PeekArgs, CliError> {
|
|
2149
|
-
let parsed = parse_args(args);
|
|
2150
|
-
Ok(PeekArgs {
|
|
2151
|
-
agent: required_pos(&parsed, 0, "agent")?,
|
|
2152
|
-
workspace: workspace(&parsed, cwd),
|
|
2153
|
-
tail: parsed.tail.unwrap_or(80),
|
|
2154
|
-
head: parsed.head,
|
|
2155
|
-
search: parsed.search,
|
|
2156
|
-
allow_raw_screen: parsed.allow_raw_screen,
|
|
2157
|
-
json: parsed.json,
|
|
2158
|
-
})
|
|
2159
|
-
}
|
|
2160
1891
|
fn run_coordinator(args: &[String], cwd: &Path) -> Result<ExitCode, CliError> {
|
|
2161
1892
|
let parsed = parse_args(args);
|
|
2162
1893
|
let workspace = crate::coordinator::WorkspacePath::new(workspace(&parsed, cwd));
|
|
@@ -2343,7 +2074,7 @@ mod tests {
|
|
|
2343
2074
|
.filter(|spec| spec.default_help)
|
|
2344
2075
|
.map(|spec| spec.name)
|
|
2345
2076
|
.collect();
|
|
2346
|
-
assert_eq!(expected.len(),
|
|
2077
|
+
assert_eq!(expected.len(), 29, "the public catalog is Human29");
|
|
2347
2078
|
for spec_name in &expected {
|
|
2348
2079
|
assert!(
|
|
2349
2080
|
visible.iter().any(|command| command == spec_name),
|
|
@@ -2357,7 +2088,7 @@ mod tests {
|
|
|
2357
2088
|
expected_sorted.sort();
|
|
2358
2089
|
assert_eq!(
|
|
2359
2090
|
actual, expected_sorted,
|
|
2360
|
-
"default help must match the exact
|
|
2091
|
+
"default help must match the exact Human29 public spec set, not a slack threshold; got {visible:?}"
|
|
2361
2092
|
);
|
|
2362
2093
|
assert!(
|
|
2363
2094
|
top_help.contains("copilot"),
|
|
@@ -2392,6 +2123,32 @@ mod tests {
|
|
|
2392
2123
|
}
|
|
2393
2124
|
}
|
|
2394
2125
|
|
|
2126
|
+
#[test]
|
|
2127
|
+
fn retired_commands_have_no_catalog_machine_or_help_gate() {
|
|
2128
|
+
for command in [
|
|
2129
|
+
"e2e",
|
|
2130
|
+
"allow-peer-talk",
|
|
2131
|
+
"results",
|
|
2132
|
+
"validate",
|
|
2133
|
+
"identity",
|
|
2134
|
+
"sessions",
|
|
2135
|
+
"watch",
|
|
2136
|
+
"peek",
|
|
2137
|
+
"wait-ready",
|
|
2138
|
+
"preflight",
|
|
2139
|
+
] {
|
|
2140
|
+
assert!(
|
|
2141
|
+
command_spec(command).is_none(),
|
|
2142
|
+
"retired catalog entry: {command}"
|
|
2143
|
+
);
|
|
2144
|
+
assert!(
|
|
2145
|
+
!is_machine_command(command),
|
|
2146
|
+
"retired Machine route: {command}"
|
|
2147
|
+
);
|
|
2148
|
+
assert!(!is_known_subcommand(command), "retired help gate: {command}");
|
|
2149
|
+
}
|
|
2150
|
+
}
|
|
2151
|
+
|
|
2395
2152
|
#[test]
|
|
2396
2153
|
fn leaders_help_publishes_selectors_and_prune() {
|
|
2397
2154
|
let help = command_help(Some("leaders"));
|
|
@@ -2405,7 +2162,6 @@ mod tests {
|
|
|
2405
2162
|
#[test]
|
|
2406
2163
|
fn observation_a_commands_have_terminal_tiers() {
|
|
2407
2164
|
for (command, tier) in [
|
|
2408
|
-
("allow-peer-talk", CommandTier::Core),
|
|
2409
2165
|
("approvals", CommandTier::Core),
|
|
2410
2166
|
("profile", CommandTier::Core),
|
|
2411
2167
|
("install-skill", CommandTier::Core),
|
|
@@ -2547,7 +2303,7 @@ mod tests {
|
|
|
2547
2303
|
}
|
|
2548
2304
|
}
|
|
2549
2305
|
let cwd = tmp_workspace();
|
|
2550
|
-
for command in ["
|
|
2306
|
+
for command in ["wait", "attach-app-server-leader", "coordinator"] {
|
|
2551
2307
|
assert_eq!(
|
|
2552
2308
|
run(&cli_argv(&[command, "--help"]), &cwd),
|
|
2553
2309
|
ExitCode::Usage,
|
|
@@ -5629,8 +5629,8 @@ pub mod diagnose_port {
|
|
|
5629
5629
|
}
|
|
5630
5630
|
}
|
|
5631
5631
|
|
|
5632
|
-
/// PLACEHOLDER → leader lane(`runtime.{takeover,claim_leader
|
|
5633
|
-
/// leader.rs 已有 `claim_leader
|
|
5632
|
+
/// PLACEHOLDER → leader lane(`runtime.{takeover,claim_leader}` 的 CLI 视图)。
|
|
5633
|
+
/// leader.rs 已有 `claim_leader`(返 `LeaseResult`);CLI 需 `takeover` +
|
|
5634
5634
|
/// 把 `LeaseResult` 投影成稳定 `--json` 形状。这两步由 leader 集成收口,本层仅声明 CLI 委派面。
|
|
5635
5635
|
pub mod leader_port {
|
|
5636
5636
|
use super::*;
|
|
@@ -5769,13 +5769,6 @@ pub mod leader_port {
|
|
|
5769
5769
|
Ok(value)
|
|
5770
5770
|
}
|
|
5771
5771
|
|
|
5772
|
-
///
|
|
5773
|
-
/// `runtime.leader_identity(workspace, team)`(`cmd_identity`)。
|
|
5774
|
-
pub fn leader_identity(workspace: &Path, team: Option<&str>) -> Result<Value, CliError> {
|
|
5775
|
-
crate::leader::leader_identity(workspace, team)
|
|
5776
|
-
.map_err(|e| CliError::Runtime(e.to_string()))
|
|
5777
|
-
}
|
|
5778
|
-
|
|
5779
5772
|
/// E7 (0.5.9 host-leader-registry-design §14 step 3): after a
|
|
5780
5773
|
/// canonical binding command succeeds, write a derived discovery
|
|
5781
5774
|
/// entry to `~/.team-agent/leaders`. Registry write failure is
|
|
@@ -49,7 +49,6 @@ pub(crate) enum DispatchKind {
|
|
|
49
49
|
Leaders,
|
|
50
50
|
Doctor,
|
|
51
51
|
Approvals,
|
|
52
|
-
AllowPeerTalk,
|
|
53
52
|
Route,
|
|
54
53
|
Profile,
|
|
55
54
|
InstallSkill,
|
|
@@ -75,7 +74,6 @@ pub(crate) const ALL_DISPATCH_KINDS: &[DispatchKind] = &[
|
|
|
75
74
|
DispatchKind::Leaders,
|
|
76
75
|
DispatchKind::Doctor,
|
|
77
76
|
DispatchKind::Approvals,
|
|
78
|
-
DispatchKind::AllowPeerTalk,
|
|
79
77
|
DispatchKind::Route,
|
|
80
78
|
DispatchKind::Profile,
|
|
81
79
|
DispatchKind::InstallSkill,
|
|
@@ -125,7 +123,6 @@ pub(crate) const COMMAND_SPECS: &[CommandSpec] = &[
|
|
|
125
123
|
CommandSpec { name: "leaders", tier: CommandTier::Core, category: CommandCategory::Observe, kind: CommandKind::Dispatch(DispatchKind::Leaders), summary: "查看本机可用的主控及其所属队伍", usage: "team-agent leaders [QUERY|--search TEXT] [--all|--stale] [--json] | --prune [--dry-run] [--json]", default_help: true, command_help: true, suggestion_index: true, token_usage: TokenUsage::No, sunset: None, action: None, governance: None },
|
|
126
124
|
CommandSpec { name: "doctor", tier: CommandTier::Core, category: CommandCategory::Observe, kind: CommandKind::Dispatch(DispatchKind::Doctor), summary: "检查队伍并给出恢复建议,默认不修复", usage: "team-agent doctor [SPEC] [--workspace WORKSPACE] [--team TEAM] [--comms] [--gate orphans|comms] [--fix] [--fix-schema] [--cleanup-orphans] [--confirm] [--json]", default_help: true, command_help: true, suggestion_index: true, token_usage: TokenUsage::No, sunset: None, action: None, governance: None },
|
|
127
125
|
CommandSpec { name: "approvals", tier: CommandTier::Core, category: CommandCategory::Observe, kind: CommandKind::Dispatch(DispatchKind::Approvals), summary: "查看队友正在等待的权限确认", usage: "team-agent approvals [<agent>] [--workspace WORKSPACE] [--team TEAM] [--json]", default_help: true, command_help: true, suggestion_index: true, token_usage: TokenUsage::No, sunset: None, action: None, governance: None },
|
|
128
|
-
CommandSpec { name: "allow-peer-talk", tier: CommandTier::Core, category: CommandCategory::Observe, kind: CommandKind::Dispatch(DispatchKind::AllowPeerTalk), summary: "允许两位队友直接交流", usage: "team-agent allow-peer-talk <agent> OTHER_AGENT [--workspace WORKSPACE] [--json]", default_help: true, command_help: true, suggestion_index: true, token_usage: TokenUsage::No, sunset: None, action: None, governance: None },
|
|
129
126
|
CommandSpec { name: "route", tier: CommandTier::Core, category: CommandCategory::Setup, kind: CommandKind::Dispatch(DispatchKind::Route), summary: "设置工具启动时额外使用的命令行参数", usage: "team-agent route [status|enable|disable|show [TOOL]|set TOOL -- ARG...|add TOOL -- ARG...|clear TOOL] [--json]", default_help: true, command_help: true, suggestion_index: true, token_usage: TokenUsage::No, sunset: None, action: None, governance: None },
|
|
130
127
|
CommandSpec { name: "profile", tier: CommandTier::Core, category: CommandCategory::Setup, kind: CommandKind::Dispatch(DispatchKind::Profile), summary: "管理登录方式和代理设置", usage: "team-agent profile init|doctor|show NAME [--workspace WORKSPACE] [--team TEAM] [--auth-mode MODE] [--proxy-mode direct|inherit] [--json]", default_help: true, command_help: true, suggestion_index: true, token_usage: TokenUsage::No, sunset: None, action: None, governance: None },
|
|
131
128
|
CommandSpec { name: "install-skill", tier: CommandTier::Core, category: CommandCategory::Setup, kind: CommandKind::Dispatch(DispatchKind::InstallSkill), summary: "可选:安装已有的公开操作指南", usage: "team-agent install-skill (--source DIR|--uninstall) [--target codex|claude|copilot|all] [--dest DIR] [--dry-run] [--json]", default_help: true, command_help: true, suggestion_index: true, token_usage: TokenUsage::No, sunset: None, action: None, governance: None },
|
|
@@ -100,11 +100,10 @@ fn cli_shutdown_kills_team_session_real_teardown() {
|
|
|
100
100
|
|
|
101
101
|
// =========================================================================
|
|
102
102
|
// WAVE-2 Lane B — CLI leader handler delegation byte-parity (leader_port::*).
|
|
103
|
-
// The
|
|
103
|
+
// The retained CLI verbs are thin pass-throughs (cli/commands.py:152-157):
|
|
104
104
|
// cmd_takeover -> runtime.takeover(ws, team, confirm)
|
|
105
105
|
// cmd_claim_leader -> runtime.claim_leader(ws, team, confirm) (Family A)
|
|
106
|
-
//
|
|
107
|
-
// leader_port::{takeover,claim_leader,leader_identity} are STUBS returning
|
|
106
|
+
// leader_port::{takeover,claim_leader} are STUBS returning
|
|
108
107
|
// the WRONG shape today -> these LOCK the golden dict so the porter wires
|
|
109
108
|
// them into leader::* / runtime.* and matches byte-for-byte.
|
|
110
109
|
// Golden re-probed @ team-agent-public (probe_claim.py / probe_rtclaim.py /
|
|
@@ -297,89 +296,6 @@ fn leader_port_claim_leader_no_pane_refuses_caller_pane_missing_family_a() {
|
|
|
297
296
|
);
|
|
298
297
|
}
|
|
299
298
|
|
|
300
|
-
// RED — leader_identity(): CLI directly emits leader.leader_identity's 9-key
|
|
301
|
-
// dict (runtime.leader_identity is imported from leader). golden probe_lid.py
|
|
302
|
-
// keys: ok, uuid_prefix, machine_fingerprint, workspace_abspath, os_user,
|
|
303
|
-
// team_id, current_pane_id, last_seen_at, source. Current stub returns
|
|
304
|
-
// {ok:true, team:...} (wrong shape) -> RED.
|
|
305
|
-
#[test]
|
|
306
|
-
fn leader_port_leader_identity_emits_nine_key_dict() {
|
|
307
|
-
let ws = leader_port_ws("identity");
|
|
308
|
-
std::fs::create_dir_all(crate::model::paths::runtime_dir(&ws)).unwrap();
|
|
309
|
-
let v = super::leader_port::leader_identity(&ws, None).unwrap();
|
|
310
|
-
assert_eq!(v["ok"], json!(true));
|
|
311
|
-
let obj = v.as_object().expect("identity → JSON object");
|
|
312
|
-
for key in [
|
|
313
|
-
"ok",
|
|
314
|
-
"uuid_prefix",
|
|
315
|
-
"machine_fingerprint",
|
|
316
|
-
"workspace_abspath",
|
|
317
|
-
"os_user",
|
|
318
|
-
"team_id",
|
|
319
|
-
"current_pane_id",
|
|
320
|
-
"last_seen_at",
|
|
321
|
-
"source",
|
|
322
|
-
] {
|
|
323
|
-
assert!(
|
|
324
|
-
obj.contains_key(key),
|
|
325
|
-
"golden identity dict must carry '{key}', got {obj:?}"
|
|
326
|
-
);
|
|
327
|
-
}
|
|
328
|
-
// no override/state uuid → source is the leader-plan "derived" string.
|
|
329
|
-
assert_eq!(v["source"], json!("derived"));
|
|
330
|
-
// uuid_prefix is exactly 12 hex chars (derive[:12]).
|
|
331
|
-
let prefix = v["uuid_prefix"].as_str().expect("uuid_prefix str");
|
|
332
|
-
assert_eq!(prefix.len(), 12, "uuid_prefix == derived[:12]");
|
|
333
|
-
assert!(prefix.chars().all(|c| c.is_ascii_hexdigit()));
|
|
334
|
-
// no team registered + no TMUX_PANE/receiver → these are JSON null.
|
|
335
|
-
if std::env::var_os("TMUX_PANE").is_none() {
|
|
336
|
-
assert_eq!(v["current_pane_id"], serde_json::Value::Null);
|
|
337
|
-
}
|
|
338
|
-
assert_eq!(v["last_seen_at"], serde_json::Value::Null);
|
|
339
|
-
}
|
|
340
|
-
|
|
341
|
-
// ── cmd_watch (cli/adapters.rs:58) [RED] — today a CmdResult::none() no-op ───────────────────────
|
|
342
|
-
// Golden cmd_watch (cli/commands.py:103-109) DELEGATES to run_watch(workspace.resolve(), team) and
|
|
343
|
-
// exits 0. Golden run_watch (watch/__init__.py:25-37) is a `while True` LIVE TAIL that streams
|
|
344
|
-
// render_event_line output (collect_watch_lines) for the watched team. The Rust cmd_watch returns
|
|
345
|
-
// CmdResult::none() — a no-op that never touches the watch subsystem. The watch LINE SHAPE is itself
|
|
346
|
-
// byte-locked by coordinator::render_event_line (coordinator/tests.rs GROUP H); the cli contract here
|
|
347
|
-
// is that cmd_watch must DELEGATE and surface those rendered lines. Seeded a result_received event ->
|
|
348
|
-
// golden render = "result_received: <agent> -> <summary>" (render_event_line: agent_id + summary[:80]).
|
|
349
|
-
//
|
|
350
|
-
// RED confirmed TODAY: cmd_watch=none() returns immediately (no watch line) -> the assertion fails
|
|
351
|
-
// (and does NOT hang). PORTER: wire cmd_watch -> coordinator::run_watch(resolved workspace, team,
|
|
352
|
-
// interval, sink). Since golden's tail is `while True`, the cli port needs a TERMINATING / bounded
|
|
353
|
-
// entry to be both byte-parity AND unit-testable (collect the current watch lines into the CmdResult,
|
|
354
|
-
// or stream to stdout with a bounded test seam) — a blocking unit test is unacceptable.
|
|
355
|
-
#[test]
|
|
356
|
-
fn cmd_watch_delegates_and_surfaces_rendered_watch_lines_not_noop() {
|
|
357
|
-
let ws = tmp_workspace();
|
|
358
|
-
let logs = crate::model::paths::logs_dir(&ws);
|
|
359
|
-
std::fs::create_dir_all(&logs).unwrap();
|
|
360
|
-
std::fs::write(
|
|
361
|
-
logs.join("events.jsonl"),
|
|
362
|
-
"{\"event\":\"result_received\",\"agent_id\":\"alpha\",\"summary\":\"did the thing\"}\n",
|
|
363
|
-
)
|
|
364
|
-
.unwrap();
|
|
365
|
-
let r = cmd_watch(&WatchArgs {
|
|
366
|
-
workspace: ws.clone(),
|
|
367
|
-
team: None,
|
|
368
|
-
})
|
|
369
|
-
.expect("cmd_watch returns a CmdResult");
|
|
370
|
-
let text = match &r.output {
|
|
371
|
-
CmdOutput::Human(s) => s.clone(),
|
|
372
|
-
CmdOutput::Json(v) => v.to_string(),
|
|
373
|
-
CmdOutput::None => String::new(),
|
|
374
|
-
};
|
|
375
|
-
assert!(
|
|
376
|
-
text.contains("result_received: alpha -> did the thing"),
|
|
377
|
-
"cmd_watch must DELEGATE to the watch subsystem (run_watch -> render_event_line) and surface the \
|
|
378
|
-
rendered watch line; today it is a CmdResult::none() no-op. got output={:?}",
|
|
379
|
-
r.output
|
|
380
|
-
);
|
|
381
|
-
}
|
|
382
|
-
|
|
383
299
|
// D7 [WARN] — WAVE-2 Lane B: empty caller binds refuse as caller_pane_missing.
|
|
384
300
|
#[test]
|
|
385
301
|
#[serial_test::serial(env)]
|