mirror of
https://github.com/openai/codex.git
synced 2026-11-10 21:00:47 +00:00
## Why Approved filesystem access should make executor skills discoverable and readable for the lifetime of the grant. Turn-scoped access must not authorize persistent plugin servers or hooks, or bypass disabled-plugin policy. ## What changed - Apply environment-specific session grants to capability discovery and refresh MCP after session grants change. - Use a separate discovery inventory for skills with turn-scoped grants, keeping plugin servers and hooks on the session inventory. Reuse discovery results across unchanged model steps. - Preserve disabled-plugin checks for unreadable manifests and symlinked manifest files or directories, including ancestor markers, while retaining standalone skill roots. ## Testing Add coverage for V1 and V2 skill discovery before approval, after approval, and on the next turn; disabled-plugin ownership checks; discovery cache reuse; and session hooks surviving turn-scoped discovery failure. GitOrigin-RevId: 929a7f8cb01f4bcfb4bee2c50ae02cd762066b2d
109 lines
3.6 KiB
Rust
109 lines
3.6 KiB
Rust
//! Shared native exec-server process fixture; dropping it stops the child process.
|
|
|
|
use anyhow::Context;
|
|
use anyhow::Result;
|
|
use anyhow::bail;
|
|
use std::process::Stdio;
|
|
use std::time::Duration;
|
|
use tempfile::TempDir;
|
|
use tokio::io::AsyncBufReadExt;
|
|
use tokio::io::BufReader;
|
|
use tokio::process::Child;
|
|
use tokio::process::ChildStdout;
|
|
use tokio::process::Command;
|
|
use tokio::time::Instant;
|
|
use tokio::time::timeout;
|
|
|
|
const EXEC_SERVER_START_TIMEOUT: Duration = Duration::from_secs(30);
|
|
|
|
pub struct ExecServerProcess {
|
|
_codex_home: TempDir,
|
|
child: Child,
|
|
_stdout: BufReader<ChildStdout>,
|
|
pub websocket_url: String,
|
|
}
|
|
|
|
impl ExecServerProcess {
|
|
pub async fn start() -> Result<Self> {
|
|
Self::start_with_env(&[]).await
|
|
}
|
|
|
|
pub async fn start_with_env(env: &[(&str, &str)]) -> Result<Self> {
|
|
let codex_home = TempDir::new()?;
|
|
let mut command = Command::new(codex_utils_cargo_bin::cargo_bin("codex")?);
|
|
command.envs(env.iter().copied());
|
|
Self::start_command(command, codex_home).await
|
|
}
|
|
|
|
pub async fn start_with_version(version: &str) -> Result<Self> {
|
|
Self::start_with_version_and_home(version, TempDir::new()?).await
|
|
}
|
|
|
|
pub async fn start_with_version_and_home(version: &str, codex_home: TempDir) -> Result<Self> {
|
|
let package = codex_home.path().join("package");
|
|
let bin = package.join("bin");
|
|
std::fs::create_dir_all(&bin)?;
|
|
let executable = bin.join(format!("codex{}", std::env::consts::EXE_SUFFIX));
|
|
codex_utils_cargo_bin::copy_executable(
|
|
&codex_utils_cargo_bin::cargo_bin("codex")?,
|
|
&executable,
|
|
)?;
|
|
std::fs::write(
|
|
package.join("codex-package.json"),
|
|
serde_json::to_vec(&serde_json::json!({"version": version}))?,
|
|
)?;
|
|
let mut command = Command::new(executable);
|
|
// V2 must not discover skills installed in the developer's home directory.
|
|
command
|
|
.env("HOME", codex_home.path())
|
|
.env("USERPROFILE", codex_home.path());
|
|
Self::start_command(command, codex_home).await
|
|
}
|
|
|
|
async fn start_command(mut command: Command, codex_home: TempDir) -> Result<Self> {
|
|
let mut child = command
|
|
.args(["exec-server", "--listen", "ws://127.0.0.1:0"])
|
|
.env("CODEX_HOME", codex_home.path())
|
|
.stdin(Stdio::null())
|
|
.stdout(Stdio::piped())
|
|
.stderr(Stdio::inherit())
|
|
.kill_on_drop(true)
|
|
.spawn()?;
|
|
let stdout = child
|
|
.stdout
|
|
.take()
|
|
.context("exec-server stdout should be piped")?;
|
|
let mut stdout = BufReader::new(stdout);
|
|
let deadline = Instant::now() + EXEC_SERVER_START_TIMEOUT;
|
|
let websocket_url = loop {
|
|
let remaining = deadline
|
|
.checked_duration_since(Instant::now())
|
|
.context("timed out waiting for exec-server listen URL")?;
|
|
let mut line = String::new();
|
|
let bytes_read = timeout(remaining, stdout.read_line(&mut line))
|
|
.await
|
|
.context("timed out reading exec-server listen URL")??;
|
|
if bytes_read == 0 {
|
|
bail!("exec-server exited before printing its listen URL");
|
|
}
|
|
let line = line.trim();
|
|
if line.starts_with("ws://") {
|
|
break line.to_string();
|
|
}
|
|
};
|
|
|
|
Ok(Self {
|
|
_codex_home: codex_home,
|
|
child,
|
|
_stdout: stdout,
|
|
websocket_url,
|
|
})
|
|
}
|
|
}
|
|
|
|
impl Drop for ExecServerProcess {
|
|
fn drop(&mut self) {
|
|
let _ = self.child.start_kill();
|
|
}
|
|
}
|