@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
@@ -797,3 +797,91 @@ fn cmd_leader_passthrough_maps_grok_and_cursor_not_claude() {
797
797
  assert_eq!(leader_passthrough_provider("cursor_agent"), None);
798
798
  assert_eq!(leader_passthrough_provider("not-a-passthrough"), None);
799
799
  }
800
+
801
+ #[cfg(unix)]
802
+ #[test]
803
+ #[serial_test::serial(env)]
804
+ fn kiro_doctor_inventory_is_read_only_and_does_not_claim_native_readiness() {
805
+ use std::os::unix::fs::PermissionsExt;
806
+ let env = crate::cli::hermetic_test_support::HermeticTestEnv::enter("kiro-inventory");
807
+ let bin = env.workspace("bin");
808
+ let _path = env.with_env("PATH", &bin.to_string_lossy());
809
+ let inventory = provider_doctor_checks();
810
+ assert_eq!(inventory["kiro"]["installed"], false);
811
+ assert_eq!(inventory["codex"]["command"], "codex");
812
+ let launcher = bin.join("kiro-cli");
813
+ std::fs::write(
814
+ &launcher,
815
+ "#!/bin/sh\nprintf called > \"$0.called\"\nexit 0\n",
816
+ )
817
+ .unwrap();
818
+ for (mode, installed) in [(0o600, false), (0o700, true)] {
819
+ std::fs::set_permissions(&launcher, std::fs::Permissions::from_mode(mode)).unwrap();
820
+ assert_eq!(
821
+ provider_doctor_checks()["kiro"],
822
+ json!({
823
+ "auth": "unknown",
824
+ "command": "kiro-cli",
825
+ "installed": installed,
826
+ "version": "unknown",
827
+ "probe_status": "not_run",
828
+ "provider_probe_status": "not_run",
829
+ })
830
+ );
831
+ }
832
+ assert!(!bin.join("kiro-cli.called").exists());
833
+ assert!(env.registry_entries().is_empty());
834
+ }
835
+
836
+ #[cfg(unix)]
837
+ #[test]
838
+ #[serial_test::serial(env)]
839
+ fn kiro_leader_entry_has_help_and_typed_refusal_without_native_or_fallback_effects() {
840
+ use std::os::unix::fs::PermissionsExt;
841
+ let env = crate::cli::hermetic_test_support::HermeticTestEnv::enter("kiro-leader-gate");
842
+ let cwd = env.workspace("empty");
843
+ let bin = env.workspace("bin");
844
+ for tool in ["kiro-cli", "kiro-cli-chat", "codex", "pi", "tmux"] {
845
+ let path = bin.join(tool);
846
+ std::fs::write(&path, "#!/bin/sh\nprintf called > \"$0.called\"\nexit 0\n").unwrap();
847
+ std::fs::set_permissions(path, std::fs::Permissions::from_mode(0o700)).unwrap();
848
+ }
849
+ let _path = env.with_env("PATH", &bin.to_string_lossy());
850
+ assert!(!crate::cli::emit::is_leader_passthrough_command("kiro"));
851
+ assert_eq!(leader_passthrough_provider("kiro"), None);
852
+ let help = crate::cli::emit::command_help(Some("kiro"));
853
+ assert!(help.contains("team-agent kiro [--json]"));
854
+ assert!(help.contains("kiro_leader_not_admitted"));
855
+ assert!(crate::cli::emit::default_help().contains("Check Kiro leader launch admission"));
856
+ for alias in ["-h", "--help"] {
857
+ assert_eq!(
858
+ crate::cli::run(&["kiro".into(), alias.into()], &cwd),
859
+ ExitCode::Ok
860
+ );
861
+ }
862
+ let cases: &[&[&str]] = &[&[], &["--json"], &["--attach-existing", "--confirm"]];
863
+ for values in cases {
864
+ let args: Vec<String> = values.iter().map(|arg| (*arg).into()).collect();
865
+ let result = cmd_kiro_leader(&args);
866
+ assert_eq!(result.exit, ExitCode::Error);
867
+ assert_eq!(result.as_json, values.contains(&"--json"));
868
+ let CmdOutput::Json(value) = result.output else {
869
+ panic!("expected structured capability refusal");
870
+ };
871
+ assert_eq!(value["schema_version"], "leader_launch.v1");
872
+ assert_eq!(value["ok"], false);
873
+ assert_eq!(value["provider"], "kiro");
874
+ assert_eq!(value["capability"], "leader_launch");
875
+ assert_eq!(value["reason"], "kiro_leader_not_admitted");
876
+ let mut argv = vec!["kiro".into()];
877
+ argv.extend(args);
878
+ assert_eq!(crate::cli::run(&argv, &cwd), ExitCode::Error);
879
+ }
880
+ assert_eq!(std::fs::read_dir(&cwd).unwrap().count(), 0);
881
+ assert_eq!(
882
+ std::fs::read_dir(&bin).unwrap().count(),
883
+ 5,
884
+ "no tool invoked"
885
+ );
886
+ assert!(env.registry_entries().is_empty());
887
+ }
@@ -436,6 +436,8 @@ fn leader_attach_command_for_plan_uses_plan_leader_window() {
436
436
  )),
