fix: remove generic overlay startup path
This commit is contained in:
+112
-134
@@ -1,9 +1,10 @@
|
||||
use std::ffi::OsString;
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::process::ExitCode;
|
||||
|
||||
use clap::Parser;
|
||||
use manifest::{NixProfileResolver, PodManifest, PodManifestConfig, ProfileSelector, paths};
|
||||
use manifest::{
|
||||
NixProfileResolver, PodManifest, PodManifestConfig, ProfileSelector, ScopeConfig, paths,
|
||||
};
|
||||
use pod::{Pod, PodController, PromptLoader};
|
||||
use session_store::{FsStore, PodMetadataStore, SegmentId, Store};
|
||||
|
||||
@@ -19,7 +20,7 @@ struct Cli {
|
||||
#[arg(
|
||||
long,
|
||||
value_name = "PROFILE",
|
||||
conflicts_with_all = ["manifest", "project", "overlay", "pod", "session", "adopt"]
|
||||
conflicts_with_all = ["manifest", "project", "pod", "session", "adopt"]
|
||||
)]
|
||||
profile: Option<String>,
|
||||
|
||||
@@ -32,7 +33,7 @@ struct Cli {
|
||||
/// 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"])]
|
||||
#[arg(long, value_name = "PATH", conflicts_with_all = ["project"])]
|
||||
manifest: Option<PathBuf>,
|
||||
|
||||
/// Deprecated manifest-cascade project root flag. Ambient project/user
|
||||
@@ -40,11 +41,23 @@ struct Cli {
|
||||
#[arg(long, value_name = "PATH")]
|
||||
project: Option<PathBuf>,
|
||||
|
||||
/// 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>,
|
||||
/// Internal typed pod-name override for session restore launched by the TUI.
|
||||
#[arg(long, value_name = "NAME", requires = "session", hide = true)]
|
||||
session_pod_name: Option<String>,
|
||||
|
||||
/// Internal typed scope snapshot for session restore launched by the TUI.
|
||||
#[arg(long, value_name = "JSON", requires = "session", hide = true)]
|
||||
resume_scope_json: Option<String>,
|
||||
|
||||
/// Internal resolved manifest config for delegated child Pod spawning.
|
||||
#[arg(
|
||||
long,
|
||||
value_name = "JSON",
|
||||
requires = "adopt",
|
||||
conflicts_with_all = ["profile", "manifest", "project", "pod", "session"],
|
||||
hide = true
|
||||
)]
|
||||
spawn_config_json: Option<String>,
|
||||
|
||||
/// Directory for session persistence. Defaults to
|
||||
/// `<data_dir>/sessions/` (see `manifest::paths`).
|
||||
@@ -83,45 +96,56 @@ struct Cli {
|
||||
}
|
||||
|
||||
fn resolve_manifest(cli: &Cli) -> Result<(PodManifest, PromptLoader), String> {
|
||||
resolve_manifest_with_user_manifest_env(cli, std::env::var_os(paths::USER_MANIFEST_ENV))
|
||||
resolve_manifest_with_profile_loader(cli, load_profile)
|
||||
}
|
||||
|
||||
fn resolve_manifest_with_user_manifest_env(
|
||||
fn resolve_manifest_with_profile_loader<F>(
|
||||
cli: &Cli,
|
||||
user_manifest_env: Option<OsString>,
|
||||
) -> Result<(PodManifest, PromptLoader), String> {
|
||||
resolve_manifest_with_user_manifest_env_and_profile_loader(cli, user_manifest_env, load_profile)
|
||||
}
|
||||
|
||||
fn resolve_manifest_with_user_manifest_env_and_profile_loader<F>(
|
||||
cli: &Cli,
|
||||
_user_manifest_env: Option<OsString>,
|
||||
load_profile_fn: F,
|
||||
) -> Result<(PodManifest, PromptLoader), String>
|
||||
where
|
||||
F: FnOnce(&ProfileSelector, Option<&str>) -> Result<(PodManifest, PromptLoader), String>,
|
||||
{
|
||||
if let Some(profile) = &cli.profile {
|
||||
let mut manifest_and_loader = if let Some(config_json) = cli.spawn_config_json.as_deref() {
|
||||
load_spawn_config_json(config_json)?
|
||||
} else if let Some(profile) = &cli.profile {
|
||||
let selector = ProfileSelector::parse_cli(profile);
|
||||
return load_profile_fn(&selector, cli.profile_pod_name.as_deref());
|
||||
}
|
||||
load_profile_fn(&selector, cli.profile_pod_name.as_deref())?
|
||||
} else if let Some(path) = &cli.manifest {
|
||||
load_single_manifest(path, cli.pod.as_deref())?
|
||||
} else {
|
||||
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;
|
||||
load_profile_fn(&selector, cli.pod.as_deref())?
|
||||
};
|
||||
|
||||
if let Some(path) = &cli.manifest {
|
||||
return load_single_manifest(path, cli.pod.as_deref());
|
||||
}
|
||||
apply_session_restore_overrides(&mut manifest_and_loader.0, cli)?;
|
||||
Ok(manifest_and_loader)
|
||||
}
|
||||
|
||||
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(),
|
||||
);
|
||||
fn apply_session_restore_overrides(manifest: &mut PodManifest, cli: &Cli) -> Result<(), String> {
|
||||
if let Some(pod_name) = cli.session_pod_name.as_deref() {
|
||||
manifest.pod.name = pod_name.to_string();
|
||||
}
|
||||
if let Some(scope_json) = cli.resume_scope_json.as_deref() {
|
||||
manifest.scope = serde_json::from_str::<ScopeConfig>(scope_json)
|
||||
.map_err(|e| format!("failed to parse --resume-scope-json: {e}"))?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
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_spawn_config_json(config_json: &str) -> Result<(PodManifest, PromptLoader), String> {
|
||||
let config = serde_json::from_str::<PodManifestConfig>(config_json)
|
||||
.map_err(|e| format!("failed to parse --spawn-config-json: {e}"))?;
|
||||
let manifest = PodManifest::try_from(PodManifestConfig::builtin_defaults().merge(config))
|
||||
.map_err(|e| format!("failed to resolve --spawn-config-json: {e}"))?;
|
||||
Ok((manifest, PromptLoader::builtins_only()))
|
||||
}
|
||||
|
||||
fn load_profile(
|
||||
@@ -168,53 +192,13 @@ fn load_single_manifest(
|
||||
.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"),
|
||||
);
|
||||
config.pod.name = Some(pod_name.to_string());
|
||||
}
|
||||
let manifest = PodManifest::try_from(config)
|
||||
.map_err(|e| format!("failed to resolve manifest {}: {e}", path.display()))?;
|
||||
Ok((manifest, PromptLoader::builtins_only()))
|
||||
}
|
||||
|
||||
fn pod_name_overlay_toml(pod_name: &str) -> String {
|
||||
let mut pod = toml::value::Table::new();
|
||||
pod.insert("name".into(), toml::Value::String(pod_name.to_string()));
|
||||
let mut root = toml::value::Table::new();
|
||||
root.insert("pod".into(), toml::Value::Table(pod));
|
||||
toml::to_string(&toml::Value::Table(root)).expect("pod name overlay serialisation cannot fail")
|
||||
}
|
||||
|
||||
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,
|
||||
) -> Result<PodManifest, String> {
|
||||
let profile = manifest.profile.clone();
|
||||
if let Some(overlay) = cli.overlay.as_deref() {
|
||||
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() {
|
||||
manifest.pod.name = pod_name.to_string();
|
||||
}
|
||||
Ok(manifest)
|
||||
}
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() -> ExitCode {
|
||||
let cli = Cli::parse();
|
||||
@@ -416,7 +400,7 @@ permission = "write"
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn manifest_conflicts_with_project_and_overlay() {
|
||||
fn manifest_conflicts_with_project() {
|
||||
let project_err = Cli::try_parse_from([
|
||||
"insomnia-pod",
|
||||
"--manifest",
|
||||
@@ -426,29 +410,23 @@ permission = "write"
|
||||
])
|
||||
.unwrap_err();
|
||||
assert_eq!(project_err.kind(), clap::error::ErrorKind::ArgumentConflict);
|
||||
|
||||
let overlay_err = Cli::try_parse_from([
|
||||
"insomnia-pod",
|
||||
"--manifest",
|
||||
"manifest.toml",
|
||||
"--overlay",
|
||||
"pod.name = 'x'",
|
||||
])
|
||||
.unwrap_err();
|
||||
assert_eq!(overlay_err.kind(), clap::error::ErrorKind::ArgumentConflict);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn manifest_ignores_non_empty_user_manifest_env() {
|
||||
fn overlay_flag_is_not_accepted() {
|
||||
let err = Cli::try_parse_from(["insomnia-pod", "--overlay", "pod.name = 'x'"]).unwrap_err();
|
||||
assert_eq!(err.kind(), clap::error::ErrorKind::UnknownArgument);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn manifest_loads_single_file_without_user_or_workspace_prompt_loader() {
|
||||
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 (manifest, loader) =
|
||||
resolve_manifest_with_user_manifest_env(&cli, Some(OsString::from("user.toml")))
|
||||
.unwrap();
|
||||
let (manifest, loader) = resolve_manifest(&cli).unwrap();
|
||||
|
||||
assert_eq!(manifest.pod.name, "single");
|
||||
assert!(loader.user_dir().is_none());
|
||||
@@ -456,7 +434,7 @@ permission = "write"
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn profile_ignores_non_empty_user_manifest_env() {
|
||||
fn profile_uses_selected_profile() {
|
||||
let tmp = TempDir::new().unwrap();
|
||||
let profile = tmp.path().join("profile.nix");
|
||||
let cli = Cli::try_parse_from([
|
||||
@@ -469,10 +447,8 @@ permission = "write"
|
||||
.unwrap();
|
||||
let mut called = false;
|
||||
|
||||
let (manifest, loader) = resolve_manifest_with_user_manifest_env_and_profile_loader(
|
||||
&cli,
|
||||
Some(OsString::from("non-existent-user-manifest.toml")),
|
||||
|selector, pod_name| {
|
||||
let (manifest, loader) =
|
||||
resolve_manifest_with_profile_loader(&cli, |selector, pod_name| {
|
||||
called = true;
|
||||
assert_eq!(selector, &ProfileSelector::path(profile.clone()));
|
||||
assert_eq!(pod_name, Some("from-profile-name"));
|
||||
@@ -482,9 +458,8 @@ permission = "write"
|
||||
manifest.pod.name = pod_name.to_string();
|
||||
}
|
||||
Ok((manifest, PromptLoader::builtins_only()))
|
||||
},
|
||||
)
|
||||
.unwrap();
|
||||
})
|
||||
.unwrap();
|
||||
|
||||
assert!(called);
|
||||
assert_eq!(manifest.pod.name, "from-profile-name");
|
||||
@@ -505,10 +480,8 @@ permission = "write"
|
||||
.unwrap();
|
||||
let mut called = false;
|
||||
|
||||
let (manifest, _loader) = resolve_manifest_with_user_manifest_env_and_profile_loader(
|
||||
&cli,
|
||||
None,
|
||||
|selector, pod_name| {
|
||||
let (manifest, _loader) =
|
||||
resolve_manifest_with_profile_loader(&cli, |selector, pod_name| {
|
||||
called = true;
|
||||
assert_eq!(
|
||||
selector,
|
||||
@@ -523,40 +496,21 @@ permission = "write"
|
||||
manifest.pod.name = pod_name.to_string();
|
||||
}
|
||||
Ok((manifest, PromptLoader::builtins_only()))
|
||||
},
|
||||
)
|
||||
.unwrap();
|
||||
})
|
||||
.unwrap();
|
||||
|
||||
assert!(called);
|
||||
assert_eq!(manifest.pod.name, "from-profile-name");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn manifest_allows_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 (manifest, loader) =
|
||||
resolve_manifest_with_user_manifest_env(&cli, Some(OsString::new())).unwrap();
|
||||
|
||||
assert_eq!(manifest.pod.name, "single");
|
||||
assert!(loader.user_dir().is_none());
|
||||
assert!(loader.workspace_dir().is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn normal_startup_uses_default_profile_and_ignores_user_manifest_env() {
|
||||
fn normal_startup_uses_default_profile() {
|
||||
let tmp = TempDir::new().unwrap();
|
||||
let cli = Cli::try_parse_from(["insomnia-pod"]).unwrap();
|
||||
let mut called = false;
|
||||
|
||||
let (manifest, _loader) = resolve_manifest_with_user_manifest_env_and_profile_loader(
|
||||
&cli,
|
||||
Some(OsString::from("ignored-user-manifest.toml")),
|
||||
|selector, pod_name| {
|
||||
let (manifest, _loader) =
|
||||
resolve_manifest_with_profile_loader(&cli, |selector, pod_name| {
|
||||
called = true;
|
||||
assert_eq!(selector, &ProfileSelector::Default);
|
||||
assert_eq!(pod_name, None);
|
||||
@@ -564,9 +518,8 @@ permission = "write"
|
||||
PodManifest::from_toml(&manifest_toml("from-default-profile", tmp.path()))
|
||||
.unwrap();
|
||||
Ok((manifest, PromptLoader::builtins_only()))
|
||||
},
|
||||
)
|
||||
.unwrap();
|
||||
})
|
||||
.unwrap();
|
||||
|
||||
assert!(called);
|
||||
assert_eq!(manifest.pod.name, "from-default-profile");
|
||||
@@ -575,7 +528,7 @@ permission = "write"
|
||||
#[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, |_, _| {
|
||||
let err = resolve_manifest_with_profile_loader(&cli, |_, _| {
|
||||
panic!("default profile loader must not run when deprecated --project is present")
|
||||
})
|
||||
.unwrap_err();
|
||||
@@ -605,7 +558,7 @@ permission = "write"
|
||||
])
|
||||
.unwrap();
|
||||
|
||||
let (manifest, _loader) = resolve_manifest_with_user_manifest_env(&cli, None).unwrap();
|
||||
let (manifest, _loader) = resolve_manifest(&cli).unwrap();
|
||||
|
||||
assert_eq!(manifest.pod.name, "from-flag");
|
||||
}
|
||||
@@ -637,12 +590,37 @@ permission = "write"
|
||||
])
|
||||
.unwrap();
|
||||
|
||||
let (manifest, _loader) = resolve_manifest_with_user_manifest_env(&cli, None).unwrap();
|
||||
let (manifest, _loader) = resolve_manifest(&cli).unwrap();
|
||||
|
||||
assert_eq!(manifest.pod.name, "from-flag");
|
||||
assert_eq!(manifest.scope.allow[0].target, tmp.path());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn pod_flag_with_no_manifest_creates_from_default_profile_with_typed_name() {
|
||||
let tmp = TempDir::new().unwrap();
|
||||
let cli = Cli::try_parse_from(["insomnia-pod", "--pod", "agent"]).unwrap();
|
||||
let mut called = false;
|
||||
|
||||
let (manifest, _loader) =
|
||||
resolve_manifest_with_profile_loader(&cli, |selector, pod_name| {
|
||||
called = true;
|
||||
assert_eq!(selector, &ProfileSelector::Default);
|
||||
assert_eq!(pod_name, Some("agent"));
|
||||
let mut manifest =
|
||||
PodManifest::from_toml(&manifest_toml("from-default-profile", tmp.path()))
|
||||
.unwrap();
|
||||
if let Some(pod_name) = pod_name {
|
||||
manifest.pod.name = pod_name.to_string();
|
||||
}
|
||||
Ok((manifest, PromptLoader::builtins_only()))
|
||||
})
|
||||
.unwrap();
|
||||
|
||||
assert!(called);
|
||||
assert_eq!(manifest.pod.name, "agent");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn profile_conflicts_with_manifest_and_restore_modes() {
|
||||
let segment_id = session_store::new_segment_id().to_string();
|
||||
@@ -696,7 +674,7 @@ permission = "write"
|
||||
])
|
||||
.unwrap();
|
||||
|
||||
let (manifest, loader) = resolve_manifest_with_user_manifest_env(&cli, None).unwrap();
|
||||
let (manifest, loader) = resolve_manifest(&cli).unwrap();
|
||||
|
||||
assert_eq!(manifest.pod.name, "single-file");
|
||||
assert!(loader.user_dir().is_none());
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
//! `SpawnPod` tool — launch a new Pod process as a child of this one.
|
||||
//!
|
||||
//! Wires pod-registry delegation, overlay-TOML construction, subprocess
|
||||
//! Wires pod-registry delegation, child manifest-config construction, subprocess
|
||||
//! launch, and socket handoff into a single `Tool` implementation. When
|
||||
//! the LLM calls `SpawnPod`, a fresh `insomnia-pod` binary is exec'd in its own
|
||||
//! process group, the pod-registry is updated atomically, and the child's
|
||||
@@ -116,8 +116,8 @@ pub struct SpawnPodTool {
|
||||
/// no-op.
|
||||
parent_socket: Option<PathBuf>,
|
||||
/// Spawner's resolved provider config — copied into every spawned
|
||||
/// Pod's overlay TOML so the child does not need its own provider
|
||||
/// configuration in the manifest cascade. Per-spawn override is
|
||||
/// Pod's internal manifest config so the child does not need its own provider
|
||||
/// configuration. Per-spawn override is
|
||||
/// out of scope here (see `tickets/spawn-inherit-provider.md`).
|
||||
spawner_model: ModelManifest,
|
||||
/// Spawner's runtime scope. After a successful spawn, the
|
||||
@@ -208,7 +208,7 @@ impl Tool for SpawnPodTool {
|
||||
// it back — even if later steps (Method::Run delivery, record
|
||||
// write) fail, the child is running and will release its own
|
||||
// entry on exit.
|
||||
let overlay_toml = match build_overlay_toml(
|
||||
let spawn_config_json = match build_spawn_config_json(
|
||||
&input.name,
|
||||
&instruction,
|
||||
&scope_allow,
|
||||
@@ -218,13 +218,13 @@ impl Tool for SpawnPodTool {
|
||||
Err(e) => {
|
||||
self.release_reservation(&lock_path, &input.name);
|
||||
return Err(ToolError::ExecutionFailed(format!(
|
||||
"overlay serialisation: {e}"
|
||||
"spawn config serialisation: {e}"
|
||||
)));
|
||||
}
|
||||
};
|
||||
|
||||
let start_outcome = self
|
||||
.exec_child(&input.name, &overlay_toml, &predicted_socket)
|
||||
.exec_child(&input.name, &spawn_config_json, &predicted_socket)
|
||||
.await;
|
||||
if let Err(e) = start_outcome {
|
||||
self.release_reservation(&lock_path, &input.name);
|
||||
@@ -300,7 +300,7 @@ impl SpawnPodTool {
|
||||
async fn exec_child(
|
||||
&self,
|
||||
pod_name: &str,
|
||||
overlay_toml: &str,
|
||||
spawn_config_json: &str,
|
||||
predicted_socket: &Path,
|
||||
) -> Result<(), ToolError> {
|
||||
let pod_command =
|
||||
@@ -329,8 +329,8 @@ impl SpawnPodTool {
|
||||
cmd.arg("--adopt")
|
||||
.arg("--callback")
|
||||
.arg(&self.callback_socket)
|
||||
.arg("--overlay")
|
||||
.arg(overlay_toml)
|
||||
.arg("--spawn-config-json")
|
||||
.arg(spawn_config_json)
|
||||
.current_dir(&self.spawner_pwd)
|
||||
.stdin(Stdio::null())
|
||||
.stdout(Stdio::null())
|
||||
@@ -382,20 +382,21 @@ fn parse_scope(rules: &[ScopeRuleInput]) -> Result<Vec<ScopeRule>, ToolError> {
|
||||
.collect()
|
||||
}
|
||||
|
||||
/// Serialise the overlay TOML that gets handed to the child `insomnia-pod`
|
||||
/// binary via `--overlay`. `PodManifestConfig`'s `Serialize` impl is
|
||||
/// the single source of truth for the on-disk manifest format.
|
||||
/// Serialise the internal manifest config that gets handed to the child
|
||||
/// `insomnia-pod` binary via the hidden `--spawn-config-json` flag.
|
||||
/// `PodManifestConfig`'s `Serialize` impl is the single source of truth for the
|
||||
/// internal handoff shape.
|
||||
///
|
||||
/// The child's working directory is set separately via
|
||||
/// `Command::current_dir` (see [`SpawnPodTool::exec_child`]) — it is
|
||||
/// not part of the manifest.
|
||||
fn build_overlay_toml(
|
||||
fn build_spawn_config_json(
|
||||
name: &str,
|
||||
instruction: &str,
|
||||
scope_allow: &[ScopeRule],
|
||||
model: &ModelManifest,
|
||||
) -> Result<String, toml::ser::Error> {
|
||||
let overlay = PodManifestConfig {
|
||||
) -> Result<String, serde_json::Error> {
|
||||
let config = PodManifestConfig {
|
||||
pod: PodMetaConfig {
|
||||
name: Some(name.to_string()),
|
||||
prompt_pack: None,
|
||||
@@ -411,7 +412,7 @@ fn build_overlay_toml(
|
||||
},
|
||||
..Default::default()
|
||||
};
|
||||
toml::to_string(&overlay)
|
||||
serde_json::to_string(&config)
|
||||
}
|
||||
|
||||
/// Tail of the spawned child's `stderr.log` to splice into a startup
|
||||
@@ -524,7 +525,7 @@ mod tests {
|
||||
use manifest::{AuthRef, SchemeKind};
|
||||
|
||||
#[test]
|
||||
fn overlay_inherits_inline_spawner_model() {
|
||||
fn spawn_config_inherits_inline_spawner_model() {
|
||||
let model = ModelManifest {
|
||||
scheme: Some(SchemeKind::Anthropic),
|
||||
base_url: Some("https://example.test".into()),
|
||||
@@ -536,8 +537,9 @@ mod tests {
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
let toml_str = build_overlay_toml("child", "$insomnia/default", &[], &model).unwrap();
|
||||
let parsed = PodManifestConfig::from_toml(&toml_str).unwrap();
|
||||
let config_json =
|
||||
build_spawn_config_json("child", "$insomnia/default", &[], &model).unwrap();
|
||||
let parsed: PodManifestConfig = serde_json::from_str(&config_json).unwrap();
|
||||
|
||||
assert_eq!(parsed.model.scheme, Some(SchemeKind::Anthropic));
|
||||
assert_eq!(parsed.model.model_id.as_deref(), Some("claude-sonnet-4"));
|
||||
@@ -553,13 +555,14 @@ mod tests {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn overlay_inherits_ref_spawner_model() {
|
||||
fn spawn_config_inherits_ref_spawner_model() {
|
||||
let model = ModelManifest {
|
||||
ref_: Some("anthropic/claude-sonnet-4-6".into()),
|
||||
..Default::default()
|
||||
};
|
||||
let toml_str = build_overlay_toml("child", "$insomnia/default", &[], &model).unwrap();
|
||||
let parsed = PodManifestConfig::from_toml(&toml_str).unwrap();
|
||||
let config_json =
|
||||
build_spawn_config_json("child", "$insomnia/default", &[], &model).unwrap();
|
||||
let parsed: PodManifestConfig = serde_json::from_str(&config_json).unwrap();
|
||||
assert_eq!(
|
||||
parsed.model.ref_.as_deref(),
|
||||
Some("anthropic/claude-sonnet-4-6")
|
||||
|
||||
Reference in New Issue
Block a user