feat: use builtin profile by default

This commit is contained in:
2026-05-30 03:57:40 +09:00
parent ffe1785262
commit 625730cb0a
8 changed files with 315 additions and 283 deletions
+118 -96
View File
@@ -4,13 +4,13 @@ use std::process::ExitCode;
use clap::Parser;
use manifest::{NixProfileResolver, PodManifest, PodManifestConfig, ProfileSelector, paths};
use pod::{Pod, PodController, PodFactory, PromptLoader};
use pod::{Pod, PodController, PromptLoader};
use session_store::{FsStore, PodMetadataStore, SegmentId, Store};
#[derive(Debug, Parser)]
#[command(
name = "insomnia-pod",
about = "Spawn a Pod process from manifest layers or a single manifest file"
about = "Spawn a Pod process from a Nix profile or a single manifest file"
)]
struct Cli {
/// Nix profile to evaluate. Accepts an explicit path, `path:<path>`, a
@@ -29,19 +29,20 @@ struct Cli {
#[arg(long, value_name = "NAME", requires = "profile", conflicts_with_all = ["pod", "session", "adopt"])]
profile_pod_name: Option<String>,
/// Manifest TOML to use directly, without loading user, project, or
/// overlay layers.
/// Manifest TOML to use directly as a one-file compatibility/debug input.
/// This bypasses profile discovery but still applies builtin defaults and
/// the same required-field validation boundary.
#[arg(long, value_name = "PATH", conflicts_with_all = ["project", "overlay"])]
manifest: Option<PathBuf>,
/// Start the project-manifest walk from this directory. When
/// omitted, the factory walks up from the current working
/// directory looking for `.insomnia/manifest.toml`.
/// Deprecated manifest-cascade project root flag. Ambient project/user
/// manifest discovery has been removed; configure/select a profile instead.
#[arg(long, value_name = "PATH")]
project: Option<PathBuf>,
/// Inline TOML string applied as the highest-priority overlay
/// layer. Example: `--overlay 'pod.name = "dbg"'`.
/// Inline TOML override applied to the implicit default profile. This is
/// retained for launchers that need to supply restore/session-time values;
/// it does not re-enable user/project manifest discovery.
#[arg(long, value_name = "TOML")]
overlay: Option<String>,
@@ -94,7 +95,7 @@ fn resolve_manifest_with_user_manifest_env(
fn resolve_manifest_with_user_manifest_env_and_profile_loader<F>(
cli: &Cli,
user_manifest_env: Option<OsString>,
_user_manifest_env: Option<OsString>,
load_profile_fn: F,
) -> Result<(PodManifest, PromptLoader), String>
where
@@ -105,29 +106,31 @@ where
return load_profile_fn(&selector, cli.profile_pod_name.as_deref());
}
let user_manifest = paths::user_manifest_path_from_env(user_manifest_env);
if let Some(path) = &cli.manifest {
if user_manifest.is_some() {
return Err(format!(
"--manifest cannot be used when {} is set",
paths::USER_MANIFEST_ENV
));
}
return load_single_manifest(path, cli.pod.as_deref());
}
let factory = build_factory_with_user_manifest_path(cli, user_manifest)?;
factory
.resolve()
.map_err(|e| format!("failed to resolve manifest cascade: {e}"))
if cli.project.is_some() {
return Err(
"--project is no longer supported; normal startup uses profile discovery/default, \
and --manifest <PATH> is the only one-file manifest mode"
.to_string(),
);
}
let selector = ProfileSelector::Default;
let (manifest, loader) = load_profile_fn(&selector, None)?;
let manifest = apply_cli_overrides_to_manifest(manifest, cli)?;
Ok((manifest, loader))
}
fn load_profile(
selector: &ProfileSelector,
pod_name_override: Option<&str>,
) -> Result<(PodManifest, PromptLoader), String> {
let resolver = NixProfileResolver::new();
let cwd = std::env::current_dir()
.map_err(|e| format!("failed to resolve current directory for profile: {e}"))?;
let resolver = NixProfileResolver::new().with_workspace_base(cwd);
let mut resolved = resolver.resolve(selector).map_err(|e| {
format!(
"failed to resolve profile {}: {e}",
@@ -146,28 +149,32 @@ fn load_single_manifest(
) -> Result<(PodManifest, PromptLoader), String> {
let toml = std::fs::read_to_string(path)
.map_err(|e| format!("failed to read manifest {}: {e}", path.display()))?;
let manifest = match pod_name_override {
Some(pod_name) => match PodManifest::from_toml(&toml) {
Ok(mut manifest) => {
manifest.pod.name = pod_name.to_string();
manifest
}
Err(_) => {
let base = PodManifestConfig::from_toml(&toml)
.map_err(|e| format!("failed to parse manifest {}: {e}", path.display()))?;
let overlay = PodManifestConfig::from_toml(&pod_name_overlay_toml(pod_name))
.expect("pod name overlay TOML is generated");
PodManifest::try_from(base.merge(overlay)).map_err(|e| {
format!(
"failed to resolve manifest {} with --pod: {e}",
path.display()
)
})?
}
},
None => PodManifest::from_toml(&toml)
.map_err(|e| format!("failed to parse manifest {}: {e}", path.display()))?,
let absolute_path = if path.is_absolute() {
path.to_path_buf()
} else {
std::env::current_dir()
.map_err(|e| format!("failed to resolve current directory: {e}"))?
.join(path)
};
let base_dir = absolute_path.parent().ok_or_else(|| {
format!(
"manifest path {} has no parent directory",
absolute_path.display()
)
})?;
let mut config = PodManifestConfig::builtin_defaults().merge(
PodManifestConfig::from_toml(&toml)
.map_err(|e| format!("failed to parse manifest {}: {e}", path.display()))?
.resolve_paths(base_dir),
);
if let Some(pod_name) = pod_name_override {
config = config.merge(
PodManifestConfig::from_toml(&pod_name_overlay_toml(pod_name))
.expect("pod name overlay TOML is generated"),
);
}
let manifest = PodManifest::try_from(config)
.map_err(|e| format!("failed to resolve manifest {}: {e}", path.display()))?;
Ok((manifest, PromptLoader::builtins_only()))
}
@@ -179,43 +186,33 @@ fn pod_name_overlay_toml(pod_name: &str) -> String {
toml::to_string(&toml::Value::Table(root)).expect("pod name overlay serialisation cannot fail")
}
fn build_factory_with_user_manifest_path(
fn manifest_to_config(manifest: &PodManifest) -> Result<PodManifestConfig, String> {
let value = serde_json::to_value(manifest)
.map_err(|e| format!("failed to serialise resolved manifest for overlay: {e}"))?;
serde_json::from_value(value)
.map_err(|e| format!("failed to convert resolved manifest for overlay: {e}"))
}
fn apply_cli_overrides_to_manifest(
mut manifest: PodManifest,
cli: &Cli,
user_manifest: Option<PathBuf>,
) -> Result<PodFactory, String> {
let mut factory = PodFactory::new();
factory = match user_manifest {
Some(path) => factory
.with_user_manifest(path)
.map_err(|e| format!("failed to load user manifest: {e}"))?,
None => factory
.with_user_manifest_auto()
.map_err(|e| format!("failed to auto-load user manifest: {e}"))?,
};
factory = match &cli.project {
Some(path) => factory
.with_project_manifest_from(path)
.map_err(|e| format!("failed to load project manifest: {e}"))?,
None => factory
.with_project_manifest_auto()
.map_err(|e| format!("failed to auto-load project manifest: {e}"))?,
};
) -> Result<PodManifest, String> {
let profile = manifest.profile.clone();
if let Some(overlay) = cli.overlay.as_deref() {
factory = factory
.with_overlay_toml(overlay)
.map_err(|e| format!("failed to parse overlay TOML: {e}"))?;
let base_dir = std::env::current_dir()
.map_err(|e| format!("failed to resolve current directory for overlay: {e}"))?;
let overlay = PodManifestConfig::from_toml(overlay)
.map_err(|e| format!("failed to parse overlay TOML: {e}"))?
.resolve_paths(&base_dir);
let config = manifest_to_config(&manifest)?.merge(overlay);
manifest = PodManifest::try_from(config)
.map_err(|e| format!("failed to resolve default profile overlay: {e}"))?;
manifest.profile = profile;
}
if let Some(pod_name) = cli.pod.as_deref() {
factory = factory
.with_overlay_toml(&pod_name_overlay_toml(pod_name))
.map_err(|e| format!("failed to apply --pod overlay: {e}"))?;
manifest.pod.name = pod_name.to_string();
}
Ok(factory)
Ok(manifest)
}
#[tokio::main]
@@ -442,18 +439,20 @@ permission = "write"
}
#[test]
fn manifest_conflicts_with_user_manifest_env_when_env_is_non_empty() {
fn manifest_ignores_non_empty_user_manifest_env() {
let tmp = TempDir::new().unwrap();
let manifest = tmp.path().join("manifest.toml");
write(&manifest, &manifest_toml("single", tmp.path()));
let cli = Cli::try_parse_from(["insomnia-pod", "--manifest", manifest.to_str().unwrap()])
.unwrap();
let err = resolve_manifest_with_user_manifest_env(&cli, Some(OsString::from("user.toml")))
.unwrap_err();
let (manifest, loader) =
resolve_manifest_with_user_manifest_env(&cli, Some(OsString::from("user.toml")))
.unwrap();
assert!(err.contains("--manifest cannot be used"));
assert!(err.contains(paths::USER_MANIFEST_ENV));
assert_eq!(manifest.pod.name, "single");
assert!(loader.user_dir().is_none());
assert!(loader.workspace_dir().is_none());
}
#[test]
@@ -549,26 +548,38 @@ permission = "write"
}
#[test]
fn user_manifest_env_overrides_auto_user_manifest_path() {
fn normal_startup_uses_default_profile_and_ignores_user_manifest_env() {
let tmp = TempDir::new().unwrap();
let user_manifest = tmp.path().join("custom-user.toml");
write(&user_manifest, &manifest_toml("from-env", tmp.path()));
let no_project_root = tmp.path().join("no-project");
std::fs::create_dir_all(&no_project_root).unwrap();
let cli = Cli::try_parse_from([
"insomnia-pod",
"--project",
no_project_root.to_str().unwrap(),
])
.unwrap();
let cli = Cli::try_parse_from(["insomnia-pod"]).unwrap();
let mut called = false;
let (manifest, _loader) = resolve_manifest_with_user_manifest_env(
let (manifest, _loader) = resolve_manifest_with_user_manifest_env_and_profile_loader(
&cli,
Some(user_manifest.as_os_str().to_os_string()),
Some(OsString::from("ignored-user-manifest.toml")),
|selector, pod_name| {
called = true;
assert_eq!(selector, &ProfileSelector::Default);
assert_eq!(pod_name, None);
let manifest =
PodManifest::from_toml(&manifest_toml("from-default-profile", tmp.path()))
.unwrap();
Ok((manifest, PromptLoader::builtins_only()))
},
)
.unwrap();
assert_eq!(manifest.pod.name, "from-env");
assert!(called);
assert_eq!(manifest.pod.name, "from-default-profile");
}
#[test]
fn project_flag_no_longer_enables_ambient_manifest_cascade() {
let cli = Cli::try_parse_from(["insomnia-pod", "--project", "."]).unwrap();
let err = resolve_manifest_with_user_manifest_env_and_profile_loader(&cli, None, |_, _| {
panic!("default profile loader must not run when deprecated --project is present")
})
.unwrap_err();
assert!(err.contains("--project is no longer supported"));
}
#[test]
@@ -605,7 +616,17 @@ permission = "write"
let manifest = tmp.path().join("manifest.toml");
write(
&manifest,
&manifest_toml("unused", tmp.path()).replace("name = \"unused\"\n", ""),
r#"
[pod]
[model]
scheme = "anthropic"
model_id = "test-model"
[[scope.allow]]
target = "."
permission = "write"
"#,
);
let cli = Cli::try_parse_from([
"insomnia-pod",
@@ -619,6 +640,7 @@ permission = "write"
let (manifest, _loader) = resolve_manifest_with_user_manifest_env(&cli, None).unwrap();
assert_eq!(manifest.pod.name, "from-flag");
assert_eq!(manifest.scope.allow[0].target, tmp.path());
}
#[test]