437
437
  argv: Vec::new(),
438
438
  provider_argv: Vec::new(),
439
+ leader_prompt: None,
440
+ session_capture: None,
439
441
  leader_window: Some(crate::transport::WindowName::new("claude_code")),
440
442
  is_external_leader: false,
441
443
  leader_env: std::collections::BTreeMap::new(),
@@ -42,10 +42,16 @@ pub struct IgnoredTeamField {
42
42
  pub fn read_front_matter(path: &Path) -> Result<(Value, String), ModelError> {
43
43
  let text = fs::read_to_string(path)
44
44
  .map_err(|e| ModelError::Runtime(format!("{}: {e}", path.display())))?;
45
+ Ok(split_front_matter(&text))
46
+ }
47
+
48
+ /// Parse the same captured bytes that a prompt receipt hashes; the existing
49
+ /// path-based compiler keeps its original normalization/plain-text fallback.
50
+ pub(crate) fn split_front_matter(text: &str) -> (Value, String) {
45
51
  let text = text.replace("\r\n", "\n").replace('\r', "\n");
46
52
  let empty_meta = || Value::Map(Vec::new());
47
53
  let Some(rest) = text.strip_prefix("---\n") else {
48
- return Ok((empty_meta(), text));
54
+ return (empty_meta(), text);
49
55
  };
50
56
 
51
57
  let mut offset = 0;
@@ -58,7 +64,7 @@ pub fn read_front_matter(path: &Path) -> Result<(Value, String), ModelError> {
58
64
  offset += line.len();
59
65
  }
60
66
  let Some(close) = close else {
61
- return Ok((empty_meta(), text));
67
+ return (empty_meta(), text);
62
68
  };
63
69
  let raw_meta = &rest[..close];
64
70
  let meta = if raw_meta.trim().is_empty() {
@@ -66,11 +72,11 @@ pub fn read_front_matter(path: &Path) -> Result<(Value, String), ModelError> {
66
72
  } else {
67
73
  match yaml::loads(raw_meta) {
68
74
  Ok(meta) if meta.is_map() => meta,
69
- _ => return Ok((empty_meta(), text)),
75
+ _ => return (empty_meta(), text),
70
76
  }
71
77
  };
72
78
  let after_marker = &rest[close + 3..];
73
- Ok((meta, after_marker.trim_start_matches('\n').to_string()))
79
+ (meta, after_marker.trim_start_matches('\n').to_string())
74
80
  }
75
81
 
76
82
  pub fn ignored_owner_team_id_from_team_md(
@@ -489,6 +495,17 @@ fn compile_role_agent_with_mode(
489
495
  .filter(|provider| !provider.trim().is_empty())
490
496
  .unwrap_or_else(|| "pi".to_string());
491
497
  let is_pi = parse_canonical_provider(&provider) == Some(Provider::Pi);
498
+ #[cfg(unix)]
499
+ let contract = crate::contract_runtime::registry::descriptor(&provider);
500
+ #[cfg(unix)]
501
+ let contract_role = contract.is_some();
502
+ #[cfg(not(unix))]
503
+ let contract_role = false;
504
+ #[cfg(unix)]
505
+ let skip_team_effort =
506
+ contract.is_some_and(|descriptor| !descriptor.effort.inherit_team_default);
507
+ #[cfg(not(unix))]
508
+ let skip_team_effort = false;
492
509
  validate_pi_role_fields(&meta, role_path, &provider)?;
493
510
  let model = string_field(&meta, "model")
494
511
  .filter(|value| !value.trim().is_empty())
@@ -567,7 +584,7 @@ fn compile_role_agent_with_mode(
567
584
  Some(raw) if !raw.trim().is_empty() => ProviderEffort::parse(raw.trim()),
568
585
  _ => None,
569
586
  };
570
- let resolved_effort = if is_pi {
587
+ let resolved_effort = if is_pi || skip_team_effort {
571
588
  role_effort
572
589
  } else {
573
590
  role_effort.or(team_effort)
@@ -582,13 +599,30 @@ fn compile_role_agent_with_mode(
582
599
  _ => None,
583
600
  })
584
601
  .unwrap_or("");
585
- let provider_enum = parse_canonical_provider(provider_str).unwrap_or(Provider::Codex);
586
- if let Err(reason) = effort.resolve_for_provider(provider_enum) {
587
- return Err(ModelError::Validation(format!(
588
- "{}: {reason} (effort: {}; provider: {provider_str})",
589
- role_path.display(),
590
- effort.as_str()
591
- )));
602
+ #[cfg(unix)]
603
+ if let Some(descriptor) = contract {
604
+ use team_agent_contract::contract::descriptor::{Effort, EffortAdmission};
605
+ let native_effort = Effort::parse(effort.as_str())
606
+ .map_err(|error| ModelError::Validation(error.to_string()))?;
607
+ if let EffortAdmission::Reject(reason) =
608
+ descriptor.effort.admission[native_effort.index()]
609
+ {
610
+ return Err(ModelError::Validation(format!(
611
+ "{}: {}",
612
+ role_path.display(),
613
+ reason.message
614
+ )));
615
+ }
616
+ }
617
+ if !contract_role {
618
+ let provider_enum = parse_canonical_provider(provider_str).unwrap_or(Provider::Codex);
619
+ if let Err(reason) = effort.resolve_for_provider(provider_enum) {
620
+ return Err(ModelError::Validation(format!(
621
+ "{}: {reason} (effort: {}; provider: {provider_str})",
622
+ role_path.display(),
623
+ effort.as_str()
624
+ )));
625
+ }
592
626
  }
593
627
  agent_items.push(("effort", Value::Str(effort.as_str().to_string())));
594
628
  }
@@ -712,12 +746,17 @@ mod pi_preflight_tests {
712
746
 
713
747
  #[test]
714
748
  fn omitted_qualified_and_non_pi_do_not_discover() {
715
- for meta in [role("pi", None), role("pi", Some("openai/gpt-5")), role("codex", Some("gpt-5"))] {
749
+ for meta in [
750
+ role("pi", None),
751
+ role("pi", Some("openai/gpt-5")),
752
+ role("codex", Some("gpt-5")),
753
+ ] {
716
754
  let calls = Cell::new(0);
717
755
  assert!(preflight_pi_role_model_with(&meta, |_| {
718
756
  calls.set(calls.get() + 1);
719
757
  Ok(vec![])
720
- }).is_ok());
758
+ })
759
+ .is_ok());
721
760
  assert_eq!(calls.get(), 0);
722
761
  }
723
762
  }
@@ -728,12 +767,21 @@ mod pi_preflight_tests {
728
767
  let error = preflight_pi_role_model_with(&role("pi", Some("gpt-5.6-sol")), |requested| {
729
768
  calls.set(calls.get() + 1);
730
769
  assert_eq!(requested, "gpt-5.6-sol");
731
- Ok(vec!["openai-codex/gpt-5.6-sol".into(), "azure/gpt-5.6-sol".into()])
732
- }).expect_err("unqualified model must fail closed");
770
+ Ok(vec![
771
+ "openai-codex/gpt-5.6-sol".into(),
772
+ "azure/gpt-5.6-sol".into(),
773
+ ])
774
+ })
775
+ .expect_err("unqualified model must fail closed");
733
776
  assert_eq!(calls.get(), 1);
734
- assert_eq!(error.candidates, vec!["openai-codex/gpt-5.6-sol", "azure/gpt-5.6-sol"]);
777
+ assert_eq!(
778
+ error.candidates,
779
+ vec!["openai-codex/gpt-5.6-sol", "azure/gpt-5.6-sol"]
780
+ );
735
781
  assert!(!error.not_ready);
736
- assert!(error.action.contains("team-agent models --provider pi --search gpt-5.6-sol"));
782
+ assert!(error
783
+ .action
784
+ .contains("team-agent models --provider pi --search gpt-5.6-sol"));
737
785
  }
738
786
 
739
787
  #[test]