cli: remove pod command env override
This commit is contained in:
parent
44ff1411a3
commit
c618fa694c
|
|
@ -3,8 +3,6 @@ use std::fmt;
|
|||
use std::io;
|
||||
use std::path::{Path, PathBuf};
|
||||
|
||||
pub const POD_COMMAND_OVERRIDE_ENV: &str = "INSOMNIA_POD_COMMAND";
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
pub struct PodRuntimeCommand {
|
||||
pub program: PathBuf,
|
||||
|
|
@ -19,10 +17,6 @@ impl PodRuntimeCommand {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn executable_only(program: impl Into<PathBuf>) -> Self {
|
||||
Self::new(program, Vec::new())
|
||||
}
|
||||
|
||||
pub fn for_current_exe() -> io::Result<Self> {
|
||||
Ok(Self::for_executable(std::env::current_exe()?))
|
||||
}
|
||||
|
|
@ -33,25 +27,12 @@ impl PodRuntimeCommand {
|
|||
|
||||
/// Resolve the Pod runtime command used for subprocess launches.
|
||||
///
|
||||
/// `INSOMNIA_POD_COMMAND` is intentionally executable-only: its value is
|
||||
/// used as the program path without shell parsing and without the unified
|
||||
/// `pod` prefix arg. That keeps development/test overrides safe while the
|
||||
/// default path is always `current_exe() + ["pod"]`.
|
||||
/// The default launch path is always the current `insomnia` executable plus
|
||||
/// the unified `pod` prefix argument.
|
||||
pub fn resolve() -> io::Result<Self> {
|
||||
if let Some(command) = Self::from_override_env() {
|
||||
return Ok(command);
|
||||
}
|
||||
Self::for_current_exe()
|
||||
}
|
||||
|
||||
pub fn from_override_env() -> Option<Self> {
|
||||
let raw = std::env::var_os(POD_COMMAND_OVERRIDE_ENV)?;
|
||||
if raw.is_empty() {
|
||||
return None;
|
||||
}
|
||||
Some(Self::executable_only(raw))
|
||||
}
|
||||
|
||||
pub fn program(&self) -> &Path {
|
||||
&self.program
|
||||
}
|
||||
|
|
@ -84,28 +65,6 @@ impl fmt::Display for PodRuntimeCommand {
|
|||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use std::sync::Mutex;
|
||||
|
||||
static ENV_LOCK: Mutex<()> = Mutex::new(());
|
||||
|
||||
struct EnvRestore(Option<OsString>);
|
||||
|
||||
impl EnvRestore {
|
||||
fn capture() -> Self {
|
||||
Self(std::env::var_os(POD_COMMAND_OVERRIDE_ENV))
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for EnvRestore {
|
||||
fn drop(&mut self) {
|
||||
unsafe {
|
||||
match &self.0 {
|
||||
Some(value) => std::env::set_var(POD_COMMAND_OVERRIDE_ENV, value),
|
||||
None => std::env::remove_var(POD_COMMAND_OVERRIDE_ENV),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn insomnia_binary_defaults_to_pod_prefix() {
|
||||
|
|
@ -139,18 +98,4 @@ mod tests {
|
|||
.collect::<Vec<_>>()
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn env_override_is_executable_only_and_not_shell_parsed() {
|
||||
let _guard = ENV_LOCK.lock().unwrap();
|
||||
let _restore = EnvRestore::capture();
|
||||
unsafe {
|
||||
std::env::set_var(POD_COMMAND_OVERRIDE_ENV, "/tmp/mock pod --flag");
|
||||
}
|
||||
|
||||
let command = PodRuntimeCommand::resolve().unwrap();
|
||||
|
||||
assert_eq!(command.program(), Path::new("/tmp/mock pod --flag"));
|
||||
assert!(command.prefix_args().is_empty());
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -220,6 +220,9 @@ pub struct SpawnPodTool {
|
|||
/// Directory the spawned Pod should run in when the LLM did not
|
||||
/// override it. Defaults to the spawner's pwd — see module docs.
|
||||
spawner_pwd: PathBuf,
|
||||
/// Optional typed runtime command injected by tests. Production resolves
|
||||
/// the runtime command from `std::env::current_exe()` at launch time.
|
||||
runtime_command: Option<PodRuntimeCommand>,
|
||||
/// Shared registry of spawned children, also used by the
|
||||
/// pod-comm tools (`SendToPod` / `ReadPodOutput` / `StopPod`) and by
|
||||
/// Pod discovery. Writes the list to runtime and durable Pod state on
|
||||
|
|
@ -258,12 +261,14 @@ impl SpawnPodTool {
|
|||
spawner_manifest: PodManifest,
|
||||
available_profiles: AvailableProfiles,
|
||||
spawner_scope: SharedScope,
|
||||
runtime_command: Option<PodRuntimeCommand>,
|
||||
) -> Self {
|
||||
Self {
|
||||
spawner_name,
|
||||
callback_socket,
|
||||
runtime_base,
|
||||
spawner_pwd,
|
||||
runtime_command,
|
||||
registry,
|
||||
parent_socket,
|
||||
spawner_manifest,
|
||||
|
|
@ -409,9 +414,14 @@ impl SpawnPodTool {
|
|||
spawn_config_json: &str,
|
||||
predicted_socket: &Path,
|
||||
) -> Result<(), ToolError> {
|
||||
let runtime_command = PodRuntimeCommand::resolve().map_err(|error| {
|
||||
ToolError::ExecutionFailed(format!("failed to resolve Pod runtime command: {error}"))
|
||||
})?;
|
||||
let runtime_command = match &self.runtime_command {
|
||||
Some(command) => command.clone(),
|
||||
None => PodRuntimeCommand::resolve().map_err(|error| {
|
||||
ToolError::ExecutionFailed(format!(
|
||||
"failed to resolve Pod runtime command: {error}"
|
||||
))
|
||||
})?,
|
||||
};
|
||||
|
||||
// Pre-create the child's runtime dir so we have a stable place to
|
||||
// capture its stderr before it has had a chance to bind anything.
|
||||
|
|
@ -764,6 +774,59 @@ pub fn spawn_pod_tool(
|
|||
spawner_manifest: PodManifest,
|
||||
spawner_scope: SharedScope,
|
||||
prompts: Arc<PromptCatalog>,
|
||||
) -> ToolDefinition {
|
||||
spawn_pod_tool_impl(
|
||||
spawner_name,
|
||||
callback_socket,
|
||||
runtime_base,
|
||||
spawner_pwd,
|
||||
registry,
|
||||
parent_socket,
|
||||
spawner_manifest,
|
||||
spawner_scope,
|
||||
prompts,
|
||||
None,
|
||||
)
|
||||
}
|
||||
|
||||
#[doc(hidden)]
|
||||
pub fn spawn_pod_tool_with_runtime_command(
|
||||
spawner_name: String,
|
||||
callback_socket: PathBuf,
|
||||
runtime_base: PathBuf,
|
||||
spawner_pwd: PathBuf,
|
||||
registry: Arc<SpawnedPodRegistry>,
|
||||
parent_socket: Option<PathBuf>,
|
||||
spawner_manifest: PodManifest,
|
||||
spawner_scope: SharedScope,
|
||||
prompts: Arc<PromptCatalog>,
|
||||
runtime_command: PodRuntimeCommand,
|
||||
) -> ToolDefinition {
|
||||
spawn_pod_tool_impl(
|
||||
spawner_name,
|
||||
callback_socket,
|
||||
runtime_base,
|
||||
spawner_pwd,
|
||||
registry,
|
||||
parent_socket,
|
||||
spawner_manifest,
|
||||
spawner_scope,
|
||||
prompts,
|
||||
Some(runtime_command),
|
||||
)
|
||||
}
|
||||
|
||||
fn spawn_pod_tool_impl(
|
||||
spawner_name: String,
|
||||
callback_socket: PathBuf,
|
||||
runtime_base: PathBuf,
|
||||
spawner_pwd: PathBuf,
|
||||
registry: Arc<SpawnedPodRegistry>,
|
||||
parent_socket: Option<PathBuf>,
|
||||
spawner_manifest: PodManifest,
|
||||
spawner_scope: SharedScope,
|
||||
prompts: Arc<PromptCatalog>,
|
||||
runtime_command: Option<PodRuntimeCommand>,
|
||||
) -> ToolDefinition {
|
||||
Arc::new(move || {
|
||||
let schema = schemars::schema_for!(SpawnPodInput);
|
||||
|
|
@ -794,6 +857,7 @@ pub fn spawn_pod_tool(
|
|||
spawner_manifest.clone(),
|
||||
available_profiles,
|
||||
spawner_scope.clone(),
|
||||
runtime_command.clone(),
|
||||
));
|
||||
(meta, tool)
|
||||
})
|
||||
|
|
|
|||
|
|
@ -1,15 +1,15 @@
|
|||
//! Integration tests for the `SpawnPod` tool.
|
||||
//!
|
||||
//! These tests exercise the tool's pod-registry delegation, subprocess
|
||||
//! launch, socket handoff, and `spawned_pods.json` write without relying
|
||||
//! on the real Pod runtime executable. `INSOMNIA_POD_COMMAND` is pointed at
|
||||
//! `/bin/true` (which exits immediately) while a test-owned Unix
|
||||
//! listener pre-binds the predicted socket path, so the tool sees the
|
||||
//! "child" as live.
|
||||
//! launch, socket handoff, and `spawned_pods.json` write through an injected
|
||||
//! typed runtime command. The mock command exits immediately while a
|
||||
//! test-owned Unix listener pre-binds the predicted socket path, so the tool
|
||||
//! sees the "child" as live.
|
||||
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::sync::{LazyLock, Mutex};
|
||||
|
||||
use insomnia::PodRuntimeCommand;
|
||||
use llm_worker::tool::{ToolError, ToolOutput};
|
||||
use manifest::{
|
||||
AuthRef, ModelManifest, Permission, PodManifest, PodManifestConfig, PodMetaConfig, SchemeKind,
|
||||
|
|
@ -18,7 +18,7 @@ use manifest::{
|
|||
use pod::runtime::dir::{RuntimeDir, SpawnedPodRecord};
|
||||
use pod::runtime::pod_registry::{self, LockFileGuard};
|
||||
use pod::spawn::registry::SpawnedPodRegistry;
|
||||
use pod::spawn::tool::spawn_pod_tool;
|
||||
use pod::spawn::tool::spawn_pod_tool_with_runtime_command;
|
||||
use protocol::stream::{JsonLineReader, JsonLineWriter};
|
||||
use protocol::{Event, Method};
|
||||
use serde_json::json;
|
||||
|
|
@ -26,8 +26,8 @@ use std::sync::Arc;
|
|||
use tempfile::TempDir;
|
||||
use tokio::net::UnixListener;
|
||||
|
||||
/// Serialises tests that mutate `INSOMNIA_RUNTIME_DIR` /
|
||||
/// `INSOMNIA_POD_COMMAND` across the thread-pooled test harness.
|
||||
/// Serialises tests that mutate `INSOMNIA_RUNTIME_DIR` across the
|
||||
/// thread-pooled test harness.
|
||||
static ENV_LOCK: LazyLock<Mutex<()>> = LazyLock::new(|| Mutex::new(()));
|
||||
|
||||
struct EnvGuard {
|
||||
|
|
@ -141,11 +141,8 @@ fn accept_one_method(listener: UnixListener) -> tokio::task::JoinHandle<Option<M
|
|||
})
|
||||
}
|
||||
|
||||
fn point_runtime_command_at_true() {
|
||||
let path = which_true();
|
||||
unsafe {
|
||||
std::env::set_var("INSOMNIA_POD_COMMAND", &path);
|
||||
}
|
||||
fn mock_runtime_command() -> PodRuntimeCommand {
|
||||
PodRuntimeCommand::new(which_true(), Vec::new())
|
||||
}
|
||||
|
||||
/// `/bin/true` only exists on FHS-compliant systems. Resolve it via PATH
|
||||
|
|
@ -213,7 +210,6 @@ fn shared_scope_for(allow_root: &Path) -> SharedScope {
|
|||
fn clear_env() {
|
||||
unsafe {
|
||||
std::env::remove_var("INSOMNIA_RUNTIME_DIR");
|
||||
std::env::remove_var("INSOMNIA_POD_COMMAND");
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -224,14 +220,13 @@ async fn spawn_pod_delegates_scope_and_sends_run() {
|
|||
let allow_root = TempDir::new().unwrap();
|
||||
let (_tmp, runtime_base, spawner_socket, spawner_rd) =
|
||||
setup_spawner("root", allow_root.path()).await;
|
||||
point_runtime_command_at_true();
|
||||
|
||||
let (_predicted_socket, listener) = bind_mock_pod_socket(&runtime_base, "child").await;
|
||||
let received = accept_one_method(listener);
|
||||
|
||||
let registry = SpawnedPodRegistry::new(spawner_rd.clone());
|
||||
let spawner_scope = shared_scope_for(allow_root.path());
|
||||
let def = spawn_pod_tool(
|
||||
let def = spawn_pod_tool_with_runtime_command(
|
||||
"root".into(),
|
||||
spawner_socket.clone(),
|
||||
runtime_base.clone(),
|
||||
|
|
@ -241,6 +236,7 @@ async fn spawn_pod_delegates_scope_and_sends_run() {
|
|||
dummy_manifest(allow_root.path()),
|
||||
spawner_scope.clone(),
|
||||
builtin_prompts(),
|
||||
mock_runtime_command(),
|
||||
);
|
||||
let (_meta, tool) = def();
|
||||
|
||||
|
|
@ -317,11 +313,10 @@ async fn spawn_pod_rejects_scope_outside_spawner() {
|
|||
let outside = TempDir::new().unwrap();
|
||||
let (_tmp, runtime_base, spawner_socket, spawner_rd) =
|
||||
setup_spawner("root", allow_root.path()).await;
|
||||
point_runtime_command_at_true();
|
||||
|
||||
let registry = SpawnedPodRegistry::new(spawner_rd);
|
||||
let spawner_scope = shared_scope_for(allow_root.path());
|
||||
let def = spawn_pod_tool(
|
||||
let def = spawn_pod_tool_with_runtime_command(
|
||||
"root".into(),
|
||||
spawner_socket,
|
||||
runtime_base,
|
||||
|
|
@ -331,6 +326,7 @@ async fn spawn_pod_rejects_scope_outside_spawner() {
|
|||
dummy_manifest(allow_root.path()),
|
||||
spawner_scope.clone(),
|
||||
builtin_prompts(),
|
||||
mock_runtime_command(),
|
||||
);
|
||||
let (_meta, tool) = def();
|
||||
|
||||
|
|
@ -379,7 +375,6 @@ async fn spawn_pod_rolls_back_reservation_when_socket_never_appears() {
|
|||
let allow_root = TempDir::new().unwrap();
|
||||
let (_tmp, runtime_base, spawner_socket, spawner_rd) =
|
||||
setup_spawner("root", allow_root.path()).await;
|
||||
point_runtime_command_at_true();
|
||||
|
||||
// Deliberately do NOT bind a socket at the predicted path. The
|
||||
// tool's wait_for_socket should time out, triggering rollback.
|
||||
|
|
@ -394,7 +389,7 @@ async fn spawn_pod_rolls_back_reservation_when_socket_never_appears() {
|
|||
|
||||
let registry = SpawnedPodRegistry::new(spawner_rd);
|
||||
let spawner_scope = shared_scope_for(allow_root.path());
|
||||
let def = spawn_pod_tool(
|
||||
let def = spawn_pod_tool_with_runtime_command(
|
||||
"root".into(),
|
||||
spawner_socket,
|
||||
runtime_base,
|
||||
|
|
@ -404,6 +399,7 @@ async fn spawn_pod_rolls_back_reservation_when_socket_never_appears() {
|
|||
dummy_manifest(allow_root.path()),
|
||||
spawner_scope.clone(),
|
||||
builtin_prompts(),
|
||||
mock_runtime_command(),
|
||||
);
|
||||
let (_meta, tool) = def();
|
||||
|
||||
|
|
|
|||
|
|
@ -77,7 +77,6 @@ Credential env var は interoperability のために現時点では残ってい
|
|||
|
||||
- `INSOMNIA_USER_MANIFEST` は通常の profile-based Pod/TUI startup の一部ではない。one-file manifest の debug / compatibility path には `insomnia pod --manifest <PATH>` を使う。
|
||||
- ambient `.insomnia/manifest.toml` discovery は通常の fresh startup の一部ではない。
|
||||
- `INSOMNIA_POD_COMMAND` は single-binary 化に伴って削除する。Pod runtime は `insomnia pod ...` の typed command として起動する。
|
||||
- `INSOMNIA_TEST_*` のような test-only 環境変数は supported surface にしない。既存利用も削除する。
|
||||
- `insomnia-pod` は installed command ではない。Pod runtime は `insomnia pod ...` から起動する。
|
||||
- 通常 runtime は `.env` ファイルを load しない。
|
||||
|
|
@ -89,7 +88,7 @@ Credential env var は interoperability のために現時点では残ってい
|
|||
1. test-only env var を削除し、public env behavior を検証する test だけを shared guard / test-support crate に集約する。
|
||||
2. path resolution は `manifest::paths` に集約し、path precedence rule を別の場所で重複実装しない。
|
||||
3. credential source は resolved config 上で明示し、process-env convention を増やすより typed secret reference へ寄せる。encrypted secret store 導入時に credential env var 依存を削除する。
|
||||
4. `INSOMNIA_POD_COMMAND` は削除し、Pod runtime 起動は `current_exe() + ["pod"]` の typed command に一本化する。
|
||||
4. Pod runtime 起動は環境変数ではなく `current_exe() + ["pod"]` の typed command に一本化する。
|
||||
5. fallback env は独立した設定項目として増やさず、対応する main key の解決順として文書化する。
|
||||
6. 空の env value は、変数 category に応じて unset / invalid のどちらとして扱うかを一貫させ、新しい supported variable を追加する場合は挙動を文書化する。
|
||||
7. 外部 process integration が env inheritance / filtering を必要とする場合は、ambient な inherited process state に頼らず、明示的な policy boundary として設計する。
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user